summaryrefslogtreecommitdiffstats
path: root/qemu/roms/seabios/src/hw/usb-hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'qemu/roms/seabios/src/hw/usb-hub.c')
-rw-r--r--qemu/roms/seabios/src/hw/usb-hub.c205
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;
+}