195 lines
6.7 KiB
C
195 lines
6.7 KiB
C
/**
|
|
******************************************************************************
|
|
* @file cdc.c
|
|
* @copyright András Wiesner, 2024-\showdate "%Y"
|
|
* @brief CDC ACM implementation module for the flatUSB project.
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "acm.h"
|
|
|
|
#include <memory.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "../usb.h"
|
|
#include "../usb_device_types.h"
|
|
#include "cmsis_os2.h"
|
|
|
|
#include <blocking_io/blocking_fifo.h>
|
|
#include <strings.h>
|
|
|
|
// -------------------------
|
|
|
|
static Usb_AcmState acms = {0}; ///< ACM module state
|
|
static uint8_t tx_buffer[USB_ACM_PCKT_BUFSIZE]; ///< Transmit buffer
|
|
static uint8_t fifo_mem[USB_ACM_FIFO_MEM_SIZE]; ///< Memory assigned to the TX BFifo
|
|
static BFifo fifo; ///< TX Blocking FIFO
|
|
static osThreadId_t th; ///< ACM thread
|
|
static osEventFlagsId_t flags; ///< Event flags
|
|
|
|
#define USB_ACM_DATA_IN_DONE (0x01) ///< IN transfer done flag
|
|
#define USB_ACM_COMM_INIT (0x02) ///< Communication has been initialized
|
|
#define USB_ACM_HOSTBOUND_DATA_AVAIL (0x04) ///< Hostbound data is available
|
|
|
|
#define USB_ACM_LOOP_TIMEOUT_TICKS (100) ///< Main loop timeout for interrupt status transmission
|
|
|
|
// -------------------------
|
|
|
|
static void thread_usb_acm(void *arg) {
|
|
osEventFlagsWait(flags, USB_ACM_COMM_INIT, 0, osWaitForever); // wait for communication to become initialized
|
|
|
|
osEventFlagsSet(flags, USB_ACM_DATA_IN_DONE); // assume we can write to the data endpoint
|
|
|
|
while (true) {
|
|
uint32_t signals = osEventFlagsWait(flags, USB_ACM_HOSTBOUND_DATA_AVAIL, osFlagsWaitAny, USB_ACM_LOOP_TIMEOUT_TICKS);
|
|
|
|
if (signals != osErrorTimeout) { // check timeout
|
|
if (signals & USB_ACM_HOSTBOUND_DATA_AVAIL) { // data hostbound available
|
|
do {
|
|
osEventFlagsWait(flags, USB_ACM_DATA_IN_DONE, osFlagsWaitAny, 1000); // wait for the IN DONE flag
|
|
uint32_t readSize = bfifo_read(&fifo, tx_buffer, USB_ACM_PCKT_BUFSIZE); // read from the fifo
|
|
if (readSize > 0) {
|
|
uint32_t writeSize = usbcore_schedule_transmission(acms.ep_assignments.data_ep, tx_buffer, readSize); // write data acquired from the buffer
|
|
bfifo_pop(&fifo, writeSize, 0); // pop with no blocking
|
|
}
|
|
} while (bfifo_get_used(&fifo) > 0);
|
|
}
|
|
} else { // timeout
|
|
// send an all-zero interrupt
|
|
usbcore_schedule_transmission(acms.ep_assignments.control_ep, (const uint8_t *)&(acms.interrupt_data), sizeof(uint16_t));
|
|
}
|
|
}
|
|
}
|
|
|
|
// -------------------------
|
|
|
|
void usb_acm_read_callback(const uint8_t *data, uint32_t size);
|
|
|
|
void usb_acm_init(const Usb_Acm_EpAssignments *as) {
|
|
// clear the structure
|
|
memset(&acms, 0, sizeof(Usb_AcmState));
|
|
|
|
// set Control Line State to something invalid
|
|
acms.control_line_state.D = USB_ACM_INVALID_CONTROL_LINE_STATE;
|
|
|
|
// fill-in assigments
|
|
acms.ep_assignments = *as;
|
|
|
|
// initialize buffer
|
|
bfifo_create(&fifo, fifo_mem, USB_ACM_FIFO_MEM_SIZE);
|
|
|
|
// initialize an all-0 interrupt
|
|
acms.interrupt_data = 0;
|
|
|
|
// communication parameters have not been set
|
|
acms.commInit = false;
|
|
|
|
// create flags
|
|
flags = osEventFlagsNew(NULL);
|
|
|
|
// create thread
|
|
osThreadAttr_t attr;
|
|
memset(&attr, 0, sizeof(osThreadAttr_t));
|
|
attr.stack_size = 1024;
|
|
attr.name = "acm";
|
|
th = osThreadNew(thread_usb_acm, NULL, &attr);
|
|
|
|
// from now on CDC module is considered initialized
|
|
acms.moduleInit = true;
|
|
}
|
|
|
|
static void usb_cdc_review_comm_init() {
|
|
// check if line coding was initialized
|
|
Usb_Acm_LineCodingStruct *lc = &acms.line_coding;
|
|
bool lcOk = (lc->dwDTERate != 0) && (lc->bDataBits != 0);
|
|
|
|
// check if control line state was initialized
|
|
bool clsOk = acms.control_line_state.D != USB_ACM_INVALID_CONTROL_LINE_STATE;
|
|
|
|
// combine the above criteria
|
|
acms.commInit = lcOk && clsOk;
|
|
|
|
// signal the processing thread
|
|
osEventFlagsSet(flags, USB_ACM_COMM_INIT);
|
|
}
|
|
|
|
int usb_acm_process_and_return(Usb_CallbackEvent *cbevt) {
|
|
int ret = -1;
|
|
switch (cbevt->type) {
|
|
case USB_CBEVT_UNKNOWN_REQ: {
|
|
// initialize reply
|
|
cbevt->reply_data = NULL;
|
|
cbevt->reply_size = 0;
|
|
cbevt->reply_valid = true;
|
|
|
|
switch (cbevt->setup_request->bRequest) {
|
|
case USB_ACM_SET_LINE_CODING: // set line coding
|
|
memcpy(&acms.line_coding, cbevt->data, sizeof(Usb_Acm_LineCodingStruct));
|
|
usb_cdc_review_comm_init(); // review the communcation initialization state
|
|
ret = 0;
|
|
break;
|
|
case USB_ACM_GET_LINE_CODING: // get line coding
|
|
cbevt->reply_data = (const uint8_t *)&acms.line_coding; // expert move: pass the pointer, no copying
|
|
cbevt->reply_size = sizeof(Usb_Acm_LineCodingStruct);
|
|
ret = 0;
|
|
break;
|
|
case USB_ACM_SEND_BREAK: // send break
|
|
// do nothing
|
|
ret = 0;
|
|
break;
|
|
case USB_ACM_SET_CONTROL_LINE_STATE: // set control line state
|
|
acms.control_line_state.D = cbevt->setup_request->wValue; // control line state is carried in wValue of the SETUP request
|
|
usb_cdc_review_comm_init(); // review the communcation initialization state
|
|
ret = 0;
|
|
break;
|
|
default:
|
|
cbevt->reply_valid = false; // this event is not processed by or not related to the CDC ACM module
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case USB_CBEVT_OUT: {
|
|
if (cbevt->ep == acms.ep_assignments.data_ep) {
|
|
ret = 0;
|
|
usb_acm_read_callback(cbevt->data, cbevt->size);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case USB_CBEVT_IN: {
|
|
if (cbevt->ep == acms.ep_assignments.data_ep) {
|
|
osEventFlagsSet(flags, USB_ACM_DATA_IN_DONE);
|
|
}
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void usb_acm_write(const uint8_t *data, uint32_t size) {
|
|
if (acms.moduleInit) {
|
|
bfifo_push_all(&fifo, data, size);
|
|
osEventFlagsSet(flags, USB_ACM_HOSTBOUND_DATA_AVAIL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @fn void usb_acm_read_callback(const uint8_t *data, uint32_t size)
|
|
* Callback function prototype for data reception. This function is
|
|
* expected to be overridden by the application.
|
|
*
|
|
* @param data ingress data
|
|
* @param size length of the data
|
|
*/
|
|
__attribute__((weak)) void usb_acm_read_callback(const uint8_t *data, uint32_t size) {
|
|
(void)data;
|
|
(void)size;
|
|
return;
|
|
} |