This patch applies with minor offsets against the latest USB BK, adding status/interrupt transfer support to the infrastructure and using it for CDC Ethernet for link status notifications. Please merge.
- Dave
This adds status/interrupt transfer infrastructure to "usbnet", and uses it for CDC Ethernet support. It can be used with other devices that define an interrupt-IN endpoint (quite a few!), so long as the meaning of the events is documented (erm, not so many). Signed-off-by: David Brownell <[EMAIL PROTECTED]> --- 1.123/drivers/usb/net/usbnet.c 2005-02-03 22:54:51 -08:00 +++ edited/drivers/usb/net/usbnet.c 2005-02-03 23:10:02 -08:00 @@ -184,6 +184,7 @@ // i/o info: pipes etc unsigned in, out; + struct usb_host_endpoint *status; unsigned maxpacket; struct timer_list delay; @@ -199,6 +200,7 @@ struct sk_buff_head rxq; struct sk_buff_head txq; struct sk_buff_head done; + struct urb *interrupt; struct tasklet_struct bh; struct work_struct kevent; @@ -206,6 +208,7 @@ # define EVENT_TX_HALT 0 # define EVENT_RX_HALT 1 # define EVENT_RX_MEMORY 2 +# define EVENT_STS_SPLIT 3 }; // device-specific info used by the driver @@ -234,6 +237,9 @@ /* see if peer is connected ... can sleep */ int (*check_connect)(struct usbnet *); + /* for status polling */ + void (*status)(struct usbnet *, struct urb *); + /* fixup rx packet (strip framing) */ int (*rx_fixup)(struct usbnet *dev, struct sk_buff *skb); @@ -309,38 +315,52 @@ get_endpoints (struct usbnet *dev, struct usb_interface *intf) { int tmp; - struct usb_host_interface *alt; - struct usb_host_endpoint *in, *out; + struct usb_host_interface *alt = NULL; + struct usb_host_endpoint *in = NULL, *out = NULL; + struct usb_host_endpoint *status = NULL; for (tmp = 0; tmp < intf->num_altsetting; tmp++) { unsigned ep; - in = out = NULL; + in = out = status = NULL; alt = intf->altsetting + tmp; /* take the first altsetting with in-bulk + out-bulk; + * remember any status endpoint, just in case; * ignore other endpoints and altsetttings. */ for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) { struct usb_host_endpoint *e; + int intr = 0; e = alt->endpoint + ep; - if (e->desc.bmAttributes != USB_ENDPOINT_XFER_BULK) + switch (e->desc.bmAttributes) { + case USB_ENDPOINT_XFER_INT: + if (!(e->desc.bEndpointAddress & USB_DIR_IN)) + continue; + intr = 1; + /* FALLTHROUGH */ + case USB_ENDPOINT_XFER_BULK: + break; + default: continue; + } if (e->desc.bEndpointAddress & USB_DIR_IN) { - if (!in) + if (!intr && !in) in = e; + else if (intr && !status) + status = e; } else { if (!out) out = e; } - if (in && out) - goto found; } + if (in && out) + break; } - return -EINVAL; + if (!alt || !in || !out) + return -EINVAL; -found: if (alt->desc.bAlternateSetting != 0 || !(dev->driver_info->flags & FLAG_NO_SETINT)) { tmp = usb_set_interface (dev->udev, alt->desc.bInterfaceNumber, @@ -353,9 +373,48 @@ in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); dev->out = usb_sndbulkpipe (dev->udev, out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->status = status; return 0; } +static void intr_complete (struct urb *urb, struct pt_regs *regs); + +static int init_status (struct usbnet *dev, struct usb_interface *intf) +{ + char *buf = NULL; + unsigned pipe = 0; + unsigned maxp; + unsigned period; + + if (!dev->driver_info->status) + return -ENODEV; + + pipe = usb_rcvintpipe (dev->udev, + dev->status->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + maxp = usb_maxpacket (dev->udev, pipe, 0); + + /* avoid 1 msec chatter: min 8 msec poll rate */ + period = max ((int) dev->status->desc.bInterval, + (dev->udev->speed == USB_SPEED_HIGH) ? 7 : 3); + + buf = kmalloc (maxp, SLAB_KERNEL); + if (buf) { + dev->interrupt = usb_alloc_urb (0, SLAB_KERNEL); + if (!dev->interrupt) { + kfree (buf); + return -ENOMEM; + } else { + usb_fill_int_urb(dev->interrupt, dev->udev, pipe, + buf, maxp, intr_complete, dev, period); + dev_dbg(&intf->dev, + "status ep%din, %d bytes period %d\n", + usb_pipeendpoint(pipe), maxp, period); + } + } + return 0; +} + static void skb_return (struct usbnet *dev, struct sk_buff *skb) { int status; @@ -1076,6 +1135,29 @@ usb_driver_release_interface (&usbnet_driver, info->data); return status; } + + /* status endpoint: optional for CDC Ethernet, not RNDIS (or ACM) */ + dev->status = NULL; + if (info->control->cur_altsetting->desc.bNumEndpoints == 1) { + struct usb_endpoint_descriptor *desc; + + dev->status = &info->control->cur_altsetting->endpoint [0]; + desc = &dev->status->desc; + if (desc->bmAttributes != USB_ENDPOINT_XFER_INT + || !(desc->bEndpointAddress & USB_DIR_IN) + || (le16_to_cpu(desc->wMaxPacketSize) + < sizeof (struct usb_cdc_notification)) + || !desc->bInterval) { + dev_dbg (&intf->dev, "bad notification endpoint\n"); + dev->status = NULL; + } + } + if (rndis && !dev->status) { + dev_dbg (&intf->dev, "missing RNDIS status endpoint\n"); + usb_set_intfdata(info->data, NULL); + usb_driver_release_interface (&usbnet_driver, info->data); + return -ENODEV; + } return 0; bad_desc: @@ -1104,6 +1186,51 @@ } } + +static void dumpspeed (struct usbnet *dev, __le32 *speeds) +{ + devinfo (dev, "link speeds: %u kbps up, %u kbps down", + __le32_to_cpu(speeds[0]) / 1000, + __le32_to_cpu(speeds[1]) / 1000); +} + +static void cdc_status (struct usbnet *dev, struct urb *urb) +{ + struct usb_cdc_notification *event; + + if (urb->actual_length < sizeof *event) + return; + + /* SPEED_CHANGE can get split into two 8-byte packets */ + if (test_and_clear_bit (EVENT_STS_SPLIT, &dev->flags)) { + dumpspeed (dev, (__le32 *) urb->transfer_buffer); + return; + } + + event = urb->transfer_buffer; + switch (event->bNotificationType) { + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + devdbg (dev, "CDC: carrier %s", event->wValue ? "on" : "off"); + if (event->wValue) + netif_carrier_on(dev->net); + else + netif_carrier_off(dev->net); + break; + case USB_CDC_NOTIFY_SPEED_CHANGE: /* tx/rx rates */ + devdbg (dev, "CDC: speed change (len %d)", urb->actual_length); + if (urb->actual_length != (sizeof *event + 8)) + set_bit (EVENT_STS_SPLIT, &dev->flags); + else + dumpspeed (dev, (__le32 *) &event[1]); + break; + // case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: /* RNDIS; or unsolicited */ + default: + deverr (dev, "CDC: unexpected notification %02x!", + event->bNotificationType); + break; + } +} + #endif /* NEED_GENERIC_CDC */ @@ -1182,6 +1309,7 @@ // .check_connect = cdc_check_connect, .bind = cdc_bind, .unbind = cdc_unbind, + .status = cdc_status, }; #endif /* CONFIG_USB_CDCETHER */ @@ -2527,6 +2655,42 @@ #endif /* VERBOSE */ } +static void intr_complete (struct urb *urb, struct pt_regs *regs) +{ + struct usbnet *dev = urb->context; + int status = urb->status; + + switch (status) { + /* success */ + case 0: + dev->driver_info->status(dev, urb); + break; + + /* software-driven interface shutdown */ + case -ENOENT: // urb killed + case -ESHUTDOWN: // hardware gone +#ifdef VERBOSE + devdbg (dev, "intr shutdown, code %d", status); +#endif + return; + + /* NOTE: not throttling like RX/TX, since this endpoint + * already polls infrequently + */ + default: + devdbg (dev, "intr status %d", status); + break; + } + + if (!netif_running (dev->net)) + return; + + memset(urb->transfer_buffer, 0, urb->transfer_buffer_length); + status = usb_submit_urb (urb, GFP_ATOMIC); + if (status != 0) + deverr(dev, "intr resubmit --> %d", status); +} + /*-------------------------------------------------------------------------*/ // unlink pending rx/tx; completion handlers do all other cleanup @@ -2594,6 +2758,8 @@ dev->wait = NULL; remove_wait_queue (&unlink_wakeup, &wait); + usb_kill_urb(dev->interrupt); + /* deferred work (task, timer, softirq) must also stop. * can't flush_scheduled_work() until we drop rtnl (later), * else workers could deadlock; so make workers a NOP. @@ -2632,6 +2798,15 @@ goto done; } + /* start any status interrupt transfer */ + if (dev->interrupt) { + retval = usb_submit_urb (dev->interrupt, GFP_KERNEL); + if (retval < 0) { + deverr (dev, "intr submit %d", retval); + goto done; + } + } + netif_start_queue (net); if (dev->msg_level >= 2) { char *framing; @@ -3133,6 +3308,8 @@ status = 0; } + if (status == 0 && dev->status) + status = init_status (dev, udev); if (status < 0) goto out1;