diff --git a/.gitmodules b/.gitmodules index 6432564096..24fe182d73 100644 --- a/.gitmodules +++ b/.gitmodules @@ -120,6 +120,10 @@ path = Sming/Arch/Host/Components/lwip/lwip url = https://github.com/lwip-tcpip/lwip ignore = dirty +[submodule "seriallib"] + path = Sming/Arch/Host/Components/SerialLib/seriallib + url = https://github.com/imabot2/serialib.git + ignore = dirty @@ -255,10 +259,6 @@ path = Sming/Libraries/RingTone url = https://github.com/mikee47/RingTone ignore = dirty -[submodule "Libraries.seriallib"] - path = Sming/Libraries/seriallib/seriallib - url = https://github.com/imabot2/serialib.git - ignore = dirty [submodule "Libraries.SignalGenerator"] path = Sming/Libraries/SignalGenerator url = https://github.com/mikee47/SignalGenerator diff --git a/Sming/Arch/Host/Components/SerialLib/SerialLib.h b/Sming/Arch/Host/Components/SerialLib/SerialLib.h new file mode 100644 index 0000000000..6f57aed795 --- /dev/null +++ b/Sming/Arch/Host/Components/SerialLib/SerialLib.h @@ -0,0 +1,7 @@ +#pragma once + +#include "seriallib/lib/serialib.h" + +class SerialDevice : public serialib +{ +}; diff --git a/Sming/Arch/Host/Components/SerialLib/component.mk b/Sming/Arch/Host/Components/SerialLib/component.mk new file mode 100644 index 0000000000..ba74706ec1 --- /dev/null +++ b/Sming/Arch/Host/Components/SerialLib/component.mk @@ -0,0 +1,4 @@ +COMPONENT_SUBMODULES += seriallib + +COMPONENT_SRCDIRS := seriallib/lib +COMPONENT_INCDIRS := . diff --git a/Sming/Libraries/seriallib/seriallib b/Sming/Arch/Host/Components/SerialLib/seriallib similarity index 100% rename from Sming/Libraries/seriallib/seriallib rename to Sming/Arch/Host/Components/SerialLib/seriallib diff --git a/Sming/Arch/Host/Components/SerialLib/seriallib.patch b/Sming/Arch/Host/Components/SerialLib/seriallib.patch new file mode 100644 index 0000000000..269d0a3cda --- /dev/null +++ b/Sming/Arch/Host/Components/SerialLib/seriallib.patch @@ -0,0 +1,22 @@ +diff --git a/lib/serialib.cpp b/lib/serialib.cpp +index 8dbe52f..dc25811 100644 +--- a/lib/serialib.cpp ++++ b/lib/serialib.cpp +@@ -135,7 +135,7 @@ char serialib::openDevice(const char *Device,const unsigned int Bauds) + case 115200 : dcbSerialParams.BaudRate=CBR_115200; break; + case 128000 : dcbSerialParams.BaudRate=CBR_128000; break; + case 256000 : dcbSerialParams.BaudRate=CBR_256000; break; +- default : return -4; ++ default : dcbSerialParams.BaudRate=Bauds; + } + // 8 bit data + dcbSerialParams.ByteSize=8; +@@ -609,7 +609,7 @@ int serialib::available() + // Device errors + DWORD commErrors; + // Device status +- COMSTAT commStatus; ++ COMSTAT commStatus{}; + // Read status + ClearCommError(hSerial, &commErrors, &commStatus); + // Return the number of pending bytes diff --git a/Sming/Arch/Host/Components/driver/component.mk b/Sming/Arch/Host/Components/driver/component.mk index 27b5e36d23..334ca3db26 100644 --- a/Sming/Arch/Host/Components/driver/component.mk +++ b/Sming/Arch/Host/Components/driver/component.mk @@ -1,6 +1,8 @@ COMPONENT_INCDIRS := include -COMPONENT_DEPENDS := arch_driver +COMPONENT_DEPENDS := \ + arch_driver \ + SerialLib COMPONENT_VARS += USE_US_TIMER USE_US_TIMER ?= 1 diff --git a/Sming/Arch/Host/Components/driver/uart.rst b/Sming/Arch/Host/Components/driver/uart.rst index 1d03f556d9..8e8a6e19c8 100644 --- a/Sming/Arch/Host/Components/driver/uart.rst +++ b/Sming/Arch/Host/Components/driver/uart.rst @@ -6,7 +6,8 @@ Host UART driver Introduction ------------ -Implements a UART driver to connect via TCP sockets, allowing terminal emulation using telnet. +Implements a UART driver to connect via TCP socket, allowing terminal emulation using telnet, +or directly to local host serial device (e.g. /dev/ttyUSB0, COM4, etc.) By default, output to UART0 is sent to the console and keyboard input is written to the UART0 receive queue. If emulation is enabled on any ports then this behaviour is disabled. @@ -44,11 +45,9 @@ Build variables Allows full customisation of UART command-line options for ``make run``. - You should not need to change this for normal use. - -Usage ------ +TCP port emulation +------------------ Set required ports for emulation using the :envvar:`ENABLE_HOST_UARTID`, then execute ``make run``. @@ -79,3 +78,26 @@ output at startup.) Port numbers are allocated sequentially from 10000. If you want to use different port numbers, set :envvar:`HOST_UART_PORTBASE`. + +Physical device connection +-------------------------- + +Override :envvar:`HOST_UART_OPTIONS` adding the `--device` option. For example:: + + make run HOST_UART_OPTIONS="--uart=0 --device=/dev/ttyUSB0" + +The ``--device`` option must follow the ``--uart`` option. Another example:: + + make run HOST_UART_OPTIONS="--uart=0 --device=/dev/ttyUSB0 --uart=1 --device=/dev/ttyUSB1" + +The port is opened when ``uart_init()`` gets called. + +The default baud rate is whatever the application has requested. This can be overridden as follows:: + + make run HOST_UART_OPTIONS="--uart=0 --device=/dev/ttyUSB0 --baud=921600" + +For Windows, substitute the appropriate device name, e.g. ``COM4`` instead of ``/dev/ttyUSB0``. + +.. note:: + + If necessary, add ``ENABLE_HOST_UARTID=`` to prevent telnet windows from being created. diff --git a/Sming/Arch/Host/Components/driver/uart_server.cpp b/Sming/Arch/Host/Components/driver/uart_server.cpp index 08e9d4f74b..2b0e5ad0a5 100644 --- a/Sming/Arch/Host/Components/driver/uart_server.cpp +++ b/Sming/Arch/Host/Components/driver/uart_server.cpp @@ -22,12 +22,15 @@ #include #include #include +#include -const unsigned IDLE_SLEEP_MS = 100; - -unsigned CUartServer::portBase = 10000; - -static CUartServer* uartServers[UART_COUNT]; +namespace UartServer +{ +namespace +{ +unsigned portBase{10000}; +std::unique_ptr servers[UART_COUNT]; +} // namespace class KeyboardThread : public CThread { @@ -131,14 +134,14 @@ static void onUart0Notify(smg_uart_t* uart, smg_uart_notify_code_t code) } } -void CUartServer::startup(const UartServerConfig& config) +void startup(const Config& config) { if(config.portBase != 0) { portBase = config.portBase; } auto notify = [](smg_uart_t* uart, smg_uart_notify_code_t code) { - auto server = uartServers[uart->uart_nr]; + auto& server = servers[uart->uart_nr]; if(server) { server->onNotify(uart, code); } else if(code == UART_NOTIFY_AFTER_WRITE) { @@ -151,8 +154,12 @@ void CUartServer::startup(const UartServerConfig& config) if(!bitRead(config.enableMask, i)) { continue; } - auto& server = uartServers[i]; - server = new CUartServer(i); + auto& server = servers[i]; + if(config.deviceNames[i] == nullptr) { + server.reset(new CUartPort(i)); + } else { + server.reset(new CUartDevice(i, config.deviceNames[i], config.baud[i])); + } server->execute(); } @@ -163,30 +170,34 @@ void CUartServer::startup(const UartServerConfig& config) } } -void CUartServer::shutdown() +void shutdown() { destroyKeyboardThread(); for(unsigned i = 0; i < UART_COUNT; ++i) { - auto& server = uartServers[i]; - if(server == nullptr) { + auto& server = servers[i]; + if(!server) { continue; } server->terminate(); - delete server; - server = nullptr; + server.reset(); } } -void CUartServer::terminate() +/* CUart */ + +CUart::CUart(unsigned uart_nr) : CThread("uart", 1), uart_nr(uart_nr) +{ +} + +void CUart::terminate() { - close(); join(); host_debug_i("UART%u server destroyed", uart_nr); } -void CUartServer::onNotify(smg_uart_t* uart, smg_uart_notify_code_t code) +void CUart::onNotify(smg_uart_t* uart, smg_uart_notify_code_t code) { switch(code) { case UART_NOTIFY_AFTER_OPEN: @@ -198,31 +209,29 @@ void CUartServer::onNotify(smg_uart_t* uart, smg_uart_notify_code_t code) break; case UART_NOTIFY_AFTER_WRITE: { - if(socket == nullptr) { - // Not connected, discard data - uart->tx_buffer->clear(); - } else { + if(uart != nullptr) { // Kick the thread to send now txsem.post(); + } else { + // Not connected, discard data + uart->tx_buffer->clear(); } break; } case UART_NOTIFY_WAIT_TX: - break; - case UART_NOTIFY_BEFORE_READ: break; } } -int CUartServer::serviceRead() +int CUart::serviceRead() { if(!smg_uart_rx_enabled(uart)) { return 0; } - int avail = socket->available(); + int avail = available(); if(avail <= 0) { return avail; } @@ -230,10 +239,13 @@ int CUartServer::serviceRead() interrupt_begin(); int space = uart->rx_buffer->getFreeSpace(); + if(space < avail) { + uart->status |= UART_RXFIFO_OVF_INT_ST; + } int read = std::min(space, avail); if(read != 0) { char buffer[read]; - read = socket->recv(buffer, read); + read = readBytes(buffer, read); if(read > 0) { for(int i = 0; i < read; ++i) { uart->rx_buffer->writeChar(buffer[i]); @@ -252,7 +264,7 @@ int CUartServer::serviceRead() return read; } -int CUartServer::serviceWrite() +int CUart::serviceWrite() { if(!smg_uart_tx_enabled(uart)) { return 0; @@ -269,7 +281,7 @@ int CUartServer::serviceWrite() interrupt_begin(); do { - int sent = socket->send(data, avail); + int sent = writeBytes(data, avail); if(sent < 0) { host_debug_w("Uart send returned %d", sent); result = sent; @@ -290,7 +302,34 @@ int CUartServer::serviceWrite() return result; } -void* CUartServer::thread_routine() +/* CUartPort */ + +CUartPort::CUartPort(unsigned uart_nr) : CUart(uart_nr) +{ +} + +void CUartPort::terminate() +{ + close(); + CUart::terminate(); +} + +int CUartPort::available() +{ + return socket ? socket->available() : 0; +} + +int CUartPort::readBytes(void* buffer, size_t size) +{ + return socket ? socket->recv(buffer, size) : 0; +} + +int CUartPort::writeBytes(const void* data, size_t size) +{ + return socket ? socket->send(data, size) : 0; +} + +void* CUartPort::thread_routine() { auto port = portBase + uart_nr; CSockAddr addr(nullptr, port); @@ -340,3 +379,102 @@ void* CUartServer::thread_routine() return nullptr; } + +/* CUartDevice */ + +CUartDevice::CUartDevice(unsigned uart_nr, const char* deviceName, unsigned baud_rate) + : CUart(uart_nr), deviceName(deviceName), baud_rate(baud_rate) +{ +} + +void CUartDevice::terminate() +{ + done = true; + txsem.post(); + CUart::terminate(); +} + +void CUartDevice::onNotify(smg_uart_t* uart, smg_uart_notify_code_t code) +{ + CUart::onNotify(uart, code); + switch(code) { + case UART_NOTIFY_AFTER_OPEN: + if(baud_rate != 0) { + uart->baud_rate = baud_rate; + } + txsem.post(); + break; + + case UART_NOTIFY_BEFORE_CLOSE: + txsem.post(); + break; + + case UART_NOTIFY_AFTER_WRITE: + case UART_NOTIFY_WAIT_TX: + case UART_NOTIFY_BEFORE_READ: + break; + } +} + +int CUartDevice::available() +{ + return device ? device->available() : 0; +} + +int CUartDevice::readBytes(void* buffer, size_t size) +{ + return device ? device->readBytes(buffer, size) : 0; +} + +int CUartDevice::writeBytes(const void* data, size_t size) +{ + return device ? device->writeBytes(data, size) : 0; +} + +void* CUartDevice::thread_routine() +{ + while(!done) { + if(txsem.timedwait(IDLE_SLEEP_MS)) { + if(uart != nullptr && !device) { + device.reset(new SerialDevice); + char res = device->openDevice(deviceName, uart->baud_rate); + if(res != 1) { + host_debug_e("UART%u error %d opening serial device '%s'", uart_nr, res, deviceName); + device.reset(); + break; + } + device->DTR(false); + device->RTS(false); + host_debug_i("UART%u connected to '%s' @ %u baud", uart_nr, deviceName, uart->baud_rate); + } else if(uart == nullptr && device) { + device.reset(); + host_debug_i("Uart #%u device closed", uart_nr); + } + + if(serviceWrite() < 0) { + break; + } + } + + if(serviceRead() < 0) { + break; + } + + if(uart != nullptr) { + interrupt_begin(); + + auto status = uart->status; + uart->status = 0; + if(status != 0 && uart->callback != nullptr) { + uart->callback(uart, status); + } + + interrupt_end(); + } + } + + device.reset(); + return nullptr; +} + +} // namespace UartServer diff --git a/Sming/Arch/Host/Components/driver/uart_server.h b/Sming/Arch/Host/Components/driver/uart_server.h index 8b053408b1..dd84fef467 100644 --- a/Sming/Arch/Host/Components/driver/uart_server.h +++ b/Sming/Arch/Host/Components/driver/uart_server.h @@ -22,52 +22,105 @@ #include #include #include +#include -#define UART_SOCKET_PORT_BASE 10000 ///< Port for UART0 +class SerialDevice; -struct UartServerConfig { - unsigned enableMask; ///< Bit mask for required servers - unsigned portBase; ///< Base port address (optional) +/* + * Manages a set of virtualised UARTs + */ +namespace UartServer +{ +struct Config { + unsigned enableMask; ///< Bit mask for required servers + unsigned portBase; ///< Base port address (optional) + const char* deviceNames[UART_COUNT]; ///< Map uart to host port + unsigned baud[UART_COUNT]; ///< Speed for physical serial device }; +class CUart; + +/** + * @brief Start requested servers + * @param config + */ +void startup(const Config& config); + +void shutdown(); + /* - * Each server allocates a thread to listen for incoming connections. Only one client - * connection is permitted. - * - * For simplicity this implementation uses sockets for communication, so we can just use - * telnet as the terminal application. + * Base class for a UART * - * A socket is opened for each uart on the system at startup. If no client is connected - * any output is discarded. + * Each server allocates a thread to handle one device. + * If no client (i.e. application `uart`) is connected any output is discarded. * */ -class CUartServer : public CThread, public CServerSocket +class CUart : public CThread { public: - /** - * @brief Start requested servers - * @param config - */ - static void startup(const UartServerConfig& config); + CUart(unsigned uart_nr); - static void shutdown(); + virtual void terminate(); - CUartServer(unsigned uart_nr) : CThread("uart", 1), uart_nr(uart_nr) - { - } - - void terminate(); + virtual void onNotify(smg_uart_t* uart, smg_uart_notify_code_t code); protected: - void onNotify(smg_uart_t* uart, smg_uart_notify_code_t code); + static const unsigned IDLE_SLEEP_MS{100}; + + virtual int available() = 0; + virtual int readBytes(void* buffer, size_t size) = 0; + virtual int writeBytes(const void* data, size_t size) = 0; int serviceRead(); int serviceWrite(); + + CSemaphore txsem; ///< Signals when there's data to be sent out + unsigned uart_nr; ///< Which port we represent + smg_uart_t* uart = nullptr; ///< On set if port is open by application +}; + +/* + * UART implementation using TCP socket for communication, so we can use telnet as a terminal application. + */ +class CUartPort : public CUart, public CServerSocket +{ +public: + CUartPort(unsigned uart_nr); + + void terminate() override; + +protected: + int available() override; + int readBytes(void* buffer, size_t size) override; + int writeBytes(const void* data, size_t size) override; void* thread_routine() override; -private: - static unsigned portBase; - CSocket* socket = nullptr; ///< Connected client - CSemaphore txsem; ///< Signals when there's data to be sent out - unsigned uart_nr; ///< Which port we represent - smg_uart_t* uart = nullptr; ///< On set if port is open by application + CSocket* socket{nullptr}; ///< Connected client }; + +/* + * UART implementation using physical serial device on local machine + */ +class CUartDevice : public CUart +{ +public: + static constexpr unsigned DEFAULT_BAUD{115200}; + + CUartDevice(unsigned uart_nr, const char* deviceName, unsigned baud); + + void terminate() override; + + void onNotify(smg_uart_t* uart, smg_uart_notify_code_t code) override; + +protected: + int available() override; + int readBytes(void* buffer, size_t size) override; + int writeBytes(const void* data, size_t size) override; + void* thread_routine() override; + + std::unique_ptr device; ///< Physical device + const char* deviceName{nullptr}; ///< Physical device name + unsigned baud_rate{0}; ///< Command-line override for baud rate + bool done{false}; +}; + +}; // namespace UartServer diff --git a/Sming/Arch/Host/Components/hostlib/options.h b/Sming/Arch/Host/Components/hostlib/options.h index a7371993b6..9634e0b2fa 100644 --- a/Sming/Arch/Host/Components/hostlib/options.h +++ b/Sming/Arch/Host/Components/hostlib/options.h @@ -31,6 +31,10 @@ XX(help, no_argument, "Show help", nullptr, nullptr, nullptr) \ XX(uart, required_argument, "Enable UART server", "PORT", "Which UART number to enable", \ "e.g. --uart=0 --uart=1 enable servers for UART0, UART1\0") \ + XX(device, required_argument, "Set device for uart", "DEVICE", "Optionally map uart to device", \ + "e.g. --uart=0 --device=/dev/ttyUSB0") \ + XX(baud, required_argument, "Set baud rate for UART", "BAUD", "Requires --device argument", \ + "e.g. --uart=0 --device=/dev/ttyUSB0 --baud=115200") \ XX(portbase, required_argument, "Specify base port number for UART socket servers", "PORT", "IP port number", \ nullptr) \ XX(ifname, required_argument, "Specify network interface", "NAME", "Network interface to use (e.g. tap0)", \ diff --git a/Sming/Arch/Host/Components/hostlib/startup.cpp b/Sming/Arch/Host/Components/hostlib/startup.cpp index 0f650f0168..aad178fea9 100644 --- a/Sming/Arch/Host/Components/hostlib/startup.cpp +++ b/Sming/Arch/Host/Components/hostlib/startup.cpp @@ -55,7 +55,7 @@ static void cleanup() { hw_timer_cleanup(); host_flashmem_cleanup(); - CUartServer::shutdown(); + UartServer::shutdown(); sockets_finalise(); #ifndef DISABLE_WIFI host_lwip_shutdown(); @@ -135,7 +135,7 @@ int main(int argc, char* argv[]) int exitpause; bool initonly; bool enable_network; - UartServerConfig uart; + UartServer::Config uart; FlashmemConfig flash; #ifndef DISABLE_WIFI struct lwip_param lwip; @@ -165,6 +165,7 @@ int main(int argc, char* argv[]) #endif }; + int uart_num{-1}; option_tag_t opt; const char* arg; while((opt = get_option(argc, argv, arg)) != opt_none) { @@ -174,7 +175,25 @@ int main(int argc, char* argv[]) return 0; case opt_uart: - bitSet(config.uart.enableMask, atoi(arg)); + uart_num = atoi(arg); + if(uart_num < 0 || uart_num >= UART_COUNT) { + host_printf("UART %d number invalid\r\n", uart_num); + return 0; + } + bitSet(config.uart.enableMask, uart_num); + break; + + case opt_device: + case opt_baud: + if(uart_num < 0) { + host_printf("--uart option missing\r\n"); + return 0; + } + if(opt == opt_device) { + config.uart.deviceNames[uart_num] = arg; + } else if(opt == opt_baud) { + config.uart.baud[uart_num] = atoi(arg); + } break; case opt_portbase: @@ -263,7 +282,7 @@ int main(int argc, char* argv[]) host_init_tasks(); sockets_initialise(); - CUartServer::startup(config.uart); + UartServer::startup(config.uart); #ifndef DISABLE_WIFI if(config.enable_network) { diff --git a/Sming/Components/Hosted/component.mk b/Sming/Components/Hosted/component.mk index 49d6f81bd2..29dde196ff 100644 --- a/Sming/Components/Hosted/component.mk +++ b/Sming/Components/Hosted/component.mk @@ -13,7 +13,7 @@ ENABLE_HOSTED ?= ifneq ($(ENABLE_HOSTED),) COMPONENT_SRCDIRS += $(COMPONENT_PATH)/init/$(ENABLE_HOSTED) EXTRA_LDFLAGS := -Wl,-wrap,host_init - COMPONENT_DEPENDS += seriallib + COMPONENT_DEPENDS += SerialLib endif COMPONENT_VARS += HOSTED_SERVER_IP diff --git a/Sming/Components/Hosted/include/Hosted/Serial.h b/Sming/Components/Hosted/include/Hosted/Serial.h index 1d26b52bd4..8bdfc94b10 100644 --- a/Sming/Components/Hosted/include/Hosted/Serial.h +++ b/Sming/Components/Hosted/include/Hosted/Serial.h @@ -19,7 +19,7 @@ #include #include -#include +#include namespace Hosted { diff --git a/Sming/Libraries/seriallib/component.mk b/Sming/Libraries/seriallib/component.mk deleted file mode 100644 index a350642adf..0000000000 --- a/Sming/Libraries/seriallib/component.mk +++ /dev/null @@ -1,6 +0,0 @@ -COMPONENT_SUBMODULES += seriallib - -SERIALLIB_ROOT := $(COMPONENT_PATH)/seriallib - -COMPONENT_SRCDIRS := $(SERIALLIB_ROOT)/lib $(COMPONENT_PATH)/src -COMPONENT_INCDIRS := $(COMPONENT_SRCDIRS) $(COMPONENT_PATH)/include \ No newline at end of file