diff options
Diffstat (limited to 'qemu/roms/seabios/src/hw/usb-hub.c')
-rw-r--r-- | qemu/roms/seabios/src/hw/usb-hub.c | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/qemu/roms/seabios/src/hw/usb-hub.c b/qemu/roms/seabios/src/hw/usb-hub.c new file mode 100644 index 000000000..54e341b73 --- /dev/null +++ b/qemu/roms/seabios/src/hw/usb-hub.c @@ -0,0 +1,205 @@ +// Code for handling standard USB hubs. +// +// Copyright (C) 2010 Kevin O'Connor <kevin@koconnor.net> +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "config.h" // CONFIG_USB_HUB +#include "output.h" // dprintf +#include "string.h" // memset +#include "usb.h" // struct usb_s +#include "usb-hub.h" // struct usb_hub_descriptor +#include "util.h" // timer_calc + +static int +get_hub_desc(struct usb_pipe *pipe, struct usb_hub_descriptor *desc) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE; + req.bRequest = USB_REQ_GET_DESCRIPTOR; + if (pipe->speed == USB_SUPERSPEED) + req.wValue = USB_DT_HUB3<<8; + else + req.wValue = USB_DT_HUB<<8; + req.wIndex = 0; + req.wLength = sizeof(*desc); + return usb_send_default_control(pipe, &req, desc); +} + +static int +set_hub_depth(struct usb_pipe *pipe, u16 depth) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_DEVICE; + req.bRequest = HUB_REQ_SET_HUB_DEPTH; + req.wValue = depth; + req.wIndex = 0; + req.wLength = 0; + return usb_send_default_control(pipe, &req, NULL); +} + +static int +set_port_feature(struct usbhub_s *hub, int port, int feature) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER; + req.bRequest = USB_REQ_SET_FEATURE; + req.wValue = feature; + req.wIndex = port + 1; + req.wLength = 0; + mutex_lock(&hub->lock); + int ret = usb_send_default_control(hub->usbdev->defpipe, &req, NULL); + mutex_unlock(&hub->lock); + return ret; +} + +static int +clear_port_feature(struct usbhub_s *hub, int port, int feature) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER; + req.bRequest = USB_REQ_CLEAR_FEATURE; + req.wValue = feature; + req.wIndex = port + 1; + req.wLength = 0; + mutex_lock(&hub->lock); + int ret = usb_send_default_control(hub->usbdev->defpipe, &req, NULL); + mutex_unlock(&hub->lock); + return ret; +} + +static int +get_port_status(struct usbhub_s *hub, int port, struct usb_port_status *sts) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_OTHER; + req.bRequest = USB_REQ_GET_STATUS; + req.wValue = 0; + req.wIndex = port + 1; + req.wLength = sizeof(*sts); + mutex_lock(&hub->lock); + int ret = usb_send_default_control(hub->usbdev->defpipe, &req, sts); + mutex_unlock(&hub->lock); + return ret; +} + +// Check if device attached to port +static int +usb_hub_detect(struct usbhub_s *hub, u32 port) +{ + struct usb_port_status sts; + int ret = get_port_status(hub, port, &sts); + if (ret) { + dprintf(1, "Failure on hub port %d detect\n", port); + return -1; + } + return (sts.wPortStatus & USB_PORT_STAT_CONNECTION) ? 1 : 0; +} + +// Disable port +static void +usb_hub_disconnect(struct usbhub_s *hub, u32 port) +{ + int ret = clear_port_feature(hub, port, USB_PORT_FEAT_ENABLE); + if (ret) + dprintf(1, "Failure on hub port %d disconnect\n", port); +} + +// Reset device on port +static int +usb_hub_reset(struct usbhub_s *hub, u32 port) +{ + int ret = set_port_feature(hub, port, USB_PORT_FEAT_RESET); + if (ret) + goto fail; + + // Wait for reset to complete. + struct usb_port_status sts; + u32 end = timer_calc(USB_TIME_DRST * 2); + for (;;) { + ret = get_port_status(hub, port, &sts); + if (ret) + goto fail; + if (!(sts.wPortStatus & USB_PORT_STAT_RESET) + && (hub->usbdev->speed != USB_SUPERSPEED + || !(sts.wPortStatus & USB_PORT_STAT_LINK_MASK))) + break; + if (timer_check(end)) { + warn_timeout(); + goto fail; + } + msleep(5); + } + + // Reset complete. + if (!(sts.wPortStatus & USB_PORT_STAT_CONNECTION)) + // Device no longer present + return -1; + + if (hub->usbdev->speed == USB_SUPERSPEED) + return USB_SUPERSPEED; + return ((sts.wPortStatus & USB_PORT_STAT_SPEED_MASK) + >> USB_PORT_STAT_SPEED_SHIFT); + +fail: + dprintf(1, "Failure on hub port %d reset\n", port); + usb_hub_disconnect(hub, port); + return -1; +} + +static struct usbhub_op_s HubOp = { + .detect = usb_hub_detect, + .reset = usb_hub_reset, + .disconnect = usb_hub_disconnect, +}; + +// Configure a usb hub and then find devices connected to it. +int +usb_hub_setup(struct usbdevice_s *usbdev) +{ + ASSERT32FLAT(); + if (!CONFIG_USB_HUB) + return -1; + + struct usb_hub_descriptor desc; + int ret = get_hub_desc(usbdev->defpipe, &desc); + if (ret) + return ret; + + struct usbhub_s hub; + memset(&hub, 0, sizeof(hub)); + hub.usbdev = usbdev; + hub.cntl = usbdev->defpipe->cntl; + hub.portcount = desc.bNbrPorts; + hub.op = &HubOp; + + if (usbdev->speed == USB_SUPERSPEED) { + int depth = 0; + struct usbdevice_s *parent = usbdev->hub->usbdev; + while (parent) { + depth++; + parent = parent->hub->usbdev; + } + + ret = set_hub_depth(usbdev->defpipe, depth); + if (ret) + return ret; + } + + // Turn on power to ports. + int port; + for (port=0; port<desc.bNbrPorts; port++) { + ret = set_port_feature(&hub, port, USB_PORT_FEAT_POWER); + if (ret) + return ret; + } + // Wait for port power to stabilize. + msleep(desc.bPwrOn2PwrGood * 2); + + usb_enumerate(&hub); + + dprintf(1, "Initialized USB HUB (%d ports used)\n", hub.devcount); + if (hub.devcount) + return 0; + return -1; +} |