This is the mail archive of the ecos-patches@sourceware.org mailing list for the eCos project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[patch] Philips D12 USB Slave Driver


This is a driver for the Philips PDIUSBD12 (D12) USB Slave Controller Chip. It was written for the D12 on a PC/104 (ISA) board. With some minor adjustments and testing should go with any CPU. I (tried to) removed all the board-specific stuff, which, unfortunately also meant removing DMA support.

The driver supports Bulk & Interrupt transfers, but not Isochronous.

A similar driver for the Philips ISP1181 is being tested and should be delivered within a few weeks.

Frank Pagliughi
SoRo Systems, Inc

diff -urN --exclude=CVS ecos-2006-04-01/eCos.hhc ecos/eCos.hhc
--- ecos-2006-04-01/eCos.hhc	1969-12-31 19:00:00.000000000 -0500
+++ ecos/eCos.hhc	2006-04-02 11:43:49.000000000 -0400
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<HTML>
+<HEAD>
+<meta name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1">
+<!-- Sitemap 1.0 -->
+</HEAD><BODY>
+<UL>
+</UL>
+</BODY></HTML>
\ No newline at end of file
diff -urN --exclude=CVS ecos-2006-04-01/eCos.hhp ecos/eCos.hhp
--- ecos-2006-04-01/eCos.hhp	1969-12-31 19:00:00.000000000 -0500
+++ ecos/eCos.hhp	2006-04-02 11:43:49.000000000 -0400
@@ -0,0 +1,19 @@
+[OPTIONS]
+Auto Index=Yes
+Binary Index=No
+Compatibility=1.1 or later
+Compiled file=eCos.chm
+Contents file=eCos.hhc
+Default Window=mainwin
+Default topic=/opt/ecos/ecos/doc/index.html
+Display compile progress=Yes
+Full-text search=Yes
+Language=0x409 English (United States)
+Title=eCos
+[WINDOWS]
+mainwin="eCos Documentation","eCos.hhc",,,"index.html","http://sources.redhat.com/ecos/","Net Release","http://www.redhat.com/products/ecos/","eCos Product",0x40060420,,0xc287e,[0,0,762,400],,,,,,,0
+
+[FILES]
+index.html
+
+[INFOTYPES]
diff -urN --exclude=CVS ecos-2006-04-01/packages/devs/usb/d12/current/cdl/usbs_d12.cdl ecos/packages/devs/usb/d12/current/cdl/usbs_d12.cdl
--- ecos-2006-04-01/packages/devs/usb/d12/current/cdl/usbs_d12.cdl	1969-12-31 19:00:00.000000000 -0500
+++ ecos/packages/devs/usb/d12/current/cdl/usbs_d12.cdl	2006-04-09 16:18:20.000000000 -0400
@@ -0,0 +1,335 @@
+# ====================================================================
+#
+#	usbs_d12.cdl
+#
+#	USB device driver for the Philips PDIUSBD12 Full Speed USB
+#	peripheral chip.
+#
+# ====================================================================
+#####ECOSGPLCOPYRIGHTBEGIN####
+## -------------------------------------------
+## This file is part of eCos, the Embedded Configurable Operating System.
+## Copyright (C) 2003, 2004 eCosCentric Limited
+## Copyright (C) 2005 Frank Pagliughi
+##
+## eCos 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 or (at your option) any later version.
+##
+## eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,
+## 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+##
+## As a special exception, if other files instantiate templates or use macros
+## or inline functions from this file, or you compile this file and link it
+## with other works to produce a work based on this file, this file does not
+## by itself cause the resulting work to be covered by the GNU General Public
+## License. However the source code for this file must still be made available
+## in accordance with section (3) of the GNU General Public License.
+##
+## This exception does not invalidate any other reasons why a work based on
+## this file might be covered by the GNU General Public License.
+## -------------------------------------------
+#####ECOSGPLCOPYRIGHTEND####
+# ====================================================================
+######DESCRIPTIONBEGIN####
+#
+# Author(s):      Frank M. Pagliughi (fmp), SoRo Systems, Inc.
+# Contributors:
+# Date:           2004-05-24
+#
+#####DESCRIPTIONEND####
+# ====================================================================
+
+cdl_package CYGPKG_DEVS_USB_D12 {
+	display     "Philips D12 USB Device Driver"
+	include_dir "cyg/io/usb"
+	parent      CYGPKG_USB
+	implements  CYGHWR_IO_USB_SLAVE
+	doc         ref/devs-usb-philips-pdiusbd12.html
+	
+	description "
+		The Philips PDIUSBD12 is a USB peripheral controller (slave)
+	chip that can connect to a microcontroller or microprocessor through
+	an 8-bit parallel bus. The SoRo Systems USB-D12-104 is a slave board 
+	for the PC's ISA or PC/104 bus that contains a D12 chip placed in the 
+	PC's I/O space with jumpered selections for IRQ and DMA settings. This 
+	package provides an eCos device driver.
+	"
+
+	cdl_component CYGFUN_DEVS_USB_D12_EP0 {
+		display       "Support the Control Endpoint 0"
+		default_value CYGINT_IO_USB_SLAVE_CLIENTS
+		requires      CYGPKG_IO_USB CYGPKG_IO_USB_SLAVE
+		compile       usbs_d12.c
+		compile       -library=libextras.a usbs_d12_data.cxx
+		description "
+			Enable support for endpoint 0. If this support is disabled
+			then the entire USB port is unusable."
+	
+		cdl_option CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY {
+			display       "Provide a devtab entry for endpoint 0"
+			default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES
+			requires      CYGPKG_IO
+			description "
+			   If endpoint 0 will only be accessed via the low-level
+			   USB-specific calls then there is no need for an entry
+			   in the device table, saving some memory. If the
+			   application intends to access the endpoint by means
+			   of open and ioctl calls then a devtab entry is needed.
+			"
+		}
+
+		cdl_option CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE {
+		    display       "Size of statically-allocated endpoint 0 transmit buffer"
+		    flavor        data
+		    default_value 256
+		    requires      { CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE >= CYGNUM_DEVS_USB_D12_EP0_PKTSIZE }
+		    description "
+		        The implementation of the support for endpoint 0 uses
+		        a single static buffer to hold the response to the
+		        current control message. Typically this buffer can be
+		        fairly small since replies to control messages tend to
+		        be small: typically some tens of bytes for the enumeration
+		        data, perhaps a bit more for unicode-encoded string
+		        descriptors. However if some application-specific protocol
+		        depends on larger control messages then this buffer
+		        size may need to be increased.
+		    "
+		}
+	}    
+
+	cdl_option CYGSEM_DEVS_USB_D12_IO_MAPPED {
+		display "I/O mapped."
+		flavor	bool
+		default_value 1
+		description  "
+			The PDIUSBD12 can be mapped into the processor's I/O space or memory
+			space. If this is set the driver accesses the chip through HAL_READ
+			and HAL_WRITE macros, otherwise it uses direct memory access.
+			"
+	}
+
+	cdl_option CYGNUM_DEVS_USB_D12_BASEADDR {
+		display       "Base Address of D12 chip"
+		flavor        data
+		legal_values  0 to 0xFF8
+		default_value 544
+		requires      CYGFUN_DEVS_USB_D12_EP0
+		description "
+			The base memory or I/O address where the USB chip resides.
+		    "
+	}
+
+	cdl_option CYGNUM_DEVS_USB_D12_IRQ {
+		display       "IRQ for the D12 chip"
+		requires      CYGFUN_DEVS_USB_D12_EP0
+		flavor        data
+		legal_values  { 3 5 7 }
+		default_value 5
+		description "
+			The IRQ assigned to the D12 chip
+		    "
+	}
+
+	cdl_option CYGNUM_DEVS_USB_D12_INT {
+		display       "INT for the D12 chip"
+		requires      CYGFUN_DEVS_USB_D12_EP0
+		flavor        data
+		legal_values  32 to 47
+		default_value { CYGNUM_DEVS_USB_D12_IRQ + 32 }
+		description "
+			The interrupt vector assigned to the D12 chip
+		    "
+	}
+
+	cdl_component CYGPKG_DEVS_USB_D12_THREAD {
+		display		"Use a thread to service D12 chip"
+		requires	CYGFUN_DEVS_USB_D12_EP0
+		default_value	0
+		description	"
+			Services the D12 USB chip with a thread, rather than at the DSR level.
+			This allows for increased debug support, like TRACE output from the 
+			driver at the expense of some throughput & reaction time. The service
+			thread MUST be at a higher priority than any application thread that
+			uses the USB port.
+		"
+
+		cdl_option CYGNUM_DEVS_USB_D12_THREAD_PRIORITY {
+			display       "Thread Priority"
+			flavor        data
+			legal_values  1 to 30
+			default_value 4
+			description "
+				The priority of the D12 device driver thread.
+			"
+		}
+
+		cdl_option CYGNUM_DEVS_USB_D12_THREAD_STACK_SIZE {
+			display		"USB Thread Stack Size"
+			flavor		data
+			default_value	4096
+			description	"
+				The stack size for the D12 device driver thread.
+			"
+		}
+	}	
+
+	cdl_component CYGFUN_DEVS_USB_D12_DEBUG {
+		display       "Debug output from the D12 Device Driver"
+		requires      CYGPKG_DEVS_USB_D12_THREAD
+		default_value 0
+		description "
+			Provide debugging output from the D12 Device Driver
+		"
+		
+		cdl_option CYGSEM_DEVS_USB_D12_DEBUG_DUMP_EP0_BUFS {
+			display		"Dump the contents of EP0 buffers"
+			flavor		bool
+			default_value	0
+			description "
+				Dump the contents of the packages going through EP0. This allows 
+				you to see things like device requests and responses.
+			"
+		}
+
+		cdl_option CYGSEM_DEVS_USB_D12_DEBUG_DUMP_BUFS {
+			display		"Dump the contents of data buffers"
+			flavor		bool
+			default_value	0
+			description "
+				Dump the contents of the packages going through the generic
+				endpoints. This allow you to see all of the data going through
+				the device.
+			"
+		}
+	}
+
+	cdl_component CYGPKG_DEVS_USB_D12_TX_EP1 {
+		display       "Endpoint 1 Interrupt IN, (tx_ep1)"
+		implements    CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS
+		requires      CYGFUN_DEVS_USB_D12_EP0
+		default_value CYGFUN_DEVS_USB_D12_EP0
+		description "
+			On the D12, Endpoint 1 IN can be used for Interrupt,
+			Bulk, or Control packages. This driver currently only supports
+			Interrupt packages on Endpoint 1 (slave -> host) transfers
+		"
+		
+		cdl_option CYGVAR_DEVS_USB_D12_TX_EP1_DEVTAB_ENTRY {
+			display       "Provide a devtab entry for Endpoint 1 IN"
+			default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES
+			requires      CYGPKG_IO
+			description "
+				If Endpoint 1 IN will only be accessed via the low-level
+				USB-specific calls then there is no need for an entry
+				in the device table, saving some memory. If the
+				application intends to access the endpoint by means
+				of open and write calls then a devtab entry is needed.
+			"
+		}
+	}
+
+	cdl_component CYGPKG_DEVS_USB_D12_RX_EP1 {
+		display       "Endpoint 1 Interrupt OUT, (rx_ep1)"
+		implements    CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS
+		requires      CYGFUN_DEVS_USB_D12_EP0
+		default_value CYGFUN_DEVS_USB_D12_EP0
+		description "
+			In the D12, Endpoint 1 OUT can be used for Interrupt,
+			Bulk, or Control packages. This driver currently only supports
+			Interrupt packages on Endpoint 1 for (host -> slave) transfers" 
+		
+		cdl_option CYGVAR_DEVS_USB_D12_RX_EP1_DEVTAB_ENTRY {
+			display       "Provide a devtab entry for Endpoint 1 OUT"
+			default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES
+			requires      CYGPKG_IO
+			description "
+				If Endpoint 1 OUT will only be accessed via the low-level
+				USB-specific calls then there is no need for an entry
+				in the device table, saving some memory. If the
+				application intends to access the endpoint by means
+				of open and write calls then a devtab entry is needed.
+			"
+		}
+	}
+
+	cdl_component CYGPKG_DEVS_USB_D12_TX_EP2 {
+		display			"Endpoint 2 Bulk IN, (tx_ep2)"
+		implements		CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS
+		requires		CYGFUN_DEVS_USB_D12_EP0
+		default_value	CYGFUN_DEVS_USB_D12_EP0
+		description "
+			In the D12, Endpoint 2 IN can be used for Bulk, Interrupt,
+			or Control packages. This driver currently only supports
+		    Bulk packages on Endpoint 2 for (slave -> host) transfers.
+		"
+		
+		cdl_option CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY {
+			display       "Provide a devtab entry for Endpoint 2 IN"
+			default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES
+			requires      CYGPKG_IO
+			description "
+				If Endpoint 2 IN will only be accessed via the low-level
+				USB-specific calls then there is no need for an entry
+				in the device table, saving some memory. If the
+				application intends to access the endpoint by means
+				of open and write calls then a devtab entry is needed.
+			"
+		}
+	}
+
+	cdl_component CYGPKG_DEVS_USB_D12_RX_EP2 {
+		display       "Endpoint 2 Bulk OUT, (rx_ep2)"
+		implements    CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS
+		requires      CYGFUN_DEVS_USB_D12_EP0
+		default_value CYGFUN_DEVS_USB_D12_EP0
+		description "
+			In the D12, Endpoint 2 OUT can be used for Bulk, Interrupt,
+			Control packages. This driver currently only supports
+			Bulk packages on Endpoint 2 for (host -> slave) transfers."
+		
+		cdl_option CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY {
+			display       "Provide a devtab entry for Endpoint 2 OUT"
+			default_value CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES
+			requires      CYGPKG_IO
+			description "
+				If Endpoint 2 OUT will only be accessed via the low-level
+				USB-specific calls then there is no need for an entry
+				in the device table, saving some memory. If the
+				application intends to access the endpoint by means
+				of open and write calls then a devtab entry is needed.
+			"
+		}
+	}
+
+	cdl_option CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME {
+		display       "Base name for devtab entries"
+		flavor        data
+		active_if     { CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY ||
+						CYGVAR_DEVS_USB_D12_TX_EP1_DEVTAB_ENTRY ||
+						CYGVAR_DEVS_USB_D12_RX_EP1_DEVTAB_ENTRY ||
+						CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY ||
+						CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY
+		}
+		default_value { "\"/dev/usbs\"" }
+		description "
+			If the D12 USB device driver package provides devtab entries
+			for any of the endpoints then this option gives
+			control over the names of these entries. By default the
+			endpoints will be called \"/dev/usbs0c\", \"/dev/usbs1r\"
+			\"/dev/usbs1w\", \"/dev/usbs2r\", \"/dev/usbs2w\"
+			(assuming those endpoints are all enabled. The common
+			part \"/dev/usbs\" is determined by this configuration
+			option. It may be necessary to change this if there are
+			multiple USB slave-side devices on the target hardware to
+			prevent a name clash.
+		"
+	}
+}
+
diff -urN --exclude=CVS ecos-2006-04-01/packages/devs/usb/d12/current/include/usbs_d12.h ecos/packages/devs/usb/d12/current/include/usbs_d12.h
--- ecos-2006-04-01/packages/devs/usb/d12/current/include/usbs_d12.h	1969-12-31 19:00:00.000000000 -0500
+++ ecos/packages/devs/usb/d12/current/include/usbs_d12.h	2006-04-02 12:33:18.000000000 -0400
@@ -0,0 +1,76 @@
+#ifndef CYGONCE_USBS_D12_H
+# define CYGONCE_USBS_D12_H
+//==========================================================================
+//
+//      include/usbs_d12.h
+//
+//      The interface exported by the D12 USB device driver
+//
+//==========================================================================
+//####ECOSGPLCOPYRIGHTBEGIN####
+// -------------------------------------------
+// This file is part of eCos, the Embedded Configurable Operating System.
+// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
+//
+// eCos 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 or (at your option) any later version.
+//
+// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,
+// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+//
+// As a special exception, if other files instantiate templates or use macros
+// or inline functions from this file, or you compile this file and link it
+// with other works to produce a work based on this file, this file does not
+// by itself cause the resulting work to be covered by the GNU General Public
+// License. However the source code for this file must still be made available
+// in accordance with section (3) of the GNU General Public License.
+//
+// This exception does not invalidate any other reasons why a work based on
+// this file might be covered by the GNU General Public License.
+//
+// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
+// at http://sources.redhat.com/ecos/ecos-license/
+// -------------------------------------------
+//####ECOSGPLCOPYRIGHTEND####
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):    Frank Pagliughi (fmp)
+// Contributors: fmp
+// Date:         2004-05-24
+// Purpose:
+//
+//####DESCRIPTIONEND####
+//==========================================================================
+
+#include <cyg/io/usb/usbs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The Philips D12 is a full speed (12Mbps) USB peripheral controller
+ * chip, with a parallel interface allowing it to be connected to nearly
+ * any microcontroller or microprocessor. 
+ */
+extern usbs_control_endpoint    usbs_d12_ep0;
+
+extern usbs_rx_endpoint         usbs_d12_rx_ep1;
+extern usbs_tx_endpoint         usbs_d12_tx_ep1;
+extern usbs_rx_endpoint         usbs_d12_rx_ep2;
+extern usbs_tx_endpoint         usbs_d12_tx_ep2;
+    
+#ifdef __cplusplus
+} /* extern "C" { */
+#endif
+
+
+#endif /* CYGONCE_USBS_D12_H */
diff -urN --exclude=CVS ecos-2006-04-01/packages/devs/usb/d12/current/src/usbs_d12.c ecos/packages/devs/usb/d12/current/src/usbs_d12.c
--- ecos-2006-04-01/packages/devs/usb/d12/current/src/usbs_d12.c	1969-12-31 19:00:00.000000000 -0500
+++ ecos/packages/devs/usb/d12/current/src/usbs_d12.c	2006-04-09 16:25:24.000000000 -0400
@@ -0,0 +1,2344 @@
+//==========================================================================
+//
+//      usbs_d12.c
+//
+//      Driver for the D12 USB Slave Board
+//
+//==========================================================================
+//####ECOSGPLCOPYRIGHTBEGIN####
+// -------------------------------------------
+// This file is part of eCos, the Embedded Configurable Operating System.
+// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
+// Copyright (C) 2004, Frank Pagliughi
+//
+// eCos 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 or (at your option) any later version.
+//
+// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,
+// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+//
+// As a special exception, if other files instantiate templates or use macros
+// or inline functions from this file, or you compile this file and link it
+// with other works to produce a work based on this file, this file does not
+// by itself cause the resulting work to be covered by the GNU General Public
+// License. However the source code for this file must still be made available
+// in accordance with section (3) of the GNU General Public License.
+//
+// This exception does not invalidate any other reasons why a work based on
+// this file might be covered by the GNU General Public License.
+//
+// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
+// at http://sources.redhat.com/ecos/ecos-license/
+// -------------------------------------------
+//####ECOSGPLCOPYRIGHTEND####
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):    Frank M. Pagliughi (fmp)
+// Date:         2004-05-22
+//
+// This code is a device driver for the SoRo Systems USB-D12-104, a PC/104
+// (ISA) Full-Speed USB slave board, which turns a PC/104 stack into a USB
+// slave device. The board contains a Philips PDIUSBD12 Peripheral Controller
+// Chip mapped into the PC's I/O space, with jumper-selectable I/O base 
+// address, IRQ, and DMA settings. The eCos config tool is used to adjust
+// settings for this driver to match the physical jumper settings. The chip
+// could run in polled mode without an IRQ, but this wouldn't be a great idea
+// other than maybe a debug environment. 
+//
+// The board supports DMA transfers over the Main endpoint, but I temporarily
+// removed that code to make the driver portable to other platforms.
+//
+// *** This driver should also work with the Philips ISA Eval Board
+//		for the D12, but I couldn't get one of them from Philips, so
+//		I couldn't test it.
+//
+// The D12 uses an indexed register set, which it describes as "commands." 
+// You first write a command (index) to the command register then you can
+// read or write data to that register. Each multi-byte command read or write
+// must be dione atomically, so all access to the chip must be serialized.
+// 
+// The D12 requests service through a single interrupt. The driver can
+// be configured to service the chip through a DSR or a thread. In either
+// case, the "service" code assumes it has unfettered access to the chip.
+// The interrupt, therefore never touches the chip. It just schedules the
+// DSR or service thread.
+// Currently, the code gets exclusive access to the chip by locking the
+// scheduler. This is suboptimal (locking the whole OS to touch one I/O 
+// chip), and better method should be explored.
+//
+// This version of the driver does not support Isocronous transfers.
+// 
+// Additional notes on the D12:
+//
+// - The D12 has 4 endpoints (2 IN, and 2 OUT) in addition to the main 
+//		control endpoint:
+//		- Endp 0 (Control In & Out, 16 byte buffer)
+//		- Endp 1 (IN & OUT, Bulk or Interrupt, 16 byte ea)
+//		- Endp 2 (IN and/or OUT, Bulk, Interrupt, or Isoc, 64 bytes ea)
+//
+// - The "main" endpoint (as Philips calls it) is Endp 2. It's double
+//		buffered and has a DMA interface, and thus, is suited for high
+//		throughput. For applications that perform either Isoc In or Out,
+//		the buffers for Endp 2 can be combined for a 128 byte space.
+//		This driver, however, currently does not support this.
+//
+// - There may be a flaw in the double buffering of the rx main endpoint. 
+//		According to the documentation it should be invisible to the software,
+//		but if both buffers fill (on an rx/OUT), they must both be read 
+//		together, otherwise it appears that the buffers/packets are returned
+//		in reverse order. ReadMainEndpointBuf() returns the data properly.
+//
+// - All the interrupt sources on the chip - individual endpoints, bus reset,
+//		suspend, and DMA - are OR'ed together and can be checked via the 
+//		interrupt status register. When using edge-sensitive interrupts, as
+//		we do here, the ISR/DSR must be sure all interrupts are cleared before
+//		returning otherwise no new interrupts will be latched.
+//
+// - If the DMA controller is not used for the Main Endpoint, you MUST enable
+//		the main endpoint interrupts in the DMA register (bits 6 & 7).
+//		Personally, I think this should be the default at reset, to make it
+//		compatible with the other endpoints, but Philips didn't see it that
+//		way.
+// 
+// - When a Setup (Device Request) packet arrives in the control endpoint, a
+//		bit is set in the endpoint's status register indicating the packet is
+//		setup and not data. By the USB standard, a setup packet can not be
+//		NAK'ed or STALL'ed, so when the chip receives a setup packet, it 
+//		flushes the Ctrl (EP0) IN buffer and disables the Validate and Clear
+//		Buffer commands. We must send an "acknowledge setup" to both
+//		EP0 IN and OUT before a Validate or Clear Buffer command is effective.
+//		See ReadSetupPacket().
+//
+//####DESCRIPTIONEND####
+//==========================================================================
+
+
+#include <cyg/infra/cyg_type.h>
+#include <cyg/infra/cyg_ass.h>
+#include <cyg/infra/cyg_trac.h>
+#include <cyg/infra/diag.h>
+
+#include <pkgconf/devs_usb_d12.h>
+
+#include <cyg/hal/drv_api.h>
+#include <cyg/hal/hal_arch.h>
+#include <cyg/hal/hal_io.h>
+#include <cyg/hal/hal_cache.h>
+#include <cyg/error/codes.h>
+
+#include <cyg/io/usb/usb.h>
+#include <cyg/io/usb/usbs.h>
+
+#include <string.h>
+
+// --------------------------------------------------------------------------
+//								Common Types
+// --------------------------------------------------------------------------
+
+typedef cyg_uint8	byte;
+typedef cyg_uint8	uint8;
+typedef cyg_int16	int16;
+typedef cyg_uint16	uint16;
+typedef cyg_int32	int32;
+typedef cyg_uint32	uint32;
+
+// --------------------------------------------------------------------------
+//								Tracing & Debug
+// --------------------------------------------------------------------------
+// If the driver is configured to use a thread to service the chip, then it
+// can also be configured to dump a lot of debug output.
+// Care must be taken that USB timing requirements are not violated by 
+// dumping debug info. If the data is sent to a serial port, it should use
+// a hardware driver and have a large output buffer (115200 baud & 2kB
+// buffer works for me).
+
+#if defined(CYGFUN_DEVS_USB_D12_DEBUG) && CYGFUN_DEVS_USB_D12_DEBUG
+	#define TRACE_D12	diag_printf
+#else
+	#define TRACE_D12	(1) ? (void)0 : diag_printf
+#endif
+
+#if defined(CYGSEM_DEVS_USB_D12_DEBUG_DUMP_EP0_BUFS) && CYGSEM_DEVS_USB_D12_DEBUG_DUMP_EP0_BUFS
+	#define TRACE_EP0	1
+#endif
+
+#if defined(CYGSEM_DEVS_USB_D12_DEBUG_DUMP_BUFS) && CYGSEM_DEVS_USB_D12_DEBUG_DUMP_BUFS
+	#define TRACE_EP	1
+#endif
+
+#if defined(TRACE_EP0) || defined(TRACE_EP)
+	static void _trace_buf(const char *hdr, const byte* buf, unsigned n)
+	{
+		unsigned i;
+
+		if (buf != 0 && n != 0) {
+			if (hdr && hdr[0])
+				TRACE_D12("%s ", hdr);
+
+			TRACE_D12("[");
+			for (i=0; i<n; i++) 
+				TRACE_D12(" x%02X", buf[i]);
+			TRACE_D12("]\n");
+		}
+	}
+#endif
+
+#if defined(TRACE_EP0)
+	#define TRACE_BUF0	_trace_buf
+#else	
+	#define TRACE_BUF0(hdr, buf, n)
+#endif
+
+#if defined(TRACE_EP)
+	#define TRACE_BUF	_trace_buf
+#else	
+	#define TRACE_BUF(hdr, buf, n)
+#endif
+
+// ==========================================================================
+//								Chip Wrapper
+// ==========================================================================
+
+// This section contains functions that wrapper the low-level access to the 
+// chip. There's a function around each register access on the chip, and then
+// some.
+
+#if defined(CYGSEM_DEVS_USB_D12_IO_MAPPED)
+	typedef void* d12_addr_type;
+#else
+	typedef byte* d12_addr_type;
+#endif
+
+#define D12_BASE_ADDR	((d12_addr_type) CYGNUM_DEVS_USB_D12_BASEADDR)
+
+#define D12_ENDP0_SIZE			 16		// Size of Ctrl Endp
+#define D12_MAIN_ENDP			  2		// The D12's "Main" Endp is special, double buffered
+#define D12_MAIN_ENDP_SIZE		 64		// Size of each main endp buffer
+#define D12_MAX_PACKET_SIZE		128		// Max packet is actually double main endp
+
+#define D12_CHIP_ID 0x1012		// Value that's returned by a read of the D12's Chip ID register
+
+// ----- Endpoint Indices -----
+
+enum {
+	D12_ENDP_INVALID = 0xFF,
+	D12_ENDP_MIN = 0,
+
+	D12_RX_CTRL_ENDP = D12_ENDP_MIN,		// Rx/Tx Nomenclature
+	D12_TX_CTRL_ENDP,
+
+	D12_RX_ENDP0 = D12_ENDP_MIN,
+	D12_TX_ENDP0,
+	D12_RX_ENDP1,
+	D12_TX_ENDP1,
+	D12_RX_ENDP2,
+	D12_TX_ENDP2,
+	D12_RX_MAIN_ENDP	= D12_RX_ENDP2,
+	D12_TX_MAIN_ENDP	= D12_TX_ENDP2,
+
+
+	D12_CTRL_ENDP_OUT = D12_ENDP_MIN,		// IN/OUT Nomenclature
+	D12_CTRL_ENDP_IN,
+
+	D12_ENDP0_OUT = D12_ENDP_MIN,
+	D12_ENDP0_IN,
+	D12_ENDP1_OUT,
+	D12_ENDP1_IN,
+	D12_ENDP2_OUT,
+	D12_ENDP2_IN,
+	D12_MAIN_ENDP_OUT	= D12_ENDP2_OUT,
+	D12_MAIN_ENDP_IN	= D12_ENDP2_IN,
+
+	D12_ENDP_INSERT_BEFORE,
+	D12_ENDP_MAX = D12_ENDP_INSERT_BEFORE-1
+};
+
+// ----- Set Mode Reg configuration byte -----
+
+enum {	
+	D12_MODE_CFG_NO_LAZYCLOCK	= 0x02,
+	D12_MODE_CFG_CLOCK_RUNNING	= 0x04,
+	D12_MODE_CFG_INTERRUPT		= 0x08,
+	D12_MODE_CFG_SOFT_CONNECT	= 0x10,
+
+	D12_MODE_CFG_NON_ISO		= 0x00,
+	D12_MODE_CFG_ISO_OUT		= 0x40,
+	D12_MODE_CFG_ISO_IN			= 0x80,
+	D12_MODE_CFG_ISO_IO			= 0xC0,
+
+	D12_MODE_CFG_DFLT = D12_MODE_CFG_NO_LAZYCLOCK |
+							D12_MODE_CFG_CLOCK_RUNNING | D12_MODE_CFG_NON_ISO
+};
+
+// ----- Set Mode Reg clock div factor -----
+
+enum {
+	D12_MODE_CLK_24_MHZ			= 1,
+	D12_MODE_CLK_16_MHZ			= 2,
+	D12_MODE_CLK_12_MHZ			= 3,
+	D12_MODE_CLK_8_MHZ			= 5,
+	D12_MODE_CLK_6_MHZ			= 7,
+	D12_MODE_CLK_4_MHZ			= 11,
+
+	D12_MODE_CLK_DIV_MASK		= 0x0F,
+
+	D12_MODE_CLK_SET_TO_ONE		= 0x40,
+	D12_MODE_CLK_SOF_ONLY_INTR	= 0x80,
+
+	D12_MODE_CLK_DFLT = D12_MODE_CLK_4_MHZ | D12_MODE_CLK_SET_TO_ONE
+};
+
+// ----- Set DMA Register -----
+
+enum {
+	D12_DMA_SINGLE_CYCLE,
+	D12_DMA_BURST_4_CYCLE,
+	D12_DMA_BURST_8_CYCLE,
+	D12_DMA_BURST_16_CYCLE,
+
+	D12_DMA_ENABLE						= 0x04,
+	D12_DMA_DIR_WRITE					= 0x08,
+	D12_DMA_DIR_READ					= 0x00,
+	D12_DMA_AUTO_RELOAD					= 0x10,
+	D12_DMA_INTR_PIN_MODE				= 0x20,
+
+	D12_DMA_MAIN_ENDP_OUT_INTR_ENABLE	= 0x40,
+	D12_DMA_MAIN_RX_ENDP_INTR_ENABLE	= 0x40,
+
+	D12_DMA_MAIN_ENDP_IN_INTR_ENABLE	= 0x80,
+	D12_DMA_MAIN_TX_ENDP_INTR_ENABLE	= 0x80,
+
+	D12_DMA_MAIN_ENDP_INTR_ENABLE		= 0xC0	// Enables IN & OUT Intr
+};
+
+// ----- Interrupt Register Bits -----
+
+enum {
+	D12_INTR_RX_CTRL_ENDP		= 0x0001,
+	D12_INTR_TX_CTRL_ENDP		= 0x0002,
+
+	D12_INTR_RX_ENDP0			= D12_INTR_RX_CTRL_ENDP,
+	D12_INTR_TX_ENDP0			= D12_INTR_TX_CTRL_ENDP,
+	D12_INTR_RX_ENDP1			= 0x0004,
+	D12_INTR_TX_ENDP1			= 0x0008,
+	D12_INTR_RX_ENDP2			= 0x0010,
+	D12_INTR_TX_ENDP2			= 0x0020,
+
+	D12_INTR_BUS_RESET			= 0x0040,
+	D12_INTR_SUSPEND_CHANGE		= 0x0080,
+	D12_INTR_DMA_EOT			= 0x0100
+};
+
+// ----- Read Endpoint Status -----
+
+enum {
+	D12_ENDP_STAT_SETUP_PACKET	= 0x04,
+	D12_ENDP_STAT_BUF0_FULL		= 0x20,
+	D12_ENDP_STAT_BUF1_FULL		= 0x40,
+	D12_ENDP_STAT_ANY_BUF_FULL	= 0x60,
+	D12_ENDP_STAT_BOTH_BUF_FULL	= 0x60,
+	D12_ENDP_STAT_STALL			= 0x80,
+};
+
+// ----- Last Transaction Status Bits -----
+
+enum {
+	D12_LAST_TRANS_DATA_SUCCESS			= 0x01,
+	D12_LAST_TRANS_ERR_CODE_MASK		= 0x1E,
+	D12_LAST_TRANS_SETUP_PACKET			= 0x20,
+	D12_LAST_TRANS_DATA1_PACKET			= 0x40,
+	D12_LAST_TRANS_PREV_STAT_NOT_READ	= 0x80
+};
+
+static const byte RX_ENDP_INDEX[] = { D12_RX_ENDP0, D12_RX_ENDP1, D12_RX_ENDP2 };
+static const byte TX_ENDP_INDEX[] = { D12_TX_ENDP0, D12_TX_ENDP1, D12_TX_ENDP2 };
+
+static const int RX_ENDP_SIZE[] = { 16, 16, 64 };
+static const int TX_ENDP_SIZE[] = { 16, 16, 64 };
+
+typedef void (*completion_fn)(void*, int);
+
+
+#ifndef USB_SETUP_PACKET_LEN 
+	#define USB_SETUP_PACKET_LEN	8
+#endif
+
+// ----- Command Definitions -----
+
+enum {	
+	CMD_SET_ADDR_EN				= 0xD0,		// Write 1 byte
+	CMD_SET_ENDP_EN				= 0xD8,		// Write 1 byte
+	CMD_SET_MODE				= 0xF3,		// Write 2 bytes
+	CMD_SET_DMA					= 0xFB,		// Write/Read 1 byte
+	CMD_READ_INTR_REG			= 0xF4,		// Read 2 bytes
+	CMD_SEL_ENDP				= 0x00,		// (+ Endp Index) Read 1 byte (opt)
+	CMD_READ_LAST_TRANS_STAT	= 0x40,		// (+ Endp Index) Read 1 byte (opt)
+	CMD_READ_ENDP_STAT			= 0x80,		// (+ Endp Index) Read 1 byte
+	CMD_READ_BUF				= 0xF0,		// Read n bytes
+	CMD_WRITE_BUF				= 0xF0,		// Write n bytes
+	CMD_SET_ENDP_STAT			= 0x40,		// (+ Endp Index) Write 1 byte
+	CMD_ACK_SETUP				= 0xF1,		// None
+	CMD_CLEAR_BUF				= 0xF2,		// None
+	CMD_VALIDATE_BUF			= 0xFA,		// None
+	CMD_SEND_RESUME				= 0xF6,		// None
+	CMD_READ_CURR_FRAME_NUM		= 0xF5,		// Read 1 or 2 bytes
+	CMD_READ_CHIP_ID			= 0xFD		// Read 2 bytes
+};
+
+// ----- Set Endpoint Enable Register -----
+
+enum {
+	ENDP_DISABLE,
+	ENDP_ENABLE
+};
+
+// ----- Select Endpoint Results -----
+
+enum {
+	SEL_ENDP_FULL	= 0x01,
+	SEL_ENDP_STALL	= 0x02
+};
+
+// ----- Error Codes from ReadLastTrans (need to be bit shifter) -----
+
+enum {
+	ERROR_NO_ERROR,
+	ERROR_PID_ENCODING,
+	ERROR_PID_UNKNOWN,
+	ERROR_UNEXPECTED_PACKET,
+	ERROR_TOKEN_CRC,
+	ERROR_DATA_CRC,
+	ERROR_TIMEOUT,
+	ERROR_BABBLE,
+	ERROR_UNEXPECTED_EOP,
+	ERROR_NAK,
+	ERROR_PACKET_ON_STALL,
+	ERROR_OVERFLOW,
+	ERROR_BITSTUFF,
+	ERROR_WRONG_DATA_PID
+};
+
+// ------------------------------------------------------------------------
+
+static inline uint16 make_word(byte hi, byte lo)
+{
+	return ((uint16) hi << 8) | lo;
+}
+
+// ------------------------------------------------------------------------
+//								Data-Only I/O                              
+// ------------------------------------------------------------------------
+//
+// These routines read or write 8 or 16-bit values to the data area.
+// 16-bit values are written a byte at a time, low byte first.
+
+static inline byte d12_read_data_byte(d12_addr_type base_addr)
+{
+	#if defined(CYGSEM_DEVS_USB_D12_IO_MAPPED)
+		byte val;
+		HAL_READ_UINT8((unsigned) base_addr, val);
+		return val;
+	#else
+		return *base_addr;
+	#endif
+}
+
+static inline uint16 d12_read_data_word(d12_addr_type base_addr)
+{
+	uint16 val = d12_read_data_byte(base_addr);
+	val |= ((uint16) d12_read_data_byte(base_addr)) << 8;
+	return val;
+}
+
+static inline void d12_write_data_byte(d12_addr_type base_addr, byte val)
+{
+	#if defined(CYGSEM_DEVS_USB_D12_IO_MAPPED)
+		HAL_WRITE_UINT8((unsigned) base_addr, val);
+	#else
+		*base_addr = val;
+	#endif
+}
+
+static inline void d12_write_data_word(d12_addr_type base_addr, uint16 val)
+{
+	d12_write_data_byte(base_addr, (byte) val);
+	d12_write_data_byte(base_addr, (byte) (val >> 8));
+}
+
+// ------------------------------------------------------------------------
+//							Command & Data I/O							    
+// ------------------------------------------------------------------------
+//
+//	These routines read & write the registers in the D12. The procedure is
+//	to write a register/command value to the command address (A0=1) then
+//	read or write any required data a byte at a time to the data address
+//	(A0=0). The data can be one byte or two. If two, the low byte is read/
+//	written first.
+
+//	NOTE: These MUST be atomic operations. It's up to the caller 
+//			to insure this.
+
+static inline void d12_write_cmd(d12_addr_type base_addr, byte cmd)
+{
+	#if defined(CYGSEM_DEVS_USB_D12_IO_MAPPED)
+		HAL_WRITE_UINT8((unsigned) base_addr+1, cmd);
+	#else
+		*(base_addr+1) = cmd;
+	#endif
+}
+
+static inline void d12_write_byte(d12_addr_type base_addr, byte cmd, byte val)
+{
+	d12_write_cmd(base_addr, cmd);
+	d12_write_data_byte(base_addr, val);
+}
+
+static inline void d12_write_word(d12_addr_type base_addr, byte cmd, uint16 val)
+{
+	d12_write_cmd(base_addr, cmd);
+	d12_write_data_word(base_addr, val);
+}
+
+static inline byte d12_read_byte(d12_addr_type base_addr, byte cmd)
+{
+	d12_write_cmd(base_addr, cmd);
+	return d12_read_data_byte(base_addr);
+}
+
+static inline uint16 d12_read_word(d12_addr_type base_addr, byte cmd)
+{
+	d12_write_cmd(base_addr, cmd);
+	return d12_read_data_word(base_addr);
+}
+
+// ------------------------------------------------------------------------
+//	Reads 'n' bytes from the data address of the D12 and places them into
+//	the buf[] array. Buf can be NULL, in which case the specified number of
+//	bytes are read and discarded.
+
+static uint8 d12_read_data(d12_addr_type base_addr, byte *buf, uint8 n)
+{
+	uint8 i;
+
+	if (buf) {
+		for (i=0; i<n; ++i)
+			buf[i] = d12_read_data_byte(base_addr);
+	}
+	else {
+		for (i=0; i<n; ++i)
+			d12_read_data_byte(base_addr);
+		n = 0;
+	}
+
+	return n;
+}
+
+// ------------------------------------------------------------------------
+// Writes 'n' bytes out the data reg of the chip
+
+static void d12_write_data(d12_addr_type base_addr, const byte *buf, uint8 n)
+{
+	uint8 i;
+
+	for (i=0; i<n; ++i)
+		d12_write_data_byte(base_addr, buf[i]);
+}
+
+// ------------------------------------------------------------------------
+//							Higher Level Commands
+// ------------------------------------------------------------------------
+
+// 	Stalls or Unstalls the endpoint. Bit0=1 for stall, =0 to unstall.
+
+static inline void d12_set_endp_status(d12_addr_type base_addr, byte endp_idx, byte stat)
+{
+	d12_write_byte(base_addr, CMD_SET_ENDP_STAT + endp_idx, stat);
+}
+
+// ------------------------------------------------------------------------
+// Stalls the control endpoint (both in & out).
+
+static void d12_stall_ctrl_endp(d12_addr_type base_addr, bool stall)
+{
+	d12_set_endp_status(base_addr, D12_TX_CTRL_ENDP, stall ? 1 : 0);
+	d12_set_endp_status(base_addr, D12_RX_CTRL_ENDP, stall ? 1 : 0);
+}
+
+// ------------------------------------------------------------------------
+// Stalls/unstalls the specified endpoint. 
+
+void inline d12_stall_endp(d12_addr_type base_addr, byte endp_idx, bool stall)
+{
+	d12_set_endp_status(base_addr, endp_idx, stall ? 1 : 0);
+}
+
+// ------------------------------------------------------------------------ */
+// Tells the chip that the selected endpoint buffer has been completely
+// read. This should be called after the application reads all the data
+// from an endpoint.  While there's data in the buffer the chip will 
+// automatically NAK any additional OUT packets from the host. 
+
+static inline void d12_clear_buffer(d12_addr_type base_addr)
+{
+	d12_write_cmd(base_addr, CMD_CLEAR_BUF);
+}
+
+// ------------------------------------------------------------------------
+// Tells the chip that the data in the selected endpoint buffer is complete
+// and ready to be sent to the host.
+
+static inline void d12_validate_buffer(d12_addr_type base_addr)
+{
+	d12_write_cmd(base_addr, CMD_VALIDATE_BUF);
+}
+
+// ------------------------------------------------------------------------
+// Sends an upstream resume signal for 10ms. This command is normally 
+// issued when the device is in suspend.
+
+static inline void d12_send_resume(d12_addr_type base_addr)
+{
+	d12_write_cmd(base_addr, CMD_SEND_RESUME);
+}
+
+// ------------------------------------------------------------------------
+// Gets the frame number of the last successfully received 
+// start-of-frame (SOF).
+
+static inline uint16 d12_read_curr_frame_num(d12_addr_type base_addr)
+{
+	return d12_read_word(base_addr, CMD_READ_CURR_FRAME_NUM);
+}
+
+// ------------------------------------------------------------------------
+// This routine acknowledges a setup packet by writing an Ack Setup command
+// to the currently selected Endpoint. This must be done for both EP0 out
+// and EP0 IN whenever a setup packet is received.
+
+static inline void d12_ack_setup(d12_addr_type base_addr)
+{
+	d12_write_cmd(base_addr, CMD_ACK_SETUP);
+}
+
+// ------------------------------------------------------------------------
+// Gets the value of the 16-bit interrupt register, which indicates the 
+// source of an interrupt (if interrupts are not used, this reg can be 
+// polled to find when service is required).
+
+static inline uint16 d12_read_intr_reg(d12_addr_type base_addr)
+{
+	return d12_read_word(base_addr, CMD_READ_INTR_REG) & 0x01FF;
+}
+
+// ------------------------------------------------------------------------
+// Gets/Sets the contents of the DMA register.
+
+static inline byte d12_get_dma(d12_addr_type base_addr)
+{
+	return d12_read_byte(base_addr, CMD_SET_DMA);
+}
+
+static inline void d12_set_dma(d12_addr_type base_addr, byte mode)
+{
+	d12_write_byte(base_addr, CMD_SET_DMA, mode);
+}
+
+// ------------------------------------------------------------------------
+// Sends the "Select Endpoint" command (0x00 - 0x0D) to the chip.
+// This command initializes an internal pointer to the start of the 
+// selected buffer.
+// 
+// Returns: Bitfield containing status of the endpoint
+
+byte d12_select_endp(d12_addr_type base_addr, byte endp_idx)
+{
+	return d12_read_byte(base_addr, CMD_SEL_ENDP + endp_idx);
+}
+
+// ------------------------------------------------------------------------
+// Gets the status of the last transaction of the endpoint. It also resets 
+// the corresponding interrupt flag in the interrupt register, and clears 
+// the status, indicating that it was read.
+//
+// Returns: Bitfield containing the last transaction status.
+
+static inline byte d12_read_last_trans_status(d12_addr_type base_addr, byte endp_idx)
+{
+	return d12_read_byte(base_addr, CMD_READ_LAST_TRANS_STAT + endp_idx);
+}
+
+// ------------------------------------------------------------------------
+// Reads the status of the requested endpoint. 
+// Just for the heck of it, we mask off the reserved bits.
+//
+// Returns: Bitfield containing the endpoint status.
+
+static inline byte d12_read_endp_status(d12_addr_type base_addr, byte endp_idx)
+{
+	return d12_read_byte(base_addr, CMD_READ_ENDP_STAT + endp_idx) & 0xE4;
+}
+
+// ------------------------------------------------------------------------
+// Returns true if there is data available in the specified endpoint's
+// ram buffer. This is determined by the buf full flags in the endp status
+// register.
+
+static inline bool d12_data_available(d12_addr_type base_addr, byte endp_idx)
+{
+	byte by = d12_read_endp_status(base_addr, endp_idx);
+	return (bool) (by & D12_ENDP_STAT_ANY_BUF_FULL);
+}
+
+// ------------------------------------------------------------------------
+// Clears the transaction status for each of the endpoints by calling the
+// d12_read_last_trans_status() function for each. 
+
+static void d12_clear_all_intr(d12_addr_type base_addr)
+{
+	uint8 endp;
+
+	d12_read_intr_reg(base_addr);
+
+	for (endp=D12_ENDP_MIN; endp<=D12_ENDP_MAX; ++endp)
+		d12_read_last_trans_status(base_addr, endp);
+}
+
+// ------------------------------------------------------------------------
+// Loads a value into the Set Address / Enable register. This sets the 
+// device's USB address (lower 7 bits) and enables/disables the function
+// (msb).
+
+static void d12_set_addr_enable(d12_addr_type base_addr, byte usb_addr, bool enable)
+{
+	if (enable) 
+		usb_addr |= 0x80;
+
+	d12_write_byte(base_addr, CMD_SET_ADDR_EN, usb_addr);
+}
+
+// ------------------------------------------------------------------------
+// Enables/disables the generic endpoints.
+
+static inline void d12_set_endp_enable(d12_addr_type base_addr, bool enable)
+{
+	d12_write_byte(base_addr, CMD_SET_ENDP_EN, (enable) ? ENDP_ENABLE : ENDP_DISABLE);
+}
+
+// ------------------------------------------------------------------------
+// Sets the device's configuration and CLKOUT frequency.
+
+static void d12_set_mode(d12_addr_type base_addr, byte config, byte clk_div)
+{
+	uint16 w = make_word(clk_div, config);
+	d12_write_word(base_addr, CMD_SET_MODE, w);
+}
+
+// ------------------------------------------------------------------------
+// Reads a setup packet from the control endpoint. This procedure is 
+// somewhat different than reading a data packet. By the USB standard, a 
+// setup packet can not be NAK'ed or STALL'ed, so when the chip receives a 
+// setup packet, it flushes the Ctrl (EP0) IN buffer and disables the 
+// Validate and Clear Buffer commands. The processor must send an 
+// acknowledge setup to both EP0 IN and OUT before a Validate or Clear
+// Buffer command is effective.
+//
+// Parameters:
+//     	buf		buffer to receive the contents of the setup packet. Must
+//     			be at least 8 bytes.
+// Returns:
+//     	true	if there are 8 bytes waiting in the EP0 OUT RAM buffer
+//     			on the D12 (i.e., true if successful)
+//     	false	otherwise
+
+static bool d12_read_setup_packet(d12_addr_type base_addr, byte *buf)
+{
+	uint8 n;
+
+	d12_select_endp(base_addr, D12_RX_CTRL_ENDP);
+
+	d12_read_byte(base_addr, CMD_READ_BUF);			// Read & discard reserved byte
+	n = d12_read_data_byte(base_addr);				// # bytes available
+
+	if (n > USB_SETUP_PACKET_LEN) {
+		//TRACE("* Warning: Setup Packet too large: %u *\n", (unsigned) n);
+		n = USB_SETUP_PACKET_LEN;
+	}
+
+	n = d12_read_data(base_addr, buf, n);
+
+	d12_ack_setup(base_addr);
+	d12_clear_buffer(base_addr);
+
+	// ----- Ack Setup to EP0 IN ------
+
+	d12_select_endp(base_addr, D12_TX_CTRL_ENDP);
+	d12_ack_setup(base_addr);
+
+	return n == USB_SETUP_PACKET_LEN;
+}
+
+// ------------------------------------------------------------------------
+// Reads the contents of the currently selected endpoint's RAM buffer into 
+// the buf[] array.
+//
+// The D12's buffer comes in as follows:
+//		[0]	junk ("reserved" - can be anything). Just disregard
+//		[1]	# data bytes to follow
+//	   	[2] data byte 0, ...
+//	up to
+//		[N+2] data byte N-1
+//
+// Parameters:
+//     	buf		byte array to receive data. This MUST be at least the size
+//     			of the chip's RAM buffer for the currently selected endpoint.
+//     			If buf is NULL, the data is read & discarded.
+//
+// Returns: the actual number of bytes read (could be <= n)
+
+static uint8 d12_read_selected_endp_buf(d12_addr_type base_addr, byte *buf)
+{
+	uint8 n;
+
+	d12_read_byte(base_addr, CMD_READ_BUF);		// Read & discard reserved byte
+	n = d12_read_data_byte(base_addr);			// # bytes in chip's buf
+	d12_read_data(base_addr, buf, n);
+	d12_clear_buffer(base_addr);
+
+	return n;
+}
+
+// ------------------------------------------------------------------------
+// Selects the specified endpoint and reads the contents of it's RAM buffer
+// into the buf[] array. For the Main OUT endpoint, it will check whether 
+// both buffers are full, and if so, read them both.
+//
+// Side Effects:
+//		- Leaves endp_idx as the currently selected endpoint.
+//
+// Parameters:
+//    	endp_idx	the endpoint from which to read
+//     	buf			buffer to receive the data. This MUST be at least the size
+//					of the chip's RAM buffer for the specified endpoint.
+//       			For the Main endp, it must be 2x the buffer size (128 total)
+//
+// Returns: the # of bytes read.
+
+static uint8 d12_read_endp_buf(d12_addr_type base_addr, byte endp_idx, byte *buf)
+{
+	return (d12_select_endp(base_addr, endp_idx) & SEL_ENDP_FULL)
+						? d12_read_selected_endp_buf(base_addr, buf) : 0;
+}
+
+// ------------------------------------------------------------------------
+// Does a read of the "main" endpoint (#2). Since it's double buffered,
+// this will check if both buffers are full, and if so it will read them
+// both. Thus the caller's buffer, buf, must be large enough to hold all
+// the data - 128 bytes total.
+// 
+// If either buffer contains less than the full amount, the done flag
+// is set indicating that a Bulk OUT transfer is complete.
+// 
+// This determines if a bulk transfer is done, since the caller can't 
+// necessarily determine this from the size of the return buffer.
+// If either buffer is less than full, '*done' is set to a non-zero value.
+
+static uint8 d12_read_main_endp_buf(d12_addr_type base_addr, byte *buf, int *done)
+{
+	int		nBuf = 1;
+	uint8	n = 0;
+	byte	stat = d12_read_endp_status(base_addr, D12_RX_MAIN_ENDP) & 
+												D12_ENDP_STAT_ANY_BUF_FULL;
+
+	if (stat == 0)
+		return 0;
+
+	if (stat == D12_ENDP_STAT_BOTH_BUF_FULL)
+		nBuf++;
+
+	*done = false;
+
+	while (nBuf--) {
+		if (d12_select_endp(base_addr, D12_RX_MAIN_ENDP) & SEL_ENDP_FULL) {
+			uint8 n1 = d12_read_selected_endp_buf(base_addr, buf+n);
+			n += n1;
+			if (n1 < D12_MAIN_ENDP_SIZE) {
+				*done = true;
+				break;
+			}
+		}
+		else
+			*done = true;
+	}
+	return n;
+}
+
+// ------------------------------------------------------------------------
+// Writes the contents of the buf[] array to the currently selected 
+// endpoint's RAM buffer. The host will get the data on the on the next IN
+// packet from the endpoint.
+//
+// Note:
+//     	- The length of the buffer, n, must be no more than the size of the
+//     	endpoint's RAM space, though, currently, this is not checked.
+//     	- It's feasible that the application needs to send an empty (NULL) 
+//     	packet. It's valid for 'n' to be zero, and/or buf NULL.
+
+static uint8 d12_write_selected_endp_buf(d12_addr_type base_addr, const byte *buf, uint8 n)
+{
+	d12_write_byte(base_addr, CMD_WRITE_BUF, 0);
+	d12_write_data_byte(base_addr, n);
+	d12_write_data(base_addr, buf, n);
+	d12_validate_buffer(base_addr);
+
+	return n;
+}
+
+// ------------------------------------------------------------------------
+// Writes the contents of the buf[] array to the specified endoint's RAM
+// buffer. The host will get this data on the next IN packet from the 
+// endpoint.
+//
+// Side Effects:
+//     	- Leaves endp_idx as the currently selected endpoint.
+
+static uint8 d12_write_endp_buf(d12_addr_type base_addr, byte endp_idx, 
+											const byte *buf, uint8 n)
+{
+	d12_select_endp(base_addr, endp_idx);
+	return d12_write_selected_endp_buf(base_addr, buf, n);
+}
+
+// ------------------------------------------------------------------------
+// Reads & returns the contents of the Chip ID register.
+
+static inline uint16 d12_read_chip_id(d12_addr_type base_addr)
+{
+	return d12_read_word(base_addr, CMD_READ_CHIP_ID);
+}
+
+
+// ==========================================================================
+//						eCos-Specific Device Driver Code
+// ==========================================================================
+
+static void usbs_d12_reset(void);
+
+// Make some abbreviations for the configuration options.
+
+#if defined(CYGPKG_DEVS_USB_D12_RX_EP1)
+	#define _RX_EP1
+#endif
+
+#if defined(CYGPKG_DEVS_USB_D12_TX_EP1)
+	#define _TX_EP1
+#endif
+
+#if defined(CYGPKG_DEVS_USB_D12_RX_EP2)
+	#define _RX_EP2
+#endif
+
+#if defined(CYGPKG_DEVS_USB_D12_TX_EP2)
+	#define _TX_EP2
+#endif
+
+// --------------------------------------------------------------------------
+//								Endpoint 0 Data
+// --------------------------------------------------------------------------
+
+static cyg_interrupt	usbs_d12_intr_data;
+static cyg_handle_t		usbs_d12_intr_handle;
+
+static byte ep0_tx_buffer[CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE];
+
+static void usbs_d12_start(usbs_control_endpoint*);
+static void usbs_d12_poll(usbs_control_endpoint*);
+
+typedef enum endp_state {
+	ENDP_STATE_IDLE,
+	ENDP_STATE_IN,
+	ENDP_STATE_OUT
+} endp_state;
+
+typedef struct ep0_impl {
+	usbs_control_endpoint	common;
+	endp_state				ep_state;
+	int                     length;
+	int                     transmitted;
+	bool					tx_empty;
+} ep0_impl;
+
+static ep0_impl ep0 = {
+	common:
+	{
+		state:                  USBS_STATE_POWERED,
+		enumeration_data:       (usbs_enumeration_data*) 0,
+		start_fn:               &usbs_d12_start,
+		poll_fn:                &usbs_d12_poll,
+		interrupt_vector:       CYGNUM_DEVS_USB_D12_IRQ,
+		control_buffer:         { 0, 0, 0, 0, 0, 0, 0, 0 },
+		state_change_fn:        0,
+		state_change_data:      0,
+		standard_control_fn:    0,
+		standard_control_data:  0,
+		class_control_fn:       0,
+		class_control_data:     0,
+		vendor_control_fn:      0,
+		vendor_control_data:    0,
+		reserved_control_fn:    0,
+		reserved_control_data:  0,
+		buffer:                 0,
+		buffer_size:            0,
+		fill_buffer_fn:         0,
+		fill_data:              0,
+		fill_index:             0,
+		complete_fn:            0
+	},
+	ep_state:		ENDP_STATE_IDLE,
+	length:			0,
+	transmitted:	0,
+	tx_empty:		0
+};
+
+extern usbs_control_endpoint usbs_d12_ep0 __attribute__((alias ("ep0")));
+
+
+// --------------------------------------------------------------------------
+//						Rx Endpoints 1 & 2 Data
+// --------------------------------------------------------------------------
+
+#if	defined(_RX_EP1) || defined(_RX_EP2)
+
+typedef struct rx_endpoint {
+	usbs_rx_endpoint	common;
+	int					endp,
+						received;
+}
+	rx_endpoint;
+
+static void usbs_d12_api_start_rx_ep(usbs_rx_endpoint*);
+static void usbs_d12_api_stall_rx_ep(usbs_rx_endpoint*, cyg_bool);
+
+static void usbs_d12_ep_rx_complete(rx_endpoint *ep, int result);
+static void usbs_d12_stall_rx_ep(rx_endpoint*, cyg_bool);
+
+#endif
+
+
+#if	defined(_RX_EP1)
+
+static rx_endpoint rx_ep1 = {
+	common: {
+		start_rx_fn:	&usbs_d12_api_start_rx_ep,
+		set_halted_fn:	&usbs_d12_api_stall_rx_ep,
+        halted:			0
+    },
+	endp:	1
+};
+
+extern usbs_rx_endpoint usbs_d12_rx_ep1 __attribute__((alias ("rx_ep1")));
+
+#endif
+
+
+#if	defined(_RX_EP2)
+
+static rx_endpoint rx_ep2 = {
+	common: {
+		start_rx_fn:	&usbs_d12_api_start_rx_ep,
+		set_halted_fn:	&usbs_d12_api_stall_rx_ep,
+		halted:			0
+    },
+	endp:	2
+};
+
+extern usbs_rx_endpoint usbs_d12_rx_ep2 __attribute__((alias ("rx_ep2")));
+
+#endif
+
+// --------------------------------------------------------------------------
+//						Tx Endpoints 1 & 2 Data
+// --------------------------------------------------------------------------
+
+#if	defined(_TX_EP1) || defined(_TX_EP2)
+
+typedef struct tx_endpoint {
+	usbs_tx_endpoint	common;
+	int					endp,
+						transmitted;
+	bool				tx_empty;
+}
+	tx_endpoint;
+
+static void usbs_d12_api_start_tx_ep(usbs_tx_endpoint*);
+static void usbs_d12_api_stall_tx_ep(usbs_tx_endpoint*, cyg_bool);
+
+static void usbs_d12_ep_tx_complete(tx_endpoint *ep, int result);
+static void usbs_d12_stall_tx_ep(tx_endpoint*, cyg_bool);
+
+#endif
+
+
+#if	defined(_TX_EP1)
+
+static tx_endpoint tx_ep1 = {
+	common: {
+		start_tx_fn:	&usbs_d12_api_start_tx_ep,
+		set_halted_fn:	&usbs_d12_api_stall_tx_ep,
+        halted:			0
+    },
+	endp:	1
+};
+
+extern usbs_tx_endpoint usbs_d12_tx_ep1 __attribute__((alias ("tx_ep1")));
+
+#endif
+
+
+#if	defined(_TX_EP2)
+
+static tx_endpoint tx_ep2 = {
+	common: {
+		start_tx_fn:	&usbs_d12_api_start_tx_ep,
+		set_halted_fn:	&usbs_d12_api_stall_tx_ep,
+		halted:			0
+    },
+	endp:	2
+};
+
+extern usbs_tx_endpoint usbs_d12_tx_ep2 __attribute__((alias ("tx_ep2")));
+
+
+#endif
+
+// --------------------------------------------------------------------------
+// Synchronization
+
+#ifdef CYGPKG_DEVS_USB_D12_THREAD
+
+static cyg_mutex_t usbs_d12_mutex;
+
+//static inline void usbs_d12_lock(void)		{ cyg_mutex_lock(&usbs_d12_mutex); }
+//static inline void usbs_d12_unlock(void)	{ cyg_mutex_unlock(&usbs_d12_mutex); }
+
+static inline void usbs_d12_lock(void)		{ cyg_scheduler_lock(); }
+static inline void usbs_d12_unlock(void)	{ cyg_scheduler_unlock(); }
+
+#else
+
+static inline void usbs_d12_lock(void)		{ cyg_scheduler_lock(); }
+static inline void usbs_d12_unlock(void)	{ cyg_scheduler_unlock(); }
+
+#endif
+
+// --------------------------------------------------------------------------
+//								Control Endpoint
+// --------------------------------------------------------------------------
+
+// Fills the EP0 transmit buffer with a packet. Partial data packets are 
+// retrieved by repeatedly calling the fill function.
+
+static int ep0_fill_tx_buffer(void)
+{
+    int nFilled = 0;
+
+	while (nFilled < CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE) {
+        if (ep0.common.buffer_size != 0) {
+            if ((nFilled + ep0.common.buffer_size) < 
+						CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE) {
+                memcpy(&ep0_tx_buffer[nFilled], ep0.common.buffer, 
+                			ep0.common.buffer_size);
+                nFilled += ep0.common.buffer_size;
+                ep0.common.buffer_size = 0;
+            }
+            else {
+                break;
+            }
+        }
+        else if (ep0.common.fill_buffer_fn) {
+            (*ep0.common.fill_buffer_fn)(&ep0.common);
+        }
+        else {
+            break;
+        }
+    }
+    CYG_ASSERT((ep0.common.buffer_size == 0) && (!ep0.common.fill_buffer_fn), 
+    					"EP0 transmit buffer overflow");
+	TRACE_D12("EP0: Filled Tx Buf with %d bytes\n", nFilled);
+
+	ep0.length = nFilled;
+
+	ep0.common.fill_buffer_fn	= 0;
+	ep0.common.fill_data		= 0;
+	ep0.common.fill_index		= 0;
+
+    return nFilled;
+}
+
+// --------------------------------------------------------------------------
+// Called when a transfer is complete on the control endpoint EP0. 
+// It resets the endpoint's data structure and calls the completion function,
+// if any.
+//
+// PARAMETERS:
+//		result		0, on success
+//					-EPIPE or -EIO to indicate a cancellation
+
+static usbs_control_return usbs_d12_ep0_complete(int result)
+{
+	usbs_control_return ret = USBS_CONTROL_RETURN_UNKNOWN;
+
+    ep0.ep_state = ENDP_STATE_IDLE;
+
+	if (ep0.common.complete_fn)
+		ret = (*ep0.common.complete_fn)(&ep0.common, result);
+
+	ep0.common.buffer			= 0;
+	ep0.common.buffer_size		= 0;
+	ep0.common.complete_fn		= 0;
+	//ep0.common.fill_buffer_fn	= 0;
+
+	return ret;
+}
+
+// --------------------------------------------------------------------------
+// This routine is called when we want to send the next packet to the tx ep0
+// on the chip. It is used to start a new transfer, and is also called when
+// the chip interrupts to indicate that the ep0 tx buffer is empty and ready
+// to receive a new packet.
+//
+// NOTE:
+//		On the D12, when you send a zero-length packet to a tx endpoint, the
+// 		chip transmits the empty packet to the host, but doesn't interrupt 
+//		indicating that it is complete. So immediately after sending the
+//		empty packet we complete the transfer.
+
+static void usbs_d12_ep0_tx(void)
+{
+	int		nRemaining = ep0.length - ep0.transmitted;
+	uint8	n;
+
+	// ----- Intermittent interrupt? Get out -----
+
+	if (!ep0.common.buffer) {
+		TRACE_D12("EP0: Tx no buffer (%d)\n", nRemaining);
+		return;
+	}
+
+	// ----- If prev packet was last, signal that we're done -----
+
+	if (nRemaining == 0 && !ep0.tx_empty) {
+		TRACE_D12("\tEP0: Tx Complete (%d) %p\n", ep0.transmitted, 
+											ep0.common.complete_fn);
+		usbs_d12_ep0_complete(0);
+		return;
+	}
+
+	// ----- Load the next tx packet onto the chip -----
+
+	if (nRemaining < D12_ENDP0_SIZE) {
+		n = (uint8) nRemaining;
+		ep0.tx_empty = false;
+	}
+	else
+		n = D12_ENDP0_SIZE;
+
+	d12_write_endp_buf(D12_BASE_ADDR, D12_TX_ENDP0, 
+					   &ep0_tx_buffer[ep0.transmitted], n);
+
+	TRACE_D12("EP0: Wrote %u bytes\n", (unsigned) n);
+	TRACE_BUF0("\t", &ep0_tx_buffer[ep0.transmitted], n);
+
+	ep0.transmitted += n;
+
+	// ----- If empty packet, D12 won't interrupt, so end now ----- */
+
+	if (n == 0) {
+		TRACE_D12("\tEP0: Tx Complete (%d) %p\n", ep0.transmitted, 
+											ep0.common.complete_fn);
+		usbs_d12_ep0_complete(0);
+	}
+}
+
+// --------------------------------------------------------------------------
+// This function is called when a packet has been successfully sent on the
+// primary control endpoint (ep0). It indicates that the chip is ready for 
+// another packet. We read the LastTransStatus for the endpoint to clear 
+// the interrupt bit, then call ep0_tx() to continue the transfer.
+
+static void usbs_d12_ep0_tx_intr(void)
+{
+	d12_read_last_trans_status(D12_BASE_ADDR, D12_TX_ENDP0);
+	usbs_d12_ep0_tx();
+}
+
+// --------------------------------------------------------------------------
+// Try to handle standard requests. This is a three step process:
+//	1.	If it's something we should handle internally we take care of it.
+//		Currently we can handle SET_ADDRESS requests, and a few others.
+//	2.	If the upper level code has installed a standard control handler
+//		we let that function have a crack at it.
+//	3.	If neither of those handle the packet we let 
+//		usbs_handle_standard_control() have a last try at it.
+//
+// Locally:
+// 		SET_ADDRESS: The host is demanding that we change our USB address.
+//		This is done by updating the Address/Enable register on the D12. 
+//		Note, however that the USB protocol requires us to ack at the old 
+//		address, change address, and then accept the next control message
+//		at the new 	address. The D12 address reg is buffered to do this 
+//		automatically for us. The updated address on the chip won't take
+//		affect until after the empty ack is sent. Nice.
+//
+
+static usbs_control_return usbs_d12_handle_std_req(usb_devreq *req)
+{
+	usbs_control_return result = USBS_CONTROL_RETURN_UNKNOWN;
+	int recipient = req->type & USB_DEVREQ_RECIPIENT_MASK;
+
+	if (req->request == USB_DEVREQ_SET_ADDRESS) {
+		TRACE_D12("Setting Addr: %u\n", (unsigned) req->value_lo);
+		d12_set_addr_enable(D12_BASE_ADDR, req->value_lo, true);
+		result = USBS_CONTROL_RETURN_HANDLED;
+	}
+	else if (req->request == USB_DEVREQ_GET_STATUS) {
+		if (recipient == USB_DEVREQ_RECIPIENT_DEVICE) {
+			const usbs_enumeration_data *enum_data = ep0.common.enumeration_data;
+			if (enum_data && enum_data->device.number_configurations == 1 &&
+					enum_data->configurations) {
+				ep0.common.control_buffer[0]  = (enum_data->configurations[0].attributes
+							 & USB_CONFIGURATION_DESCRIPTOR_ATTR_SELF_POWERED) ? 1 : 0;
+				ep0.common.control_buffer[0] |= (enum_data->configurations[0].attributes
+							& USB_CONFIGURATION_DESCRIPTOR_ATTR_REMOTE_WAKEUP) ? 2 : 0;
+				ep0.common.control_buffer[1] = 0;
+				result = USBS_CONTROL_RETURN_HANDLED;
+			}
+		}
+		else if (recipient == USB_DEVREQ_RECIPIENT_ENDPOINT) {
+    		bool halted = false;
+			result = USBS_CONTROL_RETURN_HANDLED;
+
+			switch (req->index_lo) {
+    			#if defined(_RX_EP1)
+    				case 0x01 :	halted = rx_ep1.common.halted;  break;
+    			#endif
+    			#if defined(_TX_EP1)
+    				case 0x81 : halted = tx_ep1.common.halted;	break;
+    			#endif
+    			#if defined(_RX_EP2)
+    				case 0x02 :	halted = rx_ep2.common.halted;  break;
+    			#endif
+    			#if defined(_TX_EP2)
+    				case 0x82 : halted = tx_ep2.common.halted;	break;
+    			#endif
+
+    			default:
+    				result = USBS_CONTROL_RETURN_STALL;
+    		}
+
+    		TRACE_D12("Get Status: Endp [0x%02X] %s\n", (unsigned) req->index_lo, 
+    												halted ? "Halt" : "Unhalt");
+			if (result == USBS_CONTROL_RETURN_HANDLED) {
+				ep0.common.control_buffer[0] = (halted) ? 1 : 0;
+				ep0.common.control_buffer[1] = 0;
+			}
+		}
+
+		if (result == USBS_CONTROL_RETURN_HANDLED) {
+			ep0.common.buffer				= ep0.common.control_buffer;
+			ep0.common.buffer_size			= 2;
+			ep0.common.fill_buffer_fn		= 0;
+			ep0.common.complete_fn			= 0;
+		}
+	}
+	else if ((req->request == USB_DEVREQ_SET_FEATURE || 
+			  req->request == USB_DEVREQ_CLEAR_FEATURE) && 
+			  recipient == USB_DEVREQ_RECIPIENT_ENDPOINT) {
+
+		bool halt = (req->request == USB_DEVREQ_SET_FEATURE);
+		result = USBS_CONTROL_RETURN_HANDLED;
+		TRACE_D12("Endpoint [0x%02X] %s\n", (unsigned) req->index_lo, halt ? "Halt" : "Unhalt");
+
+		switch (req->index_lo) {
+			#if defined(_RX_EP1)
+				case 0x01 :	usbs_d12_stall_rx_ep(&rx_ep1, halt);	break;
+			#endif
+			#if defined(_TX_EP1)
+				case 0x81 : usbs_d12_stall_tx_ep(&tx_ep1, halt);	break;
+			#endif
+			#if defined(_RX_EP2)
+				case 0x02 :	usbs_d12_stall_rx_ep(&rx_ep2, halt);	break;
+			#endif
+			#if defined(_TX_EP2)
+				case 0x82 : usbs_d12_stall_tx_ep(&tx_ep2, halt);	break;
+			#endif
+
+			default:
+				result = USBS_CONTROL_RETURN_STALL;
+		}
+
+	}
+	else if (ep0.common.standard_control_fn != 0) {
+		result = (*ep0.common.standard_control_fn)(&ep0.common,
+  									ep0.common.standard_control_data);
+	}
+
+	if (result == USBS_CONTROL_RETURN_UNKNOWN)
+		result = usbs_handle_standard_control(&ep0.common);
+
+	return result;
+}
+
+// --------------------------------------------------------------------------
+// Handler for the receipt of a setup (dev request) packet from the host.
+// We examine the packet to determine what function(s) should get a crack
+// at trying to handle it, then pass control to the proper function. If
+// the function handles the message we either ACK (len==0) or prepare for
+// an IN or OUT data phase. If no one handled the message, we stall the
+// control endpoint.
+
+static void usbs_d12_ep0_setup_packet(usb_devreq* req)
+{
+	int		len, dir, 
+			protocol, recipient;
+
+	usbs_control_return	result = USBS_CONTROL_RETURN_UNKNOWN;
+
+	// ----- See who should take the request -----
+
+	len = make_word(req->length_hi, req->length_lo);
+
+	dir			= req->type & USB_DEVREQ_DIRECTION_MASK;
+	protocol    = req->type & USB_DEVREQ_TYPE_MASK;
+	recipient   = req->type & USB_DEVREQ_RECIPIENT_MASK;
+
+	TRACE_BUF0("DevReq: ", ep0.common.control_buffer, sizeof(usb_devreq));
+
+	if (protocol == USB_DEVREQ_TYPE_STANDARD)
+		result = usbs_d12_handle_std_req(req);
+	else {
+		// Pass on non-standard requests to registered handlers
+
+		usbs_control_return	(*callback_fn)(usbs_control_endpoint*, void*);
+		void *callback_arg;
+
+		if (protocol == USB_DEVREQ_TYPE_CLASS) {
+			callback_fn  = ep0.common.class_control_fn;
+			callback_arg = ep0.common.class_control_data;
+		}
+		else if (protocol == USB_DEVREQ_TYPE_VENDOR) {
+			callback_fn  = ep0.common.vendor_control_fn;
+			callback_arg = ep0.common.vendor_control_data;
+		}
+		else {
+			callback_fn  = ep0.common.reserved_control_fn;
+			callback_arg = ep0.common.reserved_control_data;
+		}
+
+		result = (callback_fn)	? (*callback_fn)(&ep0.common, callback_arg)
+								: USBS_CONTROL_RETURN_STALL;
+	}
+
+	// ----- If handled prep/handle data phase, otherwise stall -----
+
+	if (result == USBS_CONTROL_RETURN_HANDLED) {
+		if (len == 0) {
+			TRACE_D12("\tCtrl ACK\n");
+			d12_write_endp_buf(D12_BASE_ADDR, D12_TX_ENDP0, 0, 0);
+		}
+		else {
+			// Set EP0 state to  IN or OUT mode for data phase
+			ep0.transmitted = 0;
+			ep0.length = len;
+
+			if (dir == USB_DEVREQ_DIRECTION_OUT) {
+				// Wait for the next packet from the host.
+				ep0.ep_state = ENDP_STATE_OUT;
+				CYG_ASSERT(ep0.common.buffer != 0, 
+						   "A rx buffer should have been provided for EP0");
+				CYG_ASSERT(ep0.common.complete_fn != 0, 
+						   "A completion function should be provided for EP0 OUT control messages");
+			}
+			else {
+				ep0.tx_empty = true;
+				ep0.ep_state = ENDP_STATE_IN;
+				ep0_fill_tx_buffer();
+				usbs_d12_ep0_tx();
+			}
+		}
+	}
+	else {
+		TRACE_D12("\t*** Unhandled Device Request ***\n");
+		// The request wasn't handled, so stall control endpoint
+		d12_stall_ctrl_endp(D12_BASE_ADDR, true);
+	}
+}
+
+// --------------------------------------------------------------------------
+// This is called when the chip indicates that a packet has been received
+// on control endpoint 0. If it's a setup packet, we handle it accordingly,
+// otherwise it's a data packet coming in on ep0.
+//
+
+static void usbs_d12_ep0_rx_intr(void)
+{
+	byte byStat = d12_read_last_trans_status(D12_BASE_ADDR, D12_RX_ENDP0);
+	TRACE_D12("\tEP0 Status: 0x%02X\n", (unsigned) byStat);
+
+	if (byStat & D12_LAST_TRANS_SETUP_PACKET) {
+		usb_devreq *req = (usb_devreq *) ep0.common.control_buffer;
+		
+		if (!d12_read_setup_packet(D12_BASE_ADDR, (byte*) req)) {
+			TRACE_D12("ep0_rx_dsr: Error reading setup packet\n");
+			d12_stall_ctrl_endp(D12_BASE_ADDR, true);
+		}
+		else
+			usbs_d12_ep0_setup_packet(req);
+	}
+	else {
+		if (ep0.common.buffer) {
+			uint8 n = d12_read_endp_buf(D12_BASE_ADDR, D12_RX_ENDP0, 
+										ep0.common.buffer + ep0.transmitted);
+			ep0.transmitted += n;
+
+			TRACE_D12("EP0: Received %d bytes\n", (unsigned) n);
+
+			if (n < D12_ENDP0_SIZE || 
+					ep0.common.buffer_size - ep0.transmitted < D12_ENDP0_SIZE) {
+				TRACE_D12("\tEP0: Rx Complete (%d) %p\n", 
+						  ep0.transmitted, ep0.common.complete_fn);
+
+	            if (usbs_d12_ep0_complete(0) == USBS_CONTROL_RETURN_HANDLED)
+					d12_write_endp_buf(D12_BASE_ADDR, D12_TX_ENDP0, 0, 0);
+	            else
+					d12_stall_ctrl_endp(D12_BASE_ADDR, true);
+			}
+		}
+		else {
+			TRACE_D12("EP0: No Rx buffer. Discarding packet\n");
+			d12_read_endp_buf(D12_BASE_ADDR, D12_RX_ENDP0, NULL);
+		}
+	}
+}
+
+// --------------------------------------------------------------------------
+// Handler for when the device is put into or taken out of suspend mode.
+// It updates the state variable in the control endpoint and calls the
+// registered state change function, if any.
+
+// TODO: Put the chip into low power mode??? Stop clocks, etc???
+
+static void usbs_d12_suspend(bool suspended)
+{
+	int 				old_state = ep0.common.state;
+	usbs_state_change	state_change;
+
+	if (suspended) {
+		ep0.common.state |= USBS_STATE_SUSPENDED;
+		state_change = USBS_STATE_CHANGE_SUSPENDED;
+	}
+	else {
+		ep0.common.state &= USBS_STATE_MASK;
+		state_change = USBS_STATE_CHANGE_RESUMED;
+	}
+
+	if (ep0.common.state_change_fn) {
+		(*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data,
+										state_change, old_state);
+	}
+}
+
+// --------------------------------------------------------------------------
+//							Common Rx Endpoint 1 & 2
+// --------------------------------------------------------------------------
+
+#if defined(_RX_EP1) || defined(_RX_EP2)
+
+static void usbs_d12_clear_rx_ep(rx_endpoint *ep)
+{
+	ep->common.buffer			= 0;
+	ep->common.buffer_size		= 0;
+	ep->common.complete_fn		= 0;
+	ep->common.complete_data	= 0;
+
+	ep->received	= 0;
+}
+
+// --------------------------------------------------------------------------
+// This is called when an rx operation is completed. It resets the endpoint
+// vars and calls the registered completion function.
+//
+
+static void usbs_d12_ep_rx_complete(rx_endpoint *ep, int result)
+{
+	completion_fn fn = ep->common.complete_fn;
+	void *data = ep->common.complete_data;
+
+	usbs_d12_clear_rx_ep(ep);
+
+	if (fn)
+		(*fn)(data, result);
+}
+
+// --------------------------------------------------------------------------
+// This routine is called when an rx buffer in the chip is full and ready to
+// be read. If there's an endpoint buffer available and room to hold the data
+// we read it in, otherwise we call the completion function, but leave the 
+// data in the chip. The hardware will automatically NAK packages from the
+// host until the app calls another start read to continue receiving data.
+//
+// CONTEXT:
+//		Called from either the DSR or application thread, via start rx.
+//		In either case, it's assumed that the chip is locked.
+//		
+
+static void usbs_d12_ep_rx(rx_endpoint *ep)
+{
+	int		n, ep_size, buf_remaining,
+			endp = ep->endp;
+	bool	done;
+
+	// The main endp is double buffered and we need to be prepared
+	// to read both simultaneously.
+	ep_size = (endp == D12_MAIN_ENDP) ? (2 * D12_MAIN_ENDP_SIZE) 
+									  : RX_ENDP_SIZE[endp];
+
+	buf_remaining = ep->common.buffer_size - ep->received;
+
+	// ----- If no space left in buffer, call completion fn -----
+
+	if (!ep->common.buffer || buf_remaining < ep_size) {
+		int ret = ep->received;
+
+		// See if caller requested a read smaller than the endp. Read & throw away extra
+		if (ep->common.buffer_size < ep_size) {
+			byte tmp_buf[D12_MAX_PACKET_SIZE];
+
+			if (endp == D12_MAIN_ENDP)
+				n = d12_read_main_endp_buf(D12_BASE_ADDR, tmp_buf, &done);
+			else
+				n = d12_read_endp_buf(D12_BASE_ADDR, RX_ENDP_INDEX[endp], tmp_buf);
+
+			if (n > ep->common.buffer_size) {
+				n = ep->received = ep->common.buffer_size;
+				ret = -ENOMEM;
+				TRACE_D12("\tEP%d: *** Rx Buffer too small. Data Lost ***\n", endp);
+			}
+			else
+				ret = ep->received = n;
+
+			memcpy(ep->common.buffer, tmp_buf, n);
+			buf_remaining = ep->common.buffer_size - n;
+		}
+
+		TRACE_D12("\tEP%d: Rx Complete. Buffer (nearly) full. [%d]\n", endp, buf_remaining);
+		usbs_d12_ep_rx_complete(ep, ret);
+		return;
+	}
+
+	// ----- Read the data from the chip -----
+
+	if (endp == D12_MAIN_ENDP)
+		n = d12_read_main_endp_buf(D12_BASE_ADDR, 
+								   ep->common.buffer + ep->received, &done);
+	else {
+		n = d12_read_endp_buf(D12_BASE_ADDR, RX_ENDP_INDEX[endp],
+							  ep->common.buffer + ep->received);
+		done = (n < RX_ENDP_SIZE[endp]);
+	}
+
+	ep->received += n;
+	buf_remaining = ep->common.buffer_size - ep->received;
+
+	done = done || (buf_remaining < ep_size);
+
+	TRACE_D12("EP%d: Received %d bytes.\n", endp, n);
+	TRACE_BUF("\t", ep->common.buffer + ep->received-n, n);
+
+	// ----- If we're done, complete the receive -----
+
+	if (done) {
+		TRACE_D12("\tEP%d Rx Complete (%d)  %p\n", endp, 
+				  ep->received, ep->common.complete_fn);
+		usbs_d12_ep_rx_complete(ep, ep->received);
+	}
+}
+
+// --------------------------------------------------------------------------
+// Stalls/unstalls the specified endpoint.
+
+static void usbs_d12_stall_rx_ep(rx_endpoint *ep, cyg_bool halt)
+{
+	ep->common.halted = halt;
+	d12_stall_endp(D12_BASE_ADDR, RX_ENDP_INDEX[ep->endp], halt);
+}
+
+// --------------------------------------------------------------------------
+// Handler for an Rx endpoint full interrupt. It clears the interrupt on the
+// D12 by reading the endpoint's status register then calls the routine to
+// read the data into the buffer.
+//
+// Called from the DSR context only.
+//
+
+static void usbs_d12_ep_rx_intr(rx_endpoint *ep)
+{
+	d12_read_last_trans_status(D12_BASE_ADDR, RX_ENDP_INDEX[ep->endp]);
+	usbs_d12_ep_rx(ep);
+}
+
+#endif
+
+// --------------------------------------------------------------------------
+//							Common Tx Endpoint 1 & 2
+// --------------------------------------------------------------------------
+
+#if defined(_TX_EP1) || defined(_TX_EP2)
+
+// Clears out the endpoint data structure before/after a tx is complete.
+
+static void usbs_d12_clear_tx_ep(tx_endpoint *ep)
+{
+	ep->common.buffer = 0;
+	ep->common.buffer_size = 0;
+	ep->common.complete_fn = 0;
+	ep->common.complete_data = 0;
+
+	ep->transmitted = 0;
+	ep->tx_empty = false;
+}
+
+// --------------------------------------------------------------------------
+// This is called when a transmit is completed. It resets the endpoint vars
+// and calls the registered completion function, if any.
+//
+// CONTEXT:
+//		Called from either the DSR or the app thread that started tx. 
+
+static void usbs_d12_ep_tx_complete(tx_endpoint *ep, int result)
+{
+	completion_fn fn = ep->common.complete_fn;
+	void *data = ep->common.complete_data;
+
+	usbs_d12_clear_tx_ep(ep);
+
+	if (fn)
+		(*fn)(data, result);
+}
+
+// --------------------------------------------------------------------------
+// The routine writes data to the chip and updates the endpoint's counters. 
+// It gets called at the start of a transfer operation to prime the device
+// and then gets called each time the chip finishes sending a packet to the
+// host and is ready for more data. If the amount of data remaining is 
+// smaller than can fit in the chip's endpoint buffer, then this is the last
+// packet to send, so we call the completion function.
+//
+// CONTEXT:
+//		Called from either the DSR or the app thread that started the tx
+//		In either case, it's assumed the chip is locked.
+
+static void usbs_d12_ep_tx(tx_endpoint *ep)
+{
+	int n, nRemaining;
+
+	// ----- Already done. Intermittent interrupt, so get out -----
+
+	if (!ep->common.buffer)
+		return;
+
+	// ----- See how many bytes remaining in buffer -----
+
+	nRemaining = ep->common.buffer_size - ep->transmitted;
+
+	TRACE_D12("EP%d: Tx %p, %d Done, %d Remaining\n", ep->endp, 
+			  ep->common.buffer, ep->transmitted, nRemaining);
+
+	// ----- If prev packet was last, signal that we're done -----
+
+	if (nRemaining == 0 && !ep->tx_empty) {
+		TRACE_D12("\tEP%d: Tx complete (%d)  %p\n", ep->endp, 
+				  ep->transmitted, ep->common.complete_fn);
+		usbs_d12_ep_tx_complete(ep, ep->transmitted);
+		return;
+	}
+
+	// ----- Write the next packet to chip -----
+
+	if (nRemaining < TX_ENDP_SIZE[ep->endp]) {
+		n = nRemaining;
+		ep->tx_empty = false;
+	}
+	else
+		n = TX_ENDP_SIZE[ep->endp];
+
+	TRACE_D12("EP%d: Writing %d bytes. %s\n", ep->endp, 
+											n, (n == 0) ? "DONE" : "");
+	TRACE_BUF("\t", ep->common.buffer + ep->transmitted, n);
+
+	d12_write_endp_buf(D12_BASE_ADDR, TX_ENDP_INDEX[ep->endp], 
+						ep->common.buffer + ep->transmitted, (uint8) n);
+
+	ep->transmitted += n;
+
+	// ----- If empty packet, complete now -----
+
+	if (n == 0) {
+		TRACE_D12("\tEP%d: Tx complete (%d)  %p\n", ep->endp, 
+				  ep->transmitted, ep->common.complete_fn);
+		usbs_d12_ep_tx_complete(ep, ep->transmitted);
+		return;
+	}
+}
+
+// --------------------------------------------------------------------------
+// Stalls/unstalls the specified tx endpoint.
+
+static void usbs_d12_stall_tx_ep(tx_endpoint *ep, cyg_bool halt)
+{
+	ep->common.halted = halt;
+	d12_stall_endp(D12_BASE_ADDR, TX_ENDP_INDEX[ep->endp], halt);
+}
+
+// --------------------------------------------------------------------------
+// Handler for when the chip's tx RAM for an endoint has just been emptied 
+// (sent to the host) and the chip is ready for more data.
+// We read the endpoint's last trans status register to clear the interrupt
+// on the D12, then call the tx function to send the next packet or 
+// complete the transfer.
+
+static void usbs_d12_ep_tx_intr(tx_endpoint *ep)
+{
+	d12_read_last_trans_status(D12_BASE_ADDR, TX_ENDP_INDEX[ep->endp]);
+	usbs_d12_ep_tx(ep);
+}
+
+#endif		// defined(_TX_EP1) || defined(_TX_EP2)
+
+// --------------------------------------------------------------------------
+//					Application Program Interface (API)
+// --------------------------------------------------------------------------
+
+#if defined(_RX_EP1) || defined(_RX_EP2)
+
+// Starts a receive operation on the specified endpoint. If the buffer size
+// is zero the completion function is called immediately. The routine checks
+// if tehre is data in the chip's endpoint buffer, and if so it will call
+// ep_rx() to start reading the data out of the chip.
+//
+// If the endpoint is currently stalled, a read size of zero can be used to 
+// block the calling thread until the stall is cleared. If the read size is
+// non-zero and the endpoint is stalled the completion function is called
+// immediately with an error result.
+
+static void usbs_d12_api_start_rx_ep(usbs_rx_endpoint *ep)
+{
+	rx_endpoint *epx = (rx_endpoint *) ep;
+
+	if (ep->halted) {
+		if (ep->buffer_size != 0)
+			usbs_d12_ep_rx_complete(epx, -EAGAIN);
+	}
+	else if (ep->buffer_size == 0) {
+		usbs_d12_ep_rx_complete(epx, 0);
+	}
+	else {
+		TRACE_D12("EP%d: Starting Rx, %p, %d\n", epx->endp, ep->buffer,
+													ep->buffer_size);
+		usbs_d12_lock();
+
+		epx->received = 0;
+		if (d12_data_available(D12_BASE_ADDR, RX_ENDP_INDEX[epx->endp]))
+			usbs_d12_ep_rx(epx);
+
+		usbs_d12_unlock();
+	}
+}
+
+// --------------------------------------------------------------------------
+// Halts/unhalts one of the generic rx (OUT) endpoints.
+//
+
+static void usbs_d12_api_stall_rx_ep(usbs_rx_endpoint *ep, cyg_bool halt)
+{
+	usbs_d12_lock();
+	usbs_d12_stall_rx_ep((rx_endpoint*) ep, halt);
+	usbs_d12_unlock();
+}
+
+#endif		// defined(_RX_EP1) || defined(_RX_EP2)
+
+// --------------------------------------------------------------------------
+//									Tx API
+// --------------------------------------------------------------------------
+
+#if defined(_TX_EP1) || defined(_TX_EP2)
+
+// This starts a transmit on one of the data endpoints. If the endpoint is
+// stalled a buffer size of zero can be used to block until the stall is
+// cleared. Any other size on a stalled endpoint will result in an error
+// callback immediately. The first packet is sent to the chip immediately,
+// in the application context. If the chip's buffer can contain the whole
+// transfer, the completion function will be called immediately, again,
+// still in the application context.
+//
+// If an empty packet is requested we send one from here and call the 
+// completion function. This should not cause an intr on the D12.
+//
+// CONTEXT:
+//		Called from an application thread
+
+static void usbs_d12_api_start_tx_ep(usbs_tx_endpoint *ep)
+{
+	tx_endpoint *epx = (tx_endpoint*) ep;
+
+	if (ep->halted) {
+		if (ep->buffer_size != 0) 
+			usbs_d12_ep_tx_complete(epx, -EAGAIN);
+	}
+	else if (ep->buffer_size == 0) {
+		usbs_d12_lock();
+
+		d12_write_endp_buf(D12_BASE_ADDR, TX_ENDP_INDEX[epx->endp], 0, 0);
+		usbs_d12_ep_tx_complete(epx, 0);
+
+		usbs_d12_unlock();
+	}
+	else {
+		TRACE_D12("EP%d: Starting Tx, %p, %d\n", epx->endp, ep->buffer,
+													ep->buffer_size);
+		usbs_d12_lock();
+
+		epx->tx_empty = true;
+		epx->transmitted = 0;
+		usbs_d12_ep_tx(epx);
+
+		usbs_d12_unlock();
+	}
+}
+
+// --------------------------------------------------------------------------
+// Halts/unhalts one of the generic endpoints.
+
+static void usbs_d12_api_stall_tx_ep(usbs_tx_endpoint *ep, cyg_bool halt)
+{
+	usbs_d12_lock();
+	usbs_d12_stall_tx_ep((tx_endpoint*) ep, halt);
+	usbs_d12_unlock();
+}
+
+#endif		// defined(_TX_ENDP1) || defined(_TX_EP2)
+
+// --------------------------------------------------------------------------
+//									DSR
+// --------------------------------------------------------------------------
+
+// The DSR for the D12 chip. This is normally called in the DSR context when
+// the D12 has raised its interrupt flag indicating that it needs to be 
+// serviced. The interrupt register contains bit flags that are OR'ed togther
+// indicating what items need to be serviced. There are flags for the 
+// following:
+//		- The endpoints (one bit for each)
+//		- Bus Reset
+//		- Suspend Change
+//		- DMA (terminal count)
+//
+// Care must be taken in that the D12's interrupt output is level-sensitive
+// (in that the interrupt sources are OR'ed together and not all cleared 
+// atomically in a single operation). Platforms (such as the PC) may be 
+// expecting edge-triggered interrupts, so we must work around that.
+// So, we loop on the interrupt register. Even though, in each loop, we
+// perform all of the required operations to clear the interrupts, a new
+// one may have arrived before we finished clearing the previous ones.
+// So we read the intr reg again. Once the intr reg gives a zero reading
+// we know that the D12 has dropped its IRQ line.
+//
+// Note, if we're configured to use a thread, this routine is called from
+// within a thread context (not a DSR context).
+//
+
+static void usbs_d12_dsr(cyg_vector_t vector, cyg_ucount32 count, 
+						 cyg_addrword_t data)
+{
+	uint16	status;
+	bool	suspended;
+
+	CYG_ASSERT(vector == CYGNUM_DEVS_USB_D12_INT,
+					"DSR should only be invoked for D12 interrupts");
+
+	while ((status = d12_read_intr_reg(D12_BASE_ADDR)) != 0) {
+		TRACE_D12("Intr Status: 0x%04X\n", (unsigned) status);
+
+		if (status & D12_INTR_BUS_RESET) {
+			TRACE_D12("\n>>> Bus Reset <<<\n");
+			usbs_d12_reset();
+		}
+		else {
+
+			// ----- Suspend Change -----
+
+			suspended = (bool) (ep0.common.state & USBS_STATE_SUSPENDED);
+
+			if (status & D12_INTR_SUSPEND_CHANGE) {
+				if (!suspended && (status & ~D12_INTR_SUSPEND_CHANGE) == 0)
+					usbs_d12_suspend(true);
+			}
+			else if (suspended)
+				usbs_d12_suspend(false);
+
+			// ----- Bulk Endpoints -----
+
+			#ifdef _TX_EP2
+				if (status & D12_INTR_TX_ENDP2)
+					usbs_d12_ep_tx_intr(&tx_ep2);
+			#endif
+
+			#ifdef _RX_EP2
+				if (status & D12_INTR_RX_ENDP2)
+					usbs_d12_ep_rx_intr(&rx_ep2);
+			#endif
+
+			// ----- Interrupt Endpoints -----
+
+			#ifdef _TX_EP1
+				if (status & D12_INTR_TX_ENDP1)
+					usbs_d12_ep_tx_intr(&tx_ep1);
+			#endif
+
+			#ifdef _RX_EP1
+				if (status & D12_INTR_RX_ENDP1)
+					usbs_d12_ep_rx_intr(&rx_ep1);
+			#endif
+
+			// ----- Control Endpoint -----
+
+			if (status & D12_INTR_TX_CTRL_ENDP)
+				usbs_d12_ep0_tx_intr();
+
+			if (status & D12_INTR_RX_CTRL_ENDP)
+				usbs_d12_ep0_rx_intr();
+		}
+	}
+
+	cyg_drv_interrupt_unmask(vector);
+}
+
+// --------------------------------------------------------------------------
+//								Interrupt
+// --------------------------------------------------------------------------
+
+// Here, the ISR does nothing but schedule the DSR to run. The ISR's/DSR's
+// are serialized. The CPU won't process another ISR until after the DSR
+// completes.
+
+static uint32 usbs_d12_isr(cyg_vector_t vector, cyg_addrword_t data)
+{
+	CYG_ASSERT(CYGNUM_DEVS_USB_D12_INT == vector, "usbs_isr: Incorrect interrupt");
+
+	// Prevent another interrupt until DSR completes
+	cyg_drv_interrupt_mask(vector);
+	cyg_drv_interrupt_acknowledge(vector);
+
+	return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR;
+}
+
+// --------------------------------------------------------------------------
+//								Polling
+// --------------------------------------------------------------------------
+
+static void usbs_d12_poll(usbs_control_endpoint *endp)
+{
+	CYG_ASSERT(endp == &ep0.common, "usbs_poll: wrong endpoint");
+
+	usbs_d12_lock();
+	usbs_d12_dsr(CYGNUM_DEVS_USB_D12_INT, 0, 0);
+	usbs_d12_unlock();
+}
+
+// --------------------------------------------------------------------------
+//								Thread Processing
+// --------------------------------------------------------------------------
+
+// The user can opt to configure the driver to service the D12 using a high
+// priority thread. The thread's priority MUST be greater than the priority
+// of any application thread making calls into the driver.
+// When we use a thread, the DSR simply signals a semaphore tio wake the
+// thread up. The thread, in turn, calls the the routine to service the D12,
+// now in a thread context. This allows for greater debug options, including
+// tracing.
+
+#ifdef CYGPKG_DEVS_USB_D12_THREAD
+
+static byte			usbs_d12_thread_stack[CYGNUM_DEVS_USB_D12_THREAD_STACK_SIZE];
+static cyg_thread	usbs_d12_thread;
+static cyg_handle_t	usbs_d12_thread_handle;
+static cyg_sem_t	usbs_d12_sem;
+
+
+static void usbs_d12_thread_dsr(cyg_vector_t vector, cyg_ucount32 count,
+														cyg_addrword_t data)
+{
+	cyg_semaphore_post(&usbs_d12_sem);
+	
+	CYG_UNUSED_PARAM(cyg_vector_t, vector);
+	CYG_UNUSED_PARAM(cyg_ucount32, count);
+	CYG_UNUSED_PARAM(cyg_addrword_t, data);
+}
+
+
+static void usbs_d12_thread_fn(cyg_addrword_t param)
+{
+	while (1) {
+		cyg_semaphore_wait(&usbs_d12_sem);
+		usbs_d12_poll(&ep0.common);
+	}
+
+	CYG_UNUSED_PARAM(cyg_addrword_t, param);
+}
+
+
+static void usbs_d12_thread_init(void)
+{
+	cyg_mutex_init(&usbs_d12_mutex);
+	cyg_semaphore_init(&usbs_d12_sem, 0);
+
+	cyg_thread_create(CYGNUM_DEVS_USB_D12_THREAD_PRIORITY,
+					  &usbs_d12_thread_fn, 0, "D12 USB Driver Thread",
+					  usbs_d12_thread_stack, CYGNUM_DEVS_USB_D12_THREAD_STACK_SIZE,
+					  &usbs_d12_thread_handle, &usbs_d12_thread);
+	cyg_thread_resume(usbs_d12_thread_handle);
+}
+
+#endif	// CYGPKG_DEVS_USB_D12_THREAD
+
+// --------------------------------------------------------------------------
+//								Start/Reset
+// --------------------------------------------------------------------------
+
+// Chip initialization and handler for a USB Bus Reset. This gets called at
+// system startup and after a USB Bus Reset. It puts the chip into the
+// default state, with USB Address 0, connected to the bus (i.e. 
+// "SoftConnect" asserted). Interrupts to the main endpoint  are turned on
+// via the DMA register. 
+
+static void usbs_d12_reset(void)
+{
+	int old_state = ep0.common.state;
+	ep0.common.state = USBS_STATE_DEFAULT;
+
+	if (ep0.common.state_change_fn) {
+        (*ep0.common.state_change_fn)(&ep0.common, ep0.common.state_change_data,
+									  USBS_STATE_CHANGE_RESET, old_state);
+	}
+
+	d12_set_addr_enable(D12_BASE_ADDR, 0, true);
+	d12_set_endp_enable(D12_BASE_ADDR, true);
+	d12_set_dma(D12_BASE_ADDR, D12_DMA_MAIN_ENDP_INTR_ENABLE);
+	d12_set_mode(D12_BASE_ADDR, D12_MODE_CFG_DFLT | D12_MODE_CFG_SOFT_CONNECT,
+				 D12_MODE_CLK_DFLT);
+
+	// If any endpoints were going, signal the end of transfers
+
+	#if defined(_TX_EP2)
+		usbs_d12_ep_tx_complete(&tx_ep2, -EPIPE);
+	#endif
+	
+	#if defined(_RX_EP2)
+		usbs_d12_ep_rx_complete(&rx_ep2, -EPIPE);
+	#endif
+
+	#if defined(_TX_EP1)
+		usbs_d12_ep_tx_complete(&tx_ep1, -EPIPE);
+	#endif
+	
+	#if defined(_RX_EP1)
+		usbs_d12_ep_rx_complete(&rx_ep1, -EPIPE);
+	#endif
+}
+
+// --------------------------------------------------------------------------
+// The start function is called indirectly by the application when 
+// initialization is complete. By this time, the enumeration data has been
+// assigned to the control endpoint and we're ready to connect to the host.
+// Within the reset function the D12's SoftConnect line is asserted which
+// allows the host (hub) to see us on the USB bus. If connected, the host 
+// should start the enumeration process.
+//
+
+static void usbs_d12_start(usbs_control_endpoint *endpoint)
+{
+	#if defined(_TRACE) && !defined(_TRACE_STDOUT)
+		TRACE_OPEN(TRACE_SINK);
+	#endif
+
+	CYG_ASSERT(endpoint == &ep0.common, "ep0 start: wrong endpoint");
+	TRACE_D12("USBS D12: Starting.\n");
+
+	d12_clear_all_intr(D12_BASE_ADDR);
+	usbs_d12_reset();
+}	
+
+// --------------------------------------------------------------------------
+//								Initialization
+// --------------------------------------------------------------------------
+
+// This routine is called early in the program's startup, possibly before
+// main (from within a C++ object initialization). We want to put this chip
+// and driver in a neutral, but ready, state until the application gets 
+// control, initializes itself and calls the usb start function.
+//
+// The D12 has a "Soft Connect" feature to tristate the USB bus, making it
+// appear that the USB device is not connected to the bus. We initially
+// keep seperated from the bus to allow for initialization.
+
+void usbs_d12_init(void)
+{
+	cyg_DSR_t *pdsr;
+
+	d12_set_mode(D12_BASE_ADDR, D12_MODE_CFG_DFLT & ~D12_MODE_CFG_SOFT_CONNECT, 
+				 D12_MODE_CLK_DFLT);
+
+	d12_set_addr_enable(D12_BASE_ADDR, 0, false);
+	d12_set_endp_enable(D12_BASE_ADDR, false);
+
+	// ----- Clear the endpoints -----
+
+	#if defined(_TX_EP2)
+		usbs_d12_clear_tx_ep(&tx_ep2);
+	#endif
+	
+	#if defined(_RX_EP2)
+		usbs_d12_clear_rx_ep(&rx_ep2);
+	#endif
+
+	#if defined(_TX_EP1)
+		usbs_d12_clear_tx_ep(&tx_ep1);
+	#endif
+	
+	#if defined(_RX_EP1)
+		usbs_d12_clear_rx_ep(&rx_ep1);
+	#endif
+
+	// ----- Start the thread (if we're using it) -----
+
+	#ifdef CYGPKG_DEVS_USB_D12_THREAD
+		usbs_d12_thread_init();
+		pdsr = &usbs_d12_thread_dsr;
+	#else
+		pdsr = &usbs_d12_dsr;
+	#endif
+
+	// ----- Attach the ISR -----
+
+	cyg_drv_interrupt_create(CYGNUM_DEVS_USB_D12_INT, 
+							 0, 0, &usbs_d12_isr, pdsr,
+							 &usbs_d12_intr_handle, &usbs_d12_intr_data);
+
+	cyg_drv_interrupt_attach(usbs_d12_intr_handle);
+	cyg_drv_interrupt_unmask(CYGNUM_DEVS_USB_D12_INT);
+}
+
+// ----------------------------------------------------------------------------
+// Testing support.
+
+usbs_testing_endpoint usbs_testing_endpoints[] = {
+    {
+        endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL, 
+        endpoint_number     : 0,
+        endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN,
+        endpoint            : (void*) &ep0.common,
+#ifdef CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY
+        devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "0c",
+#else        
+        devtab_entry        : (const char*) 0,
+#endif        
+        min_size            : 1,
+        max_size            : CYGNUM_DEVS_USB_D12_EP0_TXBUFSIZE,
+        max_in_padding      : 0,
+        alignment           : 0
+    },
+
+/*
+#ifdef _TX_EP1
+    {
+        endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT,
+        endpoint_number     : 1,
+        endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN,
+        endpoint            : (void*) &tx_ep1.common,
+# ifdef CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY
+        devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1w",
+# else        
+        devtab_entry        : (const char*) 0,
+# endif        
+        min_size            : 0,
+        max_size            : 0x0FFFF,      // Driver limitation, only a single buffer descriptor is used
+        max_in_padding      : 0,
+        alignment           : HAL_DCACHE_LINE_SIZE
+    },
+#endif
+
+#ifdef _RX_EP1
+    {
+        endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT,
+        endpoint_number     : 1,
+        endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT,
+        endpoint            : (void*) &rx_ep1.common,
+# ifdef CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY
+        devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1r",
+# else        
+        devtab_entry        : (const char*) 0,
+# endif        
+        min_size            : 1,
+        max_size            : 0x0FFFF,      // Driver limitation
+        max_in_padding      : 0,
+        alignment           : HAL_DCACHE_LINE_SIZE
+    },
+#endif
+*/
+
+#ifdef _TX_EP2
+    {
+        endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK,
+        endpoint_number     : 2,
+        endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN,
+        endpoint            : (void*) &tx_ep2.common,
+# ifdef CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY
+        devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2w",
+# else        
+        devtab_entry        : (const char*) 0,
+# endif        
+        min_size            : 0,
+        max_size            : 0x1000,	// 4k for testing
+        max_in_padding      : 0,
+        alignment           : HAL_DCACHE_LINE_SIZE
+    },
+#endif
+
+#ifdef _RX_EP2
+    {
+        endpoint_type       : USB_ENDPOINT_DESCRIPTOR_ATTR_BULK,
+        endpoint_number     : 2,
+        endpoint_direction  : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT,
+        endpoint            : (void*) &rx_ep2.common,
+# ifdef CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY
+        devtab_entry        : CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2r",
+# else        
+        devtab_entry        : (const char*) 0,
+# endif        
+        min_size            : 1,
+        max_size            : 0x1000,		// 4k for testing
+        max_in_padding      : 0,
+        alignment           : HAL_DCACHE_LINE_SIZE
+    },
+#endif
+
+    USBS_TESTING_ENDPOINTS_TERMINATOR
+};
+
diff -urN --exclude=CVS ecos-2006-04-01/packages/devs/usb/d12/current/src/usbs_d12_data.cxx ecos/packages/devs/usb/d12/current/src/usbs_d12_data.cxx
--- ecos-2006-04-01/packages/devs/usb/d12/current/src/usbs_d12_data.cxx	1969-12-31 19:00:00.000000000 -0500
+++ ecos/packages/devs/usb/d12/current/src/usbs_d12_data.cxx	2006-04-02 12:46:38.000000000 -0400
@@ -0,0 +1,222 @@
+//==========================================================================
+//
+//      usbs_d12_data.cxx
+//
+//      Static data for the D12 USB device driver
+//
+//==========================================================================
+//####ECOSGPLCOPYRIGHTBEGIN####
+// -------------------------------------------
+// This file is part of eCos, the Embedded Configurable Operating System.
+// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
+//
+// eCos 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 or (at your option) any later version.
+//
+// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,
+// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+//
+// As a special exception, if other files instantiate templates or use macros
+// or inline functions from this file, or you compile this file and link it
+// with other works to produce a work based on this file, this file does not
+// by itself cause the resulting work to be covered by the GNU General Public
+// License. However the source code for this file must still be made available
+// in accordance with section (3) of the GNU General Public License.
+//
+// This exception does not invalidate any other reasons why a work based on
+// this file might be covered by the GNU General Public License.
+//
+// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
+// at http://sources.redhat.com/ecos/ecos-license/
+// -------------------------------------------
+//####ECOSGPLCOPYRIGHTEND####
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// Author(s):    fmp
+// Contributors: fmp
+// Date:         2004-05-27
+//
+// This file contains various objects that should go into extras.o
+// rather than libtarget.a, e.g. devtab entries that would normally
+// be eliminated by the selective linking.
+//
+//####DESCRIPTIONEND####
+//==========================================================================
+
+#include <cyg/io/devtab.h>
+#include <cyg/io/usb/usbs_d12.h>
+#include <pkgconf/devs_usb_d12.h>
+
+// ----------------------------------------------------------------------------
+// Initialization. The goal here is to call usbs_d12_init()
+// early on during system startup, to take care of things like
+// registering interrupt handlers etc. which are best done
+// during system init.
+//
+// If the endpoint 0 devtab entry is available then its init()
+// function can be used to take care of this. However the devtab
+// entries are optional so an alternative mechanism must be
+// provided. Unfortunately although it is possible to give
+// a C function the constructor attribute, it cannot be given
+// an initpri attribute. Instead it is necessary to define a
+// dummy C++ class.
+
+extern "C" void usbs_d12_init(void);
+
+#ifndef CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY
+class usbs_d12_initialization {
+  public:
+	usbs_d12_initialization() {
+		usbs_d12_init();
+	}
+};
+
+static usbs_d12_initialization usbs_d12_init_object CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_IO);
+#endif
+
+// ----------------------------------------------------------------------------
+// The devtab entries. Each of these is optional, many applications
+// will want to use the lower-level API rather than go via
+// open/read/write/ioctl.
+
+#ifdef CYGVAR_DEVS_USB_D12_EP0_DEVTAB_ENTRY
+
+// For endpoint 0 the only legal operations are get_config() and
+// set_config(), and these are provided by the common package.
+
+static bool usbs_d12_devtab_ep0_init(struct cyg_devtab_entry* tab)
+{
+	CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab);
+	usbs_d12_init();
+	return true;
+}
+
+static CHAR_DEVIO_TABLE(usbs_d12_ep0_devtab_functions,
+						&cyg_devio_cwrite,
+						&cyg_devio_cread,
+						&cyg_devio_select,
+						&usbs_devtab_get_config,
+						&usbs_devtab_set_config);
+
+static CHAR_DEVTAB_ENTRY(usbs_d12_ep0_devtab_entry,
+						CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "0c",
+						0,
+						&usbs_d12_ep0_devtab_functions,
+						&usbs_d12_devtab_ep0_init,
+						0,
+						(void*) &usbs_d12_ep0);
+#endif
+
+// ----------------------------------------------------------------------------
+// Common routines for ep1 and ep2.
+
+#if defined(CYGVAR_DEVS_USB_D12_TX_EP1_DEVTAB_ENTRY) || \
+	defined(CYGVAR_DEVS_USB_D12_RX_EP1_DEVTAB_ENTRY) || \
+	defined(CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY) || \
+	defined(CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY)
+
+static bool usbs_d12_devtab_dummy_init(struct cyg_devtab_entry* tab)
+{
+	CYG_UNUSED_PARAM(struct cyg_devtab_entry*, tab);
+	return true;
+}
+#endif
+
+// ----------------------------------------------------------------------------
+// tx (in) ep1 devtab entry. This can only be used for slave->host,
+// so only the cwrite() function makes sense.
+
+#ifdef CYGVAR_DEVS_USB_D12_TX_EP1_DEVTAB_ENTRY
+
+static CHAR_DEVIO_TABLE(usbs_d12_tx_ep1_devtab_functions,
+						&usbs_devtab_cwrite,
+						&cyg_devio_cread,
+						&cyg_devio_select,
+						&usbs_devtab_get_config,
+						&usbs_devtab_set_config);
+
+static CHAR_DEVTAB_ENTRY(usbs_d12_tx_ep1_devtab_entry,
+						 CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1w",
+						 0,
+						 &usbs_d12_tx_ep1_devtab_functions,
+						 &usbs_d12_devtab_dummy_init,
+						 0,
+						 (void*) &usbs_d12_tx_ep1);
+#endif
+
+// ----------------------------------------------------------------------------
+// rx (out) ep1 devtab entry. This can only be used for host->slave, 
+// so only the cread() function makes sense.
+
+#ifdef CYGVAR_DEVS_USB_D12_RX_EP1_DEVTAB_ENTRY
+
+static CHAR_DEVIO_TABLE(usbs_d12_rx_ep1_devtab_functions,
+						&cyg_devio_cwrite,
+						&usbs_devtab_cread,
+						&cyg_devio_select,
+						&usbs_devtab_get_config,
+						&usbs_devtab_set_config);
+
+static CHAR_DEVTAB_ENTRY(usbs_d12_rx_ep1_devtab_entry,
+						CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "1r",
+						0,
+						&usbs_d12_rx_ep1_devtab_functions,
+						&usbs_d12_devtab_dummy_init,
+						0,
+						(void*) &usbs_d12_rx_ep1);
+#endif
+
+
+// ----------------------------------------------------------------------------
+// tx (in) ep2 devtab entry. This can only be used for slave->host, so only the
+// cwrite() function makes sense.
+
+#ifdef CYGVAR_DEVS_USB_D12_TX_EP2_DEVTAB_ENTRY
+
+static CHAR_DEVIO_TABLE(usbs_d12_tx_ep2_devtab_functions,
+						&usbs_devtab_cwrite,
+						&cyg_devio_cread,
+						&cyg_devio_select,
+						&usbs_devtab_get_config,
+						&usbs_devtab_set_config);
+
+static CHAR_DEVTAB_ENTRY(usbs_d12_tx_ep2_devtab_entry,
+						CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2w",
+						0,
+						&usbs_d12_tx_ep2_devtab_functions,
+						&usbs_d12_devtab_dummy_init,
+						0,
+						(void*) &usbs_d12_tx_ep2);
+#endif
+
+// ----------------------------------------------------------------------------
+// rx (out) ep2 devtab entry. This can only be used for host->slave, 
+// so only the cread() function makes sense.
+
+#ifdef CYGVAR_DEVS_USB_D12_RX_EP2_DEVTAB_ENTRY
+
+static CHAR_DEVIO_TABLE(usbs_d12_rx_ep2_devtab_functions,
+						&cyg_devio_cwrite,
+						&usbs_devtab_cread,
+						&cyg_devio_select,
+						&usbs_devtab_get_config,
+						&usbs_devtab_set_config);
+
+static CHAR_DEVTAB_ENTRY(usbs_d12_rx_ep2_devtab_entry,
+						CYGDAT_DEVS_USB_D12_DEVTAB_BASENAME "2r",
+						0,
+						&usbs_d12_rx_ep2_devtab_functions,
+						&usbs_d12_devtab_dummy_init,
+						0,
+						(void*) &usbs_d12_rx_ep2);
+#endif
+
+
diff -urN --exclude=CVS ecos-2006-04-01/packages/ecos.db ecos/packages/ecos.db
--- ecos-2006-04-01/packages/ecos.db	2006-02-25 09:04:22.000000000 -0500
+++ ecos/packages/ecos.db	2006-04-09 16:12:41.000000000 -0400
@@ -1359,6 +1359,14 @@
         description     "A device driver for the NEC uPD985xx on-chip USB device"
 }
 
+package CYGPKG_DEVS_USB_D12 {
+	alias		{ "Philips PDIUSBD12 USB Peripheral Chip" usb_d12 }
+	hardware
+	directory	devs/usb/d12
+	script		usbs_d12.cdl
+	description	"Device driver for the Philips PDIUSBD12 full speed USB peripheral chip."
+}
+
 package CYGPKG_NET {
 	alias 		{ "Networking" net }
 	directory	net/common
@@ -4924,6 +4932,28 @@
             on a standard i386 PC under wmWare."
 }
 
+target pc_usb_d12 {
+        alias		{ "i386 PC target with D12 USB Slave" }
+	packages        { CYGPKG_HAL_I386
+                          CYGPKG_HAL_I386_GENERIC
+                          CYGPKG_HAL_I386_PC
+                          CYGPKG_HAL_I386_PCMB
+			  CYGPKG_IO_PCI
+                          CYGPKG_IO_SERIAL_GENERIC_16X5X
+	                  CYGPKG_IO_SERIAL_I386_PC
+			  CYGPKG_IO_USB
+			  CYGPKG_IO_USB_SLAVE
+			  CYGPKG_IO_FILEIO
+			  CYGPKG_DEVS_USB_D12
+                          CYGPKG_DEVICES_WALLCLOCK_DALLAS_DS12887
+                          CYGPKG_DEVICES_WALLCLOCK_I386_PC
+        }
+        description "
+    	    Provides the packages needed to run eCos binaries
+	    on a standard i386 PC motherboard with USB Slave support for the
+	    Philips D12 chip."
+}
+
 # --------------------------------------------------------------------------
 # Synthetic targets.
 target linux {
diff -urN --exclude=CVS ecos-2006-04-01/packages/io/usb/slave/current/host/usbhost.c ecos/packages/io/usb/slave/current/host/usbhost.c
--- ecos-2006-04-01/packages/io/usb/slave/current/host/usbhost.c	2005-06-26 17:21:37.000000000 -0400
+++ ecos/packages/io/usb/slave/current/host/usbhost.c	2006-04-08 12:50:08.000000000 -0400
@@ -216,9 +216,9 @@
             } 
         }
         // Move to the end of the current line.
