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