592 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * sysfs.c sysfs ABI access functions for TMON program4 *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 25struct tmon_platform_data ptdata;26const char *trip_type_name[] = {27 "critical",28 "hot",29 "passive",30 "active",31};32 33int 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 354struct thermal_data_record trec[NR_THERMAL_RECORDS];55int cur_thermal_record; /* index to the trec array */56 57static 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 76static 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 */96static int probe_cdev(struct cdev_info *cdi, char *path)97{98 sysfs_get_string(path, "type", cdi->type);99 sysfs_get_ulong(path, "max_state", &cdi->max_state);100 sysfs_get_ulong(path, "cur_state", &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 109static 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 */122static 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(tz_path, filename, temp_str);133 trip_type = str_to_trip_type(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 */148static 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 */166static 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-zero175 * ignore 0/invalid trip points176 */177 sysfs_get_ulong(tz_name, d_name, &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(d_name, 2, 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_name, tz_id, 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. */198static 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(cdev_name_linked, 1,223 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 to229 * in this tzone230 */231 snprintf(cdev_trip_name, sizeof(cdev_trip_name) - 1,232 "%s%s", nl->d_name, "_trip_point");233 sysfs_get_ulong(tz_name, cdev_trip_name,234 &trip_instance);235 /* validate trip point range, e.g. trip could return -1236 * when passive is enabled237 */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 determine258 * 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[]261root@jacob-chiefriver:~# tree -d /sys/class/thermal/thermal_zone0262/sys/class/thermal/thermal_zone0263|-- cdev0 -> ../cooling_device4264|-- cdev1 -> ../cooling_device3265|-- cdev10 -> ../cooling_device7266|-- cdev11 -> ../cooling_device6267|-- cdev12 -> ../cooling_device5268|-- cdev2 -> ../cooling_device2269|-- cdev3 -> ../cooling_device1270|-- cdev4 -> ../cooling_device0271|-- cdev5 -> ../cooling_device12272|-- cdev6 -> ../cooling_device11273|-- cdev7 -> ../cooling_device10274|-- cdev8 -> ../cooling_device9275|-- cdev9 -> ../cooling_device8276|-- device -> ../../../LNXSYSTM:00/device:62/LNXTHERM:00277|-- power278`-- subsystem -> ../../../../class/thermal279*****************************************************************************/280static 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(tz_name, "type", 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, namelist[n]->d_name,314 &ptdata.tzi[k], 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(namelist[n], tz_name,322 &ptdata.tzi[k], i, 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 338static 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 again357 * 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(cdev_name, "type", 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 383int 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(namelist[n]->d_name, 1,404 sizeof("device") - 1);405 /* keep track of the max cooling device since406 * 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(namelist[n]->d_name, 1,418 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 */468int 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 */479int 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(tz_name, "temp",504 &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(&ptdata.cdi[i], 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 532void 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(ctrl_cdev_path, "cur_state",556 cdev_state);557 }558 }559}560 561void 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 on568 * 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(ctrl_cdev_path, "cur_state", state);585}586 587void free_thermal_data(void)588{589 free(ptdata.tzi);590 free(ptdata.cdi);591}592