From 3b5dd192b5f4de1bb1d40e1da2e867c91e6ef975 Mon Sep 17 00:00:00 2001 From: Sukru Senli Date: Tue, 2 Dec 2025 08:16:19 +0000 Subject: [PATCH] Update TR-181 temperature monitoring to use sysfs --- src/deviceinfo.c | 2 +- src/temperature.c | 506 ++++++++++++++++++++++++-- test/files/etc/sysmngr/temperature.sh | 56 --- 3 files changed, 484 insertions(+), 80 deletions(-) delete mode 100644 test/files/etc/sysmngr/temperature.sh diff --git a/src/deviceinfo.c b/src/deviceinfo.c index 212e8d2..ef7b482 100644 --- a/src/deviceinfo.c +++ b/src/deviceinfo.c @@ -296,7 +296,7 @@ DMOBJ tDeviceInfoObj[] = { #endif #ifdef SYSMNGR_TEMPERATURE_STATUS -{"TemperatureStatus", &DMREAD, NULL, NULL, "file:/etc/sysmngr/temperature.sh", NULL, NULL, NULL, tDeviceInfoTemperatureStatusObj, tDeviceInfoTemperatureStatusParams, NULL, BBFDM_BOTH}, +{"TemperatureStatus", &DMREAD, NULL, NULL, NULL, NULL, NULL, NULL, tDeviceInfoTemperatureStatusObj, tDeviceInfoTemperatureStatusParams, NULL, BBFDM_BOTH}, #endif #ifdef SYSMNGR_VENDOR_EXTENSIONS diff --git a/src/temperature.c b/src/temperature.c index 24cc977..cdd12df 100644 --- a/src/temperature.c +++ b/src/temperature.c @@ -10,53 +10,419 @@ */ #include "libbbfdm-api/dmcommon.h" +#include +#include +#include +#include -#define TEMPERATURE_SCRIPT "/etc/sysmngr/temperature.sh" -#define TEMPERATURE_STATUS_CMD TEMPERATURE_SCRIPT " status" +#define HWMON_PATH "/sys/class/hwmon" +#define THERMAL_PATH "/sys/class/thermal" -static void get_temperature_status(json_object **temp_status) +static int read_sysfs_int(const char *path, int *value) { - char res[1024] = {0}; + char buf[64] = {0}; + char *endptr; + long val; - if (temp_status == NULL) - return; + if (dm_read_sysfs_file(path, buf, sizeof(buf)) < 0) + return -1; - *temp_status = NULL; - if (run_cmd(TEMPERATURE_STATUS_CMD, res, sizeof(res)) != 0) - return; + errno = 0; + val = strtol(buf, &endptr, 10); + if (errno != 0 || endptr == buf || val > INT_MAX || val < INT_MIN) + return -1; - if (DM_STRLEN(res) != 0) { - remove_new_line(res); - *temp_status = json_tokener_parse(res); + *value = (int)val; + return 0; +} + +static int read_sysfs_string(const char *path, char *buf, size_t buf_size) +{ + if (dm_read_sysfs_file(path, buf, buf_size) < 0) + return -1; + + /* Remove trailing newline */ + size_t len = strlen(buf); + if (len > 0 && buf[len - 1] == '\n') + buf[len - 1] = '\0'; + + return 0; +} + +static int write_sysfs_int(const char *path, int value) +{ + FILE *f; + int ret; + + // cppcheck-suppress cert-MSC24-C + f = fopen(path, "w"); + if (!f) + return -1; + + ret = fprintf(f, "%d\n", value); + fclose(f); + + return (ret > 0) ? 0 : -1; +} + +static void derive_alarm_path(const char *temp_input_path, const char *alarm_type, char *alarm_path, size_t alarm_path_size) +{ + char *last_underscore; + char base_path[512]; + + /* Copy the input path */ + snprintf(base_path, sizeof(base_path), "%s", temp_input_path); + + /* Find last underscore (before "input") and replace with alarm type */ + last_underscore = strrchr(base_path, '_'); + if (last_underscore) { + *last_underscore = '\0'; + snprintf(alarm_path, alarm_path_size, "%s_%s", base_path, alarm_type); + } else { + alarm_path[0] = '\0'; } } +static int write_alarm_value(const char *sysfs_path, int trip_point_num, const char *hwmon_alarm_type, int value_millidegrees) +{ + char alarm_path[512]; + + /* WiFi sensors don't have sysfs paths */ + if (!sysfs_path || sysfs_path[0] == '\0') + return -1; + + /* For thermal zones: try trip_point_X_temp */ + if (strstr(sysfs_path, "thermal_zone")) { + char *last_slash = strrchr(sysfs_path, '/'); + if (last_slash) { + char zone_dir[480]; /* Leave room for suffix in alarm_path */ + size_t dir_len = last_slash - sysfs_path; + if (dir_len >= sizeof(zone_dir)) + dir_len = sizeof(zone_dir) - 1; + snprintf(zone_dir, dir_len + 1, "%s", sysfs_path); + snprintf(alarm_path, sizeof(alarm_path), "%s/trip_point_%d_temp", zone_dir, trip_point_num); + if (write_sysfs_int(alarm_path, value_millidegrees) == 0) + return 0; + } + } + + /* For hwmon: try temp*_ */ + derive_alarm_path(sysfs_path, hwmon_alarm_type, alarm_path, sizeof(alarm_path)); + if (write_sysfs_int(alarm_path, value_millidegrees) == 0) + return 0; + + return -1; +} + +static int read_alarm_value(const char *sysfs_path, int trip_point_num, const char *hwmon_alarm_type, int *value_millidegrees) +{ + char alarm_path[512]; + + /* WiFi sensors don't have sysfs paths */ + if (!sysfs_path || sysfs_path[0] == '\0') + return -1; + + /* For thermal zones: try trip_point_X_temp */ + if (strstr(sysfs_path, "thermal_zone")) { + char *last_slash = strrchr(sysfs_path, '/'); + if (last_slash) { + char zone_dir[480]; /* Leave room for suffix in alarm_path */ + size_t dir_len = last_slash - sysfs_path; + if (dir_len >= sizeof(zone_dir)) + dir_len = sizeof(zone_dir) - 1; + snprintf(zone_dir, dir_len + 1, "%s", sysfs_path); + snprintf(alarm_path, sizeof(alarm_path), "%s/trip_point_%d_temp", zone_dir, trip_point_num); + if (read_sysfs_int(alarm_path, value_millidegrees) == 0) + return 0; + } + } + + /* For hwmon: try temp*_ */ + derive_alarm_path(sysfs_path, hwmon_alarm_type, alarm_path, sizeof(alarm_path)); + if (read_sysfs_int(alarm_path, value_millidegrees) == 0) + return 0; + + return -1; +} + +static void scan_hwmon_sensors(json_object *sensors_array) +{ + DIR *dir; + struct dirent *entry; + + dir = opendir(HWMON_PATH); + if (!dir) + return; + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_name[0] == '.') + continue; + + char hwmon_dir[280]; /* HWMON_PATH (17) + "/" + d_name (255) + null */ + char real_path[512]; + snprintf(hwmon_dir, sizeof(hwmon_dir), "%s/%s", HWMON_PATH, entry->d_name); + + /* Resolve symlink to check if this is a thermal zone hwmon interface */ + if (realpath(hwmon_dir, real_path) != NULL) { + /* Skip if this hwmon device belongs to a thermal zone */ + /* Thermal zones create hwmon interfaces under /sys/devices/virtual/thermal/thermal_zoneX/hwmonY */ + if (strstr(real_path, "/thermal/thermal_zone") != NULL) + continue; + } + + /* Scan for tempX_input files */ + for (int i = 1; i <= 10; i++) { + char temp_path[512]; + char label_path[512]; + char name_path[512]; + char name[280] = {0}; /* d_name (255) + "_temp" + digit + null */ + int temp_value = 0; + + snprintf(temp_path, sizeof(temp_path), "%s/temp%d_input", hwmon_dir, i); + + if (read_sysfs_int(temp_path, &temp_value) < 0) + continue; + + /* Try to read sensor label first, then hwmon name */ + snprintf(label_path, sizeof(label_path), "%s/temp%d_label", hwmon_dir, i); + if (read_sysfs_string(label_path, name, sizeof(name)) < 0) { + snprintf(name_path, sizeof(name_path), "%s/name", hwmon_dir); + if (read_sysfs_string(name_path, name, sizeof(name)) < 0) { + snprintf(name, sizeof(name), "%s_temp%d", entry->d_name, i); + } else { + /* Append temp index */ + char suffix[32]; + snprintf(suffix, sizeof(suffix), "_temp%d", i); + strncat(name, suffix, sizeof(name) - strlen(name) - 1); + } + } + + /* Create JSON object for this sensor */ + json_object *sensor = json_object_new_object(); + json_object_object_add(sensor, "name", json_object_new_string(name)); + json_object_object_add(sensor, "sysfs_path", json_object_new_string(temp_path)); + json_object_object_add(sensor, "temperature", json_object_new_int(temp_value / 1000)); + json_object_array_add(sensors_array, sensor); + } + } + + closedir(dir); +} + +static int interface_exists(const char *ifname) +{ + char path[256]; + struct stat st; + + /* Validate interface name to prevent path traversal */ + if (!ifname || ifname[0] == '\0' || strchr(ifname, '/') || strchr(ifname, '.')) + return 0; + + snprintf(path, sizeof(path), "/sys/class/net/%s", ifname); + return (stat(path, &st) == 0 && S_ISDIR(st.st_mode)); +} + +static void scan_mwctl_sensors(json_object *sensors_array) +{ + const char *interfaces[] = {"ra0", "rai0", "rax0", NULL}; + char output[4096]; + int i; + + for (i = 0; interfaces[i] != NULL; i++) { + const char *ifname = interfaces[i]; + FILE *fp; + int found_temp = 0; + int temp_value = 0; + char cmd[256]; + + if (!interface_exists(ifname)) + continue; + + /* Build command for popen - interface name is validated */ + snprintf(cmd, sizeof(cmd), "/usr/sbin/mwctl %s stat 2>/dev/null", ifname); + + fp = popen(cmd, "r"); // flawfinder: ignore + if (!fp) + continue; + + while (fgets(output, sizeof(output), fp) != NULL) { + /* Look for "CurrentTemperature = " */ + if (strstr(output, "CurrentTemperature") != NULL) { + char *equals = strchr(output, '='); + if (equals) { + char *endptr; + long val = strtol(equals + 1, &endptr, 10); + if (endptr != equals + 1 && val >= INT_MIN && val <= INT_MAX) { + temp_value = (int)val; + found_temp = 1; + break; + } + } + } + } + + pclose(fp); + + if (found_temp) { + char name[256]; + snprintf(name, sizeof(name), "wifi_%s", ifname); + + json_object *sensor = json_object_new_object(); + json_object_object_add(sensor, "name", json_object_new_string(name)); + json_object_object_add(sensor, "sysfs_path", json_object_new_string("")); + json_object_object_add(sensor, "temperature", json_object_new_int(temp_value)); + json_object_object_add(sensor, "wifi_interface", json_object_new_string(ifname)); + json_object_object_add(sensor, "wifi_type", json_object_new_string("mwctl")); + json_object_array_add(sensors_array, sensor); + } + } +} + +static void scan_wlctl_sensors(json_object *sensors_array) +{ + const char *interfaces[] = {"wl0", "wl1", "wl2", NULL}; + char output[256]; + int i; + + for (i = 0; interfaces[i] != NULL; i++) { + const char *ifname = interfaces[i]; + FILE *fp; + int temp_value = 0; + int found_temp = 0; + char cmd[256]; + + if (!interface_exists(ifname)) + continue; + + /* Build command for popen - interface name is validated */ + snprintf(cmd, sizeof(cmd), "/usr/sbin/wlctl -i %s phy_tempsense 2>/dev/null", ifname); + + fp = popen(cmd, "r"); // flawfinder: ignore + if (!fp) + continue; + + if (fgets(output, sizeof(output), fp) != NULL) { + /* Output is typically just a number or "Temperature: " */ + char *endptr; + long val = strtol(output, &endptr, 10); + if (endptr != output && val > 0 && val <= INT_MAX) { + temp_value = (int)val; + found_temp = 1; + } + } + + pclose(fp); + + if (found_temp) { + char name[256]; + snprintf(name, sizeof(name), "wifi_%s", ifname); + + json_object *sensor = json_object_new_object(); + json_object_object_add(sensor, "name", json_object_new_string(name)); + json_object_object_add(sensor, "sysfs_path", json_object_new_string("")); + json_object_object_add(sensor, "temperature", json_object_new_int(temp_value)); + json_object_object_add(sensor, "wifi_interface", json_object_new_string(ifname)); + json_object_object_add(sensor, "wifi_type", json_object_new_string("wlctl")); + json_object_array_add(sensors_array, sensor); + } + } +} + +static void scan_thermal_zones(json_object *sensors_array) +{ + DIR *dir; + struct dirent *entry; + + dir = opendir(THERMAL_PATH); + if (!dir) + return; + + while ((entry = readdir(dir)) != NULL) { + if (strncmp(entry->d_name, "thermal_zone", 12) != 0) + continue; + + char zone_dir[280]; /* THERMAL_PATH (19) + "/" + d_name (255) + null */ + char temp_path[512]; + char type_path[512]; + char type[128] = {0}; + char name[256] = {0}; + int temp_value = 0; + + snprintf(zone_dir, sizeof(zone_dir), "%s/%s", THERMAL_PATH, entry->d_name); + snprintf(temp_path, sizeof(temp_path), "%s/temp", zone_dir); + snprintf(type_path, sizeof(type_path), "%s/type", zone_dir); + + if (read_sysfs_int(temp_path, &temp_value) < 0) + continue; + + /* Try to read the zone type for name */ + if (read_sysfs_string(type_path, type, sizeof(type)) == 0 && type[0] != '\0') { + snprintf(name, sizeof(name), "%s", type); + } else { + snprintf(name, sizeof(name), "%s", entry->d_name); + } + + /* Create JSON object for this sensor */ + json_object *sensor = json_object_new_object(); + json_object_object_add(sensor, "name", json_object_new_string(name)); + json_object_object_add(sensor, "sysfs_path", json_object_new_string(temp_path)); + json_object_object_add(sensor, "temperature", json_object_new_int(temp_value / 1000)); + json_object_array_add(sensors_array, sensor); + } + + closedir(dir); +} + +static void discover_sensors(json_object **sensors_data) +{ + *sensors_data = json_object_new_array(); + + /* Scan hwmon sensors */ + scan_hwmon_sensors(*sensors_data); + + /* Scan thermal zones */ + scan_thermal_zones(*sensors_data); + + /* Scan WiFi sensors via mwctl (MediaTek) */ + scan_mwctl_sensors(*sensors_data); + + /* Scan WiFi sensors via wlctl (Broadcom) */ + scan_wlctl_sensors(*sensors_data); +} + static int browseTemperatureSensor(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance) { - json_object *temp_status = NULL; + json_object *sensors_data = NULL; struct dm_data data = {0}; + int instance_id = 0; - get_temperature_status(&temp_status); - if (temp_status) { /* cppcheck-suppress knownConditionTrueFalse */ - int id = 0, iter = 0; - json_object *arrobj = NULL, *tobj = NULL; + discover_sensors(&sensors_data); - dmjson_foreach_obj_in_array(temp_status, arrobj, tobj, iter, 1, "status") { - char *inst; + if (sensors_data) { + int array_len = json_object_array_length(sensors_data); - data.json_object = tobj; + for (int i = 0; i < array_len; i++) { + json_object *sensor_obj = json_object_array_get_idx(sensors_data, i); + if (!sensor_obj) + continue; - inst = handle_instance_without_section(dmctx, parent_node, ++id); + data.json_object = sensor_obj; + + char *inst = handle_instance_without_section(dmctx, parent_node, ++instance_id); if (DM_LINK_INST_OBJ(dmctx, parent_node, (void *)&data, inst) == DM_STOP) break; } - json_object_put(temp_status); + json_object_put(sensors_data); } return 0; } +/************************************************************* + * GET/SET PARAMETER FUNCTIONS + *************************************************************/ + static int get_TemperatureStatus_numentries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value) { int cnt = get_number_of_entries(ctx, data, instance, browseTemperatureSensor); @@ -112,6 +478,98 @@ static int get_TemperatureSensor_value(char *refparam, struct dmctx *ctx, void * return 0; } +static int set_alarm_value_common(struct dmctx *ctx, void *data, char *value, int action, int trip_point_num, const char *hwmon_alarm_type) +{ + json_object *obj = ((struct dm_data *)data)->json_object; + const char *sysfs_path; + int alarm_value_celsius; + + switch (action) { + case VALUECHECK: + if (bbfdm_validate_string(ctx, value, -1, -1, NULL, NULL)) + return FAULT_9007; + { + char *endptr; + long val = strtol(value, &endptr, 10); + if (endptr == value || val < -274 || val > INT_MAX) + return FAULT_9007; + alarm_value_celsius = (int)val; + } + break; + case VALUESET: + sysfs_path = dmjson_get_value(obj, 1, "sysfs_path"); + if (!sysfs_path || sysfs_path[0] == '\0') + return FAULT_9002; + + { + char *endptr; + long val = strtol(value, &endptr, 10); + if (endptr == value || val < INT_MIN || val > INT_MAX) + return FAULT_9002; + alarm_value_celsius = (int)val; + } + if (alarm_value_celsius == -274) { + /* -274 means disable/unconfigure - skip writing */ + return 0; + } + + if (write_alarm_value(sysfs_path, trip_point_num, hwmon_alarm_type, alarm_value_celsius * 1000) != 0) + return FAULT_9002; + break; + } + return 0; +} + +static int get_TemperatureSensor_low_alarm_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value) +{ + json_object *obj = ((struct dm_data *)data)->json_object; + const char *sysfs_path = dmjson_get_value(obj, 1, "sysfs_path"); + int alarm_value; + + *value = dmstrdup("-274"); /* Default: not configured */ + + if (!sysfs_path || sysfs_path[0] == '\0') + return 0; + + /* trip_point_1_temp for thermal zones, temp*_crit for hwmon */ + if (read_alarm_value(sysfs_path, 1, "crit", &alarm_value) == 0) { + dmasprintf(value, "%d", alarm_value / 1000); + } + + return 0; +} + +static int set_TemperatureSensor_low_alarm_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action) +{ + /* trip_point_1_temp for thermal zones, temp*_crit for hwmon */ + return set_alarm_value_common(ctx, data, value, action, 1, "crit"); +} + +static int get_TemperatureSensor_high_alarm_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value) +{ + json_object *obj = ((struct dm_data *)data)->json_object; + const char *sysfs_path = dmjson_get_value(obj, 1, "sysfs_path"); + int alarm_value; + + *value = dmstrdup("-274"); /* Default: not configured */ + + if (!sysfs_path || sysfs_path[0] == '\0') + return 0; + + /* trip_point_0_temp for thermal zones, temp*_max for hwmon */ + if (read_alarm_value(sysfs_path, 0, "max", &alarm_value) == 0) { + dmasprintf(value, "%d", alarm_value / 1000); + } + + return 0; +} + +static int set_TemperatureSensor_high_alarm_value(char *refparam, struct dmctx *ctx, void *data, char *instance, char *value, int action) +{ + /* trip_point_0_temp for thermal zones, temp*_max for hwmon */ + return set_alarm_value_common(ctx, data, value, action, 0, "max"); +} + /****************************************************************************************************************************** * OBJ & PARAM DEFINITION *******************************************************************************************************************************/ @@ -120,10 +578,12 @@ static DMLEAF tTemperatureStatusTemperatureSensorParams[] = { {"Alias", &DMWRITE, DMT_STRING, get_TemperatureSensor_alias, set_TemperatureSensor_alias, BBFDM_BOTH, DM_FLAG_UNIQUE}, {"Name", &DMREAD, DMT_STRING, get_TemperatureSensor_name, NULL, BBFDM_BOTH, DM_FLAG_UNIQUE}, {"Value", &DMREAD, DMT_INT, get_TemperatureSensor_value, NULL, BBFDM_BOTH}, +{"LowAlarmValue", &DMWRITE, DMT_INT, get_TemperatureSensor_low_alarm_value, set_TemperatureSensor_low_alarm_value, BBFDM_BOTH}, +{"HighAlarmValue", &DMWRITE, DMT_INT, get_TemperatureSensor_high_alarm_value, set_TemperatureSensor_high_alarm_value, BBFDM_BOTH}, {0} }; -/* *** Device.DeviceInfo.TemperatureTemperatureStatus; ubus call "bbf.temp status" *** */ +/* *** Device.DeviceInfo.TemperatureStatus *** */ DMOBJ tDeviceInfoTemperatureStatusObj[] = { /* OBJ, permission, addobj, delobj, checkdep, browseinstobj, nextdynamicobj, dynamicleaf, nextobj, leaf, linker, bbfdm_type, uniqueKeys*/ {"TemperatureSensor", &DMREAD, NULL, NULL, NULL, browseTemperatureSensor, NULL, NULL, NULL, tTemperatureStatusTemperatureSensorParams, NULL, BBFDM_BOTH}, diff --git a/test/files/etc/sysmngr/temperature.sh b/test/files/etc/sysmngr/temperature.sh deleted file mode 100644 index 0318f57..0000000 --- a/test/files/etc/sysmngr/temperature.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh - -. /usr/share/libubox/jshn.sh -. /lib/functions.sh - -function get_wlan_temperature() -{ - : -} - -function get_sfp_temperature() -{ - json_add_object - json_add_string "name" "sfp" - json_add_int "temperature" "-274" - json_close_object -} - -function get_cpu_temperature() -{ - json_add_object - json_add_string "name" "cpu" - json_add_int "temperature" "-274" - json_close_object -} - -function get_temperature_status() -{ - local hasWifi="$(db -q get hw.board.hasWifi)" - local hasSfp="$(db -q get hw.board.hasSfp)" - - json_init - json_add_array "status" - get_cpu_temperature - [ "$hasSfp" = "1" ] && get_sfp_temperature - [ "$hasWifi" = "1" ] && get_wlan_temperature - json_close_array - - json_dump -} - -function dump_invalid() -{ - json_init - json_add_string "fault" "invalid request" - json_dump -} - -case "$1" in - status) - get_temperature_status - ;; - *) - dump_invalid - ;; -esac