foboot/include/grainuum.h
Sean Cross 5f0d83b2e2 wip: packets kinda flow now
Signed-off-by: Sean Cross <sean@xobs.io>
2019-01-25 14:28:03 +13:00

603 lines
22 KiB
C

/****************************************************************************
* 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 */
#define GRAINUUM_PACKET_SIZE_MAX 64
/**
* @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,
};
enum valenty_usb_pids {
VUSB_PID_IN = 0x2,
VUSB_PID_OUT = 0x0,
VUSB_PID_SETUP = 0x3,
};
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[GRAINUUM_PACKET_SIZE_MAX + 2]; /* Including CRC */
} __attribute((packed, aligned(4)));
uint8_t raw_data[GRAINUUM_PACKET_SIZE_MAX + 2];
} __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,
uint8_t pid,
const uint8_t packet[12],
uint32_t size);
/**
* @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 */