ChangeSet 1.2181.4.13, 2005/03/17 17:34:24-08:00, [EMAIL PROTECTED]
[PATCH] USB: usbnet gets status polling, uses for CDC Ethernet
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]>
Signed-off-by: Greg Kroah-Hartman <[EMAIL PROTECTED]>
usbnet.c | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 186 insertions(+), 9 deletions(-)
diff -Nru a/drivers/usb/net/usbnet.c b/drivers/usb/net/usbnet.c
--- a/drivers/usb/net/usbnet.c 2005-03-30 15:06:52 -08:00
+++ b/drivers/usb/net/usbnet.c 2005-03-30 15:06:52 -08:00
@@ -185,6 +185,7 @@
// i/o info: pipes etc
unsigned in, out;
+ struct usb_host_endpoint *status;
unsigned maxpacket;
struct timer_list delay;
@@ -200,6 +201,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;
@@ -207,6 +209,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
@@ -237,6 +240,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);
@@ -312,38 +318,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,
@@ -356,9 +376,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;
@@ -1414,6 +1473,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:
@@ -1442,6 +1524,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 */
@@ -1520,6 +1647,7 @@
// .check_connect = cdc_check_connect,
.bind = cdc_bind,
.unbind = cdc_unbind,
+ .status = cdc_status,
};
#endif /* CONFIG_USB_CDCETHER */
@@ -2871,6 +2999,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
@@ -2938,6 +3102,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.
@@ -2976,6 +3142,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;
@@ -3479,6 +3654,8 @@
status = 0;
}
+ if (status == 0 && dev->status)
+ status = init_status (dev, udev);
if (status < 0)
goto out1;
-
To unsubscribe from this list: send the line "unsubscribe bk-commits-head" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at http://vger.kernel.org/majordomo-info.html