/* * (Free|Open|Net)BSD USB support * * Derived from Linux version by Richard Tobin. * * $Id: bsd.c,v 1.33 2006/03/04 01:16:10 jerdfelt Exp $ * $Name: $ * * This library is covered by the LGPL, read LICENSE for details. */ /* * Note: I don't have a clue what I'm doing. I just looked at the * man pages and source to try and find things that did the same as * the Linux version. -- Richard * * johnjen@reynoldsnet.org - minor fixes with debug mode output. Consistent brace * use as well as indenting. More error messages put in to test for failure * modes with /dev/ permissions (when it happens). Note: I, like Richard, have * no clue what I'm doing. Patches to increase/fix functionality happily * accepted! */ /* dirkx@webweaving.org - minor changes to make things actually work * for both read and write. */ #if defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) #define __FreeBSD_kernel__ __FreeBSD__ #endif #include #include #include #include #include #include #include #include #include #include #include "usbi.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_OLD_DEV_USB_USB_H /* * It appears some of the BSD's (OpenBSD atleast) have switched over to a * new naming convention, so we setup some macro's for backward * compability with older versions --jerdfelt */ /* struct usb_ctl_request */ #define ucr_addr addr #define ucr_request request #define ucr_data data #define ucr_flags flags #define ucr_actlen actlen /* struct usb_alt_interface */ #define uai_config_index config_index #define uai_interface_index interface_index #define uai_alt_no alt_no /* struct usb_config_desc */ #define ucd_config_index config_index #define ucd_desc desc /* struct usb_interface_desc */ #define uid_config_index config_index #define uid_interface_index interface_index #define uid_alt_index alt_index #define uid_desc desc /* struct usb_endpoint_desc */ #define ued_config_index config_index #define ued_interface_index interface_index #define ued_alt_index alt_index #define ued_endpoint_index endpoint_index #define ued_desc desc /* struct usb_full_desc */ #define ufd_config_index config_index #define ufd_size size #define ufd_data data /* struct usb_string_desc */ #define usd_string_index string_index #define usd_language_id language_id #define usd_desc desc /* struct usb_ctl_report_desc */ #define ucrd_size size #define ucrd_data data /* struct usb_device_info */ #define udi_bus bus #define udi_addr addr #define udi_cookie cookie #define udi_product product #define udi_vendor vendor #define udi_release release #define udi_productNo productNo #define udi_vendorNo vendorNo #define udi_releaseNo releaseNo #define udi_class class #define udi_subclass subclass #define udi_protocol protocol #define udi_config config #define udi_lowspeed lowspeed #define udi_power power #define udi_nports nports #define udi_devnames devnames #define udi_ports ports /* struct usb_ctl_report */ #define ucr_report report #define ucr_data data /* struct usb_device_stats */ #define uds_requests requests #endif static int ensure_ep_open(usb_dev_handle *dev, int ep, int mode); #define MAX_CONTROLLERS 10 /* This records the file descriptors for the endpoints. It doesn't seem to work to re-open them for each read (as well as being inefficient). */ struct bsd_usb_dev_handle_info { int ep_fd[USB_MAX_ENDPOINTS]; }; int usb_os_open(usb_dev_handle *dev) { int i; struct bsd_usb_dev_handle_info *info; char ctlpath[PATH_MAX + 1]; info = malloc(sizeof(struct bsd_usb_dev_handle_info)); if (!info) USB_ERROR(-ENOMEM); dev->impl_info = info; #ifdef __FreeBSD_kernel__ snprintf(ctlpath, PATH_MAX, "%s", dev->device->filename); #else snprintf(ctlpath, PATH_MAX, "%s.00", dev->device->filename); #endif dev->fd = open(ctlpath, O_RDWR); if (dev->fd < 0) { dev->fd = open(ctlpath, O_RDONLY); if (dev->fd < 0) { free(info); USB_ERROR_STR(-errno, "failed to open %s: %s", ctlpath, strerror(errno)); } } /* Mark the endpoints as not yet open */ for (i = 0; i < USB_MAX_ENDPOINTS; i++) info->ep_fd[i] = -1; return 0; } int usb_os_close(usb_dev_handle *dev) { struct bsd_usb_dev_handle_info *info = dev->impl_info; int i; /* Close any open endpoints */ for (i = 0; i < USB_MAX_ENDPOINTS; i++) if (info->ep_fd[i] >= 0) { if (usb_debug >= 2) fprintf(stderr, "usb_os_close: closing endpoint %d\n", info->ep_fd[i]); close(info->ep_fd[i]); } free(info); if (dev->fd <= 0) return 0; if (close(dev->fd) == -1) /* Failing trying to close a file really isn't an error, so return 0 */ USB_ERROR_STR(0, "tried to close device fd %d: %s", dev->fd, strerror(errno)); return 0; } int usb_set_configuration(usb_dev_handle *dev, int configuration) { int ret; ret = ioctl(dev->fd, USB_SET_CONFIG, &configuration); if (ret < 0) USB_ERROR_STR(-errno, "could not set config %d: %s", configuration, strerror(errno)); dev->config = configuration; return 0; } int usb_claim_interface(usb_dev_handle *dev, int interface) { /* BSD doesn't have the corresponding ioctl. It seems to be sufficient to open the relevant endpoints as needed. */ dev->interface = interface; return 0; } int usb_release_interface(usb_dev_handle *dev, int interface) { /* See above */ return 0; } int usb_set_altinterface(usb_dev_handle *dev, int alternate) { int ret; struct usb_alt_interface intf; if (dev->interface < 0) USB_ERROR(-EINVAL); intf.uai_interface_index = dev->interface; intf.uai_alt_no = alternate; ret = ioctl(dev->fd, USB_SET_ALTINTERFACE, &intf); if (ret < 0) USB_ERROR_STR(-errno, "could not set alt intf %d/%d: %s", dev->interface, alternate, strerror(errno)); dev->altsetting = alternate; return 0; } 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; } return info->ep_fd[ep]; } int usb_bulk_write(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout) { int fd, ret; /* Ensure the endpoint address is correct */ ep &= ~USB_ENDPOINT_IN; fd = ensure_ep_open(dev, ep, O_WRONLY); if (fd < 0) { if (usb_debug >= 2) { #ifdef __FreeBSD_kernel__ fprintf (stderr, "usb_bulk_write: got negative open file descriptor for endpoint %d\n", UE_GET_ADDR(ep)); #else fprintf (stderr, "usb_bulk_write: 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 = write(fd, bytes, size); if (ret < 0) #ifdef __FreeBSD_kernel__ USB_ERROR_STR(-errno, "error writing to bulk endpoint %s.%d: %s", dev->device->filename, UE_GET_ADDR(ep), strerror(errno)); #else USB_ERROR_STR(-errno, "error writing to bulk endpoint %s.%02d: %s", dev->device->filename, UE_GET_ADDR(ep), strerror(errno)); #endif return size; } int usb_bulk_read(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout) { int fd, ret, one = 1; /* Ensure the endpoint address is correct */ ep |= USB_ENDPOINT_IN; fd = ensure_ep_open(dev, ep, O_RDONLY); if (fd < 0) { if (usb_debug >= 2) { #ifdef __FreeBSD_kernel__ fprintf (stderr, "usb_bulk_read: got negative open file descriptor for endpoint %d\n", UE_GET_ADDR(ep)); #else fprintf (stderr, "usb_bulk_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)); ret = read(fd, bytes, size); if (ret < 0) #ifdef __FreeBSD_kernel__ USB_ERROR_STR(-errno, "error reading from bulk endpoint %s.%d: %s", dev->device->filename, UE_GET_ADDR(ep), strerror(errno)); #else USB_ERROR_STR(-errno, "error reading from bulk endpoint %s.%02d: %s", dev->device->filename, UE_GET_ADDR(ep), strerror(errno)); #endif return ret; } int usb_interrupt_write(usb_dev_handle *dev, int ep, char *bytes, int size, int timeout) { int fd, ret, sent = 0; /* Ensure the endpoint address is correct */ ep &= ~USB_ENDPOINT_IN; fd = ensure_ep_open(dev, ep, O_WRONLY); if (fd < 0) { if (usb_debug >= 2) { #ifdef __FreeBSD_kernel__ fprintf (stderr, "usb_interrupt_write: got negative open file descriptor for endpoint %d\n", UE_GET_ADDR(ep)); #else fprintf (stderr, "usb_interrupt_write: 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)); do { ret = write(fd, bytes+sent, size-sent); if (ret < 0) #ifdef __FreeBSD_kernel__ USB_ERROR_STR(-errno, "error writing to interrupt endpoint %s.%d: %s", dev->device->filename, UE_GET_ADDR(ep), strerror(errno)); #else USB_ERROR_STR(-errno, "error writing to interrupt endpoint %s.%02d: %s", dev->device->filename, UE_GET_ADDR(ep), strerror(errno)); #endif sent += ret; } while (ret > 0 && sent < size); return sent; } 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; fd = ensure_ep_open(dev, ep, O_RDONLY); if (fd < 0) { 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; } int usb_control_msg(usb_dev_handle *dev, int requesttype, int request, int value, int index, char *bytes, int size, int timeout) { struct usb_ctl_request req; int ret; if (usb_debug >= 3) fprintf(stderr, "usb_control_msg: %d %d %d %d %p %d %d\n", requesttype, request, value, index, bytes, size, timeout); req.ucr_request.bmRequestType = requesttype; req.ucr_request.bRequest = request; USETW(req.ucr_request.wValue, value); USETW(req.ucr_request.wIndex, index); USETW(req.ucr_request.wLength, size); req.ucr_data = bytes; req.ucr_flags = USBD_SHORT_XFER_OK; ret = ioctl(dev->fd, USB_SET_TIMEOUT, &timeout); #if (__NetBSD__ || __OpenBSD__) if (ret < 0 && errno != EINVAL) #else if (ret < 0) #endif USB_ERROR_STR(-errno, "error setting timeout: %s", strerror(errno)); ret = ioctl(dev->fd, USB_DO_REQUEST, &req); if (ret < 0) USB_ERROR_STR(-errno, "error sending control message: %s", strerror(errno)); return UGETW(req.ucr_request.wLength); } int usb_os_find_busses(struct usb_bus **busses) { struct usb_bus *fbus = NULL; int controller; int fd; char buf[20]; for (controller = 0; controller < MAX_CONTROLLERS; controller++) { struct usb_bus *bus; snprintf(buf, sizeof(buf) - 1, "/dev/usb%d", controller); fd = open(buf, O_RDWR); if (fd < 0) { if (usb_debug >= 2) if (errno != ENXIO && errno != ENOENT) fprintf(stderr, "usb_os_find_busses: can't open %s: %s\n", buf, strerror(errno)); continue; } close(fd); bus = malloc(sizeof(*bus)); if (!bus) USB_ERROR(-ENOMEM); memset((void *)bus, 0, sizeof(*bus)); strncpy(bus->dirname, buf, sizeof(bus->dirname) - 1); bus->dirname[sizeof(bus->dirname) - 1] = 0; LIST_ADD(fbus, bus); if (usb_debug >= 2) fprintf(stderr, "usb_os_find_busses: Found %s\n", bus->dirname); } *busses = fbus; return 0; } int usb_os_find_devices(struct usb_bus *bus, struct usb_device **devices) { struct usb_device *fdev = NULL; int cfd, dfd; int device; cfd = open(bus->dirname, O_RDONLY); if (cfd < 0) USB_ERROR_STR(-errno, "couldn't open(%s): %s", bus->dirname, strerror(errno)); for (device = 1; device < USB_MAX_DEVICES; device++) { struct usb_device_info di; struct usb_device *dev; unsigned char device_desc[DEVICE_DESC_LENGTH]; char buf[20]; di.udi_addr = device; if (ioctl(cfd, USB_DEVICEINFO, &di) < 0) continue; /* There's a device; is it one we should mess with? */ if (strncmp(di.udi_devnames[0], "ugen", 4) != 0) /* best not to play with things we don't understand */ continue; #ifdef __FreeBSD_kernel__ snprintf(buf, sizeof(buf) - 1, "/dev/%s", di.udi_devnames[0]); #else snprintf(buf, sizeof(buf) - 1, "/dev/%s.00", di.udi_devnames[0]); #endif /* Open its control endpoint */ dfd = open(buf, O_RDONLY); if (dfd < 0) { if (usb_debug >= 2) fprintf(stderr, "usb_os_find_devices: couldn't open device %s: %s\n", buf, strerror(errno)); continue; } dev = malloc(sizeof(*dev)); if (!dev) USB_ERROR(-ENOMEM); memset((void *)dev, 0, sizeof(*dev)); dev->bus = bus; /* we need to report the device name as /dev/ugenx NOT /dev/ugenx.00 * This seemed easier than having 2 variables... */ #if (__NetBSD__ || __OpenBSD__) snprintf(buf, sizeof(buf) - 1, "/dev/%s", di.udi_devnames[0]); #endif strncpy(dev->filename, buf, sizeof(dev->filename) - 1); dev->filename[sizeof(dev->filename) - 1] = 0; if (ioctl(dfd, USB_GET_DEVICE_DESC, device_desc) < 0) USB_ERROR_STR(-errno, "couldn't get device descriptor for %s: %s", buf, strerror(errno)); close(dfd); usb_parse_descriptor(device_desc, "bbwbbbbwwwbbbb", &dev->descriptor); LIST_ADD(fdev, dev); if (usb_debug >= 2) fprintf(stderr, "usb_os_find_devices: Found %s on %s\n", dev->filename, bus->dirname); } close(cfd); *devices = fdev; return 0; } int usb_os_determine_children(struct usb_bus *bus) { /* Nothing yet */ return 0; } void usb_os_init(void) { /* nothing */ } int usb_resetep(usb_dev_handle *dev, unsigned int ep) { /* Not yet done, because I haven't needed it. */ USB_ERROR_STR(-ENOSYS, "usb_resetep called, unimplemented on BSD"); } int usb_clear_halt(usb_dev_handle *dev, unsigned int ep) { /* Not yet done, because I haven't needed it. */ USB_ERROR_STR(-ENOSYS, "usb_clear_halt called, unimplemented on BSD"); } int usb_reset(usb_dev_handle *dev) { /* Not yet done, because I haven't needed it. */ USB_ERROR_STR(-ENOSYS, "usb_reset called, unimplemented on BSD"); }