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 <deoliveira...@googlemail.com>
+.\"
+.\" 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 <deoliveira...@googlemail.com>,
+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 <deoliveira...@googlemail.com>
+ *
+ * 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);
+}

Reply via email to