From b19e1a5a5d41896a5f225e13d96c545b1ebd73ce Mon Sep 17 00:00:00 2001 From: Rob Riggs Date: Sun, 28 Oct 2018 21:01:24 -0500 Subject: [PATCH] Handle single-byte transfers over USB more efficiently. Linux seems to do only single byte transfers for ACM ports. --- TNC/UsbPort.cpp | 195 +++++++++++++++++++++++++++--------------------- TNC/UsbPort.hpp | 13 ++++ 2 files changed, 125 insertions(+), 83 deletions(-) diff --git a/TNC/UsbPort.cpp b/TNC/UsbPort.cpp index 5b714b1..b9620e6 100644 --- a/TNC/UsbPort.cpp +++ b/TNC/UsbPort.cpp @@ -1,10 +1,11 @@ // Copyright 2016 Rob Riggs // All rights reserved. -#include #include "UsbPort.hpp" #include "HdlcFrame.hpp" #include "Kiss.hpp" +#include "Log.h" + #include "usbd_cdc_if.h" #include "usb_device.h" #include "cmsis_os.h" @@ -13,115 +14,138 @@ extern "C" void TNC_Error_Handler(int dev, int err); extern osMessageQId ioEventQueueHandle; -extern "C" void cdc_receive(const uint8_t* buf, uint32_t len) { - using namespace mobilinkd::tnc::hdlc; - if (mobilinkd::tnc::getUsbPort()->queue() != 0) { - auto frame = mobilinkd::tnc::hdlc::acquire(); - if (frame) { - for (uint32_t i = 0; i != len; ++i) { - frame->push_back(*buf++); - } - frame->source(IoFrame::SERIAL_DATA); - if (osMessagePut(mobilinkd::tnc::getUsbPort()->queue(), (uint32_t) frame, - osWaitForever) != osOK) { - mobilinkd::tnc::hdlc::release(frame); - } - } - } -} +extern "C" void cdc_receive(const uint8_t* buf, uint32_t len) +{ + using namespace mobilinkd::tnc; + if (mobilinkd::tnc::getUsbPort()->queue() != 0) + { + if (len == 1) + { + // Send single byte via queue directly. Linux seems to do + // this for ttyACM ports. + osMessagePut(getUsbPort()->queue(), *buf, osWaitForever); + return; + } + auto frame = hdlc::acquire(); + if (frame) + { + for (uint32_t i = 0; i != len; ++i) + { + frame->push_back(*buf++); + } + frame->source(hdlc::IoFrame::SERIAL_DATA); + if (osMessagePut(mobilinkd::tnc::getUsbPort()->queue(), + (uint32_t) frame, + osWaitForever) != osOK) + { + mobilinkd::tnc::hdlc::release(frame); + } + } + } +} extern "C" void startCDCTask(void const* arg) { using namespace mobilinkd::tnc; - auto usbPort = static_cast(arg); + auto usbPort = const_cast(static_cast(arg)); - const uint8_t FEND = 0xC0; - const uint8_t FESC = 0xDB; - const uint8_t TFEND = 0xDC; - const uint8_t TFESC = 0xDD; + usbPort->run(); +} - uint8_t frame_type = kiss::FRAME_DATA; +namespace mobilinkd { namespace tnc { - enum State {WAIT_FBEGIN, WAIT_FRAME_TYPE, WAIT_FEND, WAIT_ESCAPED}; - State state = WAIT_FBEGIN; +void UsbPort::add_char(uint8_t c) +{ + switch (state_) { + case WAIT_FBEGIN: + if (c == FEND) state_ = WAIT_FRAME_TYPE; + break; + case WAIT_FRAME_TYPE: + if (c == FEND) break; // Still waiting for FRAME_TYPE. + frame_->type(c); + state_ = WAIT_FEND; + break; + case WAIT_FEND: + switch (c) { + case FESC: + state_ = WAIT_ESCAPED; + break; + case FEND: + frame_->source(hdlc::IoFrame::SERIAL_DATA); + osMessagePut(ioEventQueueHandle, reinterpret_cast(frame_), + osWaitForever); + frame_ = hdlc::acquire(); + state_ = WAIT_FBEGIN; + break; + default: + if (not frame_->push_back(c)) { + hdlc::release(frame_); + state_ = WAIT_FBEGIN; // Drop frame; + frame_ = hdlc::acquire(); + } + } + break; + case WAIT_ESCAPED: + state_ = WAIT_FEND; + switch (c) { + case TFESC: + if (not frame_->push_back(FESC)) { + hdlc::release(frame_); + state_ = WAIT_FBEGIN; // Drop frame; + frame_ = hdlc::acquire(); + } + break; + case TFEND: + if (not frame_->push_back(FEND)) { + hdlc::release(frame_); + state_ = WAIT_FBEGIN; // Drop frame; + frame_ = hdlc::acquire(); + } + break; + default: + hdlc::release(frame_); + state_ = WAIT_FBEGIN; // Drop frame; + frame_ = hdlc::acquire(); + } + break; + } +} - hdlc::IoFrame* frame = hdlc::acquire(); +void UsbPort::run() +{ + frame_ = hdlc::acquire(); while (true) { - osEvent evt = osMessageGet(usbPort->queue(), osWaitForever); + osEvent evt = osMessageGet(queue(), osWaitForever); if (evt.status != osEventMessage) { continue; } + uint32_t c = evt.value.v; + + if (c < 0x100) // Assume single byte transfer. + { + add_char(c); + continue; + } + auto input = static_cast(evt.value.p); - if (!usbPort->isOpen()) { + if (!isOpen()) { hdlc::release(input); continue; } - for (auto& c : *input) { - switch (state) { - case WAIT_FBEGIN: - if (c == FEND) state = WAIT_FRAME_TYPE; - break; - case WAIT_FRAME_TYPE: - if (c == FEND) break; // Still waiting for FRAME_TYPE. - frame->type(c); - state = WAIT_FEND; - break; - case WAIT_FEND: - switch (c) { - case FESC: - state = WAIT_ESCAPED; - break; - case FEND: - frame->source(hdlc::IoFrame::SERIAL_DATA); - osMessagePut(ioEventQueueHandle, reinterpret_cast(frame), osWaitForever); - frame = hdlc::acquire(); - state = WAIT_FBEGIN; - break; - default: - if (not frame->push_back(c)) { - hdlc::release(frame); - state = WAIT_FBEGIN; // Drop frame; - frame = hdlc::acquire(); - } - } - break; - case WAIT_ESCAPED: - state = WAIT_FEND; - switch (c) { - case TFESC: - if (not frame->push_back(FESC)) { - hdlc::release(frame); - state = WAIT_FBEGIN; // Drop frame; - frame = hdlc::acquire(); - } - break; - case TFEND: - if (not frame->push_back(FEND)) { - hdlc::release(frame); - state = WAIT_FBEGIN; // Drop frame; - frame = hdlc::acquire(); - } - break; - default: - hdlc::release(frame); - state = WAIT_FBEGIN; // Drop frame; - frame = hdlc::acquire(); - } - break; - } + for (uint8_t c : *input) { + add_char(c); } hdlc::release(input); } -} -namespace mobilinkd { namespace tnc { +} void UsbPort::init() { @@ -310,6 +334,11 @@ bool UsbPort::write(hdlc::IoFrame* frame, uint32_t timeout) result = transmit_buffer(pos, start, timeout); } + if (result and pos == TX_BUFFER_SIZE) { + // Must send an empty packet to flush the endpoint. + result = transmit_buffer(0, start, timeout); + } + hdlc::release(frame); osMutexRelease(mutex_); return result; diff --git a/TNC/UsbPort.hpp b/TNC/UsbPort.hpp index dc58604..97b9f69 100644 --- a/TNC/UsbPort.hpp +++ b/TNC/UsbPort.hpp @@ -29,13 +29,26 @@ struct UsbPort : PortInterface void init(); + void run(); + private: bool transmit_buffer(size_t pos, uint32_t start, uint32_t timeout); + static constexpr const uint8_t FEND = 0xC0; + static constexpr const uint8_t FESC = 0xDB; + static constexpr const uint8_t TFEND = 0xDC; + static constexpr const uint8_t TFESC = 0xDD; + + enum State {WAIT_FBEGIN, WAIT_FRAME_TYPE, WAIT_FEND, WAIT_ESCAPED}; + + void add_char(uint8_t c); + bool open_{false}; // opened/closed osMutexId mutex_{0}; // TX Mutex osMessageQId queue_{0}; // ISR read queue osThreadId cdcTaskHandle_{0}; // CDC read handler + State state_{WAIT_FBEGIN}; + hdlc::IoFrame* frame_{nullptr}; }; UsbPort* getUsbPort();