view Output/pjrcUSB/arm/usb_dev.c @ 308:ab4515606277

Fix whitespace Use a consistent standard - Tabs in front for indenting, spaces after for anything else. This way everything stays nice and lined up while also letting users change there prefered indent level. Most of the new files from Haata where already in this format.
author Rowan Decker <Smasher816@gmail.com>
date Sun, 08 Mar 2015 18:40:01 -0700
parents d5bf41d7f7ef
children 4f47971c45c2
line wrap: on
line source

/* Teensyduino Core Library
 * http://www.pjrc.com/teensy/
 * Copyright (c) 2013 PJRC.COM, LLC.
 * Modifications by Jacob Alexander (2013-2014)
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * 1. The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * 2. If the Software is incorporated into a build system that allows
 * selection among a list of target devices, then similar target
 * devices manufactured by PJRC.COM must be included in the list of
 * target devices and selectable in the same manner.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// ----- Includes -----

// Project Includes
#include <Lib/OutputLib.h>
#include <print.h>

// Local Includes
#include "usb_dev.h"
#include "usb_mem.h"



// ----- Defines -----

// DEBUG Mode
// XXX - Only use when using usbMuxUart Module
// Delay causes issues initializing more than 1 hid device (i.e. NKRO keyboard)
//#define UART_DEBUG 1
// Debug Unknown USB requests, usually what you want to debug USB issues
//#define UART_DEBUG_UNKNOWN 1


#define TX_STATE_BOTH_FREE_EVEN_FIRST   0
#define TX_STATE_BOTH_FREE_ODD_FIRST    1
#define TX_STATE_EVEN_FREE              2
#define TX_STATE_ODD_FREE               3
#define TX_STATE_NONE_FREE_EVEN_FIRST   4
#define TX_STATE_NONE_FREE_ODD_FIRST    5

#define BDT_OWN         0x80
#define BDT_DATA1       0x40
#define BDT_DATA0       0x00
#define BDT_DTS         0x08
#define BDT_STALL       0x04

#define TX    1
#define RX    0
#define ODD   1
#define EVEN  0
#define DATA0 0
#define DATA1 1


#define GET_STATUS              0
#define CLEAR_FEATURE           1
#define SET_FEATURE             3
#define SET_ADDRESS             5
#define GET_DESCRIPTOR          6
#define SET_DESCRIPTOR          7
#define GET_CONFIGURATION       8
#define SET_CONFIGURATION       9
#define GET_INTERFACE           10
#define SET_INTERFACE           11
#define SYNCH_FRAME             12

#define TX_STATE_BOTH_FREE_EVEN_FIRST   0
#define TX_STATE_BOTH_FREE_ODD_FIRST    1
#define TX_STATE_EVEN_FREE              2
#define TX_STATE_ODD_FREE               3
#define TX_STATE_NONE_FREE              4





// ----- Macros -----

#define BDT_PID(n)      (((n) >> 2) & 15)

#define BDT_DESC(count, data)   (BDT_OWN | BDT_DTS \
				| ((data) ? BDT_DATA1 : BDT_DATA0) \
				| ((count) << 16))

#define index(endpoint, tx, odd) (((endpoint) << 2) | ((tx) << 1) | (odd))
#define stat2bufferdescriptor(stat) (table + ((stat) >> 2))



// ----- Structs -----

// buffer descriptor table

typedef struct {
	uint32_t desc;
	void * addr;
} bdt_t;

static union {
	struct {
		union {
			struct {
				uint8_t bmRequestType;
				uint8_t bRequest;
			};
			uint16_t wRequestAndType;
		};
		uint16_t wValue;
		uint16_t wIndex;
		uint16_t wLength;
	};
	struct {
		uint32_t word1;
		uint32_t word2;
	};
} setup;



// ----- Variables -----

__attribute__ ((section(".usbdescriptortable"), used))
static bdt_t table[ (NUM_ENDPOINTS + 1) * 4 ];

static usb_packet_t *rx_first  [ NUM_ENDPOINTS ];
static usb_packet_t *rx_last   [ NUM_ENDPOINTS ];
static usb_packet_t *tx_first  [ NUM_ENDPOINTS ];
static usb_packet_t *tx_last   [ NUM_ENDPOINTS ];
uint16_t usb_rx_byte_count_data[ NUM_ENDPOINTS ];

static uint8_t tx_state[NUM_ENDPOINTS];

// SETUP always uses a DATA0 PID for the data field of the SETUP transaction.
// transactions in the data phase start with DATA1 and toggle (figure 8-12, USB1.1)
// Status stage uses a DATA1 PID.

static uint8_t ep0_rx0_buf[EP0_SIZE] __attribute__ ((aligned (4)));
static uint8_t ep0_rx1_buf[EP0_SIZE] __attribute__ ((aligned (4)));
static const uint8_t *ep0_tx_ptr = NULL;
static uint16_t ep0_tx_len;
static uint8_t ep0_tx_bdt_bank = 0;
static uint8_t ep0_tx_data_toggle = 0;
uint8_t usb_rx_memory_needed = 0;

volatile uint8_t usb_configuration = 0;
volatile uint8_t usb_reboot_timer = 0;

static uint8_t reply_buffer[8];



// ----- Functions -----

static void endpoint0_stall()
{
	USB0_ENDPT0 = USB_ENDPT_EPSTALL | USB_ENDPT_EPRXEN | USB_ENDPT_EPTXEN | USB_ENDPT_EPHSHK;
}

static void endpoint0_transmit( const void *data, uint32_t len )
{
	table[index(0, TX, ep0_tx_bdt_bank)].addr = (void *)data;
	table[index(0, TX, ep0_tx_bdt_bank)].desc = BDT_DESC(len, ep0_tx_data_toggle);
	ep0_tx_data_toggle ^= 1;
	ep0_tx_bdt_bank ^= 1;
}

static void usb_setup()
{
	const uint8_t *data = NULL;
	uint32_t datalen = 0;
	const usb_descriptor_list_t *list;
	uint32_t size;
	volatile uint8_t *reg;
	uint8_t epconf;
	const uint8_t *cfg;
	int i;

	switch ( setup.wRequestAndType )
	{
	case 0x0500: // SET_ADDRESS
		break;
	case 0x0900: // SET_CONFIGURATION
		#ifdef UART_DEBUG
		print("CONFIGURE - ");
		#endif
		usb_configuration = setup.wValue;
		reg = &USB0_ENDPT1;
		cfg = usb_endpoint_config_table;
		// clear all BDT entries, free any allocated memory...
		for ( i = 4; i < ( NUM_ENDPOINTS + 1) * 4; i++ )
		{
			if ( table[i].desc & BDT_OWN )
			{
				usb_free( (usb_packet_t *)((uint8_t *)(table[ i ].addr) - 8) );
			}
		}
		// free all queued packets
		for ( i = 0; i < NUM_ENDPOINTS; i++ )
		{
			usb_packet_t *p, *n;
			p = rx_first[i];
			while ( p )
			{
				n = p->next;
				usb_free(p);
				p = n;
			}
			rx_first[ i ] = NULL;
			rx_last[ i ] = NULL;
			p = tx_first[i];
			while (p)
			{
				n = p->next;
				usb_free(p);
				p = n;
			}
			tx_first[ i ] = NULL;
			tx_last[ i ] = NULL;
			usb_rx_byte_count_data[i] = 0;

			switch ( tx_state[ i ] )
			{
			case TX_STATE_EVEN_FREE:
			case TX_STATE_NONE_FREE_EVEN_FIRST:
				tx_state[ i ] = TX_STATE_BOTH_FREE_EVEN_FIRST;
				break;
			case TX_STATE_ODD_FREE:
			case TX_STATE_NONE_FREE_ODD_FIRST:
				tx_state[ i ] = TX_STATE_BOTH_FREE_ODD_FIRST;
				break;
			default:
				break;
			}
		}
		usb_rx_memory_needed = 0;
		for ( i = 1; i <= NUM_ENDPOINTS; i++ )
		{
			epconf = *cfg++;
			*reg = epconf;
			reg += 4;
			if ( epconf & USB_ENDPT_EPRXEN )
			{
				usb_packet_t *p;
				p = usb_malloc();
				if ( p )
				{
					table[ index( i, RX, EVEN ) ].addr = p->buf;
					table[ index( i, RX, EVEN ) ].desc = BDT_DESC( 64, 0 );
				}
				else
				{
					table[ index( i, RX, EVEN ) ].desc = 0;
					usb_rx_memory_needed++;
				}
				p = usb_malloc();
				if ( p )
				{
					table[ index( i, RX, ODD ) ].addr = p->buf;
					table[ index( i, RX, ODD ) ].desc = BDT_DESC( 64, 1 );
				}
				else
				{
					table[ index( i, RX, ODD ) ].desc = 0;
					usb_rx_memory_needed++;
				}
			}
			table[ index( i, TX, EVEN ) ].desc = 0;
			table[ index( i, TX, ODD ) ].desc = 0;
		}
		break;
	case 0x0880: // GET_CONFIGURATION
		reply_buffer[0] = usb_configuration;
		datalen = 1;
		data = reply_buffer;
		break;
	case 0x0080: // GET_STATUS (device)
		reply_buffer[0] = 0;
		reply_buffer[1] = 0;
		datalen = 2;
		data = reply_buffer;
		break;
	case 0x0082: // GET_STATUS (endpoint)
		if (setup.wIndex > NUM_ENDPOINTS)
		{
			// TODO: do we need to handle IN vs OUT here?
			endpoint0_stall();
			return;
		}
		reply_buffer[0] = 0;
		reply_buffer[1] = 0;
		if ( *(uint8_t *)(&USB0_ENDPT0 + setup.wIndex * 4) & 0x02 )
			reply_buffer[0] = 1;
		data = reply_buffer;
		datalen = 2;
		break;
	case 0x0102: // CLEAR_FEATURE (endpoint)
		i = setup.wIndex & 0x7F;
		if ( i > NUM_ENDPOINTS || setup.wValue != 0 )
		{
			// TODO: do we need to handle IN vs OUT here?
			endpoint0_stall();
			return;
		}
		(*(uint8_t *)(&USB0_ENDPT0 + setup.wIndex * 4)) &= ~0x02;
		// TODO: do we need to clear the data toggle here?
		break;
	case 0x0302: // SET_FEATURE (endpoint)
		i = setup.wIndex & 0x7F;
		if ( i > NUM_ENDPOINTS || setup.wValue != 0 )
		{
			// TODO: do we need to handle IN vs OUT here?
			endpoint0_stall();
			return;
		}
		(*(uint8_t *)(&USB0_ENDPT0 + setup.wIndex * 4)) |= 0x02;
		// TODO: do we need to clear the data toggle here?
		break;
	case 0x0680: // GET_DESCRIPTOR
	case 0x0681:
		#ifdef UART_DEBUG
		print("desc:");
		printHex( setup.wValue );
		print( NL );
		#endif
		for ( list = usb_descriptor_list; 1; list++ )
		{
			if ( list->addr == NULL )
				break;
			if ( setup.wValue == list->wValue && setup.wIndex == list->wIndex )
			{
				data = list->addr;
				if ( (setup.wValue >> 8) == 3 )
				{
					// for string descriptors, use the descriptor's
					// length field, allowing runtime configured
					// length.
					datalen = *(list->addr);
				}
				else
				{
					datalen = list->length;
				}
				#if UART_DEBUG
				print("Desc found, ");
				printHex32( (uint32_t)data );
				print(",");
				printHex( datalen );
				print(",");
				printHex_op( data[0], 2 );
				printHex_op( data[1], 2 );
				printHex_op( data[2], 2 );
				printHex_op( data[3], 2 );
				printHex_op( data[4], 2 );
				printHex_op( data[5], 2 );
				print( NL );
				#endif
				goto send;
			}
		}
		#ifdef UART_DEBUG
		print( "desc: not found" NL );
		#endif
		endpoint0_stall();
		return;

	case 0x2221: // CDC_SET_CONTROL_LINE_STATE
		usb_cdc_line_rtsdtr = setup.wValue;
		//serial_print("set control line state\n");
		endpoint0_stall();
		return;

	case 0x21A1: // CDC_GET_LINE_CODING
		data = (uint8_t*)usb_cdc_line_coding;
		datalen = sizeof( usb_cdc_line_coding );
		goto send;

	case 0x2021: // CDC_SET_LINE_CODING
		// XXX Needed?
		//serial_print("set coding, waiting...\n");
		endpoint0_stall();
		return; // Cannot stall here (causes issues)

	case 0x0921: // HID SET_REPORT
		#ifdef UART_DEBUG
		print("SET_REPORT - ");
		printHex( setup.wValue );
		print(" - ");
		printHex( setup.wValue & 0xFF );
		print( NL );
		#endif
		USBKeys_LEDs = setup.wValue & 0xFF;
		endpoint0_stall();
		return;

	case 0x01A1: // HID GET_REPORT
		#ifdef UART_DEBUG
		print("GET_REPORT - ");
		printHex( USBKeys_LEDs );
		print(NL);
		#endif
		data = (uint8_t*)&USBKeys_LEDs;
		datalen = 1;
		goto send;

	case 0x0A21: // HID SET_IDLE
		#ifdef UART_DEBUG
		print("SET_IDLE - ");
		printHex( setup.wValue );
		print(NL);
		#endif
		USBKeys_Idle_Config = (setup.wValue >> 8);
		USBKeys_Idle_Count = 0;
		endpoint0_stall();
		return;

	case 0x0B21: // HID SET_PROTOCOL
		#ifdef UART_DEBUG
		print("SET_PROTOCOL - ");
		printHex( setup.wValue );
		print(" - ");
		printHex( setup.wValue & 0xFF );
		print(NL);
		#endif
		USBKeys_Protocol = setup.wValue & 0xFF; // 0 - Boot Mode, 1 - NKRO Mode
		endpoint0_stall();
		return;

	// case 0xC940:
	default:
		#ifdef UART_DEBUG_UNKNOWN
		print("UNKNOWN");
		#endif
		endpoint0_stall();
		return;
	}

send:
	#ifdef UART_DEBUG
	print("setup send ");
	printHex32((uint32_t)data);
	print(",");
	printHex(datalen);
	print(NL);
	#endif

	if ( datalen > setup.wLength )
		datalen = setup.wLength;

	size = datalen;
	if ( size > EP0_SIZE )
		size = EP0_SIZE;

	endpoint0_transmit(data, size);
	data += size;
	datalen -= size;

	// See if transmit has finished
	if ( datalen == 0 && size < EP0_SIZE )
		return;

	size = datalen;
	if ( size > EP0_SIZE )
		size = EP0_SIZE;
	endpoint0_transmit(data, size);
	data += size;
	datalen -= size;

	// See if transmit has finished
	if ( datalen == 0 && size < EP0_SIZE )
		return;

	// Save rest of transfer for later? XXX
	ep0_tx_ptr = data;
	ep0_tx_len = datalen;
}


//A bulk endpoint's toggle sequence is initialized to DATA0 when the endpoint
//experiences any configuration event (configuration events are explained in
//Sections 9.1.1.5 and 9.4.5).

//Configuring a device or changing an alternate setting causes all of the status
//and configuration values associated with endpoints in the affected interfaces
//to be set to their default values. This includes setting the data toggle of
//any endpoint using data toggles to the value DATA0.

//For endpoints using data toggle, regardless of whether an endpoint has the
//Halt feature set, a ClearFeature(ENDPOINT_HALT) request always results in the
//data toggle being reinitialized to DATA0.

static void usb_control( uint32_t stat )
{
	#ifdef UART_DEBUG
	print("CONTROL - ");
	#endif
	bdt_t *b;
	uint32_t pid, size;
	uint8_t *buf;
	const uint8_t *data;

	b = stat2bufferdescriptor( stat );
	pid = BDT_PID( b->desc );
	buf = b->addr;
	#ifdef UART_DEBUG
	print("pid:");
	printHex(pid);
	print(", count:");
	printHex32(b->desc);
	print(" - ");
	#endif

	switch (pid)
	{
	case 0x0D: // Setup received from host
		//serial_print("PID=Setup\n");
		//if (count != 8) ; // panic?
		// grab the 8 byte setup info
		setup.word1 = *(uint32_t *)(buf);
		setup.word2 = *(uint32_t *)(buf + 4);

		// give the buffer back
		b->desc = BDT_DESC( EP0_SIZE, DATA1 );
		//table[index(0, RX, EVEN)].desc = BDT_DESC(EP0_SIZE, 1);
		//table[index(0, RX, ODD)].desc = BDT_DESC(EP0_SIZE, 1);

		// clear any leftover pending IN transactions
		ep0_tx_ptr = NULL;
		if ( ep0_tx_data_toggle )
		{
		}
		//if (table[index(0, TX, EVEN)].desc & 0x80) {
			//serial_print("leftover tx even\n");
		//}
		//if (table[index(0, TX, ODD)].desc & 0x80) {
			//serial_print("leftover tx odd\n");
		//}
		table[index(0, TX, EVEN)].desc = 0;
		table[index(0, TX, ODD)].desc = 0;
		// first IN after Setup is always DATA1
		ep0_tx_data_toggle = 1;

		#ifdef UART_DEBUG_UNKNOWN
		print("bmRequestType:");
		printHex(setup.bmRequestType);
		print(", bRequest:");
		printHex(setup.bRequest);
		print(", wValue:");
		printHex(setup.wValue);
		print(", wIndex:");
		printHex(setup.wIndex);
		print(", len:");
		printHex(setup.wLength);
		print(NL);
		#endif
		// actually "do" the setup request
		usb_setup();
		// unfreeze the USB, now that we're ready
		USB0_CTL = USB_CTL_USBENSOFEN; // clear TXSUSPENDTOKENBUSY bit
		break;
	case 0x01:  // OUT transaction received from host
	case 0x02:
		#ifdef UART_DEBUG
		print("PID=OUT"NL);
		#endif
		// CDC Interface
		if ( setup.wRequestAndType == 0x2021 /*CDC_SET_LINE_CODING*/ )
		{
			int i;
			uint8_t *dst = (uint8_t *)usb_cdc_line_coding;
			//serial_print("set line coding ");
			for ( i = 0; i < 7; i++ )
			{
				//serial_phex(*buf);
				*dst++ = *buf++;
			}
			//serial_phex32(usb_cdc_line_coding[0]);
			//serial_print("\n");
			if ( usb_cdc_line_coding[0] == 134 )
				usb_reboot_timer = 15;
			endpoint0_transmit( NULL, 0 );
		}

		// Keyboard Interface
		if ( setup.word1 == 0x02000921 && setup.word2 == ( (1<<16) | KEYBOARD_INTERFACE ) )
		{
			USBKeys_LEDs = buf[0];
			endpoint0_transmit( NULL, 0 );
		}
		// NKRO Keyboard Interface
		if ( setup.word1 == 0x02000921 && setup.word2 == ( (1<<16) | NKRO_KEYBOARD_INTERFACE ) )
		{
			USBKeys_LEDs = buf[0];
			endpoint0_transmit( NULL, 0 );
		}

		// give the buffer back
		b->desc = BDT_DESC( EP0_SIZE, DATA1 );
		break;

	case 0x09: // IN transaction completed to host
		#ifdef UART_DEBUG
		print("PID=IN:");
		printHex(stat);
		print(NL);
		#endif

		// send remaining data, if any...
		data = ep0_tx_ptr;
		if ( data )
		{
			size = ep0_tx_len;
			if (size > EP0_SIZE) size = EP0_SIZE;
			endpoint0_transmit(data, size);
			data += size;
			ep0_tx_len -= size;
			ep0_tx_ptr = (ep0_tx_len > 0 || size == EP0_SIZE) ? data : NULL;
		}

		if ( setup.bRequest == 5 && setup.bmRequestType == 0 )
		{
			setup.bRequest = 0;
			#ifdef UART_DEBUG
			print("set address: ");
			printHex(setup.wValue);
			print(NL);
			#endif
			USB0_ADDR = setup.wValue;
		}

		break;
	default:
		#ifdef UART_DEBUG
		print("PID=unknown:");
		printHex(pid);
		print(NL);
		#endif
		break;
	}
	USB0_CTL = USB_CTL_USBENSOFEN; // clear TXSUSPENDTOKENBUSY bit
}

