		How to modify libusb for use under NetBSD

From: Wolfgang Rupprecht (wolfgang@wsrcc.com)
Subject: Re: USB support for Nikon 990 camera? 
Newsgroups: muc.lists.netbsd.users
Date: 2001-04-01 13:49:38 PST 

I've got photopc working now.  Libusb needed a bit of whacking.
Luckily Greg Troxel sent me a patch to get freebsd working.  That
saved me lots of work.

        http://www.wsrcc.com/wolfgang/ftp/libusb-netbsd.diff .

This patch is against a feb-18th version of the anon-cvs version of
libusb.  Greg did mention that he'd spruce up the patches and send
them in, so for all I know it works out of the box now.

One wart is that I was too lazy to hack up a temporary string to mung
the freebsd name of the ugen0 port to the netbsd name.  I just added a
sym link

  0 lrwxr-xr-x  1 root  wheel  8 Feb 18 12:25 /dev/ugen0@ -> ugen0.00

Index: API
===================================================================
RCS file: /cvsroot/libusb/libusb/API,v
retrieving revision 1.2
diff -u -r1.2 API
--- API	2000/02/17 21:36:40	1.2
+++ API	2001/04/01 20:22:36
@@ -35,13 +35,17 @@
 	int timeout);
 
   Sends data specified by bytes of size size to the bulk endpoint specified
-  by ep.
+  by ep.  Returns 0 on success.  In case of failure, returns -1.  In
+  this case, an arbitrary amount of the data may have been transferred.
 
 int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size,
 	int timeout);
 
   Reads data into buffer specified by bytes of size size from the bulk
-  endpoint specified by ep.
+  endpoint specified by ep.  Returns -1 on failure and otherwise the number
+  of bytes read.  It is not an error to receive fewer bytes than were
+  requested ("short reads").  It will typically be an error (BABBLE) if
+  the device sends more bytes than were requested.
 
 int usb_control_msg(usb_dev_handle *dev, int requesttype, int request,
 	int value, int index, char *bytes, int size, int timeout);
Index: configure.in
===================================================================
RCS file: /cvsroot/libusb/libusb/configure.in,v
retrieving revision 1.10
diff -u -r1.10 configure.in
--- configure.in	2001/01/14 23:58:35	1.10
+++ configure.in	2001/04/01 20:22:36
@@ -86,7 +86,7 @@
     os_support=linux
     AC_MSG_RESULT(Linux)
     ;;
-  *-freebsd*)
+  *-freebsd*|*-netbsd*)
     AC_DEFINE(FREEBSD)
     AC_SUBST(FREEBSD)
     AC_MSG_RESULT(FreeBSD)
Index: freebsd.c
===================================================================
RCS file: /cvsroot/libusb/libusb/freebsd.c,v
retrieving revision 1.5
diff -u -r1.5 freebsd.c
--- freebsd.c	2001/02/11 21:57:34	1.5
+++ freebsd.c	2001/04/01 20:22:37
@@ -39,7 +39,10 @@
 
 #include "usbi.h"
 
+/* #define BSD_DEBUG 1 */
+
 static int ensure_ep_open(usb_dev_handle *dev, int ep, int mode);
+static int ensure_ep_closed(usb_dev_handle *dev, int ep);
 
 #define MAX_CONTROLLERS 10
 
@@ -69,6 +72,23 @@
       USB_ERROR_STR(-errno, "failed to open %s: %s",
                     dev->device->filename, strerror(errno));
     }
+
+    dev->fd = open(dev->device->filename, O_RDWR);
+    if (dev->fd < 0) 
+    {
+	if (usb_debug) fprintf(stderr, "RW fails: ");
+	dev->fd = open(dev->device->filename, O_RDONLY);
+	if (dev->fd < 0)
+	{
+	    if (usb_debug) fprintf(stderr, "RO fails.\n");
+	    free(info);
+	    USB_ERROR_STR(errno, "failed to open %s: %s",
+			  dev->device->filename, strerror(errno));
+	} else
+	    printf("RO OK.\n");
+    } else
+	if (usb_debug) fprintf(stderr, "RW OK.\n");
+
   }
     
   /* Mark the endpoints as not yet open */
@@ -89,6 +109,9 @@
     if (info->ep_fd[i] >= 0) {
       if (usb_debug >= 2)
         fprintf(stderr, "usb_os_close: closing endpoint %d\n", info->ep_fd[i]);
+#if BSD_DEBUG
+      fprintf(stderr, "closing endpoint %d\n", info->ep_fd[i]);
+#endif
       close(info->ep_fd[i]);
     }
   
@@ -132,6 +155,7 @@
 int usb_release_interface(usb_dev_handle *dev, int interface)
 {
   /* See above */
+  dev->interface = -1;
   return 0;
 }
 
@@ -139,6 +163,8 @@
 {
   int ret;
   struct usb_alt_interface intf;
+  if (dev->interface < 0)
+    USB_ERROR(-EINVAL);
 
   if (dev->interface < 0)
     USB_ERROR(-EINVAL);
@@ -152,15 +178,36 @@
                   dev->interface, alternate, strerror(errno));
 
   dev->altsetting = alternate;
