On 20.02.2014, at 12:23, Martin Pieuchot <mpieuc...@nolizard.org> wrote:

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

Haha, I think we also had this discussion before.
You’re really insisting on this one, don't you?

OK, for the sake of "standardization, correctness”, I see the point
of using the (ugly) uintX_t types for new code.

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

I don't like this approach. Because in the end I have “one” UPS with
several sensors. Splitting upd(4) into several devices makes it impossible
to associate them to a specific UPS. It gets worse when attaching two or
more UPSes to the system (which might happen). Having upd0, upd1 etc.
per UPS makes much more sense.

That’s why I proposed the upd(4) sensor super-driver that collects
information from the attached HIDs and presents them with a single sensor.
With your HID simplification diff, this becomes even easier and a bit cleaner.

Reyk

> 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