On 2014/02/14 14:07, Andre de Oliveira wrote: > 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
Woohoo! This is really nice. Not only does this allow nice simple shutdowns under the control of sensorsd, it has a bonus of exposing UPS status over the network via snmpd. I don't have much time now, but I'll try to take a closer look soon.. > > 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); > +} >
