grainuum: initial commit

Signed-off-by: Sean Cross <sean@xobs.io>
This commit is contained in:
Sean Cross 2019-01-01 23:03:45 +08:00
parent b31c0213b5
commit 397d153a44
6 changed files with 1172 additions and 0 deletions

592
include/grainuum.h Normal file
View File

@ -0,0 +1,592 @@
/****************************************************************************
* Grainuum Software USB Stack *
* *
* MIT License: *
* Copyright (c) 2016 Sean Cross *
* *
* 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, distribute with modifications, 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: *
* *
* The above copyright notice and this permission notice shall be included *
* in all copies or substantial portions of the Software. *
* *
* 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 ABOVE 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. *
* *
* Except as contained in this notice, the name(s) of the above copyright *
* holders shall not be used in advertising or otherwise to promote the *
* sale, use or other dealings in this Software without prior written *
* authorization. *
****************************************************************************/
#ifndef _GRAINUUM_H
#define _GRAINUUM_H
#include <stdint.h>
/**
* @brief Extra fields for GrainuumState struct.
* @note You probably can ignore this.
*/
#ifndef GRAINUUM_STATE_EXTRA
#define GRAINUUM_STATE_EXTRA
#endif /* GRAINUUM_STATE_EXTRA */
/**
* @brief Extra fields for GrainuumUSB struct.
* @note Use this to store context and thread information.
*/
#ifndef GRAINUUM_EXTRA
#define GRAINUUM_EXTRA
#endif /* GRAINUUM_EXTRA */
#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 SYNC_FRAME 12
#define GET_REPORT 1
#define GET_IDLE 2
#define GET_PROTOCOL 3
#define SET_REPORT 9
#define SET_IDLE 10
#define SET_PROTOCOL 11
enum usb_pids {
USB_PID_RESERVED = 0xf0,
USB_PID_OUT = 0xe1,
USB_PID_ACK = 0xd2,
USB_PID_DATA0 = 0xc3,
USB_PID_PING = 0xb4,
USB_PID_SOF = 0xa5,
USB_PID_NYET = 0x96,
USB_PID_DATA2 = 0x87,
USB_PID_SPLIT = 0x78,
USB_PID_IN = 0x69,
USB_PID_NAK = 0x5a,
USB_PID_DATA1 = 0x4b,
USB_PID_ERR = 0x3c,
USB_PID_SETUP = 0x2d,
USB_PID_STALL = 0x1e,
USB_PID_MDATA = 0x0f,
};
struct GrainuumUSB;
struct GrainuumState;
struct GrainuumConfig;
/* Function callbacks */
/* Each of these functions are called by the USB system to get a buffer.
* On return, *data will point to the buffer, and the number of bytes
* in the buffer will be returned.
*
* If the data does not exist, return 0.
*/
typedef int (*get_usb_descriptor_t)(struct GrainuumUSB *usb,
const void *pkt,
const void **data);
typedef void (*usb_set_config_num_t)(struct GrainuumUSB *usb,
int configNum);
/*
* Called when doing an OUT xfer (data to device) to get a buffer for
* the specified endpoint.
* It is up to the user to ensure the buffer is large enough.
*/
typedef void * (*usb_get_buffer_t)(struct GrainuumUSB *usb,
uint8_t epnum,
int32_t *size);
/*
* When data is received (i.e. OUT EP), this function will be called.
*/
typedef int (*usb_data_in_t)(struct GrainuumUSB *usb,
uint8_t epnum,
uint32_t bytes,
const void *data);
/**
* @brief Called immediately after @p grainuumSendData() has queued data.
* @note This function can be used to e.g. sleep a thread.
* @param[in] usb pointer to the @p GrainuumUSB object
* @param[in] epnum endpoint number of the transfer
* @param[in] data pointer to the data being written
* @param[in] size number of bytes being written
* @api
*/
typedef void (*usb_data_out_start_t)(struct GrainuumUSB *usb,
int epnum,
const void *data,
int size);
/**
* @brief Called once all data has been sent.
* @note This function can be used to e.g. wake up a thread.
* @param[out] usb pointer to the @p GrainuumUSB object
* @param[out] result whether the transfer was successful (0), or had an error.
* @api
*/
typedef int (*usb_data_out_finish_t)(struct GrainuumUSB *usb,
int result);
/* Structure of a USB packet on the wire, plus size field */
struct usb_packet {
union {
struct {
uint8_t pid;
uint8_t data[10]; /* Including CRC */
} __attribute((packed, aligned(4)));
uint8_t raw_data[11];
} __attribute((packed, aligned(4)));
uint8_t size; /* Not including pid (so may be 0) */
/* Checksum omitted */
} __attribute__((packed, aligned(4)));
/* USB Descriptors */
#define DT_DEVICE 0x01
#define DT_CONFIGURATION 0x02
#define DT_STRING 0x03
#define DT_INTERFACE 0x04
#define DT_ENDPOINT 0x05
#define DT_DEVICE_QUALIFIER 0x06
#define DT_OTHER_SPEED_CONFIGURATION 0x07
#define DT_INTERFACE_POWER 0x08
#define DT_HID 0x21
#define DT_HID_REPORT 0x22
#define DT_PID 0x23
struct usb_setup_packet {
uint8_t bmRequestType;
uint8_t bRequest;
union {
uint16_t wValue;
struct {
uint8_t wValueL;
uint8_t wValueH;
};
};
uint16_t wIndex;
uint16_t wLength;
} __attribute__((packed, aligned(4)));
struct usb_device_descriptor {
uint8_t bLength;
uint8_t bDescriptorType;
uint16_t bcdUSB;
uint8_t bDeviceClass;
uint8_t bDeviceSubClass;
uint8_t bDeviceProtocol;
uint8_t bMaxPacketSize0;
uint16_t idVendor;
uint16_t idProduct;
uint16_t bcdDevice;
uint8_t iManufacturer;
uint8_t iProduct;
uint8_t iSerialNumber;
uint8_t bNumConfigurations;
} __attribute__((packed, aligned(4)));
struct usb_configuration_descriptor {
uint8_t bLength; /* Size of this descriptor, in bytes (9) */
uint8_t bDescriptorType; /* DT_CONFIGURATION (2) */
uint16_t wTotalLength; /* Total length of this, plus sizeof(data) */
uint8_t bNumInterfaces; /* Number of interfaces supported by config */
uint8_t bConfigurationValue; /* Value used by Set Configuration */
uint8_t iConfiguration; /* index of string descriptor for config */
uint8_t bmAttributes; /* Bitmap of attributes. D7 must be 1. */
uint8_t bMaxPower; /* Maximum power, in units of 2mA */
uint8_t data[]; /* Remaining descriptors */
} __attribute__((packed, aligned(4)));
struct usb_string_descriptor {
uint8_t bLength; /* sizeof(usb_string_descriptor) + sizeof(data) */
uint8_t bDescriptorType; /* DT_STRING (3) */
uint8_t data[]; /* UTF-16LE string data or lang data(for string 0 */
} __attribute__((packed, aligned(4)));
struct usb_interface_descriptor {
uint8_t bLength; /* sizeof(usb_interface_descriptor) (9) */
uint8_t bDescriptorType; /* DT_INTERFACE (4) */
uint8_t bInterfaceNumber; /* Which interface this describes. Usually 0. */
uint8_t bAlternateSetting; /* ??? */
uint8_t bNumEndpoints; /* Number of endpoints, minus 1 */
uint8_t bInterfaceClass; /* Class code */
uint8_t bInterfaceSubclass; /* Class sub-code */
uint8_t bInterfaceProtocol; /* Protocol code, assigned by USB */
uint8_t iInterface; /* Index of string for this interface */
} __attribute__((packed, aligned(4)));
struct usb_endpoint_descriptor {
uint8_t bLength; /* sizeof(usb_endpoint_descriptor) (7) */
uint8_t bDescriptorType; /* DT_ENDPOINT (5) */
uint8_t bEndpointAddress; /* High bit 1:IN, 0:OUT. Lower 4-bits are EP# */
uint8_t bmAttributes; /* 0=control, 2=bulk, 3=interrupt */
uint16_t wMaxPacketSize; /* Max packet size for this EP */
uint8_t bInterval; /* Polling rate (in 1ms units) */
} __attribute__((packed, aligned(4)));
struct usb_hid_descriptor {
uint8_t bLength; /* sizeof(usb_hid_descriptor) (9) */
uint8_t bDescriptorType; /* DT_HID (0x21) */
uint16_t bcdHID; /* HID class version number, in BCD */
uint8_t bCountryCode; /* Target country (usually 0) */
uint8_t bNumDescriptors; /* Number of HID class descriptors (usually 1) */
uint8_t bReportDescriptorType; /* Report descriptor type (usually 0x22) */
uint16_t wReportDescriptorLength; /* Length of the HID/PID report descriptor */
} __attribute__((packed, aligned(4)));
#define GRAINUUM_BUFFER_ELEMENT_SIZE 12 /* 1 PID, 8 data, 2 CRC16, 1 size */
/* grainuum_buffer is aligned such that its first byte is on a word boundary.
* This is because the first byte of every packet is a PID, which is
* immediately discarded. This leaves the remainder of the packet
* word-aligned.
*/
#define GRAINUUM_BUFFER(name, sz) \
struct { \
uint8_t head; \
uint8_t tail; \
uint8_t padding; \
union { \
uint8_t buffer[(sz) * GRAINUUM_BUFFER_ELEMENT_SIZE]; \
uint8_t elements[sz][GRAINUUM_BUFFER_ELEMENT_SIZE]; \
}; \
} name __attribute__((aligned(4))); \
uint8_t * name ## _head_ptr;
#define GRAINUUM_BUFFER_INIT(name) \
do { \
(name).head = 0; \
(name).tail = 0; \
name ## _head_ptr = (name).buffer; \
} while(0)
#define GRAINUUM_BUFFER_ADVANCE(name) \
do { \
(name).head += GRAINUUM_BUFFER_ELEMENT_SIZE; \
if ((name).head >= sizeof((name).buffer)) \
(name).head = 0; \
name ## _head_ptr = ((name).buffer + (name).head); \
} while(0)
#define GRAINUUM_BUFFER_TOP(name) \
(&((name).buffer[(name).tail]))
#define GRAINUUM_BUFFER_REMOVE(name) \
do { \
(name).tail += GRAINUUM_BUFFER_ELEMENT_SIZE; \
if ((name).tail >= sizeof((name).buffer)) \
(name).tail = 0; \
} while(0)
#define GRAINUUM_BUFFER_IS_EMPTY(name) \
((name).head == (name).tail)
#define GRAINUUM_BUFFER_ENTRY(name) \
name ## _head_ptr
/* Grainuum Structs */
struct GrainuumConfig {
get_usb_descriptor_t getDescriptor;
usb_set_config_num_t setConfigNum;
usb_get_buffer_t getReceiveBuffer;
usb_data_in_t receiveData;
usb_data_out_start_t sendDataStarted;
usb_data_out_finish_t sendDataFinished;
void *data;
struct GrainuumUSB *usb;
} __attribute__((packed, aligned(4)));
struct GrainuumState {
struct GrainuumUSB *usb;
const void *data_out; /* Pointer to the data that's being sent */
int32_t data_out_left; /* How much data has yet to be sent */
int32_t data_out_max; /* The maximum number of bytes to send */
int32_t data_out_epnum; /* Which endpoint the data is for */
struct usb_packet packet; /* Currently-queued packet */
int packet_queued; /* Whether a packet is queued */
uint32_t tok_pos; /* Position within the current token */
void *tok_buf; /* Buffer storing current token's data */
uint8_t tok_epnum; /* Last token's endpoint */
uint8_t data_buffer; /* Whether we're sending DATA0 or DATA1 */
uint8_t packet_type; /* PACKET_SETUP, PACKET_IN, or PACKET_OUT */
uint8_t address; /* Our configured address */
GRAINUUM_STATE_EXTRA
} __attribute__((packed, aligned(4)));
struct GrainuumUSB {
struct GrainuumConfig *cfg; /* Callbacks */
int initialized;
/* USB D- pin specification */
uint32_t usbdnIAddr;
uint32_t usbdnSAddr;
uint32_t usbdnCAddr;
uint32_t usbdnDAddr;
uint32_t usbdnShift;
/* USB D+ pin specification */
uint32_t usbdpIAddr;
uint32_t usbdpSAddr;
uint32_t usbdpCAddr;
uint32_t usbdpDAddr;
uint32_t usbdpShift;
uint32_t usbdnMask;
uint32_t usbdpMask;
uint32_t queued_size;
uint32_t queued_epnum;
const void *queued_data;
struct GrainuumState state; /* Associated state */
GRAINUUM_EXTRA
} __attribute__((packed, aligned(4)));
#ifdef __cplusplus
extern "C" {
#endif
static inline void grainuumWritel(uint32_t value, uint32_t addr)
{
*((volatile uint32_t *)addr) = value;
}
static inline uint32_t grainuumReadl(uint32_t addr)
{
return *(volatile uint32_t *)addr;
}
/*===========================================================================*/
/* Weak hook functions. */
/*===========================================================================*/
/**
* @brief Called just before the USB device is plugged in.
* @param[in] usb pointer to the @p GrainuumUSB object
* @api
*/
void grainuumConnectPre(struct GrainuumUSB *usb);
/**
* @brief Called just after the USB device is plugged in.
* @param[in] usb pointer to the @p GrainuumUSB object
* @api
*/
void grainuumConnectPost(struct GrainuumUSB *usb);
/**
* @brief Called just before the USB device is unplugged.
* @param[in] usb pointer to the @p GrainuumUSB object
* @api
*/
void grainuumDisconnectPre(struct GrainuumUSB *usb);
/**
* @brief Called just after the USB device is unplugged.
* @param[in] usb pointer to the @p GrainuumUSB object
* @api
*/
void grainuumDisconnectPost(struct GrainuumUSB *usb);
/**
* @brief Called just before the USB device is first initialized.
* @param[in] usb pointer to the @p GrainuumUSB object
* @api
*/
void grainuumInitPre(struct GrainuumUSB *usb);
/**
* @brief Called just before the USB device is first initialized.
* @param[in] usb pointer to the @p GrainuumUSB object
* @api
*/
void grainuumInitPost(struct GrainuumUSB *usb);
/**
* @brief Called immediately after a packet has been received.
* @note This is called from an interrupt context. Data will
* be stored in the buffer that was passed to @p grainuumCaptureI()
* @param[in] usb pointer to the @p GrainuumUSB object
* @iclass
*/
void grainuumReceivePacket(struct GrainuumUSB *usb);
/*===========================================================================*/
/* External declarations. */
/*===========================================================================*/
/**
* @brief Returns nonzero if Grainuum has been initialized.
* @param[in] usb pointer to the @p GrainuumUSB object.
* @return nonzero if @p GrainuumUSB is initialized.
* @retval 0 Object is not initilized.
* @api
*/
int grainuumInitialized(struct GrainuumUSB *usb);
/**
* @brief Queues some data to be sent to the host.
* @note After the first 8 bytes, @p data must remain valid
* until the transfer has completed. This generally
* means you can send const data stored in the text
* section, or small 8-byte packets.
* @param[in] usb pointer to the @p GrainuumUSB object.
* @param[in] epnum endpoint number of the transfer.
* @param[in] data pointer to the data being written.
* @param[in] size number of bytes being written.
* @return 0 if the transfer completed successfully.
* @retval 0 Transfer completed successfully.
* @api
*/
int grainuumSendData(struct GrainuumUSB *usb, int epnum, const void *data, int size);
/**
* @brief Clears the send buffer, if not empty.
* @note If data has already been queued for the PHY, then
* this will not prevent it from being sent.
* This function is intended to be used to prevent
* grainuumSendData() from returning -EAGAIN.
* @param[in] usb pointer to the @p GrainuumUSB object.
* @api
*/
void grainuumDropData(struct GrainuumUSB *usb);
/**
* @brief Determines if data is already queued.
* @note If data has been queued, then this will return
* nonzero. If this returns zero, then you can
* trust grainuumSendData() will succeed.
* @param[in] usb pointer to the @p GrainuumUSB object.
* @return Nonzero if data is already queued.
* @api
*/
int grainuumDataQueued(struct GrainuumUSB *usb);
/**
* @brief Process one received packet through the Grainuum state machine.
* @note This feeds USB packets into the state machine. It should not
* be called as part of an interrupt.
* @param[in] usb pointer to the @p GrainuumUSB object.
* @param[in] packet The USB packet that was most recently received, with byte 12 holding the size.
* @api
*/
void grainuumProcess(struct GrainuumUSB *usb,
const uint8_t packet[12]);
/**
* @brief Initialize the Grainuum USB system.
* @note This is meant to run as part of an interrupt. Pass
* the storage buffer in as @p samples. The number
* of bytes that were read will be stored in the last
* byte of the array. For best performance, make
* sure that @p sample is on byte 3 of a 4-byte boundary,
* so that samples[1] is on a word boundary. The @p GrainuumUSB
* object will start out disconnected.
* @param[in] usb Pointer to the @p GrainuumUSB object to initialize.
* @param[in] link Pointer to the @p GrainuumConfig object to use.
* @api
*/
void grainuumInit(struct GrainuumUSB *usb, struct GrainuumConfig *link);
/**
* @brief Capture a USB packet from the wire.
* @note This is meant to run as part of an interrupt. Pass
* the storage buffer in as @p samples. The number
* of bytes that were read will be stored in the last
* byte of the array. For best performance, make
* sure that @p sample is on byte 3 of a 4-byte boundary,
* so that samples[1] is on a word boundary.
* @param[in] usb pointer to the @p GrainuumUSB object.
* @param[in] packet Buffer to store the read samples.
* @api
*/
int grainuumCaptureI(struct GrainuumUSB *usb, uint8_t samples[67]);
/**
* @brief Internal function. Queues 8 bytes to be sent by the phy.
* @note This is an internal function, and is not meant to be called.
* It is meant to queue properly-formatted USB packets complete
* with CRC-16 (if required).
* @param[in] usb pointer to the @p GrainuumUSB object.
* @param[in] epnum The endpoint number to queue data for.
* @param[in] buffer The data to queue.
* @param[in] size The number of bytes that are queued.
* @notapi
*/
void grainuumWriteQueue(struct GrainuumUSB *usb, int epnum,
const void *buffer, int size);
/**
* @brief Simulates plugging the device into USB.
* @note All USB Connect hooks will be called.
* The default USB state is "disconnected",
* so @p grainuumConnect() must be called
* to start communications.
* @param[in] usb pointer to the @p GrainuumUSB object.
* @api
*/
void grainuumConnect(struct GrainuumUSB *usb);
/**
* @brief Simulates unplugging the device from USB.
* @note All USB Disconnect hooks will be called.
* @param[in] usb pointer to the @p GrainuumUSB object.
* @api
*/
void grainuumDisconnect(struct GrainuumUSB *usb);
/**
* @brief Reads one packet from the wire.
* @note This must be called from an interrupt context with
* interrupts disabled.
* @param[in] usb Pointer to the @p GrainuumUSB object.
* @param[out] samples Buffer where the samples will be stored.
* @return The number of bytes read, or negative on error
* @retval -1 Timeout while reading.
* @retval -2 Read too many bits.
* @retval -3 Unable to find sync end.
* @retval -4 Probably a keepalive packet.
* @notapi
*/
int usbPhyReadI(const struct GrainuumUSB *usb, uint8_t samples[11]);
/**
* @brief Writes one packet from the wire.
* @note This must be called from an interrupt context with
* interrupts disabled.
* @param[in] usb Pointer to the @p GrainuumUSB object.
* @param[in] samples Buffer where the samples will be stored.
* @param[in] size Number of bytes to write.
* @notapi
*/
void usbPhyWriteI(const struct GrainuumUSB *usb, const void *buffer, uint32_t size);
#ifdef __cplusplus
};
#endif
#endif /* _GRAINUUM_H */

