| /***************************************************************************** |
| * Copyright (c) 2013 IBM Corporation |
| * All rights reserved. |
| * This program and the accompanying materials |
| * are made available under the terms of the BSD License |
| * which accompanies this distribution, and is available at |
| * http://www.opensource.org/licenses/bsd-license.php |
| * |
| * Contributors: |
| * IBM Corporation - initial implementation |
| *****************************************************************************/ |
| |
| #include <string.h> |
| #include "usb-core.h" |
| |
| #undef DEBUG |
| //#define DEBUG |
| #ifdef DEBUG |
| #define dprintf(_x ...) do { printf(_x); } while(0) |
| #else |
| #define dprintf(_x ...) |
| #endif |
| |
| #define __unused __attribute__((unused)) |
| |
| struct usb_hcd_ops *head; |
| struct usb_dev *devpool; |
| #define USB_DEVPOOL_SIZE 4096 |
| |
| static struct usb_dev *usb_alloc_devpool(void) |
| { |
| struct usb_dev *head, *curr, *prev; |
| unsigned int dev_count = 0, i; |
| |
| head = SLOF_alloc_mem(USB_DEVPOOL_SIZE); |
| if (!head) |
| return NULL; |
| |
| dev_count = USB_DEVPOOL_SIZE/sizeof(struct usb_dev); |
| dprintf("%s: %d number of devices\n", __func__, dev_count); |
| /* Although an array, link them*/ |
| for (i = 0, curr = head, prev = NULL; i < dev_count; i++, curr++) { |
| if (prev) |
| prev->next = curr; |
| curr->next = NULL; |
| prev = curr; |
| } |
| |
| #ifdef DEBUG |
| for (i = 0, curr = head; curr; curr = curr->next) |
| printf("%s: %d dev %p\n", __func__, i++, curr); |
| #endif |
| |
| return head; |
| } |
| |
| struct usb_dev *usb_devpool_get(void) |
| { |
| struct usb_dev *new; |
| |
| if (!devpool) { |
| devpool = usb_alloc_devpool(); |
| if (!devpool) |
| return NULL; |
| } |
| |
| new = devpool; |
| devpool = devpool->next; |
| memset(new, 0, sizeof(*new)); |
| new->next = NULL; |
| return new; |
| } |
| |
| void usb_devpool_put(struct usb_dev *dev) |
| { |
| struct usb_dev *curr; |
| if (!dev && !devpool) |
| return; |
| |
| curr = devpool; |
| while (curr->next) |
| curr = curr->next; |
| curr->next = dev; |
| dev->next = NULL; |
| } |
| |
| #ifndef DEBUG |
| #define validate_hcd_ops(dev) (dev && dev->hcidev && dev->hcidev->ops) |
| #else |
| int validate_hcd_ops(struct usb_dev *dev) |
| { |
| int ret = true; |
| |
| if (!dev) { |
| printf("dev is NULL\n"); |
| ret = false; |
| } else if (!dev->hcidev) { |
| printf("hcidev is NULL\n"); |
| ret = false; |
| } else if (!dev->hcidev->ops) { |
| printf("ops is NULL\n"); |
| ret = false; |
| } |
| return ret; |
| } |
| #endif |
| |
| struct usb_pipe *usb_get_pipe(struct usb_dev *dev, struct usb_ep_descr *ep, |
| char *buf, size_t len) |
| { |
| if (validate_hcd_ops(dev) && dev->hcidev->ops->get_pipe) |
| return dev->hcidev->ops->get_pipe(dev, ep, buf, len); |
| else { |
| printf("%s: Failed\n", __func__); |
| return NULL; |
| } |
| } |
| |
| void usb_put_pipe(struct usb_pipe *pipe) |
| { |
| struct usb_dev *dev = NULL; |
| if (pipe && pipe->dev) { |
| dev = pipe->dev; |
| if (validate_hcd_ops(dev) && dev->hcidev->ops->put_pipe) |
| dev->hcidev->ops->put_pipe(pipe); |
| } |
| } |
| |
| int usb_poll_intr(struct usb_pipe *pipe, uint8_t *buf) |
| { |
| struct usb_dev *dev = NULL; |
| if (pipe && pipe->dev) { |
| dev = pipe->dev; |
| if (validate_hcd_ops(dev) && dev->hcidev->ops->poll_intr) |
| return dev->hcidev->ops->poll_intr(pipe, buf); |
| } |
| return 0; |
| } |
| |
| void usb_hcd_register(struct usb_hcd_ops *ops) |
| { |
| struct usb_hcd_ops *list; |
| |
| if (!ops) |
| printf("Error"); |
| dprintf("Registering %s %d\n", ops->name, ops->usb_type); |
| |
| if (head) { |
| list = head; |
| while (list->next) |
| list = list->next; |
| list->next = ops; |
| } else |
| head = ops; |
| } |
| |
| void usb_hcd_init(void *hcidev) |
| { |
| struct usb_hcd_dev *dev = hcidev; |
| struct usb_hcd_ops *list = head; |
| |
| if (!dev) { |
| printf("Device Error"); |
| return; |
| } |
| |
| while (list) { |
| if (list->usb_type == dev->type) { |
| dprintf("usb_ops(%p) for the controller found\n", list); |
| dev->ops = list; |
| dev->ops->init(dev); |
| return; |
| } |
| list = list->next; |
| } |
| |
| dprintf("usb_ops for the controller not found\n"); |
| } |
| |
| void usb_hcd_exit(void *_hcidev) |
| { |
| struct usb_hcd_dev *hcidev = _hcidev; |
| |
| dprintf("%s: enter \n", __func__); |
| if (!hcidev) { |
| printf("Device Error"); |
| return; |
| } |
| |
| if (hcidev->ops->exit) |
| hcidev->ops->exit(hcidev); |
| } |
| |
| int usb_send_ctrl(struct usb_pipe *pipe, struct usb_dev_req *req, void *data) |
| { |
| struct usb_dev *dev = NULL; |
| if (!pipe) |
| return false; |
| dev = pipe->dev; |
| if (validate_hcd_ops(dev) && dev->hcidev->ops->send_ctrl) |
| return dev->hcidev->ops->send_ctrl(pipe, req, data); |
| else { |
| printf("%s: Failed\n", __func__); |
| return false; |
| } |
| } |
| |
| int usb_transfer_ctrl(void *dev, void *req, void *data) |
| { |
| struct usb_pipe *pipe = NULL; |
| struct usb_dev *usbdev; |
| |
| if (!dev) |
| return false; |
| usbdev = (struct usb_dev *)dev; |
| pipe = usbdev->control; |
| return usb_send_ctrl(pipe, req, data); |
| } |
| |
| int usb_transfer_bulk(void *dev, int dir, void *td, void *td_phys, void *data, int size) |
| { |
| struct usb_pipe *pipe = NULL; |
| struct usb_dev *usbdev; |
| |
| if (!dev) |
| return false; |
| usbdev = (struct usb_dev *)dev; |
| pipe = (dir == USB_PIPE_OUT) ? usbdev->bulk_out : usbdev->bulk_in; |
| if (!pipe) |
| return false; |
| if (validate_hcd_ops(usbdev) && usbdev->hcidev->ops->transfer_bulk) |
| return usbdev->hcidev->ops->transfer_bulk(pipe, td, td_phys, data, size); |
| else { |
| printf("%s: Failed\n", __func__); |
| return false; |
| } |
| } |
| |
| /* |
| * USB Specification 1.1 |
| * 9.3 USB Device Requests |
| * 9.4 Standard Device Requests |
| */ |
| static int usb_set_address(struct usb_dev *dev, uint32_t port) |
| { |
| struct usb_dev_req req; |
| struct usb_hcd_dev *hcidev; |
| |
| if (!dev) |
| return false; |
| |
| hcidev = dev->hcidev; |
| req.bmRequestType = 0; |
| req.bRequest = REQ_SET_ADDRESS; |
| req.wIndex = 0; |
| req.wLength = 0; |
| req.wValue = cpu_to_le16((uint16_t)(hcidev->nextaddr)); |
| if (usb_send_ctrl(dev->control, &req, NULL)) { |
| dev->addr = hcidev->nextaddr++; |
| return true; |
| } else |
| return false; |
| } |
| |
| static int usb_get_device_descr(struct usb_dev *dev, void *data, size_t size) |
| { |
| struct usb_dev_req req; |
| |
| if (!dev) |
| return false; |
| |
| req.bmRequestType = 0x80; |
| req.bRequest = REQ_GET_DESCRIPTOR; |
| req.wIndex = 0; |
| req.wLength = cpu_to_le16((uint16_t) size); |
| req.wValue = cpu_to_le16(DESCR_TYPE_DEVICE << 8); |
| return usb_send_ctrl(dev->control, &req, data); |
| } |
| |
| static int usb_get_config_descr(struct usb_dev *dev, void *data, size_t size) |
| { |
| struct usb_dev_req req; |
| |
| if (!dev) |
| return false; |
| |
| req.bmRequestType = 0x80; |
| req.bRequest = REQ_GET_DESCRIPTOR; |
| req.wIndex = 0; |
| req.wLength = cpu_to_le16((uint16_t) size); |
| req.wValue = cpu_to_le16(DESCR_TYPE_CONFIGURATION << 8); |
| return usb_send_ctrl(dev->control, &req, data); |
| |
| } |
| |
| static int usb_set_config(struct usb_dev *dev, uint8_t cfg_value) |
| { |
| struct usb_dev_req req; |
| |
| if (!dev) |
| return false; |
| |
| req.bmRequestType = 0x00; |
| req.bRequest = REQ_SET_CONFIGURATION; |
| req.wIndex = 0; |
| req.wLength = 0; |
| req.wValue = cpu_to_le16(0x00FF & cfg_value); |
| return usb_send_ctrl(dev->control, &req, NULL); |
| } |
| |
| static int usb_clear_halt(struct usb_pipe *pipe) |
| { |
| struct usb_dev_req req; |
| struct usb_dev *dev; |
| |
| if (pipe && pipe->dev) { |
| dev = pipe->dev; |
| dprintf("Clearing port %d dir %d type %d\n", |
| pipe->epno, pipe->dir, pipe->type); |
| req.bmRequestType = REQT_DIR_OUT | REQT_REC_EP; |
| req.bRequest = REQ_CLEAR_FEATURE; |
| req.wValue = FEATURE_ENDPOINT_HALT; |
| req.wIndex = cpu_to_le16(pipe->epno | pipe->dir); |
| req.wLength = 0; |
| return usb_send_ctrl(dev->control, &req, NULL); |
| } |
| return false; |
| } |
| |
| int usb_dev_populate_pipe(struct usb_dev *dev, struct usb_ep_descr *ep, |
| void *buf, size_t len) |
| { |
| uint8_t dir, type; |
| |
| dir = (ep->bEndpointAddress & 0x80) >> 7; |
| type = ep->bmAttributes & USB_EP_TYPE_MASK; |
| |
| dprintf("EP: %s: %d size %d type %d\n", dir ? "IN " : "OUT", |
| ep->bEndpointAddress & 0xF, le16_to_cpu(ep->wMaxPacketSize), |
| type); |
| if (type == USB_EP_TYPE_BULK) { |
| if (dir) |
| dev->bulk_in = usb_get_pipe(dev, ep, buf, len); |
| else |
| dev->bulk_out = usb_get_pipe(dev, ep, buf, len); |
| } else if (type == USB_EP_TYPE_INTR) |
| dev->intr = usb_get_pipe(dev, ep, buf, len); |
| |
| return true; |
| } |
| |
| static void usb_dev_copy_epdesc(struct usb_dev *dev, struct usb_ep_descr *ep) |
| { |
| uint32_t ep_cnt; |
| |
| ep_cnt = dev->ep_cnt; |
| if (ep_cnt < USB_DEV_EP_MAX) |
| memcpy((void *)&dev->ep[ep_cnt], ep, sizeof(*ep)); |
| else |
| dprintf("usb-core: only %d EPs supported\n", USB_DEV_EP_MAX); |
| dev->ep_cnt++; |
| } |
| |
| int usb_hid_init(void *vdev) |
| { |
| struct usb_dev *dev; |
| dev = (struct usb_dev *) vdev; |
| if (!dev) |
| return false; |
| if (dev->class == DEV_HID_KEYB) |
| usb_hid_kbd_init(dev); |
| return true; |
| } |
| |
| int usb_hid_exit(void *vdev) |
| { |
| struct usb_dev *dev; |
| dev = (struct usb_dev *) vdev; |
| if (!dev) |
| return false; |
| if (dev->class == DEV_HID_KEYB) |
| usb_hid_kbd_exit(dev); |
| return true; |
| } |
| |
| #define usb_get_intf_class(x) ((x & 0x00FF0000) >> 16) |
| |
| int usb_msc_init(void *vdev) |
| { |
| struct usb_dev *dev; |
| int i; |
| |
| dev = (struct usb_dev *) vdev; |
| dprintf("%s: enter %x\n", __func__, dev->class); |
| if (!dev) |
| return false; |
| if (usb_get_intf_class(dev->class) == 8) { |
| for (i = 0; i < dev->ep_cnt; i++) { |
| if ((dev->ep[i].bmAttributes & USB_EP_TYPE_MASK) |
| == USB_EP_TYPE_BULK) |
| usb_dev_populate_pipe(dev, &dev->ep[i], NULL, 0); |
| } |
| } |
| return true; |
| } |
| |
| int usb_msc_exit(void *vdev) |
| { |
| struct usb_dev *dev; |
| dev = (struct usb_dev *) vdev; |
| dprintf("%s: enter %x\n", __func__, dev->class); |
| if (!dev) |
| return false; |
| if (usb_get_intf_class(dev->class) == 8) { |
| if (dev->bulk_in) |
| usb_put_pipe(dev->bulk_in); |
| if (dev->bulk_out) |
| usb_put_pipe(dev->bulk_out); |
| } |
| return true; |
| } |
| |
| static int usb_msc_reset(struct usb_dev *dev) |
| { |
| struct usb_dev_req req; |
| |
| if (!dev) |
| return false; |
| |
| req.bmRequestType = REQT_TYPE_CLASS | REQT_REC_INTERFACE | REQT_DIR_OUT; |
| req.bRequest = 0xFF; |
| req.wLength = 0; |
| req.wValue = 0; |
| req.wIndex = cpu_to_le16(dev->intf_num); |
| return usb_send_ctrl(dev->control, &req, NULL); |
| } |
| |
| void usb_msc_resetrecovery(struct usb_dev *dev) |
| { |
| // usb_msc_reset(dev); |
| usb_clear_halt(dev->bulk_in); |
| usb_clear_halt(dev->bulk_out); |
| SLOF_msleep(2); |
| } |
| |
| static int usb_handle_device(struct usb_dev *dev, struct usb_dev_config_descr *cfg, |
| uint8_t *ptr, uint16_t len) |
| { |
| struct usb_dev_intf_descr *intf = NULL; |
| struct usb_ep_descr *ep = NULL; |
| struct usb_dev_hid_descr *hid __unused = NULL; |
| uint8_t desc_len, desc_type; |
| |
| len -= sizeof(struct usb_dev_config_descr); |
| ptr = (uint8_t *)(ptr + sizeof(struct usb_dev_config_descr)); |
| |
| while (len > 0) { |
| desc_len = *ptr; |
| desc_type = *(ptr + 1); |
| switch (desc_type) { |
| case DESCR_TYPE_INTERFACE: |
| intf = (struct usb_dev_intf_descr *)ptr; |
| dev->class = intf->bInterfaceClass << 16 | |
| intf->bInterfaceSubClass << 8 | |
| intf->bInterfaceProtocol; |
| break; |
| case DESCR_TYPE_ENDPOINT: |
| ep = (struct usb_ep_descr *)ptr; |
| dev->intf_num = intf->bInterfaceNumber; |
| usb_dev_copy_epdesc(dev, ep); |
| break; |
| case DESCR_TYPE_HID: |
| hid = (struct usb_dev_hid_descr *)ptr; |
| dprintf("hid-report %d size %d\n", |
| hid->bReportType, le16_to_cpu(hid->wReportLength)); |
| break; |
| case DESCR_TYPE_HUB: |
| break; |
| default: |
| printf("ptr %p desc_type %d\n", ptr, desc_type); |
| } |
| ptr += desc_len; |
| len -= desc_len; |
| } |
| return true; |
| } |
| |
| int setup_new_device(struct usb_dev *dev, unsigned int port) |
| { |
| struct usb_dev_descr descr; |
| struct usb_dev_config_descr cfg; |
| struct usb_ep_descr ep; |
| uint16_t len; |
| void *data = NULL; |
| |
| dprintf("usb: %s - port %d\n", __func__, port); |
| |
| dev->addr = 0; |
| dev->port = port; |
| ep.bEndpointAddress = 0; |
| ep.bmAttributes = USB_EP_TYPE_CONTROL; |
| ep.wMaxPacketSize = cpu_to_le16(8); |
| dev->control = usb_get_pipe(dev, &ep, NULL, 0); |
| |
| if (!usb_get_device_descr(dev, &descr, 8)) |
| goto fail; |
| dev->control->mps = descr.bMaxPacketSize0; |
| |
| /* |
| * For USB3.0 ADDRESS-SLOT command takes care of setting |
| * address, skip this during generic device setup for USB3.0 |
| * devices |
| */ |
| if (dev->speed != USB_SUPER_SPEED) { |
| /* |
| * Qemu starts the port number from 1 which was |
| * revealed in bootindex and resulted in mismatch for |
| * storage devices names. Adjusting this here for |
| * compatibility. |
| */ |
| dev->port = port + 1; |
| if(!usb_set_address(dev, dev->port)) |
| goto fail; |
| } |
| mb(); |
| SLOF_msleep(100); |
| |
| if (!usb_get_device_descr(dev, &descr, sizeof(struct usb_dev_descr))) |
| goto fail; |
| |
| if (!usb_get_config_descr(dev, &cfg, sizeof(struct usb_dev_config_descr))) |
| goto fail; |
| |
| len = le16_to_cpu(cfg.wTotalLength); |
| /* No device config descriptor present */ |
| if (len == sizeof(struct usb_dev_config_descr)) |
| goto fail; |
| |
| data = SLOF_dma_alloc(len); |
| if (!data) { |
| printf("%s: alloc failed %d\n", __func__, port); |
| goto fail; |
| } |
| |
| if (!usb_get_config_descr(dev, data, len)) |
| goto fail_mem_free; |
| if (!usb_set_config(dev, cfg.bConfigurationValue)) |
| goto fail_mem_free; |
| mb(); |
| SLOF_msleep(100); |
| |
| if (!usb_handle_device(dev, &cfg, data, len)) |
| goto fail_mem_free; |
| |
| switch (usb_get_intf_class(dev->class)) { |
| case 3: |
| dprintf("HID found %06X\n", dev->class); |
| slof_usb_handle(dev); |
| break; |
| case 8: |
| dprintf("MASS STORAGE found %d %06X\n", dev->intf_num, |
| dev->class); |
| if ((dev->class & 0x50) != 0x50) { /* Bulk-only supported */ |
| printf("Device not supported %06X\n", dev->class); |
| goto fail_mem_free; |
| } |
| |
| if (!usb_msc_reset(dev)) { |
| printf("%s: bulk reset failed\n", __func__); |
| goto fail_mem_free; |
| } |
| SLOF_msleep(100); |
| slof_usb_handle(dev); |
| break; |
| case 9: |
| dprintf("HUB found\n"); |
| slof_usb_handle(dev); |
| break; |
| default: |
| printf("USB Interface class -%x- Not supported\n", dev->class); |
| break; |
| } |
| |
| SLOF_dma_free(data, len); |
| return true; |
| fail_mem_free: |
| SLOF_dma_free(data, len); |
| fail: |
| return false; |
| } |