| /* |
| * Copyright 2017, Data61 |
| * Commonwealth Scientific and Industrial Research Organisation (CSIRO) |
| * ABN 41 687 119 230. |
| * |
| * This software may be distributed and modified according to the terms of |
| * the BSD 2-Clause license. Note that NO WARRANTY is provided. |
| * See "LICENSE_BSD2.txt" for details. |
| * |
| * @TAG(DATA61_BSD) |
| */ |
| |
| /** |
| * @brief USB hub driver |
| * @see USB 2.0 spec, Chapter 11 |
| */ |
| #include "usbhub.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #include <utils/util.h> |
| |
| #include "../services.h" |
| |
| #define HUBINT_RATE_MS 100 |
| |
| #define HUB_ENABLE_IRQS |
| |
| /*** USB spec chapter 11 page 262 ***/ |
| |
| struct hub_desc { |
| /// Number of bytes in this descriptor, including this byte. |
| uint8_t bDescLength; |
| /// Descriptor Type, value: 0x29 for Hub Descriptor. |
| #define DESCRIPTOR_TYPE_HUB 0x29 |
| uint8_t bDescriptorType; |
| /// Number of downstream ports that this hub supports. |
| uint8_t bNbrPorts; |
| /// Hub characteristics. |
| #define HUBCHAR_POWER_SWITCH_SINGLE BIT(0) |
| #define HUBCHAR_POWER_SWITCH_NONE BIT(1) |
| #define HUBCHAR_COMPOUND_DEVICE BIT(2) |
| #define HUBCHAR_OVER_CURRENT_SINGLE BIT(3) |
| #define HUBCHAR_OVER_CURRENT_NONE BIT(4) |
| uint16_t wHubCharacteristics; |
| /// Time (in 2ms intervals) until power is stable after child port power on |
| uint8_t bPwrOn2PwrGood; |
| /// Maximum current requirements of the hub controller |
| uint8_t bHubContrCurrent; |
| /* The size of the remaining fields depends on the number of ports |
| * Bit x will correspond to port x |
| * DeviceRemovable bitmap: 1-not removable. |
| * PortPwrCtrlMask bitmap: 1-requires manual power on (not ganged). |
| */ |
| uint8_t portcfg[64]; |
| } __attribute__ ((packed)); |
| |
| /**************** |
| *** FEATURES *** |
| ****************/ |
| static inline struct usbreq |
| __clear_port_feature_req(uint16_t port, uint16_t feature) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_CLS | USB_RCPT_OTHER), |
| .bRequest = CLR_FEATURE, |
| .wValue = feature, |
| .wIndex = port, |
| .wLength = 0 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq __clear_hub_feature_req(uint16_t feature) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_CLS | USB_RCPT_DEVICE), |
| .bRequest = CLR_FEATURE, |
| .wValue = feature, |
| .wIndex = 0, |
| .wLength = 0 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq |
| __set_port_feature_req(uint16_t port, uint16_t feature) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_CLS | USB_RCPT_OTHER), |
| .bRequest = SET_FEATURE, |
| .wValue = feature, |
| .wIndex = port, |
| .wLength = 0 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq __set_hub_feature_req(uint16_t feature) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_CLS | USB_RCPT_DEVICE), |
| .bRequest = SET_FEATURE, |
| .wValue = feature, |
| .wIndex = 0, |
| .wLength = 0 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq __get_port_status_req(uint16_t port) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_IN | USB_TYPE_CLS | USB_RCPT_OTHER), |
| .bRequest = GET_STATUS, |
| .wValue = 0, |
| .wIndex = port, |
| .wLength = 4 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq __get_hub_status_req(void) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_IN | USB_TYPE_CLS | USB_RCPT_DEVICE), |
| .bRequest = GET_STATUS, |
| .wValue = 0, |
| .wIndex = 0, |
| .wLength = 4 |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq __get_hub_descriptor_req(void) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_IN | USB_TYPE_CLS | USB_RCPT_DEVICE), |
| .bRequest = GET_DESCRIPTOR, |
| .wValue = DESCRIPTOR_TYPE_HUB << 8, |
| .wIndex = 0, |
| .wLength = sizeof(struct hub_desc) |
| }; |
| return r; |
| } |
| |
| static inline struct usbreq __set_hub_descriptor_req(void) |
| { |
| struct usbreq r = { |
| .bmRequestType = (USB_DIR_OUT | USB_TYPE_CLS | USB_RCPT_DEVICE), |
| .bRequest = SET_DESCRIPTOR, |
| .wValue = DESCRIPTOR_TYPE_HUB << 8, |
| .wIndex = 0, |
| .wLength = sizeof(struct hub_desc) |
| }; |
| return r; |
| } |
| |
| struct usb_hubem { |
| int hubem_nports; |
| int pwr_delay_ms; |
| int (*set_pf) (void *token, int port, enum port_feature feature); |
| int (*clr_pf) (void *token, int port, enum port_feature feature); |
| int (*get_pstat) (void *token, int port, struct port_status *ps); |
| void *token; |
| }; |
| |
| static void _handle_port_change(usb_hub_t h, int port) |
| { |
| struct xact xact[2]; |
| struct usbreq *req; |
| struct port_status *sts; |
| uint16_t change, status; |
| int ret; |
| |
| if (!h) { |
| ZF_LOGF("Invalid HUB\n"); |
| } |
| |
| if (!port) { |
| ZF_LOGD("Error: check hub status!\n"); |
| return; |
| } |
| |
| ZF_LOGD("Handle status change of port %d\n", port); |
| |
| /* Get port status change */ |
| xact[0].type = PID_SETUP; |
| xact[0].len = sizeof(struct usbreq); |
| xact[1].type = PID_IN; |
| xact[1].len = sizeof(struct port_status); |
| |
| ret = usb_alloc_xact(h->udev->dman, xact, 2); |
| if (ret) { |
| ZF_LOGF("Out of DMA memory\n"); |
| } |
| req = xact_get_vaddr(&xact[0]); |
| sts = xact_get_vaddr(&xact[1]); |
| |
| *req = __get_port_status_req(port); |
| ret = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 2, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| |
| /* Cache the port status, because we need to clear it right away. */ |
| change = sts->wPortChange; |
| status = sts->wPortStatus; |
| |
| ZF_LOGD("Status change (0x%x:0x%x) on port %d.\n", |
| change, status, port); |
| |
| /* Attach and detach detect event */ |
| if (change & BIT(PORT_CONNECTION)) { |
| /* Clear the port connection status */ |
| *req = __clear_port_feature_req(port, C_PORT_CONNECTION); |
| ret = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 1, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| |
| if (status & BIT(PORT_CONNECTION)) { |
| ZF_LOGD("Port %d connected\n", port); |
| /* Wait for the device to stabilize, USB spec 9.1.2 */ |
| ps_mdelay(100); |
| |
| /* Enable the connection by resetting the port */ |
| *req = __set_port_feature_req(port, PORT_RESET); |
| ret = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 1, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| |
| /* |
| * Wait for the hub to exit the resetting state, refer |
| * to USB spec 11.5.1.5 |
| * We also need to re-read the port status, it's updated |
| * by the reset. |
| */ |
| *req = __get_port_status_req(port); |
| do { |
| ps_mdelay(10); |
| ret = |
| usbdev_schedule_xact(h->udev, |
| h->udev->ep_ctrl, xact, |
| 2, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| |
| status = sts->wPortStatus; |
| } while (status & BIT(PORT_RESET)); |
| |
| /* Reset finished, clear reset status */ |
| *req = __clear_port_feature_req(port, C_PORT_RESET); |
| ret = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 1, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| |
| /* Create the new device */ |
| enum usb_speed speed; |
| struct usb_dev *new_dev = NULL; |
| usb_hub_t new_hub = NULL; |
| |
| if (status & BIT(PORT_HIGH_SPEED)) { |
| speed = USBSPEED_HIGH; |
| } else if (status & BIT(PORT_LOW_SPEED)) { |
| speed = USBSPEED_LOW; |
| } else { |
| speed = USBSPEED_FULL; |
| } |
| |
| ret = usb_new_device(h->udev, port, speed, &new_dev); |
| if (ret < 0) { |
| *req = __set_port_feature_req(port, PORT_RESET); |
| ret = |
| usbdev_schedule_xact(h->udev, |
| h->udev->ep_ctrl, xact, |
| 1, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| if (new_dev) { |
| usbdev_disconnect(new_dev); |
| } |
| } else { |
| h->port[port - 1].udev = new_dev; |
| usb_hub_driver_bind(new_dev, &new_hub); |
| } |
| } else { |
| ZF_LOGD("Port %d disconnected\n", port); |
| *req = __set_port_feature_req(port, PORT_SUSPEND); |
| ret = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 1, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| if (h->port[port - 1].udev) { |
| usbdev_disconnect(h->port[port - 1].udev); |
| h->port[port - 1].udev = NULL; |
| } |
| } |
| } |
| |
| /* Port enable */ |
| if (change & BIT(PORT_ENABLE)) { |
| ZF_LOGD("Port %d enabled\n", port); |
| /* Clear the port connection status */ |
| *req = __clear_port_feature_req(port, C_PORT_CONNECTION); |
| ret = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 1, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| } |
| |
| /* Port suspend */ |
| if (change & BIT(PORT_SUSPEND)) { |
| ZF_LOGD("Port %d suspended\n", port); |
| /* Clear suspend status */ |
| *req = __clear_port_feature_req(port, C_PORT_SUSPEND); |
| ret = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 1, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| } |
| |
| /* Port over-current */ |
| if (change & BIT(PORT_OVER_CURRENT)) { |
| ZF_LOGD("Port %d over-current\n", port); |
| /* Clear over-current status */ |
| *req = __clear_port_feature_req(port, C_PORT_OVER_CURRENT); |
| ret = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 1, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| } |
| |
| /* Port reset */ |
| if (change & BIT(PORT_RESET)) { |
| ZF_LOGD("Port %d reset\n", port); |
| /* Clear reset status */ |
| *req = __clear_port_feature_req(port, C_PORT_RESET); |
| ret = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 1, NULL, NULL); |
| if (ret < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| } |
| |
| usb_destroy_xact(h->udev->dman, xact, 2); |
| } |
| |
| static int |
| hub_irq_handler(void *token, enum usb_xact_status stat, int bytes_remaining) |
| { |
| usb_hub_t h = (usb_hub_t) token; |
| int i, j; |
| int handled = 0; |
| uint8_t *intbm; |
| int len = h->int_xact.len - bytes_remaining; |
| |
| /* Check the status */ |
| if (stat != XACTSTAT_SUCCESS) { |
| ZF_LOGD("Received unsuccessful IRQ\n"); |
| return 1; |
| } |
| |
| ZF_LOGD("Handling IRQ\n"); |
| |
| intbm = h->intbm; |
| if (intbm != xact_get_vaddr(&h->int_xact)) { |
| ZF_LOGF("Invalid bitmap\n"); |
| } |
| for (i = 0; i < len; i++) { |
| /* Check if any bits have changed */ |
| if (intbm[i] == 0) { |
| continue; |
| } |
| /* Scan bitfield */ |
| for (j = 0; j < 8; j++) { |
| if ((1 << j) & intbm[i]) { |
| int port = i * 8 + j; |
| _handle_port_change(h, port); |
| handled++; |
| } |
| } |
| intbm[i] = 0; |
| } |
| if (!handled) { |
| ZF_LOGD("Spurious IRQ\n"); |
| } |
| |
| usbdev_schedule_xact(h->udev, h->udev->ep[0], |
| &h->int_xact, 1, &hub_irq_handler, h); |
| return 0; |
| } |
| |
| static int hub_config_cb(void *token, int cfg, int iface, struct anon_desc *d) |
| { |
| struct endpoint_desc *e; |
| usb_hub_t hub = (usb_hub_t)token; |
| |
| if (!hub) { |
| ZF_LOGF("Invalid token\n"); |
| } |
| if (d) { |
| switch (d->bDescriptorType) { |
| case ENDPOINT: |
| e = (struct endpoint_desc*)d; |
| /* We just take the first endpoint */ |
| hub->int_ep = e->bEndpointAddress & 0xf; |
| hub->int_max_pkt = e->wMaxPacketSize; |
| hub->int_rate_ms = e->bInterval * 2; |
| hub->ifno = iface; |
| hub->cfgno = cfg; |
| break; |
| default: |
| /* Don't care */ |
| break; |
| } |
| return 0; |
| } else { |
| return 0; |
| } |
| } |
| |
| int usb_hub_driver_bind(usb_dev_t *udev, usb_hub_t *hub) |
| { |
| usb_hub_t h; |
| struct usbreq *req; |
| struct hub_desc *hdesc; |
| struct xact xact[2]; |
| int err; |
| int i; |
| |
| /* Check the class */ |
| if (usbdev_get_class(udev) != USB_CLASS_HUB) { |
| return -1; |
| } |
| |
| /* Allocate memory */ |
| h = (usb_hub_t) usb_malloc(sizeof(*h)); |
| if (h == NULL) { |
| return -2; |
| } |
| memset(h, 0, sizeof(*h)); |
| h->udev = udev; |
| udev->dev_data = (struct udev_priv *)h; |
| |
| /* Get hub descriptor for nports and power delay */ |
| ZF_LOGD("Get hub descriptor\n"); |
| xact[0].type = PID_SETUP; |
| xact[0].len = sizeof(*req); |
| xact[1].type = PID_IN; |
| xact[1].len = sizeof(*hdesc); |
| err = usb_alloc_xact(udev->dman, xact, 2); |
| if (err) { |
| ZF_LOGF("Out of DMA memory\n"); |
| } |
| req = xact_get_vaddr(&xact[0]); |
| *req = __get_hub_descriptor_req(); |
| err = usbdev_schedule_xact(udev, h->udev->ep_ctrl, xact, 2, NULL, NULL); |
| if (err < 0) { |
| usb_destroy_xact(udev->dman, xact, 2); |
| usb_free(h); |
| h = NULL; |
| return -1; |
| } |
| hdesc = xact_get_vaddr(&xact[1]); |
| h->nports = hdesc->bNbrPorts; |
| h->power_good_delay_ms = hdesc->bPwrOn2PwrGood * 2; |
| usb_destroy_xact(udev->dman, xact, 2); |
| h->port = (struct usb_hub_port *)usb_malloc(sizeof(*h->port) * h->nports); |
| if (!h->port) { |
| ZF_LOGF("Out of memory\n"); |
| } |
| memset(h->port, 0, sizeof(*h->port) * h->nports); |
| ZF_LOGD("Parsing config\n"); |
| h->int_ep = -1; |
| err = usbdev_parse_config(h->udev, &hub_config_cb, h); |
| if (err || h->int_ep == -1) { |
| usb_free(h); |
| h = NULL; |
| return -1; |
| } |
| ZF_LOGD("Configure HUB\n"); |
| xact[0].type = PID_SETUP; |
| xact[0].len = sizeof(*req); |
| |
| err = usb_alloc_xact(h->udev->dman, xact, 1); |
| if (err) { |
| ZF_LOGE("Out of DMA memory\n"); |
| return -1; |
| } |
| req = xact_get_vaddr(&xact[0]); |
| *req = __set_configuration_req(h->cfgno); |
| |
| err = usbdev_schedule_xact(udev, h->udev->ep_ctrl, xact, 1, NULL, NULL); |
| if (err < 0) { |
| usb_destroy_xact(udev->dman, xact, 1); |
| usb_free(h); |
| h = NULL; |
| return -1; |
| } |
| usb_destroy_xact(udev->dman, xact, 1); |
| |
| /* Power up ports */ |
| xact[0].type = PID_SETUP; |
| xact[0].len = sizeof(*req); |
| |
| usb_alloc_xact(h->udev->dman, xact, 1); |
| req = xact_get_vaddr(&xact[0]); |
| for (i = 1; i <= h->nports; i++) { |
| ZF_LOGD("Power on port %d\n", i); |
| *req = __set_port_feature_req(i, PORT_POWER); |
| err = usbdev_schedule_xact(h->udev, h->udev->ep_ctrl, |
| xact, 1, NULL, NULL); |
| if (err < 0) { |
| ZF_LOGF("Transaction error\n"); |
| } |
| } |
| ps_mdelay(h->power_good_delay_ms); |
| usb_destroy_xact(udev->dman, xact, 1); |
| #if !defined(HUB_ENABLE_IRQS) |
| /* Setup ports */ |
| for (i = 1; i <= h->nports; i++) { |
| _handle_port_change(h, i); |
| } |
| #endif |
| #if defined(HUB_ENABLE_IRQS) |
| h->int_xact.type = PID_IN; |
| /* |
| * USB 2.0 spec[11.12.4] says the packet size should be (nport + 7)/8, but |
| * some hubs are known to send more data, which would cause a "babble". So |
| * we use maximum packet size instead, short packet does no harm. |
| */ |
| h->int_xact.len = h->int_max_pkt; |
| err = usb_alloc_xact(udev->dman, &h->int_xact, 1); |
| if (err) { |
| ZF_LOGF("Out of DMA memory\n"); |
| } |
| h->intbm = xact_get_vaddr(&h->int_xact); |
| ZF_LOGD("Registering for INT\n"); |
| /* FIXME: Search for the right ep */ |
| usbdev_schedule_xact(udev, udev->ep[0], |
| &h->int_xact, 1, &hub_irq_handler, h); |
| #else |
| h->intbm = NULL; |
| h->int_xact.vaddr = NULL; |
| h->int_xact.paddr = 0; |
| h->int_xact.len = 0; |
| (void)hub_irq_handler; |
| #endif |
| *hub = h; |
| |
| return 0; |
| } |
| |
| /********************* |
| *** Hub emulation *** |
| *********************/ |
| |
| static struct device_desc _hub_device_desc = { |
| .bLength = sizeof(struct device_desc), |
| .bDescriptorType = DEVICE, |
| .bcdUSB = 0x200, |
| .bDeviceClass = USB_CLASS_HUB, |
| .bDeviceSubClass = 0, |
| .bDeviceProtocol = 2, |
| .bMaxPacketSize0 = 64, |
| .idVendor = 0xFEED, |
| .idProduct = 0xBEEF, |
| .bcdDevice = 1234, |
| .iManufacturer = 0, |
| .iProduct = 0, |
| .iSerialNumber = 0, |
| .bNumConfigurations = 1 |
| }; |
| |
| static struct iface_desc _hub_iface_desc = { |
| .bLength = sizeof(_hub_iface_desc), |
| .bDescriptorType = INTERFACE, |
| .bInterfaceNumber = 0, |
| .bAlternateSetting = 0, |
| .bNumEndpoints = 1, |
| .bInterfaceClass = 9, |
| .bInterfaceSubClass = 0, |
| .bInterfaceProtocol = 1, |
| .iInterface = 0 |
| }; |
| |
| static struct endpoint_desc _hub_endpoint_desc = { |
| .bLength = sizeof(_hub_endpoint_desc), |
| .bDescriptorType = ENDPOINT, |
| .bEndpointAddress = 0x81, |
| .bmAttributes = 0x3, |
| .wMaxPacketSize = 0x1, |
| .bInterval = 0xc |
| }; |
| |
| static struct config_desc _hub_config_desc = { |
| .bLength = sizeof(_hub_config_desc), |
| .bDescriptorType = CONFIGURATION, |
| .wTotalLength = sizeof(_hub_config_desc) + |
| sizeof(_hub_iface_desc) + sizeof(_hub_endpoint_desc), |
| .bNumInterfaces = 1, |
| .bConfigurationValue = 1, |
| .iConfigurationIndex = 0, |
| .bmAttributes = (1 << 7), |
| .bMaxPower = 100 /*mA */ / 2 |
| }; |
| |
| static struct hub_desc _hub_hub_desc = { |
| .bDescLength = 0x8, |
| .bDescriptorType = DESCRIPTOR_TYPE_HUB, |
| .bNbrPorts = 2, |
| .wHubCharacteristics = 0, |
| .bPwrOn2PwrGood = 0xff, |
| .bHubContrCurrent = 0, |
| .portcfg = {0} |
| }; |
| |
| static int |
| hubem_get_descriptor(usb_hubem_t dev, struct usbreq *req, void *buf, int len) |
| { |
| int act_len = 0; |
| int dtype = req->wValue >> 8; |
| switch (dtype) { |
| case DEVICE:{ |
| struct device_desc *ret = (struct device_desc *)buf; |
| ZF_LOGD("Get device descriptor\n"); |
| act_len = MIN(len, sizeof(*ret)); |
| memcpy(ret, &_hub_device_desc, act_len); |
| return act_len;} |
| case DESCRIPTOR_TYPE_HUB:{ |
| struct hub_desc *ret = (struct hub_desc *)buf; |
| int nregs = (dev->hubem_nports + 7) / 8; |
| int i; |
| ZF_LOGD("Get hub type descriptor\n"); |
| _hub_hub_desc.bNbrPorts = dev->hubem_nports; |
| _hub_hub_desc.bPwrOn2PwrGood = dev->pwr_delay_ms / 2; |
| _hub_hub_desc.bDescLength = 7 + nregs * 2; |
| for (i = 0; i < nregs; i++) { |
| _hub_hub_desc.portcfg[i] = 0; |
| _hub_hub_desc.portcfg[i + nregs] = 0; |
| } |
| act_len = MIN(_hub_hub_desc.bDescLength, len); |
| memcpy(ret, &_hub_hub_desc, act_len); |
| return act_len;} |
| case CONFIGURATION:{ |
| int cp_len; |
| int pos = 0; |
| int act_len; |
| ZF_LOGD("Get configuration descriptor\n"); |
| act_len = MIN(_hub_config_desc.wTotalLength, len); |
| /* Copy the config */ |
| cp_len = MIN(act_len - pos, _hub_config_desc.bLength); |
| memcpy(buf + pos, &_hub_config_desc, cp_len); |
| pos += cp_len; |
| /* Copy the iface */ |
| cp_len = MIN(act_len - pos, _hub_iface_desc.bLength); |
| memcpy(buf + pos, &_hub_iface_desc, cp_len); |
| pos += cp_len; |
| /* copy the endpoint */ |
| _hub_endpoint_desc.wMaxPacketSize = |
| (dev->hubem_nports + 7) / 8; |
| cp_len = MIN(act_len - pos, _hub_endpoint_desc.bLength); |
| memcpy(buf + pos, &_hub_endpoint_desc, cp_len); |
| pos += cp_len; |
| if (pos != act_len) { |
| ZF_LOGF("Invalid descriptor\n"); |
| } |
| return act_len;} |
| case INTERFACE:{ |
| int act_len; |
| ZF_LOGD("Get interface descriptor\n"); |
| act_len = MIN(_hub_iface_desc.bLength, len); |
| memcpy(buf, &_hub_iface_desc, act_len); |
| return act_len;} |
| case ENDPOINT:{ |
| int act_len; |
| ZF_LOGD("Get endpoint descriptor\n"); |
| act_len = MIN(_hub_endpoint_desc.bLength, len); |
| memcpy(buf, &_hub_endpoint_desc, act_len); |
| return act_len;} |
| case STRING: |
| case DEVICE_QUALIFIER: |
| case OTHER_SPEED_CONFIGURATION: |
| case INTERFACE_POWER: |
| default: |
| ZF_LOGD("Descriptor 0x%x not supported\n", dtype); |
| return -1; |
| } |
| } |
| |
| static int hubem_feature(usb_hubem_t dev, struct usbreq *req) |
| { |
| int f = req->wValue; |
| int p = req->wIndex; |
| void *t = dev->token; |
| int ret; |
| switch (req->bRequest) { |
| case SET_FEATURE: |
| ZF_LOGD("Set feature %d -> port %d\n", f, p); |
| return dev->set_pf(t, p, f); |
| case CLR_FEATURE: |
| ZF_LOGD("Clear feature %d -> port %d\n", f, p); |
| return dev->clr_pf(t, p, f); |
| default: |
| printf("Unsupported feature: %d\n", f); |
| return -1; |
| } |
| return ret; |
| } |
| |
| static int |
| hubem_get_status(usb_hubem_t dev, struct usbreq *req, void *buf, int len) |
| { |
| int port = req->wIndex; |
| if (port == 0) { |
| /* Device status: self powered | remote wakeup */ |
| uint16_t stat = 0; |
| int act_len; |
| ZF_LOGD("Get Status: Device status\n"); |
| act_len = MIN(len, sizeof(stat)); |
| memcpy(buf, &stat, act_len); |
| return act_len; |
| } else if (port <= dev->hubem_nports) { |
| /* Port status */ |
| struct port_status *psts = (struct port_status *)buf; |
| int act_len = MIN(len, sizeof(*psts)); |
| if (len < sizeof(*psts)) { |
| ZF_LOGF("Invalid port status\n"); |
| } |
| if (dev->get_pstat(dev->token, port, psts)) { |
| ZF_LOGD |
| ("Get Status: Failed to read status for port %d\n", |
| port); |
| return -1; |
| } else { |
| ZF_LOGD("Get Status: Success s0x%x c0x%0x on port %d\n", |
| psts->wPortStatus, psts->wPortChange, port); |
| return act_len; |
| } |
| } else { |
| ZF_LOGD("Get Status: Invalid port (%d/%d)\n", port, |
| dev->hubem_nports); |
| return -1; |
| } |
| } |
| |
| int |
| hubem_process_xact(usb_hubem_t dev, struct xact *xact, int nxact, |
| usb_cb_t cb, void *t) |
| { |
| struct usbreq *req; |
| void *buf; |
| int buf_len; |
| int i; |
| int err; |
| |
| for (err = 0, i = 0; !err && i < nxact; i++) { |
| if (xact[i].type != PID_SETUP) { |
| continue; |
| } |
| req = xact_get_vaddr(&xact[i]); |
| if (xact[i].len < sizeof(*req)) { |
| ZF_LOGF("Buffer too small\n"); |
| } |
| |
| if (i + 1 < nxact && xact[i + 1].type != PID_SETUP) { |
| buf = xact_get_vaddr(&xact[i + 1]); |
| buf_len = xact[i + 1].len; |
| } else { |
| buf = NULL; |
| buf_len = 0; |
| } |
| switch (req->bRequest) { |
| case GET_STATUS: |
| return hubem_get_status(dev, req, buf, buf_len); |
| case GET_DESCRIPTOR: |
| return hubem_get_descriptor(dev, req, buf, buf_len); |
| case SET_CONFIGURATION: |
| ZF_LOGD("Unhandled transaction: SET_CONFIGURATION\n"); |
| break; |
| case SET_INTERFACE: |
| ZF_LOGD("Unhandled transaction: SET_INTERFACE\n"); |
| break; |
| case SET_ADDRESS: |
| ZF_LOGD("Unhandled transaction: SET_ADDRESS\n"); |
| break; |
| case CLR_FEATURE: |
| case SET_FEATURE: |
| err = hubem_feature(dev, req); |
| break; |
| default: |
| ZF_LOGE("Request code %d not supported\n", |
| req->bRequest); |
| } |
| } |
| if (cb) { |
| if (err >= 0) { |
| cb(t, XACTSTAT_SUCCESS, err); |
| } else { |
| cb(t, XACTSTAT_ERROR, err); |
| } |
| } |
| return err; |
| } |
| |
| int |
| usb_hubem_driver_init(void *token, int nports, int pwr_delay_ms, |
| int (*set_pf) (void *token, int port, |
| enum port_feature feature), |
| int (*clr_pf) (void *token, int port, |
| enum port_feature feature), |
| int (*get_pstat) (void *token, int port, |
| struct port_status *ps), |
| usb_hubem_t *hub) |
| { |
| |
| usb_hubem_t h; |
| h = (usb_hubem_t) usb_malloc(sizeof(*h)); |
| if (h == NULL) { |
| ZF_LOGE("Out of memory\n"); |
| return -1; |
| } |
| |
| h->token = token; |
| h->hubem_nports = nports; |
| h->pwr_delay_ms = pwr_delay_ms; |
| h->set_pf = set_pf; |
| h->clr_pf = clr_pf; |
| h->get_pstat = get_pstat; |
| *hub = h; |
| return 0; |
| } |