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