From 397d153a447d78e5b71e33f6d1c4557fa1d26ea9 Mon Sep 17 00:00:00 2001 From: Sean Cross Date: Tue, 1 Jan 2019 23:03:45 +0800 Subject: [PATCH] grainuum: initial commit Signed-off-by: Sean Cross --- include/grainuum.h | 592 +++++++++++++++++++++++++++++++++++++++++++ include/usb.h | 1 + src/grainuum-phy.c | 187 ++++++++++++++ src/grainuum-state.c | 362 ++++++++++++++++++++++++++ src/main.c | 1 + src/usb.c | 29 +++ 6 files changed, 1172 insertions(+) create mode 100644 include/grainuum.h create mode 100644 src/grainuum-phy.c create mode 100644 src/grainuum-state.c diff --git a/include/grainuum.h b/include/grainuum.h new file mode 100644 index 0000000..4050384 --- /dev/null +++ b/include/grainuum.h @@ -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 + +/** + * @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 */ diff --git a/include/usb.h b/include/usb.h index 9868dc1..dc76772 100644 --- a/include/usb.h +++ b/include/usb.h @@ -6,6 +6,7 @@ extern "C" { #endif void usb_isr(void); +void usb_init(void); #ifdef __cplusplus } diff --git a/src/grainuum-phy.c b/src/grainuum-phy.c new file mode 100644 index 0000000..268e366 --- /dev/null +++ b/src/grainuum-phy.c @@ -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 +#include + +__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); +} diff --git a/src/grainuum-state.c b/src/grainuum-state.c new file mode 100644 index 0000000..260ed0f --- /dev/null +++ b/src/grainuum-state.c @@ -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; + } +} diff --git a/src/main.c b/src/main.c index 1efeb01..e87ae73 100644 --- a/src/main.c +++ b/src/main.c @@ -27,6 +27,7 @@ static void init(void) { irq_setmask(0); irq_setie(1); uart_init(); + usb_init(); init_printf(NULL, rv_putchar); } diff --git a/src/usb.c b/src/usb.c index 911a546..d896b70 100644 --- a/src/usb.c +++ b/src/usb.c @@ -1,5 +1,34 @@ +#include #include +#include + +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; } \ No newline at end of file