Index | Thread | Search

From:
Nick Owens <mischief@offblast.org>
Subject:
Re: crsri(4) for monitoring corsair rm/hx i series power supplies
To:
David Gwynne <david@gwynne.id.au>
Cc:
tech@openbsd.org
Date:
Mon, 25 Mar 2024 20:51:05 -0700

Download raw body.

Thread
On Mon, Mar 25, 2024 at 6:40 PM David Gwynne <david@gwynne.id.au> 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 <dlg@openbsd.org>
> + *
> + * 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 <sys/param.h>
> +#include <sys/systm.h>
> +#include <sys/kernel.h>
> +#include <sys/device.h>
> +#include <sys/kstat.h>
> +
> +#include <dev/usb/usb.h>
> +#include <dev/usb/usbhid.h>
> +
> +#include <dev/usb/usbdi.h>
> +#include <dev/usb/usbdevs.h>
> +#include <dev/usb/uhidev.h>
> +
> +#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?

> +{
> +       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
>