+  return 0;
+}
+
+static int ensure_ep_closed(usb_dev_handle *dev, int ep)
+{
+  struct freebsd_usb_dev_handle_info *info = dev->impl_info;
 
+  /* Get the address for this endpoint; we cold check
+   * the mode against the direction; but we've done that 
+   * already in the usb_build_read/write.
+   */
+  ep = UE_GET_ADDR( ep );
+
+  if(info->ep_fd[ep] >= 0) {
+    if ( usb_debug ) {
+      fprintf(stderr, "libusb:ensure_ep_closed %d fd %d\n",
+	      ep, info->ep_fd[ep]);
+    }
+    close(info->ep_fd[ep]);
+    info->ep_fd[ep] = -1;
+  }
   return 0;
 }
 
 static int ensure_ep_open(usb_dev_handle *dev, int ep, int mode)
 {
   struct freebsd_usb_dev_handle_info *info = dev->impl_info;
-  int fd;
+  int fd, ret;
   char buf[20];
+  int shortok = 1;
 
   /* Get the address for this endpoint; we cold check
    * the mode against the direction; but we've done that 
@@ -174,13 +221,42 @@
 #else
     sprintf(buf, "%s.%02d", dev->device->filename, ep);
 #endif
+    if ( usb_debug ) {
+      fprintf(stderr, "libusb:ensure_ep_open ep %d (%s) mode %d\n",
+	      ep, buf, mode);
+    }
     fd = open(buf, mode);
-    if (fd < 0)
+    if (fd < 0) {
+      if ( usb_debug ) {
+	fprintf(stderr,
+		"libusb:ensure_ep_open fails ep %d (%s) => %d %d\n",
+		ep, buf, fd, errno);
+      }
       USB_ERROR_STR(fd, "can't open %s for bulk read: %s\n",
-                    buf, strerror(errno));
+		    buf, strerror(errno));
+    }
     info->ep_fd[ep] = fd;
+    
+    if (mode == O_WRONLY){
+	/* fprintf(stderr, "Attempted to set short-xfer on write endpoint\n"); */
+    } else {
+	ret = ioctl(fd, USB_SET_SHORT_XFER, &shortok);
+    if (ret < 0) {
+      if ( usb_debug ) {
+	fprintf(stderr,
+		"libusb:ensure_ep_open: SHORT_XFER on fd %d, %s returns %d: %s\n",
+		fd, buf, ret, strerror(errno));
+      }
+#if 0
+      USB_ERROR_STR(ret, "error setting shortok: %s",
+		    strerror(errno));
+#endif
+    }
+    }
   }
 
+
+
   return info->ep_fd[ep];
 }
 
@@ -193,6 +269,14 @@
     USB_ERROR_STR(-1, "error writing to endpoint %d; not an output",
                   UE_GET_ADDR(ep));
 
+  if ( usb_debug ) {
+    fprintf(stderr,
+	    "libusb:usb_bulk_write: ep 0x%x %d bytes %d ms timeout\n",
+	    ep, size, timeout);
+  }
+
+  ep &= 0x7f;			/* XXX why is the high bit set??? */
+  
   fd = ensure_ep_open(dev, ep, O_WRONLY);
   if (fd < 0) {
     if (usb_debug >= 2) {
@@ -205,14 +289,27 @@
     return fd;
   }
 
+#if 0				/* -wsr */
+  /* XXX Should be moved to open, stored, and avoided if unchanged. */
   ret = ioctl(fd, USB_SET_TIMEOUT, &timeout);
-  if (ret < 0)
+  if (ret < 0) {
+    if ( usb_debug ) {
+      fprintf(stderr,
+	      "libusb:usb_bulk_write: SET_TIMEOUT loses %d\n",
+	      ret);
+    }
     USB_ERROR_STR(ret, "error setting timeout: %s",
-                  strerror(errno));
+                 strerror(errno));
+  }
+#endif
 
