Commit b3dca131 authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman

[PATCH] USB: track usb ch9 device state

This patch merges the USB state definitions from the ARM Linux
code (inside the sa1100 driver) and uses them to track what can
be done with the device.  That replaces the recently added
"udev->present" flag with a more complete/standard state model.

There are a few changes that might affect behavior if things
start to go really haywire:

 - usb_set_address() and usb_set_configuration(), used while
   enumerating, handle some unlikely cases more correctly:
   don't allow setting address to zero (undefined behavior),
   and do allow un-configuring (config 0).  (Adds a FIXME
   for an existing set-configuration bug too.)

 - usb_disconnect() flags the state change earlier (as soon
   as it's known).

 - usb_submit_urb() works in the states where messaging is
   allowed, and also enforces the "unless configured, only
   control traffic is legal" rule.

 - usb_unlink_urb() doesn't care any more about that state.
   (There seemed to be agreement that it must not matter.)

This will help with some further cleanups in the complex of
issues relating to driver removal, device removal, config
changing (with driver unbind and rebind), reset, and so on.
parent a9048822
...@@ -876,6 +876,7 @@ static void usb_hub_port_connect_change(struct usb_hub *hubstate, int port, ...@@ -876,6 +876,7 @@ static void usb_hub_port_connect_change(struct usb_hub *hubstate, int port,
} }
hub->children[port] = dev; hub->children[port] = dev;
dev->state = USB_STATE_POWERED;
/* Reset the device, and detect its speed */ /* Reset the device, and detect its speed */
if (usb_hub_port_reset(hub, port, dev, delay)) { if (usb_hub_port_reset(hub, port, dev, delay)) {
......
...@@ -904,17 +904,29 @@ int usb_set_configuration(struct usb_device *dev, int configuration) ...@@ -904,17 +904,29 @@ int usb_set_configuration(struct usb_device *dev, int configuration)
break; break;
} }
} }
if (!cp) { if ((!cp && configuration != 0) || (cp && configuration == 0)) {
warn("selecting invalid configuration %d", configuration); warn("selecting invalid configuration %d", configuration);
return -EINVAL; return -EINVAL;
} }
/* if it's already configured, clear out old state first. */
if (dev->state != USB_STATE_ADDRESS) {
/* FIXME unbind drivers from all "old" interfaces.
* handshake with hcd to reset cached hc endpoint state.
*/
}
if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
USB_REQ_SET_CONFIGURATION, 0, configuration, 0, USB_REQ_SET_CONFIGURATION, 0, configuration, 0,
NULL, 0, HZ * USB_CTRL_SET_TIMEOUT)) < 0) NULL, 0, HZ * USB_CTRL_SET_TIMEOUT)) < 0)
return ret; return ret;
if (configuration)
dev->state = USB_STATE_CONFIGURED;
else
dev->state = USB_STATE_ADDRESS;
dev->actconfig = cp; dev->actconfig = cp;
/* reset more hc/hcd endpoint state */
dev->toggle[0] = 0; dev->toggle[0] = 0;
dev->toggle[1] = 0; dev->toggle[1] = 0;
usb_set_maxpacket(dev); usb_set_maxpacket(dev);
......
...@@ -195,7 +195,9 @@ int usb_submit_urb(struct urb *urb, int mem_flags) ...@@ -195,7 +195,9 @@ int usb_submit_urb(struct urb *urb, int mem_flags)
if (!urb || urb->hcpriv || !urb->complete) if (!urb || urb->hcpriv || !urb->complete)
return -EINVAL; return -EINVAL;
if (!(dev = urb->dev) || !dev->present || !dev->bus || dev->devnum <= 0) if (!(dev = urb->dev) ||
(dev->state < USB_STATE_DEFAULT) ||
(!dev->bus) || (dev->devnum <= 0))
return -ENODEV; return -ENODEV;
if (!(op = dev->bus->op) || !op->submit_urb) if (!(op = dev->bus->op) || !op->submit_urb)
return -ENODEV; return -ENODEV;
...@@ -211,6 +213,9 @@ int usb_submit_urb(struct urb *urb, int mem_flags) ...@@ -211,6 +213,9 @@ int usb_submit_urb(struct urb *urb, int mem_flags)
temp = usb_pipetype (pipe); temp = usb_pipetype (pipe);
is_out = usb_pipeout (pipe); is_out = usb_pipeout (pipe);
if (!usb_pipecontrol (pipe) && dev->state < USB_STATE_CONFIGURED)
return -ENODEV;
/* (actually HCDs may need to duplicate this, endpoint might yet /* (actually HCDs may need to duplicate this, endpoint might yet
* stall due to queued bulk/intr transactions that complete after * stall due to queued bulk/intr transactions that complete after
* we check) * we check)
...@@ -376,7 +381,7 @@ int usb_submit_urb(struct urb *urb, int mem_flags) ...@@ -376,7 +381,7 @@ int usb_submit_urb(struct urb *urb, int mem_flags)
*/ */
int usb_unlink_urb(struct urb *urb) int usb_unlink_urb(struct urb *urb)
{ {
if (urb && urb->dev && urb->dev->present && urb->dev->bus && urb->dev->bus->op) if (urb && urb->dev && urb->dev->bus && urb->dev->bus->op)
return urb->dev->bus->op->unlink_urb(urb); return urb->dev->bus->op->unlink_urb(urb);
else else
return -ENODEV; return -ENODEV;
......
...@@ -679,7 +679,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus) ...@@ -679,7 +679,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, struct usb_bus *bus)
memset(dev, 0, sizeof(*dev)); memset(dev, 0, sizeof(*dev));
device_initialize(&dev->dev); device_initialize(&dev->dev);
dev->present = 1; dev->state = USB_STATE_ATTACHED;
usb_bus_get(bus); usb_bus_get(bus);
...@@ -828,6 +828,11 @@ void usb_disconnect(struct usb_device **pdev) ...@@ -828,6 +828,11 @@ void usb_disconnect(struct usb_device **pdev)
*pdev = NULL; *pdev = NULL;
/* mark the device as inactive, so any further urb submissions for
* this device will fail.
*/
dev->state = USB_STATE_NOTATTACHED;
dev_info (&dev->dev, "USB disconnect, address %d\n", dev->devnum); dev_info (&dev->dev, "USB disconnect, address %d\n", dev->devnum);
/* Free up all the children before we remove this device */ /* Free up all the children before we remove this device */
...@@ -855,10 +860,6 @@ void usb_disconnect(struct usb_device **pdev) ...@@ -855,10 +860,6 @@ void usb_disconnect(struct usb_device **pdev)
} }
device_unregister(&dev->dev); device_unregister(&dev->dev);
/* mark the device as not present so any further urb submissions for
* this device will fail. */
dev->present = 0;
/* Decrement the reference count, it'll auto free everything when */ /* Decrement the reference count, it'll auto free everything when */
/* it hits 0 which could very well be now */ /* it hits 0 which could very well be now */
usb_put_dev(dev); usb_put_dev(dev);
...@@ -906,9 +907,17 @@ void usb_connect(struct usb_device *dev) ...@@ -906,9 +907,17 @@ void usb_connect(struct usb_device *dev)
// otherwise used internally, for usb_new_device() // otherwise used internally, for usb_new_device()
int usb_set_address(struct usb_device *dev) int usb_set_address(struct usb_device *dev)
{ {
return usb_control_msg(dev, usb_snddefctrl(dev), USB_REQ_SET_ADDRESS, int retval;
// FIXME USB_CTRL_SET_TIMEOUT
0, dev->devnum, 0, NULL, 0, HZ * USB_CTRL_GET_TIMEOUT); if (dev->devnum == 0)
return -EINVAL;
if (dev->state != USB_STATE_DEFAULT && dev->state != USB_STATE_ADDRESS)
return -EINVAL;
retval = usb_control_msg(dev, usb_snddefctrl(dev), USB_REQ_SET_ADDRESS,
0, dev->devnum, 0, NULL, 0, HZ * USB_CTRL_SET_TIMEOUT);
if (retval == 0)
dev->state = USB_STATE_ADDRESS;
return retval;
} }
...@@ -1014,7 +1023,8 @@ int usb_new_device(struct usb_device *dev, struct device *parent) ...@@ -1014,7 +1023,8 @@ int usb_new_device(struct usb_device *dev, struct device *parent)
/* dma masks come from the controller; readonly, except to hcd */ /* dma masks come from the controller; readonly, except to hcd */
dev->dev.dma_mask = parent->dma_mask; dev->dev.dma_mask = parent->dma_mask;
/* USB device state == default ... it's not usable yet */ /* it's not usable yet */
dev->state = USB_STATE_DEFAULT;
/* USB 2.0 section 5.5.3 talks about ep0 maxpacket ... /* USB 2.0 section 5.5.3 talks about ep0 maxpacket ...
* it's fixed size except for full speed devices. * it's fixed size except for full speed devices.
...@@ -1049,6 +1059,7 @@ int usb_new_device(struct usb_device *dev, struct device *parent) ...@@ -1049,6 +1059,7 @@ int usb_new_device(struct usb_device *dev, struct device *parent)
if (err < 0) { if (err < 0) {
dev_err(&dev->dev, "USB device not accepting new address=%d (error=%d)\n", dev_err(&dev->dev, "USB device not accepting new address=%d (error=%d)\n",
dev->devnum, err); dev->devnum, err);
dev->state = USB_STATE_DEFAULT;
clear_bit(dev->devnum, dev->bus->devmap.devicemap); clear_bit(dev->devnum, dev->bus->devmap.devicemap);
dev->devnum = -1; dev->devnum = -1;
return 1; return 1;
......
...@@ -213,7 +213,7 @@ struct usb_tt; ...@@ -213,7 +213,7 @@ struct usb_tt;
struct usb_device { struct usb_device {
int devnum; /* Address on USB bus */ int devnum; /* Address on USB bus */
char devpath [16]; /* Use in messages: /port/port/... */ char devpath [16]; /* Use in messages: /port/port/... */
enum usb_device_state state; /* configured, not attached, etc */
enum usb_device_speed speed; /* high/full/low (or error) */ enum usb_device_speed speed; /* high/full/low (or error) */
struct usb_tt *tt; /* low/full speed dev, highspeed hub */ struct usb_tt *tt; /* low/full speed dev, highspeed hub */
...@@ -240,7 +240,6 @@ struct usb_device { ...@@ -240,7 +240,6 @@ struct usb_device {
int have_langid; /* whether string_langid is valid yet */ int have_langid; /* whether string_langid is valid yet */
int string_langid; /* language ID for strings */ int string_langid; /* language ID for strings */
int present; /* if device is present or not */
void *hcpriv; /* Host Controller private data */ void *hcpriv; /* Host Controller private data */
......
...@@ -291,4 +291,25 @@ enum usb_device_speed { ...@@ -291,4 +291,25 @@ enum usb_device_speed {
USB_SPEED_HIGH /* usb 2.0 */ USB_SPEED_HIGH /* usb 2.0 */
}; };
enum usb_device_state {
/* NOTATTACHED isn't in the USB spec, and this state acts
* the same as ATTACHED ... but it's clearer this way.
*/
USB_STATE_NOTATTACHED = 0,
/* the chapter 9 device states */
USB_STATE_ATTACHED,
USB_STATE_POWERED,
USB_STATE_DEFAULT, /* limited function */
USB_STATE_ADDRESS,
USB_STATE_CONFIGURED, /* most functions */
USB_STATE_SUSPENDED
/* NOTE: there are actually four different SUSPENDED
* states, returning to POWERED, DEFAULT, ADDRESS, or
* CONFIGURED respectively when SOF tokens flow again.
*/
};
#endif /* __LINUX_USB_CH9_H */ #endif /* __LINUX_USB_CH9_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment