Hi, attached new patch includes improved hot-plug support. It is also committed into usb branch (rev. 2428). It should work now on UHCI, OHCI and also on non-root hubs.
Could somebody test it ? (New plugged device should be accessible after "ls" command. Disconnected devices remain listed but they are not working.) Changed files: bus/usb/ohci.c bus/usb/uhci.c bus/usb/usb.c bus/usb/usbhub.c include/grub/usb.h include/grub/usbtrans.h To do: - hot-plugging on OHCI is limited because of limited number of "statically" allocated EDs: -- number of EDs could be increased in ohci.c -- de-allocation of EDs should be added in ohci.c - when device disconnect is detected on some port, related USB device structures should be de-allocated -- if disconnected device is hub, it is necessary to de-allocate also all devices possibly connected to this hub. -- to have chance to do it, it is necessary to implement some structures to know which USB device is connected to which port of which UHCI/OHCI device and non-root hub -- to avoid problem in disk cache or somewhere else, it will be maybe better to keep de-allocated device numbers not usable in future even if related device is disconnected (i.e. if device "usb0" is disconnected, no newly connected device can be named as "usb0". "ls" command should not display disconnected devices.) Regards Ales
diff -urB ./my-repo/usb/bus/usb/ohci.c ./usb/bus/usb/ohci.c --- ./my-repo/usb/bus/usb/ohci.c 2010-07-06 19:48:28.000000000 +0200 +++ ./usb/bus/usb/ohci.c 2010-07-08 21:18:37.000000000 +0200 @@ -148,6 +148,7 @@ #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_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 @@ } } + /* 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 @@ 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_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 @@ 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 @@ 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_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 @@ } 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_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_dprintf ("ohci", "root hub ports=%d\n", portinfo & 0xFF); - /* The root hub has exactly two ports. */ return portinfo & 0xFF; } diff -urB ./my-repo/usb/bus/usb/uhci.c ./usb/bus/usb/uhci.c --- ./my-repo/usb/bus/usb/uhci.c 2010-07-06 19:48:28.000000000 +0200 +++ ./usb/bus/usb/uhci.c 2010-07-06 22:24:31.000000000 +0200 @@ -644,10 +644,14 @@ 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 @@ } 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_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 -urB ./my-repo/usb/bus/usb/usb.c ./usb/bus/usb/usb.c --- ./my-repo/usb/bus/usb/usb.c 2010-07-06 19:48:28.000000000 +0200 +++ ./usb/bus/usb/usb.c 2010-07-07 00:23:38.000000000 +0200 @@ -225,6 +225,20 @@ } } + 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 @@ 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 -urB ./my-repo/usb/bus/usb/usbhub.c ./usb/bus/usb/usbhub.c --- ./my-repo/usb/bus/usb/usbhub.c 2010-07-06 19:48:28.000000000 +0200 +++ ./usb/bus/usb/usbhub.c 2010-07-08 00:28:15.000000000 +0200 @@ -23,8 +23,10 @@ #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[128]; +static struct grub_usb_device *grub_usb_devs[GRUB_USBHUB_MAX_DEVICES]; struct grub_usb_hub { @@ -44,6 +46,7 @@ { 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 @@ 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 @@ 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_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 @@ { int i; struct grub_usb_hub *hub; + int changed=0; hub = grub_malloc (sizeof (*hub)); if (!hub) @@ -248,7 +287,10 @@ 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 @@ 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 @@ 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 @@ { int i; - for (i = 0; i < 128; i++) + for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++) { if (grub_usb_devs[i]) { diff -urB ./my-repo/usb/include/grub/usb.h ./usb/include/grub/usb.h --- ./my-repo/usb/include/grub/usb.h 2010-07-06 19:48:28.000000000 +0200 +++ ./usb/include/grub/usb.h 2010-07-07 00:01:43.000000000 +0200 @@ -104,7 +104,7 @@ 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_poll_devices (void); +void grub_usb_device_attach (grub_usb_device_t dev); + #endif /* GRUB_USB_H */ diff -urB ./my-repo/usb/include/grub/usbtrans.h ./usb/include/grub/usbtrans.h --- ./my-repo/usb/include/grub/usbtrans.h 2010-07-06 19:48:28.000000000 +0200 +++ ./usb/include/grub/usbtrans.h 2010-07-06 22:04:26.000000000 +0200 @@ -93,10 +93,12 @@ #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
_______________________________________________ Grub-devel mailing list Grub-devel@gnu.org http://lists.gnu.org/mailman/listinfo/grub-devel