netBSD dev/ugen / libusb

"Nevil Thatcher" <[email protected]>
Newsgroups gmane.os.netbsd.devel.general
Message-ID <001601c6498d$5bdc52e0$f7f0a8c0@cobra>
Hi All,

 

I have found an issue with netBSD dev/ugen - libusb and am after a bit of
direction/advice.

 

This has come about whilst trying to get nut (ups monitoring) software
operational under netBSD. Nut uses libusb as the layer to access usb
devices, the device I am using being a MGE Nova 1100 UPS.

 

Software versions are:

 

netBSD 3.0_STABLE (amd64)

libusb 0.1.12

nut 2.0.3

 

Libusb offers a function usb_interrupt_read, the function looks like

 

int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size,
int timeout)

 

A call to this should I understand return with *char buffer filled with data
from usb device OR timeout after int timeout seconds.

 

The problem seems to be that the dev/ugen code ugen_do_read does not respect
this timeout and will not return until such time as data is returned from
the usb device.

 

End result is that when libusb usb_interrupt_read is called it won't return
until data is received from the usb device and the buffer filled, so
effectively hangs.

 

To get around this I have added the flag O_NONBLOCK to the call to open the
device. 

If there is no data available from the device then ugen_do_read then returns
with the error EWOULDBLOCK

This is defined as "Resource temporarily unavailable" in errno.h

 

With this patch the program works quite happily, but is this a valid
approach?

 

Should 

a)       netBSD dev/ugen respect the timeout set by = ioctl(fd,
USB_SET_TIMEOUT, &timeout); as implemented in libusb OR

b)       libusb use the O_NONBLOCK flag when opening the device.

 

I guess this needs to be fixed somewhere, but where!

 

Your thoughts?, thanks in advance

 

Nevil

 

Relevant code excerpts below:-

 

 

/*============ netBSD sys/errno.h ================*/

 

#define  EAGAIN                        35                     /* Resource
temporarily unavailable */

#define  EWOULDBLOCK           EAGAIN                        /* Operation
would block */

 

 

/*============= libusb/bsd.c===================================*/

int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size,

                       int timeout)

