From 1b43dba911d92eb3f4fe9e9e8c3a588d893e077f Mon Sep 17 00:00:00 2001 From: starous Date: Thu, 8 Jul 2010 22:54:35 +0200 Subject: [PATCH] USB hot-plugging --- bus/usb/ohci.c | 81 +++++++++------ bus/usb/uhci.c | 11 ++- bus/usb/usb.c | 23 +++-- bus/usb/usbhub.c | 213 ++++++++++++++++++++++++++++++++++++---- include/grub/usb.h | 4 +- include/grub/usbtrans.h | 2 + 6 files changed, 271 insertions(+), 63 deletions(-) diff --git a/bus/usb/ohci.c b/bus/usb/ohci.c index fed82d129..36bb5dd44 100644 --- a/bus/usb/ohci.c +++ b/bus/usb/ohci.c @@ -148,6 +148,7 @@ typedef enum #define GRUB_OHCI_REG_CONTROL_BULK_ENABLE (1 << 5) #define GRUB_OHCI_REG_CONTROL_CONTROL_ENABLE (1 << 4) +#define GRUB_OHCI_RESET_CONNECT_CHANGE (1 << 16) #define GRUB_OHCI_CTRL_EDS 16 #define GRUB_OHCI_BULK_EDS 16 #define GRUB_OHCI_TDS 256 @@ -420,23 +421,8 @@ grub_ohci_pci_iter (grub_pci_device_t dev, (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBA) & ~GRUB_OHCI_RHUB_PORT_POWER_MASK) | GRUB_OHCI_RHUB_PORT_ALL_POWERED); - /* Wait for stable power (100ms) and stable attachment (100ms) */ - /* I.e. minimum wait time should be probably 200ms. */ - /* We assume that device is attached when ohci is loaded. */ - /* Some devices take long time to power-on or indicate attach. */ - /* Here is some experimental value which should probably mostly work. */ - /* Cameras with manual USB mode selection and maybe some other similar - * devices will not work in some cases - they are repowered during - * ownership change and then they are starting slowly and mostly they - * are wanting select proper mode again... - * The same situation can be on computers where BIOS not set-up OHCI - * to be at least powered USB bus (maybe it is Yeelong case...?) - * Possible workaround could be for example some prompt - * for user with confirmation of proper USB device connection. - * Another workaround - "rmmod usbms", "rmmod ohci", proper start - * and configuration of USB device and then "insmod ohci" - * and "insmod usbms". */ - grub_millisleep (500); + /* Now we have hot-plugging, we need to wait for stable power only */ + grub_millisleep (100); /* Link to ohci now that initialisation is successful. */ o->next = ohci; @@ -998,6 +984,15 @@ grub_ohci_transfer (grub_usb_controller_t dev, } } + /* Even if we have "good" OHCI, in some cases + * tderr_phys can be zero, check it */ + else if ( !tderr_phys ) + { /* Retired TD with error should be previous TD to ED->td_head */ + tderr_phys = GRUB_OHCI_TD_PHYS2VIRT (o, + grub_le_to_cpu32 ( ed_virt->td_head) & ~0xf ) + ->prev_td_phys; + } + /* Prepare pointer to last processed TD and get error code */ tderr_virt = GRUB_OHCI_TD_PHYS2VIRT (o, tderr_phys); /* Set index of last processed TD */ @@ -1095,8 +1090,6 @@ grub_ohci_transfer (grub_usb_controller_t dev, break; } - /* Set empty ED - set HEAD = TAIL = last (not processed) TD */ - ed_virt->td_head = ed_virt->td_tail & ~0xf; } else if (err_unrec) @@ -1117,7 +1110,6 @@ grub_ohci_transfer (grub_usb_controller_t dev, grub_dprintf ("ohci", "Unrecoverable error - OHCI reset\n"); /* Misc. resets. */ - ed_virt->td_head = ed_virt->td_tail & ~0xf; /* Set empty ED - set HEAD = TAIL = last (not processed) TD */ o->hcca->donehead = 0; grub_ohci_writereg32 (o, GRUB_OHCI_REG_INTSTATUS, 0x7f); /* Clears everything */ grub_ohci_writereg32 (o, GRUB_OHCI_REG_CONTROLHEAD, o->ed_ctrl_addr); @@ -1160,10 +1152,12 @@ grub_ohci_transfer (grub_usb_controller_t dev, else transfer->last_trans = -1; - /* Set empty ED - set HEAD = TAIL = last (not processed) TD */ - ed_virt->td_head = ed_virt->td_tail & ~0xf; } + /* Set empty ED - set HEAD = TAIL = last (not processed) TD */ + ed_virt->td_head = grub_cpu_to_le32 ( grub_le_to_cpu32 ( + ed_virt->td_tail) & ~0xf); + /* At this point always should be: * ED has skip bit set and halted or empty or after next SOF, * i.e. it is safe to free all TDs except last not processed @@ -1198,10 +1192,28 @@ grub_ohci_portstatus (grub_usb_controller_t dev, unsigned int port, unsigned int enable) { struct grub_ohci *o = (struct grub_ohci *) dev->data; + grub_uint64_t endtime; grub_dprintf ("ohci", "begin of portstatus=0x%02x\n", grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)); + if (!enable) /* We don't need reset port */ + { + /* Disable the port and wait for it. */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, + GRUB_OHCI_CLEAR_PORT_ENABLE); + endtime = grub_get_time_ms () + 1000; + while ((grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port) + & (1 << 1))) + if (grub_get_time_ms () > endtime) + return grub_error (GRUB_ERR_IO, "OHCI Timed out - disable"); + + grub_dprintf ("ohci", "end of portstatus=0x%02x\n", + grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)); + return GRUB_ERR_NONE; + } + + /* Reset the port */ grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, GRUB_OHCI_SET_PORT_RESET); grub_millisleep (50); /* For root hub should be nominaly 50ms */ @@ -1211,14 +1223,21 @@ grub_ohci_portstatus (grub_usb_controller_t dev, GRUB_OHCI_SET_PORT_RESET_STATUS_CHANGE); grub_millisleep (10); - if (enable) - grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, - GRUB_OHCI_SET_PORT_ENABLE); - else - grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, - GRUB_OHCI_CLEAR_PORT_ENABLE); + /* Enable the port and wait for it. */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, + GRUB_OHCI_SET_PORT_ENABLE); + endtime = grub_get_time_ms () + 1000; + while (! (grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port) + & (1 << 1))) + if (grub_get_time_ms () > endtime) + return grub_error (GRUB_ERR_IO, "OHCI Timed out - enable"); + grub_millisleep (10); + /* Reset bit Connect Status Change */ + grub_ohci_writereg32 (o, GRUB_OHCI_REG_RHUBPORT + port, + GRUB_OHCI_RESET_CONNECT_CHANGE); + grub_dprintf ("ohci", "end of portstatus=0x%02x\n", grub_ohci_readreg32 (o, GRUB_OHCI_REG_RHUBPORT + port)); @@ -1226,7 +1245,7 @@ grub_ohci_portstatus (grub_usb_controller_t dev, } static grub_usb_speed_t -grub_ohci_detect_dev (grub_usb_controller_t dev, int port) +grub_ohci_detect_dev (grub_usb_controller_t dev, int port, int *changed) { struct grub_ohci *o = (struct grub_ohci *) dev->data; grub_uint32_t status; @@ -1235,6 +1254,9 @@ grub_ohci_detect_dev (grub_usb_controller_t dev, int port) grub_dprintf ("ohci", "detect_dev status=0x%02x\n", status); + /* Connect Status Change bit - it detects change of connection */ + *changed = ((status & GRUB_OHCI_RESET_CONNECT_CHANGE) != 0); + if (! (status & 1)) return GRUB_USB_SPEED_NONE; else if (status & (1 << 9)) @@ -1253,7 +1275,6 @@ grub_ohci_hubports (grub_usb_controller_t dev) grub_dprintf ("ohci", "root hub ports=%d\n", portinfo & 0xFF); - /* The root hub has exactly two ports. */ return portinfo & 0xFF; } diff --git a/bus/usb/uhci.c b/bus/usb/uhci.c index e85f07a6f..efdf3aceb 100644 --- a/bus/usb/uhci.c +++ b/bus/usb/uhci.c @@ -644,10 +644,14 @@ grub_uhci_portstatus (grub_usb_controller_t dev, grub_dprintf ("uhci", "waiting for the port to be enabled\n"); endtime = grub_get_time_ms () + 1000; - while (! (grub_uhci_readreg16 (u, reg) & (1 << 2))) + while (! ((status = grub_uhci_readreg16 (u, reg)) & (1 << 2))) if (grub_get_time_ms () > endtime) return grub_error (GRUB_ERR_IO, "UHCI Timed out"); + /* Reset bit Connect Status Change */ + grub_uhci_writereg16 (u, reg, status | (1 << 1)); + + /* Read final port status */ status = grub_uhci_readreg16 (u, reg); grub_dprintf ("uhci", ">3detect=0x%02x\n", status); @@ -656,7 +660,7 @@ grub_uhci_portstatus (grub_usb_controller_t dev, } static grub_usb_speed_t -grub_uhci_detect_dev (grub_usb_controller_t dev, int port) +grub_uhci_detect_dev (grub_usb_controller_t dev, int port, int *changed) { struct grub_uhci *u = (struct grub_uhci *) dev->data; int reg; @@ -676,6 +680,9 @@ grub_uhci_detect_dev (grub_usb_controller_t dev, int port) grub_dprintf ("uhci", "detect=0x%02x port=%d\n", status, port); + /* Connect Status Change bit - it detects change of connection */ + *changed = ((status & (1 << 1)) != 0); + if (! (status & 1)) return GRUB_USB_SPEED_NONE; else if (status & (1 << 8)) diff --git a/bus/usb/usb.c b/bus/usb/usb.c index 4f6f9e54c..f6a0a8b56 100644 --- a/bus/usb/usb.c +++ b/bus/usb/usb.c @@ -225,6 +225,20 @@ grub_usb_device_initialize (grub_usb_device_t dev) } } + return GRUB_USB_ERR_NONE; + + fail: + + for (i = 0; i < 8; i++) + grub_free (dev->config[i].descconf); + + return err; +} + +void grub_usb_device_attach (grub_usb_device_t dev) +{ + int i; + /* XXX: Just check configuration 0 for now. */ for (i = 0; i < dev->config[0].descconf->numif; i++) { @@ -243,15 +257,6 @@ grub_usb_device_initialize (grub_usb_device_t dev) if (interf->class == desc->class && desc->hook (dev, 0, i)) dev->config[0].interf[i].attached = 1; } - - return GRUB_USB_ERR_NONE; - - fail: - - for (i = 0; i < 8; i++) - grub_free (dev->config[i].descconf); - - return err; } void diff --git a/bus/usb/usbhub.c b/bus/usb/usbhub.c index f50418069..6c14f4b8d 100644 --- a/bus/usb/usbhub.c +++ b/bus/usb/usbhub.c @@ -23,8 +23,10 @@ #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[128]; +static struct grub_usb_device *grub_usb_devs[GRUB_USBHUB_MAX_DEVICES]; struct grub_usb_hub { @@ -44,6 +46,7 @@ grub_usb_hub_add_dev (grub_usb_controller_t controller, grub_usb_speed_t speed) { grub_usb_device_t dev; int i; + grub_usb_err_t err; dev = grub_zalloc (sizeof (struct grub_usb_device)); if (! dev) @@ -52,31 +55,51 @@ grub_usb_hub_add_dev (grub_usb_controller_t controller, grub_usb_speed_t speed) dev->controller = *controller; dev->speed = speed; - grub_usb_device_initialize (dev); + err = grub_usb_device_initialize (dev); + if (err) + { + grub_free (dev); + return NULL; + } /* Assign a new address to the device. */ - for (i = 1; i < 128; i++) + for (i = 1; i < GRUB_USBHUB_MAX_DEVICES; i++) { if (! grub_usb_devs[i]) break; } - if (i == 128) + 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; } - 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); + 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; + /* Wait "recovery interval", spec. says 2ms */ + grub_millisleep (2); + + grub_usb_device_attach (dev); + return dev; } @@ -144,7 +167,7 @@ grub_usb_add_hub (grub_usb_device_t dev) if (err) continue; grub_dprintf ("usb", "Hub port %d status: 0x%02x\n", i, status); - + /* If connected, reset and enable the port. */ if (status & GRUB_USB_HUB_STATUS_CONNECTED) { @@ -191,7 +214,22 @@ grub_usb_add_hub (grub_usb_device_t dev) (grub_get_time_ms() < timeout) ); if (err || !(status & GRUB_USB_HUB_STATUS_C_PORT_RESET) ) continue; + + /* Wait a recovery time after reset, spec. says 10ms */ + grub_millisleep (10); + /* Do reset of connection change bit */ + err = 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_CONNECTED, + i, 0, 0); + /* Just ignore the device if the Hub reports some error */ + if (err) + continue; + grub_dprintf ("usb", "Hub port - cleared connection change\n"); + /* Add the device and assign a device address to it. */ grub_dprintf ("usb", "Call hub_add_dev - port %d\n", i); next_dev = grub_usb_hub_add_dev (&dev->controller, speed); @@ -239,6 +277,7 @@ grub_usb_root_hub (grub_usb_controller_t controller) { int i; struct grub_usb_hub *hub; + int changed=0; hub = grub_malloc (sizeof (*hub)); if (!hub) @@ -248,7 +287,10 @@ grub_usb_root_hub (grub_usb_controller_t controller) hubs = hub; hub->controller = grub_malloc (sizeof (*controller)); if (!hub->controller) - return GRUB_USB_ERR_INTERNAL; + { + grub_free (hub); + return GRUB_USB_ERR_INTERNAL; + } grub_memcpy (hub->controller, controller, sizeof (*controller)); hub->dev = 0; @@ -258,13 +300,15 @@ grub_usb_root_hub (grub_usb_controller_t controller) hub->speed = grub_malloc (sizeof (hub->speed[0]) * hub->nports); if (!hub->speed) { + grub_free (hub->controller); grub_free (hub); return GRUB_USB_ERR_INTERNAL; } for (i = 0; i < hub->nports; i++) { - hub->speed[i] = controller->dev->detect_dev (hub->controller, i); + hub->speed[i] = controller->dev->detect_dev (hub->controller, i, + &changed); if (hub->speed[i] != GRUB_USB_SPEED_NONE) attach_root_port (hub->controller, i, hub->speed[i]); @@ -273,30 +317,157 @@ grub_usb_root_hub (grub_usb_controller_t controller) return GRUB_USB_ERR_NONE; } +static void +poll_nonroot_hub (grub_usb_device_t dev) +{ + struct grub_usb_usb_hubdesc hubdesc; + grub_err_t err; + int i; + grub_uint64_t timeout; + grub_usb_device_t next_dev; + + 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; + + /* Iterate over the Hub ports. */ + for (i = 1; i <= hubdesc.portcnt; i++) + { + grub_uint32_t status; + + /* 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); + /* Just ignore the device if the Hub does not report the + status. */ + if (err) + continue; + + /* Connected and status of connection changed ? */ + if ((status & GRUB_USB_HUB_STATUS_CONNECTED) + && (status & GRUB_USB_HUB_STATUS_C_CONNECTED)) + { + grub_usb_speed_t speed; + + /* Determine the device speed. */ + if (status & GRUB_USB_HUB_STATUS_LOWSPEED) + speed = GRUB_USB_SPEED_LOW; + else + { + if (status & GRUB_USB_HUB_STATUS_HIGHSPEED) + speed = GRUB_USB_SPEED_HIGH; + else + speed = GRUB_USB_SPEED_FULL; + } + + /* A device is actually connected to this port. + * Now do reset of port. */ + err = 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); + /* If the Hub does not cooperate for this port, just skip + the port. */ + if (err) + continue; + + /* Wait for reset procedure done */ + timeout = grub_get_time_ms () + 1000; + do + { + /* 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); + } + while (!err && + !(status & GRUB_USB_HUB_STATUS_C_PORT_RESET) && + (grub_get_time_ms() < timeout) ); + if (err || !(status & GRUB_USB_HUB_STATUS_C_PORT_RESET) ) + continue; + + /* Wait a recovery time after reset, spec. says 10ms */ + grub_millisleep (10); + + /* Do reset of connection change bit */ + err = 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_CONNECTED, + i, 0, 0); + /* Just ignore the device if the Hub reports some error */ + if (err) + continue; + + /* Add the device and assign a device address to it. */ + next_dev = grub_usb_hub_add_dev (&dev->controller, speed); + if (! next_dev) + continue; + + /* If the device is a Hub, scan it for more devices. */ + if (next_dev->descdev.class == 0x09) + grub_usb_add_hub (next_dev); + } + } + + return; +} + void grub_usb_poll_devices (void) { struct grub_usb_hub *hub; + int i; for (hub = hubs; hub; hub = hub->next) { - int i; + int changed=0; /* 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; - speed = hub->controller->dev->detect_dev (hub->controller, i); + speed = hub->controller->dev->detect_dev (hub->controller, i, + &changed); - if (speed == hub->speed[i]) - continue; + if (speed != GRUB_USB_SPEED_NONE) + { + if (changed) + attach_root_port (hub->controller, i, speed); + } - if (hub->speed[i] == GRUB_USB_SPEED_NONE - && speed != GRUB_USB_SPEED_NONE) - attach_root_port (hub->controller, i, speed); + /* XXX: There should be also handling + * of disconnected devices. */ + hub->speed[i] = speed; } } + + /* 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); + } + } + } int @@ -304,7 +475,7 @@ grub_usb_iterate (int (*hook) (grub_usb_device_t dev)) { int i; - for (i = 0; i < 128; i++) + for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++) { if (grub_usb_devs[i]) { diff --git a/include/grub/usb.h b/include/grub/usb.h index 7c9dba12a..3c17318fc 100644 --- a/include/grub/usb.h +++ b/include/grub/usb.h @@ -104,7 +104,7 @@ struct grub_usb_controller_dev grub_err_t (*portstatus) (grub_usb_controller_t dev, unsigned int port, unsigned int enable); - grub_usb_speed_t (*detect_dev) (grub_usb_controller_t dev, int port); + grub_usb_speed_t (*detect_dev) (grub_usb_controller_t dev, int port, int *changed); /* The next host controller. */ struct grub_usb_controller_dev *next; @@ -229,4 +229,6 @@ void grub_usb_unregister_attach_hook_class (struct grub_usb_attach_desc *desc); void grub_usb_poll_devices (void); +void grub_usb_device_attach (grub_usb_device_t dev); + #endif /* GRUB_USB_H */ diff --git a/include/grub/usbtrans.h b/include/grub/usbtrans.h index 8f49c246f..e68698c1d 100644 --- a/include/grub/usbtrans.h +++ b/include/grub/usbtrans.h @@ -93,10 +93,12 @@ typedef struct grub_usb_transfer *grub_usb_transfer_t; #define GRUB_USB_HUB_FEATURE_PORT_RESET 0x04 #define GRUB_USB_HUB_FEATURE_PORT_POWER 0x08 +#define GRUB_USB_HUB_FEATURE_C_CONNECTED 0x10 #define GRUB_USB_HUB_STATUS_CONNECTED (1 << 0) #define GRUB_USB_HUB_STATUS_LOWSPEED (1 << 9) #define GRUB_USB_HUB_STATUS_HIGHSPEED (1 << 10) +#define GRUB_USB_HUB_STATUS_C_CONNECTED (1 << 16) #define GRUB_USB_HUB_STATUS_C_PORT_RESET (1 << 20) struct grub_usb_packet_setup