usb_packet_t *usb_rx( uint32_t endpoint )
{
	//print("USB RX");
	usb_packet_t *ret;
	endpoint--;
	if ( endpoint >= NUM_ENDPOINTS )
		return NULL;
	__disable_irq();
	ret = rx_first[endpoint];
	if ( ret )
		rx_first[ endpoint ] = ret->next;
	usb_rx_byte_count_data[ endpoint ] -= ret->len;
	__enable_irq();
	//serial_print("rx, epidx=");
	//serial_phex(endpoint);
	//serial_print(", packet=");
	//serial_phex32(ret);
	//serial_print("\n");
	return ret;
}

static uint32_t usb_queue_byte_count( const usb_packet_t *p )
{
	uint32_t count=0;

	__disable_irq();
	for ( ; p; p = p->next )
	{
		count += p->len;
	}
	__enable_irq();
	return count;
}

uint32_t usb_tx_byte_count( uint32_t endpoint )
{
	endpoint--;
	if ( endpoint >= NUM_ENDPOINTS )
		return 0;
	return usb_queue_byte_count( tx_first[ endpoint ] );
}

uint32_t usb_tx_packet_count( uint32_t endpoint )
{
	const usb_packet_t *p;
	uint32_t count=0;

	endpoint--;
	if ( endpoint >= NUM_ENDPOINTS )
		return 0;
	__disable_irq();
	for ( p = tx_first[ endpoint ]; p; p = p->next )
		count++;
	__enable_irq();
	return count;
}