{

  int fd, ret, retrieved = 0, one = 1;

 

  /* Ensure the endpoint address is correct */

  ep |= USB_ENDPOINT_IN;

 

/* open in non-blocking mode so that call will return if no data */
<======= fix

  fd = ensure_ep_open(dev, ep, O_RDONLY | O_NONBLOCK);   <======= to ensure
call

/*  fd = ensure_ep_open(dev, ep, O_RDONLY );  */  if (fd < 0) {     <=======
returns 

    if (usb_debug >= 2) {

#ifdef __FreeBSD_kernel__

      fprintf (stderr, "usb_interrupt_read: got negative open file
descriptor for endpoint %d\n", UE_GET_ADDR(ep));

#else

      fprintf (stderr, "usb_interrupt_read: got negative open file
descriptor for endpoint %02d\n", UE_GET_ADDR(ep));

#endif

    }

    return fd;

  }

 

  ret = ioctl(fd, USB_SET_TIMEOUT, &timeout);

  if (ret < 0)

    USB_ERROR_STR(-errno, "error setting timeout: %s", strerror(errno));

 

  ret = ioctl(fd, USB_SET_SHORT_XFER, &one);

  if (ret < 0)

    USB_ERROR_STR(-errno, "error setting short xfer: %s", strerror(errno));

 

  do {

    ret = read(fd, bytes+retrieved, size-retrieved);

    if (ret < 0)

#ifdef __FreeBSD_kernel__

      USB_ERROR_STR(-errno, "error reading from interrupt endpoint %s.%d:
%s",

                    dev->device->filename, UE_GET_ADDR(ep),
strerror(errno));

#else

      USB_ERROR_STR(-errno, "error reading from interrupt endpoint %s.%02d:
%s",

                  dev->device->filename, UE_GET_ADDR(ep), strerror(errno));

#endif

    retrieved += ret;

  } while (ret > 0 && retrieved < size);

 

  return retrieved;

}

/*===================================================================*/

 

static int ensure_ep_open(usb_dev_handle *dev, int ep, int mode)

{

  struct bsd_usb_dev_handle_info *info = dev->impl_info;

  int fd;

  char buf[20];

 

  /* Get the address for this endpoint; we could check

   * the mode against the direction; but we've done that

   * already in the usb_bulk_read/write.

   */

  ep = UE_GET_ADDR(ep);

 

  if (info->ep_fd[ep] < 0) {

#ifdef __FreeBSD_kernel__

    snprintf(buf, sizeof(buf) - 1, "%s.%d", dev->device->filename, ep);

#else

    snprintf(buf, sizeof(buf) - 1, "%s.%02d", dev->device->filename, ep);

#endif

    /* Try to open it O_RDWR first for those devices which have in and out

     * endpoints with the same address (eg 0x02 and 0x82)

     */

    fd = open(buf, O_RDWR);

    if (fd < 0 && errno == ENXIO)

      fd = open(buf, mode);

    if (fd < 0)

      USB_ERROR_STR(-errno, "can't open %s for bulk read: %s",

                    buf, strerror(errno));

    info->ep_fd[ep] = fd;

  }

 

/*====================netBSD dev/usb/ugen.c ===============*/

Static int

ugen_do_read(struct ugen_softc *sc, int endpt, struct uio *uio, int flag)

{

            struct ugen_endpoint *sce = &sc->sc_endpoints[endpt][IN];

            u_int32_t n, tn;

            char buf[UGEN_BBSIZE];

            usbd_xfer_handle xfer;

            usbd_status err;

            int s;

            int error = 0;

            u_char buffer[UGEN_CHUNK];

 

            DPRINTFN(5, ("%s: ugenread: %d\n", USBDEVNAME(sc->sc_dev),
endpt));

 

            if (sc->sc_dying)

                        return (EIO);

 

            if (endpt == USB_CONTROL_ENDPOINT)

                        return (ENODEV);

 

#ifdef DIAGNOSTIC

            if (sce->edesc == NULL) {

                        printf("ugenread: no edesc\n");

                        return (EIO);

            }

            if (sce->pipeh == NULL) {

                        printf("ugenread: no pipe\n");

                        return (EIO);

            }

#endif

 

            switch (sce->edesc->bmAttributes & UE_XFERTYPE) {

            case UE_INTERRUPT:

                        /* Block until activity occurred. */

                        s = splusb();

                        while (sce->q.c_cc == 0) {

                                    if (flag & IO_NDELAY) {

                                                splx(s);

                                                return (EWOULDBLOCK);

                                    }

                                    sce->state |= UGEN_ASLP;

                                    DPRINTFN(5, ("ugenread: sleep on %p\n",
sce));

                                    error = tsleep(sce, PZERO | PCATCH,
"ugenri", 0);

                                    DPRINTFN(5, ("ugenread: woke,
error=%d\n", error));

                                    if (sc->sc_dying)

                                                error = EIO;

                                    if (error) {

                                                sce->state &= ~UGEN_ASLP;

                                                break;

                                    }

                        }

                        splx(s);

 

                        /* Transfer as many chunks as possible. */

                        while (sce->q.c_cc > 0 && uio->uio_resid > 0 &&
!error) {

                                    n = min(sce->q.c_cc, uio->uio_resid);

                                    if (n > sizeof(buffer))

                                                n = sizeof(buffer);

 

                                    /* Remove a small chunk from the input
queue. */

                                    q_to_b(&sce->q, buffer, n);

                                    DPRINTFN(5, ("ugenread: got %d chars\n",
n));

 

                                    /* Copy the data to the user process. */

                                    error = uiomove(buffer, n, uio);

                                    if (error)

                                                break;

                        }

                        break;

            case UE_BULK:

                        xfer = usbd_alloc_xfer(sc->sc_udev);

                        if (xfer == 0)

                                    return (ENOMEM);

                        while ((n = min(UGEN_BBSIZE, uio->uio_resid)) != 0)
{

                                    DPRINTFN(1, ("ugenread: start transfer
%d bytes\n",n));

                                    tn = n;

                                    err = usbd_bulk_transfer(

                                                  xfer, sce->pipeh,

                                                  sce->state & UGEN_SHORT_OK
?

                                                      USBD_SHORT_XFER_OK :
0,

                                                  sce->timeout, buf, &tn,
"ugenrb");

                                    if (err) {

                                                if (err == USBD_INTERRUPTED)

                                                            error = EINTR;

                                                else if (err ==
USBD_TIMEOUT)

                                                            error =
ETIMEDOUT;

                                                else

                                                            error = EIO;

                                                break;

                                    }

                                    DPRINTFN(1, ("ugenread: got %d bytes\n",
tn));

                                    error = uiomove(buf, tn, uio);

                                    if (error || tn < n)

                                                break;

                        }

                        usbd_free_xfer(xfer);

                        break;

            case UE_ISOCHRONOUS:

                        s = splusb();

                        while (sce->cur == sce->fill) {

                                    if (flag & IO_NDELAY) {

                                                splx(s);

                                                return (EWOULDBLOCK);

                                    }

                                    sce->state |= UGEN_ASLP;

                                    DPRINTFN(5, ("ugenread: sleep on %p\n",
sce));

                                    error = tsleep(sce, PZERO | PCATCH,
"ugenri", 0);

                                    DPRINTFN(5, ("ugenread: woke,
error=%d\n", error));

                                    if (sc->sc_dying)

                                                error = EIO;

                                    if (error) {

                                                sce->state &= ~UGEN_ASLP;

                                                break;

                                    }

                        }

 

                        while (sce->cur != sce->fill && uio->uio_resid > 0
&& !error) {

                                    if(sce->fill > sce->cur)

                                                n = min(sce->fill -
sce->cur, uio->uio_resid);

                                    else

                                                n = min(sce->limit -
sce->cur, uio->uio_resid);

 

                                    DPRINTFN(5, ("ugenread: isoc got %d
chars\n", n));

 

                                    /* Copy the data to the user process. */

                                    error = uiomove(sce->cur, n, uio);

                                    if (error)

                                                break;

                                    sce->cur += n;

                                    if(sce->cur >= sce->limit)

                                                sce->cur = sce->ibuf;

                        }

                        splx(s);

                        break;

 

 

            default:

                        return (ENXIO);

            }

            return (error);

}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.