-        do {
+        while ((EOF != ch) && ('\n' != ch)) {
             ch = getc(devs_file);
-        } while ((EOF != ch) && ('\n' != ch));
+		}
         if (EOF != ch) {
             ch = getc(devs_file);
         }
diff -urN --exclude=CVS ecos-2006-04-01/packages/io/usb/slave/current/tests/usbtarget.c~ ecos/packages/io/usb/slave/current/tests/usbtarget.c~
--- ecos-2006-04-01/packages/io/usb/slave/current/tests/usbtarget.c~	1969-12-31 19:00:00.000000000 -0500
+++ ecos/packages/io/usb/slave/current/tests/usbtarget.c~	2006-04-08 11:17:01.000000000 -0400
@@ -0,0 +1,1848 @@
+/*{{{  Banner                                                   */
+
+/*=================================================================
+//
+//        target.c
+//
+//        USB testing - target-side
+//
+//==========================================================================
+//####ECOSGPLCOPYRIGHTBEGIN####
+// -------------------------------------------
+// This file is part of eCos, the Embedded Configurable Operating System.
+// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
+//
+// eCos 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 or (at your option) any later version.
+//
+// eCos 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 eCos; if not, write to the Free Software Foundation, Inc.,
+// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+//
+// As a special exception, if other files instantiate templates or use macros
+// or inline functions from this file, or you compile this file and link it
+// with other works to produce a work based on this file, this file does not
+// by itself cause the resulting work to be covered by the GNU General Public
+// License. However the source code for this file must still be made available
+// in accordance with section (3) of the GNU General Public License.
+//
+// This exception does not invalidate any other reasons why a work based on
+// this file might be covered by the GNU General Public License.
+//
+// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
+// at http://sources.redhat.com/ecos/ecos-license/
+// -------------------------------------------
+//####ECOSGPLCOPYRIGHTEND####
+//==========================================================================
+//#####DESCRIPTIONBEGIN####
+//
+// This program performs appropriate USB initialization and initializes
+// itself as a specific type of USB peripheral, Red Hat eCos testing.
+// There is no actual host-side device driver for this, instead there is
+// a test application which performs ioctl's on /proc/bus/usb/... and
+// makes appropriate functionality available to a Tcl script.
+//
+// Author(s):     bartv
+// Date:          2001-07-04
+//####DESCRIPTIONEND####
+//==========================================================================
+*/
+
+/*}}}*/
+/*{{{  #include's                                               */
+
+#include <stdio.h>
+#include <cyg/infra/cyg_ass.h>
+#include <cyg/infra/diag.h>
+#include <cyg/kernel/kapi.h>
+#include <cyg/hal/hal_arch.h>
+#include <cyg/io/io.h>
+#include <cyg/io/usb/usbs.h>
+#include <cyg/infra/testcase.h>
+#include "protocol.h"
+
+/*}}}*/
+
+/*{{{  Statics                                                  */
+
+// ----------------------------------------------------------------------------
+// Statics.
+
+// The number of endpoints supported by the device driver.
+static int number_endpoints     = 0;
+
+// The control endpoint
+static usbs_control_endpoint* control_endpoint = (usbs_control_endpoint*) 0;
+
+// Buffers for incoming and outgoing data, and a length field.
+static unsigned char class_request[USBTEST_MAX_CONTROL_DATA + 1];
+static unsigned char class_reply[USBTEST_MAX_CONTROL_DATA + 1];
+static int           class_request_size  = 0;
+
+// This semaphore is used by DSRs to wake up the main thread when work has to
+// be done at thread level.
+static cyg_sem_t    main_wakeup;
+
+// And this function pointer identifies the work that has to be done.
+static void         (*main_thread_action)(void)  = (void (*)(void)) 0;
+
+// Is the system still busy processing a previous request? This variable is
+// checked in response to the synch request. It may get updated in
+// DSRs as well as at thread level, hence volatile.
+static volatile int idle    = 1;
+
+// Are any tests currently running?
+static int          running = 0;
+
+// Has the current batch of tests been terminated by the host? This
+// flag is checked by the various test handlers at appropriate
+// intervals, and helps to handle the case where one of the side has
+// terminated early because an error has been detected.
+static int          current_tests_terminated = 0;
+
+// A counter for the number of threads involved in the current batch of tests.
+static int          thread_counter    = 0;
+
+// An extra buffer for recovery operations, for example to accept and discard
+// data which the host is still trying to send.
+static unsigned char recovery_buffer[USBTEST_MAX_BULK_DATA + USBTEST_MAX_BULK_DATA_EXTRA];
+
+/*}}}*/
+/*{{{  Logging                                                  */
+
+// ----------------------------------------------------------------------------
+// The target-side code can provide various levels of run-time logging.
+// Obviously the verbose flag cannot be controlled by a command-line
+// argument, but it can be set from inside gdb or alternatively by
+// a request from the host.
+//
+// NOTE: is printf() the best I/O routine to use here?
+
+static int verbose = 0;
+
+#define VERBOSE(_level_, _format_, _args_...)   \
+    do {                                        \
+        if (verbose >= _level_) {               \
+            diag_printf(_format_, ## _args_);        \
+        }                                       \
+    } while (0);
+
+/*}}}*/
+/*{{{  Utilities                                                */
+
+// ----------------------------------------------------------------------------
+// A reimplementation of nanosleep, to avoid having to pull in the
+// POSIX compatibility testing for USB testing.
+static void
+usbs_nanosleep(int delay)
+{
+    cyg_tick_count_t ticks;
+    cyg_resolution_t resolution = cyg_clock_get_resolution(cyg_real_time_clock());
+
+    // (resolution.dividend/resolution.divisor) == nanoseconds/tick
+    //   e.g. 1000000000/100, i.e. 10000000 ns or 10 ms per tick
+    // So ticks = (delay * divisor) / dividend
+    //   e.g. (10000000 * 100) / 1000000000
+    // with a likely value of 0 for delays of less than the clock resolution,
+    // so round those up to one tick.
+
+    cyg_uint64 tmp = (cyg_uint64) delay;
+    tmp *= (cyg_uint64) resolution.divisor;
+    tmp /= (cyg_uint64) resolution.dividend;
+
+    ticks = (int) tmp;
+    if (0 != ticks) {
+        cyg_thread_delay(ticks);
+    }
+}
+
+// ----------------------------------------------------------------------------
+// Fix any problems in the driver-supplied endpoint data
+//
+// Maximum transfer sizes are limited not just by the capabilities
+// of the driver but also by the testing code itself, since e.g.
+// buffers for transfers are statically allocated.
+static void
+fix_driver_endpoint_data(void)
+{
+    int i;
+    
+    for (i = 0; !USBS_TESTING_ENDPOINTS_IS_TERMINATOR(usbs_testing_endpoints[i]); i++) {
+        if (USB_ENDPOINT_DESCRIPTOR_ATTR_BULK == usbs_testing_endpoints[i].endpoint_type) {
+            if ((-1 == usbs_testing_endpoints[i].max_size) ||
+                (usbs_testing_endpoints[i].max_size > USBTEST_MAX_BULK_DATA)) {
+                usbs_testing_endpoints[i].max_size = USBTEST_MAX_BULK_DATA;
+            }
+        }
+    }
+}
+
+// ----------------------------------------------------------------------------
+// A heartbeat thread.
+//
+// USB tests can run for a long time with no traffic on the debug channel,
+// which can cause problems. To avoid problems it is possible to have a
+// heartbeat thread running in the background, sending output at one
+// second intervals.
+//
+// Depending on the configuration the output may still be line-buffered,
+// but that is still sufficient to keep things happy.
+
+static cyg_bool     heartbeat = false;
+static cyg_thread   heartbeat_data;
+static cyg_handle_t heartbeat_handle;
+static char         heartbeat_stack[CYGNUM_HAL_STACK_SIZE_TYPICAL];
+
+static void
+heartbeat_function(cyg_addrword_t arg __attribute((unused)))
+{
+    char* message = "alive\n";
+    int i;
+    
+    for ( i = 0; ; i = (i + 1) % 6) {
+        usbs_nanosleep(1000000000);
+        if (heartbeat) {
+            diag_write_char(message[i]);
+        }
+    }
+}
+
+static void
+start_heartbeat(void)
+{
+    cyg_thread_create( 0, &heartbeat_function, 0,
+                       "heartbeat", heartbeat_stack, CYGNUM_HAL_STACK_SIZE_TYPICAL,
+                       &heartbeat_handle, &heartbeat_data);
+    cyg_thread_resume(heartbeat_handle);
+}
+
+
+/*}}}*/
+/*{{{  Endpoint usage                                           */
+
+// ----------------------------------------------------------------------------
+// It is important to keep track of which endpoints are currently in use,
+// because the behaviour of the USB I/O routines is undefined if there are
+// concurrent attempts to communicate on the same endpoint. Normally this is
+// not a problem because the host will ensure that a given endpoint is used
+// for only one endpoint at a time, but when performing recovery action it
+// is important that the system is sure that a given endpoint can be accessed
+// safely.
+
+static cyg_bool in_endpoint_in_use[16];
+static cyg_bool out_endpoint_in_use[16];
+
+// Lock the given endpoint. In theory this is only ever accessed from a single
+// test thread at a time, but just in case...
+static void
+lock_endpoint(int endpoint, int direction)
+{
+    CYG_ASSERTC((endpoint >=0) && (endpoint < 16));
+    CYG_ASSERTC((USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) || (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT == direction));
+    
+    cyg_scheduler_lock();
+    if (0 == endpoint) {
+        // Comms traffic on endpoint 0 is implemented using reserved control messages.
+        // It is not really possible to have concurrent IN and OUT operations because
+        // the two would interfere with each other.
+        CYG_ASSERTC(!in_endpoint_in_use[0] && !out_endpoint_in_use[0]);
+        in_endpoint_in_use[0]  = true;
+        out_endpoint_in_use[0] = true;
+    } else if (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) {
+        CYG_ASSERTC(!in_endpoint_in_use[endpoint]);
+        in_endpoint_in_use[endpoint] = true;
+    } else {
+        CYG_ASSERTC(!out_endpoint_in_use[endpoint]);
+        out_endpoint_in_use[endpoint] = true;
+    }
+    cyg_scheduler_unlock();
+}
+
+static void
+unlock_endpoint(int endpoint, int direction)
+{
+    CYG_ASSERTC((endpoint >= 0) && (endpoint < 16));
+    CYG_ASSERTC((USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) || (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT == direction));
+    
+    if (0 == endpoint) {
+        CYG_ASSERTC(in_endpoint_in_use[0] && out_endpoint_in_use[0]);
+        in_endpoint_in_use[0]   = false;
+        out_endpoint_in_use[0]  = false;
+    } else if (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) {
+        CYG_ASSERTC(in_endpoint_in_use[endpoint]);
+        in_endpoint_in_use[endpoint] = false;
+    } else {
+        CYG_ASSERTC(out_endpoint_in_use[endpoint]);
+        out_endpoint_in_use[endpoint] = false;
+    }
+}
+
+static cyg_bool
+is_endpoint_locked(int endpoint, int direction)
+{
+    cyg_bool    result = false;
+    
+    if (0 == endpoint) {
+        result = in_endpoint_in_use[0];
+    } else if (USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN == direction) {
+        result = in_endpoint_in_use[endpoint];
+    } else {
+        result = out_endpoint_in_use[endpoint];
+    }
+    return result;
+}
+
+// For a given endpoint number, direction and protocol, search through the table 
+// supplied by the device driver of all available endpoints. This can be used
+// to e.g. get hold of the name of the devtab entry or a pointer to the endpoint
+// data structure itself.
+static int
+lookup_endpoint(int endpoint_number, int direction, int protocol)
+{
+    int result = -1;
+    int i;
+
+    for (i = 0; !USBS_TESTING_ENDPOINTS_IS_TERMINATOR(usbs_testing_endpoints[i]); i++) {
+        if ((usbs_testing_endpoints[i].endpoint_type        == protocol)        &&
+            (usbs_testing_endpoints[i].endpoint_number      == endpoint_number) &&
+            (usbs_testing_endpoints[i].endpoint_direction   == direction)) {
+            result = i;
+            break;
+        }
+    }
+    return result;
+}
+
+/*}}}*/
+/*{{{  Enumeration data                                         */
+
+// ----------------------------------------------------------------------------
+// The enumeration data.
+//
+// For simplicity this configuration involves just a single interface.
+// The target has to list all the endpoints, or the Linux kernel will
+// not allow application code to access them. Hence the information
+// provided by the device drivers has to be turned into endpoint descriptors.
+
+usb_configuration_descriptor usb_configuration = {
+    length:             USB_CONFIGURATION_DESCRIPTOR_LENGTH,
+    type:               USB_CONFIGURATION_DESCRIPTOR_TYPE,
+    total_length_lo:    USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_LO(1, 0),
+    total_length_hi:    USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_HI(1, 0),
+    number_interfaces:  1,
+    configuration_id:   1,      // id 0 is special according to the spec
+    configuration_str:  0,
+    attributes:         USB_CONFIGURATION_DESCRIPTOR_ATTR_REQUIRED |
+                        USB_CONFIGURATION_DESCRIPTOR_ATTR_SELF_POWERED,
+    max_power:          50
+};
+
+usb_interface_descriptor usb_interface = {
+    length:             USB_INTERFACE_DESCRIPTOR_LENGTH,
+    type:               USB_INTERFACE_DESCRIPTOR_TYPE,
+    interface_id:       0,
+    alternate_setting:  0,
+    number_endpoints:   0,
+    interface_class:    USB_INTERFACE_DESCRIPTOR_CLASS_VENDOR,
+    interface_subclass: USB_INTERFACE_DESCRIPTOR_SUBCLASS_VENDOR,
+    interface_protocol: USB_INTERFACE_DESCRIPTOR_PROTOCOL_VENDOR,
+    interface_str:      0
+};
+
+usb_endpoint_descriptor usb_endpoints[USBTEST_MAX_ENDPOINTS];
+                               
+const unsigned char* usb_strings[] = {
+    "\004\003\011\004",
+    "\020\003R\000e\000d\000 \000H\000a\000t\000",
+    "\054\003R\000e\000d\000 \000H\000a\000t\000 \000e\000C\000o\000s\000 \000"
+    "U\000S\000B\000 \000t\000e\000s\000t\000"
+};
+
+usbs_enumeration_data usb_enum_data = {
+    {
+        length:                 USB_DEVICE_DESCRIPTOR_LENGTH,
+        type:                   USB_DEVICE_DESCRIPTOR_TYPE,
+        usb_spec_lo:            USB_DEVICE_DESCRIPTOR_USB11_LO,
+        usb_spec_hi:            USB_DEVICE_DESCRIPTOR_USB11_HI,
+        device_class:           USB_DEVICE_DESCRIPTOR_CLASS_VENDOR,
+        device_subclass:        USB_DEVICE_DESCRIPTOR_SUBCLASS_VENDOR,
+        device_protocol:        USB_DEVICE_DESCRIPTOR_PROTOCOL_VENDOR,
+        max_packet_size:        16,
+        vendor_lo:              0x42,   // Note: this is not an allocated vendor id
+        vendor_hi:              0x42,
+        product_lo:             0x00,
+        product_hi:             0x01,
+        device_lo:              0x00,
+        device_hi:              0x01,
+        manufacturer_str:       1,
+        product_str:            2,
+        serial_number_str:      0,
+        number_configurations:  1
+    },
+    total_number_interfaces:    1,
+    total_number_endpoints:     0,
+    total_number_strings:       3,
+    configurations:             &usb_configuration,
+    interfaces:                 &usb_interface,
+    endpoints:                  usb_endpoints,
+    strings:                    usb_strings
+};
+
+static void
+provide_endpoint_enumeration_data(void)
+{
+    int enum_endpoint_count = 0;
+    int i;
+
+    for (i = 0; !USBS_TESTING_ENDPOINTS_IS_TERMINATOR(usbs_testing_endpoints[i]); i++) {
+
+        // The control endpoint need not appear in the enumeration data.
+        if (USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL == usbs_testing_endpoints[i].endpoint_type) {
+            continue;
+        }
+
+        usb_endpoints[enum_endpoint_count].length          = USB_ENDPOINT_DESCRIPTOR_LENGTH;
+        usb_endpoints[enum_endpoint_count].type            = USB_ENDPOINT_DESCRIPTOR_TYPE;
+        usb_endpoints[enum_endpoint_count].endpoint        = usbs_testing_endpoints[i].endpoint_number |
+                                                             usbs_testing_endpoints[i].endpoint_direction;
+
+        switch (usbs_testing_endpoints[i].endpoint_type) {
+          case USB_ENDPOINT_DESCRIPTOR_ATTR_BULK:
+            usb_endpoints[enum_endpoint_count].attributes      = USB_ENDPOINT_DESCRIPTOR_ATTR_BULK;
+            usb_endpoints[enum_endpoint_count].max_packet_lo   = 64;
+            usb_endpoints[enum_endpoint_count].max_packet_hi   = 0;
+            usb_endpoints[enum_endpoint_count].interval        = 0;
+            break;
+            
+          case USB_ENDPOINT_DESCRIPTOR_ATTR_ISOCHRONOUS:
+            usb_endpoints[enum_endpoint_count].attributes      = USB_ENDPOINT_DESCRIPTOR_ATTR_ISOCHRONOUS;
+            usb_endpoints[enum_endpoint_count].max_packet_lo   = usbs_testing_endpoints[i].max_size & 0x0FF;
+            usb_endpoints[enum_endpoint_count].max_packet_hi   = (usbs_testing_endpoints[i].max_size >> 8) & 0x0FF;
+            usb_endpoints[enum_endpoint_count].interval        = 1;
+            break;
+            
+          case USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT:
+            usb_endpoints[enum_endpoint_count].attributes      = USB_ENDPOINT_DESCRIPTOR_ATTR_INTERRUPT;
+            usb_endpoints[enum_endpoint_count].max_packet_lo   = (unsigned char) usbs_testing_endpoints[i].max_size;
+            usb_endpoints[enum_endpoint_count].max_packet_hi   = 0;
+            usb_endpoints[enum_endpoint_count].interval        = 1;    // NOTE: possibly incorrect
+            break;
+        }
+
+        enum_endpoint_count++;
+    }
+
+    usb_interface.number_endpoints          = enum_endpoint_count;
+    usb_enum_data.total_number_endpoints    = enum_endpoint_count;
+    usb_configuration.total_length_lo       = USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_LO(1, enum_endpoint_count);
+    usb_configuration.total_length_hi       = USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_HI(1, enum_endpoint_count);
+}
+
+/*}}}*/
+/*{{{  Host/target common code                                  */
+
+#define TARGET
+#include "common.c"
+
+/*}}}*/
+/*{{{  The tests                                                */
+
+/*{{{  UsbTest structure                                        */
+
+// ----------------------------------------------------------------------------
+// All the information associated with a particular testcase. Much of this
+// is identical to the equivalent host-side structure, but some additional
+// information is needed so the structure and associated routines are not
+// shared.
+typedef struct UsbTest {
+
+    // A unique identifier to make verbose output easier to understand
+    int                 id;
+    
+    // Which test should be run
+    usbtest             which_test;
+
+    // Test-specific details.
+    union {
+        UsbTest_Bulk        bulk;
+        UsbTest_ControlIn   control_in;
+    } test_params;
+
+    // How to recover from any problems. Specifically, what kind of message
+    // could the target send or receive that would unlock the thread on this
+    // side.
+    UsbTest_Recovery    recovery;
+
+    // The test result, to be collected and passed back to the host.
+    int                 result_pass;
+    char                result_message[USBTEST_MAX_MESSAGE];
+
+    // Support for synchronization. This allows the UsbTest structure to be
+    // used as the callback data for low-level USB calls.
+    cyg_sem_t           sem;
+    int                 transferred;
+
+    // Some tests may need extra cancellation support
+    void                (*cancel_fn)(struct UsbTest*);
+    unsigned char       buffer[USBTEST_MAX_BULK_DATA + USBTEST_MAX_BULK_DATA_EXTRA];
+} UsbTest;
+
+// Reset the information in a given test. This is used by the pool allocation
+// code. The data union is left alone, filling in the appropriate union
+// member is left to other code.
+static void
+reset_usbtest(UsbTest* test)
+{
+    static int next_id = 1;
+    test->id                    = next_id++;
+    test->which_test            = usbtest_invalid;
+    usbtest_recovery_reset(&(test->recovery));
+    test->result_pass           = 0;
+    test->result_message[0]     = '\0';
+    cyg_semaphore_init(&(test->sem), 0);
+    test->transferred           = 0;
+    test->cancel_fn             = (void (*)(UsbTest*)) 0;
+}
+
+// Forward declaration. The pool code depends on run_test(), setting up a test requires the pool.
+static UsbTest* pool_allocate(void);
+
+/*}}}*/
+/*{{{  Bulk transfers                                           */
+
+/*{{{  handle_test_bulk()                                       */
+
+// Prepare for a bulk transfer test. This means allocating a thread to do
+// the work, and extracting the test parameters from the current buffer.
+// The thread allocation code does not require any locking since all worker
+// threads should be idle when starting a new thread, so the work can be
+// done entirely at DSR level and no synch is required.
+static usbs_control_return
+handle_test_bulk(usb_devreq* req)
+{
+    UsbTest*    test;
+    int         index   = 0;
+
+    test = pool_allocate();
+    unpack_usbtest_bulk(&(test->test_params.bulk), class_request, &index);
+    test->which_test = (USB_DEVREQ_DIRECTION_IN == (test->test_params.bulk.endpoint & USB_DEVREQ_DIRECTION_MASK)) ?
+        usbtest_bulk_in : usbtest_bulk_out;
+
+    VERBOSE(3, "Preparing USB bulk test on endpoint %d, direction %s, for %d packets\n", \
+            test->test_params.bulk.endpoint & ~USB_DEVREQ_DIRECTION_MASK,                \
+            (usbtest_bulk_in == test->which_test) ? "IN" : "OUT",                           \
+            test->test_params.bulk.number_packets);
+    VERBOSE(3, "  I/O mechanism is %s\n", \
+            (usb_io_mechanism_usb == test->test_params.bulk.io_mechanism) ? "low-level USB" : \
+            (usb_io_mechanism_dev == test->test_params.bulk.io_mechanism) ? "devtab" : "<invalid>");
+    VERBOSE(3, "  Data format %s, data1 %d, data* %d, data+ %d, data1* %d, data1+ %d, data** %d, data*+ %d, data+* %d, data++ %d\n",\
+            (usbtestdata_none     == test->test_params.bulk.data.format) ? "none" :     \
+            (usbtestdata_bytefill == test->test_params.bulk.data.format) ? "bytefill" : \
+            (usbtestdata_wordfill == test->test_params.bulk.data.format) ? "wordfill" : \
+            (usbtestdata_byteseq  == test->test_params.bulk.data.format) ? "byteseq"  : \
+            (usbtestdata_wordseq  == test->test_params.bulk.data.format) ? "wordseq"  : "<invalid>", \
+            test->test_params.bulk.data.seed,                            \
+            test->test_params.bulk.data.multiplier,                      \
+            test->test_params.bulk.data.increment,                       \
+            test->test_params.bulk.data.transfer_seed_multiplier,        \
+            test->test_params.bulk.data.transfer_seed_increment,         \
+            test->test_params.bulk.data.transfer_multiplier_multiplier,  \
+            test->test_params.bulk.data.transfer_multiplier_increment,   \
+            test->test_params.bulk.data.transfer_increment_multiplier,   \
+            test->test_params.bulk.data.transfer_increment_increment);
+    VERBOSE(3, "  txsize1 %d, txsize>= %d, txsize<= %d, txsize* %d, txsize/ %d, txsize+ %d\n", \
+            test->test_params.bulk.tx_size,         test->test_params.bulk.tx_size_min,        \
+            test->test_params.bulk.tx_size_max,     test->test_params.bulk.tx_size_multiplier, \
+            test->test_params.bulk.tx_size_divisor, test->test_params.bulk.tx_size_increment);
+    VERBOSE(3, "  rxsize1 %d, rxsize>= %d, rxsize<= %d, rxsize* %d, rxsize/ %d, rxsize+ %d\n", \
+            test->test_params.bulk.rx_size,         test->test_params.bulk.rx_size_min,        \
+            test->test_params.bulk.rx_size_max,     test->test_params.bulk.rx_size_multiplier, \
+            test->test_params.bulk.rx_size_divisor, test->test_params.bulk.rx_size_increment);
+    VERBOSE(3, "  txdelay1 %d, txdelay>= %d, txdelay<= %d, txdelay* %d, txdelay/ %d, txdelay+ %d\n", \
+            test->test_params.bulk.tx_delay,         test->test_params.bulk.tx_delay_min,            \
+            test->test_params.bulk.tx_delay_max,     test->test_params.bulk.tx_delay_multiplier,     \
+            test->test_params.bulk.tx_delay_divisor, test->test_params.bulk.tx_delay_increment);
+    VERBOSE(3, "  rxdelay1 %d, rxdelay>= %d, rxdelay<= %d, rxdelay* %d, rxdelay/ %d, rxdelay+ %d\n", \
+            test->test_params.bulk.rx_delay,         test->test_params.bulk.rx_delay_min,            \
+            test->test_params.bulk.rx_delay_max,     test->test_params.bulk.rx_delay_multiplier,     \
+            test->test_params.bulk.rx_delay_divisor, test->test_params.bulk.rx_delay_increment);
+    
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  run_test_bulk_out()                                      */
+
+// The same callback can be used for IN and OUT transfers. Note that
+// starting the next transfer is left to the thread, it is not done
+// at DSR level.
+static void
+run_test_bulk_in_out_callback(void* callback_arg, int transferred)
+{
+    UsbTest*    test    = (UsbTest*) callback_arg;
+    test->transferred   = transferred;
+    cyg_semaphore_post(&(test->sem));
+}
+
+// OUT transfers, i.e. the host will be sending some number of
+// packets. The I/O can happen in a number of different ways, e.g. via
+// the low-level USB API or via devtab routines.
+static void
+run_test_bulk_out(UsbTest* test)
+{
+    unsigned char*      buf;
+    int                 endpoint_number = test->test_params.bulk.endpoint & ~USB_DEVREQ_DIRECTION_MASK;
+    int                 ep_index;
+    usbs_rx_endpoint*   endpoint        = 0;
+    cyg_io_handle_t     io_handle       = (cyg_io_handle_t)0;
+    int                 alignment;
+    int                 transferred;
+    int                 i;
+
+    VERBOSE(1, "Starting test %d, bulk out on endpoint %d\n", test->id, endpoint_number);
+
+    ep_index = lookup_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, USB_ENDPOINT_DESCRIPTOR_ATTR_BULK);
+    if (ep_index == -1) {
+            test->result_pass   = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk OUT transfer on endpoint %d: no such bulk endpoint", endpoint_number);
+            return;
+    }
+    endpoint    = (usbs_rx_endpoint*) usbs_testing_endpoints[ep_index].endpoint;
+    alignment   = usbs_testing_endpoints[ep_index].alignment;
+    if (0 != alignment) {
+        buf         = (unsigned char*) ((((cyg_uint32)test->buffer) + alignment - 1) & ~(alignment - 1));
+    } else {
+        buf = test->buffer;
+    }
+    
+    CYG_ASSERTC((usb_io_mechanism_usb == test->test_params.bulk.io_mechanism) || \
+                (usb_io_mechanism_dev == test->test_params.bulk.io_mechanism));
+    if (usb_io_mechanism_dev == test->test_params.bulk.io_mechanism) {
+        if (((const char*)0 == usbs_testing_endpoints[ep_index].devtab_entry) ||
+            (0 != cyg_io_lookup(usbs_testing_endpoints[ep_index].devtab_entry, &io_handle))) {
+            
+            test->result_pass   = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk OUT transfer on endpoint %d: no devtab entry", endpoint_number);
+            return;
+        }
+    }
+
+    // Make sure nobody else is using this endpoint
+    lock_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT);
+
+    for (i = 0; i < test->test_params.bulk.number_packets; i++) {
+        int rx_size = test->test_params.bulk.rx_size;
+        int tx_size = test->test_params.bulk.tx_size;
+
+        VERBOSE(2, "Bulk OUT test %d: iteration %d, rx size %d, tx size %d\n", test->id, i, rx_size, tx_size);
+        
+        if (rx_size < tx_size) {
+            rx_size = tx_size;
+            VERBOSE(2, "Bulk OUT test %d: iteration %d, packet size reset to %d to match tx size\n",
+                    test->id, i, rx_size);
+        }
+                                                              
+        test->recovery.endpoint     = endpoint_number | USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT;
+        test->recovery.protocol     = USB_ENDPOINT_DESCRIPTOR_ATTR_BULK;
+        test->recovery.size         = rx_size;
+
+        // Make sure there is no old data lying around
+        if (usbtestdata_none != test->test_params.bulk.data.format) {
+            memset(buf, 0, rx_size);
+        }
+
+        // Do the actual transfer, using the I/O mechanism specified for this test.
+        switch (test->test_params.bulk.io_mechanism)
+        {
+          case usb_io_mechanism_usb :
+          {
+              test->transferred = 0;
+              usbs_start_rx_buffer(endpoint, buf, rx_size, &run_test_bulk_in_out_callback, (void*) test);
+              cyg_semaphore_wait(&(test->sem));
+              transferred = test->transferred;
+              break;
+          }
+
+          case usb_io_mechanism_dev :
+          {
+              int result;
+              transferred   = rx_size;
+              result = cyg_io_read(io_handle, (void*) buf, &transferred);
+              if (result < 0) {
+                  transferred = result;
+              }
+              break;
+          }
+
+          default:
+            CYG_FAIL("Invalid test mechanism specified");
+            break;
+        }
+
+        // Has this test been aborted for some reason?
+        if (current_tests_terminated) {
+            VERBOSE(2, "Bulk OUT test %d: iteration %d, termination detected\n", test->id, i);
+            test->result_pass = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk OUT transfer on endpoint %d: transfer aborted after iteration %d", endpoint_number, i);
+            break;
+        }
+
+        // If an error occurred, abort this run
+        if (transferred < 0) {
+            test->result_pass   = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk OUT transfer on endpoint %d: transfer failed with %d", endpoint_number, transferred);
+            VERBOSE(2, "Bulk OUT test %d: iteration %d, error:\n    %s\n", test->id, i, test->result_message);
+            break;
+        }
+
+        // Did the host send the expected amount of data?
+        if (transferred < test->test_params.bulk.tx_size) {
+            test->result_pass   = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk OUT transfer on endpoint %d : the host only sent %d bytes when %d were expected",
+                     endpoint_number, transferred, tx_size);
+            VERBOSE(2, "Bulk OUT test %d: iteration %d, error:\n    %s\n", test->id, i, test->result_message);
+            break;
+        }
+
+        if (verbose >= 3) {
+            // Output the first 32 bytes of data
+            char msg[256];
+            int  index;
+            int  j;
+            index = snprintf(msg, 255, "Bulk OUT test %d: iteration %d, transferred %d\n    Data %s:",
+                             test->id, i, transferred,
+                             (usbtestdata_none == test->test_params.bulk.data.format) ? "(uninitialized)" : "");
+
+            for (j = 0; ((j + 3) < transferred) && (j < 32); j+= 4) {
+                index += snprintf(msg+index, 255-index, " %02x%02x%02x%02x",
+                                  buf[j], buf[j+1], buf[j+2], buf[j+3]);
+            }
+            if (j < 32) {
+                index += snprintf(msg+index, 255-index, " ");
+                for ( ; j < transferred; j++) {
+                    index += snprintf(msg+index, 255-index, "%02x", buf[j]);
+                }
+                
+            }
+            VERBOSE(3, "%s\n", msg);
+        }
+        
+        // Is the data correct?
+        if (!usbtest_check_buffer(&(test->test_params.bulk.data), buf, transferred)) {
+            test->result_pass   = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk OUT transfer on endpoint %d : mismatch between received and expected data", endpoint_number);
+            VERBOSE(2, "Bulk OUt test %d: iteration %d, error:\n    %s\n", test->id, i, test->result_message);
+            break;
+        }
+
+        if (0 != test->test_params.bulk.rx_delay) {
+            VERBOSE(2, "Bulk OUT test %d: iteration %d, sleeping for %d nanoseconds\n", test->id, \
+                    i, test->test_params.bulk.rx_delay);
+            usbs_nanosleep(test->test_params.bulk.rx_delay);
+        }
+        
+        // Move on to the next transfer
+        USBTEST_BULK_NEXT(test->test_params.bulk);
+    }
+
+    // Always unlock the endpoint on completion
+    unlock_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT);
+
+    // If all the packets have been transferred this test has passed.
+    if (i >= test->test_params.bulk.number_packets) {
+        test->result_pass   = 1;
+    }
+    
+    VERBOSE(1, "Test %d bulk OUT on endpoint %d, result %d\n", test->id, endpoint_number, test->result_pass);
+}
+
+/*}}}*/
+/*{{{  run_test_bulk_in()                                       */
+
+// IN transfers, i.e. the host is expected to receive some data. These are slightly
+// easier than OUT transfers because it is the host that will do the checking.
+static void
+run_test_bulk_in(UsbTest* test)
+{
+    unsigned char*      buf;
+    int                 endpoint_number = test->test_params.bulk.endpoint & ~USB_DEVREQ_DIRECTION_MASK;
+    int                 ep_index;
+    usbs_tx_endpoint*   endpoint        = 0;
+    cyg_io_handle_t     io_handle       = (cyg_io_handle_t)0;
+    int                 alignment;
+    int                 transferred;
+    int                 i;
+
+    VERBOSE(1, "Starting test %d, bulk IN on endpoint %d\n", test->id, endpoint_number);
+    
+    ep_index = lookup_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN, USB_ENDPOINT_DESCRIPTOR_ATTR_BULK);
+    if (ep_index == -1) {
+            test->result_pass   = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk IN transfer on endpoint %d: no such bulk endpoint", endpoint_number);
+            return;
+    }
+    endpoint    = (usbs_tx_endpoint*) usbs_testing_endpoints[ep_index].endpoint;
+    alignment   = usbs_testing_endpoints[ep_index].alignment;
+    if (0 != alignment) {
+        buf         = (unsigned char*) ((((cyg_uint32)test->buffer) + alignment - 1) & ~(alignment - 1));
+    } else {
+        buf = test->buffer;
+    }
+    
+    CYG_ASSERTC((usb_io_mechanism_usb == test->test_params.bulk.io_mechanism) || \
+                (usb_io_mechanism_dev == test->test_params.bulk.io_mechanism));
+    if (usb_io_mechanism_dev == test->test_params.bulk.io_mechanism) {
+        if (((const char*)0 == usbs_testing_endpoints[ep_index].devtab_entry) ||
+            (0 != cyg_io_lookup(usbs_testing_endpoints[ep_index].devtab_entry, &io_handle))) {
+            
+            test->result_pass   = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk IN transfer on endpoint %d: no devtab entry", endpoint_number);
+            return;
+        }
+    }
+
+    // Make sure nobody else is using this endpoint
+    lock_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
+    
+    for (i = 0; i < test->test_params.bulk.number_packets; i++) {
+        int packet_size = test->test_params.bulk.tx_size;
+        
+        test->recovery.endpoint     = endpoint_number | USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN;
+        test->recovery.protocol     = USB_ENDPOINT_DESCRIPTOR_ATTR_BULK;
+        test->recovery.size         = packet_size + usbs_testing_endpoints[ep_index].max_in_padding;
+
+        // Make sure the buffer contains the data expected by the host
+        usbtest_fill_buffer(&(test->test_params.bulk.data), buf, packet_size);
+                            
+        if (verbose < 3) {
+            VERBOSE(2, "Bulk OUT test %d: iteration %d, packet size %d\n", test->id, i, packet_size);
+        } else {
+            // Output the first 32 bytes of data as well.
+            char msg[256];
+            int  index;
+            int  j;
+            index = snprintf(msg, 255, "Bulk IN test %d: iteration %d, packet size %d\n    Data %s:",
+                             test->id, i, packet_size,
+                             (usbtestdata_none == test->test_params.bulk.data.format) ? "(uninitialized)" : "");
+
+            for (j = 0; ((j + 3) < packet_size) && (j < 32); j+= 4) {
+                index += snprintf(msg+index, 255-index, " %02x%02x%02x%02x",
+                                  buf[j], buf[j+1], buf[j+2], buf[j+3]);
+            }
+            if (j < 32) {
+                index += snprintf(msg+index, 255-index, " ");
+                for ( ; j < packet_size; j++) {
+                    index += snprintf(msg+index, 255-index, "%02x", buf[j]);
+                }
+                
+            }
+            VERBOSE(3, "%s\n", msg);
+        }
+        
+        // Do the actual transfer, using the I/O mechanism specified for this test.
+        switch (test->test_params.bulk.io_mechanism)
+        {
+          case usb_io_mechanism_usb :
+          {
+              test->transferred = 0;
+              usbs_start_tx_buffer(endpoint, buf, packet_size, &run_test_bulk_in_out_callback, (void*) test);
+              cyg_semaphore_wait(&(test->sem));
+              transferred = test->transferred;
+              break;
+          }
+
+          case usb_io_mechanism_dev :
+          {
+              int result;
+              transferred   = packet_size;
+              result = cyg_io_write(io_handle, (void*) buf, &transferred);
+              if (result < 0) {
+                  transferred = result;
+              }
+              break;
+          }
+
+          default:
+            CYG_FAIL("Invalid test mechanism specified");
+            break;
+        }
+
+        // Has this test been aborted for some reason?
+        if (current_tests_terminated) {
+            VERBOSE(2, "Bulk IN test %d: iteration %d, termination detected\n", test->id, i);
+            test->result_pass   = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk IN transfer on endpoint %d : terminated on iteration %d, packet_size %d\n",
+                     endpoint_number, i, packet_size);
+            break;
+        }
+
+        // If an error occurred, abort this run
+        if (transferred < 0) {
+            test->result_pass   = 0;
+            snprintf(test->result_message, USBTEST_MAX_MESSAGE,
+                     "Target, bulk IN transfer on endpoint %d: transfer failed with %d", endpoint_number, transferred);
+            VERBOSE(2, "Bulk IN test %d: iteration %d, error:\n    %s\n", test->id, i, test->result_message);
+            break;
+        }
+
+        // No need to check the transfer size, the USB code is only
+        // allowed to send the exact amount of data requested.
+
+        if (0 != test->test_params.bulk.tx_delay) {
+            VERBOSE(2, "Bulk IN test %d: iteration %d, sleeping for %d nanoseconds\n", test->id, i, \
+                    test->test_params.bulk.tx_delay);
+            usbs_nanosleep(test->test_params.bulk.tx_delay);
+        }
+        
+        // Move on to the next transfer
+        USBTEST_BULK_NEXT(test->test_params.bulk);
+    }
+
+    // Always unlock the endpoint on completion
+    unlock_endpoint(endpoint_number, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
+
+    // If all the packets have been transferred this test has passed.
+    if (i >= test->test_params.bulk.number_packets) {
+        test->result_pass   = 1;
+    }
+    
+    VERBOSE(1, "Test %d bulk IN on endpoint %d, result %d\n", test->id, endpoint_number, test->result_pass);
+}
+
+/*}}}*/
+
+/*}}}*/
+/*{{{  Control IN transfers                                     */
+
+// Control-IN transfers. These have to be handled a little bit differently
+// from bulk transfers. The target never actually initiates anything. Instead
+// the host will send reserved control messages which are handled at DSR
+// level and passed to handle_reserved_control_messages() below. Assuming
+// a control-IN test is in progress, that will take appropriate action. The
+// thread will be woken up only once all packets have been transferred, or
+// on abnormal termination.
+
+// Is a control-IN test currently in progress?
+static UsbTest* control_in_test    = 0;
+
+// What is the expected packet size?
+static int      control_in_test_packet_size = 0;
+
+// How many packets have been transferred so far?
+static int      control_in_packets_transferred  = 0;
+
+// Cancel a control-in test. handle_test_control_in() will have updated the static
+// control_in_test so that handle_reserved_control_messages() knows what to do.
+// If the test is not actually going to be run then system consistency demands
+// that this update be undone. Also, the endpoint will have been locked to
+// detect concurrent tests on the control endpoint.
+static void
+cancel_test_control_in(UsbTest* test)
+{
+    CYG_ASSERTC(test == control_in_test);
+    control_in_test = (UsbTest*) 0;
+    control_in_test_packet_size = 0;
+    control_in_packets_transferred = 0;
+    unlock_endpoint(0, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
+    test->cancel_fn = (void (*)(UsbTest*)) 0;
+}
+
+// Prepare for a control-IN transfer test.
+static usbs_control_return
+handle_test_control_in(usb_devreq* req)
+{
+    UsbTest*    test;
+    int         index   = 0;
+
+    CYG_ASSERTC((UsbTest*)0 == control_in_test);
+                
+    test = pool_allocate();
+    unpack_usbtest_control_in(&(test->test_params.control_in), class_request, &index);
+
+    lock_endpoint(0, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
+    test->which_test            = usbtest_control_in;
+    test->recovery.endpoint     = 0;
+    test->recovery.protocol     = USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL;
+    test->recovery.size         = 0;    // Does not actually matter
+    test->cancel_fn             = &cancel_test_control_in;
+
+    // Assume a pass. Failures are easy to detect.
+    test->result_pass   = 1;
+    
+    control_in_test = test;
+    control_in_test_packet_size = test->test_params.control_in.packet_size_initial;
+    control_in_packets_transferred  = 0;
+
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+    
+// The thread for a control-in test. Actually all the hard work is done at DSR
+// level, so this thread serves simply to detect when the test has completed
+// and to perform some clean-ups.
+static void
+run_test_control_in(UsbTest* test)
+{
+    CYG_ASSERTC(test == control_in_test);
+    
+    cyg_semaphore_wait(&(test->sem));
+
+    // The DSR has detected that the test is complete.
+    control_in_test = (UsbTest*) 0;
+    control_in_test_packet_size = 0;
+    control_in_packets_transferred = 0;
+    test->cancel_fn = (void (*)(UsbTest*)) 0;
+    unlock_endpoint(0, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN);
+}
+
+// ----------------------------------------------------------------------------
+// This is installed from inside main() as the handler for reserved
+// control messages.
+static usbs_control_return
+handle_reserved_control_messages(usbs_control_endpoint* endpoint, void* data)
+{
+    usb_devreq*         req = (usb_devreq*) endpoint->control_buffer;
+    usbs_control_return result;
+
+    CYG_ASSERT(endpoint == control_endpoint, "control endpoint mismatch");
+    switch(req->request) {
+      case USBTEST_RESERVED_CONTROL_IN:
+        {
+            unsigned char*  buf;
+            int             len;
+            
+            if ((UsbTest*)0 == control_in_test) {
+                result = USBS_CONTROL_RETURN_STALL;
+                break;
+            }
+
+            // Is this test over? If so indicate a failure because we
+            // cannot have received all the control packets.
+            if (current_tests_terminated) {
+                control_in_test->result_pass   = 0;
+                snprintf(control_in_test->result_message, USBTEST_MAX_MESSAGE,
+                         "Target, control IN transfer: not all packets received.");
+                cyg_semaphore_post(&(control_in_test->sem));
+                control_in_test = (UsbTest*) 0;
+                result = USBS_CONTROL_RETURN_STALL;
+                break;
+            }
+            
+            // A control-IN test is indeed in progress, and the current state is
+            // held in control_in_test and control_in_test_packet_size. Check that
+            // the packet size matches up, i.e. that host and target are in sync.
+            len = (req->length_hi << 8) || req->length_lo;
+            if (control_in_test_packet_size != len) {
+                control_in_test->result_pass   = 0;
+                snprintf(control_in_test->result_message, USBTEST_MAX_MESSAGE,
+                         "Target, control IN transfer on endpoint %d : the host only requested %d bytes instead of %d",
+                         len, control_in_test_packet_size);
+                cyg_semaphore_post(&(control_in_test->sem));
+                control_in_test = (UsbTest*) 0;
+                result = USBS_CONTROL_RETURN_STALL;
+                break;
+            }
+
+            // Prepare a suitable reply buffer. This is happening at
+            // DSR level so runtime is important, but with an upper
+            // bound of 255 bytes the buffer should be small enough.
+            buf = control_in_test->buffer;
+            usbtest_fill_buffer(&(control_in_test->test_params.control_in.data), buf, control_in_test_packet_size);
+            control_endpoint->buffer_size   = control_in_test_packet_size;
+            control_endpoint->buffer        = buf;
+            USBTEST_CONTROL_NEXT_PACKET_SIZE(control_in_test_packet_size, control_in_test->test_params.control_in);
+
+            // Have all the packets been transferred?
+            control_in_packets_transferred++;
+            if (control_in_packets_transferred == control_in_test->test_params.control_in.number_packets) {
+                cyg_semaphore_post(&(control_in_test->sem));
+                control_in_test = (UsbTest*) 0;
+            }
+            result = USBS_CONTROL_RETURN_HANDLED;
+            break;
+      }
+      default:
+        CYG_FAIL("Unexpected reserved control message");
+        break;
+    }
+    
+    return result;
+}
+
+/*}}}*/
+
+// FIXME: add more tests.
+
+// This utility is invoked from a thread in the thread pool whenever there is
+// work to be done. It simply dispatches to the appropriate handler.
+static void
+run_test(UsbTest* test)
+{
+    switch(test->which_test)
+    {
+      case usbtest_bulk_out :       run_test_bulk_out(test); break;
+      case usbtest_bulk_in :        run_test_bulk_in(test); break;
+      case usbtest_control_in:      run_test_control_in(test); break;
+      default:
+        CYG_TEST_FAIL_EXIT("Internal error, attempt to run unknown test.\n");
+        break;
+    }
+}
+
+/*}}}*/
+/*{{{  The thread pool                                          */
+
+// ----------------------------------------------------------------------------
+// Just like on the host side, it is desirable to have a pool of
+// threads available to perform test operations. Strictly speaking
+// some tests will run without needing a separate thread, since many
+// operations can be performed at DSR level. However typical
+// application code will involve threads and it is desirable for test
+// code to behave the same way. Also, some operations like validating
+// the transferred data are expensive, and best done in thread context.
+
+typedef struct PoolEntry {
+    cyg_sem_t           wakeup;
+    cyg_thread          thread_data;
+    cyg_handle_t        thread_handle;
+    char                thread_name[16];
+    char                thread_stack[2 * CYGNUM_HAL_STACK_SIZE_TYPICAL];
+    cyg_bool            in_use;
+    cyg_bool            running;
+    UsbTest             test;
+} PoolEntry;
+
+// This array must be uninitialized, or the executable size would
+// be ludicrous.
+PoolEntry  pool[USBTEST_MAX_CONCURRENT_TESTS];
+
+// The entry point for every thread in the pool. It just loops forever,
+// waiting until it is supposed to run a test.
+static void
+pool_thread_function(cyg_addrword_t arg)
+{
+    PoolEntry*  pool_entry  = (PoolEntry*) arg;
+
+    for ( ; ; ) {
+        cyg_semaphore_wait(&(pool_entry->wakeup));
+        run_test(&(pool_entry->test));
+        pool_entry->running = 0;
+    }
+}
+
+// Initialize all threads in the pool.
+static void
+pool_initialize(void)
+{
+    int i;
+    for (i = 0; i < USBTEST_MAX_CONCURRENT_TESTS; i++) {
+        cyg_semaphore_init(&(pool[i].wakeup), 0);
+        pool[i].in_use  = 0;
+        pool[i].running = 0;
+        sprintf(pool[i].thread_name, "worker%d", i);
+        cyg_thread_create( 0, &pool_thread_function, (cyg_addrword_t) &(pool[i]),
+                           pool[i].thread_name, pool[i].thread_stack, 2 * CYGNUM_HAL_STACK_SIZE_TYPICAL,
+                           &(pool[i].thread_handle), &(pool[i].thread_data));
+        cyg_thread_resume(pool[i].thread_handle);
+    }
+}
+
+// Allocate a single entry in the thread pool
+static UsbTest*
+pool_allocate(void)
+{
+    UsbTest*    result  = (UsbTest*) 0;
+
+    if (thread_counter == USBTEST_MAX_CONCURRENT_TESTS) {
+        CYG_TEST_FAIL_EXIT("Internal error, thread resources exhaused.\n");
+    }
+    
+    result = &(pool[thread_counter].test);
+    thread_counter++;
+    reset_usbtest(result);
+    return result;
+}
+
+// Start all the threads that are supposed to be running tests.
+static void
+pool_start(void)
+{
+    int i;
+    for (i = 0; i < thread_counter; i++) {
+        pool[i].running = 1;
+        cyg_semaphore_post(&(pool[i].wakeup));
+    }
+}
+
+/*}}}*/
+/*{{{  Class control messages                                   */
+
+// ----------------------------------------------------------------------------
+// Handle class control messages. These provide the primary form of
+// communication between host and target. There are requests to find out
+// the number of endpoints, details of each endpoint, prepare a test run,
+// abort a test run, get status, terminate the target-side, and so on.
+// The handlers for starting specific test cases are kept alongside
+// the test cases themselves.
+//
+// Note that these handlers will typically be invoked from DSR context
+// and hence they are subject to the usual DSR restrictions.
+//
+// Problems have been experienced in some hosts sending control messages
+// that involve additional host->target data. An ugly workaround is
+// in place whereby any such data is sent in advance using separate
+// control messages.
+
+/*{{{  endpoint count                                           */
+
+// How many endpoints are supported by this device? That information is
+// determined during initialization.
+static usbs_control_return
+handle_endpoint_count(usb_devreq* req)
+{
+    CYG_ASSERTC((1 == req->length_lo) && (0 == req->length_hi) && \
+                ((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    
+    class_reply[0]                  = (unsigned char) number_endpoints;
+    control_endpoint->buffer        = class_reply;
+    control_endpoint->buffer_size   = 1;
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  endpoint details                                         */
+
+// The host wants to know the details of a specific USB endpoint.
+// The format is specified in protocol.h
+static usbs_control_return
+handle_endpoint_details(usb_devreq* req)
+{
+    int buf_index;
+
+    CYG_ASSERTC((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN);
+    CYG_ASSERTC((USBTEST_MAX_CONTROL_DATA == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC(req->index_lo < number_endpoints);
+    CYG_ASSERTC((0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    
+    class_reply[0]  = (unsigned char) usbs_testing_endpoints[req->index_lo].endpoint_type;
+    class_reply[1]  = (unsigned char) usbs_testing_endpoints[req->index_lo].endpoint_number;
+    class_reply[2]  = (unsigned char) usbs_testing_endpoints[req->index_lo].endpoint_direction;
+    class_reply[3]  = (unsigned char) usbs_testing_endpoints[req->index_lo].max_in_padding;
+    buf_index = 4;
+    pack_int(usbs_testing_endpoints[req->index_lo].min_size, class_reply, &buf_index);
+    pack_int(usbs_testing_endpoints[req->index_lo].max_size, class_reply, &buf_index);
+    if (NULL == usbs_testing_endpoints[req->index_lo].devtab_entry) {
+        class_reply[buf_index]    = '\0';
+        control_endpoint->buffer_size   = buf_index + 1;
+    } else {
+        int len = strlen(usbs_testing_endpoints[req->index_lo].devtab_entry) + buf_index + 1;
+        if (len > USBTEST_MAX_CONTROL_DATA) {
+            return USBS_CONTROL_RETURN_STALL;
+        } else {
+            strcpy(&(class_reply[buf_index]), usbs_testing_endpoints[req->index_lo].devtab_entry);
+            control_endpoint->buffer_size   = len;
+        }
+    }
+    control_endpoint->buffer        = class_reply;
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  sync                                                     */
+
+// The host wants to know whether or not the target is currently busy doing
+// stuff. This information is held in a static.
+static usbs_control_return
+handle_sync(usb_devreq* req)
+{
+    CYG_ASSERTC((1 == req->length_lo) && (0 == req->length_hi) && \
+                ((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(0 == class_request_size, "A sync operation should not involve any data");
+    
+    class_reply[0]                  = (unsigned char) idle;
+    control_endpoint->buffer        = class_reply;
+    control_endpoint->buffer_size   = 1;
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  pass/fail                                                */
+
+// Allow the host to generate some pass or fail messages, and
+// optionally terminate the test. These are synchronous requests
+// so the data can be left in class_request.
+
+static int passfail_request   = 0;
+
+// Invoked from thread context
+static void
+handle_passfail_action(void)
+{
+    switch (passfail_request) {
+      case USBTEST_PASS:
+        CYG_TEST_PASS(class_request);
+        break;
+      case USBTEST_PASS_EXIT:
+        CYG_TEST_PASS(class_request);
+        CYG_TEST_EXIT("Exiting normally as requested by the host");
+        break;
+      case USBTEST_FAIL:
+        CYG_TEST_FAIL(class_request);
+        break;
+      case USBTEST_FAIL_EXIT:
+        CYG_TEST_FAIL(class_request);
+        CYG_TEST_EXIT("Exiting normally as requested by the host");
+        break;
+      default:
+        CYG_FAIL("Bogus invocation of usbtest_main_passfail");
+        break;
+    }
+}
+
+// Invoked from DSR context
+static usbs_control_return
+handle_passfail(usb_devreq* req)
+{
+    CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(class_request_size > 0, "A pass/fail message should be supplied");
+    CYG_ASSERT(idle, "Pass/fail messages are only allowed when idle");
+    CYG_ASSERT((void (*)(void))0 == main_thread_action, "No thread operation should be pending.");
+    
+    passfail_request    = req->request;
+    idle                = false;
+    main_thread_action  = &handle_passfail_action;
+    cyg_semaphore_post(&main_wakeup);
+
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  abort                                                    */
+
+// The host has concluded that there is no easy way to get both target and
+// host back to a sensible state. For example there may be a thread that
+// is blocked waiting for some I/O that is not going to complete. The abort
+// should be handled at thread level, not DSR level, so that the host
+// still sees the low-level USB handshake.
+
+static void
+handle_abort_action(void)
+{
+    CYG_TEST_FAIL_EXIT("Test abort requested by host application");
+}
+
+static usbs_control_return
+handle_abort(usb_devreq* req)
+{
+    CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(idle, "Abort messages are only allowed when idle");
+    CYG_ASSERT((void (*)(void))0 == main_thread_action, "No thread operation should be pending.");
+    
+    idle                = false;
+    main_thread_action  = &handle_abort_action;
+    cyg_semaphore_post(&main_wakeup);
+    
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  cancel                                                   */
+
+// Invoked from thread context
+// Cancelling pending test cases simply involves iterating over the allocated
+// entries in the pool, invoking any cancellation functions that have been
+// defined, and then resetting the tread count. The actual tests have not
+// yet started so none of the threads will be active.
+static void
+handle_cancel_action(void)
+{
+    int i;
+    for (i = 0; i < thread_counter; i++) {
+        if ((void (*)(UsbTest*))0 != pool[i].test.cancel_fn) {
+            (*(pool[i].test.cancel_fn))(&(pool[i].test));
+            pool[i].test.cancel_fn  = (void (*)(UsbTest*)) 0;
+        }
+    }
+    thread_counter    = 0;
+}
+
+// Invoked from DSR context
+static usbs_control_return
+handle_cancel(usb_devreq* req)
+{
+    CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(0 == class_request_size, "A cancel operation should not involve any data");
+    CYG_ASSERT(idle, "Cancel requests are only allowed when idle");
+    CYG_ASSERT(!running, "Cancel requests cannot be sent once the system is running");
+    CYG_ASSERT((void (*)(void))0 == main_thread_action, "No thread operation should be pending.");
+    
+    idle                = false;
+    main_thread_action = &handle_cancel_action;
+    cyg_semaphore_post(&main_wakeup);
+
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  start                                                    */
+
+// Start the tests running. This just involves waking up the pool threads
+// and setting the running flag, with the latter serving primarily for
+// assertions. 
+
+static usbs_control_return
+handle_start(usb_devreq* req)
+{
+    CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(0 == class_request_size, "A start operation should not involve any data");
+    CYG_ASSERT(!running, "Start requests cannot be sent if the system is already running");
+
+    current_tests_terminated = false;
+    running = true;
+    pool_start();
+    
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  finished                                                 */
+
+// Have all the tests finished? This involves checking all the threads
+// involved in the current batch of tests and seeing whether or not
+// their running flag is still set.
+
+static usbs_control_return
+handle_finished(usb_devreq* req)
+{
+    int i;
+    int result = 1;
+    
+    CYG_ASSERTC((1 == req->length_lo) && (0 == req->length_hi) && \
+                ((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(0 == class_request_size, "A finished operation should not involve any data");
+    CYG_ASSERT(running, "Finished requests can only be sent if the system is already running");
+    
+    for (i = 0; i < thread_counter; i++) {
+        if (pool[i].running) {
+            result = 0;
+            break;
+        }
+    }
+    class_reply[0]                  = (unsigned char) result;
+    control_endpoint->buffer        = class_reply;
+    control_endpoint->buffer_size   = 1;
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  set terminated                                           */
+
+// A timeout has occurred, or there is some other failure. The first step
+// in recovery is to set the terminated flag so that as recovery action
+// takes place and the threads wake up they make no attempt to continue
+// doing more transfers.
+
+static usbs_control_return
+handle_set_terminated(usb_devreq* req)
+{
+    CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(0 == class_request_size, "A set-terminated operation should not involve any data");
+    CYG_ASSERT(running, "The terminated flag can only be set when there are running tests");
+
+    current_tests_terminated = 1;
+    
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  get recovery                                             */
+
+// Return the recovery information for one of the threads involved in the
+// current batch of tests, so that the host can perform a USB operation
+// that will sort out that thread.
+static usbs_control_return
+handle_get_recovery(usb_devreq* req)
+{
+    int buffer_index;
+    
+    CYG_ASSERT(current_tests_terminated, "Recovery should only be attempted when the terminated flag is set");
+    CYG_ASSERT(running, "If there are no tests running then recovery is impossible");
+    CYG_ASSERTC((12 == req->length_lo) && (0 == req->length_hi) && \
+                ((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
+    CYG_ASSERTC(req->index_lo <= thread_counter);
+    CYG_ASSERTC((0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(0 == class_request_size, "A get-recovery operation should not involve any data");
+
+    control_endpoint->buffer        = class_reply;
+    if (!pool[req->index_lo].running) {
+        // Actually, this particular thread has terminated so no recovery is needed.
+        control_endpoint->buffer_size   = 0;
+    } else {
+        buffer_index    = 0;
+        pack_usbtest_recovery(&(pool[req->index_lo].test.recovery), class_reply, &buffer_index);
+        control_endpoint->buffer_size   = buffer_index;
+    }
+    
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  perform recovery                                         */
+
+// The host has identified a course of action that could unlock a thread
+// on the host-side that is currently blocked performing a USB operation.
+// Typically this involves either sending or accepting some data. If the
+// endpoint is still locked, in other words if there is a still a local
+// thread attempting to communicate on the specified endpoint, then
+// things are messed up: both sides are trying to communicate, but nothing
+// is happening. The eCos USB API is such that attempting multiple
+// concurrent operations on a single endpoint is disallowed, so
+// the recovery request has to be ignored. If things do not sort themselves
+// out then the whole test run will have to be aborted.
+
+// A dummy completion function for when a recovery operation has completed.
+static void
+recovery_callback(void* callback_arg, int transferred)
+{
+    CYG_UNUSED_PARAM(void*, callback_arg);
+    CYG_UNUSED_PARAM(int, transferred);
+}
+    
+static usbs_control_return
+handle_perform_recovery(usb_devreq* req)
+{
+    int                 buffer_index;
+    int                 endpoint_number;
+    int                 endpoint_direction;
+    UsbTest_Recovery    recovery;
+    
+    CYG_ASSERT(current_tests_terminated, "Recovery should only be attempted when the terminated flag is set");
+    CYG_ASSERT(running, "If there are no tests running then recovery is impossible");
+    CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(12 == class_request_size, "A perform-recovery operation requires recovery data");
+
+    buffer_index = 0;
+    unpack_usbtest_recovery(&recovery, class_request, &buffer_index);
+    endpoint_number     = recovery.endpoint & ~USB_DEVREQ_DIRECTION_MASK;
+    endpoint_direction  = recovery.endpoint & USB_DEVREQ_DIRECTION_MASK;
+
+    if (!is_endpoint_locked(endpoint_number, endpoint_direction)) {
+        // Locking the endpoint here would be good, but the endpoint would then
+        // have to be unlocked again - probably in the recovery callback.
+        // This complication is ignored for now.
+
+        if (USB_ENDPOINT_DESCRIPTOR_ATTR_BULK == recovery.protocol) {
+            int ep_index = lookup_endpoint(endpoint_number, endpoint_direction, USB_ENDPOINT_DESCRIPTOR_ATTR_BULK);
+            CYG_ASSERTC(-1 != ep_index);
+
+            if (USB_DEVREQ_DIRECTION_IN == endpoint_direction) {
+                // The host wants some data. Supply it. A single byte will do fine to
+                // complete the transfer.
+                usbs_start_tx_buffer((usbs_tx_endpoint*) usbs_testing_endpoints[ep_index].endpoint,
+                                     recovery_buffer, 1, &recovery_callback, (void*) 0);
+            } else {
+                // The host is trying to send some data. Accept all of it.
+                usbs_start_rx_buffer((usbs_rx_endpoint*) usbs_testing_endpoints[ep_index].endpoint,
+                                     recovery_buffer, recovery.size, &recovery_callback, (void*) 0);
+            }
+        }
+
+        // No support for isochronous or interrupt transfers yet.
+        // handle_reserved_control_messages() should generate stalls which
+        // have the desired effect.
+    }
+    
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  get result                                               */
+
+// Return the result of one the tests. This can be a single byte for
+// a pass, or a single byte plus a message for a failure.
+
+static usbs_control_return
+handle_get_result(usb_devreq* req)
+{
+    CYG_ASSERTC((USBTEST_MAX_CONTROL_DATA == req->length_lo) && (0 == req->length_hi) && \
+                ((req->type & USB_DEVREQ_DIRECTION_MASK) == USB_DEVREQ_DIRECTION_IN));
+    CYG_ASSERTC(req->index_lo <= thread_counter);
+    CYG_ASSERTC((0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(0 == class_request_size, "A get-result operation should not involve any data");
+    CYG_ASSERT(running, "Results can only be sent if a run is in progress");
+    CYG_ASSERT(!pool[req->index_lo].running, "Cannot request results for a test that has not completed");
+
+    class_reply[0]  = pool[req->index_lo].test.result_pass;
+    if (class_reply[0]) {
+        control_endpoint->buffer_size = 1;
+    } else {
+        strncpy(&(class_reply[1]), pool[req->index_lo].test.result_message, USBTEST_MAX_CONTROL_DATA - 2);
+        class_reply[USBTEST_MAX_CONTROL_DATA - 1] = '\0';
+        control_endpoint->buffer_size = 1 + strlen(&(class_reply[1])) + 1;
+    }
+    control_endpoint->buffer = class_reply;
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  batch done                                               */
+
+// A batch of test has been completed - at least, the host thinks so.
+// If the host is correct then all that is required here is to reset
+// the thread pool and clear the global running flag - that is sufficient
+// to allow a new batch of tests to be started.
+
+static usbs_control_return
+handle_batch_done(usb_devreq* req)
+{
+    int i;
+    
+    CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi) && (0 == req->value_lo) && (0 == req->value_hi));
+    CYG_ASSERT(0 == class_request_size, "A batch-done operation should not involve any data");
+    CYG_ASSERT(running, "There must be a current batch of tests");
+
+    for (i = 0; i < thread_counter; i++) {
+        CYG_ASSERTC(!pool[i].running);
+    }
+    thread_counter  = 0;
+    running         = false;
+    
+    return USBS_CONTROL_RETURN_HANDLED;
+
+}
+
+/*}}}*/
+/*{{{  verbosity                                                */
+
+static usbs_control_return
+handle_verbose(usb_devreq* req)
+{
+    CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi));
+    CYG_ASSERT(0 == class_request_size, "A set-verbosity operation should not involve any data");
+
+    verbose = (req->value_hi << 8) + req->value_lo;
+    
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  initialise bulk out endpoint                             */
+
+// ----------------------------------------------------------------------------
+// Accept an initial endpoint on a bulk endpoint. This avoids problems
+// on some hardware such as the SA11x0 which can start to accept data
+// before the software is ready for it.
+
+static void handle_init_callback(void* arg, int result)
+{
+    idle = true;
+}
+
+static usbs_control_return
+handle_init_bulk_out(usb_devreq* req)
+{
+    static char         buf[64];
+    int                 ep_index;
+    usbs_rx_endpoint*   endpoint;
+    
+    CYG_ASSERTC((0 == req->length_lo) && (0 == req->length_hi));
+    CYG_ASSERTC((0 == req->index_lo) && (0 == req->index_hi));
+    CYG_ASSERTC((0 == req->value_hi) && (0 < req->value_lo) && (req->value_lo < 16));
+    CYG_ASSERT(0 == class_request_size, "An init_bulk_out operation should not involve any data");
+
+    ep_index = lookup_endpoint(req->value_lo, USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT, USB_ENDPOINT_DESCRIPTOR_ATTR_BULK);
+    CYG_ASSERTC(-1 != ep_index);
+    endpoint = (usbs_rx_endpoint*) usbs_testing_endpoints[ep_index].endpoint;
+    
+    idle = false;
+    usbs_start_rx_buffer(endpoint, buf, 64, &handle_init_callback, (void*) 0);
+    
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+/*{{{  additional control data                                  */
+
+// Accumulate some more data in the control buffer, ahead of an upcoming
+// request.
+static usbs_control_return
+handle_control_data(usb_devreq* req)
+{
+    class_request[class_request_size + 0] = req->value_hi;
+    class_request[class_request_size + 1] = req->value_lo;
+    class_request[class_request_size + 2] = req->index_hi;
+    class_request[class_request_size + 3] = req->index_lo;
+
+    switch(req->request) {
+      case USBTEST_CONTROL_DATA1 : class_request_size += 1; break;
+      case USBTEST_CONTROL_DATA2 : class_request_size += 2; break;
+      case USBTEST_CONTROL_DATA3 : class_request_size += 3; break;
+      case USBTEST_CONTROL_DATA4 : class_request_size += 4; break;
+    }
+
+    return USBS_CONTROL_RETURN_HANDLED;
+}
+
+/*}}}*/
+
+typedef struct class_handler {
+    int     request;
+    usbs_control_return (*handler)(usb_devreq*);
+} class_handler;
+static class_handler class_handlers[] = {
+    { USBTEST_ENDPOINT_COUNT,   &handle_endpoint_count },
+    { USBTEST_ENDPOINT_DETAILS, &handle_endpoint_details },
+    { USBTEST_PASS,             &handle_passfail },
+    { USBTEST_PASS_EXIT,        &handle_passfail },
+    { USBTEST_FAIL,             &handle_passfail },
+    { USBTEST_FAIL_EXIT,        &handle_passfail },
+    { USBTEST_SYNCH,            &handle_sync },
+    { USBTEST_ABORT,            &handle_abort },
+    { USBTEST_CANCEL,           &handle_cancel },
+    { USBTEST_START,            &handle_start },
+    { USBTEST_FINISHED,         &handle_finished },
+    { USBTEST_SET_TERMINATED,   &handle_set_terminated },
+    { USBTEST_GET_RECOVERY,     &handle_get_recovery },
+    { USBTEST_PERFORM_RECOVERY, &handle_perform_recovery },
+    { USBTEST_GET_RESULT,       &handle_get_result },
+    { USBTEST_BATCH_DONE,       &handle_batch_done },
+    { USBTEST_VERBOSE,          &handle_verbose },
+    { USBTEST_INIT_BULK_OUT,    &handle_init_bulk_out },
+    { USBTEST_TEST_BULK,        &handle_test_bulk },
+    { USBTEST_TEST_CONTROL_IN,  &handle_test_control_in },
+    { USBTEST_CONTROL_DATA1,    &handle_control_data },
+    { USBTEST_CONTROL_DATA2,    &handle_control_data },
+    { USBTEST_CONTROL_DATA3,    &handle_control_data },
+    { USBTEST_CONTROL_DATA4,    &handle_control_data },
+    { -1,                       (usbs_control_return (*)(usb_devreq*)) 0 }
+};
+
+static usbs_control_return
+handle_class_control_messages(usbs_control_endpoint* endpoint, void* data)
+{
+    usb_devreq*         req = (usb_devreq*) endpoint->control_buffer;
+    int                 request = req->request;
+    usbs_control_return result;
+    int                 i;
+
+    VERBOSE(3, "Received control message %02x\n", request);
+    
+    CYG_ASSERT(endpoint == control_endpoint, "control endpoint mismatch");
+    result  = USBS_CONTROL_RETURN_UNKNOWN;
+    for (i = 0; (usbs_control_return (*)(usb_devreq*))0 != class_handlers[i].handler; i++) {
+        if (request == class_handlers[i].request) {
+            result = (*(class_handlers[i].handler))(req);
+            if ((USBTEST_CONTROL_DATA1 != request) &&
+                (USBTEST_CONTROL_DATA2 != request) &&
+                (USBTEST_CONTROL_DATA3 != request) &&
+                (USBTEST_CONTROL_DATA4 != request)) {
+                // Reset the request data buffer after all normal requests.
+                class_request_size = 0;
+            }
+            break;
+        }
+    }
+    CYG_UNUSED_PARAM(void*, data);
+    if (USBS_CONTROL_RETURN_HANDLED != result) {
+        VERBOSE(1, "Control message %02x not handled\n", request);
+    }
+    
+    return result;
+}
+
+/*}}}*/
+/*{{{  main()                                                   */
+
+// ----------------------------------------------------------------------------
+// Initialization.
+int
+main(int argc, char** argv)
+{
+    int i;
+
+    CYG_TEST_INIT();
+
+    // The USB device driver should have provided an array of endpoint
+    // descriptors, usbs_testing_endpoints(). One entry in this array
+    // should be a control endpoint, which is needed for initialization.
+    // It is also useful to know how many endpoints there are.
+    for (i = 0; !USBS_TESTING_ENDPOINTS_IS_TERMINATOR(usbs_testing_endpoints[i]); i++) {
+        if ((0 == usbs_testing_endpoints[i].endpoint_number) &&
+            (USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL== usbs_testing_endpoints[i].endpoint_type)) {
+            CYG_ASSERT((usbs_control_endpoint*)0 == control_endpoint, "There should be only one control endpoint");
+            control_endpoint = (usbs_control_endpoint*) usbs_testing_endpoints[i].endpoint;
+        }
+    }
+    if ((usbs_control_endpoint*)0 == control_endpoint) {
+        CYG_TEST_FAIL_EXIT("Unable to find a USB control endpoint");
+    }
+    number_endpoints = i;
+    CYG_ASSERT(number_endpoints <= USBTEST_MAX_ENDPOINTS, "impossible number of endpoints");
+
+    // Some of the information provided may not match the actual capabilities
+    // of the testing code, e.g. max_size limits.
+    fix_driver_endpoint_data();
+    
+    // This semaphore is used for communication between the DSRs that process control
+    // messages and the main thread
+    cyg_semaphore_init(&main_wakeup, 0);
+
+    // Take care of the pool of threads and related data.
+    pool_initialize();
+
+    // Start the heartbeat thread, to make sure that the gdb session stays
+    // alive.
+    start_heartbeat();
+    
+    // Now it is possible to start up the USB device driver. The host can detect
+    // this, connect, get the enumeration data, and then testing will proceed
+    // in response to class control messages.
+    provide_endpoint_enumeration_data();
+    control_endpoint->enumeration_data      = &usb_enum_data;
+    control_endpoint->class_control_fn      = &handle_class_control_messages;
+    control_endpoint->reserved_control_fn   = &handle_reserved_control_messages;
+    usbs_start(control_endpoint);
+
+    // Now it is over to the host to detect this target and start performing tests.
+    // Much of this is handled at DSR level, in response to USB control messages.
+    // Some of those control messages require action at thread level, and that is
+    // achieved by signalling a semaphore and waking up this thread. A static
+    // function pointer is used to keep track of what operation is actually required.
+    for (;;) {
+        void (*handler)(void);
+        
+        cyg_semaphore_wait(&main_wakeup);
+        handler = main_thread_action;
+        main_thread_action   = 0;
+        CYG_CHECK_FUNC_PTR(handler, "Main thread woken up when there is nothing to be done");
+        (*handler)();
+        idle = true;
+    }
+}
+
+/*}}}*/

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]