/* usb.c - USB Hub Support. */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2008 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GRUB 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GRUB. If not, see . */ #include #include #include #include #include #define GRUB_USBHUB_MAX_DEVICES 128 /* USB Supports 127 devices, with device 0 as special case. */ static struct grub_usb_device *grub_usb_devs[GRUB_USBHUB_MAX_DEVICES]; static int rescan = 0; static int npending = 0; struct grub_usb_hub { struct grub_usb_hub *next; grub_usb_controller_t controller; int nports; struct grub_usb_device **devices; struct grub_usb_hub_port *ports; grub_usb_device_t dev; }; static struct grub_usb_hub *hubs; static grub_usb_controller_dev_t grub_usb_list; /* Add a device that currently has device number 0 and resides on CONTROLLER, the Hub reported that the device speed is SPEED. */ static grub_usb_device_t grub_usb_hub_add_dev (grub_usb_controller_t controller, grub_usb_speed_t speed, int split_hubport, int split_hubaddr) { grub_usb_device_t dev; int i; grub_usb_err_t err; grub_boot_time ("Attaching USB device"); dev = grub_zalloc (sizeof (struct grub_usb_device)); if (! dev) return NULL; dev->controller = *controller; dev->speed = speed; dev->split_hubport = split_hubport; dev->split_hubaddr = split_hubaddr; err = grub_usb_device_initialize (dev); if (err) { grub_free (dev); return NULL; } /* Assign a new address to the device. */ for (i = 1; i < GRUB_USBHUB_MAX_DEVICES; i++) { if (! grub_usb_devs[i]) break; } if (i == GRUB_USBHUB_MAX_DEVICES) { grub_error (GRUB_ERR_IO, "can't assign address to USB device"); for (i = 0; i < 8; i++) grub_free (dev->config[i].descconf); grub_free (dev); return NULL; } err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT | GRUB_USB_REQTYPE_STANDARD | GRUB_USB_REQTYPE_TARGET_DEV), GRUB_USB_REQ_SET_ADDRESS, i, 0, 0, NULL); if (err) { for (i = 0; i < 8; i++) grub_free (dev->config[i].descconf); grub_free (dev); return NULL; } dev->addr = i; dev->initialized = 1; grub_usb_devs[i] = dev; grub_dprintf ("usb", "Added new usb device: %p, addr=%d\n", dev, i); grub_dprintf ("usb", "speed=%d, split_hubport=%d, split_hubaddr=%d\n", speed, split_hubport, split_hubaddr); /* Wait "recovery interval", spec. says 2ms */ grub_millisleep (2); grub_boot_time ("Probing USB device driver"); grub_usb_device_attach (dev); grub_boot_time ("Attached USB device"); return dev; } static grub_usb_err_t grub_usb_add_hub (grub_usb_device_t dev) { struct grub_usb_usb_hubdesc hubdesc; grub_usb_err_t err; int i; err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_DEV), GRUB_USB_REQ_GET_DESCRIPTOR, (GRUB_USB_DESCRIPTOR_HUB << 8) | 0, 0, sizeof (hubdesc), (char *) &hubdesc); if (err) return err; grub_dprintf ("usb", "Hub descriptor:\n\t\t len:%d, typ:0x%02x, cnt:%d, char:0x%02x, pwg:%d, curr:%d\n", hubdesc.length, hubdesc.type, hubdesc.portcnt, hubdesc.characteristics, hubdesc.pwdgood, hubdesc.current); /* Activate the first configuration. Hubs should have only one conf. */ grub_dprintf ("usb", "Hub set configuration\n"); grub_usb_set_configuration (dev, 1); dev->nports = hubdesc.portcnt; dev->children = grub_zalloc (hubdesc.portcnt * sizeof (dev->children[0])); dev->ports = grub_zalloc (dev->nports * sizeof (dev->ports[0])); if (!dev->children || !dev->ports) { grub_free (dev->children); grub_free (dev->ports); return GRUB_USB_ERR_INTERNAL; } /* Power on all Hub ports. */ for (i = 1; i <= hubdesc.portcnt; i++) { grub_dprintf ("usb", "Power on - port %d\n", i); /* Power on the port and wait for possible device connect */ grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_OTHER), GRUB_USB_REQ_SET_FEATURE, GRUB_USB_HUB_FEATURE_PORT_POWER, i, 0, NULL); } /* Rest will be done on next usb poll. */ for (i = 0; i < dev->config[0].interf[0].descif->endpointcnt; i++) { struct grub_usb_desc_endp *endp = NULL; endp = &dev->config[0].interf[0].descendp[i]; if ((endp->endp_addr & 128) && grub_usb_get_ep_type(endp) == GRUB_USB_EP_INTERRUPT) { grub_size_t len; dev->hub_endpoint = endp; len = endp->maxpacket; if (len > sizeof (dev->statuschange)) len = sizeof (dev->statuschange); dev->hub_transfer = grub_usb_bulk_read_background (dev, endp, len, (char *) &dev->statuschange); break; } } rescan = 1; return GRUB_ERR_NONE; } static void attach_root_port (struct grub_usb_hub *hub, int portno, grub_usb_speed_t speed) { grub_usb_device_t dev; grub_err_t err; grub_boot_time ("After detect_dev"); /* Enable the port. */ err = hub->controller->dev->portstatus (hub->controller, portno, 1); if (err) return; hub->controller->dev->pending_reset = grub_get_time_ms () + 5000; npending++; grub_millisleep (10); grub_boot_time ("Port enabled"); /* Enable the port and create a device. */ /* High speed device needs not transaction translation and full/low speed device cannot be connected to EHCI root hub and full/low speed device connected to OHCI/UHCI needs not transaction translation - e.g. hubport and hubaddr should be always none (zero) for any device connected to any root hub. */ dev = grub_usb_hub_add_dev (hub->controller, speed, 0, 0); hub->controller->dev->pending_reset = 0; npending--; if (! dev) return; hub->devices[portno] = dev; /* If the device is a Hub, scan it for more devices. */ if (dev->descdev.class == 0x09) grub_usb_add_hub (dev); grub_boot_time ("Attached root port"); } /* Iterate over all controllers found by the driver. */ static int grub_usb_controller_dev_register_iter (grub_usb_controller_t controller, void *data) { grub_usb_controller_dev_t usb = data; struct grub_usb_hub *hub; controller->dev = usb; grub_boot_time ("Registering USB root hub"); hub = grub_malloc (sizeof (*hub)); if (!hub) return GRUB_USB_ERR_INTERNAL; hub->next = hubs; hubs = hub; hub->controller = grub_malloc (sizeof (*controller)); if (!hub->controller) { grub_free (hub); return GRUB_USB_ERR_INTERNAL; } grub_memcpy (hub->controller, controller, sizeof (*controller)); hub->dev = 0; /* Query the number of ports the root Hub has. */ hub->nports = controller->dev->hubports (controller); hub->devices = grub_zalloc (sizeof (hub->devices[0]) * hub->nports); hub->ports = grub_zalloc (sizeof (hub->ports[0]) * hub->nports); if (!hub->devices || !hub->ports) { grub_free (hub->devices); grub_free (hub->ports); grub_free (hub->controller); grub_free (hub); grub_print_error (); return 0; } return 0; } void grub_usb_controller_dev_unregister (grub_usb_controller_dev_t usb) { grub_usb_controller_dev_t *p, q; for (p = &grub_usb_list, q = *p; q; p = &(q->next), q = q->next) if (q == usb) { *p = q->next; break; } } void grub_usb_controller_dev_register (grub_usb_controller_dev_t usb) { int portno; int continue_waiting = 0; struct grub_usb_hub *hub; usb->next = grub_usb_list; grub_usb_list = usb; if (usb->iterate) usb->iterate (grub_usb_controller_dev_register_iter, usb); grub_boot_time ("waiting for stable power on USB root\n"); while (1) { for (hub = hubs; hub; hub = hub->next) if (hub->controller->dev == usb) { /* Wait for completion of insertion and stable power (USB spec.) * Should be at least 100ms, some devices requires more... * There is also another thing - some devices have worse contacts * and connected signal is unstable for some time - we should handle * it - but prevent deadlock in case when device is too faulty... */ for (portno = 0; portno < hub->nports; portno++) { grub_usb_speed_t speed; int changed = 0; speed = hub->controller->dev->detect_dev (hub->controller, portno, &changed); if (hub->ports[portno].state == PORT_STATE_NORMAL && speed != GRUB_USB_SPEED_NONE) { hub->ports[portno].soft_limit_time = grub_get_time_ms () + 250; hub->ports[portno].hard_limit_time = hub->ports[portno].soft_limit_time + 1750; hub->ports[portno].state = PORT_STATE_WAITING_FOR_STABLE_POWER; grub_boot_time ("Scheduling stable power wait for port %p:%d", usb, portno); continue_waiting++; continue; } if (hub->ports[portno].state == PORT_STATE_WAITING_FOR_STABLE_POWER && speed == GRUB_USB_SPEED_NONE) { hub->ports[portno].soft_limit_time = grub_get_time_ms () + 250; continue; } if (hub->ports[portno].state == PORT_STATE_WAITING_FOR_STABLE_POWER && grub_get_time_ms () > hub->ports[portno].soft_limit_time) { hub->ports[portno].state = PORT_STATE_STABLE_POWER; grub_boot_time ("Got stable power wait for port %p:%d", usb, portno); continue_waiting--; continue; } if (hub->ports[portno].state == PORT_STATE_WAITING_FOR_STABLE_POWER && grub_get_time_ms () > hub->ports[portno].hard_limit_time) { hub->ports[portno].state = PORT_STATE_FAILED_DEVICE; continue_waiting--; continue; } } } if (!continue_waiting) break; grub_millisleep (1); } grub_boot_time ("After the stable power wait on USB root"); for (hub = hubs; hub; hub = hub->next) if (hub->controller->dev == usb) for (portno = 0; portno < hub->nports; portno++) if (hub->ports[portno].state == PORT_STATE_STABLE_POWER) { grub_usb_speed_t speed; int changed = 0; hub->ports[portno].state = PORT_STATE_NORMAL; speed = hub->controller->dev->detect_dev (hub->controller, portno, &changed); attach_root_port (hub, portno, speed); } grub_boot_time ("USB root hub registered"); } static void detach_device (grub_usb_device_t dev); static void detach_device (grub_usb_device_t dev) { unsigned i; int k; if (!dev) return; if (dev->descdev.class == GRUB_USB_CLASS_HUB) { if (dev->hub_transfer) grub_usb_cancel_transfer (dev->hub_transfer); for (i = 0; i < dev->nports; i++) detach_device (dev->children[i]); grub_free (dev->children); } for (i = 0; i < ARRAY_SIZE (dev->config); i++) if (dev->config[i].descconf) for (k = 0; k < dev->config[i].descconf->numif; k++) { struct grub_usb_interface *inter = &dev->config[i].interf[k]; if (inter && inter->detach_hook) inter->detach_hook (dev, i, k); } grub_usb_devs[dev->addr] = 0; } static int wait_power_nonroot_hub (grub_usb_device_t dev) { grub_usb_err_t err; int continue_waiting = 0; unsigned i; for (i = 1; i <= dev->nports; i++) if (dev->ports[i - 1].state == PORT_STATE_WAITING_FOR_STABLE_POWER) { grub_uint64_t tm; grub_uint32_t current_status = 0; /* Get the port status. */ err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_OTHER), GRUB_USB_REQ_GET_STATUS, 0, i, sizeof (current_status), (char *) ¤t_status); if (err) { dev->ports[i - 1].state = PORT_STATE_FAILED_DEVICE; continue; } tm = grub_get_time_ms (); if (!(current_status & GRUB_USB_HUB_STATUS_PORT_CONNECTED)) dev->ports[i - 1].soft_limit_time = tm + 250; if (tm >= dev->ports[i - 1].soft_limit_time) { if (dev->controller.dev->pending_reset) continue; /* Now do reset of port. */ grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_OTHER), GRUB_USB_REQ_SET_FEATURE, GRUB_USB_HUB_FEATURE_PORT_RESET, i, 0, 0); dev->ports[i - 1].state = PORT_STATE_NORMAL; grub_boot_time ("Resetting port %p:%d", dev, i - 1); rescan = 1; /* We cannot reset more than one device at the same time ! * Resetting more devices together results in very bad * situation: more than one device has default address 0 * at the same time !!! * Additionaly, we cannot perform another reset * anywhere on the same OHCI controller until * we will finish addressing of reseted device ! */ dev->controller.dev->pending_reset = grub_get_time_ms () + 5000; npending++; continue; } if (tm >= dev->ports[i - 1].hard_limit_time) { dev->ports[i - 1].state = PORT_STATE_FAILED_DEVICE; continue; } continue_waiting = 1; } return continue_waiting && dev->controller.dev->pending_reset == 0; } static void poll_nonroot_hub (grub_usb_device_t dev) { grub_usb_err_t err; unsigned i; grub_uint8_t changed; grub_size_t actual, len; if (!dev->hub_transfer) return; err = grub_usb_check_transfer (dev->hub_transfer, &actual); if (err == GRUB_USB_ERR_WAIT) return; changed = dev->statuschange; len = dev->hub_endpoint->maxpacket; if (len > sizeof (dev->statuschange)) len = sizeof (dev->statuschange); dev->hub_transfer = grub_usb_bulk_read_background (dev, dev->hub_endpoint, len, (char *) &dev->statuschange); if (err || actual == 0 || changed == 0) return; /* Iterate over the Hub ports. */ for (i = 1; i <= dev->nports; i++) { grub_uint32_t status; if (!(changed & (1 << i)) || dev->ports[i - 1].state == PORT_STATE_WAITING_FOR_STABLE_POWER) continue; /* Get the port status. */ err = grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_IN | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_OTHER), GRUB_USB_REQ_GET_STATUS, 0, i, sizeof (status), (char *) &status); grub_dprintf ("usb", "dev = %p, i = %d, status = %08x\n", dev, i, status); if (err) continue; /* FIXME: properly handle these conditions. */ if (status & GRUB_USB_HUB_STATUS_C_PORT_ENABLED) grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_OTHER), GRUB_USB_REQ_CLEAR_FEATURE, GRUB_USB_HUB_FEATURE_C_PORT_ENABLED, i, 0, 0); if (status & GRUB_USB_HUB_STATUS_C_PORT_SUSPEND) grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_OTHER), GRUB_USB_REQ_CLEAR_FEATURE, GRUB_USB_HUB_FEATURE_C_PORT_SUSPEND, i, 0, 0); if (status & GRUB_USB_HUB_STATUS_C_PORT_OVERCURRENT) grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_OTHER), GRUB_USB_REQ_CLEAR_FEATURE, GRUB_USB_HUB_FEATURE_C_PORT_OVERCURRENT, i, 0, 0); if (!dev->controller.dev->pending_reset && (status & GRUB_USB_HUB_STATUS_C_PORT_CONNECTED)) { grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_OTHER), GRUB_USB_REQ_CLEAR_FEATURE, GRUB_USB_HUB_FEATURE_C_PORT_CONNECTED, i, 0, 0); detach_device (dev->children[i - 1]); dev->children[i - 1] = NULL; /* Connected and status of connection changed ? */ if (status & GRUB_USB_HUB_STATUS_PORT_CONNECTED) { grub_boot_time ("Before the stable power wait portno=%d", i); /* A device is actually connected to this port. */ /* Wait for completion of insertion and stable power (USB spec.) * Should be at least 100ms, some devices requires more... * There is also another thing - some devices have worse contacts * and connected signal is unstable for some time - we should handle * it - but prevent deadlock in case when device is too faulty... */ dev->ports[i - 1].soft_limit_time = grub_get_time_ms () + 250; dev->ports[i - 1].hard_limit_time = dev->ports[i - 1].soft_limit_time + 1750; dev->ports[i - 1].state = PORT_STATE_WAITING_FOR_STABLE_POWER; grub_boot_time ("Scheduling stable power wait for port %p:%d", dev, i - 1); continue; } } if (status & GRUB_USB_HUB_STATUS_C_PORT_RESET) { grub_usb_control_msg (dev, (GRUB_USB_REQTYPE_OUT | GRUB_USB_REQTYPE_CLASS | GRUB_USB_REQTYPE_TARGET_OTHER), GRUB_USB_REQ_CLEAR_FEATURE, GRUB_USB_HUB_FEATURE_C_PORT_RESET, i, 0, 0); grub_boot_time ("Port %d reset", i); if (status & GRUB_USB_HUB_STATUS_PORT_CONNECTED) { grub_usb_speed_t speed; grub_usb_device_t next_dev; int split_hubport = 0; int split_hubaddr = 0; /* Determine the device speed. */ if (status & GRUB_USB_HUB_STATUS_PORT_LOWSPEED) speed = GRUB_USB_SPEED_LOW; else { if (status & GRUB_USB_HUB_STATUS_PORT_HIGHSPEED) speed = GRUB_USB_SPEED_HIGH; else speed = GRUB_USB_SPEED_FULL; } /* Wait a recovery time after reset, spec. says 10ms */ grub_millisleep (10); /* Find correct values for SPLIT hubport and hubaddr */ if (speed == GRUB_USB_SPEED_HIGH) { /* HIGH speed device needs not transaction translation */ split_hubport = 0; split_hubaddr = 0; } else /* FULL/LOW device needs hub port and hub address for transaction translation (if connected to EHCI) */ if (dev->speed == GRUB_USB_SPEED_HIGH) { /* This port is the first FULL/LOW speed port in the chain from root hub. Attached device should use its port number and hub address */ split_hubport = i; split_hubaddr = dev->addr; } else { /* This port is NOT the first FULL/LOW speed port in the chain from root hub. Attached device should use values inherited from some parent HIGH speed hub - if any. */ split_hubport = dev->split_hubport; split_hubaddr = dev->split_hubaddr; } /* Add the device and assign a device address to it. */ next_dev = grub_usb_hub_add_dev (&dev->controller, speed, split_hubport, split_hubaddr); if (dev->controller.dev->pending_reset) { dev->controller.dev->pending_reset = 0; npending--; } if (! next_dev) continue; dev->children[i - 1] = next_dev; /* If the device is a Hub, scan it for more devices. */ if (next_dev->descdev.class == 0x09) grub_usb_add_hub (next_dev); } } } } void grub_usb_poll_devices (int wait_for_completion) { struct grub_usb_hub *hub; int i; for (hub = hubs; hub; hub = hub->next) { /* Do we have to recheck number of ports? */ /* No, it should be never changed, it should be constant. */ for (i = 0; i < hub->nports; i++) { grub_usb_speed_t speed = GRUB_USB_SPEED_NONE; int changed = 0; if (hub->controller->dev->pending_reset) { /* Check for possible timeout */ if (grub_get_time_ms () > hub->controller->dev->pending_reset) { /* Something went wrong, reset device was not * addressed properly, timeout happened */ hub->controller->dev->pending_reset = 0; npending--; } } if (!hub->controller->dev->pending_reset) speed = hub->controller->dev->detect_dev (hub->controller, i, &changed); if (changed) { detach_device (hub->devices[i]); hub->devices[i] = NULL; if (speed != GRUB_USB_SPEED_NONE) attach_root_port (hub, i, speed); } } } while (1) { rescan = 0; /* We should check changes of non-root hubs too. */ for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++) { grub_usb_device_t dev = grub_usb_devs[i]; if (dev && dev->descdev.class == 0x09) poll_nonroot_hub (dev); } while (1) { int continue_waiting = 0; for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++) { grub_usb_device_t dev = grub_usb_devs[i]; if (dev && dev->descdev.class == 0x09) continue_waiting = continue_waiting || wait_power_nonroot_hub (dev); } if (!continue_waiting) break; grub_millisleep (1); } if (!(rescan || (npending && wait_for_completion))) break; grub_millisleep (25); } } int grub_usb_iterate (grub_usb_iterate_hook_t hook, void *hook_data) { int i; for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++) { if (grub_usb_devs[i]) { if (hook (grub_usb_devs[i], hook_data)) return 1; } } return 0; }