hpgl_xy/stm32f103c8t6/src/usb_cdc.c

349 lines
12 KiB
C

#include "usb_cdc.h"
#include "usb_util.h"
#include "led.h"
#include "config.h"
// Ring buffer for received data
static uint8_t USBCDC_RXBuffer[CONFIG_USB_RINGBUFFER_SIZE];
// Where the next received byte will be written to
static uint8_t *volatile USBCDC_RXBufferWrite = USBCDC_RXBuffer;
// Where the receive function reads the next byte from the buffer
static uint8_t *volatile USBCDC_RXBufferRead = USBCDC_RXBuffer;
// Is set true when the receive ring buffer is too full to hold another USB
// packet and the USB endpoint is halted.
static volatile bool USBCDC_RXSuspended = false;
// Ring buffer for data to be sent
static uint8_t USBCDC_TXBuffer[CONFIG_USB_RINGBUFFER_SIZE];
// Where the USB transmit procedure will read the next byte from
static uint8_t *volatile USBCDC_TXBufferRead = USBCDC_TXBuffer;
// Where the send routine will write the next byte into the buffer
static uint8_t *volatile USBCDC_TXBufferWrite = USBCDC_TXBuffer;
// True as long as a USB in transmission is ongoing. Is set to false in
// USBCDC_BuildInPacket() after an USB interrupt when the TX ring buffer is
// empty
static volatile bool USBCDC_TXTransferring = false;
USBCDC_LineCoding_t USBCDC_LineCoding =
{
.dwDTERate = 9600,
.bCharFormat = 0,
.bParityType = 0,
.bDataBits = 8
};
void USBCDC_InitEndpoints(void)
{
// Endpoint 1: Interrupt endpoint for notifications (buffer size 8)
USB_BTABLE_ENTRIES[1].COUNT_TX_0 = 0;
USB_BTABLE_ENTRIES[1].ADDR_TX_0 = 0xc0;
USB_SetEPR(&(USB->EP1R), USB_EPR_EP_TYPE_INTERRUPT
| USB_EPR_STAT_TX_NAK | USB_EPR_STAT_RX_DISABLED
| (1 << USB_EP1R_EA_Pos));
// Endpoint 2: Data in (buffer size 64)
USB_BTABLE_ENTRIES[2].COUNT_TX = 0;
USB_BTABLE_ENTRIES[2].ADDR_TX = 0x100;
USB_SetEPR(&(USB->EP2R), USB_EPR_EP_TYPE_BULK
| USB_EPR_STAT_TX_NAK | USB_EPR_STAT_RX_DISABLED
| (2 << USB_EP2R_EA_Pos));
// Endpoint 3: Data out (buffer size 64)
#if USBCDC_ENDPOINT_SIZE == 64
USB_BTABLE_ENTRIES[3].COUNT_RX = USB_EP_RXCOUNT_BL_SIZE | (1 << 10);
#else
#error Endpoint size currently unsupported
#endif
USB_BTABLE_ENTRIES[3].ADDR_RX = 0x140;
USB_SetEPR(&(USB->EP3R), USB_EPR_EP_TYPE_BULK
| USB_EPR_STAT_TX_DISABLED | USB_EPR_STAT_RX_VALID
| (3 << USB_EP3R_EA_Pos));
}
void USBCDC_HandleLineCodingPacket(int length)
{
// Sanity check for packet length (the align byte is not counted here,
// because it is not transmitted)
if(length != sizeof(USBCDC_LineCoding_t) - 1)
{
__asm__ volatile("bkpt");
}
USB_PMAToMemory((uint8_t*)&USBCDC_LineCoding, USB_BTABLE_ENTRIES[0].ADDR_RX,
sizeof(USBCDC_LineCoding_t));
USB_EP0OutPacketHandler = NULL;
}
void USBCDC_HandleInterfaceSetup(USB_SetupPacket_t *sp,
const void **reply_data, int *reply_length, uint8_t *reply_response)
{
switch(sp->bRequest)
{
case USBCDC_REQ_GET_LINE_CODING:
*reply_data = (const uint8_t*)&USBCDC_LineCoding;
*reply_length = sizeof(USBCDC_LineCoding_t);
break;
case USBCDC_REQ_SET_LINE_CODING:
*reply_response = USB_EP_TX_VALID;
*reply_data = NULL;
// The line coding will be sent in a separate packet (which won't be
// a setup packet). We thus set a handler which will be called for
// the next out transaction on endpoint 0.
USB_EP0OutPacketHandler = USBCDC_HandleLineCodingPacket;
break;
case USBCDC_REQ_SET_CONTROL_LINE_STATE:
// Ignored at the moment. wValue contains the control signal bitmap:
// Bit 0 corresponds to DTR, bit 1 to RTS.
*reply_response = USB_EP_TX_VALID;
*reply_data = NULL;
break;
default:
// Not implemented
__asm__ volatile("bkpt");
*reply_response = USB_EP_TX_STALL;
*reply_data = NULL;
break;
}
}
void USBCDC_HandleDataOut(void)
{
// Use a local variable for the ring buffer write pointer to avoid the
// volatile access overhead (which is fine because the pointer isn't
// modified anywhere else
uint8_t *ring_buffer = USBCDC_RXBufferWrite;
// Copy packet from PMA into the ring buffer. Duplicating the code here is
// easier than reusing USB_PMAToMemory() because the PMA's addressing
// weirdness creates a lot of edge cases with the ring buffer wraparound.
int packet_length = USB_BTABLE_ENTRIES[3].COUNT_RX & 0x3ff;
uint16_t pma_offset = USB_BTABLE_ENTRIES[3].ADDR_RX;
uint8_t *pma = (uint8_t*)(USB_PMA_ADDR + 2 * pma_offset);
for(int i = 0; i < packet_length; i++)
{
*ring_buffer++ = *pma++;
if(ring_buffer >= USBCDC_RXBuffer + CONFIG_USB_RINGBUFFER_SIZE)
{
ring_buffer = USBCDC_RXBuffer;
}
if(i & 1)
{
pma += 2;
}
}
// Set the ring buffer write pointer one element past the last one written
// in this function
USBCDC_RXBufferWrite = ring_buffer;
// Determine the free space in the ring buffer and if another packet will
// fit in
int ring_buffer_space = USBCDC_RXBufferRead - USBCDC_RXBufferWrite - 1;
if(ring_buffer_space < 0)
{
ring_buffer_space += CONFIG_USB_RINGBUFFER_SIZE;
}
if(ring_buffer_space >= USBCDC_ENDPOINT_SIZE)
{
// Set EP3 ready for the next packet only when there is enough space in
// the ring buffer
USB_SetEPRXStatus(&(USB->EP3R), USB_EP_RX_VALID);
}
else
{
USB_SetEPRXStatus(&(USB->EP3R), USB_EP_RX_NAK);
USBCDC_RXSuspended = true;
}
}
int USBCDC_ReceiveData(void *buffer, int length)
{
int available = USBCDC_RXBufferWrite - USBCDC_RXBufferRead;
if(available < 0)
{
available += CONFIG_USB_RINGBUFFER_SIZE;
}
if(available < length)
{
length = available;
}
if(length <= 0)
{
return 0;
}
int buffer_length = length;
int until_wraparound = USBCDC_RXBuffer + CONFIG_USB_RINGBUFFER_SIZE
- USBCDC_RXBufferRead;
if(buffer_length >= until_wraparound)
{
memcpy(buffer, USBCDC_RXBufferRead, until_wraparound);
buffer_length -= until_wraparound;
buffer += until_wraparound;
USBCDC_RXBufferRead = USBCDC_RXBuffer;
}
memcpy(buffer, USBCDC_RXBufferRead, buffer_length);
USBCDC_RXBufferRead += buffer_length;
// If receiving has been suspended because the ring buffer is too full,
// resume if there is now enough space for another packet
if(USBCDC_RXSuspended)
{
int ring_buffer_free = USBCDC_RXBufferRead - USBCDC_RXBufferWrite - 1;
if(ring_buffer_free >= USBCDC_ENDPOINT_SIZE)
{
USB_SetEPRXStatus(&(USB->EP3R), USB_EP_RX_VALID);
USBCDC_RXSuspended = false;
}
}
return length;
}
// Build a USB in packet out of data from the transmit ring buffer and mark it
// valid for the host to fetch it
static void USBCDC_BuildInPacket(void)
{
// Wait while there's still data being transmitted
while(USB_BTABLE_ENTRIES[2].COUNT_TX);
int packet_length = 0;
int ring_buffer_bytes = USBCDC_TXBufferWrite - USBCDC_TXBufferRead;
if(ring_buffer_bytes < 0)
{
// Ring buffer read address is bigger than the write address, meaning
// the write has wrapped around. Let's first deal with the data until
// the end of the buffer
int until_wraparound = USBCDC_TXBuffer + CONFIG_USB_RINGBUFFER_SIZE
- USBCDC_TXBufferRead;
if(until_wraparound > USBCDC_ENDPOINT_SIZE)
{
// More bytes to be sent than there is space in the PMA buffer
packet_length = USBCDC_ENDPOINT_SIZE;
USB_MemoryToPMA(USB_BTABLE_ENTRIES[2].ADDR_TX, USBCDC_TXBufferRead,
packet_length);
USBCDC_TXBufferRead += packet_length;
}
else
{
// The rest of the buffer until the end of the ring buffer will fit
// into the PMA buffer, so we can transmit everything and reset the
// read pointer to the ring buffer's beginning.
packet_length = until_wraparound;
USB_MemoryToPMA(USB_BTABLE_ENTRIES[2].ADDR_TX, USBCDC_TXBufferRead,
packet_length);
USBCDC_TXBufferRead = USBCDC_TXBuffer;
}
}
else if(ring_buffer_bytes > 0)
{
// This /could/ have been an `if` instead of an `else if` to fill up
// the PMA buffer completely if the ring buffer contents wrap around and
// are larger than the PMA area if both parts are combined. However,
// the PMA area only supports 16-bit accesses and an unaligned read
// pointer would lead to problems at the wraparound boundary unless
// specially handeled. Just sending two packets here is way simpler
// than adding handling for all border cases.
if(ring_buffer_bytes > USBCDC_ENDPOINT_SIZE)
{
packet_length = USBCDC_ENDPOINT_SIZE;
}
else
{
packet_length = ring_buffer_bytes;
}
USB_MemoryToPMA(USB_BTABLE_ENTRIES[2].ADDR_TX, USBCDC_TXBufferRead,
packet_length);
USBCDC_TXBufferRead += packet_length;
}
if(packet_length != 0)
{
USBCDC_TXTransferring = true;
// Mark the in packet, which is now stored in PMA memory, as valid
USB_BTABLE_ENTRIES[2].COUNT_TX = packet_length;
USB_SetEPTXStatus(&(USB->EP2R), USB_EP_TX_VALID);
}
}
void USBCDC_SendData(const void *data, int length)
{
while(length > 0)
{
// Block until ring buffer is no longer full
int ring_buffer_space = 0;
while(ring_buffer_space == 0)
{
// The ring buffer is empty when read and write pointer are the
// same. It is considered full when the write pointer is one spot
// before the read pointer. One index is thus unused to make the
// distinction between empty and full possible.
ring_buffer_space = USBCDC_TXBufferRead - USBCDC_TXBufferWrite - 1;
if(ring_buffer_space < 0)
{
ring_buffer_space += CONFIG_USB_RINGBUFFER_SIZE;
}
}
// If more data is supplied than would fit in the ring buffer, copy as
// much as possible in this iteration.
int store_length = ring_buffer_space;
if(length < store_length)
{
store_length = length;
}
length -= store_length;
// If copying all data into the ring buffer needs a write pointer
// wraparound, copy the part before the ring buffer 'end' first
int until_wraparound = USBCDC_TXBuffer + CONFIG_USB_RINGBUFFER_SIZE
- USBCDC_TXBufferWrite;
if(store_length >= until_wraparound)
{
memcpy(USBCDC_TXBufferWrite, data, until_wraparound);
USBCDC_TXBufferWrite = USBCDC_TXBuffer;
store_length -= until_wraparound;
data += until_wraparound;
}
memcpy(USBCDC_TXBufferWrite, data, store_length);
USBCDC_TXBufferWrite += store_length;
data += store_length;
// If there isn't already data being transferred, the process has to be
// kicked off. The later USB packets will be built from the USB transfer
// complete interrupts.
if(!USBCDC_TXTransferring)
{
USBCDC_BuildInPacket();
}
}
}
void USBCDC_HandleDataInComplete(void)
{
if(USBCDC_TXBufferRead == USBCDC_TXBufferWrite)
{
USBCDC_TXTransferring = false;
}
else
{
USBCDC_BuildInPacket();
}
}