blob: f31032e9ee0040cbb5b87d31670320526ae9cbf0 [file] [log] [blame]
/* drivers/usb/function/ether.c
*
* Simple Ethernet Function Device
*
* Copyright (C) 2008 Google, Inc.
* Author: Brian Swetland <swetland@google.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Implements the "cdc_subset" bulk-only protocol supported by Linux.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include "usb_function.h"
/* Ethernet frame is 1514 + FCS, but round up to 512 * 3 so we
* always queue a multiple of the USB max packet size (64 or 512)
*/
#define USB_MTU 1536
#define MAX_TX 8
#define MAX_RX 8
struct ether_context {
spinlock_t lock;
struct net_device *dev;
struct usb_endpoint *out;
struct usb_endpoint *in;
struct list_head rx_reqs;
struct list_head tx_reqs;
struct net_device_stats stats;
};
static int ether_queue_out(struct ether_context *ctxt,
struct usb_request *req);
static void ether_in_complete(struct usb_endpoint *ept,
struct usb_request *req);
static void ether_out_complete(struct usb_endpoint *ept,
struct usb_request *req);
static void ether_bind(struct usb_endpoint **ept, void *_ctxt)
{
struct ether_context *ctxt = _ctxt;
struct usb_request *req;
unsigned long flags;
int n;
ctxt->out = ept[0];
ctxt->in = ept[1];
for (n = 0; n < MAX_RX; n++) {
req = usb_ept_alloc_req(ctxt->out, 0);
if (!req)
break;
req->complete = ether_out_complete;
spin_lock_irqsave(&ctxt->lock, flags);
list_add_tail(&req->list, &ctxt->rx_reqs);
spin_unlock_irqrestore(&ctxt->lock, flags);
}
for (n = 0; n < MAX_TX; n++) {
req = usb_ept_alloc_req(ctxt->in, 0);
if (!req)
break;
req->complete = ether_in_complete;
spin_lock_irqsave(&ctxt->lock, flags);
list_add_tail(&req->list, &ctxt->tx_reqs);
spin_unlock_irqrestore(&ctxt->lock, flags);
}
}
static void ether_in_complete(struct usb_endpoint *ept,
struct usb_request *req)
{
unsigned long flags;
struct sk_buff *skb = req->context;
struct ether_context *ctxt = *((void **) skb->cb);
if (req->status == 0) {
ctxt->stats.tx_packets++;
ctxt->stats.tx_bytes += req->actual;
} else {
ctxt->stats.tx_errors++;
}
dev_kfree_skb_any(skb);
spin_lock_irqsave(&ctxt->lock, flags);
if (list_empty(&ctxt->tx_reqs))
netif_start_queue(ctxt->dev);
list_add_tail(&req->list, &ctxt->tx_reqs);
spin_unlock_irqrestore(&ctxt->lock, flags);
}
static void ether_out_complete(struct usb_endpoint *ept,
struct usb_request *req)
{
struct sk_buff *skb = req->context;
struct ether_context *ctxt = *((void **) skb->cb);
if (req->status == 0) {
skb_put(skb, req->actual);
skb->protocol = eth_type_trans(skb, ctxt->dev);
ctxt->stats.rx_packets++;
ctxt->stats.rx_bytes += req->actual;
netif_rx(skb);
} else {
dev_kfree_skb_any(skb);
ctxt->stats.rx_errors++;
}
/* don't bother requeuing if we just went offline */
if (req->status == -ENODEV) {
unsigned long flags;
spin_lock_irqsave(&ctxt->lock, flags);
list_add_tail(&req->list, &ctxt->rx_reqs);
spin_unlock_irqrestore(&ctxt->lock, flags);
} else {
if (ether_queue_out(ctxt, req))
pr_err("ether_out: cannot requeue\n");
}
}
static int ether_queue_out(struct ether_context *ctxt,
struct usb_request *req)
{
unsigned long flags;
struct sk_buff *skb;
int ret;
skb = alloc_skb(USB_MTU + NET_IP_ALIGN, GFP_ATOMIC);
if (!skb) {
pr_err("ether_queue_out: failed to alloc skb\n");
ret = -ENOMEM;
goto fail;
}
skb_reserve(skb, NET_IP_ALIGN);
*((void **) skb->cb) = ctxt;
req->buf = skb->data;
req->length = USB_MTU;
req->context = skb;
ret = usb_ept_queue_xfer(ctxt->out, req);
if (ret) {
fail:
spin_lock_irqsave(&ctxt->lock, flags);
list_add_tail(&req->list, &ctxt->rx_reqs);
spin_unlock_irqrestore(&ctxt->lock, flags);
}
return ret;
}
static void ether_configure(int configured, void *_ctxt)
{
unsigned long flags;
struct ether_context *ctxt = _ctxt;
struct usb_request *req;
pr_info("ether_configure() %d\n", configured);
if (configured) {
/* we're online -- get all rx requests queued */
for (;;) {
spin_lock_irqsave(&ctxt->lock, flags);
if (list_empty(&ctxt->rx_reqs)) {
req = 0;
} else {
req = list_first_entry(&ctxt->rx_reqs,
struct usb_request,
list);
list_del(&req->list);
}
spin_unlock_irqrestore(&ctxt->lock, flags);
if (!req)
break;
if (ether_queue_out(ctxt, req))
break;
}
} else {
/* all pending requests will be canceled */
}
}
static struct usb_function usb_func_ether = {
.bind = ether_bind,
.configure = ether_configure,
.name = "ether",
.ifc_class = 0x02,
.ifc_subclass = 0x0a,
.ifc_protocol = 0x00,
.ifc_name = "ether",
.ifc_ept_count = 2,
.ifc_ept_type = { EPT_BULK_OUT, EPT_BULK_IN },
};
static int usb_ether_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ether_context *ctxt = netdev_priv(dev);
struct usb_request *req;
unsigned long flags;
unsigned len;
spin_lock_irqsave(&ctxt->lock, flags);
if (list_empty(&ctxt->tx_reqs)) {
req = 0;
} else {
req = list_first_entry(&ctxt->tx_reqs,
struct usb_request, list);
list_del(&req->list);
if (list_empty(&ctxt->tx_reqs))
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&ctxt->lock, flags);
if (!req) {
pr_err("usb_ether_xmit: could not obtain tx request\n");
return 1;
}
/* ensure that we end with a short packet */
len = skb->len;
if (!(len & 63) || !(len & 511))
len++;
*((void **) skb->cb) = ctxt;
req->context = skb;
req->buf = skb->data;
req->length = len;
if (usb_ept_queue_xfer(ctxt->in, req)) {
spin_lock_irqsave(&ctxt->lock, flags);
list_add_tail(&req->list, &ctxt->tx_reqs);
netif_start_queue(dev);
spin_unlock_irqrestore(&ctxt->lock, flags);
dev_kfree_skb_any(skb);
ctxt->stats.tx_dropped++;
pr_err("usb_ether_xmit: could not queue tx request\n");
}
return 0;
}
static int usb_ether_open(struct net_device *dev)
{
return 0;
}
static int usb_ether_stop(struct net_device *dev)
{
return 0;
}
static struct net_device_stats *usb_ether_get_stats(struct net_device *dev)
{
struct ether_context *ctxt = netdev_priv(dev);
return &ctxt->stats;
}
static void __init usb_ether_setup(struct net_device *dev)
{
struct ether_context *ctxt = netdev_priv(dev);
pr_info("usb_ether_setup()\n");
INIT_LIST_HEAD(&ctxt->rx_reqs);
INIT_LIST_HEAD(&ctxt->tx_reqs);
spin_lock_init(&ctxt->lock);
ctxt->dev = dev;
dev->open = usb_ether_open;
dev->stop = usb_ether_stop;
dev->hard_start_xmit = usb_ether_xmit;
dev->get_stats = usb_ether_get_stats;
dev->watchdog_timeo = 20;
ether_setup(dev);
random_ether_addr(dev->dev_addr);
}
static int __init ether_init(void)
{
struct net_device *dev;
int ret;
dev = alloc_netdev(sizeof(struct ether_context),
"usb%d", usb_ether_setup);
if (!dev)
return -ENOMEM;
ret = register_netdev(dev);
if (ret) {
free_netdev(dev);
} else {
struct ether_context *ctxt = netdev_priv(dev);
usb_func_ether.context = ctxt;
usb_function_register(&usb_func_ether);
}
return ret;
}
module_init(ether_init);