1/*
2 * (Free|Open|Net)BSD USB support
3 *
4 * Derived from Linux version by Richard Tobin.
5 *
6 * $Id: bsd.c,v 1.33 2006/03/04 01:16:10 jerdfelt Exp $
7 * $Name:  $
8 *
9 * This library is covered by the LGPL, read LICENSE for details.
10 */
11
12/*
13 * Note: I don't have a clue what I'm doing.  I just looked at the
14 * man pages and source to try and find things that did the same as
15 * the Linux version. -- Richard
16 *
17 * johnjen@reynoldsnet.org - minor fixes with debug mode output. Consistent brace
18 * use as well as indenting. More error messages put in to test for failure
19 * modes with /dev/ permissions (when it happens). Note: I, like Richard, have
20 * no clue what I'm doing. Patches to increase/fix functionality happily
21 * accepted!
22 */
23
24/* dirkx@webweaving.org - minor changes to make things actually work
25 * 	for both read and write.
26 */
27
28#if defined(__FreeBSD__) && !defined(__FreeBSD_kernel__)
29#define __FreeBSD_kernel__ __FreeBSD__
30#endif
31
32#include <stdlib.h>
33#include <unistd.h>
34#include <string.h>
35#include <stdio.h>
36#include <fcntl.h>
37#include <errno.h>
38#include <assert.h>
39#include <sys/time.h>
40#include <sys/ioctl.h>
41
42#include <dev/usb/usb.h>
43
44#include "usbi.h"
45#ifdef HAVE_CONFIG_H
46#include "config.h"
47#endif
48
49#ifdef HAVE_OLD_DEV_USB_USB_H
50/*
51 * It appears some of the BSD's (OpenBSD atleast) have switched over to a
52 * new naming convention, so we setup some macro's for backward
53 * compability with older versions --jerdfelt
54 */
55
56/* struct usb_ctl_request */
57#define ucr_addr		addr
58#define ucr_request		request
59#define ucr_data		data
60#define ucr_flags		flags
61#define ucr_actlen		actlen
62
63/* struct usb_alt_interface */
64#define uai_config_index	config_index
65#define uai_interface_index	interface_index
66#define uai_alt_no		alt_no
67
68/* struct usb_config_desc */
69#define ucd_config_index	config_index
70#define ucd_desc		desc
71
72/* struct usb_interface_desc */
73#define uid_config_index	config_index
74#define uid_interface_index	interface_index
75#define uid_alt_index		alt_index
76#define uid_desc		desc
77
78/* struct usb_endpoint_desc */
79#define ued_config_index	config_index
80#define ued_interface_index	interface_index
81#define ued_alt_index		alt_index
82#define ued_endpoint_index	endpoint_index
83#define ued_desc		desc
84
85/* struct usb_full_desc */
86#define ufd_config_index	config_index
87#define ufd_size		size
88#define ufd_data		data
89
90/* struct usb_string_desc */
91#define usd_string_index	string_index
92#define usd_language_id		language_id
93#define usd_desc		desc
94
95/* struct usb_ctl_report_desc */
96#define ucrd_size		size
97#define ucrd_data		data
98
99/* struct usb_device_info */
100#define udi_bus			bus
101#define udi_addr		addr
102#define udi_cookie		cookie
103#define udi_product		product
104#define udi_vendor		vendor
105#define udi_release		release
106#define udi_productNo		productNo
107#define udi_vendorNo		vendorNo
108#define udi_releaseNo		releaseNo
109#define udi_class		class
110#define udi_subclass		subclass
111#define udi_protocol		protocol
112#define udi_config		config
113#define udi_lowspeed		lowspeed
114#define udi_power		power
115#define udi_nports		nports
116#define udi_devnames		devnames
117#define udi_ports		ports
118
119/* struct usb_ctl_report */
120#define ucr_report		report
121#define ucr_data		data
122
123/* struct usb_device_stats */
124#define uds_requests		requests
125#endif
126
127static int ensure_ep_open(usb_dev_handle *dev, int ep, int mode);
128
129#define MAX_CONTROLLERS 10
130
131/* This records the file descriptors for the endpoints.  It doesn't seem
132   to work to re-open them for each read (as well as being inefficient). */
133
134struct bsd_usb_dev_handle_info {
135    int ep_fd[USB_MAX_ENDPOINTS];
136};
137
138int usb_os_open(usb_dev_handle *dev)
139{
140  int i;
141  struct bsd_usb_dev_handle_info *info;
142  char ctlpath[PATH_MAX + 1];
143
144  info = malloc(sizeof(struct bsd_usb_dev_handle_info));
145  if (!info)
146    USB_ERROR(-ENOMEM);
147  dev->impl_info = info;
148
149#ifdef __FreeBSD_kernel__
150  snprintf(ctlpath, PATH_MAX, "%s", dev->device->filename);
151#else
152  snprintf(ctlpath, PATH_MAX, "%s.00", dev->device->filename);
153#endif
154  dev->fd = open(ctlpath, O_RDWR);
155  if (dev->fd < 0) {
156    dev->fd = open(ctlpath, O_RDONLY);
157    if (dev->fd < 0) {
158      free(info);
159      USB_ERROR_STR(-errno, "failed to open %s: %s",
160                    ctlpath, strerror(errno));
161    }
162  }
163
164  /* Mark the endpoints as not yet open */
165  for (i = 0; i < USB_MAX_ENDPOINTS; i++)
166    info->ep_fd[i] = -1;
167
168  return 0;
169}
170
171int usb_os_close(usb_dev_handle *dev)
172{
173  struct bsd_usb_dev_handle_info *info = dev->impl_info;
174  int i;
175
176  /* Close any open endpoints */
177
178  for (i = 0; i < USB_MAX_ENDPOINTS; i++)
179    if (info->ep_fd[i] >= 0) {
180      if (usb_debug >= 2)
181        fprintf(stderr, "usb_os_close: closing endpoint %d\n", info->ep_fd[i]);
182      close(info->ep_fd[i]);
183    }
184
185  free(info);
186
187  if (dev->fd <= 0)
188    return 0;
189
190  if (close(dev->fd) == -1)
191    /* Failing trying to close a file really isn't an error, so return 0 */
192    USB_ERROR_STR(0, "tried to close device fd %d: %s", dev->fd,
193                  strerror(errno));
194
195  return 0;
196}
197
198int usb_set_configuration(usb_dev_handle *dev, int configuration)
199{
200  int ret;
201
202  ret = ioctl(dev->fd, USB_SET_CONFIG, &configuration);
203  if (ret < 0)
204    USB_ERROR_STR(-errno, "could not set config %d: %s", configuration,
205                  strerror(errno));
206
207  dev->config = configuration;
208
209  return 0;
210}
211
212int usb_claim_interface(usb_dev_handle *dev, int interface)
213{
214  /* BSD doesn't have the corresponding ioctl.  It seems to
215     be sufficient to open the relevant endpoints as needed. */
216
217  dev->interface = interface;
218
219  return 0;
220}
221
222int usb_release_interface(usb_dev_handle *dev, int interface)
223{
224  /* See above */
225  return 0;
226}
227
228int usb_set_altinterface(usb_dev_handle *dev, int alternate)
229{
230  int ret;
231  struct usb_alt_interface intf;
232
233  if (dev->interface < 0)
234    USB_ERROR(-EINVAL);
235
236  intf.uai_interface_index = dev->interface;
237  intf.uai_alt_no = alternate;
238
239  ret = ioctl(dev->fd, USB_SET_ALTINTERFACE, &intf);
240  if (ret < 0)
241    USB_ERROR_STR(-errno, "could not set alt intf %d/%d: %s",
242                  dev->interface, alternate, strerror(errno));
243
244  dev->altsetting = alternate;
245
246  return 0;
247}
248
249static int ensure_ep_open(usb_dev_handle *dev, int ep, int mode)
250{
251  struct bsd_usb_dev_handle_info *info = dev->impl_info;
252  int fd;
253  char buf[20];
254
255  /* Get the address for this endpoint; we could check
256   * the mode against the direction; but we've done that
257   * already in the usb_bulk_read/write.
258   */
259  ep = UE_GET_ADDR(ep);
260
261  if (info->ep_fd[ep] < 0) {
262#ifdef __FreeBSD_kernel__
263    snprintf(buf, sizeof(buf) - 1, "%s.%d", dev->device->filename, ep);
264#else
265    snprintf(buf, sizeof(buf) - 1, "%s.%02d", dev->device->filename, ep);
266#endif
267    /* Try to open it O_RDWR first for those devices which have in and out
268     * endpoints with the same address (eg 0x02 and 0x82)
269     */
270    fd = open(buf, O_RDWR);
271    if (fd < 0 && errno == ENXIO)
272      fd = open(buf, mode);
273    if (fd < 0)
274      USB_ERROR_STR(-errno, "can't open %s for bulk read: %s",
275                    buf, strerror(errno));
276    info->ep_fd[ep] = fd;
277  }
278
279  return info->ep_fd[ep];
280}
281
282int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size,
283                   int timeout)
284{
285  int fd, ret;
286
287  /* Ensure the endpoint address is correct */
288  ep &= ~USB_ENDPOINT_IN;
289
290  fd = ensure_ep_open(dev, ep, O_WRONLY);
291  if (fd < 0) {
292    if (usb_debug >= 2) {
293#ifdef __FreeBSD_kernel__
294      fprintf (stderr, "usb_bulk_write: got negative open file descriptor for endpoint %d\n", UE_GET_ADDR(ep));
295#else
296      fprintf (stderr, "usb_bulk_write: got negative open file descriptor for endpoint %02d\n", UE_GET_ADDR(ep));
297#endif
298    }
299    return fd;
300  }
301
302  ret = ioctl(fd, USB_SET_TIMEOUT, &timeout);
303  if (ret < 0)
304    USB_ERROR_STR(-errno, "error setting timeout: %s",
305                  strerror(errno));
306
307  ret = write(fd, bytes, size);
308  if (ret < 0)
309#ifdef __FreeBSD_kernel__
310    USB_ERROR_STR(-errno, "error writing to bulk endpoint %s.%d: %s",
311                  dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
312#else
313    USB_ERROR_STR(-errno, "error writing to bulk endpoint %s.%02d: %s",
314                dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
315#endif
316
317  return size;
318}
319
320int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size,
321                  int timeout)
322{
323  int fd, ret, one = 1;
324
325  /* Ensure the endpoint address is correct */
326  ep |= USB_ENDPOINT_IN;
327
328  fd = ensure_ep_open(dev, ep, O_RDONLY);
329  if (fd < 0) {
330    if (usb_debug >= 2) {
331#ifdef __FreeBSD_kernel__
332      fprintf (stderr, "usb_bulk_read: got negative open file descriptor for endpoint %d\n", UE_GET_ADDR(ep));
333#else
334      fprintf (stderr, "usb_bulk_read: got negative open file descriptor for endpoint %02d\n", UE_GET_ADDR(ep));
335#endif
336    }
337    return fd;
338  }
339
340  ret = ioctl(fd, USB_SET_TIMEOUT, &timeout);
341  if (ret < 0)
342    USB_ERROR_STR(-errno, "error setting timeout: %s", strerror(errno));
343
344  ret = ioctl(fd, USB_SET_SHORT_XFER, &one);
345  if (ret < 0)
346    USB_ERROR_STR(-errno, "error setting short xfer: %s", strerror(errno));
347
348  ret = read(fd, bytes, size);
349  if (ret < 0)
350#ifdef __FreeBSD_kernel__
351    USB_ERROR_STR(-errno, "error reading from bulk endpoint %s.%d: %s",
352                  dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
353#else
354    USB_ERROR_STR(-errno, "error reading from bulk endpoint %s.%02d: %s",
355                dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
356#endif
357
358  return ret;
359}
360
361int usb_interrupt_write(usb_dev_handle *dev, int ep, char *bytes, int size,
362                        int timeout)
363{
364  int fd, ret, sent = 0;
365
366  /* Ensure the endpoint address is correct */
367  ep &= ~USB_ENDPOINT_IN;
368
369  fd = ensure_ep_open(dev, ep, O_WRONLY);
370  if (fd < 0) {
371    if (usb_debug >= 2) {
372#ifdef __FreeBSD_kernel__
373      fprintf (stderr, "usb_interrupt_write: got negative open file descriptor for endpoint %d\n", UE_GET_ADDR(ep));
374#else
375      fprintf (stderr, "usb_interrupt_write: got negative open file descriptor for endpoint %02d\n", UE_GET_ADDR(ep));
376#endif
377    }
378    return fd;
379  }
380
381  ret = ioctl(fd, USB_SET_TIMEOUT, &timeout);
382  if (ret < 0)
383    USB_ERROR_STR(-errno, "error setting timeout: %s",
384                  strerror(errno));
385
386  do {
387    ret = write(fd, bytes+sent, size-sent);
388    if (ret < 0)
389#ifdef __FreeBSD_kernel__
390      USB_ERROR_STR(-errno, "error writing to interrupt endpoint %s.%d: %s",
391                    dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
392#else
393      USB_ERROR_STR(-errno, "error writing to interrupt endpoint %s.%02d: %s",
394                  dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
395#endif
396
397    sent += ret;
398  } while (ret > 0 && sent < size);
399
400  return sent;
401}
402
403int usb_interrupt_read(usb_dev_handle *dev, int ep, char *bytes, int size,
404                       int timeout)
405{
406  int fd, ret, retrieved = 0, one = 1;
407
408  /* Ensure the endpoint address is correct */
409  ep |= USB_ENDPOINT_IN;
410
411  fd = ensure_ep_open(dev, ep, O_RDONLY);
412  if (fd < 0) {
413    if (usb_debug >= 2) {
414#ifdef __FreeBSD_kernel__
415      fprintf (stderr, "usb_interrupt_read: got negative open file descriptor for endpoint %d\n", UE_GET_ADDR(ep));
416#else
417      fprintf (stderr, "usb_interrupt_read: got negative open file descriptor for endpoint %02d\n", UE_GET_ADDR(ep));
418#endif
419    }
420    return fd;
421  }
422
423  ret = ioctl(fd, USB_SET_TIMEOUT, &timeout);
424  if (ret < 0)
425    USB_ERROR_STR(-errno, "error setting timeout: %s", strerror(errno));
426
427  ret = ioctl(fd, USB_SET_SHORT_XFER, &one);
428  if (ret < 0)
429    USB_ERROR_STR(-errno, "error setting short xfer: %s", strerror(errno));
430
431  do {
432    ret = read(fd, bytes+retrieved, size-retrieved);
433    if (ret < 0)
434#ifdef __FreeBSD_kernel__
435      USB_ERROR_STR(-errno, "error reading from interrupt endpoint %s.%d: %s",
436                    dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
437#else
438      USB_ERROR_STR(-errno, "error reading from interrupt endpoint %s.%02d: %s",
439                  dev->device->filename, UE_GET_ADDR(ep), strerror(errno));
440#endif
441    retrieved += ret;
442  } while (ret > 0 && retrieved < size);
443
444  return retrieved;
445}
446
447int usb_control_msg(usb_dev_handle *dev, int requesttype, int request,
448                     int value, int index, char *bytes, int size, int timeout)
449{
450  struct usb_ctl_request req;
451  int ret;
452
453  if (usb_debug >= 3)
454    fprintf(stderr, "usb_control_msg: %d %d %d %d %p %d %d\n",
455            requesttype, request, value, index, bytes, size, timeout);
456
457  req.ucr_request.bmRequestType = requesttype;
458  req.ucr_request.bRequest = request;
459  USETW(req.ucr_request.wValue, value);
460  USETW(req.ucr_request.wIndex, index);
461  USETW(req.ucr_request.wLength, size);
462
463  req.ucr_data = bytes;
464  req.ucr_flags = USBD_SHORT_XFER_OK;
465
466  ret = ioctl(dev->fd, USB_SET_TIMEOUT, &timeout);
467#if (__NetBSD__ || __OpenBSD__)
468  if (ret < 0 && errno != EINVAL)
469#else
470  if (ret < 0)
471#endif
472    USB_ERROR_STR(-errno, "error setting timeout: %s",
473                  strerror(errno));
474
475  ret = ioctl(dev->fd, USB_DO_REQUEST, &req);
476  if (ret < 0)
477    USB_ERROR_STR(-errno, "error sending control message: %s",
478                  strerror(errno));
479
480  return UGETW(req.ucr_request.wLength);
481}
482
483int usb_os_find_busses(struct usb_bus **busses)
484{
485  struct usb_bus *fbus = NULL;
486  int controller;
487  int fd;
488  char buf[20];
489
490  for (controller = 0; controller < MAX_CONTROLLERS; controller++) {
491    struct usb_bus *bus;
492
493    snprintf(buf, sizeof(buf) - 1, "/dev/usb%d", controller);
494    fd = open(buf, O_RDWR);
495    if (fd < 0) {
496      if (usb_debug >= 2)
497        if (errno != ENXIO && errno != ENOENT)
498          fprintf(stderr, "usb_os_find_busses: can't open %s: %s\n",
499                  buf, strerror(errno));
500      continue;
501    }
502    close(fd);
503
504    bus = malloc(sizeof(*bus));
505    if (!bus)
506      USB_ERROR(-ENOMEM);
507
508    memset((void *)bus, 0, sizeof(*bus));
509
510    strncpy(bus->dirname, buf, sizeof(bus->dirname) - 1);
511    bus->dirname[sizeof(bus->dirname) - 1] = 0;
512
513    LIST_ADD(fbus, bus);
514
515    if (usb_debug >= 2)
516      fprintf(stderr, "usb_os_find_busses: Found %s\n", bus->dirname);
517  }
518
519  *busses = fbus;
520
521  return 0;
522}
523
524int usb_os_find_devices(struct usb_bus *bus, struct usb_device **devices)
525{
526  struct usb_device *fdev = NULL;
527  int cfd, dfd;
528  int device;
529
530  cfd = open(bus->dirname, O_RDONLY);
531  if (cfd < 0)
532    USB_ERROR_STR(-errno, "couldn't open(%s): %s", bus->dirname,
533                  strerror(errno));
534
535  for (device = 1; device < USB_MAX_DEVICES; device++) {
536    struct usb_device_info di;
537    struct usb_device *dev;
538    unsigned char device_desc[DEVICE_DESC_LENGTH];
539    char buf[20];
540
541    di.udi_addr = device;
542    if (ioctl(cfd, USB_DEVICEINFO, &di) < 0)
543      continue;
544
545    /* There's a device; is it one we should mess with? */
546
547    if (strncmp(di.udi_devnames[0], "ugen", 4) != 0)
548      /* best not to play with things we don't understand */
549      continue;
550
551#ifdef __FreeBSD_kernel__
552    snprintf(buf, sizeof(buf) - 1, "/dev/%s", di.udi_devnames[0]);
553#else
554    snprintf(buf, sizeof(buf) - 1, "/dev/%s.00", di.udi_devnames[0]);
555#endif
556
557    /* Open its control endpoint */
558    dfd = open(buf, O_RDONLY);
559    if (dfd < 0) {
560      if (usb_debug >= 2)
561        fprintf(stderr, "usb_os_find_devices: couldn't open device %s: %s\n",
562                buf, strerror(errno));
563      continue;
564    }
565
566    dev = malloc(sizeof(*dev));
567    if (!dev)
568      USB_ERROR(-ENOMEM);
569
570    memset((void *)dev, 0, sizeof(*dev));
571
572    dev->bus = bus;
573
574    /* we need to report the device name as /dev/ugenx NOT /dev/ugenx.00
575     * This seemed easier than having 2 variables...
576     */
577#if (__NetBSD__ || __OpenBSD__)
578    snprintf(buf, sizeof(buf) - 1, "/dev/%s", di.udi_devnames[0]);
579#endif
580
581    strncpy(dev->filename, buf, sizeof(dev->filename) - 1);
582    dev->filename[sizeof(dev->filename) - 1] = 0;
583
584    if (ioctl(dfd, USB_GET_DEVICE_DESC, device_desc) < 0)
585      USB_ERROR_STR(-errno, "couldn't get device descriptor for %s: %s",
586                    buf, strerror(errno));
587
588    close(dfd);
589
590    usb_parse_descriptor(device_desc, "bbwbbbbwwwbbbb", &dev->descriptor);
591
592    LIST_ADD(fdev, dev);
593
594    if (usb_debug >= 2)
595      fprintf(stderr, "usb_os_find_devices: Found %s on %s\n",
596              dev->filename, bus->dirname);
597  }
598
599  close(cfd);
600
601  *devices = fdev;
602
603  return 0;
604}
605
606int usb_os_determine_children(struct usb_bus *bus)
607{
608  /* Nothing yet */
609  return 0;
610}
611
612void usb_os_init(void)
613{
614  /* nothing */
615}
616
617int usb_resetep(usb_dev_handle *dev, unsigned int ep)
618{
619  /* Not yet done, because I haven't needed it. */
620
621  USB_ERROR_STR(-ENOSYS, "usb_resetep called, unimplemented on BSD");
622}
623
624int usb_clear_halt(usb_dev_handle *dev, unsigned int ep)
625{
626  /* Not yet done, because I haven't needed it. */
627
628  USB_ERROR_STR(-ENOSYS, "usb_clear_halt called, unimplemented on BSD");
629}
630
631int usb_reset(usb_dev_handle *dev)
632{
633  /* Not yet done, because I haven't needed it. */
634
635  USB_ERROR_STR(-ENOSYS, "usb_reset called, unimplemented on BSD");
636}
637
638