// Called from usb_free, but only when usb_rx_memory_needed > 0, indicating
// receive endpoints are starving for memory.  The intention is to give
// endpoints needing receive memory priority over the user's code, which is
// likely calling usb_malloc to obtain memory for transmitting.  When the
// user is creating data very quickly, their consumption could starve reception
// without this prioritization.  The packet buffer (input) is assigned to the
// first endpoint needing memory.
//
void usb_rx_memory( usb_packet_t *packet )
{
	//print("USB RX MEMORY");
	unsigned int i;
	const uint8_t *cfg;

	cfg = usb_endpoint_config_table;
	//serial_print("rx_mem:");
	__disable_irq();
	for ( i = 1; i <= NUM_ENDPOINTS; i++ )
	{
		if ( *cfg++ & USB_ENDPT_EPRXEN )
		{
			if ( table[ index( i, RX, EVEN ) ].desc == 0 )
			{
				table[ index( i, RX, EVEN ) ].addr = packet->buf;
				table[ index( i, RX, EVEN ) ].desc = BDT_DESC( 64, 0 );
				usb_rx_memory_needed--;
				__enable_irq();
				//serial_phex(i);
				//serial_print(",even\n");
				return;
			}
			if ( table[ index( i, RX, ODD ) ].desc == 0 )
			{
				table[ index( i, RX, ODD ) ].addr = packet->buf;
				table[ index( i, RX, ODD ) ].desc = BDT_DESC( 64, 1 );
				usb_rx_memory_needed--;
				__enable_irq();
				//serial_phex(i);
				//serial_print(",odd\n");
				return;
			}
		}
	}
	__enable_irq();
	// we should never reach this point.  If we get here, it means
	// usb_rx_memory_needed was set greater than zero, but no memory
	// was actually needed.
	usb_rx_memory_needed = 0;
	usb_free( packet );
	return;
}

