sysfs.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. /*
  2. * sysfs.c sysfs ABI access functions for TMON program
  3. *
  4. * Copyright (C) 2013 Intel Corporation. All rights reserved.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License version
  8. * 2 or later as published by the Free Software Foundation.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
  16. *
  17. */
  18. #include <unistd.h>
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <stdint.h>
  23. #include <dirent.h>
  24. #include <libintl.h>
  25. #include <ctype.h>
  26. #include <time.h>
  27. #include <syslog.h>
  28. #include <sys/time.h>
  29. #include <errno.h>
  30. #include "tmon.h"
  31. struct tmon_platform_data ptdata;
  32. const char *trip_type_name[] = {
  33. "critical",
  34. "hot",
  35. "passive",
  36. "active",
  37. };
  38. int sysfs_set_ulong(char *path, char *filename, unsigned long val)
  39. {
  40. FILE *fd;
  41. int ret = -1;
  42. char filepath[256];
  43. snprintf(filepath, 256, "%s/%s", path, filename);
  44. fd = fopen(filepath, "w");
  45. if (!fd) {
  46. syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath);
  47. return ret;
  48. }
  49. ret = fprintf(fd, "%lu", val);
  50. fclose(fd);
  51. return 0;
  52. }
  53. /* history of thermal data, used for control algo */
  54. #define NR_THERMAL_RECORDS 3
  55. struct thermal_data_record trec[NR_THERMAL_RECORDS];
  56. int cur_thermal_record; /* index to the trec array */
  57. static int sysfs_get_ulong(char *path, char *filename, unsigned long *p_ulong)
  58. {
  59. FILE *fd;
  60. int ret = -1;
  61. char filepath[256];
  62. snprintf(filepath, 256, "%s/%s", path, filename);
  63. fd = fopen(filepath, "r");
  64. if (!fd) {
  65. syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath);
  66. return ret;
  67. }
  68. ret = fscanf(fd, "%lu", p_ulong);
  69. fclose(fd);
  70. return 0;
  71. }
  72. static int sysfs_get_string(char *path, char *filename, char *str)
  73. {
  74. FILE *fd;
  75. int ret = -1;
  76. char filepath[256];
  77. snprintf(filepath, 256, "%s/%s", path, filename);
  78. fd = fopen(filepath, "r");
  79. if (!fd) {
  80. syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath);
  81. return ret;
  82. }
  83. ret = fscanf(fd, "%256s", str);
  84. fclose(fd);
  85. return ret;
  86. }
  87. /* get states of the cooling device instance */
  88. static int probe_cdev(struct cdev_info *cdi, char *path)
  89. {
  90. sysfs_get_string(path, "type", cdi->type);
  91. sysfs_get_ulong(path, "max_state", &cdi->max_state);
  92. sysfs_get_ulong(path, "cur_state", &cdi->cur_state);
  93. syslog(LOG_INFO, "%s: %s: type %s, max %lu, curr %lu inst %d\n",
  94. __func__, path,
  95. cdi->type, cdi->max_state, cdi->cur_state, cdi->instance);
  96. return 0;
  97. }
  98. static int str_to_trip_type(char *name)
  99. {
  100. int i;
  101. for (i = 0; i < NR_THERMAL_TRIP_TYPE; i++) {
  102. if (!strcmp(name, trip_type_name[i]))
  103. return i;
  104. }
  105. return -ENOENT;
  106. }
  107. /* scan and fill in trip point info for a thermal zone and trip point id */
  108. static int get_trip_point_data(char *tz_path, int tzid, int tpid)
  109. {
  110. char filename[256];
  111. char temp_str[256];
  112. int trip_type;
  113. if (tpid >= MAX_NR_TRIP)
  114. return -EINVAL;
  115. /* check trip point type */
  116. snprintf(filename, sizeof(filename), "trip_point_%d_type", tpid);
  117. sysfs_get_string(tz_path, filename, temp_str);
  118. trip_type = str_to_trip_type(temp_str);
  119. if (trip_type < 0) {
  120. syslog(LOG_ERR, "%s:%s no matching type\n", __func__, temp_str);
  121. return -ENOENT;
  122. }
  123. ptdata.tzi[tzid].tp[tpid].type = trip_type;
  124. syslog(LOG_INFO, "%s:tz:%d tp:%d:type:%s type id %d\n", __func__, tzid,
  125. tpid, temp_str, trip_type);
  126. /* TODO: check attribute */
  127. return 0;
  128. }
  129. /* return instance id for file format such as trip_point_4_temp */
  130. static int get_instance_id(char *name, int pos, int skip)
  131. {
  132. char *ch;
  133. int i = 0;
  134. ch = strtok(name, "_");
  135. while (ch != NULL) {
  136. ++i;
  137. syslog(LOG_INFO, "%s:%s:%s:%d", __func__, name, ch, i);
  138. ch = strtok(NULL, "_");
  139. if (pos == i)
  140. return atol(ch + skip);
  141. }
  142. return -1;
  143. }
  144. /* Find trip point info of a thermal zone */
  145. static int find_tzone_tp(char *tz_name, char *d_name, struct tz_info *tzi,
  146. int tz_id)
  147. {
  148. int tp_id;
  149. unsigned long temp_ulong;
  150. if (strstr(d_name, "trip_point") &&
  151. strstr(d_name, "temp")) {
  152. /* check if trip point temp is non-zero
  153. * ignore 0/invalid trip points
  154. */
  155. sysfs_get_ulong(tz_name, d_name, &temp_ulong);
  156. if (temp_ulong < MAX_TEMP_KC) {
  157. tzi->nr_trip_pts++;
  158. /* found a valid trip point */
  159. tp_id = get_instance_id(d_name, 2, 0);
  160. syslog(LOG_DEBUG, "tzone %s trip %d temp %lu tpnode %s",
  161. tz_name, tp_id, temp_ulong, d_name);
  162. if (tp_id < 0 || tp_id >= MAX_NR_TRIP) {
  163. syslog(LOG_ERR, "Failed to find TP inst %s\n",
  164. d_name);
  165. return -1;
  166. }
  167. get_trip_point_data(tz_name, tz_id, tp_id);
  168. tzi->tp[tp_id].temp = temp_ulong;
  169. }
  170. }
  171. return 0;
  172. }
  173. /* check cooling devices for binding info. */
  174. static int find_tzone_cdev(struct dirent *nl, char *tz_name,
  175. struct tz_info *tzi, int tz_id, int cid)
  176. {
  177. unsigned long trip_instance = 0;
  178. char cdev_name_linked[256];
  179. char cdev_name[256];
  180. char cdev_trip_name[256];
  181. int cdev_id;
  182. if (nl->d_type == DT_LNK) {
  183. syslog(LOG_DEBUG, "TZ%d: cdev: %s cid %d\n", tz_id, nl->d_name,
  184. cid);
  185. tzi->nr_cdev++;
  186. if (tzi->nr_cdev > ptdata.nr_cooling_dev) {
  187. syslog(LOG_ERR, "Err: Too many cdev? %d\n",
  188. tzi->nr_cdev);
  189. return -EINVAL;
  190. }
  191. /* find the link to real cooling device record binding */
  192. snprintf(cdev_name, 256, "%s/%s", tz_name, nl->d_name);
  193. memset(cdev_name_linked, 0, sizeof(cdev_name_linked));
  194. if (readlink(cdev_name, cdev_name_linked,
  195. sizeof(cdev_name_linked) - 1) != -1) {
  196. cdev_id = get_instance_id(cdev_name_linked, 1,
  197. sizeof("device") - 1);
  198. syslog(LOG_DEBUG, "cdev %s linked to %s : %d\n",
  199. cdev_name, cdev_name_linked, cdev_id);
  200. tzi->cdev_binding |= (1 << cdev_id);
  201. /* find the trip point in which the cdev is binded to
  202. * in this tzone
  203. */
  204. snprintf(cdev_trip_name, 256, "%s%s", nl->d_name,
  205. "_trip_point");
  206. sysfs_get_ulong(tz_name, cdev_trip_name,
  207. &trip_instance);
  208. /* validate trip point range, e.g. trip could return -1
  209. * when passive is enabled
  210. */
  211. if (trip_instance > MAX_NR_TRIP)
  212. trip_instance = 0;
  213. tzi->trip_binding[cdev_id] |= 1 << trip_instance;
  214. syslog(LOG_DEBUG, "cdev %s -> trip:%lu: 0x%lx %d\n",
  215. cdev_name, trip_instance,
  216. tzi->trip_binding[cdev_id],
  217. cdev_id);
  218. }
  219. return 0;
  220. }
  221. return -ENODEV;
  222. }
  223. /*****************************************************************************
  224. * Before calling scan_tzones, thermal sysfs must be probed to determine
  225. * the number of thermal zones and cooling devices.
  226. * We loop through each thermal zone and fill in tz_info struct, i.e.
  227. * ptdata.tzi[]
  228. root@jacob-chiefriver:~# tree -d /sys/class/thermal/thermal_zone0
  229. /sys/class/thermal/thermal_zone0
  230. |-- cdev0 -> ../cooling_device4
  231. |-- cdev1 -> ../cooling_device3
  232. |-- cdev10 -> ../cooling_device7
  233. |-- cdev11 -> ../cooling_device6
  234. |-- cdev12 -> ../cooling_device5
  235. |-- cdev2 -> ../cooling_device2
  236. |-- cdev3 -> ../cooling_device1
  237. |-- cdev4 -> ../cooling_device0
  238. |-- cdev5 -> ../cooling_device12
  239. |-- cdev6 -> ../cooling_device11
  240. |-- cdev7 -> ../cooling_device10
  241. |-- cdev8 -> ../cooling_device9
  242. |-- cdev9 -> ../cooling_device8
  243. |-- device -> ../../../LNXSYSTM:00/device:62/LNXTHERM:00
  244. |-- power
  245. `-- subsystem -> ../../../../class/thermal
  246. *****************************************************************************/
  247. static int scan_tzones(void)
  248. {
  249. DIR *dir;
  250. struct dirent **namelist;
  251. char tz_name[256];
  252. int i, j, n, k = 0;
  253. if (!ptdata.nr_tz_sensor)
  254. return -1;
  255. for (i = 0; i <= ptdata.max_tz_instance; i++) {
  256. memset(tz_name, 0, sizeof(tz_name));
  257. snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE, i);
  258. dir = opendir(tz_name);
  259. if (!dir) {
  260. syslog(LOG_INFO, "Thermal zone %s skipped\n", tz_name);
  261. continue;
  262. }
  263. /* keep track of valid tzones */
  264. n = scandir(tz_name, &namelist, 0, alphasort);
  265. if (n < 0)
  266. syslog(LOG_ERR, "scandir failed in %s", tz_name);
  267. else {
  268. sysfs_get_string(tz_name, "type", ptdata.tzi[k].type);
  269. ptdata.tzi[k].instance = i;
  270. /* detect trip points and cdev attached to this tzone */
  271. j = 0; /* index for cdev */
  272. ptdata.tzi[k].nr_cdev = 0;
  273. ptdata.tzi[k].nr_trip_pts = 0;
  274. while (n--) {
  275. char *temp_str;
  276. if (find_tzone_tp(tz_name, namelist[n]->d_name,
  277. &ptdata.tzi[k], k))
  278. break;
  279. temp_str = strstr(namelist[n]->d_name, "cdev");
  280. if (!temp_str) {
  281. free(namelist[n]);
  282. continue;
  283. }
  284. if (!find_tzone_cdev(namelist[n], tz_name,
  285. &ptdata.tzi[k], i, j))
  286. j++; /* increment cdev index */
  287. free(namelist[n]);
  288. }
  289. free(namelist);
  290. }
  291. /*TODO: reverse trip points */
  292. closedir(dir);
  293. syslog(LOG_INFO, "TZ %d has %d cdev\n", i,
  294. ptdata.tzi[k].nr_cdev);
  295. k++;
  296. }
  297. return 0;
  298. }
  299. static int scan_cdevs(void)
  300. {
  301. DIR *dir;
  302. struct dirent **namelist;
  303. char cdev_name[256];
  304. int i, n, k = 0;
  305. if (!ptdata.nr_cooling_dev) {
  306. fprintf(stderr, "No cooling devices found\n");
  307. return 0;
  308. }
  309. for (i = 0; i <= ptdata.max_cdev_instance; i++) {
  310. memset(cdev_name, 0, sizeof(cdev_name));
  311. snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV, i);
  312. dir = opendir(cdev_name);
  313. if (!dir) {
  314. syslog(LOG_INFO, "Cooling dev %s skipped\n", cdev_name);
  315. /* there is a gap in cooling device id, check again
  316. * for the same index.
  317. */
  318. continue;
  319. }
  320. n = scandir(cdev_name, &namelist, 0, alphasort);
  321. if (n < 0)
  322. syslog(LOG_ERR, "scandir failed in %s", cdev_name);
  323. else {
  324. sysfs_get_string(cdev_name, "type", ptdata.cdi[k].type);
  325. ptdata.cdi[k].instance = i;
  326. if (strstr(ptdata.cdi[k].type, ctrl_cdev)) {
  327. ptdata.cdi[k].flag |= CDEV_FLAG_IN_CONTROL;
  328. syslog(LOG_DEBUG, "control cdev id %d\n", i);
  329. }
  330. while (n--)
  331. free(namelist[n]);
  332. free(namelist);
  333. }
  334. closedir(dir);
  335. k++;
  336. }
  337. return 0;
  338. }
  339. int probe_thermal_sysfs(void)
  340. {
  341. DIR *dir;
  342. struct dirent **namelist;
  343. int n;
  344. dir = opendir(THERMAL_SYSFS);
  345. if (!dir) {
  346. fprintf(stderr, "\nNo thermal sysfs, exit\n");
  347. return -1;
  348. }
  349. n = scandir(THERMAL_SYSFS, &namelist, 0, alphasort);
  350. if (n < 0)
  351. syslog(LOG_ERR, "scandir failed in thermal sysfs");
  352. else {
  353. /* detect number of thermal zones and cooling devices */
  354. while (n--) {
  355. int inst;
  356. if (strstr(namelist[n]->d_name, CDEV)) {
  357. inst = get_instance_id(namelist[n]->d_name, 1,
  358. sizeof("device") - 1);
  359. /* keep track of the max cooling device since
  360. * there may be gaps.
  361. */
  362. if (inst > ptdata.max_cdev_instance)
  363. ptdata.max_cdev_instance = inst;
  364. syslog(LOG_DEBUG, "found cdev: %s %d %d\n",
  365. namelist[n]->d_name,
  366. ptdata.nr_cooling_dev,
  367. ptdata.max_cdev_instance);
  368. ptdata.nr_cooling_dev++;
  369. } else if (strstr(namelist[n]->d_name, TZONE)) {
  370. inst = get_instance_id(namelist[n]->d_name, 1,
  371. sizeof("zone") - 1);
  372. if (inst > ptdata.max_tz_instance)
  373. ptdata.max_tz_instance = inst;
  374. syslog(LOG_DEBUG, "found tzone: %s %d %d\n",
  375. namelist[n]->d_name,
  376. ptdata.nr_tz_sensor,
  377. ptdata.max_tz_instance);
  378. ptdata.nr_tz_sensor++;
  379. }
  380. free(namelist[n]);
  381. }
  382. free(namelist);
  383. }
  384. syslog(LOG_INFO, "found %d tzone(s), %d cdev(s), target zone %d\n",
  385. ptdata.nr_tz_sensor, ptdata.nr_cooling_dev,
  386. target_thermal_zone);
  387. closedir(dir);
  388. if (!ptdata.nr_tz_sensor) {
  389. fprintf(stderr, "\nNo thermal zones found, exit\n\n");
  390. return -1;
  391. }
  392. ptdata.tzi = calloc(ptdata.max_tz_instance+1, sizeof(struct tz_info));
  393. if (!ptdata.tzi) {
  394. fprintf(stderr, "Err: allocate tz_info\n");
  395. return -1;
  396. }
  397. /* we still show thermal zone information if there is no cdev */
  398. if (ptdata.nr_cooling_dev) {
  399. ptdata.cdi = calloc(ptdata.max_cdev_instance + 1,
  400. sizeof(struct cdev_info));
  401. if (!ptdata.cdi) {
  402. free(ptdata.tzi);
  403. fprintf(stderr, "Err: allocate cdev_info\n");
  404. return -1;
  405. }
  406. }
  407. /* now probe tzones */
  408. if (scan_tzones())
  409. return -1;
  410. if (scan_cdevs())
  411. return -1;
  412. return 0;
  413. }
  414. /* convert sysfs zone instance to zone array index */
  415. int zone_instance_to_index(int zone_inst)
  416. {
  417. int i;
  418. for (i = 0; i < ptdata.nr_tz_sensor; i++)
  419. if (ptdata.tzi[i].instance == zone_inst)
  420. return i;
  421. return -ENOENT;
  422. }
  423. /* read temperature of all thermal zones */
  424. int update_thermal_data()
  425. {
  426. int i;
  427. int next_thermal_record = cur_thermal_record + 1;
  428. char tz_name[256];
  429. static unsigned long samples;
  430. if (!ptdata.nr_tz_sensor) {
  431. syslog(LOG_ERR, "No thermal zones found!\n");
  432. return -1;
  433. }
  434. /* circular buffer for keeping historic data */
  435. if (next_thermal_record >= NR_THERMAL_RECORDS)
  436. next_thermal_record = 0;
  437. gettimeofday(&trec[next_thermal_record].tv, NULL);
  438. if (tmon_log) {
  439. fprintf(tmon_log, "%lu ", ++samples);
  440. fprintf(tmon_log, "%3.1f ", p_param.t_target);
  441. }
  442. for (i = 0; i < ptdata.nr_tz_sensor; i++) {
  443. memset(tz_name, 0, sizeof(tz_name));
  444. snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE,
  445. ptdata.tzi[i].instance);
  446. sysfs_get_ulong(tz_name, "temp",
  447. &trec[next_thermal_record].temp[i]);
  448. if (tmon_log)
  449. fprintf(tmon_log, "%lu ",
  450. trec[next_thermal_record].temp[i] / 1000);
  451. }
  452. cur_thermal_record = next_thermal_record;
  453. for (i = 0; i < ptdata.nr_cooling_dev; i++) {
  454. char cdev_name[256];
  455. unsigned long val;
  456. snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV,
  457. ptdata.cdi[i].instance);
  458. probe_cdev(&ptdata.cdi[i], cdev_name);
  459. val = ptdata.cdi[i].cur_state;
  460. if (val > 1000000)
  461. val = 0;
  462. if (tmon_log)
  463. fprintf(tmon_log, "%lu ", val);
  464. }
  465. if (tmon_log) {
  466. fprintf(tmon_log, "\n");
  467. fflush(tmon_log);
  468. }
  469. return 0;
  470. }
  471. void set_ctrl_state(unsigned long state)
  472. {
  473. char ctrl_cdev_path[256];
  474. int i;
  475. unsigned long cdev_state;
  476. if (no_control)
  477. return;
  478. /* set all ctrl cdev to the same state */
  479. for (i = 0; i < ptdata.nr_cooling_dev; i++) {
  480. if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) {
  481. if (ptdata.cdi[i].max_state < 10) {
  482. strcpy(ctrl_cdev, "None.");
  483. return;
  484. }
  485. /* scale to percentage of max_state */
  486. cdev_state = state * ptdata.cdi[i].max_state/100;
  487. syslog(LOG_DEBUG,
  488. "ctrl cdev %d set state %lu scaled to %lu\n",
  489. ptdata.cdi[i].instance, state, cdev_state);
  490. snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS,
  491. CDEV, ptdata.cdi[i].instance);
  492. syslog(LOG_DEBUG, "ctrl cdev path %s", ctrl_cdev_path);
  493. sysfs_set_ulong(ctrl_cdev_path, "cur_state",
  494. cdev_state);
  495. }
  496. }
  497. }
  498. void get_ctrl_state(unsigned long *state)
  499. {
  500. char ctrl_cdev_path[256];
  501. int ctrl_cdev_id = -1;
  502. int i;
  503. /* TODO: take average of all ctrl types. also consider change based on
  504. * uevent. Take the first reading for now.
  505. */
  506. for (i = 0; i < ptdata.nr_cooling_dev; i++) {
  507. if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) {
  508. ctrl_cdev_id = ptdata.cdi[i].instance;
  509. syslog(LOG_INFO, "ctrl cdev %d get state\n",
  510. ptdata.cdi[i].instance);
  511. break;
  512. }
  513. }
  514. if (ctrl_cdev_id == -1) {
  515. *state = 0;
  516. return;
  517. }
  518. snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS,
  519. CDEV, ctrl_cdev_id);
  520. sysfs_get_ulong(ctrl_cdev_path, "cur_state", state);
  521. }
  522. void free_thermal_data(void)
  523. {
  524. free(ptdata.tzi);
  525. free(ptdata.cdi);
  526. }