ChangeSet 1.1068.7.6, 2003/03/06 15:46:40-08:00, david-b@pacbell.net [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. drivers/usb/core/hub.c | 1 + drivers/usb/core/message.c | 16 ++++++++++++++-- drivers/usb/core/urb.c | 9 +++++++-- drivers/usb/core/usb.c | 29 ++++++++++++++++++++--------- include/linux/usb.h | 3 +-- include/linux/usb_ch9.h | 21 +++++++++++++++++++++ 6 files changed, 64 insertions(+), 15 deletions(-) diff -Nru a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c --- a/drivers/usb/core/hub.c Thu Mar 6 16:08:00 2003 +++ b/drivers/usb/core/hub.c Thu Mar 6 16:08:00 2003 @@ -876,6 +876,7 @@ } hub->children[port] = dev; + dev->state = USB_STATE_POWERED; /* Reset the device, and detect its speed */ if (usb_hub_port_reset(hub, port, dev, delay)) { diff -Nru a/drivers/usb/core/message.c b/drivers/usb/core/message.c --- a/drivers/usb/core/message.c Thu Mar 6 16:08:00 2003 +++ b/drivers/usb/core/message.c Thu Mar 6 16:08:00 2003 @@ -904,17 +904,29 @@ break; } } - if (!cp) { + if ((!cp && configuration != 0) || (cp && configuration == 0)) { warn("selecting invalid configuration %d", configuration); 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), USB_REQ_SET_CONFIGURATION, 0, configuration, 0, NULL, 0, HZ * USB_CTRL_SET_TIMEOUT)) < 0) return ret; - + if (configuration) + dev->state = USB_STATE_CONFIGURED; + else + dev->state = USB_STATE_ADDRESS; dev->actconfig = cp; + + /* reset more hc/hcd endpoint state */ dev->toggle[0] = 0; dev->toggle[1] = 0; usb_set_maxpacket(dev); diff -Nru a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c --- a/drivers/usb/core/urb.c Thu Mar 6 16:08:00 2003 +++ b/drivers/usb/core/urb.c Thu Mar 6 16:08:00 2003 @@ -195,7 +195,9 @@ if (!urb || urb->hcpriv || !urb->complete) 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; if (!(op = dev->bus->op) || !op->submit_urb) return -ENODEV; @@ -211,6 +213,9 @@ temp = usb_pipetype (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 * stall due to queued bulk/intr transactions that complete after * we check) @@ -376,7 +381,7 @@ */ 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); else return -ENODEV; diff -Nru a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c --- a/drivers/usb/core/usb.c Thu Mar 6 16:08:00 2003 +++ b/drivers/usb/core/usb.c Thu Mar 6 16:08:00 2003 @@ -679,7 +679,7 @@ memset(dev, 0, sizeof(*dev)); device_initialize(&dev->dev); - dev->present = 1; + dev->state = USB_STATE_ATTACHED; usb_bus_get(bus); @@ -828,6 +828,11 @@ *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); /* Free up all the children before we remove this device */ @@ -855,10 +860,6 @@ } 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 */ /* it hits 0 which could very well be now */ usb_put_dev(dev); @@ -906,9 +907,17 @@ // otherwise used internally, for usb_new_device() int usb_set_address(struct usb_device *dev) { - return usb_control_msg(dev, usb_snddefctrl(dev), USB_REQ_SET_ADDRESS, - // FIXME USB_CTRL_SET_TIMEOUT - 0, dev->devnum, 0, NULL, 0, HZ * USB_CTRL_GET_TIMEOUT); + int retval; + + 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 @@ /* dma masks come from the controller; readonly, except to hcd */ 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 ... * it's fixed size except for full speed devices. @@ -1049,6 +1059,7 @@ if (err < 0) { dev_err(&dev->dev, "USB device not accepting new address=%d (error=%d)\n", dev->devnum, err); + dev->state = USB_STATE_DEFAULT; clear_bit(dev->devnum, dev->bus->devmap.devicemap); dev->devnum = -1; return 1; diff -Nru a/include/linux/usb.h b/include/linux/usb.h --- a/include/linux/usb.h Thu Mar 6 16:08:00 2003 +++ b/include/linux/usb.h Thu Mar 6 16:08:00 2003 @@ -213,7 +213,7 @@ struct usb_device { int devnum; /* Address on USB bus */ 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) */ struct usb_tt *tt; /* low/full speed dev, highspeed hub */ @@ -240,7 +240,6 @@ int have_langid; /* whether string_langid is valid yet */ int string_langid; /* language ID for strings */ - int present; /* if device is present or not */ void *hcpriv; /* Host Controller private data */ diff -Nru a/include/linux/usb_ch9.h b/include/linux/usb_ch9.h --- a/include/linux/usb_ch9.h Thu Mar 6 16:08:00 2003 +++ b/include/linux/usb_ch9.h Thu Mar 6 16:08:00 2003 @@ -291,4 +291,25 @@ 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 */