//#define index(endpoint, tx, odd) (((endpoint) << 2) | ((tx) << 1) | (odd))
//#define stat2bufferdescriptor(stat) (table + ((stat) >> 2))

void usb_tx( uint32_t endpoint, usb_packet_t *packet )
{
	bdt_t *b = &table[ index( endpoint, TX, EVEN ) ];
	uint8_t next;

	endpoint--;
	if ( endpoint >= NUM_ENDPOINTS )
		return;
	__disable_irq();
	//serial_print("txstate=");
	//serial_phex(tx_state[ endpoint ]);
	//serial_print("\n");
	switch ( tx_state[ endpoint ] )
	{
	case TX_STATE_BOTH_FREE_EVEN_FIRST:
		next = TX_STATE_ODD_FREE;
		break;
	case TX_STATE_BOTH_FREE_ODD_FIRST:
		b++;
		next = TX_STATE_EVEN_FREE;
		break;
	case TX_STATE_EVEN_FREE:
		next = TX_STATE_NONE_FREE_ODD_FIRST;
		break;
	case TX_STATE_ODD_FREE:
		b++;
		next = TX_STATE_NONE_FREE_EVEN_FIRST;
		break;
	default:
		if (tx_first[ endpoint ] == NULL)
		{
			tx_first[ endpoint ] = packet;
		}
		else
		{
			tx_last[ endpoint ]->next = packet;
		}
		tx_last[ endpoint ] = packet;
		__enable_irq();
		return;
	}

	tx_state[ endpoint ] = next;
	b->addr = packet->buf;
	b->desc = BDT_DESC( packet->len, ((uint32_t)b & 8) ? DATA1 : DATA0 );
	__enable_irq();
}


