| /* |
| * |
| * Copyright (c) 2016, Oracle and/or its affiliates. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <config.h> |
| |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <strings.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <wait.h> |
| #include <unistd.h> |
| #include <aio.h> |
| #include <libdevinfo.h> |
| #include <sys/nvpair.h> |
| #include <sys/devctl.h> |
| #include <sys/usb/clients/ugen/usb_ugen.h> |
| #include <sys/usb/usba.h> |
| #include <sys/pci.h> |
| |
| #include "libusbi.h" |
| #include "sunos_usb.h" |
| |
| #define UPDATEDRV_PATH "/usr/sbin/update_drv" |
| #define UPDATEDRV "update_drv" |
| |
| #define DEFAULT_LISTSIZE 6 |
| |
| typedef struct { |
| int nargs; |
| int listsize; |
| char **string; |
| } string_list_t; |
| |
| /* |
| * Backend functions |
| */ |
| static int sunos_get_device_list(struct libusb_context *, |
| struct discovered_devs **); |
| static int sunos_open(struct libusb_device_handle *); |
| static void sunos_close(struct libusb_device_handle *); |
| static int sunos_get_active_config_descriptor(struct libusb_device *, |
| void *, size_t); |
| static int sunos_get_config_descriptor(struct libusb_device *, uint8_t, |
| void *, size_t); |
| static int sunos_get_configuration(struct libusb_device_handle *, uint8_t *); |
| static int sunos_set_configuration(struct libusb_device_handle *, int); |
| static int sunos_claim_interface(struct libusb_device_handle *, uint8_t); |
| static int sunos_release_interface(struct libusb_device_handle *, uint8_t); |
| static int sunos_set_interface_altsetting(struct libusb_device_handle *, |
| uint8_t, uint8_t); |
| static int sunos_clear_halt(struct libusb_device_handle *, unsigned char); |
| static void sunos_destroy_device(struct libusb_device *); |
| static int sunos_submit_transfer(struct usbi_transfer *); |
| static int sunos_cancel_transfer(struct usbi_transfer *); |
| static int sunos_handle_transfer_completion(struct usbi_transfer *); |
| static int sunos_kernel_driver_active(struct libusb_device_handle *, uint8_t); |
| static int sunos_detach_kernel_driver(struct libusb_device_handle *, uint8_t); |
| static int sunos_attach_kernel_driver(struct libusb_device_handle *, uint8_t); |
| static int sunos_usb_open_ep0(sunos_dev_handle_priv_t *hpriv, sunos_dev_priv_t *dpriv); |
| static int sunos_usb_ioctl(struct libusb_device *dev, int cmd); |
| |
| static int sunos_get_link(di_devlink_t devlink, void *arg) |
| { |
| walk_link_t *larg = (walk_link_t *)arg; |
| const char *p; |
| const char *q; |
| |
| if (larg->path) { |
| char *content = (char *)di_devlink_content(devlink); |
| char *start = strstr(content, "/devices/"); |
| start += strlen("/devices"); |
| usbi_dbg("%s", start); |
| |
| /* line content must have minor node */ |
| if (start == NULL || |
| strncmp(start, larg->path, larg->len) != 0 || |
| start[larg->len] != ':') |
| return (DI_WALK_CONTINUE); |
| } |
| |
| p = di_devlink_path(devlink); |
| q = strrchr(p, '/'); |
| usbi_dbg("%s", q); |
| |
| *(larg->linkpp) = strndup(p, strlen(p) - strlen(q)); |
| |
| return (DI_WALK_TERMINATE); |
| } |
| |
| |
| static int sunos_physpath_to_devlink( |
| const char *node_path, const char *match, char **link_path) |
| { |
| walk_link_t larg; |
| di_devlink_handle_t hdl; |
| |
| *link_path = NULL; |
| larg.linkpp = link_path; |
| if ((hdl = di_devlink_init(NULL, 0)) == NULL) { |
| usbi_dbg("di_devlink_init failure"); |
| return (-1); |
| } |
| |
| larg.len = strlen(node_path); |
| larg.path = (char *)node_path; |
| |
| (void) di_devlink_walk(hdl, match, NULL, DI_PRIMARY_LINK, |
| (void *)&larg, sunos_get_link); |
| |
| (void) di_devlink_fini(&hdl); |
| |
| if (*link_path == NULL) { |
| usbi_dbg("there is no devlink for this path"); |
| return (-1); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| sunos_usb_ioctl(struct libusb_device *dev, int cmd) |
| { |
| int fd; |
| nvlist_t *nvlist; |
| char *end; |
| char *phypath; |
| char *hubpath; |
| char path_arg[PATH_MAX]; |
| sunos_dev_priv_t *dpriv; |
| devctl_ap_state_t devctl_ap_state; |
| struct devctl_iocdata iocdata; |
| |
| dpriv = usbi_get_device_priv(dev); |
| phypath = dpriv->phypath; |
| |
| end = strrchr(phypath, '/'); |
| if (end == NULL) |
| return (-1); |
| hubpath = strndup(phypath, end - phypath); |
| if (hubpath == NULL) |
| return (-1); |
| |
| end = strrchr(hubpath, '@'); |
| if (end == NULL) { |
| free(hubpath); |
| return (-1); |
| } |
| end++; |
| usbi_dbg("unitaddr: %s", end); |
| |
| nvlist_alloc(&nvlist, NV_UNIQUE_NAME_TYPE, KM_NOSLEEP); |
| nvlist_add_int32(nvlist, "port", dev->port_number); |
| //find the hub path |
| snprintf(path_arg, sizeof(path_arg), "/devices%s:hubd", hubpath); |
| usbi_dbg("ioctl hub path: %s", path_arg); |
| |
| fd = open(path_arg, O_RDONLY); |
| if (fd < 0) { |
| usbi_err(DEVICE_CTX(dev), "open failed: errno %d (%s)", errno, strerror(errno)); |
| nvlist_free(nvlist); |
| free(hubpath); |
| return (-1); |
| } |
| |
| memset(&iocdata, 0, sizeof(iocdata)); |
| memset(&devctl_ap_state, 0, sizeof(devctl_ap_state)); |
| |
| nvlist_pack(nvlist, (char **)&iocdata.nvl_user, &iocdata.nvl_usersz, NV_ENCODE_NATIVE, 0); |
| |
| iocdata.cmd = DEVCTL_AP_GETSTATE; |
| iocdata.flags = 0; |
| iocdata.c_nodename = (char *)"hub"; |
| iocdata.c_unitaddr = end; |
| iocdata.cpyout_buf = &devctl_ap_state; |
| usbi_dbg("%p, %" PRIuPTR, iocdata.nvl_user, iocdata.nvl_usersz); |
| |
| errno = 0; |
| if (ioctl(fd, DEVCTL_AP_GETSTATE, &iocdata) == -1) { |
| usbi_err(DEVICE_CTX(dev), "ioctl failed: fd %d, cmd %x, errno %d (%s)", |
| fd, DEVCTL_AP_GETSTATE, errno, strerror(errno)); |
| } else { |
| usbi_dbg("dev rstate: %d", devctl_ap_state.ap_rstate); |
| usbi_dbg("dev ostate: %d", devctl_ap_state.ap_ostate); |
| } |
| |
| errno = 0; |
| iocdata.cmd = cmd; |
| if (ioctl(fd, (int)cmd, &iocdata) != 0) { |
| usbi_err(DEVICE_CTX(dev), "ioctl failed: fd %d, cmd %x, errno %d (%s)", |
| fd, cmd, errno, strerror(errno)); |
| sleep(2); |
| } |
| |
| close(fd); |
| free(iocdata.nvl_user); |
| nvlist_free(nvlist); |
| free(hubpath); |
| |
| return (-errno); |
| } |
| |
| static int |
| sunos_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) |
| { |
| sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev_handle->dev); |
| |
| UNUSED(interface); |
| |
| usbi_dbg("%s", dpriv->ugenpath); |
| |
| return (dpriv->ugenpath == NULL); |
| } |
| |
| /* |
| * Private functions |
| */ |
| static int _errno_to_libusb(int); |
| static int sunos_usb_get_status(int fd); |
| |
| static string_list_t * |
| sunos_new_string_list(void) |
| { |
| string_list_t *list; |
| |
| list = calloc(1, sizeof(string_list_t)); |
| if (list == NULL) |
| return (NULL); |
| list->string = calloc(DEFAULT_LISTSIZE, sizeof(char *)); |
| if (list->string == NULL) { |
| free(list); |
| return (NULL); |
| } |
| list->nargs = 0; |
| list->listsize = DEFAULT_LISTSIZE; |
| |
| return (list); |
| } |
| |
| static int |
| sunos_append_to_string_list(string_list_t *list, const char *arg) |
| { |
| char *str = strdup(arg); |
| |
| if (str == NULL) |
| return (-1); |
| |
| if ((list->nargs + 1) == list->listsize) { /* +1 is for NULL */ |
| char **tmp = realloc(list->string, |
| sizeof(char *) * (list->listsize + 1)); |
| if (tmp == NULL) { |
| free(str); |
| return (-1); |
| } |
| list->string = tmp; |
| list->string[list->listsize++] = NULL; |
| } |
| list->string[list->nargs++] = str; |
| |
| return (0); |
| } |
| |
| static void |
| sunos_free_string_list(string_list_t *list) |
| { |
| int i; |
| |
| for (i = 0; i < list->nargs; i++) { |
| free(list->string[i]); |
| } |
| |
| free(list->string); |
| free(list); |
| } |
| |
| static char ** |
| sunos_build_argv_list(string_list_t *list) |
| { |
| return (list->string); |
| } |
| |
| |
| static int |
| sunos_exec_command(struct libusb_context *ctx, const char *path, |
| string_list_t *list) |
| { |
| pid_t pid; |
| int status; |
| int waitstat; |
| int exit_status; |
| char **argv_list; |
| |
| argv_list = sunos_build_argv_list(list); |
| if (argv_list == NULL) |
| return (-1); |
| |
| pid = fork(); |
| if (pid == 0) { |
| /* child */ |
| execv(path, argv_list); |
| _exit(127); |
| } else if (pid > 0) { |
| /* parent */ |
| do { |
| waitstat = waitpid(pid, &status, 0); |
| } while ((waitstat == -1 && errno == EINTR) || |
| (waitstat == 0 && !WIFEXITED(status) && !WIFSIGNALED(status))); |
| |
| if (waitstat == 0) { |
| if (WIFEXITED(status)) |
| exit_status = WEXITSTATUS(status); |
| else |
| exit_status = WTERMSIG(status); |
| } else { |
| usbi_err(ctx, "waitpid failed: errno %d (%s)", errno, strerror(errno)); |
| exit_status = -1; |
| } |
| } else { |
| /* fork failed */ |
| usbi_err(ctx, "fork failed: errno %d (%s)", errno, strerror(errno)); |
| exit_status = -1; |
| } |
| |
| return (exit_status); |
| } |
| |
| static int |
| sunos_detach_kernel_driver(struct libusb_device_handle *dev_handle, |
| uint8_t interface_number) |
| { |
| struct libusb_context *ctx = HANDLE_CTX(dev_handle); |
| string_list_t *list; |
| char path_arg[PATH_MAX]; |
| sunos_dev_priv_t *dpriv; |
| int r; |
| |
| UNUSED(interface_number); |
| |
| dpriv = usbi_get_device_priv(dev_handle->dev); |
| snprintf(path_arg, sizeof(path_arg), "\'\"%s\"\'", dpriv->phypath); |
| usbi_dbg("%s", path_arg); |
| |
| list = sunos_new_string_list(); |
| if (list == NULL) |
| return (LIBUSB_ERROR_NO_MEM); |
| |
| /* attach ugen driver */ |
| r = 0; |
| r |= sunos_append_to_string_list(list, UPDATEDRV); |
| r |= sunos_append_to_string_list(list, "-a"); /* add rule */ |
| r |= sunos_append_to_string_list(list, "-i"); /* specific device */ |
| r |= sunos_append_to_string_list(list, path_arg); /* physical path */ |
| r |= sunos_append_to_string_list(list, "ugen"); |
| if (r) { |
| sunos_free_string_list(list); |
| return (LIBUSB_ERROR_NO_MEM); |
| } |
| |
| r = sunos_exec_command(ctx, UPDATEDRV_PATH, list); |
| sunos_free_string_list(list); |
| if (r < 0) |
| return (LIBUSB_ERROR_OTHER); |
| |
| /* reconfigure the driver node */ |
| r = 0; |
| r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_DISCONNECT); |
| r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_CONFIGURE); |
| if (r) |
| usbi_warn(HANDLE_CTX(dev_handle), "one or more ioctls failed"); |
| |
| snprintf(path_arg, sizeof(path_arg), "^usb/%x.%x", |
| dev_handle->dev->device_descriptor.idVendor, |
| dev_handle->dev->device_descriptor.idProduct); |
| sunos_physpath_to_devlink(dpriv->phypath, path_arg, &dpriv->ugenpath); |
| |
| if (access(dpriv->ugenpath, F_OK) == -1) { |
| usbi_err(HANDLE_CTX(dev_handle), "fail to detach kernel driver"); |
| return (LIBUSB_ERROR_IO); |
| } |
| |
| return sunos_usb_open_ep0(usbi_get_device_handle_priv(dev_handle), dpriv); |
| } |
| |
| static int |
| sunos_attach_kernel_driver(struct libusb_device_handle *dev_handle, |
| uint8_t interface_number) |
| { |
| struct libusb_context *ctx = HANDLE_CTX(dev_handle); |
| string_list_t *list; |
| char path_arg[PATH_MAX]; |
| sunos_dev_priv_t *dpriv; |
| int r; |
| |
| UNUSED(interface_number); |
| |
| /* we open the dev in detach driver, so we need close it first. */ |
| sunos_close(dev_handle); |
| |
| dpriv = usbi_get_device_priv(dev_handle->dev); |
| snprintf(path_arg, sizeof(path_arg), "\'\"%s\"\'", dpriv->phypath); |
| usbi_dbg("%s", path_arg); |
| |
| list = sunos_new_string_list(); |
| if (list == NULL) |
| return (LIBUSB_ERROR_NO_MEM); |
| |
| /* detach ugen driver */ |
| r = 0; |
| r |= sunos_append_to_string_list(list, UPDATEDRV); |
| r |= sunos_append_to_string_list(list, "-d"); /* add rule */ |
| r |= sunos_append_to_string_list(list, "-i"); /* specific device */ |
| r |= sunos_append_to_string_list(list, path_arg); /* physical path */ |
| r |= sunos_append_to_string_list(list, "ugen"); |
| if (r) { |
| sunos_free_string_list(list); |
| return (LIBUSB_ERROR_NO_MEM); |
| } |
| |
| r = sunos_exec_command(ctx, UPDATEDRV_PATH, list); |
| sunos_free_string_list(list); |
| if (r < 0) |
| return (LIBUSB_ERROR_OTHER); |
| |
| /* reconfigure the driver node */ |
| r = 0; |
| r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_CONFIGURE); |
| r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_DISCONNECT); |
| r |= sunos_usb_ioctl(dev_handle->dev, DEVCTL_AP_CONFIGURE); |
| if (r) |
| usbi_warn(HANDLE_CTX(dev_handle), "one or more ioctls failed"); |
| |
| return 0; |
| } |
| |
| static int |
| sunos_fill_in_dev_info(di_node_t node, struct libusb_device *dev) |
| { |
| int proplen; |
| int *i, n, *addr, *port_prop; |
| char *phypath; |
| uint8_t *rdata; |
| sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev); |
| char match_str[PATH_MAX]; |
| |
| /* Device descriptors */ |
| proplen = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, |
| "usb-dev-descriptor", &rdata); |
| if (proplen <= 0) { |
| return (LIBUSB_ERROR_IO); |
| } |
| bcopy(rdata, &dev->device_descriptor, LIBUSB_DT_DEVICE_SIZE); |
| |
| /* Raw configuration descriptors */ |
| proplen = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, |
| "usb-raw-cfg-descriptors", &rdata); |
| if (proplen <= 0) { |
| usbi_dbg("can't find raw config descriptors"); |
| |
| return (LIBUSB_ERROR_IO); |
| } |
| dpriv->raw_cfgdescr = calloc(1, proplen); |
| if (dpriv->raw_cfgdescr == NULL) { |
| return (LIBUSB_ERROR_NO_MEM); |
| } else { |
| bcopy(rdata, dpriv->raw_cfgdescr, proplen); |
| dpriv->cfgvalue = ((struct libusb_config_descriptor *) |
| rdata)->bConfigurationValue; |
| } |
| |
| n = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", &port_prop); |
| |
| if ((n != 1) || (*port_prop <= 0)) { |
| return (LIBUSB_ERROR_IO); |
| } |
| dev->port_number = *port_prop; |
| |
| /* device physical path */ |
| phypath = di_devfs_path(node); |
| if (phypath) { |
| dpriv->phypath = strdup(phypath); |
| snprintf(match_str, sizeof(match_str), "^usb/%x.%x", |
| dev->device_descriptor.idVendor, |
| dev->device_descriptor.idProduct); |
| usbi_dbg("match is %s", match_str); |
| sunos_physpath_to_devlink(dpriv->phypath, match_str, &dpriv->ugenpath); |
| di_devfs_path_free(phypath); |
| |
| } else { |
| free(dpriv->raw_cfgdescr); |
| |
| return (LIBUSB_ERROR_IO); |
| } |
| |
| /* address */ |
| n = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "assigned-address", &addr); |
| if (n != 1 || *addr == 0) { |
| usbi_dbg("can't get address"); |
| } else { |
| dev->device_address = *addr; |
| } |
| |
| /* speed */ |
| if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "low-speed", &i) >= 0) { |
| dev->speed = LIBUSB_SPEED_LOW; |
| } else if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "high-speed", &i) >= 0) { |
| dev->speed = LIBUSB_SPEED_HIGH; |
| } else if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "full-speed", &i) >= 0) { |
| dev->speed = LIBUSB_SPEED_FULL; |
| } else if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "super-speed", &i) >= 0) { |
| dev->speed = LIBUSB_SPEED_SUPER; |
| } |
| |
| usbi_dbg("vid=%x pid=%x, path=%s, bus_nmber=0x%x, port_number=%d, speed=%d", |
| dev->device_descriptor.idVendor, dev->device_descriptor.idProduct, |
| dpriv->phypath, dev->bus_number, dev->port_number, dev->speed); |
| |
| return (LIBUSB_SUCCESS); |
| } |
| |
| static int |
| sunos_add_devices(di_devlink_t link, void *arg) |
| { |
| struct devlink_cbarg *largs = (struct devlink_cbarg *)arg; |
| struct node_args *nargs; |
| di_node_t myself, dn; |
| uint64_t session_id = 0; |
| uint64_t sid = 0; |
| uint64_t bdf = 0; |
| struct libusb_device *dev; |
| sunos_dev_priv_t *devpriv; |
| int n, *j; |
| int i = 0; |
| int *addr_prop; |
| uint8_t bus_number = 0; |
| uint32_t * regbuf = NULL; |
| uint32_t reg; |
| |
| UNUSED(link); |
| |
| nargs = (struct node_args *)largs->nargs; |
| myself = largs->myself; |
| |
| /* |
| * Construct session ID. |
| * session ID = dev_addr | hub addr |parent hub addr|...|root hub bdf |
| * 8 bits 8bits 8 bits 16bits |
| */ |
| if (myself == DI_NODE_NIL) |
| return (DI_WALK_CONTINUE); |
| |
| dn = myself; |
| /* find the root hub */ |
| while (di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "root-hub", &j) != 0) { |
| usbi_dbg("find_root_hub:%s", di_devfs_path(dn)); |
| n = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, |
| "assigned-address", &addr_prop); |
| session_id |= ((addr_prop[0] & 0xff) << i++ * 8); |
| dn = di_parent_node(dn); |
| } |
| |
| /* dn is the root hub node */ |
| n = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "reg", (int **)®buf); |
| reg = regbuf[0]; |
| bdf = (PCI_REG_BUS_G(reg) << 8) | (PCI_REG_DEV_G(reg) << 3) | PCI_REG_FUNC_G(reg); |
| /* bdf must larger than i*8 bits */ |
| session_id |= (bdf << i * 8); |
| bus_number = (PCI_REG_DEV_G(reg) << 3) | PCI_REG_FUNC_G(reg); |
| |
| usbi_dbg("device bus address=%s:%x, name:%s", |
| di_bus_addr(myself), bus_number, di_node_name(dn)); |
| usbi_dbg("session id org:%" PRIx64, session_id); |
| |
| /* dn is the usb device */ |
| for (dn = di_child_node(myself); dn != DI_NODE_NIL; dn = di_sibling_node(dn)) { |
| usbi_dbg("device path:%s", di_devfs_path(dn)); |
| /* skip hub devices, because its driver can not been unload */ |
| if (di_prop_lookup_ints(DDI_DEV_T_ANY, dn, "usb-port-count", &addr_prop) != -1) |
| continue; |
| /* usb_addr */ |
| n = di_prop_lookup_ints(DDI_DEV_T_ANY, dn, |
| "assigned-address", &addr_prop); |
| if ((n != 1) || (addr_prop[0] == 0)) { |
| usbi_dbg("cannot get valid usb_addr"); |
| continue; |
| } |
| |
| sid = (session_id << 8) | (addr_prop[0] & 0xff) ; |
| usbi_dbg("session id %" PRIX64, sid); |
| |
| dev = usbi_get_device_by_session_id(nargs->ctx, sid); |
| if (dev == NULL) { |
| dev = usbi_alloc_device(nargs->ctx, sid); |
| if (dev == NULL) { |
| usbi_dbg("can't alloc device"); |
| continue; |
| } |
| devpriv = usbi_get_device_priv(dev); |
| dev->bus_number = bus_number; |
| |
| if (sunos_fill_in_dev_info(dn, dev) != LIBUSB_SUCCESS) { |
| libusb_unref_device(dev); |
| usbi_dbg("get information fail"); |
| continue; |
| } |
| if (usbi_sanitize_device(dev) < 0) { |
| libusb_unref_device(dev); |
| usbi_dbg("sanatize failed: "); |
| return (DI_WALK_TERMINATE); |
| } |
| } else { |
| devpriv = usbi_get_device_priv(dev); |
| usbi_dbg("Dev %s exists", devpriv->ugenpath); |
| } |
| |
| if (discovered_devs_append(*(nargs->discdevs), dev) == NULL) { |
| usbi_dbg("cannot append device"); |
| } |
| |
| /* |
| * we alloc and hence ref this dev. We don't need to ref it |
| * hereafter. Front end or app should take care of their ref. |
| */ |
| libusb_unref_device(dev); |
| |
| usbi_dbg("Device %s %s id=0x%" PRIx64 ", devcount:%" PRIuPTR |
| ", bdf=%" PRIx64, |
| devpriv->ugenpath, di_devfs_path(dn), (uint64_t)sid, |
| (*nargs->discdevs)->len, bdf); |
| } |
| |
| return (DI_WALK_CONTINUE); |
| } |
| |
| static int |
| sunos_walk_minor_node_link(di_node_t node, void *args) |
| { |
| di_minor_t minor = DI_MINOR_NIL; |
| char *minor_path; |
| struct devlink_cbarg arg; |
| struct node_args *nargs = (struct node_args *)args; |
| di_devlink_handle_t devlink_hdl = nargs->dlink_hdl; |
| |
| /* walk each minor to find usb devices */ |
| while ((minor = di_minor_next(node, minor)) != DI_MINOR_NIL) { |
| minor_path = di_devfs_minor_path(minor); |
| arg.nargs = args; |
| arg.myself = node; |
| arg.minor = minor; |
| (void) di_devlink_walk(devlink_hdl, |
| "^usb/hub[0-9]+", minor_path, |
| DI_PRIMARY_LINK, (void *)&arg, sunos_add_devices); |
| di_devfs_path_free(minor_path); |
| } |
| |
| /* switch to a different node */ |
| nargs->last_ugenpath = NULL; |
| |
| return (DI_WALK_CONTINUE); |
| } |
| |
| int |
| sunos_get_device_list(struct libusb_context * ctx, |
| struct discovered_devs **discdevs) |
| { |
| di_node_t root_node; |
| struct node_args args; |
| di_devlink_handle_t devlink_hdl; |
| |
| args.ctx = ctx; |
| args.discdevs = discdevs; |
| args.last_ugenpath = NULL; |
| if ((root_node = di_init("/", DINFOCPYALL)) == DI_NODE_NIL) { |
| usbi_dbg("di_int() failed: errno %d (%s)", errno, strerror(errno)); |
| return (LIBUSB_ERROR_IO); |
| } |
| |
| if ((devlink_hdl = di_devlink_init(NULL, 0)) == NULL) { |
| di_fini(root_node); |
| usbi_dbg("di_devlink_init() failed: errno %d (%s)", errno, strerror(errno)); |
| |
| return (LIBUSB_ERROR_IO); |
| } |
| args.dlink_hdl = devlink_hdl; |
| |
| /* walk each node to find USB devices */ |
| if (di_walk_node(root_node, DI_WALK_SIBFIRST, &args, |
| sunos_walk_minor_node_link) == -1) { |
| usbi_dbg("di_walk_node() failed: errno %d (%s)", errno, strerror(errno)); |
| di_fini(root_node); |
| |
| return (LIBUSB_ERROR_IO); |
| } |
| |
| di_fini(root_node); |
| di_devlink_fini(&devlink_hdl); |
| |
| usbi_dbg("%zu devices", (*discdevs)->len); |
| |
| return ((*discdevs)->len); |
| } |
| |
| static int |
| sunos_usb_open_ep0(sunos_dev_handle_priv_t *hpriv, sunos_dev_priv_t *dpriv) |
| { |
| char filename[PATH_MAX + 1]; |
| |
| if (hpriv->eps[0].datafd > 0) { |
| return (LIBUSB_SUCCESS); |
| } |
| snprintf(filename, PATH_MAX, "%s/cntrl0", dpriv->ugenpath); |
| |
| usbi_dbg("opening %s", filename); |
| hpriv->eps[0].datafd = open(filename, O_RDWR); |
| if (hpriv->eps[0].datafd < 0) { |
| return(_errno_to_libusb(errno)); |
| } |
| |
| snprintf(filename, PATH_MAX, "%s/cntrl0stat", dpriv->ugenpath); |
| hpriv->eps[0].statfd = open(filename, O_RDONLY); |
| if (hpriv->eps[0].statfd < 0) { |
| close(hpriv->eps[0].datafd); |
| hpriv->eps[0].datafd = -1; |
| |
| return(_errno_to_libusb(errno)); |
| } |
| |
| return (LIBUSB_SUCCESS); |
| } |
| |
| static void |
| sunos_usb_close_all_eps(sunos_dev_handle_priv_t *hdev) |
| { |
| int i; |
| |
| /* not close ep0 */ |
| for (i = 1; i < USB_MAXENDPOINTS; i++) { |
| if (hdev->eps[i].datafd != -1) { |
| (void) close(hdev->eps[i].datafd); |
| hdev->eps[i].datafd = -1; |
| } |
| if (hdev->eps[i].statfd != -1) { |
| (void) close(hdev->eps[i].statfd); |
| hdev->eps[i].statfd = -1; |
| } |
| } |
| } |
| |
| static void |
| sunos_usb_close_ep0(sunos_dev_handle_priv_t *hdev) |
| { |
| if (hdev->eps[0].datafd >= 0) { |
| close(hdev->eps[0].datafd); |
| close(hdev->eps[0].statfd); |
| hdev->eps[0].datafd = -1; |
| hdev->eps[0].statfd = -1; |
| } |
| } |
| |
| static uchar_t |
| sunos_usb_ep_index(uint8_t ep_addr) |
| { |
| return ((ep_addr & LIBUSB_ENDPOINT_ADDRESS_MASK) + |
| ((ep_addr & LIBUSB_ENDPOINT_DIR_MASK) ? 16 : 0)); |
| } |
| |
| static int |
| sunos_find_interface(struct libusb_device_handle *hdev, |
| uint8_t endpoint, uint8_t *interface) |
| { |
| struct libusb_config_descriptor *config; |
| int r; |
| int iface_idx; |
| |
| r = libusb_get_active_config_descriptor(hdev->dev, &config); |
| if (r < 0) { |
| return (LIBUSB_ERROR_INVALID_PARAM); |
| } |
| |
| for (iface_idx = 0; iface_idx < config->bNumInterfaces; iface_idx++) { |
| const struct libusb_interface *iface = |
| &config->interface[iface_idx]; |
| int altsetting_idx; |
| |
| for (altsetting_idx = 0; altsetting_idx < iface->num_altsetting; |
| altsetting_idx++) { |
| const struct libusb_interface_descriptor *altsetting = |
| &iface->altsetting[altsetting_idx]; |
| int ep_idx; |
| |
| for (ep_idx = 0; ep_idx < altsetting->bNumEndpoints; |
| ep_idx++) { |
| const struct libusb_endpoint_descriptor *ep = |
| &altsetting->endpoint[ep_idx]; |
| if (ep->bEndpointAddress == endpoint) { |
| *interface = iface_idx; |
| libusb_free_config_descriptor(config); |
| |
| return (LIBUSB_SUCCESS); |
| } |
| } |
| } |
| } |
| libusb_free_config_descriptor(config); |
| |
| return (LIBUSB_ERROR_INVALID_PARAM); |
| } |
| |
| static int |
| sunos_check_device_and_status_open(struct libusb_device_handle *hdl, |
| uint8_t ep_addr, int ep_type) |
| { |
| char filename[PATH_MAX + 1], statfilename[PATH_MAX + 1]; |
| char cfg_num[16], alt_num[16]; |
| int fd, fdstat, mode; |
| uint8_t ifc = 0; |
| uint8_t ep_index; |
| sunos_dev_handle_priv_t *hpriv; |
| |
| usbi_dbg("open ep 0x%02x", ep_addr); |
| hpriv = usbi_get_device_handle_priv(hdl); |
| ep_index = sunos_usb_ep_index(ep_addr); |
| /* ep already opened */ |
| if ((hpriv->eps[ep_index].datafd > 0) && |
| (hpriv->eps[ep_index].statfd > 0)) { |
| usbi_dbg("ep 0x%02x already opened, return success", |
| ep_addr); |
| |
| return (0); |
| } |
| |
| if (sunos_find_interface(hdl, ep_addr, &ifc) < 0) { |
| usbi_dbg("can't find interface for endpoint 0x%02x", |
| ep_addr); |
| |
| return (EACCES); |
| } |
| |
| /* create filename */ |
| if (hpriv->config_index > 0) { |
| (void) snprintf(cfg_num, sizeof(cfg_num), "cfg%d", |
| hpriv->config_index + 1); |
| } else { |
| bzero(cfg_num, sizeof(cfg_num)); |
| } |
| |
| if (hpriv->altsetting[ifc] > 0) { |
| (void) snprintf(alt_num, sizeof(alt_num), ".%d", |
| hpriv->altsetting[ifc]); |
| } else { |
| bzero(alt_num, sizeof(alt_num)); |
| } |
| |
| (void) snprintf(filename, PATH_MAX, "%s/%sif%d%s%s%d", |
| hpriv->dpriv->ugenpath, cfg_num, ifc, alt_num, |
| (ep_addr & LIBUSB_ENDPOINT_DIR_MASK) ? "in" : |
| "out", (ep_addr & LIBUSB_ENDPOINT_ADDRESS_MASK)); |
| (void) snprintf(statfilename, PATH_MAX, "%sstat", filename); |
| |
| /* |
| * In case configuration has been switched, the xfer endpoint needs |
| * to be opened before the status endpoint, due to a ugen issue. |
| * However, to enable the one transfer mode for an Interrupt-In pipe, |
| * the status endpoint needs to be opened before the xfer endpoint. |
| * So, open the xfer mode first and close it immediately |
| * as a workaround. This will handle the configuration switch. |
| * Then, open the status endpoint. If for an Interrupt-in pipe, |
| * write the USB_EP_INTR_ONE_XFER control to the status endpoint |
| * to enable the one transfer mode. Then, re-open the xfer mode. |
| */ |
| if (ep_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) { |
| mode = O_RDWR; |
| } else if (ep_addr & LIBUSB_ENDPOINT_IN) { |
| mode = O_RDONLY; |
| } else { |
| mode = O_WRONLY; |
| } |
| /* Open the xfer endpoint first */ |
| if ((fd = open(filename, mode)) == -1) { |
| usbi_dbg("can't open %s: errno %d (%s)", filename, errno, |
| strerror(errno)); |
| |
| return (errno); |
| } |
| /* And immediately close the xfer endpoint */ |
| (void) close(fd); |
| |
| /* |
| * Open the status endpoint. |
| * If for an Interrupt-IN pipe, need to enable the one transfer mode |
| * by writing USB_EP_INTR_ONE_XFER control to the status endpoint |
| * before opening the xfer endpoint |
| */ |
| if ((ep_type == LIBUSB_TRANSFER_TYPE_INTERRUPT) && |
| (ep_addr & LIBUSB_ENDPOINT_IN)) { |
| char control = USB_EP_INTR_ONE_XFER; |
| ssize_t count; |
| |
| /* Open the status endpoint with RDWR */ |
| if ((fdstat = open(statfilename, O_RDWR)) == -1) { |
| usbi_dbg("can't open %s RDWR: errno %d (%s)", |
| statfilename, errno, strerror(errno)); |
| |
| return (errno); |
| } else { |
| count = write(fdstat, &control, sizeof(control)); |
| if (count != 1) { |
| /* this should have worked */ |
| usbi_dbg("can't write to %s: errno %d (%s)", |
| statfilename, errno, strerror(errno)); |
| (void) close(fdstat); |
| |
| return (errno); |
| } |
| } |
| } else { |
| if ((fdstat = open(statfilename, O_RDONLY)) == -1) { |
| usbi_dbg("can't open %s: errno %d (%s)", statfilename, errno, |
| strerror(errno)); |
| |
| return (errno); |
| } |
| } |
| |
| /* Re-open the xfer endpoint */ |
| if ((fd = open(filename, mode)) == -1) { |
| usbi_dbg("can't open %s: errno %d (%s)", filename, errno, |
| strerror(errno)); |
| (void) close(fdstat); |
| |
| return (errno); |
| } |
| |
| hpriv->eps[ep_index].datafd = fd; |
| hpriv->eps[ep_index].statfd = fdstat; |
| usbi_dbg("ep=0x%02x datafd=%d, statfd=%d", ep_addr, fd, fdstat); |
| |
| return (0); |
| } |
| |
| int |
| sunos_open(struct libusb_device_handle *handle) |
| { |
| sunos_dev_handle_priv_t *hpriv; |
| sunos_dev_priv_t *dpriv; |
| int i; |
| int ret; |
| |
| hpriv = usbi_get_device_handle_priv(handle); |
| dpriv = usbi_get_device_priv(handle->dev); |
| hpriv->dpriv = dpriv; |
| |
| /* set all file descriptors to "closed" */ |
| for (i = 0; i < USB_MAXENDPOINTS; i++) { |
| hpriv->eps[i].datafd = -1; |
| hpriv->eps[i].statfd = -1; |
| } |
| |
| if (sunos_kernel_driver_active(handle, 0)) { |
| /* pretend we can open the device */ |
| return (LIBUSB_SUCCESS); |
| } |
| |
| if ((ret = sunos_usb_open_ep0(hpriv, dpriv)) != LIBUSB_SUCCESS) { |
| usbi_dbg("fail: %d", ret); |
| return (ret); |
| } |
| |
| return (LIBUSB_SUCCESS); |
| } |
| |
| void |
| sunos_close(struct libusb_device_handle *handle) |
| { |
| sunos_dev_handle_priv_t *hpriv; |
| |
| usbi_dbg(" "); |
| |
| hpriv = usbi_get_device_handle_priv(handle); |
| |
| sunos_usb_close_all_eps(hpriv); |
| sunos_usb_close_ep0(hpriv); |
| } |
| |
| int |
| sunos_get_active_config_descriptor(struct libusb_device *dev, |
| void *buf, size_t len) |
| { |
| sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev); |
| struct libusb_config_descriptor *cfg; |
| int proplen; |
| di_node_t node; |
| uint8_t *rdata; |
| |
| /* |
| * Keep raw configuration descriptors updated, in case config |
| * has ever been changed through setCfg. |
| */ |
| if ((node = di_init(dpriv->phypath, DINFOCPYALL)) == DI_NODE_NIL) { |
| usbi_dbg("di_int() failed: errno %d (%s)", errno, |
| strerror(errno)); |
| return (LIBUSB_ERROR_IO); |
| } |
| proplen = di_prop_lookup_bytes(DDI_DEV_T_ANY, node, |
| "usb-raw-cfg-descriptors", &rdata); |
| if (proplen <= 0) { |
| usbi_dbg("can't find raw config descriptors"); |
| |
| return (LIBUSB_ERROR_IO); |
| } |
| dpriv->raw_cfgdescr = realloc(dpriv->raw_cfgdescr, proplen); |
| if (dpriv->raw_cfgdescr == NULL) { |
| return (LIBUSB_ERROR_NO_MEM); |
| } else { |
| bcopy(rdata, dpriv->raw_cfgdescr, proplen); |
| dpriv->cfgvalue = ((struct libusb_config_descriptor *) |
| rdata)->bConfigurationValue; |
| } |
| di_fini(node); |
| |
| cfg = (struct libusb_config_descriptor *)dpriv->raw_cfgdescr; |
| len = MIN(len, libusb_le16_to_cpu(cfg->wTotalLength)); |
| memcpy(buf, dpriv->raw_cfgdescr, len); |
| usbi_dbg("path:%s len %zu", dpriv->phypath, len); |
| |
| return (len); |
| } |
| |
| int |
| sunos_get_config_descriptor(struct libusb_device *dev, uint8_t idx, |
| void *buf, size_t len) |
| { |
| UNUSED(idx); |
| /* XXX */ |
| return(sunos_get_active_config_descriptor(dev, buf, len)); |
| } |
| |
| int |
| sunos_get_configuration(struct libusb_device_handle *handle, uint8_t *config) |
| { |
| sunos_dev_priv_t *dpriv = usbi_get_device_priv(handle->dev); |
| |
| *config = dpriv->cfgvalue; |
| |
| usbi_dbg("bConfigurationValue %u", *config); |
| |
| return (LIBUSB_SUCCESS); |
| } |
| |
| int |
| sunos_set_configuration(struct libusb_device_handle *handle, int config) |
| { |
| sunos_dev_priv_t *dpriv = usbi_get_device_priv(handle->dev); |
| sunos_dev_handle_priv_t *hpriv; |
| |
| usbi_dbg("bConfigurationValue %d", config); |
| hpriv = usbi_get_device_handle_priv(handle); |
| |
| if (dpriv->ugenpath == NULL) |
| return (LIBUSB_ERROR_NOT_SUPPORTED); |
| |
| if (config < 1) |
| return (LIBUSB_ERROR_NOT_SUPPORTED); |
| |
| dpriv->cfgvalue = config; |
| hpriv->config_index = config - 1; |
| |
| return (LIBUSB_SUCCESS); |
| } |
| |
| int |
| sunos_claim_interface(struct libusb_device_handle *handle, uint8_t iface) |
| { |
| UNUSED(handle); |
| |
| usbi_dbg("iface %u", iface); |
| |
| return (LIBUSB_SUCCESS); |
| } |
| |
| int |
| sunos_release_interface(struct libusb_device_handle *handle, uint8_t iface) |
| { |
| sunos_dev_handle_priv_t *hpriv = usbi_get_device_handle_priv(handle); |
| |
| usbi_dbg("iface %u", iface); |
| |
| /* XXX: can we release it? */ |
| hpriv->altsetting[iface] = 0; |
| |
| return (LIBUSB_SUCCESS); |
| } |
| |
| int |
| sunos_set_interface_altsetting(struct libusb_device_handle *handle, uint8_t iface, |
| uint8_t altsetting) |
| { |
| sunos_dev_priv_t *dpriv = usbi_get_device_priv(handle->dev); |
| sunos_dev_handle_priv_t *hpriv = usbi_get_device_handle_priv(handle); |
| |
| usbi_dbg("iface %u, setting %u", iface, altsetting); |
| |
| if (dpriv->ugenpath == NULL) |
| return (LIBUSB_ERROR_NOT_FOUND); |
| |
| /* XXX: can we switch altsetting? */ |
| hpriv->altsetting[iface] = altsetting; |
| |
| return (LIBUSB_SUCCESS); |
| } |
| |
| static void |
| usb_dump_data(const void *data, size_t size) |
| { |
| const uint8_t *p = data; |
| size_t i; |
| |
| if (getenv("LIBUSB_DEBUG") == NULL) { |
| return; |
| } |
| |
| (void) fprintf(stderr, "data dump:"); |
| for (i = 0; i < size; i++) { |
| if (i % 16 == 0) { |
| (void) fprintf(stderr, "\n%08zx ", i); |
| } |
| (void) fprintf(stderr, "%02x ", p[i]); |
| } |
| (void) fprintf(stderr, "\n"); |
| } |
| |
| static void |
| sunos_async_callback(union sigval arg) |
| { |
| struct sunos_transfer_priv *tpriv = |
| (struct sunos_transfer_priv *)arg.sival_ptr; |
| struct libusb_transfer *xfer = tpriv->transfer; |
| struct aiocb *aiocb = &tpriv->aiocb; |
| int ret; |
| sunos_dev_handle_priv_t *hpriv; |
| uint8_t ep; |
| libusb_device_handle *dev_handle; |
| |
| dev_handle = xfer->dev_handle; |
| |
| /* libusb can forcibly interrupt transfer in do_close() */ |
| if (dev_handle != NULL) { |
| hpriv = usbi_get_device_handle_priv(dev_handle); |
| ep = sunos_usb_ep_index(xfer->endpoint); |
| |
| ret = aio_error(aiocb); |
| if (ret != 0) { |
| xfer->status = sunos_usb_get_status(hpriv->eps[ep].statfd); |
| } else { |
| xfer->actual_length = |
| LIBUSB_TRANSFER_TO_USBI_TRANSFER(xfer)->transferred = |
| aio_return(aiocb); |
| } |
| |
| usb_dump_data(xfer->buffer, xfer->actual_length); |
| |
| usbi_dbg("ret=%d, len=%d, actual_len=%d", ret, xfer->length, |
| xfer->actual_length); |
| |
| /* async notification */ |
| usbi_signal_transfer_completion(LIBUSB_TRANSFER_TO_USBI_TRANSFER(xfer)); |
| } |
| } |
| |
| static int |
| sunos_do_async_io(struct libusb_transfer *transfer) |
| { |
| int ret = -1; |
| struct aiocb *aiocb; |
| sunos_dev_handle_priv_t *hpriv; |
| uint8_t ep; |
| struct sunos_transfer_priv *tpriv; |
| |
| usbi_dbg(" "); |
| |
| tpriv = usbi_get_transfer_priv(LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)); |
| hpriv = usbi_get_device_handle_priv(transfer->dev_handle); |
| ep = sunos_usb_ep_index(transfer->endpoint); |
| |
| tpriv->transfer = transfer; |
| aiocb = &tpriv->aiocb; |
| bzero(aiocb, sizeof(*aiocb)); |
| aiocb->aio_fildes = hpriv->eps[ep].datafd; |
| aiocb->aio_buf = transfer->buffer; |
| aiocb->aio_nbytes = transfer->length; |
| aiocb->aio_lio_opcode = |
| ((transfer->endpoint & LIBUSB_ENDPOINT_DIR_MASK) == |
| LIBUSB_ENDPOINT_IN) ? LIO_READ:LIO_WRITE; |
| aiocb->aio_sigevent.sigev_notify = SIGEV_THREAD; |
| aiocb->aio_sigevent.sigev_value.sival_ptr = tpriv; |
| aiocb->aio_sigevent.sigev_notify_function = sunos_async_callback; |
| |
| if (aiocb->aio_lio_opcode == LIO_READ) { |
| ret = aio_read(aiocb); |
| } else { |
| ret = aio_write(aiocb); |
| } |
| |
| return (ret); |
| } |
| |
| /* return the number of bytes read/written */ |
| static ssize_t |
| usb_do_io(int fd, int stat_fd, void *data, size_t size, int flag, int *status) |
| { |
| int error; |
| ssize_t ret = -1; |
| |
| usbi_dbg("usb_do_io(): datafd=%d statfd=%d size=0x%zx flag=%s", |
| fd, stat_fd, size, flag? "WRITE":"READ"); |
| |
| switch (flag) { |
| case READ: |
| errno = 0; |
| ret = read(fd, data, size); |
| usb_dump_data(data, size); |
| break; |
| case WRITE: |
| usb_dump_data(data, size); |
| errno = 0; |
| ret = write(fd, data, size); |
| break; |
| } |
| |
| usbi_dbg("usb_do_io(): amount=%zd", ret); |
| |
| if (ret < 0) { |
| int save_errno = errno; |
| |
| usbi_dbg("TID=%x io %s errno %d (%s)", pthread_self(), |
| flag?"WRITE":"READ", errno, strerror(errno)); |
| |
| /* sunos_usb_get_status will do a read and overwrite errno */ |
| error = sunos_usb_get_status(stat_fd); |
| usbi_dbg("io status=%d errno %d (%s)", error, |
| save_errno, strerror(save_errno)); |
| |
| if (status) { |
| *status = save_errno; |
| } |
| |
| return (save_errno); |
| |
| } else if (status) { |
| *status = 0; |
| } |
| |
| return (ret); |
| } |
| |
| static int |
| solaris_submit_ctrl_on_default(struct libusb_transfer *transfer) |
| { |
| ssize_t ret = -1, setup_ret; |
| int status; |
| sunos_dev_handle_priv_t *hpriv; |
| struct libusb_device_handle *hdl = transfer->dev_handle; |
| uint16_t wLength; |
| uint8_t *data = transfer->buffer; |
| |
| hpriv = usbi_get_device_handle_priv(hdl); |
| wLength = transfer->length - LIBUSB_CONTROL_SETUP_SIZE; |
| |
| if (hpriv->eps[0].datafd == -1) { |
| usbi_dbg("ep0 not opened"); |
| |
| return (LIBUSB_ERROR_NOT_FOUND); |
| } |
| |
| if ((data[0] & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_IN) { |
| usbi_dbg("IN request"); |
| ret = usb_do_io(hpriv->eps[0].datafd, |
| hpriv->eps[0].statfd, data, LIBUSB_CONTROL_SETUP_SIZE, |
| WRITE, &status); |
| } else { |
| usbi_dbg("OUT request"); |
| ret = usb_do_io(hpriv->eps[0].datafd, hpriv->eps[0].statfd, |
| transfer->buffer, transfer->length, WRITE, |
| (int *)&transfer->status); |
| } |
| |
| setup_ret = ret; |
| if (ret < (ssize_t)LIBUSB_CONTROL_SETUP_SIZE) { |
| usbi_dbg("error sending control msg: %zd", ret); |
| |
| return (LIBUSB_ERROR_IO); |
| } |
| |
| ret = transfer->length - LIBUSB_CONTROL_SETUP_SIZE; |
| |
| /* Read the remaining bytes for IN request */ |
| if ((wLength) && ((data[0] & LIBUSB_ENDPOINT_DIR_MASK) == |
| LIBUSB_ENDPOINT_IN)) { |
| usbi_dbg("DATA: %d", transfer->length - (int)setup_ret); |
| ret = usb_do_io(hpriv->eps[0].datafd, |
| hpriv->eps[0].statfd, |
| transfer->buffer + LIBUSB_CONTROL_SETUP_SIZE, |
| wLength, READ, (int *)&transfer->status); |
| } |
| |
| if (ret >= 0) { |
| LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)->transferred = ret; |
| } |
| usbi_dbg("Done: ctrl data bytes %zd", ret); |
| |
| /** |
| * Sync transfer handling. |
| * We should release transfer lock here and later get it back |
| * as usbi_handle_transfer_completion() takes its own transfer lock. |
| */ |
| usbi_mutex_unlock(&LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)->lock); |
| ret = usbi_handle_transfer_completion(LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer), |
| transfer->status); |
| usbi_mutex_lock(&LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer)->lock); |
| |
| return (ret); |
| } |
| |
| int |
| sunos_clear_halt(struct libusb_device_handle *handle, unsigned char endpoint) |
| { |
| int ret; |
| |
| usbi_dbg("endpoint=0x%02x", endpoint); |
| |
| ret = libusb_control_transfer(handle, LIBUSB_ENDPOINT_OUT | |
| LIBUSB_RECIPIENT_ENDPOINT | LIBUSB_REQUEST_TYPE_STANDARD, |
| LIBUSB_REQUEST_CLEAR_FEATURE, 0, endpoint, NULL, 0, 1000); |
| |
| usbi_dbg("ret=%d", ret); |
| |
| return (ret); |
| } |
| |
| void |
| sunos_destroy_device(struct libusb_device *dev) |
| { |
| sunos_dev_priv_t *dpriv = usbi_get_device_priv(dev); |
| |
| usbi_dbg("destroy everything"); |
| free(dpriv->raw_cfgdescr); |
| free(dpriv->ugenpath); |
| free(dpriv->phypath); |
| } |
| |
| int |
| sunos_submit_transfer(struct usbi_transfer *itransfer) |
| { |
| struct libusb_transfer *transfer; |
| struct libusb_device_handle *hdl; |
| int err = 0; |
| |
| transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
| hdl = transfer->dev_handle; |
| |
| err = sunos_check_device_and_status_open(hdl, |
| transfer->endpoint, transfer->type); |
| if (err < 0) { |
| |
| return (_errno_to_libusb(err)); |
| } |
| |
| switch (transfer->type) { |
| case LIBUSB_TRANSFER_TYPE_CONTROL: |
| /* sync transfer */ |
| usbi_dbg("CTRL transfer: %d", transfer->length); |
| err = solaris_submit_ctrl_on_default(transfer); |
| break; |
| |
| case LIBUSB_TRANSFER_TYPE_BULK: |
| /* fallthru */ |
| case LIBUSB_TRANSFER_TYPE_INTERRUPT: |
| if (transfer->type == LIBUSB_TRANSFER_TYPE_BULK) |
| usbi_dbg("BULK transfer: %d", transfer->length); |
| else |
| usbi_dbg("INTR transfer: %d", transfer->length); |
| err = sunos_do_async_io(transfer); |
| break; |
| |
| case LIBUSB_TRANSFER_TYPE_ISOCHRONOUS: |
| /* Isochronous/Stream is not supported */ |
| |
| /* fallthru */ |
| case LIBUSB_TRANSFER_TYPE_BULK_STREAM: |
| if (transfer->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) |
| usbi_dbg("ISOC transfer: %d", transfer->length); |
| else |
| usbi_dbg("BULK STREAM transfer: %d", transfer->length); |
| err = LIBUSB_ERROR_NOT_SUPPORTED; |
| break; |
| } |
| |
| return (err); |
| } |
| |
| int |
| sunos_cancel_transfer(struct usbi_transfer *itransfer) |
| { |
| sunos_xfer_priv_t *tpriv; |
| sunos_dev_handle_priv_t *hpriv; |
| struct libusb_transfer *transfer; |
| struct aiocb *aiocb; |
| uint8_t ep; |
| int ret; |
| |
| tpriv = usbi_get_transfer_priv(itransfer); |
| aiocb = &tpriv->aiocb; |
| transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); |
| hpriv = usbi_get_device_handle_priv(transfer->dev_handle); |
| ep = sunos_usb_ep_index(transfer->endpoint); |
| |
| ret = aio_cancel(hpriv->eps[ep].datafd, aiocb); |
| |
| usbi_dbg("aio->fd=%d fd=%d ret = %d, %s", aiocb->aio_fildes, |
| hpriv->eps[ep].datafd, ret, (ret == AIO_CANCELED)? |
| strerror(0):strerror(errno)); |
| |
| if (ret != AIO_CANCELED) { |
| ret = _errno_to_libusb(errno); |
| } else { |
| /* |
| * we don't need to call usbi_handle_transfer_cancellation(), |
| * because we'll handle everything in sunos_async_callback. |
| */ |
| ret = LIBUSB_SUCCESS; |
| } |
| |
| return (ret); |
| } |
| |
| int |
| sunos_handle_transfer_completion(struct usbi_transfer *itransfer) |
| { |
| return usbi_handle_transfer_completion(itransfer, LIBUSB_TRANSFER_COMPLETED); |
| } |
| |
| int |
| _errno_to_libusb(int err) |
| { |
| usbi_dbg("error: %s (%d)", strerror(err), err); |
| |
| switch (err) { |
| case EIO: |
| return (LIBUSB_ERROR_IO); |
| case EACCES: |
| return (LIBUSB_ERROR_ACCESS); |
| case ENOENT: |
| return (LIBUSB_ERROR_NO_DEVICE); |
| case ENOMEM: |
| return (LIBUSB_ERROR_NO_MEM); |
| case ETIMEDOUT: |
| return (LIBUSB_ERROR_TIMEOUT); |
| } |
| |
| return (LIBUSB_ERROR_OTHER); |
| } |
| |
| /* |
| * sunos_usb_get_status: |
| * gets status of endpoint |
| * |
| * Returns: ugen's last cmd status |
| */ |
| static int |
| sunos_usb_get_status(int fd) |
| { |
| int status; |
| ssize_t ret; |
| |
| usbi_dbg("sunos_usb_get_status(): fd=%d", fd); |
| |
| ret = read(fd, &status, sizeof(status)); |
| if (ret == sizeof(status)) { |
| switch (status) { |
| case USB_LC_STAT_NOERROR: |
| usbi_dbg("No Error"); |
| break; |
| case USB_LC_STAT_CRC: |
| usbi_dbg("CRC Timeout Detected\n"); |
| break; |
| case USB_LC_STAT_BITSTUFFING: |
| usbi_dbg("Bit Stuffing Violation\n"); |
| break; |
| case USB_LC_STAT_DATA_TOGGLE_MM: |
| usbi_dbg("Data Toggle Mismatch\n"); |
| break; |
| case USB_LC_STAT_STALL: |
| usbi_dbg("End Point Stalled\n"); |
| break; |
| case USB_LC_STAT_DEV_NOT_RESP: |
| usbi_dbg("Device is Not Responding\n"); |
| break; |
| case USB_LC_STAT_PID_CHECKFAILURE: |
| usbi_dbg("PID Check Failure\n"); |
| break; |
| case USB_LC_STAT_UNEXP_PID: |
| usbi_dbg("Unexpected PID\n"); |
| break; |
| case USB_LC_STAT_DATA_OVERRUN: |
| usbi_dbg("Data Exceeded Size\n"); |
| break; |
| case USB_LC_STAT_DATA_UNDERRUN: |
| usbi_dbg("Less data received\n"); |
| break; |
| case USB_LC_STAT_BUFFER_OVERRUN: |
| usbi_dbg("Buffer Size Exceeded\n"); |
| break; |
| case USB_LC_STAT_BUFFER_UNDERRUN: |
| usbi_dbg("Buffer Underrun\n"); |
| break; |
| case USB_LC_STAT_TIMEOUT: |
| usbi_dbg("Command Timed Out\n"); |
| break; |
| case USB_LC_STAT_NOT_ACCESSED: |
| usbi_dbg("Not Accessed by h/w\n"); |
| break; |
| case USB_LC_STAT_UNSPECIFIED_ERR: |
| usbi_dbg("Unspecified Error\n"); |
| break; |
| case USB_LC_STAT_NO_BANDWIDTH: |
| usbi_dbg("No Bandwidth\n"); |
| break; |
| case USB_LC_STAT_HW_ERR: |
| usbi_dbg("Host Controller h/w Error\n"); |
| break; |
| case USB_LC_STAT_SUSPENDED: |
| usbi_dbg("Device was Suspended\n"); |
| break; |
| case USB_LC_STAT_DISCONNECTED: |
| usbi_dbg("Device was Disconnected\n"); |
| break; |
| case USB_LC_STAT_INTR_BUF_FULL: |
| usbi_dbg("Interrupt buffer was full\n"); |
| break; |
| case USB_LC_STAT_INVALID_REQ: |
| usbi_dbg("Request was Invalid\n"); |
| break; |
| case USB_LC_STAT_INTERRUPTED: |
| usbi_dbg("Request was Interrupted\n"); |
| break; |
| case USB_LC_STAT_NO_RESOURCES: |
| usbi_dbg("No resources available for " |
| "request\n"); |
| break; |
| case USB_LC_STAT_INTR_POLLING_FAILED: |
| usbi_dbg("Failed to Restart Poll"); |
| break; |
| default: |
| usbi_dbg("Error Not Determined %d\n", |
| status); |
| break; |
| } |
| } else { |
| usbi_dbg("read stat error: %s",strerror(errno)); |
| status = -1; |
| } |
| |
| return (status); |
| } |
| |
| const struct usbi_os_backend usbi_backend = { |
| .name = "Solaris", |
| .caps = 0, |
| .get_device_list = sunos_get_device_list, |
| .get_active_config_descriptor = sunos_get_active_config_descriptor, |
| .get_config_descriptor = sunos_get_config_descriptor, |
| .open = sunos_open, |
| .close = sunos_close, |
| .get_configuration = sunos_get_configuration, |
| .set_configuration = sunos_set_configuration, |
| .claim_interface = sunos_claim_interface, |
| .release_interface = sunos_release_interface, |
| .set_interface_altsetting = sunos_set_interface_altsetting, |
| .clear_halt = sunos_clear_halt, |
| .kernel_driver_active = sunos_kernel_driver_active, |
| .detach_kernel_driver = sunos_detach_kernel_driver, |
| .attach_kernel_driver = sunos_attach_kernel_driver, |
| .destroy_device = sunos_destroy_device, |
| .submit_transfer = sunos_submit_transfer, |
| .cancel_transfer = sunos_cancel_transfer, |
| .handle_transfer_completion = sunos_handle_transfer_completion, |
| .device_priv_size = sizeof(sunos_dev_priv_t), |
| .device_handle_priv_size = sizeof(sunos_dev_handle_priv_t), |
| .transfer_priv_size = sizeof(sunos_xfer_priv_t), |
| }; |