USB hot-plugging

This commit is contained in:
starous 2010-07-08 22:54:35 +02:00
parent 2a9caccf7b
commit 1b43dba911
6 changed files with 271 additions and 63 deletions

View file

@ -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;
}

View file

@ -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))

View file

@ -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

View file

@ -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_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])
{

View file

@ -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 */

View file

@ -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