-  do {
-    ret = write(fd, bytes+sent, size-sent);
-    if (ret < 0)
+  /*
+   * The kernel implementation writes all the bytes requested using
+   * multiple bulk transfers until they all succeed or there is an
+   * error.  Hence there is no loop here.
+   */
+  ret = write(fd, bytes+sent, size-sent);
+  if(ret < 0)
 #if __FreeBSD__
       USB_ERROR_STR(ret, "error writing to bulk endpoint %s.%d: %s",
                     dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
@@ -220,18 +317,34 @@
     USB_ERROR_STR(ret, "error writing to bulk endpoint %s.%02d: %s",
                   dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
 #endif
-        
-    sent += ret;
-  } while(ret > 0 && sent < size);
 
-  return sent;
+  sent = ret;
+  
+  if ( sent != size ) {
+    if ( 1 || usb_debug ) {
+      /* should not happen */
+      fprintf(stderr, "libusb:usb_bulk_write: sent %d/%d\n",
+	      sent, size);
+    }
+    USB_ERROR_STR(-1, "couldn't write all data - should not happen");
+  }
+    
+  /* callers expect 0, not #bytes written */
+  return 0;
 }
 
 int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size,
                   int timeout)
 {
   int fd, ret, retrieved = 0;
+  int repetitions = 0;
 
+  if ( usb_debug ) {
+    fprintf(stderr,
+	    "libusb:usb_bulk_read: ep 0x%x %d bytes %d ms timeout\n",
+	    ep, size, timeout);
+  }
+
   if (UE_GET_DIR(ep) != UE_DIR_IN) 
 #if __FreeBSD__
     USB_ERROR_STR(-1, "error reading from endpoint %d; not an input", UE_GET_ADDR(ep));
@@ -239,6 +352,18 @@
   USB_ERROR_STR(-1, "error reading from endpoint %02d; not an input", UE_GET_ADDR(ep));
 #endif
 
+  /* Get the address for this endpoint; we cold check
+   * the mode against the direction; but we've done that 
+   * already in the usb_build_read/write.
+   */
+  ep = UE_GET_ADDR( ep );
+
+#if CLOSE_EP_EVERY_TIME
+  /* XXX ep's seem to get broken! */
+  ensure_ep_closed(dev, ep);
+#endif
+
+again:
   fd = ensure_ep_open(dev, ep, O_RDONLY);
   if (fd < 0) {
     if (usb_debug >= 2) {
@@ -251,10 +376,44 @@
     return fd;
   }
 
+    if (ep == 0){
+	/* fprintf(stderr, "Attempted to set timeout on control endpoint\n"); */
+    } else {
   ret = ioctl(fd, USB_SET_TIMEOUT, &timeout);
   if (ret < 0)
     USB_ERROR_STR(ret, "error setting timeout: %s",
                   strerror(errno));
+    }
+
+  /*
+   * The kernel implementation reads all the bytes requested using
+   * multiple bulk transfers until all are read or a bulk transfer
+   * returns fewer bytes than requested. Hence there is no loop here.
+   */
+  ret = read(fd, bytes+retrieved, size-retrieved);
+  if(ret < 0) {
+    if ( usb_debug ) {
+      fprintf(stderr,
+	      "libusb:usb_bulk_read error %d %d\n",
+	      ret, errno);
+    }
+    USB_ERROR_STR(ret, "error reading from bulk endpoint %s.%d: %s",
+		  dev->device->filename, ep, strerror(errno));
+  }
+  if ( ret == 0 ) {
+    if ( usb_debug || repetitions > 0 ) {
+      /* should not happen! */
+      fprintf(stderr, "libusb:usb_bulk_read %d/%d ZERO [TRY %d]\n",
+	      retrieved, size, repetitions);
+    }
+    if ( repetitions++ < 3 ) {
+      ensure_ep_closed(dev, ep);
+      goto again;
+    } else
+      return 0;		/* XXX */
+  }
+  retrieved = ret;
+
   do {
     ret = read(fd, bytes+retrieved, size-retrieved);
     if (ret < 0)
@@ -268,6 +427,9 @@
     retrieved += ret;
   } while (ret > 0 && retrieved < size);
 
+  if ( usb_debug > 2 )
+    fprintf(stderr, "libusb:usb_bulk_read %d/%d ok\n", retrieved, size);
+  
   return retrieved;
 }
 
@@ -290,10 +452,12 @@
   req.data = bytes;
   req.flags = 0;
 
+#if 0				/* -wsr */
   ret = ioctl(dev->fd, USB_SET_TIMEOUT, &timeout);
   if (ret < 0)
     USB_ERROR_STR(ret, "error setting timeout: %s",
                   strerror(errno));
+#endif
 
   ret = ioctl(dev->fd, USB_DO_REQUEST, &req);
   if (ret < 0)
@@ -308,6 +472,11 @@
   int cfd, dfd;
   int device;
 
+  if ( usb_debug )
+    fprintf(stderr,
+	    "usb_find_devices_on_bus: opening %s\n",
+	    bus->dirname);
+
   cfd = open(bus->dirname, O_RDONLY);
   if (cfd < 0)
     USB_ERROR_STR(errno, "couldn't open(%s): %s", bus->dirname,
@@ -414,6 +583,9 @@
 void usb_os_init(void)
 {
     /* nothing */
+    if (usb_debug) {
+	fprintf(stderr, "libusb:usb_os_init (null routine)\n");
+    }
 }
 
 int usb_resetep (usb_dev_handle *dev, unsigned int ep)
@@ -428,8 +600,11 @@
 {
   /* Not yet done, because I haven't needed it. */
 
-  fprintf(stderr, "usb_clear_halt  called, exiting\n");
+    /*   printf("usb_clear_halt ep %d\n", ep); */
+#if 0
   exit(1);
+#endif
+  return 0;
 }
 
 int usb_reset (usb_dev_handle *dev)

