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