View File

@ -6,6 +6,7 @@ extern "C" {
#endif
void usb_isr(void);
void usb_init(void);
#ifdef __cplusplus
}

187
src/grainuum-phy.c Normal file
View File

@ -0,0 +1,187 @@
/****************************************************************************
* Grainuum Software USB Stack *
* *
* MIT License: *
* Copyright (c) 2016 Sean Cross *
* *
* 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, distribute with modifications, 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: *
* *
* The above copyright notice and this permission notice shall be included *
* in all copies or substantial portions of the Software. *
* *
* 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 ABOVE 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. *
* *
* Except as contained in this notice, the name(s) of the above copyright *
* holders shall not be used in advertising or otherwise to promote the *
* sale, use or other dealings in this Software without prior written *
* authorization. *
****************************************************************************/
#include <generated/csr.h>
#include <grainuum.h>
__attribute__((weak))
void grainuumConnectPre(struct GrainuumUSB *usb)
{
(void)usb;
}
__attribute__((weak))
void grainuumConnectPost(struct GrainuumUSB *usb)
{
(void)usb;
}
__attribute__((weak))
void grainuumDisconnectPre(struct GrainuumUSB *usb)
{
(void)usb;
}
__attribute__((weak))
void grainuumDisconnectPost(struct GrainuumUSB *usb)
{
(void)usb;
}
__attribute__((weak))
void grainuumReceivePacket(struct GrainuumUSB *usb)
{
(void)usb;
}
__attribute__((weak))
void grainuumInitPre(struct GrainuumUSB *usb)
{
(void)usb;
}
__attribute__((weak))
void grainuumInitPost(struct GrainuumUSB *usb)
{
(void)usb;
}
/* --- */
void grainuum_receive_packet(struct GrainuumUSB *usb) {
grainuumReceivePacket(usb);
}
int grainuumCaptureI(struct GrainuumUSB *usb, uint8_t samples[67])
{
int ret;
const uint8_t nak_pkt[] = {USB_PID_NAK};
const uint8_t ack_pkt[] = {USB_PID_ACK};
ret = usbPhyReadI(usb, samples);
if (ret <= 0) {
if (ret != -1)
usbPhyWriteI(usb, nak_pkt, sizeof(nak_pkt));
return 0;
}
/* Save the byte counter for later inspection */
samples[11] = ret;
switch (samples[0]) {
case USB_PID_IN:
/* Make sure we have queued data, and that it's for this particular EP */
if ((!usb->queued_size)
|| (((((const uint16_t *)(samples+1))[0] >> 7) & 0xf) != usb->queued_epnum))
{
usbPhyWriteI(usb, nak_pkt, sizeof(nak_pkt));
break;
}
usbPhyWriteI(usb, usb->queued_data, usb->queued_size);
break;
case USB_PID_SETUP:
grainuum_receive_packet(usb);
break;
case USB_PID_OUT:
grainuum_receive_packet(usb);
break;
case USB_PID_ACK:
/* Allow the next byte to be sent */
usb->queued_size = 0;
grainuum_receive_packet(usb);
break;
case USB_PID_DATA0:
case USB_PID_DATA1:
usbPhyWriteI(usb, ack_pkt, sizeof(ack_pkt));
grainuum_receive_packet(usb);
break;
default:
usbPhyWriteI(usb, nak_pkt, sizeof(nak_pkt));
break;
}
return ret;
}
int grainuumInitialized(struct GrainuumUSB *usb)
{
if (!usb)
return 0;
return usb->initialized;
}
void grainuumWriteQueue(struct GrainuumUSB *usb, int epnum,
const void *buffer, int size)
{
usb->queued_data = buffer;
usb->queued_epnum = epnum;
usb->queued_size = size;
}
void grainuumInit(struct GrainuumUSB *usb,
struct GrainuumConfig *cfg) {
if (usb->initialized)
return;
grainuumInitPre(usb);
usb->cfg = cfg;
usb->state.usb = usb;
cfg->usb = usb;
usb->initialized = 1;
grainuumInitPost(usb);
}
void grainuumDisconnect(struct GrainuumUSB *usb) {
grainuumDisconnectPre(usb);
usb_pullup_out_write(0);
grainuumDisconnectPost(usb);
}
void grainuumConnect(struct GrainuumUSB *usb) {
grainuumConnectPre(usb);
usb_pullup_out_write(1);
grainuumConnectPost(usb);
}