void usb_device_reload()
{
// MCHCK
#if defined(_mk20dx128vlf5_)

	// MCHCK Kiibohd Variant
	// Check to see if PTA3 (has a pull-up) is connected to GND (usually via jumper)
	// Only allow reload if the jumper is present (security)
	GPIOA_PDDR &= ~(1<<3); // Input
	PORTA_PCR3 = PORT_PCR_PFE | PORT_PCR_MUX(1); // Internal pull-up

	// Check for jumper
	if ( GPIOA_PDIR & (1<<3) )
	{
		print( NL );
		warn_print("Security jumper not present, cancelling firmware reload...");
		info_msg("Replace jumper on middle 2 pins, or manually press the firmware reload button.");
	}
	else
	{
		// Copies variable into the VBAT register, must be identical to the variable in the bootloader to jump to the bootloader flash mode
		for ( int pos = 0; pos < sizeof(sys_reset_to_loader_magic); pos++ )
			(&VBAT)[ pos ] = sys_reset_to_loader_magic[ pos ];
		SOFTWARE_RESET();
	}

// Teensy 3.0 and 3.1
#else
	asm volatile("bkpt");
#endif
}


void usb_isr()
{
	uint8_t status, stat, t;

	//serial_print("isr");
	//status = USB0_ISTAT;
	//serial_phex(status);
	//serial_print("\n");
restart:
	status = USB0_ISTAT;
	/*
	print("USB ISR STATUS: ");
	printHex( status );
	print( NL );
	*/

	if ( (status & USB_INTEN_SOFTOKEN /* 04 */ ) )
	{
		if ( usb_configuration )
		{
			t = usb_reboot_timer;
			if ( t )
			{
				usb_reboot_timer = --t;
				if ( !t )
					usb_device_reload();
			}

			// CDC Interface
			t = usb_cdc_transmit_flush_timer;
			if ( t )
			{
				usb_cdc_transmit_flush_timer = --t;
				if ( t == 0 )
					usb_serial_flush_callback();
			}

		}
		USB0_ISTAT = USB_INTEN_SOFTOKEN;
	}

	if ( (status & USB_ISTAT_TOKDNE /* 08 */ ) )
	{
		uint8_t endpoint;
		stat = USB0_STAT;
		//serial_print("token: ep=");
		//serial_phex(stat >> 4);
		//serial_print(stat & 0x08 ? ",tx" : ",rx");
		//serial_print(stat & 0x04 ? ",odd\n" : ",even\n");
		endpoint = stat >> 4;
		if ( endpoint == 0 )
		{
			usb_control( stat );
		}
		else
		{
			bdt_t *b = stat2bufferdescriptor(stat);
			usb_packet_t *packet = (usb_packet_t *)((uint8_t *)(b->addr) - 8);
#if 0
			serial_print("ep:");
			serial_phex(endpoint);
			serial_print(", pid:");
			serial_phex(BDT_PID(b->desc));
			serial_print(((uint32_t)b & 8) ? ", odd" : ", even");
			serial_print(", count:");
			serial_phex(b->desc >> 16);
			serial_print("\n");
#endif
			endpoint--;     // endpoint is index to zero-based arrays

			if ( stat & 0x08 )
			{ // transmit
				usb_free( packet );
				packet = tx_first[ endpoint ];
				if ( packet )
				{
					//serial_print("tx packet\n");
					tx_first[endpoint] = packet->next;
					b->addr = packet->buf;
					switch ( tx_state[ endpoint ] )
					{
					case TX_STATE_BOTH_FREE_EVEN_FIRST:
						tx_state[ endpoint ] = TX_STATE_ODD_FREE;
						break;
					case TX_STATE_BOTH_FREE_ODD_FIRST:
						tx_state[ endpoint ] = TX_STATE_EVEN_FREE;
						break;
					case TX_STATE_EVEN_FREE:
						tx_state[ endpoint ] = TX_STATE_NONE_FREE_ODD_FIRST;
						break;
					case TX_STATE_ODD_FREE:
						tx_state[ endpoint ] = TX_STATE_NONE_FREE_EVEN_FIRST;
						break;
					default:
						break;
					}
					b->desc = BDT_DESC( packet->len, ((uint32_t)b & 8) ? DATA1 : DATA0 );
				} else {
					//serial_print("tx no packet\n");
					switch ( tx_state[ endpoint ] )
					{
					case TX_STATE_BOTH_FREE_EVEN_FIRST:
					case TX_STATE_BOTH_FREE_ODD_FIRST:
						break;
					case TX_STATE_EVEN_FREE:
						tx_state[ endpoint ] = TX_STATE_BOTH_FREE_EVEN_FIRST;
						break;
					case TX_STATE_ODD_FREE:
						tx_state[ endpoint ] = TX_STATE_BOTH_FREE_ODD_FIRST;
						break;
					default:
						tx_state[ endpoint ] = ((uint32_t)b & 8)
						  ? TX_STATE_ODD_FREE
						  : TX_STATE_EVEN_FREE;
						break;
					}
				}
			}
			else
			{ // receive
				packet->len = b->desc >> 16;
				if ( packet->len > 0 )
				{
					packet->index = 0;
					packet->next = NULL;
					if ( rx_first[ endpoint ] == NULL )
					{
						//serial_print("rx 1st, epidx=");
						//serial_phex(endpoint);
						//serial_print(", packet=");
						//serial_phex32((uint32_t)packet);
						//serial_print("\n");
						rx_first[ endpoint ] = packet;
					}
					else
					{
						//serial_print("rx Nth, epidx=");
						//serial_phex(endpoint);
						//serial_print(", packet=");
						//serial_phex32((uint32_t)packet);
						//serial_print("\n");
						rx_last[ endpoint ]->next = packet;
					}
					rx_last[ endpoint ] = packet;
					usb_rx_byte_count_data[ endpoint ] += packet->len;
					// TODO: implement a per-endpoint maximum # of allocated packets
					// so a flood of incoming data on 1 endpoint doesn't starve
					// the others if the user isn't reading it regularly
					packet = usb_malloc();
					if ( packet )
					{
						b->addr = packet->buf;
						b->desc = BDT_DESC( 64, ((uint32_t)b & 8) ? DATA1 : DATA0 );
					}
					else
					{
						//serial_print("starving ");
						//serial_phex(endpoint + 1);
						//serial_print(((uint32_t)b & 8) ? ",odd\n" : ",even\n");
						b->desc = 0;
						usb_rx_memory_needed++;
					}
				}
				else
				{
					b->desc = BDT_DESC( 64, ((uint32_t)b & 8) ? DATA1 : DATA0 );
				}
			}




		}
		USB0_ISTAT = USB_ISTAT_TOKDNE;
		goto restart;
	}


	if ( status & USB_ISTAT_USBRST /* 01 */ )
	{
		//serial_print("reset\n");

		// initialize BDT toggle bits
		USB0_CTL = USB_CTL_ODDRST;
		ep0_tx_bdt_bank = 0;

		// set up buffers to receive Setup and OUT packets
		table[index( 0, RX, EVEN ) ].desc = BDT_DESC( EP0_SIZE, 0 );
		table[index( 0, RX, EVEN ) ].addr = ep0_rx0_buf;
		table[index( 0, RX, ODD ) ].desc = BDT_DESC( EP0_SIZE, 0 );
		table[index( 0, RX, ODD ) ].addr = ep0_rx1_buf;
		table[index( 0, TX, EVEN ) ].desc = 0;
		table[index( 0, TX, ODD ) ].desc = 0;

		// activate endpoint 0
		USB0_ENDPT0 = USB_ENDPT_EPRXEN | USB_ENDPT_EPTXEN | USB_ENDPT_EPHSHK;

		// clear all ending interrupts
		USB0_ERRSTAT = 0xFF;
		USB0_ISTAT = 0xFF;

		// set the address to zero during enumeration
		USB0_ADDR = 0;

		// enable other interrupts
		USB0_ERREN = 0xFF;
		USB0_INTEN = USB_INTEN_TOKDNEEN |
			USB_INTEN_SOFTOKEN |
			USB_INTEN_STALLEN |
			USB_INTEN_ERROREN |
			USB_INTEN_USBRSTEN |
			USB_INTEN_SLEEPEN;

		// is this necessary?
		USB0_CTL = USB_CTL_USBENSOFEN;
		return;
	}


	if ( (status & USB_ISTAT_STALL /* 80 */ ) )
	{
		//serial_print("stall:\n");
		USB0_ENDPT0 = USB_ENDPT_EPRXEN | USB_ENDPT_EPTXEN | USB_ENDPT_EPHSHK;
		USB0_ISTAT = USB_ISTAT_STALL;
	}
	if ( (status & USB_ISTAT_ERROR /* 02 */ ) )
	{
		uint8_t err = USB0_ERRSTAT;
		USB0_ERRSTAT = err;
		//serial_print("err:");
		//serial_phex(err);
		//serial_print("\n");
		USB0_ISTAT = USB_ISTAT_ERROR;
	}

	if ( (status & USB_ISTAT_SLEEP /* 10 */ ) )
	{
		//serial_print("sleep\n");
		USB0_ISTAT = USB_ISTAT_SLEEP;
	}
}



