/** ****************************************************************************** * @file cdc.c * @copyright AndrĂ¡s Wiesner, 2024-\showdate "%Y" * @brief CDC ACM implementation module for the flatUSB project. ****************************************************************************** */ #include "acm.h" #include #include #include #include "../usb.h" #include "../usb_device_types.h" #include "cmsis_os2.h" #include #include // ------------------------- 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 #define USB_ACM_INITIAL_DELAY_TICKS (100) ///< Delay before sending data // ------------------------- 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 osDelay(USB_ACM_INITIAL_DELAY_TICKS); // inject some initial delay 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, 1); // 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; // from now on CDC module is considered initialized acms.moduleInit = true; // create flags flags = osEventFlagsNew(NULL); // create thread osThreadAttr_t attr; memset(&attr, 0, sizeof(osThreadAttr_t)); attr.stack_size = 512; attr.name = "acm"; th = osThreadNew(thread_usb_acm, NULL, &attr); } 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; }