diff --git a/evga-card.c b/evga-card.c index b4742a3..7af209a 100644 --- a/evga-card.c +++ b/evga-card.c @@ -1,18 +1,22 @@ #include #include #include +#include #include "evga-card.h" #include "icx3.h" /* Search all i2c device files for ones are on a PCI device of a supported GPU, and respond with the correct iCX3 version information */ -int find_evga_gpu_i2cs(struct card_info **infos, int max_gpus) +int find_evga_gpu_i2cs(struct card_info *infos, int max_gpus) { char i2c_devices_path[NAME_MAX]; char device_path[NAME_MAX]; + char dev_file[NAME_MAX]; FILE *test_fd; + DIR *dir; + struct dirent *ent; int num_gpus = 0; unsigned short pci_vendor, pci_device, pci_subsystem_vendor, pci_subsystem_device = 0; @@ -38,114 +42,46 @@ int find_evga_gpu_i2cs(struct card_info **infos, int max_gpus) strcat(device_path, ent->d_name); /* Read the PCI info for the underlying device */ - pci_vendor = read_pci_id(device_path, "/vendor"); - pci_device = read_pci_id(device_path, "/device"); - pci_subsystem_vendor = read_pci_id(device_path, "/subsystem_vendor"); - pci_subsystem_device = read_pci_id(device_path, "/subsystem_device"); + pci_vendor = read_pci_id(device_path, "/device/vendor"); + pci_device = read_pci_id(device_path, "/device/device"); + pci_subsystem_vendor = read_pci_id(device_path, "/device/subsystem_vendor"); + pci_subsystem_device = read_pci_id(device_path, "/device/subsystem_device"); /* 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++) { + 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(device_path, "/dev/"); - strcat(device_path, ent->d_name); - if (check_for_icx3(device_path)) { - + strcpy(dev_file, "/dev/"); + strcat(dev_file, ent->d_name); + if (check_for_icx3(dev_file)) { /* Write our card info into the provided struct array */ - card_info[num_gpus]->card_name = &evga_pci_ids[i]->card_name; - card_info[num_gpus]->pci_id - card_info[num_gpus]->i2c_dev_path = calloc(strlen(device_path) + 1, sizeof(char)); - strcpy(card_info[num_gpus]->i2c_dev_path, device_path); + infos[num_gpus].card_name = evga_pci_ids[i].card_name; + infos[num_gpus].pci_id = read_nvidia_pci_address(device_path); + infos[num_gpus].i2c_dev_path = calloc(strlen(dev_file) + 1, sizeof(char)); + strcpy(infos[num_gpus].i2c_dev_path, dev_file); + num_gpus++; break; } } } - strcat(device_string, "/name"); + if (num_gpus == max_gpus) + break; - - if(strncmp(ent->d_name, "i2c-", 4) == 0) - { - strcpy(device_string, i2c_devices_path); - strcat(device_string, ent->d_name); - strcat(device_string, "/name"); - test_fd = fopen(device_string, O_RDONLY); - - if(test_fd) - { - memset(device_string, 0x00, sizeof(device_string)); - - if(read(test_fd, device_string, sizeof(device_string)) < 0) - { - printf("Failed to read i2c device name\n"); - return -1; - } - - device_string[strlen(device_string) - 1] = 0x00; - - close(test_fd); - - // Clear PCI Information - pci_vendor = 0; - pci_device = 0; - pci_subsystem_vendor = 0; - pci_subsystem_device = 0; - port_id = 0; - - // Get port ID for NVidia GPUs - sscanf(device_string, "NVIDIA i2c adapter %hu at", &port_id); - - // Get device path - strcpy(path, i2c_devices_path); - strcat(path, ent->d_name); - if(ent->d_type == DT_LNK) - { - ptr = realpath(path, NULL); - if(ptr == NULL) - continue; - - strcpy(path, ptr); - strcat(path, "/.."); - free(ptr); - } - else - { - strcat(path, "/device"); - } - ptr = path + strlen(path); - - - - if (pci_vendor == NVIDIA_VEN && pci_subsystem_vendor == EVGA_SUB_VEN) { - - strcpy(device_string, "/dev/"); - strcat(device_string, ent->d_name); - test_fd = open(device_string, O_RDWR); - - if (test_fd < 0) - { - printf("Failed to open device file %s, are you root or have permissions for i2c?\n", device_string); - return -1; - } else { - } else { - printf("Bad FW version read %s\n", device_string); - } - } - } - } - } - } } + + return num_gpus; } +/* Read a 16-bit unsigned int from a pci id file */ unsigned short read_pci_id(char *device_path, char *field) { - char *buf[16]; + char buf[16]; char file_path[NAME_MAX]; strcpy(file_path, device_path); @@ -166,4 +102,23 @@ unsigned short read_pci_id(char *device_path, char *field) return (unsigned short)strtoul(buf, NULL, 16); } - +char *read_nvidia_pci_address(char *device_path) +{ + char file_path[NAME_MAX]; + + char *ret = calloc(16 + 1, sizeof(char)); /* assuming pci ids could look as large as 00000000:0C:00.0 */ + + strcpy(file_path, device_path); + strcat(file_path, "/name"); + + FILE *fp = fopen(file_path, "r"); + + if (fp == NULL) + return NULL; + + fscanf(fp, "NVIDIA i2c adapter %*u at %16s", ret); + + fclose(fp); + + return ret; +} diff --git a/evga-card.h b/evga-card.h index ba98308..ad2f635 100644 --- a/evga-card.h +++ b/evga-card.h @@ -1,3 +1,5 @@ +#pragma once + #define NVIDIA_VEN 0x10DE #define NVIDIA_RTX3060_DEV 0x2503 @@ -80,16 +82,11 @@ #define EVGA_RTX3090TI_FTW3_GAMING_SUB_DEV 0x4983 #define EVGA_RTX3090TI_FTW3_ULTRA_GAMING_SUB_DEV 0x4985 -#define MAX_GPUS 16 - -int find_evga_gpu_i2cs(struct card_info **infos, int max_gpus); -unsigned short read_pci_id(char *path); - -struct card_info ( +struct card_info { char *card_name; char *pci_id; char *i2c_dev_path; -); +}; struct gpu_pci_info { char *card_name; @@ -99,7 +96,7 @@ struct gpu_pci_info { unsigned short subdevice_id; }; -struct gpu_pci_info evga_pci_ids[] = +static struct gpu_pci_info evga_pci_ids[] = { {"EVGA GeForce RTX 3060 Ti FTW3 Gaming" , NVIDIA_VEN, NVIDIA_RTX3060TI_DEV, EVGA_SUB_VEN, EVGA_RTX3060TI_FTW3_GAMING_SUB_DEV }, {"EVGA GeForce RTX 3060 Ti FTW3 Ultra" , NVIDIA_VEN, NVIDIA_RTX3060TI_DEV, EVGA_SUB_VEN, EVGA_RTX3060TI_FTW3_ULTRA_SUB_DEV }, @@ -157,5 +154,9 @@ struct gpu_pci_info evga_pci_ids[] = {"EVGA GeForce RTX 3090 K|NGP|N Hydro Copper" , NVIDIA_VEN, NVIDIA_RTX3090_DEV, EVGA_SUB_VEN, EVGA_RTX3090_KINGPIN_HC_SUB_DEV }, {"EVGA GeForce RTX 3090 Ti FTW3 Black Gaming" , NVIDIA_VEN, NVIDIA_RTX3090TI_DEV, EVGA_SUB_VEN, EVGA_RTX3090TI_FTW3_BLACK_SUB_DEV }, {"EVGA GeForce RTX 3090 Ti FTW3 Gaming" , NVIDIA_VEN, NVIDIA_RTX3090TI_DEV, EVGA_SUB_VEN, EVGA_RTX3090TI_FTW3_GAMING_SUB_DEV }, - {"EVGA GeForce RTX 3090 Ti FTW3 Ultra Gaming" , NVIDIA_VEN, NVIDIA_RTX3090TI_DEV, EVGA_SUB_VEN, EVGA_RTX3090TI_FTW3_ULTRA_GAMING_SUB_DEV }, -} \ No newline at end of file + {"EVGA GeForce RTX 3090 Ti FTW3 Ultra Gaming" , NVIDIA_VEN, NVIDIA_RTX3090TI_DEV, EVGA_SUB_VEN, EVGA_RTX3090TI_FTW3_ULTRA_GAMING_SUB_DEV } +}; + +int find_evga_gpu_i2cs(struct card_info *infos, int max_gpus); +unsigned short read_pci_id(char *device_path, char *field); +char *read_nvidia_pci_address(char *device_path); diff --git a/evga-icx.c b/evga-icx.c index b2002ee..631d24e 100644 --- a/evga-icx.c +++ b/evga-icx.c @@ -5,17 +5,32 @@ #include #include - #include "icx3.h" -#include "nvidia_pci.h" +#include "evga-card.h" + +#define MAX_GPUS 16 int main (int argc, char **argv) { - int *gpus[MAX_GPUS]; - if (find_evga_gpu_i2c(gpus) == -1) { + struct card_info gpus[MAX_GPUS]; + int num_gpus; + + num_gpus = find_evga_gpu_i2cs(gpus, MAX_GPUS); + + if (num_gpus == -1) { + printf("Error scanning I2C devices\n"); + return -1; + } else if (num_gpus == 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"); } + } - \ No newline at end of file + diff --git a/icx3.c b/icx3.c index b56290e..58732c6 100644 --- a/icx3.c +++ b/icx3.c @@ -1,72 +1,163 @@ #include -#include #include +#include +#include +#include +#include #include "icx3.h" -void *print_icx3_temp(int fp) +/* Check an I2C device file for an ICX3 controller, returns the product id, or ICX3_PRODUCT_NONE on bad read. */ +enum icx3_product_id check_for_icx3(char *i2c_dev_path) { - int read_result; - char buff[1024]; - struct icx3_fancontrol *fanstatus; + char data[I2C_SMBUS_BLOCK_MAX] = {}; + int fd, read_result; + struct icx3_info *temp_info; + + fd = open(i2c_dev_path, O_RDONLY); + if (!fd) + 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) { + temp_info = (struct icx3_info*)&data; + if (temp_info->slave_address == ICX3_I2C_ADDR) + return temp_info->product_id; + } + + return ICX3_PRODUCT_NONE; +} + +void print_icx3_fans(char *i2c_dev_path) +{ + char data[I2C_SMBUS_BLOCK_MAX] = {}; + int fd, read_result; + + 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}; + int product_id = check_for_icx3(i2c_dev_path); + + /* From ICX3TotalFanCtrl.cs */ + switch(product_id) { + case ICX3_PRODUCT_E221_E222_XC3: + case ICX3_PRODUCT_E229_XC3: + case ICX3_PRODUCT_E222_XC3_LPC54113: + case ICX3_PRODUCT_E229_XC3_LPC54113: + case ICX3_PRODUCT_E221_XC3_STM32L431: + case ICX3_PRODUCT_E229_E237_XC3_STM32L431: + /* XC3, default plus fan 3 */ + fans_avail[0] = 1; + fans_avail[1] = 1; + fans_avail[2] = 1; + break; + case ICX3_PRODUCT_E227_E228_FTW3: + case ICX3_PRODUCT_E230_FTW3: + case ICX3_PRODUCT_E227_E228_FTW3_LPC54113: + case ICX3_PRODUCT_E230_FTW3_LPC54113: + case ICX3_PRODUCT_E230_E238_FTW3_STM32L431: + case ICX3_PRODUCT_E250_FTW3: + /* FTW3, default plus fan 3 and external fan */ + fans_avail[0] = 1; + fans_avail[1] = 1; + fans_avail[2] = 1; + fans_avail[3] = 1; + break; + case ICX3_PRODUCT_E223_KINGPIN_HYBRID: + case ICX3_PRODUCT_E227_E228_FTW3_HYBRID: + case ICX3_PRODUCT_E251_KINGPIN_HYBRID: + case ICX3_PRODUCT_E227_E228_FTW3_HYBRID_LPC54113: + case ICX3_PRODUCT_E250_FTW3_HYBRID: + /* KINGPIN, default and external */ + fans_avail[0] = 1; + fans_avail[1] = 1; + fans_avail[3] = 1; + break; + case ICX3_PRODUCT_E227_E228_FTW3_HC: + case ICX3_PRODUCT_E223_KINGPIN_HC: + case ICX3_PRODUCT_E251_KINGPIN_HC: + /* External fan only */ + fans_avail[3] = 1; + break; + default: + fans_avail[0] = 1; + fans_avail[1] = 1; + break; + case ICX3_PRODUCT_E221_E222_XC3_HC: + /* 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++) { - read_result = i2c_smbus_read_i2c_block_data(fp, ICX3_REG_FANCONTROL + i, ICX3_FANCONTROL_SIZE, buff); + 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) { - return 0; + close(fd); + return; } - fanstatus = (struct icx3_fancontrol*) &buff; + fan_status = (struct icx3_fan_control*) &data; printf("%s: %d RPM (%d/%d%%, %s)\n", icx3_fan_names[i], - fanstatus->rpm_status, - fanstatus->duty_status, - fanstatus->duty, - icx3_fan_mode_names[fanstatus->fanmode] + 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) +{ + char data[I2C_SMBUS_BLOCK_MAX] = {}; + int fd, read_result; + struct icx3_temp_sensors *temp_sensors; - struct icx3_tempsensors *tempsensors; - read_result = i2c_smbus_read_i2c_block_data(fp, ICX3_REG_TEMPSENSOR, ICX3_TEMPSENSOR_SIZE, buff); - if (read_result != ICX3_TEMPSENSOR_SIZE) { - return 0; + 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; } - tempsensors = (struct icx3_tempsensors*) &buff; + + 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)(tempsensors->data[2*i]); + /* endian swap */ + cur_data = (short)(temp_sensors->data[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); } -} - -/* Check an I2C device file for an ICX3 controller, returs the product id. */ -enum icx3_product_id check_for_icx3(char *i2c_dev_path) -{ - char data[I2C_SMBUS_BLOCK_MAX] = {}; - int test_fd, read_result; - struct icx3_info *temp_info; - - test_fd = fopen(i2c_dev_path, O_RDONLY); - if (!test_fd) - return ICX3_NONE; - - if (ioctl(test_fd, I2C_SLAVE, ICX3_I2C_ADDR) < 0) { - close(test_fd); - return ICX3_NONE; - } - - read_result = i2c_smbus_read_i2c_block_data(test_fd, ICX3_REG_READINFORMATION, ICX3_READINFORMATION_SIZE, buff); - if (read_result == ICX3_READINFORMATION_SIZE) { - temp_info = (struct *icx3_info)&buff; - if (temp_info->slave_address == ICX3_I2C_ADDR) - return temp_info->product_id; - } - - return ICX3_NONE; - -} +} \ No newline at end of file diff --git a/icx3.h b/icx3.h index 3771162..dd566d8 100644 --- a/icx3.h +++ b/icx3.h @@ -1,3 +1,5 @@ +#pragma once + #define ICX3_I2C_ADDR 0x2D #define ICX3_REG_FANCONTROL 80 @@ -11,8 +13,7 @@ #define ICX3_MAX_FANS 4 #define ICX3_NUM_TEMP_SENSORS 9 - -struct icx3_fancontrol { +struct icx3_fan_control { unsigned char length; unsigned char fanmode; unsigned short rpm_offset; @@ -21,12 +22,13 @@ struct icx3_fancontrol { unsigned short rpm_status; }; -struct icx3_tempsensors { +struct icx3_temp_sensors { unsigned char length; unsigned char data[18]; }; struct icx3_info { + unsigned char length; unsigned char reserved; unsigned char slave_address; unsigned char product_id; @@ -35,45 +37,45 @@ struct icx3_info { }; enum icx3_product_id { - ICX3_IAP = 160, - ICX3_E227_E228_FTW3 = 2, - ICX3_E230_FTW3 = 4, - ICX3_E227_E228_FTW3_HYBRID = 8, - ICX3_E227_E228_FTW3_HC = 9, - ICX3_E221_E222_XC3 = 1, - ICX3_E229_XC3 = 5, - ICX3_E221_E222_XC3_HYBRID = 6, - ICX3_E221_E222_XC3_HC = 7, - ICX3_IAP_KINGPIN = 161, - ICX3_E223_KINGPIN_HYBRID = 3, - ICX3_E223_KINGPIN_HC = 10, - ICX3_IAP_KINGPIN_E251 = 165, - ICX3_E251_KINGPIN_HYBRID = 19, - ICX3_E251_KINGPIN_HC = 26, - ICX3_IAP_LPC5516 = 164, - ICX3_E250_FTW3 = 50, - ICX3_E250_FTW3_HYBRID = 56, - ICX3_IAP_LPC54113 = 162, - ICX3_E227_E228_FTW3_LPC54113 = 18, - ICX3_E227_E228_FTW3_HYBRID_LPC54113 = 24, - ICX3_E230_FTW3_LPC54113 = 20, - ICX3_E222_XC3_LPC54113 = 17, - ICX3_E229_XC3_LPC54113 = 21, - ICX3_IAP_STM32L431 = 163, - ICX3_E230_E238_FTW3_STM32L431 = 36, - ICX3_E221_XC3_STM32L431 = 33, - ICX3_E229_E237_XC3_STM32L431 = 37, - ICX3_NONE = 0 -} + ICX3_PRODUCT_IAP = 160, + ICX3_PRODUCT_E227_E228_FTW3 = 2, + ICX3_PRODUCT_E230_FTW3 = 4, + ICX3_PRODUCT_E227_E228_FTW3_HYBRID = 8, + ICX3_PRODUCT_E227_E228_FTW3_HC = 9, + ICX3_PRODUCT_E221_E222_XC3 = 1, + ICX3_PRODUCT_E229_XC3 = 5, + ICX3_PRODUCT_E221_E222_XC3_HYBRID = 6, + ICX3_PRODUCT_E221_E222_XC3_HC = 7, + ICX3_PRODUCT_IAP_KINGPIN = 161, + ICX3_PRODUCT_E223_KINGPIN_HYBRID = 3, + ICX3_PRODUCT_E223_KINGPIN_HC = 10, + ICX3_PRODUCT_IAP_KINGPIN_E251 = 165, + ICX3_PRODUCT_E251_KINGPIN_HYBRID = 19, + ICX3_PRODUCT_E251_KINGPIN_HC = 26, + ICX3_PRODUCT_IAP_LPC5516 = 164, + ICX3_PRODUCT_E250_FTW3 = 50, + ICX3_PRODUCT_E250_FTW3_HYBRID = 56, + ICX3_PRODUCT_IAP_LPC54113 = 162, + ICX3_PRODUCT_E227_E228_FTW3_LPC54113 = 18, + ICX3_PRODUCT_E227_E228_FTW3_HYBRID_LPC54113 = 24, + ICX3_PRODUCT_E230_FTW3_LPC54113 = 20, + ICX3_PRODUCT_E222_XC3_LPC54113 = 17, + ICX3_PRODUCT_E229_XC3_LPC54113 = 21, + ICX3_PRODUCT_IAP_STM32L431 = 163, + ICX3_PRODUCT_E230_E238_FTW3_STM32L431 = 36, + ICX3_PRODUCT_E221_XC3_STM32L431 = 33, + ICX3_PRODUCT_E229_E237_XC3_STM32L431 = 37, + ICX3_PRODUCT_NONE = 0 +}; -const char *icx3_fan_names[] = { +static char *icx3_fan_names[] = { "Fan 1", "Fan 2", "Fan 3", "Ext. fan" }; -const char *icx3_fan_mode_names[] = { +static char *icx3_fan_mode_names[] = { "Default", "Auto", "Manual", @@ -82,7 +84,7 @@ const char *icx3_fan_mode_names[] = { "Mode 3" }; -const char *icx3_temp_sensor_names[] = { +static char *icx3_temp_sensor_names[] = { "GPU2", "MEM1", "MEM2", @@ -94,4 +96,6 @@ const char *icx3_temp_sensor_names[] = { "PWR5", }; -enum icx3_product_id check_for_icx3(char *i2c_dev_path); \ No newline at end of file +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); diff --git a/makefile b/makefile index c530f65..4444197 100644 --- a/makefile +++ b/makefile @@ -1,13 +1,16 @@ .PHONY : clean debug +OBJS = evga-icx.o evga-card.o icx3.o LDLIBS = -li2c -objects = evga-icx.o +CFLAGS = -MD -evga-icx : $(objects) +evga-icx : $(OBJS) debug : CFLAGS += -g -O0 debug : evga-icx clean : - rm evga-icx $(objects) - \ No newline at end of file + rm evga-icx $(OBJS) + rm *.d + +-include $(OBJS:.o=.d) \ No newline at end of file