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); +}