Index | Thread | Search

From:
David Gwynne <david@gwynne.id.au>
Subject:
Re: crsri(4) for monitoring corsair rm/hx i series power supplies
To:
tech@openbsd.org
Date:
Tue, 26 Mar 2024 11:35:23 +1000

Download raw body.

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