📄 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-2005 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/platform_device.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 "02 May 2005"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;};enum dummy_rh_state { DUMMY_RH_RESET, DUMMY_RH_SUSPENDED, DUMMY_RH_RUNNING};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; unsigned udc_suspended:1; unsigned pullup:1; unsigned active:1; unsigned old_active:1; /* * MASTER/HOST side support */ enum dummy_rh_state rh_state; struct timer_list timer; u32 port_status; u32 old_status; 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 device *udc_dev (struct dummy *dum){ return dum->gadget.dev.parent;}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;/*-------------------------------------------------------------------------*//* SLAVE/GADGET SIDE UTILITY ROUTINES *//* 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); }}/* caller must hold lock */static voidstop_activity (struct dummy *dum){ struct dummy_ep *ep; /* prevent any more requests */ dum->address = 0; /* The timer is left running so that outstanding URBs can fail */ /* nuke any pending requests first, so driver i/o is quiesced */ list_for_each_entry (ep, &dum->gadget.ep_list, ep.ep_list) nuke (dum, ep); /* driver now does any non-usb quiescing necessary */}/* caller must hold lock */static voidset_link_state (struct dummy *dum){ dum->active = 0; if ((dum->port_status & USB_PORT_STAT_POWER) == 0) dum->port_status = 0; /* UDC suspend must cause a disconnect */ else if (!dum->pullup || dum->udc_suspended) { dum->port_status &= ~(USB_PORT_STAT_CONNECTION | USB_PORT_STAT_ENABLE | USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED | USB_PORT_STAT_SUSPEND); if ((dum->old_status & USB_PORT_STAT_CONNECTION) != 0) dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16); } else { dum->port_status |= USB_PORT_STAT_CONNECTION; if ((dum->old_status & USB_PORT_STAT_CONNECTION) == 0) dum->port_status |= (USB_PORT_STAT_C_CONNECTION << 16); if ((dum->port_status & USB_PORT_STAT_ENABLE) == 0) dum->port_status &= ~USB_PORT_STAT_SUSPEND; else if ((dum->port_status & USB_PORT_STAT_SUSPEND) == 0 && dum->rh_state != DUMMY_RH_SUSPENDED) dum->active = 1; } if ((dum->port_status & USB_PORT_STAT_ENABLE) == 0 || dum->active) dum->resuming = 0; if ((dum->port_status & USB_PORT_STAT_CONNECTION) == 0 || (dum->port_status & USB_PORT_STAT_RESET) != 0) { if ((dum->old_status & USB_PORT_STAT_CONNECTION) != 0 && (dum->old_status & USB_PORT_STAT_RESET) == 0 && dum->driver) { stop_activity (dum); spin_unlock (&dum->lock); dum->driver->disconnect (&dum->gadget); spin_lock (&dum->lock); } } else if (dum->active != dum->old_active) { if (dum->old_active && dum->driver->suspend) { spin_unlock (&dum->lock); dum->driver->suspend (&dum->gadget); spin_lock (&dum->lock); } else if (!dum->old_active && dum->driver->resume) { spin_unlock (&dum->lock); dum->driver->resume (&dum->gadget); spin_lock (&dum->lock); } } dum->old_status = dum->port_status; dum->old_active = dum->active;}/*-------------------------------------------------------------------------*//* 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 (udc_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;}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 (udc_dev(dum), "disabled %s\n", _ep->name); return retval;}static struct usb_request *dummy_alloc_request (struct usb_ep *_ep, gfp_t 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, gfp_t mem_flags
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -