diff --git a/evga-card.c b/evga-card.c index 7af209a..1cecfe4 100644 --- a/evga-card.c +++ b/evga-card.c @@ -27,9 +27,7 @@ int find_evga_gpu_i2cs(struct card_info *infos, int max_gpus) /* make sure we can open the i2c device */ if(dir == NULL) - { return -1; - } /* loop over all i2c devices */ while((ent = readdir(dir)) != NULL) @@ -50,10 +48,10 @@ int find_evga_gpu_i2cs(struct card_info *infos, int max_gpus) /* See if it's a matching device for a supported EVGA card */ for (int i = 0; i < (sizeof(evga_pci_ids) / sizeof(struct gpu_pci_info)); i++) { - if (pci_vendor == evga_pci_ids[i].vendor_id && - pci_device == evga_pci_ids[i].device_id && - pci_subsystem_vendor == evga_pci_ids[i].subvendor_id && - pci_subsystem_device == evga_pci_ids[i].subdevice_id) { + if (pci_vendor == evga_pci_ids[i].vendor_id && + pci_device == evga_pci_ids[i].device_id && + pci_subsystem_vendor == evga_pci_ids[i].subvendor_id && + pci_subsystem_device == evga_pci_ids[i].subdevice_id) { /* Matched all PCI IDs, check for good firmware read */ strcpy(dev_file, "/dev/"); diff --git a/evga-card.h b/evga-card.h index ad2f635..164fd33 100644 --- a/evga-card.h +++ b/evga-card.h @@ -1,5 +1,3 @@ -#pragma once - #define NVIDIA_VEN 0x10DE #define NVIDIA_RTX3060_DEV 0x2503 diff --git a/evga-icx.c b/evga-icx.c index 631d24e..dd5444a 100644 --- a/evga-icx.c +++ b/evga-icx.c @@ -10,27 +10,126 @@ #define MAX_GPUS 16 +static const char helpstring[] = "Available options:\n" + "--gpu N : Control only GPU N instead of all supported cards\n" + "--fan SPEED : Set all fans at once to SPEED (see below)\n" + "--fanN SPEED : Set fan N (0-3) to SPEED\n" + " SPEED may be one of the following:\n" + " 'auto' to return the fan to its default control mode\n" + " N to set the fan to that manual % speed\n" + " [+/-]N to set that fan to an RPM offset from the GPU-controlled speed\n" + "--reset : Reset all fans to their default mode\n" + "--sensors : Print sensor readings even if setting a fan speed \n"; + +void print_gpu_info(int gpu_num, struct card_info gpus[]); + int main (int argc, char **argv) { struct card_info gpus[MAX_GPUS]; - int num_gpus; + int gpu_count; + int print_info = 0; + int gpu_num = -1; /* Card to control */ + char *fan_speed[ICX3_MAX_FANS] = {NULL}; - num_gpus = find_evga_gpu_i2cs(gpus, MAX_GPUS); + /* Input parsing */ + for (int i = 1; i < argc; i++){ + if (strcmp(argv[i], "--gpu") == 0) { + i++; + if (i < argc) { + gpu_num = atoi(argv[i]); + } else { + printf(helpstring); + return -1; + } + } else if (strcmp(argv[i], "--fan") == 0) { + i++; + if (i < argc) { + for (int j = 0; j < ICX3_MAX_FANS; j++) + fan_speed[j] = argv[i]; + } else { + printf(helpstring); + return -1; + } + } else if (strncmp(argv[i], "--fan", 5) == 0) { + int fan_num = atoi(argv[i]+5); + i++; + if (i < argc) { + if (fan_num <= ICX3_MAX_FANS) + fan_speed[fan_num] = argv[i]; + } else { + printf(helpstring); + return -1; + } + } else if (strcmp(argv[i], "--reset") == 0) { + for (int j = 0; j < ICX3_MAX_FANS; j++) + fan_speed[j] = "auto"; + } else if (strcmp(argv[i], "--sensors") == 0) { + print_info = 1; + } else { + printf(helpstring); + return 0; + } + } - if (num_gpus == -1) { + if (print_info == 0) { + /* Check for no fan commands given, so display info by default */ + print_info = 1; + for (int i = 0; i < ICX3_MAX_FANS; i++) { + if (fan_speed[i] != NULL) + print_info = 0; + } + } + + gpu_count = find_evga_gpu_i2cs(gpus, MAX_GPUS); + + if (gpu_count == -1) { printf("Error scanning I2C devices\n"); return -1; - } else if (num_gpus == 0) { + } else if (gpu_count == 0) { 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; } - for (int i = 0; i < num_gpus; i++){ - printf("#%d: %s (%s) @ %s\n", i, gpus[i].card_name, gpus[i].i2c_dev_path, gpus[i].pci_id); - print_icx3_fans(gpus[i].i2c_dev_path); - print_icx3_temps(gpus[i].i2c_dev_path); - printf("\n"); + if (gpu_num > gpu_count - 1) { + printf("Invalid GPU number specified (%d, max %d)\n", gpu_num, gpu_count - 1); + return -1; + } + + + /* execute fan commands */ + if (gpu_num == -1) { + for (int i = 0; i < gpu_count; i++){ + for (int j = 0; j < ICX3_MAX_FANS; j++) { + if (fan_speed[j] != NULL) + set_fan(j, fan_speed[j], gpus[i].i2c_dev_path); + /* printf("gpu %d fan %d : %s\n", i, j, fan_speed[j]); */ + } + } + } else if (gpu_num <= gpu_count - 1) { + for (int j = 0; j < ICX3_MAX_FANS; j++) { + if (fan_speed[j] != NULL) + set_fan(gpu_num, fan_speed[j], gpus[gpu_num].i2c_dev_path); + /* printf("gpu %d fan %d : %s\n", gpu_num, j, fan_speed[j]);*/ + } + } + + /* print sensor info */ + if (print_info) { + if (gpu_num == -1) { + for (int i = 0; i < gpu_count; i++){ + print_gpu_info(i, gpus); + } + } else if (gpu_num <= gpu_count - 1) { + print_gpu_info(gpu_num, gpus); + } } } + +void print_gpu_info(int gpu_num, struct card_info gpus[]) { + printf("#%d: %s (%s) @ %s\n", gpu_num, gpus[gpu_num].card_name, gpus[gpu_num].i2c_dev_path, gpus[gpu_num].pci_id); + print_icx3_fans(gpus[gpu_num].i2c_dev_path); + print_icx3_temps(gpus[gpu_num].i2c_dev_path); + printf("\n"); +} diff --git a/icx3.c b/icx3.c index 58732c6..7bda775 100644 --- a/icx3.c +++ b/icx3.c @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "icx3.h" @@ -14,15 +16,10 @@ enum icx3_product_id check_for_icx3(char *i2c_dev_path) int fd, read_result; struct icx3_info *temp_info; - fd = open(i2c_dev_path, O_RDONLY); - if (!fd) + fd = open_i2c_dev(i2c_dev_path); + if (fd == -1) return ICX3_PRODUCT_NONE; - if (ioctl(fd, I2C_SLAVE, ICX3_I2C_ADDR) < 0) { - close(fd); - return ICX3_PRODUCT_NONE; - } - read_result = i2c_smbus_read_i2c_block_data(fd, ICX3_REG_READINFORMATION, ICX3_READINFORMATION_SIZE, data); close(fd); if (read_result == ICX3_READINFORMATION_SIZE) { @@ -42,7 +39,68 @@ void print_icx3_fans(char *i2c_dev_path) struct icx3_fan_control *fan_status; /* First thing is to check for the product ID to figure out how many fans we have */ - int fans_avail[ICX3_MAX_FANS] = {0}; + char fans_avail[ICX3_MAX_FANS] = {0}; + get_available_fans(i2c_dev_path, fans_avail); + + fd = open_i2c_dev(i2c_dev_path); + if (fd == -1) + return; + + for (int i=0; i < ICX3_MAX_FANS; i++) { + if (!fans_avail[i]) + continue; + + read_result = i2c_smbus_read_i2c_block_data(fd, ICX3_REG_FANCONTROL + i, ICX3_FANCONTROL_SIZE, data); + if (read_result != ICX3_FANCONTROL_SIZE) { + close(fd); + return; + } + fan_status = (struct icx3_fan_control*) &data; + printf("%s: %d RPM (%d/%d%%, %s)\n", + icx3_fan_names[i], + fan_status->rpm_status, + fan_status->duty_status, + fan_status->duty, + icx3_fan_mode_names[fan_status->fanmode] + ); + } + + close(fd); +} + +void print_icx3_temps(char *i2c_dev_path) +{ + char data[I2C_SMBUS_BLOCK_MAX] = {}; + int fd, read_result; + struct icx3_temp_sensors *temp_sensors; + + fd = open_i2c_dev(i2c_dev_path); + if (fd == -1) + return; + + read_result = i2c_smbus_read_i2c_block_data(fd, ICX3_REG_TEMPSENSOR, ICX3_TEMPSENSOR_SIZE, data); + if (read_result != ICX3_TEMPSENSOR_SIZE) { + close(fd); + return; + } + temp_sensors = (struct icx3_temp_sensors*) &data; + + float cur_temp; + short cur_data; + + for (int i=0; idata[2*i+1] << 8) | (short)(temp_sensors->data[2*i]); + /* temp is reported in tenths of deg C */ + cur_temp = (float)cur_data/10; + printf("%s: %.1f C\n", + icx3_temp_sensor_names[i], + cur_temp); + } +} + +void get_available_fans(char *i2c_dev_path, char *fans_avail) +{ int product_id = check_for_icx3(i2c_dev_path); /* From ICX3TotalFanCtrl.cs */ @@ -87,6 +145,7 @@ void print_icx3_fans(char *i2c_dev_path) fans_avail[3] = 1; break; default: + /* default fans only */ fans_avail[0] = 1; fans_avail[1] = 1; break; @@ -94,70 +153,103 @@ void print_icx3_fans(char *i2c_dev_path) /* Not sure what this case is for */ break; } - - fd = open(i2c_dev_path, O_RDONLY); - - /* Error silently here because we should have already checked we can read the i2c in check_for_icx3() */ - if (!fd) - return; - if (ioctl(fd, I2C_SLAVE, ICX3_I2C_ADDR) < 0) { - close(fd); - return; - } - - for (int i=0; i < ICX3_MAX_FANS; i++) { - if (!fans_avail[i]) - continue; - - read_result = i2c_smbus_read_i2c_block_data(fd, ICX3_REG_FANCONTROL + i, ICX3_FANCONTROL_SIZE, data); - if (read_result != ICX3_FANCONTROL_SIZE) { - close(fd); - return; - } - fan_status = (struct icx3_fan_control*) &data; - printf("%s: %d RPM (%d/%d%%, %s)\n", - icx3_fan_names[i], - fan_status->rpm_status, - fan_status->duty_status, - fan_status->duty, - icx3_fan_mode_names[fan_status->fanmode] - ); - } } -void print_icx3_temps(char *i2c_dev_path) +int open_i2c_dev(char *i2c_dev_path) { - char data[I2C_SMBUS_BLOCK_MAX] = {}; - int fd, read_result; - struct icx3_temp_sensors *temp_sensors; - - fd = open(i2c_dev_path, O_RDONLY); + int fd = open(i2c_dev_path, O_RDONLY); /* Error silently here because we should have already checked we can read the i2c in check_for_icx3() */ - if (!fd) - return; + if (fd == -1) + return -1; + if (ioctl(fd, I2C_SLAVE, ICX3_I2C_ADDR) < 0) { close(fd); + return -1; + } + return fd; +} + +void enable_write(int enable, char *i2c_dev_path) +{ + int fd = open_i2c_dev(i2c_dev_path); + if (fd == -1) + return; + + unsigned char *data; + if (enable) + data = icx3_write_enable; + else + data = icx3_write_disable; + /* Enable or disable write */ + i2c_smbus_write_i2c_block_data(fd, ICX3_REG_ENABLEWRITE, ICX3_ENABLEWRITE_SIZE, data); + + /* Read back the result to verify */ + unsigned char read_result[ICX3_ENABLEWRITE_SIZE]; + int write_ok = 0; + i2c_smbus_read_i2c_block_data(fd, ICX3_REG_ENABLEWRITE, ICX3_ENABLEWRITE_SIZE, read_result); + if (enable) + write_ok = (read_result[1] == 0xFC); + else + write_ok = (read_result[1] == 0xFE); + + if (!write_ok) + printf("Unable to enable/disable write on %s\n", i2c_dev_path); + +} + +/* Sets a given fan on the GPU according to a string setting +'auto' resets the fan to default (fans 1-2 go to GPU control, 3-4 to +0 RPM offset from GPU control) +A number without a sign (e.g. 50) manually sets the fan to a given % duty cycle +A number with a sign (e.g. +500) sets the fan to run at an RPM offset from GPU control */ +void set_fan(int fan, char *setting, char *i2c_dev_path) +{ + char fans_avail[ICX3_MAX_FANS] = {0}; + + struct icx3_fan_control fan_control = {}; + struct icx3_fan_control fan_readback = {}; + char reg = ICX3_REG_FANCONTROL + fan; + + int write_result; + + /* Check to make sure we're setting a valid fan */ + get_available_fans(i2c_dev_path, fans_avail); + if (fans_avail[fan] == 0) { + printf("Fan %d does not exist on this card \n", fan); return; } - - read_result = i2c_smbus_read_i2c_block_data(fd, ICX3_REG_TEMPSENSOR, ICX3_TEMPSENSOR_SIZE, data); - if (read_result != ICX3_TEMPSENSOR_SIZE) { - close(fd); + + int fp = open_i2c_dev(i2c_dev_path); + if (fp == -1) return; - } - temp_sensors = (struct icx3_temp_sensors*) &data; - float cur_temp; - short cur_data; - - for (int i=0; idata[2*i+1] << 8) | (short)(temp_sensors->data[2*i]); - /* temp is reported in tenths of deg C */ - cur_temp = (float)cur_data/10; - printf("%s %.1f C\n", - icx3_temp_sensor_names[i], - cur_temp); + fan_control.length = ICX3_FANCONTROL_SIZE - 1; + if (strcmp(setting, "auto") == 0) { + /* auto setting */ + fan_control.rpm_offset = 0; + if (fan <= 1) + fan_control.fanmode = ICX3_FANMODE_GPU_CONTROL; + else + fan_control.fanmode = ICX3_FANMODE_GPU_RPM_OFFSET; + } else if (setting[0] == '+' || setting[0] == '-') { + /* RPM offset */ + fan_control.fanmode = ICX3_FANMODE_GPU_RPM_OFFSET; + fan_control.rpm_offset = atoi(setting); + } else { + fan_control.fanmode = ICX3_FANMODE_RPM; + fan_control.duty = atoi(setting); } -} \ No newline at end of file + + enable_write(1, i2c_dev_path); + + i2c_smbus_write_i2c_block_data(fp, reg, ICX3_FANCONTROL_SIZE, (char *)&fan_control); + + /* Read back data and verify we set the fan properly */ + i2c_smbus_read_i2c_block_data(fp, reg, ICX3_FANCONTROL_SIZE, (char *)&fan_readback); + if (fan_readback.fanmode != fan_control.fanmode || + fan_readback.rpm_offset != fan_control.rpm_offset || + fan_readback.duty != fan_control.duty) + printf("Error setting fan %d on %s\n", fan, i2c_dev_path); + close(fp); +} + diff --git a/icx3.h b/icx3.h index dd566d8..030beaf 100644 --- a/icx3.h +++ b/icx3.h @@ -1,18 +1,21 @@ -#pragma once - #define ICX3_I2C_ADDR 0x2D #define ICX3_REG_FANCONTROL 80 #define ICX3_REG_TEMPSENSOR 102 #define ICX3_REG_READINFORMATION 177 +#define ICX3_REG_ENABLEWRITE 178 #define ICX3_FANCONTROL_SIZE 0x09 #define ICX3_TEMPSENSOR_SIZE 0x13 #define ICX3_READINFORMATION_SIZE 0x06 +#define ICX3_ENABLEWRITE_SIZE 0x05 #define ICX3_MAX_FANS 4 #define ICX3_NUM_TEMP_SENSORS 9 +#define ICX3_WRITE_ENABLED 0xFC +#define ICX3_WRITE_DISABLED 0xFE + struct icx3_fan_control { unsigned char length; unsigned char fanmode; @@ -68,10 +71,22 @@ enum icx3_product_id { ICX3_PRODUCT_NONE = 0 }; +enum icx3_fan_mode { + ICX3_FANMODE_DEFAULT, + ICX3_FANMODE_GPU_CONTROL, + ICX3_FANMODE_RPM, + ICX3_FANMODE_GPU_RPM_OFFSET, + ICX3_FANMODE_MCU_CURVE, + ICX3_FANMODE_MODE3, +}; + +static unsigned char icx3_write_enable[ICX3_ENABLEWRITE_SIZE] = {ICX3_ENABLEWRITE_SIZE-1, 198, 235, 234, 21}; +static unsigned char icx3_write_disable[ICX3_ENABLEWRITE_SIZE] = {ICX3_ENABLEWRITE_SIZE-1, 144, 235, 203, 52}; + static char *icx3_fan_names[] = { + "Fan 0", "Fan 1", "Fan 2", - "Fan 3", "Ext. fan" }; @@ -99,3 +114,7 @@ static char *icx3_temp_sensor_names[] = { enum icx3_product_id check_for_icx3(char *i2c_dev_path); void print_icx3_fans(char *i2c_dev_path); void print_icx3_temps(char *i2c_dev_path); +void get_available_fans(char *i2c_dev_path, char *fans_avail); +int open_i2c_dev(char *i2c_dev_path); +void enable_write(int enable, char *i2c_dev_path); +void set_fan(int fan, char *setting, char *i2c_dev_path); \ No newline at end of file