sysfs.c 14 KB

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