Files
linuxSmartie/src/aio.c
2026-08-24 22:47:26 +02:00

256 lines
7.4 KiB
C

#include "aio.h"
#include "log.h"
#include <math.h>
#include <stdio.h>
#include <string.h>
static constexpr uint16_t usb_timeout = 250;
static constexpr uint8_t buf_size = 32; // needs 32 bc of overflow issue on these aios?
static constexpr uint8_t max_curve_points = 7;
static uint8_t req[buf_size], resp[buf_size];
static const struct curve_point_t fan_curve[] = {
{ .temp = 37, .duty = 15 },
{ .temp = 40, .duty = 40 },
{ .temp = 46, .duty = 60 },
{ .temp = 50, .duty = 100 }
};
static void aio_usb_claim(libusb_device_handle *dev) {
int ret = libusb_claim_interface(dev, 0);
if (ret != 0) {
applog(LOG_ERR, "failed to claim AIO device: %s", libusb_error_name(ret));
return;
}
ret = libusb_control_transfer(dev, (LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE), 0x02, 0x02, 0, nullptr, 0, usb_timeout);
if (ret < 0)
applog(LOG_ERR, "AIO clear-to-send failed: %s", libusb_error_name(ret));
}
static void aio_usb_release(libusb_device_handle *dev) {
libusb_release_interface(dev, 0);
}
static int aio_post(libusb_device_handle *dev, const int req_len, const int resp_len) {
// applog(LOG_DBG, "writing to aio reg 0x%02x", req[0]);
int ret = 0;
ret += libusb_bulk_transfer(dev, 0x01, req, req_len, nullptr, usb_timeout);
ret += libusb_bulk_transfer(dev, 0x81, resp, resp_len, nullptr, usb_timeout);
return ret;
}
static float aio_get_water_temp(libusb_device_handle *dev) {
req[0] = 0xa9;
aio_post(dev, 1, 6);
return (float)resp[3] + (float)resp[4] / 10.0f;
}
static uint16_t aio_get_pump_speed(libusb_device_handle *dev) {
req[0] = 0x31;
if (aio_post(dev, 1, 5) != 0)
return UINT16_MAX;
return (uint16_t)(resp[3] << 8 | resp[4]);
}
static enum aio_pump_mode_t aio_get_pump_mode(libusb_device_handle *dev) {
req[0] = 0x33;
if (aio_post(dev, 1, 4) != 0)
return AIO_PUMP_MODE_UNKNOWN;
return (enum aio_pump_mode_t)resp[3];
}
static void aio_get_fan_speed(libusb_device_handle *dev, uint16_t *speeds) {
for (uint8_t i = 0; i < 2; i++) {
req[0] = 0x41;
req[1] = i;
if (aio_post(dev, 2, 6) != 0)
speeds[i] = UINT16_MAX;
speeds[i] = (uint16_t)(resp[4] << 8 | resp[5]);
}
}
static void aio_set_fan_duty(libusb_device_handle *dev, const uint8_t *duty) {
if (duty == nullptr)
return;
static uint8_t last_duty = 255;
if (duty[0] == last_duty)
return;
req[0] = 0x42;
for (uint8_t i = 0; i < 2; i++) {
req[1] = i;
req[2] = MIN(MAX(duty[i], 0), 100);
if (aio_post(dev, sizeof(req), buf_size) == 0)
last_duty = req[2];
}
}
static char *aio_get_firmware_version(libusb_device_handle *dev, char *buf) {
req[0] = 0xaa;
aio_post(dev, 1, buf_size);
sprintf(buf, "%d.%d.%d.%d", resp[3], resp[4], resp[5], resp[6]);
return buf;
}
static void aio_set_pump_mode(libusb_device_handle *dev, const enum aio_pump_mode_t mode) {
static enum aio_pump_mode_t last_mode = AIO_PUMP_MODE_UNKNOWN;
if (mode == last_mode)
return;
applog(LOG_DBG, "setting pump to %u", mode);
req[0] = 0x32;
req[1] = (uint8_t)mode;
if (aio_post(dev, 2, 6) == 0)
last_mode = mode;
}
static void aio_set_color(libusb_device_handle *dev, const enum aio_color_mode_t mode, const uint8_t speed, const struct rgb_t *colors, const uint8_t color_count) {
if (colors == nullptr) // NOLINT no its not
return;
constexpr uint8_t modes[] = {0x52, 0x55, 0x55, 0x58};
req[0] = 0x56;
req[1] = color_count;
// sort out and send color table
memcpy(&req[2], colors, color_count * 3);
if (color_count < 2 || mode == AIO_COLOR_FIXED) {
memcpy(&req[5], colors, 3); // copy the same color to the 2nd pos in the table
req[1] = 2;
}
aio_post(dev, 2 + req[1] * 3, buf_size);
// send speed, skip if fixed color
if (mode != AIO_COLOR_FIXED) {
constexpr uint8_t speed_shift[] = {0x46, 0x28, 0x0F};
constexpr uint8_t speed_pulse[] = {0x50, 0x37, 0x1E};
req[0] = 0x53;
req[1] = mode == AIO_COLOR_SHIFT ? speed_shift[speed] : speed_pulse[speed];
aio_post(dev,2, buf_size);
}
// actually set the aio to desired mode
req[0] = modes[mode];
req[1] = 0x01;
aio_post(dev, 2, buf_size);
}
static void aio_set_fan_curve(libusb_device_handle *dev, const struct curve_point_t *curve, const uint8_t count) {
if (curve == nullptr)
return;
if (count < 1 || count > max_curve_points) {
applog(LOG_ERR, "aio: curve must have 1-%d points", max_curve_points);
return;
}
struct curve_point_t padded[max_curve_points];
for (int i = 0; i < max_curve_points; i++)
padded[i] = (i < count) ? curve[i] : curve[count - 1];
// uint8_t req[16], resp[32];
req[0] = 0x40;
// setup request
for (int i = 0; i < max_curve_points; i++)
req[2 + i] = padded[i].temp;
for (int i = 0; i < max_curve_points; i++)
req[9 + i] = padded[i].duty;
// set curve for both fans
for (uint8_t i = 0; i < 2; i++) {
req[1] = i;
aio_post(dev, sizeof(req), sizeof(resp));
}
applog(LOG_INF, "set up AIO fan curve");
}
void aio_detect(struct aio_t *aio) {
libusb_init(nullptr);
aio->handle = libusb_open_device_with_vid_pid(nullptr, AIO_USB_VID, AIO_USB_PID);
if (aio->handle == nullptr) {
applog(LOG_ERR, "Can't open AIO USB device %x:%x", AIO_USB_VID, AIO_USB_PID);
return;
}
/* detaches other software that might take the device? */
if (libusb_kernel_driver_active(aio->handle, 0))
libusb_detach_kernel_driver(aio->handle, 0);
aio_usb_claim(aio->handle);
applog(LOG_INF, "found Corsair AIO device at %04x:%04x, fw ver: %s", AIO_USB_VID, AIO_USB_PID, aio_get_firmware_version(aio->handle, (char[16]){0}));
aio_set_fan_duty(aio->handle, nullptr); // so it doenst complain the func is unused
aio_set_fan_curve(aio->handle, fan_curve, sizeof(fan_curve) / sizeof(fan_curve[0]));
aio_set_pump_mode(aio->handle, AIO_PUMP_MODE_QUIET); // needed for init ig?
aio_set_color(aio->handle, AIO_COLOR_FIXED, 0, nullptr, 0); // so it doenst complain the func is unused
aio_usb_release(aio->handle);
}
void aio_update(struct aio_t *aio) {
if (aio->handle == nullptr)
return;
aio_usb_claim(aio->handle);
aio->water_temp = aio_get_water_temp(aio->handle);
aio->pump_speed = aio_get_pump_speed(aio->handle);
aio->pump_mode = aio_get_pump_mode(aio->handle);
aio_get_fan_speed(aio->handle, aio->fan_speed);
const uint16_t avg_rpm = (uint16_t)((aio->fan_speed[0] + aio->fan_speed[1]) / 2);
switch (aio->pump_mode) {
case AIO_PUMP_MODE_QUIET:
if (avg_rpm >= 500)
aio->pump_counter++;
else
aio->pump_counter = 0;
if (aio->pump_counter >= 300) {
aio->pump_counter = 0;
aio_set_pump_mode(aio->handle, AIO_PUMP_MODE_BALANCED);
}
break;
case AIO_PUMP_MODE_BALANCED:
if (avg_rpm >= 700)
aio->pump_counter++;
else if (avg_rpm <= 400)
aio->pump_counter--;
else
aio->pump_counter = 0;
if (aio->pump_counter >= 300) {
aio->pump_counter = 0;
aio_set_pump_mode(aio->handle, AIO_PUMP_MODE_EXTREME);
}
if (aio->pump_counter <= -300) {
aio->pump_counter = 0;
aio_set_pump_mode(aio->handle, AIO_PUMP_MODE_QUIET);
}
break;
case AIO_PUMP_MODE_EXTREME:
if (avg_rpm <= 500)
aio->pump_counter--;
else
aio->pump_counter = 0;
if (aio->pump_counter <= -300) {
aio->pump_counter = 0;
aio_set_pump_mode(aio->handle, AIO_PUMP_MODE_BALANCED);
}
break;
case AIO_PUMP_MODE_UNKNOWN:
break;
}
aio_usb_release(aio->handle);
}
void aio_close(const struct aio_t *aio) {
aio_set_pump_mode(aio->handle, AIO_PUMP_MODE_BALANCED);
libusb_attach_kernel_driver(aio->handle, 0); // reattach so liquidctl still works
libusb_close(aio->handle);
libusb_exit(nullptr);
applog(LOG_INF, "AIO device closed");
}