On 17/02/14(Mon) 01:11, Andre de Oliveira wrote:
> On Fri, Feb 14, 2014 at 02:20:57PM +0100, Ingo Schwarze wrote:
> > Hi,
> > 
> > a few comments regarding the manual:
> 
> Ingo, thanks for your feedback.
> Here follows an updated version, just documentation changes.
> 
> I also submitted it to mandoc-lint, now seems cleaner.

Some comments below.

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

For such a simple driver I really recommend to use only one DPRINTF()
level, our USB drivers are not the best examples of simplicity debug
messages...

> +#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

Do you really need a non standard timeout?

> +
> +/* 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 */

I'm not sure to understand what are these magic number and how to you
map them to the Usage ID specified, for example in sec 4.2 Battery
System Page?  Don't tell me you're hardcoding the report IDs of your
UPS, no? :)

> +
> +#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) */

What about using uint8_t and the other related types, instead of
u_intXX_t, for new code since they are standardized?

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

I wouldn't leave such debug message they are very annoying  when
debugging.  Plus you can get vendor & product number with usbdevs(8).

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

Even if for the moment you're only dealing with Feature report, be
prepared to support Input and Ouput too, see below.

But instead of hardcoding magic values, you should iterate over the
report IDs available and try to find the Usage ID your driver supports.
Have a look at the various HUP_* macro, HID_USAGE2() hid_is_collection()
and friend.

Maybe you'll find out that it's easier to split your driver into various
drivers, for example one dealing with the Power Device Page and another
one with the Battery System Page...

If your driver is generic enough you might even have various upd(4)
instances, one for each report ID, that just parse the available usages
and map them to sensors.

Have a look at hidms(4) it do some parsing based on a given report ID
provided by ums(4). 

You'll need a lot of device descriptors to see how the mappings can
differ.

> +
> +     DPRINTFN(3, ("upd_attach: complete\n"));

Same comment as before regarding debugging ;)

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

This won't work on all architecture, have a look at USETW2() if you
want to see why ;).  But more importantly,  you clearly want to use
uhidev_get_report(), here with UHID_FEATURE_REPORT, instead of rewriting
your own: upd_read_data().

I would even suggest to add a uhidev_get_report_async() so that you can
submit your request and don't wait for it to be processed.  The sensor
will then be updated when the USB transfer is completed.


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

Instead of sending USB commands and waiting for all of them to complete
before updating the sensors (that means in worst case, with your custom
timeout, you'll wait n seconds, where n is the number of sensors you
have), you should use a callback that'll update the data of your
sensors.

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

By matching reportid you would avoid such lookup.

> +
> +             /* 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 */

If I'm not mistaken, units are properly specified by the HID Power
Devices specification.  It would be nice to have  a better comment a be
prepared to handle other conversions ;)


> +                     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