From: David Gwynne Subject: Re: crsri(4) for monitoring corsair rm/hx i series power supplies To: Nick Owens Cc: tech@openbsd.org Date: Tue, 26 Mar 2024 14:00:17 +1000 On Mon, Mar 25, 2024 at 08:51:05PM -0700, Nick Owens wrote: > On Mon, Mar 25, 2024 at 6:40???PM David Gwynne wrote: > > > > On Tue, Mar 26, 2024 at 11:11:34AM +1000, David Gwynne wrote: > > > i'm trying to put a new box together to run at home, and was interested > > > in keeping an eye on the power consumption of the system. the most > > > common way to do this is with a power monitoring plug thing, but it > > > seems like a no brainer to me that this functionality would be part of > > > the actual power supply in the computer. > > > > > > turns out the no brainer is actually quite niche, and there's only a > > > couple of manufacturers that do it and only on a subset of their > > > products. corsair is one of those, and one of these psus was on ebay so > > > i grabbed it. > > > > > > the psu has a usb socket which you can plug into your system, and then > > > you can ask it for information. > > > > > > the following is a fairly quick and dirty driver to talk to my psu. it's > > > based on information figured out by Wilken Gottwalt, as per > > > https://github.com/wgottwalt/corsair-psu/tree/main. > > > > > > i say it's quick and dirty cos it won't cope with the usb device being > > > unplugged while the sensors are being read, and apparently different > > > PSUs can present different subsets of the possible information. eg, mine > > > does not appear to know how much current i'm pulling from mains, and > > > doesn't do any power calculations. i should probably omit those values > > > if i detect that situation. > > > > > > these feel like things we could work on in the tree, if the driver is > > > acceptibe in the first place. > > > > > > the readings are presented as kstats like this: > > > > > > crsri0:0:corsair-psu:0 > > > product: RM850i > > > vrm-temp: 47.00 degC > > > case-temp: 41.25 degC > > > fan-speed: 0 > > > input-volts: 230.00 VAC > > > input-current: 0.000 A > > > 12v-volts: 12.06 VDC > > > 12v-current: 1.250 A > > > 12v-power: 0.000 W > > > 5v-volts: 5.03 VDC > > > 5v-current: 1.938 A > > > 5v-power: 0.000 W > > > 3.3v-volts: 3.33 VDC > > > 3.3v-current: 0.688 A > > > 3.3v-power: 0.000 W > > > > jmatthew@ pointed out that i forgot the diff > > > > 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) > > is not referencing mul intentional? no, it's supposed to be used where i have 1000000 below... > > +{ > > + 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 > >