Added fan controls
This commit is contained in:
parent
bcd4457c2f
commit
3a3fa98eab
10
evga-card.c
10
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/");
|
||||
|
@ -1,5 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define NVIDIA_VEN 0x10DE
|
||||
|
||||
#define NVIDIA_RTX3060_DEV 0x2503
|
||||
|
117
evga-icx.c
117
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");
|
||||
}
|
||||
|
||||
|
218
icx3.c
218
icx3.c
@ -4,6 +4,8 @@
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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; i<ICX3_NUM_TEMP_SENSORS; 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);
|
||||
}
|
||||
}
|
||||
|
||||
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; i<ICX3_NUM_TEMP_SENSORS; 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
25
icx3.h
25
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);
|
Loading…
x
Reference in New Issue
Block a user