diff --git a/Makefile b/Makefile index ebb8082..641aeb4 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY : clean debug -OBJS = evga-icx.o evga-card.o icx3.o +OBJS = evga-icx.o evga-card.o icx3.o board-sensors.o LDLIBS = -li2c CFLAGS = -MD @@ -18,7 +18,7 @@ endif evga-icx : $(OBJS) -debug : CFLAGS += -g -O0 +debug : CFLAGS += -g -Og debug : evga-icx clean : diff --git a/README.md b/README.md index e272e24..7ff5f76 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Available options: --watch N : Keep printing output every N seconds --overwrite : Overwrite previously displayed info with --watch and --compact instead of continuously logging new lines --color : Print headers in color in --compact mode for better readability ---no-reasons : Do not query NVML for clocks reasons (can cause stuttering) +--no-reasons : Do not query NVML for clock reasons (can cause stuttering) ``` ### Examples: diff --git a/board-sensors.c b/board-sensors.c new file mode 100644 index 0000000..b2847ef --- /dev/null +++ b/board-sensors.c @@ -0,0 +1,122 @@ +#include +#include +#include + +#include "board-sensors.h" + +int find_board_sensors(struct hwmon_avail_sensor *board_sensors, int max_sensors) +{ + const char *hwmon_path = "/sys/class/hwmon/"; + char device_path[NAME_MAX]; + char sensor_path[NAME_MAX]; + + char driver_name[256]; + + FILE *file; + DIR *dir; + struct dirent *ent; + + int num_sensors = 0; + + /* Start looking for hwmon devices in /sys/class/hwmon/ */ + dir = opendir(hwmon_path); + + /* make sure we can open the device directory */ + if(dir == NULL) + return 0; + + /* loop over all hwmon devices */ + while((ent = readdir(dir)) != NULL) + { + /* Ignore any non-hwmon dirs */ + if(strncmp(ent->d_name, "hwmon", 5) != 0) + continue; + + strcpy(device_path, hwmon_path); + strcat(device_path, ent->d_name); + + /* Read in the name of the device */ + strcpy(sensor_path, device_path); + strcat(sensor_path, "/name"); + + file = fopen(sensor_path, "r"); + if (file == NULL) + continue; + + if (fgets(driver_name, sizeof(driver_name), file) == NULL) { + fclose(file); + continue; + } + + fclose(file); + + /* Driver names have a linebreak at the end so let's remove that for comparison*/ + driver_name[strlen(driver_name) - 1] = '\0'; + + /* Loop through all supported sensors and see if any are present in this device */ + for (int i = 0; i < (sizeof(hwmon_sensor_info) / sizeof(struct hwmon_sensor)); i++) { + if (strcmp(driver_name, hwmon_sensor_info[i].driver_name) == 0) { + /* We matched the driver name, try to open the files */ + strcpy(sensor_path, device_path); + strcat(sensor_path, "/"); + strcat(sensor_path, hwmon_sensor_info[i].sensor_file_name); + strcat(sensor_path, "_input"); + + file = fopen(sensor_path, "r"); + if (file != NULL) { + fclose(file); + /* Good open of the sensor file */ + board_sensors[num_sensors].file = calloc(NAME_MAX, sizeof(char)); + strcpy(board_sensors[num_sensors].file, sensor_path); + board_sensors[num_sensors].sort_index = i; + board_sensors[num_sensors].sensor_info = &hwmon_sensor_info[i]; + + /* Read in the sensor name */ + board_sensors[num_sensors].sensor_name = calloc(MAX_SENSOR_NAME_LENGTH, sizeof(char)); + strcpy(sensor_path, device_path); + strcat(sensor_path, "/"); + strcat(sensor_path, hwmon_sensor_info[i].sensor_file_name); + strcat(sensor_path, "_label"); + + file = fopen(sensor_path, "r"); + if (file != NULL) + fgets(board_sensors[num_sensors].sensor_name, MAX_SENSOR_NAME_LENGTH, file); + + /* Sensor name seems to always have a trailing newline we don't want */ + size_t len_without_newline = strcspn(board_sensors[num_sensors].sensor_name, "\n"); + board_sensors[num_sensors].sensor_name[len_without_newline] = '\0'; + + if (num_sensors == max_sensors) + return num_sensors; + num_sensors++; + } + } + } + + } + + return num_sensors; +} + +/* Returns 0 on a bad read or missing sensor, 1 on OK */ +int get_sensor_reading(struct hwmon_avail_sensor *sensor, float *reading) { + char buf[256] = {0}; + long int raw; + FILE *file; + + file = fopen(sensor->file, "r"); + if (file == NULL) + return 0; + + fgets(buf, 256, file); + raw = strtol(buf, NULL, 10); + + fclose(file); + + *reading = (float)raw / sensor->sensor_info->divisor; + + if (*reading == sensor->sensor_info->bad_value) + return 0; + + return 1; +} diff --git a/board-sensors.h b/board-sensors.h new file mode 100644 index 0000000..4ebd4e6 --- /dev/null +++ b/board-sensors.h @@ -0,0 +1,45 @@ +#include + +#define MAX_SENSOR_NAME_LENGTH 256 + +struct hwmon_sensor { + char *driver_name; /* Contents of /sys/class/hwmon/hwmonX/name */ + char *sensor_file_name; /* Sysfs file to read */ + char *name_prefix; /* Prefix to attach to temp*_label for clarity */ + char *short_name; /* 'Category' name when using compact mode */ + char *units; /* Units string */ + float divisor; /* Divisor to convert temp* to units */ + float bad_value; /* Raw value that indicates a bad (missing) sensor TODO: verfiy most of these*/ +}; + +/* Note the order here matters, it's the order these will be printed in */ +static struct hwmon_sensor hwmon_sensor_info[] = +{ + {"zenpower", "temp1", "CPU ", "CPU", "°C", 1000.0, -40.0}, /* Tdie */ + {"asusec", "temp2", "Motherboard ", "CPU", "°C", 1000.0, -40.0}, /* CPU */ + {"zenpower", "temp3", "CPU ", "CCD", "°C", 1000.0, -40.0}, /* Tccd1 */ + {"zenpower", "temp4", "CPU ", "CCD", "°C", 1000.0, -40.0}, /* Tccd2 */ + {"zenpower", "temp5", "CPU ", "CCD", "°C", 1000.0, -40.0}, /* Tccd3 */ + {"zenpower", "temp6", "CPU ", "CCD", "°C", 1000.0, -40.0}, /* Tccd4 */ + {"zenpower", "temp7", "CPU ", "CCD", "°C", 1000.0, -40.0}, /* Tccd5 */ + {"zenpower", "temp8", "CPU ", "CCD", "°C", 1000.0, -40.0}, /* Tccd6 */ + {"zenpower", "temp9", "CPU ", "CCD", "°C", 1000.0, -40.0}, /* Tccd7 */ + {"zenpower", "temp10", "CPU ", "CCD", "°C", 1000.0, -40.0}, /* Tccd8 */ + {"asusec", "temp1", "Motherboard ", "CHIP", "°C", 1000.0, -40.0}, /* Chipset */ + {"asusec", "temp5", "Motherboard ", "VRM", "°C", 1000.0, -40.0}, /* VRM */ + {"asusec", "temp3", "", "MOBO", "°C", 1000.0, -40.0}, /* Motherboard */ + {"asusec", "temp4", "Motherboard ", "SENS", "°C", 1000.0, -40.0}, /* T_Sensor */ + {"asusec", "temp6", "Motherboard ", "H2O", "°C", 1000.0, -40.0}, /* Water_In */ + {"asusec", "temp7", "Motherboard ", "H2O", "°C", 1000.0, -40.0}, /* Water_Out */ + {"nvme", "temp1", "NVMe ", "NVME", "°C", 1000.0, -40.0}, /* NVME Composite */ +}; + +struct hwmon_avail_sensor { + char *sensor_name; /* Sensor name as read from the sysfs file */ + int sort_index; /* Sort index for order to display in */ + char *file; /* File to read from */ + struct hwmon_sensor *sensor_info; /* Associated sensor info struct */ +}; + +int find_board_sensors(struct hwmon_avail_sensor *board_sensors, int max_sensors); +int get_sensor_reading(struct hwmon_avail_sensor *sensor, float *reading); \ No newline at end of file diff --git a/evga-icx.c b/evga-icx.c index a2c7b27..2433d0c 100644 --- a/evga-icx.c +++ b/evga-icx.c @@ -15,8 +15,10 @@ #include "icx3.h" #include "evga-card.h" +#include "board-sensors.h" #define MAX_GPUS 16 +#define MAX_BOARD_SENSORS 256 #define HEADER_COLOR_START "\x1b[36m" #define HEADER_COLOR_END "\x1b[39m" @@ -38,21 +40,25 @@ static const char helpstring[] = "Available options:\n" "--watch N : Keep printing output every N seconds\n" "--overwrite : Overwrite previously displayed info with --watch and --compact instead of continuously logging\n" "--color : Print headers in color in --compact mode for better readability\n" - "--no-reasons : Do not query NVML for clocks reasons (can cause stuttering)\n"; + "--no-reasons : Do not query NVML for clock reasons (can cause stuttering)\n" + "--board : Also print temperatures from the CPU, motherboard, and other sensors"; void print_gpu_info(int gpu_num, struct card_info gpus[], int compact, int no_reasons); +void print_board_info(struct hwmon_avail_sensor *board_sensors, int num_sensors, int compact); int main (int argc, char **argv) { struct card_info gpus[MAX_GPUS]; - int gpu_count; + struct hwmon_avail_sensor board_sensors[MAX_BOARD_SENSORS]; + int gpu_count, board_sensor_count; int print_info = 0; - int compact = 0; - int gpu_num = -1; /* Card to control */ - int i2c_bus = -1; - int overwrite = 0; - int no_reasons = 0; - unsigned int watch = 0; + int compact = 0; + int gpu_num = -1; /* Card to control */ + int i2c_bus = -1; /* Specific i2c bus to probe instead of all */ + int overwrite = 0; /* Overwrite printed console info in compact mode */ + int no_reasons = 0; /* Don't probe or display NVML clock reasons */ + unsigned int watch = 0; /* Refresh display every this many seconds */ + int print_board_sensors = 0; /* Print CPU/motherbord/other sensors as well */ char *fan_speed[ICX3_MAX_FANS] = {NULL}; /* Input parsing */ @@ -114,6 +120,8 @@ int main (int argc, char **argv) header_end = HEADER_COLOR_END; } else if (strcmp(argv[i], "--no-reasons") == 0) { no_reasons = 1; + } else if (strcmp(argv[i], "--board") == 0) { + print_board_sensors = 1; } else { printf(helpstring); return 0; @@ -133,8 +141,10 @@ int main (int argc, char **argv) if (overwrite && !compact) overwrite = 0; + /* Scan for supported GPUs */ gpu_count = find_evga_gpu_i2cs(gpus, MAX_GPUS, i2c_bus); + /* Check for no GPUs found or other errors */ if (gpu_count == -1) { printf("Error scanning I2C devices\n"); return -1; @@ -142,12 +152,15 @@ int main (int argc, char **argv) printf("No supported GPUs found.\nAre you root or do you have udev access to i2c devices?\nDo you need to run `modprobe i2c-dev`?\n"); return -1; } - + /* Check for invalid GPUs */ if (gpu_num > gpu_count - 1) { printf("Invalid GPU number specified (%d, max %d)\n", gpu_num, gpu_count - 1); return -1; } + /* Scan for motherboard/CPU/other sensors */ + if (print_board_sensors) + board_sensor_count = find_board_sensors(board_sensors, MAX_BOARD_SENSORS); /* execute fan commands */ if (gpu_num == -1) { @@ -183,6 +196,9 @@ int main (int argc, char **argv) if (overwrite) printf("\x1b[K"); /* Clear current console line */ + if (print_board_sensors) + print_board_info(board_sensors, board_sensor_count, compact); + if (gpu_num == -1) { /* No GPU specified on command line, loop over all supported GPUs */ for (int i = 0; i < gpu_count; i++){ @@ -199,8 +215,8 @@ int main (int argc, char **argv) if (overwrite && compact) { printf("\x1b[1G"); /* Move cursor back to column 1 */ - if (gpu_count > 1) - printf("\x1b[%dA", gpu_count-1); /* Move cursor back up to the top of gpu list */ + if (gpu_count > 1 || print_board_sensors) + printf("\x1b[%dA", gpu_count-1+print_board_sensors); /* Move cursor back up to the top of gpu list */ } fflush(stdout); @@ -213,7 +229,64 @@ int main (int argc, char **argv) #endif } -void print_gpu_info(int gpu_num, struct card_info *gpu, int compact, int no_reasons) { +void print_board_info(struct hwmon_avail_sensor *board_sensors, int num_sensors, int compact) +{ + int printed_sensors = 0; + int current_sort_index = 0; + float current_reading = 0.0; + int good_reading = 0; + /* These allow us to 'summarize' units and categories by only printing them when they change */ + char *last_short_name = NULL; + char *last_units = NULL; + + while (printed_sensors < num_sensors) { + for (int i=0; i < num_sensors; i++){ + /* Loop over all sensors, but only output those with the current sort index so they come out sort of sorted + Duplicates (e.g.) multiple NVMe will come out in whatever sort of order the directory listing happened to */ + if (board_sensors[i].sort_index == current_sort_index) { + printed_sensors++; + good_reading = get_sensor_reading(&board_sensors[i], ¤t_reading); + + if (!good_reading) + continue; + + if (compact) { + /* Print units if needed */ + if (last_units != NULL && strcmp(board_sensors[i].sensor_info->units, last_units)) + printf("%s", last_units); + + /* Print new section header if needed */ + if (last_short_name == NULL || strcmp(board_sensors[i].sensor_info->short_name, last_short_name)) { + if (last_short_name != NULL) /* Spacer for all headings not the first one */ + printf(" "); + printf("%s%s%s", header_start, board_sensors[i].sensor_info->short_name, header_end); + } + printf(" %3.0f", current_reading); + + last_short_name = board_sensors[i].sensor_info->short_name; + last_units = board_sensors[i].sensor_info->units; + + } else { + printf("%s%s: %+.1f%s\n", + board_sensors[i].sensor_info->name_prefix, + board_sensors[i].sensor_name, + current_reading, + board_sensors[i].sensor_info->units); + } + + } + } + current_sort_index++; + } + + if (compact && last_units != NULL) + printf("%s", last_units); + + printf("\n"); +} + +void print_gpu_info(int gpu_num, struct card_info *gpu, int compact, int no_reasons) +{ if (compact) { /* One line per GPU */ printf("%s#%d FAN%s", header_start, gpu_num, header_end); @@ -268,8 +341,8 @@ void print_gpu_info(int gpu_num, struct card_info *gpu, int compact, int no_reas printf("VRAM: +%.0f°C\n", get_vram_temp(gpu)); /* Print the VRAM temp before the rest of the memory sensors */ #endif printf("%s: %+.1f°C\n", - icx3_temp_sensor_names[i], - icx_temp_sensors[i]); + icx3_temp_sensor_names[i], + icx_temp_sensors[i]); } #ifdef USE_LIBPCI