📄 dummy_hcd.c
字号:
/* * dummy_hcd.c -- Dummy/Loopback USB host and device emulator driver. * * Maintainer: Alan Stern <stern@rowland.harvard.edu> * * Copyright (C) 2003 David Brownell * Copyright (C) 2003, 2004 Alan Stern * * 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 *//* * This exposes a device side "USB gadget" API, driven by requests to a * Linux-USB host controller driver. USB traffic is simulated; there's * no need for USB hardware. Use this with two other drivers: * * - Gadget driver, responding to requests (slave); * - Host-side device driver, as already familiar in Linux. * * Having this all in one kernel can help some stages of development, * bypassing some hardware (and driver) issues. UML could help too. */#define DEBUG#include <linux/config.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/ioport.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/smp_lock.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/timer.h>#include <linux/list.h>#include <linux/interrupt.h>#include <linux/version.h>#include <linux/usb.h>#include <linux/usb_gadget.h>#include <asm/byteorder.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/unaligned.h>#include "../core/hcd.h"#define DRIVER_DESC "USB Host+Gadget Emulator"#define DRIVER_VERSION "17 Dec 2004"static const char driver_name [] = "dummy_hcd";static const char driver_desc [] = "USB Host+Gadget Emulator";static const char gadget_name [] = "dummy_udc";MODULE_DESCRIPTION (DRIVER_DESC);MODULE_AUTHOR ("David Brownell");MODULE_LICENSE ("GPL");/*-------------------------------------------------------------------------*//* gadget side driver data structres */struct dummy_ep { struct list_head queue; unsigned long last_io; /* jiffies timestamp */ struct usb_gadget *gadget; const struct usb_endpoint_descriptor *desc; struct usb_ep ep; unsigned halted : 1; unsigned already_seen : 1; unsigned setup_stage : 1;};struct dummy_request { struct list_head queue; /* ep's requests */ struct usb_request req;};static inline struct dummy_ep *usb_ep_to_dummy_ep (struct usb_ep *_ep){ return container_of (_ep, struct dummy_ep, ep);}static inline struct dummy_request *usb_request_to_dummy_request (struct usb_request *_req){ return container_of (_req, struct dummy_request, req);}/*-------------------------------------------------------------------------*//* * Every device has ep0 for control requests, plus up to 30 more endpoints, * in one of two types: * * - Configurable: direction (in/out), type (bulk, iso, etc), and endpoint * number can be changed. Names like "ep-a" are used for this type. * * - Fixed Function: in other cases. some characteristics may be mutable; * that'd be hardware-specific. Names like "ep12out-bulk" are used. * * Gadget drivers are responsible for not setting up conflicting endpoint * configurations, illegal or unsupported packet lengths, and so on. */static const char ep0name [] = "ep0";static const char *const ep_name [] = { ep0name, /* everyone has ep0 */ /* act like a net2280: high speed, six configurable endpoints */ "ep-a", "ep-b", "ep-c", "ep-d", "ep-e", "ep-f", /* or like pxa250: fifteen fixed function endpoints */ "ep1in-bulk", "ep2out-bulk", "ep3in-iso", "ep4out-iso", "ep5in-int", "ep6in-bulk", "ep7out-bulk", "ep8in-iso", "ep9out-iso", "ep10in-int", "ep11in-bulk", "ep12out-bulk", "ep13in-iso", "ep14out-iso", "ep15in-int", /* or like sa1100: two fixed function endpoints */ "ep1out-bulk", "ep2in-bulk",};#define DUMMY_ENDPOINTS (sizeof(ep_name)/sizeof(char *))#define FIFO_SIZE 64struct urbp { struct urb *urb; struct list_head urbp_list;};struct dummy { spinlock_t lock; /* * SLAVE/GADGET side support */ struct dummy_ep ep [DUMMY_ENDPOINTS]; int address; struct usb_gadget gadget; struct usb_gadget_driver *driver; struct dummy_request fifo_req; u8 fifo_buf [FIFO_SIZE]; u16 devstatus; /* * MASTER/HOST side support */ struct timer_list timer; u32 port_status; unsigned started:1; unsigned resuming:1; unsigned long re_timeout; struct usb_device *udev; struct list_head urbp_list;};static inline struct dummy *hcd_to_dummy (struct usb_hcd *hcd){ return (struct dummy *) (hcd->hcd_priv);}static inline struct usb_hcd *dummy_to_hcd (struct dummy *dum){ return container_of((void *) dum, struct usb_hcd, hcd_priv);}static inline struct device *dummy_dev (struct dummy *dum){ return dummy_to_hcd(dum)->self.controller;}static inline struct dummy *ep_to_dummy (struct dummy_ep *ep){ return container_of (ep->gadget, struct dummy, gadget);}static inline struct dummy *gadget_to_dummy (struct usb_gadget *gadget){ return container_of (gadget, struct dummy, gadget);}static inline struct dummy *gadget_dev_to_dummy (struct device *dev){ return container_of (dev, struct dummy, gadget.dev);}static struct dummy *the_controller;/*-------------------------------------------------------------------------*//* * This "hardware" may look a bit odd in diagnostics since it's got both * host and device sides; and it binds different drivers to each side. */static struct platform_device the_pdev;static struct device_driver dummy_driver = { .name = (char *) driver_name, .bus = &platform_bus_type,};/*-------------------------------------------------------------------------*//* SLAVE/GADGET SIDE DRIVER * * This only tracks gadget state. All the work is done when the host * side tries some (emulated) i/o operation. Real device controller * drivers would do real i/o using dma, fifos, irqs, timers, etc. */#define is_enabled(dum) \ (dum->port_status & USB_PORT_STAT_ENABLE)static intdummy_enable (struct usb_ep *_ep, const struct usb_endpoint_descriptor *desc){ struct dummy *dum; struct dummy_ep *ep; unsigned max; int retval; ep = usb_ep_to_dummy_ep (_ep); if (!_ep || !desc || ep->desc || _ep->name == ep0name || desc->bDescriptorType != USB_DT_ENDPOINT) return -EINVAL; dum = ep_to_dummy (ep); if (!dum->driver || !is_enabled (dum)) return -ESHUTDOWN; max = le16_to_cpu(desc->wMaxPacketSize) & 0x3ff; /* drivers must not request bad settings, since lower levels * (hardware or its drivers) may not check. some endpoints * can't do iso, many have maxpacket limitations, etc. * * since this "hardware" driver is here to help debugging, we * have some extra sanity checks. (there could be more though, * especially for "ep9out" style fixed function ones.) */ retval = -EINVAL; switch (desc->bmAttributes & 0x03) { case USB_ENDPOINT_XFER_BULK: if (strstr (ep->ep.name, "-iso") || strstr (ep->ep.name, "-int")) { goto done; } switch (dum->gadget.speed) { case USB_SPEED_HIGH: if (max == 512) break; /* conserve return statements */ default: switch (max) { case 8: case 16: case 32: case 64: /* we'll fake any legal size */ break; default: case USB_SPEED_LOW: goto done; } } break; case USB_ENDPOINT_XFER_INT: if (strstr (ep->ep.name, "-iso")) /* bulk is ok */ goto done; /* real hardware might not handle all packet sizes */ switch (dum->gadget.speed) { case USB_SPEED_HIGH: if (max <= 1024) break; /* save a return statement */ case USB_SPEED_FULL: if (max <= 64) break; /* save a return statement */ default: if (max <= 8) break; goto done; } break; case USB_ENDPOINT_XFER_ISOC: if (strstr (ep->ep.name, "-bulk") || strstr (ep->ep.name, "-int")) goto done; /* real hardware might not handle all packet sizes */ switch (dum->gadget.speed) { case USB_SPEED_HIGH: if (max <= 1024) break; /* save a return statement */ case USB_SPEED_FULL: if (max <= 1023) break; /* save a return statement */ default: goto done; } break; default: /* few chips support control except on ep0 */ goto done; } _ep->maxpacket = max; ep->desc = desc; dev_dbg (dummy_dev(dum), "enabled %s (ep%d%s-%s) maxpacket %d\n", _ep->name, desc->bEndpointAddress & 0x0f, (desc->bEndpointAddress & USB_DIR_IN) ? "in" : "out", ({ char *val; switch (desc->bmAttributes & 0x03) { case USB_ENDPOINT_XFER_BULK: val = "bulk"; break; case USB_ENDPOINT_XFER_ISOC: val = "iso"; break; case USB_ENDPOINT_XFER_INT: val = "intr"; break; default: val = "ctrl"; break; }; val; }), max); /* at this point real hardware should be NAKing transfers * to that endpoint, until a buffer is queued to it. */ retval = 0;done: return retval;}/* called with spinlock held */static void nuke (struct dummy *dum, struct dummy_ep *ep){ while (!list_empty (&ep->queue)) { struct dummy_request *req; req = list_entry (ep->queue.next, struct dummy_request, queue); list_del_init (&req->queue); req->req.status = -ESHUTDOWN; spin_unlock (&dum->lock); req->req.complete (&ep->ep, &req->req); spin_lock (&dum->lock); }}static int dummy_disable (struct usb_ep *_ep){ struct dummy_ep *ep; struct dummy *dum; unsigned long flags; int retval; ep = usb_ep_to_dummy_ep (_ep); if (!_ep || !ep->desc || _ep->name == ep0name) return -EINVAL; dum = ep_to_dummy (ep); spin_lock_irqsave (&dum->lock, flags); ep->desc = NULL; retval = 0; nuke (dum, ep); spin_unlock_irqrestore (&dum->lock, flags); dev_dbg (dummy_dev(dum), "disabled %s\n", _ep->name); return retval;}static struct usb_request *dummy_alloc_request (struct usb_ep *_ep, int mem_flags){ struct dummy_ep *ep; struct dummy_request *req; if (!_ep) return NULL; ep = usb_ep_to_dummy_ep (_ep); req = kmalloc (sizeof *req, mem_flags); if (!req) return NULL; memset (req, 0, sizeof *req); INIT_LIST_HEAD (&req->queue); return &req->req;}static voiddummy_free_request (struct usb_ep *_ep, struct usb_request *_req){ struct dummy_ep *ep; struct dummy_request *req; ep = usb_ep_to_dummy_ep (_ep); if (!ep || !_req || (!ep->desc && _ep->name != ep0name)) return; req = usb_request_to_dummy_request (_req); WARN_ON (!list_empty (&req->queue)); kfree (req);}static void *dummy_alloc_buffer ( struct usb_ep *_ep, unsigned bytes, dma_addr_t *dma, int mem_flags) { char *retval; struct dummy_ep *ep; struct dummy *dum; ep = usb_ep_to_dummy_ep (_ep); dum = ep_to_dummy (ep); if (!dum->driver) return NULL; retval = kmalloc (bytes, mem_flags); *dma = (dma_addr_t) retval; return retval;}static voiddummy_free_buffer ( struct usb_ep *_ep, void *buf, dma_addr_t dma, unsigned bytes) { if (bytes) kfree (buf);}static voidfifo_complete (struct usb_ep *ep, struct usb_request *req){}static intdummy_queue (struct usb_ep *_ep, struct usb_request *_req, int mem_flags){ struct dummy_ep *ep; struct dummy_request *req; struct dummy *dum; unsigned long flags; req = usb_request_to_dummy_request (_req); if (!_req || !list_empty (&req->queue) || !_req->complete) return -EINVAL; ep = usb_ep_to_dummy_ep (_ep); if (!_ep || (!ep->desc && _ep->name != ep0name)) return -EINVAL; dum = ep_to_dummy (ep); if (!dum->driver || !is_enabled (dum)) return -ESHUTDOWN;#if 0 dev_dbg (dummy_dev(dum), "ep %p queue req %p to %s, len %d buf %p\n", ep, _req, _ep->name, _req->length, _req->buf);#endif _req->status = -EINPROGRESS; _req->actual = 0; spin_lock_irqsave (&dum->lock, flags); /* implement an emulated single-request FIFO */ if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && list_empty (&dum->fifo_req.queue) && list_empty (&ep->queue) && _req->length <= FIFO_SIZE) { req = &dum->fifo_req; req->req = *_req; req->req.buf = dum->fifo_buf; memcpy (dum->fifo_buf, _req->buf, _req->length); req->req.context = dum; req->req.complete = fifo_complete; spin_unlock (&dum->lock); _req->actual = _req->length; _req->status = 0; _req->complete (_ep, _req); spin_lock (&dum->lock); } list_add_tail (&req->queue, &ep->queue); spin_unlock_irqrestore (&dum->lock, flags); /* real hardware would likely enable transfers here, in case * it'd been left NAKing. */ return 0;}static int dummy_dequeue (struct usb_ep *_ep, struct usb_request *_req){ struct dummy_ep *ep; struct dummy *dum; int retval = -EINVAL; unsigned long flags; struct dummy_request *req = NULL; if (!_ep || !_req) return retval; ep = usb_ep_to_dummy_ep (_ep); dum = ep_to_dummy (ep); if (!dum->driver) return -ESHUTDOWN; spin_lock_irqsave (&dum->lock, flags); list_for_each_entry (req, &ep->queue, queue) { if (&req->req == _req) { list_del_init (&req->queue); _req->status = -ECONNRESET; retval = 0; break; } } spin_unlock_irqrestore (&dum->lock, flags); if (retval == 0) { dev_dbg (dummy_dev(dum), "dequeued req %p from %s, len %d buf %p\n", req, _ep->name, _req->length, _req->buf); _req->complete (_ep, _req); } return retval;}static intdummy_set_halt (struct usb_ep *_ep, int value){ struct dummy_ep *ep; struct dummy *dum; if (!_ep) return -EINVAL; ep = usb_ep_to_dummy_ep (_ep); dum = ep_to_dummy (ep); if (!dum->driver) return -ESHUTDOWN; if (!value) ep->halted = 0; else if (ep->desc && (ep->desc->bEndpointAddress & USB_DIR_IN) && !list_empty (&ep->queue)) return -EAGAIN; else ep->halted = 1; /* FIXME clear emulated data toggle too */ return 0;}static const struct usb_ep_ops dummy_ep_ops = { .enable = dummy_enable, .disable = dummy_disable, .alloc_request = dummy_alloc_request, .free_request = dummy_free_request, .alloc_buffer = dummy_alloc_buffer, .free_buffer = dummy_free_buffer, /* map, unmap, ... eventually hook the "generic" dma calls */ .queue = dummy_queue, .dequeue = dummy_dequeue, .set_halt = dummy_set_halt,};/*-------------------------------------------------------------------------*//* there are both host and device side versions of this call ... */static int dummy_g_get_frame (struct usb_gadget *_gadget){ struct timeval tv; do_gettimeofday (&tv); return tv.tv_usec / 1000;}static int dummy_wakeup (struct usb_gadget *_gadget){ struct dummy *dum; dum = gadget_to_dummy (_gadget); if ((dum->devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) == 0 || !(dum->port_status & (1 << USB_PORT_FEAT_SUSPEND))) return -EINVAL; /* hub notices our request, issues downstream resume, etc */ dum->resuming = 1; dum->port_status |= (1 << USB_PORT_FEAT_C_SUSPEND); return 0;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -