⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 superh_udc.c

📁 LINUX2.4.18内核下的usb GADGET驱动程序
💻 C
📖 第 1 页 / 共 4 页
字号:
/* * Renesas SuperH USB 1.1 device controller (found on SH7705, SH7727...) * * Copyright (C) 2003 Renesas Technology Europe Limited * Copyright (C) 2003 Julian Back (jback@mpc-data.co.uk), MPC Data Limited * * 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 is a driver for the USB Device Controller found on Renesas SH * processors.  This is a full-speed controller which has four * endpoints in a single fixed configuration. * * Limitations * * Only tested on SH7705.  Mostly tested with Mass Storage gadget * using Bulk-Only Transport.  It has been tested with Linux 2.4, * Linux 2.6, Windows 2000 and Windows XP hosts. *  * DMA is not (yet) implemented. * * Handling of application stalls is tricky.  We set a bit to stall an * endpoint.  When the host tries to access the ep it gets a stall and * another stall bit is latched by the device.  The host clears the * stall with a clear feature but the hardware doesn't inform us, the * latched bit is cleared but not the bit we have set, so the next * time the host accesses the ep it will get another stall and the * latch will be set again unless we have cleared our stall bit.  The * solution adopted in this driver is to use a timer to clear the * application stall bit some time after setting the stall.  This * seems to work most of the time but is not 100% reliable.  Because * of this it is best to avoid USB protocols that require the USB * device to stall the host.  Unfortunately USB mass storage does * require the device to stall when it gets unsupported commands, * Linux hosts don't send any of these unsupported commands but * Windows hosts do. * * Another place where the hardware is too clever is in the handling * of setup packets.  Many setup packets including SET_INTERFACE and * SET_CONFIGURATION are handled by the hardware without informing the * driver software.  But we need to inform the gadget driver of at * least one of these as it uses this to kick of it's data processing. * The solution adopted is that after we have recieved N setup packets * following a bus reset a fake SET_CONFIGURATION is sent to the * gadget.  We also have to arrange things so that the reply to the * fake packet is not sent out. * */#include <linux/config.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/ioport.h>#include <linux/types.h>#include <linux/version.h>#include <linux/errno.h>#include <linux/delay.h>#include <linux/sched.h>#include <linux/slab.h>#include <linux/init.h>#include <linux/timer.h>#include <linux/list.h>#include <linux/interrupt.h>#include <linux/proc_fs.h>#include <linux/mm.h>#include <asm/atomic.h>#include <asm/byteorder.h>#include <asm/dma.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/system.h>#include <asm/unaligned.h>#include <linux/usb_ch9.h>#include <linux/usb_gadget.h>#undef DEBUG#undef VERY_NOISY#define	DRIVER_DESC		"SuperH USB Peripheral Controller"#define	DRIVER_VERSION		"alpha (11 November 2003)"#ifdef USE_DMA#error "DMA not supported"#endifstatic const char driver_name [] = "superh_udc";static const char driver_desc [] = DRIVER_DESC;static const char ep0name [] = "ep0";static const char *ep_name [] = {	ep0name,	"ep1out-bulk",	"ep2in-bulk",	"ep3in-bulk",};static struct superh_udc *the_controller;#include "superh_udc.h"/* High priority interrupts */#define F0_HIGH (EP1_FULL | EP2_TR | EP2_EMPTY )#define F1_HIGH (0)/* Low priority interrupts */#define F0_LOW  (BRST | SETUP_TS | EP0o_TS | EP0i_TR | EP0i_TS)#define F1_LOW  (EP3_TR | EP3_TS | VBUSF)/* How long to leave the stall bit set - this value is quite critical * to making stalls work.  Unfortunately it doesn't seem possible to * get a value that will work reliably with both fast and slow * machines. */#define STALL_TIME     (HZ/75)/* Number of endpoints to check in the unstall timer.  It should not * be necessary to unstall bulk endpoints using the timer as long as * the gadget code is aware that this device cannot stall properly * (see the file backed storage gadget for an example).  But if the * UDC driver stalls ep0 due to a bad SETUP then the timer is still * required otherwise the stall will never get cleared.  If it is * necessary to unstall all endpoints using the timer then set this to * 4. */#define EP_TO_UNSTALL  1/* Number of packets to wait for before sending a fake * SET_CONFIGURATION to the gadget driver */#define DEFAULT_SETUP_COUNT     7#define RESET_SETUP_COUNT       2/* How long to wait for the number of packets specified above */#define SETUP_TIME              (HZ/10 )static void superh_ep_fifo_flush(struct usb_ep *_ep);static void stop_activity(struct superh_udc *dev, struct usb_gadget_driver *driver);static int superh_ep_set_halt(struct usb_ep *_ep, int value);static void udc_timer(unsigned long _dev);static struct superh_request* process_ep_req(struct superh_ep *ep,					     struct superh_request *req);static void done(struct superh_ep *ep, struct superh_request *req, int status);/* * IO */static inline void and_b(u8 mask, unsigned long addr){	ctrl_outb(ctrl_inb(addr) & mask, addr);}static inline void or_b(u8 mask, unsigned long addr){	ctrl_outb(ctrl_inb(addr) | mask, addr);}static inline void ep0_idle (struct superh_udc *dev){	DBG(DBG_VERY_NOISY, "ep0_idle\n");	dev->ep0state = EP0_IDLE;}static void init_udc_timer(struct superh_udc *dev){	init_timer(&dev->timer);	dev->timer.function = udc_timer;	dev->timer.data = (unsigned long) dev;	dev->timer.expires = jiffies + STALL_TIME;	add_timer(&dev->timer);}/* Send a fake SET_CONFIGURATION to the gadget to start it up. * Needed because the hardware doesn't let us know when the real packet * has arrived. */static void send_fake_config(struct superh_udc *dev){	struct usb_ctrlrequest r;	dev->fake_config = 1;	dev->setup_countdown = 0;	r.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD		| USB_RECIP_DEVICE;	r.bRequest = USB_REQ_SET_CONFIGURATION;	r.wValue = 1;  /* configuration to select */	r.wIndex = 0;	r.wLength = 0;	if (dev->driver->setup(&dev->gadget, &r) < 0) {		DMSG("SET_CONFIGURATION failed.\n");	}}/* * Timer function.  Clears stall from any stalled endpoints as we * don't get informed when the host has sent a clear feature. */static void udc_timer(unsigned long _dev){	struct superh_udc	*dev = (void *)_dev;	int                      i;	unsigned long		flags;	local_irq_save(flags);	if (atomic_read(&dev->in_interrupt) == 0) {		/* Check if a bus reset has been done and we haven't faked a SET_CONFIGURATION */		if (dev->gadget.speed != USB_SPEED_UNKNOWN		    && dev->setup_countdown > 0		    && jiffies - dev->reset_time > SETUP_TIME		    &&list_empty(&dev->ep[0].queue)) {			send_fake_config(dev);		}		/* Check if any end points are halted and restart them */		for (i = 0; i < EP_TO_UNSTALL; i++) {			struct superh_ep *ep = &dev->ep[i];			if (ep->halted) {				DBG(DBG_VERBOSE, "unstalling ep %d\n", i);				superh_ep_set_halt(&ep->ep, 0);				if (likely (!list_empty(&ep->queue))) {					struct superh_request *req						= list_entry(ep->queue.next,							     struct superh_request, queue);					process_ep_req(ep, req);				}			}		}	}	init_udc_timer(dev);	local_irq_restore(flags);}/* *	done - retire a request; caller blocked irqs */static void done(struct superh_ep *ep, struct superh_request *req, int status){	unsigned		stopped = ep->stopped;	DBG(DBG_NOISY, "done: %s %p %d\n", ep->ep.name, req, status);	list_del_init(&req->queue);	if (likely (req->req.status == -EINPROGRESS))		req->req.status = status;	else		status = req->req.status;	if (status && status != -ESHUTDOWN)		DBG(DBG_VERBOSE, "complete %s req %p stat %d len %u/%u\n",			ep->ep.name, &req->req, status,			req->req.actual, req->req.length);	/* don't modify queue heads during completion callback */	ep->stopped = 1;	req->req.complete(&ep->ep, &req->req);	ep->stopped = stopped;}/* *	Enable interrupts for the specified endpoint */static inline void pio_irq_enable(struct superh_ep *ep){	or_b(ep->interrupt_mask, ep->interrupt_reg);}/* *	Disable interrupts for the specified endpoint */static inline void pio_irq_disable(struct superh_ep *ep){	and_b(~ep->interrupt_mask, ep->interrupt_reg);}/* * 	nuke - dequeue ALL requests */static void nuke(struct superh_ep *ep, int status){	struct superh_request *req;	DBG(DBG_NOISY, "nuke %s %d\n", ep->ep.name, status);	/* called with irqs blocked */#ifdef	USE_DMA	if (ep->dma >= 0 && !ep->stopped)		cancel_dma(ep);#endif	while (!list_empty(&ep->queue)) {		req = list_entry(ep->queue.next,				struct superh_request,				queue);		done(ep, req, status);	}	if (ep->desc)		pio_irq_disable (ep);}static inline void clear_ep_state (struct superh_udc *dev){	unsigned i;	/* hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint	 * fifos, and pending transactions mustn't be continued in any case.	 */	for (i = 1; i < 4; i++)		nuke(&dev->ep[i], -ECONNABORTED);}/* * write a packet to an endpoint data register */static intwrite_packet(u32 epdr, struct superh_request *req, unsigned max){	u8		*buf;	unsigned	length, count;	buf = req->req.buf + req->req.actual;	prefetch(buf);	/* how big will this packet be? */	length = min(req->req.length - req->req.actual, max);	req->req.actual += length;	count = length;	while (likely(count--))		ctrl_outb(*buf++, epdr);	return length;}static intwrite_ep0_fifo (struct superh_ep *ep, struct superh_request *req){	unsigned	count;	int		is_short;	count = write_packet(USBEPDR0I, req, EP0_FIFO_SIZE);	ep->dev->stats.write.bytes += count;	/* last packet "must be" short (or a zlp) */	is_short = (count != EP0_FIFO_SIZE);	DBG(DBG_VERY_NOISY, "ep0in %d bytes %d left %p\n", count,	  req->req.length - req->req.actual, req);	ctrl_outb(EP0i_PKTE, USBTRG);	if (unlikely (is_short)) {		ep->dev->ep0state = EP0_END_XFER;		count = req->req.length;		done (ep, req, 0);		/*		 * If we have received a specified number of setups		 * after a bus reset or connect then fake a		 * SET_CONFIGURATION to the driver (as we don't get		 * them from the hardware).		 */		if (ep->dev->setup_countdown >= 0)			ep->dev->setup_countdown--;		if (ep->dev->setup_countdown == 0) {			send_fake_config(ep->dev);		}	}	return is_short;}/* * handle_ep0_setup * * Handles a SETUP request on EP0 */static void handle_ep0_setup(struct superh_udc* dev){	int i;	union { u8 raw [8]; struct usb_ctrlrequest r; } u;        for (i = 0; i < 8; i++) {		u.raw[i] = ctrl_inb(USBEPDR0S);        }		/* Send ACK */	ctrl_outb(EP0s_RDFN, USBTRG);	le16_to_cpus (&u.r.wValue);	le16_to_cpus (&u.r.wIndex);	le16_to_cpus (&u.r.wLength);	DBG(DBG_VERBOSE, "SETUP %02x.%02x v%04x i%04x l%04x\n",	    u.r.bRequestType, u.r.bRequest,	    u.r.wValue, u.r.wIndex, u.r.wLength);		if (u.r.bRequestType & USB_DIR_IN) {		DBG(DBG_VERY_NOISY, "handle_ep0_setup: EP0_IN_DATA_PHASE\n");		dev->ep0state = EP0_IN_DATA_PHASE;	}	else {		DBG(DBG_VERY_NOISY, "handle_ep0_setup: EP0_OUT_DATA_PHASE\n");		dev->ep0state = EP0_OUT_DATA_PHASE;	}		i = dev->driver->setup(&dev->gadget, &u.r);	if (i < 0) {		DMSG("SETUP %02x.%02x v%04x i%04x l%04x failed\n",		    u.r.bRequestType, u.r.bRequest,		    u.r.wValue, u.r.wIndex, u.r.wLength);		superh_ep_set_halt(&dev->ep[0].ep, 1);	}}/* * write to an IN endpoint fifo, as many packets as possible. * irqs will use this to write the rest later. * caller guarantees at least one packet buffer is ready. */static intwrite_fifo (struct superh_ep *ep, struct superh_request *req){	unsigned		max;	DBG(DBG_VERY_NOISY, "write_fifo\n");	if ((ep->bEndpointAddress & USB_DIR_IN) != USB_DIR_IN) {		DMSG("write_fifo from invalid EP (%s)\n", ep->ep.name);		return -EINVAL;	}	max = ep->desc->wMaxPacketSize;	do {

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -