From 43bfc8826ac6ea24d735382408821275ad9e8265 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 19 Aug 2021 15:22:04 +0200 Subject: [PATCH 01/62] Optimizes includes (#566) Reduces the compilation footprint by optimizing which files include which other files. This enables writing code that compiles faster. === * Makes the compile flag parametrizable for gcc. * Adds commands to generate statistics about includes. * Adds missing include. * Refactors the SNIP static valuesto a separate include file. * Refactors static includes from memoryconfig into a separate file. * Moves out the declaration of conversion helper functions from If.hxx to a new file Convert.hxx. * Makes the traction throttle interface usable without having a class declaration for traction throttle itself. * Separates the traction throttle interface from the implementation. * Fixes whitespace. * fix hanging brace --- etc/freertos.armv7m.mk | 8 +- etc/prog.mk | 11 + src/openlcb/ConfigEntry.hxx | 5 +- src/openlcb/ConfigRenderer.hxx | 2 +- src/openlcb/ConfigRepresentation.hxx | 2 +- src/openlcb/Convert.hxx | 133 +++++++++++ src/openlcb/Defs.hxx | 3 + src/openlcb/If.hxx | 93 +------- src/openlcb/MemoryConfig.hxx | 228 +----------------- src/openlcb/MemoryConfigDefs.hxx | 279 ++++++++++++++++++++++ src/openlcb/SimpleNodeInfo.hxx | 33 +-- src/openlcb/SimpleNodeInfoDefs.hxx | 78 ++++++ src/openlcb/TractionDefs.hxx | 7 - src/openlcb/TractionTestTrain.cxx | 1 + src/openlcb/TractionThrottle.hxx | 177 +------------- src/openlcb/TractionThrottleInterface.hxx | 231 ++++++++++++++++++ src/openlcb/TrainInterface.hxx | 9 +- src/openlcb/Velocity.hxx | 7 + 18 files changed, 773 insertions(+), 534 deletions(-) create mode 100644 src/openlcb/Convert.hxx create mode 100644 src/openlcb/MemoryConfigDefs.hxx create mode 100644 src/openlcb/SimpleNodeInfoDefs.hxx create mode 100644 src/openlcb/TractionThrottleInterface.hxx diff --git a/etc/freertos.armv7m.mk b/etc/freertos.armv7m.mk index 8782b078c..7b4b288fc 100644 --- a/etc/freertos.armv7m.mk +++ b/etc/freertos.armv7m.mk @@ -48,19 +48,21 @@ ifdef DEBUG_MEMORY_USE ARCHFLAGS += -funwind-tables endif -ASFLAGS = -c $(ARCHFLAGS) +COMPILEOPT = -c + +ASFLAGS = $(COMPILEOPT) $(ARCHFLAGS) CORECFLAGS = $(ARCHFLAGS) -Wall -Werror -Wno-unknown-pragmas \ -fdata-sections -ffunction-sections \ -fno-builtin -fno-stack-protector -mfix-cortex-m3-ldrd \ -D__FreeRTOS__ -DGCC_ARMCM3 -specs=nano.specs -CFLAGS += -c $(ARCHOPTIMIZATION) $(CORECFLAGS) -std=c99 \ +CFLAGS += $(COMPILEOPT) $(ARCHOPTIMIZATION) $(CORECFLAGS) -std=c99 \ -Wstrict-prototypes -D_REENT_SMALL \ $(CFLAGSENV) $(CFLAGSEXTRA) \ -CXXFLAGS += -c $(ARCHOPTIMIZATION) $(CORECFLAGS) -std=c++14 \ +CXXFLAGS += $(COMPILEOPT) $(ARCHOPTIMIZATION) $(CORECFLAGS) -std=c++14 \ -D_ISOC99_SOURCE -D__STDC_FORMAT_MACROS \ -fno-exceptions -fno-rtti \ -Wsuggest-override -Wno-psabi \ diff --git a/etc/prog.mk b/etc/prog.mk index 778568048..325e3e693 100644 --- a/etc/prog.mk +++ b/etc/prog.mk @@ -238,6 +238,17 @@ endif cg.svg: $(EXECUTABLE).ndlst $(OPENMRNPATH)/bin/callgraph.py $(OPENMRNPATH)/bin/callgraph.py --max_indep 6 --min_size $(CGMINSIZE) $(CGARGS) --map $(EXECUTABLE).map < $(EXECUTABLE).ndlst 2> cg.debug.txt | tee cg.dot | dot -Tsvg > cg.svg +incstatsprep: + make -j3 clean + $(MAKE) -j9 -k COMPILEOPT=-E || true + +incstats: incstatsprep + $(MAKE) cincstats + +cincstats: + @find . -name "*.o" | xargs cat | wc -l + @find . -name "*.o" | xargs -L 1 parse-gcc-e.awk | sort -k 2 | group.awk | sort -n > /tmp/stats.txt + -include $(OBJS:.o=.d) -include $(TESTOBJS:.o=.d) diff --git a/src/openlcb/ConfigEntry.hxx b/src/openlcb/ConfigEntry.hxx index a0af9ef0c..1891744e5 100644 --- a/src/openlcb/ConfigEntry.hxx +++ b/src/openlcb/ConfigEntry.hxx @@ -35,9 +35,10 @@ #ifndef _OPENLCB_CONFIGENTRY_HXX_ #define _OPENLCB_CONFIGENTRY_HXX_ -#include -#include #include +#include +#include +#include #include diff --git a/src/openlcb/ConfigRenderer.hxx b/src/openlcb/ConfigRenderer.hxx index cfa078d28..5a39225f0 100644 --- a/src/openlcb/ConfigRenderer.hxx +++ b/src/openlcb/ConfigRenderer.hxx @@ -38,7 +38,7 @@ #include #include -#include "openlcb/SimpleNodeInfo.hxx" +#include "openlcb/SimpleNodeInfoDefs.hxx" #include "utils/OptionalArgs.hxx" #include "utils/StringPrintf.hxx" diff --git a/src/openlcb/ConfigRepresentation.hxx b/src/openlcb/ConfigRepresentation.hxx index 7e7e96c22..0b8e1560b 100644 --- a/src/openlcb/ConfigRepresentation.hxx +++ b/src/openlcb/ConfigRepresentation.hxx @@ -36,7 +36,7 @@ #define _OPENLCB_CONFIGREPRESENTATION_HXX_ #include "openlcb/ConfigEntry.hxx" -#include "openlcb/MemoryConfig.hxx" +#include "openlcb/MemoryConfigDefs.hxx" namespace openlcb { diff --git a/src/openlcb/Convert.hxx b/src/openlcb/Convert.hxx new file mode 100644 index 000000000..23f3f0c97 --- /dev/null +++ b/src/openlcb/Convert.hxx @@ -0,0 +1,133 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Convert.hxx + * + * Conversion routines for OpenLCB types. + * + * @author Balazs Racz + * @date 17 Aug 2021 + */ + +#ifndef _OPENLCB_CONVERT_HXX_ +#define _OPENLCB_CONVERT_HXX_ + +#include +#include + +#include "openlcb/Defs.hxx" + +namespace openlcb +{ + +/** Convenience function to render a 48-bit NMRAnet node ID into a new buffer. + * + * @param id is the 48-bit ID to render. + * @returns a new buffer (from the main pool) with 6 bytes of used space, a + * big-endian representation of the node ID. + */ +extern string node_id_to_buffer(NodeID id); +/** Convenience function to render a 48-bit NMRAnet node ID into an existing + * buffer. + * + * @param id is the 48-bit ID to render. + * @param data is the memory space to write the rendered ID into. There must be + * at least 6 bytes available at this address. + */ +extern void node_id_to_data(NodeID id, void *data); + +/** Converts a 6-byte-long buffer to a node ID. + * + * @param buf is a buffer that has to have exactly 6 bytes used, filled with a + * big-endian node id. + * @returns the node id (in host endian). + */ +extern NodeID buffer_to_node_id(const string &buf); +/** Converts 6 bytes of big-endian data to a node ID. + * + * @param d is a pointer to at least 6 valid bytes. + * @returns the node ID represented by the first 6 bytes of d. + */ +extern NodeID data_to_node_id(const void *d); + +/** Converts an Event ID to a Payload suitable to be sent as an event report. */ +extern Payload eventid_to_buffer(uint64_t eventid); + +/** Takes 8 bytes (big-endian) from *data, and returns the event id they + * represent. */ +inline uint64_t data_to_eventid(const void *data) +{ + uint64_t ret = 0; + memcpy(&ret, data, 8); + return be64toh(ret); +} + +/** Formats a payload for response of error response messages such as OPtioanl + * Interaction Rejected or Terminate Due To Error. */ +extern string error_to_buffer(uint16_t error_code, uint16_t mti); + +/** Formats a payload for response of error response messages such as Datagram + * Rejected. */ +extern string error_to_buffer(uint16_t error_code); + +/** Writes an error code into a payload object at a given pointer. */ +extern void error_to_data(uint16_t error_code, void *data); + +/** Parses an error code from a payload object at a given pointer. */ +extern uint16_t data_to_error(const void *data); + +/** Appends an error to the end of an existing buffer. */ +extern void append_error_to_buffer(uint16_t error_code, Payload *p); + +/** Parses the payload of an Optional Interaction Rejected or Terminate Due To + * Error message. + * @param payload is the contents of the incoming addressed message. + * @param error_code will hold the 2-byte error code, or ERROR_PERMANENT if not + * specified + * @param mti will hold the MTI value, or 0 if not specified + * @param error_message will hold all remaining bytes that came with the error + * message. + */ +extern void buffer_to_error(const Payload &payload, uint16_t *error_code, + uint16_t *mti, string *error_message); + +/** A global class / variable for empty or not-yet-initialized payloads. */ +extern string EMPTY_PAYLOAD; + +/// @return the high 4 bytes of a node ID. @param id is the node ID. +inline unsigned node_high(NodeID id) +{ + return id >> 32; +} +/// @return the low 4 bytes of a node ID. @param id is the node ID. +inline unsigned node_low(NodeID id) +{ + return id & 0xffffffffU; +} + +} // namespace openlcb + +#endif diff --git a/src/openlcb/Defs.hxx b/src/openlcb/Defs.hxx index 855e6bb31..8e047b797 100644 --- a/src/openlcb/Defs.hxx +++ b/src/openlcb/Defs.hxx @@ -47,6 +47,9 @@ typedef uint64_t NodeID; /** Alias to a 48-bit NMRAnet Node ID type */ typedef uint16_t NodeAlias; +/// Container that carries the data bytes in an NMRAnet message. +typedef string Payload; + /// Guard value put into the the internal node alias maps when a node ID could /// not be translated to a valid alias. static const NodeAlias NOT_RESPONDING = 0xF000; diff --git a/src/openlcb/If.hxx b/src/openlcb/If.hxx index f6b181bd6..f76384c2c 100644 --- a/src/openlcb/If.hxx +++ b/src/openlcb/If.hxx @@ -38,104 +38,21 @@ /// @todo(balazs.racz) remove this dep #include -#include "openlcb/Node.hxx" -#include "openlcb/Defs.hxx" #include "executor/Dispatcher.hxx" -#include "executor/Service.hxx" #include "executor/Executor.hxx" +#include "executor/Service.hxx" +#include "openlcb/Convert.hxx" +#include "openlcb/Defs.hxx" +#include "openlcb/Node.hxx" #include "utils/Buffer.hxx" -#include "utils/Queue.hxx" #include "utils/Map.hxx" +#include "utils/Queue.hxx" namespace openlcb { class Node; -/// Container that carries the data bytes in an NMRAnet message. -typedef string Payload; - -/** Convenience function to render a 48-bit NMRAnet node ID into a new buffer. - * - * @param id is the 48-bit ID to render. - * @returns a new buffer (from the main pool) with 6 bytes of used space, a - * big-endian representation of the node ID. - */ -extern string node_id_to_buffer(NodeID id); -/** Convenience function to render a 48-bit NMRAnet node ID into an existing - * buffer. - * - * @param id is the 48-bit ID to render. - * @param data is the memory space to write the rendered ID into. There must be - * at least 6 bytes available at this address. - */ -extern void node_id_to_data(NodeID id, void* data); - -/** Converts a 6-byte-long buffer to a node ID. - * - * @param buf is a buffer that has to have exactly 6 bytes used, filled with a - * big-endian node id. - * @returns the node id (in host endian). - */ -extern NodeID buffer_to_node_id(const string& buf); -/** Converts 6 bytes of big-endian data to a node ID. - * - * @param d is a pointer to at least 6 valid bytes. - * @returns the node ID represented by the first 6 bytes of d. - */ -extern NodeID data_to_node_id(const void* d); - -/** Converts an Event ID to a Payload suitable to be sent as an event report. */ -extern Payload eventid_to_buffer(uint64_t eventid); - -/** Takes 8 bytes (big-endian) from *data, and returns the event id they - * represent. */ -inline uint64_t data_to_eventid(const void* data) { - uint64_t ret = 0; - memcpy(&ret, data, 8); - return be64toh(ret); -} - -/** Formats a payload for response of error response messages such as OPtioanl - * Interaction Rejected or Terminate Due To Error. */ -extern string error_to_buffer(uint16_t error_code, uint16_t mti); - -/** Formats a payload for response of error response messages such as Datagram - * Rejected. */ -extern string error_to_buffer(uint16_t error_code); - -/** Writes an error code into a payload object at a given pointer. */ -extern void error_to_data(uint16_t error_code, void* data); - -/** Parses an error code from a payload object at a given pointer. */ -extern uint16_t data_to_error(const void *data); - -/** Appends an error to the end of an existing buffer. */ -extern void append_error_to_buffer(uint16_t error_code, Payload* p); - -/** Parses the payload of an Optional Interaction Rejected or Terminate Due To - * Error message. - * @param payload is the contents of the incoming addressed message. - * @param error_code will hold the 2-byte error code, or ERROR_PERMANENT if not - * specified - * @param mti will hold the MTI value, or 0 if not specified - * @param error_message will hold all remaining bytes that came with the error - * message. - */ -extern void buffer_to_error(const Payload& payload, uint16_t* error_code, uint16_t* mti, string* error_message); - -/** A global class / variable for empty or not-yet-initialized payloads. */ -extern string EMPTY_PAYLOAD; - -/// @return the high 4 bytes of a node ID. @param id is the node ID. -inline unsigned node_high(NodeID id) { - return id >> 32; -} -/// @return the low 4 bytes of a node ID. @param id is the node ID. -inline unsigned node_low(NodeID id) { - return id & 0xffffffffU; -} - /// Helper function to send an event report to the bus. Performs /// synchronous (dynamic) memory allocation so use it sparingly and when /// there is sufficient amount of RAM available. diff --git a/src/openlcb/MemoryConfig.hxx b/src/openlcb/MemoryConfig.hxx index c205ec083..04dd3cffc 100644 --- a/src/openlcb/MemoryConfig.hxx +++ b/src/openlcb/MemoryConfig.hxx @@ -35,12 +35,12 @@ #ifndef _OPENLCB_MEMORYCONFIG_HXX_ #define _OPENLCB_MEMORYCONFIG_HXX_ -#include "openmrn_features.h" #include "openlcb/DatagramDefs.hxx" #include "openlcb/DatagramHandlerDefault.hxx" -#include "openlcb/MemoryConfig.hxx" -#include "utils/Destructable.hxx" +#include "openlcb/MemoryConfigDefs.hxx" +#include "openmrn_features.h" #include "utils/ConfigUpdateService.hxx" +#include "utils/Destructable.hxx" class Notifiable; @@ -55,228 +55,6 @@ extern void reboot(); namespace openlcb { -/// Static constants and helper functions related to the Memory Configuration -/// Protocol. -struct MemoryConfigDefs { - /** Possible Commands for a configuration datagram. - */ - enum commands - { - COMMAND_MASK = 0xFC, - COMMAND_FLAG_MASK = 0x03, /**< mask for special memory space flags */ - COMMAND_PRESENT_MASK = 0x01, /**< mask for address space present bit */ - COMMAND_REPLY_BIT_FOR_RW = 0x10, /**< This bit is present in REPLY commands for read-write commands. */ - - COMMAND_WRITE = 0x00, /**< command to write data to address space */ - COMMAND_WRITE_UNDER_MASK = 0x08, /**< command to write data under mask */ - COMMAND_WRITE_REPLY = 0x10, /**< reply to write data to address space */ - COMMAND_WRITE_FAILED = 0x18, /**< failed to write data to address space */ - COMMAND_WRITE_STREAM = 0x20, /**< command to write data using a stream */ - COMMAND_WRITE_STREAM_REPLY= 0x30, /**< reply to write data using a stream */ - COMMAND_WRITE_STREAM_FAILED= 0x38, /**< failed to write data using a stream */ - COMMAND_READ = 0x40, /**< command to read data from address space */ - COMMAND_READ_REPLY = 0x50, /**< reply to read data from address space */ - COMMAND_READ_FAILED = 0x58, /**< failed to read data from address space */ - COMMAND_READ_STREAM = 0x60, /**< command to read data using a stream */ - COMMAND_MAX_FOR_RW = 0x80, /**< command <= this value have fixed bit arrangement. */ - COMMAND_OPTIONS = 0x80, - COMMAND_OPTIONS_REPLY = 0x82, - COMMAND_INFORMATION = 0x84, - COMMAND_INFORMATION_REPLY = 0x86, - COMMAND_LOCK = 0x88, /**< lock the configuration space */ - COMMAND_LOCK_REPLY = 0x8A, /**< unlock the configuration space */ - COMMAND_UNIQUE_ID = 0x8C, /**< ask for a node unique id */ - COMMAND_UNIQUE_ID_REPLY = 0x8D, /**< node unique id */ - COMMAND_UPDATE_COMPLETE = 0xA8, /**< indicate that a sequence of commands is complete */ - COMMAND_RESET = 0xA9, /**< reset node to its power on state */ - COMMAND_FACTORY_RESET = 0xAA, /**< reset node to factory defaults */ - COMMAND_ENTER_BOOTLOADER = 0xAB, /**< reset node in bootloader mode */ - COMMAND_FREEZE = 0xA1, /**< freeze operation of node */ - COMMAND_UNFREEZE = 0xA0, /**< unfreeze operation of node */ - - COMMAND_PRESENT = 0x01, /**< address space is present */ - - COMMAND_CDI = 0x03, /**< flags for a CDI space */ - COMMAND_ALL_MEMORY = 0x02, /**< flags for an all memory space */ - COMMAND_CONFIG = 0x01, /**< flags for a config memory space */ - }; - - /** Possible memory spaces. - */ - enum spaces - { - SPACE_SPECIAL = 0xFC, /**< offset for the special memory spaces */ - SPACE_CDI = 0xFF, /**< CDI space */ - SPACE_ALL_MEMORY = 0xFE, /**< all memory space */ - SPACE_CONFIG = 0xFD, /**< config memory space */ - SPACE_ACDI_SYS = 0xFC, /**< read-only ACDI space */ - SPACE_ACDI_USR = 0xFB, /**< read-write ACDI space */ - SPACE_FDI = 0xFA, /**< read-only for function definition XML */ - SPACE_FUNCTION = 0xF9, /**< read-write for function data */ - SPACE_DCC_CV = 0xF8, /**< proxy space for DCC functions */ - SPACE_FIRMWARE = 0xEF, /**< firmware upgrade space */ - }; - - /** Possible available options. - */ - enum available - { - AVAIL_WUM = 0x8000, /**< write under mask supported */ - AVAIL_UR = 0x4000, /**< unaligned reads supported */ - AVAIL_UW = 0x2000, /**< unaligned writes supported */ - AVAIL_R0xFC = 0x0800, /**< read from adddress space 0xFC available */ - AVAIL_R0xFB = 0x0400, /**< read from adddress space 0xFB available */ - AVAIL_W0xFB = 0x0200, /**< write from adddress space 0xFB available */ - }; - - /** Possible supported write lengths. - */ - enum lengths - { - LENGTH_1 = 0x80, /**< write length of 1 supported */ - LENGTH_2 = 0x40, /**< write length of 2 supported */ - LENGTH_4 = 0x20, /**< write length of 4 supported */ - LENGTH_63 = 0x10, /**< write length of 64 supported */ - LENGTH_ARBITRARY = 0x02, /**< arbitrary write of any length supported */ - LENGTH_STREAM = 0x01, /**< stream writes supported */ - }; - - /** Possible address space information flags. - */ - enum flags - { - FLAG_RO = 0x01, /**< space is read only */ - FLAG_NZLA = 0x02, /**< space has a nonzero low address */ - }; - - enum errors - { - ERROR_SPACE_NOT_KNOWN = Defs::ERROR_INVALID_ARGS | 0x0001, - ERROR_OUT_OF_BOUNDS = Defs::ERROR_INVALID_ARGS | 0x0002, - ERROR_WRITE_TO_RO = Defs::ERROR_INVALID_ARGS | 0x0003, - }; - - static constexpr unsigned MAX_DATAGRAM_RW_BYTES = 64; - - static bool is_special_space(uint8_t space) { - return space > SPACE_SPECIAL; - } - - static DatagramPayload write_datagram( - uint8_t space, uint32_t offset, const string &data = "") - { - DatagramPayload p; - p.reserve(7 + data.size()); - p.push_back(DatagramDefs::CONFIGURATION); - p.push_back(COMMAND_WRITE); - p.push_back(0xff & (offset >> 24)); - p.push_back(0xff & (offset >> 16)); - p.push_back(0xff & (offset >> 8)); - p.push_back(0xff & (offset)); - if (is_special_space(space)) { - p[1] |= space & ~SPACE_SPECIAL; - } else { - p.push_back(space); - } - p += data; - return p; - } - - static DatagramPayload read_datagram( - uint8_t space, uint32_t offset, uint8_t length) - { - DatagramPayload p; - p.reserve(7); - p.push_back(DatagramDefs::CONFIGURATION); - p.push_back(COMMAND_READ); - p.push_back(0xff & (offset >> 24)); - p.push_back(0xff & (offset >> 16)); - p.push_back(0xff & (offset >> 8)); - p.push_back(0xff & (offset)); - if (is_special_space(space)) { - p[1] |= space & ~SPACE_SPECIAL; - } else { - p.push_back(space); - } - p.push_back(length); - return p; - } - - /// @return true if the payload has minimum number of bytes you need in a - /// read or write datagram message to cover for the necessary fields - /// (command, offset, space). - /// @param payload is a datagram (read or write, request or response) - /// @param extra is the needed bytes after address and space, usually 0 for - /// write and 1 for read. - static bool payload_min_length_check( - const DatagramPayload &payload, unsigned extra) - { - auto *bytes = payload_bytes(payload); - size_t sz = payload.size(); - if (sz < 6 + extra) - { - return false; - } - if (((bytes[1] & COMMAND_FLAG_MASK) == 0) && (sz < 7 + extra)) - { - return false; - } - return true; - } - - /// @return addressed memory space number. - /// @param payload is a read or write datagram request or response message - static uint8_t get_space(const DatagramPayload &payload) - { - auto *bytes = payload_bytes(payload); - if (bytes[1] & COMMAND_FLAG_MASK) - { - return COMMAND_MASK + (bytes[1] & COMMAND_FLAG_MASK); - } - return bytes[6]; - } - - static unsigned get_payload_offset(const DatagramPayload &payload) - { - auto *bytes = payload_bytes(payload); - if (bytes[1] & COMMAND_FLAG_MASK) - { - return 6; - } - else - { - return 7; - } - } - - /// @param payload is a datagram read or write request or response - /// @return the address field from the datagram - static uint32_t get_address(const DatagramPayload &payload) - { - auto *bytes = payload_bytes(payload); - uint32_t a = bytes[2]; - a <<= 8; - a |= bytes[3]; - a <<= 8; - a |= bytes[4]; - a <<= 8; - a |= bytes[5]; - return a; - } - - /// Type casts a DatagramPayload to an array of bytes. - /// @param payload datagram - /// @return byte array pointing to the same memory - static const uint8_t *payload_bytes(const DatagramPayload &payload) - { - return (uint8_t *)payload.data(); - } - -private: - /** Do not instantiate this class. */ - MemoryConfigDefs(); -}; - /// Abstract base class for the address spaces exported via the Memory Config /// Protocol. /// diff --git a/src/openlcb/MemoryConfigDefs.hxx b/src/openlcb/MemoryConfigDefs.hxx new file mode 100644 index 000000000..34432e537 --- /dev/null +++ b/src/openlcb/MemoryConfigDefs.hxx @@ -0,0 +1,279 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file MemoryConfigDefs.hxx + * + * Declarations related to the memory config protocol + * + * @author Balazs Racz + * @date 17 Aug 2021 + */ + +#ifndef _OPENLCB_MEMORYCONFIGDEFS_HXX_ +#define _OPENLCB_MEMORYCONFIGDEFS_HXX_ + +#include "openlcb/DatagramDefs.hxx" +#include "openlcb/Defs.hxx" +#include "utils/macros.h" + +namespace openlcb +{ + +/// Static constants and helper functions related to the Memory Configuration +/// Protocol. +struct MemoryConfigDefs +{ + using DatagramPayload = string; + + /** Possible Commands for a configuration datagram. + */ + enum commands + { + COMMAND_MASK = 0xFC, + COMMAND_FLAG_MASK = 0x03, /**< mask for special memory space flags */ + COMMAND_PRESENT_MASK = 0x01, /**< mask for address space present bit */ + COMMAND_REPLY_BIT_FOR_RW = 0x10, /**< This bit is present in REPLY commands for read-write commands. */ + + COMMAND_WRITE = 0x00, /**< command to write data to address space */ + COMMAND_WRITE_UNDER_MASK = 0x08, /**< command to write data under mask */ + COMMAND_WRITE_REPLY = 0x10, /**< reply to write data to address space */ + COMMAND_WRITE_FAILED = 0x18, /**< failed to write data to address space */ + COMMAND_WRITE_STREAM = 0x20, /**< command to write data using a stream */ + COMMAND_WRITE_STREAM_REPLY= 0x30, /**< reply to write data using a stream */ + COMMAND_WRITE_STREAM_FAILED= 0x38, /**< failed to write data using a stream */ + COMMAND_READ = 0x40, /**< command to read data from address space */ + COMMAND_READ_REPLY = 0x50, /**< reply to read data from address space */ + COMMAND_READ_FAILED = 0x58, /**< failed to read data from address space */ + COMMAND_READ_STREAM = 0x60, /**< command to read data using a stream */ + COMMAND_MAX_FOR_RW = 0x80, /**< command <= this value have fixed bit arrangement. */ + COMMAND_OPTIONS = 0x80, + COMMAND_OPTIONS_REPLY = 0x82, + COMMAND_INFORMATION = 0x84, + COMMAND_INFORMATION_REPLY = 0x86, + COMMAND_LOCK = 0x88, /**< lock the configuration space */ + COMMAND_LOCK_REPLY = 0x8A, /**< unlock the configuration space */ + COMMAND_UNIQUE_ID = 0x8C, /**< ask for a node unique id */ + COMMAND_UNIQUE_ID_REPLY = 0x8D, /**< node unique id */ + COMMAND_UPDATE_COMPLETE = 0xA8, /**< indicate that a sequence of commands is complete */ + COMMAND_RESET = 0xA9, /**< reset node to its power on state */ + COMMAND_FACTORY_RESET = 0xAA, /**< reset node to factory defaults */ + COMMAND_ENTER_BOOTLOADER = 0xAB, /**< reset node in bootloader mode */ + COMMAND_FREEZE = 0xA1, /**< freeze operation of node */ + COMMAND_UNFREEZE = 0xA0, /**< unfreeze operation of node */ + + COMMAND_PRESENT = 0x01, /**< address space is present */ + + COMMAND_CDI = 0x03, /**< flags for a CDI space */ + COMMAND_ALL_MEMORY = 0x02, /**< flags for an all memory space */ + COMMAND_CONFIG = 0x01, /**< flags for a config memory space */ + }; + + /** Possible memory spaces. + */ + enum spaces + { + SPACE_SPECIAL = 0xFC, /**< offset for the special memory spaces */ + SPACE_CDI = 0xFF, /**< CDI space */ + SPACE_ALL_MEMORY = 0xFE, /**< all memory space */ + SPACE_CONFIG = 0xFD, /**< config memory space */ + SPACE_ACDI_SYS = 0xFC, /**< read-only ACDI space */ + SPACE_ACDI_USR = 0xFB, /**< read-write ACDI space */ + SPACE_FDI = 0xFA, /**< read-only for function definition XML */ + SPACE_FUNCTION = 0xF9, /**< read-write for function data */ + SPACE_DCC_CV = 0xF8, /**< proxy space for DCC functions */ + SPACE_FIRMWARE = 0xEF, /**< firmware upgrade space */ + }; + + /** Possible available options. + */ + enum available + { + AVAIL_WUM = 0x8000, /**< write under mask supported */ + AVAIL_UR = 0x4000, /**< unaligned reads supported */ + AVAIL_UW = 0x2000, /**< unaligned writes supported */ + AVAIL_R0xFC = 0x0800, /**< read from adddress space 0xFC available */ + AVAIL_R0xFB = 0x0400, /**< read from adddress space 0xFB available */ + AVAIL_W0xFB = 0x0200, /**< write from adddress space 0xFB available */ + }; + + /** Possible supported write lengths. + */ + enum lengths + { + LENGTH_1 = 0x80, /**< write length of 1 supported */ + LENGTH_2 = 0x40, /**< write length of 2 supported */ + LENGTH_4 = 0x20, /**< write length of 4 supported */ + LENGTH_63 = 0x10, /**< write length of 64 supported */ + LENGTH_ARBITRARY = 0x02, /**< arbitrary write of any length supported */ + LENGTH_STREAM = 0x01, /**< stream writes supported */ + }; + + /** Possible address space information flags. + */ + enum flags + { + FLAG_RO = 0x01, /**< space is read only */ + FLAG_NZLA = 0x02, /**< space has a nonzero low address */ + }; + + enum errors + { + ERROR_SPACE_NOT_KNOWN = Defs::ERROR_INVALID_ARGS | 0x0001, + ERROR_OUT_OF_BOUNDS = Defs::ERROR_INVALID_ARGS | 0x0002, + ERROR_WRITE_TO_RO = Defs::ERROR_INVALID_ARGS | 0x0003, + }; + + static constexpr unsigned MAX_DATAGRAM_RW_BYTES = 64; + + static bool is_special_space(uint8_t space) + { + return space > SPACE_SPECIAL; + } + + static DatagramPayload write_datagram( + uint8_t space, uint32_t offset, const string &data = "") + { + DatagramPayload p; + p.reserve(7 + data.size()); + p.push_back(DatagramDefs::CONFIGURATION); + p.push_back(COMMAND_WRITE); + p.push_back(0xff & (offset >> 24)); + p.push_back(0xff & (offset >> 16)); + p.push_back(0xff & (offset >> 8)); + p.push_back(0xff & (offset)); + if (is_special_space(space)) + { + p[1] |= space & ~SPACE_SPECIAL; + } + else + { + p.push_back(space); + } + p += data; + return p; + } + + static DatagramPayload read_datagram( + uint8_t space, uint32_t offset, uint8_t length) + { + DatagramPayload p; + p.reserve(7); + p.push_back(DatagramDefs::CONFIGURATION); + p.push_back(COMMAND_READ); + p.push_back(0xff & (offset >> 24)); + p.push_back(0xff & (offset >> 16)); + p.push_back(0xff & (offset >> 8)); + p.push_back(0xff & (offset)); + if (is_special_space(space)) + { + p[1] |= space & ~SPACE_SPECIAL; + } + else + { + p.push_back(space); + } + p.push_back(length); + return p; + } + + /// @return true if the payload has minimum number of bytes you need in a + /// read or write datagram message to cover for the necessary fields + /// (command, offset, space). + /// @param payload is a datagram (read or write, request or response) + /// @param extra is the needed bytes after address and space, usually 0 for + /// write and 1 for read. + static bool payload_min_length_check( + const DatagramPayload &payload, unsigned extra) + { + auto *bytes = payload_bytes(payload); + size_t sz = payload.size(); + if (sz < 6 + extra) + { + return false; + } + if (((bytes[1] & COMMAND_FLAG_MASK) == 0) && (sz < 7 + extra)) + { + return false; + } + return true; + } + + /// @return addressed memory space number. + /// @param payload is a read or write datagram request or response message + static uint8_t get_space(const DatagramPayload &payload) + { + auto *bytes = payload_bytes(payload); + if (bytes[1] & COMMAND_FLAG_MASK) + { + return COMMAND_MASK + (bytes[1] & COMMAND_FLAG_MASK); + } + return bytes[6]; + } + + static unsigned get_payload_offset(const DatagramPayload &payload) + { + auto *bytes = payload_bytes(payload); + if (bytes[1] & COMMAND_FLAG_MASK) + { + return 6; + } + else + { + return 7; + } + } + + /// @param payload is a datagram read or write request or response + /// @return the address field from the datagram + static uint32_t get_address(const DatagramPayload &payload) + { + auto *bytes = payload_bytes(payload); + uint32_t a = bytes[2]; + a <<= 8; + a |= bytes[3]; + a <<= 8; + a |= bytes[4]; + a <<= 8; + a |= bytes[5]; + return a; + } + + /// Type casts a DatagramPayload to an array of bytes. + /// @param payload datagram + /// @return byte array pointing to the same memory + static const uint8_t *payload_bytes(const DatagramPayload &payload) + { + return (uint8_t *)payload.data(); + } + +private: + /** Do not instantiate this class. */ + MemoryConfigDefs(); +}; + +} // namespace openlcb + +#endif // _OPENLCB_MEMORYCONFIGDEFS_HXX_ diff --git a/src/openlcb/SimpleNodeInfo.hxx b/src/openlcb/SimpleNodeInfo.hxx index fbd13d018..f8a3a8346 100644 --- a/src/openlcb/SimpleNodeInfo.hxx +++ b/src/openlcb/SimpleNodeInfo.hxx @@ -37,42 +37,11 @@ #include "openlcb/If.hxx" #include "openlcb/SimpleInfoProtocol.hxx" +#include "openlcb/SimpleNodeInfoDefs.hxx" namespace openlcb { -/// Structure representing the layout of the memory space for Simple Node -/// Identification manufacturer-specified data. -struct SimpleNodeStaticValues -{ - const uint8_t version; - const char manufacturer_name[41]; - const char model_name[41]; - const char hardware_version[21]; - const char software_version[21]; -}; - -/// Structure representing the layout of the memory space for Simple Node -/// Identification user-editable data. -struct SimpleNodeDynamicValues -{ - uint8_t version; - char user_name[63]; - char user_description[64]; -}; - -static_assert(sizeof(struct SimpleNodeDynamicValues) == 128, - "SNIP dynamic file is not of the right size in your compiler"); - -static_assert(sizeof(struct SimpleNodeStaticValues) == 125, - "SNIP static file is not of the right size in your compiler"); - -/** This static data will be exported as the first block of SNIP. The version - * field must contain "4". */ -extern const SimpleNodeStaticValues SNIP_STATIC_DATA; -/** The SNIP dynamic data will be read from this file. It should be 128 bytes - * long, and include the version number of "2" at the beginning. */ -extern const char *const SNIP_DYNAMIC_FILENAME; /** Helper function for test nodes. Fills a file with the given SNIP user * values. */ diff --git a/src/openlcb/SimpleNodeInfoDefs.hxx b/src/openlcb/SimpleNodeInfoDefs.hxx new file mode 100644 index 000000000..bbc618831 --- /dev/null +++ b/src/openlcb/SimpleNodeInfoDefs.hxx @@ -0,0 +1,78 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SimpleNodeInfoDefs.hxx + * + * Structure definitions for simple node info. + * + * @author Balazs Racz + * @date 17 Aug 2021 + */ + +#ifndef _OPENLCB_SIMPLENODEINFODEFS_HXX_ +#define _OPENLCB_SIMPLENODEINFODEFS_HXX_ + +#include + +namespace openlcb +{ + +/// Structure representing the layout of the memory space for Simple Node +/// Identification manufacturer-specified data. +struct SimpleNodeStaticValues +{ + const uint8_t version; + const char manufacturer_name[41]; + const char model_name[41]; + const char hardware_version[21]; + const char software_version[21]; +}; + +/// Structure representing the layout of the memory space for Simple Node +/// Identification user-editable data. +struct SimpleNodeDynamicValues +{ + uint8_t version; + char user_name[63]; + char user_description[64]; +}; + +static_assert(sizeof(struct SimpleNodeDynamicValues) == 128, + "SNIP dynamic file is not of the right size in your compiler"); + +static_assert(sizeof(struct SimpleNodeStaticValues) == 125, + "SNIP static file is not of the right size in your compiler"); + +/** This static data will be exported as the first block of SNIP. The version + * field must contain "4". */ +extern const SimpleNodeStaticValues SNIP_STATIC_DATA; +/** The SNIP dynamic data will be read from this file. It should be 128 bytes + * long, and include the version number of "2" at the beginning. */ +extern const char *const SNIP_DYNAMIC_FILENAME; + +} // namespace openlcb + +#endif // _OPENLCB_SIMPLENODEINFODEFS_HXX_ diff --git a/src/openlcb/TractionDefs.hxx b/src/openlcb/TractionDefs.hxx index 8992bcd64..afa241c9a 100644 --- a/src/openlcb/TractionDefs.hxx +++ b/src/openlcb/TractionDefs.hxx @@ -63,13 +63,6 @@ SpeedType fp16_to_speed(const void *fp16); * to.*/ void speed_to_fp16(SpeedType speed, void *fp16); -/** @returns NAN as speed. */ -inline SpeedType nan_to_speed() { - SpeedType s; - s.set_wire(0xFFFFU); - return s; -} - /// Static constants and helper functions for the Traciton protocol family. struct TractionDefs { /// This event should be produced by train nodes. diff --git a/src/openlcb/TractionTestTrain.cxx b/src/openlcb/TractionTestTrain.cxx index d847ca29b..5bcb1a1a1 100644 --- a/src/openlcb/TractionTestTrain.cxx +++ b/src/openlcb/TractionTestTrain.cxx @@ -34,6 +34,7 @@ #include "openlcb/TractionTestTrain.hxx" +#include "openlcb/TractionDefs.hxx" #include "utils/logging.h" namespace openlcb diff --git a/src/openlcb/TractionThrottle.hxx b/src/openlcb/TractionThrottle.hxx index a78be880d..f5d271bc8 100644 --- a/src/openlcb/TractionThrottle.hxx +++ b/src/openlcb/TractionThrottle.hxx @@ -35,191 +35,25 @@ #ifndef _OPENLCB_TRACTIONTHROTTLE_HXX_ #define _OPENLCB_TRACTIONTHROTTLE_HXX_ +#include "executor/CallableFlow.hxx" #include "openlcb/TractionClient.hxx" #include "openlcb/TractionDefs.hxx" +#include "openlcb/TractionThrottleInterface.hxx" #include "openlcb/TrainInterface.hxx" -#include "executor/CallableFlow.hxx" namespace openlcb { -struct TractionThrottleInput; - -/// C++ Namespace for collecting all commands that can be sent to the -/// TractionThrottle flow. -struct TractionThrottleCommands -{ - enum SetDst - { - SET_DST, - }; - - enum AssignTrain - { - ASSIGN_TRAIN, - }; - - enum ReleaseTrain - { - RELEASE_TRAIN, - }; - - enum LoadState - { - LOAD_STATE, - }; - - enum ConsistAdd - { - CONSIST_ADD, - }; - - enum ConsistDel - { - CONSIST_DEL, - }; - - enum ConsistQry - { - CONSIST_QRY, - }; -}; - -/// Request structure used to send requests to the TractionThrottle -/// class. Contains parametrized reset calls for properly supporting -/// @ref StateFlowBase::invoke_subflow_and_wait() syntax. -struct TractionThrottleInput : public CallableFlowRequestBase -{ - enum Command - { - CMD_SET_DST, - CMD_ASSIGN_TRAIN, - CMD_RELEASE_TRAIN, - CMD_LOAD_STATE, - CMD_CONSIST_ADD, - CMD_CONSIST_DEL, - CMD_CONSIST_QRY, - }; - - /// Sets the destination node to send messages to without sending assign - /// commands to that train node. - void reset(const TractionThrottleCommands::SetDst &, const NodeID &dst) - { - cmd = CMD_SET_DST; - this->dst = dst; - } - - void reset(const TractionThrottleCommands::AssignTrain &, const NodeID &dst, - bool listen) - { - cmd = CMD_ASSIGN_TRAIN; - this->dst = dst; - this->flags = listen ? 1 : 0; - } - - void reset(const TractionThrottleCommands::ReleaseTrain &) - { - cmd = CMD_RELEASE_TRAIN; - } - - void reset(const TractionThrottleCommands::LoadState &) - { - cmd = CMD_LOAD_STATE; - } - - void reset(const TractionThrottleCommands::ConsistAdd &, NodeID slave, uint8_t flags) - { - cmd = CMD_CONSIST_ADD; - dst = slave; - this->flags = flags; - } - - void reset(const TractionThrottleCommands::ConsistDel &, NodeID slave) - { - cmd = CMD_CONSIST_DEL; - dst = slave; - } - - void reset(const TractionThrottleCommands::ConsistQry &) - { - cmd = CMD_CONSIST_QRY; - replyCause = 0xff; - } - - void reset(const TractionThrottleCommands::ConsistQry &, uint8_t ofs) - { - cmd = CMD_CONSIST_QRY; - consistIndex = ofs; - replyCause = 0; - } - - Command cmd; - /// For assign, this carries the destination node ID. For consisting - /// requests, this is an in-out argument. - NodeID dst; - /// Contains the flags for the consist listener. Specified for Attach - /// requests, and filled for Query responses. - uint8_t flags; - - /// For assign controller reply REJECTED, this is 1 for controller refused - /// connection, 2 fortrain refused connection. - uint8_t replyCause; - /// Total number of entries in the consisting list. - uint8_t consistCount; - /// Index of the entry in the consisting list that needs to be returned. - uint8_t consistIndex; -}; - -class TractionThrottleInterface - : public openlcb::TrainImpl -{ -public: - /// Flips a function on<>off. - virtual void toggle_fn(uint32_t fn) = 0; - - /// Sends a query for a function to the server. The response will be - /// asynchronously reported by the throttle listener update callback. - /// @param fn function number. - virtual void query_fn(uint32_t fn) - { - } - - /// Determine if a train is currently assigned to this trottle. - /// @return true if a train is assigned, else false - virtual bool is_train_assigned() = 0; - - /// @return the controlling node (virtual node of the throttle, i.e., us.) - /// @todo this function should not be here - virtual openlcb::Node* throttle_node() = 0; - - /// Sets up a callback for listening for remote throttle updates. When a - /// different throttle modifies the train node's state, and the - /// ASSIGN_TRAIN command was executed with "listen==true" parameter, we - /// will get notifications about those remote changes. The notifications - /// update the cached state in TractionThrottle, and call this update - /// callback. Repeat with nullptr if the callbacks are not desired anymore. - /// @param update_callback will be executed when a different throttle - /// changes the train state. fn is the function number changed, or -1 for - /// speed update. - virtual void set_throttle_listener(std::function update_callback) = 0; - - /// @return the controlled node (the train node) ID. - /// @todo this function should not be here - virtual openlcb::NodeID target_node() = 0; -}; - /** Interface for a single throttle for running a train node. * */ -class TractionThrottle - : public CallableFlow, - public TractionThrottleInterface +class TractionThrottle : public TractionThrottleBase { public: /// @param node is the openlcb node from which this throttle will be /// sending its messages. TractionThrottle(Node *node) - : CallableFlow(node->iface()) + : TractionThrottleBase(node->iface()) , node_(node) { clear_cache(); @@ -237,9 +71,6 @@ public: { /// Timeout for assign controller request. TIMEOUT_NSEC = SEC_TO_NSEC(2), - /// Returned from get_fn() when we don't have a cahced value for a - /// function. - FN_NOT_KNOWN = 0xffff, /// Upon a load state request, how far do we go into the function list? MAX_FN_QUERY = 28, ERROR_UNASSIGNED = 0x4000000, diff --git a/src/openlcb/TractionThrottleInterface.hxx b/src/openlcb/TractionThrottleInterface.hxx new file mode 100644 index 000000000..7db13ac4a --- /dev/null +++ b/src/openlcb/TractionThrottleInterface.hxx @@ -0,0 +1,231 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file TractionThrottleInterface.hxx + * + * Client API for the Traction protocol (declarations only). + * + * @author Balazs Racz + * @date 20 May 2014 + */ + +#ifndef _OPENLCB_TRACTIONTHROTTLEINTERFACE_HXX_ +#define _OPENLCB_TRACTIONTHROTTLEINTERFACE_HXX_ + +#include "executor/CallableFlow.hxx" +#include "openlcb/Defs.hxx" +#include "openlcb/TrainInterface.hxx" + +namespace openlcb +{ + +class Node; +struct TractionThrottleInput; + +/// C++ Namespace for collecting all commands that can be sent to the +/// TractionThrottle flow. +struct TractionThrottleCommands +{ + enum SetDst + { + SET_DST, + }; + + enum AssignTrain + { + ASSIGN_TRAIN, + }; + + enum ReleaseTrain + { + RELEASE_TRAIN, + }; + + enum LoadState + { + LOAD_STATE, + }; + + enum ConsistAdd + { + CONSIST_ADD, + }; + + enum ConsistDel + { + CONSIST_DEL, + }; + + enum ConsistQry + { + CONSIST_QRY, + }; +}; + +/// Request structure used to send requests to the TractionThrottle +/// class. Contains parametrized reset calls for properly supporting +/// @ref StateFlowBase::invoke_subflow_and_wait() syntax. +struct TractionThrottleInput : public CallableFlowRequestBase +{ + enum Command + { + CMD_SET_DST, + CMD_ASSIGN_TRAIN, + CMD_RELEASE_TRAIN, + CMD_LOAD_STATE, + CMD_CONSIST_ADD, + CMD_CONSIST_DEL, + CMD_CONSIST_QRY, + }; + + /// Sets the destination node to send messages to without sending assign + /// commands to that train node. + void reset(const TractionThrottleCommands::SetDst &, const NodeID &dst) + { + cmd = CMD_SET_DST; + this->dst = dst; + } + + void reset(const TractionThrottleCommands::AssignTrain &, const NodeID &dst, + bool listen) + { + cmd = CMD_ASSIGN_TRAIN; + this->dst = dst; + this->flags = listen ? 1 : 0; + } + + void reset(const TractionThrottleCommands::ReleaseTrain &) + { + cmd = CMD_RELEASE_TRAIN; + } + + void reset(const TractionThrottleCommands::LoadState &) + { + cmd = CMD_LOAD_STATE; + } + + void reset(const TractionThrottleCommands::ConsistAdd &, NodeID slave, + uint8_t flags) + { + cmd = CMD_CONSIST_ADD; + dst = slave; + this->flags = flags; + } + + void reset(const TractionThrottleCommands::ConsistDel &, NodeID slave) + { + cmd = CMD_CONSIST_DEL; + dst = slave; + } + + void reset(const TractionThrottleCommands::ConsistQry &) + { + cmd = CMD_CONSIST_QRY; + replyCause = 0xff; + } + + void reset(const TractionThrottleCommands::ConsistQry &, uint8_t ofs) + { + cmd = CMD_CONSIST_QRY; + consistIndex = ofs; + replyCause = 0; + } + + Command cmd; + /// For assign, this carries the destination node ID. For consisting + /// requests, this is an in-out argument. + NodeID dst; + /// Contains the flags for the consist listener. Specified for Attach + /// requests, and filled for Query responses. + uint8_t flags; + + /// For assign controller reply REJECTED, this is 1 for controller refused + /// connection, 2 fortrain refused connection. + uint8_t replyCause; + /// Total number of entries in the consisting list. + uint8_t consistCount; + /// Index of the entry in the consisting list that needs to be returned. + uint8_t consistIndex; +}; + +class TractionThrottleInterface : public openlcb::TrainImpl +{ +public: + /// Flips a function on<>off. + virtual void toggle_fn(uint32_t fn) = 0; + + /// Sends a query for a function to the server. The response will be + /// asynchronously reported by the throttle listener update callback. + /// @param fn function number. + virtual void query_fn(uint32_t fn) + { + } + + /// Determine if a train is currently assigned to this trottle. + /// @return true if a train is assigned, else false + virtual bool is_train_assigned() = 0; + + /// @return the controlling node (virtual node of the throttle, i.e., us.) + /// @todo this function should not be here + virtual openlcb::Node *throttle_node() = 0; + + /// Sets up a callback for listening for remote throttle updates. When a + /// different throttle modifies the train node's state, and the + /// ASSIGN_TRAIN command was executed with "listen==true" parameter, we + /// will get notifications about those remote changes. The notifications + /// update the cached state in TractionThrottle, and call this update + /// callback. Repeat with nullptr if the callbacks are not desired anymore. + /// @param update_callback will be executed when a different throttle + /// changes the train state. fn is the function number changed, or -1 for + /// speed update. + virtual void set_throttle_listener( + std::function update_callback) = 0; + + /// @return the controlled node (the train node) ID. + /// @todo this function should not be here + virtual openlcb::NodeID target_node() = 0; +}; + +class TractionThrottleBase : public CallableFlow, + public TractionThrottleInterface +{ +public: + TractionThrottleBase(Service *s) + : CallableFlow(s) + { + } + + enum + { + /// Returned from get_fn() when we don't have a cahced value for a + /// function. + FN_NOT_KNOWN = 0xffff, + }; +}; + +} // namespace openlcb + +#endif // _OPENLCB_TRACTIONTHROTTLEINTERFACE_HXX_ diff --git a/src/openlcb/TrainInterface.hxx b/src/openlcb/TrainInterface.hxx index 74fbd167c..d2ffefaa7 100644 --- a/src/openlcb/TrainInterface.hxx +++ b/src/openlcb/TrainInterface.hxx @@ -35,10 +35,15 @@ #ifndef _OPENLCB_TRAININTERFACE_HXX_ #define _OPENLCB_TRAININTERFACE_HXX_ -#include "openlcb/TractionDefs.hxx" #include "dcc/Defs.hxx" +#include "openlcb/Velocity.hxx" -namespace openlcb { +namespace openlcb +{ + +/// Represents an OpenLCB speed value with accessors to convert to and from +/// various formats. +typedef Velocity SpeedType; /// Abstract base class for train implementations. This interface links the /// OpenLCB trains to the dcc packet sources. diff --git a/src/openlcb/Velocity.hxx b/src/openlcb/Velocity.hxx index 9a5426304..4abbae791 100644 --- a/src/openlcb/Velocity.hxx +++ b/src/openlcb/Velocity.hxx @@ -486,6 +486,13 @@ private: } }; +/** @returns NAN as speed. */ +inline Velocity nan_to_speed() +{ + Velocity s; + s.set_wire(0xFFFFU); + return s; +} }; /* namespace openlcb */ From 122241168f1e40047e00e5b00a4eb5811740cf38 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 21 Aug 2021 14:27:06 +0200 Subject: [PATCH 02/62] Fixed16 comparisons operators (#570) Updates to the Fixed16 class: - adds comparison operators between Fixed16 values. - removes implicit conversion to int. This caused confusing behavior as many operations were executed by the compiler on ints instead of Fixed16's. - Adds some comments. === * Add unit test to fixed16. * Adds comparison operators to Fixed16. Removes the implicit int16 conversion because it makes C++ believe weird. Co-authored-by: Stuart Baker --- src/utils/Fixed16.cxxtest | 130 ++++++++++++++++++++++++++++++++++---- src/utils/Fixed16.hxx | 57 +++++++++++++++-- 2 files changed, 170 insertions(+), 17 deletions(-) diff --git a/src/utils/Fixed16.cxxtest b/src/utils/Fixed16.cxxtest index 5dac6e5ca..c1750ddb1 100644 --- a/src/utils/Fixed16.cxxtest +++ b/src/utils/Fixed16.cxxtest @@ -40,19 +40,18 @@ using ::testing::FloatNear; TEST(Fixed16Test, CreateRound) { Fixed16 v1(13); - EXPECT_EQ(13, (uint16_t)v1); + EXPECT_EQ(13, v1.round()); v1 = Fixed16(13, 0x7fff); - EXPECT_EQ(13, (uint16_t)v1); + EXPECT_EQ(13, v1.round()); v1 = Fixed16(13, 0x8000); - EXPECT_EQ(14, (uint16_t)v1); - EXPECT_EQ(14, (int)v1); + EXPECT_EQ(14, v1.round()); v1 = Fixed16(13, 0xff00); - EXPECT_EQ(14, (uint16_t)v1); + EXPECT_EQ(14, v1.round()); v1 = Fixed16(13, 0xffff); - EXPECT_EQ(14, (uint16_t)v1); + EXPECT_EQ(14, v1.round()); } TEST(Fixed16Test, ToFloat) @@ -124,20 +123,20 @@ TEST(Fixed16Test, Constexpr) TEST(Fixed16Test, Arithmetics) { Fixed16 v1(13); - EXPECT_EQ(13, (uint16_t)v1); + EXPECT_EQ(13, v1.round()); v1 += 4; - EXPECT_EQ(17, (uint16_t)v1); + EXPECT_EQ(17, v1.round()); v1 -= 2; - EXPECT_EQ(15, (uint16_t)v1); + EXPECT_EQ(15, v1.round()); v1 += Fixed16(0, 0x8000); - EXPECT_EQ(16, (uint16_t)v1); + EXPECT_EQ(16, v1.round()); EXPECT_EQ(15, v1.trunc()); v1 *= 2; - EXPECT_EQ(31, (uint16_t)v1); + EXPECT_EQ(31, v1.round()); EXPECT_EQ(31, v1.trunc()); EXPECT_EQ(0, v1.frac()); @@ -149,7 +148,7 @@ TEST(Fixed16Test, Arithmetics) Fixed16 v2 = 1; v2 /= 2; v1 += v2; - EXPECT_EQ(16, (uint16_t)v1); + EXPECT_EQ(16, v1.round()); EXPECT_THAT(v1.to_float(), FloatNear(16.0, 1e-5)); v1 += Fixed16(1) / 2; @@ -157,6 +156,44 @@ TEST(Fixed16Test, Arithmetics) EXPECT_THAT(v1.to_float(), FloatNear(16.5, 1e-5)); } +TEST(Fixed16Test, ArithmeticsNegative) +{ + Fixed16 v1(13); + v1 += -2; + EXPECT_EQ(11, v1.round()); + + v1 = -15; + + v1 += -2; + EXPECT_EQ(-17, v1.round()); + + v1 -= -2; + EXPECT_EQ(-15, v1.round()); + + v1 += 2; + EXPECT_EQ(-13, v1.round()); + + v1 *= 2; + EXPECT_EQ(-26, v1.round()); +} + +TEST(Fixed16Test, TruncNegative) +{ + Fixed16 v1(Fixed16::FROM_DOUBLE, -7.5); + EXPECT_EQ(-7, v1.trunc()); + EXPECT_EQ(0x8000, v1.frac()); + + // Note that the exact half is rounded away from zero. + EXPECT_EQ(-8, v1.round()); + + v1 = {Fixed16::FROM_DOUBLE, 7.5}; + EXPECT_EQ(7, v1.trunc()); + EXPECT_EQ(0x8000, v1.frac()); + + // Note that the exact half is rounded away from zero. + EXPECT_EQ(8, v1.round()); +} + TEST(Fixed16Test, Division) { Fixed16 v1(256); @@ -214,6 +251,64 @@ TEST(Fixed16Test, SignedZero) EXPECT_TRUE(v1.is_positive()); } +TEST(Fixed16Test, Compare) +{ + // This array is sorted. + Fixed16 arr[] = { + {-32767, 0xffff}, + {-32767, 0x8000}, + {-32767, 1}, + {-32767, 0}, + {-32766, 0xffff}, + {-32766, 0x8000}, + {-32766, 1}, + {-32766, 0}, + {-1, 0xffff}, + {-1, 0x8000}, + {-1, 1}, + {-1, 0}, + {0, 0}, + {0, 1}, + {0, 0x8000}, + {0, 0xffff}, + {1, 0}, + {1, 1}, + {1, 0x8000}, + {1, 0xffff}, + {32767, 0}, + {32767, 1}, + {32767, 0x8000}, + {32767, 0xffff} + }; + + for (unsigned i = 0; i < ARRAYSIZE(arr); i++) { + for (unsigned j = 0; j < i; j++) { + string s = StringPrintf("i [%d] %d:%d k=%08x j [%d] %d:%d k=%08x", + i, arr[i].trunc(), arr[i].frac(), arr[i].to_key(), j, + arr[j].trunc(), arr[j].frac(), arr[j].to_key()); + SCOPED_TRACE(s); + EXPECT_TRUE(arr[j] < arr[i]); + EXPECT_TRUE(arr[j] <= arr[i]); + EXPECT_FALSE(arr[j] > arr[i]); + EXPECT_FALSE(arr[j] >= arr[i]); + EXPECT_FALSE(arr[i] < arr[j]); + EXPECT_FALSE(arr[i] <= arr[j]); + EXPECT_TRUE(arr[i] > arr[j]); + EXPECT_TRUE(arr[i] >= arr[j]); + + EXPECT_TRUE(arr[i] != arr[j]); + EXPECT_FALSE(arr[i] == arr[j]); + } + EXPECT_FALSE(arr[i] < arr[i]); + EXPECT_FALSE(arr[i] > arr[i]); + EXPECT_TRUE(arr[i] <= arr[i]); + EXPECT_TRUE(arr[i] >= arr[i]); + + EXPECT_TRUE(arr[i] == arr[i]); + EXPECT_FALSE(arr[i] != arr[i]); + } +} + /// Helper function to test mulpow2 operation. Computes base mulpow2 shift with /// fixed16 and with double, and verifies that the results is within the given /// relative precision form each other. @@ -249,3 +344,14 @@ TEST(Fixed16Test, MulPow2) mulpow2_test(30481, -12.5, 0.00001); mulpow2_test(4377, 2.375, 0.0001); } + +TEST(Fixed16Test, IntegerDivision) +{ + Fixed16 ohmsMin{505}; + Fixed16 ohmsMax{9495}; + Fixed16 ohmsMid{ohmsMin + ((ohmsMax - ohmsMin) / 2)}; + Fixed16 stepSizeLow{(ohmsMid - ohmsMin) / 127}; + Fixed16 stepSizeHigh{(ohmsMax - ohmsMid) / 127}; + + EXPECT_NEAR(35.39, stepSizeLow.to_float(), 0.01); +} diff --git a/src/utils/Fixed16.hxx b/src/utils/Fixed16.hxx index 0dbeab734..76c79f6cd 100644 --- a/src/utils/Fixed16.hxx +++ b/src/utils/Fixed16.hxx @@ -42,6 +42,12 @@ class Fixed16 { public: + /// Constructs a Fixed16. + /// @param integer is the integer part and the sign. Valid values are from + /// -32767 to 32767. + /// @param frac is the fractional part. All uint16 values are valid. For + /// positive integer the fractional part goes above the int value, for + /// negative integers the fractional part goes below the int value. constexpr Fixed16(int16_t integer, uint16_t frac = 0) : value_(((integer < 0 ? -integer : integer) << 16) | frac) , sign_(integer < 0 ? 1 : 0) @@ -53,6 +59,9 @@ public: FROM_DOUBLE }; + /// Constructs a Fixed16. + /// @param value is the value to store. Valid values are -32767.99999 to + /// 32767.99999. constexpr Fixed16(FromDouble, double value) : value_(value < 0 ? -value * 65536 + 0.5 : value * 65536 + 0.5) , sign_(value < 0 ? 1 : 0) @@ -126,12 +135,42 @@ public: return ret; } - /// @return the rounded value to the nearest integer - operator uint16_t() const + /// Comparison operator. + bool operator<(Fixed16 o) { - return round(); + return to_key() < o.to_key(); } + /// Comparison operator. + bool operator<=(Fixed16 o) + { + return to_key() <= o.to_key(); + } + + /// Comparison operator. + bool operator>(Fixed16 o) + { + return to_key() > o.to_key(); + } + + /// Comparison operator. + bool operator>=(Fixed16 o) + { + return to_key() >= o.to_key(); + } + + /// Comparison operator. + bool operator==(Fixed16 o) + { + return to_key() == o.to_key(); + } + + /// Comparison operator. + bool operator!=(Fixed16 o) + { + return to_key() != o.to_key(); + } + /// Multiplies *this with pow(2, o). This is effectively a generalized /// shift operation that works on fractional numbers too. The precision is /// limited. @@ -189,7 +228,7 @@ public: return b; } - /// @return the integer part, rounded down + /// @return the integer part, rounded towards zero. int16_t trunc() const { int16_t b = value_ >> 16; @@ -198,6 +237,8 @@ public: } /// @return the fractional part, as an uint16 value between 0 and 0xffff + /// Note: the fractional part inherits the sign of the integer part, + /// similarly to the decimal notation. uint16_t frac() const { return value_ & 0xffff; @@ -245,6 +286,12 @@ public: void negate() { sign_ ^= 1; } + + /// Turns the value into a comparison key. + int32_t to_key() + { + return to_int(); + } private: /// Translates the current value to a signed fixed-point 32-bit integer. @@ -271,7 +318,7 @@ private: } value_ = v & 0x7fffffffu; } - + uint32_t value_ : 31; uint32_t sign_ : 1; }; From 8c52743df906080d381934bfd00696d956407957 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 1 Sep 2021 20:33:23 +0200 Subject: [PATCH 03/62] Adds SPI driver for Tiva (#568) * Adds a SPI driver for tiva 123/129. This driver does not yet support DMA and only supports single bit transfers. * Fixes whitespace. * fix hanging brace * Fixes bugs in the tiva SPI driver. * Fix comment --- src/freertos_drivers/ti/TivaSPI.cxx | 167 +++++++++ src/freertos_drivers/ti/TivaSPI.hxx | 320 ++++++++++++++++++ .../freertos_drivers/tivaware/sources | 1 + 3 files changed, 488 insertions(+) create mode 100644 src/freertos_drivers/ti/TivaSPI.cxx create mode 100644 src/freertos_drivers/ti/TivaSPI.hxx diff --git a/src/freertos_drivers/ti/TivaSPI.cxx b/src/freertos_drivers/ti/TivaSPI.cxx new file mode 100644 index 000000000..337f5e844 --- /dev/null +++ b/src/freertos_drivers/ti/TivaSPI.cxx @@ -0,0 +1,167 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file TivaSPI.cxx + * This file implements a SPI device driver for Tiva (129). + * + * @author Balazs Racz + * @date 18 Aug 2021 + */ + +#include "TivaSPI.hxx" + +#include "inc/hw_memmap.h" +#include "driverlib/sysctl.h" +#include "driverlib/rom.h" +#include "driverlib/rom_map.h" +#include "driverlib/interrupt.h" + +/// One semaphore per peripheral. +OSSem sem[4]; + +TivaSPI::TivaSPI(const char *name, unsigned long base, uint32_t interrupt, + ChipSelectMethod cs_assert, ChipSelectMethod cs_deassert, + OSMutex *bus_lock, + size_t dma_threshold, + uint32_t dma_channel_index_tx, + uint32_t dma_channel_index_rx) + : SPI(name, cs_assert, cs_deassert, bus_lock) + , base_(base) + , interrupt_(interrupt) + , dmaThreshold_(dma_threshold) + , dmaChannelIndexTx_(dma_channel_index_tx) + , dmaChannelIndexRx_(dma_channel_index_rx) +{ + uint32_t p = 0; + switch(base_) { + case SSI0_BASE: + p = SYSCTL_PERIPH_SSI0; + sem_ = sem + 0; + break; + case SSI1_BASE: + p = SYSCTL_PERIPH_SSI1; + sem_ = sem + 1; + break; + case SSI2_BASE: + p = SYSCTL_PERIPH_SSI2; + sem_ = sem + 2; + break; + case SSI3_BASE: + p = SYSCTL_PERIPH_SSI3; + sem_ = sem + 3; + break; + default: + DIE("Unknown SPI peripheral."); + } + MAP_SysCtlPeripheralEnable(p); + MAP_SysCtlPeripheralReset(p); + + update_configuration(); +} + +TivaSPI::~TivaSPI() +{ +} + +void TivaSPI::enable() +{ +} + +void TivaSPI::disable() +{ +} + +/** Update the configuration of the bus. + * @return >= 0 upon success, -errno upon failure + */ +int TivaSPI::update_configuration() +{ + unsigned long new_mode; + + switch (mode_) + { + default: + case SPI_MODE_0: + new_mode = SSI_FRF_MOTO_MODE_0; + break; + case SPI_MODE_1: + new_mode = SSI_FRF_MOTO_MODE_1; + break; + case SPI_MODE_2: + new_mode = SSI_FRF_MOTO_MODE_2; + break; + case SPI_MODE_3: + new_mode = SSI_FRF_MOTO_MODE_3; + break; + } + + HASSERT(bitsPerWord <= 16); + + uint32_t clock = cm3_cpu_clock_hz; + /* The SPI peripheral only supoprts certain frequencies and driverlib does + * not properly round down. We can do some math to make sure that driverlib + * makes the correct selection. + */ + if (speedHz > (clock / 2)) + { + speedHz = clock / 2; + } + else if ((clock % speedHz) != 0) + { + speedHz = clock / ((clock / speedHz) + 1); + } + + MAP_SSIDisable(base_); + MAP_SSIConfigSetExpClk(base_, clock, new_mode, SSI_MODE_MASTER, + speedHz, bitsPerWord); + MAP_IntPrioritySet(interrupt_, configKERNEL_INTERRUPT_PRIORITY); + MAP_IntEnable(interrupt_); + MAP_SSIEnable(base_); + + // these values are used to quickly program instance local configuration + // settings + spiCr0_ = HWREG(base_ + SSI_O_CR0); + spiCr1_ = HWREG(base_ + SSI_O_CR1); + spiPrescaler_ = HWREG(base_ + SSI_O_CPSR); + + return 0; +} + +/** Configure a DMA transaction. + * @param msg message to transact. + */ +__attribute__((optimize("-O3"))) +void TivaSPI::config_dma(struct spi_ioc_transfer *msg) +{ + DIE("DMA transfer is not yet implemented."); +} + +__attribute__((optimize("-O3"))) +void TivaSPI::interrupt_handler() +{ + DIE("SPI interrupt is not yet implemented."); +} + diff --git a/src/freertos_drivers/ti/TivaSPI.hxx b/src/freertos_drivers/ti/TivaSPI.hxx new file mode 100644 index 000000000..52202e37a --- /dev/null +++ b/src/freertos_drivers/ti/TivaSPI.hxx @@ -0,0 +1,320 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file TivaSPI.hxx + * This file implements a SPI device driver for Tiva (129). + * + * @author Balazs Racz + * @date 18 Aug 2021 + */ + +#ifndef _FREERTOS_DRIVERS_TI_TIVASPI_HXX_ +#define _FREERTOS_DRIVERS_TI_TIVASPI_HXX_ + + +#include "SPI.hxx" +#include "driverlib/ssi.h" +#include "inc/hw_ssi.h" +#include "inc/hw_types.h" +#include "driverlib/udma.h" + +class TivaSPI : public SPI +{ +public: + /** Constructor. + * @param name name of this device instance in the file system + * @param base base address of this device + * @param interrupt interrupt number of this device + * @param cs_assert function pointer to a method that asserts chip select + * @param cs_deassert function pointer to a method that deasserts chip + * select + * @param bus_lock the user must provide a shared mutex if the device + * instance represents more than one chip select on the + * same bus interface. + * @param dma_threshold the threshold in bytes to use a DMA transaction, + * 0 = DMA always disabled + * @param dma_channel_index_tx UDMA_CHn_SSInTX + * @param dma_channel_index_rx UDMA_CHn_SSInRX + */ + TivaSPI(const char *name, unsigned long base, uint32_t interrupt, + ChipSelectMethod cs_assert, ChipSelectMethod cs_deassert, + OSMutex *bus_lock = nullptr, + size_t dma_threshold = DEFAULT_DMA_THRESHOLD_BYTES, + uint32_t dma_channel_index_tx = UDMA_CH11_SSI0TX, + uint32_t dma_channel_index_rx = UDMA_CH10_SSI0RX); + + /// Destructor + ~TivaSPI(); + + /// Call this from the SPI device's interrupt handler. + void interrupt_handler(); + + /** This method provides a reference to the Mutex used by this device + * driver. It can be passed into another SPI driver instance as a bus + * wide lock such that a SPI bus can be shared between this driver and + * another use case with another chip select. + * @return a reference to this device driver instance lock + */ + OSMutex *get_lock() + { + return &lock_; + } + +private: + /// Transfers longer than this will be with dma by default. + /// @todo this should be 64 bytes + static constexpr size_t DEFAULT_DMA_THRESHOLD_BYTES = 0; + + /// Maximum number of bytes transferred in a single DMA transaction + static constexpr size_t MAX_DMA_TRANSFER_AMOUNT = 1024; + + /** + * This lookup table is used to configure the DMA channels for the + * appropriate (8bit or 16bit) transfer sizes. + * Table for an SPI DMA RX channel. + */ + static constexpr uint32_t dmaRxConfig_[] = + { + UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_8 | UDMA_ARB_1, + UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_16 | UDMA_ARB_1, + }; + + /** + * This lookup table is used to configure the DMA channels for the + * appropriate (8bit or 16bit) transfer sizes. + * Table for an SPI DMA TX channel + */ + static constexpr uint32_t dmaTxConfig_[] = + { + UDMA_SIZE_8 | UDMA_SRC_INC_8 | UDMA_DST_INC_NONE | UDMA_ARB_1, + UDMA_SIZE_16 | UDMA_SRC_INC_16 | UDMA_DST_INC_NONE | UDMA_ARB_1, + }; + + /** + * This lookup table is used to configure the DMA channels for the + * appropriate (8bit or 16bit) transfer sizes when either txBuf or + * rxBuf are NULL. + */ + static constexpr uint32_t dmaNullConfig_[] = + { + UDMA_SIZE_8 | UDMA_SRC_INC_NONE | UDMA_DST_INC_NONE | UDMA_ARB_1, + UDMA_SIZE_16 | UDMA_SRC_INC_NONE | UDMA_DST_INC_NONE | UDMA_ARB_1, + }; + + /** Function to enable device. + */ + void enable() override; + + /** Function to disable device. + */ + void disable() override; + + /** Update the configuration of the bus. + * @return >= 0 upon success, -errno upon failure + */ + int update_configuration() override; + + /** Method to transmit/receive the data. + * @param msg message to transact. + */ + __attribute__((optimize("-O3"))) + int transfer(struct spi_ioc_transfer *msg) override + { + return transfer_polled(msg); +#if 0 + /// @todo support DMA + if (LIKELY(msg->len < dmaThreshold_)) + { + return transfer_polled(msg); + } + else + { + /* use DMA */ + config_dma(msg); + } + + return msg->len; +#endif + } + + /** Method to transmit/receive the data. This is a template in order to + * preserve execution speed on type specific pointer math. + * @param msg message to transact. + */ + template + __attribute__((optimize("-O3"))) + int transfer_polled(struct spi_ioc_transfer *msg) + { + T dummy = 0; + + /* we are assuming that at least one byte will be transferred, and + * we want to start tranfering data as soon as possible + */ + data_put(msg->tx_buf ? *((T*)msg->tx_buf) : 0xFFFFFFFF); + + T *tx_buf = msg->tx_buf ? ((T*)msg->tx_buf) + 1 : &dummy; + T *rx_buf = (T*)msg->rx_buf; + + /* note that we already have transmitted one SPI word above, hence the + * subtract one from the tx_len + */ + uint32_t rx_len = msg->len / sizeof(T); + uint32_t tx_len = rx_len - 1; + + uint16_t data; + + do + { + /* fill TX FIFO but make sure we don't fill it to overflow */ + if (tx_len && ((rx_len - tx_len) < (32 / sizeof(T)))) + { + if (data_put_non_blocking(*tx_buf) != 0) + { + if (msg->tx_buf) + { + ++tx_buf; + } + --tx_len; + } + } + + /* empty RX FIFO */ + if (rx_len) + { + if (data_get_non_blocking(&data) != 0) + { + if (msg->rx_buf) + { + *rx_buf++ = data; + } + --rx_len; + } + } + } + while (tx_len || rx_len); + + return msg->len; + } + + /** Method to transmit/receive the data. + * @param msg message to transact. + */ + __attribute__((optimize("-O3"))) + int transfer_polled(struct spi_ioc_transfer *msg) override + { + /* set instance specific configuration */ + set_configuration(); + + switch (bitsPerWord) + { + default: + case 8: + return transfer_polled(msg); + case 16: + return transfer_polled(msg); + case 32: + DIE("32-bit transers are not supported on this MCU."); + } + } + + /** Configure a DMA transaction. + * @param msg message to transact. + */ + void config_dma(struct spi_ioc_transfer *msg); + + /** Receives a word from the specified port. This function gets a SPI word + * from the receive FIFO for the specified port. + * @param data is pointer to receive data variable. + * @return Returns the number of elements read from the receive FIFO. + */ + __attribute__((optimize("-O3"))) + long data_get_non_blocking(uint16_t *data) + { + if(HWREG(base_ + SSI_O_SR) & SSI_SR_RNE) + { + *data = HWREG(base_ + SSI_O_DR); + return 1; + } + + return 0; + } + + /** Transmits a word on the specified port. This function transmits a SPI + * word on the transmit FIFO for the specified port. + * @param data is data to be transmitted. + * @return Returns the number of elements written to the transmit FIFO. + */ + __attribute__((optimize("-O3"))) + long data_put_non_blocking(uint16_t data) + { + if(HWREG(base_ + SSI_O_SR) & SSI_SR_TNF) + { + HWREG(base_ + SSI_O_DR) = data; + return 1; + } + + return 0; + } + + /** Waits until the word is transmitted on the specified port. This + * function transmits a SPI word on the transmit FIFO for the specified + * port. This function waits until the space is available on transmit FIFO. + * @param data is data to be transmitted. + */ + void data_put(uint16_t data) + { + while((HWREG(base_ + SSI_O_SR) & SSI_SR_TNF) == 0); + HWREG(base_ + SSI_O_DR) = data; + } + + /** Set the instance local configuration. + */ + void set_configuration() + { + // Disable port first for reconfiguration. + HWREG(base_ + SSI_O_CR1) &= ~SSI_CR1_SSE; + + HWREG(base_ + SSI_O_CR0) = spiCr0_; + HWREG(base_ + SSI_O_CPSR) = spiPrescaler_; + HWREG(base_ + SSI_O_CR1) = spiCr1_; + } + + OSSem *sem_; /**< reference to the semaphore belonging to this bus */ + unsigned long base_; /**< base address of this device */ + unsigned long interrupt_; /**< interrupt of this device */ + size_t dmaThreshold_; /**< threshold in bytes to start using DMA */ + uint32_t dmaChannelIndexTx_; /**< TX DMA channel index */ + uint32_t dmaChannelIndexRx_; /**< RX DMA channel index */ + + uint16_t spiCr0_; ///< Configuration register 0 local copy. + uint16_t spiCr1_; ///< Configuration register 1 local copy. + uint16_t spiPrescaler_; ///< Prescale register local copy. + + DISALLOW_COPY_AND_ASSIGN(TivaSPI); +}; + + +#endif // _FREERTOS_DRIVERS_TI_TIVASPI_HXX_ diff --git a/targets/freertos.armv7m/freertos_drivers/tivaware/sources b/targets/freertos.armv7m/freertos_drivers/tivaware/sources index 421aee225..4ad305215 100644 --- a/targets/freertos.armv7m/freertos_drivers/tivaware/sources +++ b/targets/freertos.armv7m/freertos_drivers/tivaware/sources @@ -4,6 +4,7 @@ VPATH=$(OPENMRNPATH)/src/freertos_drivers/ti CXXSRCS += TivaCan.cxx \ TivaI2C.cxx \ + TivaSPI.cxx \ TivaUart.cxx \ TivaUsbCdcDevice.cxx \ TivaUsbKeyboardDev.cxx \ From bc8162ed44780e9d322c4679c14c1863fd78d7a8 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 1 Sep 2021 20:37:16 +0200 Subject: [PATCH 04/62] Adds SPIFFs driver for Tiva 129 (#569) Generalizes the SPIFFs driver form the CC32xxSF to run on a Tiva 129 device as well using the same driverlib calls. === * Renames cc32xxspiffs to TiSPIFFS. * Generalizes the SPIFFs driver form the CC32xxSF to run on a Tiva 129 device as well using the same driverlib calls. --- src/freertos_drivers/sources | 6 +- .../spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx | 180 +------------- .../spiffs/cc32x0sf/CC32x0SFSPIFFS.hxx | 50 +--- .../spiffs/cc32x0sf/TiSPIFFS.hxx | 85 +++++++ .../spiffs/cc32x0sf/TiSPIFFSImpl.hxx | 222 ++++++++++++++++++ .../spiffs/tm4c129/TM4C129xSPIFFS.cxx | 51 ++++ .../spiffs/tm4c129/TM4C129xSPIFFS.hxx | 43 ++++ .../spiffs/tm4c129/spiffs_config.h | 1 + .../freertos_drivers/spiffs_tm4c129/Makefile | 2 + .../freertos_drivers/spiffs_tm4c129/sources | 27 +++ 10 files changed, 445 insertions(+), 222 deletions(-) create mode 100644 src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFS.hxx create mode 100644 src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx create mode 100644 src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx create mode 100644 src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.hxx create mode 120000 src/freertos_drivers/spiffs/tm4c129/spiffs_config.h create mode 100644 targets/freertos.armv7m/freertos_drivers/spiffs_tm4c129/Makefile create mode 100644 targets/freertos.armv7m/freertos_drivers/spiffs_tm4c129/sources diff --git a/src/freertos_drivers/sources b/src/freertos_drivers/sources index 34027857b..781176d2f 100644 --- a/src/freertos_drivers/sources +++ b/src/freertos_drivers/sources @@ -41,8 +41,10 @@ SUBDIRS += mbed_lpc1768 drivers_lpc1768 tivaware lpc_chip_175x_6x \ stm32cubel431xx stm32cubel432xx stm32cubef767xx \ cc3220sdk net_cc3220 cc3220 \ net_freertos_tcp freertos_tcp ti_grlib \ - spiffs_cc32x0sf spiffs_stm32f303xe spiffs_stm32f767xx \ - + spiffs_cc32x0sf spiffs_tm4c129 \ + spiffs_stm32f303xe spiffs_stm32f767xx \ + +#spiffs_tm4c123 \ ifdef BUILDTIVAWARE SUBDIRS += tivadriverlib tivausblib diff --git a/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx b/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx index 0c090199c..381e482ba 100644 --- a/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx +++ b/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx @@ -31,14 +31,10 @@ * @date 1 January 2018 */ -// #define LOGLEVEL INFO - // This define is needed to call any ROM_xx function in the driverlib. #define USE_CC3220_ROM_DRV_API -#include "utils/logging.h" - -#include "CC32x0SFSPIFFS.hxx" +#define TI_DUAL_BANK_FLASH #include "spiffs.h" #include "inc/hw_types.h" @@ -46,173 +42,9 @@ #include "driverlib/rom.h" #include "driverlib/cpu.h" -/// 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; - -// 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. - -// Global disable interrupts. -// -// #define DI() asm("cpsid i\n") -// #define EI() asm("cpsie i\n") - -// Critical section (interrupts better than MIN_SYSCALL_PRIORITY are still -// running). -// -// #define DI() portENTER_CRITICAL() -// #define EI() portEXIT_CRITICAL() - -// Disable interrupts with a custom priority limit (must not be zero). -// -// unsigned ppri; -// constexpr unsigned minpri = 0x40; -// #define DI() ppri = CPUbasepriGet(); CPUbasepriSet(minpri); HWREG(FLASH_CONF) |= 0x20110000; -// #define EI() CPUbasepriSet(ppri); - -// No write locking. -#define DI() -#define EI() - -// 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 - -// -// CC32x0SFSPIFFS::flash_read() -// -int32_t CC32x0SFSPIFFS::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; -} - -// -// CC32x0SFSPIFFS::flash_write() -// -int32_t CC32x0SFSPIFFS::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"); - - return 0; -} - -// -// CC32x0SFSPIFFS::flash_erase() -// -int32_t CC32x0SFSPIFFS::flash_erase(uint32_t addr, uint32_t size) -{ - LOG(INFO, "Erasing %x sz %d", (unsigned)addr, (unsigned)size); - HASSERT(addr >= fs_->cfg.phys_addr && - (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; - } - - LOG(INFO, "Erasing %x done", (unsigned)addr); - return 0; -} +#include "CC32x0SFSPIFFS.hxx" +#include "../cc32x0sf/TiSPIFFSImpl.hxx" +/// Explicit instantion of the template so that the functions get compiled and +/// into this .o file and the linker would find them. +template class TiSPIFFS; diff --git a/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.hxx b/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.hxx index 253de785d..a4cffc8dc 100644 --- a/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.hxx +++ b/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.hxx @@ -34,53 +34,11 @@ #ifndef _FREERTOS_DRIVERS_SPIFFS_CC3220SF_CC32X0SFSPIFFS_HXX_ #define _FREERTOS_DRIVERS_SPIFFS_CC3220SF_CC32X0SFSPIFFS_HXX_ -#include +#include "../cc32x0sf/TiSPIFFS.hxx" -#include "SPIFFS.hxx" +static constexpr unsigned CC32xxSF_ERASE_PAGE_SIZE = 2 * 1024; -/// Specialization of Serial SPIFFS driver for CC32xx devices. -class CC32x0SFSPIFFS : public SPIFFS -{ -public: - /// Constructor. - CC32x0SFSPIFFS(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. - ~CC32x0SFSPIFFS() - { - 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(CC32x0SFSPIFFS); -}; +using CC32x0SFSPIFFS = TiSPIFFS; #endif // _FREERTOS_DRIVERS_SPIFFS_CC3220SF_CC32X0SFSPIFFS_HXX_ + diff --git a/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFS.hxx b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFS.hxx new file mode 100644 index 000000000..c6e7c8c72 --- /dev/null +++ b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFS.hxx @@ -0,0 +1,85 @@ +/** @copyright + * Copyright (c) 2018, 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 TiSPIFFS.hxx + * This file implements a SPIFFS FLASH driver using the TI driverlib abstraction. + * + * @author Stuart W. Baker + * @date 1 January 2018 + */ + +#ifndef _FREERTOS_DRIVERS_SPIFFS_CC3220SF_TISPIFFS_HXX_ +#define _FREERTOS_DRIVERS_SPIFFS_CC3220SF_TISPIFFS_HXX_ + +#include + +#include "freertos_drivers/spiffs/SPIFFS.hxx" + +/// Specialization of Serial SPIFFS driver for TI driverlib devices. +/// @param ERASE_PAGE_SIZE size of an erase page in FLASH +template +class TiSPIFFS : public SPIFFS +{ +public: + /// Constructor. + TiSPIFFS(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. + ~TiSPIFFS() + { + unmount(); + } + +private: + /// 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(TiSPIFFS); +}; + +#endif // _FREERTOS_DRIVERS_SPIFFS_CC3220SF_TISPIFFS_HXX_ diff --git a/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx new file mode 100644 index 000000000..019354a3d --- /dev/null +++ b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx @@ -0,0 +1,222 @@ +/** @copyright + * Copyright (c) 2018, 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 CC32x0SFSPIFFS.cxx + * This file implements a SPIFFS FLASH driver specific to CC32xx. + * + * @author Stuart W. Baker + * @date 1 January 2018 + */ + +// #define LOGLEVEL INFO + +#include "utils/logging.h" + +#include "TiSPIFFS.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. + +// Global disable interrupts. +// +// #define DI() asm("cpsid i\n") +// #define EI() asm("cpsie i\n") + +// Critical section (interrupts better than MIN_SYSCALL_PRIORITY are still +// running). +// +// #define DI() portENTER_CRITICAL() +// #define EI() portEXIT_CRITICAL() + +// Disable interrupts with a custom priority limit (must not be zero). +// +// unsigned ppri; +// constexpr unsigned minpri = 0x40; +// #define DI() ppri = CPUbasepriGet(); CPUbasepriSet(minpri); HWREG(FLASH_CONF) |= 0x20110000; +// #define EI() CPUbasepriSet(ppri); + +// No write locking. +#define DI() +#define EI() + +// 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() +// +template +int32_t TiSPIFFS::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; +} + +// +// TiSPIFFS::flash_write() +// +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"); + + return 0; +} + +// +// TiSPIFFS::flash_erase() +// +template +int32_t TiSPIFFS::flash_erase(uint32_t addr, uint32_t size) +{ + LOG(INFO, "Erasing %x sz %d", (unsigned)addr, (unsigned)size); + HASSERT(addr >= fs_->cfg.phys_addr && + (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; + } + + LOG(INFO, "Erasing %x done", (unsigned)addr); + return 0; +} + diff --git a/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx b/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx new file mode 100644 index 000000000..1884e68d1 --- /dev/null +++ b/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx @@ -0,0 +1,51 @@ +/** @copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @file TM4C129xSPIFFS.cxx + * This file implements a SPIFFS FLASH driver specific to TI TM4C129x. + * + * @author Balazs Racz + * @date 19 Aug 2021 + */ + +// This define is needed to call any ROM_xx function in the driverlib. +#define TARGET_IS_TM4C129_RA1 + +#define TI_DUAL_BANK_FLASH + +#include "spiffs.h" +#include "inc/hw_types.h" +#include "driverlib/flash.h" +#include "driverlib/rom.h" +#include "driverlib/rom_map.h" +#include "driverlib/cpu.h" + +#include "TM4C129xSPIFFS.hxx" +#include "../cc32x0sf/TiSPIFFSImpl.hxx" + +/// Explicit instantion of the template so that the functions get compiled and +/// into this .o file and the linker would find them. +template class TiSPIFFS; diff --git a/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.hxx b/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.hxx new file mode 100644 index 000000000..ad51067c8 --- /dev/null +++ b/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.hxx @@ -0,0 +1,43 @@ +/** @copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @file TM4C129xSPIFFS.hxx + * This file implements a SPIFFS FLASH driver specific to TI TM4C129x. + * + * @author Balazs Racz + * @date 19 August 2021 + */ + +#ifndef _FREERTOS_DRIVERS_SPIFFS_TM4C129_TM4C129xSPIFFS_HXX_ +#define _FREERTOS_DRIVERS_SPIFFS_TM4C129_TM4C129xSPIFFS_HXX_ + +#include "../cc32x0sf/TiSPIFFS.hxx" + +static constexpr unsigned TM4C129x_ERASE_PAGE_SIZE = 16 * 1024; + +using TM4C129xSPIFFS = TiSPIFFS; + +#endif // _FREERTOS_DRIVERS_SPIFFS_TM4C129_TM4C129xSPIFFS_HXX_ diff --git a/src/freertos_drivers/spiffs/tm4c129/spiffs_config.h b/src/freertos_drivers/spiffs/tm4c129/spiffs_config.h new file mode 120000 index 000000000..05fd7656f --- /dev/null +++ b/src/freertos_drivers/spiffs/tm4c129/spiffs_config.h @@ -0,0 +1 @@ +../cc32x0sf/spiffs_config.h \ No newline at end of file diff --git a/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c129/Makefile b/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c129/Makefile new file mode 100644 index 000000000..72e52af15 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c129/Makefile @@ -0,0 +1,2 @@ +OPENMRNPATH ?= $(realpath ../../../..) +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c129/sources b/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c129/sources new file mode 100644 index 000000000..c185dc1c8 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c129/sources @@ -0,0 +1,27 @@ +DEPS += SPIFFSPATH + +include $(OPENMRNPATH)/etc/tivaware.mk + +VPATH := $(SPIFFSPATH)/src: \ + $(OPENMRNPATH)/src/freertos_drivers/spiffs: \ + $(OPENMRNPATH)/src/freertos_drivers/spiffs/tm4c129 + +CSRCS += spiffs_cache.c \ + spiffs_check.c \ + spiffs_gc.c \ + spiffs_hydrogen.c \ + spiffs_nucleus.c \ + spiffs_nucleus.c \ + +CXXSRCS += SPIFFS.cxx \ + TM4C129xSPIFFS.cxx + + +INCLUDES += -I$(SPIFFSPATH)/src \ + -I$(OPENMRNPATH)/src/freertos_drivers/spiffs \ + -I$(OPENMRNPATH)/src/freertos_drivers/spiffs/tm4c129 \ + +CFLAGS += -DNO_TEST + +CXXFLAGS += + From 040bce0ee86e0ea85323040210c3081aaef64f35 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 11 Sep 2021 19:24:22 +0200 Subject: [PATCH 05/62] Fixes a static initialization bug under linux. (#571) The osGlobalAtomic variable was accessed from the non-default threads while the static initializers were running. This caused the mutex to be lost sometimes. After this fix a static Atomic variable does not have a constructor call, but is linker-initialized from the .data section. This bug caused the hub application to get sometimes "livelocked" on linux, with the packets printed to the screen but not being sent to any connected devices actually. --- src/os/OSImpl.cxx | 6 ++++-- src/utils/Atomic.hxx | 19 +++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/os/OSImpl.cxx b/src/os/OSImpl.cxx index 8eadd2671..2213bd6b6 100644 --- a/src/os/OSImpl.cxx +++ b/src/os/OSImpl.cxx @@ -37,11 +37,13 @@ static Atomic osGlobalAtomic; extern "C" { -void os_atomic_lock() { +void os_atomic_lock() +{ osGlobalAtomic.lock(); } -void os_atomic_unlock() { +void os_atomic_unlock() +{ osGlobalAtomic.unlock(); } diff --git a/src/utils/Atomic.hxx b/src/utils/Atomic.hxx index 76c82cb3c..143b0f794 100644 --- a/src/utils/Atomic.hxx +++ b/src/utils/Atomic.hxx @@ -148,9 +148,24 @@ private: /// Usage: Declare Atomic as a private base class, add a class member /// variable or a global variable of type Atomic. Then use AtomicHolder to /// protect the critical sections. -class Atomic : public OSMutex { +class Atomic +{ public: - Atomic() : OSMutex(true) {} + void lock() + { + os_mutex_lock(&mu_); + } + void unlock() + { + os_mutex_unlock(&mu_); + } +private: + /// Mutex that protects. + /// + /// NOTE: it is important that this be trivially initialized and the Atomic + /// class have no (nontrivial) constructor. This is the only way to avoid + /// race conditions and initialization order problems during startup. + os_mutex_t mu_ = OS_RECURSIVE_MUTEX_INITIALIZER; }; #endif From 76133245072c06a6ed39475ef1ef28ba31a3783b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 11 Sep 2021 19:56:50 +0200 Subject: [PATCH 06/62] Allows negative length for EmptyGroup, causing reverse jumps in the address space. (#574) --- src/openlcb/ConfigRenderer.hxx | 6 +++--- src/openlcb/ConfigRepresentation.cxxtest | 27 ++++++++++++++++++++++++ src/openlcb/ConfigRepresentation.hxx | 4 ++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/openlcb/ConfigRenderer.hxx b/src/openlcb/ConfigRenderer.hxx index 5a39225f0..2a72d7140 100644 --- a/src/openlcb/ConfigRenderer.hxx +++ b/src/openlcb/ConfigRenderer.hxx @@ -346,19 +346,19 @@ public: typedef GroupConfigOptions OptionsType; - constexpr EmptyGroupConfigRenderer(unsigned size) + constexpr EmptyGroupConfigRenderer(int size) : size_(size) { } template void render_cdi(string *s, Args... args) const { - *s += StringPrintf("\n", size_); + *s += StringPrintf("\n", size_); } private: /// The number of bytes this group has to skip. - unsigned size_; + int size_; }; /// Helper class for rendering the cdi.xml of groups, segments and the toplevel diff --git a/src/openlcb/ConfigRepresentation.cxxtest b/src/openlcb/ConfigRepresentation.cxxtest index 40fb099fd..d3205db5d 100644 --- a/src/openlcb/ConfigRepresentation.cxxtest +++ b/src/openlcb/ConfigRepresentation.cxxtest @@ -304,5 +304,32 @@ TEST(Test2GroupTest, Offsets) { } // namespace test2 +namespace test3 { + +CDI_GROUP(TestGroup); +CDI_GROUP_ENTRY(first, Uint8ConfigEntry); +CDI_GROUP_ENTRY(back, EmptyGroup<-1>); +CDI_GROUP_ENTRY(second, Uint8ConfigEntry); +CDI_GROUP_ENTRY(third, Uint16ConfigEntry); +CDI_GROUP_ENTRY(back2, EmptyGroup<-2>); +// some comment in here +CDI_GROUP_ENTRY(fourth, Uint16ConfigEntry); +CDI_GROUP_ENTRY(back3, EmptyGroup<-2>); +CDI_GROUP_END(); + +TEST(Test3GroupTest, NegativeOffsets) +{ + TestGroup grp(11); + EXPECT_EQ(1u, TestGroup::size()); + EXPECT_EQ(11u, grp.first().offset()); + EXPECT_EQ(11u, grp.second().offset()); + EXPECT_EQ(12u, grp.third().offset()); + EXPECT_EQ(12u, grp.fourth().offset()); + EXPECT_EQ(12u, grp.end_offset()); +} + +} // namespace test2 + + } // namespace } // namespace openlcb diff --git a/src/openlcb/ConfigRepresentation.hxx b/src/openlcb/ConfigRepresentation.hxx index 0b8e1560b..18caf02b4 100644 --- a/src/openlcb/ConfigRepresentation.hxx +++ b/src/openlcb/ConfigRepresentation.hxx @@ -368,12 +368,12 @@ public: /// Defines an empty group with no members, but blocking a certain amount of /// space in the rendered configuration. /// -template class EmptyGroup : public ConfigEntryBase +template class EmptyGroup : public ConfigEntryBase { public: using base_type = ConfigEntryBase; INHERIT_CONSTEXPR_CONSTRUCTOR(EmptyGroup, base_type) - static constexpr unsigned size() + static constexpr int size() { return N; } From 20548a5a8fa6da783fe17d7b7deaf9fff1462f3d Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 11 Sep 2021 19:57:46 +0200 Subject: [PATCH 07/62] Adds locking to Tiva SPIFFs. (#573) * Fixes a missing initialization in the SPIFFS driver. * Switches on write locking for flash writes using basepri. * Makes the locking configurable. * Adds locking choice. --- src/freertos_drivers/spiffs/SPIFFS.cxx | 1 + .../spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx | 1 + .../spiffs/cc32x0sf/TiSPIFFSImpl.hxx | 70 +++++++++++++++---- .../spiffs/tm4c129/TM4C129xSPIFFS.cxx | 1 + 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/freertos_drivers/spiffs/SPIFFS.cxx b/src/freertos_drivers/spiffs/SPIFFS.cxx index faf89a091..c48ee7882 100644 --- a/src/freertos_drivers/spiffs/SPIFFS.cxx +++ b/src/freertos_drivers/spiffs/SPIFFS.cxx @@ -117,6 +117,7 @@ SPIFFS::SPIFFS(size_t physical_address, size_t size_on_disk, , anyDirty_(false) { fs_ = new spiffs; + memset(fs_, 0, sizeof(spiffs)); fs_->user_data = this; spiffs_config tmp{ // .hal_read_f = flash_read, diff --git a/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx b/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx index 381e482ba..77aee9694 100644 --- a/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx +++ b/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx @@ -35,6 +35,7 @@ #define USE_CC3220_ROM_DRV_API #define TI_DUAL_BANK_FLASH +#define TISPIFFS_LOCK_NONE #include "spiffs.h" #include "inc/hw_types.h" diff --git a/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx index 019354a3d..19b4d9222 100644 --- a/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx +++ b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx @@ -60,28 +60,74 @@ static constexpr unsigned addr_mirror = 0; // 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") + +#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() -// Disable interrupts with a custom priority limit (must not be zero). -// -// unsigned ppri; -// constexpr unsigned minpri = 0x40; -// #define DI() ppri = CPUbasepriGet(); CPUbasepriSet(minpri); HWREG(FLASH_CONF) |= 0x20110000; -// #define EI() CPUbasepriSet(ppri); +#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. diff --git a/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx b/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx index 1884e68d1..79696e36b 100644 --- a/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx +++ b/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx @@ -35,6 +35,7 @@ #define TARGET_IS_TM4C129_RA1 #define TI_DUAL_BANK_FLASH +#define TISPIFFS_LOCK_BASEPRI_FF #include "spiffs.h" #include "inc/hw_types.h" From 450b2eb5fa09c67527237d726ccf0e7f9dc972de Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 11 Sep 2021 19:58:05 +0200 Subject: [PATCH 08/62] Fix TivaSPI bug. The FIFO depth is just 8. (#572) --- src/freertos_drivers/ti/TivaSPI.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/freertos_drivers/ti/TivaSPI.hxx b/src/freertos_drivers/ti/TivaSPI.hxx index 52202e37a..c2e8443b9 100644 --- a/src/freertos_drivers/ti/TivaSPI.hxx +++ b/src/freertos_drivers/ti/TivaSPI.hxx @@ -189,7 +189,7 @@ private: do { /* fill TX FIFO but make sure we don't fill it to overflow */ - if (tx_len && ((rx_len - tx_len) < (32 / sizeof(T)))) + if (tx_len && ((rx_len - tx_len) < 8)) { if (data_put_non_blocking(*tx_buf) != 0) { From 802bfd5587b6db630c386e27afcb4e5312ee9be8 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 2 Oct 2021 19:33:47 +0200 Subject: [PATCH 09/62] Adds platform-independent interface for sleeping. (#577) The intention is to replace all usage of deprecated POSIX function `usleep` with the function defined in this cross-platform header. Then different platforms can provide their implementation. This will allow us to remove the feature macro hacks that we had to put in to make the code compile on recent releases of armgcc, such as `#define _BSD_SOURCE` or `#define _DEFAULT_SOURCE`. These are harmful, because they often conflict with each other and sometimes with expectations from middleware libraries (e.g. a macro declaring C99 source). === * Adds platform-independent interface for sleeping. * Fix typo --- src/os/sleep.h | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/os/sleep.h diff --git a/src/os/sleep.h b/src/os/sleep.h new file mode 100644 index 000000000..bd8c883c9 --- /dev/null +++ b/src/os/sleep.h @@ -0,0 +1,52 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file sleep.h + * + * Declares cross-platform sleep functions. + * + * @author Balazs Racz + * @date 10 Sep 2021 + */ + +#ifndef _OS_SLEEP_H_ +#define _OS_SLEEP_H_ + +#include + +/// Sleep a given number of microseconds. The granularity of sleep depends on +/// the operating system, for FreeRTOS sleeping less than 1 msec is not +/// possible with this function. +/// @param microseconds how long to sleep. +static void microsleep(uint32_t microseconds) __attribute__((weakref("usleep"))); + + +/// Executes a busy loop for a given amount of time. It is recommended to use +/// this only for small number of microseconds (e.g. <100 usec). +/// @param microseconds how long to delay. +extern void microdelay(uint32_t microseconds); + +#endif // _OS_SLEEP_H_ From c2ecedc65375361eeaabce0c0b5fdd1a33dfa8dd Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 4 Oct 2021 20:19:36 +0200 Subject: [PATCH 10/62] Adds integration API for HTTP POST uploads on the CC32xx web server. (#578) - adds implementation to the NetAppRequestHandler - adds callback storage holding POST callbacks by URI. - adds API to register POST callback - The first POST callback is called on the network thread, further uploaded data is acquired by additional calls on the CC32xxWiFi class. - Adds POST response function on the CC32xxWiFi. Homogenizes the calls for Get Token and Post Callbacks: - Simplifies the API of registering Tokens - Clarifies the documentation to avoid confusion of a Get Token with an HTTP GET operation. - Reverses the order of pairs to match it with what an std::map<>::iterator has. - Fixes a bug in string comparison for the token get implementation. --- .../net_cc32xx/CC32xxWiFi.cxx | 177 ++++++++++++++++-- .../net_cc32xx/CC32xxWiFi.hxx | 99 ++++++++-- 2 files changed, 244 insertions(+), 32 deletions(-) diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx index bb5dcb399..731571a26 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx @@ -64,6 +64,12 @@ struct CC32xxWiFi::HttpServerEvent : public ::SlNetAppHttpServerEvent_t {}; /** CC32xx forward declaration Helper */ struct CC32xxWiFi::HttpServerResponse : public ::SlNetAppHttpServerResponse_t {}; +/** CC32xx forward declaration Helper */ +struct CC32xxWiFi::NetAppRequest : public ::SlNetAppRequest_t {}; + +/** CC32xx forward declaration Helper */ +struct CC32xxWiFi::NetAppResponse : public ::SlNetAppResponse_t {}; + /** CC32xx forward declaration Helper */ struct CC32xxWiFi::FatalErrorEvent : public ::SlDeviceFatal_t {}; @@ -192,8 +198,8 @@ CC32xxWiFi::CC32xxWiFi() SL_SOCKET_FD_ZERO(&efds); ssid[0] = '\0'; - add_http_get_callback(make_pair(bind(&CC32xxWiFi::http_get_ip_address, - this), "__SL_G_UNA")); + add_http_get_token_callback( + "__SL_G_UNA", bind(&CC32xxWiFi::http_get_ip_address, this)); } uint8_t CC32xxWiFi::security_type_to_simplelink(SecurityType sec_type) @@ -1355,31 +1361,41 @@ void CC32xxWiFi::http_server_callback(HttpServerEvent *event, case SL_NETAPP_EVENT_HTTP_TOKEN_GET: { unsigned char *ptr; - ptr = response->ResponseData.TokenValue.pData; response->ResponseData.TokenValue.Len = 0; - - for (unsigned i = 0; i < httpGetCallbacks_.size(); ++i) + string token((const char *)event->EventData.HttpTokenName.pData, + event->EventData.HttpTokenName.Len); + LOG(VERBOSE, "token get %s", token.c_str()); { - if (strcmp((const char *)event->EventData.HttpTokenName.pData, - httpGetCallbacks_[i].second) == 0) + OSMutexLock l(&lock_); + for (unsigned i = 0; i < httpGetTokenCallbacks_.size(); ++i) { - string result = httpGetCallbacks_[i].first(); - // clip string if required - if (result.size() >= SL_NETAPP_MAX_TOKEN_VALUE_LEN) + if (token == httpGetTokenCallbacks_[i].first) { - result.erase(SL_NETAPP_MAX_TOKEN_VALUE_LEN); + string result = httpGetTokenCallbacks_[i].second(); + // clip string if required + if (result.size() >= SL_NETAPP_MAX_TOKEN_VALUE_LEN) + { + result.resize(SL_NETAPP_MAX_TOKEN_VALUE_LEN); + } + memcpy(ptr, result.data(), result.size()); + ptr += result.size(); + response->ResponseData.TokenValue.Len += result.size(); + break; } - memcpy(ptr, result.c_str(), result.size()); - ptr += result.size(); - response->ResponseData.TokenValue.Len += result.size(); - break; } } break; } case SL_NETAPP_EVENT_HTTP_TOKEN_POST: { + string token( + (const char *)event->EventData.HttpPostData.TokenName.pData, + event->EventData.HttpPostData.TokenName.Len); + string val( + (const char *)event->EventData.HttpPostData.TokenValue.pData, + event->EventData.HttpPostData.TokenValue.Len); + LOG(VERBOSE, "token post %s=%s", token.c_str(), val.c_str()); break; } default: @@ -1387,6 +1403,127 @@ void CC32xxWiFi::http_server_callback(HttpServerEvent *event, } } +/* + * CC32xxWiFi::netapp_request_callback() + */ +void CC32xxWiFi::netapp_request_callback( + NetAppRequest *request, NetAppResponse *response) +{ + if (!request || !response || request->AppId != SL_NETAPP_HTTP_SERVER_ID) + { + return; + } + + string uri; + uint32_t content_length = 0xFFFFFFFFu; + + uint8_t *meta_curr = request->requestData.pMetadata; + uint8_t *meta_end = meta_curr + request->requestData.MetadataLen; + while (meta_curr < meta_end) + { + uint8_t meta_type = *meta_curr; /* meta_type is one byte */ + meta_curr++; + uint16_t meta_len = *(_u16 *)meta_curr; /* Length is two bytes */ + meta_curr += 2; + switch (meta_type) + { + case SL_NETAPP_REQUEST_METADATA_TYPE_HTTP_CONTENT_LEN: + memcpy(&content_length, meta_curr, meta_len); + break; + case SL_NETAPP_REQUEST_METADATA_TYPE_HTTP_REQUEST_URI: + uri.assign((const char *)meta_curr, meta_len); + break; + } + meta_curr += meta_len; + } + // Do we have more data to come? + bool has_more = request->requestData.Flags & + SL_NETAPP_REQUEST_RESPONSE_FLAGS_CONTINUATION; + bool found = false; + switch (request->Type) + { + case SL_NETAPP_REQUEST_HTTP_POST: + { + OSMutexLock l(&lock_); + for (unsigned i = 0; i < httpPostCallbacks_.size(); ++i) + { + if (uri == httpPostCallbacks_[i].first) + { + found = true; + httpPostCallbacks_[i].second(request->Handle, + content_length, request->requestData.pMetadata, + request->requestData.MetadataLen, + request->requestData.pPayload, + request->requestData.PayloadLen, has_more); + break; + } + } + if (found) + { + break; + } + } + // Fall through to 404. + default: + response->Status = SL_NETAPP_HTTP_RESPONSE_404_NOT_FOUND; + response->ResponseData.pMetadata = NULL; + response->ResponseData.MetadataLen = 0; + response->ResponseData.pPayload = NULL; + response->ResponseData.PayloadLen = 0; + response->ResponseData.Flags = 0; + return; + } +} + +/* + * CC32xxWiFi::get_post_data() + */ +bool CC32xxWiFi::get_post_data(uint16_t handle, void *buf, size_t *len) +{ + uint16_t ulen = *len; + uint32_t flags; + int ret = sl_NetAppRecv(handle, &ulen, (uint8_t *)buf, &flags); + if (ret < 0 || (flags & SL_NETAPP_REQUEST_RESPONSE_FLAGS_ERROR)) + { + *len = 0; + return false; + } + *len = ulen; + return (flags & SL_NETAPP_REQUEST_RESPONSE_FLAGS_CONTINUATION) != 0; +} + +/* + * CC32xxWiFi::send_post_response() + */ +void CC32xxWiFi::send_post_respose( + uint16_t handle, uint16_t http_status, const string &redirect) +{ + // Pieces together a valid metadata structure for SimpleLink. + string md; + uint16_t len; + if (!redirect.empty()) + { + http_status = SL_NETAPP_HTTP_RESPONSE_302_MOVED_TEMPORARILY; + } + md.push_back(SL_NETAPP_REQUEST_METADATA_TYPE_STATUS); + len = 2; + md.append((const char *)&len, 2); + md.append((const char *)&http_status, 2); + if (!redirect.empty()) + { + md.push_back(SL_NETAPP_REQUEST_METADATA_TYPE_HTTP_LOCATION); + len = redirect.size(); + md.append((const char *)&len, 2); + md += redirect; + } + // Now we need to move the metadata content to a buffer that is + // malloc'ed. This buffer will be freed asynchronously through a callback. + void *buf = malloc(md.size()); + memcpy(buf, md.data(), md.size()); + + uint32_t flags = SL_NETAPP_REQUEST_RESPONSE_FLAGS_METADATA; + sl_NetAppSend(handle, md.size(), (uint8_t *)buf, flags); +} /* * CC32xxWiFi::fatal_error_callback() @@ -1521,7 +1658,15 @@ void SimpleLinkHttpServerEventHandler( void SimpleLinkNetAppRequestEventHandler(SlNetAppRequest_t *pNetAppRequest, SlNetAppResponse_t *pNetAppResponse) { - /* Unused in this application */ + LOG(VERBOSE, + "netapprq app %d type %d hdl %d mdlen %u payloadlen %u flags %u", + pNetAppRequest->AppId, pNetAppRequest->Type, pNetAppRequest->Handle, + pNetAppRequest->requestData.MetadataLen, + pNetAppRequest->requestData.PayloadLen, + (unsigned)pNetAppRequest->requestData.Flags); + CC32xxWiFi::instance()->netapp_request_callback( + static_cast(pNetAppRequest), + static_cast(pNetAppResponse)); } /** diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx index 52abc7db3..2ea783681 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx @@ -118,6 +118,12 @@ public: /** CC32xx SimpleLink forward declaration */ struct HttpServerResponse; + /** CC32xx SimpleLink forward declaration */ + struct NetAppRequest; + + /** CC32xx SimpleLink forward declaration */ + struct NetAppResponse; + /** CC32xx SimpleLink forward declaration */ struct FatalErrorEvent; @@ -151,6 +157,24 @@ public: int rssi; /**< receive signal strength indicator of the AP */ }; + /** This function type is used for POST callback operations to the + * application. + * @param handle the operation handle, needs to be provided to the future + * operations to fetch followup data and send response. + * @param content_length value of the Content-Length header, or -1 if such + * a header is not found. + * @param md encoded metadata. See the CC32xx documentation on how metadata + * is encoded. The lifetime is restricted to this call inline. + * @param md_len number of bytes in the metadata array + * @param payload the content (or beginning of the content). The lifetime is + * restricted to this call inline. + * @param payload_len how many bytes are in this chunk of the content + * @param has_more true if there is a continuation of the payload, which + * needs to be fetched with get_post_data. */ + using PostFunction = std::function; + /** Constructor. */ CC32xxWiFi(); @@ -425,12 +449,14 @@ public: * isthe function to execute.*/ void run_on_network_thread(std::function callback); - /** Add an HTTP get token callback. A get token is a string that takes the - * form "__SL_G_*". The form "__SL_G_U*" is the form that is reserved for - * user defined tokens. The * can be any two characters that uniquely - * identify the token. When the token is found in an HTML file, the - * network processor will call the supplied callback in order for the user - * to return the resulting string. The result returned will be clipped at + /** Add an HTTP get token callback. A get token is a simple macro + * substitution that is applied to all files (e.g. HTML, JS) served by the + * builtin webserver of the CC32xx. The token has a fixed form "__SL_G_*". + * The form "__SL_G_U*" is the form that is reserved for user defined + * tokens. The * can be any two characters that uniquely identify the + * token. When the token is found in an HTML file, the network processor + * will call the supplied callback in order for the user to return the + * substitution string. The result returned will be clipped at * (MAX_TOKEN_VALUE_LEN - 1), which is (64 - 1) bytes. All tokens must be * an exact match. * @@ -441,7 +467,7 @@ public: * public: * SomeClass() * { - * add_http_get_callback(std::make_pair(std::bind(&SomeClass::http_get, this), "__SL_G_U.A"); + * add_http_get_token_callback("__SL_G_U.A", std::bind(&SomeClass::http_get, this)); * } * * private: @@ -460,18 +486,48 @@ public: * * CC3100/CC3200 SimpleLink Wi-Fi Internet-on-a-Chip User's Guide * - * @param callback the std::pair<> of the function to execute and the - * matching token to execute the callback on. The second (const - * char *) argument of the std::pair must live for as long as the - * callback is valid. + * @param token_name The token name to match. Must live for the entire + * lifetime of the binary. Must be of the form __SL_G_U?? + * @param callback the function to execute to give the replacement. */ - void add_http_get_callback(std::pair, - const char *> callback) + void add_http_get_token_callback( + const char *token_name, std::function callback) { OSMutexLock l(&lock_); - httpGetCallbacks_.emplace_back(std::move(callback)); + httpGetTokenCallbacks_.emplace_back(token_name, std::move(callback)); } + /** Registers a handler for an HTTP POST operation. + * @param uri the target of the form submit, of the format "/foo/bar" + * @param callback this function will be called from the network processor + * context when a POST happens to the given URI. + */ + void add_http_post_callback(const char *uri, PostFunction callback) + { + OSMutexLock l(&lock_); + httpPostCallbacks_.emplace_back(uri, std::move(callback)); + } + + /** Retrieves additional payload for http POST operations. This function + * blocks the calling thread. After the lat chunk is retrieved, the caller + * must invoke the post response function. + * @param handle the POST operation handle, given by the POST callback. + * @param buf where to deposit additional data. + * @param len at input, set to the max number of bytes to store. Will be + * overwritten by the number of actual bytes that arrived. + * @return true if there is additional data that needs to be fetched, false + * if this was the last chunk. */ + bool get_post_data(uint16_t handle, void *buf, size_t *len); + + /** Sends a POST response. + * @param handle the POST operation handle, given by the POST callback. + * @param code HTTP error code (e.g. 204 for success). + * @param redirect optional, if present, will send back a 302 redirect + * status with this URL (http_status will be ignored). + */ + void send_post_respose(uint16_t handle, uint16_t http_status = 204, + const string &redirect = ""); + /** This function handles WLAN events. This is public only so that an * extern "C" method can call it. DO NOT use directly. * @param event pointer to WLAN Event Info @@ -499,6 +555,14 @@ public: void http_server_callback(HttpServerEvent *event, HttpServerResponse *response); + /** This function handles netapp request callbacks. This is public + * only so that an extern "C" method can call it. DO NOT use directly. + * @param request pointer to NetApp Request info + * @param response pointer to NetApp Response info + */ + void netapp_request_callback( + NetAppRequest *request, NetAppResponse *response); + /** This Function Handles the Fatal errors * @param event - Contains the fatal error data * @return None @@ -586,8 +650,11 @@ private: std::vector > callbacks_; /// List of callbacks for http get tokens - std::vector, const char *>> - httpGetCallbacks_; + std::vector>> + httpGetTokenCallbacks_; + + /// List of callbacks for http post handlers + std::vector> httpPostCallbacks_; /// Protects callbacks_ vector. OSMutex lock_; From d9ae5f738f3a22502ec638a598bd084f9f7ca759 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Tue, 5 Oct 2021 10:39:24 -0500 Subject: [PATCH 11/62] WIP Refactor EntryModel (#576) * refactor Entry model for integeter and STL API likeness. * Add more test coverage. * Only place leading zeros if there is room. * Overflow protection for leading zero count. * Access for leading zeros present. * increment/decrement operators plus improved clamping functionality. * Add more tests. * remove dead code. * Add EntryModelBounded. * Change all comments to C++ style for consistency. * remove lower case hex option. * Cleanup size_ handling. * Change logic into assert which should always be true. * Simplify code. * Fix other review comments. * Fix/add tests. * Add access method for maximum size (in digits). --- src/utils/EntryModel.cxxtest | 503 +++++++++++++++++++++++++++++ src/utils/EntryModel.hxx | 610 ++++++++++++++++++++--------------- 2 files changed, 849 insertions(+), 264 deletions(-) create mode 100644 src/utils/EntryModel.cxxtest diff --git a/src/utils/EntryModel.cxxtest b/src/utils/EntryModel.cxxtest new file mode 100644 index 000000000..033853e46 --- /dev/null +++ b/src/utils/EntryModel.cxxtest @@ -0,0 +1,503 @@ +#include "utils/test_main.hxx" +#include "utils/EntryModel.hxx" + +TEST(EntryModelTest, Create) +{ + EntryModel em; + + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(0U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_TRUE(em.empty()); +} + +TEST(EntryModelTest, InitEmpty) +{ + EntryModel em; + EntryModel uem; + + // signed + em.init(4, 10); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(0U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_TRUE(em.empty()); + + // unsigned + em.init(4, 10); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0U, em.get_value()); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(0U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_TRUE(em.empty()); +} + +TEST(EntryModelTest, InitValue) +{ + EntryModel em; + EntryModel uem; + + // signed + // non-zero positive init + em.init(4, 10, 24); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(24, em.get_value()); + EXPECT_EQ("24", em.get_string()); + EXPECT_EQ(" 24", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // non-zero negagive init + em.init(4, 10, -24); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(-24, em.get_value()); + EXPECT_EQ("-24", em.get_string()); + EXPECT_EQ(" -24", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // zero init + em.init(4, 10, 0); + + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("0", em.get_string()); + EXPECT_EQ(" 0", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // unsigned + // non-zero positive init + uem.init(4, 10, 24); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(24U, uem.get_value()); + EXPECT_EQ("24", uem.get_string()); + EXPECT_EQ(" 24", uem.get_string(true)); + EXPECT_EQ(0U, uem.size()); + EXPECT_TRUE(uem.is_at_initial_value()); + EXPECT_FALSE(uem.empty()); + + // zero init + uem.init(4, 10, 0); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_EQ(0U, uem.size()); + EXPECT_TRUE(uem.is_at_initial_value()); + EXPECT_FALSE(uem.empty()); +} + +TEST(EntryModelTest, InitValueClear) +{ + EntryModel em; + EntryModel uem; + + em.init(4, 10, -24); + em.clear(); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(0U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_TRUE(em.empty()); + + uem.init(4, 10, 24); + uem.clear(); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(0U, uem.size()); + EXPECT_FALSE(uem.is_at_initial_value()); + EXPECT_TRUE(uem.empty()); +} + +TEST(EntryModelTest, InitValuePopBack) +{ + EntryModel em; + EntryModel uem; + + // signed + em.init(4, 10, -24); + em.pop_back(); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(-2, em.get_value()); + EXPECT_EQ("-2", em.get_string()); + EXPECT_EQ(" -2", em.get_string(true)); + EXPECT_EQ(2U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // unsigned + uem.init(4, 10, 24); + uem.pop_back(); + + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(2U, uem.get_value()); + EXPECT_EQ("2", uem.get_string()); + EXPECT_EQ(" 2", uem.get_string(true)); + EXPECT_EQ(1U, uem.size()); + EXPECT_FALSE(uem.is_at_initial_value()); + EXPECT_FALSE(uem.empty()); + + // satureate the pop_back operations + uem.pop_back(); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + uem.pop_back(); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + + // unsigned 0 value with leading zeros + uem.init(4, 10, 0); + + EXPECT_EQ(4U, em.max_size()); + uem.push_back_char('0'); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_FALSE(uem.has_leading_zeros()); + uem.push_back(0); + EXPECT_EQ("00", uem.get_string()); + EXPECT_EQ(" 00", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + uem.pop_back(); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_FALSE(uem.has_leading_zeros()); + uem.pop_back(); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + EXPECT_FALSE(uem.has_leading_zeros()); +} + +TEST(EntryModelTest, PushBackAndAppend) +{ + EntryModel em; + EntryModel uem; + + // + // signed base 10 + // + em.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + em.push_back_char('1'); + EXPECT_EQ("1", em.get_string()); + EXPECT_EQ(" 1", em.get_string(true)); + EXPECT_EQ(1, em.get_value()); + + em.change_sign(); + EXPECT_EQ("-1", em.get_string()); + EXPECT_EQ(" -1", em.get_string(true)); + EXPECT_EQ(-1, em.get_value()); + + em.push_back_char('2'); + EXPECT_EQ("-12", em.get_string()); + EXPECT_EQ(" -12", em.get_string(true)); + EXPECT_EQ(-12, em.get_value()); + + em.push_back_char('3'); + EXPECT_EQ("-123", em.get_string()); + EXPECT_EQ("-123", em.get_string(true)); + EXPECT_EQ(-123, em.get_value()); + + em.push_back_char('4'); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(" ", em.get_string(true)); + EXPECT_EQ(0, em.get_value()); + EXPECT_TRUE(em.empty()); + + em.push_back_char('5'); + EXPECT_EQ("5", em.get_string()); + EXPECT_EQ(" 5", em.get_string(true)); + EXPECT_EQ(5, em.get_value()); + + // + // unsigned base 10 + // + uem.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + uem.push_back_char('1'); + EXPECT_EQ("1", uem.get_string()); + EXPECT_EQ(" 1", uem.get_string(true)); + EXPECT_EQ(1U, uem.get_value()); + + uem.push_back_char('2'); + EXPECT_EQ("12", uem.get_string()); + EXPECT_EQ(" 12", uem.get_string(true)); + EXPECT_EQ(12U, uem.get_value()); + + uem.push_back_char('3'); + EXPECT_EQ("123", uem.get_string()); + EXPECT_EQ(" 123", uem.get_string(true)); + EXPECT_EQ(123U, uem.get_value()); + + uem.push_back_char('4'); + EXPECT_EQ("1234", uem.get_string()); + EXPECT_EQ("1234", uem.get_string(true)); + EXPECT_EQ(1234U, uem.get_value()); + + uem.push_back_char('5'); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_TRUE(uem.empty()); + + uem.push_back_char('6'); + EXPECT_EQ("6", uem.get_string()); + EXPECT_EQ(" 6", uem.get_string(true)); + EXPECT_EQ(6U, uem.get_value()); + + // + // signed base 16 + // + em.init(4, 16, 0); + EXPECT_EQ(4U, em.max_size()); + em.push_back_char('1'); + EXPECT_EQ("1", em.get_string()); + EXPECT_EQ(" 1", em.get_string(true)); + EXPECT_EQ(0x1, em.get_value()); + + em.change_sign(); + EXPECT_EQ("-1", em.get_string()); + EXPECT_EQ(" -1", em.get_string(true)); + EXPECT_EQ(-0x1, em.get_value()); + + em.push_back_char('A'); + EXPECT_EQ("-1A", em.get_string()); + EXPECT_EQ(" -1A", em.get_string(true)); + EXPECT_EQ(-0x1A, em.get_value()); + + em.change_sign(); + EXPECT_EQ("1A", em.get_string()); + EXPECT_EQ(" 1A", em.get_string(true)); + EXPECT_EQ(0x1A, em.get_value()); + + em.push_back_char('3'); + EXPECT_EQ("1A3", em.get_string()); + EXPECT_EQ(" 1A3", em.get_string(true)); + EXPECT_EQ(0x1A3, em.get_value()); + + em.push_back_char('b'); + EXPECT_EQ("1A3B", em.get_string()); + EXPECT_EQ("1A3B", em.get_string(true)); + EXPECT_EQ(0x1A3B, em.get_value()); + + em.push_back_char('5'); + EXPECT_EQ("", em.get_string()); + EXPECT_EQ(" ", em.get_string(true)); + EXPECT_EQ(0x0, em.get_value()); + EXPECT_TRUE(em.empty()); + + em.push_back_char('6'); + EXPECT_EQ("6", em.get_string()); + EXPECT_EQ(" 6", em.get_string(true)); + EXPECT_EQ(0x6, em.get_value()); + + // + // unsigned base 16 + // + uem.init(4, 16, 0); + EXPECT_EQ(4U, em.max_size()); + uem.push_back_char('1'); + EXPECT_EQ("1", uem.get_string()); + EXPECT_EQ(" 1", uem.get_string(true)); + EXPECT_EQ(0x1U, uem.get_value()); + + uem.push_back_char('A'); + EXPECT_EQ("1A", uem.get_string()); + EXPECT_EQ(" 1A", uem.get_string(true)); + EXPECT_EQ(0x1AU, uem.get_value()); + + uem.push_back_char('3'); + EXPECT_EQ("1A3", uem.get_string()); + EXPECT_EQ(" 1A3", uem.get_string(true)); + EXPECT_EQ(0x1A3U, uem.get_value()); + + uem.push_back_char('b'); + EXPECT_EQ("1A3B", uem.get_string()); + EXPECT_EQ("1A3B", uem.get_string(true)); + EXPECT_EQ(0x1A3BU, uem.get_value()); + + uem.push_back_char('5'); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + EXPECT_EQ(0x0U, uem.get_value()); + EXPECT_TRUE(uem.empty()); + + uem.push_back_char('6'); + EXPECT_EQ("6", uem.get_string()); + EXPECT_EQ(" 6", uem.get_string(true)); + EXPECT_EQ(0x6U, uem.get_value()); + + // + // append unsigned base 10 + // + uem.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + uem.append_char('1').append(2).append_char('3'); + EXPECT_EQ("123", uem.get_string()); + EXPECT_EQ(" 123", uem.get_string(true)); + EXPECT_EQ(123U, uem.get_value()); +} + +TEST(EntryModelTest, IncrementDecrementClamp) +{ + EntryModel em; + + // + // signed base 10, decrement + // + em.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + --em; + --em; + EXPECT_EQ("-2", em.get_string()); + EXPECT_EQ(" -2", em.get_string(true)); + EXPECT_EQ(-2, em.get_value()); + for (unsigned i = 0; i < 997; ++i) + { + --em; + } + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + --em; + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + + // increment + em.set_value(0); + ++em; + ++em; + EXPECT_EQ("2", em.get_string()); + EXPECT_EQ(" 2", em.get_string(true)); + EXPECT_EQ(2, em.get_value()); + for (unsigned i = 0; i < 9997; ++i) + { + ++em; + } + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); + ++em; + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); + + // minimum initial value, then modify/clamp + em.set_value(INT16_MIN); + EXPECT_EQ("-32768", em.get_string()); + EXPECT_EQ("-32768", em.get_string(true)); + EXPECT_EQ(-32768, em.get_value()); + --em; + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + em.set_value(INT16_MIN); + ++em; + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + + // maximum initial value, then modify/clamp + em.set_value(INT16_MAX); + EXPECT_EQ("32767", em.get_string()); + EXPECT_EQ("32767", em.get_string(true)); + EXPECT_EQ(32767, em.get_value()); + ++em; + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); + em.set_value(INT16_MAX); + --em; + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); +} + +TEST(EntryModelTest, SetMinMax) +{ + EntryModel em; + + // + // signed base 10, set_min() + // + em.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + em.set_min(); + EXPECT_EQ("-999", em.get_string()); + EXPECT_EQ("-999", em.get_string(true)); + EXPECT_EQ(-999, em.get_value()); + + // set_max() + em.set_max(); + EXPECT_EQ("9999", em.get_string()); + EXPECT_EQ("9999", em.get_string(true)); + EXPECT_EQ(9999, em.get_value()); +} + +TEST(EntryModelBoundedTest, SetMinMax) +{ + EntryModelBounded em; + + // intial value + em.init(4, 10, 9, -100, 100, 11); + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(9, em.get_value()); + EXPECT_EQ("9", em.get_string()); + EXPECT_EQ(" 9", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // set default + em.set_default(); + EXPECT_EQ(11, em.get_value()); + EXPECT_EQ("11", em.get_string()); + EXPECT_EQ(" 11", em.get_string(true)); + EXPECT_EQ(0U, em.size()); + EXPECT_TRUE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + // boundary checks + em.push_back(1); + em.push_back(2); + em.push_back(3); + em.push_back(4); + EXPECT_EQ(100, em.get_value()); + EXPECT_EQ("100", em.get_string()); + EXPECT_EQ(" 100", em.get_string(true)); + EXPECT_EQ(3U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); + + em.init(4, 10, 9, -100, 1003, 11); + EXPECT_EQ(4U, em.max_size()); + em.push_back(1); + em.push_back(2); + em.push_back(3); + em.push_back(4); + EXPECT_EQ(1003, em.get_value()); + EXPECT_EQ("1003", em.get_string()); + EXPECT_EQ("1003", em.get_string(true)); + EXPECT_EQ(4U, em.size()); + EXPECT_FALSE(em.is_at_initial_value()); + EXPECT_FALSE(em.empty()); +} diff --git a/src/utils/EntryModel.hxx b/src/utils/EntryModel.hxx index 6a95495c4..1c873a937 100644 --- a/src/utils/EntryModel.hxx +++ b/src/utils/EntryModel.hxx @@ -41,389 +41,471 @@ #include "utils/format_utils.hxx" -/** Implementation of a text entry menu. - * @tparam T the data type up to 64-bits in size - * @tparam N the size of the entry in max number of visible digits. - */ -template +/// Implementation of a text entry menu. +/// @tparam T the data type up to 64-bits in size, signed or unsigned +/// +template class EntryModel { public: - /** Constructor. - * @param transform force characters to be upper case - * @param clamp_callback callback method to clamp min/max - */ - EntryModel(bool transform = false, - std::function clamp_callback = nullptr) - : clampCallback_(clamp_callback) - , digits_(0) - , index_(0) - , hasInitial_(false) - , transform_(transform) + /// Constructor. + EntryModel() + : value_(0) + , valueMin_(std::numeric_limits::lowest()) + , valueMax_(std::numeric_limits::max()) + , numLeadingZeros_(0) + , maxSize_(0) + , size_(0) + , isAtInitialValue_(false) + , empty_(true) , base_(10) { - clear(); - data_[N] = '\0'; } - /** Initialize empty. - * @param digits max number of significant digits in the base type - * @param base base type, 10 or 16 - */ - void init(unsigned digits, int base) + ///Clear the entry string. + void clear() { - HASSERT(digits <= N); - digits_ = digits; - clear(); - hasInitial_ = true; + value_ = 0; + numLeadingZeros_ = 0; + size_ = 0; + empty_ = true; + isAtInitialValue_ = false; } - /** Initialize with a value. - * @param digits max number of significant digits in the base type - * @param base base type, 10 or 16 - * @param value value to initialize with - */ - void init(unsigned digits, int base, T value) + /// Initialize empty. + /// @param max_size max number of digits in the base type + /// @param base base type, 10 or 16 + void init(unsigned max_size, int base) { - HASSERT(digits <= N); - digits_ = digits; + maxSize_ = max_size; clear(); + set_base(base); + set_boundaries(); + } - string str; - switch (base) + /// Initialize with a value. + /// @param max_size max number of digits in the base type + /// @param base base type, 10 or 16 + /// @param value value to initialize with + void init(unsigned max_size, int base, T value) + { + init(max_size, base); + value_ = value; + isAtInitialValue_ = true; + empty_ = false; + } + + /// Append a value to the "back". + /// @param val value to append, base 10: 0 - 9, base 16: 0x0 - 0xF + void push_back(uint8_t val) + { + HASSERT(val < base_); + if (size_ >= maxSize_) { - default: - HASSERT(0); - case 10: - if (std::is_signed::value) - { - str = int64_to_string(value, digits); - } - else - { - str = uint64_to_string(value, digits); - } - break; - case 16: - if (std::is_signed::value) - { - str = int64_to_string_hex(value, digits); - } - else - { - str = uint64_to_string_hex(value, digits); - } - if (transform_) - { - /* equires all characters in upper case */ - transform(str.begin(), str.end(), str.begin(), toupper); - } - break; + // clear entry, return without appending the character + clear(); + return; } - str_populate(data_, str.c_str()); - hasInitial_ = true; + if (isAtInitialValue_) + { + // clear entry before inserting character + clear(); + } + value_ *= base_; + if (value_ < 0) + { + value_ -= val; + } + else + { + value_ += val; + } + if (value_ == 0 && !empty_) + { + if (numLeadingZeros_ < 31) + { + ++numLeadingZeros_; + } + } + empty_ = false; + ++size_; + clamp(); } - /** Clear the entry string. - * @param data data to fill in the buffer with - */ - void clear(const char *data = nullptr) + /// Append a character to the "back". + /// @param c character to append, base 10: 0 - 9, base 16: 0 - F + void push_back_char(char c) { - memset(data_, ' ', digits_); - if (data) + switch (base_) { - memcpy(data_, data, strlen(data)); + case 10: + HASSERT(c >= '0' && c <= '9'); + push_back(c - '0'); + break; + case 16: + c = toupper(c); + HASSERT((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'F')); + push_back(c <= '9' ? c - '0' : c - 'A' + 10); + break; } - data_[digits_] = '\0'; - index_ = 0; } - /** Get the current index. - * @return current cursor index - */ - unsigned cursor_index() + /// Append a value to the "back". + /// @param val value to append, base 10: 0 - 9, base 16: 0x0 - 0xF + /// @return *this + EntryModel &append(uint8_t val) { - return index_; + push_back(val); + return *this; } - /** Test if cursor is visible. - * @return true if cursor is visiable, else false - */ - bool cursor_visible() + /// Append a character to the "back". + /// @param c character to append, base 10: 0 - 9, base 16: 0 - F + /// @return *this + EntryModel &append_char(char c) { - return index_ < digits_; + push_back_char(c); + return *this; } - /** Put a character at the current index, and increment the index by 1. - * @param c Character to place - * @return true if the string was cleared out and an LCD refresh is - * may be required. - */ - bool putc_inc(char c) + /// Removes (deletes) a character off the end. + void pop_back() { - bool refresh = false; - if (index_ >= digits_) + if (value_ == 0 && numLeadingZeros_) { - refresh = true; - clear(); + --numLeadingZeros_; } else { - if (hasInitial_) - { - hasInitial_ = false; - refresh = true; - clear(); - } - data_[index_++] = transform_ ? toupper(c) : c; + value_ /= base_; } - clamp(); - return refresh; - } - - /** Delete a character off the end. - */ - void backspace() - { - if (index_ == 0) + if (size_) + { + --size_; + } + if (isAtInitialValue_) + { + isAtInitialValue_ = false; + clamp(); + // need to compute the size now that the initial value is false + calculate_size(); + } + if (size_ == 0) { - index_ = parsed().size(); + // no more characters left, so the entry is "empty" + empty_ = true; } - data_[--index_] = ' '; - hasInitial_ = false; clamp(); } - /** Set the radix base. - * @param base new radix base to set. - */ + /// Set the radix base. + /// @param base new radix base to set. void set_base(int base) { HASSERT(base == 10 || base == 16); base_ = base; } - /** Set the value, keep the digits and base the same. - * @param value value to initialize with - */ + /// Set the value, keep the max number of digits and base the same. + /// @param value value to initialize with void set_value(T value) { - init(digits_, base_, value); + init(maxSize_, base_, value); } - /** Get the entry as an unsigned integer value. - * @param start_index starting index in string to start conversion - * @return value representation of the string - */ - T get_value(unsigned start_index = 0) + /// Get the size (actual number of digits). Note, if the entry is still + /// at its initial value, the result will be 0. + /// @return size actual size in number of digits + size_t size() { - HASSERT(start_index < digits_); - if (std::is_signed::value) - { - return strtoll(data_ + start_index, NULL, base_); - } - else - { - return strtoull(data_ + start_index, NULL, base_); - } + return size_; } - /** Get the C style string representing the menu entry. - * @return the string data representing the menu entry - */ - const char *c_str() + /// Get the max size (in digits). + /// @return max size in number of digits + size_t max_size() { - return data_; + return maxSize_; } - /** Get a copy of the string without any whitespace. - * @param strip_leading true to also strip off leading '0' or ' ' - * @return the string data representing the menu entry - */ - string parsed(bool strip_leading = false) + /// Test if the entry is "empty". Having an initial value is not empty. + /// @return true if empty, else false + bool empty() { - const char *parse = data_; - string result; - result.reserve(N); - if (strip_leading) + return (!isAtInitialValue_ && empty_); + } + + /// Test if cursor is visible. + /// @return true if cursor is visible, else false + bool cursor_visible() + { + return size_ < maxSize_; + } + + /// Determine if this object is holding an initial or modified value. + /// @return true if if holding an initial value, else false if modified + bool is_at_initial_value() + { + return isAtInitialValue_; + } + + /// It is not always possible with get_string() to return the leading + /// zeros. Furthermore, get_value() does not tell the caller if there are + /// leading zeros. Therefore, this API provides a definitive answer. + bool has_leading_zeros() + { + return numLeadingZeros_ > 0; + } + + /// Get the entry as an unsigned integer value. Note, that '0' is returned + /// both when the actual value is '0' and when the entry is "empty". If the + /// caller needs to distinguish between these two states, check for + /// "empty()". + /// @param force_clamp Normally, clamping doesn't occur if the entry is + /// "empty". However, if force is set to true, we will + /// clamp anyways. This may be valuable when wanting an + /// "empty" entry to return a valid value and '0' is out + /// of bounds. + /// @return value representation of the entry + T get_value(bool force_clamp = false) + { + if (force_clamp) { - while (*parse == '0' || *parse == ' ') + clamp(true); + } + return value_; + } + + /// Get the value as a string. The number of characters will not be trimmed + /// to maxSize_. If trimming is required, it must be done by the caller. + /// @param right_justify true to right justify. + string get_string(bool right_justify = false) + { + string str; + if (isAtInitialValue_ || !empty_) + { + switch (base_) { - ++parse; + default: + // should never get here. + break; + case 10: + if (std::is_signed::value) + { + str = int64_to_string(value_); + } + else + { + str = uint64_to_string(value_); + } + break; + case 16: + if (std::is_signed::value) + { + str = int64_to_string_hex(value_); + } + else + { + str = uint64_to_string_hex(value_); + } + // requires all characters in upper case + transform(str.begin(), str.end(), str.begin(), toupper); + break; } } - while (*parse != ' ' && *parse != '\0') + // Assert that we do not have more leading zeros than space allows. + // The logic of push_back should never allow it. + HASSERT(numLeadingZeros_ == 0 || + (str.size() + numLeadingZeros_) <= maxSize_); + + if (numLeadingZeros_) + { + str.insert(0, numLeadingZeros_, '0'); + } + if (right_justify && str.size() < maxSize_) { - result.push_back(*parse++); + str.insert(0, maxSize_ - str.size(), ' '); } - return result; + return str; + } + + /// Set the value to the minimum. + void set_min() + { + set_value(valueMin_); } - /** Copy the entry data into the middle of a buffer. - * @param buf pointer to destination buffer - * @param start_index starting index of buffer for the copy destination - * @param digits number of digits to copy - */ - void copy_to_buffer(char *buf, int start_index, int digits) + /// Set the value to the maximum. + void set_max() { - HASSERT(digits <= digits_); - memcpy(buf + start_index, data_, digits); + set_value(valueMax_); } - /** Change the sign of the data. - */ + /// Change the sign of the data. void change_sign() { - HASSERT(std::is_signed::value); - if (hasInitial_) + if (value_ < 0) { - set_value(-get_value()); + --size_; } - else if (data_[0] == '-') + value_ = -value_; + if (value_ < 0) { - memmove(data_, data_ + 1, digits_ - 1); - data_[digits_] = ' '; + ++size_; } - else + clamp(); + } + + /// Clamp the value at the min or max. + /// @param force Normally, clamping doesn't occur if the entry is "empty". + /// However, if force is set to true, we will clamp anyways. + virtual void clamp(bool force = false) + { + if (force || !empty_) + { + // purposely do not reset the numLeadingZeros_ + empty_ = false; + if (value_ < valueMin_) + { + value_ = valueMin_; + calculate_size(); + } + else if (value_ > valueMax_) + { + value_ = valueMax_; + calculate_size(); + } + } + } + + /// Pre-increment value. While this method does prevent wrap around of + /// the native type limits, it is incumbent on the caller to limit the + /// resulting number of digits. + T operator ++() + { + isAtInitialValue_ = false; + if (value_ < std::numeric_limits::max()) { - memmove(data_ + 1, data_, digits_ - 1); - data_[0] = '-'; + ++value_; } clamp(); + return value_; } - /// Get the number of significant digits - /// @return number of significant digits - unsigned digits() + /// Pre-decrement value. While this method does prevent wrap around of + /// the native type limits, it is incumbent on the caller to limit the + /// resulting number of digits. + T operator --() { - return digits_; + isAtInitialValue_ = false; + if (value_ > std::numeric_limits::lowest()) + { + --value_; + } + clamp(); + return value_; } - /// Determine if this object is holding an initial or modified value. - /// @return true if holding an initial value, else false if modified - bool has_initial() +protected: + /// Set min and max boundaries supported based on maxSize_ (digit count). + virtual void set_boundaries() { - return hasInitial_; + valueMax_ = 0; + for (unsigned i = 0; i < maxSize_; ++i) + { + valueMax_ *= base_; + valueMax_ += base_ - 1; + } + valueMin_ = std::is_signed::value ? valueMax_ / -base_ : 0; } - /// Clamp the value at the min or max. - void clamp() + /// Calculate the size in digits + void calculate_size() { - if (clampCallback_) + // calculate new size_ + size_ = value_ < 0 ? numLeadingZeros_ + 1 : numLeadingZeros_; + for (T tmp = value_ < 0 ? -value_ : value_; tmp != 0; tmp /= base_) { - clampCallback_(); + ++size_; } } -private: - std::function clampCallback_; /**< callback to clamp value */ - unsigned digits_ : 5; /**< number of significant digits */ - unsigned index_ : 5; /**< present write index */ - unsigned hasInitial_ : 1; /**< has an initial value */ - unsigned transform_ : 1; /**< force characters to be upper case */ - unsigned reserved_ : 20; /**< reserved bit space */ + T value_; ///< present value held + T valueMin_; ///< minimum value representable by maxSize_ + T valueMax_; ///< maximum value representable by maxSize_ - int base_; /**< radix base */ - char data_[N + 1]; /**< data string */ + unsigned numLeadingZeros_ : 5; ///< number of leading zeros + unsigned maxSize_ : 5; ///< maximum number of digits + unsigned size_ : 5; ///< actual number of digits + unsigned isAtInitialValue_ : 1; ///< true if still has the initial value + unsigned empty_ : 1; ///< true if the value_ is "empty" + unsigned base_ : 6; ///< radix base DISALLOW_COPY_AND_ASSIGN(EntryModel); }; -/** Specialization of EntryModel with upper and lower bounds - * @tparam T the data type up to 64-bits in size - * @tparam N the size of the entry in max number of visible digits. - */ -template class EntryModelBounded : public EntryModel +/// Specialization of EntryModel with upper and lower bounds +/// @tparam T the data type up to 64-bits in size +/// @tparam N the size of the entry in max number of visible digits. +template class EntryModelBounded : public EntryModel { public: - /** Constructor. - * @param transform force characters to be upper case - */ - EntryModelBounded(bool transform = false) - : EntryModel(transform, - std::bind(&EntryModelBounded::clamp, this)) - { - } - - /** Initialize with a value. - * @param digits max number of significant digits in the base type - * @param base base type, 10 or 16 - * @param value unsigned value to initialize with - * @param min minumum value - * @param max maximum value - * @param default_val default value - */ - void init(unsigned digits, int base, T value, T min, T max, T default_val) - { - min_ = min; - max_ = max; - default_ = default_val; - EntryModel::init(digits, base, value); - } - - /// Set the value to the minimum. - void set_min() + /// Constructor. + EntryModelBounded() + : EntryModel() { - EntryModel::set_value(min_); } - /// Set the value to the maximum. - void set_max() + /// Initialize with a value. + /// @param max_size max number of digits in the base type + /// @param base base type, 10 or 16 + /// @param value value to initialize with + /// @param min minumum value + /// @param max maximum value + /// @param default_val default value + void init(unsigned max_size, int base, T value, T min, T max, T default_val) { - EntryModel::set_value(max_); + // purposely do not boundary check the min, max, and default values + EntryModel::init(max_size, base, value); + // override type min/max values + EntryModel::valueMin_ = min; + EntryModel::valueMax_ = max; + valueDefault_ = default_val; } /// Set the value to the default. void set_default() { - EntryModel::set_value(default_); + EntryModel::set_value(valueDefault_); } - /// Pre-increment value. - T operator ++() +private: + /// Clamp the value at the min or max. + /// @param force Normally, clamping doesn't occur if the entry is "empty". + /// However, if force is set to true, we will clamp anyways. + void clamp(bool force = false) override { - T value = EntryModel::get_value(); - if (value < max_) + if (force && EntryModel::empty_) { - ++value; - EntryModel::set_value(value); + set_default(); } - return value; - } - - /// Pre-decrement value. - T operator --() - { - T value = EntryModel::get_value(); - if (value > min_) + else { - --value; - EntryModel::set_value(value); + EntryModel::clamp(force); } - return value; } -private: - /// Clamp the value at the min or max. - void clamp() + /// Override base class to do nothing. The boundaries come from + /// EntryModelBounded::init(). + void set_boundaries() override { - volatile T value = EntryModel::get_value(); - if (value < min_) - { - EntryModel::set_value(min_); - } - else if (value > max_) - { - EntryModel::set_value(max_); - } } - T min_; ///< minimum value - T max_; ///< maximum value - T default_; ///< default value + T valueDefault_; ///< default value DISALLOW_COPY_AND_ASSIGN(EntryModelBounded); }; -#endif /* _UTILS_ENTRYMODEL_HXX_ */ +#endif // _UTILS_ENTRYMODEL_HXX_ From 1a1b9f8014f08b96e331b326892e1737bdcbf680 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sat, 9 Oct 2021 09:39:16 -0500 Subject: [PATCH 12/62] Entry Model Tweaks (#579) * Reset boundaries whenever the base is changed. * Add support for converting the value to the new base. * Don't clamp if the value is 0 and there is space for more leading zeros. * Temove redundant boundary check. * Handle the case of going from leading zeros to non-zero push_back(). * handle leading zeros correctly for pop_back(). * Adjust for leading zeros when recalculating size. * Properly handle leading zeros for negative numbers. * Remove unnecessary assert. * Add tests. * Fix review comments. --- src/utils/EntryModel.cxxtest | 121 ++++++++++++++++++++++++++++++++++- src/utils/EntryModel.hxx | 67 +++++++++++++------ 2 files changed, 169 insertions(+), 19 deletions(-) diff --git a/src/utils/EntryModel.cxxtest b/src/utils/EntryModel.cxxtest index 033853e46..48b5ad03e 100644 --- a/src/utils/EntryModel.cxxtest +++ b/src/utils/EntryModel.cxxtest @@ -166,16 +166,19 @@ TEST(EntryModelTest, InitValuePopBack) // unsigned 0 value with leading zeros uem.init(4, 10, 0); - EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(4U, uem.max_size()); uem.push_back_char('0'); + EXPECT_EQ(0U, uem.get_value()); EXPECT_EQ("0", uem.get_string()); EXPECT_EQ(" 0", uem.get_string(true)); EXPECT_FALSE(uem.has_leading_zeros()); uem.push_back(0); + EXPECT_EQ(0U, uem.get_value()); EXPECT_EQ("00", uem.get_string()); EXPECT_EQ(" 00", uem.get_string(true)); EXPECT_TRUE(uem.has_leading_zeros()); uem.pop_back(); + EXPECT_EQ(0U, uem.get_value()); EXPECT_EQ("0", uem.get_string()); EXPECT_EQ(" 0", uem.get_string(true)); EXPECT_FALSE(uem.has_leading_zeros()); @@ -183,6 +186,75 @@ TEST(EntryModelTest, InitValuePopBack) EXPECT_EQ("", uem.get_string()); EXPECT_EQ(" ", uem.get_string(true)); EXPECT_FALSE(uem.has_leading_zeros()); + EXPECT_TRUE(uem.empty()); + + // unsigned 0 value with leading zeros, non-zero result + uem.init(4, 10, 0); + + EXPECT_EQ(4U, uem.max_size()); + uem.push_back_char('0'); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_FALSE(uem.has_leading_zeros()); + uem.push_back(0); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_EQ("00", uem.get_string()); + EXPECT_EQ(" 00", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + uem.push_back(5); + EXPECT_EQ(5U, uem.get_value()); + EXPECT_EQ("005", uem.get_string()); + EXPECT_EQ(" 005", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + uem.pop_back(); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_EQ("00", uem.get_string()); + EXPECT_EQ(" 00", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + uem.pop_back(); + EXPECT_EQ(0U, uem.get_value()); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_FALSE(uem.has_leading_zeros()); +} + +TEST(EntryModelTest, SetBaseConvert) +{ + EntryModel em; + EntryModel uem; + + // signed + em.init(4, 10, -43); + EXPECT_EQ(-43, em.get_value()); + EXPECT_EQ("-43", em.get_string()); + EXPECT_EQ(" -43", em.get_string(true)); + + em.set_base(16, true); + EXPECT_EQ(-0x43, em.get_value()); + EXPECT_EQ("-43", em.get_string()); + EXPECT_EQ(" -43", em.get_string(true)); + + em.set_base(10, true); + EXPECT_EQ(-43, em.get_value()); + EXPECT_EQ("-43", em.get_string()); + EXPECT_EQ(" -43", em.get_string(true)); + + // unsigned + uem.init(4, 10, 34); + EXPECT_EQ(34U, uem.get_value()); + EXPECT_EQ("34", uem.get_string()); + EXPECT_EQ(" 34", uem.get_string(true)); + + uem.set_base(16, true); + EXPECT_EQ(0x34U, uem.get_value()); + EXPECT_EQ("34", uem.get_string()); + EXPECT_EQ(" 34", uem.get_string(true)); + + uem.set_base(10, true); + EXPECT_EQ(34U, uem.get_value()); + EXPECT_EQ("34", uem.get_string()); + EXPECT_EQ(" 34", uem.get_string(true)); } TEST(EntryModelTest, PushBackAndAppend) @@ -501,3 +573,50 @@ TEST(EntryModelBoundedTest, SetMinMax) EXPECT_FALSE(em.is_at_initial_value()); EXPECT_FALSE(em.empty()); } + +TEST(EntryModelBoundedTest, LeadingZerosNonZeroMin) +{ + EntryModelBounded uem; + + // unsigned 0 value with leading zeros + uem.init(4, 10, 5, 5, 9999, 5); + uem.clear(); + + EXPECT_EQ(4U, uem.max_size()); + uem.push_back_char('0'); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_EQ(1U, uem.size()); + EXPECT_FALSE(uem.has_leading_zeros()); + uem.push_back(0); + EXPECT_EQ("00", uem.get_string()); + EXPECT_EQ(" 00", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + uem.push_back(0); + EXPECT_EQ("000", uem.get_string()); + EXPECT_EQ(" 000", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + uem.push_back(0); + EXPECT_EQ("0005", uem.get_string()); + EXPECT_EQ("0005", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + + uem.pop_back(); + EXPECT_EQ("000", uem.get_string()); + EXPECT_EQ(" 000", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + uem.pop_back(); + EXPECT_EQ("00", uem.get_string()); + EXPECT_EQ(" 00", uem.get_string(true)); + EXPECT_TRUE(uem.has_leading_zeros()); + uem.pop_back(); + EXPECT_EQ("0", uem.get_string()); + EXPECT_EQ(" 0", uem.get_string(true)); + EXPECT_FALSE(uem.empty()); + EXPECT_FALSE(uem.has_leading_zeros()); + uem.pop_back(); + EXPECT_EQ("", uem.get_string()); + EXPECT_EQ(" ", uem.get_string(true)); + EXPECT_TRUE(uem.empty()); + EXPECT_FALSE(uem.has_leading_zeros()); +} diff --git a/src/utils/EntryModel.hxx b/src/utils/EntryModel.hxx index 1c873a937..ece89ca40 100644 --- a/src/utils/EntryModel.hxx +++ b/src/utils/EntryModel.hxx @@ -79,8 +79,7 @@ public: { maxSize_ = max_size; clear(); - set_base(base); - set_boundaries(); + set_base(base); // this will call set_boundaries() } /// Initialize with a value. @@ -111,6 +110,13 @@ public: // clear entry before inserting character clear(); } + if (value_ == 0 && val != 0 && size_) + { + // This is a special case where we transition from a user having + // entered all 0 (as a first digits) and then enters a non-zero + // (as a next digit). + ++numLeadingZeros_; + } value_ *= base_; if (value_ < 0) { @@ -139,13 +145,10 @@ public: switch (base_) { case 10: - HASSERT(c >= '0' && c <= '9'); push_back(c - '0'); break; case 16: c = toupper(c); - HASSERT((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'F')); push_back(c <= '9' ? c - '0' : c - 'A' + 10); break; } @@ -172,14 +175,11 @@ public: /// Removes (deletes) a character off the end. void pop_back() { + value_ /= base_; if (value_ == 0 && numLeadingZeros_) { --numLeadingZeros_; } - else - { - value_ /= base_; - } if (size_) { --size_; @@ -205,6 +205,30 @@ public: { HASSERT(base == 10 || base == 16); base_ = base; + set_boundaries(); + } + + /// Set the radix base. + /// @param base new radix base to set. + /// @param convert convert the current value, as a string, to the new base. + void set_base(int base, bool convert) + { + if (base != base_) + { + if (convert) + { + string str = get_string(); + if (std::is_signed::value) + { + value_ = strtoll(str.c_str(), nullptr, base); + } + else + { + value_ = strtoull(str.c_str(), nullptr, base); + } + } + set_base(base); + } } /// Set the value, keep the max number of digits and base the same. @@ -314,14 +338,10 @@ public: break; } } - // Assert that we do not have more leading zeros than space allows. - // The logic of push_back should never allow it. - HASSERT(numLeadingZeros_ == 0 || - (str.size() + numLeadingZeros_) <= maxSize_); if (numLeadingZeros_) { - str.insert(0, numLeadingZeros_, '0'); + str.insert(value_ < 0 ? 1 : 0, numLeadingZeros_, '0'); } if (right_justify && str.size() < maxSize_) { @@ -357,14 +377,21 @@ public: clamp(); } - /// Clamp the value at the min or max. + /// Clamp the value at the min or max. Clamping will not occur if the value + /// is zero and there is space for more leading zeros. /// @param force Normally, clamping doesn't occur if the entry is "empty". /// However, if force is set to true, we will clamp anyways. + /// force also applies if the value is zero yet there is space + /// for more leading zeros. virtual void clamp(bool force = false) { + if (value_ == 0 && size_ < maxSize_ && !force) + { + // skip clamping if we have space for more leading zeros + return; + } if (force || !empty_) { - // purposely do not reset the numLeadingZeros_ empty_ = false; if (value_ < valueMin_) { @@ -424,11 +451,14 @@ protected: void calculate_size() { // calculate new size_ - size_ = value_ < 0 ? numLeadingZeros_ + 1 : numLeadingZeros_; + size_ = value_ < 0 ? 1 : 0; for (T tmp = value_ < 0 ? -value_ : value_; tmp != 0; tmp /= base_) { ++size_; } + numLeadingZeros_ = std::min(static_cast(numLeadingZeros_), + static_cast(maxSize_ - size_)); + size_ += numLeadingZeros_; } T value_; ///< present value held @@ -481,7 +511,8 @@ public: } private: - /// Clamp the value at the min or max. + /// Clamp the value at the min or max. Clamping will not occur if the value + /// is zero and there is space for more leading zeros. /// @param force Normally, clamping doesn't occur if the entry is "empty". /// However, if force is set to true, we will clamp anyways. void clamp(bool force = false) override From 5b6fcb92b5518bfb49a819eac4738b1ecd0324c9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 9 Oct 2021 21:34:13 +0200 Subject: [PATCH 13/62] Fix the order in which libraries are linked when building tests. (#580) Previously we had the order of {core libs} {sys libs} {app libs} This had a problem when an app lib was relying on something from a sys library; for example an app lib using MDNS that is provided by the syslib avahi-client. Now the order is {app libs} {core libs} {syslibs}. --- etc/core_test.mk | 2 +- etc/prog.mk | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/etc/core_test.mk b/etc/core_test.mk index 415539837..f373c3b5c 100644 --- a/etc/core_test.mk +++ b/etc/core_test.mk @@ -44,7 +44,7 @@ $(LIBDIR)/timestamp: $(BUILDDIRS) $(info test deps $(TESTOBJSEXTRA) $(LIBDIR)/timestamp ) $(TESTBINS): %.test$(EXTENTION) : %.test.o $(TESTOBJSEXTRA) $(LIBDIR)/timestamp lib/timestamp $(TESTLIBDEPS) $(TESTEXTRADEPS) | $(BUILDDIRS) - $(LD) -o $@ $(LDFLAGS) -los $< $(TESTOBJSEXTRA) $(STARTGROUP) $(LINKCORELIBS) $(ENDGROUP) $(SYSLIBRARIES) + $(LD) -o $@ $(LDFLAGS) -los $< $(TESTOBJSEXTRA) $(LIBS) $(STARTGROUP) $(LINKCORELIBS) $(ENDGROUP) $(SYSLIBRARIES) -include $(TESTOBJS:.test.o=.dtest) -include $(TESTOBJSEXTRA:.o=.d) diff --git a/etc/prog.mk b/etc/prog.mk index 325e3e693..280890879 100644 --- a/etc/prog.mk +++ b/etc/prog.mk @@ -307,9 +307,8 @@ SRCDIR=$(abspath ../../) #old code from prog.mk #$(TEST_EXTRA_OBJS) $(OBJEXTRA) $(LDFLAGS) $(LIBS) $(SYSLIBRARIES) #new code in core_test.mk -#$(LDFLAGS) -los $< $(TESTOBJSEXTRA) $(LINKCORELIBS) $(SYSLIBRARIES) +#$(LDFLAGS) -los $< $(TESTOBJSEXTRA) $(LIBS) $(LINKCORELIBS) $(SYSLIBRARIES) #TESTOBJSEXTRA += $(TEST_EXTRA_OBJS) -SYSLIBRARIES += $(LIBS) TESTEXTRADEPS += lib/timestamp include $(OPENMRNPATH)/etc/core_test.mk From 58d0221ddc5f31940355887a3ca0512b55b82793 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 29 Oct 2021 21:03:55 +0200 Subject: [PATCH 14/62] Fix bug in TempFile::rewrite. --- src/os/TempFile.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/os/TempFile.hxx b/src/os/TempFile.hxx index ee3100836..0b9a30ac9 100644 --- a/src/os/TempFile.hxx +++ b/src/os/TempFile.hxx @@ -117,6 +117,7 @@ public: /// the data to write. void rewrite(const string& s) { ::lseek(fd_, 0, SEEK_SET); + ::ftruncate(fd_, 0); write(s); } From 0f9a7436c98ba37f74b76a9b420ed3a52c658173 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 30 Oct 2021 17:55:27 +0200 Subject: [PATCH 15/62] Adds API to set the wifi scan parameters. (#581) --- .../net_cc32xx/CC32xxWiFi.cxx | 32 +++++++++++++++++++ .../net_cc32xx/CC32xxWiFi.hxx | 7 ++++ 2 files changed, 39 insertions(+) diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx index 731571a26..9620019eb 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx @@ -323,6 +323,38 @@ int CC32xxWiFi::wlan_country_code_set(CountryCode cc, bool restart) return 0; } +/* + * CC32xxWiFi::wlan_set_scan_params() + */ +void CC32xxWiFi::wlan_set_scan_params(int mask, int min_rssi) +{ + SlWlanScanParamCommand_t param_config = {0}; + uint16_t option = SL_WLAN_GENERAL_PARAM_OPT_SCAN_PARAMS; + uint16_t param_len = sizeof(param_config); + int ret = sl_WlanGet(SL_WLAN_CFG_GENERAL_PARAM_ID, &option, ¶m_len, + (_u8 *)¶m_config); + SlCheckResult(ret); + bool apply = false; + if (mask >= 0 && param_config.ChannelsMask != (uint32_t)mask) + { + param_config.ChannelsMask = mask; + apply = true; + } + if (min_rssi < 0 && param_config.RssiThreshold != min_rssi) + { + param_config.RssiThreshold = min_rssi; + apply = true; + } + if (!apply) + { + return; + } + ret = sl_WlanSet(SL_WLAN_CFG_GENERAL_PARAM_ID, + SL_WLAN_GENERAL_PARAM_OPT_SCAN_PARAMS, sizeof(param_config), + (_u8 *)¶m_config); + SlCheckResult(ret); +} + /* * CC32xxWiFi::wlan_profile_add() */ diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx index 2ea783681..8f13a1547 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx @@ -330,6 +330,13 @@ public: */ int wlan_country_code_set(CountryCode cc, bool restart = false); + /** Sets the scan parameters. + * @param mask the channel mask (bit 0 = channel1, bit1=channel2). If -1 + * then the channel mask is not changed. + * @param min_rssi the minimal RSSI to return a wifi in the scan. If >= 0 + * then the min_rssi is not changed. (Default min_rssi is -95.) */ + void wlan_set_scan_params(int mask, int min_rssi); + /** Add a saved WLAN profile. * @param ssid WLAN SSID of the profile to save * @param sec_type @ref SecurityType of the profile to be saved From 1135c596fdd0099ae104ec643f0a1be655ceb537 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Wed, 24 Nov 2021 10:47:13 -0600 Subject: [PATCH 16/62] Fix ftruncate build (POSIX version). (#584) --- src/os/TempFile.cxx | 11 +++++++++++ src/os/TempFile.hxx | 6 +----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/os/TempFile.cxx b/src/os/TempFile.cxx index a422f4075..38b6c2835 100644 --- a/src/os/TempFile.cxx +++ b/src/os/TempFile.cxx @@ -66,3 +66,14 @@ TempFile::TempFile(const TempDir& dir, const string& basename) fileName_.c_str(); fd_ = mkstemp((char*)fileName_.c_str()); } + +// +// TempFile::rewrite() +// +void TempFile::rewrite(const string& s) +{ + ::lseek(fd_, 0, SEEK_SET); + ::ftruncate(fd_, 0); + write(s); +} + diff --git a/src/os/TempFile.hxx b/src/os/TempFile.hxx index 0b9a30ac9..bec4b181e 100644 --- a/src/os/TempFile.hxx +++ b/src/os/TempFile.hxx @@ -115,11 +115,7 @@ public: /// writes the given data to the temporary file from offset 0. @param s is /// the data to write. - void rewrite(const string& s) { - ::lseek(fd_, 0, SEEK_SET); - ::ftruncate(fd_, 0); - write(s); - } + void rewrite(const string& s); /// writes the given data to the temporary file. @param s is the data to /// write. From 0ff57a997d7bce6e32386a33d38c3acd7501f638 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 5 Dec 2021 12:28:36 +0100 Subject: [PATCH 17/62] Moves spiffs config to the generic directory. --- src/freertos_drivers/spiffs/{cc32x0sf => }/spiffs_config.h | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/freertos_drivers/spiffs/{cc32x0sf => }/spiffs_config.h (100%) diff --git a/src/freertos_drivers/spiffs/cc32x0sf/spiffs_config.h b/src/freertos_drivers/spiffs/spiffs_config.h similarity index 100% rename from src/freertos_drivers/spiffs/cc32x0sf/spiffs_config.h rename to src/freertos_drivers/spiffs/spiffs_config.h From c65a6b6b3a7ded310d62c2367fc9e0334bc5eb90 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 5 Dec 2021 12:29:55 +0100 Subject: [PATCH 18/62] Re-creates spiffs_configs as symlink. --- .../spiffs/cc32x0sf/spiffs_config.h | 1 + .../spiffs/stm32f0_f3/spiffs_config.h | 381 +----------------- .../spiffs/stm32f7/spiffs_config.h | 381 +----------------- .../spiffs/tm4c129/spiffs_config.h | 2 +- 4 files changed, 4 insertions(+), 761 deletions(-) create mode 120000 src/freertos_drivers/spiffs/cc32x0sf/spiffs_config.h mode change 100644 => 120000 src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h mode change 100644 => 120000 src/freertos_drivers/spiffs/stm32f7/spiffs_config.h diff --git a/src/freertos_drivers/spiffs/cc32x0sf/spiffs_config.h b/src/freertos_drivers/spiffs/cc32x0sf/spiffs_config.h new file mode 120000 index 000000000..96f2b5bad --- /dev/null +++ b/src/freertos_drivers/spiffs/cc32x0sf/spiffs_config.h @@ -0,0 +1 @@ +../spiffs_config.h \ No newline at end of file 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 100644 index 87e0dca60..000000000 --- a/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h +++ /dev/null @@ -1,380 +0,0 @@ -/* - * spiffs_config.h - * - * Created on: Jul 3, 2013 - * Author: petera - */ - -#ifndef SPIFFS_CONFIG_H_ -#define SPIFFS_CONFIG_H_ - -// ----------- 8< ------------ -// Following includes are for the linux test build of spiffs -// These may/should/must be removed/altered/replaced in your target -//#include "params_test.h" -#include -#include -#include -#include -#include -#include -#ifdef _SPIFFS_TEST -#include "testrunner.h" -#endif - -typedef signed int s32_t; -typedef unsigned int u32_t; -typedef signed short s16_t; -typedef unsigned short u16_t; -typedef signed char s8_t; -typedef unsigned char u8_t; - - -// ----------- >8 ------------ - -// compile time switches - -// Set generic spiffs debug output call. -#ifndef SPIFFS_DBG -#define SPIFFS_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif -// Set spiffs debug output call for garbage collecting. -#ifndef SPIFFS_GC_DBG -#define SPIFFS_GC_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif -// Set spiffs debug output call for caching. -#ifndef SPIFFS_CACHE_DBG -#define SPIFFS_CACHE_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif -// Set spiffs debug output call for system consistency checks. -#ifndef SPIFFS_CHECK_DBG -#define SPIFFS_CHECK_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif -// Set spiffs debug output call for all api invocations. -#ifndef SPIFFS_API_DBG -#define SPIFFS_API_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif - - - -// Defines spiffs debug print formatters -// some general signed number -#ifndef _SPIPRIi -#define _SPIPRIi "%d" -#endif -// address -#ifndef _SPIPRIad -#define _SPIPRIad "%08x" -#endif -// block -#ifndef _SPIPRIbl -#define _SPIPRIbl "%04x" -#endif -// page -#ifndef _SPIPRIpg -#define _SPIPRIpg "%04x" -#endif -// span index -#ifndef _SPIPRIsp -#define _SPIPRIsp "%04x" -#endif -// file descriptor -#ifndef _SPIPRIfd -#define _SPIPRIfd "%d" -#endif -// file object id -#ifndef _SPIPRIid -#define _SPIPRIid "%04x" -#endif -// file flags -#ifndef _SPIPRIfl -#define _SPIPRIfl "%02x" -#endif - - -// Enable/disable API functions to determine exact number of bytes -// for filedescriptor and cache buffers. Once decided for a configuration, -// this can be disabled to reduce flash. -#ifndef SPIFFS_BUFFER_HELP -#define SPIFFS_BUFFER_HELP 0 -#endif - -// Enables/disable memory read caching of nucleus file system operations. -// If enabled, memory area must be provided for cache in SPIFFS_mount. -#ifndef SPIFFS_CACHE -#define SPIFFS_CACHE 1 -#endif -#if SPIFFS_CACHE -// Enables memory write caching for file descriptors in hydrogen -#ifndef SPIFFS_CACHE_WR -#define SPIFFS_CACHE_WR 1 -#endif - -// Enable/disable statistics on caching. Debug/test purpose only. -#ifndef SPIFFS_CACHE_STATS -#define SPIFFS_CACHE_STATS 1 -#endif -#endif - -// Always check header of each accessed page to ensure consistent state. -// If enabled it will increase number of reads, will increase flash. -#ifndef SPIFFS_PAGE_CHECK -#define SPIFFS_PAGE_CHECK 1 -#endif - -// Define maximum number of gc runs to perform to reach desired free pages. -#ifndef SPIFFS_GC_MAX_RUNS -#define SPIFFS_GC_MAX_RUNS 5 -#endif - -// Enable/disable statistics on gc. Debug/test purpose only. -#ifndef SPIFFS_GC_STATS -#define SPIFFS_GC_STATS 1 -#endif - -// Garbage collecting examines all pages in a block which and sums up -// to a block score. Deleted pages normally gives positive score and -// used pages normally gives a negative score (as these must be moved). -// To have a fair wear-leveling, the erase age is also included in score, -// whose factor normally is the most positive. -// The larger the score, the more likely it is that the block will -// picked for garbage collection. - -// Garbage collecting heuristics - weight used for deleted pages. -#ifndef SPIFFS_GC_HEUR_W_DELET -#define SPIFFS_GC_HEUR_W_DELET (5) -#endif -// Garbage collecting heuristics - weight used for used pages. -#ifndef SPIFFS_GC_HEUR_W_USED -#define SPIFFS_GC_HEUR_W_USED (-1) -#endif -// Garbage collecting heuristics - weight used for time between -// last erased and erase of this block. -#ifndef SPIFFS_GC_HEUR_W_ERASE_AGE -#define SPIFFS_GC_HEUR_W_ERASE_AGE (50) -#endif - -// Object name maximum length. Note that this length include the -// zero-termination character, meaning maximum string of characters -// can at most be SPIFFS_OBJ_NAME_LEN - 1. -#ifndef SPIFFS_OBJ_NAME_LEN -#define SPIFFS_OBJ_NAME_LEN (32) -#endif - -// Maximum length of the metadata associated with an object. -// Setting to non-zero value enables metadata-related API but also -// changes the on-disk format, so the change is not backward-compatible. -// -// Do note: the meta length must never exceed -// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64) -// -// This is derived from following: -// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) + -// spiffs_object_ix_header fields + at least some LUT entries) -#ifndef SPIFFS_OBJ_META_LEN -#define SPIFFS_OBJ_META_LEN (0) -#endif - -// Size of buffer allocated on stack used when copying data. -// Lower value generates more read/writes. No meaning having it bigger -// than logical page size. -#ifndef SPIFFS_COPY_BUFFER_STACK -#define SPIFFS_COPY_BUFFER_STACK (64) -#endif - -// Enable this to have an identifiable spiffs filesystem. This will look for -// a magic in all sectors to determine if this is a valid spiffs system or -// not on mount point. If not, SPIFFS_format must be called prior to mounting -// again. -#define SPIFFS_USE_MAGIC (1) - -#if SPIFFS_USE_MAGIC -// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is -// enabled, the magic will also be dependent on the length of the filesystem. -// For example, a filesystem configured and formatted for 4 megabytes will not -// be accepted for mounting with a configuration defining the filesystem as 2 -// megabytes. -#define SPIFFS_USE_MAGIC_LENGTH (1) -#endif - -// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level -// These should be defined on a multithreaded system - -struct spiffs_t; -extern void extern_spiffs_lock(struct spiffs_t *fs); -extern void extern_spiffs_unlock(struct spiffs_t *fs); - -// define this to enter a mutex if you're running on a multithreaded system -#define SPIFFS_LOCK(fs) extern_spiffs_lock(fs) - -// define this to exit a mutex if you're running on a multithreaded system -#define SPIFFS_UNLOCK(fs) extern_spiffs_unlock(fs) - -// Enable if only one spiffs instance with constant configuration will exist -// on the target. This will reduce calculations, flash and memory accesses. -// Parts of configuration must be defined below instead of at time of mount. -#ifndef SPIFFS_SINGLETON -#define SPIFFS_SINGLETON 0 -#endif - -#if SPIFFS_SINGLETON -// Instead of giving parameters in config struct, singleton build must -// give parameters in defines below. -#ifndef SPIFFS_CFG_PHYS_SZ -#define SPIFFS_CFG_PHYS_SZ(ignore) (256 * 1024) -#endif -#ifndef SPIFFS_CFG_PHYS_ERASE_SZ -#define SPIFFS_CFG_PHYS_ERASE_SZ(ignore) (1024 * 2) -#endif -#ifndef SPIFFS_CFG_PHYS_ADDR -#define SPIFFS_CFG_PHYS_ADDR(ignore) (512 * 1024) -#endif -#ifndef SPIFFS_CFG_LOG_PAGE_SZ -#define SPIFFS_CFG_LOG_PAGE_SZ(ignore) (128) -#endif -#ifndef SPIFFS_CFG_LOG_BLOCK_SZ -#define SPIFFS_CFG_LOG_BLOCK_SZ(ignore) (8 * 1024) -#endif -#endif - -// Enable this if your target needs aligned data for index tables -#ifndef SPIFFS_ALIGNED_OBJECT_INDEX_TABLES -#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 0 -#endif - -// Enable this if you want the HAL callbacks to be called with the spiffs struct -#ifndef SPIFFS_HAL_CALLBACK_EXTRA -#define SPIFFS_HAL_CALLBACK_EXTRA 1 -#endif - -// Enable this if you want to add an integer offset to all file handles -// (spiffs_file). This is useful if running multiple instances of spiffs on -// same target, in order to recognise to what spiffs instance a file handle -// belongs. -// NB: This adds config field fh_ix_offset in the configuration struct when -// mounting, which must be defined. -#ifndef SPIFFS_FILEHDL_OFFSET -#define SPIFFS_FILEHDL_OFFSET 0 -#endif - -// Enable this to compile a read only version of spiffs. -// This will reduce binary size of spiffs. All code comprising modification -// of the file system will not be compiled. Some config will be ignored. -// HAL functions for erasing and writing to spi-flash may be null. Cache -// can be disabled for even further binary size reduction (and ram savings). -// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL. -// If the file system cannot be mounted due to aborted erase operation and -// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be -// returned. -// Might be useful for e.g. bootloaders and such. -#ifndef SPIFFS_READ_ONLY -#define SPIFFS_READ_ONLY 0 -#endif - -// Enable this to add a temporal file cache using the fd buffer. -// The effects of the cache is that SPIFFS_open will find the file faster in -// certain cases. It will make it a lot easier for spiffs to find files -// opened frequently, reducing number of readings from the spi flash for -// finding those files. -// This will grow each fd by 6 bytes. If your files are opened in patterns -// with a degree of temporal locality, the system is optimized. -// Examples can be letting spiffs serve web content, where one file is the css. -// The css is accessed for each html file that is opened, meaning it is -// accessed almost every second time a file is opened. Another example could be -// a log file that is often opened, written, and closed. -// The size of the cache is number of given file descriptors, as it piggybacks -// on the fd update mechanism. The cache lives in the closed file descriptors. -// When closed, the fd know the whereabouts of the file. Instead of forgetting -// this, the temporal cache will keep handling updates to that file even if the -// fd is closed. If the file is opened again, the location of the file is found -// directly. If all available descriptors become opened, all cache memory is -// lost. -#ifndef SPIFFS_TEMPORAL_FD_CACHE -#define SPIFFS_TEMPORAL_FD_CACHE 1 -#endif - -// Temporal file cache hit score. Each time a file is opened, all cached files -// will lose one point. If the opened file is found in cache, that entry will -// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this -// value for the specific access patterns of the application. However, it must -// be between 1 (no gain for hitting a cached entry often) and 255. -#ifndef SPIFFS_TEMPORAL_CACHE_HIT_SCORE -#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 4 -#endif - -// Enable to be able to map object indices to memory. -// This allows for faster and more deterministic reading if cases of reading -// large files and when changing file offset by seeking around a lot. -// When mapping a file's index, the file system will be scanned for index pages -// and the info will be put in memory provided by user. When reading, the -// memory map can be looked up instead of searching for index pages on the -// medium. This way, user can trade memory against performance. -// Whole, parts of, or future parts not being written yet can be mapped. The -// memory array will be owned by spiffs and updated accordingly during garbage -// collecting or when modifying the indices. The latter is invoked by when the -// file is modified in some way. The index buffer is tied to the file -// descriptor. -#ifndef SPIFFS_IX_MAP -#define SPIFFS_IX_MAP 1 -#endif - -// By default SPIFFS in some cases relies on the property of NOR flash that bits -// cannot be set from 0 to 1 by writing and that controllers will ignore such -// bit changes. This results in fewer reads as SPIFFS can in some cases perform -// blind writes, with all bits set to 1 and only those it needs reset set to 0. -// Most of the chips and controllers allow this behavior, so the default is to -// use this technique. If your controller is one of the rare ones that don't, -// turn this option on and SPIFFS will perform a read-modify-write instead. -#ifndef SPIFFS_NO_BLIND_WRITES -#define SPIFFS_NO_BLIND_WRITES 0 -#endif - -// Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function -// in the api. This function will visualize all filesystem using given printf -// function. -#ifndef SPIFFS_TEST_VISUALISATION -#define SPIFFS_TEST_VISUALISATION 1 -#endif -#if SPIFFS_TEST_VISUALISATION -#ifndef spiffs_printf -#define spiffs_printf(...) printf(__VA_ARGS__) -#endif -// spiffs_printf argument for a free page -#ifndef SPIFFS_TEST_VIS_FREE_STR -#define SPIFFS_TEST_VIS_FREE_STR "_" -#endif -// spiffs_printf argument for a deleted page -#ifndef SPIFFS_TEST_VIS_DELE_STR -#define SPIFFS_TEST_VIS_DELE_STR "/" -#endif -// spiffs_printf argument for an index page for given object id -#ifndef SPIFFS_TEST_VIS_INDX_STR -#define SPIFFS_TEST_VIS_INDX_STR(id) "i" -#endif -// spiffs_printf argument for a data page for given object id -#ifndef SPIFFS_TEST_VIS_DATA_STR -#define SPIFFS_TEST_VIS_DATA_STR(id) "d" -#endif -#endif - -// Types depending on configuration such as the amount of flash bytes -// given to spiffs file system in total (spiffs_file_system_size), -// the logical block size (log_block_size), and the logical page size -// (log_page_size) - -// Block index type. Make sure the size of this type can hold -// the highest number of all blocks - i.e. spiffs_file_system_size / log_block_size -typedef u16_t spiffs_block_ix; -// Page index type. Make sure the size of this type can hold -// the highest page number of all pages - i.e. spiffs_file_system_size / log_page_size -typedef u16_t spiffs_page_ix; -// Object id type - most significant bit is reserved for index flag. Make sure the -// size of this type can hold the highest object id on a full system, -// i.e. 2 + (spiffs_file_system_size / (2*log_page_size))*2 -typedef u16_t spiffs_obj_id; -// Object span index type. Make sure the size of this type can -// hold the largest possible span index on the system - -// i.e. (spiffs_file_system_size / log_page_size) - 1 -typedef u16_t spiffs_span_ix; - -#endif /* SPIFFS_CONFIG_H_ */ diff --git a/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h b/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h new file mode 120000 index 000000000..96f2b5bad --- /dev/null +++ b/src/freertos_drivers/spiffs/stm32f0_f3/spiffs_config.h @@ -0,0 +1 @@ +../spiffs_config.h \ No newline at end of file diff --git a/src/freertos_drivers/spiffs/stm32f7/spiffs_config.h b/src/freertos_drivers/spiffs/stm32f7/spiffs_config.h deleted file mode 100644 index 87e0dca60..000000000 --- a/src/freertos_drivers/spiffs/stm32f7/spiffs_config.h +++ /dev/null @@ -1,380 +0,0 @@ -/* - * spiffs_config.h - * - * Created on: Jul 3, 2013 - * Author: petera - */ - -#ifndef SPIFFS_CONFIG_H_ -#define SPIFFS_CONFIG_H_ - -// ----------- 8< ------------ -// Following includes are for the linux test build of spiffs -// These may/should/must be removed/altered/replaced in your target -//#include "params_test.h" -#include -#include -#include -#include -#include -#include -#ifdef _SPIFFS_TEST -#include "testrunner.h" -#endif - -typedef signed int s32_t; -typedef unsigned int u32_t; -typedef signed short s16_t; -typedef unsigned short u16_t; -typedef signed char s8_t; -typedef unsigned char u8_t; - - -// ----------- >8 ------------ - -// compile time switches - -// Set generic spiffs debug output call. -#ifndef SPIFFS_DBG -#define SPIFFS_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif -// Set spiffs debug output call for garbage collecting. -#ifndef SPIFFS_GC_DBG -#define SPIFFS_GC_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif -// Set spiffs debug output call for caching. -#ifndef SPIFFS_CACHE_DBG -#define SPIFFS_CACHE_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif -// Set spiffs debug output call for system consistency checks. -#ifndef SPIFFS_CHECK_DBG -#define SPIFFS_CHECK_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif -// Set spiffs debug output call for all api invocations. -#ifndef SPIFFS_API_DBG -#define SPIFFS_API_DBG(_f, ...) //printf(_f, ## __VA_ARGS__) -#endif - - - -// Defines spiffs debug print formatters -// some general signed number -#ifndef _SPIPRIi -#define _SPIPRIi "%d" -#endif -// address -#ifndef _SPIPRIad -#define _SPIPRIad "%08x" -#endif -// block -#ifndef _SPIPRIbl -#define _SPIPRIbl "%04x" -#endif -// page -#ifndef _SPIPRIpg -#define _SPIPRIpg "%04x" -#endif -// span index -#ifndef _SPIPRIsp -#define _SPIPRIsp "%04x" -#endif -// file descriptor -#ifndef _SPIPRIfd -#define _SPIPRIfd "%d" -#endif -// file object id -#ifndef _SPIPRIid -#define _SPIPRIid "%04x" -#endif -// file flags -#ifndef _SPIPRIfl -#define _SPIPRIfl "%02x" -#endif - - -// Enable/disable API functions to determine exact number of bytes -// for filedescriptor and cache buffers. Once decided for a configuration, -// this can be disabled to reduce flash. -#ifndef SPIFFS_BUFFER_HELP -#define SPIFFS_BUFFER_HELP 0 -#endif - -// Enables/disable memory read caching of nucleus file system operations. -// If enabled, memory area must be provided for cache in SPIFFS_mount. -#ifndef SPIFFS_CACHE -#define SPIFFS_CACHE 1 -#endif -#if SPIFFS_CACHE -// Enables memory write caching for file descriptors in hydrogen -#ifndef SPIFFS_CACHE_WR -#define SPIFFS_CACHE_WR 1 -#endif - -// Enable/disable statistics on caching. Debug/test purpose only. -#ifndef SPIFFS_CACHE_STATS -#define SPIFFS_CACHE_STATS 1 -#endif -#endif - -// Always check header of each accessed page to ensure consistent state. -// If enabled it will increase number of reads, will increase flash. -#ifndef SPIFFS_PAGE_CHECK -#define SPIFFS_PAGE_CHECK 1 -#endif - -// Define maximum number of gc runs to perform to reach desired free pages. -#ifndef SPIFFS_GC_MAX_RUNS -#define SPIFFS_GC_MAX_RUNS 5 -#endif - -// Enable/disable statistics on gc. Debug/test purpose only. -#ifndef SPIFFS_GC_STATS -#define SPIFFS_GC_STATS 1 -#endif - -// Garbage collecting examines all pages in a block which and sums up -// to a block score. Deleted pages normally gives positive score and -// used pages normally gives a negative score (as these must be moved). -// To have a fair wear-leveling, the erase age is also included in score, -// whose factor normally is the most positive. -// The larger the score, the more likely it is that the block will -// picked for garbage collection. - -// Garbage collecting heuristics - weight used for deleted pages. -#ifndef SPIFFS_GC_HEUR_W_DELET -#define SPIFFS_GC_HEUR_W_DELET (5) -#endif -// Garbage collecting heuristics - weight used for used pages. -#ifndef SPIFFS_GC_HEUR_W_USED -#define SPIFFS_GC_HEUR_W_USED (-1) -#endif -// Garbage collecting heuristics - weight used for time between -// last erased and erase of this block. -#ifndef SPIFFS_GC_HEUR_W_ERASE_AGE -#define SPIFFS_GC_HEUR_W_ERASE_AGE (50) -#endif - -// Object name maximum length. Note that this length include the -// zero-termination character, meaning maximum string of characters -// can at most be SPIFFS_OBJ_NAME_LEN - 1. -#ifndef SPIFFS_OBJ_NAME_LEN -#define SPIFFS_OBJ_NAME_LEN (32) -#endif - -// Maximum length of the metadata associated with an object. -// Setting to non-zero value enables metadata-related API but also -// changes the on-disk format, so the change is not backward-compatible. -// -// Do note: the meta length must never exceed -// logical_page_size - (SPIFFS_OBJ_NAME_LEN + 64) -// -// This is derived from following: -// logical_page_size - (SPIFFS_OBJ_NAME_LEN + sizeof(spiffs_page_header) + -// spiffs_object_ix_header fields + at least some LUT entries) -#ifndef SPIFFS_OBJ_META_LEN -#define SPIFFS_OBJ_META_LEN (0) -#endif - -// Size of buffer allocated on stack used when copying data. -// Lower value generates more read/writes. No meaning having it bigger -// than logical page size. -#ifndef SPIFFS_COPY_BUFFER_STACK -#define SPIFFS_COPY_BUFFER_STACK (64) -#endif - -// Enable this to have an identifiable spiffs filesystem. This will look for -// a magic in all sectors to determine if this is a valid spiffs system or -// not on mount point. If not, SPIFFS_format must be called prior to mounting -// again. -#define SPIFFS_USE_MAGIC (1) - -#if SPIFFS_USE_MAGIC -// Only valid when SPIFFS_USE_MAGIC is enabled. If SPIFFS_USE_MAGIC_LENGTH is -// enabled, the magic will also be dependent on the length of the filesystem. -// For example, a filesystem configured and formatted for 4 megabytes will not -// be accepted for mounting with a configuration defining the filesystem as 2 -// megabytes. -#define SPIFFS_USE_MAGIC_LENGTH (1) -#endif - -// SPIFFS_LOCK and SPIFFS_UNLOCK protects spiffs from reentrancy on api level -// These should be defined on a multithreaded system - -struct spiffs_t; -extern void extern_spiffs_lock(struct spiffs_t *fs); -extern void extern_spiffs_unlock(struct spiffs_t *fs); - -// define this to enter a mutex if you're running on a multithreaded system -#define SPIFFS_LOCK(fs) extern_spiffs_lock(fs) - -// define this to exit a mutex if you're running on a multithreaded system -#define SPIFFS_UNLOCK(fs) extern_spiffs_unlock(fs) - -// Enable if only one spiffs instance with constant configuration will exist -// on the target. This will reduce calculations, flash and memory accesses. -// Parts of configuration must be defined below instead of at time of mount. -#ifndef SPIFFS_SINGLETON -#define SPIFFS_SINGLETON 0 -#endif - -#if SPIFFS_SINGLETON -// Instead of giving parameters in config struct, singleton build must -// give parameters in defines below. -#ifndef SPIFFS_CFG_PHYS_SZ -#define SPIFFS_CFG_PHYS_SZ(ignore) (256 * 1024) -#endif -#ifndef SPIFFS_CFG_PHYS_ERASE_SZ -#define SPIFFS_CFG_PHYS_ERASE_SZ(ignore) (1024 * 2) -#endif -#ifndef SPIFFS_CFG_PHYS_ADDR -#define SPIFFS_CFG_PHYS_ADDR(ignore) (512 * 1024) -#endif -#ifndef SPIFFS_CFG_LOG_PAGE_SZ -#define SPIFFS_CFG_LOG_PAGE_SZ(ignore) (128) -#endif -#ifndef SPIFFS_CFG_LOG_BLOCK_SZ -#define SPIFFS_CFG_LOG_BLOCK_SZ(ignore) (8 * 1024) -#endif -#endif - -// Enable this if your target needs aligned data for index tables -#ifndef SPIFFS_ALIGNED_OBJECT_INDEX_TABLES -#define SPIFFS_ALIGNED_OBJECT_INDEX_TABLES 0 -#endif - -// Enable this if you want the HAL callbacks to be called with the spiffs struct -#ifndef SPIFFS_HAL_CALLBACK_EXTRA -#define SPIFFS_HAL_CALLBACK_EXTRA 1 -#endif - -// Enable this if you want to add an integer offset to all file handles -// (spiffs_file). This is useful if running multiple instances of spiffs on -// same target, in order to recognise to what spiffs instance a file handle -// belongs. -// NB: This adds config field fh_ix_offset in the configuration struct when -// mounting, which must be defined. -#ifndef SPIFFS_FILEHDL_OFFSET -#define SPIFFS_FILEHDL_OFFSET 0 -#endif - -// Enable this to compile a read only version of spiffs. -// This will reduce binary size of spiffs. All code comprising modification -// of the file system will not be compiled. Some config will be ignored. -// HAL functions for erasing and writing to spi-flash may be null. Cache -// can be disabled for even further binary size reduction (and ram savings). -// Functions modifying the fs will return SPIFFS_ERR_RO_NOT_IMPL. -// If the file system cannot be mounted due to aborted erase operation and -// SPIFFS_USE_MAGIC is enabled, SPIFFS_ERR_RO_ABORTED_OPERATION will be -// returned. -// Might be useful for e.g. bootloaders and such. -#ifndef SPIFFS_READ_ONLY -#define SPIFFS_READ_ONLY 0 -#endif - -// Enable this to add a temporal file cache using the fd buffer. -// The effects of the cache is that SPIFFS_open will find the file faster in -// certain cases. It will make it a lot easier for spiffs to find files -// opened frequently, reducing number of readings from the spi flash for -// finding those files. -// This will grow each fd by 6 bytes. If your files are opened in patterns -// with a degree of temporal locality, the system is optimized. -// Examples can be letting spiffs serve web content, where one file is the css. -// The css is accessed for each html file that is opened, meaning it is -// accessed almost every second time a file is opened. Another example could be -// a log file that is often opened, written, and closed. -// The size of the cache is number of given file descriptors, as it piggybacks -// on the fd update mechanism. The cache lives in the closed file descriptors. -// When closed, the fd know the whereabouts of the file. Instead of forgetting -// this, the temporal cache will keep handling updates to that file even if the -// fd is closed. If the file is opened again, the location of the file is found -// directly. If all available descriptors become opened, all cache memory is -// lost. -#ifndef SPIFFS_TEMPORAL_FD_CACHE -#define SPIFFS_TEMPORAL_FD_CACHE 1 -#endif - -// Temporal file cache hit score. Each time a file is opened, all cached files -// will lose one point. If the opened file is found in cache, that entry will -// gain SPIFFS_TEMPORAL_CACHE_HIT_SCORE points. One can experiment with this -// value for the specific access patterns of the application. However, it must -// be between 1 (no gain for hitting a cached entry often) and 255. -#ifndef SPIFFS_TEMPORAL_CACHE_HIT_SCORE -#define SPIFFS_TEMPORAL_CACHE_HIT_SCORE 4 -#endif - -// Enable to be able to map object indices to memory. -// This allows for faster and more deterministic reading if cases of reading -// large files and when changing file offset by seeking around a lot. -// When mapping a file's index, the file system will be scanned for index pages -// and the info will be put in memory provided by user. When reading, the -// memory map can be looked up instead of searching for index pages on the -// medium. This way, user can trade memory against performance. -// Whole, parts of, or future parts not being written yet can be mapped. The -// memory array will be owned by spiffs and updated accordingly during garbage -// collecting or when modifying the indices. The latter is invoked by when the -// file is modified in some way. The index buffer is tied to the file -// descriptor. -#ifndef SPIFFS_IX_MAP -#define SPIFFS_IX_MAP 1 -#endif - -// By default SPIFFS in some cases relies on the property of NOR flash that bits -// cannot be set from 0 to 1 by writing and that controllers will ignore such -// bit changes. This results in fewer reads as SPIFFS can in some cases perform -// blind writes, with all bits set to 1 and only those it needs reset set to 0. -// Most of the chips and controllers allow this behavior, so the default is to -// use this technique. If your controller is one of the rare ones that don't, -// turn this option on and SPIFFS will perform a read-modify-write instead. -#ifndef SPIFFS_NO_BLIND_WRITES -#define SPIFFS_NO_BLIND_WRITES 0 -#endif - -// Set SPIFFS_TEST_VISUALISATION to non-zero to enable SPIFFS_vis function -// in the api. This function will visualize all filesystem using given printf -// function. -#ifndef SPIFFS_TEST_VISUALISATION -#define SPIFFS_TEST_VISUALISATION 1 -#endif -#if SPIFFS_TEST_VISUALISATION -#ifndef spiffs_printf -#define spiffs_printf(...) printf(__VA_ARGS__) -#endif -// spiffs_printf argument for a free page -#ifndef SPIFFS_TEST_VIS_FREE_STR -#define SPIFFS_TEST_VIS_FREE_STR "_" -#endif -// spiffs_printf argument for a deleted page -#ifndef SPIFFS_TEST_VIS_DELE_STR -#define SPIFFS_TEST_VIS_DELE_STR "/" -#endif -// spiffs_printf argument for an index page for given object id -#ifndef SPIFFS_TEST_VIS_INDX_STR -#define SPIFFS_TEST_VIS_INDX_STR(id) "i" -#endif -// spiffs_printf argument for a data page for given object id -#ifndef SPIFFS_TEST_VIS_DATA_STR -#define SPIFFS_TEST_VIS_DATA_STR(id) "d" -#endif -#endif - -// Types depending on configuration such as the amount of flash bytes -// given to spiffs file system in total (spiffs_file_system_size), -// the logical block size (log_block_size), and the logical page size -// (log_page_size) - -// Block index type. Make sure the size of this type can hold -// the highest number of all blocks - i.e. spiffs_file_system_size / log_block_size -typedef u16_t spiffs_block_ix; -// Page index type. Make sure the size of this type can hold -// the highest page number of all pages - i.e. spiffs_file_system_size / log_page_size -typedef u16_t spiffs_page_ix; -// Object id type - most significant bit is reserved for index flag. Make sure the -// size of this type can hold the highest object id on a full system, -// i.e. 2 + (spiffs_file_system_size / (2*log_page_size))*2 -typedef u16_t spiffs_obj_id; -// Object span index type. Make sure the size of this type can -// hold the largest possible span index on the system - -// i.e. (spiffs_file_system_size / log_page_size) - 1 -typedef u16_t spiffs_span_ix; - -#endif /* SPIFFS_CONFIG_H_ */ diff --git a/src/freertos_drivers/spiffs/stm32f7/spiffs_config.h b/src/freertos_drivers/spiffs/stm32f7/spiffs_config.h new file mode 120000 index 000000000..96f2b5bad --- /dev/null +++ b/src/freertos_drivers/spiffs/stm32f7/spiffs_config.h @@ -0,0 +1 @@ +../spiffs_config.h \ No newline at end of file diff --git a/src/freertos_drivers/spiffs/tm4c129/spiffs_config.h b/src/freertos_drivers/spiffs/tm4c129/spiffs_config.h index 05fd7656f..96f2b5bad 120000 --- a/src/freertos_drivers/spiffs/tm4c129/spiffs_config.h +++ b/src/freertos_drivers/spiffs/tm4c129/spiffs_config.h @@ -1 +1 @@ -../cc32x0sf/spiffs_config.h \ No newline at end of file +../spiffs_config.h \ No newline at end of file From e3cf5c7855078ef07a311a241e7b836235b65b34 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Dec 2021 22:22:22 +0100 Subject: [PATCH 19/62] Adds support for SPI connected flash chips. (#585) * Adds support for SPI connected flash chips. - Adds a helper class that implements the common operations. - Adds an instantiation of the SPIFFS driver to use the external flash chip. * Revert log change --- src/freertos_drivers/common/SPIFlash.cxx | 204 ++++++++++++++++++ src/freertos_drivers/common/SPIFlash.hxx | 168 +++++++++++++++ src/freertos_drivers/sources | 5 +- src/freertos_drivers/spiffs/SpiSPIFFS.cxx | 80 +++++++ src/freertos_drivers/spiffs/SpiSPIFFS.hxx | 94 ++++++++ .../spiffs/stm32f0_f3/Stm32SPIFFS.hxx | 2 +- .../freertos_drivers/spiffs_cc32x0sf/sources | 1 - .../freertos_drivers/spiffs_spi/Makefile | 2 + .../freertos_drivers/spiffs_spi/sources | 22 ++ 9 files changed, 575 insertions(+), 3 deletions(-) create mode 100644 src/freertos_drivers/common/SPIFlash.cxx create mode 100644 src/freertos_drivers/common/SPIFlash.hxx create mode 100644 src/freertos_drivers/spiffs/SpiSPIFFS.cxx create mode 100644 src/freertos_drivers/spiffs/SpiSPIFFS.hxx create mode 100644 targets/freertos.armv7m/freertos_drivers/spiffs_spi/Makefile create mode 100644 targets/freertos.armv7m/freertos_drivers/spiffs_spi/sources diff --git a/src/freertos_drivers/common/SPIFlash.cxx b/src/freertos_drivers/common/SPIFlash.cxx new file mode 100644 index 000000000..8d22b9a47 --- /dev/null +++ b/src/freertos_drivers/common/SPIFlash.cxx @@ -0,0 +1,204 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SPIFlash.cxx + * + * Shared implementation for operating spiflash devices. This class is intended + * to be used by other device drivers. + * + * @author Balazs Racz + * @date 4 Dec 2021 + */ + +//#define LOGLEVEL INFO + +#include "freertos_drivers/common/SPIFlash.hxx" + +#include +#include +#include +#include +#include +#include + +#include "os/OS.hxx" +#include "utils/logging.h" + +/// Conditional OSMutexLock which can handle a nullptr as mutex (in which case +/// it does not lock anything). +class LockIfExists +{ +public: + LockIfExists(OSMutex *mu) + : mu_(mu) + { + if (mu_) + { + mu_->lock(); + } + } + + ~LockIfExists() + { + if (mu_) + { + mu_->unlock(); + } + } + +private: + OSMutex *mu_; +}; + +#define LockIfExists(l) int error_omitted_mutex_lock_variable[-1] + +void SPIFlash::init(const char *dev_name) +{ + spiFd_ = ::open(dev_name, O_RDWR); + HASSERT(spiFd_ >= 0); + + uint8_t spi_bpw = 8; + int ret; + ret = ::ioctl(spiFd_, SPI_IOC_WR_MODE, &cfg_->spiMode_); + HASSERT(ret == 0); + ret = ::ioctl(spiFd_, SPI_IOC_WR_BITS_PER_WORD, &spi_bpw); + HASSERT(ret == 0); + ret = ::ioctl(spiFd_, SPI_IOC_WR_MAX_SPEED_HZ, &cfg_->speedHz_); + HASSERT(ret == 0); +} + +void SPIFlash::get_id(char id_out[3]) +{ + LockIfExists l(lock_); + struct spi_ioc_transfer xfer[2] = {0, 0}; + xfer[0].tx_buf = (uintptr_t)&cfg_->idCommand_; + xfer[0].len = 1; + xfer[1].rx_buf = (uintptr_t)id_out; + xfer[1].len = 3; + xfer[1].cs_change = true; + ::ioctl(spiFd_, SPI_IOC_MESSAGE(2), xfer); +} + +void SPIFlash::read(uint32_t addr, void *buf, size_t len) +{ + LockIfExists l(lock_); + struct spi_ioc_transfer xfer[2] = {0, 0}; + uint8_t rdreq[5]; + rdreq[0] = cfg_->readCommand_; + rdreq[1] = (addr >> 16) & 0xff; + rdreq[2] = (addr >> 8) & 0xff; + rdreq[3] = (addr)&0xff; + rdreq[4] = 0; + xfer[0].tx_buf = (uintptr_t)rdreq; + xfer[0].len = 4 + cfg_->readNeedsStuffing_; + xfer[1].rx_buf = (uintptr_t)buf; + xfer[1].len = len; + xfer[1].cs_change = true; + ::ioctl(spiFd_, SPI_IOC_MESSAGE(2), xfer); + + auto db = (const uint8_t *)buf; + LOG(INFO, "read [%x]=%02x%02x%02x%02x, %u bytes success", (unsigned)addr, + db[0], db[1], db[2], db[3], len); +} + +void SPIFlash::write(uint32_t addr, const void *buf, size_t len) +{ + HASSERT((addr & cfg_->pageSizeMask_) == + ((addr + len - 1) & cfg_->pageSizeMask_)); + LockIfExists l(lock_); + struct spi_ioc_transfer xfer[3] = {0, 0, 0}; + uint8_t wreq[4]; + wreq[0] = cfg_->writeCommand_; + wreq[1] = (addr >> 16) & 0xff; + wreq[2] = (addr >> 8) & 0xff; + wreq[3] = addr & 0xff; + xfer[0].tx_buf = (uintptr_t)&cfg_->writeEnableCommand_; + xfer[0].len = 1; + xfer[0].cs_change = true; + xfer[1].tx_buf = (uintptr_t)wreq; + xfer[1].len = 4; + xfer[2].tx_buf = (uintptr_t)buf; + xfer[2].len = len; + xfer[2].cs_change = true; + ::ioctl(spiFd_, SPI_IOC_MESSAGE(3), xfer); + + unsigned waitcount = wait_for_write(); + auto db = (const uint8_t *)buf; + LOG(INFO, "write [%x]=%02x%02x%02x%02x, %u bytes success after %u iter", + (unsigned)addr, db[0], db[1], db[2], db[3], len, waitcount); +} + +unsigned SPIFlash::wait_for_write() +{ + // Now we wait for the write to be complete. + unsigned waitcount = 0; + while (true) + { + struct spi_ioc_transfer sxfer = {0}; + uint8_t streq[2]; + streq[0] = cfg_->statusReadCommand_; + streq[1] = 0xFF; + sxfer.tx_buf = (uintptr_t)streq; + sxfer.rx_buf = (uintptr_t)streq; + sxfer.len = 2; + sxfer.cs_change = true; + ::ioctl(spiFd_, SPI_IOC_MESSAGE(1), &sxfer); + + if ((streq[1] & cfg_->statusWritePendingBit_) == 0) + { + return waitcount; + } + waitcount++; + } +} + +void SPIFlash::erase(uint32_t addr, size_t len) +{ + size_t end = addr + len; + while (addr < end) + { + struct spi_ioc_transfer xfer[2] = {0, 0}; + uint8_t ereq[4]; + ereq[0] = cfg_->eraseCommand_; + ereq[1] = (addr >> 16) & 0xff; + ereq[2] = (addr >> 8) & 0xff; + ereq[3] = (addr)&0xff; + xfer[0].tx_buf = (uintptr_t)&cfg_->writeEnableCommand_; + xfer[0].len = 1; + xfer[0].cs_change = true; + xfer[1].tx_buf = (uintptr_t)ereq; + xfer[1].len = 4; + xfer[1].cs_change = true; + + ::ioctl(spiFd_, SPI_IOC_MESSAGE(2), &xfer); + + unsigned waitcount = wait_for_write(); + LOG(INFO, "erase at %x, success after %u iter", (unsigned)addr, + waitcount); + + addr += cfg_->sectorSize_; + } +} diff --git a/src/freertos_drivers/common/SPIFlash.hxx b/src/freertos_drivers/common/SPIFlash.hxx new file mode 100644 index 000000000..c32d21d95 --- /dev/null +++ b/src/freertos_drivers/common/SPIFlash.hxx @@ -0,0 +1,168 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SPIFlash.hxx + * + * Shared implementation for operating spiflash devices. This class is intended + * to be used by other device drivers. + * + * @author Balazs Racz + * @date 4 Dec 2021 + */ + +#ifndef _FREERTOS_DRIVERS_COMMON_SPIFLASH_HXX_ +#define _FREERTOS_DRIVERS_COMMON_SPIFLASH_HXX_ + +#include +#include +#include + +class OSMutex; +class SPI; + +/// Create a const structure like this to tell the spiflash driver how to talk +/// to your spiflash device. +/// +/// Use it like this: +/// +/// static const SPIFlashConfig cfg = { +/// .speedHz_ = 2000000, +/// .spiMode_ = 3, +/// .writeCommand_ = 0xff, +///}; +struct SPIFlashConfig +{ + /// Use this frequency to talk to SPI. + uint32_t speedHz_ {1000000}; + + /// How many bytes is an erase sector. + uint32_t sectorSize_ {4 * 1024}; + + /// A page program operation might wrap around a page. This will cause + /// bytes to be written to the wrong place. There is a check that prevents + /// this. + /// + /// This variable is the mask on the address bits that define the + /// page. Each write operation must start and finish within the same + /// address & pageSizeMask_. + uint32_t pageSizeMask_ {~(256u - 1)}; + + /// SPI mode to use. + uint8_t spiMode_ {SPI_MODE_0}; + + /// Command to use for get identification bytes. + uint8_t idCommand_ {0x9F}; + /// Command to use for reads. + uint8_t readCommand_ {0x03}; + /// Command sent out before each write/erase command. + uint8_t writeEnableCommand_ {0x06}; + /// Command to use for writes. + uint8_t writeCommand_ {0x02}; + /// Command to use for sector erases. + uint8_t eraseCommand_ {0x20}; + /// Command to use for chip erase. + uint8_t chipEraseCommand_ {0x60}; + + /// Command to use for status register read. + uint8_t statusReadCommand_ {0x05}; + /// Which bit to check in the status register for write complete. (This is + /// a mask, it should have exactly one bit set.) + uint8_t statusWritePendingBit_ {0x01}; + + /// Set this to 1 if the read command needs a dummy byte after the address. + uint8_t readNeedsStuffing_ : 1; +}; + +/// Shared implementation for operating spiflash devices. This class is intended +/// to be used by other device drivers. +class SPIFlash +{ +public: + /// Constructor. + /// @param cfg static configuration for this SPI flash device. + /// @param lock this lock will be taken before performing any operation on + /// the chip. Can be null. + SPIFlash(const SPIFlashConfig *cfg, OSMutex *lock) + : cfg_(cfg) + , lock_(lock) + { + } + + /// @return the configuration. + const SPIFlashConfig &cfg() + { + return *cfg_; + } + + /// Opens the SPI bus. This is typically called in hw_postinit or main. + /// @param dev_name the name of the SPI device. + void init(const char *dev_name); + + /// Performs write to the device. Thiscall is synchronous; does not return + /// until the write is complete. + /// @param addr where to write (0 = beginning of the device). + /// @param buf data to write + /// @param len how many bytes to write + void write(uint32_t addr, const void *buf, size_t len); + + /// Reads data from the device. + /// @param addr where to read from + /// @param buf points to where to put the data read + /// @param len how many bytes to read + void read(uint32_t addr, void *buf, size_t len); + + /// 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). + void erase(uint32_t addr, size_t len); + + /// Erases the entire device. + void chip_erase(); + + /// Fetches the identification bytes form the SPIFlash. + /// @param id_out return parameter, will be filled with the received + /// identification bytes. + void get_id(char id_out[3]); + +private: + /// Waits until write is complete. + /// @return how many iterations the wait took + unsigned wait_for_write(); + + /// Configuration. + const SPIFlashConfig *cfg_; + + /// Lock that protects accesses to the flash chip. + OSMutex *lock_; + + /// File descriptor for the opened SPI bus. + int spiFd_ {-1}; + /// Direct access of the SPI device pointer. + /// @todo maybe we are not actually using this. + SPI *spi_; +}; + +#endif // _FREERTOS_DRIVERS_COMMON_SPIFLASH_HXX_ diff --git a/src/freertos_drivers/sources b/src/freertos_drivers/sources index 781176d2f..68bd1dcd8 100644 --- a/src/freertos_drivers/sources +++ b/src/freertos_drivers/sources @@ -26,7 +26,9 @@ CXXSRCS += Fileio.cxx \ WifiDefs.cxx \ PCA9685PWM.cxx \ SN74HC595GPO.cxx \ - TCAN4550Can.cxx + TCAN4550Can.cxx \ + SPIFlash.cxx \ + ifeq ($(TARGET),freertos.mips4k.pic32mx) @@ -43,6 +45,7 @@ SUBDIRS += mbed_lpc1768 drivers_lpc1768 tivaware lpc_chip_175x_6x \ net_freertos_tcp freertos_tcp ti_grlib \ spiffs_cc32x0sf spiffs_tm4c129 \ spiffs_stm32f303xe spiffs_stm32f767xx \ + spiffs_spi \ #spiffs_tm4c123 \ diff --git a/src/freertos_drivers/spiffs/SpiSPIFFS.cxx b/src/freertos_drivers/spiffs/SpiSPIFFS.cxx new file mode 100644 index 000000000..d320be92e --- /dev/null +++ b/src/freertos_drivers/spiffs/SpiSPIFFS.cxx @@ -0,0 +1,80 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SpiSPIFFS.cxx + * + * Hardware-independent SPIFFS implementation that uses a SPI connected flash + * chip. + * + * @author Balazs Racz + * @date 5 Dec 2021 + */ + +#include "freertos_drivers/spiffs/SpiSPIFFS.hxx" + +#include "freertos_drivers/common/SPIFlash.hxx" +#include "utils/logging.h" + +SpiSPIFFS::SpiSPIFFS(SPIFlash *flash, 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, size_t cache_pages, + std::function post_format_hook) + : SPIFFS(physical_address, size_on_disk, flash->cfg().sectorSize_, + logical_block_size, logical_page_size, max_num_open_descriptors, + cache_pages, post_format_hook) + , flash_(flash) +{ +} + +SpiSPIFFS::~SpiSPIFFS() +{ + unmount(); +} + +int32_t SpiSPIFFS::flash_read(uint32_t addr, uint32_t size, uint8_t *dst) +{ + flash_->read(addr, dst, size); + return 0; +} + +int32_t SpiSPIFFS::flash_write(uint32_t addr, uint32_t size, uint8_t *src) +{ + flash_->write(addr, src, size); + +#if (LOGLEVEL >= VERBOSE) + uint8_t db[4]; + flash_read(addr, 4, db); + LOG(VERBOSE, "check [%x]=%02x%02x%02x%02x", (unsigned)addr, db[0], db[1], + db[2], db[3]); +#endif + return 0; +} + +int32_t SpiSPIFFS::flash_erase(uint32_t addr, uint32_t size) +{ + flash_->erase(addr, size); + return 0; +} diff --git a/src/freertos_drivers/spiffs/SpiSPIFFS.hxx b/src/freertos_drivers/spiffs/SpiSPIFFS.hxx new file mode 100644 index 000000000..9ea8d4c42 --- /dev/null +++ b/src/freertos_drivers/spiffs/SpiSPIFFS.hxx @@ -0,0 +1,94 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SpiSPIFFS.hxx + * + * Hardware-independent SPIFFS implementation that uses a SPI connected flash + * chip. + * + * @author Balazs Racz + * @date 5 Dec 2021 + */ + +#ifndef _FREERTOS_DRIVERS_SPIFFS_SPISPIFFS_HXX_ +#define _FREERTOS_DRIVERS_SPIFFS_SPISPIFFS_HXX_ + +#include "freertos_drivers/spiffs/SPIFFS.hxx" + +class SPIFlash; + +class SpiSPIFFS : public SPIFFS +{ +public: + /// Constructor. + /// @param flash is the spi flash driver. This must be initialized before + /// calling any operation (such as mount) on this file system. This object + /// must stay alive as long as the filesystem is in use (ownership is not + /// transferred). + /// @param physical_address address relative to the beginning of the flash + /// device. + /// @param size_on_disk how long the filesystem is + /// @param logical_block_size see SPIFFS documentation + /// @param logical_page_size see SPIFFS documentation + /// @param max_num_open_descriptors how many fds should we allocate memory + /// for + /// @param cache_pages how many pages SPIFFS should store in RAM + /// @param post_format_hook method to be called after a clean format of the + /// file system. This allows the user to prime a clean or factory reset + /// file system with an initial set files. + SpiSPIFFS(SPIFlash *flash, 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); + + /// Destructor. + ~SpiSPIFFS(); + +private: + /// 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; + + /// Flash access helper. + SPIFlash *flash_; + + DISALLOW_COPY_AND_ASSIGN(SpiSPIFFS); +}; + +#endif // _FREERTOS_DRIVERS_SPIFFS_SPISPIFFS_HXX_ diff --git a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx b/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx index dcd4a07c6..a952f43bb 100644 --- a/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx +++ b/src/freertos_drivers/spiffs/stm32f0_f3/Stm32SPIFFS.hxx @@ -40,7 +40,7 @@ #include "freertos_drivers/spiffs/SPIFFS.hxx" -/// Specialization of Serial SPIFFS driver for CC32xx devices. +/// Specialization of Serial SPIFFS driver for Stm32 F0-F3 devices. class Stm32SPIFFS : public SPIFFS { public: diff --git a/targets/freertos.armv7m/freertos_drivers/spiffs_cc32x0sf/sources b/targets/freertos.armv7m/freertos_drivers/spiffs_cc32x0sf/sources index 96f098066..d8780f7fd 100644 --- a/targets/freertos.armv7m/freertos_drivers/spiffs_cc32x0sf/sources +++ b/targets/freertos.armv7m/freertos_drivers/spiffs_cc32x0sf/sources @@ -10,7 +10,6 @@ CSRCS += spiffs_cache.c \ spiffs_gc.c \ spiffs_hydrogen.c \ spiffs_nucleus.c \ - spiffs_nucleus.c \ CXXSRCS += SPIFFS.cxx \ CC32x0SFSPIFFS.cxx diff --git a/targets/freertos.armv7m/freertos_drivers/spiffs_spi/Makefile b/targets/freertos.armv7m/freertos_drivers/spiffs_spi/Makefile new file mode 100644 index 000000000..72e52af15 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/spiffs_spi/Makefile @@ -0,0 +1,2 @@ +OPENMRNPATH ?= $(realpath ../../../..) +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/freertos.armv7m/freertos_drivers/spiffs_spi/sources b/targets/freertos.armv7m/freertos_drivers/spiffs_spi/sources new file mode 100644 index 000000000..d7e194ff5 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/spiffs_spi/sources @@ -0,0 +1,22 @@ +DEPS += SPIFFSPATH + +VPATH := $(SPIFFSPATH)/src: \ + $(OPENMRNPATH)/src/freertos_drivers/spiffs \ + +CSRCS += spiffs_cache.c \ + spiffs_check.c \ + spiffs_gc.c \ + spiffs_hydrogen.c \ + spiffs_nucleus.c \ + +CXXSRCS += SPIFFS.cxx \ + SpiSPIFFS.cxx + + +INCLUDES += -I$(SPIFFSPATH)/src \ + -I$(OPENMRNPATH)/src/freertos_drivers/spiffs \ + +CFLAGS += -DNO_TEST + +CXXFLAGS += + From 7a900399ef8abe1e2ff527142dec7c4fd5a9f4b5 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Dec 2021 22:27:55 +0100 Subject: [PATCH 20/62] Fixes emscripten build. --- src/os/sleep.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/os/sleep.h b/src/os/sleep.h index bd8c883c9..42d796f90 100644 --- a/src/os/sleep.h +++ b/src/os/sleep.h @@ -37,12 +37,13 @@ #include +#ifndef EMSCRIPTEN /// Sleep a given number of microseconds. The granularity of sleep depends on /// the operating system, for FreeRTOS sleeping less than 1 msec is not /// possible with this function. /// @param microseconds how long to sleep. static void microsleep(uint32_t microseconds) __attribute__((weakref("usleep"))); - +#endif /// Executes a busy loop for a given amount of time. It is recommended to use /// this only for small number of microseconds (e.g. <100 usec). From de1ca52735568edf322436f8fcb9da22a5dd9775 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Dec 2021 22:40:43 +0100 Subject: [PATCH 21/62] Fix console test. --- src/console/Console.cxxtest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/console/Console.cxxtest b/src/console/Console.cxxtest index 5e7c96ba6..746f1c750 100644 --- a/src/console/Console.cxxtest +++ b/src/console/Console.cxxtest @@ -22,7 +22,7 @@ TEST(ConsoleTest, testHelp) " has an effect on socket based logins sessions\n" "> ", 154)); - EXPECT_EQ(::write(s, "?\n", 5), 5); + EXPECT_EQ(::write(s, "?\n", 3), 3); usleep(1000); EXPECT_EQ(::read(s, buf, 1024), 154); EXPECT_TRUE(!strncmp(buf, " help | ? : print out this help menu\n" From ab0a282def0f20c271751989dc02d0f668d5ce49 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 14 Dec 2021 21:54:51 +0100 Subject: [PATCH 22/62] Fix broken build under recent gcc. --- src/utils/EntryModel.hxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/EntryModel.hxx b/src/utils/EntryModel.hxx index ece89ca40..237a3e848 100644 --- a/src/utils/EntryModel.hxx +++ b/src/utils/EntryModel.hxx @@ -38,6 +38,7 @@ #include #include #include +#include #include "utils/format_utils.hxx" From c0ae67c1b339afa6870aba7780e60c23d320284e Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 14 Dec 2021 22:00:28 +0100 Subject: [PATCH 23/62] Fork applications/bootloader_client/targets/linux.x86/main.cxx to applications/bootloader_client/main.cxx --- applications/bootloader_client/{targets/linux.x86 => }/main.cxx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/bootloader_client/{targets/linux.x86 => }/main.cxx (100%) diff --git a/applications/bootloader_client/targets/linux.x86/main.cxx b/applications/bootloader_client/main.cxx similarity index 100% rename from applications/bootloader_client/targets/linux.x86/main.cxx rename to applications/bootloader_client/main.cxx From 61202e2f82587c0e82a835a73f31856a180bd1aa Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 14 Dec 2021 22:00:28 +0100 Subject: [PATCH 24/62] Fork applications/bootloader_client/targets/linux.x86/main.cxx to applications/bootloader_client/main.cxx step 2 --- .../targets/linux.x86/{main.cxx => main.cxx.bak} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/bootloader_client/targets/linux.x86/{main.cxx => main.cxx.bak} (100%) diff --git a/applications/bootloader_client/targets/linux.x86/main.cxx b/applications/bootloader_client/targets/linux.x86/main.cxx.bak similarity index 100% rename from applications/bootloader_client/targets/linux.x86/main.cxx rename to applications/bootloader_client/targets/linux.x86/main.cxx.bak From dd38a6578e975fa8ad43ed3a8b1a6b4d19d77d6e Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 14 Dec 2021 22:00:30 +0100 Subject: [PATCH 25/62] Fork applications/bootloader_client/targets/linux.x86/main.cxx to applications/bootloader_client/main.cxx step 4 --- .../targets/linux.x86/{main.cxx.bak => main.cxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/bootloader_client/targets/linux.x86/{main.cxx.bak => main.cxx} (100%) diff --git a/applications/bootloader_client/targets/linux.x86/main.cxx.bak b/applications/bootloader_client/targets/linux.x86/main.cxx similarity index 100% rename from applications/bootloader_client/targets/linux.x86/main.cxx.bak rename to applications/bootloader_client/targets/linux.x86/main.cxx From f9aef24e922454105c8b21eda54c40ba61e53b47 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 14 Dec 2021 23:47:46 +0100 Subject: [PATCH 26/62] Rename main.cxx to main.hxx so that we can include it in real main.cxx. --- applications/bootloader_client/{main.cxx => main.hxx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename applications/bootloader_client/{main.cxx => main.hxx} (100%) diff --git a/applications/bootloader_client/main.cxx b/applications/bootloader_client/main.hxx similarity index 100% rename from applications/bootloader_client/main.cxx rename to applications/bootloader_client/main.hxx From f9926a0337653b96bc099cdde7d0c4fea4e160de Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 14 Dec 2021 23:51:24 +0100 Subject: [PATCH 27/62] Adds bootloader application for STM32-F091 (#587) * Adds F091 bootloader. * Fixes bootloader hardware pinout. Adds bootloader appentry pointer to interrupt vector table. Adds rflash command. * Renames the directory because the pinout is different actually. --- .../BootloaderHal.cxx | 1 + .../BootloaderHal.hxx | 1 + .../Makefile | 64 +++ .../NodeId.cxx | 15 + .../bootloader_startup.c | 1 + .../memory_map.ld | 1 + .../target.ld | 1 + .../st-stm32f091rc-nucleo/BootloaderHal.cxx | 182 +++++++++ boards/st-stm32f091rc-nucleo/Makefile | 6 + boards/st-stm32f091rc-nucleo/memory_map.ld | 9 +- .../BootloaderHal.hxx | 313 +++++++++++++++ .../st-stm32f0x1_x2_x8-generic/bootloader.ld | 178 ++++++++ .../bootloader_startup.c | 379 ++++++++++++++++++ boards/st-stm32f0x1_x2_x8-generic/startup.c | 2 +- 14 files changed, 1151 insertions(+), 2 deletions(-) create mode 120000 applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/BootloaderHal.cxx create mode 120000 applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/BootloaderHal.hxx create mode 100644 applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/Makefile create mode 100644 applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/NodeId.cxx create mode 120000 applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/bootloader_startup.c create mode 120000 applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/memory_map.ld create mode 120000 applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/target.ld create mode 100644 boards/st-stm32f091rc-nucleo/BootloaderHal.cxx create mode 100644 boards/st-stm32f0x1_x2_x8-generic/BootloaderHal.hxx create mode 100644 boards/st-stm32f0x1_x2_x8-generic/bootloader.ld create mode 100644 boards/st-stm32f0x1_x2_x8-generic/bootloader_startup.c diff --git a/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/BootloaderHal.cxx b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/BootloaderHal.cxx new file mode 120000 index 000000000..99b8babee --- /dev/null +++ b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/BootloaderHal.cxx @@ -0,0 +1 @@ +../../../../boards/st-stm32f091rc-nucleo/BootloaderHal.cxx \ No newline at end of file diff --git a/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/BootloaderHal.hxx b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/BootloaderHal.hxx new file mode 120000 index 000000000..9843f233c --- /dev/null +++ b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/BootloaderHal.hxx @@ -0,0 +1 @@ +../../../../boards/st-stm32f0x1_x2_x8-generic/BootloaderHal.hxx \ No newline at end of file diff --git a/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/Makefile b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/Makefile new file mode 100644 index 000000000..23764dd39 --- /dev/null +++ b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/Makefile @@ -0,0 +1,64 @@ +APP_PATH ?= $(realpath ../..) +-include $(APP_PATH)/config.mk +-include local_config.mk + +OPENMRNPATH ?= $(shell \ +sh -c "if [ \"X`printenv OPENMRNPATH`\" != \"X\" ]; then printenv OPENMRNPATH; \ + elif [ -d /opt/openmrn/src ]; then echo /opt/openmrn; \ + elif [ -d ~/openmrn/src ]; then echo ~/openmrn; \ + elif [ -d ../../../src ]; then echo ../../..; \ + else echo OPENMRNPATH not found; fi" \ +) + +-include $(OPENMRNPATH)/etc/config.mk +LINKCORELIBS = -lopenlcb -lutils + +include $(OPENMRNPATH)/etc/stm32cubef0.mk + +CFLAGSEXTRA += -DSTM32F091xC +CXXFLAGSEXTRA += -DSTM32F091xC +SYSLIBRARIES += -lfreertos_drivers_stm32cubef091xc +OPENOCDARGS = -f board/st_nucleo_f0.cfg + +export TARGET := bare.armv6m + +include $(OPENMRNPATH)/etc/prog.mk + +ifndef DEFAULT_ADDRESS +DEFAULT_ADDRESS=0xff +endif + +include $(OPENMRNPATH)/etc/node_id.mk + +SYSLIB_SUBDIRS= +OBJEXTRA= +LDFLAGSEXTRA += -nostartfiles + +all: $(EXECUTABLE).bin + +# How to use: make multibin ADDRESS=0x20 ADDRHIGH=0x45 NUM=3 +# starting address, high bits (user range), count +multibin: + for i in $$(seq 1 $(NUM)) ; do $(MAKE) $(EXECUTABLE).bin ADDRESS=$$(printf 0x%02x $$(($(ADDRESS)+$$i))) ; cp $(EXECUTABLE).bin $(EXECUTABLE).$(MCU_SHORT).$$(printf %02x%02x $(ADDRHIGH) $$(($(ADDRESS)+$$i-1))).bin ; done + +ifeq ($(call find_missing_deps,OPENOCDPATH OPENOCDSCRIPTSPATH),) + +flash: $(EXECUTABLE)$(EXTENTION) $(EXECUTABLE).bin $(EXECUTABLE).lst + @if ps ax -o comm | grep -q openocd ; then echo openocd already running. quit existing first. ; exit 1 ; fi + $(GDB) $< -ex "target remote | $(OPENOCDPATH)/openocd -c \"gdb_port pipe\" --search $(OPENOCDSCRIPTSPATH) $(OPENOCDARGS)" -ex "monitor reset halt" -ex "load" -ex "monitor reset init" -ex "monitor reset run" -ex "detach" -ex "quit" + +lflash: $(EXECUTABLE).bin $(EXECUTABLE).lst + @if ps ax -o comm | grep -q openocd ; then echo openocd already running. quit existing first. ; exit 1 ; fi + $(GDB) $< -ex "target remote | $(OPENOCDPATH)/openocd -c \"gdb_port pipe\" --search $(OPENOCDSCRIPTSPATH) $(OPENOCDARGS)" -ex "monitor reset halt" -ex "monitor program $< 0x08000000 verify reset exit" -ex "monitor reset run" -ex "detach" -ex "quit" + +gdb: + @if ps ax -o comm | grep -q openocd ; then echo openocd already running. quit existing first. ; exit 1 ; fi + $(GDB) $(EXECUTABLE)$(EXTENTION) -ex "target remote | $(OPENOCDPATH)/openocd -c \"gdb_port pipe\" --search $(OPENOCDSCRIPTSPATH) $(OPENOCDARGS)" -ex "continue" # -ex "monitor reset halt" + +else + +flash gdb: + echo OPENOCD not found ; exit 1 + +endif + diff --git a/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/NodeId.cxx b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/NodeId.cxx new file mode 100644 index 000000000..1b0457070 --- /dev/null +++ b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/NodeId.cxx @@ -0,0 +1,15 @@ +#include "openlcb/If.hxx" +#include "address.h" + +#ifndef NODEID_HIGH_BITS +#define NODEID_HIGH_BITS 0x18 +#endif + +extern const openlcb::NodeID NODE_ID; +const openlcb::NodeID NODE_ID = 0x050101010000ULL | (NODEID_HIGH_BITS << 8) | NODEID_LOW_BITS; +extern const uint16_t DEFAULT_ALIAS; +const uint16_t DEFAULT_ALIAS = 0x400 | NODEID_LOW_BITS; + +//#define BOOTLOADER_STREAM +//#define BOOTLOADER_DATAGRAM +//#include "openlcb/Bootloader.hxx" diff --git a/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/bootloader_startup.c b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/bootloader_startup.c new file mode 120000 index 000000000..893503fbe --- /dev/null +++ b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/bootloader_startup.c @@ -0,0 +1 @@ +../../../../boards/st-stm32f0x1_x2_x8-generic/bootloader_startup.c \ No newline at end of file diff --git a/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/memory_map.ld b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/memory_map.ld new file mode 120000 index 000000000..eb8a16a86 --- /dev/null +++ b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32f091rc-nucleo/memory_map.ld \ No newline at end of file diff --git a/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/target.ld b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/target.ld new file mode 120000 index 000000000..127e976d7 --- /dev/null +++ b/applications/bootloader/targets/bare.armv6m.st-stm32f091rc-nucleo-dev-board/target.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32f0x1_x2_x8-generic/bootloader.ld \ No newline at end of file diff --git a/boards/st-stm32f091rc-nucleo/BootloaderHal.cxx b/boards/st-stm32f091rc-nucleo/BootloaderHal.cxx new file mode 100644 index 000000000..86fa2a5d8 --- /dev/null +++ b/boards/st-stm32f091rc-nucleo/BootloaderHal.cxx @@ -0,0 +1,182 @@ +#include + +#define BOOTLOADER_STREAM +//#define BOOTLOADER_DATAGRAM + +#include "BootloaderHal.hxx" +#include "bootloader_hal.h" + +#include "nmranet_config.h" +#include "openlcb/Defs.hxx" +#include "Stm32Gpio.hxx" +#include "openlcb/Bootloader.hxx" +#include "openlcb/If.hxx" +#include "utils/GpioInitializer.hxx" + +const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9}; +const uint8_t APBPrescTable[8] = {0, 0, 0, 0, 1, 2, 3, 4}; +const uint32_t HSEValue = 8000000; + +int g_death_lineno = 0; + +extern "C" { + +GPIO_PIN(LED_GREEN, LedPin, A, 5); +GPIO_PIN(SW1, GpioInputPU, C, 13); + +static constexpr unsigned clock_hz = 48000000; + +void bootloader_hw_set_to_safe(void) +{ + SW1_Pin::hw_set_to_safe(); + LED_GREEN_Pin::hw_set_to_safe(); +} + +extern void bootloader_reset_segments(void); +//extern unsigned long cm3_cpu_clock_hz; + +/** Setup the system clock */ +static void clock_setup(void) +{ + /* reset clock configuration to default state */ + RCC->CR = RCC_CR_HSITRIM_4 | RCC_CR_HSION; + while (!(RCC->CR & RCC_CR_HSIRDY)) + ; + +#define USE_EXTERNAL_8_MHz_CLOCK_SOURCE 1 +/* configure PLL: 8 MHz * 6 = 48 MHz */ +#if USE_EXTERNAL_8_MHz_CLOCK_SOURCE + RCC->CR |= RCC_CR_HSEON | RCC_CR_HSEBYP; + while (!(RCC->CR & RCC_CR_HSERDY)) + ; + RCC->CFGR = RCC_CFGR_PLLMUL6 | RCC_CFGR_PLLSRC_HSE_PREDIV | RCC_CFGR_SW_HSE; + while (!((RCC->CFGR & RCC_CFGR_SWS) == RCC_CFGR_SWS_HSE)) + ; +#else + RCC->CFGR = RCC_CFGR_PLLMUL6 | RCC_CFGR_PLLSRC_HSI_PREDIV | RCC_CFGR_SW_HSI; + while (!((RCC->CFGR & RCC_CFGR_SWS) == RCC_CFGR_SWS_HSI)) + ; +#endif + /* enable PLL */ + RCC->CR |= RCC_CR_PLLON; + while (!(RCC->CR & RCC_CR_PLLRDY)) + ; + + /* set PLL as system clock */ + RCC->CFGR = (RCC->CFGR & (~RCC_CFGR_SW)) | RCC_CFGR_SW_PLL; + while (!((RCC->CFGR & RCC_CFGR_SWS) == RCC_CFGR_SWS_PLL)) + ; +} + +void bootloader_hw_init() +{ + /* Globally disables interrupts until the FreeRTOS scheduler is up. */ + asm("cpsid i\n"); + + /* these FLASH settings enable opertion at 48 MHz */ + __HAL_FLASH_PREFETCH_BUFFER_ENABLE(); + __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_1); + + /* Reset HSI14 bit */ + RCC->CR2 &= (uint32_t)0xFFFFFFFEU; + + /* Disable all interrupts */ + RCC->CIR = 0x00000000U; + + clock_setup(); + + /* enable peripheral clocks */ + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_CAN1_CLK_ENABLE(); + + /* setup pinmux */ + GPIO_InitTypeDef gpio_init; + memset(&gpio_init, 0, sizeof(gpio_init)); + + /* CAN pinmux on PB8 and PB9 */ + gpio_init.Mode = GPIO_MODE_AF_PP; + // Disables pull-ups because this is a 5V tolerant pin. + gpio_init.Pull = GPIO_NOPULL; + gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + gpio_init.Alternate = GPIO_AF4_CAN; + gpio_init.Pin = GPIO_PIN_8; + HAL_GPIO_Init(GPIOB, &gpio_init); + gpio_init.Pin = GPIO_PIN_9; + HAL_GPIO_Init(GPIOB, &gpio_init); + + LED_GREEN_Pin::hw_init(); + SW1_Pin::hw_init(); + + /* disable sleep, enter init mode */ + CAN->MCR = CAN_MCR_INRQ; + + /* Time triggered tranmission off + * Bus off state is left automatically + * Auto-Wakeup mode disabled + * automatic re-transmission enabled + * receive FIFO not locked on overrun + * TX FIFO mode on + */ + CAN->MCR |= (CAN_MCR_ABOM | CAN_MCR_TXFP); + + /* Setup timing. + * 125,000 Kbps = 8 usec/bit + */ + CAN->BTR = (CAN_BS1_5TQ | CAN_BS2_2TQ | CAN_SJW_1TQ | + ((clock_hz / 1000000) - 1)); + + /* enter normal mode */ + CAN->MCR &= ~CAN_MCR_INRQ; + + /* Enter filter initialization mode. Filter 0 will be used as a single + * 32-bit filter, ID Mask Mode, we accept everything, no mask. + */ + CAN->FMR |= CAN_FMR_FINIT; + CAN->FM1R = 0; + CAN->FS1R = 0x000000001; + CAN->FFA1R = 0; + CAN->sFilterRegister[0].FR1 = 0; + CAN->sFilterRegister[0].FR2 = 0; + + /* Activeate filter and exit initialization mode. */ + CAN->FA1R = 0x000000001; + CAN->FMR &= ~CAN_FMR_FINIT; +} + +void bootloader_led(enum BootloaderLed id, bool value) +{ + switch(id) + { + case LED_ACTIVE: + LED_GREEN_Pin::set(value); + return; + case LED_WRITING: + LED_GREEN_Pin::set(value); + return; + case LED_CSUM_ERROR: + return; + case LED_REQUEST: + return; + case LED_FRAME_LOST: + return; + default: + /* ignore */ + break; + } +} + +bool request_bootloader() +{ + extern uint32_t __bootloader_magic_ptr; + if (__bootloader_magic_ptr == REQUEST_BOOTLOADER) { + __bootloader_magic_ptr = 0; + LED_GREEN_Pin::set(true); + return true; + } + LED_GREEN_Pin::set(SW1_Pin::get()); + return !SW1_Pin::get(); +} + +} // extern "C" diff --git a/boards/st-stm32f091rc-nucleo/Makefile b/boards/st-stm32f091rc-nucleo/Makefile index bff14cf9b..51d512bf7 100644 --- a/boards/st-stm32f091rc-nucleo/Makefile +++ b/boards/st-stm32f091rc-nucleo/Makefile @@ -52,3 +52,9 @@ flash gdb: echo OPENOCD not found ; exit 1 endif + +BLOAD_HOST ?= localhost + +rflash: $(EXECUTABLE).bin + $(OPENMRNPATH)/applications/bootloader_client/targets/linux.x86/bootloader_client -w 100 -W 100 -c tiva123 -i $(BLOAD_HOST) -r -n 0x0501010118$$(printf %02x $(ADDRESS)) -f $(EXECUTABLE).bin + cp -f $(EXECUTABLE)$(EXTENTION) $(EXECUTABLE)-$$(printf %02x $(ADDRESS))$(EXTENTION) diff --git a/boards/st-stm32f091rc-nucleo/memory_map.ld b/boards/st-stm32f091rc-nucleo/memory_map.ld index 8e704deeb..6ea720a70 100644 --- a/boards/st-stm32f091rc-nucleo/memory_map.ld +++ b/boards/st-stm32f091rc-nucleo/memory_map.ld @@ -1,9 +1,14 @@ +___ram_for_bootloader_api = 8; + MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 240K EEPROMEMU (r) : ORIGIN = 0x0803C000, LENGTH = 8K BOOTLOADER (rx) : ORIGIN = 0x0803E000, LENGTH = 8K - RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, + LENGTH = 32K - ___ram_for_bootloader_api + BOOTLOADERAPI (rw) : ORIGIN = 0x20000000 + 32K - ___ram_for_bootloader_api, + LENGTH = ___ram_for_bootloader_api } __flash_start = ORIGIN(FLASH); @@ -12,4 +17,6 @@ __eeprom_start = ORIGIN(EEPROMEMU); __eeprom_end = ORIGIN(EEPROMEMU) + LENGTH(EEPROMEMU); __bootloader_start = ORIGIN(BOOTLOADER); __app_header_offset = 0x270; +__app_header_address = ORIGIN(FLASH) + __app_header_offset; __bootloader_magic_ptr = ORIGIN(RAM); +__application_node_id = ORIGIN(BOOTLOADERAPI); diff --git a/boards/st-stm32f0x1_x2_x8-generic/BootloaderHal.hxx b/boards/st-stm32f0x1_x2_x8-generic/BootloaderHal.hxx new file mode 100644 index 000000000..ed687cee3 --- /dev/null +++ b/boards/st-stm32f0x1_x2_x8-generic/BootloaderHal.hxx @@ -0,0 +1,313 @@ +/** \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 BootloaderHal.hxx + * + * STM-specific implementation of the HAL (Hardware Abstraction Layer) used by + * the OpenLCB bootloader. + * + * Usage: You have to define in your makefile a symbol like + * CXXFLAGSEXTRA+= -DSTM32F091xC + * + * @author Balazs Racz + * @date 31 January 2015 + */ + +#ifndef _BOARDS_STM32F0_BOOTLOADERHAL_HXX_ +#define _BOARDS_STM32F0_BOOTLOADERHAL_HXX_ + +#include + +#include "bootloader_hal.h" +#include "stm32f0xx_hal_conf.h" + +#include "nmranet_config.h" +#include "openlcb/Defs.hxx" +#include "utils/Crc.hxx" +#include "Stm32Gpio.hxx" + +extern "C" { + +void get_flash_boundaries(const void **flash_min, const void **flash_max, + const struct app_header **app_header) +{ + extern char __flash_start; + extern char __flash_end; + extern struct app_header __app_header_address; + *flash_min = &__flash_start; + *flash_max = &__flash_end; + *app_header = &__app_header_address; +} + +void checksum_data(const void *data, uint32_t size, uint32_t *checksum) +{ + extern uint8_t __flash_start; + if (static_cast(data) == &__flash_start) + { + // ignores the reset vector for checksum calculations. + data = static_cast(data) + 8; + size -= 8; + } + memset(checksum, 0, 16); + crc3_crc16_ibm(data, size, reinterpret_cast(checksum)); +} + +extern const openlcb::NodeID NODE_ID; + +uint16_t nmranet_alias() +{ + /// TODO: we should probably read this from someplace else + return 0x400 ^ (NODE_ID & 0xFFF); +} + +uint64_t nmranet_nodeid() +{ + /// TODO(balazs.racz): read some form of EEPROM instead. + return NODE_ID; +} + +bool read_can_frame(struct can_frame *can_frame) +{ + if (!(CAN->RF0R & CAN_RF0R_FMP0)) + { + return false; + } + + /* Read a message from CAN and clear the interrupt source */ + if (CAN->sFIFOMailBox[0].RIR & CAN_RI0R_IDE) + { + /* extended frame */ + can_frame->can_id = CAN->sFIFOMailBox[0].RIR >> 3; + can_frame->can_eff = 1; + } + else + { + /* standard frame */ + can_frame->can_id = CAN->sFIFOMailBox[0].RIR >> 21; + can_frame->can_eff = 0; + } + if (CAN->sFIFOMailBox[0].RIR & CAN_RI0R_RTR) + { + /* remote frame */ + can_frame->can_rtr = 1; + can_frame->can_dlc = 0; + } + else + { + /* data frame */ + can_frame->can_rtr = 0; + can_frame->can_dlc = CAN->sFIFOMailBox[0].RDTR & CAN_RDT0R_DLC; + can_frame->data[0] = (CAN->sFIFOMailBox[0].RDLR >> 0) & 0xFF; + can_frame->data[1] = (CAN->sFIFOMailBox[0].RDLR >> 8) & 0xFF; + can_frame->data[2] = (CAN->sFIFOMailBox[0].RDLR >> 16) & 0xFF; + can_frame->data[3] = (CAN->sFIFOMailBox[0].RDLR >> 24) & 0xFF; + can_frame->data[4] = (CAN->sFIFOMailBox[0].RDHR >> 0) & 0xFF; + can_frame->data[5] = (CAN->sFIFOMailBox[0].RDHR >> 8) & 0xFF; + can_frame->data[6] = (CAN->sFIFOMailBox[0].RDHR >> 16) & 0xFF; + can_frame->data[7] = (CAN->sFIFOMailBox[0].RDHR >> 24) & 0xFF; + } + /* release FIFO */; + CAN->RF0R |= CAN_RF0R_RFOM0; + return true; +} + +bool try_send_can_frame(const struct can_frame &can_frame) +{ + /* load the next message to transmit */ + volatile CAN_TxMailBox_TypeDef *mailbox; + if (CAN->TSR & CAN_TSR_TME0) + { + mailbox = CAN->sTxMailBox + 0; + } + else if (CAN->TSR & CAN_TSR_TME1) + { + mailbox = CAN->sTxMailBox + 1; + } + else if (CAN->TSR & CAN_TSR_TME2) + { + mailbox = CAN->sTxMailBox + 2; + } + else + { + // no buffer available + return false; + } + + /* setup frame */ + if (can_frame.can_eff) + { + mailbox->TIR = (can_frame.can_id << 3) | CAN_TI0R_IDE; + } + else + { + mailbox->TIR = can_frame.can_id << 21; + } + if (can_frame.can_rtr) + { + mailbox->TIR |= CAN_TI0R_RTR; + } + else + { + mailbox->TDTR = can_frame.can_dlc; + mailbox->TDLR = (can_frame.data[0] << 0) | + (can_frame.data[1] << 8) | + (can_frame.data[2] << 16) | + (can_frame.data[3] << 24); + mailbox->TDHR = (can_frame.data[4] << 0) | + (can_frame.data[5] << 8) | + (can_frame.data[6] << 16) | + (can_frame.data[7] << 24); + } + + /* request transmission */ + mailbox->TIR |= CAN_TI0R_TXRQ; + + return true; +} + +void bootloader_reboot(void) +{ + /* wait for TX messages to all go out */ + while (!(CAN->TSR & CAN_TSR_TME0)); + while (!(CAN->TSR & CAN_TSR_TME1)); + while (!(CAN->TSR & CAN_TSR_TME2)); + + bootloader_hw_set_to_safe(); + + HAL_NVIC_SystemReset(); +} + +void application_entry(void) +{ + bootloader_hw_set_to_safe(); + /* Globally disables interrupts. */ + asm("cpsid i\n"); + extern uint64_t __application_node_id; + __application_node_id = nmranet_nodeid(); + extern char __flash_start; + // We store the application reset in interrupt vecor 13, which is reserved + // / unused on all Cortex-M0 processors. + asm volatile(" mov r3, %[flash_addr] \n" + : + : [flash_addr] "r"(&__flash_start)); + asm volatile(" ldr r0, [r3]\n" + " mov sp, r0\n" + " ldr r0, [r3, #52]\n" + " bx r0\n"); +} + +void raw_erase_flash_page(const void *address) +{ + bootloader_led(LED_ACTIVE, 0); + bootloader_led(LED_WRITING, 1); + + FLASH_EraseInitTypeDef erase_init; + erase_init.TypeErase = FLASH_TYPEERASE_PAGES; + erase_init.PageAddress = (uint32_t)address; + erase_init.NbPages = 1; + + uint32_t page_error; + HAL_FLASH_Unlock(); + HAL_FLASHEx_Erase(&erase_init, &page_error); + HAL_FLASH_Lock(); + + bootloader_led(LED_WRITING, 0); + bootloader_led(LED_ACTIVE, 1); +} + +void erase_flash_page(const void *address) +{ + raw_erase_flash_page(address); + + extern char __flash_start; + if (static_cast(address) == &__flash_start) { + // If we erased page zero, we ensure to write back the reset pointer + // immiediately or we brick the bootloader. + extern unsigned long *__stack; + extern void reset_handler(void); + uint32_t bootdata[2]; + bootdata[0] = reinterpret_cast(&__stack); + bootdata[1] = reinterpret_cast(&reset_handler); + raw_write_flash(address, bootdata, sizeof(bootdata)); + } +} + +void raw_write_flash(const void *address, const void *data, uint32_t size_bytes) +{ + uint32_t *data_ptr = (uint32_t*)data; + uint32_t addr_ptr = (uintptr_t)address; + bootloader_led(LED_ACTIVE, 0); + bootloader_led(LED_WRITING, 1); + + HAL_FLASH_Unlock(); + while (size_bytes) + { + HAL_FLASH_Program((uint32_t)FLASH_TYPEPROGRAM_WORD, + (uint32_t)addr_ptr, *data_ptr); + size_bytes -= sizeof(uint32_t); + addr_ptr += sizeof(uint32_t); + ++data_ptr; + } + HAL_FLASH_Lock(); + + bootloader_led(LED_WRITING, 0); + bootloader_led(LED_ACTIVE, 1); +} + +void write_flash(const void *address, const void *data, uint32_t size_bytes) +{ + extern char __flash_start; + if (address == &__flash_start) { + address = static_cast(address) + 8; + data = static_cast(data) + 8; + size_bytes -= 8; + } + raw_write_flash(address, (uint32_t*)data, (size_bytes + 3) & ~3); +} + +void get_flash_page_info(const void *address, const void **page_start, + uint32_t *page_length_bytes) +{ + // STM32F091 has 2 KB flash pages. + uint32_t value = (uint32_t)address; + value &= ~(FLASH_PAGE_SIZE - 1); + *page_start = (const void *)value; + *page_length_bytes = FLASH_PAGE_SIZE; +} + +uint16_t flash_complete(void) +{ + return 0; +} + +void ignore_fn(void) +{ +} + +} // extern "C" + + +#endif // _BOARDS_STM32F0_BOOTLOADERHAL_HXX_ diff --git a/boards/st-stm32f0x1_x2_x8-generic/bootloader.ld b/boards/st-stm32f0x1_x2_x8-generic/bootloader.ld new file mode 100644 index 000000000..1683c0842 --- /dev/null +++ b/boards/st-stm32f0x1_x2_x8-generic/bootloader.ld @@ -0,0 +1,178 @@ +OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm", "elf32-littlearm") +ENTRY(reset_handler) +SEARCH_DIR(.) +GROUP(-lgcc -lc) + +/* include device specific memory map */ +INCLUDE memory_map.ld + +__top_RAM = ORIGIN(RAM) + LENGTH(RAM); + +__cs3_heap_end = __top_RAM; + +__start_ram = ORIGIN(RAM); +__end_ram = __top_RAM; + +SECTIONS +{ + /* INTERRUPT VECTORS */ + .interrupt_vector : + { + FILL(0xff) + KEEP(*(.interrupt_vector)) + + /* This will force the app header to be cleared if the bootloader is + flashed on top of an application. */ + . = __app_header_offset; + QUAD(0); /* App header: checksum for vector table. */ + QUAD(0); + LONG(0); /* App header: data size */ + QUAD(0); /* App header: checksum for payload. */ + QUAD(0); + } > FLASH + + + /* MAIN TEXT SECTION */ + .text : ALIGN(4) + { + + /* Global Section Table */ + . = ALIGN(4) ; + __section_table_start = .; + __data_section_table = .; + LONG(LOADADDR(.data)); + LONG( ADDR(.data)) ; + LONG( SIZEOF(.data)); + __data_section_table_end = .; + __bss_section_table = .; + LONG( ADDR(.bss)); + LONG( SIZEOF(.bss)); + __bss_section_table_end = .; + __section_table_end = . ; + /* End of Global Section Table */ + + + *(.after_vectors*) + + *(SORT(.text*)) + *(.rodata) + *(SORT(.rodata.*)) + . = ALIGN(4); + + /* C++ constructors etc */ + . = ALIGN(4); + KEEP(*(.init)) + + . = ALIGN(4); + __preinit_array_start = .; + KEEP (*(.preinit_array)) + __preinit_array_end = .; + + . = ALIGN(4); + __init_array_start = .; + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + __init_array_end = .; + + /* KEEP(*(.fini)); */ + + . = ALIGN(4); + KEEP (*crtbegin.o(.ctors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*crtend.o(.ctors)) + + . = ALIGN(4); + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*crtend.o(.dtors)) + /* End C++ */ + + __text_section_guard = .; + LONG( 0 ); + } > BOOTLOADER + + /* + * for exception handling/unwind - some Newlib functions (in common + * with C++ and STDC++) use this. + */ + .ARM.extab : ALIGN(4) + { + *(.ARM.extab* .gnu.linkonce.armextab.*) + } > BOOTLOADER + __exidx_start = .; + + .ARM.exidx : ALIGN(4) + { + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + } > BOOTLOADER + __exidx_end = .; + + _etext = .; + + + /* MAIN DATA SECTION */ + + .uninit_RESERVED(NOLOAD) : ALIGN(4) + { + __bootloader_magic_ptr_actual = .; + LONG( 0 ); /* The magic pointer is in the uninit part. */ + KEEP(*(.bss.$RESERVED*)) + . = ALIGN(4) ; + _end_uninit_RESERVED = .; + } > RAM + + ASSERT(__bootloader_magic_ptr_actual == __bootloader_magic_ptr, "Bootloader magic ptr is not at the expected location.") + + + /* It seems that in order for LPCXpresso to properly flash the data + section, an alignment of 256 bytes is necessary. Otherwise the separate + flashing of the data section will corrupt the end of the text section. */ + .data : ALIGN(256) + { + FILL(0xff) + _data = .; + *(vtable) + __impure_data_start = .; + *(.data.impure_data) + __impure_data_end = .; + *(.data*) + + /* this magic is needed for the device tables of openMRN */ + . = ALIGN (8); + KEEP(*( SORT (.device.table.*))) ; + . = ALIGN (4); + + _edata = .; + } > RAM AT>BOOTLOADER + + + /* MAIN BSS SECTION */ + .bss : ALIGN(4) + { + _bss = .; + *(.bss*) + *(COMMON) + . = ALIGN(4) ; + _ebss = .; + PROVIDE(end = .); + } > RAM + + + /* DEFAULT NOINIT SECTION */ + .noinit (NOLOAD): ALIGN(4) + { + _noinit = .; + *(.noinit*) + . = ALIGN(4) ; + _end_noinit = .; + } > RAM + + PROVIDE(_pvHeapStart = .); + PROVIDE(__cs3_heap_start = .); + /* This pointer will be written to the SP register at reset. */ + PROVIDE(__stack = __top_RAM); + + PROVIDE(__impure_data_size = __impure_data_end - __impure_data_start); +} diff --git a/boards/st-stm32f0x1_x2_x8-generic/bootloader_startup.c b/boards/st-stm32f0x1_x2_x8-generic/bootloader_startup.c new file mode 100644 index 000000000..eb4160980 --- /dev/null +++ b/boards/st-stm32f0x1_x2_x8-generic/bootloader_startup.c @@ -0,0 +1,379 @@ +/** @copyright + * Copyright (c) 2019, 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 bootloader_startup.c + * This file sets up the runtime environment for TI Stellaris/Tiva MCUs. + * + * @author Stuart W. Baker + * @date 15 September 2019 + */ + +#include + +#include "bootloader_hal.h" +/* we define this our selves because TivaWare forces us to otherwise bring in + * a device specific header to define this. We want to keep this file generic + * to all Cortex-M based TI MCU's + */ +#define NVIC_INT_CTRL_R (*((volatile uint32_t *)0xE000ED04)) + +/* prototypes */ +extern unsigned long *__stack; +extern void reset_handler(void); +extern void bootloader_entry(void); +static void nmi_handler(void); +static void hard_fault_handler(void); +extern void SVC_Handler(void); +extern void PendSV_Handler(void); +extern void SysTick_Handler(void); + +extern void __libc_init_array(void); + +extern int main(int argc, char *argv[]); + +extern void watchdog_interrupt_handler(void); +extern void pvd_vddio2_interrupt_handler(void); +extern void rtc_interrupt_handler(void); +extern void flash_interrupt_handler(void); +extern void rcc_crs_interrupt_handler(void); +extern void external0_1_interrupt_handler(void); +extern void external2_3_interrupt_handler(void); +extern void external4_15_interrupt_handler(void); +extern void touch_interrupt_handler(void); +extern void dma_ch1_interrupt_handler(void); +extern void dma_ch2_3_dma2_ch1_2_interrupt_handler(void); +extern void dma_ch4_5_6_7_dma2_ch3_4_5_interrupt_handler(void); +extern void adc_comp_interrupt_handler(void); +extern void timer1_break_update_trigger_commutation_interrupt_handler(void); +extern void timer1_cc_interrupt_handler(void); +extern void timer2_interrupt_handler(void); +extern void timer3_interrupt_handler(void); +extern void timer6_dac_interrupt_handler(void); +extern void timer7_interrupt_handler(void); +extern void timer14_interrupt_handler(void); +extern void timer15_interrupt_handler(void); +extern void timer16_interrupt_handler(void); +extern void timer17_interrupt_handler(void); +extern void i2c1_interrupt_handler(void); +extern void i2c2_interrupt_handler(void); +extern void spi1_interrupt_handler(void); +extern void spi2_interrupt_handler(void); +extern void uart1_interrupt_handler(void); +extern void uart2_interrupt_handler(void); +extern void uart3_4_5_6_7_8_interrupt_handler(void); +extern void cec_can_interrupt_handler(void); +extern void usb_interrupt_handler(void); + +extern void ignore_fn(void); + +extern const unsigned long cpu_clock_hz; +/** CPU clock speed. */ +const unsigned long cpu_clock_hz = 48000000; +uint32_t SystemCoreClock = 48000000; + +/** Exception table */ +__attribute__ ((section(".interrupt_vector"))) +void (* const __interrupt_vector[])(void) = +{ + (void (*)(void))(&__stack), /**< 0 initial stack pointer */ + reset_handler, /**< 1 reset vector */ + nmi_handler, /**< 2 non-maskable interrupt */ + hard_fault_handler, /**< 3 hard fault */ + 0, /**< 4 reserved */ + 0, /**< 5 reserved */ + 0, /**< 6 reserved */ + 0, /**< 7 reserved */ + 0, /**< 8 reserved */ + 0, /**< 9 reserved */ + 0, /**< 10 reserved */ + SVC_Handler, /**< 11 SV call */ + 0, /**< 12 reservedd */ + reset_handler, /**< 13 reserved -- bootloader appentry */ + PendSV_Handler, /**< 14 pend SV */ + SysTick_Handler, /**< 15 system tick */ + watchdog_interrupt_handler, /**< 16 watchdog timer */ + + /** PVD and VDDIO2 supply comparator + EXTI lines[31,16] */ + pvd_vddio2_interrupt_handler, /**< 17 */ + + /** real time clock + EXTI lines[19,17,20] */ + rtc_interrupt_handler, /**< 18 */ + flash_interrupt_handler, /**< 19 flash global interrupt */ + rcc_crs_interrupt_handler, /**< 20 RCC and CRS global interrupt */ + external0_1_interrupt_handler, /**< 21 EXTI line[1:0] */ + external2_3_interrupt_handler, /**< 22 EXTI line[3:2] */ + external4_15_interrupt_handler, /**< 23 EXTI line[15:4] */ + touch_interrupt_handler, /**< 24 touch sensing */ + dma_ch1_interrupt_handler, /**< 25 DMA channel 1 */ + + /** DMA channel 2 and 3, DMA2 channel 1 and 2 */ + dma_ch2_3_dma2_ch1_2_interrupt_handler, /* 26 */ + + /** DMA channel 4, 5, 6, and 7, DMA2 channel 3, 4, and 5 */ + dma_ch4_5_6_7_dma2_ch3_4_5_interrupt_handler, /* 27 */ + adc_comp_interrupt_handler, /**< 28 ADC and COMP + EXTI line[22:21] */ + + /** timer 1 break, update, trigger, and commutation */ + timer1_break_update_trigger_commutation_interrupt_handler, /* 29 */ + timer1_cc_interrupt_handler, /**< 30 timer 1 capture compare */ + timer2_interrupt_handler, /**< 31 timer 2 */ + timer3_interrupt_handler, /**< 32 timer 3 */ + timer6_dac_interrupt_handler, /**< 33 timer 6 and DAC underrun */ + timer7_interrupt_handler, /**< 34 timer 7 */ + timer14_interrupt_handler, /**< 35 timer 14 */ + timer15_interrupt_handler, /**< 36 timer 15 */ + timer16_interrupt_handler, /**< 37 timer 16 */ + timer17_interrupt_handler, /**< 38 timer 17 */ + i2c1_interrupt_handler, /**< 39 I2C1 + EXTI line[23] */ + i2c2_interrupt_handler, /**< 40 I2C2 */ + spi1_interrupt_handler, /**< 41 SPI1 */ + spi2_interrupt_handler, /**< 42 SPI2 */ + uart1_interrupt_handler, /**< 43 UART1 + EXTI line[25] */ + uart2_interrupt_handler, /**< 44 UART2 + EXTI line[26] */ + + /** UART3, UART4, UART5, UART6, UART7, UART8 + EXTI line[28] */ + uart3_4_5_6_7_8_interrupt_handler, /* 45 */ + cec_can_interrupt_handler, /**< 46 CEC and CAN + EXTI line[27] */ + usb_interrupt_handler, /**< 47 USB + EXTI line[18] */ + + ignore_fn /**< forces the linker to add this fn */ +}; + +extern unsigned long __data_section_table; +extern unsigned long __data_section_table_end; +extern unsigned long __bss_section_table; +extern unsigned long __bss_section_table_end; + +/** Get the system clock requency. + * @return SystemCoreClock +*/ +uint32_t HAL_RCC_GetSysClockFreq(void) +{ + return cpu_clock_hz; +} + + +void reset_handler(void) +{ + bootloader_entry(); + for ( ; /* forever */ ;) + { + /* if we ever return from main, loop forever */ + } +} + + +/** Startup the C/C++ runtime environment. + */ +void bootloader_reset_segments(void) +{ + unsigned long *section_table_addr = &__data_section_table; + + /* copy ram load sections from flash to ram */ + while (section_table_addr < &__data_section_table_end) + { + unsigned long *src = (unsigned long *)*section_table_addr++; + unsigned long *dst = (unsigned long *)*section_table_addr++; + long len = (long) *section_table_addr++; + + for ( ; len > 0; len -= 4) + { + *dst++ = *src++; + } + } + + /* zero initialize bss segment(s) */ + while (section_table_addr < &__bss_section_table_end) + { + unsigned long *zero = (unsigned long *)*section_table_addr++; + long len = (long) *section_table_addr++; + + for ( ; len > 0; len -= 4) + { + *zero++ = 0; + } + } + + /* call static constructors */ + __libc_init_array(); +} + + + +volatile uint32_t r0; +volatile uint32_t r1; +volatile uint32_t r2; +volatile uint32_t r3; +volatile uint32_t r12; +volatile uint32_t lr; /* Link register. */ +volatile uint32_t pc; /* Program counter. */ +volatile uint32_t psr;/* Program status register. */ + +/* These are volatile to try and prevent the compiler/linker optimising them + away as the variables never actually get used. */ +volatile unsigned long stacked_r0 ; +volatile unsigned long stacked_r1 ; +volatile unsigned long stacked_r2 ; +volatile unsigned long stacked_r3 ; +volatile unsigned long stacked_r12 ; +volatile unsigned long stacked_lr ; +volatile unsigned long stacked_pc ; +volatile unsigned long stacked_psr ; +volatile unsigned long _CFSR ; +volatile unsigned long _HFSR ; +volatile unsigned long _DFSR ; +volatile unsigned long _AFSR ; +volatile unsigned long _BFAR ; +volatile unsigned long _MMAR ; + +/** Decode the stack state prior to an exception occuring. This code is + * inspired by FreeRTOS. + * @param address address of the stack + */ +__attribute__((optimize("-O0"))) void hard_fault_handler_c( unsigned long *hardfault_args ) +{ + bootloader_hw_set_to_safe(); + + stacked_r0 = ((unsigned long)hardfault_args[0]) ; + stacked_r1 = ((unsigned long)hardfault_args[1]) ; + stacked_r2 = ((unsigned long)hardfault_args[2]) ; + stacked_r3 = ((unsigned long)hardfault_args[3]) ; + stacked_r12 = ((unsigned long)hardfault_args[4]) ; + stacked_lr = ((unsigned long)hardfault_args[5]) ; + stacked_pc = ((unsigned long)hardfault_args[6]) ; + stacked_psr = ((unsigned long)hardfault_args[7]) ; + + // Configurable Fault Status Register + // Consists of MMSR, BFSR and UFSR + _CFSR = (*((volatile unsigned long *)(0xE000ED28))) ; + + // Hard Fault Status Register + _HFSR = (*((volatile unsigned long *)(0xE000ED2C))) ; + + // Debug Fault Status Register + _DFSR = (*((volatile unsigned long *)(0xE000ED30))) ; + + // Auxiliary Fault Status Register + _AFSR = (*((volatile unsigned long *)(0xE000ED3C))) ; + + // Read the Fault Address Registers. These may not contain valid values. + // Check BFARVALID/MMARVALID to see if they are valid values + // MemManage Fault Address Register + _MMAR = (*((volatile unsigned long *)(0xE000ED34))) ; + // Bus Fault Address Register + _BFAR = (*((volatile unsigned long *)(0xE000ED38))) ; + + __asm("BKPT #0\n") ; // Break into the debugger + + /* When the following line is hit, the variables contain the register values. */ + if (stacked_r0 || stacked_r1 || stacked_r2 || stacked_r3 || stacked_r12 || + stacked_lr || stacked_pc || stacked_psr || _CFSR || _HFSR || _DFSR || + _AFSR || _MMAR || _BFAR) + { + //resetblink(BLINK_DIE_HARDFAULT); + for( ;; ); + } +} + +/** The fault handler implementation. This code is inspired by FreeRTOS. + */ +static void hard_fault_handler(void) +{ + __asm volatile + ( + "MOVS R0, #4 \n" + "MOV R1, LR \n" + "TST R0, R1 \n" + "BEQ _MSP \n" + "MRS R0, PSP \n" + "B hard_fault_handler_c \n" + "_MSP: \n" + "MRS R0, MSP \n" + "B hard_fault_handler_c \n" + "BX LR\n" + ); +} + +static void nmi_handler(void) +{ + for ( ; /* forever */ ; ) + { + } +} + + +volatile unsigned long _INTCTRL = 0; + +/** This is the default handler for exceptions not defined by the application. + */ +void default_interrupt_handler(void) __attribute__ ((weak)); +void default_interrupt_handler(void) +{ + _INTCTRL = NVIC_INT_CTRL_R; + while(1); + //diewith(BLINK_DIE_UNEXPIRQ); +} + + +extern void SVC_Handler(void); +extern void PendSV_Handler(void); +extern void SysTick_Handler(void); + +void SVC_Handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void PendSV_Handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void SysTick_Handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void watchdog_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void pvd_vddio2_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void rtc_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void flash_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void rcc_crs_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void external0_1_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void external2_3_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void external4_15_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void touch_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void dma_ch1_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void dma_ch2_3_dma2_ch1_2_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void dma_ch4_5_6_7_dma2_ch3_4_5_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void adc_comp_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer1_break_update_trigger_commutation_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer1_cc_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer2_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer3_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer6_dac_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer7_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer14_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer15_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer16_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void timer17_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void i2c1_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void i2c2_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void spi1_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void spi2_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void uart1_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void uart2_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void uart3_4_5_6_7_8_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void cec_can_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); +void usb_interrupt_handler(void) __attribute__ ((weak, alias ("default_interrupt_handler"))); diff --git a/boards/st-stm32f0x1_x2_x8-generic/startup.c b/boards/st-stm32f0x1_x2_x8-generic/startup.c index 7f5f55cdd..6a1dc518a 100644 --- a/boards/st-stm32f0x1_x2_x8-generic/startup.c +++ b/boards/st-stm32f0x1_x2_x8-generic/startup.c @@ -107,7 +107,7 @@ void (* const __interrupt_vector[])(void) = 0, /**< 10 reserved */ SVC_Handler, /**< 11 SV call */ 0, /**< 12 reserved */ - 0, /**< 13 reserved -- bootloader appentry */ + reset_handler, /**< 13 reserved -- bootloader appentry */ PendSV_Handler, /**< 14 pend SV */ SysTick_Handler, /**< 15 system tick */ watchdog_interrupt_handler, /**< 16 watchdog timer */ From 24ca60ba3cd22f3e14e4d4a3656ea169fbfbbb56 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 19 Dec 2021 23:43:59 +0100 Subject: [PATCH 28/62] Refactor bootloader client application (#588) - The main.cxx from linux.x86 was forked into main.hxx before this PR - Removes most of the code from linux.x86/main.cxx - Removes most of the code from js.emscripten/main.cxx - Uses the shared code that is now in main.hxx This unifies the feature set that is in the linux and js.emscripten version of bootloader_client. Misc fixes - adds package.json to js version - adds support for serial port to js version - adds commands to build self-sufficient binaries for js version === * bootloader_client: Adds release script to build js binary * Rename main.cxx to main.hxx so that we can include it in real main.cxx. * Implements write_String_to_file for emscripten. * Refactors the main files for bootloader client to remove duplicate code between linux and js targets. This unifies the supported features between them. * Adds package.json configuration. * Fix whitespace. * Adds a feature to specify the second word for the cc3200 checksum algorithm. --- applications/bootloader_client/main.hxx | 127 +++---- .../targets/js.emscripten/Makefile | 17 +- .../targets/js.emscripten/main.cxx | 220 +----------- .../targets/js.emscripten/package.json | 13 + .../targets/linux.x86/main.cxx | 325 ++---------------- src/utils/FileUtils.cxx | 10 + 6 files changed, 129 insertions(+), 583 deletions(-) create mode 100644 applications/bootloader_client/targets/js.emscripten/package.json diff --git a/applications/bootloader_client/main.hxx b/applications/bootloader_client/main.hxx index d6496bbb1..e80c98fc5 100644 --- a/applications/bootloader_client/main.hxx +++ b/applications/bootloader_client/main.hxx @@ -24,7 +24,7 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - * \file main.cxx + * \file main.hxx * * An application for updating the firmware of a remote node on the bus. * @@ -32,13 +32,6 @@ * @date 3 Aug 2013 */ -#include -#include -#include -#include - -#include - #include "os/os.h" #include "utils/constants.hxx" #include "utils/Hub.hxx" @@ -93,12 +86,13 @@ bool request_reboot_after = true; bool skip_pip = false; long long stream_timeout_nsec = 3000; uint32_t hardware_magic = 0x73a92bd1; +uint32_t hardware_magic2 = 0x5a5a55aa; void usage(const char *e) { fprintf(stderr, "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) [-s " - "memory_space_id] [-c csum_algo [-m hw_magic]] [-r] [-t] [-x] " + "memory_space_id] [-c csum_algo [-m hw_magic] [-M hw_magic2]] [-r] [-t] [-x] " "[-w dg_timeout] [-W stream_timeout] [-D dump_filename] " "(-n nodeid | -a alias) -f filename\n", e); @@ -112,30 +106,38 @@ void usage(const char *e) "(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 write the " - "data into. Default is '-s 0xF0'.\n"); - fprintf(stderr, "csum_algo defines the checksum algorithm to use. If " - "omitted, no checksumming is done before writing the " - "data. hw_magic is an argument to the checksum.\n"); fprintf(stderr, - "-r request the target to enter bootloader mode before sending data\n"); - fprintf(stderr, "Unless -t is specified the target will be rebooted after " - "flashing complete.\n"); - fprintf(stderr, "-x skips the PIP request and uses streams.\n"); + "\n\tnodeid should be a 12-char hex string with 0x prefix and " + "no separators, like '-b 0x05010101141F'\n"); + fprintf(stderr, + "\n\talias should be a 3-char hex string with 0x prefix and no " + "separators, like '-a 0x3F9'\n"); + fprintf(stderr, + "\n\tmemory_space_id defines which memory space to write the " + "data into. Default is '-s 0xEF'.\n"); + fprintf(stderr, + "\n\tcsum_algo defines the checksum algorithm to use. If " + "omitted, no checksumming is done before writing the " + "data. hw_magic and hw_magic2 are arguments to the checksum.\n"); + fprintf(stderr, + "\n\t-r request the target to enter bootloader mode before sending " + "data\n"); + fprintf(stderr, + "\n\tUnless -t is specified the target will be rebooted after " + "flashing complete.\n"); + fprintf(stderr, "\n\t-x skips the PIP request and uses streams.\n"); + fprintf(stderr, + "\n\t-w dg_timeout sets how many seconds to wait for a datagram " + "reply.\n"); fprintf(stderr, - "-w dg_timeout sets how many seconds to wait for a datagram reply.\n"); - fprintf(stderr, "-D filename writes the checksummed payload to the given file.\n"); + "\n\t-D filename writes the checksummed payload to the given file.\n"); exit(1); } void parse_args(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "hp:i:rtd:n:a:s:f:c:m:xw:W:D:")) >= 0) + while ((opt = getopt(argc, argv, "hp:i:rtd:n:a:s:f:c:m:M:xw:W:D:")) >= 0) { switch (opt) { @@ -178,6 +180,9 @@ void parse_args(int argc, char *argv[]) case 'm': hardware_magic = strtol(optarg, nullptr, 0); break; + case 'M': + hardware_magic2 = strtol(optarg, nullptr, 0); + break; case 'r': request_reboot = true; break; @@ -263,15 +268,16 @@ void maybe_checksum(string *firmware) crc3_crc16_ibm( firmware->data(), offset, (uint16_t *)hdr.checksum_pre); hdr.checksum_pre[2] = hardware_magic; - hdr.checksum_pre[3] = 0x5a5a55aa; + hdr.checksum_pre[3] = hardware_magic2; crc3_crc16_ibm(&(*firmware)[offset + sizeof(hdr)], (firmware->size() - offset - sizeof(hdr)) & ~3, (uint16_t *)hdr.checksum_post); hdr.checksum_post[2] = hardware_magic; - hdr.checksum_post[3] = 0x5a5a55aa; + hdr.checksum_post[3] = hardware_magic2; memcpy(&(*firmware)[offset], &hdr, sizeof(hdr)); - printf("Checksummed firmware with algorithm cc3200\n"); + printf("Checksummed firmware with algorithm cc3200 (0x%08x 0x%08x)\n", + (unsigned)hardware_magic, (unsigned)hardware_magic2); } else if (algo == "esp8266") { @@ -305,42 +311,11 @@ void maybe_checksum(string *firmware) } } -/** 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[]) +Buffer *fill_request() { - parse_args(argc, argv); - if (!dump_filename) - { - int conn_fd = 0; - if (device_path) - { - conn_fd = ::open(device_path, O_RDWR); - } - else - { - conn_fd = ConnectSocket(host, port); - } - 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); - } - - SyncNotifiable n; - BarrierNotifiable bn(&n); Buffer *b; mainBufferPool->alloc(&b); - b->set_done(&bn); b->data()->dst.alias = destination_alias; b->data()->dst.id = destination_nodeid; b->data()->memory_space = memory_space_id; @@ -350,26 +325,24 @@ int appl_main(int argc, char *argv[]) b->data()->request_reboot_after = request_reboot_after ? 1 : 0; b->data()->skip_pip = skip_pip ? 1 : 0; b->data()->data = read_file_to_string(filename); - - printf("Read %" PRIdPTR - " bytes from file %s. Writing to memory space 0x%02x\n", - b->data()->data.size(), filename, memory_space_id); + printf("Read %" PRIdPTR " bytes from file %s.\n", b->data()->data.size(), + filename); maybe_checksum(&b->data()->data); - if (dump_filename) - { - write_string_to_file(dump_filename, b->data()->data); - exit(0); - } + return b; +} - bootloader_client.send(b); - n.wait_for_notification(); - printf("Result: %04x %s\n", response.error_code, - response.error_details.c_str()); - if (response.error_code != 0) +bool process_dump() +{ + if (!dump_filename) { - exit(1); + return false; } - exit(0); - return 0; + string d = read_file_to_string(filename); + printf("Read %" PRIdPTR " bytes from file %s.\n", d.size(), filename); + maybe_checksum(&d); + write_string_to_file(dump_filename, d); + printf("Written data to %s.\n", dump_filename); + + return true; } diff --git a/applications/bootloader_client/targets/js.emscripten/Makefile b/applications/bootloader_client/targets/js.emscripten/Makefile index bdf68a5bb..1675ca81a 100644 --- a/applications/bootloader_client/targets/js.emscripten/Makefile +++ b/applications/bootloader_client/targets/js.emscripten/Makefile @@ -1,3 +1,18 @@ -include ../../config.mk include $(OPENMRNPATH)/etc/prog.mk -LDFLAGS += --bind +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-wasm: + rm -f $(EXECUTABLE).{wasm,wast} diff --git a/applications/bootloader_client/targets/js.emscripten/main.cxx b/applications/bootloader_client/targets/js.emscripten/main.cxx index 67bf86272..eef8445c8 100644 --- a/applications/bootloader_client/targets/js.emscripten/main.cxx +++ b/applications/bootloader_client/targets/js.emscripten/main.cxx @@ -32,192 +32,14 @@ * @date 3 Aug 2013 */ +#include "main.hxx" + #include #include #include -#include - -#include "executor/Executor.hxx" -#include "executor/Service.hxx" -#include "executor/StateFlow.hxx" -#include "freertos/bootloader_hal.h" -#include "openlcb/AliasAllocator.hxx" -#include "openlcb/BootloaderClient.hxx" -#include "openlcb/DatagramCan.hxx" -#include "openlcb/DefaultNode.hxx" -#include "openlcb/If.hxx" -#include "openlcb/IfCan.hxx" -#include "openlcb/NodeInitializeFlow.hxx" -#include "os/os.h" -#include "utils/Crc.hxx" -#include "utils/GridConnectHub.hxx" -#include "utils/Hub.hxx" +#include "utils/JSSerialPort.hxx" #include "utils/JSTcpClient.hxx" -#include "utils/FileUtils.hxx" -#include "utils/constants.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); - -namespace openlcb -{ -Pool *const g_incoming_datagram_allocator = mainBufferPool; -} - -int port = 12021; -const char *host = "localhost"; - -const char *filename = nullptr; -uint64_t destination_nodeid = 0; -uint64_t destination_alias = 0; -int memory_space_id = openlcb::MemoryConfigDefs::SPACE_FIRMWARE; -const char *checksum_algorithm = nullptr; -bool request_reboot = false; -bool request_reboot_after = true; - -OVERRIDE_CONST(gc_generate_newlines, 1); - -void usage(const char *e) -{ - fprintf(stderr, "Usage: %s [-i destination_host] [-p port] [-s " - "memory_space_id] [-c csum_algo] [-r] [-t] (-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."); - 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 write the " - "data into. Default is '-s 0xEF'.\n"); - fprintf(stderr, "csum_algo defines the checksum algorithm to use. If " - "omitted, no checksumming is done before writing the " - "data.\n"); - fprintf(stderr, - "-r request the target to enter bootloader mode before sending data\n"); - fprintf(stderr, "Unless -t is specified the target will be rebooted after " - "flashing complete.\n"); - exit(1); -} - -void parse_args(int argc, char *argv[]) -{ - int opt; - while ((opt = getopt(argc, argv, "hp:rtn:a:s:f:c:")) >= 0) - { - switch (opt) - { - case 'h': - usage(argv[0]); - break; - case 'p': - port = atoi(optarg); - break; - case 'i': - host = 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 'c': - checksum_algorithm = optarg; - break; - case 'r': - request_reboot = true; - break; - case 't': - request_reboot_after = false; - break; - default: - fprintf(stderr, "Unknown option %c\n", opt); - usage(argv[0]); - } - } - if (!filename || (!destination_nodeid && !destination_alias)) - { - usage(argv[0]); - } -} - -void maybe_checksum(string *firmware) -{ - if (!checksum_algorithm) - return; - string algo = checksum_algorithm; - if (algo == "tiva123") - { - struct app_header hdr; - memset(&hdr, 0, sizeof(hdr)); - // magic constant that comes from the size of the interrupt table. The - // actual target has this in memory_map.ld. - uint32_t offset = 0x270; - if (firmware->size() < offset + sizeof(hdr)) - { - fprintf(stderr, "Failed to checksum: firmware too small.\n"); - exit(1); - } - if (memcmp(&hdr, &(*firmware)[offset], sizeof(hdr))) - { - fprintf(stderr, - "Failed to checksum: location of checksum is not empty.\n"); - exit(1); - } - hdr.app_size = firmware->size(); - crc3_crc16_ibm( - &(*firmware)[8], (offset - 8) & ~3, (uint16_t *)hdr.checksum_pre); - crc3_crc16_ibm(&(*firmware)[offset + sizeof(hdr)], - (firmware->size() - offset - sizeof(hdr)) & ~3, - (uint16_t *)hdr.checksum_post); - memcpy(&(*firmware)[offset], &hdr, sizeof(hdr)); - printf("Checksummed firmware with algorithm tiva123\n"); - uint32_t reset_handler; - memcpy(&reset_handler, firmware->data() + 52, 4); - if (!reset_handler) - { - fprintf(stderr, - "Firmware does not contain any entry vector at offset 52.\n"); - exit(1); - } - } - else - { - fprintf(stderr, - "Unknown checksumming algo %s. Known algorithms are: tiva123.\n", - checksum_algorithm); - exit(1); - } -} - -openlcb::BootloaderClient bootloader_client( - &g_node, &g_datagram_can, &g_if_can); -openlcb::BootloaderResponse response; class BootloaderClientStateFlow : public StateFlowBase { @@ -231,29 +53,14 @@ class BootloaderClientStateFlow : public StateFlowBase private: Action wait_for_boot() { - return sleep_and_call(&timer_, MSEC_TO_NSEC(400), STATE(fill_request)); + return sleep_and_call(&timer_, MSEC_TO_NSEC(400), STATE(send_request)); } - Action fill_request() + Action send_request() { - Buffer *b; - mainBufferPool->alloc(&b); + Buffer *b = fill_request(); b->set_done(bn_.reset(this)); - b->data()->dst.alias = destination_alias; - b->data()->dst.id = destination_nodeid; - b->data()->memory_space = memory_space_id; - b->data()->offset = 0; - b->data()->response = &response; - b->data()->request_reboot = request_reboot ? 1 : 0; - b->data()->request_reboot_after = request_reboot_after ? 1 : 0; - b->data()->data = read_file_to_string(filename); - - printf("Read %" PRIdPTR - " bytes from file %s. Writing to memory space 0x%02x\n", - b->data()->data.size(), filename, memory_space_id); - maybe_checksum(&b->data()->data); - bootloader_client.send(b); return wait_and_call(STATE(bootload_done)); } @@ -281,9 +88,20 @@ class BootloaderClientStateFlow : public StateFlowBase int appl_main(int argc, char *argv[]) { parse_args(argc, argv); + if (process_dump()) + { + return 0; + } + std::unique_ptr dev; std::unique_ptr client; - client.reset(new JSTcpClient(&can_hub0, host, port)); - + if (device_path) + { + dev.reset(new JSSerialPort(&can_hub0, device_path)); + } + else + { + client.reset(new JSTcpClient(&can_hub0, host, port)); + } g_if_can.add_addressed_message_support(); // Bootstraps the alias allocation process. g_if_can.alias_allocator()->send(g_if_can.alias_allocator()->alloc()); diff --git a/applications/bootloader_client/targets/js.emscripten/package.json b/applications/bootloader_client/targets/js.emscripten/package.json new file mode 100644 index 000000000..e20a9b4e7 --- /dev/null +++ b/applications/bootloader_client/targets/js.emscripten/package.json @@ -0,0 +1,13 @@ +{ + "name": "openmrn-bootloader-client", + "version": "1.0.0", + "dependencies": { + "serialport": "^9.2.7" + }, + "bin": "bootloader_client.js", + "pkg": { + "assets": [ + "./node_modules/@serialport/bindings/build/Release/bindings.node" + ] + } +} diff --git a/applications/bootloader_client/targets/linux.x86/main.cxx b/applications/bootloader_client/targets/linux.x86/main.cxx index d6496bbb1..f86c5b816 100644 --- a/applications/bootloader_client/targets/linux.x86/main.cxx +++ b/applications/bootloader_client/targets/linux.x86/main.cxx @@ -39,271 +39,7 @@ #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 "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 "utils/socket_listener.hxx" - -#include "freertos/bootloader_hal.h" - -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); - -namespace openlcb -{ -Pool *const g_incoming_datagram_allocator = mainBufferPool; -extern long long DATAGRAM_RESPONSE_TIMEOUT_NSEC; -} - -int port = 12021; -const char *host = "localhost"; -const char *device_path = nullptr; -const char *filename = nullptr; -const char *dump_filename = nullptr; -uint64_t destination_nodeid = 0; -uint64_t destination_alias = 0; -int memory_space_id = openlcb::MemoryConfigDefs::SPACE_FIRMWARE; -const char *checksum_algorithm = nullptr; -bool request_reboot = false; -bool request_reboot_after = true; -bool skip_pip = false; -long long stream_timeout_nsec = 3000; -uint32_t hardware_magic = 0x73a92bd1; - -void usage(const char *e) -{ - fprintf(stderr, - "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) [-s " - "memory_space_id] [-c csum_algo [-m hw_magic]] [-r] [-t] [-x] " - "[-w dg_timeout] [-W stream_timeout] [-D dump_filename] " - "(-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 write the " - "data into. Default is '-s 0xF0'.\n"); - fprintf(stderr, "csum_algo defines the checksum algorithm to use. If " - "omitted, no checksumming is done before writing the " - "data. hw_magic is an argument to the checksum.\n"); - fprintf(stderr, - "-r request the target to enter bootloader mode before sending data\n"); - fprintf(stderr, "Unless -t is specified the target will be rebooted after " - "flashing complete.\n"); - fprintf(stderr, "-x skips the PIP request and uses streams.\n"); - fprintf(stderr, - "-w dg_timeout sets how many seconds to wait for a datagram reply.\n"); - fprintf(stderr, "-D filename writes the checksummed payload to the given file.\n"); - exit(1); -} - -void parse_args(int argc, char *argv[]) -{ - int opt; - while ((opt = getopt(argc, argv, "hp:i:rtd:n:a:s:f:c:m:xw:W: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 'D': - dump_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 'w': - openlcb::DATAGRAM_RESPONSE_TIMEOUT_NSEC = SEC_TO_NSEC(strtoul(optarg, nullptr, 10)); - break; - case 'W': - openlcb::g_bootloader_timeout_sec = atoi(optarg); - break; - case 'c': - checksum_algorithm = optarg; - break; - case 'm': - hardware_magic = strtol(optarg, nullptr, 0); - break; - case 'r': - request_reboot = true; - break; - case 'x': - skip_pip = true; - break; - case 't': - request_reboot_after = false; - break; - default: - fprintf(stderr, "Unknown option %c\n", opt); - usage(argv[0]); - } - } - if (!filename || (!destination_nodeid && !destination_alias && !dump_filename)) - { - usage(argv[0]); - } -} - -openlcb::BootloaderClient bootloader_client( - &g_node, &g_datagram_can, &g_if_can); -openlcb::BootloaderResponse response; - -void maybe_checksum(string *firmware) -{ - if (!checksum_algorithm) - return; - string algo = checksum_algorithm; - if (algo == "tiva123") - { - struct app_header hdr; - memset(&hdr, 0, sizeof(hdr)); - // magic constant that comes from the size of the interrupt table. The - // actual target has this in memory_map.ld. - uint32_t offset = 0x270; - if (firmware->size() < offset + sizeof(hdr)) - { - fprintf(stderr, "Failed to checksum: firmware too small.\n"); - exit(1); - } - if (memcmp(&hdr, &(*firmware)[offset], sizeof(hdr))) - { - fprintf(stderr, - "Failed to checksum: location of checksum is not empty.\n"); - exit(1); - } - hdr.app_size = firmware->size(); - crc3_crc16_ibm( - &(*firmware)[8], (offset - 8) & ~3, (uint16_t *)hdr.checksum_pre); - crc3_crc16_ibm(&(*firmware)[offset + sizeof(hdr)], - (firmware->size() - offset - sizeof(hdr)) & ~3, - (uint16_t *)hdr.checksum_post); - memcpy(&(*firmware)[offset], &hdr, sizeof(hdr)); - printf("Checksummed firmware with algorithm tiva123\n"); - uint32_t reset_handler; - memcpy(&reset_handler, firmware->data() + 52, 4); - if (!reset_handler) { - fprintf(stderr, - "Firmware does not contain any entry vector at offset 52.\n"); - exit(1); - } - } - else if (algo == "cc3200") - { - struct app_header hdr; - memset(&hdr, 0, sizeof(hdr)); - // magic constant that comes from the size of the interrupt table. The - // actual target has this in memory_map.ld. - uint32_t offset = 0x270; - if (firmware->size() < offset + sizeof(hdr)) - { - fprintf(stderr, "Failed to checksum: firmware too small.\n"); - exit(1); - } - if (memcmp(&hdr, &(*firmware)[offset], sizeof(hdr))) - { - fprintf(stderr, - "Failed to checksum: location of checksum is not empty.\n"); - exit(1); - } - hdr.app_size = firmware->size(); - crc3_crc16_ibm( - firmware->data(), offset, (uint16_t *)hdr.checksum_pre); - hdr.checksum_pre[2] = hardware_magic; - hdr.checksum_pre[3] = 0x5a5a55aa; - crc3_crc16_ibm(&(*firmware)[offset + sizeof(hdr)], - (firmware->size() - offset - sizeof(hdr)) & ~3, - (uint16_t *)hdr.checksum_post); - hdr.checksum_post[2] = hardware_magic; - hdr.checksum_post[3] = 0x5a5a55aa; - - memcpy(&(*firmware)[offset], &hdr, sizeof(hdr)); - printf("Checksummed firmware with algorithm cc3200\n"); - } - else if (algo == "esp8266") - { - struct app_header hdr; - memset(&hdr, 0, sizeof(hdr)); - string nfirmware(4096, 0); - nfirmware += *firmware; - - hdr.app_size = firmware->size() + 4096; - crc3_crc16_ibm( - nullptr, 0, (uint16_t *)hdr.checksum_pre); - hdr.checksum_pre[2] = hardware_magic; - hdr.checksum_pre[3] = 0x5a5a55aa; - - unsigned post_size = hdr.app_size - sizeof(hdr); - crc3_crc16_ibm( - &nfirmware[sizeof(hdr)], post_size, (uint16_t *)hdr.checksum_post); - hdr.checksum_post[2] = hardware_magic; - hdr.checksum_post[3] = 0x5a5a55aa; - - memcpy(&nfirmware[0], &hdr, sizeof(hdr)); - swap(*firmware, nfirmware); - printf("Checksummed firmware with algorithm esp8266 (p sz %u\n", post_size); - } - else - { - fprintf(stderr, "Unknown checksumming algo %s. Known algorithms are: " - "tiva123, cc3200, esp8266.\n", - checksum_algorithm); - exit(1); - } -} +#include "main.hxx" /** Entry point to application. * @param argc number of command line arguments @@ -313,55 +49,36 @@ void maybe_checksum(string *firmware) int appl_main(int argc, char *argv[]) { parse_args(argc, argv); - if (!dump_filename) + if (process_dump()) + { + return 0; + } + int conn_fd = 0; + if (device_path) + { + conn_fd = ::open(device_path, O_RDWR); + } + else { - int conn_fd = 0; - if (device_path) - { - conn_fd = ::open(device_path, O_RDWR); - } - else - { - conn_fd = ConnectSocket(host, port); - } - HASSERT(conn_fd >= 0); - create_gc_port_for_can_hub(&can_hub0, conn_fd); + conn_fd = ConnectSocket(host, port); + } + 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_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); - } + g_executor.start_thread("g_executor", 0, 1024); + usleep(400000); SyncNotifiable n; BarrierNotifiable bn(&n); - Buffer *b; - mainBufferPool->alloc(&b); + Buffer *b = fill_request(); b->set_done(&bn); - b->data()->dst.alias = destination_alias; - b->data()->dst.id = destination_nodeid; - b->data()->memory_space = memory_space_id; - b->data()->offset = 0; - b->data()->response = &response; - b->data()->request_reboot = request_reboot ? 1 : 0; - b->data()->request_reboot_after = request_reboot_after ? 1 : 0; - b->data()->skip_pip = skip_pip ? 1 : 0; - b->data()->data = read_file_to_string(filename); - - printf("Read %" PRIdPTR - " bytes from file %s. Writing to memory space 0x%02x\n", - b->data()->data.size(), filename, memory_space_id); maybe_checksum(&b->data()->data); - if (dump_filename) - { - write_string_to_file(dump_filename, b->data()->data); - exit(0); - } - bootloader_client.send(b); n.wait_for_notification(); printf("Result: %04x %s\n", response.error_code, diff --git a/src/utils/FileUtils.cxx b/src/utils/FileUtils.cxx index 979769910..3201674d0 100644 --- a/src/utils/FileUtils.cxx +++ b/src/utils/FileUtils.cxx @@ -51,6 +51,16 @@ string read_file_to_string(const string &filename) return contents; } +void write_string_to_file(const string &filename, const string &data) +{ + using emscripten::val; + EM_ASM(var fs = require('fs'); Module.fs = fs;); + val fs = val::module_property("fs"); + fs.call("writeFileSync", string(filename), + emscripten::typed_memory_view(data.size(), (uint8_t *)data.data()), + string("binary")); +} + #else #include From 9770c9bd169463e7e69b6b245c9e260b6b4608e7 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 19 Dec 2021 23:46:31 +0100 Subject: [PATCH 29/62] Adds POM commands to the dcc debug print routine. (#589) * Adds POM commands to the dcc debug print routine. * fix mixed up printf --- src/dcc/DccDebug.cxx | 51 +++++++++++++++++++++++++++++++++++++++- src/dcc/DccDebug.cxxtest | 32 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/dcc/DccDebug.cxx b/src/dcc/DccDebug.cxx index e0b1ccbc0..22f09f7c3 100644 --- a/src/dcc/DccDebug.cxx +++ b/src/dcc/DccDebug.cxx @@ -229,7 +229,56 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) else if (cmd == 0 && is_idle_packet) { } - + else if ((cmd >> 4) == 0b1110) + { + // POM command + options += " POM CV"; + unsigned kk = (cmd >> 2) & 3; + unsigned cv = (cmd & 3) << 8; + cv |= pkt.payload[ofs]; + ofs++; + options += StringPrintf("%d", cv + 1); + uint8_t d = pkt.payload[ofs++]; + + switch (kk) + { + case 0b00: + { + options += StringPrintf(" resvd %02x", d); + break; + } + case 0b01: + { + options += StringPrintf(" read/verify %d", d); + break; + } + case 0b11: + { + options += StringPrintf(" write = %d", d); + break; + } + case 0b10: + { + unsigned bit = d & 7; + unsigned value = (d >> 3) & 1; + if ((d & 0xE0) != 0xE0) + { + options += StringPrintf(" bit manipulate unknown (%02x)", d); + break; + } + if ((d & 0x10) == 0x10) + { + options += StringPrintf(" bit %d write = %d", bit, value); + } + else + { + options += StringPrintf(" bit %d verify ?= %d", bit, value); + } + break; + } + } + } + // checksum of packet if (ofs == pkt.dlc && pkt.packet_header.skip_ec == 0) { diff --git a/src/dcc/DccDebug.cxxtest b/src/dcc/DccDebug.cxxtest index a1e09ac9d..cab803ac1 100644 --- a/src/dcc/DccDebug.cxxtest +++ b/src/dcc/DccDebug.cxxtest @@ -125,6 +125,38 @@ TEST(DccDebug, F21_28) EXPECT_EQ("[dcc] Short Address 3 F[21-28]=01101001", packet_to_string(pkt)); } +TEST(DccDebug, POMWrite) +{ + Packet pkt; + pkt.add_dcc_address(DccShortAddress(3)); + pkt.add_dcc_pom_write1(13, 67); + EXPECT_EQ( + "[dcc] Short Address 3 POM CV14 write = 67", packet_to_string(pkt)); +} + +TEST(DccDebug, POMWriteHighCV) +{ + Packet pkt; + pkt.add_dcc_address(DccShortAddress(3)); + pkt.add_dcc_pom_write1(950, 255); + EXPECT_EQ( + "[dcc] Short Address 3 POM CV951 write = 255", packet_to_string(pkt)); + pkt.clear(); + pkt.add_dcc_address(DccShortAddress(3)); + pkt.add_dcc_pom_write1(1023, 0); + EXPECT_EQ( + "[dcc] Short Address 3 POM CV1024 write = 0", packet_to_string(pkt)); +} + +TEST(DccDebug, POMRead) +{ + Packet pkt; + pkt.add_dcc_address(DccShortAddress(3)); + pkt.add_dcc_pom_read1(13); + EXPECT_EQ( + "[dcc] Short Address 3 POM CV14 read/verify 0", packet_to_string(pkt)); +} + TEST(DccDebug, Accy) { Packet pkt; From 5635dd4c4a6f229f64d5066902d020b2f400d574 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 19 Dec 2021 23:48:09 +0100 Subject: [PATCH 30/62] Fixes the PacketIs expectation used in unit tests. (#590) The matcher will now fix when the skip_ec bit is differently set in the expectation and in the actual value by computing the correct EC byte. Previously the expectation would silently fail in this case. === * Fixes the PacketIs expectation used in unit tests. The matcher will now fix when the skip_ec bit is differently set in the expectation and in the actual value by computing the correct EC byte. Previously the expectation would silently fail in this case. --- src/dcc/dcc_test_utils.hxx | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/dcc/dcc_test_utils.hxx b/src/dcc/dcc_test_utils.hxx index c1e683140..59969b6ad 100644 --- a/src/dcc/dcc_test_utils.hxx +++ b/src/dcc/dcc_test_utils.hxx @@ -63,10 +63,42 @@ dcc::Packet packet_from(uint8_t hdr, std::vector payload) return pkt; } -MATCHER_P2(PacketIs, hdr, payload, - std::string(" is a packet of ") + PrintToString(packet_from(hdr, payload))) +MATCHER_P2(PacketIs, hdr, payload, PrintToString(packet_from(hdr, payload))) { dcc::Packet pkt = packet_from(hdr, payload); + dcc::Packet argc = arg; + vector exp_bytes(pkt.payload, pkt.payload + pkt.dlc); + vector act_bytes(arg.payload, arg.payload + arg.dlc); + if (pkt.packet_header.is_pkt == 0 && pkt.packet_header.is_marklin == 0 && + arg.packet_header.is_pkt == 0 && arg.packet_header.is_marklin == 0 && + pkt.packet_header.skip_ec != arg.packet_header.skip_ec) + { + // Mismatch in whether the EC byte is there. We fix this by adding the + // EC byte to the place where it is missing. This avoids test failures + // where the packets really are equivalent. + if (pkt.packet_header.skip_ec == 0) + { + uint8_t ec = 0; + for (uint8_t b : exp_bytes) + { + ec ^= b; + } + exp_bytes.push_back(ec); + pkt.packet_header.skip_ec = 1; + } + if (arg.packet_header.skip_ec == 0) + { + uint8_t ec = 0; + for (uint8_t b : act_bytes) + { + ec ^= b; + } + act_bytes.push_back(ec); + argc.packet_header.skip_ec = 1; + } + return (pkt.header_raw_data == argc.header_raw_data && + exp_bytes == act_bytes); + } return (pkt.header_raw_data == arg.header_raw_data && pkt.dlc == arg.dlc && memcmp(pkt.payload, arg.payload, pkt.dlc) == 0); } From 3716342bb43500df33060ae8af5ad49f96c3b12c Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 19 Dec 2021 23:49:36 +0100 Subject: [PATCH 31/62] Adds helper macro expect_packet_and_send_response. (#591) * Adds helper macro expect_packet_and_send_response. This allows building simpler test scripts where the script can automatically react to packets sent by the code under test. Previously the script had to have separate steps. --- src/utils/async_if_test_helper.hxx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/utils/async_if_test_helper.hxx b/src/utils/async_if_test_helper.hxx index f2af652f9..ea6e5cd11 100644 --- a/src/utils/async_if_test_helper.hxx +++ b/src/utils/async_if_test_helper.hxx @@ -158,6 +158,26 @@ protected: #define expect_packet(gc_packet) \ EXPECT_CALL(canBus_, mwrite(StrCaseEq(gc_packet))) +/** Adds an expectation that the code will send a packet to the CANbus. Upon + * receiving that packet, sends a packet to CAN-bus. This is helpful when the + * test script is simulating a node connected to the CAN-bus and that remote + * node has to respond with an ack to the packet emitted by the code under + * test. + + Example: + expect_packet_and_send_response(":X1A555444N0102030405060708;", + ":X19A28555N044400;"); + + @param gc_packet the packet that the code under test should emit, in + GridConnect format, including the leading : and trailing ; + @param resp_packet the packet that will be sent to the code under test + after seeing the emitted packet. +*/ +#define expect_packet_and_send_response(gc_packet, resp_packet) \ + EXPECT_CALL(canBus_, mwrite(StrCaseEq(gc_packet))) \ + .WillOnce(::testing::InvokeWithoutArgs( \ + [this]() { send_packet(resp_packet); })) + /** Ignores all produced packets. * * Tihs can be used in tests where the expectations are tested in a higher From b6c249023ba2317d62add9c7ee2dea1ddeebb983 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 19 Dec 2021 23:50:17 +0100 Subject: [PATCH 32/62] Adds extra unit tests where the CV read has a couple of ACKs on it. (#592) --- src/openlcb/TractionCvSpace.cxxtest | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/openlcb/TractionCvSpace.cxxtest b/src/openlcb/TractionCvSpace.cxxtest index dc95cd53c..ebc16f9b4 100644 --- a/src/openlcb/TractionCvSpace.cxxtest +++ b/src/openlcb/TractionCvSpace.cxxtest @@ -101,6 +101,42 @@ TEST_F(TractionCvTest, SingleCvRead) wait(); } +TEST_F(TractionCvTest, SingleCvReadWithStuffing) +{ + print_all_packets(); + expect_packet(":X19A28272N088380;"); + EXPECT_CALL(track_if_, packet(ElementsAre(0xC0, 0xAF, 0b11100100, 0x37, 0), + expected_feedback_key())).Times(1); + send_packet(":X1A272883N204000000037EF01;"); + wait(); + Mock::VerifyAndClear(&track_if_); + expect_packet(":X1A883272N205000000037EFC5;"); + // payload stuffed with 0x0f / ACK2 bytes + send_railcom_response(expected_feedback_key(), + {0b10100101, 0b10100110, 0x0F, 0x0F, 0x0F, 0x0F}); + wait(); + send_packet(":X19A28883N027200;"); + wait(); +} + +TEST_F(TractionCvTest, SingleCvReadWithOtherStuffing) +{ + print_all_packets(); + expect_packet(":X19A28272N088380;"); + EXPECT_CALL(track_if_, packet(ElementsAre(0xC0, 0xAF, 0b11100100, 0x37, 0), + expected_feedback_key())).Times(1); + send_packet(":X1A272883N204000000037EF01;"); + wait(); + Mock::VerifyAndClear(&track_if_); + expect_packet(":X1A883272N205000000037EFC5;"); + // payload stuffed with 0xF0 / ACK bytes + send_railcom_response(expected_feedback_key(), + {0b10100101, 0b10100110, 0xF0, 0xF0, 0xF0, 0xF0}); + wait(); + send_packet(":X19A28883N027200;"); + wait(); +} + TEST_F(TractionCvTest, SingleCvBusyRetry) { print_all_packets(); @@ -135,6 +171,9 @@ TEST_F(TractionCvTest, SingleCvWrite) wait(); } +// Tests the human-operable part of the CV space. This has individual variable +// for different parameters, and it is expected that JMRI will be used to +// individually set these. TEST_F(TractionCvTest, IndirectWrite) { print_all_packets(); From 24b5afaa6f766d6284d1bcdb74090379453ddf03 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 27 Dec 2021 19:25:45 +0100 Subject: [PATCH 33/62] Adds support for distcc (#593) Updates the compilation scripts to be able to wrap compilations with distcc. Adds documentation on howto set up distcc. Adds helper script to start correct SSH tunnel. Adds auto-detection on whether the tunnel is up or not. This dramatically reduces compilation time for complex projects, sometimes by a factor of 2x-3x if a single powerful backend machine is used. Fixes all usages of .d (dependency) file generation to skip the system headers. === * Ensures that we always use -MMD -MF to generate dependency files. This will remove system headers from the dependencies, which is important because the system headers might change between compiler versions. * Adds distcc scripts and howto documentation. * Updates cortex-m targets to use distcc when available. * Adds more comments to the docs. * Fixes formatting in documentation. * Fix some style in the doc. * Kills the remote distccd before trying to start it. * Enables forwarding g++ and gcc invocations as well. * Enables distcc for test compilations. --- DISTCC.md | 178 +++++++++++++++++++++++++++++++++++++++++ bin/find_distcc.sh | 45 +++++++++++ bin/start-distcc.sh | 24 ++++++ etc/applib.mk | 11 +-- etc/bare.armv6m.mk | 4 +- etc/bare.armv7m.mk | 4 +- etc/cov.mk | 4 +- etc/freertos.armv6m.mk | 4 +- etc/freertos.armv7m.mk | 4 +- etc/lib.mk | 10 +-- etc/prog.mk | 13 ++- 11 files changed, 272 insertions(+), 29 deletions(-) create mode 100644 DISTCC.md create mode 100755 bin/find_distcc.sh create mode 100755 bin/start-distcc.sh diff --git a/DISTCC.md b/DISTCC.md new file mode 100644 index 000000000..0d7726b4f --- /dev/null +++ b/DISTCC.md @@ -0,0 +1,178 @@ +# Using distcc with OpenMRN compilation framework + +## Overview + +### What is this? + +This feature allows using a powerful remote computer to perform most of the +compilation steps when compiling OpenMRN-based projects. + +### Why? + +It is much faster. + +OpenMRN compiles a large number of c++ files that can take a lot of CPU to +compile. If your workstation has constrained CPU (e.g. a laptop or a virtual +machine with limited number of CPUs), you can use a powerful remote machine to +offload most of the compilation steps. + +### How does it work? + +We use the opensource distcc package (`git@github.com:distcc/distcc.git`). We +create an SSH link to the remote machine, and start the distcc daemon. We wrap +the local compilation command in distcc. Distcc will preprocess the source file +locally, then transmit the preprocessed source to the remote machine over the +SSH link. The daemon running on the powerful machine will then invoke the +compiler, create the .o file, and transmit the .o file back over the SSH +link. The linking steps then happen locally. + +## Prerequisites + +- You have to have the same compiler that OpenMRN uses available on the remote + machine. We have a [specific step](#configuring-the-compilers) for telling + distcc and OpenMRN what the matching compilers are. +- You need to start the SSH link before trying to compile things. If you forget + this, local compilation will be performed instead. +- A small change needs to be made to the distcc sources, because it does not + support a compiler flag that we routinely pass to armgcc. For this reason you + need to compile it from source. + +### Installing + +#### Installing distcc + +1. `sudo apt-get install distcc` on both the local and remote machine. +2. Clone the distcc sources from `git@github.com:distcc/distcc.git`. Check the + INSTALL file for an apt-get commandline near the very top for compile-time + dependencies to be installed. +3. edit `src/arg.c`. Find the place where it checks for `"-specs="` + argument. Comment out the early return. (Not doing anything in that if is + what you want.) +4. edit `src/serve.c`. Find where it checks for `"-specs="`, and disable that + check. +5. run through the compile steps of distcc, see the INSTALL file. These are + normally: + ``` + ./autogen.sh + ./configure + make + ``` +6. Copy `distcc` to `~/bin`, and copy `distccd` to the remote machine at `~/bin` + +#### Configuring the compilers + +When setting up compilers, we need to ensure that we can determine what the +remote compiler will be called for any given local compiler that we have. In +addition to this, we need to be able to call the same compiler underthe same +command on both machines, because the local machine will be calling the +compiler for preprocessing and the remote machine will be calling it for +compilation. + +For any given compiler, you need to make a symlink on both the local machine +and the remote machine: + +```bash +cd ~/bin +ln -sf $(realpath /opt/armgcc/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gcc) armgcc-2018-q4-gcc +ln -sf $(realpath /opt/armgcc/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-g++) armgcc-2018-q4-g++ +``` + +Do this on BOTH the local and remote machine. The real path may differ but the +name must be exactly the same. + +Only on the remote machine, the compiler also needs to be added to the +masquerade directory, otherwise distccd server will refuse to execute it. + +```bash +cd /usr/lib/distccd +sudo ln -sf ../../bin/distcc armgcc-2018-q4-g++ +sudo ln -sf ../../bin/distcc armgcc-2018-q4-gcc +``` + +#### Setting parameters + +For the OpenMRN compilation scripts to find distcc and the remote machine, we +store a few parameters in files under `~/.distcc`. + +```bash +mkdir -p ~/.distcc +echo 3434 > ~/.distcc/port +echo my-fast-remote-machine.myplace.com > ~/.distcc/ssh_host +echo '127.0.0.1:3434/20,lzo' > ~/.distcc/hosts +``` + +The port number will be used with SSH port forwarding. The remote machine is +used by the ssh tunnel setup script. + +The "/20" part of the hosts line is the capacity of the remote machine. This +one says it's good for 20 parallel jobs, which I picked for a 6-core (12 +hyperthread) machine with lots of RAM. + +You can configure a lot of other things in the hosts file. `man distcc` for the +full documentation. It is also possible to configure the local CPU to run some +number of jobs while the remote CPU runs other builds. It is possible to use +multiple remote machines as well. + +## Using + +After every restart of your machine you need to start the ssh tunnel: + +```bash +~/openmrn/bin/start-distcc.sh +``` + +This will set up the SSH tunnel to the remote host and start the distccd server +on the far end of the tunnel. + +Then compile OpenMRN stuff with very high parallelism: + +```bash +~/openmrn/applications/io_board/target/freertos.armv7m.ek-tm4c123gxl$ make -j21 +``` + +### How do I know if it worked? + +OpenMRN build system will automatically detect that you have the SSH tunnel +running to distcc and use distcc. + +If it uses distcc, then you will see the compilation done remotely. Check the +beginning of most lines: + +``` +distcc armgcc-2018-q4-gcc -c -Os -fno-strict-aliasing [...] STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_usart.c -o stm32l4xx_hal_usart.o +``` + +If it is done locally, then you don't see distcc but see the execution of the +compiler directly: + +``` +/opt/armgcc/gcc-arm-none-eabi-8-2018-q4-major/bin/arm-none-eabi-gcc -c -Os -fno-strict-aliasing [...] STM32L4xx_HAL_Driver/Src/stm32l4xx_hal_usart.c -o stm32l4xx_hal_usart.o +``` + +### How much does it help? + +With distcc (parallelism of 21, all remotely; this is on a W-2135 Xeon CPU @ +3.70GHz): + +```bash + text data bss dec hex filename + 113808 312 3768 117888 1cc80 io_board.elf +/opt/armgcc/default/bin/arm-none-eabi-objdump -C -d -h io_board.elf > io_board.lst + +real 0m44.704s +user 2m41.551s +sys 0m29.235s +``` + +Without distcc (parallelism of 9, all locally; this is on an intel i7-8665U CPU +@ 1.90GHz -- this is a 15W TDP mobile CPU with 4 cores, 8 hyperthreads): + +```bash + text data bss dec hex filename + 113808 312 3768 117888 1cc80 io_board.elf +/opt/armgcc/default/bin/arm-none-eabi-objdump -C -d -h io_board.elf > io_board.lst + +real 2m8.602s +user 14m21.471s +sys 0m42.488s +``` diff --git a/bin/find_distcc.sh b/bin/find_distcc.sh new file mode 100755 index 000000000..5248614d0 --- /dev/null +++ b/bin/find_distcc.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# +# usage: find-distcc.sh /opt/armgcc/default/bin/arm-none-eabi-g++ +# +# Checks if distcc is available right now,. If yes, returns `distcc +# compiler-short-name` otherwise returns the absolute path to the compiler for +# local execution. +# +# The example output for the above incovation might be: +# /opt/armgcc/default/bin/arm-none-eabi-g++ +# or +# distcc armgcc-2018-q4-g++ +# +# See DISTCC.md for additional information. + +DISTCC_PORT=$(< ~/.distcc/port) +if [ -z "${DISTCC_PORT}" ] ; then + echo missing ~/.distcc/port. See DISTCC.md. >&2 + echo no-distcc-port-file-found + exit +fi + +if ! netstat --tcp -l -n | grep -q ":${DISTCC_PORT} " ; then + # nobody is listening to the distcc port + echo "$1" + exit +fi + +# Always enable remoting g++ and gcc +if [ "$1" == "gcc" -o "$1" == "g++" ]; then + echo distcc "$1" + exit +fi + +#Find masquerading compiler name +#echo find ~/bin -type l -lname "$1" -print +CNAME=$(find ~/bin -type l -lname "$1" -print) + +if [ -z "${CNAME}" ] ; then + echo missing distcc compiler link for "$1" >&2 + echo unknown-distcc-compiler + exit +fi + +echo distcc $(basename "${CNAME}") diff --git a/bin/start-distcc.sh b/bin/start-distcc.sh new file mode 100755 index 000000000..7eaff7d5e --- /dev/null +++ b/bin/start-distcc.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Starts ssh with port fordwaring for your remote distCC host. + + + +DISTCC_PORT=$(< ~/.distcc/port) +if [ -z "${DISTCC_PORT}" ] ; then + echo missing ~/.distcc/port. See DISTCC.md. >&2 + exit 1 +fi + +DISTCC_RHOST=$(< ~/.distcc/ssh_host) +if [ -z "${DISTCC_RHOST}" ] ; then + echo missing ~/.distcc/ssh_host. See DISTCC.md. >&2 + exit 1 +fi + +DISTCC_SSHARGS=$(< ~/.distcc/ssh_args) + + +set -x + +ssh ${DISTCC_SSHARGS} -L localhost:${DISTCC_PORT}:localhost:${DISTCC_PORT} ${DISTCC_RHOST} "killall distccd; ~/bin/distccd --no-detach --listen 127.0.0.1 --jobs 20 -p ${DISTCC_PORT} --allow 127.0.0.1/24 --verbose --daemon --log-stderr" diff --git a/etc/applib.mk b/etc/applib.mk index 192dd394d..43090b902 100644 --- a/etc/applib.mk +++ b/etc/applib.mk @@ -61,19 +61,16 @@ all: $(LIBNAME) .SUFFIXES: .o .c .cxx .cpp .S .cpp.o: - $(CXX) $(CXXFLAGS) $< -o $@ - $(CXX) -MM $(CXXFLAGS) $< > $*.d + $(CXX) -MMD -MF $*.d $(CXXFLAGS) $< -o $@ .cxx.o: - $(CXX) $(CXXFLAGS) $< -o $@ - $(CXX) -MM $(CXXFLAGS) $< > $*.d + $(CXX) -MMD -MF $*.d $(CXXFLAGS) $< -o $@ .c.o: - $(CC) $(CFLAGS) $< -o $@ - $(CC) -MM $(CFLAGS) $< > $*.d + $(CC) -MMD -MF $*.d $(CFLAGS) $< -o $@ .S.o: - $(AS) $(ASFLAGS) -MD -MF $*.d $(abspath $<) -o $@ + $(AS) $(ASFLAGS) -MMD -MF $*.d $(abspath $<) -o $@ $(LIBNAME): $(OBJS) $(AR) cr $(LIBNAME) $(OBJS) diff --git a/etc/bare.armv6m.mk b/etc/bare.armv6m.mk index f01a1184e..9617a44a2 100644 --- a/etc/bare.armv6m.mk +++ b/etc/bare.armv6m.mk @@ -9,8 +9,8 @@ endif PREFIX = $(TOOLPATH)/bin/arm-none-eabi- AS = $(PREFIX)gcc -CC = $(PREFIX)gcc -CXX = $(PREFIX)g++ +CC = $(shell $(OPENMRNPATH)/bin/find_distcc.sh $(realpath $(PREFIX)gcc)) +CXX = $(shell $(OPENMRNPATH)/bin/find_distcc.sh $(realpath $(PREFIX)g++)) AR = $(PREFIX)ar LD = $(PREFIX)g++ SIZE = $(PREFIX)size diff --git a/etc/bare.armv7m.mk b/etc/bare.armv7m.mk index 566329f44..625f11898 100644 --- a/etc/bare.armv7m.mk +++ b/etc/bare.armv7m.mk @@ -9,8 +9,8 @@ endif PREFIX = $(TOOLPATH)/bin/arm-none-eabi- AS = $(PREFIX)gcc -CC = $(PREFIX)gcc -CXX = $(PREFIX)g++ +CC = $(shell $(OPENMRNPATH)/bin/find_distcc.sh $(realpath $(PREFIX)gcc)) +CXX = $(shell $(OPENMRNPATH)/bin/find_distcc.sh $(realpath $(PREFIX)g++)) AR = $(PREFIX)ar LD = $(PREFIX)g++ SIZE = $(PREFIX)size diff --git a/etc/cov.mk b/etc/cov.mk index 37c865f8c..d06b0b179 100644 --- a/etc/cov.mk +++ b/etc/cov.mk @@ -8,8 +8,8 @@ include $(OPENMRNPATH)/etc/env.mk # instead of the system default. # GCCVERSION=-8 -CC = gcc$(GCCVERSION) -CXX = g++$(GCCVERSION) +CC = $(shell $(OPENMRNPATH)/bin/find_distcc.sh gcc$(GCCVERSION)) +CXX = $(shell $(OPENMRNPATH)/bin/find_distcc.sh g++$(GCCVERSION)) AR = ar LD = g++$(GCCVERSION) diff --git a/etc/freertos.armv6m.mk b/etc/freertos.armv6m.mk index 9ccb973fa..b9582cf63 100644 --- a/etc/freertos.armv6m.mk +++ b/etc/freertos.armv6m.mk @@ -16,8 +16,8 @@ include $(OPENMRNPATH)/etc/mbed.mk PREFIX = $(TOOLPATH)/bin/arm-none-eabi- AS = $(PREFIX)gcc -CC = $(PREFIX)gcc -CXX = $(PREFIX)g++ +CC = $(shell $(OPENMRNPATH)/bin/find_distcc.sh $(realpath $(PREFIX)gcc)) +CXX = $(shell $(OPENMRNPATH)/bin/find_distcc.sh $(realpath $(PREFIX)g++)) AR = $(PREFIX)ar LD = $(PREFIX)g++ SIZE = $(PREFIX)size diff --git a/etc/freertos.armv7m.mk b/etc/freertos.armv7m.mk index 7b4b288fc..8594f0729 100644 --- a/etc/freertos.armv7m.mk +++ b/etc/freertos.armv7m.mk @@ -12,8 +12,8 @@ endif PREFIX = $(TOOLPATH)/bin/arm-none-eabi- AS = $(PREFIX)gcc -CC = $(PREFIX)gcc -CXX = $(PREFIX)g++ +CC = $(shell $(OPENMRNPATH)/bin/find_distcc.sh $(realpath $(PREFIX)gcc)) +CXX = $(shell $(OPENMRNPATH)/bin/find_distcc.sh $(realpath $(PREFIX)g++)) AR = $(PREFIX)ar LD = $(PREFIX)g++ SIZE = $(PREFIX)size diff --git a/etc/lib.mk b/etc/lib.mk index 7984cb79f..a21e1b995 100644 --- a/etc/lib.mk +++ b/etc/lib.mk @@ -79,19 +79,19 @@ endif .SUFFIXES: .o .c .cxx .cpp .S .cpp.o: - $(CXX) $(CXXFLAGS) -MD -MF $*.d $< -o $@ + $(CXX) $(CXXFLAGS) -MMD -MF $*.d $< -o $@ .cxx.o: - $(CXX) $(CXXFLAGS) -MD -MF $*.d $< -o $@ + $(CXX) $(CXXFLAGS) -MMD -MF $*.d $< -o $@ .S.o: - $(AS) $(ASFLAGS) -MD -MF $*.d $< -o $@ + $(AS) $(ASFLAGS) -MMD -MF $*.d $< -o $@ .c.o: - $(CC) $(CFLAGS) -MD -MF $*.d $< -o $@ + $(CC) $(CFLAGS) -MMD -MF $*.d $< -o $@ $(ARM_OBJS): %.o : %.c - $(CC) $(ARM_CFLAGS) -MD -MF $*.d $< -o $@ + $(CC) $(ARM_CFLAGS) -MMD -MF $*.d $< -o $@ $(LIBNAME): $(OBJS) diff --git a/etc/prog.mk b/etc/prog.mk index 280890879..ce67948ad 100644 --- a/etc/prog.mk +++ b/etc/prog.mk @@ -140,7 +140,7 @@ cdi.o : compile_cdi rm -f cdi.d compile_cdi: config.hxx $(OPENMRNPATH)/src/openlcb/CompileCdiMain.cxx - g++ -o $@ -I. -I$(OPENMRNPATH)/src -I$(OPENMRNPATH)/include $(CDIEXTRA) --std=c++11 -MD -MF $@.d $(CXXFLAGSEXTRA) $(OPENMRNPATH)/src/openlcb/CompileCdiMain.cxx + g++ -o $@ -I. -I$(OPENMRNPATH)/src -I$(OPENMRNPATH)/include $(CDIEXTRA) --std=c++11 -MMD -MF $@.d $(CXXFLAGSEXTRA) $(OPENMRNPATH)/src/openlcb/CompileCdiMain.cxx config.hxx: Revision.hxxout @@ -257,8 +257,7 @@ cincstats: %.xml: %.o $(OPENMRNPATH)/bin/build_cdi.py $(OPENMRNPATH)/bin/build_cdi.py -i $< -o $*.cxxout - $(CXX) $(CXXFLAGS) -x c++ $*.cxxout -o $@ - $(CXX) -MM $(CXXFLAGS) -x c++ $*.cxxout > $*.d + $(CXX) -MMD -MF $*.d $(CXXFLAGS) -x c++ $*.cxxout -o $@ ifeq ($(TARGET),bare.pruv3) .cpp.o: @@ -275,16 +274,16 @@ ifeq ($(TARGET),bare.pruv3) else .cpp.o: - $(CXX) $(CXXFLAGS) -MD -MF $*.d $(abspath $<) -o $@ + $(CXX) $(CXXFLAGS) -MMD -MF $*.d $(abspath $<) -o $@ .cxx.o: - $(CXX) $(CXXFLAGS) -MD -MF $*.d $(abspath $<) -o $@ + $(CXX) $(CXXFLAGS) -MMD -MF $*.d $(abspath $<) -o $@ .S.o: - $(AS) $(ASFLAGS) -MD -MF $*.d $(abspath $<) -o $@ + $(AS) $(ASFLAGS) -MMD -MF $*.d $(abspath $<) -o $@ .c.o: - $(CC) $(CFLAGS) -MD -MF $*.d $(abspath $<) -o $@ + $(CC) $(CFLAGS) -MMD -MF $*.d $(abspath $<) -o $@ endif clean: clean-local From 83b76242cb67719333611ecc9ed63c7d67fd5a23 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 27 Dec 2021 19:26:17 +0100 Subject: [PATCH 34/62] Fixes gcc-11 compatibility issue. (#594) Due to a source code problem in the GTEST package that we depend on we have to disable a warning when compiling a given source file with g++-11. * Fixes gcc-11 compatibility issue. --- etc/core_test.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/core_test.mk b/etc/core_test.mk index f373c3b5c..1485137eb 100644 --- a/etc/core_test.mk +++ b/etc/core_test.mk @@ -53,7 +53,7 @@ $(TESTOBJS): %.test.o : $(SRCDIR)/%.cxxtest $(CXX) $(CXXFLAGS) -MD -MF $*.dtest -x c++ $< -o $@ gtest-all.o : %.o : $(GTESTSRCPATH)/src/%.cc - $(CXX) $(CXXFLAGS) -I$(GTESTPATH) -I$(GTESTSRCPATH) -MD -MF $*.d $< -o $@ + $(CXX) $(CXXFLAGS) -Wno-uninitialized -I$(GTESTPATH) -I$(GTESTSRCPATH) -MD -MF $*.d $< -o $@ gmock-all.o : %.o : $(GMOCKSRCPATH)/src/%.cc $(CXX) $(CXXFLAGS) -I$(GMOCKPATH) -I$(GMOCKSRCPATH) -MD -MF $*.d $< -o $@ From 31d0f15707ca9b522218bee0464e57493ff0eabf Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 27 Dec 2021 19:27:24 +0100 Subject: [PATCH 35/62] Adds more tests for fixed16 to validate that division has appropriate precision. (#595) This does not change the implementation. The existing implementation already had a givision algorithm with good precision. The additional tests will ensure that this property will hold up in the future. --- src/utils/Fixed16.cxxtest | 40 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/utils/Fixed16.cxxtest b/src/utils/Fixed16.cxxtest index c1750ddb1..14866a06b 100644 --- a/src/utils/Fixed16.cxxtest +++ b/src/utils/Fixed16.cxxtest @@ -207,6 +207,46 @@ TEST(Fixed16Test, Division) EXPECT_EQ(0, multiplier.trunc()); } +TEST(Fixed16Test, ShiftByDivision) +{ + Fixed16 v1(1); + v1 /= 4; + EXPECT_EQ(0, v1.trunc()); + EXPECT_EQ(0x4000, v1.frac()); + + v1 = 0x5650; + v1 /= 16; + EXPECT_EQ(0x565, v1.trunc()); + EXPECT_EQ(0, v1.frac()); + + v1 = 0x569F; + v1 /= 256; + EXPECT_EQ(0x56, v1.trunc()); + EXPECT_EQ(0x9F00, v1.frac()); + + // Negative bit is also correctly handled. + v1 = 0x569F; + v1 /= -256; + EXPECT_EQ(-0x56, v1.trunc()); + EXPECT_EQ(0x9F00, v1.frac()); +} + +TEST(Fixed16Test, DivisionPrecision) +{ + Fixed16 v1(31000); // close to largest representable integer + Fixed16 v2(Fixed16::FROM_DOUBLE, 1.01754); + EXPECT_EQ(1150, v2.frac()); + v1 /= v2; + // - True result is 30465.6328 + // - Due to rounding while assigning v2, we are actually dividing + // by 1.0175476 + // - Precise division result is then 30465.40503 + EXPECT_EQ(30465, v1.trunc()); + EXPECT_EQ((unsigned)(0.40503 * 65536), v1.frac()); + EXPECT_THAT(v1.to_float(), FloatNear(30465.40503, 1e-4)); + EXPECT_THAT(v1.to_float(), FloatNear(31000.0 / 1.0175476, 1e-4)); +} + TEST(Fixed16Test, Sign) { Fixed16 v1(13); From 90ff0151b2d318bd3380a9077524c75af48b941a Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 27 Dec 2021 20:22:08 +0100 Subject: [PATCH 36/62] Fix find_distcc.sh to operate correctly when the user does not want to use distcc. --- bin/find_distcc.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bin/find_distcc.sh b/bin/find_distcc.sh index 5248614d0..4e47536f2 100755 --- a/bin/find_distcc.sh +++ b/bin/find_distcc.sh @@ -13,10 +13,11 @@ # # See DISTCC.md for additional information. -DISTCC_PORT=$(< ~/.distcc/port) +DISTCC_PORT=$(cat ~/.distcc/port 2>/dev/null) if [ -z "${DISTCC_PORT}" ] ; then - echo missing ~/.distcc/port. See DISTCC.md. >&2 - echo no-distcc-port-file-found + # .distcc/port file is not set up. This means the user does not want to use + # distcc. + echo "$1" exit fi From 61951f645630aea440242ce026b81c988b68e974 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 29 Dec 2021 10:21:33 +0100 Subject: [PATCH 37/62] Fixes compilation problems with GCC-10.3 (armgcc-2021-10) (#596) - new GCC does not like variable arrays on the stack where the size of the array is not known. For EEPROMEMu we define a constant maximum byte size of these arrays. - There is a warning emitted for the tivaware source code, this is disabled with a targeted flag change. --- src/freertos_drivers/common/EEPROMEmulation.cxx | 15 ++++++++------- src/freertos_drivers/common/EEPROMEmulation.hxx | 3 +++ .../freertos_drivers/tivadriverlib/Makefile | 1 + 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/freertos_drivers/common/EEPROMEmulation.cxx b/src/freertos_drivers/common/EEPROMEmulation.cxx index d220a4701..1f371d39f 100644 --- a/src/freertos_drivers/common/EEPROMEmulation.cxx +++ b/src/freertos_drivers/common/EEPROMEmulation.cxx @@ -55,6 +55,7 @@ EEPROMEmulation::EEPROMEmulation(const char *name, size_t file_size) HASSERT(file_size <= (SECTOR_SIZE >> 1)); // single block fit all the data HASSERT(file_size <= (1024 * 64 - 2)); // uint16 indexes, 0xffff reserved HASSERT(BLOCK_SIZE >= 4); // we don't support block sizes less than 4 bytes + HASSERT(BLOCK_SIZE <= MAX_BLOCK_SIZE); // this is how big our buffers are. HASSERT((BLOCK_SIZE % 4) == 0); // block size must be on 4 byte boundary } @@ -140,7 +141,7 @@ void EEPROMEmulation::write(unsigned int index, const void *buf, size_t len) if (lsa) { /* head, (unaligned) address */ - uint8_t data[BYTES_PER_BLOCK]; + uint8_t data[MAX_BLOCK_SIZE]; size_t write_size = len < (BYTES_PER_BLOCK - lsa) ? len : (BYTES_PER_BLOCK - lsa); read_fblock(index / BYTES_PER_BLOCK, data); @@ -159,7 +160,7 @@ void EEPROMEmulation::write(unsigned int index, const void *buf, size_t len) else if (len < BYTES_PER_BLOCK) { /* tail, (unaligned) address */ - uint8_t data[BYTES_PER_BLOCK]; + uint8_t data[MAX_BLOCK_SIZE]; read_fblock(index / BYTES_PER_BLOCK, data); if (memcmp(data, byte_data, len) != 0) @@ -174,7 +175,7 @@ void EEPROMEmulation::write(unsigned int index, const void *buf, size_t len) else { /* aligned data */ - uint8_t data[BYTES_PER_BLOCK]; + uint8_t data[MAX_BLOCK_SIZE]; read_fblock(index / BYTES_PER_BLOCK, data); if (memcmp(data, byte_data, BYTES_PER_BLOCK) != 0) @@ -207,7 +208,7 @@ void EEPROMEmulation::write_fblock(unsigned int index, const uint8_t data[]) if (availableSlots_) { /* still have room in this sector for at least one more write */ - uint32_t slot_data[BLOCK_SIZE / sizeof(uint32_t)]; + uint32_t slot_data[MAX_BLOCK_SIZE / sizeof(uint32_t)]; for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i) { slot_data[i] = (index << 16) | @@ -233,7 +234,7 @@ void EEPROMEmulation::write_fblock(unsigned int index, const uint8_t data[]) /* move any existing data over */ for (unsigned int fblock = 0; fblock < (file_size() / BYTES_PER_BLOCK); ++fblock) { - uint32_t slot_data[BLOCK_SIZE / sizeof(uint32_t)]; + uint32_t slot_data[MAX_BLOCK_SIZE / sizeof(uint32_t)]; if (fblock == index) // the new data to be written { for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i) @@ -246,7 +247,7 @@ void EEPROMEmulation::write_fblock(unsigned int index, const uint8_t data[]) else { /* this is old data we need to move over */ - uint8_t read_data[BYTES_PER_BLOCK]; + uint8_t read_data[MAX_BLOCK_SIZE]; if (!read_fblock(fblock, read_data)) { /* nothing to write, this is the default "erased" value */ @@ -307,7 +308,7 @@ void EEPROMEmulation::read(unsigned int offset, void *buf, size_t len) continue; } // Reads the block - uint8_t data[BYTES_PER_BLOCK]; + uint8_t data[MAX_BLOCK_SIZE]; for (unsigned int i = 0; i < BLOCK_SIZE / sizeof(uint32_t); ++i) { data[(i * 2) + 0] = (address[i] >> 0) & 0xFF; diff --git a/src/freertos_drivers/common/EEPROMEmulation.hxx b/src/freertos_drivers/common/EEPROMEmulation.hxx index 07e646d76..9631e70f1 100644 --- a/src/freertos_drivers/common/EEPROMEmulation.hxx +++ b/src/freertos_drivers/common/EEPROMEmulation.hxx @@ -153,6 +153,9 @@ protected: /** block size in bytes */ static const size_t BLOCK_SIZE; + /** Maximum byte size of a single block. */ + static constexpr unsigned MAX_BLOCK_SIZE = 16; + private: /** This function will be called after every write. The default * implementation is a weak symbol with an empty function. It is intended diff --git a/targets/freertos.armv7m/freertos_drivers/tivadriverlib/Makefile b/targets/freertos.armv7m/freertos_drivers/tivadriverlib/Makefile index 6b1ee77cf..c5f30658a 100644 --- a/targets/freertos.armv7m/freertos_drivers/tivadriverlib/Makefile +++ b/targets/freertos.armv7m/freertos_drivers/tivadriverlib/Makefile @@ -7,3 +7,4 @@ CFLAGS += -Dcodered include $(OPENMRNPATH)/etc/lib.mk +emac.o : CFLAGS += -Wno-address-of-packed-member From c2dbb0dab9d22520bae895282aa60838e50e36ae Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Wed, 29 Dec 2021 10:23:09 +0100 Subject: [PATCH 38/62] Implements BUS_OFF handling in the TivaCan driver. (#597) - Adds can bus status ioctl. - Flushes the TX queue when bus warning or bus error is observed by the controller. - Ensures that all message transmissions continue flowing (and are discarded) while we're in an error condition like bus off. --- src/freertos_drivers/ti/TivaCan.cxx | 59 ++++++++++++++++++++++------- src/freertos_drivers/ti/TivaDev.hxx | 9 ++++- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/freertos_drivers/ti/TivaCan.cxx b/src/freertos_drivers/ti/TivaCan.cxx index e8bc6f2f8..876af210f 100644 --- a/src/freertos_drivers/ti/TivaCan.cxx +++ b/src/freertos_drivers/ti/TivaCan.cxx @@ -33,15 +33,16 @@ #include -#include "inc/hw_types.h" -#include "inc/hw_memmap.h" -#include "inc/hw_ints.h" -#include "inc/hw_can.h" -#include "driverlib/rom.h" -#include "driverlib/rom_map.h" +#include "can_ioctl.h" #include "driverlib/can.h" #include "driverlib/interrupt.h" +#include "driverlib/rom.h" +#include "driverlib/rom_map.h" #include "driverlib/sysctl.h" +#include "inc/hw_can.h" +#include "inc/hw_ints.h" +#include "inc/hw_memmap.h" +#include "inc/hw_types.h" #include "nmranet_config.h" #include "TivaDev.hxx" @@ -55,10 +56,11 @@ static TivaCan *instances[2] = {NULL}; * @param interrupt interrupt number of this device */ TivaCan::TivaCan(const char *name, unsigned long base, uint32_t interrupt) - : Can(name), - base(base), - interrupt(interrupt), - txPending(false) + : Can(name) + , base(base) + , interrupt(interrupt) + , txPending(false) + , canState(CAN_STATE_STOPPED) { switch (base) { @@ -86,6 +88,19 @@ TivaCan::TivaCan(const char *name, unsigned long base, uint32_t interrupt) MAP_CANMessageSet(base, 1, &can_message, MSG_OBJ_TYPE_RX); } +// +// TCAN4550Can::ioctl() +// +int TivaCan::ioctl(File *file, unsigned long int key, unsigned long data) +{ + if (key == SIOCGCANSTATE) + { + *((can_state_t *)data) = canState; + return 0; + } + return -EINVAL; +} + /** Enable use of the device. */ void TivaCan::enable() @@ -96,12 +111,14 @@ void TivaCan::enable() // FreeRTOS compatibility. MAP_IntPrioritySet(interrupt, configKERNEL_INTERRUPT_PRIORITY); MAP_CANEnable(base); + canState = CAN_STATE_ACTIVE; } /** Disable use of the device. */ void TivaCan::disable() { + canState = CAN_STATE_STOPPED; MAP_IntDisable(interrupt); MAP_CANDisable(base); } @@ -110,7 +127,7 @@ void TivaCan::disable() */ void TivaCan::tx_msg() { - if (txPending == false) + if (txPending == false || canState != CAN_STATE_ACTIVE) { struct can_frame *can_frame; @@ -137,6 +154,11 @@ void TivaCan::tx_msg() txPending = true; } } + if (canState != CAN_STATE_ACTIVE) + { + txBuf->flush(); + txBuf->signal_condition(); + } } /** Common interrupt handler for all CAN devices. @@ -155,13 +177,20 @@ void TivaCan::interrupt_handler() { /* bus off error condition */ ++busOffCount; + canState = CAN_STATE_BUS_OFF; + + /* flush data in the tx pipeline */ + txBuf->flush(); + txPending = false; + txBuf->signal_condition_from_isr(); } if (status & CAN_STATUS_EWARN) { /* One of the error counters has exceded a value of 96 */ ++softErrorCount; - /* flush and data in the tx pipeline */ - MAP_CANMessageClear(base, 2); + canState = CAN_STATE_BUS_PASSIVE; + + /* flush data in the tx pipeline */ txBuf->flush(); txPending = false; txBuf->signal_condition_from_isr(); @@ -190,6 +219,8 @@ void TivaCan::interrupt_handler() else if (status == 1) { /* rx data received */ + canState = CAN_STATE_ACTIVE; + struct can_frame *can_frame; if (rxBuf->data_write_pointer(&can_frame)) { @@ -231,6 +262,8 @@ void TivaCan::interrupt_handler() { /* tx complete */ MAP_CANIntClear(base, 2); + canState = CAN_STATE_ACTIVE; + /* previous (zero copy) message from buffer no longer needed */ txBuf->consume(1); ++numTransmittedPackets_; diff --git a/src/freertos_drivers/ti/TivaDev.hxx b/src/freertos_drivers/ti/TivaDev.hxx index 87b396eb2..0ec2c080d 100644 --- a/src/freertos_drivers/ti/TivaDev.hxx +++ b/src/freertos_drivers/ti/TivaDev.hxx @@ -243,6 +243,13 @@ public: */ void interrupt_handler(); + /// 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; + private: void enable() override; /**< function to enable device */ void disable() override; /**< function to disable device */ @@ -251,7 +258,7 @@ private: unsigned long base; /**< base address of this device */ unsigned long interrupt; /**< interrupt of this device */ bool txPending; /**< transmission currently pending */ - + uint8_t canState; /**< current state of the CAN-bus. */ /** Default constructor. */ TivaCan(); From e874b9d7431672f69f85471b151117c1fa9348fc Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 1 Jan 2022 19:11:58 +0100 Subject: [PATCH 39/62] Fixes bug in short circuiting toplevel target compilation. --- etc/prog.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/prog.mk b/etc/prog.mk index ce67948ad..4019b914b 100644 --- a/etc/prog.mk +++ b/etc/prog.mk @@ -179,7 +179,7 @@ endif # in the core target libraries. $(LIBDIR)/timestamp: $(LIBBUILDDEP) $(BUILDDIRS) ifdef FLOCKPATH - @$(FLOCKPATH)/flock $(OPENMRNPATH)/targets/$(TARGET) -c "if [ $< -ot $(LIBBUILDDEP) -o ! -f $(LIBBUILDDEP) ] ; then $(MAKE) -C $(OPENMRNPATH)/targets/$(TARGET) all ; else echo short-circuiting core target build ; fi" + @$(FLOCKPATH)/flock $(OPENMRNPATH)/targets/$(TARGET) -c "if [ $@ -ot $(LIBBUILDDEP) -o ! -f $(LIBBUILDDEP) ] ; then $(MAKE) -C $(OPENMRNPATH)/targets/$(TARGET) all ; else echo short-circuiting core target build, because $@ is older than $(LIBBUILDDEP) ; fi" else @echo warning: no flock support. If you use make -jN then you can run into occasional compilation errors when multiple makes are progressing in the same directory. Usually re-running make solved them. $(MAKE) -C $(OPENMRNPATH)/targets/$(TARGET) all From 56c9a787d0aa01d57f8add2737bd0e58e74b918a Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 1 Jan 2022 21:19:07 +0100 Subject: [PATCH 40/62] Adds FlashFile (#598) FlashFile is a file driver for laying out storage directly in flash, taking in some limitations on what form of writes can be performed. FlashFile supports both SPIFlash driver, as well as on-chip flash (although no on-chip flash examples exist at this point). --- src/freertos_drivers/common/DeviceFile.hxx | 8 +- src/freertos_drivers/common/FlashFile.hxx | 163 +++++++++++++++++++++ src/freertos_drivers/common/SPIFlash.hxx | 14 ++ 3 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 src/freertos_drivers/common/FlashFile.hxx diff --git a/src/freertos_drivers/common/DeviceFile.hxx b/src/freertos_drivers/common/DeviceFile.hxx index 7ca27c44f..2d1c5fe2b 100644 --- a/src/freertos_drivers/common/DeviceFile.hxx +++ b/src/freertos_drivers/common/DeviceFile.hxx @@ -25,7 +25,9 @@ * POSSIBILITY OF SUCH DAMAGE. * * \file DeviceFile.hxx - * This implements a common device file abstraction. + * + * Base class for implementing block devices and other file drivers which can + * be seeked and addressed by absolute byte offsets. * * @author Stuart W. Baker * @date 16 July 2016 @@ -38,7 +40,9 @@ #include "Devtab.hxx" -/** Common base class for all DeviceFile access. +/** + * Base class for implementing block devices and other file drivers which can + * be seeked and addressed by absolute byte offsets. */ class DeviceFile : public Node { diff --git a/src/freertos_drivers/common/FlashFile.hxx b/src/freertos_drivers/common/FlashFile.hxx new file mode 100644 index 000000000..21c4f983d --- /dev/null +++ b/src/freertos_drivers/common/FlashFile.hxx @@ -0,0 +1,163 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file FlashFile.hxx + * + * File with backing data in flash (on-chip or SPI) in direct layout. This file + * has restrictions on how it can be written. + * + * @author Balazs Racz + * @date 30 Dec 2021 + */ + +#ifndef _FREERTOS_DRIVERS_COMMON_FLASHFILE_HXX_ +#define _FREERTOS_DRIVERS_COMMON_FLASHFILE_HXX_ + +#include +#include +#include + +#include "freertos_drivers/common/DeviceFile.hxx" + +/// FlashFile is a driver for a single file that is backed by flash. +/// Instantiations may use serial flash (using SPIFlash) or internal flash. +/// +/// There are limitations on writes. +/// +/// - A sequential write of the file with no seeks from the beginning to the +/// end will work. Whenever the first byte of a sector is written, the entire +/// sector will be erased. +/// +/// - If the file is opened with O_TRUNC, then all sectors of the file are +/// erased. +/// +/// - The file does not remember its size. fstat always returns the maximum +/// size. +template class FlashFile : public DeviceFile +{ +public: + /// Constructor. + /// @param name what should be the name of this file be (to pass to ::open). + /// @param flash accessor object to the backing flash. One such object can + /// be used for multiple FlashFiles. The flash object shall do locking + /// internally. + /// @param address where does the data of this file start. Must be aligned + /// on a sector boundary on the flash device. The unit is whatever address + /// the flash driver understands. + /// @param size maximum size of this file on flash. + FlashFile(const char *name, FLASH *flash, size_t address, size_t size) + : DeviceFile(name) + , flash_(flash) + , flashStart_(address) + , size_(size) + { + auto start_sec = flash_->next_sector_address(flashStart_); + // Beginning of the file must be on a sector boundary. + HASSERT(start_sec == flashStart_); + } + + /// Overrides behavior of open for O_TRUNC. + int open(File *file, const char *path, int flags, int mode) override + { + if ((mode & O_WRONLY) && (flags & O_TRUNC)) + { + // erase entire file. + flash_->erase(flashStart_, size_); + flags &= ~O_TRUNC; + } + return DeviceFile::open(file, path, flags, mode); + } + + /// Implements querying the file size. + int fstat(File *file, struct stat *stat) override + { + DeviceFile::fstat(file, stat); + stat->st_size = size_; + return 0; + } + + /// Write to the flash. + /// @param index index within the file address space to start write + /// @param buf data to write + /// @param len length in bytes of data to write + /// @return number of bytes written upon success, -errno upon failure + ssize_t write(unsigned int index, const void *buf, size_t len) override + { + if (index >= size_) + { + return 0; // EOF + } + if (len > size_ - index) + { + len = size_ - index; + } + size_t addr = flashStart_ + index; + size_t sec_addr = flash_->next_sector_address(addr); + if (addr == sec_addr) + { + /// Writing at the beginning of a sector. Need an erase. + size_t next_sec = flash_->next_sector_address(sec_addr + 1); + flash_->erase(sec_addr, next_sec - sec_addr); + sec_addr = next_sec; + } + if ((sec_addr - addr) < len) + { + len = sec_addr - addr; + } + flash_->write(addr, buf, len); + return len; + } + + /// Read from the flash. + /// @param index index within DeviceFile address space to start read + /// @param buf location to post read data + /// @param len length in bytes of data to read + /// @return number of bytes read upon success, -errno upon failure + ssize_t read(unsigned int index, void *buf, size_t len) override + { + if (index >= size_) + { + return 0; // EOF + } + if (len > size_ - index) + { + len = size_ - index; + } + size_t addr = flashStart_ + index; + flash_->read(addr, buf, len); + return len; + } + +private: + /// Accessor to the flash device. + FLASH *flash_; + /// Offset where our file start on flash. + size_t flashStart_; + /// How many bytes our file is on flash. + size_t size_; +}; + +#endif diff --git a/src/freertos_drivers/common/SPIFlash.hxx b/src/freertos_drivers/common/SPIFlash.hxx index c32d21d95..73f68c5b8 100644 --- a/src/freertos_drivers/common/SPIFlash.hxx +++ b/src/freertos_drivers/common/SPIFlash.hxx @@ -40,6 +40,8 @@ #include #include +#include "utils/logging.h" + class OSMutex; class SPI; @@ -109,6 +111,8 @@ public: : cfg_(cfg) , lock_(lock) { + /// This ensures that the sector size is a power of two. + HASSERT((cfg->sectorSize_ & (cfg->sectorSize_ - 1)) == 0); } /// @return the configuration. @@ -134,6 +138,16 @@ public: /// @param len how many bytes to read void read(uint32_t addr, void *buf, size_t 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. + uint32_t next_sector_address(uint32_t addr) + { + return (addr + cfg_->sectorSize_ - 1) & ~(cfg_->sectorSize_ - 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). From 52f305faaad616d8b4f0a31d2512465e4bae2210 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 2 Jan 2022 00:32:41 +0100 Subject: [PATCH 41/62] Adds binary release script. (#599) Adds script (in the makefile) to compile a binary release for OpenMRN. The binary release contains the standalone applications that are useful outside of OpenMRN. Currently this list is as follows: - hub - bootloader_client - memconfig_utils - send_datagram The binary release script compiles these applications using the host g++ compiler, and packages it as a zip file. Also added is a release-js script which compiles two applications using node.js package for win.exe and macos binaries. These are: - js_hub - bootloader_client === * Adds binary release script. Adds script (in the makefile) to compile a binary release for OpenMRN. The binary release contains the standalone applications that are useful outside of OpenMRN. Currently this list is as follows: - hub - bootloader_client - memconfig_utils - send_datagram The binary release script compiles these applications using the host g++ compiler, and packages it as a zip file. * renames the zipfile to "applications.OS.CPU.zip" * Adds javascript release binary targets. Adds license.txt to the release zips. Makes the js_hub be compatible with node.js pkg tool. * Adds documentation on how to do a release. * Adds documentation of release numbers. --- Makefile | 34 ++++++++ RELEASE.md | 85 +++++++++++++++++++ .../js_hub/targets/js.emscripten/Makefile | 14 ++- .../js_hub/targets/js.emscripten/package.json | 8 +- bin/.gitignore | 1 + etc/release.mk | 63 ++++++++++++++ 6 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 RELEASE.md create mode 100644 etc/release.mk diff --git a/Makefile b/Makefile index 09ee8d798..e8e7a6e18 100644 --- a/Makefile +++ b/Makefile @@ -38,3 +38,37 @@ js-tests: $(MAKE) -C targets/js.emscripten run-tests alltests: tests llvm-tests + +release-clean: + $(MAKE) -C targets/linux.x86 clean + +RELNAME=$(shell uname -sm | tr ' A-Z' '.a-z') +RELDIR=$(OPENMRNPATH)/bin/release/staging-$(RELNAME) +JSRELDIR=$(OPENMRNPATH)/bin/release/staging-js + +include $(OPENMRNPATH)/etc/release.mk + +# These are the applications that are packaged into the binary release. +$(call RELEASE_BIN_template,hub,applications/hub/targets/linux.x86) +$(call RELEASE_BIN_template,memconfig_utils,applications/memconfig_utils/targets/linux.x86) +$(call RELEASE_BIN_template,bootloader_client,applications/bootloader_client/targets/linux.x86) +$(call RELEASE_BIN_template,send_datagram,applications/send_datagram/targets/linux.x86) + +$(call RELEASE_JS_template,openmrn-bootloader-client,applications/bootloader_client/targets/js.emscripten) +$(call RELEASE_JS_template,openmrn-hub,applications/js_hub/targets/js.emscripten) + +release-bin: + rm -rf $(RELDIR)/* + mkdir -p $(RELDIR) + +$(MAKE) -C . release-bin-all + cp LICENSE.md $(RELDIR)/LICENSE.txt + cd $(RELDIR); zip -9r ../applications.$(RELNAME).zip . + +release-js: + rm -rf $(JSRELDIR)/* + mkdir -p $(JSRELDIR) + +$(MAKE) -C . release-js-all + cp LICENSE.md $(JSRELDIR)/win/LICENSE.txt + cp LICENSE.md $(JSRELDIR)/macos/LICENSE.txt + cd $(JSRELDIR)/win; zip -9r ../../applications.win.zip . + cd $(JSRELDIR)/macos; zip -9r ../../applications.macos.zip . diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..c2806e594 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,85 @@ +# Release process + +This document details how releases need to be built and published for OpenMRN. + +## Purpose and content of releases + +Releases fulfill two purposes: + +- Precompiled binaries are made available for download. This is important for + those that do not have facilities or the experience to compile OpenMRN + applications from source. + +- (At a later point) a packaged library is made available with include headers + and `*.a` files that enable importing OpenMRN into a different compilation + environment, such as various IDEs for embedded development. + +The following is out of scope of this documentation: + +- Releases for OpenMRNLite (Arduino compatible library) are documented in the + [arduino/RELEASE.md](arduino/RELEASE.md) file. + +## Requirements for building releases + +- You need to be able to compile OpenMRN binaries. You need a linux host (or + VM). The necessary packages have to be installed: + + - beyond standard `g++` you will need `sudo apt-get install + libavahi-client-dev` + +- You need both a linux.x86_64 and a raspberry pi host. + +- On the linux host you should install node.js, npm, emscripten, and the + packager: + + `sudo npm install -g pkg` + +## Release numbering + +Release numbers are marked as major.minor.patch, such as `v2.10.1`. + +- Major release numbers are incremented once per year (2020 is 0, 2021 is 1, + 2022 is 2, ...). + +- Minor release numbers are 10, 20, 30, 40 for given quarters, then incremented + by one if there are multiple releases built within a quarter. + +- Patch release numbers start at 1, and are only incremented if the same + release needs to be re-built with a patch. + + +## How to build + +All of the make commands here need to be run in the openmrn directory +(toplevel). + +1. Start with a clean checkout. Run `make release-clean`. + +2. On the linux.x86_64 host, run + + `make -j5 release-bin` + +3. Copy `openmrn/bin/release/applications.linux.x86_64.zip` as one of the + artifacts. + +3. On the raspberry pi host, do the same: + + `make release-clean` + + `make -j4 release-bin` + +4. Copy `openmrn/bin/release/applications.linux.armv7l.zip` as one of the + artifacts. Rename it to `applications.linux.armv7l-raspberry-pi.zip` + +5. On the linux.x86_64 host, build the javascript binaries. Run + + `make -j5 release-js` + +6. Copy `openmrn/bin/release/applications.win.zip` and + `openmrn/bin/release/applications.macos.zip`. + +7. On the OpenMRN GitHub project, select Releases, create a new release, select + create a new tag in the form of `release-v2.10.1`. Upload the release + artifacts that you collected. + +8. Publish the release. diff --git a/applications/js_hub/targets/js.emscripten/Makefile b/applications/js_hub/targets/js.emscripten/Makefile index 48571997a..d18ae3837 100644 --- a/applications/js_hub/targets/js.emscripten/Makefile +++ b/applications/js_hub/targets/js.emscripten/Makefile @@ -1,3 +1,15 @@ -include ../../config.mk include $(OPENMRNPATH)/etc/prog.mk -LDFLAGS += --bind -s DEMANGLE_SUPPORT=1 +LDFLAGS += --bind -s DEMANGLE_SUPPORT=1 -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-wasm: + rm -f $(EXECUTABLE).{wasm,wast} diff --git a/applications/js_hub/targets/js.emscripten/package.json b/applications/js_hub/targets/js.emscripten/package.json index 1cf8d4da6..4deec3cf7 100644 --- a/applications/js_hub/targets/js.emscripten/package.json +++ b/applications/js_hub/targets/js.emscripten/package.json @@ -1,9 +1,15 @@ { - "name": "openmrn-js-hub", + "name": "openmrn-hub", "version": "0.9.1", "dependencies": { "websocket": "*", "ecstatic": "*", "serialport": "*" + }, + "bin": "js_hub.js", + "pkg": { + "assets": [ + "./node_modules/@serialport/bindings/build/Release/bindings.node" + ] } } diff --git a/bin/.gitignore b/bin/.gitignore index 6a1415f2c..c54124fe8 100644 --- a/bin/.gitignore +++ b/bin/.gitignore @@ -1 +1,2 @@ !pic32_change_lma.exe +release diff --git a/etc/release.mk b/etc/release.mk new file mode 100644 index 000000000..5e2a3bac1 --- /dev/null +++ b/etc/release.mk @@ -0,0 +1,63 @@ +# Helper makefile for building releases of OpenMRN. + + +### Call this template for each binary application that should be built for a +### release. The call site should be in the toplevel makefile. +### +### Arguments: app-name target-path +### +### example: $(call RELEASE_BIN_template,hub,applications/hub/targets/linux.x86) +define RELEASE_BIN_template_helper + +release-bin-all: $(RELDIR)/$(1) + +$(RELDIR)/$(1): $(2)/$(1) + strip -o $$@ $$< + +$(2)/$(1): + $(MAKE) -C $(2) + +release-clean: release-clean-$(1) + +release-clean-$(1): + $(MAKE) -C $(2) clean rclean + +endef + +define RELEASE_BIN_template +$(eval $(call RELEASE_BIN_template_helper,$(1),$(2))) +endef + + +### Call this template for each JS application that should be built for a +### release. The call site should be in the toplevel makefile. +### +### Arguments: app-name target-path +### +### example: $(call RELEASE_JS_template,openmrn-bootloader-client,applications/bootloader_client/targets/js.emscripten) +define RELEASE_JS_template_helper + +release-js-all: $(JSRELDIR)/win/$(1)-win.exe + +$(JSRELDIR)/win/$(1)-win.exe: $(2)/$(1)-win.exe + mkdir -p $(JSRELDIR)/win $(JSRELDIR)/macos + cp $(2)/$(1)-win.exe $(JSRELDIR)/win/$(1)-win.exe + cp $(2)/$(1)-macos $(JSRELDIR)/macos/$(1)-macos + +$(2)/$(1)-win.exe: + +$(MAKE) -C $(2) + +$(MAKE) -C $(2) release + +release-clean: release-clean-$(1) + +release-clean-$(1): + $(MAKE) -C $(2) clean rclean + rm -rf $(2)/$(1)-win.exe $(2)/$(1)-macos $(2)/$(1)-linux + +endef + +define RELEASE_JS_template +$(eval $(call RELEASE_JS_template_helper,$(1),$(2))) +endef + + From 321235a83c11bd57c25c39d55b576ab0ce6a4202 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 2 Jan 2022 00:37:26 +0100 Subject: [PATCH 42/62] Fixes recursive make bugs. --- etc/release.mk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/release.mk b/etc/release.mk index 5e2a3bac1..82b4ce1fc 100644 --- a/etc/release.mk +++ b/etc/release.mk @@ -15,12 +15,12 @@ $(RELDIR)/$(1): $(2)/$(1) strip -o $$@ $$< $(2)/$(1): - $(MAKE) -C $(2) + +$(MAKE) -C $(2) release-clean: release-clean-$(1) release-clean-$(1): - $(MAKE) -C $(2) clean rclean + +$(MAKE) -C $(2) clean rclean endef @@ -51,7 +51,7 @@ $(2)/$(1)-win.exe: release-clean: release-clean-$(1) release-clean-$(1): - $(MAKE) -C $(2) clean rclean + +$(MAKE) -C $(2) clean rclean rm -rf $(2)/$(1)-win.exe $(2)/$(1)-macos $(2)/$(1)-linux endef From 87af9325b20398bd1f8a56119079ab4f29b21369 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 8 Jan 2022 17:42:43 +0100 Subject: [PATCH 43/62] Updates distcc documentation. --- DISTCC.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DISTCC.md b/DISTCC.md index 0d7726b4f..3872c7de0 100644 --- a/DISTCC.md +++ b/DISTCC.md @@ -50,6 +50,8 @@ link. The linking steps then happen locally. what you want.) 4. edit `src/serve.c`. Find where it checks for `"-specs="`, and disable that check. +4. edit `src/filename.c`. Find where it checks for `"cxx"`, and add another + line so that it supports `"cxxtest"` as well. 5. run through the compile steps of distcc, see the INSTALL file. These are normally: ``` From beb576cc7bc0035c17a5a255107023db94376d07 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 8 Jan 2022 17:43:20 +0100 Subject: [PATCH 44/62] Avoids adding the system directory files to the dependency outputs. --- etc/core_test.mk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/core_test.mk b/etc/core_test.mk index 1485137eb..328360c64 100644 --- a/etc/core_test.mk +++ b/etc/core_test.mk @@ -50,13 +50,13 @@ $(TESTBINS): %.test$(EXTENTION) : %.test.o $(TESTOBJSEXTRA) $(LIBDIR)/timestamp -include $(TESTOBJSEXTRA:.o=.d) $(TESTOBJS): %.test.o : $(SRCDIR)/%.cxxtest - $(CXX) $(CXXFLAGS) -MD -MF $*.dtest -x c++ $< -o $@ + $(CXX) $(CXXFLAGS) -MMD -MF $*.dtest -MT $@ -x c++ $< -o $@ gtest-all.o : %.o : $(GTESTSRCPATH)/src/%.cc - $(CXX) $(CXXFLAGS) -Wno-uninitialized -I$(GTESTPATH) -I$(GTESTSRCPATH) -MD -MF $*.d $< -o $@ + $(CXX) $(CXXFLAGS) -Wno-uninitialized -I$(GTESTPATH) -I$(GTESTSRCPATH) -MMD -MF $*.d $< -o $@ gmock-all.o : %.o : $(GMOCKSRCPATH)/src/%.cc - $(CXX) $(CXXFLAGS) -I$(GMOCKPATH) -I$(GMOCKSRCPATH) -MD -MF $*.d $< -o $@ + $(CXX) $(CXXFLAGS) -I$(GMOCKPATH) -I$(GMOCKSRCPATH) -MMD -MF $*.d $< -o $@ # This target takes the test binary output and compares the md5sum against the # md5sum of the previous run. If the md5sum of the test binary didn't change, From 80ed3980943abc6099fad3263b5bd82141d9e7c0 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 9 Jan 2022 11:43:20 +0100 Subject: [PATCH 45/62] Removes dependency on usleep from header. --- src/utils/SocketClient.hxx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/utils/SocketClient.hxx b/src/utils/SocketClient.hxx index 4a0b33eed..f3c7d494a 100644 --- a/src/utils/SocketClient.hxx +++ b/src/utils/SocketClient.hxx @@ -35,11 +35,6 @@ #ifndef _UTILS_SOCKET_CLIENT_HXX_ #define _UTILS_SOCKET_CLIENT_HXX_ -/// @todo(balazs.racz) remove this by moving all calls to usleep to the .cxx file. -#ifndef _DEFAULT_SOURCE -#define _DEFAULT_SOURCE -#endif - #include #include #ifndef ESP32 // this doesn't exist on the ESP32 with LWiP @@ -52,6 +47,7 @@ #include "executor/StateFlow.hxx" #include "executor/Timer.hxx" #include "os/MDNS.hxx" +#include "os/sleep.h" #include "utils/Atomic.hxx" #include "utils/SocketClientParams.hxx" #include "utils/format_utils.hxx" @@ -170,7 +166,7 @@ public: } while (!is_terminated()) { - usleep(1000); + microsleep(1000); } } From 9ed634f2eb81870f256a5ac5fecfd521f68f7c4c Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 15 Jan 2022 10:57:15 +0100 Subject: [PATCH 46/62] Minor fixes to SNIP client and SNIP handler: (#600) - Adds proper shutdown to SNIP client. This is needed for unittesting purposes. - Fixes crashing bug in SNIP client when returned payload is empty. - Moves SNIP handler's static descriptor to a separate compilation unit to avoid it being unexpectedly linked into binaries. This is not only flash space wasted, but also presents an unexpected dependency on SNIP_DYNAMIC_FILENAME being available. --- src/openlcb/SNIPClient.hxx | 12 ++++++ src/openlcb/SimpleNodeInfo.cxx | 24 +++-------- src/openlcb/SimpleNodeInfoResponse.cxx | 58 ++++++++++++++++++++++++++ src/openlcb/sources | 1 + 4 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 src/openlcb/SimpleNodeInfoResponse.cxx diff --git a/src/openlcb/SNIPClient.hxx b/src/openlcb/SNIPClient.hxx index 4d87ab99c..b2264d71e 100644 --- a/src/openlcb/SNIPClient.hxx +++ b/src/openlcb/SNIPClient.hxx @@ -39,6 +39,7 @@ #include "executor/CallableFlow.hxx" #include "openlcb/Defs.hxx" #include "openlcb/If.hxx" +#include "os/sleep.h" namespace openlcb { @@ -88,6 +89,17 @@ public: { } + /// Flushes the pending timed operations. + void shutdown() + { + while (!is_waiting()) + { + service()->executor()->sync_run( + [this]() { timer_.ensure_triggered(); }); + microsleep(500); + } + } + Action entry() override { request()->resultCode = SNIPClientRequest::OPERATION_PENDING; diff --git a/src/openlcb/SimpleNodeInfo.cxx b/src/openlcb/SimpleNodeInfo.cxx index eb627f748..8776e7421 100644 --- a/src/openlcb/SimpleNodeInfo.cxx +++ b/src/openlcb/SimpleNodeInfo.cxx @@ -43,24 +43,6 @@ namespace openlcb extern const SimpleNodeStaticValues __attribute__((weak)) SNIP_STATIC_DATA = { 4, "OpenMRN", "Undefined model", "Undefined HW version", "0.9"}; -const SimpleInfoDescriptor SNIPHandler::SNIP_RESPONSE[] = { - {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}, -#if OPENMRN_HAVE_POSIX_FD - {SimpleInfoDescriptor::FILE_LITERAL_BYTE, 2, 0, SNIP_DYNAMIC_FILENAME}, - {SimpleInfoDescriptor::FILE_C_STRING, 63, 1, SNIP_DYNAMIC_FILENAME}, - {SimpleInfoDescriptor::FILE_C_STRING, 64, 64, SNIP_DYNAMIC_FILENAME}, -#else - /// @todo(balazs.racz) Add eeprom support to arduino. - {SimpleInfoDescriptor::LITERAL_BYTE, 2, 0, nullptr}, - {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, - {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, -#endif - {SimpleInfoDescriptor::END_OF_DATA, 0, 0, 0}}; - void init_snip_user_file(int fd, const char *user_name, const char *user_description) { @@ -86,7 +68,7 @@ void init_snip_user_file(int fd, const char *user_name, } static size_t find_string_at(const openlcb::Payload& payload, size_t start_pos, string* output) { - if (start_pos == string::npos) { + if (start_pos >= payload.size()) { output->clear(); return start_pos; } @@ -103,6 +85,10 @@ void decode_snip_response( const openlcb::Payload &payload, SnipDecodedData *output) { output->clear(); + if (payload.empty()) + { + return; + } char sys_ver = payload[0]; size_t pos = 1; pos = find_string_at(payload, pos, &output->manufacturer_name); diff --git a/src/openlcb/SimpleNodeInfoResponse.cxx b/src/openlcb/SimpleNodeInfoResponse.cxx new file mode 100644 index 000000000..17df91d55 --- /dev/null +++ b/src/openlcb/SimpleNodeInfoResponse.cxx @@ -0,0 +1,58 @@ +/** \copyright + * Copyright (c) 2022, 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 SimpleNodeInfoResponse.cxx + * + * Response descriptor for the Simple Node Ident Info protocol handler. + * + * @author Balazs Racz + * @date 3 Jan 2022 + */ + +#include "openlcb/SimpleNodeInfo.hxx" + +namespace openlcb +{ + +const SimpleInfoDescriptor SNIPHandler::SNIP_RESPONSE[] = { + {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}, +#if OPENMRN_HAVE_POSIX_FD + {SimpleInfoDescriptor::FILE_LITERAL_BYTE, 2, 0, SNIP_DYNAMIC_FILENAME}, + {SimpleInfoDescriptor::FILE_C_STRING, 63, 1, SNIP_DYNAMIC_FILENAME}, + {SimpleInfoDescriptor::FILE_C_STRING, 64, 64, SNIP_DYNAMIC_FILENAME}, +#else + /// @todo(balazs.racz) Add eeprom support to arduino. + {SimpleInfoDescriptor::LITERAL_BYTE, 2, 0, nullptr}, + {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, + {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, +#endif + {SimpleInfoDescriptor::END_OF_DATA, 0, 0, 0}}; + +} diff --git a/src/openlcb/sources b/src/openlcb/sources index 5e8e4a01a..b7b35e734 100644 --- a/src/openlcb/sources +++ b/src/openlcb/sources @@ -39,6 +39,7 @@ CXXSRCS += \ DatagramTcp.cxx \ MemoryConfig.cxx \ SimpleNodeInfo.cxx \ + SimpleNodeInfoResponse.cxx \ SimpleNodeInfoMockUserFile.cxx \ SimpleStack.cxx \ TractionTestTrain.cxx \ From 456945f3b1b01561e93eca604b89c4b769f9edb0 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 15 Jan 2022 10:58:08 +0100 Subject: [PATCH 47/62] Fixes sequence problem when more than one CDI file gets rendered. (#601) * Fixes sequence problem when more than one CDI file gets rendered. Due to specific rules on partial template specializations, the C++ compiler would emit an error depending on which order the RENDER_CDI invocatoins were listed in the source file of config.hxx, incliuding being sensitive to different order of #include lines. This is fixed by allowing only every second integer to be partially specialized. * Remove dead code. --- src/openlcb/CompileCdiMain.cxx | 12 ++---------- src/openlcb/ConfigRepresentation.hxx | 4 ++-- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/openlcb/CompileCdiMain.cxx b/src/openlcb/CompileCdiMain.cxx index 92b3069c6..f8c391c98 100644 --- a/src/openlcb/CompileCdiMain.cxx +++ b/src/openlcb/CompileCdiMain.cxx @@ -63,16 +63,8 @@ int main(int argc, char *argv[]) )"); } - render_all_cdi<10>(); - /* render_all_cdi<9>(); - render_all_cdi<8>(); - render_all_cdi<7>(); - render_all_cdi<6>(); - render_all_cdi<5>(); - render_all_cdi<4>(); - render_all_cdi<3>(); - render_all_cdi<2>(); - render_all_cdi<1>();*/ + // Internally calls all smaller numbered instances all the way down to 1. + render_all_cdi<20>(); std::vector event_offsets; openlcb::ConfigDef def(0); diff --git a/src/openlcb/ConfigRepresentation.hxx b/src/openlcb/ConfigRepresentation.hxx index 18caf02b4..100991f65 100644 --- a/src/openlcb/ConfigRepresentation.hxx +++ b/src/openlcb/ConfigRepresentation.hxx @@ -502,11 +502,11 @@ template <> inline void render_all_cdi<0>() * @param N is a unique integer between 2 and 10 for the invocation. */ #define RENDER_CDI(NS, TYPE, NAME, N) \ - template <> inline void render_all_cdi() \ + template <> inline void render_all_cdi<2 * N>() \ { \ NS::TYPE def(0); \ render_cdi_helper(def, #NS, NAME); \ - render_all_cdi(); \ + render_all_cdi<2 * N - 1>(); \ } #endif // _OPENLCB_CONFIGREPRESENTATION_HXX_ From ea2fd25bff7def20f8390665139696882e1e9bec Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 29 Jan 2022 22:25:45 +0100 Subject: [PATCH 48/62] JS.emscripten: adds connect callbacks to the ports. (#602) This allows delaying the stack start until the asynchronous connections succeed. --- src/utils/JSHubPort.cxx | 11 +++++++++++ src/utils/JSSerialPort.hxx | 13 +++++++++++-- src/utils/JSTcpClient.hxx | 16 ++++++++++++++-- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/utils/JSHubPort.cxx b/src/utils/JSHubPort.cxx index 6aa7e2768..7a3dd923d 100644 --- a/src/utils/JSHubPort.cxx +++ b/src/utils/JSHubPort.cxx @@ -34,7 +34,18 @@ #ifdef __EMSCRIPTEN__ +#include + extern int JSHubPort_debug_port_num; int JSHubPort_debug_port_num = 0; +/// Invokes a function pointer. +extern "C" void __attribute__((used)) invoke_fnp(std::function *fp) +{ + if (fp && *fp) + { + (*fp)(); + } +} + #endif // __EMSCRIPTEN__ diff --git a/src/utils/JSSerialPort.hxx b/src/utils/JSSerialPort.hxx index b09593923..461c14770 100644 --- a/src/utils/JSSerialPort.hxx +++ b/src/utils/JSSerialPort.hxx @@ -46,8 +46,14 @@ class JSSerialPort { public: - JSSerialPort(CanHubFlow *hflow, string device) + /// Constructor + /// @param hflow the CAN hub object in the local binary to add this port to. + /// @param device the serial device name (see list_ports output) + /// @param cb will be invoked when the connection succeeds + JSSerialPort(CanHubFlow *hflow, string device, + std::function cb = []() {}) : canHub_(hflow) + , connectCallback_(std::move(cb)) { string script = "Module.serial_device = '" + device + "';\n"; emscripten_run_script(script.c_str()); @@ -96,9 +102,10 @@ public: client_port.abandon(); }); c.on('data', function(data) { client_port.recv(data.toString()); }); + _invoke_fnp($1); }); }, - (unsigned long)canHub_); + (unsigned long)canHub_, (unsigned long)&connectCallback_); } static void list_ports() { @@ -119,6 +126,8 @@ public: private: CanHubFlow *canHub_; + /// This function will be invoked when the connection succeeds. + std::function connectCallback_; }; #endif // __EMSCRIPTEN__ diff --git a/src/utils/JSTcpClient.hxx b/src/utils/JSTcpClient.hxx index 30ed27c31..54046ce91 100644 --- a/src/utils/JSTcpClient.hxx +++ b/src/utils/JSTcpClient.hxx @@ -47,11 +47,20 @@ class JSTcpClient { public: - JSTcpClient(CanHubFlow *hflow, string host, int port) + /// Constructor + /// @param hflow the CAN hub object in the local binary to add this port to. + /// @param host the IP address or name of the remote host + /// @param port the TCP port number to connect to + /// @param cb will be invoked when the connection succeeds + JSTcpClient( + CanHubFlow *hflow, string host, int port, + std::function cb = []() {}) : canHub_(hflow) + , connectCallback_(std::move(cb)) { string script = "Module.remote_server = '" + host + "';\n"; emscripten_run_script(script.c_str()); + EM_ASM_( { var net = require('net'); @@ -73,13 +82,16 @@ public: client_port.abandon(); }); c.on('data', function(data) { client_port.recv(data); }); + _invoke_fnp($2); }); }, - port, (unsigned long)canHub_); + port, (unsigned long)canHub_, (unsigned long)&connectCallback_); } private: CanHubFlow *canHub_; + /// This function will be invoked when the connection succeeds. + std::function connectCallback_; }; #endif // __EMSCRIPTEN__ From da9188fdb5fcf635993e0616dc2d5042af265a3f Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 29 Jan 2022 22:37:40 +0100 Subject: [PATCH 49/62] Switched GcPacketPrinter to use stderr. (#603) This object is used by the hub under linux to print the packets. Unfortunately stdout is running in buffered mode typically, which makes it impossible to run hub processes in the background and still get a log of data. --- src/utils/GridConnectHub.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/GridConnectHub.cxx b/src/utils/GridConnectHub.cxx index 736397855..b148d06ae 100644 --- a/src/utils/GridConnectHub.cxx +++ b/src/utils/GridConnectHub.cxx @@ -357,15 +357,15 @@ struct GcPacketPrinter::Impl : public CanHubPortInterface gettimeofday(&tv, nullptr); struct tm t; localtime_r(&tv.tv_sec, &t); - printf("%04d-%02d-%02d %02d:%02d:%02d:%06ld [%p] ", + fprintf(stderr, "%04d-%02d-%02d %02d:%02d:%02d:%06ld [%p] ", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, (long)tv.tv_usec, message->data()->skipMember_); #endif } - printf("%s", str); + fprintf(stderr, "%s", str); if (config_gc_generate_newlines() != 1) { - printf("\n"); + fprintf(stderr, "\n"); } } From 7b793c8dd0fb77e3c77fd2ba23c433d9f6d65a96 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 29 Jan 2022 22:38:57 +0100 Subject: [PATCH 50/62] Fixes Tiva GPIO and build (#604) The TivaGPIO implementation was missing a volatile qualification on the input read function. - Refactors the three similar GPIO classes to remove duplicate code. - Adds necessary volatile qualification using the standard TivaWare syntax. Misc: - Fixes a build problem when using tivaware in bare.armv7m target. --- src/freertos_drivers/sources | 7 +- src/freertos_drivers/ti/TivaGPIO.hxx | 146 +++++++++------------------ 2 files changed, 52 insertions(+), 101 deletions(-) diff --git a/src/freertos_drivers/sources b/src/freertos_drivers/sources index 68bd1dcd8..a570b39e6 100644 --- a/src/freertos_drivers/sources +++ b/src/freertos_drivers/sources @@ -69,7 +69,12 @@ CXXSRCS += c++_operators.cxx endif ifeq ($(TARGET),bare.armv7m) -SUBDIRS += cc32xxsdk ti_grlib stm32cubel431xx stm32cubel432xx +SUBDIRS += cc32xxsdk ti_grlib stm32cubel431xx stm32cubel432xx + +ifdef BUILDTIVAWARE +SUBDIRS += tivadriverlib tivausblib +endif + endif ifeq ($(TARGET),bare.armv6m) diff --git a/src/freertos_drivers/ti/TivaGPIO.hxx b/src/freertos_drivers/ti/TivaGPIO.hxx index ce7321a20..6ca080cac 100644 --- a/src/freertos_drivers/ti/TivaGPIO.hxx +++ b/src/freertos_drivers/ti/TivaGPIO.hxx @@ -125,8 +125,7 @@ public: } private: - template friend struct GpioOutputPin; - template friend struct GpioInputPin; + template friend struct GpioShared; /// Static instance variable that can be used for libraries expectiong a /// generic Gpio pointer. This instance variable will be initialized by the /// linker and (assuming the application developer initialized the hardware @@ -151,31 +150,14 @@ private: template const TivaGpio TivaGpio::instance_; -/// Defines a GPIO output pin. Writes to this structure will change the output -/// level of the pin. Reads will return the pin's current level. -/// -/// The pin is set to output at initialization time, with the level defined by -/// `SAFE_VALUE'. -/// -/// Do not use this class directly. Use @ref GPIO_PIN instead. -template struct GpioOutputPin : public Defs -{ +/// Shared class that defines static functions used by both GpioInputPin, +/// GpioOutputPin and GpioHwPin. +template struct GpioShared : public Defs { public: using Defs::GPIO_PERIPH; using Defs::GPIO_BASE; using Defs::GPIO_PIN; - /// Initializes the hardware pin. - static void hw_init() - { - MAP_SysCtlPeripheralEnable(GPIO_PERIPH); - MAP_GPIOPinTypeGPIOOutput(GPIO_BASE, GPIO_PIN); - set(SAFE_VALUE); - } - /// Sets the hardware pin to a safe value. - static void hw_set_to_safe() - { - hw_init(); - } + /// Used to unlock special consideration pins such as JTAG or NMI pins. static void unlock() { @@ -193,20 +175,17 @@ public: HWREG(GPIO_BASE + GPIO_O_CR) &= ~GPIO_PIN; HWREG(GPIO_BASE + GPIO_O_LOCK) = 0; } + /// Sets the output pin to a specified value; @param value if true, output /// is set to HIGH otherwise LOW. static void set(bool value) { - uint8_t *ptr = reinterpret_cast( - GPIO_BASE + (((unsigned)GPIO_PIN) << 2)); - *ptr = value ? 0xff : 0; + HWREGB(ptr_address()) = value ? 0xff : 0; } /// @return current value of an input pin, if true HIGH, of false LOW. static bool get() { - const uint8_t *ptr = reinterpret_cast( - GPIO_BASE + (((unsigned)GPIO_PIN) << 2)); - return *ptr; + return HWREGB(ptr_address()) != 0; } /// Changes the value of an output pin. static void toggle() @@ -214,11 +193,46 @@ public: set(!get()); } + /// @return the IO address of the port that controls the specific pin. + static constexpr uint32_t ptr_address() + { + return GPIO_BASE + (((unsigned)GPIO_PIN) << 2); + } + /// @return static Gpio ovject instance that controls this output pin. static constexpr const Gpio *instance() { return &TivaGpio::instance_; } +}; + +/// Defines a GPIO output pin. Writes to this structure will change the output +/// level of the pin. Reads will return the pin's current level. +/// +/// The pin is set to output at initialization time, with the level defined by +/// `SAFE_VALUE'. +/// +/// Do not use this class directly. Use @ref GPIO_PIN instead. +template +struct GpioOutputPin : public GpioShared +{ +public: + using Defs::GPIO_PERIPH; + using Defs::GPIO_BASE; + using Defs::GPIO_PIN; + using GpioShared::set; + /// Initializes the hardware pin. + static void hw_init() + { + MAP_SysCtlPeripheralEnable(GPIO_PERIPH); + MAP_GPIOPinTypeGPIOOutput(GPIO_BASE, GPIO_PIN); + set(SAFE_VALUE); + } + /// Sets the hardware pin to a safe value. + static void hw_set_to_safe() + { + hw_init(); + } /// @return true if this pin is an output pin. static bool is_output() @@ -320,7 +334,8 @@ public: typedef BaseClass NAME##_Pin /// Common class for GPIO input pins. -template struct GpioInputPin : public Defs +template +struct GpioInputPin : public GpioShared { public: using Defs::GPIO_PERIPH; @@ -338,40 +353,11 @@ public: { hw_init(); } - /// Used to unlock special consideration pins such as JTAG or NMI pins. - static void unlock() - { - MAP_SysCtlPeripheralEnable(GPIO_PERIPH); - MAP_SysCtlDelay(26); - HWREG(GPIO_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY; - HWREG(GPIO_BASE + GPIO_O_CR) |= GPIO_PIN; - HWREG(GPIO_BASE + GPIO_O_LOCK) = 0; - } - /// Used to lock special consideration pins such as JTAG or NMI pins. - static void lock() - { - MAP_SysCtlPeripheralEnable(GPIO_PERIPH); - HWREG(GPIO_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY; - HWREG(GPIO_BASE + GPIO_O_CR) &= ~GPIO_PIN; - HWREG(GPIO_BASE + GPIO_O_LOCK) = 0; - } - /// @return true if the pin is currently seeing a high value on the input.. - static bool get() - { - const uint8_t *ptr = reinterpret_cast( - GPIO_BASE + (((unsigned)GPIO_PIN) << 2)); - return *ptr; - } /// @return true if the pin is set to an output. static bool is_output() { return false; } - /// @return the static Gpio instance controlling this pin. - static constexpr const Gpio *instance() - { - return &TivaGpio::instance_; - } }; /// GPIO Input pin with weak pull up. @@ -455,7 +441,7 @@ template struct GpioUSBAPin : public Defs /// functions. /// /// Do not use this class directly. Use @ref GPIO_HWPIN instead. -template struct GpioHwPin : public Defs +template struct GpioHwPin : public GpioShared { using Defs::GPIO_PERIPH; using Defs::GPIO_BASE; @@ -480,25 +466,6 @@ template struct GpioHwPin : public Defs hw_init(); } - /// Used to unlock special consideration pins such as JTAG or NMI pins. - static void unlock() - { - MAP_SysCtlPeripheralEnable(GPIO_PERIPH); - MAP_SysCtlDelay(26); - HWREG(GPIO_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY; - HWREG(GPIO_BASE + GPIO_O_CR) |= GPIO_PIN; - HWREG(GPIO_BASE + GPIO_O_LOCK) = 0; - } - - /// Used to lock special consideration pins such as JTAG or NMI pins. - static void lock() - { - MAP_SysCtlPeripheralEnable(GPIO_PERIPH); - HWREG(GPIO_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY; - HWREG(GPIO_BASE + GPIO_O_CR) &= ~GPIO_PIN; - HWREG(GPIO_BASE + GPIO_O_LOCK) = 0; - } - /** Switches the GPIO pin to the hardware peripheral. */ static void set_hw() { @@ -526,27 +493,6 @@ template struct GpioHwPin : public Defs MAP_GPIOPadConfigSet( GPIO_BASE, GPIO_PIN, GPIO_STRENGTH_2MA, drive_type); } - - /// Change the output state to a specified value. - /// @param value if true, output will be set to HIGH, else LOW. - static void set(bool value) - { - volatile uint8_t *ptr = reinterpret_cast( - GPIO_BASE + (((unsigned)GPIO_PIN) << 2)); - *ptr = value ? 0xff : 0; - } - /// @return vurrent value of the pin, true for HIGH and false for LOW. - static bool get() - { - const volatile uint8_t *ptr = reinterpret_cast( - GPIO_BASE + (((unsigned)GPIO_PIN) << 2)); - return *ptr; - } - /// Change the current value of the output pin. - static void toggle() - { - set(!get()); - } }; /// Helper macro for defining GPIO pins with a specific hardware config on the From d0a59e63e73946e41d143e1764a4ecf6bdd5349b Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Feb 2022 23:06:54 +0100 Subject: [PATCH 51/62] Updates the CAN bit timing parameters of the TivaCAN driver. (#605) * Updates the CAN bit timing parameters of the TivaCAN driver. This takes over the recommended parameters from the OpenLCB documentation: 16 TQ, 7 PropSeg, 4 Phase1, 4Phase2, 4SJW. This allows for 0.98% clock tolerance. * Fixes comments. --- src/freertos_drivers/ti/TivaCan.cxx | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/freertos_drivers/ti/TivaCan.cxx b/src/freertos_drivers/ti/TivaCan.cxx index 876af210f..d08607a60 100644 --- a/src/freertos_drivers/ti/TivaCan.cxx +++ b/src/freertos_drivers/ti/TivaCan.cxx @@ -77,7 +77,34 @@ TivaCan::TivaCan(const char *name, unsigned long base, uint32_t interrupt) } MAP_CANInit(base); - MAP_CANBitRateSet(base, cm3_cpu_clock_hz, config_nmranet_can_bitrate()); + + uint32_t ftq = config_nmranet_can_bitrate() * 16; + // If this fails, the CAN bit timings do not support this CPU clock. The + // CPU clock has to be an even number of MHz. + HASSERT(cm3_cpu_clock_hz % ftq == 0); + + /* Nominal 2 MHz quantum clock + * SyncSeg = 1 TQ + * PropSeg = 7 TQ + * PS1 = 4 TQ + * PS2 = 4 TQ + * Bit total = 16 TQ + * Baud = 125 kHz + * sample time = (1 TQ + 7 TQ + 4 TQ) / 16 TQ = 75% + * SJW = 4 TQ + * + * Oscillator Tolerance: + * 4 / (2 * ((13 * 16) - 4)) = 0.980% + * 4 / (20 * 16) = 1.250% + * = 0.980% + */ + tCANBitClkParms clk_params = { + .ui32SyncPropPhase1Seg = 11, // Sum of PropSeg and PS1 in #TQ + .ui32Phase2Seg = 4, // PS2 in #TQ + .ui32SJW = 4, + .ui32QuantumPrescaler = cm3_cpu_clock_hz / ftq + }; + MAP_CANBitTimingSet(base, &clk_params); MAP_CANIntEnable(base, CAN_INT_MASTER | CAN_INT_ERROR | CAN_INT_STATUS); tCANMsgObject can_message; From fa927d8b89ca1611ff3e3d85e7edc48475dff681 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Feb 2022 23:14:11 +0100 Subject: [PATCH 52/62] Adds a script for computing optimal CAN-bus timing configurations. (#606) --- bin/can_osc_tolerance.py | 86 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100755 bin/can_osc_tolerance.py diff --git a/bin/can_osc_tolerance.py b/bin/can_osc_tolerance.py new file mode 100755 index 000000000..b5fa0c3ca --- /dev/null +++ b/bin/can_osc_tolerance.py @@ -0,0 +1,86 @@ +#!/usr/bin/python +# +# Prints information about possible CAN-bus timing configurations that fulfill +# the needs for OpenLCB, i.e., those that are good enough for 300m long buses +# at 125 kbps. The basis upon which this is built is AN1798 CAN Bit Timing +# Requirements by NXP/Freescale +# (https://www.nxp.com/docs/en/application-note/AN1798.pdf) +# +# Output is sorted by the osc tolerance decreasing. When configuring a CAN +# controller, you should pick the first line that is possible to be configured +# in your CAN controller, meaning the one which gives the best oscillator +# tolerance. +# +# Interpreting output: +# +# 0.98 : ['len 310 m, prop 7 ps1 4 ps2 4 sjw 4, sample 75.0, o1 1.25 o2 0.98', 'len 320 m, prop 9 ps1 5 ps2 5 sjw 4, sample 75.0, o1 1 o2 0.98'] +# +# Before colon: the oscillator tolerance in percent. This is to be interpreted +# as +- this percentage at both source and destination (independently). +# +# len = max cable length +# prop = number of TQ for propagation segment +# ps1 = number of TQ for phase segment 1 +# ps2 = number of TQ for phase segment 2 +# sjw = number of TQ for (re)-synchronization jump width +# sample = sample point within bit in % +# o1 = osc tolerance limit from one constraint +# o2 = osc tolerance limit from another constraint +# +# There are two entries in this line, which means that two different +# configurations reach the same osc tolerance. + + +from collections import defaultdict + +found = defaultdict(list) +optimal = [] + +MIN_LENGTH = 300 + +def eval_timing(propseg, ps1, ps2, sjw): + global found, MIN_LENGTH, optimal + # number of time quanta in the bit period. SyncSeg is always 1. + ntq = 1 + propseg + ps1 + ps2 + sample_point = 1.0 * (1 + propseg + ps1) / ntq + BAUD = 125000 + sec_per_tq = 1.0 / BAUD / ntq + # 400 nsec for delay of trnasceiver and receiver. (100 nsec each twice for + # tx and rx) + TXRX_DELAY_SEC = 400e-9 + # Cable propagation delay is 5 nsec per meter. + PROP_SEC_PER_METER = 5e-9 + max_length = (propseg * sec_per_tq - TXRX_DELAY_SEC) / PROP_SEC_PER_METER / 2 + # One formula says that the relative frequency * 2 over 10 bit time + # cannot be more than the SJW. + osc1_limit = sjw * 1.0 / (2 * 10 * ntq) + # Another formula says that over 13 bits time less ps2, we cannot have more + # drift than -ps1 or +ps2. + osc2_limit = min(ps1, ps2) / (2 * (13*ntq - ps2)) + real_limit = min(osc1_limit, osc2_limit) + params = 'len %.0f m, prop %d ps1 %d ps2 %d sjw %d, sample %.1f, o1 %.3g o2 %.3g' % (max_length, propseg, ps1, ps2, sjw, sample_point * 100, osc1_limit * 100, osc2_limit * 100) + if max_length > MIN_LENGTH: + found[real_limit].append(params) + for x in optimal: + if x[0] > real_limit and x[1] > max_length: + return + optimal = [x for x in optimal if x[0] >= real_limit or x[1] >= max_length] + optimal.append([real_limit, max_length, params]) + +def print_findings(): + global found, optimal + for (tol, v) in sorted(found.items(), reverse=True)[:10]: + print('%-5.3g' % (tol * 100), ": ", v) + print('\n\noptimal:') + for x in sorted(optimal, reverse=True): + print('%-5.3g' % (x[0] * 100), ": ", x[2]) + + +# enumerates all possibilities +for propseg in range(1, 16): + for ps1 in range(1, 8): + for ps2 in range(1, 8): + for sjw in range(1, 1+min(4, min(ps1, ps2))): + eval_timing(propseg, ps1, ps2, sjw) + +print_findings() From f03967ea49459d9e033458b4f5a7fd12748be25e Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Feb 2022 23:19:13 +0100 Subject: [PATCH 53/62] Removes duplicate -MD argument. The prog.mk already contains the necessary -MMD instead, and clang complains if both are set. --- etc/mach.x86_64.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etc/mach.x86_64.mk b/etc/mach.x86_64.mk index 72ef97bb1..eb889ef1f 100644 --- a/etc/mach.x86_64.mk +++ b/etc/mach.x86_64.mk @@ -20,9 +20,9 @@ ENDGROUP := INCLUDES += -I$(OPENMRNPATH)/include/mach -CFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c99 -fno-stack-protector \ +CFLAGS = -c -g -O0 -Wall -Werror -MP -std=c99 -fno-stack-protector \ -D_GNU_SOURCE -CXXFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c++14 -fno-stack-protector \ +CXXFLAGS = -c -g -O0 -Wall -Werror -MP -std=c++14 -fno-stack-protector \ -D_GNU_SOURCE LDFLAGS = -g From 283ff16c746a924e15790e8ad56e2fe878be6d36 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Feb 2022 23:19:58 +0100 Subject: [PATCH 54/62] Adds a hello message to the DCC decoder. (#607) --- applications/dcc_decoder/main.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/applications/dcc_decoder/main.cxx b/applications/dcc_decoder/main.cxx index 9134ef9cb..44d38732d 100644 --- a/applications/dcc_decoder/main.cxx +++ b/applications/dcc_decoder/main.cxx @@ -298,6 +298,9 @@ int appl_main(int argc, char *argv[]) eefd = ::open("/dev/eeprom", O_RDWR); HASSERT(eefd >= 0); + static const char HELLO[] = "DCC Decoder program.\n"; + ::write(wfd, HELLO, sizeof(HELLO)); + irqProc.init(); set_dcc_interrupt_processor(&irqProc); From 66bf27cb2b0873c33534bca5d966a7022e6573f1 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Feb 2022 23:21:19 +0100 Subject: [PATCH 55/62] Fixes the flash command for TM4C129. (#608) * Fixes the flash command for TM4C129. Sometimes it can fail if the processor is not halted in reset. --- .../targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile b/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile index fd1b66ab4..b75c16173 100644 --- a/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile +++ b/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile @@ -48,7 +48,7 @@ ifeq ($(call find_missing_deps,OPENOCDSCRIPTSPATH OPENOCDPATH),) flash: $(EXECUTABLE)$(EXTENTION) $(EXECUTABLE).lst @if ps ax -o comm | grep -q openocd ; then echo openocd already running. quit existing first. ; exit 1 ; fi cp $< last-flashed-$< - $(GDB) $< -ex "target remote | $(OPENOCDPATH)/openocd -c \"gdb_port pipe\" --search $(OPENOCDSCRIPTSPATH) $(OPENOCDARGS)" -ex "monitor reset init" -ex "monitor reset run" -ex "load" -ex "monitor reset init" -ex "monitor reset run" -ex "detach" -ex "quit" + $(GDB) $< -ex "target remote | $(OPENOCDPATH)/openocd -c \"gdb_port pipe\" --search $(OPENOCDSCRIPTSPATH) $(OPENOCDARGS)" -ex "monitor reset init" -ex "load" -ex "monitor reset init" -ex "monitor reset run" -ex "detach" -ex "quit" gdb: @if ps ax -o comm | grep -q openocd ; then echo openocd already running. quit existing first. ; exit 1 ; fi From 8f768c8c2da28739bdc1c222550abbb5bd01597a Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Fri, 4 Feb 2022 23:21:55 +0100 Subject: [PATCH 56/62] Python3 fixes in revision.py (#609) --- bin/revision.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/revision.py b/bin/revision.py index 2cfce0451..51bae5cd9 100755 --- a/bin/revision.py +++ b/bin/revision.py @@ -50,7 +50,7 @@ if (options.input == None) : parser.error('missing parameter -i') -print options.input +print(options.input) options.input = options.input.replace(' ', ' ') inputs = options.input.split(" ") @@ -96,14 +96,14 @@ main_git_hash = None for x in inputs : - print x + print(x) # go into the root of the repo os.chdir(orig_dir) os.chdir(x) # get the short hash git_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']) - git_hash = git_hash[:7] + git_hash = str(git_hash[:7]) # get the dirty flag dirty = os.system('git diff --quiet') From 3196adb088f9cd1bf555cc251855bd98a72c466e Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 6 Feb 2022 21:45:18 +0100 Subject: [PATCH 57/62] Fixes interpretation of O_CREAT in the CC32xxDeviceFile. (#610) When SL_FS_CREATE fails we will still attempt to do an FsOpen with SL_FS_WRITE instead of returning an error or crashing. --- src/freertos_drivers/ti/CC32xxDeviceFile.cxx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/freertos_drivers/ti/CC32xxDeviceFile.cxx b/src/freertos_drivers/ti/CC32xxDeviceFile.cxx index 5ff55e7a0..eb2a8b75d 100644 --- a/src/freertos_drivers/ti/CC32xxDeviceFile.cxx +++ b/src/freertos_drivers/ti/CC32xxDeviceFile.cxx @@ -92,13 +92,16 @@ int CC32xxDeviceFile::open(File* file, const char *path, int flags, int mode) else { /* file not open yet, open and intialize metadata */ - int32_t result; + int32_t result = 0; if (flags & O_CREAT) { result = sl_FsOpen((const unsigned char *)path, SL_FS_CREATE | SL_FS_CREATE_MAX_SIZE(maxSizeOnCreate), nullptr); + } + if (result > 0) + { writeEnable = true; } else if (flags & O_WRONLY) @@ -163,6 +166,7 @@ void CC32xxDeviceFile::disable() { sl_FsClose(handle, nullptr, nullptr, 0); handle = -1; + writeEnable = false; } } From 7d0cdbc9993cdcb37847a05c5d9fc272f2623a61 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 6 Feb 2022 22:10:43 +0100 Subject: [PATCH 58/62] Fixes bug in FlashFile O_TRUNC handling. (#611) There was a bug in how we interpreted the O_TRUNC in FlashFile::open. We were looking for O_WRONLY in the mode bits but O_WRONLY is actually part of the flags bits. === * Fixes bug in FlashFile O_TRUNC handling. * excludes RDONLY mode from truncating. --- src/freertos_drivers/common/FlashFile.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/freertos_drivers/common/FlashFile.hxx b/src/freertos_drivers/common/FlashFile.hxx index 21c4f983d..f10d09e48 100644 --- a/src/freertos_drivers/common/FlashFile.hxx +++ b/src/freertos_drivers/common/FlashFile.hxx @@ -82,7 +82,7 @@ public: /// Overrides behavior of open for O_TRUNC. int open(File *file, const char *path, int flags, int mode) override { - if ((mode & O_WRONLY) && (flags & O_TRUNC)) + if ((flags & O_TRUNC) && ((flags & O_ACCMODE) != O_RDONLY)) { // erase entire file. flash_->erase(flashStart_, size_); From 0fac0bd428f6cb30f46eb8b2c46fec21731bcb29 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Tue, 22 Feb 2022 20:30:28 +0100 Subject: [PATCH 59/62] Optimizes the computation speed for crc16-ibm. (#612) Switches to a 4-bit translation table. --- src/utils/Crc.cxx | 32 ++++++++++++++++++++++++++++---- src/utils/Crc.cxxtest | 24 ++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/utils/Crc.cxx b/src/utils/Crc.cxx index a70179ade..16df9c64c 100644 --- a/src/utils/Crc.cxx +++ b/src/utils/Crc.cxx @@ -85,17 +85,41 @@ inline uint16_t crc_16_ibm_finish(uint16_t state) { //return state; }*/ +/// Translation table for crc16-ibm, high nibble. +static const uint16_t CRC16_IBM_HI[16] = +{ + 0x0000, 0xcc01, 0xd801, 0x1400, 0xf001, 0x3c00, 0x2800, 0xe401, 0xa001, 0x6c00, 0x7800, 0xb401, 0x5000, 0x9c01, 0x8801, 0x4400, +}; +/// Translation table for crc16-ibm, low nibble. +static const uint16_t CRC16_IBM_LO[16] = +{ + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, +}; + /// Appends a byte to a CRC16 state machine. /// /// @param state the state machine of the CRC computer. /// @param data next byte to add. /// -inline void crc_16_ibm_add(uint16_t& state, uint8_t data) { +inline void __attribute__((always_inline)) crc_16_ibm_add(uint16_t &state, uint8_t data) +{ state ^= data; - for (int i = 0; i < 8; i++) { - if (state & 1) { + state = (state >> 8) ^ CRC16_IBM_LO[state & 0xf] ^ + CRC16_IBM_HI[(state >> 4) & 0xf]; +} + +/// Bitwise implementation of the crc16 ibm add. +void crc_16_ibm_add_basic(uint16_t &state, uint8_t data) +{ + state ^= data; + for (int i = 0; i < 8; i++) + { + if (state & 1) + { state = (state >> 1) ^ crc_16_ibm_poly; - } else { + } + else + { state = (state >> 1); } } diff --git a/src/utils/Crc.cxxtest b/src/utils/Crc.cxxtest index 8f2b255a4..2c5db06df 100644 --- a/src/utils/Crc.cxxtest +++ b/src/utils/Crc.cxxtest @@ -42,6 +42,30 @@ TEST(Crc3Test, Example) EXPECT_EQ(0x0459, data[2]); } + +extern void crc_16_ibm_add_basic(uint16_t& state, uint8_t data); + +// This is not a test. It generates the translation table for the CRC16-IBM. +TEST(Crc3Generate, generate16) +{ + printf("static const uint16_t CRC16_IBM_HI[16] =\n{\n "); + for (unsigned nib = 0; nib < 16; nib++) + { + uint16_t state = 0; + crc_16_ibm_add_basic(state, nib << 4); + printf("0x%04x, ", state); + } + printf("\n};\n"); + printf("static const uint16_t CRC16_IBM_LO[16] =\n{\n "); + for (unsigned nib = 0; nib < 16; nib++) + { + uint16_t state = 0; + crc_16_ibm_add_basic(state, nib); + printf("0x%04x, ", state); + } + printf("\n};\n"); +} + TEST(Crc8Test, Example) { // This test vector comes from the RCN-218 document by RailCommunity. It is From f27dc08a7ffc78b537121d9b44320495dec5ccc4 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 6 Mar 2022 20:17:29 +0100 Subject: [PATCH 60/62] Adds support for RS485 mode to the tiva UART driver. (#613) * Adds support for RS485 mode to the tiva UART driver. * Update Tiva UART for EOT support for RS-485 enable line. * One more further simplification of the Tiva UART driver. Co-authored-by: Stuart Baker --- src/freertos_drivers/ti/TivaDev.hxx | 19 +++++- src/freertos_drivers/ti/TivaUart.cxx | 90 ++++++++++++++++++---------- 2 files changed, 78 insertions(+), 31 deletions(-) diff --git a/src/freertos_drivers/ti/TivaDev.hxx b/src/freertos_drivers/ti/TivaDev.hxx index 0ec2c080d..1bd0a92e0 100644 --- a/src/freertos_drivers/ti/TivaDev.hxx +++ b/src/freertos_drivers/ti/TivaDev.hxx @@ -166,6 +166,9 @@ public: CSTOPB = UART_CONFIG_STOP_TWO, /**< send two stop bits instead of 1 */ }; + /** Function point for the tx enable assert and deassert methods */ + typedef void (*TxEnableMethod)(); + /** Constructor. * @param name name of this device instance in the file system * @param base base address of this device @@ -173,9 +176,13 @@ public: * @param baud desired baud rate * @param mode to configure the UART for * @param hw_fifo true if hardware fifo is to be enabled, else false. + * @param tx_enable_assert callback to assert the transmit enable + * @param tx_enable_deassert callback to deassert the transmit enable */ TivaUart(const char *name, unsigned long base, uint32_t interrupt, - uint32_t baud = 115200, uint32_t mode = CS8, bool hw_fifo = true); + uint32_t baud = 115200, uint32_t mode = CS8, bool hw_fifo = true, + TxEnableMethod tx_enable_assert = nullptr, + TxEnableMethod tx_enable_deassert = nullptr); /** Destructor. */ @@ -200,8 +207,18 @@ private: */ void tx_char() override; + /** Send data until there is no more space left. + */ + void send(); + /** Sets the port baud rate and mode from the class variables. */ void set_mode(); + + /** function pointer to a method that asserts the transmit enable. */ + TxEnableMethod txEnableAssert_; + + /** function pointer to a method that deasserts the transmit enable. */ + TxEnableMethod txEnableDeassert_; /** Notifiable to invoke when the transmit engine has finished operation. */ Notifiable* txComplete_{nullptr}; diff --git a/src/freertos_drivers/ti/TivaUart.cxx b/src/freertos_drivers/ti/TivaUart.cxx index 5e41cb7b4..1ec1f1c27 100644 --- a/src/freertos_drivers/ti/TivaUart.cxx +++ b/src/freertos_drivers/ti/TivaUart.cxx @@ -60,8 +60,11 @@ static Atomic isr_lock; * @param interrupt interrupt number of this device */ TivaUart::TivaUart(const char *name, unsigned long base, uint32_t interrupt, - uint32_t baud, uint32_t mode, bool hw_fifo) + uint32_t baud, uint32_t mode, bool hw_fifo, TxEnableMethod tx_enable_assert, + TxEnableMethod tx_enable_deassert) : Serial(name) + , txEnableAssert_(tx_enable_assert) + , txEnableDeassert_(tx_enable_deassert) , base_(base) , interrupt_(interrupt) , baud_(baud) @@ -148,24 +151,54 @@ void TivaUart::disable() MAP_UARTDisable(base_); } -/** Try and transmit a message. +/** Send data until there is no more space left. */ -void TivaUart::tx_char() +void TivaUart::send() { - if (txPending_ == false) + do { uint8_t data = 0; - if (txBuf->get(&data, 1)) { MAP_UARTCharPutNonBlocking(base_, data); - MAP_IntDisable(interrupt_); - txPending_ = true; - MAP_UARTIntEnable(base_, UART_INT_TX); - MAP_IntEnable(interrupt_); - txBuf->signal_condition(); } + else + { + break; + } + } + while (MAP_UARTSpaceAvail(base_)); + + if (txBuf->pending()) + { + /* more data to send later */ + MAP_UARTTxIntModeSet(base_, UART_TXINT_MODE_FIFO); + } + else + { + /* no more data left to send */ + MAP_UARTTxIntModeSet(base_, UART_TXINT_MODE_EOT); + MAP_UARTIntClear(base_, UART_INT_TX); + } +} + +/** Try and transmit a message. + */ +void TivaUart::tx_char() +{ + if (txPending_ == false) + { + if (txEnableAssert_) + { + txEnableAssert_(); + } + + send(); + txPending_ = true; + + MAP_UARTIntEnable(base_, UART_INT_TX); + txBuf->signal_condition(); } } @@ -196,32 +229,29 @@ void TivaUart::interrupt_handler() } } /* transmit a character if we have pending tx data */ - if (txPending_) + if (txPending_ && (status & UART_INT_TX)) { - /** @todo (Stuart Baker) optimization opportunity by getting a read - * pointer to fill the fifo and then consume the buffer when finished. - */ - while (MAP_UARTSpaceAvail(base_)) + if (txBuf->pending()) { - unsigned char data; - if (txBuf->get(&data, 1) != 0) + send(); + txBuf->signal_condition_from_isr(); + } + else + { + /* no more data left to send */ + HASSERT(MAP_UARTTxIntModeGet(base_) == UART_TXINT_MODE_EOT); + if (txEnableDeassert_) { - MAP_UARTCharPutNonBlocking(base_, data); - txBuf->signal_condition_from_isr(); + txEnableDeassert_(); } - else + txPending_ = false; + if (txComplete_) { - /* no more data pending */ - txPending_ = false; - if (txComplete_) - { - Notifiable *t = txComplete_; - txComplete_ = nullptr; - t->notify_from_isr(); - } - MAP_UARTIntDisable(base_, UART_INT_TX); - break; + Notifiable *t = txComplete_; + txComplete_ = nullptr; + t->notify_from_isr(); } + MAP_UARTIntDisable(base_, UART_INT_TX); } } os_isr_exit_yield_test(woken); From 4704bda53dc83bacea88ef6e06f887bc16cd1ea8 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sat, 19 Mar 2022 18:29:38 +0100 Subject: [PATCH 61/62] Explicit handling of NOT_RESPONDING aliases. (#614) * Explicit handling of NOT_RESPONDING aliases. Ensures that we can enter more than one ID with NOT_RESPONDING alias into the alias cache. This is critical so that the AddressedWriteFlow can correctly function in the presence of node IDs that disappeared from the network (or never existed). We already have a workaround where we add a node ID with the special alias NOT_RESPONDING, but the AliasCache was not prepared for having more than one such entry, and thus it was evicting these entries unexpectedly. This PR makes the NOT_RESPONDING entries unique in the aliasMap by appending part of the storage offset to the alias. * Fix style. --- src/openlcb/AliasCache.cxx | 81 ++++++++++++++++++++++++++++++---- src/openlcb/AliasCache.cxxtest | 25 +++++++++++ src/openlcb/AliasCache.hxx | 6 --- 3 files changed, 98 insertions(+), 14 deletions(-) diff --git a/src/openlcb/AliasCache.cxx b/src/openlcb/AliasCache.cxx index d4258fbcf..d1781229b 100644 --- a/src/openlcb/AliasCache.cxx +++ b/src/openlcb/AliasCache.cxx @@ -47,6 +47,19 @@ namespace openlcb #define CONSTANT 0x1B0CA37ABA9 /**< constant for random number generation */ +/// This code removes the unique bits in the stored node alias in case this is +/// a NOT_RESPONDING entry. +/// @param stored alias in the metadata storage +/// @return the alias if it's valid or NOT_RESPONDING ifthis is a sentinel +static NodeAlias resolve_notresponding(NodeAlias stored) +{ + if ((stored & NOT_RESPONDING) == NOT_RESPONDING) + { + return NOT_RESPONDING; + } + return stored; +} + #if defined(TEST_CONSISTENCY) extern volatile int consistency_result; volatile int consistency_result = 0; @@ -55,12 +68,14 @@ int AliasCache::check_consistency() { if (idMap.size() != aliasMap.size()) { + LOG(INFO, "idmap size != aliasmap size."); return 1; } if (aliasMap.size() == entries) { if (!freeList.empty()) { + LOG(INFO, "Found freelist entry when map is full."); return 2; } } @@ -68,11 +83,13 @@ int AliasCache::check_consistency() { if (freeList.empty()) { + LOG(INFO, "No freelist entry although map is not full."); return 3; } } if (aliasMap.size() == 0 && (!oldest.empty() || !newest.empty())) { + LOG(INFO, "LRU head/tail elements should be null when map is empty."); return 4; } std::set free_entries; @@ -81,18 +98,21 @@ int AliasCache::check_consistency() Metadata *m = p.deref(this); if (free_entries.count(m)) { - return 5; // duplicate entry on freelist + LOG(INFO, "Duplicate entry on freelist."); + return 5; } free_entries.insert(m); } if (free_entries.size() + aliasMap.size() != entries) { - return 6; // lost some metadata entries + LOG(INFO, "Lost some metadata entries."); + return 6; } for (auto kv : aliasMap) { if (free_entries.count(kv.deref(this))) { + LOG(INFO, "Found an aliasmap entry in the freelist."); return 19; } } @@ -100,6 +120,7 @@ int AliasCache::check_consistency() { if (free_entries.count(kv.deref(this))) { + LOG(INFO, "Found an id entry in the freelist."); return 20; } } @@ -107,10 +128,12 @@ int AliasCache::check_consistency() { if (!oldest.empty()) { + LOG(INFO, "Oldest should be empty when map is empty."); return 7; } if (!newest.empty()) { + LOG(INFO, "Newest should be empty when map is empty."); return 8; } } @@ -118,18 +141,22 @@ int AliasCache::check_consistency() { if (oldest.empty()) { + LOG(INFO, "Oldest should be nonempty when map is nonempty."); return 9; } if (newest.empty()) { + LOG(INFO, "Newest should be nonempty when map is nonempty."); return 10; } if (free_entries.count(oldest.deref(this))) { + LOG(INFO, "Oldest is on the freelist."); return 11; // oldest is free } if (free_entries.count(newest.deref(this))) { + LOG(INFO, "Newest is on the freelist."); return 12; // newest is free } } @@ -143,6 +170,7 @@ int AliasCache::check_consistency() unsigned count = 1; if (!prev.deref(this)->older_.empty()) { + LOG(INFO, "Prev link points to empty."); return 13; } while (!prev.deref(this)->newer_.empty()) @@ -151,20 +179,24 @@ int AliasCache::check_consistency() ++count; if (free_entries.count(next.deref(this))) { + LOG(INFO, "Next link points to the freelist."); return 21; } if (next.deref(this)->older_.idx_ != prev.idx_) { + LOG(INFO, "Next link broken."); return 14; } prev = next; } if (prev.idx_ != newest.idx_) { + LOG(INFO, "Prev link points to newest."); return 18; } if (count != aliasMap.size()) { + LOG(INFO, "LRU link list length is incorrect."); return 27; } } @@ -172,6 +204,7 @@ int AliasCache::check_consistency() PoolIdx next = newest; if (!next.deref(this)->newer_.empty()) { + LOG(INFO, "Newest has a newer link."); return 15; } while (!next.deref(this)->older_.empty()) @@ -179,16 +212,19 @@ int AliasCache::check_consistency() auto prev = next.deref(this)->older_; if (free_entries.count(prev.deref(this))) { + LOG(INFO, "Prev link points to the freelist."); return 22; } if (prev.deref(this)->newer_.idx_ != next.idx_) { + LOG(INFO, "Prev link broken."); return 16; } next = prev; } if (next.idx_ != oldest.idx_) { + LOG(INFO, "Next link points to oldest."); return 17; } } @@ -201,18 +237,24 @@ int AliasCache::check_consistency() auto *e = pool + i; if (idMap.find(e->get_node_id()) == idMap.end()) { + LOG(INFO, "Metadata ID is not in the id map."); return 23; } if (idMap.find(e->get_node_id())->idx_ != i) { + LOG(INFO, + "Id map entry does not point back to the expected index."); return 24; } if (aliasMap.find(e->alias_) == aliasMap.end()) { + LOG(INFO, "Metadata alias is not in the alias map."); return 25; } if (aliasMap.find(e->alias_)->idx_ != i) { + LOG(INFO, + "Alis map entry does not point back to the expected index."); return 26; } } @@ -260,6 +302,11 @@ void AliasCache::add(NodeID id, NodeAlias alias) Metadata *insert; auto it = aliasMap.find(alias); + if (alias == NOT_RESPONDING) + { + // We can have more than one NOT_RESPONDING entry. + it = aliasMap.end(); + } if (it != aliasMap.end()) { /* we already have a mapping for this alias, so lets remove it */ @@ -319,6 +366,14 @@ void AliasCache::add(NodeID id, NodeAlias alias) } } + if (alias == NOT_RESPONDING) + { + // This code will make all NOT_RESPONDING aliases unique in our map. + unsigned ofs = insert - pool; + alias = NOT_RESPONDING | ofs; + auto it = aliasMap.find(alias); + HASSERT(it == aliasMap.end()); + } insert->set_node_id(id); insert->alias_ = alias; @@ -397,9 +452,18 @@ bool AliasCache::retrieve(unsigned entry, NodeID* node, NodeAlias* alias) { HASSERT(entry < size()); Metadata* md = pool + entry; - if (!md->alias_) return false; - if (node) *node = md->get_node_id(); - if (alias) *alias = md->alias_; + if (!md->alias_) + { + return false; + } + if (node) + { + *node = md->get_node_id(); + } + if (alias) + { + *alias = resolve_notresponding(md->alias_); + } return true; } @@ -413,7 +477,7 @@ bool AliasCache::next_entry(NodeID bound, NodeID *node, NodeAlias *alias) Metadata *metadata = it->deref(this); if (alias) { - *alias = metadata->alias_; + *alias = resolve_notresponding(metadata->alias_); } if (node) { @@ -438,7 +502,7 @@ NodeAlias AliasCache::lookup(NodeID id) /* update timestamp */ touch(metadata); - return metadata->alias_; + return resolve_notresponding(metadata->alias_); } /* no match found */ @@ -480,7 +544,8 @@ void AliasCache::for_each(void (*callback)(void*, NodeID, NodeAlias), void *cont for (PoolIdx idx = newest; !idx.empty(); idx = idx.deref(this)->older_) { Metadata *metadata = idx.deref(this); - (*callback)(context, metadata->get_node_id(), metadata->alias_); + (*callback)(context, metadata->get_node_id(), + resolve_notresponding(metadata->alias_)); } } diff --git a/src/openlcb/AliasCache.cxxtest b/src/openlcb/AliasCache.cxxtest index c778a6e6a..618a75b1e 100644 --- a/src/openlcb/AliasCache.cxxtest +++ b/src/openlcb/AliasCache.cxxtest @@ -448,6 +448,31 @@ TEST(AliasCacheTest, reinsert_flush) aliasCache->add((NodeID)108, (NodeAlias)99); } +TEST(AliasCacheTest, notresponding) +{ + AliasCache *aliasCache = new AliasCache(0, 10); + + EXPECT_EQ(0, aliasCache->lookup((NodeID)101)); + aliasCache->add((NodeID)101, NOT_RESPONDING); + EXPECT_EQ(NOT_RESPONDING, aliasCache->lookup((NodeID)101)); + aliasCache->add((NodeID)101, 0x123); + EXPECT_EQ(0x123u, aliasCache->lookup((NodeID)101)); + + aliasCache->add((NodeID)102, NOT_RESPONDING); + EXPECT_EQ(NOT_RESPONDING, aliasCache->lookup((NodeID)102)); + aliasCache->add((NodeID)103, NOT_RESPONDING); + EXPECT_EQ(NOT_RESPONDING, aliasCache->lookup((NodeID)103)); + EXPECT_EQ(NOT_RESPONDING, aliasCache->lookup((NodeID)102)); + EXPECT_EQ(NOT_RESPONDING, aliasCache->lookup((NodeID)103)); + + aliasCache->add((NodeID)104, NOT_RESPONDING); + EXPECT_EQ(NOT_RESPONDING, aliasCache->lookup((NodeID)104)); + aliasCache->add((NodeID)103, 0x567); + + EXPECT_EQ(NOT_RESPONDING, aliasCache->lookup((NodeID)102)); + EXPECT_EQ(NOT_RESPONDING, aliasCache->lookup((NodeID)104)); + EXPECT_EQ(0x567, aliasCache->lookup((NodeID)103)); +} class AliasStressTest : public ::testing::Test { protected: diff --git a/src/openlcb/AliasCache.hxx b/src/openlcb/AliasCache.hxx index 1483d6092..9c54a3e39 100644 --- a/src/openlcb/AliasCache.hxx +++ b/src/openlcb/AliasCache.hxx @@ -187,12 +187,6 @@ public: int check_consistency(); private: - enum - { - /** marks an unused mapping */ - UNUSED_MASK = 0x10000000 - }; - struct Metadata; class PoolIdx; friend class PoolIdx; From 7c8e61bb961f91d434e9dfb84122a2375144a2b3 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 20 Mar 2022 09:10:10 +0100 Subject: [PATCH 62/62] Simplifies traction node OpenLCB addresses for DCC locomotives. (#615) * Simplifies traction node OpenLCB addresses for DCC locomotives. Currently these functions use an encoding that is more complicated than necessary. The encoding is not fixed in the standard in any way, so it's rather arbitrary what we put here. After this PR we have the following encoding: node_id = 0x060100000000 | address | (is_long ? 0xC000 : 0); * Updates tests that had hardcoded train node IDs in expectations and constants. * Adds a symbol for the DCC long address selector. --- src/openlcb/TractionConsist.cxxtest | 12 ++++++------ src/openlcb/TractionCvSpace.cxx | 4 ++-- src/openlcb/TractionCvSpace.cxxtest | 6 +++--- src/openlcb/TractionDefs.hxx | 14 +++++--------- src/openlcb/TractionTestTrain.cxxtest | 4 ++-- src/openlcb/TractionThrottle.cxxtest | 2 +- src/openlcb/TractionTrain.cxxtest | 6 +++--- 7 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/openlcb/TractionConsist.cxxtest b/src/openlcb/TractionConsist.cxxtest index 06ad096a3..e4c77394d 100644 --- a/src/openlcb/TractionConsist.cxxtest +++ b/src/openlcb/TractionConsist.cxxtest @@ -7,12 +7,12 @@ namespace openlcb { -static constexpr NodeID nodeIdLead = 0x060100000000 | 1370; -static constexpr NodeID nodeIdC1 = 0x060100000000 | 1371; -static constexpr NodeID nodeIdC2 = 0x060100000000 | 1372; -static constexpr NodeID nodeIdC3 = 0x060100000000 | 1373; -static constexpr NodeID nodeIdC4 = 0x060100000000 | 1374; -static constexpr NodeID nodeIdC5 = 0x060100000000 | 1375; +static constexpr NodeID nodeIdLead = 0x06010000C000 | 1370; +static constexpr NodeID nodeIdC1 = 0x06010000C000 | 1371; +static constexpr NodeID nodeIdC2 = 0x06010000C000 | 1372; +static constexpr NodeID nodeIdC3 = 0x06010000C000 | 1373; +static constexpr NodeID nodeIdC4 = 0x06010000C000 | 1374; +static constexpr NodeID nodeIdC5 = 0x06010000C000 | 1375; class TrainNodeWithMockPolicy : public TrainNodeForProxy { diff --git a/src/openlcb/TractionCvSpace.cxx b/src/openlcb/TractionCvSpace.cxx index c345938ca..615d4476e 100644 --- a/src/openlcb/TractionCvSpace.cxx +++ b/src/openlcb/TractionCvSpace.cxx @@ -282,7 +282,7 @@ StateFlowBase::Action TractionCvSpace::fill_read1_packet() * between long and short addresses */ if (dccAddress_ >= 0x80) { - b->data()->add_dcc_address(dcc::DccLongAddress(dccAddress_)); + b->data()->add_dcc_address(dcc::DccLongAddress(dccAddress_ & 0x3FFF)); } else { @@ -389,7 +389,7 @@ StateFlowBase::Action TractionCvSpace::fill_write1_packet() * between long and short addresses */ if (dccAddress_ >= 0x80) { - b->data()->add_dcc_address(dcc::DccLongAddress(dccAddress_)); + b->data()->add_dcc_address(dcc::DccLongAddress(dccAddress_ & 0x3FFF)); } else { diff --git a/src/openlcb/TractionCvSpace.cxxtest b/src/openlcb/TractionCvSpace.cxxtest index ebc16f9b4..d613207bc 100644 --- a/src/openlcb/TractionCvSpace.cxxtest +++ b/src/openlcb/TractionCvSpace.cxxtest @@ -20,9 +20,9 @@ protected: TractionCvTestBase() { run_x([this]() { - ifCan_->local_aliases()->add(0x0601000000AFULL, 0x272U); + ifCan_->local_aliases()->add(0x06010000C0AFULL, 0x272U); }); - expect_packet(":X19100272N0601000000AF;"); + expect_packet(":X19100272N06010000C0AF;"); } ~TractionCvTestBase() @@ -37,7 +37,7 @@ protected: enum { TRAIN_NODE_ALIAS = 0x272, - TRAIN_NODE_ID = 0x0601000000AF + TRAIN_NODE_ID = 0x06010000C0AF }; TractionCvTest() diff --git a/src/openlcb/TractionDefs.hxx b/src/openlcb/TractionDefs.hxx index afa241c9a..2ab6dbdb6 100644 --- a/src/openlcb/TractionDefs.hxx +++ b/src/openlcb/TractionDefs.hxx @@ -77,6 +77,8 @@ struct TractionDefs { static const uint64_t NODE_ID_DC_BLOCK = 0x060000000000ULL; /// Node ID space allocated for DCC locomotives. static const uint64_t NODE_ID_DCC = 0x060100000000ULL; + /// Long addresses should OR this selector to {\link NODE_ID_DCC }. + static const uint16_t DCC_LONG_SELECTOR = 0xC000; /// Node ID space allocated for TMCC protocol. static const uint64_t NODE_ID_TMCC = 0x060200000000ULL; /// Node ID space allocated for the Marklin-Motorola protocol. @@ -216,14 +218,7 @@ struct TractionDefs { case dcc::TrainAddressType::DCC_SHORT_ADDRESS: return NODE_ID_DCC | addr; case dcc::TrainAddressType::DCC_LONG_ADDRESS: - if (addr < 128) - { - return NODE_ID_DCC | 0xC000 | addr; - } - else - { - return NODE_ID_DCC | addr; - } + return NODE_ID_DCC | DCC_LONG_SELECTOR | addr; case dcc::TrainAddressType::MM: return NODE_ID_MARKLIN_MOTOROLA | addr; default: @@ -249,7 +244,8 @@ struct TractionDefs { if ((id & NODE_ID_MASK) == NODE_ID_DCC) { *addr = (id & 0x3FFF); - if (((id & 0xC000) == 0xC000) || (*addr >= 128u)) + if (((id & DCC_LONG_SELECTOR) == DCC_LONG_SELECTOR) || + (*addr >= 128u)) { // overlapping long address *type = dcc::TrainAddressType::DCC_LONG_ADDRESS; diff --git a/src/openlcb/TractionTestTrain.cxxtest b/src/openlcb/TractionTestTrain.cxxtest index d8a1db91d..3fce6d68a 100644 --- a/src/openlcb/TractionTestTrain.cxxtest +++ b/src/openlcb/TractionTestTrain.cxxtest @@ -45,9 +45,9 @@ protected: { create_allocated_alias(); // alias reservation - expect_packet(":X1070133AN0601000006C4;"); + expect_packet(":X1070133AN06010000C6C4;"); // initialized - expect_packet(":X1910033AN0601000006C4;"); + expect_packet(":X1910033AN06010000C6C4;"); trainNode_.reset(new TrainNodeForProxy(&trainService_, &trainImpl_)); wait(); } diff --git a/src/openlcb/TractionThrottle.cxxtest b/src/openlcb/TractionThrottle.cxxtest index 5e390f975..b425afdf8 100644 --- a/src/openlcb/TractionThrottle.cxxtest +++ b/src/openlcb/TractionThrottle.cxxtest @@ -7,7 +7,7 @@ namespace openlcb { -static constexpr NodeID TRAIN_NODE_ID = 0x060100000000 | 1372; +static constexpr NodeID TRAIN_NODE_ID = 0x06010000C000 | 1372; class ThrottleTest : public AsyncNodeTest { diff --git a/src/openlcb/TractionTrain.cxxtest b/src/openlcb/TractionTrain.cxxtest index 96eaf5e02..c30cda516 100644 --- a/src/openlcb/TractionTrain.cxxtest +++ b/src/openlcb/TractionTrain.cxxtest @@ -101,9 +101,9 @@ protected: .Times(AtLeast(0)) .WillRepeatedly(Return(dcc::TrainAddressType::DCC_LONG_ADDRESS)); // alias reservation - expect_packet(":X1070133AN060100003456;"); + expect_packet(":X1070133AN06010000F456;"); // initialized - expect_packet(":X1910033AN060100003456;"); + expect_packet(":X1910033AN06010000F456;"); trainNode_.reset(new TrainNodeForProxy(&trainService_, &m1_)); wait(); } @@ -112,7 +112,7 @@ protected: wait(); } - static const NodeID kTrainNodeID = 0x060100003456U; + static const NodeID kTrainNodeID = 0x06010000F456U; std::unique_ptr trainNode_; };