From 239d13928e810d48ea12454a744e801ac0ae49e2 Mon Sep 17 00:00:00 2001 From: Alex Shepherd Date: Sat, 19 Oct 2019 23:39:53 -0400 Subject: [PATCH 001/171] Added SocketCAN support to the hub application on the Raspberry Pi --- .../hub/targets/linux.rpi1/AvaHiMDNS.cxx | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx diff --git a/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx b/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx new file mode 100644 index 000000000..1c967d280 --- /dev/null +++ b/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx @@ -0,0 +1,140 @@ +/** \copyright + * Copyright (c) 2016, Stuart W. Baker + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file AvaHiMDNS.cxx + * + * A simple abstraction to publish mDNS sevices using Avahi. + * + * @author Stuart Baker + * @date 30 July 2016 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils/macros.h" +#include "openlcb/TcpDefs.hxx" + +static AvahiEntryGroup *group = nullptr; +static AvahiSimplePoll *simplePoll = nullptr; +static AvahiClient *client = nullptr; +static sem_t wait; + +/** Avahi group callback. + */ +static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, + void *userdata) +{ + group = g; +} + +/** Avahi client callback. + */ +static void client_callback(AvahiClient *c, AvahiClientState state, + void * userdata) +{ + switch (state) + { + default: + break; + case AVAHI_CLIENT_S_RUNNING: + printf("client running\n"); + break; + } +} + +/** mdns polling thread. + * @param unused unused parameter + * @return should never return + */ +static void *mdns_thread(void *unused) +{ + int error; + + simplePoll = avahi_simple_poll_new(); + HASSERT(simplePoll); + + client = avahi_client_new(avahi_simple_poll_get(simplePoll), + (AvahiClientFlags)0, client_callback, nullptr, + &error); + printf("client created\n"); + HASSERT(client); + + sem_post(&wait); + avahi_simple_poll_loop(simplePoll); + + return nullptr; +} + +/** Start the mDNS client. + */ +void mdns_client_start() +{ + sem_init(&wait, 0, 0); + + pthread_t thread; + pthread_create(&thread, nullptr, mdns_thread, nullptr); + printf("client start\n"); + + sem_wait(&wait); +} + +/** Publish an mDNS name. + */ +void mdns_publish(const char *name, uint16_t port) +{ + name = avahi_strdup(name); + + if (!group) + { + group = avahi_entry_group_new(client, entry_group_callback, nullptr); + if (!group) { + fprintf(stderr, "avahi_entry_group_new() failed: %s\n", + avahi_strerror(avahi_client_errno(client))); + } + HASSERT(group); + } + + int result = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, (AvahiPublishFlags)0, name, + openlcb::TcpDefs::MDNS_SERVICE_NAME_GRIDCONNECT_CAN_TCP, NULL, NULL, port, + "platform=linux-x86-openmrn", NULL); + + if (result != 0) + { + fprintf(stderr, "Error exporting mDNS name (%d) %s\n", result, + avahi_strerror(result)); + } + + HASSERT(result == 0); + avahi_entry_group_commit(group); +} From ce9cb5bf685a91e43f6a33d7fc27d1882d7d2719 Mon Sep 17 00:00:00 2001 From: Alex Shepherd Date: Sat, 19 Oct 2019 23:42:14 -0400 Subject: [PATCH 002/171] Added SocketCAN support to the hub application on the Raspberry Pi --- applications/hub/main.cxx | 53 +++++++++++++++++++++- src/openlcb/SimpleNodeInfo.cxx | 5 +- src/openlcb/SimpleNodeInfoMockUserFile.cxx | 2 +- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/applications/hub/main.cxx b/applications/hub/main.cxx index 599710baf..ed641eee9 100644 --- a/applications/hub/main.cxx +++ b/applications/hub/main.cxx @@ -37,6 +37,17 @@ #include #include +#if defined(__linux__) || defined(__MACH__) +#include +#include /* tc* functions */ +#endif + +#if defined(__linux__) +#include "utils/HubDeviceSelect.hxx" +#include +#include +#endif + #include #include "os/os.h" @@ -58,6 +69,7 @@ OVERRIDE_CONST(gridconnect_buffer_delay_usec, 2000); int port = 12021; const char *device_path = nullptr; +const char *socket_can_path = nullptr; int upstream_port = 12021; const char *upstream_host = nullptr; bool timestamped = false; @@ -79,6 +91,8 @@ void usage(const char *e) fprintf(stderr, "\t-d device is a path to a physical device doing " "serial-CAN or USB-CAN. If specified, opens device and " "adds it to the hub.\n"); + fprintf(stderr, "\t-s socketcan device is a path to a socketcan device. " + "If specified, opens device and adds it to the hub.\n"); fprintf(stderr, "\t-u upstream_host is the host name for an upstream " "hub. If specified, this hub will connect to an upstream " "hub.\n"); @@ -100,7 +114,7 @@ void usage(const char *e) void parse_args(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "hp:d:u:q:tlmn:")) >= 0) + while ((opt = getopt(argc, argv, "hp:d:s:u:q:tlmn:")) >= 0) { switch (opt) { @@ -110,6 +124,9 @@ void parse_args(int argc, char *argv[]) case 'd': device_path = optarg; break; + case 's': + socket_can_path = optarg; + break; case 'p': port = atoi(optarg); break; @@ -178,6 +195,40 @@ int appl_main(int argc, char *argv[]) new DeviceConnectionClient("device", &can_hub0, device_path)); } + if (socket_can_path) + { + int s; + struct sockaddr_can addr; + struct ifreq ifr; + int loopback = 1; + + s = socket(PF_CAN, SOCK_RAW, CAN_RAW); + + // Set the blocking limit to the minimum allowed, typically 1024 in Linux + int sndbuf = 0; + setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); + + // turn on/off loopback + setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback)); + + // setup error notifications + can_err_mask_t err_mask = CAN_ERR_TX_TIMEOUT | CAN_ERR_LOSTARB | + CAN_ERR_CRTL | CAN_ERR_PROT | CAN_ERR_TRX | CAN_ERR_ACK | + CAN_ERR_BUSOFF | CAN_ERR_BUSERROR | CAN_ERR_RESTARTED; + setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask)); + strcpy(ifr.ifr_name, socket_can_path); + + ::ioctl(s, SIOCGIFINDEX, &ifr); + + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + + bind(s, (struct sockaddr *)&addr, sizeof(addr)); + + new HubDeviceSelect(&can_hub0, s); +// connections.emplace_back(port); + } + while (1) { for (const auto &p : connections) diff --git a/src/openlcb/SimpleNodeInfo.cxx b/src/openlcb/SimpleNodeInfo.cxx index 35766f2ca..7df04e519 100644 --- a/src/openlcb/SimpleNodeInfo.cxx +++ b/src/openlcb/SimpleNodeInfo.cxx @@ -65,9 +65,8 @@ void init_snip_user_file(int fd, const char *user_name, SimpleNodeDynamicValues data; memset(&data, 0, sizeof(data)); data.version = 2; - strncpy(data.user_name, user_name, sizeof(data.user_name)); - strncpy(data.user_description, user_description, - sizeof(data.user_description)); + memcpy(data.user_name, user_name, sizeof(data.user_name)); + memcpy(data.user_description, user_description, sizeof(data.user_description)); int ofs = 0; auto *p = (const uint8_t *)&data; const int len = sizeof(data); diff --git a/src/openlcb/SimpleNodeInfoMockUserFile.cxx b/src/openlcb/SimpleNodeInfoMockUserFile.cxx index f8a48b951..c5c22fb95 100644 --- a/src/openlcb/SimpleNodeInfoMockUserFile.cxx +++ b/src/openlcb/SimpleNodeInfoMockUserFile.cxx @@ -63,7 +63,7 @@ openlcb::MockSNIPUserFile::MockSNIPUserFile(const char *user_name, { init_snip_user_file(userFile_.fd(), user_name, user_description); HASSERT(userFile_.name().size() < sizeof(snip_user_file_path)); - strncpy(snip_user_file_path, userFile_.name().c_str(), + memcpy(snip_user_file_path, userFile_.name().c_str(), sizeof(snip_user_file_path)); } From 8af69a5d1ba61fbbdcbac6a8887631f852309a86 Mon Sep 17 00:00:00 2001 From: Alex Shepherd Date: Sun, 20 Oct 2019 00:45:58 -0400 Subject: [PATCH 003/171] Added SocketCAN support to the hub application on the Raspberry Pi --- applications/hub/targets/linux.rpi1/README.md | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 applications/hub/targets/linux.rpi1/README.md diff --git a/applications/hub/targets/linux.rpi1/README.md b/applications/hub/targets/linux.rpi1/README.md new file mode 100644 index 000000000..b2c866806 --- /dev/null +++ b/applications/hub/targets/linux.rpi1/README.md @@ -0,0 +1,36 @@ +# Adding SocketCAN support on the Raspberry Pi 4 + +These are my notes from adding SocketCAN support and building the **hub** application in a Raspberry Pi. + +- Start with the pre-built JMRI - RPI disk image from: [M Steve Todd's - JMRI RaspberryPi as Access Point](https://mstevetodd.com/rpi) + +- Install the MCP2517 CAN interface hardware from: [2-Channel CAN-BUS(FD) Shield for Raspberry Pi](https://www.seeedstudio.com/2-Channel-CAN-BUS-FD-Shield-for-Raspberry-Pi-p-4072.html) and followed their instructions for building the MCP2517 kernel device driver on their [Support Wiki](http://wiki.seeedstudio.com/2-Channel-CAN-BUS-FD-Shield-for-Raspberry-Pi/#software) page. + +- Install some needed packages on the RPi: + + `sudo apt install git doxygen libavahi-client-dev` + +- Download the OpenMRN source code to the RPi: + + `cd ~` + + `git clone https://github.com/bakerstu/openmrn.git` + +- Build the **hub** application: + + `cd openmrn/applications/hub/targets/linux.rpi1/` + + `make` + +- Configure the **can0** interface for 125,000 bps and run the **hub** application at system at start-up by creating the file: `/etc/network/interfaces.d/can0` with the following lines: + ``` + allow-hotplug can0 + iface can0 can static + bitrate 125000 + up /home/pi/openmrn/applications/hub/targets/linux.rpi1/hub -s $IFACE & + ``` + +- Configure the LCC Layout Connection in JMRI to use + - System Connection: `CAN via GridConnect Network Interface` + - IP Address/Host Name: `localhost` + - TCP/UDP Port: `12021` From 74d22f91126aaf7522930093c68e0be2ac75b2fb Mon Sep 17 00:00:00 2001 From: Alex Shepherd Date: Wed, 29 Jan 2020 13:55:32 +1300 Subject: [PATCH 004/171] Update main.cxx Removed dead code --- applications/hub/main.cxx | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/hub/main.cxx b/applications/hub/main.cxx index ed641eee9..86898fbad 100644 --- a/applications/hub/main.cxx +++ b/applications/hub/main.cxx @@ -226,7 +226,6 @@ int appl_main(int argc, char *argv[]) bind(s, (struct sockaddr *)&addr, sizeof(addr)); new HubDeviceSelect(&can_hub0, s); -// connections.emplace_back(port); } while (1) From d5a3d9c35b464e1ca0d9101f994b0b95f4115845 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 19 Jul 2020 17:12:19 +0200 Subject: [PATCH 005/171] Feature macro work to remove ARDUINO ifdefs. (#407) * Adds feature macro OPENMRN_HAVE_POSIX_FD. Replaces uses of ifdef ARDUINO with feature macros. * Adds comment. --- include/openmrn_features.h | 7 +++++++ src/executor/Executor.cxx | 17 +++++++++-------- src/openlcb/SimpleInfoProtocol.hxx | 7 ++++--- src/openlcb/SimpleNodeInfo.cxx | 12 +++++++----- src/openlcb/SimpleStack.cxx | 9 +++++---- src/utils/Queue.hxx | 3 ++- 6 files changed, 34 insertions(+), 21 deletions(-) diff --git a/include/openmrn_features.h b/include/openmrn_features.h index 870b97a40..8a619a0f6 100644 --- a/include/openmrn_features.h +++ b/include/openmrn_features.h @@ -48,6 +48,13 @@ #define OPENMRN_FEATURE_REENT 1 #endif +#if defined(__linux__) || defined(__MACH__) || defined(__WINNT__) || defined(ESP32) || defined(OPENMRN_FEATURE_DEVTAB) +/// Enables the code using ::open ::close ::read ::write for non-volatile +/// storage, FileMemorySpace for the configuration space, and +/// SNIP_DYNAMIC_FILE_NAME for node names. +#define OPENMRN_HAVE_POSIX_FD 1 +#endif + /// @todo this should probably be a whitelist: __linux__ || __MACH__. #if !defined(__FreeRTOS__) && !defined(__WINNT__) && !defined(ESP32) && \ !defined(ARDUINO) && !defined(ESP_NONOS) diff --git a/src/executor/Executor.cxx b/src/executor/Executor.cxx index ed8394dd2..3fcc69317 100644 --- a/src/executor/Executor.cxx +++ b/src/executor/Executor.cxx @@ -37,6 +37,7 @@ #include "executor/Executor.hxx" +#include "openmrn_features.h" #include #ifdef __WINNT__ @@ -228,14 +229,6 @@ void *ExecutorBase::entry() return nullptr; } -#elif defined(ARDUINO) && !defined(ESP32) - -void *ExecutorBase::entry() -{ - DIE("Arduino code should not start the executor."); - return nullptr; -} - #elif defined(ESP_NONOS) #define EXECUTOR_TASK_PRIO USER_TASK_PRIO_0 @@ -285,6 +278,14 @@ void ICACHE_FLASH_ATTR *ExecutorBase::entry() return nullptr; } +#elif OPENMRN_FEATURE_SINGLE_THREADED + +void *ExecutorBase::entry() +{ + DIE("Arduino code should not start the executor."); + return nullptr; +} + #else /** Thread entry point. * @return Should never return diff --git a/src/openlcb/SimpleInfoProtocol.hxx b/src/openlcb/SimpleInfoProtocol.hxx index 272447061..b8def4bb2 100644 --- a/src/openlcb/SimpleInfoProtocol.hxx +++ b/src/openlcb/SimpleInfoProtocol.hxx @@ -38,6 +38,7 @@ #include #include +#include "openmrn_features.h" #include "openlcb/If.hxx" #include "executor/StateFlow.hxx" @@ -217,7 +218,7 @@ private: } break; } -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD case SimpleInfoDescriptor::FILE_CHAR_ARRAY: open_and_seek_next_file(); // fall through @@ -227,7 +228,7 @@ private: currentLength_ = d.arg; HASSERT(currentLength_); break; -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD case SimpleInfoDescriptor::FILE_LITERAL_BYTE: { open_and_seek_next_file(); @@ -240,7 +241,7 @@ private: byteOffset_ = 0; break; } -#endif // NOT ARDUINO, YES ESP32 +#endif // if have fd default: currentLength_ = 0; } diff --git a/src/openlcb/SimpleNodeInfo.cxx b/src/openlcb/SimpleNodeInfo.cxx index 35766f2ca..1bfc666b5 100644 --- a/src/openlcb/SimpleNodeInfo.cxx +++ b/src/openlcb/SimpleNodeInfo.cxx @@ -34,6 +34,8 @@ #include "openlcb/SimpleNodeInfo.hxx" +#include "openmrn_features.h" + namespace openlcb { @@ -46,15 +48,15 @@ const SimpleInfoDescriptor SNIPHandler::SNIP_RESPONSE[] = { {SimpleInfoDescriptor::C_STRING, 0, 0, SNIP_STATIC_DATA.model_name}, {SimpleInfoDescriptor::C_STRING, 0, 0, SNIP_STATIC_DATA.hardware_version}, {SimpleInfoDescriptor::C_STRING, 0, 0, SNIP_STATIC_DATA.software_version}, -#if defined(ARDUINO) && (!defined(ESP32)) +#if OPENMRN_HAVE_POSIX_FD + {SimpleInfoDescriptor::FILE_LITERAL_BYTE, 2, 0, SNIP_DYNAMIC_FILENAME}, + {SimpleInfoDescriptor::FILE_C_STRING, 63, 1, SNIP_DYNAMIC_FILENAME}, + {SimpleInfoDescriptor::FILE_C_STRING, 64, 64, SNIP_DYNAMIC_FILENAME}, +#else /// @todo(balazs.racz) Add eeprom support to arduino. {SimpleInfoDescriptor::LITERAL_BYTE, 2, 0, nullptr}, {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, -#else - {SimpleInfoDescriptor::FILE_LITERAL_BYTE, 2, 0, SNIP_DYNAMIC_FILENAME}, - {SimpleInfoDescriptor::FILE_C_STRING, 63, 1, SNIP_DYNAMIC_FILENAME}, - {SimpleInfoDescriptor::FILE_C_STRING, 64, 64, SNIP_DYNAMIC_FILENAME}, #endif {SimpleInfoDescriptor::END_OF_DATA, 0, 0, 0}}; diff --git a/src/openlcb/SimpleStack.cxx b/src/openlcb/SimpleStack.cxx index 49890ac99..f13728e56 100644 --- a/src/openlcb/SimpleStack.cxx +++ b/src/openlcb/SimpleStack.cxx @@ -53,6 +53,7 @@ #include "openlcb/SimpleStack.hxx" +#include "openmrn_features.h" #include "openlcb/EventHandler.hxx" #include "openlcb/NodeInitializeFlow.hxx" #include "openlcb/SimpleNodeInfo.hxx" @@ -103,12 +104,12 @@ SimpleTcpStack::SimpleTcpStack(const openlcb::NodeID node_id) void SimpleStackBase::start_stack(bool delay_start) { -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD // Opens the eeprom file and sends configuration update commands to all // listeners. configUpdateFlow_.open_file(CONFIG_FILENAME); configUpdateFlow_.init_flow(); -#endif // NOT ARDUINO, YES ESP32 +#endif // have posix fd if (!delay_start) { @@ -138,7 +139,7 @@ void SimpleStackBase::default_start_node() node(), MemoryConfigDefs::SPACE_ACDI_SYS, space); additionalComponents_.emplace_back(space); } -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD { auto *space = new FileMemorySpace( SNIP_DYNAMIC_FILENAME, sizeof(SimpleNodeDynamicValues)); @@ -156,7 +157,7 @@ void SimpleStackBase::default_start_node() node(), MemoryConfigDefs::SPACE_CDI, space); additionalComponents_.emplace_back(space); } -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD if (CONFIG_FILENAME != nullptr) { auto *space = new FileMemorySpace(CONFIG_FILENAME, CONFIG_FILE_SIZE); diff --git a/src/utils/Queue.hxx b/src/utils/Queue.hxx index 3a81d5f8e..bf7819c5d 100644 --- a/src/utils/Queue.hxx +++ b/src/utils/Queue.hxx @@ -38,6 +38,7 @@ #include #include +#include "openmrn_features.h" #include "executor/Executable.hxx" #include "executor/Notifiable.hxx" #include "os/OS.hxx" @@ -868,7 +869,7 @@ public: return result; } -#if !(defined(ESP_NONOS) || defined(ARDUINO)) +#if OPENMRN_FEATURE_SEM_TIMEDWAIT /** Wait for an item from the front of the queue. * @param timeout time to wait in nanoseconds * @return item retrieved from queue, else NULL with errno set: From a2dee1e87fc621af417c7b176a8e027f11b3d2ee Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 19 Jul 2020 17:14:43 +0200 Subject: [PATCH 006/171] Adds a single-file implementation of the newlib file APIs for Arduino. (#408) The implementation forwards the calls to Arduino-STM32's eeprom library. This is the bare minimum to support OpenMRN's use of config file as the storage mechanism. === * Adds feature macro OPENMRN_HAVE_POSIX_FD. Replaces uses of ifdef ARDUINO with feature macros. * Adds a single-file implementation of the newlib file APIs. The implementation forwards the calls to Arduino-STM32's eeprom library. This is the bare minimum to support OpenMRN's use of config file as the storage mechanism. * Adds comment. * Changes the constant type to off_t. --- src/freertos_drivers/arduino/ArduinoFs.hxx | 304 +++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 src/freertos_drivers/arduino/ArduinoFs.hxx diff --git a/src/freertos_drivers/arduino/ArduinoFs.hxx b/src/freertos_drivers/arduino/ArduinoFs.hxx new file mode 100644 index 000000000..2b223c8ab --- /dev/null +++ b/src/freertos_drivers/arduino/ArduinoFs.hxx @@ -0,0 +1,304 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file ArduinoFs.hxx + * + * Single-file POSIX compatible filesystem using Arduino's EEPROM + * implementation for STM32. + * + * This file needs to be included into the .ino of the arduino sketch. It may + * only be compiled once as it has definitions of C library functions. + * + * @author Balazs Racz + * @date 18 July 2020 + */ + +#ifndef _FREERTOS_DRIVERS_ARDUINO_ARDUINOFS_HXX_ +#define _FREERTOS_DRIVERS_ARDUINO_ARDUINOFS_HXX_ + +#ifndef ARDUINO_ARCH_STM32 +#error This module only works for STM32 boards. +#endif + +#ifndef OPENMRN_HAVE_POSIX_FD +#error You must add a file build_opt.h to the sketch directory and add -DOPENMRN_HAVE_POSIX_FD to it to make use of this module. +#endif + +#include "stm32_eeprom.h" +#include + +#define EEPROM_FILENAME "/dev/eeprom" + +/// Class that holds information and code for the single-file filesystem for +/// emulating eeprom. +class FsStatic +{ +public: + /// Number of possible open file desriptors. + static constexpr unsigned MAX_FD = 8; + /// Offset markng that a file descriptor is not in use. + static constexpr off_t UNUSED_FILE = (off_t)-1; + + /// We have one of these for each open file descriptor. + struct FileInfo + { + /// POSIX file offset. + off_t offset = UNUSED_FILE; + + /// @return true if this file descriptor is in use. + bool in_use() + { + return offset != UNUSED_FILE; + } + + /// Marks the file descriptor to be in use. + void open() + { + offset = 0; + } + + /// Marks the file descriptor to be not in use. + void close() + { + offset = UNUSED_FILE; + } + }; + + /// Stores all file descriptors. + static FileInfo fds[MAX_FD]; + + /// Lookup a file descriptor. + /// @param fd the file descriptor. + /// @return nullptr if fd is an invalid file descriptor (in which case also + /// sets errno), otherwise the file descriptor structure. + static FileInfo *get_file(int fd) + { + if (fd >= MAX_FD || !fds[fd].in_use()) + { + errno = EBADF; + return nullptr; + } + return &fds[fd]; + } + + /// Allocates a new file descriptor. + /// @return new fd. If there is no free file descriptor, returns -1 and + /// sets errno. + static int new_fd() + { + for (int fd = 0; fd < MAX_FD; ++fd) + { + if (!fds[fd].in_use()) + { + fds[fd].open(); + return fd; + } + } + errno = ENFILE; + return -1; + } + + /// If there is unflushed writes, performs the flash write. + static void flush_if_dirty() + { + if (dirty_) + { + eeprom_buffer_flush(); + dirty_ = 0; + } + } + + /// 1 if we have filled the eeprom buffer at least once since startup. + static uint8_t loaded_; + /// 1 if we have unflushed written data in the eeprom buffer. + static uint8_t dirty_; +}; + +FsStatic::FileInfo FsStatic::fds[FsStatic::MAX_FD]; +uint8_t FsStatic::loaded_ = 0; +uint8_t FsStatic::dirty_ = 0; + +extern "C" +{ + +int _open_r(struct _reent *reent, const char *path, int flags, int mode) +{ + if (strcmp(path, EEPROM_FILENAME) != 0) + { + errno = ENOENT; + return -1; + } + if (!FsStatic::loaded_) + { + eeprom_buffer_fill(); + FsStatic::loaded_ = 1; + } + return FsStatic::new_fd(); +} + +int _close_r(struct _reent *reent, int fd) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + finfo->close(); + FsStatic::flush_if_dirty(); + return 0; +} + +ssize_t _read_r(struct _reent *reent, int fd, void *buf, size_t count) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + ssize_t ret = 0; + uint8_t *dst = (uint8_t *)buf; + int left = (int)E2END - (int)finfo->offset; + if (left < 0) + { + left = 0; + } + if (left < count) + { + count = left; + } + while (count > 0) + { + *dst = eeprom_buffered_read_byte(finfo->offset); + ++dst; + ++finfo->offset; + --count; + ++ret; + } + return ret; +} + +ssize_t _write_r(struct _reent *reent, int fd, const void *buf, size_t count) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + ssize_t ret = 0; + const uint8_t *src = (const uint8_t *)buf; + int left = (int)E2END - (int)finfo->offset; + if (left < 0) + { + left = 0; + } + if (left < count) + { + count = left; + } + if (count) + { + FsStatic::dirty_ = 1; + } + while (count > 0) + { + eeprom_buffered_write_byte(finfo->offset, *src); + ++src; + ++finfo->offset; + --count; + ++ret; + } + return ret; +} + +int fsync(int fd) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + FsStatic::flush_if_dirty(); + return 0; +} + +int _stat_r(struct _reent *reent, const char *path, struct stat *stat) +{ + if (strcmp(path, EEPROM_FILENAME) != 0) + { + errno = ENOENT; + return -1; + } + memset(stat, 0, sizeof(*stat)); + stat->st_size = E2END; + return 0; +} + +int _fstat_r(struct _reent *reent, int fd, struct stat *stat) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + memset(stat, 0, sizeof(*stat)); + stat->st_size = E2END; + return 0; +} + +_off_t _lseek_r(struct _reent *reent, int fd, _off_t offset, int whence) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + off_t new_offset = finfo->offset; + switch (whence) + { + case SEEK_SET: + new_offset = offset; + break; + case SEEK_CUR: + new_offset += offset; + break; + case SEEK_END: + new_offset = E2END + offset; + break; + default: + new_offset = E2END + 1; + } + if (new_offset > E2END) + { + errno = EINVAL; + return -1; + } + finfo->offset = new_offset; + return new_offset; +} + +} // extern "C" + +#endif // _FREERTOS_DRIVERS_ARDUINO_ARDUINOFS_HXX_ From 57f2812282916b3f30b4243c5cd8844df2869624 Mon Sep 17 00:00:00 2001 From: Robert Heller Date: Fri, 7 Aug 2020 13:52:45 -0400 Subject: [PATCH 007/171] Add linux.aarch64 target. (#413) Add 64-bit ARM Linux as a OpenMRN target. Initially supporting my Banana Pi M64 (Allwinner A64 SBC with a Raspberry Pi B+ form factor, including the Raspberry Pi 40-pin GPIO header. I'm guessing this will also support a 'Pi 4 or really any 64-bit ARM Linux SBC running a 64-bit (aarch64) Linux O/S. This is *lightly* tested -- I built and tested the hub application. Not sure how to run more detailed tests. Authored-by: Robert Heller --- .../hub/targets/linux.aarch64/Makefile | 3 ++ etc/linux.aarch64.mk | 45 +++++++++++++++++++ etc/path.mk | 11 +++++ targets/linux.aarch64/Makefile | 1 + targets/linux.aarch64/console/Makefile | 3 ++ targets/linux.aarch64/cue/Makefile | 1 + targets/linux.aarch64/dcc/Makefile | 1 + targets/linux.aarch64/executor/Makefile | 1 + targets/linux.aarch64/lib/Makefile | 1 + targets/linux.aarch64/openlcb/Makefile | 1 + targets/linux.aarch64/os/Makefile | 3 ++ targets/linux.aarch64/utils/Makefile | 1 + targets/linux.aarch64/withrottle/Makefile | 1 + 13 files changed, 73 insertions(+) create mode 100644 applications/hub/targets/linux.aarch64/Makefile create mode 100644 etc/linux.aarch64.mk create mode 100644 targets/linux.aarch64/Makefile create mode 100644 targets/linux.aarch64/console/Makefile create mode 100644 targets/linux.aarch64/cue/Makefile create mode 100644 targets/linux.aarch64/dcc/Makefile create mode 100644 targets/linux.aarch64/executor/Makefile create mode 100644 targets/linux.aarch64/lib/Makefile create mode 100644 targets/linux.aarch64/openlcb/Makefile create mode 100644 targets/linux.aarch64/os/Makefile create mode 100644 targets/linux.aarch64/utils/Makefile create mode 100644 targets/linux.aarch64/withrottle/Makefile diff --git a/applications/hub/targets/linux.aarch64/Makefile b/applications/hub/targets/linux.aarch64/Makefile new file mode 100644 index 000000000..ed0c910ab --- /dev/null +++ b/applications/hub/targets/linux.aarch64/Makefile @@ -0,0 +1,3 @@ +-include ../../config.mk +include $(OPENMRNPATH)/etc/prog.mk + diff --git a/etc/linux.aarch64.mk b/etc/linux.aarch64.mk new file mode 100644 index 000000000..fb497c9d4 --- /dev/null +++ b/etc/linux.aarch64.mk @@ -0,0 +1,45 @@ +# Get the toolchain paths for openmrn +include $(OPENMRNPATH)/etc/path.mk + + +ifndef TOOLPATH +#TOOLPATHCOMMAND := $(shell \ +#sh -c "which aarch64-linux-gnu-gcc" \ +#) +TOOLPATH := $(AARCH64LINUXGCCPATH) +endif + +$(info armv7alinux toolpath '$(TOOLPATH)') + +# Get the $(CFLAGSENV), $(CXXFLAGSENV), $(LDFLAGSENV) +include $(OPENMRNPATH)/etc/env.mk + +CC = $(TOOLPATH)/aarch64-linux-gnu-gcc +CXX = $(TOOLPATH)/aarch64-linux-gnu-g++ +AR = $(TOOLPATH)/aarch64-linux-gnu-ar +LD = $(TOOLPATH)/aarch64-linux-gnu-g++ +OBJDUMP = $(TOOLPATH)/aarch64-linux-gnu-objdump + +AROPTS=D + +HOST_TARGET := 1 + +STARTGROUP := -Wl,--start-group +ENDGROUP := -Wl,--end-group + +ARCHOPTIMIZATION = -g3 -O0 -march=armv8-a + +CSHAREDFLAGS = -c $(ARCHOPTIMIZATION) -Wall -Werror -Wno-unknown-pragmas \ + -MD -MP -fno-stack-protector -D_GNU_SOURCE + +CFLAGS = $(CSHAREDFLAGS) -std=gnu99 + +CXXFLAGS = $(CSHAREDFLAGS) -std=c++0x -D__STDC_FORMAT_MACROS \ + -D__STDC_LIMIT_MACROS -D__USE_LIBSTDCPP__ + +LDFLAGS = $(ARCHOPTIMIZATION) -Wl,-Map="$(@:%=%.map)" +SYSLIB_SUBDIRS += +SYSLIBRARIES = -lrt -lpthread + +EXTENTION = + diff --git a/etc/path.mk b/etc/path.mk index 2d6a1b05d..5bb3a6f2f 100644 --- a/etc/path.mk +++ b/etc/path.mk @@ -301,6 +301,17 @@ ARMLINUXGCCPATH:=$(TRYPATH) endif endif #ARMLINUXGCCPATH +################### AARCH64-LINUX GCC PATH ##################### +ifndef AARCH64LINUXGCCPATH +SEARCHPATH := \ + /usr/bin \ + +TRYPATH:=$(call findfirst,aarch64-linux-gnu-gcc,$(SEARCHPATH)) +ifneq ($(TRYPATH),) +AARCH64LINUXGCCPATH:=$(TRYPATH) +endif +endif #AARCH64LINUXGCCPATH + ################### TI-CC3200-SDK ##################### ifndef TICC3200SDKPATH SEARCHPATH := \ diff --git a/targets/linux.aarch64/Makefile b/targets/linux.aarch64/Makefile new file mode 100644 index 000000000..95c3e21d6 --- /dev/null +++ b/targets/linux.aarch64/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/core_target.mk diff --git a/targets/linux.aarch64/console/Makefile b/targets/linux.aarch64/console/Makefile new file mode 100644 index 000000000..0c54f81dd --- /dev/null +++ b/targets/linux.aarch64/console/Makefile @@ -0,0 +1,3 @@ +OPENMRNPATH ?= $(realpath ../../..) +include $(OPENMRNPATH)/etc/lib.mk + diff --git a/targets/linux.aarch64/cue/Makefile b/targets/linux.aarch64/cue/Makefile new file mode 100644 index 000000000..5e5bf973e --- /dev/null +++ b/targets/linux.aarch64/cue/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/linux.aarch64/dcc/Makefile b/targets/linux.aarch64/dcc/Makefile new file mode 100644 index 000000000..5e5bf973e --- /dev/null +++ b/targets/linux.aarch64/dcc/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/linux.aarch64/executor/Makefile b/targets/linux.aarch64/executor/Makefile new file mode 100644 index 000000000..5e5bf973e --- /dev/null +++ b/targets/linux.aarch64/executor/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/linux.aarch64/lib/Makefile b/targets/linux.aarch64/lib/Makefile new file mode 100644 index 000000000..a8d2e7abf --- /dev/null +++ b/targets/linux.aarch64/lib/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/target_lib.mk diff --git a/targets/linux.aarch64/openlcb/Makefile b/targets/linux.aarch64/openlcb/Makefile new file mode 100644 index 000000000..5e5bf973e --- /dev/null +++ b/targets/linux.aarch64/openlcb/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/linux.aarch64/os/Makefile b/targets/linux.aarch64/os/Makefile new file mode 100644 index 000000000..0c54f81dd --- /dev/null +++ b/targets/linux.aarch64/os/Makefile @@ -0,0 +1,3 @@ +OPENMRNPATH ?= $(realpath ../../..) +include $(OPENMRNPATH)/etc/lib.mk + diff --git a/targets/linux.aarch64/utils/Makefile b/targets/linux.aarch64/utils/Makefile new file mode 100644 index 000000000..5e5bf973e --- /dev/null +++ b/targets/linux.aarch64/utils/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/linux.aarch64/withrottle/Makefile b/targets/linux.aarch64/withrottle/Makefile new file mode 100644 index 000000000..5e5bf973e --- /dev/null +++ b/targets/linux.aarch64/withrottle/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/lib.mk From 05399d732ae3654083af95b51fc019a9dbccbbfb Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 9 Aug 2020 16:45:38 +0200 Subject: [PATCH 008/171] Traction service unregister; consist updates. (#414) * Adds the ability to unregister a train node from the train service. (Does not do this automatically at this point due to bugs.) Makes the consisting operations virtual so train node implementations can override the default behavior. * Adds a function that determines whether a node is known to the traction service. * Fixes comments. --- src/openlcb/TractionTrain.cxx | 22 +++++++++++++++++++ src/openlcb/TractionTrain.hxx | 41 +++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/openlcb/TractionTrain.cxx b/src/openlcb/TractionTrain.cxx index 0e2773b59..f59ba03cf 100644 --- a/src/openlcb/TractionTrain.cxx +++ b/src/openlcb/TractionTrain.cxx @@ -62,6 +62,13 @@ TrainNodeForProxy::TrainNodeForProxy(TrainService *service, TrainImpl *train) service_->register_train(this); } +TrainNodeForProxy::~TrainNodeForProxy() +{ + /// @todo enable this line of code. It currently breaks unit tests due to + /// bugs + // service_->unregister_train(this); +} + TrainNodeWithId::TrainNodeWithId( TrainService *service, TrainImpl *train, NodeID node_id) : TrainNode(service, train) @@ -70,6 +77,13 @@ TrainNodeWithId::TrainNodeWithId( service_->register_train(this); } +TrainNodeWithId::~TrainNodeWithId() +{ + /// @todo enable this line of code. It currently breaks unit tests due to + /// bugs + // service_->unregister_train(this); +} + NodeID TrainNodeForProxy::node_id() { return TractionDefs::train_node_id_from_legacy( @@ -628,4 +642,12 @@ void TrainService::register_train(TrainNode *node) HASSERT(nodes_.find(node) != nodes_.end()); } +void TrainService::unregister_train(TrainNode *node) +{ + HASSERT(nodes_.find(node) != nodes_.end()); + iface_->delete_local_node(node); + AtomicHolder h(this); + nodes_.erase(node); +} + } // namespace openlcb diff --git a/src/openlcb/TractionTrain.hxx b/src/openlcb/TractionTrain.hxx index fdb16b619..25761de77 100644 --- a/src/openlcb/TractionTrain.hxx +++ b/src/openlcb/TractionTrain.hxx @@ -126,7 +126,7 @@ public: /** Adds a node ID to the consist targets. @return false if the node was * already in the target list, true if it was newly added. */ - bool add_consist(NodeID tgt, uint8_t flags) + virtual bool add_consist(NodeID tgt, uint8_t flags) { if (!tgt) { @@ -150,8 +150,8 @@ public: } /** Removes a node ID from the consist targets. @return true if the target - * was removesd, false if the target was not on the list. */ - bool remove_consist(NodeID tgt) + * was removed, false if the target was not on the list. */ + virtual bool remove_consist(NodeID tgt) { for (auto it = consistSlaves_.begin(); it != consistSlaves_.end(); ++it) { @@ -210,8 +210,17 @@ private: /// Train node class with a an OpenLCB Node ID from the DCC pool. Used for command stations. class TrainNodeForProxy : public TrainNode { public: + /// Constructor. + /// @param service the traction service object that will own this node. + /// @param train the implementation object that the traction messages + /// should be forwarded to. TrainNodeForProxy(TrainService *service, TrainImpl *train); + /// Destructor. + ~TrainNodeForProxy(); + + /// @return the OpenLCB node ID, generated from the legacy protocol types + /// that we get from TrainImpl. NodeID node_id() OVERRIDE; }; @@ -219,13 +228,24 @@ public: /// train nodes that are not dynamically generated by a command station. class TrainNodeWithId : public TrainNode { public: + /// Constructor. + /// @param service the traction service object that will own this node. + /// @param train the implementation object that the traction messages + /// should be forwarded to. + /// @param node_id the OpenLCB node ID for this train. TrainNodeWithId(TrainService *service, TrainImpl *train, NodeID node_id); - NodeID node_id() OVERRIDE { + /// Destructor. + ~TrainNodeWithId(); + + /// @return the openlcb node ID. + NodeID node_id() OVERRIDE + { return nodeId_; } private: + /// The OpenLCB node ID. NodeID nodeId_; }; @@ -249,6 +269,19 @@ public: initialization flow for the train. */ void register_train(TrainNode *node); + /// Removes a train node from the local interface. + /// @param node train to remove from registry. + void unregister_train(TrainNode *node); + + /// Checks if the a given node is a train node operated by this Traction + /// Service. + /// @param node a virtual node + /// @return true if this is a known train node. + bool is_known_train_node(Node *node) + { + return nodes_.find((TrainNode*)node) != nodes_.end(); + } + private: struct Impl; /** Implementation flows. */ From 844b4671de91616cc579bb3cff598762961961e3 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sun, 9 Aug 2020 13:24:33 -0500 Subject: [PATCH 009/171] Bakerstu macos cleanup (#409) * Remove 32-bit Mac OS X build. * Fix Mac OS X build. * Fixup toolpath discovery. * Add support for and use HOSTCLANGPPPATH. Co-authored-by: Stuart Baker --- applications/async_blink/targets/Makefile | 1 - .../async_blink/targets/mach.x86/.gitignore | 2 - .../async_blink/targets/mach.x86/Makefile | 1 - .../async_blink/targets/mach.x86/NodeId.cxx | 4 - .../async_blink/targets/mach.x86_64/Makefile | 1 + applications/clinic_app/targets/Makefile | 10 +- .../{mach.x86 => mach.x86_64}/.gitignore | 0 .../{mach.x86 => mach.x86_64}/Makefile | 0 .../{mach.x86 => mach.x86_64}/config.hxx | 0 .../{mach.x86 => mach.x86_64}/main.cxx | 0 applications/hub/targets/Makefile | 2 +- .../hub/targets/mach.x86/lib/Makefile | 1 - .../{mach.x86 => mach.x86_64}/.gitignore | 0 .../{mach.x86 => mach.x86_64}/Makefile | 0 .../targets/mach.x86_64}/lib/Makefile | 0 applications/simple_client/targets/Makefile | 2 +- .../targets/mach.x86_64/.gitignore | 1 + .../{mach.x86 => mach.x86_64}/Makefile | 0 .../{mach.x86 => mach.x86_64}/hardware.mk | 0 etc/mach.x86.mk | 30 ------ etc/mach.x86_64.mk | 20 ++-- etc/path.mk | 12 +++ src/os/OSSelectWakeup.cxx | 100 ++++++++++++++++++ src/os/OSSelectWakeup.hxx | 62 +---------- src/utils/SocketClient.hxx | 1 + targets/Makefile | 2 +- targets/mach.x86/Makefile | 1 - targets/mach.x86/console/Makefile | 1 - targets/mach.x86/cue/Makefile | 1 - targets/mach.x86/dcc/Makefile | 1 - targets/mach.x86/executor/Makefile | 1 - targets/mach.x86/lib/Makefile | 1 - targets/mach.x86/openlcb/Makefile | 1 - targets/mach.x86/os/Makefile | 3 - targets/mach.x86/utils/Makefile | 1 - targets/mach.x86/withrottle/Makefile | 1 - 36 files changed, 134 insertions(+), 130 deletions(-) delete mode 100644 applications/async_blink/targets/mach.x86/.gitignore delete mode 100644 applications/async_blink/targets/mach.x86/Makefile delete mode 100644 applications/async_blink/targets/mach.x86/NodeId.cxx rename applications/clinic_app/targets/{mach.x86 => mach.x86_64}/.gitignore (100%) rename applications/clinic_app/targets/{mach.x86 => mach.x86_64}/Makefile (100%) rename applications/clinic_app/targets/{mach.x86 => mach.x86_64}/config.hxx (100%) rename applications/clinic_app/targets/{mach.x86 => mach.x86_64}/main.cxx (100%) delete mode 100644 applications/hub/targets/mach.x86/lib/Makefile rename applications/hub/targets/{mach.x86 => mach.x86_64}/.gitignore (100%) rename applications/hub/targets/{mach.x86 => mach.x86_64}/Makefile (100%) rename applications/{async_blink/targets/mach.x86 => hub/targets/mach.x86_64}/lib/Makefile (100%) create mode 100644 applications/simple_client/targets/mach.x86_64/.gitignore rename applications/simple_client/targets/{mach.x86 => mach.x86_64}/Makefile (100%) rename applications/simple_client/targets/{mach.x86 => mach.x86_64}/hardware.mk (100%) delete mode 100644 etc/mach.x86.mk delete mode 100644 targets/mach.x86/Makefile delete mode 100644 targets/mach.x86/console/Makefile delete mode 100644 targets/mach.x86/cue/Makefile delete mode 100644 targets/mach.x86/dcc/Makefile delete mode 100644 targets/mach.x86/executor/Makefile delete mode 100644 targets/mach.x86/lib/Makefile delete mode 100644 targets/mach.x86/openlcb/Makefile delete mode 100644 targets/mach.x86/os/Makefile delete mode 100644 targets/mach.x86/utils/Makefile delete mode 100644 targets/mach.x86/withrottle/Makefile diff --git a/applications/async_blink/targets/Makefile b/applications/async_blink/targets/Makefile index 228c445ca..dd450c0a7 100644 --- a/applications/async_blink/targets/Makefile +++ b/applications/async_blink/targets/Makefile @@ -1,7 +1,6 @@ SUBDIRS = linux.x86 \ linux.armv7a \ linux.llvm \ - mach.x86 \ mach.x86_64 \ freertos.armv7m.ek-lm4f120xl \ freertos.armv7m.ek-tm4c123gxl \ diff --git a/applications/async_blink/targets/mach.x86/.gitignore b/applications/async_blink/targets/mach.x86/.gitignore deleted file mode 100644 index 43148b79f..000000000 --- a/applications/async_blink/targets/mach.x86/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -async_blink -*_test diff --git a/applications/async_blink/targets/mach.x86/Makefile b/applications/async_blink/targets/mach.x86/Makefile deleted file mode 100644 index 3bb6034b2..000000000 --- a/applications/async_blink/targets/mach.x86/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/prog.mk diff --git a/applications/async_blink/targets/mach.x86/NodeId.cxx b/applications/async_blink/targets/mach.x86/NodeId.cxx deleted file mode 100644 index 3cd6d1db7..000000000 --- a/applications/async_blink/targets/mach.x86/NodeId.cxx +++ /dev/null @@ -1,4 +0,0 @@ -#include "openlcb/If.hxx" - -extern const openlcb::NodeID NODE_ID; -const openlcb::NodeID NODE_ID = 0x050101011410ULL; diff --git a/applications/async_blink/targets/mach.x86_64/Makefile b/applications/async_blink/targets/mach.x86_64/Makefile index 3bb6034b2..c6386dac3 100644 --- a/applications/async_blink/targets/mach.x86_64/Makefile +++ b/applications/async_blink/targets/mach.x86_64/Makefile @@ -1 +1,2 @@ +-include ../../config.mk include $(OPENMRNPATH)/etc/prog.mk diff --git a/applications/clinic_app/targets/Makefile b/applications/clinic_app/targets/Makefile index f999fd393..8b3bed0a4 100644 --- a/applications/clinic_app/targets/Makefile +++ b/applications/clinic_app/targets/Makefile @@ -1,11 +1,11 @@ SUBDIRS = \ - freertos.armv7m.ek-tm4c123gxl \ - freertos.armv7m.ek-tm4c1294xl \ - freertos.armv6m.st-stm32f072b-discovery \ - freertos.armv7m.st-stm32f303-discovery \ + freertos.armv7m.ek-tm4c123gxl \ + freertos.armv7m.ek-tm4c1294xl \ + freertos.armv6m.st-stm32f072b-discovery \ + freertos.armv7m.st-stm32f303-discovery \ linux.armv7a \ linux.x86 \ - mach.x86 + mach.x86_64 # freertos.armv7m.lpc1768-mbed \ diff --git a/applications/clinic_app/targets/mach.x86/.gitignore b/applications/clinic_app/targets/mach.x86_64/.gitignore similarity index 100% rename from applications/clinic_app/targets/mach.x86/.gitignore rename to applications/clinic_app/targets/mach.x86_64/.gitignore diff --git a/applications/clinic_app/targets/mach.x86/Makefile b/applications/clinic_app/targets/mach.x86_64/Makefile similarity index 100% rename from applications/clinic_app/targets/mach.x86/Makefile rename to applications/clinic_app/targets/mach.x86_64/Makefile diff --git a/applications/clinic_app/targets/mach.x86/config.hxx b/applications/clinic_app/targets/mach.x86_64/config.hxx similarity index 100% rename from applications/clinic_app/targets/mach.x86/config.hxx rename to applications/clinic_app/targets/mach.x86_64/config.hxx diff --git a/applications/clinic_app/targets/mach.x86/main.cxx b/applications/clinic_app/targets/mach.x86_64/main.cxx similarity index 100% rename from applications/clinic_app/targets/mach.x86/main.cxx rename to applications/clinic_app/targets/mach.x86_64/main.cxx diff --git a/applications/hub/targets/Makefile b/applications/hub/targets/Makefile index 3d075d4b8..5ff3034dd 100644 --- a/applications/hub/targets/Makefile +++ b/applications/hub/targets/Makefile @@ -1,5 +1,5 @@ SUBDIRS = linux.x86 \ linux.armv7a \ - mach.x86 + mach.x86_64 include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/hub/targets/mach.x86/lib/Makefile b/applications/hub/targets/mach.x86/lib/Makefile deleted file mode 100644 index a414ed98e..000000000 --- a/applications/hub/targets/mach.x86/lib/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/app_target_lib.mk diff --git a/applications/hub/targets/mach.x86/.gitignore b/applications/hub/targets/mach.x86_64/.gitignore similarity index 100% rename from applications/hub/targets/mach.x86/.gitignore rename to applications/hub/targets/mach.x86_64/.gitignore diff --git a/applications/hub/targets/mach.x86/Makefile b/applications/hub/targets/mach.x86_64/Makefile similarity index 100% rename from applications/hub/targets/mach.x86/Makefile rename to applications/hub/targets/mach.x86_64/Makefile diff --git a/applications/async_blink/targets/mach.x86/lib/Makefile b/applications/hub/targets/mach.x86_64/lib/Makefile similarity index 100% rename from applications/async_blink/targets/mach.x86/lib/Makefile rename to applications/hub/targets/mach.x86_64/lib/Makefile diff --git a/applications/simple_client/targets/Makefile b/applications/simple_client/targets/Makefile index 722e7127c..090bf476c 100644 --- a/applications/simple_client/targets/Makefile +++ b/applications/simple_client/targets/Makefile @@ -1,3 +1,3 @@ -SUBDIRS = linux.x86 mach.x86 +SUBDIRS = linux.x86 mach.x86_64 -include config.mk include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/simple_client/targets/mach.x86_64/.gitignore b/applications/simple_client/targets/mach.x86_64/.gitignore new file mode 100644 index 000000000..7d1a13c95 --- /dev/null +++ b/applications/simple_client/targets/mach.x86_64/.gitignore @@ -0,0 +1 @@ +simple_client diff --git a/applications/simple_client/targets/mach.x86/Makefile b/applications/simple_client/targets/mach.x86_64/Makefile similarity index 100% rename from applications/simple_client/targets/mach.x86/Makefile rename to applications/simple_client/targets/mach.x86_64/Makefile diff --git a/applications/simple_client/targets/mach.x86/hardware.mk b/applications/simple_client/targets/mach.x86_64/hardware.mk similarity index 100% rename from applications/simple_client/targets/mach.x86/hardware.mk rename to applications/simple_client/targets/mach.x86_64/hardware.mk diff --git a/etc/mach.x86.mk b/etc/mach.x86.mk deleted file mode 100644 index b94225e19..000000000 --- a/etc/mach.x86.mk +++ /dev/null @@ -1,30 +0,0 @@ -ifndef TOOLPATH -TOOLPATH := $(shell \ -sh -c "if [ -d /usr/include/mach ]; then echo /usr/bin; \ - else echo; fi" \ -) -endif - -# Get the $(CFLAGSENV), $(CXXFLAGSENV), $(LDFLAGSENV) -include $(OPENMRNPATH)/etc/env.mk - -CC = gcc -CXX = g++ -AR = ar -LD = g++ - -STARTGROUP := -ENDGROUP := - -INCLUDES += -I$(OPENMRNPATH)/include/mach - -CFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=gnu99 -m32 -fno-stack-protector \ - -D_GNU_SOURCE -CXXFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c++0x -m32 -fno-stack-protector \ - -D_GNU_SOURCE -D__STDC_FORMAT_MACROS - -LDFLAGS = -g -m32 -SYSLIBRARIES = -lpthread - -EXTENTION = - diff --git a/etc/mach.x86_64.mk b/etc/mach.x86_64.mk index 5842b51db..72ef97bb1 100644 --- a/etc/mach.x86_64.mk +++ b/etc/mach.x86_64.mk @@ -1,8 +1,8 @@ -ifndef TOOLPATH -TOOLPATH := $(shell \ -sh -c "if [ -d /usr/include/mach ]; then echo /usr/bin; \ - else echo; fi" \ -) +# Get the toolchain path +include $(OPENMRNPATH)/etc/path.mk + +ifeq ($(shell uname -sm),Darwin x86_64) +TOOLPATH := $(HOSTCLANGPPPATH) endif $(info mach toolpath '$(TOOLPATH)') @@ -10,19 +10,19 @@ $(info mach toolpath '$(TOOLPATH)') # Get the $(CFLAGSENV), $(CXXFLAGSENV), $(LDFLAGSENV) include $(OPENMRNPATH)/etc/env.mk -CC = gcc -CXX = g++ +CC = clang +CXX = clang++ AR = ar -LD = g++ +LD = clang++ STARTGROUP := ENDGROUP := INCLUDES += -I$(OPENMRNPATH)/include/mach -CFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=gnu99 -fno-stack-protector \ +CFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c99 -fno-stack-protector \ -D_GNU_SOURCE -CXXFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c++0x -fno-stack-protector \ +CXXFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c++14 -fno-stack-protector \ -D_GNU_SOURCE LDFLAGS = -g diff --git a/etc/path.mk b/etc/path.mk index 5bb3a6f2f..261a95eff 100644 --- a/etc/path.mk +++ b/etc/path.mk @@ -582,6 +582,18 @@ CLANGPPPATH:=$(TRYPATH) endif endif #CLANGPPPATH +##################### HOSTCLANGPP ###################### +ifndef HOSTCLANGPPPATH +SEARCHPATH := \ + /usr/bin \ + + +TRYPATH:=$(call findfirst,clang++,$(SEARCHPATH)) +ifneq ($(TRYPATH),) +HOSTCLANGPPPATH:=$(TRYPATH) +endif +endif #HOSTCLANGPPPATH + ##################### NODEJS ###################### ifndef NODEJSPATH SEARCHPATH := \ diff --git a/src/os/OSSelectWakeup.cxx b/src/os/OSSelectWakeup.cxx index b040906e9..2fa97bf57 100644 --- a/src/os/OSSelectWakeup.cxx +++ b/src/os/OSSelectWakeup.cxx @@ -1,10 +1,110 @@ +/** \copyright +* Copyright (c) 2015, Balazs Racz +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +* \file OSSelectWakeup.cxx +* Helper class for portable wakeup of a thread blocked in a select call. +* +* @author Balazs Racz +* @date 10 Apr 2015 +*/ + #include "os/OSSelectWakeup.hxx" #include "utils/logging.h" +#if defined(__MACH__) +#define _DARWIN_C_SOURCE // pselect +#endif void empty_signal_handler(int) { } +int OSSelectWakeup::select(int nfds, fd_set *readfds, + fd_set *writefds, fd_set *exceptfds, + long long deadline_nsec) +{ + { + AtomicHolder l(this); + inSelect_ = true; + if (pendingWakeup_) + { + deadline_nsec = 0; + } + else + { +#if OPENMRN_FEATURE_DEVICE_SELECT + Device::select_clear(); +#endif + } + } +#if OPENMRN_FEATURE_DEVICE_SELECT + int ret = + Device::select(nfds, readfds, writefds, exceptfds, deadline_nsec); + if (!ret && pendingWakeup_) + { + ret = -1; + errno = EINTR; + } +#elif OPENMRN_HAVE_PSELECT + struct timespec timeout; + timeout.tv_sec = deadline_nsec / 1000000000; + timeout.tv_nsec = deadline_nsec % 1000000000; + int ret = + ::pselect(nfds, readfds, writefds, exceptfds, &timeout, &origMask_); +#elif OPENMRN_HAVE_SELECT +#ifdef ESP32 + fd_set newexcept; + if (!exceptfds) + { + FD_ZERO(&newexcept); + exceptfds = &newexcept; + } + FD_SET(vfsFd_, exceptfds); + if (vfsFd_ >= nfds) { + nfds = vfsFd_ + 1; + } +#endif //ESP32 + struct timeval timeout; + timeout.tv_sec = deadline_nsec / 1000000000; + timeout.tv_usec = (deadline_nsec / 1000) % 1000000; + int ret = + ::select(nfds, readfds, writefds, exceptfds, &timeout); +#elif !defined(OPENMRN_FEATURE_SINGLE_THREADED) + #error no select implementation in multi threaded OS. +#else + // Single threaded OS: nothing to wake up. + int ret = 0; +#endif + { + AtomicHolder l(this); + pendingWakeup_ = false; + inSelect_ = false; + } + return ret; +} + #ifdef ESP32 #include #include diff --git a/src/os/OSSelectWakeup.hxx b/src/os/OSSelectWakeup.hxx index 2471ba8fa..5d284256e 100644 --- a/src/os/OSSelectWakeup.hxx +++ b/src/os/OSSelectWakeup.hxx @@ -181,67 +181,7 @@ public: * asynchronously */ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, - long long deadline_nsec) - { - { - AtomicHolder l(this); - inSelect_ = true; - if (pendingWakeup_) - { - deadline_nsec = 0; - } - else - { -#if OPENMRN_FEATURE_DEVICE_SELECT - Device::select_clear(); -#endif - } - } -#if OPENMRN_FEATURE_DEVICE_SELECT - int ret = - Device::select(nfds, readfds, writefds, exceptfds, deadline_nsec); - if (!ret && pendingWakeup_) - { - ret = -1; - errno = EINTR; - } -#elif OPENMRN_HAVE_PSELECT - struct timespec timeout; - timeout.tv_sec = deadline_nsec / 1000000000; - timeout.tv_nsec = deadline_nsec % 1000000000; - int ret = - ::pselect(nfds, readfds, writefds, exceptfds, &timeout, &origMask_); -#elif OPENMRN_HAVE_SELECT -#ifdef ESP32 - fd_set newexcept; - if (!exceptfds) - { - FD_ZERO(&newexcept); - exceptfds = &newexcept; - } - FD_SET(vfsFd_, exceptfds); - if (vfsFd_ >= nfds) { - nfds = vfsFd_ + 1; - } -#endif //ESP32 - struct timeval timeout; - timeout.tv_sec = deadline_nsec / 1000000000; - timeout.tv_usec = (deadline_nsec / 1000) % 1000000; - int ret = - ::select(nfds, readfds, writefds, exceptfds, &timeout); -#elif !defined(OPENMRN_FEATURE_SINGLE_THREADED) - #error no select implementation in multi threaded OS. -#else - // Single threaded OS: nothing to wake up. - int ret = 0; -#endif - { - AtomicHolder l(this); - pendingWakeup_ = false; - inSelect_ = false; - } - return ret; - } + long long deadline_nsec); private: #ifdef ESP32 diff --git a/src/utils/SocketClient.hxx b/src/utils/SocketClient.hxx index f649e9ad0..4a0b33eed 100644 --- a/src/utils/SocketClient.hxx +++ b/src/utils/SocketClient.hxx @@ -47,6 +47,7 @@ #endif #include #include +#include #include "executor/StateFlow.hxx" #include "executor/Timer.hxx" diff --git a/targets/Makefile b/targets/Makefile index 41b678b59..82b88cf30 100644 --- a/targets/Makefile +++ b/targets/Makefile @@ -6,7 +6,7 @@ SUBDIRS = linux.x86 \ freertos.armv6m \ freertos.armv4t \ freertos.mips4k.pic32mx \ - mach.x86 mach.x86_64 \ + mach.x86_64 \ bare.armv7m \ js.emscripten \ mingw.x86 \ diff --git a/targets/mach.x86/Makefile b/targets/mach.x86/Makefile deleted file mode 100644 index 95c3e21d6..000000000 --- a/targets/mach.x86/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/core_target.mk diff --git a/targets/mach.x86/console/Makefile b/targets/mach.x86/console/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/console/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/cue/Makefile b/targets/mach.x86/cue/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/cue/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/dcc/Makefile b/targets/mach.x86/dcc/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/dcc/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/executor/Makefile b/targets/mach.x86/executor/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/executor/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/lib/Makefile b/targets/mach.x86/lib/Makefile deleted file mode 100644 index a8d2e7abf..000000000 --- a/targets/mach.x86/lib/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/target_lib.mk diff --git a/targets/mach.x86/openlcb/Makefile b/targets/mach.x86/openlcb/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/openlcb/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/os/Makefile b/targets/mach.x86/os/Makefile deleted file mode 100644 index 0c54f81dd..000000000 --- a/targets/mach.x86/os/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -OPENMRNPATH ?= $(realpath ../../..) -include $(OPENMRNPATH)/etc/lib.mk - diff --git a/targets/mach.x86/utils/Makefile b/targets/mach.x86/utils/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/utils/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/withrottle/Makefile b/targets/mach.x86/withrottle/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/withrottle/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk From 5c2b3a087efdf638b6b2804af21be45276052d8c Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sun, 9 Aug 2020 13:51:55 -0500 Subject: [PATCH 010/171] Make dead code into less dead code. (#417) * Make dead code into less dead code. * fix code style. * remove superfluous space. --- src/utils/FileUtils.cxx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/utils/FileUtils.cxx b/src/utils/FileUtils.cxx index c72b38011..979769910 100644 --- a/src/utils/FileUtils.cxx +++ b/src/utils/FileUtils.cxx @@ -106,10 +106,15 @@ void write_string_to_file(const string &filename, const string &data) while ((nr = fwrite(data.data() + offset, 1, data.size() - offset, f)) > 0) { offset += nr; - if (offset >= data.size()) break; + if (offset >= data.size()) + { + break; + } } - if (nr < 0) { - fprintf(stderr, "error writing: %s\n", strerror(errno)); + if (offset != data.size()) + { + fprintf(stderr, "error writing: %s, offset: %zu, size: %zu\n", + strerror(errno), offset, data.size()); } fclose(f); } From 95b30e235976c1687bb514f42e0b8eda1b366d61 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 9 Aug 2020 23:56:09 +0200 Subject: [PATCH 011/171] Adds a railcom debug flow that sends a pulse to a GPIO when it sees a valid packet. (#415) --- src/dcc/RailcomPortDebug.hxx | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/dcc/RailcomPortDebug.hxx b/src/dcc/RailcomPortDebug.hxx index dc2e61eb2..c41d224bd 100644 --- a/src/dcc/RailcomPortDebug.hxx +++ b/src/dcc/RailcomPortDebug.hxx @@ -129,6 +129,72 @@ private: dcc::RailcomHubFlow *parent_; }; +/// This flow listens to Railcom packets coming from the hub, and if they are +/// correctly decoded, pulses the given GPIO output. Correctly decoded is +/// defined as having every single byte be a correct 4/8 codepoint. +class RailcomToGpioFlow : public dcc::RailcomHubPortInterface +{ +public: + /// Constructor. + /// @param source is the railcom hub to listen to. + /// @param output + RailcomToGpioFlow(dcc::RailcomHubFlow *source, const Gpio *output) + : parent_(source) + , output_(output) + { + source->register_port(this); + } + + ~RailcomToGpioFlow() + { + parent_->unregister_port(this); + } + +private: + /// Incoming railcom data. + /// + /// @param d railcom buffer. + /// @param prio priority + void send(Buffer *d, unsigned prio) OVERRIDE + { + AutoReleaseBuffer rb(d); + dcc::Feedback &fb = *d->data(); + if (fb.channel >= 0xfe) + { + // Occupancy feedback, not railcom data. + return; + } + unsigned correct = 0; + unsigned total = 0; + for (unsigned i = 0; i < fb.ch1Size; i++) + { + ++total; + correct += (dcc::railcom_decode[fb.ch1Data[i]] != RailcomDefs::INV) + ? 1 + : 0; + } + for (unsigned i = 0; i < fb.ch2Size; i++) + { + ++total; + correct += (dcc::railcom_decode[fb.ch2Data[i]] != RailcomDefs::INV) + ? 1 + : 0; + } + if (total > 0 && correct == total) + { + // Produces a short pulse on the output + output_->write(true); + for (volatile int i = 0; i < 3000; i++) { } + output_->write(false); + } + } + + /// Flow to which we are registered. + dcc::RailcomHubFlow *parent_; + /// Output gpio to toggle. + const Gpio *output_; +}; // RailcomToGpioFlow + } // namespace dcc namespace openlcb From 6920d7e3604a53272b50ca027de14abee9f9a266 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 9 Aug 2020 23:56:37 +0200 Subject: [PATCH 012/171] Adds the ability to run tests under performance profiling. (#416) * Adds the ability to run tests under performance profiling. For this apt-get install libgoogle-perftools-dev needs to be installed. * removes unnecessary whitespace --- etc/cov.mk | 15 +++++++++++++-- etc/test.mk | 8 +++++--- src/utils/test_main.hxx | 8 ++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/etc/cov.mk b/etc/cov.mk index db1f5d37c..12082186f 100644 --- a/etc/cov.mk +++ b/etc/cov.mk @@ -18,10 +18,12 @@ HOST_TARGET := 1 STARTGROUP := -Wl,--start-group ENDGROUP := -Wl,--end-group +TESTOPTIMIZATION=-O0 + ifdef SKIP_COVERAGE -ARCHOPTIMIZATION = -g -O0 +ARCHOPTIMIZATION = -g $(TESTOPTIMIZATION) else -ARCHOPTIMIZATION = -g -O0 -fprofile-arcs -ftest-coverage +ARCHOPTIMIZATION = -g $(TESTOPTIMIZATION) -fprofile-arcs -ftest-coverage endif CSHAREDFLAGS = -c -frandom-seed=$(shell echo $(abspath $<) | md5sum | sed 's/\(.*\) .*/\1/') $(ARCHOPTIMIZATION) $(INCLUDES) -Wall -Werror -Wno-unknown-pragmas -MD -MP -fno-stack-protector -D_GNU_SOURCE -DGTEST @@ -36,6 +38,15 @@ LDFLAGS = $(ARCHOPTIMIZATION) -Wl,-Map="$(@:%=%.map)" SYSLIB_SUBDIRS += SYSLIBRARIES = -lrt -lpthread -lavahi-client -lavahi-common $(SYSLIBRARIESEXTRA) +ifdef RUN_GPERF +CXXFLAGS += -DWITHGPERFTOOLS +LDFLAGS += -DWITHGPERFTOOLS +SYSLIBRARIES += -lprofiler +TESTOPTIMIZATION = -O3 +SKIP_COVERAGE = 1 +endif + + ifndef SKIP_COVERAGE LDFLAGS += -pg SYSLIBRARIES += -lgcov diff --git a/etc/test.mk b/etc/test.mk index 7ca2fe721..e60514e6c 100644 --- a/etc/test.mk +++ b/etc/test.mk @@ -45,11 +45,13 @@ LIBS = $(STARTGROUP) \ $(ENDGROUP) \ $(LINKCORELIBS) +TESTOPTIMIZATION=-O0 + INCLUDES += -I$(GTESTPATH)/include -I$(GMOCKPATH)/include -I$(GMOCKPATH) \ -I$(OPENMRNPATH)/src -I$(OPENMRNPATH)/include -CFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage -O0 -CXXFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage -O0 -SYSLIBRARIES += -lgcov -fprofile-arcs -ftest-coverage -O0 +CFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage $(TESTOPTIMIZATION) +CXXFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage $(TESTOPTIMIZATION) +SYSLIBRARIES += -lgcov -fprofile-arcs -ftest-coverage $(TESTOPTIMIZATION) LDFLAGS += -L$(LIBDIR) .SUFFIXES: diff --git a/src/utils/test_main.hxx b/src/utils/test_main.hxx index 104e03ef2..2504ec543 100644 --- a/src/utils/test_main.hxx +++ b/src/utils/test_main.hxx @@ -44,6 +44,7 @@ #include #include #include +#include #include "gtest/gtest.h" #include "gmock/gmock.h" @@ -54,6 +55,13 @@ #include "os/os.h" #include "utils/StringPrintf.hxx" +#ifdef WITHGPERFTOOLS +#include + +std::function profiler_enable{&ProfilerEnable}; +std::function profiler_disable{&ProfilerDisable}; +#endif + int appl_main(int argc, char *argv[]) { testing::InitGoogleMock(&argc, argv); From 1225b67642dec553fbcd016beae9d482a8ffe097 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 27 Aug 2020 23:21:51 +0200 Subject: [PATCH 013/171] Adds a partial implementation of VirtualMemorySpace (#418) The VirtualMemorySpace is an implementation of a MemorySpace interface which does not have a linear storage space behind. Instead, each variable is registered with a callback to write and read. This allows various advanced features, such as remapping address space, creating holes, really huge repetitions, etc. This is the first cut of an implementation that is not feature complete, only one data type is supported, and has todos left. === * Adds VirtualMemorySpace a memory space where the values are not stored in a contiguous storage area but are read and written via callbacks. * Adds comments. * Adds more tests. * Adds another test. * Fix copyright date. * fix typos. * Adds unit tests for asynchronous execution on the memory space. Fixes a respective bug in the implementation. * fully parenthesizes complex expressions. --- src/openlcb/VirtualMemorySpace.cxxtest | 370 +++++++++++++++++++++++++ src/openlcb/VirtualMemorySpace.hxx | 329 ++++++++++++++++++++++ 2 files changed, 699 insertions(+) create mode 100644 src/openlcb/VirtualMemorySpace.cxxtest create mode 100644 src/openlcb/VirtualMemorySpace.hxx diff --git a/src/openlcb/VirtualMemorySpace.cxxtest b/src/openlcb/VirtualMemorySpace.cxxtest new file mode 100644 index 000000000..ba84e044c --- /dev/null +++ b/src/openlcb/VirtualMemorySpace.cxxtest @@ -0,0 +1,370 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file VirtualMemorySpace.cxxtest + * + * Unit tests for the virtual memory space. + * + * @author Balazs Racz + * @date 10 Aug 2020 + */ + +#include "openlcb/VirtualMemorySpace.hxx" + +#include "openlcb/ConfigRepresentation.hxx" +#include "openlcb/MemoryConfigClient.hxx" +#include "utils/async_datagram_test_helper.hxx" + +namespace openlcb +{ + +string arg1; +string arg2; + +CDI_GROUP(ExampleMemorySpace); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(first, StringConfigEntry<13>); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +CDI_GROUP_ENTRY(second, StringConfigEntry<20>); +CDI_GROUP_END(); + +ExampleMemorySpace cfg(44); + +const unsigned arg1_ofs = 44 + 5; +const unsigned arg2_ofs = 44 + 5 + 13 + 8; + +class VirtualMemorySpaceTest : public AsyncDatagramTest +{ +protected: + ~VirtualMemorySpaceTest() + { + wait(); + } + + MemoryConfigHandler memCfg_ {&datagram_support_, node_, 3}; + std::unique_ptr space_; + MemoryConfigClient client_ {node_, &memCfg_}; +}; + +class TestSpace : public VirtualMemorySpace +{ +public: + TestSpace() + { + arg1.clear(); + arg2.clear(); + register_string( + cfg.first(), string_reader(&arg1), string_writer(&arg1)); + register_string( + cfg.second(), string_reader(&arg2), string_writer(&arg2)); + } + + /// Creates a ReaderFunction that just returns a string from a given + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_reader(string *ptr) + { + return + [ptr](unsigned repeat, string *contents, BarrierNotifiable *done) { + *contents = *ptr; + done->notify(); + return true; + }; + } + + /// Creates a WriterFunction that just stores the data in a given string + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_writer(string *ptr) + { + return + [ptr](unsigned repeat, string contents, BarrierNotifiable *done) { + *ptr = std::move(contents); + done->notify(); + }; + } +}; + +class TestSpaceTest : public VirtualMemorySpaceTest +{ +protected: + TestSpaceTest() + { + space_.reset(new TestSpace); + memCfg_.registry()->insert(node_, SPACE, space_.get()); + } + + /// Memory space number where the test space is registered. + const uint8_t SPACE = 0x52; +}; + +TEST_F(TestSpaceTest, create) +{ +} + +/// Basic tests reading variables from the exact offset including partial +/// reads. +TEST_F(TestSpaceTest, read_payload) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("hello", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, 20); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("", b->data()->payload.c_str()); + EXPECT_EQ(20u, b->data()->payload.size()); + + // prefix read + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, 3); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("hel", b->data()->payload); +} + +/// Test reading a variable from an imprecise offset (too early). +TEST_F(TestSpaceTest, read_early) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 2, 10); + ASSERT_EQ(0, b->data()->resultCode); + string exp("\0\0hello\0\0\0", 10); + EXPECT_EQ(exp, b->data()->payload); + EXPECT_EQ(10u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 4, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp2("\0\0\0", 3); + EXPECT_EQ(exp2, b->data()->payload); + EXPECT_EQ(3u, b->data()->payload.size()); +} + +/// Test writing a variable to a offset that is not covered at all. +TEST_F(TestSpaceTest, read_hole) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 5, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp("\0\0\0", 3); + EXPECT_EQ(exp, b->data()->payload); + + /// @todo this return seems to be wrong, although this address is out of + /// the memory space limits. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 5, 2); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string(), b->data()->payload); + + /// @todo this return seems to be wrong, although this address is out of + /// the memory space limits. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 100, 4); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string(), b->data()->payload); +} + +/// Basic tests writing variables from the exact offset but not including +/// partial writes. +TEST_F(TestSpaceTest, write_payload) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, "xyzw"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("xyzw", arg1); + EXPECT_EQ(4u, arg1.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, "abcde"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("abcde", arg2); + EXPECT_EQ(5u, arg2.size()); +} + +/// Test writing a variable to a offset that is too early. +TEST_F(TestSpaceTest, write_early) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 2, "xyzw"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("zw", arg1); + EXPECT_EQ(2u, arg1.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 1, "qwert"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("wert", arg2); + EXPECT_EQ(4u, arg2.size()); +} + +/// Test writing a variable to a offset that is not covered at all. +TEST_F(TestSpaceTest, write_hole) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 5, "xyz"); + ASSERT_EQ(0, b->data()->resultCode); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 5, "qw"); + ASSERT_EQ(0, b->data()->resultCode); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 100, "qw"); + ASSERT_EQ(0, b->data()->resultCode); +} + +class TestSpaceAsync : public VirtualMemorySpace +{ +public: + TestSpaceAsync() + { + arg1.clear(); + arg2.clear(); + register_string( + cfg.first(), string_reader(&arg1), string_writer(&arg1)); + register_string( + cfg.second(), string_reader(&arg2), string_writer(&arg2)); + } + + /// Creates a ReaderFunction that just returns a string from a given + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_reader(string *ptr) + { + return [this, ptr]( + unsigned repeat, string *contents, BarrierNotifiable *done) { + attempt++; + if ((attempt & 1) == 0) + { + *contents = *ptr; + done->notify(); + return true; + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + return false; + } + }; + } + + /// Creates a WriterFunction that just stores the data in a given string + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_writer(string *ptr) + { + return [this, ptr]( + unsigned repeat, string contents, BarrierNotifiable *done) { + attempt++; + if ((attempt & 1) == 0) + { + *ptr = std::move(contents); + done->notify(); + return true; + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + return false; + } + }; + } + +private: + size_t attempt = 0; +}; + +class TestSpaceAsyncTest : public VirtualMemorySpaceTest +{ +protected: + TestSpaceAsyncTest() + { + space_.reset(new TestSpaceAsync); + memCfg_.registry()->insert(node_, SPACE, space_.get()); + } + + /// Memory space number where the test space is registered. + const uint8_t SPACE = 0x52; +}; + +/// Basic tests reading variables from async space. +TEST_F(TestSpaceAsyncTest, read_payload_async) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("hello", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, 20); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("", b->data()->payload.c_str()); + EXPECT_EQ(20u, b->data()->payload.size()); +} + +/// Basic tests writing variables from the exact offset but not including +/// partial writes. +TEST_F(TestSpaceAsyncTest, write_payload_async) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, "xyzw"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("xyzw", arg1); + EXPECT_EQ(4u, arg1.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, "abcde"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("abcde", arg2); + EXPECT_EQ(5u, arg2.size()); +} + +} // namespace openlcb diff --git a/src/openlcb/VirtualMemorySpace.hxx b/src/openlcb/VirtualMemorySpace.hxx new file mode 100644 index 000000000..fc6e48f30 --- /dev/null +++ b/src/openlcb/VirtualMemorySpace.hxx @@ -0,0 +1,329 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file VirtualMemorySpace.hxx + * + * Implementation of a memory space where the values are not stored in a + * contiguous storage area but are read and written via callbacks. + * + * @author Balazs Racz + * @date 10 Aug 2020 + */ + +#ifndef _OPENLCB_VIRTUALMEMORYSPACE_HXX +#define _OPENLCB_VIRTUALMEMORYSPACE_HXX + +#include "openlcb/ConfigEntry.hxx" +#include "openlcb/MemoryConfig.hxx" +#include "utils/SortedListMap.hxx" + +namespace openlcb +{ + +/// Implementation of a memory space where the values are not stored in a +/// contiguous storage area but are read and written via callbacks. +class VirtualMemorySpace : public MemorySpace +{ +public: + /// @returns whether the memory space does not accept writes. + bool read_only() override + { + return isReadOnly_; + } + /// @returns the lowest address that's valid for this block. + address_t min_address() override + { + return minAddress_; + } + /// @returns the largest valid address for this block. A read of 1 from + /// this address should succeed in returning the last byte. + address_t max_address() override + { + return maxAddress_; + } + + /// @return the number of bytes successfully written (before hitting end + /// of space). + /// @param destination address to write to + /// @param data to write + /// @param len how many bytes to write + /// @param error if set to non-null, then the operation has failed. If the + /// operation needs to be continued, then sets error to + /// MemorySpace::ERROR_AGAIN, and calls the Notifiable + /// @param again when a re-try makes sense. The caller should call write + /// once more, with the offset adjusted with the previously returned + /// bytes. + size_t write(address_t destination, const uint8_t *data, size_t len, + errorcode_t *error, Notifiable *again) override + { + if ((destination > maxAddress_) || ((destination + len) <= minAddress_)) + { + *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS; + return 0; + } + *error = 0; + unsigned repeat; + const DataElement *element = nullptr; + ssize_t skip = find_data_element(destination, len, &element, &repeat); + string payload; + if (skip > 0) + { + // Will cause a new call be delivered with adjusted data and len. + return skip; + } + else if (skip < 0) + { + // We have some missing bytes that we need to read out first, then + // can perform the write. + DIE("unimplemented"); + // if (!element->readImpl_(repeat, &payload, + } + HASSERT(element); + payload.assign( + (const char *)data, std::min((size_t)len, (size_t)element->size_)); + size_t written_len = payload.size(); + bn_.reset(again); + element->writeImpl_(repeat, std::move(payload), bn_.new_child()); + if (bn_.abort_if_almost_done()) + { + return written_len; + } + else + { + // did not succeed synchronously. + bn_.notify(); // our slice + *error = MemorySpace::ERROR_AGAIN; + return 0; + } + } + + /** @returns the number of bytes successfully read (before hitting end of + * space). If *error is set to non-null, then the operation has failed. If + * the operation needs to be continued, then sets error to ERROR_AGAIN, and + * calls the Notifiable @param again when a re-try makes sense. The caller + * should call read once more, with the offset adjusted with the previously + * returned bytes. */ + size_t read(address_t source, uint8_t *dst, size_t len, errorcode_t *error, + Notifiable *again) override + { + if ((source > maxAddress_) || ((source + len) <= minAddress_)) + { + *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS; + return 0; + } + *error = 0; + unsigned repeat; + const DataElement *element = nullptr; + ssize_t skip = find_data_element(source, len, &element, &repeat); + if (skip > 0) + { + memset(dst, 0, skip); + return skip; + } + else if (skip < 0) + { + DIE("unimplemented"); + } + HASSERT(element); + string payload; + bn_.reset(again); + element->readImpl_(repeat, &payload, bn_.new_child()); + if (!bn_.abort_if_almost_done()) + { + // did not succeed synchronously. + bn_.notify(); // our slice + *error = MemorySpace::ERROR_AGAIN; + return 0; + } + payload.resize(element->size_); // pads with zeroes + size_t data_len = std::min(payload.size(), len); + memcpy(dst, payload.data(), data_len); + return data_len; + } + +protected: + /// Function that will be called for writes. + /// @param repeat 0 to number of repeats if this is in a repeated + /// group. Always 0 if not repeated group. + /// @param contents data payload that needs to be written. The data + /// bytes of this container start at the address_. + /// @param done must be notified when the write is complete (possibly + /// inline). + using WriteFunction = std::function; + /// Function that will be called for reads. + /// @param repeat 0 to number of repeats if this is in a repeated + /// group. Always 0 if not repeated group. + /// @param contents the payload to be returned from this variable shall + /// be written here. Will be zero-padded to size_ bytes if shorter. + /// @param done must be notified when the read values are ready. The + /// call will be re-tried in this case. + /// @return true if the read was successful, false if the read needs to be + /// re-tried later, + using ReadFunction = std::function; + + /// Setup the address bounds from a single CDI group declaration. + /// @param group is an instance of a group, for example a segment. + template void set_bounds_from_group(const G &group) + { + minAddress_ = group.offset(); + maxAddress_ = group.offset() + group.size() - 1; + } + + /// Expand the address bounds from a single CDI group declaration. + /// @param group is an instance of a group, for example a segment. + template void expand_bounds_from_group(const G &group) + { + minAddress_ = std::min(minAddress_, (address_t)group.offset()); + maxAddress_ = std::max( + maxAddress_, (address_t)(group.offset() + group.size() - 1)); + } + + /// Register an untyped element. + /// @param address the address in the memory space + /// @param size how many bytes this elements occupes + /// @param read_f will be called to read this data + /// @param write_f will be called to write this data + void register_element(address_t address, address_t size, + ReadFunction read_f, WriteFunction write_f) + { + elements_.insert(DataElement(address, size, read_f, write_f)); + } + + /// Registers a string typed element. + /// @param entry is the CDI ConfigRepresentation. + /// @param read_f will be called to read this data + /// @param write_f will be called to write this data + template + void register_string(const StringConfigEntry &entry, + ReadFunction read_f, WriteFunction write_f) + { + expand_bounds_from_group(entry); + register_element(entry.offset(), SIZE, read_f, write_f); + } + + /// Bounds for valid addresses. + address_t minAddress_ = 0xFFFFFFFFu; + /// Bounds for valid addresses. A read of length 1 from this address + /// should succeed in returning the last byte. + address_t maxAddress_ = 0; + /// Whether the space should report as RO. + bool isReadOnly_ = false; + +private: + /// We keep one of these for each variable that was declared. + struct DataElement + { + DataElement(address_t address, address_t size, ReadFunction read_f, + WriteFunction write_f) + : address_(address) + , size_(size) + , writeImpl_(write_f) + , readImpl_(read_f) + { + } + /// Base offset of this variable (first repeat only). + address_t address_; + /// Size of this variable. This is how many bytes of address space this + /// variable occupies. + address_t size_; + /// Function that will be called for writes. + WriteFunction writeImpl_; + /// Function that will be called for reads. + ReadFunction readImpl_; + }; + + struct Comparator + { + /// Sorting operator by address. + bool operator()(const DataElement &a, const DataElement &b) const + { + return a.address_ < b.address_; + } + /// Sorting operator by address. + bool operator()(unsigned a, const DataElement &b) const + { + return a < b.address_; + } + }; + + /// Look up the first matching data element given an address in the virtual + /// memory space. + /// @param address byte offset to look up. + /// @param len how many bytes long range to search from address. + /// @param ptr will be filled with a pointer to the data element when + /// found, or filled with nullptr if no data element overlaps with the + /// given range. + /// @param repeat output argument, filled with zero or the repetition + /// number. + /// @return 0 if an exact match is found. -N if a data element is found, + /// but N first bytes of this element are not covered. A number N in + /// [1..len-1] if a data element is found, but this many bytes need to be + /// skipped from address to arrive at the given data element's offset. len + /// if there was no data element found (in which case also set ptr to + /// null). + ssize_t find_data_element(address_t address, address_t len, + const DataElement **ptr, unsigned *repeat) + { + *repeat = 0; + *ptr = nullptr; + auto it = elements_.upper_bound(address); + if (it != elements_.begin()) + { + auto pit = it - 1; + // now: pit->address_ <= address + if (pit->address_ + pit->size_ > address) + { + // found overlap + *ptr = &*pit; + return (ssize_t)pit->address_ - + (ssize_t)address; // may be negative! + } + // else: no overlap, look at the next item + } + // now: it->address_ > address + if (address + len > it->address_) + { + // found overlap, but some data needs to be discarded. + *ptr = &*it; + return it->address_ - address; + } + /// @todo try repeated fields here first. + + // now: no overlap either before or after. + return len; + } + + /// Stores all the registered variables. + SortedListSet elements_; + /// Helper object in the function calls. + BarrierNotifiable bn_; +}; // class VirtualMemorySpace + +} // namespace openlcb + +#endif // _OPENLCB_VIRTUALMEMORYSPACE_HXX From 1e82c83f23c5a13ca57425071e7ff7b62b6d8b88 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 27 Aug 2020 23:24:39 +0200 Subject: [PATCH 014/171] Adds handling of repetitions to the virtual memory space. (#419) * Adds handling of repetitions to the virtual memory space. * Adds more comments. * removes info logs. * fully parenthesizes complex expressions. --- src/openlcb/VirtualMemorySpace.cxxtest | 204 +++++++++++++++++++++++++ src/openlcb/VirtualMemorySpace.hxx | 140 ++++++++++++++--- 2 files changed, 326 insertions(+), 18 deletions(-) diff --git a/src/openlcb/VirtualMemorySpace.cxxtest b/src/openlcb/VirtualMemorySpace.cxxtest index ba84e044c..6d92ef448 100644 --- a/src/openlcb/VirtualMemorySpace.cxxtest +++ b/src/openlcb/VirtualMemorySpace.cxxtest @@ -49,6 +49,7 @@ CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); CDI_GROUP_ENTRY(first, StringConfigEntry<13>); CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); CDI_GROUP_ENTRY(second, StringConfigEntry<20>); +CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>); CDI_GROUP_END(); ExampleMemorySpace cfg(44); @@ -367,4 +368,207 @@ TEST_F(TestSpaceAsyncTest, write_payload_async) EXPECT_EQ(5u, arg2.size()); } +CDI_GROUP(RepeatMemoryDef); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(before, StringConfigEntry<13>); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +using GroupRept = RepeatedGroup; +CDI_GROUP_ENTRY(grp, GroupRept); +CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>); +CDI_GROUP_ENTRY(after, StringConfigEntry<20>); +CDI_GROUP_END(); + +RepeatMemoryDef spacerept(22); + +class SpaceWithRepeat : public VirtualMemorySpace +{ +public: + SpaceWithRepeat() + { + arg1.clear(); + arg2.clear(); + register_string(spacerept.grp().entry<0>().first(), + string_reader(&arg1), string_writer(&arg1)); + register_string(spacerept.grp().entry<0>().second(), + string_reader(&arg2), string_writer(&arg2)); + register_string(spacerept.before(), string_reader(&before_), + string_writer(&before_)); + register_string( + spacerept.after(), string_reader(&after_), string_writer(&after_)); + register_repeat(spacerept.grp()); + } + + /// Creates a ReaderFunction that just returns a string from a given + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_reader(string *ptr) + { + return [this, ptr]( + unsigned repeat, string *contents, BarrierNotifiable *done) { + lastRepeat_ = repeat; + *contents = *ptr; + done->notify(); + return true; + }; + } + + /// Creates a WriterFunction that just stores the data in a given string + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_writer(string *ptr) + { + return [this, ptr]( + unsigned repeat, string contents, BarrierNotifiable *done) { + lastRepeat_ = repeat; + *ptr = std::move(contents); + done->notify(); + }; + } + + /// Saves the last repeat variable into this value. + unsigned lastRepeat_; + /// Storage variable for a field. + string before_; + /// Storage variable for a field. + string after_; +}; + +class ReptSpaceTest : public VirtualMemorySpaceTest +{ +protected: + ReptSpaceTest() + { + memCfg_.registry()->insert(node_, SPACE, &s_); + } + + /// Memory space number where the test space is registered. + const uint8_t SPACE = 0x52; + SpaceWithRepeat s_; +}; + +TEST_F(ReptSpaceTest, create) +{ +} + +// Looks for a field that is before the repeated group. +TEST_F(ReptSpaceTest, before) +{ + s_.before_ = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, spacerept.before().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("hello", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(0u, s_.lastRepeat_); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, spacerept.before().offset() - 2, + 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0hel", 5), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); +} + +// Looks for a field in the first repetition of the group. +TEST_F(ReptSpaceTest, first_repeat) +{ + arg1 = "world"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().first().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("world", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(0u, s_.lastRepeat_); + + // Start offset within the group. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().first().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0wor", 5), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); + + // Start offset _before_ the group. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().first().offset() - 7, 10); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0\0\0\0\0\0wor", 10), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); + + arg2 = "ahoi"; + // Second field, exact match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().second().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("ahoi", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(0u, s_.lastRepeat_); + + // Second field, before match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().second().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0aho", 5), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); +} + +// Looks for a field in the first repetition of the group. +TEST_F(ReptSpaceTest, mid_repeat) +{ + arg1 = "world"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().first().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("world", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(2u, s_.lastRepeat_); + + // Start offset within the group. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().first().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0wor", 5), b->data()->payload); + EXPECT_EQ(2u, s_.lastRepeat_); + + // Start offset in the previous group repeat. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().first().offset() - 7, 10); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0\0\0\0\0\0wor", 10), b->data()->payload); + EXPECT_EQ(2u, s_.lastRepeat_); + + arg2 = "ahoi"; + // Second field, exact match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().second().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("ahoi", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(2u, s_.lastRepeat_); + + // Second field, before match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().second().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0aho", 5), b->data()->payload); + EXPECT_EQ(2u, s_.lastRepeat_); +} + } // namespace openlcb diff --git a/src/openlcb/VirtualMemorySpace.hxx b/src/openlcb/VirtualMemorySpace.hxx index fc6e48f30..5393b3d6a 100644 --- a/src/openlcb/VirtualMemorySpace.hxx +++ b/src/openlcb/VirtualMemorySpace.hxx @@ -37,6 +37,7 @@ #define _OPENLCB_VIRTUALMEMORYSPACE_HXX #include "openlcb/ConfigEntry.hxx" +#include "openlcb/ConfigRepresentation.hxx" #include "openlcb/MemoryConfig.hxx" #include "utils/SortedListMap.hxx" @@ -226,6 +227,25 @@ protected: register_element(entry.offset(), SIZE, read_f, write_f); } + /// Registers a repeated group. Calling this function means that the + /// virtual memory space of the group will be looped onto the first + /// repetition. The correct usage is to register the elements of the first + /// repetition, then register the repetition itself using this call. Nested + /// repetitions are not supported (either the outer or the inner repetition + /// needs to be unrolled and registered for each repeat there). + /// @param group is the repeated group instance. Will take the start + /// offset, repeat count and repeat size from it. + template + void register_repeat(const RepeatedGroup &group) + { + RepeatElement re; + re.start_ = group.offset(); + re.end_ = group.end_offset(); + re.repeatSize_ = Group::size(); + HASSERT(re.repeatSize_ * N == re.end_ - re.start_); + repeats_.insert(std::move(re)); + } + /// Bounds for valid addresses. address_t minAddress_ = 0xFFFFFFFFu; /// Bounds for valid addresses. A read of length 1 from this address @@ -257,7 +277,8 @@ private: ReadFunction readImpl_; }; - struct Comparator + /// STL-compatible comparator function for sorting DataElements. + struct DataComparator { /// Sorting operator by address. bool operator()(const DataElement &a, const DataElement &b) const @@ -269,6 +290,38 @@ private: { return a < b.address_; } + /// Sorting operator by address. + bool operator()(const DataElement &a, unsigned b) const + { + return a.address_ < b; + } + }; + + /// Represents a repeated group. + struct RepeatElement + { + /// Offset of the repeated group (first repeat). + uint32_t start_; + /// Address bytes per repeat. + uint32_t repeatSize_; + /// Address byte after the last repeat. + uint32_t end_; + }; + + /// STL-compatible comparator function for sorting RepeatElements. Sorts + /// repeats by the end_ as the key. + struct RepeatComparator + { + /// Sorting operator by end address. + bool operator()(const RepeatElement &a, const RepeatElement &b) const + { + return a.end_ < b.end_; + } + /// Sorting operator by end address against a lookup key. + bool operator()(uint32_t a, const RepeatElement &b) const + { + return a < b.end_; + } }; /// Look up the first matching data element given an address in the virtual @@ -291,35 +344,86 @@ private: { *repeat = 0; *ptr = nullptr; - auto it = elements_.upper_bound(address); - if (it != elements_.begin()) + bool in_repeat = false; + address_t original_address = address; + ElementsType::iterator b = elements_.begin(); + ElementsType::iterator e = elements_.end(); + // Align in the known repetitions first. + auto rit = repeats_.upper_bound(address); + if (rit == repeats_.end()) + { + // not a repeat. + } + else { - auto pit = it - 1; - // now: pit->address_ <= address - if (pit->address_ + pit->size_ > address) + if (rit->start_ <= address && rit->end_ > address) { - // found overlap - *ptr = &*pit; - return (ssize_t)pit->address_ - - (ssize_t)address; // may be negative! + // we are in the repeat. + unsigned cnt = (address - rit->start_) / rit->repeatSize_; + *repeat = cnt; + // re-aligns address to the first repetition. + address -= cnt * rit->repeatSize_; + in_repeat = true; + b = elements_.lower_bound(rit->start_); + e = elements_.lower_bound(rit->start_ + rit->repeatSize_); } - // else: no overlap, look at the next item } - // now: it->address_ > address - if (address + len > it->address_) + + for (int is_repeat = 0; is_repeat <= 1; ++is_repeat) { - // found overlap, but some data needs to be discarded. - *ptr = &*it; - return it->address_ - address; + auto it = std::upper_bound(b, e, address, DataComparator()); + if (it != elements_.begin()) + { + auto pit = it - 1; + // now: pit->address_ <= address + if (pit->address_ + pit->size_ > address) + { + // found overlap + *ptr = &*pit; + return (ssize_t)pit->address_ - + (ssize_t)address; // may be negative! + } + // else: no overlap, look at the next item + } + // now: it->address_ > address + if (address + len > it->address_) + { + // found overlap, but some data needs to be discarded. + *ptr = &*it; + return it->address_ - address; + } + + if (in_repeat) + { + // We might be too close to the end of a repetition, we will + // try with the next repeat instead. + address -= rit->repeatSize_; + *repeat += 1; + if (original_address + rit->repeatSize_ >= rit->end_) + { + // We ran out of repeats. Look at the range beyond the + // group instead. + b = elements_.lower_bound(rit->end_); + e = elements_.end(); + *repeat = 0; + } + } + else + { + break; + } } - /// @todo try repeated fields here first. // now: no overlap either before or after. return len; } + /// Container type for storing the data elements. + typedef SortedListSet ElementsType; + /// Stores all the registered variables. + ElementsType elements_; /// Stores all the registered variables. - SortedListSet elements_; + SortedListSet repeats_; /// Helper object in the function calls. BarrierNotifiable bn_; }; // class VirtualMemorySpace From d14cf8b28cf8334810d620c7a59f3380d25f7bc1 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 1 Sep 2020 23:14:17 +0200 Subject: [PATCH 015/171] Adds support to reading and writing numeric variables to the VirtualMemorySpace. (#420) --- src/openlcb/VirtualMemorySpace.cxxtest | 201 ++++++++++++++++++++++++- src/openlcb/VirtualMemorySpace.hxx | 46 +++++- 2 files changed, 237 insertions(+), 10 deletions(-) diff --git a/src/openlcb/VirtualMemorySpace.cxxtest b/src/openlcb/VirtualMemorySpace.cxxtest index 6d92ef448..80b76f3a3 100644 --- a/src/openlcb/VirtualMemorySpace.cxxtest +++ b/src/openlcb/VirtualMemorySpace.cxxtest @@ -250,6 +250,28 @@ TEST_F(TestSpaceTest, write_hole) ASSERT_EQ(0, b->data()->resultCode); } +CDI_GROUP(NumericGroup); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(first, Uint32ConfigEntry); +CDI_GROUP_ENTRY(second, Int16ConfigEntry); +CDI_GROUP_ENTRY(third, Uint8ConfigEntry); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +CDI_GROUP_END(); + +CDI_GROUP(NumericMemorySpace); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(first, StringConfigEntry<13>); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +CDI_GROUP_ENTRY(second, StringConfigEntry<20>); +CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>); +CDI_GROUP_ENTRY(outer_before, Uint32ConfigEntry); +using RG = RepeatedGroup; +CDI_GROUP_ENTRY(grp, RG); +CDI_GROUP_ENTRY(outer_after, Uint32ConfigEntry); +CDI_GROUP_END(); + +NumericMemorySpace ncfg(44); + class TestSpaceAsync : public VirtualMemorySpace { public: @@ -258,9 +280,20 @@ public: arg1.clear(); arg2.clear(); register_string( - cfg.first(), string_reader(&arg1), string_writer(&arg1)); + ncfg.first(), string_reader(&arg1), string_writer(&arg1)); register_string( - cfg.second(), string_reader(&arg2), string_writer(&arg2)); + ncfg.second(), string_reader(&arg2), string_writer(&arg2)); + register_numeric(ncfg.outer_before(), typed_reader(&rnBefore_), + typed_writer(&rnBefore_)); + register_numeric(ncfg.outer_after(), typed_reader(&rnAfter_), + typed_writer(&rnAfter_)); + register_numeric(ncfg.grp().entry(0).first(), typed_reader(&rnFirst_), + typed_writer(&rnFirst_)); + register_numeric(ncfg.grp().entry(0).second(), typed_reader(&rnSecond_), + typed_writer(&rnSecond_)); + register_numeric(ncfg.grp().entry(0).third(), typed_reader(&rnThird_), + typed_writer(&rnThird_)); + register_repeat(ncfg.grp()); } /// Creates a ReaderFunction that just returns a string from a given @@ -275,6 +308,7 @@ public: return [this, ptr]( unsigned repeat, string *contents, BarrierNotifiable *done) { attempt++; + lastRepeat_ = repeat; if ((attempt & 1) == 0) { *contents = *ptr; @@ -290,11 +324,37 @@ public: }; } + /// Creates a TypedReaderFunction that just returns a value from a given + /// variable. + /// @param ptr the variable whose contents to return as read value. Must + /// stay alive as long as the function is in use. + /// @return the TypedReaderFunction. + template + typename std::function + typed_reader(T *ptr) + { + return [this, ptr](unsigned repeat, BarrierNotifiable *done) { + attempt++; + lastRepeat_ = repeat; + if ((attempt & 1) == 0) + { + done->notify(); + return *ptr; + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + return T(); + } + }; + } + /// Creates a WriterFunction that just stores the data in a given string /// variable. - /// @param ptr the string whose contents to return as read value. Must stay - /// alive as long as the function is in use. - /// @return the ReaderFunction. + /// @param ptr the variable where to store the contents. Must stay alive as + /// long as the function is in use. + /// @return the WriterFunction. std::function string_writer(string *ptr) @@ -302,6 +362,7 @@ public: return [this, ptr]( unsigned repeat, string contents, BarrierNotifiable *done) { attempt++; + lastRepeat_ = repeat; if ((attempt & 1) == 0) { *ptr = std::move(contents); @@ -317,6 +378,46 @@ public: }; } + /// Creates a TypedWriterFunction that just stores the data in a given + /// variable. + /// @param ptr the variable where to store the contents. Must stay alive as + /// long as the function is in use. + /// @return the TypedWriterFunction. + template + std::function + typed_writer(T *ptr) + { + return + [this, ptr](unsigned repeat, T contents, BarrierNotifiable *done) { + attempt++; + lastRepeat_ = repeat; + if ((attempt & 1) == 0) + { + *ptr = std::move(contents); + done->notify(); + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + } + }; + } + + /// Stores the last invoked repetition number. + unsigned lastRepeat_ = 0; + /// Shadow for NumericGroup.first. + uint32_t rnFirst_ = 0; + /// Shadow for NumericGroup.second. + int16_t rnSecond_ = 0; + /// Shadow for NumericGroup.third. + uint8_t rnThird_ = 0; + + /// Shadow for NUmericMemorySpace.before. + uint32_t rnBefore_ = 0; + /// Shadow for NUmericMemorySpace.after. + uint32_t rnAfter_ = 0; + private: size_t attempt = 0; }; @@ -326,10 +427,11 @@ class TestSpaceAsyncTest : public VirtualMemorySpaceTest protected: TestSpaceAsyncTest() { - space_.reset(new TestSpaceAsync); + space_.reset(tspace_); memCfg_.registry()->insert(node_, SPACE, space_.get()); } + TestSpaceAsync *tspace_ = new TestSpaceAsync; /// Memory space number where the test space is registered. const uint8_t SPACE = 0x52; }; @@ -368,6 +470,93 @@ TEST_F(TestSpaceAsyncTest, write_payload_async) EXPECT_EQ(5u, arg2.size()); } +/// Tests reading and writing numeric variables with endianness. +TEST_F(TestSpaceAsyncTest, rw_numeric_async) +{ + string u32payload; + u32payload.push_back(0xAA); + u32payload.push_back(2); + u32payload.push_back(3); + u32payload.push_back(4); + + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, ncfg.outer_before().offset(), + u32payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0xAA020304u, tspace_->rnBefore_); + + tspace_->rnBefore_ = 0xbb554433; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, ncfg.outer_before().offset(), 4); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ((char)0xbb, b->data()->payload[0]); + EXPECT_EQ(0x55, b->data()->payload[1]); + EXPECT_EQ(0x44, b->data()->payload[2]); + EXPECT_EQ(0x33, b->data()->payload[3]); +} + +/// Tests variable after repeted group. +TEST_F(TestSpaceAsyncTest, rw_numeric_after_repeat) +{ + string u32payload; + u32payload.push_back(0xAA); + u32payload.push_back(2); + u32payload.push_back(3); + u32payload.push_back(4); + + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, ncfg.outer_after().offset(), + u32payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0xAA020304u, tspace_->rnAfter_); + + tspace_->rnAfter_ = 0xbb554433; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, ncfg.outer_after().offset(), 4); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ((char)0xbb, b->data()->payload[0]); + EXPECT_EQ(0x55, b->data()->payload[1]); + EXPECT_EQ(0x44, b->data()->payload[2]); + EXPECT_EQ(0x33, b->data()->payload[3]); +} + +/// Tests reading and writing numeric variables with endianness from repetitions. +TEST_F(TestSpaceAsyncTest, rw_numeric_repeat) +{ + string u32payload; + u32payload.push_back(0xAA); + u32payload.push_back(2); + u32payload.push_back(3); + u32payload.push_back(4); + + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, + ncfg.grp().entry(3).first().offset(), u32payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(3u, tspace_->lastRepeat_); + EXPECT_EQ(0xAA020304u, tspace_->rnFirst_); + + tspace_->rnSecond_ = -2; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + ncfg.grp().entry(4).second().offset(), 2); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(4u, tspace_->lastRepeat_); + ASSERT_EQ(2u, b->data()->payload.size()); + EXPECT_EQ((char)0xFF, b->data()->payload[0]); + EXPECT_EQ((char)0xFE, b->data()->payload[1]); + + tspace_->rnSecond_ = 55; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + ncfg.grp().entry(0).second().offset(), 2); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, tspace_->lastRepeat_); + ASSERT_EQ(2u, b->data()->payload.size()); + EXPECT_EQ((char)0, b->data()->payload[0]); + EXPECT_EQ((char)55, b->data()->payload[1]); +} + CDI_GROUP(RepeatMemoryDef); CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); CDI_GROUP_ENTRY(before, StringConfigEntry<13>); diff --git a/src/openlcb/VirtualMemorySpace.hxx b/src/openlcb/VirtualMemorySpace.hxx index 5393b3d6a..c0ce099e2 100644 --- a/src/openlcb/VirtualMemorySpace.hxx +++ b/src/openlcb/VirtualMemorySpace.hxx @@ -181,12 +181,21 @@ protected: /// @param contents the payload to be returned from this variable shall /// be written here. Will be zero-padded to size_ bytes if shorter. /// @param done must be notified when the read values are ready. The - /// call will be re-tried in this case. - /// @return true if the read was successful, false if the read needs to be - /// re-tried later, - using ReadFunction = std::function; + /// Typed WriteFunction for primitive types. + template + using TypedWriteFunction = typename std::function; + + /// Typed ReadFunction for primitive types. @return the read value if the + /// read was successful. If the read did not complete, return 0. + template + using TypedReadFunction = typename std::function; + /// Setup the address bounds from a single CDI group declaration. /// @param group is an instance of a group, for example a segment. template void set_bounds_from_group(const G &group) @@ -227,6 +236,35 @@ protected: register_element(entry.offset(), SIZE, read_f, write_f); } + /// Registers a numeric typed element. + /// @param T is the type argument, e.g. uint8_t. + /// @param entry is the CDI ConfigRepresentation. + /// @param read_f will be called to read this data + /// @param write_f will be called to write this data + template + void register_numeric(const NumericConfigEntry &entry, + TypedReadFunction read_f, TypedWriteFunction write_f) + { + expand_bounds_from_group(entry); + auto trf = [read_f](unsigned repeat, string *contents, + BarrierNotifiable *done) { + T result = read_f(repeat, done); + contents->clear(); + contents->resize(sizeof(T)); + *((T *)&((*contents)[0])) = + NumericConfigEntry::endian_convert(result); + }; + auto twf = [write_f](unsigned repeat, string contents, + BarrierNotifiable *done) { + contents.resize(sizeof(T)); + T result = NumericConfigEntry::endian_convert( + *(const T *)contents.data()); + write_f(repeat, result, done); + }; + register_element( + entry.offset(), entry.size(), std::move(trf), std::move(twf)); + } + /// Registers a repeated group. Calling this function means that the /// virtual memory space of the group will be looped onto the first /// repetition. The correct usage is to register the elements of the first From 10ce4bd6624c10362d93ca860d68b85f98c15ee9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 2 Sep 2020 00:21:44 +0200 Subject: [PATCH 016/171] Adds SocketCan support to the openmrn hub. This enables running the hub on a BeagleBone or a Raspberry Pi with a CAN HAT. All other options are still usable, including repeating traffic between a SocketCAN port and a TCP client, or a SocketCAN port and a USB port via GridConnect. --- applications/hub/main.cxx | 28 +++++++++- src/openlcb/SimpleStack.cxx | 42 +++------------ src/utils/SocketCan.cxx | 101 ++++++++++++++++++++++++++++++++++++ src/utils/SocketCan.hxx | 49 +++++++++++++++++ src/utils/sources | 1 + 5 files changed, 185 insertions(+), 36 deletions(-) create mode 100644 src/utils/SocketCan.cxx create mode 100644 src/utils/SocketCan.hxx diff --git a/applications/hub/main.cxx b/applications/hub/main.cxx index 599710baf..7da8bee03 100644 --- a/applications/hub/main.cxx +++ b/applications/hub/main.cxx @@ -42,6 +42,8 @@ #include "os/os.h" #include "utils/constants.hxx" #include "utils/Hub.hxx" +#include "utils/HubDeviceSelect.hxx" +#include "utils/SocketCan.hxx" #include "utils/GcTcpHub.hxx" #include "utils/ClientConnection.hxx" #include "executor/Executor.hxx" @@ -64,6 +66,7 @@ bool timestamped = false; bool export_mdns = false; const char* mdns_name = "openmrn_hub"; bool printpackets = false; +const char* socketcan_port = nullptr; void usage(const char *e) { @@ -100,7 +103,7 @@ void usage(const char *e) void parse_args(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "hp:d:u:q:tlmn:")) >= 0) + while ((opt = getopt(argc, argv, "hp:d:u:q:tlmn:s:")) >= 0) { switch (opt) { @@ -132,6 +135,11 @@ void parse_args(int argc, char *argv[]) case 'l': printpackets = true; break; +#if defined(__linux__) + case 's': + socketcan_port = optarg; + break; +#endif default: fprintf(stderr, "Unknown option %c\n", opt); usage(argv[0]); @@ -160,11 +168,27 @@ int appl_main(int argc, char *argv[]) void mdns_client_start(); void mdns_publish(const char *name, uint16_t port); - if (export_mdns) { + if (export_mdns) + { mdns_client_start(); mdns_publish(mdns_name, port); } #endif +#if defined(__linux__) + if (socketcan_port) + { + int s = socketcan_open(socketcan_port, 1); + if (s >= 0) + { + new HubDeviceSelect(&can_hub0, s); + fprintf(stderr, "Opened SocketCan %s: fd %d\n", socketcan_port, s); + } + else + { + fprintf(stderr, "Failed to open SocketCan %s.\n", socketcan_port); + } + } +#endif if (upstream_host) { diff --git a/src/openlcb/SimpleStack.cxx b/src/openlcb/SimpleStack.cxx index f13728e56..7dbe9fed4 100644 --- a/src/openlcb/SimpleStack.cxx +++ b/src/openlcb/SimpleStack.cxx @@ -41,11 +41,6 @@ #include #include /* tc* functions */ #endif -#if defined(__linux__) -#include "utils/HubDeviceSelect.hxx" -#include -#include -#endif #include #include @@ -57,6 +52,8 @@ #include "openlcb/EventHandler.hxx" #include "openlcb/NodeInitializeFlow.hxx" #include "openlcb/SimpleNodeInfo.hxx" +#include "utils/HubDeviceSelect.hxx" +#include "utils/SocketCan.hxx" namespace openlcb { @@ -419,35 +416,12 @@ void SimpleCanStackBase::add_gridconnect_tty( void SimpleCanStackBase::add_socketcan_port_select( const char *device, int loopback) { - int s; - struct sockaddr_can addr; - struct ifreq ifr; - - s = socket(PF_CAN, SOCK_RAW, CAN_RAW); - - // Set the blocking limit to the minimum allowed, typically 1024 in Linux - int sndbuf = 0; - setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); - - // turn on/off loopback - setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback)); - - // setup error notifications - can_err_mask_t err_mask = CAN_ERR_TX_TIMEOUT | CAN_ERR_LOSTARB | - CAN_ERR_CRTL | CAN_ERR_PROT | CAN_ERR_TRX | CAN_ERR_ACK | - CAN_ERR_BUSOFF | CAN_ERR_BUSERROR | CAN_ERR_RESTARTED; - setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask)); - strcpy(ifr.ifr_name, device); - - ::ioctl(s, SIOCGIFINDEX, &ifr); - - addr.can_family = AF_CAN; - addr.can_ifindex = ifr.ifr_ifindex; - - bind(s, (struct sockaddr *)&addr, sizeof(addr)); - - auto *port = new HubDeviceSelect(can_hub(), s); - additionalComponents_.emplace_back(port); + int s = socketcan_open(device, loopback); + if (s >= 0) + { + auto *port = new HubDeviceSelect(can_hub(), s); + additionalComponents_.emplace_back(port); + } } #endif extern Pool *const __attribute__((__weak__)) g_incoming_datagram_allocator = diff --git a/src/utils/SocketCan.cxx b/src/utils/SocketCan.cxx new file mode 100644 index 000000000..a3df5d487 --- /dev/null +++ b/src/utils/SocketCan.cxx @@ -0,0 +1,101 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SocketCan.cxx + * + * Helper functions to connect to CAN devices via SocketCan. + * + * @author Balazs Racz + * @date 1 Sep 2020 + */ + +#include "utils/SocketCan.hxx" +#include "can_frame.h" +#include + +#if defined(__linux__) + +#include +#include +#include +#include +#include + +/// This macro executes an OS call, and if it returns negative result, then +/// prints the errno to stderr, and terminates the current function with -1 +/// return value. +/// @param where textual description of what function was called +/// (e.g. "socket") +/// @param x... the function call. +#define ERRNOLOG(where, x...) \ + do \ + { \ + if ((x) < 0) \ + { \ + perror(where); \ + return -1; \ + } \ + } while (0) + +int socketcan_open(const char *device, int loopback) +{ + int s; + struct sockaddr_can addr; + struct ifreq ifr; + + s = socket(PF_CAN, SOCK_RAW, CAN_RAW); + ERRNOLOG("socket", s); + + // Set the blocking limit to the minimum allowed, typically 1024 in Linux + int sndbuf = 0; + ERRNOLOG("setsockopt(sndbuf)", + setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))); + + // turn on/off loopback + ERRNOLOG("setsockopt(loopback)", + setsockopt( + s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback))); + + // setup error notifications + can_err_mask_t err_mask = CAN_ERR_TX_TIMEOUT | CAN_ERR_LOSTARB | + CAN_ERR_CRTL | CAN_ERR_PROT | CAN_ERR_TRX | CAN_ERR_ACK | + CAN_ERR_BUSOFF | CAN_ERR_BUSERROR | CAN_ERR_RESTARTED; + ERRNOLOG("setsockopt(filter)", + setsockopt( + s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask))); + strcpy(ifr.ifr_name, device); + + ERRNOLOG("interface set", ::ioctl(s, SIOCGIFINDEX, &ifr)); + + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + + ERRNOLOG("bind", bind(s, (struct sockaddr *)&addr, sizeof(addr))); + + return s; +} + +#endif diff --git a/src/utils/SocketCan.hxx b/src/utils/SocketCan.hxx new file mode 100644 index 000000000..23a724141 --- /dev/null +++ b/src/utils/SocketCan.hxx @@ -0,0 +1,49 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SocketCan.hxx + * + * Helper functions to connect to CAN devices via SocketCan. + * + * @author Balazs Racz + * @date 1 Sep 2020 + */ + +#ifndef _UTILS_SOCKETCAN_HXX_ +#define _UTILS_SOCKETCAN_HXX_ + +#if defined(__linux__) + +/// Opens a SocketCan socket. +/// @param device the name of the CAN device, e.g. can0 +/// @param loopback 1 to enable loopback locally to other open references, +/// 0 to disable loopback locally to other open references. +/// @return an open socket file descriptor, or -1 if there was an error. +int socketcan_open(const char* device, int loopback); + +#endif + +#endif // _UTILS_SOCKETCAN_HXX_ diff --git a/src/utils/sources b/src/utils/sources index 4dc7e26c7..7140a788a 100644 --- a/src/utils/sources +++ b/src/utils/sources @@ -22,6 +22,7 @@ CXXSRCS += \ Queue.cxx \ JSHubPort.cxx \ ReflashBootloader.cxx \ + SocketCan.cxx \ constants.cxx \ gc_format.cxx \ logging.cxx \ From 02aea797f19a8af7a7ea653d36c68659d2338b1b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 2 Sep 2020 00:22:16 +0200 Subject: [PATCH 017/171] fix whitespace. --- applications/hub/main.cxx | 10 +++++----- src/utils/SocketCan.cxx | 4 ++-- src/utils/SocketCan.hxx | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/applications/hub/main.cxx b/applications/hub/main.cxx index 7da8bee03..ac3fad7ac 100644 --- a/applications/hub/main.cxx +++ b/applications/hub/main.cxx @@ -66,7 +66,7 @@ bool timestamped = false; bool export_mdns = false; const char* mdns_name = "openmrn_hub"; bool printpackets = false; -const char* socketcan_port = nullptr; +const char *socketcan_port = nullptr; void usage(const char *e) { @@ -135,11 +135,11 @@ void parse_args(int argc, char *argv[]) case 'l': printpackets = true; break; -#if defined(__linux__) +#if defined(__linux__) case 's': socketcan_port = optarg; break; -#endif +#endif default: fprintf(stderr, "Unknown option %c\n", opt); usage(argv[0]); @@ -174,7 +174,7 @@ int appl_main(int argc, char *argv[]) mdns_publish(mdns_name, port); } #endif -#if defined(__linux__) +#if defined(__linux__) if (socketcan_port) { int s = socketcan_open(socketcan_port, 1); @@ -189,7 +189,7 @@ int appl_main(int argc, char *argv[]) } } #endif - + if (upstream_host) { connections.emplace_back(new UpstreamConnectionClient( diff --git a/src/utils/SocketCan.cxx b/src/utils/SocketCan.cxx index a3df5d487..791b46c8a 100644 --- a/src/utils/SocketCan.cxx +++ b/src/utils/SocketCan.cxx @@ -38,11 +38,11 @@ #if defined(__linux__) +#include #include #include -#include -#include #include +#include /// This macro executes an OS call, and if it returns negative result, then /// prints the errno to stderr, and terminates the current function with -1 diff --git a/src/utils/SocketCan.hxx b/src/utils/SocketCan.hxx index 23a724141..9e14354b4 100644 --- a/src/utils/SocketCan.hxx +++ b/src/utils/SocketCan.hxx @@ -42,7 +42,7 @@ /// @param loopback 1 to enable loopback locally to other open references, /// 0 to disable loopback locally to other open references. /// @return an open socket file descriptor, or -1 if there was an error. -int socketcan_open(const char* device, int loopback); +int socketcan_open(const char *device, int loopback); #endif From 6eb5206ebbb8fe55671b9a18ae3db1c1a090363a Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 2 Sep 2020 00:24:25 +0200 Subject: [PATCH 018/171] fixes include order problems. --- applications/hub/main.cxx | 10 +++++----- src/openlcb/SimpleStack.cxx | 2 +- src/utils/ClientConnection.hxx | 4 +++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/applications/hub/main.cxx b/applications/hub/main.cxx index ac3fad7ac..3f610a8f3 100644 --- a/applications/hub/main.cxx +++ b/applications/hub/main.cxx @@ -39,15 +39,15 @@ #include +#include "executor/Executor.hxx" +#include "executor/Service.hxx" #include "os/os.h" -#include "utils/constants.hxx" +#include "utils/ClientConnection.hxx" +#include "utils/GcTcpHub.hxx" #include "utils/Hub.hxx" #include "utils/HubDeviceSelect.hxx" #include "utils/SocketCan.hxx" -#include "utils/GcTcpHub.hxx" -#include "utils/ClientConnection.hxx" -#include "executor/Executor.hxx" -#include "executor/Service.hxx" +#include "utils/constants.hxx" Executor<1> g_executor("g_executor", 0, 1024); Service g_service(&g_executor); diff --git a/src/openlcb/SimpleStack.cxx b/src/openlcb/SimpleStack.cxx index 7dbe9fed4..17e69460f 100644 --- a/src/openlcb/SimpleStack.cxx +++ b/src/openlcb/SimpleStack.cxx @@ -48,10 +48,10 @@ #include "openlcb/SimpleStack.hxx" -#include "openmrn_features.h" #include "openlcb/EventHandler.hxx" #include "openlcb/NodeInitializeFlow.hxx" #include "openlcb/SimpleNodeInfo.hxx" +#include "openmrn_features.h" #include "utils/HubDeviceSelect.hxx" #include "utils/SocketCan.hxx" diff --git a/src/utils/ClientConnection.hxx b/src/utils/ClientConnection.hxx index 60973ddde..35121c2bd 100644 --- a/src/utils/ClientConnection.hxx +++ b/src/utils/ClientConnection.hxx @@ -35,11 +35,13 @@ #ifndef _UTILS_CLIENTCONNECTION_HXX_ #define _UTILS_CLIENTCONNECTION_HXX_ -#include "utils/GridConnectHub.hxx" #include #include /* tc* functions */ #include +#include "utils/GridConnectHub.hxx" +#include "utils/socket_listener.hxx" + /// Abstract base class for the Hub's connections. class ConnectionClient { From c1271c114dcdf440b1875963173f6acb7d4eae08 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 2 Sep 2020 00:38:34 +0200 Subject: [PATCH 019/171] adds documentation usage. --- applications/hub/main.cxx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/applications/hub/main.cxx b/applications/hub/main.cxx index 3f610a8f3..5abdf1405 100644 --- a/applications/hub/main.cxx +++ b/applications/hub/main.cxx @@ -71,7 +71,11 @@ const char *socketcan_port = nullptr; void usage(const char *e) { fprintf(stderr, "Usage: %s [-p port] [-d device_path] [-u upstream_host] " - "[-q upstream_port] [-m] [-n mdns_name] [-t] [-l]\n\n", + "[-q upstream_port] [-m] [-n mdns_name] " +#if defined(__linux__) + "[-s socketcan_interface] " +#endif + "[-t] [-l]\n\n", e); fprintf(stderr, "GridConnect CAN HUB.\nListens to a specific TCP port, " "reads CAN packets from the incoming connections using " @@ -87,6 +91,10 @@ void usage(const char *e) "hub.\n"); fprintf(stderr, "\t-q upstream_port is the port number for the upstream hub.\n"); +#if defined(__linux__) + fprintf(stderr, + "\t-s can0 adds the SocketCan interface 'can0' to the hub.\n"); +#endif fprintf(stderr, "\t-t prints timestamps for each packet.\n"); fprintf(stderr, From b1849d14609063ab2a7d798c4db6399e27b99439 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 2 Sep 2020 21:59:43 +0200 Subject: [PATCH 020/171] Replace avahi mdns with a symlink. --- .../hub/targets/linux.rpi1/AvaHiMDNS.cxx | 141 +----------------- 1 file changed, 1 insertion(+), 140 deletions(-) mode change 100644 => 120000 applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx diff --git a/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx b/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx deleted file mode 100644 index 1c967d280..000000000 --- a/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx +++ /dev/null @@ -1,140 +0,0 @@ -/** \copyright - * Copyright (c) 2016, Stuart W. Baker - * All rights reserved - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * \file AvaHiMDNS.cxx - * - * A simple abstraction to publish mDNS sevices using Avahi. - * - * @author Stuart Baker - * @date 30 July 2016 - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "utils/macros.h" -#include "openlcb/TcpDefs.hxx" - -static AvahiEntryGroup *group = nullptr; -static AvahiSimplePoll *simplePoll = nullptr; -static AvahiClient *client = nullptr; -static sem_t wait; - -/** Avahi group callback. - */ -static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, - void *userdata) -{ - group = g; -} - -/** Avahi client callback. - */ -static void client_callback(AvahiClient *c, AvahiClientState state, - void * userdata) -{ - switch (state) - { - default: - break; - case AVAHI_CLIENT_S_RUNNING: - printf("client running\n"); - break; - } -} - -/** mdns polling thread. - * @param unused unused parameter - * @return should never return - */ -static void *mdns_thread(void *unused) -{ - int error; - - simplePoll = avahi_simple_poll_new(); - HASSERT(simplePoll); - - client = avahi_client_new(avahi_simple_poll_get(simplePoll), - (AvahiClientFlags)0, client_callback, nullptr, - &error); - printf("client created\n"); - HASSERT(client); - - sem_post(&wait); - avahi_simple_poll_loop(simplePoll); - - return nullptr; -} - -/** Start the mDNS client. - */ -void mdns_client_start() -{ - sem_init(&wait, 0, 0); - - pthread_t thread; - pthread_create(&thread, nullptr, mdns_thread, nullptr); - printf("client start\n"); - - sem_wait(&wait); -} - -/** Publish an mDNS name. - */ -void mdns_publish(const char *name, uint16_t port) -{ - name = avahi_strdup(name); - - if (!group) - { - group = avahi_entry_group_new(client, entry_group_callback, nullptr); - if (!group) { - fprintf(stderr, "avahi_entry_group_new() failed: %s\n", - avahi_strerror(avahi_client_errno(client))); - } - HASSERT(group); - } - - int result = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC, - AVAHI_PROTO_UNSPEC, (AvahiPublishFlags)0, name, - openlcb::TcpDefs::MDNS_SERVICE_NAME_GRIDCONNECT_CAN_TCP, NULL, NULL, port, - "platform=linux-x86-openmrn", NULL); - - if (result != 0) - { - fprintf(stderr, "Error exporting mDNS name (%d) %s\n", result, - avahi_strerror(result)); - } - - HASSERT(result == 0); - avahi_entry_group_commit(group); -} diff --git a/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx b/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx new file mode 120000 index 000000000..71132b521 --- /dev/null +++ b/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx @@ -0,0 +1 @@ +../linux.x86/AvaHiMDNS.cxx \ No newline at end of file From 9ca2116227d024decd8b22669c8f4737dd8783f8 Mon Sep 17 00:00:00 2001 From: Mike Dunston Date: Fri, 4 Sep 2020 01:44:17 -0700 Subject: [PATCH 021/171] Moving socket listener stack size and backlog count to constants. (#423) As we discussed over email, the default number of sockets available for use is 10 with a max of 16. With the default backlog size of five it leaves only five additional sockets before errors will be reported by LwIP. ESP-IDF examples limit the backlog to one and thus that is the default with this PR. This PR also includes optional naming of the SocketListener thread (useful when more than one listener is in use) Fixes #256 === * Moving socket listener stack size and backlog count to constants. Fixes https://github.com/bakerstu/openmrn/issues/256 * Update socket_listener.cxx Moved include references per feedback. --- include/nmranet_config.h | 5 ++++ src/utils/constants.cxx | 12 ++++++++ src/utils/socket_listener.cxx | 56 ++++++++++++++++++----------------- src/utils/socket_listener.hxx | 4 ++- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/include/nmranet_config.h b/include/nmranet_config.h index e23a20adb..85dc8da42 100644 --- a/include/nmranet_config.h +++ b/include/nmranet_config.h @@ -146,5 +146,10 @@ DECLARE_CONST(enable_all_memory_space); * standard. */ DECLARE_CONST(node_init_identify); +/** Stack size for @ref SocketListener threads. */ +DECLARE_CONST(socket_listener_stack_size); + +/** Number of sockets to allow for @ref SocketListener backlog. */ +DECLARE_CONST(socket_listener_backlog); #endif /* _nmranet_config_h_ */ diff --git a/src/utils/constants.cxx b/src/utils/constants.cxx index 6f654a2d0..d25f90ca1 100644 --- a/src/utils/constants.cxx +++ b/src/utils/constants.cxx @@ -152,3 +152,15 @@ DEFAULT_CONST(gridconnect_bridge_max_incoming_packets, 1); DEFAULT_CONST(gridconnect_bridge_max_outgoing_packets, 1); DEFAULT_CONST_FALSE(gridconnect_tcp_use_select); + +#ifdef ESP32 +/// Use a stack size of 3kb for SocketListener tasks. +DEFAULT_CONST(socket_listener_stack_size, 3072); +/// Allow one socket to be pending for accept() in SocketListener. +DEFAULT_CONST(socket_listener_backlog, 1); +#else +/// Use a stack size of 1000 for SocketListener tasks. +DEFAULT_CONST(socket_listener_stack_size, 1000); +/// Allow up to five sockets to be pending for accept() in SocketListener. +DEFAULT_CONST(socket_listener_backlog, 5); +#endif \ No newline at end of file diff --git a/src/utils/socket_listener.cxx b/src/utils/socket_listener.cxx index bd2f45872..5dfa241f4 100644 --- a/src/utils/socket_listener.cxx +++ b/src/utils/socket_listener.cxx @@ -40,6 +40,12 @@ #define _DEFAULT_SOURCE #endif +#include "utils/socket_listener.hxx" + +#include "nmranet_config.h" +#include "utils/logging.h" +#include "utils/macros.h" + #ifndef ESP32 // these don't exist on the ESP32 with LWiP #include #include @@ -51,34 +57,22 @@ #include #include -#include "utils/socket_listener.hxx" - -#include "utils/macros.h" -#include "utils/logging.h" - - -static void* accept_thread_start(void* arg) { +static void* accept_thread_start(void* arg) +{ SocketListener* l = static_cast(arg); l->AcceptThreadBody(); return NULL; } -#ifdef ESP32 -/// Stack size to use for the accept_thread_. -static constexpr size_t listener_stack_size = 2048; -#else -/// Stack size to use for the accept_thread_. -static constexpr size_t listener_stack_size = 1000; -#endif // ESP32 - -SocketListener::SocketListener(int port, connection_callback_t callback) +SocketListener::SocketListener(int port, connection_callback_t callback, + const char *thread_name) : startupComplete_(0), shutdownRequested_(0), shutdownComplete_(0), port_(port), callback_(callback), - accept_thread_("accept_thread", 0, listener_stack_size, - accept_thread_start, this) + accept_thread_(thread_name, 0, config_socket_listener_stack_size(), + accept_thread_start, this) { #if OPENMRN_FEATURE_BSD_SOCKETS_IGNORE_SIGPIPE // We expect write failures to occur but we want to handle them where the @@ -87,8 +81,10 @@ SocketListener::SocketListener(int port, connection_callback_t callback) #endif // OPENMRN_FEATURE_BSD_SOCKETS_IGNORE_SIGPIPE } -SocketListener::~SocketListener() { - if (!shutdownComplete_) { +SocketListener::~SocketListener() +{ + if (!shutdownComplete_) + { shutdown(); } } @@ -96,12 +92,14 @@ SocketListener::~SocketListener() { void SocketListener::shutdown() { shutdownRequested_ = 1; - while (!shutdownComplete_) { + while (!shutdownComplete_) + { usleep(1000); } } -void SocketListener::AcceptThreadBody() { +void SocketListener::AcceptThreadBody() +{ socklen_t namelen; struct sockaddr_in addr; int listenfd; @@ -129,7 +127,7 @@ void SocketListener::AcceptThreadBody() { // FreeRTOS+TCP uses the parameter to listen to set the maximum number of // connections to the given socket, so allow some room - ERRNOCHECK("listen", listen(listenfd, 5)); + ERRNOCHECK("listen", listen(listenfd, config_socket_listener_backlog())); LOG(INFO, "Listening on port %d, fd %d", ntohs(addr.sin_port), listenfd); @@ -147,16 +145,20 @@ void SocketListener::AcceptThreadBody() { startupComplete_ = 1; - while (!shutdownRequested_) { + while (!shutdownRequested_) + { namelen = sizeof(addr); connfd = accept(listenfd, (struct sockaddr *)&addr, &namelen); - if (connfd < 0) { - if (errno == EINTR || errno == EAGAIN || errno == EMFILE) { + if (connfd < 0) + { + if (errno == EINTR || errno == EAGAIN || errno == EMFILE) + { continue; } - else if (errno == ECONNABORTED) { + else if (errno == ECONNABORTED) + { break; } print_errno_and_exit("accept"); diff --git a/src/utils/socket_listener.hxx b/src/utils/socket_listener.hxx index 5ff45e26d..667406702 100644 --- a/src/utils/socket_listener.hxx +++ b/src/utils/socket_listener.hxx @@ -60,7 +60,9 @@ public: /// /// @param port which TCP port number to listen upon. /// @param callback will be called on each incoming connection. - SocketListener(int port, connection_callback_t callback); + /// @param thread_name name to assign to the OSThread. + SocketListener(int port, connection_callback_t callback, + const char *thread_name = "accept_thread"); ~SocketListener(); /// Implementation of the accept thread. From beb5f19815df81d9d650e741126f3d6c72e066a4 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Sep 2020 10:44:49 +0200 Subject: [PATCH 022/171] Adds string to hex to format_utils. (#421) --- src/utils/format_utils.cxx | 45 +++++++++++++++++++++----------------- src/utils/format_utils.hxx | 6 +++++ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/utils/format_utils.cxx b/src/utils/format_utils.cxx index dd96df507..d763e46a4 100644 --- a/src/utils/format_utils.cxx +++ b/src/utils/format_utils.cxx @@ -35,6 +35,14 @@ #include "utils/macros.h" #include "utils/format_utils.hxx" +/// Translates a number 0..15 to a hex character. +/// @param nibble input number +/// @return character in 0-9a-f +static char nibble_to_hex(unsigned nibble) +{ + return nibble <= 9 ? '0' + nibble : 'a' + (nibble - 10); +} + char* unsigned_integer_to_buffer_hex(unsigned int value, char* buffer) { int num_digits = 0; @@ -50,16 +58,8 @@ char* unsigned_integer_to_buffer_hex(unsigned int value, char* buffer) do { HASSERT(num_digits >= 0); - unsigned int tmp2 = tmp % 16; - if (tmp2 <= 9) - { - buffer[num_digits--] = '0' + tmp2; - } - else - { - buffer[num_digits--] = 'a' + (tmp2 - 10); - } - tmp /= 16; + buffer[num_digits--] = nibble_to_hex(tmp & 0xf); + tmp >>= 4; } while (tmp); HASSERT(num_digits == -1); return ret; @@ -80,16 +80,8 @@ char* uint64_integer_to_buffer_hex(uint64_t value, char* buffer) do { HASSERT(num_digits >= 0); - uint64_t tmp2 = tmp % 16; - if (tmp2 <= 9) - { - buffer[num_digits--] = '0' + tmp2; - } - else - { - buffer[num_digits--] = 'a' + (tmp2 - 10); - } - tmp /= 16; + buffer[num_digits--] = nibble_to_hex(tmp & 0xf); + tmp >>= 4; } while (tmp); HASSERT(num_digits == -1); return ret; @@ -237,6 +229,19 @@ string int64_to_string_hex(int64_t value, unsigned padding) return ret; } +string string_to_hex(const string &arg) +{ + string ret; + ret.reserve(arg.size() * 2); + for (char c : arg) + { + uint8_t cc = static_cast(c); + ret.push_back(nibble_to_hex((cc >> 4) & 0xf)); + ret.push_back(nibble_to_hex(cc & 0xf)); + } + return ret; +} + string mac_to_string(uint8_t mac[6], bool colon) { string ret; diff --git a/src/utils/format_utils.hxx b/src/utils/format_utils.hxx index d1b8890b8..ff36714c6 100644 --- a/src/utils/format_utils.hxx +++ b/src/utils/format_utils.hxx @@ -121,6 +121,12 @@ string uint64_to_string_hex(uint64_t value, unsigned padding = 0); */ string int64_to_string_hex(int64_t value, unsigned padding = 0); +/// Converts a (binary) string into a sequence of hex digits. +/// @param arg input string +/// @return string twice the length of arg with hex digits representing the +/// original data. +string string_to_hex(const string& arg); + /// Formats a MAC address to string. Works both for Ethernet addresses as well /// as for OpenLCB node IDs. /// From d350d1b281d043961fe5d89606f97fa23fbe7226 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Sep 2020 13:12:06 +0200 Subject: [PATCH 023/171] Ensures that repeated groups are covering the range they cover. (#426) --- src/openlcb/VirtualMemorySpace.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/openlcb/VirtualMemorySpace.hxx b/src/openlcb/VirtualMemorySpace.hxx index c0ce099e2..2b72041bf 100644 --- a/src/openlcb/VirtualMemorySpace.hxx +++ b/src/openlcb/VirtualMemorySpace.hxx @@ -282,6 +282,7 @@ protected: re.repeatSize_ = Group::size(); HASSERT(re.repeatSize_ * N == re.end_ - re.start_); repeats_.insert(std::move(re)); + expand_bounds_from_group(group); } /// Bounds for valid addresses. From c49aba6806f827be75182d2474e52fee034c4c0b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Sep 2020 15:55:24 +0200 Subject: [PATCH 024/171] Adds missing trailing newline to the crash output macro. --- src/utils/macros.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/macros.h b/src/utils/macros.h index 7cb4670f7..142b7f9c5 100644 --- a/src/utils/macros.h +++ b/src/utils/macros.h @@ -104,7 +104,7 @@ extern const char* g_death_file; #include #ifdef NDEBUG -#define HASSERT(x) do { if (!(x)) { fprintf(stderr, "Assertion failed in file " __FILE__ " line %d: assert(" #x ")", __LINE__); g_death_file = __FILE__; g_death_lineno = __LINE__; abort();} } while(0) +#define HASSERT(x) do { if (!(x)) { fprintf(stderr, "Assertion failed in file " __FILE__ " line %d: assert(" #x ")\n", __LINE__); g_death_file = __FILE__; g_death_lineno = __LINE__; abort();} } while(0) #else /// Checks that the value of expression x is true, else terminates the current /// process. @@ -114,7 +114,7 @@ extern const char* g_death_file; /// Unconditionally terminates the current process with a message. /// @param MSG is the message to print as cause of death. -#define DIE(MSG) do { fprintf(stderr, "Crashed in file " __FILE__ " line %d: " MSG, __LINE__); g_death_file = __FILE__; g_death_lineno = __LINE__; abort(); } while(0) +#define DIE(MSG) do { fprintf(stderr, "Crashed in file " __FILE__ " line %d: " MSG "\n", __LINE__); g_death_file = __FILE__; g_death_lineno = __LINE__; abort(); } while(0) #endif From 6013d0edb3c38334c25e64767b2f205837344c62 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 6 Sep 2020 11:16:26 +0200 Subject: [PATCH 025/171] Completes VirtualMemorySpace implementation (#428) This PR fixes bugs and implements missing features in the VirtualMemorySpace: - A bug caused returning more data read than the end of space when hitting the last variable in the last repeat of a group. - Correctly trims reads and writes at the end of the space. - Implements reading from the middle of a variable. - Implements writing to a middle of a variable. - Updates unittests with new test cases. === * Fixes bug where container.end() was dereferenced. * Limits reads and writes to the maxAddress_. * Adds tests to read at eof in repeated and non-repeated groups. * Add debug statements. * Implements reading and writing in the middle of a variable. * Fix whitespace * downgrade log statements. * Adds async read-middle and r-m-w tests. --- src/openlcb/VirtualMemorySpace.cxxtest | 196 +++++++++++++++++++++++++ src/openlcb/VirtualMemorySpace.hxx | 86 +++++++++-- 2 files changed, 266 insertions(+), 16 deletions(-) diff --git a/src/openlcb/VirtualMemorySpace.cxxtest b/src/openlcb/VirtualMemorySpace.cxxtest index 80b76f3a3..749c171e4 100644 --- a/src/openlcb/VirtualMemorySpace.cxxtest +++ b/src/openlcb/VirtualMemorySpace.cxxtest @@ -177,6 +177,27 @@ TEST_F(TestSpaceTest, read_early) EXPECT_EQ(3u, b->data()->payload.size()); } +/// Test reading a variable from an imprecise offset (too late -- middle of +/// variable). +TEST_F(TestSpaceTest, read_middle) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 2, 10); + ASSERT_EQ(0, b->data()->resultCode); + string exp("llo\0\0\0\0\0\0\0", 10); + EXPECT_EQ(exp, b->data()->payload); + EXPECT_EQ(10u, b->data()->payload.size()); + + arg2 = "abcdefghij"; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 2, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp2("cde", 3); + EXPECT_EQ(exp2, b->data()->payload); + EXPECT_EQ(3u, b->data()->payload.size()); +} + /// Test writing a variable to a offset that is not covered at all. TEST_F(TestSpaceTest, read_hole) { @@ -201,6 +222,25 @@ TEST_F(TestSpaceTest, read_hole) EXPECT_EQ(string(), b->data()->payload); } +/// Test reading beyond eof. +TEST_F(TestSpaceTest, read_eof) +{ + unsigned reg_last = cfg.second().offset(); + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(20u, b->data()->payload.size()); + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 20, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 23, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->payload.size()); +} + /// Basic tests writing variables from the exact offset but not including /// partial writes. TEST_F(TestSpaceTest, write_payload) @@ -234,6 +274,52 @@ TEST_F(TestSpaceTest, write_early) EXPECT_EQ(4u, arg2.size()); } +/// Test writing into to a offset that is in the middle of a variable. +TEST_F(TestSpaceTest, write_middle) +{ + arg1 = "hellllo"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 3, "xy"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("helxylo\0\0\0\0\0\0", 13), arg1); + + // Writes to middle then beyond the end of the variable. + string payload(19, 'a'); + payload += "bbbbbbbbb"; + arg2 = "0123456789i123456789"; + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 3, payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("helaaaaaaaaaa", arg1); + EXPECT_EQ(13u, arg1.size()); + EXPECT_EQ("abbbbbbbbb", arg2); + EXPECT_EQ(10u, arg2.size()); + + // Writes a string in multiple datagrams. + payload = "0123456789abcdef"; + payload.push_back(0); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, payload.substr(0, 10)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("0123456789", arg2); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 10, + payload.substr(10, 3)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abc", arg2.c_str()); + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 13, + payload.substr(13, 2)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abcde", arg2.c_str()); + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 15, payload.substr(15)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abcdef", arg2.c_str()); +} + /// Test writing a variable to a offset that is not covered at all. TEST_F(TestSpaceTest, write_hole) { @@ -453,6 +539,27 @@ TEST_F(TestSpaceAsyncTest, read_payload_async) EXPECT_EQ(20u, b->data()->payload.size()); } +/// Test reading a variable from an imprecise offset (too late -- middle of +/// variable). +TEST_F(TestSpaceAsyncTest, read_middle) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 2, 10); + ASSERT_EQ(0, b->data()->resultCode); + string exp("llo\0\0\0\0\0\0\0", 10); + EXPECT_EQ(exp, b->data()->payload); + EXPECT_EQ(10u, b->data()->payload.size()); + + arg2 = "abcdefghij"; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 2, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp2("cde", 3); + EXPECT_EQ(exp2, b->data()->payload); + EXPECT_EQ(3u, b->data()->payload.size()); +} + /// Basic tests writing variables from the exact offset but not including /// partial writes. TEST_F(TestSpaceAsyncTest, write_payload_async) @@ -470,6 +577,52 @@ TEST_F(TestSpaceAsyncTest, write_payload_async) EXPECT_EQ(5u, arg2.size()); } +/// Test writing into to a offset that is in the middle of a variable. +TEST_F(TestSpaceAsyncTest, write_middle) +{ + arg1 = "hellllo"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 3, "xy"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("helxylo\0\0\0\0\0\0", 13), arg1); + + // Writes to middle then beyond the end of the variable. + string payload(19, 'a'); + payload += "bbbbbbbbb"; + arg2 = "0123456789i123456789"; + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 3, payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("helaaaaaaaaaa", arg1); + EXPECT_EQ(13u, arg1.size()); + EXPECT_EQ("abbbbbbbbb", arg2); + EXPECT_EQ(10u, arg2.size()); + + // Writes a string in multiple datagrams. + payload = "0123456789abcdef"; + payload.push_back(0); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, payload.substr(0, 10)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("0123456789", arg2); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 10, + payload.substr(10, 3)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abc", arg2.c_str()); + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 13, + payload.substr(13, 2)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abcde", arg2.c_str()); + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 15, payload.substr(15)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abcdef", arg2.c_str()); +} + /// Tests reading and writing numeric variables with endianness. TEST_F(TestSpaceAsyncTest, rw_numeric_async) { @@ -565,6 +718,7 @@ using GroupRept = RepeatedGroup; CDI_GROUP_ENTRY(grp, GroupRept); CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>); CDI_GROUP_ENTRY(after, StringConfigEntry<20>); +CDI_GROUP_ENTRY(grp2, GroupRept); CDI_GROUP_END(); RepeatMemoryDef spacerept(22); @@ -585,6 +739,9 @@ public: register_string( spacerept.after(), string_reader(&after_), string_writer(&after_)); register_repeat(spacerept.grp()); + register_string(spacerept.grp2().entry<0>().second(), + string_reader(&arg2), string_writer(&arg2)); + register_repeat(spacerept.grp2()); } /// Creates a ReaderFunction that just returns a string from a given @@ -760,4 +917,43 @@ TEST_F(ReptSpaceTest, mid_repeat) EXPECT_EQ(2u, s_.lastRepeat_); } +/// Test reading beyond eof. The end of the space there is a repeated group +/// where the registry entries do not cover all bytes. This is a difficult +/// cornercase and we test that all bytes until the end of the repetition can +/// be read but not beyond. +TEST_F(ReptSpaceTest, read_eof) +{ + unsigned reg_last = spacerept.grp2().entry<2>().second().offset(); + // EOF is 28 bytes away from here. + EXPECT_EQ(reg_last + 27, s_.max_address()); + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(28u, b->data()->payload.size()); + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 20, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(8u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 23, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(5u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 27, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(1u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 28, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 29, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->payload.size()); +} + } // namespace openlcb diff --git a/src/openlcb/VirtualMemorySpace.hxx b/src/openlcb/VirtualMemorySpace.hxx index 2b72041bf..9007abe8b 100644 --- a/src/openlcb/VirtualMemorySpace.hxx +++ b/src/openlcb/VirtualMemorySpace.hxx @@ -49,6 +49,11 @@ namespace openlcb class VirtualMemorySpace : public MemorySpace { public: + VirtualMemorySpace() + : isReadOnly_(false) + { + } + /// @returns whether the memory space does not accept writes. bool read_only() override { @@ -85,11 +90,16 @@ public: *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS; return 0; } + if (destination + len > maxAddress_ + 1) + { + len = maxAddress_ + 1 - destination; + } *error = 0; unsigned repeat; const DataElement *element = nullptr; ssize_t skip = find_data_element(destination, len, &element, &repeat); string payload; + size_t written_len; if (skip > 0) { // Will cause a new call be delivered with adjusted data and len. @@ -97,19 +107,44 @@ public: } else if (skip < 0) { + HASSERT(element); // We have some missing bytes that we need to read out first, then // can perform the write. - DIE("unimplemented"); - // if (!element->readImpl_(repeat, &payload, + address_t field_start = destination + skip; + if (!(cacheOffset_ == field_start && + (cachedData_.size() >= (size_t)-skip))) + { + cacheOffset_ = field_start; + cachedData_.clear(); + bn_.reset(again); + element->readImpl_(repeat, &cachedData_, bn_.new_child()); + if (!bn_.abort_if_almost_done()) + { + // did not succeed synchronously. + bn_.notify(); // our slice + *error = MemorySpace::ERROR_AGAIN; + return 0; + } + cachedData_.resize(element->size_); // pads with zeroes + } + // Now: cachedData_ contains the payload in the current storage. + payload = cachedData_; + written_len = + std::min((size_t)len, (size_t)(element->size_ + skip)); + memcpy(&payload[-skip], (const char *)data, written_len); + } + else // exact address write. + { + HASSERT(element); + payload.assign((const char *)data, + std::min((size_t)len, (size_t)element->size_)); + written_len = payload.size(); } - HASSERT(element); - payload.assign( - (const char *)data, std::min((size_t)len, (size_t)element->size_)); - size_t written_len = payload.size(); bn_.reset(again); element->writeImpl_(repeat, std::move(payload), bn_.new_child()); if (bn_.abort_if_almost_done()) { + cachedData_.clear(); return written_len; } else @@ -135,6 +170,10 @@ public: *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS; return 0; } + if (source + len > maxAddress_ + 1) + { + len = maxAddress_ + 1 - source; + } *error = 0; unsigned repeat; const DataElement *element = nullptr; @@ -144,10 +183,7 @@ public: memset(dst, 0, skip); return skip; } - else if (skip < 0) - { - DIE("unimplemented"); - } + // Now: skip <= 0 HASSERT(element); string payload; bn_.reset(again); @@ -160,8 +196,8 @@ public: return 0; } payload.resize(element->size_); // pads with zeroes - size_t data_len = std::min(payload.size(), len); - memcpy(dst, payload.data(), data_len); + size_t data_len = std::min(payload.size() + skip, len); + memcpy(dst, payload.data() - skip, data_len); return data_len; } @@ -291,7 +327,7 @@ protected: /// should succeed in returning the last byte. address_t maxAddress_ = 0; /// Whether the space should report as RO. - bool isReadOnly_ = false; + unsigned isReadOnly_ : 1; private: /// We keep one of these for each variable that was declared. @@ -389,17 +425,23 @@ private: ElementsType::iterator e = elements_.end(); // Align in the known repetitions first. auto rit = repeats_.upper_bound(address); + int max_repeat = 0; if (rit == repeats_.end()) { // not a repeat. } else { - if (rit->start_ <= address && rit->end_ > address) + if (rit->start_ <= address && address < rit->end_) { // we are in the repeat. unsigned cnt = (address - rit->start_) / rit->repeatSize_; *repeat = cnt; + if (address + rit->repeatSize_ < rit->end_) + { + // Try one repetition later too. + max_repeat = 1; + } // re-aligns address to the first repetition. address -= cnt * rit->repeatSize_; in_repeat = true; @@ -407,8 +449,13 @@ private: e = elements_.lower_bound(rit->start_ + rit->repeatSize_); } } + LOG(VERBOSE, + "searching for element at address %u in_repeat=%d address=%u " + "len=%u", + (unsigned)original_address, in_repeat, (unsigned)address, + (unsigned)len); - for (int is_repeat = 0; is_repeat <= 1; ++is_repeat) + for (int is_repeat = 0; is_repeat <= max_repeat; ++is_repeat) { auto it = std::upper_bound(b, e, address, DataComparator()); if (it != elements_.begin()) @@ -425,7 +472,7 @@ private: // else: no overlap, look at the next item } // now: it->address_ > address - if (address + len > it->address_) + if ((it != elements_.end()) && (address + len > it->address_)) { // found overlap, but some data needs to be discarded. *ptr = &*it; @@ -454,9 +501,16 @@ private: } // now: no overlap either before or after. + LOG(VERBOSE, "element not found for address %u", + (unsigned)original_address); return len; } + static constexpr unsigned NO_CACHE = static_cast(-1); + /// Offset in the memory space at which cachedData_ starts. + address_t cacheOffset_ = NO_CACHE; + /// Stored information for read-modify-write calls. + string cachedData_; /// Container type for storing the data elements. typedef SortedListSet ElementsType; /// Stores all the registered variables. From 9d88c3590f706ec93e2ed155e46629c4fb6d1e24 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 6 Sep 2020 11:17:19 +0200 Subject: [PATCH 026/171] Refactors all usages of strncpy. (#425) Adds a new function str_populate that invokes strncpy in length-safe manner, ensuring that a terminating null is present even if the string is truncated. Using C++ templates this also allows the caller not to need to specify the length of the char[] array, which is safer. Replaces all calls to strncpy that were writing into a fixed size C array with the new function. Fixes #424. --- src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx | 2 +- src/openlcb/SimpleNodeInfo.cxx | 6 +++--- src/openlcb/SimpleNodeInfoMockUserFile.cxx | 12 ++++++------ src/os/MDNS.cxx | 2 +- src/utils/EntryModel.hxx | 3 +-- src/utils/format_utils.hxx | 14 ++++++++++++++ 6 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx index fe54b2018..0ebd27395 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx @@ -765,7 +765,7 @@ void CC32xxWiFi::wlan_setup_ap(const char *ssid, const char *security_key, (uint8_t*)ssid); if (wlanRole == WlanRole::AP) { - strncpy(this->ssid, ssid, sizeof(this->ssid)); + str_populate(this->ssid, ssid); } sl_WlanSet(SL_WLAN_CFG_AP_ID, SL_WLAN_AP_OPT_SECURITY_TYPE, 1, diff --git a/src/openlcb/SimpleNodeInfo.cxx b/src/openlcb/SimpleNodeInfo.cxx index 1bfc666b5..eb627f748 100644 --- a/src/openlcb/SimpleNodeInfo.cxx +++ b/src/openlcb/SimpleNodeInfo.cxx @@ -35,6 +35,7 @@ #include "openlcb/SimpleNodeInfo.hxx" #include "openmrn_features.h" +#include "utils/format_utils.hxx" namespace openlcb { @@ -67,9 +68,8 @@ void init_snip_user_file(int fd, const char *user_name, SimpleNodeDynamicValues data; memset(&data, 0, sizeof(data)); data.version = 2; - strncpy(data.user_name, user_name, sizeof(data.user_name)); - strncpy(data.user_description, user_description, - sizeof(data.user_description)); + str_populate(data.user_name, user_name); + str_populate(data.user_description, user_description); int ofs = 0; auto *p = (const uint8_t *)&data; const int len = sizeof(data); diff --git a/src/openlcb/SimpleNodeInfoMockUserFile.cxx b/src/openlcb/SimpleNodeInfoMockUserFile.cxx index f8a48b951..32c11e537 100644 --- a/src/openlcb/SimpleNodeInfoMockUserFile.cxx +++ b/src/openlcb/SimpleNodeInfoMockUserFile.cxx @@ -37,7 +37,9 @@ #define _POSIX_C_SOURCE 200112L #endif -#include "SimpleNodeInfoMockUserFile.hxx" +#include "openlcb/SimpleNodeInfoMockUserFile.hxx" + +#include "utils/format_utils.hxx" #ifdef __FreeRTOS__ openlcb::MockSNIPUserFile::MockSNIPUserFile(const char *user_name, @@ -45,9 +47,8 @@ openlcb::MockSNIPUserFile::MockSNIPUserFile(const char *user_name, : snipData_{2} , userFile_(MockSNIPUserFile::snip_user_file_path, &snipData_, false) { - strncpy(snipData_.user_name, user_name, sizeof(snipData_.user_name)); - strncpy(snipData_.user_description, user_description, - sizeof(snipData_.user_description)); + str_populate(snipData_.user_name, user_name); + str_populate(snipData_.user_description, user_description); } openlcb::MockSNIPUserFile::~MockSNIPUserFile() @@ -63,8 +64,7 @@ openlcb::MockSNIPUserFile::MockSNIPUserFile(const char *user_name, { init_snip_user_file(userFile_.fd(), user_name, user_description); HASSERT(userFile_.name().size() < sizeof(snip_user_file_path)); - strncpy(snip_user_file_path, userFile_.name().c_str(), - sizeof(snip_user_file_path)); + str_populate(snip_user_file_path, userFile_.name().c_str()); } char openlcb::MockSNIPUserFile::snip_user_file_path[128] = "/dev/zero"; diff --git a/src/os/MDNS.cxx b/src/os/MDNS.cxx index f84cde383..ffa5e1fa9 100644 --- a/src/os/MDNS.cxx +++ b/src/os/MDNS.cxx @@ -296,7 +296,7 @@ void MDNS::resolve_callback(AvahiServiceResolver *r, sa_in->sin6_flowinfo = 0; sa_in->sin6_family = AF_INET6; sa_in->sin6_port = htons(port); - memcpy(&sa_in->sin6_addr.s6_addr, + memcpy(&(sa_in->sin6_addr.s6_addr), address->data.ipv6.address, sizeof(address->data.ipv6.address)); break; diff --git a/src/utils/EntryModel.hxx b/src/utils/EntryModel.hxx index b6c834f48..6a95495c4 100644 --- a/src/utils/EntryModel.hxx +++ b/src/utils/EntryModel.hxx @@ -120,8 +120,7 @@ public: } break; } - strncpy(data_, str.c_str(), sizeof(data_) - 1); - data_[sizeof(data_) - 1] = '\0'; + str_populate(data_, str.c_str()); hasInitial_ = true; } diff --git a/src/utils/format_utils.hxx b/src/utils/format_utils.hxx index ff36714c6..0cc8985e1 100644 --- a/src/utils/format_utils.hxx +++ b/src/utils/format_utils.hxx @@ -36,6 +36,7 @@ #define _UTILS_FORMAT_UTILS_HXX_ #include +#include /** Renders an integer to string, left-justified. @param buffer must be an at * @param buffer must be an at least 10 character long array. @@ -163,4 +164,17 @@ inline string ipv4_to_string(uint32_t ip) return ipv4_to_string((uint8_t*)&ip); } +/// Populates a character array with a C string. Copies the C string, +/// appropriately truncating if it is too long and filling the remaining space +/// with zeroes. Ensures that at least one null terminator character is +/// present. +/// @param dst a character array of fixed length, declared as char sdata[N] +/// @param src a C string to fill it with. +template +inline void str_populate(char (&dst)[N], const char *src) +{ + strncpy(dst, src, N - 1); + dst[N - 1] = 0; +} + #endif // _UTILS_FORMAT_UTILS_HXX_ From 368a0c4be3b433c872fdc89c1b52993bee326294 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 11 Sep 2020 20:24:37 +0200 Subject: [PATCH 027/171] Makes TrainNode pure abstract. (#430) * Makes the TrainNode object pure abstract with very little implementation. Creates DefaultTrainNode in the class hierarchy to own most of the existing implementation. * fix whitespace. * refactor consist storage implementation from TrainNode into a separate intermediate class. --- src/openlcb/TractionTrain.cxx | 24 +++-- src/openlcb/TractionTrain.hxx | 175 +++++++++++++++++++++++----------- 2 files changed, 137 insertions(+), 62 deletions(-) diff --git a/src/openlcb/TractionTrain.cxx b/src/openlcb/TractionTrain.cxx index f59ba03cf..68c371bec 100644 --- a/src/openlcb/TractionTrain.cxx +++ b/src/openlcb/TractionTrain.cxx @@ -42,7 +42,7 @@ namespace openlcb { -TrainNode::TrainNode(TrainService *service, TrainImpl *train) +DefaultTrainNode::DefaultTrainNode(TrainService *service, TrainImpl *train) : service_(service) , train_(train) , isInitialized_(0) @@ -52,14 +52,24 @@ TrainNode::TrainNode(TrainService *service, TrainImpl *train) TrainNode::~TrainNode() { - while (!consistSlaves_.empty()) { +} + +TrainNodeWithConsist::~TrainNodeWithConsist() +{ + while (!consistSlaves_.empty()) + { delete consistSlaves_.pop_front(); } } +DefaultTrainNode::~DefaultTrainNode() +{ +} + TrainNodeForProxy::TrainNodeForProxy(TrainService *service, TrainImpl *train) - : TrainNode(service, train) { - service_->register_train(this); + : DefaultTrainNode(service, train) +{ + service->register_train(this); } TrainNodeForProxy::~TrainNodeForProxy() @@ -71,10 +81,10 @@ TrainNodeForProxy::~TrainNodeForProxy() TrainNodeWithId::TrainNodeWithId( TrainService *service, TrainImpl *train, NodeID node_id) - : TrainNode(service, train) + : DefaultTrainNode(service, train) , nodeId_(node_id) { - service_->register_train(this); + service->register_train(this); } TrainNodeWithId::~TrainNodeWithId() @@ -90,7 +100,7 @@ NodeID TrainNodeForProxy::node_id() train_->legacy_address_type(), train_->legacy_address()); } -If *TrainNode::iface() +If *DefaultTrainNode::iface() { return service_->iface(); } diff --git a/src/openlcb/TractionTrain.hxx b/src/openlcb/TractionTrain.hxx index 25761de77..c6729076b 100644 --- a/src/openlcb/TractionTrain.hxx +++ b/src/openlcb/TractionTrain.hxx @@ -48,24 +48,6 @@ namespace openlcb class TrainService; -/// Linked list entry for all registered consist clients for a given train -/// node. -struct ConsistEntry : public QMember { - ConsistEntry(NodeID s, uint8_t flags) : payload((s << 8) | flags) {} - NodeID get_slave() const { - return payload >> 8; - } - uint8_t get_flags() const { - return payload & 0xff; - } - void set_flags(uint8_t new_flags) { - payload ^= (payload & 0xff); - payload |= new_flags; - } -private: - uint64_t payload; -}; - /// Virtual node class for an OpenLCB train protocol node. /// /// Usage: @@ -77,39 +59,17 @@ private: class TrainNode : public Node { public: - TrainNode(TrainService *service, TrainImpl *train); ~TrainNode(); - If *iface() OVERRIDE; - bool is_initialized() OVERRIDE - { - return isInitialized_; - } - void set_initialized() OVERRIDE - { - isInitialized_ = 1; - } + /// @return the train implementation object for issuing control commands to + /// this train. + virtual TrainImpl *train() = 0; - // Used for restarting the stack. - void clear_initialized() OVERRIDE - { - isInitialized_ = 0; - } + /// @return the last stored controller node. + virtual NodeHandle get_controller() = 0; - TrainImpl *train() - { - return train_; - } - - NodeHandle get_controller() - { - return controllerNodeId_; - } - - void set_controller(NodeHandle id) - { - controllerNodeId_ = id; - } + /// @param id the controller node of this train. + virtual void set_controller(NodeHandle id) = 0; // Thread-safety information // @@ -124,9 +84,71 @@ public: // before any consist change requests would reach the front of the queue // for the traction flow. + /// Adds a node ID to the consist targets. @return false if the node was + /// already in the target list, true if it was newly added. + /// @param tgt the destination of the consist link + /// @param flags consisting flags from the Traction protocol. + virtual bool add_consist(NodeID tgt, uint8_t flags) = 0; + + /// Removes a node ID from the consist targets. @return true if the target + /// was removed, false if the target was not on the list. + /// @param tgt destination of consist link to remove. + virtual bool remove_consist(NodeID tgt) = 0; + + /// Fetch a given consist link. + /// @return The target of a given consist link, or NodeID(0) if there are + /// fewer than id consist targets. + /// @param id zero-based index of consist links. + /// @param flags retrieved consist link's flags go here. + virtual NodeID query_consist(int id, uint8_t* flags) = 0; + + /// @return the number of slaves in this consist. + virtual int query_consist_length() = 0; +}; + +/// Linked list entry for all registered consist clients for a given train +/// node. +struct ConsistEntry : public QMember +{ + /// Creates a new consist entry storage. + /// @param s the stored node ID + /// @param flags the stored flag byte + ConsistEntry(NodeID s, uint8_t flags) + : payload((s << 8) | flags) + { + } + /// @return the stored Node ID. + NodeID get_slave() const + { + return payload >> 8; + } + /// @return the stored flags byte. + uint8_t get_flags() const + { + return payload & 0xff; + } + /// Overrides the stored flags. + /// @param new_flags the new value of the flags byte. + void set_flags(uint8_t new_flags) + { + payload ^= (payload & 0xff); + payload |= new_flags; + } + +private: + /// Data contents. + uint64_t payload; +}; + +/// Intermediate class which is still abstract, but adds implementation for the +/// consist management functions. +class TrainNodeWithConsist : public TrainNode { +public: + ~TrainNodeWithConsist(); + /** Adds a node ID to the consist targets. @return false if the node was * already in the target list, true if it was newly added. */ - virtual bool add_consist(NodeID tgt, uint8_t flags) + bool add_consist(NodeID tgt, uint8_t flags) override { if (!tgt) { @@ -151,7 +173,7 @@ public: /** Removes a node ID from the consist targets. @return true if the target * was removed, false if the target was not on the list. */ - virtual bool remove_consist(NodeID tgt) + bool remove_consist(NodeID tgt) override { for (auto it = consistSlaves_.begin(); it != consistSlaves_.end(); ++it) { @@ -168,7 +190,7 @@ public: /** Returns the consist target with offset id, or NodeID(0) if there are * fewer than id consist targets. id is zero-based. */ - NodeID query_consist(int id, uint8_t* flags) + NodeID query_consist(int id, uint8_t* flags) override { int k = 0; for (auto it = consistSlaves_.begin(); @@ -184,7 +206,7 @@ public: } /** Returns the number of slaves in this consist. */ - int query_consist_length() + int query_consist_length() override { int ret = 0; for (auto it = consistSlaves_.begin(); it != consistSlaves_.end(); @@ -194,21 +216,63 @@ public: return ret; } + TypedQueue consistSlaves_; +}; + +/// Default implementation of a train node. +class DefaultTrainNode : public TrainNodeWithConsist +{ +public: + DefaultTrainNode(TrainService *service, TrainImpl *impl); + ~DefaultTrainNode(); + + NodeHandle get_controller() override + { + return controllerNodeId_; + } + + void set_controller(NodeHandle id) override + { + controllerNodeId_ = id; + } + + If *iface() override; + bool is_initialized() override + { + return isInitialized_; + } + void set_initialized() override + { + isInitialized_ = 1; + } + // Used for restarting the stack. + void clear_initialized() override + { + isInitialized_ = 0; + } + + TrainImpl *train() override + { + return train_; + } + protected: + /// Pointer to the traction service. TrainService *service_; + /// Pointer to the train implementation object. TrainImpl *train_; private: + /// Node is initialized bit for startup transient. unsigned isInitialized_ : 1; /// Controller node that is assigned to run this train. 0 if none. NodeHandle controllerNodeId_; - TypedQueue consistSlaves_; }; - /// Train node class with a an OpenLCB Node ID from the DCC pool. Used for command stations. -class TrainNodeForProxy : public TrainNode { +class TrainNodeForProxy : public DefaultTrainNode +{ public: /// Constructor. /// @param service the traction service object that will own this node. @@ -226,7 +290,8 @@ public: /// Train node class with a fixed OpenLCB Node ID. This is useful for native /// train nodes that are not dynamically generated by a command station. -class TrainNodeWithId : public TrainNode { +class TrainNodeWithId : public DefaultTrainNode +{ public: /// Constructor. /// @param service the traction service object that will own this node. From c832de7f7f0059debd655a55def65638d79a5662 Mon Sep 17 00:00:00 2001 From: TrainzLuvr <46116299+TrainzLuvr@users.noreply.github.com> Date: Fri, 11 Sep 2020 14:28:05 -0400 Subject: [PATCH 028/171] Fixes compilation of boards/bracz-railcom (#429) --- boards/bracz-railcom/hardware.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/boards/bracz-railcom/hardware.hxx b/boards/bracz-railcom/hardware.hxx index 35a485f46..215fa5d2d 100644 --- a/boards/bracz-railcom/hardware.hxx +++ b/boards/bracz-railcom/hardware.hxx @@ -128,6 +128,8 @@ struct Debug typedef DummyPin DccDecodeInterrupts; //typedef LED_GREEN_Pin DccDecodeInterrupts; + typedef DummyPin DccPacketFinishedHook; + // Flips every timer capture interrupt from the dcc deocder flow. // typedef DBG_SIGNAL_Pin RailcomE0; //typedef LED_GREEN_Pin RailcomE0; From 7ec055a25b39f89b31619c7376bef5133dcff5ce Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Sep 2020 22:17:27 +0200 Subject: [PATCH 029/171] Refactors the known nodes' registry from TrainService into a separate class. (#431) * Refactors the node registry from the TrainService into a separate class. * fix whitespace. * Adds missing include guards. --- src/openlcb/DefaultNodeRegistry.hxx | 78 +++++++++++++++++++++++++++++ src/openlcb/NodeRegistry.hxx | 64 +++++++++++++++++++++++ src/openlcb/TractionTrain.cxx | 13 +++-- src/openlcb/TractionTrain.hxx | 14 ++++-- 4 files changed, 158 insertions(+), 11 deletions(-) create mode 100644 src/openlcb/DefaultNodeRegistry.hxx create mode 100644 src/openlcb/NodeRegistry.hxx diff --git a/src/openlcb/DefaultNodeRegistry.hxx b/src/openlcb/DefaultNodeRegistry.hxx new file mode 100644 index 000000000..2b10b6b88 --- /dev/null +++ b/src/openlcb/DefaultNodeRegistry.hxx @@ -0,0 +1,78 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file DefaultNodeRegistry.hxx + * + * Default implementations for data structures keyed by virtual nodes. + * + * @author Balazs Racz + * @date 12 Sep 2020 + */ + +#ifndef _OPENLCB_DEFAULTNODEREGISTRY_HXX_ +#define _OPENLCB_DEFAULTNODEREGISTRY_HXX_ + +#include + +#include "openlcb/NodeRegistry.hxx" + +namespace openlcb +{ + +class Node; + +class DefaultNodeRegistry : public NodeRegistry +{ +public: + /// Adds a node to the list of registered nodes. + /// @param node a virtual node. + void register_node(openlcb::Node *node) override + { + nodes_.insert(node); + } + + /// Removes a node from the list of registered nodes. + /// @param node a virtual node. + void unregister_node(openlcb::Node *node) override + { + nodes_.erase(node); + } + + /// Checks if a node is registered. + /// @param node a virtual node. + /// @return true if this node has been registered. + bool is_node_registered(openlcb::Node *node) override + { + return nodes_.find(node) != nodes_.end(); + } + +private: + std::set nodes_; +}; + +} // namespace openlcb + +#endif // _OPENLCB_DEFAULTNODEREGISTRY_HXX_ diff --git a/src/openlcb/NodeRegistry.hxx b/src/openlcb/NodeRegistry.hxx new file mode 100644 index 000000000..868bbf9ef --- /dev/null +++ b/src/openlcb/NodeRegistry.hxx @@ -0,0 +1,64 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file NodeRegistry.hxx + * + * Abstract class for data structures keyed by virtual nodes. + * + * @author Balazs Racz + * @date 12 Sep 2020 + */ + +#ifndef _OPENLCB_NODEREGISTRY_HXX_ +#define _OPENLCB_NODEREGISTRY_HXX_ + +#include "utils/Destructable.hxx" + +namespace openlcb +{ + +class Node; + +class NodeRegistry : public Destructable +{ +public: + /// Adds a node to the list of registered nodes. + /// @param node a virtual node. + virtual void register_node(openlcb::Node *node) = 0; + + /// Removes a node from the list of registered nodes. + /// @param node a virtual node. + virtual void unregister_node(openlcb::Node *node) = 0; + + /// Checks if a node is registered. + /// @param node a virtual node. + /// @return true if this node has been registered. + virtual bool is_node_registered(openlcb::Node *node) = 0; +}; + +} // namespace openlcb + +#endif // _OPENLCB_NODEREGISTRY_HXX_ diff --git a/src/openlcb/TractionTrain.cxx b/src/openlcb/TractionTrain.cxx index 68c371bec..908b046de 100644 --- a/src/openlcb/TractionTrain.cxx +++ b/src/openlcb/TractionTrain.cxx @@ -174,8 +174,7 @@ struct TrainService::Impl return release_and_exit(); } // Checks if destination is a local traction-enabled node. - if (trainService_->nodes_.find(train_node()) == - trainService_->nodes_.end()) + if (!trainService_->nodes_->is_node_registered(train_node())) { LOG(VERBOSE, "Traction message for node %p that is not " "traction enabled.", @@ -629,9 +628,10 @@ struct TrainService::Impl TractionRequestFlow traction_; }; -TrainService::TrainService(If *iface) +TrainService::TrainService(If *iface, NodeRegistry *train_node_registry) : Service(iface->executor()) , iface_(iface) + , nodes_(train_node_registry) { impl_ = new Impl(this); } @@ -647,17 +647,16 @@ void TrainService::register_train(TrainNode *node) extern void StartInitializationFlow(Node * node); StartInitializationFlow(node); AtomicHolder h(this); - nodes_.insert(node); + nodes_->register_node(node); LOG(VERBOSE, "Registered node %p for traction.", node); - HASSERT(nodes_.find(node) != nodes_.end()); } void TrainService::unregister_train(TrainNode *node) { - HASSERT(nodes_.find(node) != nodes_.end()); + HASSERT(nodes_->is_node_registered(node)); iface_->delete_local_node(node); AtomicHolder h(this); - nodes_.erase(node); + nodes_->unregister_node(node); } } // namespace openlcb diff --git a/src/openlcb/TractionTrain.hxx b/src/openlcb/TractionTrain.hxx index c6729076b..8c1d08ac2 100644 --- a/src/openlcb/TractionTrain.hxx +++ b/src/openlcb/TractionTrain.hxx @@ -38,6 +38,7 @@ #include #include "executor/Service.hxx" +#include "openlcb/DefaultNodeRegistry.hxx" #include "openlcb/Node.hxx" #include "openlcb/TractionDefs.hxx" #include "openlcb/TrainInterface.hxx" @@ -322,7 +323,12 @@ private: class TrainService : public Service, private Atomic { public: - TrainService(If *iface); + /// Constructor. + /// @param iface the OpenLCB interface to which the train nodes are bound. + /// @param train_node_registry implementation of the + /// NodeRegistry. Ownership is transferred. + TrainService( + If *iface, NodeRegistry *train_node_registry = new DefaultNodeRegistry); ~TrainService(); If *iface() @@ -344,17 +350,17 @@ public: /// @return true if this is a known train node. bool is_known_train_node(Node *node) { - return nodes_.find((TrainNode*)node) != nodes_.end(); + return nodes_->is_node_registered(node); } private: struct Impl; /** Implementation flows. */ Impl *impl_; - + /** OpenLCB interface */ If *iface_; /** List of train nodes managed by this Service. */ - std::set nodes_; + std::unique_ptr nodes_; }; } // namespace openlcb From e1b0c2d1e61918288ec169763b050e19d36f94d8 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Sep 2020 22:38:36 +0200 Subject: [PATCH 030/171] Optimizes memory usage of AliasCache (#432) Changes the storage datastructure in AliasCache to use less memory. The new structure uses 16-bit indexes instead of pointers, and a sorted array of 16-bit indexes instead of red-black trees for the lookup methods. The LRU double-linked list is kept. With this change the memory cost goes from 88 bytes per entry to 16 bytes per entry. The compiled code size is also smaller. === * Changes the storage datastructure in AliasCache to use less memory. The new structure uses 16-bit indexes instead of pointers, and a sorted array of 16-bit indexes instead of red-black trees for the lookup methods. The LRU double-linked list is kept. With this change the memory cost goes from 88 bytes per entry to 16 bytes per entry. The compiled code size is also smaller. * fix whitespaces * Add more comments. * fix comment typos. --- src/openlcb/AliasCache.cxx | 159 +++++++++++---------- src/openlcb/AliasCache.cxxtest | 72 +++++----- src/openlcb/AliasCache.hxx | 246 +++++++++++++++++++++++++++------ src/utils/SortedListMap.hxx | 28 ++-- 4 files changed, 342 insertions(+), 163 deletions(-) diff --git a/src/openlcb/AliasCache.cxx b/src/openlcb/AliasCache.cxx index a637b3d6b..32400b2e8 100644 --- a/src/openlcb/AliasCache.cxx +++ b/src/openlcb/AliasCache.cxx @@ -46,15 +46,15 @@ void AliasCache::clear() { idMap.clear(); aliasMap.clear(); - oldest = nullptr; - newest = nullptr; - freeList = nullptr; + oldest.idx_ = NONE_ENTRY; + newest.idx_ = NONE_ENTRY; + freeList.idx_ = NONE_ENTRY; /* initialize the freeList */ for (size_t i = 0; i < entries; ++i) { - pool[i].prev = NULL; - pool[i].next = freeList; - freeList = pool + i; + pool[i].newer_.idx_ = NONE_ENTRY; + pool[i].older_ = freeList; + freeList.idx_ = i; } } @@ -69,78 +69,78 @@ void AliasCache::add(NodeID id, NodeAlias alias) Metadata *insert; - AliasMap::Iterator it = aliasMap.find(alias); + auto it = aliasMap.find(alias); if (it != aliasMap.end()) { /* we already have a mapping for this alias, so lets remove it */ - insert = (*it).second; - remove(alias); - + insert = it->deref(this); + remove(insert->alias_); + if (removeCallback) { /* tell the interface layer that we removed this mapping */ - (*removeCallback)(insert->id, insert->alias, context); + (*removeCallback)(insert->get_node_id(), insert->alias_, context); } } - if (freeList) + if (!freeList.empty()) { /* found an empty slot */ - insert = freeList; - freeList = insert->next; + insert = freeList.deref(this); + freeList = insert->older_; } else { - HASSERT(oldest != NULL && newest != NULL); + HASSERT(!oldest.empty() && !newest.empty()); /* kick out the oldest mapping and re-link the oldest endpoint */ - insert = oldest; - if (oldest->newer) + insert = oldest.deref(this); + auto second = insert->newer_; + if (!second.empty()) { - oldest->newer->older = NULL; + second.deref(this)->older_.idx_ = NONE_ENTRY; } - if (insert == newest) + if (insert == newest.deref(this)) { - newest = NULL; + newest.idx_ = NONE_ENTRY; } - oldest = oldest->newer; + oldest = second; - aliasMap.erase(insert->alias); - idMap.erase(insert->id); + aliasMap.erase(aliasMap.find(insert->alias_)); + idMap.erase(idMap.find(insert->get_node_id())); if (removeCallback) { /* tell the interface layer that we removed this mapping */ - (*removeCallback)(insert->id, insert->alias, context); + (*removeCallback)(insert->get_node_id(), insert->alias_, context); } } - - insert->timestamp = OSTime::get_monotonic(); - insert->id = id; - insert->alias = alias; - aliasMap[alias] = insert; - idMap[id] = insert; + insert->set_node_id(id); + insert->alias_ = alias; + + PoolIdx n; + n.idx_ = insert - pool; + aliasMap.insert(PoolIdx(n)); + idMap.insert(PoolIdx(n)); /* update the time based list */ - insert->newer = NULL; - if (newest == NULL) + insert->newer_.idx_ = NONE_ENTRY; + if (newest.empty()) { /* if newest == NULL, then oldest must also be NULL */ - HASSERT(oldest == NULL); + HASSERT(oldest.empty()); - insert->older = NULL; - oldest = insert; + insert->older_.idx_ = NONE_ENTRY; + oldest = n; } else { - insert->older = newest; - newest->newer = insert; + insert->older_ = newest; + newest.deref(this)->newer_ = n; } - newest = insert; - - return; + newest = n; } /** Remove an alias from an alias cache. This method does not call the @@ -150,33 +150,33 @@ void AliasCache::add(NodeID id, NodeAlias alias) */ void AliasCache::remove(NodeAlias alias) { - AliasMap::Iterator it = aliasMap.find(alias); + auto it = aliasMap.find(alias); if (it != aliasMap.end()) { - Metadata *metadata = (*it).second; + Metadata *metadata = it->deref(this); aliasMap.erase(it); - idMap.erase(metadata->id); - - if (metadata->newer) + idMap.erase(idMap.find(metadata->get_node_id())); + + if (!metadata->newer_.empty()) { - metadata->newer->older = metadata->older; + metadata->newer_.deref(this)->older_ = metadata->older_; } - if (metadata->older) + if (!metadata->older_.empty()) { - metadata->older->newer = metadata->newer; + metadata->older_.deref(this)->newer_ = metadata->newer_; } - if (metadata == newest) + if (metadata == newest.deref(this)) { - newest = metadata->older; + newest = metadata->older_; } - if (metadata == oldest) + if (metadata == oldest.deref(this)) { - oldest = metadata->newer; + oldest = metadata->newer_; } - - metadata->next = freeList; - freeList = metadata; + + metadata->older_ = freeList; + freeList.idx_ = metadata - pool; } } @@ -185,9 +185,9 @@ bool AliasCache::retrieve(unsigned entry, NodeID* node, NodeAlias* alias) { HASSERT(entry < size()); Metadata* md = pool + entry; - if (!md->alias) return false; - if (node) *node = md->id; - if (alias) *alias = md->alias; + if (!md->alias_) return false; + if (node) *node = md->get_node_id(); + if (alias) *alias = md->alias_; return true; } @@ -199,15 +199,15 @@ NodeAlias AliasCache::lookup(NodeID id) { HASSERT(id != 0); - IdMap::Iterator it = idMap.find(id); + auto it = idMap.find(id); if (it != idMap.end()) { - Metadata *metadata = (*it).second; - + Metadata *metadata = it->deref(this); + /* update timestamp */ touch(metadata); - return metadata->alias; + return metadata->alias_; } /* no match found */ @@ -222,15 +222,15 @@ NodeID AliasCache::lookup(NodeAlias alias) { HASSERT(alias != 0); - AliasMap::Iterator it = aliasMap.find(alias); + auto it = aliasMap.find(alias); if (it != aliasMap.end()) { - Metadata *metadata = (*it).second; - + Metadata *metadata = it->deref(this); + /* update timestamp */ touch(metadata); - return metadata->id; + return metadata->get_node_id(); } /* no match found */ @@ -246,9 +246,10 @@ void AliasCache::for_each(void (*callback)(void*, NodeID, NodeAlias), void *cont { HASSERT(callback != NULL); - for (Metadata *metadata = newest; metadata != NULL; metadata = metadata->older) + for (PoolIdx idx = newest; !idx.empty(); idx = idx.deref(this)->older_) { - (*callback)(context, metadata->id, metadata->alias); + Metadata *metadata = idx.deref(this); + (*callback)(context, metadata->get_node_id(), metadata->alias_); } } @@ -277,25 +278,23 @@ NodeAlias AliasCache::generate() */ void AliasCache::touch(Metadata* metadata) { - metadata->timestamp = OSTime::get_monotonic(); - - if (metadata != newest) + if (metadata != newest.deref(this)) { - if (metadata == oldest) + if (metadata == oldest.deref(this)) { - oldest = metadata->newer; - oldest->older = NULL; + oldest = metadata->newer_; + oldest.deref(this)->older_.idx_ = NONE_ENTRY; } else { /* we have someone older */ - metadata->older->newer = metadata->newer; + metadata->older_.deref(this)->newer_ = metadata->newer_; } - metadata->newer->older = metadata->older; - metadata->newer = NULL; - metadata->older = newest; - newest->newer = metadata; - newest = metadata; + metadata->newer_.deref(this)->older_ = metadata->older_; + metadata->newer_.idx_ = NONE_ENTRY; + metadata->older_ = newest; + newest.deref(this)->newer_.idx_ = metadata - pool; + newest.idx_ = metadata - pool; } } diff --git a/src/openlcb/AliasCache.cxxtest b/src/openlcb/AliasCache.cxxtest index 9998d8cb4..6a648a242 100644 --- a/src/openlcb/AliasCache.cxxtest +++ b/src/openlcb/AliasCache.cxxtest @@ -467,16 +467,18 @@ namespace openlcb { int AliasCache::check_consistency() { if (idMap.size() != aliasMap.size()) return 1; if (aliasMap.size() == entries) { - if (freeList != nullptr) return 2; + if (!freeList.empty()) return 2; } else { - if (freeList == nullptr) return 3; + if (freeList.empty()) return 3; } - if (aliasMap.size() == 0 && - (oldest != nullptr || newest != nullptr)) { + if (aliasMap.size() == 0 && (!oldest.empty() || !newest.empty())) + { return 4; } std::set free_entries; - for (Metadata* m = freeList; m; m=m->next) { + for (PoolIdx p = freeList; !p.empty(); p = p.deref(this)->older_) + { + Metadata *m = p.deref(this); if (free_entries.count(m)) { return 5; // duplicate entry on freelist } @@ -486,66 +488,74 @@ int AliasCache::check_consistency() { return 6; // lost some metadata entries } for (auto kv : aliasMap) { - if (free_entries.count(kv.second)) { + if (free_entries.count(kv.deref(this))) + { return 19; } } for (auto kv : idMap) { - if (free_entries.count(kv.second)) { + if (free_entries.count(kv.deref(this))) + { return 20; } } if (aliasMap.size() == 0) { - if (oldest != nullptr) return 7; - if (newest != nullptr) return 8; + if (!oldest.empty()) return 7; + if (!newest.empty()) return 8; } else { - if (oldest == nullptr) return 9; - if (newest == nullptr) return 10; + if (oldest.empty()) return 9; + if (newest.empty()) return 10; } - if (free_entries.count(oldest)) { + if (free_entries.count(oldest.deref(this))) + { return 11; // oldest is free } - if (free_entries.count(newest)) { + if (free_entries.count(newest.deref(this))) + { return 12; // newest is free } if (aliasMap.size() == 0) return 0; // Check linking. { - Metadata* prev = oldest; + PoolIdx prev = oldest; unsigned count = 1; - if (prev->older) return 13; - while (prev->newer) { - auto* next = prev->newer; + if (!prev.deref(this)->older_.empty()) return 13; + while (!prev.deref(this)->newer_.empty()) + { + auto next = prev.deref(this)->newer_; ++count; - if (free_entries.count(next)) { + if (free_entries.count(next.deref(this))) + { return 21; } - if (next->older != prev) return 14; + if (next.deref(this)->older_.idx_ != prev.idx_) return 14; prev = next; } - if (prev != newest) return 18; + if (prev.idx_ != newest.idx_) return 18; if (count != aliasMap.size()) return 27; } { - Metadata* next = newest; - if (next->newer) return 15; - while (next->older) { - auto* prev = next->older; - if (free_entries.count(prev)) { + PoolIdx next = newest; + if (!next.deref(this)->newer_.empty()) return 15; + while (!next.deref(this)->older_.empty()) + { + auto prev = next.deref(this)->older_; + if (free_entries.count(prev.deref(this))) + { return 22; } - if (prev->newer != next) return 16; + if (prev.deref(this)->newer_.idx_ != next.idx_) return 16; next = prev; } - if (next != oldest) return 17; + if (next.idx_ != oldest.idx_) return 17; } for (unsigned i = 0; i < entries; ++i) { if (free_entries.count(pool+i)) continue; auto* e = pool+i; - if (idMap.find(e->id) == idMap.end()) return 23; - if (idMap[e->id] != e) return 24; - if (aliasMap.find(e->alias) == aliasMap.end()) return 25; - if (aliasMap[e->alias] != e) return 26; + if (idMap.find(e->get_node_id()) == idMap.end()) return 23; + if (idMap.find(e->get_node_id())->idx_ != i) return 24; + if (aliasMap.find(e->alias_) == aliasMap.end()) return 25; + if (aliasMap.find(e->alias_)->idx_ != i) return 26; } return 0; } diff --git a/src/openlcb/AliasCache.hxx b/src/openlcb/AliasCache.hxx index b59ddc55d..fdbafbe57 100644 --- a/src/openlcb/AliasCache.hxx +++ b/src/openlcb/AliasCache.hxx @@ -35,9 +35,9 @@ #define _OPENLCB_ALIASCACHE_HXX_ #include "openlcb/Defs.hxx" -#include "utils/macros.h" #include "utils/Map.hxx" -#include "utils/RBTree.hxx" +#include "utils/SortedListMap.hxx" +#include "utils/macros.h" namespace openlcb { @@ -48,8 +48,40 @@ namespace openlcb * is no mutual exclusion locking mechanism built into this class. Mutual * exclusion must be handled by the user as needed. * - * @todo the class uses RBTree, consider a version that is a linear search for - * a small number of entries. + * This data structure is sometimes used with very large entry count + * (hundreds), therefore we must be careful about memory efficiency! + * + * Theory of operation: + * + * The data structure has three access patterns: + * - lookup of alias -> ID + * - lookup of ID -> alias + * - eviction of oldest entry from the cache + * + * We have three data structures to match these use-cases: + * + * The struct Metadata stores the actual NodeID and NodeAlias values. This is + * laid out in the pre-allocated C array `pool`. A freeList shows where unused + * entries are. + * + * To support the eviction of oldest entry, an LRU doubly-linked list is + * created in these Metadata entries. The links are represented by indexes into + * the `pool` array. + * + * Indexes into the `pool` are encapsulated into the PoolIdx struct to make + * them a unique C++ type. This is needed for template disambiguation. We also + * have a dereference function on PoolIdx that turns it into a Metadata + * pointer. + * + * To support lookup by alias, we have a SortedListSet which contains all used + * indexes as PoolIdx, sorted by the alias property in the respective entry in + * `pool`. This is achieved by a custom comparator that dereferences the + * PoolIdx object and fetches the alias from the Metadata struct. The sorted + * vector is maintained using the SortedListSet<> template, and takes a total + * of only 2 bytes per entry. + * + * A similar sorted vector is kept sorted by the NodeID values. This also takes + * only 2 bytes per entry. */ class AliasCache { @@ -62,24 +94,25 @@ public: * @param context context pointer to pass to remove_callback */ AliasCache(NodeID seed, size_t _entries, - void (*remove_callback)(NodeID id, NodeAlias alias, void *) = NULL, - void *context = NULL) - : pool(new Metadata[_entries]), - freeList(NULL), - aliasMap(_entries), - idMap(_entries), - oldest(NULL), - newest(NULL), - seed(seed), - entries(_entries), - removeCallback(remove_callback), - context(context) + void (*remove_callback)(NodeID id, NodeAlias alias, void *) = NULL, + void *context = NULL) + : pool(new Metadata[_entries]) + , aliasMap(this) + , idMap(this) + , seed(seed) + , entries(_entries) + , removeCallback(remove_callback) + , context(context) { + aliasMap.reserve(_entries); + idMap.reserve(_entries); clear(); } /** This NodeID will be used for reserved but unused local aliases. */ static const NodeID RESERVED_ALIAS_NODE_ID; + /// Sentinel entry for empty lists. + static constexpr uint16_t NONE_ENTRY = 0xFFFFu; /** Reinitializes the entire map. */ void clear(); @@ -126,12 +159,12 @@ public: * changes. * @param entry is between 0 and size() - 1. * @param node will be filled with the node ID. May be null. - * @param aliad will be filles with the alias. May be null. + * @param alias will be filled with the alias. May be null. * @return true if the entry is valid, and node and alias were filled, otherwise false if the entry is not allocated. */ bool retrieve(unsigned entry, NodeID* node, NodeAlias* alias); - /** Generate a 12-bit pseudo-random alias for a givin alias cache. + /** Generate a 12-bit pseudo-random alias for a given alias cache. * @return pseudo-random 12-bit alias, an alias of zero is invalid */ NodeAlias generate(); @@ -152,47 +185,172 @@ private: UNUSED_MASK = 0x10000000 }; + struct Metadata; + class PoolIdx; + friend class PoolIdx; + + /// Encapsulation of a pointer into the pool array. + class PoolIdx + { + public: + /// Constructor. Sets the pointer to invalid. + PoolIdx() + : idx_(NONE_ENTRY) + { + } + /// @return true if this entry does not point anywhere. + bool empty() + { + return idx_ == NONE_ENTRY; + } + /// Indexes the pool of the AliasCache. + uint16_t idx_; + /// Dereferences a pool index as if it was a pointer. + /// @param parent the AliasCache whose pool to index. + /// @return referenced Metadata pointer. + Metadata *deref(AliasCache *parent) + { + DASSERT(idx_ != NONE_ENTRY); + return parent->pool + idx_; + } + }; + /** Interesting information about a given cache entry. */ struct Metadata { - NodeID id = 0; /**< 48-bit NMRAnet Node ID */ - NodeAlias alias = 0; /**< NMRAnet alias */ - long long timestamp; /**< time stamp of last usage */ - union + /// Sets the node ID field. + /// @param id the node ID to set. + void set_node_id(NodeID id) { - Metadata *prev; /**< unused */ - Metadata *newer; /**< pointer to the next newest entry */ - }; - union + nodeIdLow_ = id & 0xFFFFFFFFu; + nodeIdHigh_ = (id >> 32) & 0xFFFFu; + } + + /// @return the node ID field. + NodeID get_node_id() { - Metadata *next; /**< pointer to next freeList entry */ - Metadata *older; /**< pointer to the next oldest entry */ - }; + uint64_t h = nodeIdHigh_; + h <<= 32; + h |= nodeIdLow_; + return h; + } + + /// OpenLCB Node ID low 32 bits. + uint32_t nodeIdLow_ = 0; + /// OpenLCB Node ID high 16 bits. + uint16_t nodeIdHigh_ = 0; + /// OpenLCB-CAN alias + NodeAlias alias_ = 0; + /// Index of next-newer entry according to the LRU linked list. + PoolIdx newer_; + /// Index of next-older entry according to the LRU linked list. + PoolIdx older_; }; /** pointer to allocated Metadata pool */ Metadata *pool; - - /** list of unused mapping entries */ - Metadata *freeList; - + + /// Comparator object comparing the aliases stored in the pool. + class AliasComparator + { + public: + /// Constructor + /// @param parent owning AliasCache. + AliasComparator(AliasCache *parent) + : parent_(parent) + { + } + + /// Less-than action. + /// @param e left hand side + /// @param alias right hand side + bool operator()(PoolIdx e, uint16_t alias) const + { + return e.deref(parent_)->alias_ < alias; + } + + /// Less-than action. + /// @param alias left hand side + /// @param e right hand side + bool operator()(uint16_t alias, PoolIdx e) const + { + return alias < e.deref(parent_)->alias_; + } + + /// Less-than action. + /// @param a left hand side + /// @param b right hand side + bool operator()(PoolIdx a, PoolIdx b) const + { + return a.deref(parent_)->alias_ < b.deref(parent_)->alias_; + } + + private: + /// AliasCache whose pool we are indexing into. + AliasCache *parent_; + }; + + /// Comparator object comparing the aliases stored in the pool. + class IdComparator + { + public: + /// Constructor + /// @param parent owning AliasCache. + IdComparator(AliasCache *parent) + : parent_(parent) + { + } + + /// Less-than action. + /// @param e left hand side + /// @param id right hand side + bool operator()(PoolIdx e, NodeID id) const + { + return e.deref(parent_)->get_node_id() < id; + } + + /// Less-than action. + /// @param id left hand side + /// @param e right hand side + bool operator()(NodeID id, PoolIdx e) const + { + return id < e.deref(parent_)->get_node_id(); + } + + /// Less-than action. + /// @param a left hand side + /// @param b right hand side + bool operator()(PoolIdx a, PoolIdx b) const + { + return a.deref(parent_)->get_node_id() < + b.deref(parent_)->get_node_id(); + } + + private: + /// AliasCache whose pool we are indexing into. + AliasCache *parent_; + }; + /** Short hand for the alias Map type */ - typedef Map AliasMap; - + typedef SortedListSet AliasMap; + /** Short hand for the ID Map type */ - typedef Map IdMap; + typedef SortedListSet IdMap; /** Map of alias to corresponding Metadata */ AliasMap aliasMap; /** Map of Node ID to corresponding Metadata */ IdMap idMap; - - /** oldest untouched entry */ - Metadata *oldest; - - /** newest, most recently touched entry */ - Metadata *newest; + + /** list of unused mapping entries (index into pool) */ + PoolIdx freeList; + + /** oldest untouched entry (index into pool) */ + PoolIdx oldest; + + /** newest, most recently touched entry (index into pool) */ + PoolIdx newest; /** Seed for the generation of the next alias */ NodeID seed; @@ -214,6 +372,6 @@ private: DISALLOW_COPY_AND_ASSIGN(AliasCache); }; -} /* namepace NMRAnet */ +} /* namespace openlcb */ -#endif /* _NMRAnetAliasCache_hxx */ +#endif // _OPENLCB_ALIASCACHE_HXX_ diff --git a/src/utils/SortedListMap.hxx b/src/utils/SortedListMap.hxx index 293727d58..598e934d5 100644 --- a/src/utils/SortedListMap.hxx +++ b/src/utils/SortedListMap.hxx @@ -59,10 +59,19 @@ public: /// Const Iterator type. typedef typename container_type::const_iterator const_iterator; - SortedListSet() + template + SortedListSet(Args &&...args) + : cmp_(std::forward(args)...) { } + /// Ensures that a given size can be reached without memory allocation. + /// @param sz the number of entries to prepare for. + void reserve(size_t sz) + { + container_.reserve(sz); + } + /// @return first iterator. iterator begin() { @@ -86,8 +95,8 @@ public: iterator lower_bound(key_type key) { lazy_init(); - return std::lower_bound(container_.begin(), container_.end(), key, - CMP()); + return std::lower_bound( + container_.begin(), container_.end(), key, cmp_); } /// @param key what to search for @return iterator, see std::upper_bound. @@ -95,8 +104,8 @@ public: iterator upper_bound(key_type key) { lazy_init(); - return std::upper_bound(container_.begin(), container_.end(), key, - CMP()); + return std::upper_bound( + container_.begin(), container_.end(), key, cmp_); } /// Searches for a single entry. @param key is what to search for. @return @@ -116,8 +125,8 @@ public: std::pair equal_range(key_type key) { lazy_init(); - return std::equal_range(container_.begin(), container_.end(), key, - CMP()); + return std::equal_range( + container_.begin(), container_.end(), key, cmp_); } /// Adds new entry to the vector. @@ -154,7 +163,7 @@ private: { if (sortedCount_ != container_.size()) { - sort(container_.begin(), container_.end(), CMP()); + sort(container_.begin(), container_.end(), cmp_); sortedCount_ = container_.size(); } } @@ -162,6 +171,9 @@ private: /// Holds the actual data elements. container_type container_; + /// Comparator instance. + CMP cmp_; + /// The first this many elements in the container are already sorted. size_t sortedCount_{0}; }; From 1060956dc07165011ac1d3343ae9fba0a2545556 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Sep 2020 22:41:19 +0200 Subject: [PATCH 031/171] Fixes consistency issues in the AliasCache. (#433) Fixes consistency issues in the AliasCache. - Makes sure that when the AliasCache adds a new mapping for (id, alias) then the id also gets checked in idMap, not just the alias in aliasMap. This avoids the situation where two different alias might be registered for the same ID. - Runs the Alias Cache consistency test after each modification when running under GTEST. - Removes RESERVED_ALIAS_NODE_ID because that created exactly this situation. - Adds helper functions to generate a reserved alias node ID that is different for each alias, by using a range of invalid Node IDs. - Updates existing code and tests that were depending on RESERVED_ALIAS_NODE_ID. - Adds missing braces to the consistency check function. === * Fixes consistency issues in the AliasCache. - Makes sure that when the AliasCache adds a new mapping for (id, alias) then the id also gets checked in idMap, not just the alias in aliasMap. This avoids the situation where two different alias might be registered for the same ID. - Runs the Alias Cache consistency test after each modification when running under GTEST. - Removes RESERVED_ALIAS_NODE_ID because that created exactly this situation. - Adds helper functions to generate a reserved alias node ID that is different for each alias, by using a range of invalid Node IDs. - Updates existing code and tests that were depending on RESERVED_ALIAS_NODE_ID. * Fix whitespace * Adds missing braces. --- src/openlcb/AliasAllocator.cxx | 8 +- src/openlcb/AliasAllocator.cxxtest | 3 +- src/openlcb/AliasCache.cxx | 208 ++++++++++++++++++++++++++++- src/openlcb/AliasCache.cxxtest | 98 -------------- src/openlcb/AliasCache.hxx | 2 - src/openlcb/CanDefs.hxx | 29 ++++ src/openlcb/IfCan.cxx | 2 +- src/openlcb/IfCan.cxxtest | 7 +- src/openlcb/IfCanImpl.hxx | 4 +- 9 files changed, 249 insertions(+), 112 deletions(-) diff --git a/src/openlcb/AliasAllocator.cxx b/src/openlcb/AliasAllocator.cxx index 17f386545..526e5c345 100644 --- a/src/openlcb/AliasAllocator.cxx +++ b/src/openlcb/AliasAllocator.cxx @@ -216,8 +216,9 @@ StateFlowBase::Action AliasAllocator::send_rid_frame() pending_alias()->state = AliasInfo::STATE_RESERVED; if_can()->frame_dispatcher()->unregister_handler( &conflictHandler_, pending_alias()->alias, ~0x1FFFF000U); - if_can()->local_aliases()->add(AliasCache::RESERVED_ALIAS_NODE_ID, - pending_alias()->alias); + if_can()->local_aliases()->add( + CanDefs::get_reserved_alias_node_id(pending_alias()->alias), + pending_alias()->alias); reserved_alias_pool_.insert(transfer_message()); return release_and_exit(); } @@ -259,7 +260,8 @@ void AliasAllocator::TEST_add_allocated_alias(NodeAlias alias, bool repeat) a->data()->do_not_reallocate(); } if_can()->local_aliases()->add( - AliasCache::RESERVED_ALIAS_NODE_ID, a->data()->alias); + CanDefs::get_reserved_alias_node_id(a->data()->alias), + a->data()->alias); reserved_aliases()->insert(a); } diff --git a/src/openlcb/AliasAllocator.cxxtest b/src/openlcb/AliasAllocator.cxxtest index faef26b0d..67a284dcd 100644 --- a/src/openlcb/AliasAllocator.cxxtest +++ b/src/openlcb/AliasAllocator.cxxtest @@ -3,6 +3,7 @@ #include "utils/async_if_test_helper.hxx" #include "openlcb/AliasAllocator.hxx" #include "openlcb/AliasCache.hxx" +#include "openlcb/CanDefs.hxx" namespace openlcb { @@ -156,7 +157,7 @@ TEST_F(AsyncAliasAllocatorTest, AllocationConflict) EXPECT_EQ(AliasInfo::STATE_RESERVED, b_->data()->state); run_x([this]() { // This one should be marked as reserved. - EXPECT_EQ(AliasCache::RESERVED_ALIAS_NODE_ID, + EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0xAA5), ifCan_->local_aliases()->lookup(NodeAlias(0xAA5))); // This one should be unknown. EXPECT_EQ(0U, ifCan_->local_aliases()->lookup(NodeAlias(0x555))); diff --git a/src/openlcb/AliasCache.cxx b/src/openlcb/AliasCache.cxx index 32400b2e8..cb4585603 100644 --- a/src/openlcb/AliasCache.cxx +++ b/src/openlcb/AliasCache.cxx @@ -33,14 +33,192 @@ #include "openlcb/AliasCache.hxx" +#include + #include "os/OS.hxx" +#ifdef GTEST +#define TEST_CONSISTENCY +#endif + namespace openlcb { #define CONSTANT 0x1B0CA37ABA9 /**< constant for random number generation */ -const NodeID AliasCache::RESERVED_ALIAS_NODE_ID = 1; +#if defined(TEST_CONSISTENCY) +extern volatile int consistency_result; +volatile int consistency_result = 0; + +int AliasCache::check_consistency() +{ + if (idMap.size() != aliasMap.size()) + { + return 1; + } + if (aliasMap.size() == entries) + { + if (!freeList.empty()) + { + return 2; + } + } + else + { + if (freeList.empty()) + { + return 3; + } + } + if (aliasMap.size() == 0 && (!oldest.empty() || !newest.empty())) + { + return 4; + } + std::set free_entries; + for (PoolIdx p = freeList; !p.empty(); p = p.deref(this)->older_) + { + Metadata *m = p.deref(this); + if (free_entries.count(m)) + { + return 5; // duplicate entry on freelist + } + free_entries.insert(m); + } + if (free_entries.size() + aliasMap.size() != entries) + { + return 6; // lost some metadata entries + } + for (auto kv : aliasMap) + { + if (free_entries.count(kv.deref(this))) + { + return 19; + } + } + for (auto kv : idMap) + { + if (free_entries.count(kv.deref(this))) + { + return 20; + } + } + if (aliasMap.size() == 0) + { + if (!oldest.empty()) + { + return 7; + } + if (!newest.empty()) + { + return 8; + } + } + else + { + if (oldest.empty()) + { + return 9; + } + if (newest.empty()) + { + return 10; + } + if (free_entries.count(oldest.deref(this))) + { + return 11; // oldest is free + } + if (free_entries.count(newest.deref(this))) + { + return 12; // newest is free + } + } + if (aliasMap.size() == 0) + { + return 0; + } + // Check linking. + { + PoolIdx prev = oldest; + unsigned count = 1; + if (!prev.deref(this)->older_.empty()) + { + return 13; + } + while (!prev.deref(this)->newer_.empty()) + { + auto next = prev.deref(this)->newer_; + ++count; + if (free_entries.count(next.deref(this))) + { + return 21; + } + if (next.deref(this)->older_.idx_ != prev.idx_) + { + return 14; + } + prev = next; + } + if (prev.idx_ != newest.idx_) + { + return 18; + } + if (count != aliasMap.size()) + { + return 27; + } + } + { + PoolIdx next = newest; + if (!next.deref(this)->newer_.empty()) + { + return 15; + } + while (!next.deref(this)->older_.empty()) + { + auto prev = next.deref(this)->older_; + if (free_entries.count(prev.deref(this))) + { + return 22; + } + if (prev.deref(this)->newer_.idx_ != next.idx_) + { + return 16; + } + next = prev; + } + if (next.idx_ != oldest.idx_) + { + return 17; + } + } + for (unsigned i = 0; i < entries; ++i) + { + if (free_entries.count(pool + i)) + { + continue; + } + auto *e = pool + i; + if (idMap.find(e->get_node_id()) == idMap.end()) + { + return 23; + } + if (idMap.find(e->get_node_id())->idx_ != i) + { + return 24; + } + if (aliasMap.find(e->alias_) == aliasMap.end()) + { + return 25; + } + if (aliasMap.find(e->alias_)->idx_ != i) + { + return 26; + } + } + return 0; +} + +#endif void AliasCache::clear() { @@ -82,6 +260,19 @@ void AliasCache::add(NodeID id, NodeAlias alias) (*removeCallback)(insert->get_node_id(), insert->alias_, context); } } + auto nit = idMap.find(id); + if (nit != idMap.end()) + { + /* we already have a mapping for this id, so lets remove it */ + insert = nit->deref(this); + remove(insert->alias_); + + if (removeCallback) + { + /* tell the interface layer that we removed this mapping */ + (*removeCallback)(insert->get_node_id(), insert->alias_, context); + } + } if (!freeList.empty()) { @@ -141,6 +332,11 @@ void AliasCache::add(NodeID id, NodeAlias alias) } newest = n; + +#if defined(TEST_CONSISTENCY) + consistency_result = check_consistency(); + HASSERT(0 == consistency_result); +#endif } /** Remove an alias from an alias cache. This method does not call the @@ -178,7 +374,11 @@ void AliasCache::remove(NodeAlias alias) metadata->older_ = freeList; freeList.idx_ = metadata - pool; } - + +#if defined(TEST_CONSISTENCY) + consistency_result = check_consistency(); + HASSERT(0 == consistency_result); +#endif } bool AliasCache::retrieve(unsigned entry, NodeID* node, NodeAlias* alias) @@ -296,6 +496,10 @@ void AliasCache::touch(Metadata* metadata) newest.deref(this)->newer_.idx_ = metadata - pool; newest.idx_ = metadata - pool; } +#if defined(TEST_CONSISTENCY) + consistency_result = check_consistency(); + HASSERT(0 == consistency_result); +#endif } } diff --git a/src/openlcb/AliasCache.cxxtest b/src/openlcb/AliasCache.cxxtest index 6a648a242..f2f0ca637 100644 --- a/src/openlcb/AliasCache.cxxtest +++ b/src/openlcb/AliasCache.cxxtest @@ -463,104 +463,6 @@ protected: AliasCache c_{get_id(0x33), 10}; }; -namespace openlcb { -int AliasCache::check_consistency() { - if (idMap.size() != aliasMap.size()) return 1; - if (aliasMap.size() == entries) { - if (!freeList.empty()) return 2; - } else { - if (freeList.empty()) return 3; - } - if (aliasMap.size() == 0 && (!oldest.empty() || !newest.empty())) - { - return 4; - } - std::set free_entries; - for (PoolIdx p = freeList; !p.empty(); p = p.deref(this)->older_) - { - Metadata *m = p.deref(this); - if (free_entries.count(m)) { - return 5; // duplicate entry on freelist - } - free_entries.insert(m); - } - if (free_entries.size() + aliasMap.size() != entries) { - return 6; // lost some metadata entries - } - for (auto kv : aliasMap) { - if (free_entries.count(kv.deref(this))) - { - return 19; - } - } - for (auto kv : idMap) { - if (free_entries.count(kv.deref(this))) - { - return 20; - } - } - if (aliasMap.size() == 0) { - if (!oldest.empty()) return 7; - if (!newest.empty()) return 8; - } else { - if (oldest.empty()) return 9; - if (newest.empty()) return 10; - } - if (free_entries.count(oldest.deref(this))) - { - return 11; // oldest is free - } - if (free_entries.count(newest.deref(this))) - { - return 12; // newest is free - } - if (aliasMap.size() == 0) return 0; - // Check linking. - { - PoolIdx prev = oldest; - unsigned count = 1; - if (!prev.deref(this)->older_.empty()) return 13; - while (!prev.deref(this)->newer_.empty()) - { - auto next = prev.deref(this)->newer_; - ++count; - if (free_entries.count(next.deref(this))) - { - return 21; - } - if (next.deref(this)->older_.idx_ != prev.idx_) return 14; - prev = next; - } - if (prev.idx_ != newest.idx_) return 18; - if (count != aliasMap.size()) return 27; - } - { - PoolIdx next = newest; - if (!next.deref(this)->newer_.empty()) return 15; - while (!next.deref(this)->older_.empty()) - { - auto prev = next.deref(this)->older_; - if (free_entries.count(prev.deref(this))) - { - return 22; - } - if (prev.deref(this)->newer_.idx_ != next.idx_) return 16; - next = prev; - } - if (next.idx_ != oldest.idx_) return 17; - } - for (unsigned i = 0; i < entries; ++i) { - if (free_entries.count(pool+i)) continue; - auto* e = pool+i; - if (idMap.find(e->get_node_id()) == idMap.end()) return 23; - if (idMap.find(e->get_node_id())->idx_ != i) return 24; - if (aliasMap.find(e->alias_) == aliasMap.end()) return 25; - if (aliasMap.find(e->alias_)->idx_ != i) return 26; - } - return 0; -} - -} TEST_F(AliasStressTest, stress_test) { diff --git a/src/openlcb/AliasCache.hxx b/src/openlcb/AliasCache.hxx index fdbafbe57..69fe1a5d8 100644 --- a/src/openlcb/AliasCache.hxx +++ b/src/openlcb/AliasCache.hxx @@ -109,8 +109,6 @@ public: clear(); } - /** This NodeID will be used for reserved but unused local aliases. */ - static const NodeID RESERVED_ALIAS_NODE_ID; /// Sentinel entry for empty lists. static constexpr uint16_t NONE_ENTRY = 0xFFFFu; diff --git a/src/openlcb/CanDefs.hxx b/src/openlcb/CanDefs.hxx index 08749f49a..9c230e3db 100644 --- a/src/openlcb/CanDefs.hxx +++ b/src/openlcb/CanDefs.hxx @@ -136,6 +136,18 @@ struct CanDefs { NOT_LAST_FRAME = 0x10, }; + /// Constants used in the LocalAliasCache for reserved but not used + /// aliases. + enum ReservedAliasNodeId + { + /// To mark a reserved alias in the local alias cache, we use this as a + /// node ID and add the alias to the lowest 12 bits. Since this value + /// starts with a zero MSB byte, it is not a valid node ID. + RESERVED_ALIAS_NODE_BITS = 0xF000, + /// Mask for the reserved aliases. + RESERVED_ALIAS_NODE_MASK = 0xFFFFFFFFF000 + }; + /** Get the source field value of the CAN ID. * @param can_id identifier to act upon * @return source field @@ -363,6 +375,23 @@ struct CanDefs { frame.can_dlc = 0; } + /** Computes a reserved alias node ID for the local alias cache map. + * @param alias the alias to reserve + * @return Node ID to use in the alias map as a key. + */ + static NodeID get_reserved_alias_node_id(NodeAlias alias) + { + return RESERVED_ALIAS_NODE_BITS | alias; + } + + /** Tests if a node ID is a reserved alias Node ID. + * @param id node id to test + * @return true if this is a reserved alias node ID. */ + static bool is_reserved_alias_node_id(NodeID id) + { + return (id & RESERVED_ALIAS_NODE_MASK) == RESERVED_ALIAS_NODE_BITS; + } + private: /** This class should not be instantiated. */ CanDefs(); diff --git a/src/openlcb/IfCan.cxx b/src/openlcb/IfCan.cxx index 121c8ddd3..ad68b5e04 100644 --- a/src/openlcb/IfCan.cxx +++ b/src/openlcb/IfCan.cxx @@ -718,7 +718,7 @@ void IfCan::delete_local_node(Node *node) { if (alias) { // The node had a local alias. localAliases_.remove(alias); - localAliases_.add(AliasCache::RESERVED_ALIAS_NODE_ID, alias); + localAliases_.add(CanDefs::get_reserved_alias_node_id(alias), alias); // Sends AMR & returns alias to pool. aliasAllocator_->return_alias(node->node_id(), alias); } diff --git a/src/openlcb/IfCan.cxxtest b/src/openlcb/IfCan.cxxtest index fc1d5af65..42e66c3bc 100644 --- a/src/openlcb/IfCan.cxxtest +++ b/src/openlcb/IfCan.cxxtest @@ -1,5 +1,6 @@ #include "utils/async_if_test_helper.hxx" +#include "openlcb/CanDefs.hxx" #include "openlcb/WriteHelper.hxx" namespace openlcb @@ -417,13 +418,13 @@ TEST_F(AsyncMessageCanTests, ReservedAliasReclaimed) usleep(250000); wait(); // Here we have the next reserved alias. - RX(EXPECT_EQ(AliasCache::RESERVED_ALIAS_NODE_ID, + RX(EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0x44C), ifCan_->local_aliases()->lookup(NodeAlias(0x44C)))); // A CID packet gets replied to. send_packet_and_expect_response(":X1478944CN;", ":X1070044CN;"); // We still have it in the cache. - RX(EXPECT_EQ(AliasCache::RESERVED_ALIAS_NODE_ID, + RX(EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0x44C), ifCan_->local_aliases()->lookup(NodeAlias(0x44C)))); // We kick it out with a regular frame. send_packet(":X1800044CN;"); @@ -449,7 +450,7 @@ TEST_F(AsyncMessageCanTests, ReservedAliasReclaimed) RX(EXPECT_EQ( TEST_NODE_ID + 1, ifCan_->local_aliases()->lookup(NodeAlias(0x44D)))); usleep(250000); - RX(EXPECT_EQ(AliasCache::RESERVED_ALIAS_NODE_ID, + RX(EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0x6AA), ifCan_->local_aliases()->lookup(NodeAlias(0x6AA)))); } diff --git a/src/openlcb/IfCanImpl.hxx b/src/openlcb/IfCanImpl.hxx index bd3129ca0..ccb8282c6 100644 --- a/src/openlcb/IfCanImpl.hxx +++ b/src/openlcb/IfCanImpl.hxx @@ -137,8 +137,8 @@ private: nmsg()->src.id); // Checks that there was no conflict on this alias. - if (if_can()->local_aliases()->lookup(alias) != - AliasCache::RESERVED_ALIAS_NODE_ID) + if (!CanDefs::is_reserved_alias_node_id( + if_can()->local_aliases()->lookup(alias))) { LOG(INFO, "Alias has seen conflict: %03X", alias); // Problem. Let's take another alias. From 789f9151a65c5d060bd7be0fdd49eda5c53f83b5 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 19 Sep 2020 16:46:32 +0200 Subject: [PATCH 032/171] Makes the total number of open file descriptors configurable. (#434) The configuration works with weak symbols that the application owner has to override in their own .cxx file if needed to be changed. It was not possible to make use of the usual OVERRIDE_CONST facility, because it is important to have the files[] array allocated in .bss and an array definition does not compile with the C++ compiler when using an extern size symbol. Having files[] in .bss allows us not to worry about initialization time race conditions. --- src/freertos_drivers/common/Devtab.hxx | 11 +++--- src/freertos_drivers/common/Fileio.cxx | 8 ++--- src/freertos_drivers/common/FileioWeak.cxx | 41 ++++++++++++++++++++++ src/freertos_drivers/sources | 1 + src/freertos_drivers/spiffs/SPIFFS.hxx | 2 +- 5 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 src/freertos_drivers/common/FileioWeak.cxx diff --git a/src/freertos_drivers/common/Devtab.hxx b/src/freertos_drivers/common/Devtab.hxx index e86d2e4c6..76b062336 100644 --- a/src/freertos_drivers/common/Devtab.hxx +++ b/src/freertos_drivers/common/Devtab.hxx @@ -46,13 +46,6 @@ class FileSystem; class Notifiable; class DeviceBufferBase; -#ifdef TARGET_LPC11Cxx -#define NUM_OPEN_FILES 4 -#else -/// How many concurrently open fd we support. -#define NUM_OPEN_FILES 20 //12 -#endif - /** File information. */ struct File @@ -259,6 +252,10 @@ protected: */ static int fd_lookup(File *file); + /** @return the maximum number of open file descriptors possible (the size + * of the files[] array. */ + static const unsigned int numOpenFiles; + /** File descriptor pool */ static File files[]; diff --git a/src/freertos_drivers/common/Fileio.cxx b/src/freertos_drivers/common/Fileio.cxx index 9120abf62..2ebd83206 100644 --- a/src/freertos_drivers/common/Fileio.cxx +++ b/src/freertos_drivers/common/Fileio.cxx @@ -39,16 +39,14 @@ #include #include - OSMutex FileIO::mutex; -File FileIO::files[NUM_OPEN_FILES]; /** Allocate a free file descriptor. * @return file number on success, else -1 on failure */ int FileIO::fd_alloc(void) { - for (unsigned int i = 0; i < NUM_OPEN_FILES; i++) + for (unsigned int i = 0; i < numOpenFiles; i++) { if (files[i].inuse == false) { @@ -82,7 +80,7 @@ void FileIO::fd_free(int fd) */ File* FileIO::file_lookup(int fd) { - if (fd < 0 || fd >= NUM_OPEN_FILES) + if (fd < 0 || fd >= (int)numOpenFiles) { errno = EBADF; return nullptr; @@ -101,7 +99,7 @@ File* FileIO::file_lookup(int fd) */ int FileIO::fd_lookup(File *file) { - HASSERT(file >= files && file <= (files + NUM_OPEN_FILES) && file->inuse); + HASSERT(file >= files && file <= (files + numOpenFiles) && file->inuse); return (file - files); } diff --git a/src/freertos_drivers/common/FileioWeak.cxx b/src/freertos_drivers/common/FileioWeak.cxx new file mode 100644 index 000000000..0976075fd --- /dev/null +++ b/src/freertos_drivers/common/FileioWeak.cxx @@ -0,0 +1,41 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file FileioWeak.cxx + * + * Weak definitions for FileIO. These can be overridden by client applications. + * + * @author Balazs Racz + * @date 18 September 2020 + */ + +#include "Devtab.hxx" + +// Override both of these symbols in a .cxx file in your application (without +// the weak attribute) if you want to change the nuber of open files. + +const unsigned int __attribute__((weak)) FileIO::numOpenFiles = 20; +File __attribute__((weak)) FileIO::files[FileIO::numOpenFiles]; diff --git a/src/freertos_drivers/sources b/src/freertos_drivers/sources index 1851ce7e1..a7e08f519 100644 --- a/src/freertos_drivers/sources +++ b/src/freertos_drivers/sources @@ -4,6 +4,7 @@ VPATH := $(SRCDIR): \ CSRCS += CXXSRCS += Fileio.cxx \ + FileioWeak.cxx \ Device.cxx \ FileSystem.cxx \ DeviceBuffer.cxx \ diff --git a/src/freertos_drivers/spiffs/SPIFFS.hxx b/src/freertos_drivers/spiffs/SPIFFS.hxx index ce7035fb4..85f5d2b8e 100644 --- a/src/freertos_drivers/spiffs/SPIFFS.hxx +++ b/src/freertos_drivers/spiffs/SPIFFS.hxx @@ -101,7 +101,7 @@ public: void flush_cache() { mutex.lock(); - for (unsigned int i = 0; i < NUM_OPEN_FILES; i++) + for (unsigned int i = 0; i < numOpenFiles; i++) { if (files[i].inuse && files[i].dev == this && files[i].dirty) { From fcf9b1358f3653290aa59da35ceb3fd80f1beca4 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Sep 2020 18:01:22 +0200 Subject: [PATCH 033/171] Adds an approximate LRU counting algorithm. (#435) * Adds an approximate LRU counting algorithm. The GlobalLruCounter and a set of LruCounter<> objects cooperate in order to create an approximate LRU order over a set of objects. The particular optimization criterion is that the memory storage per object should be very low, target is one byte. * Ensures that counters do not overflow with the LRUCounter. Adds missing doucmentation comment. * fix whitespace --- src/utils/LruCounter.cxxtest | 293 +++++++++++++++++++++++++++++++++++ src/utils/LruCounter.hxx | 169 ++++++++++++++++++++ 2 files changed, 462 insertions(+) create mode 100644 src/utils/LruCounter.cxxtest create mode 100644 src/utils/LruCounter.hxx diff --git a/src/utils/LruCounter.cxxtest b/src/utils/LruCounter.cxxtest new file mode 100644 index 000000000..cbecefadd --- /dev/null +++ b/src/utils/LruCounter.cxxtest @@ -0,0 +1,293 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file LruCounter.cxxtest + * + * Unit tests for LruCounter. + * + * @author Balazs Racz + * @date 18 Sep 2020 + */ + +#include "utils/LruCounter.hxx" + +#include "utils/test_main.hxx" + +class LruCounterTest : public ::testing::Test +{ +protected: + GlobalLruCounter global_; + + void tick_n(unsigned n) + { + for (unsigned i = 0; i < n; ++i) + { + global_.tick(); + cb1.tick(global_); + cb2.tick(global_); + cb3.tick(global_); + cb4.tick(global_); + cs1.tick(global_); + cs2.tick(global_); + cs3.tick(global_); + cs4.tick(global_); + } + } + + void set_bits_per_bit(unsigned bpb) + { + new (&global_) GlobalLruCounter(bpb); + } + + /// Runs the sequence test on a given set of counters. + /// @param entries the counters + /// @param num_tick how much to wait between resetting each counter. + template + void sequence_test(std::initializer_list entries, unsigned num_tick) + { + for (T *e : entries) + { + EXPECT_EQ(0u, e->value()); + } + for (unsigned i = 1; i < entries.size(); i++) + { + tick_n(num_tick); + entries.begin()[i]->touch(); + } + tick_n(num_tick); + + for (unsigned i = 1; i < entries.size(); i++) + { + EXPECT_GT( + entries.begin()[i - 1]->value(), entries.begin()[i]->value()); + } + } + + /// Expects that an entry is going to flip forward to the next value in + /// num_tick counts. + template void next_increment(T *entry, unsigned num_tick) + { + LOG(INFO, "Next increment from %u", entry->value()); + unsigned current = entry->value(); + tick_n(num_tick - 1); + EXPECT_EQ(current, entry->value()); + tick_n(1); + EXPECT_EQ(current + 1, entry->value()); + } + + /// Byte sized LRU counters for testing. + LruCounter cb1, cb2, cb3, cb4; + /// Short sized LRU counters for testing. + LruCounter cs1, cs2, cs3, cs4; +}; + +TEST_F(LruCounterTest, create) +{ +} + +/// Tests that the initial value is zero and the reset value is zero. +TEST_F(LruCounterTest, initial) +{ + EXPECT_EQ(0u, cb1.value()); + EXPECT_EQ(0u, cs1.value()); + + cb1.touch(); + cs1.touch(); + + EXPECT_EQ(0u, cb1.value()); + EXPECT_EQ(0u, cs1.value()); +} + +/// Increments a counter through the first few values, which take exponentially +/// increasing tick count. +TEST_F(LruCounterTest, simple_increment) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cb1.value()); + tick_n(1); // 1 + EXPECT_EQ(1u, cb1.value()); + tick_n(1); // 2 + EXPECT_EQ(2u, cb1.value()); + tick_n(2); // 4 + EXPECT_EQ(3u, cb1.value()); + tick_n(4); // 8 + EXPECT_EQ(4u, cb1.value()); + tick_n(8); // 16 + EXPECT_EQ(5u, cb1.value()); +} + +/// Increments a 16-bit counter through the first few values, which take +/// exponentially increasing tick count. +TEST_F(LruCounterTest, simple_increment_short) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cs1.value()); + tick_n(1); // 1 + EXPECT_EQ(1u, cs1.value()); + tick_n(1); // 2 + EXPECT_EQ(2u, cs1.value()); + tick_n(2); // 4 + EXPECT_EQ(3u, cs1.value()); + tick_n(4); // 8 + EXPECT_EQ(4u, cs1.value()); + tick_n(8); // 16 + EXPECT_EQ(5u, cs1.value()); +} + +/// Increments a 2 bit/bit counter through the first few values, which take +/// exponentially increasing tick count. +TEST_F(LruCounterTest, simple_increment_2bit) +{ + EXPECT_EQ(0u, cb1.value()); + next_increment(&cb1, 1); // old value = 0, next tick = 1 + next_increment(&cb1, 3); // old value = 1, next tick = 4 + next_increment(&cb1, 12); // old value = 2, next tick = 16 + next_increment(&cb1, 16); // old value = 3, next tick = 32 + next_increment(&cb1, 32); // old value = 4, next tick = 64 + next_increment(&cb1, 64); // old value = 5, next tick = 128 + next_increment(&cb1, 64); // old value = 6, next tick = 192 + next_increment(&cb1, 64); // old value = 7, next tick = 256 + next_increment(&cb1, 256); // old value = 8, next tick = 512 +} + +/// Increments a 16-bit 2 bit/bit counter through the first few values, which +/// take exponentially increasing tick count. +TEST_F(LruCounterTest, simple_increment_short_2bit) +{ + EXPECT_EQ(0u, cs1.value()); + next_increment(&cs1, 1); // old value = 0, next tick = 1 + next_increment(&cs1, 3); // old value = 1, next tick = 4 + next_increment(&cs1, 12); // old value = 2, next tick = 16 + next_increment(&cs1, 16); // old value = 3, next tick = 32 + next_increment(&cs1, 32); // old value = 4, next tick = 64 + next_increment(&cs1, 64); // old value = 5, next tick = 128 + next_increment(&cs1, 64); // old value = 6, next tick = 192 + next_increment(&cs1, 64); // old value = 7, next tick = 256 + next_increment(&cs1, 256); // old value = 8, next tick = 512 +} + +/// Saturates a byte sized counter and expects that no overflow has happened. +TEST_F(LruCounterTest, no_overflow) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cb1.value()); + tick_n(100000); + EXPECT_EQ(255u, cb1.value()); + tick_n(1); + EXPECT_EQ(255u, cb1.value()); + tick_n(100000); + EXPECT_EQ(255u, cb1.value()); +} + +/// Checks that a 2 bit/bit exponent bytes sized counter can count more than a +/// few 100k ticks. +TEST_F(LruCounterTest, byte_range) +{ + set_bits_per_bit(2); + EXPECT_EQ(0u, cb1.value()); + tick_n(100000); + EXPECT_EQ(52u, cb1.value()); + tick_n(100000); + EXPECT_EQ(67u, cb1.value()); +} + +/// Tests resetting the counter, then incrementing. +TEST_F(LruCounterTest, reset) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cb1.value()); + tick_n(16); + EXPECT_EQ(5u, cb1.value()); + + cb1.touch(); + EXPECT_EQ(0u, cb1.value()); + tick_n(1); // 1 + EXPECT_EQ(1u, cb1.value()); + tick_n(1); // 2 + EXPECT_EQ(2u, cb1.value()); + tick_n(2); // 4 + EXPECT_EQ(3u, cb1.value()); + tick_n(4); // 8 + EXPECT_EQ(4u, cb1.value()); + tick_n(8); // 16 + EXPECT_EQ(5u, cb1.value()); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. +TEST_F(LruCounterTest, sequence) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cb1.value()); + EXPECT_EQ(0u, cb2.value()); + EXPECT_EQ(0u, cb3.value()); + EXPECT_EQ(0u, cb4.value()); + + cb1.touch(); + tick_n(50); + cb2.touch(); + tick_n(50); + cb3.touch(); + tick_n(50); + cb4.touch(); + tick_n(50); + + EXPECT_GT(cb1.value(), cb2.value()); + EXPECT_GT(cb2.value(), cb3.value()); + EXPECT_GT(cb3.value(), cb4.value()); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. 1-byte, 1-bit-per-bit exponent +TEST_F(LruCounterTest, sequence_byte_1) +{ + set_bits_per_bit(1); + sequence_test({&cb1, &cb2, &cb3, &cb4}, 50); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. 2-byte, 1-bit-per-bit exponent +TEST_F(LruCounterTest, sequence_short_1) +{ + set_bits_per_bit(1); + sequence_test({&cs1, &cs2, &cs3, &cs4}, 50); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. 1-byte 2-bit-per-bit exponent +TEST_F(LruCounterTest, sequence_byte_2) +{ + set_bits_per_bit(2); + sequence_test({&cb1, &cb2, &cb3, &cb4}, 400); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. 2-byte 2-bit-per-bit exponent +TEST_F(LruCounterTest, sequence_short_2) +{ + set_bits_per_bit(2); + sequence_test({&cs1, &cs2, &cs3, &cs4}, 400); +} diff --git a/src/utils/LruCounter.hxx b/src/utils/LruCounter.hxx new file mode 100644 index 000000000..aec479ff0 --- /dev/null +++ b/src/utils/LruCounter.hxx @@ -0,0 +1,169 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file LruCounter.hxx + * + * A monotonic counter that is usable for approximate LRU age determination. + * + * @author Balazs Racz + * @date 18 Sep 2020 + */ + +#ifndef _UTILS_LRUCOUNTER_HXX_ +#define _UTILS_LRUCOUNTER_HXX_ + +#include + +template class LruCounter; + +/// The GlobalLruCounter and a set of LruCounter<> objects cooperate in order +/// to create an approximate LRU order over a set of objects. The particular +/// optimization criterion is that the memory storage per object should be very +/// low. (Target is one byte.) Touching an object is constant time, but there +/// is linear time background maintenance operations that need to run +/// regularly. Picking the oldest object is linear time. The oldest concept is +/// an approximation in that the older an object becomes the less time +/// granularity is available to distinguish exact age. This is generally fine +/// in applications. +/// +/// How to use: +/// +/// Create one GlobalLruCounter. Create for each tracked object an +/// LruCounter or LruCounter. +/// +/// Periodically call the tick() function once on the GlobalLruCounter, then +/// for each live object the tick(global) function. This is linear cost in the +/// number of tracked objects, so do it rather rarely (e.g. once per second). +/// +/// When a specific object is used, call the touch() function on it. +/// +/// When the oldest object needs to be selected, pick the one which has the +/// highest returned value() from its LruCounter<>. +/// +/// Theory of operation: +/// +/// The GlobalLruCounter maintains a global tick count. It gets incremented by +/// one in each tick. In the per-object local counter we only increment the +/// counter for a subset of the global ticks. How many global ticks we skip +/// between two local counter increments depends on the age of the object. The +/// older an object becomes the more rarely we increment the object's counter. +/// +/// Specifically, if the object counter has reached to be k bits long, then we +/// only increment it, when the global counter's bottom k bits are all +/// zero. Example: if the object counter is 35 (6 bits long), then we increment +/// it to 36 when the global counter is divisible by 64 (all 6 bottom bits are +/// zero). In a variant we double the zero-bits requirement, needing that the +/// bottom 12 bits are all zero. +/// +/// Example calculations, assuming 1 tick per second: +/// +/// +-------------------------------------------------------------------+ +/// | Exponent 1 bit/bit 2 bits/bit | +/// +------------+------------------------------------------------------+ +/// | data type: | | +/// | | | +/// | uint8_t | max count: ~43k max count: 9.5M | +/// | | (0.5 days) (110 days) | +/// | | end granularity: 256 end granularity: 64k | +/// | | (4 min) (0.5 days) | +/// +------------+------------------------------------------------------+ +/// | uint16_t | max count: ~2.8B max count: 161T | +/// | | (100 years) (5M years) | +/// | | end granularity: 64k end granularity: 4B | +/// | | (0.5 days) (136 years) | +/// +------------+------------------------------------------------------+ +class GlobalLruCounter +{ +public: + /// Constructor. + /// @param bits_per_bit How aggressive the exponential downsampling should + /// be. Meaningful values are 1 and 2. + GlobalLruCounter(unsigned bits_per_bit = 2) + : bitsPerBit_(bits_per_bit) + { + } + void tick() + { + ++tick_; + } + +private: + template friend class LruCounter; + /// Setting defining the exponent. + unsigned bitsPerBit_; + /// Rolling counter of global ticks. This is used by the local counters to + /// synchronize their increments. + unsigned tick_ {0}; +}; + +/// Create an instance of this type for each object whose age needs to be +/// measured with the GlobalLruCounter. For further details, see +/// { \link GlobalLruCounter }. +/// @param T is the storage type, typically uint8_t or uint16_t. +template class LruCounter +{ +public: + /// @return A value monotonic in the age of the current counter. + unsigned value() + { + return counter_; + } + + /// Increments the local counter. + /// @param global reference to the global tick counter. All calls must use + /// the same global counter. + void tick(const GlobalLruCounter &global) + { + if (!counter_) + { + ++counter_; + return; + } + if (counter_ == std::numeric_limits::max()) + { + // Counter is saturated. + return; + } + int nlz = __builtin_clz((unsigned)counter_); + int needzero = (32 - nlz) * global.bitsPerBit_; + if ((global.tick_ & ((1U << needzero) - 1)) == 0) + { + ++counter_; + } + } + + /// Signals that the object has been used now. + void touch() + { + counter_ = 0; + } + +private: + /// Internal counter. + T counter_ {0}; +}; + +#endif // _UTILS_LRUCOUNTER_HXX_ From 4e3f14bef02f553cc9c3caa18cc70b8460b4e6d2 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 26 Sep 2020 12:28:05 +0200 Subject: [PATCH 034/171] Adds support for using the stored setting for AP/STA mode. (#436) Adds WlanRole::DEFAULT as an argument to the start() method that allows to use the AP/STA setting from the NWP stored configuration instead of from the application. Adds wlan_set_role to override such configuration. Fixes a bug where the IP address handed out via DHCP was incorrectly saved in the AP's own IP address variable. This caused the "is local connection" check in SocketClient to misbehave in AP mode. --- src/freertos_drivers/common/WifiDefs.hxx | 7 +++--- .../net_cc32xx/CC32xxWiFi.cxx | 25 ++++++++++++++++--- .../net_cc32xx/CC32xxWiFi.hxx | 7 ++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/freertos_drivers/common/WifiDefs.hxx b/src/freertos_drivers/common/WifiDefs.hxx index 751bc5071..fc4a841e2 100644 --- a/src/freertos_drivers/common/WifiDefs.hxx +++ b/src/freertos_drivers/common/WifiDefs.hxx @@ -32,9 +32,10 @@ enum class WlanState : uint8_t */ enum class WlanRole : uint8_t { - UNKNOWN = 0, /**< Wi-Fi station mode */ - STA, /**< Wi-Fi station mode */ - AP /**< Wi-Fi access point mode */ + UNKNOWN = 0, /**< Default mode (from stored configuration) */ + DEFAULT = UNKNOWN, /**< Default mode (from stored configuration) */ + STA, /**< Wi-Fi station mode */ + AP /**< Wi-Fi access point mode */ }; enum class CountryCode : uint8_t diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx index 0ebd27395..3c2494de1 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx @@ -805,8 +805,10 @@ void CC32xxWiFi::set_default_state() } SlCheckError(result); - if (wlanRole == WlanRole::AP) + if (wlanRole == WlanRole::AP || + (wlanRole == WlanRole::UNKNOWN && result == ROLE_AP)) { + wlanRole = WlanRole::AP; if (result != ROLE_AP) { sl_WlanSetMode(ROLE_AP); @@ -821,6 +823,7 @@ void CC32xxWiFi::set_default_state() } else { + wlanRole = WlanRole::STA; if (wlan_profile_test_none()) { /* no profiles saved, add the default profile */ @@ -845,6 +848,24 @@ void CC32xxWiFi::set_default_state() started = true; } +/* + * CC32xxWiFi::wlan_set_role() + */ +void CC32xxWiFi::wlan_set_role(WlanRole new_role) +{ + switch (new_role) + { + case WlanRole::STA: + sl_WlanSetMode(ROLE_STA); + break; + case WlanRole::AP: + sl_WlanSetMode(ROLE_AP); + break; + default: + DIE("Unsupported wlan role"); + } +} + /* * CC32xxWiFi::wlan_task() */ @@ -1192,8 +1213,6 @@ void CC32xxWiFi::net_app_event_handler(NetAppEvent *event) // event_data = &event->EventData.ipLeased; // - SlIpLeasedAsync_t *ip_leased = &event->Data.IpLeased; - ipAddress = ip_leased->IpAddress; break; } case SL_NETAPP_EVENT_IP_COLLISION: diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx index 54267d73b..d64cbf322 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx @@ -226,6 +226,13 @@ public: return wlanRole; } + /** Change the default Wlan Role. This will be used in the next start(...) + * if the UNKNOWN role is specified. The new setting takes effect when the + * device is restarted (either via reboot or stop + start). + * @param role new role. Must not be UNKNOWN + */ + void wlan_set_role(WlanRole new_role); + /** @return 0 if !wlan_ready, else a debugging status code. */ WlanState wlan_startup_state() { From ad4c454bf288aa050501cb58ec9f170e6dd4f64b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 28 Sep 2020 21:28:03 +0200 Subject: [PATCH 035/171] Minor tweaks to the CC32xx WiFi driver to allow better setting of the AP configuration. (#437) --- .../net_cc32xx/CC32xxWiFi.cxx | 27 ++++++++++++++++++- .../net_cc32xx/CC32xxWiFi.hxx | 15 ++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx index 3c2494de1..7076754c3 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx @@ -771,7 +771,8 @@ void CC32xxWiFi::wlan_setup_ap(const char *ssid, const char *security_key, sl_WlanSet(SL_WLAN_CFG_AP_ID, SL_WLAN_AP_OPT_SECURITY_TYPE, 1, (uint8_t*)&sec_type); - if (sec_type == SL_WLAN_SEC_TYPE_OPEN) + if (sec_type == SL_WLAN_SEC_TYPE_OPEN || + security_key == nullptr) { return; } @@ -780,6 +781,30 @@ void CC32xxWiFi::wlan_setup_ap(const char *ssid, const char *security_key, strlen(security_key), (uint8_t*)security_key); } +/* + * CC32xxWiFi::wlan_get_ap_config() + */ +void CC32xxWiFi::wlan_get_ap_config(string *ssid, SecurityType *security_type) +{ + if (ssid) + { + // Reads AP SSID configuration from NWP. + ssid->clear(); + ssid->resize(33); + uint16_t len = ssid->size(); + uint16_t config_opt = SL_WLAN_AP_OPT_SSID; + sl_WlanGet(SL_WLAN_CFG_AP_ID, &config_opt, &len, (_u8*) &(*ssid)[0]); + ssid->resize(len); + } + if (security_type) + { + uint16_t len = sizeof(*security_type); + uint16_t config_opt = SL_WLAN_AP_OPT_SECURITY_TYPE; + sl_WlanGet(SL_WLAN_CFG_AP_ID, &config_opt, &len, (_u8*) security_type); + } +} + + void CC32xxWiFi::connecting_update_blinker() { if (!connected) diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx index d64cbf322..ae52944dd 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx @@ -44,6 +44,7 @@ #include "freertos_drivers/common/WifiDefs.hxx" class CC32xxSocket; +class NetworkSpace; /** Interface that aids in unit testing. */ @@ -211,6 +212,12 @@ public: void wlan_setup_ap(const char *ssid, const char *security_key, SecurityType security_type) override; + /** Retrieve current AP config. + * @param ssid will be filled with the SSID of the AP + * @param security_type will be filled with the security type + */ + void wlan_get_ap_config(string *ssid, SecurityType *security_type); + /** @return true if the wlan interface is ready to establish outgoing * connections. */ bool wlan_ready() @@ -488,24 +495,26 @@ public: static std::string get_version(); private: + friend class ::NetworkSpace; + /** Translates the SecurityType enum to the internal SimpleLink code. * @param sec_type security type * @return simplelink security type */ - uint8_t security_type_to_simplelink(SecurityType sec_type); + static uint8_t security_type_to_simplelink(SecurityType sec_type); /** Translates the SimpleLink code to SecurityType enum. * @param sec_type simplelink security type * @return security type */ - SecurityType security_type_from_simplelink(uint8_t sec_type); + static SecurityType security_type_from_simplelink(uint8_t sec_type); /** Translates the SimpleLink code from the network scan to SecurityType * enum. * @param sec_type simplelink network scan security result * @return security type */ - SecurityType security_type_from_scan(unsigned sec_type); + static SecurityType security_type_from_scan(unsigned sec_type); /** Set the CC32xx to its default state, including station mode. */ From 1721b05ed756c89625a9d2d7b5246915485f6a37 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 29 Sep 2020 20:49:08 +0200 Subject: [PATCH 036/171] Adds activity LED support. (#438) * Adds activity LED support. Adds a hook into the openlcb interface that calls a user supplied function whenever a message is transmitted to the network. Adds a simple implementaiton for this hook that flashes an LED when there is bus activity originated from this node. * Adds comments and parametrizes the period of activity LED. --- src/openlcb/If.hxx | 21 ++++++++- src/openlcb/SimpleStack.hxx | 12 +++++ src/utils/ActivityLed.hxx | 88 +++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 src/utils/ActivityLed.hxx diff --git a/src/openlcb/If.hxx b/src/openlcb/If.hxx index 4dd3ee4fa..c0b82c516 100644 --- a/src/openlcb/If.hxx +++ b/src/openlcb/If.hxx @@ -282,12 +282,20 @@ public: MessageHandler *global_message_write_flow() { HASSERT(globalWriteFlow_); + if (txHook_) + { + txHook_(); + } return globalWriteFlow_; } /** @return Flow to send addressed messages to the NMRAnet bus. */ MessageHandler *addressed_message_write_flow() { HASSERT(addressedWriteFlow_); + if (txHook_) + { + txHook_(); + } return addressedWriteFlow_; } @@ -399,7 +407,15 @@ public: * the interface holds internally. Noop for TCP interface. Must be called * on the interface executor. */ virtual void canonicalize_handle(NodeHandle *h) {} - + + /// Sets a transmit hook. This function will be called once for every + /// OpenLCB message transmitted. Used for implementing activity LEDs. + /// @param hook function to call for each transmit message. + void set_tx_hook(std::function hook) + { + txHook_ = std::move(hook); + } + protected: void remove_local_node_from_map(Node *node) { auto it = localNodes_.find(node->node_id()); @@ -416,6 +432,9 @@ private: /// Flow responsible for routing incoming messages to handlers. MessageDispatchFlow dispatcher_; + /// This function is pinged every time a message is transmitted. + std::function txHook_; + typedef Map VNodeMap; /// Local virtual nodes registered on this interface. diff --git a/src/openlcb/SimpleStack.hxx b/src/openlcb/SimpleStack.hxx index 5adb6a2b5..6a5c67f2d 100644 --- a/src/openlcb/SimpleStack.hxx +++ b/src/openlcb/SimpleStack.hxx @@ -54,6 +54,7 @@ #include "openlcb/SimpleNodeInfo.hxx" #include "openlcb/TractionTrain.hxx" #include "openlcb/TrainInterface.hxx" +#include "utils/ActivityLed.hxx" #include "utils/GcTcpHub.hxx" #include "utils/GridConnectHub.hxx" #include "utils/HubDevice.hxx" @@ -153,6 +154,17 @@ public: return &configUpdateFlow_; } + /// Adds an activiy LED which will be flashed every time a message is sent + /// from this node to the network. + /// @param gpio LED that will be flashed on for each packet. + /// @param period defines in nanosecond the time to spend between updates. + void set_tx_activity_led( + const Gpio *led, long long period = MSEC_TO_NSEC(33)) + { + auto *al = new ActivityLed(iface(), led, period); + iface()->set_tx_hook(std::bind(&ActivityLed::activity, al)); + } + /// Reinitializes the node. Useful to call after the connection has flapped /// (gone down and up). void restart_stack(); diff --git a/src/utils/ActivityLed.hxx b/src/utils/ActivityLed.hxx new file mode 100644 index 000000000..0e78cd145 --- /dev/null +++ b/src/utils/ActivityLed.hxx @@ -0,0 +1,88 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file ActivityLed.hxx + * + * State flow that controls an activity LED based on triggers of events. + * + * @author Balazs Racz + * @date 26 Sep 2020 + */ + +#ifndef _UTILS_ACTIVITYLED_HXX_ +#define _UTILS_ACTIVITYLED_HXX_ + +#include "executor/StateFlow.hxx" +#include "os/Gpio.hxx" + +/// Operates an LED to visually display some activity. When the activity() +/// function is called at least once within a period, we turn the LED on for +/// the next period, if no call was made, the LED turns off for the next +/// period. +class ActivityLed : private ::Timer +{ +public: + /// Constructor. + /// @param service defines which executor this timer should be running on. + /// @param pin the LED of the output. Will be high for activity, low for + /// inactivity. Use InvertedGPIO if needed. + /// @param period defines in nanosecond the time to spend between updates. + ActivityLed( + Service *service, const Gpio *pin, long long period = MSEC_TO_NSEC(33)) + : ::Timer(service->executor()->active_timers()) + , gpio_(pin) + { + start(period); + } + + /// Call this function when activity happens. + void activity() + { + triggerCount_++; + } + +private: + long long timeout() override + { + if (triggerCount_) + { + gpio_->write(true); + triggerCount_ = 0; + } + else + { + gpio_->write(false); + } + return RESTART; + } + + /// Output pin to blink for activity. + const Gpio *gpio_; + /// How many triggers happened since the last run of the timer. + unsigned triggerCount_ {0}; +}; + +#endif // _UTILS_ACTIVITYLED_HXX_ From b715b9b24efb58fffe30a80b10fb690a8f9854d3 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 7 Oct 2020 18:39:22 +0200 Subject: [PATCH 037/171] Fixes compile errors on bracz-railcom board. --- boards/bracz-railcom/HwInit.cxx | 2 +- boards/bracz-railcom/hardware.hxx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/boards/bracz-railcom/HwInit.cxx b/boards/bracz-railcom/HwInit.cxx index 56808c353..27b8ce840 100644 --- a/boards/bracz-railcom/HwInit.cxx +++ b/boards/bracz-railcom/HwInit.cxx @@ -99,7 +99,7 @@ static TivaCan can0("/dev/can0", CAN0_BASE, INT_RESOLVE(INT_CAN0_, 0)); const unsigned TivaEEPROMEmulation::FAMILY = TM4C123; const size_t EEPROMEmulation::SECTOR_SIZE = 4 * 1024; -static TivaEEPROMEmulation eeprom("/dev/eeprom", 1024); +static TivaEEPROMEmulation eeprom("/dev/eeprom", 1524); const uint32_t RailcomDefs::UART_BASE[] = RAILCOM_BASE; const uint32_t RailcomDefs::UART_PERIPH[] = RAILCOM_PERIPH; diff --git a/boards/bracz-railcom/hardware.hxx b/boards/bracz-railcom/hardware.hxx index 215fa5d2d..cb4a6b284 100644 --- a/boards/bracz-railcom/hardware.hxx +++ b/boards/bracz-railcom/hardware.hxx @@ -15,6 +15,7 @@ #include "BlinkerGPIO.hxx" #include "utils/Debouncer.hxx" +#define HARDWARE_REVA GPIO_PIN(SW1, GpioInputPU, F, 4); GPIO_PIN(SW2, GpioInputPU, F, 0); From 83a76c3cfc485d0494744ec9c9be85e9ef78006c Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 8 Oct 2020 13:44:30 +0200 Subject: [PATCH 038/171] Fixes deletion order in CC32xx socket shutdown. (#442) This change ensures that threads higher priority than the closing call have a chance to exit their kernel processing before the socket object gets deleted. This makes https://github.com/bakerstu/openmrn/issues/440 slightly better. --- src/freertos_drivers/net_cc32xx/CC32xxSocket.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/freertos_drivers/net_cc32xx/CC32xxSocket.cxx b/src/freertos_drivers/net_cc32xx/CC32xxSocket.cxx index 276b3cd31..51ae25256 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxSocket.cxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxSocket.cxx @@ -755,8 +755,8 @@ int CC32xxSocket::close(File *file) portENTER_CRITICAL(); remove_instance_from_sd(sd); portEXIT_CRITICAL(); - delete this; sl_Close(sd); + delete this; } else { From abba7dd7da953990568f0d5eaa83701de30d0c8f Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 8 Oct 2020 13:49:04 +0200 Subject: [PATCH 039/171] Adds an intermediate stage in closing an fd. (#441) * Adds an intermediate stage in closing an fd. Adds a new bit to mark an fd being in shutdown. This is a state during close that marks the fd as being bad, with all kernel calls (from other threads) returning EBADF if they refer to this fd. At the same time the fd is not marked as free for reuse by another open. This fixes the race condition in https://github.com/bakerstu/openmrn/issues/440 * Fix bug when dev->close returns an error. --- src/freertos_drivers/common/Device.cxx | 2 ++ src/freertos_drivers/common/Devtab.hxx | 1 + src/freertos_drivers/common/Fileio.cxx | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/freertos_drivers/common/Device.cxx b/src/freertos_drivers/common/Device.cxx index 5530fdab9..8b0347945 100644 --- a/src/freertos_drivers/common/Device.cxx +++ b/src/freertos_drivers/common/Device.cxx @@ -144,9 +144,11 @@ int Device::close(struct _reent *reent, int fd) // stdin, stdout, and stderr never get closed return 0; } + files[fd].inshdn = true; int result = f->dev->close(f); if (result < 0) { + files[fd].inshdn = false; errno = -result; return -1; } diff --git a/src/freertos_drivers/common/Devtab.hxx b/src/freertos_drivers/common/Devtab.hxx index 76b062336..c807ed58f 100644 --- a/src/freertos_drivers/common/Devtab.hxx +++ b/src/freertos_drivers/common/Devtab.hxx @@ -62,6 +62,7 @@ struct File off_t offset; /**< current offset within file */ int flags; /**< open flags */ uint8_t inuse : 1; /**< true if this is an open fd. */ + uint8_t inshdn : 1; /**< true if this fd is in shutdown. */ uint8_t device : 1; /**< true if this is a device, false if file system */ uint8_t dir : 1; /**< true if this is a directory, else false */ uint8_t dirty : 1; /**< true if this file is dirty and needs flush */ diff --git a/src/freertos_drivers/common/Fileio.cxx b/src/freertos_drivers/common/Fileio.cxx index 2ebd83206..d6c64b692 100644 --- a/src/freertos_drivers/common/Fileio.cxx +++ b/src/freertos_drivers/common/Fileio.cxx @@ -51,6 +51,7 @@ int FileIO::fd_alloc(void) if (files[i].inuse == false) { files[i].inuse = true; + files[i].inshdn = false; files[i].device = true; files[i].dir = false; files[i].dirty = false; @@ -85,7 +86,7 @@ File* FileIO::file_lookup(int fd) errno = EBADF; return nullptr; } - if (files[fd].inuse == 0) + if (files[fd].inuse == 0 || files[fd].inshdn == 1) { errno = EBADF; return nullptr; From 7f67efb08cf3b8b18c07ea268cea7f5291c945dd Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 8 Oct 2020 13:59:34 +0200 Subject: [PATCH 040/171] Adds railcom driver middle-cutout hook (#439) Adds a hook to the Tiva railcom driver that calls a hardware-specific function in the middle of the cutout, between the two windows. --- boards/bracz-railcom/hardware.hxx | 1 + boards/ti-bracz-acc3/hardware.v3.hxx | 3 ++- boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx | 1 + src/freertos_drivers/ti/TivaRailcom.hxx | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/boards/bracz-railcom/hardware.hxx b/boards/bracz-railcom/hardware.hxx index cb4a6b284..585cf19c3 100644 --- a/boards/bracz-railcom/hardware.hxx +++ b/boards/bracz-railcom/hardware.hxx @@ -179,6 +179,7 @@ struct RailcomDefs static bool need_ch1_cutout() { return true; } + static void middle_cutout_hook() {} static void enable_measurement(bool); static void disable_measurement(); diff --git a/boards/ti-bracz-acc3/hardware.v3.hxx b/boards/ti-bracz-acc3/hardware.v3.hxx index fe875b6ea..414315132 100644 --- a/boards/ti-bracz-acc3/hardware.v3.hxx +++ b/boards/ti-bracz-acc3/hardware.v3.hxx @@ -174,7 +174,8 @@ struct RailcomHw static bool need_ch1_cutout() { return true; } static uint8_t get_feedback_channel() { return 0xff; } - + static void middle_cutout_hook() {} + /// @returns a bitmask telling which pins are active. Bit 0 will be set if /// channel 0 is active (drawing current). static uint8_t sample() { diff --git a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx index 4a113c0a2..15f7b211a 100644 --- a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx +++ b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx @@ -172,6 +172,7 @@ struct RailcomDefs static void disable_measurement() {} static bool need_ch1_cutout() { return true; } static uint8_t get_feedback_channel() { return 0xff; } + static void middle_cutout_hook() {} /** @returns a bitmask telling which pins are active. Bit 0 will be set if * channel 0 is active (drawing current).*/ diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx index 19657bcef..b4d75ba81 100644 --- a/src/freertos_drivers/ti/TivaRailcom.hxx +++ b/src/freertos_drivers/ti/TivaRailcom.hxx @@ -394,6 +394,7 @@ private: } HWREG(HW::UART_BASE[i] + UART_O_CTL) |= UART_CTL_RXE; } + HW::middle_cutout_hook(); Debug::RailcomDriverCutout::set(true); } From 21c97b496e462f39bdf4ddf8426ea2fb071e3395 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 11 Oct 2020 10:13:23 +0200 Subject: [PATCH 041/171] Adds a stride scheduler to split bandwidth between two sources of traffic. (#443) --- src/utils/BandwidthMerger.cxxtest | 92 +++++++++++++++++++++++++++++++ src/utils/BandwidthMerger.hxx | 89 ++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 src/utils/BandwidthMerger.cxxtest create mode 100644 src/utils/BandwidthMerger.hxx diff --git a/src/utils/BandwidthMerger.cxxtest b/src/utils/BandwidthMerger.cxxtest new file mode 100644 index 000000000..71720c578 --- /dev/null +++ b/src/utils/BandwidthMerger.cxxtest @@ -0,0 +1,92 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BandwidthMerger.cxxtest + * + * Unit tests for the stride scheduler. + * + * @author Balazs Racz + * @date 10 Oct 2020 + */ + +#include "trains/BandwidthMerger.hxx" + +#include "utils/test_main.hxx" + +class BandwidthMergerTest : public ::testing::Test +{ +protected: + /// Runs a simulation with a certain number of steps with a bandwidth + /// merger. + void collect_stats(uint8_t percent, unsigned num_steps) + { + BandwidthMerger m {percent}; + results_[0] = results_[1] = 0; + for (unsigned i = 0; i < num_steps; ++i) + { + if (m.step()) + { + ++results_[1]; + } + else + { + ++results_[0]; + } + } + } + + /// Results of the simulation. [0] is the number of steps with false + /// output, [1] is the number of steps with true output. + unsigned results_[2]; +}; + +TEST_F(BandwidthMergerTest, fifty) +{ + collect_stats(50, 34); + EXPECT_EQ(17u, results_[0]); + EXPECT_EQ(17u, results_[1]); +} + +TEST_F(BandwidthMergerTest, hundred) +{ + collect_stats(100, 34); + EXPECT_EQ(0u, results_[0]); + EXPECT_EQ(34u, results_[1]); +} + +TEST_F(BandwidthMergerTest, zero) +{ + collect_stats(0, 34); + EXPECT_EQ(34u, results_[0]); + EXPECT_EQ(0u, results_[1]); +} + +TEST_F(BandwidthMergerTest, ten) +{ + collect_stats(10, 34); + EXPECT_EQ(31u, results_[0]); + EXPECT_EQ(3u, results_[1]); +} diff --git a/src/utils/BandwidthMerger.hxx b/src/utils/BandwidthMerger.hxx new file mode 100644 index 000000000..096fd1402 --- /dev/null +++ b/src/utils/BandwidthMerger.hxx @@ -0,0 +1,89 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BandwidthMerger.hxx + * + * Simple stride scheduler for splitting bandwidth. + * + * @author Balazs Racz + * @date 10 Oct 2020 + */ + +#ifndef _TRAINS_BANDWIDTHMERGER_HXX_ +#define _TRAINS_BANDWIDTHMERGER_HXX_ + +#include + +#include "utils/macros.h" + +/// Simple stride scheduler. Emulates a two-way split of bandwidth between a +/// first source and a second source. The percentage assigned to the first +/// source is a parameter. Over time the fraction of outputs selected from the +/// first source converges to this percentage. +struct BandwidthMerger +{ + /// Constructor. + /// @param percent is the percentage (0..100) of the bandwidth that + /// should be assigned to the first source. + BandwidthMerger(uint8_t percent) + : percentFirst_(percent) + { + HASSERT(percentFirst_ <= 100); + } + /// Runs one step. + /// @return true if the first source is selected in this step. + bool step() + { + currentState_ += percentFirst_; + if (currentState_ >= 100) + { + currentState_ -= 100; + return true; + } + return false; + } + + /// If the step function returned false, but still the first source was + /// taken (e.g. because the second source was empty), call this function to + /// clear the state. This will avoid selecting the first source twice in a + /// row for example. + void reset() + { + // Reduces the state by 100 but clips it to zero. Since the state is + // always < 100, the clipping will always win. + currentState_ = 0; + } + + /// State of the current stride. This is always between 0..99. It + /// represents the fractional steps that the first source has accumulated + /// but not paid out in the form of selections yet. + uint8_t currentState_ {0}; + /// Percentage of the bandwidth that should be assigned to the first + /// source. Range 0..100. + uint8_t percentFirst_; +}; + +#endif // _TRAINS_BANDWIDTHMERGER_HXX_ From be0cc332b0265af66b63721bb3c9180ce5a55158 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 11 Oct 2020 10:28:44 +0200 Subject: [PATCH 042/171] Fix incorrect includes (#444) * Adds a stride scheduler to split bandwidth between two sources of traffic. * Fix incrrect includes. --- src/utils/BandwidthMerger.cxxtest | 2 +- src/utils/BandwidthMerger.hxx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/BandwidthMerger.cxxtest b/src/utils/BandwidthMerger.cxxtest index 71720c578..a7fa83578 100644 --- a/src/utils/BandwidthMerger.cxxtest +++ b/src/utils/BandwidthMerger.cxxtest @@ -32,7 +32,7 @@ * @date 10 Oct 2020 */ -#include "trains/BandwidthMerger.hxx" +#include "utils/BandwidthMerger.hxx" #include "utils/test_main.hxx" diff --git a/src/utils/BandwidthMerger.hxx b/src/utils/BandwidthMerger.hxx index 096fd1402..caf32a2d4 100644 --- a/src/utils/BandwidthMerger.hxx +++ b/src/utils/BandwidthMerger.hxx @@ -32,8 +32,8 @@ * @date 10 Oct 2020 */ -#ifndef _TRAINS_BANDWIDTHMERGER_HXX_ -#define _TRAINS_BANDWIDTHMERGER_HXX_ +#ifndef _UTILS_BANDWIDTHMERGER_HXX_ +#define _UTILS_BANDWIDTHMERGER_HXX_ #include @@ -86,4 +86,4 @@ struct BandwidthMerger uint8_t percentFirst_; }; -#endif // _TRAINS_BANDWIDTHMERGER_HXX_ +#endif // _UTILS_BANDWIDTHMERGER_HXX_ From 594910d005d501b4e9b3c405ae792b5405100773 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 12 Oct 2020 18:19:07 +0200 Subject: [PATCH 043/171] Simplifies the synchronous run test utility. --- src/utils/test_main.hxx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils/test_main.hxx b/src/utils/test_main.hxx index 2504ec543..be1f1a19c 100644 --- a/src/utils/test_main.hxx +++ b/src/utils/test_main.hxx @@ -174,9 +174,7 @@ private: /// Synchronously runs a function in the main executor. void run_x(std::function fn) { - FnExecutable e(std::move(fn)); - g_executor.add(&e); - e.n.wait_for_notification(); + g_executor.sync_run(std::move(fn)); } /** Utility class to block an executor for a while. From 729062067971f0b2bfb2e1b816474b2ac1074c95 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 17 Oct 2020 17:38:55 +0200 Subject: [PATCH 044/171] Always report from railcom driver (#445) Ensures that the railcom driver returns data from the feedback channel even if there was no cutout generated. --- src/freertos_drivers/common/RailcomDriver.hxx | 4 ++++ src/freertos_drivers/ti/TivaDCC.hxx | 3 +++ src/freertos_drivers/ti/TivaRailcom.hxx | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/freertos_drivers/common/RailcomDriver.hxx b/src/freertos_drivers/common/RailcomDriver.hxx index fbda26b6b..c09bbec3a 100644 --- a/src/freertos_drivers/common/RailcomDriver.hxx +++ b/src/freertos_drivers/common/RailcomDriver.hxx @@ -58,6 +58,9 @@ public: /** Instructs the driver that the railcom cutout is over now. The driver * will use this information to disable the UART receiver. */ virtual void end_cutout() = 0; + /** Called instead of start/mid/end-cutout at the end of the current packet + * if there was no cutout requested. */ + virtual void no_cutout() = 0; /** Specifies the feedback key to write into the received railcom data * packets. This feedback key is used by the application layer to correlate * the stream of DCC packets to the stream of Railcom packets. This method @@ -74,6 +77,7 @@ class NoRailcomDriver : public RailcomDriver { void start_cutout() OVERRIDE {} void middle_cutout() OVERRIDE {} void end_cutout() OVERRIDE {} + void no_cutout() OVERRIDE {} void set_feedback_key(uint32_t key) OVERRIDE {} }; diff --git a/src/freertos_drivers/ti/TivaDCC.hxx b/src/freertos_drivers/ti/TivaDCC.hxx index d3c3c8ead..454cde429 100644 --- a/src/freertos_drivers/ti/TivaDCC.hxx +++ b/src/freertos_drivers/ti/TivaDCC.hxx @@ -546,6 +546,7 @@ inline void TivaDCC::interrupt_handler() } else { + railcomDriver_->no_cutout(); current_bit = DCC_ONE; state_ = DCC_LEADOUT; } @@ -697,6 +698,8 @@ inline void TivaDCC::interrupt_handler() } break; case MM_LEADOUT: + // MM packets never have a cutout. + railcomDriver_->no_cutout(); current_bit = MM_PREAMBLE; if (++preamble_count >= 2) { get_next_packet = true; diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx index b4d75ba81..dcf0f2473 100644 --- a/src/freertos_drivers/ti/TivaRailcom.hxx +++ b/src/freertos_drivers/ti/TivaRailcom.hxx @@ -440,6 +440,24 @@ private: Debug::RailcomCh2Data::set(false); Debug::RailcomDriverCutout::set(false); } + + void no_cutout() OVERRIDE + { + for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) + { + if (!returnedPackets_[i]) + { + returnedPackets_[i] = this->alloc_new_packet(i); + } + if (returnedPackets_[i]) + { + this->feedbackQueue_.commit_back(); + Debug::RailcomPackets::toggle(); + returnedPackets_[i] = nullptr; + MAP_IntPendSet(HW::OS_INTERRUPT); + } + } + } }; #endif // _FREERTOS_DRIVERS_TI_TIVARAILCOM_HXX_ From 7f4522d3c1b68ca8856874d5c690680088394632 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 17 Oct 2020 17:50:30 +0200 Subject: [PATCH 045/171] Adds an accessor to fetch the number of connected stations. (#446) * Adds an accessor to fetch the number of connected stations. * fix whitespace --- src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx | 16 ++++++++++++++++ src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx | 6 ++++++ 2 files changed, 22 insertions(+) diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx index 7076754c3..2abd43825 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx @@ -804,6 +804,22 @@ void CC32xxWiFi::wlan_get_ap_config(string *ssid, SecurityType *security_type) } } +int CC32xxWiFi::wlan_get_ap_station_count() +{ + if (wlanRole != WlanRole::AP) + { + return 0; + } + uint8_t num_connected = 0; + uint16_t len = sizeof(num_connected); + auto status = sl_NetCfgGet( + SL_NETCFG_AP_STATIONS_NUM_CONNECTED, NULL, &len, &num_connected); + if (status) + { + return -1; + } + return num_connected; +} void CC32xxWiFi::connecting_update_blinker() { diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx index ae52944dd..2a2b8fd50 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx @@ -218,6 +218,12 @@ public: */ void wlan_get_ap_config(string *ssid, SecurityType *security_type); + /** Retrieves how many stations are connected to the wifi in AP mode. + * @return number of connected stations (0 to 4). If not in AP mode, + * returns 0. + */ + int wlan_get_ap_station_count(); + /** @return true if the wlan interface is ready to establish outgoing * connections. */ bool wlan_ready() From 0e2303aad32038f82534836d4687fe610a6f3065 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 17 Oct 2020 23:13:55 +0200 Subject: [PATCH 046/171] Keeps track of the number of connected clients (#448) Keeps track of the number of connected clients in the GridConnect TCP hub. Conversation is in #447. * Adds an accessor to fetch the number of connected stations. * fix whitespace * Keeps track of the number of connected clients in the GridConnect TCP hub. * fix whitespace * Fix typo --- src/openlcb/SimpleStack.hxx | 14 ++++++++++++-- src/utils/GcTcpHub.cxx | 21 +++++++++++++++++---- src/utils/GcTcpHub.cxxtest | 7 +++++++ src/utils/GcTcpHub.hxx | 16 ++++++++++++++-- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/openlcb/SimpleStack.hxx b/src/openlcb/SimpleStack.hxx index 6a5c67f2d..fe37aa76e 100644 --- a/src/openlcb/SimpleStack.hxx +++ b/src/openlcb/SimpleStack.hxx @@ -383,8 +383,15 @@ public: { /// @TODO (balazs.racz) make this more efficient by rendering to string /// only once for all connections. - /// @TODO (balazs.racz) do not leak this. - new GcTcpHub(can_hub(), port); + gcHubServer_.reset(new GcTcpHub(can_hub(), port)); + } + + /// Retrieve the instance of the GridConnect Hub server, which was started + /// with start_tcp_hub_server(). + /// @return the TCP hub server, or nullptr if no server was ever started. + GcTcpHub *get_tcp_hub_server() + { + return gcHubServer_.get(); } /// Connects to a CAN hub using TCP with the gridconnect protocol. @@ -473,6 +480,9 @@ private: /// the CAN interface to function. Will be called exactly once by the /// constructor of the base class. std::unique_ptr create_if(const openlcb::NodeID node_id); + + /// Holds the ownership of the TCP hub server (if one was created). + std::unique_ptr gcHubServer_; }; class SimpleTcpStackBase : public SimpleStackBase diff --git a/src/utils/GcTcpHub.cxx b/src/utils/GcTcpHub.cxx index 6f94a510a..99ec6e1fe 100644 --- a/src/utils/GcTcpHub.cxx +++ b/src/utils/GcTcpHub.cxx @@ -38,17 +38,30 @@ #include "nmranet_config.h" #include "utils/GridConnectHub.hxx" -void GcTcpHub::OnNewConnection(int fd) +void GcTcpHub::on_new_connection(int fd) { const bool use_select = (config_gridconnect_tcp_use_select() == CONSTANT_TRUE); - create_gc_port_for_can_hub(canHub_, fd, nullptr, use_select); + { + AtomicHolder h(this); + numClients_++; + } + create_gc_port_for_can_hub(canHub_, fd, this, use_select); +} + +void GcTcpHub::notify() +{ + AtomicHolder h(this); + if (numClients_) + { + numClients_--; + } } GcTcpHub::GcTcpHub(CanHubFlow *can_hub, int port) : canHub_(can_hub) - , tcpListener_(port, std::bind(&GcTcpHub::OnNewConnection, this, - std::placeholders::_1)) + , tcpListener_(port, + std::bind(&GcTcpHub::on_new_connection, this, std::placeholders::_1)) { } diff --git a/src/utils/GcTcpHub.cxxtest b/src/utils/GcTcpHub.cxxtest index e8a1f6ba5..d24c6a8f3 100644 --- a/src/utils/GcTcpHub.cxxtest +++ b/src/utils/GcTcpHub.cxxtest @@ -64,6 +64,7 @@ protected: fprintf(stderr, "waiting for exiting.\r"); usleep(100000); } + EXPECT_EQ(0U, tcpHub_.get_num_clients()); } struct Client @@ -154,6 +155,7 @@ protected: TEST_F(GcTcpHubTest, CreateDestroy) { + EXPECT_EQ(0u, tcpHub_.get_num_clients()); } TEST_F(GcTcpHubTest, TwoClientsPingPong) @@ -165,6 +167,9 @@ TEST_F(GcTcpHubTest, TwoClientsPingPong) writeline(b.fd_, ":S001N01;"); EXPECT_EQ(":S001N01;", readline(a.fd_, ';')); EXPECT_EQ(3U, can_hub0.size()); + + EXPECT_EQ(2u, tcpHub_.get_num_clients()); + // Test writing outwards. send_packet(":S002N0102;"); EXPECT_EQ(":S002N0102;", readline(a.fd_, ';')); @@ -177,6 +182,7 @@ TEST_F(GcTcpHubTest, ClientCloseExpect) unsigned can_hub_size = can_hub0.size(); LOG(INFO, "can hub: %p ", &can_hub0); EXPECT_EQ(1U, can_hub_size); + EXPECT_EQ(0U, tcpHub_.get_num_clients()); { Client a; Client b; @@ -184,6 +190,7 @@ TEST_F(GcTcpHubTest, ClientCloseExpect) writeline(b.fd_, ":S001N01;"); EXPECT_EQ(":S001N01;", readline(a.fd_, ';')); EXPECT_EQ(can_hub_size + 2, can_hub0.size()); + EXPECT_EQ(2U, tcpHub_.get_num_clients()); wait(); } // Test writing outwards. diff --git a/src/utils/GcTcpHub.hxx b/src/utils/GcTcpHub.hxx index f81d55bbf..04888ed1f 100644 --- a/src/utils/GcTcpHub.hxx +++ b/src/utils/GcTcpHub.hxx @@ -43,7 +43,7 @@ class ExecutorBase; * format. Any new incoming connection will be wired into the same virtual CAN * hub. All packets will be forwarded to every participant, without * loopback. */ -class GcTcpHub +class GcTcpHub : private Notifiable, private Atomic { public: /// Constructor. @@ -60,16 +60,28 @@ public: return tcpListener_.is_started(); } + /// @return currently connected client count. + unsigned get_num_clients() + { + return numClients_; + } + private: /// Callback when a new connection arrives. /// /// @param fd filedes of the freshly established incoming connection. /// - void OnNewConnection(int fd); + void on_new_connection(int fd); + + /// Error callback from the gridconnect socket. This is invoked when a + /// client disconnects. + void notify() override; /// @param can_hub Which CAN-hub should we attach the TCP gridconnect hub /// onto. CanHubFlow *canHub_; + /// How many clients are connected right now. + unsigned numClients_ {0}; /// Helper object representing the listening on the socket. SocketListener tcpListener_; }; From be87ee74938fa4e8c092da07d0971d5c46d5fbbc Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 19 Oct 2020 15:22:45 +0200 Subject: [PATCH 047/171] Adds SNIPClient, a helper library for sending SNIP requests to remote nodes. (#451) * Adds SNIPClient, a helper library for sending SNIP requests to remote nodes. Fixes a bug in the parser method used for OIR errors. * fix typo. --- src/openlcb/If.cxx | 4 +- src/openlcb/SNIPClient.cxxtest | 160 ++++++++++++++++++++++++++ src/openlcb/SNIPClient.hxx | 198 +++++++++++++++++++++++++++++++++ 3 files changed, 360 insertions(+), 2 deletions(-) create mode 100644 src/openlcb/SNIPClient.cxxtest create mode 100644 src/openlcb/SNIPClient.hxx diff --git a/src/openlcb/If.cxx b/src/openlcb/If.cxx index 5e15582df..d53d06008 100644 --- a/src/openlcb/If.cxx +++ b/src/openlcb/If.cxx @@ -119,11 +119,11 @@ void buffer_to_error(const Payload &payload, uint16_t *error_code, error_message->clear(); if (payload.size() >= 2 && error_code) { - *error_code = (((uint16_t)payload[0]) << 8) | payload[1]; + *error_code = (((uint16_t)payload[0]) << 8) | (uint8_t)payload[1]; } if (payload.size() >= 4 && mti) { - *mti = (((uint16_t)payload[2]) << 8) | payload[3]; + *mti = (((uint16_t)payload[2]) << 8) | (uint8_t)payload[3]; } if (payload.size() > 4 && error_message) { diff --git a/src/openlcb/SNIPClient.cxxtest b/src/openlcb/SNIPClient.cxxtest new file mode 100644 index 000000000..c921df941 --- /dev/null +++ b/src/openlcb/SNIPClient.cxxtest @@ -0,0 +1,160 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SNIPClient.cxxtest + * + * Unit test for SNIP client library. + * + * @author Balazs Racz + * @date 18 Oct 2020 + */ + +static long long snipTimeout = 50 * 1000000; +#define SNIP_CLIENT_TIMEOUT_NSEC snipTimeout + +#include "openlcb/SNIPClient.hxx" + +#include "openlcb/SimpleNodeInfo.hxx" +#include "openlcb/SimpleNodeInfoMockUserFile.hxx" +#include "utils/async_if_test_helper.hxx" + +namespace openlcb +{ + +const char *const SNIP_DYNAMIC_FILENAME = MockSNIPUserFile::snip_user_file_path; + +const SimpleNodeStaticValues SNIP_STATIC_DATA = { + 4, "TestingTesting", "Undefined model", "Undefined HW version", "0.9"}; + +class SNIPClientTest : public AsyncNodeTest +{ +protected: + SNIPClientTest() + { + eb_.release_block(); + run_x([this]() { + ifTwo_.alias_allocator()->TEST_add_allocated_alias(0xFF2); + }); + wait(); + } + + ~SNIPClientTest() + { + wait(); + } + + MockSNIPUserFile userFile_ {"Undefined node name", "Undefined node descr"}; + + /// General flow for simple info requests. + SimpleInfoFlow infoFlow_ {ifCan_.get()}; + /// Handles SNIP requests. + SNIPHandler snipHandler_ {ifCan_.get(), node_, &infoFlow_}; + /// The actual client to test. + SNIPClient client_ {ifCan_.get()}; + + // These objects create a second node on the CAN bus (with its own + // interface). + BlockExecutor eb_ {&g_executor}; + static constexpr NodeID TWO_NODE_ID = 0x02010d0000ddULL; + + IfCan ifTwo_ {&g_executor, &can_hub0, local_alias_cache_size, + remote_alias_cache_size, local_node_count}; + AddAliasAllocator alloc_ {TWO_NODE_ID, &ifTwo_}; + DefaultNode nodeTwo_ {&ifTwo_, TWO_NODE_ID}; +}; + +TEST_F(SNIPClientTest, create) +{ +} + +static const char kExpectedData[] = + "\x04TestingTesting\0Undefined model\0Undefined HW version\0" + "0.9\0" + "\x02Undefined node name\0Undefined node descr"; // C adds another \0. + +TEST_F(SNIPClientTest, localhost) +{ + auto b = invoke_flow(&client_, node_, NodeHandle(node_->node_id())); + EXPECT_EQ(0, b->data()->resultCode); + EXPECT_EQ( + string(kExpectedData, sizeof(kExpectedData)), b->data()->response); + + // do another request. + auto bb = invoke_flow(&client_, node_, NodeHandle(node_->node_id())); + EXPECT_EQ(0, bb->data()->resultCode); + EXPECT_EQ( + string(kExpectedData, sizeof(kExpectedData)), bb->data()->response); +} + +TEST_F(SNIPClientTest, remote) +{ + auto b = invoke_flow(&client_, &nodeTwo_, NodeHandle(node_->node_id())); + EXPECT_EQ(0, b->data()->resultCode); + EXPECT_EQ( + string(kExpectedData, sizeof(kExpectedData)), b->data()->response); + + // do another request. + auto bb = invoke_flow(&client_, &nodeTwo_, NodeHandle(node_->node_id())); + EXPECT_EQ(0, bb->data()->resultCode); + EXPECT_EQ( + string(kExpectedData, sizeof(kExpectedData)), bb->data()->response); +} + +TEST_F(SNIPClientTest, timeout) +{ + long long start = os_get_time_monotonic(); + auto b = invoke_flow(&client_, &nodeTwo_, NodeHandle(nodeTwo_.node_id())); + EXPECT_EQ(SNIPClientRequest::OPENMRN_TIMEOUT, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->response.size()); + long long time = os_get_time_monotonic() - start; + EXPECT_LT(MSEC_TO_NSEC(49), time); +} + +TEST_F(SNIPClientTest, reject) +{ + SyncNotifiable n; + auto b = get_buffer_deleter(client_.alloc()); + b->data()->reset(node_, NodeHandle(NodeAlias(0x555))); + b->data()->done.reset(&n); + + expect_packet(":X19DE822AN0555;"); + client_.send(b.get()); + wait(); + clear_expect(true); + EXPECT_EQ(SNIPClientRequest::OPERATION_PENDING, b->data()->resultCode); + + send_packet(":X19068555N022A209905EB;"); + wait(); + EXPECT_EQ(SNIPClientRequest::OPERATION_PENDING, b->data()->resultCode); + + send_packet(":X19068555N022A20990DE8;"); + wait(); + EXPECT_EQ(SNIPClientRequest::ERROR_REJECTED | 0x2099, b->data()->resultCode); + n.wait_for_notification(); + +} + +} // namespace openlcb diff --git a/src/openlcb/SNIPClient.hxx b/src/openlcb/SNIPClient.hxx new file mode 100644 index 000000000..4d87ab99c --- /dev/null +++ b/src/openlcb/SNIPClient.hxx @@ -0,0 +1,198 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SNIPClient.hxx + * + * A client library for talking to an arbitrary openlcb Node and ask it for the + * Simple Node Ident Info data. + * + * @author Balazs Racz + * @date 18 Oct 2020 + */ + +#ifndef _OPENLCB_SNIPCLIENT_HXX_ +#define _OPENLCB_SNIPCLIENT_HXX_ + +#include "executor/CallableFlow.hxx" +#include "openlcb/Defs.hxx" +#include "openlcb/If.hxx" + +namespace openlcb +{ + +/// Buffer contents for invoking the SNIP client. +struct SNIPClientRequest : public CallableFlowRequestBase +{ + /// Helper function for invoke_subflow. + /// @param src the openlcb node to call from + /// @param dst the openlcb node to target + void reset(Node *src, NodeHandle dst) + { + reset_base(); + resultCode = OPERATION_PENDING; + src_ = src; + dst_ = dst; + } + + enum + { + OPERATION_PENDING = 0x20000, //< cleared when done is called. + ERROR_REJECTED = 0x200000, //< Target node has rejected the request. + OPENMRN_TIMEOUT = 0x80000, //< Timeout waiting for ack/nack. + }; + + /// Source node where to send the request from. + Node *src_; + /// Destination node to query. + NodeHandle dst_; + /// Response payload if successful. + Payload response; +}; + +#if !defined(GTEST) || !defined(SNIP_CLIENT_TIMEOUT_NSEC) +/// Specifies how long to wait for a SNIP request to get a response. Writable +/// for unittesting purposes. +static constexpr long long SNIP_CLIENT_TIMEOUT_NSEC = MSEC_TO_NSEC(1500); +#endif + +class SNIPClient : public CallableFlow +{ +public: + /// Constructor. + /// @param s service of the openlcb executor. + SNIPClient(Service *s) + : CallableFlow(s) + { + } + + Action entry() override + { + request()->resultCode = SNIPClientRequest::OPERATION_PENDING; + return allocate_and_call( + iface()->addressed_message_write_flow(), STATE(write_request)); + } + +private: + enum + { + MTI_1a = Defs::MTI_TERMINATE_DUE_TO_ERROR, + MTI_1b = Defs::MTI_OPTIONAL_INTERACTION_REJECTED, + MASK_1 = ~(MTI_1a ^ MTI_1b), + MTI_1 = MTI_1a, + + MTI_2 = Defs::MTI_IDENT_INFO_REPLY, + MASK_2 = Defs::MTI_EXACT, + }; + + /// Called once the allocation is complete. Sends out the SNIP request to + /// the bus. + Action write_request() + { + auto *b = + get_allocation_result(iface()->addressed_message_write_flow()); + b->data()->reset(Defs::MTI_IDENT_INFO_REQUEST, + request()->src_->node_id(), request()->dst_, EMPTY_PAYLOAD); + + iface()->dispatcher()->register_handler( + &responseHandler_, MTI_1, MASK_1); + iface()->dispatcher()->register_handler( + &responseHandler_, MTI_2, MASK_2); + + iface()->addressed_message_write_flow()->send(b); + + return sleep_and_call( + &timer_, SNIP_CLIENT_TIMEOUT_NSEC, STATE(response_came)); + } + + /// Callback from the response handler. + /// @param message the incoming response message from the bus + void handle_response(Buffer *message) + { + auto rb = get_buffer_deleter(message); + if (request()->src_ != message->data()->dstNode || + !iface()->matching_node(request()->dst_, message->data()->src)) + { + // Not from the right place. + return; + } + if (message->data()->mti == Defs::MTI_OPTIONAL_INTERACTION_REJECTED || + message->data()->mti == Defs::MTI_TERMINATE_DUE_TO_ERROR) + { + uint16_t mti, error_code; + buffer_to_error( + message->data()->payload, &error_code, &mti, nullptr); + LOG(INFO, "rejection err %04x mti %04x", error_code, mti); + if (mti && mti != Defs::MTI_IDENT_INFO_REQUEST) + { + // Got error response for a different interaction. Ignore. + return; + } + request()->resultCode = + error_code | SNIPClientRequest::ERROR_REJECTED; + } + else if (message->data()->mti == Defs::MTI_IDENT_INFO_REPLY) + { + request()->response = std::move(message->data()->payload); + request()->resultCode = 0; + } + else + { + // Dunno what this MTI is. Ignore. + LOG(INFO, "Unexpected MTI for SNIP response handler: %04x", + message->data()->mti); + return; + } + // Wakes up parent flow. + request()->resultCode &= ~SNIPClientRequest::OPERATION_PENDING; + timer_.trigger(); + } + + Action response_came() + { + iface()->dispatcher()->unregister_handler_all(&responseHandler_); + if (request()->resultCode & SNIPClientRequest::OPERATION_PENDING) + { + return return_with_error(SNIPClientRequest::OPENMRN_TIMEOUT); + } + return return_with_error(request()->resultCode); + } + + /// @return openlcb source interface. + If *iface() + { + return request()->src_->iface(); + } + + /// Handles the timeout feature. + StateFlowTimer timer_ {this}; + /// Registered handler for response messages. + IncomingMessageStateFlow::GenericHandler responseHandler_ { + this, &SNIPClient::handle_response}; +}; + +} // namespace openlcb + +#endif // _OPENLCB_SNIPCLIENT_HXX_ From f6264bc58a6f1a65a1339fe4a6468dd6ae6b9aa9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 19 Oct 2020 16:36:51 +0200 Subject: [PATCH 048/171] Always send back a feedback packet, even if empty. (#452) This PR ensures that the railcom driver sends back at least one feedback packet for each track packet, even if no railcom feedback was received. --- src/freertos_drivers/ti/TivaRailcom.hxx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx index dcf0f2473..ecabfde77 100644 --- a/src/freertos_drivers/ti/TivaRailcom.hxx +++ b/src/freertos_drivers/ti/TivaRailcom.hxx @@ -401,6 +401,7 @@ private: void end_cutout() OVERRIDE { HW::disable_measurement(); + bool have_packets = false; for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) { while (MAP_UARTCharsAvail(HW::UART_BASE[i])) @@ -431,12 +432,25 @@ private: Debug::RailcomRxActivate::set(false); //HWREGBITW(HW::UART_BASE[i] + UART_O_CTL, UART_CTL_RXE) = 0; if (returnedPackets_[i]) { + have_packets = true; this->feedbackQueue_.commit_back(); Debug::RailcomPackets::toggle(); returnedPackets_[i] = nullptr; MAP_IntPendSet(HW::OS_INTERRUPT); } } + if (!have_packets) + { + // Ensures that at least one feedback packet is sent back even when + // it is with no railcom payload. + auto *p = this->alloc_new_packet(0); + if (p) + { + this->feedbackQueue_.commit_back(); + Debug::RailcomPackets::toggle(); + MAP_IntPendSet(HW::OS_INTERRUPT); + } + } Debug::RailcomCh2Data::set(false); Debug::RailcomDriverCutout::set(false); } From 7896fc0fb5dd6aa9da9de95ee650485d5e3cc6ca Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 19 Oct 2020 16:37:10 +0200 Subject: [PATCH 049/171] Adds a progress callback to the memory config client. (#450) --- src/openlcb/MemoryConfigClient.hxx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/openlcb/MemoryConfigClient.hxx b/src/openlcb/MemoryConfigClient.hxx index f306fe40e..9df55f32b 100644 --- a/src/openlcb/MemoryConfigClient.hxx +++ b/src/openlcb/MemoryConfigClient.hxx @@ -83,7 +83,10 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase /// @param ReadCmd polymorphic matching arg; always set to READ. /// @param d is the destination node to query /// @param space is the memory space to read out - void reset(ReadCmd, NodeHandle d, uint8_t space) + /// @param cb if specified, will be called inline multiple times during the + /// processing as more data arrives. + void reset(ReadCmd, NodeHandle d, uint8_t space, + std::function cb = nullptr) { reset_base(); cmd = CMD_READ; @@ -92,6 +95,7 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase address = 0; size = 0xffffffffu; payload.clear(); + progressCb = std::move(cb); } /// Sets up a command to read a part of a memory space. @@ -201,6 +205,17 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase CMD_WRITE, CMD_META_REQUEST }; + + /// Helper function invoked at every other reset call. + void reset_base() + { + CallableFlowRequestBase::reset_base(); + progressCb = nullptr; + payload.clear(); + size = 0; + address = 0; + } + Command cmd; uint8_t memory_space; unsigned address; @@ -208,6 +223,8 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase /// Node to send the request to. NodeHandle dst; string payload; + /// Callback to execute as progress is being made. + std::function progressCb; }; class MemoryConfigClient : public CallableFlow @@ -364,6 +381,10 @@ private: unsigned dlen = len - ofs; request()->payload.append((char *)(bytes + ofs), dlen); offset_ += dlen; + if (request()->progressCb) + { + request()->progressCb(request()); + } if ((dlen < 64) || (request()->size == 0)) { return call_immediately(STATE(finish_read)); From 9e4724691ea79d15cf3f836d461f12aca01e9118 Mon Sep 17 00:00:00 2001 From: Mike Dunston Date: Tue, 20 Oct 2020 04:53:41 -0700 Subject: [PATCH 050/171] Add node brownout and ident event constants (#454) Adds constants for standard events: - Power brownout below required by standard; - Power brownout below required by node; - Node ident button pressed. --- src/openlcb/Defs.hxx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/openlcb/Defs.hxx b/src/openlcb/Defs.hxx index e45ff994d..68f3e734e 100644 --- a/src/openlcb/Defs.hxx +++ b/src/openlcb/Defs.hxx @@ -246,6 +246,21 @@ struct Defs /// station may react to this by restoring locomotive speed settings. static constexpr uint64_t CLEAR_EMERGENCY_STOP_EVENT = 0x010000000000FFFCULL; + /// "Power supply brownout detected below minimum required by standard" + /// This event can be generated when a node detects that the CAN bus power + /// has dropped below the minimum declared in the standard. + static constexpr uint64_t POWER_STANDARD_BROWNOUT_EVENT = 0x010000000000FFF0ULL; + + /// "Power supply brownout detected below minimum required by node" + /// This event can be generated when a node detects that it has + /// insufficient power for normal operations. + static constexpr uint64_t NODE_POWER_BROWNOUT_EVENT = 0x010000000000FFF1ULL; + + /// "Ident button combination pressed" + /// This event can be generated by a node when it is instructed to generate + /// an identification event. + static constexpr uint64_t NODE_IDENT_BUTTON_EVENT = 0x010000000000FE00ULL; + /** Status of the pysical layer link */ enum LinkStatus { From 5e8df0cc58f3d25ed8fa3333a69848d2132346db Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 23 Oct 2020 22:30:50 +0200 Subject: [PATCH 051/171] Revision script fixes (#453) - suppresses the error messages that are currently printed on the output at every run. - adds a command to the hxx output that looks like this: #define REVISION_GIT_HASH "b60e1f7:-d-u" This is suitable for being included into the SNIP version string. --- bin/revision.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/bin/revision.py b/bin/revision.py index f011cd8c0..2cfce0451 100755 --- a/bin/revision.py +++ b/bin/revision.py @@ -93,6 +93,8 @@ outputcxx += ' "' + gcc + '",\n' outputhxx += '"' + gcc + '\\n"\n' +main_git_hash = None + for x in inputs : print x # go into the root of the repo @@ -121,17 +123,19 @@ outputcxx += ':' + os.path.split(os.path.abspath(x))[1] outputhxx += '"' + git_hash + ':' + os.path.split(os.path.abspath(x))[1] + hashopts = "" + if dirty or untracked : - outputcxx += ':' - outputhxx += ':' + hashopts += ':' if dirty : - outputcxx += '-d' - outputhxx += '-d' + hashopts += '-d' if untracked : - outputcxx += '-u' - outputhxx += '-u' - outputcxx += '",\n' - outputhxx += '\\n"\n' + hashopts += '-u' + outputcxx += hashopts + '",\n' + outputhxx += hashopts + '\\n"\n' + + if main_git_hash is None: + main_git_hash = git_hash + hashopts outputcxx += ' nullptr\n' outputcxx += '};\n' @@ -142,6 +146,9 @@ outputhxx += '));\n' outputhxx += 'CDI_GROUP_END();\n' +if main_git_hash is not None: + outputhxx += '\n#define REVISION_GIT_HASH "' + main_git_hash + '"\n' + os.chdir(orig_dir) # generate the *.cxxout style content @@ -165,14 +172,18 @@ "-I", """Thu, """, "-I", """Fri, """, "-I", """Sat, """, options.output + 'Try.hxxout', - options.output + '.hxxout'], stdout=f_null) + options.output + '.hxxout'], + stdout=f_null, stderr=f_null) diffcxx = subprocess.call(['diff', "-I", """Sun, """, "-I", """Mon, """, "-I", """Tue, """, "-I", """Wed, """, "-I", """Thu, """, "-I", """Fri, """, "-I", """Sat, """, options.output + 'Try.cxxout', - options.output + '.cxxout'], stdout=f_null) + options.output + '.cxxout'], + stdout=f_null, stderr=f_null) +# disable this because we are not actually writing a cxx file above. +diffcxx = 0 if diffhxx != 0 : os.system('rm -f ' + options.output + '.hxxout') From 7d04c7b9f53e7c17a7ea55e10e97c57878d42702 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 25 Oct 2020 14:01:13 +0100 Subject: [PATCH 052/171] Add return statement in hard fault handler. --- boards/armv7m/default_handlers.h | 1 + 1 file changed, 1 insertion(+) diff --git a/boards/armv7m/default_handlers.h b/boards/armv7m/default_handlers.h index 3ba22438d..f6616ce5e 100644 --- a/boards/armv7m/default_handlers.h +++ b/boards/armv7m/default_handlers.h @@ -149,6 +149,7 @@ __attribute__((__naked__)) static void hard_fault_handler(void) " mrsne r0, psp\n" " mov sp, r0 \n" " bkpt #1 \n" + " bx lr \n" ); #endif #if 0 From d8af0f2137db4a2e3b1f6bdec95b3073216487c9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 26 Oct 2020 18:41:25 +0100 Subject: [PATCH 053/171] Adds a flag to indicate consist messages (#455) Adds a flag bit to the command byte in the traction request messages. This bit will be set to one when a set speed / set fn message is forwarded to a listener. The listener (e.g. a consisted train node) can deduce from this whether the message is applied from a consist or directly from a throttle. === * Adds a flag into the traction request command byte that signals that the currently forwarded message is for a listener. * fix whitespace --- src/openlcb/TractionConsist.cxxtest | 139 ++++++++++++++++++++++------ src/openlcb/TractionDefs.hxx | 6 ++ src/openlcb/TractionThrottle.hxx | 2 +- src/openlcb/TractionTrain.cxx | 12 ++- 4 files changed, 125 insertions(+), 34 deletions(-) diff --git a/src/openlcb/TractionConsist.cxxtest b/src/openlcb/TractionConsist.cxxtest index 92c764435..434bdb115 100644 --- a/src/openlcb/TractionConsist.cxxtest +++ b/src/openlcb/TractionConsist.cxxtest @@ -1,60 +1,83 @@ #include "utils/async_traction_test_helper.hxx" -#include "openlcb/TractionTrain.hxx" #include "openlcb/TractionTestTrain.hxx" #include "openlcb/TractionThrottle.hxx" +#include "openlcb/TractionTrain.hxx" namespace openlcb { -static constexpr NodeID nodeIdLead = 0x060100000000 | 1371; -static constexpr NodeID nodeIdC1 = 0x060100000000 | 1372; -static constexpr NodeID nodeIdC2 = 0x060100000000 | 1373; +static constexpr NodeID nodeIdLead = 0x060100000000 | 1370; +static constexpr NodeID nodeIdC1 = 0x060100000000 | 1371; +static constexpr NodeID nodeIdC2 = 0x060100000000 | 1372; +static constexpr NodeID nodeIdC3 = 0x060100000000 | 1373; +static constexpr NodeID nodeIdC4 = 0x060100000000 | 1374; +static constexpr NodeID nodeIdC5 = 0x060100000000 | 1375; -class ConsistTest : public TractionTest { +class ConsistTest : public TractionTest +{ protected: - ConsistTest() { + ConsistTest() + { create_allocated_alias(); run_x([this]() { - otherIf_.local_aliases()->add(nodeIdLead, 0x771); - otherIf_.local_aliases()->add(nodeIdC1, 0x772); - otherIf_.local_aliases()->add(nodeIdC2, 0x773); + otherIf_.local_aliases()->add(nodeIdLead, 0x770); + otherIf_.local_aliases()->add(nodeIdC1, 0x771); + otherIf_.local_aliases()->add(nodeIdC2, 0x772); + otherIf_.local_aliases()->add(nodeIdC3, 0x773); + otherIf_.remote_aliases()->add(nodeIdC4, 0x774); + otherIf_.remote_aliases()->add(nodeIdC5, 0x775); }); nodeLead_.reset(new TrainNodeForProxy(&trainService_, &trainLead_)); nodeC1_.reset(new TrainNodeForProxy(&trainService_, &trainC1_)); nodeC2_.reset(new TrainNodeForProxy(&trainService_, &trainC2_)); + nodeC3_.reset(new TrainNodeForProxy(&trainService_, &trainC3_)); + wait(); + auto b = invoke_flow(&throttle_, TractionThrottleCommands::ASSIGN_TRAIN, + nodeIdLead, false); + EXPECT_EQ(0, b->data()->resultCode); wait(); } - TractionThrottle throttle_{node_}; + void create_consist() + { + auto b = invoke_flow( + &throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC1, 0); + ASSERT_EQ(0, b->data()->resultCode); + b = invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, + nodeIdC2, + TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0); + ASSERT_EQ(0, b->data()->resultCode); + b = invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, + nodeIdC3, + TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0 | + TractionDefs::CNSTFLAGS_LINKFN); + ASSERT_EQ(0, b->data()->resultCode); + wait(); + } - IfCan otherIf_{&g_executor, &can_hub0, 5, 5, 5}; - TrainService trainService_{&otherIf_}; + TractionThrottle throttle_ {node_}; - LoggingTrain trainLead_{1371}; - LoggingTrain trainC1_{1372}; - LoggingTrain trainC2_{1373}; + IfCan otherIf_ {&g_executor, &can_hub0, 5, 5, 5}; + TrainService trainService_ {&otherIf_}; + + LoggingTrain trainLead_ {1370}; + LoggingTrain trainC1_ {1371}; + LoggingTrain trainC2_ {1372}; + LoggingTrain trainC3_ {1373}; std::unique_ptr nodeLead_; std::unique_ptr nodeC1_; std::unique_ptr nodeC2_; + std::unique_ptr nodeC3_; }; -TEST_F(ConsistTest, CreateDestroy) { +TEST_F(ConsistTest, CreateDestroy) +{ } -TEST_F(ConsistTest, CreateAndRunConsist) { - auto b = invoke_flow( - &throttle_, TractionThrottleCommands::ASSIGN_TRAIN, nodeIdLead, false); - ASSERT_EQ(0, b->data()->resultCode); - wait(); - - b = invoke_flow( - &throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC1, 0); - ASSERT_EQ(0, b->data()->resultCode); - b = invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC2, - TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0); - ASSERT_EQ(0, b->data()->resultCode); - +TEST_F(ConsistTest, CreateAndRunConsist) +{ + create_consist(); Velocity v; v.set_mph(37.5); throttle_.set_speed(v); @@ -78,9 +101,67 @@ TEST_F(ConsistTest, CreateAndRunConsist) { EXPECT_EQ(Velocity::REVERSE, trainLead_.get_speed().direction()); EXPECT_EQ(Velocity::REVERSE, trainC1_.get_speed().direction()); EXPECT_EQ(Velocity::FORWARD, trainC2_.get_speed().direction()); + + EXPECT_FALSE(trainLead_.get_fn(0)); + EXPECT_FALSE(trainLead_.get_fn(2)); + EXPECT_FALSE(trainC1_.get_fn(0)); + EXPECT_FALSE(trainC1_.get_fn(2)); + EXPECT_FALSE(trainC2_.get_fn(0)); + EXPECT_FALSE(trainC2_.get_fn(2)); + EXPECT_FALSE(trainC3_.get_fn(0)); + EXPECT_FALSE(trainC3_.get_fn(2)); + + throttle_.set_fn(0, 1); + wait(); + + // F0 forwarded to C2 and C3, not to C1. + EXPECT_TRUE(trainLead_.get_fn(0)); + EXPECT_FALSE(trainC1_.get_fn(0)); + EXPECT_TRUE(trainC2_.get_fn(0)); + EXPECT_TRUE(trainC3_.get_fn(0)); + + throttle_.set_fn(2, 1); + wait(); + + // F2 forwarded to C2 and C3, not to C0. + EXPECT_TRUE(trainLead_.get_fn(0)); + EXPECT_FALSE(trainC1_.get_fn(0)); + EXPECT_TRUE(trainC2_.get_fn(0)); + EXPECT_TRUE(trainC3_.get_fn(0)); } +TEST_F(ConsistTest, ListenerExpectations) +{ + auto b = + invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC4, + TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0 | + TractionDefs::CNSTFLAGS_LINKFN); + ASSERT_EQ(0, b->data()->resultCode); + b = invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC5, + TractionDefs::CNSTFLAGS_LINKF0 | TractionDefs::CNSTFLAGS_LINKFN); + ASSERT_EQ(0, b->data()->resultCode); + + clear_expect(true); + Velocity v; + v.set_mph(37.5); + // Throttle to lead + expect_packet(":X195EB22AN0770004C31;"); + // Lead to follower with listening flag and reversed value + expect_packet(":X195EB770N077480CC31;"); + // Lead to follower with listening flag and regular value + expect_packet(":X195EB770N0775804C31;"); + throttle_.set_speed(v); + wait(); + // Throttle to lead + expect_packet(":X195EB22AN0770010000020001;"); + // Lead to follower with listening flag + expect_packet(":X195EB770N0774810000020001;"); + // Lead to follower with listening flag + expect_packet(":X195EB770N0775810000020001;"); + throttle_.set_fn(2, 1); + wait(); +} } // namespace openlcb diff --git a/src/openlcb/TractionDefs.hxx b/src/openlcb/TractionDefs.hxx index 0d05b9f5a..389a984f4 100644 --- a/src/openlcb/TractionDefs.hxx +++ b/src/openlcb/TractionDefs.hxx @@ -107,6 +107,12 @@ struct TractionDefs { REQ_CONSIST_CONFIG = 0x30, REQ_TRACTION_MGMT = 0x40, + /// Mask to apply to the command byte of the requests. + REQ_MASK = 0x7F, + /// Flag bit in the command byte set when a listener command is + /// forwarded. + REQ_LISTENER = 0x80, + // Byte 1 of REQ_CONTROLLER_CONFIG command CTRLREQ_ASSIGN_CONTROLLER = 0x01, CTRLREQ_RELEASE_CONTROLLER = 0x02, diff --git a/src/openlcb/TractionThrottle.hxx b/src/openlcb/TractionThrottle.hxx index cb7a400be..3863f45e0 100644 --- a/src/openlcb/TractionThrottle.hxx +++ b/src/openlcb/TractionThrottle.hxx @@ -739,7 +739,7 @@ private: const Payload &p = msg->data()->payload; if (p.size() < 1) return; - switch (p[0]) + switch (p[0] & TractionDefs::REQ_MASK) { case TractionDefs::REQ_SET_SPEED: { diff --git a/src/openlcb/TractionTrain.cxx b/src/openlcb/TractionTrain.cxx index 908b046de..5038c0963 100644 --- a/src/openlcb/TractionTrain.cxx +++ b/src/openlcb/TractionTrain.cxx @@ -190,7 +190,7 @@ struct TrainService::Impl LOG(VERBOSE, "Traction message with no command byte."); return reject_permanent(); } - uint8_t cmd = payload()[0]; + uint8_t cmd = payload()[0] & TractionDefs::REQ_MASK; switch (cmd) { /** @TODO(balazs.racz) need to validate caller of mutating @@ -450,7 +450,7 @@ struct TrainService::Impl ++nextConsistIndex_; return again(); } - uint8_t cmd = payload()[0]; + uint8_t cmd = payload()[0] & TractionDefs::REQ_MASK; bool flip_speed = false; if (cmd == TractionDefs::REQ_SET_SPEED) { if (flags & TractionDefs::CNSTFLAGS_REVERSE) { @@ -486,6 +486,7 @@ struct TrainService::Impl if (flip_speed) { b->data()->payload[1] ^= 0x80; } + b->data()->payload[0] |= TractionDefs::REQ_LISTENER; iface()->addressed_message_write_flow()->send(b); return exit(); } @@ -512,8 +513,11 @@ struct TrainService::Impl } b->data()->reset(message()->data()->mti, train_node()->node_id(), NodeHandle(dst), message()->data()->payload); - if ((payload()[0] == TractionDefs::REQ_SET_SPEED) && - (flags & TractionDefs::CNSTFLAGS_REVERSE)) { + b->data()->payload[0] |= TractionDefs::REQ_LISTENER; + if (((payload()[0] & TractionDefs::REQ_MASK) == + TractionDefs::REQ_SET_SPEED) && + (flags & TractionDefs::CNSTFLAGS_REVERSE)) + { b->data()->payload[1] ^= 0x80; } iface()->addressed_message_write_flow()->send(b); From d8d228311507a00a89faf2bc23323329a7476235 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 26 Oct 2020 18:42:25 +0100 Subject: [PATCH 054/171] Fixes make warning. --- etc/prog.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/prog.mk b/etc/prog.mk index 70bc0e46b..7b1e2f00c 100644 --- a/etc/prog.mk +++ b/etc/prog.mk @@ -244,7 +244,7 @@ cg.svg: $(EXECUTABLE).ndlst $(OPENMRNPATH)/bin/callgraph.py .SUFFIXES: .SUFFIXES: .o .c .cxx .cpp .S .xml .cout .cxxout -.xml.o: $(OPENMRNPATH)/bin/build_cdi.py +%.xml: %.o $(OPENMRNPATH)/bin/build_cdi.py $(OPENMRNPATH)/bin/build_cdi.py -i $< -o $*.cxxout $(CXX) $(CXXFLAGS) -x c++ $*.cxxout -o $@ $(CXX) -MM $(CXXFLAGS) -x c++ $*.cxxout > $*.d From 60dcdb0e590f4ee61e3ef2d5dd297e312abde3c1 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 26 Oct 2020 19:17:27 +0100 Subject: [PATCH 055/171] Adds traction node listener policy callback (#456) Adds a policy function to the traction node which allows the node implementation to decide whether a function change request should be applied or not. Adds additional unit tests for consisting with function forwarding. ==== * Adds a callback to evaluate a function policy in the traction handler. * Fixes bug in implementation. Adds unit tests. * Add comments. --- src/openlcb/TractionConsist.cxxtest | 135 +++++++++++++++++++++++++--- src/openlcb/TractionTrain.cxx | 20 ++++- src/openlcb/TractionTrain.hxx | 27 +++++- 3 files changed, 167 insertions(+), 15 deletions(-) diff --git a/src/openlcb/TractionConsist.cxxtest b/src/openlcb/TractionConsist.cxxtest index 434bdb115..111e21391 100644 --- a/src/openlcb/TractionConsist.cxxtest +++ b/src/openlcb/TractionConsist.cxxtest @@ -14,6 +14,16 @@ static constexpr NodeID nodeIdC3 = 0x060100000000 | 1373; static constexpr NodeID nodeIdC4 = 0x060100000000 | 1374; static constexpr NodeID nodeIdC5 = 0x060100000000 | 1375; +class TrainNodeWithMockPolicy : public TrainNodeForProxy +{ +public: + INHERIT_CONSTRUCTOR(TrainNodeWithMockPolicy, TrainNodeForProxy); + + MOCK_METHOD5(function_policy, + bool(NodeHandle src, uint8_t command_byte, uint32_t fnum, + uint16_t value, Notifiable *done)); +}; + class ConsistTest : public TractionTest { protected: @@ -28,10 +38,14 @@ protected: otherIf_.remote_aliases()->add(nodeIdC4, 0x774); otherIf_.remote_aliases()->add(nodeIdC5, 0x775); }); - nodeLead_.reset(new TrainNodeForProxy(&trainService_, &trainLead_)); - nodeC1_.reset(new TrainNodeForProxy(&trainService_, &trainC1_)); - nodeC2_.reset(new TrainNodeForProxy(&trainService_, &trainC2_)); - nodeC3_.reset(new TrainNodeForProxy(&trainService_, &trainC3_)); + nodeLead_.reset(new StrictMock( + &trainService_, &trainLead_)); + nodeC1_.reset( + new StrictMock(&trainService_, &trainC1_)); + nodeC2_.reset( + new StrictMock(&trainService_, &trainC2_)); + nodeC3_.reset( + new StrictMock(&trainService_, &trainC3_)); wait(); auto b = invoke_flow(&throttle_, TractionThrottleCommands::ASSIGN_TRAIN, nodeIdLead, false); @@ -39,6 +53,7 @@ protected: wait(); } + /// Sets up a star shaped consist. void create_consist() { auto b = invoke_flow( @@ -53,9 +68,29 @@ protected: TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0 | TractionDefs::CNSTFLAGS_LINKFN); ASSERT_EQ(0, b->data()->resultCode); + // reverse link + nodeC3_->add_consist(nodeIdLead, + TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0 | + TractionDefs::CNSTFLAGS_LINKFN); wait(); } + void inject_default_true_policy(TrainNodeWithMockPolicy *tn) + { + EXPECT_CALL(*tn, function_policy(_, _, _, _, _)) + .WillRepeatedly( + DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(true))); + } + + /// Sets up a default "true" function policy for all train nodes. + void inject_default_policy() + { + inject_default_true_policy(nodeLead_.get()); + inject_default_true_policy(nodeC1_.get()); + inject_default_true_policy(nodeC2_.get()); + inject_default_true_policy(nodeC3_.get()); + } + TractionThrottle throttle_ {node_}; IfCan otherIf_ {&g_executor, &can_hub0, 5, 5, 5}; @@ -65,10 +100,10 @@ protected: LoggingTrain trainC1_ {1371}; LoggingTrain trainC2_ {1372}; LoggingTrain trainC3_ {1373}; - std::unique_ptr nodeLead_; - std::unique_ptr nodeC1_; - std::unique_ptr nodeC2_; - std::unique_ptr nodeC3_; + std::unique_ptr nodeLead_; + std::unique_ptr nodeC1_; + std::unique_ptr nodeC2_; + std::unique_ptr nodeC3_; }; TEST_F(ConsistTest, CreateDestroy) @@ -78,6 +113,7 @@ TEST_F(ConsistTest, CreateDestroy) TEST_F(ConsistTest, CreateAndRunConsist) { create_consist(); + inject_default_policy(); Velocity v; v.set_mph(37.5); throttle_.set_speed(v); @@ -123,15 +159,16 @@ TEST_F(ConsistTest, CreateAndRunConsist) throttle_.set_fn(2, 1); wait(); - // F2 forwarded to C2 and C3, not to C0. - EXPECT_TRUE(trainLead_.get_fn(0)); - EXPECT_FALSE(trainC1_.get_fn(0)); - EXPECT_TRUE(trainC2_.get_fn(0)); - EXPECT_TRUE(trainC3_.get_fn(0)); + // F2 forwarded to C3, not to C1 and C2. + EXPECT_TRUE(trainLead_.get_fn(2)); + EXPECT_FALSE(trainC1_.get_fn(2)); + EXPECT_FALSE(trainC2_.get_fn(2)); + EXPECT_TRUE(trainC3_.get_fn(2)); } TEST_F(ConsistTest, ListenerExpectations) { + inject_default_policy(); auto b = invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC4, TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0 | @@ -164,4 +201,76 @@ TEST_F(ConsistTest, ListenerExpectations) wait(); } +TEST_F(ConsistTest, FunctionPolicy) +{ + create_consist(); + inject_default_true_policy(nodeLead_.get()); + inject_default_true_policy(nodeC1_.get()); + inject_default_true_policy(nodeC2_.get()); + + Notifiable *done = nullptr; + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::id, nodeIdLead), 0x81, 3, 1, _)) + .WillOnce(DoAll(SaveArg<4>(&done), Return(true))); + + throttle_.set_fn(3, 1); + wait(); + EXPECT_TRUE(trainLead_.get_fn(3)); + EXPECT_FALSE(trainC1_.get_fn(3)); + EXPECT_FALSE(trainC2_.get_fn(3)); + EXPECT_FALSE(trainC3_.get_fn(3)); + ASSERT_TRUE(done); + + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::id, nodeIdLead), 0x81, 3, 1, _)) + .WillOnce(DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(true))); + + done->notify(); + wait(); + EXPECT_TRUE(trainC3_.get_fn(3)); + + // rejected by policy + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::id, nodeIdLead), 0x81, 4, 1, _)) + .WillOnce( + DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(false))); + + throttle_.set_fn(4, 1); + wait(); + EXPECT_TRUE(trainLead_.get_fn(4)); + EXPECT_FALSE(trainC1_.get_fn(4)); + EXPECT_FALSE(trainC2_.get_fn(4)); + EXPECT_FALSE(trainC3_.get_fn(4)); + + // Switch cab to C3. + auto b = invoke_flow( + &throttle_, TractionThrottleCommands::ASSIGN_TRAIN, nodeIdC3, false); + EXPECT_EQ(0, b->data()->resultCode); + + // Different policy now + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::alias, 0x22A), 0x01, 5, 1, _)) + .WillOnce(DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(true))); + + throttle_.set_fn(5, 1); + wait(); + EXPECT_TRUE(trainLead_.get_fn(5)); + EXPECT_FALSE(trainC1_.get_fn(5)); + EXPECT_FALSE(trainC2_.get_fn(5)); + EXPECT_TRUE(trainC3_.get_fn(5)); + + // Local policy false should not prevent remote propagation. + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::alias, 0x22A), 0x01, 0, 1, _)) + .WillOnce( + DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(false))); + + throttle_.set_fn(0, 1); + wait(); + EXPECT_TRUE(trainLead_.get_fn(0)); + EXPECT_FALSE(trainC1_.get_fn(0)); // no link F0 + EXPECT_TRUE(trainC2_.get_fn(0)); + EXPECT_FALSE(trainC3_.get_fn(0)); // no policy +} + } // namespace openlcb diff --git a/src/openlcb/TractionTrain.cxx b/src/openlcb/TractionTrain.cxx index 5038c0963..c2214dadd 100644 --- a/src/openlcb/TractionTrain.cxx +++ b/src/openlcb/TractionTrain.cxx @@ -213,7 +213,24 @@ struct TrainService::Impl uint16_t value = payload()[4]; value <<= 8; value |= payload()[5]; - train_node()->train()->set_fn(address, value); + bn_.reset(this); + bool should_apply = + train_node()->function_policy(nmsg()->src, payload()[0], + address, value, bn_.new_child()); + // The function_policy call may have completed inline. We + // can inquire from the barrier. If it was not completed + // inline, we have to wait for the notification and re-try + // the call. + if (!bn_.abort_if_almost_done()) + { + // Not notified inline. + bn_.notify(); // consumes our share + return wait(); + } + if (should_apply) + { + train_node()->train()->set_fn(address, value); + } nextConsistIndex_ = 0; return call_immediately(STATE(maybe_forward_consist)); } @@ -627,6 +644,7 @@ struct TrainService::Impl unsigned reserved_ : 1; TrainService *trainService_; Buffer *response_; + BarrierNotifiable bn_; }; TractionRequestFlow traction_; diff --git a/src/openlcb/TractionTrain.hxx b/src/openlcb/TractionTrain.hxx index 8c1d08ac2..61e29bc31 100644 --- a/src/openlcb/TractionTrain.hxx +++ b/src/openlcb/TractionTrain.hxx @@ -66,6 +66,22 @@ public: /// this train. virtual TrainImpl *train() = 0; + /// Applies a policy to function change requests coming in from the OpenLCB + /// bus. If the policy returns false, the change will not be applied to the + /// TrainImpl. This is used to implement consist function behavior. + /// @param src source node where the request came from. + /// @param command_byte is the first byte of the payload (usually 0x01 or + /// 0x81 depending on the REQ_LISTENER bit) + /// @param fnum which function to set + /// @param value what value to set this function to + /// @param done must be notified inline if the policy application is + /// successful. If not notified inline, then the returned value is ignored + /// and the call is repeated after done has been invoked by the callee. + /// @return true if the function should be applied to the TrainImpl, false + /// if it should not be applied. + virtual bool function_policy(NodeHandle src, uint8_t command_byte, + uint32_t fnum, uint16_t value, Notifiable *done) = 0; + /// @return the last stored controller node. virtual NodeHandle get_controller() = 0; @@ -146,7 +162,16 @@ private: class TrainNodeWithConsist : public TrainNode { public: ~TrainNodeWithConsist(); - + + /// @copydoc TrainNode::function_policy() + /// The default function policy applies everything. + bool function_policy(NodeHandle src, uint8_t command_byte, uint32_t fnum, + uint16_t value, Notifiable *done) override + { + AutoNotify an(done); + return true; + } + /** Adds a node ID to the consist targets. @return false if the node was * already in the target list, true if it was newly added. */ bool add_consist(NodeID tgt, uint8_t flags) override From b58eec9e3bd27084ea2af80268a3c30b011d3d1e Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 31 Oct 2020 13:57:58 +0100 Subject: [PATCH 056/171] Adds a counter for DCC signal transitions. (#458) --- boards/bracz-railcom/HwInit.cxx | 2 +- boards/bracz-railcom/hardware.hxx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/boards/bracz-railcom/HwInit.cxx b/boards/bracz-railcom/HwInit.cxx index 27b8ce840..d955d7451 100644 --- a/boards/bracz-railcom/HwInit.cxx +++ b/boards/bracz-railcom/HwInit.cxx @@ -107,7 +107,7 @@ const uint32_t RailcomDefs::UART_PERIPH[] = RAILCOM_PERIPH; TivaDAC dac; TivaGNDControl gnd_control; TivaBypassControl bypass_control; - +unsigned DCCDecode::sampleCount_ = 0; uint8_t RailcomDefs::feedbackChannel_ = 0xff; uint8_t dac_next_packet_mode = 0; diff --git a/boards/bracz-railcom/hardware.hxx b/boards/bracz-railcom/hardware.hxx index 585cf19c3..57ffab6c1 100644 --- a/boards/bracz-railcom/hardware.hxx +++ b/boards/bracz-railcom/hardware.hxx @@ -448,8 +448,6 @@ struct DCCDecode HWREG(TIMER_BASE + TIMER_O_TAMR) |= (TIMER_TAMR_TAMIE); } - static void cap_event_hook() {} - static const auto RCOM_TIMER = TIMER_A; static const auto SAMPLE_PERIOD_CLOCKS = 60000; //static const auto SAMPLE_TIMER_TIMEOUT = TIMER_TIMA_TIMEOUT; @@ -470,6 +468,10 @@ struct DCCDecode static inline void dcc_before_cutout_hook(); static inline void dcc_packet_finished_hook(); static inline void after_feedback_hook(); + + /// counts how many edges / transitions we had on the DCC signal. + static unsigned sampleCount_; + static inline void cap_event_hook() { ++sampleCount_; } }; #endif // ! pindefs_only From 7635ce270020a1215c507e83d8d15e873b6d1828 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 1 Nov 2020 11:24:16 +0100 Subject: [PATCH 057/171] Adds directional F0 functions to the DCC locomotives. (#459) * Adds directional F0 functions to the DCC locomotives. These functions manipulate what is sent as F0 to the locomotive depending on the current direction: - directional F0 means that we store two bits for lights on forward and reverse. We send out always whichever direction is the loco facing. The user can set and get F0 state using the usual button 0. - Blank F0 forward will turn off F0 bit in the packet even if function 0 is on whenever the speed is facing forward. This does not change the reported f0 state when get_fn is invoked. - Blank F0 reverse is the dual thereof. By default these are mapped to F100-F102 in the OpenLCB function space. The mapping can be overridden by the application. * Fix comment --- src/dcc/Defs.hxx | 2 + src/dcc/Loco.cxx | 7 +- src/dcc/Loco.hxx | 167 +++++++++++++++++++++++++++++++++++++- src/dcc/dcc_constants.cxx | 37 +++++++++ 4 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 src/dcc/dcc_constants.cxx diff --git a/src/dcc/Defs.hxx b/src/dcc/Defs.hxx index 375c1b70b..f7ddc1754 100644 --- a/src/dcc/Defs.hxx +++ b/src/dcc/Defs.hxx @@ -35,6 +35,8 @@ #ifndef _DCC_DEFS_HXX_ #define _DCC_DEFS_HXX_ +#include + namespace dcc { /// Which address type this legacy train node uses. These address types diff --git a/src/dcc/Loco.cxx b/src/dcc/Loco.cxx index cad1533b7..78462a1b1 100644 --- a/src/dcc/Loco.cxx +++ b/src/dcc/Loco.cxx @@ -110,7 +110,8 @@ void DccTrain::get_next_packet(unsigned code, Packet *packet) { case FUNCTION0: { - packet->add_dcc_function0_4(this->p.fn_ & 0x1F); + packet->add_dcc_function0_4( + (this->p.fn_ & 0x1E) | this->get_effective_f0()); return; } case FUNCTION5: @@ -171,7 +172,7 @@ MMOldTrain::~MMOldTrain() void MMOldTrain::get_next_packet(unsigned code, Packet *packet) { packet->start_mm_packet(); - packet->add_mm_address(MMAddress(p.address_), p.fn_ & 1); + packet->add_mm_address(MMAddress(p.address_), get_effective_f0()); if (code == ESTOP) { @@ -210,7 +211,7 @@ MMNewTrain::~MMNewTrain() void MMNewTrain::get_next_packet(unsigned code, Packet *packet) { packet->start_mm_packet(); - packet->add_mm_address(MMAddress(p.address_), p.fn_ & 1); + packet->add_mm_address(MMAddress(p.address_), get_effective_f0()); if (code == REFRESH) { diff --git a/src/dcc/Loco.hxx b/src/dcc/Loco.hxx index 425f13329..07b18a0bf 100644 --- a/src/dcc/Loco.hxx +++ b/src/dcc/Loco.hxx @@ -35,12 +35,18 @@ #ifndef _DCC_LOCO_HXX_ #define _DCC_LOCO_HXX_ +#include "dcc/Defs.hxx" #include "dcc/Packet.hxx" #include "dcc/PacketSource.hxx" #include "dcc/UpdateLoop.hxx" -#include "dcc/Defs.hxx" +#include "utils/constants.hxx" #include "utils/logging.h" +/// At this function number there will be three virtual functions available on +/// the OpenLCB TrainImpl, controlling advanced functions related to the light +/// (f0) function of trains. +DECLARE_CONST(dcc_virtual_f0_offset); + namespace dcc { @@ -111,10 +117,12 @@ public: return; } p.lastSetSpeed_ = new_speed; + unsigned previous_light = get_effective_f0(); if (speed.direction() != p.direction_) { p.directionChanged_ = 1; p.direction_ = speed.direction(); + update_f0_direction_changed(); } float f_speed = speed.mph(); if (f_speed > 0) @@ -131,7 +139,18 @@ public: { p.speed_ = 0; } + unsigned light = get_effective_f0(); + if (previous_light && !light) + { + // Turns off light first then sends speed packet. + packet_processor_notify_update(this, p.get_fn_update_code(0)); + } packet_processor_notify_update(this, SPEED); + if (light && !previous_light) + { + // Turns on light after sending speed packets. + packet_processor_notify_update(this, p.get_fn_update_code(0)); + } } /// @return the last set speed. @@ -169,6 +188,59 @@ public: /// (0..28), @param value is 0 for funciton OFF, 1 for function ON. void set_fn(uint32_t address, uint16_t value) OVERRIDE { + const uint32_t virtf0 = config_dcc_virtual_f0_offset(); + if (address == 0 && p.f0SetDirectional_) + { + if (p.direction_ == 0) + { + p.f0OnForward_ = value ? 1 : 0; + } + else + { + p.f0OnReverse_ = value ? 1 : 0; + } + // continue into the handling of f0. + } + else if (address == virtf0 + VIRTF0_DIRECTIONAL_ENABLE) + { + if (value) + { + p.f0SetDirectional_ = 1; + // Populates new state of separate f0 forward and f0 + // reverse. + if (p.direction_ == 0) + { + p.f0OnForward_ = p.fn_ & 1; + p.f0OnReverse_ = 0; + } + else + { + p.f0OnReverse_ = p.fn_ & 1; + p.f0OnForward_ = 0; + } + } + else + { + p.f0SetDirectional_ = 0; + // whatever value we have in fn_[0] now is going to be the + // new state, so we don't change anything. + } + // This command never changes f0, so no packets need to be sent to + // the track. + return; + } + else if (address == virtf0 + VIRTF0_BLANK_FWD) + { + p.f0BlankForward_ = value ? 1 : 0; + packet_processor_notify_update(this, p.get_fn_update_code(0)); + return; + } + else if (address == virtf0 + VIRTF0_BLANK_REV) + { + p.f0BlankReverse_ = value ? 1 : 0; + packet_processor_notify_update(this, p.get_fn_update_code(0)); + return; + } if (address > p.get_max_fn()) { // Ignore. @@ -208,6 +280,56 @@ public: } protected: + /// Function number of "enable directional F0". Offset from config option + /// dcc_virtual_f0_offset. When this function is enabled, F0 is set and + /// cleared separately for forward and reverse drive. + static constexpr unsigned VIRTF0_DIRECTIONAL_ENABLE = 0; + /// Function number of "Blank F0 Forward". Offset from config option + /// dcc_virtual_f0_offset. When this function is enabled, F0 on the track + /// packet will turn off when direction==forward, even if function 0 is + /// set. + static constexpr unsigned VIRTF0_BLANK_FWD = 1; + /// Function number of "Blank F0 Reverse". Offset from config option + /// dcc_virtual_f0_offset. When this function is enabled, F0 on the track + /// packet will turn off when direction==reverse, even if function 0 is + /// set. + static constexpr unsigned VIRTF0_BLANK_REV = 2; + + /// @return the currently applicable value of F0 to be sent out to the + /// packets (1 if on, 0 if off). + unsigned get_effective_f0() + { + unsigned is_on = p.f0SetDirectional_ == 0 ? (p.fn_ & 1) + : p.direction_ == 0 ? p.f0OnForward_ + : p.f0OnReverse_; + if (p.direction_ == 0 && p.f0BlankForward_) + { + is_on = 0; + } + if (p.direction_ == 1 && p.f0BlankReverse_) + { + is_on = 0; + } + return is_on; + } + + /// Updates the f0 states after a direction change occurred. + void update_f0_direction_changed() + { + if (p.f0SetDirectional_) + { + p.fn_ &= ~1; + if (p.direction_ == 0 && p.f0OnForward_) + { + p.fn_ |= 1; + } + if (p.direction_ == 1 && p.f0OnReverse_) + { + p.fn_ |= 1; + } + } + } + /// Payload -- actual data we know about the train. P p; }; @@ -235,6 +357,16 @@ struct Dcc28Payload unsigned speed_ : 5; /// Whether the direction change packet still needs to go out. unsigned directionChanged_ : 1; + /// 1 if the F0 function should be set/get in a directional way. + unsigned f0SetDirectional_ : 1; + /// 1 if directional f0 is used and f0 is on for F. + unsigned f0OnForward_ : 1; + /// 1 if directional f0 is used and f0 is on for R. + unsigned f0OnReverse_ : 1; + /// 1 if F0 should be turned off when dir==forward. + unsigned f0BlankForward_ : 1; + /// 1 if F0 should be turned off when dir==reverse. + unsigned f0BlankReverse_ : 1; /** @return the number of speed steps (in float). */ static unsigned get_speed_steps() @@ -328,6 +460,17 @@ struct Dcc128Payload /// Whether the direction change packet still needs to go out. unsigned directionChanged_ : 1; + /// 1 if the F0 function should be set/get in a directional way. + unsigned f0SetDirectional_ : 1; + /// 1 if directional f0 is used and f0 is on for F. + unsigned f0OnForward_ : 1; + /// 1 if directional f0 is used and f0 is on for R. + unsigned f0OnReverse_ : 1; + /// 1 if F0 should be turned off when dir==forward. + unsigned f0BlankForward_ : 1; + /// 1 if F0 should be turned off when dir==reverse. + unsigned f0BlankReverse_ : 1; + /** @return the number of speed steps (the largest valid speed step). */ static unsigned get_speed_steps() { @@ -393,6 +536,17 @@ struct MMOldPayload /// Speed step we last set. unsigned speed_ : 4; + /// 1 if the F0 function should be set/get in a directional way. + unsigned f0SetDirectional_ : 1; + /// 1 if directional f0 is used and f0 is on for F. + unsigned f0OnForward_ : 1; + /// 1 if directional f0 is used and f0 is on for R. + unsigned f0OnReverse_ : 1; + /// 1 if F0 should be turned off when dir==forward. + unsigned f0BlankForward_ : 1; + /// 1 if F0 should be turned off when dir==reverse. + unsigned f0BlankReverse_ : 1; + /** @return the number of speed steps (in float). */ unsigned get_speed_steps() { @@ -459,6 +613,17 @@ struct MMNewPayload /// internal refresh cycle state machine unsigned nextRefresh_ : 3; + /// 1 if the F0 function should be set/get in a directional way. + unsigned f0SetDirectional_ : 1; + /// 1 if directional f0 is used and f0 is on for F. + unsigned f0OnForward_ : 1; + /// 1 if directional f0 is used and f0 is on for R. + unsigned f0OnReverse_ : 1; + /// 1 if F0 should be turned off when dir==forward. + unsigned f0BlankForward_ : 1; + /// 1 if F0 should be turned off when dir==reverse. + unsigned f0BlankReverse_ : 1; + /** @return the number of speed steps (in float). */ unsigned get_speed_steps() { diff --git a/src/dcc/dcc_constants.cxx b/src/dcc/dcc_constants.cxx new file mode 100644 index 000000000..e4070dd28 --- /dev/null +++ b/src/dcc/dcc_constants.cxx @@ -0,0 +1,37 @@ +/** \copyright + * Copyright (c) 2014, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file dcc_constants.cxx + * + * Default values of constants from the dcc package. + * + * @author Balazs Racz + * @date 10 May 2014 + */ + +#include "utils/constants.hxx" + +DEFAULT_CONST(dcc_virtual_f0_offset, 100); From cb7ec934b774bac09dd833193aa123dd2062a4d2 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 1 Nov 2020 17:47:48 +0100 Subject: [PATCH 058/171] Implements reads for virtual f0 functions. (#460) --- src/dcc/Loco.hxx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/dcc/Loco.hxx b/src/dcc/Loco.hxx index 07b18a0bf..caed8643b 100644 --- a/src/dcc/Loco.hxx +++ b/src/dcc/Loco.hxx @@ -261,6 +261,19 @@ public: /// not known. @param address is the function address. uint16_t get_fn(uint32_t address) OVERRIDE { + const uint32_t virtf0 = config_dcc_virtual_f0_offset(); + if (address == virtf0 + VIRTF0_DIRECTIONAL_ENABLE) + { + return p.f0SetDirectional_; + } + else if (address == virtf0 + VIRTF0_BLANK_FWD) + { + return p.f0BlankForward_; + } + else if (address == virtf0 + VIRTF0_BLANK_REV) + { + return p.f0BlankReverse_; + } if (address > p.get_max_fn()) { // Unknown. From 065bbc183bd678b6505357876dd19272896543c3 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 7 Nov 2020 17:34:10 +0100 Subject: [PATCH 059/171] Query function for throttles. (#461) The query function allows the throttle to request the current function state from the remote (train). Adds query_fn to the Throttle interface. Fixes todo to invoke client callback when a function state response arrives. --- src/openlcb/TractionThrottle.hxx | 38 +++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/openlcb/TractionThrottle.hxx b/src/openlcb/TractionThrottle.hxx index 3863f45e0..ed4c0e29e 100644 --- a/src/openlcb/TractionThrottle.hxx +++ b/src/openlcb/TractionThrottle.hxx @@ -174,8 +174,16 @@ class TractionThrottleInterface : public openlcb::TrainImpl { public: + /// Flips a function on<>off. virtual void toggle_fn(uint32_t fn) = 0; + /// Sends a query for a function to the server. The response will be + /// asynchronously reported by the throttle listener update callback. + /// @param fn function number. + virtual void query_fn(uint32_t fn) + { + } + /// Determine if a train is currently assigned to this trottle. /// @return true if a train is assigned, else false virtual bool is_train_assigned() = 0; @@ -295,7 +303,15 @@ public: } set_fn(fn, fnstate); } - + + /// Sends out a function query command. The throttle listener will be + /// called when the response is available. + /// @param address function to query. + void query_fn(uint32_t address) override + { + send_traction_message(TractionDefs::fn_get_payload(address)); + } + uint32_t legacy_address() override { return 0; @@ -567,7 +583,9 @@ private: } } - void pending_reply_arrived() + /// Notifies that a pending query during load has gotten a reply. + /// @return true if we were in the load state. + bool pending_reply_arrived() { if (pendingQueries_ > 0) { @@ -575,7 +593,9 @@ private: { timer_.trigger(); } + return true; } + return false; } void speed_reply(Buffer *msg) @@ -592,13 +612,15 @@ private: { case TractionDefs::RESP_QUERY_SPEED: { - pending_reply_arrived(); + bool expected = pending_reply_arrived(); Velocity v; if (TractionDefs::speed_get_parse_last(p, &v)) { lastSetSpeed_ = v; - /// @todo (balazs.racz): call a callback for the client. - + if (updateCallback_ && !expected) + { + updateCallback_(-1); + } /// @todo (Stuart.Baker): Do we need to do anything with /// estopActive_? } @@ -606,12 +628,16 @@ private: } case TractionDefs::RESP_QUERY_FN: { - pending_reply_arrived(); + bool expected = pending_reply_arrived(); uint16_t v; unsigned num; if (TractionDefs::fn_get_parse(p, &v, &num)) { lastKnownFn_[num] = v; + if (updateCallback_ && !expected) + { + updateCallback_(num); + } } } } From 44f31ee36e2a8cd85b1538ac9f66921671c1cde6 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 7 Nov 2020 19:13:11 +0100 Subject: [PATCH 060/171] Checks throttle node pointer upon speed reply messages. --- src/openlcb/TractionThrottle.hxx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/openlcb/TractionThrottle.hxx b/src/openlcb/TractionThrottle.hxx index ed4c0e29e..c06c44b98 100644 --- a/src/openlcb/TractionThrottle.hxx +++ b/src/openlcb/TractionThrottle.hxx @@ -601,6 +601,11 @@ private: void speed_reply(Buffer *msg) { AutoReleaseBuffer rb(msg); + if (msg->data()->dstNode != node_) + { + // For a different throttle. + return; + } if (!iface()->matching_node(msg->data()->src, NodeHandle(dst_))) { return; From c867668ec827563106ce8bd23ec8dbb238bf70c1 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 14 Nov 2020 19:06:07 +0100 Subject: [PATCH 061/171] Adds config option for how much memory should the bulk alias allocator use. --- include/nmranet_config.h | 4 ++++ src/openlcb/nmranet_constants.cxx | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/include/nmranet_config.h b/include/nmranet_config.h index 85dc8da42..287e9a481 100644 --- a/include/nmranet_config.h +++ b/include/nmranet_config.h @@ -146,6 +146,10 @@ DECLARE_CONST(enable_all_memory_space); * standard. */ DECLARE_CONST(node_init_identify); +/** How many CAN frames should the bulk alias allocator be sending at the same + * time. */ +DECLARE_CONST(bulk_alias_num_can_frames); + /** Stack size for @ref SocketListener threads. */ DECLARE_CONST(socket_listener_stack_size); diff --git a/src/openlcb/nmranet_constants.cxx b/src/openlcb/nmranet_constants.cxx index 87c9f3465..c246c047b 100644 --- a/src/openlcb/nmranet_constants.cxx +++ b/src/openlcb/nmranet_constants.cxx @@ -63,3 +63,7 @@ DEFAULT_CONST_FALSE(enable_all_memory_space); * identified messages at boot time. This is required by the OpenLCB * standard. */ DEFAULT_CONST_TRUE(node_init_identify); + +/** How many CAN frames should the bulk alias allocator be sending at the same + * time. */ +DEFAULT_CONST(bulk_alias_num_can_frames, 20); From 9ad85a61cad1063cdc1b6fb26b8aea941085538f Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 14 Nov 2020 19:06:27 +0100 Subject: [PATCH 062/171] Adds interface and implementation to bulk alias allocator. --- src/openlcb/BulkAliasAllocator.cxx | 235 +++++++++++++++++++++++++++++ src/openlcb/BulkAliasAllocator.hxx | 64 ++++++++ src/openlcb/sources | 1 + 3 files changed, 300 insertions(+) create mode 100644 src/openlcb/BulkAliasAllocator.cxx create mode 100644 src/openlcb/BulkAliasAllocator.hxx diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx new file mode 100644 index 000000000..32956249c --- /dev/null +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -0,0 +1,235 @@ +/** \copyright + * Copyright (c) 2020 Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BulkAliasAllocator.cxx + * + * State flow for allocating many aliases at the same time. + * + * @author Balazs Racz + * @date 14 Nov 2020 + */ + +#include "openlcb/BulkAliasAllocator.hxx" +#include "openlcb/CanDefs.hxx" + +namespace openlcb +{ + +class BulkAliasAllocator : public CallableFlow +{ +public: + BulkAliasAllocator(IfCan *iface) + : CallableFlow(iface) + { + } + + Action entry() override + { + startTime_ = os_get_time_monotonic(); + pendingAliasesByTime_.clear(); + pendingAliasesByKey_.clear(); + nextToStampTime_ = 0; + nextToClaim_ = 0; + return call_immediately(STATE(send_cid_frames)); + } + + /// Picks a bunch of random aliases, sends CID frames for them to the bus. + Action send_cid_frames() + { + unsigned needed = std::min(request()->numAliases_, + (unsigned)(config_bulk_alias_num_can_frames() + 3) / 4); + if (!needed) + { + return call_immediately(STATE(wait_for_results)); + } + bn_.reset(this); + for (unsigned i = 0; i < needed; ++i) + { + NodeAlias next_alias = if_can()->alias_allocator()->get_new_seed(); + auto if_id = if_can()->alias_allocator()->if_id(); + send_can_frame(next_alias, (if_id >> 36) & 0xfff, 7); + send_can_frame(next_alias, (if_id >> 24) & 0xfff, 6); + send_can_frame(next_alias, (if_id >> 12) & 0xfff, 5); + send_can_frame(next_alias, (if_id >> 0) & 0xfff, 4); + --request()->numAliases_; + pendingAliasesByTime_.push_back({next_alias}); + pendingAliasesByKey_.insert({next_alias}); + } + bn_.notify(); + return call_immediately(STATE(wait_for_results)); + } + + /// Adds the timestamps when the CID requests were sent out. + Action stamp_time() + { + for (unsigned i = nextToStampTime_; i < pendingAliasesByTime_.size(); + ++i) + { + pendingAliasesByTime_[i].cidTime_ = relative_time(); + } + nextToStampTime_ = pendingAliasesByTime_.size(); + /// @todo this is wrong. + return call_immediately(STATE(send_cid_frames)); + } + + Action wait_for_results() + { + if (nextToClaim_ == pendingAliasesByTime_.size()) + { + return complete(); + } + auto ctime = relative_time(); + unsigned num_sent = 0; + bn_.reset(this); + while ((nextToClaim_ < pendingAliasesByTime_.size()) && + (num_sent < (unsigned)(config_bulk_alias_num_can_frames())) && + (pendingAliasesByTime_[nextToClaim_].cidTime_ + ALLOCATE_DELAY > + ctime)) + { + NodeAlias a = + (NodeAlias)(pendingAliasesByTime_[nextToClaim_].alias_); + ++nextToClaim_; + auto it = pendingAliasesByKey_.find(a); + if (it->hasConflict_) + { + // we skip this alias because there was a conflict. + continue; + } + /// @todo add alias to the cache as reserved alias. + ++num_sent; + send_can_frame(a, CanDefs::RID_FRAME, 0); + } + if (bn_.abort_if_almost_done()) + { + // no frame sent + return sleep_and_call( + &timer_, MSEC_TO_NSEC(10), STATE(wait_for_results)); + } + else + { + // Wait for outgoing frames to be gone and call this again. + return wait(); + } + } + + Action complete() + { + pendingAliasesByTime_.clear(); + pendingAliasesByKey_.clear(); + return return_ok(); + } + +private: + /// How many count to wait before sending out the RID frames. One count is + /// 10 msec (see { \link relative_time } ). + static constexpr unsigned ALLOCATE_DELAY = 20; + + /// Sends a CAN control frame to the bus. Take a share of the barrier bn_ + /// to send with the frame. + /// @param src source alias to use on the frame. + /// @param control_field 16-bit control value (e.g. RID_FRAME, or 0 top + /// nibble and a chunk of the unique node ID in the middle). + /// @param sequence used for CID messages. + void send_can_frame(NodeAlias src, uint16_t control_field, int sequence) + { + auto *b = if_can()->frame_write_flow()->alloc(); + b->set_done(bn_.new_child()); + CanDefs::control_init(*b->data(), src, control_field, sequence); + if_can()->frame_write_flow()->send(b, 0); + } + + IfCan *if_can() + { + return static_cast(service()); + } + + /// @return the time elapsed from start time in 10 msec units. + unsigned relative_time() + { + return (os_get_time_monotonic() - startTime_) / MSEC_TO_NSEC(10); + } + + struct PendingAliasInfo + { + PendingAliasInfo(NodeAlias alias) + : alias_(alias) + , cidTime_(0) + { + } + + /// The value of the alias + unsigned alias_ : 12; + /// The time when the CID requests were sent. Counterd in + /// relative_time(), i.e. 10 msec per increment. + unsigned cidTime_ : 8; + }; + static_assert(sizeof(PendingAliasInfo) == 4, "memory bloat"); + + /// We store this type in the sorted map lookup structure. + struct AliasLookupInfo + { + AliasLookupInfo(NodeAlias alias) + : alias_(alias) + , hasConflict_(0) + { + } + + /// The value of the alias + uint16_t alias_ : 12; + /// 1 if we have seen a conflict + uint16_t hasConflict_ : 1; + }; + static_assert(sizeof(AliasLookupInfo) == 2, "memory bloat"); + /// Comparator function on AliasLookupInfo objects. + struct LookupCompare + { + bool operator()(AliasLookupInfo a, AliasLookupInfo b) + { + return a.alias_ < b.alias_; + } + }; + + /// Helper object for sleeping. + StateFlowTimer timer_ {this}; + /// Helper object to determine when the CAN frames have flushed from the + /// system. + BarrierNotifiable bn_; + /// We measure time elapsed relative to this point. + long long startTime_; + /// Stores the aliases we are trying to allocate in time order of picking + /// them. + std::vector pendingAliasesByTime_; + /// Stores the aliases we are trying to allocate in the alias order. + SortedListSet pendingAliasesByKey_; + /// Index into the pendingAliasesByTime_ vector where we need to stmap + /// time. + uint16_t nextToStampTime_; + /// Index into the pendingAliasesByTime_ vector where we need to send out + /// the reserve frame. + uint16_t nextToClaim_; +}; + +} // namespace openlcb diff --git a/src/openlcb/BulkAliasAllocator.hxx b/src/openlcb/BulkAliasAllocator.hxx new file mode 100644 index 000000000..2fee889a0 --- /dev/null +++ b/src/openlcb/BulkAliasAllocator.hxx @@ -0,0 +1,64 @@ +/** \copyright + * Copyright (c) 2020 Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BulkAliasAllocator.hxx + * + * State flow for allocating many aliases at the same time. + * + * @author Balazs Racz + * @date 14 Nov 2020 + */ + +#include "executor/CallableFlow.hxx" +#include "openlcb/AliasAllocator.hxx" +#include "openlcb/AliasCache.hxx" +#include "openlcb/CanDefs.hxx" + +namespace openlcb +{ + +/// Message type to request allocating many aliases for an interface. +struct BulkAliasRequest : CallableFlowRequestBase +{ + /// @param count how many aliases to allocate. + void reset(unsigned count) + { + reset_base(); + numAliases_ = count; + } + + /// How many aliases to allocate. + unsigned numAliases_; +}; + +using BulkAliasAllocatorInterface = FlowInterface>; + +/// Creates a bulk alias allocator. +/// @param can_if the interface to bind it to. +std::unique_ptr create_bulk_alias_allocator( + IfCan *can_if); + +} // namespace openlcb diff --git a/src/openlcb/sources b/src/openlcb/sources index 9f849e954..5e8e4a01a 100644 --- a/src/openlcb/sources +++ b/src/openlcb/sources @@ -8,6 +8,7 @@ CXXSRCS += \ BroadcastTime.cxx \ BroadcastTimeClient.cxx \ BroadcastTimeServer.cxx \ + BulkAliasAllocator.cxx \ CanDefs.cxx \ ConfigEntry.cxx \ ConfigUpdateFlow.cxx \ From 983502f2f6232357c922d0d2591f10dd728f3209 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 14 Nov 2020 19:08:33 +0100 Subject: [PATCH 063/171] fix whitespace --- src/utils/SortedListMap.hxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/SortedListMap.hxx b/src/utils/SortedListMap.hxx index 598e934d5..7a65bc45d 100644 --- a/src/utils/SortedListMap.hxx +++ b/src/utils/SortedListMap.hxx @@ -152,7 +152,8 @@ public: } /// Removes all entries. - void clear() { + void clear() + { container_.clear(); sortedCount_ = 0; } From 2ada5344f9c47153018fd429beff569fa0ad46be Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 14 Nov 2020 19:11:19 +0100 Subject: [PATCH 064/171] Small changes to alias allocator: - adds accessor to get the node id used for allocation. - fixes todo to check whether newly generated aliases are in the existing caches. - adds ability to get the next proposed alias from the sequence. --- src/openlcb/AliasAllocator.cxx | 22 +++++++++++++++++++--- src/openlcb/AliasAllocator.hxx | 11 +++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/openlcb/AliasAllocator.cxx b/src/openlcb/AliasAllocator.cxx index 526e5c345..343fa1ea7 100644 --- a/src/openlcb/AliasAllocator.cxx +++ b/src/openlcb/AliasAllocator.cxx @@ -111,9 +111,7 @@ StateFlowBase::Action AliasAllocator::entry() HASSERT(pending_alias()->state == AliasInfo::STATE_EMPTY); while (!pending_alias()->alias) { - pending_alias()->alias = seed_; - next_seed(); - // TODO(balazs.racz): check if the alias is already known about. + pending_alias()->alias = get_new_seed(); } // Registers ourselves as a handler for incoming CAN frames to detect // conflicts. @@ -124,6 +122,24 @@ StateFlowBase::Action AliasAllocator::entry() return call_immediately(STATE(handle_allocate_for_cid_frame)); } +NodeAlias AliasAllocator::get_new_seed() +{ + while (true) + { + NodeAlias ret = seed_; + next_seed(); + if (if_can()->local_aliases()->lookup(ret)) + { + continue; + } + if (if_can()->remote_aliases()->lookup(ret)) + { + continue; + } + return ret; + } +} + void AliasAllocator::next_seed() { uint16_t offset; diff --git a/src/openlcb/AliasAllocator.hxx b/src/openlcb/AliasAllocator.hxx index c39606a6b..83bc048b9 100644 --- a/src/openlcb/AliasAllocator.hxx +++ b/src/openlcb/AliasAllocator.hxx @@ -108,13 +108,24 @@ public: */ AliasAllocator(NodeID if_id, IfCan *if_can); + /** Destructor */ virtual ~AliasAllocator(); + /** @return the Node ID for the interface. */ + NodeID if_id() + { + return if_id_; + } + /** Resets the alias allocator to the state it was at construction. useful * after connection restart in order to ensure it will try to allocate the * same alias. */ void reinit_seed(); + /** Returns a new alias to check from the random sequence. Checks that it + * is not in the alias cache yet.*/ + NodeAlias get_new_seed(); + /** "Allocate" a buffer from this pool (but without initialization) in * order to get a reserved alias. */ QAsync *reserved_aliases() From 7fc0ef2b1cb53f798ee9e8b18b438c8ee87e9061 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 14 Nov 2020 19:11:36 +0100 Subject: [PATCH 065/171] Adds bulk allocator to the test. --- src/openlcb/AliasAllocator.cxxtest | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/openlcb/AliasAllocator.cxxtest b/src/openlcb/AliasAllocator.cxxtest index 67a284dcd..436e0bd13 100644 --- a/src/openlcb/AliasAllocator.cxxtest +++ b/src/openlcb/AliasAllocator.cxxtest @@ -4,6 +4,7 @@ #include "openlcb/AliasAllocator.hxx" #include "openlcb/AliasCache.hxx" #include "openlcb/CanDefs.hxx" +#include "openlcb/BulkAliasAllocator.hxx" namespace openlcb { @@ -13,6 +14,7 @@ protected: AsyncAliasAllocatorTest() : b_(nullptr) , alias_allocator_(TEST_NODE_ID, ifCan_.get()) + , bulkAllocator_(create_bulk_alias_allocator(ifCan_.get())) { } @@ -50,6 +52,7 @@ protected: Buffer *b_; AliasAllocator alias_allocator_; + std::unique_ptr bulkAllocator_; }; TEST_F(AsyncAliasAllocatorTest, SetupTeardown) From 4116fcc3d06a398f47044269da32a5616b272696 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 14 Nov 2020 23:24:05 +0100 Subject: [PATCH 066/171] Adds a test facility to invoke a flow without waiting for the result. --- src/utils/test_main.hxx | 44 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/utils/test_main.hxx b/src/utils/test_main.hxx index be1f1a19c..393cb5ae9 100644 --- a/src/utils/test_main.hxx +++ b/src/utils/test_main.hxx @@ -54,6 +54,7 @@ #include "os/TempFile.hxx" #include "os/os.h" #include "utils/StringPrintf.hxx" +#include "executor/CallableFlow.hxx" #ifdef WITHGPERFTOOLS #include @@ -177,6 +178,49 @@ void run_x(std::function fn) g_executor.sync_run(std::move(fn)); } +/// Structure holding returned objects for an invoke_flow_nowait command. +template struct PendingInvocation +{ + /// Buffer sent to the flow. + BufferPtr b; + /// Notifiable to wait for. + SyncNotifiable notifiable; + /// Barrier notifiable given to the buffer. + BarrierNotifiable barrier {¬ifiable}; + /// True if wait has been invoked. + bool isWaited {false}; + + ~PendingInvocation() + { + wait(); + } + + void wait() + { + if (isWaited) + { + return; + } + notifiable.wait_for_notification(); + isWaited = true; + } +}; + +/// Executes a callable flow similar to invoke_flow(...) but does not wait for +/// the result to come back. Instead, returns a PendingInvocation object, where +/// there is a wait() method to be called. +template +std::unique_ptr> invoke_flow_nowait( + FlowInterface> *flow, Args &&...args) +{ + auto ret = std::make_unique>(); + ret->b.reset(flow->alloc()); + ret->b->data()->reset(std::forward(args)...); + ret->b->data()->done.reset(&ret->barrier); + flow->send(ret->b->ref()); + return ret; +} + /** Utility class to block an executor for a while. * * Usage: add an instance of BlockExecutor to the executor you want to block, From 3cde76305ad22fff9581a19ddea57beee3fcdd75 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 14 Nov 2020 23:24:15 +0100 Subject: [PATCH 067/171] fixes bugs --- src/openlcb/BulkAliasAllocator.cxx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx index 32956249c..c19e5855e 100644 --- a/src/openlcb/BulkAliasAllocator.cxx +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -79,7 +79,7 @@ class BulkAliasAllocator : public CallableFlow pendingAliasesByKey_.insert({next_alias}); } bn_.notify(); - return call_immediately(STATE(wait_for_results)); + return wait_and_call(STATE(stamp_time)); } /// Adds the timestamps when the CID requests were sent out. @@ -95,6 +95,8 @@ class BulkAliasAllocator : public CallableFlow return call_immediately(STATE(send_cid_frames)); } + /// Sends out the RID frames for any alias that the 200 msec has already + /// elapsed, then waits a bit and tries again. Action wait_for_results() { if (nextToClaim_ == pendingAliasesByTime_.size()) @@ -106,7 +108,7 @@ class BulkAliasAllocator : public CallableFlow bn_.reset(this); while ((nextToClaim_ < pendingAliasesByTime_.size()) && (num_sent < (unsigned)(config_bulk_alias_num_can_frames())) && - (pendingAliasesByTime_[nextToClaim_].cidTime_ + ALLOCATE_DELAY > + (pendingAliasesByTime_[nextToClaim_].cidTime_ + ALLOCATE_DELAY < ctime)) { NodeAlias a = @@ -130,6 +132,7 @@ class BulkAliasAllocator : public CallableFlow } else { + bn_.notify(); // Wait for outgoing frames to be gone and call this again. return wait(); } @@ -232,4 +235,10 @@ class BulkAliasAllocator : public CallableFlow uint16_t nextToClaim_; }; +std::unique_ptr create_bulk_alias_allocator( + IfCan *can_if) { + return std::make_unique(can_if); +} + + } // namespace openlcb From 6f36b83a77b4f91932006931cd74d301bdca8f1c Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 14 Nov 2020 23:24:25 +0100 Subject: [PATCH 068/171] Adds basic test for bulk allocator. --- src/openlcb/AliasAllocator.cxxtest | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/openlcb/AliasAllocator.cxxtest b/src/openlcb/AliasAllocator.cxxtest index 436e0bd13..12dc2aed1 100644 --- a/src/openlcb/AliasAllocator.cxxtest +++ b/src/openlcb/AliasAllocator.cxxtest @@ -221,4 +221,39 @@ TEST_F(AsyncAliasAllocatorTest, DifferentGenerated) // Makes sure 'other' disappears from the executor before destructing it. wait(); } + +TEST_F(AsyncAliasAllocatorTest, BulkFew) +{ + set_seed(0x555, ifCan_->alias_allocator()); + std::vector aliases; + run_x([this, &aliases]() { + for (unsigned i = 0; i < 5; i++) + { + auto a = ifCan_->alias_allocator()->get_new_seed(); + aliases.push_back(a); + } + }); + for (unsigned i = 0; i < 5; i++) { + auto a = aliases[i]; + LOG(INFO, "alias %03X", a); + expect_packet(StringPrintf(":X17020%03XN;", a)); + expect_packet(StringPrintf(":X1610D%03XN;", a)); + expect_packet(StringPrintf(":X15000%03XN;", a)); + expect_packet(StringPrintf(":X14003%03XN;", a)); + } + LOG(INFO, "invoke"); + set_seed(0x555, ifCan_->alias_allocator()); + auto invocation = invoke_flow_nowait(bulkAllocator_.get(), 5); + wait(); + LOG(INFO, "expect RIDs"); + clear_expect(true); + for (unsigned i = 0; i < 5; i++) { + auto a = aliases[i]; + expect_packet(StringPrintf(":X10700%03XN;", a)); + } + LOG(INFO, "wait for complete"); + invocation->wait(); + clear_expect(true); +} + } // namespace openlcb From 06841e8a0fa5a78dd6a393f0b2fe9ae83d9bf6ef Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 14 Nov 2020 23:25:45 +0100 Subject: [PATCH 069/171] adds time expectation --- src/openlcb/AliasAllocator.cxxtest | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/openlcb/AliasAllocator.cxxtest b/src/openlcb/AliasAllocator.cxxtest index 12dc2aed1..44080c417 100644 --- a/src/openlcb/AliasAllocator.cxxtest +++ b/src/openlcb/AliasAllocator.cxxtest @@ -243,6 +243,7 @@ TEST_F(AsyncAliasAllocatorTest, BulkFew) } LOG(INFO, "invoke"); set_seed(0x555, ifCan_->alias_allocator()); + auto start_time = os_get_time_monotonic(); auto invocation = invoke_flow_nowait(bulkAllocator_.get(), 5); wait(); LOG(INFO, "expect RIDs"); @@ -254,6 +255,8 @@ TEST_F(AsyncAliasAllocatorTest, BulkFew) LOG(INFO, "wait for complete"); invocation->wait(); clear_expect(true); + auto end_time = os_get_time_monotonic(); + EXPECT_LT(MSEC_TO_NSEC(200), end_time - start_time); } } // namespace openlcb From 60e854f8f56dc0c0dbc28ff8a938f7b61a219fb2 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 15 Nov 2020 00:27:52 +0100 Subject: [PATCH 070/171] Adds support for conflict handling in the bulk alias allocator. --- src/openlcb/AliasAllocator.cxxtest | 110 +++++++++++++++++++++++------ src/openlcb/BulkAliasAllocator.cxx | 21 ++++++ 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/src/openlcb/AliasAllocator.cxxtest b/src/openlcb/AliasAllocator.cxxtest index 44080c417..621c6559b 100644 --- a/src/openlcb/AliasAllocator.cxxtest +++ b/src/openlcb/AliasAllocator.cxxtest @@ -50,9 +50,55 @@ protected: } } + /// Pre-generates some aliases into a vector. + void generate_aliases(AliasAllocator *alloc, unsigned count) + { + set_seed(0x555, alloc); + run_x([this, count, alloc]() { + for (unsigned i = 0; i < count; i++) + { + auto a = alloc->get_new_seed(); + LOG(INFO, "alias %03X", a); + aliases_.push_back(a); + } + }); + set_seed(0x555, alloc); + } + + /// Expects that CID frames are sent to the bus. + /// @param begin iterator into alias array + /// @param end iterator (end) into alias array + template void expect_cid(It begin, It end) + { + for (auto it = begin; it != end; ++it) + { + NodeAlias a = *it; + string msg = StringPrintf("cid %03X", a); + LOG(INFO, "cid %03X", a); + expect_packet(StringPrintf(":X17020%03XN;", a)); + expect_packet(StringPrintf(":X1610D%03XN;", a)); + expect_packet(StringPrintf(":X15000%03XN;", a)); + expect_packet(StringPrintf(":X14003%03XN;", a)); + } + } + + /// Expects that RID frames are sent to the bus. + /// @param begin iterator into alias array + /// @param end iterator (end) into alias array + template void expect_rid(It begin, It end) + { + for (auto it = begin; it != end; ++it) + { + NodeAlias a = *it; + LOG(INFO, "rid %03X", a); + expect_packet(StringPrintf(":X10700%03XN;", a)); + } + } + Buffer *b_; AliasAllocator alias_allocator_; std::unique_ptr bulkAllocator_; + std::vector aliases_; }; TEST_F(AsyncAliasAllocatorTest, SetupTeardown) @@ -224,34 +270,15 @@ TEST_F(AsyncAliasAllocatorTest, DifferentGenerated) TEST_F(AsyncAliasAllocatorTest, BulkFew) { - set_seed(0x555, ifCan_->alias_allocator()); - std::vector aliases; - run_x([this, &aliases]() { - for (unsigned i = 0; i < 5; i++) - { - auto a = ifCan_->alias_allocator()->get_new_seed(); - aliases.push_back(a); - } - }); - for (unsigned i = 0; i < 5; i++) { - auto a = aliases[i]; - LOG(INFO, "alias %03X", a); - expect_packet(StringPrintf(":X17020%03XN;", a)); - expect_packet(StringPrintf(":X1610D%03XN;", a)); - expect_packet(StringPrintf(":X15000%03XN;", a)); - expect_packet(StringPrintf(":X14003%03XN;", a)); - } + generate_aliases(ifCan_->alias_allocator(), 5); + expect_cid(aliases_.begin(), aliases_.end()); LOG(INFO, "invoke"); - set_seed(0x555, ifCan_->alias_allocator()); auto start_time = os_get_time_monotonic(); auto invocation = invoke_flow_nowait(bulkAllocator_.get(), 5); wait(); LOG(INFO, "expect RIDs"); clear_expect(true); - for (unsigned i = 0; i < 5; i++) { - auto a = aliases[i]; - expect_packet(StringPrintf(":X10700%03XN;", a)); - } + expect_rid(aliases_.begin(), aliases_.end()); LOG(INFO, "wait for complete"); invocation->wait(); clear_expect(true); @@ -259,4 +286,43 @@ TEST_F(AsyncAliasAllocatorTest, BulkFew) EXPECT_LT(MSEC_TO_NSEC(200), end_time - start_time); } +TEST_F(AsyncAliasAllocatorTest, BulkConflict) +{ + generate_aliases(ifCan_->alias_allocator(), 7); + clear_expect(true); + expect_cid(aliases_.begin(), aliases_.begin() + 5); + LOG(INFO, "invoke"); + auto invocation = invoke_flow_nowait(bulkAllocator_.get(), 5); + wait(); + LOG(INFO, "send conflicts"); + clear_expect(true); + expect_cid(aliases_.begin()+5, aliases_.end()); + send_packet(StringPrintf(":X10700%03XN;", aliases_[0])); + send_packet(StringPrintf(":X10700%03XN;", aliases_[1])); + wait(); + usleep(10000); + wait(); + LOG(INFO, "expect RIDs"); + clear_expect(true); + expect_rid(aliases_.begin() + 2, aliases_.end()); + LOG(INFO, "wait for complete"); + invocation->wait(); + clear_expect(true); +} + +TEST_F(AsyncAliasAllocatorTest, BulkMany) +{ + generate_aliases(ifCan_->alias_allocator(), 150); + expect_cid(aliases_.begin(), aliases_.end()); + LOG(INFO, "invoke"); + auto invocation = invoke_flow_nowait(bulkAllocator_.get(), 150); + wait(); + LOG(INFO, "expect RIDs"); + clear_expect(true); + expect_rid(aliases_.begin(), aliases_.end()); + LOG(INFO, "wait for complete"); + invocation->wait(); + clear_expect(true); +} + } // namespace openlcb diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx index c19e5855e..ba9deafca 100644 --- a/src/openlcb/BulkAliasAllocator.cxx +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -53,6 +53,7 @@ class BulkAliasAllocator : public CallableFlow pendingAliasesByKey_.clear(); nextToStampTime_ = 0; nextToClaim_ = 0; + if_can()->frame_dispatcher()->register_handler(&conflictHandler_, 0, 0); return call_immediately(STATE(send_cid_frames)); } @@ -103,6 +104,11 @@ class BulkAliasAllocator : public CallableFlow { return complete(); } + if (request()->numAliases_) + { + // Some conflicts were identified, go and allocate more. + return call_immediately(STATE(send_cid_frames)); + } auto ctime = relative_time(); unsigned num_sent = 0; bn_.reset(this); @@ -140,12 +146,27 @@ class BulkAliasAllocator : public CallableFlow Action complete() { + if_can()->frame_dispatcher()->unregister_handler_all(&conflictHandler_); pendingAliasesByTime_.clear(); pendingAliasesByKey_.clear(); return return_ok(); } private: + void handle_conflict(Buffer *message) + { + auto rb = get_buffer_deleter(message); + auto alias = CanDefs::get_src(GET_CAN_FRAME_ID_EFF(*message->data())); + auto it = pendingAliasesByKey_.find(alias); + if (it != pendingAliasesByKey_.end() && !it->hasConflict_) { + it->hasConflict_ = 1; + ++request()->numAliases_; + } + } + + /// Listens to incoming CAN frames and handles alias conflicts. + IncomingFrameHandler::GenericHandler conflictHandler_{this, &BulkAliasAllocator::handle_conflict}; + /// How many count to wait before sending out the RID frames. One count is /// 10 msec (see { \link relative_time } ). static constexpr unsigned ALLOCATE_DELAY = 20; From 4170f433fd9bbd6289238796a5811412ee489413 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 15 Nov 2020 00:28:12 +0100 Subject: [PATCH 071/171] fix whitespace --- src/openlcb/AliasAllocator.cxxtest | 8 ++++---- src/openlcb/AliasAllocator.hxx | 2 +- src/openlcb/BulkAliasAllocator.cxx | 12 +++++++----- src/utils/test_main.hxx | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/openlcb/AliasAllocator.cxxtest b/src/openlcb/AliasAllocator.cxxtest index 621c6559b..2ff91b437 100644 --- a/src/openlcb/AliasAllocator.cxxtest +++ b/src/openlcb/AliasAllocator.cxxtest @@ -1,10 +1,10 @@ #include -#include "utils/async_if_test_helper.hxx" #include "openlcb/AliasAllocator.hxx" #include "openlcb/AliasCache.hxx" -#include "openlcb/CanDefs.hxx" #include "openlcb/BulkAliasAllocator.hxx" +#include "openlcb/CanDefs.hxx" +#include "utils/async_if_test_helper.hxx" namespace openlcb { @@ -94,7 +94,7 @@ protected: expect_packet(StringPrintf(":X10700%03XN;", a)); } } - + Buffer *b_; AliasAllocator alias_allocator_; std::unique_ptr bulkAllocator_; @@ -296,7 +296,7 @@ TEST_F(AsyncAliasAllocatorTest, BulkConflict) wait(); LOG(INFO, "send conflicts"); clear_expect(true); - expect_cid(aliases_.begin()+5, aliases_.end()); + expect_cid(aliases_.begin() + 5, aliases_.end()); send_packet(StringPrintf(":X10700%03XN;", aliases_[0])); send_packet(StringPrintf(":X10700%03XN;", aliases_[1])); wait(); diff --git a/src/openlcb/AliasAllocator.hxx b/src/openlcb/AliasAllocator.hxx index 83bc048b9..b79edb797 100644 --- a/src/openlcb/AliasAllocator.hxx +++ b/src/openlcb/AliasAllocator.hxx @@ -125,7 +125,7 @@ public: /** Returns a new alias to check from the random sequence. Checks that it * is not in the alias cache yet.*/ NodeAlias get_new_seed(); - + /** "Allocate" a buffer from this pool (but without initialization) in * order to get a reserved alias. */ QAsync *reserved_aliases() diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx index ba9deafca..429051b87 100644 --- a/src/openlcb/BulkAliasAllocator.cxx +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -158,15 +158,17 @@ class BulkAliasAllocator : public CallableFlow auto rb = get_buffer_deleter(message); auto alias = CanDefs::get_src(GET_CAN_FRAME_ID_EFF(*message->data())); auto it = pendingAliasesByKey_.find(alias); - if (it != pendingAliasesByKey_.end() && !it->hasConflict_) { + if (it != pendingAliasesByKey_.end() && !it->hasConflict_) + { it->hasConflict_ = 1; ++request()->numAliases_; } } /// Listens to incoming CAN frames and handles alias conflicts. - IncomingFrameHandler::GenericHandler conflictHandler_{this, &BulkAliasAllocator::handle_conflict}; - + IncomingFrameHandler::GenericHandler conflictHandler_ { + this, &BulkAliasAllocator::handle_conflict}; + /// How many count to wait before sending out the RID frames. One count is /// 10 msec (see { \link relative_time } ). static constexpr unsigned ALLOCATE_DELAY = 20; @@ -257,9 +259,9 @@ class BulkAliasAllocator : public CallableFlow }; std::unique_ptr create_bulk_alias_allocator( - IfCan *can_if) { + IfCan *can_if) +{ return std::make_unique(can_if); } - } // namespace openlcb diff --git a/src/utils/test_main.hxx b/src/utils/test_main.hxx index 393cb5ae9..e85348124 100644 --- a/src/utils/test_main.hxx +++ b/src/utils/test_main.hxx @@ -49,12 +49,12 @@ #include "gmock/gmock.h" #include "can_frame.h" +#include "executor/CallableFlow.hxx" #include "executor/Executor.hxx" #include "executor/Service.hxx" #include "os/TempFile.hxx" #include "os/os.h" #include "utils/StringPrintf.hxx" -#include "executor/CallableFlow.hxx" #ifdef WITHGPERFTOOLS #include From de178251ba8ec9f65d6ecc6cc156d44c8485f0c0 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 15 Nov 2020 00:32:09 +0100 Subject: [PATCH 072/171] Fix comments. --- src/openlcb/BulkAliasAllocator.cxx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx index 429051b87..e6f5879df 100644 --- a/src/openlcb/BulkAliasAllocator.cxx +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -38,9 +38,13 @@ namespace openlcb { +/// Implementation of the BulkAliasAllocatorInterface to allocate many aliases +/// at the same time. class BulkAliasAllocator : public CallableFlow { public: + /// Constructor + /// @param iface the openlcb CAN interface BulkAliasAllocator(IfCan *iface) : CallableFlow(iface) { @@ -144,6 +148,7 @@ class BulkAliasAllocator : public CallableFlow } } + /// Called when all RID frames are sent out. Action complete() { if_can()->frame_dispatcher()->unregister_handler_all(&conflictHandler_); @@ -153,6 +158,10 @@ class BulkAliasAllocator : public CallableFlow } private: + /// Callback from the stack for all incoming frames while we are + /// operating. We sniff the alias uot of it and record any conflicts we + /// see. + /// @param message an incoming CAN frame. void handle_conflict(Buffer *message) { auto rb = get_buffer_deleter(message); @@ -187,6 +196,7 @@ class BulkAliasAllocator : public CallableFlow if_can()->frame_write_flow()->send(b, 0); } + /// @return the openlcb CAN interface IfCan *if_can() { return static_cast(service()); @@ -198,6 +208,7 @@ class BulkAliasAllocator : public CallableFlow return (os_get_time_monotonic() - startTime_) / MSEC_TO_NSEC(10); } + /// We store this type in the time-ordered aliases structure. struct PendingAliasInfo { PendingAliasInfo(NodeAlias alias) @@ -208,7 +219,7 @@ class BulkAliasAllocator : public CallableFlow /// The value of the alias unsigned alias_ : 12; - /// The time when the CID requests were sent. Counterd in + /// The time when the CID requests were sent. Counter in /// relative_time(), i.e. 10 msec per increment. unsigned cidTime_ : 8; }; From c57b098f52fb653b069fa980d7b28732cf1b1a62 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 15 Nov 2020 00:37:37 +0100 Subject: [PATCH 073/171] Fixes comments. --- src/openlcb/BulkAliasAllocator.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx index e6f5879df..093894f15 100644 --- a/src/openlcb/BulkAliasAllocator.cxx +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -96,7 +96,7 @@ class BulkAliasAllocator : public CallableFlow pendingAliasesByTime_[i].cidTime_ = relative_time(); } nextToStampTime_ = pendingAliasesByTime_.size(); - /// @todo this is wrong. + // Go back to sending more CID frames as needed. return call_immediately(STATE(send_cid_frames)); } @@ -211,6 +211,8 @@ class BulkAliasAllocator : public CallableFlow /// We store this type in the time-ordered aliases structure. struct PendingAliasInfo { + /// Constructor + /// @param alias the openlcb alias that is being represented here. PendingAliasInfo(NodeAlias alias) : alias_(alias) , cidTime_(0) @@ -228,6 +230,8 @@ class BulkAliasAllocator : public CallableFlow /// We store this type in the sorted map lookup structure. struct AliasLookupInfo { + /// Constructor + /// @param alias the openlcb alias that is being represented here. AliasLookupInfo(NodeAlias alias) : alias_(alias) , hasConflict_(0) From 6b8fba2f1b4adf40ee14e0283caf241bc27492a8 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 15 Nov 2020 14:31:18 +0100 Subject: [PATCH 074/171] fixes comments. --- src/openlcb/AliasAllocator.hxx | 2 +- src/openlcb/BulkAliasAllocator.cxx | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/openlcb/AliasAllocator.hxx b/src/openlcb/AliasAllocator.hxx index b79edb797..4abf9d5ff 100644 --- a/src/openlcb/AliasAllocator.hxx +++ b/src/openlcb/AliasAllocator.hxx @@ -112,7 +112,7 @@ public: virtual ~AliasAllocator(); /** @return the Node ID for the interface. */ - NodeID if_id() + NodeID if_node_id() { return if_id_; } diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx index 093894f15..d4767f418 100644 --- a/src/openlcb/BulkAliasAllocator.cxx +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -50,6 +50,8 @@ class BulkAliasAllocator : public CallableFlow { } + /// Start of flow when a request arrives to allocate many aliases. Resets + /// the internal state and goes on to start the allocation process. Action entry() override { startTime_ = os_get_time_monotonic(); @@ -74,7 +76,7 @@ class BulkAliasAllocator : public CallableFlow for (unsigned i = 0; i < needed; ++i) { NodeAlias next_alias = if_can()->alias_allocator()->get_new_seed(); - auto if_id = if_can()->alias_allocator()->if_id(); + auto if_id = if_can()->alias_allocator()->if_node_id(); send_can_frame(next_alias, (if_id >> 36) & 0xfff, 7); send_can_frame(next_alias, (if_id >> 24) & 0xfff, 6); send_can_frame(next_alias, (if_id >> 12) & 0xfff, 5); @@ -90,10 +92,11 @@ class BulkAliasAllocator : public CallableFlow /// Adds the timestamps when the CID requests were sent out. Action stamp_time() { + auto ctime = relative_time(); for (unsigned i = nextToStampTime_; i < pendingAliasesByTime_.size(); ++i) { - pendingAliasesByTime_[i].cidTime_ = relative_time(); + pendingAliasesByTime_[i].cidTime_ = ctime; } nextToStampTime_ = pendingAliasesByTime_.size(); // Go back to sending more CID frames as needed. From 0626614b1fdd49c36afe54ea01ff2422b1c7ac56 Mon Sep 17 00:00:00 2001 From: Robert Heller Date: Sun, 15 Nov 2020 09:04:21 -0500 Subject: [PATCH 075/171] Fix OpenMRNLite problem with DEFAULT symbol (#462) We need to avoid DEFAULT as a symbol due to Arduino.h --- src/freertos_drivers/common/WifiDefs.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/freertos_drivers/common/WifiDefs.hxx b/src/freertos_drivers/common/WifiDefs.hxx index fc4a841e2..3005737a2 100644 --- a/src/freertos_drivers/common/WifiDefs.hxx +++ b/src/freertos_drivers/common/WifiDefs.hxx @@ -33,7 +33,7 @@ enum class WlanState : uint8_t enum class WlanRole : uint8_t { UNKNOWN = 0, /**< Default mode (from stored configuration) */ - DEFAULT = UNKNOWN, /**< Default mode (from stored configuration) */ + DEFAULT_ROLE = UNKNOWN, /**< Default mode (from stored configuration) */ STA, /**< Wi-Fi station mode */ AP /**< Wi-Fi access point mode */ }; From 51b6cfb3b034d52f0565c7a15ddb94959ad0f4fc Mon Sep 17 00:00:00 2001 From: Robert Heller Date: Sat, 21 Nov 2020 06:29:29 -0500 Subject: [PATCH 076/171] Protect unimplemented POSIX calls in BroadcastTime::clear_timezone() to allow OpenMRNLite to compile on Arduino flavored platforms. (#464) setenv() and tset() not available in the Arduino ESP32 platform. This fix takes those calls out. I don't know what (if anything) could replace them, but making BroadcastTime::clear_timezone() seems like a harmless short term solution. === * Protect unimplemented POSIX calls in BroadcastTime::clear_timezone() to allow OpenMRNLite to compile on Arduino flavored platforms. * Protect unimplemented POSIX calls in BroadcastTime::clear_timezone() to allow OpenMRNLite to compile on arduino-esp32. --- src/openlcb/BroadcastTime.cxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/openlcb/BroadcastTime.cxx b/src/openlcb/BroadcastTime.cxx index fb5a91be0..0e2a8630a 100644 --- a/src/openlcb/BroadcastTime.cxx +++ b/src/openlcb/BroadcastTime.cxx @@ -46,8 +46,10 @@ namespace openlcb // void BroadcastTime::clear_timezone() { +#ifndef ESP32 setenv("TZ", "GMT0", 1); tzset(); +#endif } } // namespace openlcb From c044aeda8aa76960c690a85795f004101d860f38 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sat, 21 Nov 2020 09:52:30 -0600 Subject: [PATCH 077/171] update to C++14 for cov and Linux.x86 targets. (#465) --- etc/cov.mk | 2 +- etc/linux.x86.mk | 2 +- src/utils/StoredBitSet.cxxtest | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/etc/cov.mk b/etc/cov.mk index 12082186f..37c865f8c 100644 --- a/etc/cov.mk +++ b/etc/cov.mk @@ -30,7 +30,7 @@ CSHAREDFLAGS = -c -frandom-seed=$(shell echo $(abspath $<) | md5sum | sed 's/\( CFLAGS = $(CSHAREDFLAGS) -std=gnu99 $(CFLAGSEXTRA) -CXXFLAGS = $(CSHAREDFLAGS) -std=c++1y -D__STDC_FORMAT_MACROS \ +CXXFLAGS = $(CSHAREDFLAGS) -std=c++14 -D__STDC_FORMAT_MACROS \ -D__STDC_LIMIT_MACROS $(CXXFLAGSEXTRA) #-D__LINEAR_MAP__ diff --git a/etc/linux.x86.mk b/etc/linux.x86.mk index bd3f9f5e6..27f940908 100644 --- a/etc/linux.x86.mk +++ b/etc/linux.x86.mk @@ -35,7 +35,7 @@ CFLAGS = $(CSHAREDFLAGS) -std=gnu99 \ $(CFLAGSENV) $(CFLAGSEXTRA) \ -CXXFLAGS = $(CSHAREDFLAGS) -std=c++0x -D__STDC_FORMAT_MACROS \ +CXXFLAGS = $(CSHAREDFLAGS) -std=c++14 -D__STDC_FORMAT_MACROS \ -D__STDC_LIMIT_MACROS $(CXXFLAGSENV) \ $(CXXFLAGSENV) $(CXXFLAGSEXTRA) \ diff --git a/src/utils/StoredBitSet.cxxtest b/src/utils/StoredBitSet.cxxtest index f0cf54efe..cd67e6212 100644 --- a/src/utils/StoredBitSet.cxxtest +++ b/src/utils/StoredBitSet.cxxtest @@ -54,7 +54,7 @@ TEST(ShadowedBitSetSingleTest, size) } class BitSetMultiTest - : public ::testing::TestWithParam> + : public ::testing::TestWithParam> { protected: BitSetMultiTest() @@ -63,11 +63,11 @@ protected: unsigned get_size() { - return std::tr1::get<0>(GetParam()); + return std::get<0>(GetParam()); } uint8_t get_granularity() { - return std::tr1::get<1>(GetParam()); + return std::get<1>(GetParam()); } #define expect_all_zero(x...) \ From 028f3d423319dd7ebd3f9b36b7a40bb54d099f1e Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sat, 21 Nov 2020 10:02:18 -0600 Subject: [PATCH 078/171] Fix console session close bug (#466) * Remove redundant file close. * add commentary. --- src/console/Console.hxx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/console/Console.hxx b/src/console/Console.hxx index 4444e5c40..41d8cf0b7 100644 --- a/src/console/Console.hxx +++ b/src/console/Console.hxx @@ -369,7 +369,9 @@ private: */ HASSERT(fdIn == fdOut); fclose(fp); - close(fdIn); + /* There is no need for a "close(fdIn)" because the "fclose(fp)" + * will already have completed that operation. + */ free(line); } From 6e669afd0e737b9ed2269a80b4d7cf9d54ba8173 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 22 Nov 2020 14:07:17 +0100 Subject: [PATCH 079/171] Adds more test helpers to run code on the main executor. (#468) Adds more test helpers: - RunInConstruct allows running a piece of code within a sequence of constructors called. This is helpful when a test is instantiating a bunch of objects, and something should be done between two of those objects' constructor (e.g. injecting something to a recently created object before the next object gets created). This could previously only be done by making everything a unique_ptr and constructing by hand. - RunInConstructOnMainExecutor is the same but executes the code on the main executor. Needed for injecting aliases into IfCan objects. - RX is a shorthand for run_x([this](){ statement }); which is a bit cumbersome to write. === * Adds helpers to run code inline with class construction order. * Adds a shorter alternative to run_x. * Removes duplicate definition of RX test macro. --- src/openlcb/IfCan.cxxtest | 2 -- src/utils/test_main.hxx | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/openlcb/IfCan.cxxtest b/src/openlcb/IfCan.cxxtest index 42e66c3bc..c291f775f 100644 --- a/src/openlcb/IfCan.cxxtest +++ b/src/openlcb/IfCan.cxxtest @@ -36,8 +36,6 @@ TEST_F(AsyncIfTest, InjectFrame) wait(); } -#define RX(args) run_x([this]() { args; }) - TEST_F(AsyncIfTest, InjectFrameAndExpectHandler) { StrictMock h; diff --git a/src/utils/test_main.hxx b/src/utils/test_main.hxx index e85348124..cfbd77840 100644 --- a/src/utils/test_main.hxx +++ b/src/utils/test_main.hxx @@ -178,6 +178,31 @@ void run_x(std::function fn) g_executor.sync_run(std::move(fn)); } +/// Runs some code in the constructor. Useful if custom code needs to be +/// injected into the constructor initialization order. +class RunInConstruct +{ +public: + RunInConstruct(std::function f) + { + f(); + } +}; + +/// Runs some code in the constructor on the main executor. +class RunInConstructOnMain +{ +public: + RunInConstructOnMain(std::function f) + { + run_x(f); + } +}; + +/// Helper macro to make running certain test commands run on the main executor +/// simpler. +#define RX(statement) run_x([&](){ statement; }) + /// Structure holding returned objects for an invoke_flow_nowait command. template struct PendingInvocation { From 7311f43edc95871aae6b11d7b7aade0db9a9b7f6 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 22 Nov 2020 14:20:35 +0100 Subject: [PATCH 080/171] Stores reserved aliases in the local alias cache (#467) Updates the code and API of the AliasAllocator to store the list of aliases that are reserved but unused in the local_aliases() cache. This allows a large number of aliases to be pre-allocated using the BulkAliasAllocator without incurring extra memory overhead for keeping a list of Buffer<> objects in a queue. This PR makes the output of the BulkAliasAllocator be usable by the IfCan send flows. The API contract of for alias allocation between the IfCan send flow and the AliasAllocator changes. Previously it was using a QAsync; how there is a direct sync/async API exposed on the AliasAllocator object. This new API simplifies the interaction because the checking for conflicts on reserved aliases is not the responsibility of the IfCan flow anymore but the AliasAllocator. There is a feature that gets impacted by this change: to keep a certain number of reserved aliases around at all times even if they are not in use by any local node ID. The storage and API to trigger this feature was in the queued object that does not exist anymore. The feature is now triggered instead by an OVERRIDE_CONST. The default behavior will also change from true to false: previously an OpenMRN IO board would allocate two aliases and use only one of them; after this PR only one alias will be allocated and used by a simple IO board. Applications that use many virtual nodes should set the OVERRIDE_CONST instead. Adds an API to the AliasCache to walk entries in node-ID-ascending order. == * start adding code for storing the reserved aliases differently. * Adds a feature to call upper bound on the alias cache. * Rewrites the accessor API for how to get allocated aliases from AliasAllocator. * Updates the API how the reserved aliases are accessed. * Makes create_allocated_alias test helper not require expect_next_alias_allocation call right afterwards. * Removes unneeded alias injection. * Fixes crashing bugs. * removes unneeded call to create alias. * give more sleep time to reduce flakiness. * Adjust implementation to the new alias allocator behavior. * TEST add log * Adds a parameter to control whether a reserved but unused alias will be allocated by the stack or not. * Implements feature to keep a reserved but unused alias in the cache. * Adds more comments to this test case. * switch to using the alloc_result call on the passed-in executor. * Adjusts the alias allocator tests for the new APIs. * Fixes whitespace. * Fix temporarily changed test APIs. * Adds helpers to run code inline with class construction order. * DEBUG statements. * Adds a useful debugging facility. * DEBUG Unnecessary debugging facilities. * Adds a shorter alternative to run_x. * Revert "DEBUG statements." This reverts commit 00b0edd65c029ed0d7cc9bdaf3b55dbc8c81f0c4. * Revert "DEBUG Unnecessary debugging facilities." This reverts commit 2a7bbc20221172b0c546e14fbcbadeb8b918697b. * Fixes comments. * fix whitespace. --- include/nmranet_config.h | 4 + src/openlcb/AliasAllocator.cxx | 91 +++++++++--- src/openlcb/AliasAllocator.cxxtest | 165 ++++++++++++++++----- src/openlcb/AliasAllocator.hxx | 54 +++++-- src/openlcb/AliasCache.cxx | 31 ++++ src/openlcb/AliasCache.cxxtest | 28 +++- src/openlcb/AliasCache.hxx | 10 ++ src/openlcb/IfCan.cxxtest | 30 ++-- src/openlcb/IfCanImpl.hxx | 44 +----- src/openlcb/IfImpl.cxxtest | 1 - src/openlcb/NodeInitializeFlow.cxxtest | 1 - src/openlcb/ProtocolIdentification.cxxtest | 1 - src/openlcb/SimpleStack.cxx | 10 -- src/openlcb/TractionConsist.cxxtest | 1 - src/openlcb/TractionTestTrain.cxxtest | 1 - src/openlcb/TractionThrottle.cxxtest | 1 - src/openlcb/TractionTrain.cxxtest | 1 - src/openlcb/nmranet_constants.cxx | 3 + src/utils/SocketClient.cxxtest | 4 +- src/utils/async_if_test_helper.hxx | 8 +- 20 files changed, 338 insertions(+), 151 deletions(-) diff --git a/include/nmranet_config.h b/include/nmranet_config.h index 287e9a481..1e44f39e3 100644 --- a/include/nmranet_config.h +++ b/include/nmranet_config.h @@ -122,6 +122,10 @@ DECLARE_CONST(remote_alias_cache_size); /** Number of entries in the local alias cache */ DECLARE_CONST(local_alias_cache_size); +/** Keep this many allocated but unused aliases around. (Currently supported + * values are 0 or 1.) */ +DECLARE_CONST(reserve_unused_alias_count); + /** Maximum number of local nodes */ DECLARE_CONST(local_nodes_count); diff --git a/src/openlcb/AliasAllocator.cxx b/src/openlcb/AliasAllocator.cxx index 343fa1ea7..4a6b1b740 100644 --- a/src/openlcb/AliasAllocator.cxx +++ b/src/openlcb/AliasAllocator.cxx @@ -33,6 +33,7 @@ */ #include "openlcb/AliasAllocator.hxx" +#include "nmranet_config.h" #include "openlcb/CanDefs.hxx" namespace openlcb @@ -47,6 +48,7 @@ AliasAllocator::AliasAllocator(NodeID if_id, IfCan *if_can) , if_id_(if_id) , cid_frame_sequence_(0) , conflict_detected_(0) + , reserveUnusedAliases_(config_reserve_unused_alias_count()) { reinit_seed(); // Moves all the allocated alias buffers over to the input queue for @@ -89,15 +91,68 @@ void AliasAllocator::return_alias(NodeID id, NodeAlias alias) if_can()->frame_write_flow()->send(b); } - // This is synchronous allocation, which is not nice. + add_allocated_alias(alias); +} + +void AliasAllocator::add_allocated_alias(NodeAlias alias) +{ + // Note: We leak aliases here in case of eviction by the AliasCache + // object. This is okay for two reasons: 1) Generally the local alias cache + // size should be about equal to the local nodes count. 2) OpenLCB alias + // allocation algorithm is able to reuse aliases that were allocated by + // nodes that are not on the network anymore. + if_can()->local_aliases()->add( + CanDefs::get_reserved_alias_node_id(alias), alias); + if (!waitingClients_.empty()) + { + // Wakes up exactly one executable that is waiting for an alias. + Executable *w = static_cast(waitingClients_.next().item); + // This schedules a state flow onto its executor. + w->alloc_result(nullptr); + } +} + +NodeAlias AliasAllocator::get_allocated_alias( + NodeID destination_id, Executable *done) +{ + NodeID found_id; + NodeAlias found_alias = 0; + bool allocate_new = false; + bool found = if_can()->local_aliases()->next_entry( + CanDefs::get_reserved_alias_node_id(0), &found_id, &found_alias); + if (found) + { + found = (found_id == CanDefs::get_reserved_alias_node_id(found_alias)); + } + if (found) { - auto* b = alloc(); - b->data()->reset(); - b->data()->alias = alias; + if_can()->local_aliases()->add(destination_id, found_alias); + if (reserveUnusedAliases_) + { + NodeID next_id; + NodeAlias next_alias = 0; + if (!if_can()->local_aliases()->next_entry( + CanDefs::get_reserved_alias_node_id(0), &next_id, + &next_alias) || + !CanDefs::is_reserved_alias_node_id(next_id)) + { + allocate_new = true; + } + } + } + else + { + found_alias = 0; + allocate_new = true; + waitingClients_.insert(done); + } + if (allocate_new) + { + Buffer *b = alloc(); b->data()->do_not_reallocate(); - b->data()->state = AliasInfo::STATE_RESERVED; - reserved_aliases()->insert(b); + this->send(b); } + return found_alias; } AliasAllocator::~AliasAllocator() @@ -128,6 +183,8 @@ NodeAlias AliasAllocator::get_new_seed() { NodeAlias ret = seed_; next_seed(); + LOG(VERBOSE, "(%p) alias test seed is %03X (next %03X)", this, ret, + seed_); if (if_can()->local_aliases()->lookup(ret)) { continue; @@ -136,6 +193,7 @@ NodeAlias AliasAllocator::get_new_seed() { continue; } + LOG(VERBOSE, "alias get seed is %03X (next %03X)", ret, seed_); return ret; } } @@ -232,10 +290,7 @@ StateFlowBase::Action AliasAllocator::send_rid_frame() pending_alias()->state = AliasInfo::STATE_RESERVED; if_can()->frame_dispatcher()->unregister_handler( &conflictHandler_, pending_alias()->alias, ~0x1FFFF000U); - if_can()->local_aliases()->add( - CanDefs::get_reserved_alias_node_id(pending_alias()->alias), - pending_alias()->alias); - reserved_alias_pool_.insert(transfer_message()); + add_allocated_alias(pending_alias()->alias); return release_and_exit(); } @@ -264,21 +319,9 @@ void AliasAllocator::TEST_finish_pending_allocation() { } } -void AliasAllocator::TEST_add_allocated_alias(NodeAlias alias, bool repeat) +void AliasAllocator::TEST_add_allocated_alias(NodeAlias alias) { - Buffer *a; - mainBufferPool->alloc(&a); - a->data()->reset(); - a->data()->alias = alias; - a->data()->state = AliasInfo::STATE_RESERVED; - if (!repeat) - { - a->data()->do_not_reallocate(); - } - if_can()->local_aliases()->add( - CanDefs::get_reserved_alias_node_id(a->data()->alias), - a->data()->alias); - reserved_aliases()->insert(a); + add_allocated_alias(alias); } } // namespace openlcb diff --git a/src/openlcb/AliasAllocator.cxxtest b/src/openlcb/AliasAllocator.cxxtest index 2ff91b437..fc4fd6df8 100644 --- a/src/openlcb/AliasAllocator.cxxtest +++ b/src/openlcb/AliasAllocator.cxxtest @@ -33,7 +33,9 @@ protected: unsigned next_seed(AliasAllocator *alloc = nullptr) { if (!alloc) + { alloc = &alias_allocator_; + } alloc->next_seed(); return alloc->seed_; } @@ -42,12 +44,22 @@ protected: * until one is available. The alias will be saved into the buffer b_. */ void get_next_alias() { - while (b_ = static_cast *>( - alias_allocator_.reserved_aliases()->next().item), - !b_) + NodeAlias a; + nextAliasNodeId_++; + do { - usleep(10); - } + run_x([this, &a]() { + a = alias_allocator_.get_allocated_alias( + nextAliasNodeId_, &ex_); + }); + if (!a) + { + n_.wait_for_notification(); + } + } while (a == 0); + b_ = alias_allocator_.alloc(); + b_->data()->alias = a; + b_->data()->state = AliasInfo::STATE_RESERVED; } /// Pre-generates some aliases into a vector. @@ -75,10 +87,14 @@ protected: NodeAlias a = *it; string msg = StringPrintf("cid %03X", a); LOG(INFO, "cid %03X", a); - expect_packet(StringPrintf(":X17020%03XN;", a)); - expect_packet(StringPrintf(":X1610D%03XN;", a)); - expect_packet(StringPrintf(":X15000%03XN;", a)); - expect_packet(StringPrintf(":X14003%03XN;", a)); + expect_packet(StringPrintf(":X17020%03XN;", a)) + .RetiresOnSaturation(); + expect_packet(StringPrintf(":X1610D%03XN;", a)) + .RetiresOnSaturation(); + expect_packet(StringPrintf(":X15000%03XN;", a)) + .RetiresOnSaturation(); + expect_packet(StringPrintf(":X14003%03XN;", a)) + .RetiresOnSaturation(); } } @@ -91,14 +107,37 @@ protected: { NodeAlias a = *it; LOG(INFO, "rid %03X", a); - expect_packet(StringPrintf(":X10700%03XN;", a)); + expect_packet(StringPrintf(":X10700%03XN;", a)) + .RetiresOnSaturation(); } } + /// Helper class to pass into the asynchronous alias allocation wait. Will + /// notify n_ when the alias is ready to be taken. + class AllocExecutable : public Executable + { + public: + AllocExecutable(AsyncAliasAllocatorTest *parent) + : parent_(parent) + { + } + void run() override + { + } + void alloc_result(QMember *item) override + { + parent_->n_.notify(); + } + AsyncAliasAllocatorTest *parent_; + } ex_ {this}; + friend class AllocExecutable; + Buffer *b_; AliasAllocator alias_allocator_; std::unique_ptr bulkAllocator_; std::vector aliases_; + /// Will use this node ID to mark the next alias gotten. + openlcb::NodeID nextAliasNodeId_ = TEST_NODE_ID + 1; }; TEST_F(AsyncAliasAllocatorTest, SetupTeardown) @@ -113,21 +152,75 @@ TEST_F(AsyncAliasAllocatorTest, AllocateOne) expect_packet(":X1610D555N;"); expect_packet(":X15000555N;"); expect_packet(":X14003555N;"); - - mainBufferPool->alloc(&b_); - alias_allocator_.send(b_); - b_ = nullptr; - - /** @todo(balazs.racz) this should be after the wait because there should be - * a delay before sending it. */ expect_packet(":X10700555N;"); - wait(); get_next_alias(); ASSERT_TRUE(b_); EXPECT_EQ(0x555U, b_->data()->alias); EXPECT_EQ(AliasInfo::STATE_RESERVED, b_->data()->state); } + +TEST_F(AsyncAliasAllocatorTest, ReserveThenAllocate) +{ + set_seed(0x555); + clear_expect(true); + mainBufferPool->alloc(&b_); + expect_packet(":X17020555N;"); + expect_packet(":X1610D555N;"); + expect_packet(":X15000555N;"); + expect_packet(":X14003555N;"); + alias_allocator_.send(b_); + wait(); + expect_packet(":X10700555N;"); + usleep(250000); + wait(); + clear_expect(true); + run_x([this]() { + EXPECT_EQ(0x555, + alias_allocator_.get_allocated_alias(nextAliasNodeId_, nullptr)); + EXPECT_EQ(nextAliasNodeId_, + ifCan_->local_aliases()->lookup(NodeAlias(0x555))); + }); +} + +TEST_F(AsyncAliasAllocatorTest, ReserveUnused) +{ + alias_allocator_.TEST_set_reserve_unused_alias_count(1); + set_seed(0x555); + clear_expect(true); + expect_packet(":X17020555N;"); + expect_packet(":X1610D555N;"); + expect_packet(":X15000555N;"); + expect_packet(":X14003555N;"); + run_x([this]() { + EXPECT_EQ( + 0, alias_allocator_.get_allocated_alias(nextAliasNodeId_, &ex_)); + }); + wait(); + clear_expect(true); + expect_packet(":X10700555N;"); + usleep(250000); + clear_expect(true); + expect_packet(":X17020AAAN;"); + expect_packet(":X1610DAAAN;"); + expect_packet(":X15000AAAN;"); + expect_packet(":X14003AAAN;"); + set_seed(0xAAA); + run_x([this]() { + EXPECT_EQ(0x555, + alias_allocator_.get_allocated_alias(nextAliasNodeId_, &ex_)); + }); + wait(); + clear_expect(true); + expect_packet(":X10700AAAN;"); + usleep(250000); + run_x([this]() { + // This one should be marked as reserved. + EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0xAAA), + ifCan_->local_aliases()->lookup(NodeAlias(0xAAA))); + }); +} + #if 0 TEST_F(AsyncAliasAllocatorTest, TestDelay) { @@ -148,13 +241,11 @@ TEST_F(AsyncAliasAllocatorTest, TestDelay) TEST_F(AsyncAliasAllocatorTest, AllocateMultiple) { set_seed(0x555); - mainBufferPool->alloc(&b_); expect_packet(":X17020555N;"); expect_packet(":X1610D555N;"); expect_packet(":X15000555N;"); expect_packet(":X14003555N;"); expect_packet(":X10700555N;"); - alias_allocator_.send(b_); b_ = nullptr; get_next_alias(); EXPECT_EQ(0x555U, b_->data()->alias); @@ -167,15 +258,10 @@ TEST_F(AsyncAliasAllocatorTest, AllocateMultiple) expect_packet(":X14003AAAN;"); expect_packet(":X10700AAAN;"); - mainBufferPool->alloc(&b_); - alias_allocator_.send(b_); - b_ = nullptr; - /* Conflicts with the previous alias to be tested. That's not a problem at * this point however, because that alias has already left the * allocator. */ - send_packet( - ":X10700555N;"); + send_packet(":X10700555N;"); get_next_alias(); EXPECT_EQ(0xAAAU, b_->data()->alias); @@ -184,6 +270,7 @@ TEST_F(AsyncAliasAllocatorTest, AllocateMultiple) TEST_F(AsyncAliasAllocatorTest, AllocationConflict) { + clear_expect(true); set_seed(0x555); mainBufferPool->alloc(&b_); expect_packet(":X17020555N;"); @@ -193,6 +280,7 @@ TEST_F(AsyncAliasAllocatorTest, AllocationConflict) alias_allocator_.send(b_); b_ = nullptr; wait(); + clear_expect(true); set_seed(0xAA5); expect_packet(":X17020AA5N;"); expect_packet(":X1610DAA5N;"); @@ -200,10 +288,8 @@ TEST_F(AsyncAliasAllocatorTest, AllocationConflict) expect_packet(":X14003AA5N;"); expect_packet(":X10700AA5N;"); send_packet(":X10700555N;"); - - get_next_alias(); - EXPECT_EQ(0xAA5U, b_->data()->alias); - EXPECT_EQ(AliasInfo::STATE_RESERVED, b_->data()->state); + twait(); + clear_expect(true); run_x([this]() { // This one should be marked as reserved. EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0xAA5), @@ -211,10 +297,20 @@ TEST_F(AsyncAliasAllocatorTest, AllocationConflict) // This one should be unknown. EXPECT_EQ(0U, ifCan_->local_aliases()->lookup(NodeAlias(0x555))); }); + + get_next_alias(); + EXPECT_EQ(0xAA5U, b_->data()->alias); + EXPECT_EQ(AliasInfo::STATE_RESERVED, b_->data()->state); + run_x([this]() { + // This one should be marked for the new node ID. + EXPECT_EQ(nextAliasNodeId_, + ifCan_->local_aliases()->lookup(NodeAlias(0xAA5))); + }); } TEST_F(AsyncAliasAllocatorTest, LateAllocationConflict) { + clear_expect(true); set_seed(0x555); mainBufferPool->alloc(&b_); expect_packet(":X17020555N;"); @@ -224,6 +320,7 @@ TEST_F(AsyncAliasAllocatorTest, LateAllocationConflict) alias_allocator_.send(b_); b_ = nullptr; wait(); + clear_expect(true); set_seed(0xAA5); usleep(100000); expect_packet(":X17020AA5N;"); @@ -232,9 +329,11 @@ TEST_F(AsyncAliasAllocatorTest, LateAllocationConflict) expect_packet(":X14003AA5N;"); send_packet(":X10700555N;"); wait(); + clear_expect(true); usleep(100000); expect_packet(":X10700AA5N;"); send_packet(":X10700555N;"); + twait(); get_next_alias(); EXPECT_EQ(0xAA5U, b_->data()->alias); EXPECT_EQ(AliasInfo::STATE_RESERVED, b_->data()->state); @@ -281,6 +380,7 @@ TEST_F(AsyncAliasAllocatorTest, BulkFew) expect_rid(aliases_.begin(), aliases_.end()); LOG(INFO, "wait for complete"); invocation->wait(); + wait(); clear_expect(true); auto end_time = os_get_time_monotonic(); EXPECT_LT(MSEC_TO_NSEC(200), end_time - start_time); @@ -312,14 +412,13 @@ TEST_F(AsyncAliasAllocatorTest, BulkConflict) TEST_F(AsyncAliasAllocatorTest, BulkMany) { + clear_expect(true); generate_aliases(ifCan_->alias_allocator(), 150); expect_cid(aliases_.begin(), aliases_.end()); + expect_rid(aliases_.begin(), aliases_.end()); LOG(INFO, "invoke"); auto invocation = invoke_flow_nowait(bulkAllocator_.get(), 150); wait(); - LOG(INFO, "expect RIDs"); - clear_expect(true); - expect_rid(aliases_.begin(), aliases_.end()); LOG(INFO, "wait for complete"); invocation->wait(); clear_expect(true); diff --git a/src/openlcb/AliasAllocator.hxx b/src/openlcb/AliasAllocator.hxx index 4abf9d5ff..8874f4c6a 100644 --- a/src/openlcb/AliasAllocator.hxx +++ b/src/openlcb/AliasAllocator.hxx @@ -69,9 +69,9 @@ struct AliasInfo } /** The current alias. This is 0 if the alias needs to be generated. */ - unsigned alias : 12; - unsigned state : 3; - unsigned return_to_reallocation : 1; + uint16_t alias : 12; + uint16_t state : 3; + uint16_t return_to_reallocation : 1; enum State { @@ -126,17 +126,32 @@ public: * is not in the alias cache yet.*/ NodeAlias get_new_seed(); - /** "Allocate" a buffer from this pool (but without initialization) in - * order to get a reserved alias. */ - QAsync *reserved_aliases() - { - return &reserved_alias_pool_; - } + /** Allocates an alias from the reserved but unused aliases list. If there + * is a free alias there, that alias will be reassigned to destination_id + * in the local alias cache, and done will never be notified. If there is + * no free alias, then a new alias will be allocated, and done will be + * notified when the allocation is complete. Then the call has to be + * re-tried by the destination flow. + * @param destination_id if there is a free alias right now, it will be + * assigned to this Node ID in the local alias cache. + * @param done if an async allocation is necessary, this will be notified + * after a new alias has been received. + * @return the alias if the it was allocated inline, or 0 if there will be + * an asynchronous notification coming later. */ + NodeAlias get_allocated_alias(NodeID destination_id, Executable *done); /** Releases a given alias. Sends out an AMR frame and puts the alias into * the reserved aliases queue. */ void return_alias(NodeID id, NodeAlias alias); + /** Call from an alternate alias allocator. Marks that alias is reserved + * for the local interface (RID frame is just sent out). Adds the alias to + * the local alias cache and wakes up a flow that might be waiting for an + * alias. + * @param alias a reserved node alias. */ + void add_allocated_alias(NodeAlias alias); + +#ifdef GTEST /** If there is a pending alias allocation waiting for the timer to expire, * finishes it immediately. Needed in test destructors. */ void TEST_finish_pending_allocation(); @@ -144,8 +159,15 @@ public: /** Adds an allocated aliad to the reserved aliases queue. @param alias the next allocated alias to add. */ - void TEST_add_allocated_alias(NodeAlias alias, bool repeat=false); - + void TEST_add_allocated_alias(NodeAlias alias); + + /** Overrides the configured value for reserve_unused_alias_count. */ + void TEST_set_reserve_unused_alias_count(unsigned count) + { + reserveUnusedAliases_ = count; + } +#endif + private: /** Listens to incoming CAN frames and handles alias conflicts. */ class ConflictHandler : public IncomingFrameHandler @@ -183,10 +205,8 @@ private: StateFlowTimer timer_; - /** Freelist of reserved aliases that can be used by virtual nodes. The - AliasAllocatorFlow will post successfully reserved aliases to this - allocator. */ - QAsync reserved_alias_pool_; + /// Set of client flows that are waiting for allocating an alias. + Q waitingClients_; /// 48-bit nodeID that we will use for alias reservations. NodeID if_id_; @@ -206,6 +226,10 @@ private: /// Seed for generating random-looking alias numbers. unsigned seed_ : 12; + /// How many unused aliases we should reserve. Currently we only support 0 + /// or 1 as value. + unsigned reserveUnusedAliases_ : 8; + /// Notifiable used for tracking outgoing frames. BarrierNotifiable n_; diff --git a/src/openlcb/AliasCache.cxx b/src/openlcb/AliasCache.cxx index cb4585603..d4258fbcf 100644 --- a/src/openlcb/AliasCache.cxx +++ b/src/openlcb/AliasCache.cxx @@ -36,6 +36,7 @@ #include #include "os/OS.hxx" +#include "utils/logging.h" #ifdef GTEST #define TEST_CONSISTENCY @@ -236,6 +237,17 @@ void AliasCache::clear() } } +void debug_print_entry(void *, NodeID id, NodeAlias alias) +{ + LOG(INFO, "[%012" PRIx64 "]: %03X", id, alias); +} + +void debug_print_cache(AliasCache *c) +{ + LOG(INFO, "Alias cache:"); + c->for_each(&debug_print_entry, nullptr); +} + /** Add an alias to an alias cache. * @param id 48-bit NMRAnet Node ID to associate alias with * @param alias 12-bit alias associated with Node ID @@ -391,6 +403,25 @@ bool AliasCache::retrieve(unsigned entry, NodeID* node, NodeAlias* alias) return true; } +bool AliasCache::next_entry(NodeID bound, NodeID *node, NodeAlias *alias) +{ + auto it = idMap.upper_bound(bound); + if (it == idMap.end()) + { + return false; + } + Metadata *metadata = it->deref(this); + if (alias) + { + *alias = metadata->alias_; + } + if (node) + { + *node = metadata->get_node_id(); + } + return true; +} + /** Lookup a node's alias based on its Node ID. * @param id Node ID to look for * @return alias that matches the Node ID, else 0 if not found diff --git a/src/openlcb/AliasCache.cxxtest b/src/openlcb/AliasCache.cxxtest index f2f0ca637..c778a6e6a 100644 --- a/src/openlcb/AliasCache.cxxtest +++ b/src/openlcb/AliasCache.cxxtest @@ -52,6 +52,22 @@ static void alias_callback(void *context, NodeID node_id, NodeAlias alias) count++; } +/// Verifies that calling the next method gives back the correct next node ID. +void test_alias_next(AliasCache *cache, int count) +{ + NodeID last = 0; + NodeID next = 0; + NodeAlias next_alias; + for (int i = 0; i < count; i++) + { + ASSERT_TRUE(cache->next_entry(last, &next, &next_alias)); + EXPECT_EQ(node_ids[i], next); + EXPECT_EQ(aliases[i], next_alias); + last = next; + } + EXPECT_FALSE(cache->next_entry(last, &next, &next_alias)); +} + TEST(AliasCacheTest, constructor) { /* construct an object, map in a node, and run the for_each */ @@ -103,6 +119,8 @@ TEST(AliasCacheTest, ordering) EXPECT_TRUE(aliasCache->lookup((NodeID)103) == 6); EXPECT_TRUE(aliasCache->lookup((NodeID)102) == 11); EXPECT_TRUE(aliasCache->lookup((NodeID)101) == 10); + + test_alias_next(aliasCache, 6); } TEST(AliasCacheTest, reordering) @@ -126,8 +144,10 @@ TEST(AliasCacheTest, reordering) EXPECT_TRUE(aliasCache->lookup((NodeAlias)84) == 104); EXPECT_TRUE(aliasCache->lookup((NodeAlias)6) == 103); EXPECT_TRUE(aliasCache->lookup((NodeAlias)11) == 102); - EXPECT_TRUE(aliasCache->lookup((NodeAlias)10) == 101); - + EXPECT_TRUE(aliasCache->lookup((NodeAlias)10) == 101); + + test_alias_next(aliasCache, 6); + aliasCache->for_each(alias_callback, (void*)0xDEADBEEF); EXPECT_EQ(count, 6); @@ -152,7 +172,9 @@ TEST(AliasCacheTest, reordering) EXPECT_TRUE(aliasCache->lookup((NodeID)104) == 84); EXPECT_TRUE(aliasCache->lookup((NodeID)103) == 6); EXPECT_TRUE(aliasCache->lookup((NodeID)102) == 11); - EXPECT_TRUE(aliasCache->lookup((NodeID)101) == 10); + EXPECT_TRUE(aliasCache->lookup((NodeID)101) == 10); + + test_alias_next(aliasCache, 6); } TEST(AliasCacheTest, generate) diff --git a/src/openlcb/AliasCache.hxx b/src/openlcb/AliasCache.hxx index 69fe1a5d8..1483d6092 100644 --- a/src/openlcb/AliasCache.hxx +++ b/src/openlcb/AliasCache.hxx @@ -162,6 +162,16 @@ public: */ bool retrieve(unsigned entry, NodeID* node, NodeAlias* alias); + /** Retrieves the next entry by increasing node ID. + * @param bound is a Node ID. Will search for the next largest node ID + * (upper bound of this key). + * @param node will be filled with the node ID. May be null. + * @param alias will be filled with the alias. May be null. + * @return true if a larger element is found and node and alias were + * filled, otherwise false if bound is >= the largest node ID in the cache. + */ + bool next_entry(NodeID bound, NodeID *node, NodeAlias *alias); + /** Generate a 12-bit pseudo-random alias for a given alias cache. * @return pseudo-random 12-bit alias, an alias of zero is invalid */ diff --git a/src/openlcb/IfCan.cxxtest b/src/openlcb/IfCan.cxxtest index c291f775f..cb83e49c1 100644 --- a/src/openlcb/IfCan.cxxtest +++ b/src/openlcb/IfCan.cxxtest @@ -347,7 +347,6 @@ TEST_F(AsyncMessageCanTests, WriteByMTIAllocatesLocalAlias) auto *b = ifCan_->global_message_write_flow()->alloc(); create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000004;"); expect_packet(":X195B433AN0102030405060708;"); b->data()->reset(Defs::MTI_EVENT_REPORT, TEST_NODE_ID + 1, @@ -396,7 +395,7 @@ TEST_F(AsyncMessageCanTests, AliasConflictCIDReply) TEST_F(AsyncMessageCanTests, ReservedAliasReclaimed) { - /** In this test we exercie the case when an alias that was previously + /** In this test we exercise the case when an alias that was previously * reserved by us but not used for any virtual node yet experiences various * conflicts. In the first case we see a regular CID conflict that gets * replied to. In the second case we see someone else actively using that @@ -404,21 +403,27 @@ TEST_F(AsyncMessageCanTests, ReservedAliasReclaimed) * detected at the time the next outgoing virtual node tries to allocate * that alias, and we'll test that it actually generates a new one * instead. */ - RX(ifCan_->local_aliases()->remove(NodeAlias(0x22A))); // resets the cache. + ifCan_->alias_allocator()->TEST_set_reserve_unused_alias_count(1); + RX(ifCan_->local_aliases()->clear()); // resets the cache. + // Sets up a regular alias for our standard node. + RX(ifCan_->alias_allocator()->add_allocated_alias(0x33A)); auto* b = ifCan_->global_message_write_flow()->alloc(); - create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000003;"); expect_packet(":X195B433AN0102030405060708;"); + LOG(INFO, "Next alias %03X", aliasSeed_); + // When we send out a packet from our node, a new alias will be grabbed. + expect_next_alias_allocation(); b->data()->reset(Defs::MTI_EVENT_REPORT, TEST_NODE_ID, eventid_to_buffer(UINT64_C(0x0102030405060708))); ifCan_->global_message_write_flow()->send(b); + wait(); + // Ticks down the time for the new alias to take hold. usleep(250000); wait(); - // Here we have the next reserved alias. + // Checks that we have the next reserved alias. RX(EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0x44C), ifCan_->local_aliases()->lookup(NodeAlias(0x44C)))); - // A CID packet gets replied to. + // A CID packet on that alias gets replied to. send_packet_and_expect_response(":X1478944CN;", ":X1070044CN;"); // We still have it in the cache. @@ -429,8 +434,8 @@ TEST_F(AsyncMessageCanTests, ReservedAliasReclaimed) wait(); RX(EXPECT_EQ(0U, ifCan_->local_aliases()->lookup(NodeAlias(0x44C)))); - // At this point we have an invalid alias in the reserved_aliases() - // queue. We check here that a new node gets a new alias. + // At this point we have no valid reserved alias in the cache. We check + // here that a new node gets a new alias. expect_next_alias_allocation(); // Unfortunately we have to guess the second next alias here because we // can't inject it. We can only inject one alias at a time, but now two @@ -443,11 +448,11 @@ TEST_F(AsyncMessageCanTests, ReservedAliasReclaimed) eventid_to_buffer(UINT64_C(0x0102030405060709))); b->set_done(get_notifiable()); ifCan_->global_message_write_flow()->send(b); - wait_for_notification(); + wait_for_notification(); // Will wait for one alias allocation to complete. RX(EXPECT_EQ( TEST_NODE_ID + 1, ifCan_->local_aliases()->lookup(NodeAlias(0x44D)))); - usleep(250000); + usleep(250000); // Second alias allocation to complete. RX(EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0x6AA), ifCan_->local_aliases()->lookup(NodeAlias(0x6AA)))); } @@ -769,7 +774,6 @@ TEST_F(AsyncNodeTest, SendAddressedMessageFromNewNodeWithCachedAlias) // Simulate cache miss on local alias cache. RX(ifCan_->local_aliases()->remove(0x22A)); create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000003;"); // AMD for our new alias. // And the frame goes out. expect_packet(":X1948833AN0210050101FFFFDD;"); @@ -789,7 +793,6 @@ TEST_F(AsyncNodeTest, SendAddressedMessageFromNewNodeWithCacheMiss) // Simulate cache miss on local alias cache. RX(ifCan_->local_aliases()->remove(0x22A)); create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000003;"); // AMD for our new alias. // And the new alias will do the lookup. Not with an AME frame but straight // to the verify node id. @@ -816,7 +819,6 @@ TEST_F(AsyncNodeTest, SendAddressedMessageFromNewNodeWithCacheMissTimeout) // Simulate cache miss on local alias cache. RX(ifCan_->local_aliases()->remove(0x22A)); create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000003;"); // AMD for our new alias. // And the new alias will do the lookup. Not with an AME frame but straight // to the verify node id. diff --git a/src/openlcb/IfCanImpl.hxx b/src/openlcb/IfCanImpl.hxx index ccb8282c6..5d3455d4e 100644 --- a/src/openlcb/IfCanImpl.hxx +++ b/src/openlcb/IfCanImpl.hxx @@ -104,51 +104,17 @@ private: * @TODO(balazs.racz): implement proper local alias reclaim/reuse * mechanism. */ HASSERT(if_can()->alias_allocator()); - return allocate_and_call( - STATE(take_new_alias), - if_can()->alias_allocator()->reserved_aliases()); - } - - Action take_new_alias() - { - /* In this function we do a number of queries to the local alias - * cache. It is important that these queries show a consistent state; - * we do not need to hold any mutex though because only the current - * executor is allowed to access that object. */ - NodeAlias alias = 0; + NodeAlias alias = if_can()->alias_allocator()->get_allocated_alias( + nmsg()->src.id, this); + if (!alias) { - Buffer *new_alias = - full_allocation_result(if_can()->alias_allocator()); - HASSERT(new_alias->data()->alias); - alias = new_alias->data()->alias; - /* Sends the alias back for reallocating. This will trigger the - * alias allocator flow. */ - if (new_alias->data()->return_to_reallocation) - { - new_alias->data()->reset(); - if_can()->alias_allocator()->send(new_alias); - } - else - { - new_alias->unref(); - } + // wait for notification and re-try this step. + return wait(); } LOG(INFO, "Allocating new alias %03X for node %012" PRIx64, alias, nmsg()->src.id); - // Checks that there was no conflict on this alias. - if (!CanDefs::is_reserved_alias_node_id( - if_can()->local_aliases()->lookup(alias))) - { - LOG(INFO, "Alias has seen conflict: %03X", alias); - // Problem. Let's take another alias. - return call_immediately(STATE(allocate_new_alias)); - } - srcAlias_ = alias; - /** @TODO(balazs.racz): We leak aliases here in case of eviction by the - * AliasCache object. */ - if_can()->local_aliases()->add(nmsg()->src.id, alias); // Take a CAN frame to send off the AMD frame. return allocate_and_call(if_can()->frame_write_flow(), STATE(send_amd_frame)); diff --git a/src/openlcb/IfImpl.cxxtest b/src/openlcb/IfImpl.cxxtest index 37ba84c1b..2f019c05f 100644 --- a/src/openlcb/IfImpl.cxxtest +++ b/src/openlcb/IfImpl.cxxtest @@ -14,7 +14,6 @@ public: TwoNodeTest() { create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000004;"); expect_packet(":X1910033AN02010D000004;"); secondNode_.reset( diff --git a/src/openlcb/NodeInitializeFlow.cxxtest b/src/openlcb/NodeInitializeFlow.cxxtest index a353beba5..4735f8261 100644 --- a/src/openlcb/NodeInitializeFlow.cxxtest +++ b/src/openlcb/NodeInitializeFlow.cxxtest @@ -36,7 +36,6 @@ TEST_F(AsyncIfTest, TwoNodesInitialize) wait(); expect_packet(":X1070133AN02010d000004;"); // AMD frame expect_packet(":X1910033AN02010d000004;"); // initialization complete - expect_next_alias_allocation(); DefaultNode node2(ifCan_.get(), TEST_NODE_ID + 1); wait(); } diff --git a/src/openlcb/ProtocolIdentification.cxxtest b/src/openlcb/ProtocolIdentification.cxxtest index 83545d81f..6dbd1e995 100644 --- a/src/openlcb/ProtocolIdentification.cxxtest +++ b/src/openlcb/ProtocolIdentification.cxxtest @@ -67,7 +67,6 @@ TEST_F(ProtocolIdentificationTest, DoNotHandleTest) expect_packet(":X1070133AN02010d000004;"); // AMD frame expect_packet(":X1910033AN02010d000004;"); // initialization complete create_allocated_alias(); - expect_next_alias_allocation(); DefaultNode node2(ifCan_.get(), TEST_NODE_ID + 1); wait(); send_packet(":X198289FFN033A;"); diff --git a/src/openlcb/SimpleStack.cxx b/src/openlcb/SimpleStack.cxx index 17e69460f..dd0214252 100644 --- a/src/openlcb/SimpleStack.cxx +++ b/src/openlcb/SimpleStack.cxx @@ -199,16 +199,6 @@ void SimpleCanStackBase::start_iface(bool restart) if_can()->alias_allocator()->reinit_seed(); if_can()->local_aliases()->clear(); if_can()->remote_aliases()->clear(); - // Deletes all reserved aliases from the queue. - while (!if_can()->alias_allocator()->reserved_aliases()->empty()) - { - Buffer *a = static_cast *>( - if_can()->alias_allocator()->reserved_aliases()->next().item); - if (a) - { - a->unref(); - } - } } // Bootstraps the fresh alias allocation process. diff --git a/src/openlcb/TractionConsist.cxxtest b/src/openlcb/TractionConsist.cxxtest index 111e21391..06ad096a3 100644 --- a/src/openlcb/TractionConsist.cxxtest +++ b/src/openlcb/TractionConsist.cxxtest @@ -29,7 +29,6 @@ class ConsistTest : public TractionTest protected: ConsistTest() { - create_allocated_alias(); run_x([this]() { otherIf_.local_aliases()->add(nodeIdLead, 0x770); otherIf_.local_aliases()->add(nodeIdC1, 0x771); diff --git a/src/openlcb/TractionTestTrain.cxxtest b/src/openlcb/TractionTestTrain.cxxtest index e44566efc..d8a1db91d 100644 --- a/src/openlcb/TractionTestTrain.cxxtest +++ b/src/openlcb/TractionTestTrain.cxxtest @@ -44,7 +44,6 @@ protected: LoggingTrainTest() : trainImpl_(1732) { create_allocated_alias(); - expect_next_alias_allocation(); // alias reservation expect_packet(":X1070133AN0601000006C4;"); // initialized diff --git a/src/openlcb/TractionThrottle.cxxtest b/src/openlcb/TractionThrottle.cxxtest index 01695e960..8f4f66de4 100644 --- a/src/openlcb/TractionThrottle.cxxtest +++ b/src/openlcb/TractionThrottle.cxxtest @@ -15,7 +15,6 @@ protected: ThrottleTest() { print_all_packets(); - create_allocated_alias(); run_x( [this]() { otherIf_.local_aliases()->add(TRAIN_NODE_ID, 0x771); }); trainNode_.reset(new TrainNodeForProxy(&trainService_, &trainImpl_)); diff --git a/src/openlcb/TractionTrain.cxxtest b/src/openlcb/TractionTrain.cxxtest index 91a19e31d..9c8c386c7 100644 --- a/src/openlcb/TractionTrain.cxxtest +++ b/src/openlcb/TractionTrain.cxxtest @@ -95,7 +95,6 @@ protected: TractionSingleMockTest() { create_allocated_alias(); - expect_next_alias_allocation(); EXPECT_CALL(m1_, legacy_address()).Times(AtLeast(0)).WillRepeatedly( Return(0x00003456U)); EXPECT_CALL(m1_, legacy_address_type()) diff --git a/src/openlcb/nmranet_constants.cxx b/src/openlcb/nmranet_constants.cxx index c246c047b..8476bf031 100644 --- a/src/openlcb/nmranet_constants.cxx +++ b/src/openlcb/nmranet_constants.cxx @@ -39,6 +39,9 @@ DEFAULT_CONST(remote_alias_cache_size, 10); /** Number of entries in the local alias cache */ DEFAULT_CONST(local_alias_cache_size, 3); +/** Keep this many allocated but unused aliases around. */ +DEFAULT_CONST(reserve_unused_alias_count, 0); + /** Maximum number of local nodes */ DEFAULT_CONST(local_nodes_count, 2); diff --git a/src/utils/SocketClient.cxxtest b/src/utils/SocketClient.cxxtest index 199bfd560..1277fb50c 100644 --- a/src/utils/SocketClient.cxxtest +++ b/src/utils/SocketClient.cxxtest @@ -151,9 +151,9 @@ TEST_F(SocketClientTest, connect_mdns) std::bind(&SocketClientTest::connect_callback, this, _1, _2))); EXPECT_CALL(*this, connect_callback(_, sc_.get())).Times(1); - usleep(10000); + usleep(20000); wait(); - usleep(10000); + usleep(20000); wait(); } diff --git a/src/utils/async_if_test_helper.hxx b/src/utils/async_if_test_helper.hxx index 2355a9b0b..f2af652f9 100644 --- a/src/utils/async_if_test_helper.hxx +++ b/src/utils/async_if_test_helper.hxx @@ -378,19 +378,19 @@ protected: * alias. */ void create_allocated_alias() { - inject_allocated_alias(0x33A, true); + inject_allocated_alias(0x33A); aliasSeed_ = 0x44C; pendingAliasAllocation_ = false; } - void inject_allocated_alias(NodeAlias alias, bool repeat = false) + void inject_allocated_alias(NodeAlias alias) { if (!ifCan_->alias_allocator()) { ifCan_->set_alias_allocator( new AliasAllocator(TEST_NODE_ID, ifCan_.get())); } - run_x([this, alias, repeat]() { - ifCan_->alias_allocator()->TEST_add_allocated_alias(alias, repeat); + run_x([this, alias]() { + ifCan_->alias_allocator()->TEST_add_allocated_alias(alias); }); } From 2bcdef3d13c6fc60b50be3f09143f53ece1f84e6 Mon Sep 17 00:00:00 2001 From: Mike Dunston Date: Sun, 22 Nov 2020 10:54:31 -0800 Subject: [PATCH 081/171] Add support for C++11 compilation of std::make_unique() (#470) --- src/openlcb/BulkAliasAllocator.cxx | 1 + src/utils/MakeUnique.hxx | 50 ++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 src/utils/MakeUnique.hxx diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx index d4767f418..b59203c09 100644 --- a/src/openlcb/BulkAliasAllocator.cxx +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -34,6 +34,7 @@ #include "openlcb/BulkAliasAllocator.hxx" #include "openlcb/CanDefs.hxx" +#include "utils/MakeUnique.hxx" namespace openlcb { diff --git a/src/utils/MakeUnique.hxx b/src/utils/MakeUnique.hxx new file mode 100644 index 000000000..2e3b8e7ad --- /dev/null +++ b/src/utils/MakeUnique.hxx @@ -0,0 +1,50 @@ +/** + * \file MakeUnique.hxx + * + * C++11 version of std::make_unique which is only available from c++14 or + * later. + * + * This is based on https://isocpp.org/files/papers/N3656.txt. + * + * The __cplusplus constant reference is from: + * http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2014/n3938.html + * + */ + +// Check if we are building with less than C++14 and if so we need to define +// the std::make_unique() API. +#if __cplusplus < 201402L + +#include +#include +#include + +namespace std +{ + +template +unique_ptr make_unique_helper(false_type, Args&&... args) +{ + return unique_ptr(new T(forward(args)...)); +} + +template +unique_ptr make_unique_helper(true_type, Args&&... args) +{ + static_assert(extent::value == 0, + "make_unique() is forbidden, please use make_unique()."); + + typedef typename remove_extent::type U; + return unique_ptr(new U[sizeof...(Args)]{forward(args)...}); +} + +template +unique_ptr make_unique(Args&&... args) +{ + return make_unique_helper(is_array(), forward(args)...); +} + +} + +#endif // __cplusplus < 201402L + From 3f837c1a001f87bfd8c56e4e86853e94ad8eaed5 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 22 Nov 2020 20:06:34 +0100 Subject: [PATCH 082/171] Fix compile errors. --- src/openlcb/AliasAllocator.cxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/openlcb/AliasAllocator.cxx b/src/openlcb/AliasAllocator.cxx index 4a6b1b740..12f488734 100644 --- a/src/openlcb/AliasAllocator.cxx +++ b/src/openlcb/AliasAllocator.cxx @@ -313,6 +313,8 @@ void AliasAllocator::ConflictHandler::send(Buffer *message, message->unref(); } +#ifdef GTEST + void AliasAllocator::TEST_finish_pending_allocation() { if (is_state(STATE(wait_done))) { timer_.trigger(); @@ -324,4 +326,6 @@ void AliasAllocator::TEST_add_allocated_alias(NodeAlias alias) add_allocated_alias(alias); } +#endif + } // namespace openlcb From a22e3cbff9461288f826c9954081fbb90de4b2a8 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 22 Nov 2020 23:00:53 +0100 Subject: [PATCH 083/171] Supports nullptr as argument to the node init flow. (#472) --- src/openlcb/NodeInitializeFlow.hxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/openlcb/NodeInitializeFlow.hxx b/src/openlcb/NodeInitializeFlow.hxx index dfda0ee6d..b62f39a84 100644 --- a/src/openlcb/NodeInitializeFlow.hxx +++ b/src/openlcb/NodeInitializeFlow.hxx @@ -80,6 +80,10 @@ private: Action entry() OVERRIDE { + if (!node()) + { + return release_and_exit(); + } HASSERT(message()->data()->node); return allocate_and_call( node()->iface()->global_message_write_flow(), From 2e5358eb6ac187c1db91d32af4a7d91a42f563c0 Mon Sep 17 00:00:00 2001 From: Mike Dunston Date: Tue, 24 Nov 2020 13:00:34 -0800 Subject: [PATCH 084/171] Adjust DeviceBuffer to use OPENMRN_FEATURE_DEVTAB instead of ARDUINO define. (#474) --- src/freertos_drivers/common/DeviceBuffer.cxx | 6 +++-- src/freertos_drivers/common/DeviceBuffer.hxx | 26 +++++++++++--------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/freertos_drivers/common/DeviceBuffer.cxx b/src/freertos_drivers/common/DeviceBuffer.cxx index 3516e65d1..249690619 100644 --- a/src/freertos_drivers/common/DeviceBuffer.cxx +++ b/src/freertos_drivers/common/DeviceBuffer.cxx @@ -34,7 +34,9 @@ #include "DeviceBuffer.hxx" -#ifndef ARDUINO +#include "openmrn_features.h" + +#ifdef OPENMRN_FEATURE_DEVTAB #include @@ -61,4 +63,4 @@ void DeviceBufferBase::block_until_condition(File *file, bool read) ::select(fd + 1, read ? &fds : NULL, read ? NULL : &fds, NULL, NULL); } -#endif +#endif // OPENMRN_FEATURE_DEVTAB diff --git a/src/freertos_drivers/common/DeviceBuffer.hxx b/src/freertos_drivers/common/DeviceBuffer.hxx index 7d4fe8b74..ac7d70688 100644 --- a/src/freertos_drivers/common/DeviceBuffer.hxx +++ b/src/freertos_drivers/common/DeviceBuffer.hxx @@ -39,32 +39,34 @@ #include #include #include + +#include "openmrn_features.h" #include "utils/macros.h" -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB #include "Devtab.hxx" -#endif +#endif // OPENMRN_FEATURE_DEVTAB /** Helper for DeviceBuffer which allows for methods to not be inlined. */ class DeviceBufferBase { public: -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB /** Wait for blocking condition to become true. * @param file file to wait on * @param read true if this is a read operation, false for write operation */ static void block_until_condition(File *file, bool read); -#endif +#endif // OPENMRN_FEATURE_DEVTAB /** Signal the wakeup condition. This will also wakeup select. */ void signal_condition() { -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB Device::select_wakeup(&selectInfo); -#endif +#endif // OPENMRN_FEATURE_DEVTAB } /** Signal the wakeup condition from an ISR context. This will also @@ -72,10 +74,10 @@ public: */ void signal_condition_from_isr() { -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB int woken = 0; Device::select_wakeup_from_isr(&selectInfo, &woken); -#endif +#endif // OPENMRN_FEATURE_DEVTAB } /** flush all the data out of the buffer and reset the buffer. It is @@ -110,9 +112,9 @@ public: */ void select_insert() { -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB return Device::select_insert(&selectInfo); -#endif +#endif // OPENMRN_FEATURE_DEVTAB } /** Remove a number of items from the buffer by advancing the readIndex. @@ -180,10 +182,10 @@ protected: { } -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB /** Metadata for select() logic */ Device::SelectInfo selectInfo; -#endif +#endif // OPENMRN_FEATURE_DEVTAB /** level of space required in buffer in order to wakeup, 0 if unused */ uint16_t level; From d3f829ce7600f87878aaeb6b9da5769c0090a549 Mon Sep 17 00:00:00 2001 From: Mike Dunston Date: Wed, 25 Nov 2020 05:00:52 -0800 Subject: [PATCH 085/171] ESP32: Prep for Arduino-esp32 v1.0.5 (#473) * Adjust the ESP-IDF version detection logic. The original logic worked with ESP-IDF v3.2 and v4.0+ due to `ESP_IDF_VERSION_MAJOR` only being defined in v4.0+. With arduino-esp32 v1.0.5 picking up ESP-IDF v3.3 this is no longer working due to ESP-IDF v3.3 now defining this constant. Shifting to use __has_include to switch between the new and old include paths with fallback to use old paths when __has_include is not usable (though it always should be with GCC). This will need to be revisited as part of the ESP-IDF v4.x rework that I have in progress since there are a few other breaking API changes starting with ESP-IDF v4.1. * Update Esp32WiFiManager.cxx --- .../esp32/Esp32WiFiManager.cxx | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/freertos_drivers/esp32/Esp32WiFiManager.cxx b/src/freertos_drivers/esp32/Esp32WiFiManager.cxx index a7c36cc14..1381f5702 100644 --- a/src/freertos_drivers/esp32/Esp32WiFiManager.cxx +++ b/src/freertos_drivers/esp32/Esp32WiFiManager.cxx @@ -48,17 +48,42 @@ #include #include -// ESP-IDF v4+ has a slightly different directory structure to previous -// versions. -#ifdef ESP_IDF_VERSION_MAJOR -// ESP-IDF v4+ +// Starting in ESP-IDF v4.0 a few header files have been relocated so we need +// to adjust the include paths accordingly. If the __has_include preprocessor +// directive is defined we can use it to find the appropriate header files. +// If it is not usable then we will default the older header filenames. +#if defined(__has_include) + +// rom/crc.h was relocated to esp32/rom/crc.h in ESP-IDF v4.0 +// TODO: This will need to be platform specific in IDF v4.1 since this is +// exposed in unique header paths for each supported platform. Detecting the +// operating platform (ESP32, ESP32-S2, ESP32-S3, etc) can be done by checking +// for the presence of one of the following defines: +// CONFIG_IDF_TARGET_ESP32 -- ESP32 +// CONFIG_IDF_TARGET_ESP32S2 -- ESP32-S2 +// CONFIG_IDF_TARGET_ESP32S3 -- ESP32-S3 +// If none of these are defined it means the ESP-IDF version is v4.0 or +// earlier. +#if __has_include("esp32/rom/crc.h") #include +#else +#include +#endif + +// esp_wifi_internal.h was relocated to esp_private/wifi.h in ESP-IDF v4.0 +#if __has_include("esp_private/wifi.h") #include #else -// ESP-IDF v3.x +#include +#endif + +#else + +// We are unable to use __has_include, default to the old include paths. #include #include -#endif // ESP_IDF_VERSION_MAJOR + +#endif // defined __has_include using openlcb::NodeID; using openlcb::SimpleCanStack; From b99cbea42aea49585550e706153fef5fd3118f4e Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 28 Nov 2020 10:56:35 +0100 Subject: [PATCH 086/171] CC32xx set mac feature (#478) * Adds API call to set the wifi MAC address. --- src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx | 8 ++++++++ src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx index 2abd43825..bb5dcb399 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx @@ -565,6 +565,14 @@ void CC32xxWiFi::wlan_mac(uint8_t mac[6]) sl_NetCfgGet(SL_NETCFG_MAC_ADDRESS_GET, nullptr, &len, mac); } +/* + * CC32xxWiFi::wlan_set_mac() + */ +void CC32xxWiFi::wlan_set_mac(uint8_t mac[6]) +{ + sl_NetCfgSet(SL_NETCFG_MAC_ADDRESS_SET, 1, 6, mac); +} + /* * CC32xxWiFi::test_mode_start() */ diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx index 2a2b8fd50..52abc7db3 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx @@ -379,6 +379,15 @@ public: */ void wlan_mac(uint8_t mac[6]); + /** Sets the device MAC address. WARNING. The MAC address will be + * persistently set to the value indicated. Only a factory reset of the + * device can undo this operation. After calling this API there is no way + * to recover the factory MAC address. Make sure not to call this API too + * many times in the lifetime of the product, as flash wear is a concern. + * @param mac 6 byte array which holds the desired MAC address. + */ + void wlan_set_mac(uint8_t mac[6]); + /** Get the assigned IP address. * @return assigned IP address, else 0 if not assigned */ From b2f90c9fb515c7e7637b790bfe7c9e63758c0b9a Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 28 Nov 2020 10:57:42 +0100 Subject: [PATCH 087/171] Fixes todo in the bulk alias allocator that makes it actually operable. (#471) Inserts output of bulk allocator to the reserved aliases structure. Adds new features to the alias allocator: - get the number of reserved but unused aliases. - clear these. --- src/openlcb/AliasAllocator.cxx | 44 ++++++++++++++++++++++++++++++ src/openlcb/AliasAllocator.cxxtest | 8 ++++++ src/openlcb/AliasAllocator.hxx | 7 +++++ src/openlcb/BulkAliasAllocator.cxx | 2 +- 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/src/openlcb/AliasAllocator.cxx b/src/openlcb/AliasAllocator.cxx index 12f488734..e81f91f61 100644 --- a/src/openlcb/AliasAllocator.cxx +++ b/src/openlcb/AliasAllocator.cxx @@ -79,6 +79,50 @@ void seed_alias_allocator(AliasAllocator* aliases, Pool* pool, int n) { } } +/** @return the number of aliases that are reserved and available for new + * virtual nodes to use. */ +unsigned AliasAllocator::num_reserved_aliases() +{ + unsigned cnt = 0; + NodeID found_id = CanDefs::get_reserved_alias_node_id(0); + NodeAlias found_alias = 0; + do + { + if (if_can()->local_aliases()->next_entry( + found_id, &found_id, &found_alias) && + CanDefs::is_reserved_alias_node_id(found_id)) + { + ++cnt; + } + else + { + break; + } + } while (true); + return cnt; +} + +/** Removes all aliases that are reserved but not yet used. */ +void AliasAllocator::clear_reserved_aliases() +{ + do + { + NodeID found_id = CanDefs::get_reserved_alias_node_id(0); + NodeAlias found_alias = 0; + if (if_can()->local_aliases()->next_entry( + CanDefs::get_reserved_alias_node_id(0), &found_id, + &found_alias) && + CanDefs::is_reserved_alias_node_id(found_id)) + { + if_can()->local_aliases()->remove(found_alias); + } + else + { + break; + } + } while (true); +} + void AliasAllocator::return_alias(NodeID id, NodeAlias alias) { // This is synchronous allocation, which is not nice. diff --git a/src/openlcb/AliasAllocator.cxxtest b/src/openlcb/AliasAllocator.cxxtest index fc4fd6df8..4813a442c 100644 --- a/src/openlcb/AliasAllocator.cxxtest +++ b/src/openlcb/AliasAllocator.cxxtest @@ -334,7 +334,9 @@ TEST_F(AsyncAliasAllocatorTest, LateAllocationConflict) expect_packet(":X10700AA5N;"); send_packet(":X10700555N;"); twait(); + RX(EXPECT_EQ(1u, ifCan_->alias_allocator()->num_reserved_aliases())); get_next_alias(); + RX(EXPECT_EQ(0u, ifCan_->alias_allocator()->num_reserved_aliases())); EXPECT_EQ(0xAA5U, b_->data()->alias); EXPECT_EQ(AliasInfo::STATE_RESERVED, b_->data()->state); } @@ -369,6 +371,7 @@ TEST_F(AsyncAliasAllocatorTest, DifferentGenerated) TEST_F(AsyncAliasAllocatorTest, BulkFew) { + RX(EXPECT_EQ(0u, ifCan_->alias_allocator()->num_reserved_aliases())); generate_aliases(ifCan_->alias_allocator(), 5); expect_cid(aliases_.begin(), aliases_.end()); LOG(INFO, "invoke"); @@ -384,6 +387,11 @@ TEST_F(AsyncAliasAllocatorTest, BulkFew) clear_expect(true); auto end_time = os_get_time_monotonic(); EXPECT_LT(MSEC_TO_NSEC(200), end_time - start_time); + RX({ + EXPECT_EQ(5u, ifCan_->alias_allocator()->num_reserved_aliases()); + ifCan_->alias_allocator()->clear_reserved_aliases(); + EXPECT_EQ(0u, ifCan_->alias_allocator()->num_reserved_aliases()); + }); } TEST_F(AsyncAliasAllocatorTest, BulkConflict) diff --git a/src/openlcb/AliasAllocator.hxx b/src/openlcb/AliasAllocator.hxx index 8874f4c6a..81f8b6592 100644 --- a/src/openlcb/AliasAllocator.hxx +++ b/src/openlcb/AliasAllocator.hxx @@ -140,6 +140,13 @@ public: * an asynchronous notification coming later. */ NodeAlias get_allocated_alias(NodeID destination_id, Executable *done); + /** @return the number of aliases that are reserved and available for new + * virtual nodes to use. */ + unsigned num_reserved_aliases(); + + /** Removes all aliases that are reserved but not yet used. */ + void clear_reserved_aliases(); + /** Releases a given alias. Sends out an AMR frame and puts the alias into * the reserved aliases queue. */ void return_alias(NodeID id, NodeAlias alias); diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx index b59203c09..dea531fa1 100644 --- a/src/openlcb/BulkAliasAllocator.cxx +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -134,7 +134,7 @@ class BulkAliasAllocator : public CallableFlow // we skip this alias because there was a conflict. continue; } - /// @todo add alias to the cache as reserved alias. + if_can()->alias_allocator()->add_allocated_alias(a); ++num_sent; send_can_frame(a, CanDefs::RID_FRAME, 0); } From d70607ec2faf9dd58bbf5186d71985fac929cee4 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 28 Nov 2020 11:07:29 +0100 Subject: [PATCH 088/171] Adds a method to send out a global alias mapping enquiry to the bus. (#479) * Adds a method to send out a global alias mapping enquiry to the bus. Updates the global AME handler to clear the remote alias cache. This cache will be re-populated from the responses coming back from the bus after the AME frame. * Fixes bug where the local node's alias was lost due to a falsely perceived conflict when a global AME was sent out from it. * Fixes typos. --- src/openlcb/CanDefs.hxx | 4 ++-- src/openlcb/IfCan.cxx | 30 ++++++++++++++++++++++++++++++ src/openlcb/IfCan.cxxtest | 24 ++++++++++++++++++++++++ src/openlcb/IfCan.hxx | 9 +++++++++ 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/openlcb/CanDefs.hxx b/src/openlcb/CanDefs.hxx index 9c230e3db..9f4fdd189 100644 --- a/src/openlcb/CanDefs.hxx +++ b/src/openlcb/CanDefs.hxx @@ -121,12 +121,12 @@ struct CanDefs { HIGH_PRIORITY = 0, /**< high priority CAN message */ NORMAL_PRIORITY = 1 /**< normal priority CAN message */ }; - + enum ControlField { RID_FRAME = 0x0700, /**< Reserve ID Frame */ AMD_FRAME = 0x0701, /**< Alias Map Definition frame */ - AME_FRAME = 0x0702, /**< Alias Mapping Inquery */ + AME_FRAME = 0x0702, /**< Alias Mapping Enquiry */ AMR_FRAME = 0x0703 /**< Alias Map Reset */ }; diff --git a/src/openlcb/IfCan.cxx b/src/openlcb/IfCan.cxx index ad68b5e04..441258229 100644 --- a/src/openlcb/IfCan.cxx +++ b/src/openlcb/IfCan.cxx @@ -362,6 +362,9 @@ class AMEGlobalQueryHandler : public StateFlowBase, return; } needRerun_ = true; + // Drops all remote aliases from the cache to re-populate this cache + // from the network responses. + if_can()->remote_aliases()->clear(); if (is_terminated()) { start_flow(STATE(rerun)); @@ -702,6 +705,33 @@ void IfCan::set_alias_allocator(AliasAllocator *a) aliasAllocator_.reset(a); } +void IfCan::send_global_alias_enquiry(Node *source) +{ + if (!source->is_initialized()) + { + LOG_ERROR("Tried to send global AME from not initialized node."); + return; + } + NodeAlias send_alias = local_aliases()->lookup(source->node_id()); + if (!send_alias) + { + LOG_ERROR("Tried to send global AME without a local alias."); + return; + } + { + auto *b = frame_write_flow()->alloc(); + CanDefs::control_init(*b->data(), send_alias, CanDefs::AME_FRAME, 0); + // Sends it out + frame_write_flow()->send(b); + } + { + // Sends another to the local node, but not using the local alias. + auto *b = frame_dispatcher()->alloc(); + CanDefs::control_init(*b->data(), 0, CanDefs::AME_FRAME, 0); + frame_dispatcher()->send(b); + } +} + void IfCan::add_addressed_message_support() { if (addressedWriteFlow_) diff --git a/src/openlcb/IfCan.cxxtest b/src/openlcb/IfCan.cxxtest index cb83e49c1..34c1a3b11 100644 --- a/src/openlcb/IfCan.cxxtest +++ b/src/openlcb/IfCan.cxxtest @@ -137,6 +137,30 @@ TEST_F(AsyncIfTest, AMESupport) wait(); } +TEST_F(AsyncNodeTest, GlobalAMESupport) +{ + EXPECT_TRUE(node_->is_initialized()); + RX({ + ifCan_->local_aliases()->add(UINT64_C(0x050101011877), 0x729U); + ifCan_->remote_aliases()->add(UINT64_C(0x050101011811), 0x111U); + EXPECT_EQ( + 0x111u, ifCan_->remote_aliases()->lookup(UINT64_C(0x050101011811))); + }); + // The enquiry is sent out... + expect_packet(":X1070222AN;"); + // ...along with all local aliases... + expect_packet(":X1070122AN02010D000003;"); + expect_packet(":X10701729N050101011877;"); + RX(ifCan_->send_global_alias_enquiry(node_)); + wait(); + // and the remote cache was cleared. + RX(EXPECT_EQ( + 0u, ifCan_->remote_aliases()->lookup(UINT64_C(0x050101011811)))); + // Checks regression: we did not lose the local node alias. + RX(EXPECT_EQ( + 0x22Au, ifCan_->local_aliases()->lookup(node_->node_id()))); +} + TEST_F(AsyncNodeTest, NodeIdLookupLocal) { NodeIdLookupFlow lflow(ifCan_.get()); diff --git a/src/openlcb/IfCan.hxx b/src/openlcb/IfCan.hxx index 484aa5be7..b5121483f 100644 --- a/src/openlcb/IfCan.hxx +++ b/src/openlcb/IfCan.hxx @@ -115,6 +115,15 @@ public: /// Sets the alias allocator for this If. Takes ownership of pointer. void set_alias_allocator(AliasAllocator *a); + /// Sends a global alias enquiry packet. This will also clear the remote + /// alias cache (in this node and in all other OpenMRN-based nodes on the + /// bus) and let it re-populate from the responses coming back. This call + /// should be used very sparingly because of the effect it has on the + /// entire bus. + /// @param source a local node to use for sending this message out. Must be + /// already initialized. + void send_global_alias_enquiry(Node *source); + void add_owned_flow(Executable *e) override; bool matching_node(NodeHandle expected, NodeHandle actual) override; From f3609a0b953714d1b56f728360740af159ceef3d Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 28 Nov 2020 20:31:25 +0100 Subject: [PATCH 089/171] Adds a fake clock utility. (#481) * Adds a fake clock utility. A fake clock allows a unit test to be in perfect control of how time is advancing, without using any sleep or usleep statements. * fix whitespace --- src/os/FakeClock.cxx | 57 +++++++++++++++++++++++ src/os/FakeClock.cxxtest | 86 ++++++++++++++++++++++++++++++++++ src/os/FakeClock.hxx | 94 ++++++++++++++++++++++++++++++++++++++ src/os/os.c | 11 ++++- src/os/os.h | 5 ++ src/utils/LinkedObject.hxx | 6 +++ 6 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 src/os/FakeClock.cxx create mode 100644 src/os/FakeClock.cxxtest create mode 100644 src/os/FakeClock.hxx diff --git a/src/os/FakeClock.cxx b/src/os/FakeClock.cxx new file mode 100644 index 000000000..5b1c03441 --- /dev/null +++ b/src/os/FakeClock.cxx @@ -0,0 +1,57 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file FakeClock.cxx + * + * Helper class for unit tests that want to control the advancement of time by + * hand. + * + * @author Balazs Racz + * @date 28 Nov 2020 + */ + +#include "os/FakeClock.hxx" + +#ifdef GTEST + +extern "C" +{ + +long long os_get_fake_time(void) +{ + if (FakeClock::exists()) + { + return FakeClock::instance()->get_time_nsec(); + } + else + { + return -1; + } +} + +} // extern C + +#endif // GTEST diff --git a/src/os/FakeClock.cxxtest b/src/os/FakeClock.cxxtest new file mode 100644 index 000000000..9b5a4e949 --- /dev/null +++ b/src/os/FakeClock.cxxtest @@ -0,0 +1,86 @@ +#include "os/FakeClock.hxx" + +#include "utils/test_main.hxx" + +TEST(FakeClockTest, advance) +{ + long long t1 = os_get_time_monotonic(); + usleep(20000); + long long t2 = os_get_time_monotonic(); + EXPECT_LT(t1 + MSEC_TO_NSEC(20), t2); + + FakeClock clk; + long long tfreeze = os_get_time_monotonic(); + // Upon startup the time should be pretty close. + EXPECT_GT(t2 + MSEC_TO_NSEC(1), tfreeze); + + // Time will not advance too much when frozen. + for (unsigned i = 0; i < 100; ++i) + { + EXPECT_GT(tfreeze + 500, os_get_time_monotonic()); + } + + // But still be monotonic. + t1 = os_get_time_monotonic(); + t2 = os_get_time_monotonic(); + EXPECT_EQ(1, t2 - t1); +} + +TEST(FakeClockTest, independent_test) +{ + // There should be no freezing left over for the next test. + long long t1 = os_get_time_monotonic(); + usleep(20000); + long long t2 = os_get_time_monotonic(); + EXPECT_LT(t1 + MSEC_TO_NSEC(20), t2); +} + +class CountingTimer : public Timer +{ +public: + CountingTimer() + : Timer(g_executor.active_timers()) + { + start(MSEC_TO_NSEC(20)); + } + + long long timeout() override + { + ++count_; + if (needStop_) + { + return DELETE; + } + return RESTART; + } + + bool needStop_ = false; + int count_ = 0; +}; + +TEST(FakeClockTest, executor_timer) +{ + FakeClock clk; + CountingTimer *tim = new CountingTimer; + + EXPECT_EQ(0, tim->count_); + usleep(50000); + wait_for_main_executor(); + EXPECT_EQ(0, tim->count_); + clk.advance(MSEC_TO_NSEC(20) - 100); + wait_for_main_executor(); + EXPECT_EQ(0, tim->count_); + clk.advance(100); + wait_for_main_executor(); + EXPECT_EQ(1, tim->count_); + + clk.advance(MSEC_TO_NSEC(200)); + wait_for_main_executor(); + EXPECT_EQ(11, tim->count_); + clk.advance(MSEC_TO_NSEC(20)); + wait_for_main_executor(); + EXPECT_EQ(12, tim->count_); + tim->needStop_ = true; + clk.advance(MSEC_TO_NSEC(20)); + wait_for_main_executor(); +} diff --git a/src/os/FakeClock.hxx b/src/os/FakeClock.hxx new file mode 100644 index 000000000..fda615f86 --- /dev/null +++ b/src/os/FakeClock.hxx @@ -0,0 +1,94 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file FakeClock.hxx + * + * Helper class for unit tests that want to control the advancement of time by + * hand. + * + * @author Balazs Racz + * @date 28 Nov 2020 + */ + +#ifndef _OS_FAKECLOCK_HXX_ +#define _OS_FAKECLOCK_HXX_ + +#include "executor/Executor.hxx" +#include "os/os.h" +#include "utils/Singleton.hxx" + +/// Stores the private variables of a fake clock. +struct FakeClockContent +{ +protected: + /// @param t the starting timestamp for the fake clock. + FakeClockContent(long long t) + : lastTime_(t) + { + } + + long long lastTime_; +}; + +/// Class that injects a fake progression of time for unit tests. When this +/// class is created, the time as returned by os_get_time_monotonic() +/// freezes. From that point on time only moves forward when advance() is +/// called. +/// +/// There can be at most one instance of this class at any time. +class FakeClock : private FakeClockContent, public Singleton +{ +public: + FakeClock() + : FakeClockContent(os_get_time_monotonic()) + { + } + + /// Advances the time returned by os_get_time_monotonic(). + /// @param nsec how much the time should jump forward (relative to now). + void advance(long long nsec) + { + lastTime_ += nsec; + // Wakes up all executors. This will cause them to evaluate if their + // timers have something expired. + ExecutorBase *current = ExecutorBase::link_head(); + while (current) + { + current->add(new CallbackExecutable([]() {})); + current = current->link_next(); + } + } + + /// @return the currently set time. + long long get_time_nsec() + { + return lastTime_++; + } + +private: +}; + +#endif // _OS_FAKECLOCK_HXX_ diff --git a/src/os/os.c b/src/os/os.c index d4d04b6af..899d35bf6 100644 --- a/src/os/os.c +++ b/src/os/os.c @@ -625,6 +625,7 @@ long long os_get_time_monotonic(void) time *= clockmul; time >>= 2; #else + struct timespec ts; #if defined (__nuttx__) clock_gettime(CLOCK_REALTIME, &ts); @@ -632,7 +633,15 @@ long long os_get_time_monotonic(void) clock_gettime(CLOCK_MONOTONIC, &ts); #endif time = ((long long)ts.tv_sec * 1000000000LL) + ts.tv_nsec; - + +#ifdef GTEST + long long fake_time = os_get_fake_time(); + if (fake_time >= 0) + { + time = fake_time; + } +#endif // not GTEST + #endif /* This logic ensures that every successive call is one value larger * than the last. Each call returns a unique value. diff --git a/src/os/os.h b/src/os/os.h index cda7da346..fe608e145 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -164,6 +164,11 @@ typedef struct */ extern long long os_get_time_monotonic(void); +/** Get the fake time for a unit test. + * @return time in nanoseconds, <= 0 if there is no fake clock. + */ +extern long long os_get_fake_time(void); + #ifndef OPENMRN_FEATURE_MUTEX_PTHREAD /** @ref os_thread_once states. */ diff --git a/src/utils/LinkedObject.hxx b/src/utils/LinkedObject.hxx index 93020fa52..463c03cf3 100644 --- a/src/utils/LinkedObject.hxx +++ b/src/utils/LinkedObject.hxx @@ -90,6 +90,12 @@ public: return static_cast(link_); } + /// @return the subclass pointer of the beginning of the list. + static T *link_head() + { + return static_cast(head_); + } + /// Locks the list for modification (at any entry!). static Atomic* head_mu() { return LinkedObjectHeadMutex::headMu_.get(); From b41e8b5cab0fdb8f8235eeb741b1f03a9d3ad908 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 28 Nov 2020 20:32:07 +0100 Subject: [PATCH 090/171] Fix incorrect global AME replies. (#480) Fixes a bug where a global AME request was also getting replies for reserved but unallocated aliases. These carry an invalid node ID from the local alias cache and should not be sent to the bus. Adds reserved aliases to the test cases and to test that no additional packets show up. --- src/openlcb/IfCan.cxx | 5 +++-- src/openlcb/IfCan.cxxtest | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/openlcb/IfCan.cxx b/src/openlcb/IfCan.cxx index 441258229..60799416e 100644 --- a/src/openlcb/IfCan.cxx +++ b/src/openlcb/IfCan.cxx @@ -382,8 +382,9 @@ class AMEGlobalQueryHandler : public StateFlowBase, { while (nextIndex_ < if_can()->local_aliases()->size()) { - if (if_can()->local_aliases()->retrieve( - nextIndex_, nullptr, nullptr)) + NodeID n; + if (if_can()->local_aliases()->retrieve(nextIndex_, &n, nullptr) && + ((n >> (5 * 8)) != 0)) { return allocate_and_call( if_can()->frame_write_flow(), STATE(fill_response)); diff --git a/src/openlcb/IfCan.cxxtest b/src/openlcb/IfCan.cxxtest index 34c1a3b11..5a7c267b0 100644 --- a/src/openlcb/IfCan.cxxtest +++ b/src/openlcb/IfCan.cxxtest @@ -123,9 +123,13 @@ TEST_F(AsyncIfTest, RemoteAliasLearned) }); } -TEST_F(AsyncIfTest, AMESupport) +TEST_F(AsyncIfTest, AMEReceiveSupport) { + // Example virtual node. RX(ifCan_->local_aliases()->add(UINT64_C(0x050101011877), 0x729U)); + // This is a reserved but unused alias which should not get AMD frames as + // replies for AME. + RX(ifCan_->alias_allocator()->add_allocated_alias(0x567u)); send_packet_and_expect_response(":X10702643N050101011877;", ":X10701729N050101011877;"); wait(); @@ -137,12 +141,13 @@ TEST_F(AsyncIfTest, AMESupport) wait(); } -TEST_F(AsyncNodeTest, GlobalAMESupport) +TEST_F(AsyncNodeTest, GlobalAMESendSupport) { EXPECT_TRUE(node_->is_initialized()); RX({ ifCan_->local_aliases()->add(UINT64_C(0x050101011877), 0x729U); ifCan_->remote_aliases()->add(UINT64_C(0x050101011811), 0x111U); + ifCan_->alias_allocator()->add_allocated_alias(0x567u); EXPECT_EQ( 0x111u, ifCan_->remote_aliases()->lookup(UINT64_C(0x050101011811))); }); From 20aaa2e64a9ec15be75ad55571a8f8f8ac5b29a2 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 28 Nov 2020 20:33:07 +0100 Subject: [PATCH 091/171] Adds a half zero bit after the railcom cutout (#412) * Implements adding a half zero bit after the railcom cutout to make older decoders reset their packet parsing routine. This follows https://nmra.org/sites/default/files/standards/sandrp/pdf/tn-2-05draft2005-02-25_for_rp-9.2.3.pdf * Cleanup some of the unnecessary changes. * Cleans up unnecessary changes. * Adds a configuration option for generating the halfzero. --- src/freertos_drivers/ti/TivaDCC.hxx | 58 ++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/freertos_drivers/ti/TivaDCC.hxx b/src/freertos_drivers/ti/TivaDCC.hxx index 454cde429..e84a21b89 100644 --- a/src/freertos_drivers/ti/TivaDCC.hxx +++ b/src/freertos_drivers/ti/TivaDCC.hxx @@ -311,14 +311,40 @@ private: /// Bit timings that we store and precalculate. typedef enum { + /// Zero bit for DCC (this is the longer) DCC_ZERO, + /// One bit for DCC (this is the shorter) DCC_ONE, + /// One bit for DCC that we generate during the railcom cutout. Can be + /// used to play with alignment of edges coming out of the railcom + /// cutout. + DCC_RC_ONE, + /// Half zero bit which is sent directly after the railcom cutout is + /// over. Needed to reset certain old decoders packet + /// recognizer. Recommended by + /// https://nmra.org/sites/default/files/standards/sandrp/pdf/tn-2-05draft2005-02-25_for_rp-9.2.3.pdf + DCC_RC_HALF_ZERO, + /// This is not a bit, but specifies when to wake up during the railcom + /// cutout. The time is T_CS, usually 26 usec. RAILCOM_CUTOUT_PRE, + /// This is not a bit, but specifies when to wake up during the railcom + /// cutout. The time is the time elapsed between T_CS and the middle of + /// the two windows. RAILCOM_CUTOUT_FIRST, + /// This is not a bit, but specifies when to wake up during the railcom + /// cutout. The time is the time elapsed between the end of the cutout + /// and the the middle of the two windows. RAILCOM_CUTOUT_SECOND, + /// This is not a bit, but specifies when to wake up during the railcom + /// cutout. This is used for re-synchronizing the RAILCOM_CUTOUT_POST, + /// Long negative DC pulse to act as a preamble for a Marklin packet. MM_PREAMBLE, + /// Zero bit for MM packet, which is a short pulse in one direction, + /// then a long pulse in the other. MM_ZERO, + /// One bit for MM packet, which is a long pulse in one direction, then + /// a short pulse in the other. MM_ONE, NUM_TIMINGS @@ -519,7 +545,7 @@ inline void TivaDCC::interrupt_handler() case FRAME: if (++count >= packet->dlc) { - current_bit = DCC_ONE; // end-of-packet bit + current_bit = DCC_RC_ONE; // end-of-packet bit state_ = DCC_MAYBE_RAILCOM; preamble_count = 0; } @@ -535,8 +561,7 @@ inline void TivaDCC::interrupt_handler() HW::Output2::need_railcom_cutout() || HW::Output3::need_railcom_cutout())) { - //current_bit = RAILCOM_CUTOUT_PRE; - current_bit = DCC_ONE; + current_bit = DCC_RC_ONE; // It takes about 5 usec to get here from the previous // transition of the output. // We change the time of the next IRQ. @@ -558,7 +583,7 @@ inline void TivaDCC::interrupt_handler() } break; case DCC_CUTOUT_PRE: - current_bit = DCC_ONE; + current_bit = DCC_RC_ONE; // It takes about 3.6 usec to get here from the transition seen on // the output. // We change the time of the next IRQ. @@ -621,7 +646,7 @@ inline void TivaDCC::interrupt_handler() // Enables UART RX. railcomDriver_->start_cutout(); // Set up for next wakeup. - current_bit = DCC_ONE; + current_bit = DCC_RC_ONE; MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timings[RAILCOM_CUTOUT_SECOND].period); state_ = DCC_MIDDLE_RAILCOM_CUTOUT; @@ -629,17 +654,26 @@ inline void TivaDCC::interrupt_handler() } case DCC_MIDDLE_RAILCOM_CUTOUT: railcomDriver_->middle_cutout(); - current_bit = DCC_ONE; + current_bit = DCC_RC_ONE; MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timings[RAILCOM_CUTOUT_POST].period); state_ = DCC_STOP_RAILCOM_RECEIVE; break; case DCC_STOP_RAILCOM_RECEIVE: { - current_bit = DCC_ONE; + if (HW::generate_railcom_halfzero()) + { + current_bit = DCC_RC_HALF_ZERO; + } + else + { + current_bit = DCC_RC_ONE; + } + // This causes the timers to be reinitialized so no fractional bits + // are left in their counters. + resync = true; + get_next_packet = true; state_ = DCC_ENABLE_AFTER_RAILCOM; - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[DCC_ONE].period); railcomDriver_->end_cutout(); unsigned delay = 0; if (HW::Output1::isRailcomCutoutActive_) @@ -914,6 +948,12 @@ TivaDCC::TivaDCC(const char *name, RailcomDriver *railcom_driver) fill_timing(DCC_ZERO, 105<<1, 105); fill_timing(DCC_ONE, 56<<1, 56); + /// @todo tune this bit to line up with the bit stream starting after the + /// railcom cutout. + fill_timing(DCC_RC_ONE, 56<<1, 56); + // A small pulse in one direction then a half zero bit in the other + // direction. + fill_timing(DCC_RC_HALF_ZERO, 100 + 15, 15); fill_timing(MM_ZERO, 208, 26); fill_timing(MM_ONE, 208, 182); // Motorola preamble is negative DC signal. From f0f8e5bab4f9fdc342649fa728d0629cc3a5df44 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 1 Dec 2020 03:13:54 +0100 Subject: [PATCH 092/171] Moves broadcast time server unit test to fake clocks. (#482) * Makes time jump backwards after the fake clock is deleted. This is a desirable behavior even if os_get_time_monotonic() will then be not really monotonic. The alternative is to halt the monotonic clock until real time catches up with the fake clock which is less desirable and will cause cross-test interactions. * Adds test to verify that fake clocks advance correctly. * Moves broadcast time server unit test to fake clocks. * Adds test ensuring that sleep is accurate. --- src/openlcb/BroadcastTimeServer.cxxtest | 106 ++++++++++++++++++++---- src/os/FakeClock.cxxtest | 27 ++++++ src/os/os.c | 2 +- 3 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/openlcb/BroadcastTimeServer.cxxtest b/src/openlcb/BroadcastTimeServer.cxxtest index 6e66463e0..eed02578d 100644 --- a/src/openlcb/BroadcastTimeServer.cxxtest +++ b/src/openlcb/BroadcastTimeServer.cxxtest @@ -1,6 +1,7 @@ #include "utils/async_if_test_helper.hxx" #include "openlcb/BroadcastTimeServer.hxx" +#include "os/FakeClock.hxx" #if 1 #define PRINT_ALL_PACKETS() print_all_packets() @@ -68,6 +69,37 @@ protected: delete server_; } + /// Helper function for sleeping. + /// @param clk fake clock or nullptr if no fake clock exists + /// @param len_msec how long to sleep + /// @param step_msec what granularity to use for sleeping wiht fake clock. + void sleep_helper(FakeClock *clk, unsigned len_msec, + unsigned step_msec = 50) + { + if (clk) + { + for (unsigned i = 0; i < len_msec; i += step_msec) + { + clk->advance(MSEC_TO_NSEC(step_msec)); + wait(); + } + } + else + { + usleep(MSEC_TO_USEC(len_msec)); + } + } + + /// Performs the sleep sequence needed for the server to output the sync + /// events. + /// @param clk if not null, uses fake clock to advance. + /// @param step_msec how big steps should we advance the fake clock. + void sync_sleep(FakeClock *clk = nullptr, unsigned step_msec = 50) + { + // Sleep 3.2 seconds. + sleep_helper(clk, 3200, step_msec); + } + MOCK_METHOD0(update_callback, void()); BroadcastTimeServer *server_; @@ -81,10 +113,26 @@ TEST_F(BroadcastTimeServerTest, Create) EXPECT_EQ(server_->day_of_year(), 0); }; +TEST_F(BroadcastTimeServerTest, SleepTest) +{ + FakeClock clk; + long long t1 = os_get_time_monotonic(); + sync_sleep(&clk); + long long t2 = os_get_time_monotonic(); + EXPECT_NEAR(t2-t1, MSEC_TO_NSEC(3200), USEC_TO_NSEC(1)); +}; + TEST_F(BroadcastTimeServerTest, Query) { + FakeClock clk; ::testing::Sequence s1; + send_packet(":X195B4001N010100000100F000;"); // query + wait(); + + clear_expect(true); + // The server only responds a bit later in case multiple queries arrive. + // sync response expect_packet(":X1954422AN010100000100F001;").InSequence(s1); expect_packet(":X1954422AN0101000001004000;").InSequence(s1); @@ -92,9 +140,8 @@ TEST_F(BroadcastTimeServerTest, Query) expect_packet(":X1954422AN0101000001002101;").InSequence(s1); expect_packet(":X1954422AN0101000001000000;").InSequence(s1); - send_packet(":X195B4001N010100000100F000;"); // query - usleep(400000); // wait for response agrigation - wait_for_event_thread(); + clk.advance(MSEC_TO_NSEC(400)); + wait(); // time is not setup, clock is not running, expect 0 as default EXPECT_EQ(server_->time(), 0); @@ -104,6 +151,7 @@ TEST_F(BroadcastTimeServerTest, Query) TEST_F(BroadcastTimeServerTest, StartSetTime) { + FakeClock clk; ::testing::Sequence s1, s2; // set events @@ -130,7 +178,7 @@ TEST_F(BroadcastTimeServerTest, StartSetTime) // start expect_packet(":X195B422AN010100000100F002;").InSequence(s2); - // sync seqeunce + // sync sequence expect_packet(":X1954422AN010100000100F002;").InSequence(s2); expect_packet(":X1954422AN01010000010047D0;").InSequence(s2); expect_packet(":X1954422AN01010000010037B2;").InSequence(s2); @@ -141,12 +189,18 @@ TEST_F(BroadcastTimeServerTest, StartSetTime) // callbacks on clock update EXPECT_CALL(*this, update_callback()).Times(1); + LOG(INFO, "start server"); server_->start(); - - // allow time for the sync timeout - usleep(3200000); - wait_for_event_thread(); - + wait(); + LOG(INFO, "exec wait done"); + + // Time for the sync timeout. There are two sleeps there in order to send + // out two minute events, so we need to sleep twice too. + clk.advance(MSEC_TO_NSEC(3000)); + wait(); + clk.advance(MSEC_TO_NSEC(200)); + wait(); + // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(server_->time(), 1599, 1602)); EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); @@ -155,6 +209,7 @@ TEST_F(BroadcastTimeServerTest, StartSetTime) TEST_F(BroadcastTimeServerTest, DateRolloverForward) { + FakeClock clk; ::testing::Sequence s1, s2; // set events @@ -179,6 +234,8 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForward) clear_expect(true); + EXPECT_TRUE(IsBetweenInclusive(server_->time(), 86340, 86340)); + // start expect_packet(":X195B422AN010100000100F002;").InSequence(s2); @@ -186,7 +243,7 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForward) expect_packet(":X195B422AN010100000100F003;").InSequence(s2); expect_packet(":X195B422AN0101000001000000;").InSequence(s2); - // sync seqeunce + // sync sequence expect_packet(":X1954422AN010100000100F002;").InSequence(s2); expect_packet(":X1954422AN01010000010047D0;").InSequence(s2); expect_packet(":X1954422AN01010000010037B2;").InSequence(s2); @@ -205,8 +262,12 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForward) server_->start(); // allow time for the sync timeout - usleep(3200000); - wait_for_event_thread(); + + /// @todo this does not pass when using more than one msec of granularity + /// for the fake clock advances. That suggests there is an off by one bug + /// somewhere. The reported time is one tick lower than how much the + /// faketime advanced. + sync_sleep(&clk, 1); // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(server_->time(), 87940, 87941)); @@ -216,6 +277,7 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForward) TEST_F(BroadcastTimeServerTest, DateRolloverForwardOnTopOfSync) { + FakeClock clk; ::testing::Sequence s1, s2, s3; // set events @@ -265,17 +327,22 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForwardOnTopOfSync) server_->start(); // allow time for the sync timeout - usleep(3200000); + sync_sleep(&clk, 1); wait_for_event_thread(); // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(server_->time(), 87820, 87821)); EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::FRIDAY); EXPECT_EQ(server_->day_of_year(), 1); + + // Clears out timer from queue. + clk.advance(MSEC_TO_NSEC(200)); + wait(); }; #if 1 TEST_F(BroadcastTimeServerTest, DateRolloverForwardAllEventBasedSetup) { + FakeClock clk; ::testing::Sequence s1, s2; // report events @@ -319,7 +386,8 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForwardAllEventBasedSetup) send_packet(":X195B4001N010100000100F002;"); // start // allow time for the sync timeout - usleep(3200000); + sync_sleep(&clk, 1); + //usleep(3200000); wait_for_event_thread(); // check the time, we give it a finite range just in case of some OS jitter @@ -330,6 +398,7 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForwardAllEventBasedSetup) #endif TEST_F(BroadcastTimeServerTest, DateRolloverBackward) { + FakeClock clk; ::testing::Sequence s1, s2; // set events @@ -379,7 +448,7 @@ TEST_F(BroadcastTimeServerTest, DateRolloverBackward) server_->start(); // allow time for the sync timeout - usleep(3200000); + sync_sleep(&clk, 1); wait_for_event_thread(); // check the time, we give it a finite range just in case of some OS jitter @@ -390,6 +459,7 @@ TEST_F(BroadcastTimeServerTest, DateRolloverBackward) TEST_F(BroadcastTimeServerTest, Subscribe) { + FakeClock clk; ::testing::Sequence s1, s2; // set events @@ -411,6 +481,7 @@ TEST_F(BroadcastTimeServerTest, Subscribe) server_->set_rate_quarters(2000); wait_for_event_thread(); + clear_expect(true); clear_expect(true); // start @@ -430,8 +501,7 @@ TEST_F(BroadcastTimeServerTest, Subscribe) server_->start(); // allow time for the sync timeout - usleep(3200000); - wait_for_event_thread(); + sync_sleep(&clk, 1); // subscribe to some times clear_expect(true); @@ -444,7 +514,7 @@ TEST_F(BroadcastTimeServerTest, Subscribe) send_packet(":X194C7001N010100000100003A;"); // subscribe to 00:58 send_packet(":X194C7001N010100000100003B;"); // subscribe to 00:59 send_packet(":X194C7001N0101000001000100;"); // subscribe to 01:00 - usleep(4100000); + sleep_helper(&clk, 4100, 1); // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(server_->time(), 3650, 3651)); diff --git a/src/os/FakeClock.cxxtest b/src/os/FakeClock.cxxtest index 9b5a4e949..e6a0a173b 100644 --- a/src/os/FakeClock.cxxtest +++ b/src/os/FakeClock.cxxtest @@ -20,6 +20,12 @@ TEST(FakeClockTest, advance) EXPECT_GT(tfreeze + 500, os_get_time_monotonic()); } + // Advance should be accurate. + long long t3 = os_get_time_monotonic(); + clk.advance(MSEC_TO_NSEC(3540)); + long long t4 = os_get_time_monotonic(); + EXPECT_NEAR(t4, t3 + MSEC_TO_NSEC(3540), 10); + // But still be monotonic. t1 = os_get_time_monotonic(); t2 = os_get_time_monotonic(); @@ -35,6 +41,27 @@ TEST(FakeClockTest, independent_test) EXPECT_LT(t1 + MSEC_TO_NSEC(20), t2); } +TEST(FakeClockTest, time_jumps_backwards) +{ + // This test verifies that when a fake clock is deleted, time will jump + // backwards. This is desirable, because the fake clock might have its time + // far in the future and we cannot affort to halt the monotonic clock until + // the real time catches up with the fake time. + long long t1 = os_get_time_monotonic(); + long long t2, t3, t4; + { + FakeClock clk; + t2 = os_get_time_monotonic(); + clk.advance(SEC_TO_NSEC(15)); + t3 = os_get_time_monotonic(); + } + t4 = os_get_time_monotonic(); + EXPECT_LT(t1, t2); + EXPECT_LT(t2, t3 - SEC_TO_NSEC(15)); + EXPECT_LT(t1, t4); + EXPECT_GT(t3, t4); +} + class CountingTimer : public Timer { public: diff --git a/src/os/os.c b/src/os/os.c index 899d35bf6..f956cd173 100644 --- a/src/os/os.c +++ b/src/os/os.c @@ -638,7 +638,7 @@ long long os_get_time_monotonic(void) long long fake_time = os_get_fake_time(); if (fake_time >= 0) { - time = fake_time; + return fake_time; } #endif // not GTEST From 16eb593b95d091f1833d0d712c52fbf9d92d358d Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Dec 2020 13:18:39 +0100 Subject: [PATCH 093/171] Adds support for E-Stop query to trains. (#483) - Makes the DCC train objects remember that they were set to estop speed. - Correctly implements get_emergencystop() on them. - Makes the Traction Service fill in the is_estop bit in the Get Speed Response payload. - Updates the API that parses the Get Speed Response payload to be able to produce the is_estop bit. - Adds support to the TractionThrottle for querying estop. Verifies that estop state is correctly queried during load and updated when listener commands arrive. - Fixes estop handling bug in TractionTestTrain. === * Adds support for E-Stop query to trains. - Makes the DCC train objects remember that they were set to estop speed. - Correctly implements get_emergencystop() on them. - Makes the Traction Service fill in the is_estop bit in the Get Speed Response payload. - Updates the API that parses the Get Speed Response payload to be able to produce the is_estop bit. * Adds support to the TractionThrottle for querying estop. Verifies that estop state is correctly queried during load. Fixes estop handling bug in TractionTestTrain. * Adds test to verify that speed replies from the listener protocol correctly clear the estopActive_ in the throttle client cache. --- src/dcc/Loco.hxx | 12 +++++++- src/dcc/Packet.cxxtest | 42 ++++++++++++++++++++++++++++ src/openlcb/TractionDefs.hxx | 19 +++++++++++-- src/openlcb/TractionTestTrain.cxx | 3 +- src/openlcb/TractionThrottle.cxxtest | 27 ++++++++++++++++++ src/openlcb/TractionThrottle.hxx | 6 ++-- src/openlcb/TractionTrain.cxx | 7 ++++- src/openlcb/TractionTrain.cxxtest | 14 ++++++++++ 8 files changed, 122 insertions(+), 8 deletions(-) diff --git a/src/dcc/Loco.hxx b/src/dcc/Loco.hxx index caed8643b..55e2d9b71 100644 --- a/src/dcc/Loco.hxx +++ b/src/dcc/Loco.hxx @@ -117,6 +117,7 @@ public: return; } p.lastSetSpeed_ = new_speed; + p.isEstop_ = false; unsigned previous_light = get_effective_f0(); if (speed.direction() != p.direction_) { @@ -170,6 +171,7 @@ public: void set_emergencystop() OVERRIDE { p.speed_ = 0; + p.isEstop_ = true; SpeedType dir0; dir0.set_direction(p.direction_); p.lastSetSpeed_ = dir0.get_wire(); @@ -182,7 +184,7 @@ public: /// Gets the train's ESTOP state. bool get_emergencystop() OVERRIDE { - return false; + return p.isEstop_; } /// Sets a function to a given value. @param address is the function number /// (0..28), @param value is 0 for funciton OFF, 1 for function ON. @@ -368,6 +370,8 @@ struct Dcc28Payload unsigned nextRefresh_ : 3; /// Speed step we last set. unsigned speed_ : 5; + /// 1 if the last speed set was estop. + unsigned isEstop_ : 1; /// Whether the direction change packet still needs to go out. unsigned directionChanged_ : 1; /// 1 if the F0 function should be set/get in a directional way. @@ -473,6 +477,8 @@ struct Dcc128Payload /// Whether the direction change packet still needs to go out. unsigned directionChanged_ : 1; + /// 1 if the last speed set was estop. + unsigned isEstop_ : 1; /// 1 if the F0 function should be set/get in a directional way. unsigned f0SetDirectional_ : 1; /// 1 if directional f0 is used and f0 is on for F. @@ -548,6 +554,8 @@ struct MMOldPayload unsigned directionChanged_ : 1; /// Speed step we last set. unsigned speed_ : 4; + /// 1 if the last speed set was estop. + unsigned isEstop_ : 1; /// 1 if the F0 function should be set/get in a directional way. unsigned f0SetDirectional_ : 1; @@ -625,6 +633,8 @@ struct MMNewPayload unsigned speed_ : 4; /// internal refresh cycle state machine unsigned nextRefresh_ : 3; + /// 1 if the last speed set was estop. + unsigned isEstop_ : 1; /// 1 if the F0 function should be set/get in a directional way. unsigned f0SetDirectional_ : 1; diff --git a/src/dcc/Packet.cxxtest b/src/dcc/Packet.cxxtest index f6392c2ba..0f9bf934a 100644 --- a/src/dcc/Packet.cxxtest +++ b/src/dcc/Packet.cxxtest @@ -355,12 +355,15 @@ protected: EXPECT_CALL(loop_, unregister_source(&train_)); } + /// Requests a refresh packet from the train. void do_refresh() { new (&pkt_) Packet(); train_.get_next_packet(0, &pkt_); } + /// Requests the packet from the train that the last code was generated + /// for. void do_callback() { Mock::VerifyAndClear(&loop_); @@ -455,6 +458,45 @@ TEST_F(Train28Test, ZeroSpeed) EXPECT_THAT(get_packet(), ElementsAre(55, 0b01100000, _)); } +/// Verifies that the train correctly remembers that it was commanded to estop. +TEST_F(Train28Test, EstopSaved) +{ + SpeedType s; + s.set_mph(128); + code_ = 0; + EXPECT_CALL(loop_, send_update(&train_, _)) + .WillRepeatedly(SaveArg<1>(&code_)); + + // First make the train move. + train_.set_speed(s); + EXPECT_EQ(DccTrainUpdateCode::SPEED, code_); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b01111111, _)); + EXPECT_FALSE(train_.get_emergencystop()); + EXPECT_NEAR(128, train_.get_speed().mph(), 0.1); + + // Then estop the train. + EXPECT_FALSE(train_.get_emergencystop()); + train_.set_emergencystop(); + EXPECT_EQ(DccTrainUpdateCode::ESTOP, code_); + EXPECT_TRUE(train_.get_emergencystop()); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b01100001, _)); + // Checks that the train knows it's estopped and the speed is reported as + // zero. + EXPECT_TRUE(train_.get_emergencystop()); + EXPECT_NEAR(0, train_.get_speed().mph(), 0.1); + + // Move the train again. + s = 37.5; + train_.set_speed(s); + EXPECT_EQ(DccTrainUpdateCode::SPEED, code_); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b01101011, _)); + EXPECT_FALSE(train_.get_emergencystop()); + EXPECT_NEAR(37.5, train_.get_speed().speed(), 0.1); +} + TEST_F(Train28Test, RefreshLoop) { EXPECT_CALL(loop_, send_update(&train_, _)).Times(AtLeast(1)); diff --git a/src/openlcb/TractionDefs.hxx b/src/openlcb/TractionDefs.hxx index 389a984f4..ed05f939d 100644 --- a/src/openlcb/TractionDefs.hxx +++ b/src/openlcb/TractionDefs.hxx @@ -134,6 +134,9 @@ struct TractionDefs { RESP_CONSIST_CONFIG = REQ_CONSIST_CONFIG, RESP_TRACTION_MGMT = REQ_TRACTION_MGMT, + // Status byte of the Speed Query response + SPEEDRESP_STATUS_IS_ESTOP = 1, + // Byte 1 of Controller Configuration response CTRLRESP_ASSIGN_CONTROLLER = CTRLREQ_ASSIGN_CONTROLLER, CTRLRESP_QUERY_CONTROLLER = CTRLREQ_QUERY_CONTROLLER, @@ -356,8 +359,12 @@ struct TractionDefs { /** Parses the response payload of a GET_SPEED packet. * @returns true if the last_set_speed value was present and non-NaN. * @param p is the response payload. - * @param v is the velocity that will be set to the speed value. */ - static bool speed_get_parse_last(const Payload &p, Velocity *v) + * @param v is the velocity that will be set to the speed value. + * @param is_estop if non-null, will be set to true if the train was last + * set to estop instead of a speed. + */ + static bool speed_get_parse_last( + const Payload &p, Velocity *v, bool *is_estop = nullptr) { if (p.size() < 3) { @@ -368,6 +375,14 @@ struct TractionDefs { { return false; } + if (is_estop) + { + if (p.size() < 4) + { + return false; + } + *is_estop = (p[3] & SPEEDRESP_STATUS_IS_ESTOP) != 0; + } return true; } diff --git a/src/openlcb/TractionTestTrain.cxx b/src/openlcb/TractionTestTrain.cxx index 8cd587e4c..d847ca29b 100644 --- a/src/openlcb/TractionTestTrain.cxx +++ b/src/openlcb/TractionTestTrain.cxx @@ -86,7 +86,8 @@ void LoggingTrain::set_emergencystop() TractionDefs::train_node_name_from_legacy( legacyAddressType_, legacyAddress_) .c_str()); - estopActive_ = 0; + currentSpeed_.set_mph(0); // keeps sign + estopActive_ = true; } bool LoggingTrain::get_emergencystop() diff --git a/src/openlcb/TractionThrottle.cxxtest b/src/openlcb/TractionThrottle.cxxtest index 8f4f66de4..3f8298281 100644 --- a/src/openlcb/TractionThrottle.cxxtest +++ b/src/openlcb/TractionThrottle.cxxtest @@ -225,6 +225,7 @@ TEST_F(ThrottleTest, SendQuery) EXPECT_EQ(0, flow.response()->resultCode); EXPECT_NEAR(13.2, flow.throttle_.get_speed().mph(), 0.1); + EXPECT_FALSE(flow.throttle_.get_emergencystop()); EXPECT_EQ(0, flow.throttle_.get_fn(0)); EXPECT_EQ(1, flow.throttle_.get_fn(1)); EXPECT_EQ(0, flow.throttle_.get_fn(2)); @@ -233,6 +234,15 @@ TEST_F(ThrottleTest, SendQuery) EXPECT_EQ(0, flow.throttle_.get_fn(5)); EXPECT_EQ(0, flow.throttle_.get_fn(6)); EXPECT_EQ(1, flow.throttle_.get_fn(7)); + + // Checks emergency stop load. + trainImpl_.set_emergencystop(); + flow.load(); + n_.wait_for_notification(); + wait(); + EXPECT_EQ(0, flow.response()->resultCode); + EXPECT_NEAR(0, flow.throttle_.get_speed().mph(), 0.1); + EXPECT_TRUE(flow.throttle_.get_emergencystop()); } class ThrottleClientTest : public ThrottleTest { @@ -523,6 +533,23 @@ TEST_F(ThrottleClientTest, ListenerCallback) // send another speed command to verify that E-Stop gets cleared throttle_.set_speed(v); EXPECT_FALSE(throttle_.get_emergencystop()); + + // Go back to estop. + EXPECT_CALL(l, update(-1)); + EXPECT_FALSE(throttle_.get_emergencystop()); + send_packet(":X195EB330N077102;"); // E-Stop + wait(); + Mock::VerifyAndClear(&l); + EXPECT_TRUE(throttle_.get_emergencystop()); + + // Speed replies will also clear estop. + EXPECT_CALL(l, update(-1)); + send_packet(":X195EB330N07710045D0;"); // speed 13 mph + wait(); + Mock::VerifyAndClear(&l); + EXPECT_NEAR(13, throttle_.get_speed().mph(), 0.1); + EXPECT_EQ(Velocity::FORWARD, throttle_.get_speed().direction()); + EXPECT_FALSE(throttle_.get_emergencystop()); } } // namespace openlcb diff --git a/src/openlcb/TractionThrottle.hxx b/src/openlcb/TractionThrottle.hxx index c06c44b98..9951b1c9e 100644 --- a/src/openlcb/TractionThrottle.hxx +++ b/src/openlcb/TractionThrottle.hxx @@ -619,15 +619,15 @@ private: { bool expected = pending_reply_arrived(); Velocity v; - if (TractionDefs::speed_get_parse_last(p, &v)) + bool is_estop; + if (TractionDefs::speed_get_parse_last(p, &v, &is_estop)) { lastSetSpeed_ = v; + estopActive_ = is_estop; if (updateCallback_ && !expected) { updateCallback_(-1); } - /// @todo (Stuart.Baker): Do we need to do anything with - /// estopActive_? } return; } diff --git a/src/openlcb/TractionTrain.cxx b/src/openlcb/TractionTrain.cxx index c2214dadd..2e2b81407 100644 --- a/src/openlcb/TractionTrain.cxx +++ b/src/openlcb/TractionTrain.cxx @@ -280,7 +280,12 @@ struct TrainService::Impl uint8_t *d = reinterpret_cast(&(*p)[0]); d[0] = TractionDefs::RESP_QUERY_SPEED; speed_to_fp16(train_node()->train()->get_speed(), d + 1); - d[3] = 0; // status byte: reserved. + uint8_t status = 0; + if (train_node()->train()->get_emergencystop()) + { + status |= TractionDefs::SPEEDRESP_STATUS_IS_ESTOP; + } + d[3] = status; speed_to_fp16(train_node()->train()->get_commanded_speed(), d + 4); speed_to_fp16(train_node()->train()->get_actual_speed(), diff --git a/src/openlcb/TractionTrain.cxxtest b/src/openlcb/TractionTrain.cxxtest index 9c8c386c7..96eaf5e02 100644 --- a/src/openlcb/TractionTrain.cxxtest +++ b/src/openlcb/TractionTrain.cxxtest @@ -128,6 +128,7 @@ TEST_F(TractionSingleMockTest, SetSpeed) TEST_F(TractionSingleMockTest, GetSpeed) { EXPECT_CALL(m1_, get_speed()).WillOnce(Return(37.5)); + EXPECT_CALL(m1_, get_emergencystop()).WillOnce(Return(false)); EXPECT_CALL(m1_, get_commanded_speed()).WillOnce(Return(nan_to_speed())); EXPECT_CALL(m1_, get_actual_speed()).WillOnce(Return(nan_to_speed())); expect_packet(":X191E933AN15511050B000FFFF;"); @@ -138,6 +139,7 @@ TEST_F(TractionSingleMockTest, GetSpeed) TEST_F(TractionSingleMockTest, GetSpeedTestWithCommandedSpeed) { EXPECT_CALL(m1_, get_speed()).WillOnce(Return(37.5)); + EXPECT_CALL(m1_, get_emergencystop()).WillOnce(Return(false)); EXPECT_CALL(m1_, get_commanded_speed()).WillOnce(Return(37.0)); EXPECT_CALL(m1_, get_actual_speed()).WillOnce(Return(nan_to_speed())); expect_packet(":X191E933AN15511050B00050A0;"); @@ -148,6 +150,7 @@ TEST_F(TractionSingleMockTest, GetSpeedTestWithCommandedSpeed) TEST_F(TractionSingleMockTest, GetSpeedTestWithActualSpeed) { EXPECT_CALL(m1_, get_speed()).WillOnce(Return(37.5)); + EXPECT_CALL(m1_, get_emergencystop()).WillOnce(Return(false)); EXPECT_CALL(m1_, get_commanded_speed()).WillOnce(Return(37.0)); EXPECT_CALL(m1_, get_actual_speed()).WillOnce(Return(38.0)); expect_packet(":X191E933AN15511050B00050A0;"); @@ -155,6 +158,17 @@ TEST_F(TractionSingleMockTest, GetSpeedTestWithActualSpeed) send_packet(":X195EB551N033A10;"); } +TEST_F(TractionSingleMockTest, GetSpeedTestWithEstop) +{ + EXPECT_CALL(m1_, get_speed()).WillOnce(Return(-0.0)); + EXPECT_CALL(m1_, get_emergencystop()).WillOnce(Return(true)); + EXPECT_CALL(m1_, get_commanded_speed()).WillOnce(Return(-0.0)); + EXPECT_CALL(m1_, get_actual_speed()).WillOnce(Return(-0.0)); + expect_packet(":X191E933AN1551108000018000;"); + expect_packet(":X191E933AN25518000;"); + send_packet(":X195EB551N033A10;"); +} + TEST_F(TractionSingleMockTest, SetFn) { EXPECT_CALL(m1_, set_fn(0x112233, 0x4384)); From 9741a0d6ac0f53b1fdc8869d2f6684652db44a4a Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 7 Dec 2020 23:07:59 +0100 Subject: [PATCH 094/171] Fix logic expression bug. --- src/openlcb/IfCan.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openlcb/IfCan.cxx b/src/openlcb/IfCan.cxx index 60799416e..704b16f23 100644 --- a/src/openlcb/IfCan.cxx +++ b/src/openlcb/IfCan.cxx @@ -758,7 +758,7 @@ void IfCan::delete_local_node(Node *node) { void IfCan::canonicalize_handle(NodeHandle *h) { - if (!h->id & !h->alias) + if (!h->id && !h->alias) return; if (!h->id) { From ed1b12c945685ad1ac52182326904e907b2d272b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 7 Dec 2020 23:09:38 +0100 Subject: [PATCH 095/171] Adds send_message helper. (#484) * Adds a helper function similar to send_event that sends an arbitrary OpenLCB message to the bus using synchronous allocation. --- src/openlcb/If.hxx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/openlcb/If.hxx b/src/openlcb/If.hxx index c0b82c516..f6b181bd6 100644 --- a/src/openlcb/If.hxx +++ b/src/openlcb/If.hxx @@ -471,6 +471,28 @@ public: } }; +/// Sends an OpenLCB message to the bus. Performs synchronous (dynamic) memory +/// allocation so use it sparingly and when there is sufficient amount of RAM +/// available. +/// @param src_node A local virtual node from which to send the message. +/// @param mti message type indicator +/// @param args either a Payload to send a global message, or a NodeHandle dst +/// and a Payload to send an addressed message. +template void send_message(Node *src_node, Defs::MTI mti, Args &&...args) +{ + Buffer *msg; + mainBufferPool->alloc(&msg); + msg->data()->reset(mti, src_node->node_id(), std::forward(args)...); + if (msg->data()->dst == NodeHandle()) + { + src_node->iface()->global_message_write_flow()->send(msg); + } + else + { + src_node->iface()->addressed_message_write_flow()->send(msg); + } +} + } // namespace openlcb #endif // _OPENLCB_IF_HXX_ From e1a5c7deb8e7cbac4c658dbdd074a68e540bdf1c Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 7 Dec 2020 23:11:20 +0100 Subject: [PATCH 096/171] Heartbeat support in the traction protocol (#485) Adds comprehensive support for heartbeats in the traction protocol components: - Defines the new message bytes in the TractionDefs - Adds helper functions to generate payloads for noop and heartbeat request. - Implements the noop request in the traction service. - Adds a hook that forwards every incoming traction packet to the train virtual node. This way the virtual node can keep track of the heartbeat interval and get to know when it was cleared. - Adds implementation of the heartbeat request to the traction throttle, automatically clearing if it came from the right train node. === * Adds definitions for Traction no-op and heartbeat. * Adds implementation of the noop request. Adds a hook that forwards every incoming traction packet to the train virtual node. * Adds implementation of the heartbeat request to the traction throttle. * Adds more tests to the heartbeat message. --- src/openlcb/TractionDefs.hxx | 22 ++++++++ src/openlcb/TractionThrottle.cxxtest | 75 ++++++++++++++++++++++++++++ src/openlcb/TractionThrottle.hxx | 8 +++ src/openlcb/TractionTrain.cxx | 8 ++- src/openlcb/TractionTrain.hxx | 9 ++++ 5 files changed, 121 insertions(+), 1 deletion(-) diff --git a/src/openlcb/TractionDefs.hxx b/src/openlcb/TractionDefs.hxx index ed05f939d..8992bcd64 100644 --- a/src/openlcb/TractionDefs.hxx +++ b/src/openlcb/TractionDefs.hxx @@ -127,6 +127,7 @@ struct TractionDefs { // Byte 1 of REQ_TRACTION_MGMT command MGMTREQ_RESERVE = 0x01, MGMTREQ_RELEASE = 0x02, + MGMTREQ_NOOP = 0x03, // Byte 0 of response commands RESP_QUERY_SPEED = REQ_QUERY_SPEED, RESP_QUERY_FN = REQ_QUERY_FN, @@ -159,6 +160,7 @@ struct TractionDefs { // Byte 1 of Traction Management replies MGMTRESP_RESERVE = MGMTREQ_RESERVE, + MGMTRESP_HEARTBEAT = 0x03, PROXYREQ_ALLOCATE = 0x01, PROXYREQ_ATTACH = 0x02, @@ -522,6 +524,26 @@ struct TractionDefs { node_id_to_data(slave, &p[5]); return p; } + + /// Generates a Heartbeat Request, to be sent from the train node to the + /// controller. + static Payload heartbeat_request_payload(uint8_t deadline_sec = 3) + { + Payload p(3, 0); + p[0] = RESP_TRACTION_MGMT; + p[1] = MGMTRESP_HEARTBEAT; + p[2] = deadline_sec; + return p; + } + + /// Generates a Noop message, to be sent from the throttle to the train node. + static Payload noop_payload() + { + Payload p(2, 0); + p[0] = REQ_TRACTION_MGMT; + p[1] = MGMTREQ_NOOP; + return p; + } }; } // namespace openlcb diff --git a/src/openlcb/TractionThrottle.cxxtest b/src/openlcb/TractionThrottle.cxxtest index 3f8298281..5e390f975 100644 --- a/src/openlcb/TractionThrottle.cxxtest +++ b/src/openlcb/TractionThrottle.cxxtest @@ -468,6 +468,81 @@ TEST_F(ThrottleClientTest, ReassignWithoutListener) wait(); } +TEST_F(ThrottleClientTest, HeartbeatWithListener) +{ + auto b = invoke_flow(&throttle_, TractionThrottleCommands::ASSIGN_TRAIN, + TRAIN_NODE_ID, true); + ASSERT_EQ(0, b->data()->resultCode); + + // Primes the caches. + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_COMMAND, + NodeHandle(node_->node_id()), TractionDefs::fn_set_payload(13, 1)); + wait(); + EXPECT_EQ(1u, throttle_.get_fn(13)); + + clear_expect(true); // checks packets from now on + // heartbeat request is sent from train to throttle + expect_packet(":X191E9771N022A400303;"); + // throttle responds with NOOP. + expect_packet(":X195EB22AN07714003;"); + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_REPLY, + NodeHandle(node_->node_id()), + TractionDefs::heartbeat_request_payload()); + wait(); + clear_expect(true); +} + +TEST_F(ThrottleClientTest, HeartbeatNoListener) +{ + auto b = invoke_flow(&throttle_, TractionThrottleCommands::ASSIGN_TRAIN, + TRAIN_NODE_ID, false); + ASSERT_EQ(0, b->data()->resultCode); + + // Primes the caches. + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_COMMAND, + NodeHandle(node_->node_id()), TractionDefs::fn_set_payload(13, 1)); + wait(); + + clear_expect(true); // checks packets from now on + // heartbeat request is sent from train to throttle + expect_packet(":X191E9771N022A400303;"); + // throttle responds with NOOP. + expect_packet(":X195EB22AN07714003;"); + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_REPLY, + NodeHandle(node_->node_id()), + TractionDefs::heartbeat_request_payload()); + wait(); + clear_expect(true); +} + +TEST_F(ThrottleClientTest, HeartbeatWrongTrain) +{ + auto b = invoke_flow(&throttle_, TractionThrottleCommands::ASSIGN_TRAIN, + TRAIN_NODE_ID, false); + ASSERT_EQ(0, b->data()->resultCode); + + static auto TRAIN_NODE_ID2 = TRAIN_NODE_ID + 1; + run_x([this]() { otherIf_.local_aliases()->add(TRAIN_NODE_ID2, 0x772); }); + LoggingTrain train_impl2{1373}; + TrainNodeForProxy node2(&trainService_, &train_impl2); + wait(); + + // Primes the caches. + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_COMMAND, + NodeHandle(node_->node_id()), TractionDefs::fn_set_payload(13, 1)); + wait(); + + clear_expect(true); // checks packets from now on + // heartbeat request is sent from wrong train to throttle + expect_packet(":X191E9772N022A400303;"); + // no response from the throttle. + openlcb::send_message(&node2, Defs::MTI_TRACTION_CONTROL_REPLY, + NodeHandle(node_->node_id()), + TractionDefs::heartbeat_request_payload()); + wait(); + clear_expect(true); +} + class ListenerInterface { public: diff --git a/src/openlcb/TractionThrottle.hxx b/src/openlcb/TractionThrottle.hxx index 9951b1c9e..a78be880d 100644 --- a/src/openlcb/TractionThrottle.hxx +++ b/src/openlcb/TractionThrottle.hxx @@ -645,6 +645,14 @@ private: } } } + case TractionDefs::RESP_TRACTION_MGMT: + { + if (p.size() >= 2 && p[1] == TractionDefs::MGMTRESP_HEARTBEAT) + { + // Automatically responds to heartbeat requests. + send_traction_message(TractionDefs::noop_payload()); + } + } } } diff --git a/src/openlcb/TractionTrain.cxx b/src/openlcb/TractionTrain.cxx index 2e2b81407..489610e0e 100644 --- a/src/openlcb/TractionTrain.cxx +++ b/src/openlcb/TractionTrain.cxx @@ -184,6 +184,7 @@ struct TrainService::Impl * send a reject response. */ return release_and_exit(); } + train_node()->command_hook(nmsg()->src, nmsg()->payload); // No command byte? if (size() < 1) { @@ -575,8 +576,13 @@ struct TrainService::Impl reserved_ = 0; return release_and_exit(); } + case TractionDefs::MGMTREQ_NOOP: + { + // Nothing to do. + return release_and_exit(); + } default: - LOG(VERBOSE, "Unknown Traction proxy manage subcommand %x", + LOG(VERBOSE, "Unknown Traction management subcommand %x", cmd); return reject_permanent(); } diff --git a/src/openlcb/TractionTrain.hxx b/src/openlcb/TractionTrain.hxx index 61e29bc31..aeb80eba1 100644 --- a/src/openlcb/TractionTrain.hxx +++ b/src/openlcb/TractionTrain.hxx @@ -82,6 +82,11 @@ public: virtual bool function_policy(NodeHandle src, uint8_t command_byte, uint32_t fnum, uint16_t value, Notifiable *done) = 0; + /// Invoked for every incoming traction command targeted to this node. + /// @param src what node sent this command + /// @param p command payload + virtual void command_hook(NodeHandle src, const Payload& p) = 0; + /// @return the last stored controller node. virtual NodeHandle get_controller() = 0; @@ -172,6 +177,10 @@ public: return true; } + void command_hook(NodeHandle src, const Payload &p) override + { + } + /** Adds a node ID to the consist targets. @return false if the node was * already in the target list, true if it was newly added. */ bool add_consist(NodeID tgt, uint8_t flags) override From c9749ed2db88e49f67d627b3764cc790896ef46e Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 12 Dec 2020 11:43:08 +0100 Subject: [PATCH 097/171] Adds a feature to compile targets in a deterministic way. --- etc/bare.armv7m.mk | 4 ++++ etc/freertos.armv7m.mk | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/etc/bare.armv7m.mk b/etc/bare.armv7m.mk index d78e1c597..5f799a5b5 100644 --- a/etc/bare.armv7m.mk +++ b/etc/bare.armv7m.mk @@ -33,6 +33,10 @@ ARCHOPTIMIZATION += -O3 -fno-strict-aliasing -fno-strength-reduce -fomit-frame-p ARCHFLAGS = -g -MD -MP -march=armv7-m -mthumb -mfloat-abi=soft +ifdef DETERMINISTIC_COMPILATION +ARCHFLAGS += -frandom-seed=$(shell echo $(abspath $<) | md5sum | sed 's/\(.*\) .*/\1/') +endif + ASFLAGS = -c $(ARCHFLAGS) CORECFLAGS = $(ARCHFLAGS) -Wall -Werror -Wno-unknown-pragmas \ diff --git a/etc/freertos.armv7m.mk b/etc/freertos.armv7m.mk index b47665d38..8782b078c 100644 --- a/etc/freertos.armv7m.mk +++ b/etc/freertos.armv7m.mk @@ -39,6 +39,10 @@ ARCHOPTIMIZATION += -Os -fno-strict-aliasing -fno-strength-reduce -fomit-frame-p ARCHFLAGS = -g -MD -MP -mcpu=cortex-m3 -mthumb -mfloat-abi=soft +ifdef DETERMINISTIC_COMPILATION +ARCHFLAGS += -frandom-seed=$(shell echo $(abspath $<) | md5sum | sed 's/\(.*\) .*/\1/') +endif + ifdef DEBUG_MEMORY_USE #warning: -funwind-tables adds 10k code size. Needed for malloc debugging. ARCHFLAGS += -funwind-tables From f49b682aed04e64265ed6a48c7c8a25c8ca35415 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Mon, 14 Dec 2020 20:11:37 -0600 Subject: [PATCH 098/171] Use BarrierNotifiable in the implementation of Broadcast Time Alarms. (#337) * Use BarrierNotifiable in the implementation of Broadcast Time Alarms. * streamline code style. * Format comment. * Fix barrier notifiable ownership count issue. * Fix shutdown. * Fix rounding errors. * fix minute alarm and create tests. * Test/fix BroadcastTimeAlarmDate * Disable instrumentation. * Fix BroadcastTime example application builds. * remove whitespace. --- applications/Makefile | 2 + .../time_client/targets/linux.x86/main.cxx | 10 +- .../time_server/targets/linux.x86/main.cxx | 2 +- src/openlcb/BroadcastTime.hxx | 21 +- src/openlcb/BroadcastTimeAlarm.cxxtest | 410 ++++++++++++++++++ src/openlcb/BroadcastTimeAlarm.hxx | 81 ++-- src/openlcb/BroadcastTimeClient.cxxtest | 2 +- src/openlcb/BroadcastTimeServer.cxx | 12 +- src/openlcb/BroadcastTimeServer.cxxtest | 219 +++++++++- 9 files changed, 705 insertions(+), 54 deletions(-) diff --git a/applications/Makefile b/applications/Makefile index e04be9d31..0ec560890 100644 --- a/applications/Makefile +++ b/applications/Makefile @@ -25,6 +25,8 @@ SUBDIRS = \ train \ tcp_blink_client \ usb_can \ + time_client \ + time_server include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/time_client/targets/linux.x86/main.cxx b/applications/time_client/targets/linux.x86/main.cxx index b60bde4f7..f48e5c89a 100644 --- a/applications/time_client/targets/linux.x86/main.cxx +++ b/applications/time_client/targets/linux.x86/main.cxx @@ -73,7 +73,7 @@ extern const char *const openlcb::SNIP_DYNAMIC_FILENAME = openlcb::CONFIG_FILENAME; void time_update_callback(); -void minute_update_callback(); +void minute_update_callback(BarrierNotifiable *done); // Main time protocol client. openlcb::BroadcastTimeClient @@ -88,13 +88,15 @@ void print_time(const char* prefix) { tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, // tm->tm_hour, tm->tm_min, // timeClient.is_running() ? "running" : "stopped", // - timeClient.rate() * 1.0f / 4); + timeClient.get_rate_quarters() * 1.0f / 4); } /// Callback from the alarm that we will make called on every minute change. -void minute_update_callback() +/// @param done used to notify we are finished +void minute_update_callback(BarrierNotifiable *done) { print_time(""); + done->notify(); } /// Callback from the time client when the time jumps or is reset for any other @@ -115,7 +117,7 @@ int appl_main(int argc, char *argv[]) // Connects to a TCP hub on the internet. stack.connect_tcp_gridconnect_hub("localhost", 12021); - timeClient.update_subscribe(&time_update_callback); + timeClient.update_subscribe_add(&time_update_callback); // This command donates the main thread to the operation of the // stack. Alternatively the stack could be started in a separate stack and diff --git a/applications/time_server/targets/linux.x86/main.cxx b/applications/time_server/targets/linux.x86/main.cxx index c6dbdaaf0..2e3d03b3a 100644 --- a/applications/time_server/targets/linux.x86/main.cxx +++ b/applications/time_server/targets/linux.x86/main.cxx @@ -92,7 +92,7 @@ int appl_main(int argc, char *argv[]) timeServer.set_time(0, 0); timeServer.set_date(1, 1); timeServer.set_year(1970); - timeServer.set_rate(4); + timeServer.set_rate_quarters(4); timeServer.start(); // This command donates the main thread to the operation of the diff --git a/src/openlcb/BroadcastTime.hxx b/src/openlcb/BroadcastTime.hxx index 4aeb591a0..76383825b 100644 --- a/src/openlcb/BroadcastTime.hxx +++ b/src/openlcb/BroadcastTime.hxx @@ -130,9 +130,10 @@ public: if (started_) { long long elapsed = OSTime::get_monotonic() - timestamp_; - elapsed = ((elapsed * rate_) + 2) / 4; + elapsed = ((elapsed * std::abs(rate_)) + 2) / 4; - return seconds_ + (time_t)NSEC_TO_SEC_ROUNDED(elapsed); + time_t diff = (time_t)NSEC_TO_SEC_ROUNDED(elapsed); + return (rate_ < 0) ? seconds_ - diff : seconds_ + diff; } else { @@ -210,7 +211,12 @@ public: { if (rate != 0 && rate >= -2048 && rate <= 2047) { - *real_nsec = ((SEC_TO_NSEC(fast_sec) * 4) + (rate / 2)) / rate; + *real_nsec = ((SEC_TO_NSEC(std::abs(fast_sec)) * 4) + + (std::abs(rate) / 2)) / rate; + if (fast_sec < 0) + { + *real_nsec = -(*real_nsec); + } return true; } else @@ -230,7 +236,11 @@ public: { if (rate != 0 && rate >= -2048 && rate <= 2047) { - *fast_sec = (NSEC_TO_SEC(real_nsec * rate) + 2) / 4; + *fast_sec = (std::abs(NSEC_TO_SEC(real_nsec * rate)) + 2) / 4; + if ((real_nsec < 0 && rate > 0) || (real_nsec >= 0 && rate < 0)) + { + *fast_sec = -(*fast_sec); + } return true; } else @@ -252,7 +262,8 @@ public: if (fast_sec_to_real_nsec_period_abs(fast_sec - seconds_, real_nsec)) { *real_nsec += timestamp_; - *real_nsec -= OSTime::get_monotonic(); + long long monotonic = OSTime::get_monotonic(); + *real_nsec -= monotonic; return true; } else diff --git a/src/openlcb/BroadcastTimeAlarm.cxxtest b/src/openlcb/BroadcastTimeAlarm.cxxtest index 769615a68..d70ecd2ef 100644 --- a/src/openlcb/BroadcastTimeAlarm.cxxtest +++ b/src/openlcb/BroadcastTimeAlarm.cxxtest @@ -1,4 +1,414 @@ #include "utils/async_if_test_helper.hxx" #include "openlcb/BroadcastTimeAlarm.hxx" +#include "openlcb/BroadcastTimeServer.hxx" +#if 0 +#define PRINT_ALL_PACKETS() print_all_packets() +#else +#define PRINT_ALL_PACKETS() +#endif + +namespace openlcb +{ + +class BroadcastTimeAlarmTest : public AsyncNodeTest +{ +public: + MOCK_METHOD0(mock_callback, void()); + + void update_callback(BarrierNotifiable* done) + { + mock_callback(); + done->notify(); + } + +protected: + BroadcastTimeAlarmTest() + { + PRINT_ALL_PACKETS(); + + ::testing::Sequence s1; + + // consumer/producer identify ranges + expect_packet(":X1952422AN010100000100FFFF;").InSequence(s1); + expect_packet(":X194A422AN0101000001008000;").InSequence(s1); + + // sync sequence + expect_packet(":X1954422AN010100000100F001;").InSequence(s1); + expect_packet(":X1954422AN0101000001004000;").InSequence(s1); + expect_packet(":X1954422AN01010000010037B2;").InSequence(s1); + expect_packet(":X1954422AN0101000001002101;").InSequence(s1); + expect_packet(":X1954422AN0101000001000000;").InSequence(s1); + + server_ = new BroadcastTimeServer( + node_, BroadcastTimeDefs::DEFAULT_FAST_CLOCK_ID); + + send_packet(":X19970001N;"); + wait_for_event_thread(); + + clear_expect(true); + + alarm_ = new BroadcastTimeAlarm( + node_, server_, + std::bind(&BroadcastTimeAlarmTest::update_callback, this, + std::placeholders::_1)); + } + + ~BroadcastTimeAlarmTest() + { + clear_expect(); + wait_for_event_thread(); + alarm_->shutdown(); + server_->shutdown(); + wait(); + while (!alarm_->is_shutdown()) + { + usleep(10000); + wait(); + } + while (!server_->is_shutdown()) + { + usleep(10000); + wait(); + } + twait(); + + delete alarm_; + delete server_; + } + + BroadcastTimeServer *server_; + BroadcastTimeAlarm *alarm_; +}; + +TEST_F(BroadcastTimeAlarmTest, Create) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); +}; + +TEST_F(BroadcastTimeAlarmTest, SetPeriod) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set_period(60); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureExpires) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set(60); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureDoesNotExpire) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0); + alarm_->set(60); + usleep(110000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureJumpOver) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set(60); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump over + server_->set_time(2, 0); + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + // jump back + server_->set_time(0, 0); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureExpiresBackward) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 2); + server_->set_year(1970); + server_->set_rate_quarters(-2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set(86400 - 60); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureDoesNotExpireBackward) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 2); + server_->set_year(1970); + server_->set_rate_quarters(-2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0); + alarm_->set(86400 - 60); + usleep(110000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureJumpOverBackward) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(12, 0); + server_->set_date(1, 2); + server_->set_year(1970); + server_->set_rate_quarters(-2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set(129600 - 60); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump over + server_->set_time(10, 0); + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + // jump back + server_->set_time(12, 0); +}; + +TEST_F(BroadcastTimeAlarmTest, Date) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(23, 59); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + BroadcastTimeAlarmDate da(node_, server_, + std::bind(&BroadcastTimeAlarmTest::update_callback, this, + std::placeholders::_1)); + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow date rollover + usleep(030000); + + // now test backwards + server_->stop(); + server_->set_rate_quarters(-2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->start(); + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow date rollover + usleep(30000); + + da.shutdown(); + while (!da.is_shutdown()) + { + usleep(10000); + } +}; + +TEST_F(BroadcastTimeAlarmTest, Minute) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + BroadcastTimeAlarmMinute ma(node_, server_, + std::bind(&BroadcastTimeAlarmTest::update_callback, this, + std::placeholders::_1)); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); + + // run for five more fast minutes + EXPECT_CALL(*this, mock_callback()).Times(5).InSequence(s1); + usleep(600000); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump forward + server_->set_time(0, 8); + usleep(030000); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump back + server_->set_time(0, 6); + usleep(030000); + + ma.shutdown(); + while (!ma.is_shutdown()) + { + usleep(10000); + } +}; + +TEST_F(BroadcastTimeAlarmTest, MinuteBackward) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_rate_quarters(-2000); + server_->set_time(12, 0); + server_->set_date(1, 2); + server_->set_year(1970); + server_->start(); + + ::testing::Sequence s1; + + BroadcastTimeAlarmMinute ma(node_, server_, + std::bind(&BroadcastTimeAlarmTest::update_callback, this, + std::placeholders::_1)); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); + + // run for five more fast minutes + EXPECT_CALL(*this, mock_callback()).Times(5).InSequence(s1); + usleep(600000); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump forward + server_->set_time(11, 52); + usleep(030000); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump back + server_->set_time(11, 54); + usleep(030000); + + ma.shutdown(); + while (!ma.is_shutdown()) + { + usleep(10000); + } +}; + +} // namespace openlcb diff --git a/src/openlcb/BroadcastTimeAlarm.hxx b/src/openlcb/BroadcastTimeAlarm.hxx index 5752571ff..3523c4e82 100644 --- a/src/openlcb/BroadcastTimeAlarm.hxx +++ b/src/openlcb/BroadcastTimeAlarm.hxx @@ -50,16 +50,17 @@ public: /// @param clock clock that our alarm is based off of /// @param callback callback for when alarm expires BroadcastTimeAlarm(Node *node, BroadcastTime *clock, - std::function callback) + std::function callback) : StateFlowBase(node->iface()) , clock_(clock) , wakeup_(this) , callback_(callback) , timer_(this) + , bn_() + , bnPtr_(nullptr) , expires_(0) , running_(false) , set_(false) - , waiting_(true) #if defined(GTEST) , shutdown_(false) #endif @@ -80,14 +81,17 @@ public: } /// Start the alarm to expire at the given period from now. - /// @time period in fast seconds from now to expire + /// @param period in fast seconds from now to expire. @ref period is a + /// a signed value. If the fast time rate is negative, the @ref + /// period passed in should also be negative for an expiration in + /// the future. void set_period(time_t period) { set(clock_->time() + period); } /// Start the alarm to expire at the given fast time. - /// @time time in seconds since epoch to expire + /// @param time in seconds since epoch to expire void set(time_t time) { bool need_wakeup = false; @@ -121,7 +125,6 @@ public: #if defined(GTEST) void shutdown() { - AtomicHolder h(this); shutdown_ = true; wakeup_.trigger(); } @@ -195,7 +198,6 @@ private: /// setup() if clock and/or alarm is not currently active Action setup() { - waiting_ = false; #if defined(GTEST) if (shutdown_) { @@ -227,7 +229,7 @@ private: } } - waiting_ = true; + bnPtr_ = bn_.reset(this); return wait_and_call(STATE(setup)); } @@ -252,34 +254,36 @@ private: /// @return setup() Action expired() { + bnPtr_ = bn_.reset(this); if (running_ && clock_->is_running() && callback_) { running_ = false; - callback_(); + callback_(bnPtr_->new_child()); } - waiting_ = true; return wait_and_call(STATE(setup)); - } + } /// Wakeup the state machine. Must be called from this service's executor. void wakeup() { timer_.ensure_triggered(); - if (waiting_) + if (bnPtr_) { - waiting_ = false; - notify(); + bnPtr_ = nullptr; + bn_.notify(); } } Wakeup wakeup_; ///< wakeup helper for scheduling alarms - std::function callback_; ///< callback for when alarm expires + /// callback for when alarm expires + std::function callback_; StateFlowTimer timer_; ///< timer helper + BarrierNotifiable bn_; ///< notifiable for callback callee + BarrierNotifiable *bnPtr_; ///< not null we have an outstanding notification time_t expires_; ///< time at which the alarm expires uint8_t running_ : 1; ///< true if running (alarm armed), else false uint8_t set_ : 1; ///< true if a start request is pending - uint8_t waiting_ : 1; ///< true if waiting for stateflow to be notified #if defined(GTEST) uint8_t shutdown_ : 1; ///< true if test has requested shutdown #endif @@ -302,10 +306,10 @@ public: /// @param clock clock that our alarm is based off of /// @param callback callback for when alarm expires BroadcastTimeAlarmDate(Node *node, BroadcastTime *clock, - std::function callback) + std::function callback) : BroadcastTimeAlarm( node, clock, std::bind(&BroadcastTimeAlarmDate::expired_callback, - this)) + this, std::placeholders::_1)) , callbackUser_(callback) { } @@ -340,19 +344,17 @@ private: else if (clock_->get_rate_quarters() < 0) { set(seconds - ((tm->tm_sec + 1) + - (60 * (tm->tm_min + 1)) + + (60 * (tm->tm_min)) + (60 * 60 * tm->tm_hour))); } } /// callback for when the alarm expires - void expired_callback() + /// @param done used to notify we are finished + void expired_callback(BarrierNotifiable *done) { reset_expired_time(); - if (callbackUser_) - { - callbackUser_(); - } + callbackUser_ ? callbackUser_(done) : done->notify(); } /// Called when the clock time has changed. @@ -362,7 +364,8 @@ private: BroadcastTimeAlarm::update_notify(); } - std::function callbackUser_; ///< callback for when alarm expires + /// callback for when alarm expires + std::function callbackUser_; DISALLOW_COPY_AND_ASSIGN(BroadcastTimeAlarmDate); }; @@ -377,10 +380,11 @@ public: /// @param clock clock that our alarm is based off of /// @param callback callback for when alarm expires BroadcastTimeAlarmMinute(Node *node, BroadcastTime *clock, - std::function callback) + std::function callback) : BroadcastTimeAlarm( node, clock, - std::bind(&BroadcastTimeAlarmMinute::expired_callback, this)) + std::bind(&BroadcastTimeAlarmMinute::expired_callback, this, + std::placeholders::_1)) , callbackUser_(callback) { } @@ -401,11 +405,22 @@ private: } /// Reset the expired time based on what time it is now. - void reset_expired_time() + /// @param force_on_match true to force an expiration if on a minute + /// rollover boundary + void reset_expired_time(bool force_on_match = false) { const struct tm *tm = clock_->gmtime_recalculate(); time_t seconds = clock_->time(); + if (force_on_match) + { + if ((clock_->get_rate_quarters() > 0 && tm->tm_sec == 0) || + (clock_->get_rate_quarters() < 0 && tm->tm_sec == 59)) + { + set(seconds); + return; + } + } if (clock_->get_rate_quarters() > 0) { set(seconds + (60 - tm->tm_sec)); @@ -417,24 +432,22 @@ private: } /// callback for when the alarm expires - void expired_callback() + /// @param done used to notify we are finished + void expired_callback(BarrierNotifiable *done) { reset_expired_time(); - if (callbackUser_) - { - callbackUser_(); - } + callbackUser_ ? callbackUser_(done) : done->notify(); } /// Called when the clock time has changed. void update_notify() override { - reset_expired_time(); + reset_expired_time(true); BroadcastTimeAlarm::update_notify(); } /// callback for when alarm expires - std::function callbackUser_; + std::function callbackUser_; DISALLOW_COPY_AND_ASSIGN(BroadcastTimeAlarmMinute); }; diff --git a/src/openlcb/BroadcastTimeClient.cxxtest b/src/openlcb/BroadcastTimeClient.cxxtest index 83eac9b7b..34008dd06 100644 --- a/src/openlcb/BroadcastTimeClient.cxxtest +++ b/src/openlcb/BroadcastTimeClient.cxxtest @@ -4,7 +4,7 @@ #include -#if 1 +#if 0 #define PRINT_ALL_PACKETS() print_all_packets() #else #define PRINT_ALL_PACKETS() diff --git a/src/openlcb/BroadcastTimeServer.cxx b/src/openlcb/BroadcastTimeServer.cxx index 83a40c4ea..ba339901f 100644 --- a/src/openlcb/BroadcastTimeServer.cxx +++ b/src/openlcb/BroadcastTimeServer.cxx @@ -561,6 +561,7 @@ class BroadcastTimeServerSet { tm.tm_hour = BroadcastTimeDefs::event_to_hour(suffix); tm.tm_min = BroadcastTimeDefs::event_to_min(suffix); + tm.tm_sec = server_->rate_ < 0 ? 59 : 0; break; } case BroadcastTimeDefs::SET_DATE: @@ -576,7 +577,7 @@ class BroadcastTimeServerSet } case BroadcastTimeDefs::SET_RATE: { - server_->rate_ = BroadcastTimeDefs::event_to_rate(suffix); + server_->rate_ = BroadcastTimeDefs::event_to_rate(suffix); break; } default: @@ -652,7 +653,8 @@ class BroadcastTimeServerAlarm : public BroadcastTimeAlarm BroadcastTimeServerAlarm(BroadcastTimeServer *server) : BroadcastTimeAlarm( server->node(), server, - std::bind(&BroadcastTimeServerAlarm::expired_callback, this)) + std::bind(&BroadcastTimeServerAlarm::expired_callback, this, + std::placeholders::_1)) , server_(server) { memset(activeMinutes_, 0, sizeof(activeMinutes_)); @@ -690,10 +692,12 @@ class BroadcastTimeServerAlarm : public BroadcastTimeAlarm } /// callback for when the alarm expires. - void expired_callback() + /// @param done used to notify we are finished + void expired_callback(BarrierNotifiable *done) { server_->time_->request_time(); update_notify(); + done->notify(); } /// Called when the clock time has changed. @@ -726,7 +730,7 @@ class BroadcastTimeServerAlarm : public BroadcastTimeAlarm } else { - seconds -= tm->tm_sec ? tm->tm_sec : 60; + seconds -= tm->tm_sec + 1; } do diff --git a/src/openlcb/BroadcastTimeServer.cxxtest b/src/openlcb/BroadcastTimeServer.cxxtest index eed02578d..ffbde2a3f 100644 --- a/src/openlcb/BroadcastTimeServer.cxxtest +++ b/src/openlcb/BroadcastTimeServer.cxxtest @@ -3,7 +3,7 @@ #include "openlcb/BroadcastTimeServer.hxx" #include "os/FakeClock.hxx" -#if 1 +#if 0 #define PRINT_ALL_PACKETS() print_all_packets() #else #define PRINT_ALL_PACKETS() @@ -113,6 +113,215 @@ TEST_F(BroadcastTimeServerTest, Create) EXPECT_EQ(server_->day_of_year(), 0); }; +TEST_F(BroadcastTimeServerTest, Time) +{ + expect_any_packet(); + EXPECT_CALL(*this, update_callback()).Times(AtLeast(0)); + + // positive rate + server_->set_rate_quarters(2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + wait_for_event_thread(); + + EXPECT_EQ(server_->time(), 0); + server_->start(); + usleep(123456); + EXPECT_EQ(server_->time(), 62); + + // negative rate + server_->stop(); + server_->set_rate_quarters(-2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + wait_for_event_thread(); + + EXPECT_EQ(server_->time(), 59); + server_->start(); + usleep(123456); + EXPECT_EQ(server_->time(), -3); +} + +TEST_F(BroadcastTimeServerTest, FastSecToRealNsecPeriod) +{ + long long real_nsec; + bool result; + + result = server_->fast_sec_to_real_nsec_period(2000, 60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period(-2000, 60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, -120000000LL); + + result = server_->fast_sec_to_real_nsec_period(2000, -60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, -120000000LL); + + result = server_->fast_sec_to_real_nsec_period(-2000, -60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + // boundary check + result = server_->fast_sec_to_real_nsec_period(0, -60, &real_nsec); + EXPECT_FALSE(result); + result = server_->fast_sec_to_real_nsec_period(2048, -60, &real_nsec); + EXPECT_FALSE(result); + result = server_->fast_sec_to_real_nsec_period(-2049, -60, &real_nsec); + EXPECT_FALSE(result); +} + +TEST_F(BroadcastTimeServerTest, RealNsecToFastSecPeriod) +{ + time_t fast_sec; + bool result; + + result = server_->real_nsec_to_fast_sec_period(2000, 1000000000LL, + &fast_sec); + EXPECT_TRUE(result); + EXPECT_EQ(fast_sec, 500); + + result = server_->real_nsec_to_fast_sec_period(-2000, 1000000000LL, + &fast_sec); + EXPECT_TRUE(result); + EXPECT_EQ(fast_sec, -500); + + result = server_->real_nsec_to_fast_sec_period(-2000, -1000000000LL, + &fast_sec); + EXPECT_TRUE(result); + EXPECT_EQ(fast_sec, 500); + + result = server_->real_nsec_to_fast_sec_period(2000, -1000000000LL, + &fast_sec); + EXPECT_TRUE(result); + EXPECT_EQ(fast_sec, -500); + + result = server_->real_nsec_to_fast_sec_period(0, -1000000000LL, + &fast_sec); + EXPECT_FALSE(result); + result = server_->real_nsec_to_fast_sec_period(2048, -1000000000LL, + &fast_sec); + EXPECT_FALSE(result); + result = server_->real_nsec_to_fast_sec_period(-2049, -1000000000LL, + &fast_sec); + EXPECT_FALSE(result); +} + +TEST_F(BroadcastTimeServerTest, FastSecToRealNsecPeriodAbs) +{ + long long real_nsec; + bool result; + + expect_any_packet(); + EXPECT_CALL(*this, update_callback()).Times(AtLeast(0)); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + wait_for_event_thread(); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); +} + +TEST_F(BroadcastTimeServerTest, RealNsecUntilFastTimeAbs) +{ + long long real_nsec; + bool result; + + expect_any_packet(); + EXPECT_CALL(*this, update_callback()).Times(AtLeast(0)); + + // positive rate + server_->set_rate_quarters(2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + wait_for_event_thread(); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + // now start the clock + server_->start(); + usleep(123456); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + // negative rate + server_->stop(); + server_->set_rate_quarters(-2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + wait_for_event_thread(); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(1, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 2000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-1, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 2000000LL); + + // now start the clock + server_->start(); + usleep(123456); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(1, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 2000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-1, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 2000000LL); + +} + TEST_F(BroadcastTimeServerTest, SleepTest) { FakeClock clk; @@ -402,22 +611,22 @@ TEST_F(BroadcastTimeServerTest, DateRolloverBackward) ::testing::Sequence s1, s2; // set events + expect_packet(":X195B422AN010100000100C830;").InSequence(s1); expect_packet(":X195B422AN0101000001008000;").InSequence(s1); expect_packet(":X195B422AN010100000100A102;").InSequence(s1); expect_packet(":X195B422AN010100000100B7B2;").InSequence(s1); - expect_packet(":X195B422AN010100000100C830;").InSequence(s1); // report events + expect_packet(":X195B422AN0101000001004830;").InSequence(s1); expect_packet(":X195B422AN0101000001000000;").InSequence(s1); expect_packet(":X195B422AN0101000001002102;").InSequence(s1); expect_packet(":X195B422AN01010000010037B2;").InSequence(s1); - expect_packet(":X195B422AN0101000001004830;").InSequence(s1); EXPECT_CALL(*this, update_callback()).Times(4); + server_->set_rate_quarters(-2000); server_->set_time(0, 0); server_->set_date(1, 2); server_->set_year(1970); - server_->set_rate_quarters(-2000); wait_for_event_thread(); clear_expect(true); @@ -452,7 +661,7 @@ TEST_F(BroadcastTimeServerTest, DateRolloverBackward) wait_for_event_thread(); // check the time, we give it a finite range just in case of some OS jitter - EXPECT_TRUE(IsBetweenInclusive(server_->time(), 84800, 84802)); + EXPECT_TRUE(IsBetweenInclusive(server_->time(), 84857, 84860)); EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); EXPECT_EQ(server_->day_of_year(), 0); }; From 09ef482b4a53e7cd5305bd5c1f2b2dc210570f60 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:31:26 +0100 Subject: [PATCH 099/171] Fork boards/st-stm32f303x_28x_58x_98x-generic/startup.c to boards/st-stm32l4-generic/startup.c --- .../startup.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/{st-stm32f303x_28x_58x_98x-generic => st-stm32l4-generic}/startup.c (100%) diff --git a/boards/st-stm32f303x_28x_58x_98x-generic/startup.c b/boards/st-stm32l4-generic/startup.c similarity index 100% rename from boards/st-stm32f303x_28x_58x_98x-generic/startup.c rename to boards/st-stm32l4-generic/startup.c From 1ff035e395bdb94e8cd62bb5d234db81f3f03e15 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:31:27 +0100 Subject: [PATCH 100/171] Fork boards/st-stm32f303x_28x_58x_98x-generic/startup.c to boards/st-stm32l4-generic/startup.c step 2 --- .../{startup.c => startup.c.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303x_28x_58x_98x-generic/{startup.c => startup.c.bak} (100%) diff --git a/boards/st-stm32f303x_28x_58x_98x-generic/startup.c b/boards/st-stm32f303x_28x_58x_98x-generic/startup.c.bak similarity index 100% rename from boards/st-stm32f303x_28x_58x_98x-generic/startup.c rename to boards/st-stm32f303x_28x_58x_98x-generic/startup.c.bak From 20b8ee31f806b6daaec20a0b8e06ba496c6c1abe Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:31:28 +0100 Subject: [PATCH 101/171] Fork boards/st-stm32f303x_28x_58x_98x-generic/startup.c to boards/st-stm32l4-generic/startup.c step 4 --- .../{startup.c.bak => startup.c} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303x_28x_58x_98x-generic/{startup.c.bak => startup.c} (100%) diff --git a/boards/st-stm32f303x_28x_58x_98x-generic/startup.c.bak b/boards/st-stm32f303x_28x_58x_98x-generic/startup.c similarity index 100% rename from boards/st-stm32f303x_28x_58x_98x-generic/startup.c.bak rename to boards/st-stm32f303x_28x_58x_98x-generic/startup.c From 2092bd75fe2867241b12ec1de5d71afd99c5a31b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:36:37 +0100 Subject: [PATCH 102/171] Fork boards/st-stm32f303re-nucleo/hardware.hxx to boards/st-stm32l432kc-nucleo/hardware.hxx --- .../{st-stm32f303re-nucleo => st-stm32l432kc-nucleo}/hardware.hxx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/{st-stm32f303re-nucleo => st-stm32l432kc-nucleo}/hardware.hxx (100%) diff --git a/boards/st-stm32f303re-nucleo/hardware.hxx b/boards/st-stm32l432kc-nucleo/hardware.hxx similarity index 100% rename from boards/st-stm32f303re-nucleo/hardware.hxx rename to boards/st-stm32l432kc-nucleo/hardware.hxx From 67500e4daf98a4633efbf57d8eafb1e1975c92d3 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:36:38 +0100 Subject: [PATCH 103/171] Fork boards/st-stm32f303re-nucleo/hardware.hxx to boards/st-stm32l432kc-nucleo/hardware.hxx step 2 --- boards/st-stm32f303re-nucleo/{hardware.hxx => hardware.hxx.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303re-nucleo/{hardware.hxx => hardware.hxx.bak} (100%) diff --git a/boards/st-stm32f303re-nucleo/hardware.hxx b/boards/st-stm32f303re-nucleo/hardware.hxx.bak similarity index 100% rename from boards/st-stm32f303re-nucleo/hardware.hxx rename to boards/st-stm32f303re-nucleo/hardware.hxx.bak From 889e0a3a18c7c49c1d1b7e83a9c2aa038b4d76eb Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:36:39 +0100 Subject: [PATCH 104/171] Fork boards/st-stm32f303re-nucleo/hardware.hxx to boards/st-stm32l432kc-nucleo/hardware.hxx step 4 --- boards/st-stm32f303re-nucleo/{hardware.hxx.bak => hardware.hxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303re-nucleo/{hardware.hxx.bak => hardware.hxx} (100%) diff --git a/boards/st-stm32f303re-nucleo/hardware.hxx.bak b/boards/st-stm32f303re-nucleo/hardware.hxx similarity index 100% rename from boards/st-stm32f303re-nucleo/hardware.hxx.bak rename to boards/st-stm32f303re-nucleo/hardware.hxx From d675ba74853281c1c52bffd0cebc494342c26ac6 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:36:55 +0100 Subject: [PATCH 105/171] Fork boards/st-stm32f303re-nucleo/HwInit.cxx to boards/st-stm32l432kc-nucleo/HwInit.cxx --- .../{st-stm32f303re-nucleo => st-stm32l432kc-nucleo}/HwInit.cxx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/{st-stm32f303re-nucleo => st-stm32l432kc-nucleo}/HwInit.cxx (100%) diff --git a/boards/st-stm32f303re-nucleo/HwInit.cxx b/boards/st-stm32l432kc-nucleo/HwInit.cxx similarity index 100% rename from boards/st-stm32f303re-nucleo/HwInit.cxx rename to boards/st-stm32l432kc-nucleo/HwInit.cxx From ff16e4a1a31d1a534ac1302f6bf6e1cce6ef3aa1 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:36:55 +0100 Subject: [PATCH 106/171] Fork boards/st-stm32f303re-nucleo/HwInit.cxx to boards/st-stm32l432kc-nucleo/HwInit.cxx step 2 --- boards/st-stm32f303re-nucleo/{HwInit.cxx => HwInit.cxx.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303re-nucleo/{HwInit.cxx => HwInit.cxx.bak} (100%) diff --git a/boards/st-stm32f303re-nucleo/HwInit.cxx b/boards/st-stm32f303re-nucleo/HwInit.cxx.bak similarity index 100% rename from boards/st-stm32f303re-nucleo/HwInit.cxx rename to boards/st-stm32f303re-nucleo/HwInit.cxx.bak From c67946883b8d94cb8bb6334fa94d9a625b833e16 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:36:56 +0100 Subject: [PATCH 107/171] Fork boards/st-stm32f303re-nucleo/HwInit.cxx to boards/st-stm32l432kc-nucleo/HwInit.cxx step 4 --- boards/st-stm32f303re-nucleo/{HwInit.cxx.bak => HwInit.cxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303re-nucleo/{HwInit.cxx.bak => HwInit.cxx} (100%) diff --git a/boards/st-stm32f303re-nucleo/HwInit.cxx.bak b/boards/st-stm32f303re-nucleo/HwInit.cxx similarity index 100% rename from boards/st-stm32f303re-nucleo/HwInit.cxx.bak rename to boards/st-stm32f303re-nucleo/HwInit.cxx From ebbf7e9745f053d0ce0c3d13c84870b8a615204f Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:37:11 +0100 Subject: [PATCH 108/171] Fork boards/st-stm32f303re-nucleo/Makefile to boards/st-stm32l432kc-nucleo/Makefile --- boards/{st-stm32f303re-nucleo => st-stm32l432kc-nucleo}/Makefile | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/{st-stm32f303re-nucleo => st-stm32l432kc-nucleo}/Makefile (100%) diff --git a/boards/st-stm32f303re-nucleo/Makefile b/boards/st-stm32l432kc-nucleo/Makefile similarity index 100% rename from boards/st-stm32f303re-nucleo/Makefile rename to boards/st-stm32l432kc-nucleo/Makefile From 6301ee013d79fb3fc5c0fcd05b6151350b3c4dca Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:37:11 +0100 Subject: [PATCH 109/171] Fork boards/st-stm32f303re-nucleo/Makefile to boards/st-stm32l432kc-nucleo/Makefile step 2 --- boards/st-stm32f303re-nucleo/{Makefile => Makefile.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303re-nucleo/{Makefile => Makefile.bak} (100%) diff --git a/boards/st-stm32f303re-nucleo/Makefile b/boards/st-stm32f303re-nucleo/Makefile.bak similarity index 100% rename from boards/st-stm32f303re-nucleo/Makefile rename to boards/st-stm32f303re-nucleo/Makefile.bak From ea7297836177382fe4d0ec23a81f80142f9da581 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:37:12 +0100 Subject: [PATCH 110/171] Fork boards/st-stm32f303re-nucleo/Makefile to boards/st-stm32l432kc-nucleo/Makefile step 4 --- boards/st-stm32f303re-nucleo/{Makefile.bak => Makefile} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303re-nucleo/{Makefile.bak => Makefile} (100%) diff --git a/boards/st-stm32f303re-nucleo/Makefile.bak b/boards/st-stm32f303re-nucleo/Makefile similarity index 100% rename from boards/st-stm32f303re-nucleo/Makefile.bak rename to boards/st-stm32f303re-nucleo/Makefile From 69f257d0bb91714d51479c33a7198984bc618bad Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:37:24 +0100 Subject: [PATCH 111/171] Fork boards/st-stm32f303re-nucleo/memory_map.ld to boards/st-stm32l432kc-nucleo/memory_map.ld --- .../memory_map.ld | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/{st-stm32f303re-nucleo => st-stm32l432kc-nucleo}/memory_map.ld (100%) diff --git a/boards/st-stm32f303re-nucleo/memory_map.ld b/boards/st-stm32l432kc-nucleo/memory_map.ld similarity index 100% rename from boards/st-stm32f303re-nucleo/memory_map.ld rename to boards/st-stm32l432kc-nucleo/memory_map.ld From 86a407fbb14c6b02a459e43d7bc0709bc735ba30 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:37:24 +0100 Subject: [PATCH 112/171] Fork boards/st-stm32f303re-nucleo/memory_map.ld to boards/st-stm32l432kc-nucleo/memory_map.ld step 2 --- boards/st-stm32f303re-nucleo/{memory_map.ld => memory_map.ld.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303re-nucleo/{memory_map.ld => memory_map.ld.bak} (100%) diff --git a/boards/st-stm32f303re-nucleo/memory_map.ld b/boards/st-stm32f303re-nucleo/memory_map.ld.bak similarity index 100% rename from boards/st-stm32f303re-nucleo/memory_map.ld rename to boards/st-stm32f303re-nucleo/memory_map.ld.bak From 23be4b682c229882b7e1d7ddda4261df90627508 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:37:25 +0100 Subject: [PATCH 113/171] Fork boards/st-stm32f303re-nucleo/memory_map.ld to boards/st-stm32l432kc-nucleo/memory_map.ld step 4 --- boards/st-stm32f303re-nucleo/{memory_map.ld.bak => memory_map.ld} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename boards/st-stm32f303re-nucleo/{memory_map.ld.bak => memory_map.ld} (100%) diff --git a/boards/st-stm32f303re-nucleo/memory_map.ld.bak b/boards/st-stm32f303re-nucleo/memory_map.ld similarity index 100% rename from boards/st-stm32f303re-nucleo/memory_map.ld.bak rename to boards/st-stm32f303re-nucleo/memory_map.ld From 6fd7b18c6aaa8a28eeb1983bd6205b2332ca8ad1 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:42:14 +0100 Subject: [PATCH 114/171] Fork etc/stm32cubef3.mk to etc/stm32cubef4.mk --- etc/{stm32cubef3.mk => stm32cubef4.mk} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename etc/{stm32cubef3.mk => stm32cubef4.mk} (100%) diff --git a/etc/stm32cubef3.mk b/etc/stm32cubef4.mk similarity index 100% rename from etc/stm32cubef3.mk rename to etc/stm32cubef4.mk From d6cb091a13a8d01d1262e015147633f8436e8671 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:42:14 +0100 Subject: [PATCH 115/171] Fork etc/stm32cubef3.mk to etc/stm32cubef4.mk step 2 --- etc/{stm32cubef3.mk => stm32cubef3.mk.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename etc/{stm32cubef3.mk => stm32cubef3.mk.bak} (100%) diff --git a/etc/stm32cubef3.mk b/etc/stm32cubef3.mk.bak similarity index 100% rename from etc/stm32cubef3.mk rename to etc/stm32cubef3.mk.bak From d463d806599993167d09fde9b6d0796711d1d037 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:42:15 +0100 Subject: [PATCH 116/171] Fork etc/stm32cubef3.mk to etc/stm32cubef4.mk step 4 --- etc/{stm32cubef3.mk.bak => stm32cubef3.mk} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename etc/{stm32cubef3.mk.bak => stm32cubef3.mk} (100%) diff --git a/etc/stm32cubef3.mk.bak b/etc/stm32cubef3.mk similarity index 100% rename from etc/stm32cubef3.mk.bak rename to etc/stm32cubef3.mk From f99071094e36242d30d9b3524c29569b9db72aa9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:50:11 +0100 Subject: [PATCH 117/171] Fork etc/stm32cubef4.mk to etc/stm32cubel4.mk --- etc/{stm32cubef4.mk => stm32cubel4.mk} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename etc/{stm32cubef4.mk => stm32cubel4.mk} (100%) diff --git a/etc/stm32cubef4.mk b/etc/stm32cubel4.mk similarity index 100% rename from etc/stm32cubef4.mk rename to etc/stm32cubel4.mk From ff2cd0a29da6d65c847ec108dc8daacaf0ea5dfb Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:50:12 +0100 Subject: [PATCH 118/171] Fork etc/stm32cubef4.mk to etc/stm32cubel4.mk step 2 --- etc/{stm32cubef4.mk => stm32cubef4.mk.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename etc/{stm32cubef4.mk => stm32cubef4.mk.bak} (100%) diff --git a/etc/stm32cubef4.mk b/etc/stm32cubef4.mk.bak similarity index 100% rename from etc/stm32cubef4.mk rename to etc/stm32cubef4.mk.bak From 07549dfb0fba281b1fa95482c9f65be2f453ac8d Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 15 Dec 2020 20:50:13 +0100 Subject: [PATCH 119/171] Fork etc/stm32cubef4.mk to etc/stm32cubel4.mk step 4 --- etc/{stm32cubef4.mk.bak => stm32cubef4.mk} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename etc/{stm32cubef4.mk.bak => stm32cubef4.mk} (100%) diff --git a/etc/stm32cubef4.mk.bak b/etc/stm32cubef4.mk similarity index 100% rename from etc/stm32cubef4.mk.bak rename to etc/stm32cubef4.mk From e89db63bb3e7cc3d95b244dda8d57bc92e6e36c1 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Dec 2020 21:07:56 +0100 Subject: [PATCH 120/171] Adds support for STM32L43x MCUs (#486) Adds support for the STM32L432 and STM32L431 MCUs. Adds blink_raw for the nucleo-L432KC board. - Adds stm32 cube L4 makefiles and path variables. - Adds generic board definition for L4 MCUs including interrupt vector table. - Adds compilation target for CubeL4 HAL library for the L432 and L431. - Adds HwInit and board directory for nucleo-L432KC. This HwInit uses the internal RC clock as using an external clock would need soldering on the nucleo board. - Renames FLASH_SIZE constant to EEPROMEMU_FLASH_SIZE to resolve a symbol conflict with the L4 HAL library. - Updates STM32 device drivers to support the L432 and L431 chips - Reduces complexity in some of the ifdefs in the STM32 device drivers. === * Adds stm32F4 cube to path.mk * Update F3 to F4 in cubef4.mk * Adds cubeL4 into path and .mk. * Adds interrupt vector table for STM32L4xx * Adds hardware pin mapping for stm32L432KC Adds clock setup routine. Adds blinker timer routine. * Adds template for HAL module configuration. * Fixes a too common symbol in eepromemu driver. This symbol conflicts with an STM32L4 HAL definition. * Adds L4 HAL includes. * Moves over definitions to L4 hal.h * Adds a missing but required definition for default_handlers. * updates forward declarationsfor handlers. * Updates HwInit to fix compilation errors. * Adjusts makefile for L4 includes. * Adds memory map for L432KC. * Adds missing symbols to cover for link errors. * Updates openocd target cfg file. * Adds l432 driver lib build. * Adds necessary build symlinks. * Adds L4 to stm32can. * Fixes stm32gpio to not need to be range specific. * Fixes stm32I2C to not be range specific. * Fixes stm32SPI for L4. * Fixes stm32uart for L4. * Adds cube432 drivers to the target. * Adds blink_raw target for nucleo 432. * fix compile errors. * fix compile error on master. * line up comments. * more alignment work. * Adds l431 directory for HAL / freertos drivers. * cleanup commented code * Add 431 * Fix whitespace. --- .../HwInit.cxx | 1 + .../Makefile | 1 + .../hardware.hxx | 1 + .../memory_map.ld | 1 + .../startup.c | 1 + .../target.ld | 1 + boards/armv7m/default_handlers.h | 2 + boards/st-stm32l4-generic/startup.c | 540 ++++++------------ boards/st-stm32l4-generic/target.ld | 1 + boards/st-stm32l432kc-nucleo/HwInit.cxx | 113 ++-- boards/st-stm32l432kc-nucleo/Makefile | 12 +- boards/st-stm32l432kc-nucleo/hardware.hxx | 6 +- boards/st-stm32l432kc-nucleo/memory_map.ld | 6 +- boards/st-stm32l432kc-nucleo/startup.c | 1 + boards/st-stm32l432kc-nucleo/target.ld | 1 + boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx | 2 + etc/path.mk | 22 + etc/stm32cubef4.mk | 10 +- etc/stm32cubel4.mk | 10 +- .../common/EEPROMEmulation.cxx | 4 +- .../common/EEPROMEmulation.hxx | 14 +- src/freertos_drivers/sources | 2 +- src/freertos_drivers/st/Stm32Can.cxx | 11 +- src/freertos_drivers/st/Stm32Can.hxx | 12 +- src/freertos_drivers/st/Stm32Gpio.hxx | 12 +- src/freertos_drivers/st/Stm32I2C.cxx | 3 + src/freertos_drivers/st/Stm32I2C.hxx | 12 +- src/freertos_drivers/st/Stm32SPI.cxx | 2 + src/freertos_drivers/st/Stm32SPI.hxx | 14 +- src/freertos_drivers/st/Stm32Uart.cxx | 12 +- src/freertos_drivers/st/Stm32Uart.hxx | 22 +- src/freertos_drivers/st/stm32f_hal_conf.hxx | 2 + src/freertos_drivers/st/stm32l4xx_hal_conf.h | 354 ++++++++++++ .../freertos_drivers/stm32cubel431xx/Makefile | 2 + .../freertos_drivers/stm32cubel431xx/sources | 96 ++++ .../freertos_drivers/stm32cubel432xx/Makefile | 2 + .../freertos_drivers/stm32cubel432xx/sources | 96 ++++ 37 files changed, 877 insertions(+), 527 deletions(-) create mode 120000 applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx create mode 120000 applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile create mode 120000 applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx create mode 120000 applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld create mode 120000 applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c create mode 120000 applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld create mode 120000 boards/st-stm32l4-generic/target.ld create mode 120000 boards/st-stm32l432kc-nucleo/startup.c create mode 120000 boards/st-stm32l432kc-nucleo/target.ld create mode 100644 src/freertos_drivers/st/stm32l4xx_hal_conf.h create mode 100644 targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile create mode 100644 targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources create mode 100644 targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile create mode 100644 targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx new file mode 120000 index 000000000..8bf2ed18c --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/HwInit.cxx \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile new file mode 120000 index 000000000..e13c2970c --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/Makefile \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx new file mode 120000 index 000000000..e27187a64 --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/hardware.hxx \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld new file mode 120000 index 000000000..52c792882 --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/memory_map.ld \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c new file mode 120000 index 000000000..cd5f598b1 --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/startup.c \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld new file mode 120000 index 000000000..403497dce --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/target.ld \ No newline at end of file diff --git a/boards/armv7m/default_handlers.h b/boards/armv7m/default_handlers.h index f6616ce5e..6e867c658 100644 --- a/boards/armv7m/default_handlers.h +++ b/boards/armv7m/default_handlers.h @@ -59,6 +59,8 @@ extern unsigned long __data_section_table_end; extern unsigned long __bss_section_table; extern unsigned long __bss_section_table_end; +#define NVIC_INT_CTRL_R (*((volatile uint32_t *)0xE000ED04)) + /** This hardware initialization code will be called before C++ global objects * are initialized. */ extern void hw_preinit(void); diff --git a/boards/st-stm32l4-generic/startup.c b/boards/st-stm32l4-generic/startup.c index 37cf81d27..77cd48efa 100644 --- a/boards/st-stm32l4-generic/startup.c +++ b/boards/st-stm32l4-generic/startup.c @@ -53,10 +53,10 @@ extern void __libc_init_array(void); extern int main(int argc, char *argv[]); extern void debug_interrupt_handler(void); -extern void watchdog_interrupt_handler(void); -extern void pvd_interrupt_handler(void); -extern void tamper_interrupt_handler(void); -extern void rtc_interrupt_handler(void); +extern void wwdg_interrupt_handler(void); +extern void pvd_pvm_interrupt_handler(void); +extern void tamp_stamp_interrupt_handler(void); +extern void rtc_wkup_interrupt_handler(void); extern void flash_interrupt_handler(void); extern void rcc_interrupt_handler(void); extern void exti0_interrupt_handler(void); @@ -71,19 +71,18 @@ extern void dma1_channel4_interrupt_handler(void); extern void dma1_channel5_interrupt_handler(void); extern void dma1_channel6_interrupt_handler(void); extern void dma1_channel7_interrupt_handler(void); -extern void adc1_2_interrupt_handler(void); -extern void usb_hp_can1_tx_interrupt_handler(void); -extern void usb_lp_can1_rx0_interrupt_handler(void); +extern void adc1_interrupt_handler(void); +extern void can1_tx_interrupt_handler(void); +extern void can1_rx0_interrupt_handler(void); extern void can1_rx1_interrupt_handler(void); extern void can1_sce_interrupt_handler(void); extern void exti9_5_interrupt_handler(void); -extern void tim1_brk_interrupt_handler(void); -extern void tim1_up_interrupt_handler(void); +extern void tim1_brk_tim15_interrupt_handler(void); +extern void tim1_up_tim16_interrupt_handler(void); extern void tim1_trg_com_interrupt_handler(void); extern void tim1_cc_interrupt_handler(void); extern void tim2_interrupt_handler(void); extern void tim3_interrupt_handler(void); -extern void tim4_interrupt_handler(void); extern void i2c1_ev_interrupt_handler(void); extern void i2c1_er_interrupt_handler(void); extern void i2c2_ev_interrupt_handler(void); @@ -95,49 +94,45 @@ extern void usart2_interrupt_handler(void); extern void usart3_interrupt_handler(void); extern void exti15_10_interrupt_handler(void); extern void rtc_alarm_interrupt_handler(void); -extern void usbwakeup_interrupt_handler(void); -extern void tim8_brk_interrupt_handler(void); -extern void tim8_up_interrupt_handler(void); -extern void tim8_trg_com_interrupt_handler(void); -extern void tim8_cc_interrupt_handler(void); -extern void adc3_interrupt_handler(void); -extern void fsmc_interrupt_handler(void); -extern void sdio_interrupt_handler(void); -extern void tim5_interrupt_handler(void); +extern void sdmmc1_interrupt_handler(void); extern void spi3_interrupt_handler(void); extern void uart4_interrupt_handler(void); -extern void uart5_interrupt_handler(void); -extern void tim6_interrupt_handler(void); +extern void tim6_dac_interrupt_handler(void); extern void tim7_interrupt_handler(void); extern void dma2_channel1_interrupt_handler(void); extern void dma2_channel2_interrupt_handler(void); extern void dma2_channel3_interrupt_handler(void); extern void dma2_channel4_interrupt_handler(void); extern void dma2_channel5_interrupt_handler(void); -extern void adc4_interrupt_handler(void); -extern void comp1_2_3_interrupt_handler(void); -extern void comp4_5_6_interrupt_handler(void); -extern void comp7_interrupt_handler(void); +extern void dfsdm1_flt0_interrupt_handler(void); +extern void dfsdm1_flt1_interrupt_handler(void); +extern void comp_interrupt_handler(void); +extern void lptim1_interrupt_handler(void); +extern void lptim2_interrupt_handler(void); +extern void usb_interrupt_handler(void); +extern void dma2_channel6_interrupt_handler(void); +extern void dma2_channel7_interrupt_handler(void); +extern void lpuart1_interrupt_handler(void); +extern void quadspi_interrupt_handler(void); extern void i2c3_ev_interrupt_handler(void); extern void i2c3_er_interrupt_handler(void); -extern void usb_hp_interrupt_handler(void); -extern void usb_lp_interrupt_handler(void); -extern void usb_wakeup_interrupt_handler(void); -extern void tim20_brk_interrupt_handler(void); -extern void tim20_up_interrupt_handler(void); -extern void tim20_trig_com_interrupt_handler(void); -extern void tim20_cc_interrupt_handler(void); +extern void sai1_interrupt_handler(void); +extern void swpmi1_interrupt_handler(void); +extern void tsc_interrupt_handler(void); +extern void aes_interrupt_handler(void); +extern void rng_interrupt_handler(void); extern void fpu_interrupt_handler(void); -extern void spi4_interrupt_handler(void); +extern void crs_interrupt_handler(void); +extern void i2c4_ev_interrupt_handler(void); +extern void i2c4_er_interrupt_handler(void); extern void ignore_fn(void); extern const unsigned long cm3_cpu_clock_hz; /** Exception table */ -__attribute__((section(".interrupt_vector"))) -void ( *const __interrupt_vector[])(void) = -{ - (void (*)(void))(&__stack), /**< 0 initial stack pointer */ +__attribute__(( + section(".interrupt_vector"))) void (*const __interrupt_vector[])(void) = { + (void (*)(void))(&__stack), /**< 0 initial stack pointer */ reset_handler, /**< 1 reset vector */ nmi_handler, /**< 2 non-maskable interrupt */ hard_fault_handler, /**< 3 hard fault */ @@ -153,101 +148,102 @@ void ( *const __interrupt_vector[])(void) = 0, /**< 13 reserved -- bootloader appentry */ PendSV_Handler, /**< 14 pend SV */ SysTick_Handler, /**< 15 system tick */ - watchdog_interrupt_handler, /**< 16 watchdog timer */ - pvd_interrupt_handler, /**< 17 */ - tamper_interrupt_handler, /**< 18 */ - rtc_interrupt_handler, /**< 19 */ - flash_interrupt_handler, /**< 20 */ - rcc_interrupt_handler, /**< 21 */ - exti0_interrupt_handler, /**< 22 */ - exti1_interrupt_handler, /**< 23 */ - exti2_interrupt_handler, /**< 24 */ - exti3_interrupt_handler, /**< 25 */ - exti4_interrupt_handler, /**< 26 */ - dma1_channel1_interrupt_handler, /**< 27 */ - dma1_channel2_interrupt_handler, /**< 28 */ - dma1_channel3_interrupt_handler, /**< 29 */ - dma1_channel4_interrupt_handler, /**< 30 */ - dma1_channel5_interrupt_handler, /**< 31 */ - dma1_channel6_interrupt_handler, /**< 32 */ - dma1_channel7_interrupt_handler, /**< 33 */ - adc1_2_interrupt_handler, /**< 34 */ - usb_hp_can1_tx_interrupt_handler, /**< 35 */ - usb_lp_can1_rx0_interrupt_handler, /**< 36 */ - can1_rx1_interrupt_handler, /**< 37 */ - can1_sce_interrupt_handler, /**< 38 */ - exti9_5_interrupt_handler, /**< 39 */ - tim1_brk_interrupt_handler, /**< 40 */ - tim1_up_interrupt_handler, /**< 41 */ - tim1_trg_com_interrupt_handler, /**< 42 */ - tim1_cc_interrupt_handler, /**< 43 */ - tim2_interrupt_handler, /**< 44 */ - tim3_interrupt_handler, /**< 45 */ - tim4_interrupt_handler, /**< 46 */ - i2c1_ev_interrupt_handler, /**< 47 */ - i2c1_er_interrupt_handler, /**< 48 */ - i2c2_ev_interrupt_handler, /**< 49 */ - i2c2_er_interrupt_handler, /**< 50 */ - spi1_interrupt_handler, /**< 51 */ - spi2_interrupt_handler, /**< 52 */ - usart1_interrupt_handler, /**< 53 */ - usart2_interrupt_handler, /**< 54 */ - usart3_interrupt_handler, /**< 55 */ - exti15_10_interrupt_handler, /**< 56 */ - rtc_alarm_interrupt_handler, /**< 57 */ - usbwakeup_interrupt_handler, /**< 58 */ - tim8_brk_interrupt_handler, /**< 59 */ - tim8_up_interrupt_handler, /**< 60 */ - tim8_trg_com_interrupt_handler, /**< 61 */ - tim8_cc_interrupt_handler, /**< 62 */ - adc3_interrupt_handler, /**< 63 */ - fsmc_interrupt_handler, /**< 64 */ - 0, /**< 65 */ - 0, /**< 66 */ - spi3_interrupt_handler, /**< 67 */ - uart4_interrupt_handler, /**< 68 */ - uart5_interrupt_handler, /**< 69 */ - tim6_interrupt_handler, /**< 70 */ - tim7_interrupt_handler, /**< 71 */ - dma2_channel1_interrupt_handler, /**< 72 */ - dma2_channel2_interrupt_handler, /**< 73 */ - dma2_channel3_interrupt_handler, /**< 74 */ - dma2_channel4_interrupt_handler, /**< 75 */ - dma2_channel5_interrupt_handler, /**< 76 */ - adc4_interrupt_handler, /**< 77 */ + wwdg_interrupt_handler, /**< 0 Window WatchDog Interrupt */ + pvd_pvm_interrupt_handler, /**< 1 PVD/PVM1/PVM3/PVM4 through EXTI Line + detection Interrupts */ + tamp_stamp_interrupt_handler, /**< 2 Tamper and TimeStamp interrupts through + the EXTI line */ + rtc_wkup_interrupt_handler, /**< 3 RTC Wakeup interrupt through the EXTI + line */ + flash_interrupt_handler, /**< 4 FLASH global Interrupt */ + rcc_interrupt_handler, /**< 5 RCC global Interrupt */ + exti0_interrupt_handler, /**< 6 EXTI Line0 Interrupt */ + exti1_interrupt_handler, /**< 7 EXTI Line1 Interrupt */ + exti2_interrupt_handler, /**< 8 EXTI Line2 Interrupt */ + exti3_interrupt_handler, /**< 9 EXTI Line3 Interrupt */ + exti4_interrupt_handler, /**< 10 EXTI Line4 Interrupt */ + dma1_channel1_interrupt_handler, /**< 11 DMA1 Channel 1 global Interrupt */ + dma1_channel2_interrupt_handler, /**< 12 DMA1 Channel 2 global Interrupt */ + dma1_channel3_interrupt_handler, /**< 13 DMA1 Channel 3 global Interrupt */ + dma1_channel4_interrupt_handler, /**< 14 DMA1 Channel 4 global Interrupt */ + dma1_channel5_interrupt_handler, /**< 15 DMA1 Channel 5 global Interrupt */ + dma1_channel6_interrupt_handler, /**< 16 DMA1 Channel 6 global Interrupt */ + dma1_channel7_interrupt_handler, /**< 17 DMA1 Channel 7 global Interrupt */ + adc1_interrupt_handler, /**< 18 ADC1 global Interrupt */ + can1_tx_interrupt_handler, /**< 19 CAN1 TX Interrupt */ + can1_rx0_interrupt_handler, /**< 20 CAN1 RX0 Interrupt */ + can1_rx1_interrupt_handler, /**< 21 CAN1 RX1 Interrupt */ + can1_sce_interrupt_handler, /**< 22 CAN1 SCE Interrupt */ + exti9_5_interrupt_handler, /**< 23 External Line[9:5] Interrupts */ + tim1_brk_tim15_interrupt_handler, /**< 24 TIM1 Break interrupt and TIM15 + global interrupt */ + tim1_up_tim16_interrupt_handler, /**< 25 TIM1 Update Interrupt and TIM16 + global interrupt */ + tim1_trg_com_interrupt_handler, /**< 26 TIM1 Trigger and Commutation + Interrupt */ + tim1_cc_interrupt_handler, /**< 27 TIM1 Capture Compare Interrupt */ + tim2_interrupt_handler, /**< 28 TIM2 global Interrupt */ + tim3_interrupt_handler, /**< 29 TIM3 global Interrupt */ + 0, /**< 30 */ + i2c1_ev_interrupt_handler, /**< 31 I2C1 Event Interrupt */ + i2c1_er_interrupt_handler, /**< 32 I2C1 Error Interrupt */ + i2c2_ev_interrupt_handler, /**< 33 I2C2 Event Interrupt */ + i2c2_er_interrupt_handler, /**< 34 I2C2 Error Interrupt */ + spi1_interrupt_handler, /**< 35 SPI1 global Interrupt */ + spi2_interrupt_handler, /**< 36 SPI2 global Interrupt */ + usart1_interrupt_handler, /**< 37 USART1 global Interrupt */ + usart2_interrupt_handler, /**< 38 USART2 global Interrupt */ + usart3_interrupt_handler, /**< 39 USART3 global Interrupt */ + exti15_10_interrupt_handler, /**< 40 External Line[15:10] Interrupts */ + rtc_alarm_interrupt_handler, /**< 41 RTC Alarm (A and B) through EXTI Line + Interrupt */ + 0, /**< 42 */ + 0, /**< 43 */ + 0, /**< 44 */ + 0, /**< 45 */ + 0, /**< 46 */ + 0, /**< 47 */ + 0, /**< 48 */ + sdmmc1_interrupt_handler, /**< 49 SDMMC1 global Interrupt */ + 0, /**< 50 */ + spi3_interrupt_handler, /**< 51 SPI3 global Interrupt */ + uart4_interrupt_handler, /**< 52 UART4 global Interrupt */ + 0, /**< 53 */ + tim6_dac_interrupt_handler, /**< 54 TIM6 global and DAC1&2 underrun error + interrupts */ + tim7_interrupt_handler, /**< 55 TIM7 global interrupt */ + dma2_channel1_interrupt_handler, /**< 56 DMA2 Channel 1 global Interrupt */ + dma2_channel2_interrupt_handler, /**< 57 DMA2 Channel 2 global Interrupt */ + dma2_channel3_interrupt_handler, /**< 58 DMA2 Channel 3 global Interrupt */ + dma2_channel4_interrupt_handler, /**< 59 DMA2 Channel 4 global Interrupt */ + dma2_channel5_interrupt_handler, /**< 60 DMA2 Channel 5 global Interrupt */ + dfsdm1_flt0_interrupt_handler, /**< 61 DFSDM1 Filter 0 global Interrupt */ + dfsdm1_flt1_interrupt_handler, /**< 62 DFSDM1 Filter 1 global Interrupt */ + 0, /**< 63 */ + comp_interrupt_handler, /**< 64 COMP1 and COMP2 Interrupts */ + lptim1_interrupt_handler, /**< 65 LP TIM1 interrupt */ + lptim2_interrupt_handler, /**< 66 LP TIM2 interrupt */ + usb_interrupt_handler, /**< 67 USB event Interrupt */ + dma2_channel6_interrupt_handler, /**< 68 DMA2 Channel 6 global interrupt */ + dma2_channel7_interrupt_handler, /**< 69 DMA2 Channel 7 global interrupt */ + lpuart1_interrupt_handler, /**< 70 LP UART1 interrupt */ + quadspi_interrupt_handler, /**< 71 Quad SPI global interrupt */ + i2c3_ev_interrupt_handler, /**< 72 I2C3 event interrupt */ + i2c3_er_interrupt_handler, /**< 73 I2C3 error interrupt */ + sai1_interrupt_handler, /**< 74 Serial Audio Interface 1 global interrupt */ + 0, /**< 75 */ + swpmi1_interrupt_handler, /**< 76 SWPMI1 global interrupt */ + tsc_interrupt_handler, /**< 77 Touch Sense Controller global interrupt */ 0, /**< 78 */ - 0, /**< 79 */ - comp1_2_3_interrupt_handler, /**< 80 */ - comp4_5_6_interrupt_handler, /**< 81 */ - comp7_interrupt_handler, /**< 82 */ - 0, /**< 83 */ - 0, /**< 84 */ - 0, /**< 85 */ - 0, /**< 86 */ - 0, /**< 87 */ - i2c3_ev_interrupt_handler, /**< 88 */ - i2c3_er_interrupt_handler, /**< 89 */ - usb_hp_interrupt_handler, /**< 90 */ - usb_lp_interrupt_handler, /**< 91 */ - usb_wakeup_interrupt_handler, /**< 92 */ - tim20_brk_interrupt_handler, /**< 93 */ - tim20_up_interrupt_handler, /**< 94 */ - tim20_trig_com_interrupt_handler, /**< 95 */ - tim20_cc_interrupt_handler, /**< 96 */ - fpu_interrupt_handler, /**< 97 */ - 0, /**< 98 */ - 0, /**< 99 */ - spi4_interrupt_handler, /**< 100 */ - ignore_fn /**< forces the linker to add this fn */ + aes_interrupt_handler, /**< 79 AES global interrupt */ + rng_interrupt_handler, /**< 80 RNG global interrupt */ + fpu_interrupt_handler, /**< 81 FPU global interrupt */ + crs_interrupt_handler, /**< 82 CRS global interrupt */ + i2c4_ev_interrupt_handler, /**< 83 I2C4 Event interrupt */ + i2c4_er_interrupt_handler, /**< 84 I2C4 Error interrupt */ + ignore_fn /**< forces the linker to add this fn */ }; -/*extern unsigned long _etext; -extern unsigned long _data; -extern unsigned long _edata; -extern unsigned long _bss; -extern unsigned long _ebss;*/ - - /** Get the system clock requency. * @return SystemCoreClock */ @@ -266,221 +262,17 @@ uint32_t HAL_GetTick(void) return 0; } -extern unsigned long __data_section_table; -extern unsigned long __data_section_table_end; -extern unsigned long __bss_section_table; -extern unsigned long __bss_section_table_end; - -extern unsigned long _etext; -extern unsigned long _data; -extern unsigned long _edata; -extern unsigned long _bss; -extern unsigned long _ebss; - -/** This hardware initialization code will be called before C++ global objects - * are initialized. */ -extern void hw_preinit(void); - -extern void hw_set_to_safe(void); - -/** Startup the C/C++ runtime environment. - */ -void reset_handler(void) -{ - /* Globally disables interrupts until the FreeRTOS scheduler is up. */ - __asm("cpsid i\n"); - - unsigned long *section_table_addr = &__data_section_table; - - /* copy ram load sections from flash to ram */ - while (section_table_addr < &__data_section_table_end) - { - unsigned long *src = (unsigned long *)*section_table_addr++; - unsigned long *dst = (unsigned long *)*section_table_addr++; - unsigned long len = (unsigned long) *section_table_addr++; - - for ( ; len; len -= 4) - { - *dst++ = *src++; - } - } - - /* zero initialize bss segment(s) */ - while (section_table_addr < &__bss_section_table_end) - { - unsigned long *zero = (unsigned long *)*section_table_addr++; - unsigned long len = (unsigned long) *section_table_addr++; - - for ( ; len; len -= 4) - { - *zero++ = 0; - } - } - - hw_preinit(); - - /* call static constructors */ - __libc_init_array(); - - /* execute main */ - char *argv[] = {0}; - main(0, argv); - - for ( ; /* forever */ ;) - { - /* if we ever return from main, loop forever */ - } -} - -extern void resetblink(unsigned long pattern); -//extern void diewith(unsigned pattern); - - - /* These are volatile to try and prevent the compiler/linker optimising them - away as the variables never actually get used. If the debugger won't show the - values of the variables, make them global my moving their declaration outside - of this function. */ -/** Fault handling information */ -typedef struct fault_information -{ - volatile unsigned long stacked_r0 ; - volatile unsigned long stacked_r1 ; - volatile unsigned long stacked_r2 ; - volatile unsigned long stacked_r3 ; - volatile unsigned long stacked_r12 ; - volatile unsigned long stacked_lr ; - volatile unsigned long stacked_pc ; - volatile unsigned long stacked_psr ; - volatile unsigned long _CFSR ; - volatile unsigned long _HFSR ; - volatile unsigned long _DFSR ; - volatile unsigned long _AFSR ; - volatile unsigned long _BFAR ; - volatile unsigned long _MMAR ; -} FaultInformation; - -/** Global instance so that it can be added to the watch expressions */ -volatile FaultInformation faultInfo; - -/** Decode the stack state prior to an exception occuring. This code is - * inspired by FreeRTOS. - * @param address address of the stack - */ -__attribute__((optimize("-O0"))) void hard_fault_handler_c( unsigned long *hardfault_args ) -{ - /* force a reference in the local variables for debug */ - volatile FaultInformation *fault_info = &faultInfo; - - fault_info->stacked_r0 = ((unsigned long)hardfault_args[0]) ; - fault_info->stacked_r1 = ((unsigned long)hardfault_args[1]) ; - fault_info->stacked_r2 = ((unsigned long)hardfault_args[2]) ; - fault_info->stacked_r3 = ((unsigned long)hardfault_args[3]) ; - fault_info->stacked_r12 = ((unsigned long)hardfault_args[4]) ; - fault_info->stacked_lr = ((unsigned long)hardfault_args[5]) ; - fault_info->stacked_pc = ((unsigned long)hardfault_args[6]) ; - fault_info->stacked_psr = ((unsigned long)hardfault_args[7]) ; - - // Configurable Fault Status Register - // Consists of MMSR, BFSR and UFSR - fault_info->_CFSR = (*((volatile unsigned long *)(0xE000ED28))) ; - - // Hard Fault Status Register - fault_info->_HFSR = (*((volatile unsigned long *)(0xE000ED2C))) ; - - // Debug Fault Status Register - fault_info->_DFSR = (*((volatile unsigned long *)(0xE000ED30))) ; - - // Auxiliary Fault Status Register - fault_info->_AFSR = (*((volatile unsigned long *)(0xE000ED3C))) ; - - // Read the Fault Address Registers. These may not contain valid values. - // Check BFARVALID/MMARVALID to see if they are valid values - // MemManage Fault Address Register - fault_info->_MMAR = (*((volatile unsigned long *)(0xE000ED34))) ; - // Bus Fault Address Register - fault_info->_BFAR = (*((volatile unsigned long *)(0xE000ED38))) ; - - hw_set_to_safe(); - - __asm("BKPT #0\n") ; // Break into the debugger - - /* When the following line is hit, the variables contain the register values. */ - if (fault_info->stacked_r0 || fault_info->stacked_r1 || - fault_info->stacked_r2 || fault_info->stacked_r3 || - fault_info->stacked_r12 || fault_info->stacked_lr || - fault_info->stacked_pc || fault_info->stacked_psr || - fault_info->_CFSR || fault_info->_HFSR || - fault_info->_DFSR || fault_info->_AFSR || - fault_info->_MMAR || fault_info->_BFAR) - { - resetblink(BLINK_DIE_HARDFAULT); - for( ;; ); - } -} - -/** The fault handler implementation. This code is inspired by FreeRTOS. - */ -__attribute__((__naked__)) static void hard_fault_handler(void) -{ - __asm volatile - ( - " tst lr, #4 \n" - " ite eq \n" - " mrseq r0, msp \n" - " mrsne r0, psp \n" - " b hard_fault_handler_c \n" - " bx lr \n" - ); -} - - -static void nmi_handler(void) -{ - for (; /* forever */;) - { - } -} - -static void mpu_fault_handler(void) -{ - for ( ; /* forever */ ; ) - { - } -} - -static void bus_fault_handler(void) -{ - for ( ; /* forever */ ; ) - { - } -} - -static void usage_fault_handler(void) -{ - for ( ; /* forever */ ; ) - { - } -} - -/** This is the default handler for exceptions not defined by the application. - */ -void default_interrupt_handler(void) __attribute__((weak)); -void default_interrupt_handler(void) -{ - while (1) - ; - diewith(BLINK_DIE_UNEXPIRQ); -} +#include "../boards/armv7m/default_handlers.h" void debug_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); -void watchdog_interrupt_handler(void) +void wwdg_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void pvd_interrupt_handler(void) +void pvd_pvm_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void tamper_interrupt_handler(void) +void tamp_stamp_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void rtc_interrupt_handler(void) +void rtc_wkup_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void flash_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); @@ -510,11 +302,11 @@ void dma1_channel6_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void dma1_channel7_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void adc1_2_interrupt_handler(void) +void adc1_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void usb_hp_can1_tx_interrupt_handler(void) +void can1_tx_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void usb_lp_can1_rx0_interrupt_handler(void) +void can1_rx0_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void can1_rx1_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); @@ -522,9 +314,9 @@ void can1_sce_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void exti9_5_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void tim1_brk_interrupt_handler(void) +void tim1_brk_tim15_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void tim1_up_interrupt_handler(void) +void tim1_up_tim16_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void tim1_trg_com_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); @@ -534,8 +326,6 @@ void tim2_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void tim3_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void tim4_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); void i2c1_ev_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void i2c1_er_interrupt_handler(void) @@ -558,31 +348,13 @@ void exti15_10_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void rtc_alarm_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void usbwakeup_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); -void tim8_brk_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); -void tim8_up_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); -void tim8_trg_com_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); -void tim8_cc_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); -void adc3_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); -void fsmc_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); -void sdio_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); -void tim5_interrupt_handler(void) +void sdmmc1_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void spi3_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void uart4_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void uart5_interrupt_handler(void) - __attribute__((weak, alias("default_interrupt_handler"))); -void tim6_interrupt_handler(void) +void tim6_dac_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void tim7_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); @@ -596,33 +368,45 @@ void dma2_channel4_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void dma2_channel5_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void adc4_interrupt_handler(void) +void dfsdm1_flt0_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dfsdm1_flt1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void comp_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void lptim1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void lptim2_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void comp1_2_3_interrupt_handler(void) +void usb_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void comp4_5_6_interrupt_handler(void) +void dma2_channel6_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void comp7_interrupt_handler(void) +void dma2_channel7_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void lpuart1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void quadspi_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void i2c3_ev_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); void i2c3_er_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void usb_hp_interrupt_handler(void) +void sai1_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void usb_lp_interrupt_handler(void) +void swpmi1_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void usb_wakeup_interrupt_handler(void) +void tsc_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void tim20_brk_interrupt_handler(void) +void aes_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void tim20_up_interrupt_handler(void) +void rng_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void tim20_trig_com_interrupt_handler(void) +void fpu_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void tim20_cc_interrupt_handler(void) +void crs_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void fpu_interrupt_handler(void) +void i2c4_ev_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); -void spi4_interrupt_handler(void) +void i2c4_er_interrupt_handler(void) __attribute__((weak, alias("default_interrupt_handler"))); diff --git a/boards/st-stm32l4-generic/target.ld b/boards/st-stm32l4-generic/target.ld new file mode 120000 index 000000000..6a6d5092b --- /dev/null +++ b/boards/st-stm32l4-generic/target.ld @@ -0,0 +1 @@ +../armv7m/target.ld \ No newline at end of file diff --git a/boards/st-stm32l432kc-nucleo/HwInit.cxx b/boards/st-stm32l432kc-nucleo/HwInit.cxx index c25c032a8..675c33298 100644 --- a/boards/st-stm32l432kc-nucleo/HwInit.cxx +++ b/boards/st-stm32l432kc-nucleo/HwInit.cxx @@ -36,8 +36,8 @@ #include #include -#include "stm32f3xx_hal_conf.h" -#include "stm32f3xx_hal.h" +#include "stm32f_hal_conf.hxx" +#include "stm32l4xx_hal.h" #include "os/OS.hxx" #include "Stm32Uart.hxx" @@ -61,7 +61,7 @@ static Stm32Uart uart0("/dev/ser0", USART2, USART2_IRQn); static Stm32Can can0("/dev/can0"); /** EEPROM emulation driver. The file size might be made bigger. */ -static Stm32EEPROMEmulation eeprom0("/dev/eeprom", 512); +//static Stm32EEPROMEmulation eeprom0("/dev/eeprom", 512); const size_t EEPROMEmulation::SECTOR_SIZE = 2048; @@ -94,13 +94,12 @@ void setblink(uint32_t pattern) } -/// TIM17 shares this interrupt with certain features of timer1 -void tim1_trg_com_interrupt_handler(void) +void tim7_interrupt_handler(void) { // // Clear the timer interrupt. // - TIM17->SR = ~TIM_IT_UPDATE; + TIM7->SR = ~TIM_IT_UPDATE; // Set output LED. BLINKER_RAW_Pin::set(rest_pattern & 1); @@ -124,62 +123,76 @@ void diewith(uint32_t pattern) } /** CPU clock speed. */ -const unsigned long cm3_cpu_clock_hz = 72000000; +const unsigned long cm3_cpu_clock_hz = 80000000; uint32_t SystemCoreClock; const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9}; const uint8_t APBPrescTable[8] = {0, 0, 0, 0, 1, 2, 3, 4}; +const uint32_t MSIRangeTable[12] = {100000U, 200000U, 400000U, 800000U, + 1000000U, 2000000U, 4000000U, 8000000U, 16000000U, 24000000U, 32000000U, + 48000000U}; +const uint32_t HSEValue = 8000000; /** * @brief System Clock Configuration * The system Clock is configured as follow : - * System Clock source = PLL (HSE) - * SYSCLK(Hz) = 72000000 - * HCLK(Hz) = 72000000 + * System Clock source = PLL (MSI) + * SYSCLK(Hz) = 80000000 + * HCLK(Hz) = 80000000 * AHB Prescaler = 1 - * APB1 Prescaler = 2 + * APB1 Prescaler = 1 * APB2 Prescaler = 1 - * HSE Frequency(Hz) = 8000000 - * HSE PREDIV = 1 - * PLLMUL = 9 - * Flash Latency(WS) = 2 + * MSI Frequency(Hz) = 4000000 + * PLL_M = 1 + * PLL_N = 40 + * PLL_R = 2 + * PLL_P = 7 + * PLL_Q = 4 + * Flash Latency(WS) = 4 * @param None * @retval None */ static void clock_setup(void) { HAL_RCC_DeInit(); - + RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitTypeDef RCC_OscInitStruct; - /* Enable HSE Oscillator and activate PLL with HSE as source on bypass - * mode. This allows using the MCO clock output from the ST_Link part of - * the nucleo board and freeing up the other clock pin for GPIO. */ - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; - RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS; + /* MSI is enabled after System reset, activate PLL with MSI as source */ + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI; + RCC_OscInitStruct.MSIState = RCC_MSI_ON; + RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6; + RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; - RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; - RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1; - - HAL_RCC_OscConfig(&RCC_OscInitStruct); - - /* Select PLL as system clock source and configure the HCLK, PCLK1 and - * PCLK2 clocks dividers - */ + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI; + RCC_OscInitStruct.PLL.PLLM = 1; + RCC_OscInitStruct.PLL.PLLN = 40; + RCC_OscInitStruct.PLL.PLLR = 2; + RCC_OscInitStruct.PLL.PLLP = 7; + RCC_OscInitStruct.PLL.PLLQ = 4; + HASSERT(HAL_RCC_OscConfig(&RCC_OscInitStruct) == HAL_OK); + + /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 + clocks dividers */ RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | - RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); + RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; - - HASSERT(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) == HAL_OK); + HASSERT(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) == HAL_OK); // This will fail if the clocks are somehow misconfigured. HASSERT(SystemCoreClock == cm3_cpu_clock_hz); } +/// We don't need the HAL tick configuration code to run. FreeRTOS will take +/// care of that. +HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority) +{ + return HAL_OK; +} + /** Initialize the processor hardware. */ void hw_preinit(void) @@ -187,9 +200,9 @@ void hw_preinit(void) /* Globally disables interrupts until the FreeRTOS scheduler is up. */ asm("cpsid i\n"); - /* these FLASH settings enable opertion at 72 MHz */ + /* these FLASH settings enable opertion at 80 MHz */ __HAL_FLASH_PREFETCH_BUFFER_ENABLE(); - __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_2); + __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_4); /* setup the system clock */ clock_setup(); @@ -197,41 +210,41 @@ void hw_preinit(void) /* enable peripheral clocks */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); - __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_CAN1_CLK_ENABLE(); - __HAL_RCC_TIM17_CLK_ENABLE(); + __HAL_RCC_TIM7_CLK_ENABLE(); /* setup pinmux */ GPIO_InitTypeDef gpio_init; memset(&gpio_init, 0, sizeof(gpio_init)); - /* USART2 pinmux on PA2 and PA3 */ + /* USART2 pinmux on PA2 and PA15 */ gpio_init.Mode = GPIO_MODE_AF_PP; gpio_init.Pull = GPIO_PULLUP; gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; gpio_init.Alternate = GPIO_AF7_USART2; gpio_init.Pin = GPIO_PIN_2; HAL_GPIO_Init(GPIOA, &gpio_init); - gpio_init.Pin = GPIO_PIN_3; + gpio_init.Pin = GPIO_PIN_15; + gpio_init.Alternate = GPIO_AF3_USART2; HAL_GPIO_Init(GPIOA, &gpio_init); - /* CAN pinmux on PB8 and PB9 */ + /* CAN pinmux on PA11 and PA12 */ gpio_init.Mode = GPIO_MODE_AF_PP; gpio_init.Pull = GPIO_PULLUP; gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; - gpio_init.Alternate = GPIO_AF9_CAN; - gpio_init.Pin = GPIO_PIN_8; - HAL_GPIO_Init(GPIOB, &gpio_init); - gpio_init.Pin = GPIO_PIN_9; - HAL_GPIO_Init(GPIOB, &gpio_init); + gpio_init.Alternate = GPIO_AF9_CAN1; + gpio_init.Pin = GPIO_PIN_11; + HAL_GPIO_Init(GPIOA, &gpio_init); + gpio_init.Pin = GPIO_PIN_12; + HAL_GPIO_Init(GPIOA, &gpio_init); GpioInit::hw_init(); /* Initializes the blinker timer. */ TIM_HandleTypeDef TimHandle; memset(&TimHandle, 0, sizeof(TimHandle)); - TimHandle.Instance = TIM17; + TimHandle.Instance = TIM7; TimHandle.Init.Period = configCPU_CLOCK_HZ / 10000 / 8; TimHandle.Init.Prescaler = 10000; TimHandle.Init.ClockDivision = 0; @@ -247,9 +260,9 @@ void hw_preinit(void) /* Starting Error */ HASSERT(0); } - __HAL_DBGMCU_FREEZE_TIM17(); - SetInterruptPriority(TIM17_IRQn, 0); - NVIC_EnableIRQ(TIM17_IRQn); + __HAL_DBGMCU_FREEZE_TIM7(); + SetInterruptPriority(TIM7_IRQn, 0); + NVIC_EnableIRQ(TIM7_IRQn); } void usart2_interrupt_handler(void) diff --git a/boards/st-stm32l432kc-nucleo/Makefile b/boards/st-stm32l432kc-nucleo/Makefile index c0c8ce173..414f19595 100644 --- a/boards/st-stm32l432kc-nucleo/Makefile +++ b/boards/st-stm32l432kc-nucleo/Makefile @@ -10,16 +10,16 @@ sh -c "if [ \"X`printenv OPENMRNPATH`\" != \"X\" ]; then printenv OPENMRNPATH; \ else echo OPENMRNPATH not found; fi" \ ) -# Find STM32CubeF3 libraries -include $(OPENMRNPATH)/etc/stm32cubef3.mk +# Find STM32CubeL4 libraries +include $(OPENMRNPATH)/etc/stm32cubel4.mk LDFLAGSEXTRA += -SYSLIBRARIESEXTRA += -lfreertos_drivers_stm32cubef303xe +SYSLIBRARIESEXTRA += -lfreertos_drivers_stm32cubel432xx OBJEXTRA += -CFLAGS += -DSTM32F303xE -CXXFLAGS += -DSTM32F303xE -OPENOCDARGS = -f board/st_nucleo_f3.cfg +CFLAGS += -DSTM32L432xx +CXXFLAGS += -DSTM32L432xx +OPENOCDARGS = -f board/st_nucleo_l476rg.cfg ifndef TARGET export TARGET := freertos.armv7m diff --git a/boards/st-stm32l432kc-nucleo/hardware.hxx b/boards/st-stm32l432kc-nucleo/hardware.hxx index 98f53461b..3b34753f9 100644 --- a/boards/st-stm32l432kc-nucleo/hardware.hxx +++ b/boards/st-stm32l432kc-nucleo/hardware.hxx @@ -3,11 +3,9 @@ #include "utils/GpioInitializer.hxx" #include "BlinkerGPIO.hxx" -GPIO_PIN(LED_GREEN_RAW, LedPin, A, 5); +GPIO_PIN(LED_GREEN_RAW, LedPin, B, 3); -GPIO_PIN(SW_USER, GpioInputPU, C, 13); - -typedef GpioInitializer GpioInit; +typedef GpioInitializer GpioInit; typedef LED_GREEN_RAW_Pin BLINKER_RAW_Pin; typedef BLINKER_Pin LED_GREEN_Pin; diff --git a/boards/st-stm32l432kc-nucleo/memory_map.ld b/boards/st-stm32l432kc-nucleo/memory_map.ld index 02e5eb212..f5ed6e1e7 100644 --- a/boards/st-stm32l432kc-nucleo/memory_map.ld +++ b/boards/st-stm32l432kc-nucleo/memory_map.ld @@ -1,8 +1,8 @@ MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 488K - EEPROMEMU (r) : ORIGIN = 0x0807A000, LENGTH = 16K - BOOTLOADER (rx) : ORIGIN = 0x0807E000, LENGTH = 8K + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 232K + EEPROMEMU (r) : ORIGIN = 0x0803A000, LENGTH = 16K + BOOTLOADER (rx) : ORIGIN = 0x0803E000, LENGTH = 8K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } diff --git a/boards/st-stm32l432kc-nucleo/startup.c b/boards/st-stm32l432kc-nucleo/startup.c new file mode 120000 index 000000000..d4c86bd16 --- /dev/null +++ b/boards/st-stm32l432kc-nucleo/startup.c @@ -0,0 +1 @@ +../st-stm32l4-generic/startup.c \ No newline at end of file diff --git a/boards/st-stm32l432kc-nucleo/target.ld b/boards/st-stm32l432kc-nucleo/target.ld new file mode 120000 index 000000000..4c654e89a --- /dev/null +++ b/boards/st-stm32l432kc-nucleo/target.ld @@ -0,0 +1 @@ +../st-stm32l4-generic/target.ld \ No newline at end of file diff --git a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx index 15f7b211a..4eb4a0fe5 100644 --- a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx +++ b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx @@ -238,6 +238,8 @@ struct DccHwDefs { * '1' bit */ static int dcc_preamble_count() { return 16; } + static bool generate_railcom_halfzero() { return false; } + static void flip_led() {} /** the time (in nanoseconds) to wait between turning off the low driver and diff --git a/etc/path.mk b/etc/path.mk index 261a95eff..b0a54986c 100644 --- a/etc/path.mk +++ b/etc/path.mk @@ -103,6 +103,28 @@ STM32CUBEF3PATH:=$(TRYPATH) endif endif #STM32CUBEF3PATH +################ STM32Cube_F4 ################## +ifndef STM32CUBEF4PATH +SEARCHPATH := \ + /opt/st/STM32Cube_FW_F4/default + +TRYPATH:=$(call findfirst,Drivers,$(SEARCHPATH)) +ifneq ($(TRYPATH),) +STM32CUBEF4PATH:=$(TRYPATH) +endif +endif #STM32CUBEF4PATH + +################ STM32Cube_L4 ################## +ifndef STM32CUBEL4PATH +SEARCHPATH := \ + /opt/st/STM32Cube_FW_L4/default + +TRYPATH:=$(call findfirst,Drivers,$(SEARCHPATH)) +ifneq ($(TRYPATH),) +STM32CUBEL4PATH:=$(TRYPATH) +endif +endif #STM32CUBEL4PATH + ################ STM32Cube_F7 ################## ifndef STM32CUBEF7PATH SEARCHPATH := \ diff --git a/etc/stm32cubef4.mk b/etc/stm32cubef4.mk index 78a949cf9..d8322912d 100644 --- a/etc/stm32cubef4.mk +++ b/etc/stm32cubef4.mk @@ -1,13 +1,13 @@ include $(OPENMRNPATH)/etc/path.mk -ifdef STM32CUBEF3PATH +ifdef STM32CUBEF4PATH INCLUDES += -I$(OPENMRNPATH)/src/freertos_drivers/st \ - -I$(STM32CUBEF3PATH)/Drivers/STM32F3xx_HAL_Driver/Inc \ - -I$(STM32CUBEF3PATH)/Drivers/CMSIS/Device/ST/STM32F3xx/Include \ - -I$(STM32CUBEF3PATH)/Drivers/CMSIS/Include + -I$(STM32CUBEF4PATH)/Drivers/STM32F4xx_HAL_Driver/Inc \ + -I$(STM32CUBEF4PATH)/Drivers/CMSIS/Device/ST/STM32F4xx/Include \ + -I$(STM32CUBEF4PATH)/Drivers/CMSIS/Include endif CFLAGS += CXXFLAGS += -DEPS += STM32CUBEF3PATH +DEPS += STM32CUBEF4PATH diff --git a/etc/stm32cubel4.mk b/etc/stm32cubel4.mk index 78a949cf9..bfb699b1b 100644 --- a/etc/stm32cubel4.mk +++ b/etc/stm32cubel4.mk @@ -1,13 +1,13 @@ include $(OPENMRNPATH)/etc/path.mk -ifdef STM32CUBEF3PATH +ifdef STM32CUBEL4PATH INCLUDES += -I$(OPENMRNPATH)/src/freertos_drivers/st \ - -I$(STM32CUBEF3PATH)/Drivers/STM32F3xx_HAL_Driver/Inc \ - -I$(STM32CUBEF3PATH)/Drivers/CMSIS/Device/ST/STM32F3xx/Include \ - -I$(STM32CUBEF3PATH)/Drivers/CMSIS/Include + -I$(STM32CUBEL4PATH)/Drivers/STM32L4xx_HAL_Driver/Inc \ + -I$(STM32CUBEL4PATH)/Drivers/CMSIS/Device/ST/STM32L4xx/Include \ + -I$(STM32CUBEL4PATH)/Drivers/CMSIS/Include endif CFLAGS += CXXFLAGS += -DEPS += STM32CUBEF3PATH +DEPS += STM32CUBEL4PATH diff --git a/src/freertos_drivers/common/EEPROMEmulation.cxx b/src/freertos_drivers/common/EEPROMEmulation.cxx index 0751899d1..89630aff5 100644 --- a/src/freertos_drivers/common/EEPROMEmulation.cxx +++ b/src/freertos_drivers/common/EEPROMEmulation.cxx @@ -50,8 +50,8 @@ EEPROMEmulation::EEPROMEmulation(const char *name, size_t file_size) : EEPROM(name, file_size) { /* make sure we have an appropriate sized region of memory for our device */ - HASSERT(FLASH_SIZE >= (2 * SECTOR_SIZE)); // at least two of them - HASSERT((FLASH_SIZE % SECTOR_SIZE) == 0); // and nothing remaining + HASSERT(EEPROMEMU_FLASH_SIZE >= (2 * SECTOR_SIZE)); // at least two of them + HASSERT((EEPROMEMU_FLASH_SIZE % SECTOR_SIZE) == 0); // and nothing remaining HASSERT(file_size <= (SECTOR_SIZE >> 1)); // single block fit all the data HASSERT(file_size <= (1024 * 64 - 2)); // uint16 indexes, 0xffff reserved HASSERT(BLOCK_SIZE >= 4); // we don't support block sizes less than 4 bytes diff --git a/src/freertos_drivers/common/EEPROMEmulation.hxx b/src/freertos_drivers/common/EEPROMEmulation.hxx index c0932e1e8..b8e5d4680 100644 --- a/src/freertos_drivers/common/EEPROMEmulation.hxx +++ b/src/freertos_drivers/common/EEPROMEmulation.hxx @@ -36,14 +36,12 @@ #include "EEPROM.hxx" -#ifndef FLASH_SIZE /// Linker-defined symbol where in the memory space (flash) the eeprom /// emulation data starts. extern const char __eeprom_start; /// Linker-defined symbol where in the memory space (flash) the eeprom /// emulation data ends. extern const char __eeprom_end; -#endif /** Emulates EEPROM in FLASH for the Tiva, LPC17xx and LPC40xx * platforms. Applicable in general to any microcontroller with self-writeable @@ -86,8 +84,8 @@ extern const char __eeprom_end; * Parameters: * @param SECTOR_SIZE: size of independently erased flash areas. Usually in * the range of kilobytes; for example somewhere between 1-16 kbytes. - * @param FLASH_SIZE: Automatically detected from the linker symbols. An - * integer (at least 2) multiple of SECTOR_SIZE. Sectors within the + * @param EEPROMEMU_FLASH_SIZE: Automatically detected from the linker symbols. + * An integer (at least 2) multiple of SECTOR_SIZE. Sectors within the * designatedflash are will be used in a round-robin manner to maximize flash * endurance. * @param BLOCK_SIZE: Defines how many bytes shall be flashed in one @@ -172,16 +170,12 @@ private: */ void read(unsigned int offset, void *buf, size_t len) OVERRIDE; -#ifndef FLASH_SIZE /** Total FLASH memory size to use for EEPROM Emulation. Must be at least * 2 sectors large and at least 4x the total amount of EEPROM address space * that will be emulated. Larger sizes will result in greater endurance. * must be a macro in order to calculate from link time constants. */ - #define FLASH_SIZE ((uintptr_t)(&__eeprom_end - &__eeprom_start)) -#else - static const size_t FLASH_SIZE; -#endif + #define EEPROMEMU_FLASH_SIZE ((uintptr_t)(&__eeprom_end - &__eeprom_start)) /** useful data bytes size in bytes * @todo maybe this should be a macro of BLOCK_SIZE / 2 @@ -307,7 +301,7 @@ private: protected: /** Total number of sectors available. */ - const uint8_t sectorCount_{(uint8_t)(FLASH_SIZE / SECTOR_SIZE)}; + const uint8_t sectorCount_{(uint8_t)(EEPROMEMU_FLASH_SIZE / SECTOR_SIZE)}; /** Index of the active sector. */ uint8_t activeSector_{0}; diff --git a/src/freertos_drivers/sources b/src/freertos_drivers/sources index a7e08f519..838c81928 100644 --- a/src/freertos_drivers/sources +++ b/src/freertos_drivers/sources @@ -38,7 +38,7 @@ endif ifeq ($(TARGET),freertos.armv7m) SUBDIRS += mbed_lpc1768 drivers_lpc1768 tivaware lpc_chip_175x_6x \ stm32cubef103xb stm32cubef303x_28x_58x_98x stm32cubef303xe \ - stm32cubef767xx \ + stm32cubel431xx stm32cubel432xx stm32cubef767xx \ cc3220sdk net_cc3220 cc3220 \ net_freertos_tcp freertos_tcp ti_grlib \ spiffs_cc32x0sf spiffs_stm32f303xe spiffs_stm32f767xx \ diff --git a/src/freertos_drivers/st/Stm32Can.cxx b/src/freertos_drivers/st/Stm32Can.cxx index 5d45803a4..b62d25707 100644 --- a/src/freertos_drivers/st/Stm32Can.cxx +++ b/src/freertos_drivers/st/Stm32Can.cxx @@ -65,6 +65,15 @@ #define CAN_SECOND_IRQN USB_LP_CAN_RX0_IRQn #define CAN_CLOCK (cm3_cpu_clock_hz >> 1) +#elif defined (STM32L431xx) || defined (STM32L432xx) + +#include "stm32l4xx_hal_cortex.h" +#define SPLIT_INT +#define CAN_TX_IRQN CAN1_TX_IRQn +#define CAN_IRQN CAN_TX_IRQN +#define CAN_SECOND_IRQN CAN1_RX0_IRQn +#define CAN_CLOCK (cm3_cpu_clock_hz) + #elif defined (STM32F767xx) #include "stm32f7xx_hal_cortex.h" @@ -430,7 +439,7 @@ void usb_lp_can1_rx0_interrupt_handler(void) Stm32Can::instances[0]->rx_interrupt_handler(); } -#elif defined(STM32F767xx) +#elif defined(STM32F767xx) || defined(STM32L431xx) || defined(STM32L432xx) void can1_tx_interrupt_handler(void) { diff --git a/src/freertos_drivers/st/Stm32Can.hxx b/src/freertos_drivers/st/Stm32Can.hxx index 492273e86..86812386f 100644 --- a/src/freertos_drivers/st/Stm32Can.hxx +++ b/src/freertos_drivers/st/Stm32Can.hxx @@ -43,17 +43,7 @@ #include "freertos_drivers/common/Can.hxx" #endif -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_can.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_can.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_can.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_can.h" -#else -#error Dont know what STM32 chip you have. -#endif +#include "stm32f_hal_conf.hxx" /** Specialization of CAN driver for LPC17xx and LPC40xx CAN. */ diff --git a/src/freertos_drivers/st/Stm32Gpio.hxx b/src/freertos_drivers/st/Stm32Gpio.hxx index ec2897897..868532010 100644 --- a/src/freertos_drivers/st/Stm32Gpio.hxx +++ b/src/freertos_drivers/st/Stm32Gpio.hxx @@ -39,17 +39,7 @@ #include "os/Gpio.hxx" #include "GpioWrapper.hxx" -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_gpio.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_gpio.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_gpio.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_gpio.h" -#else -#error Dont know what STM32 chip you have. -#endif +#include "stm32f_hal_conf.hxx" /// Static GPIO implementation for the STM32 microcontrollers. Do not use /// directly: use @ref GPIO_PIN macro. diff --git a/src/freertos_drivers/st/Stm32I2C.cxx b/src/freertos_drivers/st/Stm32I2C.cxx index b0ec31737..4884d2419 100644 --- a/src/freertos_drivers/st/Stm32I2C.cxx +++ b/src/freertos_drivers/st/Stm32I2C.cxx @@ -41,6 +41,9 @@ #include "stm32f1xx_ll_rcc.h" #elif defined(STM32F303xC) || defined(STM32F303xE) #include "stm32f3xx_ll_rcc.h" +#elif defined(STM32L431xx) || defined(STM32L432xx) +#include "stm32l4xx_ll_rcc.h" +#include "stm32l4xx_ll_i2c.h" #elif defined(STM32F767xx) #include "stm32f7xx_ll_rcc.h" #include "stm32f7xx_ll_i2c.h" diff --git a/src/freertos_drivers/st/Stm32I2C.hxx b/src/freertos_drivers/st/Stm32I2C.hxx index 1a509f9ba..062ba81cd 100644 --- a/src/freertos_drivers/st/Stm32I2C.hxx +++ b/src/freertos_drivers/st/Stm32I2C.hxx @@ -38,17 +38,7 @@ #include "I2C.hxx" -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_conf.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_conf.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_conf.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_conf.h" -#else -#error Dont know what STM32 chip you have. -#endif +#include "stm32f_hal_conf.hxx" /** Specialization of I2C driver for STM32 devices. */ diff --git a/src/freertos_drivers/st/Stm32SPI.cxx b/src/freertos_drivers/st/Stm32SPI.cxx index d4f6bea23..3e3170b2a 100644 --- a/src/freertos_drivers/st/Stm32SPI.cxx +++ b/src/freertos_drivers/st/Stm32SPI.cxx @@ -41,6 +41,8 @@ #include "stm32f1xx_ll_rcc.h" #elif defined(STM32F303xC) || defined(STM32F303xE) #include "stm32f3xx_ll_rcc.h" +#elif defined(STM32L432xx) || defined(STM32L431xx) +#include "stm32l4xx_ll_rcc.h" #elif defined(STM32F767xx) #include "stm32f7xx_ll_rcc.h" #else diff --git a/src/freertos_drivers/st/Stm32SPI.hxx b/src/freertos_drivers/st/Stm32SPI.hxx index c4953e1d8..33154da2e 100644 --- a/src/freertos_drivers/st/Stm32SPI.hxx +++ b/src/freertos_drivers/st/Stm32SPI.hxx @@ -38,19 +38,7 @@ #include "SPI.hxx" - -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_conf.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_conf.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_conf.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_conf.h" -#else -#error Dont know what STM32 chip you have. -#endif - +#include "stm32f_hal_conf.hxx" /** Specialization of SPI driver for STM32 devices. */ diff --git a/src/freertos_drivers/st/Stm32Uart.cxx b/src/freertos_drivers/st/Stm32Uart.cxx index 341f4d4b5..10cfd784c 100644 --- a/src/freertos_drivers/st/Stm32Uart.cxx +++ b/src/freertos_drivers/st/Stm32Uart.cxx @@ -41,6 +41,8 @@ #include "stm32f1xx_hal_cortex.h" #elif defined(STM32F303xC) || defined(STM32F303xE) #include "stm32f3xx_hal_cortex.h" +#elif defined(STM32L431xx) || defined(STM32L432xx) +#include "stm32l4xx_hal_cortex.h" #elif defined(STM32F767xx) #include "stm32f7xx_hal_cortex.h" #else @@ -54,9 +56,15 @@ Stm32Uart *Stm32Uart::instances[1] = {NULL}; #elif defined (STM32F030x8) || defined (STM32F042x6) || defined (STM32F048xx) \ || defined (STM32F051x8) || defined (STM32F058xx) || defined (STM32F070x6) Stm32Uart *Stm32Uart::instances[2] = {NULL}; +#elif defined (STM32L432xx) +Stm32Uart *Stm32Uart::instances[3] = {NULL}; +#define USART3 LPUART1 #elif defined (STM32F070xB) || defined (STM32F071xB) || defined (STM32F072xB) \ || defined (STM32F078xx) Stm32Uart *Stm32Uart::instances[4] = {NULL}; +#elif defined (STM32L431xx) +Stm32Uart *Stm32Uart::instances[4] = {NULL}; +#define USART4 LPUART1 #elif defined (STM32F303xC) || defined (STM32F303xE) Stm32Uart *Stm32Uart::instances[5] = {NULL}; #define USART4 UART4 @@ -96,6 +104,7 @@ Stm32Uart::Stm32Uart(const char *name, USART_TypeDef *base, IRQn_Type interrupt) { instances[2] = this; } +#if !defined(STM32L432xx) #ifdef USART4 else if (base == USART4) #elif defined(UART4) @@ -105,7 +114,7 @@ Stm32Uart::Stm32Uart(const char *name, USART_TypeDef *base, IRQn_Type interrupt) instances[3] = this; } #if !defined (STM32F070xB) && !defined (STM32F071xB) && !defined (STM32F072xB) \ - && !defined (STM32F078xx) + && !defined (STM32F078xx) && !defined(STM32L431xx) #ifdef USART5 else if (base == USART5) #elif defined(UART5) @@ -143,6 +152,7 @@ Stm32Uart::Stm32Uart(const char *name, USART_TypeDef *base, IRQn_Type interrupt) #endif #endif #endif +#endif #endif else { diff --git a/src/freertos_drivers/st/Stm32Uart.hxx b/src/freertos_drivers/st/Stm32Uart.hxx index 781e688d9..e7998057c 100644 --- a/src/freertos_drivers/st/Stm32Uart.hxx +++ b/src/freertos_drivers/st/Stm32Uart.hxx @@ -36,21 +36,7 @@ #include -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_dma.h" -#include "stm32f0xx_hal_uart.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_dma.h" -#include "stm32f1xx_hal_uart.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_dma.h" -#include "stm32f3xx_hal_uart.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_dma.h" -#include "stm32f7xx_hal_uart.h" -#else -#error Dont know what STM32 chip you have. -#endif +#include "stm32f_hal_conf.hxx" #include "Serial.hxx" @@ -105,6 +91,12 @@ private: || defined (STM32F051x8) || defined (STM32F058xx) || defined (STM32F070x6) /** Instance pointers help us get context from the interrupt handler(s) */ static Stm32Uart *instances[2]; +#elif defined (STM32L432xx) + /** Instance pointers help us get context from the interrupt handler(s) */ + static Stm32Uart *instances[3]; +#elif defined (STM32L431xx) + /** Instance pointers help us get context from the interrupt handler(s) */ + static Stm32Uart *instances[4]; #elif defined (STM32F070xB) || defined (STM32F071xB) || defined (STM32F072xB) \ || defined (STM32F078xx) /** Instance pointers help us get context from the interrupt handler(s) */ diff --git a/src/freertos_drivers/st/stm32f_hal_conf.hxx b/src/freertos_drivers/st/stm32f_hal_conf.hxx index 880a939a5..7b669841a 100644 --- a/src/freertos_drivers/st/stm32f_hal_conf.hxx +++ b/src/freertos_drivers/st/stm32f_hal_conf.hxx @@ -47,6 +47,8 @@ #include "stm32f3xx_hal_conf.h" #elif defined(STM32F767xx) #include "stm32f7xx_hal_conf.h" +#elif defined(STM32L432xx) || defined(STM32L431xx) +#include "stm32l4xx_hal_conf.h" #else #error "STM32F_HAL_CONF unsupported STM32 device" #endif diff --git a/src/freertos_drivers/st/stm32l4xx_hal_conf.h b/src/freertos_drivers/st/stm32l4xx_hal_conf.h new file mode 100644 index 000000000..efb3559bf --- /dev/null +++ b/src/freertos_drivers/st/stm32l4xx_hal_conf.h @@ -0,0 +1,354 @@ +/** + ****************************************************************************** + * @file stm32l4xx_hal_conf.h + * @author MCD Application Team + * @brief HAL configuration file. + ****************************************************************************** + * @attention + * + *

© Copyright (c) 2017 STMicroelectronics. + * All rights reserved.

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32L4xx_HAL_CONF_H +#define __STM32L4xx_HAL_CONF_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* Exported types ------------------------------------------------------------*/ +/* Exported constants --------------------------------------------------------*/ + +/* ########################## Module Selection ############################## */ +/** + * @brief This is the list of modules to be used in the HAL driver + */ +#define HAL_MODULE_ENABLED +#define HAL_ADC_MODULE_ENABLED +#define HAL_CAN_MODULE_ENABLED +/* #define HAL_CAN_LEGACY_MODULE_ENABLED */ +#define HAL_COMP_MODULE_ENABLED +#define HAL_CORTEX_MODULE_ENABLED +#define HAL_CRC_MODULE_ENABLED +#define HAL_CRYP_MODULE_ENABLED +#define HAL_DAC_MODULE_ENABLED +#define HAL_DMA_MODULE_ENABLED +#define HAL_FIREWALL_MODULE_ENABLED +#define HAL_FLASH_MODULE_ENABLED +#define HAL_GPIO_MODULE_ENABLED +#define HAL_I2C_MODULE_ENABLED +#define HAL_IRDA_MODULE_ENABLED +#define HAL_IWDG_MODULE_ENABLED +#define HAL_LPTIM_MODULE_ENABLED +#define HAL_MMC_MODULE_ENABLED +#define HAL_OPAMP_MODULE_ENABLED +#define HAL_PCD_MODULE_ENABLED +#define HAL_PWR_MODULE_ENABLED +#define HAL_QSPI_MODULE_ENABLED +#define HAL_RCC_MODULE_ENABLED +#define HAL_RNG_MODULE_ENABLED +#define HAL_RTC_MODULE_ENABLED +#define HAL_SAI_MODULE_ENABLED +#define HAL_SMARTCARD_MODULE_ENABLED +#define HAL_SMBUS_MODULE_ENABLED +#define HAL_SPI_MODULE_ENABLED +#define HAL_SWPMI_MODULE_ENABLED +#define HAL_TIM_MODULE_ENABLED +#define HAL_TSC_MODULE_ENABLED +#define HAL_UART_MODULE_ENABLED +#define HAL_USART_MODULE_ENABLED +#define HAL_WWDG_MODULE_ENABLED + + +/* ########################## Oscillator Values adaptation ####################*/ +/** + * @brief Adjust the value of External High Speed oscillator (HSE) used in your application. + * This value is used by the RCC HAL module to compute the system frequency + * (when HSE is used as system clock source, directly or through the PLL). + */ +extern const uint32_t HSEValue; /*!< Value of the External oscillator in Hz */ +#define HSE_VALUE HSEValue + +#if !defined (HSE_STARTUP_TIMEOUT) + #define HSE_STARTUP_TIMEOUT 100U /*!< Time out for HSE start up, in ms */ +#endif /* HSE_STARTUP_TIMEOUT */ + +/** + * @brief Internal Multiple Speed oscillator (MSI) default value. + * This value is the default MSI range value after Reset. + */ +#if !defined (MSI_VALUE) + #define MSI_VALUE 4000000U /*!< Value of the Internal oscillator in Hz*/ +#endif /* MSI_VALUE */ + +/** + * @brief Internal High Speed oscillator (HSI) value. + * This value is used by the RCC HAL module to compute the system frequency + * (when HSI is used as system clock source, directly or through the PLL). + */ +#if !defined (HSI_VALUE) + #define HSI_VALUE 16000000U /*!< Value of the Internal oscillator in Hz*/ +#endif /* HSI_VALUE */ + +/** + * @brief Internal High Speed oscillator (HSI48) value for USB FS, SDMMC and RNG. + * This internal oscillator is mainly dedicated to provide a high precision clock to + * the USB peripheral by means of a special Clock Recovery System (CRS) circuitry. + * When the CRS is not used, the HSI48 RC oscillator runs on it default frequency + * which is subject to manufacturing process variations. + */ +#if !defined (HSI48_VALUE) + #define HSI48_VALUE 48000000U /*!< Value of the Internal High Speed oscillator for USB FS/SDMMC/RNG in Hz. + The real value my vary depending on manufacturing process variations.*/ +#endif /* HSI48_VALUE */ + +/** + * @brief Internal Low Speed oscillator (LSI) value. + */ +#if !defined (LSI_VALUE) + #define LSI_VALUE 32000U /*!< LSI Typical Value in Hz*/ +#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz + The real value may vary depending on the variations + in voltage and temperature.*/ +/** + * @brief External Low Speed oscillator (LSE) value. + * This value is used by the UART, RTC HAL module to compute the system frequency + */ +#if !defined (LSE_VALUE) + #define LSE_VALUE 32768U /*!< Value of the External oscillator in Hz*/ +#endif /* LSE_VALUE */ + +#if !defined (LSE_STARTUP_TIMEOUT) + #define LSE_STARTUP_TIMEOUT 5000U /*!< Time out for LSE start up, in ms */ +#endif /* HSE_STARTUP_TIMEOUT */ + +/** + * @brief External clock source for SAI1 peripheral + * This value is used by the RCC HAL module to compute the SAI1 & SAI2 clock source + * frequency. + */ +#if !defined (EXTERNAL_SAI1_CLOCK_VALUE) + #define EXTERNAL_SAI1_CLOCK_VALUE 48000U /*!< Value of the SAI1 External clock source in Hz*/ +#endif /* EXTERNAL_SAI1_CLOCK_VALUE */ + +/** + * @brief External clock source for SAI2 peripheral + * This value is used by the RCC HAL module to compute the SAI1 & SAI2 clock source + * frequency. + */ +#if !defined (EXTERNAL_SAI2_CLOCK_VALUE) + #define EXTERNAL_SAI2_CLOCK_VALUE 48000U /*!< Value of the SAI2 External clock source in Hz*/ +#endif /* EXTERNAL_SAI2_CLOCK_VALUE */ + +/* Tip: To avoid modifying this file each time you need to use different HSE, + === you can define the HSE value in your toolchain compiler preprocessor. */ + +/* ########################### System Configuration ######################### */ +/** + * @brief This is the HAL system configuration section + */ +#define VDD_VALUE 3300U /*!< Value of VDD in mv */ +#define TICK_INT_PRIORITY 0x0FU /*!< tick interrupt priority */ +#define USE_RTOS 0U +#define PREFETCH_ENABLE 0U +#define INSTRUCTION_CACHE_ENABLE 1U +#define DATA_CACHE_ENABLE 1U + +/* ########################## Assert Selection ############################## */ +/** + * @brief Uncomment the line below to expanse the "assert_param" macro in the + * HAL drivers code + */ +/* #define USE_FULL_ASSERT 1U */ + +/* ################## SPI peripheral configuration ########################## */ + +/* CRC FEATURE: Use to activate CRC feature inside HAL SPI Driver + * Activated: CRC code is present inside driver + * Deactivated: CRC code cleaned from driver + */ + +#define USE_SPI_CRC 1U + +/* Includes ------------------------------------------------------------------*/ +/** + * @brief Include module's header file + */ + +#ifdef HAL_RCC_MODULE_ENABLED + #include "stm32l4xx_hal_rcc.h" +#endif /* HAL_RCC_MODULE_ENABLED */ + +#ifdef HAL_GPIO_MODULE_ENABLED + #include "stm32l4xx_hal_gpio.h" +#endif /* HAL_GPIO_MODULE_ENABLED */ + +#ifdef HAL_DMA_MODULE_ENABLED + #include "stm32l4xx_hal_dma.h" +#endif /* HAL_DMA_MODULE_ENABLED */ + +#ifdef HAL_CORTEX_MODULE_ENABLED + #include "stm32l4xx_hal_cortex.h" +#endif /* HAL_CORTEX_MODULE_ENABLED */ + +#ifdef HAL_ADC_MODULE_ENABLED + #include "stm32l4xx_hal_adc.h" +#endif /* HAL_ADC_MODULE_ENABLED */ + +#ifdef HAL_CAN_MODULE_ENABLED + #include "stm32l4xx_hal_can.h" +#endif /* HAL_CAN_MODULE_ENABLED */ + +#ifdef HAL_CAN_LEGACY_MODULE_ENABLED + #include "Legacy/stm32l4xx_hal_can_legacy.h" +#endif /* HAL_CAN_LEGACY_MODULE_ENABLED */ + +#ifdef HAL_COMP_MODULE_ENABLED + #include "stm32l4xx_hal_comp.h" +#endif /* HAL_COMP_MODULE_ENABLED */ + +#ifdef HAL_CRC_MODULE_ENABLED + #include "stm32l4xx_hal_crc.h" +#endif /* HAL_CRC_MODULE_ENABLED */ + +#ifdef HAL_CRYP_MODULE_ENABLED + #include "stm32l4xx_hal_cryp.h" +#endif /* HAL_CRYP_MODULE_ENABLED */ + +#ifdef HAL_DAC_MODULE_ENABLED + #include "stm32l4xx_hal_dac.h" +#endif /* HAL_DAC_MODULE_ENABLED */ + +#ifdef HAL_FIREWALL_MODULE_ENABLED + #include "stm32l4xx_hal_firewall.h" +#endif /* HAL_FIREWALL_MODULE_ENABLED */ + +#ifdef HAL_FLASH_MODULE_ENABLED + #include "stm32l4xx_hal_flash.h" +#endif /* HAL_FLASH_MODULE_ENABLED */ + +#ifdef HAL_I2C_MODULE_ENABLED + #include "stm32l4xx_hal_i2c.h" +#endif /* HAL_I2C_MODULE_ENABLED */ + +#ifdef HAL_IWDG_MODULE_ENABLED + #include "stm32l4xx_hal_iwdg.h" +#endif /* HAL_IWDG_MODULE_ENABLED */ + +#ifdef HAL_LPTIM_MODULE_ENABLED +#include "stm32l4xx_hal_lptim.h" +#endif /* HAL_LPTIM_MODULE_ENABLED */ + +#ifdef HAL_MMC_MODULE_ENABLED +#include "stm32l4xx_hal_mmc.h" +#endif /* HAL_MMC_MODULE_ENABLED */ + +#ifdef HAL_OPAMP_MODULE_ENABLED +#include "stm32l4xx_hal_opamp.h" +#endif /* HAL_OPAMP_MODULE_ENABLED */ + +#ifdef HAL_PWR_MODULE_ENABLED + #include "stm32l4xx_hal_pwr.h" +#endif /* HAL_PWR_MODULE_ENABLED */ + +#ifdef HAL_QSPI_MODULE_ENABLED + #include "stm32l4xx_hal_qspi.h" +#endif /* HAL_QSPI_MODULE_ENABLED */ + +#ifdef HAL_RNG_MODULE_ENABLED + #include "stm32l4xx_hal_rng.h" +#endif /* HAL_RNG_MODULE_ENABLED */ + +#ifdef HAL_RTC_MODULE_ENABLED + #include "stm32l4xx_hal_rtc.h" +#endif /* HAL_RTC_MODULE_ENABLED */ + +#ifdef HAL_SAI_MODULE_ENABLED + #include "stm32l4xx_hal_sai.h" +#endif /* HAL_SAI_MODULE_ENABLED */ + +#ifdef HAL_SMBUS_MODULE_ENABLED + #include "stm32l4xx_hal_smbus.h" +#endif /* HAL_SMBUS_MODULE_ENABLED */ + +#ifdef HAL_SPI_MODULE_ENABLED + #include "stm32l4xx_hal_spi.h" +#endif /* HAL_SPI_MODULE_ENABLED */ + +#ifdef HAL_SWPMI_MODULE_ENABLED + #include "stm32l4xx_hal_swpmi.h" +#endif /* HAL_SWPMI_MODULE_ENABLED */ + +#ifdef HAL_TIM_MODULE_ENABLED + #include "stm32l4xx_hal_tim.h" +#endif /* HAL_TIM_MODULE_ENABLED */ + +#ifdef HAL_TSC_MODULE_ENABLED + #include "stm32l4xx_hal_tsc.h" +#endif /* HAL_TSC_MODULE_ENABLED */ + +#ifdef HAL_UART_MODULE_ENABLED + #include "stm32l4xx_hal_uart.h" +#endif /* HAL_UART_MODULE_ENABLED */ + +#ifdef HAL_USART_MODULE_ENABLED + #include "stm32l4xx_hal_usart.h" +#endif /* HAL_USART_MODULE_ENABLED */ + +#ifdef HAL_IRDA_MODULE_ENABLED + #include "stm32l4xx_hal_irda.h" +#endif /* HAL_IRDA_MODULE_ENABLED */ + +#ifdef HAL_SMARTCARD_MODULE_ENABLED + #include "stm32l4xx_hal_smartcard.h" +#endif /* HAL_SMARTCARD_MODULE_ENABLED */ + +#ifdef HAL_WWDG_MODULE_ENABLED + #include "stm32l4xx_hal_wwdg.h" +#endif /* HAL_WWDG_MODULE_ENABLED */ + +#ifdef HAL_PCD_MODULE_ENABLED + #include "stm32l4xx_hal_pcd.h" +#endif /* HAL_PCD_MODULE_ENABLED */ + +/* Exported macro ------------------------------------------------------------*/ +#ifdef USE_FULL_ASSERT +/** + * @brief The assert_param macro is used for function's parameters check. + * @param expr: If expr is false, it calls assert_failed function + * which reports the name of the source file and the source + * line number of the call that failed. + * If expr is true, it returns no value. + * @retval None + */ + #define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) +/* Exported functions ------------------------------------------------------- */ + void assert_failed(uint8_t *file, uint32_t line); +#else + #define assert_param(expr) ((void)0U) +#endif /* USE_FULL_ASSERT */ + + static inline void SetInterruptPriority(uint32_t irq, uint8_t priority) + { + NVIC_SetPriority((IRQn_Type)irq, priority >> (8U - __NVIC_PRIO_BITS)); + } + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32L4xx_HAL_CONF_H */ + + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile new file mode 100644 index 000000000..72e52af15 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile @@ -0,0 +1,2 @@ +OPENMRNPATH ?= $(realpath ../../../..) +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources new file mode 100644 index 000000000..ee86ad4fc --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources @@ -0,0 +1,96 @@ +include $(OPENMRNPATH)/etc/stm32cubel4.mk +VPATH = $(OPENMRNPATH)/src/freertos_drivers/st \ + $(STM32CUBEL4PATH)/Drivers/STM32L4xx_HAL_Driver/Src + +CFLAGS += -DSTM32L431xx +CXXFLAGS += -DSTM32L431xx + +CSRCS += \ + stm32l4xx_hal_adc.c \ + stm32l4xx_hal_adc_ex.c \ + stm32l4xx_hal.c \ + stm32l4xx_hal_can.c \ + stm32l4xx_hal_comp.c \ + stm32l4xx_hal_cortex.c \ + stm32l4xx_hal_crc.c \ + stm32l4xx_hal_crc_ex.c \ + stm32l4xx_hal_cryp.c \ + stm32l4xx_hal_cryp_ex.c \ + stm32l4xx_hal_dac.c \ + stm32l4xx_hal_dac_ex.c \ + stm32l4xx_hal_dcmi.c \ + stm32l4xx_hal_dfsdm.c \ + stm32l4xx_hal_dfsdm_ex.c \ + stm32l4xx_hal_dma2d.c \ + stm32l4xx_hal_dma.c \ + stm32l4xx_hal_dma_ex.c \ + stm32l4xx_hal_dsi.c \ + stm32l4xx_hal_exti.c \ + stm32l4xx_hal_firewall.c \ + stm32l4xx_hal_flash.c \ + stm32l4xx_hal_flash_ex.c \ + stm32l4xx_hal_flash_ramfunc.c \ + stm32l4xx_hal_gfxmmu.c \ + stm32l4xx_hal_gpio.c \ + stm32l4xx_hal_hash.c \ + stm32l4xx_hal_hash_ex.c \ + stm32l4xx_hal_hcd.c \ + stm32l4xx_hal_i2c.c \ + stm32l4xx_hal_i2c_ex.c \ + stm32l4xx_hal_irda.c \ + stm32l4xx_hal_iwdg.c \ + stm32l4xx_hal_lcd.c \ + stm32l4xx_hal_lptim.c \ + stm32l4xx_hal_ltdc.c \ + stm32l4xx_hal_ltdc_ex.c \ + stm32l4xx_hal_mmc.c \ + stm32l4xx_hal_mmc_ex.c \ + stm32l4xx_hal_msp_template.c \ + stm32l4xx_hal_nand.c \ + stm32l4xx_hal_nor.c \ + stm32l4xx_hal_opamp.c \ + stm32l4xx_hal_opamp_ex.c \ + stm32l4xx_hal_ospi.c \ + stm32l4xx_hal_pcd.c \ + stm32l4xx_hal_pcd_ex.c \ + stm32l4xx_hal_pka.c \ + stm32l4xx_hal_pssi.c \ + stm32l4xx_hal_pwr.c \ + stm32l4xx_hal_pwr_ex.c \ + stm32l4xx_hal_qspi.c \ + stm32l4xx_hal_rcc.c \ + stm32l4xx_hal_rcc_ex.c \ + stm32l4xx_hal_rng.c \ + stm32l4xx_hal_rng_ex.c \ + stm32l4xx_hal_rtc.c \ + stm32l4xx_hal_rtc_ex.c \ + stm32l4xx_hal_sai.c \ + stm32l4xx_hal_sai_ex.c \ + stm32l4xx_hal_sd.c \ + stm32l4xx_hal_sd_ex.c \ + stm32l4xx_hal_smartcard.c \ + stm32l4xx_hal_smartcard_ex.c \ + stm32l4xx_hal_smbus.c \ + stm32l4xx_hal_spi.c \ + stm32l4xx_hal_spi_ex.c \ + stm32l4xx_hal_sram.c \ + stm32l4xx_hal_swpmi.c \ + stm32l4xx_hal_tim.c \ + stm32l4xx_hal_tim_ex.c \ + stm32l4xx_hal_tsc.c \ + stm32l4xx_hal_uart.c \ + stm32l4xx_hal_uart_ex.c \ + stm32l4xx_hal_usart.c \ + stm32l4xx_hal_usart_ex.c \ + stm32l4xx_hal_wwdg.c \ + + +CXXSRCS += Stm32Can.cxx \ + Stm32Uart.cxx \ + Stm32SPI.cxx \ + Stm32I2C.cxx \ + + +# does not work yet +# Stm32EEPROMEmulation.cxx + diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile new file mode 100644 index 000000000..72e52af15 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile @@ -0,0 +1,2 @@ +OPENMRNPATH ?= $(realpath ../../../..) +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources new file mode 100644 index 000000000..768f5cf54 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources @@ -0,0 +1,96 @@ +include $(OPENMRNPATH)/etc/stm32cubel4.mk +VPATH = $(OPENMRNPATH)/src/freertos_drivers/st \ + $(STM32CUBEL4PATH)/Drivers/STM32L4xx_HAL_Driver/Src + +CFLAGS += -DSTM32L432xx +CXXFLAGS += -DSTM32L432xx + +CSRCS += \ + stm32l4xx_hal_adc.c \ + stm32l4xx_hal_adc_ex.c \ + stm32l4xx_hal.c \ + stm32l4xx_hal_can.c \ + stm32l4xx_hal_comp.c \ + stm32l4xx_hal_cortex.c \ + stm32l4xx_hal_crc.c \ + stm32l4xx_hal_crc_ex.c \ + stm32l4xx_hal_cryp.c \ + stm32l4xx_hal_cryp_ex.c \ + stm32l4xx_hal_dac.c \ + stm32l4xx_hal_dac_ex.c \ + stm32l4xx_hal_dcmi.c \ + stm32l4xx_hal_dfsdm.c \ + stm32l4xx_hal_dfsdm_ex.c \ + stm32l4xx_hal_dma2d.c \ + stm32l4xx_hal_dma.c \ + stm32l4xx_hal_dma_ex.c \ + stm32l4xx_hal_dsi.c \ + stm32l4xx_hal_exti.c \ + stm32l4xx_hal_firewall.c \ + stm32l4xx_hal_flash.c \ + stm32l4xx_hal_flash_ex.c \ + stm32l4xx_hal_flash_ramfunc.c \ + stm32l4xx_hal_gfxmmu.c \ + stm32l4xx_hal_gpio.c \ + stm32l4xx_hal_hash.c \ + stm32l4xx_hal_hash_ex.c \ + stm32l4xx_hal_hcd.c \ + stm32l4xx_hal_i2c.c \ + stm32l4xx_hal_i2c_ex.c \ + stm32l4xx_hal_irda.c \ + stm32l4xx_hal_iwdg.c \ + stm32l4xx_hal_lcd.c \ + stm32l4xx_hal_lptim.c \ + stm32l4xx_hal_ltdc.c \ + stm32l4xx_hal_ltdc_ex.c \ + stm32l4xx_hal_mmc.c \ + stm32l4xx_hal_mmc_ex.c \ + stm32l4xx_hal_msp_template.c \ + stm32l4xx_hal_nand.c \ + stm32l4xx_hal_nor.c \ + stm32l4xx_hal_opamp.c \ + stm32l4xx_hal_opamp_ex.c \ + stm32l4xx_hal_ospi.c \ + stm32l4xx_hal_pcd.c \ + stm32l4xx_hal_pcd_ex.c \ + stm32l4xx_hal_pka.c \ + stm32l4xx_hal_pssi.c \ + stm32l4xx_hal_pwr.c \ + stm32l4xx_hal_pwr_ex.c \ + stm32l4xx_hal_qspi.c \ + stm32l4xx_hal_rcc.c \ + stm32l4xx_hal_rcc_ex.c \ + stm32l4xx_hal_rng.c \ + stm32l4xx_hal_rng_ex.c \ + stm32l4xx_hal_rtc.c \ + stm32l4xx_hal_rtc_ex.c \ + stm32l4xx_hal_sai.c \ + stm32l4xx_hal_sai_ex.c \ + stm32l4xx_hal_sd.c \ + stm32l4xx_hal_sd_ex.c \ + stm32l4xx_hal_smartcard.c \ + stm32l4xx_hal_smartcard_ex.c \ + stm32l4xx_hal_smbus.c \ + stm32l4xx_hal_spi.c \ + stm32l4xx_hal_spi_ex.c \ + stm32l4xx_hal_sram.c \ + stm32l4xx_hal_swpmi.c \ + stm32l4xx_hal_tim.c \ + stm32l4xx_hal_tim_ex.c \ + stm32l4xx_hal_tsc.c \ + stm32l4xx_hal_uart.c \ + stm32l4xx_hal_uart_ex.c \ + stm32l4xx_hal_usart.c \ + stm32l4xx_hal_usart_ex.c \ + stm32l4xx_hal_wwdg.c \ + + +CXXSRCS += Stm32Can.cxx \ + Stm32Uart.cxx \ + Stm32SPI.cxx \ + Stm32I2C.cxx \ + + +# does not work yet +# Stm32EEPROMEmulation.cxx + From e1cf317eee8ddaef93dad8b3e3529a84c2e2fe2f Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Dec 2020 23:03:01 +0100 Subject: [PATCH 121/171] Adds async_blink targets for nucleo F303 and L432. (#487) --- applications/async_blink/targets/Makefile | 2 ++ .../HwInit.cxx | 1 + .../freertos.armv7m.st-stm32f303re-nucleo-dev-board/Makefile | 1 + .../NodeId.cxx | 4 ++++ .../hardware.hxx | 1 + .../memory_map.ld | 1 + .../freertos.armv7m.st-stm32f303re-nucleo-dev-board/startup.c | 1 + .../freertos.armv7m.st-stm32f303re-nucleo-dev-board/target.ld | 1 + .../targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx | 1 + .../targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile | 1 + .../targets/freertos.armv7m.st-stm32l432kc-nucleo/NodeId.cxx | 4 ++++ .../freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx | 1 + .../freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld | 1 + .../targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c | 1 + .../targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld | 1 + 15 files changed, 22 insertions(+) create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/HwInit.cxx create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/Makefile create mode 100644 applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/NodeId.cxx create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/hardware.hxx create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/memory_map.ld create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/startup.c create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/target.ld create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile create mode 100644 applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/NodeId.cxx create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c create mode 120000 applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld diff --git a/applications/async_blink/targets/Makefile b/applications/async_blink/targets/Makefile index dd450c0a7..59e13559a 100644 --- a/applications/async_blink/targets/Makefile +++ b/applications/async_blink/targets/Makefile @@ -12,6 +12,8 @@ SUBDIRS = linux.x86 \ freertos.armv6m.st-stm32f072b-discovery \ freertos.armv7m.st-stm32f103rb-olimexino \ freertos.armv7m.st-stm32f303-discovery \ + freertos.armv7m.st-stm32f303re-nucleo-dev-board \ + freertos.armv7m.st-stm32l432kc-nucleo \ freertos.armv7m.st-maple-cmsis \ freertos.armv4t.panda2 \ freertos.mips4k.pic32mx-duinomitemega \ diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/HwInit.cxx b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/HwInit.cxx new file mode 120000 index 000000000..bdf333297 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/HwInit.cxx \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/Makefile b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/Makefile new file mode 120000 index 000000000..952f1a832 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/Makefile @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/Makefile \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/NodeId.cxx b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/NodeId.cxx new file mode 100644 index 000000000..92d3e27e9 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/NodeId.cxx @@ -0,0 +1,4 @@ +#include "openlcb/If.hxx" + +extern const openlcb::NodeID NODE_ID; +const openlcb::NodeID NODE_ID = 0x050101011869ULL; diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/hardware.hxx b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/hardware.hxx new file mode 120000 index 000000000..477a1b21d --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/hardware.hxx \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/memory_map.ld b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/memory_map.ld new file mode 120000 index 000000000..7f99cc39f --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/memory_map.ld \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/startup.c b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/startup.c new file mode 120000 index 000000000..178a8f559 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/startup.c @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/startup.c \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/target.ld b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/target.ld new file mode 120000 index 000000000..bf20b7120 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/target.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/target.ld \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx new file mode 120000 index 000000000..8bf2ed18c --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/HwInit.cxx \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile new file mode 120000 index 000000000..e13c2970c --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/Makefile \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/NodeId.cxx b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/NodeId.cxx new file mode 100644 index 000000000..994ff0847 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/NodeId.cxx @@ -0,0 +1,4 @@ +#include "openlcb/If.hxx" + +extern const openlcb::NodeID NODE_ID; +const openlcb::NodeID NODE_ID = 0x050101011867ULL; diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx new file mode 120000 index 000000000..e27187a64 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/hardware.hxx \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld new file mode 120000 index 000000000..52c792882 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/memory_map.ld \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c new file mode 120000 index 000000000..cd5f598b1 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/startup.c \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld new file mode 120000 index 000000000..403497dce --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/target.ld \ No newline at end of file From 3020ebb5081abe37dbcd98cdcd03558d87f177ec Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Dec 2020 23:11:05 +0100 Subject: [PATCH 122/171] Fork freertos.armv7m.st-stm32f303re-nucleo/main.cxx to freertos.armv7m.st-stm32l432kc-nucleo/main.cxx --- .../main.cxx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/io_board/targets/{freertos.armv7m.st-stm32f303re-nucleo => freertos.armv7m.st-stm32l432kc-nucleo}/main.cxx (100%) diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/main.cxx similarity index 100% rename from applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx rename to applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/main.cxx From a79a73ccea2b2c1eadcef79b7c50aa6a6c2e1b2d Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Dec 2020 23:11:05 +0100 Subject: [PATCH 123/171] Fork freertos.armv7m.st-stm32f303re-nucleo/main.cxx to freertos.armv7m.st-stm32l432kc-nucleo/main.cxx step 2 --- .../{main.cxx => main.cxx.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/{main.cxx => main.cxx.bak} (100%) diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx b/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx.bak similarity index 100% rename from applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx rename to applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx.bak From 033dc198f0ebb09bc1bd3eedcb57526a2e4bc1c9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Dec 2020 23:11:06 +0100 Subject: [PATCH 124/171] Fork freertos.armv7m.st-stm32f303re-nucleo/main.cxx to freertos.armv7m.st-stm32l432kc-nucleo/main.cxx step 4 --- .../{main.cxx.bak => main.cxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/{main.cxx.bak => main.cxx} (100%) diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx.bak b/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx similarity index 100% rename from applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx.bak rename to applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/main.cxx From 1dbe1076263ef1e14e62a2e46bd31d9a90622312 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Dec 2020 23:11:32 +0100 Subject: [PATCH 125/171] Fork freertos.armv7m.st-stm32f303re-nucleo/config.hxx to freertos.armv7m.st-stm32l432kc-nucleo/config.hxx --- .../config.hxx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/io_board/targets/{freertos.armv7m.st-stm32f303re-nucleo => freertos.armv7m.st-stm32l432kc-nucleo}/config.hxx (100%) diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/config.hxx similarity index 100% rename from applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx rename to applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/config.hxx From 2f8b644efd620c5fcfeddbb5246892218d600013 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Dec 2020 23:11:32 +0100 Subject: [PATCH 126/171] Fork freertos.armv7m.st-stm32f303re-nucleo/config.hxx to freertos.armv7m.st-stm32l432kc-nucleo/config.hxx step 2 --- .../{config.hxx => config.hxx.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/{config.hxx => config.hxx.bak} (100%) diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx b/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx.bak similarity index 100% rename from applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx rename to applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx.bak From 335251aa732f1e5dc96e0f64706507796febe3a6 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 17 Dec 2020 23:11:33 +0100 Subject: [PATCH 127/171] Fork freertos.armv7m.st-stm32f303re-nucleo/config.hxx to freertos.armv7m.st-stm32l432kc-nucleo/config.hxx step 4 --- .../{config.hxx.bak => config.hxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/{config.hxx.bak => config.hxx} (100%) diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx.bak b/applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx similarity index 100% rename from applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx.bak rename to applications/io_board/targets/freertos.armv7m.st-stm32f303re-nucleo/config.hxx From 127c549a483a113dc3adf36230e9e6d192ae00e4 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 13:58:15 +0100 Subject: [PATCH 128/171] Adds L4 support to the eepromemulation. (#488) * Adds L4 support to the eepromemulation. Fixes bugs in the 8-byte-per-write case for STM32 flash. * Removes commented out code. --- .../st/Stm32EEPROMEmulation.cxx | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/src/freertos_drivers/st/Stm32EEPROMEmulation.cxx b/src/freertos_drivers/st/Stm32EEPROMEmulation.cxx index f08a7708c..0fc44b1bb 100644 --- a/src/freertos_drivers/st/Stm32EEPROMEmulation.cxx +++ b/src/freertos_drivers/st/Stm32EEPROMEmulation.cxx @@ -35,21 +35,7 @@ #include -#if defined (STM32F030x6) || defined (STM32F031x6) || defined (STM32F038xx) \ - || defined (STM32F030x8) || defined (STM32F030xC) || defined (STM32F042x6) \ - || defined (STM32F048xx) || defined (STM32F051x8) || defined (STM32F058xx) \ - || defined (STM32F070x6) || defined (STM32F070xB) || defined (STM32F071xB) \ - || defined (STM32F072xB) || defined (STM32F078xx) \ - || defined (STM32F091xC) || defined (STM32F098xx) -#include "stm32f0xx_hal_flash.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_flash.h" -#elif defined(STM32F767xx) -#define F7_FLASH -#include "stm32f7xx_hal_flash.h" -#else -#error "stm32EEPROMEmulation unsupported STM32 device" -#endif +#include "stm32f_hal_conf.hxx" #if defined (STM32F030x6) || defined (STM32F031x6) || defined (STM32F038xx) \ || defined (STM32F030x8) || defined (STM32F030xC) || defined (STM32F042x6) \ @@ -64,11 +50,19 @@ const size_t EEPROMEmulation::BYTES_PER_BLOCK = 2; const size_t Stm32EEPROMEmulation::PAGE_SIZE = 0x800; const size_t EEPROMEmulation::BLOCK_SIZE = 4; const size_t EEPROMEmulation::BYTES_PER_BLOCK = 2; +#elif defined(STM32L432xx) || defined(STM32L431xx) +const size_t Stm32EEPROMEmulation::PAGE_SIZE = 0x800; +const size_t EEPROMEmulation::BLOCK_SIZE = 8; +const size_t EEPROMEmulation::BYTES_PER_BLOCK = 4; +#define L4_FLASH #elif defined(STM32F767xx) // Note this assumes single-bank usage const size_t Stm32EEPROMEmulation::PAGE_SIZE = 256*1024; const size_t EEPROMEmulation::BLOCK_SIZE = 8; const size_t EEPROMEmulation::BYTES_PER_BLOCK = 4; +#define F7_FLASH +#else +#error "stm32EEPROMEmulation unsupported STM32 device" #endif /** Constructor. @@ -133,7 +127,13 @@ void Stm32EEPROMEmulation::flash_erase(unsigned sector) FLASH_EraseInitTypeDef erase_init; erase_init.TypeErase = FLASH_TYPEERASE_PAGES; +#ifdef L4_FLASH + erase_init.Banks = FLASH_BANK_1; + uint32_t start_page = erase_init.Page = + (((uint32_t)address) - FLASH_BASE) / PAGE_SIZE; +#else erase_init.PageAddress = (uint32_t)address; +#endif erase_init.NbPages = SECTOR_SIZE / PAGE_SIZE; portENTER_CRITICAL(); @@ -142,11 +142,19 @@ void Stm32EEPROMEmulation::flash_erase(unsigned sector) // there. This is to make corruption less likely in case of a power // interruption happens. if (SECTOR_SIZE > PAGE_SIZE) { +#ifdef L4_FLASH + erase_init.Page += 1; +#else erase_init.PageAddress += PAGE_SIZE; +#endif erase_init.NbPages--; HAL_FLASHEx_Erase(&erase_init, &page_error); erase_init.NbPages = 1; +#ifdef L4_FLASH + erase_init.Page = start_page; +#else erase_init.PageAddress = (uint32_t)address; +#endif } HAL_FLASHEx_Erase(&erase_init, &page_error); HAL_FLASH_Lock(); @@ -183,8 +191,8 @@ void Stm32EEPROMEmulation::flash_program( { portENTER_CRITICAL(); HAL_FLASH_Unlock(); -#ifdef F7_FLASH - HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, uint_address, *data); +#if defined(F7_FLASH)|| defined(L4_FLASH) + HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, uint_address, *(uint64_t*)data); #else HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, uint_address, *data); #endif @@ -192,7 +200,7 @@ void Stm32EEPROMEmulation::flash_program( portEXIT_CRITICAL(); count -= BLOCK_SIZE; - uint_address += sizeof(uint32_t); - ++data; + uint_address += BLOCK_SIZE; + data += (BLOCK_SIZE / sizeof(uint32_t)); } } From 4f9ff2e19a3e186a3c382d038ada7d806d927e30 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 14:01:01 +0100 Subject: [PATCH 129/171] Adds IO board target for the L432KC nucleo (#489) - Adds a couple of additional pins to the hardware.hxx - Reenables EEPROMEmu compilation for L432 and L431 - Adds EEPROMEMU driver to HwInit - Adds factory reset method to the main. === * Adds io board target for the L432. * Reenables compilation of eepromemu for L4. * Adds a bunch of input/output pins to the L432 hardware.hxx * Reenables eepromemu in the HwINit for the L432. * Enables eepromemu for L431 as well. * Implements all declared input and output pins. * Fixes identification and moves the factory reset a bit higher up. --- .../HwInit.cxx | 1 + .../Makefile | 1 + .../config.hxx | 13 ++-- .../hardware.hxx | 1 + .../main.cxx | 65 +++++++++++++++++-- .../memory_map.ld | 1 + .../startup.c | 1 + .../target.ld | 1 + boards/st-stm32l432kc-nucleo/HwInit.cxx | 3 +- boards/st-stm32l432kc-nucleo/hardware.hxx | 15 ++++- .../freertos_drivers/stm32cubel431xx/sources | 4 +- .../freertos_drivers/stm32cubel432xx/sources | 5 +- 12 files changed, 89 insertions(+), 22 deletions(-) create mode 120000 applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx create mode 120000 applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile create mode 120000 applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx create mode 120000 applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld create mode 120000 applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c create mode 120000 applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx new file mode 120000 index 000000000..8bf2ed18c --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/HwInit.cxx \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile new file mode 120000 index 000000000..e13c2970c --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/Makefile \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/config.hxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/config.hxx index 1d2ed0d59..f93bff471 100644 --- a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/config.hxx +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/config.hxx @@ -22,12 +22,13 @@ namespace openlcb /// - the generated cdi.xml will include this data /// - the Simple Node Ident Info Protocol will return this data /// - the ACDI memory space will contain this data. -extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { - 4, "OpenMRN", "Test IO Board - STM32F303RE Nucleo", - "STM32F091RC-Nucleo", "1.01"}; +extern const SimpleNodeStaticValues SNIP_STATIC_DATA = {4, "OpenMRN", // + "Test IO Board - STM32L432KC Nucleo", // + "STM32L432KC-Nucleo", // + "1.01"}; -#define NUM_OUTPUTS 1 -#define NUM_INPUTS 1 +#define NUM_OUTPUTS 5 +#define NUM_INPUTS 4 /// Declares a repeated group of a given base group and number of repeats. The /// ProducerConfig and ConsumerConfig groups represent the configuration layout @@ -38,7 +39,7 @@ using AllProducers = RepeatedGroup; /// Modify this value every time the EEPROM needs to be cleared on the node /// after an update. -static constexpr uint16_t CANONICAL_VERSION = 0x184a; +static constexpr uint16_t CANONICAL_VERSION = 0x1844; /// Defines the main segment in the configuration CDI. This is laid out at /// origin 128 to give space for the ACDI user data at the beginning. diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx new file mode 120000 index 000000000..e27187a64 --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/hardware.hxx \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/main.cxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/main.cxx index 238825088..4d8bfb6e2 100644 --- a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/main.cxx +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/main.cxx @@ -47,9 +47,9 @@ // These preprocessor symbols are used to select which physical connections // will be enabled in the main(). See @ref appl_main below. -#define SNIFF_ON_SERIAL +//#define SNIFF_ON_SERIAL //#define SNIFF_ON_USB -//#define HAVE_PHYSICAL_CAN_PORT +#define HAVE_PHYSICAL_CAN_PORT // Changes the default behavior by adding a newline after each gridconnect // packet. Makes it easier for debugging the raw device. @@ -81,13 +81,45 @@ extern const char *const openlcb::CONFIG_FILENAME = "/dev/eeprom"; // The size of the memory space to export over the above device. extern const size_t openlcb::CONFIG_FILE_SIZE = cfg.seg().size() + cfg.seg().offset(); -static_assert(openlcb::CONFIG_FILE_SIZE <= 300, "Need to adjust eeprom size"); +static_assert(openlcb::CONFIG_FILE_SIZE <= 512, "Need to adjust eeprom size"); // The SNIP user-changeable information in also stored in the above eeprom // device. In general this could come from different eeprom segments, but it is // simpler to keep them together. extern const char *const openlcb::SNIP_DYNAMIC_FILENAME = openlcb::CONFIG_FILENAME; +// Object that handles factory reset for our config setup. +class CustomFactoryReset : public DefaultConfigUpdateListener { +public: + void factory_reset(int fd) override + { + // Resets user names. + cfg.userinfo().name().write(fd, "Default user name"); + cfg.userinfo().description().write(fd, "Default user description"); + // Makes the IO pin descriptions with default value. + cfg.seg().consumers().entry<0>().description().write(fd, "LD3"); + cfg.seg().consumers().entry<1>().description().write(fd, "D3"); + cfg.seg().consumers().entry<2>().description().write(fd, "D4"); + cfg.seg().consumers().entry<3>().description().write(fd, "D5"); + cfg.seg().consumers().entry<4>().description().write(fd, "D6"); + + cfg.seg().producers().entry<0>().description().write(fd, "A0"); + cfg.seg().producers().entry<1>().description().write(fd, "A1"); + cfg.seg().producers().entry<2>().description().write(fd, "A2"); + cfg.seg().producers().entry<3>().description().write(fd, "A3"); + for (unsigned i = 0; i < cfg.seg().producers().num_repeats(); ++i) { + cfg.seg().producers().entry(i).debounce().write(fd, 3); + } + } + + UpdateAction apply_configuration( + int fd, bool initial_load, BarrierNotifiable *done) override { + done->notify(); + // Nothing to do; we don't read the configuration. + return UPDATED; + } +} g_custom_factory_reset; + // Instantiates the actual producer and consumer objects for the given GPIO // pins from above. The ConfiguredConsumer class takes care of most of the // complicated setup and operation requirements. We need to give it the virtual @@ -99,16 +131,35 @@ extern const char *const openlcb::SNIP_DYNAMIC_FILENAME = // own GPIO pin. openlcb::ConfiguredConsumer consumer_green( stack.node(), cfg.seg().consumers().entry<0>(), LED_GREEN_Pin()); +openlcb::ConfiguredConsumer consumer_d3( + stack.node(), cfg.seg().consumers().entry<1>(), OUT_D3_Pin()); +openlcb::ConfiguredConsumer consumer_d4( + stack.node(), cfg.seg().consumers().entry<2>(), OUT_D4_Pin()); +openlcb::ConfiguredConsumer consumer_d5( + stack.node(), cfg.seg().consumers().entry<3>(), OUT_D5_Pin()); +openlcb::ConfiguredConsumer consumer_d6( + stack.node(), cfg.seg().consumers().entry<4>(), OUT_D6_Pin()); // Similar syntax for the producers. -openlcb::ConfiguredProducer producer_sw1( - stack.node(), cfg.seg().producers().entry<0>(), SW_USER_Pin()); +openlcb::ConfiguredProducer producer_a0( + stack.node(), cfg.seg().producers().entry<0>(), IN_A0_Pin()); +openlcb::ConfiguredProducer producer_a1( + stack.node(), cfg.seg().producers().entry<1>(), IN_A1_Pin()); +openlcb::ConfiguredProducer producer_a2( + stack.node(), cfg.seg().producers().entry<2>(), IN_A2_Pin()); +openlcb::ConfiguredProducer producer_a3( + stack.node(), cfg.seg().producers().entry<3>(), IN_A3_Pin()); // The producers need to be polled repeatedly for changes and to execute the // debouncing algorithm. This class instantiates a refreshloop and adds the two // producers to it. -openlcb::RefreshLoop loop( - stack.node(), {producer_sw1.polling()}); +openlcb::RefreshLoop loop(stack.node(), + { + producer_a0.polling(), // + producer_a1.polling(), // + producer_a2.polling(), // + producer_a3.polling() // + }); /** Entry point to application. * @param argc number of command line arguments diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld new file mode 120000 index 000000000..52c792882 --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/memory_map.ld \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c new file mode 120000 index 000000000..cd5f598b1 --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/startup.c \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld new file mode 120000 index 000000000..403497dce --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/target.ld \ No newline at end of file diff --git a/boards/st-stm32l432kc-nucleo/HwInit.cxx b/boards/st-stm32l432kc-nucleo/HwInit.cxx index 675c33298..e62215757 100644 --- a/boards/st-stm32l432kc-nucleo/HwInit.cxx +++ b/boards/st-stm32l432kc-nucleo/HwInit.cxx @@ -61,9 +61,10 @@ static Stm32Uart uart0("/dev/ser0", USART2, USART2_IRQn); static Stm32Can can0("/dev/can0"); /** EEPROM emulation driver. The file size might be made bigger. */ -//static Stm32EEPROMEmulation eeprom0("/dev/eeprom", 512); +static Stm32EEPROMEmulation eeprom0("/dev/eeprom", 512); const size_t EEPROMEmulation::SECTOR_SIZE = 2048; +const bool EEPROMEmulation::SHADOW_IN_RAM = true; extern "C" { diff --git a/boards/st-stm32l432kc-nucleo/hardware.hxx b/boards/st-stm32l432kc-nucleo/hardware.hxx index 3b34753f9..0598aa2ba 100644 --- a/boards/st-stm32l432kc-nucleo/hardware.hxx +++ b/boards/st-stm32l432kc-nucleo/hardware.hxx @@ -5,7 +5,20 @@ GPIO_PIN(LED_GREEN_RAW, LedPin, B, 3); -typedef GpioInitializer GpioInit; +GPIO_PIN(IN_A0, GpioInputPU, A, 0); +GPIO_PIN(IN_A1, GpioInputPU, A, 1); +GPIO_PIN(IN_A2, GpioInputPU, A, 3); +GPIO_PIN(IN_A3, GpioInputPU, A, 4); + +GPIO_PIN(OUT_D3, GpioOutputSafeLow, B, 0); +GPIO_PIN(OUT_D4, GpioOutputSafeLow, B, 7); +GPIO_PIN(OUT_D5, GpioOutputSafeLow, B, 6); +GPIO_PIN(OUT_D6, GpioOutputSafeLow, B, 1); + +typedef GpioInitializer + GpioInit; typedef LED_GREEN_RAW_Pin BLINKER_RAW_Pin; typedef BLINKER_Pin LED_GREEN_Pin; diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources index ee86ad4fc..ddd6657d8 100644 --- a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources @@ -89,8 +89,6 @@ CXXSRCS += Stm32Can.cxx \ Stm32Uart.cxx \ Stm32SPI.cxx \ Stm32I2C.cxx \ + Stm32EEPROMEmulation.cxx \ -# does not work yet -# Stm32EEPROMEmulation.cxx - diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources index 768f5cf54..2209fe22a 100644 --- a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources @@ -89,8 +89,5 @@ CXXSRCS += Stm32Can.cxx \ Stm32Uart.cxx \ Stm32SPI.cxx \ Stm32I2C.cxx \ - - -# does not work yet -# Stm32EEPROMEmulation.cxx + Stm32EEPROMEmulation.cxx \ From a8b8dbe31eaa7aeea70ecd2ab4154d1b26424676 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:09:42 +0100 Subject: [PATCH 130/171] Adds L431 and L432 to bare.armv7m subdirectories. --- src/freertos_drivers/sources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/freertos_drivers/sources b/src/freertos_drivers/sources index 838c81928..34027857b 100644 --- a/src/freertos_drivers/sources +++ b/src/freertos_drivers/sources @@ -64,7 +64,7 @@ CXXSRCS += c++_operators.cxx endif ifeq ($(TARGET),bare.armv7m) -SUBDIRS += cc32xxsdk ti_grlib +SUBDIRS += cc32xxsdk ti_grlib stm32cubel431xx stm32cubel432xx endif ifeq ($(TARGET),bare.armv6m) From 77b9f8b74da2a7f038263e80ebe2a1c6a5ae94ca Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:09:45 +0100 Subject: [PATCH 131/171] Fork freertos.armv7m/freertos_drivers/stm32cubel431xx/sources to bare.armv7m/freertos_drivers/stm32cubel431xx/sources --- .../freertos_drivers/stm32cubel431xx/sources | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/{freertos.armv7m => bare.armv7m}/freertos_drivers/stm32cubel431xx/sources (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources b/targets/bare.armv7m/freertos_drivers/stm32cubel431xx/sources similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources rename to targets/bare.armv7m/freertos_drivers/stm32cubel431xx/sources From dd554dee927d5b33b227a120d3a08f3b4f0897da Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:09:46 +0100 Subject: [PATCH 132/171] Fork freertos.armv7m/freertos_drivers/stm32cubel431xx/sources to bare.armv7m/freertos_drivers/stm32cubel431xx/sources step 2 --- .../freertos_drivers/stm32cubel431xx/{sources => sources.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/{sources => sources.bak} (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources.bak similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources rename to targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources.bak From 68a5fad43d4ada80ffb6179ce804881dd5fe5ba9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:09:47 +0100 Subject: [PATCH 133/171] Fork freertos.armv7m/freertos_drivers/stm32cubel431xx/sources to bare.armv7m/freertos_drivers/stm32cubel431xx/sources step 4 --- .../freertos_drivers/stm32cubel431xx/{sources.bak => sources} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/{sources.bak => sources} (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources.bak b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources.bak rename to targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources From e3a02de29d30b1e18ac56f0befda771165c3b8b0 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:09:54 +0100 Subject: [PATCH 134/171] Fork freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile to bare.armv7m/freertos_drivers/stm32cubel431xx/Makefile --- .../freertos_drivers/stm32cubel431xx/Makefile | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/{freertos.armv7m => bare.armv7m}/freertos_drivers/stm32cubel431xx/Makefile (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile b/targets/bare.armv7m/freertos_drivers/stm32cubel431xx/Makefile similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile rename to targets/bare.armv7m/freertos_drivers/stm32cubel431xx/Makefile From 778b0b9c45e0eca8e3e3899f7a3b4eea66eabc73 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:09:54 +0100 Subject: [PATCH 135/171] Fork freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile to bare.armv7m/freertos_drivers/stm32cubel431xx/Makefile step 2 --- .../freertos_drivers/stm32cubel431xx/{Makefile => Makefile.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/{Makefile => Makefile.bak} (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile.bak similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile rename to targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile.bak From 175e6aa82521112a99afd0a278166ac5a8b6b9f4 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:09:56 +0100 Subject: [PATCH 136/171] Fork freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile to bare.armv7m/freertos_drivers/stm32cubel431xx/Makefile step 4 --- .../freertos_drivers/stm32cubel431xx/{Makefile.bak => Makefile} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/{Makefile.bak => Makefile} (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile.bak b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile.bak rename to targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile From cc39fc507e18daf7a893e6b050cea282ef528544 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:10:00 +0100 Subject: [PATCH 137/171] Fork freertos.armv7m/freertos_drivers/stm32cubel432xx/sources to bare.armv7m/freertos_drivers/stm32cubel432xx/sources --- .../freertos_drivers/stm32cubel432xx/sources | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/{freertos.armv7m => bare.armv7m}/freertos_drivers/stm32cubel432xx/sources (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources b/targets/bare.armv7m/freertos_drivers/stm32cubel432xx/sources similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources rename to targets/bare.armv7m/freertos_drivers/stm32cubel432xx/sources From 4ccd1b6dcc82475bba9608e9a087673e6a608425 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:10:00 +0100 Subject: [PATCH 138/171] Fork freertos.armv7m/freertos_drivers/stm32cubel432xx/sources to bare.armv7m/freertos_drivers/stm32cubel432xx/sources step 2 --- .../freertos_drivers/stm32cubel432xx/{sources => sources.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/{sources => sources.bak} (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources.bak similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources rename to targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources.bak From 879528a03186a2d1360c4b7602e3663599d3625b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:10:02 +0100 Subject: [PATCH 139/171] Fork freertos.armv7m/freertos_drivers/stm32cubel432xx/sources to bare.armv7m/freertos_drivers/stm32cubel432xx/sources step 4 --- .../freertos_drivers/stm32cubel432xx/{sources.bak => sources} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/{sources.bak => sources} (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources.bak b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources.bak rename to targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources From 36382f575b187731929e2889ea8f31962949b5e4 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:10:06 +0100 Subject: [PATCH 140/171] Fork freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile to bare.armv7m/freertos_drivers/stm32cubel432xx/Makefile --- .../freertos_drivers/stm32cubel432xx/Makefile | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/{freertos.armv7m => bare.armv7m}/freertos_drivers/stm32cubel432xx/Makefile (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile b/targets/bare.armv7m/freertos_drivers/stm32cubel432xx/Makefile similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile rename to targets/bare.armv7m/freertos_drivers/stm32cubel432xx/Makefile From 11e10bf5dc2fbc547a237e7af85dd72e16a1318a Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:10:06 +0100 Subject: [PATCH 141/171] Fork freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile to bare.armv7m/freertos_drivers/stm32cubel432xx/Makefile step 2 --- .../freertos_drivers/stm32cubel432xx/{Makefile => Makefile.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/{Makefile => Makefile.bak} (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile.bak similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile rename to targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile.bak From f07e0939a7178dfac9c1b58f9b20daa2038fa63d Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Dec 2020 15:10:08 +0100 Subject: [PATCH 142/171] Fork freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile to bare.armv7m/freertos_drivers/stm32cubel432xx/Makefile step 4 --- .../freertos_drivers/stm32cubel432xx/{Makefile.bak => Makefile} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/{Makefile.bak => Makefile} (100%) diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile.bak b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile similarity index 100% rename from targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile.bak rename to targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile From 9a29192e67dc7ad316609e21342791688786cdbd Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 23 Dec 2020 17:03:10 +0100 Subject: [PATCH 143/171] Adds support for compiling ASM files in an application library. (#491) --- etc/applib.mk | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/etc/applib.mk b/etc/applib.mk index 386dfb071..fc066e35d 100644 --- a/etc/applib.mk +++ b/etc/applib.mk @@ -20,9 +20,11 @@ exist := $(wildcard sources) ifneq ($(strip $(exist)),) include sources else +FULLPATHASMSRCS := $(wildcard $(VPATH)/*.S) FULLPATHCSRCS = $(wildcard $(VPATH)/*.c) FULLPATHCXXSRCS = $(wildcard $(VPATH)/*.cxx) FULLPATHCPPSRCS = $(wildcard $(VPATH)/*.cpp) +ASMSRCS = $(notdir $(FULLPATHASMSRCS)) $(wildcard *.S) CSRCS = $(notdir $(FULLPATHCSRCS)) CXXSRCS = $(notdir $(FULLPATHCXXSRCS)) CPPSRCS = $(notdir $(FULLPATHCPPSRCS)) @@ -33,7 +35,7 @@ ifdef APP_PATH INCLUDES += -I$(APP_PATH) endif -OBJS = $(CXXSRCS:.cxx=.o) $(CPPSRCS:.cpp=.o) $(CSRCS:.c=.o) +OBJS = $(CXXSRCS:.cxx=.o) $(CPPSRCS:.cpp=.o) $(CSRCS:.c=.o) $(ASMSRCS:.S=.o) LIBNAME = lib$(BASENAME).a ifdef BOARD @@ -56,7 +58,7 @@ all: $(LIBNAME) -include $(OBJS:.o=.d) .SUFFIXES: -.SUFFIXES: .o .c .cxx .cpp +.SUFFIXES: .o .c .cxx .cpp .S .cpp.o: $(CXX) $(CXXFLAGS) $< -o $@ @@ -70,6 +72,9 @@ all: $(LIBNAME) $(CC) $(CFLAGS) $< -o $@ $(CC) -MM $(CFLAGS) $< > $*.d +.S.o: + $(AS) $(ASFLAGS) -MD -MF $*.d $(abspath $<) -o $@ + $(LIBNAME): $(OBJS) $(AR) cr $(LIBNAME) $(OBJS) mkdir -p ../lib From 1f8f205e549a3230008f872c985d06bc6d2da9f3 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 23 Dec 2020 17:03:24 +0100 Subject: [PATCH 144/171] Fixes atomic bug in ConfigUpdateFlow. (#490) --- src/openlcb/ConfigUpdateFlow.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openlcb/ConfigUpdateFlow.hxx b/src/openlcb/ConfigUpdateFlow.hxx index b4821e170..f298c1e60 100644 --- a/src/openlcb/ConfigUpdateFlow.hxx +++ b/src/openlcb/ConfigUpdateFlow.hxx @@ -112,8 +112,8 @@ private: return call_immediately(STATE(do_initial_load)); } l = nextRefresh_.operator->(); + ++nextRefresh_; } - ++nextRefresh_; return call_listener(l, false); } From 96d5ab883fac9a5919f1869b994d4b63a0b3eb26 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 23 Dec 2020 17:04:05 +0100 Subject: [PATCH 145/171] Data/bss section initialization fix (#492) Makes data/bss initialization support sections with length not a multiple of 4-bytes. Previously such a binary caused an infinite loop or hard fault upon boot. --- boards/armv7m/default_handlers.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/boards/armv7m/default_handlers.h b/boards/armv7m/default_handlers.h index 6e867c658..db67288c2 100644 --- a/boards/armv7m/default_handlers.h +++ b/boards/armv7m/default_handlers.h @@ -90,7 +90,7 @@ void reset_handler(void) unsigned long *dst = (unsigned long *)*section_table_addr++; unsigned long len = (unsigned long)*section_table_addr++; - for (; len; len -= 4) + for (; len > 0; len -= 4) { *dst++ = *src++; } @@ -102,7 +102,7 @@ void reset_handler(void) unsigned long *zero = (unsigned long *)*section_table_addr++; unsigned long len = (unsigned long)*section_table_addr++; - for (; len; len -= 4) + for (; len > 0; len -= 4) { *zero++ = 0; } From b9c925373c84074a0e4f4bd443a64d1a5e814675 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 23 Dec 2020 18:21:47 +0100 Subject: [PATCH 146/171] Adds error codes defined by the firmware upgrade standard. (#493) --- src/openlcb/Defs.hxx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/openlcb/Defs.hxx b/src/openlcb/Defs.hxx index 68f3e734e..855e6bb31 100644 --- a/src/openlcb/Defs.hxx +++ b/src/openlcb/Defs.hxx @@ -160,7 +160,8 @@ struct Defs }; - enum ErrorCodes { + enum ErrorCodes + { ERROR_CODE_OK = 0, ERROR_PERMANENT = 0x1000, @@ -182,6 +183,15 @@ struct Defs ERROR_OPENMRN_ALREADY_EXISTS = ERROR_OPENMRN_NOT_FOUND | 2, + // Codes defined by the firmware upgrade standard + + /// The firmware data is incompatible with this hardware node. + ERROR_FIRMWARE_INCOMPATIBLE = ERROR_INVALID_ARGS | 8, + /// The firmware data is invalid or corrupted. + ERROR_FIRMWARE_CORRUPTED = ERROR_INVALID_ARGS | 9, + /// The firmware written has failed checksum (temporary error). + ERROR_FIRMWARE_CSUM = 0x2088, + // Internal error codes generated by the stack. ERROR_DST_NOT_FOUND = 0x40000, //< on CAN. Permanent error code. // There is a conflict with MinGW macros here. From 03b94d61222c48be8d07c2a5c0cf2a179fcd1a66 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 23 Dec 2020 18:23:24 +0100 Subject: [PATCH 147/171] Switches bare.armv7m to optimize for size. (#494) * Switches bare.armv7m to optimize for size. This was already the case for freertos.armv7m, freertos.armv6m and bare.armv6m targets. --- etc/bare.armv7m.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/bare.armv7m.mk b/etc/bare.armv7m.mk index 5f799a5b5..566329f44 100644 --- a/etc/bare.armv7m.mk +++ b/etc/bare.armv7m.mk @@ -28,8 +28,8 @@ INCLUDES += -I$(FREERTOSPATH)/Source/include \ -I$(OPENMRNPATH)/src/freertos_drivers/common #ARCHOPTIMIZATION = -D__NEWLIB__ -ARCHOPTIMIZATION += -O3 -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer -fdata-sections -ffunction-sections -#ARCHOPTIMIZATION += -Os -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer -fdata-sections -ffunction-sections +#ARCHOPTIMIZATION += -O3 -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer -fdata-sections -ffunction-sections +ARCHOPTIMIZATION += -Os -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer -fdata-sections -ffunction-sections ARCHFLAGS = -g -MD -MP -march=armv7-m -mthumb -mfloat-abi=soft From 1b328517e112c1e18bb1ab779476e959fd8f9f25 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 31 Dec 2020 12:51:17 +0100 Subject: [PATCH 148/171] Adds a version of the Queue where the scheduling algorithm can be tuned by weights. (#497) * Adds a version of the Queue where the scheduling algorithm can be tuned by weights. * Switches to using mutexes instead of atomic for locking. The stride algorithm could run a bit too long for an atomic holder. * Adds comments about the API. --- src/utils/ScheduledQueue.cxxtest | 382 +++++++++++++++++++++++++++++++ src/utils/ScheduledQueue.hxx | 223 ++++++++++++++++++ 2 files changed, 605 insertions(+) create mode 100644 src/utils/ScheduledQueue.cxxtest create mode 100644 src/utils/ScheduledQueue.hxx diff --git a/src/utils/ScheduledQueue.cxxtest b/src/utils/ScheduledQueue.cxxtest new file mode 100644 index 000000000..e4dfb358b --- /dev/null +++ b/src/utils/ScheduledQueue.cxxtest @@ -0,0 +1,382 @@ +#include "utils/ScheduledQueue.hxx" + +#include "utils/test_main.hxx" + +class ScheduledQueueTest : public ::testing::Test +{ +protected: + ScheduledQueueTest() + { + entries_.resize(100); + for (unsigned i = 0; i < entries_.size(); ++i) + { + entries_[i].idx = i; + } + } + + ~ScheduledQueueTest() + { + if (!q_) + return; + while (!q_->empty()) + { + take(); + } + for (unsigned i = 0; i < nextEntry_; ++i) + { + EXPECT_TRUE(entries_[i].queued); + EXPECT_TRUE(entries_[i].done); + } + } + + struct Entry : QMember + { + /// Which entry this is in the vector. + unsigned idx; + /// True if this entry was added to the queue. + bool queued {false}; + /// True if this entry was returned from the queue. + bool done {false}; + }; + + /// Adds a new entry to the queue. + /// @param prio which priority band ot add to + /// @return the index of the new member. + unsigned add_empty(unsigned prio) + { + HASSERT(nextEntry_ < entries_.size()); + Entry *e = &entries_[nextEntry_++]; + e->queued = true; + q_->insert(e, prio); + return e->idx; + } + + /// Takes the next entry from the queue. Fills in lastIdx_ and lastPrio_. + void take() + { + auto ret = q_->next(); + if (!ret.item) + { + lastIdx_ = EMPTY; + lastPrio_ = EMPTY; + } + else + { + lastPrio_ = ret.index; + if (frequency_.size() <= lastPrio_) + { + frequency_.resize(lastPrio_ + 1); + } + frequency_[lastPrio_]++; + auto *e = (Entry *)ret.item; + lastIdx_ = e->idx; + ASSERT_TRUE(e->queued); + ASSERT_FALSE(e->done); + e->done = true; + } + } + + /// Takes an entry from the queue and returns its index. + unsigned take_idx() + { + take(); + return lastIdx_; + } + + /// Takes an entry from the queue and returns its priority. + unsigned take_prio() + { + take(); + return lastPrio_; + } + + /// Runs a statistical test with always full priority bands and returns the + /// percentage bandwidth allocated. + /// @param count how many iterations. + std::vector run_stat_test(unsigned count) + { + // Fills each priority band with 10 entries. + for (unsigned p = 0; p < q_->num_prio(); ++p) + { + for (unsigned i = 0; i < 2; i++) + { + add_empty(p); + } + } + + EXPECT_EQ(2 * q_->num_prio(), q_->pending()); + + for (unsigned i = 0; i < count; i++) + { + take(); + // re-add the same entry. + entries_[lastIdx_].done = false; + q_->insert(&entries_[lastIdx_], lastPrio_); + } + + std::vector v(frequency_.begin(), frequency_.end()); + for (unsigned p = 0; p < q_->num_prio(); ++p) + { + v[p] /= count; + } + return v; + } + + /// Pre-allocated entries. + vector entries_; + /// Index where to take next entry from. + unsigned nextEntry_ {0}; + /// The queue object under test. + std::unique_ptr q_; + /// Frequency of removals. index: priority. value: count. + vector frequency_; + + /// Index of the last returned entry. + unsigned lastIdx_ {0}; + /// Priority of the last returned entry. + unsigned lastPrio_ {0}; + + static constexpr unsigned EMPTY = 0xffffu; +}; + +TEST_F(ScheduledQueueTest, create) +{ +} + +TEST_F(ScheduledQueueTest, empty) +{ + constexpr Fixed16 ps[] = {{1, 0}}; + q_.reset(new ScheduledQueue(1, ps)); + + EXPECT_TRUE(q_->empty()); +} + +TEST_F(ScheduledQueueTest, fifo) +{ + constexpr Fixed16 ps[] = {{1, 0}}; + q_.reset(new ScheduledQueue(1, ps)); + add_empty(0); + add_empty(0); + add_empty(0); + EXPECT_EQ(0u, take_idx()); + EXPECT_EQ(1u, take_idx()); + add_empty(0); + EXPECT_EQ(2u, take_idx()); + add_empty(0); + add_empty(0); + EXPECT_EQ(3u, take_idx()); + EXPECT_EQ(4u, take_idx()); + + EXPECT_FALSE(q_->empty()); +} + +TEST_F(ScheduledQueueTest, strict_prio) +{ + constexpr Fixed16 ps[] = {{1, 0}, {1, 0}}; + q_.reset(new ScheduledQueue(2, ps)); + add_empty(0); + add_empty(1); + add_empty(0); + add_empty(1); + add_empty(0); + add_empty(1); + // We get back first the entries from the zero priority. + EXPECT_EQ(0u, take_idx()); + EXPECT_EQ(0u, lastPrio_); + EXPECT_EQ(2u, take_idx()); + EXPECT_EQ(0u, lastPrio_); + EXPECT_EQ(4u, take_idx()); + EXPECT_EQ(0u, lastPrio_); + + // Then the entries from the zero priority. + EXPECT_EQ(1u, take_idx()); + EXPECT_EQ(1u, lastPrio_); + EXPECT_EQ(3u, take_idx()); + EXPECT_EQ(1u, lastPrio_); +} + +TEST_F(ScheduledQueueTest, prio_pending_empty) +{ + constexpr Fixed16 ps[] = {{1, 0}, {1, 0}, {1, 0}, {1, 0}}; + q_.reset(new ScheduledQueue(4, ps)); + add_empty(0); + add_empty(1); + add_empty(2); + add_empty(2); + + // Checks empty and num pending by band + EXPECT_FALSE(q_->empty()); + EXPECT_EQ(4u, q_->pending()); + EXPECT_EQ(1u, q_->pending(0)); + EXPECT_EQ(1u, q_->pending(1)); + EXPECT_EQ(2u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + + take(); + + EXPECT_EQ(3u, q_->pending()); + EXPECT_EQ(0u, q_->pending(0)); + EXPECT_EQ(1u, q_->pending(1)); + EXPECT_EQ(2u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + EXPECT_FALSE(q_->empty()); + + take(); + + EXPECT_EQ(2u, q_->pending()); + EXPECT_EQ(0u, q_->pending(0)); + EXPECT_EQ(0u, q_->pending(1)); + EXPECT_EQ(2u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + EXPECT_FALSE(q_->empty()); + + take(); + + EXPECT_EQ(1u, q_->pending()); + EXPECT_EQ(0u, q_->pending(0)); + EXPECT_EQ(0u, q_->pending(1)); + EXPECT_EQ(1u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + EXPECT_FALSE(q_->empty()); + + take(); + + EXPECT_EQ(0u, q_->pending()); + EXPECT_EQ(0u, q_->pending(0)); + EXPECT_EQ(0u, q_->pending(1)); + EXPECT_EQ(0u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + EXPECT_TRUE(q_->empty()); +} + +TEST_F(ScheduledQueueTest, schedule_full) +{ + constexpr Fixed16 ps[] = {{Fixed16::FROM_DOUBLE, 0.5}, + {Fixed16::FROM_DOUBLE, 0.5}, {Fixed16::FROM_DOUBLE, 0.5}, + {Fixed16::FROM_DOUBLE, 0.5}}; + q_.reset(new ScheduledQueue(4, ps)); + // Fills each priority band with 10 entries. + for (unsigned p = 0; p < q_->num_prio(); ++p) + { + for (unsigned i = 0; i < 10; i++) + { + add_empty(p); + } + } + + EXPECT_EQ(40u, q_->pending()); + + // Every second comes from p0 + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(1u, take_prio()); + EXPECT_EQ(0u, take_prio()); + // every fourth from p2 + EXPECT_EQ(2u, take_prio()); + + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(1u, take_prio()); + EXPECT_EQ(0u, take_prio()); + + // every eight from p3 + EXPECT_EQ(3u, take_prio()); + + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(1u, take_prio()); + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(2u, take_prio()); + + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(1u, take_prio()); + EXPECT_EQ(0u, take_prio()); + + // There is no p4, so p3 will repeat here. + EXPECT_EQ(3u, take_prio()); +} + +TEST_F(ScheduledQueueTest, statistical) +{ + constexpr Fixed16 ps[] = {{Fixed16::FROM_DOUBLE, 0.2}, + {Fixed16::FROM_DOUBLE, 0.2}, {Fixed16::FROM_DOUBLE, 0.5}, + {Fixed16::FROM_DOUBLE, 0.5}}; + q_.reset(new ScheduledQueue(4, ps)); + + std::vector bw_frac = run_stat_test(10000); + + // 20% of bandwidth to p0 + EXPECT_NEAR(0.2, bw_frac[0], 0.01); + // 80% * 20% = 16% of bandwidth to p1 + EXPECT_NEAR(0.16, bw_frac[1], 0.01); + // 80% * 80% * 50% = 32% of bandwidth to p2 + EXPECT_NEAR(0.32, bw_frac[2], 0.01); + // same to p3 + EXPECT_NEAR(0.32, bw_frac[3], 0.01); +} + +TEST_F(ScheduledQueueTest, statistical_skewed) +{ + constexpr Fixed16 ps[] = {{Fixed16::FROM_DOUBLE, 0.8}, + {Fixed16::FROM_DOUBLE, 0.8}, {Fixed16::FROM_DOUBLE, 0.8}, + {Fixed16::FROM_DOUBLE, 0.5}}; + q_.reset(new ScheduledQueue(4, ps)); + + std::vector bw_frac = run_stat_test(10000); + + // 80% of bandwidth to p0 + EXPECT_NEAR(0.8, bw_frac[0], 0.01); + // 20% * 80% = 16% of bandwidth to p1 + EXPECT_NEAR(0.16, bw_frac[1], 0.01); + // 20% * 20% * 80% = 3.2% of bandwidth to p2 + EXPECT_NEAR(0.032, bw_frac[2], 0.001); + // 20% * 20% * 20% = 0.8% of bandwidth to p2 + EXPECT_NEAR(0.008, bw_frac[3], 0.001); +} + +TEST_F(ScheduledQueueTest, schedule_with_empties) +{ + constexpr Fixed16 ps[] = {{Fixed16::FROM_DOUBLE, 0.4}, + {Fixed16::FROM_DOUBLE, 0.22}, {Fixed16::FROM_DOUBLE, 0.1}, + {Fixed16::FROM_DOUBLE, 1}}; + q_.reset(new ScheduledQueue(4, ps)); + + // We leave p0 empty for now + add_empty(1); + add_empty(1); + add_empty(1); + add_empty(2); + add_empty(2); + + // First nonempty is found on p1. + EXPECT_EQ(1u, take_prio()); + // The next will be sent down, finds the first nonempty on p2. + EXPECT_EQ(2u, take_prio()); + // The next token will skip p2, but find nothing more so traces back to + // take p2 again. + EXPECT_EQ(2u, take_prio()); + // Now p1 is still not scheduled to arrive but that's the only nonempty. + EXPECT_EQ(1u, take_prio()); + + // Now stocking up lower priorities will cause p1 to skip quite a few. + add_empty(2); + add_empty(2); + add_empty(2); + add_empty(2); + add_empty(2); + add_empty(2); + add_empty(2); + + // p1 is not scheduled to send + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + // now p1 exceeded the token threshold + EXPECT_EQ(1u, take_prio()); + + // remaining entries + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + + // Empty + EXPECT_EQ((unsigned)EMPTY, take_prio()); +} diff --git a/src/utils/ScheduledQueue.hxx b/src/utils/ScheduledQueue.hxx new file mode 100644 index 000000000..723d4b37a --- /dev/null +++ b/src/utils/ScheduledQueue.hxx @@ -0,0 +1,223 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file ScheduledQueue.hxx + * + * A Queue implementation that has a priority based scheduler on the different + * bands. + * + * @author Balazs Racz + * @date 30 Dec 2020 + */ + +#ifndef _UTILS_SCHEDULEDQUEUE_HXX_ +#define _UTILS_SCHEDULEDQUEUE_HXX_ + +#include "os/OS.hxx" +#include "utils/Fixed16.hxx" +#include "utils/Queue.hxx" +#include "utils/logging.h" + +/// ScheduledQueue is a queue with multiple priorities, where each priority is +/// a FIFO list. The different priorities are polled according to a weighted +/// stride scheduler instead of in strict numerical priority order. +class ScheduledQueue +{ +public: + /// Constructor. + /// @param num_bands now many priority bands should there be. + /// @param strides is an array of the size num_bands. It contains the + /// stride coefficients (they should all be numbers <= 1. A value of 1 + /// degrades to strict priority order). This array is not used anymore + /// after the constructor returns. A stride of 0.2 means that 20% of all + /// tokens at this level will be allocated to this band, and 80% of the + /// tokens are passed down to lower priorities (bands with higher index). + ScheduledQueue(unsigned num_bands, const Fixed16 *strides) + : numBands_(num_bands) + , bands_(new Band[num_bands]) + { + for (unsigned i = 0; i < numBands_; i++) + { + bands_[i].stride_ = strides[i]; + bands_[i].currentToken_ -= strides[i]; + } + } + + /// Destructor. + ~ScheduledQueue() + { + delete[] bands_; + } + + /// Get an item from the queue. The returned item will be according to the + /// priority scheduler. + /// @return the member and the priority from which it came. + Result next() + { + OSMutexLock h(lock()); + return next_locked(); + } + + /// Get an item from the queue. The returned item will be according to the + /// priority scheduler. The caller must acquire the lock() first. + /// @return the member and the priority from which it came. + Result next_locked() + { + if (!numPending_) + { + // Empty queue. + return Result(0, 0); + } + // Execute the priority based scheduling algorithm. + for (unsigned i = 0; i < numBands_; ++i) + { + bands_[i].currentToken_ += bands_[i].stride_; + if (bands_[i].currentToken_.trunc() >= 1) + { + Result ret = bands_[i].queue_.next_locked(); + if (ret.item) + { + ret.index = i; + --numPending_; + bands_[i].currentToken_ -= 1; + return ret; + } + else + { + // This queue has a token but is empty. We remove + // fractional tokens and keep searching onwards in the + // priorities. + bands_[i].currentToken_ = 1; + } + } + } + // Fallen off at the end. We go backwards to find any queue with + // nonempty members. + for (int i = numBands_ - 1; i >= 0; --i) + { + if (!bands_[i].queue_.empty()) + { + Result ret = bands_[i].queue_.next_locked(); + bands_[i].currentToken_ = 0; + ret.index = i; + --numPending_; + return ret; + } + } + DIE("Unexpected nonempty queue"); + return Result(0, 0); + } + + /// The caller must acquire this lock before using any of the _locked() + /// functions. If the caller needs to do many operations in quick + /// succession, it might be faster to do them under a single lock, + /// i.e. acquire lock() first, then call xxx_locked() repeatedly, then + /// unlock. + /// @return the lock to use for the _locked() functions. + OSMutex *lock() + { + return &lock_; + } + + /// Adds an entry to the queue. It will be added to the end of the given + /// priority band. + /// @param item the entry to be added to the queue. + /// @param prio which priority band to add to. 0 = highest priority. Must + /// be within 0 and numBands_ - 1. + void insert(QMember *item, unsigned prio) + { + OSMutexLock h(lock()); + return insert_locked(item, prio); + } + + /// Adds an entry to the queue. It will be added to the end of the given + /// priority band. The caller must hold lock(). + /// @param item the entry to be added to the queue. + /// @param prio which priority band to add to. 0 = highest priority. Must + /// be within 0 and numBands_ - 1. + void insert_locked(QMember *item, unsigned prio) + { + HASSERT(prio < numBands_); + ++numPending_; + bands_[prio].queue_.insert_locked(item); + } + + /// Get the number of pending items in the queue. + /// @param prio in the list to operate on + /// @return number of pending items in that priority band in the queue + size_t pending(unsigned prio) + { + HASSERT(prio < numBands_); + return bands_[prio].queue_.pending(); + }; + + /// Get the number of pending items in the queue (all bands total) + /// @return number of pending items + size_t pending() const + { + return numPending_; + } + + /// @return true if the queue is empty (on all priority bands). + bool empty() const + { + return numPending_ == 0; + } + + /// @return the number of available priority bands. + unsigned num_prio() const + { + return numBands_; + } + +private: + /// This structure contains information about one priority band. + struct Band + { + /// Holds the queue for this priority band. + Q queue_; + /// How many tokens we add each call. + Fixed16 stride_ {0}; + /// How many tokens we have right now. If this is > 1 then we will emit + /// the front of this queue, if it is < 1 then we move on to the next + /// priority item. + Fixed16 currentToken_ {1, 0}; + }; + + /// Protects insert and next operations. + OSMutex lock_; + + /// How many priority bands we have. + unsigned numBands_; + + /// How many queue entries are pending. + unsigned numPending_ {0}; + + /// The actual priority bands. + Band *bands_; +}; + +#endif // _UTILS_SCHEDULEDQUEUE_HXX_ From 3e486ff4a3f540a9d42dea18600c8819f2e40606 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 1 Jan 2021 22:23:58 +0100 Subject: [PATCH 149/171] Adds a bus master pattern that shows how to organize a polling loop. (#498) * Adds a bus master pattern that shows how to organize a polling loop. * Adds comments about the API. --- src/utils/BusMaster.cxxtest | 160 +++++++++++++++++++++++++++++++++ src/utils/BusMaster.hxx | 173 ++++++++++++++++++++++++++++++++++++ 2 files changed, 333 insertions(+) create mode 100644 src/utils/BusMaster.cxxtest create mode 100644 src/utils/BusMaster.hxx diff --git a/src/utils/BusMaster.cxxtest b/src/utils/BusMaster.cxxtest new file mode 100644 index 000000000..770719e4d --- /dev/null +++ b/src/utils/BusMaster.cxxtest @@ -0,0 +1,160 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BusMaster.cxxtest + * + * Unit tests for the bus master pattern. + * + * @author Balazs Racz + * @date 31 Dec 2020 + */ + +#include "utils/BusMaster.hxx" + +#include "utils/test_main.hxx" +#include + +/// Our imaginary bus will send these as packets. +struct PacketPayload +{ + /// Identifies the bus activity that generated this packet. + unsigned source; +}; + +/// Declares the bus type. +using FakeBus = Bus; + +/// Collects the outgoing packets into a vector. +class FakeSink : public FakeBus::PacketSink +{ +public: + void send(FakeBus::Packet *pkt, unsigned prio) override + { + packets_.emplace_back(pkt); + if (shutdown_) + { + packets_.clear(); + } + } + + /// Call this for test shutdown. + void shutdown() + { + shutdown_ = true; + packets_.clear(); + } + + /// If true, all packets will be immediately returned. + bool shutdown_ {false}; + /// Packets that arrived. Front is the oldest packet, back is the newest. + std::deque packets_; +}; + +class FakeActivity : public FakeBus::Activity +{ +public: + FakeActivity(unsigned num) + : num_(num) + { + } + + void fill_packet(Packet *packet) override + { + packet->data()->source = num_; + } + +private: + /// Identifies this activity. Will be stored in the packets. + unsigned num_; +}; + +class BusMasterTest : public ::testing::Test +{ +protected: + ~BusMasterTest() + { + sink_.shutdown(); + master_.shutdown(); + wait_for_main_executor(); + } + + /// Simulates completing a packet in the sink. Expects another packet to + /// arrive and returns the source ID for the newly arrived packet. + /// @return pkt->source for the newly arrived packet. + unsigned get_next_packet() + { + EXPECT_EQ(3u, sink_.packets_.size()); + // Simulate sink completing a packet. + sink_.packets_.pop_front(); + wait_for_main_executor(); + + EXPECT_EQ(3u, sink_.packets_.size()); + return sink_.packets_.back()->data()->source; + } + + static constexpr unsigned IDLE = 0xFFFu; + FakeSink sink_; + FakeActivity idleActivity_ {IDLE}; + FakeBus::Master master_ {&g_service, &sink_, &idleActivity_, 3}; +}; + +const unsigned BusMasterTest::IDLE; + +TEST_F(BusMasterTest, create) +{ +} + +TEST_F(BusMasterTest, roundrobin) +{ + FakeActivity a1 {1}; + FakeActivity a2 {2}; + FakeActivity a3 {3}; + constexpr Fixed16 policy[] = {{1}}; + master_.set_policy(1, policy); + wait_for_main_executor(); + // There should be three idles now in the queue. + ASSERT_EQ(3u, sink_.packets_.size()); + EXPECT_EQ(IDLE, sink_.packets_[0]->data()->source); + EXPECT_EQ(IDLE, sink_.packets_[1]->data()->source); + EXPECT_EQ(IDLE, sink_.packets_[2]->data()->source); + + master_.schedule_activity(&a1, 0); + master_.schedule_activity(&a2, 0); + + // Simulate sink completing a packet: arrival is from activity 1. + EXPECT_EQ(1u, get_next_packet()); + // next from 2 + EXPECT_EQ(2u, get_next_packet()); + // then idle. + EXPECT_EQ(IDLE, get_next_packet()); + + master_.schedule_activity(&a1, 0); + master_.schedule_activity(&a2, 0); + master_.schedule_activity(&a3, 0); + EXPECT_EQ(1u, get_next_packet()); + EXPECT_EQ(2u, get_next_packet()); + EXPECT_EQ(3u, get_next_packet()); +} diff --git a/src/utils/BusMaster.hxx b/src/utils/BusMaster.hxx new file mode 100644 index 000000000..b6f33e7b2 --- /dev/null +++ b/src/utils/BusMaster.hxx @@ -0,0 +1,173 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BusMaster.hxx + * + * An abstract controller to manage a shared bus which needs to be polled. + * + * @author Balazs Racz + * @date 30 Dec 2020 + */ + +#ifndef _UTILS_BUSMASTER_HXX_ +#define _UTILS_BUSMASTER_HXX_ + +#include "executor/StateFlow.hxx" +#include "utils/Buffer.hxx" +#include "utils/LimitedPool.hxx" +#include "utils/ScheduledQueue.hxx" + +/// This is a namespace class that contains a variety of related classes for +/// handling a bus that needs a polling loop. +template class Bus +{ +public: + /// The buffer type that is handed around to the different participants. + using Packet = Buffer; + + /// Self-owned buffer type. + using PacketPtr = BufferPtr; + + /// This is the consumer of the generated packets. Usually a device driver + /// or something like that. + using PacketSink = FlowInterface; + + /// Abstract class that gets scheduled for polling and when the master + /// decides to take it, gets a buffer to fill in with a packet. + class Activity : public QMember + { + public: + using Packet = Bus::Packet; + using PacketPtr = Bus::PacketPtr; + + /// Complete a packet to send to the bus. + /// @param packet must be filled in the by callee. Ownership is + /// retained by the caller. + virtual void fill_packet(Packet *packet) = 0; + }; + + /// The Bus Master class. This keeps the scheduler of activities and owns a + /// variety of auxiliary objects and memory management. + class Master : public StateFlowBase + { + public: + /// Constructor. + /// @param s the executor to run the service on. + /// @param sink this is where the generated packets will be sent out. + /// @param idle when no activity is scheduled, this activity will be + /// invoked with the packet. This activity must be ready to render a + /// packet at all times. + /// @param num_enqueued this is how many packets we fill in and enqueue + /// for the sink. It is a tradeoff between the sink being out of work + /// vs the what is the lowest latency between enqueueing a high + /// priority activity and that getting the next packet. Recommended + /// values are 2 or 3. + Master( + Service *s, PacketSink *sink, Activity *idle, unsigned num_enqueued) + : StateFlowBase(s) + , idle_(idle) + , sink_(sink) + , pool_(sizeof(Packet), num_enqueued) + , numPacketsInPool_(num_enqueued) + , needShutdown_(false) + { + } + + /// Used in unittests to cleanly shutdown the bus master. + void shutdown() + { + needShutdown_ = true; + while (!is_terminated() && (pool_.free_items() < numPacketsInPool_)) + { + usleep(200); + } + } + + /// Sets the scheduling policy. This must be called exactly once after + /// construction before scheduling any bus activity. + void set_policy(unsigned num_prio, const Fixed16 *strides) + { + queue_.emplace(num_prio, strides); + start_flow(STATE(get_buffer)); + } + + /// Adds an activity to the bus master's scheduler. The activity must + /// be ready to fill in a packet right now. + /// @param a the activity to schedule. The caller retains ownership. + /// @param prio the priority band to schedule in. + void schedule_activity(Activity *a, unsigned prio) + { + queue_->insert(a, prio); + } + + private: + /// Start of scheduling flow. + Action get_buffer() + { + return allocate_and_call(sink_, STATE(fill_pkt), &pool_); + } + + /// Executes the scheduling decision, fills in the packet by the + /// selected activity and sends it to the bus sink. + Action fill_pkt() + { + auto buf = get_buffer_deleter(get_allocation_result(sink_)); + if (needShutdown_) + { + return exit(); + } + // Picks the next activity to do on the bus. + Activity *a = (Activity *)queue_->next().item; + if (!a) + { + a = idle_; + } + a->fill_packet(buf.get()); + sink_->send(buf.release()); + return call_immediately(STATE(get_buffer)); + } + + /// Handles the policy of polling. + uninitialized queue_; + /// If we have no activity to do, this activity gets invoked. It must + /// always be able to fill in a packet. + Activity *idle_; + /// Where to send the generated packets. + PacketSink *sink_; + /// Source of the buffers that we fill and hand out. + LimitedPool pool_; + /// Total number of packets that the pool can generate. + uint16_t numPacketsInPool_; + /// True if shutdown was requested. + uint16_t needShutdown_ : 1; + }; // class Master + +private: + /// This class cannot be instantiated. + Bus(); +}; + +#endif // _UTILS_BUSMASTER_HXX_ From b476403001fc3cb4e22d14f32b663b3cd16b4ec9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 1 Jan 2021 22:24:17 +0100 Subject: [PATCH 150/171] Support eeprom update notification. (#499) Adds a weak callback to the eepromemulation driver, triggered after each write is completed. Shows with an example how to use this to automatically apply changes that happened from JMRI configuration UI. --- .../freertos.armv7m.ek-tm4c123gxl/main.cxx | 34 +++++++++++++++++++ boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx | 9 +++++ .../common/EEPROMEmulation.cxx | 2 ++ .../common/EEPROMEmulation.hxx | 6 ++++ .../common/EEPROMEmulation_weak.cxx | 8 +++++ 5 files changed, 59 insertions(+) diff --git a/applications/io_board/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx b/applications/io_board/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx index 03cd6e908..a514e76d1 100644 --- a/applications/io_board/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx +++ b/applications/io_board/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx @@ -127,6 +127,40 @@ openlcb::ConfiguredProducer producer_sw2( openlcb::RefreshLoop loop( stack.node(), {producer_sw1.polling(), producer_sw2.polling()}); +/// This timer checks the eeprom once a second and if the user has written +/// something, executes a reload of the configuration via the OpenLCB config +/// service. +class AutoUpdateTimer : public ::Timer +{ +public: + AutoUpdateTimer() + : ::Timer(stack.executor()->active_timers()) + { + start(SEC_TO_NSEC(1)); + } + + long long timeout() override + { + extern uint8_t eeprom_updated; + if (eeprom_updated) + { + needUpdate_ = true; + eeprom_updated = 0; + } + else + { + if (needUpdate_) + { + stack.config_service()->trigger_update(); + needUpdate_ = false; + } + } + return RESTART; + } + + bool needUpdate_ {false}; +} update_timer; + /** Entry point to application. * @param argc number of command line arguments * @param argv array of command line arguments diff --git a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx index 4eb4a0fe5..eb087679f 100644 --- a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx +++ b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx @@ -110,6 +110,15 @@ StoredBitSet* g_gpio_stored_bit_set = nullptr; constexpr unsigned EEPROM_BIT_COUNT = 84; constexpr unsigned EEPROM_BITS_PER_CELL = 28; +/// This variable will be set to 1 when a write arrives to the eeprom. +uint8_t eeprom_updated = 0; + +// Overridesthe default behavior to keep track of eeprom writes. +void EEPROMEmulation::updated_notification() +{ + eeprom_updated = 1; +} + extern "C" { void hw_set_to_safe(void); diff --git a/src/freertos_drivers/common/EEPROMEmulation.cxx b/src/freertos_drivers/common/EEPROMEmulation.cxx index 89630aff5..d220a4701 100644 --- a/src/freertos_drivers/common/EEPROMEmulation.cxx +++ b/src/freertos_drivers/common/EEPROMEmulation.cxx @@ -194,6 +194,8 @@ void EEPROMEmulation::write(unsigned int index, const void *buf, size_t len) { memcpy(shadow_ + shadow_index, shadow_data, shadow_len); } + + updated_notification(); } /** Write to the EEPROM on a native block boundary. diff --git a/src/freertos_drivers/common/EEPROMEmulation.hxx b/src/freertos_drivers/common/EEPROMEmulation.hxx index b8e5d4680..07e646d76 100644 --- a/src/freertos_drivers/common/EEPROMEmulation.hxx +++ b/src/freertos_drivers/common/EEPROMEmulation.hxx @@ -154,6 +154,12 @@ protected: static const size_t BLOCK_SIZE; private: + /** This function will be called after every write. The default + * implementation is a weak symbol with an empty function. It is intended + * to be overridden in the application to get callbacks for eeprom writes + * that can trigger a reload. */ + void updated_notification(); + /** Write to the EEPROM. NOTE!!! This is not necessarily atomic across * byte boundaries in the case of power loss. The user should take this * into account as it relates to data integrity of a whole block. diff --git a/src/freertos_drivers/common/EEPROMEmulation_weak.cxx b/src/freertos_drivers/common/EEPROMEmulation_weak.cxx index e77dcd606..b7b4aca61 100644 --- a/src/freertos_drivers/common/EEPROMEmulation_weak.cxx +++ b/src/freertos_drivers/common/EEPROMEmulation_weak.cxx @@ -37,3 +37,11 @@ // emulation implementation to prevent GCC from mistakenly optimizing away the // constant into a linker reference. const bool __attribute__((weak)) EEPROMEmulation::SHADOW_IN_RAM = false; + +/// This function will be called after every write. The default +/// implementation is a weak symbol with an empty function. It is intended +/// to be overridden in the application to get callbacks for eeprom writes +/// that can trigger a reload. +void __attribute__((weak)) EEPROMEmulation::updated_notification() +{ +} From c3e992c09d7cddc89f537d72fd9e92ed3f196d36 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 1 Jan 2021 22:26:51 +0100 Subject: [PATCH 151/171] Fix typo --- src/utils/ScheduledQueue.cxxtest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/ScheduledQueue.cxxtest b/src/utils/ScheduledQueue.cxxtest index e4dfb358b..aaea7db7e 100644 --- a/src/utils/ScheduledQueue.cxxtest +++ b/src/utils/ScheduledQueue.cxxtest @@ -40,7 +40,7 @@ protected: }; /// Adds a new entry to the queue. - /// @param prio which priority band ot add to + /// @param prio which priority band to add to /// @return the index of the new member. unsigned add_empty(unsigned prio) { From 6a149cfd6992cabc2af6ec5db00f00d5d1e63f33 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 2 Jan 2021 12:48:40 +0100 Subject: [PATCH 152/171] fix eepromemu tests. --- src/utils/EEPROMEmuTest.hxx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/utils/EEPROMEmuTest.hxx b/src/utils/EEPROMEmuTest.hxx index 5f04de8f0..bd5288b2b 100644 --- a/src/utils/EEPROMEmuTest.hxx +++ b/src/utils/EEPROMEmuTest.hxx @@ -59,6 +59,10 @@ static const char FILENAME[] = "/tmp/eeprom"; #define EELEN 32768 +void EEPROMEmulation::updated_notification() +{ +} + // We need to jump through some hoops to define a linker symbol // "__eeprom_start" in a place that is not actually constant. namespace foo { From 8e9690d8b49651be04a8bdc29ba18b5839903d32 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 2 Jan 2021 21:25:47 +0100 Subject: [PATCH 153/171] Adds functions to generate paged-mode programming packets. (#500) --- src/dcc/Packet.cxx | 21 +++++++++++++++++++++ src/dcc/Packet.cxxtest | 36 ++++++++++++++++++++++++++++++++++++ src/dcc/Packet.hxx | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/src/dcc/Packet.cxx b/src/dcc/Packet.cxx index 92a73dc8b..84d953669 100644 --- a/src/dcc/Packet.cxx +++ b/src/dcc/Packet.cxx @@ -82,6 +82,9 @@ enum DCC_SVC_BITVAL_VERIFY = 0b11100000, DCC_SVC_BITVAL_VALUE = 0b00001000, + DCC_SVC_PAGED_WRITE = 0b01111000, + DCC_SVC_PAGED_VERIFY = 0b01110000, + DCC_BASIC_ACCESSORY_B1 = 0b10000000, DCC_BASIC_ACCESSORY_B2 = 0b10000000, @@ -293,6 +296,24 @@ void Packet::set_dcc_svc_write_bit( add_dcc_prog_command(DCC_SVC_BIT_MANIPULATE, cv_number, vvv); } +void Packet::set_dcc_svc_paged_write_reg(uint8_t reg, uint8_t value) +{ + HASSERT(reg < 8); + start_dcc_svc_packet(); + payload[dlc++] = DCC_SVC_PAGED_WRITE | reg; + payload[dlc++] = value; + add_dcc_checksum(); +} + +void Packet::set_dcc_svc_paged_verify_reg(uint8_t reg, uint8_t value) +{ + HASSERT(reg < 8); + start_dcc_svc_packet(); + payload[dlc++] = DCC_SVC_PAGED_VERIFY | reg; + payload[dlc++] = value; + add_dcc_checksum(); +} + void Packet::add_dcc_basic_accessory(unsigned address, bool is_activate) { payload[dlc++] = DCC_BASIC_ACCESSORY_B1 | ((address >> 3) & 0b111111); uint8_t b2 = 1; diff --git a/src/dcc/Packet.cxxtest b/src/dcc/Packet.cxxtest index 0f9bf934a..06b4c23ce 100644 --- a/src/dcc/Packet.cxxtest +++ b/src/dcc/Packet.cxxtest @@ -22,6 +22,17 @@ protected: return vector(pkt_.payload + 0, pkt_.payload + pkt_.dlc); } + /// @return true if pkt_ has correct checksum. + bool check_checksum() + { + uint8_t p = 0; + for (unsigned i = 0; i < pkt_.dlc; i++) + { + p ^= pkt_.payload[i]; + } + return p == 0; + } + vector packet(const std::initializer_list &data) { vector v; @@ -205,6 +216,31 @@ TEST_F(PacketTest, SvcProgDirectBit) EXPECT_THAT(get_packet(), ElementsAre(b1, b2, b3, b1 ^ b2 ^ b3)); } +TEST_F(PacketTest, SvcProgPagedWrite) +{ + pkt_.set_dcc_svc_paged_set_page(); + EXPECT_THAT(get_packet(), ElementsAre(0b01111101, 0b00000001, 0b01111100)); + EXPECT_TRUE(pkt_.packet_header.send_long_preamble); + EXPECT_TRUE(check_checksum()); + + pkt_.set_dcc_svc_paged_write_reg(3, 0xaa); + EXPECT_THAT(get_packet(), ElementsAre(0b01111011, 0xaa, _)); + EXPECT_TRUE(check_checksum()); +} + +TEST_F(PacketTest, SvcProgPagedVerify) +{ + pkt_.set_dcc_svc_paged_set_page(55); + EXPECT_THAT(get_packet(), ElementsAre(0b01111101, 55, _)); + EXPECT_TRUE(pkt_.packet_header.send_long_preamble); + EXPECT_TRUE(check_checksum()); + + pkt_.set_dcc_svc_paged_verify_reg(0, 0x34); + EXPECT_THAT(get_packet(), ElementsAre(0b01110000, 0x34, _)); + EXPECT_TRUE(pkt_.packet_header.send_long_preamble); + EXPECT_TRUE(check_checksum()); +} + TEST_F(PacketTest, MMOld) { pkt_.start_mm_packet(); diff --git a/src/dcc/Packet.hxx b/src/dcc/Packet.hxx index 072ab478e..276bd75e6 100644 --- a/src/dcc/Packet.hxx +++ b/src/dcc/Packet.hxx @@ -57,6 +57,9 @@ struct Packet : public DCCPacket * for marklin-14-step speed commands. */ static const unsigned CHANGE_DIR = DCC_PACKET_EMERGENCY_STOP; + /** Used for page-preset packets. */ + static const unsigned PAGE_REGISTER_ID = 0b101; + Packet() { clear(); @@ -230,6 +233,35 @@ struct Packet : public DCCPacket * @param desired is true if bit:=1 should be written */ void set_dcc_svc_write_bit(unsigned cv_number, unsigned bit, bool desired); + /** Sets the packet to a DCC service mode packet in Paged Mode, setting the + * page register. This function does not need a DCC address. Includes the + * checksum. + * @param page Page to set, 1 is the default page, zero is reserved, 255 + * max. + */ + void set_dcc_svc_paged_set_page(unsigned page = 1) + { + set_dcc_svc_paged_write_reg(PAGE_REGISTER_ID, page); + } + + /** Sets the packet to a DCC service mode packet in Paged Mode, setting any + * register. This function does not need a DCC address. Includes the + * checksum. + * @param reg register, 0 to 7. On the default page register 0 is CV1 + * (address). + * @param value Payload to write to that register. 0 to 255. + */ + void set_dcc_svc_paged_write_reg(uint8_t reg, uint8_t value); + + /** Sets the packet to a DCC service mode packet in Paged Mode, setting the + * page register. This function does not need a DCC address. Includes the + * checksum. + * @param reg register, 0 to 7. On the default page register 0 is CV1 + * (address). + * @param value Payload to check on that register. + */ + void set_dcc_svc_paged_verify_reg(uint8_t reg, uint8_t value); + /** Adds a DCC basic accessory decoder command packet and the checksum * byte. * @param address is the unencoded 12-bit address, containing both the A From a2e2124f9189c6aedebec797dc96f6d097d70881 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 3 Jan 2021 19:10:39 +0100 Subject: [PATCH 154/171] Fixes library.a dependencies and other makefile cleanup. (#501) - Removes dead code from prog.mk - Adds endif comments - Makes the library subdirectories build in an application target be a direct dependency of the executable. - updates core_test.mk so that the libsomething.a files are dependencies there as well. This should avoid certain cases where a change in a file did not cause a test to be rebuilt. === * Makes the library subdirectories build in an application target be a direct dependency of the executable. The lib/timestamp is still updated, but make should now know about which libxxx.a files belong together. * Deletes dead code from prog.mk * Ensures that test targets are dependent on core library files. --- etc/core_test.mk | 11 +++++++-- etc/make_utils.mk | 10 +++++--- etc/prog.mk | 60 ++++------------------------------------------- 3 files changed, 21 insertions(+), 60 deletions(-) diff --git a/etc/core_test.mk b/etc/core_test.mk index a9262eb84..415539837 100644 --- a/etc/core_test.mk +++ b/etc/core_test.mk @@ -30,13 +30,20 @@ CXXFLAGS += $(INCLUDES) .SUFFIXES: .o .otest .c .cxx .cxxtest .test .testmd5 .testout -LIBDIR ?= lib +ifdef LIBDIR +# we are under prog.mk +TESTLIBDEPS += $(foreach lib,$(SUBDIRS),lib/lib$(lib).a) +else +LIBDIR = lib +endif +TESTLIBDEPS += $(foreach lib,$(CORELIBS),$(LIBDIR)/lib$(lib).a) + LDFLAGS += -L$(LIBDIR) $(LIBDIR)/timestamp: $(BUILDDIRS) $(info test deps $(TESTOBJSEXTRA) $(LIBDIR)/timestamp ) -$(TESTBINS): %.test$(EXTENTION) : %.test.o $(TESTOBJSEXTRA) $(LIBDIR)/timestamp $(TESTEXTRADEPS) | $(BUILDDIRS) +$(TESTBINS): %.test$(EXTENTION) : %.test.o $(TESTOBJSEXTRA) $(LIBDIR)/timestamp lib/timestamp $(TESTLIBDEPS) $(TESTEXTRADEPS) | $(BUILDDIRS) $(LD) -o $@ $(LDFLAGS) -los $< $(TESTOBJSEXTRA) $(STARTGROUP) $(LINKCORELIBS) $(ENDGROUP) $(SYSLIBRARIES) -include $(TESTOBJS:.test.o=.dtest) diff --git a/etc/make_utils.mk b/etc/make_utils.mk index 3fbd76b56..fca8fbd01 100644 --- a/etc/make_utils.mk +++ b/etc/make_utils.mk @@ -27,10 +27,14 @@ endef ### Helper template to declare a dependency. ### Arguments: target_file dependency_file ### Example on how to call: Put the following on a standalone line in the Makefile -### $(foreach lib,$(LIBDIRS),$(eval $(call DEP_helper_template,lib/lib$(lib).a,build-$(lib)))) -define DEP_helper_template +### $(foreach lib,$(LIBDIRS),$(eval $(call SUBDIR_helper_template,lib/lib$(lib).a,build-$(lib)))) +define SUBDIR_helper_template -$(1): $(2) +$(1)/lib$(1).a: | build-$(1) + +lib/lib$(1).a: $(1)/lib$(1).a + +lib/timestamp: lib/lib$(1).a endef diff --git a/etc/prog.mk b/etc/prog.mk index 7b1e2f00c..778568048 100644 --- a/etc/prog.mk +++ b/etc/prog.mk @@ -38,8 +38,8 @@ OBJS = $(CXXSRCS:.cxx=.o) $(CPPSRCS:.cpp=.o) $(CSRCS:.c=.o) $(ASMSRCS:.S=.o) \ $(XMLSRCS:.xml=.o) LIBDIR = $(OPENMRNPATH)/targets/$(TARGET)/lib -FULLPATHLIBS = $(wildcard $(LIBDIR)/*.a) $(wildcard lib/*.a) LIBDIRS := $(SUBDIRS) +FULLPATHLIBS = $(wildcard $(LIBDIR)/*.a) $(wildcard lib/*.a) $(foreach lib,$(LIBDIRS),lib/lib$(lib).a) LIBS = $(STARTGROUP) \ $(foreach lib,$(LIBDIRS),-l$(lib)) \ $(LINKCORELIBS) \ @@ -53,7 +53,7 @@ all: # the directory foo and rebuild stuff that's there. However, the dependency is # phrased in a way that if recursing does not change the library (when it's # up-to-date) then the .elf linking is not re-done. -$(foreach lib,$(LIBDIRS),$(eval $(call DEP_helper_template,lib/lib$(lib).a,build-$(lib)))) +$(foreach lib,$(LIBDIRS),$(eval $(call SUBDIR_helper_template,$(lib)))) CDIEXTRA := -I. INCLUDES += -I. @@ -151,7 +151,7 @@ clean: clean_cdi .PHONY: clean_cdi clean_cdi: rm -f cdi.xmlout cdi.nxml cdi.cxxout compile_cdi -endif +endif # have_config_cdi # Makes sure the subdirectory builds are done before linking the binary. # The targets and variable BUILDDIRS are defined in recurse.mk. @@ -159,7 +159,7 @@ endif # This file acts as a guard describing when the last libsomething.a was remade # in the application libraries. -lib/timestamp : FORCE $(BUILDDIRS) +lib/timestamp : FORCE # creates the lib directory @[ -d lib ] || mkdir lib # in case there are not applibs. @@ -291,7 +291,6 @@ tests: @echo "***Not building tests at target $(TARGET), because missing: $(TEST_MISSING_DEPS) ***" else -ifeq (1,1) SRCDIR=$(abspath ../../) #old code from prog.mk @@ -303,55 +302,6 @@ SYSLIBRARIES += $(LIBS) TESTEXTRADEPS += lib/timestamp include $(OPENMRNPATH)/etc/core_test.mk -else -FULLPATHTESTSRCS ?= $(wildcard $(VPATH)/tests/*_test.cc) -TESTSRCS = $(notdir $(FULLPATHTESTSRCS)) $(wildcard *_test.cc) -TESTOBJS := $(TESTSRCS:.cc=.o) - -VPATH:=$(VPATH):$(GTESTPATH)/src:$(GTESTSRCPATH):$(GMOCKPATH)/src:$(GMOCKSRCPATH):$(abspath ../../tests) -INCLUDES += -I$(GTESTPATH)/include -I$(GTESTPATH) -I$(GMOCKPATH)/include -I$(GMOCKPATH) - -TEST_OUTPUTS=$(TESTOBJS:.o=.output) - -TEST_EXTRA_OBJS += gtest-all.o gmock-all.o - -.cc.o: - $(CXX) $(CXXFLAGS) $< -o $@ - $(CXX) -MM $(CXXFLAGS) $< > $*.d - -gtest-all.o : %.o : $(GTESTSRCPATH)/src/%.cc - $(CXX) $(CXXFLAGS) -I$(GTESTPATH) -I$(GTESTSRCPATH) $< -o $@ - $(CXX) -MM $(CXXFLAGS) -I$(GTESTPATH) -I$(GTESTSRCPATH) $< > $*.d - -gmock-all.o : %.o : $(GMOCKSRCPATH)/src/%.cc - $(CXX) $(CXXFLAGS) -I$(GMOCKPATH) -I$(GMOCKSRCPATH) $< -o $@ - $(CXX) -MM $(CXXFLAGS) -I$(GMOCKPATH) -I$(GMOCKSRCPATH) $< > $*.d - -#.PHONY: $(TEST_OUTPUTS) - -$(TEST_OUTPUTS) : %_test.output : %_test - ./$*_test --gtest_death_test_style=threadsafe - touch $@ - -$(TESTOBJS:.o=) : %_test : %_test.o $(TEST_EXTRA_OBJS) $(FULLPATHLIBS) $(LIBDIR)/timestamp lib/timestamp - $(LD) -o $*_test$(EXTENTION) $*_test.o $(TEST_EXTRA_OBJS) $(OBJEXTRA) $(LDFLAGS) $(LIBS) $(SYSLIBRARIES) -lstdc++ - -%_test.o : %_test.cc - $(CXX) $(CXXFLAGS:-Werror=) -DTESTING -fpermissive $< -o $*_test.o - $(CXX) -MM $(CXXFLAGS) $< > $*_test.d - -#$(TEST_OUTPUTS) : %_test.output : %_test.cc gtest-all.o gtest_main.o -# $(CXX) $(CXXFLAGS) $< -o $*_test.o -# $(CXX) -MM $(CXXFLAGS) $< > $*_test.d -# $(LD) -o $*_test$(EXTENTION) $+ $(OBJEXTRA) $(LDFLAGS) $(LIBS) $(SYSLIBRARIES) -lstdc++ -# ./$*_test - -tests : all $(TEST_OUTPUTS) - -mksubdirs: - [ -d lib ] || mkdir lib -endif # old testrunner code - endif # if we are able to run tests -endif +endif # if we can build anything -- MISSING_DEPS is empty From 66698b2d3432d29ac5f6ae07bd698b3095274fb0 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 6 Jan 2021 11:22:45 +0100 Subject: [PATCH 155/171] Adds helper functions to generate different DCC packet types: (#502) - functions 29 to 68 - binary states - analog states --- src/dcc/Packet.cxx | 38 +++++++++++++++++++---- src/dcc/Packet.cxxtest | 69 ++++++++++++++++++++++++++++++++++++++++++ src/dcc/Packet.hxx | 27 +++++++++++++++-- 3 files changed, 126 insertions(+), 8 deletions(-) diff --git a/src/dcc/Packet.cxx b/src/dcc/Packet.cxx index 84d953669..b35922024 100644 --- a/src/dcc/Packet.cxx +++ b/src/dcc/Packet.cxx @@ -69,6 +69,10 @@ enum DCC_FUNCTION2_F9 = 0b10100000, DCC_FEATURE_EXP_F13 = 0b11011110, DCC_FEATURE_EXP_F21 = 0b11011111, + DCC_FEATURE_EXP_FNHI = 0b11011000, + DCC_BINARY_SHORT = 0b11011101, + DCC_BINARY_LONG = 0b11000000, + DCC_ANALOG_FN = 0b00111101, DCC_PROG_READ1 = 0b11100100, DCC_PROG_WRITE1 = 0b11101100, @@ -234,17 +238,39 @@ void Packet::add_dcc_function9_12(unsigned values) add_dcc_checksum(); } -void Packet::add_dcc_function13_20(unsigned values) +void Packet::add_dcc_function_hi(uint8_t base, uint8_t values) { - payload[dlc++] = DCC_FEATURE_EXP_F13; - payload[dlc++] = values & 0xff; + base -= 13; + HASSERT((base & 0b111) == 0); + HASSERT(base <= (61 - 13)); + base >>= 3; + base -= 2; + payload[dlc++] = DCC_FEATURE_EXP_FNHI | (base & 0b111); + payload[dlc++] = values; add_dcc_checksum(); } -void Packet::add_dcc_function21_28(unsigned values) +void Packet::add_dcc_binary_state(uint16_t fn, bool value) { - payload[dlc++] = DCC_FEATURE_EXP_F21; - payload[dlc++] = values & 0xff; + if (fn <= 127) + { + payload[dlc++] = DCC_BINARY_SHORT; + payload[dlc++] = fn | (value ? 0x80 : 0); + } + else + { + payload[dlc++] = DCC_BINARY_LONG; + payload[dlc++] = (fn & 0x7F) | (value ? 0x80 : 0); + payload[dlc++] = (fn >> 8) & 0xFF; + } + add_dcc_checksum(); +} + +void Packet::add_dcc_analog_function(uint8_t fn, uint8_t value) +{ + payload[dlc++] = DCC_ANALOG_FN; + payload[dlc++] = fn; + payload[dlc++] = value; add_dcc_checksum(); } diff --git a/src/dcc/Packet.cxxtest b/src/dcc/Packet.cxxtest index 06b4c23ce..afd6e878b 100644 --- a/src/dcc/Packet.cxxtest +++ b/src/dcc/Packet.cxxtest @@ -172,6 +172,75 @@ TEST_F(PacketTest, Fn20) EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011111, 0xAA, _)); } +TEST_F(PacketTest, Fn29) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(29, 0x5A); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011000, 0x5A, _)); +} + +TEST_F(PacketTest, Fn37) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(37, 0x11); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011001, 0x11, _)); +} + +TEST_F(PacketTest, Fn45) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(45, 0x11); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011010, 0x11, _)); +} + +TEST_F(PacketTest, Fn53) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(53, 0x11); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011011, 0x11, _)); +} + +TEST_F(PacketTest, Fn61) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(61, 0x11); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011100, 0x11, _)); +} + +TEST_F(PacketTest, BinaryStateShort) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_binary_state(61, true); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011101, 61 | 0x80, _)); +} + +TEST_F(PacketTest, BinaryStateShortOff) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_binary_state(127, false); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011101, 127, _)); +} + +TEST_F(PacketTest, BinaryStateLongOn) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_binary_state(16 * 256 + 61, true); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11000000, 61 + 0x80, 16, _)); +} + +TEST_F(PacketTest, BinaryStateLongOff) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_binary_state(16 * 256 + 61, false); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11000000, 61, 16, _)); +} + +TEST_F(PacketTest, AnalogFunction) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_analog_function(17, 99); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b00111101, 17, 99, _)); +} TEST_F(PacketTest, DccBasicAccyOn) { diff --git a/src/dcc/Packet.hxx b/src/dcc/Packet.hxx index 276bd75e6..ff6460145 100644 --- a/src/dcc/Packet.hxx +++ b/src/dcc/Packet.hxx @@ -182,11 +182,34 @@ struct Packet : public DCCPacket /** Adds a DCC function group command to the packet. The lowest numbered * function is always at bit zero. @param values are bitmask of functions * to send to the loco. */ - void add_dcc_function13_20(unsigned values); + void add_dcc_function13_20(unsigned values) + { + add_dcc_function_hi(13, values); + } /** Adds a DCC function group command to the packet. The lowest numbered * function is always at bit zero. @param values are bitmask of functions * to send to the loco. */ - void add_dcc_function21_28(unsigned values); + void add_dcc_function21_28(unsigned values) + { + add_dcc_function_hi(21, values); + } + /** Adds a DCC function group command to the packet. The lowest numbered + * function is always at bit zero. + * @param base is a valid function number base, 13, 21, 29, 37, 45, 53 + * or 61. + * @param values are bitmask of functions to send to the loco. */ + void add_dcc_function_hi(uint8_t base, uint8_t values); + + /** Adds a DCC binary state control command to the packet. Automatically + * picks the short or long form, depending on the range of the argument. + * @param fn is a binary function variable, 0 to 32767. + * @param value true/false, what to set to. */ + void add_dcc_binary_state(uint16_t fn, bool value); + + /** Adds a DCC analog function control command to the packet. + * @param fn is an analog function variable, 0 to 255. + * @param value to set it to, 0 to 255. */ + void add_dcc_analog_function(uint8_t fn, uint8_t value); /** Helper function for adding programming mode packets. */ void add_dcc_prog_command( From b55d4d809770db896e0745d701bafff32348e35b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 6 Jan 2021 11:24:50 +0100 Subject: [PATCH 156/171] Adds support for DCC function 29-68. (#503) - Refactors the storage for DCC train payloads. - Adds additional storage for higher functions. - Adds refresh codes for higher functions and implementation generating the values. - Adds unittests that the locomotive object generates the correct DCC packets. === * Factors out the DCC payload type to a shared base. * Adds storage and packet generation for F29-F68. --- src/dcc/Loco.cxx | 20 ++-- src/dcc/Loco.hxx | 243 +++++++++++++++++++++++++---------------- src/dcc/Packet.cxxtest | 18 ++- 3 files changed, 180 insertions(+), 101 deletions(-) diff --git a/src/dcc/Loco.cxx b/src/dcc/Loco.cxx index 78462a1b1..50f34279a 100644 --- a/src/dcc/Loco.cxx +++ b/src/dcc/Loco.cxx @@ -55,7 +55,7 @@ template <> DccTrain::~DccTrain() packet_processor_remove_refresh_source(this); } -unsigned Dcc28Payload::get_fn_update_code(unsigned address) +unsigned DccPayloadBase::get_fn_update_code(unsigned address) { if (address < 5) { @@ -69,13 +69,9 @@ unsigned Dcc28Payload::get_fn_update_code(unsigned address) { return FUNCTION9; } - else if (address < 21) + else if (address <= 68) { - return FUNCTION13; - } - else if (address <= 28) - { - return FUNCTION21; + return FUNCTION13 + (address - 13) / 8; } return SPEED; } @@ -134,6 +130,16 @@ void DccTrain::get_next_packet(unsigned code, Packet *packet) packet->add_dcc_function21_28(this->p.fn_ >> 21); return; } + case FUNCTION29: + case FUNCTION37: + case FUNCTION45: + case FUNCTION53: + case FUNCTION61: + { + unsigned s = code - FUNCTION29; + packet->add_dcc_function_hi(29 + s * 8, this->p.fhi_[s]); + return; + } case ESTOP: { this->p.add_dcc_estop_to_packet(packet); diff --git a/src/dcc/Loco.hxx b/src/dcc/Loco.hxx index 55e2d9b71..223bfb6d2 100644 --- a/src/dcc/Loco.hxx +++ b/src/dcc/Loco.hxx @@ -70,6 +70,11 @@ enum DccTrainUpdateCode FUNCTION9 = 4, FUNCTION13 = 5, FUNCTION21 = 6, + FUNCTION29 = 7, + FUNCTION37 = 8, + FUNCTION45 = 9, + FUNCTION53 = 10, + FUNCTION61 = 11, MM_F1 = 2, MM_F2, MM_F3, @@ -212,12 +217,12 @@ public: // reverse. if (p.direction_ == 0) { - p.f0OnForward_ = p.fn_ & 1; + p.f0OnForward_ = p.get_fn_store(0); p.f0OnReverse_ = 0; } else { - p.f0OnReverse_ = p.fn_ & 1; + p.f0OnReverse_ = p.get_fn_store(0); p.f0OnForward_ = 0; } } @@ -248,15 +253,7 @@ public: // Ignore. return; } - unsigned bit = 1 << address; - if (value) - { - p.fn_ |= bit; - } - else - { - p.fn_ &= ~bit; - } + p.set_fn_store(address, value); packet_processor_notify_update(this, p.get_fn_update_code(address)); } /// @return the last set value of a given function, or 0 if the function is @@ -281,7 +278,7 @@ public: // Unknown. return 0; } - return (p.fn_ & (1 << address)) ? 1 : 0; + return p.get_fn_store(address) ? 1 : 0; } /// @return the legacy address of this loco. uint32_t legacy_address() OVERRIDE @@ -349,57 +346,125 @@ protected: P p; }; -/// Structure defining the volatile state for a 28-speed-step DCC locomotive. -struct Dcc28Payload +/// Common storage variables for the different DCC Payload types. +struct DccPayloadBase { - Dcc28Payload() - { - memset(this, 0, sizeof(*this)); - } /// Track address. largest address allowed is 10239. - unsigned address_ : 14; + uint16_t address_ : 14; /// 1 if this is a short address train. - unsigned isShortAddress_ : 1; + uint16_t isShortAddress_ : 1; /// 0: forward, 1: reverse - unsigned direction_ : 1; + uint16_t direction_ : 1; /// fp16 value of the last set speed. - unsigned lastSetSpeed_ : 16; + uint16_t lastSetSpeed_ : 16; + + // ==== 32-bit boundary ==== + /// functions f0-f28. unsigned fn_ : 29; /// Which refresh packet should go out next. - unsigned nextRefresh_ : 3; - /// Speed step we last set. - unsigned speed_ : 5; + uint8_t nextRefresh_ : 3; + + // ==== 32-bit boundary ==== + /// 1 if the last speed set was estop. - unsigned isEstop_ : 1; + uint8_t isEstop_ : 1; /// Whether the direction change packet still needs to go out. - unsigned directionChanged_ : 1; + uint8_t directionChanged_ : 1; /// 1 if the F0 function should be set/get in a directional way. - unsigned f0SetDirectional_ : 1; + uint8_t f0SetDirectional_ : 1; /// 1 if directional f0 is used and f0 is on for F. - unsigned f0OnForward_ : 1; + uint8_t f0OnForward_ : 1; /// 1 if directional f0 is used and f0 is on for R. - unsigned f0OnReverse_ : 1; + uint8_t f0OnReverse_ : 1; /// 1 if F0 should be turned off when dir==forward. - unsigned f0BlankForward_ : 1; + uint8_t f0BlankForward_ : 1; /// 1 if F0 should be turned off when dir==reverse. - unsigned f0BlankReverse_ : 1; + uint8_t f0BlankReverse_ : 1; + /// Speed step we last set. + uint8_t speed_ : 7; - /** @return the number of speed steps (in float). */ - static unsigned get_speed_steps() + /// f29-f68 state. + uint8_t fhi_[5]; + + /// @return the largest function number supported by this train + /// (inclusive). + static unsigned get_max_fn() { - return 28; + return 68; } - /** @returns the largest function number that is still valid. */ - static unsigned get_max_fn() + /// Set a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @param value function state + void set_fn_store(unsigned idx, bool value) { - return 28; + if (idx < 29) + { + if (value) + { + fn_ |= (1u << idx); + } + else + { + fn_ &= ~(1u << idx); + } + } + else + { + idx -= 29; + if (value) + { + fhi_[idx / 8] |= (1u << (idx & 7)); + } + else + { + fhi_[idx / 8] &= ~(1u << (idx & 7)); + } + } + } + + /// Get a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @return function state + bool get_fn_store(unsigned idx) + { + if (idx < 29) + { + return (fn_ & (1u << idx)) != 0; + } + else + { + idx -= 29; + return (fhi_[idx / 8] & (1u << (idx & 7))) != 0; + } } /** @return the update code to send ot the packet handler for a given * function value change. @param address is the function number(0..28). */ static unsigned get_fn_update_code(unsigned address); + + /// @return what type of address this train has. + TrainAddressType get_address_type() + { + return isShortAddress_ ? TrainAddressType::DCC_SHORT_ADDRESS + : TrainAddressType::DCC_LONG_ADDRESS; + } +}; + +/// Structure defining the volatile state for a 28-speed-step DCC locomotive. +struct Dcc28Payload : public DccPayloadBase +{ + Dcc28Payload() + { + memset(this, 0, sizeof(*this)); + } + + /** @return the number of speed steps (in float). */ + static unsigned get_speed_steps() + { + return 28; + } /** Adds the speed payload to a DCC packet. @param p is the packet to add * the speed payload to. */ @@ -414,14 +479,10 @@ struct Dcc28Payload { p->add_dcc_speed28(!direction_, Packet::EMERGENCY_STOP); } - - /// @return what type of address this train has. - TrainAddressType get_address_type() - { - return isShortAddress_ ? TrainAddressType::DCC_SHORT_ADDRESS : TrainAddressType::DCC_LONG_ADDRESS; - } }; +static_assert(sizeof(Dcc28Payload) == 16, "size of dcc payload is wrong"); + /// TrainImpl class for a DCC locomotive. template class DccTrain : public AbstractTrain { @@ -454,41 +515,12 @@ public: typedef DccTrain Dcc28Train; /// Structure defining the volatile state for a 128-speed-step DCC locomotive. -struct Dcc128Payload +struct Dcc128Payload : public DccPayloadBase { Dcc128Payload() { memset(this, 0, sizeof(*this)); } - /// Track address. largest address allowed is 10239. - unsigned address_ : 14; - /// 1 if this is a short address train. - unsigned isShortAddress_ : 1; - /// 0: forward, 1: reverse - unsigned direction_ : 1; - /// fp16 value of the last set speed. - unsigned lastSetSpeed_ : 16; - /// functions f0-f28. - unsigned fn_ : 29; - /// Which refresh packet should go out next. - unsigned nextRefresh_ : 3; - /// Speed step we last set. - unsigned speed_ : 7; - /// Whether the direction change packet still needs to go out. - unsigned directionChanged_ : 1; - - /// 1 if the last speed set was estop. - unsigned isEstop_ : 1; - /// 1 if the F0 function should be set/get in a directional way. - unsigned f0SetDirectional_ : 1; - /// 1 if directional f0 is used and f0 is on for F. - unsigned f0OnForward_ : 1; - /// 1 if directional f0 is used and f0 is on for R. - unsigned f0OnReverse_ : 1; - /// 1 if F0 should be turned off when dir==forward. - unsigned f0BlankForward_ : 1; - /// 1 if F0 should be turned off when dir==reverse. - unsigned f0BlankReverse_ : 1; /** @return the number of speed steps (the largest valid speed step). */ static unsigned get_speed_steps() @@ -496,19 +528,6 @@ struct Dcc128Payload return 126; } - /** @return the largest function number that is still valid. */ - static unsigned get_max_fn() - { - return 28; - } - - /** @return the update code to send ot the packet handler for a given - * function value change. */ - static unsigned get_fn_update_code(unsigned address) - { - return Dcc28Payload::get_fn_update_code(address); - } - /** Adds the speed payload to a DCC packet. @param p is the packet to add * the speed payload to. */ void add_dcc_speed_to_packet(dcc::Packet *p) @@ -522,12 +541,6 @@ struct Dcc128Payload { p->add_dcc_speed128(!direction_, Packet::EMERGENCY_STOP); } - - /// @return what type of address this train has. - TrainAddressType get_address_type() - { - return isShortAddress_ ? TrainAddressType::DCC_SHORT_ADDRESS : TrainAddressType::DCC_LONG_ADDRESS; - } }; /// TrainImpl class for a 128-speed-step DCC locomotive. @@ -580,6 +593,28 @@ struct MMOldPayload return 0; } + /// Set a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @param value function state + void set_fn_store(unsigned idx, bool value) { + if (value) + { + fn_ |= (1u << idx); + } + else + { + fn_ &= ~(1u << idx); + } + } + + /// Get a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @return function state + bool get_fn_store(unsigned idx) + { + return (fn_ & (1u << idx)) != 0; + } + /** @return the update code to send to the packet handler for a given * function value change. @param address is ignored */ unsigned get_fn_update_code(unsigned address) @@ -659,6 +694,28 @@ struct MMNewPayload return 4; } + /// Set a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @param value function state + void set_fn_store(unsigned idx, bool value) { + if (value) + { + fn_ |= (1u << idx); + } + else + { + fn_ &= ~(1u << idx); + } + } + + /// Get a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @return function state + bool get_fn_store(unsigned idx) + { + return (fn_ & (1u << idx)) != 0; + } + /** @return the update code to send to the packet handler for a given * function value change. @param address is the function number (0..4) */ unsigned get_fn_update_code(unsigned address) diff --git a/src/dcc/Packet.cxxtest b/src/dcc/Packet.cxxtest index afd6e878b..a6e843157 100644 --- a/src/dcc/Packet.cxxtest +++ b/src/dcc/Packet.cxxtest @@ -669,9 +669,25 @@ TEST_F(Train28Test, Function28) EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011111, 0x80, _)); } +TEST_F(Train28Test, Function47) +{ + EXPECT_CALL(loop_, send_update(&train_, _)).WillOnce(SaveArg<1>(&code_)); + train_.set_fn(47, 1); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011010, 0b100, _)); +} + +TEST_F(Train28Test, Function68) +{ + EXPECT_CALL(loop_, send_update(&train_, _)).WillOnce(SaveArg<1>(&code_)); + train_.set_fn(68, 1); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011100, 0x80, _)); +} + TEST_F(Train28Test, AllFunctions) { - for (int a = 0; a <= 28; ++a) + for (int a = 0; a <= 68; ++a) { EXPECT_CALL(loop_, send_update(&train_, _)) .WillOnce(SaveArg<1>(&code_)); From 170e6d8196d65a6bc2df6b2b15620fe7e3470457 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 6 Jan 2021 17:29:20 +0100 Subject: [PATCH 157/171] Tune DCC end of packet behavior (#504) Adjusts the end of packet behavior of DCC packet generator, focusing on DCC->Marklin, Marklin->DCC transition, with and without the railcom cutout and with or without the half-zero bit. - adds five extra one bits even if we are not generating a railcom cutout. This ensures that switching from DCC to Marklin is possible with keeping a DCC cutout window that will be used by a booster. - Moves the half-zero to the beginning of the DCC preamble instead of being after the cutout. This makes the marklin-dcc interoperation better. - Removes unnecessary states at the packet transition. - Adds a hack for the resync handling at the beginning of the DCC preamble. - Adjusts the timing for the half-zero to be an upper half of one bit and a lower half of zero. - Adds separate period setting for the PWM timer and the interval timer. - Fixes off by one errors in the counting of the preamble bits and the leadout bits. - Adds a new bit type used during the leadout to avoid resynchronization at the packet start causing an output glitch. - improves readability of some expressions - Adds a truncated one bit after the cutout to complete the five onebits that are underlaid the cutout. The timing is HW-tunable. This is forced out with a `resync=true` after the output is reenabled. - removes unnecessary interrupt disable from the write path. === * Adds space for railcom cutout even if the DCC driver does not generate it. This is necessary in case a booster is being driven which wants to add the railcom cutout. Also adds support for generating a railcom half-zero even if no cutout is generated. * Adds half-zero feature and adjusts how many extra zero bits are generated. * - Removes unnecessary states at the packet transition. - Moves the generate halfzero feature to be at the beginning of DCC packets instead of at the end of them. - Adds a hack for the resync handling at the beginning of the DCC preamble. - Adjusts the timing for the half-zero to be an upper half of one bit and a lower half of zero. * - Adds separate setting for the PWM timer and the interval timer. - Fixes off by one errors in the counting of the preamble bits and the leadout bits. - Adds a new bit type used during the leadout to avoid resynchronization causing an output glitch. - simplifies some expressions - Adds a truncated one bit after the cutout to complete the five onebits that are underlaid the cutout. The timing is HW-tunable. This is forced out with a resync=true after the output is reenabled. - removes unnecessary interrupt disable from the write path. * Fix whitespace. * Removes commented code. --- src/freertos_drivers/ti/TivaDCC.hxx | 182 +++++++++++++++++----------- 1 file changed, 111 insertions(+), 71 deletions(-) diff --git a/src/freertos_drivers/ti/TivaDCC.hxx b/src/freertos_drivers/ti/TivaDCC.hxx index e84a21b89..6ed3e48b0 100644 --- a/src/freertos_drivers/ti/TivaDCC.hxx +++ b/src/freertos_drivers/ti/TivaDCC.hxx @@ -231,7 +231,9 @@ public: /** Structure for supporting bit timing. */ struct Timing { - /// In clock cycles: period ofthe timers + /// In clock cycles: period of the interval timer + uint32_t interval_period; + /// In clock cycles: period of the PWM timer uint32_t period; /// When to transition output A; must be within the period uint32_t transition_a; @@ -310,11 +312,16 @@ private: static dcc::Packet IDLE_PKT; /// Bit timings that we store and precalculate. - typedef enum { + typedef enum + { /// Zero bit for DCC (this is the longer) DCC_ZERO, /// One bit for DCC (this is the shorter) DCC_ONE, + /// One bit for DCC that we generate at the end of packet + /// transition. This has a stretched negative part so that the next + /// packet resync avoids a glitch in the output. + DCC_EOP_ONE, /// One bit for DCC that we generate during the railcom cutout. Can be /// used to play with alignment of edges coming out of the railcom /// cutout. @@ -336,7 +343,7 @@ private: /// and the the middle of the two windows. RAILCOM_CUTOUT_SECOND, /// This is not a bit, but specifies when to wake up during the railcom - /// cutout. This is used for re-synchronizing the + /// cutout. This is used for re-synchronizing the RAILCOM_CUTOUT_POST, /// Long negative DC pulse to act as a preamble for a Marklin packet. MM_PREAMBLE, @@ -392,6 +399,9 @@ private: // State at the end of packet where we make a decision whether to go // into a railcom cutout or not. DCC_MAYBE_RAILCOM, + // If we did not generate a cutout, we generate 5 empty one bits here + // in case a booster wants to insert a cutout. + DCC_NO_CUTOUT, // Counts 26 usec into the appropriate preamble bit after which to turn // off output power. DCC_CUTOUT_PRE, @@ -400,13 +410,9 @@ private: // Point between railcom window 1 and railcom window 2 where we have to // read whatever arrived for channel1. DCC_MIDDLE_RAILCOM_CUTOUT, - // Railcom end-of-channel2 window. Reads out the UART values. + // Railcom end-of-channel2 window. Reads out the UART values, and + // enables output power. DCC_STOP_RAILCOM_RECEIVE, - // End of railcom cutout. Reenables output power. - DCC_ENABLE_AFTER_RAILCOM, - // A few one bits after the DCC packet is over in case we didn't go - // into railcom cutout. Form here we go into getting the next packet. - DCC_LEADOUT, // Same for marklin. A bit of negative voltage after the packet is over // but before loading the next packet. This ensures that old marklin // decoders confirm receiving the packet correctly before we go into a @@ -426,9 +432,12 @@ private: * @param transition_usec is the time of the transition inside the bit, * counted from the beginning of the bit (i.e. the length of the HIGH part * of the period). Can be zero for DC output LOW or can be == period_usec - * for DC output HIGH. */ + * for DC output HIGH. + * @param interval_period_usec tells when the interval timer should expire + * (next interrupt). Most of the time this should be the same as + * period_usec.*/ void fill_timing(BitEnum ofs, uint32_t period_usec, - uint32_t transition_usec); + uint32_t transition_usec, uint32_t interval_period_usec); /// Checks each output and enables those that need to be on. void check_and_enable_outputs() @@ -501,25 +510,37 @@ inline void TivaDCC::interrupt_handler() { default: case RESYNC: - current_bit = DCC_ONE; if (packet->packet_header.is_marklin) { + current_bit = MM_PREAMBLE; state_ = ST_MM_PREAMBLE; + break; } else { state_ = PREAMBLE; } - break; + // fall through case PREAMBLE: { current_bit = DCC_ONE; - int preamble_needed = HW::dcc_preamble_count(); + // preamble zero is output twice due to the resync, so we deduct + // one from the count. + int preamble_needed = HW::dcc_preamble_count() - 1; + if (HW::generate_railcom_halfzero() && + !packet->packet_header.send_long_preamble) + { + if (preamble_count == 0) + { + current_bit = DCC_RC_HALF_ZERO; + } + preamble_needed++; + } if (packet->packet_header.send_long_preamble) { preamble_needed = 21; } - if (++preamble_count == preamble_needed) + if (++preamble_count >= preamble_needed) { state_ = START; preamble_count = 0; @@ -539,9 +560,12 @@ inline void TivaDCC::interrupt_handler() case DATA_5: case DATA_6: case DATA_7: - current_bit = static_cast(DCC_ZERO + ((packet->payload[count] >> (DATA_7 - state_)) & 0x01)); + { + uint8_t bit = (packet->payload[count] >> (DATA_7 - state_)) & 0x01; + current_bit = static_cast(DCC_ZERO + bit); state_ = static_cast(static_cast(state_) + 1); break; + } case FRAME: if (++count >= packet->dlc) { @@ -566,19 +590,29 @@ inline void TivaDCC::interrupt_handler() // transition of the output. // We change the time of the next IRQ. MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_PRE].period); + timings[RAILCOM_CUTOUT_PRE].interval_period); state_ = DCC_CUTOUT_PRE; } else { railcomDriver_->no_cutout(); current_bit = DCC_ONE; - state_ = DCC_LEADOUT; + state_ = DCC_NO_CUTOUT; } break; - case DCC_LEADOUT: + case DCC_NO_CUTOUT: current_bit = DCC_ONE; - if (++preamble_count >= 2) { + ++preamble_count; + // maybe railcom already sent one extra ONE bit after the + // end-of-packet one bit. We need four more. + if (preamble_count >= 4) + { + current_bit = DCC_EOP_ONE; + } + if (preamble_count >= 5) + { + // The last bit will be removed by the next packet's beginning + // sync. get_next_packet = true; } break; @@ -588,7 +622,7 @@ inline void TivaDCC::interrupt_handler() // the output. // We change the time of the next IRQ. MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_FIRST].period); + timings[RAILCOM_CUTOUT_FIRST].interval_period); state_ = DCC_START_RAILCOM_RECEIVE; break; case DCC_START_RAILCOM_RECEIVE: @@ -648,7 +682,7 @@ inline void TivaDCC::interrupt_handler() // Set up for next wakeup. current_bit = DCC_RC_ONE; MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_SECOND].period); + timings[RAILCOM_CUTOUT_SECOND].interval_period); state_ = DCC_MIDDLE_RAILCOM_CUTOUT; break; } @@ -656,24 +690,16 @@ inline void TivaDCC::interrupt_handler() railcomDriver_->middle_cutout(); current_bit = DCC_RC_ONE; MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_POST].period); - state_ = DCC_STOP_RAILCOM_RECEIVE; + timings[RAILCOM_CUTOUT_POST].interval_period); + state_ = DCC_STOP_RAILCOM_RECEIVE; break; case DCC_STOP_RAILCOM_RECEIVE: { - if (HW::generate_railcom_halfzero()) - { - current_bit = DCC_RC_HALF_ZERO; - } - else - { - current_bit = DCC_RC_ONE; - } + current_bit = RAILCOM_CUTOUT_POST; // This causes the timers to be reinitialized so no fractional bits // are left in their counters. resync = true; get_next_packet = true; - state_ = DCC_ENABLE_AFTER_RAILCOM; railcomDriver_->end_cutout(); unsigned delay = 0; if (HW::Output1::isRailcomCutoutActive_) @@ -714,11 +740,6 @@ inline void TivaDCC::interrupt_handler() check_and_enable_outputs(); break; } - case DCC_ENABLE_AFTER_RAILCOM: - current_bit = DCC_ONE; - state_ = DCC_LEADOUT; - ++preamble_count; - break; case ST_MM_PREAMBLE: current_bit = MM_PREAMBLE; ++preamble_count; @@ -746,24 +767,29 @@ inline void TivaDCC::interrupt_handler() case MM_DATA_4: case MM_DATA_5: case MM_DATA_6: - current_bit = static_cast( - MM_ZERO + - ((packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01)); + { + uint8_t bit = + (packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01; + current_bit = static_cast(MM_ZERO + bit); state_ = static_cast(static_cast(state_) + 1); break; + } case MM_DATA_7: - current_bit = static_cast( - MM_ZERO + - ((packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01)); + { + uint8_t bit = + (packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01; + current_bit = static_cast(MM_ZERO + bit); ++count; if (count == 3) { state_ = ST_MM_PREAMBLE; } else if (count >= packet->dlc) { + preamble_count = 0; state_ = MM_LEADOUT; } else { state_ = MM_DATA_0; } break; + } case POWER_IMM_TURNON: current_bit = DCC_ONE; packet_repeat_count = 0; @@ -788,7 +814,7 @@ inline void TivaDCC::interrupt_handler() // These have to happen very fast because syncing depends on it. We do // direct register writes here instead of using the plib calls. HWREG(HW::INTERVAL_BASE + TIMER_O_TAILR) = - timing->period + hDeadbandDelay_ * 2; + timing->interval_period + hDeadbandDelay_ * 2; if (!HW::H_DEADBAND_DELAY_NSEC) { @@ -800,7 +826,8 @@ inline void TivaDCC::interrupt_handler() MAP_TimerEnable(HW::CCP_BASE, TIMER_A|TIMER_B); MAP_TimerDisable(HW::INTERVAL_BASE, TIMER_A); - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timing->period); + MAP_TimerLoadSet( + HW::INTERVAL_BASE, TIMER_A, timing->interval_period); MAP_TimerEnable(HW::INTERVAL_BASE, TIMER_A); // Switches back to asynch timer update. @@ -838,14 +865,22 @@ inline void TivaDCC::interrupt_handler() // Sets final values for the cycle. MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timing->period); MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a); - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timing->period); + MAP_TimerLoadSet( + HW::INTERVAL_BASE, TIMER_A, timing->interval_period); } last_bit = current_bit; - } else if (last_bit != current_bit) + if (current_bit == DCC_RC_HALF_ZERO) + { + // After resync the same bit is output twice. We don't want that + // with the half-zero, so we preload the DCC preamble bit. + current_bit = DCC_ONE; + } + } + if (last_bit != current_bit) { auto* timing = &timings[current_bit]; - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timing->period); + MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timing->interval_period); MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timing->period); MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, timing->period); MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a); @@ -908,12 +943,13 @@ static uint32_t nsec_to_clocks(uint32_t nsec) { return ((configCPU_CLOCK_HZ / 1000000) * nsec) / 1000; } -template +template void TivaDCC::fill_timing(BitEnum ofs, uint32_t period_usec, - uint32_t transition_usec) + uint32_t transition_usec, uint32_t interval_usec) { auto* timing = &timings[ofs]; timing->period = usec_to_clocks(period_usec); + timing->interval_period = usec_to_clocks(interval_usec); if (transition_usec == 0) { // DC voltage negative. timing->transition_a = timing->transition_b = timing->period; @@ -946,39 +982,48 @@ TivaDCC::TivaDCC(const char *name, RailcomDriver *railcom_driver) { state_ = PREAMBLE; - fill_timing(DCC_ZERO, 105<<1, 105); - fill_timing(DCC_ONE, 56<<1, 56); + fill_timing(DCC_ZERO, 105 << 1, 105, 105 << 1); + fill_timing(DCC_ONE, 56 << 1, 56, 56 << 1); /// @todo tune this bit to line up with the bit stream starting after the /// railcom cutout. - fill_timing(DCC_RC_ONE, 56<<1, 56); + fill_timing(DCC_RC_ONE, 57 << 1, 57, 57 << 1); // A small pulse in one direction then a half zero bit in the other // direction. - fill_timing(DCC_RC_HALF_ZERO, 100 + 15, 15); - fill_timing(MM_ZERO, 208, 26); - fill_timing(MM_ONE, 208, 182); + fill_timing(DCC_RC_HALF_ZERO, 100 + 56, 56, 100 + 56); + // At the end of the packet we stretch the negative side but let the + // interval timer kick in on time. The next bit will resync, and this + // avoids a glitch output to the track when a marklin preamble is coming. + fill_timing(DCC_EOP_ONE, (56 << 1) + 20, 58, 56 << 1); + fill_timing(MM_ZERO, 208, 26, 208); + fill_timing(MM_ONE, 208, 182, 208); // Motorola preamble is negative DC signal. - fill_timing(MM_PREAMBLE, 208, 0); + fill_timing(MM_PREAMBLE, 208, 0, 208); unsigned h_deadband = 2 * (HW::H_DEADBAND_DELAY_NSEC / 1000); unsigned railcom_part = 0; unsigned target = RAILCOM_CUTOUT_START_USEC + HW::RAILCOM_CUTOUT_START_DELTA_USEC; - fill_timing(RAILCOM_CUTOUT_PRE, target - railcom_part, 0); + fill_timing(RAILCOM_CUTOUT_PRE, 56 << 1, 56, target - railcom_part); railcom_part = target; target = RAILCOM_CUTOUT_MID_USEC + HW::RAILCOM_CUTOUT_MID_DELTA_USEC; - fill_timing(RAILCOM_CUTOUT_FIRST, target - railcom_part, 0); + fill_timing(RAILCOM_CUTOUT_FIRST, 56 << 1, 56, target - railcom_part); railcom_part = target; target = RAILCOM_CUTOUT_END_USEC + HW::RAILCOM_CUTOUT_END_DELTA_USEC; - fill_timing(RAILCOM_CUTOUT_SECOND, target - railcom_part, 0); + fill_timing(RAILCOM_CUTOUT_SECOND, 56 << 1, 56, target - railcom_part); railcom_part = target; - static_assert(5 * 56 * 2 > + static_assert((5 * 56 * 2 - 56 + HW::RAILCOM_CUTOUT_POST_DELTA_USEC) > RAILCOM_CUTOUT_END_USEC + HW::RAILCOM_CUTOUT_END_DELTA_USEC, "railcom cutout too long"); - // remaining time until 5 one bits are complete. - fill_timing(RAILCOM_CUTOUT_POST, 5*56*2 - railcom_part + h_deadband, 0); + target = 5 * 56 * 2 - 56 + HW::RAILCOM_CUTOUT_POST_DELTA_USEC; + unsigned remaining_high = target - railcom_part; + // remaining time until 5 one bits are complete. For the PWM timer we have + // some fraction of the high part, then a full low side, then we stretch + // the low side to avoid the packet transition glitch. + fill_timing(RAILCOM_CUTOUT_POST, remaining_high + 56 + 20, remaining_high, + remaining_high + 56 + h_deadband); // We need to disable the timers before making changes to the config. MAP_TimerDisable(HW::CCP_BASE, TIMER_A); @@ -1031,7 +1076,8 @@ TivaDCC::TivaDCC(const char *name, RailcomDriver *railcom_driver) #endif MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, timings[DCC_ONE].period); - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timings[DCC_ONE].period); + MAP_TimerLoadSet( + HW::INTERVAL_BASE, TIMER_A, timings[DCC_ONE].interval_period); MAP_IntEnable(HW::INTERVAL_INTERRUPT); // The OS interrupt does not come from the hardware timer. @@ -1069,14 +1115,9 @@ ssize_t TivaDCC::write(File *file, const void *buf, size_t count) } OSMutexLock l(&lock_); - // TODO(balazs.racz) this interrupt disable is not actually needed. Writing - // to the back of the queue should be okay while the interrupt reads from - // the front of it. - MAP_TimerIntDisable(HW::INTERVAL_BASE, TIMER_TIMA_TIMEOUT); if (packetQueue_.full()) { - MAP_TimerIntEnable(HW::INTERVAL_BASE, TIMER_TIMA_TIMEOUT); return -ENOSPC; } @@ -1103,7 +1144,6 @@ ssize_t TivaDCC::write(File *file, const void *buf, size_t count) HW::flip_led(); } - MAP_TimerIntEnable(HW::INTERVAL_BASE, TIMER_TIMA_TIMEOUT); return count; } From c8f5c57bcfa8bb7720eca50cde6e3667f412abaf Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 6 Jan 2021 17:39:27 +0100 Subject: [PATCH 158/171] Adds support for a random jitter in the DCC signal timing. (#505) - Adds support for a random jitter in the DCC signal timing. This help reducing EMI of the system. - reduces nominal size of zero bit to 100 usec to save on bandwidth. === * Adds support for a random jitter in the DCC signal timing. * Adds documentation for EMC spectrum spreading. --- src/freertos_drivers/ti/TivaDCC.hxx | 101 +++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 15 deletions(-) diff --git a/src/freertos_drivers/ti/TivaDCC.hxx b/src/freertos_drivers/ti/TivaDCC.hxx index 6ed3e48b0..1bd9637c3 100644 --- a/src/freertos_drivers/ti/TivaDCC.hxx +++ b/src/freertos_drivers/ti/TivaDCC.hxx @@ -71,6 +71,10 @@ #include "dcc/RailCom.hxx" #include "executor/Notifiable.hxx" +/// If non-zero, enables the jitter feature to spread the EMC spectrum of DCC +/// signal +extern "C" uint8_t spreadSpectrum; + /// This structure is safe to use from an interrupt context and a regular /// context at the same time, provided that /// @@ -202,6 +206,30 @@ private: * * The application can request notification of readable and writable status * using the regular IOCTL method. + * + * + * EMC spectrum spreading + * + * There is an optional feature that helps with passing EMC certification for + * systems that are built on this driver. The observation is that if the + * output signal has may repeats of a certain period, then in the measured + * spectrum there will be a big spike in energy that might exceed the + * thresholds for compliance. However, by slightly varying the timing of the + * output signal, the energy will be spread across a wider spectrum, thus the + * peak of emission will be smaller. + * + * This feature is enabled by `extern uint8_t spreadSpectrum;`. This can come + * from a constant or configuration dependent variable. If enabled, then the + * timing of DCC zero bits are stretched to be a random value between 100.5 + * and 105 usec each half; the timing of DCC one bits will be stretched from + * 56.5 to 60 usec per half. The symmetry within each bit is still perfectly + * matched. Marklin-Motorola packets get up to 2 usec of stretching on each + * phase. + * + * The actual stretching is generated using a uniform random number generator + * within said limits to ensure we spread uniformly across the available + * timings. Up to four bits are output with the same timing, then a new random + * timing is generated. */ template class TivaDCC : public Node @@ -239,6 +267,12 @@ public: uint32_t transition_a; /// When to transition output B; must be within the period uint32_t transition_b; + /// How many ticks (minimum) we can add to the period and transition for + /// spectrum spreading. + uint16_t spread_min = 0; + /// How many ticks (maximum) we can add to the period and transition for + /// spectrum spreading. + uint16_t spread_max = 0; }; /* WARNING: these functions (hw_init, enable_output, disable_output) MUST @@ -435,9 +469,13 @@ private: * for DC output HIGH. * @param interval_period_usec tells when the interval timer should expire * (next interrupt). Most of the time this should be the same as - * period_usec.*/ + * period_usec. + * @param timing_spread_usec if non-zero, allows the high and low of the + * timing to be stretched by at most this many usec. + */ void fill_timing(BitEnum ofs, uint32_t period_usec, - uint32_t transition_usec, uint32_t interval_period_usec); + uint32_t transition_usec, uint32_t interval_period_usec, + uint32_t timing_spread_usec = 0); /// Checks each output and enables those that need to be on. void check_and_enable_outputs() @@ -480,7 +518,14 @@ private: FixedQueue packetQueue_; Notifiable* writableNotifiable_; /**< Notify this when we have free buffers. */ RailcomDriver* railcomDriver_; /**< Will be notified for railcom cutout events. */ - + /// Seed for a pseudorandom sequence. + unsigned seed_ = 0xb7a11bae; + /// Parameters for a linear RNG: modulus + static constexpr unsigned PMOD = 65213; + /// Parameters for a linear RNG: multiplier + static constexpr unsigned PMUL = 52253; + /// Parameters for a linear RNG: additive + static constexpr unsigned PADD = 42767; /** Default constructor. */ TivaDCC(); @@ -499,6 +544,7 @@ inline void TivaDCC::interrupt_handler() static BitEnum last_bit = DCC_ONE; static int count = 0; static int packet_repeat_count = 0; + static int bit_repeat_count = 0; static const dcc::Packet *packet = &IDLE_PKT; static bool resync = true; BitEnum current_bit; @@ -877,15 +923,36 @@ inline void TivaDCC::interrupt_handler() current_bit = DCC_ONE; } } + if (bit_repeat_count >= 4) + { + // Forces reinstalling the timing. + last_bit = NUM_TIMINGS; + } if (last_bit != current_bit) { auto* timing = &timings[current_bit]; - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timing->interval_period); - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timing->period); - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, timing->period); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_B, timing->transition_b); + // The delta in ticks we add to each side of the signal. + uint32_t spread = 0; + if (spreadSpectrum) + { + spread = timing->spread_max - timing->spread_min; + seed_ *= PMUL; + seed_ += PADD; + seed_ %= PMOD; + spread = (seed_ % spread) + timing->spread_min; + } + MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, + timing->interval_period + (spread << 1)); + MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timing->period + (spread << 1)); + MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, timing->period + (spread << 1)); + MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a + spread); + MAP_TimerMatchSet(HW::CCP_BASE, TIMER_B, timing->transition_b + spread); last_bit = current_bit; + bit_repeat_count = 0; + } + else + { + bit_repeat_count++; } if (get_next_packet) @@ -945,7 +1012,7 @@ static uint32_t nsec_to_clocks(uint32_t nsec) { template void TivaDCC::fill_timing(BitEnum ofs, uint32_t period_usec, - uint32_t transition_usec, uint32_t interval_usec) + uint32_t transition_usec, uint32_t interval_usec, uint32_t spread_max) { auto* timing = &timings[ofs]; timing->period = usec_to_clocks(period_usec); @@ -965,9 +1032,13 @@ void TivaDCC::fill_timing(BitEnum ofs, uint32_t period_usec, timing->transition_b = nominal_transition - (hDeadbandDelay_ + lDeadbandDelay_) / 2; } + if (spread_max > 0) + { + timing->spread_min = usec_to_clocks(1) / 2; + timing->spread_max = usec_to_clocks(spread_max); + } } - template dcc::Packet TivaDCC::IDLE_PKT = dcc::Packet::DCC_IDLE(); @@ -982,20 +1053,20 @@ TivaDCC::TivaDCC(const char *name, RailcomDriver *railcom_driver) { state_ = PREAMBLE; - fill_timing(DCC_ZERO, 105 << 1, 105, 105 << 1); - fill_timing(DCC_ONE, 56 << 1, 56, 56 << 1); + fill_timing(DCC_ZERO, 100 << 1, 100, 100 << 1, 5); + fill_timing(DCC_ONE, 56 << 1, 56, 56 << 1, 4); /// @todo tune this bit to line up with the bit stream starting after the /// railcom cutout. fill_timing(DCC_RC_ONE, 57 << 1, 57, 57 << 1); // A small pulse in one direction then a half zero bit in the other // direction. - fill_timing(DCC_RC_HALF_ZERO, 100 + 56, 56, 100 + 56); + fill_timing(DCC_RC_HALF_ZERO, 100 + 56, 56, 100 + 56, 5); // At the end of the packet we stretch the negative side but let the // interval timer kick in on time. The next bit will resync, and this // avoids a glitch output to the track when a marklin preamble is coming. fill_timing(DCC_EOP_ONE, (56 << 1) + 20, 58, 56 << 1); - fill_timing(MM_ZERO, 208, 26, 208); - fill_timing(MM_ONE, 208, 182, 208); + fill_timing(MM_ZERO, 208, 26, 208, 2); + fill_timing(MM_ONE, 208, 182, 208, 2); // Motorola preamble is negative DC signal. fill_timing(MM_PREAMBLE, 208, 0, 208); From 641c330be0b7db24968c6cb08e97fa1d25058b79 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sun, 10 Jan 2021 20:19:16 -0600 Subject: [PATCH 159/171] Limit Timer (#506) * Add limit timer. * Add some comments. * Add a check for non-null callback. * Add destructor. * Add tests. * Add more tests. * Fix review comments. --- src/utils/LimitTimer.cxxtest | 136 +++++++++++++++++++++++++++++++++++ src/utils/LimitTimer.hxx | 135 ++++++++++++++++++++++++++++++++++ 2 files changed, 271 insertions(+) create mode 100644 src/utils/LimitTimer.cxxtest create mode 100644 src/utils/LimitTimer.hxx diff --git a/src/utils/LimitTimer.cxxtest b/src/utils/LimitTimer.cxxtest new file mode 100644 index 000000000..4e1e9ae1f --- /dev/null +++ b/src/utils/LimitTimer.cxxtest @@ -0,0 +1,136 @@ +/** \copyright + * Copyright (c) 2021, Stuart Baker + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @file LimitTimer.cxxtest + * + * Unit tests for LimitTimer. + * + * @author Stuart Baker + * @date 10 January 2021 + */ + +#include "utils/test_main.hxx" + +#include "utils/LimitTimer.hxx" +#include "os/FakeClock.hxx" + +class MockCallback +{ +public: + MOCK_METHOD0(callback, void()); +}; + +class MyLimitTimer +{ +public: + /// Constructor. + /// @param update_delay_msec cooldown time delay in milliseconds + /// @param max_tokens number of available tokens + MyLimitTimer(uint16_t update_delay_msec, + uint8_t max_tokens) + : mockCallback_() + , limitTimer_(&g_executor, update_delay_msec, max_tokens, + std::bind(&MockCallback::callback, &mockCallback_)) + { + } + + /// Destructor. + ~MyLimitTimer() + { + wait_for_main_executor(); + while (!g_executor.active_timers()->empty()) + { + sleep_helper(20); + } + } + + /// Helper function for sleeping. + /// @param clk fake clock or nullptr if no fake clock exists + /// @param len_msec how long to sleep + /// @param step_msec what granularity to use for sleeping wiht fake clock. + void sleep_helper(unsigned len_msec, unsigned step_msec = 50) + { + for (unsigned i = 0; i < len_msec; i += step_msec) + { + clk_.advance(MSEC_TO_NSEC(step_msec)); + wait_for_main_executor(); + } + } + + FakeClock clk_; + + ::testing::StrictMock mockCallback_; + LimitTimer limitTimer_; +}; + +TEST(LimitTimerTest, Create) +{ + MyLimitTimer(200, 3); +} + +TEST(LimitTimerTest, TryTake) +{ + MyLimitTimer lt(200, 3); + + ::testing::MockFunction check; + + ::testing::InSequence s; + + // flush out the tokens + EXPECT_CALL(lt.mockCallback_, callback()).Times(0); + EXPECT_TRUE(lt.limitTimer_.try_take()); + EXPECT_TRUE(lt.limitTimer_.try_take()); + EXPECT_TRUE(lt.limitTimer_.try_take()); + EXPECT_CALL(check, Call("1")); + check.Call("1"); + + // try to pull one more token out (that is not available) + EXPECT_CALL(lt.mockCallback_, callback()).Times(0); + EXPECT_FALSE(lt.limitTimer_.try_take()); + EXPECT_CALL(check, Call("2")); + check.Call("2"); + + // verify the callback after timeout + EXPECT_CALL(lt.mockCallback_, callback()).Times(1); + EXPECT_CALL(check, Call("3")); + lt.sleep_helper(200); + check.Call("3"); + + // timer should still be running as bucket gets refilled + lt.sleep_helper(200); + EXPECT_FALSE(g_executor.active_timers()->empty()); + + // pull a token from the bucket + lt.limitTimer_.take_no_callback(); + + // timer should still be running as bucket gets refilled + lt.sleep_helper(200); + EXPECT_FALSE(g_executor.active_timers()->empty()); + + // timer should stop running once bucket gets refilled + lt.sleep_helper(200); + EXPECT_TRUE(g_executor.active_timers()->empty()); +} diff --git a/src/utils/LimitTimer.hxx b/src/utils/LimitTimer.hxx new file mode 100644 index 000000000..d2a1cf83f --- /dev/null +++ b/src/utils/LimitTimer.hxx @@ -0,0 +1,135 @@ +/** @copyright + * Copyright (c) 2020, Balazs Racz; 2021 Stuart Baker + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @file LimitTimer.hxx + * + * Limits the number of updates per unit time. Initial version written by + * Balazs Racz, modified for generalization by Stuart Baker + * + * @author Balazs Racz, Stuart Baker + * @date 9 January 2021 + */ + +#include "executor/Timer.hxx" + +/// This timer takes care of limiting the number of speed updates we send +/// out in a second. It is a token bucket filter. +class LimitTimer : public Timer +{ +public: + /// Constructor. + /// @param ex executor to run on + /// @param update_delay_msec cooldown time delay in milliseconds + /// @param max_tokens number of available tokens, <= 127 max + /// @param callback callback called once after cooldown time delay + LimitTimer(ExecutorBase *ex, uint16_t update_delay_msec, uint8_t max_tokens, + std::function callback) + : Timer(ex->active_timers()) + , updateDelayMsec_(update_delay_msec) + , bucket_(max_tokens > 127 ? 127 : max_tokens) + , bucketMax_(max_tokens) + , needUpdate_(false) + , callback_(callback) + { + HASSERT(callback); + } + + /// Destructor. + ~LimitTimer() + { + cancel(); + } + + /// Attempts to take a token out of the bucket. Must be called from the + /// same executor that was passed in the object construction. + /// @return true if the take is successful, false if there are no available + /// tokens, in which case there will be a callback generated when + /// tokens become available. + bool try_take() + { + if (bucket_ == bucketMax_) + { + start(MSEC_TO_NSEC(updateDelayMsec_)); + } + if (bucket_ > 0) + { + --bucket_; + return true; + } + else + { + needUpdate_ = true; + return false; + } + } + + /// Takes one entry from the bucket, and does not give a callback if + /// there is no entry left. Must be called from the + /// same executor that was passed in the object construction. + void take_no_callback() + { + if (bucket_ > 0) + { + --bucket_; + } + } + +private: + /// Callback from the timer infrastructure. Called periodically. + long long timeout() override + { + ++bucket_; + if (needUpdate_) + { + callback_(); + needUpdate_ = false; + } + if (bucket_ >= bucketMax_) + { + return NONE; + } + else + { + return RESTART; + } + } + + /// cooldown delay in msec + uint16_t updateDelayMsec_; + + /// number of available tokens + uint8_t bucket_ ; + + /// maximum number of tokens in the bucket + uint8_t bucketMax_ : 7; + + /// if non-zero, wake up parent when token is available. + uint8_t needUpdate_ : 1; + + /// callback after cooldown period. + std::function callback_; +}; + From c8d37bff1b1c7d6ab0c6240cd6a38b316eede102 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 16 Jan 2021 18:55:06 +0100 Subject: [PATCH 160/171] Adds ability to remove an event handler from the callbacks. (#507) * Adds ability to remove an event handler from the callbacks. Minor fixes: - adds ability to unregister event handlers by user_arg value and mask - fixes todos around missing locking - removes a crashing branch that makes not a lot of sense * fix whitespace * Adds test for the new functionality. --- src/openlcb/CallbackEventHandler.hxx | 17 +++++++- src/openlcb/EventHandler.hxx | 12 +++++- src/openlcb/EventHandlerContainer.cxx | 24 ++++++----- src/openlcb/EventHandlerContainer.cxxtest | 26 ++++++++++-- src/openlcb/EventHandlerContainer.hxx | 51 +++++++++++------------ 5 files changed, 87 insertions(+), 43 deletions(-) diff --git a/src/openlcb/CallbackEventHandler.hxx b/src/openlcb/CallbackEventHandler.hxx index 051710933..e9e748a77 100644 --- a/src/openlcb/CallbackEventHandler.hxx +++ b/src/openlcb/CallbackEventHandler.hxx @@ -101,7 +101,7 @@ public: ~CallbackEventHandler() { - EventRegistry::instance()->unregister_handler(this); + remove_all_entries(); } /// Registers this event handler for a given event ID in the global event @@ -121,6 +121,21 @@ public: EventRegistryEntry(this, event, entry_bits), 0); } + /// Removes the registration of every single entry added so far. + void remove_all_entries() + { + EventRegistry::instance()->unregister_handler(this); + } + + /// Removes the registration of entries added before with a given user_arg + /// value. + /// @param user_arg argument to match on. + void remove_entry(uint32_t entry_bits) + { + EventRegistry::instance()->unregister_handler( + this, entry_bits, 0xFFFFFFFFu); + } + /// @return the node pointer for which this handler is exported. Node *node() { diff --git a/src/openlcb/EventHandler.hxx b/src/openlcb/EventHandler.hxx index 103749424..df75e7855 100644 --- a/src/openlcb/EventHandler.hxx +++ b/src/openlcb/EventHandler.hxx @@ -272,7 +272,17 @@ public: virtual void register_handler(const EventRegistryEntry &entry, unsigned mask) = 0; /// Removes all registered instances of a given event handler pointer. - virtual void unregister_handler(EventHandler *handler) = 0; + /// @param handler the handler for which to unregister entries + /// @param user_arg values of the 32-bit user arg to remove + /// @param user_arg_mask 32-bit mask where to verify user_arg being equal + virtual void unregister_handler(EventHandler *handler, + uint32_t user_arg = 0, uint32_t user_arg_mask = 0) = 0; + + /// Prepares storage for adding many event handlers. + /// @param count how many empty slots to reserve. + virtual void reserve(size_t count) + { + } /// Creates a new event iterator. Caller takes ownership of object. virtual EventIterator *create_iterator() = 0; diff --git a/src/openlcb/EventHandlerContainer.cxx b/src/openlcb/EventHandlerContainer.cxx index e0310c22c..65126afc6 100644 --- a/src/openlcb/EventHandlerContainer.cxx +++ b/src/openlcb/EventHandlerContainer.cxx @@ -48,31 +48,33 @@ void TreeEventHandlers::register_handler(const EventRegistryEntry &entry, handlers_[mask].insert(EventRegistryEntry(entry)); } -void TreeEventHandlers::unregister_handler(EventHandler *handler) +void TreeEventHandlers::unregister_handler( + EventHandler *handler, uint32_t user_arg, uint32_t user_arg_mask) { AtomicHolder h(this); set_dirty(); LOG(VERBOSE, "%p: unregister %p", this, handler); - bool found = false; for (auto r = handlers_.begin(); r != handlers_.end(); ++r) { auto begin_it = r->second.begin(); auto end_it = r->second.end(); - auto erase_it = std::remove_if( - begin_it, end_it, [handler](const EventRegistryEntry ®) { - return reg.handler == handler; + auto erase_it = std::remove_if(begin_it, end_it, + [handler, user_arg, user_arg_mask](const EventRegistryEntry &e) { + return e.handler == handler && + ((e.user_arg & user_arg_mask) == + (user_arg & user_arg_mask)); }); if (erase_it != end_it) { r->second.erase(erase_it, end_it); - found = true; } } - if (found) - { - return; - } - DIE("tried to unregister a handler that was not registered"); +} + +void TreeEventHandlers::reserve(size_t count) +{ + AtomicHolder h(this); + handlers_[0].reserve(handlers_[0].size() + count); } /// Class representing the iteration state on the binary tree-based event diff --git a/src/openlcb/EventHandlerContainer.cxxtest b/src/openlcb/EventHandlerContainer.cxxtest index 604e507c6..f9d525549 100644 --- a/src/openlcb/EventHandlerContainer.cxxtest +++ b/src/openlcb/EventHandlerContainer.cxxtest @@ -313,7 +313,7 @@ public: } vector get_all_matching(uint64_t event, - uint64_t mask = 1) + uint64_t mask = 0) { report_.event = event; report_.mask = mask; @@ -332,9 +332,9 @@ public: return reinterpret_cast(0x100 + n); } - void add_handler(int n, uint64_t eventid, unsigned mask) + void add_handler(int n, uint64_t eventid, unsigned mask, uint32_t arg = 0) { - handlers_.register_handler(EventRegistryEntry(h(n), eventid), mask); + handlers_.register_handler(EventRegistryEntry(h(n), eventid, arg), mask); } protected: @@ -369,6 +369,26 @@ TEST_F(TreeEventHandlerTest, SingleLookup) EXPECT_THAT(get_all_matching(0x103FF, 0), ElementsAre()); } +TEST_F(TreeEventHandlerTest, RemoveByMask) +{ + handlers_.reserve(3); + + add_handler(1, 0x3FF, 0, 0xB); + add_handler(1, 0x3FE, 0, 7); + add_handler(1, 0x3FD, 0, 0xFB); + EXPECT_THAT(get_all_matching(0x3F0, 0xF), ElementsAre(h(1),h(1),h(1))); + EXPECT_THAT(get_all_matching(0x3FF), ElementsAre(h(1))); + EXPECT_THAT(get_all_matching(0x3FE), ElementsAre(h(1))); + EXPECT_THAT(get_all_matching(0x3FD), ElementsAre(h(1))); + + handlers_.unregister_handler(h(1), 0xB, 0xF); + + EXPECT_THAT(get_all_matching(0x3F0, 0xF), ElementsAre(h(1))); + EXPECT_THAT(get_all_matching(0x3FF), ElementsAre()); + EXPECT_THAT(get_all_matching(0x3FE), ElementsAre(h(1))); + EXPECT_THAT(get_all_matching(0x3FD), ElementsAre()); +} + TEST_F(TreeEventHandlerTest, MultiLookup) { add_handler(1, 0x3FF, 0); diff --git a/src/openlcb/EventHandlerContainer.hxx b/src/openlcb/EventHandlerContainer.hxx index 6dafb2c8e..192ce259e 100644 --- a/src/openlcb/EventHandlerContainer.hxx +++ b/src/openlcb/EventHandlerContainer.hxx @@ -109,8 +109,9 @@ private: /// EventRegistry implementation that keeps all event handlers in a vector and /// forwards every single call to each event handler. -class VectorEventHandlers : public EventRegistry { - public: +class VectorEventHandlers : public EventRegistry, private Atomic +{ +public: VectorEventHandlers() {} // Creates a new event iterator. Caller takes ownership of object. @@ -118,30 +119,24 @@ class VectorEventHandlers : public EventRegistry { return new FullContainerIterator(&handlers_); } - void register_handler(const EventRegistryEntry& entry, unsigned mask) OVERRIDE { - // @TODO(balazs.racz): need some kind of locking here. - handlers_.push_front(entry); - set_dirty(); - } - void unregister_handler(EventHandler *handler) OVERRIDE - { - // @TODO(balazs.racz): need some kind of locking here. - struct HandlerEquals - { - HandlerEquals(EventHandler *h) : h_(h) - { - } - bool operator()(const EventRegistryEntry &e) - { - return e.handler == h_; - } - - private: - EventHandler *h_; - } predicate(handler); - handlers_.remove_if(predicate); - set_dirty(); - } + void register_handler( + const EventRegistryEntry &entry, unsigned mask) OVERRIDE + { + AtomicHolder h(this); + handlers_.push_front(entry); + set_dirty(); + } + void unregister_handler(EventHandler *handler, uint32_t user_arg = 0, + uint32_t user_arg_mask = 0) OVERRIDE + { + AtomicHolder h(this); + handlers_.remove_if([handler, user_arg, user_arg_mask]( + const EventRegistryEntry &e) { + return e.handler == handler && + ((e.user_arg & user_arg_mask) == (user_arg & user_arg_mask)); + }); + set_dirty(); + } private: typedef std::forward_list HandlersList; @@ -158,7 +153,9 @@ public: EventIterator* create_iterator() OVERRIDE; void register_handler(const EventRegistryEntry &entry, unsigned mask) OVERRIDE; - void unregister_handler(EventHandler* handler) OVERRIDE; + void unregister_handler(EventHandler *handler, uint32_t user_arg = 0, + uint32_t user_arg_mask = 0) OVERRIDE; + void reserve(size_t count) OVERRIDE; private: class Iterator; From 35501ce5890864ca5c69f48e91a55c2a67dad44a Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 24 Jan 2021 23:29:42 +0100 Subject: [PATCH 161/171] Adds a generalized shift operation to the Fixed16 class. (#508) * Adds a generalized shift operation to the Fixed16 class. * Simplifies the code a bit. --- src/utils/Fixed16.cxxtest | 36 ++++++++++++++++++++++++++++ src/utils/Fixed16.hxx | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/src/utils/Fixed16.cxxtest b/src/utils/Fixed16.cxxtest index b46e550a5..5dac6e5ca 100644 --- a/src/utils/Fixed16.cxxtest +++ b/src/utils/Fixed16.cxxtest @@ -213,3 +213,39 @@ TEST(Fixed16Test, SignedZero) // Adding and subtracting does not preserve the sign. EXPECT_TRUE(v1.is_positive()); } + +/// Helper function to test mulpow2 operation. Computes base mulpow2 shift with +/// fixed16 and with double, and verifies that the results is within the given +/// relative precision form each other. +/// @param base left operand of mulpow2 +/// @param shift right operand of mulpow2 +/// @param precision relative precisoin of how close the FIxed16 computation +/// should be to the real result. Example 0.01 for 1% precision. +void mulpow2_test(double base, double shift, double precision) +{ + double result = base * pow(2.0, shift); + string expl = StringPrintf("while computing %lg mulpow2 %lg", base, shift); + SCOPED_TRACE(expl); + Fixed16 fbase {Fixed16::FROM_DOUBLE, base}; + Fixed16 fshift {Fixed16::FROM_DOUBLE, shift}; + fbase.mulpow2(fshift); + EXPECT_NEAR(result, fbase.to_float(), result * precision); +} + +TEST(Fixed16Test, MulPow2) +{ + // General case is within 1% precision. + mulpow2_test(13, 1.385, 0.01); + mulpow2_test(13, -2.442, 0.01); + + // Integer shifts should work very well. + mulpow2_test(1, 4, 0.00001); + mulpow2_test(1.77429, 4, 0.00001); + mulpow2_test(1, -4, 0.00001); + mulpow2_test(1.77429, -4, 0.0001); + + // When the fractional part does not have a lot of binary digits, the + // result should be pretty good too. + mulpow2_test(30481, -12.5, 0.00001); + mulpow2_test(4377, 2.375, 0.0001); +} diff --git a/src/utils/Fixed16.hxx b/src/utils/Fixed16.hxx index 71e0d08fa..0dbeab734 100644 --- a/src/utils/Fixed16.hxx +++ b/src/utils/Fixed16.hxx @@ -132,6 +132,56 @@ public: return round(); } + /// Multiplies *this with pow(2, o). This is effectively a generalized + /// shift operation that works on fractional numbers too. The precision is + /// limited. + /// + /// Modifies *this. + /// @param o number of "bits" to shift with. May be positive or negative. + /// @return *this = *this * pow(2, o); + Fixed16 &mulpow2(Fixed16 o) + { + const Fixed16* coeffs; + uint16_t f; + if (o.is_positive()) + { + // multiplying + value_ <<= o.trunc(); + f = o.frac(); + static constexpr const Fixed16 pown[6] = { + {FROM_DOUBLE, 1.4142135623730951}, // 2^(1/2) + {FROM_DOUBLE, 1.1892071150027210}, // 2^(1/4) + {FROM_DOUBLE, 1.0905077326652577}, // 2^(1/8) + {FROM_DOUBLE, 1.0442737824274138}, // 2^(1/16) + {FROM_DOUBLE, 1.0218971486541166}, // 2^(1/32) + {FROM_DOUBLE, 1.0108892860517005}}; // 2^(1/64) + coeffs = pown; + } + else + { + // dividing + o.negate(); + value_ >>= o.trunc(); + f = o.frac(); + static constexpr const Fixed16 pown[6] = { + {FROM_DOUBLE, 0.7071067811865476}, // 2^(-1/2) + {FROM_DOUBLE, 0.8408964152537145}, // 2^(-1/4) + {FROM_DOUBLE, 0.9170040432046712}, // 2^(-1/8) + {FROM_DOUBLE, 0.9576032806985737}, // 2^(-1/16) + {FROM_DOUBLE, 0.9785720620877001}, // 2^(-1/32) + {FROM_DOUBLE, 0.9892280131939755}}; // 2^(-1/64) + coeffs = pown; + } + for (unsigned idx = 0, bit = 0x8000; idx < 6; ++idx, bit >>= 1) + { + if (f & bit) + { + *this *= coeffs[idx]; + } + } + return *this; + } + /// @return the value rounded to nearest integer. int16_t round() const { int16_t b = (value_ + 0x8000) >> 16; From e24c7a549829304582db913ddead63977496bfbd Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Mon, 25 Jan 2021 11:49:29 -0600 Subject: [PATCH 162/171] limit timer cleanup (#509) * Add include guards. * use std::min --- src/utils/LimitTimer.hxx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils/LimitTimer.hxx b/src/utils/LimitTimer.hxx index d2a1cf83f..57c9ab61f 100644 --- a/src/utils/LimitTimer.hxx +++ b/src/utils/LimitTimer.hxx @@ -33,6 +33,11 @@ * @date 9 January 2021 */ +#ifndef _UTILS_LIMITTIMER_HXX_ +#define _UTILS_LIMITTIMER_HXX_ + +#include + #include "executor/Timer.hxx" /// This timer takes care of limiting the number of speed updates we send @@ -49,7 +54,7 @@ public: std::function callback) : Timer(ex->active_timers()) , updateDelayMsec_(update_delay_msec) - , bucket_(max_tokens > 127 ? 127 : max_tokens) + , bucket_(std::min(static_cast(127), max_tokens)) , bucketMax_(max_tokens) , needUpdate_(false) , callback_(callback) @@ -133,3 +138,4 @@ private: std::function callback_; }; +#endif // _UTILS_LIMITTIMER_HXX_ From 6e0afdc41ae5fb20455097e68b0e72196f74f8cf Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Thu, 28 Jan 2021 20:26:13 -0600 Subject: [PATCH 163/171] Flip the order of operations for resetting the callback flag and calling the actual callback. This allows for rearming the callback from the callback itself. (#510) --- src/utils/LimitTimer.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/LimitTimer.hxx b/src/utils/LimitTimer.hxx index 57c9ab61f..a42ba6f1e 100644 --- a/src/utils/LimitTimer.hxx +++ b/src/utils/LimitTimer.hxx @@ -109,8 +109,8 @@ private: ++bucket_; if (needUpdate_) { - callback_(); needUpdate_ = false; + callback_(); } if (bucket_ >= bucketMax_) { From fc05734f8a3afca429aaf2b863ebccfb1ab435fc Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Mon, 15 Feb 2021 15:16:15 -0600 Subject: [PATCH 164/171] Allow applib.mk SRCDIR to be overridden. (#512) --- etc/applib.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/applib.mk b/etc/applib.mk index fc066e35d..192dd394d 100644 --- a/etc/applib.mk +++ b/etc/applib.mk @@ -3,7 +3,7 @@ ifeq ($(TARGET),) TARGET := $(notdir $(realpath $(CURDIR)/..)) endif BASENAME := $(notdir $(CURDIR)) -SRCDIR = $(abspath ../../../$(BASENAME)) +SRCDIR ?= $(abspath ../../../$(BASENAME)) VPATH = $(SRCDIR) INCLUDES += -I./ -I../ -I../include From 01081b404c19fbf4fdf136299654d5ac6f71a80e Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 23 Feb 2021 23:33:20 +0100 Subject: [PATCH 165/171] Fixes broken build of DCC driver instantiation. (#514) --- boards/st-stm32f103rb-cmsis/Stm32Can.cxx | 6 ++++++ boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/boards/st-stm32f103rb-cmsis/Stm32Can.cxx b/boards/st-stm32f103rb-cmsis/Stm32Can.cxx index f3ab88941..399a626ea 100644 --- a/boards/st-stm32f103rb-cmsis/Stm32Can.cxx +++ b/boards/st-stm32f103rb-cmsis/Stm32Can.cxx @@ -49,6 +49,12 @@ extern "C" { void USB_HP_CAN1_TX_IRQHandler(void); void USB_LP_CAN1_RX0_IRQHandler(void); + +static inline void SetInterruptPriority(uint32_t irq, uint8_t priority) +{ + NVIC_SetPriority((IRQn_Type)irq, priority >> (8U - __NVIC_PRIO_BITS)); +} + } class Stm32CanDriver : public Can diff --git a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx index eb087679f..924c5b34a 100644 --- a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx +++ b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx @@ -202,6 +202,9 @@ const uint32_t RailcomDefs::UART_PERIPH[] = {SYSCTL_PERIPH_UART1}; static TivaRailcomDriver railcom_driver("/dev/railcom"); +// If 1, enabled spread spectrum randomization of the DCC timings. +uint8_t spreadSpectrum = 0; + struct DccHwDefs { /// base address of a capture compare pwm timer pair static const unsigned long CCP_BASE = TIMER0_BASE; @@ -218,6 +221,7 @@ struct DccHwDefs { static const int RAILCOM_CUTOUT_START_DELTA_USEC = -20; static const int RAILCOM_CUTOUT_MID_DELTA_USEC = 0; static const int RAILCOM_CUTOUT_END_DELTA_USEC = -10; + static const int RAILCOM_CUTOUT_POST_DELTA_USEC = -10; /** These timer blocks will be synchronized once per packet, when the * deadband delay is set up. */ From 61e89e7e0278ea63c7573df97b49fc5ad89b9f71 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 28 Feb 2021 23:39:09 +0100 Subject: [PATCH 166/171] Adds a parseblink method to turn a blinker code into a numeric display. (#516) * Adds a parseblink method to turn a blinker code into a numeric display. * Clarify the bit order. --- src/utils/Blinker.cxx | 96 +++++++++++++++++++++++++++++++++++++++ src/utils/Blinker.cxxtest | 14 ++++++ src/utils/blinker.h | 8 ++++ src/utils/sources | 1 + 4 files changed, 119 insertions(+) create mode 100644 src/utils/Blinker.cxx create mode 100644 src/utils/Blinker.cxxtest diff --git a/src/utils/Blinker.cxx b/src/utils/Blinker.cxx new file mode 100644 index 000000000..f094ebb30 --- /dev/null +++ b/src/utils/Blinker.cxx @@ -0,0 +1,96 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Blinker.cxx + * + * Implementations of blinker routines + * + * @author Balazs Racz + * @date 27 Feb 2021 + */ + +#include "utils/blinker.h" + +extern "C" +{ + +unsigned parseblink(uint32_t pattern) +{ + unsigned ret = 0; + if (!pattern) + { + return ret; + } + // Finds the top bit. + uint32_t tb = 1U << 31; + while ((tb & pattern) == 0) + { + tb >>= 1; + } + unsigned nibble_shift = 1; + unsigned last_len = 1; + unsigned curr_len = 1; + pattern &= ~tb; + while (true) + { + if (curr_len) + { + if (pattern & 1) + { + curr_len++; + } + else + { + // end of lit period + if (last_len != curr_len) + { + // new blink length + nibble_shift <<= 4; + last_len = curr_len; + } + ret += nibble_shift; + curr_len = 0; + } + } + else + { + // we are in unlit + if (pattern & 1) + { + // start of a new lit period. + curr_len = 1; + } + } + if (!pattern) + { + break; + } + pattern >>= 1; + } + return ret; +} + +} diff --git a/src/utils/Blinker.cxxtest b/src/utils/Blinker.cxxtest new file mode 100644 index 000000000..2831a4054 --- /dev/null +++ b/src/utils/Blinker.cxxtest @@ -0,0 +1,14 @@ +#include "utils/blinker.h" + +#include "utils/test_main.hxx" + +TEST(BlinkerTest, parse_test) +{ + EXPECT_EQ(0u, parseblink(0)); + EXPECT_EQ(0x33u, parseblink(BLINK_DIE_ABORT)); + EXPECT_EQ(0x213u, parseblink(BLINK_DIE_HARDFAULT)); + EXPECT_EQ(0x123u, parseblink(BLINK_DIE_OUTOFMEM)); + EXPECT_EQ(0x313u, parseblink(BLINK_DIE_NMI)); + EXPECT_EQ(0x223u, parseblink(BLINK_DIE_ASSERT)); + EXPECT_EQ(0x133u, parseblink(BLINK_DIE_WATCHDOG)); +}; diff --git a/src/utils/blinker.h b/src/utils/blinker.h index 7151f3bbe..b61cfe243 100644 --- a/src/utils/blinker.h +++ b/src/utils/blinker.h @@ -90,6 +90,14 @@ extern uint32_t blinker_pattern; /** Sets a blinking pattern and never returns. */ extern void diewith(uint32_t); +/** Turns a blinker pattern into an error code in BCD. + * @param pattern a blinker pattern from the above examples. + * @return a nonnegative integer with each 4-bit nibble representing the number + * of repeats in the block of blinks, in LSB-first order. So a blink 3-1-2 will + * be represented as 0x213. + */ +extern unsigned parseblink(uint32_t pattern); + #ifdef __cplusplus } #endif // cplusplus diff --git a/src/utils/sources b/src/utils/sources index 7140a788a..8e7a00a40 100644 --- a/src/utils/sources +++ b/src/utils/sources @@ -5,6 +5,7 @@ CSRCS += errno_exit.c \ CXXSRCS += \ Base64.cxx \ + Blinker.cxx \ CanIf.cxx \ Crc.cxx \ StringPrintf.cxx \ From b8d44b19ab58bce9d343a332bebe9f2a639db7b2 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 28 Feb 2021 23:42:12 +0100 Subject: [PATCH 167/171] Adds support for write to the memoryconfig utils application. (#515) --- .../targets/linux.x86/main.cxx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/applications/memconfig_utils/targets/linux.x86/main.cxx b/applications/memconfig_utils/targets/linux.x86/main.cxx index d5aac2c83..52962e08d 100644 --- a/applications/memconfig_utils/targets/linux.x86/main.cxx +++ b/applications/memconfig_utils/targets/linux.x86/main.cxx @@ -200,22 +200,30 @@ int appl_main(int argc, char *argv[]) openlcb::NodeHandle dst; dst.alias = destination_alias; dst.id = destination_nodeid; - HASSERT(do_read); - auto b = invoke_flow(&g_memcfg_cli, openlcb::MemoryConfigClientRequest::READ, dst, memory_space_id); - - if (0 && do_write) + HASSERT((!!do_read) + (!!do_write) == 1); + if (do_write) { - b->data()->payload = read_file_to_string(filename); - //b->data()->cmd = openlcb::MemoryConfigClientRequest::WRITE; + auto payload = read_file_to_string(filename); printf("Read %" PRIdPTR " bytes from file %s. Writing to memory space 0x%02x\n", - b->data()->payload.size(), filename, memory_space_id); + payload.size(), filename, memory_space_id); + auto b = invoke_flow(&g_memcfg_cli, + openlcb::MemoryConfigClientRequest::WRITE, dst, memory_space_id, 0, + std::move(payload)); + printf("Result: %04x\n", b->data()->resultCode); } - printf("Result: %04x\n", b->data()->resultCode); - if (do_read) { + auto cb = [](openlcb::MemoryConfigClientRequest *rq) { + static size_t last_len = rq->payload.size(); + if ((last_len & ~1023) != (rq->payload.size() & ~1023)) { + printf("Loaded %d bytes\n", (int)rq->payload.size()); + last_len = rq->payload.size(); + } + }; + auto b = invoke_flow(&g_memcfg_cli, openlcb::MemoryConfigClientRequest::READ, dst, memory_space_id, std::move(cb)); + printf("Result: %04x\n", b->data()->resultCode); write_string_to_file(filename, b->data()->payload); fprintf(stderr, "Written %" PRIdPTR " bytes to file %s.\n", b->data()->payload.size(), filename); From 11e680f1f3801e2bdf49a2115fa1c46b92328502 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sun, 7 Mar 2021 13:36:49 -0600 Subject: [PATCH 168/171] Update the descriptions in the User Info Segment. (#518) --- src/openlcb/ConfigRepresentation.hxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openlcb/ConfigRepresentation.hxx b/src/openlcb/ConfigRepresentation.hxx index c2485acb0..7e7e96c22 100644 --- a/src/openlcb/ConfigRepresentation.hxx +++ b/src/openlcb/ConfigRepresentation.hxx @@ -448,12 +448,12 @@ CDI_GROUP( CDI_GROUP_ENTRY(name, StringConfigEntry<63>, // Name("User Name"), // Description( - "This name will appear in network browsers for the current node.")); + "This name will appear in network browsers for this device.")); /// User description entry CDI_GROUP_ENTRY(description, StringConfigEntry<64>, // Name("User Description"), // - Description("This description will appear in network browsers for the " - "current node.")); + Description("This description will appear in network browsers for " + "this device.")); /// Signals termination of the group. CDI_GROUP_END(); From 365a5e4f9a5007e2005df5cb195ad855eb4d0069 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sun, 28 Mar 2021 13:34:38 -0500 Subject: [PATCH 169/171] Fix test. (#522) --- src/openlcb/ConfigRenderer.cxxtest | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openlcb/ConfigRenderer.cxxtest b/src/openlcb/ConfigRenderer.cxxtest index 65a903fee..07f9662c7 100644 --- a/src/openlcb/ConfigRenderer.cxxtest +++ b/src/openlcb/ConfigRenderer.cxxtest @@ -328,11 +328,11 @@ TEST(CdiRender, RenderIdent) User Name -This name will appear in network browsers for the current node. +This name will appear in network browsers for this device. User Description -This description will appear in network browsers for the current node. +This description will appear in network browsers for this device. From ad44e29390611fed5fff74db5edd1116deb7a3bc Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sun, 4 Apr 2021 11:53:07 -0500 Subject: [PATCH 170/171] Broadcast Time Server Detection (#521) * Add method for determining if a timer server has been discovered. * Add test for time server discovery. * Make methods public. * Mask "server" detection for date events only. --- src/openlcb/BroadcastTime.hxx | 4 ++++ src/openlcb/BroadcastTimeClient.cxxtest | 3 +++ src/openlcb/BroadcastTimeClient.hxx | 16 ++++++++++++++++ src/openlcb/BroadcastTimeServer.cxxtest | 1 + src/openlcb/BroadcastTimeServer.hxx | 7 +++++++ 5 files changed, 31 insertions(+) diff --git a/src/openlcb/BroadcastTime.hxx b/src/openlcb/BroadcastTime.hxx index 76383825b..f01c42881 100644 --- a/src/openlcb/BroadcastTime.hxx +++ b/src/openlcb/BroadcastTime.hxx @@ -358,6 +358,10 @@ public: return &tm_; } + /// Has a time server been detected? + /// @return true if a time server has been detected, else false + virtual bool is_server_detected() = 0; + protected: class SetFlow : public StateFlowBase { diff --git a/src/openlcb/BroadcastTimeClient.cxxtest b/src/openlcb/BroadcastTimeClient.cxxtest index 34008dd06..33bef9eaa 100644 --- a/src/openlcb/BroadcastTimeClient.cxxtest +++ b/src/openlcb/BroadcastTimeClient.cxxtest @@ -250,6 +250,8 @@ TEST_F(BroadcastTimeClientTest, Create) EXPECT_EQ(client2_->time(), 0); EXPECT_EQ(client2_->day_of_week(), BroadcastTimeDefs::THURSDAY); EXPECT_EQ(client2_->day_of_year(), 0); + EXPECT_FALSE(client1_->is_server_detected()); + EXPECT_FALSE(client2_->is_server_detected()); }; TEST_F(BroadcastTimeClientTest, Start) @@ -265,6 +267,7 @@ TEST_F(BroadcastTimeClientTest, Start) sync(client1_, 500); wait(); + EXPECT_TRUE(client1_->is_server_detected()); // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(client1_->time(), 60, 62)); diff --git a/src/openlcb/BroadcastTimeClient.hxx b/src/openlcb/BroadcastTimeClient.hxx index 88cc9226f..608cdb209 100644 --- a/src/openlcb/BroadcastTimeClient.hxx +++ b/src/openlcb/BroadcastTimeClient.hxx @@ -62,6 +62,7 @@ public: , rolloverPending_(false) , rolloverPendingDate_(false) , rolloverPendingYear_(false) + , serverDetected_(false) { EventRegistry::instance()->register_handler( EventRegistryEntry(this, eventBase_), 16); @@ -73,6 +74,13 @@ public: EventRegistry::instance()->unregister_handler(this); } + /// Has a time server been detected? + /// @return true if a time server has been detected, else false + bool is_server_detected() override + { + return serverDetected_; + } + /// Handle requested identification message. /// @param entry registry entry for the event range /// @param event information about the incoming message @@ -176,6 +184,13 @@ public: if (event->state == EventState::VALID) { + // Look for a Report Date Event ID. + if ((event->event & 0x000000000000F000ULL) == 0x2000ULL) + { + // We can only get here if there is a time server detected. + serverDetected_ = true; + } + // We only care about valid event state. // Producer identified only happens when a clock synchronization // is taking place. This voids previous date rollover events. @@ -422,6 +437,7 @@ private: uint16_t rolloverPending_ : 1; ///< a day rollover is about to occur uint16_t rolloverPendingDate_ : 1; ///< a day rollover is about to occur uint16_t rolloverPendingYear_ : 1; ///< a day rollover is about to occur + uint16_t serverDetected_ : 1; ///< has a time server been detected DISALLOW_COPY_AND_ASSIGN(BroadcastTimeClient); diff --git a/src/openlcb/BroadcastTimeServer.cxxtest b/src/openlcb/BroadcastTimeServer.cxxtest index ffbde2a3f..192df80d7 100644 --- a/src/openlcb/BroadcastTimeServer.cxxtest +++ b/src/openlcb/BroadcastTimeServer.cxxtest @@ -111,6 +111,7 @@ TEST_F(BroadcastTimeServerTest, Create) EXPECT_EQ(server_->time(), 0); EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); EXPECT_EQ(server_->day_of_year(), 0); + EXPECT_TRUE(server_->is_server_detected()); }; TEST_F(BroadcastTimeServerTest, Time) diff --git a/src/openlcb/BroadcastTimeServer.hxx b/src/openlcb/BroadcastTimeServer.hxx index 6ece4ca29..fc7c96343 100644 --- a/src/openlcb/BroadcastTimeServer.hxx +++ b/src/openlcb/BroadcastTimeServer.hxx @@ -59,6 +59,13 @@ public: /// Destructor. ~BroadcastTimeServer(); + /// Has a time server been detected? + /// @return true if a time server has been detected, else false + bool is_server_detected() override + { + return true; + } + #if defined(GTEST) void shutdown(); From c09a1534959109082424c707a57ac8690dfffa18 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 5 Apr 2021 19:50:16 +0200 Subject: [PATCH 171/171] Fix os_thread_create_helper for arduino esp32 (#525) Fix code that runs for task creation when a freertos config that enables static allocation. The previous code was probably never compiled because it had an undeclared variable. --- src/os/os.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/os/os.c b/src/os/os.c index f956cd173..d51c51f60 100644 --- a/src/os/os.c +++ b/src/os/os.c @@ -421,17 +421,15 @@ int __attribute__((weak)) os_thread_create_helper(os_thread_t *thread, void *priv) { HASSERT(thread); -#if (configSUPPORT_STATIC_ALLOCATION == 1) +#if (configSUPPORT_DYNAMIC_ALLOCATION == 1) + xTaskCreate(os_thread_start, (const char *const)name, + stack_size/sizeof(portSTACK_TYPE), priv, priority, thread); +#elif (configSUPPORT_STATIC_ALLOCATION == 1) *thread = xTaskCreateStatic(os_thread_start, (const char *const)name, stack_size/sizeof(portSTACK_TYPE), priv, priority, (StackType_t *)stack_malloc(stack_size), (StaticTask_t *) malloc(sizeof(StaticTask_t))); - task_new->task = *thread; - task_new->name = (char*)pcTaskGetTaskName(*thread); -#elif (configSUPPORT_DYNAMIC_ALLOCATION == 1) - xTaskCreate(os_thread_start, (const char *const)name, - stack_size/sizeof(portSTACK_TYPE), priv, priority, thread); #else #error FREERTOS version v9.0.0 or later required #endif