blob: 5443ea72954d9032e855b801dd3ffc0190e4b6c7 [file] [log] [blame]
/*
* Gadget Driver for Motorola USBNet
*
* Copyright (C) 2009 Motorola, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/device.h>
#include <linux/fcntl.h>
#include <linux/spinlock.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/platform_device.h>
#include <linux/skbuff.h>
#include <linux/if.h>
#include <linux/inetdevice.h>
#include <linux/uaccess.h>
#include <linux/workqueue.h>
#include <linux/usb/ch9.h>
#include <asm/cacheflush.h>
#include <linux/switch.h>
/*
* Macro Defines
*/
#define EP0_BUFSIZE 256
/* Vendor Request to config IP */
#define USBNET_SET_IP_ADDRESS 0x05
#define USBNET_SET_SUBNET_MASK 0x06
#define USBNET_SET_HOST_IP 0x07
/* Linux Network Interface */
#define USB_MTU 1536
#define MAX_BULK_TX_REQ_NUM 8
#define MAX_BULK_RX_REQ_NUM 8
#define MAX_INTR_RX_REQ_NUM 8
#define STRING_INTERFACE 0
struct usbnet_context {
spinlock_t lock; /* For RX/TX list */
struct net_device *dev;
struct usb_gadget *gadget;
struct usb_ep *bulk_in;
struct usb_ep *bulk_out;
struct usb_ep *intr_out;
u16 config; /* current USB config w_value */
struct list_head rx_reqs;
struct list_head tx_reqs;
struct net_device_stats stats;
struct work_struct usbnet_config_wq;
u32 ip_addr;
u32 subnet_mask;
u32 router_ip;
u32 iff_flag;
};
struct usbnet_device {
struct usb_function function;
struct usb_composite_dev *cdev;
struct usbnet_context *net_ctxt;
};
/* static strings, in UTF-8 */
static struct usb_string usbnet_string_defs[] = {
[STRING_INTERFACE].s = "Motorola Test Command",
{ /* ZEROES END LIST */ },
};
static struct usb_gadget_strings usbnet_string_table = {
.language = 0x0409, /* en-us */
.strings = usbnet_string_defs,
};
static struct usb_gadget_strings *usbnet_strings[] = {
&usbnet_string_table,
NULL,
};
/* There is only one interface. */
static struct usb_interface_descriptor usbnet_intf_desc = {
.bLength = sizeof usbnet_intf_desc,
.bDescriptorType = USB_DT_INTERFACE,
.bNumEndpoints = 3,
.bInterfaceClass = 0x02,
.bInterfaceSubClass = 0x0a,
.bInterfaceProtocol = 0x01,
};
static struct usb_endpoint_descriptor usbnet_fs_bulk_in_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
static struct usb_endpoint_descriptor usbnet_fs_bulk_out_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
};
static struct usb_endpoint_descriptor intr_out_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_INT,
.wMaxPacketSize = __constant_cpu_to_le16(64),
.bInterval = 1,
};
static struct usb_descriptor_header *fs_function[] = {
(struct usb_descriptor_header *) &usbnet_intf_desc,
(struct usb_descriptor_header *) &usbnet_fs_bulk_in_desc,
(struct usb_descriptor_header *) &usbnet_fs_bulk_out_desc,
(struct usb_descriptor_header *) &intr_out_desc,
NULL,
};
static struct usb_endpoint_descriptor usbnet_hs_bulk_in_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_IN,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = __constant_cpu_to_le16(512),
.bInterval = 0,
};
static struct usb_endpoint_descriptor usbnet_hs_bulk_out_desc = {
.bLength = USB_DT_ENDPOINT_SIZE,
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = USB_DIR_OUT,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = __constant_cpu_to_le16(512),
.bInterval = 0,
};
static struct usb_descriptor_header *hs_function[] = {
(struct usb_descriptor_header *) &usbnet_intf_desc,
(struct usb_descriptor_header *) &usbnet_hs_bulk_in_desc,
(struct usb_descriptor_header *) &usbnet_hs_bulk_out_desc,
(struct usb_descriptor_header *) &intr_out_desc,
NULL,
};
#ifdef CONFIG_SWITCH
static struct switch_dev usbnet_enable_device = {
.name = "usbnet_enable",
};
#endif
#define DO_NOT_STOP_QUEUE 0
#define STOP_QUEUE 1
#define USBNETDBG(context, fmt, args...) \
if (context && context->gadget) \
dev_dbg(&(context->gadget->dev) , fmt , ## args)
static const char *usb_description = "Motorola BLAN Interface";
static ssize_t usbnet_desc_show(struct device *dev,
struct device_attribute *attr, char *buff)
{
ssize_t status = 0;
status = sprintf(buff, "%s\n", usb_description);
return status;
}
static DEVICE_ATTR(description, S_IRUGO, usbnet_desc_show, NULL);
static inline struct usbnet_device *usbnet_func_to_dev(struct usb_function *f)
{
return container_of(f, struct usbnet_device, function);
}
static int ether_queue_out(struct usb_request *req ,
struct usbnet_context *context)
{
unsigned long flags;
struct sk_buff *skb;
int ret;
skb = alloc_skb(USB_MTU + NET_IP_ALIGN, GFP_ATOMIC);
if (!skb) {
USBNETDBG(context, "%s: failed to alloc skb\n", __func__);
ret = -ENOMEM;
goto fail;
}
skb_reserve(skb, NET_IP_ALIGN);
req->buf = skb->data;
req->length = USB_MTU;
req->context = skb;
ret = usb_ep_queue(context->bulk_out, req, GFP_KERNEL);
if (ret == 0)
return 0;
else
kfree_skb(skb);
fail:
spin_lock_irqsave(&context->lock, flags);
list_add_tail(&req->list, &context->rx_reqs);
spin_unlock_irqrestore(&context->lock, flags);
return ret;
}
struct usb_request *usb_get_recv_request(struct usbnet_context *context)
{
unsigned long flags;
struct usb_request *req;
spin_lock_irqsave(&context->lock, flags);
if (list_empty(&context->rx_reqs)) {
req = NULL;
} else {
req = list_first_entry(&context->rx_reqs,
struct usb_request, list);
list_del(&req->list);
}
spin_unlock_irqrestore(&context->lock, flags);
return req;
}
struct usb_request *usb_get_xmit_request(int stop_flag, struct net_device *dev)
{
struct usbnet_context *context = netdev_priv(dev);
unsigned long flags;
struct usb_request *req;
spin_lock_irqsave(&context->lock, flags);
if (list_empty(&context->tx_reqs)) {
req = NULL;
} else {
req = list_first_entry(&context->tx_reqs,
struct usb_request, list);
list_del(&req->list);
if (stop_flag == STOP_QUEUE &&
list_empty(&context->tx_reqs))
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&context->lock, flags);
return req;
}
static int usb_ether_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct usbnet_context *context = netdev_priv(dev);
struct usb_request *req;
unsigned long flags;
unsigned len;
int rc;
req = usb_get_xmit_request(STOP_QUEUE, dev);
if (!req) {
USBNETDBG(context, "%s: could not obtain tx request\n",
__func__);
return 1;
}
/* Add 4 bytes CRC */
skb->len += 4;
/* ensure that we end with a short packet */
len = skb->len;
if (!(len & 63) || !(len & 511))
len++;
req->context = skb;
req->buf = skb->data;
req->length = len;
rc = usb_ep_queue(context->bulk_in, req, GFP_KERNEL);
if (rc != 0) {
spin_lock_irqsave(&context->lock, flags);
list_add_tail(&req->list, &context->tx_reqs);
spin_unlock_irqrestore(&context->lock, flags);
dev_kfree_skb_any(skb);
context->stats.tx_dropped++;
USBNETDBG(context,
"%s: could not queue tx request\n", __func__);
}
return 0;
}
static int usb_ether_open(struct net_device *dev)
{
struct usbnet_context *context = netdev_priv(dev);
USBNETDBG(context, "%s\n", __func__);
return 0;
}
static int usb_ether_stop(struct net_device *dev)
{
struct usbnet_context *context = netdev_priv(dev);
USBNETDBG(context, "%s\n", __func__);
return 0;
}
static struct net_device_stats *usb_ether_get_stats(struct net_device *dev)
{
struct usbnet_context *context = netdev_priv(dev);
USBNETDBG(context, "%s\n", __func__);
return &context->stats;
}
static void usbnet_if_config(struct work_struct *work)
{
struct ifreq ifr;
mm_segment_t saved_fs;
unsigned err;
struct sockaddr_in *sin;
struct usbnet_context *context = container_of(work,
struct usbnet_context, usbnet_config_wq);
pr_info("%s : Configuring with config = %d, ip_addr = 0x%08x,"
" subnet = 0x%08x, router_ip = 0x%08x, flags = 0x%08x\n",
__func__, context->config, context->ip_addr, context->subnet_mask,
context->router_ip, context->iff_flag);
memset(&ifr, 0, sizeof(ifr));
sin = (void *) &(ifr.ifr_ifru.ifru_addr);
strncpy(ifr.ifr_ifrn.ifrn_name, context->dev->name,
sizeof(ifr.ifr_ifrn.ifrn_name));
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = context->ip_addr;
saved_fs = get_fs();
set_fs(get_ds());
err = devinet_ioctl(dev_net(context->dev), SIOCSIFADDR, &ifr);
if (err)
USBNETDBG(context, "%s: Error in SIOCSIFADDR\n", __func__);
sin->sin_addr.s_addr = context->subnet_mask;
err = devinet_ioctl(dev_net(context->dev), SIOCSIFNETMASK, &ifr);
if (err)
USBNETDBG(context, "%s: Error in SIOCSIFNETMASK\n", __func__);
sin->sin_addr.s_addr = context->ip_addr | ~(context->subnet_mask);
err = devinet_ioctl(dev_net(context->dev), SIOCSIFBRDADDR, &ifr);
if (err)
USBNETDBG(context, "%s: Error in SIOCSIFBRDADDR\n", __func__);
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_ifrn.ifrn_name, context->dev->name,
sizeof(ifr.ifr_ifrn.ifrn_name));
ifr.ifr_flags = context->dev->flags & ~IFF_UP;
ifr.ifr_flags |= context->iff_flag;
err = devinet_ioctl(dev_net(context->dev), SIOCSIFFLAGS, &ifr);
if (err)
USBNETDBG(context, "%s: Error in SIOCSIFFLAGS\n", __func__);
set_fs(saved_fs);
#ifdef CONFIG_SWITCH
switch_set_state(&usbnet_enable_device, context->config);
#endif
}
static const struct net_device_ops usbnet_eth_netdev_ops = {
.ndo_open = usb_ether_open,
.ndo_stop = usb_ether_stop,
.ndo_start_xmit = usb_ether_xmit,
.ndo_get_stats = usb_ether_get_stats,
};
static void usb_ether_setup(struct net_device *dev)
{
struct usbnet_context *context = netdev_priv(dev);
INIT_LIST_HEAD(&context->rx_reqs);
INIT_LIST_HEAD(&context->tx_reqs);
spin_lock_init(&context->lock);
context->dev = dev;
dev->netdev_ops = &usbnet_eth_netdev_ops;
dev->watchdog_timeo = 20;
ether_setup(dev);
random_ether_addr(dev->dev_addr);
}
/*-------------------------------------------------------------------------*/
static void usbnet_cleanup(struct usbnet_device *dev)
{
struct usbnet_context *context = dev->net_ctxt;
if (context) {
device_remove_file(&(context->dev->dev), &dev_attr_description);
unregister_netdev(context->dev);
free_netdev(context->dev);
dev->net_ctxt = NULL;
}
}
static void usbnet_unbind(struct usb_configuration *c, struct usb_function *f)
{
struct usbnet_device *dev = usbnet_func_to_dev(f);
struct usb_composite_dev *cdev = c->cdev;
struct usbnet_context *context = dev->net_ctxt;
struct usb_request *req;
dev->cdev = cdev;
usb_ep_disable(context->bulk_in);
usb_ep_disable(context->bulk_out);
/* Free BULK OUT Requests */
while ((req = usb_get_recv_request(context)))
usb_ep_free_request(context->bulk_out, req);
/* Free BULK IN Requests */
while ((req = usb_get_xmit_request(DO_NOT_STOP_QUEUE,
context->dev))) {
usb_ep_free_request(context->bulk_in, req);
}
context->config = 0;
}
static void ether_out_complete(struct usb_ep *ep, struct usb_request *req)
{
struct sk_buff *skb = req->context;
struct usbnet_context *context = ep->driver_data;
if (req->status == 0) {
skb_put(skb, req->actual);
skb->protocol = eth_type_trans(skb, context->dev);
context->stats.rx_packets++;
context->stats.rx_bytes += req->actual;
netif_rx(skb);
} else {
dev_kfree_skb_any(skb);
context->stats.rx_errors++;
}
/* don't bother requeuing if we just went offline */
if ((req->status == -ENODEV) || (req->status == -ESHUTDOWN)) {
unsigned long flags;
spin_lock_irqsave(&context->lock, flags);
list_add_tail(&req->list, &context->rx_reqs);
spin_unlock_irqrestore(&context->lock, flags);
} else {
if (ether_queue_out(req, context))
USBNETDBG(context, "ether_out: cannot requeue\n");
}
}
static void ether_in_complete(struct usb_ep *ep, struct usb_request *req)
{
unsigned long flags;
struct sk_buff *skb = req->context;
struct usbnet_context *context = ep->driver_data;
if (req->status == 0) {
context->stats.tx_packets++;
context->stats.tx_bytes += req->actual;
} else {
context->stats.tx_errors++;
}
dev_kfree_skb_any(skb);
spin_lock_irqsave(&context->lock, flags);
if (list_empty(&context->tx_reqs))
netif_start_queue(context->dev);
list_add_tail(&req->list, &context->tx_reqs);
spin_unlock_irqrestore(&context->lock, flags);
}
static int usbnet_bind(struct usb_configuration *c,
struct usb_function *f)
{
struct usb_composite_dev *cdev = c->cdev;
struct usbnet_device *dev = usbnet_func_to_dev(f);
struct usbnet_context *context = dev->net_ctxt;
int n, rc, id;
struct usb_ep *ep;
struct usb_request *req;
unsigned long flags;
dev->cdev = cdev;
id = usb_interface_id(c, f);
if (id < 0)
return id;
usbnet_intf_desc.bInterfaceNumber = id;
context->gadget = cdev->gadget;
/* Find all the endpoints we will use */
ep = usb_ep_autoconfig(cdev->gadget, &usbnet_fs_bulk_in_desc);
if (!ep) {
USBNETDBG(context, "%s auto-configure usbnet_hs_bulk_in_desc error\n",
__func__);
goto autoconf_fail;
}
ep->driver_data = context;
context->bulk_in = ep;
ep = usb_ep_autoconfig(cdev->gadget, &usbnet_fs_bulk_out_desc);
if (!ep) {
USBNETDBG(context, "%s auto-configure usbnet_hs_bulk_out_desc error\n",
__func__);
goto autoconf_fail;
}
ep->driver_data = context;
context->bulk_out = ep;
ep = usb_ep_autoconfig(cdev->gadget, &intr_out_desc);
if (!ep) {
USBNETDBG(context, "%s auto-configure intr_out_desc error\n",
__func__);
goto autoconf_fail;
}
ep->driver_data = context;
context->intr_out = ep;
if (gadget_is_dualspeed(cdev->gadget)) {
/* Assume endpoint addresses are the same for both speeds */
usbnet_hs_bulk_in_desc.bEndpointAddress =
usbnet_fs_bulk_in_desc.bEndpointAddress;
usbnet_hs_bulk_out_desc.bEndpointAddress =
usbnet_fs_bulk_out_desc.bEndpointAddress;
}
rc = -ENOMEM;
for (n = 0; n < MAX_BULK_RX_REQ_NUM; n++) {
req = usb_ep_alloc_request(context->bulk_out,
GFP_KERNEL);
if (!req) {
USBNETDBG(context, "%s: alloc request bulk_out fail\n",
__func__);
break;
}
req->complete = ether_out_complete;
spin_lock_irqsave(&context->lock, flags);
list_add_tail(&req->list, &context->rx_reqs);
spin_unlock_irqrestore(&context->lock, flags);
}
for (n = 0; n < MAX_BULK_TX_REQ_NUM; n++) {
req = usb_ep_alloc_request(context->bulk_in,
GFP_KERNEL);
if (!req) {
USBNETDBG(context, "%s: alloc request bulk_in fail\n",
__func__);
break;
}
req->complete = ether_in_complete;
spin_lock_irqsave(&context->lock, flags);
list_add_tail(&req->list, &context->tx_reqs);
spin_unlock_irqrestore(&context->lock, flags);
}
return 0;
autoconf_fail:
rc = -ENOTSUPP;
usbnet_unbind(c, f);
return rc;
}
static void do_set_config(struct usb_function *f, u16 new_config)
{
struct usbnet_device *dev = usbnet_func_to_dev(f);
struct usbnet_context *context = dev->net_ctxt;
struct usb_composite_dev *cdev = f->config->cdev;
int result = 0;
struct usb_request *req;
if (context->config == new_config) /* Config did not change */
return;
context->config = new_config;
if (new_config == 1) { /* Enable End points */
result = config_ep_by_speed(cdev->gadget, f, context->bulk_in);
if (result) {
context->bulk_in->desc = NULL;
USBNETDBG(context, "config_ep_by_speed failes for ep %s, result %d\n",
context->bulk_in->name, result);
return;
}
result = usb_ep_enable(context->bulk_in);
if (result != 0) {
USBNETDBG(context,
"%s: failed to enable BULK_IN EP ret=%d\n",
__func__, result);
}
context->bulk_in->driver_data = context;
result = config_ep_by_speed(cdev->gadget, f, context->bulk_out);
if (result) {
context->bulk_out->desc = NULL;
USBNETDBG(context, "config_ep_by_speed failes for ep %s, result %d\n",
context->bulk_out->name, result);
usb_ep_disable(context->bulk_in);
return;
}
result = usb_ep_enable(context->bulk_out);
if (result != 0) {
USBNETDBG(context,
"%s: failed to enable BULK_OUT EP ret = %d\n",
__func__, result);
}
context->bulk_out->driver_data = context;
context->intr_out->desc = &intr_out_desc;
result = usb_ep_enable(context->intr_out);
if (result != 0) {
USBNETDBG(context,
"%s: failed to enable INTR_OUT EP ret = %d\n",
__func__, result);
}
context->intr_out->driver_data = context;
/* we're online -- get all rx requests queued */
while ((req = usb_get_recv_request(context))) {
if (ether_queue_out(req, context)) {
USBNETDBG(context,
"%s: ether_queue_out failed\n",
__func__);
break;
}
}
} else {/* Disable Endpoints */
if (context->bulk_in)
usb_ep_disable(context->bulk_in);
if (context->bulk_out)
usb_ep_disable(context->bulk_out);
context->ip_addr = 0;
context->subnet_mask = 0;
context->router_ip = 0;
context->iff_flag = 0;
queue_work(system_nrt_wq, &context->usbnet_config_wq);
}
}
static int usbnet_set_alt(struct usb_function *f,
unsigned intf, unsigned alt)
{
struct usbnet_device *dev = usbnet_func_to_dev(f);
struct usbnet_context *context = dev->net_ctxt;
USBNETDBG(context, "usbnet_set_alt intf: %d alt: %d\n", intf, alt);
do_set_config(f, 1);
return 0;
}
static int usbnet_ctrlrequest(struct usbnet_device *dev,
struct usb_composite_dev *cdev,
const struct usb_ctrlrequest *ctrl)
{
struct usbnet_context *context = dev->net_ctxt;
int rc = -EOPNOTSUPP;
int wIndex = le16_to_cpu(ctrl->wIndex);
int wValue = le16_to_cpu(ctrl->wValue);
int wLength = le16_to_cpu(ctrl->wLength);
struct usb_request *req = cdev->req;
if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) {
switch (ctrl->bRequest) {
case USBNET_SET_IP_ADDRESS:
context->ip_addr = (wValue << 16) | wIndex;
rc = 0;
break;
case USBNET_SET_SUBNET_MASK:
context->subnet_mask = (wValue << 16) | wIndex;
rc = 0;
break;
case USBNET_SET_HOST_IP:
context->router_ip = (wValue << 16) | wIndex;
rc = 0;
break;
default:
break;
}
if (context->ip_addr && context->subnet_mask
&& context->router_ip) {
context->iff_flag = IFF_UP;
/* schedule a work queue to do this because we
need to be able to sleep */
queue_work(system_nrt_wq, &context->usbnet_config_wq);
}
}
/* respond with data transfer or status phase? */
if (rc >= 0) {
req->zero = rc < wLength;
req->length = rc;
rc = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
if (rc < 0)
USBNETDBG(context, "usbnet setup response error\n");
}
return rc;
}
static void usbnet_disable(struct usb_function *f)
{
struct usbnet_device *dev = usbnet_func_to_dev(f);
struct usbnet_context *context = dev->net_ctxt;
USBNETDBG(context, "%s\n", __func__);
do_set_config(f, 0);
}
static void usbnet_suspend(struct usb_function *f)
{
struct usbnet_device *dev = usbnet_func_to_dev(f);
struct usbnet_context *context = dev->net_ctxt;
USBNETDBG(context, "%s\n", __func__);
}
static void usbnet_resume(struct usb_function *f)
{
struct usbnet_device *dev = usbnet_func_to_dev(f);
struct usbnet_context *context = dev->net_ctxt;
USBNETDBG(context, "%s\n", __func__);
}
int usbnet_bind_config(struct usbnet_device *dev, struct usb_configuration *c)
{
int ret, status;
pr_debug("usbnet_bind_config\n");
if (usbnet_string_defs[STRING_INTERFACE].id == 0) {
status = usb_string_id(c->cdev);
if (status < 0) {
pr_err("%s: failed to get string id, err:%d\n",
__func__, status);
return status;
}
usbnet_string_defs[STRING_INTERFACE].id = status;
usbnet_intf_desc.iInterface = status;
}
dev->cdev = c->cdev;
dev->function.name = "usbnet";
dev->function.fs_descriptors = fs_function;
dev->function.hs_descriptors = hs_function;
dev->function.bind = usbnet_bind;
dev->function.unbind = usbnet_unbind;
dev->function.set_alt = usbnet_set_alt;
dev->function.disable = usbnet_disable;
dev->function.suspend = usbnet_suspend;
dev->function.resume = usbnet_resume;
dev->function.strings = usbnet_strings;
ret = usb_add_function(c, &dev->function);
return ret;
}