| /* 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 <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include <grub/dl.h> |
| #include <grub/mm.h> |
| #include <grub/usb.h> |
| #include <grub/misc.h> |
| #include <grub/time.h> |
| |
| #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_USB_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_usb_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_uint32_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; |
| } |