Last active
June 19, 2023 15:02
-
-
Save jdryg/5af306c28c78f619a51c797a5963234e to your computer and use it in GitHub Desktop.
USB CDC
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "usb_desc_builder.h" | |
#include "allocator.h" | |
#include <usb_stack/usb.h> | |
#include <stddef.h> | |
#include <string.h> | |
#include <memory.h> | |
typedef struct USBDescBuilder | |
{ | |
StackAllocator* m_Allocator; | |
uint8_t* m_StringDescPtr; | |
uint8_t* m_DeviceDescPtr; | |
uint8_t* m_ConfigDescPtr; | |
USBDescConfig* m_CurConfig; | |
USBDescIAD* m_CurIAD; | |
USBDescInterface* m_CurInterface; | |
uint32_t m_NextStringID; | |
uint32_t m_NextInterfaceID; | |
uint8_t m_NextEndpointNum; | |
} USBDescBuilder; | |
inline uint32_t alignSize(uint32_t sz, uint32_t alignment) | |
{ | |
const uint32_t mask = alignment - 1; | |
return (sz & (~mask)) + ((sz & mask) != 0 ? alignment : 0); | |
} | |
USBDescBuilder* initUSBDescBuilder(uint8_t* buffer, uint32_t sz) | |
{ | |
StackAllocator* allocator = stackAllocInit(buffer, sz); | |
if (!allocator) { | |
return NULL; | |
} | |
USBDescBuilder* db = (USBDescBuilder*)stackAlloc(allocator, sizeof(USBDescBuilder)); | |
if (db == NULL) { | |
return NULL; | |
} | |
memset(db, 0, sizeof(USBDescBuilder)); | |
db->m_Allocator = allocator; | |
db->m_NextEndpointNum = 1; // 0 = control endpoint; always present | |
return db; | |
} | |
uint8_t* usbdbGetDeviceDesc(USBDescBuilder* db) | |
{ | |
return db->m_DeviceDescPtr; | |
} | |
uint8_t* usbdbGetConfigDesc(USBDescBuilder* db, uint8_t id, uint32_t* sz) | |
{ | |
if (db->m_DeviceDescPtr == NULL || db->m_ConfigDescPtr == NULL || id >= ((USBDescDevice*)db->m_DeviceDescPtr)->bNumConfigurations) { | |
*sz = 0; | |
return NULL; | |
} | |
uint8_t* ptr = db->m_ConfigDescPtr; | |
while (id-- > 0) { | |
const uint16_t totalLen = ((USBDescConfig*)ptr)->wTotalLength; | |
ptr += totalLen; | |
} | |
*sz = ((USBDescConfig*)ptr)->wTotalLength; | |
return ptr; | |
} | |
uint8_t* usbdbGetConfigDescByVal(USBDescBuilder* db, uint8_t val, uint32_t* sz) | |
{ | |
if (db->m_DeviceDescPtr == NULL || db->m_ConfigDescPtr == NULL) { | |
*sz = 0; | |
return NULL; | |
} | |
const uint8_t* bufferEnd = stackAllocGetPtr(db->m_Allocator); | |
uint8_t* ptr = db->m_ConfigDescPtr; | |
while (ptr < bufferEnd) { | |
const USBDescConfig* cfgDesc = (USBDescConfig*)ptr; | |
if (cfgDesc->bConfigurationValue == val) { | |
*sz = cfgDesc->wTotalLength; | |
return ptr; | |
} | |
ptr += cfgDesc->wTotalLength; | |
} | |
*sz = 0; | |
return NULL; | |
} | |
uint8_t* usbdbGetStringDesc(USBDescBuilder* db, uint16_t langID, uint8_t id, uint32_t* sz) | |
{ | |
if (db->m_StringDescPtr == NULL) { | |
*sz = 0; | |
return NULL; | |
} | |
uint8_t* ptr = db->m_StringDescPtr; | |
if (id != 0) { | |
// Check language ID against string descriptor 0 | |
if (*(uint16_t*)(ptr + 2) != langID) { | |
*sz = 0; | |
return NULL; | |
} | |
while (id-- > 0) { | |
// NOTE: All string descriptors are aligned so we must skip the aligned size | |
// instead of the descriptor's size. | |
const uint8_t descSize = alignSize(ptr[0], USB_DATA_ALIGN_SIZE); | |
ptr += descSize; | |
} | |
} | |
*sz = ptr[0]; | |
return ptr; | |
} | |
uint8_t usbdbAllocEndpoint(USBDescBuilder* db) | |
{ | |
const uint8_t ep = db->m_NextEndpointNum; | |
db->m_NextEndpointNum++; | |
return ep; | |
} | |
int32_t usbdbBeginStrings(USBDescBuilder* db, uint16_t langID) | |
{ | |
if (db->m_StringDescPtr != NULL || db->m_DeviceDescPtr != NULL || db->m_NextStringID != 0) { | |
return USBDescBuilderError_InvalidState; | |
} | |
const uint32_t totalMemory = 0 | |
+ 1 // bLength | |
+ 1 // bDescriptorType | |
+ 2 // bString (Language ID) | |
; | |
uint8_t* ptr = stackAlignedAlloc(db->m_Allocator, totalMemory, USB_DATA_ALIGN_SIZE); | |
if (!ptr) { | |
return USBDescBuilderError_OutOfMemory; | |
} | |
db->m_StringDescPtr = ptr; | |
*ptr++ = totalMemory; | |
*ptr++ = USBDesc_String; | |
*(uint16_t*)ptr = langID; | |
db->m_NextStringID = 1; | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbEndStrings(USBDescBuilder* db) | |
{ | |
// TODO: | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbStringDesc(USBDescBuilder* db, const char* str, uint8_t* stringID) | |
{ | |
if (db->m_StringDescPtr == NULL || db->m_DeviceDescPtr != NULL || db->m_NextStringID == 0) { | |
return USBDescBuilderError_InvalidState; | |
} | |
const uint32_t len = strlen(str); | |
const uint32_t totalMemory = 0 | |
+ 1 // bLength | |
+ 1 // bDescriptorType | |
+ 2 * len // bString | |
; | |
uint8_t* ptr = stackAlignedAlloc(db->m_Allocator, totalMemory, USB_DATA_ALIGN_SIZE); | |
if (!ptr) { | |
return USBDescBuilderError_OutOfMemory; | |
} | |
*ptr++ = 2 * len + 2; | |
*ptr++ = USBDesc_String; | |
for (uint32_t i = 0;i < len;++i) { | |
*(uint16_t*)ptr = (uint16_t)str[i]; | |
ptr += 2; | |
} | |
*stringID = db->m_NextStringID; | |
db->m_NextStringID++; | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbDeviceDesc(USBDescBuilder* db, uint16_t bcdUSB, uint8_t devClass, uint8_t devSubclass, uint8_t devProtocol, uint8_t ctrlMaxPacketSize, uint16_t vendorID, uint16_t productID, uint16_t bcdDevice, uint8_t manufacturerString, uint8_t productString, uint8_t serialNumberString) | |
{ | |
if (db->m_DeviceDescPtr != NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
// TODO: Parameter check (e.g. bMaxPacketSize, valid string indexes, etc.) | |
const uint32_t totalMemory = sizeof(USBDescDevice); | |
uint8_t* ptr = stackAlignedAlloc(db->m_Allocator, totalMemory, USB_DATA_ALIGN_SIZE); | |
if (!ptr) { | |
return USBDescBuilderError_OutOfMemory; | |
} | |
db->m_DeviceDescPtr = ptr; | |
USBDescDevice* desc = (USBDescDevice*)ptr; | |
desc->bLength = sizeof(USBDescDevice); | |
desc->bDescriptorType = USBDesc_Device; | |
desc->bcdUSB = bcdUSB; | |
desc->bDeviceClass = devClass; | |
desc->bDeviceSubClass = devSubclass; | |
desc->bDeviceProtocol = devProtocol; | |
desc->bMaxPacketSize = ctrlMaxPacketSize; | |
desc->idVendor = vendorID; | |
desc->idProduct = productID; | |
desc->bcdDevice = bcdDevice; | |
desc->iManufacturer = manufacturerString; | |
desc->iProduct = productString; | |
desc->iSerialNumber = serialNumberString; | |
desc->bNumConfigurations = 0; // Will be incremented every time usbdbBeginConfigDesc() is called. | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbBeginConfigDesc(USBDescBuilder* db, uint8_t cfgVal, uint8_t cfgString, uint8_t attrs, uint8_t maxPower) | |
{ | |
if (db->m_DeviceDescPtr == NULL || db->m_CurConfig != NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
const uint32_t totalMemory = sizeof(USBDescConfig); | |
uint8_t* ptr = stackAlignedAlloc(db->m_Allocator, totalMemory, USB_DATA_ALIGN_SIZE); | |
if (!ptr) { | |
return USBDescBuilderError_OutOfMemory; | |
} | |
if (db->m_ConfigDescPtr == NULL) { | |
db->m_ConfigDescPtr = ptr; | |
} | |
USBDescConfig* desc = (USBDescConfig*)ptr; | |
desc->bLength = sizeof(USBDescConfig); | |
desc->bDescriptorType = USBDesc_Config; | |
desc->wTotalLength = 0; // Will be filled in usbdbEndConfigDesc() | |
desc->bNumInterfaces = 0; // Will be increased every time usbdbBeginInterfaceDesc() is called. | |
desc->bConfigurationValue = cfgVal; | |
desc->iConfiguration = cfgString; | |
desc->bmAttributes = attrs; | |
desc->bMaxPower = maxPower; | |
db->m_CurConfig = desc; | |
((USBDescDevice*)db->m_DeviceDescPtr)->bNumConfigurations++; | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbEndConfigDesc(USBDescBuilder* db) | |
{ | |
if (db->m_CurConfig == NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
const uint32_t totalLength = (uint32_t)(stackAllocGetPtr(db->m_Allocator) - (uint8_t*)db->m_CurConfig); | |
db->m_CurConfig->wTotalLength = (uint16_t)totalLength; | |
db->m_CurConfig = NULL; | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbBeginInterfaceAssocDesc(USBDescBuilder* db, uint8_t funcClass, uint8_t funcSubclass, uint8_t funcProtocol, uint8_t functionString) | |
{ | |
if (db->m_CurConfig == NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
const uint32_t totalMemory = sizeof(USBDescIAD); | |
uint8_t* ptr = stackAlloc(db->m_Allocator, totalMemory); | |
if (!ptr) { | |
return USBDescBuilderError_OutOfMemory; | |
} | |
USBDescIAD* desc = (USBDescIAD*)ptr; | |
desc->bLength = sizeof(USBDescIAD); | |
desc->bDescriptorType = USBDesc_IAD; | |
desc->bFirstInterface = db->m_NextInterfaceID; | |
desc->bInterfaceCount = 0; // Will be set inside usbdbEndInterfaceAssocDesc() | |
desc->bFunctionClass = funcClass; | |
desc->bFunctionSubClass = funcSubclass; | |
desc->bFunctionProtocol = funcProtocol; | |
desc->iFunction = functionString; | |
db->m_CurIAD = (USBDescIAD*)ptr; | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbEndInterfaceAssocDesc(USBDescBuilder* db) | |
{ | |
if (db->m_CurIAD == NULL || db->m_CurInterface != NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
db->m_CurIAD->bInterfaceCount = db->m_NextInterfaceID - db->m_CurIAD->bFirstInterface; | |
db->m_CurIAD = NULL; | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbBeginInterfaceDesc(USBDescBuilder* db, uint8_t altSetting, uint8_t ifaceClass, uint8_t ifaceSubclass, uint8_t ifaceProtocol, uint8_t interfaceString, uint8_t* interfaceNumber) | |
{ | |
if (db->m_CurConfig == NULL || db->m_CurInterface != NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
const uint32_t totalMemory = sizeof(USBDescInterface); | |
uint8_t* ptr = stackAlloc(db->m_Allocator, totalMemory); | |
if (!ptr) { | |
return USBDescBuilderError_OutOfMemory; | |
} | |
USBDescInterface* desc = (USBDescInterface*)ptr; | |
desc->bLength = sizeof(USBDescInterface); | |
desc->bDescriptorType = USBDesc_Interface; | |
desc->bInterfaceNumber = db->m_NextInterfaceID; | |
desc->bAlternateSetting = altSetting; | |
desc->bNumEndpoints = 0; // Will be increased every time usbdbEndpointDesc() is called. | |
desc->bInterfaceClass = ifaceClass; | |
desc->bInterfaceSubClass = ifaceSubclass; | |
desc->bInterfaceProtocol = ifaceProtocol; | |
desc->iInterface = interfaceString; | |
if (interfaceNumber) { | |
*interfaceNumber = desc->bInterfaceNumber; | |
} | |
// Only increase interface count when alternate setting is 0. | |
if (altSetting == 0) { | |
db->m_CurConfig->bNumInterfaces++; | |
} | |
db->m_NextInterfaceID++; | |
db->m_CurInterface = desc; | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbEndInterfaceDesc(USBDescBuilder* db) | |
{ | |
if (db->m_CurInterface == NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
db->m_CurInterface = NULL; | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbEndpointDesc(USBDescBuilder* db, uint8_t addr, uint8_t attribs, uint16_t maxPacketSize, uint8_t interval) | |
{ | |
if (db->m_CurInterface == NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
const uint32_t totalMemory = sizeof(USBDescEndpoint); | |
uint8_t* ptr = stackAlloc(db->m_Allocator, totalMemory); | |
if (!ptr) { | |
return USBDescBuilderError_OutOfMemory; | |
} | |
USBDescEndpoint* desc = (USBDescEndpoint*)ptr; | |
desc->bLength = sizeof(USBDescEndpoint); | |
desc->bDescriptorType = USBDesc_Endpoint; | |
desc->bEndpointAddress = addr; | |
desc->bmAttributes = attribs; | |
desc->wMaxPacketSize = maxPacketSize; | |
desc->bInterval = interval; | |
db->m_CurInterface->bNumEndpoints++; | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbCDCFuncDesc(USBDescBuilder* db, uint8_t type, uint8_t subtype, const uint8_t* data, uint32_t n) | |
{ | |
if (db->m_CurInterface == NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
const uint32_t totalMemory = sizeof(USBDescFunctionalHeader) + n; | |
uint8_t* ptr = stackAlloc(db->m_Allocator, totalMemory); | |
if (!ptr) { | |
return USBDescBuilderError_OutOfMemory; | |
} | |
USBDescFunctionalHeader* desc = (USBDescFunctionalHeader*)ptr; | |
desc->bFunctionLength = totalMemory; | |
desc->bDescriptorType = type; | |
desc->bDescriptorSubtype = subtype; | |
memcpy(&ptr[sizeof(USBDescFunctionalHeader)], data, n); | |
return USBDescBuilderError_None; | |
} | |
int32_t usbdbCDCHeaderFuncDesc(USBDescBuilder* db, uint16_t bcdCDC) | |
{ | |
return usbdbCDCFuncDesc(db, USBFuncDescType_Interface, USBFuncDescSubtype_Header, (uint8_t*)&bcdCDC, 2); | |
} | |
int32_t usbdbCDCCallManagementFuncDesc(USBDescBuilder* db, uint8_t caps, uint8_t dataInterfaceNum) | |
{ | |
const uint8_t data[] = { caps, dataInterfaceNum }; | |
return usbdbCDCFuncDesc(db, USBFuncDescType_Interface, USBFuncDescSubtype_CallManagement, &data[0], 2); | |
} | |
int32_t usbdbCDCAbstractControlManagementFuncDesc(USBDescBuilder* db, uint8_t caps) | |
{ | |
return usbdbCDCFuncDesc(db, USBFuncDescType_Interface, USBFuncDescSubtype_ACM, &caps, 1); | |
} | |
int32_t usbdbCDCUnionInterfaceFuncDesc(USBDescBuilder* db, const uint8_t* interfaces, uint32_t n) | |
{ | |
if (n < 2) { | |
return USBDescBuilderError_InvalidArg; | |
} | |
return usbdbCDCFuncDesc(db, USBFuncDescType_Interface, USBFuncDescSubtype_Union, interfaces, n); | |
} | |
int32_t usbdbHIDDesc(USBDescBuilder* db, uint16_t bcdHID, uint8_t countryCode, const USBDescHIDReport* descList, uint32_t n) | |
{ | |
if (db->m_CurInterface == NULL) { | |
return USBDescBuilderError_InvalidState; | |
} | |
if (n == 0) { | |
return USBDescBuilderError_InvalidArg; | |
} | |
// NOTE: USBDescHID already includes 1 USBDescHIDReport | |
const uint32_t totalMemory = sizeof(USBDescHID) + sizeof(USBDescHIDReport) * (n - 1); | |
uint8_t* ptr = stackAlloc(db->m_Allocator, totalMemory); | |
if (!ptr) { | |
return USBDescBuilderError_OutOfMemory; | |
} | |
USBDescHID* desc = (USBDescHID*)ptr; | |
desc->bLength = sizeof(USBDescHID); | |
desc->bDescriptorType = USBDesc_HID; | |
desc->bcdHID = bcdHID; | |
desc->bCountryCode = countryCode; | |
desc->bNumDescriptors = n; | |
memcpy(&desc->m_ReportDesc[0], descList, sizeof(USBDescHIDReport) * n); | |
return USBDescBuilderError_None; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef UTIL_USB_DESC_BUILDER_H | |
#define UTIL_USB_DESC_BUILDER_H | |
#include <stdint.h> | |
typedef enum USBDescBuilderError | |
{ | |
USBDescBuilderError_None = 0, | |
USBDescBuilderError_InvalidArg = -1, | |
USBDescBuilderError_InvalidState = -2, | |
USBDescBuilderError_OutOfMemory = -3 | |
} USBDescBuilderError; | |
typedef struct USBDescHIDReport USBDescHIDReport; | |
typedef struct USBDescBuilder USBDescBuilder; | |
USBDescBuilder* initUSBDescBuilder(uint8_t* buffer, uint32_t sz); | |
uint8_t* usbdbGetDeviceDesc(USBDescBuilder* db); | |
uint8_t* usbdbGetConfigDesc(USBDescBuilder* db, uint8_t id, uint32_t* sz); | |
uint8_t* usbdbGetConfigDescByVal(USBDescBuilder* db, uint8_t val, uint32_t* sz); | |
uint8_t* usbdbGetStringDesc(USBDescBuilder* db, uint16_t langID, uint8_t id, uint32_t* sz); | |
uint8_t usbdbAllocEndpoint(USBDescBuilder* db); | |
// NOTE: Only 1 language is supported at the moment. There's no field in the string descriptor to specify which | |
// language each string corresponds to so it's the application's responsibility to handle strings from different languages. | |
int32_t usbdbBeginStrings(USBDescBuilder* db, uint16_t langID); | |
int32_t usbdbEndStrings(USBDescBuilder* db); | |
int32_t usbdbStringDesc(USBDescBuilder* db, const char* str, uint8_t* stringID); | |
// TODO: usbdbMultiFunctionalDeviceDesc() shortcut (class = 0xEF, subclass = 0x02, protocol = 0x01) | |
int32_t usbdbDeviceDesc(USBDescBuilder* db, uint16_t bcdUSB, uint8_t devClass, uint8_t devSubclass, uint8_t devProtocol, uint8_t ctrlMaxPacketSize, uint16_t vendorID, uint16_t productID, uint16_t bcdDevice, uint8_t manufacturerString, uint8_t productString, uint8_t serialNumberString); | |
int32_t usbdbBeginConfigDesc(USBDescBuilder* db, uint8_t cfgVal, uint8_t cfgString, uint8_t attrs, uint8_t maxPower); | |
int32_t usbdbEndConfigDesc(USBDescBuilder* db); | |
int32_t usbdbBeginInterfaceAssocDesc(USBDescBuilder* db, uint8_t funcClass, uint8_t funcSubclass, uint8_t funcProtocol, uint8_t functionString); | |
int32_t usbdbEndInterfaceAssocDesc(USBDescBuilder* db); | |
int32_t usbdbBeginInterfaceDesc(USBDescBuilder* db, uint8_t altSetting, uint8_t ifaceClass, uint8_t ifaceSubclass, uint8_t ifaceProtocol, uint8_t interfaceString, uint8_t* interfaceNumber); | |
int32_t usbdbEndInterfaceDesc(USBDescBuilder* db); | |
int32_t usbdbEndpointDesc(USBDescBuilder* db, uint8_t addr, uint8_t attribs, uint16_t maxPacketSize, uint8_t interval); | |
// Class specific descriptors | |
int32_t usbdbCDCFuncDesc(USBDescBuilder* db, uint8_t type, uint8_t subtype, const uint8_t* data, uint32_t n); | |
int32_t usbdbCDCHeaderFuncDesc(USBDescBuilder* db, uint16_t bcdCDC); | |
int32_t usbdbCDCCallManagementFuncDesc(USBDescBuilder* db, uint8_t caps, uint8_t dataInterfaceNum); | |
int32_t usbdbCDCAbstractControlManagementFuncDesc(USBDescBuilder* db, uint8_t caps); | |
int32_t usbdbCDCUnionInterfaceFuncDesc(USBDescBuilder* db, const uint8_t* interfaces, uint32_t n); | |
int32_t usbdbHIDDesc(USBDescBuilder* db, uint16_t bcdHID, uint8_t countryCode, const USBDescHIDReport* descList, uint32_t n); | |
#endif // UTIL_USB_DESC_BUILDER_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "usb_serial.h" | |
#include "../util/usb_desc_builder.h" | |
#include <atomic.h> | |
#include <stddef.h> | |
#include <stdarg.h> | |
#include <stdio.h> | |
#include <memory.h> | |
#define USB_SERIAL_CONFIG_BUFFER_SIZE 4096 | |
// NOTE(JD): PSTN120.pdf, 6.3.12 SetControlLineState, Table 18 | |
#define USB_CDC_CONTROL_SIGNAL_DTE_PRESENCE_Pos 0 | |
#define USB_CDC_CONTROL_SIGNAL_DTE_PRESENCE_Msk (0x01 << USB_CDC_CONTROL_SIGNAL_DTE_PRESENCE_Pos) | |
#define USB_CDC_CONTROL_SIGNAL_CARRIER_ACTIVATION_Pos 1 | |
#define USB_CDC_CONTROL_SIGNAL_CARRIER_ACTIVATION_Msk (0x01 << USB_CDC_CONTROL_SIGNAL_CARRIER_ACTIVATION_Pos) | |
// NOTE(JD): PSTN120.pdf, 6.5.4 SerialState, Table 31 | |
#define USB_CDC_UART_STATE_RX_CARRIER_Pos 0 | |
#define USB_CDC_UART_STATE_RX_CARRIER_Msk (0x01 << USB_CDC_UART_STATE_RX_CARRIER_Pos) | |
#define USB_CDC_UART_STATE_TX_CARRIER_Pos 1 | |
#define USB_CDC_UART_STATE_TX_CARRIER_Msk (0x01 << USB_CDC_UART_STATE_TX_CARRIER_Pos) | |
#define USB_CDC_UART_STATE_BREAK_Pos 2 | |
#define USB_CDC_UART_STATE_BREAK_Msk (0x01 << USB_CDC_UART_STATE_BREAK_Pos) | |
#define USB_CDC_UART_STATE_RING_SIGNAL_Pos 3 | |
#define USB_CDC_UART_STATE_RING_SIGNAL_Msk (0x01 << USB_CDC_UART_STATE_RING_SIGNAL_Pos) | |
#define USB_CDC_UART_STATE_FRAMING_Pos 4 | |
#define USB_CDC_UART_STATE_FRAMING_Msk (0x01 << USB_CDC_UART_STATE_FRAMING_Pos) | |
#define USB_CDC_UART_STATE_PARITY_Pos 5 | |
#define USB_CDC_UART_STATE_PARITY_Msk (0x01 << USB_CDC_UART_STATE_PARITY_Pos) | |
#define USB_CDC_UART_STATE_OVERRUN_Pos 6 | |
#define USB_CDC_UART_STATE_OVERRUN_Msk (0x01 << USB_CDC_UART_STATE_OVERRUN_Pos) | |
// NOTE(JD): PSTN120.pdf, 6.5 PSTN Subclass Specific Notifications, bmRequestType | |
#define USB_CDC_REQTYPE_NOTIFICATION 0xA1 | |
#define HS_CDC_VCOM_INTERRUPT_PACKET_SIZE (16) | |
#define HS_CDC_VCOM_INTERRUPT_INTERVAL (0x07) /* 2^(7-1) = 8ms */ | |
#define HS_CDC_VCOM_BULK_PACKET_SIZE (512) | |
#define FS_CDC_VCOM_INTERRUPT_PACKET_SIZE (16) | |
#define FS_CDC_VCOM_INTERRUPT_INTERVAL (0x08) | |
#define FS_CDC_VCOM_BULK_PACKET_SIZE (64) | |
#define DATA_BUFF_SIZE HS_CDC_VCOM_BULK_PACKET_SIZE | |
typedef enum USBCDCRequest | |
{ | |
// NOTE(JD): CDC120.pdf, 6.2 Management Element Requests | |
USBCDCReq_SendEncapsulatedCommand = 0x00, | |
USBCDCReq_GetEncapsulatedResponse = 0x01, | |
// NOTE(JD): PSTN120.pdf, 6.3 PSTN Subclass Specific Requests | |
USBCDCReq_SetCommFeature = 0x02, | |
USBCDCReq_GetCommFeature = 0x03, | |
USBCDCReq_ClearCommFeature = 0x04, | |
USBCDCReq_SetAuxLineState = 0x10, | |
USBCDCReq_SetHookState = 0x11, | |
USBCDCReq_PulseSetup = 0x12, | |
USBCDCReq_SendPulse = 0x13, | |
USBCDCReq_SetPulseTime = 0x14, | |
USBCDCReq_RingAuxJack = 0x15, | |
USBCDCReq_SetLineCoding = 0x20, | |
USBCDCReq_GetLineCoding = 0x21, | |
USBCDCReq_SetControlLineState = 0x22, | |
USBCDCReq_SendBreak = 0x23, | |
USBCDCReq_SetRingerParams = 0x30, | |
USBCDCReq_GetRingerParams = 0x31, | |
USBCDCReq_SetOperationParams = 0x32, | |
USBCDCReq_GetOperationParams = 0x33, | |
USBCDCReq_SetLineParams = 0x34, | |
USBCDCReq_GetLineParams = 0x35, | |
USBCDCReq_DialDigits = 0x36, | |
} USBCDCRequest; | |
// NOTE(JD): PSTN120.pdf, 6.3.2 GetCommFeature, Table 14 | |
typedef enum USBCDCCommFeature | |
{ | |
USBCDCFeature_AbstractState = 0x01, | |
USBCDCFeature_CountrySetting = 0x02 | |
} USBCDCCommFeature; | |
// NOTE(JD): PSTN120.pdf, 6.3.11 GetLineCoding, Table 17 | |
typedef enum USBCDCLineCodingCharFormat | |
{ | |
USBCDCCharFmt_Stop_1 = 0, | |
USBCDCCharFmt_Stop_1_5 = 1, | |
USBCDCCharFmt_Stop_2 = 2, | |
} USBCDCLineCodingCharFormat; | |
// NOTE(JD): PSTN120.pdf, 6.3.11 GetLineCoding, Table 17 | |
typedef enum USBCDCLineCodingParityType | |
{ | |
USBCDCParity_None = 0, | |
USBCDCParity_Odd = 1, | |
USBCDCParity_Even = 2, | |
USBCDCParity_Mark = 3, | |
USBCDCParity_Space = 4, | |
} USBCDCLineCodingParityType; | |
// NOTE(JD): PSTN120.pdf, 6.5 PSTN Subclass Specific Notifications, Table 30 | |
typedef enum USBCDCNotification | |
{ | |
USBCDCNotif_NetworkConnection = 0x00, | |
UDBCDCNotif_ResponseAvailable = 0x01, | |
USBCDCNotif_AuxJackHookState = 0x08, | |
USBCDCNotif_RingDetect = 0x09, | |
USBCDCNotif_SerialState = 0x20, | |
USBCDCNotif_CallStateChange = 0x28, | |
USBCDCNotif_LineStateChange = 0x23, // NOTE(JD): NXP's core had this as 0x29. We don't use it so it doesn't make a difference. Is it in the errata? | |
} USBCDCNotification; | |
// NOTE(JD): PSTN120.pdf, 6.3.11 GetLineCoding | |
typedef struct __PACKED USBCDCLineCoding | |
{ | |
uint32_t dwDTERate; // Data terminal rate, in bits per second. | |
uint8_t bCharFormat; // Stop bits | |
uint8_t bParityType; // Parity | |
uint8_t bDataBits; // Data bits (5, 6, 7, 8 or 16) | |
} USBCDCLineCoding; | |
typedef struct USBSerialPipe | |
{ | |
uint8_t* m_DataBuffer; // pipe data buffer backup when stall | |
uint32_t m_DataLen; // pipe data length backup when stall | |
uint8_t m_Stall; // pipe is stall | |
uint8_t m_IsBusy; // 1: The pipe is transferring packet, 0: The pipe is idle. | |
} USBSerialPipe; | |
typedef struct USBSerialACMInfo | |
{ | |
uint8_t m_SerialStateBuf[sizeof(USBSetupRequest) + sizeof(uint16_t)]; | |
uint8_t m_DTEStatus; | |
uint16_t m_UARTState; | |
} USBSerialACMInfo; | |
typedef struct USBSerial | |
{ | |
USBDescBuilder* m_DescBuilder; | |
usb_device_handle m_DeviceHandle; | |
USBSerialPipe m_InterruptInPipe; | |
USBSerialPipe m_BulkInPipe; | |
USBSerialPipe m_BulkOutPipe; | |
uint8_t m_Configuration; | |
uint8_t m_ControlInterfaceNum; | |
uint8_t m_DataInterfaceNum; | |
uint8_t m_InterfaceAltSetting; | |
uint8_t m_InterruptEndpointNum; | |
uint8_t m_BulkEndpointNum; | |
uint16_t m_InterruptEndpointMaxPacketSize; | |
uint16_t m_BulkEndpointMaxPacketSize; | |
// TODO: Merge those into a bitfield. | |
uint8_t m_Attach; | |
uint8_t m_StartTransactions; | |
uint8_t m_HasSentState; | |
} USBSerial; | |
// Data buffer for receiving and sending | |
USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) | |
static uint8_t s_currRecvBuf[DATA_BUFF_SIZE]; | |
volatile static uint32_t s_recvSize = 0; | |
USB_DMA_NONINIT_DATA_ALIGN(32) | |
static uint8_t s_USBSerialSendBuf0[USB_SERIAL_CONFIG_BUFFER_SIZE]; | |
USB_DMA_NONINIT_DATA_ALIGN(32) | |
static uint8_t s_USBSerialSendBuf1[USB_SERIAL_CONFIG_BUFFER_SIZE]; | |
static uint8_t* s_USBSerialSendBuf[] = { | |
s_USBSerialSendBuf0, | |
s_USBSerialSendBuf1 | |
}; | |
volatile static uint32_t s_USBSerialSendBufID = 0; | |
volatile static uint32_t s_USBSerialSendSize = 0; | |
// Abstract state of cdc device | |
// NOTE(JD): PSTN120.pdf, 6.3.2 GetCommFeature | |
USB_DMA_INIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) | |
static uint16_t s_USBSerialAbstractState = 0x0000; | |
// Country code of cdc device | |
// NOTE(JD): PSTN120.pdf, 6.3.2 GetCommFeature | |
USB_DMA_INIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) | |
static uint16_t s_USBSerialCountrySetting = 0x0000; | |
// Line coding of cdc device | |
USB_DMA_INIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) | |
static USBCDCLineCoding s_USBSerialLineCoding = { | |
.dwDTERate = 115200, | |
.bCharFormat = USBCDCCharFmt_Stop_1, | |
.bParityType = USBCDCParity_None, | |
.bDataBits = 8 | |
}; | |
USB_DMA_NONINIT_DATA_ALIGN(USB_DATA_ALIGN_SIZE) | |
static USBSerialACMInfo s_usbCdcAcmInfo; | |
static USBSerial s_USBSerialInstance; | |
static usb_status_t usbSerialEndpointsInit(USBSerial* ser); | |
static usb_status_t usbSerialEndpointsDeinit(USBSerial* ser); | |
static usb_status_t usbSerialInterruptIn(usb_device_handle handle, usb_device_endpoint_callback_message_struct_t* message, void* callbackParam); | |
static usb_status_t usbSerialBulkIn(usb_device_handle handle, usb_device_endpoint_callback_message_struct_t* message, void* callbackParam); | |
static usb_status_t usbSerialBulkOut(usb_device_handle handle, usb_device_endpoint_callback_message_struct_t* message, void* callbackParam); | |
static usb_status_t usbSerialSend(USBSerial* ser, uint8_t ep, uint8_t* buffer, uint32_t length); | |
static usb_status_t usbSerialRecv(USBSerial* ser, uint8_t ep, uint8_t *buffer, uint32_t length); | |
USBSerial* usbSerialCreate() | |
{ | |
USBSerial* ser = &s_USBSerialInstance; | |
ser->m_DescBuilder = NULL; | |
ser->m_DeviceHandle = NULL; | |
ser->m_Configuration = 0; | |
ser->m_ControlInterfaceNum = 0xFF; | |
ser->m_DataInterfaceNum = 0xFF; | |
ser->m_InterfaceAltSetting = 0xFF; | |
ser->m_InterruptEndpointNum = 0xFF; | |
ser->m_BulkEndpointNum = 0xFF; | |
ser->m_Attach = 0; | |
ser->m_StartTransactions = 0; | |
return ser; | |
} | |
void usbSerialBuildDesc(USBSerial* ser, USBDescBuilder* db) | |
{ | |
usbdbBeginInterfaceAssocDesc(db, USBClass_CDC, 0x03, 0x00, 0); | |
{ | |
// Control interface | |
usbdbBeginInterfaceDesc(db, 0, USBClass_CDC, 0x02, 0x00, 0, &ser->m_ControlInterfaceNum); | |
{ | |
// NOTE: Data interface is expected to be the next interface. | |
const uint8_t interfaceUnion[2] = { | |
ser->m_ControlInterfaceNum, | |
ser->m_ControlInterfaceNum + 1 | |
}; | |
// CDC specific functional descriptors | |
usbdbCDCHeaderFuncDesc(db, 0x0110); | |
usbdbCDCCallManagementFuncDesc(db, 0x01, interfaceUnion[1]); | |
usbdbCDCAbstractControlManagementFuncDesc(db, 0x06); | |
usbdbCDCUnionInterfaceFuncDesc(db, &interfaceUnion[0], 2); | |
// Endpoints | |
ser->m_InterruptEndpointNum = usbdbAllocEndpoint(db); | |
ser->m_InterruptEndpointMaxPacketSize = 16; | |
usbdbEndpointDesc(db, USB_DESC_EP_MAKE_ADDR(ser->m_InterruptEndpointNum, USB_IN), USB_DESC_EP_ATTRIB_TYPE(USBEPType_Interrupt), ser->m_InterruptEndpointMaxPacketSize, 8); | |
} | |
usbdbEndInterfaceDesc(db); | |
// Data interface | |
usbdbBeginInterfaceDesc(db, 0, USBClass_CDCData, 0x00, 0x00, 0, &ser->m_DataInterfaceNum); | |
{ | |
// Endpoints | |
ser->m_BulkEndpointNum = usbdbAllocEndpoint(db); | |
ser->m_BulkEndpointMaxPacketSize = 64; | |
usbdbEndpointDesc(db, USB_DESC_EP_MAKE_ADDR(ser->m_BulkEndpointNum, USB_IN), USB_DESC_EP_ATTRIB_TYPE(USBEPType_Bulk), ser->m_BulkEndpointMaxPacketSize, 0); | |
usbdbEndpointDesc(db, USB_DESC_EP_MAKE_ADDR(ser->m_BulkEndpointNum, USB_OUT), USB_DESC_EP_ATTRIB_TYPE(USBEPType_Bulk), ser->m_BulkEndpointMaxPacketSize, 0); | |
} | |
usbdbEndInterfaceDesc(db); | |
} | |
usbdbEndInterfaceAssocDesc(db); | |
} | |
usb_status_t usbSerialInit(USBSerial* ser, usb_device_handle devHandle, USBDescBuilder* db) | |
{ | |
ser->m_DescBuilder = db; | |
ser->m_DeviceHandle = devHandle; | |
ser->m_Configuration = 0; | |
ser->m_InterfaceAltSetting = 0xFF; | |
ser->m_Attach = 0; | |
ser->m_StartTransactions = 0; | |
return kStatus_USB_Success; | |
} | |
usb_status_t usbSerialReset(USBSerial* ser) | |
{ | |
// Bus reset, clear the configuration. | |
ser->m_Configuration = 0; | |
return kStatus_USB_Success; | |
} | |
usb_status_t usbSerialSetConfiguration(USBSerial* ser, uint8_t cfgVal) | |
{ | |
if (ser->m_Configuration == cfgVal) { | |
return kStatus_USB_Success; | |
} | |
usbSerialEndpointsDeinit(ser); | |
ser->m_Configuration = cfgVal; | |
ser->m_InterfaceAltSetting = 0; | |
usb_status_t error = usbSerialEndpointsInit(ser); | |
if (error == kStatus_USB_Success) { | |
if (cfgVal != 0) { | |
ser->m_Attach = 1; | |
// Schedule buffer for receive | |
usbSerialRecv(ser, ser->m_BulkEndpointNum, s_currRecvBuf, ser->m_BulkEndpointMaxPacketSize); | |
} | |
} | |
return error; | |
} | |
bool usbSerialHasInterface(USBSerial* ser, uint8_t interfaceNum) | |
{ | |
return false | |
|| ser->m_ControlInterfaceNum == interfaceNum | |
|| ser->m_DataInterfaceNum == interfaceNum | |
; | |
} | |
usb_status_t usbSerialSetInterfaceAltSetting(USBSerial* ser, uint8_t interfaceNum, uint8_t altSetting) | |
{ | |
if (interfaceNum != ser->m_ControlInterfaceNum) { | |
return kStatus_USB_Error; | |
} | |
if (altSetting == ser->m_InterfaceAltSetting) { | |
return kStatus_USB_Success; | |
} | |
usbSerialEndpointsDeinit(ser); | |
ser->m_InterfaceAltSetting = altSetting; | |
return usbSerialEndpointsInit(ser); | |
} | |
uint8_t usbSerialGetInterfaceAltSetting(USBSerial* ser, uint8_t interfaceNum) | |
{ | |
return (interfaceNum == ser->m_ControlInterfaceNum || interfaceNum == ser->m_DataInterfaceNum) | |
? ser->m_InterfaceAltSetting | |
: 0xFF | |
; | |
} | |
usb_status_t usbSerialClassRequest(USBSerial* ser, usb_device_control_request_struct_t* controlRequest) | |
{ | |
if ((controlRequest->setup->bmRequestType & USBSetupReqRecip_Msk) != USBSetupReqRecip_Interface) { | |
return kStatus_USB_Error; | |
} | |
if ((controlRequest->setup->wIndex & 0xFF) != ser->m_ControlInterfaceNum) { | |
return kStatus_USB_Error; | |
} | |
usb_status_t error = kStatus_USB_InvalidRequest; | |
switch (controlRequest->setup->bRequest) { | |
case USBCDCReq_SendEncapsulatedCommand: | |
break; | |
case USBCDCReq_GetEncapsulatedResponse: | |
break; | |
case USBCDCReq_SetCommFeature: | |
if (((controlRequest->setup->bmRequestType & USBSetupReqDir_Msk) == USBSetupReqDir_Host2Dev) && (controlRequest->setup->wLength != 0)) { | |
if (controlRequest->setup->wValue == USBCDCFeature_AbstractState) { | |
if (controlRequest->isSetup == 1) { | |
controlRequest->buffer = (uint8_t*)&s_USBSerialAbstractState; | |
controlRequest->length = 2; | |
} | |
error = kStatus_USB_Success; | |
} else if (controlRequest->setup->wValue == USBCDCFeature_CountrySetting) { | |
if (controlRequest->isSetup == 1) { | |
controlRequest->buffer = (uint8_t*)&s_USBSerialCountrySetting; | |
controlRequest->length = 2; | |
} | |
error = kStatus_USB_Success; | |
} | |
} | |
break; | |
case USBCDCReq_GetCommFeature: | |
if (((controlRequest->setup->bmRequestType & USBSetupReqDir_Msk) == USBSetupReqDir_Dev2Host) && (controlRequest->setup->wLength != 0)) { | |
if (controlRequest->setup->wValue == USBCDCFeature_AbstractState) { | |
controlRequest->buffer = (uint8_t*)&s_USBSerialAbstractState; | |
controlRequest->length = 2; | |
error = kStatus_USB_Success; | |
} else if (controlRequest->setup->wValue == USBCDCFeature_CountrySetting) { | |
controlRequest->buffer = (uint8_t*)&s_USBSerialCountrySetting; | |
controlRequest->length = 2; | |
error = kStatus_USB_Success; | |
} | |
} | |
break; | |
case USBCDCReq_ClearCommFeature: | |
break; | |
case USBCDCReq_GetLineCoding: | |
if (((controlRequest->setup->bmRequestType & USBSetupReqDir_Msk) == USBSetupReqDir_Dev2Host) && (controlRequest->setup->wLength != 0)) { | |
controlRequest->buffer = (uint8_t*)&s_USBSerialLineCoding; | |
controlRequest->length = sizeof(USBCDCLineCoding); | |
error = kStatus_USB_Success; | |
} | |
break; | |
case USBCDCReq_SetLineCoding: | |
if (((controlRequest->setup->bmRequestType & USBSetupReqDir_Msk) == USBSetupReqDir_Host2Dev) && (controlRequest->setup->wLength != 0)) { | |
if (controlRequest->isSetup == 1) { | |
controlRequest->buffer = (uint8_t*)&s_USBSerialLineCoding; | |
controlRequest->length = sizeof(USBCDCLineCoding); | |
} | |
error = kStatus_USB_Success; | |
} | |
break; | |
case USBCDCReq_SetControlLineState: | |
if (((controlRequest->setup->bmRequestType & USBSetupReqDir_Msk) == USBSetupReqDir_Host2Dev) && (controlRequest->setup->wLength == 0)) { | |
USBSerialACMInfo* acmInfo = &s_usbCdcAcmInfo; | |
acmInfo->m_DTEStatus = controlRequest->setup->wValue; | |
// activate/deactivate Tx carrier | |
if ((acmInfo->m_DTEStatus & USB_CDC_CONTROL_SIGNAL_CARRIER_ACTIVATION_Msk) != 0) { | |
acmInfo->m_UARTState |= USB_CDC_UART_STATE_TX_CARRIER_Msk; | |
} else { | |
acmInfo->m_UARTState &= (uint16_t)~USB_CDC_UART_STATE_TX_CARRIER_Msk; | |
} | |
// activate carrier and DTE. Com port of terminal tool running on PC is open now | |
if ((acmInfo->m_DTEStatus & USB_CDC_CONTROL_SIGNAL_DTE_PRESENCE_Msk) != 0) { | |
acmInfo->m_UARTState |= USB_CDC_UART_STATE_RX_CARRIER_Msk; | |
} else { | |
// Com port of terminal tool running on PC is closed now | |
acmInfo->m_UARTState &= (uint16_t)~USB_CDC_UART_STATE_RX_CARRIER_Msk; | |
} | |
#if 0 | |
// Initialize the serial state buffer | |
acmInfo->m_SerialStateBuf[0] = 0xA1; | |
acmInfo->m_SerialStateBuf[1] = USBCDCNotif_SerialState; | |
acmInfo->m_SerialStateBuf[2] = 0x00; | |
acmInfo->m_SerialStateBuf[3] = 0x00; | |
acmInfo->m_SerialStateBuf[4] = controlRequest->setup->wIndex; // Notify to host the line state | |
acmInfo->m_SerialStateBuf[5] = 0x00; | |
acmInfo->m_SerialStateBuf[6] = 2; | |
acmInfo->m_SerialStateBuf[7] = 0x00; | |
acmInfo->m_SerialStateBuf[8] = (acmInfo->m_UARTState & 0x00FF) >> 0; // UART bitmap | |
acmInfo->m_SerialStateBuf[9] = (acmInfo->m_UARTState & 0xFF00) >> 8; // UART bitmap | |
#else | |
// Initialize the serial state buffer | |
// NOTE(JD): PSTN120.pdf, 6.5.4 Serial State | |
USBSetupRequest* req = (USBSetupRequest*)&acmInfo->m_SerialStateBuf[0]; | |
req->bmRequestType = USB_CDC_REQTYPE_NOTIFICATION; | |
req->bRequest = USBCDCNotif_SerialState; | |
req->wValue = 0; | |
req->wIndex = controlRequest->setup->wIndex; | |
req->wLength = 2; | |
uint16_t* uartBitmap = (uint16_t*)&acmInfo->m_SerialStateBuf[sizeof(USBSetupRequest)]; | |
*uartBitmap = acmInfo->m_UARTState; | |
#endif | |
if (ser->m_HasSentState == 0) { | |
error = usbSerialSend(ser, ser->m_InterruptEndpointNum, acmInfo->m_SerialStateBuf, sizeof(acmInfo->m_SerialStateBuf)); | |
ser->m_HasSentState = 1; | |
} | |
// Update status | |
if ((acmInfo->m_DTEStatus & USB_CDC_CONTROL_SIGNAL_CARRIER_ACTIVATION_Msk) != 0) { | |
// TODO: CARRIER_ACTIVATED | |
} else { | |
// TODO: CARRIER_DEACTIVATED | |
} | |
// if ((acmInfo->m_DTEStatus & USB_CDC_CONTROL_SIGNAL_DTE_PRESENCE_Msk) != 0) | |
{ | |
// DTE_ACTIVATED | |
if (ser->m_Attach == 1) { | |
ser->m_StartTransactions = 1; | |
usbSerialWrite(ser, (const uint8_t*)"Hello\r\n", 7); | |
} | |
} | |
// else { | |
// // DTE_DEACTIVATED | |
// if (ser->m_Attach == 1) { | |
// ser->m_StartTransactions = 0; | |
// } | |
// } | |
error = kStatus_USB_Success; | |
} | |
break; | |
case USBCDCReq_SendBreak: | |
break; | |
default: | |
break; | |
} | |
return error; | |
} | |
usb_status_t usbSerialSetEndpointHalt(USBSerial* ser, uint8_t epAddr) | |
{ | |
if (ser->m_ControlInterfaceNum == 0xFF || ser->m_DataInterfaceNum == 0xFF) { | |
return kStatus_USB_Error; | |
} | |
const uint8_t epNum = USB_DESC_EP_ADDR_GET_NUM(epAddr); | |
const uint8_t epDir = USB_DESC_EP_ADDR_GET_DIR(epAddr); | |
usb_status_t error = kStatus_USB_InvalidRequest; | |
if (epNum == ser->m_InterruptEndpointNum) { | |
ser->m_InterruptInPipe.m_Stall = 1; | |
error = USB_DeviceStallEndpoint(ser->m_DeviceHandle, epAddr); | |
} else if (epNum == ser->m_BulkEndpointNum) { | |
if (epDir == USB_IN) { | |
ser->m_BulkInPipe.m_Stall = 1; | |
} else { | |
ser->m_BulkOutPipe.m_Stall = 1; | |
} | |
error = USB_DeviceStallEndpoint(ser->m_DeviceHandle, epAddr); | |
} | |
return error; | |
} | |
usb_status_t usbSerialClearEndpointHalt(USBSerial* ser, uint8_t epAddr) | |
{ | |
if (ser->m_ControlInterfaceNum == 0xFF || ser->m_DataInterfaceNum == 0xFF) { | |
return kStatus_USB_Error; | |
} | |
const uint8_t epNum = USB_DESC_EP_ADDR_GET_NUM(epAddr); | |
const uint8_t epDir = USB_DESC_EP_ADDR_GET_DIR(epAddr); | |
usb_status_t error = kStatus_USB_InvalidRequest; | |
if (epNum == ser->m_InterruptEndpointNum) { | |
error = USB_DeviceUnstallEndpoint(ser->m_DeviceHandle, epAddr); | |
if (epDir == USB_IN) { | |
USBSerialPipe* pipe = &ser->m_InterruptInPipe; | |
if (pipe->m_Stall != 0) { | |
pipe->m_Stall = 0; | |
if (pipe->m_DataBuffer != (uint8_t*)USB_INVALID_TRANSFER_BUFFER) { | |
error = USB_DeviceSendRequest(ser->m_DeviceHandle, USB_DESC_EP_MAKE_ADDR(ser->m_InterruptEndpointNum, USB_IN), pipe->m_DataBuffer, pipe->m_DataLen); | |
if (error != kStatus_USB_Success) { | |
usb_device_endpoint_callback_message_struct_t endpointCallbackMessage; | |
endpointCallbackMessage.buffer = pipe->m_DataBuffer; | |
endpointCallbackMessage.length = pipe->m_DataLen; | |
endpointCallbackMessage.isSetup = 0; | |
usbSerialBulkIn(ser->m_DeviceHandle, &endpointCallbackMessage, ser); | |
} | |
pipe->m_DataBuffer = (uint8_t*)USB_INVALID_TRANSFER_BUFFER; | |
pipe->m_DataLen = 0; | |
} | |
} | |
} | |
} else if (epNum == ser->m_BulkEndpointNum) { | |
error = USB_DeviceUnstallEndpoint(ser->m_DeviceHandle, epAddr); | |
if (epDir == USB_IN) { | |
USBSerialPipe* pipe = &ser->m_BulkInPipe; | |
if (pipe->m_Stall != 0) { | |
pipe->m_Stall = 0; | |
if (pipe->m_DataBuffer != (uint8_t*)USB_INVALID_TRANSFER_BUFFER) { | |
error = USB_DeviceSendRequest(ser->m_DeviceHandle, USB_DESC_EP_MAKE_ADDR(ser->m_BulkEndpointNum, USB_IN), pipe->m_DataBuffer, pipe->m_DataLen); | |
if (error != kStatus_USB_Success) { | |
usb_device_endpoint_callback_message_struct_t endpointCallbackMessage; | |
endpointCallbackMessage.buffer = pipe->m_DataBuffer; | |
endpointCallbackMessage.length = pipe->m_DataLen; | |
endpointCallbackMessage.isSetup = 0; | |
usbSerialBulkIn(ser->m_DeviceHandle, &endpointCallbackMessage, ser); | |
} | |
pipe->m_DataBuffer = (uint8_t*)USB_INVALID_TRANSFER_BUFFER; | |
pipe->m_DataLen = 0; | |
} | |
} | |
} else { | |
USBSerialPipe* pipe = &ser->m_BulkOutPipe; | |
if (pipe->m_Stall != 0) { | |
pipe->m_Stall = 0; | |
if (pipe->m_DataBuffer != (uint8_t*)USB_INVALID_TRANSFER_BUFFER) { | |
error = USB_DeviceRecvRequest(ser->m_DeviceHandle, USB_DESC_EP_MAKE_ADDR(ser->m_BulkEndpointNum, USB_OUT), pipe->m_DataBuffer, pipe->m_DataLen); | |
if (error != kStatus_USB_Success) { | |
usb_device_endpoint_callback_message_struct_t endpointCallbackMessage; | |
endpointCallbackMessage.buffer = pipe->m_DataBuffer; | |
endpointCallbackMessage.length = pipe->m_DataLen; | |
endpointCallbackMessage.isSetup = 0; | |
usbSerialBulkOut(ser->m_DeviceHandle, &endpointCallbackMessage, ser); | |
} | |
pipe->m_DataBuffer = (uint8_t*)USB_INVALID_TRANSFER_BUFFER; | |
pipe->m_DataLen = 0; | |
} | |
} | |
} | |
} | |
return error; | |
} | |
// TODO(JD): Replace usb_descriptor_union_t with USBDescHeader and cast to the correct type inside the loop | |
typedef union _usb_descriptor_union | |
{ | |
USBDescHeader common; | |
USBDescDevice device; | |
USBDescConfig configuration; | |
USBDescInterface interface; | |
USBDescEndpoint endpoint; | |
USBDescEndpointCompanion endpointCompanion; | |
} usb_descriptor_union_t; | |
usb_status_t usbSerialSetSpeed(USBSerial* ser, uint8_t speed) | |
{ | |
uint32_t cfgDescSize = 0; | |
uint8_t* cfgPtr = usbdbGetConfigDescByVal(ser->m_DescBuilder, ser->m_Configuration, &cfgDescSize); | |
if (cfgPtr == NULL) { | |
return kStatus_USB_InvalidRequest; | |
} | |
usb_descriptor_union_t* ptr1 = (usb_descriptor_union_t*)cfgPtr; | |
usb_descriptor_union_t* ptr2 = (usb_descriptor_union_t*)(cfgPtr + cfgDescSize - 1); | |
while (ptr1 < ptr2) { | |
if (ptr1->common.bDescriptorType == USBDesc_Endpoint) { | |
const uint8_t epNum = USB_DESC_EP_ADDR_GET_NUM(ptr1->endpoint.bEndpointAddress); | |
const uint8_t epDir = USB_DESC_EP_ADDR_GET_DIR(ptr1->endpoint.bEndpointAddress); | |
if (speed == USBDevSpeed_High) { | |
if (epNum == ser->m_InterruptEndpointNum && epDir == USB_IN) { | |
ptr1->endpoint.bInterval = HS_CDC_VCOM_INTERRUPT_INTERVAL; | |
ptr1->endpoint.wMaxPacketSize = HS_CDC_VCOM_INTERRUPT_PACKET_SIZE; | |
} else if (epNum == ser->m_BulkEndpointNum) { | |
ptr1->endpoint.wMaxPacketSize = HS_CDC_VCOM_BULK_PACKET_SIZE; | |
} | |
} else { | |
if (epNum == ser->m_InterruptEndpointNum && epDir == USB_IN) { | |
ptr1->endpoint.bInterval = FS_CDC_VCOM_INTERRUPT_INTERVAL; | |
ptr1->endpoint.wMaxPacketSize = FS_CDC_VCOM_INTERRUPT_PACKET_SIZE; | |
} else if (epNum == ser->m_BulkEndpointNum) { | |
ptr1->endpoint.wMaxPacketSize = FS_CDC_VCOM_BULK_PACKET_SIZE; | |
} | |
} | |
} | |
ptr1 = (usb_descriptor_union_t*)((uint8_t*)ptr1 + ptr1->common.bLength); | |
} | |
ser->m_InterruptEndpointMaxPacketSize = speed == USBDevSpeed_High | |
? HS_CDC_VCOM_INTERRUPT_PACKET_SIZE | |
: FS_CDC_VCOM_INTERRUPT_PACKET_SIZE | |
; | |
ser->m_BulkEndpointMaxPacketSize = speed == USBDevSpeed_High | |
? HS_CDC_VCOM_BULK_PACKET_SIZE | |
: FS_CDC_VCOM_BULK_PACKET_SIZE | |
; | |
return kStatus_USB_Success; | |
} | |
int32_t usbSerialRead(USBSerial* ser, uint8_t* buffer, uint32_t len) | |
{ | |
if (ser->m_Attach != 1 || ser->m_StartTransactions != 1) { | |
return 0; | |
} | |
if (s_recvSize == 0 || s_recvSize == USB_CANCELLED_TRANSFER_LENGTH) { | |
return 0; | |
} | |
const uint32_t copyLen = len < s_recvSize ? len : s_recvSize; | |
memcpy(buffer, s_currRecvBuf, copyLen); | |
return (int32_t)copyLen; | |
} | |
int32_t usbSerialWrite(USBSerial* ser, const uint8_t* buffer, uint32_t len) | |
{ | |
if (ser->m_Attach != 1 || ser->m_StartTransactions != 1) { | |
return 0; | |
} | |
#if 0 | |
const uint32_t copyLen = len < DATA_BUFF_SIZE ? len : DATA_BUFF_SIZE; | |
memcpy(s_currSendBuf, buffer, copyLen); | |
usbSerialSend(ser, ser->m_BulkEndpointNum, s_currSendBuf, copyLen); | |
return copyLen; | |
#else | |
CRITICAL_SECTION_ENTER(); | |
if (s_USBSerialSendSize + len <= USB_SERIAL_CONFIG_BUFFER_SIZE) { | |
uint8_t* dst = s_USBSerialSendBuf[s_USBSerialSendBufID]; | |
memcpy(&dst[s_USBSerialSendSize], buffer, len); | |
s_USBSerialSendSize += len; | |
__DSB(); | |
} | |
CRITICAL_SECTION_LEAVE(); | |
return len; | |
#endif | |
} | |
void usbSerialTick(USBSerial* ser) | |
{ | |
CRITICAL_SECTION_ENTER(); | |
if (s_USBSerialSendSize != 0) { | |
usb_status_t err = usbSerialSend(ser, ser->m_BulkEndpointNum, s_USBSerialSendBuf[s_USBSerialSendBufID], s_USBSerialSendSize); | |
if (err == kStatus_USB_Success) { | |
s_USBSerialSendBufID ^= 1; | |
s_USBSerialSendSize = 0; | |
} | |
} | |
CRITICAL_SECTION_LEAVE(); | |
} | |
static usb_status_t usbSerialEndpointsInit(USBSerial* ser) | |
{ | |
// return error when configuration is invalid (0 or more than the configuration number) | |
if (ser->m_Configuration == 0 || ser->m_ControlInterfaceNum == 0xFF || ser->m_DataInterfaceNum == 0xFF) { | |
return kStatus_USB_Error; | |
} | |
uint32_t cfgDescSize = 0; | |
const uint8_t* cfgDescPtr = usbdbGetConfigDescByVal(ser->m_DescBuilder, ser->m_Configuration, &cfgDescSize); | |
if (cfgDescPtr == NULL) { | |
return kStatus_USB_Error; | |
} | |
const uint8_t* endOfConfigDesc = cfgDescPtr + cfgDescSize; | |
// Initialize control interface | |
{ | |
// Find interface descriptor | |
const USBDescInterface* interfaceDesc = usbFindInterfaceDesc((const USBDescConfig*)cfgDescPtr, ser->m_ControlInterfaceNum, ser->m_InterfaceAltSetting); | |
if (interfaceDesc == NULL) { | |
return kStatus_USB_Error; | |
} | |
const uint32_t numEndpoints = interfaceDesc->bNumEndpoints; | |
for (uint32_t i = 0;i < numEndpoints; ++i) { | |
const USBDescEndpoint* epDesc = usbFindEndpointDesc(interfaceDesc, endOfConfigDesc, i); | |
if (epDesc == NULL) { | |
continue; | |
} | |
usb_device_endpoint_init_struct_t epInitStruct; | |
epInitStruct.zlt = 0; | |
epInitStruct.interval = epDesc->bInterval; | |
epInitStruct.endpointAddress = epDesc->bEndpointAddress; | |
epInitStruct.maxPacketSize = epDesc->wMaxPacketSize; | |
epInitStruct.transferType = epDesc->bmAttributes; | |
usb_device_endpoint_callback_struct_t epCallback; | |
if (USB_DESC_EP_ADDR_GET_DIR(epInitStruct.endpointAddress) == USB_IN && epInitStruct.transferType == USBEPType_Interrupt) { | |
ser->m_InterruptInPipe.m_DataBuffer = (uint8_t*)USB_INVALID_TRANSFER_BUFFER; | |
ser->m_InterruptInPipe.m_DataLen = 0; | |
ser->m_InterruptInPipe.m_Stall = 0; | |
ser->m_InterruptInPipe.m_IsBusy = 0; | |
epCallback.callbackFn = usbSerialInterruptIn; | |
} | |
epCallback.callbackParam = ser; | |
usb_status_t error = USB_DeviceInitEndpoint(ser->m_DeviceHandle, &epInitStruct, &epCallback); | |
if (error != kStatus_USB_Success) { | |
return error; | |
} | |
} | |
} | |
usb_status_t error = kStatus_USB_Error; | |
// Initialize data interface | |
{ | |
// Find interface descriptor | |
const USBDescInterface* interfaceDesc = usbFindInterfaceDesc((const USBDescConfig*)cfgDescPtr, ser->m_DataInterfaceNum, ser->m_InterfaceAltSetting); | |
if (interfaceDesc == NULL) { | |
return kStatus_USB_Error; | |
} | |
const uint32_t numEndpoints = interfaceDesc->bNumEndpoints; | |
for (uint32_t i = 0;i < numEndpoints; ++i) { | |
const USBDescEndpoint* epDesc = usbFindEndpointDesc(interfaceDesc, endOfConfigDesc, i); | |
if (epDesc == NULL) { | |
continue; | |
} | |
usb_device_endpoint_init_struct_t epInitStruct; | |
epInitStruct.zlt = 0; | |
epInitStruct.interval = epDesc->bInterval; | |
epInitStruct.endpointAddress = epDesc->bEndpointAddress; | |
epInitStruct.maxPacketSize = epDesc->wMaxPacketSize; | |
epInitStruct.transferType = epDesc->bmAttributes; | |
const uint8_t epDir = USB_DESC_EP_ADDR_GET_DIR(epInitStruct.endpointAddress); | |
usb_device_endpoint_callback_struct_t epCallback; | |
if (epDir == USB_IN && epInitStruct.transferType == USBEPType_Bulk) { | |
ser->m_BulkInPipe.m_DataBuffer = (uint8_t*)USB_INVALID_TRANSFER_BUFFER; | |
ser->m_BulkInPipe.m_DataLen = 0; | |
ser->m_BulkInPipe.m_Stall = 0; | |
ser->m_BulkInPipe.m_IsBusy = 0; | |
epCallback.callbackFn = usbSerialBulkIn; | |
} else if (epDir == USB_OUT && epInitStruct.transferType == USBEPType_Bulk) { | |
ser->m_BulkOutPipe.m_DataBuffer = (uint8_t*)USB_INVALID_TRANSFER_BUFFER; | |
ser->m_BulkOutPipe.m_DataLen = 0; | |
ser->m_BulkOutPipe.m_Stall = 0; | |
ser->m_BulkOutPipe.m_IsBusy = 0; | |
epCallback.callbackFn = usbSerialBulkOut; | |
} | |
epCallback.callbackParam = ser; | |
error = USB_DeviceInitEndpoint(ser->m_DeviceHandle, &epInitStruct, &epCallback); | |
} | |
} | |
return error; | |
} | |
static usb_status_t usbSerialEndpointsDeinit(USBSerial* ser) | |
{ | |
// return error when configuration is invalid (0 or more than the configuration number) | |
if (ser->m_Configuration == 0 || ser->m_ControlInterfaceNum == 0xFF || ser->m_DataInterfaceNum == 0xFF) { | |
return kStatus_USB_Error; | |
} | |
uint32_t cfgDescSize = 0; | |
const uint8_t* cfgDescPtr = usbdbGetConfigDescByVal(ser->m_DescBuilder, ser->m_Configuration, &cfgDescSize); | |
if (cfgDescPtr == NULL) { | |
return kStatus_USB_Error; | |
} | |
const uint8_t* endOfConfigDesc = cfgDescPtr + cfgDescSize; | |
// Deinitialize control interface | |
{ | |
// Find interface descriptor | |
const USBDescInterface* interfaceDesc = usbFindInterfaceDesc((const USBDescConfig*)cfgDescPtr, ser->m_ControlInterfaceNum, ser->m_InterfaceAltSetting); | |
if (interfaceDesc == NULL) { | |
return kStatus_USB_Error; | |
} | |
const uint32_t numEndpoints = interfaceDesc->bNumEndpoints; | |
for (uint32_t i = 0;i < numEndpoints; ++i) { | |
const USBDescEndpoint* epDesc = usbFindEndpointDesc(interfaceDesc, endOfConfigDesc, i); | |
if (epDesc == NULL) { | |
continue; | |
} | |
USB_DeviceDeinitEndpoint(ser->m_DeviceHandle, epDesc->bEndpointAddress); | |
} | |
} | |
// Deinitialize data interface | |
{ | |
// Find interface descriptor | |
const USBDescInterface* interfaceDesc = usbFindInterfaceDesc((const USBDescConfig*)cfgDescPtr, ser->m_DataInterfaceNum, ser->m_InterfaceAltSetting); | |
if (interfaceDesc == NULL) { | |
return kStatus_USB_Error; | |
} | |
const uint32_t numEndpoints = interfaceDesc->bNumEndpoints; | |
for (uint32_t i = 0;i < numEndpoints; ++i) { | |
const USBDescEndpoint* epDesc = usbFindEndpointDesc(interfaceDesc, endOfConfigDesc, i); | |
if (epDesc == NULL) { | |
continue; | |
} | |
USB_DeviceDeinitEndpoint(ser->m_DeviceHandle, epDesc->bEndpointAddress); | |
} | |
} | |
return kStatus_USB_Success; | |
} | |
static usb_status_t usbSerialInterruptIn(usb_device_handle handle, usb_device_endpoint_callback_message_struct_t* message, void* callbackParam) | |
{ | |
if (callbackParam == NULL) { | |
return kStatus_USB_InvalidHandle; | |
} | |
USBSerial* ser = (USBSerial*)callbackParam; | |
ser->m_InterruptInPipe.m_IsBusy = 0; | |
ser->m_HasSentState = 0; | |
return kStatus_USB_Success; | |
} | |
static usb_status_t usbSerialBulkIn(usb_device_handle handle, usb_device_endpoint_callback_message_struct_t* message, void* callbackParam) | |
{ | |
if (callbackParam == NULL) { | |
return kStatus_USB_InvalidHandle; | |
} | |
USBSerial* ser = (USBSerial*)callbackParam; | |
CRITICAL_SECTION_ENTER(); | |
ser->m_BulkInPipe.m_IsBusy = 0; | |
CRITICAL_SECTION_LEAVE(); | |
usb_status_t error = kStatus_USB_InvalidRequest; | |
if (message->length != 0 && !(message->length % ser->m_BulkEndpointMaxPacketSize)) { | |
// If the last packet is the size of endpoint, then send also zero-ended packet, | |
// meaning that we want to inform the host that we do not have any additional | |
// data, so it can flush the output. | |
error = usbSerialSend(ser, ser->m_BulkEndpointNum, NULL, 0); | |
#if 0 | |
} else if (ser->m_Attach == 1 && ser->m_StartTransactions == 1) { | |
if (message->buffer != NULL || (message->buffer == NULL && message->length == 0)) { | |
// User: add your own code for send complete event | |
// Schedule buffer for next receive event | |
error = usbSerialRecv(ser, ser->m_BulkEndpointNum, s_currRecvBuf, ser->m_BulkEndpointMaxPacketSize); | |
} | |
#endif | |
} | |
return error; | |
} | |
static usb_status_t usbSerialBulkOut(usb_device_handle handle, usb_device_endpoint_callback_message_struct_t* message, void* callbackParam) | |
{ | |
if (callbackParam == NULL) { | |
return kStatus_USB_InvalidHandle; | |
} | |
USBSerial* ser = (USBSerial*)callbackParam; | |
CRITICAL_SECTION_ENTER(); | |
ser->m_BulkOutPipe.m_IsBusy = 0; | |
CRITICAL_SECTION_LEAVE(); | |
usb_status_t error = kStatus_USB_InvalidRequest; | |
if (ser->m_Attach == 1 && ser->m_StartTransactions == 1) { | |
s_recvSize = message->length; | |
if (!s_recvSize) { | |
// Schedule buffer for next receive event | |
error = usbSerialRecv(ser, ser->m_BulkEndpointNum, s_currRecvBuf, ser->m_BulkEndpointMaxPacketSize); | |
} | |
} | |
return error; | |
} | |
static usb_status_t usbSerialSend(USBSerial* ser, uint8_t epNum, uint8_t* buffer, uint32_t length) | |
{ | |
if (ser == NULL) { | |
return kStatus_USB_InvalidHandle; | |
} | |
USBSerialPipe* pipe = NULL; | |
if (epNum == ser->m_BulkEndpointNum) { | |
pipe = &ser->m_BulkInPipe; | |
} else if (epNum == ser->m_InterruptEndpointNum) { | |
pipe = &ser->m_InterruptInPipe; | |
} else { | |
return kStatus_USB_Error; | |
} | |
volatile atomic_t criticalSection; | |
atomic_enter_critical(&criticalSection); | |
if (pipe->m_IsBusy == 1) { | |
atomic_leave_critical(&criticalSection); | |
return kStatus_USB_Busy; | |
} | |
pipe->m_IsBusy = 1; | |
atomic_leave_critical(&criticalSection); | |
if (pipe->m_Stall != 0) { | |
pipe->m_DataBuffer = buffer; | |
pipe->m_DataLen = length; | |
return kStatus_USB_Success; | |
} | |
usb_status_t status = USB_DeviceSendRequest(ser->m_DeviceHandle, epNum, buffer, length); | |
if (status != kStatus_USB_Success) { | |
atomic_enter_critical(&criticalSection); | |
pipe->m_IsBusy = 0; | |
atomic_leave_critical(&criticalSection); | |
} | |
return status; | |
} | |
static usb_status_t usbSerialRecv(USBSerial* ser, uint8_t epNum, uint8_t *buffer, uint32_t length) | |
{ | |
if (ser == NULL) { | |
return kStatus_USB_InvalidHandle; | |
} | |
USBSerialPipe* pipe = NULL; | |
if (epNum == ser->m_BulkEndpointNum) { | |
pipe = &ser->m_BulkOutPipe; | |
} else { | |
return kStatus_USB_Error; | |
} | |
volatile atomic_t criticalSection; | |
atomic_enter_critical(&criticalSection); | |
if (pipe->m_IsBusy == 1) { | |
atomic_leave_critical(&criticalSection); | |
return kStatus_USB_Busy; | |
} | |
pipe->m_IsBusy = 1; | |
atomic_leave_critical(&criticalSection); | |
if (pipe->m_Stall != 0) { | |
pipe->m_DataBuffer = buffer; | |
pipe->m_DataLen = length; | |
return kStatus_USB_Success; | |
} | |
usb_status_t status = USB_DeviceRecvRequest(ser->m_DeviceHandle, epNum, buffer, length); | |
if (status != kStatus_USB_Success) { | |
atomic_enter_critical(&criticalSection); | |
pipe->m_IsBusy = 0; | |
atomic_leave_critical(&criticalSection); | |
} | |
return status; | |
} | |
////////////////////////////////////////////////////////////////////////// | |
// DEBUG | |
// | |
int32_t usbDbgPrintf(const char* fmt, ...) | |
{ | |
if (s_USBSerialInstance.m_Attach == 0) { | |
return 0; | |
} | |
char buffer[512]; | |
va_list va; | |
va_start(va, fmt); | |
const int32_t len = vsnprintf(buffer, 512, fmt, va); | |
const int32_t res = usbSerialWrite(&s_USBSerialInstance, (const uint8_t*)buffer, len); | |
va_end(va); | |
return res; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment