Skip to content

Instantly share code, notes, and snippets.

@jdryg
Last active June 19, 2023 15:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdryg/5af306c28c78f619a51c797a5963234e to your computer and use it in GitHub Desktop.
Save jdryg/5af306c28c78f619a51c797a5963234e to your computer and use it in GitHub Desktop.
USB CDC
#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;
}
#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
#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