hello.
blambert@ suggested me to write a driver for exposing usb-based
uninterruptable power systems devices data as sysctl(8) sensors, thus
enabling people to write "shutdown policies" using sensorsd(8), which
can replaces apcupsd in some cases.
I received some feedback from reyk@ and blambert@, hope it's now ok for
tech@ comments.
-a
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a43f9f5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+**.swp
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index beaffb8..76b20e7 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -61,7 +61,7 @@ MAN= aac.4 ac97.4 acphy.4 \
uk.4 ukbd.4 \
ukphy.4 ulpt.4 umass.4 umbg.4 umct.4 umidi.4 umodem.4 ums.4 umsm.4 \
unix.4 uow.4 uoaklux.4 uoakrh.4 uoakv.4 \
- upgt.4 upl.4 uplcom.4 ural.4 urio.4 url.4 urlphy.4 \
+ upd.4 upgt.4 upl.4 uplcom.4 ural.4 urio.4 url.4 urlphy.4 \
urndis.4 urtw.4 urtwn.4 usb.4 usbf.4 uslcom.4 usps.4 \
uthum.4 uticom.4 utpms.4 utwitch.4 utrh.4 uts.4 uvideo.4 uvisor.4 \
uvscom.4 uyap.4 \
diff --git a/share/man/man4/upd.4 b/share/man/man4/upd.4
new file mode 100644
index 0000000..5a9054d
--- /dev/null
+++ b/share/man/man4/upd.4
@@ -0,0 +1,96 @@
+.\" $OpenBSD$
+.\"
+.\" Copyright (c) 2014 Andre de Oliveira <[email protected]>
+.\"
+.\" 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 DISCLAIMS 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.
+.\"
+.Dd $Mdocdate: February 8 2014 $
+.Dt UPD 4
+.Os
+.Sh NAME
+.Nm upd
+.Nd USB Power Devices battery voltage and load sensor
+.Sh SYNOPSIS
+.Cd "upd* at uhub?"
+.Sh DESCRIPTION
+The
+.Nm
+exposes data from USB Power Devices (such as an UPS), as hardware sensors
+via
+.Xr sysctl 3 .
+The following devices are supported by the
+.Nm
+driver:
+.Bl -column
+.Pp
+.It Li "American Power Conversion Smart-UPS 750"
+.It Li "American Power Conversion Back-UPS CS 350"
+.El
+.Pp
+The following sensors are provided by the
+.Nm
+driver, which can be monitored using
+.Xr sensorsd 8 :
+.Bl -column
+.Pp
+.It Battery Nominal Voltage
+.It Battery Current Voltage
+.It Battery Charging
+.It Battery Discharging
+.It Battery Present
+.It Shutdown Imminent
+.It AC Power Present
+.It Battery Current Load
+.El
+.Sh EXAMPLES
+In this example, the upd0 device is a regular UPS.
+We use an entry on
+.Xr sensorsd 8
+to take an action when the battery level is below a 70%:
+.Pp
+.Bd -literal -offset indent
+hw.sensors.upd0.percent0:low=70:command=/etc/sensorsd/lowbattwarn %l
+.Ed
+.Pp
+The contents of lowbattwarn could be:
+.Pp
+.Bd -literal -offset indent
+#!/bin/ksh
+
+if [ $# -lt 1 ]; then
+ return;
+fi
+
+if [ $1 -lt 70 ]; then
+ logger "ups battery warning-level, halting"
+ /sbin/halt -p
+fi
+.Ed
+.Sh SEE ALSO
+.Xr intro 4 ,
+.Xr uhub 4 ,
+.Xr sensorsd 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in .Ox
+.Sh AUTHORS
+The
+.Nm
+driver was written by Andre de Oliveira <[email protected]>,
+sponsored by Esdenera Networks GmbH.
+.Sh BUGS
+When the battery is not present even with Battery Presence indicator set to Off
+the remaining battery sensors will still be present, besides their values can
+not be trusted.
diff --git a/share/man/man4/usb.4 b/share/man/man4/usb.4
index 9009e62..f631fe2 100644
--- a/share/man/man4/usb.4
+++ b/share/man/man4/usb.4
@@ -281,6 +281,8 @@ Maxim/Dallas DS2490 USB 1-Wire adapter
Prolific based host-to-host adapters
.It Xr usps 4
USPS composite AC power and temperature sensor
+.It Xr upd 4
+USB Power Devices battery voltage and load sensor
.It Xr uts 4
USB touchscreen support
.It Xr uyap 4
diff --git a/sys/arch/.gitignore b/sys/arch/.gitignore
new file mode 100644
index 0000000..af7465e
--- /dev/null
+++ b/sys/arch/.gitignore
@@ -0,0 +1 @@
+*/compile
diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC
index 0a1bf36..77225c2 100644
--- a/sys/arch/amd64/conf/GENERIC
+++ b/sys/arch/amd64/conf/GENERIC
@@ -236,6 +236,7 @@ udsbr* at uhub? # D-Link DSB-R100 radio
radio* at udsbr? # USB radio
uberry* at uhub? # Research In Motion Blackberry
ugen* at uhub? # USB Generic driver
+upd* at uhub? # USB Power Devices sensors
uath* at uhub? # Atheros AR5005UG/AR5005UX
ural* at uhub? # Ralink RT2500USB
rum* at uhub? # Ralink RT2501USB/RT2601USB
diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb
index 9ad82c5..42592f7 100644
--- a/sys/dev/usb/files.usb
+++ b/sys/dev/usb/files.usb
@@ -60,6 +60,10 @@ device ugen
attach ugen at uhub
file dev/usb/ugen.c ugen needs-flag
+# USB Power Devices
+device upd
+attach upd at uhub
+file dev/usb/upd.c upd
# HID
# HID "bus"
diff --git a/sys/dev/usb/upd.c b/sys/dev/usb/upd.c
new file mode 100644
index 0000000..5a770b9
--- /dev/null
+++ b/sys/dev/usb/upd.c
@@ -0,0 +1,413 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2014 Andre de Oliveira <[email protected]>
+ *
+ * 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.
+ */
+
+/* Driver for USB Power Devices sensors */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/device.h>
+#include <sys/sensors.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdevs.h>
+#include <dev/usb/usbhid.h>
+#include <dev/usb/hid.h>
+
+#ifdef USB_DEBUG
+#define UPD_DEBUG
+#endif
+
+#ifdef UPD_DEBUG
+int upddebug = 9;
+#define DPRINTF(x) do { if (upddebug) printf x; } while (0)
+#define DPRINTFN(n, x) do { if (upddebug >= (n)) printf x; } while (0)
+#else
+#define DPRINTF(x)
+#define DPRINTFN(n, x)
+#endif
+
+#define UPD_USB_TMOUT 1000
+
+/* protocol */
+#define UPD_CMD_GET_BATTNOMV 0x0a
+#define UPD_CMD_GET_BATTCURV 0x0b
+#define UPD_CMD_GET_BATTLOAD 0x0c
+#define UPD_CMD_GET_UPSMISC 0x07 /* misc flags from ups */
+
+#define UPD_CMD_NUM 4
+
+enum upd_sensor_id {
+ UPD_SENSOR_UNKNOWN,
+ UPD_SENSOR_BATTNOMV,
+ UPD_SENSOR_BATTCURV,
+ UPD_SENSOR_BATTLOAD,
+ UPD_SENSOR_BATTCHARGING,
+ UPD_SENSOR_BATTDISCHARG,
+ UPD_SENSOR_BATTPRESENT,
+ UPD_SENSOR_SHUTIMMINENT,
+ UPD_SENSOR_ACPOWER,
+ UPD_SENSOR_ACOFFTIME,
+ UPD_SENSOR_NUM
+};
+
+struct upd_report_param {
+ int enabled;
+ u_int8_t cmd; /* usage id (issue command) */
+ char *type_s; /* sensor string */
+ struct hid_location loc; /* size, count, pos */
+};
+
+static struct upd_report_param upd_report_params[UPD_SENSOR_NUM] = {
+ { 0, 0x0, "unknown", { 0, 0, 0 } },
+ { 1, UPD_CMD_GET_BATTNOMV, "battery nominal volt", { 0x10, 0x0, 0x0 } },
+ { 1, UPD_CMD_GET_BATTCURV, "battery current volt", { 0x10, 0x0, 0x0 } },
+ { 1, UPD_CMD_GET_BATTLOAD, "battery load", { 0x08, 0x0, 0x0 } },
+ { 1, UPD_CMD_GET_UPSMISC, "battery charging", { 0x01, 0x0, 0x0 } },
+ { 1, UPD_CMD_GET_UPSMISC, "battery discharging", { 0x01, 0x0, 0x01 }
},
+ { 1, UPD_CMD_GET_UPSMISC, "battery present", { 0x01, 0x0, 0x03 }
},
+ { 1, UPD_CMD_GET_UPSMISC, "shutdown imminent", { 0x01, 0x0, 0x06 }
},
+ { 1, UPD_CMD_GET_UPSMISC, "acpower present", { 0x0, 0x0, 0x0 } },
+ { 0, UPD_CMD_GET_UPSMISC, "acpower off time", { 0x0, 0x0, 0x0 } }
+};
+
+struct upd_udata {
+ u_int8_t cmd;
+ u_int16_t dlen;
+ enum hid_kind hkind;
+ u_int8_t data[8];
+ int rfail;
+};
+
+struct upd_sensor {
+ struct ksensor sensor;
+ enum upd_sensor_id id;
+ int attached;
+};
+
+struct upd_softc {
+ struct device sc_hdev;
+ struct usbd_device *sc_udev;
+ int sc_num_sensors;
+
+ /* sensor framework */
+ struct upd_sensor sc_sensor[UPD_SENSOR_NUM];
+ struct upd_udata sc_usbdata[UPD_CMD_NUM];
+ struct ksensordev sc_sensordev;
+ struct sensor_task *sc_sensortask;
+};
+
+struct upd_type {
+ struct usb_devno upd_dev;
+ u_int16_t upd_flags;
+};
+
+static const struct upd_type upd_devs[] = {
+ { { USB_VENDOR_APC, USB_PRODUCT_APC_UPS }, 0 },
+ { { USB_VENDOR_APC, USB_PRODUCT_APC_UPS5G }, 0 },
+};
+#define upd_lookup(v, p) \
+ ((const struct upd_type *)usb_lookup(upd_devs, v, p))
+
+int upd_match(struct device *, void *, void *);
+void upd_attach(struct device *, struct device *, void *);
+int upd_detach(struct device *, int);
+
+void upd_setup_sensors(struct upd_softc *);
+void upd_refresh(void *);
+void upd_refresh_data(struct upd_softc *);
+int upd_read_data(struct upd_softc *, struct upd_udata *, int);
+struct upd_udata * upd_usbdata_lookup(struct upd_udata *, u_int8_t);
+
+struct cfdriver upd_cd = {
+ NULL, "upd", DV_DULL
+};
+
+const struct cfattach upd_ca = {
+ sizeof(struct upd_softc),
+ upd_match,
+ upd_attach,
+ upd_detach
+};
+
+int
+upd_match(struct device *parent, void *match, void *aux)
+{
+ struct usb_attach_arg *uaa = aux;
+
+ if (upd_lookup(uaa->vendor, uaa->product) == NULL)
+ return (UMATCH_NONE);
+
+ DPRINTFN(3, ("upd: vendor=0x%x, product=0x%x\n", uaa->vendor,
+ uaa->product));
+
+ return (UMATCH_VENDOR_PRODUCT);
+}
+
+void
+upd_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct upd_softc *sc = (struct upd_softc *)self;
+ struct usb_attach_arg *uaa = (struct usb_attach_arg *)aux;
+ struct usbd_device *udev;
+ int i;
+
+ sc->sc_udev = udev = uaa->device;
+ sc->sc_num_sensors = 0;
+
+ strlcpy(sc->sc_sensordev.xname, sc->sc_hdev.dv_xname,
+ sizeof(sc->sc_sensordev.xname));
+ upd_setup_sensors(sc);
+
+ /* attach sensors */
+ for (i = 0; i < UPD_SENSOR_NUM; i++) {
+ if (sc->sc_sensor[i].id == UPD_SENSOR_UNKNOWN)
+ continue;
+ sc->sc_sensor[i].sensor.flags |= SENSOR_FINVALID;
+ sc->sc_sensor[i].sensor.value = 0;
+ sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i].sensor);
+ sc->sc_sensor[i].attached = 1;
+ sc->sc_num_sensors++;
+ }
+
+ DPRINTFN(3, ("upd: sc_num_sensors=%d\n", sc->sc_num_sensors));
+
+ if (sc->sc_num_sensors > 0) {
+ sc->sc_sensortask = sensor_task_register(sc, upd_refresh, 6);
+ if (sc->sc_sensortask == NULL) {
+ printf(", unable to register update task\n");
+ return;
+ }
+ sensordev_install(&sc->sc_sensordev);
+ }
+
+ /*
+ * init usbdata
+ * those commands width were identified from observing apcupsd/ugen(4)
+ * data exchange
+ */
+ sc->sc_usbdata[0].cmd = UPD_CMD_GET_BATTNOMV;
+ sc->sc_usbdata[0].dlen = 0x03;
+ sc->sc_usbdata[0].hkind = hid_feature;
+ sc->sc_usbdata[1].cmd = UPD_CMD_GET_BATTCURV;
+ sc->sc_usbdata[1].dlen = 0x02;
+ sc->sc_usbdata[1].hkind = hid_feature;
+ sc->sc_usbdata[2].cmd = UPD_CMD_GET_BATTLOAD;
+ sc->sc_usbdata[2].dlen = 0x03;
+ sc->sc_usbdata[2].hkind = hid_feature;
+ sc->sc_usbdata[3].cmd = UPD_CMD_GET_UPSMISC;
+ sc->sc_usbdata[3].dlen = 0x03;
+ sc->sc_usbdata[3].hkind = hid_feature;
+
+ DPRINTFN(3, ("upd_attach: complete\n"));
+}
+
+int
+upd_detach(struct device *self, int flags)
+{
+ struct upd_softc *sc = (struct upd_softc *)self;
+ int i;
+
+ if (sc->sc_num_sensors > 0) {
+ wakeup(&sc->sc_sensortask);
+ sensordev_deinstall(&sc->sc_sensordev);
+ for (i = 0; i < UPD_SENSOR_NUM; i++) {
+ if (sc->sc_sensor[i].attached) {
+ sensor_detach(&sc->sc_sensordev,
+ &sc->sc_sensor[i].sensor);
+ DPRINTFN(5, ("upd_detach: %s\n",
+ sc->sc_sensor[i].sensor.desc));
+ }
+ }
+ if (sc->sc_sensortask != NULL)
+ sensor_task_unregister(sc->sc_sensortask);
+ }
+
+ DPRINTFN(3, ("upd_detach: complete\n"));
+ return (0);
+}
+
+int
+upd_read_data(struct upd_softc *sc, struct upd_udata *udata, int tmout)
+{
+ usb_device_request_t req;
+ int ucr_actlen = 0;
+ int err = 0;
+ enum hid_kind hkind;
+ size_t buflen;
+ u_int16_t dlen;
+ u_int8_t cmd, *buf = NULL;
+ void *ptr = NULL;
+
+ buf = udata->data;
+ cmd = udata->cmd;
+ dlen = udata->dlen;
+ hkind = udata->hkind;
+ buflen = sizeof(buf);
+
+ if (buf == NULL || buflen == 0)
+ return (0);
+
+ ptr = malloc(buflen, M_TEMP, M_WAITOK);
+
+ req.bmRequestType = UT_READ_CLASS_INTERFACE;
+ req.bRequest = UR_GET_REPORT;
+
+ USETW(req.wValue, ((hkind + 1) << 8) | cmd);
+ USETW(req.wIndex, 0);
+ USETW(req.wLength, dlen);
+
+ err = usbd_do_request_flags(sc->sc_udev, &req, ptr, USBD_SHORT_XFER_OK,
+ &ucr_actlen, tmout);
+
+ if (err)
+ goto ret;
+
+ memcpy(buf, ptr, buflen);
+
+ DPRINTFN(5, ("actlen=%02x ptr[0]=%02x ptr[1]=%02x ptr[2]=%02x\n",
+ ucr_actlen, ((u_int8_t *)ptr)[0], ((u_int8_t *)ptr)[1],
+ ((u_int8_t *)ptr)[2]));
+
+ret:
+ if (ptr)
+ free(ptr, M_TEMP);
+
+ return (err);
+}
+
+void
+upd_setup_sensors(struct upd_softc *sc)
+{
+ int i;
+
+ for (i = 0; i < UPD_SENSOR_NUM; i++) {
+ if (upd_report_params[i].enabled != 1)
+ continue;
+
+ sc->sc_sensor[i].id = i;
+ strlcpy(sc->sc_sensor[i].sensor.desc,
+ upd_report_params[i].type_s,
+ sizeof(sc->sc_sensor[i].sensor.desc));
+ sc->sc_sensor[i].attached = 0;
+
+ switch (i) {
+ case UPD_SENSOR_BATTNOMV:
+ case UPD_SENSOR_BATTCURV:
+ sc->sc_sensor[i].sensor.type = SENSOR_VOLTS_DC;
+ break;
+ case UPD_SENSOR_BATTLOAD:
+ sc->sc_sensor[i].sensor.type = SENSOR_PERCENT;
+ break;
+ case UPD_SENSOR_SHUTIMMINENT:
+ case UPD_SENSOR_BATTCHARGING:
+ case UPD_SENSOR_BATTDISCHARG:
+ case UPD_SENSOR_BATTPRESENT:
+ case UPD_SENSOR_ACPOWER:
+ sc->sc_sensor[i].sensor.type = SENSOR_INDICATOR;
+ break;
+ }
+ }
+}
+
+void
+upd_refresh(void *arg)
+{
+ struct upd_softc *sc = (struct upd_softc *)arg;
+ struct hid_location *loc;
+ struct upd_udata *udata;
+ u_long hdata;
+ u_int8_t cmd;
+ u_int8_t *buf;
+ int i;
+
+ upd_refresh_data(sc);
+
+ for (i = 0; i < UPD_SENSOR_NUM; i++) {
+ if (sc->sc_sensor[i].attached != 1 ||
+ upd_report_params[i].enabled != 1)
+ continue;
+
+ cmd = upd_report_params[i].cmd;
+ udata = upd_usbdata_lookup(sc->sc_usbdata, cmd);
+
+ /* skip on eventual read failures or unsupported features */
+ if (udata->rfail)
+ continue;
+
+ buf = udata->data;
+ loc = &upd_report_params[i].loc;
+ hdata = hid_get_data(buf + 1, loc);
+
+ switch (i) {
+ case UPD_SENSOR_BATTNOMV:
+ case UPD_SENSOR_BATTCURV:
+ case UPD_SENSOR_BATTLOAD:
+ if (sc->sc_sensor[UPD_SENSOR_BATTPRESENT].sensor.value)
+ hdata *= 1000; /* scale adjust */
+ else
+ hdata = 0;
+ break;
+ case UPD_SENSOR_ACPOWER:
+ /* use discharging flag as acpower indicator */
+ if (sc->sc_sensor[UPD_SENSOR_BATTDISCHARG].sensor.value)
+ hdata = 0;
+ else
+ hdata = 1;
+ break;
+ }
+
+ sc->sc_sensor[i].sensor.flags &= ~SENSOR_FINVALID;
+ sc->sc_sensor[i].sensor.value = hdata;
+ DPRINTFN(3, ("%s: %s: hidget data: %d\n",
+ sc->sc_sensordev.xname,
+ upd_report_params[i].type_s, hdata));
+ }
+}
+
+void
+upd_refresh_data(struct upd_softc *sc)
+{
+ struct upd_udata *udata;
+ int i, ret;
+
+ for (i = 0; i < UPD_CMD_NUM; i++) {
+ udata = &sc->sc_usbdata[i];
+ bzero(udata->data, sizeof(udata->data));
+ ret = upd_read_data(sc, udata, UPD_USB_TMOUT);
+ if (ret != USBD_NORMAL_COMPLETION) {
+ DPRINTFN(5, ("upd: data read fail\n"));
+ udata->rfail = 1;
+ }
+ udata->rfail = 0;
+ }
+}
+
+struct upd_udata *
+upd_usbdata_lookup(struct upd_udata *usbdata, u_int8_t cmd)
+{
+ int i;
+
+ for (i = 0; i < UPD_CMD_NUM; i++)
+ if (usbdata[i].cmd == cmd)
+ return (&usbdata[i]);
+
+ return (NULL);
+}