uint8_t usb_init()
{
	#ifdef UART_DEBUG
	print("USB INIT"NL);
	#endif

	// If no USB cable is attached, do not initialize usb
	// XXX Test -HaaTa
	//if ( USB0_OTGISTAT & USB_OTGSTAT_ID )
	//      return 0;

	// Clear out endpoints table
	for ( int i = 0; i <= NUM_ENDPOINTS * 4; i++ )
	{
		table[i].desc = 0;
		table[i].addr = 0;
	}

	// this basically follows the flowchart in the Kinetis
	// Quick Reference User Guide, Rev. 1, 03/2012, page 141

	// assume 48 MHz clock already running
	// SIM - enable clock
	SIM_SCGC4 |= SIM_SCGC4_USBOTG;

	// reset USB module
	USB0_USBTRC0 = USB_USBTRC_USBRESET;
	while ( (USB0_USBTRC0 & USB_USBTRC_USBRESET) != 0 ); // wait for reset to end

	// set desc table base addr
	USB0_BDTPAGE1 = ((uint32_t)table) >> 8;
	USB0_BDTPAGE2 = ((uint32_t)table) >> 16;
	USB0_BDTPAGE3 = ((uint32_t)table) >> 24;

	// clear all ISR flags
	USB0_ISTAT = 0xFF;
	USB0_ERRSTAT = 0xFF;
	USB0_OTGISTAT = 0xFF;

	USB0_USBTRC0 |= 0x40; // undocumented bit

	// enable USB
	USB0_CTL = USB_CTL_USBENSOFEN;
	USB0_USBCTRL = 0;

	// enable reset interrupt
	USB0_INTEN = USB_INTEN_USBRSTEN;

	// enable interrupt in NVIC...
	NVIC_SET_PRIORITY( IRQ_USBOTG, 112 );
	NVIC_ENABLE_IRQ( IRQ_USBOTG );

	// enable d+ pullup
	USB0_CONTROL = USB_CONTROL_DPPULLUPNONOTG;

	return 1;
}

// return 0 if the USB is not configured, or the configuration
// number selected by the HOST
uint8_t usb_configured()
{
	return usb_configuration;
}