362
src/grainuum-state.c Normal file
View File

@ -0,0 +1,362 @@
/****************************************************************************
* Grainuum Software USB Stack *
* *
* MIT License: *
* Copyright (c) 2016 Sean Cross *
* *
* 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, distribute with modifications, 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: *
* *
* The above copyright notice and this permission notice shall be included *
* in all copies or substantial portions of the Software. *
* *
* 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 ABOVE 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. *
* *
* Except as contained in this notice, the name(s) of the above copyright *
* holders shall not be used in advertising or otherwise to promote the *
* sale, use or other dealings in this Software without prior written *
* authorization. *
****************************************************************************/
#include "grainuum.h"
#ifndef NULL
#define NULL ((void *)0)
#endif
void *memcpy(void *dest, const void *src, unsigned int n);
enum usb_state_packet_type {
packet_type_none,
packet_type_setup,
packet_type_setup_in,
packet_type_setup_out,
packet_type_in,
packet_type_out,
};
__attribute__((weak))
void grainuumSendWait(struct GrainuumUSB *usb, int epnum,
const void *data, int size)
{
(void)usb;
(void)epnum;
(void)data;
(void)size;
}
static uint16_t crc16_add(uint16_t crc, uint8_t c, uint16_t poly)
{
uint8_t i;
for (i = 0; i < 8; i++) {
if ((crc ^ c) & 1)
crc = (crc >> 1) ^ poly;
else
crc >>= 1;
c >>= 1;
}
return crc;
}
static uint16_t crc16(const uint8_t *data, uint32_t size,
uint16_t init, uint32_t poly)
{
while (size--)
init = crc16_add(init, *data++, poly);
return init;
}
static void grainuum_state_clear_tx(struct GrainuumState *state, int result)
{
struct GrainuumUSB *usb = state->usb;
/* If a thread is blocking, wake it up with a failure */
if (usb->cfg->sendDataFinished && state->packet_queued)
usb->cfg->sendDataFinished(usb, result);
state->data_out_left = 0;
state->data_out_max = 0;
state->data_out = NULL;
state->packet_queued = 0;
}
static void grainuum_state_process_tx(struct GrainuumState *state)
{
uint16_t crc;
struct GrainuumUSB *usb = state->usb;
/* Don't allow us to re-prepare data */
if (state->packet_queued) {
return;
}
state->packet_queued = 1;
/* If there's no data to send, then don't send any */
if (!state->data_out) {
state->packet_queued = 0;
return;
}
/* If we've sent all of our data, then there's nothing else to send */
if ((state->data_out_left < 0) || (state->data_out_max < 0)) {
grainuum_state_clear_tx(state, 0);
return;
}
/* Pick the correct PID, DATA0 or DATA1 */
if (state->data_buffer & (1 << state->tok_epnum))
state->packet.pid = USB_PID_DATA1;
else
state->packet.pid = USB_PID_DATA0;
/* If there's no data, prepare a special NULL packet */
if ((state->data_out_left == 0) || (state->data_out_max == 0)) {
/* The special-null thing only happens for EP0 */
if (state->data_out_epnum != 0) {
grainuum_state_clear_tx(state, 0);
return;
}
state->packet.data[0] = 0; /* CRC16 for empty packets is 0 */
state->packet.data[1] = 0;
state->packet.size = 2;
grainuumWriteQueue(usb, state->data_out_epnum,
&state->packet, state->packet.size + 1);
return;
}
/* Keep the packet size to 8 bytes max */
if (state->data_out_left > 8)
state->packet.size = 8;
else
state->packet.size = state->data_out_left;
/* Limit the amount of data transferred to data_out_max */
if (state->packet.size > state->data_out_max)
state->packet.size = state->data_out_max;
/* Copy over data bytes */
memcpy(state->packet.data, state->data_out, state->packet.size);
/* Calculate and copy the crc16 */
crc = ~crc16(state->packet.data, state->packet.size, 0xffff, 0xa001);
state->packet.data[state->packet.size++] = crc;
state->packet.data[state->packet.size++] = crc >> 8;
/* Prepare the packet, including the PID at the end */
grainuumWriteQueue(usb, state->data_out_epnum,
&state->packet, state->packet.size + 1);
}
/* Called when a packet is ACKed.
* Updates the outgoing packet buffer.
*/
static void usbStateTransferSuccess(struct GrainuumState *state)
{
/* Reduce the amount of data left.
* If the packet is divisible by 8, this will cause one more call
* to this function with state->data_out_left == 0. This will send
* a NULL packet, which indicates end-of-transfer.
*/
state->data_out_left -= 8;
state->data_out_max -= 8;
state->data_out += 8;
if ((state->data_out_left < 0) || (state->data_out_max < 0)) {
grainuum_state_clear_tx(state, 0);
/* End of a State setup packet */
if (state->packet_type == packet_type_setup_out)
state->packet_type = packet_type_none;
if (state->packet_type == packet_type_setup_in)
state->packet_type = packet_type_none;
if (state->packet_type == packet_type_out)
state->packet_type = packet_type_none;
}
state->packet_queued = 0;
}
/* Send data down the wire, interrupting any existing
* data that may be queued.
*/
static int grainuum_state_send_data(struct GrainuumState *state,
int epnum,
const void *data,
int size,
int max)
{
/* De-queue any data that may already be queued. */
grainuum_state_clear_tx(state, 1);
state->data_out_epnum = epnum;
state->data_out_left = size;
state->data_out_max = max;
state->data_out = data;
return 0;
}
void grainuumDropData(struct GrainuumUSB *usb)
{
usb->state.packet_queued = 0;
usb->state.data_out = 0;
grainuumWriteQueue(usb, 0, NULL, 0);
}
int grainuumDataQueued(struct GrainuumUSB *usb)
{
return (usb->state.data_out || usb->state.packet_queued);
}
int grainuumSendData(struct GrainuumUSB *usb, int epnum,
const void *data, int size)
{
struct GrainuumState *state = &usb->state;
int ret;
if (state->data_out || !state->address || state->packet_queued) {
return -11; /* EAGAIN */
}
ret = grainuum_state_send_data(state, epnum, data, size, size);
if (ret)
return ret;
grainuum_state_process_tx(state);
if (usb->cfg->sendDataStarted)
usb->cfg->sendDataStarted(usb, epnum, data, size);
return 0;
}
static int grainuum_state_process_setup(struct GrainuumState *state, const uint8_t packet[10])
{
const struct usb_setup_packet *setup;
const void *response = (void *)-1;
uint32_t response_len = 0;
struct GrainuumUSB *usb = state->usb;
struct GrainuumConfig *cfg = usb->cfg;
setup = (const struct usb_setup_packet *)packet;
if ((setup->bmRequestType == 0x00) && (setup->bRequest == SET_ADDRESS)) {
state->address = setup->wValue;
}
else if ((setup->bmRequestType == 0x00) && (setup->bRequest == SET_CONFIGURATION)) {
if (cfg->setConfigNum)
cfg->setConfigNum(usb, setup->wValue);
}
else {
response_len = cfg->getDescriptor(usb, setup, &response);
}
grainuum_state_send_data(state, state->tok_epnum, response, response_len, setup->wLength);
return 0;
}
static void grainuum_state_parse_data(struct GrainuumState *state,
const uint8_t packet[10],
uint32_t size)
{
(void)size;
struct GrainuumUSB *usb = state->usb;
switch (state->packet_type) {
case packet_type_setup:
grainuum_state_process_setup(state, packet);
grainuum_state_process_tx(state);
state->packet_type = packet_type_none;
break;
case packet_type_out:
// XXX HACK: An OUT packet gets generated (on Windows at least) when
// terminating a SETUP sequence. This seems odd.
if (state->tok_epnum == 0)
break;
// Copy over the packet, minus the CRC16
memcpy(state->tok_buf + state->tok_pos, packet, size - 2);
state->tok_pos += (size - 2);
if (!usb->cfg->receiveData(usb, state->tok_epnum, size - 2, packet))
state->packet_type = packet_type_none;
break;
case packet_type_in:
case packet_type_none:
default:
break;
}
}
static inline void grainuum_state_parse_token(struct GrainuumState *state,
const uint8_t packet[2])
{
state->tok_epnum = (((const uint16_t *)packet)[0] >> 7) & 0xf;
/*state->tok_addr = (((const uint16_t *)packet)[0] >> 11) & 0x1f; // Field unused in this code*/
}
void grainuumProcess(struct GrainuumUSB *usb,
const uint8_t packet[12])
{
uint32_t size = packet[11];
struct GrainuumState *state = &usb->state;
switch(packet[0]) {
case USB_PID_SETUP:
state->packet_type = packet_type_setup;
grainuum_state_clear_tx(state, 1);
grainuum_state_parse_token(state, packet + 1);
break;
case USB_PID_DATA0:
state->data_buffer |= (1 << state->tok_epnum);
grainuum_state_parse_data(state, packet + 1, size - 1);
break;
case USB_PID_DATA1:
state->data_buffer &= ~(1 << state->tok_epnum);
grainuum_state_parse_data(state, packet + 1, size - 1);
break;
case USB_PID_OUT:
grainuum_state_parse_token(state, packet + 1);
state->packet_type = packet_type_out;
state->tok_pos = 0;
state->tok_buf = usb->cfg->getReceiveBuffer(usb, state->tok_epnum, NULL);
break;
case USB_PID_ACK:
state->data_buffer ^= (1 << state->tok_epnum);
usbStateTransferSuccess(state);
if (state->data_out) {
grainuum_state_process_tx(state);
}
else {
grainuum_state_clear_tx(state, 0);
}
break;
default:
break;
}
}

View File

@ -27,6 +27,7 @@ static void init(void) {
irq_setmask(0);
irq_setie(1);
uart_init();
usb_init();
init_printf(NULL, rv_putchar);
}

View File

@ -1,5 +1,34 @@
#include <grainuum.h>
#include <usb.h>
#include <generated/csr.h>
static struct GrainuumConfig cfg;
static struct GrainuumUSB usb;
static uint8_t usb_buf[67];
void usb_isr(void) {
grainuumCaptureI(&usb, usb_buf);
return;
}
void usb_init(void) {
grainuumInit(&usb, &cfg);
return;
}
void usbPhyWriteI(const struct GrainuumUSB *usb, const void *buffer, uint32_t size) {
(void)usb;
const uint8_t *ubuffer = (const uint8_t *)buffer;
uint32_t i = 0;
while (i < size)
usb_obuf_head_write(ubuffer[i]);
}
int usbPhyReadI(const struct GrainuumUSB *usb, uint8_t *samples) {
(void)usb;
int count = 0;
while (!usb_ibuf_empty_read()) {
samples[count++] = usb_ibuf_head_read();
}
return count;
}