#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
(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;
}
}
+ /* 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 */
break;
}
- /* Set empty ED - set HEAD = TAIL = last (not processed) TD */
- ed_virt->td_head = ed_virt->td_tail & ~0xf;
}
else if (err_unrec)
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);
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
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 */
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));
}
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;
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))
grub_dprintf ("ohci", "root hub ports=%d\n", portinfo & 0xFF);
- /* The root hub has exactly two ports. */
return portinfo & 0xFF;
}
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);
}
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;
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))
}
}
+ 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++)
{
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
#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
{
{
grub_usb_device_t dev;
int i;
+ grub_usb_err_t err;
dev = grub_zalloc (sizeof (struct grub_usb_device));
if (! dev)
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;
}
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)
{
(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);
{
int i;
struct grub_usb_hub *hub;
+ int changed=0;
hub = grub_malloc (sizeof (*hub));
if (!hub)
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;
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]);
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
{
int i;
- for (i = 0; i < 128; i++)
+ for (i = 0; i < GRUB_USBHUB_MAX_DEVICES; i++)
{
if (grub_usb_devs[i])
{
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;
void grub_usb_poll_devices (void);
+void grub_usb_device_attach (grub_usb_device_t dev);
+
#endif /* GRUB_USB_H */
#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