Index: crsri.c =================================================================== RCS file: crsri.c diff -N crsri.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ crsri.c 26 Mar 2024 00:54:50 -0000 @@ -0,0 +1,410 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2024 David Gwynne + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "kstat.h" + +#if NKSTAT == 0 +#error crsri(4) is pointless without kstat(4) +#endif + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define CRSRI_CMD_SELECT_RAIL 0x00 + +#define CRSRI_CMD_FAN_PWM 0x3b +#define CRSRI_CMD_RAIL_V_HIGH 0x40 +#define CRSRI_CMD_RAIL_V_LOW 0x44 +#define CRSRI_CMD_RAIL_A_HIGH 0x46 +#define CRSRI_CMD_C_HIGH 0x4f +#define CRSRI_CMD_IN_V 0x88 +#define CRSRI_CMD_IN_A 0x89 +#define CRSRI_CMD_RAIL_V 0x8b +#define CRSRI_CMD_RAIL_A 0x8c +#define CRSRI_CMD_VRM_C 0x8d +#define CRSRI_CMD_CASE_C 0x8e +#define CRSRI_CMD_FAN 0x90 +#define CRSRI_CMD_RAIL_W 0x96 +#define CRSRI_CMD_STR_VENDOR 0x99 +#define CRSRI_CMD_STR_PRODUCT 0x9a +#define CRSRI_CMD_TOTAL_UPTIME 0xd1 +#define CRSRI_CMD_UPTIME 0xd2 +#define CRSRI_CMD_TOTAL_W 0xee +#define CRSRI_CMD_FAN_PWM_EN 0xf0 +#define CRSRI_CMD_INIT 0xfe + +#define CRSRI_KS_READ_RATELIMIT 5 /* seconds */ + +#define CRSRI_RAIL_12V 0 +#define CRSRI_RAIL_5V 1 +#define CRSRI_RAIL_3V3 2 +#define CRSRI_RAIL_COUNT 3 + +static const char *crsri_rail_names[CRSRI_RAIL_COUNT] = { + [CRSRI_RAIL_12V] = "12v", + [CRSRI_RAIL_5V] = "5v", + [CRSRI_RAIL_3V3] = "3.3v", +}; + +struct crsri_kstats { + struct kstat_kv product; + + struct kstat_kv vrm_c; + struct kstat_kv case_c; + struct kstat_kv fan; + + struct kstat_kv in_v; + struct kstat_kv in_a; + + struct { + struct kstat_kv v; + struct kstat_kv a; + struct kstat_kv w; + } rails[CRSRI_RAIL_COUNT]; +}; + +struct crsri_softc { + struct uhidev sc_hdev; + struct usbd_device *sc_udev; + + struct rwlock sc_lock; + uint8_t sc_buf[64]; + unsigned int sc_state; +#define CRSRI_S_IDLE 0 +#define CRSRI_S_WAITING 1 +#define CRSRI_S_DONE 2 + + struct rwlock sc_kslock; + struct crsri_kstats sc_kstats; + struct kstat *sc_ks; +}; + +#define DEVNAME(_sc) ((_sc)->sc_hdev.sc_dev.dv_xname) + +static const struct usb_devno crsri_devs[] = { + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_HX550I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_HX650I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_HX750I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_HX850I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_HX1000I_2022 }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_HX1000I_2023 }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_HX1200I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_HX1500I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_RM550I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_RM650I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_RM750I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_RM850I }, + { USB_VENDOR_CORSAIR, USB_PRODUCT_CORSAIR_RM1000I }, +}; + +static int crsri_match(struct device *, void *, void *); +static void crsri_attach(struct device *, struct device *, void *); +static int crsri_detach(struct device *, int); + +static void crsri_intr(struct uhidev *, void *, u_int); + +static int crsri_report(struct crsri_softc *, uint8_t, uint8_t, uint8_t); +static int crsri_kstat_read(struct kstat *); + +const struct cfattach crsri_ca = { + sizeof(struct crsri_softc), crsri_match, crsri_attach, crsri_detach, +}; + +struct cfdriver crsri_cd = { + NULL, "crsri", DV_DULL +}; + +static int +crsri_match(struct device *parent, void *match, void *aux) +{ + struct uhidev_attach_arg *uha = aux; + + if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha)) + return (UMATCH_NONE); + + if (usb_lookup(crsri_devs, uha->uaa->vendor, uha->uaa->product) == NULL) + return (UMATCH_NONE); + + return (UMATCH_VENDOR_PRODUCT); +} + +static void +crsri_attach(struct device *parent, struct device *self, void *aux) +{ + struct crsri_softc *sc = (struct crsri_softc *)self; + struct uhidev_attach_arg *uha = aux; + struct usbd_device *dev = uha->parent->sc_udev; + int repid = uha->reportid; + int size; + void *desc; + int rv; + struct kstat *ks; + unsigned int i; + + rw_init(&sc->sc_lock, "crsri"); + rw_init(&sc->sc_kslock, "crsriks"); + + sc->sc_udev = dev; + sc->sc_hdev.sc_intr = crsri_intr; + sc->sc_hdev.sc_parent = uha->parent; + sc->sc_hdev.sc_report_id = repid; + + uhidev_get_report_desc(uha->parent, &desc, &size); + sc->sc_hdev.sc_isize = hid_report_size(desc, size, hid_input, repid); + sc->sc_hdev.sc_osize = hid_report_size(desc, size, hid_output, repid); + sc->sc_hdev.sc_fsize = hid_report_size(desc, size, hid_feature, repid); + + if (uhidev_open(&sc->sc_hdev) != 0) { + printf(": unable to open interrupt pipe\n"); + return; + } + + /* do this to wire crsri_intr up now */ + if (uhidev_set_report_dev(uha->parent, &sc->sc_hdev, repid) != 0) { + printf(": unable to set report\n"); + return; + } + + rw_enter_write(&sc->sc_lock); + + rv = crsri_report(sc, CRSRI_CMD_INIT, 3, 0); + if (rv == -1) { + printf(": init failed\n"); + goto unlock; + } + + rv = crsri_report(sc, 3, CRSRI_CMD_STR_PRODUCT, 0); + if (rv == -1) { + printf(": query product string failed\n"); + goto unlock; + } + printf(": %s\n", sc->sc_buf + 2); + rw_exit_write(&sc->sc_lock); + + ks = kstat_create(DEVNAME(sc), 0, "corsair-psu", 0, KSTAT_T_KV, 0); + if (ks == NULL) { + printf("%s: unable to create corsair-psu kstats\n", + DEVNAME(sc)); + return; + } + + kstat_kv_init(&sc->sc_kstats.product, "product", KSTAT_KV_T_ISTR); + strlcpy(kstat_kv_istr(&sc->sc_kstats.product), sc->sc_buf + 2, + sizeof(kstat_kv_istr(&sc->sc_kstats.product))); + + kstat_kv_init(&sc->sc_kstats.vrm_c, "vrm-temp", KSTAT_KV_T_TEMP); + kstat_kv_init(&sc->sc_kstats.case_c, "case-temp", KSTAT_KV_T_TEMP); + kstat_kv_init(&sc->sc_kstats.fan, "fan-speed", KSTAT_KV_T_UINT64); + kstat_kv_init(&sc->sc_kstats.in_v, "input-volts", KSTAT_KV_T_VOLTS_AC); + kstat_kv_init(&sc->sc_kstats.in_a, "input-current", KSTAT_KV_T_AMPS); + + for (i = 0; i < CRSRI_RAIL_COUNT; i++) { + char key[KSTAT_KV_NAMELEN]; + + snprintf(key, sizeof(key), "%s-volts", crsri_rail_names[i]); + kstat_kv_init(&sc->sc_kstats.rails[i].v, + key, KSTAT_KV_T_VOLTS_DC); + + snprintf(key, sizeof(key), "%s-current", crsri_rail_names[i]); + kstat_kv_init(&sc->sc_kstats.rails[i].a, + key, KSTAT_KV_T_AMPS); + + snprintf(key, sizeof(key), "%s-power", crsri_rail_names[i]); + kstat_kv_init(&sc->sc_kstats.rails[i].w, + key, KSTAT_KV_T_WATTS); + } + + ks->ks_softc = sc; + ks->ks_data = &sc->sc_kstats; + ks->ks_datalen = sizeof(sc->sc_kstats); + ks->ks_read = crsri_kstat_read; + ks->ks_interval.tv_sec = CRSRI_KS_READ_RATELIMIT; + kstat_set_wlock(ks, &sc->sc_kslock); + + kstat_install(ks); + + sc->sc_ks = ks; + + return; + +unlock: + rw_exit_write(&sc->sc_lock); +} + +static int +crsri_report(struct crsri_softc *sc, uint8_t b0, uint8_t b1, uint8_t b2) +{ + int rv; + + if (sc->sc_state != CRSRI_S_IDLE) + return (-1); + + memset(sc->sc_buf, 0, sizeof(sc->sc_buf)); + sc->sc_buf[0] = b0; + sc->sc_buf[1] = b1; + sc->sc_buf[2] = b2; + + KERNEL_LOCK(); /* sigh */ + rv = uhidev_set_report(sc->sc_hdev.sc_parent, UHID_OUTPUT_REPORT, + sc->sc_hdev.sc_report_id, sc->sc_buf, sizeof(sc->sc_buf)); + KERNEL_UNLOCK(); + if (rv == -1) + return (-1); + + /* wait ack */ + sc->sc_state = CRSRI_S_WAITING; + do { + rwsleep_nsec(sc->sc_buf, &sc->sc_lock, 0, + "crsri", MSEC_TO_NSEC(1000)); + } while (sc->sc_state != CRSRI_S_DONE); + + sc->sc_state = CRSRI_S_IDLE; + + return (0); +} + +static int +crsri_detach(struct device *self, int flags) +{ + struct crsri_softc *sc = (struct crsri_softc *)self; + struct kstat *ks = sc->sc_ks; + + if (ks != NULL) { + kstat_remove(ks); + sc->sc_ks = NULL; + kstat_destroy(ks); + } + + if (sc->sc_hdev.sc_state & UHIDEV_OPEN) + uhidev_close(&sc->sc_hdev); + + return (0); +} + +static void +crsri_intr(struct uhidev *uself, void *ibuf, u_int len) +{ + struct crsri_softc *sc = (struct crsri_softc *)uself; + int wake = 0; + + if (len > sizeof(sc->sc_buf)) + len = sizeof(sc->sc_buf); + + rw_enter_write(&sc->sc_lock); + if (sc->sc_state == CRSRI_S_WAITING) { + memcpy(sc->sc_buf, ibuf, len); + sc->sc_state = CRSRI_S_DONE; + wake = 1; + } + rw_exit_write(&sc->sc_lock); + + if (wake) + wakeup(sc->sc_buf); +} + +static uint64_t +crsri_val(uint16_t u16, int64_t mul) +{ + int16_t s16 = u16; + int base = s16 >> 11; + int64_t val; + + s16 = u16 << 5; + val = (s16 >> 5) * 1000000; + + if (base >= 0) + val <<= base; + else + val >>= -base; + + return (val); +} + +static uint64_t +crsri_temp(uint16_t u16) +{ + return (crsri_val(u16, 1000000) + 273150000); +} + +static int +crsri_kstat_read(struct kstat *ks) +{ + struct crsri_softc *sc = ks->ks_softc; + struct timespec now, diff; + uint16_t temp; + unsigned int i; + + getnanouptime(&now); + timespecsub(&now, &ks->ks_updated, &diff); + if (diff.tv_sec < ks->ks_interval.tv_sec) + return (0); + + rw_enter_write(&sc->sc_lock); + crsri_report(sc, 3, CRSRI_CMD_VRM_C, 0); + temp = ((uint16_t)sc->sc_buf[2] << 0) | ((uint16_t)sc->sc_buf[3] << 8); + kstat_kv_temp(&sc->sc_kstats.vrm_c) = crsri_temp(temp); + + crsri_report(sc, 3, CRSRI_CMD_CASE_C, 0); + temp = ((uint16_t)sc->sc_buf[2] << 0) | ((uint16_t)sc->sc_buf[3] << 8); + kstat_kv_temp(&sc->sc_kstats.case_c) = crsri_temp(temp); + + crsri_report(sc, 3, CRSRI_CMD_FAN, 0); + temp = ((uint16_t)sc->sc_buf[2] << 0) | ((uint16_t)sc->sc_buf[3] << 8); + kstat_kv_u64(&sc->sc_kstats.fan) = crsri_val(temp, 1); + + crsri_report(sc, 3, CRSRI_CMD_IN_V, 0); + temp = ((uint16_t)sc->sc_buf[2] << 0) | ((uint16_t)sc->sc_buf[3] << 8); + kstat_kv_volts(&sc->sc_kstats.in_v) = crsri_val(temp, 1000000); + + crsri_report(sc, 3, CRSRI_CMD_IN_A, 0); + temp = ((uint16_t)sc->sc_buf[2] << 0) | ((uint16_t)sc->sc_buf[3] << 8); + kstat_kv_amps(&sc->sc_kstats.in_a) = crsri_val(temp, 1000000); + + for (i = 0; i < CRSRI_RAIL_COUNT; i++) { + crsri_report(sc, 2, CRSRI_CMD_SELECT_RAIL, i); + + crsri_report(sc, 3, CRSRI_CMD_RAIL_V, 0); + temp = + ((uint16_t)sc->sc_buf[2] << 0) | + ((uint16_t)sc->sc_buf[3] << 8); + kstat_kv_volts(&sc->sc_kstats.rails[i].v) = + crsri_val(temp, 1000000); + + crsri_report(sc, 3, CRSRI_CMD_RAIL_A, 0); + temp = + ((uint16_t)sc->sc_buf[2] << 0) | + ((uint16_t)sc->sc_buf[3] << 8); + kstat_kv_watts(&sc->sc_kstats.rails[i].a) = + crsri_val(temp, 1000000); + } + rw_exit_write(&sc->sc_lock); + + ks->ks_updated = now; + + return (0); +} Index: files.usb =================================================================== RCS file: /cvs/src/sys/dev/usb/files.usb,v retrieving revision 1.150 diff -u -p -r1.150 files.usb --- files.usb 11 Nov 2022 06:48:38 -0000 1.150 +++ files.usb 26 Mar 2024 00:54:50 -0000 @@ -502,3 +502,8 @@ file dev/usb/uhidpp.c uhidpp device ucc: hid, hidcc, wskbddev attach ucc at uhidbus file dev/usb/ucc.c ucc + +# Corsair PSU +device crsri: hid +attach crsri at uhidbus +file dev/usb/crsri.c crsri Index: usbdevs =================================================================== RCS file: /cvs/src/sys/dev/usb/usbdevs,v retrieving revision 1.760 diff -u -p -r1.760 usbdevs --- usbdevs 27 Nov 2023 20:03:50 -0000 1.760 +++ usbdevs 26 Mar 2024 00:54:51 -0000 @@ -1471,6 +1471,19 @@ product COREGA FETHER_USB_TXC 0x9601 FEt /* Corsair products */ product CORSAIR CP210X 0x1c00 CP210X +product CORSAIR HX550I 0x1c03 HX550i +product CORSAIR HX650I 0x1c04 HX650i +product CORSAIR HX750I 0x1c05 HX750i +product CORSAIR HX850I 0x1c06 HX850i +product CORSAIR HX1000I_2022 0x1c07 HX1000i 2022 +product CORSAIR HX1200I 0x1c08 HX1200i +product CORSAIR RM550I 0x1c09 RM550i +product CORSAIR RM650I 0x1c0a RM650i +product CORSAIR RM750I 0x1c0b RM750i +product CORSAIR RM850I 0x1c0c RM850i +product CORSAIR RM1000I 0x1c0d RM1000i +product CORSAIR HX1000I_2023 0x1c1e HX1000i 2023 +product CORSAIR HX1500I 0x1c1f HX1500i /* Creative Labs products */ product CREATIVE NOMAD_II 0x1002 Nomad II