diff options
author | Brian Niebuhr <bniebuhr@efjohnson.com> | 2009-08-14 10:04:22 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-09-23 06:46:35 -0700 |
commit | 9b39e9ddedeef48569f8aac60a7b4c1fbb127c7d (patch) | |
tree | d2cc583190e18fa03298e84093c3346e1646007c /drivers/usb/gadget/u_ether.c | |
parent | 877accca79b706afe5d78b9a92cf4f22919fb2b0 (diff) |
USB: gadget: Add EEM gadget driver
This patch adds a CDC EEM ethernet gadget driver. CDC EEM is a newer
USB ethernet specification that uses a simpler interface than the older
CDC ECM. This makes CDC EEM usable by a wider set of USB hardware.
By default the ethernet gadget will still use CDC ECM/Subset, but kernel
configuration and/or a module parameter will allow alternative use of
the CDC EEM protocol.
Changes since last version:
- Brought in missing RNDIS changes that caused compile error
- Modified 'sentinel CRC' checking to match EEM host driver
Signed-off-by: Brian Niebuhr <bniebuhr@efjohnson.com>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/gadget/u_ether.c')
-rw-r--r-- | drivers/usb/gadget/u_ether.c | 85 |
1 files changed, 57 insertions, 28 deletions
diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index c6652195391..f8751ff863c 100644 --- a/drivers/usb/gadget/u_ether.c +++ b/drivers/usb/gadget/u_ether.c @@ -37,8 +37,9 @@ * one (!) network link through the USB gadget stack, normally "usb0". * * The control and data models are handled by the function driver which - * connects to this code; such as CDC Ethernet, "CDC Subset", or RNDIS. - * That includes all descriptor and endpoint management. + * connects to this code; such as CDC Ethernet (ECM or EEM), + * "CDC Subset", or RNDIS. That includes all descriptor and endpoint + * management. * * Link level addressing is handled by this component using module * parameters; if no such parameters are provided, random link level @@ -68,9 +69,13 @@ struct eth_dev { struct list_head tx_reqs, rx_reqs; atomic_t tx_qlen; + struct sk_buff_head rx_frames; + unsigned header_len; - struct sk_buff *(*wrap)(struct sk_buff *skb); - int (*unwrap)(struct sk_buff *skb); + struct sk_buff *(*wrap)(struct gether *, struct sk_buff *skb); + int (*unwrap)(struct gether *, + struct sk_buff *skb, + struct sk_buff_head *list); struct work_struct work; @@ -269,7 +274,7 @@ enomem: static void rx_complete(struct usb_ep *ep, struct usb_request *req) { - struct sk_buff *skb = req->context; + struct sk_buff *skb = req->context, *skb2; struct eth_dev *dev = ep->driver_data; int status = req->status; @@ -278,26 +283,47 @@ static void rx_complete(struct usb_ep *ep, struct usb_request *req) /* normal completion */ case 0: skb_put(skb, req->actual); - if (dev->unwrap) - status = dev->unwrap(skb); - if (status < 0 - || ETH_HLEN > skb->len - || skb->len > ETH_FRAME_LEN) { - dev->net->stats.rx_errors++; - dev->net->stats.rx_length_errors++; - DBG(dev, "rx length %d\n", skb->len); - break; - } - skb->protocol = eth_type_trans(skb, dev->net); - dev->net->stats.rx_packets++; - dev->net->stats.rx_bytes += skb->len; + if (dev->unwrap) { + unsigned long flags; - /* no buffer copies needed, unless hardware can't - * use skb buffers. - */ - status = netif_rx(skb); + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) { + status = dev->unwrap(dev->port_usb, + skb, + &dev->rx_frames); + } else { + dev_kfree_skb_any(skb); + status = -ENOTCONN; + } + spin_unlock_irqrestore(&dev->lock, flags); + } else { + skb_queue_tail(&dev->rx_frames, skb); + } skb = NULL; + + skb2 = skb_dequeue(&dev->rx_frames); + while (skb2) { + if (status < 0 + || ETH_HLEN > skb2->len + || skb2->len > ETH_FRAME_LEN) { + dev->net->stats.rx_errors++; + dev->net->stats.rx_length_errors++; + DBG(dev, "rx length %d\n", skb2->len); + dev_kfree_skb_any(skb2); + goto next_frame; + } + skb2->protocol = eth_type_trans(skb2, dev->net); + dev->net->stats.rx_packets++; + dev->net->stats.rx_bytes += skb2->len; + + /* no buffer copies needed, unless hardware can't + * use skb buffers. + */ + status = netif_rx(skb2); +next_frame: + skb2 = skb_dequeue(&dev->rx_frames); + } break; /* software-driven interface shutdown */ @@ -537,14 +563,15 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, * or there's not enough space for extra headers we need */ if (dev->wrap) { - struct sk_buff *skb_new; + unsigned long flags; - skb_new = dev->wrap(skb); - if (!skb_new) + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) + skb = dev->wrap(dev->port_usb, skb); + spin_unlock_irqrestore(&dev->lock, flags); + if (!skb) goto drop; - dev_kfree_skb_any(skb); - skb = skb_new; length = skb->len; } req->buf = skb->data; @@ -578,9 +605,9 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, } if (retval) { + dev_kfree_skb_any(skb); drop: dev->net->stats.tx_dropped++; - dev_kfree_skb_any(skb); spin_lock_irqsave(&dev->req_lock, flags); if (list_empty(&dev->tx_reqs)) netif_start_queue(net); @@ -753,6 +780,8 @@ int __init gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]) INIT_LIST_HEAD(&dev->tx_reqs); INIT_LIST_HEAD(&dev->rx_reqs); + skb_queue_head_init(&dev->rx_frames); + /* network device setup */ dev->net = net; strcpy(net->name, "usb%d"); |