diff --git a/.clang-format b/.clang-format index e7d623a0f..584ed7ada 100644 --- a/.clang-format +++ b/.clang-format @@ -34,7 +34,7 @@ Standard: Cpp11 IndentWidth: 4 TabWidth: 8 UseTab: Never -BreakBeforeBraces: Allman +# BreakBeforeBraces: Allman IndentFunctionDeclarationAfterType: false SpacesInParentheses: false SpacesInAngles: false diff --git a/applications/bootloader_client/targets/js.emscripten/.gitignore b/applications/bootloader_client/targets/js.emscripten/.gitignore index 97dc46a68..e5244b63b 100644 --- a/applications/bootloader_client/targets/js.emscripten/.gitignore +++ b/applications/bootloader_client/targets/js.emscripten/.gitignore @@ -1,2 +1,4 @@ bootloader_client.js node_modules +# released binary +openmrn-bootloader-client-* diff --git a/applications/bootloader_client/targets/js.emscripten/Makefile b/applications/bootloader_client/targets/js.emscripten/Makefile index 1675ca81a..363292221 100644 --- a/applications/bootloader_client/targets/js.emscripten/Makefile +++ b/applications/bootloader_client/targets/js.emscripten/Makefile @@ -11,8 +11,11 @@ release: pkg -C Brotli . -clean: clean-wasm +clean: clean-wasm clean-bin clean-wasm: rm -f $(EXECUTABLE).{wasm,wast} + +clean-bin: + rm -f openmrn-bootloader-client-{linux,macos,win.exe} diff --git a/applications/memconfig_utils/main.hxx b/applications/memconfig_utils/main.hxx new file mode 100644 index 000000000..d37e5f6e2 --- /dev/null +++ b/applications/memconfig_utils/main.hxx @@ -0,0 +1,316 @@ +/** \copyright + * Copyright (c) 2013 - 2023, 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 main.hxx + * + * An application for downloading an entire memory space from a node. + * + * @author Balazs Racz + * @date 7 Sep 2017 + */ + +#include +#include +#include +#include + +#include + +#include "os/os.h" +#include "utils/constants.hxx" +#include "utils/Hub.hxx" +#include "utils/GridConnectHub.hxx" +#include "utils/GcTcpHub.hxx" +#include "utils/Crc.hxx" +#include "utils/FileUtils.hxx" +#include "utils/format_utils.hxx" +#include "executor/Executor.hxx" +#include "executor/Service.hxx" + +#include "openlcb/IfCan.hxx" +#include "openlcb/DatagramCan.hxx" +#include "openlcb/BootloaderClient.hxx" +#include "openlcb/If.hxx" +#include "openlcb/AliasAllocator.hxx" +#include "openlcb/DefaultNode.hxx" +#include "openlcb/NodeInitializeFlow.hxx" +#include "openlcb/MemoryConfig.hxx" +#include "openlcb/MemoryConfigClient.hxx" +#include "utils/socket_listener.hxx" + +NO_THREAD nt; +Executor<1> g_executor(nt); +Service g_service(&g_executor); +CanHubFlow can_hub0(&g_service); + +OVERRIDE_CONST(gc_generate_newlines, 1); + +static const openlcb::NodeID NODE_ID = 0x05010101181FULL; + +openlcb::IfCan g_if_can(&g_executor, &can_hub0, 3, 3, 2); +openlcb::InitializeFlow g_init_flow{&g_service}; +openlcb::CanDatagramService g_datagram_can(&g_if_can, 10, 2); +static openlcb::AddAliasAllocator g_alias_allocator(NODE_ID, &g_if_can); +openlcb::DefaultNode g_node(&g_if_can, NODE_ID); +openlcb::MemoryConfigHandler g_memcfg(&g_datagram_can, &g_node, 10); +openlcb::MemoryConfigClient g_memcfg_cli(&g_node, &g_memcfg); + +namespace openlcb +{ +Pool *const g_incoming_datagram_allocator = mainBufferPool; +} + +static int port = 12021; +static const char *host = "localhost"; +static const char *device_path = nullptr; +static const char *filename = nullptr; +static uint64_t destination_nodeid = 0; +static uint64_t destination_alias = 0; +static int memory_space_id = openlcb::MemoryConfigDefs::SPACE_CONFIG; +static uint32_t offset = 0; +static constexpr uint32_t NLEN = (uint32_t)-1; +static uint32_t len = NLEN; +static bool partial_read = false; +static bool do_read = false; +static bool do_write = false; + +void usage(const char *e) +{ + fprintf(stderr, + "Usage: %s ([-i destination_host] [-p port] | [-d serial_port]) [-s " + "memory_space_id] [-o offset] [-l len] [-c csum_algo] (-r|-w) " + "(-n nodeid | -a alias) -f filename\n", + e); + fprintf(stderr, + "Connects to an openlcb bus and performs memory configuration protocol " + "operations on openlcb node with id `nodeid` with the contents of a " + "given file or arguments.\n"); + fprintf(stderr, + "The bus connection will be through an OpenLCB HUB on " + "destination_host:port with OpenLCB over TCP " + "(in GridConnect format) protocol, or through the CAN-USB device " + "(also in GridConnect protocol) found at serial_port. Device takes " + "precedence over TCP host:port specification."); + fprintf(stderr, "\tThe default target is localhost:12021.\n"); + fprintf(stderr, "\tnodeid should be a 12-char hex string with 0x prefix and " + "no separators, like '-n 0x05010101141F'\n"); + fprintf(stderr, "\talias should be a 3-char hex string with 0x prefix and no " + "separators, like '-a 0x3F9'\n"); + fprintf(stderr, + "\tmemory_space_id defines which memory space to use " + "data into. Default is '-s 0x%02x'.\n", + openlcb::MemoryConfigDefs::SPACE_CONFIG); + fprintf(stderr, "\t-r or -w defines whether to read or write.\n"); + fprintf(stderr, + "\tIf offset and len are skipped for a read, then the entire memory " + "space will be downloaded.\n"); +#ifdef __EMSCRIPTEN__ + fprintf(stderr, "\t-D lists available serial ports.\n"); +#endif + exit(1); +} + +void parse_args(int argc, char *argv[]) +{ + int opt; + while ((opt = getopt(argc, argv, "hp:i:d:n:a:s:f:rwo:l:D")) >= 0) + { + switch (opt) + { + case 'h': + usage(argv[0]); + break; + case 'p': + port = atoi(optarg); + break; + case 'i': + host = optarg; + break; + case 'd': + device_path = optarg; + break; + case 'f': + filename = optarg; + break; + case 'n': + destination_nodeid = strtoll(optarg, nullptr, 16); + break; + case 'a': + destination_alias = strtoul(optarg, nullptr, 16); + break; + case 's': + memory_space_id = strtol(optarg, nullptr, 16); + break; + case 'o': + offset = strtol(optarg, nullptr, 10); + break; + case 'l': + len = strtol(optarg, nullptr, 10); + break; + case 'r': + do_read = true; + break; + case 'w': + do_write = true; + break; +#ifdef __EMSCRIPTEN__ + case 'D': + JSSerialPort::list_ports(); + break; +#endif + default: + fprintf(stderr, "Unknown option %c\n", opt); + usage(argv[0]); + } + } + partial_read = do_read && ((offset != 0) || (len != NLEN)); + if ((!filename && !partial_read) || + (!destination_nodeid && !destination_alias)) + { + usage(argv[0]); + } + if ((do_read ? 1 : 0) + (do_write ? 1 : 0) != 1) + { + fprintf(stderr, "Must set exactly one of option -r and option -w.\n\n"); + usage(argv[0]); + } +} + + +class HelperFlow : public StateFlowBase { +public: + HelperFlow() : StateFlowBase(&g_service) { + start_flow(STATE(wait_for_boot)); + } + + Action wait_for_boot() + { + return sleep_and_call(&timer_, MSEC_TO_NSEC(400), STATE(send_request)); + } + + /// Application business logic. + Action send_request() + { + openlcb::NodeHandle dst; + dst.alias = destination_alias; + dst.id = destination_nodeid; + HASSERT((!!do_read) + (!!do_write) == 1); + if (do_write) + { + auto payload = read_file_to_string(filename); + printf("Read %" PRIdPTR + " bytes from file %s. Writing to memory space 0x%02x\n", + payload.size(), filename, memory_space_id); + return invoke_subflow_and_wait(&g_memcfg_cli, STATE(write_done), + openlcb::MemoryConfigClientRequest::WRITE, dst, memory_space_id, + 0, std::move(payload)); + } + + if (do_read && partial_read) + { + printf("Loading from space 0x%02x offset %u length %d\n", + (unsigned)memory_space_id, (unsigned)offset, (int)len); + return invoke_subflow_and_wait(&g_memcfg_cli, STATE(part_read_done), + openlcb::MemoryConfigClientRequest::READ_PART, dst, + memory_space_id, offset, len); + } + else 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(); + } + }; + printf("Loading memory space 0x%02x\n", + (unsigned)memory_space_id); + return invoke_subflow_and_wait(&g_memcfg_cli, STATE(read_done), + openlcb::MemoryConfigClientRequest::READ, dst, memory_space_id, + std::move(cb)); + } + + printf("Nothing to do."); + return call_immediately(STATE(flow_done)); + } + + /// Invoked when a write operation is complete. Prints result and + /// terminates. + Action write_done() { + auto b = get_buffer_deleter(full_allocation_result(&g_memcfg_cli)); + printf("Result: %04x\n", b->data()->resultCode); + hasError_ = b->data()->resultCode != 0; + return call_immediately(STATE(flow_done)); + } + + /// Invoked when a partial read operation is complete. Prints result and + /// terminates. + Action part_read_done() { + auto b = get_buffer_deleter(full_allocation_result(&g_memcfg_cli)); + printf("Result: %04x\n", b->data()->resultCode); + printf("Data: %s\n", string_to_hex(b->data()->payload).c_str()); + hasError_ = b->data()->resultCode != 0; + return call_immediately(STATE(flow_done)); + } + + /// Invoked when a read operation is complete. Prints result and + /// terminates. + Action read_done() { + auto b = get_buffer_deleter(full_allocation_result(&g_memcfg_cli)); + 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); + hasError_ = b->data()->resultCode != 0; + return call_immediately(STATE(flow_done)); + } + + /// Terminates the process. + Action flow_done() + { + fflush(stdout); +#ifdef __EMSCRIPTEN__ + EM_ASM(process.exit()); +#endif + _exit(hasError_ ? 1 : 0); + + return exit(); + } + + StateFlowTimer timer_{this}; + bool hasError_ = false; +} helper_flow; + + +/// Runs the executor. Never returns. +void execute() { + g_if_can.add_addressed_message_support(); + // Bootstraps the alias allocation process. + g_if_can.alias_allocator()->send(g_if_can.alias_allocator()->alloc()); + + g_executor.thread_body(); +} diff --git a/applications/memconfig_utils/targets/Makefile b/applications/memconfig_utils/targets/Makefile index e09efa82d..4e1071528 100644 --- a/applications/memconfig_utils/targets/Makefile +++ b/applications/memconfig_utils/targets/Makefile @@ -1,5 +1,5 @@ SUBDIRS = linux.x86 \ -# js.emscripten + js.emscripten include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/memconfig_utils/targets/js.emscripten/.gitignore b/applications/memconfig_utils/targets/js.emscripten/.gitignore new file mode 100644 index 000000000..eba1e38bc --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/.gitignore @@ -0,0 +1,6 @@ +memconfig_utils.js +node_modules +openmrn-memconfig-utils-linux +openmrn-memconfig-utils-macos +openmrn-memconfig-utils-win.exe + diff --git a/applications/memconfig_utils/targets/js.emscripten/Makefile b/applications/memconfig_utils/targets/js.emscripten/Makefile new file mode 100644 index 000000000..7e9f4547d --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/Makefile @@ -0,0 +1,21 @@ +-include ../../config.mk +include $(OPENMRNPATH)/etc/prog.mk +LDFLAGS += --bind -s WASM=0 + + +# How to prepare for releasing this: +# as administrator do +# npm install -g pkg +# then you can call make release +release: + pkg -C Brotli . + + +clean: clean-wasm clean-bin + + +clean-wasm: + rm -f $(EXECUTABLE).{wasm,wast} + +clean-bin: + rm -f openmrn-memconfig-utils-{linux,macos,win.exe} diff --git a/applications/memconfig_utils/targets/js.emscripten/lib/Makefile b/applications/memconfig_utils/targets/js.emscripten/lib/Makefile new file mode 100644 index 000000000..a414ed98e --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/lib/Makefile @@ -0,0 +1 @@ +include $(OPENMRNPATH)/etc/app_target_lib.mk diff --git a/applications/memconfig_utils/targets/js.emscripten/main.cxx b/applications/memconfig_utils/targets/js.emscripten/main.cxx new file mode 100644 index 000000000..36c4fc48c --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/main.cxx @@ -0,0 +1,65 @@ +/** \copyright + * Copyright (c) 2013, 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 main.cxx + * + * An application for updating the firmware of a remote node on the bus. + * + * @author Balazs Racz + * @date 3 Aug 2013 + */ + +#include +#include +#include + +#include "utils/JSSerialPort.hxx" +#include "utils/JSTcpClient.hxx" + +#include "main.hxx" + +/** Entry point to application. + * @param argc number of command line arguments + * @param argv array of command line arguments + * @return 0, should never return + */ +int appl_main(int argc, char *argv[]) +{ + parse_args(argc, argv); + std::unique_ptr dev; + std::unique_ptr client; + if (device_path) + { + dev.reset(new JSSerialPort(&can_hub0, device_path)); + } + else + { + client.reset(new JSTcpClient(&can_hub0, host, port)); + } + + execute(); + return 0; +} diff --git a/applications/memconfig_utils/targets/js.emscripten/package.json b/applications/memconfig_utils/targets/js.emscripten/package.json new file mode 100644 index 000000000..c1fc258ea --- /dev/null +++ b/applications/memconfig_utils/targets/js.emscripten/package.json @@ -0,0 +1,13 @@ +{ + "name": "openmrn-memconfig-utils", + "version": "1.0.0", + "dependencies": { + "serialport": "^9.2.7" + }, + "bin": "memconfig_utils.js", + "pkg": { + "assets": [ + "./node_modules/@serialport/bindings/build/Release/bindings.node" + ] + } +} diff --git a/applications/memconfig_utils/targets/linux.x86/main.cxx b/applications/memconfig_utils/targets/linux.x86/main.cxx index 746a4deb1..511d02f9c 100644 --- a/applications/memconfig_utils/targets/linux.x86/main.cxx +++ b/applications/memconfig_utils/targets/linux.x86/main.cxx @@ -1,5 +1,5 @@ /** \copyright - * Copyright (c) 2013 - 2017, Balazs Racz + * Copyright (c) 2013 - 2023, Balazs Racz * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,159 +29,10 @@ * An application for downloading an entire memory space from a node. * * @author Balazs Racz - * @date 7 Sep 2017 + * @date 1 Mar 2023 */ -#include -#include -#include -#include - -#include - -#include "os/os.h" -#include "utils/constants.hxx" -#include "utils/Hub.hxx" -#include "utils/GridConnectHub.hxx" -#include "utils/GcTcpHub.hxx" -#include "utils/Crc.hxx" -#include "utils/FileUtils.hxx" -#include "utils/format_utils.hxx" -#include "executor/Executor.hxx" -#include "executor/Service.hxx" - -#include "openlcb/IfCan.hxx" -#include "openlcb/DatagramCan.hxx" -#include "openlcb/BootloaderClient.hxx" -#include "openlcb/If.hxx" -#include "openlcb/AliasAllocator.hxx" -#include "openlcb/DefaultNode.hxx" -#include "openlcb/NodeInitializeFlow.hxx" -#include "openlcb/MemoryConfig.hxx" -#include "openlcb/MemoryConfigClient.hxx" -#include "utils/socket_listener.hxx" - -NO_THREAD nt; -Executor<1> g_executor(nt); -Service g_service(&g_executor); -CanHubFlow can_hub0(&g_service); - -static const openlcb::NodeID NODE_ID = 0x05010101181FULL; - -openlcb::IfCan g_if_can(&g_executor, &can_hub0, 3, 3, 2); -openlcb::InitializeFlow g_init_flow{&g_service}; -openlcb::CanDatagramService g_datagram_can(&g_if_can, 10, 2); -static openlcb::AddAliasAllocator g_alias_allocator(NODE_ID, &g_if_can); -openlcb::DefaultNode g_node(&g_if_can, NODE_ID); -openlcb::MemoryConfigHandler g_memcfg(&g_datagram_can, &g_node, 10); -openlcb::MemoryConfigClient g_memcfg_cli(&g_node, &g_memcfg); - -namespace openlcb -{ -Pool *const g_incoming_datagram_allocator = mainBufferPool; -} - -static int port = 12021; -static const char *host = "localhost"; -static const char *device_path = nullptr; -static const char *filename = nullptr; -static uint64_t destination_nodeid = 0; -static uint64_t destination_alias = 0; -static int memory_space_id = openlcb::MemoryConfigDefs::SPACE_CONFIG; -static uint32_t offset = 0; -static constexpr uint32_t NLEN = (uint32_t)-1; -static uint32_t len = NLEN; -static bool partial_read = false; -static bool do_read = false; -static bool do_write = false; - -void usage(const char *e) -{ - fprintf(stderr, - "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) [-s " - "memory_space_id] [-o offset] [-l len] [-c csum_algo] (-r|-w) " - "(-n nodeid | -a alias) -f filename\n", - e); - fprintf(stderr, "Connects to an openlcb bus and performs the " - "bootloader protocol on openlcb node with id nodeid with " - "the contents of a given file.\n"); - fprintf(stderr, - "The bus connection will be through an OpenLCB HUB on " - "destination_host:port with OpenLCB over TCP " - "(in GridConnect format) protocol, or through the CAN-USB device " - "(also in GridConnect protocol) found at device_path. Device takes " - "precedence over TCP host:port specification."); - fprintf(stderr, "The default target is localhost:12021.\n"); - fprintf(stderr, "nodeid should be a 12-char hex string with 0x prefix and " - "no separators, like '-b 0x05010101141F'\n"); - fprintf(stderr, "alias should be a 3-char hex string with 0x prefix and no " - "separators, like '-a 0x3F9'\n"); - fprintf(stderr, "memory_space_id defines which memory space to use " - "data into. Default is '-s 0xF0'.\n"); - fprintf(stderr, "-r or -w defines whether to read or write.\n"); - exit(1); -} - -void parse_args(int argc, char *argv[]) -{ - int opt; - while ((opt = getopt(argc, argv, "hp:i:d:n:a:s:f:rwo:l:")) >= 0) - { - switch (opt) - { - case 'h': - usage(argv[0]); - break; - case 'p': - port = atoi(optarg); - break; - case 'i': - host = optarg; - break; - case 'd': - device_path = optarg; - break; - case 'f': - filename = optarg; - break; - case 'n': - destination_nodeid = strtoll(optarg, nullptr, 16); - break; - case 'a': - destination_alias = strtoul(optarg, nullptr, 16); - break; - case 's': - memory_space_id = strtol(optarg, nullptr, 16); - break; - case 'o': - offset = strtol(optarg, nullptr, 10); - break; - case 'l': - len = strtol(optarg, nullptr, 10); - break; - case 'r': - do_read = true; - break; - case 'w': - do_write = true; - break; - default: - fprintf(stderr, "Unknown option %c\n", opt); - usage(argv[0]); - } - } - partial_read = do_read && ((offset != 0) || (len != NLEN)); - if ((!filename && !partial_read) || - (!destination_nodeid && !destination_alias)) - { - usage(argv[0]); - } - if ((do_read ? 1 : 0) + (do_write ? 1 : 0) != 1) - { - fprintf(stderr, "Must set exactly one of option -r and option -w.\n\n"); - usage(argv[0]); - } -} +#include "main.hxx" /** Entry point to application. * @param argc number of command line arguments @@ -191,6 +42,7 @@ void parse_args(int argc, char *argv[]) int appl_main(int argc, char *argv[]) { parse_args(argc, argv); + int conn_fd = 0; if (device_path) { @@ -203,50 +55,6 @@ int appl_main(int argc, char *argv[]) HASSERT(conn_fd >= 0); create_gc_port_for_can_hub(&can_hub0, conn_fd); - g_if_can.add_addressed_message_support(); - // Bootstraps the alias allocation process. - g_if_can.alias_allocator()->send(g_if_can.alias_allocator()->alloc()); - - g_executor.start_thread("g_executor", 0, 1024); - usleep(400000); - - openlcb::NodeHandle dst; - dst.alias = destination_alias; - dst.id = destination_nodeid; - HASSERT((!!do_read) + (!!do_write) == 1); - if (do_write) - { - auto payload = read_file_to_string(filename); - printf("Read %" PRIdPTR - " bytes from file %s. Writing to memory space 0x%02x\n", - 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); - } - - if (do_read && partial_read) - { - auto b = invoke_flow(&g_memcfg_cli, openlcb::MemoryConfigClientRequest::READ_PART, dst, memory_space_id, offset, len); - printf("Result: %04x\n", b->data()->resultCode); - printf("Data: %s\n", string_to_hex(b->data()->payload).c_str()); - } - else 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); - } - + execute(); return 0; } diff --git a/applications/train/targets/linux.x86/main.cxx b/applications/train/targets/linux.x86/main.cxx index 60e3b0aba..ddd2d40fe 100644 --- a/applications/train/targets/linux.x86/main.cxx +++ b/applications/train/targets/linux.x86/main.cxx @@ -34,8 +34,8 @@ #define LOGLEVEL INFO -#include #include +#include #include @@ -47,6 +47,7 @@ #include "openlcb/TractionTestTrain.hxx" #include "openlcb/TractionTrain.hxx" #include "os/os.h" +#include "os/sleep.h" #include "utils/constants.hxx" static const openlcb::NodeID NODE_ID = 0x0501010100F5ULL; @@ -55,7 +56,8 @@ openlcb::SimpleCanStack stack(NODE_ID); openlcb::TrainService traction_service(stack.iface()); -const char *const openlcb::SNIP_DYNAMIC_FILENAME = openlcb::MockSNIPUserFile::snip_user_file_path; +const char *const openlcb::SNIP_DYNAMIC_FILENAME = + openlcb::MockSNIPUserFile::snip_user_file_path; const char *const openlcb::CONFIG_FILENAME = openlcb::SNIP_DYNAMIC_FILENAME; int port = 12021; @@ -64,6 +66,8 @@ const char *device_path = nullptr; const char *name = "Deadrail Train"; int address = 1732; OVERRIDE_CONST(num_memory_spaces, 4); +OVERRIDE_CONST(local_nodes_count, 250); +OVERRIDE_CONST(local_alias_cache_size, 251); namespace openlcb { @@ -74,17 +78,17 @@ const SimpleNodeStaticValues SNIP_STATIC_DATA = { void usage(const char *e) { fprintf(stderr, - "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) " - "[-a address] [-n name]\n\n", - e); + "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) " + "[-a address] [-n name]\n\n", + e); + fprintf( + stderr, "Connects to an openlcb bus and exports a virtual train.\n"); fprintf(stderr, - "Connects to an openlcb bus and exports a virtual train.\n"); - fprintf(stderr, - "The bus connection will be through an OpenLCB HUB on " - "destination_host:port with OpenLCB over TCP " - "(in GridConnect format) protocol, or through the CAN-USB device " - "(also in GridConnect protocol) found at device_path. Device takes " - "precedence over TCP host:port specification."); + "The bus connection will be through an OpenLCB HUB on " + "destination_host:port with OpenLCB over TCP " + "(in GridConnect format) protocol, or through the CAN-USB device " + "(also in GridConnect protocol) found at device_path. Device takes " + "precedence over TCP host:port specification."); fprintf(stderr, "The default target is localhost:12021.\n"); fprintf(stderr, "Address is the virtual loco address. Default 1726.\n"); exit(1); @@ -122,8 +126,92 @@ void parse_args(int argc, char *argv[]) } } + + +namespace openlcb +{ + +/// This struct encompasses all objects needed for a virtual train node. +struct TrainInstance : private IncomingMessageStateFlow +{ + /// Constructor + /// + /// @param addr Train (DCC) address. Will be used for generating the node + /// ID. + /// @param name Train Name. Will be used for SNIP. + /// @param desc Train User Description. Will be used for SNIP. + TrainInstance(unsigned addr, const string &name, const string &desc) + : IncomingMessageStateFlow(stack.iface()) + , address_(addr) + , nodeName_(name) + , nodeDescription_(desc) + , responseFlow_(stack.info_flow()) + { + stack.iface()->dispatcher()->register_handler( + this, Defs::MTI_IDENT_INFO_REQUEST, Defs::MTI_EXACT); + } + + /// DCC address + unsigned address_; + /// Text name for the train. Will appear on the throttle. + string nodeName_; + /// Text description of the node. Will be used in the SNIP reply. + string nodeDescription_; + /// Common helper flow for responding to SNIP requests. + SimpleInfoFlow *responseFlow_; + /// This implementtion object is supposed to drive the hardware. + LoggingTrain train_impl {address_}; + /// Virtual Train Node object. + TrainNodeWithId node_ {&traction_service, &train_impl, + TractionDefs::NODE_ID_DCC | 0xFF000000u | address_}; + /// Handles PIP requests for this node. + ProtocolIdentificationHandler pip {&node_, + Defs::EVENT_EXCHANGE | Defs::SIMPLE_NODE_INFORMATION | + Defs::TRACTION_CONTROL}; + /// Ensures that this train responds to the IS_TRAIN event requests. + FixedEventProducer isTrainEventHandler_ { + &node_}; + + /// What should be our response to SNIP requests. + const SimpleInfoDescriptor SNIP_CUSTOM_RESPONSE[9] = { + {SimpleInfoDescriptor::LITERAL_BYTE, 4, 0, nullptr}, + {SimpleInfoDescriptor::C_STRING, 0, 0, + SNIP_STATIC_DATA.manufacturer_name}, + {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}, + {SimpleInfoDescriptor::LITERAL_BYTE, 2, 0, nullptr}, + {SimpleInfoDescriptor::C_STRING, 0, 0, nodeName_.c_str()}, + {SimpleInfoDescriptor::C_STRING, 0, 0, nodeDescription_.c_str()}, + {SimpleInfoDescriptor::END_OF_DATA, 0, 0, 0}}; + + /// Custom handler for SNIP reply. + Action entry() OVERRIDE + { + if (!nmsg()->dstNode) + return release_and_exit(); + if (nmsg()->dstNode != &node_) + return release_and_exit(); + auto *b = responseFlow_->alloc(); + b->data()->reset( + nmsg(), SNIP_CUSTOM_RESPONSE, Defs::MTI_IDENT_INFO_REPLY); + responseFlow_->send(b); + return release_and_exit(); + } +}; // struct TrainInstance + +} // namespace openlcb + int appl_main(int argc, char *argv[]) { + if (false) + { + // This will make it appear that the device is slow to respond to + // anything. + stack.iface()->set_tx_hook([]() { microsleep(50000); }); + } parse_args(argc, argv); LOG(INFO, "Train name: %s", name); openlcb::MockSNIPUserFile snip_user_file(name, "Deadrail--description"); @@ -137,16 +225,33 @@ int appl_main(int argc, char *argv[]) stack.connect_tcp_gridconnect_hub(host, port); } - openlcb::LoggingTrain train_impl(address); - openlcb::TrainNodeWithId train_node(&traction_service, &train_impl, openlcb::TractionDefs::NODE_ID_DCC | 0xFF000000u | address); - openlcb::FixedEventProducer - is_train_event_handler(&train_node); - openlcb::ProtocolIdentificationHandler pip( - &train_node, - openlcb::Defs::EVENT_EXCHANGE | openlcb::Defs::SIMPLE_NODE_INFORMATION | - openlcb::Defs::TRACTION_CONTROL); - openlcb::SNIPHandler snip_handler{stack.iface(), nullptr, stack.info_flow()}; - - stack.loop_executor(); + if (false) + { + // This is the simple way to create one train + openlcb::LoggingTrain train_impl(address); + openlcb::TrainNodeWithId train_node(&traction_service, &train_impl, + openlcb::TractionDefs::NODE_ID_DCC | 0xFF000000u | address); + openlcb::FixedEventProducer + is_train_event_handler(&train_node); + openlcb::ProtocolIdentificationHandler pip(&train_node, + openlcb::Defs::EVENT_EXCHANGE | + openlcb::Defs::SIMPLE_NODE_INFORMATION | + openlcb::Defs::TRACTION_CONTROL); + openlcb::SNIPHandler snip_handler { + stack.iface(), &train_node, stack.info_flow()}; + + // Have to loop before the objects above go out of scope. + stack.loop_executor(); + } + else + { + // This is how we can create multiple trains. + openlcb::TrainInstance some_train( + address, name, "Deadrail--description"); + + // Have to loop before the objects above go out of scope. + stack.loop_executor(); + } + return 0; } diff --git a/arduino/examples/Stm32CanSerialNode/Stm32CanSerialNode.ino b/arduino/examples/Stm32CanSerialNode/Stm32CanSerialNode.ino index d5ab1b92c..b577294b1 100644 --- a/arduino/examples/Stm32CanSerialNode/Stm32CanSerialNode.ino +++ b/arduino/examples/Stm32CanSerialNode/Stm32CanSerialNode.ino @@ -71,6 +71,9 @@ extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { BOARD_NAME, "1.00" }; + +extern const char* const SNIP_DYNAMIC_FILENAME = nullptr; + } // namespace openlcb /// Arduino setup routine. Initializes the OpenLCB software and connects the diff --git a/bin/revision.py b/bin/revision.py index 51bae5cd9..b6c555562 100755 --- a/bin/revision.py +++ b/bin/revision.py @@ -103,7 +103,7 @@ # get the short hash git_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']) - git_hash = str(git_hash[:7]) + git_hash = str(git_hash[:7].decode()) # get the dirty flag dirty = os.system('git diff --quiet') diff --git a/boards/armv7m/target.ld b/boards/armv7m/target.ld index 3a4b351b4..15a2692e6 100644 --- a/boards/armv7m/target.ld +++ b/boards/armv7m/target.ld @@ -8,7 +8,7 @@ INCLUDE memory_map.ld __top_RAM = ORIGIN(RAM) + LENGTH(RAM); -__cs3_heap_end = __top_RAM; +__cs3_heap_end = __top_RAM - 1024; __start_ram = ORIGIN(RAM); __end_ram = __top_RAM; @@ -41,6 +41,8 @@ SECTIONS __bss_section_table = .; LONG( ADDR(.bss)); LONG( SIZEOF(.bss)); + LONG(__cs3_heap_end); + LONG(__top_RAM - __cs3_heap_end - 64); __bss_section_table_end = .; __section_table_end = . ; /* End of Global Section Table */ diff --git a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx index e337017e5..1f5b0f68c 100644 --- a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx +++ b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx @@ -224,7 +224,10 @@ struct DccHwDefs { static const int RAILCOM_CUTOUT_POST_DELTA_USEC = -16; /// Adds this to the negative half after the railcom cutout is done. static const int RAILCOM_CUTOUT_POST_NEGATIVE_DELTA_USEC = -4; - + /// The DCC end of packet bit (after the cutout) has an asymmetry due to the + /// interrupt CPU latency, this constant tunes it to disappear. This time + /// gets deducted from the second half. + static const int RESYNC_DELAY_USEC = 7; /** These timer blocks will be synchronized once per packet, when the * deadband delay is set up. */ diff --git a/etc/cc322x.mk b/etc/cc322x.mk index 0133d298f..2f450e82d 100644 --- a/etc/cc322x.mk +++ b/etc/cc322x.mk @@ -6,7 +6,6 @@ ifdef TICC3220SDKPATH INCLUDES += -DSL_PLATFORM_MULTI_THREADED -DSL_FULL -DTARGET_IS_CC3220 \ -I$(OPENMRNPATH)/src/freertos_drivers/ti \ -I$(OPENMRNPATH)/src/freertos_drivers/net_cc32xx \ - -I$(TICC3220SDKPATH)/source/ti/devices/cc32xx/driverlib \ -I$(TICC3220SDKPATH)/source/ti/devices/cc32xx \ -I$(TICC3220SDKPATH)/source/ti/posix/gcc diff --git a/src/dcc/RailcomBroadcastDecoder.cxx b/src/dcc/RailcomBroadcastDecoder.cxx index 40aef122d..a9cc9b54e 100644 --- a/src/dcc/RailcomBroadcastDecoder.cxx +++ b/src/dcc/RailcomBroadcastDecoder.cxx @@ -48,15 +48,18 @@ bool RailcomBroadcastDecoder::process_packet(const dcc::Feedback &packet) { if (packet.ch1Size) { - return process_data(packet.ch1Data, packet.ch1Size); - } - else if (packet.ch2Size) - { - return process_data(packet.ch2Data, packet.ch2Size); + return process_data(packet.ch1Data, packet.ch1Size) && + (packet.ch2Size == 0); } else { - return true; // empty packet. + // No channel1 data. + notify_empty(); + if (!packet.ch2Size) + { + return true; // empty packet. + } + return false; } } @@ -65,13 +68,17 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size) for (unsigned i = 0; i < size; ++i) { if (railcom_decode[data[i]] == RailcomDefs::INV) + { return true; // garbage. + } } /// TODO(balazs.racz) if we have only one byte in ch1 but we have a second /// byte in ch2, we should still process those because it might be a /// misaligned window. if (size < 2) - return true; // Dunno what this is.a + { + return true; // Dunno what this is. + } uint8_t type = (dcc::railcom_decode[data[0]] >> 2); if (size == 2) { @@ -81,17 +88,29 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size) switch (type) { case dcc::RMOB_ADRLOW: - if (currentL_ == payload) { - if (countL_ < REPEAT_COUNT) ++countL_; - } else { + if (currentL_ == payload) + { + if (countL_ < MIN_EMPTY_COUNT) + { + countL_ += 2; + } + } + else + { currentL_ = payload; countL_ = 0; } break; case dcc::RMOB_ADRHIGH: - if (currentH_ == payload) { - if (countH_ < REPEAT_COUNT) ++countH_; - } else { + if (currentH_ == payload) + { + if (countH_ < MIN_EMPTY_COUNT) + { + countH_ += 2; + } + } + else + { currentH_ = payload; countH_ = 0; } @@ -99,7 +118,9 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size) default: return false; // This is something we don't know about. } - if (countL_ >= REPEAT_COUNT && countH_ >= REPEAT_COUNT) { + if (countL_ >= (MIN_REPEAT_COUNT * 2) && + countH_ >= (MIN_REPEAT_COUNT * 2)) + { currentAddress_ = (uint16_t(currentH_) << 8) | currentL_; } return true; @@ -110,11 +131,27 @@ bool RailcomBroadcastDecoder::process_data(const uint8_t *data, unsigned size) } } -void RailcomBroadcastDecoder::set_occupancy(bool value) { - if (value) return; - if (countH_) --countH_; - if (countL_) --countL_; - if ((!countH_) || (!countL_)) { +void RailcomBroadcastDecoder::set_occupancy(bool value) +{ + if (value) + { + return; + } + notify_empty(); +} + +void RailcomBroadcastDecoder::notify_empty() +{ + if (countH_) + { + --countH_; + } + if (countL_) + { + --countL_; + } + if ((!countH_) || (!countL_)) + { currentAddress_ = 0; } } diff --git a/src/dcc/RailcomBroadcastDecoder.hxx b/src/dcc/RailcomBroadcastDecoder.hxx index acbfeb9a6..a63fa7943 100644 --- a/src/dcc/RailcomBroadcastDecoder.hxx +++ b/src/dcc/RailcomBroadcastDecoder.hxx @@ -86,9 +86,15 @@ private: /// bytes arethere to decode. @return dunno. bool process_data(const uint8_t *data, unsigned size); + /// Notifies the state machine that there is no occupancy detected. + void notify_empty(); + /// How many times we shall get the same data out of railcom before we /// believe it and report to the bus. - static const uint8_t REPEAT_COUNT = 3; + static const uint8_t MIN_REPEAT_COUNT = 3; + /// This is how many empty packets we need to forget the current address + /// when we're getting empty packets. + static const uint8_t MIN_EMPTY_COUNT = 8; uint8_t currentH_; ///< last received high address bits uint8_t currentL_; ///< last received low address bits diff --git a/src/dcc/RailcomPortDebug.hxx b/src/dcc/RailcomPortDebug.hxx index ef06c2d09..936af2735 100644 --- a/src/dcc/RailcomPortDebug.hxx +++ b/src/dcc/RailcomPortDebug.hxx @@ -36,6 +36,7 @@ #define _DCC_RAILCOMPORTDEBUG_HXX #include "dcc/RailcomHub.hxx" +#include "utils/LimitedPool.hxx" namespace dcc { @@ -213,14 +214,17 @@ public: RailcomToOpenLCBDebugProxy(dcc::RailcomHubFlow *parent, Node *node, dcc::RailcomHubPort *occupancy_port, bool ch1_enabled = true, bool ack_enabled = true) - : dcc::RailcomHubPort(parent->service()) + : dcc::RailcomHubPort(node->iface()) , parent_(parent) , node_(node) , occupancyPort_(occupancy_port) , ch1Enabled_(ch1_enabled) , ackEnabled_(ack_enabled) { - parent_->register_port(this); + if (parent_) + { + parent_->register_port(this); + } } RailcomToOpenLCBDebugProxy(Node *node) @@ -254,11 +258,15 @@ public: { return release_and_exit(); } + if (outputPool_.free_items() == 0) + { + return release_and_exit(); + } if (message()->data()->ch1Size && ch1Enabled_) { return allocate_and_call( node_->iface()->global_message_write_flow(), - STATE(ch1_msg_allocated)); + STATE(ch1_msg_allocated), &outputPool_); } else { @@ -285,13 +293,14 @@ public: Action maybe_send_ch2() { if (message()->data()->ch2Size && + outputPool_.free_items() != 0 && (ackEnabled_ || dcc::railcom_decode[message()->data()->ch2Data[0]] != dcc::RailcomDefs::ACK)) { return allocate_and_call( node_->iface()->global_message_write_flow(), - STATE(ch2_msg_allocated)); + STATE(ch2_msg_allocated), &outputPool_); } else { @@ -318,6 +327,7 @@ public: dcc::RailcomHubFlow *parent_{nullptr}; Node *node_; dcc::RailcomHubPort *occupancyPort_; + LimitedPool outputPool_{sizeof(Buffer), 20}; /// True if we should transmit channel1 data. uint8_t ch1Enabled_ : 1; /// True if we should transmit data that starts with an ACK. diff --git a/src/freertos_drivers/common/FixedQueue.hxx b/src/freertos_drivers/common/FixedQueue.hxx new file mode 100644 index 000000000..6cdf0a48c --- /dev/null +++ b/src/freertos_drivers/common/FixedQueue.hxx @@ -0,0 +1,136 @@ +/** \copyright + * Copyright (c) 2014, 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 FixedQueue.hxx + * Deprecated queue class for device drivers. + * + * @author Stuart W. Baker + * @date 6 May 2014 + */ + +#ifndef _FREERTOS_DRIVERS_COMMON_FIXEDQUEUE_HXX_ +#define _FREERTOS_DRIVERS_COMMON_FIXEDQUEUE_HXX_ + +#include +#include + +#include "Devtab.hxx" +#include "executor/Notifiable.hxx" + +/// This structure is safe to use from an interrupt context and a regular +/// context at the same time, provided that +/// +/// . one context uses only the front() and the other only the back() functions. +/// +/// . ++ and -- are compiled into atomic operations on the processor (on the +/// count_ variable). +/// +/// @deprecated, use @ref DeviceBuffer instead. +/// +/// @todo(balazs.racz) replace uses of this class with DeviceBuffer (to enable +/// select support for example). +template class FixedQueue { +public: + FixedQueue() + : rdIndex_(0) + , wrIndex_(0) + , count_(0) + { + } + + /// @return true if there is no entry in the queue. + bool empty() { return size() == 0; } + /// @return true if the queue cannot accept more elements. + bool full() { return size() >= SIZE; } + /// @return the current number of entries in the queue. + size_t size() { return __atomic_load_n(&count_, __ATOMIC_SEQ_CST); } + + /// Returns the head of the FIFO (next element to read). + T& front() { + HASSERT(!empty()); + return storage_[rdIndex_]; + } + + /// Removes the head of the FIFO from the queue. + void increment_front() { + HASSERT(!empty()); + if (++rdIndex_ >= SIZE) rdIndex_ = 0; + __atomic_fetch_add(&count_, -1, __ATOMIC_SEQ_CST); + } + + /// Returns the space to write the next element to. + T& back() { + HASSERT(!full()); + return storage_[wrIndex_]; + } + + /// Commits the element at back() into the queue. + void increment_back() { + HASSERT(!full()); + if (++wrIndex_ >= SIZE) wrIndex_ = 0; + __atomic_fetch_add(&count_, 1, __ATOMIC_SEQ_CST); + } + + /** Increments the back pointer without committing the entry into the + * queue. + * + * This essentially reserves an entry in the queue for filling in, without + * making that entry available for reading. Must be followed by a + * commit_back call when filling in the entry is finished. An arbitrary + * number of such entries can be reserved (up to the number of free entries + * in the queue). */ + void noncommit_back() { + HASSERT(has_noncommit_space()); + if (++wrIndex_ >= SIZE) wrIndex_ = 0; + } + + /** @returns true if we can do a noncommit back. */ + bool has_noncommit_space() { + if (full()) return false; + auto new_index = wrIndex_; + if (++new_index >= SIZE) new_index = 0; + return new_index != rdIndex_; + } + + /** Commits the oldest entry reserved by noncommit_back. */ + void commit_back() { + HASSERT(count_ <= SIZE); + __atomic_fetch_add(&count_, 1, __ATOMIC_SEQ_CST); + } + +private: + /// Payload of elements stored. + T storage_[SIZE]; + /// The index of the element to return next upon a read. This element is + /// typically full (unless the queue is empty itself). + uint8_t rdIndex_; + /// The index of the element where to write the next input to. + uint8_t wrIndex_; + /// How many elements are there in the queue. + volatile uint8_t count_; +}; + +#endif // _FREERTOS_DRIVERS_COMMON_FIXEDQUEUE_HXX_ diff --git a/src/freertos_drivers/common/PWM.hxx b/src/freertos_drivers/common/PWM.hxx index 2a50bb60d..9e073da53 100644 --- a/src/freertos_drivers/common/PWM.hxx +++ b/src/freertos_drivers/common/PWM.hxx @@ -83,15 +83,14 @@ public: protected: /// Constructor. - PWM() - { - } - - /// Destructor. - ~PWM() - { - } - + constexpr PWM() = default; + + /// Destructor. This is protected, because only child classes should be + /// allowed to destruct a PWM; but no implementation exists in order to + /// make the the destructor trivial for C++. A trivial destructor is + /// required for a constexpr class. + ~PWM() = default; + private: DISALLOW_COPY_AND_ASSIGN(PWM); diff --git a/src/freertos_drivers/common/RailcomImpl.hxx b/src/freertos_drivers/common/RailcomImpl.hxx new file mode 100644 index 000000000..74ecf660f --- /dev/null +++ b/src/freertos_drivers/common/RailcomImpl.hxx @@ -0,0 +1,280 @@ +/** \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 TivaRailcom.hxx + * + * Device driver for TivaWare to read one or more UART inputs for railcom data. + * + * usage: + * + * struct MyRailcomPins { + * // add all definitions from the example RailcomHw here + * }; + * // Some definitions need to go outside of the class. + * // no need for attribute weak if this is in a .cxx file. + * const uint32_t MyRailcomPins::UART_BASE[] __attribute__((weak)) = {...}; + * ... + * + * TivaRailcomDriver railcom_driver("/dev/railcom"); + * + * // assuming you put OS_INTERRRUPT = INT_UART3 into the structure. + * void uart3_interrupt_handler() { + * railcom_driver.os_interrrupt(); + * } + * + * Then open /dev/railcom and read dcc::Feedback structures from it. Each read + * much be exactly sizeof(dcc::Feedback) length. If there are no more packets to + * read, you'll get return=0, errno==EAGAIN. Add an ioctl CAN_READ_ACTIVE to + * get a notification when there is something to read. + * + * @author Balazs Racz + * @date 6 Jan 2015 + */ + +#ifndef _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_ +#define _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_ + +#include "freertos_drivers/common/RailcomDriver.hxx" +#include "freertos_drivers/common/FixedQueue.hxx" +#include "dcc/RailCom.hxx" + +/* +struct RailcomHw +{ + static const uint32_t CHANNEL_COUNT = 4; + static const uint32_t UART_PERIPH[CHANNEL_COUNT]; + static const uint32_t UART_BASE[CHANNEL_COUNT]; + // Make sure there are enough entries here for all the channels times a few + // DCC packets. + static const uint32_t Q_SIZE = 16; + + static const auto OS_INTERRUPT = INT_UART2; + + GPIO_HWPIN(CH1, GpioHwPin, C, 4, U4RX); + GPIO_HWPIN(CH2, GpioHwPin, C, 6, U3RX); + GPIO_HWPIN(CH3, GpioHwPin, G, 4, U2RX); + GPIO_HWPIN(CH4, GpioHwPin, E, 0, U7RX); + + static void hw_init() { + CH1_Pin::hw_init(); + CH2_Pin::hw_init(); + CH3_Pin::hw_init(); + CH4_Pin::hw_init(); + } + + static void set_input() { + CH1_Pin::set_input(); + CH2_Pin::set_input(); + CH3_Pin::set_input(); + CH4_Pin::set_input(); + } + + static void set_hw() { + CH1_Pin::set_hw(); + CH2_Pin::set_hw(); + CH3_Pin::set_hw(); + CH4_Pin::set_hw(); + } + + /// @returns a bitmask telling which pins are active. Bit 0 will be set if + /// channel 0 is active (drawing current). + static uint8_t sample() { + uint8_t ret = 0; + if (!CH1_Pin::get()) ret |= 1; + if (!CH2_Pin::get()) ret |= 2; + if (!CH3_Pin::get()) ret |= 4; + if (!CH4_Pin::get()) ret |= 8; + return ret; + } +}; + +// The weak attribute is needed if the definition is put into a header file. +const uint32_t RailcomHw::UART_BASE[] __attribute__((weak)) = {UART4_BASE, UART3_BASE, UART2_BASE, UART7_BASE}; +const uint32_t RailcomHw::UART_PERIPH[] +__attribute__((weak)) = {SYSCTL_PERIPH_UART4, SYSCTL_PERIPH_UART3, SYSCTL_PERIPH_UART2, SYSCTL_PERIPH_UART7}; + +*/ + +/// Base class for railcom driver implementations. +template +class RailcomDriverBase : public RailcomDriver, private Node +{ +public: + /// Constructor. @param name is the device node (e.g. "/dev/railcom0"). + RailcomDriverBase(const char *name) + : Node(name) + , readableNotifiable_(nullptr) + { + HW::hw_init(); + } + + ~RailcomDriverBase() + { + } + +private: + /// Sets a given software interrupt pending. + /// @param int_nr interrupt number (will be HW::OS_INTERRUPT) + virtual void int_set_pending(unsigned int_nr) = 0; + + ssize_t write(File *, const void *, size_t) OVERRIDE + { + // This device is read-only. + return -ENOSYS; + } + + ssize_t read(File *file, void *buf, size_t count) OVERRIDE + { + if (count != sizeof(dcc::Feedback)) + { + return -EINVAL; + } + OSMutexLock l(&lock_); + if (feedbackQueue_.empty()) + { + return -EAGAIN; + } + memcpy(buf, &feedbackQueue_.front(), count); + feedbackQueue_.increment_front(); + return count; + } + + void flush_buffers() OVERRIDE + { + while (!feedbackQueue_.empty()) + { + feedbackQueue_.increment_front(); + } + } + + // Asynchronous interface + int ioctl(File *file, unsigned long int key, unsigned long data) override + { + if (IOC_TYPE(key) == CAN_IOC_MAGIC && + IOC_SIZE(key) == NOTIFIABLE_TYPE && key == CAN_IOC_READ_ACTIVE) + { + Notifiable *n = reinterpret_cast(data); + HASSERT(n); + // If there is no data for reading, we put the incoming notification + // into the holder. Otherwise we notify it immediately. + if (feedbackQueue_.empty()) + { + portENTER_CRITICAL(); + if (feedbackQueue_.empty()) + { + // We are in a critical section now. If we got into this + // branch, then the buffer was full at the beginning of the + // critical section. If the hardware interrupt kicks in + // now, and sets the os_interrupt to pending, the os + // interrupt will not happen until we leave the critical + // section, and thus the swap will be in effect by then. + std::swap(n, readableNotifiable_); + } + portEXIT_CRITICAL(); + } + if (n) + { + n->notify(); + } + return 0; + } + errno = EINVAL; + return -1; + } + +public: + /// Implementation of the interrupt handler running at the kernel interrupt + /// priority. Call this function from the hardware interrupt handler. + void os_interrupt_handler() __attribute__((always_inline)) + { + if (!feedbackQueue_.empty()) + { + Notifiable *n = readableNotifiable_; + readableNotifiable_ = nullptr; + if (n) + { + n->notify_from_isr(); + os_isr_exit_yield_test(true); + } + } + } + +private: + void set_feedback_key(uint32_t key) OVERRIDE + { + feedbackKey_ = key; + } + +protected: + /** Takes a new empty packet at the front of the queue, fills in feedback + * key and channel information. + * @param channel is which channel to set the packet for. + * @return a packet pointer to write into. If there is no space, returns + * nullptr.*/ + dcc::Feedback *alloc_new_packet(uint8_t channel) + { + if (!feedbackQueue_.has_noncommit_space()) + { + return nullptr; + } + dcc::Feedback *entry = &feedbackQueue_.back(); + feedbackQueue_.noncommit_back(); + entry->reset(feedbackKey_); + entry->channel = channel; + return entry; + } + + /// Adds a sample for a preamble bit. @param sample is the next sample to + /// return to the application layer (usually a bitmask setting which + /// channels are active). + void add_sample(int sample) { + if (sample < 0) return; // skipped sampling + if (feedbackQueue_.full()) { + extern uint32_t feedback_sample_overflow_count; + ++feedback_sample_overflow_count; + return; + } + feedbackQueue_.back().reset(feedbackKey_); + feedbackQueue_.back().channel = HW::get_feedback_channel(); + feedbackQueue_.back().add_ch1_data(sample); + uint32_t tick_timer = HW::get_timer_tick(); + memcpy(feedbackQueue_.back().ch2Data, &tick_timer, 4); + feedbackQueue_.increment_back(); + int_set_pending(HW::OS_INTERRUPT); + } + + /** Notify this when we have data in our buffers. */ + Notifiable *readableNotifiable_; + /** The packets we have read so far. */ + FixedQueue feedbackQueue_; + /// Stores the key for the next packets to read. + uint32_t feedbackKey_; + /** Stores pointers to packets we are filling right now, one for each + * channel. */ + dcc::Feedback *returnedPackets_[HW::CHANNEL_COUNT]; +}; + +#endif // _FREERTOS_DRIVERS_COMMON_RAILCOMIMPL_HXX_ diff --git a/src/freertos_drivers/common/libatomic.c b/src/freertos_drivers/common/libatomic.c index 4e4b5d12c..bd7db1f98 100644 --- a/src/freertos_drivers/common/libatomic.c +++ b/src/freertos_drivers/common/libatomic.c @@ -46,6 +46,18 @@ /// Restores the interrupte enable flag from a register. #define REL_LOCK() __asm volatile(" msr PRIMASK, %0\n " : : "r"(_pastlock)); +/// __atomic_fetch_add_1 +/// +/// This function is needed for GCC-generated code. +uint8_t __atomic_fetch_add_1(uint8_t *ptr, uint8_t val, int memorder) +{ + ACQ_LOCK(); + uint16_t ret = *ptr; + *ptr += val; + REL_LOCK(); + return ret; +} + /// __atomic_fetch_sub_2 /// /// This function is needed for GCC-generated code. diff --git a/src/freertos_drivers/sources b/src/freertos_drivers/sources index a570b39e6..0473a59b7 100644 --- a/src/freertos_drivers/sources +++ b/src/freertos_drivers/sources @@ -44,7 +44,7 @@ SUBDIRS += mbed_lpc1768 drivers_lpc1768 tivaware lpc_chip_175x_6x \ cc3220sdk net_cc3220 cc3220 \ net_freertos_tcp freertos_tcp ti_grlib \ spiffs_cc32x0sf spiffs_tm4c129 \ - spiffs_stm32f303xe spiffs_stm32f767xx \ + spiffs_stm32f767xx \ spiffs_spi \ #spiffs_tm4c123 \ @@ -60,7 +60,7 @@ endif ifeq ($(TARGET),freertos.armv7m.exc) SUBDIRS += stm32cubef303x_28x_58x_98x stm32cubef303xe stm32cubef767xx \ - spiffs_stm32f303xe spiffs_stm32f767xx \ + spiffs_stm32f767xx \ # Avoids exception handling from operator new. diff --git a/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx index 19b4d9222..3d96fe26b 100644 --- a/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx +++ b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx @@ -36,108 +36,10 @@ #include "utils/logging.h" #include "TiSPIFFS.hxx" - +#include "freertos_drivers/ti/TiFlash.hxx" #include "spiffs.h" -#ifdef TI_DUAL_BANK_FLASH - -/// Flash configuration register. -#define FLASH_CONF 0x400FDFC8 -/// This bit in the FLASH_CONF register means that the banks are reversed by -/// their address mapping. -#define FCMME 0x40000000 -/// This value defines up to one bit that needs to be XOR-ed to the flash -/// address before calling the flash APIs to cover for reversed flash banks. -static const unsigned addr_mirror = (HWREG(FLASH_CONF) & FCMME) ? 0x80000 : 0; - -#else - -static constexpr unsigned addr_mirror = 0; - -#endif // Dual bank flash - -// Different options for what to set for flash write locking. These are only -// needed to debug when the operating system is misbehaving during flash -// writes. - -#if defined(TISPIFFS_LOCK_ALL_INTERRUPTS) - -// Global disable interrupts. - -#define DI() asm("cpsid i\n") -#define EI() asm("cpsie i\n") - -#elif defined(TISPIFFS_LOCK_CRITICAL) - -// Critical section (interrupts better than MIN_SYSCALL_PRIORITY are still -// running). - -#define DI() portENTER_CRITICAL() -#define EI() portEXIT_CRITICAL() - -#elif defined(TISPIFFS_LOCK_BASEPRI_FF) - -// Disable interrupts with a priority limit of 0xFF (these are the lowest -// priority interrupts, including the FreeRTOS kernel task switch interrupt). -unsigned ppri; -constexpr unsigned minpri = 0xFF; -#define DI() \ - do \ - { \ - unsigned r; \ - __asm volatile(" mrs %0, basepri\n mov %1, %2\n msr basepri, %1\n" \ - : "=r"(ppri), "=r"(r) \ - : "i"(minpri) \ - : "memory"); \ - } while (0) -#define EI() __asm volatile(" msr basepri, %0\n" : : "r"(ppri) : "memory") - -#elif defined(TISPIFFS_LOCK_NOTICK) - -// Disable the systick timer to prevent preemptive multi-tasking from changing -// to a different task. - -static constexpr unsigned SYSTICKCFG = 0xE000E010; -#define DI() HWREG(SYSTICKCFG) &= ~2; -#define EI() HWREG(SYSTICKCFG) |= 2; - -#elif defined(TISPIFFS_LOCK_SCHEDULER_SUSPEND) - -// Disable freertos scheduler - -#define DI() vTaskSuspendAll() -#define EI() xTaskResumeAll() - -#elif defined(TISPIFFS_LOCK_NONE) -// No write locking. - -#define DI() -#define EI() - -#elif defined(TISPIFFS_LOCK_CRASH) - -// Crashes if two different executions of this locking mechanism are -// concurrent. - -unsigned pend = 0; -#define DI() HASSERT(pend==0); pend=1; -#define EI() pend=0; - -#else -#error Must specify what kind of locking to use for TISPIFFS. -#endif - -// This ifdef decides whether we use the ROM or the flash based implementations -// for Flash write and erase. It also supports correcting for the reversed bank -// addresses. -#if 1 -#define FPG(data, addr, size) ROM_FlashProgram(data, (addr) ^ addr_mirror, size) -#define FER(addr) ROM_FlashErase((addr) ^ addr_mirror) -#else -#define FPG(data, addr, size) FlashProgram(data, (addr) ^ addr_mirror, size) -#define FER(addr) FlashErase((addr) ^ addr_mirror) -#endif // // TiSPIFFS::flash_read() @@ -148,7 +50,7 @@ int32_t TiSPIFFS::flash_read(uint32_t addr, uint32_t size, uint HASSERT(addr >= fs_->cfg.phys_addr && (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - memcpy(dst, (void*)addr, size); + TiFlash::read(addr, dst, size); return 0; } @@ -160,85 +62,8 @@ template int32_t TiSPIFFS::flash_write(uint32_t addr, uint32_t size, uint8_t *src) { LOG(INFO, "Write %x sz %d", (unsigned)addr, (unsigned)size); - union WriteWord - { - uint8_t data[4]; - uint32_t data_word; - }; - - HASSERT(addr >= fs_->cfg.phys_addr && - (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - - if ((addr % 4) && ((addr % 4) + size) < 4) - { - // single unaligned write in the middle of a word. - WriteWord ww; - ww.data_word = 0xFFFFFFFF; - - memcpy(ww.data + (addr % 4), src, size); - ww.data_word &= *((uint32_t*)(addr & (~0x3))); - DI(); - HASSERT(FPG(&ww.data_word, addr & (~0x3), 4) == 0); - EI(); - LOG(INFO, "Write done1"); - return 0; - } - - int misaligned = (addr + size) % 4; - if (misaligned != 0) - { - // last write unaligned data - WriteWord ww; - ww.data_word = 0xFFFFFFFF; - - memcpy(&ww.data_word, src + size - misaligned, misaligned); - ww.data_word &= *((uint32_t*)((addr + size) & (~0x3))); - DI(); - HASSERT(FPG(&ww.data_word, (addr + size) & (~0x3), 4) == 0); - EI(); - - size -= misaligned; - } - - misaligned = addr % 4; - if (size && misaligned != 0) - { - // first write unaligned data - WriteWord ww; - ww.data_word = 0xFFFFFFFF; - - memcpy(ww.data + misaligned, src, 4 - misaligned); - ww.data_word &= *((uint32_t*)(addr & (~0x3))); - DI(); - HASSERT(FPG(&ww.data_word, addr & (~0x3), 4) == 0); - EI(); - addr += 4 - misaligned; - size -= 4 - misaligned; - src += 4 - misaligned; - } - - HASSERT((addr % 4) == 0); - HASSERT((size % 4) == 0); - - if (size) - { - // the rest of the aligned data - uint8_t *flash = (uint8_t*)addr; - for (uint32_t i = 0; i < size; i += 4) - { - src[i + 0] &= flash[i + 0]; - src[i + 1] &= flash[i + 1]; - src[i + 2] &= flash[i + 2]; - src[i + 3] &= flash[i + 3]; - } - - DI(); - HASSERT(FPG((unsigned long *)src, addr, size) == 0); - EI(); - } - - LOG(INFO, "Write done2"); + TiFlash::write(addr, src, size); return 0; } @@ -253,14 +78,7 @@ int32_t TiSPIFFS::flash_erase(uint32_t addr, uint32_t size) (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); HASSERT((size % ERASE_PAGE_SIZE) == 0); - while (size) - { - DI(); - HASSERT(FER(addr) == 0); - EI(); - addr += ERASE_PAGE_SIZE; - size -= ERASE_PAGE_SIZE; - } + TiFlash::erase(addr, size); LOG(INFO, "Erasing %x done", (unsigned)addr); return 0; diff --git a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx b/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx deleted file mode 100644 index 89ce2c0d6..000000000 --- a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.cxx +++ /dev/null @@ -1,149 +0,0 @@ -/** @copyright - * Copyright (c) 2019, 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 Stm32SPIFFS.cxx - * - * This file implements a SPIFFS FLASH driver specific to STM32 F0..F3 flash - * API. - * - * @author Balazs Racz - * @date 13 July 2019 - */ - -#include "Stm32SPIFFS.hxx" -#include "spiffs.h" -#include "stm32f_hal_conf.hxx" - -// -// Stm32SPIFFS::flash_read() -// -int32_t Stm32SPIFFS::flash_read(uint32_t addr, uint32_t size, uint8_t *dst) -{ - HASSERT(addr >= fs_->cfg.phys_addr && - (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - - memcpy(dst, (void *)addr, size); - - return 0; -} - -// -// Stm32SPIFFS::flash_write() -// -int32_t Stm32SPIFFS::flash_write(uint32_t addr, uint32_t size, uint8_t *src) -{ - union WriteWord - { - uint8_t data[8]; - uint16_t data_hword[4]; - uint32_t data_word[2]; - uint64_t data_dw; - }; - - HASSERT(addr >= fs_->cfg.phys_addr && - (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - - HAL_FLASH_Unlock(); - - if (addr & 1) - { - // Unaligned write at the beginning. - WriteWord ww; - ww.data_hword[0] = 0xffff; - ww.data[1] = src[0]; - - HASSERT(HAL_OK == HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, - addr - 1, ww.data_dw)); - addr += 1; - size -= 1; - src += 1; - } - - while (size > 8) - { - WriteWord ww; - memcpy(ww.data, src, 8); - HASSERT(HAL_OK == - HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, ww.data_dw)); - addr += 8; - size -= 8; - src += 8; - } - - while (size > 2) - { - WriteWord ww; - memcpy(ww.data_hword, src, 2); - HASSERT(HAL_OK == - HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr, ww.data_dw)); - addr += 2; - size -= 2; - src += 2; - } - - if (size) - { - // Unaligned write at the end of the range. - HASSERT(size == 1); - HASSERT((addr & 1) == 0); - - WriteWord ww; - ww.data_hword[0] = 0xffff; - ww.data[0] = src[0]; - - HASSERT(HAL_OK == - HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr, ww.data_dw)); - addr += 1; - size -= 1; - src += 1; - } - - HAL_FLASH_Lock(); - - return 0; -} - -// -// Stm32SPIFFS::flash_erase() -// -int32_t Stm32SPIFFS::flash_erase(uint32_t addr, uint32_t size) -{ - HASSERT(addr >= fs_->cfg.phys_addr && - (addr + size) <= (fs_->cfg.phys_addr + fs_->cfg.phys_size)); - HASSERT((size % ERASE_PAGE_SIZE) == 0); - HASSERT((size % FLASH_PAGE_SIZE) == 0); - - FLASH_EraseInitTypeDef erase_init; - erase_init.TypeErase = FLASH_TYPEERASE_PAGES; - erase_init.PageAddress = (uint32_t)addr; - erase_init.NbPages = size / FLASH_PAGE_SIZE; - uint32_t page_error; - - HASSERT(HAL_OK == HAL_FLASHEx_Erase(&erase_init, &page_error)); - - return 0; -} - diff --git a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx b/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx deleted file mode 100644 index a952f43bb..000000000 --- a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx +++ /dev/null @@ -1,88 +0,0 @@ -/** @copyright - * Copyright (c) 2019, 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 Stm32SPIFFS.hxx - * - * This file implements a SPIFFS FLASH driver specific to STM32 F0..F3 flash - * API. - * - * @author Balazs Racz - * @date 13 July 2019 - */ - -#ifndef _FREERTOS_DRIVERS_SPIFFS_STM32F0_F3_STM32SPIFFS_HXX_ -#define _FREERTOS_DRIVERS_SPIFFS_STM32F0_F3_STM32SPIFFS_HXX_ - -#include - -#include "freertos_drivers/spiffs/SPIFFS.hxx" - -/// Specialization of Serial SPIFFS driver for Stm32 F0-F3 devices. -class Stm32SPIFFS : public SPIFFS -{ -public: - /// Constructor. - Stm32SPIFFS(size_t physical_address, size_t size_on_disk, - size_t logical_block_size, size_t logical_page_size, - size_t max_num_open_descriptors = 16, size_t cache_pages = 8, - std::function post_format_hook = nullptr) - : SPIFFS(physical_address, size_on_disk, ERASE_PAGE_SIZE, - logical_block_size, logical_page_size, - max_num_open_descriptors, cache_pages, post_format_hook) - { - } - - /// Destructor. - ~Stm32SPIFFS() - { - unmount(); - } - -private: - /// size of an erase page in FLASH - static constexpr size_t ERASE_PAGE_SIZE = 2 * 1024; - - /// SPIFFS callback to read flash, in context. - /// @param addr adddress location to read - /// @param size size of read in bytes - /// @param dst destination buffer for read - int32_t flash_read(uint32_t addr, uint32_t size, uint8_t *dst) override; - - /// SPIFFS callback to write flash, in context. - /// @param addr adddress location to write - /// @param size size of write in bytes - /// @param src source buffer for write - int32_t flash_write(uint32_t addr, uint32_t size, uint8_t *src) override; - - /// SPIFFS callback to erase flash, in context. - /// @param addr adddress location to erase - /// @param size size of erase region in bytes - int32_t flash_erase(uint32_t addr, uint32_t size) override; - - DISALLOW_COPY_AND_ASSIGN(Stm32SPIFFS); -}; - -#endif // _FREERTOS_DRIVERS_SPIFFS_STM32F0_F3_STM32SPIFFS_HXX_ diff --git a/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h b/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h deleted file mode 120000 index 96f2b5bad..000000000 --- a/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h +++ /dev/null @@ -1 +0,0 @@ -../spiffs_config.h \ No newline at end of file diff --git a/src/freertos_drivers/st/Stm32Can.cxx b/src/freertos_drivers/st/Stm32Can.cxx index 185c1a221..eb20fc2dc 100644 --- a/src/freertos_drivers/st/Stm32Can.cxx +++ b/src/freertos_drivers/st/Stm32Can.cxx @@ -132,6 +132,7 @@ Stm32Can::Stm32Can(const char *name) #endif } +#ifndef ARDUINO // // Stm32Can::ioctl() // @@ -144,6 +145,7 @@ int Stm32Can::ioctl(File *file, unsigned long int key, unsigned long data) } return -EINVAL; } +#endif // !ARDUINO /** Enable use of the device. */ diff --git a/src/freertos_drivers/st/Stm32Can.hxx b/src/freertos_drivers/st/Stm32Can.hxx index 499bbfc2d..fb54c334a 100644 --- a/src/freertos_drivers/st/Stm32Can.hxx +++ b/src/freertos_drivers/st/Stm32Can.hxx @@ -72,13 +72,15 @@ public: static Stm32Can *instances[1]; private: +#ifndef ARDUINO /// Request an ioctl transaction. /// @param file file reference for this device /// @param key ioctl key /// @param data key data /// @return >= 0 upon success, -errno upon failure int ioctl(File *file, unsigned long int key, unsigned long data) override; - +#endif + void enable() override; /**< function to enable device */ void disable() override; /**< function to disable device */ void tx_msg() override; /**< function to try and transmit a message */ diff --git a/src/freertos_drivers/st/Stm32Gpio.hxx b/src/freertos_drivers/st/Stm32Gpio.hxx index 868532010..717d65e23 100644 --- a/src/freertos_drivers/st/Stm32Gpio.hxx +++ b/src/freertos_drivers/st/Stm32Gpio.hxx @@ -88,6 +88,12 @@ template struct Stm32GpioDefs return port()->IDR & pin(); } + /// Changes the output pin level. + static void toggle() + { + set(!get()); + } + /// @return a os-indepentent Gpio abstraction instance for use in /// libraries. static constexpr const Gpio *instance() diff --git a/src/freertos_drivers/st/Stm32Railcom.hxx b/src/freertos_drivers/st/Stm32Railcom.hxx new file mode 100644 index 000000000..54719c519 --- /dev/null +++ b/src/freertos_drivers/st/Stm32Railcom.hxx @@ -0,0 +1,426 @@ +/** \copyright + * Copyright (c) 2023, 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 Stm32Railcom.hxx + * + * Device driver for STM32 chips to read one or more UART inputs for railcom + * data. + * + * @author Balazs Racz + * @date 6 Mar 2023 + */ + +#ifndef _FREERTOS_DRIVERS_ST_STM32RAILCOM_HXX_ +#define _FREERTOS_DRIVERS_ST_STM32RAILCOM_HXX_ + +#include "stm32f_hal_conf.hxx" + +#include "freertos_drivers/common/RailcomImpl.hxx" + +#if defined(STM32F072xB) || defined(STM32F091xC) +#include "stm32f0xx_ll_dma.h" +#include "stm32f0xx_ll_usart.h" +#elif defined(STM32F103xB) +#include "stm32f1xx_ll_dma.h" +#include "stm32f1xx_ll_usart.h" +#elif defined(STM32F303xC) || defined(STM32F303xE) +#include "stm32f3xx_ll_dma.h" +#include "stm32f3xx_ll_usart.h" +#elif defined(STM32L431xx) || defined(STM32L432xx) +#include "stm32l4xx_ll_dma.h" +#include "stm32l4xx_ll_usart.h" +#elif defined(STM32F767xx) +#include "stm32f7xx_ll_dma.h" +#include "stm32f7xx_ll_usart.h" +#else +#error Dont know what STM32 chip you have. +#endif + +/// This struct helps casting the UART base addresses to the appropriate +/// type. It can be allocated in read-only memory (flash) as a static array. It +/// can be passed to the STM32 HAL macros as well. +struct RailcomUartHandle +{ + union + { + /// Initialized by the constants from the processor header like + /// USART1_BASE. + uint32_t baseAddress; + /// Use this to access the registers. + USART_TypeDef *Instance; + }; +}; + +/// This struct helps casting the DMA channel base addresses to the appropriate +/// type. It can be allocated in read-only memory (flash) as a static array. +struct RailcomDmaChannel +{ + union + { + /// Initialized by the constants from the processor header like + /// USART1_BASE. + uint32_t baseAddress; + /// Use this to access the registers. + DMA_Channel_TypeDef *Instance; + }; +}; + +/* +struct RailcomHw +{ + static const uint32_t CHANNEL_COUNT = 4; + static const RailcomUartHandle UART[CHANNEL_COUNT]; + + // If DMA channel routing is in use, that has to be set up externally + // (e.g. in hw_preinit). + static const RailcomDmaChannel DMA[CHANNEL_COUNT]; + + // Make sure there are enough entries here for all the channels times a few + // DCC packets. + static const uint32_t Q_SIZE = 32; + + static const auto OS_INTERRUPT = USART2_IRQn; + + GPIO_HWPIN(CH1, GpioHwPin, C, 4, U4RX); + GPIO_HWPIN(CH2, GpioHwPin, C, 6, U3RX); + GPIO_HWPIN(CH3, GpioHwPin, G, 4, U2RX); + GPIO_HWPIN(CH4, GpioHwPin, E, 0, U7RX); + + static void hw_init() { + CH1_Pin::hw_init(); + CH2_Pin::hw_init(); + CH3_Pin::hw_init(); + CH4_Pin::hw_init(); + } + + static void set_input() { + CH1_Pin::set_input(); + CH2_Pin::set_input(); + CH3_Pin::set_input(); + CH4_Pin::set_input(); + } + + static void set_hw() { + CH1_Pin::set_hw(); + CH2_Pin::set_hw(); + CH3_Pin::set_hw(); + CH4_Pin::set_hw(); + } + + /// @returns a bitmask telling which pins are active. Bit 0 will be set if + /// channel 0 is active (drawing current). + static uint8_t sample() { + uint8_t ret = 0; + if (!CH1_Pin::get()) ret |= 1; + if (!CH2_Pin::get()) ret |= 2; + if (!CH3_Pin::get()) ret |= 4; + if (!CH4_Pin::get()) ret |= 8; + return ret; + } +}; + +// The weak attribute is needed if the definition is put into a header file. +const uint32_t RailcomHw::UART[] __attribute__((weak)) = { USART4_BASE, +USART1_BASE, USART3_BASE, USART2_BASE }; + +const RailcomDmaChannel RailcomHw::DMA[] __attribute__((weak)) = { +DMA1_Channel1_BASE, DMA1_Channel3_BASE, DMA1_Channel6_BASE, DMA1_Channel5_BASE +}; + +In hw_preinit(), we have this for DMA channel routing: + //DMA channel routing for railcom UARTs + MODIFY_REG(DMA1->CSELR, DMA_CSELR_C1S_Msk, DMA1_CSELR_CH1_USART4_RX); + MODIFY_REG(DMA1->CSELR, DMA_CSELR_C3S_Msk, DMA1_CSELR_CH3_USART1_RX); + MODIFY_REG(DMA1->CSELR, DMA_CSELR_C6S_Msk, DMA1_CSELR_CH3_USART3_RX); + MODIFY_REG(DMA1->CSELR, DMA_CSELR_C5S_Msk, DMA1_CSELR_CH3_USART2_RX); + +*/ +/// Railcom driver for STM32-class microcontrollers using the HAL middleware +/// library. +/// +/// This railcom driver supports parallel polling of multiple UART channels for +/// the railcom data. +template class Stm32RailcomDriver : public RailcomDriverBase +{ +public: + /// Constructor. @param path is the device node path (e.g. "/dev/railcom0"). + Stm32RailcomDriver(const char *path) + : RailcomDriverBase(path) + { +#ifdef GCC_CM3 + SetInterruptPriority(HW::OS_INTERRUPT, configKERNEL_INTERRUPT_PRIORITY); +#else + SetInterruptPriority(HW::OS_INTERRUPT, 0xff); +#endif + HAL_NVIC_EnableIRQ(HW::OS_INTERRUPT); + } + + ~Stm32RailcomDriver() + { + HAL_NVIC_DisableIRQ(HW::OS_INTERRUPT); + } + +private: + /// True when we are currently within a cutout. + bool inCutout_ = false; + + using RailcomDriverBase::returnedPackets_; + + /// @param i channel number (0..HW::NUM_CHANNEL) + /// @return uart hardware instance for channel i. + static constexpr USART_TypeDef *uart(unsigned i) + { + return HW::UART[i].Instance; + } + + /// @param i channel number (0..HW::NUM_CHANNEL) + /// @return dma channel instance for channel i. + static constexpr DMA_Channel_TypeDef *dma_ch(unsigned i) + { + return HW::DMA[i].Instance; + } + + /// Sets a given software interrupt pending. + /// @param int_nr interrupt number (will be HW::OS_INTERRUPT) + void int_set_pending(unsigned int_nr) override + { + HAL_NVIC_SetPendingIRQ((IRQn_Type)int_nr); + } + + // File node interface + void enable() OVERRIDE + { + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) + { + UART_HandleTypeDef uart_handle; + memset(&uart_handle, 0, sizeof(uart_handle)); + uart_handle.Init.BaudRate = 250000; + uart_handle.Init.WordLength = UART_WORDLENGTH_8B; + uart_handle.Init.StopBits = UART_STOPBITS_1; + uart_handle.Init.Parity = UART_PARITY_NONE; + uart_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; + uart_handle.Init.Mode = UART_MODE_RX; + uart_handle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; + uart_handle.Instance = uart(i); + HAL_UART_DeInit(&uart_handle); + volatile auto ret = HAL_UART_Init(&uart_handle); + HASSERT(HAL_OK == ret); + + // Disables the receiver. + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_NONE); + + // configure DMA + + // peripheral address + dma_ch(i)->CPAR = (uint32_t)(&uart(i)->RDR); + + // memory address and num transfers not set, these will come from + // each cutout. + // dma_ch->CMAR = (uint32_t)adcSamplesRaw_; + // dma_ch->CNDTR = NUM_SAMPLES * NUM_CHANNELS; + + uint32_t config = LL_DMA_DIRECTION_PERIPH_TO_MEMORY | + LL_DMA_MODE_NORMAL | LL_DMA_PERIPH_NOINCREMENT | + LL_DMA_MEMORY_INCREMENT | LL_DMA_PDATAALIGN_BYTE | + LL_DMA_MDATAALIGN_BYTE | LL_DMA_PRIORITY_HIGH; + MODIFY_REG(dma_ch(i)->CCR, + DMA_CCR_DIR | DMA_CCR_MEM2MEM | DMA_CCR_CIRC | DMA_CCR_PINC | + DMA_CCR_MINC | DMA_CCR_PSIZE | DMA_CCR_MSIZE | DMA_CCR_PL | + DMA_CCR_TCIE | DMA_CCR_HTIE | DMA_CCR_TEIE, + config); + + LL_USART_Enable(uart(i)); + } + } + void disable() override + { + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) + { + LL_USART_Disable(uart(i)); + } + } + + // RailcomDriver interface + void feedback_sample() override + { + HW::enable_measurement(true); + this->add_sample(HW::sample()); + HW::disable_measurement(); + } + + void start_cutout() override + { + HW::enable_measurement(false); + const bool need_ch1_cutout = + HW::need_ch1_cutout() || (this->feedbackKey_ < 11000); + Debug::RailcomRxActivate::set(true); + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) + { + while (LL_USART_IsActiveFlag_RXNE(uart(i))) + { + uint8_t data = uart(i)->RDR; + (void)data; + } + returnedPackets_[i] = this->alloc_new_packet(i); + if (need_ch1_cutout && returnedPackets_[i]) + { + LL_USART_EnableDMAReq_RX(uart(i)); + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_RX); + LL_USART_ClearFlag_FE(uart(i)); + LL_USART_Enable(uart(i)); + + dma_ch(i)->CNDTR = 2; + dma_ch(i)->CMAR = (uint32_t)returnedPackets_[i]->ch1Data; + dma_ch(i)->CCR |= DMA_CCR_EN; // enable DMA + } + } + Debug::RailcomDriverCutout::set(true); + } + + void middle_cutout() override + { + Debug::RailcomDriverCutout::set(false); + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) + { + if (!returnedPackets_[i]) + { + continue; + } + LL_USART_Disable(uart(i)); + CLEAR_BIT(dma_ch(i)->CCR, DMA_CCR_EN); + // How many bytes did we transfer? + returnedPackets_[i]->ch1Size = 2 - dma_ch(i)->CNDTR; + if (returnedPackets_[i]->ch1Size == 1) + { + Debug::RailcomError::toggle(); + } + if (returnedPackets_[i]->ch1Size) + { + Debug::RailcomDataReceived::toggle(); + Debug::RailcomAnyData::set(true); + } + + if (LL_USART_IsActiveFlag_FE(uart(i))) + { + // We reset the receiver circuitry because we got an error + // in channel 1. Typical cause of this error is if there + // are multiple locomotives on the block (e.g. if this is + // the global detector) and they talk over each other + // during ch1 broadcast. There is a good likelihood that + // the asynchronous receiver is out of sync with the + // transmitter, but right now we should be in the + // between-byte space. + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_NONE); + LL_USART_ClearFlag_FE(uart(i)); + Debug::RailcomError::toggle(); + // Not a valid railcom byte. + returnedPackets_[i]->ch1Data[0] = 0xF8; + returnedPackets_[i]->ch1Size = 1; + } + + // Set up channel 2 reception with DMA. + dma_ch(i)->CNDTR = 6; + dma_ch(i)->CMAR = (uint32_t)returnedPackets_[i]->ch2Data; + dma_ch(i)->CCR |= DMA_CCR_EN; // enable DMA + + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_RX); + LL_USART_ClearFlag_FE(uart(i)); + LL_USART_Enable(uart(i)); + } + HW::middle_cutout_hook(); + Debug::RailcomDriverCutout::set(true); + } + + void end_cutout() override + { + HW::disable_measurement(); + bool have_packets = false; + for (unsigned i = 0; i < HW::CHANNEL_COUNT; ++i) + { + LL_USART_Disable(uart(i)); + if (!returnedPackets_[i]) + { + continue; + } + CLEAR_BIT(dma_ch(i)->CCR, DMA_CCR_EN); + // How many bytes did we transfer? + returnedPackets_[i]->ch2Size = 6 - dma_ch(i)->CNDTR; + if (returnedPackets_[i]->ch2Size) + { + Debug::RailcomDataReceived::toggle(); + Debug::RailcomAnyData::set(true); + Debug::RailcomCh2Data::set(true); + } + if (LL_USART_IsActiveFlag_FE(uart(i))) + { + Debug::RailcomError::toggle(); + LL_USART_ClearFlag_FE(uart(i)); + if (returnedPackets_[i]->ch2Size < 6) + { + returnedPackets_[i]->add_ch2_data(0xF8); + } + } + + LL_USART_SetTransferDirection(uart(i), LL_USART_DIRECTION_NONE); + + Debug::RailcomPackets::toggle(); + have_packets = true; + this->feedbackQueue_.commit_back(); + returnedPackets_[i] = nullptr; + HAL_NVIC_SetPendingIRQ(HW::OS_INTERRUPT); + } + Debug::RailcomRxActivate::set(false); + 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(15); + if (p) + { + this->feedbackQueue_.commit_back(); + Debug::RailcomPackets::toggle(); + HAL_NVIC_SetPendingIRQ(HW::OS_INTERRUPT); + } + } + Debug::RailcomCh2Data::set(false); + Debug::RailcomDriverCutout::set(false); + } + + void no_cutout() override + { + // Ensures that at least one feedback packet is sent back even when + // it is with no railcom payload. + auto *p = this->alloc_new_packet(15); + if (p) + { + this->feedbackQueue_.commit_back(); + Debug::RailcomPackets::toggle(); + HAL_NVIC_SetPendingIRQ(HW::OS_INTERRUPT); + } + } +}; + +#endif // _FREERTOS_DRIVERS_ST_STM32RAILCOM_HXX_ diff --git a/src/freertos_drivers/ti/CC32xxUart.cxx b/src/freertos_drivers/ti/CC32xxUart.cxx index 1c72cd2ea..910938b89 100644 --- a/src/freertos_drivers/ti/CC32xxUart.cxx +++ b/src/freertos_drivers/ti/CC32xxUart.cxx @@ -72,6 +72,7 @@ CC32xxUart::CC32xxUart(const char *name, unsigned long base, uint32_t interrupt, , base_(base) , interrupt_(interrupt) , baud_(baud) + , uartMode_(mode | UART_CONFIG_PAR_NONE) , txPending_(false) , hwFIFO_(hw_fifo) { @@ -79,7 +80,7 @@ CC32xxUart::CC32xxUart(const char *name, unsigned long base, uint32_t interrupt, UART_CONFIG_PAR_NONE == 0, "driverlib changed against our assumptions"); static_assert( UART_CONFIG_STOP_ONE == 0, "driverlib changed against our assumptions"); - HASSERT(mode <= 0xFFu); + HASSERT(uartMode_ <= 0xFFu); switch (base) { @@ -95,8 +96,7 @@ CC32xxUart::CC32xxUart(const char *name, unsigned long base, uint32_t interrupt, break; } - MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud, - mode | UART_CONFIG_PAR_NONE); + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud, uartMode_); MAP_IntDisable(interrupt_); /* We set the priority so that it is slightly lower than the highest needed * for FreeRTOS compatibility. This will ensure that CAN interrupts take @@ -150,38 +150,38 @@ int CC32xxUart::ioctl(File *file, unsigned long int key, unsigned long data) MAP_UtilsDelay(12 * 26); break; case TCPARNONE: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_NONE; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_NONE; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_NONE); break; case TCPARODD: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ODD; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ODD; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ODD); break; case TCPAREVEN: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_EVEN; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_EVEN; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_EVEN); break; case TCPARONE: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ONE; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ONE; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ONE); break; case TCPARZERO: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ZERO; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ZERO; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ZERO); break; case TCSTOPONE: - mode_ &= ~UART_CONFIG_STOP_MASK; - mode_ |= UART_CONFIG_STOP_ONE; + uartMode_ &= ~UART_CONFIG_STOP_MASK; + uartMode_ |= UART_CONFIG_STOP_ONE; set_mode(); break; case TCSTOPTWO: - mode_ &= ~UART_CONFIG_STOP_MASK; - mode_ |= UART_CONFIG_STOP_TWO; + uartMode_ &= ~UART_CONFIG_STOP_MASK; + uartMode_ |= UART_CONFIG_STOP_TWO; set_mode(); break; case TCBAUDRATE: @@ -217,7 +217,9 @@ int CC32xxUart::ioctl(File *file, unsigned long int key, unsigned long data) /** Sets the port baud rate and mode from the class variables. */ void CC32xxUart::set_mode() { - MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, mode_); + disable(); + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, uartMode_); + enable(); } /** Send data until there is no more space left. diff --git a/src/freertos_drivers/ti/CC32xxUart.hxx b/src/freertos_drivers/ti/CC32xxUart.hxx index 663d36be9..b63ae4c09 100644 --- a/src/freertos_drivers/ti/CC32xxUart.hxx +++ b/src/freertos_drivers/ti/CC32xxUart.hxx @@ -121,6 +121,7 @@ private: unsigned long base_; /**< base address of this device */ uint32_t interrupt_ : 8; /**< interrupt of this device */ uint32_t baud_ : 24; /**< desired baud rate */ + uint32_t uartMode_; /**< mode of the UART, 8 or 9 bit, 1 or 2 stop... */ uint8_t txPending_; /**< transmission currently pending */ uint8_t hwFIFO_; /**< true if hardware fifo is to be enabled, else false */ diff --git a/src/freertos_drivers/ti/TiFlash.hxx b/src/freertos_drivers/ti/TiFlash.hxx new file mode 100644 index 000000000..0d3c82dd3 --- /dev/null +++ b/src/freertos_drivers/ti/TiFlash.hxx @@ -0,0 +1,267 @@ +/** \copyright + * Copyright (c) 2023, 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 TiFlash.hxx + * + * Implementation for TI internal flash for Tiva and CC32xxSF devices. This + * class is intended to be used by other device drivers. + * + * @author Balazs Racz + * @date 23 Apr 2023 + */ + +#ifndef _FREERTOS_DRIVERS_TI_TIFLASH_HXX_ +#define _FREERTOS_DRIVERS_TI_TIFLASH_HXX_ + +#ifdef TI_DUAL_BANK_FLASH + +/// Flash configuration register. +#define FLASH_CONF 0x400FDFC8 +/// This bit in the FLASH_CONF register means that the banks are reversed by +/// their address mapping. +#define FCMME 0x40000000 +/// This value defines up to one bit that needs to be XOR-ed to the flash +/// address before calling the flash APIs to cover for reversed flash banks. +static const unsigned addr_mirror = (HWREG(FLASH_CONF) & FCMME) ? 0x80000 : 0; + +#else + +static constexpr unsigned addr_mirror = 0; + +#endif // Dual bank flash + +// Different options for what to set for flash write locking. These are only +// needed to debug when the operating system is misbehaving during flash +// writes. + +#if defined(TISPIFFS_LOCK_ALL_INTERRUPTS) + +// Global disable interrupts. + +#define DI() asm("cpsid i\n") +#define EI() asm("cpsie i\n") + +#elif defined(TISPIFFS_LOCK_CRITICAL) + +// Critical section (interrupts better than MIN_SYSCALL_PRIORITY are still +// running). + +#define DI() portENTER_CRITICAL() +#define EI() portEXIT_CRITICAL() + +#elif defined(TISPIFFS_LOCK_BASEPRI_FF) + +// Disable interrupts with a priority limit of 0xFF (these are the lowest +// priority interrupts, including the FreeRTOS kernel task switch interrupt). +unsigned ppri; +constexpr unsigned minpri = 0xFF; +#define DI() \ + do \ + { \ + unsigned r; \ + __asm volatile(" mrs %0, basepri\n mov %1, %2\n msr basepri, %1\n" \ + : "=r"(ppri), "=r"(r) \ + : "i"(minpri) \ + : "memory"); \ + } while (0) +#define EI() __asm volatile(" msr basepri, %0\n" : : "r"(ppri) : "memory") + +#elif defined(TISPIFFS_LOCK_NOTICK) + +// Disable the systick timer to prevent preemptive multi-tasking from changing +// to a different task. + +static constexpr unsigned SYSTICKCFG = 0xE000E010; +#define DI() HWREG(SYSTICKCFG) &= ~2; +#define EI() HWREG(SYSTICKCFG) |= 2; + +#elif defined(TISPIFFS_LOCK_SCHEDULER_SUSPEND) + +// Disable freertos scheduler + +#define DI() vTaskSuspendAll() +#define EI() xTaskResumeAll() + +#elif defined(TISPIFFS_LOCK_NONE) + +// No write locking. + +#define DI() +#define EI() + +#elif defined(TISPIFFS_LOCK_CRASH) + +// Crashes if two different executions of this locking mechanism are +// concurrent. + +unsigned pend = 0; +#define DI() \ + HASSERT(pend == 0); \ + pend = 1; +#define EI() pend = 0; + +#else +#error Must specify what kind of locking to use for TISPIFFS. +#endif + +// This ifdef decides whether we use the ROM or the flash based implementations +// for Flash write and erase. It also supports correcting for the reversed bank +// addresses. +#if 1 +#define FPG(data, addr, size) ROM_FlashProgram(data, (addr) ^ addr_mirror, size) +#define FER(addr) ROM_FlashErase((addr) ^ addr_mirror) +#else +#define FPG(data, addr, size) FlashProgram(data, (addr) ^ addr_mirror, size) +#define FER(addr) FlashErase((addr) ^ addr_mirror) +#endif + +template class TiFlash +{ +public: + constexpr TiFlash() + { } + + /// Performs write to the device. This call is synchronous; does not return + /// until the write is complete. + /// @param addr where to write (in the address space of the MCU). + /// @param buf data to write + /// @param len how many bytes to write + static void write(uint32_t addr, const void *buf, uint32_t len) + { + // Theory of operation: + // + // The hardware has 32 word long write buffer. This is aligned based on + // the flash address that's being written. We split the writes on the + // boundary of this buffer (which is the low 7 bits of the address). + // + // For each buffer, we identify which words are touched by the + // write. We load these words (32 bits at a time), then we AND the data + // to be written into it (one byte at a time). + // + // Then we program from the buffer using the driverlib API. + // + // This algorithm is agnostic to the alignment of the actual writes, + // and we also guarantee that we never attempt to set a 0 bit to a 1 + // bit in the flash. It also makes optimal use of the parallel write + // capability of the flash hardware. + static constexpr uint32_t BUF_WORD_LEN = 32; + static constexpr uint32_t BUF_BYTE_LEN = 128; + static constexpr uint32_t BUF_BYTE_MASK = BUF_BYTE_LEN - 1; + static constexpr uint32_t BYTE_PER_WORD = 4; + typedef union + { + uint32_t w[BUF_WORD_LEN]; + uint8_t b[BUF_BYTE_LEN]; + } BufType; + static BufType wrbuf; + + const uint8_t *src = (const uint8_t *)buf; + + while (len) + { + // memory address of the first byte in the buffer. + uint32_t *buf_address = (uint32_t *)(addr & ~BUF_BYTE_MASK); + // first word in the buffer that we need to program + uint32_t buf_word_ofs = (addr & BUF_BYTE_MASK) / BYTE_PER_WORD; + // first byte in the buffer that we need to load + uint32_t buf_byte_ofs = (addr & BUF_BYTE_MASK); + // how many bytes to copy to this buffer. + uint32_t buf_byte_count = + std::min((uint32_t)len, BUF_BYTE_LEN - buf_byte_ofs); + // first word that does not need to be touched + uint32_t buf_word_end = + (buf_byte_ofs + buf_byte_count + BYTE_PER_WORD - 1) / + BYTE_PER_WORD; + // how many words do we need to program. + uint32_t buf_word_count = buf_word_end - buf_word_ofs; + + // Pre-fill data with existing flash content. + for (uint32_t i = 0; i < buf_word_count; ++i) + { + wrbuf.w[buf_word_ofs + i] = buf_address[buf_word_ofs + i]; + } + + // Copy the to-be-programmed data with AND operator. + for (uint32_t i = 0; i < buf_byte_count; ++i) + { + wrbuf.b[buf_byte_ofs + i] &= src[i]; + } + + // Program the buffer to flash. + DI(); + HASSERT(FPG(wrbuf.w + buf_word_ofs, + ((uint32_t)buf_address) + buf_word_ofs * BYTE_PER_WORD, + buf_word_count * BYTE_PER_WORD) == 0); + EI(); + + // Go to the next flash buffer page. + len -= buf_byte_count; + addr += buf_byte_count; + src += buf_byte_count; + } + } + + /// Reads data from the device. + /// @param addr where to read from (address space of the MCU) + /// @param buf points to where to put the data read + /// @param len how many bytes to read + static void read(uint32_t addr, void *buf, size_t len) + { + memcpy(buf, (void *)addr, len); + } + + /// Aligns an address to the next possible sector start (i.e., rounds up to + /// sector boundary). + /// @param addr an address in the flash address space. + /// @return If addr is the first byte of a sector, then returns addr + /// unmodified. Otherwise returns the starting address of the next sector. + static uint32_t next_sector_address(uint32_t addr) + { + return (addr + ERASE_PAGE_SIZE - 1) & ~(ERASE_PAGE_SIZE - 1); + } + + /// Erases sector(s) of the device. + /// @param addr beginning of the sector to erase. Must be sector aligned. + /// @param len how many bytes to erase (must be multiple of sector size). + static void erase(uint32_t addr, size_t len) + { + while (len) + { + DI(); + HASSERT(FER(addr) == 0); + EI(); + addr += ERASE_PAGE_SIZE; + len -= ERASE_PAGE_SIZE; + } + } +}; + +#undef DI +#undef EI +#undef FPG +#undef FER + +#endif // _FREERTOS_DRIVERS_TI_TIFLASH_HXX_ diff --git a/src/freertos_drivers/ti/TivaDCC.hxx b/src/freertos_drivers/ti/TivaDCC.hxx index 3166819c5..29b122afe 100644 --- a/src/freertos_drivers/ti/TivaDCC.hxx +++ b/src/freertos_drivers/ti/TivaDCC.hxx @@ -64,6 +64,7 @@ #include "driverlib/utils.h" #endif +#include "freertos_drivers/common/FixedQueue.hxx" #include "Devtab.hxx" #include "RailcomDriver.hxx" #include "dcc/DccOutput.hxx" @@ -75,99 +76,6 @@ /// 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 -/// -/// . one context uses only the front() and the other only the back() functions. -/// -/// . ++ and -- are compiled into atomic operations on the processor (on the -/// count_ variable). -/// -/// @deprecated, use @ref DeviceBuffer instead. -/// -/// @todo(balazs.racz) replace uses of this class with DeviceBuffer (to enable -/// select support for example). -template class FixedQueue { -public: - FixedQueue() - : rdIndex_(0) - , wrIndex_(0) - , count_(0) - { - } - - /// @return true if there is no entry in the queue. - bool empty() { return size() == 0; } - /// @return true if the queue cannot accept more elements. - bool full() { return size() >= SIZE; } - /// @return the current number of entries in the queue. - size_t size() { return __atomic_load_n(&count_, __ATOMIC_SEQ_CST); } - - /// Returns the head of the FIFO (next element to read). - T& front() { - HASSERT(!empty()); - return storage_[rdIndex_]; - } - - /// Removes the head of the FIFO from the queue. - void increment_front() { - HASSERT(!empty()); - if (++rdIndex_ >= SIZE) rdIndex_ = 0; - __atomic_fetch_add(&count_, -1, __ATOMIC_SEQ_CST); - } - - /// Returns the space to write the next element to. - T& back() { - HASSERT(!full()); - return storage_[wrIndex_]; - } - - /// Commits the element at back() into the queue. - void increment_back() { - HASSERT(!full()); - if (++wrIndex_ >= SIZE) wrIndex_ = 0; - __atomic_fetch_add(&count_, 1, __ATOMIC_SEQ_CST); - } - - /** Increments the back pointer without committing the entry into the - * queue. - * - * This essentially reserves an entry in the queue for filling in, without - * making that entry available for reading. Must be followed by a - * commit_back call when filling in the entry is finished. An arbitrary - * number of such entries can be reserved (up to the number of free entries - * in the queue). */ - void noncommit_back() { - HASSERT(has_noncommit_space()); - if (++wrIndex_ >= SIZE) wrIndex_ = 0; - } - - /** @returns true if we can do a noncommit back. */ - bool has_noncommit_space() { - if (full()) return false; - auto new_index = wrIndex_; - if (++new_index >= SIZE) new_index = 0; - return new_index != rdIndex_; - } - - /** Commits the oldest entry reserved by noncommit_back. */ - void commit_back() { - HASSERT(count_ <= SIZE); - __atomic_fetch_add(&count_, 1, __ATOMIC_SEQ_CST); - } - -private: - /// Payload of elements stored. - T storage_[SIZE]; - /// The index of the element to return next upon a read. This element is - /// typically full (unless the queue is empty itself). - uint8_t rdIndex_; - /// The index of the element where to write the next input to. - uint8_t wrIndex_; - /// How many elements are there in the queue. - volatile uint8_t count_; -}; - /** A device driver for sending DCC packets. If the packet queue is empty, * then the device driver automatically sends out idle DCC packets. The * device driver uses two instances of the 16/32-bit timer pairs. The user diff --git a/src/freertos_drivers/ti/TivaDev.hxx b/src/freertos_drivers/ti/TivaDev.hxx index 1bd0a92e0..b36d8023a 100644 --- a/src/freertos_drivers/ti/TivaDev.hxx +++ b/src/freertos_drivers/ti/TivaDev.hxx @@ -227,7 +227,7 @@ private: uint32_t interrupt_ : 8; /**< interrupt of this device */ uint32_t baud_ : 24; /**< desired baud rate */ uint8_t hwFIFO_; /**< enable HW FIFO */ - uint8_t mode_; /**< uart config (mode) flags */ + uint8_t uartMode_; /**< uart config (mode) flags */ uint8_t txPending_; /**< transmission currently pending */ /** Default constructor. diff --git a/src/freertos_drivers/ti/TivaEEPROMFile.hxx b/src/freertos_drivers/ti/TivaEEPROMFile.hxx index f9e6a0331..efcec0af7 100644 --- a/src/freertos_drivers/ti/TivaEEPROMFile.hxx +++ b/src/freertos_drivers/ti/TivaEEPROMFile.hxx @@ -75,10 +75,44 @@ public: /// @return number of bytes written upon success, -errno upon failure ssize_t write(unsigned int index, const void *buf, size_t len) override { - HASSERT(index % 4 == 0); - HASSERT(len % 4 == 0); - MAP_EEPROMProgram((uint32_t *)buf, index + byteOffset_, len); - return len; + size_t count = 0; + uint8_t *b = (uint8_t *)buf; + // Partial write at the beginning. + if (index & 0x3) + { + uint32_t rd = 0; + MAP_EEPROMRead(&rd, (index + byteOffset_) & ~0x3, 4); + size_t actual_len = 4 - (index & 0x3); + actual_len = std::min(len, actual_len); + memcpy(((uint8_t*)&rd) + (index & 0x3), b, actual_len); + MAP_EEPROMProgram(&rd, (index + byteOffset_) & ~0x3, 4); + len -= actual_len; + index += actual_len; + b += actual_len; + count += actual_len; + } + // Full writes in the middle. + size_t word_len = len & ~0x3; + if (word_len) + { + HASSERT((index & 0x3) == 0); + MAP_EEPROMProgram((uint32_t *)b, index + byteOffset_, word_len); + index += word_len; + b += word_len; + len -= word_len; + count += word_len; + } + // Partial write at the end. + if (len & 0x3) + { + HASSERT((index & 0x3) == 0); + uint32_t rd = 0; + MAP_EEPROMRead(&rd, index + byteOffset_, 4); + memcpy(&rd, b, len); + MAP_EEPROMProgram(&rd, (index + byteOffset_), 4); + count += len; + } + return count; } /// Read from the eeprom. @@ -88,10 +122,42 @@ public: /// @return number of bytes read upon success, -errno upon failure ssize_t read(unsigned int index, void *buf, size_t len) override { - HASSERT(index % 4 == 0); - HASSERT(len % 4 == 0); - MAP_EEPROMRead((uint32_t *)buf, index + byteOffset_, len); - return len; + size_t count = 0; + uint8_t *b = (uint8_t *)buf; + // Partial read at the beginning. + if (index & 0x3) + { + uint32_t rd = 0; + MAP_EEPROMRead(&rd, (index + byteOffset_) & ~0x3, 4); + size_t actual_len = 4 - (index & 0x3); + actual_len = std::min(len, actual_len); + memcpy(b, ((uint8_t *)&rd) + (index & 0x3), actual_len); + len -= actual_len; + index += actual_len; + b += actual_len; + count += actual_len; + } + // Full reads in the middle. + size_t word_len = len & ~0x3; + if (word_len) + { + HASSERT((index & 0x3) == 0); + MAP_EEPROMRead((uint32_t *)b, index + byteOffset_, word_len); + index += word_len; + b += word_len; + len -= word_len; + count += word_len; + } + // Partial read at the end. + if (len & 0x3) + { + HASSERT((index & 0x3) == 0); + uint32_t rd = 0; + MAP_EEPROMRead(&rd, index + byteOffset_, 4); + memcpy(b, &rd, len); + count += len; + } + return count; } private: diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx index ecabfde77..da84c510d 100644 --- a/src/freertos_drivers/ti/TivaRailcom.hxx +++ b/src/freertos_drivers/ti/TivaRailcom.hxx @@ -61,9 +61,7 @@ #error must define either TIVADCC_TIVA or TIVADCC_CC3200 #endif -#include "TivaDCC.hxx" // for FixedQueue - -#include "RailcomDriver.hxx" +#include "freertos_drivers/common/RailcomImpl.hxx" #include "dcc/RailCom.hxx" /* @@ -123,186 +121,40 @@ __attribute__((weak)) = {SYSCTL_PERIPH_UART4, SYSCTL_PERIPH_UART3, SYSCTL_PERIPH */ -/// Base class for railcom drivers. Ideally this base class should be -/// non-specific to the hardwsre or the TivaWare driver library. +/// Railcom driver for TI Tiva-class microcontrollers using the TivaWare +/// peripheral library. /// -/// @todo(balazs.racz) factor this class out into -/// freertos_drivers/common/Railcom.hxx. -template -class RailcomDriverBase : public RailcomDriver, private Node +/// This railcom driver supports parallel polling of multiple UART channels for +/// the railcom data. +template class TivaRailcomDriver : public RailcomDriverBase { public: - /// Constructor. @param name is the device node (e.g. "/dev/railcom0"). - RailcomDriverBase(const char *name) - : Node(name) - , readableNotifiable_(nullptr) + /// Constructor. @param path is the device node path (e.g. "/dev/railcom0"). + TivaRailcomDriver(const char *path) + : RailcomDriverBase(path) { - HW::hw_init(); MAP_IntPrioritySet(HW::OS_INTERRUPT, configKERNEL_INTERRUPT_PRIORITY); MAP_IntEnable(HW::OS_INTERRUPT); } - ~RailcomDriverBase() + ~TivaRailcomDriver() { MAP_IntDisable(HW::OS_INTERRUPT); } private: - ssize_t write(File *, const void *, size_t) OVERRIDE - { - // This device is read-only. - return -ENOSYS; - } - - ssize_t read(File *file, void *buf, size_t count) OVERRIDE - { - if (count != sizeof(dcc::Feedback)) - { - return -EINVAL; - } - OSMutexLock l(&lock_); - if (feedbackQueue_.empty()) - { - return -EAGAIN; - } - memcpy(buf, &feedbackQueue_.front(), count); - feedbackQueue_.increment_front(); - return count; - } - - void flush_buffers() OVERRIDE - { - while (!feedbackQueue_.empty()) - { - feedbackQueue_.increment_front(); - } - } - - // Asynchronous interface - int ioctl(File *file, unsigned long int key, unsigned long data) override - { - if (IOC_TYPE(key) == CAN_IOC_MAGIC && - IOC_SIZE(key) == NOTIFIABLE_TYPE && key == CAN_IOC_READ_ACTIVE) - { - Notifiable *n = reinterpret_cast(data); - HASSERT(n); - // If there is no data for reading, we put the incoming notification - // into the holder. Otherwise we notify it immediately. - if (feedbackQueue_.empty()) - { - portENTER_CRITICAL(); - if (feedbackQueue_.empty()) - { - // We are in a critical section now. If we got into this - // branch, then the buffer was full at the beginning of the - // critical section. If the hardware interrupt kicks in - // now, and sets the os_interrupt to pending, the os - // interrupt will not happen until we leave the critical - // section, and thus the swap will be in effect by then. - std::swap(n, readableNotifiable_); - } - portEXIT_CRITICAL(); - } - if (n) - { - n->notify(); - } - return 0; - } - errno = EINVAL; - return -1; - } - -public: - /// Implementation of the interrupt handler running at the kernel interrupt - /// priority. Call this function from the hardware interrupt handler. - void os_interrupt_handler() __attribute__((always_inline)) - { - if (!feedbackQueue_.empty()) - { - Notifiable *n = readableNotifiable_; - readableNotifiable_ = nullptr; - if (n) - { - n->notify_from_isr(); - os_isr_exit_yield_test(true); - } - } - } - -private: - void set_feedback_key(uint32_t key) OVERRIDE - { - feedbackKey_ = key; - } - -protected: - /** Takes a new empty packet at the front of the queue, fills in feedback - * key and channel information. - * @param channel is which channel to set the packet for. - * @return a packet pointer to write into. If there is no space, returns - * nullptr.*/ - dcc::Feedback *alloc_new_packet(uint8_t channel) - { - if (!feedbackQueue_.has_noncommit_space()) - { - return nullptr; - } - dcc::Feedback *entry = &feedbackQueue_.back(); - feedbackQueue_.noncommit_back(); - entry->reset(feedbackKey_); - entry->channel = channel; - return entry; - } - - /// Adds a sample for a preamble bit. @param sample is the next sample to - /// return to the application layer (usually a bitmask setting which - /// channels are active). - void add_sample(int sample) { - if (sample < 0) return; // skipped sampling - if (feedbackQueue_.full()) { - extern uint32_t feedback_sample_overflow_count; - ++feedback_sample_overflow_count; - return; - } - feedbackQueue_.back().reset(feedbackKey_); - feedbackQueue_.back().channel = HW::get_feedback_channel(); - feedbackQueue_.back().add_ch1_data(sample); - uint32_t tick_timer = HW::get_timer_tick(); - memcpy(feedbackQueue_.back().ch2Data, &tick_timer, 4); - feedbackQueue_.increment_back(); - MAP_IntPendSet(HW::OS_INTERRUPT); - } + /// True when we are currently within a cutout. + bool inCutout_ = false; - /** Notify this when we have data in our buffers. */ - Notifiable *readableNotifiable_; - /** The packets we have read so far. */ - FixedQueue feedbackQueue_; - /// Stores the key for the next packets to read. - uint32_t feedbackKey_; - /** Stores pointers to packets we are filling right now, one for each - * channel. */ - dcc::Feedback *returnedPackets_[HW::CHANNEL_COUNT]; -}; + using RailcomDriverBase::returnedPackets_; -/// Railcom driver for TI Tiva-class microcontrollers using the TivaWare -/// peripheral library. -/// -/// This railcom driver supports parallel polling of multiple UART channels for -/// the railcom data. -template class TivaRailcomDriver : public RailcomDriverBase -{ -public: - /// Constructor. @param path is the device node path (e.g. "/dev/railcom0"). - TivaRailcomDriver(const char *path) : RailcomDriverBase(path) + /// Sets a given software interrupt pending. + /// @param int_nr interrupt number (will be HW::OS_INTERRUPT) + void int_set_pending(unsigned int_nr) override { + MAP_IntPendSet(int_nr); } -private: - /// True when we are currently within a cutout. - bool inCutout_ = false; - - using RailcomDriverBase::returnedPackets_; // File node interface void enable() OVERRIDE { diff --git a/src/freertos_drivers/ti/TivaUart.cxx b/src/freertos_drivers/ti/TivaUart.cxx index 1ec1f1c27..384dd17d4 100644 --- a/src/freertos_drivers/ti/TivaUart.cxx +++ b/src/freertos_drivers/ti/TivaUart.cxx @@ -69,7 +69,7 @@ TivaUart::TivaUart(const char *name, unsigned long base, uint32_t interrupt, , interrupt_(interrupt) , baud_(baud) , hwFIFO_(hw_fifo) - , mode_(mode) + , uartMode_(mode) , txPending_(false) { static_assert( @@ -116,7 +116,7 @@ TivaUart::TivaUart(const char *name, unsigned long base, uint32_t interrupt, break; } - MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, mode_); + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, uartMode_); MAP_UARTTxIntModeSet(base_, UART_TXINT_MODE_EOT); MAP_IntDisable(interrupt_); /* We set the priority so that it is slightly lower than the highest needed @@ -260,7 +260,7 @@ void TivaUart::interrupt_handler() /** Sets the port baud rate and mode from the class variables. */ void TivaUart::set_mode() { - MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, mode_); + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, uartMode_); } /** Request an ioctl transaction @@ -283,38 +283,38 @@ int TivaUart::ioctl(File *file, unsigned long int key, unsigned long data) MAP_SysCtlDelay(12 * 26); break; case TCPARNONE: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_NONE; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_NONE; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_NONE); break; case TCPARODD: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ODD; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ODD; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ODD); break; case TCPAREVEN: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_EVEN; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_EVEN; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_EVEN); break; case TCPARONE: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ONE; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ONE; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ONE); break; case TCPARZERO: - mode_ &= ~UART_CONFIG_PAR_MASK; - mode_ |= UART_CONFIG_PAR_ZERO; + uartMode_ &= ~UART_CONFIG_PAR_MASK; + uartMode_ |= UART_CONFIG_PAR_ZERO; MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ZERO); break; case TCSTOPONE: - mode_ &= ~UART_CONFIG_STOP_MASK; - mode_ |= UART_CONFIG_STOP_ONE; + uartMode_ &= ~UART_CONFIG_STOP_MASK; + uartMode_ |= UART_CONFIG_STOP_ONE; set_mode(); break; case TCSTOPTWO: - mode_ &= ~UART_CONFIG_STOP_MASK; - mode_ |= UART_CONFIG_STOP_TWO; + uartMode_ &= ~UART_CONFIG_STOP_MASK; + uartMode_ |= UART_CONFIG_STOP_TWO; set_mode(); break; case TCBAUDRATE: diff --git a/src/openlcb/CompileCdiMain.cxx b/src/openlcb/CompileCdiMain.cxx index f8c391c98..2569b1d87 100644 --- a/src/openlcb/CompileCdiMain.cxx +++ b/src/openlcb/CompileCdiMain.cxx @@ -40,8 +40,10 @@ void render_cdi_helper(const CdiType &t, string ns, string name) printf("const char %s_DATA[] = R\"xmlpayload(%s)xmlpayload\";\n", name.c_str(), payload.c_str()); printf("extern const size_t %s_SIZE;\n", name.c_str()); - printf("const size_t %s_SIZE = sizeof(%s_DATA);\n", name.c_str(), + printf("extern const size_t %s_SIZE = sizeof(%s_DATA);\n", name.c_str(), name.c_str()); + printf("extern const size_t %s_END_OFFSET = %u;\n", name.c_str(), + (unsigned)t.end_offset()); printf("\n} // namespace %s\n\n", ns.c_str()); } } diff --git a/src/openlcb/StreamReceiverInterface.hxx b/src/openlcb/StreamReceiverInterface.hxx index f87eb1da5..fc7a743b3 100644 --- a/src/openlcb/StreamReceiverInterface.hxx +++ b/src/openlcb/StreamReceiverInterface.hxx @@ -41,7 +41,7 @@ template class FlowInterface; template class Buffer; -class ByteChunk; +struct ByteChunk; using ByteBuffer = Buffer; using ByteSink = FlowInterface; diff --git a/src/utils/Ewma.hxx b/src/utils/Ewma.hxx index facb3b4b6..42e796ccd 100644 --- a/src/utils/Ewma.hxx +++ b/src/utils/Ewma.hxx @@ -32,6 +32,9 @@ * @date 2 May 2015 */ +#ifndef _UTILS_EWMA_HXX_ +#define _UTILS_EWMA_HXX_ + #include #include @@ -178,3 +181,5 @@ public: float alpha_; ///< coefficient for EWMA float avg_{0.0}; ///< current state of EWMA }; + +#endif // _UTILS_EWMA_HXX_ diff --git a/src/utils/format_utils.cxx b/src/utils/format_utils.cxx index 9bed54be7..10138abb0 100644 --- a/src/utils/format_utils.cxx +++ b/src/utils/format_utils.cxx @@ -242,6 +242,57 @@ string string_to_hex(const string &arg) return ret; } +static uint8_t get_nibble(char b) +{ + if ('0' <= b && b <= '9') + { + return b - '0'; + } + if ('a' <= b && b <= 'f') + { + return b - 'a' + 10; + } + if ('A' <= b && b <= 'F') + { + return b - 'A' + 10; + } + return 0xff; +} + +size_t hex_to_string( + const char *input, size_t len, string *output, bool ignore_nonhex) +{ + uint8_t b = 0; // current byte + bool next_high = true; // next nibble is high of the byte + for (size_t ofs = 0; ofs < len; ++ofs) + { + uint8_t nib = get_nibble(input[ofs]); + if (nib == 0xff) + { + if (!ignore_nonhex) + { + return ofs; + } + continue; + } + b |= nib & 0xf; + if (next_high) + { + b <<= 4; + next_high = false; + continue; + } + else + { + output->push_back(b); + b = 0; + next_high = true; + continue; + } + } + return len; +} + string mac_to_string(uint8_t mac[6], char colon) { string ret; diff --git a/src/utils/format_utils.cxxtest b/src/utils/format_utils.cxxtest new file mode 100644 index 000000000..622e4a1da --- /dev/null +++ b/src/utils/format_utils.cxxtest @@ -0,0 +1,35 @@ +#include "utils/format_utils.hxx" + +#include "utils/test_main.hxx" + +TEST(HexToString, basic) +{ + string input = "303132"; + string output; + EXPECT_EQ(6u, hex_to_string(input.data(), input.size(), &output)); + EXPECT_EQ("012", output); + + output = "xx"; + EXPECT_EQ(6u, hex_to_string(input.data(), input.size(), &output)); + EXPECT_EQ("xx012", output); + + output.clear(); + input = "0A0BfC"; + EXPECT_EQ(6u, hex_to_string(input.data(), input.size(), &output)); + EXPECT_EQ("\x0a\x0b\xfc", output); + + output.clear(); + input = "30.31.32"; + EXPECT_EQ(2u, hex_to_string(input.data(), input.size(), &output)); + EXPECT_EQ("0", output); + + output.clear(); + EXPECT_EQ(8u, hex_to_string(input.data(), input.size(), &output, true)); + EXPECT_EQ("012", output); + + output.clear(); + input = "303132F"; + EXPECT_EQ(7u, hex_to_string(input.data(), input.size(), &output)); + // lost one nibble, this is ok according to the contract + EXPECT_EQ("012", output); +} diff --git a/src/utils/format_utils.hxx b/src/utils/format_utils.hxx index 80ed7ab78..2f3cbaf73 100644 --- a/src/utils/format_utils.hxx +++ b/src/utils/format_utils.hxx @@ -130,6 +130,21 @@ string int64_to_string_hex(int64_t value, unsigned padding = 0); /// original data. string string_to_hex(const string& arg); +/// Converts hex bytes to binary representation. +/// +/// @param input points to the input hexadecimal string +/// @param len how many bytes are there +/// @param output parsed byte data will be appended here +/// @param ignore_nonhex if true, jumps over all nonhex characters. If false, +/// stops at the first nonhex character. +/// +/// @return number of ascii bytes consumed from the input. For example 0 if the +/// first byte was not hex and ignore_nonhex=false. If the number of hex digits +/// is not even, there will be data loss. +/// +size_t hex_to_string( + const char *input, size_t len, string *output, bool ignore_nonhex = false); + /// Formats a MAC address to string. Works both for Ethernet addresses as well /// as for OpenLCB node IDs. ///