This is the mail archive of the
ecos-patches@sourceware.org
mailing list for the eCos project.
[patch] Philips D12 USB Slave Driver
- From: Frank Pagliughi <fpagliughi at mindspring dot com>
- To: ecos-patches at ecos dot sourceware dot org
- Date: Tue, 11 Apr 2006 09:35:28 -0400
- Subject: [patch] Philips D12 USB Slave Driver
- Domainkey-signature: a=rsa-sha1; q=dns; c=nofws; s=dk20050327; d=mindspring.com; b=EnrpiRiOwayoCLrQDULjIgdWo4uclVtYQxDdWKqHSNzi7wsg+WgN6Z/W+sy8WWT1; h=Received:Message-ID:Date:From:User-Agent:X-Accept-Language:MIME-Version:To:Subject:Content-Type:X-ELNK-Trace:X-Originating-IP;
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® 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;
+ }
+}
+
+/*}}}*/