diff --git a/.clang-format b/.clang-format index 01a63dcba..e7d623a0f 100644 --- a/.clang-format +++ b/.clang-format @@ -43,5 +43,26 @@ SpacesInCStyleCastParentheses: false SpaceAfterControlStatementKeyword: true SpaceBeforeAssignmentOperators: true ContinuationIndentWidth: 4 + +# This big block of settings works around indentation after an extern "C" +BraceWrapping: { + AfterCaseLabel: true, + AfterClass: true, + AfterControlStatement: true, + AfterEnum: true, + AfterFunction: true, + AfterNamespace: true, + AfterObjCDeclaration: true, + AfterStruct: true, + AfterUnion: true, + AfterExternBlock: false, + BeforeCatch: true, + BeforeElse: true, + IndentBraces: false, + SplitEmptyFunction: true, + SplitEmptyRecord: false, + SplitEmptyNamespace: false } +BreakBeforeBraces: Custom +IndentExternBlock: false ... diff --git a/.github/workflows/ArduinoBuild.yml b/.github/workflows/ArduinoBuild.yml new file mode 100644 index 000000000..9ad9a0eba --- /dev/null +++ b/.github/workflows/ArduinoBuild.yml @@ -0,0 +1,49 @@ +# ArduinoBuild.yml +# +# Github workflow script that compiles all examples from the OpenMRNLite +# Arduino library. +# +# Copyright (C) 2021 Balazs Racz +# + +# Name of workflow +name: ArduinoBuild +on: + push: + paths: + - '**.ino' + - '**rduino**' + - '**ArduinoBuild.yml' + pull_request: +jobs: + build: + name: Build ESP32 examples + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Generate OpenMRNLite library + run: | + mkdir --parents $HOME/Arduino/libraries/OpenMRNLite + $GITHUB_WORKSPACE/arduino/libify.sh $HOME/Arduino/libraries/OpenMRNLite $GITHUB_WORKSPACE -f -l + rm -f $GITHUB_WORKSPACE/arduino/examples/Stm*/build_opt.h + + - name: Compile all STM32 examples + uses: ArminJo/arduino-test-compile@v3.0.0 + with: + platform-url: https://raw.githubusercontent.com/stm32duino/BoardManagerFiles/master/STM32/package_stm_index.json + arduino-board-fqbn: STM32:stm32:Nucleo_144:pnum=NUCLEO_F767ZI,upload_method=MassStorage,xserial=generic,usb=CDCgen,xusb=FS,opt=osstd,rtlib=nano + sketch-names: Stm32*.ino + build-properties: '{ "All": "-DHAL_CAN_MODULE_ENABLED" }' + debug-compile: true + + - name: Compile all ESP32 examples + uses: ArminJo/arduino-test-compile@v3.0.0 + with: + platform-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + arduino-board-fqbn: esp32:esp32:node32s + sketch-names: ESP*.ino + debug-compile: true + + diff --git a/DISTCC.md b/DISTCC.md new file mode 100644 index 000000000..3872c7de0 --- /dev/null +++ b/DISTCC.md @@ -0,0 +1,180 @@ +# 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. +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: + ``` + ./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/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/Makefile b/applications/Makefile index e04be9d31..0ec560890 100644 --- a/applications/Makefile +++ b/applications/Makefile @@ -25,6 +25,8 @@ SUBDIRS = \ train \ tcp_blink_client \ usb_can \ + time_client \ + time_server include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/async_blink/main.cxx b/applications/async_blink/main.cxx index 7a3c4e33a..cbf0c8020 100644 --- a/applications/async_blink/main.cxx +++ b/applications/async_blink/main.cxx @@ -63,7 +63,8 @@ #include "utils/ESPWifiClient.hxx" #endif -#if defined (BOARD_LAUNCHPAD_EK) || defined (__linux__) +#if (defined (TARGET_IS_CC3200) || defined (__linux__) || defined(PART_TM4C1294NCPDT)) && (!defined(NO_CONSOLE)) +#define HAVE_CONSOLE #include "console/Console.hxx" #endif @@ -206,9 +207,7 @@ openlcb::BitEventConsumer consumer(&logger); */ int appl_main(int argc, char* argv[]) { -#if defined (BOARD_LAUNCHPAD_EK) - //new Console(stack.executor(), Console::FD_STDIN, Console::FD_STDOUT); -#elif defined (__linux__) +#ifdef HAVE_CONSOLE new Console(stack.executor(), Console::FD_STDIN, Console::FD_STDOUT, 2121); #endif diff --git a/applications/async_blink/targets/Makefile b/applications/async_blink/targets/Makefile index 228c445ca..59e13559a 100644 --- a/applications/async_blink/targets/Makefile +++ b/applications/async_blink/targets/Makefile @@ -1,7 +1,6 @@ SUBDIRS = linux.x86 \ linux.armv7a \ linux.llvm \ - mach.x86 \ mach.x86_64 \ freertos.armv7m.ek-lm4f120xl \ freertos.armv7m.ek-tm4c123gxl \ @@ -13,6 +12,8 @@ SUBDIRS = linux.x86 \ freertos.armv6m.st-stm32f072b-discovery \ freertos.armv7m.st-stm32f103rb-olimexino \ freertos.armv7m.st-stm32f303-discovery \ + freertos.armv7m.st-stm32f303re-nucleo-dev-board \ + freertos.armv7m.st-stm32l432kc-nucleo \ freertos.armv7m.st-maple-cmsis \ freertos.armv4t.panda2 \ freertos.mips4k.pic32mx-duinomitemega \ diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/HwInit.cxx b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/HwInit.cxx new file mode 120000 index 000000000..bdf333297 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/HwInit.cxx \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/Makefile b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/Makefile new file mode 120000 index 000000000..952f1a832 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/Makefile @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/Makefile \ No newline at end of file diff --git a/applications/async_blink/targets/mach.x86/NodeId.cxx b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/NodeId.cxx similarity index 56% rename from applications/async_blink/targets/mach.x86/NodeId.cxx rename to applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/NodeId.cxx index 3cd6d1db7..92d3e27e9 100644 --- a/applications/async_blink/targets/mach.x86/NodeId.cxx +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/NodeId.cxx @@ -1,4 +1,4 @@ #include "openlcb/If.hxx" extern const openlcb::NodeID NODE_ID; -const openlcb::NodeID NODE_ID = 0x050101011410ULL; +const openlcb::NodeID NODE_ID = 0x050101011869ULL; diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/hardware.hxx b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/hardware.hxx new file mode 120000 index 000000000..477a1b21d --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/hardware.hxx \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/memory_map.ld b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/memory_map.ld new file mode 120000 index 000000000..7f99cc39f --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/memory_map.ld \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/startup.c b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/startup.c new file mode 120000 index 000000000..178a8f559 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/startup.c @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/startup.c \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/target.ld b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/target.ld new file mode 120000 index 000000000..bf20b7120 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/target.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32f303re-nucleo-dev-board/target.ld \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx new file mode 120000 index 000000000..8bf2ed18c --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/HwInit.cxx \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile new file mode 120000 index 000000000..e13c2970c --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/Makefile \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/NodeId.cxx b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/NodeId.cxx new file mode 100644 index 000000000..994ff0847 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/NodeId.cxx @@ -0,0 +1,4 @@ +#include "openlcb/If.hxx" + +extern const openlcb::NodeID NODE_ID; +const openlcb::NodeID NODE_ID = 0x050101011867ULL; diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx new file mode 120000 index 000000000..e27187a64 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/hardware.hxx \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld new file mode 120000 index 000000000..52c792882 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/memory_map.ld \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c new file mode 120000 index 000000000..cd5f598b1 --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/startup.c \ No newline at end of file diff --git a/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld new file mode 120000 index 000000000..403497dce --- /dev/null +++ b/applications/async_blink/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/target.ld \ No newline at end of file diff --git a/applications/async_blink/targets/mach.x86/.gitignore b/applications/async_blink/targets/mach.x86/.gitignore deleted file mode 100644 index 43148b79f..000000000 --- a/applications/async_blink/targets/mach.x86/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -async_blink -*_test diff --git a/applications/async_blink/targets/mach.x86_64/Makefile b/applications/async_blink/targets/mach.x86_64/Makefile index 3bb6034b2..c6386dac3 100644 --- a/applications/async_blink/targets/mach.x86_64/Makefile +++ b/applications/async_blink/targets/mach.x86_64/Makefile @@ -1 +1,2 @@ +-include ../../config.mk include $(OPENMRNPATH)/etc/prog.mk diff --git a/applications/blink_raw/main.cxx b/applications/blink_raw/main.cxx index d6eca82d7..387a2abc2 100644 --- a/applications/blink_raw/main.cxx +++ b/applications/blink_raw/main.cxx @@ -41,15 +41,6 @@ #include "os/os.h" #include "utils/blinker.h" -#include "console/Console.hxx" - -#if (defined (TARGET_IS_CC3200) || defined (__linux__) || defined(PART_TM4C1294NCPDT)) && (!defined(NO_CONSOLE)) -#define HAVE_CONSOLE -#endif - -#ifdef HAVE_CONSOLE -Executor<1> executor("executor", 0, 2048); -#endif /** Entry point to application. * @param argc number of command line arguments @@ -59,9 +50,6 @@ Executor<1> executor("executor", 0, 2048); int appl_main(int argc, char *argv[]) { setblink(0); -#ifdef HAVE_CONSOLE - new Console(&executor, Console::FD_STDIN, Console::FD_STDOUT, 2121); -#endif while (1) { resetblink(1); diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx new file mode 120000 index 000000000..8bf2ed18c --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/HwInit.cxx \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile new file mode 120000 index 000000000..e13c2970c --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/Makefile \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx new file mode 120000 index 000000000..e27187a64 --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/hardware.hxx \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld new file mode 120000 index 000000000..52c792882 --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/memory_map.ld \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c new file mode 120000 index 000000000..cd5f598b1 --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/startup.c \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld new file mode 120000 index 000000000..403497dce --- /dev/null +++ b/applications/blink_raw/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/target.ld \ No newline at end of file diff --git a/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile b/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile index a2248d081..b75c16173 100644 --- a/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile +++ b/applications/blink_raw/targets/freertos.armv7m.ti-launchpad-tm4c129/Makefile @@ -34,6 +34,13 @@ export BOARD OPENOCDARGS = -f board/ek-tm4c1294xl.cfg +# this works for XDS110 with SWD (2 wire) +#OPENOCDARGS = -c 'set TRANSPORT swd' -f interface/xds110.cfg -c 'transport select swd' -c 'set WORKAREASIZE 0x8000' -c 'set CHIPNAME tm4c1294ncpdt' -f target/stellaris.cfg + +#OPENOCDARGS = -f interface/xds110.cfg -c 'set WORKAREASIZE 0x8000' -c 'set CHIPNAME tm4c1294ncpdt' -f target/stellaris.cfg + +# this works for XDS110 with JTAG (4 wire) +#OPENOCDARGS = -f interface/xds110.cfg -c 'transport select jtag' -c 'set WORKAREASIZE 0x8000' -c 'set CHIPNAME tm4c1294ncpdt' -f target/stellaris.cfg include $(OPENMRNPATH)/etc/prog.mk @@ -41,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 halt" -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 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/applications/bootloader_client/main.hxx b/applications/bootloader_client/main.hxx new file mode 100644 index 000000000..e80c98fc5 --- /dev/null +++ b/applications/bootloader_client/main.hxx @@ -0,0 +1,348 @@ +/** \copyright + * Copyright (c) 2013, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file main.hxx + * + * An application for updating the firmware of a remote node on the bus. + * + * @author Balazs Racz + * @date 3 Aug 2013 + */ + +#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; +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] [-M hw_magic2]] [-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, + "\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, + "\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: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 'M': + hardware_magic2 = 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] = 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] = hardware_magic2; + + memcpy(&(*firmware)[offset], &hdr, sizeof(hdr)); + printf("Checksummed firmware with algorithm cc3200 (0x%08x 0x%08x)\n", + (unsigned)hardware_magic, (unsigned)hardware_magic2); + } + 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); + } +} + +Buffer *fill_request() +{ + Buffer *b; + mainBufferPool->alloc(&b); + + 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.\n", b->data()->data.size(), + filename); + maybe_checksum(&b->data()->data); + + return b; +} + +bool process_dump() +{ + if (!dump_filename) + { + return false; + } + 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/applications/clinic_app/targets/Makefile b/applications/clinic_app/targets/Makefile index f999fd393..8b3bed0a4 100644 --- a/applications/clinic_app/targets/Makefile +++ b/applications/clinic_app/targets/Makefile @@ -1,11 +1,11 @@ SUBDIRS = \ - freertos.armv7m.ek-tm4c123gxl \ - freertos.armv7m.ek-tm4c1294xl \ - freertos.armv6m.st-stm32f072b-discovery \ - freertos.armv7m.st-stm32f303-discovery \ + freertos.armv7m.ek-tm4c123gxl \ + freertos.armv7m.ek-tm4c1294xl \ + freertos.armv6m.st-stm32f072b-discovery \ + freertos.armv7m.st-stm32f303-discovery \ linux.armv7a \ linux.x86 \ - mach.x86 + mach.x86_64 # freertos.armv7m.lpc1768-mbed \ diff --git a/applications/clinic_app/targets/mach.x86/.gitignore b/applications/clinic_app/targets/mach.x86_64/.gitignore similarity index 100% rename from applications/clinic_app/targets/mach.x86/.gitignore rename to applications/clinic_app/targets/mach.x86_64/.gitignore diff --git a/applications/clinic_app/targets/mach.x86/Makefile b/applications/clinic_app/targets/mach.x86_64/Makefile similarity index 100% rename from applications/clinic_app/targets/mach.x86/Makefile rename to applications/clinic_app/targets/mach.x86_64/Makefile diff --git a/applications/clinic_app/targets/mach.x86/config.hxx b/applications/clinic_app/targets/mach.x86_64/config.hxx similarity index 100% rename from applications/clinic_app/targets/mach.x86/config.hxx rename to applications/clinic_app/targets/mach.x86_64/config.hxx diff --git a/applications/clinic_app/targets/mach.x86/main.cxx b/applications/clinic_app/targets/mach.x86_64/main.cxx similarity index 100% rename from applications/clinic_app/targets/mach.x86/main.cxx rename to applications/clinic_app/targets/mach.x86_64/main.cxx diff --git a/applications/dcc_cs_login/Makefile b/applications/dcc_cs_login/Makefile new file mode 100644 index 000000000..f5cedde67 --- /dev/null +++ b/applications/dcc_cs_login/Makefile @@ -0,0 +1,3 @@ +SUBDIRS = targets +-include config.mk +include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/dcc_cs_login/TrainStorage.hxx b/applications/dcc_cs_login/TrainStorage.hxx new file mode 100644 index 000000000..20e0dff4f --- /dev/null +++ b/applications/dcc_cs_login/TrainStorage.hxx @@ -0,0 +1,102 @@ +/** \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 TrainStorage.hxx + * + * Storage policies for trains in the automatic logon example. + * + * @author Balazs Racz + * @date 14 Aug 2021 + */ + +#ifndef _APPLICATION_CS_LOGON_TRAINSTORAGE_HXX_ +#define _APPLICATION_CS_LOGON_TRAINSTORAGE_HXX_ + + +#include "dcc/Defs.hxx" +#include "dcc/Loco.hxx" +#include "dcc/LogonModule.hxx" +#include "openlcb/EventHandlerTemplates.hxx" +#include "openlcb/TractionTrain.hxx" +#include "utils/macros.h" + +class ModuleBase { +public: + struct Storage + { + std::unique_ptr impl_; + std::unique_ptr node_; + std::unique_ptr< + openlcb::FixedEventProducer> + eventProducer_; + }; +}; + +class TrainLogonModule : public dcc::ParameterizedLogonModule { +public: + TrainLogonModule(openlcb::TrainService *s) + : trainService_(s) + { + } + + using Base = ParameterizedLogonModule; + + void assign_complete(unsigned loco_id) + { + Base::assign_complete(loco_id); + auto& t = locos_[loco_id]; + uint8_t part = (t.assignedAddress_ >> 8) & dcc::Defs::ADR_MASK; + + if (part == dcc::Defs::ADR_MOBILE_SHORT) { + LOG(INFO, "started a short address decoder"); + t.impl_.reset(new dcc::Dcc28Train( + dcc::DccShortAddress(t.assignedAddress_ & 0x7f))); + } + else if (part >= dcc::Defs::ADR_MOBILE_LONG && + part <= dcc::Defs::MAX_MOBILE_LONG) + { + LOG(INFO, "started a long address decoder"); + t.impl_.reset(new dcc::Dcc28Train(dcc::DccLongAddress( + t.assignedAddress_ - (dcc::Defs::ADR_MOBILE_LONG << 8)))); + } else { + // Not a mobile decoder. We don't have an implementation for those + // yet. + LOG(INFO, "started a non-mobile decoder %04x %x", + t.assignedAddress_, part); + return; + } + t.node_.reset(new openlcb::TrainNodeForProxy( + trainService_, t.impl_.get())); + t.eventProducer_.reset(new openlcb::FixedEventProducer< + openlcb::TractionDefs::IS_TRAIN_EVENT>(t.node_.get())); + } + +private: + openlcb::TrainService* trainService_; +}; + + +#endif // _APPLICATION_CS_LOGON_TRAINSTORAGE_HXX_ diff --git a/applications/dcc_cs_login/config.mk b/applications/dcc_cs_login/config.mk new file mode 120000 index 000000000..e270c0389 --- /dev/null +++ b/applications/dcc_cs_login/config.mk @@ -0,0 +1 @@ +../default_config.mk \ No newline at end of file diff --git a/applications/dcc_cs_login/subdirs b/applications/dcc_cs_login/subdirs new file mode 100644 index 000000000..4e0254829 --- /dev/null +++ b/applications/dcc_cs_login/subdirs @@ -0,0 +1,2 @@ +SUBDIRS = \ + diff --git a/applications/dcc_cs_login/targets/.gitignore b/applications/dcc_cs_login/targets/.gitignore new file mode 100644 index 000000000..492730923 --- /dev/null +++ b/applications/dcc_cs_login/targets/.gitignore @@ -0,0 +1,2 @@ +compile_cdi + diff --git a/applications/dcc_cs_login/targets/Makefile b/applications/dcc_cs_login/targets/Makefile new file mode 100644 index 000000000..171d1de9e --- /dev/null +++ b/applications/dcc_cs_login/targets/Makefile @@ -0,0 +1,4 @@ +SUBDIRS = \ + freertos.armv7m.ek-tm4c123gxl \ + +include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx new file mode 120000 index 000000000..d3fdcc84a --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/Makefile b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/Makefile new file mode 120000 index 000000000..65ed1ddfb --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/Makefile @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/Makefile \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/config.hxx b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/config.hxx new file mode 100644 index 000000000..97b294a3c --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/config.hxx @@ -0,0 +1,68 @@ +#ifndef _APPLICATIONS_DCC_CS_LOGIN_TARGET_CONFIG_HXX_ +#define _APPLICATIONS_DCC_CS_LOGIN_TARGET_CONFIG_HXX_ + +#include "openlcb/ConfiguredConsumer.hxx" +#include "openlcb/ConfiguredProducer.hxx" +#include "openlcb/ConfigRepresentation.hxx" +#include "openlcb/MemoryConfig.hxx" + +namespace openlcb +{ + +/// Defines the identification information for the node. The arguments are: +/// +/// - 4 (version info, always 4 by the standard +/// - Manufacturer name +/// - Model name +/// - Hardware version +/// - Software version +/// +/// This data will be used for all purposes of the identification: +/// +/// - the generated cdi.xml will include this data +/// - the Simple Node Ident Info Protocol will return this data +/// - the ACDI memory space will contain this data. +extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { + 4, "OpenMRN", "DCC CS with Logon - Tiva Launchpad 123", + "ek-tm4c123gxl", "1.01"}; + +#define NUM_OUTPUTS 3 +#define NUM_INPUTS 2 + +/// Declares a repeated group of a given base group and number of repeats. The +/// ProducerConfig and ConsumerConfig groups represent the configuration layout +/// needed by the ConfiguredProducer and ConfiguredConsumer classes, and come +/// from their respective hxx file. +using AllConsumers = RepeatedGroup; +using AllProducers = RepeatedGroup; + +/// Modify this value every time the EEPROM needs to be cleared on the node +/// after an update. +static constexpr uint16_t CANONICAL_VERSION = 0x1111; + + +/// Defines the main segment in the configuration CDI. This is laid out at +/// origin 128 to give space for the ACDI user data at the beginning. +CDI_GROUP(IoBoardSegment, Segment(MemoryConfigDefs::SPACE_CONFIG), Offset(128)); +/// Each entry declares the name of the current entry, then the type and then +/// optional arguments list. +CDI_GROUP_ENTRY(internal_config, InternalConfigData); +CDI_GROUP_END(); + +/// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx +/// to refer to the configuration defined here. +CDI_GROUP(ConfigDef, MainCdi()); +/// Adds the tag with the values from SNIP_STATIC_DATA above. +CDI_GROUP_ENTRY(ident, Identification); +/// Adds an tag. +CDI_GROUP_ENTRY(acdi, Acdi); +/// Adds a segment for changing the values in the ACDI user-defined +/// space. UserInfoSegment is defined in the system header. +CDI_GROUP_ENTRY(userinfo, UserInfoSegment); +/// Adds the main configuration segment. +CDI_GROUP_ENTRY(seg, IoBoardSegment); +CDI_GROUP_END(); + +} // namespace openlcb + +#endif // _APPLICATIONS_DCC_CS_LOGIN_TARGET_CONFIG_HXX_ diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/hardware.hxx b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/hardware.hxx new file mode 120000 index 000000000..f5521469a --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/hardware.hxx \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx new file mode 100644 index 000000000..f9c6799e9 --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx @@ -0,0 +1,220 @@ +/** \copyright + * Copyright (c) 2013, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file main.cxx + * + * Main file for the DCC CS with Logon application on the Tiva Launchpad board. + * + * @author Balazs Racz + * @date 11 Aug 2021 + */ + +#define LOGLEVEL INFO + +#include "os/os.h" +#include "nmranet_config.h" + +#include "openlcb/SimpleStack.hxx" +#include "openlcb/TractionTrain.hxx" +#include "openlcb/EventHandlerTemplates.hxx" +#include "dcc/Loco.hxx" +#include "dcc/Logon.hxx" +#include "dcc/SimpleUpdateLoop.hxx" +#include "dcc/LocalTrackIf.hxx" +#include "dcc/RailcomHub.hxx" +#include "dcc/RailcomPortDebug.hxx" +#include "executor/PoolToQueueFlow.hxx" +#include "openlcb/TractionCvSpace.hxx" + +#include "freertos_drivers/ti/TivaGPIO.hxx" +#include "freertos_drivers/common/BlinkerGPIO.hxx" +#include "freertos_drivers/common/PersistentGPIO.hxx" +#include "config.hxx" +#include "hardware.hxx" +#include "TrainStorage.hxx" + +#include "utils/stdio_logging.h" + +// These preprocessor symbols are used to select which physical connections +// will be enabled in the main(). See @ref appl_main below. +//#define SNIFF_ON_SERIAL +#define SNIFF_ON_USB +//#define HAVE_PHYSICAL_CAN_PORT + +// Changes the default behavior by adding a newline after each gridconnect +// packet. Makes it easier for debugging the raw device. +OVERRIDE_CONST(gc_generate_newlines, 1); +// Specifies how much RAM (in bytes) we allocate to the stack of the main +// thread. Useful tuning parameter in case the application runs out of memory. +OVERRIDE_CONST(main_thread_stack_size, 2500); + +OVERRIDE_CONST(local_nodes_count, 30); +OVERRIDE_CONST(local_alias_cache_size, 32); + +// Specifies the 48-bit OpenLCB node identifier. This must be unique for every +// hardware manufactured, so in production this should be replaced by some +// easily incrementable method. +extern const openlcb::NodeID NODE_ID = 0x0501010118DAULL; + +// Sets up a comprehensive OpenLCB stack for a single virtual node. This stack +// contains everything needed for a usual peripheral node -- all +// CAN-bus-specific components, a virtual node, PIP, SNIP, Memory configuration +// protocol, ACDI, CDI, a bunch of memory spaces, etc. +openlcb::SimpleCanStack stack(NODE_ID); + +// ConfigDef comes from config.hxx and is specific to the particular device and +// target. It defines the layout of the configuration memory space and is also +// used to generate the cdi.xml file. Here we instantiate the configuration +// layout. The argument of offset zero is ignored and will be removed later. +openlcb::ConfigDef cfg(0); +// Defines weak constants used by the stack to tell it which device contains +// the volatile configuration information. This device name appears in +// HwInit.cxx that creates the device drivers. +extern const char *const openlcb::CONFIG_FILENAME = "/dev/eeprom"; +// The size of the memory space to export over the above device. +extern const size_t openlcb::CONFIG_FILE_SIZE = + cfg.seg().size() + cfg.seg().offset(); +static_assert(openlcb::CONFIG_FILE_SIZE <= 300, "Need to adjust eeprom size"); +// The SNIP user-changeable information in also stored in the above eeprom +// device. In general this could come from different eeprom segments, but it is +// simpler to keep them together. +extern const char *const openlcb::SNIP_DYNAMIC_FILENAME = + openlcb::CONFIG_FILENAME; + +/// This timer checks the eeprom once a second and if the user has written +/// something, executes a reload of the configuration via the OpenLCB config +/// service. +class AutoUpdateTimer : public ::Timer +{ +public: + AutoUpdateTimer() + : ::Timer(stack.executor()->active_timers()) + { + start(SEC_TO_NSEC(1)); + } + + long long timeout() override + { + extern uint8_t eeprom_updated; + if (eeprom_updated) + { + needUpdate_ = true; + eeprom_updated = 0; + } + else + { + if (needUpdate_) + { + stack.config_service()->trigger_update(); + needUpdate_ = false; + } + } + return RESTART; + } + + bool needUpdate_ {false}; +} update_timer; + +// ====== Command Station components ======= +OVERRIDE_CONST(num_memory_spaces, 10); + +dcc::LocalTrackIf track(stack.service(), 2); +dcc::SimpleUpdateLoop updateLoop(stack.service(), &track); +PoolToQueueFlow> pool_translator( + stack.service(), track.pool(), &updateLoop); + +openlcb::TrainService trainService(stack.iface()); + +dcc::Dcc28Train train3Impl(dcc::DccShortAddress(3)); +openlcb::TrainNodeForProxy train3Node(&trainService, &train3Impl); +openlcb::FixedEventProducer + trainEventProducer(&train3Node); + +// ===== RailCom components ====== +dcc::RailcomHubFlow railcom_hub(stack.service()); +openlcb::RailcomToOpenLCBDebugProxy gRailcomProxy(&railcom_hub, stack.node(), + nullptr, true /*broadcast enabled*/, false /* ack enabled */); + +openlcb::TractionCvSpace traction_cv(stack.memory_config_handler(), &track, + &railcom_hub, openlcb::MemoryConfigDefs::SPACE_DCC_CV); + +// ===== Logon components ===== +TrainLogonModule module{&trainService}; +dcc::LogonHandler logonHandler{ + &trainService, &track, &railcom_hub, &module}; + +/** 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[]) +{ + stack.check_version_and_factory_reset( + cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, false); + + int fd = ::open("/dev/mainline", O_WRONLY); + HASSERT(fd >= 0); + track.set_fd(fd); + + // The necessary physical ports must be added to the stack. + // + // It is okay to enable multiple physical ports, in which case the stack + // will behave as a bridge between them. For example enabling both the + // physical CAN port and the USB port will make this firmware act as an + // USB-CAN adapter in addition to the producers/consumers created above. + // + // If a port is enabled, it must be functional or else the stack will + // freeze waiting for that port to send the packets out. +#if defined(HAVE_PHYSICAL_CAN_PORT) + stack.add_can_port_select("/dev/can0"); +#endif +#if defined(SNIFF_ON_USB) + stack.add_gridconnect_port("/dev/serUSB0"); +#endif +#if defined(SNIFF_ON_SERIAL) + stack.add_gridconnect_port("/dev/ser0"); +#endif + + HubDeviceNonBlock railcom_port(&railcom_hub, + "/dev/railcom"); + + // Enables weak pull-up on the UART input pin. + MAP_GPIOPadConfigSet(RAILCOM_CH1_Pin::GPIO_BASE, RAILCOM_CH1_Pin::GPIO_PIN, + GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU); + + /// @todo store the session ID and generate a new one every startup. + logonHandler.startup_logon(0x3344, 25); + + LOG(ALWAYS, "hello world"); + + // This command donates the main thread to the operation of the + // stack. Alternatively the stack could be started in a separate stack and + // then application-specific business logic could be executed ion a busy + // loop in the main thread. + stack.loop_executor(); + return 0; +} diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/memory_map.ld b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/memory_map.ld new file mode 120000 index 000000000..c4f4a2559 --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/memory_map.ld \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/startup.c b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/startup.c new file mode 120000 index 000000000..ab6b1925f --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/startup.c @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/startup.c \ No newline at end of file diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/target.ld b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/target.ld new file mode 120000 index 000000000..accf48b3b --- /dev/null +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/target.ld @@ -0,0 +1 @@ +../../../../boards/ti-ek-tm4c123gxl-launchpad/target.ld \ No newline at end of file diff --git a/applications/dcc_decoder/main.cxx b/applications/dcc_decoder/main.cxx index 3d7ca40e0..e6dfc72ea 100644 --- a/applications/dcc_decoder/main.cxx +++ b/applications/dcc_decoder/main.cxx @@ -32,21 +32,385 @@ * @date 4 Sep 2018 */ +#include #include #include -#include -#include "os/os.h" -#include "executor/Executor.hxx" #include "dcc/DccDebug.hxx" -#include "utils/constants.hxx" +#include "dcc/PacketProcessor.hxx" +#include "dcc/RailCom.hxx" +#include "executor/Executor.hxx" +#include "freertos/tc_ioctl.h" +#include "freertos_drivers/common/RailcomDriver.hxx" +#include "os/os.h" #include "utils/StringPrintf.hxx" +#include "utils/constants.hxx" +#include "utils/Crc.hxx" + +#include "hardware.hxx" Executor<1> executor("executor", 0, 2048); + +/// If true, sends channel1 broadcast on mobile decoder addresses. +static const bool SEND_CH1_BROADCAST = false; +/// If true, sends ack to all correctly received regular addressed packet. +static const bool SEND_CH2_ACK = false; + // We reserve a lot of buffer for transmit to cover for small hiccups in the // host reading data. OVERRIDE_CONST(serial_tx_buffer_size, 2048); +OVERRIDE_CONST(main_thread_priority, 3); + +// The DCC address to listen at. This is in wire format. The first address byte +// is the high byte. +uint16_t dcc_address_wire = 3 << 8; + +uint8_t f0 = 0; + +/// DIY / public domain decoder. +const uint16_t manufacturer_id = 0x0D; +uint64_t decoder_id = 0; + +/// Last known command station ID. +uint16_t cid; +/// Last known session ID. +uint8_t session_id; +/// 1 if we have been selected in this session. +uint8_t is_selected = 0; + + +/// Stores a programming packet which is conditional on a repetition. +DCCPacket progStored; +/// Feedback for the stored programming packet. +dcc::Feedback fbStored; + +// File descriptor for the eeprom storage. +int eefd; + +/// Constants used for DCC packet decoding. +enum PacketCode { + /// Which bits in the command are coding the CV number high bits. + DCC_PROG_CVMASK = 3, + /// POM read 1 byte command + DCC_PROG_READ1 = 0b11100100, + /// POM write 1 byte command + DCC_PROG_WRITE1 = 0b11101100, +}; + +/// Reads CVs. Should only be called from the main thread. +void read_cvs(uint32_t ofs, unsigned len, uint8_t *dst) +{ + for (unsigned i = 0; i < len; i++) + { + dst[i] = 42; + } +} + +void write_cv(uint32_t ofs, uint8_t value) { + +} + +/// Checks if a packet is addressed to our current DCC address. +/// @param payload the DCC packet payload +/// @return true if addressed to us. +bool match_dcc_address(const uint8_t *payload) +{ + return ((payload[0] == (dcc_address_wire >> 8)) && + ((dcc_address_wire < (128 << 8)) || + (payload[1] == (dcc_address_wire & 0xff)))); +} + +/// @return true if the two DCC packets are the same on the wire. +bool packet_match(const DCCPacket &a, const DCCPacket &b) +{ + if (a.dlc != b.dlc) + { + return false; + } + for (unsigned i = 0; i < a.dlc; i++) + { + if (a.payload[i] != b.payload[i]) + return false; + } + return true; +} + +class IrqProcessor : public dcc::PacketProcessor +{ +public: + IrqProcessor() + { + } + + /// Called in the main to prepare the railcom feedback packets. + void init() + { + // Figures out the decoder ID. + Crc8DallasMaxim m; + uint8_t *p = (uint8_t*)0x1FFFF7AC; + uint32_t d = 0; + + // Computes the decoder ID using a hash of the chip serial number. + m.update16(*p++); + m.update16(*p++); + d |= m.get(); + d <<= 8; + m.update16(*p++); + m.update16(*p++); + d |= m.get(); + d <<= 8; + m.update16(*p++); + m.update16(*p++); + m.update16(*p++); + m.update16(*p++); + d |= m.get(); + d <<= 8; + m.update16(*p++); + m.update16(*p++); + m.update16(*p++); + m.update16(*p++); + d |= m.get(); + decoder_id = manufacturer_id; + decoder_id <<= 32; + decoder_id |= d; + + update_address(); + // Programming response packet + ((dcc::Packet*)&progStored)->clear(); + // 6-byte Ack packet + ack_.reset(0); + for (unsigned i = 0; i < 6; i++) + { + ack_.add_ch2_data(dcc::RailcomDefs::CODE_ACK); + } + // Also usable for 8-byte ack packet + ack_.add_ch1_data(dcc::RailcomDefs::CODE_ACK); + ack_.add_ch1_data(dcc::RailcomDefs::CODE_ACK); + // Decoder ID feedback packet. + dcc::RailcomDefs::add_did_feedback(decoder_id, &did_); + + // ShortInfo feedback packet + uint16_t addr; + if (dcc_address_wire & 0x8000) { + // 14 bit address + addr = dcc_address_wire - 0xC000 + (dcc::Defs::ADR_MOBILE_LONG << 8); + } else { + addr = (dcc::Defs::ADR_MOBILE_SHORT << 8) | + ((dcc_address_wire >> 8) & 0x7f); + } + dcc::RailcomDefs::add_shortinfo_feedback(addr, 0 /*max fn*/, + 0 /* capabilities */, 0 /*spaces*/, &shortInfo_); + is_selected = 0; + cid = 0xFFFF; + session_id = 0xFF; + + // Logon assign feedback + dcc::RailcomDefs::add_assign_feedback(0xff /* changeflags */, + 0xfff /*changecount*/, 0 /* capabilities 2 */, 0 /*capabilties 3*/, + &assignInfo_); + } + + /// Updates the broadcast datagrams based on the active DCC address. + void update_address() + { + bcastHigh_.reset(0); + bcastLow_.reset(0); + if (dcc_address_wire < (128 << 8)) + { + dcc::RailcomDefs::append12( + dcc::RMOB_ADRHIGH, 0, bcastHigh_.ch1Data); + dcc::RailcomDefs::append12( + dcc::RMOB_ADRLOW, dcc_address_wire >> 8, bcastLow_.ch1Data); + } + else + { + uint8_t ah = 0x80 | ((dcc_address_wire >> 8) & 0x3F); + uint8_t al = dcc_address_wire & 0xFF; + dcc::RailcomDefs::append12( + dcc::RMOB_ADRHIGH, ah, bcastHigh_.ch1Data); + dcc::RailcomDefs::append12(dcc::RMOB_ADRLOW, al, bcastLow_.ch1Data); + } + bcastHigh_.ch1Size = 2; + bcastLow_.ch1Size = 2; + } + + /// Called from the interrupt routine to process a packet and generate the + /// railcom response. + /// @param pkt the last received packet + /// @param railcom pointer to the railcom driver where the railcom feedback + /// needs to be sent. + void packet_arrived(const DCCPacket *pkt, RailcomDriver *railcom) override + { + using namespace dcc::Defs; + DEBUG1_Pin::set(true); + if (pkt->packet_header.csum_error) + { + return; + } + uint8_t adrhi = pkt->payload[0]; + if (adrhi && (adrhi < 232) && ((adrhi & 0xC0) != 0x80) && + SEND_CH1_BROADCAST) + { + // Mobile decoder addressed. Send back address. + if (bcastAtHi_) + { + bcastHigh_.feedbackKey = pkt->feedback_key; + railcom->send_ch1(&bcastHigh_); + } + else + { + bcastLow_.feedbackKey = pkt->feedback_key; + railcom->send_ch1(&bcastLow_); + } + bcastAtHi_ ^= 1; + } + // Checks for regular addressing. + if (match_dcc_address(pkt->payload)) + { + // Addressed packet to our DCC address. + + // Check for a known POM packet. + if (packet_match(*pkt, progStored) && + (fbStored.feedbackKey == progStored.feedback_key)) + { + prog_ = fbStored; + prog_.feedbackKey = pkt->feedback_key; + railcom->send_ch2(&prog_); + } + else if (SEND_CH2_ACK) + { + // No specific reply prepared -- send just some acks. + ack_.feedbackKey = pkt->feedback_key; + railcom->send_ch2(&ack_); + } + } + if (adrhi == dcc::Defs::ADDRESS_LOGON) + { + if (pkt->dlc == 6 && + ((pkt->payload[1] & DCC_LOGON_ENABLE_MASK) == + dcc::Defs::DCC_LOGON_ENABLE) && + !is_selected && + ((pkt->payload[1] & ~DCC_LOGON_ENABLE_MASK) != + (uint8_t)LogonEnableParam::ACC)) + { + /// @todo add code for backoff algorithm. + + /// @todo recognize whether we are talking to the same command + /// station. + + did_.feedbackKey = pkt->feedback_key; + railcom->send_ch1(&did_); + railcom->send_ch2(&did_); + } + if (pkt->payload[1] >= DCC_DID_MIN && + pkt->payload[1] <= DCC_DID_MAX && pkt->dlc >= 7 && + ((pkt->payload[1] & 0x0F) == ((decoder_id >> 40) & 0x0f)) && + (pkt->payload[2] == ((decoder_id >> 32) & 0xff)) && + (pkt->payload[3] == ((decoder_id >> 24) & 0xff)) && + (pkt->payload[4] == ((decoder_id >> 16) & 0xff)) && + (pkt->payload[5] == ((decoder_id >> 8) & 0xff)) && + (pkt->payload[6] == ((decoder_id)&0xff))) + { + // Correctly addressed UID packet. + if ((pkt->payload[1] & DCC_DID_MASK) == DCC_SELECT && + pkt->dlc > 7 && pkt->payload[7] == CMD_READ_SHORT_INFO) + { + shortInfo_.feedbackKey = pkt->feedback_key; + railcom->send_ch1(&shortInfo_); + railcom->send_ch2(&shortInfo_); + is_selected = 1; + } + else if ((pkt->payload[1] & DCC_DID_MASK) == DCC_LOGON_ASSIGN) + { + assignInfo_.feedbackKey = pkt->feedback_key; + railcom->send_ch1(&assignInfo_); + railcom->send_ch2(&assignInfo_); + } + else + { + ack_.feedbackKey = pkt->feedback_key; + railcom->send_ch1(&ack_); + railcom->send_ch2(&ack_); + } + } + } + DEBUG1_Pin::set(false); + } + +private: + /// RailCom packet to send for address high in the broadcast channel. + dcc::Feedback bcastHigh_; + /// RailCom packet to send for address low in the broadcast channel. + dcc::Feedback bcastLow_; + + /// Copy of the stored programming feedback. + dcc::Feedback prog_; + + /// RailCom packet with all ACKs in channel2. + dcc::Feedback ack_; + + /// Railcom packet with decoder ID for logon. + dcc::Feedback did_; + + /// Railcom packet with ShortInfo for select. + dcc::Feedback shortInfo_; + + /// Railcom packet with decoder state for Logon Assign. + dcc::Feedback assignInfo_; + + /// 1 if the next broadcast packet should be adrhi, 0 if adrlo. + uint8_t bcastAtHi_ : 1; +} irqProc; + +/// Called from main with the last received packet. +/// @param p the DCC packet from the track. +void process_packet(const DCCPacket &p) +{ + if (p.packet_header.csum_error) + { + return; + } + if (p.dlc < 3) + { + return; + } + unsigned ofs = 1; + if (match_dcc_address(p.payload)) + { + if (dcc_address_wire >= (128 << 8)) + { + // Two byte address. + ofs++; + } + if ((p.payload[ofs] >> 5) == 0b100) + { + // F0-F4 packet + ofs++; + f0 = p.payload[ofs] & 0b00010000 ? 1 : 0; + } else if ((p.payload[ofs] & ~DCC_PROG_CVMASK) == DCC_PROG_READ1) { + if (packet_match(p, progStored)) + { + // We already processed this packet, nothing to do. + return; + } + fbStored.reset(0); + progStored = p; + uint32_t cv = ((p.payload[ofs] & DCC_PROG_CVMASK) << 8) | + (p.payload[ofs + 1]); + uint8_t value; + read_cvs(cv, 1, &value); + dcc::RailcomDefs::append12(dcc::RMOB_POM, value, fbStored.ch2Data); + fbStored.ch2Size = 2; + fbStored.feedbackKey = progStored.feedback_key; + } + } +} + +extern "C" +{ + void set_dcc_interrupt_processor(dcc::PacketProcessor *p); +} /** Entry point to application. * @param argc number of command line arguments @@ -58,15 +422,30 @@ int appl_main(int argc, char *argv[]) setblink(0); int fd = ::open("/dev/dcc_decoder0", O_RDONLY); HASSERT(fd >= 0); - //int wfd = ::open("/dev/serUSB0", O_RDWR); + // int wfd = ::open("/dev/serUSB0", O_RDWR); int wfd = ::open("/dev/ser0", O_RDWR); HASSERT(wfd >= 0); + int rcfd = ::open("/dev/ser1", O_WRONLY); + HASSERT(rcfd >= 0); + auto ret = ::ioctl(rcfd, TCBAUDRATE, 250000); + HASSERT(ret == 0); + 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); + int cnt = 0; while (1) { DCCPacket packet_data; int sret = ::read(fd, &packet_data, sizeof(packet_data)); HASSERT(sret == sizeof(packet_data)); + DEBUG1_Pin::set(true); + process_packet(packet_data); long long t = os_get_time_monotonic(); string txt = StringPrintf("\n%02d.%06d %04d ", (unsigned)((t / 1000000000) % 100), @@ -78,7 +457,9 @@ int appl_main(int argc, char *argv[]) // not enough space in the serial write buffer, we need to throw away // data. ++cnt; - resetblink((cnt >> 3) & 1); + uint8_t blink = ((cnt >> 3) & 15) == 1u ? 1 : 0; + resetblink(f0 ^ blink); + DEBUG1_Pin::set(false); } return 0; } diff --git a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx index 853b0d1b4..71a782777 100644 --- a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx +++ b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx @@ -48,6 +48,7 @@ #include "Stm32Uart.hxx" #include "Stm32Can.hxx" #include "Stm32EEPROMEmulation.hxx" +#include "Stm32RailcomSender.hxx" #include "hardware.hxx" #include "DummyGPIO.hxx" @@ -73,6 +74,9 @@ const char *STDERR_DEVICE = "/dev/ser0"; /** UART 0 serial driver instance */ static Stm32Uart uart0("/dev/ser0", USART2, USART2_IRQn); +/** RailCom sender UART */ +static Stm32RailcomSender railcomUart("/dev/ser1", USART1, USART1_IRQn); + /** CAN 0 CAN driver instance */ static Stm32Can can0("/dev/can0"); @@ -122,6 +126,24 @@ struct DccDecoderHW /// driver to take a feedback sample. static inline void after_feedback_hook() {} + /// How many usec later/earlier should the railcom cutout start happen. + static int time_delta_railcom_pre_usec() + { + return 80 - 26; + } + + /// How many usec later/earlier should the railcom cutout middle happen. + static int time_delta_railcom_mid_usec() + { + return 193 - 185; + } + + /// How many usec later/earlier should the railcom cutout end happen. + static int time_delta_railcom_end_usec() + { + return 0; + } + /// Second timer resource that will be used to measure microseconds for the /// railcom cutout. May be the same as the Capture Timer, if there are at /// least two channels on that timer resource. @@ -142,14 +164,16 @@ struct DccDecoderHW static constexpr auto OS_IRQn = TSC_IRQn; }; -// Dummy implementation because we are not a railcom detector. -NoRailcomDriver railcom_driver; - Stm32DccDecoder dcc_decoder0( - "/dev/dcc_decoder0", &railcom_driver); + "/dev/dcc_decoder0", &railcomUart); extern "C" { +void set_dcc_interrupt_processor(dcc::PacketProcessor *p) +{ + dcc_decoder0.set_packet_processor(p); +} + /** Blink LED */ uint32_t blinker_pattern = 0; static uint32_t rest_pattern = 0; @@ -256,6 +280,7 @@ void hw_preinit(void) __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_USART2_CLK_ENABLE(); __HAL_RCC_CAN1_CLK_ENABLE(); __HAL_RCC_TIM14_CLK_ENABLE(); @@ -275,6 +300,14 @@ void hw_preinit(void) gpio_init.Pin = GPIO_PIN_3; HAL_GPIO_Init(GPIOA, &gpio_init); + /* USART1 pinmux on railCom TX pin PB6 with open drain and pullup */ + gpio_init.Mode = GPIO_MODE_AF_OD; + gpio_init.Pull = GPIO_PULLUP; + gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + gpio_init.Alternate = GPIO_AF0_USART1; + gpio_init.Pin = GPIO_PIN_6; + HAL_GPIO_Init(GPIOB, &gpio_init); + /* CAN pinmux on PB8 and PB9 */ gpio_init.Mode = GPIO_MODE_AF_PP; gpio_init.Pull = GPIO_PULLUP; @@ -307,7 +340,7 @@ void hw_preinit(void) HASSERT(0); } __HAL_DBGMCU_FREEZE_TIM14(); - NVIC_SetPriority(TIM14_IRQn, 0); + SetInterruptPriority(TIM14_IRQn, 0); NVIC_EnableIRQ(TIM14_IRQn); } @@ -321,6 +354,7 @@ void timer3_interrupt_handler(void) { void touch_interrupt_handler(void) { dcc_decoder0.os_interrupt_handler(); + portYIELD_FROM_ISR(true); } } diff --git a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/hardware.hxx b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/hardware.hxx index 3f022fb6c..646e67af0 100644 --- a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/hardware.hxx +++ b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/hardware.hxx @@ -8,8 +8,18 @@ GPIO_PIN(LED_GREEN_RAW, LedPin, A, 5); GPIO_PIN(SW_USER, GpioInputPU, C, 13); GPIO_PIN(DCC_IN, GpioInputPU, A, 6); +GPIO_PIN(RAILCOM_TX, GpioOutputSafeHigh, B, 6); -typedef GpioInitializer GpioInit; +GPIO_PIN(DEBUG1, GpioOutputSafeLow, C, 9); +GPIO_PIN(DEBUG2, GpioOutputSafeLow, C, 8); + +typedef GpioInitializer< // + LED_GREEN_RAW_Pin, // + SW_USER_Pin, // + DCC_IN_Pin, // + DEBUG1_Pin, // + DEBUG2_Pin> + GpioInit; typedef LED_GREEN_RAW_Pin BLINKER_RAW_Pin; typedef BLINKER_Pin LED_GREEN_Pin; diff --git a/applications/dcc_decoder/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx b/applications/dcc_decoder/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx index 6471746e0..47389cb34 100644 --- a/applications/dcc_decoder/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx +++ b/applications/dcc_decoder/targets/freertos.armv7m.ek-tm4c123gxl/HwInit.cxx @@ -167,6 +167,23 @@ struct DCCDecode static inline void dcc_before_cutout_hook() {} static inline void dcc_packet_finished_hook() {} static inline void after_feedback_hook() {} + /// How many usec later/earlier should the railcom cutout start happen. + static int time_delta_railcom_pre_usec() + { + return 0; + } + + /// How many usec later/earlier should the railcom cutout middle happen. + static int time_delta_railcom_mid_usec() + { + return 0; + } + + /// How many usec later/earlier should the railcom cutout end happen. + static int time_delta_railcom_end_usec() + { + return 0; + } }; // Dummy implementation because we are not a railcom detector. diff --git a/applications/hub/main.cxx b/applications/hub/main.cxx index 599710baf..67315b3ea 100644 --- a/applications/hub/main.cxx +++ b/applications/hub/main.cxx @@ -39,13 +39,15 @@ #include -#include "os/os.h" -#include "utils/constants.hxx" -#include "utils/Hub.hxx" -#include "utils/GcTcpHub.hxx" -#include "utils/ClientConnection.hxx" #include "executor/Executor.hxx" #include "executor/Service.hxx" +#include "os/os.h" +#include "utils/ClientConnection.hxx" +#include "utils/GcTcpHub.hxx" +#include "utils/Hub.hxx" +#include "utils/HubDeviceSelect.hxx" +#include "utils/SocketCan.hxx" +#include "utils/constants.hxx" Executor<1> g_executor("g_executor", 0, 1024); Service g_service(&g_executor); @@ -58,6 +60,7 @@ OVERRIDE_CONST(gridconnect_buffer_delay_usec, 2000); int port = 12021; const char *device_path = nullptr; +const char *socket_can_path = nullptr; int upstream_port = 12021; const char *upstream_host = nullptr; bool timestamped = false; @@ -67,18 +70,28 @@ bool printpackets = false; void usage(const char *e) { - fprintf(stderr, "Usage: %s [-p port] [-d device_path] [-u upstream_host] " - "[-q upstream_port] [-m] [-n mdns_name] [-t] [-l]\n\n", - e); - fprintf(stderr, "GridConnect CAN HUB.\nListens to a specific TCP port, " - "reads CAN packets from the incoming connections using " - "the GridConnect protocol, and forwards all incoming " - "packets to all other participants.\n\nArguments:\n"); + fprintf(stderr, + "Usage: %s [-p port] [-d device_path] [-u upstream_host] " + "[-q upstream_port] [-m] [-n mdns_name] " +#if defined(__linux__) + "[-s socketcan_interface] " +#endif + "[-t] [-l]\n\n", + e); + fprintf(stderr, + "GridConnect CAN HUB.\nListens to a specific TCP port, " + "reads CAN packets from the incoming connections using " + "the GridConnect protocol, and forwards all incoming " + "packets to all other participants.\n\nArguments:\n"); fprintf(stderr, "\t-p port specifies the port number to listen on, " "default is 12021.\n"); fprintf(stderr, "\t-d device is a path to a physical device doing " "serial-CAN or USB-CAN. If specified, opens device and " "adds it to the hub.\n"); +#if defined(__linux__) + fprintf(stderr, "\t-s socketcan_interface is a socketcan device (e.g. 'can0'). " + "If specified, opens device and adds it to the hub.\n"); +#endif fprintf(stderr, "\t-u upstream_host is the host name for an upstream " "hub. If specified, this hub will connect to an upstream " "hub.\n"); @@ -100,7 +113,7 @@ void usage(const char *e) void parse_args(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "hp:d:u:q:tlmn:")) >= 0) + while ((opt = getopt(argc, argv, "hp:d:s:u:q:tlmn:")) >= 0) { switch (opt) { @@ -110,6 +123,11 @@ void parse_args(int argc, char *argv[]) case 'd': device_path = optarg; break; +#if defined(__linux__) + case 's': + socket_can_path = optarg; + break; +#endif case 'p': port = atoi(optarg); break; @@ -160,12 +178,28 @@ int appl_main(int argc, char *argv[]) void mdns_client_start(); void mdns_publish(const char *name, uint16_t port); - if (export_mdns) { + if (export_mdns) + { mdns_client_start(); mdns_publish(mdns_name, port); } #endif - +#if defined(__linux__) + if (socket_can_path) + { + int s = socketcan_open(socket_can_path, 1); + if (s >= 0) + { + new HubDeviceSelect(&can_hub0, s); + fprintf(stderr, "Opened SocketCan %s: fd %d\n", socket_can_path, s); + } + else + { + fprintf(stderr, "Failed to open SocketCan %s.\n", socket_can_path); + } + } +#endif + if (upstream_host) { connections.emplace_back(new UpstreamConnectionClient( diff --git a/applications/hub/targets/Makefile b/applications/hub/targets/Makefile index 3d075d4b8..5ff3034dd 100644 --- a/applications/hub/targets/Makefile +++ b/applications/hub/targets/Makefile @@ -1,5 +1,5 @@ SUBDIRS = linux.x86 \ linux.armv7a \ - mach.x86 + mach.x86_64 include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/hub/targets/linux.aarch64/Makefile b/applications/hub/targets/linux.aarch64/Makefile new file mode 100644 index 000000000..ed0c910ab --- /dev/null +++ b/applications/hub/targets/linux.aarch64/Makefile @@ -0,0 +1,3 @@ +-include ../../config.mk +include $(OPENMRNPATH)/etc/prog.mk + diff --git a/applications/hub/targets/linux.llvm/.gitignore b/applications/hub/targets/linux.llvm/.gitignore new file mode 120000 index 000000000..e438d7842 --- /dev/null +++ b/applications/hub/targets/linux.llvm/.gitignore @@ -0,0 +1 @@ +../linux.x86/.gitignore \ No newline at end of file diff --git a/applications/hub/targets/mach.x86/Makefile b/applications/hub/targets/linux.llvm/Makefile similarity index 100% rename from applications/hub/targets/mach.x86/Makefile rename to applications/hub/targets/linux.llvm/Makefile diff --git a/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx b/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx new file mode 120000 index 000000000..71132b521 --- /dev/null +++ b/applications/hub/targets/linux.rpi1/AvaHiMDNS.cxx @@ -0,0 +1 @@ +../linux.x86/AvaHiMDNS.cxx \ No newline at end of file diff --git a/applications/hub/targets/linux.rpi1/README.md b/applications/hub/targets/linux.rpi1/README.md new file mode 100644 index 000000000..b2c866806 --- /dev/null +++ b/applications/hub/targets/linux.rpi1/README.md @@ -0,0 +1,36 @@ +# Adding SocketCAN support on the Raspberry Pi 4 + +These are my notes from adding SocketCAN support and building the **hub** application in a Raspberry Pi. + +- Start with the pre-built JMRI - RPI disk image from: [M Steve Todd's - JMRI RaspberryPi as Access Point](https://mstevetodd.com/rpi) + +- Install the MCP2517 CAN interface hardware from: [2-Channel CAN-BUS(FD) Shield for Raspberry Pi](https://www.seeedstudio.com/2-Channel-CAN-BUS-FD-Shield-for-Raspberry-Pi-p-4072.html) and followed their instructions for building the MCP2517 kernel device driver on their [Support Wiki](http://wiki.seeedstudio.com/2-Channel-CAN-BUS-FD-Shield-for-Raspberry-Pi/#software) page. + +- Install some needed packages on the RPi: + + `sudo apt install git doxygen libavahi-client-dev` + +- Download the OpenMRN source code to the RPi: + + `cd ~` + + `git clone https://github.com/bakerstu/openmrn.git` + +- Build the **hub** application: + + `cd openmrn/applications/hub/targets/linux.rpi1/` + + `make` + +- Configure the **can0** interface for 125,000 bps and run the **hub** application at system at start-up by creating the file: `/etc/network/interfaces.d/can0` with the following lines: + ``` + allow-hotplug can0 + iface can0 can static + bitrate 125000 + up /home/pi/openmrn/applications/hub/targets/linux.rpi1/hub -s $IFACE & + ``` + +- Configure the LCC Layout Connection in JMRI to use + - System Connection: `CAN via GridConnect Network Interface` + - IP Address/Host Name: `localhost` + - TCP/UDP Port: `12021` diff --git a/applications/hub/targets/mach.x86/lib/Makefile b/applications/hub/targets/mach.x86/lib/Makefile deleted file mode 100644 index a414ed98e..000000000 --- a/applications/hub/targets/mach.x86/lib/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/app_target_lib.mk diff --git a/applications/hub/targets/mach.x86/.gitignore b/applications/hub/targets/mach.x86_64/.gitignore similarity index 100% rename from applications/hub/targets/mach.x86/.gitignore rename to applications/hub/targets/mach.x86_64/.gitignore diff --git a/applications/simple_client/targets/mach.x86/hardware.mk b/applications/hub/targets/mach.x86_64/Makefile similarity index 100% rename from applications/simple_client/targets/mach.x86/hardware.mk rename to applications/hub/targets/mach.x86_64/Makefile diff --git a/applications/async_blink/targets/mach.x86/lib/Makefile b/applications/hub/targets/mach.x86_64/lib/Makefile similarity index 100% rename from applications/async_blink/targets/mach.x86/lib/Makefile rename to applications/hub/targets/mach.x86_64/lib/Makefile diff --git a/applications/io_board/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx b/applications/io_board/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx index 03cd6e908..a514e76d1 100644 --- a/applications/io_board/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx +++ b/applications/io_board/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx @@ -127,6 +127,40 @@ openlcb::ConfiguredProducer producer_sw2( openlcb::RefreshLoop loop( stack.node(), {producer_sw1.polling(), producer_sw2.polling()}); +/// This timer checks the eeprom once a second and if the user has written +/// something, executes a reload of the configuration via the OpenLCB config +/// service. +class AutoUpdateTimer : public ::Timer +{ +public: + AutoUpdateTimer() + : ::Timer(stack.executor()->active_timers()) + { + start(SEC_TO_NSEC(1)); + } + + long long timeout() override + { + extern uint8_t eeprom_updated; + if (eeprom_updated) + { + needUpdate_ = true; + eeprom_updated = 0; + } + else + { + if (needUpdate_) + { + stack.config_service()->trigger_update(); + needUpdate_ = false; + } + } + return RESTART; + } + + bool needUpdate_ {false}; +} update_timer; + /** Entry point to application. * @param argc number of command line arguments * @param argv array of command line arguments diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx new file mode 120000 index 000000000..8bf2ed18c --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/HwInit.cxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/HwInit.cxx \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile new file mode 120000 index 000000000..e13c2970c --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/Makefile @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/Makefile \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/config.hxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/config.hxx new file mode 100644 index 000000000..f93bff471 --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/config.hxx @@ -0,0 +1,80 @@ +#ifndef _APPLICATIONS_IO_BOARD_TARGET_CONFIG_HXX_ +#define _APPLICATIONS_IO_BOARD_TARGET_CONFIG_HXX_ + +#include "openlcb/ConfiguredConsumer.hxx" +#include "openlcb/ConfiguredProducer.hxx" +#include "openlcb/ConfigRepresentation.hxx" +#include "openlcb/MemoryConfig.hxx" + +namespace openlcb +{ + +/// Defines the identification information for the node. The arguments are: +/// +/// - 4 (version info, always 4 by the standard +/// - Manufacturer name +/// - Model name +/// - Hardware version +/// - Software version +/// +/// This data will be used for all purposes of the identification: +/// +/// - the generated cdi.xml will include this data +/// - the Simple Node Ident Info Protocol will return this data +/// - the ACDI memory space will contain this data. +extern const SimpleNodeStaticValues SNIP_STATIC_DATA = {4, "OpenMRN", // + "Test IO Board - STM32L432KC Nucleo", // + "STM32L432KC-Nucleo", // + "1.01"}; + +#define NUM_OUTPUTS 5 +#define NUM_INPUTS 4 + +/// Declares a repeated group of a given base group and number of repeats. The +/// ProducerConfig and ConsumerConfig groups represent the configuration layout +/// needed by the ConfiguredProducer and ConfiguredConsumer classes, and come +/// from their respective hxx file. +using AllConsumers = RepeatedGroup; +using AllProducers = RepeatedGroup; + +/// Modify this value every time the EEPROM needs to be cleared on the node +/// after an update. +static constexpr uint16_t CANONICAL_VERSION = 0x1844; + +/// Defines the main segment in the configuration CDI. This is laid out at +/// origin 128 to give space for the ACDI user data at the beginning. +CDI_GROUP(IoBoardSegment, Segment(MemoryConfigDefs::SPACE_CONFIG), Offset(128)); +/// Each entry declares the name of the current entry, then the type and then +/// optional arguments list. +CDI_GROUP_ENTRY(internal_config, InternalConfigData); +CDI_GROUP_ENTRY(consumers, AllConsumers, Name("Output LEDs")); +CDI_GROUP_ENTRY(producers, AllProducers, Name("Input buttons")); +CDI_GROUP_END(); + +/// This segment is only needed temporarily until there is program code to set +/// the ACDI user data version byte. +CDI_GROUP(VersionSeg, Segment(MemoryConfigDefs::SPACE_CONFIG), + Name("Version information")); +CDI_GROUP_ENTRY(acdi_user_version, Uint8ConfigEntry, + Name("ACDI User Data version"), Description("Set to 2 and do not change.")); +CDI_GROUP_END(); + +/// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx +/// to refer to the configuration defined here. +CDI_GROUP(ConfigDef, MainCdi()); +/// Adds the tag with the values from SNIP_STATIC_DATA above. +CDI_GROUP_ENTRY(ident, Identification); +/// Adds an tag. +CDI_GROUP_ENTRY(acdi, Acdi); +/// Adds a segment for changing the values in the ACDI user-defined +/// space. UserInfoSegment is defined in the system header. +CDI_GROUP_ENTRY(userinfo, UserInfoSegment); +/// Adds the main configuration segment. +CDI_GROUP_ENTRY(seg, IoBoardSegment); +/// Adds the versioning segment. +CDI_GROUP_ENTRY(version, VersionSeg); +CDI_GROUP_END(); + +} // namespace openlcb + +#endif // _APPLICATIONS_IO_BOARD_TARGET_CONFIG_HXX_ diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx new file mode 120000 index 000000000..e27187a64 --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/hardware.hxx @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/hardware.hxx \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/main.cxx b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/main.cxx new file mode 100644 index 000000000..4d8bfb6e2 --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/main.cxx @@ -0,0 +1,199 @@ +/** \copyright + * Copyright (c) 2013, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file main.cxx + * + * Main file for the io board application on the STM32 Nucleo board. + * + * @author Balazs Racz + * @date 5 Jun 2015 + */ + +#include "os/os.h" +#include "nmranet_config.h" + +#include "openlcb/SimpleStack.hxx" +#include "openlcb/ConfiguredConsumer.hxx" +#include "openlcb/ConfiguredProducer.hxx" + +#include "freertos_drivers/st/Stm32Gpio.hxx" +#include "freertos_drivers/common/BlinkerGPIO.hxx" +#include "freertos_drivers/common/DummyGPIO.hxx" +#include "config.hxx" +#include "hardware.hxx" + +// These preprocessor symbols are used to select which physical connections +// will be enabled in the main(). See @ref appl_main below. +//#define SNIFF_ON_SERIAL +//#define SNIFF_ON_USB +#define HAVE_PHYSICAL_CAN_PORT + +// Changes the default behavior by adding a newline after each gridconnect +// packet. Makes it easier for debugging the raw device. +OVERRIDE_CONST(gc_generate_newlines, 1); +// Specifies how much RAM (in bytes) we allocate to the stack of the main +// thread. Useful tuning parameter in case the application runs out of memory. +OVERRIDE_CONST(main_thread_stack_size, 1300); + +// Specifies the 48-bit OpenLCB node identifier. This must be unique for every +// hardware manufactured, so in production this should be replaced by some +// easily incrementable method. +extern const openlcb::NodeID NODE_ID = 0x050101011816ULL; + +// Sets up a comprehensive OpenLCB stack for a single virtual node. This stack +// contains everything needed for a usual peripheral node -- all +// CAN-bus-specific components, a virtual node, PIP, SNIP, Memory configuration +// protocol, ACDI, CDI, a bunch of memory spaces, etc. +openlcb::SimpleCanStack stack(NODE_ID); + +// ConfigDef comes from config.hxx and is specific to the particular device and +// target. It defines the layout of the configuration memory space and is also +// used to generate the cdi.xml file. Here we instantiate the configuration +// layout. The argument of offset zero is ignored and will be removed later. +openlcb::ConfigDef cfg(0); +// Defines weak constants used by the stack to tell it which device contains +// the volatile configuration information. This device name appears in +// HwInit.cxx that creates the device drivers. +extern const char *const openlcb::CONFIG_FILENAME = "/dev/eeprom"; +// The size of the memory space to export over the above device. +extern const size_t openlcb::CONFIG_FILE_SIZE = + cfg.seg().size() + cfg.seg().offset(); +static_assert(openlcb::CONFIG_FILE_SIZE <= 512, "Need to adjust eeprom size"); +// The SNIP user-changeable information in also stored in the above eeprom +// device. In general this could come from different eeprom segments, but it is +// simpler to keep them together. +extern const char *const openlcb::SNIP_DYNAMIC_FILENAME = + openlcb::CONFIG_FILENAME; + +// Object that handles factory reset for our config setup. +class CustomFactoryReset : public DefaultConfigUpdateListener { +public: + void factory_reset(int fd) override + { + // Resets user names. + cfg.userinfo().name().write(fd, "Default user name"); + cfg.userinfo().description().write(fd, "Default user description"); + // Makes the IO pin descriptions with default value. + cfg.seg().consumers().entry<0>().description().write(fd, "LD3"); + cfg.seg().consumers().entry<1>().description().write(fd, "D3"); + cfg.seg().consumers().entry<2>().description().write(fd, "D4"); + cfg.seg().consumers().entry<3>().description().write(fd, "D5"); + cfg.seg().consumers().entry<4>().description().write(fd, "D6"); + + cfg.seg().producers().entry<0>().description().write(fd, "A0"); + cfg.seg().producers().entry<1>().description().write(fd, "A1"); + cfg.seg().producers().entry<2>().description().write(fd, "A2"); + cfg.seg().producers().entry<3>().description().write(fd, "A3"); + for (unsigned i = 0; i < cfg.seg().producers().num_repeats(); ++i) { + cfg.seg().producers().entry(i).debounce().write(fd, 3); + } + } + + UpdateAction apply_configuration( + int fd, bool initial_load, BarrierNotifiable *done) override { + done->notify(); + // Nothing to do; we don't read the configuration. + return UPDATED; + } +} g_custom_factory_reset; + +// Instantiates the actual producer and consumer objects for the given GPIO +// pins from above. The ConfiguredConsumer class takes care of most of the +// complicated setup and operation requirements. We need to give it the virtual +// node pointer, the configuration configuration from the CDI definition, and +// the hardware pin definition. The virtual node pointer comes from the stack +// object. The configuration structure comes from the CDI definition object, +// segment 'seg', in which there is a repeated group 'consumers', and we assign +// the individual entries to the individual consumers. Each consumer gets its +// own GPIO pin. +openlcb::ConfiguredConsumer consumer_green( + stack.node(), cfg.seg().consumers().entry<0>(), LED_GREEN_Pin()); +openlcb::ConfiguredConsumer consumer_d3( + stack.node(), cfg.seg().consumers().entry<1>(), OUT_D3_Pin()); +openlcb::ConfiguredConsumer consumer_d4( + stack.node(), cfg.seg().consumers().entry<2>(), OUT_D4_Pin()); +openlcb::ConfiguredConsumer consumer_d5( + stack.node(), cfg.seg().consumers().entry<3>(), OUT_D5_Pin()); +openlcb::ConfiguredConsumer consumer_d6( + stack.node(), cfg.seg().consumers().entry<4>(), OUT_D6_Pin()); + +// Similar syntax for the producers. +openlcb::ConfiguredProducer producer_a0( + stack.node(), cfg.seg().producers().entry<0>(), IN_A0_Pin()); +openlcb::ConfiguredProducer producer_a1( + stack.node(), cfg.seg().producers().entry<1>(), IN_A1_Pin()); +openlcb::ConfiguredProducer producer_a2( + stack.node(), cfg.seg().producers().entry<2>(), IN_A2_Pin()); +openlcb::ConfiguredProducer producer_a3( + stack.node(), cfg.seg().producers().entry<3>(), IN_A3_Pin()); + +// The producers need to be polled repeatedly for changes and to execute the +// debouncing algorithm. This class instantiates a refreshloop and adds the two +// producers to it. +openlcb::RefreshLoop loop(stack.node(), + { + producer_a0.polling(), // + producer_a1.polling(), // + producer_a2.polling(), // + producer_a3.polling() // + }); + +/** 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[]) +{ + stack.check_version_and_factory_reset( + cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, false); + + // The necessary physical ports must be added to the stack. + // + // It is okay to enable multiple physical ports, in which case the stack + // will behave as a bridge between them. For example enabling both the + // physical CAN port and the USB port will make this firmware act as an + // USB-CAN adapter in addition to the producers/consumers created above. + // + // If a port is enabled, it must be functional or else the stack will + // freeze waiting for that port to send the packets out. +#if defined(HAVE_PHYSICAL_CAN_PORT) + stack.add_can_port_select("/dev/can0"); +#endif +#if defined(SNIFF_ON_USB) + stack.add_gridconnect_port("/dev/serUSB0"); +#endif +#if defined(SNIFF_ON_SERIAL) + stack.add_gridconnect_port("/dev/ser0"); +#endif + + // This command donates the main thread to the operation of the + // stack. Alternatively the stack could be started in a separate stack and + // then application-specific business logic could be executed ion a busy + // loop in the main thread. + stack.loop_executor(); + return 0; +} diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld new file mode 120000 index 000000000..52c792882 --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/memory_map.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/memory_map.ld \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c new file mode 120000 index 000000000..cd5f598b1 --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/startup.c @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/startup.c \ No newline at end of file diff --git a/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld new file mode 120000 index 000000000..403497dce --- /dev/null +++ b/applications/io_board/targets/freertos.armv7m.st-stm32l432kc-nucleo/target.ld @@ -0,0 +1 @@ +../../../../boards/st-stm32l432kc-nucleo/target.ld \ No newline at end of file diff --git a/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/applications/memconfig_utils/targets/linux.x86/main.cxx b/applications/memconfig_utils/targets/linux.x86/main.cxx index d5aac2c83..746a4deb1 100644 --- a/applications/memconfig_utils/targets/linux.x86/main.cxx +++ b/applications/memconfig_utils/targets/linux.x86/main.cxx @@ -46,6 +46,7 @@ #include "utils/GcTcpHub.hxx" #include "utils/Crc.hxx" #include "utils/FileUtils.hxx" +#include "utils/format_utils.hxx" #include "executor/Executor.hxx" #include "executor/Service.hxx" @@ -87,6 +88,10 @@ static const char *filename = nullptr; static uint64_t destination_nodeid = 0; static uint64_t destination_alias = 0; static int memory_space_id = openlcb::MemoryConfigDefs::SPACE_CONFIG; +static uint32_t offset = 0; +static constexpr uint32_t NLEN = (uint32_t)-1; +static uint32_t len = NLEN; +static bool partial_read = false; static bool do_read = false; static bool do_write = false; @@ -94,8 +99,8 @@ void usage(const char *e) { fprintf(stderr, "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) [-s " - "memory_space_id] [-c csum_algo] (-r|-w) (-n nodeid | -a " - "alias) -f filename\n", + "memory_space_id] [-o offset] [-l len] [-c csum_algo] (-r|-w) " + "(-n nodeid | -a alias) -f filename\n", e); fprintf(stderr, "Connects to an openlcb bus and performs the " "bootloader protocol on openlcb node with id nodeid with " @@ -120,7 +125,7 @@ void usage(const char *e) void parse_args(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "hp:i:d:n:a:s:f:rw")) >= 0) + while ((opt = getopt(argc, argv, "hp:i:d:n:a:s:f:rwo:l:")) >= 0) { switch (opt) { @@ -148,6 +153,12 @@ void parse_args(int argc, char *argv[]) case 's': memory_space_id = strtol(optarg, nullptr, 16); break; + case 'o': + offset = strtol(optarg, nullptr, 10); + break; + case 'l': + len = strtol(optarg, nullptr, 10); + break; case 'r': do_read = true; break; @@ -159,7 +170,9 @@ void parse_args(int argc, char *argv[]) usage(argv[0]); } } - if (!filename || (!destination_nodeid && !destination_alias)) + partial_read = do_read && ((offset != 0) || (len != NLEN)); + if ((!filename && !partial_read) || + (!destination_nodeid && !destination_alias)) { usage(argv[0]); } @@ -200,22 +213,36 @@ int appl_main(int argc, char *argv[]) openlcb::NodeHandle dst; dst.alias = destination_alias; dst.id = destination_nodeid; - HASSERT(do_read); - auto b = invoke_flow(&g_memcfg_cli, openlcb::MemoryConfigClientRequest::READ, dst, memory_space_id); - - if (0 && do_write) + HASSERT((!!do_read) + (!!do_write) == 1); + if (do_write) { - b->data()->payload = read_file_to_string(filename); - //b->data()->cmd = openlcb::MemoryConfigClientRequest::WRITE; + auto payload = read_file_to_string(filename); printf("Read %" PRIdPTR " bytes from file %s. Writing to memory space 0x%02x\n", - b->data()->payload.size(), filename, memory_space_id); + payload.size(), filename, memory_space_id); + auto b = invoke_flow(&g_memcfg_cli, + openlcb::MemoryConfigClientRequest::WRITE, dst, memory_space_id, 0, + std::move(payload)); + printf("Result: %04x\n", b->data()->resultCode); } - printf("Result: %04x\n", b->data()->resultCode); - - if (do_read) + if (do_read && partial_read) + { + auto b = invoke_flow(&g_memcfg_cli, openlcb::MemoryConfigClientRequest::READ_PART, dst, memory_space_id, offset, len); + printf("Result: %04x\n", b->data()->resultCode); + printf("Data: %s\n", string_to_hex(b->data()->payload).c_str()); + } + else if (do_read) { + auto cb = [](openlcb::MemoryConfigClientRequest *rq) { + static size_t last_len = rq->payload.size(); + if ((last_len & ~1023) != (rq->payload.size() & ~1023)) { + printf("Loaded %d bytes\n", (int)rq->payload.size()); + last_len = rq->payload.size(); + } + }; + auto b = invoke_flow(&g_memcfg_cli, openlcb::MemoryConfigClientRequest::READ, dst, memory_space_id, std::move(cb)); + printf("Result: %04x\n", b->data()->resultCode); write_string_to_file(filename, b->data()->payload); fprintf(stderr, "Written %" PRIdPTR " bytes to file %s.\n", b->data()->payload.size(), filename); diff --git a/applications/nucleo_io/targets/freertos.armv6m.st-stm32f091rc-nucleo-dev-board/config.hxx b/applications/nucleo_io/targets/freertos.armv6m.st-stm32f091rc-nucleo-dev-board/config.hxx index af09c0a5b..b14add6f2 100644 --- a/applications/nucleo_io/targets/freertos.armv6m.st-stm32f091rc-nucleo-dev-board/config.hxx +++ b/applications/nucleo_io/targets/freertos.armv6m.st-stm32f091rc-nucleo-dev-board/config.hxx @@ -24,7 +24,7 @@ namespace openlcb /// - the Simple Node Ident Info Protocol will return this data /// - the ACDI memory space will contain this data. extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { - 4, "OpenMRN", "Nucleo F091RC with SNAPs Enabled", + 4, "OpenMRN", "OpenLCB Dev Board + Nucleo F091RC", "Rev A", "1.02"}; #define NUM_OUTPUTS 16 @@ -40,7 +40,7 @@ extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { // When PORTD_SNAP is not defined (remarked out), this sets port D to be a constant on/off // state as dictated by consumed events. -#define PORTD_SNAP +//#define PORTD_SNAP /// Declares a repeated group of a given base group and number of repeats. The /// ProducerConfig and ConsumerConfig groups represent the configuration layout diff --git a/applications/nucleo_io/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/config.hxx b/applications/nucleo_io/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/config.hxx index 581eedd9c..d75232f3f 100644 --- a/applications/nucleo_io/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/config.hxx +++ b/applications/nucleo_io/targets/freertos.armv7m.st-stm32f303re-nucleo-dev-board/config.hxx @@ -25,8 +25,8 @@ namespace openlcb /// - the Simple Node Ident Info Protocol will return this data /// - the ACDI memory space will contain this data. extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { - 4, "OpenMRN", "OpenLCB DevKit + Nucleo", - "Rev A", "1.06"}; + 4, "OpenMRN", "OpenLCB DevKit + Nucleo F303 dev board", + "Rev A", "1.02"}; #define NUM_OUTPUTS 16 #define NUM_INPUTS 1 @@ -66,14 +66,17 @@ using ServoConsumers = RepeatedGroup; // we are updating this define to track number of MCPs instead of // expansion boards. // The maximum number of MCPs is 8 (3 address bits available). -#if NUM_MCPIOS == 2 +#if NUM_MCPIOS == 0 +#elif NUM_MCPIOS == 2 using Ext0PC = RepeatedGroup; #elif NUM_MCPIOS == 4 using Ext0PC = RepeatedGroup; #elif NUM_MCPIOS == 6 -using Ext0PC = RepeatedGroup; +using Ext0PC = RepeatedGroup; #elif NUM_MCPIOS == 8 using Ext0PC = RepeatedGroup; +#else +#error invalid value for NUM_MCPIOS. Should be 0 2 4 6 or 8. #endif @@ -100,6 +103,9 @@ CDI_GROUP_ENTRY(direct_consumers, DirectConsumers, Name("Tortoise/Hi-Power outpu CDI_GROUP_ENTRY(servo_consumers, ServoConsumers, Name("Servo Pin outputs"), Description("3-pin servo outputs."), RepName("Line")); CDI_GROUP_ENTRY(hidden_servo_5_8, ServoConsumers, Hidden(true)); CDI_GROUP_ENTRY(portde_consumers, PortDEConsumers, Name("Port D/E outputs"), Description("Line 1-8 is port D, Line 9-16 is port E"), RepName("Line")); +//#ifdef PORTD_SNAP +//CDI_GROUP_ENTRY(portde_consumers, PortDEConsumers, Name("Port E outputs"), Description("Line 1-4 is port E 5 - 8; offset due to Snap Switches"), RepName("Line")); +//#endif CDI_GROUP_ENTRY(portab_producers, PortABProducers, Name("Port A/B inputs"), Description("Line 1-8 is port A, Line 9-16 is port B"), RepName("Line")); #if NUM_MCPIOS > 0 CDI_GROUP_ENTRY(ext0_pc, Ext0PC, Name("IO Expansion Board with MCP23017 Lines"), diff --git a/applications/simple_client/targets/Makefile b/applications/simple_client/targets/Makefile index 722e7127c..090bf476c 100644 --- a/applications/simple_client/targets/Makefile +++ b/applications/simple_client/targets/Makefile @@ -1,3 +1,3 @@ -SUBDIRS = linux.x86 mach.x86 +SUBDIRS = linux.x86 mach.x86_64 -include config.mk include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/simple_client/targets/mach.x86_64/.gitignore b/applications/simple_client/targets/mach.x86_64/.gitignore new file mode 100644 index 000000000..7d1a13c95 --- /dev/null +++ b/applications/simple_client/targets/mach.x86_64/.gitignore @@ -0,0 +1 @@ +simple_client diff --git a/applications/simple_client/targets/mach.x86/Makefile b/applications/simple_client/targets/mach.x86_64/Makefile similarity index 100% rename from applications/simple_client/targets/mach.x86/Makefile rename to applications/simple_client/targets/mach.x86_64/Makefile diff --git a/applications/async_blink/targets/mach.x86/Makefile b/applications/simple_client/targets/mach.x86_64/hardware.mk similarity index 58% rename from applications/async_blink/targets/mach.x86/Makefile rename to applications/simple_client/targets/mach.x86_64/hardware.mk index 3bb6034b2..c6386dac3 100644 --- a/applications/async_blink/targets/mach.x86/Makefile +++ b/applications/simple_client/targets/mach.x86_64/hardware.mk @@ -1 +1,2 @@ +-include ../../config.mk include $(OPENMRNPATH)/etc/prog.mk diff --git a/applications/time_client/targets/linux.x86/main.cxx b/applications/time_client/targets/linux.x86/main.cxx index b60bde4f7..f48e5c89a 100644 --- a/applications/time_client/targets/linux.x86/main.cxx +++ b/applications/time_client/targets/linux.x86/main.cxx @@ -73,7 +73,7 @@ extern const char *const openlcb::SNIP_DYNAMIC_FILENAME = openlcb::CONFIG_FILENAME; void time_update_callback(); -void minute_update_callback(); +void minute_update_callback(BarrierNotifiable *done); // Main time protocol client. openlcb::BroadcastTimeClient @@ -88,13 +88,15 @@ void print_time(const char* prefix) { tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, // tm->tm_hour, tm->tm_min, // timeClient.is_running() ? "running" : "stopped", // - timeClient.rate() * 1.0f / 4); + timeClient.get_rate_quarters() * 1.0f / 4); } /// Callback from the alarm that we will make called on every minute change. -void minute_update_callback() +/// @param done used to notify we are finished +void minute_update_callback(BarrierNotifiable *done) { print_time(""); + done->notify(); } /// Callback from the time client when the time jumps or is reset for any other @@ -115,7 +117,7 @@ int appl_main(int argc, char *argv[]) // Connects to a TCP hub on the internet. stack.connect_tcp_gridconnect_hub("localhost", 12021); - timeClient.update_subscribe(&time_update_callback); + timeClient.update_subscribe_add(&time_update_callback); // This command donates the main thread to the operation of the // stack. Alternatively the stack could be started in a separate stack and diff --git a/applications/time_server/targets/linux.x86/main.cxx b/applications/time_server/targets/linux.x86/main.cxx index c6dbdaaf0..2e3d03b3a 100644 --- a/applications/time_server/targets/linux.x86/main.cxx +++ b/applications/time_server/targets/linux.x86/main.cxx @@ -92,7 +92,7 @@ int appl_main(int argc, char *argv[]) timeServer.set_time(0, 0); timeServer.set_date(1, 1); timeServer.set_year(1970); - timeServer.set_rate(4); + timeServer.set_rate_quarters(4); timeServer.start(); // This command donates the main thread to the operation of the diff --git a/applications/train/targets/linux.x86/main.cxx b/applications/train/targets/linux.x86/main.cxx index ea0988c91..60e3b0aba 100644 --- a/applications/train/targets/linux.x86/main.cxx +++ b/applications/train/targets/linux.x86/main.cxx @@ -56,6 +56,7 @@ openlcb::SimpleCanStack stack(NODE_ID); openlcb::TrainService traction_service(stack.iface()); const char *const openlcb::SNIP_DYNAMIC_FILENAME = openlcb::MockSNIPUserFile::snip_user_file_path; +const char *const openlcb::CONFIG_FILENAME = openlcb::SNIP_DYNAMIC_FILENAME; int port = 12021; const char *host = "localhost"; @@ -137,9 +138,9 @@ int appl_main(int argc, char *argv[]) } openlcb::LoggingTrain train_impl(address); - openlcb::TrainNodeForProxy train_node(&traction_service, &train_impl); + openlcb::TrainNodeWithId train_node(&traction_service, &train_impl, openlcb::TractionDefs::NODE_ID_DCC | 0xFF000000u | address); openlcb::FixedEventProducer - is_train_event_handler(&train_node); + is_train_event_handler(&train_node); openlcb::ProtocolIdentificationHandler pip( &train_node, openlcb::Defs::EVENT_EXCHANGE | openlcb::Defs::SIMPLE_NODE_INFORMATION | diff --git a/arduino/examples/ESP32CanLoadTest/ESP32CanLoadTest.ino b/arduino/examples/ESP32CanLoadTest/ESP32CanLoadTest.ino index 62ad5e57b..19b5fd99c 100644 --- a/arduino/examples/ESP32CanLoadTest/ESP32CanLoadTest.ino +++ b/arduino/examples/ESP32CanLoadTest/ESP32CanLoadTest.ino @@ -69,7 +69,7 @@ /// This is the node id to assign to this device, this must be unique /// on the CAN bus. -static constexpr uint64_t NODE_ID = UINT64_C(0x050101011823); +static constexpr uint64_t NODE_ID = UINT64_C(0x05010101182b); #if defined(USE_WIFI) // Configuring WiFi accesspoint name and password diff --git a/arduino/examples/ESP32CanLoadTest/config.h b/arduino/examples/ESP32CanLoadTest/config.h index 6884c7fd4..eeeb2bbc2 100644 --- a/arduino/examples/ESP32CanLoadTest/config.h +++ b/arduino/examples/ESP32CanLoadTest/config.h @@ -56,7 +56,7 @@ using AllProducers = RepeatedGroup; /// Modify this value every time the EEPROM needs to be cleared on the node /// after an update. -static constexpr uint16_t CANONICAL_VERSION = 0x1000; +static constexpr uint16_t CANONICAL_VERSION = 0x100b; /// Defines the main segment in the configuration CDI. This is laid out at /// origin 128 to give space for the ACDI user data at the beginning. @@ -71,14 +71,6 @@ CDI_GROUP_ENTRY(wifi, WiFiConfiguration, Name("WiFi Configuration")); #endif CDI_GROUP_END(); -/// This segment is only needed temporarily until there is program code to set -/// the ACDI user data version byte. -CDI_GROUP(VersionSeg, Segment(MemoryConfigDefs::SPACE_CONFIG), - Name("Version information")); -CDI_GROUP_ENTRY(acdi_user_version, Uint8ConfigEntry, - Name("ACDI User Data version"), Description("Set to 2 and do not change.")); -CDI_GROUP_END(); - /// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx /// to refer to the configuration defined here. CDI_GROUP(ConfigDef, MainCdi()); @@ -88,11 +80,9 @@ CDI_GROUP_ENTRY(ident, Identification); CDI_GROUP_ENTRY(acdi, Acdi); /// Adds a segment for changing the values in the ACDI user-defined /// space. UserInfoSegment is defined in the system header. -CDI_GROUP_ENTRY(userinfo, UserInfoSegment); +CDI_GROUP_ENTRY(userinfo, UserInfoSegment, Name("User Info")); /// Adds the main configuration segment. -CDI_GROUP_ENTRY(seg, IoBoardSegment); -/// Adds the versioning segment. -CDI_GROUP_ENTRY(version, VersionSeg); +CDI_GROUP_ENTRY(seg, IoBoardSegment, Name("Settings")); CDI_GROUP_END(); } // namespace openlcb diff --git a/arduino/examples/ESP32IOBoard/ESP32IOBoard.ino b/arduino/examples/ESP32IOBoard/ESP32IOBoard.ino index f76c665ed..a9ecfb2ba 100644 --- a/arduino/examples/ESP32IOBoard/ESP32IOBoard.ino +++ b/arduino/examples/ESP32IOBoard/ESP32IOBoard.ino @@ -54,11 +54,17 @@ // output. This is not recommended for production deployment. //#define PRINT_PACKETS +// uncomment the line below to specify a GPIO pin that should be used to force +// a factory reset when the node starts and the GPIO pin reads LOW. An external +// weak pull-up resistor to 3v3 would be recommended to prevent unintended +// factory resets. +//#define FACTORY_RESET_GPIO_PIN 22 + #include "config.h" /// This is the node id to assign to this device, this must be unique /// on the CAN bus. -static constexpr uint64_t NODE_ID = UINT64_C(0x050101011823); +static constexpr uint64_t NODE_ID = UINT64_C(0x05010101182a); #if defined(USE_WIFI) // Configuring WiFi accesspoint name and password @@ -165,6 +171,10 @@ GPIO_PIN(IO13, GpioInputPU, 39); GPIO_PIN(IO14, GpioInputPU, 25); GPIO_PIN(IO15, GpioInputPU, 26); +#if defined(FACTORY_RESET_GPIO_PIN) +GPIO_PIN(FACTORY_RESET, GpioInputPU, FACTORY_RESET_GPIO_PIN); +#endif // FACTORY_RESET_GPIO_PIN + openlcb::ConfiguredProducer IO8_producer( openmrn.stack()->node(), cfg.seg().producers().entry<0>(), IO8_Pin()); openlcb::ConfiguredProducer IO9_producer( @@ -184,6 +194,9 @@ openlcb::ConfiguredProducer IO15_producer( // Create an initializer that can initialize all the GPIO pins in one shot typedef GpioInitializer< +#if defined(FACTORY_RESET_GPIO_PIN) + FACTORY_RESET_Pin, // factory reset +#endif // FACTORY_RESET_GPIO_PIN IO0_Pin, IO1_Pin, IO2_Pin, IO3_Pin, // outputs 0-3 IO4_Pin, IO5_Pin, IO6_Pin, IO7_Pin, // outputs 4-7 IO8_Pin, IO9_Pin, IO10_Pin, IO11_Pin, // inputs 0-3 @@ -269,6 +282,36 @@ void setup() } } + // initialize all declared GPIO pins + GpioInit::hw_init(); + +#if defined(FACTORY_RESET_GPIO_PIN) + // Check the factory reset pin which should normally read HIGH (set), if it + // reads LOW (clr) delete the cdi.xml and openlcb_config + if (!FACTORY_RESET_Pin::get()) + { + printf("!!!! WARNING WARNING WARNING WARNING WARNING !!!!\n"); + printf("The factory reset GPIO pin %d has been triggered.\n", + FACTORY_RESET_GPIO_PIN); + for (uint8_t sec = 10; sec > 0 && !FACTORY_RESET_Pin::get(); sec--) + { + printf("Factory reset will be initiated in %d seconds.\n", sec); + usleep(SEC_TO_USEC(1)); + } + if (!FACTORY_RESET_Pin::get()) + { + unlink(openlcb::CDI_FILENAME); + unlink(openlcb::CONFIG_FILENAME); + printf("Factory reset complete\n"); + } + else + { + printf("Factory reset aborted as pin %d was not held LOW\n", + FACTORY_RESET_GPIO_PIN); + } + } +#endif // FACTORY_RESET_GPIO_PIN + // Create the CDI.xml dynamically openmrn.create_config_descriptor_xml(cfg, openlcb::CDI_FILENAME); @@ -276,9 +319,6 @@ void setup() openmrn.stack()->create_config_file_if_needed(cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, openlcb::CONFIG_FILE_SIZE); - // initialize all declared GPIO pins - GpioInit::hw_init(); - // Start the OpenMRN stack openmrn.begin(); openmrn.start_executor_thread(); diff --git a/arduino/examples/ESP32IOBoard/config.h b/arduino/examples/ESP32IOBoard/config.h index 6884c7fd4..78bad363d 100644 --- a/arduino/examples/ESP32IOBoard/config.h +++ b/arduino/examples/ESP32IOBoard/config.h @@ -56,7 +56,7 @@ using AllProducers = RepeatedGroup; /// Modify this value every time the EEPROM needs to be cleared on the node /// after an update. -static constexpr uint16_t CANONICAL_VERSION = 0x1000; +static constexpr uint16_t CANONICAL_VERSION = 0x100a; /// Defines the main segment in the configuration CDI. This is laid out at /// origin 128 to give space for the ACDI user data at the beginning. @@ -71,14 +71,6 @@ CDI_GROUP_ENTRY(wifi, WiFiConfiguration, Name("WiFi Configuration")); #endif CDI_GROUP_END(); -/// This segment is only needed temporarily until there is program code to set -/// the ACDI user data version byte. -CDI_GROUP(VersionSeg, Segment(MemoryConfigDefs::SPACE_CONFIG), - Name("Version information")); -CDI_GROUP_ENTRY(acdi_user_version, Uint8ConfigEntry, - Name("ACDI User Data version"), Description("Set to 2 and do not change.")); -CDI_GROUP_END(); - /// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx /// to refer to the configuration defined here. CDI_GROUP(ConfigDef, MainCdi()); @@ -88,11 +80,9 @@ CDI_GROUP_ENTRY(ident, Identification); CDI_GROUP_ENTRY(acdi, Acdi); /// Adds a segment for changing the values in the ACDI user-defined /// space. UserInfoSegment is defined in the system header. -CDI_GROUP_ENTRY(userinfo, UserInfoSegment); +CDI_GROUP_ENTRY(userinfo, UserInfoSegment, Name("User Info")); /// Adds the main configuration segment. -CDI_GROUP_ENTRY(seg, IoBoardSegment); -/// Adds the versioning segment. -CDI_GROUP_ENTRY(version, VersionSeg); +CDI_GROUP_ENTRY(seg, IoBoardSegment, Name("Settings")); CDI_GROUP_END(); } // namespace openlcb diff --git a/arduino/examples/ESP32SerialBridge/ESP32SerialBridge.ino b/arduino/examples/ESP32SerialBridge/ESP32SerialBridge.ino index 4d9a9aba9..870457596 100644 --- a/arduino/examples/ESP32SerialBridge/ESP32SerialBridge.ino +++ b/arduino/examples/ESP32SerialBridge/ESP32SerialBridge.ino @@ -72,7 +72,7 @@ constexpr gpio_num_t CAN_TX_PIN = GPIO_NUM_5; /// This is the node id to assign to this device, this must be unique /// on the CAN bus. -static constexpr uint64_t NODE_ID = UINT64_C(0x050101011822); +static constexpr uint64_t NODE_ID = UINT64_C(0x050101011829); /// This is the primary entrypoint for the OpenMRN/LCC stack. OpenMRN openmrn(NODE_ID); diff --git a/arduino/examples/ESP32SerialBridge/config.h b/arduino/examples/ESP32SerialBridge/config.h index 38ed3dd1a..6df063fa3 100644 --- a/arduino/examples/ESP32SerialBridge/config.h +++ b/arduino/examples/ESP32SerialBridge/config.h @@ -31,7 +31,7 @@ extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { /// Modify this value every time the EEPROM needs to be cleared on the node /// after an update. -static constexpr uint16_t CANONICAL_VERSION = 0x1000; +static constexpr uint16_t CANONICAL_VERSION = 0x1009; /// Defines the main segment in the configuration CDI. This is laid out at /// origin 128 to give space for the ACDI user data at the beginning. @@ -41,14 +41,6 @@ CDI_GROUP(IoBoardSegment, Segment(MemoryConfigDefs::SPACE_CONFIG), Offset(128)); CDI_GROUP_ENTRY(internal_config, InternalConfigData); CDI_GROUP_END(); -/// This segment is only needed temporarily until there is program code to set -/// the ACDI user data version byte. -CDI_GROUP(VersionSeg, Segment(MemoryConfigDefs::SPACE_CONFIG), - Name("Version information")); -CDI_GROUP_ENTRY(acdi_user_version, Uint8ConfigEntry, - Name("ACDI User Data version"), Description("Set to 2 and do not change.")); -CDI_GROUP_END(); - /// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx /// to refer to the configuration defined here. CDI_GROUP(ConfigDef, MainCdi()); @@ -58,11 +50,9 @@ CDI_GROUP_ENTRY(ident, Identification); CDI_GROUP_ENTRY(acdi, Acdi); /// Adds a segment for changing the values in the ACDI user-defined /// space. UserInfoSegment is defined in the system header. -CDI_GROUP_ENTRY(userinfo, UserInfoSegment); +CDI_GROUP_ENTRY(userinfo, UserInfoSegment, Name("User Info")); /// Adds the main configuration segment. -CDI_GROUP_ENTRY(seg, IoBoardSegment); -/// Adds the versioning segment. -CDI_GROUP_ENTRY(version, VersionSeg); +CDI_GROUP_ENTRY(seg, IoBoardSegment, Name("Settings")); CDI_GROUP_END(); } // namespace openlcb diff --git a/arduino/examples/ESP32WifiCanBridge/ESP32WifiCanBridge.ino b/arduino/examples/ESP32WifiCanBridge/ESP32WifiCanBridge.ino index 0712c9859..b09e94bbc 100644 --- a/arduino/examples/ESP32WifiCanBridge/ESP32WifiCanBridge.ino +++ b/arduino/examples/ESP32WifiCanBridge/ESP32WifiCanBridge.ino @@ -62,7 +62,7 @@ constexpr gpio_num_t CAN_TX_PIN = GPIO_NUM_5; /// This is the node id to assign to this device, this must be unique /// on the CAN bus. -static constexpr uint64_t NODE_ID = UINT64_C(0x050101011821); +static constexpr uint64_t NODE_ID = UINT64_C(0x050101011828); // Configuring WiFi accesspoint name and password // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/arduino/examples/ESP32WifiCanBridge/config.h b/arduino/examples/ESP32WifiCanBridge/config.h index 045980397..15a1c91a3 100644 --- a/arduino/examples/ESP32WifiCanBridge/config.h +++ b/arduino/examples/ESP32WifiCanBridge/config.h @@ -33,7 +33,7 @@ extern const SimpleNodeStaticValues SNIP_STATIC_DATA = { /// Modify this value every time the EEPROM needs to be cleared on the node /// after an update. -static constexpr uint16_t CANONICAL_VERSION = 0x1000; +static constexpr uint16_t CANONICAL_VERSION = 0x1008; /// Defines the main segment in the configuration CDI. This is laid out at /// origin 128 to give space for the ACDI user data at the beginning. @@ -44,14 +44,6 @@ CDI_GROUP_ENTRY(internal_config, InternalConfigData); CDI_GROUP_ENTRY(wifi, WiFiConfiguration, Name("WiFi Configuration")); CDI_GROUP_END(); -/// This segment is only needed temporarily until there is program code to set -/// the ACDI user data version byte. -CDI_GROUP(VersionSeg, Segment(MemoryConfigDefs::SPACE_CONFIG), - Name("Version information")); -CDI_GROUP_ENTRY(acdi_user_version, Uint8ConfigEntry, - Name("ACDI User Data version"), Description("Set to 2 and do not change.")); -CDI_GROUP_END(); - /// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx /// to refer to the configuration defined here. CDI_GROUP(ConfigDef, MainCdi()); @@ -61,11 +53,9 @@ CDI_GROUP_ENTRY(ident, Identification); CDI_GROUP_ENTRY(acdi, Acdi); /// Adds a segment for changing the values in the ACDI user-defined /// space. UserInfoSegment is defined in the system header. -CDI_GROUP_ENTRY(userinfo, UserInfoSegment); +CDI_GROUP_ENTRY(userinfo, UserInfoSegment, Name("User Info")); /// Adds the main configuration segment. -CDI_GROUP_ENTRY(seg, IoBoardSegment); -/// Adds the versioning segment. -CDI_GROUP_ENTRY(version, VersionSeg); +CDI_GROUP_ENTRY(seg, IoBoardSegment, Name("Settings")); CDI_GROUP_END(); } // namespace openlcb diff --git a/arduino/libify.sh b/arduino/libify.sh index b185fd249..eaa8e3ba6 100755 --- a/arduino/libify.sh +++ b/arduino/libify.sh @@ -158,18 +158,19 @@ rm -f ${TARGET_LIB_DIR}/src/openlcb/CompileCdiMain.cxx \ ${TARGET_LIB_DIR}/src/openlcb/Stream.hxx copy_file src/freertos_drivers/arduino \ + src/freertos_drivers/arduino/* \ src/freertos_drivers/common/DeviceBuffer.{hxx,cxx} \ src/freertos_drivers/common/GpioWrapper.hxx \ src/freertos_drivers/common/CpuLoad.{hxx,cxx} \ src/freertos_drivers/common/WifiDefs.{hxx,cxx} \ src/freertos_drivers/common/libatomic.c \ - src/freertos_drivers/arduino/* copy_file src/freertos_drivers/esp32 \ src/freertos_drivers/esp32/* copy_file src/freertos_drivers/stm32 \ - src/freertos_drivers/st/Stm32Can.* + src/freertos_drivers/st/Stm32Can.* \ + arduino/stm32f_hal_conf.hxx \ copy_file src/os src/os/*.h src/os/*.c src/os/*.hxx \ src/os/{OSImpl,MDNS,OSSelectWakeup}.cxx diff --git a/arduino/library.json b/arduino/library.json index 27ad75cc0..c6d1c12a6 100644 --- a/arduino/library.json +++ b/arduino/library.json @@ -21,7 +21,7 @@ "type": "git", "url": "https://github.com/openmrn/OpenMRNLite" }, - "version": "0.1.1", + "version": "1.0.3", "license": "BSD-2-Clause", "frameworks": "arduino", "platforms": "espressif32", diff --git a/arduino/library.properties b/arduino/library.properties index e679c3390..26e825065 100644 --- a/arduino/library.properties +++ b/arduino/library.properties @@ -1,5 +1,5 @@ name=OpenMRNLite -version=0.1.1 +version=1.0.3 author=Stuart Baker, Mike Dunston, Balazs Racz maintainer=Mike Dunston , Balazs Racz includes=OpenMRNLite.h @@ -7,5 +7,5 @@ sentence=Network protocol stack for model railroading: OpenLCB and LCC implement paragraph=This library implements network protocols for model railroading. In the center is the OpenLCB protocol suite (Open Layout Control Bus), which has been adopted by the NMRA and referenced as LCC (Layout Command Control): a high-performance and highly extensible communications protocol suite for model railroad control. OpenMRN is one of the most extensible implementation of this protocol suite. The Lite version has been adapted to work with the programming model and drivers of the Arduino ecosystem. Currently supports esp32 and stm32 cores. category=Communication url=http://github.com/openmrn/OpenMRNLite -architectures=esp32,stm32 +architectures=esp32,stm32,sam,samd dot_a_linkage=true diff --git a/arduino/stm32f_hal_conf.hxx b/arduino/stm32f_hal_conf.hxx new file mode 100644 index 000000000..da11d1b39 --- /dev/null +++ b/arduino/stm32f_hal_conf.hxx @@ -0,0 +1,15 @@ +#ifndef _OPENMRN_ARDUINO_STM32F_HAL_CONF_HXX +#define _OPENMRN_ARDUINO_STM32F_HAL_CONF_HXX + +#include + +static inline void SetInterruptPriority(uint32_t irq, uint8_t priority) +{ + NVIC_SetPriority((IRQn_Type)irq, priority >> (8U - __NVIC_PRIO_BITS)); +} + +#ifndef configKERNEL_INTERRUPT_PRIORITY +#define configKERNEL_INTERRUPT_PRIORITY 0xA0 +#endif + +#endif // _OPENMRN_ARDUINO_STM32F_HAL_CONF_HXX 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/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() diff --git a/bin/find_distcc.sh b/bin/find_distcc.sh new file mode 100755 index 000000000..4e47536f2 --- /dev/null +++ b/bin/find_distcc.sh @@ -0,0 +1,46 @@ +#!/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=$(cat ~/.distcc/port 2>/dev/null) +if [ -z "${DISTCC_PORT}" ] ; then + # .distcc/port file is not set up. This means the user does not want to use + # distcc. + echo "$1" + 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/revision.py b/bin/revision.py index f011cd8c0..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(" ") @@ -93,15 +93,17 @@ outputcxx += ' "' + gcc + '",\n' outputhxx += '"' + gcc + '\\n"\n' +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') @@ -121,17 +123,19 @@ outputcxx += ':' + os.path.split(os.path.abspath(x))[1] outputhxx += '"' + git_hash + ':' + os.path.split(os.path.abspath(x))[1] + hashopts = "" + if dirty or untracked : - outputcxx += ':' - outputhxx += ':' + hashopts += ':' if dirty : - outputcxx += '-d' - outputhxx += '-d' + hashopts += '-d' if untracked : - outputcxx += '-u' - outputhxx += '-u' - outputcxx += '",\n' - outputhxx += '\\n"\n' + hashopts += '-u' + outputcxx += hashopts + '",\n' + outputhxx += hashopts + '\\n"\n' + + if main_git_hash is None: + main_git_hash = git_hash + hashopts outputcxx += ' nullptr\n' outputcxx += '};\n' @@ -142,6 +146,9 @@ outputhxx += '));\n' outputhxx += 'CDI_GROUP_END();\n' +if main_git_hash is not None: + outputhxx += '\n#define REVISION_GIT_HASH "' + main_git_hash + '"\n' + os.chdir(orig_dir) # generate the *.cxxout style content @@ -165,14 +172,18 @@ "-I", """Thu, """, "-I", """Fri, """, "-I", """Sat, """, options.output + 'Try.hxxout', - options.output + '.hxxout'], stdout=f_null) + options.output + '.hxxout'], + stdout=f_null, stderr=f_null) diffcxx = subprocess.call(['diff', "-I", """Sun, """, "-I", """Mon, """, "-I", """Tue, """, "-I", """Wed, """, "-I", """Thu, """, "-I", """Fri, """, "-I", """Sat, """, options.output + 'Try.cxxout', - options.output + '.cxxout'], stdout=f_null) + options.output + '.cxxout'], + stdout=f_null, stderr=f_null) +# disable this because we are not actually writing a cxx file above. +diffcxx = 0 if diffhxx != 0 : os.system('rm -f ' + options.output + '.hxxout') 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/boards/armv7m/default_handlers.h b/boards/armv7m/default_handlers.h index 4963dabd6..db67288c2 100644 --- a/boards/armv7m/default_handlers.h +++ b/boards/armv7m/default_handlers.h @@ -59,6 +59,8 @@ extern unsigned long __data_section_table_end; extern unsigned long __bss_section_table; extern unsigned long __bss_section_table_end; +#define NVIC_INT_CTRL_R (*((volatile uint32_t *)0xE000ED04)) + /** This hardware initialization code will be called before C++ global objects * are initialized. */ extern void hw_preinit(void); @@ -88,7 +90,7 @@ void reset_handler(void) unsigned long *dst = (unsigned long *)*section_table_addr++; unsigned long len = (unsigned long)*section_table_addr++; - for (; len; len -= 4) + for (; len > 0; len -= 4) { *dst++ = *src++; } @@ -100,7 +102,7 @@ void reset_handler(void) unsigned long *zero = (unsigned long *)*section_table_addr++; unsigned long len = (unsigned long)*section_table_addr++; - for (; len; len -= 4) + for (; len > 0; len -= 4) { *zero++ = 0; } @@ -149,6 +151,7 @@ __attribute__((__naked__)) static void hard_fault_handler(void) " mrsne r0, psp\n" " mov sp, r0 \n" " bkpt #1 \n" + " bx lr \n" ); #endif #if 0 @@ -248,6 +251,18 @@ __attribute__((optimize("-O0"),unused)) void hard_fault_handler_step_2(unsigned // hard_fault_handler_step_3. } +/// This function will be called in an infinite loop from the hard fualt handler. +/// +/// Define it in HwInit.cxx to enable blinking during hard faults. The function +/// should check the timer interrupt flag and if set, call the timer interrupt +/// handler inline, then return. +void wait_with_blinker(void) __attribute__ ((weak)); +void wait_with_blinker(void) +{ + // noop +} + + void hard_fault_handler_step_3(void) { const uint32_t C_DEBUGEN = 0x00000001; uint32_t debugreg = *(volatile uint32_t*)0xE000EDF0; @@ -261,20 +276,26 @@ void hard_fault_handler_step_3(void) { " BKPT #1 \n" ::: "r0", "r1", "r2", "r3"); } - __asm( - " mov r1, %0 \n" - " msr basepri, r1 \n" - " cpsie i\n" - " ldr r0, =faultInfo \n" - " ldr r3, [r0, 12] \n" - " ldr r2, [r0, 8] \n" - " ldr r1, [r0, 4] \n" - " ldr r0, [r0, 0] \n" - ".infloop: \n" - " b.n .infloop \n" - :: "i" ( 0x20 ) : "r0", "r1", "r2", "r3" - ); - while (1); + while (1) + { + // In gdb use `break hard_fault_debug` to get the best possible + // backtrace if you find a target in this infinite loop. + __asm volatile ( + " cpsid i\n" + " ldr r0, =faultInfo \n" + " ldr r3, [r0, 12] \n" + " ldr r2, [r0, 8] \n" + " ldr r1, [r0, 4] \n" + " ldr r0, [r0, 0] \n" + " nop \n" + " nop \n" + " .global hard_fault_debug \n" + "hard_fault_debug: \n" + " nop \n" + ::: "r0", "r1", "r2", "r3" + ); + wait_with_blinker(); + } } static void nmi_handler(void) @@ -313,6 +334,6 @@ void default_interrupt_handler(void) __attribute__ ((weak)); void default_interrupt_handler(void) { _INTCTRL = NVIC_INT_CTRL_R; - while(1); diewith(BLINK_DIE_UNEXPIRQ); + while(1); } diff --git a/boards/bracz-railcom/HwInit.cxx b/boards/bracz-railcom/HwInit.cxx index 56808c353..d955d7451 100644 --- a/boards/bracz-railcom/HwInit.cxx +++ b/boards/bracz-railcom/HwInit.cxx @@ -99,7 +99,7 @@ static TivaCan can0("/dev/can0", CAN0_BASE, INT_RESOLVE(INT_CAN0_, 0)); const unsigned TivaEEPROMEmulation::FAMILY = TM4C123; const size_t EEPROMEmulation::SECTOR_SIZE = 4 * 1024; -static TivaEEPROMEmulation eeprom("/dev/eeprom", 1024); +static TivaEEPROMEmulation eeprom("/dev/eeprom", 1524); const uint32_t RailcomDefs::UART_BASE[] = RAILCOM_BASE; const uint32_t RailcomDefs::UART_PERIPH[] = RAILCOM_PERIPH; @@ -107,7 +107,7 @@ const uint32_t RailcomDefs::UART_PERIPH[] = RAILCOM_PERIPH; TivaDAC dac; TivaGNDControl gnd_control; TivaBypassControl bypass_control; - +unsigned DCCDecode::sampleCount_ = 0; uint8_t RailcomDefs::feedbackChannel_ = 0xff; uint8_t dac_next_packet_mode = 0; diff --git a/boards/bracz-railcom/hardware.hxx b/boards/bracz-railcom/hardware.hxx index 35a485f46..57ffab6c1 100644 --- a/boards/bracz-railcom/hardware.hxx +++ b/boards/bracz-railcom/hardware.hxx @@ -15,6 +15,7 @@ #include "BlinkerGPIO.hxx" #include "utils/Debouncer.hxx" +#define HARDWARE_REVA GPIO_PIN(SW1, GpioInputPU, F, 4); GPIO_PIN(SW2, GpioInputPU, F, 0); @@ -128,6 +129,8 @@ struct Debug typedef DummyPin DccDecodeInterrupts; //typedef LED_GREEN_Pin DccDecodeInterrupts; + typedef DummyPin DccPacketFinishedHook; + // Flips every timer capture interrupt from the dcc deocder flow. // typedef DBG_SIGNAL_Pin RailcomE0; //typedef LED_GREEN_Pin RailcomE0; @@ -176,6 +179,7 @@ struct RailcomDefs static bool need_ch1_cutout() { return true; } + static void middle_cutout_hook() {} static void enable_measurement(bool); static void disable_measurement(); @@ -444,8 +448,6 @@ struct DCCDecode HWREG(TIMER_BASE + TIMER_O_TAMR) |= (TIMER_TAMR_TAMIE); } - static void cap_event_hook() {} - static const auto RCOM_TIMER = TIMER_A; static const auto SAMPLE_PERIOD_CLOCKS = 60000; //static const auto SAMPLE_TIMER_TIMEOUT = TIMER_TIMA_TIMEOUT; @@ -466,6 +468,10 @@ struct DCCDecode static inline void dcc_before_cutout_hook(); static inline void dcc_packet_finished_hook(); static inline void after_feedback_hook(); + + /// counts how many edges / transitions we had on the DCC signal. + static unsigned sampleCount_; + static inline void cap_event_hook() { ++sampleCount_; } }; #endif // ! pindefs_only diff --git a/boards/st-stm32f072b-discovery/HwInit.cxx b/boards/st-stm32f072b-discovery/HwInit.cxx index 3694b444a..f00cfed82 100644 --- a/boards/st-stm32f072b-discovery/HwInit.cxx +++ b/boards/st-stm32f072b-discovery/HwInit.cxx @@ -218,7 +218,7 @@ void hw_preinit(void) /* Starting Error */ HASSERT(0); } - NVIC_SetPriority(TIM14_IRQn, 0); + SetInterruptPriority(TIM14_IRQn, 0); NVIC_EnableIRQ(TIM14_IRQn); } diff --git a/boards/st-stm32f091rc-nucleo-dev-board/HwInit.cxx b/boards/st-stm32f091rc-nucleo-dev-board/HwInit.cxx index be327322b..6a662b198 100644 --- a/boards/st-stm32f091rc-nucleo-dev-board/HwInit.cxx +++ b/boards/st-stm32f091rc-nucleo-dev-board/HwInit.cxx @@ -323,7 +323,7 @@ void hw_preinit(void) HASSERT(0); } __HAL_DBGMCU_FREEZE_TIM14(); - NVIC_SetPriority(TIM14_IRQn, 0); + SetInterruptPriority(TIM14_IRQn, 0); NVIC_EnableIRQ(TIM14_IRQn); } 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/HwInit.cxx b/boards/st-stm32f091rc-nucleo/HwInit.cxx index 5311f984a..9e0a133cf 100644 --- a/boards/st-stm32f091rc-nucleo/HwInit.cxx +++ b/boards/st-stm32f091rc-nucleo/HwInit.cxx @@ -228,7 +228,7 @@ void hw_preinit(void) HASSERT(0); } __HAL_DBGMCU_FREEZE_TIM14(); - NVIC_SetPriority(TIM14_IRQn, 0); + SetInterruptPriority(TIM14_IRQn, 0); NVIC_EnableIRQ(TIM14_IRQn); } 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 */ diff --git a/boards/st-stm32f103rb-cmsis/Stm32Can.cxx b/boards/st-stm32f103rb-cmsis/Stm32Can.cxx index 2df061128..399a626ea 100644 --- a/boards/st-stm32f103rb-cmsis/Stm32Can.cxx +++ b/boards/st-stm32f103rb-cmsis/Stm32Can.cxx @@ -49,6 +49,12 @@ extern "C" { void USB_HP_CAN1_TX_IRQHandler(void); void USB_LP_CAN1_RX0_IRQHandler(void); + +static inline void SetInterruptPriority(uint32_t irq, uint8_t priority) +{ + NVIC_SetPriority((IRQn_Type)irq, priority >> (8U - __NVIC_PRIO_BITS)); +} + } class Stm32CanDriver : public Can @@ -134,8 +140,8 @@ class Stm32CanDriver : public Can { init_can_filter(); // Sets the CAN interrupt priorities to be compatible with FreeRTOS. - NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, configKERNEL_INTERRUPT_PRIORITY); - NVIC_SetPriority(USB_HP_CAN1_TX_IRQn, configKERNEL_INTERRUPT_PRIORITY); + SetInterruptPriority(USB_LP_CAN1_RX0_IRQn, configKERNEL_INTERRUPT_PRIORITY); + SetInterruptPriority(USB_HP_CAN1_TX_IRQn, configKERNEL_INTERRUPT_PRIORITY); CAN_ITConfig(instance_, CAN_IT_TME, DISABLE); NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); NVIC_EnableIRQ(USB_HP_CAN1_TX_IRQn); diff --git a/boards/st-stm32f103rb-cmsis/hw_init.cxx b/boards/st-stm32f103rb-cmsis/hw_init.cxx index a7f97590c..5db0e0e06 100644 --- a/boards/st-stm32f103rb-cmsis/hw_init.cxx +++ b/boards/st-stm32f103rb-cmsis/hw_init.cxx @@ -156,7 +156,7 @@ void setblink(uint32_t pattern) LPC_TIM1->MR0 = 125; LPC_TIM1->MCR = 3; // reset and interrupt on match 0 - NVIC_SetPriority(TIMER1_IRQn, 0); + SetInterruptPriority(TIMER1_IRQn, 0); NVIC_EnableIRQ(TIMER1_IRQn); LPC_TIM1->TCR = 1; // Timer go.*/ diff --git a/boards/st-stm32f103rb-maple/hw_init.cxx b/boards/st-stm32f103rb-maple/hw_init.cxx index 8f4adaff2..556663219 100644 --- a/boards/st-stm32f103rb-maple/hw_init.cxx +++ b/boards/st-stm32f103rb-maple/hw_init.cxx @@ -92,7 +92,7 @@ void setblink(uint32_t pattern) LPC_TIM1->MR0 = 125; LPC_TIM1->MCR = 3; // reset and interrupt on match 0 - NVIC_SetPriority(TIMER1_IRQn, 0); + SetInterruptPriority(TIMER1_IRQn, 0); NVIC_EnableIRQ(TIMER1_IRQn); LPC_TIM1->TCR = 1; // Timer go.*/ diff --git a/boards/st-stm32f303-discovery/HwInit.cxx b/boards/st-stm32f303-discovery/HwInit.cxx index 9320fe1f1..9e033f579 100644 --- a/boards/st-stm32f303-discovery/HwInit.cxx +++ b/boards/st-stm32f303-discovery/HwInit.cxx @@ -245,7 +245,7 @@ void hw_preinit(void) /* Starting Error */ HASSERT(0); } - NVIC_SetPriority(TIM7_IRQn, 0); + SetInterruptPriority(TIM7_IRQn, 0); NVIC_EnableIRQ(TIM7_IRQn); } diff --git a/boards/st-stm32f303re-nucleo-dev-board/HwInit.cxx b/boards/st-stm32f303re-nucleo-dev-board/HwInit.cxx index 65c1f83fd..a62892963 100644 --- a/boards/st-stm32f303re-nucleo-dev-board/HwInit.cxx +++ b/boards/st-stm32f303re-nucleo-dev-board/HwInit.cxx @@ -357,7 +357,7 @@ void hw_preinit(void) HASSERT(0); } __HAL_DBGMCU_FREEZE_TIM17(); - NVIC_SetPriority(TIM17_IRQn, 0); + SetInterruptPriority(TIM17_IRQn, 0); NVIC_EnableIRQ(TIM17_IRQn); } diff --git a/boards/st-stm32f303re-nucleo/HwInit.cxx b/boards/st-stm32f303re-nucleo/HwInit.cxx index 1d443e2ca..c25c032a8 100644 --- a/boards/st-stm32f303re-nucleo/HwInit.cxx +++ b/boards/st-stm32f303re-nucleo/HwInit.cxx @@ -248,7 +248,7 @@ void hw_preinit(void) HASSERT(0); } __HAL_DBGMCU_FREEZE_TIM17(); - NVIC_SetPriority(TIM17_IRQn, 0); + SetInterruptPriority(TIM17_IRQn, 0); NVIC_EnableIRQ(TIM17_IRQn); } diff --git a/boards/st-stm32f767zi-nucleo/HwInit.cxx b/boards/st-stm32f767zi-nucleo/HwInit.cxx index 2c5a7ff9c..53f3ce3fd 100644 --- a/boards/st-stm32f767zi-nucleo/HwInit.cxx +++ b/boards/st-stm32f767zi-nucleo/HwInit.cxx @@ -290,7 +290,7 @@ void hw_preinit(void) /* Starting Error */ HASSERT(0); } - NVIC_SetPriority(TIM8_TRG_COM_TIM14_IRQn, 0); + SetInterruptPriority(TIM8_TRG_COM_TIM14_IRQn, 0); NVIC_EnableIRQ(TIM8_TRG_COM_TIM14_IRQn); } diff --git a/boards/st-stm32l4-generic/startup.c b/boards/st-stm32l4-generic/startup.c new file mode 100644 index 000000000..77cd48efa --- /dev/null +++ b/boards/st-stm32l4-generic/startup.c @@ -0,0 +1,412 @@ +/** \copyright + * Copyright (c) 2015, 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 startup.c + * This file sets up the runtime environment for ST STM32F3 MCUs. + * + * @author Stuart W. Baker + * @date 25 August 2015 + */ + +#include + +#include "FreeRTOSConfig.h" + +/* prototypes */ +extern unsigned long *__stack; +extern void reset_handler(void); +static void nmi_handler(void); +static void hard_fault_handler(void); +static void mpu_fault_handler(void); +static void bus_fault_handler(void); +static void usage_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 debug_interrupt_handler(void); +extern void wwdg_interrupt_handler(void); +extern void pvd_pvm_interrupt_handler(void); +extern void tamp_stamp_interrupt_handler(void); +extern void rtc_wkup_interrupt_handler(void); +extern void flash_interrupt_handler(void); +extern void rcc_interrupt_handler(void); +extern void exti0_interrupt_handler(void); +extern void exti1_interrupt_handler(void); +extern void exti2_interrupt_handler(void); +extern void exti3_interrupt_handler(void); +extern void exti4_interrupt_handler(void); +extern void dma1_channel1_interrupt_handler(void); +extern void dma1_channel2_interrupt_handler(void); +extern void dma1_channel3_interrupt_handler(void); +extern void dma1_channel4_interrupt_handler(void); +extern void dma1_channel5_interrupt_handler(void); +extern void dma1_channel6_interrupt_handler(void); +extern void dma1_channel7_interrupt_handler(void); +extern void adc1_interrupt_handler(void); +extern void can1_tx_interrupt_handler(void); +extern void can1_rx0_interrupt_handler(void); +extern void can1_rx1_interrupt_handler(void); +extern void can1_sce_interrupt_handler(void); +extern void exti9_5_interrupt_handler(void); +extern void tim1_brk_tim15_interrupt_handler(void); +extern void tim1_up_tim16_interrupt_handler(void); +extern void tim1_trg_com_interrupt_handler(void); +extern void tim1_cc_interrupt_handler(void); +extern void tim2_interrupt_handler(void); +extern void tim3_interrupt_handler(void); +extern void i2c1_ev_interrupt_handler(void); +extern void i2c1_er_interrupt_handler(void); +extern void i2c2_ev_interrupt_handler(void); +extern void i2c2_er_interrupt_handler(void); +extern void spi1_interrupt_handler(void); +extern void spi2_interrupt_handler(void); +extern void usart1_interrupt_handler(void); +extern void usart2_interrupt_handler(void); +extern void usart3_interrupt_handler(void); +extern void exti15_10_interrupt_handler(void); +extern void rtc_alarm_interrupt_handler(void); +extern void sdmmc1_interrupt_handler(void); +extern void spi3_interrupt_handler(void); +extern void uart4_interrupt_handler(void); +extern void tim6_dac_interrupt_handler(void); +extern void tim7_interrupt_handler(void); +extern void dma2_channel1_interrupt_handler(void); +extern void dma2_channel2_interrupt_handler(void); +extern void dma2_channel3_interrupt_handler(void); +extern void dma2_channel4_interrupt_handler(void); +extern void dma2_channel5_interrupt_handler(void); +extern void dfsdm1_flt0_interrupt_handler(void); +extern void dfsdm1_flt1_interrupt_handler(void); +extern void comp_interrupt_handler(void); +extern void lptim1_interrupt_handler(void); +extern void lptim2_interrupt_handler(void); +extern void usb_interrupt_handler(void); +extern void dma2_channel6_interrupt_handler(void); +extern void dma2_channel7_interrupt_handler(void); +extern void lpuart1_interrupt_handler(void); +extern void quadspi_interrupt_handler(void); +extern void i2c3_ev_interrupt_handler(void); +extern void i2c3_er_interrupt_handler(void); +extern void sai1_interrupt_handler(void); +extern void swpmi1_interrupt_handler(void); +extern void tsc_interrupt_handler(void); +extern void aes_interrupt_handler(void); +extern void rng_interrupt_handler(void); +extern void fpu_interrupt_handler(void); +extern void crs_interrupt_handler(void); +extern void i2c4_ev_interrupt_handler(void); +extern void i2c4_er_interrupt_handler(void); +extern void ignore_fn(void); + +extern const unsigned long cm3_cpu_clock_hz; + +/** Exception table */ +__attribute__(( + section(".interrupt_vector"))) void (*const __interrupt_vector[])(void) = { + (void (*)(void))(&__stack), /**< 0 initial stack pointer */ + reset_handler, /**< 1 reset vector */ + nmi_handler, /**< 2 non-maskable interrupt */ + hard_fault_handler, /**< 3 hard fault */ + mpu_fault_handler, /**< 4 reserved */ + bus_fault_handler, /**< 5 reserved */ + usage_fault_handler, /**< 6 reserved */ + 0, /**< 7 reserved */ + 0, /**< 8 reserved */ + 0, /**< 9 reserved */ + 0, /**< 10 reserved */ + SVC_Handler, /**< 11 SV call */ + debug_interrupt_handler, /**< 12 reserved */ + 0, /**< 13 reserved -- bootloader appentry */ + PendSV_Handler, /**< 14 pend SV */ + SysTick_Handler, /**< 15 system tick */ + wwdg_interrupt_handler, /**< 0 Window WatchDog Interrupt */ + pvd_pvm_interrupt_handler, /**< 1 PVD/PVM1/PVM3/PVM4 through EXTI Line + detection Interrupts */ + tamp_stamp_interrupt_handler, /**< 2 Tamper and TimeStamp interrupts through + the EXTI line */ + rtc_wkup_interrupt_handler, /**< 3 RTC Wakeup interrupt through the EXTI + line */ + flash_interrupt_handler, /**< 4 FLASH global Interrupt */ + rcc_interrupt_handler, /**< 5 RCC global Interrupt */ + exti0_interrupt_handler, /**< 6 EXTI Line0 Interrupt */ + exti1_interrupt_handler, /**< 7 EXTI Line1 Interrupt */ + exti2_interrupt_handler, /**< 8 EXTI Line2 Interrupt */ + exti3_interrupt_handler, /**< 9 EXTI Line3 Interrupt */ + exti4_interrupt_handler, /**< 10 EXTI Line4 Interrupt */ + dma1_channel1_interrupt_handler, /**< 11 DMA1 Channel 1 global Interrupt */ + dma1_channel2_interrupt_handler, /**< 12 DMA1 Channel 2 global Interrupt */ + dma1_channel3_interrupt_handler, /**< 13 DMA1 Channel 3 global Interrupt */ + dma1_channel4_interrupt_handler, /**< 14 DMA1 Channel 4 global Interrupt */ + dma1_channel5_interrupt_handler, /**< 15 DMA1 Channel 5 global Interrupt */ + dma1_channel6_interrupt_handler, /**< 16 DMA1 Channel 6 global Interrupt */ + dma1_channel7_interrupt_handler, /**< 17 DMA1 Channel 7 global Interrupt */ + adc1_interrupt_handler, /**< 18 ADC1 global Interrupt */ + can1_tx_interrupt_handler, /**< 19 CAN1 TX Interrupt */ + can1_rx0_interrupt_handler, /**< 20 CAN1 RX0 Interrupt */ + can1_rx1_interrupt_handler, /**< 21 CAN1 RX1 Interrupt */ + can1_sce_interrupt_handler, /**< 22 CAN1 SCE Interrupt */ + exti9_5_interrupt_handler, /**< 23 External Line[9:5] Interrupts */ + tim1_brk_tim15_interrupt_handler, /**< 24 TIM1 Break interrupt and TIM15 + global interrupt */ + tim1_up_tim16_interrupt_handler, /**< 25 TIM1 Update Interrupt and TIM16 + global interrupt */ + tim1_trg_com_interrupt_handler, /**< 26 TIM1 Trigger and Commutation + Interrupt */ + tim1_cc_interrupt_handler, /**< 27 TIM1 Capture Compare Interrupt */ + tim2_interrupt_handler, /**< 28 TIM2 global Interrupt */ + tim3_interrupt_handler, /**< 29 TIM3 global Interrupt */ + 0, /**< 30 */ + i2c1_ev_interrupt_handler, /**< 31 I2C1 Event Interrupt */ + i2c1_er_interrupt_handler, /**< 32 I2C1 Error Interrupt */ + i2c2_ev_interrupt_handler, /**< 33 I2C2 Event Interrupt */ + i2c2_er_interrupt_handler, /**< 34 I2C2 Error Interrupt */ + spi1_interrupt_handler, /**< 35 SPI1 global Interrupt */ + spi2_interrupt_handler, /**< 36 SPI2 global Interrupt */ + usart1_interrupt_handler, /**< 37 USART1 global Interrupt */ + usart2_interrupt_handler, /**< 38 USART2 global Interrupt */ + usart3_interrupt_handler, /**< 39 USART3 global Interrupt */ + exti15_10_interrupt_handler, /**< 40 External Line[15:10] Interrupts */ + rtc_alarm_interrupt_handler, /**< 41 RTC Alarm (A and B) through EXTI Line + Interrupt */ + 0, /**< 42 */ + 0, /**< 43 */ + 0, /**< 44 */ + 0, /**< 45 */ + 0, /**< 46 */ + 0, /**< 47 */ + 0, /**< 48 */ + sdmmc1_interrupt_handler, /**< 49 SDMMC1 global Interrupt */ + 0, /**< 50 */ + spi3_interrupt_handler, /**< 51 SPI3 global Interrupt */ + uart4_interrupt_handler, /**< 52 UART4 global Interrupt */ + 0, /**< 53 */ + tim6_dac_interrupt_handler, /**< 54 TIM6 global and DAC1&2 underrun error + interrupts */ + tim7_interrupt_handler, /**< 55 TIM7 global interrupt */ + dma2_channel1_interrupt_handler, /**< 56 DMA2 Channel 1 global Interrupt */ + dma2_channel2_interrupt_handler, /**< 57 DMA2 Channel 2 global Interrupt */ + dma2_channel3_interrupt_handler, /**< 58 DMA2 Channel 3 global Interrupt */ + dma2_channel4_interrupt_handler, /**< 59 DMA2 Channel 4 global Interrupt */ + dma2_channel5_interrupt_handler, /**< 60 DMA2 Channel 5 global Interrupt */ + dfsdm1_flt0_interrupt_handler, /**< 61 DFSDM1 Filter 0 global Interrupt */ + dfsdm1_flt1_interrupt_handler, /**< 62 DFSDM1 Filter 1 global Interrupt */ + 0, /**< 63 */ + comp_interrupt_handler, /**< 64 COMP1 and COMP2 Interrupts */ + lptim1_interrupt_handler, /**< 65 LP TIM1 interrupt */ + lptim2_interrupt_handler, /**< 66 LP TIM2 interrupt */ + usb_interrupt_handler, /**< 67 USB event Interrupt */ + dma2_channel6_interrupt_handler, /**< 68 DMA2 Channel 6 global interrupt */ + dma2_channel7_interrupt_handler, /**< 69 DMA2 Channel 7 global interrupt */ + lpuart1_interrupt_handler, /**< 70 LP UART1 interrupt */ + quadspi_interrupt_handler, /**< 71 Quad SPI global interrupt */ + i2c3_ev_interrupt_handler, /**< 72 I2C3 event interrupt */ + i2c3_er_interrupt_handler, /**< 73 I2C3 error interrupt */ + sai1_interrupt_handler, /**< 74 Serial Audio Interface 1 global interrupt */ + 0, /**< 75 */ + swpmi1_interrupt_handler, /**< 76 SWPMI1 global interrupt */ + tsc_interrupt_handler, /**< 77 Touch Sense Controller global interrupt */ + 0, /**< 78 */ + aes_interrupt_handler, /**< 79 AES global interrupt */ + rng_interrupt_handler, /**< 80 RNG global interrupt */ + fpu_interrupt_handler, /**< 81 FPU global interrupt */ + crs_interrupt_handler, /**< 82 CRS global interrupt */ + i2c4_ev_interrupt_handler, /**< 83 I2C4 Event interrupt */ + i2c4_er_interrupt_handler, /**< 84 I2C4 Error interrupt */ + ignore_fn /**< forces the linker to add this fn */ +}; + +/** Get the system clock requency. + * @return SystemCoreClock + */ +__attribute__((__weak__)) uint32_t HAL_RCC_GetSysClockFreq(void) +{ + return cm3_cpu_clock_hz; +} + +/** Stub function to make the HAL happy. We don't need it for any of our + * drivers. + * + * @return 0 + */ +uint32_t HAL_GetTick(void) +{ + return 0; +} + +#include "../boards/armv7m/default_handlers.h" + +void debug_interrupt_handler(void) + __attribute__ ((weak, alias ("default_interrupt_handler"))); +void wwdg_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void pvd_pvm_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tamp_stamp_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void rtc_wkup_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void flash_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void rcc_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void exti0_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void exti1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void exti2_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void exti3_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void exti4_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma1_channel1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma1_channel2_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma1_channel3_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma1_channel4_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma1_channel5_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma1_channel6_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma1_channel7_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void adc1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void can1_tx_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void can1_rx0_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void can1_rx1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void can1_sce_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void exti9_5_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tim1_brk_tim15_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tim1_up_tim16_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tim1_trg_com_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tim1_cc_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tim2_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tim3_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void i2c1_ev_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void i2c1_er_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void i2c2_ev_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void i2c2_er_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 usart1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void usart2_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void usart3_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void exti15_10_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void rtc_alarm_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void sdmmc1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void spi3_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void uart4_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tim6_dac_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tim7_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma2_channel1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma2_channel2_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma2_channel3_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma2_channel4_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma2_channel5_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dfsdm1_flt0_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dfsdm1_flt1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void comp_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void lptim1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void lptim2_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void usb_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma2_channel6_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void dma2_channel7_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void lpuart1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void quadspi_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void i2c3_ev_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void i2c3_er_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void sai1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void swpmi1_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void tsc_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void aes_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void rng_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void fpu_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void crs_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void i2c4_ev_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); +void i2c4_er_interrupt_handler(void) + __attribute__((weak, alias("default_interrupt_handler"))); diff --git a/boards/st-stm32l4-generic/target.ld b/boards/st-stm32l4-generic/target.ld new file mode 120000 index 000000000..6a6d5092b --- /dev/null +++ b/boards/st-stm32l4-generic/target.ld @@ -0,0 +1 @@ +../armv7m/target.ld \ No newline at end of file diff --git a/boards/st-stm32l432kc-nucleo/HwInit.cxx b/boards/st-stm32l432kc-nucleo/HwInit.cxx new file mode 100644 index 000000000..e62215757 --- /dev/null +++ b/boards/st-stm32l432kc-nucleo/HwInit.cxx @@ -0,0 +1,274 @@ +/** \copyright + * Copyright (c) 2018, 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 HwInit.cxx + * + * This file represents the hardware initialization for the STM32F303RE Nucelo + * board (bare). + * + * @author Balazs Racz + * @date April 18, 2018 + */ + +#include +#include + +#include "stm32f_hal_conf.hxx" +#include "stm32l4xx_hal.h" + +#include "os/OS.hxx" +#include "Stm32Uart.hxx" +#include "Stm32Can.hxx" +#include "Stm32EEPROMEmulation.hxx" +#include "hardware.hxx" + +/** override stdin */ +const char *STDIN_DEVICE = "/dev/ser0"; + +/** override stdout */ +const char *STDOUT_DEVICE = "/dev/ser0"; + +/** override stderr */ +const char *STDERR_DEVICE = "/dev/ser0"; + +/** UART 0 serial driver instance */ +static Stm32Uart uart0("/dev/ser0", USART2, USART2_IRQn); + +/** CAN 0 CAN driver instance */ +static Stm32Can can0("/dev/can0"); + +/** EEPROM emulation driver. The file size might be made bigger. */ +static Stm32EEPROMEmulation eeprom0("/dev/eeprom", 512); + +const size_t EEPROMEmulation::SECTOR_SIZE = 2048; +const bool EEPROMEmulation::SHADOW_IN_RAM = true; + +extern "C" { + +/** Blink LED */ +uint32_t blinker_pattern = 0; +static uint32_t rest_pattern = 0; + +void hw_set_to_safe(void) +{ +} + +void reboot() +{ + NVIC_SystemReset(); +} + +void resetblink(uint32_t pattern) +{ + blinker_pattern = pattern; + rest_pattern = pattern ? 1 : 0; + BLINKER_RAW_Pin::set(pattern ? true : false); + /* make a timer event trigger immediately */ +} + +void setblink(uint32_t pattern) +{ + resetblink(pattern); +} + + +void tim7_interrupt_handler(void) +{ + // + // Clear the timer interrupt. + // + TIM7->SR = ~TIM_IT_UPDATE; + + // Set output LED. + BLINKER_RAW_Pin::set(rest_pattern & 1); + + // Shift and maybe reset pattern. + rest_pattern >>= 1; + if (!rest_pattern) + { + rest_pattern = blinker_pattern; + } +} + +void diewith(uint32_t pattern) +{ + // vPortClearInterruptMask(0x20); + asm("cpsie i\n"); + + resetblink(pattern); + while (1) + ; +} + +/** CPU clock speed. */ +const unsigned long cm3_cpu_clock_hz = 80000000; +uint32_t SystemCoreClock; +const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9}; +const uint8_t APBPrescTable[8] = {0, 0, 0, 0, 1, 2, 3, 4}; +const uint32_t MSIRangeTable[12] = {100000U, 200000U, 400000U, 800000U, + 1000000U, 2000000U, 4000000U, 8000000U, 16000000U, 24000000U, 32000000U, + 48000000U}; +const uint32_t HSEValue = 8000000; + +/** + * @brief System Clock Configuration + * The system Clock is configured as follow : + * System Clock source = PLL (MSI) + * SYSCLK(Hz) = 80000000 + * HCLK(Hz) = 80000000 + * AHB Prescaler = 1 + * APB1 Prescaler = 1 + * APB2 Prescaler = 1 + * MSI Frequency(Hz) = 4000000 + * PLL_M = 1 + * PLL_N = 40 + * PLL_R = 2 + * PLL_P = 7 + * PLL_Q = 4 + * Flash Latency(WS) = 4 + * @param None + * @retval None + */ +static void clock_setup(void) +{ + HAL_RCC_DeInit(); + + RCC_ClkInitTypeDef RCC_ClkInitStruct; + RCC_OscInitTypeDef RCC_OscInitStruct; + + /* MSI is enabled after System reset, activate PLL with MSI as source */ + RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI; + RCC_OscInitStruct.MSIState = RCC_MSI_ON; + RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6; + RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT; + RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; + RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI; + RCC_OscInitStruct.PLL.PLLM = 1; + RCC_OscInitStruct.PLL.PLLN = 40; + RCC_OscInitStruct.PLL.PLLR = 2; + RCC_OscInitStruct.PLL.PLLP = 7; + RCC_OscInitStruct.PLL.PLLQ = 4; + HASSERT(HAL_RCC_OscConfig(&RCC_OscInitStruct) == HAL_OK); + + /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 + clocks dividers */ + RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | + RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2); + RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; + RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; + RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; + RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; + HASSERT(HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) == HAL_OK); + + // This will fail if the clocks are somehow misconfigured. + HASSERT(SystemCoreClock == cm3_cpu_clock_hz); +} + +/// We don't need the HAL tick configuration code to run. FreeRTOS will take +/// care of that. +HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority) +{ + return HAL_OK; +} + +/** Initialize the processor hardware. + */ +void hw_preinit(void) +{ + /* Globally disables interrupts until the FreeRTOS scheduler is up. */ + asm("cpsid i\n"); + + /* these FLASH settings enable opertion at 80 MHz */ + __HAL_FLASH_PREFETCH_BUFFER_ENABLE(); + __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_4); + + /* setup the system clock */ + clock_setup(); + + /* enable peripheral clocks */ + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_USART2_CLK_ENABLE(); + __HAL_RCC_CAN1_CLK_ENABLE(); + __HAL_RCC_TIM7_CLK_ENABLE(); + + /* setup pinmux */ + GPIO_InitTypeDef gpio_init; + memset(&gpio_init, 0, sizeof(gpio_init)); + + /* USART2 pinmux on PA2 and PA15 */ + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_PULLUP; + gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + gpio_init.Alternate = GPIO_AF7_USART2; + gpio_init.Pin = GPIO_PIN_2; + HAL_GPIO_Init(GPIOA, &gpio_init); + gpio_init.Pin = GPIO_PIN_15; + gpio_init.Alternate = GPIO_AF3_USART2; + HAL_GPIO_Init(GPIOA, &gpio_init); + + /* CAN pinmux on PA11 and PA12 */ + gpio_init.Mode = GPIO_MODE_AF_PP; + gpio_init.Pull = GPIO_PULLUP; + gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; + gpio_init.Alternate = GPIO_AF9_CAN1; + gpio_init.Pin = GPIO_PIN_11; + HAL_GPIO_Init(GPIOA, &gpio_init); + gpio_init.Pin = GPIO_PIN_12; + HAL_GPIO_Init(GPIOA, &gpio_init); + + GpioInit::hw_init(); + + /* Initializes the blinker timer. */ + TIM_HandleTypeDef TimHandle; + memset(&TimHandle, 0, sizeof(TimHandle)); + TimHandle.Instance = TIM7; + TimHandle.Init.Period = configCPU_CLOCK_HZ / 10000 / 8; + TimHandle.Init.Prescaler = 10000; + TimHandle.Init.ClockDivision = 0; + TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP; + TimHandle.Init.RepetitionCounter = 0; + if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK) + { + /* Initialization Error */ + HASSERT(0); + } + if (HAL_TIM_Base_Start_IT(&TimHandle) != HAL_OK) + { + /* Starting Error */ + HASSERT(0); + } + __HAL_DBGMCU_FREEZE_TIM7(); + SetInterruptPriority(TIM7_IRQn, 0); + NVIC_EnableIRQ(TIM7_IRQn); +} + +void usart2_interrupt_handler(void) +{ + Stm32Uart::interrupt_handler(1); +} + +} diff --git a/boards/st-stm32l432kc-nucleo/Makefile b/boards/st-stm32l432kc-nucleo/Makefile new file mode 100644 index 000000000..414f19595 --- /dev/null +++ b/boards/st-stm32l432kc-nucleo/Makefile @@ -0,0 +1,56 @@ +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" \ +) + +# Find STM32CubeL4 libraries +include $(OPENMRNPATH)/etc/stm32cubel4.mk + +LDFLAGSEXTRA += +SYSLIBRARIESEXTRA += -lfreertos_drivers_stm32cubel432xx +OBJEXTRA += + +CFLAGS += -DSTM32L432xx +CXXFLAGS += -DSTM32L432xx +OPENOCDARGS = -f board/st_nucleo_l476rg.cfg + +ifndef TARGET +export TARGET := freertos.armv7m +endif +include $(OPENMRNPATH)/etc/prog.mk + +ifndef DEFAULT_ADDRESS +DEFAULT_ADDRESS=0x16 +endif + +include $(OPENMRNPATH)/etc/node_id.mk + +# 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).f303.$$(printf %02x%02x $(ADDRHIGH) $$(($(ADDRESS)+$$i-1))).bin ; done + +ifeq ($(call find_missing_deps,OPENOCDPATH OPENOCDSCRIPTSPATH),) +all: $(EXECUTABLE).bin + +flash: $(EXECUTABLE)$(EXTENTION) $(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" + +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/boards/st-stm32l432kc-nucleo/hardware.hxx b/boards/st-stm32l432kc-nucleo/hardware.hxx new file mode 100644 index 000000000..0598aa2ba --- /dev/null +++ b/boards/st-stm32l432kc-nucleo/hardware.hxx @@ -0,0 +1,24 @@ + +#include "Stm32Gpio.hxx" +#include "utils/GpioInitializer.hxx" +#include "BlinkerGPIO.hxx" + +GPIO_PIN(LED_GREEN_RAW, LedPin, B, 3); + +GPIO_PIN(IN_A0, GpioInputPU, A, 0); +GPIO_PIN(IN_A1, GpioInputPU, A, 1); +GPIO_PIN(IN_A2, GpioInputPU, A, 3); +GPIO_PIN(IN_A3, GpioInputPU, A, 4); + +GPIO_PIN(OUT_D3, GpioOutputSafeLow, B, 0); +GPIO_PIN(OUT_D4, GpioOutputSafeLow, B, 7); +GPIO_PIN(OUT_D5, GpioOutputSafeLow, B, 6); +GPIO_PIN(OUT_D6, GpioOutputSafeLow, B, 1); + +typedef GpioInitializer + GpioInit; + +typedef LED_GREEN_RAW_Pin BLINKER_RAW_Pin; +typedef BLINKER_Pin LED_GREEN_Pin; diff --git a/boards/st-stm32l432kc-nucleo/memory_map.ld b/boards/st-stm32l432kc-nucleo/memory_map.ld new file mode 100644 index 000000000..f5ed6e1e7 --- /dev/null +++ b/boards/st-stm32l432kc-nucleo/memory_map.ld @@ -0,0 +1,15 @@ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 232K + EEPROMEMU (r) : ORIGIN = 0x0803A000, LENGTH = 16K + BOOTLOADER (rx) : ORIGIN = 0x0803E000, LENGTH = 8K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K +} + +__flash_start = ORIGIN(FLASH); +__flash_end = ORIGIN(FLASH) + LENGTH(FLASH); +__eeprom_start = ORIGIN(EEPROMEMU); +__eeprom_end = ORIGIN(EEPROMEMU) + LENGTH(EEPROMEMU); +__bootloader_start = ORIGIN(BOOTLOADER); +__app_header_offset = 0x270; +__bootloader_magic_ptr = ORIGIN(RAM); diff --git a/boards/st-stm32l432kc-nucleo/startup.c b/boards/st-stm32l432kc-nucleo/startup.c new file mode 120000 index 000000000..d4c86bd16 --- /dev/null +++ b/boards/st-stm32l432kc-nucleo/startup.c @@ -0,0 +1 @@ +../st-stm32l4-generic/startup.c \ No newline at end of file diff --git a/boards/st-stm32l432kc-nucleo/target.ld b/boards/st-stm32l432kc-nucleo/target.ld new file mode 120000 index 000000000..4c654e89a --- /dev/null +++ b/boards/st-stm32l432kc-nucleo/target.ld @@ -0,0 +1 @@ +../st-stm32l4-generic/target.ld \ No newline at end of file diff --git a/boards/ti-bracz-acc3/hardware.v3.hxx b/boards/ti-bracz-acc3/hardware.v3.hxx index fe875b6ea..414315132 100644 --- a/boards/ti-bracz-acc3/hardware.v3.hxx +++ b/boards/ti-bracz-acc3/hardware.v3.hxx @@ -174,7 +174,8 @@ struct RailcomHw static bool need_ch1_cutout() { return true; } static uint8_t get_feedback_channel() { return 0xff; } - + static void middle_cutout_hook() {} + /// @returns a bitmask telling which pins are active. Bit 0 will be set if /// channel 0 is active (drawing current). static uint8_t sample() { diff --git a/boards/ti-bracz-cs-123/HwInit.cxx b/boards/ti-bracz-cs-123/HwInit.cxx index 89d8e26b2..9c70c7330 100644 --- a/boards/ti-bracz-cs-123/HwInit.cxx +++ b/boards/ti-bracz-cs-123/HwInit.cxx @@ -84,19 +84,33 @@ const size_t EEPROMEmulation::SECTOR_SIZE = (4*1024); static TivaEEPROMEmulation eeprom("/dev/eeprom", 2000); -// Bit storing whether our dcc output is enabled or not. -static bool g_dcc_on = false; - uint32_t feedback_sample_overflow_count = 0; +// If 1, enabled spread spectrum randomization of the DCC timings. +uint8_t spreadSpectrum = 0; TivaRailcomDriver railcom_driver("/dev/railcom"); TivaDCC dcc_hw("/dev/mainline", &railcom_driver); + +DccOutput* get_dcc_output(DccOutput::Type type) { + switch (type) + { + case DccOutput::TRACK: + return DccOutputImpl::instance(); + case DccOutput::PGM: + return DccOutputImpl::instance(); + case DccOutput::LCC: + return DccOutputImpl::instance(); + } + return nullptr; +} + extern "C" { void hw_set_to_safe(void) { - dcc_hw.disable_output(); + DccHwDefs::Output::set_disable_reason( + DccOutput::DisableReason::INITIALIZATION_PENDING); } void timer1a_interrupt_handler(void) @@ -193,6 +207,7 @@ void set_gpio_puinput(uint32_t port, uint32_t pin) { MAP_GPIOPadConfigSet(port, pin, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU); } +/* void enable_dcc() { g_dcc_on = true; MAP_GPIOPinWrite(LED_BLUE, 0xff); @@ -214,6 +229,7 @@ void setshorted_dcc() { bool query_dcc() { return g_dcc_on; } +*/ /** Initialize the processor hardware. */ @@ -232,8 +248,9 @@ void hw_preinit(void) // These pins are parallel-connected to the outputs. set_gpio_extinput(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_1); /* Initialize the DCC Timers and GPIO outputs */ - dcc_hw.hw_init(); - disable_dcc(); + dcc_hw.hw_preinit(); + DccHwDefs::Output::set_disable_reason( + DccOutput::DisableReason::INITIALIZATION_PENDING); /* Setup the system clock. */ MAP_SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | diff --git a/boards/ti-bracz-cs-connected/HwInit.cxx b/boards/ti-bracz-cs-connected/HwInit.cxx index b53358eb8..e5f244d19 100644 --- a/boards/ti-bracz-cs-connected/HwInit.cxx +++ b/boards/ti-bracz-cs-connected/HwInit.cxx @@ -106,12 +106,26 @@ static TivaUart uart2("/dev/ser0", UART2_BASE, INT_RESOLVE(INT_UART2_, 0)); /** CAN 0 CAN driver instance */ static TivaCan can0("/dev/can0", CAN0_BASE, INT_RESOLVE(INT_CAN0_, 0)); -// Bit storing whether our dcc output is enabled or not. -static bool g_dcc_on = false; +uint32_t feedback_sample_overflow_count = 0; +// If 1, enabled spread spectrum randomization of the DCC timings. +uint8_t spreadSpectrum = 0; TivaRailcomDriver railcom_driver("/dev/railcom"); TivaDCC dcc_hw("/dev/mainline", &railcom_driver); +DccOutput* get_dcc_output(DccOutput::Type type) { + switch (type) + { + case DccOutput::TRACK: + return DccOutputImpl::instance(); + case DccOutput::PGM: + return DccOutputImpl::instance(); + case DccOutput::LCC: + return DccOutputImpl::instance(); + } + return nullptr; +} + extern "C" { /** Timer interrupt for DCC packet handling. */ @@ -138,7 +152,8 @@ void uart2_interrupt_handler(void) void hw_set_to_safe(void) { - dcc_hw.disable_output(); + DccHwDefs::Output::set_disable_reason( + DccOutput::DisableReason::INITIALIZATION_PENDING); AccHwDefs::ACC_ENABLE_Pin::hw_set_to_safe(); } @@ -221,6 +236,7 @@ void set_gpio_puinput(uint32_t port, uint32_t pin) { MAP_GPIOPadConfigSet(port, pin, GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU); } +/* void enable_dcc() { g_dcc_on = true; io::TrackOnLed::set(true); @@ -242,6 +258,7 @@ void setshorted_dcc() { bool query_dcc() { return g_dcc_on; } +*/ /** Initialize the processor hardware. */ @@ -270,8 +287,9 @@ void hw_preinit(void) MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION); MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); - dcc_hw.hw_init(); - disable_dcc(); + dcc_hw.hw_preinit(); + DccHwDefs::Output::set_disable_reason( + DccOutput::DisableReason::INITIALIZATION_PENDING); RC_DEBUG_Pin::hw_init(); // controls the accessory bus. diff --git a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx index 4a113c0a2..e337017e5 100644 --- a/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx +++ b/boards/ti-ek-tm4c123gxl-launchpad/HwInit.cxx @@ -110,6 +110,15 @@ StoredBitSet* g_gpio_stored_bit_set = nullptr; constexpr unsigned EEPROM_BIT_COUNT = 84; constexpr unsigned EEPROM_BITS_PER_CELL = 28; +/// This variable will be set to 1 when a write arrives to the eeprom. +uint8_t eeprom_updated = 0; + +// Overridesthe default behavior to keep track of eeprom writes. +void EEPROMEmulation::updated_notification() +{ + eeprom_updated = 1; +} + extern "C" { void hw_set_to_safe(void); @@ -172,6 +181,7 @@ struct RailcomDefs static void disable_measurement() {} static bool need_ch1_cutout() { return true; } static uint8_t get_feedback_channel() { return 0xff; } + static void middle_cutout_hook() {} /** @returns a bitmask telling which pins are active. Bit 0 will be set if * channel 0 is active (drawing current).*/ @@ -192,6 +202,9 @@ const uint32_t RailcomDefs::UART_PERIPH[] = {SYSCTL_PERIPH_UART1}; static TivaRailcomDriver railcom_driver("/dev/railcom"); +// If 1, enabled spread spectrum randomization of the DCC timings. +uint8_t spreadSpectrum = 0; + struct DccHwDefs { /// base address of a capture compare pwm timer pair static const unsigned long CCP_BASE = TIMER0_BASE; @@ -208,6 +221,10 @@ struct DccHwDefs { static const int RAILCOM_CUTOUT_START_DELTA_USEC = -20; static const int RAILCOM_CUTOUT_MID_DELTA_USEC = 0; static const int RAILCOM_CUTOUT_END_DELTA_USEC = -10; + static const int RAILCOM_CUTOUT_POST_DELTA_USEC = -16; + /// Adds this to the negative half after the railcom cutout is done. + static const int RAILCOM_CUTOUT_POST_NEGATIVE_DELTA_USEC = -4; + /** These timer blocks will be synchronized once per packet, when the * deadband delay is set up. */ @@ -237,14 +254,16 @@ struct DccHwDefs { * '1' bit */ static int dcc_preamble_count() { return 16; } + static bool generate_railcom_halfzero() { return true; } + static void flip_led() {} /** the time (in nanoseconds) to wait between turning off the low driver and * turning on the high driver. */ - static const int H_DEADBAND_DELAY_NSEC = 250; + static const int H_DEADBAND_DELAY_NSEC = 0; /** the time (in nanoseconds) to wait between turning off the high driver and * turning on the low driver. */ - static const int L_DEADBAND_DELAY_NSEC = 250; + static const int L_DEADBAND_DELAY_NSEC = 0; /** number of outgoing messages we can queue */ static const size_t Q_SIZE = 4; diff --git a/doc/byte_stream.md b/doc/byte_stream.md new file mode 100644 index 000000000..5b2f4d394 --- /dev/null +++ b/doc/byte_stream.md @@ -0,0 +1,137 @@ +# Byte Streams + +***WARNING*** this is currently a proposal that is not implemented yet. + +This document describes a mechanism by which OpenMRN components can exchange a +unidirectional untyped data stream. The API is laid out for compatibility with +the StateFlow concept and provides asynchronous implementations to limit memory +usage and flow control. + +## Use-cases + +- OpenLCB Stream sending. The client component sends data to the stream + transmitter using Byte Streams. + +- OpenLCB Stream receiving. The client component is receiving data from the + Stream service using Byte Streams. + +- A fast implementation of a TCP Hub should use Byte Streams to represent the + TCP messages for proxying between ports. + +## Non-goals + +- It is not a goal to use the Byte Stream API to write from a State Flow to an + fd. (Neither sockets, pipes, nor physical files.) Writing to an fd should be + done by the native StateFlow features like `write_repeated`. + +## Requirements + +- Memory allocation + + - Allocation has to avoid memory fragmentation. While the expectation is that + larger blocks of memory are allocated in one go, these blocks should be + reused between different users of Byte Streams and at different times, + instead of returning to malloc/free. + + - Block allocation size should be 1 kbyte. This is small enough that even + 32kbyte MCUs can use the codebase, but large enough to capture meaningful + TCP packet sizes. + +- Use and copy + + - The implementation should be zero-copy. Once some data is in a byte buffer, + that data should not need to be copied in order to forward it to other byte + buffers or byte streams. + + - Reference counting should be available when the payload needs to be used + in multiple places. + + - It should be possible to take a subset of a buffer and send it to a + different byte stream. (Routers will do this.) + + - A special zero-copy mechanism should be available when the source data is + already in memory or in flash. Sending these bytes into the flow should not + need block allocation and data copy. + +- Flow control + + - The data source has to be blocked in memory allocation when the sink has + not yet consumed the data. + + - The amount of read-ahead should be configurable (i.e., how much memory does + the source fill in before it gets blocked on waiting for the sink). + + +## Implementation + +We define two buffer types. + +- `using RawBuffer = Buffer;` + + This buffer holds the raw data. This buffer is reference counted, shared + between all sinks, and never entered into a `Q`. All sinks consider this + buffer as read-only. + +- `using ByteBuffer = Buffer;` + + This buffer holds a reference to a RawBuffer, and start/end pointers within + that describe the exact range of bytes to use. This buffer is not shareable, + it can be entered into a queue, i.e., sent to a `StateFlowWithQueue` / + `FlowInterface`. + +### Memory allocation + +The `RawBuffer` data blocks should come from `rawBufferPool`, which is a +`DynamicPool` that is not the same as mainBufferPool, but instantiated with a +single bucket of ~1 kbyte size (technically, `sizeof(RawBuffer)`). This ensures +that the memory fragmentation requirements are met. When a RawBuffer is +released, it goes back to the freelist of the `rawBufferPool`. + +To limit the amount of memory allocated, a `LimitedPool` can be instantiated by +the data source which specifies a fixed number of 1kbyte blocks. The backing +pool shall be set to `rawBufferPool`. + +### Memory ownership / deallocation + +`ByteChunk` contains an `BufferPtr`, which is a unique_ptr that +unref's the buffer upon the destructor. This represents the ownership of a +reference to a `RawBuffer`. The destructor of `ByteChunk` will automatically +release this reference. + +It is optional for a ByteChunk to own a RawBuffer reference. A ByteChunk can +also be created from externally-owned memory, such as flash. In this case the +`unique_ptr` remains as nullptr, and no unref happens in the destructor. + +### Zero-copy + +To make a copy of a piece of data, a new ByteBuffer is allocated, and +initialized with a new reference of the same RawBuffer. The copy can have the +same data, or a contiguous substring of the data. Using a substring is helpful +for example when we take the payload of an incoming TCP stream message and +forward it to the stream reader client. It can also be helpful when taking an +incoming message, taking off the header, prepending a new header, and sending +it out on a different port. + +## Definition + +``` +struct RawData +{ + uint8_t payload[1024]; +}; + +using RawBuffer = Buffer; + +struct ByteChunk +{ + BufferPtr ownedData_; + + uint8_t* data_ {nullptr}; + + size_t size_ {0}; +}; + +using ByteBuffer = Buffer; + +using ByteSink = FlowInterface>; +``` diff --git a/etc/applib.mk b/etc/applib.mk index 386dfb071..43090b902 100644 --- a/etc/applib.mk +++ b/etc/applib.mk @@ -3,7 +3,7 @@ ifeq ($(TARGET),) TARGET := $(notdir $(realpath $(CURDIR)/..)) endif BASENAME := $(notdir $(CURDIR)) -SRCDIR = $(abspath ../../../$(BASENAME)) +SRCDIR ?= $(abspath ../../../$(BASENAME)) VPATH = $(SRCDIR) INCLUDES += -I./ -I../ -I../include @@ -20,9 +20,11 @@ exist := $(wildcard sources) ifneq ($(strip $(exist)),) include sources else +FULLPATHASMSRCS := $(wildcard $(VPATH)/*.S) FULLPATHCSRCS = $(wildcard $(VPATH)/*.c) FULLPATHCXXSRCS = $(wildcard $(VPATH)/*.cxx) FULLPATHCPPSRCS = $(wildcard $(VPATH)/*.cpp) +ASMSRCS = $(notdir $(FULLPATHASMSRCS)) $(wildcard *.S) CSRCS = $(notdir $(FULLPATHCSRCS)) CXXSRCS = $(notdir $(FULLPATHCXXSRCS)) CPPSRCS = $(notdir $(FULLPATHCPPSRCS)) @@ -33,7 +35,7 @@ ifdef APP_PATH INCLUDES += -I$(APP_PATH) endif -OBJS = $(CXXSRCS:.cxx=.o) $(CPPSRCS:.cpp=.o) $(CSRCS:.c=.o) +OBJS = $(CXXSRCS:.cxx=.o) $(CPPSRCS:.cpp=.o) $(CSRCS:.c=.o) $(ASMSRCS:.S=.o) LIBNAME = lib$(BASENAME).a ifdef BOARD @@ -56,19 +58,19 @@ all: $(LIBNAME) -include $(OBJS:.o=.d) .SUFFIXES: -.SUFFIXES: .o .c .cxx .cpp +.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) -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 d78e1c597..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 @@ -28,11 +28,15 @@ INCLUDES += -I$(FREERTOSPATH)/Source/include \ -I$(OPENMRNPATH)/src/freertos_drivers/common #ARCHOPTIMIZATION = -D__NEWLIB__ -ARCHOPTIMIZATION += -O3 -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer -fdata-sections -ffunction-sections -#ARCHOPTIMIZATION += -Os -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer -fdata-sections -ffunction-sections +#ARCHOPTIMIZATION += -O3 -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer -fdata-sections -ffunction-sections +ARCHOPTIMIZATION += -Os -fno-strict-aliasing -fno-strength-reduce -fomit-frame-pointer -fdata-sections -ffunction-sections ARCHFLAGS = -g -MD -MP -march=armv7-m -mthumb -mfloat-abi=soft +ifdef DETERMINISTIC_COMPILATION +ARCHFLAGS += -frandom-seed=$(shell echo $(abspath $<) | md5sum | sed 's/\(.*\) .*/\1/') +endif + ASFLAGS = -c $(ARCHFLAGS) CORECFLAGS = $(ARCHFLAGS) -Wall -Werror -Wno-unknown-pragmas \ diff --git a/etc/core_test.mk b/etc/core_test.mk index a9262eb84..328360c64 100644 --- a/etc/core_test.mk +++ b/etc/core_test.mk @@ -30,26 +30,33 @@ CXXFLAGS += $(INCLUDES) .SUFFIXES: .o .otest .c .cxx .cxxtest .test .testmd5 .testout -LIBDIR ?= lib +ifdef LIBDIR +# we are under prog.mk +TESTLIBDEPS += $(foreach lib,$(SUBDIRS),lib/lib$(lib).a) +else +LIBDIR = lib +endif +TESTLIBDEPS += $(foreach lib,$(CORELIBS),$(LIBDIR)/lib$(lib).a) + LDFLAGS += -L$(LIBDIR) $(LIBDIR)/timestamp: $(BUILDDIRS) $(info test deps $(TESTOBJSEXTRA) $(LIBDIR)/timestamp ) -$(TESTBINS): %.test$(EXTENTION) : %.test.o $(TESTOBJSEXTRA) $(LIBDIR)/timestamp $(TESTEXTRADEPS) | $(BUILDDIRS) - $(LD) -o $@ $(LDFLAGS) -los $< $(TESTOBJSEXTRA) $(STARTGROUP) $(LINKCORELIBS) $(ENDGROUP) $(SYSLIBRARIES) +$(TESTBINS): %.test$(EXTENTION) : %.test.o $(TESTOBJSEXTRA) $(LIBDIR)/timestamp lib/timestamp $(TESTLIBDEPS) $(TESTEXTRADEPS) | $(BUILDDIRS) + $(LD) -o $@ $(LDFLAGS) -los $< $(TESTOBJSEXTRA) $(LIBS) $(STARTGROUP) $(LINKCORELIBS) $(ENDGROUP) $(SYSLIBRARIES) -include $(TESTOBJS:.test.o=.dtest) -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) -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, diff --git a/etc/cov.mk b/etc/cov.mk index db1f5d37c..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) @@ -18,17 +18,19 @@ HOST_TARGET := 1 STARTGROUP := -Wl,--start-group ENDGROUP := -Wl,--end-group +TESTOPTIMIZATION=-O0 + ifdef SKIP_COVERAGE -ARCHOPTIMIZATION = -g -O0 +ARCHOPTIMIZATION = -g $(TESTOPTIMIZATION) else -ARCHOPTIMIZATION = -g -O0 -fprofile-arcs -ftest-coverage +ARCHOPTIMIZATION = -g $(TESTOPTIMIZATION) -fprofile-arcs -ftest-coverage endif CSHAREDFLAGS = -c -frandom-seed=$(shell echo $(abspath $<) | md5sum | sed 's/\(.*\) .*/\1/') $(ARCHOPTIMIZATION) $(INCLUDES) -Wall -Werror -Wno-unknown-pragmas -MD -MP -fno-stack-protector -D_GNU_SOURCE -DGTEST CFLAGS = $(CSHAREDFLAGS) -std=gnu99 $(CFLAGSEXTRA) -CXXFLAGS = $(CSHAREDFLAGS) -std=c++1y -D__STDC_FORMAT_MACROS \ +CXXFLAGS = $(CSHAREDFLAGS) -std=c++14 -D__STDC_FORMAT_MACROS \ -D__STDC_LIMIT_MACROS $(CXXFLAGSEXTRA) #-D__LINEAR_MAP__ @@ -36,6 +38,15 @@ LDFLAGS = $(ARCHOPTIMIZATION) -Wl,-Map="$(@:%=%.map)" SYSLIB_SUBDIRS += SYSLIBRARIES = -lrt -lpthread -lavahi-client -lavahi-common $(SYSLIBRARIESEXTRA) +ifdef RUN_GPERF +CXXFLAGS += -DWITHGPERFTOOLS +LDFLAGS += -DWITHGPERFTOOLS +SYSLIBRARIES += -lprofiler +TESTOPTIMIZATION = -O3 +SKIP_COVERAGE = 1 +endif + + ifndef SKIP_COVERAGE LDFLAGS += -pg SYSLIBRARIES += -lgcov diff --git a/etc/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 b47665d38..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 @@ -39,24 +39,30 @@ ARCHOPTIMIZATION += -Os -fno-strict-aliasing -fno-strength-reduce -fomit-frame-p ARCHFLAGS = -g -MD -MP -mcpu=cortex-m3 -mthumb -mfloat-abi=soft +ifdef DETERMINISTIC_COMPILATION +ARCHFLAGS += -frandom-seed=$(shell echo $(abspath $<) | md5sum | sed 's/\(.*\) .*/\1/') +endif + ifdef DEBUG_MEMORY_USE #warning: -funwind-tables adds 10k code size. Needed for malloc debugging. ARCHFLAGS += -funwind-tables 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/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/linux.aarch64.mk b/etc/linux.aarch64.mk new file mode 100644 index 000000000..fb497c9d4 --- /dev/null +++ b/etc/linux.aarch64.mk @@ -0,0 +1,45 @@ +# Get the toolchain paths for openmrn +include $(OPENMRNPATH)/etc/path.mk + + +ifndef TOOLPATH +#TOOLPATHCOMMAND := $(shell \ +#sh -c "which aarch64-linux-gnu-gcc" \ +#) +TOOLPATH := $(AARCH64LINUXGCCPATH) +endif + +$(info armv7alinux toolpath '$(TOOLPATH)') + +# Get the $(CFLAGSENV), $(CXXFLAGSENV), $(LDFLAGSENV) +include $(OPENMRNPATH)/etc/env.mk + +CC = $(TOOLPATH)/aarch64-linux-gnu-gcc +CXX = $(TOOLPATH)/aarch64-linux-gnu-g++ +AR = $(TOOLPATH)/aarch64-linux-gnu-ar +LD = $(TOOLPATH)/aarch64-linux-gnu-g++ +OBJDUMP = $(TOOLPATH)/aarch64-linux-gnu-objdump + +AROPTS=D + +HOST_TARGET := 1 + +STARTGROUP := -Wl,--start-group +ENDGROUP := -Wl,--end-group + +ARCHOPTIMIZATION = -g3 -O0 -march=armv8-a + +CSHAREDFLAGS = -c $(ARCHOPTIMIZATION) -Wall -Werror -Wno-unknown-pragmas \ + -MD -MP -fno-stack-protector -D_GNU_SOURCE + +CFLAGS = $(CSHAREDFLAGS) -std=gnu99 + +CXXFLAGS = $(CSHAREDFLAGS) -std=c++0x -D__STDC_FORMAT_MACROS \ + -D__STDC_LIMIT_MACROS -D__USE_LIBSTDCPP__ + +LDFLAGS = $(ARCHOPTIMIZATION) -Wl,-Map="$(@:%=%.map)" +SYSLIB_SUBDIRS += +SYSLIBRARIES = -lrt -lpthread + +EXTENTION = + diff --git a/etc/linux.llvm.mk b/etc/linux.llvm.mk index ee645ab8d..1db47a7cf 100644 --- a/etc/linux.llvm.mk +++ b/etc/linux.llvm.mk @@ -11,10 +11,10 @@ include $(OPENMRNPATH)/etc/path.mk DEPS += CLANGPPPATH -CC = clang -CXX = clang++ -AR = llvm-ar -LD = clang++ +CC = $(CLANGPPPATH)/clang +CXX = $(CLANGPPPATH)/clang++ +AR = $(CLANGPPPATH)/llvm-ar +LD = $(CLANGPPPATH)/clang++ # llvm-objdump is not 100% compatible with GCC conventions. It turns out that # newer versionf of llvm remove the -symbolize option causing errors. For diff --git a/etc/linux.x86.mk b/etc/linux.x86.mk index bd3f9f5e6..27f940908 100644 --- a/etc/linux.x86.mk +++ b/etc/linux.x86.mk @@ -35,7 +35,7 @@ CFLAGS = $(CSHAREDFLAGS) -std=gnu99 \ $(CFLAGSENV) $(CFLAGSEXTRA) \ -CXXFLAGS = $(CSHAREDFLAGS) -std=c++0x -D__STDC_FORMAT_MACROS \ +CXXFLAGS = $(CSHAREDFLAGS) -std=c++14 -D__STDC_FORMAT_MACROS \ -D__STDC_LIMIT_MACROS $(CXXFLAGSENV) \ $(CXXFLAGSENV) $(CXXFLAGSEXTRA) \ diff --git a/etc/mach.x86.mk b/etc/mach.x86.mk deleted file mode 100644 index b94225e19..000000000 --- a/etc/mach.x86.mk +++ /dev/null @@ -1,30 +0,0 @@ -ifndef TOOLPATH -TOOLPATH := $(shell \ -sh -c "if [ -d /usr/include/mach ]; then echo /usr/bin; \ - else echo; fi" \ -) -endif - -# Get the $(CFLAGSENV), $(CXXFLAGSENV), $(LDFLAGSENV) -include $(OPENMRNPATH)/etc/env.mk - -CC = gcc -CXX = g++ -AR = ar -LD = g++ - -STARTGROUP := -ENDGROUP := - -INCLUDES += -I$(OPENMRNPATH)/include/mach - -CFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=gnu99 -m32 -fno-stack-protector \ - -D_GNU_SOURCE -CXXFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c++0x -m32 -fno-stack-protector \ - -D_GNU_SOURCE -D__STDC_FORMAT_MACROS - -LDFLAGS = -g -m32 -SYSLIBRARIES = -lpthread - -EXTENTION = - diff --git a/etc/mach.x86_64.mk b/etc/mach.x86_64.mk index 5842b51db..eb889ef1f 100644 --- a/etc/mach.x86_64.mk +++ b/etc/mach.x86_64.mk @@ -1,8 +1,8 @@ -ifndef TOOLPATH -TOOLPATH := $(shell \ -sh -c "if [ -d /usr/include/mach ]; then echo /usr/bin; \ - else echo; fi" \ -) +# Get the toolchain path +include $(OPENMRNPATH)/etc/path.mk + +ifeq ($(shell uname -sm),Darwin x86_64) +TOOLPATH := $(HOSTCLANGPPPATH) endif $(info mach toolpath '$(TOOLPATH)') @@ -10,19 +10,19 @@ $(info mach toolpath '$(TOOLPATH)') # Get the $(CFLAGSENV), $(CXXFLAGSENV), $(LDFLAGSENV) include $(OPENMRNPATH)/etc/env.mk -CC = gcc -CXX = g++ +CC = clang +CXX = clang++ AR = ar -LD = g++ +LD = clang++ STARTGROUP := ENDGROUP := INCLUDES += -I$(OPENMRNPATH)/include/mach -CFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=gnu99 -fno-stack-protector \ +CFLAGS = -c -g -O0 -Wall -Werror -MP -std=c99 -fno-stack-protector \ -D_GNU_SOURCE -CXXFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c++0x -fno-stack-protector \ +CXXFLAGS = -c -g -O0 -Wall -Werror -MP -std=c++14 -fno-stack-protector \ -D_GNU_SOURCE LDFLAGS = -g diff --git a/etc/make_utils.mk b/etc/make_utils.mk index 3fbd76b56..fca8fbd01 100644 --- a/etc/make_utils.mk +++ b/etc/make_utils.mk @@ -27,10 +27,14 @@ endef ### Helper template to declare a dependency. ### Arguments: target_file dependency_file ### Example on how to call: Put the following on a standalone line in the Makefile -### $(foreach lib,$(LIBDIRS),$(eval $(call DEP_helper_template,lib/lib$(lib).a,build-$(lib)))) -define DEP_helper_template +### $(foreach lib,$(LIBDIRS),$(eval $(call SUBDIR_helper_template,lib/lib$(lib).a,build-$(lib)))) +define SUBDIR_helper_template -$(1): $(2) +$(1)/lib$(1).a: | build-$(1) + +lib/lib$(1).a: $(1)/lib$(1).a + +lib/timestamp: lib/lib$(1).a endef diff --git a/etc/path.mk b/etc/path.mk index 506b724b5..a8462bd38 100644 --- a/etc/path.mk +++ b/etc/path.mk @@ -103,6 +103,28 @@ STM32CUBEF3PATH:=$(TRYPATH) endif endif #STM32CUBEF3PATH +################ STM32Cube_F4 ################## +ifndef STM32CUBEF4PATH +SEARCHPATH := \ + /opt/st/STM32Cube_FW_F4/default + +TRYPATH:=$(call findfirst,Drivers,$(SEARCHPATH)) +ifneq ($(TRYPATH),) +STM32CUBEF4PATH:=$(TRYPATH) +endif +endif #STM32CUBEF4PATH + +################ STM32Cube_L4 ################## +ifndef STM32CUBEL4PATH +SEARCHPATH := \ + /opt/st/STM32Cube_FW_L4/default + +TRYPATH:=$(call findfirst,Drivers,$(SEARCHPATH)) +ifneq ($(TRYPATH),) +STM32CUBEL4PATH:=$(TRYPATH) +endif +endif #STM32CUBEL4PATH + ################ STM32Cube_F7 ################## ifndef STM32CUBEF7PATH SEARCHPATH := \ @@ -301,6 +323,17 @@ ARMLINUXGCCPATH:=$(TRYPATH) endif endif #ARMLINUXGCCPATH +################### AARCH64-LINUX GCC PATH ##################### +ifndef AARCH64LINUXGCCPATH +SEARCHPATH := \ + /usr/bin \ + +TRYPATH:=$(call findfirst,aarch64-linux-gnu-gcc,$(SEARCHPATH)) +ifneq ($(TRYPATH),) +AARCH64LINUXGCCPATH:=$(TRYPATH) +endif +endif #AARCH64LINUXGCCPATH + ################### TI-CC3200-SDK ##################### ifndef TICC3200SDKPATH SEARCHPATH := \ @@ -520,7 +553,7 @@ SEARCHPATH := \ /usr/share/openocd/scripts \ -TRYPATH:=$(call findfirst,target/stellaris_icdi.cfg,$(SEARCHPATH)) +TRYPATH:=$(call findfirst,target/stm32f0x.cfg,$(SEARCHPATH)) ifneq ($(TRYPATH),) OPENOCDSCRIPTSPATH:=$(TRYPATH) endif @@ -559,7 +592,10 @@ endif #EMLLVMPATH ##################### CLANGPPP ###################### ifndef CLANGPPPATH SEARCHPATH := \ - /usr/bin + /usr/bin \ + /usr/lib/llvm-10/bin \ + /usr/lib/llvm-9/bin \ + /usr/lib/llvm-8/bin \ TRYPATH:=$(call findfirst,clang++,$(SEARCHPATH)) @@ -568,6 +604,18 @@ CLANGPPPATH:=$(TRYPATH) endif endif #CLANGPPPATH +##################### HOSTCLANGPP ###################### +ifndef HOSTCLANGPPPATH +SEARCHPATH := \ + /usr/bin \ + + +TRYPATH:=$(call findfirst,clang++,$(SEARCHPATH)) +ifneq ($(TRYPATH),) +HOSTCLANGPPPATH:=$(TRYPATH) +endif +endif #HOSTCLANGPPPATH + ##################### NODEJS ###################### ifndef NODEJSPATH SEARCHPATH := \ diff --git a/etc/prog.mk b/etc/prog.mk index 70bc0e46b..4019b914b 100644 --- a/etc/prog.mk +++ b/etc/prog.mk @@ -38,8 +38,8 @@ OBJS = $(CXXSRCS:.cxx=.o) $(CPPSRCS:.cpp=.o) $(CSRCS:.c=.o) $(ASMSRCS:.S=.o) \ $(XMLSRCS:.xml=.o) LIBDIR = $(OPENMRNPATH)/targets/$(TARGET)/lib -FULLPATHLIBS = $(wildcard $(LIBDIR)/*.a) $(wildcard lib/*.a) LIBDIRS := $(SUBDIRS) +FULLPATHLIBS = $(wildcard $(LIBDIR)/*.a) $(wildcard lib/*.a) $(foreach lib,$(LIBDIRS),lib/lib$(lib).a) LIBS = $(STARTGROUP) \ $(foreach lib,$(LIBDIRS),-l$(lib)) \ $(LINKCORELIBS) \ @@ -53,7 +53,7 @@ all: # the directory foo and rebuild stuff that's there. However, the dependency is # phrased in a way that if recursing does not change the library (when it's # up-to-date) then the .elf linking is not re-done. -$(foreach lib,$(LIBDIRS),$(eval $(call DEP_helper_template,lib/lib$(lib).a,build-$(lib)))) +$(foreach lib,$(LIBDIRS),$(eval $(call SUBDIR_helper_template,$(lib)))) CDIEXTRA := -I. INCLUDES += -I. @@ -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 @@ -151,7 +151,7 @@ clean: clean_cdi .PHONY: clean_cdi clean_cdi: rm -f cdi.xmlout cdi.nxml cdi.cxxout compile_cdi -endif +endif # have_config_cdi # Makes sure the subdirectory builds are done before linking the binary. # The targets and variable BUILDDIRS are defined in recurse.mk. @@ -159,7 +159,7 @@ endif # This file acts as a guard describing when the last libsomething.a was remade # in the application libraries. -lib/timestamp : FORCE $(BUILDDIRS) +lib/timestamp : FORCE # creates the lib directory @[ -d lib ] || mkdir lib # in case there are not applibs. @@ -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 @@ -238,16 +238,26 @@ 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) .SUFFIXES: .SUFFIXES: .o .c .cxx .cpp .S .xml .cout .cxxout -.xml.o: $(OPENMRNPATH)/bin/build_cdi.py +%.xml: %.o $(OPENMRNPATH)/bin/build_cdi.py $(OPENMRNPATH)/bin/build_cdi.py -i $< -o $*.cxxout - $(CXX) $(CXXFLAGS) -x c++ $*.cxxout -o $@ - $(CXX) -MM $(CXXFLAGS) -x c++ $*.cxxout > $*.d + $(CXX) -MMD -MF $*.d $(CXXFLAGS) -x c++ $*.cxxout -o $@ ifeq ($(TARGET),bare.pruv3) .cpp.o: @@ -264,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 @@ -291,67 +301,16 @@ tests: @echo "***Not building tests at target $(TARGET), because missing: $(TEST_MISSING_DEPS) ***" else -ifeq (1,1) 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 -else -FULLPATHTESTSRCS ?= $(wildcard $(VPATH)/tests/*_test.cc) -TESTSRCS = $(notdir $(FULLPATHTESTSRCS)) $(wildcard *_test.cc) -TESTOBJS := $(TESTSRCS:.cc=.o) - -VPATH:=$(VPATH):$(GTESTPATH)/src:$(GTESTSRCPATH):$(GMOCKPATH)/src:$(GMOCKSRCPATH):$(abspath ../../tests) -INCLUDES += -I$(GTESTPATH)/include -I$(GTESTPATH) -I$(GMOCKPATH)/include -I$(GMOCKPATH) - -TEST_OUTPUTS=$(TESTOBJS:.o=.output) - -TEST_EXTRA_OBJS += gtest-all.o gmock-all.o - -.cc.o: - $(CXX) $(CXXFLAGS) $< -o $@ - $(CXX) -MM $(CXXFLAGS) $< > $*.d - -gtest-all.o : %.o : $(GTESTSRCPATH)/src/%.cc - $(CXX) $(CXXFLAGS) -I$(GTESTPATH) -I$(GTESTSRCPATH) $< -o $@ - $(CXX) -MM $(CXXFLAGS) -I$(GTESTPATH) -I$(GTESTSRCPATH) $< > $*.d - -gmock-all.o : %.o : $(GMOCKSRCPATH)/src/%.cc - $(CXX) $(CXXFLAGS) -I$(GMOCKPATH) -I$(GMOCKSRCPATH) $< -o $@ - $(CXX) -MM $(CXXFLAGS) -I$(GMOCKPATH) -I$(GMOCKSRCPATH) $< > $*.d - -#.PHONY: $(TEST_OUTPUTS) - -$(TEST_OUTPUTS) : %_test.output : %_test - ./$*_test --gtest_death_test_style=threadsafe - touch $@ - -$(TESTOBJS:.o=) : %_test : %_test.o $(TEST_EXTRA_OBJS) $(FULLPATHLIBS) $(LIBDIR)/timestamp lib/timestamp - $(LD) -o $*_test$(EXTENTION) $*_test.o $(TEST_EXTRA_OBJS) $(OBJEXTRA) $(LDFLAGS) $(LIBS) $(SYSLIBRARIES) -lstdc++ - -%_test.o : %_test.cc - $(CXX) $(CXXFLAGS:-Werror=) -DTESTING -fpermissive $< -o $*_test.o - $(CXX) -MM $(CXXFLAGS) $< > $*_test.d - -#$(TEST_OUTPUTS) : %_test.output : %_test.cc gtest-all.o gtest_main.o -# $(CXX) $(CXXFLAGS) $< -o $*_test.o -# $(CXX) -MM $(CXXFLAGS) $< > $*_test.d -# $(LD) -o $*_test$(EXTENTION) $+ $(OBJEXTRA) $(LDFLAGS) $(LIBS) $(SYSLIBRARIES) -lstdc++ -# ./$*_test - -tests : all $(TEST_OUTPUTS) - -mksubdirs: - [ -d lib ] || mkdir lib -endif # old testrunner code - endif # if we are able to run tests -endif +endif # if we can build anything -- MISSING_DEPS is empty diff --git a/etc/release.mk b/etc/release.mk new file mode 100644 index 000000000..82b4ce1fc --- /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 + + diff --git a/etc/stm32cubef4.mk b/etc/stm32cubef4.mk new file mode 100644 index 000000000..d8322912d --- /dev/null +++ b/etc/stm32cubef4.mk @@ -0,0 +1,13 @@ +include $(OPENMRNPATH)/etc/path.mk + +ifdef STM32CUBEF4PATH +INCLUDES += -I$(OPENMRNPATH)/src/freertos_drivers/st \ + -I$(STM32CUBEF4PATH)/Drivers/STM32F4xx_HAL_Driver/Inc \ + -I$(STM32CUBEF4PATH)/Drivers/CMSIS/Device/ST/STM32F4xx/Include \ + -I$(STM32CUBEF4PATH)/Drivers/CMSIS/Include +endif + +CFLAGS += +CXXFLAGS += + +DEPS += STM32CUBEF4PATH diff --git a/etc/stm32cubel4.mk b/etc/stm32cubel4.mk new file mode 100644 index 000000000..bfb699b1b --- /dev/null +++ b/etc/stm32cubel4.mk @@ -0,0 +1,13 @@ +include $(OPENMRNPATH)/etc/path.mk + +ifdef STM32CUBEL4PATH +INCLUDES += -I$(OPENMRNPATH)/src/freertos_drivers/st \ + -I$(STM32CUBEL4PATH)/Drivers/STM32L4xx_HAL_Driver/Inc \ + -I$(STM32CUBEL4PATH)/Drivers/CMSIS/Device/ST/STM32L4xx/Include \ + -I$(STM32CUBEL4PATH)/Drivers/CMSIS/Include +endif + +CFLAGS += +CXXFLAGS += + +DEPS += STM32CUBEL4PATH diff --git a/etc/test.mk b/etc/test.mk index 7ca2fe721..e60514e6c 100644 --- a/etc/test.mk +++ b/etc/test.mk @@ -45,11 +45,13 @@ LIBS = $(STARTGROUP) \ $(ENDGROUP) \ $(LINKCORELIBS) +TESTOPTIMIZATION=-O0 + INCLUDES += -I$(GTESTPATH)/include -I$(GMOCKPATH)/include -I$(GMOCKPATH) \ -I$(OPENMRNPATH)/src -I$(OPENMRNPATH)/include -CFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage -O0 -CXXFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage -O0 -SYSLIBRARIES += -lgcov -fprofile-arcs -ftest-coverage -O0 +CFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage $(TESTOPTIMIZATION) +CXXFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage $(TESTOPTIMIZATION) +SYSLIBRARIES += -lgcov -fprofile-arcs -ftest-coverage $(TESTOPTIMIZATION) LDFLAGS += -L$(LIBDIR) .SUFFIXES: diff --git a/include/freertos/tc_ioctl.h b/include/freertos/tc_ioctl.h index 54179e66f..b7ab168ef 100644 --- a/include/freertos/tc_ioctl.h +++ b/include/freertos/tc_ioctl.h @@ -39,7 +39,24 @@ /** Magic number for this driver's ioctl calls */ #define TERMIOS_IOC_MAGIC ('T') -#define TCSBRK IO(TERMIOS_IOC_MAGIC, 9) +#define TCSBRK IO(TERMIOS_IOC_MAGIC, 9) + +#define TCPARNONE IO(TERMIOS_IOC_MAGIC, 0xF0) +#define TCPARODD IO(TERMIOS_IOC_MAGIC, 0xF1) +#define TCPAREVEN IO(TERMIOS_IOC_MAGIC, 0xF2) +#define TCPARONE IO(TERMIOS_IOC_MAGIC, 0xF3) +#define TCPARZERO IO(TERMIOS_IOC_MAGIC, 0xF4) +/// One stop bit +#define TCSTOPONE IO(TERMIOS_IOC_MAGIC, 0xF8) +/// Two stop bits +#define TCSTOPTWO IO(TERMIOS_IOC_MAGIC, 0xF9) + +/// Argument is a Notifiable* pointer. This notifiable will be invoked when all +/// bytes have completed transferring and the transmit engine is idle. +#define TCDRAINNOTIFY IOW(TERMIOS_IOC_MAGIC, 0xE0, 4) + +/// Argument is the desired baud rate for the port. +#define TCBAUDRATE IOW(TERMIOS_IOC_MAGIC, 0xE1, 4) #endif // _FREERTOS_TC_IOCTL_H_ diff --git a/include/nmranet_config.h b/include/nmranet_config.h index e23a20adb..1e44f39e3 100644 --- a/include/nmranet_config.h +++ b/include/nmranet_config.h @@ -122,6 +122,10 @@ DECLARE_CONST(remote_alias_cache_size); /** Number of entries in the local alias cache */ DECLARE_CONST(local_alias_cache_size); +/** Keep this many allocated but unused aliases around. (Currently supported + * values are 0 or 1.) */ +DECLARE_CONST(reserve_unused_alias_count); + /** Maximum number of local nodes */ DECLARE_CONST(local_nodes_count); @@ -146,5 +150,14 @@ DECLARE_CONST(enable_all_memory_space); * standard. */ DECLARE_CONST(node_init_identify); +/** How many CAN frames should the bulk alias allocator be sending at the same + * time. */ +DECLARE_CONST(bulk_alias_num_can_frames); + +/** Stack size for @ref SocketListener threads. */ +DECLARE_CONST(socket_listener_stack_size); + +/** Number of sockets to allow for @ref SocketListener backlog. */ +DECLARE_CONST(socket_listener_backlog); #endif /* _nmranet_config_h_ */ diff --git a/include/openmrn_features.h b/include/openmrn_features.h index 870b97a40..8a619a0f6 100644 --- a/include/openmrn_features.h +++ b/include/openmrn_features.h @@ -48,6 +48,13 @@ #define OPENMRN_FEATURE_REENT 1 #endif +#if defined(__linux__) || defined(__MACH__) || defined(__WINNT__) || defined(ESP32) || defined(OPENMRN_FEATURE_DEVTAB) +/// Enables the code using ::open ::close ::read ::write for non-volatile +/// storage, FileMemorySpace for the configuration space, and +/// SNIP_DYNAMIC_FILE_NAME for node names. +#define OPENMRN_HAVE_POSIX_FD 1 +#endif + /// @todo this should probably be a whitelist: __linux__ || __MACH__. #if !defined(__FreeRTOS__) && !defined(__WINNT__) && !defined(ESP32) && \ !defined(ARDUINO) && !defined(ESP_NONOS) diff --git a/src/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" diff --git a/src/console/Console.hxx b/src/console/Console.hxx index 4444e5c40..41d8cf0b7 100644 --- a/src/console/Console.hxx +++ b/src/console/Console.hxx @@ -369,7 +369,9 @@ private: */ HASSERT(fdIn == fdOut); fclose(fp); - close(fdIn); + /* There is no need for a "close(fdIn)" because the "fclose(fp)" + * will already have completed that operation. + */ free(line); } diff --git a/src/dcc/DccDebug.cxx b/src/dcc/DccDebug.cxx index 7564b4372..ceb55acbd 100644 --- a/src/dcc/DccDebug.cxx +++ b/src/dcc/DccDebug.cxx @@ -33,7 +33,9 @@ */ #include "dcc/DccDebug.hxx" +#include "dcc/Defs.hxx" #include "utils/StringPrintf.hxx" +#include "utils/logging.h" namespace dcc { @@ -81,14 +83,94 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) options.pop_back(); options += "]"; } - if (pkt.packet_header.is_marklin) { + if (pkt.packet_header.is_marklin) + { return options; } unsigned ofs = 0; bool is_idle_packet = false; bool is_basic_accy_packet = false; + bool is_unknown_packet = false; + bool is_svc_packet = false; unsigned accy_address = 0; - if (pkt.payload[ofs] == 0xff) + if (pkt.packet_header.send_long_preamble) + { + // Checks for service mode packets. + + // WARNING: This code is only guaranteed to correctly decide between + // service mode packets and basic decoder packets if the packets were + // generated by OpenMRN. Otherwise it is not guaranteed that + // long_preamble is equivalent to service mode. + using namespace dcc::Defs; + is_svc_packet = true; + uint8_t cmd = pkt.payload[ofs]; + int cv = (((pkt.payload[ofs] & 0x3) << 8) | pkt.payload[ofs + 1]) + 1; + int reg = (pkt.payload[ofs] & 0x7) + 1; + // How many bytes does the command have (cmd and arguments), usually 2 + // for paged mode and 3 for direct mode commands. + int num_bytes = + pkt.packet_header.skip_ec ? pkt.dlc - ofs - 1 : pkt.dlc - ofs; + if (num_bytes == 3 && (cmd & DCC_SVC_MASK) == DCC_SVC_WRITE) + { + options += StringPrintf( + "[svc] Direct Write Byte CV %d = %d", cv, pkt.payload[ofs + 2]); + ofs += 2; + } + else if (num_bytes == 3 && (cmd & DCC_SVC_MASK) == DCC_SVC_VERIFY) + { + options += StringPrintf("[svc] Direct Verify Byte CV %d =?= %d", cv, + pkt.payload[ofs + 2]); + ofs += 2; + } + else if (num_bytes == 3 && + ((cmd & DCC_SVC_MASK) == DCC_SVC_BIT_MANIPULATE) && + ((pkt.payload[ofs + 2] & DCC_SVC_BITVAL_MASK) == + DCC_SVC_BITVAL_WRITE)) + { + int bitnum = pkt.payload[ofs + 2] & 7; + int bitval = pkt.payload[ofs + 2] & DCC_SVC_BITVAL_VALUE ? 1 : 0; + options += StringPrintf( + "[svc] Direct Write Bit CV %d bit %d = %d", cv, bitnum, bitval); + ofs += 2; + } + else if (num_bytes == 3 && + ((cmd & DCC_SVC_MASK) == DCC_SVC_BIT_MANIPULATE) && + ((pkt.payload[ofs + 2] & DCC_SVC_BITVAL_MASK) == + DCC_SVC_BITVAL_VERIFY)) + { + int bitnum = pkt.payload[ofs + 2] & 7; + int bitval = pkt.payload[ofs + 2] & DCC_SVC_BITVAL_VALUE ? 1 : 0; + options += + StringPrintf("[svc] Direct Verify Bit CV %d bit %d =?= %d", cv, + bitnum, bitval); + ofs += 2; + } + else if (num_bytes == 2 && + (cmd & DCC_SVC_PAGED_MASK) == DCC_SVC_PAGED_WRITE) + { + options += StringPrintf("[svc] Paged Write Byte Reg %d = %d", reg, + pkt.payload[ofs + 1]); + ofs++; + } + else if (num_bytes == 2 && + (cmd & DCC_SVC_PAGED_MASK) == DCC_SVC_PAGED_VERIFY) + { + options += StringPrintf("[svc] Paged Verify Byte Reg %d =?= %d", + reg, pkt.payload[ofs + 1]); + ofs++; + } + else + { + // Not recognized. + is_svc_packet = false; + } + } + + if (is_svc_packet) + { + // Do not use the regular address partition logic here. + } + else if (pkt.payload[ofs] == 0xff) { options += " Idle packet"; ofs++; @@ -102,6 +184,10 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) { options += " Broadcast"; ofs++; + if (pkt.payload[ofs] == 0) + { + options += " reset"; + } } else if ((pkt.payload[ofs] & 0x80) == 0) { @@ -124,9 +210,26 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) addr |= pkt.payload[ofs]; ofs++; options += StringPrintf(" Long Address %u", addr); + } else if (pkt.payload[ofs] == 254) { + options += " Logon packet"; + is_unknown_packet = true; + while (ofs < pkt.dlc) + { + options += StringPrintf(" 0x%02x", pkt.payload[ofs++]); + } + } else if (pkt.payload[ofs] == 253) { + options += " Advanced extended packet"; + is_unknown_packet = true; + while (ofs < pkt.dlc) + { + options += StringPrintf(" 0x%02x", pkt.payload[ofs++]); + } } uint8_t cmd = pkt.payload[ofs]; - ofs++; + if (!is_unknown_packet) + { + ofs++; + } if (is_basic_accy_packet && ((cmd & 0x80) == 0x80)) { accy_address |= cmd & 0b111; @@ -137,6 +240,9 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) options += StringPrintf(" Accy %u %s", accy_address, is_activate ? "activate" : "deactivate"); } + else if (is_unknown_packet || is_svc_packet) + { + } else if ((cmd & 0xC0) == 0x40) { // Speed and direction @@ -229,9 +335,58 @@ 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) + if (ofs == pkt.dlc && (pkt.packet_header.skip_ec == 0 || is_unknown_packet)) { // EC skipped. } @@ -250,12 +405,16 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) } else { - options += StringPrintf(" [bad dlc, exp %u, actual %u]", ofs, pkt.dlc); + options += StringPrintf(" [bad dlc, exp %u, actual %u]", ofs+1, pkt.dlc); while (ofs < pkt.dlc) { options += StringPrintf(" 0x%02x", pkt.payload[ofs++]); } } + if (pkt.packet_header.csum_error) + { + options += " [csum err]"; + } return options; } diff --git a/src/dcc/DccDebug.cxxtest b/src/dcc/DccDebug.cxxtest index a1e09ac9d..146b268df 100644 --- a/src/dcc/DccDebug.cxxtest +++ b/src/dcc/DccDebug.cxxtest @@ -125,6 +125,78 @@ 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, BroadcastReset) +{ + Packet pkt; + pkt.set_dcc_reset_all_decoders(); + EXPECT_EQ("[dcc] Broadcast reset", packet_to_string(pkt)); +} + +TEST(DccDebug, SvcMode) +{ + Packet pkt; + pkt.set_dcc_svc_verify_byte(567, 133); + EXPECT_EQ("[dcc][long_preamble][svc] Direct Verify Byte CV 568 =?= 133", + packet_to_string(pkt)); + pkt.set_dcc_svc_write_byte(567, 133); + EXPECT_EQ("[dcc][long_preamble][svc] Direct Write Byte CV 568 = 133", + packet_to_string(pkt)); + pkt.set_dcc_svc_verify_bit(567, 3, true); + EXPECT_EQ("[dcc][long_preamble][svc] Direct Verify Bit CV 568 bit 3 =?= 1", + packet_to_string(pkt)); + pkt.set_dcc_svc_verify_bit(567, 7, false); + EXPECT_EQ("[dcc][long_preamble][svc] Direct Verify Bit CV 568 bit 7 =?= 0", + packet_to_string(pkt)); + pkt.set_dcc_svc_write_bit(567, 3, true); + EXPECT_EQ("[dcc][long_preamble][svc] Direct Write Bit CV 568 bit 3 = 1", + packet_to_string(pkt)); + pkt.set_dcc_svc_write_bit(567, 7, false); + EXPECT_EQ("[dcc][long_preamble][svc] Direct Write Bit CV 568 bit 7 = 0", + packet_to_string(pkt)); + + pkt.set_dcc_svc_paged_write_reg(3, 133); + EXPECT_EQ("[dcc][long_preamble][svc] Paged Write Byte Reg 4 = 133", + packet_to_string(pkt)); + pkt.set_dcc_svc_paged_verify_reg(4, 133); + EXPECT_EQ("[dcc][long_preamble][svc] Paged Verify Byte Reg 5 =?= 133", + packet_to_string(pkt)); + pkt.set_dcc_svc_paged_set_page(27); + EXPECT_EQ("[dcc][long_preamble][svc] Paged Write Byte Reg 6 = 27", + packet_to_string(pkt)); +} + TEST(DccDebug, Accy) { Packet pkt; diff --git a/src/dcc/DccOutput.hxx b/src/dcc/DccOutput.hxx index 8ec19d063..6548b89a4 100644 --- a/src/dcc/DccOutput.hxx +++ b/src/dcc/DccOutput.hxx @@ -186,11 +186,11 @@ public: /// \link DccOutput::DisableReason } These bits are all set by the /// application. The DCC Driver will only read this variable, and enable /// the output if all bits are zero. - static std::atomic_uint8_t outputDisableReasons_; + static std::atomic_uint_least8_t outputDisableReasons_; /// 0 if we should not produce a railcom cutout; 1 for short cutout; 2 for /// regular cutout. Set by the application and read by the DCC driver. - static std::atomic_uint8_t isRailcomCutoutEnabled_; + static std::atomic_uint_least8_t isRailcomCutoutEnabled_; /// 1 if we are in a railcom cutout currently. Set and cleared by the /// driver before calling the start/stop railcom cutout functions. @@ -233,10 +233,10 @@ private: }; template -std::atomic_uint8_t DccOutputHw::outputDisableReasons_ { +std::atomic_uint_least8_t DccOutputHw::outputDisableReasons_ { (uint8_t)DccOutput::DisableReason::INITIALIZATION_PENDING}; template -std::atomic_uint8_t DccOutputHw::isRailcomCutoutEnabled_ { +std::atomic_uint_least8_t DccOutputHw::isRailcomCutoutEnabled_ { (uint8_t)DccOutput::RailcomCutout::LONG_CUTOUT}; template uint8_t DccOutputHw::isRailcomCutoutActive_ {0}; diff --git a/src/dcc/Defs.hxx b/src/dcc/Defs.hxx index 375c1b70b..183989185 100644 --- a/src/dcc/Defs.hxx +++ b/src/dcc/Defs.hxx @@ -35,7 +35,10 @@ #ifndef _DCC_DEFS_HXX_ #define _DCC_DEFS_HXX_ -namespace dcc { +#include + +namespace dcc +{ /// Which address type this legacy train node uses. These address types /// translate to mutually independent packets on the track. @@ -54,6 +57,142 @@ enum class TrainAddressType : uint8_t UNSPECIFIED = 254, }; -} // namespace dcc +namespace Defs +{ + +enum +{ + MARKLIN_DEFAULT_CMD = 0b00100110, + // Direction change bits for marklin-old format. + MARKLIN_CHANGE_DIR_B2 = 0b11000000, + + DCC_DEFAULT_CMD = 0, + DCC_LONG_PREAMBLE_CMD = 0b00001100, + DCC_SERVICE_MODE_5X_WITH_ACK_CMD = 0b00111000, + // standard dcc packet with 5x repeat. + DCC_SERVICE_MODE_5X_CMD = 0b00101000, + DCC_SERVICE_MODE_1X_CMD = 0b00001000, + /// Prefix for DCC long addresses (first byte). + DCC_LONG_ADDRESS_FIRST = 0b11000000, + + // Baseline packet: speed and direction. + DCC_BASELINE_SPEED = 0b01000000, + DCC_BASELINE_SPEED_FORWARD = 0b00100000, + DCC_BASELINE_SPEED_LIGHT = 0b00010000, + DCC_FUNCTION1 = 0b10000000, + DCC_FUNCTION1_F0 = 0b00010000, + DCC_FUNCTION2_F5 = 0b10110000, + DCC_FUNCTION2_F9 = 0b10100000, + DCC_FEATURE_EXP_F13 = 0b11011110, + DCC_FEATURE_EXP_F21 = 0b11011111, + DCC_FEATURE_EXP_FNHI = 0b11011000, + DCC_BINARY_SHORT = 0b11011101, + DCC_BINARY_LONG = 0b11000000, + DCC_ANALOG_FN = 0b00111101, + + DCC_PROG_READ1 = 0b11100100, + DCC_PROG_WRITE1 = 0b11101100, + DCC_PROG_READ4 = 0b11100000, + + DCC_SVC_BIT_MANIPULATE = 0b01111000, + DCC_SVC_WRITE = 0b01111100, + DCC_SVC_VERIFY = 0b01110100, + DCC_SVC_MASK = 0b11111100, + + DCC_SVC_BITVAL_WRITE = 0b11110000, + DCC_SVC_BITVAL_VERIFY = 0b11100000, + DCC_SVC_BITVAL_VALUE = 0b00001000, + DCC_SVC_BITVAL_MASK = 0b11110000, + + DCC_SVC_PAGED_WRITE = 0b01111000, + DCC_SVC_PAGED_VERIFY = 0b01110000, + DCC_SVC_PAGED_MASK = 0b11111000, + + DCC_BASIC_ACCESSORY_B1 = 0b10000000, + DCC_BASIC_ACCESSORY_B2 = 0b10000000, + + // Extended packet: 128-step speed. + DCC_EXT_SPEED = 0b00111111, + DCC_EXT_SPEED_FORWARD = 0x80, + + /// Address partition used for logon features per S-9.2.1.1 and RCN-218 + ADDRESS_LOGON = 254, + /// Address partition used for advanced extended packets per S-9.2.1.1 + ADDRESS_EXT = 253, + + // Logon commands + /// Logon enable in the 254 address partition + DCC_LOGON_ENABLE = 0b11111100, + DCC_LOGON_ENABLE_MASK = 0b11111100, + /// Select command in the 254 address partition + DCC_SELECT = 0b11010000, + DCC_SELECT_MASK = 0b11110000, + /// Get Data Start command in the 254 address partition + DCC_GET_DATA_START = 0, + /// Get Data Continue command in the 254 address partition + DCC_GET_DATA_CONT = 1, + /// Logon Assign command the 254 address partition + DCC_LOGON_ASSIGN = 0b11100000, + DCC_LOGON_ASSIGN_MASK = 0b11110000, + + /// Minimum value of second byte for DID assigned packets. + DCC_DID_MIN = DCC_SELECT, + /// Maximum value of second byte for DID assigned packets. + DCC_DID_MAX = DCC_LOGON_ASSIGN + 0xF, + /// Mask used for the DCC_DID packets primary command byte. + DCC_DID_MASK = 0xF0, + + // Commands in 254 Select and 253 addressed. + CMD_READ_SHORT_INFO = 0b11111111, + CMD_READ_BLOCK = 0b11111110, + CMD_READ_BACKGROUND = 0b11111101, + CMD_WRITE_BLOCK = 0b11111100, + + // Address partitions as defined by S-9.2.1.1. These are 6-bit values for + // the first byte of the reported and assigned address. + + /// Mask for the address partition bits. + ADR_MASK = 0b111111, + + /// 7-bit mobile decoders + ADR_MOBILE_SHORT = 0b00111000, + /// Mask for 7-bit mobile decoders + ADR_MOBILE_SHORT_MASK = 0xFF, + /// 14-bit mobile decoders + ADR_MOBILE_LONG = 0, + /// Maximum value of the first byte for a 14-bit mobile decoder. + MAX_MOBILE_LONG = 0b00100111, + /// 11-bit extended accessory decoder + ADR_ACC_EXT = 0b00101000, + /// Mask for 11-bit extended accessory decoder + MASK_ACC_EXT = 0b00111000, + /// 9-bit basic accessory decoder + ADR_ACC_BASIC = 0b00110000, + /// Mask for 9-bit basic accessory decoder + MASK_ACC_BASIC = 0b00111000, + + /// This value, when given to a decoder, represents an invalid + /// (unassignable) address. This is a 2-byte value that can go to the wire + /// -- above we only have the constants for address partitions, which is + /// the first byte. + ADR_INVALID = (ADR_MOBILE_SHORT << 8), +}; + +/// Parameters for the Logon Enable command. +enum class LogonEnableParam +{ + /// All decoders respond and ignore backoff. + NOW = 0b11, + /// Locomotive decoders only. + LOCO = 0b01, + /// Accessory decoders only. + ACC = 0b10, + /// All decoders respond. + ALL = 0b00, +}; + +} // namespace dcc::Defs + +} // namespace dcc #endif diff --git a/src/dcc/Loco.cxx b/src/dcc/Loco.cxx index cad1533b7..50f34279a 100644 --- a/src/dcc/Loco.cxx +++ b/src/dcc/Loco.cxx @@ -55,7 +55,7 @@ template <> DccTrain::~DccTrain() packet_processor_remove_refresh_source(this); } -unsigned Dcc28Payload::get_fn_update_code(unsigned address) +unsigned DccPayloadBase::get_fn_update_code(unsigned address) { if (address < 5) { @@ -69,13 +69,9 @@ unsigned Dcc28Payload::get_fn_update_code(unsigned address) { return FUNCTION9; } - else if (address < 21) + else if (address <= 68) { - return FUNCTION13; - } - else if (address <= 28) - { - return FUNCTION21; + return FUNCTION13 + (address - 13) / 8; } return SPEED; } @@ -110,7 +106,8 @@ void DccTrain::get_next_packet(unsigned code, Packet *packet) { case FUNCTION0: { - packet->add_dcc_function0_4(this->p.fn_ & 0x1F); + packet->add_dcc_function0_4( + (this->p.fn_ & 0x1E) | this->get_effective_f0()); return; } case FUNCTION5: @@ -133,6 +130,16 @@ void DccTrain::get_next_packet(unsigned code, Packet *packet) packet->add_dcc_function21_28(this->p.fn_ >> 21); return; } + case FUNCTION29: + case FUNCTION37: + case FUNCTION45: + case FUNCTION53: + case FUNCTION61: + { + unsigned s = code - FUNCTION29; + packet->add_dcc_function_hi(29 + s * 8, this->p.fhi_[s]); + return; + } case ESTOP: { this->p.add_dcc_estop_to_packet(packet); @@ -171,7 +178,7 @@ MMOldTrain::~MMOldTrain() void MMOldTrain::get_next_packet(unsigned code, Packet *packet) { packet->start_mm_packet(); - packet->add_mm_address(MMAddress(p.address_), p.fn_ & 1); + packet->add_mm_address(MMAddress(p.address_), get_effective_f0()); if (code == ESTOP) { @@ -210,7 +217,7 @@ MMNewTrain::~MMNewTrain() void MMNewTrain::get_next_packet(unsigned code, Packet *packet) { packet->start_mm_packet(); - packet->add_mm_address(MMAddress(p.address_), p.fn_ & 1); + packet->add_mm_address(MMAddress(p.address_), get_effective_f0()); if (code == REFRESH) { diff --git a/src/dcc/Loco.hxx b/src/dcc/Loco.hxx index 425f13329..df0f3ab0a 100644 --- a/src/dcc/Loco.hxx +++ b/src/dcc/Loco.hxx @@ -35,12 +35,18 @@ #ifndef _DCC_LOCO_HXX_ #define _DCC_LOCO_HXX_ +#include "dcc/Defs.hxx" #include "dcc/Packet.hxx" #include "dcc/PacketSource.hxx" #include "dcc/UpdateLoop.hxx" -#include "dcc/Defs.hxx" +#include "utils/constants.hxx" #include "utils/logging.h" +/// At this function number there will be three virtual functions available on +/// the OpenLCB TrainImpl, controlling advanced functions related to the light +/// (f0) function of trains. +DECLARE_CONST(dcc_virtual_f0_offset); + namespace dcc { @@ -64,6 +70,11 @@ enum DccTrainUpdateCode FUNCTION9 = 4, FUNCTION13 = 5, FUNCTION21 = 6, + FUNCTION29 = 7, + FUNCTION37 = 8, + FUNCTION45 = 9, + FUNCTION53 = 10, + FUNCTION61 = 11, MM_F1 = 2, MM_F2, MM_F3, @@ -111,19 +122,27 @@ public: return; } p.lastSetSpeed_ = new_speed; + p.isEstop_ = false; + unsigned previous_light = get_effective_f0(); if (speed.direction() != p.direction_) { p.directionChanged_ = 1; p.direction_ = speed.direction(); + update_f0_direction_changed(); } float f_speed = speed.mph(); if (f_speed > 0) { f_speed *= ((p.get_speed_steps() * 1.0) / 126); - unsigned sp = f_speed; - sp++; // makes sure it is at least speed step 1. + unsigned sp = f_speed + 0.5; // round + if (sp == 0) // ceiling between speed step 0 and 1 + { + sp = 1; + } if (sp > p.get_speed_steps()) + { sp = p.get_speed_steps(); + } LOG(VERBOSE, "set speed to step %u", sp); p.speed_ = sp; } @@ -131,7 +150,18 @@ public: { p.speed_ = 0; } + unsigned light = get_effective_f0(); + if (previous_light && !light) + { + // Turns off light first then sends speed packet. + packet_processor_notify_update(this, p.get_fn_update_code(0)); + } packet_processor_notify_update(this, SPEED); + if (light && !previous_light) + { + // Turns on light after sending speed packets. + packet_processor_notify_update(this, p.get_fn_update_code(0)); + } } /// @return the last set speed. @@ -151,6 +181,7 @@ public: void set_emergencystop() OVERRIDE { p.speed_ = 0; + p.isEstop_ = true; SpeedType dir0; dir0.set_direction(p.direction_); p.lastSetSpeed_ = dir0.get_wire(); @@ -163,38 +194,96 @@ public: /// Gets the train's ESTOP state. bool get_emergencystop() OVERRIDE { - return false; + return p.isEstop_; } /// Sets a function to a given value. @param address is the function number /// (0..28), @param value is 0 for funciton OFF, 1 for function ON. void set_fn(uint32_t address, uint16_t value) OVERRIDE { - if (address > p.get_max_fn()) + const uint32_t virtf0 = config_dcc_virtual_f0_offset(); + if (address == 0 && p.f0SetDirectional_) { - // Ignore. + if (p.direction_ == 0) + { + p.f0OnForward_ = value ? 1 : 0; + } + else + { + p.f0OnReverse_ = value ? 1 : 0; + } + // continue into the handling of f0. + } + else if (address == virtf0 + VIRTF0_DIRECTIONAL_ENABLE) + { + if (value) + { + p.f0SetDirectional_ = 1; + // Populates new state of separate f0 forward and f0 + // reverse. + if (p.direction_ == 0) + { + p.f0OnForward_ = p.get_fn_store(0); + p.f0OnReverse_ = 0; + } + else + { + p.f0OnReverse_ = p.get_fn_store(0); + p.f0OnForward_ = 0; + } + } + else + { + p.f0SetDirectional_ = 0; + // whatever value we have in fn_[0] now is going to be the + // new state, so we don't change anything. + } + // This command never changes f0, so no packets need to be sent to + // the track. return; } - unsigned bit = 1 << address; - if (value) + else if (address == virtf0 + VIRTF0_BLANK_FWD) { - p.fn_ |= bit; + p.f0BlankForward_ = value ? 1 : 0; + packet_processor_notify_update(this, p.get_fn_update_code(0)); + return; } - else + else if (address == virtf0 + VIRTF0_BLANK_REV) + { + p.f0BlankReverse_ = value ? 1 : 0; + packet_processor_notify_update(this, p.get_fn_update_code(0)); + return; + } + if (address > p.get_max_fn()) { - p.fn_ &= ~bit; + // Ignore. + return; } + p.set_fn_store(address, value); packet_processor_notify_update(this, p.get_fn_update_code(address)); } /// @return the last set value of a given function, or 0 if the function is /// not known. @param address is the function address. uint16_t get_fn(uint32_t address) OVERRIDE { + const uint32_t virtf0 = config_dcc_virtual_f0_offset(); + if (address == virtf0 + VIRTF0_DIRECTIONAL_ENABLE) + { + return p.f0SetDirectional_; + } + else if (address == virtf0 + VIRTF0_BLANK_FWD) + { + return p.f0BlankForward_; + } + else if (address == virtf0 + VIRTF0_BLANK_REV) + { + return p.f0BlankReverse_; + } if (address > p.get_max_fn()) { // Unknown. return 0; } - return (p.fn_ & (1 << address)) ? 1 : 0; + return p.get_fn_store(address) ? 1 : 0; } /// @return the legacy address of this loco. uint32_t legacy_address() OVERRIDE @@ -208,49 +297,179 @@ public: } protected: + /// Function number of "enable directional F0". Offset from config option + /// dcc_virtual_f0_offset. When this function is enabled, F0 is set and + /// cleared separately for forward and reverse drive. + static constexpr unsigned VIRTF0_DIRECTIONAL_ENABLE = 0; + /// Function number of "Blank F0 Forward". Offset from config option + /// dcc_virtual_f0_offset. When this function is enabled, F0 on the track + /// packet will turn off when direction==forward, even if function 0 is + /// set. + static constexpr unsigned VIRTF0_BLANK_FWD = 1; + /// Function number of "Blank F0 Reverse". Offset from config option + /// dcc_virtual_f0_offset. When this function is enabled, F0 on the track + /// packet will turn off when direction==reverse, even if function 0 is + /// set. + static constexpr unsigned VIRTF0_BLANK_REV = 2; + + /// @return the currently applicable value of F0 to be sent out to the + /// packets (1 if on, 0 if off). + unsigned get_effective_f0() + { + unsigned is_on = p.f0SetDirectional_ == 0 ? (p.fn_ & 1) + : p.direction_ == 0 ? p.f0OnForward_ + : p.f0OnReverse_; + if (p.direction_ == 0 && p.f0BlankForward_) + { + is_on = 0; + } + if (p.direction_ == 1 && p.f0BlankReverse_) + { + is_on = 0; + } + return is_on; + } + + /// Updates the f0 states after a direction change occurred. + void update_f0_direction_changed() + { + if (p.f0SetDirectional_) + { + p.fn_ &= ~1; + if (p.direction_ == 0 && p.f0OnForward_) + { + p.fn_ |= 1; + } + if (p.direction_ == 1 && p.f0OnReverse_) + { + p.fn_ |= 1; + } + } + } + /// Payload -- actual data we know about the train. P p; }; -/// Structure defining the volatile state for a 28-speed-step DCC locomotive. -struct Dcc28Payload +/// Common storage variables for the different DCC Payload types. +struct DccPayloadBase { - Dcc28Payload() - { - memset(this, 0, sizeof(*this)); - } /// Track address. largest address allowed is 10239. - unsigned address_ : 14; + uint16_t address_ : 14; /// 1 if this is a short address train. - unsigned isShortAddress_ : 1; + uint16_t isShortAddress_ : 1; /// 0: forward, 1: reverse - unsigned direction_ : 1; + uint16_t direction_ : 1; /// fp16 value of the last set speed. - unsigned lastSetSpeed_ : 16; + uint16_t lastSetSpeed_ : 16; + + // ==== 32-bit boundary ==== + /// functions f0-f28. unsigned fn_ : 29; /// Which refresh packet should go out next. - unsigned nextRefresh_ : 3; - /// Speed step we last set. - unsigned speed_ : 5; + uint8_t nextRefresh_ : 3; + + // ==== 32-bit boundary ==== + + /// 1 if the last speed set was estop. + uint8_t isEstop_ : 1; /// Whether the direction change packet still needs to go out. - unsigned directionChanged_ : 1; + uint8_t directionChanged_ : 1; + /// 1 if the F0 function should be set/get in a directional way. + uint8_t f0SetDirectional_ : 1; + /// 1 if directional f0 is used and f0 is on for F. + uint8_t f0OnForward_ : 1; + /// 1 if directional f0 is used and f0 is on for R. + uint8_t f0OnReverse_ : 1; + /// 1 if F0 should be turned off when dir==forward. + uint8_t f0BlankForward_ : 1; + /// 1 if F0 should be turned off when dir==reverse. + uint8_t f0BlankReverse_ : 1; + /// Speed step we last set. + uint8_t speed_ : 7; - /** @return the number of speed steps (in float). */ - static unsigned get_speed_steps() + /// f29-f68 state. + uint8_t fhi_[5]; + + /// @return the largest function number supported by this train + /// (inclusive). + static unsigned get_max_fn() { - return 28; + return 68; } - /** @returns the largest function number that is still valid. */ - static unsigned get_max_fn() + /// Set a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @param value function state + void set_fn_store(unsigned idx, bool value) { - return 28; + if (idx < 29) + { + if (value) + { + fn_ |= (1u << idx); + } + else + { + fn_ &= ~(1u << idx); + } + } + else + { + idx -= 29; + if (value) + { + fhi_[idx / 8] |= (1u << (idx & 7)); + } + else + { + fhi_[idx / 8] &= ~(1u << (idx & 7)); + } + } + } + + /// Get a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @return function state + bool get_fn_store(unsigned idx) + { + if (idx < 29) + { + return (fn_ & (1u << idx)) != 0; + } + else + { + idx -= 29; + return (fhi_[idx / 8] & (1u << (idx & 7))) != 0; + } } /** @return the update code to send ot the packet handler for a given * function value change. @param address is the function number(0..28). */ static unsigned get_fn_update_code(unsigned address); + + /// @return what type of address this train has. + TrainAddressType get_address_type() + { + return isShortAddress_ ? TrainAddressType::DCC_SHORT_ADDRESS + : TrainAddressType::DCC_LONG_ADDRESS; + } +}; + +/// Structure defining the volatile state for a 28-speed-step DCC locomotive. +struct Dcc28Payload : public DccPayloadBase +{ + Dcc28Payload() + { + memset(this, 0, sizeof(*this)); + } + + /** @return the number of speed steps (in float). */ + static unsigned get_speed_steps() + { + return 28; + } /** Adds the speed payload to a DCC packet. @param p is the packet to add * the speed payload to. */ @@ -265,14 +484,10 @@ struct Dcc28Payload { p->add_dcc_speed28(!direction_, Packet::EMERGENCY_STOP); } - - /// @return what type of address this train has. - TrainAddressType get_address_type() - { - return isShortAddress_ ? TrainAddressType::DCC_SHORT_ADDRESS : TrainAddressType::DCC_LONG_ADDRESS; - } }; +static_assert(sizeof(Dcc28Payload) == 16, "size of dcc payload is wrong"); + /// TrainImpl class for a DCC locomotive. template class DccTrain : public AbstractTrain { @@ -305,28 +520,12 @@ public: typedef DccTrain Dcc28Train; /// Structure defining the volatile state for a 128-speed-step DCC locomotive. -struct Dcc128Payload +struct Dcc128Payload : public DccPayloadBase { Dcc128Payload() { memset(this, 0, sizeof(*this)); } - /// Track address. largest address allowed is 10239. - unsigned address_ : 14; - /// 1 if this is a short address train. - unsigned isShortAddress_ : 1; - /// 0: forward, 1: reverse - unsigned direction_ : 1; - /// fp16 value of the last set speed. - unsigned lastSetSpeed_ : 16; - /// functions f0-f28. - unsigned fn_ : 29; - /// Which refresh packet should go out next. - unsigned nextRefresh_ : 3; - /// Speed step we last set. - unsigned speed_ : 7; - /// Whether the direction change packet still needs to go out. - unsigned directionChanged_ : 1; /** @return the number of speed steps (the largest valid speed step). */ static unsigned get_speed_steps() @@ -334,19 +533,6 @@ struct Dcc128Payload return 126; } - /** @return the largest function number that is still valid. */ - static unsigned get_max_fn() - { - return 28; - } - - /** @return the update code to send ot the packet handler for a given - * function value change. */ - static unsigned get_fn_update_code(unsigned address) - { - return Dcc28Payload::get_fn_update_code(address); - } - /** Adds the speed payload to a DCC packet. @param p is the packet to add * the speed payload to. */ void add_dcc_speed_to_packet(dcc::Packet *p) @@ -360,12 +546,6 @@ struct Dcc128Payload { p->add_dcc_speed128(!direction_, Packet::EMERGENCY_STOP); } - - /// @return what type of address this train has. - TrainAddressType get_address_type() - { - return isShortAddress_ ? TrainAddressType::DCC_SHORT_ADDRESS : TrainAddressType::DCC_LONG_ADDRESS; - } }; /// TrainImpl class for a 128-speed-step DCC locomotive. @@ -392,6 +572,19 @@ struct MMOldPayload unsigned directionChanged_ : 1; /// Speed step we last set. unsigned speed_ : 4; + /// 1 if the last speed set was estop. + unsigned isEstop_ : 1; + + /// 1 if the F0 function should be set/get in a directional way. + unsigned f0SetDirectional_ : 1; + /// 1 if directional f0 is used and f0 is on for F. + unsigned f0OnForward_ : 1; + /// 1 if directional f0 is used and f0 is on for R. + unsigned f0OnReverse_ : 1; + /// 1 if F0 should be turned off when dir==forward. + unsigned f0BlankForward_ : 1; + /// 1 if F0 should be turned off when dir==reverse. + unsigned f0BlankReverse_ : 1; /** @return the number of speed steps (in float). */ unsigned get_speed_steps() @@ -405,6 +598,28 @@ struct MMOldPayload return 0; } + /// Set a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @param value function state + void set_fn_store(unsigned idx, bool value) { + if (value) + { + fn_ |= (1u << idx); + } + else + { + fn_ &= ~(1u << idx); + } + } + + /// Get a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @return function state + bool get_fn_store(unsigned idx) + { + return (fn_ & (1u << idx)) != 0; + } + /** @return the update code to send to the packet handler for a given * function value change. @param address is ignored */ unsigned get_fn_update_code(unsigned address) @@ -458,6 +673,19 @@ struct MMNewPayload unsigned speed_ : 4; /// internal refresh cycle state machine unsigned nextRefresh_ : 3; + /// 1 if the last speed set was estop. + unsigned isEstop_ : 1; + + /// 1 if the F0 function should be set/get in a directional way. + unsigned f0SetDirectional_ : 1; + /// 1 if directional f0 is used and f0 is on for F. + unsigned f0OnForward_ : 1; + /// 1 if directional f0 is used and f0 is on for R. + unsigned f0OnReverse_ : 1; + /// 1 if F0 should be turned off when dir==forward. + unsigned f0BlankForward_ : 1; + /// 1 if F0 should be turned off when dir==reverse. + unsigned f0BlankReverse_ : 1; /** @return the number of speed steps (in float). */ unsigned get_speed_steps() @@ -471,6 +699,28 @@ struct MMNewPayload return 4; } + /// Set a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @param value function state + void set_fn_store(unsigned idx, bool value) { + if (value) + { + fn_ |= (1u << idx); + } + else + { + fn_ &= ~(1u << idx); + } + } + + /// Get a given function bit in storage. + /// @param idx function number, 0 to get_max_fn. + /// @return function state + bool get_fn_store(unsigned idx) + { + return (fn_ & (1u << idx)) != 0; + } + /** @return the update code to send to the packet handler for a given * function value change. @param address is the function number (0..4) */ unsigned get_fn_update_code(unsigned address) diff --git a/src/dcc/Logon.cxxtest b/src/dcc/Logon.cxxtest new file mode 100644 index 000000000..276ca6e77 --- /dev/null +++ b/src/dcc/Logon.cxxtest @@ -0,0 +1,156 @@ +#include "dcc/Logon.hxx" + +#include "dcc/LogonModule.hxx" +#include "os/FakeClock.hxx" +#include "utils/async_traction_test_helper.hxx" + +using ::testing::ElementsAre; + +namespace dcc +{ + +class LogonTest : public openlcb::TractionTest +{ +protected: + ~LogonTest() + { + logonHandler_.shutdown(); + twait(); + } + + DefaultLogonModule module_; + RailcomHubFlow railcomHub_ {&g_service}; + StrictMock track_; + LogonHandler logonHandler_ { + &g_service, &track_, &railcomHub_, &module_}; +}; + +// This function is never called, and thus is optimized away at linker +// stage. However, it ensures that the logon handler can be compiled with the +// interface as the module parameter. +void compile_test() +{ + LogonHandler *f = nullptr; + f->~LogonHandler(); +} + +TEST_F(LogonTest, create) +{ +} + +TEST_F(LogonTest, logon_per_300msec) +{ + FakeClock clk; + EXPECT_CALL( + track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull)); + logonHandler_.startup_logon(0x2211, 0x5a); + wait(); + Mock::VerifyAndClear(&track_); + clk.advance(MSEC_TO_NSEC(20)); + wait(); + + clk.advance(MSEC_TO_NSEC(250)); + + EXPECT_CALL( + track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull)); + clk.advance(MSEC_TO_NSEC(50)); + wait(); + + EXPECT_CALL( + track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull)); + clk.advance(MSEC_TO_NSEC(300)); + wait(); +} + +TEST_F(LogonTest, select_shortinfo) +{ + FakeClock clk; + EXPECT_CALL( + track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull)) + .Times(AtLeast(1)); + logonHandler_.startup_logon(0x2211, 0x5a); + wait(); + + uint64_t decoder_id = 0x39944332211ull; + auto *b = railcomHub_.alloc(); + RailcomDefs::add_did_feedback(decoder_id, b->data()); + b->data()->feedbackKey = 0xFEFC0000ull; + + EXPECT_CALL(track_, + packet(ElementsAre(254, 0xD3, 0x99, 0x44, 0x33, 0x22, 0x11, 0xFF, _), + 0xFEDFF000ull)); + + railcomHub_.send(b); + wait(); + + clk.advance(MSEC_TO_NSEC(99)); + + // If there is no feedback for a while, the packet will get repeated. + EXPECT_CALL(track_, + packet(ElementsAre(254, 0xD3, 0x99, 0x44, 0x33, 0x22, 0x11, 0xFF, _), + 0xFEDFF000ull)); + clk.advance(MSEC_TO_NSEC(10)); + wait(); + + // After one re-try no more packets are generated for this locomotive. + clk.advance(MSEC_TO_NSEC(500)); + wait(); +} + +TEST_F(LogonTest, full_assign_sequence) +{ + FakeClock clk; + EXPECT_CALL( + track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull)) + .Times(AtLeast(1)); + logonHandler_.startup_logon(0x2211, 0x5a); + wait(); + + uint64_t decoder_id = 0x39944332211ull; + auto *b = railcomHub_.alloc(); + RailcomDefs::add_did_feedback(decoder_id, b->data()); + b->data()->feedbackKey = 0xFEFC0000ull; + + const uintptr_t SELECT_FB_KEY = 0xFEDFF000ull; + EXPECT_CALL(track_, + packet(ElementsAre(254, 0xD3, 0x99, 0x44, 0x33, 0x22, 0x11, 0xFF, _), + SELECT_FB_KEY)); + + railcomHub_.send(b); + wait(); + EXPECT_TRUE( + module_.loco_flags(0) & LogonHandlerModule::FLAG_PENDING_GET_SHORTINFO); + + // Select / shortinfo feedback. + b = railcomHub_.alloc(); + RailcomDefs::add_shortinfo_feedback( + (Defs::ADR_MOBILE_SHORT << 8) | 3, 17, 0, 0, b->data()); + b->data()->feedbackKey = SELECT_FB_KEY; + + const uintptr_t ASSIGN_FB_KEY = 0xFEE00000ull; + + // Assign packet + EXPECT_CALL(track_, + packet(ElementsAre(254, 0xE3, 0x99, 0x44, 0x33, 0x22, 0x11, + 0xC0 | (10000 >> 8), 10000 & 0xFF, _), + ASSIGN_FB_KEY)); + + railcomHub_.send(b); + wait(); + + EXPECT_TRUE( + module_.loco_flags(0) & LogonHandlerModule::FLAG_PENDING_ASSIGN); + + // Assign feedback. + b = railcomHub_.alloc(); + RailcomDefs::add_assign_feedback(0xff, 0xfff, 0, 0, b->data()); + b->data()->feedbackKey = ASSIGN_FB_KEY; + + railcomHub_.send(b); + wait(); + + uint8_t &flags = module_.loco_flags(0); + EXPECT_EQ(LogonHandlerModule::FLAG_COMPLETE, flags); +} + +} // namespace dcc diff --git a/src/dcc/Logon.hxx b/src/dcc/Logon.hxx new file mode 100644 index 000000000..f5f86ef33 --- /dev/null +++ b/src/dcc/Logon.hxx @@ -0,0 +1,697 @@ +/** \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 Logon.hxx + * Control flows for supporting DCC Automatic Logon. + * + * @author Balazs Racz + * @date 12 Aug 2021 + */ + +#ifndef _DCC_LOGON_HXX_ +#define _DCC_LOGON_HXX_ + +#include "dcc/LogonFeedback.hxx" +#include "dcc/PacketSource.hxx" +#include "dcc/TrackIf.hxx" +#include "dcc/UpdateLoop.hxx" +#include "executor/StateFlow.hxx" + +namespace dcc +{ + +/// This class needs to be a base class for the template argument of the Logon +/// Handler. +class LogonHandlerModule +{ +public: + /// @return the number of locomotives known. The locomotive IDs are + /// 0..num_locos() - 1. + unsigned num_locos(); + + /// @param loco_id a locomotive identifier + /// @return true if this is valid and belongs to a loco we know about. + bool is_valid_loco_id(unsigned loco_id); + + /// Finds the storage cell for a locomotive and returns the flag byte for + /// it. + /// @param loco_id a valid locomotive ID. + /// @return the flag byte for this loco. + uint8_t &loco_flags(unsigned loco_id); + + /// Retrieves the decoder unique ID. + /// @param loco_id the dense locomotive identifier. + /// @return the decoder unique ID (44 bit, LSb-aligned). + uint64_t loco_did(unsigned loco_id); + + /// Creates a new locomotive by decoder ID, or looks up an existing + /// locomotive by decoder ID. + /// @param decoder_id 44-bit decoder ID (aligned to LSb). + /// @return locomotive ID for this cell. + unsigned create_or_lookup_loco(uint64_t decoder_id); + + /// Runs the locomotive address policy. After the address policy is run, + /// the loco should have the ability to answer the assigned_address + /// question. + /// @param loco_id which locomotive this is + /// @param desired_address the S-9.2.1.1 encoded desired address for this + /// decoder. + void run_address_policy(unsigned loco_id, uint16_t desired_address); + + /// @param loco_id + /// @return the address to be assigned to this locomotive. 14-bit. + uint16_t assigned_address(unsigned loco_id); + + /// Invoked when the address assignment completes for a decoder. + /// @param loco_id which decoder. + void assign_complete(unsigned loco_id); + + /// Flags for the logon handler module. + enum Flags + { + /// This decoder needs a get shortinfo command. + FLAG_NEEDS_GET_SHORTINFO = 0x01, + /// We sent a get shortinfo command. + FLAG_PENDING_GET_SHORTINFO = 0x02, + + /// This decoder needs an assign command. + FLAG_NEEDS_ASSIGN = 0x04, + /// We sent an assign command + FLAG_PENDING_ASSIGN = 0x08, + + /// 1 if we completed the address assignment. + FLAG_COMPLETE = 0x10, + /// 1 if we ended up in an error state for this loco. + FLAG_ERROR_STATE = 0x20, + /// 1 if we have asked for a re-try. + FLAG_PENDING_RETRY = 0x40, + /// This is a 1-bit pre-scaler on a shared 50 msec timer that controls + /// the delay of re-tries. This makes a retry happen in 50 to 100 msec + /// time from the original failed attempt. + FLAG_PENDING_TICK = 0x80, + }; +}; // LogonHandlerModule + +/// Handles the automatic logon flow for DCC decoders. +template +class LogonHandler : public StateFlowBase, private LogonFeedbackCallbacks +{ +public: + /// Constructor + /// + /// @param service points to the executor to use. + /// @param track pointer to the track interface to send DCC packets to. + /// @param rcom_hub will register to this railcom hub to get feedback. + /// @param m the module for the storage and CS interface + LogonHandler( + Service *service, TrackIf *track, RailcomHubFlow *rcom_hub, Module *m) + : StateFlowBase(service) + , trackIf_(track) + , module_(m) + , fbParser_(this, rcom_hub) + , hasLogonEnableConflict_(0) + , hasLogonEnableFeedback_(0) + , needShutdown_(0) + { + } + + /// Initiates a logon sequence at startup. + void startup_logon(uint16_t cid, uint8_t session_id) + { + cid_ = cid; + sessionId_ = session_id; + start_flow(STATE(allocate_logon_now)); + } + +#ifdef GTEST + void shutdown() + { + needShutdown_ = 1; + timer_.ensure_triggered(); + logonSelect_.ensure_triggered(); + } +#endif + + // Callbacks from LogonFeedback + + /// Determines based on feedback key what the given DCC packet was. + /// @param feedback_key from the railcom packet. + /// @return the packet classification wrt the logon feature. + PacketType classify_packet(uintptr_t feedback_key) override + { + if (feedback_key >= 1 << 14) + { + LOG(INFO, "classify %x", (unsigned)feedback_key); + } + if (is_logon_enable_key(feedback_key)) + { + return LOGON_ENABLE; + } + else if (is_select_shortinfo_key(feedback_key)) + { + return SELECT_SHORTINFO; + } + else if (is_logon_assign_key(feedback_key)) + { + return LOGON_ASSIGN; + } + return UNKNOWN; + } + + /// Handles a Select ShortInfo feedback message. + /// @param feedback_key refers to the packet it came from. + /// @param error true if there was a transmission error or the data came in + /// incorrect format. + /// @param data 48 bits of payload. + void process_select_shortinfo( + uintptr_t feedback_key, bool error, uint64_t data) override + { + if (!is_select_shortinfo_key(feedback_key)) + { + LOG(WARNING, "Unexpected select shortinfo key: %08x", + (unsigned)feedback_key); + return; + } + unsigned loco_id = feedback_key & LOCO_ID_MASK; + if (!module_->is_valid_loco_id(loco_id)) + { + LOG(WARNING, + "Unexpected select shortinfo key: %08x - invalid loco id", + (unsigned)feedback_key); + return; + } + uint8_t &flags = module_->loco_flags(loco_id); + LOG(INFO, "Select shortinfo for loco ID %d, flags %02x error %d", + loco_id, flags, error); + flags &= ~LogonHandlerModule::FLAG_PENDING_GET_SHORTINFO; + if (error) + { + if (flags & LogonHandlerModule::FLAG_PENDING_RETRY) + { + flags &= ~LogonHandlerModule::FLAG_PENDING_RETRY; + flags |= LogonHandlerModule::FLAG_ERROR_STATE; + return; + } + else + { + flags |= LogonHandlerModule::FLAG_NEEDS_GET_SHORTINFO | + LogonHandlerModule::FLAG_PENDING_RETRY; + logonSelect_.wakeup(); + return; + } + } + if (flags & + (LogonHandlerModule::FLAG_NEEDS_ASSIGN | + LogonHandlerModule::FLAG_PENDING_ASSIGN)) + { + // Got multiple returns. + return; + } + module_->run_address_policy(loco_id, (data >> 32) & 0x3FFF); + flags |= LogonHandlerModule::FLAG_NEEDS_ASSIGN; + logonSelect_.wakeup(); + } + + /// Handles a Logon Assign feedback message. + /// @param feedback_key refers to the packet it came from. + /// @param error true if there was a transmission error or the data came in + /// incorrect format. + /// @param data 48 bits of payload. + void process_logon_assign( + uintptr_t feedback_key, bool error, uint64_t data) override + { + if (!is_logon_assign_key(feedback_key)) + { + LOG(WARNING, "Unexpected logon assign key: %08x", + (unsigned)feedback_key); + return; + } + unsigned loco_id = feedback_key & LOCO_ID_MASK; + if (!module_->is_valid_loco_id(loco_id)) + { + LOG(WARNING, "Unexpected logon assign key: %08x - invalid loco id", + (unsigned)feedback_key); + return; + } + uint8_t &flags = module_->loco_flags(loco_id); + flags &= ~LogonHandlerModule::FLAG_PENDING_ASSIGN; + if (flags & LogonHandlerModule::FLAG_COMPLETE) + { + // duplicate responses. + return; + } + if (error) + { + if (flags & LogonHandlerModule::FLAG_PENDING_RETRY) + { + flags &= ~LogonHandlerModule::FLAG_PENDING_RETRY; + flags |= LogonHandlerModule::FLAG_ERROR_STATE; + return; + } + else + { + flags |= LogonHandlerModule::FLAG_NEEDS_ASSIGN | + LogonHandlerModule::FLAG_PENDING_RETRY; + logonSelect_.wakeup(); + return; + } + } + module_->assign_complete(loco_id); + flags &= ~LogonHandlerModule::FLAG_PENDING_TICK; + LOG(INFO, "Assign completed for loco %d address %d", loco_id, + module_->assigned_address(loco_id)); + } + + /// Handles a Decoder ID feedback message. + /// @param feedback_key refers to the packet it came from. + /// @param error true if there was a transmission error or the data came in + /// incorrect format. + /// @param data 48 bits of payload. The low 44 bits of this is a decoder ID. + void process_decoder_id( + uintptr_t feedback_key, bool error, uint64_t data) override + { + timer_.ensure_triggered(); + if (data) + { + hasLogonEnableFeedback_ = 1; + } else { + // No railcom feedback returned. + return; + } + if (LOGLEVEL >= INFO) + { + unsigned didh = (data >> 32) & 0xfff; + unsigned didl = data & 0xffffffffu; + LOG(INFO, "Decoder id %03x%08x error %d", didh, didl, error); + } + if (error) + { + hasLogonEnableConflict_ = 1; + return; + } + uint64_t did = data & DECODER_ID_MASK; + auto lid = module_->create_or_lookup_loco(did); + if (!module_->is_valid_loco_id(lid)) + { + return; + } + auto &flags = module_->loco_flags(lid); + flags |= LogonHandlerModule::FLAG_NEEDS_GET_SHORTINFO; + logonSelect_.wakeup(); + } + +private: + /// Allocates a buffer and sends a Logon Enable(now) packet. + Action allocate_logon_now() + { + return allocate_and_call(trackIf_, STATE(send_logon_now)); + } + + /// Sends a Logon Enable(now) packet. + Action send_logon_now() + { + logon_send_helper(Defs::LogonEnableParam::NOW, 0); + return wait_and_call(STATE(start_logon_wait)); + } + + /// Called when the logon now packet is released. This means the packet is + /// enqueued in the device driver, but not necessarily that it is on the + /// track yet. + /// + /// Computes the next time that we need to send out a logon packet, and + /// starts a sleep. + Action start_logon_wait() + { + if (needShutdown_) + { + return exit(); + } + auto next_time = lastLogonTime_ + MSEC_TO_NSEC(LOGON_PERIOD_MSEC); + timer_.start_absolute(next_time); + return wait_and_call(STATE(evaluate_logon)); + } + + /// Called when the logon timer expires or is cancelled due to feedback. + Action evaluate_logon() + { + if (needShutdown_) + { + return exit(); + } + if (timer_.is_triggered()) + { + // found something via logon + if (hasLogonEnableConflict_) + { + hasLogonEnableConflict_ = 0; + hasLogonEnableFeedback_ = 0; + return call_immediately(STATE(allocate_logon_many)); + } + // Not sure why we were woken up, let's start a sleep again. + return call_immediately(STATE(start_logon_wait)); + } + else + { + // timer expired, send another logon. + return call_immediately(STATE(allocate_logon_now)); + } + } + + /// Called when we have seen a conflict on a logon enable packet. Allocates + /// a buffer to send out many logon packets. + Action allocate_logon_many() + { + return allocate_and_call(trackIf_, STATE(send_logon_many)); + } + + /// Send out the repeated logon request. + Action send_logon_many() + { + logon_send_helper(Defs::LogonEnableParam::ALL, 3); + return wait_and_call(STATE(many_logon_wait)); + } + + /// Called after the many logon packets' buffer is freed. + Action many_logon_wait() + { + if (countLogonToSend_ >= 4) + { + countLogonToSend_ -= 4; + return call_immediately(STATE(allocate_logon_many)); + } + else + { + countLogonToSend_ = 0; + /// @TODO we should really evaluate whether we've seen conflicts + /// coming back. + return call_immediately(STATE(start_logon_wait)); + } + } + + /// Helper function to send out logon enable commands. Requirement: a + /// buffer is allocated before calling. + /// @param param option of which logon enable command to send out. + /// @param rept 0-3 for how many repeats to send out (N-1, so it means 1-4 + /// repeats). + void logon_send_helper(Defs::LogonEnableParam param, unsigned rept) + { + HASSERT(rept < 4u); + auto *b = get_allocation_result(trackIf_); + b->data()->set_dcc_logon_enable(param, cid_, sessionId_); + b->data()->feedback_key = LOGON_ENABLE_KEY; + b->data()->packet_header.rept_count = rept; + b->set_done(bn_.reset(this)); + + hasLogonEnableFeedback_ = 0; + hasLogonEnableConflict_ = 0; + lastLogonTime_ = os_get_time_monotonic(); + + trackIf_->send(b); + } + + class LogonSelect; + friend class LogonSelect; + + /// Flow that sends out addressed packets that are part of the logon + /// sequences. + class LogonSelect : public StateFlowBase, public ::Timer + { + public: + LogonSelect(LogonHandler *parent) + : StateFlowBase(parent->service()) + , ::Timer(parent->service()->executor()->active_timers()) + , parent_(parent) + { + start(MSEC_TO_NSEC(50)); + } + + /// Notifies the flow that there is work to do. + void wakeup() + { + if (is_terminated()) + { + cycleNextId_ = 0; + start_flow(STATE(search)); + } + } + + /// Called by a timer every 50 msec. + void tick() + { + bool need_wakeup = false; + for (unsigned id = 0; id < m()->num_locos() && id < MAX_LOCO_ID; + ++id) + { + uint8_t &fl = m()->loco_flags(cycleNextId_); + if (fl & LogonHandlerModule::FLAG_PENDING_TICK) + { + fl &= ~LogonHandlerModule::FLAG_PENDING_TICK; + } + else if (fl & LogonHandlerModule::FLAG_PENDING_RETRY) + { + fl &= ~LogonHandlerModule::FLAG_PENDING_RETRY; + fl |= LogonHandlerModule::FLAG_ERROR_STATE; + } + else if (fl & LogonHandlerModule::FLAG_ERROR_STATE) + { + /// @todo locomotives that are in error state should be + // re-tried every now and then. We would probably need an + // extra counter for this though somewhere. + } + else if (fl & LogonHandlerModule::FLAG_PENDING_GET_SHORTINFO) + { + fl &= ~LogonHandlerModule::FLAG_PENDING_GET_SHORTINFO; + fl |= LogonHandlerModule::FLAG_NEEDS_GET_SHORTINFO | + LogonHandlerModule::FLAG_PENDING_RETRY; + need_wakeup = true; + } + else if (fl & LogonHandlerModule::FLAG_PENDING_ASSIGN) + { + fl &= ~LogonHandlerModule::FLAG_PENDING_ASSIGN; + fl |= LogonHandlerModule::FLAG_NEEDS_ASSIGN | + LogonHandlerModule::FLAG_PENDING_RETRY; + need_wakeup = true; + } + } + if (need_wakeup) + { + wakeup(); + } + } + + private: + /// Timer callback. + long long timeout() override + { + if (parent_->needShutdown_) + { + return 0; + } + tick(); + return RESTART; + } + + /// @return the pointer to the storage module. + Module *m() + { + return parent_->module_; + } + + /// @return the pointer to the track interface. + TrackIf *track() + { + return parent_->trackIf_; + } + + /// Entry to the flow. Looks through the states to see what we needs to + /// be done. + Action search() + { + if (parent_->needShutdown_) + { + return exit(); + } + bool mid_cycle = (cycleNextId_ != 0); + for (; + cycleNextId_ < m()->num_locos() && cycleNextId_ <= MAX_LOCO_ID; + ++cycleNextId_) + { + uint8_t fl = m()->loco_flags(cycleNextId_); + if (fl & LogonHandlerModule::FLAG_NEEDS_GET_SHORTINFO) + { + return allocate_and_call( + parent_->trackIf_, STATE(send_get_shortinfo)); + } + if (fl & LogonHandlerModule::FLAG_NEEDS_ASSIGN) + { + return allocate_and_call( + parent_->trackIf_, STATE(send_assign)); + } + } + cycleNextId_ = 0; + // Check if we need to run the search for the first half of the + // loco space too. + if (mid_cycle) + { + return again(); + } + return exit(); + } + + /// Called with a buffer allocated. Sends a get shortinfo command to + /// the current decoder. + Action send_get_shortinfo() + { + auto *b = get_allocation_result(parent_->trackIf_); + uint64_t did = m()->loco_did(cycleNextId_); + b->data()->set_dcc_select_shortinfo(did); + b->data()->feedback_key = + SELECT_SHORTINFO_KEY | (cycleNextId_ & LOCO_ID_MASK); + b->set_done(bn_.reset((StateFlowBase *)this)); + track()->send(b); + uint8_t &fl = m()->loco_flags(cycleNextId_); + fl &= ~LogonHandlerModule::FLAG_NEEDS_GET_SHORTINFO; + fl |= LogonHandlerModule::FLAG_PENDING_GET_SHORTINFO | + LogonHandlerModule::FLAG_PENDING_TICK; + return wait_and_call(STATE(search)); + } + + /// Called with a buffer allocated. Sends an assign command to + /// the current decoder. + Action send_assign() + { + auto *b = get_allocation_result(parent_->trackIf_); + uint64_t did = m()->loco_did(cycleNextId_); + b->data()->set_dcc_logon_assign( + did, m()->assigned_address(cycleNextId_)); + b->data()->feedback_key = + LOGON_ASSIGN_KEY | (cycleNextId_ & LOCO_ID_MASK); + b->set_done(bn_.reset((StateFlowBase *)this)); + track()->send(b); + uint8_t &fl = m()->loco_flags(cycleNextId_); + fl &= ~LogonHandlerModule::FLAG_NEEDS_ASSIGN; + fl |= LogonHandlerModule::FLAG_PENDING_ASSIGN | + LogonHandlerModule::FLAG_PENDING_TICK; + return wait_and_call(STATE(search)); + } + + /// Owning logon handler. + LogonHandler *parent_; + + /// Identifier of the storage that provides the next locomotive to look + /// at. + unsigned cycleNextId_; + + /// Helper for self notification. + BarrierNotifiable bn_; + } logonSelect_ {this}; + + /// We send this as feedback key for logon enable packets. + static constexpr uintptr_t LOGON_ENABLE_KEY = + uint32_t((Defs::ADDRESS_LOGON << 24) | (Defs::DCC_LOGON_ENABLE << 16)); + + /// Checks if a feedback key is for logon enable. + /// @param feedback_key the key + /// @return true if this is for a logon enable + static constexpr bool is_logon_enable_key(uintptr_t feedback_key) + { + return feedback_key == LOGON_ENABLE_KEY; + } + + /// We send this as feedback key for select/get short info packets. + static constexpr uintptr_t SELECT_SHORTINFO_KEY = + uint32_t((Defs::ADDRESS_LOGON << 24) | (Defs::DCC_SELECT << 16) | + (Defs::CMD_READ_SHORT_INFO << 12)); + + /// Checks if a feedback key is for select shortinfo. + /// @param feedback_key the key + /// @return true if this is for a select short info + static constexpr bool is_select_shortinfo_key(uintptr_t feedback_key) + { + return ((feedback_key & ~LOCO_ID_MASK) == SELECT_SHORTINFO_KEY); + } + + /// We send this as feedback key for logon assign packets. + static constexpr uintptr_t LOGON_ASSIGN_KEY = + uint32_t((Defs::ADDRESS_LOGON << 24) | (Defs::DCC_LOGON_ASSIGN << 16)); + + /// Checks if a feedback key is for logon assign. + /// @param feedback_key the key + /// @return true if this is for a logon assign + static constexpr bool is_logon_assign_key(uintptr_t feedback_key) + { + return ((feedback_key & ~LOCO_ID_MASK) == LOGON_ASSIGN_KEY); + } + + /// How often to send logon enable packets. + static constexpr unsigned LOGON_PERIOD_MSEC = 295; + + /// Maximum allowed locomotive ID. + static constexpr unsigned MAX_LOCO_ID = 0xfff; + /// Mask selecting bits that belong to the locomotive ID. + static constexpr uintptr_t LOCO_ID_MASK = MAX_LOCO_ID; + + /// Mask selecting bits that belong to the decoder ID. + static constexpr uint64_t DECODER_ID_MASK = (1ull << 44) - 1; + + /// If we need to send packets to the track, we can do it here directly. + TrackIf *trackIf_; + + /// Storage module. + Module *module_; + + /// Helper object for timing. + StateFlowTimer timer_ {this}; + + /// Helper object for parsing railcom feedback from the railcom hub. + LogonFeedbackParser fbParser_; + + BarrierNotifiable bn_; + + /// Timestamp of the last logon packet we sent out. + long long lastLogonTime_ {0}; + + /// Command station unique ID. + uint16_t cid_; + /// Session ID of the current session. + uint8_t sessionId_; + + /// 1 if we got an error (presumably a conflict) in the logon enable + /// feedback. + uint8_t hasLogonEnableConflict_ : 1; + /// 1 if we got any feedback packet from logon enable. + uint8_t hasLogonEnableFeedback_ : 1; + /// Signals that we need to shut down the flow. + uint8_t needShutdown_ : 1; + + /// Tracks how many logons to send out. + uint8_t countLogonToSend_ {0}; + +}; // LogonHandler + +} // namespace dcc + +#endif // _DCC_LOGON_HXX_ diff --git a/src/dcc/LogonFeedback.cxxtest b/src/dcc/LogonFeedback.cxxtest new file mode 100644 index 000000000..6736e266c --- /dev/null +++ b/src/dcc/LogonFeedback.cxxtest @@ -0,0 +1,220 @@ +#include "dcc/LogonFeedback.hxx" + +#include "utils/test_main.hxx" + +using ::testing::Return; + +namespace dcc +{ + +class MockFeedbackCallbacks : public LogonFeedbackCallbacks +{ +public: + MOCK_METHOD1( + classify_packet, LogonFeedbackCallbacks::PacketType(uintptr_t)); + MOCK_METHOD3(process_select_shortinfo, + void(uintptr_t feedback_key, bool error, uint64_t data)); + + MOCK_METHOD3(process_logon_assign, + void(uintptr_t feedback_key, bool error, uint64_t data)); + + MOCK_METHOD3(process_decoder_id, + void(uintptr_t feedback_key, bool error, uint64_t data)); +}; + +class ParseFeedbackTest : public ::testing::Test +{ +protected: + /// Creates a valid 8-byte railcom feedback in fb_. + void create_valid_code() + { + RailcomDefs::append12(15, 0x44, fb_.ch1Data); + fb_.ch1Size = 2; + + RailcomDefs::append36(0xa, 0x11223344, fb_.ch2Data); + fb_.ch2Size = 6; + } + + /// Sends the current value in fb_ to the railcom hub. + void send() + { + auto *b = hub_.alloc(); + b->data()->value() = fb_; + hub_.send(b); + wait_for_main_executor(); + } + + ::testing::StrictMock cb_; + dcc::Feedback fb_; + RailcomHubFlow hub_ {&g_service}; + LogonFeedbackParser parser_ {&cb_, &hub_}; +}; + +TEST_F(ParseFeedbackTest, valid_code) +{ + create_valid_code(); + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(0u, d >> LogonFeedbackParser::ERROR_SHIFT); + EXPECT_EQ(8u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + EXPECT_EQ(0xf44a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, conflict_code) +{ + create_valid_code(); + fb_.ch1Data[1] |= 0xFF; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_GARBAGE | + LogonFeedbackParser::ERROR_OUT_OF_ORDER, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(7u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf40a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, short_code) +{ + create_valid_code(); + fb_.ch2Size = 4; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_MISSING_DATA, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(6u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf44a11223000u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, short_code_ack) +{ + create_valid_code(); + RailcomDefs::append36(0xa, 0x11220000, fb_.ch2Data); + fb_.ch2Data[5] = RailcomDefs::CODE_ACK; + fb_.ch2Data[4] = RailcomDefs::CODE_ACK2; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ( + LogonFeedbackParser::ERROR_ACK, d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(6u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf44a11220000u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, out_of_order_ack) +{ + create_valid_code(); + fb_.ch1Data[1] = RailcomDefs::CODE_ACK; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_ACK | + LogonFeedbackParser::ERROR_OUT_OF_ORDER, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(7u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf40a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, one_ack) +{ + fb_.ch1Data[0] = RailcomDefs::CODE_ACK; + fb_.ch1Size = 1; + fb_.ch2Size = 0; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_ACK | + LogonFeedbackParser::ERROR_MISSING_DATA, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(0u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + EXPECT_EQ(0u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, unknown) +{ + create_valid_code(); + fb_.ch1Data[1] = RailcomDefs::CODE_BUSY; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_UNKNOWN | + LogonFeedbackParser::ERROR_OUT_OF_ORDER, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(7u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf40a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, decoder_id_reply) +{ + constexpr uint64_t decoder_id = 0x79944332211ull; + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_did_feedback(decoder_id, &fb_); + fb_.feedbackKey = key; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::LOGON_ENABLE)); + EXPECT_CALL( + cb_, process_decoder_id(key, false, decoder_id | (15ull << 44))); + send(); +} + +TEST_F(ParseFeedbackTest, short_info_reply) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_); + fb_.feedbackKey = key; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::SELECT_SHORTINFO)); + EXPECT_CALL(cb_, process_select_shortinfo(key, false, 0x938255a7030bull)); + send(); +} + +TEST_F(ParseFeedbackTest, short_info_crc_error) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_); + fb_.feedbackKey = key; + fb_.ch2Data[5] = railcom_encode[0x3f]; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::SELECT_SHORTINFO)); + // Reports error + EXPECT_CALL(cb_, process_select_shortinfo(key, true, 0x938255a7033full)); + send(); +} + +TEST_F(ParseFeedbackTest, short_info_garbage) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_); + fb_.feedbackKey = key; + fb_.ch2Data[5] = 0xF3; // invalid 4/8 code + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::SELECT_SHORTINFO)); + // Reports error + EXPECT_CALL(cb_, process_select_shortinfo(key, true, 0x938255a70300ull)); + send(); +} + +TEST_F(ParseFeedbackTest, assign_response) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_assign_feedback(0x5a, 0x327, 0x18, 0x22, &fb_); + fb_.feedbackKey = key; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::LOGON_ASSIGN)); + EXPECT_CALL(cb_, process_logon_assign(key, false, 0xd5a327182246ull)); + send(); +} + +TEST_F(ParseFeedbackTest, assign_response_crc_error) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_assign_feedback(0x5a, 0x327, 0x18, 0x22, &fb_); + fb_.feedbackKey = key; + fb_.ch2Data[5] = railcom_encode[0x3f]; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::LOGON_ASSIGN)); + // Reports error + EXPECT_CALL(cb_, process_logon_assign(key, true, 0xd5a32718227full)); + send(); +} + +} // namespace dcc diff --git a/src/dcc/LogonFeedback.hxx b/src/dcc/LogonFeedback.hxx new file mode 100644 index 000000000..b8a90f85b --- /dev/null +++ b/src/dcc/LogonFeedback.hxx @@ -0,0 +1,278 @@ +/** \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 LogonFeedback.hxx + * Parser for RailCom feedback that recognizes logon messages. + * + * @author Balazs Racz + * @date 12 Aug 2021 + */ + +#ifndef _DCC_LOGONFEEDBACK_HXX_ +#define _DCC_LOGONFEEDBACK_HXX_ + +#include "dcc/RailcomHub.hxx" +#include "utils/Crc.hxx" + +namespace dcc +{ + +/// Abstract class to get callbacks for recognized feedback messages +class LogonFeedbackCallbacks +{ +public: + enum PacketType + { + /// Non-254 packet or not known (not relevant) 254 packet type + UNKNOWN = 0, + /// Get Data Start packet + GET_DATA_START, + /// Get Data Continue packet + GET_DATA_CONT, + /// Logon Enable packet + LOGON_ENABLE, + /// Select packet with Get Short Info command + SELECT_SHORTINFO, + /// Logon Assign packet + LOGON_ASSIGN, + /// Misc 254 packet (ID based responses) + MISC_254 + }; + + /// Determines based on feedback key what the given DCC packet was. + /// @param feedback_key from the railcom packet. + /// @return the packet classification wrt the logon feature. + virtual PacketType classify_packet(uintptr_t feedback_key) = 0; + + /// Handles a Select ShortInfo feedback message. + /// @param feedback_key refers to the packet it came from. + /// @param error true if there was a transmission error or the data came in + /// incorrect format. + /// @param data 48 bits of payload. + virtual void process_select_shortinfo( + uintptr_t feedback_key, bool error, uint64_t data) = 0; + + /// Handles a Logon Assign feedback message. + /// @param feedback_key refers to the packet it came from. + /// @param error true if there was a transmission error or the data came in + /// incorrect format. + /// @param data 48 bits of payload. + virtual void process_logon_assign( + uintptr_t feedback_key, bool error, uint64_t data) = 0; + + /// Handles a Decoder ID feedback message. + /// @param feedback_key refers to the packet it came from. + /// @param error true if there was a transmission error or the data came in + /// incorrect format. + /// @param data 48 bits of payload. The low 44 bits of this is a decoder ID. + virtual void process_decoder_id( + uintptr_t feedback_key, bool error, uint64_t data) = 0; +}; + +/// Parser for RailCom feedback that recognizes logon messages. +class LogonFeedbackParser : public RailcomHubPortInterface +{ +public: + using PacketType = LogonFeedbackCallbacks::PacketType; + /// Constructor. + /// @param cb recognized packets get calls on this object. + /// @param hub the railcom hub for gathering feedback. + LogonFeedbackParser(LogonFeedbackCallbacks *cb, RailcomHubFlow *hub) + : cb_(cb) + , hub_(hub) + { + hub_->register_port(this); + } + + ~LogonFeedbackParser() + { + hub_->unregister_port(this); + } + + /// Receives railcom feedback. + void send(Buffer *b, unsigned priority) override + { + auto rb = get_buffer_deleter(b); + PacketType type = cb_->classify_packet(b->data()->feedbackKey); + if (type == PacketType::UNKNOWN) + { + return; + } + uint64_t data = parse_code(b->data()); + bool any_error = data & ERROR_MASK; + auto key = b->data()->feedbackKey; + switch (type) + { + case PacketType::SELECT_SHORTINFO: + any_error |= has_crc_error(data); + any_error |= (((data >> 47) & 1) != 1); + cb_->process_select_shortinfo( + key, any_error, data & PAYLOAD_MASK); + break; + case PacketType::LOGON_ASSIGN: + any_error |= has_crc_error(data); + any_error |= + (((data >> 44) & 0xf) != RMOB_LOGON_ASSIGN_FEEDBACK); + cb_->process_logon_assign(key, any_error, data & PAYLOAD_MASK); + break; + case PacketType::LOGON_ENABLE: + any_error |= + (((data >> 44) & 0xf) != RMOB_LOGON_ENABLE_FEEDBACK); + cb_->process_decoder_id(key, any_error, data & PAYLOAD_MASK); + break; + case PacketType::GET_DATA_START: + case PacketType::GET_DATA_CONT: + case PacketType::MISC_254: + case PacketType::UNKNOWN: + default: + break; + } + } + +#ifndef GTEST +private: +#endif + enum Errors + { + /// Which bit offset do the error bits start. + ERROR_SHIFT = 56, + /// Mask where the error bits are. + ERROR_MASK = 0xffULL << ERROR_SHIFT, + /// Which bit offset do the error bits start. + LENGTH_SHIFT = 48, + /// Counts the number of valid 6-bit counts. + LENGTH_OFFSET = 1ULL << LENGTH_SHIFT, + /// Which bit offset do the error bits start. + LENGTH_MASK = 0xffULL << LENGTH_SHIFT, + /// Mask where the decoded payload is. + PAYLOAD_MASK = LENGTH_OFFSET - 1, + + /// Not enough bytes in the feedback response. + ERROR_MISSING_DATA = 1ULL << ERROR_SHIFT, + /// Found an ACK byte. + ERROR_ACK = 2ULL << ERROR_SHIFT, + /// Found valid data past an ACK byte or a missing byte. + ERROR_OUT_OF_ORDER = 4ULL << ERROR_SHIFT, + /// Found invalid 6/8 code. + ERROR_GARBAGE = 8ULL << ERROR_SHIFT, + /// Found unknown codepoint (e.g. NACK or BUSY). + ERROR_UNKNOWN = 16ULL << ERROR_SHIFT, + }; + + /// Appends 6 bits of incoming data from railcom. + /// @param data the 48-bit aggregated data. + /// @param shift where the next 6 bits should be at (0 to 42) + /// @param next_byte the next byte from the uart. + static void append_data(uint64_t &data, unsigned &shift, uint8_t next_byte) + { + uint8_t code = railcom_decode[next_byte]; + if (code < 64) + { + data |= ((uint64_t)code) << shift; + if (data >> ERROR_SHIFT) + { + // Valid data beyond some error. + data |= ERROR_OUT_OF_ORDER; + } + data += LENGTH_OFFSET; + } + else if (code == RailcomDefs::ACK) + { + data |= ERROR_ACK; + } + else if (code == RailcomDefs::INV) + { + data |= ERROR_GARBAGE; + } + else + { + data |= ERROR_UNKNOWN; + } + shift -= 6; + } + + /// Parses a concatenated ch1+ch2 response into a single uint64_t. It + /// contains error flags in the MSB, the number of valid 6-bit codepoints + /// in the second MSB , and 6 bytes data in the LSB, entered with the first + /// wire byte in the third MSB and last wire byte in the LSB. + /// + /// Any input byte that was missing or contained any other codepoint than a + /// valid 6-bit data codepoint (including garbage or ACK) is translated + /// into zero bits in the parse result. + /// @param fb feedback to parse + /// @return parse result. + static uint64_t parse_code(const Feedback *fb) + { + uint64_t data = 0; + unsigned shift = 48 - 6; + for (unsigned i = 0; i < 2; i++) + { + if (fb->ch1Size > i) + { + append_data(data, shift, fb->ch1Data[i]); + } + else + { + data |= ERROR_MISSING_DATA; + } + } + for (unsigned i = 0; i < 6; i++) + { + if (fb->ch2Size > i) + { + append_data(data, shift, fb->ch2Data[i]); + } + else + { + data |= ERROR_MISSING_DATA; + } + } + return data; + } + + /// Checks the CRC8 on a 6-byte payload in this feedback. + /// @param data the feedback bytes (right aligned, first is MSB). + /// @return true if there is a CRC error + static bool has_crc_error(uint64_t data) + { + Crc8DallasMaxim m; + for (int i = 48 - 8; i >= 0; i -= 8) + { + m.update16((data >> i) & 0xff); + } + return !m.check_ok(); + } + +private: + /// Callbacks object. + LogonFeedbackCallbacks *cb_; + /// The railcom hub we are registered to. + RailcomHubFlow *hub_; +}; + +} // namespace dcc + +#endif // _DCC_LOGONFEEDBACK_HXX_ diff --git a/src/dcc/LogonModule.hxx b/src/dcc/LogonModule.hxx new file mode 100644 index 000000000..f7032486c --- /dev/null +++ b/src/dcc/LogonModule.hxx @@ -0,0 +1,164 @@ +/** \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 LogonModule.hxx + * Default implementation of a storage and policy module for automatic logon. + * + * @author Balazs Racz + * @date 14 Aug 2021 + */ + +#ifndef _DCC_LOGONMODULE_HXX_ +#define _DCC_LOGONMODULE_HXX_ + +#include +#include + +#include "dcc/Defs.hxx" +#include "dcc/Logon.hxx" + +namespace dcc +{ + +/// Default implementation of the storage and policy module for trains. +template +class ParameterizedLogonModule : public LogonHandlerModule +{ +public: + /// We store this structure about each locomotive. + struct LocoInfo : public Base::Storage + { + /// State machine flags about this loco. + uint8_t flags_ {0}; + + /// The assigned DCC address. The encoding is in the S-9.2.1.1 format. + /// The default value is an invalid address causing an error on the + /// locomotive. + uint16_t assignedAddress_ {Defs::ADR_INVALID}; + + /// 44-bit decoder unique ID. + uint64_t decoderId_; + }; + + std::vector locos_; + std::map ids_; + + /// @return the number of locomotives known. The locomotive IDs are + /// 0..num_locos() - 1. + unsigned num_locos() + { + return locos_.size(); + } + + /// @param loco_id a locomotive identifier + /// @return true if this is valid and belongs to a loco we know about. + bool is_valid_loco_id(unsigned loco_id) + { + return loco_id < num_locos(); + } + + /// Finds the storage cell for a locomotive and returns the flag byte for + /// it. + /// @param loco_id a valid locomotive ID. + /// @return the flag byte for this loco. + uint8_t &loco_flags(unsigned loco_id) + { + return locos_[loco_id].flags_; + } + + /// Retrieves the decoder unique ID. + /// @param loco_id the dense locomotive identifier. + /// @return the decoder unique ID (44 bit, LSb-aligned). + uint64_t loco_did(unsigned loco_id) + { + return locos_[loco_id].decoderId_; + } + + /// Creates a new locomotive by decoder ID, or looks up an existing + /// locomotive by decoder ID. + /// @param decoder_id 44-bit decoder ID (aligned to LSb). + /// @return locomotive ID for this cell. + unsigned create_or_lookup_loco(uint64_t decoder_id) + { + auto it = ids_.find(decoder_id); + if (it == ids_.end()) + { + // create new. + uint16_t lid = locos_.size(); + locos_.emplace_back(); + locos_[lid].decoderId_ = decoder_id; + ids_[decoder_id] = lid; + return lid; + } + else + { + return it->second; + } + } + + /// Runs the locomotive address policy. After the address policy is run, + /// the loco should have the ability to answer the assigned_address + /// question. + /// @param loco_id which locomotive this is + /// @param desired_address the S-9.2.1.1 encoded desired address for this + /// decoder. + void run_address_policy(unsigned loco_id, uint16_t desired_address) + { + /// @todo support accessory decoders. + + // Note: we ignore the desired address and start assigning addresses + // from 10000 and up. + locos_[loco_id].assignedAddress_ = nextAddress_++; + } + + /// @param loco_id + /// @return the address to be assigned to this locomotive. 14-bit. + uint16_t assigned_address(unsigned loco_id) + { + return locos_[loco_id].assignedAddress_; + } + + /// Invoked when the address assignment completes for a decoder. + /// @param loco_id which decoder. + void assign_complete(unsigned loco_id) + { + loco_flags(loco_id) |= LogonHandlerModule::FLAG_COMPLETE; + } + + uint16_t nextAddress_ {(Defs::ADR_MOBILE_LONG << 8) + 10000}; + +}; // class ParameterizedLogonModule + +class DefaultBase { +public: + struct Storage {}; +}; + +using DefaultLogonModule = ParameterizedLogonModule; + +} // namespace dcc + +#endif // _DCC_LOGONMODULE_HXX_ diff --git a/src/dcc/Packet.cxx b/src/dcc/Packet.cxx index 92a73dc8b..4866449c6 100644 --- a/src/dcc/Packet.cxx +++ b/src/dcc/Packet.cxx @@ -36,6 +36,8 @@ #include "dcc/Packet.hxx" +#include "dcc/Defs.hxx" +#include "utils/Crc.hxx" #include "utils/logging.h" #include "utils/macros.h" @@ -43,58 +45,30 @@ namespace dcc { -static_assert(sizeof(Packet) == sizeof(DCCPacket), "DCCPacket size missmatch"); +// Imports the bit declarations from the enums in Defs. This import may only be +// performed in a .cxx file. +using namespace Defs; -enum -{ - MARKLIN_DEFAULT_CMD = 0b00100110, - // Direction change bits for marklin-old format. - MARKLIN_CHANGE_DIR_B2 = 0b11000000, - - DCC_DEFAULT_CMD = 0, - DCC_LONG_PREAMBLE_CMD = 0b00001100, - DCC_SERVICE_MODE_5X_WITH_ACK_CMD = 0b00111000, - // standard dcc packet with 5x repeat. - DCC_SERVICE_MODE_5X_CMD = 0b00101000, - DCC_SERVICE_MODE_1X_CMD = 0b00001000, - DCC_LONG_ADDRESS_FIRST = 0b11000000, - - // Baseline packet: speed and direction. - DCC_BASELINE_SPEED = 0b01000000, - DCC_BASELINE_SPEED_FORWARD = 0b00100000, - DCC_BASELINE_SPEED_LIGHT = 0b00010000, - DCC_FUNCTION1 = 0b10000000, - DCC_FUNCTION1_F0 = 0b00010000, - DCC_FUNCTION2_F5 = 0b10110000, - DCC_FUNCTION2_F9 = 0b10100000, - DCC_FEATURE_EXP_F13 = 0b11011110, - DCC_FEATURE_EXP_F21 = 0b11011111, - - DCC_PROG_READ1 = 0b11100100, - DCC_PROG_WRITE1 = 0b11101100, - DCC_PROG_READ4 = 0b11100000, - - DCC_SVC_BIT_MANIPULATE = 0b01111000, - DCC_SVC_WRITE = 0b01111100, - DCC_SVC_VERIFY = 0b01110100, - - DCC_SVC_BITVAL_WRITE = 0b11110000, - DCC_SVC_BITVAL_VERIFY = 0b11100000, - DCC_SVC_BITVAL_VALUE = 0b00001000, - - DCC_BASIC_ACCESSORY_B1 = 0b10000000, - DCC_BASIC_ACCESSORY_B2 = 0b10000000, - - // Extended packet: 128-step speed. - DCC_EXT_SPEED = 0b00111111, - DCC_EXT_SPEED_FORWARD = 0x80, -}; +static_assert(sizeof(Packet) == sizeof(DCCPacket), "DCCPacket size missmatch"); void Packet::add_dcc_checksum() { HASSERT(dlc < MAX_PAYLOAD); // Protects against double call of add checksum. HASSERT(!packet_header.skip_ec); + + // Performs CRC computation if needed. + if ((payload[0] == ADDRESS_LOGON || payload[0] == ADDRESS_EXT) && + (dlc >= 6)) + { + Crc8DallasMaxim m; + for (int i = 0; i < dlc; ++i) + { + m.update16(payload[i]); + } + payload[dlc++] = m.get(); + } + uint8_t cs = 0; for (int i = 0; i < dlc; ++i) { @@ -231,17 +205,39 @@ void Packet::add_dcc_function9_12(unsigned values) add_dcc_checksum(); } -void Packet::add_dcc_function13_20(unsigned values) +void Packet::add_dcc_function_hi(uint8_t base, uint8_t values) { - payload[dlc++] = DCC_FEATURE_EXP_F13; - payload[dlc++] = values & 0xff; + base -= 13; + HASSERT((base & 0b111) == 0); + HASSERT(base <= (61 - 13)); + base >>= 3; + base -= 2; + payload[dlc++] = DCC_FEATURE_EXP_FNHI | (base & 0b111); + payload[dlc++] = values; add_dcc_checksum(); } -void Packet::add_dcc_function21_28(unsigned values) +void Packet::add_dcc_binary_state(uint16_t fn, bool value) { - payload[dlc++] = DCC_FEATURE_EXP_F21; - payload[dlc++] = values & 0xff; + if (fn <= 127) + { + payload[dlc++] = DCC_BINARY_SHORT; + payload[dlc++] = fn | (value ? 0x80 : 0); + } + else + { + payload[dlc++] = DCC_BINARY_LONG; + payload[dlc++] = (fn & 0x7F) | (value ? 0x80 : 0); + payload[dlc++] = (fn >> 8) & 0xFF; + } + add_dcc_checksum(); +} + +void Packet::add_dcc_analog_function(uint8_t fn, uint8_t value) +{ + payload[dlc++] = DCC_ANALOG_FN; + payload[dlc++] = fn; + payload[dlc++] = value; add_dcc_checksum(); } @@ -293,6 +289,24 @@ void Packet::set_dcc_svc_write_bit( add_dcc_prog_command(DCC_SVC_BIT_MANIPULATE, cv_number, vvv); } +void Packet::set_dcc_svc_paged_write_reg(uint8_t reg, uint8_t value) +{ + HASSERT(reg < 8); + start_dcc_svc_packet(); + payload[dlc++] = DCC_SVC_PAGED_WRITE | reg; + payload[dlc++] = value; + add_dcc_checksum(); +} + +void Packet::set_dcc_svc_paged_verify_reg(uint8_t reg, uint8_t value) +{ + HASSERT(reg < 8); + start_dcc_svc_packet(); + payload[dlc++] = DCC_SVC_PAGED_VERIFY | reg; + payload[dlc++] = value; + add_dcc_checksum(); +} + void Packet::add_dcc_basic_accessory(unsigned address, bool is_activate) { payload[dlc++] = DCC_BASIC_ACCESSORY_B1 | ((address >> 3) & 0b111111); uint8_t b2 = 1; @@ -306,6 +320,49 @@ void Packet::add_dcc_basic_accessory(unsigned address, bool is_activate) { add_dcc_checksum(); } +void Packet::set_dcc_logon_enable( + Defs::LogonEnableParam param, uint16_t cid, uint8_t session_id) +{ + start_dcc_packet(); + payload[dlc++] = ADDRESS_LOGON; + payload[dlc++] = DCC_LOGON_ENABLE | ((uint8_t)param & 0x3); + payload[dlc++] = cid >> 8; + payload[dlc++] = cid & 0xff; + payload[dlc++] = session_id; + add_dcc_checksum(); +} + +void Packet::set_dcc_select_shortinfo(uint64_t decoder_id) +{ + start_dcc_packet(); + payload[dlc++] = ADDRESS_LOGON; + + payload[dlc++] = DCC_SELECT | ((decoder_id >> 40) & 0xf); + payload[dlc++] = (decoder_id >> 32) & 0xff; + payload[dlc++] = (decoder_id >> 24) & 0xff; + payload[dlc++] = (decoder_id >> 16) & 0xff; + payload[dlc++] = (decoder_id >> 8) & 0xff; + payload[dlc++] = (decoder_id) & 0xff; + payload[dlc++] = CMD_READ_SHORT_INFO; + add_dcc_checksum(); +} + +void Packet::set_dcc_logon_assign(uint64_t decoder_id, uint16_t address) +{ + start_dcc_packet(); + payload[dlc++] = ADDRESS_LOGON; + + payload[dlc++] = DCC_LOGON_ASSIGN | ((decoder_id >> 40) & 0xf); + payload[dlc++] = (decoder_id >> 32) & 0xff; + payload[dlc++] = (decoder_id >> 24) & 0xff; + payload[dlc++] = (decoder_id >> 16) & 0xff; + payload[dlc++] = (decoder_id >> 8) & 0xff; + payload[dlc++] = (decoder_id) & 0xff; + payload[dlc++] = ((address >> 8) & 0xff) ^ 0b11000000; + payload[dlc++] = (address) & 0xff; + add_dcc_checksum(); +} + void Packet::start_mm_packet() { dlc = 3; diff --git a/src/dcc/Packet.cxxtest b/src/dcc/Packet.cxxtest index f6392c2ba..b3bda0d7d 100644 --- a/src/dcc/Packet.cxxtest +++ b/src/dcc/Packet.cxxtest @@ -3,6 +3,7 @@ #include "dcc/Packet.hxx" #include "dcc/Loco.hxx" #include "dcc/UpdateLoop.hxx" +#include "utils/Crc.hxx" using ::testing::AtLeast; using ::testing::ElementsAre; @@ -22,6 +23,28 @@ protected: return vector(pkt_.payload + 0, pkt_.payload + pkt_.dlc); } + /// @return true if pkt_ has correct checksum. + bool check_checksum() + { + uint8_t p = 0; + for (unsigned i = 0; i < pkt_.dlc; i++) + { + p ^= pkt_.payload[i]; + } + return p == 0; + } + + /// @return true if pkt_ has correct CRC8. + bool check_crc() + { + Crc8DallasMaxim m; + for (int i = 0; i < pkt_.dlc - 1; i++) + { + m.update256(pkt_.payload[i]); + } + return m.get() == 0; + } + vector packet(const std::initializer_list &data) { vector v; @@ -65,6 +88,71 @@ TEST_F(PacketTest, ChecksumFF) EXPECT_EQ(0xFF, pkt_.payload[3]); } +TEST_F(PacketTest, ChecksumLogon) +{ + pkt_.clear(); + pkt_.payload[pkt_.dlc++] = 254; + pkt_.payload[pkt_.dlc++] = 0x01; + pkt_.add_dcc_checksum(); + EXPECT_EQ(3, pkt_.dlc); + EXPECT_EQ(255, pkt_.payload[2]); + + pkt_.clear(); + pkt_.payload[pkt_.dlc++] = 253; + pkt_.payload[pkt_.dlc++] = 0x00; + pkt_.add_dcc_checksum(); + EXPECT_EQ(3, pkt_.dlc); + EXPECT_EQ(253, pkt_.payload[2]); + + pkt_.clear(); + pkt_.payload[pkt_.dlc++] = 253; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.add_dcc_checksum(); + EXPECT_EQ(6, pkt_.dlc); + EXPECT_EQ(253, pkt_.payload[5]); + + pkt_.clear(); + pkt_.payload[pkt_.dlc++] = 254; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.add_dcc_checksum(); + EXPECT_EQ(6, pkt_.dlc); + EXPECT_EQ(254, pkt_.payload[5]); +} + +TEST_F(PacketTest, CrcLogon) +{ + pkt_.clear(); + pkt_.payload[pkt_.dlc++] = 253; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.add_dcc_checksum(); + EXPECT_EQ(8, pkt_.dlc); + EXPECT_EQ(0x40, pkt_.payload[6]); + EXPECT_EQ(253 ^ 0xaa ^ 0x40, pkt_.payload[7]); + + pkt_.clear(); + pkt_.payload[pkt_.dlc++] = 254; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.payload[pkt_.dlc++] = 0xaa; + pkt_.add_dcc_checksum(); + EXPECT_EQ(8, pkt_.dlc); + EXPECT_EQ(0x19, pkt_.payload[6]); + EXPECT_EQ(254 ^ 0xaa ^ 0x19, pkt_.payload[7]); +} + + TEST_F(PacketTest, DccSpeed28) { pkt_.set_dcc_speed28(DccShortAddress(55), true, 6); @@ -161,6 +249,75 @@ TEST_F(PacketTest, Fn20) EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011111, 0xAA, _)); } +TEST_F(PacketTest, Fn29) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(29, 0x5A); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011000, 0x5A, _)); +} + +TEST_F(PacketTest, Fn37) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(37, 0x11); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011001, 0x11, _)); +} + +TEST_F(PacketTest, Fn45) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(45, 0x11); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011010, 0x11, _)); +} + +TEST_F(PacketTest, Fn53) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(53, 0x11); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011011, 0x11, _)); +} + +TEST_F(PacketTest, Fn61) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_function_hi(61, 0x11); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011100, 0x11, _)); +} + +TEST_F(PacketTest, BinaryStateShort) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_binary_state(61, true); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011101, 61 | 0x80, _)); +} + +TEST_F(PacketTest, BinaryStateShortOff) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_binary_state(127, false); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011101, 127, _)); +} + +TEST_F(PacketTest, BinaryStateLongOn) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_binary_state(16 * 256 + 61, true); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11000000, 61 + 0x80, 16, _)); +} + +TEST_F(PacketTest, BinaryStateLongOff) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_binary_state(16 * 256 + 61, false); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11000000, 61, 16, _)); +} + +TEST_F(PacketTest, AnalogFunction) +{ + pkt_.add_dcc_address(DccShortAddress(55)); + pkt_.add_dcc_analog_function(17, 99); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b00111101, 17, 99, _)); +} TEST_F(PacketTest, DccBasicAccyOn) { @@ -205,6 +362,62 @@ TEST_F(PacketTest, SvcProgDirectBit) EXPECT_THAT(get_packet(), ElementsAre(b1, b2, b3, b1 ^ b2 ^ b3)); } +TEST_F(PacketTest, SvcProgPagedWrite) +{ + pkt_.set_dcc_svc_paged_set_page(); + EXPECT_THAT(get_packet(), ElementsAre(0b01111101, 0b00000001, 0b01111100)); + EXPECT_TRUE(pkt_.packet_header.send_long_preamble); + EXPECT_TRUE(check_checksum()); + + pkt_.set_dcc_svc_paged_write_reg(3, 0xaa); + EXPECT_THAT(get_packet(), ElementsAre(0b01111011, 0xaa, _)); + EXPECT_TRUE(check_checksum()); +} + +TEST_F(PacketTest, SvcProgPagedVerify) +{ + pkt_.set_dcc_svc_paged_set_page(55); + EXPECT_THAT(get_packet(), ElementsAre(0b01111101, 55, _)); + EXPECT_TRUE(pkt_.packet_header.send_long_preamble); + EXPECT_TRUE(check_checksum()); + + pkt_.set_dcc_svc_paged_verify_reg(0, 0x34); + EXPECT_THAT(get_packet(), ElementsAre(0b01110000, 0x34, _)); + EXPECT_TRUE(pkt_.packet_header.send_long_preamble); + EXPECT_TRUE(check_checksum()); +} + +TEST_F(PacketTest, LogonEnable) +{ + pkt_.set_dcc_logon_enable(Defs::LogonEnableParam::LOCO, 0x55aa, 0x31); + EXPECT_THAT(get_packet(), ElementsAre(254, 0b11111101, 0x55, 0xaa, 0x31, _)); + EXPECT_TRUE(check_checksum()); +} + +TEST_F(PacketTest, SelectInfo) +{ + constexpr uint64_t decoder_id = 0x39944332211ull; + pkt_.set_dcc_select_shortinfo(decoder_id); + EXPECT_THAT(get_packet(), + ElementsAre( + 254, 0b11010000 | 0x3, 0x99, 0x44, 0x33, 0x22, 0x11, 0xff, _, _)); + EXPECT_TRUE(check_crc()); + EXPECT_TRUE(check_checksum()); +} + +TEST_F(PacketTest, LogonAssign) +{ + constexpr uint64_t decoder_id = 0x39944332211ull; + pkt_.set_dcc_logon_assign(decoder_id, 0x8233); + // There are too many bytes here to use elementsare, this is the longer + // syntax. + ::testing::Matcher exp[] = { + 254, 0b11100000 | 0x3, 0x99, 0x44, 0x33, 0x22, 0x11, 0x42, 0x33, _, _}; + EXPECT_THAT(get_packet(), ::testing::ElementsAreArray(exp)); + EXPECT_TRUE(check_crc()); + EXPECT_TRUE(check_checksum()); +} + TEST_F(PacketTest, MMOld) { pkt_.start_mm_packet(); @@ -355,12 +568,15 @@ protected: EXPECT_CALL(loop_, unregister_source(&train_)); } + /// Requests a refresh packet from the train. void do_refresh() { new (&pkt_) Packet(); train_.get_next_packet(0, &pkt_); } + /// Requests the packet from the train that the last code was generated + /// for. void do_callback() { Mock::VerifyAndClear(&loop_); @@ -455,6 +671,45 @@ TEST_F(Train28Test, ZeroSpeed) EXPECT_THAT(get_packet(), ElementsAre(55, 0b01100000, _)); } +/// Verifies that the train correctly remembers that it was commanded to estop. +TEST_F(Train28Test, EstopSaved) +{ + SpeedType s; + s.set_mph(128); + code_ = 0; + EXPECT_CALL(loop_, send_update(&train_, _)) + .WillRepeatedly(SaveArg<1>(&code_)); + + // First make the train move. + train_.set_speed(s); + EXPECT_EQ(DccTrainUpdateCode::SPEED, code_); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b01111111, _)); + EXPECT_FALSE(train_.get_emergencystop()); + EXPECT_NEAR(128, train_.get_speed().mph(), 0.1); + + // Then estop the train. + EXPECT_FALSE(train_.get_emergencystop()); + train_.set_emergencystop(); + EXPECT_EQ(DccTrainUpdateCode::ESTOP, code_); + EXPECT_TRUE(train_.get_emergencystop()); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b01100001, _)); + // Checks that the train knows it's estopped and the speed is reported as + // zero. + EXPECT_TRUE(train_.get_emergencystop()); + EXPECT_NEAR(0, train_.get_speed().mph(), 0.1); + + // Move the train again. + s = 37.5; + train_.set_speed(s); + EXPECT_EQ(DccTrainUpdateCode::SPEED, code_); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b01101011, _)); + EXPECT_FALSE(train_.get_emergencystop()); + EXPECT_NEAR(37.5, train_.get_speed().speed(), 0.1); +} + TEST_F(Train28Test, RefreshLoop) { EXPECT_CALL(loop_, send_update(&train_, _)).Times(AtLeast(1)); @@ -522,9 +777,25 @@ TEST_F(Train28Test, Function28) EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011111, 0x80, _)); } +TEST_F(Train28Test, Function47) +{ + EXPECT_CALL(loop_, send_update(&train_, _)).WillOnce(SaveArg<1>(&code_)); + train_.set_fn(47, 1); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011010, 0b100, _)); +} + +TEST_F(Train28Test, Function68) +{ + EXPECT_CALL(loop_, send_update(&train_, _)).WillOnce(SaveArg<1>(&code_)); + train_.set_fn(68, 1); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b11011100, 0x80, _)); +} + TEST_F(Train28Test, AllFunctions) { - for (int a = 0; a <= 28; ++a) + for (int a = 0; a <= 68; ++a) { EXPECT_CALL(loop_, send_update(&train_, _)) .WillOnce(SaveArg<1>(&code_)); @@ -562,4 +833,102 @@ TEST_F(Train28Test, isSmall) // bits would fit into the cracks. } +class Train128Test : public PacketTest +{ +protected: + Train128Test() + : code_(0) + , regExpect_(&loop_, &train_) + , train_(DccShortAddress(55)) + { + } + + ~Train128Test() + { + EXPECT_CALL(loop_, unregister_source(&train_)); + } + + /// Requests a refresh packet from the train. + void do_refresh() + { + new (&pkt_) Packet(); + train_.get_next_packet(0, &pkt_); + } + + /// Requests the packet from the train that the last code was generated + /// for. + void do_callback() + { + Mock::VerifyAndClear(&loop_); + new (&pkt_) Packet(); + train_.get_next_packet(code_, &pkt_); + code_ = 0; + } + + unsigned code_; + StrictMock loop_; + // We use a constructor trick here to expect the registration expectation + // at the right time. + struct AddRegisterExpect + { + AddRegisterExpect(StrictMock *l, Dcc128Train *t) + { + EXPECT_CALL(*l, register_source(t)); + } + } regExpect_; + Dcc128Train train_; +}; + +/// Tests that all reverse speed steps are correctly generated to the track. +TEST_F(Train128Test, SetSpeedReverse) +{ + for (int mph = 1; mph <= 126; mph++) + { + string ss = StringPrintf("mph=%d", mph); + SCOPED_TRACE(ss); + EXPECT_CALL(loop_, send_update(&train_, _)) + .WillOnce(SaveArg<1>(&code_)); + SpeedType s; + s.set_mph(mph); + s.reverse(); + auto w = s.get_wire(); + s.set_wire(w); + train_.set_speed(s); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b00111111, mph + 1, _)); + } +} + +/// Tests that all forward speed steps are correctly generated to the track. +TEST_F(Train128Test, SetSpeedForward) +{ + for (int mph = 1; mph <= 126; mph++) + { + string ss = StringPrintf("mph=%d", mph); + SCOPED_TRACE(ss); + EXPECT_CALL(loop_, send_update(&train_, _)) + .WillOnce(SaveArg<1>(&code_)); + SpeedType s; + s.set_mph(mph); + auto w = s.get_wire(); + s.set_wire(w); + train_.set_speed(s); + do_callback(); + EXPECT_THAT( + get_packet(), ElementsAre(55, 0b00111111, 0x80 | (mph + 1), _)); + } +} + +TEST_F(Train128Test, MaxSpeed) +{ + EXPECT_CALL(loop_, send_update(&train_, _)).WillOnce(SaveArg<1>(&code_)); + SpeedType s; + s.set_mph(128); + s.reverse(); + train_.set_speed(s); + do_callback(); + EXPECT_THAT(get_packet(), ElementsAre(55, 0b00111111, 0x7F, _)); +} + + } // namespace dcc diff --git a/src/dcc/Packet.hxx b/src/dcc/Packet.hxx index 072ab478e..f26d9a134 100644 --- a/src/dcc/Packet.hxx +++ b/src/dcc/Packet.hxx @@ -39,6 +39,7 @@ #include #include "dcc/Address.hxx" +#include "dcc/Defs.hxx" #include "dcc/packet.h" namespace dcc @@ -57,6 +58,9 @@ struct Packet : public DCCPacket * for marklin-14-step speed commands. */ static const unsigned CHANGE_DIR = DCC_PACKET_EMERGENCY_STOP; + /** Used for page-preset packets. */ + static const unsigned PAGE_REGISTER_ID = 0b101; + Packet() { clear(); @@ -179,11 +183,34 @@ struct Packet : public DCCPacket /** Adds a DCC function group command to the packet. The lowest numbered * function is always at bit zero. @param values are bitmask of functions * to send to the loco. */ - void add_dcc_function13_20(unsigned values); + void add_dcc_function13_20(unsigned values) + { + add_dcc_function_hi(13, values); + } /** Adds a DCC function group command to the packet. The lowest numbered * function is always at bit zero. @param values are bitmask of functions * to send to the loco. */ - void add_dcc_function21_28(unsigned values); + void add_dcc_function21_28(unsigned values) + { + add_dcc_function_hi(21, values); + } + /** Adds a DCC function group command to the packet. The lowest numbered + * function is always at bit zero. + * @param base is a valid function number base, 13, 21, 29, 37, 45, 53 + * or 61. + * @param values are bitmask of functions to send to the loco. */ + void add_dcc_function_hi(uint8_t base, uint8_t values); + + /** Adds a DCC binary state control command to the packet. Automatically + * picks the short or long form, depending on the range of the argument. + * @param fn is a binary function variable, 0 to 32767. + * @param value true/false, what to set to. */ + void add_dcc_binary_state(uint16_t fn, bool value); + + /** Adds a DCC analog function control command to the packet. + * @param fn is an analog function variable, 0 to 255. + * @param value to set it to, 0 to 255. */ + void add_dcc_analog_function(uint8_t fn, uint8_t value); /** Helper function for adding programming mode packets. */ void add_dcc_prog_command( @@ -230,6 +257,35 @@ struct Packet : public DCCPacket * @param desired is true if bit:=1 should be written */ void set_dcc_svc_write_bit(unsigned cv_number, unsigned bit, bool desired); + /** Sets the packet to a DCC service mode packet in Paged Mode, setting the + * page register. This function does not need a DCC address. Includes the + * checksum. + * @param page Page to set, 1 is the default page, zero is reserved, 255 + * max. + */ + void set_dcc_svc_paged_set_page(unsigned page = 1) + { + set_dcc_svc_paged_write_reg(PAGE_REGISTER_ID, page); + } + + /** Sets the packet to a DCC service mode packet in Paged Mode, setting any + * register. This function does not need a DCC address. Includes the + * checksum. + * @param reg register, 0 to 7. On the default page register 0 is CV1 + * (address). + * @param value Payload to write to that register. 0 to 255. + */ + void set_dcc_svc_paged_write_reg(uint8_t reg, uint8_t value); + + /** Sets the packet to a DCC service mode packet in Paged Mode, setting the + * page register. This function does not need a DCC address. Includes the + * checksum. + * @param reg register, 0 to 7. On the default page register 0 is CV1 + * (address). + * @param value Payload to check on that register. + */ + void set_dcc_svc_paged_verify_reg(uint8_t reg, uint8_t value); + /** Adds a DCC basic accessory decoder command packet and the checksum * byte. * @param address is the unencoded 12-bit address, containing both the A @@ -241,7 +297,24 @@ struct Packet : public DCCPacket * closed or thrown. */ void add_dcc_basic_accessory(unsigned address, bool is_activate); + + /// Sets the packet to a logon enable packet. + /// @param param defines which decoders should be requested to logon. + /// @param cid the command station unique ID hashed. + /// @param session_id is the session id of the current power cycle. + void set_dcc_logon_enable( + Defs::LogonEnableParam param, uint16_t cid, uint8_t session_id); + + /// Sets the packet to a Select+GetShortInfo packet. + /// @param decoder_id unique ID of the decoder (44 bits at LSB). + void set_dcc_select_shortinfo(uint64_t decoder_id); + /// Sets the packet to a logon assign packet. + /// @param decoder_id unique ID of the decoder (44 bits at LSB). + /// @param address address to assign. Normally 14 bits, but the two high + /// bits are reserved for option bits (these will be output inverted). + void set_dcc_logon_assign(uint64_t decoder_id, uint16_t address); + /** Appends one byte to the packet payload that represents the XOR checksum * for DCC. */ void add_dcc_checksum(); diff --git a/src/dcc/PacketProcessor.hxx b/src/dcc/PacketProcessor.hxx new file mode 100644 index 000000000..96448cd2f --- /dev/null +++ b/src/dcc/PacketProcessor.hxx @@ -0,0 +1,54 @@ +/** \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 PacketProcessor.hxx + * + * Interface used for processing DCC packets to generate the feedback data. + * + * @author Balazs Racz + * @date 10 July 2021 + */ + +#include "dcc/packet.h" + +class RailcomDriver; + +namespace dcc +{ + +/// Abstract class that is used as a plugin in the DCC decoder. The application +/// logic can implement this class and it will be called from the +/// interrupt. This is necessary to correctly generate the RailCom feedback to +/// be sent back. +class PacketProcessor +{ +public: + /// Called in an OS interrupt with the arrived packet. + virtual void packet_arrived( + const DCCPacket *pkt, RailcomDriver *railcom) = 0; +}; + +} // namespace dcc diff --git a/src/dcc/ProgrammingTrackBackend.hxx b/src/dcc/ProgrammingTrackBackend.hxx index 6d978b640..b556dcd7d 100644 --- a/src/dcc/ProgrammingTrackBackend.hxx +++ b/src/dcc/ProgrammingTrackBackend.hxx @@ -50,6 +50,9 @@ extern "C" { void enable_dcc(); } +// If defined, adds instrumentation calls to a logging function. +// #define DEBUG_PROGRAMTRACK_BACKEND + struct ProgrammingTrackRequest : public CallableFlowRequestBase { enum EnterServiceMode @@ -161,6 +164,9 @@ private: } }; +extern void progdebug_log_packet(dcc::Packet *pkt); +extern void progdebug_log_string(const char *s); + class ProgrammingTrackBackend : public CallableFlow, private dcc::NonTrainPacketSource, public Singleton @@ -346,10 +352,16 @@ private: if (request() == nullptr) { packet->set_dcc_reset_all_decoders(); +#ifdef DEBUG_PROGRAMTRACK_BACKEND + progdebug_log_packet(packet); +#endif return; } *packet = request()->packetToSend_; +#ifdef DEBUG_PROGRAMTRACK_BACKEND + progdebug_log_packet(packet); +#endif if (request()->repeatCount_ > 0) { --request()->repeatCount_; @@ -358,6 +370,9 @@ private: { if (isWaitingForPackets_) { +#ifdef DEBUG_PROGRAMTRACK_BACKEND + progdebug_log_string("done flush"); +#endif isWaitingForPackets_ = 0; /// @todo: wait for flushing the packets to the track. // resume flow diff --git a/src/dcc/RailCom.cxx b/src/dcc/RailCom.cxx index cd6981c2e..25db547ab 100644 --- a/src/dcc/RailCom.cxx +++ b/src/dcc/RailCom.cxx @@ -35,48 +35,116 @@ #include #include "dcc/RailCom.hxx" +#include "utils/Crc.hxx" namespace dcc { -using RailcomDefs::INV; -using RailcomDefs::ACK; -using RailcomDefs::NACK; -using RailcomDefs::BUSY; -using RailcomDefs::RESVD1; -using RailcomDefs::RESVD2; -using RailcomDefs::RESVD3; +static constexpr uint8_t INV = RailcomDefs::INV; +static constexpr uint8_t ACK = RailcomDefs::ACK; +static constexpr uint8_t NACK = RailcomDefs::NACK; +static constexpr uint8_t BUSY = RailcomDefs::BUSY; +static constexpr uint8_t RESVD1 = RailcomDefs::RESVD1; +static constexpr uint8_t RESVD2 = RailcomDefs::RESVD2; const uint8_t railcom_decode[256] = -{ INV, INV, INV, INV, INV, INV, INV, INV, - INV, INV, INV, INV, INV, INV, INV, NACK, - INV, INV, INV, INV, INV, INV, INV, 0x33, - INV, INV, INV, 0x34, INV, 0x35, 0x36, INV, - INV, INV, INV, INV, INV, INV, INV, 0x3A, - INV, INV, INV, 0x3B, INV, 0x3C, 0x37, INV, - INV, INV, INV, 0x3F, INV, 0x3D, 0x38, INV, - INV, 0x3E, 0x39, INV, RESVD3, INV, INV, INV, - INV, INV, INV, INV, INV, INV, INV, 0x24, - INV, INV, INV, 0x23, INV, 0x22, 0x21, INV, - INV, INV, INV, 0x1F, INV, 0x1E, 0x20, INV, - INV, 0x1D, 0x1C, INV, 0x1B, INV, INV, INV, - INV, INV, INV, 0x19, INV, 0x18, 0x1A, INV, - INV, 0x17, 0x16, INV, 0x15, INV, INV, INV, - INV, 0x25, 0x14, INV, 0x13, INV, INV, INV, - 0x32, INV, INV, INV, INV, INV, INV, INV, - INV, INV, INV, INV, INV, INV, INV, RESVD2, - INV, INV, INV, 0x0E, INV, 0x0D, 0x0C, INV, - INV, INV, INV, 0x0A, INV, 0x09, 0x0B, INV, - INV, 0x08, 0x07, INV, 0x06, INV, INV, INV, - INV, INV, INV, 0x04, INV, 0x03, 0x05, INV, - INV, 0x02, 0x01, INV, 0x00, INV, INV, INV, - INV, 0x0F, 0x10, INV, 0x11, INV, INV, INV, - 0x12, INV, INV, INV, INV, INV, INV, INV, - INV, INV, INV, RESVD1, INV, 0x2B, 0x30, INV, - INV, 0x2A, 0x2F, INV, 0x31, INV, INV, INV, - INV, 0x29, 0x2E, INV, 0x2D, INV, INV, INV, - 0x2C, INV, INV, INV, INV, INV, INV, INV, - INV, BUSY, 0x28, INV, 0x27, INV, INV, INV, - 0x26, INV, INV, INV, INV, INV, INV, INV, - ACK, INV, INV, INV, INV, INV, INV, INV, - INV, INV, INV, INV, INV, INV, INV, INV, + // 0|8 1|9 2|a 3|b 4|c 5|d 6|e 7|f +{ INV, INV, INV, INV, INV, INV, INV, INV, // 0 + INV, INV, INV, INV, INV, INV, INV, ACK, // 0 + INV, INV, INV, INV, INV, INV, INV, 0x33, // 1 + INV, INV, INV, 0x34, INV, 0x35, 0x36, INV, // 1 + INV, INV, INV, INV, INV, INV, INV, 0x3A, // 2 + INV, INV, INV, 0x3B, INV, 0x3C, 0x37, INV, // 2 + INV, INV, INV, 0x3F, INV, 0x3D, 0x38, INV, // 3 + INV, 0x3E, 0x39, INV, NACK, INV, INV, INV, // 3 + INV, INV, INV, INV, INV, INV, INV, 0x24, // 4 + INV, INV, INV, 0x23, INV, 0x22, 0x21, INV, // 4 + INV, INV, INV, 0x1F, INV, 0x1E, 0x20, INV, // 5 + INV, 0x1D, 0x1C, INV, 0x1B, INV, INV, INV, // 5 + INV, INV, INV, 0x19, INV, 0x18, 0x1A, INV, // 6 + INV, 0x17, 0x16, INV, 0x15, INV, INV, INV, // 6 + INV, 0x25, 0x14, INV, 0x13, INV, INV, INV, // 7 + 0x32, INV, INV, INV, INV, INV, INV, INV, // 7 + INV, INV, INV, INV, INV, INV, INV, RESVD2, // 8 + INV, INV, INV, 0x0E, INV, 0x0D, 0x0C, INV, // 8 + INV, INV, INV, 0x0A, INV, 0x09, 0x0B, INV, // 9 + INV, 0x08, 0x07, INV, 0x06, INV, INV, INV, // 9 + INV, INV, INV, 0x04, INV, 0x03, 0x05, INV, // a + INV, 0x02, 0x01, INV, 0x00, INV, INV, INV, // a + INV, 0x0F, 0x10, INV, 0x11, INV, INV, INV, // b + 0x12, INV, INV, INV, INV, INV, INV, INV, // b + INV, INV, INV, RESVD1, INV, 0x2B, 0x30, INV, // c + INV, 0x2A, 0x2F, INV, 0x31, INV, INV, INV, // c + INV, 0x29, 0x2E, INV, 0x2D, INV, INV, INV, // d + 0x2C, INV, INV, INV, INV, INV, INV, INV, // d + INV, BUSY, 0x28, INV, 0x27, INV, INV, INV, // e + 0x26, INV, INV, INV, INV, INV, INV, INV, // e + ACK, INV, INV, INV, INV, INV, INV, INV, // f + INV, INV, INV, INV, INV, INV, INV, INV, // f +}; + +const uint8_t railcom_encode[64] = { + 0b10101100, + 0b10101010, + 0b10101001, + 0b10100101, + 0b10100011, + 0b10100110, + 0b10011100, + 0b10011010, + 0b10011001, + 0b10010101, + 0b10010011, + 0b10010110, + 0b10001110, + 0b10001101, + 0b10001011, + 0b10110001, + 0b10110010, + 0b10110100, + 0b10111000, + 0b01110100, + 0b01110010, + 0b01101100, + 0b01101010, + 0b01101001, + 0b01100101, + 0b01100011, + 0b01100110, + 0b01011100, + 0b01011010, + 0b01011001, + 0b01010101, + 0b01010011, + 0b01010110, + 0b01001110, + 0b01001101, + 0b01001011, + 0b01000111, + 0b01110001, + 0b11101000, + 0b11100100, + 0b11100010, + 0b11010001, + 0b11001001, + 0b11000101, + 0b11011000, + 0b11010100, + 0b11010010, + 0b11001010, + 0b11000110, + 0b11001100, + 0b01111000, + 0b00010111, + 0b00011011, + 0b00011101, + 0b00011110, + 0b00101110, + 0b00110110, + 0b00111010, + 0b00100111, + 0b00101011, + 0b00101101, + 0b00110101, + 0b00111001, + 0b00110011, }; /// Helper function to parse a part of a railcom packet. @@ -159,6 +227,13 @@ void parse_internal(uint8_t fb_channel, uint8_t railcom_channel, len = 2; } break; + case RMOB_XPOM0: + case RMOB_XPOM1: + case RMOB_XPOM2: + case RMOB_XPOM3: + type = RailcomPacket::MOB_XPOM0 + (packet_id - RMOB_XPOM0); + len = 6; + break; case RMOB_DYN: type = RailcomPacket::MOB_DYN; len = 3; @@ -226,4 +301,60 @@ void parse_railcom_data( } } +// static +void RailcomDefs::add_did_feedback(uint64_t decoder_id, Feedback *fb) +{ + fb->ch1Size = 2; + fb->ch2Size = 6; + append12( + RMOB_LOGON_ENABLE_FEEDBACK, (decoder_id >> 36) & 0xff, fb->ch1Data); + append36((decoder_id >> 32) & 0xf, (decoder_id & 0xffffffffu), fb->ch2Data); +} + +// static +void RailcomDefs::add_shortinfo_feedback(uint16_t requested_address, + uint8_t max_fn, uint8_t psupp, uint8_t ssupp, Feedback *fb) +{ + Crc8DallasMaxim m; + requested_address &= 0x3FFF; + requested_address |= 0x8000; + m.update16(requested_address >> 8); + m.update16(requested_address & 0xff); + m.update16(max_fn); + m.update16(psupp); + m.update16(ssupp); + fb->ch1Size = 2; + fb->ch2Size = 6; + append12( + requested_address >> 12, (requested_address >> 4) & 0xff, fb->ch1Data); + uint32_t lp = (uint32_t(max_fn) << 24) | (uint32_t(psupp) << 16) | + (uint32_t(ssupp) << 8) | m.get(); + append36(requested_address & 0xf, lp, fb->ch2Data); +} + +// static +void RailcomDefs::add_assign_feedback(uint8_t changeflags, uint16_t changecount, + uint8_t supp2, uint8_t supp3, Feedback *fb) +{ + changecount &= 0xFFF; + fb->ch1Size = 2; + fb->ch2Size = 6; + + Crc8DallasMaxim m; + uint8_t h = (RMOB_LOGON_ASSIGN_FEEDBACK << 4) | (changeflags >> 4); + m.update16(h); + h = ((changeflags & 0xf) << 4) | (changecount >> 8); + m.update16(h); + append12(RMOB_LOGON_ASSIGN_FEEDBACK, changeflags, fb->ch1Data); + + h = changecount & 0xff; + m.update16(h); + m.update16(supp2); + m.update16(supp3); + + uint32_t lp = + ((changecount & 0xff) << 24) | (supp2 << 16) | (supp3 << 8) | m.get(); + append36(changecount >> 8, lp, fb->ch2Data); +} + } // namespace dcc diff --git a/src/dcc/RailCom.cxxtest b/src/dcc/RailCom.cxxtest index 7e026e3ca..de95134b2 100644 --- a/src/dcc/RailCom.cxxtest +++ b/src/dcc/RailCom.cxxtest @@ -82,13 +82,29 @@ TEST_F(RailcomDecodeTest, SimpleAck) { } TEST_F(RailcomDecodeTest, MultipleAckNackBusy) { - fb_.add_ch1_data(0xF0); - fb_.add_ch1_data(0xE1); - fb_.add_ch2_data(0x0F); + fb_.add_ch1_data(0xF0); // one type of ack + fb_.add_ch1_data(0xE1); // NMRA busy + fb_.add_ch2_data(0x3C); // RCN nack + fb_.add_ch2_data(0x0F); // other ack decode(); - EXPECT_THAT(output_, ElementsAre(RailcomPacket(3, 1, RailcomPacket::ACK, 0), - RailcomPacket(3, 1, RailcomPacket::BUSY, 0), - RailcomPacket(3, 2, RailcomPacket::NACK, 0))); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 1, RailcomPacket::ACK, 0), + RailcomPacket(3, 1, RailcomPacket::BUSY, 0), + RailcomPacket(3, 2, RailcomPacket::NACK, 0), + RailcomPacket(3, 2, RailcomPacket::ACK, 0))); +} + +TEST_F(RailcomDecodeTest, MultipleAckNackBusyCode) { + fb_.add_ch1_data(RailcomDefs::CODE_ACK); // one type of ack + fb_.add_ch1_data(RailcomDefs::CODE_BUSY); // NMRA busy + fb_.add_ch2_data(RailcomDefs::CODE_NACK); // RCN nack + fb_.add_ch2_data(RailcomDefs::CODE_ACK2); // other ack + decode(); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 1, RailcomPacket::ACK, 0), + RailcomPacket(3, 1, RailcomPacket::BUSY, 0), + RailcomPacket(3, 2, RailcomPacket::NACK, 0), + RailcomPacket(3, 2, RailcomPacket::ACK, 0))); } TEST_F(RailcomDecodeTest, Ch2Ext) { @@ -109,6 +125,17 @@ TEST_F(RailcomDecodeTest, Ch2ExtAndFeedback) { RailcomPacket(3, 2, RailcomPacket::MOB_POM, 0xA5))); } +TEST_F(RailcomDecodeTest, Ch1Bcast) { + fb_.add_ch1_data(0xA3); + fb_.add_ch1_data(0xAC); + fb_.add_ch2_data(0x99); + fb_.add_ch2_data(0xa5); + decode(); + EXPECT_THAT( + output_, ElementsAre(RailcomPacket(3, 1, RailcomPacket::MOB_ADRHIGH, 0), + RailcomPacket(3, 2, RailcomPacket::MOB_ADRLOW, 3))); +} + TEST_F(RailcomDecodeTest, ChannelBoundaryProblem) { fb_.add_ch1_data(0x8b); // note wrong channel assignment fb_.add_ch2_data(0xac); @@ -124,4 +151,119 @@ TEST_F(RailcomDecodeTest, FalseChannelBoundaryProblem) { EXPECT_THAT(output_, ElementsAre(RailcomPacket(3, 1, RailcomPacket::GARBAGE, 0), RailcomPacket(3, 2, RailcomPacket::MOB_EXT, 128))); } +// Verifies that the railcom encode and railcom decode table are inverse of +// each other. +TEST(RailcomEncodeTest, BitsMatch) { + for (unsigned i = 0; i < 63; i++) + { + uint8_t encoded = railcom_encode[i]; + uint8_t decoded = railcom_decode[encoded]; + EXPECT_EQ(i, decoded); + } + EXPECT_EQ(RailcomDefs::ACK, railcom_decode[RailcomDefs::CODE_ACK]); + EXPECT_EQ(RailcomDefs::ACK, railcom_decode[RailcomDefs::CODE_ACK2]); + EXPECT_EQ(RailcomDefs::NACK, railcom_decode[RailcomDefs::CODE_NACK]); +} + +// Verifies that the railcom encode 12 command works correctly. +TEST_F(RailcomDecodeTest, Encode12) { + uint16_t d = RailcomDefs::encode12(RMOB_ADRHIGH, 42); + fb_.add_ch1_data(d>>8); + fb_.add_ch1_data(d & 0xff); + decode(); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 1, RailcomPacket::MOB_ADRHIGH, 42))); +} + +// Verifies that the railcom encode 12 command works correctly. +TEST_F(RailcomDecodeTest, Append12) { + RailcomDefs::append12(RMOB_ADRHIGH, 42, fb_.ch1Data); + fb_.ch1Size = 2; + decode(); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 1, RailcomPacket::MOB_ADRHIGH, 42))); +} + +// Verifies that the railcom encode 36 command works correctly. +TEST_F(RailcomDecodeTest, Append36) { + RailcomDefs::append36(RMOB_XPOM2, 0xfedc5432, fb_.ch2Data); + fb_.ch2Size = 6; + decode(); + EXPECT_THAT(output_, + ElementsAre(RailcomPacket(3, 2, RailcomPacket::MOB_XPOM2, 0xfedc5432u))); +} + +TEST_F(RailcomDecodeTest, DecoderId) +{ + uint64_t decoder_id = 0x19911223344ull; + RailcomDefs::add_did_feedback(decoder_id, &fb_); + + EXPECT_EQ(2u, fb_.ch1Size); + EXPECT_EQ(6u, fb_.ch2Size); + uint8_t d[2]; + RailcomDefs::append12(0xf, 0x19, d); + EXPECT_EQ(d[0], fb_.ch1Data[0]); + EXPECT_EQ(d[1], fb_.ch1Data[1]); + + RailcomDefs::append12(0x9, 0x11, d); + EXPECT_EQ(d[0], fb_.ch2Data[0]); + EXPECT_EQ(d[1], fb_.ch2Data[1]); + + RailcomDefs::append12(0x2, 0x23, d); + EXPECT_EQ(d[0], fb_.ch2Data[2]); + EXPECT_EQ(d[1], fb_.ch2Data[3]); + + RailcomDefs::append12(0x3, 0x44, d); + EXPECT_EQ(d[0], fb_.ch2Data[4]); + EXPECT_EQ(d[1], fb_.ch2Data[5]); +} + +TEST_F(RailcomDecodeTest, ShortInfo) +{ + RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_); + EXPECT_EQ(2u, fb_.ch1Size); + EXPECT_EQ(6u, fb_.ch2Size); + uint8_t d[2]; + RailcomDefs::append12(0x8 | 0x1, 0x38, d); + EXPECT_EQ(d[0], fb_.ch1Data[0]); + EXPECT_EQ(d[1], fb_.ch1Data[1]); + + RailcomDefs::append12(0x2, 0x55, d); + EXPECT_EQ(d[0], fb_.ch2Data[0]); + EXPECT_EQ(d[1], fb_.ch2Data[1]); + + RailcomDefs::append12(0xa, 0x70, d); + EXPECT_EQ(d[0], fb_.ch2Data[2]); + EXPECT_EQ(d[1], fb_.ch2Data[3]); + + // The CRC of this example is 0x0b. + RailcomDefs::append12(0x3, 0x0b, d); + EXPECT_EQ(d[0], fb_.ch2Data[4]); + EXPECT_EQ(d[1], fb_.ch2Data[5]); +} + +TEST_F(RailcomDecodeTest, AssignFeedback) +{ + RailcomDefs::add_assign_feedback(0x5a, 0x327, 0x18, 0x22, &fb_); + EXPECT_EQ(2u, fb_.ch1Size); + EXPECT_EQ(6u, fb_.ch2Size); + uint8_t d[2]; + RailcomDefs::append12(13, 0x5a, d); + EXPECT_EQ(d[0], fb_.ch1Data[0]); + EXPECT_EQ(d[1], fb_.ch1Data[1]); + + RailcomDefs::append12(0x3, 0x27, d); + EXPECT_EQ(d[0], fb_.ch2Data[0]); + EXPECT_EQ(d[1], fb_.ch2Data[1]); + + RailcomDefs::append12(0x1, 0x82, d); + EXPECT_EQ(d[0], fb_.ch2Data[2]); + EXPECT_EQ(d[1], fb_.ch2Data[3]); + + // The CRC of this example is 0x46. + RailcomDefs::append12(0x2, 0x46, d); + EXPECT_EQ(d[0], fb_.ch2Data[4]); + EXPECT_EQ(d[1], fb_.ch2Data[5]); +} + } // namespace dcc diff --git a/src/dcc/RailCom.hxx b/src/dcc/RailCom.hxx index 9a41f473d..3ffd5ce56 100644 --- a/src/dcc/RailCom.hxx +++ b/src/dcc/RailCom.hxx @@ -79,33 +79,117 @@ struct Feedback : public DCCFeedback /// Formats a dcc::Feedback message into a debug string. std::string railcom_debug(const Feedback& fb); -/// Special constant values returned by the @ref railcom_decode[] array. -namespace RailcomDefs -{ - /// invalid value (not conforming to the 4bit weighting requirement) - static const uint8_t INV = 0xff; - /// Railcom ACK; the decoder received the message ok. NOTE: some early - /// software versions may have ACK and NACK exchanged. - static const uint8_t ACK = 0xfe; - /// The decoder rejected the packet. - static const uint8_t NACK = 0xfd; - /// The decoder is busy; send the packet again. This is typically returned - /// when a POM CV write is still pending; the caller must re-try sending the - /// packet later. - static const uint8_t BUSY = 0xfc; - /// Reserved for future expansion. - static const uint8_t RESVD1 = 0xfb; - /// Reserved for future expansion. - static const uint8_t RESVD2 = 0xfa; - /// Reserved for future expansion. - static const uint8_t RESVD3 = 0xf8; -} - /** Table for 8-to-6 decoding of railcom data. This table can be indexed by the * 8-bit value read from the railcom channel, and the return value will be * either a 6-bit number, or one of the constants in @ref RailcomDefs. If the * value is invalid, the INV constant is returned. */ extern const uint8_t railcom_decode[256]; +/// Table for 6-to-8 encoding of railcom data. The table can be indexed by a +/// 6-bit value that is the semantic content of a railcom byte, and returns the +/// matching 8-bit value to put out on the UART. This table only contains the +/// standard codes, for the special codes like ACK use RailcomDefs::ACK. +extern const uint8_t railcom_encode[64]; + +/// Special constant values returned by the @ref railcom_decode[] array. +struct RailcomDefs +{ + // These values appear in the railcom_decode table to mean special symbols. + enum + { + /// invalid value (not conforming to the 4bit weighting requirement) + INV = 0xff, + /// Railcom ACK; the decoder received the message ok. NOTE: There are + /// two codepoints that map to this. + ACK = 0xfe, + /// The decoder rejected the packet. + NACK = 0xfd, + /// The decoder is busy; send the packet again. This is typically + /// returned when a POM CV write is still pending; the caller must + /// re-try sending the packet later. + BUSY = 0xfc, + + /// Reserved for future expansion. + RESVD1 = 0xfb, + /// Reserved for future expansion. + RESVD2 = 0xfa, + }; + + // These values need to be sent on the UART + enum + { + /// Code point for ACK (according to RCN-217) + CODE_ACK = 0xf0, + /// Another accepted code point for ACK (according to RCN-217) + CODE_ACK2 = 0x0f, + /// Code point for NACK (according to RCN-217) + CODE_NACK = 0x3c, + /// Code point for BUSY (according to NMRA S-9.3.2) + CODE_BUSY = 0xE1, + }; + + /// Encodes 12 bits of useful payload into 16 bits of UART data to transmit. + /// @param nibble top 4 bits of the payload to send + /// @param data bottom 8 bits of payload to send. + /// @return the uart bytes, first byte in the high 8 bits, second byte in + /// the low 8 bits. + static uint16_t encode12(uint8_t nibble, uint8_t data) + { + return (railcom_encode[((nibble << 2) | (data >> 6)) & 0x3F] << 8) | + railcom_encode[data & 0x3f]; + } + + /// Encodes 12 bits of useful payload into 16 bits of UART data to transmit. + /// @param nibble top 4 bits of the payload to send + /// @param data bottom 8 bits of payload to send. + /// @param dst this is where the payload will be stored. + static void append12(uint8_t nibble, uint8_t data, uint8_t* dst) + { + *dst++ = railcom_encode[((nibble << 2) | (data >> 6)) & 0x3F]; + *dst++ = railcom_encode[data & 0x3f]; + } + + /// Encodes a 36-bit railcom datagram into UART bytes. + /// @param nibble the railcom ID (top 4 bits) + /// @param data the 32 bit payload. Will be transmitted MSbyte-first. + /// @param dst this is where the payload will be stored. + static void append36(uint8_t nibble, uint32_t data, uint8_t* dst) + { + *dst++ = railcom_encode[((nibble << 2) | (data >> 30)) & 0x3F]; + *dst++ = railcom_encode[(data >> 24) & 0x3F]; + *dst++ = railcom_encode[(data >> 18) & 0x3F]; + *dst++ = railcom_encode[(data >> 12) & 0x3F]; + *dst++ = railcom_encode[(data >> 6) & 0x3F]; + *dst++ = railcom_encode[data & 0x3F]; + } + + /// Creates a Logon Enable feedback with the decoder unique ID. + /// @param decoder_id the 44-bit decoder ID (justified to MSb). + /// @param fb the feedback packet to generate. + static void add_did_feedback(uint64_t decoder_id, Feedback *fb); + + /// Creates a ShortInfo feedback. + /// @param requested_address 14-bit encoding of the requested address. + /// @param max_fn maximum supported function (0-255) + /// @param psupp protocol support flags (capabilities[0]) + /// @param ssupp space support flags (capabilities[1]) + /// @param fb the feedback packet to generate. + static void add_shortinfo_feedback(uint16_t requested_address, + uint8_t max_fn, uint8_t psupp, uint8_t ssupp, Feedback *fb); + + /// Creates a Logon Assign feedback. + /// @param changeflags 8 bits of change flags + /// @param changecount 12 bits of changecount + /// @param supp2 protocol support flags (capabilities[2]) + /// @param supp3 protocol support flags (capabilities[3]) + /// @param fb the feedback packet to generate. + static void add_assign_feedback(uint8_t changeflags, uint16_t changecount, + uint8_t supp2, uint8_t supp3, Feedback *fb); + +private: + /// This struct cannot be instantiated. + RailcomDefs(); +}; + /// Packet identifiers from Mobile Decoders. enum RailcomMobilePacketId @@ -115,7 +199,13 @@ enum RailcomMobilePacketId RMOB_ADRLOW = 2, RMOB_EXT = 3, RMOB_DYN = 7, + RMOB_XPOM0 = 8, + RMOB_XPOM1 = 9, + RMOB_XPOM2 = 10, + RMOB_XPOM3 = 11, RMOB_SUBID = 12, + RMOB_LOGON_ASSIGN_FEEDBACK = 13, + RMOB_LOGON_ENABLE_FEEDBACK = 15, }; /// Represents a single Railcom datagram. There can be multiple railcom @@ -134,6 +224,10 @@ struct RailcomPacket MOB_ADRLOW, MOB_EXT, MOB_DYN, + MOB_XPOM0, + MOB_XPOM1, + MOB_XPOM2, + MOB_XPOM3, MOB_SUBID }; /// which detector supplied this data diff --git a/src/dcc/RailcomPortDebug.hxx b/src/dcc/RailcomPortDebug.hxx index dc2e61eb2..ef06c2d09 100644 --- a/src/dcc/RailcomPortDebug.hxx +++ b/src/dcc/RailcomPortDebug.hxx @@ -129,6 +129,72 @@ private: dcc::RailcomHubFlow *parent_; }; +/// This flow listens to Railcom packets coming from the hub, and if they are +/// correctly decoded, pulses the given GPIO output. Correctly decoded is +/// defined as having every single byte be a correct 4/8 codepoint. +class RailcomToGpioFlow : public dcc::RailcomHubPortInterface +{ +public: + /// Constructor. + /// @param source is the railcom hub to listen to. + /// @param output + RailcomToGpioFlow(dcc::RailcomHubFlow *source, const Gpio *output) + : parent_(source) + , output_(output) + { + source->register_port(this); + } + + ~RailcomToGpioFlow() + { + parent_->unregister_port(this); + } + +private: + /// Incoming railcom data. + /// + /// @param d railcom buffer. + /// @param prio priority + void send(Buffer *d, unsigned prio) OVERRIDE + { + AutoReleaseBuffer rb(d); + dcc::Feedback &fb = *d->data(); + if (fb.channel >= 0xfe) + { + // Occupancy feedback, not railcom data. + return; + } + unsigned correct = 0; + unsigned total = 0; + for (unsigned i = 0; i < fb.ch1Size; i++) + { + ++total; + correct += (dcc::railcom_decode[fb.ch1Data[i]] != RailcomDefs::INV) + ? 1 + : 0; + } + for (unsigned i = 0; i < fb.ch2Size; i++) + { + ++total; + correct += (dcc::railcom_decode[fb.ch2Data[i]] != RailcomDefs::INV) + ? 1 + : 0; + } + if (total > 0 && correct == total) + { + // Produces a short pulse on the output + output_->write(true); + for (volatile int i = 0; i < 3000; i++) { } + output_->write(false); + } + } + + /// Flow to which we are registered. + dcc::RailcomHubFlow *parent_; + /// Output gpio to toggle. + const Gpio *output_; +}; // RailcomToGpioFlow + } // namespace dcc namespace openlcb @@ -145,11 +211,14 @@ class RailcomToOpenLCBDebugProxy : public dcc::RailcomHubPort { public: RailcomToOpenLCBDebugProxy(dcc::RailcomHubFlow *parent, Node *node, - dcc::RailcomHubPort *occupancy_port) + dcc::RailcomHubPort *occupancy_port, bool ch1_enabled = true, + bool ack_enabled = true) : dcc::RailcomHubPort(parent->service()) , parent_(parent) , node_(node) , occupancyPort_(occupancy_port) + , ch1Enabled_(ch1_enabled) + , ackEnabled_(ack_enabled) { parent_->register_port(this); } @@ -185,7 +254,7 @@ public: { return release_and_exit(); } - if (message()->data()->ch1Size) + if (message()->data()->ch1Size && ch1Enabled_) { return allocate_and_call( node_->iface()->global_message_write_flow(), @@ -215,7 +284,10 @@ public: Action maybe_send_ch2() { - if (message()->data()->ch2Size) + if (message()->data()->ch2Size && + (ackEnabled_ || + dcc::railcom_decode[message()->data()->ch2Data[0]] != + dcc::RailcomDefs::ACK)) { return allocate_and_call( node_->iface()->global_message_write_flow(), @@ -246,6 +318,10 @@ public: dcc::RailcomHubFlow *parent_{nullptr}; Node *node_; dcc::RailcomHubPort *occupancyPort_; + /// True if we should transmit channel1 data. + uint8_t ch1Enabled_ : 1; + /// True if we should transmit data that starts with an ACK. + uint8_t ackEnabled_ : 1; }; } // namespace openlcb diff --git a/src/dcc/Receiver.hxx b/src/dcc/Receiver.hxx index f941ef2ca..9ee2407fc 100644 --- a/src/dcc/Receiver.hxx +++ b/src/dcc/Receiver.hxx @@ -42,6 +42,8 @@ #include "freertos/can_ioctl.h" #include "freertos_drivers/common/SimpleLog.hxx" +#include "dcc/packet.h" +#include "utils/Crc.hxx" // If defined, collects samples of timing and state into a ring buffer. //#define DCC_DECODER_DEBUG @@ -66,7 +68,7 @@ public: } /// Internal states of the decoding state machine. - enum State + enum State : uint8_t { UNKNOWN, // 0 DCC_PREAMBLE, // 1 @@ -89,6 +91,27 @@ public: return parseState_; } + /// Sets where to write the decoded DCC data to. If this function is not + /// called before a packet preamble starts, the packet will be discarded. + /// @param pkt where to store the incoming payload bytes. This packet will + /// be reset to an empty packet. + void set_packet(DCCPacket *pkt) + { + pkt_ = pkt; + if (pkt_) + { + clear_packet(); + } + } + + /// Retrieves the last applied DCC packet pointer. + /// @return DCC packet, or nullptr if there was no DCC packet pointer + /// assigned yet. + DCCPacket *pkt() + { + return pkt_; + }; + /// Call this function for each time the polarity of the signal changes. /// @param value is the number of clock cycles since the last polarity /// change. @@ -110,12 +133,13 @@ public: parseState_ = DCC_PREAMBLE; return; } - if (timings_[MM_PREAMBLE].match(value)) + if (timings_[MM_PREAMBLE].match(value) && pkt_) { + clear_packet(); + pkt_->packet_header.is_marklin = 1; parseCount_ = 1 << 2; - ofs_ = 0; - data_[ofs_] = 0; parseState_ = MM_DATA; + havePacket_ = true; } break; } @@ -139,8 +163,18 @@ public: { parseState_ = DCC_DATA; parseCount_ = 1 << 7; - ofs_ = 0; - data_[ofs_] = 0; + xorState_ = 0; + crcState_.init(); + if (pkt_) + { + clear_packet(); + havePacket_ = true; + pkt_->packet_header.skip_ec = 1; + } + else + { + havePacket_ = false; + } return; } break; @@ -165,7 +199,10 @@ public: { if (parseCount_) { - data_[ofs_] |= parseCount_; + if (havePacket_) + { + pkt_->payload[pkt_->dlc] |= parseCount_; + } parseCount_ >>= 1; parseState_ = DCC_DATA; return; @@ -173,6 +210,20 @@ public: else { // end of packet 1 bit. + if (havePacket_) + { + if (checkCRC_ && (pkt_->dlc > 6) && + !crcState_.check_ok()) + { + pkt_->packet_header.csum_error = 1; + } + xorState_ ^= pkt_->payload[pkt_->dlc]; + if (xorState_) + { + pkt_->packet_header.csum_error = 1; + } + pkt_->dlc++; + } parseState_ = DCC_MAYBE_CUTOUT; return; } @@ -192,9 +243,29 @@ public: else { // end of byte zero bit. Packet is not finished yet. - ofs_++; - HASSERT(ofs_ < sizeof(data_)); - data_[ofs_] = 0; + if (havePacket_) + { + xorState_ ^= pkt_->payload[pkt_->dlc]; + if ((pkt_->dlc == 0) && + (pkt_->payload[0] == 254 || + pkt_->payload[0] == 253)) + { + checkCRC_ = true; + } + if (checkCRC_) + { + crcState_.update16(pkt_->payload[pkt_->dlc]); + } + pkt_->dlc++; + if (pkt_->dlc >= DCC_PACKET_MAX_PAYLOAD) + { + havePacket_ = false; + } + else + { + pkt_->payload[pkt_->dlc] = 0; + } + } parseCount_ = 1 << 7; } parseState_ = DCC_DATA; @@ -239,16 +310,16 @@ public: parseCount_ >>= 1; if (!parseCount_) { - if (ofs_ == 2) + if (pkt_->dlc == 2) { parseState_ = MM_PACKET_FINISHED; return; } else { - ofs_++; + pkt_->dlc++; parseCount_ = 1 << 7; - data_[ofs_] = 0; + pkt_->payload[pkt_->dlc] = 0; } } parseState_ = MM_DATA; @@ -260,20 +331,20 @@ public: { if (timings_[MM_LONG].match(value)) { - data_[ofs_] |= parseCount_; + pkt_->payload[pkt_->dlc] |= parseCount_; parseCount_ >>= 1; if (!parseCount_) { - if (ofs_ == 2) + if (pkt_->dlc == 2) { parseState_ = MM_PACKET_FINISHED; return; } else { - ofs_++; + pkt_->dlc++; parseCount_ = 1 << 7; - data_[ofs_] = 0; + pkt_->payload[pkt_->dlc] = 0; } } parseState_ = MM_DATA; @@ -293,26 +364,34 @@ public: (parseState_ == DCC_DATA_ONE); // one bit comes } - /// Returns the number of payload bytes in the current packet. - uint8_t packet_length() - { - return ofs_ + 1; - } +private: + /// Counter that works through bit patterns. + uint8_t parseCount_ = 0; + /// True if we have storage in the right time for the current packet. + uint8_t havePacket_ : 1; + /// True if we need to check CRC + uint8_t checkCRC_ : 1; + /// Checksum state for XOR checksum. + uint8_t xorState_; + /// Checksum state for CRC8 checksum. + Crc8DallasMaxim crcState_; + /// State machine for parsing. + State parseState_ = UNKNOWN; + /// Storage for the current packet. + DCCPacket* pkt_ = nullptr; - /// Returns the current packet payload buffer. The buffer gets invalidated - /// at the next call to process_data. - const uint8_t *packet_data() + /// Sets the input DCC packet to empty. + void clear_packet() { - return data_; + pkt_->header_raw_data = 0; + pkt_->dlc = 0; + pkt_->feedback_key = 0; + pkt_->payload[0] = 0; + checkCRC_ = 0; + xorState_ = 0; + crcState_.init(); } -private: - uint32_t parseCount_ = 0; - State parseState_ = UNKNOWN; - // Payload of current packet. - uint8_t data_[6]; - uint8_t ofs_; // offset inside data_; - /// Represents the timing of a half-wave of the digital track signal. struct Timing { @@ -377,6 +456,7 @@ public: : StateFlowBase(s) { fd_ = ::open(dev, O_RDONLY | O_NONBLOCK); + decoder_.set_packet(&pkt_); start_flow(STATE(register_and_sleep)); } @@ -401,13 +481,11 @@ private: decoder_.process_data(value); if (decoder_.state() == DccDecoder::DCC_PACKET_FINISHED) { - dcc_packet_finished( - decoder_.packet_data(), decoder_.packet_length()); + dcc_packet_finished(pkt_.payload, pkt_.dlc); } else if (decoder_.state() == DccDecoder::MM_PACKET_FINISHED) { - mm_packet_finished( - decoder_.packet_data(), decoder_.packet_length()); + mm_packet_finished(pkt_.payload, pkt_.dlc); } static uint8_t x = 0; @@ -426,6 +504,8 @@ private: uint32_t lastValue_ = 0; protected: + /// Packet buffer. + DCCPacket pkt_; /// State machine that does the DCC decoding. We have 1 usec per tick, as /// these are the numbers we receive from the driver. DccDecoder decoder_ {1}; diff --git a/src/dcc/SimpleUpdateLoop.cxx b/src/dcc/SimpleUpdateLoop.cxx index 70e7bbb4f..1383b808e 100644 --- a/src/dcc/SimpleUpdateLoop.cxx +++ b/src/dcc/SimpleUpdateLoop.cxx @@ -40,8 +40,7 @@ namespace dcc { -SimpleUpdateLoop::SimpleUpdateLoop(Service *service, - PacketFlowInterface *track_send) +SimpleUpdateLoop::SimpleUpdateLoop(Service *service, TrackIf *track_send) : StateFlow(service) , trackSend_(track_send) , nextRefreshIndex_(0) diff --git a/src/dcc/SimpleUpdateLoop.hxx b/src/dcc/SimpleUpdateLoop.hxx index 5943bb514..cb91dbaaa 100644 --- a/src/dcc/SimpleUpdateLoop.hxx +++ b/src/dcc/SimpleUpdateLoop.hxx @@ -62,7 +62,7 @@ class SimpleUpdateLoop : public StateFlow, QList<1>>, private UpdateLoopBase { public: - SimpleUpdateLoop(Service *service, PacketFlowInterface *track_send); + SimpleUpdateLoop(Service *service, TrackIf *track_send); ~SimpleUpdateLoop(); /** Adds a new refresh source to the background refresh packets. */ @@ -94,7 +94,7 @@ public: private: // Place where we forward the packets filled in. - PacketFlowInterface *trackSend_; + TrackIf *trackSend_; // Packet sources to ask about refreshing data periodically. vector refreshSources_; diff --git a/src/dcc/PacketFlowInterface.hxx b/src/dcc/TrackIf.hxx similarity index 73% rename from src/dcc/PacketFlowInterface.hxx rename to src/dcc/TrackIf.hxx index f6f5b900f..e013bc91f 100644 --- a/src/dcc/PacketFlowInterface.hxx +++ b/src/dcc/TrackIf.hxx @@ -1,9 +1,9 @@ /** \copyright - * Copyright (c) 2015, Balazs Racz + * 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: + * 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. @@ -24,26 +24,24 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - * \file PacketFlowInterface.hxx - * - * Shared declarations for sending DCC packets. + * \file TrackIf.hxx + * Helper definitions for classes using track interface directly. * * @author Balazs Racz - * @date 16 May 2015 + * @date 12 Aug 2021 */ -#ifndef _DCC_PACKETFLOWINTERFACE_HXX_ -#define _DCC_PACKETFLOWINTERFACE_HXX_ +#ifndef _DCC_TRACKIF_HXX_ +#define _DCC_TRACKIF_HXX_ -#include "executor/StateFlow.hxx" #include "dcc/Packet.hxx" +#include "executor/StateFlow.hxx" -namespace dcc { - -/// Interface for flows and ports receiving a sequence of DCC (track) packets. -typedef FlowInterface> PacketFlowInterface; +namespace dcc +{ -} // namespace dcc +using TrackIf = FlowInterface>; +} // namespace dcc -#endif +#endif // _DCC_TRACKIF_HXX_ diff --git a/src/dcc/UpdateLoop.hxx b/src/dcc/UpdateLoop.hxx index 0d19be8b8..bd8c3f65a 100644 --- a/src/dcc/UpdateLoop.hxx +++ b/src/dcc/UpdateLoop.hxx @@ -35,8 +35,8 @@ #ifndef _DCC_UPDATELOOP_HXX_ #define _DCC_UPDATELOOP_HXX_ +#include "dcc/TrackIf.hxx" #include "utils/Singleton.hxx" -#include "dcc/PacketFlowInterface.hxx" namespace dcc { diff --git a/src/dcc/dcc_constants.cxx b/src/dcc/dcc_constants.cxx new file mode 100644 index 000000000..e4070dd28 --- /dev/null +++ b/src/dcc/dcc_constants.cxx @@ -0,0 +1,37 @@ +/** \copyright + * Copyright (c) 2014, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file dcc_constants.cxx + * + * Default values of constants from the dcc package. + * + * @author Balazs Racz + * @date 10 May 2014 + */ + +#include "utils/constants.hxx" + +DEFAULT_CONST(dcc_virtual_f0_offset, 100); 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); } diff --git a/src/dcc/packet.h b/src/dcc/packet.h index fa1bc5d36..66775034d 100644 --- a/src/dcc/packet.h +++ b/src/dcc/packet.h @@ -42,7 +42,7 @@ extern "C" { #endif /** Maximum number of payload bytes. */ -#define DCC_PACKET_MAX_PAYLOAD (6) +#define DCC_PACKET_MAX_PAYLOAD (32) /** Send this speed step to emergency-stop the locomotive. */ #define DCC_PACKET_EMERGENCY_STOP (0xFFFF) /** Send this speed step to switch direction of the locomotive. Only used @@ -72,8 +72,8 @@ typedef struct dcc_packet /// The packet will be sent 1 + rept_count times to the wire. default: /// 0. uint8_t rept_count : 2; - /// reserved for future use. - uint8_t reserved : 1; + /// 1 if there was a checksum error in this packet. + uint8_t csum_error : 1; }; /// Specifies the meaning of the command byte for meta-commands to send. diff --git a/src/executor/CallableFlow.hxx b/src/executor/CallableFlow.hxx index afe57fea5..04391cf2a 100644 --- a/src/executor/CallableFlow.hxx +++ b/src/executor/CallableFlow.hxx @@ -65,7 +65,7 @@ protected: /// @return the current request we are working on. RequestType* request() { - return this->message()->data(); + return this->message() ? this->message()->data() : nullptr; } /// Terminates the flow and returns the request buffer to the caller with diff --git a/src/executor/Executor.cxx b/src/executor/Executor.cxx index ed8394dd2..310e7ce18 100644 --- a/src/executor/Executor.cxx +++ b/src/executor/Executor.cxx @@ -37,6 +37,7 @@ #include "executor/Executor.hxx" +#include "openmrn_features.h" #include #ifdef __WINNT__ @@ -228,14 +229,6 @@ void *ExecutorBase::entry() return nullptr; } -#elif defined(ARDUINO) && !defined(ESP32) - -void *ExecutorBase::entry() -{ - DIE("Arduino code should not start the executor."); - return nullptr; -} - #elif defined(ESP_NONOS) #define EXECUTOR_TASK_PRIO USER_TASK_PRIO_0 @@ -285,6 +278,14 @@ void ICACHE_FLASH_ATTR *ExecutorBase::entry() return nullptr; } +#elif OPENMRN_FEATURE_SINGLE_THREADED + +void *ExecutorBase::entry() +{ + DIE("Arduino code should not start the executor."); + return nullptr; +} + #else /** Thread entry point. * @return Should never return @@ -431,6 +432,10 @@ void ExecutorBase::shutdown() { if (!started_) return; add(this); +#if defined(__EMSCRIPTEN__) + emscripten_cancel_main_loop(); + return; +#endif while (!done_) { #if defined(ARDUINO) diff --git a/src/executor/StateFlow.hxx b/src/executor/StateFlow.hxx index 57be225a5..ffefd6e4f 100644 --- a/src/executor/StateFlow.hxx +++ b/src/executor/StateFlow.hxx @@ -529,6 +529,7 @@ protected: return wait_and_call(c); } +public: /** Calls a helper flow to perform some actions. Performs inline * synchronous allocation form the main buffer pool. Ignores the target * flow's buffer pool settings, because that makes it impossible to @@ -543,7 +544,7 @@ protected: * buffer type. */ template - void invoke_subflow_and_ignore_result( + static void invoke_subflow_and_ignore_result( FlowInterface> *target_flow, Args &&... args) { Buffer *b; @@ -552,7 +553,8 @@ protected: b->data()->done.reset(EmptyNotifiable::DefaultInstance()); target_flow->send(b); } - + +protected: struct StateFlowSelectHelper; struct StateFlowTimedSelectHelper; diff --git a/src/freertos_drivers/arduino/ArduinoFs.hxx b/src/freertos_drivers/arduino/ArduinoFs.hxx new file mode 100644 index 000000000..2b223c8ab --- /dev/null +++ b/src/freertos_drivers/arduino/ArduinoFs.hxx @@ -0,0 +1,304 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file ArduinoFs.hxx + * + * Single-file POSIX compatible filesystem using Arduino's EEPROM + * implementation for STM32. + * + * This file needs to be included into the .ino of the arduino sketch. It may + * only be compiled once as it has definitions of C library functions. + * + * @author Balazs Racz + * @date 18 July 2020 + */ + +#ifndef _FREERTOS_DRIVERS_ARDUINO_ARDUINOFS_HXX_ +#define _FREERTOS_DRIVERS_ARDUINO_ARDUINOFS_HXX_ + +#ifndef ARDUINO_ARCH_STM32 +#error This module only works for STM32 boards. +#endif + +#ifndef OPENMRN_HAVE_POSIX_FD +#error You must add a file build_opt.h to the sketch directory and add -DOPENMRN_HAVE_POSIX_FD to it to make use of this module. +#endif + +#include "stm32_eeprom.h" +#include + +#define EEPROM_FILENAME "/dev/eeprom" + +/// Class that holds information and code for the single-file filesystem for +/// emulating eeprom. +class FsStatic +{ +public: + /// Number of possible open file desriptors. + static constexpr unsigned MAX_FD = 8; + /// Offset markng that a file descriptor is not in use. + static constexpr off_t UNUSED_FILE = (off_t)-1; + + /// We have one of these for each open file descriptor. + struct FileInfo + { + /// POSIX file offset. + off_t offset = UNUSED_FILE; + + /// @return true if this file descriptor is in use. + bool in_use() + { + return offset != UNUSED_FILE; + } + + /// Marks the file descriptor to be in use. + void open() + { + offset = 0; + } + + /// Marks the file descriptor to be not in use. + void close() + { + offset = UNUSED_FILE; + } + }; + + /// Stores all file descriptors. + static FileInfo fds[MAX_FD]; + + /// Lookup a file descriptor. + /// @param fd the file descriptor. + /// @return nullptr if fd is an invalid file descriptor (in which case also + /// sets errno), otherwise the file descriptor structure. + static FileInfo *get_file(int fd) + { + if (fd >= MAX_FD || !fds[fd].in_use()) + { + errno = EBADF; + return nullptr; + } + return &fds[fd]; + } + + /// Allocates a new file descriptor. + /// @return new fd. If there is no free file descriptor, returns -1 and + /// sets errno. + static int new_fd() + { + for (int fd = 0; fd < MAX_FD; ++fd) + { + if (!fds[fd].in_use()) + { + fds[fd].open(); + return fd; + } + } + errno = ENFILE; + return -1; + } + + /// If there is unflushed writes, performs the flash write. + static void flush_if_dirty() + { + if (dirty_) + { + eeprom_buffer_flush(); + dirty_ = 0; + } + } + + /// 1 if we have filled the eeprom buffer at least once since startup. + static uint8_t loaded_; + /// 1 if we have unflushed written data in the eeprom buffer. + static uint8_t dirty_; +}; + +FsStatic::FileInfo FsStatic::fds[FsStatic::MAX_FD]; +uint8_t FsStatic::loaded_ = 0; +uint8_t FsStatic::dirty_ = 0; + +extern "C" +{ + +int _open_r(struct _reent *reent, const char *path, int flags, int mode) +{ + if (strcmp(path, EEPROM_FILENAME) != 0) + { + errno = ENOENT; + return -1; + } + if (!FsStatic::loaded_) + { + eeprom_buffer_fill(); + FsStatic::loaded_ = 1; + } + return FsStatic::new_fd(); +} + +int _close_r(struct _reent *reent, int fd) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + finfo->close(); + FsStatic::flush_if_dirty(); + return 0; +} + +ssize_t _read_r(struct _reent *reent, int fd, void *buf, size_t count) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + ssize_t ret = 0; + uint8_t *dst = (uint8_t *)buf; + int left = (int)E2END - (int)finfo->offset; + if (left < 0) + { + left = 0; + } + if (left < count) + { + count = left; + } + while (count > 0) + { + *dst = eeprom_buffered_read_byte(finfo->offset); + ++dst; + ++finfo->offset; + --count; + ++ret; + } + return ret; +} + +ssize_t _write_r(struct _reent *reent, int fd, const void *buf, size_t count) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + ssize_t ret = 0; + const uint8_t *src = (const uint8_t *)buf; + int left = (int)E2END - (int)finfo->offset; + if (left < 0) + { + left = 0; + } + if (left < count) + { + count = left; + } + if (count) + { + FsStatic::dirty_ = 1; + } + while (count > 0) + { + eeprom_buffered_write_byte(finfo->offset, *src); + ++src; + ++finfo->offset; + --count; + ++ret; + } + return ret; +} + +int fsync(int fd) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + FsStatic::flush_if_dirty(); + return 0; +} + +int _stat_r(struct _reent *reent, const char *path, struct stat *stat) +{ + if (strcmp(path, EEPROM_FILENAME) != 0) + { + errno = ENOENT; + return -1; + } + memset(stat, 0, sizeof(*stat)); + stat->st_size = E2END; + return 0; +} + +int _fstat_r(struct _reent *reent, int fd, struct stat *stat) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + memset(stat, 0, sizeof(*stat)); + stat->st_size = E2END; + return 0; +} + +_off_t _lseek_r(struct _reent *reent, int fd, _off_t offset, int whence) +{ + FsStatic::FileInfo *finfo = FsStatic::get_file(fd); + if (!finfo) + { + return -1; + } + off_t new_offset = finfo->offset; + switch (whence) + { + case SEEK_SET: + new_offset = offset; + break; + case SEEK_CUR: + new_offset += offset; + break; + case SEEK_END: + new_offset = E2END + offset; + break; + default: + new_offset = E2END + 1; + } + if (new_offset > E2END) + { + errno = EINVAL; + return -1; + } + finfo->offset = new_offset; + return new_offset; +} + +} // extern "C" + +#endif // _FREERTOS_DRIVERS_ARDUINO_ARDUINOFS_HXX_ diff --git a/src/freertos_drivers/common/DccDecoder.hxx b/src/freertos_drivers/common/DccDecoder.hxx index dc8977f6c..45a9cdf3a 100644 --- a/src/freertos_drivers/common/DccDecoder.hxx +++ b/src/freertos_drivers/common/DccDecoder.hxx @@ -35,6 +35,7 @@ #include "RailcomDriver.hxx" // for debug pins #include "dcc/Receiver.hxx" +#include "dcc/PacketProcessor.hxx" #include "dcc/packet.h" /** @@ -95,6 +96,14 @@ public: inputData_->destroy(); } + /// Installs a hook that will be called in the interrupt context for each + /// incoming packet. + /// @param p the hook interface to be called. + void set_packet_processor(dcc::PacketProcessor *p) + { + packetProcessor_ = p; + } + /** Handles a raw interrupt. */ inline void interrupt_handler() __attribute__((always_inline)); @@ -119,9 +128,11 @@ private: { portENTER_CRITICAL(); unsigned copied = inputData_->get((input_data_type *)buf, max); - if (!nextPacketData_ && inputData_->space()) + if (!decoder_.pkt() && inputData_->space()) { - inputData_->data_write_pointer(&nextPacketData_); + DCCPacket* next; + inputData_->data_write_pointer(&next); + decoder_.set_packet(next); } portEXIT_CRITICAL(); if (copied > 0) @@ -154,7 +165,7 @@ private: * exceptions * @return true if active, false if inactive */ - bool select(File *file, int mode) + bool select(File *file, int mode) OVERRIDE { bool retval = false; portENTER_CRITICAL(); @@ -187,10 +198,13 @@ private: inputData_->flush(); }; +#ifdef DCC_DECODER_DEBUG + LogRing debugLog_; +#endif + typedef DCCPacket input_data_type; DeviceBuffer *inputData_ { DeviceBuffer::create(Module::Q_SIZE)}; - DCCPacket* nextPacketData_{nullptr}; bool nextPacketFilled_{false}; /// Holds the value of the free running timer at the time we captured the /// previous edge. @@ -211,12 +225,17 @@ private: bool prepCutout_ = false; /// Which window of the cutout we are in. uint32_t cutoutState_; + /// Counts unique identifiers for DCC packets to be returned. + uint32_t packetId_ = 0; /// How many times did we lose a DCC packet due to no buffer available. - uint32_t overflowCount_ {0}; + //uint32_t overflowCount_ {0}; /// notified for cutout events. RailcomDriver *railcomDriver_; + /// notified for every arrived DCC / MM packet within the interrupt. + dcc::PacketProcessor* packetProcessor_ = nullptr; + /// DCC packet decoder state machine and internal state. dcc::DccDecoder decoder_ {Module::get_ticks_per_usec()}; @@ -253,12 +272,11 @@ template void DccDecoder::enable() lastTimerValue_ = Module::TIMER_MAX_VALUE; nextSample_ = lastTimerValue_ - Module::SAMPLE_PERIOD_CLOCKS; - if (!nextPacketData_) + if (!decoder_.pkt() && inputData_->space()) { - if (inputData_->space()) - { - inputData_->data_write_pointer(&nextPacketData_); - } + DCCPacket *next; + inputData_->data_write_pointer(&next); + decoder_.set_packet(next); } } @@ -277,6 +295,11 @@ __attribute__((optimize("-O3"))) void DccDecoder::interrupt_handler() // Debug::DccDecodeInterrupts::toggle(); uint32_t raw_new_value = Module::get_capture_counter(); uint32_t old_value = lastTimerValue_; +#ifdef DCC_DECODER_DEBUG + debugLog_.add(0); + debugLog_.add(old_value); + debugLog_.add(raw_new_value); +#endif if (raw_new_value > old_value) { // Timer has overflowed. if (nextSample_ < old_value) { @@ -297,11 +320,20 @@ __attribute__((optimize("-O3"))) void DccDecoder::interrupt_handler() nextSample_ -= Module::SAMPLE_PERIOD_CLOCKS; } uint32_t new_value = old_value - raw_new_value; +#ifdef DCC_DECODER_DEBUG + debugLog_.add(new_value); +#endif bool cutout_just_finished = false; decoder_.process_data(new_value); if (decoder_.before_dcc_cutout()) { prepCutout_ = true; + auto* p = decoder_.pkt(); + if (p) + { + p->feedback_key = ++packetId_; + } + railcomDriver_->set_feedback_key(packetId_); Module::dcc_before_cutout_hook(); } // If we are at the second half of the last 1 bit and the @@ -314,9 +346,15 @@ __attribute__((optimize("-O3"))) void DccDecoder::interrupt_handler() { //Debug::RailcomDriverCutout::set(true); Module::set_cap_timer_time(); - Module::set_cap_timer_delay_usec(RAILCOM_CUTOUT_PRE); + Module::set_cap_timer_delay_usec( + RAILCOM_CUTOUT_PRE + Module::time_delta_railcom_pre_usec()); inCutout_ = true; cutoutState_ = 0; + if (decoder_.pkt()) + { + nextPacketFilled_ = true; + } + Module::trigger_os_interrupt(); } else if (decoder_.state() == dcc::DccDecoder::DCC_CUTOUT) { @@ -333,24 +371,6 @@ __attribute__((optimize("-O3"))) void DccDecoder::interrupt_handler() Module::dcc_packet_finished_hook(); prepCutout_ = false; cutout_just_finished = true; - // Record packet to send back to userspace - if (nextPacketData_) - { - nextPacketData_->header_raw_data = 0; - nextPacketData_->packet_header.skip_ec = 1; - nextPacketData_->dlc = decoder_.packet_length(); - memcpy(nextPacketData_->payload, decoder_.packet_data(), - decoder_.packet_length()); - nextPacketData_ = nullptr; - nextPacketFilled_ = true; - - Module::trigger_os_interrupt(); - } - else - { - // Lost DCC packet. - overflowCount_++; - } Debug::DccPacketFinishedHook::set(false); } lastTimerValue_ = raw_new_value; @@ -378,14 +398,16 @@ DccDecoder::rcom_interrupt_handler() { case 0: { - Module::set_cap_timer_delay_usec(RAILCOM_CUTOUT_MID); + Module::set_cap_timer_delay_usec( + RAILCOM_CUTOUT_MID + Module::time_delta_railcom_mid_usec()); railcomDriver_->start_cutout(); cutoutState_ = 1; break; } case 1: { - Module::set_cap_timer_delay_usec(RAILCOM_CUTOUT_END); + Module::set_cap_timer_delay_usec( + RAILCOM_CUTOUT_END + Module::time_delta_railcom_end_usec()); railcomDriver_->middle_cutout(); cutoutState_ = 2; break; @@ -406,15 +428,24 @@ DccDecoder::rcom_interrupt_handler() template __attribute__((optimize("-O3"))) void DccDecoder::os_interrupt_handler() { - if (nextPacketFilled_) { + unsigned woken = 0; + if (nextPacketFilled_) + { + if (packetProcessor_) + { + packetProcessor_->packet_arrived(decoder_.pkt(), railcomDriver_); + } inputData_->advance(1); nextPacketFilled_ = false; inputData_->signal_condition_from_isr(); + woken = 1; + decoder_.set_packet(nullptr); } - if (!nextPacketData_) { - if (inputData_->space()) - { - inputData_->data_write_pointer(&nextPacketData_); - } + if (!decoder_.pkt() && inputData_->space()) + { + DCCPacket *next; + inputData_->data_write_pointer(&next); + decoder_.set_packet(next); } + portYIELD_FROM_ISR(woken); } diff --git a/src/freertos_drivers/common/Device.cxx b/src/freertos_drivers/common/Device.cxx index 5530fdab9..8b0347945 100644 --- a/src/freertos_drivers/common/Device.cxx +++ b/src/freertos_drivers/common/Device.cxx @@ -144,9 +144,11 @@ int Device::close(struct _reent *reent, int fd) // stdin, stdout, and stderr never get closed return 0; } + files[fd].inshdn = true; int result = f->dev->close(f); if (result < 0) { + files[fd].inshdn = false; errno = -result; return -1; } diff --git a/src/freertos_drivers/common/DeviceBuffer.cxx b/src/freertos_drivers/common/DeviceBuffer.cxx index 3516e65d1..249690619 100644 --- a/src/freertos_drivers/common/DeviceBuffer.cxx +++ b/src/freertos_drivers/common/DeviceBuffer.cxx @@ -34,7 +34,9 @@ #include "DeviceBuffer.hxx" -#ifndef ARDUINO +#include "openmrn_features.h" + +#ifdef OPENMRN_FEATURE_DEVTAB #include @@ -61,4 +63,4 @@ void DeviceBufferBase::block_until_condition(File *file, bool read) ::select(fd + 1, read ? &fds : NULL, read ? NULL : &fds, NULL, NULL); } -#endif +#endif // OPENMRN_FEATURE_DEVTAB diff --git a/src/freertos_drivers/common/DeviceBuffer.hxx b/src/freertos_drivers/common/DeviceBuffer.hxx index 7d4fe8b74..ac7d70688 100644 --- a/src/freertos_drivers/common/DeviceBuffer.hxx +++ b/src/freertos_drivers/common/DeviceBuffer.hxx @@ -39,32 +39,34 @@ #include #include #include + +#include "openmrn_features.h" #include "utils/macros.h" -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB #include "Devtab.hxx" -#endif +#endif // OPENMRN_FEATURE_DEVTAB /** Helper for DeviceBuffer which allows for methods to not be inlined. */ class DeviceBufferBase { public: -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB /** Wait for blocking condition to become true. * @param file file to wait on * @param read true if this is a read operation, false for write operation */ static void block_until_condition(File *file, bool read); -#endif +#endif // OPENMRN_FEATURE_DEVTAB /** Signal the wakeup condition. This will also wakeup select. */ void signal_condition() { -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB Device::select_wakeup(&selectInfo); -#endif +#endif // OPENMRN_FEATURE_DEVTAB } /** Signal the wakeup condition from an ISR context. This will also @@ -72,10 +74,10 @@ public: */ void signal_condition_from_isr() { -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB int woken = 0; Device::select_wakeup_from_isr(&selectInfo, &woken); -#endif +#endif // OPENMRN_FEATURE_DEVTAB } /** flush all the data out of the buffer and reset the buffer. It is @@ -110,9 +112,9 @@ public: */ void select_insert() { -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB return Device::select_insert(&selectInfo); -#endif +#endif // OPENMRN_FEATURE_DEVTAB } /** Remove a number of items from the buffer by advancing the readIndex. @@ -180,10 +182,10 @@ protected: { } -#ifndef ARDUINO +#ifdef OPENMRN_FEATURE_DEVTAB /** Metadata for select() logic */ Device::SelectInfo selectInfo; -#endif +#endif // OPENMRN_FEATURE_DEVTAB /** level of space required in buffer in order to wakeup, 0 if unused */ uint16_t level; 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/Devtab.hxx b/src/freertos_drivers/common/Devtab.hxx index e86d2e4c6..c807ed58f 100644 --- a/src/freertos_drivers/common/Devtab.hxx +++ b/src/freertos_drivers/common/Devtab.hxx @@ -46,13 +46,6 @@ class FileSystem; class Notifiable; class DeviceBufferBase; -#ifdef TARGET_LPC11Cxx -#define NUM_OPEN_FILES 4 -#else -/// How many concurrently open fd we support. -#define NUM_OPEN_FILES 20 //12 -#endif - /** File information. */ struct File @@ -69,6 +62,7 @@ struct File off_t offset; /**< current offset within file */ int flags; /**< open flags */ uint8_t inuse : 1; /**< true if this is an open fd. */ + uint8_t inshdn : 1; /**< true if this fd is in shutdown. */ uint8_t device : 1; /**< true if this is a device, false if file system */ uint8_t dir : 1; /**< true if this is a directory, else false */ uint8_t dirty : 1; /**< true if this file is dirty and needs flush */ @@ -259,6 +253,10 @@ protected: */ static int fd_lookup(File *file); + /** @return the maximum number of open file descriptors possible (the size + * of the files[] array. */ + static const unsigned int numOpenFiles; + /** File descriptor pool */ static File files[]; diff --git a/src/freertos_drivers/common/EEPROMEmulation.cxx b/src/freertos_drivers/common/EEPROMEmulation.cxx index 0751899d1..1f371d39f 100644 --- a/src/freertos_drivers/common/EEPROMEmulation.cxx +++ b/src/freertos_drivers/common/EEPROMEmulation.cxx @@ -50,11 +50,12 @@ EEPROMEmulation::EEPROMEmulation(const char *name, size_t file_size) : EEPROM(name, file_size) { /* make sure we have an appropriate sized region of memory for our device */ - HASSERT(FLASH_SIZE >= (2 * SECTOR_SIZE)); // at least two of them - HASSERT((FLASH_SIZE % SECTOR_SIZE) == 0); // and nothing remaining + HASSERT(EEPROMEMU_FLASH_SIZE >= (2 * SECTOR_SIZE)); // at least two of them + HASSERT((EEPROMEMU_FLASH_SIZE % SECTOR_SIZE) == 0); // and nothing remaining HASSERT(file_size <= (SECTOR_SIZE >> 1)); // single block fit all the data HASSERT(file_size <= (1024 * 64 - 2)); // uint16 indexes, 0xffff reserved HASSERT(BLOCK_SIZE >= 4); // we don't support block sizes less than 4 bytes + 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) @@ -194,6 +195,8 @@ void EEPROMEmulation::write(unsigned int index, const void *buf, size_t len) { memcpy(shadow_ + shadow_index, shadow_data, shadow_len); } + + updated_notification(); } /** Write to the EEPROM on a native block boundary. @@ -205,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) | @@ -231,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) @@ -244,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 */ @@ -305,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 c0932e1e8..9631e70f1 100644 --- a/src/freertos_drivers/common/EEPROMEmulation.hxx +++ b/src/freertos_drivers/common/EEPROMEmulation.hxx @@ -36,14 +36,12 @@ #include "EEPROM.hxx" -#ifndef FLASH_SIZE /// Linker-defined symbol where in the memory space (flash) the eeprom /// emulation data starts. extern const char __eeprom_start; /// Linker-defined symbol where in the memory space (flash) the eeprom /// emulation data ends. extern const char __eeprom_end; -#endif /** Emulates EEPROM in FLASH for the Tiva, LPC17xx and LPC40xx * platforms. Applicable in general to any microcontroller with self-writeable @@ -86,8 +84,8 @@ extern const char __eeprom_end; * Parameters: * @param SECTOR_SIZE: size of independently erased flash areas. Usually in * the range of kilobytes; for example somewhere between 1-16 kbytes. - * @param FLASH_SIZE: Automatically detected from the linker symbols. An - * integer (at least 2) multiple of SECTOR_SIZE. Sectors within the + * @param EEPROMEMU_FLASH_SIZE: Automatically detected from the linker symbols. + * An integer (at least 2) multiple of SECTOR_SIZE. Sectors within the * designatedflash are will be used in a round-robin manner to maximize flash * endurance. * @param BLOCK_SIZE: Defines how many bytes shall be flashed in one @@ -155,7 +153,16 @@ 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 + * to be overridden in the application to get callbacks for eeprom writes + * that can trigger a reload. */ + void updated_notification(); + /** Write to the EEPROM. NOTE!!! This is not necessarily atomic across * byte boundaries in the case of power loss. The user should take this * into account as it relates to data integrity of a whole block. @@ -172,16 +179,12 @@ private: */ void read(unsigned int offset, void *buf, size_t len) OVERRIDE; -#ifndef FLASH_SIZE /** Total FLASH memory size to use for EEPROM Emulation. Must be at least * 2 sectors large and at least 4x the total amount of EEPROM address space * that will be emulated. Larger sizes will result in greater endurance. * must be a macro in order to calculate from link time constants. */ - #define FLASH_SIZE ((uintptr_t)(&__eeprom_end - &__eeprom_start)) -#else - static const size_t FLASH_SIZE; -#endif + #define EEPROMEMU_FLASH_SIZE ((uintptr_t)(&__eeprom_end - &__eeprom_start)) /** useful data bytes size in bytes * @todo maybe this should be a macro of BLOCK_SIZE / 2 @@ -307,7 +310,7 @@ private: protected: /** Total number of sectors available. */ - const uint8_t sectorCount_{(uint8_t)(FLASH_SIZE / SECTOR_SIZE)}; + const uint8_t sectorCount_{(uint8_t)(EEPROMEMU_FLASH_SIZE / SECTOR_SIZE)}; /** Index of the active sector. */ uint8_t activeSector_{0}; diff --git a/src/freertos_drivers/common/EEPROMEmulation_weak.cxx b/src/freertos_drivers/common/EEPROMEmulation_weak.cxx index e77dcd606..b7b4aca61 100644 --- a/src/freertos_drivers/common/EEPROMEmulation_weak.cxx +++ b/src/freertos_drivers/common/EEPROMEmulation_weak.cxx @@ -37,3 +37,11 @@ // emulation implementation to prevent GCC from mistakenly optimizing away the // constant into a linker reference. const bool __attribute__((weak)) EEPROMEmulation::SHADOW_IN_RAM = false; + +/// This function will be called after every write. The default +/// implementation is a weak symbol with an empty function. It is intended +/// to be overridden in the application to get callbacks for eeprom writes +/// that can trigger a reload. +void __attribute__((weak)) EEPROMEmulation::updated_notification() +{ +} diff --git a/src/freertos_drivers/common/Fileio.cxx b/src/freertos_drivers/common/Fileio.cxx index 9120abf62..d6c64b692 100644 --- a/src/freertos_drivers/common/Fileio.cxx +++ b/src/freertos_drivers/common/Fileio.cxx @@ -39,20 +39,19 @@ #include #include - OSMutex FileIO::mutex; -File FileIO::files[NUM_OPEN_FILES]; /** Allocate a free file descriptor. * @return file number on success, else -1 on failure */ int FileIO::fd_alloc(void) { - for (unsigned int i = 0; i < NUM_OPEN_FILES; i++) + for (unsigned int i = 0; i < numOpenFiles; i++) { if (files[i].inuse == false) { files[i].inuse = true; + files[i].inshdn = false; files[i].device = true; files[i].dir = false; files[i].dirty = false; @@ -82,12 +81,12 @@ void FileIO::fd_free(int fd) */ File* FileIO::file_lookup(int fd) { - if (fd < 0 || fd >= NUM_OPEN_FILES) + if (fd < 0 || fd >= (int)numOpenFiles) { errno = EBADF; return nullptr; } - if (files[fd].inuse == 0) + if (files[fd].inuse == 0 || files[fd].inshdn == 1) { errno = EBADF; return nullptr; @@ -101,7 +100,7 @@ File* FileIO::file_lookup(int fd) */ int FileIO::fd_lookup(File *file) { - HASSERT(file >= files && file <= (files + NUM_OPEN_FILES) && file->inuse); + HASSERT(file >= files && file <= (files + numOpenFiles) && file->inuse); return (file - files); } diff --git a/src/freertos_drivers/common/FileioWeak.cxx b/src/freertos_drivers/common/FileioWeak.cxx new file mode 100644 index 000000000..0976075fd --- /dev/null +++ b/src/freertos_drivers/common/FileioWeak.cxx @@ -0,0 +1,41 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file FileioWeak.cxx + * + * Weak definitions for FileIO. These can be overridden by client applications. + * + * @author Balazs Racz + * @date 18 September 2020 + */ + +#include "Devtab.hxx" + +// Override both of these symbols in a .cxx file in your application (without +// the weak attribute) if you want to change the nuber of open files. + +const unsigned int __attribute__((weak)) FileIO::numOpenFiles = 20; +File __attribute__((weak)) FileIO::files[FileIO::numOpenFiles]; diff --git a/src/freertos_drivers/common/FlashFile.hxx b/src/freertos_drivers/common/FlashFile.hxx new file mode 100644 index 000000000..f10d09e48 --- /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 ((flags & O_TRUNC) && ((flags & O_ACCMODE) != O_RDONLY)) + { + // 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/RailcomDriver.hxx b/src/freertos_drivers/common/RailcomDriver.hxx index fbda26b6b..44a0f8a34 100644 --- a/src/freertos_drivers/common/RailcomDriver.hxx +++ b/src/freertos_drivers/common/RailcomDriver.hxx @@ -36,6 +36,11 @@ #ifndef _FREERTOS_DRIVERS_COMMON_RAILCOMDRIVER_HXX_ #define _FREERTOS_DRIVERS_COMMON_RAILCOMDRIVER_HXX_ +#include + +#include "utils/macros.h" +#include "dcc/railcom.h" + /// Abstract base class for railcom drivers. This interface is used to /// communicate when the railcom cutout happens. The railcom cutout is produced /// or detected in the DCC generator or DCC parser driver, but the railcom @@ -58,12 +63,35 @@ public: /** Instructs the driver that the railcom cutout is over now. The driver * will use this information to disable the UART receiver. */ virtual void end_cutout() = 0; + /** Called instead of start/mid/end-cutout at the end of the current packet + * if there was no cutout requested. */ + virtual void no_cutout() = 0; /** Specifies the feedback key to write into the received railcom data * packets. This feedback key is used by the application layer to correlate * the stream of DCC packets to the stream of Railcom packets. This method * shall be called before start_cutout. The feedback key set here is used * until this method is called again. @param key is the new feedback key. */ virtual void set_feedback_key(uint32_t key) = 0; + + /** Specifies what packet should be sent for the channel1 cutout. It is + * okay to specify the same packet pointer for ch1 and ch2 cutout. + * @param ch1_pkt the RailCom packet. Only the ch1 data will be read from + * this packet. This pointer must stay alive until the next DCC packet + * comes. The FeedbackKey in this packet must be correct for the current + * DCC packet or else the data will not be sent. */ + virtual void send_ch1(const DCCFeedback *ch1_pkt) + { + } + + /** Specifies what packet should be sent for the channel2 cutout. It is + * okay to specify the same packet pointer for ch1 and ch2 cutout. + * @param ch2_pkt the RailCom packet. Only the ch2 data will be read from + * this packet. This pointer must stay alive until the next DCC packet + * comes. The FeedbackKey in this packet must be correct for the current + * DCC packet or else the data will not be sent. */ + virtual void send_ch2(const DCCFeedback *ch2_pkt) + { + } }; @@ -74,6 +102,7 @@ class NoRailcomDriver : public RailcomDriver { void start_cutout() OVERRIDE {} void middle_cutout() OVERRIDE {} void end_cutout() OVERRIDE {} + void no_cutout() OVERRIDE {} void set_feedback_key(uint32_t key) OVERRIDE {} }; diff --git a/src/freertos_drivers/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..73f68c5b8 --- /dev/null +++ b/src/freertos_drivers/common/SPIFlash.hxx @@ -0,0 +1,182 @@ +/** \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 + +#include "utils/logging.h" + +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) + { + /// This ensures that the sector size is a power of two. + HASSERT((cfg->sectorSize_ & (cfg->sectorSize_ - 1)) == 0); + } + + /// @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); + + /// 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). + 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/common/SimpleLog.hxx b/src/freertos_drivers/common/SimpleLog.hxx index 79cf4e081..75e8e8d5d 100644 --- a/src/freertos_drivers/common/SimpleLog.hxx +++ b/src/freertos_drivers/common/SimpleLog.hxx @@ -64,24 +64,22 @@ private: /// Actual class that keeps 8 log entries of one byte each. typedef SimpleLog LogBuffer; - /// Alternative for hundreds of entries. -template class LogRing { +template class LogRing +{ public: - void add(T data) { - data_[next_] = data; - last_ = data_ + next_; - if (next_) { - --next_; - } else { - next_ = N-1; + void add(T data) + { + data_[next_++] = data; + if (next_ >= N) + { + next_ = 0; } } - + private: T data_[N]; - unsigned next_{N}; - T* last_{data_}; + unsigned next_ {0}; }; #endif // _FREERTOS_DRIVERS_COMMON_SIMPLELOG_HXX_ diff --git a/src/freertos_drivers/common/TCAN4550Can.cxx b/src/freertos_drivers/common/TCAN4550Can.cxx index 5e00d62e6..1693837b5 100644 --- a/src/freertos_drivers/common/TCAN4550Can.cxx +++ b/src/freertos_drivers/common/TCAN4550Can.cxx @@ -650,7 +650,7 @@ void *TCAN4550Can::entry() spiStatus_ = register_read(STATUS); status_ = register_read(INTERRUPT_STATUS); - MRAMMessage msg; + MRAMSPIMessage msg; msg.cmd = READ; msg.addrH = 0x10; msg.addrL = 0x00; @@ -659,7 +659,7 @@ void *TCAN4550Can::entry() spi_ioc_transfer xfer[2]; xfer[0].tx_buf = (unsigned long)&msg; xfer[0].rx_buf = 0; - xfer[0].len = sizeof(MRAMMessage); + xfer[0].len = sizeof(MRAMSPIMessage); xfer[1].tx_buf = 0; xfer[1].rx_buf = (unsigned long)regs_; xfer[1].len = sizeof(regs_); @@ -710,16 +710,17 @@ void *TCAN4550Can::entry() if (psr.ep) { // error passive state +#if TCAN4550_DEBUG + testPin_->clr(); +#endif ++softErrorCount; state_ = CAN_STATE_BUS_PASSIVE; - - // cancel TX FIFO buffers - register_write(TXBCR, TX_FIFO_BUFFERS_MASK); - - txBuf->signal_condition(); } else { +#if TCAN4550_DEBUG + testPin_->set(); +#endif state_ = CAN_STATE_ACTIVE; } } @@ -728,8 +729,34 @@ void *TCAN4550Can::entry() if (psr.bo) { // bus off +#if TCAN4550_DEBUG + testPin_->write(!testPin_->read()); +#endif ++busOffCount; state_ = CAN_STATE_BUS_OFF; + // attempt recovery + Cccr cccr; + do + { + cccr.data = 0; + register_write(CCCR, cccr.data); + cccr.data = register_read(CCCR); + } while (cccr.init == 1); + + // cancel TX FIFO buffers + register_write(TXBCR, TX_FIFO_BUFFERS_MASK); + + txBuf->signal_condition(); +#if TCAN4550_DEBUG + testPin_->write(!testPin_->read()); +#endif + } + else + { +#if TCAN4550_DEBUG + testPin_->set(); +#endif + state_ = CAN_STATE_ACTIVE; } } } diff --git a/src/freertos_drivers/common/TCAN4550Can.hxx b/src/freertos_drivers/common/TCAN4550Can.hxx index 11a2497b3..af8d264e7 100644 --- a/src/freertos_drivers/common/TCAN4550Can.hxx +++ b/src/freertos_drivers/common/TCAN4550Can.hxx @@ -35,8 +35,10 @@ #define _FREERTOS_DRIVERS_COMMON_TCAN4550CAN_HXX_ #include "Can.hxx" +#include "DummyGPIO.hxx" #include "SPI.hxx" +#include "os/Gpio.hxx" #include "os/OS.hxx" #include "utils/Atomic.hxx" @@ -55,8 +57,15 @@ public: /// @param name name of this device instance in the file system /// @param interrupt_enable callback to enable the interrupt /// @param interrupt_disable callback to disable the interrupt + /// @param test_pin test GPIO pin for instrumenting the code TCAN4550Can(const char *name, - void (*interrupt_enable)(), void (*interrupt_disable)()) + void (*interrupt_enable)(), void (*interrupt_disable)(), +#if TCAN4550_DEBUG + const Gpio *test_pin = DummyPinWithRead::instance() +#else + const Gpio *test_pin = nullptr +#endif + ) : Can(name, 0, 0) , OSThread() , interruptEnable_(interrupt_enable) @@ -69,7 +78,13 @@ public: , state_(CAN_STATE_STOPPED) , txPending_(false) , rxPending_(false) +#if TCAN4550_DEBUG + , testPin_(test_pin) +#endif { +#if TCAN4550_DEBUG + testPin_->set(); +#endif } /// Destructor. @@ -1124,6 +1139,10 @@ private: MCANInterrupt mcanInterruptEnable_; ///< shadow for the interrupt enable uint32_t txCompleteMask_; ///< shadow for the transmit complete buffer mask uint8_t state_; ///< present bus state + + // These bitmasks are protected from a read-modify-write race condition + // because they are only set from a thread context that holds the SPI bus + // mutex. uint8_t txPending_ : 1; ///< waiting on a TX active event uint8_t rxPending_ : 1; ///< waiting on a RX active event @@ -1135,6 +1154,7 @@ private: volatile uint32_t status_; volatile uint32_t enable_; volatile uint32_t spiStatus_; + const Gpio *testPin_; ///< test GPIO pin for instrumenting the code #endif /// baud rate settings table diff --git a/src/freertos_drivers/common/WifiDefs.hxx b/src/freertos_drivers/common/WifiDefs.hxx index 751bc5071..3005737a2 100644 --- a/src/freertos_drivers/common/WifiDefs.hxx +++ b/src/freertos_drivers/common/WifiDefs.hxx @@ -32,9 +32,10 @@ enum class WlanState : uint8_t */ enum class WlanRole : uint8_t { - UNKNOWN = 0, /**< Wi-Fi station mode */ - STA, /**< Wi-Fi station mode */ - AP /**< Wi-Fi access point mode */ + UNKNOWN = 0, /**< Default mode (from stored configuration) */ + DEFAULT_ROLE = UNKNOWN, /**< Default mode (from stored configuration) */ + STA, /**< Wi-Fi station mode */ + AP /**< Wi-Fi access point mode */ }; enum class CountryCode : uint8_t diff --git a/src/freertos_drivers/esp32/Esp32WiFiConfiguration.hxx b/src/freertos_drivers/esp32/Esp32WiFiConfiguration.hxx index 4ef873840..72a4ba10d 100644 --- a/src/freertos_drivers/esp32/Esp32WiFiConfiguration.hxx +++ b/src/freertos_drivers/esp32/Esp32WiFiConfiguration.hxx @@ -69,13 +69,20 @@ public: static constexpr const char *HUB_DESC = "Configuration settings for an OpenLCB Hub"; - /// Visible name for the hub enable field. - static constexpr const char *HUB_ENABLE_NAME = "Enable Hub Mode"; + /// Visible name for the hub/uplink enable field. + static constexpr const char *CONN_MODE_NAME = "Connection Mode"; - /// Visible description for the hub enable field. - static constexpr const char *HUB_ENABLE_DESC = - "Defines this node as a hub which can accept connections"; + /// Visible description for the hub/uplink enable field. + static constexpr const char *CONN_MODE_DESC = + "Defines whether to allow accepting connections (according to the Hub configuration), making a connection (according to the Uplink configuration), or both."; + /// of possible keys and descriptive values to show to the user for + /// the connection_mode fields. + static constexpr const char *CONN_MODE_MAP = + "1Uplink Only" + "2Hub Only" + "3Hub+Uplink"; + /// Visible name for the hub_listener_port field. static constexpr const char *HUB_LISTENER_PORT_NAME = "Hub Listener Port"; @@ -94,11 +101,6 @@ public: /// CDI Configuration for an @ref Esp32WiFiManager managed hub. CDI_GROUP(HubConfiguration); -/// Allows the node to become a Grid Connect Hub. -CDI_GROUP_ENTRY(enable, openlcb::Uint8ConfigEntry, - Name(Esp32WiFiConfigurationParams::HUB_ENABLE_NAME), - Description(Esp32WiFiConfigurationParams::HUB_ENABLE_DESC), Min(0), Max(1), - Default(0), MapValues(Esp32WiFiConfigurationParams::BOOLEAN_MAP)); /// Specifies the port which should be used by the hub. CDI_GROUP_ENTRY(port, openlcb::Uint16ConfigEntry, Name(Esp32WiFiConfigurationParams::HUB_LISTENER_PORT_NAME), @@ -120,6 +122,11 @@ CDI_GROUP_ENTRY(sleep, openlcb::Uint8ConfigEntry, Name(Esp32WiFiConfigurationParams::WIFI_POWER_SAVE_NAME), Description(Esp32WiFiConfigurationParams::WIFI_POWER_SAVE_DESC), Min(0), Max(1), Default(0), MapValues(Esp32WiFiConfigurationParams::BOOLEAN_MAP)); +/// Defines configuration of hub or uplink +CDI_GROUP_ENTRY(connection_mode, openlcb::Uint8ConfigEntry, + Name(Esp32WiFiConfigurationParams::CONN_MODE_NAME), + Description(Esp32WiFiConfigurationParams::CONN_MODE_DESC), Min(1), Max(3), + Default(1), MapValues(Esp32WiFiConfigurationParams::CONN_MODE_MAP)); /// CDI Configuration to enable this node to be a hub. CDI_GROUP_ENTRY(hub, HubConfiguration, Name(Esp32WiFiConfigurationParams::HUB_NAME), @@ -135,4 +142,4 @@ CDI_GROUP_END(); using openmrn_arduino::WiFiConfiguration; -#endif // _FREERTOS_DRIVERS_ESP32_ESP32WIFICONFIG_HXX_ \ No newline at end of file +#endif // _FREERTOS_DRIVERS_ESP32_ESP32WIFICONFIG_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32WiFiManager.cxx b/src/freertos_drivers/esp32/Esp32WiFiManager.cxx index a7c36cc14..1fcf009f2 100644 --- a/src/freertos_drivers/esp32/Esp32WiFiManager.cxx +++ b/src/freertos_drivers/esp32/Esp32WiFiManager.cxx @@ -48,20 +48,45 @@ #include #include -// ESP-IDF v4+ has a slightly different directory structure to previous -// versions. -#ifdef ESP_IDF_VERSION_MAJOR -// ESP-IDF v4+ +// Starting in ESP-IDF v4.0 a few header files have been relocated so we need +// to adjust the include paths accordingly. If the __has_include preprocessor +// directive is defined we can use it to find the appropriate header files. +// If it is not usable then we will default the older header filenames. +#if defined(__has_include) + +// rom/crc.h was relocated to esp32/rom/crc.h in ESP-IDF v4.0 +// TODO: This will need to be platform specific in IDF v4.1 since this is +// exposed in unique header paths for each supported platform. Detecting the +// operating platform (ESP32, ESP32-S2, ESP32-S3, etc) can be done by checking +// for the presence of one of the following defines: +// CONFIG_IDF_TARGET_ESP32 -- ESP32 +// CONFIG_IDF_TARGET_ESP32S2 -- ESP32-S2 +// CONFIG_IDF_TARGET_ESP32S3 -- ESP32-S3 +// If none of these are defined it means the ESP-IDF version is v4.0 or +// earlier. +#if __has_include("esp32/rom/crc.h") #include +#else +#include +#endif + +// esp_wifi_internal.h was relocated to esp_private/wifi.h in ESP-IDF v4.0 +#if __has_include("esp_private/wifi.h") #include #else -// ESP-IDF v3.x +#include +#endif + +#else + +// We are unable to use __has_include, default to the old include paths. #include #include -#endif // ESP_IDF_VERSION_MAJOR + +#endif // defined __has_include using openlcb::NodeID; -using openlcb::SimpleCanStack; +using openlcb::SimpleCanStackBase; using openlcb::TcpAutoAddress; using openlcb::TcpClientConfig; using openlcb::TcpClientDefaultParams; @@ -115,9 +140,16 @@ namespace openmrn_arduino /// Esp32WiFiManager::apply_configuration. static constexpr UBaseType_t WIFI_TASK_PRIORITY = 2; +/// Priority for the task performing the mdns lookups and connections for the +/// wifi uplink. +static constexpr UBaseType_t CONNECT_TASK_PRIORITY = 3; + /// Stack size for the wifi_manager_task. static constexpr uint32_t WIFI_TASK_STACK_SIZE = 2560L; +/// Stack size for the connect_executor. +static constexpr uint32_t CONNECT_TASK_STACK_SIZE = 2560L; + /// Interval at which to check the WiFi connection status. static constexpr TickType_t WIFI_CONNECT_CHECK_INTERVAL = pdMS_TO_TICKS(5000); @@ -272,7 +304,7 @@ class Esp32SocketParams : public DefaultSocketClientParams // WiFi connection, mDNS system and the hostname of the ESP32. Esp32WiFiManager::Esp32WiFiManager(const char *ssid , const char *password - , SimpleCanStack *stack + , SimpleCanStackBase *stack , const WiFiConfiguration &cfg , const char *hostname_prefix , wifi_mode_t wifi_mode @@ -325,7 +357,7 @@ Esp32WiFiManager::Esp32WiFiManager(const char *ssid // With this constructor being used, it will be the responsibility of the // application to manage the WiFi and mDNS systems. Esp32WiFiManager::Esp32WiFiManager( - SimpleCanStack *stack, const WiFiConfiguration &cfg) + SimpleCanStackBase *stack, const WiFiConfiguration &cfg) : DefaultConfigUpdateListener() , cfg_(cfg) , manageWiFi_(false) @@ -403,9 +435,9 @@ void Esp32WiFiManager::factory_reset(int fd) // General WiFi configuration settings. CDI_FACTORY_RESET(cfg_.sleep); + CDI_FACTORY_RESET(cfg_.connection_mode); // Hub specific configuration settings. - CDI_FACTORY_RESET(cfg_.hub().enable); CDI_FACTORY_RESET(cfg_.hub().port); cfg_.hub().service_name().write( fd, TcpDefs::MDNS_SERVICE_NAME_GRIDCONNECT_CAN_TCP); @@ -508,10 +540,7 @@ void Esp32WiFiManager::process_wifi_event(system_event_t *event) // Retrieve the configured IP address from the TCP/IP stack. tcpip_adapter_ip_info_t ip_info; tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); - LOG(INFO, - "[WiFi] IP address is " IPSTR ", starting hub (if enabled) and " - "uplink.", - IP2STR(&ip_info.ip)); + LOG(INFO, "[WiFi] IP address is " IPSTR ".", IP2STR(&ip_info.ip)); // Start the mDNS system since we have an IP address, the mDNS system // on the ESP32 requires that the IP address be assigned otherwise it @@ -958,13 +987,28 @@ void *Esp32WiFiManager::wifi_manager_task(void *param) ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); } - if (CDI_READ_TRIMMED(wifi->cfg_.hub().enable, wifi->configFd_)) + bool have_hub = false; + uint8_t conn_cfg = CDI_READ_TRIMMED(wifi->cfg_.connection_mode, + wifi->configFd_); + if (conn_cfg & 2) { + LOG(INFO, "[WiFi] Starting hub."); // Since hub mode is enabled start the HUB creation process. wifi->start_hub(); + have_hub = true; + } else { + LOG(INFO, "[WiFi] Hub disabled by configuration."); + } + if (conn_cfg & 1) { + LOG(INFO, "[WiFi] Starting uplink."); + wifi->start_uplink(); + } else if (!have_hub) { + LOG(INFO, "[WiFi] Starting uplink, because hub is disabled."); + wifi->start_uplink(); + } else { + LOG(INFO, "[WiFi] Uplink disabled by configuration."); } - // Start the uplink connection process in the background. - wifi->start_uplink(); + wifi->configReloadRequested_ = false; } @@ -1021,10 +1065,15 @@ void Esp32WiFiManager::start_uplink() { unique_ptr params( new Esp32SocketParams(configFd_, cfg_.uplink())); - uplink_.reset(new SocketClient(stack_->service(), stack_->executor(), - stack_->executor(), std::move(params), + uplink_.reset(new SocketClient(stack_->service(), &connectExecutor_, + &connectExecutor_, std::move(params), std::bind(&Esp32WiFiManager::on_uplink_created, this, std::placeholders::_1, std::placeholders::_2))); + if (!connectExecutorStarted_) { + connectExecutorStarted_ = true; + connectExecutor_.start_thread( + "Esp32WiFiConn", CONNECT_TASK_PRIORITY, CONNECT_TASK_STACK_SIZE); + } } // Converts the passed fd into a GridConnect port and adds it to the stack. @@ -1128,7 +1177,7 @@ void Esp32WiFiManager::mdns_publish(string service, const uint16_t port) split_mdns_service_name(&service_name, &protocol_name); esp_err_t res = mdns_service_add( NULL, service_name.c_str(), protocol_name.c_str(), port, NULL, 0); - LOG(VERBOSE, "[mDNS] mdns_service_add(%s.%s:%d): %s." + LOG(INFO, "[mDNS] mdns_service_add(%s.%s:%d): %s." , service_name.c_str(), protocol_name.c_str(), port , esp_err_to_name(res)); // ESP_FAIL will be triggered if there is a timeout during publish of diff --git a/src/freertos_drivers/esp32/Esp32WiFiManager.hxx b/src/freertos_drivers/esp32/Esp32WiFiManager.hxx index 4c832472a..9ba82fe1e 100644 --- a/src/freertos_drivers/esp32/Esp32WiFiManager.hxx +++ b/src/freertos_drivers/esp32/Esp32WiFiManager.hxx @@ -84,7 +84,7 @@ public: /// @param ssid is the WiFi AP to connect to. Must stay alive forever. /// @param password is the password for the WiFi AP being connected /// to. Must stay alive forever. - /// @param stack is the SimpleCanStack for this node. Must stay alive + /// @param stack is the SimpleCanStackBase for this node. Must stay alive /// forever. /// @param cfg is the WiFiConfiguration instance used for this node. This /// will be monitored for changes and the WiFi behavior altered @@ -118,7 +118,7 @@ public: /// node uptime. Esp32WiFiManager(const char *ssid , const char *password - , openlcb::SimpleCanStack *stack + , openlcb::SimpleCanStackBase *stack , const WiFiConfiguration &cfg , const char *hostname_prefix = "esp32_" , wifi_mode_t wifi_mode = WIFI_MODE_STA @@ -138,12 +138,12 @@ public: /// the application code starts the the WiFi and MDNS systems before /// calling OpenMRN::begin(). /// - /// @param stack is the SimpleCanStack for this node. + /// @param stack is the SimpleCanStackBase for this node. /// @param cfg is the WiFiConfiguration instance used for this node. This /// will be monitored for changes and the WiFi behavior altered /// accordingly. Esp32WiFiManager( - openlcb::SimpleCanStack *stack, const WiFiConfiguration &cfg); + openlcb::SimpleCanStackBase *stack, const WiFiConfiguration &cfg); /// Updates the WiFiConfiguration settings used by this node. /// @@ -215,7 +215,7 @@ public: /// @param port is the port for the service to be published. /// /// Note: This will schedule a @ref CallbackExecutable on the @ref Executor - /// used by the @ref SimpleCanStack. + /// used by the @ref SimpleCanStackBase. void mdns_publish(std::string service, uint16_t port); /// Removes the advertisement of a service via mDNS. @@ -294,7 +294,7 @@ private: const bool manageWiFi_; /// OpenMRN stack for the Arduino system. - openlcb::SimpleCanStack *stack_; + openlcb::SimpleCanStackBase *stack_; /// WiFi operating mode. wifi_mode_t wifiMode_{WIFI_MODE_STA}; @@ -368,6 +368,12 @@ private: /// Internal flag for tracking that the mDNS system has been initialized. bool mdnsInitialized_{false}; + /// True if we have started the connect executor thread. + bool connectExecutorStarted_{false}; + + /// Executor to use for the uplink connections. + Executor<1> connectExecutor_{NO_THREAD()}; + /// Internal holder for mDNS entries which could not be published due to /// mDNS not being initialized yet. std::map mdnsDeferredPublish_; diff --git a/src/freertos_drivers/net_cc32xx/CC32xxSocket.cxx b/src/freertos_drivers/net_cc32xx/CC32xxSocket.cxx index 276b3cd31..51ae25256 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxSocket.cxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxSocket.cxx @@ -755,8 +755,8 @@ int CC32xxSocket::close(File *file) portENTER_CRITICAL(); remove_instance_from_sd(sd); portEXIT_CRITICAL(); - delete this; sl_Close(sd); + delete this; } else { diff --git a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.cxx index fe54b2018..9620019eb 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) @@ -317,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() */ @@ -565,6 +603,14 @@ void CC32xxWiFi::wlan_mac(uint8_t mac[6]) sl_NetCfgGet(SL_NETCFG_MAC_ADDRESS_GET, nullptr, &len, mac); } +/* + * CC32xxWiFi::wlan_set_mac() + */ +void CC32xxWiFi::wlan_set_mac(uint8_t mac[6]) +{ + sl_NetCfgSet(SL_NETCFG_MAC_ADDRESS_SET, 1, 6, mac); +} + /* * CC32xxWiFi::test_mode_start() */ @@ -765,13 +811,14 @@ void CC32xxWiFi::wlan_setup_ap(const char *ssid, const char *security_key, (uint8_t*)ssid); if (wlanRole == WlanRole::AP) { - strncpy(this->ssid, ssid, sizeof(this->ssid)); + str_populate(this->ssid, ssid); } sl_WlanSet(SL_WLAN_CFG_AP_ID, SL_WLAN_AP_OPT_SECURITY_TYPE, 1, (uint8_t*)&sec_type); - if (sec_type == SL_WLAN_SEC_TYPE_OPEN) + if (sec_type == SL_WLAN_SEC_TYPE_OPEN || + security_key == nullptr) { return; } @@ -780,6 +827,46 @@ void CC32xxWiFi::wlan_setup_ap(const char *ssid, const char *security_key, strlen(security_key), (uint8_t*)security_key); } +/* + * CC32xxWiFi::wlan_get_ap_config() + */ +void CC32xxWiFi::wlan_get_ap_config(string *ssid, SecurityType *security_type) +{ + if (ssid) + { + // Reads AP SSID configuration from NWP. + ssid->clear(); + ssid->resize(33); + uint16_t len = ssid->size(); + uint16_t config_opt = SL_WLAN_AP_OPT_SSID; + sl_WlanGet(SL_WLAN_CFG_AP_ID, &config_opt, &len, (_u8*) &(*ssid)[0]); + ssid->resize(len); + } + if (security_type) + { + uint16_t len = sizeof(*security_type); + uint16_t config_opt = SL_WLAN_AP_OPT_SECURITY_TYPE; + sl_WlanGet(SL_WLAN_CFG_AP_ID, &config_opt, &len, (_u8*) security_type); + } +} + +int CC32xxWiFi::wlan_get_ap_station_count() +{ + if (wlanRole != WlanRole::AP) + { + return 0; + } + uint8_t num_connected = 0; + uint16_t len = sizeof(num_connected); + auto status = sl_NetCfgGet( + SL_NETCFG_AP_STATIONS_NUM_CONNECTED, NULL, &len, &num_connected); + if (status) + { + return -1; + } + return num_connected; +} + void CC32xxWiFi::connecting_update_blinker() { if (!connected) @@ -805,8 +892,10 @@ void CC32xxWiFi::set_default_state() } SlCheckError(result); - if (wlanRole == WlanRole::AP) + if (wlanRole == WlanRole::AP || + (wlanRole == WlanRole::UNKNOWN && result == ROLE_AP)) { + wlanRole = WlanRole::AP; if (result != ROLE_AP) { sl_WlanSetMode(ROLE_AP); @@ -821,6 +910,7 @@ void CC32xxWiFi::set_default_state() } else { + wlanRole = WlanRole::STA; if (wlan_profile_test_none()) { /* no profiles saved, add the default profile */ @@ -845,6 +935,24 @@ void CC32xxWiFi::set_default_state() started = true; } +/* + * CC32xxWiFi::wlan_set_role() + */ +void CC32xxWiFi::wlan_set_role(WlanRole new_role) +{ + switch (new_role) + { + case WlanRole::STA: + sl_WlanSetMode(ROLE_STA); + break; + case WlanRole::AP: + sl_WlanSetMode(ROLE_AP); + break; + default: + DIE("Unsupported wlan role"); + } +} + /* * CC32xxWiFi::wlan_task() */ @@ -1192,8 +1300,6 @@ void CC32xxWiFi::net_app_event_handler(NetAppEvent *event) // event_data = &event->EventData.ipLeased; // - SlIpLeasedAsync_t *ip_leased = &event->Data.IpLeased; - ipAddress = ip_leased->IpAddress; break; } case SL_NETAPP_EVENT_IP_COLLISION: @@ -1287,31 +1393,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: @@ -1319,6 +1435,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() @@ -1453,7 +1690,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 54267d73b..8f13a1547 100644 --- a/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx +++ b/src/freertos_drivers/net_cc32xx/CC32xxWiFi.hxx @@ -44,6 +44,7 @@ #include "freertos_drivers/common/WifiDefs.hxx" class CC32xxSocket; +class NetworkSpace; /** Interface that aids in unit testing. */ @@ -117,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; @@ -150,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(); @@ -211,6 +236,18 @@ public: void wlan_setup_ap(const char *ssid, const char *security_key, SecurityType security_type) override; + /** Retrieve current AP config. + * @param ssid will be filled with the SSID of the AP + * @param security_type will be filled with the security type + */ + void wlan_get_ap_config(string *ssid, SecurityType *security_type); + + /** Retrieves how many stations are connected to the wifi in AP mode. + * @return number of connected stations (0 to 4). If not in AP mode, + * returns 0. + */ + int wlan_get_ap_station_count(); + /** @return true if the wlan interface is ready to establish outgoing * connections. */ bool wlan_ready() @@ -226,6 +263,13 @@ public: return wlanRole; } + /** Change the default Wlan Role. This will be used in the next start(...) + * if the UNKNOWN role is specified. The new setting takes effect when the + * device is restarted (either via reboot or stop + start). + * @param role new role. Must not be UNKNOWN + */ + void wlan_set_role(WlanRole new_role); + /** @return 0 if !wlan_ready, else a debugging status code. */ WlanState wlan_startup_state() { @@ -286,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 @@ -359,6 +410,15 @@ public: */ void wlan_mac(uint8_t mac[6]); + /** Sets the device MAC address. WARNING. The MAC address will be + * persistently set to the value indicated. Only a factory reset of the + * device can undo this operation. After calling this API there is no way + * to recover the factory MAC address. Make sure not to call this API too + * many times in the lifetime of the product, as flash wear is a concern. + * @param mac 6 byte array which holds the desired MAC address. + */ + void wlan_set_mac(uint8_t mac[6]); + /** Get the assigned IP address. * @return assigned IP address, else 0 if not assigned */ @@ -396,12 +456,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. * @@ -412,7 +474,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: @@ -431,18 +493,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_token_callback( + const char *token_name, std::function callback) + { + OSMutexLock l(&lock_); + 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_get_callback(std::pair, - const char *> callback) + void add_http_post_callback(const char *uri, PostFunction callback) { OSMutexLock l(&lock_); - httpGetCallbacks_.emplace_back(std::move(callback)); + 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 @@ -470,6 +562,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 @@ -481,24 +581,26 @@ public: static std::string get_version(); private: + friend class ::NetworkSpace; + /** Translates the SecurityType enum to the internal SimpleLink code. * @param sec_type security type * @return simplelink security type */ - uint8_t security_type_to_simplelink(SecurityType sec_type); + static uint8_t security_type_to_simplelink(SecurityType sec_type); /** Translates the SimpleLink code to SecurityType enum. * @param sec_type simplelink security type * @return security type */ - SecurityType security_type_from_simplelink(uint8_t sec_type); + static SecurityType security_type_from_simplelink(uint8_t sec_type); /** Translates the SimpleLink code from the network scan to SecurityType * enum. * @param sec_type simplelink network scan security result * @return security type */ - SecurityType security_type_from_scan(unsigned sec_type); + static SecurityType security_type_from_scan(unsigned sec_type); /** Set the CC32xx to its default state, including station mode. */ @@ -555,8 +657,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_; diff --git a/src/freertos_drivers/sources b/src/freertos_drivers/sources index 1851ce7e1..a570b39e6 100644 --- a/src/freertos_drivers/sources +++ b/src/freertos_drivers/sources @@ -4,6 +4,7 @@ VPATH := $(SRCDIR): \ CSRCS += CXXSRCS += Fileio.cxx \ + FileioWeak.cxx \ Device.cxx \ FileSystem.cxx \ DeviceBuffer.cxx \ @@ -25,7 +26,9 @@ CXXSRCS += Fileio.cxx \ WifiDefs.cxx \ PCA9685PWM.cxx \ SN74HC595GPO.cxx \ - TCAN4550Can.cxx + TCAN4550Can.cxx \ + SPIFlash.cxx \ + ifeq ($(TARGET),freertos.mips4k.pic32mx) @@ -37,11 +40,14 @@ endif ifeq ($(TARGET),freertos.armv7m) SUBDIRS += mbed_lpc1768 drivers_lpc1768 tivaware lpc_chip_175x_6x \ stm32cubef103xb stm32cubef303x_28x_58x_98x stm32cubef303xe \ - stm32cubef767xx \ + stm32cubel431xx stm32cubel432xx stm32cubef767xx \ cc3220sdk net_cc3220 cc3220 \ net_freertos_tcp freertos_tcp ti_grlib \ - spiffs_cc32x0sf spiffs_stm32f303xe spiffs_stm32f767xx \ - + spiffs_cc32x0sf spiffs_tm4c129 \ + spiffs_stm32f303xe spiffs_stm32f767xx \ + spiffs_spi \ + +#spiffs_tm4c123 \ ifdef BUILDTIVAWARE SUBDIRS += tivadriverlib tivausblib @@ -63,7 +69,12 @@ CXXSRCS += c++_operators.cxx endif ifeq ($(TARGET),bare.armv7m) -SUBDIRS += cc32xxsdk ti_grlib +SUBDIRS += cc32xxsdk ti_grlib stm32cubel431xx stm32cubel432xx + +ifdef BUILDTIVAWARE +SUBDIRS += tivadriverlib tivausblib +endif + endif ifeq ($(TARGET),bare.armv6m) 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/SPIFFS.hxx b/src/freertos_drivers/spiffs/SPIFFS.hxx index ce7035fb4..85f5d2b8e 100644 --- a/src/freertos_drivers/spiffs/SPIFFS.hxx +++ b/src/freertos_drivers/spiffs/SPIFFS.hxx @@ -101,7 +101,7 @@ public: void flush_cache() { mutex.lock(); - for (unsigned int i = 0; i < NUM_OPEN_FILES; i++) + for (unsigned int i = 0; i < numOpenFiles; i++) { if (files[i].inuse && files[i].dev == this && files[i].dirty) { 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/cc32x0sf/CC32x0SFSPIFFS.cxx b/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx index 0c090199c..77aee9694 100644 --- a/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx +++ b/src/freertos_drivers/spiffs/cc32x0sf/CC32x0SFSPIFFS.cxx @@ -31,14 +31,11 @@ * @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 +#define TISPIFFS_LOCK_NONE #include "spiffs.h" #include "inc/hw_types.h" @@ -46,173 +43,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..19b4d9222 --- /dev/null +++ b/src/freertos_drivers/spiffs/cc32x0sf/TiSPIFFSImpl.hxx @@ -0,0 +1,268 @@ +/** @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. + +#if defined(TISPIFFS_LOCK_ALL_INTERRUPTS) + +// Global disable interrupts. + +#define DI() asm("cpsid i\n") +#define EI() asm("cpsie i\n") + +#elif defined(TISPIFFS_LOCK_CRITICAL) + +// Critical section (interrupts better than MIN_SYSCALL_PRIORITY are still +// running). + +#define DI() portENTER_CRITICAL() +#define EI() portEXIT_CRITICAL() + +#elif defined(TISPIFFS_LOCK_BASEPRI_FF) + +// Disable interrupts with a priority limit of 0xFF (these are the lowest +// priority interrupts, including the FreeRTOS kernel task switch interrupt). +unsigned ppri; +constexpr unsigned minpri = 0xFF; +#define DI() \ + do \ + { \ + unsigned r; \ + __asm volatile(" mrs %0, basepri\n mov %1, %2\n msr basepri, %1\n" \ + : "=r"(ppri), "=r"(r) \ + : "i"(minpri) \ + : "memory"); \ + } while (0) +#define EI() __asm volatile(" msr basepri, %0\n" : : "r"(ppri) : "memory") + +#elif defined(TISPIFFS_LOCK_NOTICK) + +// Disable the systick timer to prevent preemptive multi-tasking from changing +// to a different task. + +static constexpr unsigned SYSTICKCFG = 0xE000E010; +#define DI() HWREG(SYSTICKCFG) &= ~2; +#define EI() HWREG(SYSTICKCFG) |= 2; + +#elif defined(TISPIFFS_LOCK_SCHEDULER_SUSPEND) + +// Disable freertos scheduler + +#define DI() vTaskSuspendAll() +#define EI() xTaskResumeAll() + +#elif defined(TISPIFFS_LOCK_NONE) + +// No write locking. + +#define DI() +#define EI() + +#elif defined(TISPIFFS_LOCK_CRASH) + +// Crashes if two different executions of this locking mechanism are +// concurrent. + +unsigned pend = 0; +#define DI() HASSERT(pend==0); pend=1; +#define EI() pend=0; + +#else +#error Must specify what kind of locking to use for TISPIFFS. +#endif + +// This ifdef decides whether we use the ROM or the flash based implementations +// for Flash write and erase. It also supports correcting for the reversed bank +// addresses. +#if 1 +#define FPG(data, addr, size) ROM_FlashProgram(data, (addr) ^ addr_mirror, size) +#define FER(addr) ROM_FlashErase((addr) ^ addr_mirror) +#else +#define FPG(data, addr, size) FlashProgram(data, (addr) ^ addr_mirror, size) +#define FER(addr) FlashErase((addr) ^ addr_mirror) +#endif + +// +// TiSPIFFS::flash_read() +// +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/cc32x0sf/spiffs_config.h b/src/freertos_drivers/spiffs/cc32x0sf/spiffs_config.h deleted file mode 100644 index 87e0dca60..000000000 --- a/src/freertos_drivers/spiffs/cc32x0sf/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/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/spiffs_config.h b/src/freertos_drivers/spiffs/spiffs_config.h new file mode 100644 index 000000000..87e0dca60 --- /dev/null +++ b/src/freertos_drivers/spiffs/spiffs_config.h @@ -0,0 +1,380 @@ +/* + * 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/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/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/TM4C129xSPIFFS.cxx b/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx new file mode 100644 index 000000000..79696e36b --- /dev/null +++ b/src/freertos_drivers/spiffs/tm4c129/TM4C129xSPIFFS.cxx @@ -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 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 +#define TISPIFFS_LOCK_BASEPRI_FF + +#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..96f2b5bad --- /dev/null +++ b/src/freertos_drivers/spiffs/tm4c129/spiffs_config.h @@ -0,0 +1 @@ +../spiffs_config.h \ No newline at end of file diff --git a/src/freertos_drivers/st/Stm32Blinker.hxx b/src/freertos_drivers/st/Stm32Blinker.hxx new file mode 100644 index 000000000..fd2bc4680 --- /dev/null +++ b/src/freertos_drivers/st/Stm32Blinker.hxx @@ -0,0 +1,161 @@ +/** \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 Stm32Blinker.hxx + * Implements the crash blinker using an STM32 timer. + * + * @author Balazs Racz + * @date 10 April 2022 + */ + +#ifndef _FREERTOS_DRIVERS_ST_STM32BLINKER_HXX_ +#define _FREERTOS_DRIVERS_ST_STM32BLINKER_HXX_ + +/* How to use: + +Add the following code to the hardware.hxx +``` + #define BLINKER_INTERRUPT_HANDLER timer14_interrupt_handler + + struct BlinkerHw + { + static constexpr uint32_t TIMER_BASE = TIM14_BASE; + static constexpr auto TIMER_IRQn = TIM14_IRQn; + static void clock_enable() + { + __HAL_RCC_TIM14_CLK_ENABLE(); + } + }; +``` + +then +``` +#include "freertos_drivers/st/Stm32Blinker.hxx" +``` + +in HwInit.cxx after hardware.hxx was included. Call setup_blinker() in +hw_preinit. +*/ + +#ifndef BLINKER_INTERRUPT_HANDLER +#error must include hardware.hxx before Stm32Blinker.hxx +#endif + +/// @return constexpr timer instance typecast to the correct struct pointer. +static inline TIM_TypeDef *get_blinker_timer() +{ + return (TIM_TypeDef *)BlinkerHw::TIMER_BASE; +} + +extern "C" { + +/// Call this function in hw_preinit to set up the blinker timer. +void setup_blinker() +{ + BlinkerHw::clock_enable(); + /* Initializes the blinker timer. */ + TIM_HandleTypeDef TimHandle; + memset(&TimHandle, 0, sizeof(TimHandle)); + TimHandle.Instance = get_blinker_timer(); + TimHandle.Init.Period = configCPU_CLOCK_HZ / 10000 / 5; + TimHandle.Init.Prescaler = 10000; + TimHandle.Init.ClockDivision = 0; + TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP; + TimHandle.Init.RepetitionCounter = 0; + if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK) + { + /* Initialization Error */ + HASSERT(0); + } + if (HAL_TIM_Base_Start_IT(&TimHandle) != HAL_OK) + { + /* Starting Error */ + HASSERT(0); + } + NVIC_SetPriority(BlinkerHw::TIMER_IRQn, 0); + NVIC_EnableIRQ(BlinkerHw::TIMER_IRQn); +} + +/// Stores the canonical pattern for the blinker. +uint32_t blinker_pattern = 0; +/// Stores what is left of the pattern during the current period. +static uint32_t rest_pattern = 0; + +extern void hw_set_to_safe(void); + +void resetblink(uint32_t pattern) +{ + blinker_pattern = pattern; + rest_pattern = pattern ? 1 : 0; + BLINKER_RAW_Pin::set(pattern ? true : false); + /* todo: make a timer event trigger immediately */ +} + +void setblink(uint32_t pattern) +{ + resetblink(pattern); +} + +void BLINKER_INTERRUPT_HANDLER(void) +{ + // + // Clear the timer interrupt. + // + get_blinker_timer()->SR = ~TIM_IT_UPDATE; + + // Set output LED. + BLINKER_RAW_Pin::set(rest_pattern & 1); + + // Shift and maybe reset pattern. + rest_pattern >>= 1; + if (!rest_pattern) + { + rest_pattern = blinker_pattern; + } +} + +void wait_with_blinker(void) +{ + if (get_blinker_timer()->SR & TIM_IT_UPDATE) + { + BLINKER_INTERRUPT_HANDLER(); + } +} + +void diewith(uint32_t pattern) +{ + asm("cpsid i\n"); + hw_set_to_safe(); + resetblink(pattern); + while (1) + { + wait_with_blinker(); + } +} + +} // extern "C" + +#endif // _FREERTOS_DRIVERS_ST_STM32BLINKER_HXX_ diff --git a/src/freertos_drivers/st/Stm32Can.cxx b/src/freertos_drivers/st/Stm32Can.cxx index c49fc8c5c..b62d25707 100644 --- a/src/freertos_drivers/st/Stm32Can.cxx +++ b/src/freertos_drivers/st/Stm32Can.cxx @@ -37,6 +37,8 @@ #include +#include "stm32f_hal_conf.hxx" + #if defined (STM32F072xB) || defined (STM32F091xC) #include "stm32f0xx_hal_cortex.h" @@ -63,6 +65,15 @@ #define CAN_SECOND_IRQN USB_LP_CAN_RX0_IRQn #define CAN_CLOCK (cm3_cpu_clock_hz >> 1) +#elif defined (STM32L431xx) || defined (STM32L432xx) + +#include "stm32l4xx_hal_cortex.h" +#define SPLIT_INT +#define CAN_TX_IRQN CAN1_TX_IRQn +#define CAN_IRQN CAN_TX_IRQN +#define CAN_SECOND_IRQN CAN1_RX0_IRQn +#define CAN_CLOCK (cm3_cpu_clock_hz) + #elif defined (STM32F767xx) #include "stm32f7xx_hal_cortex.h" @@ -103,11 +114,11 @@ Stm32Can::Stm32Can(const char *name) /* The priority of CAN interrupt is as high as possible while maintaining * FreeRTOS compatibility. */ - NVIC_SetPriority(CAN_IRQN, configKERNEL_INTERRUPT_PRIORITY); + SetInterruptPriority(CAN_IRQN, configKERNEL_INTERRUPT_PRIORITY); #ifdef SPLIT_INT HAL_NVIC_DisableIRQ(CAN_SECOND_IRQN); - NVIC_SetPriority(CAN_SECOND_IRQN, configKERNEL_INTERRUPT_PRIORITY); + SetInterruptPriority(CAN_SECOND_IRQN, configKERNEL_INTERRUPT_PRIORITY); #endif #endif } @@ -428,7 +439,7 @@ void usb_lp_can1_rx0_interrupt_handler(void) Stm32Can::instances[0]->rx_interrupt_handler(); } -#elif defined(STM32F767xx) +#elif defined(STM32F767xx) || defined(STM32L431xx) || defined(STM32L432xx) void can1_tx_interrupt_handler(void) { diff --git a/src/freertos_drivers/st/Stm32Can.hxx b/src/freertos_drivers/st/Stm32Can.hxx index 492273e86..86812386f 100644 --- a/src/freertos_drivers/st/Stm32Can.hxx +++ b/src/freertos_drivers/st/Stm32Can.hxx @@ -43,17 +43,7 @@ #include "freertos_drivers/common/Can.hxx" #endif -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_can.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_can.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_can.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_can.h" -#else -#error Dont know what STM32 chip you have. -#endif +#include "stm32f_hal_conf.hxx" /** Specialization of CAN driver for LPC17xx and LPC40xx CAN. */ diff --git a/src/freertos_drivers/st/Stm32DCCDecoder.hxx b/src/freertos_drivers/st/Stm32DCCDecoder.hxx index c92d5166d..71f72ee37 100644 --- a/src/freertos_drivers/st/Stm32DCCDecoder.hxx +++ b/src/freertos_drivers/st/Stm32DCCDecoder.hxx @@ -175,6 +175,24 @@ public: HW::after_feedback_hook(); } + /// How many usec later should the railcom cutout start happen. + static int time_delta_railcom_pre_usec() + { + return HW::time_delta_railcom_pre_usec(); + } + + /// How many usec later should the railcom cutout middle happen. + static int time_delta_railcom_mid_usec() + { + return HW::time_delta_railcom_mid_usec(); + } + + /// How many usec later should the railcom cutout middle happen. + static int time_delta_railcom_end_usec() + { + return HW::time_delta_railcom_end_usec(); + } + /// Called from the capture interrupt handler. Checks interrupt status, /// clears interrupt. /// @return true if the interrupt was generated by a capture event. @@ -204,7 +222,7 @@ public: // This code handles underflow of the timer correctly. We cannot wait // longer than one full cycle though (65 msec -- typical RailCom waits // are 20-500 usec). - uint32_t new_match_v = usecTimerStart_ + TIMER_MAX_VALUE + 1 - usec; + uint32_t new_match_v = usecTimerStart_ + usec; new_match_v &= 0xffff; __HAL_TIM_SET_COMPARE( usec_timer_handle(), HW::USEC_CHANNEL, new_match_v); @@ -227,7 +245,7 @@ public: static void set_cap_timer_time() { if (shared_timers()) { - usecTimerStart_ = get_capture_counter(); + usecTimerStart_ = get_raw_capture_counter(); } else { usecTimerStart_ = __HAL_TIM_GET_COUNTER(usec_timer_handle()); } @@ -262,13 +280,18 @@ private: // 1 usec per tick handle->Init.Prescaler = configCPU_CLOCK_HZ / 1000000; handle->Init.ClockDivision = 0; - handle->Init.CounterMode = TIM_COUNTERMODE_DOWN; + handle->Init.CounterMode = TIM_COUNTERMODE_UP; handle->Init.RepetitionCounter = 0; handle->Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; + HASSERT(HAL_TIM_IC_DeInit(handle) == HAL_OK); HASSERT(HAL_TIM_IC_Init(handle) == HAL_OK); } + /// Helper function to read the raw capture register value. + /// @return the value of the CCx register matching the capture channel. + static inline uint32_t get_raw_capture_counter(); + /// @return the hardware pointer to the capture timer. static TIM_TypeDef *capture_timer() { @@ -390,9 +413,9 @@ template void Stm32DccTimerModule::module_enable() HAL_NVIC_SetPriority(HW::TIMER_IRQn, 0, 0); HAL_NVIC_SetPriority(HW::OS_IRQn, 3, 0); #elif defined(GCC_ARMCM3) - NVIC_SetPriority(HW::CAPTURE_IRQn, 0x20); - NVIC_SetPriority(HW::TIMER_IRQn, 0x20); - NVIC_SetPriority(HW::OS_IRQn, configKERNEL_INTERRUPT_PRIORITY); + SetInterruptPriority(HW::CAPTURE_IRQn, 0x20); + SetInterruptPriority(HW::TIMER_IRQn, 0x20); + SetInterruptPriority(HW::OS_IRQn, configKERNEL_INTERRUPT_PRIORITY); #else #error not defined how to set interrupt priority #endif @@ -439,8 +462,34 @@ bool Stm32DccTimerModule::int_get_and_clear_capture_event() template uint32_t Stm32DccTimerModule::get_capture_counter() { - return HAL_TIM_ReadCapturedValue( - capture_timer_handle(), HW::CAPTURE_CHANNEL); + // Simulates down-counting. + return TIMER_MAX_VALUE - get_raw_capture_counter(); +} + +template uint32_t Stm32DccTimerModule::get_raw_capture_counter() +{ + // This if structure will be optimized away. + if (HW::CAPTURE_CHANNEL == TIM_CHANNEL_1) + { + return capture_timer()->CCR1; + } + else if (HW::CAPTURE_CHANNEL == TIM_CHANNEL_2) + { + return capture_timer()->CCR2; + } + else if (HW::CAPTURE_CHANNEL == TIM_CHANNEL_3) + { + return capture_timer()->CCR3; + } + else if (HW::CAPTURE_CHANNEL == TIM_CHANNEL_4) + { + return capture_timer()->CCR4; + } + else + { + DIE("Unknown capture channel"); + return 0; + } } template diff --git a/src/freertos_drivers/st/Stm32EEPROMEmulation.cxx b/src/freertos_drivers/st/Stm32EEPROMEmulation.cxx index f08a7708c..0fc44b1bb 100644 --- a/src/freertos_drivers/st/Stm32EEPROMEmulation.cxx +++ b/src/freertos_drivers/st/Stm32EEPROMEmulation.cxx @@ -35,21 +35,7 @@ #include -#if defined (STM32F030x6) || defined (STM32F031x6) || defined (STM32F038xx) \ - || defined (STM32F030x8) || defined (STM32F030xC) || defined (STM32F042x6) \ - || defined (STM32F048xx) || defined (STM32F051x8) || defined (STM32F058xx) \ - || defined (STM32F070x6) || defined (STM32F070xB) || defined (STM32F071xB) \ - || defined (STM32F072xB) || defined (STM32F078xx) \ - || defined (STM32F091xC) || defined (STM32F098xx) -#include "stm32f0xx_hal_flash.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_flash.h" -#elif defined(STM32F767xx) -#define F7_FLASH -#include "stm32f7xx_hal_flash.h" -#else -#error "stm32EEPROMEmulation unsupported STM32 device" -#endif +#include "stm32f_hal_conf.hxx" #if defined (STM32F030x6) || defined (STM32F031x6) || defined (STM32F038xx) \ || defined (STM32F030x8) || defined (STM32F030xC) || defined (STM32F042x6) \ @@ -64,11 +50,19 @@ const size_t EEPROMEmulation::BYTES_PER_BLOCK = 2; const size_t Stm32EEPROMEmulation::PAGE_SIZE = 0x800; const size_t EEPROMEmulation::BLOCK_SIZE = 4; const size_t EEPROMEmulation::BYTES_PER_BLOCK = 2; +#elif defined(STM32L432xx) || defined(STM32L431xx) +const size_t Stm32EEPROMEmulation::PAGE_SIZE = 0x800; +const size_t EEPROMEmulation::BLOCK_SIZE = 8; +const size_t EEPROMEmulation::BYTES_PER_BLOCK = 4; +#define L4_FLASH #elif defined(STM32F767xx) // Note this assumes single-bank usage const size_t Stm32EEPROMEmulation::PAGE_SIZE = 256*1024; const size_t EEPROMEmulation::BLOCK_SIZE = 8; const size_t EEPROMEmulation::BYTES_PER_BLOCK = 4; +#define F7_FLASH +#else +#error "stm32EEPROMEmulation unsupported STM32 device" #endif /** Constructor. @@ -133,7 +127,13 @@ void Stm32EEPROMEmulation::flash_erase(unsigned sector) FLASH_EraseInitTypeDef erase_init; erase_init.TypeErase = FLASH_TYPEERASE_PAGES; +#ifdef L4_FLASH + erase_init.Banks = FLASH_BANK_1; + uint32_t start_page = erase_init.Page = + (((uint32_t)address) - FLASH_BASE) / PAGE_SIZE; +#else erase_init.PageAddress = (uint32_t)address; +#endif erase_init.NbPages = SECTOR_SIZE / PAGE_SIZE; portENTER_CRITICAL(); @@ -142,11 +142,19 @@ void Stm32EEPROMEmulation::flash_erase(unsigned sector) // there. This is to make corruption less likely in case of a power // interruption happens. if (SECTOR_SIZE > PAGE_SIZE) { +#ifdef L4_FLASH + erase_init.Page += 1; +#else erase_init.PageAddress += PAGE_SIZE; +#endif erase_init.NbPages--; HAL_FLASHEx_Erase(&erase_init, &page_error); erase_init.NbPages = 1; +#ifdef L4_FLASH + erase_init.Page = start_page; +#else erase_init.PageAddress = (uint32_t)address; +#endif } HAL_FLASHEx_Erase(&erase_init, &page_error); HAL_FLASH_Lock(); @@ -183,8 +191,8 @@ void Stm32EEPROMEmulation::flash_program( { portENTER_CRITICAL(); HAL_FLASH_Unlock(); -#ifdef F7_FLASH - HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, uint_address, *data); +#if defined(F7_FLASH)|| defined(L4_FLASH) + HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, uint_address, *(uint64_t*)data); #else HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, uint_address, *data); #endif @@ -192,7 +200,7 @@ void Stm32EEPROMEmulation::flash_program( portEXIT_CRITICAL(); count -= BLOCK_SIZE; - uint_address += sizeof(uint32_t); - ++data; + uint_address += BLOCK_SIZE; + data += (BLOCK_SIZE / sizeof(uint32_t)); } } diff --git a/src/freertos_drivers/st/Stm32Gpio.hxx b/src/freertos_drivers/st/Stm32Gpio.hxx index ec2897897..868532010 100644 --- a/src/freertos_drivers/st/Stm32Gpio.hxx +++ b/src/freertos_drivers/st/Stm32Gpio.hxx @@ -39,17 +39,7 @@ #include "os/Gpio.hxx" #include "GpioWrapper.hxx" -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_gpio.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_gpio.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_gpio.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_gpio.h" -#else -#error Dont know what STM32 chip you have. -#endif +#include "stm32f_hal_conf.hxx" /// Static GPIO implementation for the STM32 microcontrollers. Do not use /// directly: use @ref GPIO_PIN macro. diff --git a/src/freertos_drivers/st/Stm32I2C.cxx b/src/freertos_drivers/st/Stm32I2C.cxx index f1c627f00..4884d2419 100644 --- a/src/freertos_drivers/st/Stm32I2C.cxx +++ b/src/freertos_drivers/st/Stm32I2C.cxx @@ -41,6 +41,9 @@ #include "stm32f1xx_ll_rcc.h" #elif defined(STM32F303xC) || defined(STM32F303xE) #include "stm32f3xx_ll_rcc.h" +#elif defined(STM32L431xx) || defined(STM32L432xx) +#include "stm32l4xx_ll_rcc.h" +#include "stm32l4xx_ll_i2c.h" #elif defined(STM32F767xx) #include "stm32f7xx_ll_rcc.h" #include "stm32f7xx_ll_i2c.h" @@ -159,9 +162,9 @@ Stm32I2C::Stm32I2C(const char *name, I2C_TypeDef *port, uint32_t ev_interrupt, // call above. i2cHandle_.Init.Timing = (uint32_t) this; - NVIC_SetPriority((IRQn_Type)ev_interrupt, configKERNEL_INTERRUPT_PRIORITY); + SetInterruptPriority((IRQn_Type)ev_interrupt, configKERNEL_INTERRUPT_PRIORITY); HAL_NVIC_EnableIRQ((IRQn_Type)ev_interrupt); - NVIC_SetPriority((IRQn_Type)er_interrupt, configKERNEL_INTERRUPT_PRIORITY); + SetInterruptPriority((IRQn_Type)er_interrupt, configKERNEL_INTERRUPT_PRIORITY); HAL_NVIC_EnableIRQ((IRQn_Type)er_interrupt); } diff --git a/src/freertos_drivers/st/Stm32I2C.hxx b/src/freertos_drivers/st/Stm32I2C.hxx index 1a509f9ba..062ba81cd 100644 --- a/src/freertos_drivers/st/Stm32I2C.hxx +++ b/src/freertos_drivers/st/Stm32I2C.hxx @@ -38,17 +38,7 @@ #include "I2C.hxx" -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_conf.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_conf.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_conf.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_conf.h" -#else -#error Dont know what STM32 chip you have. -#endif +#include "stm32f_hal_conf.hxx" /** Specialization of I2C driver for STM32 devices. */ diff --git a/src/freertos_drivers/st/Stm32RailcomSender.cxx b/src/freertos_drivers/st/Stm32RailcomSender.cxx new file mode 100644 index 000000000..d90f5a667 --- /dev/null +++ b/src/freertos_drivers/st/Stm32RailcomSender.cxx @@ -0,0 +1,88 @@ +/** \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 Stm32RailcomSender.hxx + * + * Implements a RailcomDriver that sends outgoing railcom data through a UART + * TX on an STM32 target. Designed to be invoked from the priority zero + * interrupt of the DCC decoder. + * + * @author Balazs Racz + * @date July 10, 2021 + */ + +#include "freertos_drivers/st/Stm32RailcomSender.hxx" + +#include "stm32f_hal_conf.hxx" + +/// Called at the beginning of the first window. +void Stm32RailcomSender::start_cutout() +{ + const auto* pkt = ch1Pkt_; + if (!pkt || pkt->feedbackKey != expectedFeedbackKey_) + { + // Nothing to send or came too late and should not be sent. + return; + } + if (!__HAL_UART_GET_IT(&uartHandle, UART_IT_TXE)) { + // Transmission is not complete yet. That's weird. + return; + } + if (pkt->ch1Size == 0) { + // Nothing to send. + return; + } + uartHandle.Instance->TDR = pkt->ch1Data[0]; + if (pkt->ch1Size > 1) + { + txBuf->put(pkt->ch1Data + 1, 1); + __HAL_UART_ENABLE_IT(&uartHandle, UART_IT_TXE); + } +} + +void Stm32RailcomSender::middle_cutout() +{ + const auto* pkt = ch2Pkt_; + if (!pkt || pkt->feedbackKey != expectedFeedbackKey_) + { + // Nothing to send or came too late and should not be sent. + return; + } + if (!__HAL_UART_GET_IT(&uartHandle, UART_IT_TXE)) { + // Transmission is not complete yet. That's weird. + return; + } + if (pkt->ch2Size == 0) { + // Nothing to send. + return; + } + uartHandle.Instance->TDR = pkt->ch2Data[0]; + if (pkt->ch2Size > 1) + { + txBuf->put(pkt->ch2Data + 1, pkt->ch2Size - 1); + __HAL_UART_ENABLE_IT(&uartHandle, UART_IT_TXE); + } +} diff --git a/src/freertos_drivers/st/Stm32RailcomSender.hxx b/src/freertos_drivers/st/Stm32RailcomSender.hxx new file mode 100644 index 000000000..eeff2291b --- /dev/null +++ b/src/freertos_drivers/st/Stm32RailcomSender.hxx @@ -0,0 +1,126 @@ +/** \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 Stm32RailcomSender.hxx + * + * Implements a RailcomDriver that sends outgoing railcom data through a UART + * TX on an STM32 target. Designed to be invoked from the priority zero + * interrupt of the DCC decoder. + * + * @author Balazs Racz + * @date July 10, 2021 + */ + +#include "freertos_drivers/common/RailcomDriver.hxx" +#include "dcc/railcom.h" +#include "freertos_drivers/st/Stm32Uart.hxx" + +class Stm32RailcomSender : public RailcomDriver, protected Stm32Uart +{ +public: + Stm32RailcomSender( + const char *name, USART_TypeDef *base, IRQn_Type interrupt) + : Stm32Uart(name, base, interrupt) + { + } + +public: + /// Specifies what packet should be sent for the channel1 cutout. It is + /// okay to specify the same packet pointer for ch1 and ch2 cutout. + /// @param ch1_pkt the RailCom packet. Only the ch1 data will be read from + /// this packet. This pointer must stay alive until the next DCC packet + /// comes. The FeedbackKey in this packet must be correct for the current + /// DCC packet or else the data will not be sent. + void send_ch1(const DCCFeedback *ch1_pkt) override + { + ch1Pkt_ = ch1_pkt; + } + + /// Specifies what packet should be sent for the channel2 cutout. It is + /// okay to specify the same packet pointer for ch1 and ch2 cutout. + /// @param ch2_pkt the RailCom packet. Only the ch2 data will be read from + /// this packet. This pointer must stay alive until the next DCC packet + /// comes. The FeedbackKey in this packet must be correct for the current + /// DCC packet or else the data will not be sent. + void send_ch2(const DCCFeedback *ch2_pkt) override + { + ch2Pkt_ = ch2_pkt; + } + + ssize_t write(File *file, const void *buf, size_t count) override + { + // We do not support writing through the regular posix API. + return -EIO; + } + + ssize_t read(File *file, void *buf, size_t count) override + { + // We do not support reading through the regular posix API. + return -EIO; + } + +private: + // ======= DCC driver API ======== + /// No implementation needed. + void feedback_sample() override + { + } + /// Called at the beginning of the first window. + void start_cutout() override; + /// Called at the beginning of the middle window. + void middle_cutout() override; + /// Called after the cutout is over. + void end_cutout() override { + // We throw away the packets that we got given. + //ch1Pkt_ = nullptr; + //ch2Pkt_ = nullptr; + } + /// Called instead of start/mid/end-cutout at the end of the current packet + /// if there was no cutout requested. + void no_cutout() override + { + // We throw away the packets that we got given. + //ch1Pkt_ = nullptr; + //ch2Pkt_ = nullptr; + } + /// Feedback key is set by the DCC decoder driver. The feedback packet must + /// carry the same feedback key or else it will not be transmitted. + void set_feedback_key(uint32_t key) override + { + expectedFeedbackKey_ = key; + } + +private: + /// What should be the feedback key in the packet. This value comes from + /// the DCC driver and is compared to the RailCom packets we should be + /// sending at the beginning of the cutout windows. + uintptr_t expectedFeedbackKey_ = 0; + + /// The packet to send in channel 1. Externally owned. + const DCCFeedback *ch1Pkt_ = nullptr; + /// The packet to send in channel 2. Externally owned. + const DCCFeedback *ch2Pkt_ = nullptr; +}; diff --git a/src/freertos_drivers/st/Stm32SPI.cxx b/src/freertos_drivers/st/Stm32SPI.cxx index d4f6bea23..3e3170b2a 100644 --- a/src/freertos_drivers/st/Stm32SPI.cxx +++ b/src/freertos_drivers/st/Stm32SPI.cxx @@ -41,6 +41,8 @@ #include "stm32f1xx_ll_rcc.h" #elif defined(STM32F303xC) || defined(STM32F303xE) #include "stm32f3xx_ll_rcc.h" +#elif defined(STM32L432xx) || defined(STM32L431xx) +#include "stm32l4xx_ll_rcc.h" #elif defined(STM32F767xx) #include "stm32f7xx_ll_rcc.h" #else diff --git a/src/freertos_drivers/st/Stm32SPI.hxx b/src/freertos_drivers/st/Stm32SPI.hxx index c4953e1d8..33154da2e 100644 --- a/src/freertos_drivers/st/Stm32SPI.hxx +++ b/src/freertos_drivers/st/Stm32SPI.hxx @@ -38,19 +38,7 @@ #include "SPI.hxx" - -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_conf.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_conf.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_conf.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_conf.h" -#else -#error Dont know what STM32 chip you have. -#endif - +#include "stm32f_hal_conf.hxx" /** Specialization of SPI driver for STM32 devices. */ diff --git a/src/freertos_drivers/st/Stm32Uart.cxx b/src/freertos_drivers/st/Stm32Uart.cxx index 3c2dd9e87..fcee7849b 100644 --- a/src/freertos_drivers/st/Stm32Uart.cxx +++ b/src/freertos_drivers/st/Stm32Uart.cxx @@ -33,12 +33,17 @@ #include "Stm32Uart.hxx" +#include "freertos/tc_ioctl.h" +#include "stm32f_hal_conf.hxx" + #if defined(STM32F072xB) || defined(STM32F091xC) #include "stm32f0xx_hal_cortex.h" #elif defined(STM32F103xB) #include "stm32f1xx_hal_cortex.h" #elif defined(STM32F303xC) || defined(STM32F303xE) #include "stm32f3xx_hal_cortex.h" +#elif defined(STM32L431xx) || defined(STM32L432xx) +#include "stm32l4xx_hal_cortex.h" #elif defined(STM32F767xx) #include "stm32f7xx_hal_cortex.h" #else @@ -52,9 +57,15 @@ Stm32Uart *Stm32Uart::instances[1] = {NULL}; #elif defined (STM32F030x8) || defined (STM32F042x6) || defined (STM32F048xx) \ || defined (STM32F051x8) || defined (STM32F058xx) || defined (STM32F070x6) Stm32Uart *Stm32Uart::instances[2] = {NULL}; +#elif defined (STM32L432xx) +Stm32Uart *Stm32Uart::instances[3] = {NULL}; +#define USART3 LPUART1 #elif defined (STM32F070xB) || defined (STM32F071xB) || defined (STM32F072xB) \ || defined (STM32F078xx) Stm32Uart *Stm32Uart::instances[4] = {NULL}; +#elif defined (STM32L431xx) +Stm32Uart *Stm32Uart::instances[4] = {NULL}; +#define USART4 LPUART1 #elif defined (STM32F303xC) || defined (STM32F303xE) Stm32Uart *Stm32Uart::instances[5] = {NULL}; #define USART4 UART4 @@ -94,6 +105,7 @@ Stm32Uart::Stm32Uart(const char *name, USART_TypeDef *base, IRQn_Type interrupt) { instances[2] = this; } +#if !defined(STM32L432xx) #ifdef USART4 else if (base == USART4) #elif defined(UART4) @@ -103,7 +115,7 @@ Stm32Uart::Stm32Uart(const char *name, USART_TypeDef *base, IRQn_Type interrupt) instances[3] = this; } #if !defined (STM32F070xB) && !defined (STM32F071xB) && !defined (STM32F072xB) \ - && !defined (STM32F078xx) + && !defined (STM32F078xx) && !defined(STM32L431xx) #ifdef USART5 else if (base == USART5) #elif defined(UART5) @@ -141,6 +153,7 @@ Stm32Uart::Stm32Uart(const char *name, USART_TypeDef *base, IRQn_Type interrupt) #endif #endif #endif +#endif #endif else { @@ -152,7 +165,7 @@ Stm32Uart::Stm32Uart(const char *name, USART_TypeDef *base, IRQn_Type interrupt) HAL_NVIC_SetPriority(interrupt, 3, 0); #elif defined(GCC_ARMCM3) // Below kernel-compatible interrupt priority. - NVIC_SetPriority(interrupt, configMAX_SYSCALL_INTERRUPT_PRIORITY + 0x20); + SetInterruptPriority(interrupt, configMAX_SYSCALL_INTERRUPT_PRIORITY + 0x20); #else #error not defined how to set interrupt priority #endif @@ -220,6 +233,21 @@ void Stm32Uart::disable() HAL_UART_DeInit(&uartHandle); } +int Stm32Uart::ioctl(File *file, unsigned long int key, unsigned long data) +{ + switch (key) + { + default: + return -EINVAL; + case TCBAUDRATE: + uartHandle.Init.BaudRate = data; + volatile auto ret = HAL_UART_Init(&uartHandle); + HASSERT(HAL_OK == ret); + break; + } + return 0; +} + /** Try and transmit a message. */ void Stm32Uart::tx_char() diff --git a/src/freertos_drivers/st/Stm32Uart.hxx b/src/freertos_drivers/st/Stm32Uart.hxx index 781e688d9..aa403d9c8 100644 --- a/src/freertos_drivers/st/Stm32Uart.hxx +++ b/src/freertos_drivers/st/Stm32Uart.hxx @@ -36,21 +36,7 @@ #include -#if defined(STM32F072xB) || defined(STM32F091xC) -#include "stm32f0xx_hal_dma.h" -#include "stm32f0xx_hal_uart.h" -#elif defined(STM32F103xB) -#include "stm32f1xx_hal_dma.h" -#include "stm32f1xx_hal_uart.h" -#elif defined(STM32F303xC) || defined(STM32F303xE) -#include "stm32f3xx_hal_dma.h" -#include "stm32f3xx_hal_uart.h" -#elif defined(STM32F767xx) -#include "stm32f7xx_hal_dma.h" -#include "stm32f7xx_hal_uart.h" -#else -#error Dont know what STM32 chip you have. -#endif +#include "stm32f_hal_conf.hxx" #include "Serial.hxx" @@ -77,7 +63,11 @@ public: */ static void interrupt_handler(unsigned index); -private: + /** Request an ioctl transaction. Supported ioctl is TCBAUDRATE from + * include/freertos/tc_ioctl.h */ + int ioctl(File *file, unsigned long int key, unsigned long data) override; + +protected: void enable() override; /**< function to enable device */ void disable() override; /**< function to disable device */ @@ -105,6 +95,12 @@ private: || defined (STM32F051x8) || defined (STM32F058xx) || defined (STM32F070x6) /** Instance pointers help us get context from the interrupt handler(s) */ static Stm32Uart *instances[2]; +#elif defined (STM32L432xx) + /** Instance pointers help us get context from the interrupt handler(s) */ + static Stm32Uart *instances[3]; +#elif defined (STM32L431xx) + /** Instance pointers help us get context from the interrupt handler(s) */ + static Stm32Uart *instances[4]; #elif defined (STM32F070xB) || defined (STM32F071xB) || defined (STM32F072xB) \ || defined (STM32F078xx) /** Instance pointers help us get context from the interrupt handler(s) */ @@ -119,6 +115,7 @@ private: static Stm32Uart *instances[8]; #endif +private: /** Default constructor. */ Stm32Uart(); diff --git a/src/freertos_drivers/st/stm32f0xx_hal_conf.h b/src/freertos_drivers/st/stm32f0xx_hal_conf.h index 883654b61..ee4a5d6fe 100644 --- a/src/freertos_drivers/st/stm32f0xx_hal_conf.h +++ b/src/freertos_drivers/st/stm32f0xx_hal_conf.h @@ -336,7 +336,12 @@ extern const uint32_t HSEValue; #else #define assert_param(expr) ((void)0) #endif /* USE_FULL_ASSERT */ - + + static inline void SetInterruptPriority(uint32_t irq, uint8_t priority) + { + NVIC_SetPriority((IRQn_Type)irq, priority >> (8U - __NVIC_PRIO_BITS)); + } + #ifdef __cplusplus } #endif diff --git a/src/freertos_drivers/st/stm32f1xx_hal_conf.h b/src/freertos_drivers/st/stm32f1xx_hal_conf.h index ce83ae12f..479bde038 100644 --- a/src/freertos_drivers/st/stm32f1xx_hal_conf.h +++ b/src/freertos_drivers/st/stm32f1xx_hal_conf.h @@ -406,6 +406,11 @@ #define assert_param(expr) ((void)0) #endif /* USE_FULL_ASSERT */ + static inline void SetInterruptPriority(uint32_t irq, uint8_t priority) + { + NVIC_SetPriority((IRQn_Type)irq, priority >> (8U - __NVIC_PRIO_BITS)); + } + #ifdef __cplusplus } #endif diff --git a/src/freertos_drivers/st/stm32f3xx_hal_conf.h b/src/freertos_drivers/st/stm32f3xx_hal_conf.h index dfa51146d..4cfc9bb71 100644 --- a/src/freertos_drivers/st/stm32f3xx_hal_conf.h +++ b/src/freertos_drivers/st/stm32f3xx_hal_conf.h @@ -371,7 +371,12 @@ #else #define assert_param(expr) ((void)0) #endif /* USE_FULL_ASSERT */ - + + static inline void SetInterruptPriority(uint32_t irq, uint8_t priority) + { + NVIC_SetPriority((IRQn_Type)irq, priority >> (8U - __NVIC_PRIO_BITS)); + } + #ifdef __cplusplus } #endif diff --git a/src/freertos_drivers/st/stm32f7xx_hal_conf.h b/src/freertos_drivers/st/stm32f7xx_hal_conf.h index 37ebba91d..2ebad7cdc 100644 --- a/src/freertos_drivers/st/stm32f7xx_hal_conf.h +++ b/src/freertos_drivers/st/stm32f7xx_hal_conf.h @@ -466,6 +466,10 @@ #define assert_param(expr) ((void)0U) #endif /* USE_FULL_ASSERT */ + static inline void SetInterruptPriority(uint32_t irq, uint8_t priority) + { + NVIC_SetPriority((IRQn_Type)irq, priority >> (8U - __NVIC_PRIO_BITS)); + } #ifdef __cplusplus } diff --git a/src/freertos_drivers/st/stm32f_hal_conf.hxx b/src/freertos_drivers/st/stm32f_hal_conf.hxx index 880a939a5..7b669841a 100644 --- a/src/freertos_drivers/st/stm32f_hal_conf.hxx +++ b/src/freertos_drivers/st/stm32f_hal_conf.hxx @@ -47,6 +47,8 @@ #include "stm32f3xx_hal_conf.h" #elif defined(STM32F767xx) #include "stm32f7xx_hal_conf.h" +#elif defined(STM32L432xx) || defined(STM32L431xx) +#include "stm32l4xx_hal_conf.h" #else #error "STM32F_HAL_CONF unsupported STM32 device" #endif diff --git a/src/freertos_drivers/st/stm32l4xx_hal_conf.h b/src/freertos_drivers/st/stm32l4xx_hal_conf.h new file mode 100644 index 000000000..efb3559bf --- /dev/null +++ b/src/freertos_drivers/st/stm32l4xx_hal_conf.h @@ -0,0 +1,354 @@ +/** + ****************************************************************************** + * @file stm32l4xx_hal_conf.h + * @author MCD Application Team + * @brief HAL configuration file. + ****************************************************************************** + * @attention + * + *

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

+ * + * This software component is licensed by ST under BSD 3-Clause license, + * the "License"; You may not use this file except in compliance with the + * License. You may obtain a copy of the License at: + * opensource.org/licenses/BSD-3-Clause + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32L4xx_HAL_CONF_H +#define __STM32L4xx_HAL_CONF_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/* Exported types ------------------------------------------------------------*/ +/* Exported constants --------------------------------------------------------*/ + +/* ########################## Module Selection ############################## */ +/** + * @brief This is the list of modules to be used in the HAL driver + */ +#define HAL_MODULE_ENABLED +#define HAL_ADC_MODULE_ENABLED +#define HAL_CAN_MODULE_ENABLED +/* #define HAL_CAN_LEGACY_MODULE_ENABLED */ +#define HAL_COMP_MODULE_ENABLED +#define HAL_CORTEX_MODULE_ENABLED +#define HAL_CRC_MODULE_ENABLED +#define HAL_CRYP_MODULE_ENABLED +#define HAL_DAC_MODULE_ENABLED +#define HAL_DMA_MODULE_ENABLED +#define HAL_FIREWALL_MODULE_ENABLED +#define HAL_FLASH_MODULE_ENABLED +#define HAL_GPIO_MODULE_ENABLED +#define HAL_I2C_MODULE_ENABLED +#define HAL_IRDA_MODULE_ENABLED +#define HAL_IWDG_MODULE_ENABLED +#define HAL_LPTIM_MODULE_ENABLED +#define HAL_MMC_MODULE_ENABLED +#define HAL_OPAMP_MODULE_ENABLED +#define HAL_PCD_MODULE_ENABLED +#define HAL_PWR_MODULE_ENABLED +#define HAL_QSPI_MODULE_ENABLED +#define HAL_RCC_MODULE_ENABLED +#define HAL_RNG_MODULE_ENABLED +#define HAL_RTC_MODULE_ENABLED +#define HAL_SAI_MODULE_ENABLED +#define HAL_SMARTCARD_MODULE_ENABLED +#define HAL_SMBUS_MODULE_ENABLED +#define HAL_SPI_MODULE_ENABLED +#define HAL_SWPMI_MODULE_ENABLED +#define HAL_TIM_MODULE_ENABLED +#define HAL_TSC_MODULE_ENABLED +#define HAL_UART_MODULE_ENABLED +#define HAL_USART_MODULE_ENABLED +#define HAL_WWDG_MODULE_ENABLED + + +/* ########################## Oscillator Values adaptation ####################*/ +/** + * @brief Adjust the value of External High Speed oscillator (HSE) used in your application. + * This value is used by the RCC HAL module to compute the system frequency + * (when HSE is used as system clock source, directly or through the PLL). + */ +extern const uint32_t HSEValue; /*!< Value of the External oscillator in Hz */ +#define HSE_VALUE HSEValue + +#if !defined (HSE_STARTUP_TIMEOUT) + #define HSE_STARTUP_TIMEOUT 100U /*!< Time out for HSE start up, in ms */ +#endif /* HSE_STARTUP_TIMEOUT */ + +/** + * @brief Internal Multiple Speed oscillator (MSI) default value. + * This value is the default MSI range value after Reset. + */ +#if !defined (MSI_VALUE) + #define MSI_VALUE 4000000U /*!< Value of the Internal oscillator in Hz*/ +#endif /* MSI_VALUE */ + +/** + * @brief Internal High Speed oscillator (HSI) value. + * This value is used by the RCC HAL module to compute the system frequency + * (when HSI is used as system clock source, directly or through the PLL). + */ +#if !defined (HSI_VALUE) + #define HSI_VALUE 16000000U /*!< Value of the Internal oscillator in Hz*/ +#endif /* HSI_VALUE */ + +/** + * @brief Internal High Speed oscillator (HSI48) value for USB FS, SDMMC and RNG. + * This internal oscillator is mainly dedicated to provide a high precision clock to + * the USB peripheral by means of a special Clock Recovery System (CRS) circuitry. + * When the CRS is not used, the HSI48 RC oscillator runs on it default frequency + * which is subject to manufacturing process variations. + */ +#if !defined (HSI48_VALUE) + #define HSI48_VALUE 48000000U /*!< Value of the Internal High Speed oscillator for USB FS/SDMMC/RNG in Hz. + The real value my vary depending on manufacturing process variations.*/ +#endif /* HSI48_VALUE */ + +/** + * @brief Internal Low Speed oscillator (LSI) value. + */ +#if !defined (LSI_VALUE) + #define LSI_VALUE 32000U /*!< LSI Typical Value in Hz*/ +#endif /* LSI_VALUE */ /*!< Value of the Internal Low Speed oscillator in Hz + The real value may vary depending on the variations + in voltage and temperature.*/ +/** + * @brief External Low Speed oscillator (LSE) value. + * This value is used by the UART, RTC HAL module to compute the system frequency + */ +#if !defined (LSE_VALUE) + #define LSE_VALUE 32768U /*!< Value of the External oscillator in Hz*/ +#endif /* LSE_VALUE */ + +#if !defined (LSE_STARTUP_TIMEOUT) + #define LSE_STARTUP_TIMEOUT 5000U /*!< Time out for LSE start up, in ms */ +#endif /* HSE_STARTUP_TIMEOUT */ + +/** + * @brief External clock source for SAI1 peripheral + * This value is used by the RCC HAL module to compute the SAI1 & SAI2 clock source + * frequency. + */ +#if !defined (EXTERNAL_SAI1_CLOCK_VALUE) + #define EXTERNAL_SAI1_CLOCK_VALUE 48000U /*!< Value of the SAI1 External clock source in Hz*/ +#endif /* EXTERNAL_SAI1_CLOCK_VALUE */ + +/** + * @brief External clock source for SAI2 peripheral + * This value is used by the RCC HAL module to compute the SAI1 & SAI2 clock source + * frequency. + */ +#if !defined (EXTERNAL_SAI2_CLOCK_VALUE) + #define EXTERNAL_SAI2_CLOCK_VALUE 48000U /*!< Value of the SAI2 External clock source in Hz*/ +#endif /* EXTERNAL_SAI2_CLOCK_VALUE */ + +/* Tip: To avoid modifying this file each time you need to use different HSE, + === you can define the HSE value in your toolchain compiler preprocessor. */ + +/* ########################### System Configuration ######################### */ +/** + * @brief This is the HAL system configuration section + */ +#define VDD_VALUE 3300U /*!< Value of VDD in mv */ +#define TICK_INT_PRIORITY 0x0FU /*!< tick interrupt priority */ +#define USE_RTOS 0U +#define PREFETCH_ENABLE 0U +#define INSTRUCTION_CACHE_ENABLE 1U +#define DATA_CACHE_ENABLE 1U + +/* ########################## Assert Selection ############################## */ +/** + * @brief Uncomment the line below to expanse the "assert_param" macro in the + * HAL drivers code + */ +/* #define USE_FULL_ASSERT 1U */ + +/* ################## SPI peripheral configuration ########################## */ + +/* CRC FEATURE: Use to activate CRC feature inside HAL SPI Driver + * Activated: CRC code is present inside driver + * Deactivated: CRC code cleaned from driver + */ + +#define USE_SPI_CRC 1U + +/* Includes ------------------------------------------------------------------*/ +/** + * @brief Include module's header file + */ + +#ifdef HAL_RCC_MODULE_ENABLED + #include "stm32l4xx_hal_rcc.h" +#endif /* HAL_RCC_MODULE_ENABLED */ + +#ifdef HAL_GPIO_MODULE_ENABLED + #include "stm32l4xx_hal_gpio.h" +#endif /* HAL_GPIO_MODULE_ENABLED */ + +#ifdef HAL_DMA_MODULE_ENABLED + #include "stm32l4xx_hal_dma.h" +#endif /* HAL_DMA_MODULE_ENABLED */ + +#ifdef HAL_CORTEX_MODULE_ENABLED + #include "stm32l4xx_hal_cortex.h" +#endif /* HAL_CORTEX_MODULE_ENABLED */ + +#ifdef HAL_ADC_MODULE_ENABLED + #include "stm32l4xx_hal_adc.h" +#endif /* HAL_ADC_MODULE_ENABLED */ + +#ifdef HAL_CAN_MODULE_ENABLED + #include "stm32l4xx_hal_can.h" +#endif /* HAL_CAN_MODULE_ENABLED */ + +#ifdef HAL_CAN_LEGACY_MODULE_ENABLED + #include "Legacy/stm32l4xx_hal_can_legacy.h" +#endif /* HAL_CAN_LEGACY_MODULE_ENABLED */ + +#ifdef HAL_COMP_MODULE_ENABLED + #include "stm32l4xx_hal_comp.h" +#endif /* HAL_COMP_MODULE_ENABLED */ + +#ifdef HAL_CRC_MODULE_ENABLED + #include "stm32l4xx_hal_crc.h" +#endif /* HAL_CRC_MODULE_ENABLED */ + +#ifdef HAL_CRYP_MODULE_ENABLED + #include "stm32l4xx_hal_cryp.h" +#endif /* HAL_CRYP_MODULE_ENABLED */ + +#ifdef HAL_DAC_MODULE_ENABLED + #include "stm32l4xx_hal_dac.h" +#endif /* HAL_DAC_MODULE_ENABLED */ + +#ifdef HAL_FIREWALL_MODULE_ENABLED + #include "stm32l4xx_hal_firewall.h" +#endif /* HAL_FIREWALL_MODULE_ENABLED */ + +#ifdef HAL_FLASH_MODULE_ENABLED + #include "stm32l4xx_hal_flash.h" +#endif /* HAL_FLASH_MODULE_ENABLED */ + +#ifdef HAL_I2C_MODULE_ENABLED + #include "stm32l4xx_hal_i2c.h" +#endif /* HAL_I2C_MODULE_ENABLED */ + +#ifdef HAL_IWDG_MODULE_ENABLED + #include "stm32l4xx_hal_iwdg.h" +#endif /* HAL_IWDG_MODULE_ENABLED */ + +#ifdef HAL_LPTIM_MODULE_ENABLED +#include "stm32l4xx_hal_lptim.h" +#endif /* HAL_LPTIM_MODULE_ENABLED */ + +#ifdef HAL_MMC_MODULE_ENABLED +#include "stm32l4xx_hal_mmc.h" +#endif /* HAL_MMC_MODULE_ENABLED */ + +#ifdef HAL_OPAMP_MODULE_ENABLED +#include "stm32l4xx_hal_opamp.h" +#endif /* HAL_OPAMP_MODULE_ENABLED */ + +#ifdef HAL_PWR_MODULE_ENABLED + #include "stm32l4xx_hal_pwr.h" +#endif /* HAL_PWR_MODULE_ENABLED */ + +#ifdef HAL_QSPI_MODULE_ENABLED + #include "stm32l4xx_hal_qspi.h" +#endif /* HAL_QSPI_MODULE_ENABLED */ + +#ifdef HAL_RNG_MODULE_ENABLED + #include "stm32l4xx_hal_rng.h" +#endif /* HAL_RNG_MODULE_ENABLED */ + +#ifdef HAL_RTC_MODULE_ENABLED + #include "stm32l4xx_hal_rtc.h" +#endif /* HAL_RTC_MODULE_ENABLED */ + +#ifdef HAL_SAI_MODULE_ENABLED + #include "stm32l4xx_hal_sai.h" +#endif /* HAL_SAI_MODULE_ENABLED */ + +#ifdef HAL_SMBUS_MODULE_ENABLED + #include "stm32l4xx_hal_smbus.h" +#endif /* HAL_SMBUS_MODULE_ENABLED */ + +#ifdef HAL_SPI_MODULE_ENABLED + #include "stm32l4xx_hal_spi.h" +#endif /* HAL_SPI_MODULE_ENABLED */ + +#ifdef HAL_SWPMI_MODULE_ENABLED + #include "stm32l4xx_hal_swpmi.h" +#endif /* HAL_SWPMI_MODULE_ENABLED */ + +#ifdef HAL_TIM_MODULE_ENABLED + #include "stm32l4xx_hal_tim.h" +#endif /* HAL_TIM_MODULE_ENABLED */ + +#ifdef HAL_TSC_MODULE_ENABLED + #include "stm32l4xx_hal_tsc.h" +#endif /* HAL_TSC_MODULE_ENABLED */ + +#ifdef HAL_UART_MODULE_ENABLED + #include "stm32l4xx_hal_uart.h" +#endif /* HAL_UART_MODULE_ENABLED */ + +#ifdef HAL_USART_MODULE_ENABLED + #include "stm32l4xx_hal_usart.h" +#endif /* HAL_USART_MODULE_ENABLED */ + +#ifdef HAL_IRDA_MODULE_ENABLED + #include "stm32l4xx_hal_irda.h" +#endif /* HAL_IRDA_MODULE_ENABLED */ + +#ifdef HAL_SMARTCARD_MODULE_ENABLED + #include "stm32l4xx_hal_smartcard.h" +#endif /* HAL_SMARTCARD_MODULE_ENABLED */ + +#ifdef HAL_WWDG_MODULE_ENABLED + #include "stm32l4xx_hal_wwdg.h" +#endif /* HAL_WWDG_MODULE_ENABLED */ + +#ifdef HAL_PCD_MODULE_ENABLED + #include "stm32l4xx_hal_pcd.h" +#endif /* HAL_PCD_MODULE_ENABLED */ + +/* Exported macro ------------------------------------------------------------*/ +#ifdef USE_FULL_ASSERT +/** + * @brief The assert_param macro is used for function's parameters check. + * @param expr: If expr is false, it calls assert_failed function + * which reports the name of the source file and the source + * line number of the call that failed. + * If expr is true, it returns no value. + * @retval None + */ + #define assert_param(expr) ((expr) ? (void)0U : assert_failed((uint8_t *)__FILE__, __LINE__)) +/* Exported functions ------------------------------------------------------- */ + void assert_failed(uint8_t *file, uint32_t line); +#else + #define assert_param(expr) ((void)0U) +#endif /* USE_FULL_ASSERT */ + + static inline void SetInterruptPriority(uint32_t irq, uint8_t priority) + { + NVIC_SetPriority((IRQn_Type)irq, priority >> (8U - __NVIC_PRIO_BITS)); + } + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32L4xx_HAL_CONF_H */ + + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/src/freertos_drivers/ti/CC3200GPIO.hxx b/src/freertos_drivers/ti/CC3200GPIO.hxx index 82c3f6c79..bd416c64e 100644 --- a/src/freertos_drivers/ti/CC3200GPIO.hxx +++ b/src/freertos_drivers/ti/CC3200GPIO.hxx @@ -28,6 +28,19 @@ * * Helper declarations for using GPIO pins on CC3200 MCUs. * + * Note about barriers: + * + * GCC is extremely efficient at optimizing the memory access instructions that + * we use for writing to GPIO output pins. In many cases writing to multiple + * GPIO pins turns into back-to-back Thumb instructions, and the hardware + * peripheral seems to be unable to process bus transactions this fast. We + * therefore add a barrier after each GPIO write. The barrier ensures that the + * execution continues after the GPIO write only once the transaction is + * successfully completed by the bus. We tested that back to back GPIO writes + * are operational. The barrier also ensures correct sequencing against other + * effects of the running program. One GPIO write is about 50 nsec (4 clock + * cycles), the shortest pulse we can generate is 100 nsec. + * * @author Balazs Racz * @date 2 Dec 2014 */ @@ -70,16 +83,22 @@ public: void write(Value new_state) const OVERRIDE { *pin_address() = (new_state ? 0xff : 0); + /// See note at the top of the file about barriers. + __asm__ volatile("dsb" : : : "memory"); } void set() const OVERRIDE { *pin_address() = 0xff; + /// See note at the top of the file about barriers. + __asm__ volatile("dsb" : : : "memory"); } void clr() const OVERRIDE { *pin_address() = 0; + /// See note at the top of the file about barriers. + __asm__ volatile("dsb" : : : "memory"); } Value read() const OVERRIDE @@ -171,6 +190,8 @@ public: volatile uint8_t *ptr = reinterpret_cast( GPIO_BASE + (((unsigned)GPIO_PIN) << 2)); *ptr = value ? 0xff : 0; + /// See note at the top of the file about barriers. + __asm__ volatile("dsb" : : : "memory"); } /// @return current value of the input pin: if true HIGH. static bool __attribute__((always_inline)) get() 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; } } diff --git a/src/freertos_drivers/ti/CC32xxSPI.hxx b/src/freertos_drivers/ti/CC32xxSPI.hxx index 4404ee883..c8476b780 100644 --- a/src/freertos_drivers/ti/CC32xxSPI.hxx +++ b/src/freertos_drivers/ti/CC32xxSPI.hxx @@ -206,7 +206,7 @@ private: do { /* fill TX FIFO but make sure we don't fill it to overflow */ - if (tx_len && (rx_len - tx_len) < (8 * sizeof(T))) + if (tx_len && ((rx_len - tx_len) < (32 / sizeof(T)))) { if (data_put_non_blocking(*tx_buf) != 0) { diff --git a/src/freertos_drivers/ti/CC32xxUart.cxx b/src/freertos_drivers/ti/CC32xxUart.cxx index 0c7601e04..1c72cd2ea 100644 --- a/src/freertos_drivers/ti/CC32xxUart.cxx +++ b/src/freertos_drivers/ti/CC32xxUart.cxx @@ -43,11 +43,14 @@ #include "driverlib/prcm.h" #include "driverlib/utils.h" #include "freertos/tc_ioctl.h" +#include "executor/Notifiable.hxx" #include "CC32xxUart.hxx" /** Instance pointers help us get context from the interrupt handler(s) */ static CC32xxUart *instances[2] = {NULL}; +/** Critical section lock between ISR and ioctl */ +static Atomic isr_lock; /** Constructor. * @param name name of this device instance in the file system @@ -64,13 +67,19 @@ CC32xxUart::CC32xxUart(const char *name, unsigned long base, uint32_t interrupt, TxEnableMethod tx_enable_assert, TxEnableMethod tx_enable_deassert) : Serial(name) - , txEnableAssert(tx_enable_assert) - , txEnableDeassert(tx_enable_deassert) - , base(base) - , interrupt(interrupt) - , txPending(false) - , hwFIFO(hw_fifo) + , txEnableAssert_(tx_enable_assert) + , txEnableDeassert_(tx_enable_deassert) + , base_(base) + , interrupt_(interrupt) + , baud_(baud) + , txPending_(false) + , hwFIFO_(hw_fifo) { + static_assert( + UART_CONFIG_PAR_NONE == 0, "driverlib changed against our assumptions"); + static_assert( + UART_CONFIG_STOP_ONE == 0, "driverlib changed against our assumptions"); + HASSERT(mode <= 0xFFu); switch (base) { @@ -86,30 +95,30 @@ CC32xxUart::CC32xxUart(const char *name, unsigned long base, uint32_t interrupt, break; } - MAP_UARTConfigSetExpClk(base, cm3_cpu_clock_hz, baud, + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud, mode | UART_CONFIG_PAR_NONE); - MAP_IntDisable(interrupt); + MAP_IntDisable(interrupt_); /* We set the priority so that it is slightly lower than the highest needed * for FreeRTOS compatibility. This will ensure that CAN interrupts take * precedence over UART. */ - MAP_IntPrioritySet(interrupt, + MAP_IntPrioritySet(interrupt_, std::min(0xff, configKERNEL_INTERRUPT_PRIORITY + 0x20)); - MAP_UARTIntEnable(base, UART_INT_RX | UART_INT_RT); + MAP_UARTIntEnable(base_, UART_INT_RX | UART_INT_RT); } /** Enable use of the device. */ void CC32xxUart::enable() { - MAP_IntEnable(interrupt); - MAP_UARTEnable(base); - if (hwFIFO) + MAP_IntEnable(interrupt_); + MAP_UARTEnable(base_); + if (hwFIFO_) { - MAP_UARTFIFOEnable(base); + MAP_UARTFIFOEnable(base_); } else { - MAP_UARTFIFODisable(base); + MAP_UARTFIFODisable(base_); } } @@ -117,8 +126,8 @@ void CC32xxUart::enable() */ void CC32xxUart::disable() { - MAP_IntDisable(interrupt); - MAP_UARTDisable(base); + MAP_IntDisable(interrupt_); + MAP_UARTDisable(base_); } /** Request an ioctl transaction @@ -134,17 +143,83 @@ int CC32xxUart::ioctl(File *file, unsigned long int key, unsigned long data) default: return -EINVAL; case TCSBRK: - MAP_UARTBreakCtl(base, true); + MAP_UARTBreakCtl(base_, true); // need to wait at least two frames here MAP_UtilsDelay(100 * 26); - MAP_UARTBreakCtl(base, false); + MAP_UARTBreakCtl(base_, false); MAP_UtilsDelay(12 * 26); break; + case TCPARNONE: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_NONE; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_NONE); + break; + case TCPARODD: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_ODD; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ODD); + break; + case TCPAREVEN: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_EVEN; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_EVEN); + break; + case TCPARONE: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_ONE; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ONE); + break; + case TCPARZERO: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_ZERO; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ZERO); + break; + case TCSTOPONE: + mode_ &= ~UART_CONFIG_STOP_MASK; + mode_ |= UART_CONFIG_STOP_ONE; + set_mode(); + break; + case TCSTOPTWO: + mode_ &= ~UART_CONFIG_STOP_MASK; + mode_ |= UART_CONFIG_STOP_TWO; + set_mode(); + break; + case TCBAUDRATE: + baud_ = data; + set_mode(); + break; + case TCDRAINNOTIFY: + { + Notifiable* arg = (Notifiable*)data; + { + AtomicHolder h(&isr_lock); + if (txComplete_ != nullptr) + { + return -EBUSY; + } + if (txPending_) + { + txComplete_ = arg; + arg = nullptr; + } + } + if (arg) + { + arg->notify(); + } + break; + } } return 0; } +/** Sets the port baud rate and mode from the class variables. */ +void CC32xxUart::set_mode() +{ + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, mode_); +} + /** Send data until there is no more space left. */ void CC32xxUart::send() @@ -154,7 +229,7 @@ void CC32xxUart::send() uint8_t data = 0; if (txBuf->get(&data, 1)) { - MAP_UARTCharPutNonBlocking(base, data); + MAP_UARTCharPutNonBlocking(base_, data); } else @@ -162,18 +237,18 @@ void CC32xxUart::send() break; } } - while (MAP_UARTSpaceAvail(base)); + while (MAP_UARTSpaceAvail(base_)); if (txBuf->pending()) { /* more data to send later */ - MAP_UARTTxIntModeSet(base, UART_TXINT_MODE_FIFO); + 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); + MAP_UARTTxIntModeSet(base_, UART_TXINT_MODE_EOT); + MAP_UARTIntClear(base_, UART_INT_TX); } } @@ -181,17 +256,17 @@ void CC32xxUart::send() */ void CC32xxUart::tx_char() { - if (txPending == false) + if (txPending_ == false) { - if (txEnableAssert) + if (txEnableAssert_) { - txEnableAssert(); + txEnableAssert_(); } send(); - txPending = true; + txPending_ = true; - MAP_UARTIntEnable(base, UART_INT_TX); + MAP_UARTIntEnable(base_, UART_INT_TX); txBuf->signal_condition(); } } @@ -202,16 +277,16 @@ void CC32xxUart::interrupt_handler() { int woken = false; /* get and clear the interrupt status */ - unsigned long status = MAP_UARTIntStatus(base, true); - MAP_UARTIntClear(base, status); + unsigned long status = MAP_UARTIntStatus(base_, true); + MAP_UARTIntClear(base_, status); /** @todo (Stuart Baker) optimization opportunity by getting a write * pointer to fill the fifo and then advance the buffer when finished */ /* receive charaters as long as we can */ - while (MAP_UARTCharsAvail(base)) + while (MAP_UARTCharsAvail(base_)) { - long data = MAP_UARTCharGetNonBlocking(base); + long data = MAP_UARTCharGetNonBlocking(base_); if (data >= 0 && data <= 0xff) { unsigned char c = data; @@ -223,7 +298,7 @@ void CC32xxUart::interrupt_handler() } } /* transmit a character if we have pending tx data */ - if (txPending && (status & UART_INT_TX)) + if (txPending_ && (status & UART_INT_TX)) { if (txBuf->pending()) { @@ -233,13 +308,19 @@ void CC32xxUart::interrupt_handler() else { /* no more data left to send */ - HASSERT(MAP_UARTTxIntModeGet(base) == UART_TXINT_MODE_EOT); - if (txEnableDeassert) + HASSERT(MAP_UARTTxIntModeGet(base_) == UART_TXINT_MODE_EOT); + if (txEnableDeassert_) + { + txEnableDeassert_(); + } + txPending_ = false; + if (txComplete_) { - txEnableDeassert(); + Notifiable *t = txComplete_; + txComplete_ = nullptr; + t->notify_from_isr(); } - txPending = false; - MAP_UARTIntDisable(base, UART_INT_TX); + MAP_UARTIntDisable(base_, UART_INT_TX); } } os_isr_exit_yield_test(woken); diff --git a/src/freertos_drivers/ti/CC32xxUart.hxx b/src/freertos_drivers/ti/CC32xxUart.hxx index 737dd602b..663d36be9 100644 --- a/src/freertos_drivers/ti/CC32xxUart.hxx +++ b/src/freertos_drivers/ti/CC32xxUart.hxx @@ -44,6 +44,8 @@ #include "driverlib/uart.h" +class Notifiable; + /** Specialization of Serial driver for CC32xx UART. */ class CC32xxUart : public Serial @@ -87,8 +89,8 @@ public: */ void interrupt_handler(); - /** Request an ioctl transaction. Currently the only supported ioctl is - * TCSBRK. */ + /** Request an ioctl transaction. Supported ioctl is TCSBRK, TCDRAINNOTIFY, + * TCSTOP*, TCBAUDRATE and TCPAR* from include/freertos/tc_ioctl.h */ int ioctl(File *file, unsigned long int key, unsigned long data) override; private: @@ -104,16 +106,23 @@ private: */ 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; + TxEnableMethod txEnableAssert_; /** function pointer to a method that deasserts the transmit enable. */ - TxEnableMethod txEnableDeassert; - - unsigned long base; /**< base address of this device */ - unsigned long interrupt; /**< interrupt of this device */ - bool txPending; /**< transmission currently pending */ - bool hwFIFO; /**< true if hardware fifo is to be enabled, else false */ + TxEnableMethod txEnableDeassert_; + + /** Notifiable to invoke when the transmit engine has finished operation. */ + Notifiable* txComplete_{nullptr}; + + unsigned long base_; /**< base address of this device */ + uint32_t interrupt_ : 8; /**< interrupt of this device */ + uint32_t baud_ : 24; /**< desired baud rate */ + uint8_t txPending_; /**< transmission currently pending */ + uint8_t hwFIFO_; /**< true if hardware fifo is to be enabled, else false */ /** Default constructor. */ diff --git a/src/freertos_drivers/ti/TivaCan.cxx b/src/freertos_drivers/ti/TivaCan.cxx index e8bc6f2f8..d08607a60 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) { @@ -75,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; @@ -86,6 +115,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 +138,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 +154,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 +181,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 +204,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 +246,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 +289,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/TivaDCC.hxx b/src/freertos_drivers/ti/TivaDCC.hxx index d3c3c8ead..3bdd3adb6 100644 --- a/src/freertos_drivers/ti/TivaDCC.hxx +++ b/src/freertos_drivers/ti/TivaDCC.hxx @@ -71,6 +71,10 @@ #include "dcc/RailCom.hxx" #include "executor/Notifiable.hxx" +/// If non-zero, enables the jitter feature to spread the EMC spectrum of DCC +/// signal +extern "C" uint8_t spreadSpectrum; + /// This structure is safe to use from an interrupt context and a regular /// context at the same time, provided that /// @@ -202,6 +206,30 @@ private: * * The application can request notification of readable and writable status * using the regular IOCTL method. + * + * + * EMC spectrum spreading + * + * There is an optional feature that helps with passing EMC certification for + * systems that are built on this driver. The observation is that if the + * output signal has may repeats of a certain period, then in the measured + * spectrum there will be a big spike in energy that might exceed the + * thresholds for compliance. However, by slightly varying the timing of the + * output signal, the energy will be spread across a wider spectrum, thus the + * peak of emission will be smaller. + * + * This feature is enabled by `extern uint8_t spreadSpectrum;`. This can come + * from a constant or configuration dependent variable. If enabled, then the + * timing of DCC zero bits are stretched to be a random value between 100.5 + * and 105 usec each half; the timing of DCC one bits will be stretched from + * 56.5 to 60 usec per half. The symmetry within each bit is still perfectly + * matched. Marklin-Motorola packets get up to 2 usec of stretching on each + * phase. + * + * The actual stretching is generated using a uniform random number generator + * within said limits to ensure we spread uniformly across the available + * timings. Up to four bits are output with the same timing, then a new random + * timing is generated. */ template class TivaDCC : public Node @@ -231,12 +259,20 @@ public: /** Structure for supporting bit timing. */ struct Timing { - /// In clock cycles: period ofthe timers + /// In clock cycles: period of the interval timer + uint32_t interval_period; + /// In clock cycles: period of the PWM timer uint32_t period; /// When to transition output A; must be within the period uint32_t transition_a; /// When to transition output B; must be within the period uint32_t transition_b; + /// How many ticks (minimum) we can add to the period and transition for + /// spectrum spreading. + uint16_t spread_min = 0; + /// How many ticks (maximum) we can add to the period and transition for + /// spectrum spreading. + uint16_t spread_max = 0; }; /* WARNING: these functions (hw_init, enable_output, disable_output) MUST @@ -310,15 +346,46 @@ private: static dcc::Packet IDLE_PKT; /// Bit timings that we store and precalculate. - typedef enum { + typedef enum + { + /// Zero bit for DCC (this is the longer) DCC_ZERO, + /// One bit for DCC (this is the shorter) DCC_ONE, + /// One bit for DCC that we generate at the end of packet + /// transition. This has a stretched negative part so that the next + /// packet resync avoids a glitch in the output. + DCC_EOP_ONE, + /// One bit for DCC that we generate during the railcom cutout. Can be + /// used to play with alignment of edges coming out of the railcom + /// cutout. + DCC_RC_ONE, + /// Half zero bit which is sent directly after the railcom cutout is + /// over. Needed to reset certain old decoders packet + /// recognizer. Recommended by + /// https://nmra.org/sites/default/files/standards/sandrp/pdf/tn-2-05draft2005-02-25_for_rp-9.2.3.pdf + DCC_RC_HALF_ZERO, + /// This is not a bit, but specifies when to wake up during the railcom + /// cutout. The time is T_CS, usually 26 usec. RAILCOM_CUTOUT_PRE, + /// This is not a bit, but specifies when to wake up during the railcom + /// cutout. The time is the time elapsed between T_CS and the middle of + /// the two windows. RAILCOM_CUTOUT_FIRST, + /// This is not a bit, but specifies when to wake up during the railcom + /// cutout. The time is the time elapsed between the end of the cutout + /// and the the middle of the two windows. RAILCOM_CUTOUT_SECOND, + /// This is not a bit, but specifies when to wake up during the railcom + /// cutout. This is used for re-synchronizing the RAILCOM_CUTOUT_POST, + /// Long negative DC pulse to act as a preamble for a Marklin packet. MM_PREAMBLE, + /// Zero bit for MM packet, which is a short pulse in one direction, + /// then a long pulse in the other. MM_ZERO, + /// One bit for MM packet, which is a long pulse in one direction, then + /// a short pulse in the other. MM_ONE, NUM_TIMINGS @@ -366,6 +433,9 @@ private: // State at the end of packet where we make a decision whether to go // into a railcom cutout or not. DCC_MAYBE_RAILCOM, + // If we did not generate a cutout, we generate 5 empty one bits here + // in case a booster wants to insert a cutout. + DCC_NO_CUTOUT, // Counts 26 usec into the appropriate preamble bit after which to turn // off output power. DCC_CUTOUT_PRE, @@ -374,13 +444,9 @@ private: // Point between railcom window 1 and railcom window 2 where we have to // read whatever arrived for channel1. DCC_MIDDLE_RAILCOM_CUTOUT, - // Railcom end-of-channel2 window. Reads out the UART values. + // Railcom end-of-channel2 window. Reads out the UART values, and + // enables output power. DCC_STOP_RAILCOM_RECEIVE, - // End of railcom cutout. Reenables output power. - DCC_ENABLE_AFTER_RAILCOM, - // A few one bits after the DCC packet is over in case we didn't go - // into railcom cutout. Form here we go into getting the next packet. - DCC_LEADOUT, // Same for marklin. A bit of negative voltage after the packet is over // but before loading the next packet. This ensures that old marklin // decoders confirm receiving the packet correctly before we go into a @@ -400,9 +466,16 @@ private: * @param transition_usec is the time of the transition inside the bit, * counted from the beginning of the bit (i.e. the length of the HIGH part * of the period). Can be zero for DC output LOW or can be == period_usec - * for DC output HIGH. */ + * for DC output HIGH. + * @param interval_period_usec tells when the interval timer should expire + * (next interrupt). Most of the time this should be the same as + * period_usec. + * @param timing_spread_usec if non-zero, allows the high and low of the + * timing to be stretched by at most this many usec. + */ void fill_timing(BitEnum ofs, uint32_t period_usec, - uint32_t transition_usec); + uint32_t transition_usec, uint32_t interval_period_usec, + uint32_t timing_spread_usec = 0); /// Checks each output and enables those that need to be on. void check_and_enable_outputs() @@ -445,7 +518,15 @@ private: FixedQueue packetQueue_; Notifiable* writableNotifiable_; /**< Notify this when we have free buffers. */ RailcomDriver* railcomDriver_; /**< Will be notified for railcom cutout events. */ - + /// Seed for a pseudorandom sequence. + unsigned seed_ = 0xb7a11bae; + + /// Parameters for a linear RNG: modulus + static constexpr unsigned PMOD = 65213; + /// Parameters for a linear RNG: multiplier + static constexpr unsigned PMUL = 52253; + /// Parameters for a linear RNG: additive + static constexpr unsigned PADD = 42767; /** Default constructor. */ TivaDCC(); @@ -464,6 +545,7 @@ inline void TivaDCC::interrupt_handler() static BitEnum last_bit = DCC_ONE; static int count = 0; static int packet_repeat_count = 0; + static int bit_repeat_count = 0; static const dcc::Packet *packet = &IDLE_PKT; static bool resync = true; BitEnum current_bit; @@ -475,25 +557,37 @@ inline void TivaDCC::interrupt_handler() { default: case RESYNC: - current_bit = DCC_ONE; if (packet->packet_header.is_marklin) { + current_bit = MM_PREAMBLE; state_ = ST_MM_PREAMBLE; + break; } else { state_ = PREAMBLE; } - break; + // fall through case PREAMBLE: { current_bit = DCC_ONE; - int preamble_needed = HW::dcc_preamble_count(); + // preamble zero is output twice due to the resync, so we deduct + // one from the count. + int preamble_needed = HW::dcc_preamble_count() - 1; + if (HW::generate_railcom_halfzero() && + !packet->packet_header.send_long_preamble) + { + if (preamble_count == 0) + { + current_bit = DCC_RC_HALF_ZERO; + } + preamble_needed++; + } if (packet->packet_header.send_long_preamble) { preamble_needed = 21; } - if (++preamble_count == preamble_needed) + if (++preamble_count >= preamble_needed) { state_ = START; preamble_count = 0; @@ -513,13 +607,16 @@ inline void TivaDCC::interrupt_handler() case DATA_5: case DATA_6: case DATA_7: - current_bit = static_cast(DCC_ZERO + ((packet->payload[count] >> (DATA_7 - state_)) & 0x01)); + { + uint8_t bit = (packet->payload[count] >> (DATA_7 - state_)) & 0x01; + current_bit = static_cast(DCC_ZERO + bit); state_ = static_cast(static_cast(state_) + 1); break; + } case FRAME: if (++count >= packet->dlc) { - current_bit = DCC_ONE; // end-of-packet bit + current_bit = DCC_RC_ONE; // end-of-packet bit state_ = DCC_MAYBE_RAILCOM; preamble_count = 0; } @@ -535,34 +632,44 @@ inline void TivaDCC::interrupt_handler() HW::Output2::need_railcom_cutout() || HW::Output3::need_railcom_cutout())) { - //current_bit = RAILCOM_CUTOUT_PRE; - current_bit = DCC_ONE; + current_bit = DCC_RC_ONE; // It takes about 5 usec to get here from the previous // transition of the output. // We change the time of the next IRQ. MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_PRE].period); + timings[RAILCOM_CUTOUT_PRE].interval_period); state_ = DCC_CUTOUT_PRE; } else { + railcomDriver_->no_cutout(); current_bit = DCC_ONE; - state_ = DCC_LEADOUT; + state_ = DCC_NO_CUTOUT; } break; - case DCC_LEADOUT: + case DCC_NO_CUTOUT: current_bit = DCC_ONE; - if (++preamble_count >= 2) { + ++preamble_count; + // maybe railcom already sent one extra ONE bit after the + // end-of-packet one bit. We need four more. + if (preamble_count >= 4) + { + current_bit = DCC_EOP_ONE; + } + if (preamble_count >= 5) + { + // The last bit will be removed by the next packet's beginning + // sync. get_next_packet = true; } break; case DCC_CUTOUT_PRE: - current_bit = DCC_ONE; + current_bit = DCC_RC_ONE; // It takes about 3.6 usec to get here from the transition seen on // the output. // We change the time of the next IRQ. MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_FIRST].period); + timings[RAILCOM_CUTOUT_FIRST].interval_period); state_ = DCC_START_RAILCOM_RECEIVE; break; case DCC_START_RAILCOM_RECEIVE: @@ -620,25 +727,26 @@ inline void TivaDCC::interrupt_handler() // Enables UART RX. railcomDriver_->start_cutout(); // Set up for next wakeup. - current_bit = DCC_ONE; + current_bit = DCC_RC_ONE; MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_SECOND].period); + timings[RAILCOM_CUTOUT_SECOND].interval_period); state_ = DCC_MIDDLE_RAILCOM_CUTOUT; break; } case DCC_MIDDLE_RAILCOM_CUTOUT: railcomDriver_->middle_cutout(); - current_bit = DCC_ONE; + current_bit = DCC_RC_ONE; MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[RAILCOM_CUTOUT_POST].period); - state_ = DCC_STOP_RAILCOM_RECEIVE; + timings[RAILCOM_CUTOUT_POST].interval_period); + state_ = DCC_STOP_RAILCOM_RECEIVE; break; case DCC_STOP_RAILCOM_RECEIVE: { - current_bit = DCC_ONE; - state_ = DCC_ENABLE_AFTER_RAILCOM; - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, - timings[DCC_ONE].period); + current_bit = RAILCOM_CUTOUT_POST; + // This causes the timers to be reinitialized so no fractional bits + // are left in their counters. + resync = true; + get_next_packet = true; railcomDriver_->end_cutout(); unsigned delay = 0; if (HW::Output1::isRailcomCutoutActive_) @@ -679,11 +787,6 @@ inline void TivaDCC::interrupt_handler() check_and_enable_outputs(); break; } - case DCC_ENABLE_AFTER_RAILCOM: - current_bit = DCC_ONE; - state_ = DCC_LEADOUT; - ++preamble_count; - break; case ST_MM_PREAMBLE: current_bit = MM_PREAMBLE; ++preamble_count; @@ -697,6 +800,8 @@ inline void TivaDCC::interrupt_handler() } break; case MM_LEADOUT: + // MM packets never have a cutout. + railcomDriver_->no_cutout(); current_bit = MM_PREAMBLE; if (++preamble_count >= 2) { get_next_packet = true; @@ -709,24 +814,29 @@ inline void TivaDCC::interrupt_handler() case MM_DATA_4: case MM_DATA_5: case MM_DATA_6: - current_bit = static_cast( - MM_ZERO + - ((packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01)); + { + uint8_t bit = + (packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01; + current_bit = static_cast(MM_ZERO + bit); state_ = static_cast(static_cast(state_) + 1); break; + } case MM_DATA_7: - current_bit = static_cast( - MM_ZERO + - ((packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01)); + { + uint8_t bit = + (packet->payload[count] >> (MM_DATA_7 - state_)) & 0x01; + current_bit = static_cast(MM_ZERO + bit); ++count; if (count == 3) { state_ = ST_MM_PREAMBLE; } else if (count >= packet->dlc) { + preamble_count = 0; state_ = MM_LEADOUT; } else { state_ = MM_DATA_0; } break; + } case POWER_IMM_TURNON: current_bit = DCC_ONE; packet_repeat_count = 0; @@ -751,20 +861,23 @@ inline void TivaDCC::interrupt_handler() // These have to happen very fast because syncing depends on it. We do // direct register writes here instead of using the plib calls. HWREG(HW::INTERVAL_BASE + TIMER_O_TAILR) = - timing->period + hDeadbandDelay_ * 2; + timing->interval_period + hDeadbandDelay_ * 2; if (!HW::H_DEADBAND_DELAY_NSEC) { + TDebug::Resync::toggle(); MAP_TimerDisable(HW::CCP_BASE, TIMER_A|TIMER_B); // Sets final values for the cycle. MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A|TIMER_B, timing->period); MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a); MAP_TimerMatchSet(HW::CCP_BASE, TIMER_B, timing->transition_b); - MAP_TimerEnable(HW::CCP_BASE, TIMER_A|TIMER_B); + MAP_TimerEnable(HW::CCP_BASE, TIMER_A | TIMER_B); MAP_TimerDisable(HW::INTERVAL_BASE, TIMER_A); - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timing->period); + MAP_TimerLoadSet( + HW::INTERVAL_BASE, TIMER_A, timing->interval_period); MAP_TimerEnable(HW::INTERVAL_BASE, TIMER_A); + TDebug::Resync::toggle(); // Switches back to asynch timer update. HWREG(HW::CCP_BASE + TIMER_O_TAMR) |= @@ -801,19 +914,56 @@ inline void TivaDCC::interrupt_handler() // Sets final values for the cycle. MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timing->period); MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a); - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timing->period); + MAP_TimerLoadSet( + HW::INTERVAL_BASE, TIMER_A, timing->interval_period); } last_bit = current_bit; - } else if (last_bit != current_bit) + bit_repeat_count = 0; + if (current_bit == RAILCOM_CUTOUT_POST) + { + // RAILCOM_CUTOUT_POST purposefully misaligns the two timers. We + // need to resync when the next interval timer ticks to get them + // back. + resync = true; + } + else if (current_bit == DCC_RC_HALF_ZERO) + { + // After resync the same bit is output twice. We don't want that + // with the half-zero, so we preload the DCC preamble bit. + current_bit = DCC_ONE; + } + } + if (bit_repeat_count >= 4) + { + // Forces reinstalling the timing. + last_bit = NUM_TIMINGS; + } + if (last_bit != current_bit) { auto* timing = &timings[current_bit]; - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timing->period); - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timing->period); - MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, timing->period); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a); - MAP_TimerMatchSet(HW::CCP_BASE, TIMER_B, timing->transition_b); + // The delta in ticks we add to each side of the signal. + uint32_t spread = 0; + if (spreadSpectrum) + { + spread = timing->spread_max - timing->spread_min; + seed_ *= PMUL; + seed_ += PADD; + seed_ %= PMOD; + spread = (seed_ % spread) + timing->spread_min; + } + MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, + timing->interval_period + (spread << 1)); + MAP_TimerLoadSet(HW::CCP_BASE, TIMER_A, timing->period + (spread << 1)); + MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, timing->period + (spread << 1)); + MAP_TimerMatchSet(HW::CCP_BASE, TIMER_A, timing->transition_a + spread); + MAP_TimerMatchSet(HW::CCP_BASE, TIMER_B, timing->transition_b + spread); last_bit = current_bit; + bit_repeat_count = 0; + } + else + { + bit_repeat_count++; } if (get_next_packet) @@ -859,7 +1009,7 @@ inline void TivaDCC::interrupt_handler() /// Converts a time length given in microseconds to the number of clock cycles. /// @param usec is time given in microseconds. /// @return time given in clock cycles. -static uint32_t usec_to_clocks(uint32_t usec) { +static const uint32_t usec_to_clocks(uint32_t usec) { return (configCPU_CLOCK_HZ / 1000000) * usec; } @@ -871,12 +1021,13 @@ static uint32_t nsec_to_clocks(uint32_t nsec) { return ((configCPU_CLOCK_HZ / 1000000) * nsec) / 1000; } -template +template void TivaDCC::fill_timing(BitEnum ofs, uint32_t period_usec, - uint32_t transition_usec) + uint32_t transition_usec, uint32_t interval_usec, uint32_t spread_max) { auto* timing = &timings[ofs]; timing->period = usec_to_clocks(period_usec); + timing->interval_period = usec_to_clocks(interval_usec); if (transition_usec == 0) { // DC voltage negative. timing->transition_a = timing->transition_b = timing->period; @@ -892,9 +1043,13 @@ void TivaDCC::fill_timing(BitEnum ofs, uint32_t period_usec, timing->transition_b = nominal_transition - (hDeadbandDelay_ + lDeadbandDelay_) / 2; } + if (spread_max > 0) + { + timing->spread_min = usec_to_clocks(1) / 2; + timing->spread_max = usec_to_clocks(spread_max); + } } - template dcc::Packet TivaDCC::IDLE_PKT = dcc::Packet::DCC_IDLE(); @@ -909,33 +1064,49 @@ TivaDCC::TivaDCC(const char *name, RailcomDriver *railcom_driver) { state_ = PREAMBLE; - fill_timing(DCC_ZERO, 105<<1, 105); - fill_timing(DCC_ONE, 56<<1, 56); - fill_timing(MM_ZERO, 208, 26); - fill_timing(MM_ONE, 208, 182); + fill_timing(DCC_ZERO, 100 << 1, 100, 100 << 1, 5); + fill_timing(DCC_ONE, 56 << 1, 56, 56 << 1, 4); + /// @todo tune this bit to line up with the bit stream starting after the + /// railcom cutout. + fill_timing(DCC_RC_ONE, 57 << 1, 57, 57 << 1); + // A small pulse in one direction then a half zero bit in the other + // direction. + fill_timing(DCC_RC_HALF_ZERO, 100 + 56, 56, 100 + 56, 5); + // At the end of the packet we stretch the negative side but let the + // interval timer kick in on time. The next bit will resync, and this + // avoids a glitch output to the track when a marklin preamble is coming. + fill_timing(DCC_EOP_ONE, (56 << 1) + 20, 58, 56 << 1); + fill_timing(MM_ZERO, 208, 26, 208, 2); + fill_timing(MM_ONE, 208, 182, 208, 2); // Motorola preamble is negative DC signal. - fill_timing(MM_PREAMBLE, 208, 0); + fill_timing(MM_PREAMBLE, 208, 0, 208); unsigned h_deadband = 2 * (HW::H_DEADBAND_DELAY_NSEC / 1000); unsigned railcom_part = 0; unsigned target = RAILCOM_CUTOUT_START_USEC + HW::RAILCOM_CUTOUT_START_DELTA_USEC; - fill_timing(RAILCOM_CUTOUT_PRE, target - railcom_part, 0); + fill_timing(RAILCOM_CUTOUT_PRE, 56 << 1, 56, target - railcom_part); railcom_part = target; target = RAILCOM_CUTOUT_MID_USEC + HW::RAILCOM_CUTOUT_MID_DELTA_USEC; - fill_timing(RAILCOM_CUTOUT_FIRST, target - railcom_part, 0); + fill_timing(RAILCOM_CUTOUT_FIRST, 56 << 1, 56, target - railcom_part); railcom_part = target; target = RAILCOM_CUTOUT_END_USEC + HW::RAILCOM_CUTOUT_END_DELTA_USEC; - fill_timing(RAILCOM_CUTOUT_SECOND, target - railcom_part, 0); + fill_timing(RAILCOM_CUTOUT_SECOND, 56 << 1, 56, target - railcom_part); railcom_part = target; - static_assert(5 * 56 * 2 > + static_assert((5 * 56 * 2 - 56 + HW::RAILCOM_CUTOUT_POST_DELTA_USEC) > RAILCOM_CUTOUT_END_USEC + HW::RAILCOM_CUTOUT_END_DELTA_USEC, "railcom cutout too long"); - // remaining time until 5 one bits are complete. - fill_timing(RAILCOM_CUTOUT_POST, 5*56*2 - railcom_part + h_deadband, 0); + target = 5 * 56 * 2 - 56 + HW::RAILCOM_CUTOUT_POST_DELTA_USEC; + unsigned remaining_high = target - railcom_part; + // remaining time until 5 one bits are complete. For the PWM timer we have + // some fraction of the high part, then a full low side, then we stretch + // the low side to avoid the packet transition glitch. + fill_timing(RAILCOM_CUTOUT_POST, remaining_high + 56 + 20, remaining_high, + remaining_high + 56 + h_deadband + + HW::RAILCOM_CUTOUT_POST_NEGATIVE_DELTA_USEC); // We need to disable the timers before making changes to the config. MAP_TimerDisable(HW::CCP_BASE, TIMER_A); @@ -988,7 +1159,8 @@ TivaDCC::TivaDCC(const char *name, RailcomDriver *railcom_driver) #endif MAP_TimerLoadSet(HW::CCP_BASE, TIMER_B, timings[DCC_ONE].period); - MAP_TimerLoadSet(HW::INTERVAL_BASE, TIMER_A, timings[DCC_ONE].period); + MAP_TimerLoadSet( + HW::INTERVAL_BASE, TIMER_A, timings[DCC_ONE].interval_period); MAP_IntEnable(HW::INTERVAL_INTERRUPT); // The OS interrupt does not come from the hardware timer. @@ -1026,14 +1198,9 @@ ssize_t TivaDCC::write(File *file, const void *buf, size_t count) } OSMutexLock l(&lock_); - // TODO(balazs.racz) this interrupt disable is not actually needed. Writing - // to the back of the queue should be okay while the interrupt reads from - // the front of it. - MAP_TimerIntDisable(HW::INTERVAL_BASE, TIMER_TIMA_TIMEOUT); if (packetQueue_.full()) { - MAP_TimerIntEnable(HW::INTERVAL_BASE, TIMER_TIMA_TIMEOUT); return -ENOSPC; } @@ -1060,7 +1227,6 @@ ssize_t TivaDCC::write(File *file, const void *buf, size_t count) HW::flip_led(); } - MAP_TimerIntEnable(HW::INTERVAL_BASE, TIMER_TIMA_TIMEOUT); return count; } diff --git a/src/freertos_drivers/ti/TivaDCCDecoder.hxx b/src/freertos_drivers/ti/TivaDCCDecoder.hxx index 4b762d49a..74203105a 100644 --- a/src/freertos_drivers/ti/TivaDCCDecoder.hxx +++ b/src/freertos_drivers/ti/TivaDCCDecoder.hxx @@ -136,6 +136,24 @@ public: HW::after_feedback_hook(); } + /// How many usec later should the railcom cutout start happen. + static int time_delta_railcom_pre_usec() + { + return HW::time_delta_railcom_pre_usec(); + } + + /// How many usec later should the railcom cutout middle happen. + static int time_delta_railcom_mid_usec() + { + return HW::time_delta_railcom_mid_usec(); + } + + /// How many usec later should the railcom cutout middle happen. + static int time_delta_railcom_end_usec() + { + return HW::time_delta_railcom_end_usec(); + } + /// Called from the capture interrupt handler. Checks interrupt status, /// clears interrupt. /// @return true if the interrupt was generated by a capture event. diff --git a/src/freertos_drivers/ti/TivaDev.hxx b/src/freertos_drivers/ti/TivaDev.hxx index f42130ee8..1bd0a92e0 100644 --- a/src/freertos_drivers/ti/TivaDev.hxx +++ b/src/freertos_drivers/ti/TivaDev.hxx @@ -44,6 +44,7 @@ #include "driverlib/rom_map.h" #include "driverlib/interrupt.h" #include "driverlib/timer.h" +#include "driverlib/uart.h" #include "usblib/usblib.h" #include "usblib/usbcdc.h" #include "usblib/usb-ids.h" @@ -154,12 +155,34 @@ private: class TivaUart : public Serial { public: + /** These mode bits need to be OR-ed together for the mode argument and + * ioctl. */ + enum Mode + { + CS5 = UART_CONFIG_WLEN_5, /**< 5-bits word length */ + CS6 = UART_CONFIG_WLEN_6, /**< 6-bits word length */ + CS7 = UART_CONFIG_WLEN_7, /**< 7-bits word length */ + CS8 = UART_CONFIG_WLEN_8, /**< 8-bits word length */ + 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 * @param interrupt interrupt number of this device + * @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); + TivaUart(const char *name, unsigned long base, uint32_t interrupt, + uint32_t baud = 115200, uint32_t mode = CS8, bool hw_fifo = true, + TxEnableMethod tx_enable_assert = nullptr, + TxEnableMethod tx_enable_deassert = nullptr); /** Destructor. */ @@ -172,6 +195,10 @@ public: */ void interrupt_handler(); + /** Request an ioctl transaction. Supported ioctl is TCSBRK, TCDRAINNOTIFY, + * TCSTOP*, TCBAUDRATE and TCPAR* from include/freertos/tc_ioctl.h */ + 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 */ @@ -180,9 +207,28 @@ private: */ void tx_char() override; - unsigned long base; /**< base address of this device */ - unsigned long interrupt; /**< interrupt of this device */ - bool txPending; /**< transmission currently pending */ + /** 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}; + + uint32_t base_; /**< base address of this device */ + uint32_t interrupt_ : 8; /**< interrupt of this device */ + uint32_t baud_ : 24; /**< desired baud rate */ + uint8_t hwFIFO_; /**< enable HW FIFO */ + uint8_t mode_; /**< uart config (mode) flags */ + uint8_t txPending_; /**< transmission currently pending */ /** Default constructor. */ @@ -214,6 +260,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 */ @@ -222,7 +275,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(); 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 diff --git a/src/freertos_drivers/ti/TivaRailcom.hxx b/src/freertos_drivers/ti/TivaRailcom.hxx index 19657bcef..ecabfde77 100644 --- a/src/freertos_drivers/ti/TivaRailcom.hxx +++ b/src/freertos_drivers/ti/TivaRailcom.hxx @@ -394,12 +394,14 @@ private: } HWREG(HW::UART_BASE[i] + UART_O_CTL) |= UART_CTL_RXE; } + HW::middle_cutout_hook(); Debug::RailcomDriverCutout::set(true); } void end_cutout() OVERRIDE { HW::disable_measurement(); + bool have_packets = false; for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) { while (MAP_UARTCharsAvail(HW::UART_BASE[i])) @@ -430,15 +432,46 @@ private: Debug::RailcomRxActivate::set(false); //HWREGBITW(HW::UART_BASE[i] + UART_O_CTL, UART_CTL_RXE) = 0; if (returnedPackets_[i]) { + have_packets = true; this->feedbackQueue_.commit_back(); Debug::RailcomPackets::toggle(); returnedPackets_[i] = nullptr; MAP_IntPendSet(HW::OS_INTERRUPT); } } + if (!have_packets) + { + // Ensures that at least one feedback packet is sent back even when + // it is with no railcom payload. + auto *p = this->alloc_new_packet(0); + if (p) + { + this->feedbackQueue_.commit_back(); + Debug::RailcomPackets::toggle(); + MAP_IntPendSet(HW::OS_INTERRUPT); + } + } Debug::RailcomCh2Data::set(false); Debug::RailcomDriverCutout::set(false); } + + void no_cutout() OVERRIDE + { + for (unsigned i = 0; i < ARRAYSIZE(HW::UART_BASE); ++i) + { + if (!returnedPackets_[i]) + { + returnedPackets_[i] = this->alloc_new_packet(i); + } + if (returnedPackets_[i]) + { + this->feedbackQueue_.commit_back(); + Debug::RailcomPackets::toggle(); + returnedPackets_[i] = nullptr; + MAP_IntPendSet(HW::OS_INTERRUPT); + } + } + } }; #endif // _FREERTOS_DRIVERS_TI_TIVARAILCOM_HXX_ 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..c2e8443b9 --- /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) < 8)) + { + 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/src/freertos_drivers/ti/TivaUart.cxx b/src/freertos_drivers/ti/TivaUart.cxx index a93208557..1ec1f1c27 100644 --- a/src/freertos_drivers/ti/TivaUart.cxx +++ b/src/freertos_drivers/ti/TivaUart.cxx @@ -34,6 +34,7 @@ #include #include +#include #include "inc/hw_types.h" #include "inc/hw_memmap.h" @@ -50,19 +51,34 @@ /** Instance pointers help us get context from the interrupt handler(s) */ static TivaUart *instances[8] = {NULL}; +/** Critical section lock between ISR and ioctl */ +static Atomic isr_lock; + /** 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 */ -TivaUart::TivaUart(const char *name, unsigned long base, uint32_t interrupt) - : Serial(name), - base(base), - interrupt(interrupt), - txPending(false) +TivaUart::TivaUart(const char *name, unsigned long base, uint32_t interrupt, + 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) + , hwFIFO_(hw_fifo) + , mode_(mode) + , txPending_(false) { + static_assert( + UART_CONFIG_PAR_NONE == 0, "driverlib changed against our assumptions"); + static_assert( + UART_CONFIG_STOP_ONE == 0, "driverlib changed against our assumptions"); + HASSERT(mode <= 0xFFu); - switch (base) + switch (base_) { default: HASSERT(0); @@ -99,60 +115,90 @@ TivaUart::TivaUart(const char *name, unsigned long base, uint32_t interrupt) instances[7] = this; break; } - - /* We set the preliminary clock here, but it will be re-set when the device - * gets enabled. The reason for re-setting is that the system clock is - * switched in HwInit but that has not run yet at this point. */ - MAP_UARTConfigSetExpClk(base, cm3_cpu_clock_hz, 115200, - UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); - MAP_UARTFIFOEnable(base); - MAP_UARTTxIntModeSet(base, UART_TXINT_MODE_EOT); - MAP_IntDisable(interrupt); + + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, mode_); + MAP_UARTTxIntModeSet(base_, UART_TXINT_MODE_EOT); + MAP_IntDisable(interrupt_); /* We set the priority so that it is slightly lower than the highest needed * for FreeRTOS compatibility. This will ensure that CAN interrupts take * precedence over UART. */ - MAP_IntPrioritySet(interrupt, + MAP_IntPrioritySet(interrupt_, std::min(0xff, configKERNEL_INTERRUPT_PRIORITY + 0x20)); - MAP_UARTIntEnable(base, UART_INT_RX | UART_INT_RT); + MAP_UARTIntEnable(base_, UART_INT_RX | UART_INT_RT); } /** Enable use of the device. */ void TivaUart::enable() { - MAP_UARTConfigSetExpClk(base, cm3_cpu_clock_hz, 115200, - UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE); - MAP_IntEnable(interrupt); - MAP_UARTEnable(base); - MAP_UARTFIFOEnable(base); + MAP_IntEnable(interrupt_); + MAP_UARTEnable(base_); + if (hwFIFO_) + { + MAP_UARTFIFOEnable(base_); + } + else + { + MAP_UARTFIFODisable(base_); + } } /** Disable use of the device. */ void TivaUart::disable() { - MAP_IntDisable(interrupt); - MAP_UARTDisable(base); + MAP_IntDisable(interrupt_); + 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_UARTCharPutNonBlocking(base_, data); + + } + 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); + } +} - MAP_IntDisable(interrupt); - txPending = true; - MAP_UARTIntEnable(base, UART_INT_TX); - MAP_IntEnable(interrupt); - txBuf->signal_condition(); +/** 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(); } } @@ -162,16 +208,16 @@ void TivaUart::interrupt_handler() { int woken = false; /* get and clear the interrupt status */ - unsigned long status = MAP_UARTIntStatus(base, true); - MAP_UARTIntClear(base, status); + unsigned long status = MAP_UARTIntStatus(base_, true); + MAP_UARTIntClear(base_, status); /** @todo (Stuart Baker) optimization opportunity by getting a write * pointer to fill the fifo and then advance the buffer when finished */ /* receive charaters as long as we can */ - while (MAP_UARTCharsAvail(base)) + while (MAP_UARTCharsAvail(base_)) { - long data = MAP_UARTCharGetNonBlocking(base); + long data = MAP_UARTCharGetNonBlocking(base_); if (data >= 0 && data <= 0xff) { unsigned char c = data; @@ -183,31 +229,124 @@ 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; - 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); } +/** Sets the port baud rate and mode from the class variables. */ +void TivaUart::set_mode() +{ + MAP_UARTConfigSetExpClk(base_, cm3_cpu_clock_hz, baud_, mode_); +} + +/** 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 TivaUart::ioctl(File *file, unsigned long int key, unsigned long data) +{ + switch (key) + { + default: + return -EINVAL; + case TCSBRK: + MAP_UARTBreakCtl(base_, true); + // need to wait at least two frames here + MAP_SysCtlDelay(100 * 26); + MAP_UARTBreakCtl(base_, false); + MAP_SysCtlDelay(12 * 26); + break; + case TCPARNONE: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_NONE; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_NONE); + break; + case TCPARODD: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_ODD; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ODD); + break; + case TCPAREVEN: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_EVEN; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_EVEN); + break; + case TCPARONE: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_ONE; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ONE); + break; + case TCPARZERO: + mode_ &= ~UART_CONFIG_PAR_MASK; + mode_ |= UART_CONFIG_PAR_ZERO; + MAP_UARTParityModeSet(base_, UART_CONFIG_PAR_ZERO); + break; + case TCSTOPONE: + mode_ &= ~UART_CONFIG_STOP_MASK; + mode_ |= UART_CONFIG_STOP_ONE; + set_mode(); + break; + case TCSTOPTWO: + mode_ &= ~UART_CONFIG_STOP_MASK; + mode_ |= UART_CONFIG_STOP_TWO; + set_mode(); + break; + case TCBAUDRATE: + baud_ = data; + set_mode(); + break; + case TCDRAINNOTIFY: + { + Notifiable* arg = (Notifiable*)data; + { + AtomicHolder h(&isr_lock); + if (txComplete_ != nullptr) + { + return -EBUSY; + } + if (txPending_) + { + txComplete_ = arg; + arg = nullptr; + } + } + if (arg) + { + arg->notify(); + } + break; + } + } + + return 0; +} + extern "C" { /** UART0 interrupt handler. */ diff --git a/src/openlcb/AliasAllocator.cxx b/src/openlcb/AliasAllocator.cxx index 17f386545..e81f91f61 100644 --- a/src/openlcb/AliasAllocator.cxx +++ b/src/openlcb/AliasAllocator.cxx @@ -33,6 +33,7 @@ */ #include "openlcb/AliasAllocator.hxx" +#include "nmranet_config.h" #include "openlcb/CanDefs.hxx" namespace openlcb @@ -47,6 +48,7 @@ AliasAllocator::AliasAllocator(NodeID if_id, IfCan *if_can) , if_id_(if_id) , cid_frame_sequence_(0) , conflict_detected_(0) + , reserveUnusedAliases_(config_reserve_unused_alias_count()) { reinit_seed(); // Moves all the allocated alias buffers over to the input queue for @@ -77,6 +79,50 @@ void seed_alias_allocator(AliasAllocator* aliases, Pool* pool, int n) { } } +/** @return the number of aliases that are reserved and available for new + * virtual nodes to use. */ +unsigned AliasAllocator::num_reserved_aliases() +{ + unsigned cnt = 0; + NodeID found_id = CanDefs::get_reserved_alias_node_id(0); + NodeAlias found_alias = 0; + do + { + if (if_can()->local_aliases()->next_entry( + found_id, &found_id, &found_alias) && + CanDefs::is_reserved_alias_node_id(found_id)) + { + ++cnt; + } + else + { + break; + } + } while (true); + return cnt; +} + +/** Removes all aliases that are reserved but not yet used. */ +void AliasAllocator::clear_reserved_aliases() +{ + do + { + NodeID found_id = CanDefs::get_reserved_alias_node_id(0); + NodeAlias found_alias = 0; + if (if_can()->local_aliases()->next_entry( + CanDefs::get_reserved_alias_node_id(0), &found_id, + &found_alias) && + CanDefs::is_reserved_alias_node_id(found_id)) + { + if_can()->local_aliases()->remove(found_alias); + } + else + { + break; + } + } while (true); +} + void AliasAllocator::return_alias(NodeID id, NodeAlias alias) { // This is synchronous allocation, which is not nice. @@ -89,15 +135,68 @@ void AliasAllocator::return_alias(NodeID id, NodeAlias alias) if_can()->frame_write_flow()->send(b); } - // This is synchronous allocation, which is not nice. + add_allocated_alias(alias); +} + +void AliasAllocator::add_allocated_alias(NodeAlias alias) +{ + // Note: We leak aliases here in case of eviction by the AliasCache + // object. This is okay for two reasons: 1) Generally the local alias cache + // size should be about equal to the local nodes count. 2) OpenLCB alias + // allocation algorithm is able to reuse aliases that were allocated by + // nodes that are not on the network anymore. + if_can()->local_aliases()->add( + CanDefs::get_reserved_alias_node_id(alias), alias); + if (!waitingClients_.empty()) { - auto* b = alloc(); - b->data()->reset(); - b->data()->alias = alias; + // Wakes up exactly one executable that is waiting for an alias. + Executable *w = static_cast(waitingClients_.next().item); + // This schedules a state flow onto its executor. + w->alloc_result(nullptr); + } +} + +NodeAlias AliasAllocator::get_allocated_alias( + NodeID destination_id, Executable *done) +{ + NodeID found_id; + NodeAlias found_alias = 0; + bool allocate_new = false; + bool found = if_can()->local_aliases()->next_entry( + CanDefs::get_reserved_alias_node_id(0), &found_id, &found_alias); + if (found) + { + found = (found_id == CanDefs::get_reserved_alias_node_id(found_alias)); + } + if (found) + { + if_can()->local_aliases()->add(destination_id, found_alias); + if (reserveUnusedAliases_) + { + NodeID next_id; + NodeAlias next_alias = 0; + if (!if_can()->local_aliases()->next_entry( + CanDefs::get_reserved_alias_node_id(0), &next_id, + &next_alias) || + !CanDefs::is_reserved_alias_node_id(next_id)) + { + allocate_new = true; + } + } + } + else + { + found_alias = 0; + allocate_new = true; + waitingClients_.insert(done); + } + if (allocate_new) + { + Buffer *b = alloc(); b->data()->do_not_reallocate(); - b->data()->state = AliasInfo::STATE_RESERVED; - reserved_aliases()->insert(b); + this->send(b); } + return found_alias; } AliasAllocator::~AliasAllocator() @@ -111,9 +210,7 @@ StateFlowBase::Action AliasAllocator::entry() HASSERT(pending_alias()->state == AliasInfo::STATE_EMPTY); while (!pending_alias()->alias) { - pending_alias()->alias = seed_; - next_seed(); - // TODO(balazs.racz): check if the alias is already known about. + pending_alias()->alias = get_new_seed(); } // Registers ourselves as a handler for incoming CAN frames to detect // conflicts. @@ -124,6 +221,27 @@ StateFlowBase::Action AliasAllocator::entry() return call_immediately(STATE(handle_allocate_for_cid_frame)); } +NodeAlias AliasAllocator::get_new_seed() +{ + while (true) + { + NodeAlias ret = seed_; + next_seed(); + LOG(VERBOSE, "(%p) alias test seed is %03X (next %03X)", this, ret, + seed_); + if (if_can()->local_aliases()->lookup(ret)) + { + continue; + } + if (if_can()->remote_aliases()->lookup(ret)) + { + continue; + } + LOG(VERBOSE, "alias get seed is %03X (next %03X)", ret, seed_); + return ret; + } +} + void AliasAllocator::next_seed() { uint16_t offset; @@ -216,9 +334,7 @@ StateFlowBase::Action AliasAllocator::send_rid_frame() pending_alias()->state = AliasInfo::STATE_RESERVED; if_can()->frame_dispatcher()->unregister_handler( &conflictHandler_, pending_alias()->alias, ~0x1FFFF000U); - if_can()->local_aliases()->add(AliasCache::RESERVED_ALIAS_NODE_ID, - pending_alias()->alias); - reserved_alias_pool_.insert(transfer_message()); + add_allocated_alias(pending_alias()->alias); return release_and_exit(); } @@ -241,26 +357,19 @@ void AliasAllocator::ConflictHandler::send(Buffer *message, message->unref(); } +#ifdef GTEST + void AliasAllocator::TEST_finish_pending_allocation() { if (is_state(STATE(wait_done))) { timer_.trigger(); } } -void AliasAllocator::TEST_add_allocated_alias(NodeAlias alias, bool repeat) +void AliasAllocator::TEST_add_allocated_alias(NodeAlias alias) { - Buffer *a; - mainBufferPool->alloc(&a); - a->data()->reset(); - a->data()->alias = alias; - a->data()->state = AliasInfo::STATE_RESERVED; - if (!repeat) - { - a->data()->do_not_reallocate(); - } - if_can()->local_aliases()->add( - AliasCache::RESERVED_ALIAS_NODE_ID, a->data()->alias); - reserved_aliases()->insert(a); + add_allocated_alias(alias); } +#endif + } // namespace openlcb diff --git a/src/openlcb/AliasAllocator.cxxtest b/src/openlcb/AliasAllocator.cxxtest index faef26b0d..4813a442c 100644 --- a/src/openlcb/AliasAllocator.cxxtest +++ b/src/openlcb/AliasAllocator.cxxtest @@ -1,8 +1,10 @@ #include -#include "utils/async_if_test_helper.hxx" #include "openlcb/AliasAllocator.hxx" #include "openlcb/AliasCache.hxx" +#include "openlcb/BulkAliasAllocator.hxx" +#include "openlcb/CanDefs.hxx" +#include "utils/async_if_test_helper.hxx" namespace openlcb { @@ -12,6 +14,7 @@ protected: AsyncAliasAllocatorTest() : b_(nullptr) , alias_allocator_(TEST_NODE_ID, ifCan_.get()) + , bulkAllocator_(create_bulk_alias_allocator(ifCan_.get())) { } @@ -30,7 +33,9 @@ protected: unsigned next_seed(AliasAllocator *alloc = nullptr) { if (!alloc) + { alloc = &alias_allocator_; + } alloc->next_seed(); return alloc->seed_; } @@ -39,16 +44,100 @@ protected: * until one is available. The alias will be saved into the buffer b_. */ void get_next_alias() { - while (b_ = static_cast *>( - alias_allocator_.reserved_aliases()->next().item), - !b_) + NodeAlias a; + nextAliasNodeId_++; + do + { + run_x([this, &a]() { + a = alias_allocator_.get_allocated_alias( + nextAliasNodeId_, &ex_); + }); + if (!a) + { + n_.wait_for_notification(); + } + } while (a == 0); + b_ = alias_allocator_.alloc(); + b_->data()->alias = a; + b_->data()->state = AliasInfo::STATE_RESERVED; + } + + /// Pre-generates some aliases into a vector. + void generate_aliases(AliasAllocator *alloc, unsigned count) + { + set_seed(0x555, alloc); + run_x([this, count, alloc]() { + for (unsigned i = 0; i < count; i++) + { + auto a = alloc->get_new_seed(); + LOG(INFO, "alias %03X", a); + aliases_.push_back(a); + } + }); + set_seed(0x555, alloc); + } + + /// Expects that CID frames are sent to the bus. + /// @param begin iterator into alias array + /// @param end iterator (end) into alias array + template void expect_cid(It begin, It end) + { + for (auto it = begin; it != end; ++it) + { + NodeAlias a = *it; + string msg = StringPrintf("cid %03X", a); + LOG(INFO, "cid %03X", a); + expect_packet(StringPrintf(":X17020%03XN;", a)) + .RetiresOnSaturation(); + expect_packet(StringPrintf(":X1610D%03XN;", a)) + .RetiresOnSaturation(); + expect_packet(StringPrintf(":X15000%03XN;", a)) + .RetiresOnSaturation(); + expect_packet(StringPrintf(":X14003%03XN;", a)) + .RetiresOnSaturation(); + } + } + + /// Expects that RID frames are sent to the bus. + /// @param begin iterator into alias array + /// @param end iterator (end) into alias array + template void expect_rid(It begin, It end) + { + for (auto it = begin; it != end; ++it) { - usleep(10); + NodeAlias a = *it; + LOG(INFO, "rid %03X", a); + expect_packet(StringPrintf(":X10700%03XN;", a)) + .RetiresOnSaturation(); } } + /// Helper class to pass into the asynchronous alias allocation wait. Will + /// notify n_ when the alias is ready to be taken. + class AllocExecutable : public Executable + { + public: + AllocExecutable(AsyncAliasAllocatorTest *parent) + : parent_(parent) + { + } + void run() override + { + } + void alloc_result(QMember *item) override + { + parent_->n_.notify(); + } + AsyncAliasAllocatorTest *parent_; + } ex_ {this}; + friend class AllocExecutable; + Buffer *b_; AliasAllocator alias_allocator_; + std::unique_ptr bulkAllocator_; + std::vector aliases_; + /// Will use this node ID to mark the next alias gotten. + openlcb::NodeID nextAliasNodeId_ = TEST_NODE_ID + 1; }; TEST_F(AsyncAliasAllocatorTest, SetupTeardown) @@ -63,21 +152,75 @@ TEST_F(AsyncAliasAllocatorTest, AllocateOne) expect_packet(":X1610D555N;"); expect_packet(":X15000555N;"); expect_packet(":X14003555N;"); - - mainBufferPool->alloc(&b_); - alias_allocator_.send(b_); - b_ = nullptr; - - /** @todo(balazs.racz) this should be after the wait because there should be - * a delay before sending it. */ expect_packet(":X10700555N;"); - wait(); get_next_alias(); ASSERT_TRUE(b_); EXPECT_EQ(0x555U, b_->data()->alias); EXPECT_EQ(AliasInfo::STATE_RESERVED, b_->data()->state); } + +TEST_F(AsyncAliasAllocatorTest, ReserveThenAllocate) +{ + set_seed(0x555); + clear_expect(true); + mainBufferPool->alloc(&b_); + expect_packet(":X17020555N;"); + expect_packet(":X1610D555N;"); + expect_packet(":X15000555N;"); + expect_packet(":X14003555N;"); + alias_allocator_.send(b_); + wait(); + expect_packet(":X10700555N;"); + usleep(250000); + wait(); + clear_expect(true); + run_x([this]() { + EXPECT_EQ(0x555, + alias_allocator_.get_allocated_alias(nextAliasNodeId_, nullptr)); + EXPECT_EQ(nextAliasNodeId_, + ifCan_->local_aliases()->lookup(NodeAlias(0x555))); + }); +} + +TEST_F(AsyncAliasAllocatorTest, ReserveUnused) +{ + alias_allocator_.TEST_set_reserve_unused_alias_count(1); + set_seed(0x555); + clear_expect(true); + expect_packet(":X17020555N;"); + expect_packet(":X1610D555N;"); + expect_packet(":X15000555N;"); + expect_packet(":X14003555N;"); + run_x([this]() { + EXPECT_EQ( + 0, alias_allocator_.get_allocated_alias(nextAliasNodeId_, &ex_)); + }); + wait(); + clear_expect(true); + expect_packet(":X10700555N;"); + usleep(250000); + clear_expect(true); + expect_packet(":X17020AAAN;"); + expect_packet(":X1610DAAAN;"); + expect_packet(":X15000AAAN;"); + expect_packet(":X14003AAAN;"); + set_seed(0xAAA); + run_x([this]() { + EXPECT_EQ(0x555, + alias_allocator_.get_allocated_alias(nextAliasNodeId_, &ex_)); + }); + wait(); + clear_expect(true); + expect_packet(":X10700AAAN;"); + usleep(250000); + run_x([this]() { + // This one should be marked as reserved. + EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0xAAA), + ifCan_->local_aliases()->lookup(NodeAlias(0xAAA))); + }); +} + #if 0 TEST_F(AsyncAliasAllocatorTest, TestDelay) { @@ -98,13 +241,11 @@ TEST_F(AsyncAliasAllocatorTest, TestDelay) TEST_F(AsyncAliasAllocatorTest, AllocateMultiple) { set_seed(0x555); - mainBufferPool->alloc(&b_); expect_packet(":X17020555N;"); expect_packet(":X1610D555N;"); expect_packet(":X15000555N;"); expect_packet(":X14003555N;"); expect_packet(":X10700555N;"); - alias_allocator_.send(b_); b_ = nullptr; get_next_alias(); EXPECT_EQ(0x555U, b_->data()->alias); @@ -117,15 +258,10 @@ TEST_F(AsyncAliasAllocatorTest, AllocateMultiple) expect_packet(":X14003AAAN;"); expect_packet(":X10700AAAN;"); - mainBufferPool->alloc(&b_); - alias_allocator_.send(b_); - b_ = nullptr; - /* Conflicts with the previous alias to be tested. That's not a problem at * this point however, because that alias has already left the * allocator. */ - send_packet( - ":X10700555N;"); + send_packet(":X10700555N;"); get_next_alias(); EXPECT_EQ(0xAAAU, b_->data()->alias); @@ -134,6 +270,7 @@ TEST_F(AsyncAliasAllocatorTest, AllocateMultiple) TEST_F(AsyncAliasAllocatorTest, AllocationConflict) { + clear_expect(true); set_seed(0x555); mainBufferPool->alloc(&b_); expect_packet(":X17020555N;"); @@ -143,6 +280,7 @@ TEST_F(AsyncAliasAllocatorTest, AllocationConflict) alias_allocator_.send(b_); b_ = nullptr; wait(); + clear_expect(true); set_seed(0xAA5); expect_packet(":X17020AA5N;"); expect_packet(":X1610DAA5N;"); @@ -150,21 +288,29 @@ TEST_F(AsyncAliasAllocatorTest, AllocationConflict) expect_packet(":X14003AA5N;"); expect_packet(":X10700AA5N;"); send_packet(":X10700555N;"); + twait(); + clear_expect(true); + run_x([this]() { + // This one should be marked as reserved. + EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0xAA5), + ifCan_->local_aliases()->lookup(NodeAlias(0xAA5))); + // This one should be unknown. + EXPECT_EQ(0U, ifCan_->local_aliases()->lookup(NodeAlias(0x555))); + }); get_next_alias(); EXPECT_EQ(0xAA5U, b_->data()->alias); EXPECT_EQ(AliasInfo::STATE_RESERVED, b_->data()->state); run_x([this]() { - // This one should be marked as reserved. - EXPECT_EQ(AliasCache::RESERVED_ALIAS_NODE_ID, + // This one should be marked for the new node ID. + EXPECT_EQ(nextAliasNodeId_, ifCan_->local_aliases()->lookup(NodeAlias(0xAA5))); - // This one should be unknown. - EXPECT_EQ(0U, ifCan_->local_aliases()->lookup(NodeAlias(0x555))); }); } TEST_F(AsyncAliasAllocatorTest, LateAllocationConflict) { + clear_expect(true); set_seed(0x555); mainBufferPool->alloc(&b_); expect_packet(":X17020555N;"); @@ -174,6 +320,7 @@ TEST_F(AsyncAliasAllocatorTest, LateAllocationConflict) alias_allocator_.send(b_); b_ = nullptr; wait(); + clear_expect(true); set_seed(0xAA5); usleep(100000); expect_packet(":X17020AA5N;"); @@ -182,10 +329,14 @@ TEST_F(AsyncAliasAllocatorTest, LateAllocationConflict) expect_packet(":X14003AA5N;"); send_packet(":X10700555N;"); wait(); + clear_expect(true); usleep(100000); expect_packet(":X10700AA5N;"); send_packet(":X10700555N;"); + twait(); + RX(EXPECT_EQ(1u, ifCan_->alias_allocator()->num_reserved_aliases())); get_next_alias(); + RX(EXPECT_EQ(0u, ifCan_->alias_allocator()->num_reserved_aliases())); EXPECT_EQ(0xAA5U, b_->data()->alias); EXPECT_EQ(AliasInfo::STATE_RESERVED, b_->data()->state); } @@ -217,4 +368,68 @@ TEST_F(AsyncAliasAllocatorTest, DifferentGenerated) // Makes sure 'other' disappears from the executor before destructing it. wait(); } + +TEST_F(AsyncAliasAllocatorTest, BulkFew) +{ + RX(EXPECT_EQ(0u, ifCan_->alias_allocator()->num_reserved_aliases())); + generate_aliases(ifCan_->alias_allocator(), 5); + expect_cid(aliases_.begin(), aliases_.end()); + LOG(INFO, "invoke"); + auto start_time = os_get_time_monotonic(); + auto invocation = invoke_flow_nowait(bulkAllocator_.get(), 5); + wait(); + LOG(INFO, "expect RIDs"); + clear_expect(true); + expect_rid(aliases_.begin(), aliases_.end()); + LOG(INFO, "wait for complete"); + invocation->wait(); + wait(); + clear_expect(true); + auto end_time = os_get_time_monotonic(); + EXPECT_LT(MSEC_TO_NSEC(200), end_time - start_time); + RX({ + EXPECT_EQ(5u, ifCan_->alias_allocator()->num_reserved_aliases()); + ifCan_->alias_allocator()->clear_reserved_aliases(); + EXPECT_EQ(0u, ifCan_->alias_allocator()->num_reserved_aliases()); + }); +} + +TEST_F(AsyncAliasAllocatorTest, BulkConflict) +{ + generate_aliases(ifCan_->alias_allocator(), 7); + clear_expect(true); + expect_cid(aliases_.begin(), aliases_.begin() + 5); + LOG(INFO, "invoke"); + auto invocation = invoke_flow_nowait(bulkAllocator_.get(), 5); + wait(); + LOG(INFO, "send conflicts"); + clear_expect(true); + expect_cid(aliases_.begin() + 5, aliases_.end()); + send_packet(StringPrintf(":X10700%03XN;", aliases_[0])); + send_packet(StringPrintf(":X10700%03XN;", aliases_[1])); + wait(); + usleep(10000); + wait(); + LOG(INFO, "expect RIDs"); + clear_expect(true); + expect_rid(aliases_.begin() + 2, aliases_.end()); + LOG(INFO, "wait for complete"); + invocation->wait(); + clear_expect(true); +} + +TEST_F(AsyncAliasAllocatorTest, BulkMany) +{ + clear_expect(true); + generate_aliases(ifCan_->alias_allocator(), 150); + expect_cid(aliases_.begin(), aliases_.end()); + expect_rid(aliases_.begin(), aliases_.end()); + LOG(INFO, "invoke"); + auto invocation = invoke_flow_nowait(bulkAllocator_.get(), 150); + wait(); + LOG(INFO, "wait for complete"); + invocation->wait(); + clear_expect(true); +} + } // namespace openlcb diff --git a/src/openlcb/AliasAllocator.hxx b/src/openlcb/AliasAllocator.hxx index c39606a6b..81f8b6592 100644 --- a/src/openlcb/AliasAllocator.hxx +++ b/src/openlcb/AliasAllocator.hxx @@ -69,9 +69,9 @@ struct AliasInfo } /** The current alias. This is 0 if the alias needs to be generated. */ - unsigned alias : 12; - unsigned state : 3; - unsigned return_to_reallocation : 1; + uint16_t alias : 12; + uint16_t state : 3; + uint16_t return_to_reallocation : 1; enum State { @@ -108,24 +108,57 @@ public: */ AliasAllocator(NodeID if_id, IfCan *if_can); + /** Destructor */ virtual ~AliasAllocator(); + /** @return the Node ID for the interface. */ + NodeID if_node_id() + { + return if_id_; + } + /** Resets the alias allocator to the state it was at construction. useful * after connection restart in order to ensure it will try to allocate the * same alias. */ void reinit_seed(); - /** "Allocate" a buffer from this pool (but without initialization) in - * order to get a reserved alias. */ - QAsync *reserved_aliases() - { - return &reserved_alias_pool_; - } + /** Returns a new alias to check from the random sequence. Checks that it + * is not in the alias cache yet.*/ + NodeAlias get_new_seed(); + + /** Allocates an alias from the reserved but unused aliases list. If there + * is a free alias there, that alias will be reassigned to destination_id + * in the local alias cache, and done will never be notified. If there is + * no free alias, then a new alias will be allocated, and done will be + * notified when the allocation is complete. Then the call has to be + * re-tried by the destination flow. + * @param destination_id if there is a free alias right now, it will be + * assigned to this Node ID in the local alias cache. + * @param done if an async allocation is necessary, this will be notified + * after a new alias has been received. + * @return the alias if the it was allocated inline, or 0 if there will be + * an asynchronous notification coming later. */ + NodeAlias get_allocated_alias(NodeID destination_id, Executable *done); + + /** @return the number of aliases that are reserved and available for new + * virtual nodes to use. */ + unsigned num_reserved_aliases(); + + /** Removes all aliases that are reserved but not yet used. */ + void clear_reserved_aliases(); /** Releases a given alias. Sends out an AMR frame and puts the alias into * the reserved aliases queue. */ void return_alias(NodeID id, NodeAlias alias); + /** Call from an alternate alias allocator. Marks that alias is reserved + * for the local interface (RID frame is just sent out). Adds the alias to + * the local alias cache and wakes up a flow that might be waiting for an + * alias. + * @param alias a reserved node alias. */ + void add_allocated_alias(NodeAlias alias); + +#ifdef GTEST /** If there is a pending alias allocation waiting for the timer to expire, * finishes it immediately. Needed in test destructors. */ void TEST_finish_pending_allocation(); @@ -133,8 +166,15 @@ public: /** Adds an allocated aliad to the reserved aliases queue. @param alias the next allocated alias to add. */ - void TEST_add_allocated_alias(NodeAlias alias, bool repeat=false); - + void TEST_add_allocated_alias(NodeAlias alias); + + /** Overrides the configured value for reserve_unused_alias_count. */ + void TEST_set_reserve_unused_alias_count(unsigned count) + { + reserveUnusedAliases_ = count; + } +#endif + private: /** Listens to incoming CAN frames and handles alias conflicts. */ class ConflictHandler : public IncomingFrameHandler @@ -172,10 +212,8 @@ private: StateFlowTimer timer_; - /** Freelist of reserved aliases that can be used by virtual nodes. The - AliasAllocatorFlow will post successfully reserved aliases to this - allocator. */ - QAsync reserved_alias_pool_; + /// Set of client flows that are waiting for allocating an alias. + Q waitingClients_; /// 48-bit nodeID that we will use for alias reservations. NodeID if_id_; @@ -195,6 +233,10 @@ private: /// Seed for generating random-looking alias numbers. unsigned seed_ : 12; + /// How many unused aliases we should reserve. Currently we only support 0 + /// or 1 as value. + unsigned reserveUnusedAliases_ : 8; + /// Notifiable used for tracking outgoing frames. BarrierNotifiable n_; diff --git a/src/openlcb/AliasCache.cxx b/src/openlcb/AliasCache.cxx index a637b3d6b..d1781229b 100644 --- a/src/openlcb/AliasCache.cxx +++ b/src/openlcb/AliasCache.cxx @@ -33,31 +33,263 @@ #include "openlcb/AliasCache.hxx" +#include + #include "os/OS.hxx" +#include "utils/logging.h" + +#ifdef GTEST +#define TEST_CONSISTENCY +#endif namespace openlcb { #define CONSTANT 0x1B0CA37ABA9 /**< constant for random number generation */ -const NodeID AliasCache::RESERVED_ALIAS_NODE_ID = 1; +/// 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; + +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; + } + } + else + { + 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; + for (PoolIdx p = freeList; !p.empty(); p = p.deref(this)->older_) + { + Metadata *m = p.deref(this); + if (free_entries.count(m)) + { + LOG(INFO, "Duplicate entry on freelist."); + return 5; + } + free_entries.insert(m); + } + if (free_entries.size() + aliasMap.size() != 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; + } + } + for (auto kv : idMap) + { + if (free_entries.count(kv.deref(this))) + { + LOG(INFO, "Found an id entry in the freelist."); + return 20; + } + } + if (aliasMap.size() == 0) + { + 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; + } + } + else + { + 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 + } + } + if (aliasMap.size() == 0) + { + return 0; + } + // Check linking. + { + PoolIdx prev = oldest; + unsigned count = 1; + if (!prev.deref(this)->older_.empty()) + { + LOG(INFO, "Prev link points to empty."); + return 13; + } + while (!prev.deref(this)->newer_.empty()) + { + auto next = prev.deref(this)->newer_; + ++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; + } + } + { + PoolIdx next = newest; + if (!next.deref(this)->newer_.empty()) + { + LOG(INFO, "Newest has a newer link."); + return 15; + } + while (!next.deref(this)->older_.empty()) + { + 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; + } + } + for (unsigned i = 0; i < entries; ++i) + { + if (free_entries.count(pool + i)) + { + continue; + } + auto *e = pool + i; + if (idMap.find(e->get_node_id()) == idMap.end()) + { + 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; + } + } + return 0; +} + +#endif void AliasCache::clear() { idMap.clear(); aliasMap.clear(); - oldest = nullptr; - newest = nullptr; - freeList = nullptr; + oldest.idx_ = NONE_ENTRY; + newest.idx_ = NONE_ENTRY; + freeList.idx_ = NONE_ENTRY; /* initialize the freeList */ for (size_t i = 0; i < entries; ++i) { - pool[i].prev = NULL; - pool[i].next = freeList; - freeList = pool + i; + pool[i].newer_.idx_ = NONE_ENTRY; + pool[i].older_ = freeList; + freeList.idx_ = i; } } +void debug_print_entry(void *, NodeID id, NodeAlias alias) +{ + LOG(INFO, "[%012" PRIx64 "]: %03X", id, alias); +} + +void debug_print_cache(AliasCache *c) +{ + LOG(INFO, "Alias cache:"); + c->for_each(&debug_print_entry, nullptr); +} + /** Add an alias to an alias cache. * @param id 48-bit NMRAnet Node ID to associate alias with * @param alias 12-bit alias associated with Node ID @@ -69,78 +301,109 @@ void AliasCache::add(NodeID id, NodeAlias alias) Metadata *insert; - AliasMap::Iterator it = aliasMap.find(alias); + 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 */ - insert = (*it).second; - remove(alias); - + insert = it->deref(this); + remove(insert->alias_); + + if (removeCallback) + { + /* tell the interface layer that we removed this mapping */ + (*removeCallback)(insert->get_node_id(), insert->alias_, context); + } + } + auto nit = idMap.find(id); + if (nit != idMap.end()) + { + /* we already have a mapping for this id, so lets remove it */ + insert = nit->deref(this); + remove(insert->alias_); + if (removeCallback) { /* tell the interface layer that we removed this mapping */ - (*removeCallback)(insert->id, insert->alias, context); + (*removeCallback)(insert->get_node_id(), insert->alias_, context); } } - if (freeList) + if (!freeList.empty()) { /* found an empty slot */ - insert = freeList; - freeList = insert->next; + insert = freeList.deref(this); + freeList = insert->older_; } else { - HASSERT(oldest != NULL && newest != NULL); + HASSERT(!oldest.empty() && !newest.empty()); /* kick out the oldest mapping and re-link the oldest endpoint */ - insert = oldest; - if (oldest->newer) + insert = oldest.deref(this); + auto second = insert->newer_; + if (!second.empty()) { - oldest->newer->older = NULL; + second.deref(this)->older_.idx_ = NONE_ENTRY; } - if (insert == newest) + if (insert == newest.deref(this)) { - newest = NULL; + newest.idx_ = NONE_ENTRY; } - oldest = oldest->newer; + oldest = second; - aliasMap.erase(insert->alias); - idMap.erase(insert->id); + aliasMap.erase(aliasMap.find(insert->alias_)); + idMap.erase(idMap.find(insert->get_node_id())); if (removeCallback) { /* tell the interface layer that we removed this mapping */ - (*removeCallback)(insert->id, insert->alias, context); + (*removeCallback)(insert->get_node_id(), insert->alias_, context); } } - - insert->timestamp = OSTime::get_monotonic(); - insert->id = id; - insert->alias = alias; - aliasMap[alias] = insert; - idMap[id] = insert; + 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; + + PoolIdx n; + n.idx_ = insert - pool; + aliasMap.insert(PoolIdx(n)); + idMap.insert(PoolIdx(n)); /* update the time based list */ - insert->newer = NULL; - if (newest == NULL) + insert->newer_.idx_ = NONE_ENTRY; + if (newest.empty()) { /* if newest == NULL, then oldest must also be NULL */ - HASSERT(oldest == NULL); + HASSERT(oldest.empty()); - insert->older = NULL; - oldest = insert; + insert->older_.idx_ = NONE_ENTRY; + oldest = n; } else { - insert->older = newest; - newest->newer = insert; + insert->older_ = newest; + newest.deref(this)->newer_ = n; } - newest = insert; - - return; + newest = n; + +#if defined(TEST_CONSISTENCY) + consistency_result = check_consistency(); + HASSERT(0 == consistency_result); +#endif } /** Remove an alias from an alias cache. This method does not call the @@ -150,44 +413,76 @@ void AliasCache::add(NodeID id, NodeAlias alias) */ void AliasCache::remove(NodeAlias alias) { - AliasMap::Iterator it = aliasMap.find(alias); + auto it = aliasMap.find(alias); if (it != aliasMap.end()) { - Metadata *metadata = (*it).second; + Metadata *metadata = it->deref(this); aliasMap.erase(it); - idMap.erase(metadata->id); - - if (metadata->newer) + idMap.erase(idMap.find(metadata->get_node_id())); + + if (!metadata->newer_.empty()) { - metadata->newer->older = metadata->older; + metadata->newer_.deref(this)->older_ = metadata->older_; } - if (metadata->older) + if (!metadata->older_.empty()) { - metadata->older->newer = metadata->newer; + metadata->older_.deref(this)->newer_ = metadata->newer_; } - if (metadata == newest) + if (metadata == newest.deref(this)) { - newest = metadata->older; + newest = metadata->older_; } - if (metadata == oldest) + if (metadata == oldest.deref(this)) { - oldest = metadata->newer; + oldest = metadata->newer_; } - - metadata->next = freeList; - freeList = metadata; + + metadata->older_ = freeList; + freeList.idx_ = metadata - pool; } - + +#if defined(TEST_CONSISTENCY) + consistency_result = check_consistency(); + HASSERT(0 == consistency_result); +#endif } bool AliasCache::retrieve(unsigned entry, NodeID* node, NodeAlias* alias) { HASSERT(entry < size()); Metadata* md = pool + entry; - if (!md->alias) return false; - if (node) *node = md->id; - if (alias) *alias = md->alias; + if (!md->alias_) + { + return false; + } + if (node) + { + *node = md->get_node_id(); + } + if (alias) + { + *alias = resolve_notresponding(md->alias_); + } + return true; +} + +bool AliasCache::next_entry(NodeID bound, NodeID *node, NodeAlias *alias) +{ + auto it = idMap.upper_bound(bound); + if (it == idMap.end()) + { + return false; + } + Metadata *metadata = it->deref(this); + if (alias) + { + *alias = resolve_notresponding(metadata->alias_); + } + if (node) + { + *node = metadata->get_node_id(); + } return true; } @@ -199,15 +494,15 @@ NodeAlias AliasCache::lookup(NodeID id) { HASSERT(id != 0); - IdMap::Iterator it = idMap.find(id); + auto it = idMap.find(id); if (it != idMap.end()) { - Metadata *metadata = (*it).second; - + Metadata *metadata = it->deref(this); + /* update timestamp */ touch(metadata); - return metadata->alias; + return resolve_notresponding(metadata->alias_); } /* no match found */ @@ -222,15 +517,15 @@ NodeID AliasCache::lookup(NodeAlias alias) { HASSERT(alias != 0); - AliasMap::Iterator it = aliasMap.find(alias); + auto it = aliasMap.find(alias); if (it != aliasMap.end()) { - Metadata *metadata = (*it).second; - + Metadata *metadata = it->deref(this); + /* update timestamp */ touch(metadata); - return metadata->id; + return metadata->get_node_id(); } /* no match found */ @@ -246,9 +541,11 @@ void AliasCache::for_each(void (*callback)(void*, NodeID, NodeAlias), void *cont { HASSERT(callback != NULL); - for (Metadata *metadata = newest; metadata != NULL; metadata = metadata->older) + for (PoolIdx idx = newest; !idx.empty(); idx = idx.deref(this)->older_) { - (*callback)(context, metadata->id, metadata->alias); + Metadata *metadata = idx.deref(this); + (*callback)(context, metadata->get_node_id(), + resolve_notresponding(metadata->alias_)); } } @@ -277,26 +574,28 @@ NodeAlias AliasCache::generate() */ void AliasCache::touch(Metadata* metadata) { - metadata->timestamp = OSTime::get_monotonic(); - - if (metadata != newest) + if (metadata != newest.deref(this)) { - if (metadata == oldest) + if (metadata == oldest.deref(this)) { - oldest = metadata->newer; - oldest->older = NULL; + oldest = metadata->newer_; + oldest.deref(this)->older_.idx_ = NONE_ENTRY; } else { /* we have someone older */ - metadata->older->newer = metadata->newer; + metadata->older_.deref(this)->newer_ = metadata->newer_; } - metadata->newer->older = metadata->older; - metadata->newer = NULL; - metadata->older = newest; - newest->newer = metadata; - newest = metadata; + metadata->newer_.deref(this)->older_ = metadata->older_; + metadata->newer_.idx_ = NONE_ENTRY; + metadata->older_ = newest; + newest.deref(this)->newer_.idx_ = metadata - pool; + newest.idx_ = metadata - pool; } +#if defined(TEST_CONSISTENCY) + consistency_result = check_consistency(); + HASSERT(0 == consistency_result); +#endif } } diff --git a/src/openlcb/AliasCache.cxxtest b/src/openlcb/AliasCache.cxxtest index 9998d8cb4..618a75b1e 100644 --- a/src/openlcb/AliasCache.cxxtest +++ b/src/openlcb/AliasCache.cxxtest @@ -52,6 +52,22 @@ static void alias_callback(void *context, NodeID node_id, NodeAlias alias) count++; } +/// Verifies that calling the next method gives back the correct next node ID. +void test_alias_next(AliasCache *cache, int count) +{ + NodeID last = 0; + NodeID next = 0; + NodeAlias next_alias; + for (int i = 0; i < count; i++) + { + ASSERT_TRUE(cache->next_entry(last, &next, &next_alias)); + EXPECT_EQ(node_ids[i], next); + EXPECT_EQ(aliases[i], next_alias); + last = next; + } + EXPECT_FALSE(cache->next_entry(last, &next, &next_alias)); +} + TEST(AliasCacheTest, constructor) { /* construct an object, map in a node, and run the for_each */ @@ -103,6 +119,8 @@ TEST(AliasCacheTest, ordering) EXPECT_TRUE(aliasCache->lookup((NodeID)103) == 6); EXPECT_TRUE(aliasCache->lookup((NodeID)102) == 11); EXPECT_TRUE(aliasCache->lookup((NodeID)101) == 10); + + test_alias_next(aliasCache, 6); } TEST(AliasCacheTest, reordering) @@ -126,8 +144,10 @@ TEST(AliasCacheTest, reordering) EXPECT_TRUE(aliasCache->lookup((NodeAlias)84) == 104); EXPECT_TRUE(aliasCache->lookup((NodeAlias)6) == 103); EXPECT_TRUE(aliasCache->lookup((NodeAlias)11) == 102); - EXPECT_TRUE(aliasCache->lookup((NodeAlias)10) == 101); - + EXPECT_TRUE(aliasCache->lookup((NodeAlias)10) == 101); + + test_alias_next(aliasCache, 6); + aliasCache->for_each(alias_callback, (void*)0xDEADBEEF); EXPECT_EQ(count, 6); @@ -152,7 +172,9 @@ TEST(AliasCacheTest, reordering) EXPECT_TRUE(aliasCache->lookup((NodeID)104) == 84); EXPECT_TRUE(aliasCache->lookup((NodeID)103) == 6); EXPECT_TRUE(aliasCache->lookup((NodeID)102) == 11); - EXPECT_TRUE(aliasCache->lookup((NodeID)101) == 10); + EXPECT_TRUE(aliasCache->lookup((NodeID)101) == 10); + + test_alias_next(aliasCache, 6); } TEST(AliasCacheTest, generate) @@ -426,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: @@ -463,94 +510,6 @@ protected: AliasCache c_{get_id(0x33), 10}; }; -namespace openlcb { -int AliasCache::check_consistency() { - if (idMap.size() != aliasMap.size()) return 1; - if (aliasMap.size() == entries) { - if (freeList != nullptr) return 2; - } else { - if (freeList == nullptr) return 3; - } - if (aliasMap.size() == 0 && - (oldest != nullptr || newest != nullptr)) { - return 4; - } - std::set free_entries; - for (Metadata* m = freeList; m; m=m->next) { - if (free_entries.count(m)) { - return 5; // duplicate entry on freelist - } - free_entries.insert(m); - } - if (free_entries.size() + aliasMap.size() != entries) { - return 6; // lost some metadata entries - } - for (auto kv : aliasMap) { - if (free_entries.count(kv.second)) { - return 19; - } - } - for (auto kv : idMap) { - if (free_entries.count(kv.second)) { - return 20; - } - } - if (aliasMap.size() == 0) { - if (oldest != nullptr) return 7; - if (newest != nullptr) return 8; - } else { - if (oldest == nullptr) return 9; - if (newest == nullptr) return 10; - } - if (free_entries.count(oldest)) { - return 11; // oldest is free - } - if (free_entries.count(newest)) { - return 12; // newest is free - } - if (aliasMap.size() == 0) return 0; - // Check linking. - { - Metadata* prev = oldest; - unsigned count = 1; - if (prev->older) return 13; - while (prev->newer) { - auto* next = prev->newer; - ++count; - if (free_entries.count(next)) { - return 21; - } - if (next->older != prev) return 14; - prev = next; - } - if (prev != newest) return 18; - if (count != aliasMap.size()) return 27; - } - { - Metadata* next = newest; - if (next->newer) return 15; - while (next->older) { - auto* prev = next->older; - if (free_entries.count(prev)) { - return 22; - } - if (prev->newer != next) return 16; - next = prev; - } - if (next != oldest) return 17; - } - for (unsigned i = 0; i < entries; ++i) { - if (free_entries.count(pool+i)) continue; - auto* e = pool+i; - if (idMap.find(e->id) == idMap.end()) return 23; - if (idMap[e->id] != e) return 24; - if (aliasMap.find(e->alias) == aliasMap.end()) return 25; - if (aliasMap[e->alias] != e) return 26; - } - return 0; -} - -} TEST_F(AliasStressTest, stress_test) { diff --git a/src/openlcb/AliasCache.hxx b/src/openlcb/AliasCache.hxx index b59ddc55d..9c54a3e39 100644 --- a/src/openlcb/AliasCache.hxx +++ b/src/openlcb/AliasCache.hxx @@ -35,9 +35,9 @@ #define _OPENLCB_ALIASCACHE_HXX_ #include "openlcb/Defs.hxx" -#include "utils/macros.h" #include "utils/Map.hxx" -#include "utils/RBTree.hxx" +#include "utils/SortedListMap.hxx" +#include "utils/macros.h" namespace openlcb { @@ -48,8 +48,40 @@ namespace openlcb * is no mutual exclusion locking mechanism built into this class. Mutual * exclusion must be handled by the user as needed. * - * @todo the class uses RBTree, consider a version that is a linear search for - * a small number of entries. + * This data structure is sometimes used with very large entry count + * (hundreds), therefore we must be careful about memory efficiency! + * + * Theory of operation: + * + * The data structure has three access patterns: + * - lookup of alias -> ID + * - lookup of ID -> alias + * - eviction of oldest entry from the cache + * + * We have three data structures to match these use-cases: + * + * The struct Metadata stores the actual NodeID and NodeAlias values. This is + * laid out in the pre-allocated C array `pool`. A freeList shows where unused + * entries are. + * + * To support the eviction of oldest entry, an LRU doubly-linked list is + * created in these Metadata entries. The links are represented by indexes into + * the `pool` array. + * + * Indexes into the `pool` are encapsulated into the PoolIdx struct to make + * them a unique C++ type. This is needed for template disambiguation. We also + * have a dereference function on PoolIdx that turns it into a Metadata + * pointer. + * + * To support lookup by alias, we have a SortedListSet which contains all used + * indexes as PoolIdx, sorted by the alias property in the respective entry in + * `pool`. This is achieved by a custom comparator that dereferences the + * PoolIdx object and fetches the alias from the Metadata struct. The sorted + * vector is maintained using the SortedListSet<> template, and takes a total + * of only 2 bytes per entry. + * + * A similar sorted vector is kept sorted by the NodeID values. This also takes + * only 2 bytes per entry. */ class AliasCache { @@ -62,24 +94,23 @@ public: * @param context context pointer to pass to remove_callback */ AliasCache(NodeID seed, size_t _entries, - void (*remove_callback)(NodeID id, NodeAlias alias, void *) = NULL, - void *context = NULL) - : pool(new Metadata[_entries]), - freeList(NULL), - aliasMap(_entries), - idMap(_entries), - oldest(NULL), - newest(NULL), - seed(seed), - entries(_entries), - removeCallback(remove_callback), - context(context) + void (*remove_callback)(NodeID id, NodeAlias alias, void *) = NULL, + void *context = NULL) + : pool(new Metadata[_entries]) + , aliasMap(this) + , idMap(this) + , seed(seed) + , entries(_entries) + , removeCallback(remove_callback) + , context(context) { + aliasMap.reserve(_entries); + idMap.reserve(_entries); clear(); } - /** This NodeID will be used for reserved but unused local aliases. */ - static const NodeID RESERVED_ALIAS_NODE_ID; + /// Sentinel entry for empty lists. + static constexpr uint16_t NONE_ENTRY = 0xFFFFu; /** Reinitializes the entire map. */ void clear(); @@ -126,12 +157,22 @@ public: * changes. * @param entry is between 0 and size() - 1. * @param node will be filled with the node ID. May be null. - * @param aliad will be filles with the alias. May be null. + * @param alias will be filled with the alias. May be null. * @return true if the entry is valid, and node and alias were filled, otherwise false if the entry is not allocated. */ bool retrieve(unsigned entry, NodeID* node, NodeAlias* alias); - /** Generate a 12-bit pseudo-random alias for a givin alias cache. + /** Retrieves the next entry by increasing node ID. + * @param bound is a Node ID. Will search for the next largest node ID + * (upper bound of this key). + * @param node will be filled with the node ID. May be null. + * @param alias will be filled with the alias. May be null. + * @return true if a larger element is found and node and alias were + * filled, otherwise false if bound is >= the largest node ID in the cache. + */ + bool next_entry(NodeID bound, NodeID *node, NodeAlias *alias); + + /** Generate a 12-bit pseudo-random alias for a given alias cache. * @return pseudo-random 12-bit alias, an alias of zero is invalid */ NodeAlias generate(); @@ -146,53 +187,172 @@ public: int check_consistency(); private: - enum + struct Metadata; + class PoolIdx; + friend class PoolIdx; + + /// Encapsulation of a pointer into the pool array. + class PoolIdx { - /** marks an unused mapping */ - UNUSED_MASK = 0x10000000 + public: + /// Constructor. Sets the pointer to invalid. + PoolIdx() + : idx_(NONE_ENTRY) + { + } + /// @return true if this entry does not point anywhere. + bool empty() + { + return idx_ == NONE_ENTRY; + } + /// Indexes the pool of the AliasCache. + uint16_t idx_; + /// Dereferences a pool index as if it was a pointer. + /// @param parent the AliasCache whose pool to index. + /// @return referenced Metadata pointer. + Metadata *deref(AliasCache *parent) + { + DASSERT(idx_ != NONE_ENTRY); + return parent->pool + idx_; + } }; /** Interesting information about a given cache entry. */ struct Metadata { - NodeID id = 0; /**< 48-bit NMRAnet Node ID */ - NodeAlias alias = 0; /**< NMRAnet alias */ - long long timestamp; /**< time stamp of last usage */ - union + /// Sets the node ID field. + /// @param id the node ID to set. + void set_node_id(NodeID id) { - Metadata *prev; /**< unused */ - Metadata *newer; /**< pointer to the next newest entry */ - }; - union + nodeIdLow_ = id & 0xFFFFFFFFu; + nodeIdHigh_ = (id >> 32) & 0xFFFFu; + } + + /// @return the node ID field. + NodeID get_node_id() { - Metadata *next; /**< pointer to next freeList entry */ - Metadata *older; /**< pointer to the next oldest entry */ - }; + uint64_t h = nodeIdHigh_; + h <<= 32; + h |= nodeIdLow_; + return h; + } + + /// OpenLCB Node ID low 32 bits. + uint32_t nodeIdLow_ = 0; + /// OpenLCB Node ID high 16 bits. + uint16_t nodeIdHigh_ = 0; + /// OpenLCB-CAN alias + NodeAlias alias_ = 0; + /// Index of next-newer entry according to the LRU linked list. + PoolIdx newer_; + /// Index of next-older entry according to the LRU linked list. + PoolIdx older_; }; /** pointer to allocated Metadata pool */ Metadata *pool; - - /** list of unused mapping entries */ - Metadata *freeList; - + + /// Comparator object comparing the aliases stored in the pool. + class AliasComparator + { + public: + /// Constructor + /// @param parent owning AliasCache. + AliasComparator(AliasCache *parent) + : parent_(parent) + { + } + + /// Less-than action. + /// @param e left hand side + /// @param alias right hand side + bool operator()(PoolIdx e, uint16_t alias) const + { + return e.deref(parent_)->alias_ < alias; + } + + /// Less-than action. + /// @param alias left hand side + /// @param e right hand side + bool operator()(uint16_t alias, PoolIdx e) const + { + return alias < e.deref(parent_)->alias_; + } + + /// Less-than action. + /// @param a left hand side + /// @param b right hand side + bool operator()(PoolIdx a, PoolIdx b) const + { + return a.deref(parent_)->alias_ < b.deref(parent_)->alias_; + } + + private: + /// AliasCache whose pool we are indexing into. + AliasCache *parent_; + }; + + /// Comparator object comparing the aliases stored in the pool. + class IdComparator + { + public: + /// Constructor + /// @param parent owning AliasCache. + IdComparator(AliasCache *parent) + : parent_(parent) + { + } + + /// Less-than action. + /// @param e left hand side + /// @param id right hand side + bool operator()(PoolIdx e, NodeID id) const + { + return e.deref(parent_)->get_node_id() < id; + } + + /// Less-than action. + /// @param id left hand side + /// @param e right hand side + bool operator()(NodeID id, PoolIdx e) const + { + return id < e.deref(parent_)->get_node_id(); + } + + /// Less-than action. + /// @param a left hand side + /// @param b right hand side + bool operator()(PoolIdx a, PoolIdx b) const + { + return a.deref(parent_)->get_node_id() < + b.deref(parent_)->get_node_id(); + } + + private: + /// AliasCache whose pool we are indexing into. + AliasCache *parent_; + }; + /** Short hand for the alias Map type */ - typedef Map AliasMap; - + typedef SortedListSet AliasMap; + /** Short hand for the ID Map type */ - typedef Map IdMap; + typedef SortedListSet IdMap; /** Map of alias to corresponding Metadata */ AliasMap aliasMap; /** Map of Node ID to corresponding Metadata */ IdMap idMap; - - /** oldest untouched entry */ - Metadata *oldest; - - /** newest, most recently touched entry */ - Metadata *newest; + + /** list of unused mapping entries (index into pool) */ + PoolIdx freeList; + + /** oldest untouched entry (index into pool) */ + PoolIdx oldest; + + /** newest, most recently touched entry (index into pool) */ + PoolIdx newest; /** Seed for the generation of the next alias */ NodeID seed; @@ -214,6 +374,6 @@ private: DISALLOW_COPY_AND_ASSIGN(AliasCache); }; -} /* namepace NMRAnet */ +} /* namespace openlcb */ -#endif /* _NMRAnetAliasCache_hxx */ +#endif // _OPENLCB_ALIASCACHE_HXX_ diff --git a/src/openlcb/BroadcastTime.cxx b/src/openlcb/BroadcastTime.cxx index fb5a91be0..0e2a8630a 100644 --- a/src/openlcb/BroadcastTime.cxx +++ b/src/openlcb/BroadcastTime.cxx @@ -46,8 +46,10 @@ namespace openlcb // void BroadcastTime::clear_timezone() { +#ifndef ESP32 setenv("TZ", "GMT0", 1); tzset(); +#endif } } // namespace openlcb diff --git a/src/openlcb/BroadcastTime.hxx b/src/openlcb/BroadcastTime.hxx index 4aeb591a0..f01c42881 100644 --- a/src/openlcb/BroadcastTime.hxx +++ b/src/openlcb/BroadcastTime.hxx @@ -130,9 +130,10 @@ public: if (started_) { long long elapsed = OSTime::get_monotonic() - timestamp_; - elapsed = ((elapsed * rate_) + 2) / 4; + elapsed = ((elapsed * std::abs(rate_)) + 2) / 4; - return seconds_ + (time_t)NSEC_TO_SEC_ROUNDED(elapsed); + time_t diff = (time_t)NSEC_TO_SEC_ROUNDED(elapsed); + return (rate_ < 0) ? seconds_ - diff : seconds_ + diff; } else { @@ -210,7 +211,12 @@ public: { if (rate != 0 && rate >= -2048 && rate <= 2047) { - *real_nsec = ((SEC_TO_NSEC(fast_sec) * 4) + (rate / 2)) / rate; + *real_nsec = ((SEC_TO_NSEC(std::abs(fast_sec)) * 4) + + (std::abs(rate) / 2)) / rate; + if (fast_sec < 0) + { + *real_nsec = -(*real_nsec); + } return true; } else @@ -230,7 +236,11 @@ public: { if (rate != 0 && rate >= -2048 && rate <= 2047) { - *fast_sec = (NSEC_TO_SEC(real_nsec * rate) + 2) / 4; + *fast_sec = (std::abs(NSEC_TO_SEC(real_nsec * rate)) + 2) / 4; + if ((real_nsec < 0 && rate > 0) || (real_nsec >= 0 && rate < 0)) + { + *fast_sec = -(*fast_sec); + } return true; } else @@ -252,7 +262,8 @@ public: if (fast_sec_to_real_nsec_period_abs(fast_sec - seconds_, real_nsec)) { *real_nsec += timestamp_; - *real_nsec -= OSTime::get_monotonic(); + long long monotonic = OSTime::get_monotonic(); + *real_nsec -= monotonic; return true; } else @@ -347,6 +358,10 @@ public: return &tm_; } + /// Has a time server been detected? + /// @return true if a time server has been detected, else false + virtual bool is_server_detected() = 0; + protected: class SetFlow : public StateFlowBase { diff --git a/src/openlcb/BroadcastTimeAlarm.cxxtest b/src/openlcb/BroadcastTimeAlarm.cxxtest index 769615a68..d70ecd2ef 100644 --- a/src/openlcb/BroadcastTimeAlarm.cxxtest +++ b/src/openlcb/BroadcastTimeAlarm.cxxtest @@ -1,4 +1,414 @@ #include "utils/async_if_test_helper.hxx" #include "openlcb/BroadcastTimeAlarm.hxx" +#include "openlcb/BroadcastTimeServer.hxx" +#if 0 +#define PRINT_ALL_PACKETS() print_all_packets() +#else +#define PRINT_ALL_PACKETS() +#endif + +namespace openlcb +{ + +class BroadcastTimeAlarmTest : public AsyncNodeTest +{ +public: + MOCK_METHOD0(mock_callback, void()); + + void update_callback(BarrierNotifiable* done) + { + mock_callback(); + done->notify(); + } + +protected: + BroadcastTimeAlarmTest() + { + PRINT_ALL_PACKETS(); + + ::testing::Sequence s1; + + // consumer/producer identify ranges + expect_packet(":X1952422AN010100000100FFFF;").InSequence(s1); + expect_packet(":X194A422AN0101000001008000;").InSequence(s1); + + // sync sequence + expect_packet(":X1954422AN010100000100F001;").InSequence(s1); + expect_packet(":X1954422AN0101000001004000;").InSequence(s1); + expect_packet(":X1954422AN01010000010037B2;").InSequence(s1); + expect_packet(":X1954422AN0101000001002101;").InSequence(s1); + expect_packet(":X1954422AN0101000001000000;").InSequence(s1); + + server_ = new BroadcastTimeServer( + node_, BroadcastTimeDefs::DEFAULT_FAST_CLOCK_ID); + + send_packet(":X19970001N;"); + wait_for_event_thread(); + + clear_expect(true); + + alarm_ = new BroadcastTimeAlarm( + node_, server_, + std::bind(&BroadcastTimeAlarmTest::update_callback, this, + std::placeholders::_1)); + } + + ~BroadcastTimeAlarmTest() + { + clear_expect(); + wait_for_event_thread(); + alarm_->shutdown(); + server_->shutdown(); + wait(); + while (!alarm_->is_shutdown()) + { + usleep(10000); + wait(); + } + while (!server_->is_shutdown()) + { + usleep(10000); + wait(); + } + twait(); + + delete alarm_; + delete server_; + } + + BroadcastTimeServer *server_; + BroadcastTimeAlarm *alarm_; +}; + +TEST_F(BroadcastTimeAlarmTest, Create) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); +}; + +TEST_F(BroadcastTimeAlarmTest, SetPeriod) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set_period(60); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureExpires) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set(60); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureDoesNotExpire) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0); + alarm_->set(60); + usleep(110000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureJumpOver) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set(60); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump over + server_->set_time(2, 0); + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + // jump back + server_->set_time(0, 0); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureExpiresBackward) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 2); + server_->set_year(1970); + server_->set_rate_quarters(-2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set(86400 - 60); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureDoesNotExpireBackward) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 2); + server_->set_year(1970); + server_->set_rate_quarters(-2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0); + alarm_->set(86400 - 60); + usleep(110000); +}; + +TEST_F(BroadcastTimeAlarmTest, SetFutureJumpOverBackward) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(12, 0); + server_->set_date(1, 2); + server_->set_year(1970); + server_->set_rate_quarters(-2000); + server_->start(); + + ::testing::Sequence s1; + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + alarm_->set(129600 - 60); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump over + server_->set_time(10, 0); + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + // jump back + server_->set_time(12, 0); +}; + +TEST_F(BroadcastTimeAlarmTest, Date) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(23, 59); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + BroadcastTimeAlarmDate da(node_, server_, + std::bind(&BroadcastTimeAlarmTest::update_callback, this, + std::placeholders::_1)); + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow date rollover + usleep(030000); + + // now test backwards + server_->stop(); + server_->set_rate_quarters(-2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->start(); + + EXPECT_CALL(*this, mock_callback()).Times(0).InSequence(s1); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow date rollover + usleep(30000); + + da.shutdown(); + while (!da.is_shutdown()) + { + usleep(10000); + } +}; + +TEST_F(BroadcastTimeAlarmTest, Minute) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + server_->start(); + + ::testing::Sequence s1; + + BroadcastTimeAlarmMinute ma(node_, server_, + std::bind(&BroadcastTimeAlarmTest::update_callback, this, + std::placeholders::_1)); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); + + // run for five more fast minutes + EXPECT_CALL(*this, mock_callback()).Times(5).InSequence(s1); + usleep(600000); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump forward + server_->set_time(0, 8); + usleep(030000); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump back + server_->set_time(0, 6); + usleep(030000); + + ma.shutdown(); + while (!ma.is_shutdown()) + { + usleep(10000); + } +}; + +TEST_F(BroadcastTimeAlarmTest, MinuteBackward) +{ + // time is not setup, clock is not running, expect 0 as default + EXPECT_EQ(server_->time(), 0); + EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); + EXPECT_EQ(server_->day_of_year(), 0); + + expect_any_packet(); + + server_->set_rate_quarters(-2000); + server_->set_time(12, 0); + server_->set_date(1, 2); + server_->set_year(1970); + server_->start(); + + ::testing::Sequence s1; + + BroadcastTimeAlarmMinute ma(node_, server_, + std::bind(&BroadcastTimeAlarmTest::update_callback, this, + std::placeholders::_1)); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + usleep(110000); + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // allow time to expire + usleep(030000); + + // run for five more fast minutes + EXPECT_CALL(*this, mock_callback()).Times(5).InSequence(s1); + usleep(600000); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump forward + server_->set_time(11, 52); + usleep(030000); + + EXPECT_CALL(*this, mock_callback()).Times(1).InSequence(s1); + // jump back + server_->set_time(11, 54); + usleep(030000); + + ma.shutdown(); + while (!ma.is_shutdown()) + { + usleep(10000); + } +}; + +} // namespace openlcb diff --git a/src/openlcb/BroadcastTimeAlarm.hxx b/src/openlcb/BroadcastTimeAlarm.hxx index 5752571ff..3523c4e82 100644 --- a/src/openlcb/BroadcastTimeAlarm.hxx +++ b/src/openlcb/BroadcastTimeAlarm.hxx @@ -50,16 +50,17 @@ public: /// @param clock clock that our alarm is based off of /// @param callback callback for when alarm expires BroadcastTimeAlarm(Node *node, BroadcastTime *clock, - std::function callback) + std::function callback) : StateFlowBase(node->iface()) , clock_(clock) , wakeup_(this) , callback_(callback) , timer_(this) + , bn_() + , bnPtr_(nullptr) , expires_(0) , running_(false) , set_(false) - , waiting_(true) #if defined(GTEST) , shutdown_(false) #endif @@ -80,14 +81,17 @@ public: } /// Start the alarm to expire at the given period from now. - /// @time period in fast seconds from now to expire + /// @param period in fast seconds from now to expire. @ref period is a + /// a signed value. If the fast time rate is negative, the @ref + /// period passed in should also be negative for an expiration in + /// the future. void set_period(time_t period) { set(clock_->time() + period); } /// Start the alarm to expire at the given fast time. - /// @time time in seconds since epoch to expire + /// @param time in seconds since epoch to expire void set(time_t time) { bool need_wakeup = false; @@ -121,7 +125,6 @@ public: #if defined(GTEST) void shutdown() { - AtomicHolder h(this); shutdown_ = true; wakeup_.trigger(); } @@ -195,7 +198,6 @@ private: /// setup() if clock and/or alarm is not currently active Action setup() { - waiting_ = false; #if defined(GTEST) if (shutdown_) { @@ -227,7 +229,7 @@ private: } } - waiting_ = true; + bnPtr_ = bn_.reset(this); return wait_and_call(STATE(setup)); } @@ -252,34 +254,36 @@ private: /// @return setup() Action expired() { + bnPtr_ = bn_.reset(this); if (running_ && clock_->is_running() && callback_) { running_ = false; - callback_(); + callback_(bnPtr_->new_child()); } - waiting_ = true; return wait_and_call(STATE(setup)); - } + } /// Wakeup the state machine. Must be called from this service's executor. void wakeup() { timer_.ensure_triggered(); - if (waiting_) + if (bnPtr_) { - waiting_ = false; - notify(); + bnPtr_ = nullptr; + bn_.notify(); } } Wakeup wakeup_; ///< wakeup helper for scheduling alarms - std::function callback_; ///< callback for when alarm expires + /// callback for when alarm expires + std::function callback_; StateFlowTimer timer_; ///< timer helper + BarrierNotifiable bn_; ///< notifiable for callback callee + BarrierNotifiable *bnPtr_; ///< not null we have an outstanding notification time_t expires_; ///< time at which the alarm expires uint8_t running_ : 1; ///< true if running (alarm armed), else false uint8_t set_ : 1; ///< true if a start request is pending - uint8_t waiting_ : 1; ///< true if waiting for stateflow to be notified #if defined(GTEST) uint8_t shutdown_ : 1; ///< true if test has requested shutdown #endif @@ -302,10 +306,10 @@ public: /// @param clock clock that our alarm is based off of /// @param callback callback for when alarm expires BroadcastTimeAlarmDate(Node *node, BroadcastTime *clock, - std::function callback) + std::function callback) : BroadcastTimeAlarm( node, clock, std::bind(&BroadcastTimeAlarmDate::expired_callback, - this)) + this, std::placeholders::_1)) , callbackUser_(callback) { } @@ -340,19 +344,17 @@ private: else if (clock_->get_rate_quarters() < 0) { set(seconds - ((tm->tm_sec + 1) + - (60 * (tm->tm_min + 1)) + + (60 * (tm->tm_min)) + (60 * 60 * tm->tm_hour))); } } /// callback for when the alarm expires - void expired_callback() + /// @param done used to notify we are finished + void expired_callback(BarrierNotifiable *done) { reset_expired_time(); - if (callbackUser_) - { - callbackUser_(); - } + callbackUser_ ? callbackUser_(done) : done->notify(); } /// Called when the clock time has changed. @@ -362,7 +364,8 @@ private: BroadcastTimeAlarm::update_notify(); } - std::function callbackUser_; ///< callback for when alarm expires + /// callback for when alarm expires + std::function callbackUser_; DISALLOW_COPY_AND_ASSIGN(BroadcastTimeAlarmDate); }; @@ -377,10 +380,11 @@ public: /// @param clock clock that our alarm is based off of /// @param callback callback for when alarm expires BroadcastTimeAlarmMinute(Node *node, BroadcastTime *clock, - std::function callback) + std::function callback) : BroadcastTimeAlarm( node, clock, - std::bind(&BroadcastTimeAlarmMinute::expired_callback, this)) + std::bind(&BroadcastTimeAlarmMinute::expired_callback, this, + std::placeholders::_1)) , callbackUser_(callback) { } @@ -401,11 +405,22 @@ private: } /// Reset the expired time based on what time it is now. - void reset_expired_time() + /// @param force_on_match true to force an expiration if on a minute + /// rollover boundary + void reset_expired_time(bool force_on_match = false) { const struct tm *tm = clock_->gmtime_recalculate(); time_t seconds = clock_->time(); + if (force_on_match) + { + if ((clock_->get_rate_quarters() > 0 && tm->tm_sec == 0) || + (clock_->get_rate_quarters() < 0 && tm->tm_sec == 59)) + { + set(seconds); + return; + } + } if (clock_->get_rate_quarters() > 0) { set(seconds + (60 - tm->tm_sec)); @@ -417,24 +432,22 @@ private: } /// callback for when the alarm expires - void expired_callback() + /// @param done used to notify we are finished + void expired_callback(BarrierNotifiable *done) { reset_expired_time(); - if (callbackUser_) - { - callbackUser_(); - } + callbackUser_ ? callbackUser_(done) : done->notify(); } /// Called when the clock time has changed. void update_notify() override { - reset_expired_time(); + reset_expired_time(true); BroadcastTimeAlarm::update_notify(); } /// callback for when alarm expires - std::function callbackUser_; + std::function callbackUser_; DISALLOW_COPY_AND_ASSIGN(BroadcastTimeAlarmMinute); }; diff --git a/src/openlcb/BroadcastTimeClient.cxxtest b/src/openlcb/BroadcastTimeClient.cxxtest index 83eac9b7b..33bef9eaa 100644 --- a/src/openlcb/BroadcastTimeClient.cxxtest +++ b/src/openlcb/BroadcastTimeClient.cxxtest @@ -4,7 +4,7 @@ #include -#if 1 +#if 0 #define PRINT_ALL_PACKETS() print_all_packets() #else #define PRINT_ALL_PACKETS() @@ -250,6 +250,8 @@ TEST_F(BroadcastTimeClientTest, Create) EXPECT_EQ(client2_->time(), 0); EXPECT_EQ(client2_->day_of_week(), BroadcastTimeDefs::THURSDAY); EXPECT_EQ(client2_->day_of_year(), 0); + EXPECT_FALSE(client1_->is_server_detected()); + EXPECT_FALSE(client2_->is_server_detected()); }; TEST_F(BroadcastTimeClientTest, Start) @@ -265,6 +267,7 @@ TEST_F(BroadcastTimeClientTest, Start) sync(client1_, 500); wait(); + EXPECT_TRUE(client1_->is_server_detected()); // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(client1_->time(), 60, 62)); diff --git a/src/openlcb/BroadcastTimeClient.hxx b/src/openlcb/BroadcastTimeClient.hxx index 88cc9226f..608cdb209 100644 --- a/src/openlcb/BroadcastTimeClient.hxx +++ b/src/openlcb/BroadcastTimeClient.hxx @@ -62,6 +62,7 @@ public: , rolloverPending_(false) , rolloverPendingDate_(false) , rolloverPendingYear_(false) + , serverDetected_(false) { EventRegistry::instance()->register_handler( EventRegistryEntry(this, eventBase_), 16); @@ -73,6 +74,13 @@ public: EventRegistry::instance()->unregister_handler(this); } + /// Has a time server been detected? + /// @return true if a time server has been detected, else false + bool is_server_detected() override + { + return serverDetected_; + } + /// Handle requested identification message. /// @param entry registry entry for the event range /// @param event information about the incoming message @@ -176,6 +184,13 @@ public: if (event->state == EventState::VALID) { + // Look for a Report Date Event ID. + if ((event->event & 0x000000000000F000ULL) == 0x2000ULL) + { + // We can only get here if there is a time server detected. + serverDetected_ = true; + } + // We only care about valid event state. // Producer identified only happens when a clock synchronization // is taking place. This voids previous date rollover events. @@ -422,6 +437,7 @@ private: uint16_t rolloverPending_ : 1; ///< a day rollover is about to occur uint16_t rolloverPendingDate_ : 1; ///< a day rollover is about to occur uint16_t rolloverPendingYear_ : 1; ///< a day rollover is about to occur + uint16_t serverDetected_ : 1; ///< has a time server been detected DISALLOW_COPY_AND_ASSIGN(BroadcastTimeClient); diff --git a/src/openlcb/BroadcastTimeServer.cxx b/src/openlcb/BroadcastTimeServer.cxx index 83a40c4ea..ba339901f 100644 --- a/src/openlcb/BroadcastTimeServer.cxx +++ b/src/openlcb/BroadcastTimeServer.cxx @@ -561,6 +561,7 @@ class BroadcastTimeServerSet { tm.tm_hour = BroadcastTimeDefs::event_to_hour(suffix); tm.tm_min = BroadcastTimeDefs::event_to_min(suffix); + tm.tm_sec = server_->rate_ < 0 ? 59 : 0; break; } case BroadcastTimeDefs::SET_DATE: @@ -576,7 +577,7 @@ class BroadcastTimeServerSet } case BroadcastTimeDefs::SET_RATE: { - server_->rate_ = BroadcastTimeDefs::event_to_rate(suffix); + server_->rate_ = BroadcastTimeDefs::event_to_rate(suffix); break; } default: @@ -652,7 +653,8 @@ class BroadcastTimeServerAlarm : public BroadcastTimeAlarm BroadcastTimeServerAlarm(BroadcastTimeServer *server) : BroadcastTimeAlarm( server->node(), server, - std::bind(&BroadcastTimeServerAlarm::expired_callback, this)) + std::bind(&BroadcastTimeServerAlarm::expired_callback, this, + std::placeholders::_1)) , server_(server) { memset(activeMinutes_, 0, sizeof(activeMinutes_)); @@ -690,10 +692,12 @@ class BroadcastTimeServerAlarm : public BroadcastTimeAlarm } /// callback for when the alarm expires. - void expired_callback() + /// @param done used to notify we are finished + void expired_callback(BarrierNotifiable *done) { server_->time_->request_time(); update_notify(); + done->notify(); } /// Called when the clock time has changed. @@ -726,7 +730,7 @@ class BroadcastTimeServerAlarm : public BroadcastTimeAlarm } else { - seconds -= tm->tm_sec ? tm->tm_sec : 60; + seconds -= tm->tm_sec + 1; } do diff --git a/src/openlcb/BroadcastTimeServer.cxxtest b/src/openlcb/BroadcastTimeServer.cxxtest index 6e66463e0..192df80d7 100644 --- a/src/openlcb/BroadcastTimeServer.cxxtest +++ b/src/openlcb/BroadcastTimeServer.cxxtest @@ -1,8 +1,9 @@ #include "utils/async_if_test_helper.hxx" #include "openlcb/BroadcastTimeServer.hxx" +#include "os/FakeClock.hxx" -#if 1 +#if 0 #define PRINT_ALL_PACKETS() print_all_packets() #else #define PRINT_ALL_PACKETS() @@ -68,6 +69,37 @@ protected: delete server_; } + /// Helper function for sleeping. + /// @param clk fake clock or nullptr if no fake clock exists + /// @param len_msec how long to sleep + /// @param step_msec what granularity to use for sleeping wiht fake clock. + void sleep_helper(FakeClock *clk, unsigned len_msec, + unsigned step_msec = 50) + { + if (clk) + { + for (unsigned i = 0; i < len_msec; i += step_msec) + { + clk->advance(MSEC_TO_NSEC(step_msec)); + wait(); + } + } + else + { + usleep(MSEC_TO_USEC(len_msec)); + } + } + + /// Performs the sleep sequence needed for the server to output the sync + /// events. + /// @param clk if not null, uses fake clock to advance. + /// @param step_msec how big steps should we advance the fake clock. + void sync_sleep(FakeClock *clk = nullptr, unsigned step_msec = 50) + { + // Sleep 3.2 seconds. + sleep_helper(clk, 3200, step_msec); + } + MOCK_METHOD0(update_callback, void()); BroadcastTimeServer *server_; @@ -79,12 +111,238 @@ TEST_F(BroadcastTimeServerTest, Create) EXPECT_EQ(server_->time(), 0); EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); EXPECT_EQ(server_->day_of_year(), 0); + EXPECT_TRUE(server_->is_server_detected()); +}; + +TEST_F(BroadcastTimeServerTest, Time) +{ + expect_any_packet(); + EXPECT_CALL(*this, update_callback()).Times(AtLeast(0)); + + // positive rate + server_->set_rate_quarters(2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + wait_for_event_thread(); + + EXPECT_EQ(server_->time(), 0); + server_->start(); + usleep(123456); + EXPECT_EQ(server_->time(), 62); + + // negative rate + server_->stop(); + server_->set_rate_quarters(-2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + wait_for_event_thread(); + + EXPECT_EQ(server_->time(), 59); + server_->start(); + usleep(123456); + EXPECT_EQ(server_->time(), -3); +} + +TEST_F(BroadcastTimeServerTest, FastSecToRealNsecPeriod) +{ + long long real_nsec; + bool result; + + result = server_->fast_sec_to_real_nsec_period(2000, 60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period(-2000, 60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, -120000000LL); + + result = server_->fast_sec_to_real_nsec_period(2000, -60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, -120000000LL); + + result = server_->fast_sec_to_real_nsec_period(-2000, -60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + // boundary check + result = server_->fast_sec_to_real_nsec_period(0, -60, &real_nsec); + EXPECT_FALSE(result); + result = server_->fast_sec_to_real_nsec_period(2048, -60, &real_nsec); + EXPECT_FALSE(result); + result = server_->fast_sec_to_real_nsec_period(-2049, -60, &real_nsec); + EXPECT_FALSE(result); +} + +TEST_F(BroadcastTimeServerTest, RealNsecToFastSecPeriod) +{ + time_t fast_sec; + bool result; + + result = server_->real_nsec_to_fast_sec_period(2000, 1000000000LL, + &fast_sec); + EXPECT_TRUE(result); + EXPECT_EQ(fast_sec, 500); + + result = server_->real_nsec_to_fast_sec_period(-2000, 1000000000LL, + &fast_sec); + EXPECT_TRUE(result); + EXPECT_EQ(fast_sec, -500); + + result = server_->real_nsec_to_fast_sec_period(-2000, -1000000000LL, + &fast_sec); + EXPECT_TRUE(result); + EXPECT_EQ(fast_sec, 500); + + result = server_->real_nsec_to_fast_sec_period(2000, -1000000000LL, + &fast_sec); + EXPECT_TRUE(result); + EXPECT_EQ(fast_sec, -500); + + result = server_->real_nsec_to_fast_sec_period(0, -1000000000LL, + &fast_sec); + EXPECT_FALSE(result); + result = server_->real_nsec_to_fast_sec_period(2048, -1000000000LL, + &fast_sec); + EXPECT_FALSE(result); + result = server_->real_nsec_to_fast_sec_period(-2049, -1000000000LL, + &fast_sec); + EXPECT_FALSE(result); +} + +TEST_F(BroadcastTimeServerTest, FastSecToRealNsecPeriodAbs) +{ + long long real_nsec; + bool result; + + expect_any_packet(); + EXPECT_CALL(*this, update_callback()).Times(AtLeast(0)); + + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + server_->set_rate_quarters(2000); + wait_for_event_thread(); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); +} + +TEST_F(BroadcastTimeServerTest, RealNsecUntilFastTimeAbs) +{ + long long real_nsec; + bool result; + + expect_any_packet(); + EXPECT_CALL(*this, update_callback()).Times(AtLeast(0)); + + // positive rate + server_->set_rate_quarters(2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + wait_for_event_thread(); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + // now start the clock + server_->start(); + usleep(123456); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + // negative rate + server_->stop(); + server_->set_rate_quarters(-2000); + server_->set_time(0, 0); + server_->set_date(1, 1); + server_->set_year(1970); + wait_for_event_thread(); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(1, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 2000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-1, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 2000000LL); + + // now start the clock + server_->start(); + usleep(123456); + + result = server_->fast_sec_to_real_nsec_period_abs(60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-60, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 120000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(1, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 2000000LL); + + result = server_->fast_sec_to_real_nsec_period_abs(-1, &real_nsec); + EXPECT_TRUE(result); + EXPECT_EQ(real_nsec, 2000000LL); + +} + +TEST_F(BroadcastTimeServerTest, SleepTest) +{ + FakeClock clk; + long long t1 = os_get_time_monotonic(); + sync_sleep(&clk); + long long t2 = os_get_time_monotonic(); + EXPECT_NEAR(t2-t1, MSEC_TO_NSEC(3200), USEC_TO_NSEC(1)); }; TEST_F(BroadcastTimeServerTest, Query) { + FakeClock clk; ::testing::Sequence s1; + send_packet(":X195B4001N010100000100F000;"); // query + wait(); + + clear_expect(true); + // The server only responds a bit later in case multiple queries arrive. + // sync response expect_packet(":X1954422AN010100000100F001;").InSequence(s1); expect_packet(":X1954422AN0101000001004000;").InSequence(s1); @@ -92,9 +350,8 @@ TEST_F(BroadcastTimeServerTest, Query) expect_packet(":X1954422AN0101000001002101;").InSequence(s1); expect_packet(":X1954422AN0101000001000000;").InSequence(s1); - send_packet(":X195B4001N010100000100F000;"); // query - usleep(400000); // wait for response agrigation - wait_for_event_thread(); + clk.advance(MSEC_TO_NSEC(400)); + wait(); // time is not setup, clock is not running, expect 0 as default EXPECT_EQ(server_->time(), 0); @@ -104,6 +361,7 @@ TEST_F(BroadcastTimeServerTest, Query) TEST_F(BroadcastTimeServerTest, StartSetTime) { + FakeClock clk; ::testing::Sequence s1, s2; // set events @@ -130,7 +388,7 @@ TEST_F(BroadcastTimeServerTest, StartSetTime) // start expect_packet(":X195B422AN010100000100F002;").InSequence(s2); - // sync seqeunce + // sync sequence expect_packet(":X1954422AN010100000100F002;").InSequence(s2); expect_packet(":X1954422AN01010000010047D0;").InSequence(s2); expect_packet(":X1954422AN01010000010037B2;").InSequence(s2); @@ -141,12 +399,18 @@ TEST_F(BroadcastTimeServerTest, StartSetTime) // callbacks on clock update EXPECT_CALL(*this, update_callback()).Times(1); + LOG(INFO, "start server"); server_->start(); - - // allow time for the sync timeout - usleep(3200000); - wait_for_event_thread(); - + wait(); + LOG(INFO, "exec wait done"); + + // Time for the sync timeout. There are two sleeps there in order to send + // out two minute events, so we need to sleep twice too. + clk.advance(MSEC_TO_NSEC(3000)); + wait(); + clk.advance(MSEC_TO_NSEC(200)); + wait(); + // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(server_->time(), 1599, 1602)); EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); @@ -155,6 +419,7 @@ TEST_F(BroadcastTimeServerTest, StartSetTime) TEST_F(BroadcastTimeServerTest, DateRolloverForward) { + FakeClock clk; ::testing::Sequence s1, s2; // set events @@ -179,6 +444,8 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForward) clear_expect(true); + EXPECT_TRUE(IsBetweenInclusive(server_->time(), 86340, 86340)); + // start expect_packet(":X195B422AN010100000100F002;").InSequence(s2); @@ -186,7 +453,7 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForward) expect_packet(":X195B422AN010100000100F003;").InSequence(s2); expect_packet(":X195B422AN0101000001000000;").InSequence(s2); - // sync seqeunce + // sync sequence expect_packet(":X1954422AN010100000100F002;").InSequence(s2); expect_packet(":X1954422AN01010000010047D0;").InSequence(s2); expect_packet(":X1954422AN01010000010037B2;").InSequence(s2); @@ -205,8 +472,12 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForward) server_->start(); // allow time for the sync timeout - usleep(3200000); - wait_for_event_thread(); + + /// @todo this does not pass when using more than one msec of granularity + /// for the fake clock advances. That suggests there is an off by one bug + /// somewhere. The reported time is one tick lower than how much the + /// faketime advanced. + sync_sleep(&clk, 1); // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(server_->time(), 87940, 87941)); @@ -216,6 +487,7 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForward) TEST_F(BroadcastTimeServerTest, DateRolloverForwardOnTopOfSync) { + FakeClock clk; ::testing::Sequence s1, s2, s3; // set events @@ -265,17 +537,22 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForwardOnTopOfSync) server_->start(); // allow time for the sync timeout - usleep(3200000); + sync_sleep(&clk, 1); wait_for_event_thread(); // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(server_->time(), 87820, 87821)); EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::FRIDAY); EXPECT_EQ(server_->day_of_year(), 1); + + // Clears out timer from queue. + clk.advance(MSEC_TO_NSEC(200)); + wait(); }; #if 1 TEST_F(BroadcastTimeServerTest, DateRolloverForwardAllEventBasedSetup) { + FakeClock clk; ::testing::Sequence s1, s2; // report events @@ -319,7 +596,8 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForwardAllEventBasedSetup) send_packet(":X195B4001N010100000100F002;"); // start // allow time for the sync timeout - usleep(3200000); + sync_sleep(&clk, 1); + //usleep(3200000); wait_for_event_thread(); // check the time, we give it a finite range just in case of some OS jitter @@ -330,25 +608,26 @@ TEST_F(BroadcastTimeServerTest, DateRolloverForwardAllEventBasedSetup) #endif TEST_F(BroadcastTimeServerTest, DateRolloverBackward) { + FakeClock clk; ::testing::Sequence s1, s2; // set events + expect_packet(":X195B422AN010100000100C830;").InSequence(s1); expect_packet(":X195B422AN0101000001008000;").InSequence(s1); expect_packet(":X195B422AN010100000100A102;").InSequence(s1); expect_packet(":X195B422AN010100000100B7B2;").InSequence(s1); - expect_packet(":X195B422AN010100000100C830;").InSequence(s1); // report events + expect_packet(":X195B422AN0101000001004830;").InSequence(s1); expect_packet(":X195B422AN0101000001000000;").InSequence(s1); expect_packet(":X195B422AN0101000001002102;").InSequence(s1); expect_packet(":X195B422AN01010000010037B2;").InSequence(s1); - expect_packet(":X195B422AN0101000001004830;").InSequence(s1); EXPECT_CALL(*this, update_callback()).Times(4); + server_->set_rate_quarters(-2000); server_->set_time(0, 0); server_->set_date(1, 2); server_->set_year(1970); - server_->set_rate_quarters(-2000); wait_for_event_thread(); clear_expect(true); @@ -379,17 +658,18 @@ TEST_F(BroadcastTimeServerTest, DateRolloverBackward) server_->start(); // allow time for the sync timeout - usleep(3200000); + sync_sleep(&clk, 1); wait_for_event_thread(); // check the time, we give it a finite range just in case of some OS jitter - EXPECT_TRUE(IsBetweenInclusive(server_->time(), 84800, 84802)); + EXPECT_TRUE(IsBetweenInclusive(server_->time(), 84857, 84860)); EXPECT_EQ(server_->day_of_week(), BroadcastTimeDefs::THURSDAY); EXPECT_EQ(server_->day_of_year(), 0); }; TEST_F(BroadcastTimeServerTest, Subscribe) { + FakeClock clk; ::testing::Sequence s1, s2; // set events @@ -411,6 +691,7 @@ TEST_F(BroadcastTimeServerTest, Subscribe) server_->set_rate_quarters(2000); wait_for_event_thread(); + clear_expect(true); clear_expect(true); // start @@ -430,8 +711,7 @@ TEST_F(BroadcastTimeServerTest, Subscribe) server_->start(); // allow time for the sync timeout - usleep(3200000); - wait_for_event_thread(); + sync_sleep(&clk, 1); // subscribe to some times clear_expect(true); @@ -444,7 +724,7 @@ TEST_F(BroadcastTimeServerTest, Subscribe) send_packet(":X194C7001N010100000100003A;"); // subscribe to 00:58 send_packet(":X194C7001N010100000100003B;"); // subscribe to 00:59 send_packet(":X194C7001N0101000001000100;"); // subscribe to 01:00 - usleep(4100000); + sleep_helper(&clk, 4100, 1); // check the time, we give it a finite range just in case of some OS jitter EXPECT_TRUE(IsBetweenInclusive(server_->time(), 3650, 3651)); diff --git a/src/openlcb/BroadcastTimeServer.hxx b/src/openlcb/BroadcastTimeServer.hxx index 6ece4ca29..0a208116d 100644 --- a/src/openlcb/BroadcastTimeServer.hxx +++ b/src/openlcb/BroadcastTimeServer.hxx @@ -46,7 +46,15 @@ class BroadcastTimeServerSync; class BroadcastTimeServerSet; class BroadcastTimeServerAlarm; -/// Implementation of a Broadcast Time Protocol client. +/// Implementation of a Broadcast Time Protocol server. Note: A Broadcast Time +/// server must produce all the individual time events for which there is an +/// identified consumer. In order to guarantee that the server can identify all +/// of the consumers, it is important to have an Event Identify Global message +/// sent after the creation of the server object. In order to prevent +/// unnecessary duplication of Event Identify Global messages, it is left to +/// the application to send the Event Identify Global message. An application +/// can use the @ref openlcb::EventIdentifyGlobal object for production of an +/// Event Identify Global message. class BroadcastTimeServer : public BroadcastTime { public: @@ -59,6 +67,13 @@ public: /// Destructor. ~BroadcastTimeServer(); + /// Has a time server been detected? + /// @return true if a time server has been detected, else false + bool is_server_detected() override + { + return true; + } + #if defined(GTEST) void shutdown(); diff --git a/src/openlcb/BulkAliasAllocator.cxx b/src/openlcb/BulkAliasAllocator.cxx new file mode 100644 index 000000000..dea531fa1 --- /dev/null +++ b/src/openlcb/BulkAliasAllocator.cxx @@ -0,0 +1,286 @@ +/** \copyright + * Copyright (c) 2020 Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BulkAliasAllocator.cxx + * + * State flow for allocating many aliases at the same time. + * + * @author Balazs Racz + * @date 14 Nov 2020 + */ + +#include "openlcb/BulkAliasAllocator.hxx" +#include "openlcb/CanDefs.hxx" +#include "utils/MakeUnique.hxx" + +namespace openlcb +{ + +/// Implementation of the BulkAliasAllocatorInterface to allocate many aliases +/// at the same time. +class BulkAliasAllocator : public CallableFlow +{ +public: + /// Constructor + /// @param iface the openlcb CAN interface + BulkAliasAllocator(IfCan *iface) + : CallableFlow(iface) + { + } + + /// Start of flow when a request arrives to allocate many aliases. Resets + /// the internal state and goes on to start the allocation process. + Action entry() override + { + startTime_ = os_get_time_monotonic(); + pendingAliasesByTime_.clear(); + pendingAliasesByKey_.clear(); + nextToStampTime_ = 0; + nextToClaim_ = 0; + if_can()->frame_dispatcher()->register_handler(&conflictHandler_, 0, 0); + return call_immediately(STATE(send_cid_frames)); + } + + /// Picks a bunch of random aliases, sends CID frames for them to the bus. + Action send_cid_frames() + { + unsigned needed = std::min(request()->numAliases_, + (unsigned)(config_bulk_alias_num_can_frames() + 3) / 4); + if (!needed) + { + return call_immediately(STATE(wait_for_results)); + } + bn_.reset(this); + for (unsigned i = 0; i < needed; ++i) + { + NodeAlias next_alias = if_can()->alias_allocator()->get_new_seed(); + auto if_id = if_can()->alias_allocator()->if_node_id(); + send_can_frame(next_alias, (if_id >> 36) & 0xfff, 7); + send_can_frame(next_alias, (if_id >> 24) & 0xfff, 6); + send_can_frame(next_alias, (if_id >> 12) & 0xfff, 5); + send_can_frame(next_alias, (if_id >> 0) & 0xfff, 4); + --request()->numAliases_; + pendingAliasesByTime_.push_back({next_alias}); + pendingAliasesByKey_.insert({next_alias}); + } + bn_.notify(); + return wait_and_call(STATE(stamp_time)); + } + + /// Adds the timestamps when the CID requests were sent out. + Action stamp_time() + { + auto ctime = relative_time(); + for (unsigned i = nextToStampTime_; i < pendingAliasesByTime_.size(); + ++i) + { + pendingAliasesByTime_[i].cidTime_ = ctime; + } + nextToStampTime_ = pendingAliasesByTime_.size(); + // Go back to sending more CID frames as needed. + return call_immediately(STATE(send_cid_frames)); + } + + /// Sends out the RID frames for any alias that the 200 msec has already + /// elapsed, then waits a bit and tries again. + Action wait_for_results() + { + if (nextToClaim_ == pendingAliasesByTime_.size()) + { + return complete(); + } + if (request()->numAliases_) + { + // Some conflicts were identified, go and allocate more. + return call_immediately(STATE(send_cid_frames)); + } + auto ctime = relative_time(); + unsigned num_sent = 0; + bn_.reset(this); + while ((nextToClaim_ < pendingAliasesByTime_.size()) && + (num_sent < (unsigned)(config_bulk_alias_num_can_frames())) && + (pendingAliasesByTime_[nextToClaim_].cidTime_ + ALLOCATE_DELAY < + ctime)) + { + NodeAlias a = + (NodeAlias)(pendingAliasesByTime_[nextToClaim_].alias_); + ++nextToClaim_; + auto it = pendingAliasesByKey_.find(a); + if (it->hasConflict_) + { + // we skip this alias because there was a conflict. + continue; + } + if_can()->alias_allocator()->add_allocated_alias(a); + ++num_sent; + send_can_frame(a, CanDefs::RID_FRAME, 0); + } + if (bn_.abort_if_almost_done()) + { + // no frame sent + return sleep_and_call( + &timer_, MSEC_TO_NSEC(10), STATE(wait_for_results)); + } + else + { + bn_.notify(); + // Wait for outgoing frames to be gone and call this again. + return wait(); + } + } + + /// Called when all RID frames are sent out. + Action complete() + { + if_can()->frame_dispatcher()->unregister_handler_all(&conflictHandler_); + pendingAliasesByTime_.clear(); + pendingAliasesByKey_.clear(); + return return_ok(); + } + +private: + /// Callback from the stack for all incoming frames while we are + /// operating. We sniff the alias uot of it and record any conflicts we + /// see. + /// @param message an incoming CAN frame. + void handle_conflict(Buffer *message) + { + auto rb = get_buffer_deleter(message); + auto alias = CanDefs::get_src(GET_CAN_FRAME_ID_EFF(*message->data())); + auto it = pendingAliasesByKey_.find(alias); + if (it != pendingAliasesByKey_.end() && !it->hasConflict_) + { + it->hasConflict_ = 1; + ++request()->numAliases_; + } + } + + /// Listens to incoming CAN frames and handles alias conflicts. + IncomingFrameHandler::GenericHandler conflictHandler_ { + this, &BulkAliasAllocator::handle_conflict}; + + /// How many count to wait before sending out the RID frames. One count is + /// 10 msec (see { \link relative_time } ). + static constexpr unsigned ALLOCATE_DELAY = 20; + + /// Sends a CAN control frame to the bus. Take a share of the barrier bn_ + /// to send with the frame. + /// @param src source alias to use on the frame. + /// @param control_field 16-bit control value (e.g. RID_FRAME, or 0 top + /// nibble and a chunk of the unique node ID in the middle). + /// @param sequence used for CID messages. + void send_can_frame(NodeAlias src, uint16_t control_field, int sequence) + { + auto *b = if_can()->frame_write_flow()->alloc(); + b->set_done(bn_.new_child()); + CanDefs::control_init(*b->data(), src, control_field, sequence); + if_can()->frame_write_flow()->send(b, 0); + } + + /// @return the openlcb CAN interface + IfCan *if_can() + { + return static_cast(service()); + } + + /// @return the time elapsed from start time in 10 msec units. + unsigned relative_time() + { + return (os_get_time_monotonic() - startTime_) / MSEC_TO_NSEC(10); + } + + /// We store this type in the time-ordered aliases structure. + struct PendingAliasInfo + { + /// Constructor + /// @param alias the openlcb alias that is being represented here. + PendingAliasInfo(NodeAlias alias) + : alias_(alias) + , cidTime_(0) + { + } + + /// The value of the alias + unsigned alias_ : 12; + /// The time when the CID requests were sent. Counter in + /// relative_time(), i.e. 10 msec per increment. + unsigned cidTime_ : 8; + }; + static_assert(sizeof(PendingAliasInfo) == 4, "memory bloat"); + + /// We store this type in the sorted map lookup structure. + struct AliasLookupInfo + { + /// Constructor + /// @param alias the openlcb alias that is being represented here. + AliasLookupInfo(NodeAlias alias) + : alias_(alias) + , hasConflict_(0) + { + } + + /// The value of the alias + uint16_t alias_ : 12; + /// 1 if we have seen a conflict + uint16_t hasConflict_ : 1; + }; + static_assert(sizeof(AliasLookupInfo) == 2, "memory bloat"); + /// Comparator function on AliasLookupInfo objects. + struct LookupCompare + { + bool operator()(AliasLookupInfo a, AliasLookupInfo b) + { + return a.alias_ < b.alias_; + } + }; + + /// Helper object for sleeping. + StateFlowTimer timer_ {this}; + /// Helper object to determine when the CAN frames have flushed from the + /// system. + BarrierNotifiable bn_; + /// We measure time elapsed relative to this point. + long long startTime_; + /// Stores the aliases we are trying to allocate in time order of picking + /// them. + std::vector pendingAliasesByTime_; + /// Stores the aliases we are trying to allocate in the alias order. + SortedListSet pendingAliasesByKey_; + /// Index into the pendingAliasesByTime_ vector where we need to stmap + /// time. + uint16_t nextToStampTime_; + /// Index into the pendingAliasesByTime_ vector where we need to send out + /// the reserve frame. + uint16_t nextToClaim_; +}; + +std::unique_ptr create_bulk_alias_allocator( + IfCan *can_if) +{ + return std::make_unique(can_if); +} + +} // namespace openlcb diff --git a/src/openlcb/BulkAliasAllocator.hxx b/src/openlcb/BulkAliasAllocator.hxx new file mode 100644 index 000000000..2fee889a0 --- /dev/null +++ b/src/openlcb/BulkAliasAllocator.hxx @@ -0,0 +1,64 @@ +/** \copyright + * Copyright (c) 2020 Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BulkAliasAllocator.hxx + * + * State flow for allocating many aliases at the same time. + * + * @author Balazs Racz + * @date 14 Nov 2020 + */ + +#include "executor/CallableFlow.hxx" +#include "openlcb/AliasAllocator.hxx" +#include "openlcb/AliasCache.hxx" +#include "openlcb/CanDefs.hxx" + +namespace openlcb +{ + +/// Message type to request allocating many aliases for an interface. +struct BulkAliasRequest : CallableFlowRequestBase +{ + /// @param count how many aliases to allocate. + void reset(unsigned count) + { + reset_base(); + numAliases_ = count; + } + + /// How many aliases to allocate. + unsigned numAliases_; +}; + +using BulkAliasAllocatorInterface = FlowInterface>; + +/// Creates a bulk alias allocator. +/// @param can_if the interface to bind it to. +std::unique_ptr create_bulk_alias_allocator( + IfCan *can_if); + +} // namespace openlcb diff --git a/src/openlcb/CallbackEventHandler.hxx b/src/openlcb/CallbackEventHandler.hxx index 051710933..e9e748a77 100644 --- a/src/openlcb/CallbackEventHandler.hxx +++ b/src/openlcb/CallbackEventHandler.hxx @@ -101,7 +101,7 @@ public: ~CallbackEventHandler() { - EventRegistry::instance()->unregister_handler(this); + remove_all_entries(); } /// Registers this event handler for a given event ID in the global event @@ -121,6 +121,21 @@ public: EventRegistryEntry(this, event, entry_bits), 0); } + /// Removes the registration of every single entry added so far. + void remove_all_entries() + { + EventRegistry::instance()->unregister_handler(this); + } + + /// Removes the registration of entries added before with a given user_arg + /// value. + /// @param user_arg argument to match on. + void remove_entry(uint32_t entry_bits) + { + EventRegistry::instance()->unregister_handler( + this, entry_bits, 0xFFFFFFFFu); + } + /// @return the node pointer for which this handler is exported. Node *node() { diff --git a/src/openlcb/CanDefs.hxx b/src/openlcb/CanDefs.hxx index 08749f49a..9f4fdd189 100644 --- a/src/openlcb/CanDefs.hxx +++ b/src/openlcb/CanDefs.hxx @@ -121,12 +121,12 @@ struct CanDefs { HIGH_PRIORITY = 0, /**< high priority CAN message */ NORMAL_PRIORITY = 1 /**< normal priority CAN message */ }; - + enum ControlField { RID_FRAME = 0x0700, /**< Reserve ID Frame */ AMD_FRAME = 0x0701, /**< Alias Map Definition frame */ - AME_FRAME = 0x0702, /**< Alias Mapping Inquery */ + AME_FRAME = 0x0702, /**< Alias Mapping Enquiry */ AMR_FRAME = 0x0703 /**< Alias Map Reset */ }; @@ -136,6 +136,18 @@ struct CanDefs { NOT_LAST_FRAME = 0x10, }; + /// Constants used in the LocalAliasCache for reserved but not used + /// aliases. + enum ReservedAliasNodeId + { + /// To mark a reserved alias in the local alias cache, we use this as a + /// node ID and add the alias to the lowest 12 bits. Since this value + /// starts with a zero MSB byte, it is not a valid node ID. + RESERVED_ALIAS_NODE_BITS = 0xF000, + /// Mask for the reserved aliases. + RESERVED_ALIAS_NODE_MASK = 0xFFFFFFFFF000 + }; + /** Get the source field value of the CAN ID. * @param can_id identifier to act upon * @return source field @@ -363,6 +375,23 @@ struct CanDefs { frame.can_dlc = 0; } + /** Computes a reserved alias node ID for the local alias cache map. + * @param alias the alias to reserve + * @return Node ID to use in the alias map as a key. + */ + static NodeID get_reserved_alias_node_id(NodeAlias alias) + { + return RESERVED_ALIAS_NODE_BITS | alias; + } + + /** Tests if a node ID is a reserved alias Node ID. + * @param id node id to test + * @return true if this is a reserved alias node ID. */ + static bool is_reserved_alias_node_id(NodeID id) + { + return (id & RESERVED_ALIAS_NODE_MASK) == RESERVED_ALIAS_NODE_BITS; + } + private: /** This class should not be instantiated. */ CanDefs(); diff --git a/src/openlcb/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/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.cxxtest b/src/openlcb/ConfigRenderer.cxxtest index 65a903fee..2d3125bca 100644 --- a/src/openlcb/ConfigRenderer.cxxtest +++ b/src/openlcb/ConfigRenderer.cxxtest @@ -274,7 +274,7 @@ TEST(CdiRender, Render) string s; TestCdi1 cfg(0); cfg.config_renderer().render_cdi(&s); - const char kExpectedTestNodeCdi[] = "" R"data( + const char kExpectedTestNodeCdi[] = "" R"data( testseg @@ -316,7 +316,7 @@ TEST(CdiRender, RenderIdent) string s; TestCdi2 cfg(0); cfg.config_renderer().render_cdi(&s); - const char kExpectedTestNodeCdi[] = "" R"data( + const char kExpectedTestNodeCdi[] = "" R"data( Manuf @@ -328,11 +328,11 @@ TEST(CdiRender, RenderIdent) User Name -This name will appear in network browsers for the current node. +This name will appear in network browsers for this device. User Description -This description will appear in network browsers for the current node. +This description will appear in network browsers for this device. diff --git a/src/openlcb/ConfigRenderer.hxx b/src/openlcb/ConfigRenderer.hxx index cfa078d28..34f5ce0aa 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" @@ -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 @@ -387,7 +387,7 @@ public: *s += "<"; if (opts.is_cdi()) { - *s += "?xml version=\"1.0\"?>\n<"; + *s += "?xml version=\"1.0\" encoding=\"utf-8\"?>\n<"; tag = "cdi"; *s += tag; *s += " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " 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 c2485acb0..100991f65 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 { @@ -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; } @@ -448,12 +448,12 @@ CDI_GROUP( CDI_GROUP_ENTRY(name, StringConfigEntry<63>, // Name("User Name"), // Description( - "This name will appear in network browsers for the current node.")); + "This name will appear in network browsers for this device.")); /// User description entry CDI_GROUP_ENTRY(description, StringConfigEntry<64>, // Name("User Description"), // - Description("This description will appear in network browsers for the " - "current node.")); + Description("This description will appear in network browsers for " + "this device.")); /// Signals termination of the group. CDI_GROUP_END(); @@ -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_ diff --git a/src/openlcb/ConfigUpdateFlow.cxx b/src/openlcb/ConfigUpdateFlow.cxx index 13a4ae6a4..f46784f3c 100644 --- a/src/openlcb/ConfigUpdateFlow.cxx +++ b/src/openlcb/ConfigUpdateFlow.cxx @@ -42,16 +42,10 @@ namespace openlcb int ConfigUpdateFlow::open_file(const char *path) { - if (fd_ >= 0) return fd_; - if (!path) - { - fd_ = -1; - } - else - { - fd_ = ::open(path, O_RDWR); - HASSERT(fd_ >= 0); - } + HASSERT(fd_ < 0); + HASSERT(path); + fd_ = ::open(path, O_RDWR); + HASSERT(fd_ >= 0); return fd_; } diff --git a/src/openlcb/ConfigUpdateFlow.hxx b/src/openlcb/ConfigUpdateFlow.hxx index b4821e170..2d07ea943 100644 --- a/src/openlcb/ConfigUpdateFlow.hxx +++ b/src/openlcb/ConfigUpdateFlow.hxx @@ -71,14 +71,22 @@ public: { } - /// Must be called once before calling anything else. Returns the file - /// descriptor. + /// Must be called once (only) before calling anything else. Returns the + /// file descriptor. int open_file(const char *path); /// Asynchronously invokes all update listeners with the config FD. void init_flow(); /// Synchronously invokes all update listeners to factory reset. void factory_reset(); + /// @return the file descriptor of the configuration file, or -1 if the + /// configuration file has not yet been opened. + int get_fd() + { + return fd_; + } + +#ifdef GTEST void TEST_set_fd(int fd) { fd_ = fd; @@ -86,6 +94,7 @@ public: bool TEST_is_terminated() { return is_terminated(); } +#endif // GTEST void trigger_update() override { @@ -112,8 +121,8 @@ private: return call_immediately(STATE(do_initial_load)); } l = nextRefresh_.operator->(); + ++nextRefresh_; } - ++nextRefresh_; return call_listener(l, false); } 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/DccAccyConsumer.cxxtest b/src/openlcb/DccAccyConsumer.cxxtest index 1b88a7cc3..d961e3878 100644 --- a/src/openlcb/DccAccyConsumer.cxxtest +++ b/src/openlcb/DccAccyConsumer.cxxtest @@ -32,16 +32,16 @@ * @date 4 Feb 2017 */ +#include "dcc/TrackIf.hxx" #include "openlcb/DccAccyConsumer.hxx" #include "utils/async_traction_test_helper.hxx" -#include "dcc/PacketFlowInterface.hxx" namespace openlcb { using ::testing::ElementsAre; -class MockPacketQueue : public dcc::PacketFlowInterface +class MockPacketQueue : public dcc::TrackIf { public: MOCK_METHOD2(arrived, void(uint8_t, vector)); diff --git a/src/openlcb/DccAccyConsumer.hxx b/src/openlcb/DccAccyConsumer.hxx index 44b89f42a..1f6c47721 100644 --- a/src/openlcb/DccAccyConsumer.hxx +++ b/src/openlcb/DccAccyConsumer.hxx @@ -36,9 +36,9 @@ #ifndef _OPENLCB_DCCACCYCONSUMER_HXX_ #define _OPENLCB_DCCACCYCONSUMER_HXX_ -#include "openlcb/TractionDefs.hxx" +#include "dcc/TrackIf.hxx" #include "openlcb/EventHandlerTemplates.hxx" -#include "dcc/PacketFlowInterface.hxx" +#include "openlcb/TractionDefs.hxx" namespace openlcb { @@ -51,8 +51,9 @@ public: /// responding to Identify messages. /// @param track is the interface through which we will be writing DCC /// accessory packets. - DccAccyConsumer(Node *node, dcc::PacketFlowInterface* track) - : node_(node), track_(track) + DccAccyConsumer(Node *node, dcc::TrackIf *track) + : node_(node) + , track_(track) { EventRegistry::instance()->register_handler( EventRegistryEntry( @@ -110,7 +111,7 @@ public: lastSetState_[eventOfs_] &= ~m; } - dcc::PacketFlowInterface::message_type *pkt; + dcc::TrackIf::message_type *pkt; mainBufferPool->alloc(&pkt); pkt->data()->add_dcc_basic_accessory(dccAddress_, onOff_); pkt->data()->packet_header.rept_count = 3; @@ -216,7 +217,7 @@ private: /// OpenLCB node to export the consumer on. Node *node_; /// Track to send DCC packets to. - dcc::PacketFlowInterface* track_; + dcc::TrackIf *track_; }; } // namespace openlcb diff --git a/src/openlcb/DefaultNodeRegistry.hxx b/src/openlcb/DefaultNodeRegistry.hxx new file mode 100644 index 000000000..2b10b6b88 --- /dev/null +++ b/src/openlcb/DefaultNodeRegistry.hxx @@ -0,0 +1,78 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file DefaultNodeRegistry.hxx + * + * Default implementations for data structures keyed by virtual nodes. + * + * @author Balazs Racz + * @date 12 Sep 2020 + */ + +#ifndef _OPENLCB_DEFAULTNODEREGISTRY_HXX_ +#define _OPENLCB_DEFAULTNODEREGISTRY_HXX_ + +#include + +#include "openlcb/NodeRegistry.hxx" + +namespace openlcb +{ + +class Node; + +class DefaultNodeRegistry : public NodeRegistry +{ +public: + /// Adds a node to the list of registered nodes. + /// @param node a virtual node. + void register_node(openlcb::Node *node) override + { + nodes_.insert(node); + } + + /// Removes a node from the list of registered nodes. + /// @param node a virtual node. + void unregister_node(openlcb::Node *node) override + { + nodes_.erase(node); + } + + /// Checks if a node is registered. + /// @param node a virtual node. + /// @return true if this node has been registered. + bool is_node_registered(openlcb::Node *node) override + { + return nodes_.find(node) != nodes_.end(); + } + +private: + std::set nodes_; +}; + +} // namespace openlcb + +#endif // _OPENLCB_DEFAULTNODEREGISTRY_HXX_ diff --git a/src/openlcb/Defs.hxx b/src/openlcb/Defs.hxx index e45ff994d..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; @@ -160,7 +163,8 @@ struct Defs }; - enum ErrorCodes { + enum ErrorCodes + { ERROR_CODE_OK = 0, ERROR_PERMANENT = 0x1000, @@ -182,6 +186,15 @@ struct Defs ERROR_OPENMRN_ALREADY_EXISTS = ERROR_OPENMRN_NOT_FOUND | 2, + // Codes defined by the firmware upgrade standard + + /// The firmware data is incompatible with this hardware node. + ERROR_FIRMWARE_INCOMPATIBLE = ERROR_INVALID_ARGS | 8, + /// The firmware data is invalid or corrupted. + ERROR_FIRMWARE_CORRUPTED = ERROR_INVALID_ARGS | 9, + /// The firmware written has failed checksum (temporary error). + ERROR_FIRMWARE_CSUM = 0x2088, + // Internal error codes generated by the stack. ERROR_DST_NOT_FOUND = 0x40000, //< on CAN. Permanent error code. // There is a conflict with MinGW macros here. @@ -246,6 +259,21 @@ struct Defs /// station may react to this by restoring locomotive speed settings. static constexpr uint64_t CLEAR_EMERGENCY_STOP_EVENT = 0x010000000000FFFCULL; + /// "Power supply brownout detected below minimum required by standard" + /// This event can be generated when a node detects that the CAN bus power + /// has dropped below the minimum declared in the standard. + static constexpr uint64_t POWER_STANDARD_BROWNOUT_EVENT = 0x010000000000FFF0ULL; + + /// "Power supply brownout detected below minimum required by node" + /// This event can be generated when a node detects that it has + /// insufficient power for normal operations. + static constexpr uint64_t NODE_POWER_BROWNOUT_EVENT = 0x010000000000FFF1ULL; + + /// "Ident button combination pressed" + /// This event can be generated by a node when it is instructed to generate + /// an identification event. + static constexpr uint64_t NODE_IDENT_BUTTON_EVENT = 0x010000000000FE00ULL; + /** Status of the pysical layer link */ enum LinkStatus { diff --git a/src/openlcb/EventHandler.hxx b/src/openlcb/EventHandler.hxx index 103749424..df75e7855 100644 --- a/src/openlcb/EventHandler.hxx +++ b/src/openlcb/EventHandler.hxx @@ -272,7 +272,17 @@ public: virtual void register_handler(const EventRegistryEntry &entry, unsigned mask) = 0; /// Removes all registered instances of a given event handler pointer. - virtual void unregister_handler(EventHandler *handler) = 0; + /// @param handler the handler for which to unregister entries + /// @param user_arg values of the 32-bit user arg to remove + /// @param user_arg_mask 32-bit mask where to verify user_arg being equal + virtual void unregister_handler(EventHandler *handler, + uint32_t user_arg = 0, uint32_t user_arg_mask = 0) = 0; + + /// Prepares storage for adding many event handlers. + /// @param count how many empty slots to reserve. + virtual void reserve(size_t count) + { + } /// Creates a new event iterator. Caller takes ownership of object. virtual EventIterator *create_iterator() = 0; diff --git a/src/openlcb/EventHandlerContainer.cxx b/src/openlcb/EventHandlerContainer.cxx index e0310c22c..65126afc6 100644 --- a/src/openlcb/EventHandlerContainer.cxx +++ b/src/openlcb/EventHandlerContainer.cxx @@ -48,31 +48,33 @@ void TreeEventHandlers::register_handler(const EventRegistryEntry &entry, handlers_[mask].insert(EventRegistryEntry(entry)); } -void TreeEventHandlers::unregister_handler(EventHandler *handler) +void TreeEventHandlers::unregister_handler( + EventHandler *handler, uint32_t user_arg, uint32_t user_arg_mask) { AtomicHolder h(this); set_dirty(); LOG(VERBOSE, "%p: unregister %p", this, handler); - bool found = false; for (auto r = handlers_.begin(); r != handlers_.end(); ++r) { auto begin_it = r->second.begin(); auto end_it = r->second.end(); - auto erase_it = std::remove_if( - begin_it, end_it, [handler](const EventRegistryEntry ®) { - return reg.handler == handler; + auto erase_it = std::remove_if(begin_it, end_it, + [handler, user_arg, user_arg_mask](const EventRegistryEntry &e) { + return e.handler == handler && + ((e.user_arg & user_arg_mask) == + (user_arg & user_arg_mask)); }); if (erase_it != end_it) { r->second.erase(erase_it, end_it); - found = true; } } - if (found) - { - return; - } - DIE("tried to unregister a handler that was not registered"); +} + +void TreeEventHandlers::reserve(size_t count) +{ + AtomicHolder h(this); + handlers_[0].reserve(handlers_[0].size() + count); } /// Class representing the iteration state on the binary tree-based event diff --git a/src/openlcb/EventHandlerContainer.cxxtest b/src/openlcb/EventHandlerContainer.cxxtest index 604e507c6..f9d525549 100644 --- a/src/openlcb/EventHandlerContainer.cxxtest +++ b/src/openlcb/EventHandlerContainer.cxxtest @@ -313,7 +313,7 @@ public: } vector get_all_matching(uint64_t event, - uint64_t mask = 1) + uint64_t mask = 0) { report_.event = event; report_.mask = mask; @@ -332,9 +332,9 @@ public: return reinterpret_cast(0x100 + n); } - void add_handler(int n, uint64_t eventid, unsigned mask) + void add_handler(int n, uint64_t eventid, unsigned mask, uint32_t arg = 0) { - handlers_.register_handler(EventRegistryEntry(h(n), eventid), mask); + handlers_.register_handler(EventRegistryEntry(h(n), eventid, arg), mask); } protected: @@ -369,6 +369,26 @@ TEST_F(TreeEventHandlerTest, SingleLookup) EXPECT_THAT(get_all_matching(0x103FF, 0), ElementsAre()); } +TEST_F(TreeEventHandlerTest, RemoveByMask) +{ + handlers_.reserve(3); + + add_handler(1, 0x3FF, 0, 0xB); + add_handler(1, 0x3FE, 0, 7); + add_handler(1, 0x3FD, 0, 0xFB); + EXPECT_THAT(get_all_matching(0x3F0, 0xF), ElementsAre(h(1),h(1),h(1))); + EXPECT_THAT(get_all_matching(0x3FF), ElementsAre(h(1))); + EXPECT_THAT(get_all_matching(0x3FE), ElementsAre(h(1))); + EXPECT_THAT(get_all_matching(0x3FD), ElementsAre(h(1))); + + handlers_.unregister_handler(h(1), 0xB, 0xF); + + EXPECT_THAT(get_all_matching(0x3F0, 0xF), ElementsAre(h(1))); + EXPECT_THAT(get_all_matching(0x3FF), ElementsAre()); + EXPECT_THAT(get_all_matching(0x3FE), ElementsAre(h(1))); + EXPECT_THAT(get_all_matching(0x3FD), ElementsAre()); +} + TEST_F(TreeEventHandlerTest, MultiLookup) { add_handler(1, 0x3FF, 0); diff --git a/src/openlcb/EventHandlerContainer.hxx b/src/openlcb/EventHandlerContainer.hxx index 6dafb2c8e..192ce259e 100644 --- a/src/openlcb/EventHandlerContainer.hxx +++ b/src/openlcb/EventHandlerContainer.hxx @@ -109,8 +109,9 @@ private: /// EventRegistry implementation that keeps all event handlers in a vector and /// forwards every single call to each event handler. -class VectorEventHandlers : public EventRegistry { - public: +class VectorEventHandlers : public EventRegistry, private Atomic +{ +public: VectorEventHandlers() {} // Creates a new event iterator. Caller takes ownership of object. @@ -118,30 +119,24 @@ class VectorEventHandlers : public EventRegistry { return new FullContainerIterator(&handlers_); } - void register_handler(const EventRegistryEntry& entry, unsigned mask) OVERRIDE { - // @TODO(balazs.racz): need some kind of locking here. - handlers_.push_front(entry); - set_dirty(); - } - void unregister_handler(EventHandler *handler) OVERRIDE - { - // @TODO(balazs.racz): need some kind of locking here. - struct HandlerEquals - { - HandlerEquals(EventHandler *h) : h_(h) - { - } - bool operator()(const EventRegistryEntry &e) - { - return e.handler == h_; - } - - private: - EventHandler *h_; - } predicate(handler); - handlers_.remove_if(predicate); - set_dirty(); - } + void register_handler( + const EventRegistryEntry &entry, unsigned mask) OVERRIDE + { + AtomicHolder h(this); + handlers_.push_front(entry); + set_dirty(); + } + void unregister_handler(EventHandler *handler, uint32_t user_arg = 0, + uint32_t user_arg_mask = 0) OVERRIDE + { + AtomicHolder h(this); + handlers_.remove_if([handler, user_arg, user_arg_mask]( + const EventRegistryEntry &e) { + return e.handler == handler && + ((e.user_arg & user_arg_mask) == (user_arg & user_arg_mask)); + }); + set_dirty(); + } private: typedef std::forward_list HandlersList; @@ -158,7 +153,9 @@ public: EventIterator* create_iterator() OVERRIDE; void register_handler(const EventRegistryEntry &entry, unsigned mask) OVERRIDE; - void unregister_handler(EventHandler* handler) OVERRIDE; + void unregister_handler(EventHandler *handler, uint32_t user_arg = 0, + uint32_t user_arg_mask = 0) OVERRIDE; + void reserve(size_t count) OVERRIDE; private: class Iterator; diff --git a/src/openlcb/EventIdentifyGlobal.cxxtest b/src/openlcb/EventIdentifyGlobal.cxxtest new file mode 100644 index 000000000..4aeecfe7b --- /dev/null +++ b/src/openlcb/EventIdentifyGlobal.cxxtest @@ -0,0 +1,175 @@ +/** @copyright + * Copyright (c) 2021, Stuart Baker + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @file EventIdentifyGlobal.cxxtest + * + * Unit tests for Event Identify Global messages. + * + * @author Stuart Baker + * @date 5 April 2021 + */ + +#include "utils/async_if_test_helper.hxx" + +#include "openlcb/EventIdentifyGlobal.hxx" +#include "os/FakeClock.hxx" + +namespace openlcb +{ + +class EventIdentifyGlobalTest : public AsyncNodeTest +{ +protected: + EventIdentifyGlobalTest() + { + flow_.reset(new EventIdentifyGlobal(node_)); + } + + ~EventIdentifyGlobalTest() + { + wait(); + } + + /// Helper function for sleeping. + /// @param clk fake clock or nullptr if no fake clock exists + /// @param len_msec how long to sleep + /// @param step_msec what granularity to use for sleeping wiht fake clock. + void sleep_helper(unsigned len_msec, unsigned step_msec = 50) + { + for (unsigned i = 0; i < len_msec; i += step_msec) + { + clk_.advance(MSEC_TO_NSEC(step_msec)); + wait(); + } + } + + /// Generate the random "hash" from the test Node ID. + long long generate_random() + { + uint64_t h = TEST_NODE_ID * 0x1c19a66d; + uint32_t hh = h ^ (h >> 32); + return (hh ^ (hh >> 12) ^ (hh >> 24)) & 0x1FF; + + } + + FakeClock clk_; + std::unique_ptr flow_; +}; + +TEST_F(EventIdentifyGlobalTest, Create) +{ +} + +TEST_F(EventIdentifyGlobalTest, Arm) +{ + ::testing::Sequence s1; + + flow_->arm(); + + expect_packet(":X1997022AN;").Times(0).InSequence(s1); + sleep_helper(1500 + generate_random() - 50); + + expect_packet(":X1997022AN;").Times(1).InSequence(s1); + sleep_helper(100); +} + +TEST_F(EventIdentifyGlobalTest, ArmDeleteSelf) +{ + ::testing::Sequence s1; + + // test coverage for this is limited because we loose track of the + // pointer for "self" for the object. + flow_->arm(true); + + expect_packet(":X1997022AN;").Times(0).InSequence(s1); + sleep_helper(1500 + generate_random() - 50); + + expect_packet(":X1997022AN;").Times(1).InSequence(s1); + sleep_helper(100); + + // prevent crash of test since pointer is no longer valid. + flow_.release(); +} + +TEST_F(EventIdentifyGlobalTest, ArmMultipleTimes) +{ + ::testing::Sequence s1; + + flow_->arm(); + flow_->arm(); + + expect_packet(":X1997022AN;").Times(0).InSequence(s1); + sleep_helper(1500 + generate_random() - 50); + + flow_->arm(); + + expect_packet(":X1997022AN;").Times(1).InSequence(s1); + sleep_helper(100); +} + +TEST_F(EventIdentifyGlobalTest, ArmWithAbort) +{ + ::testing::Sequence s1; + + flow_->arm(); + + expect_packet(":X1997022AN;").Times(0).InSequence(s1); + sleep_helper(1400); + send_packet(":X19970123N;"); + + expect_packet(":X1997022AN;").Times(0).InSequence(s1); + sleep_helper(100 + generate_random() - 50); + + expect_packet(":X1997022AN;").Times(0).InSequence(s1); + sleep_helper(100); +} + +TEST_F(EventIdentifyGlobalTest, ArmWithLateInitialize) +{ + ::testing::Sequence s1; + + node_->clear_initialized(); + flow_->arm(); + + expect_packet(":X1997022AN;").Times(0).InSequence(s1); + sleep_helper(1500 + generate_random(), 20); + + EXPECT_CALL(canBus_, + mwrite(":X1910022AN02010D000003;")).Times(1).InSequence(s1); + // Causes all nodes to grab a new alias and send out node initialization + // done messages. This object owns itself and will do `delete this;` at the + // end of the process. + new ReinitAllNodes(node_->iface()); + + expect_packet(":X1997022AN;").Times(0).InSequence(s1); + sleep_helper(1500 + generate_random() - 100); + + expect_packet(":X1997022AN;").Times(1).InSequence(s1); + sleep_helper(150); + +} + +} // namespace openlcb diff --git a/src/openlcb/EventIdentifyGlobal.hxx b/src/openlcb/EventIdentifyGlobal.hxx new file mode 100644 index 000000000..a813973e2 --- /dev/null +++ b/src/openlcb/EventIdentifyGlobal.hxx @@ -0,0 +1,164 @@ +/** @copyright + * Copyright (c) 2021, Stuart Baker + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @file EventIdentifyGlobal.hxx + * + * Implements the necessary logic to produce EventIdentifyGlobal messages. + * + * @author Stuart Baker + * @date 4 April 2021 + */ + +#ifndef _OPENLCB_EVENTIDENTIFYGLOBAL_HXX_ +#define _OPENLCB_EVENTIDENTIFYGLOBAL_HXX_ + +#include "openlcb/If.hxx" +#include "utils/Atomic.hxx" + +namespace openlcb +{ + +/// Helper object for producing an Event Identify Global message. Once armed, +/// will produce an Event Identify Global message on the node's interface +/// at a pseudo random time between 1.500 and 2.011 seconds in the future. The +/// randomness comes a hash of the Node ID and aids in reducing the likeliness +/// of multiple nodes of the same design producing the same Event Identify +/// Global message unnecessarily. If another node produces the Event Identify +/// Global first (after arm but before timeout), then we abort the state +/// machine early. +class EventIdentifyGlobal : public StateFlowBase, private Atomic +{ +public: + /// Constructor. + /// @param node Node ID associated with this object + EventIdentifyGlobal(Node *node) + : StateFlowBase(node->iface()) + , eventIdentifyGlobalHandler_( + this, &EventIdentifyGlobal::handle_incoming_event_identify_global) + , timer_(this) + , node_(node) + , aborted_(false) + , deleteSelf_(false) + { + node_->iface()->dispatcher()->register_handler( + &eventIdentifyGlobalHandler_, Defs::MTI_EVENTS_IDENTIFY_GLOBAL, + Defs::MTI_EXACT); + } + + /// Destructor. + ~EventIdentifyGlobal() + { + node_->iface()->dispatcher()->unregister_handler( + &eventIdentifyGlobalHandler_, Defs::MTI_EVENTS_IDENTIFY_GLOBAL, + Defs::MTI_EXACT); + } + + /// Arm the EventIdentifyGlobal request. Multi-thread safe. + /// @param delete_self true if the object should delete itself on next + /// state flow completion (one-shot). + void arm(bool delete_self = false) + { + AtomicHolder h(this); + if (is_terminated()) + { + aborted_ = false; + deleteSelf_ = delete_self; + start_flow(STATE(entry)); + } + } + +private: + /// Callback upon receiving a Defs::MTI_EVENTS_IDENTIFY_GLOBAL message. + /// @param msg unused, need to unref to prevent memory leak + void handle_incoming_event_identify_global(Buffer *msg) + { + msg->unref(); + AtomicHolder h(this); + aborted_ = true; + } + + /// Entry/reset point into state machine. + Action entry() + { + // get a pseudo random number between 1.500 and 2.011 seconds + uint64_t h = node_->node_id() * 0x1c19a66d; + uint32_t hh = h ^ (h >> 32); + hh = hh ^ (hh >> 12) ^ (hh >> 24); + long long timeout_msec = 1500 + (hh & 0x1FF); + + return sleep_and_call( + &timer_, MSEC_TO_NSEC(timeout_msec), STATE(timeout)); + } + + /// Will be called on the executor of the timer. + Action timeout() + { + { + // Making the state flow termination atomic cleans up a potential + // race condition with an arm() call coming in after the if + // statement but before the StateFlowBase::exit() call. + AtomicHolder h(this); + if (aborted_) + { + // no need to send Event Identify Global, already detected one + return deleteSelf_ ? delete_this() : exit(); + } + } + + if (!node_->is_initialized()) + { + // node not initialized yet, try again later + return call_immediately(STATE(entry)); + } + + // allocate a buffer for the Event Identify Global message + return allocate_and_call( + node_->iface()->global_message_write_flow(), STATE(fill_buffer)); + } + + /// Fill in allocated buffer and send Event Identify Global message + Action fill_buffer() + { + auto *b = get_allocation_result( + node_->iface()->global_message_write_flow()); + b->data()->reset( + Defs::MTI_EVENTS_IDENTIFY_GLOBAL, node_->node_id(), EMPTY_PAYLOAD); + node_->iface()->global_message_write_flow()->send(b); + + return deleteSelf_ ? delete_this() : exit(); + } + + /// handler for incoming messages + MessageHandler::GenericHandler eventIdentifyGlobalHandler_; + StateFlowTimer timer_; ///< timer object for handling the timeout + Node *node_; ///< node to send message from + uint8_t aborted_ : 1; ///< true if message received, abort need to send + uint8_t deleteSelf_ : 1; ///< delete object upon state flow exit (one-shot) +}; + +} // namespace openlcb + +#endif // _OPENLCB_EVENTIDENTIFYGLOBAL_HXX_ diff --git a/src/openlcb/If.cxx b/src/openlcb/If.cxx index 5e15582df..d53d06008 100644 --- a/src/openlcb/If.cxx +++ b/src/openlcb/If.cxx @@ -119,11 +119,11 @@ void buffer_to_error(const Payload &payload, uint16_t *error_code, error_message->clear(); if (payload.size() >= 2 && error_code) { - *error_code = (((uint16_t)payload[0]) << 8) | payload[1]; + *error_code = (((uint16_t)payload[0]) << 8) | (uint8_t)payload[1]; } if (payload.size() >= 4 && mti) { - *mti = (((uint16_t)payload[2]) << 8) | payload[3]; + *mti = (((uint16_t)payload[2]) << 8) | (uint8_t)payload[3]; } if (payload.size() > 4 && error_message) { diff --git a/src/openlcb/If.hxx b/src/openlcb/If.hxx index 4dd3ee4fa..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. @@ -282,12 +199,20 @@ public: MessageHandler *global_message_write_flow() { HASSERT(globalWriteFlow_); + if (txHook_) + { + txHook_(); + } return globalWriteFlow_; } /** @return Flow to send addressed messages to the NMRAnet bus. */ MessageHandler *addressed_message_write_flow() { HASSERT(addressedWriteFlow_); + if (txHook_) + { + txHook_(); + } return addressedWriteFlow_; } @@ -399,7 +324,15 @@ public: * the interface holds internally. Noop for TCP interface. Must be called * on the interface executor. */ virtual void canonicalize_handle(NodeHandle *h) {} - + + /// Sets a transmit hook. This function will be called once for every + /// OpenLCB message transmitted. Used for implementing activity LEDs. + /// @param hook function to call for each transmit message. + void set_tx_hook(std::function hook) + { + txHook_ = std::move(hook); + } + protected: void remove_local_node_from_map(Node *node) { auto it = localNodes_.find(node->node_id()); @@ -416,6 +349,9 @@ private: /// Flow responsible for routing incoming messages to handlers. MessageDispatchFlow dispatcher_; + /// This function is pinged every time a message is transmitted. + std::function txHook_; + typedef Map VNodeMap; /// Local virtual nodes registered on this interface. @@ -452,6 +388,28 @@ public: } }; +/// Sends an OpenLCB message to the bus. Performs synchronous (dynamic) memory +/// allocation so use it sparingly and when there is sufficient amount of RAM +/// available. +/// @param src_node A local virtual node from which to send the message. +/// @param mti message type indicator +/// @param args either a Payload to send a global message, or a NodeHandle dst +/// and a Payload to send an addressed message. +template void send_message(Node *src_node, Defs::MTI mti, Args &&...args) +{ + Buffer *msg; + mainBufferPool->alloc(&msg); + msg->data()->reset(mti, src_node->node_id(), std::forward(args)...); + if (msg->data()->dst == NodeHandle()) + { + src_node->iface()->global_message_write_flow()->send(msg); + } + else + { + src_node->iface()->addressed_message_write_flow()->send(msg); + } +} + } // namespace openlcb #endif // _OPENLCB_IF_HXX_ diff --git a/src/openlcb/IfCan.cxx b/src/openlcb/IfCan.cxx index 121c8ddd3..704b16f23 100644 --- a/src/openlcb/IfCan.cxx +++ b/src/openlcb/IfCan.cxx @@ -362,6 +362,9 @@ class AMEGlobalQueryHandler : public StateFlowBase, return; } needRerun_ = true; + // Drops all remote aliases from the cache to re-populate this cache + // from the network responses. + if_can()->remote_aliases()->clear(); if (is_terminated()) { start_flow(STATE(rerun)); @@ -379,8 +382,9 @@ class AMEGlobalQueryHandler : public StateFlowBase, { while (nextIndex_ < if_can()->local_aliases()->size()) { - if (if_can()->local_aliases()->retrieve( - nextIndex_, nullptr, nullptr)) + NodeID n; + if (if_can()->local_aliases()->retrieve(nextIndex_, &n, nullptr) && + ((n >> (5 * 8)) != 0)) { return allocate_and_call( if_can()->frame_write_flow(), STATE(fill_response)); @@ -702,6 +706,33 @@ void IfCan::set_alias_allocator(AliasAllocator *a) aliasAllocator_.reset(a); } +void IfCan::send_global_alias_enquiry(Node *source) +{ + if (!source->is_initialized()) + { + LOG_ERROR("Tried to send global AME from not initialized node."); + return; + } + NodeAlias send_alias = local_aliases()->lookup(source->node_id()); + if (!send_alias) + { + LOG_ERROR("Tried to send global AME without a local alias."); + return; + } + { + auto *b = frame_write_flow()->alloc(); + CanDefs::control_init(*b->data(), send_alias, CanDefs::AME_FRAME, 0); + // Sends it out + frame_write_flow()->send(b); + } + { + // Sends another to the local node, but not using the local alias. + auto *b = frame_dispatcher()->alloc(); + CanDefs::control_init(*b->data(), 0, CanDefs::AME_FRAME, 0); + frame_dispatcher()->send(b); + } +} + void IfCan::add_addressed_message_support() { if (addressedWriteFlow_) @@ -718,7 +749,7 @@ void IfCan::delete_local_node(Node *node) { if (alias) { // The node had a local alias. localAliases_.remove(alias); - localAliases_.add(AliasCache::RESERVED_ALIAS_NODE_ID, alias); + localAliases_.add(CanDefs::get_reserved_alias_node_id(alias), alias); // Sends AMR & returns alias to pool. aliasAllocator_->return_alias(node->node_id(), alias); } @@ -727,7 +758,7 @@ void IfCan::delete_local_node(Node *node) { void IfCan::canonicalize_handle(NodeHandle *h) { - if (!h->id & !h->alias) + if (!h->id && !h->alias) return; if (!h->id) { diff --git a/src/openlcb/IfCan.cxxtest b/src/openlcb/IfCan.cxxtest index fc1d5af65..5a7c267b0 100644 --- a/src/openlcb/IfCan.cxxtest +++ b/src/openlcb/IfCan.cxxtest @@ -1,5 +1,6 @@ #include "utils/async_if_test_helper.hxx" +#include "openlcb/CanDefs.hxx" #include "openlcb/WriteHelper.hxx" namespace openlcb @@ -35,8 +36,6 @@ TEST_F(AsyncIfTest, InjectFrame) wait(); } -#define RX(args) run_x([this]() { args; }) - TEST_F(AsyncIfTest, InjectFrameAndExpectHandler) { StrictMock h; @@ -124,9 +123,13 @@ TEST_F(AsyncIfTest, RemoteAliasLearned) }); } -TEST_F(AsyncIfTest, AMESupport) +TEST_F(AsyncIfTest, AMEReceiveSupport) { + // Example virtual node. RX(ifCan_->local_aliases()->add(UINT64_C(0x050101011877), 0x729U)); + // This is a reserved but unused alias which should not get AMD frames as + // replies for AME. + RX(ifCan_->alias_allocator()->add_allocated_alias(0x567u)); send_packet_and_expect_response(":X10702643N050101011877;", ":X10701729N050101011877;"); wait(); @@ -138,6 +141,31 @@ TEST_F(AsyncIfTest, AMESupport) wait(); } +TEST_F(AsyncNodeTest, GlobalAMESendSupport) +{ + EXPECT_TRUE(node_->is_initialized()); + RX({ + ifCan_->local_aliases()->add(UINT64_C(0x050101011877), 0x729U); + ifCan_->remote_aliases()->add(UINT64_C(0x050101011811), 0x111U); + ifCan_->alias_allocator()->add_allocated_alias(0x567u); + EXPECT_EQ( + 0x111u, ifCan_->remote_aliases()->lookup(UINT64_C(0x050101011811))); + }); + // The enquiry is sent out... + expect_packet(":X1070222AN;"); + // ...along with all local aliases... + expect_packet(":X1070122AN02010D000003;"); + expect_packet(":X10701729N050101011877;"); + RX(ifCan_->send_global_alias_enquiry(node_)); + wait(); + // and the remote cache was cleared. + RX(EXPECT_EQ( + 0u, ifCan_->remote_aliases()->lookup(UINT64_C(0x050101011811)))); + // Checks regression: we did not lose the local node alias. + RX(EXPECT_EQ( + 0x22Au, ifCan_->local_aliases()->lookup(node_->node_id()))); +} + TEST_F(AsyncNodeTest, NodeIdLookupLocal) { NodeIdLookupFlow lflow(ifCan_.get()); @@ -348,7 +376,6 @@ TEST_F(AsyncMessageCanTests, WriteByMTIAllocatesLocalAlias) auto *b = ifCan_->global_message_write_flow()->alloc(); create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000004;"); expect_packet(":X195B433AN0102030405060708;"); b->data()->reset(Defs::MTI_EVENT_REPORT, TEST_NODE_ID + 1, @@ -397,7 +424,7 @@ TEST_F(AsyncMessageCanTests, AliasConflictCIDReply) TEST_F(AsyncMessageCanTests, ReservedAliasReclaimed) { - /** In this test we exercie the case when an alias that was previously + /** In this test we exercise the case when an alias that was previously * reserved by us but not used for any virtual node yet experiences various * conflicts. In the first case we see a regular CID conflict that gets * replied to. In the second case we see someone else actively using that @@ -405,33 +432,39 @@ TEST_F(AsyncMessageCanTests, ReservedAliasReclaimed) * detected at the time the next outgoing virtual node tries to allocate * that alias, and we'll test that it actually generates a new one * instead. */ - RX(ifCan_->local_aliases()->remove(NodeAlias(0x22A))); // resets the cache. + ifCan_->alias_allocator()->TEST_set_reserve_unused_alias_count(1); + RX(ifCan_->local_aliases()->clear()); // resets the cache. + // Sets up a regular alias for our standard node. + RX(ifCan_->alias_allocator()->add_allocated_alias(0x33A)); auto* b = ifCan_->global_message_write_flow()->alloc(); - create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000003;"); expect_packet(":X195B433AN0102030405060708;"); + LOG(INFO, "Next alias %03X", aliasSeed_); + // When we send out a packet from our node, a new alias will be grabbed. + expect_next_alias_allocation(); b->data()->reset(Defs::MTI_EVENT_REPORT, TEST_NODE_ID, eventid_to_buffer(UINT64_C(0x0102030405060708))); ifCan_->global_message_write_flow()->send(b); + wait(); + // Ticks down the time for the new alias to take hold. usleep(250000); wait(); - // Here we have the next reserved alias. - RX(EXPECT_EQ(AliasCache::RESERVED_ALIAS_NODE_ID, + // Checks that we have the next reserved alias. + RX(EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0x44C), ifCan_->local_aliases()->lookup(NodeAlias(0x44C)))); - // A CID packet gets replied to. + // A CID packet on that alias gets replied to. send_packet_and_expect_response(":X1478944CN;", ":X1070044CN;"); // We still have it in the cache. - RX(EXPECT_EQ(AliasCache::RESERVED_ALIAS_NODE_ID, + RX(EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0x44C), ifCan_->local_aliases()->lookup(NodeAlias(0x44C)))); // We kick it out with a regular frame. send_packet(":X1800044CN;"); wait(); RX(EXPECT_EQ(0U, ifCan_->local_aliases()->lookup(NodeAlias(0x44C)))); - // At this point we have an invalid alias in the reserved_aliases() - // queue. We check here that a new node gets a new alias. + // At this point we have no valid reserved alias in the cache. We check + // here that a new node gets a new alias. expect_next_alias_allocation(); // Unfortunately we have to guess the second next alias here because we // can't inject it. We can only inject one alias at a time, but now two @@ -444,12 +477,12 @@ TEST_F(AsyncMessageCanTests, ReservedAliasReclaimed) eventid_to_buffer(UINT64_C(0x0102030405060709))); b->set_done(get_notifiable()); ifCan_->global_message_write_flow()->send(b); - wait_for_notification(); + wait_for_notification(); // Will wait for one alias allocation to complete. RX(EXPECT_EQ( TEST_NODE_ID + 1, ifCan_->local_aliases()->lookup(NodeAlias(0x44D)))); - usleep(250000); - RX(EXPECT_EQ(AliasCache::RESERVED_ALIAS_NODE_ID, + usleep(250000); // Second alias allocation to complete. + RX(EXPECT_EQ(CanDefs::get_reserved_alias_node_id(0x6AA), ifCan_->local_aliases()->lookup(NodeAlias(0x6AA)))); } @@ -770,7 +803,6 @@ TEST_F(AsyncNodeTest, SendAddressedMessageFromNewNodeWithCachedAlias) // Simulate cache miss on local alias cache. RX(ifCan_->local_aliases()->remove(0x22A)); create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000003;"); // AMD for our new alias. // And the frame goes out. expect_packet(":X1948833AN0210050101FFFFDD;"); @@ -790,7 +822,6 @@ TEST_F(AsyncNodeTest, SendAddressedMessageFromNewNodeWithCacheMiss) // Simulate cache miss on local alias cache. RX(ifCan_->local_aliases()->remove(0x22A)); create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000003;"); // AMD for our new alias. // And the new alias will do the lookup. Not with an AME frame but straight // to the verify node id. @@ -817,7 +848,6 @@ TEST_F(AsyncNodeTest, SendAddressedMessageFromNewNodeWithCacheMissTimeout) // Simulate cache miss on local alias cache. RX(ifCan_->local_aliases()->remove(0x22A)); create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000003;"); // AMD for our new alias. // And the new alias will do the lookup. Not with an AME frame but straight // to the verify node id. diff --git a/src/openlcb/IfCan.hxx b/src/openlcb/IfCan.hxx index 484aa5be7..b5121483f 100644 --- a/src/openlcb/IfCan.hxx +++ b/src/openlcb/IfCan.hxx @@ -115,6 +115,15 @@ public: /// Sets the alias allocator for this If. Takes ownership of pointer. void set_alias_allocator(AliasAllocator *a); + /// Sends a global alias enquiry packet. This will also clear the remote + /// alias cache (in this node and in all other OpenMRN-based nodes on the + /// bus) and let it re-populate from the responses coming back. This call + /// should be used very sparingly because of the effect it has on the + /// entire bus. + /// @param source a local node to use for sending this message out. Must be + /// already initialized. + void send_global_alias_enquiry(Node *source); + void add_owned_flow(Executable *e) override; bool matching_node(NodeHandle expected, NodeHandle actual) override; diff --git a/src/openlcb/IfCanImpl.hxx b/src/openlcb/IfCanImpl.hxx index 20e5fee8b..5d3455d4e 100644 --- a/src/openlcb/IfCanImpl.hxx +++ b/src/openlcb/IfCanImpl.hxx @@ -60,10 +60,10 @@ public: } protected: - unsigned srcAlias_ : 12; ///< Source node alias. - unsigned dstAlias_ : 12; ///< Destination node alias. - unsigned dataOffset_ : 8; /**< for continuation frames: which offset in - * the Buffer should we start the payload at. */ + NodeAlias srcAlias_; ///< Source node alias. + NodeAlias dstAlias_; ///< Destination node alias. + uint8_t dataOffset_; /**< for continuation frames: which offset in + * the Buffer should we start the payload at. */ Action send_to_hardware() override { @@ -104,51 +104,17 @@ private: * @TODO(balazs.racz): implement proper local alias reclaim/reuse * mechanism. */ HASSERT(if_can()->alias_allocator()); - return allocate_and_call( - STATE(take_new_alias), - if_can()->alias_allocator()->reserved_aliases()); - } - - Action take_new_alias() - { - /* In this function we do a number of queries to the local alias - * cache. It is important that these queries show a consistent state; - * we do not need to hold any mutex though because only the current - * executor is allowed to access that object. */ - NodeAlias alias = 0; + NodeAlias alias = if_can()->alias_allocator()->get_allocated_alias( + nmsg()->src.id, this); + if (!alias) { - Buffer *new_alias = - full_allocation_result(if_can()->alias_allocator()); - HASSERT(new_alias->data()->alias); - alias = new_alias->data()->alias; - /* Sends the alias back for reallocating. This will trigger the - * alias allocator flow. */ - if (new_alias->data()->return_to_reallocation) - { - new_alias->data()->reset(); - if_can()->alias_allocator()->send(new_alias); - } - else - { - new_alias->unref(); - } + // wait for notification and re-try this step. + return wait(); } LOG(INFO, "Allocating new alias %03X for node %012" PRIx64, alias, nmsg()->src.id); - // Checks that there was no conflict on this alias. - if (if_can()->local_aliases()->lookup(alias) != - AliasCache::RESERVED_ALIAS_NODE_ID) - { - LOG(INFO, "Alias has seen conflict: %03X", alias); - // Problem. Let's take another alias. - return call_immediately(STATE(allocate_new_alias)); - } - srcAlias_ = alias; - /** @TODO(balazs.racz): We leak aliases here in case of eviction by the - * AliasCache object. */ - if_can()->local_aliases()->add(nmsg()->src.id, alias); // Take a CAN frame to send off the AMD frame. return allocate_and_call(if_can()->frame_write_flow(), STATE(send_amd_frame)); diff --git a/src/openlcb/IfImpl.cxxtest b/src/openlcb/IfImpl.cxxtest index 37ba84c1b..2f019c05f 100644 --- a/src/openlcb/IfImpl.cxxtest +++ b/src/openlcb/IfImpl.cxxtest @@ -14,7 +14,6 @@ public: TwoNodeTest() { create_allocated_alias(); - expect_next_alias_allocation(); expect_packet(":X1070133AN02010D000004;"); expect_packet(":X1910033AN02010D000004;"); secondNode_.reset( diff --git a/src/openlcb/MemoryConfig.cxx b/src/openlcb/MemoryConfig.cxx index fd543f154..f6d6ee215 100644 --- a/src/openlcb/MemoryConfig.cxx +++ b/src/openlcb/MemoryConfig.cxx @@ -44,6 +44,8 @@ #include "can_ioctl.h" #endif +#include "openlcb/ConfigUpdateFlow.hxx" + extern "C" { /// Implement this function (usually in HwInit.cxx) to enter the /// bootloader. Usual implementations write some magic value to RAM and the @@ -65,6 +67,21 @@ void reboot() namespace openlcb { +#ifdef GTEST +static constexpr unsigned FACTORY_RESET_REBOOT_DELAY_MSEC = 50; +#else +static constexpr unsigned FACTORY_RESET_REBOOT_DELAY_MSEC = 500; +#endif + + +void __attribute__((weak)) MemoryConfigHandler::handle_factory_reset() +{ + static_cast(ConfigUpdateFlow::instance()) + ->factory_reset(); + (new RebootTimer(service())) + ->start(MSEC_TO_NSEC(FACTORY_RESET_REBOOT_DELAY_MSEC)); +} + FileMemorySpace::FileMemorySpace(int fd, address_t len) : fileSize_(len) , name_(nullptr) diff --git a/src/openlcb/MemoryConfig.cxxtest b/src/openlcb/MemoryConfig.cxxtest index 88dba9471..839a3115c 100644 --- a/src/openlcb/MemoryConfig.cxxtest +++ b/src/openlcb/MemoryConfig.cxxtest @@ -32,9 +32,12 @@ * @date 23 Feb 2014 */ -#include "utils/async_datagram_test_helper.hxx" #include "openlcb/MemoryConfig.hxx" +#include "utils/async_datagram_test_helper.hxx" +#include "utils/ConfigUpdateListener.hxx" +#include "openlcb/ConfigUpdateFlow.hxx" + #include #include #include @@ -372,6 +375,60 @@ TEST_F(MemoryConfigTest, GetSpaceInfoRO_NZLA) wait(); } +struct GlobalMock : public Singleton { + MOCK_METHOD0(reboot, void()); + MOCK_METHOD0(factory_reset, void()); +}; + +extern "C" void reboot() +{ + GlobalMock::instance()->reboot(); +} + +struct FactoryResetListener : public DefaultConfigUpdateListener +{ + void factory_reset(int fd) override + { + GlobalMock::instance()->factory_reset(); + } + + UpdateAction apply_configuration( + int fd, bool initial_load, BarrierNotifiable *done) + { + done->notify(); + return UPDATED; + } +}; + +TEST_F(MemoryConfigTest, Reboot) +{ + StrictMock mock; + // Normally, a reboot function never returns. We can't do that under linux + // though, so the stack will generate an error datagram reply. + expect_packet(":X19A4822AN077C1041;"); // unsupported command + + EXPECT_CALL(mock, reboot()); + send_packet(":X1A22A77CN20A9;"); + wait(); +} + +TEST_F(MemoryConfigTest, FactoryReset) +{ + StrictMock mock; + ConfigUpdateFlow update_flow{ifCan_.get()}; + update_flow.TEST_set_fd(23); + + FactoryResetListener l; + expect_packet(":X19A2822AN077C00;"); // received OK, no response + + EXPECT_CALL(mock, factory_reset()); + send_packet(":X1A22A77CN20AA;"); + wait(); + + EXPECT_CALL(mock, reboot()); + twait(); +} + static const char MEMORY_BLOCK_DATA[] = "abrakadabra12345678xxxxyyyyzzzzwww."; class StaticBlockTest : public MemoryConfigTest diff --git a/src/openlcb/MemoryConfig.hxx b/src/openlcb/MemoryConfig.hxx index c205ec083..218a7b151 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. /// @@ -704,6 +482,11 @@ private: #endif return respond_reject(Defs::ERROR_UNIMPLEMENTED_SUBCMD); } + case MemoryConfigDefs::COMMAND_FACTORY_RESET: + { + handle_factory_reset(); + return respond_ok(0); + } case MemoryConfigDefs::COMMAND_OPTIONS: { return call_immediately(STATE(handle_options)); @@ -735,6 +518,26 @@ private: } } + /// Used internally by the factory_reset implementation to reboot the + /// binary asynchronously. + class RebootTimer : public ::Timer + { + public: + RebootTimer(Service *s) + : ::Timer(s->executor()->active_timers()) + { } + + long long timeout() override + { + reboot(); + return DELETE; + } + }; + + /// Invokes the openlcb config handler to do a factory reset. Starts a + /// timer to reboot the device after a little time. + void handle_factory_reset(); + Action ok_response_sent() OVERRIDE { if (!response_.empty()) diff --git a/src/openlcb/MemoryConfigClient.hxx b/src/openlcb/MemoryConfigClient.hxx index f306fe40e..965400d3d 100644 --- a/src/openlcb/MemoryConfigClient.hxx +++ b/src/openlcb/MemoryConfigClient.hxx @@ -83,7 +83,10 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase /// @param ReadCmd polymorphic matching arg; always set to READ. /// @param d is the destination node to query /// @param space is the memory space to read out - void reset(ReadCmd, NodeHandle d, uint8_t space) + /// @param cb if specified, will be called inline multiple times during the + /// processing as more data arrives. + void reset(ReadCmd, NodeHandle d, uint8_t space, + std::function cb = nullptr) { reset_base(); cmd = CMD_READ; @@ -92,6 +95,7 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase address = 0; size = 0xffffffffu; payload.clear(); + progressCb = std::move(cb); } /// Sets up a command to read a part of a memory space. @@ -112,11 +116,11 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase payload.clear(); } - /// Sets up a command to read a part of a memory space. + /// Sets up a command to write a part of a memory space. /// @param WriteCmd polymorphic matching arg; always set to WRITE. - /// @param d is the destination node to query + /// @param d is the destination node to write to /// @param space is the memory space to write to - /// @param offset if the address of the first byte to read + /// @param offset if the address of the first byte to write /// @param data is the data to write void reset( WriteCmd, NodeHandle d, uint8_t space, unsigned offset, string data) @@ -126,7 +130,7 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase memory_space = space; dst = d; this->address = offset; - this->size = size; + this->size = data.size(); payload = std::move(data); } @@ -201,6 +205,17 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase CMD_WRITE, CMD_META_REQUEST }; + + /// Helper function invoked at every other reset call. + void reset_base() + { + CallableFlowRequestBase::reset_base(); + progressCb = nullptr; + payload.clear(); + size = 0; + address = 0; + } + Command cmd; uint8_t memory_space; unsigned address; @@ -208,6 +223,8 @@ struct MemoryConfigClientRequest : public CallableFlowRequestBase /// Node to send the request to. NodeHandle dst; string payload; + /// Callback to execute as progress is being made. + std::function progressCb; }; class MemoryConfigClient : public CallableFlow @@ -364,6 +381,10 @@ private: unsigned dlen = len - ofs; request()->payload.append((char *)(bytes + ofs), dlen); offset_ += dlen; + if (request()->progressCb) + { + request()->progressCb(request()); + } if ((dlen < 64) || (request()->size == 0)) { return call_immediately(STATE(finish_read)); 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/NodeInitializeFlow.cxxtest b/src/openlcb/NodeInitializeFlow.cxxtest index a353beba5..4735f8261 100644 --- a/src/openlcb/NodeInitializeFlow.cxxtest +++ b/src/openlcb/NodeInitializeFlow.cxxtest @@ -36,7 +36,6 @@ TEST_F(AsyncIfTest, TwoNodesInitialize) wait(); expect_packet(":X1070133AN02010d000004;"); // AMD frame expect_packet(":X1910033AN02010d000004;"); // initialization complete - expect_next_alias_allocation(); DefaultNode node2(ifCan_.get(), TEST_NODE_ID + 1); wait(); } diff --git a/src/openlcb/NodeInitializeFlow.hxx b/src/openlcb/NodeInitializeFlow.hxx index dfda0ee6d..b62f39a84 100644 --- a/src/openlcb/NodeInitializeFlow.hxx +++ b/src/openlcb/NodeInitializeFlow.hxx @@ -80,6 +80,10 @@ private: Action entry() OVERRIDE { + if (!node()) + { + return release_and_exit(); + } HASSERT(message()->data()->node); return allocate_and_call( node()->iface()->global_message_write_flow(), diff --git a/src/openlcb/NodeRegistry.hxx b/src/openlcb/NodeRegistry.hxx new file mode 100644 index 000000000..868bbf9ef --- /dev/null +++ b/src/openlcb/NodeRegistry.hxx @@ -0,0 +1,64 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file NodeRegistry.hxx + * + * Abstract class for data structures keyed by virtual nodes. + * + * @author Balazs Racz + * @date 12 Sep 2020 + */ + +#ifndef _OPENLCB_NODEREGISTRY_HXX_ +#define _OPENLCB_NODEREGISTRY_HXX_ + +#include "utils/Destructable.hxx" + +namespace openlcb +{ + +class Node; + +class NodeRegistry : public Destructable +{ +public: + /// Adds a node to the list of registered nodes. + /// @param node a virtual node. + virtual void register_node(openlcb::Node *node) = 0; + + /// Removes a node from the list of registered nodes. + /// @param node a virtual node. + virtual void unregister_node(openlcb::Node *node) = 0; + + /// Checks if a node is registered. + /// @param node a virtual node. + /// @return true if this node has been registered. + virtual bool is_node_registered(openlcb::Node *node) = 0; +}; + +} // namespace openlcb + +#endif // _OPENLCB_NODEREGISTRY_HXX_ diff --git a/src/openlcb/ProtocolIdentification.cxxtest b/src/openlcb/ProtocolIdentification.cxxtest index 83545d81f..6dbd1e995 100644 --- a/src/openlcb/ProtocolIdentification.cxxtest +++ b/src/openlcb/ProtocolIdentification.cxxtest @@ -67,7 +67,6 @@ TEST_F(ProtocolIdentificationTest, DoNotHandleTest) expect_packet(":X1070133AN02010d000004;"); // AMD frame expect_packet(":X1910033AN02010d000004;"); // initialization complete create_allocated_alias(); - expect_next_alias_allocation(); DefaultNode node2(ifCan_.get(), TEST_NODE_ID + 1); wait(); send_packet(":X198289FFN033A;"); diff --git a/src/openlcb/SNIPClient.cxxtest b/src/openlcb/SNIPClient.cxxtest new file mode 100644 index 000000000..c921df941 --- /dev/null +++ b/src/openlcb/SNIPClient.cxxtest @@ -0,0 +1,160 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SNIPClient.cxxtest + * + * Unit test for SNIP client library. + * + * @author Balazs Racz + * @date 18 Oct 2020 + */ + +static long long snipTimeout = 50 * 1000000; +#define SNIP_CLIENT_TIMEOUT_NSEC snipTimeout + +#include "openlcb/SNIPClient.hxx" + +#include "openlcb/SimpleNodeInfo.hxx" +#include "openlcb/SimpleNodeInfoMockUserFile.hxx" +#include "utils/async_if_test_helper.hxx" + +namespace openlcb +{ + +const char *const SNIP_DYNAMIC_FILENAME = MockSNIPUserFile::snip_user_file_path; + +const SimpleNodeStaticValues SNIP_STATIC_DATA = { + 4, "TestingTesting", "Undefined model", "Undefined HW version", "0.9"}; + +class SNIPClientTest : public AsyncNodeTest +{ +protected: + SNIPClientTest() + { + eb_.release_block(); + run_x([this]() { + ifTwo_.alias_allocator()->TEST_add_allocated_alias(0xFF2); + }); + wait(); + } + + ~SNIPClientTest() + { + wait(); + } + + MockSNIPUserFile userFile_ {"Undefined node name", "Undefined node descr"}; + + /// General flow for simple info requests. + SimpleInfoFlow infoFlow_ {ifCan_.get()}; + /// Handles SNIP requests. + SNIPHandler snipHandler_ {ifCan_.get(), node_, &infoFlow_}; + /// The actual client to test. + SNIPClient client_ {ifCan_.get()}; + + // These objects create a second node on the CAN bus (with its own + // interface). + BlockExecutor eb_ {&g_executor}; + static constexpr NodeID TWO_NODE_ID = 0x02010d0000ddULL; + + IfCan ifTwo_ {&g_executor, &can_hub0, local_alias_cache_size, + remote_alias_cache_size, local_node_count}; + AddAliasAllocator alloc_ {TWO_NODE_ID, &ifTwo_}; + DefaultNode nodeTwo_ {&ifTwo_, TWO_NODE_ID}; +}; + +TEST_F(SNIPClientTest, create) +{ +} + +static const char kExpectedData[] = + "\x04TestingTesting\0Undefined model\0Undefined HW version\0" + "0.9\0" + "\x02Undefined node name\0Undefined node descr"; // C adds another \0. + +TEST_F(SNIPClientTest, localhost) +{ + auto b = invoke_flow(&client_, node_, NodeHandle(node_->node_id())); + EXPECT_EQ(0, b->data()->resultCode); + EXPECT_EQ( + string(kExpectedData, sizeof(kExpectedData)), b->data()->response); + + // do another request. + auto bb = invoke_flow(&client_, node_, NodeHandle(node_->node_id())); + EXPECT_EQ(0, bb->data()->resultCode); + EXPECT_EQ( + string(kExpectedData, sizeof(kExpectedData)), bb->data()->response); +} + +TEST_F(SNIPClientTest, remote) +{ + auto b = invoke_flow(&client_, &nodeTwo_, NodeHandle(node_->node_id())); + EXPECT_EQ(0, b->data()->resultCode); + EXPECT_EQ( + string(kExpectedData, sizeof(kExpectedData)), b->data()->response); + + // do another request. + auto bb = invoke_flow(&client_, &nodeTwo_, NodeHandle(node_->node_id())); + EXPECT_EQ(0, bb->data()->resultCode); + EXPECT_EQ( + string(kExpectedData, sizeof(kExpectedData)), bb->data()->response); +} + +TEST_F(SNIPClientTest, timeout) +{ + long long start = os_get_time_monotonic(); + auto b = invoke_flow(&client_, &nodeTwo_, NodeHandle(nodeTwo_.node_id())); + EXPECT_EQ(SNIPClientRequest::OPENMRN_TIMEOUT, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->response.size()); + long long time = os_get_time_monotonic() - start; + EXPECT_LT(MSEC_TO_NSEC(49), time); +} + +TEST_F(SNIPClientTest, reject) +{ + SyncNotifiable n; + auto b = get_buffer_deleter(client_.alloc()); + b->data()->reset(node_, NodeHandle(NodeAlias(0x555))); + b->data()->done.reset(&n); + + expect_packet(":X19DE822AN0555;"); + client_.send(b.get()); + wait(); + clear_expect(true); + EXPECT_EQ(SNIPClientRequest::OPERATION_PENDING, b->data()->resultCode); + + send_packet(":X19068555N022A209905EB;"); + wait(); + EXPECT_EQ(SNIPClientRequest::OPERATION_PENDING, b->data()->resultCode); + + send_packet(":X19068555N022A20990DE8;"); + wait(); + EXPECT_EQ(SNIPClientRequest::ERROR_REJECTED | 0x2099, b->data()->resultCode); + n.wait_for_notification(); + +} + +} // namespace openlcb diff --git a/src/openlcb/SNIPClient.hxx b/src/openlcb/SNIPClient.hxx new file mode 100644 index 000000000..b2264d71e --- /dev/null +++ b/src/openlcb/SNIPClient.hxx @@ -0,0 +1,210 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SNIPClient.hxx + * + * A client library for talking to an arbitrary openlcb Node and ask it for the + * Simple Node Ident Info data. + * + * @author Balazs Racz + * @date 18 Oct 2020 + */ + +#ifndef _OPENLCB_SNIPCLIENT_HXX_ +#define _OPENLCB_SNIPCLIENT_HXX_ + +#include "executor/CallableFlow.hxx" +#include "openlcb/Defs.hxx" +#include "openlcb/If.hxx" +#include "os/sleep.h" + +namespace openlcb +{ + +/// Buffer contents for invoking the SNIP client. +struct SNIPClientRequest : public CallableFlowRequestBase +{ + /// Helper function for invoke_subflow. + /// @param src the openlcb node to call from + /// @param dst the openlcb node to target + void reset(Node *src, NodeHandle dst) + { + reset_base(); + resultCode = OPERATION_PENDING; + src_ = src; + dst_ = dst; + } + + enum + { + OPERATION_PENDING = 0x20000, //< cleared when done is called. + ERROR_REJECTED = 0x200000, //< Target node has rejected the request. + OPENMRN_TIMEOUT = 0x80000, //< Timeout waiting for ack/nack. + }; + + /// Source node where to send the request from. + Node *src_; + /// Destination node to query. + NodeHandle dst_; + /// Response payload if successful. + Payload response; +}; + +#if !defined(GTEST) || !defined(SNIP_CLIENT_TIMEOUT_NSEC) +/// Specifies how long to wait for a SNIP request to get a response. Writable +/// for unittesting purposes. +static constexpr long long SNIP_CLIENT_TIMEOUT_NSEC = MSEC_TO_NSEC(1500); +#endif + +class SNIPClient : public CallableFlow +{ +public: + /// Constructor. + /// @param s service of the openlcb executor. + SNIPClient(Service *s) + : CallableFlow(s) + { + } + + /// 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; + return allocate_and_call( + iface()->addressed_message_write_flow(), STATE(write_request)); + } + +private: + enum + { + MTI_1a = Defs::MTI_TERMINATE_DUE_TO_ERROR, + MTI_1b = Defs::MTI_OPTIONAL_INTERACTION_REJECTED, + MASK_1 = ~(MTI_1a ^ MTI_1b), + MTI_1 = MTI_1a, + + MTI_2 = Defs::MTI_IDENT_INFO_REPLY, + MASK_2 = Defs::MTI_EXACT, + }; + + /// Called once the allocation is complete. Sends out the SNIP request to + /// the bus. + Action write_request() + { + auto *b = + get_allocation_result(iface()->addressed_message_write_flow()); + b->data()->reset(Defs::MTI_IDENT_INFO_REQUEST, + request()->src_->node_id(), request()->dst_, EMPTY_PAYLOAD); + + iface()->dispatcher()->register_handler( + &responseHandler_, MTI_1, MASK_1); + iface()->dispatcher()->register_handler( + &responseHandler_, MTI_2, MASK_2); + + iface()->addressed_message_write_flow()->send(b); + + return sleep_and_call( + &timer_, SNIP_CLIENT_TIMEOUT_NSEC, STATE(response_came)); + } + + /// Callback from the response handler. + /// @param message the incoming response message from the bus + void handle_response(Buffer *message) + { + auto rb = get_buffer_deleter(message); + if (request()->src_ != message->data()->dstNode || + !iface()->matching_node(request()->dst_, message->data()->src)) + { + // Not from the right place. + return; + } + if (message->data()->mti == Defs::MTI_OPTIONAL_INTERACTION_REJECTED || + message->data()->mti == Defs::MTI_TERMINATE_DUE_TO_ERROR) + { + uint16_t mti, error_code; + buffer_to_error( + message->data()->payload, &error_code, &mti, nullptr); + LOG(INFO, "rejection err %04x mti %04x", error_code, mti); + if (mti && mti != Defs::MTI_IDENT_INFO_REQUEST) + { + // Got error response for a different interaction. Ignore. + return; + } + request()->resultCode = + error_code | SNIPClientRequest::ERROR_REJECTED; + } + else if (message->data()->mti == Defs::MTI_IDENT_INFO_REPLY) + { + request()->response = std::move(message->data()->payload); + request()->resultCode = 0; + } + else + { + // Dunno what this MTI is. Ignore. + LOG(INFO, "Unexpected MTI for SNIP response handler: %04x", + message->data()->mti); + return; + } + // Wakes up parent flow. + request()->resultCode &= ~SNIPClientRequest::OPERATION_PENDING; + timer_.trigger(); + } + + Action response_came() + { + iface()->dispatcher()->unregister_handler_all(&responseHandler_); + if (request()->resultCode & SNIPClientRequest::OPERATION_PENDING) + { + return return_with_error(SNIPClientRequest::OPENMRN_TIMEOUT); + } + return return_with_error(request()->resultCode); + } + + /// @return openlcb source interface. + If *iface() + { + return request()->src_->iface(); + } + + /// Handles the timeout feature. + StateFlowTimer timer_ {this}; + /// Registered handler for response messages. + IncomingMessageStateFlow::GenericHandler responseHandler_ { + this, &SNIPClient::handle_response}; +}; + +} // namespace openlcb + +#endif // _OPENLCB_SNIPCLIENT_HXX_ diff --git a/src/openlcb/SimpleInfoProtocol.hxx b/src/openlcb/SimpleInfoProtocol.hxx index 272447061..b8def4bb2 100644 --- a/src/openlcb/SimpleInfoProtocol.hxx +++ b/src/openlcb/SimpleInfoProtocol.hxx @@ -38,6 +38,7 @@ #include #include +#include "openmrn_features.h" #include "openlcb/If.hxx" #include "executor/StateFlow.hxx" @@ -217,7 +218,7 @@ private: } break; } -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD case SimpleInfoDescriptor::FILE_CHAR_ARRAY: open_and_seek_next_file(); // fall through @@ -227,7 +228,7 @@ private: currentLength_ = d.arg; HASSERT(currentLength_); break; -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD case SimpleInfoDescriptor::FILE_LITERAL_BYTE: { open_and_seek_next_file(); @@ -240,7 +241,7 @@ private: byteOffset_ = 0; break; } -#endif // NOT ARDUINO, YES ESP32 +#endif // if have fd default: currentLength_ = 0; } diff --git a/src/openlcb/SimpleNodeInfo.cxx b/src/openlcb/SimpleNodeInfo.cxx index 35766f2ca..8776e7421 100644 --- a/src/openlcb/SimpleNodeInfo.cxx +++ b/src/openlcb/SimpleNodeInfo.cxx @@ -34,30 +34,15 @@ #include "openlcb/SimpleNodeInfo.hxx" +#include "openmrn_features.h" +#include "utils/format_utils.hxx" + 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 defined(ARDUINO) && (!defined(ESP32)) - /// @todo(balazs.racz) Add eeprom support to arduino. - {SimpleInfoDescriptor::LITERAL_BYTE, 2, 0, nullptr}, - {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, - {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, -#else - {SimpleInfoDescriptor::FILE_LITERAL_BYTE, 2, 0, SNIP_DYNAMIC_FILENAME}, - {SimpleInfoDescriptor::FILE_C_STRING, 63, 1, SNIP_DYNAMIC_FILENAME}, - {SimpleInfoDescriptor::FILE_C_STRING, 64, 64, SNIP_DYNAMIC_FILENAME}, -#endif - {SimpleInfoDescriptor::END_OF_DATA, 0, 0, 0}}; - void init_snip_user_file(int fd, const char *user_name, const char *user_description) { @@ -65,9 +50,8 @@ void init_snip_user_file(int fd, const char *user_name, SimpleNodeDynamicValues data; memset(&data, 0, sizeof(data)); data.version = 2; - strncpy(data.user_name, user_name, sizeof(data.user_name)); - strncpy(data.user_description, user_description, - sizeof(data.user_description)); + str_populate(data.user_name, user_name); + str_populate(data.user_description, user_description); int ofs = 0; auto *p = (const uint8_t *)&data; const int len = sizeof(data); @@ -84,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; } @@ -101,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/SimpleNodeInfo.hxx b/src/openlcb/SimpleNodeInfo.hxx index fbd13d018..c93ece6ff 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. */ @@ -115,7 +84,7 @@ public: Action send_response_request() { auto *b = get_allocation_result(responseFlow_); - b->data()->reset(nmsg(), SNIP_RESPONSE, Defs::MTI_IDENT_INFO_REPLY); + b->data()->reset(nmsg(), SNIP_DYNAMIC_FILENAME == nullptr ? SNIP_STATIC_RESPONSE : SNIP_RESPONSE, Defs::MTI_IDENT_INFO_REPLY); responseFlow_->send(b); return release_and_exit(); } @@ -123,6 +92,8 @@ public: private: /** Defines the SNIP response fields. */ static const SimpleInfoDescriptor SNIP_RESPONSE[]; + /** Defines the SNIP response fields without dynamic file. */ + static const SimpleInfoDescriptor SNIP_STATIC_RESPONSE[]; Node* node_; SimpleInfoFlow *responseFlow_; 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/SimpleNodeInfoMockUserFile.cxx b/src/openlcb/SimpleNodeInfoMockUserFile.cxx index f8a48b951..32c11e537 100644 --- a/src/openlcb/SimpleNodeInfoMockUserFile.cxx +++ b/src/openlcb/SimpleNodeInfoMockUserFile.cxx @@ -37,7 +37,9 @@ #define _POSIX_C_SOURCE 200112L #endif -#include "SimpleNodeInfoMockUserFile.hxx" +#include "openlcb/SimpleNodeInfoMockUserFile.hxx" + +#include "utils/format_utils.hxx" #ifdef __FreeRTOS__ openlcb::MockSNIPUserFile::MockSNIPUserFile(const char *user_name, @@ -45,9 +47,8 @@ openlcb::MockSNIPUserFile::MockSNIPUserFile(const char *user_name, : snipData_{2} , userFile_(MockSNIPUserFile::snip_user_file_path, &snipData_, false) { - strncpy(snipData_.user_name, user_name, sizeof(snipData_.user_name)); - strncpy(snipData_.user_description, user_description, - sizeof(snipData_.user_description)); + str_populate(snipData_.user_name, user_name); + str_populate(snipData_.user_description, user_description); } openlcb::MockSNIPUserFile::~MockSNIPUserFile() @@ -63,8 +64,7 @@ openlcb::MockSNIPUserFile::MockSNIPUserFile(const char *user_name, { init_snip_user_file(userFile_.fd(), user_name, user_description); HASSERT(userFile_.name().size() < sizeof(snip_user_file_path)); - strncpy(snip_user_file_path, userFile_.name().c_str(), - sizeof(snip_user_file_path)); + str_populate(snip_user_file_path, userFile_.name().c_str()); } char openlcb::MockSNIPUserFile::snip_user_file_path[128] = "/dev/zero"; diff --git a/src/openlcb/SimpleNodeInfoResponse.cxx b/src/openlcb/SimpleNodeInfoResponse.cxx new file mode 100644 index 000000000..84ab4782a --- /dev/null +++ b/src/openlcb/SimpleNodeInfoResponse.cxx @@ -0,0 +1,74 @@ +/** \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} +}; + +const SimpleInfoDescriptor SNIPHandler::SNIP_STATIC_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}, + {SimpleInfoDescriptor::LITERAL_BYTE, 2, 0, nullptr}, + {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, + {SimpleInfoDescriptor::LITERAL_BYTE, 0, 0, nullptr}, + {SimpleInfoDescriptor::END_OF_DATA, 0, 0, 0} +}; + +} + diff --git a/src/openlcb/SimpleStack.cxx b/src/openlcb/SimpleStack.cxx index 49890ac99..0a187635a 100644 --- a/src/openlcb/SimpleStack.cxx +++ b/src/openlcb/SimpleStack.cxx @@ -41,11 +41,6 @@ #include #include /* tc* functions */ #endif -#if defined(__linux__) -#include "utils/HubDeviceSelect.hxx" -#include -#include -#endif #include #include @@ -56,6 +51,9 @@ #include "openlcb/EventHandler.hxx" #include "openlcb/NodeInitializeFlow.hxx" #include "openlcb/SimpleNodeInfo.hxx" +#include "openmrn_features.h" +#include "utils/HubDeviceSelect.hxx" +#include "utils/SocketCan.hxx" namespace openlcb { @@ -103,12 +101,17 @@ SimpleTcpStack::SimpleTcpStack(const openlcb::NodeID node_id) void SimpleStackBase::start_stack(bool delay_start) { -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD // Opens the eeprom file and sends configuration update commands to all - // listeners. - configUpdateFlow_.open_file(CONFIG_FILENAME); + // listeners. We must only call ConfigUpdateFlow::open_file() once and it + // may have been done by an earlier call to create_config_file_if_needed() + // or check_version_and_factory_reset(). + if (configUpdateFlow_.get_fd() < 0) + { + configUpdateFlow_.open_file(CONFIG_FILENAME); + } configUpdateFlow_.init_flow(); -#endif // NOT ARDUINO, YES ESP32 +#endif // have posix fd if (!delay_start) { @@ -138,15 +141,16 @@ void SimpleStackBase::default_start_node() node(), MemoryConfigDefs::SPACE_ACDI_SYS, space); additionalComponents_.emplace_back(space); } -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD + if (SNIP_DYNAMIC_FILENAME != nullptr) { auto *space = new FileMemorySpace( - SNIP_DYNAMIC_FILENAME, sizeof(SimpleNodeDynamicValues)); + configUpdateFlow_.get_fd(), sizeof(SimpleNodeDynamicValues)); memoryConfigHandler_.registry()->insert( node(), MemoryConfigDefs::SPACE_ACDI_USR, space); additionalComponents_.emplace_back(space); } -#endif // NOT ARDUINO, YES ESP32 +#endif // OPENMRN_HAVE_POSIX_FD size_t cdi_size = strlen(CDI_DATA); if (cdi_size > 0) { @@ -156,15 +160,16 @@ void SimpleStackBase::default_start_node() node(), MemoryConfigDefs::SPACE_CDI, space); additionalComponents_.emplace_back(space); } -#if (!defined(ARDUINO)) || defined(ESP32) +#if OPENMRN_HAVE_POSIX_FD if (CONFIG_FILENAME != nullptr) { - auto *space = new FileMemorySpace(CONFIG_FILENAME, CONFIG_FILE_SIZE); + auto *space = + new FileMemorySpace(configUpdateFlow_.get_fd(), CONFIG_FILE_SIZE); memory_config_handler()->registry()->insert( node(), openlcb::MemoryConfigDefs::SPACE_CONFIG, space); additionalComponents_.emplace_back(space); } -#endif // NOT ARDUINO, YES ESP32 +#endif // OPENMRN_HAVE_POSIX_FD } SimpleTrainCanStack::SimpleTrainCanStack( @@ -201,16 +206,6 @@ void SimpleCanStackBase::start_iface(bool restart) if_can()->alias_allocator()->reinit_seed(); if_can()->local_aliases()->clear(); if_can()->remote_aliases()->clear(); - // Deletes all reserved aliases from the queue. - while (!if_can()->alias_allocator()->reserved_aliases()->empty()) - { - Buffer *a = static_cast *>( - if_can()->alias_allocator()->reserved_aliases()->next().item); - if (a) - { - a->unref(); - } - } } // Bootstraps the fresh alias allocation process. @@ -232,6 +227,7 @@ int SimpleStackBase::create_config_file_if_needed(const InternalConfigData &cfg, uint16_t expected_version, unsigned file_size) { HASSERT(CONFIG_FILENAME); + HASSERT(configUpdateFlow_.get_fd() < 0); struct stat statbuf; bool reset = false; bool extend = false; @@ -330,7 +326,12 @@ int SimpleStackBase::check_version_and_factory_reset( const InternalConfigData &cfg, uint16_t expected_version, bool force) { HASSERT(CONFIG_FILENAME); - int fd = configUpdateFlow_.open_file(CONFIG_FILENAME); + int fd = configUpdateFlow_.get_fd(); + if (fd < 0) + { + fd = configUpdateFlow_.open_file(CONFIG_FILENAME); + } + if (cfg.version().read(fd) != expected_version) { /// @todo (balazs.racz): We need to clear the eeprom. Best would be if @@ -418,35 +419,12 @@ void SimpleCanStackBase::add_gridconnect_tty( void SimpleCanStackBase::add_socketcan_port_select( const char *device, int loopback) { - int s; - struct sockaddr_can addr; - struct ifreq ifr; - - s = socket(PF_CAN, SOCK_RAW, CAN_RAW); - - // Set the blocking limit to the minimum allowed, typically 1024 in Linux - int sndbuf = 0; - setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)); - - // turn on/off loopback - setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback)); - - // setup error notifications - can_err_mask_t err_mask = CAN_ERR_TX_TIMEOUT | CAN_ERR_LOSTARB | - CAN_ERR_CRTL | CAN_ERR_PROT | CAN_ERR_TRX | CAN_ERR_ACK | - CAN_ERR_BUSOFF | CAN_ERR_BUSERROR | CAN_ERR_RESTARTED; - setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask)); - strcpy(ifr.ifr_name, device); - - ::ioctl(s, SIOCGIFINDEX, &ifr); - - addr.can_family = AF_CAN; - addr.can_ifindex = ifr.ifr_ifindex; - - bind(s, (struct sockaddr *)&addr, sizeof(addr)); - - auto *port = new HubDeviceSelect(can_hub(), s); - additionalComponents_.emplace_back(port); + int s = socketcan_open(device, loopback); + if (s >= 0) + { + auto *port = new HubDeviceSelect(can_hub(), s); + additionalComponents_.emplace_back(port); + } } #endif extern Pool *const __attribute__((__weak__)) g_incoming_datagram_allocator = diff --git a/src/openlcb/SimpleStack.hxx b/src/openlcb/SimpleStack.hxx index 5adb6a2b5..fe37aa76e 100644 --- a/src/openlcb/SimpleStack.hxx +++ b/src/openlcb/SimpleStack.hxx @@ -54,6 +54,7 @@ #include "openlcb/SimpleNodeInfo.hxx" #include "openlcb/TractionTrain.hxx" #include "openlcb/TrainInterface.hxx" +#include "utils/ActivityLed.hxx" #include "utils/GcTcpHub.hxx" #include "utils/GridConnectHub.hxx" #include "utils/HubDevice.hxx" @@ -153,6 +154,17 @@ public: return &configUpdateFlow_; } + /// Adds an activiy LED which will be flashed every time a message is sent + /// from this node to the network. + /// @param gpio LED that will be flashed on for each packet. + /// @param period defines in nanosecond the time to spend between updates. + void set_tx_activity_led( + const Gpio *led, long long period = MSEC_TO_NSEC(33)) + { + auto *al = new ActivityLed(iface(), led, period); + iface()->set_tx_hook(std::bind(&ActivityLed::activity, al)); + } + /// Reinitializes the node. Useful to call after the connection has flapped /// (gone down and up). void restart_stack(); @@ -371,8 +383,15 @@ public: { /// @TODO (balazs.racz) make this more efficient by rendering to string /// only once for all connections. - /// @TODO (balazs.racz) do not leak this. - new GcTcpHub(can_hub(), port); + gcHubServer_.reset(new GcTcpHub(can_hub(), port)); + } + + /// Retrieve the instance of the GridConnect Hub server, which was started + /// with start_tcp_hub_server(). + /// @return the TCP hub server, or nullptr if no server was ever started. + GcTcpHub *get_tcp_hub_server() + { + return gcHubServer_.get(); } /// Connects to a CAN hub using TCP with the gridconnect protocol. @@ -461,6 +480,9 @@ private: /// the CAN interface to function. Will be called exactly once by the /// constructor of the base class. std::unique_ptr create_if(const openlcb::NodeID node_id); + + /// Holds the ownership of the TCP hub server (if one was created). + std::unique_ptr gcHubServer_; }; class SimpleTcpStackBase : public SimpleStackBase diff --git a/src/openlcb/TractionConsist.cxxtest b/src/openlcb/TractionConsist.cxxtest index 92c764435..e4c77394d 100644 --- a/src/openlcb/TractionConsist.cxxtest +++ b/src/openlcb/TractionConsist.cxxtest @@ -1,60 +1,118 @@ #include "utils/async_traction_test_helper.hxx" -#include "openlcb/TractionTrain.hxx" #include "openlcb/TractionTestTrain.hxx" #include "openlcb/TractionThrottle.hxx" +#include "openlcb/TractionTrain.hxx" namespace openlcb { -static constexpr NodeID nodeIdLead = 0x060100000000 | 1371; -static constexpr NodeID nodeIdC1 = 0x060100000000 | 1372; -static constexpr NodeID nodeIdC2 = 0x060100000000 | 1373; +static constexpr NodeID nodeIdLead = 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 +{ +public: + INHERIT_CONSTRUCTOR(TrainNodeWithMockPolicy, TrainNodeForProxy); + + MOCK_METHOD5(function_policy, + bool(NodeHandle src, uint8_t command_byte, uint32_t fnum, + uint16_t value, Notifiable *done)); +}; -class ConsistTest : public TractionTest { +class ConsistTest : public TractionTest +{ protected: - ConsistTest() { - create_allocated_alias(); + ConsistTest() + { run_x([this]() { - otherIf_.local_aliases()->add(nodeIdLead, 0x771); - otherIf_.local_aliases()->add(nodeIdC1, 0x772); - otherIf_.local_aliases()->add(nodeIdC2, 0x773); + otherIf_.local_aliases()->add(nodeIdLead, 0x770); + otherIf_.local_aliases()->add(nodeIdC1, 0x771); + otherIf_.local_aliases()->add(nodeIdC2, 0x772); + otherIf_.local_aliases()->add(nodeIdC3, 0x773); + otherIf_.remote_aliases()->add(nodeIdC4, 0x774); + otherIf_.remote_aliases()->add(nodeIdC5, 0x775); }); - nodeLead_.reset(new TrainNodeForProxy(&trainService_, &trainLead_)); - nodeC1_.reset(new TrainNodeForProxy(&trainService_, &trainC1_)); - nodeC2_.reset(new TrainNodeForProxy(&trainService_, &trainC2_)); + nodeLead_.reset(new StrictMock( + &trainService_, &trainLead_)); + nodeC1_.reset( + new StrictMock(&trainService_, &trainC1_)); + nodeC2_.reset( + new StrictMock(&trainService_, &trainC2_)); + nodeC3_.reset( + new StrictMock(&trainService_, &trainC3_)); + wait(); + auto b = invoke_flow(&throttle_, TractionThrottleCommands::ASSIGN_TRAIN, + nodeIdLead, false); + EXPECT_EQ(0, b->data()->resultCode); wait(); } - TractionThrottle throttle_{node_}; + /// Sets up a star shaped consist. + void create_consist() + { + auto b = invoke_flow( + &throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC1, 0); + ASSERT_EQ(0, b->data()->resultCode); + b = invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, + nodeIdC2, + TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0); + ASSERT_EQ(0, b->data()->resultCode); + b = invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, + nodeIdC3, + TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0 | + TractionDefs::CNSTFLAGS_LINKFN); + ASSERT_EQ(0, b->data()->resultCode); + // reverse link + nodeC3_->add_consist(nodeIdLead, + TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0 | + TractionDefs::CNSTFLAGS_LINKFN); + wait(); + } - IfCan otherIf_{&g_executor, &can_hub0, 5, 5, 5}; - TrainService trainService_{&otherIf_}; + void inject_default_true_policy(TrainNodeWithMockPolicy *tn) + { + EXPECT_CALL(*tn, function_policy(_, _, _, _, _)) + .WillRepeatedly( + DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(true))); + } - LoggingTrain trainLead_{1371}; - LoggingTrain trainC1_{1372}; - LoggingTrain trainC2_{1373}; - std::unique_ptr nodeLead_; - std::unique_ptr nodeC1_; - std::unique_ptr nodeC2_; -}; + /// Sets up a default "true" function policy for all train nodes. + void inject_default_policy() + { + inject_default_true_policy(nodeLead_.get()); + inject_default_true_policy(nodeC1_.get()); + inject_default_true_policy(nodeC2_.get()); + inject_default_true_policy(nodeC3_.get()); + } -TEST_F(ConsistTest, CreateDestroy) { -} + TractionThrottle throttle_ {node_}; -TEST_F(ConsistTest, CreateAndRunConsist) { - auto b = invoke_flow( - &throttle_, TractionThrottleCommands::ASSIGN_TRAIN, nodeIdLead, false); - ASSERT_EQ(0, b->data()->resultCode); - wait(); + IfCan otherIf_ {&g_executor, &can_hub0, 5, 5, 5}; + TrainService trainService_ {&otherIf_}; - b = invoke_flow( - &throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC1, 0); - ASSERT_EQ(0, b->data()->resultCode); - b = invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC2, - TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0); - ASSERT_EQ(0, b->data()->resultCode); + LoggingTrain trainLead_ {1370}; + LoggingTrain trainC1_ {1371}; + LoggingTrain trainC2_ {1372}; + LoggingTrain trainC3_ {1373}; + std::unique_ptr nodeLead_; + std::unique_ptr nodeC1_; + std::unique_ptr nodeC2_; + std::unique_ptr nodeC3_; +}; +TEST_F(ConsistTest, CreateDestroy) +{ +} + +TEST_F(ConsistTest, CreateAndRunConsist) +{ + create_consist(); + inject_default_policy(); Velocity v; v.set_mph(37.5); throttle_.set_speed(v); @@ -78,9 +136,140 @@ TEST_F(ConsistTest, CreateAndRunConsist) { EXPECT_EQ(Velocity::REVERSE, trainLead_.get_speed().direction()); EXPECT_EQ(Velocity::REVERSE, trainC1_.get_speed().direction()); EXPECT_EQ(Velocity::FORWARD, trainC2_.get_speed().direction()); + + EXPECT_FALSE(trainLead_.get_fn(0)); + EXPECT_FALSE(trainLead_.get_fn(2)); + EXPECT_FALSE(trainC1_.get_fn(0)); + EXPECT_FALSE(trainC1_.get_fn(2)); + EXPECT_FALSE(trainC2_.get_fn(0)); + EXPECT_FALSE(trainC2_.get_fn(2)); + EXPECT_FALSE(trainC3_.get_fn(0)); + EXPECT_FALSE(trainC3_.get_fn(2)); + + throttle_.set_fn(0, 1); + wait(); + + // F0 forwarded to C2 and C3, not to C1. + EXPECT_TRUE(trainLead_.get_fn(0)); + EXPECT_FALSE(trainC1_.get_fn(0)); + EXPECT_TRUE(trainC2_.get_fn(0)); + EXPECT_TRUE(trainC3_.get_fn(0)); + + throttle_.set_fn(2, 1); + wait(); + + // F2 forwarded to C3, not to C1 and C2. + EXPECT_TRUE(trainLead_.get_fn(2)); + EXPECT_FALSE(trainC1_.get_fn(2)); + EXPECT_FALSE(trainC2_.get_fn(2)); + EXPECT_TRUE(trainC3_.get_fn(2)); } +TEST_F(ConsistTest, ListenerExpectations) +{ + inject_default_policy(); + auto b = + invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC4, + TractionDefs::CNSTFLAGS_REVERSE | TractionDefs::CNSTFLAGS_LINKF0 | + TractionDefs::CNSTFLAGS_LINKFN); + ASSERT_EQ(0, b->data()->resultCode); + b = invoke_flow(&throttle_, TractionThrottleCommands::CONSIST_ADD, nodeIdC5, + TractionDefs::CNSTFLAGS_LINKF0 | TractionDefs::CNSTFLAGS_LINKFN); + ASSERT_EQ(0, b->data()->resultCode); + + clear_expect(true); + Velocity v; + v.set_mph(37.5); + + // Throttle to lead + expect_packet(":X195EB22AN0770004C31;"); + // Lead to follower with listening flag and reversed value + expect_packet(":X195EB770N077480CC31;"); + // Lead to follower with listening flag and regular value + expect_packet(":X195EB770N0775804C31;"); + throttle_.set_speed(v); + wait(); + + // Throttle to lead + expect_packet(":X195EB22AN0770010000020001;"); + // Lead to follower with listening flag + expect_packet(":X195EB770N0774810000020001;"); + // Lead to follower with listening flag + expect_packet(":X195EB770N0775810000020001;"); + throttle_.set_fn(2, 1); + wait(); +} + +TEST_F(ConsistTest, FunctionPolicy) +{ + create_consist(); + inject_default_true_policy(nodeLead_.get()); + inject_default_true_policy(nodeC1_.get()); + inject_default_true_policy(nodeC2_.get()); + + Notifiable *done = nullptr; + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::id, nodeIdLead), 0x81, 3, 1, _)) + .WillOnce(DoAll(SaveArg<4>(&done), Return(true))); + throttle_.set_fn(3, 1); + wait(); + EXPECT_TRUE(trainLead_.get_fn(3)); + EXPECT_FALSE(trainC1_.get_fn(3)); + EXPECT_FALSE(trainC2_.get_fn(3)); + EXPECT_FALSE(trainC3_.get_fn(3)); + ASSERT_TRUE(done); + + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::id, nodeIdLead), 0x81, 3, 1, _)) + .WillOnce(DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(true))); + + done->notify(); + wait(); + EXPECT_TRUE(trainC3_.get_fn(3)); + // rejected by policy + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::id, nodeIdLead), 0x81, 4, 1, _)) + .WillOnce( + DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(false))); + + throttle_.set_fn(4, 1); + wait(); + EXPECT_TRUE(trainLead_.get_fn(4)); + EXPECT_FALSE(trainC1_.get_fn(4)); + EXPECT_FALSE(trainC2_.get_fn(4)); + EXPECT_FALSE(trainC3_.get_fn(4)); + + // Switch cab to C3. + auto b = invoke_flow( + &throttle_, TractionThrottleCommands::ASSIGN_TRAIN, nodeIdC3, false); + EXPECT_EQ(0, b->data()->resultCode); + + // Different policy now + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::alias, 0x22A), 0x01, 5, 1, _)) + .WillOnce(DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(true))); + + throttle_.set_fn(5, 1); + wait(); + EXPECT_TRUE(trainLead_.get_fn(5)); + EXPECT_FALSE(trainC1_.get_fn(5)); + EXPECT_FALSE(trainC2_.get_fn(5)); + EXPECT_TRUE(trainC3_.get_fn(5)); + + // Local policy false should not prevent remote propagation. + EXPECT_CALL(*nodeC3_, + function_policy(Field(&NodeHandle::alias, 0x22A), 0x01, 0, 1, _)) + .WillOnce( + DoAll(WithArg<4>(Invoke(&InvokeNotification)), Return(false))); + + throttle_.set_fn(0, 1); + wait(); + EXPECT_TRUE(trainLead_.get_fn(0)); + EXPECT_FALSE(trainC1_.get_fn(0)); // no link F0 + EXPECT_TRUE(trainC2_.get_fn(0)); + EXPECT_FALSE(trainC3_.get_fn(0)); // no policy +} } // namespace openlcb diff --git a/src/openlcb/TractionCvSpace.cxx b/src/openlcb/TractionCvSpace.cxx index 53d5c693f..db4ec9fb1 100644 --- a/src/openlcb/TractionCvSpace.cxx +++ b/src/openlcb/TractionCvSpace.cxx @@ -52,7 +52,7 @@ namespace openlcb { TractionCvSpace::TractionCvSpace(MemoryConfigHandler *parent, - dcc::PacketFlowInterface *track, + dcc::TrackIf *track, dcc::RailcomHubFlow *railcom_hub, uint8_t space_id) : StateFlowBase(parent->service()) @@ -83,23 +83,37 @@ bool TractionCvSpace::set_node(Node *node) return false; } NodeID id = node->node_id(); - /* NOTE(balazs.racz): It is difficult to figure out the DCC address given - * an abstract NMRAnet node. Here we hardcode the reserved DCC node ID - * space, which is not a great solution. */ - if ((id & 0xFFFF00000000ULL) == TractionDefs::NODE_ID_DCC) + if ((id & 0xFFFF00000000ULL) != TractionDefs::NODE_ID_DCC) { - uint16_t new_address = id & 0xFFFFU; - if (dccAddress_ != new_address) - { - dccAddress_ = new_address; - errorCode_ = ERROR_NOOP; - } - return true; + currId_ = 0; + return false; } - else + if ((id & 0xFFFF) == currId_) { + return true; + } + currId_ = (id & 0xFFFF); + dcc::TrainAddressType type; + uint32_t addr; + if (!TractionDefs::legacy_address_from_train_node_id(id, &type, &addr)) { + currId_ = 0; return false; } + switch(type) { + case dcc::TrainAddressType::DCC_LONG_ADDRESS: + dccAddressNum_ = addr; + dccIsLong_ = true; + break; + case dcc::TrainAddressType::DCC_SHORT_ADDRESS: + dccAddressNum_ = addr; + dccIsLong_ = false; + break; + default: + currId_ = 0; + return false; + } + errorCode_ = ERROR_NOOP; + return true; } const unsigned TractionCvSpace::MAX_CV; @@ -108,7 +122,7 @@ size_t TractionCvSpace::read(const address_t source, uint8_t *dst, size_t len, errorcode_t *error, Notifiable *again) { if (source == OFFSET_CV_INDEX) { - lastIndexedNode_ = dccAddress_; + lastIndexedNode_ = currId_; uint8_t* lastcv = (uint8_t*)&lastIndexedCv_; if (len > 0) dst[0] = lastcv[3]; if (len > 1) dst[1] = lastcv[2]; @@ -119,7 +133,7 @@ size_t TractionCvSpace::read(const address_t source, uint8_t *dst, size_t len, uint32_t cv = -1; if (source == OFFSET_CV_VALUE || source == OFFSET_CV_VERIFY_RESULT) { - if (dccAddress_ != lastIndexedNode_) + if (currId_ != lastIndexedNode_) { *error = Defs::ERROR_PERMANENT; return 0; @@ -278,20 +292,20 @@ StateFlowBase::Action TractionCvSpace::fill_read1_packet() { auto *b = get_allocation_result(track_); b->data()->start_dcc_packet(); - /** @TODO(balazs.racz) here we make bad assumptions about how to decide - * between long and short addresses */ - if (dccAddress_ >= 0x80) + if (dccIsLong_) { - b->data()->add_dcc_address(dcc::DccLongAddress(dccAddress_)); + b->data()->add_dcc_address(dcc::DccLongAddress(dccAddressNum_)); } else { - b->data()->add_dcc_address(dcc::DccShortAddress(dccAddress_)); + b->data()->add_dcc_address(dcc::DccShortAddress(dccAddressNum_)); } b->data()->add_dcc_pom_read1(cvNumber_); b->data()->feedback_key = reinterpret_cast(this); - railcomHub_->register_port(this); + // We proactively repeat read packets twice. + b->data()->packet_header.rept_count = 1; errorCode_ = ERROR_PENDING; + railcomHub_->register_port(this); track_->send(b); return sleep_and_call(&timer_, MSEC_TO_NSEC(500), STATE(read_returned)); } @@ -328,7 +342,7 @@ size_t TractionCvSpace::write(address_t destination, const uint8_t *src, size_t len, errorcode_t *error, Notifiable *again) { if (destination == OFFSET_CV_INDEX) { - lastIndexedNode_ = dccAddress_; + lastIndexedNode_ = currId_; uint8_t* lastcv = (uint8_t*)&lastIndexedCv_; if (len > 0) lastcv[3] = src[0]; if (len > 1) lastcv[2] = src[1]; @@ -342,7 +356,7 @@ size_t TractionCvSpace::write(address_t destination, const uint8_t *src, return 1; } if (destination == OFFSET_CV_VALUE) { - if (dccAddress_ != lastIndexedNode_) { + if (currId_ != lastIndexedNode_) { *error = Defs::ERROR_TEMPORARY; return 0; } @@ -383,15 +397,13 @@ StateFlowBase::Action TractionCvSpace::fill_write1_packet() { auto *b = get_allocation_result(track_); b->data()->start_dcc_packet(); - /** @todo(balazs.racz) here we make bad assumptions about how to decide - * between long and short addresses */ - if (dccAddress_ >= 0x80) + if (dccIsLong_) { - b->data()->add_dcc_address(dcc::DccLongAddress(dccAddress_)); + b->data()->add_dcc_address(dcc::DccLongAddress(dccAddressNum_)); } else { - b->data()->add_dcc_address(dcc::DccShortAddress(dccAddress_)); + b->data()->add_dcc_address(dcc::DccShortAddress(dccAddressNum_)); } b->data()->add_dcc_pom_write1(cvNumber_, cvData_); b->data()->feedback_key = reinterpret_cast(this); @@ -399,8 +411,8 @@ StateFlowBase::Action TractionCvSpace::fill_write1_packet() // packets by the standard. We make 4 back to back packets and that // fulfills the requirement. b->data()->packet_header.rept_count = 3; - railcomHub_->register_port(this); errorCode_ = ERROR_PENDING; + railcomHub_->register_port(this); track_->send(b); return sleep_and_call(&timer_, MSEC_TO_NSEC(500), STATE(write_returned)); } @@ -483,12 +495,15 @@ void TractionCvSpace::send(Buffer *b, unsigned priority) break; case dcc::RailcomPacket::ACK: if (new_status == ERROR_PENDING) { - new_status = ERROR_OK; + // Ack should not change the state machine of CV reads or + // writes. Both of those need to return explicitly with a + // MOB_POM. } break; case dcc::RailcomPacket::GARBAGE: if (new_status == ERROR_PENDING) { - new_status = ERROR_GARBAGE; + // If we got garbage, we stay in pending state which will + // re-send the command again. } break; case dcc::RailcomPacket::MOB_POM: @@ -496,12 +511,14 @@ void TractionCvSpace::send(Buffer *b, unsigned priority) new_status = ERROR_OK; break; default: - if (new_status == ERROR_PENDING) { - new_status = ERROR_UNKNOWN_RESPONSE; - } break; } } + if (new_status == ERROR_PENDING) + { + // Do not record status if it is still pending. + return; + } return record_railcom_status(new_status); } diff --git a/src/openlcb/TractionCvSpace.cxxtest b/src/openlcb/TractionCvSpace.cxxtest index 17e322c58..d613207bc 100644 --- a/src/openlcb/TractionCvSpace.cxxtest +++ b/src/openlcb/TractionCvSpace.cxxtest @@ -1,10 +1,11 @@ -#include "utils/async_traction_test_helper.hxx" -#include "openlcb/TractionTestTrain.hxx" #include "openlcb/TractionCvSpace.hxx" -#include "openlcb/MemoryConfig.hxx" -#include "openlcb/DatagramCan.hxx" -#include "dcc/PacketFlowInterface.hxx" + #include "dcc/RailcomHub.hxx" +#include "dcc/TrackIf.hxx" +#include "openlcb/DatagramCan.hxx" +#include "openlcb/MemoryConfig.hxx" +#include "openlcb/TractionTestTrain.hxx" +#include "utils/async_traction_test_helper.hxx" using ::testing::ElementsAre; @@ -13,29 +14,15 @@ namespace openlcb extern Pool *const g_incoming_datagram_allocator = mainBufferPool; -class MockTrackIf : public dcc::PacketFlowInterface -{ -public: - MOCK_METHOD2(packet, - void(const vector &payload, uintptr_t feedback_key)); - void send(Buffer *b, unsigned prio) OVERRIDE - { - vector payload; - payload.assign(b->data()->payload, - b->data()->payload + b->data()->dlc - 1); - this->packet(payload, b->data()->feedback_key); - } -}; - class TractionCvTestBase : public TractionTest { protected: TractionCvTestBase() { run_x([this]() { - ifCan_->local_aliases()->add(0x0601000000AFULL, 0x272U); + ifCan_->local_aliases()->add(0x06010000C0AFULL, 0x272U); }); - expect_packet(":X19100272N0601000000AF;"); + expect_packet(":X19100272N06010000C0AF;"); } ~TractionCvTestBase() @@ -50,7 +37,7 @@ protected: enum { TRAIN_NODE_ALIAS = 0x272, - TRAIN_NODE_ID = 0x0601000000AF + TRAIN_NODE_ID = 0x06010000C0AF }; TractionCvTest() @@ -83,7 +70,7 @@ protected: CanDatagramService datagram_support_{ifCan_.get(), 10, 2}; MemoryConfigHandler memory_config_handler_{&datagram_support_, nullptr, 3}; dcc::RailcomHubFlow railcom_hub_{&g_service}; - StrictMock track_if_; + StrictMock track_if_; TractionCvSpace cv_space_{&memory_config_handler_, &track_if_, &railcom_hub_, 0xEF}; }; @@ -114,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(); @@ -148,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(); diff --git a/src/openlcb/TractionCvSpace.hxx b/src/openlcb/TractionCvSpace.hxx index 656512a99..b2b4f46c1 100644 --- a/src/openlcb/TractionCvSpace.hxx +++ b/src/openlcb/TractionCvSpace.hxx @@ -36,11 +36,11 @@ #ifndef _OPENLCB_TRACTIONCVSPACE_HXX_ #define _OPENLCB_TRACTIONCVSPACE_HXX_ -#include "openlcb/MemoryConfig.hxx" -#include "executor/StateFlow.hxx" -#include "dcc/PacketFlowInterface.hxx" #include "dcc/RailCom.hxx" #include "dcc/RailcomHub.hxx" +#include "dcc/TrackIf.hxx" +#include "executor/StateFlow.hxx" +#include "openlcb/MemoryConfig.hxx" namespace openlcb { @@ -68,9 +68,8 @@ class TractionCvSpace : private MemorySpace, public StateFlowBase { public: - TractionCvSpace(MemoryConfigHandler *parent, - dcc::PacketFlowInterface *track, - dcc::RailcomHubFlow *railcom_hub, uint8_t space_id); + TractionCvSpace(MemoryConfigHandler *parent, dcc::TrackIf *track, + dcc::RailcomHubFlow *railcom_hub, uint8_t space_id); ~TractionCvSpace(); @@ -117,11 +116,18 @@ private: void record_railcom_status(unsigned code); MemoryConfigHandler *parent_; - dcc::PacketFlowInterface *track_; + dcc::TrackIf *track_; dcc::RailcomHubFlow *railcomHub_; - uint16_t dccAddress_; + /// Last accepted DCC locomotive node ID (bottom 16 bits). + uint16_t currId_; + /// Numberic value of last used DCC address. + uint16_t dccAddressNum_ : 14; + /// 1 for long address, 0 for short address. + uint16_t dccIsLong_ : 1; + /// CV to read (0 to 1023). uint16_t cvNumber_; - uint8_t cvData_; //< data to read or write. + /// data read or data to write + uint8_t cvData_; uint8_t errorCode_ : 4; uint8_t numTry_ : 4; enum diff --git a/src/openlcb/TractionDefs.hxx b/src/openlcb/TractionDefs.hxx index 0d05b9f5a..2ab6dbdb6 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. @@ -84,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. @@ -107,6 +102,12 @@ struct TractionDefs { REQ_CONSIST_CONFIG = 0x30, REQ_TRACTION_MGMT = 0x40, + /// Mask to apply to the command byte of the requests. + REQ_MASK = 0x7F, + /// Flag bit in the command byte set when a listener command is + /// forwarded. + REQ_LISTENER = 0x80, + // Byte 1 of REQ_CONTROLLER_CONFIG command CTRLREQ_ASSIGN_CONTROLLER = 0x01, CTRLREQ_RELEASE_CONTROLLER = 0x02, @@ -121,6 +122,7 @@ struct TractionDefs { // Byte 1 of REQ_TRACTION_MGMT command MGMTREQ_RESERVE = 0x01, MGMTREQ_RELEASE = 0x02, + MGMTREQ_NOOP = 0x03, // Byte 0 of response commands RESP_QUERY_SPEED = REQ_QUERY_SPEED, RESP_QUERY_FN = REQ_QUERY_FN, @@ -128,6 +130,9 @@ struct TractionDefs { RESP_CONSIST_CONFIG = REQ_CONSIST_CONFIG, RESP_TRACTION_MGMT = REQ_TRACTION_MGMT, + // Status byte of the Speed Query response + SPEEDRESP_STATUS_IS_ESTOP = 1, + // Byte 1 of Controller Configuration response CTRLRESP_ASSIGN_CONTROLLER = CTRLREQ_ASSIGN_CONTROLLER, CTRLRESP_QUERY_CONTROLLER = CTRLREQ_QUERY_CONTROLLER, @@ -150,6 +155,7 @@ struct TractionDefs { // Byte 1 of Traction Management replies MGMTRESP_RESERVE = MGMTREQ_RESERVE, + MGMTRESP_HEARTBEAT = 0x03, PROXYREQ_ALLOCATE = 0x01, PROXYREQ_ATTACH = 0x02, @@ -212,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: @@ -245,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; @@ -350,8 +350,12 @@ struct TractionDefs { /** Parses the response payload of a GET_SPEED packet. * @returns true if the last_set_speed value was present and non-NaN. * @param p is the response payload. - * @param v is the velocity that will be set to the speed value. */ - static bool speed_get_parse_last(const Payload &p, Velocity *v) + * @param v is the velocity that will be set to the speed value. + * @param is_estop if non-null, will be set to true if the train was last + * set to estop instead of a speed. + */ + static bool speed_get_parse_last( + const Payload &p, Velocity *v, bool *is_estop = nullptr) { if (p.size() < 3) { @@ -362,6 +366,14 @@ struct TractionDefs { { return false; } + if (is_estop) + { + if (p.size() < 4) + { + return false; + } + *is_estop = (p[3] & SPEEDRESP_STATUS_IS_ESTOP) != 0; + } return true; } @@ -501,6 +513,26 @@ struct TractionDefs { node_id_to_data(slave, &p[5]); return p; } + + /// Generates a Heartbeat Request, to be sent from the train node to the + /// controller. + static Payload heartbeat_request_payload(uint8_t deadline_sec = 3) + { + Payload p(3, 0); + p[0] = RESP_TRACTION_MGMT; + p[1] = MGMTRESP_HEARTBEAT; + p[2] = deadline_sec; + return p; + } + + /// Generates a Noop message, to be sent from the throttle to the train node. + static Payload noop_payload() + { + Payload p(2, 0); + p[0] = REQ_TRACTION_MGMT; + p[1] = MGMTREQ_NOOP; + return p; + } }; } // namespace openlcb diff --git a/src/openlcb/TractionTestTrain.cxx b/src/openlcb/TractionTestTrain.cxx index 8cd587e4c..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 @@ -86,7 +87,8 @@ void LoggingTrain::set_emergencystop() TractionDefs::train_node_name_from_legacy( legacyAddressType_, legacyAddress_) .c_str()); - estopActive_ = 0; + currentSpeed_.set_mph(0); // keeps sign + estopActive_ = true; } bool LoggingTrain::get_emergencystop() diff --git a/src/openlcb/TractionTestTrain.cxxtest b/src/openlcb/TractionTestTrain.cxxtest index e44566efc..3fce6d68a 100644 --- a/src/openlcb/TractionTestTrain.cxxtest +++ b/src/openlcb/TractionTestTrain.cxxtest @@ -44,11 +44,10 @@ protected: LoggingTrainTest() : trainImpl_(1732) { create_allocated_alias(); - expect_next_alias_allocation(); // 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 01695e960..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 { @@ -15,7 +15,6 @@ protected: ThrottleTest() { print_all_packets(); - create_allocated_alias(); run_x( [this]() { otherIf_.local_aliases()->add(TRAIN_NODE_ID, 0x771); }); trainNode_.reset(new TrainNodeForProxy(&trainService_, &trainImpl_)); @@ -226,6 +225,7 @@ TEST_F(ThrottleTest, SendQuery) EXPECT_EQ(0, flow.response()->resultCode); EXPECT_NEAR(13.2, flow.throttle_.get_speed().mph(), 0.1); + EXPECT_FALSE(flow.throttle_.get_emergencystop()); EXPECT_EQ(0, flow.throttle_.get_fn(0)); EXPECT_EQ(1, flow.throttle_.get_fn(1)); EXPECT_EQ(0, flow.throttle_.get_fn(2)); @@ -234,6 +234,15 @@ TEST_F(ThrottleTest, SendQuery) EXPECT_EQ(0, flow.throttle_.get_fn(5)); EXPECT_EQ(0, flow.throttle_.get_fn(6)); EXPECT_EQ(1, flow.throttle_.get_fn(7)); + + // Checks emergency stop load. + trainImpl_.set_emergencystop(); + flow.load(); + n_.wait_for_notification(); + wait(); + EXPECT_EQ(0, flow.response()->resultCode); + EXPECT_NEAR(0, flow.throttle_.get_speed().mph(), 0.1); + EXPECT_TRUE(flow.throttle_.get_emergencystop()); } class ThrottleClientTest : public ThrottleTest { @@ -459,6 +468,81 @@ TEST_F(ThrottleClientTest, ReassignWithoutListener) wait(); } +TEST_F(ThrottleClientTest, HeartbeatWithListener) +{ + auto b = invoke_flow(&throttle_, TractionThrottleCommands::ASSIGN_TRAIN, + TRAIN_NODE_ID, true); + ASSERT_EQ(0, b->data()->resultCode); + + // Primes the caches. + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_COMMAND, + NodeHandle(node_->node_id()), TractionDefs::fn_set_payload(13, 1)); + wait(); + EXPECT_EQ(1u, throttle_.get_fn(13)); + + clear_expect(true); // checks packets from now on + // heartbeat request is sent from train to throttle + expect_packet(":X191E9771N022A400303;"); + // throttle responds with NOOP. + expect_packet(":X195EB22AN07714003;"); + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_REPLY, + NodeHandle(node_->node_id()), + TractionDefs::heartbeat_request_payload()); + wait(); + clear_expect(true); +} + +TEST_F(ThrottleClientTest, HeartbeatNoListener) +{ + auto b = invoke_flow(&throttle_, TractionThrottleCommands::ASSIGN_TRAIN, + TRAIN_NODE_ID, false); + ASSERT_EQ(0, b->data()->resultCode); + + // Primes the caches. + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_COMMAND, + NodeHandle(node_->node_id()), TractionDefs::fn_set_payload(13, 1)); + wait(); + + clear_expect(true); // checks packets from now on + // heartbeat request is sent from train to throttle + expect_packet(":X191E9771N022A400303;"); + // throttle responds with NOOP. + expect_packet(":X195EB22AN07714003;"); + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_REPLY, + NodeHandle(node_->node_id()), + TractionDefs::heartbeat_request_payload()); + wait(); + clear_expect(true); +} + +TEST_F(ThrottleClientTest, HeartbeatWrongTrain) +{ + auto b = invoke_flow(&throttle_, TractionThrottleCommands::ASSIGN_TRAIN, + TRAIN_NODE_ID, false); + ASSERT_EQ(0, b->data()->resultCode); + + static auto TRAIN_NODE_ID2 = TRAIN_NODE_ID + 1; + run_x([this]() { otherIf_.local_aliases()->add(TRAIN_NODE_ID2, 0x772); }); + LoggingTrain train_impl2{1373}; + TrainNodeForProxy node2(&trainService_, &train_impl2); + wait(); + + // Primes the caches. + openlcb::send_message(trainNode_.get(), Defs::MTI_TRACTION_CONTROL_COMMAND, + NodeHandle(node_->node_id()), TractionDefs::fn_set_payload(13, 1)); + wait(); + + clear_expect(true); // checks packets from now on + // heartbeat request is sent from wrong train to throttle + expect_packet(":X191E9772N022A400303;"); + // no response from the throttle. + openlcb::send_message(&node2, Defs::MTI_TRACTION_CONTROL_REPLY, + NodeHandle(node_->node_id()), + TractionDefs::heartbeat_request_payload()); + wait(); + clear_expect(true); +} + class ListenerInterface { public: @@ -524,6 +608,23 @@ TEST_F(ThrottleClientTest, ListenerCallback) // send another speed command to verify that E-Stop gets cleared throttle_.set_speed(v); EXPECT_FALSE(throttle_.get_emergencystop()); + + // Go back to estop. + EXPECT_CALL(l, update(-1)); + EXPECT_FALSE(throttle_.get_emergencystop()); + send_packet(":X195EB330N077102;"); // E-Stop + wait(); + Mock::VerifyAndClear(&l); + EXPECT_TRUE(throttle_.get_emergencystop()); + + // Speed replies will also clear estop. + EXPECT_CALL(l, update(-1)); + send_packet(":X195EB330N07710045D0;"); // speed 13 mph + wait(); + Mock::VerifyAndClear(&l); + EXPECT_NEAR(13, throttle_.get_speed().mph(), 0.1); + EXPECT_EQ(Velocity::FORWARD, throttle_.get_speed().direction()); + EXPECT_FALSE(throttle_.get_emergencystop()); } } // namespace openlcb diff --git a/src/openlcb/TractionThrottle.hxx b/src/openlcb/TractionThrottle.hxx index 5ace6dcd9..f5d271bc8 100644 --- a/src/openlcb/TractionThrottle.hxx +++ b/src/openlcb/TractionThrottle.hxx @@ -35,183 +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: - virtual void toggle_fn(uint32_t fn) = 0; - - /// 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(); @@ -229,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, @@ -295,7 +134,15 @@ public: } set_fn(fn, fnstate); } - + + /// Sends out a function query command. The throttle listener will be + /// called when the response is available. + /// @param address function to query. + void query_fn(uint32_t address) override + { + send_traction_message(TractionDefs::fn_get_payload(address)); + } + uint32_t legacy_address() override { return 0; @@ -339,6 +186,15 @@ public: updateCallback_ = std::move(update_callback); } +#ifdef GTEST + void TEST_assign_listening_node(openlcb::NodeID dst) + { + dst_ = dst; + set_assigned(); + set_listening(); + } +#endif + private: Action entry() override { @@ -558,7 +414,9 @@ private: } } - void pending_reply_arrived() + /// Notifies that a pending query during load has gotten a reply. + /// @return true if we were in the load state. + bool pending_reply_arrived() { if (pendingQueries_ > 0) { @@ -566,12 +424,19 @@ private: { timer_.trigger(); } + return true; } + return false; } void speed_reply(Buffer *msg) { AutoReleaseBuffer rb(msg); + if (msg->data()->dstNode != node_) + { + // For a different throttle. + return; + } if (!iface()->matching_node(msg->data()->src, NodeHandle(dst_))) { return; @@ -583,26 +448,40 @@ private: { case TractionDefs::RESP_QUERY_SPEED: { - pending_reply_arrived(); + bool expected = pending_reply_arrived(); Velocity v; - if (TractionDefs::speed_get_parse_last(p, &v)) + bool is_estop; + if (TractionDefs::speed_get_parse_last(p, &v, &is_estop)) { lastSetSpeed_ = v; - /// @todo (balazs.racz): call a callback for the client. - - /// @todo (Stuart.Baker): Do we need to do anything with - /// estopActive_? + estopActive_ = is_estop; + if (updateCallback_ && !expected) + { + updateCallback_(-1); + } } return; } case TractionDefs::RESP_QUERY_FN: { - pending_reply_arrived(); + bool expected = pending_reply_arrived(); uint16_t v; unsigned num; if (TractionDefs::fn_get_parse(p, &v, &num)) { lastKnownFn_[num] = v; + if (updateCallback_ && !expected) + { + updateCallback_(num); + } + } + } + case TractionDefs::RESP_TRACTION_MGMT: + { + if (p.size() >= 2 && p[1] == TractionDefs::MGMTRESP_HEARTBEAT) + { + // Automatically responds to heartbeat requests. + send_traction_message(TractionDefs::noop_payload()); } } } @@ -718,6 +597,11 @@ private: void listen_reply(Buffer *msg) { AutoReleaseBuffer rb(msg); + if (msg->data()->dstNode != node_) + { + // For a different throttle. + return; + } if (!iface()->matching_node(msg->data()->src, NodeHandle(dst_))) { return; @@ -725,7 +609,7 @@ private: const Payload &p = msg->data()->payload; if (p.size() < 1) return; - switch (p[0]) + switch (p[0] & TractionDefs::REQ_MASK) { case TractionDefs::REQ_SET_SPEED: { diff --git a/src/openlcb/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/TractionTrain.cxx b/src/openlcb/TractionTrain.cxx index 0e2773b59..489610e0e 100644 --- a/src/openlcb/TractionTrain.cxx +++ b/src/openlcb/TractionTrain.cxx @@ -42,7 +42,7 @@ namespace openlcb { -TrainNode::TrainNode(TrainService *service, TrainImpl *train) +DefaultTrainNode::DefaultTrainNode(TrainService *service, TrainImpl *train) : service_(service) , train_(train) , isInitialized_(0) @@ -52,22 +52,46 @@ TrainNode::TrainNode(TrainService *service, TrainImpl *train) TrainNode::~TrainNode() { - while (!consistSlaves_.empty()) { +} + +TrainNodeWithConsist::~TrainNodeWithConsist() +{ + while (!consistSlaves_.empty()) + { delete consistSlaves_.pop_front(); } } +DefaultTrainNode::~DefaultTrainNode() +{ +} + TrainNodeForProxy::TrainNodeForProxy(TrainService *service, TrainImpl *train) - : TrainNode(service, train) { - service_->register_train(this); + : DefaultTrainNode(service, train) +{ + service->register_train(this); +} + +TrainNodeForProxy::~TrainNodeForProxy() +{ + /// @todo enable this line of code. It currently breaks unit tests due to + /// bugs + // service_->unregister_train(this); } TrainNodeWithId::TrainNodeWithId( TrainService *service, TrainImpl *train, NodeID node_id) - : TrainNode(service, train) + : DefaultTrainNode(service, train) , nodeId_(node_id) { - service_->register_train(this); + service->register_train(this); +} + +TrainNodeWithId::~TrainNodeWithId() +{ + /// @todo enable this line of code. It currently breaks unit tests due to + /// bugs + // service_->unregister_train(this); } NodeID TrainNodeForProxy::node_id() @@ -76,7 +100,7 @@ NodeID TrainNodeForProxy::node_id() train_->legacy_address_type(), train_->legacy_address()); } -If *TrainNode::iface() +If *DefaultTrainNode::iface() { return service_->iface(); } @@ -150,8 +174,7 @@ struct TrainService::Impl return release_and_exit(); } // Checks if destination is a local traction-enabled node. - if (trainService_->nodes_.find(train_node()) == - trainService_->nodes_.end()) + if (!trainService_->nodes_->is_node_registered(train_node())) { LOG(VERBOSE, "Traction message for node %p that is not " "traction enabled.", @@ -161,13 +184,14 @@ struct TrainService::Impl * send a reject response. */ return release_and_exit(); } + train_node()->command_hook(nmsg()->src, nmsg()->payload); // No command byte? if (size() < 1) { LOG(VERBOSE, "Traction message with no command byte."); return reject_permanent(); } - uint8_t cmd = payload()[0]; + uint8_t cmd = payload()[0] & TractionDefs::REQ_MASK; switch (cmd) { /** @TODO(balazs.racz) need to validate caller of mutating @@ -190,7 +214,24 @@ struct TrainService::Impl uint16_t value = payload()[4]; value <<= 8; value |= payload()[5]; - train_node()->train()->set_fn(address, value); + bn_.reset(this); + bool should_apply = + train_node()->function_policy(nmsg()->src, payload()[0], + address, value, bn_.new_child()); + // The function_policy call may have completed inline. We + // can inquire from the barrier. If it was not completed + // inline, we have to wait for the notification and re-try + // the call. + if (!bn_.abort_if_almost_done()) + { + // Not notified inline. + bn_.notify(); // consumes our share + return wait(); + } + if (should_apply) + { + train_node()->train()->set_fn(address, value); + } nextConsistIndex_ = 0; return call_immediately(STATE(maybe_forward_consist)); } @@ -240,7 +281,12 @@ struct TrainService::Impl uint8_t *d = reinterpret_cast(&(*p)[0]); d[0] = TractionDefs::RESP_QUERY_SPEED; speed_to_fp16(train_node()->train()->get_speed(), d + 1); - d[3] = 0; // status byte: reserved. + uint8_t status = 0; + if (train_node()->train()->get_emergencystop()) + { + status |= TractionDefs::SPEEDRESP_STATUS_IS_ESTOP; + } + d[3] = status; speed_to_fp16(train_node()->train()->get_commanded_speed(), d + 4); speed_to_fp16(train_node()->train()->get_actual_speed(), @@ -427,7 +473,7 @@ struct TrainService::Impl ++nextConsistIndex_; return again(); } - uint8_t cmd = payload()[0]; + uint8_t cmd = payload()[0] & TractionDefs::REQ_MASK; bool flip_speed = false; if (cmd == TractionDefs::REQ_SET_SPEED) { if (flags & TractionDefs::CNSTFLAGS_REVERSE) { @@ -463,6 +509,7 @@ struct TrainService::Impl if (flip_speed) { b->data()->payload[1] ^= 0x80; } + b->data()->payload[0] |= TractionDefs::REQ_LISTENER; iface()->addressed_message_write_flow()->send(b); return exit(); } @@ -489,8 +536,11 @@ struct TrainService::Impl } b->data()->reset(message()->data()->mti, train_node()->node_id(), NodeHandle(dst), message()->data()->payload); - if ((payload()[0] == TractionDefs::REQ_SET_SPEED) && - (flags & TractionDefs::CNSTFLAGS_REVERSE)) { + b->data()->payload[0] |= TractionDefs::REQ_LISTENER; + if (((payload()[0] & TractionDefs::REQ_MASK) == + TractionDefs::REQ_SET_SPEED) && + (flags & TractionDefs::CNSTFLAGS_REVERSE)) + { b->data()->payload[1] ^= 0x80; } iface()->addressed_message_write_flow()->send(b); @@ -526,8 +576,13 @@ struct TrainService::Impl reserved_ = 0; return release_and_exit(); } + case TractionDefs::MGMTREQ_NOOP: + { + // Nothing to do. + return release_and_exit(); + } default: - LOG(VERBOSE, "Unknown Traction proxy manage subcommand %x", + LOG(VERBOSE, "Unknown Traction management subcommand %x", cmd); return reject_permanent(); } @@ -600,14 +655,16 @@ struct TrainService::Impl unsigned reserved_ : 1; TrainService *trainService_; Buffer *response_; + BarrierNotifiable bn_; }; TractionRequestFlow traction_; }; -TrainService::TrainService(If *iface) +TrainService::TrainService(If *iface, NodeRegistry *train_node_registry) : Service(iface->executor()) , iface_(iface) + , nodes_(train_node_registry) { impl_ = new Impl(this); } @@ -623,9 +680,16 @@ void TrainService::register_train(TrainNode *node) extern void StartInitializationFlow(Node * node); StartInitializationFlow(node); AtomicHolder h(this); - nodes_.insert(node); + nodes_->register_node(node); LOG(VERBOSE, "Registered node %p for traction.", node); - HASSERT(nodes_.find(node) != nodes_.end()); +} + +void TrainService::unregister_train(TrainNode *node) +{ + HASSERT(nodes_->is_node_registered(node)); + iface_->delete_local_node(node); + AtomicHolder h(this); + nodes_->unregister_node(node); } } // namespace openlcb diff --git a/src/openlcb/TractionTrain.cxxtest b/src/openlcb/TractionTrain.cxxtest index 91a19e31d..c30cda516 100644 --- a/src/openlcb/TractionTrain.cxxtest +++ b/src/openlcb/TractionTrain.cxxtest @@ -95,16 +95,15 @@ protected: TractionSingleMockTest() { create_allocated_alias(); - expect_next_alias_allocation(); EXPECT_CALL(m1_, legacy_address()).Times(AtLeast(0)).WillRepeatedly( Return(0x00003456U)); EXPECT_CALL(m1_, legacy_address_type()) .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(); } @@ -113,7 +112,7 @@ protected: wait(); } - static const NodeID kTrainNodeID = 0x060100003456U; + static const NodeID kTrainNodeID = 0x06010000F456U; std::unique_ptr trainNode_; }; @@ -129,6 +128,7 @@ TEST_F(TractionSingleMockTest, SetSpeed) TEST_F(TractionSingleMockTest, GetSpeed) { EXPECT_CALL(m1_, get_speed()).WillOnce(Return(37.5)); + EXPECT_CALL(m1_, get_emergencystop()).WillOnce(Return(false)); EXPECT_CALL(m1_, get_commanded_speed()).WillOnce(Return(nan_to_speed())); EXPECT_CALL(m1_, get_actual_speed()).WillOnce(Return(nan_to_speed())); expect_packet(":X191E933AN15511050B000FFFF;"); @@ -139,6 +139,7 @@ TEST_F(TractionSingleMockTest, GetSpeed) TEST_F(TractionSingleMockTest, GetSpeedTestWithCommandedSpeed) { EXPECT_CALL(m1_, get_speed()).WillOnce(Return(37.5)); + EXPECT_CALL(m1_, get_emergencystop()).WillOnce(Return(false)); EXPECT_CALL(m1_, get_commanded_speed()).WillOnce(Return(37.0)); EXPECT_CALL(m1_, get_actual_speed()).WillOnce(Return(nan_to_speed())); expect_packet(":X191E933AN15511050B00050A0;"); @@ -149,6 +150,7 @@ TEST_F(TractionSingleMockTest, GetSpeedTestWithCommandedSpeed) TEST_F(TractionSingleMockTest, GetSpeedTestWithActualSpeed) { EXPECT_CALL(m1_, get_speed()).WillOnce(Return(37.5)); + EXPECT_CALL(m1_, get_emergencystop()).WillOnce(Return(false)); EXPECT_CALL(m1_, get_commanded_speed()).WillOnce(Return(37.0)); EXPECT_CALL(m1_, get_actual_speed()).WillOnce(Return(38.0)); expect_packet(":X191E933AN15511050B00050A0;"); @@ -156,6 +158,17 @@ TEST_F(TractionSingleMockTest, GetSpeedTestWithActualSpeed) send_packet(":X195EB551N033A10;"); } +TEST_F(TractionSingleMockTest, GetSpeedTestWithEstop) +{ + EXPECT_CALL(m1_, get_speed()).WillOnce(Return(-0.0)); + EXPECT_CALL(m1_, get_emergencystop()).WillOnce(Return(true)); + EXPECT_CALL(m1_, get_commanded_speed()).WillOnce(Return(-0.0)); + EXPECT_CALL(m1_, get_actual_speed()).WillOnce(Return(-0.0)); + expect_packet(":X191E933AN1551108000018000;"); + expect_packet(":X191E933AN25518000;"); + send_packet(":X195EB551N033A10;"); +} + TEST_F(TractionSingleMockTest, SetFn) { EXPECT_CALL(m1_, set_fn(0x112233, 0x4384)); diff --git a/src/openlcb/TractionTrain.hxx b/src/openlcb/TractionTrain.hxx index fdb16b619..aeb80eba1 100644 --- a/src/openlcb/TractionTrain.hxx +++ b/src/openlcb/TractionTrain.hxx @@ -38,6 +38,7 @@ #include #include "executor/Service.hxx" +#include "openlcb/DefaultNodeRegistry.hxx" #include "openlcb/Node.hxx" #include "openlcb/TractionDefs.hxx" #include "openlcb/TrainInterface.hxx" @@ -48,24 +49,6 @@ namespace openlcb class TrainService; -/// Linked list entry for all registered consist clients for a given train -/// node. -struct ConsistEntry : public QMember { - ConsistEntry(NodeID s, uint8_t flags) : payload((s << 8) | flags) {} - NodeID get_slave() const { - return payload >> 8; - } - uint8_t get_flags() const { - return payload & 0xff; - } - void set_flags(uint8_t new_flags) { - payload ^= (payload & 0xff); - payload |= new_flags; - } -private: - uint64_t payload; -}; - /// Virtual node class for an OpenLCB train protocol node. /// /// Usage: @@ -77,56 +60,130 @@ private: class TrainNode : public Node { public: - TrainNode(TrainService *service, TrainImpl *train); ~TrainNode(); - If *iface() OVERRIDE; - bool is_initialized() OVERRIDE + /// @return the train implementation object for issuing control commands to + /// this train. + virtual TrainImpl *train() = 0; + + /// Applies a policy to function change requests coming in from the OpenLCB + /// bus. If the policy returns false, the change will not be applied to the + /// TrainImpl. This is used to implement consist function behavior. + /// @param src source node where the request came from. + /// @param command_byte is the first byte of the payload (usually 0x01 or + /// 0x81 depending on the REQ_LISTENER bit) + /// @param fnum which function to set + /// @param value what value to set this function to + /// @param done must be notified inline if the policy application is + /// successful. If not notified inline, then the returned value is ignored + /// and the call is repeated after done has been invoked by the callee. + /// @return true if the function should be applied to the TrainImpl, false + /// if it should not be applied. + virtual bool function_policy(NodeHandle src, uint8_t command_byte, + uint32_t fnum, uint16_t value, Notifiable *done) = 0; + + /// Invoked for every incoming traction command targeted to this node. + /// @param src what node sent this command + /// @param p command payload + virtual void command_hook(NodeHandle src, const Payload& p) = 0; + + /// @return the last stored controller node. + virtual NodeHandle get_controller() = 0; + + /// @param id the controller node of this train. + virtual void set_controller(NodeHandle id) = 0; + + // Thread-safety information + // + // The consisting functionality is thread-compatible, which means that it + // is the responsibility of the caller to ensure that no two threads are + // calling these methods concurrently. + // + // In practice these methods are always called from the TractionService + // which only operates on a single thread (the service's executor) and will + // only process one request at a time. All traction protocol requests being + // forwarded and thus traversing the consist list will be fully processed + // before any consist change requests would reach the front of the queue + // for the traction flow. + + /// Adds a node ID to the consist targets. @return false if the node was + /// already in the target list, true if it was newly added. + /// @param tgt the destination of the consist link + /// @param flags consisting flags from the Traction protocol. + virtual bool add_consist(NodeID tgt, uint8_t flags) = 0; + + /// Removes a node ID from the consist targets. @return true if the target + /// was removed, false if the target was not on the list. + /// @param tgt destination of consist link to remove. + virtual bool remove_consist(NodeID tgt) = 0; + + /// Fetch a given consist link. + /// @return The target of a given consist link, or NodeID(0) if there are + /// fewer than id consist targets. + /// @param id zero-based index of consist links. + /// @param flags retrieved consist link's flags go here. + virtual NodeID query_consist(int id, uint8_t* flags) = 0; + + /// @return the number of slaves in this consist. + virtual int query_consist_length() = 0; +}; + +/// Linked list entry for all registered consist clients for a given train +/// node. +struct ConsistEntry : public QMember +{ + /// Creates a new consist entry storage. + /// @param s the stored node ID + /// @param flags the stored flag byte + ConsistEntry(NodeID s, uint8_t flags) + : payload((s << 8) | flags) { - return isInitialized_; } - void set_initialized() OVERRIDE + /// @return the stored Node ID. + NodeID get_slave() const { - isInitialized_ = 1; + return payload >> 8; } - - // Used for restarting the stack. - void clear_initialized() OVERRIDE + /// @return the stored flags byte. + uint8_t get_flags() const { - isInitialized_ = 0; + return payload & 0xff; } - - TrainImpl *train() + /// Overrides the stored flags. + /// @param new_flags the new value of the flags byte. + void set_flags(uint8_t new_flags) { - return train_; + payload ^= (payload & 0xff); + payload |= new_flags; } - NodeHandle get_controller() +private: + /// Data contents. + uint64_t payload; +}; + +/// Intermediate class which is still abstract, but adds implementation for the +/// consist management functions. +class TrainNodeWithConsist : public TrainNode { +public: + ~TrainNodeWithConsist(); + + /// @copydoc TrainNode::function_policy() + /// The default function policy applies everything. + bool function_policy(NodeHandle src, uint8_t command_byte, uint32_t fnum, + uint16_t value, Notifiable *done) override { - return controllerNodeId_; + AutoNotify an(done); + return true; } - void set_controller(NodeHandle id) + void command_hook(NodeHandle src, const Payload &p) override { - controllerNodeId_ = id; } - // Thread-safety information - // - // The consisting functionality is thread-compatible, which means that it - // is the responsibility of the caller to ensure that no two threads are - // calling these methods concurrently. - // - // In practice these methods are always called from the TractionService - // which only operates on a single thread (the service's executor) and will - // only process one request at a time. All traction protocol requests being - // forwarded and thus traversing the consist list will be fully processed - // before any consist change requests would reach the front of the queue - // for the traction flow. - /** Adds a node ID to the consist targets. @return false if the node was * already in the target list, true if it was newly added. */ - bool add_consist(NodeID tgt, uint8_t flags) + bool add_consist(NodeID tgt, uint8_t flags) override { if (!tgt) { @@ -150,8 +207,8 @@ public: } /** Removes a node ID from the consist targets. @return true if the target - * was removesd, false if the target was not on the list. */ - bool remove_consist(NodeID tgt) + * was removed, false if the target was not on the list. */ + bool remove_consist(NodeID tgt) override { for (auto it = consistSlaves_.begin(); it != consistSlaves_.end(); ++it) { @@ -168,7 +225,7 @@ public: /** Returns the consist target with offset id, or NodeID(0) if there are * fewer than id consist targets. id is zero-based. */ - NodeID query_consist(int id, uint8_t* flags) + NodeID query_consist(int id, uint8_t* flags) override { int k = 0; for (auto it = consistSlaves_.begin(); @@ -184,7 +241,7 @@ public: } /** Returns the number of slaves in this consist. */ - int query_consist_length() + int query_consist_length() override { int ret = 0; for (auto it = consistSlaves_.begin(); it != consistSlaves_.end(); @@ -194,38 +251,101 @@ public: return ret; } + TypedQueue consistSlaves_; +}; + +/// Default implementation of a train node. +class DefaultTrainNode : public TrainNodeWithConsist +{ +public: + DefaultTrainNode(TrainService *service, TrainImpl *impl); + ~DefaultTrainNode(); + + NodeHandle get_controller() override + { + return controllerNodeId_; + } + + void set_controller(NodeHandle id) override + { + controllerNodeId_ = id; + } + + If *iface() override; + bool is_initialized() override + { + return isInitialized_; + } + void set_initialized() override + { + isInitialized_ = 1; + } + // Used for restarting the stack. + void clear_initialized() override + { + isInitialized_ = 0; + } + + TrainImpl *train() override + { + return train_; + } + protected: + /// Pointer to the traction service. TrainService *service_; + /// Pointer to the train implementation object. TrainImpl *train_; private: + /// Node is initialized bit for startup transient. unsigned isInitialized_ : 1; /// Controller node that is assigned to run this train. 0 if none. NodeHandle controllerNodeId_; - TypedQueue consistSlaves_; }; - /// Train node class with a an OpenLCB Node ID from the DCC pool. Used for command stations. -class TrainNodeForProxy : public TrainNode { +class TrainNodeForProxy : public DefaultTrainNode +{ public: + /// Constructor. + /// @param service the traction service object that will own this node. + /// @param train the implementation object that the traction messages + /// should be forwarded to. TrainNodeForProxy(TrainService *service, TrainImpl *train); + /// Destructor. + ~TrainNodeForProxy(); + + /// @return the OpenLCB node ID, generated from the legacy protocol types + /// that we get from TrainImpl. NodeID node_id() OVERRIDE; }; /// Train node class with a fixed OpenLCB Node ID. This is useful for native /// train nodes that are not dynamically generated by a command station. -class TrainNodeWithId : public TrainNode { +class TrainNodeWithId : public DefaultTrainNode +{ public: + /// Constructor. + /// @param service the traction service object that will own this node. + /// @param train the implementation object that the traction messages + /// should be forwarded to. + /// @param node_id the OpenLCB node ID for this train. TrainNodeWithId(TrainService *service, TrainImpl *train, NodeID node_id); - NodeID node_id() OVERRIDE { + /// Destructor. + ~TrainNodeWithId(); + + /// @return the openlcb node ID. + NodeID node_id() OVERRIDE + { return nodeId_; } private: + /// The OpenLCB node ID. NodeID nodeId_; }; @@ -237,7 +357,12 @@ private: class TrainService : public Service, private Atomic { public: - TrainService(If *iface); + /// Constructor. + /// @param iface the OpenLCB interface to which the train nodes are bound. + /// @param train_node_registry implementation of the + /// NodeRegistry. Ownership is transferred. + TrainService( + If *iface, NodeRegistry *train_node_registry = new DefaultNodeRegistry); ~TrainService(); If *iface() @@ -249,14 +374,27 @@ public: initialization flow for the train. */ void register_train(TrainNode *node); + /// Removes a train node from the local interface. + /// @param node train to remove from registry. + void unregister_train(TrainNode *node); + + /// Checks if the a given node is a train node operated by this Traction + /// Service. + /// @param node a virtual node + /// @return true if this is a known train node. + bool is_known_train_node(Node *node) + { + return nodes_->is_node_registered(node); + } + private: struct Impl; /** Implementation flows. */ Impl *impl_; - + /** OpenLCB interface */ If *iface_; /** List of train nodes managed by this Service. */ - std::set nodes_; + std::unique_ptr nodes_; }; } // namespace openlcb 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.cxx b/src/openlcb/Velocity.cxx index dbe8d5d31..94352d208 100644 --- a/src/openlcb/Velocity.cxx +++ b/src/openlcb/Velocity.cxx @@ -48,7 +48,7 @@ namespace openlcb uint8_t Velocity::get_dcc_128() { uint8_t result; - uint32_t tmp = (speed() * MPH_FACTOR) + 0.5; + uint32_t tmp = mph() + 0.5; if (tmp == 0) { diff --git a/src/openlcb/Velocity.cxxtest b/src/openlcb/Velocity.cxxtest index f84a619bf..a11b04e98 100644 --- a/src/openlcb/Velocity.cxxtest +++ b/src/openlcb/Velocity.cxxtest @@ -38,6 +38,7 @@ #include "gtest/gtest.h" #include "openlcb/Velocity.hxx" #include "openlcb/TractionDefs.hxx" +#include "utils/StringPrintf.hxx" using namespace openlcb; @@ -247,18 +248,46 @@ TEST(NMRAnetVelocityTest, mph_negative) TEST(NMRAnetVelocityTest, get_dcc_128_forward) { - Velocity *velocity = new Velocity(100.5F); + Velocity *velocity = new Velocity(20.1168); // 45 mph EXPECT_EQ(velocity->direction(), Velocity::FORWARD); - EXPECT_TRUE(velocity->get_dcc_128() == (46 | 0x80)); + EXPECT_EQ((46 | 0x80), velocity->get_dcc_128()); } TEST(NMRAnetVelocityTest, get_dcc_128_reverse) { - Velocity *velocity = new Velocity(-100.5F); + Velocity *velocity = new Velocity(-20.1168); // -45 mph EXPECT_EQ(velocity->direction(), Velocity::REVERSE); - EXPECT_TRUE(velocity->get_dcc_128() == 46); + EXPECT_EQ(46, velocity->get_dcc_128()); +} + +/// Tests all 126 speed steps in DCC. +TEST(NMRAnetVelocityTest, dcc_128_round) +{ + Velocity v; + for (int mph = 1; mph <= 126; mph++) + { + string ss = StringPrintf("mph=%d", mph); + SCOPED_TRACE(ss); + v.set_mph(mph); + auto wire = v.get_wire(); + Velocity vv; + vv.set_wire(wire); + EXPECT_EQ(0x80 | (mph + 1), vv.get_dcc_128()); + } + + for (int mph = 1; mph <= 126; mph++) + { + string ss = StringPrintf("mph=-%d", mph); + SCOPED_TRACE(ss); + v.set_mph(mph); + v.reverse(); + auto wire = v.get_wire(); + Velocity vv; + vv.set_wire(wire); + EXPECT_EQ((mph + 1), vv.get_dcc_128()); + } } TEST(NMRAnetVelocityTest, get_dcc_128_zero) 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 */ diff --git a/src/openlcb/VirtualMemorySpace.cxxtest b/src/openlcb/VirtualMemorySpace.cxxtest new file mode 100644 index 000000000..749c171e4 --- /dev/null +++ b/src/openlcb/VirtualMemorySpace.cxxtest @@ -0,0 +1,959 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file VirtualMemorySpace.cxxtest + * + * Unit tests for the virtual memory space. + * + * @author Balazs Racz + * @date 10 Aug 2020 + */ + +#include "openlcb/VirtualMemorySpace.hxx" + +#include "openlcb/ConfigRepresentation.hxx" +#include "openlcb/MemoryConfigClient.hxx" +#include "utils/async_datagram_test_helper.hxx" + +namespace openlcb +{ + +string arg1; +string arg2; + +CDI_GROUP(ExampleMemorySpace); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(first, StringConfigEntry<13>); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +CDI_GROUP_ENTRY(second, StringConfigEntry<20>); +CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>); +CDI_GROUP_END(); + +ExampleMemorySpace cfg(44); + +const unsigned arg1_ofs = 44 + 5; +const unsigned arg2_ofs = 44 + 5 + 13 + 8; + +class VirtualMemorySpaceTest : public AsyncDatagramTest +{ +protected: + ~VirtualMemorySpaceTest() + { + wait(); + } + + MemoryConfigHandler memCfg_ {&datagram_support_, node_, 3}; + std::unique_ptr space_; + MemoryConfigClient client_ {node_, &memCfg_}; +}; + +class TestSpace : public VirtualMemorySpace +{ +public: + TestSpace() + { + arg1.clear(); + arg2.clear(); + register_string( + cfg.first(), string_reader(&arg1), string_writer(&arg1)); + register_string( + cfg.second(), string_reader(&arg2), string_writer(&arg2)); + } + + /// Creates a ReaderFunction that just returns a string from a given + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_reader(string *ptr) + { + return + [ptr](unsigned repeat, string *contents, BarrierNotifiable *done) { + *contents = *ptr; + done->notify(); + return true; + }; + } + + /// Creates a WriterFunction that just stores the data in a given string + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_writer(string *ptr) + { + return + [ptr](unsigned repeat, string contents, BarrierNotifiable *done) { + *ptr = std::move(contents); + done->notify(); + }; + } +}; + +class TestSpaceTest : public VirtualMemorySpaceTest +{ +protected: + TestSpaceTest() + { + space_.reset(new TestSpace); + memCfg_.registry()->insert(node_, SPACE, space_.get()); + } + + /// Memory space number where the test space is registered. + const uint8_t SPACE = 0x52; +}; + +TEST_F(TestSpaceTest, create) +{ +} + +/// Basic tests reading variables from the exact offset including partial +/// reads. +TEST_F(TestSpaceTest, read_payload) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("hello", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, 20); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("", b->data()->payload.c_str()); + EXPECT_EQ(20u, b->data()->payload.size()); + + // prefix read + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, 3); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("hel", b->data()->payload); +} + +/// Test reading a variable from an imprecise offset (too early). +TEST_F(TestSpaceTest, read_early) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 2, 10); + ASSERT_EQ(0, b->data()->resultCode); + string exp("\0\0hello\0\0\0", 10); + EXPECT_EQ(exp, b->data()->payload); + EXPECT_EQ(10u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 4, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp2("\0\0\0", 3); + EXPECT_EQ(exp2, b->data()->payload); + EXPECT_EQ(3u, b->data()->payload.size()); +} + +/// Test reading a variable from an imprecise offset (too late -- middle of +/// variable). +TEST_F(TestSpaceTest, read_middle) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 2, 10); + ASSERT_EQ(0, b->data()->resultCode); + string exp("llo\0\0\0\0\0\0\0", 10); + EXPECT_EQ(exp, b->data()->payload); + EXPECT_EQ(10u, b->data()->payload.size()); + + arg2 = "abcdefghij"; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 2, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp2("cde", 3); + EXPECT_EQ(exp2, b->data()->payload); + EXPECT_EQ(3u, b->data()->payload.size()); +} + +/// Test writing a variable to a offset that is not covered at all. +TEST_F(TestSpaceTest, read_hole) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 5, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp("\0\0\0", 3); + EXPECT_EQ(exp, b->data()->payload); + + /// @todo this return seems to be wrong, although this address is out of + /// the memory space limits. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 5, 2); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string(), b->data()->payload); + + /// @todo this return seems to be wrong, although this address is out of + /// the memory space limits. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 100, 4); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string(), b->data()->payload); +} + +/// Test reading beyond eof. +TEST_F(TestSpaceTest, read_eof) +{ + unsigned reg_last = cfg.second().offset(); + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(20u, b->data()->payload.size()); + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 20, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 23, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->payload.size()); +} + +/// Basic tests writing variables from the exact offset but not including +/// partial writes. +TEST_F(TestSpaceTest, write_payload) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, "xyzw"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("xyzw", arg1); + EXPECT_EQ(4u, arg1.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, "abcde"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("abcde", arg2); + EXPECT_EQ(5u, arg2.size()); +} + +/// Test writing a variable to a offset that is too early. +TEST_F(TestSpaceTest, write_early) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 2, "xyzw"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("zw", arg1); + EXPECT_EQ(2u, arg1.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 1, "qwert"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("wert", arg2); + EXPECT_EQ(4u, arg2.size()); +} + +/// Test writing into to a offset that is in the middle of a variable. +TEST_F(TestSpaceTest, write_middle) +{ + arg1 = "hellllo"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 3, "xy"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("helxylo\0\0\0\0\0\0", 13), arg1); + + // Writes to middle then beyond the end of the variable. + string payload(19, 'a'); + payload += "bbbbbbbbb"; + arg2 = "0123456789i123456789"; + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 3, payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("helaaaaaaaaaa", arg1); + EXPECT_EQ(13u, arg1.size()); + EXPECT_EQ("abbbbbbbbb", arg2); + EXPECT_EQ(10u, arg2.size()); + + // Writes a string in multiple datagrams. + payload = "0123456789abcdef"; + payload.push_back(0); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, payload.substr(0, 10)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("0123456789", arg2); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 10, + payload.substr(10, 3)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abc", arg2.c_str()); + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 13, + payload.substr(13, 2)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abcde", arg2.c_str()); + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 15, payload.substr(15)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abcdef", arg2.c_str()); +} + +/// Test writing a variable to a offset that is not covered at all. +TEST_F(TestSpaceTest, write_hole) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 5, "xyz"); + ASSERT_EQ(0, b->data()->resultCode); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 5, "qw"); + ASSERT_EQ(0, b->data()->resultCode); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 100, "qw"); + ASSERT_EQ(0, b->data()->resultCode); +} + +CDI_GROUP(NumericGroup); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(first, Uint32ConfigEntry); +CDI_GROUP_ENTRY(second, Int16ConfigEntry); +CDI_GROUP_ENTRY(third, Uint8ConfigEntry); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +CDI_GROUP_END(); + +CDI_GROUP(NumericMemorySpace); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(first, StringConfigEntry<13>); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +CDI_GROUP_ENTRY(second, StringConfigEntry<20>); +CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>); +CDI_GROUP_ENTRY(outer_before, Uint32ConfigEntry); +using RG = RepeatedGroup; +CDI_GROUP_ENTRY(grp, RG); +CDI_GROUP_ENTRY(outer_after, Uint32ConfigEntry); +CDI_GROUP_END(); + +NumericMemorySpace ncfg(44); + +class TestSpaceAsync : public VirtualMemorySpace +{ +public: + TestSpaceAsync() + { + arg1.clear(); + arg2.clear(); + register_string( + ncfg.first(), string_reader(&arg1), string_writer(&arg1)); + register_string( + ncfg.second(), string_reader(&arg2), string_writer(&arg2)); + register_numeric(ncfg.outer_before(), typed_reader(&rnBefore_), + typed_writer(&rnBefore_)); + register_numeric(ncfg.outer_after(), typed_reader(&rnAfter_), + typed_writer(&rnAfter_)); + register_numeric(ncfg.grp().entry(0).first(), typed_reader(&rnFirst_), + typed_writer(&rnFirst_)); + register_numeric(ncfg.grp().entry(0).second(), typed_reader(&rnSecond_), + typed_writer(&rnSecond_)); + register_numeric(ncfg.grp().entry(0).third(), typed_reader(&rnThird_), + typed_writer(&rnThird_)); + register_repeat(ncfg.grp()); + } + + /// Creates a ReaderFunction that just returns a string from a given + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_reader(string *ptr) + { + return [this, ptr]( + unsigned repeat, string *contents, BarrierNotifiable *done) { + attempt++; + lastRepeat_ = repeat; + if ((attempt & 1) == 0) + { + *contents = *ptr; + done->notify(); + return true; + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + return false; + } + }; + } + + /// Creates a TypedReaderFunction that just returns a value from a given + /// variable. + /// @param ptr the variable whose contents to return as read value. Must + /// stay alive as long as the function is in use. + /// @return the TypedReaderFunction. + template + typename std::function + typed_reader(T *ptr) + { + return [this, ptr](unsigned repeat, BarrierNotifiable *done) { + attempt++; + lastRepeat_ = repeat; + if ((attempt & 1) == 0) + { + done->notify(); + return *ptr; + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + return T(); + } + }; + } + + /// Creates a WriterFunction that just stores the data in a given string + /// variable. + /// @param ptr the variable where to store the contents. Must stay alive as + /// long as the function is in use. + /// @return the WriterFunction. + std::function + string_writer(string *ptr) + { + return [this, ptr]( + unsigned repeat, string contents, BarrierNotifiable *done) { + attempt++; + lastRepeat_ = repeat; + if ((attempt & 1) == 0) + { + *ptr = std::move(contents); + done->notify(); + return true; + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + return false; + } + }; + } + + /// Creates a TypedWriterFunction that just stores the data in a given + /// variable. + /// @param ptr the variable where to store the contents. Must stay alive as + /// long as the function is in use. + /// @return the TypedWriterFunction. + template + std::function + typed_writer(T *ptr) + { + return + [this, ptr](unsigned repeat, T contents, BarrierNotifiable *done) { + attempt++; + lastRepeat_ = repeat; + if ((attempt & 1) == 0) + { + *ptr = std::move(contents); + done->notify(); + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + } + }; + } + + /// Stores the last invoked repetition number. + unsigned lastRepeat_ = 0; + /// Shadow for NumericGroup.first. + uint32_t rnFirst_ = 0; + /// Shadow for NumericGroup.second. + int16_t rnSecond_ = 0; + /// Shadow for NumericGroup.third. + uint8_t rnThird_ = 0; + + /// Shadow for NUmericMemorySpace.before. + uint32_t rnBefore_ = 0; + /// Shadow for NUmericMemorySpace.after. + uint32_t rnAfter_ = 0; + +private: + size_t attempt = 0; +}; + +class TestSpaceAsyncTest : public VirtualMemorySpaceTest +{ +protected: + TestSpaceAsyncTest() + { + space_.reset(tspace_); + memCfg_.registry()->insert(node_, SPACE, space_.get()); + } + + TestSpaceAsync *tspace_ = new TestSpaceAsync; + /// Memory space number where the test space is registered. + const uint8_t SPACE = 0x52; +}; + +/// Basic tests reading variables from async space. +TEST_F(TestSpaceAsyncTest, read_payload_async) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("hello", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, 20); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("", b->data()->payload.c_str()); + EXPECT_EQ(20u, b->data()->payload.size()); +} + +/// Test reading a variable from an imprecise offset (too late -- middle of +/// variable). +TEST_F(TestSpaceAsyncTest, read_middle) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 2, 10); + ASSERT_EQ(0, b->data()->resultCode); + string exp("llo\0\0\0\0\0\0\0", 10); + EXPECT_EQ(exp, b->data()->payload); + EXPECT_EQ(10u, b->data()->payload.size()); + + arg2 = "abcdefghij"; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 2, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp2("cde", 3); + EXPECT_EQ(exp2, b->data()->payload); + EXPECT_EQ(3u, b->data()->payload.size()); +} + +/// Basic tests writing variables from the exact offset but not including +/// partial writes. +TEST_F(TestSpaceAsyncTest, write_payload_async) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, "xyzw"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("xyzw", arg1); + EXPECT_EQ(4u, arg1.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, "abcde"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("abcde", arg2); + EXPECT_EQ(5u, arg2.size()); +} + +/// Test writing into to a offset that is in the middle of a variable. +TEST_F(TestSpaceAsyncTest, write_middle) +{ + arg1 = "hellllo"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 3, "xy"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("helxylo\0\0\0\0\0\0", 13), arg1); + + // Writes to middle then beyond the end of the variable. + string payload(19, 'a'); + payload += "bbbbbbbbb"; + arg2 = "0123456789i123456789"; + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs + 3, payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("helaaaaaaaaaa", arg1); + EXPECT_EQ(13u, arg1.size()); + EXPECT_EQ("abbbbbbbbb", arg2); + EXPECT_EQ(10u, arg2.size()); + + // Writes a string in multiple datagrams. + payload = "0123456789abcdef"; + payload.push_back(0); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, payload.substr(0, 10)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("0123456789", arg2); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 10, + payload.substr(10, 3)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abc", arg2.c_str()); + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 13, + payload.substr(13, 2)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abcde", arg2.c_str()); + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 15, payload.substr(15)); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("0123456789abcdef", arg2.c_str()); +} + +/// Tests reading and writing numeric variables with endianness. +TEST_F(TestSpaceAsyncTest, rw_numeric_async) +{ + string u32payload; + u32payload.push_back(0xAA); + u32payload.push_back(2); + u32payload.push_back(3); + u32payload.push_back(4); + + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, ncfg.outer_before().offset(), + u32payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0xAA020304u, tspace_->rnBefore_); + + tspace_->rnBefore_ = 0xbb554433; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, ncfg.outer_before().offset(), 4); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ((char)0xbb, b->data()->payload[0]); + EXPECT_EQ(0x55, b->data()->payload[1]); + EXPECT_EQ(0x44, b->data()->payload[2]); + EXPECT_EQ(0x33, b->data()->payload[3]); +} + +/// Tests variable after repeted group. +TEST_F(TestSpaceAsyncTest, rw_numeric_after_repeat) +{ + string u32payload; + u32payload.push_back(0xAA); + u32payload.push_back(2); + u32payload.push_back(3); + u32payload.push_back(4); + + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, ncfg.outer_after().offset(), + u32payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0xAA020304u, tspace_->rnAfter_); + + tspace_->rnAfter_ = 0xbb554433; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, ncfg.outer_after().offset(), 4); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ((char)0xbb, b->data()->payload[0]); + EXPECT_EQ(0x55, b->data()->payload[1]); + EXPECT_EQ(0x44, b->data()->payload[2]); + EXPECT_EQ(0x33, b->data()->payload[3]); +} + +/// Tests reading and writing numeric variables with endianness from repetitions. +TEST_F(TestSpaceAsyncTest, rw_numeric_repeat) +{ + string u32payload; + u32payload.push_back(0xAA); + u32payload.push_back(2); + u32payload.push_back(3); + u32payload.push_back(4); + + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, + ncfg.grp().entry(3).first().offset(), u32payload); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(3u, tspace_->lastRepeat_); + EXPECT_EQ(0xAA020304u, tspace_->rnFirst_); + + tspace_->rnSecond_ = -2; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + ncfg.grp().entry(4).second().offset(), 2); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(4u, tspace_->lastRepeat_); + ASSERT_EQ(2u, b->data()->payload.size()); + EXPECT_EQ((char)0xFF, b->data()->payload[0]); + EXPECT_EQ((char)0xFE, b->data()->payload[1]); + + tspace_->rnSecond_ = 55; + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + ncfg.grp().entry(0).second().offset(), 2); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, tspace_->lastRepeat_); + ASSERT_EQ(2u, b->data()->payload.size()); + EXPECT_EQ((char)0, b->data()->payload[0]); + EXPECT_EQ((char)55, b->data()->payload[1]); +} + +CDI_GROUP(RepeatMemoryDef); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(before, StringConfigEntry<13>); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +using GroupRept = RepeatedGroup; +CDI_GROUP_ENTRY(grp, GroupRept); +CDI_GROUP_ENTRY(skipped3, EmptyGroup<8>); +CDI_GROUP_ENTRY(after, StringConfigEntry<20>); +CDI_GROUP_ENTRY(grp2, GroupRept); +CDI_GROUP_END(); + +RepeatMemoryDef spacerept(22); + +class SpaceWithRepeat : public VirtualMemorySpace +{ +public: + SpaceWithRepeat() + { + arg1.clear(); + arg2.clear(); + register_string(spacerept.grp().entry<0>().first(), + string_reader(&arg1), string_writer(&arg1)); + register_string(spacerept.grp().entry<0>().second(), + string_reader(&arg2), string_writer(&arg2)); + register_string(spacerept.before(), string_reader(&before_), + string_writer(&before_)); + register_string( + spacerept.after(), string_reader(&after_), string_writer(&after_)); + register_repeat(spacerept.grp()); + register_string(spacerept.grp2().entry<0>().second(), + string_reader(&arg2), string_writer(&arg2)); + register_repeat(spacerept.grp2()); + } + + /// Creates a ReaderFunction that just returns a string from a given + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_reader(string *ptr) + { + return [this, ptr]( + unsigned repeat, string *contents, BarrierNotifiable *done) { + lastRepeat_ = repeat; + *contents = *ptr; + done->notify(); + return true; + }; + } + + /// Creates a WriterFunction that just stores the data in a given string + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_writer(string *ptr) + { + return [this, ptr]( + unsigned repeat, string contents, BarrierNotifiable *done) { + lastRepeat_ = repeat; + *ptr = std::move(contents); + done->notify(); + }; + } + + /// Saves the last repeat variable into this value. + unsigned lastRepeat_; + /// Storage variable for a field. + string before_; + /// Storage variable for a field. + string after_; +}; + +class ReptSpaceTest : public VirtualMemorySpaceTest +{ +protected: + ReptSpaceTest() + { + memCfg_.registry()->insert(node_, SPACE, &s_); + } + + /// Memory space number where the test space is registered. + const uint8_t SPACE = 0x52; + SpaceWithRepeat s_; +}; + +TEST_F(ReptSpaceTest, create) +{ +} + +// Looks for a field that is before the repeated group. +TEST_F(ReptSpaceTest, before) +{ + s_.before_ = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, spacerept.before().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("hello", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(0u, s_.lastRepeat_); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, spacerept.before().offset() - 2, + 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0hel", 5), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); +} + +// Looks for a field in the first repetition of the group. +TEST_F(ReptSpaceTest, first_repeat) +{ + arg1 = "world"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().first().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("world", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(0u, s_.lastRepeat_); + + // Start offset within the group. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().first().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0wor", 5), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); + + // Start offset _before_ the group. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().first().offset() - 7, 10); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0\0\0\0\0\0wor", 10), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); + + arg2 = "ahoi"; + // Second field, exact match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().second().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("ahoi", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(0u, s_.lastRepeat_); + + // Second field, before match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<0>().second().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0aho", 5), b->data()->payload); + EXPECT_EQ(0u, s_.lastRepeat_); +} + +// Looks for a field in the first repetition of the group. +TEST_F(ReptSpaceTest, mid_repeat) +{ + arg1 = "world"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().first().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("world", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(2u, s_.lastRepeat_); + + // Start offset within the group. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().first().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0wor", 5), b->data()->payload); + EXPECT_EQ(2u, s_.lastRepeat_); + + // Start offset in the previous group repeat. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().first().offset() - 7, 10); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0\0\0\0\0\0wor", 10), b->data()->payload); + EXPECT_EQ(2u, s_.lastRepeat_); + + arg2 = "ahoi"; + // Second field, exact match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().second().offset(), 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("ahoi", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + EXPECT_EQ(2u, s_.lastRepeat_); + + // Second field, before match + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, + spacerept.grp().entry<2>().second().offset() - 2, 5); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string("\0\0aho", 5), b->data()->payload); + EXPECT_EQ(2u, s_.lastRepeat_); +} + +/// Test reading beyond eof. The end of the space there is a repeated group +/// where the registry entries do not cover all bytes. This is a difficult +/// cornercase and we test that all bytes until the end of the repetition can +/// be read but not beyond. +TEST_F(ReptSpaceTest, read_eof) +{ + unsigned reg_last = spacerept.grp2().entry<2>().second().offset(); + // EOF is 28 bytes away from here. + EXPECT_EQ(reg_last + 27, s_.max_address()); + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(28u, b->data()->payload.size()); + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 20, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(8u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 23, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(5u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 27, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(1u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 28, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, reg_last + 29, 50); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(0u, b->data()->payload.size()); +} + +} // namespace openlcb diff --git a/src/openlcb/VirtualMemorySpace.hxx b/src/openlcb/VirtualMemorySpace.hxx new file mode 100644 index 000000000..9007abe8b --- /dev/null +++ b/src/openlcb/VirtualMemorySpace.hxx @@ -0,0 +1,526 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file VirtualMemorySpace.hxx + * + * Implementation of a memory space where the values are not stored in a + * contiguous storage area but are read and written via callbacks. + * + * @author Balazs Racz + * @date 10 Aug 2020 + */ + +#ifndef _OPENLCB_VIRTUALMEMORYSPACE_HXX +#define _OPENLCB_VIRTUALMEMORYSPACE_HXX + +#include "openlcb/ConfigEntry.hxx" +#include "openlcb/ConfigRepresentation.hxx" +#include "openlcb/MemoryConfig.hxx" +#include "utils/SortedListMap.hxx" + +namespace openlcb +{ + +/// Implementation of a memory space where the values are not stored in a +/// contiguous storage area but are read and written via callbacks. +class VirtualMemorySpace : public MemorySpace +{ +public: + VirtualMemorySpace() + : isReadOnly_(false) + { + } + + /// @returns whether the memory space does not accept writes. + bool read_only() override + { + return isReadOnly_; + } + /// @returns the lowest address that's valid for this block. + address_t min_address() override + { + return minAddress_; + } + /// @returns the largest valid address for this block. A read of 1 from + /// this address should succeed in returning the last byte. + address_t max_address() override + { + return maxAddress_; + } + + /// @return the number of bytes successfully written (before hitting end + /// of space). + /// @param destination address to write to + /// @param data to write + /// @param len how many bytes to write + /// @param error if set to non-null, then the operation has failed. If the + /// operation needs to be continued, then sets error to + /// MemorySpace::ERROR_AGAIN, and calls the Notifiable + /// @param again when a re-try makes sense. The caller should call write + /// once more, with the offset adjusted with the previously returned + /// bytes. + size_t write(address_t destination, const uint8_t *data, size_t len, + errorcode_t *error, Notifiable *again) override + { + if ((destination > maxAddress_) || ((destination + len) <= minAddress_)) + { + *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS; + return 0; + } + if (destination + len > maxAddress_ + 1) + { + len = maxAddress_ + 1 - destination; + } + *error = 0; + unsigned repeat; + const DataElement *element = nullptr; + ssize_t skip = find_data_element(destination, len, &element, &repeat); + string payload; + size_t written_len; + if (skip > 0) + { + // Will cause a new call be delivered with adjusted data and len. + return skip; + } + else if (skip < 0) + { + HASSERT(element); + // We have some missing bytes that we need to read out first, then + // can perform the write. + address_t field_start = destination + skip; + if (!(cacheOffset_ == field_start && + (cachedData_.size() >= (size_t)-skip))) + { + cacheOffset_ = field_start; + cachedData_.clear(); + bn_.reset(again); + element->readImpl_(repeat, &cachedData_, bn_.new_child()); + if (!bn_.abort_if_almost_done()) + { + // did not succeed synchronously. + bn_.notify(); // our slice + *error = MemorySpace::ERROR_AGAIN; + return 0; + } + cachedData_.resize(element->size_); // pads with zeroes + } + // Now: cachedData_ contains the payload in the current storage. + payload = cachedData_; + written_len = + std::min((size_t)len, (size_t)(element->size_ + skip)); + memcpy(&payload[-skip], (const char *)data, written_len); + } + else // exact address write. + { + HASSERT(element); + payload.assign((const char *)data, + std::min((size_t)len, (size_t)element->size_)); + written_len = payload.size(); + } + bn_.reset(again); + element->writeImpl_(repeat, std::move(payload), bn_.new_child()); + if (bn_.abort_if_almost_done()) + { + cachedData_.clear(); + return written_len; + } + else + { + // did not succeed synchronously. + bn_.notify(); // our slice + *error = MemorySpace::ERROR_AGAIN; + return 0; + } + } + + /** @returns the number of bytes successfully read (before hitting end of + * space). If *error is set to non-null, then the operation has failed. If + * the operation needs to be continued, then sets error to ERROR_AGAIN, and + * calls the Notifiable @param again when a re-try makes sense. The caller + * should call read once more, with the offset adjusted with the previously + * returned bytes. */ + size_t read(address_t source, uint8_t *dst, size_t len, errorcode_t *error, + Notifiable *again) override + { + if ((source > maxAddress_) || ((source + len) <= minAddress_)) + { + *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS; + return 0; + } + if (source + len > maxAddress_ + 1) + { + len = maxAddress_ + 1 - source; + } + *error = 0; + unsigned repeat; + const DataElement *element = nullptr; + ssize_t skip = find_data_element(source, len, &element, &repeat); + if (skip > 0) + { + memset(dst, 0, skip); + return skip; + } + // Now: skip <= 0 + HASSERT(element); + string payload; + bn_.reset(again); + element->readImpl_(repeat, &payload, bn_.new_child()); + if (!bn_.abort_if_almost_done()) + { + // did not succeed synchronously. + bn_.notify(); // our slice + *error = MemorySpace::ERROR_AGAIN; + return 0; + } + payload.resize(element->size_); // pads with zeroes + size_t data_len = std::min(payload.size() + skip, len); + memcpy(dst, payload.data() - skip, data_len); + return data_len; + } + +protected: + /// Function that will be called for writes. + /// @param repeat 0 to number of repeats if this is in a repeated + /// group. Always 0 if not repeated group. + /// @param contents data payload that needs to be written. The data + /// bytes of this container start at the address_. + /// @param done must be notified when the write is complete (possibly + /// inline). + using WriteFunction = std::function; + /// Function that will be called for reads. + /// @param repeat 0 to number of repeats if this is in a repeated + /// group. Always 0 if not repeated group. + /// @param contents the payload to be returned from this variable shall + /// be written here. Will be zero-padded to size_ bytes if shorter. + /// @param done must be notified when the read values are ready. The + /// call will be re-tried if this does not happen inline. + using ReadFunction = std::function; + + /// Typed WriteFunction for primitive types. + template + using TypedWriteFunction = typename std::function; + + /// Typed ReadFunction for primitive types. @return the read value if the + /// read was successful. If the read did not complete, return 0. + template + using TypedReadFunction = typename std::function; + + /// Setup the address bounds from a single CDI group declaration. + /// @param group is an instance of a group, for example a segment. + template void set_bounds_from_group(const G &group) + { + minAddress_ = group.offset(); + maxAddress_ = group.offset() + group.size() - 1; + } + + /// Expand the address bounds from a single CDI group declaration. + /// @param group is an instance of a group, for example a segment. + template void expand_bounds_from_group(const G &group) + { + minAddress_ = std::min(minAddress_, (address_t)group.offset()); + maxAddress_ = std::max( + maxAddress_, (address_t)(group.offset() + group.size() - 1)); + } + + /// Register an untyped element. + /// @param address the address in the memory space + /// @param size how many bytes this elements occupes + /// @param read_f will be called to read this data + /// @param write_f will be called to write this data + void register_element(address_t address, address_t size, + ReadFunction read_f, WriteFunction write_f) + { + elements_.insert(DataElement(address, size, read_f, write_f)); + } + + /// Registers a string typed element. + /// @param entry is the CDI ConfigRepresentation. + /// @param read_f will be called to read this data + /// @param write_f will be called to write this data + template + void register_string(const StringConfigEntry &entry, + ReadFunction read_f, WriteFunction write_f) + { + expand_bounds_from_group(entry); + register_element(entry.offset(), SIZE, read_f, write_f); + } + + /// Registers a numeric typed element. + /// @param T is the type argument, e.g. uint8_t. + /// @param entry is the CDI ConfigRepresentation. + /// @param read_f will be called to read this data + /// @param write_f will be called to write this data + template + void register_numeric(const NumericConfigEntry &entry, + TypedReadFunction read_f, TypedWriteFunction write_f) + { + expand_bounds_from_group(entry); + auto trf = [read_f](unsigned repeat, string *contents, + BarrierNotifiable *done) { + T result = read_f(repeat, done); + contents->clear(); + contents->resize(sizeof(T)); + *((T *)&((*contents)[0])) = + NumericConfigEntry::endian_convert(result); + }; + auto twf = [write_f](unsigned repeat, string contents, + BarrierNotifiable *done) { + contents.resize(sizeof(T)); + T result = NumericConfigEntry::endian_convert( + *(const T *)contents.data()); + write_f(repeat, result, done); + }; + register_element( + entry.offset(), entry.size(), std::move(trf), std::move(twf)); + } + + /// Registers a repeated group. Calling this function means that the + /// virtual memory space of the group will be looped onto the first + /// repetition. The correct usage is to register the elements of the first + /// repetition, then register the repetition itself using this call. Nested + /// repetitions are not supported (either the outer or the inner repetition + /// needs to be unrolled and registered for each repeat there). + /// @param group is the repeated group instance. Will take the start + /// offset, repeat count and repeat size from it. + template + void register_repeat(const RepeatedGroup &group) + { + RepeatElement re; + re.start_ = group.offset(); + re.end_ = group.end_offset(); + re.repeatSize_ = Group::size(); + HASSERT(re.repeatSize_ * N == re.end_ - re.start_); + repeats_.insert(std::move(re)); + expand_bounds_from_group(group); + } + + /// Bounds for valid addresses. + address_t minAddress_ = 0xFFFFFFFFu; + /// Bounds for valid addresses. A read of length 1 from this address + /// should succeed in returning the last byte. + address_t maxAddress_ = 0; + /// Whether the space should report as RO. + unsigned isReadOnly_ : 1; + +private: + /// We keep one of these for each variable that was declared. + struct DataElement + { + DataElement(address_t address, address_t size, ReadFunction read_f, + WriteFunction write_f) + : address_(address) + , size_(size) + , writeImpl_(write_f) + , readImpl_(read_f) + { + } + /// Base offset of this variable (first repeat only). + address_t address_; + /// Size of this variable. This is how many bytes of address space this + /// variable occupies. + address_t size_; + /// Function that will be called for writes. + WriteFunction writeImpl_; + /// Function that will be called for reads. + ReadFunction readImpl_; + }; + + /// STL-compatible comparator function for sorting DataElements. + struct DataComparator + { + /// Sorting operator by address. + bool operator()(const DataElement &a, const DataElement &b) const + { + return a.address_ < b.address_; + } + /// Sorting operator by address. + bool operator()(unsigned a, const DataElement &b) const + { + return a < b.address_; + } + /// Sorting operator by address. + bool operator()(const DataElement &a, unsigned b) const + { + return a.address_ < b; + } + }; + + /// Represents a repeated group. + struct RepeatElement + { + /// Offset of the repeated group (first repeat). + uint32_t start_; + /// Address bytes per repeat. + uint32_t repeatSize_; + /// Address byte after the last repeat. + uint32_t end_; + }; + + /// STL-compatible comparator function for sorting RepeatElements. Sorts + /// repeats by the end_ as the key. + struct RepeatComparator + { + /// Sorting operator by end address. + bool operator()(const RepeatElement &a, const RepeatElement &b) const + { + return a.end_ < b.end_; + } + /// Sorting operator by end address against a lookup key. + bool operator()(uint32_t a, const RepeatElement &b) const + { + return a < b.end_; + } + }; + + /// Look up the first matching data element given an address in the virtual + /// memory space. + /// @param address byte offset to look up. + /// @param len how many bytes long range to search from address. + /// @param ptr will be filled with a pointer to the data element when + /// found, or filled with nullptr if no data element overlaps with the + /// given range. + /// @param repeat output argument, filled with zero or the repetition + /// number. + /// @return 0 if an exact match is found. -N if a data element is found, + /// but N first bytes of this element are not covered. A number N in + /// [1..len-1] if a data element is found, but this many bytes need to be + /// skipped from address to arrive at the given data element's offset. len + /// if there was no data element found (in which case also set ptr to + /// null). + ssize_t find_data_element(address_t address, address_t len, + const DataElement **ptr, unsigned *repeat) + { + *repeat = 0; + *ptr = nullptr; + bool in_repeat = false; + address_t original_address = address; + ElementsType::iterator b = elements_.begin(); + ElementsType::iterator e = elements_.end(); + // Align in the known repetitions first. + auto rit = repeats_.upper_bound(address); + int max_repeat = 0; + if (rit == repeats_.end()) + { + // not a repeat. + } + else + { + if (rit->start_ <= address && address < rit->end_) + { + // we are in the repeat. + unsigned cnt = (address - rit->start_) / rit->repeatSize_; + *repeat = cnt; + if (address + rit->repeatSize_ < rit->end_) + { + // Try one repetition later too. + max_repeat = 1; + } + // re-aligns address to the first repetition. + address -= cnt * rit->repeatSize_; + in_repeat = true; + b = elements_.lower_bound(rit->start_); + e = elements_.lower_bound(rit->start_ + rit->repeatSize_); + } + } + LOG(VERBOSE, + "searching for element at address %u in_repeat=%d address=%u " + "len=%u", + (unsigned)original_address, in_repeat, (unsigned)address, + (unsigned)len); + + for (int is_repeat = 0; is_repeat <= max_repeat; ++is_repeat) + { + auto it = std::upper_bound(b, e, address, DataComparator()); + if (it != elements_.begin()) + { + auto pit = it - 1; + // now: pit->address_ <= address + if (pit->address_ + pit->size_ > address) + { + // found overlap + *ptr = &*pit; + return (ssize_t)pit->address_ - + (ssize_t)address; // may be negative! + } + // else: no overlap, look at the next item + } + // now: it->address_ > address + if ((it != elements_.end()) && (address + len > it->address_)) + { + // found overlap, but some data needs to be discarded. + *ptr = &*it; + return it->address_ - address; + } + + if (in_repeat) + { + // We might be too close to the end of a repetition, we will + // try with the next repeat instead. + address -= rit->repeatSize_; + *repeat += 1; + if (original_address + rit->repeatSize_ >= rit->end_) + { + // We ran out of repeats. Look at the range beyond the + // group instead. + b = elements_.lower_bound(rit->end_); + e = elements_.end(); + *repeat = 0; + } + } + else + { + break; + } + } + + // now: no overlap either before or after. + LOG(VERBOSE, "element not found for address %u", + (unsigned)original_address); + return len; + } + + static constexpr unsigned NO_CACHE = static_cast(-1); + /// Offset in the memory space at which cachedData_ starts. + address_t cacheOffset_ = NO_CACHE; + /// Stored information for read-modify-write calls. + string cachedData_; + /// Container type for storing the data elements. + typedef SortedListSet ElementsType; + /// Stores all the registered variables. + ElementsType elements_; + /// Stores all the registered variables. + SortedListSet repeats_; + /// Helper object in the function calls. + BarrierNotifiable bn_; +}; // class VirtualMemorySpace + +} // namespace openlcb + +#endif // _OPENLCB_VIRTUALMEMORYSPACE_HXX diff --git a/src/openlcb/nmranet_constants.cxx b/src/openlcb/nmranet_constants.cxx index 87c9f3465..8476bf031 100644 --- a/src/openlcb/nmranet_constants.cxx +++ b/src/openlcb/nmranet_constants.cxx @@ -39,6 +39,9 @@ DEFAULT_CONST(remote_alias_cache_size, 10); /** Number of entries in the local alias cache */ DEFAULT_CONST(local_alias_cache_size, 3); +/** Keep this many allocated but unused aliases around. */ +DEFAULT_CONST(reserve_unused_alias_count, 0); + /** Maximum number of local nodes */ DEFAULT_CONST(local_nodes_count, 2); @@ -63,3 +66,7 @@ DEFAULT_CONST_FALSE(enable_all_memory_space); * identified messages at boot time. This is required by the OpenLCB * standard. */ DEFAULT_CONST_TRUE(node_init_identify); + +/** How many CAN frames should the bulk alias allocator be sending at the same + * time. */ +DEFAULT_CONST(bulk_alias_num_can_frames, 20); diff --git a/src/openlcb/sources b/src/openlcb/sources index 9f849e954..b7b35e734 100644 --- a/src/openlcb/sources +++ b/src/openlcb/sources @@ -8,6 +8,7 @@ CXXSRCS += \ BroadcastTime.cxx \ BroadcastTimeClient.cxx \ BroadcastTimeServer.cxx \ + BulkAliasAllocator.cxx \ CanDefs.cxx \ ConfigEntry.cxx \ ConfigUpdateFlow.cxx \ @@ -38,6 +39,7 @@ CXXSRCS += \ DatagramTcp.cxx \ MemoryConfig.cxx \ SimpleNodeInfo.cxx \ + SimpleNodeInfoResponse.cxx \ SimpleNodeInfoMockUserFile.cxx \ SimpleStack.cxx \ TractionTestTrain.cxx \ diff --git a/src/os/FakeClock.cxx b/src/os/FakeClock.cxx new file mode 100644 index 000000000..5b1c03441 --- /dev/null +++ b/src/os/FakeClock.cxx @@ -0,0 +1,57 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file FakeClock.cxx + * + * Helper class for unit tests that want to control the advancement of time by + * hand. + * + * @author Balazs Racz + * @date 28 Nov 2020 + */ + +#include "os/FakeClock.hxx" + +#ifdef GTEST + +extern "C" +{ + +long long os_get_fake_time(void) +{ + if (FakeClock::exists()) + { + return FakeClock::instance()->get_time_nsec(); + } + else + { + return -1; + } +} + +} // extern C + +#endif // GTEST diff --git a/src/os/FakeClock.cxxtest b/src/os/FakeClock.cxxtest new file mode 100644 index 000000000..e6a0a173b --- /dev/null +++ b/src/os/FakeClock.cxxtest @@ -0,0 +1,113 @@ +#include "os/FakeClock.hxx" + +#include "utils/test_main.hxx" + +TEST(FakeClockTest, advance) +{ + long long t1 = os_get_time_monotonic(); + usleep(20000); + long long t2 = os_get_time_monotonic(); + EXPECT_LT(t1 + MSEC_TO_NSEC(20), t2); + + FakeClock clk; + long long tfreeze = os_get_time_monotonic(); + // Upon startup the time should be pretty close. + EXPECT_GT(t2 + MSEC_TO_NSEC(1), tfreeze); + + // Time will not advance too much when frozen. + for (unsigned i = 0; i < 100; ++i) + { + EXPECT_GT(tfreeze + 500, os_get_time_monotonic()); + } + + // Advance should be accurate. + long long t3 = os_get_time_monotonic(); + clk.advance(MSEC_TO_NSEC(3540)); + long long t4 = os_get_time_monotonic(); + EXPECT_NEAR(t4, t3 + MSEC_TO_NSEC(3540), 10); + + // But still be monotonic. + t1 = os_get_time_monotonic(); + t2 = os_get_time_monotonic(); + EXPECT_EQ(1, t2 - t1); +} + +TEST(FakeClockTest, independent_test) +{ + // There should be no freezing left over for the next test. + long long t1 = os_get_time_monotonic(); + usleep(20000); + long long t2 = os_get_time_monotonic(); + EXPECT_LT(t1 + MSEC_TO_NSEC(20), t2); +} + +TEST(FakeClockTest, time_jumps_backwards) +{ + // This test verifies that when a fake clock is deleted, time will jump + // backwards. This is desirable, because the fake clock might have its time + // far in the future and we cannot affort to halt the monotonic clock until + // the real time catches up with the fake time. + long long t1 = os_get_time_monotonic(); + long long t2, t3, t4; + { + FakeClock clk; + t2 = os_get_time_monotonic(); + clk.advance(SEC_TO_NSEC(15)); + t3 = os_get_time_monotonic(); + } + t4 = os_get_time_monotonic(); + EXPECT_LT(t1, t2); + EXPECT_LT(t2, t3 - SEC_TO_NSEC(15)); + EXPECT_LT(t1, t4); + EXPECT_GT(t3, t4); +} + +class CountingTimer : public Timer +{ +public: + CountingTimer() + : Timer(g_executor.active_timers()) + { + start(MSEC_TO_NSEC(20)); + } + + long long timeout() override + { + ++count_; + if (needStop_) + { + return DELETE; + } + return RESTART; + } + + bool needStop_ = false; + int count_ = 0; +}; + +TEST(FakeClockTest, executor_timer) +{ + FakeClock clk; + CountingTimer *tim = new CountingTimer; + + EXPECT_EQ(0, tim->count_); + usleep(50000); + wait_for_main_executor(); + EXPECT_EQ(0, tim->count_); + clk.advance(MSEC_TO_NSEC(20) - 100); + wait_for_main_executor(); + EXPECT_EQ(0, tim->count_); + clk.advance(100); + wait_for_main_executor(); + EXPECT_EQ(1, tim->count_); + + clk.advance(MSEC_TO_NSEC(200)); + wait_for_main_executor(); + EXPECT_EQ(11, tim->count_); + clk.advance(MSEC_TO_NSEC(20)); + wait_for_main_executor(); + EXPECT_EQ(12, tim->count_); + tim->needStop_ = true; + clk.advance(MSEC_TO_NSEC(20)); + wait_for_main_executor(); +} diff --git a/src/os/FakeClock.hxx b/src/os/FakeClock.hxx new file mode 100644 index 000000000..fda615f86 --- /dev/null +++ b/src/os/FakeClock.hxx @@ -0,0 +1,94 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file FakeClock.hxx + * + * Helper class for unit tests that want to control the advancement of time by + * hand. + * + * @author Balazs Racz + * @date 28 Nov 2020 + */ + +#ifndef _OS_FAKECLOCK_HXX_ +#define _OS_FAKECLOCK_HXX_ + +#include "executor/Executor.hxx" +#include "os/os.h" +#include "utils/Singleton.hxx" + +/// Stores the private variables of a fake clock. +struct FakeClockContent +{ +protected: + /// @param t the starting timestamp for the fake clock. + FakeClockContent(long long t) + : lastTime_(t) + { + } + + long long lastTime_; +}; + +/// Class that injects a fake progression of time for unit tests. When this +/// class is created, the time as returned by os_get_time_monotonic() +/// freezes. From that point on time only moves forward when advance() is +/// called. +/// +/// There can be at most one instance of this class at any time. +class FakeClock : private FakeClockContent, public Singleton +{ +public: + FakeClock() + : FakeClockContent(os_get_time_monotonic()) + { + } + + /// Advances the time returned by os_get_time_monotonic(). + /// @param nsec how much the time should jump forward (relative to now). + void advance(long long nsec) + { + lastTime_ += nsec; + // Wakes up all executors. This will cause them to evaluate if their + // timers have something expired. + ExecutorBase *current = ExecutorBase::link_head(); + while (current) + { + current->add(new CallbackExecutable([]() {})); + current = current->link_next(); + } + } + + /// @return the currently set time. + long long get_time_nsec() + { + return lastTime_++; + } + +private: +}; + +#endif // _OS_FAKECLOCK_HXX_ diff --git a/src/os/MDNS.cxx b/src/os/MDNS.cxx index f84cde383..ffa5e1fa9 100644 --- a/src/os/MDNS.cxx +++ b/src/os/MDNS.cxx @@ -296,7 +296,7 @@ void MDNS::resolve_callback(AvahiServiceResolver *r, sa_in->sin6_flowinfo = 0; sa_in->sin6_family = AF_INET6; sa_in->sin6_port = htons(port); - memcpy(&sa_in->sin6_addr.s6_addr, + memcpy(&(sa_in->sin6_addr.s6_addr), address->data.ipv6.address, sizeof(address->data.ipv6.address)); break; diff --git a/src/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/os/OSSelectWakeup.cxx b/src/os/OSSelectWakeup.cxx index b040906e9..2fa97bf57 100644 --- a/src/os/OSSelectWakeup.cxx +++ b/src/os/OSSelectWakeup.cxx @@ -1,10 +1,110 @@ +/** \copyright +* Copyright (c) 2015, Balazs Racz +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +* \file OSSelectWakeup.cxx +* Helper class for portable wakeup of a thread blocked in a select call. +* +* @author Balazs Racz +* @date 10 Apr 2015 +*/ + #include "os/OSSelectWakeup.hxx" #include "utils/logging.h" +#if defined(__MACH__) +#define _DARWIN_C_SOURCE // pselect +#endif void empty_signal_handler(int) { } +int OSSelectWakeup::select(int nfds, fd_set *readfds, + fd_set *writefds, fd_set *exceptfds, + long long deadline_nsec) +{ + { + AtomicHolder l(this); + inSelect_ = true; + if (pendingWakeup_) + { + deadline_nsec = 0; + } + else + { +#if OPENMRN_FEATURE_DEVICE_SELECT + Device::select_clear(); +#endif + } + } +#if OPENMRN_FEATURE_DEVICE_SELECT + int ret = + Device::select(nfds, readfds, writefds, exceptfds, deadline_nsec); + if (!ret && pendingWakeup_) + { + ret = -1; + errno = EINTR; + } +#elif OPENMRN_HAVE_PSELECT + struct timespec timeout; + timeout.tv_sec = deadline_nsec / 1000000000; + timeout.tv_nsec = deadline_nsec % 1000000000; + int ret = + ::pselect(nfds, readfds, writefds, exceptfds, &timeout, &origMask_); +#elif OPENMRN_HAVE_SELECT +#ifdef ESP32 + fd_set newexcept; + if (!exceptfds) + { + FD_ZERO(&newexcept); + exceptfds = &newexcept; + } + FD_SET(vfsFd_, exceptfds); + if (vfsFd_ >= nfds) { + nfds = vfsFd_ + 1; + } +#endif //ESP32 + struct timeval timeout; + timeout.tv_sec = deadline_nsec / 1000000000; + timeout.tv_usec = (deadline_nsec / 1000) % 1000000; + int ret = + ::select(nfds, readfds, writefds, exceptfds, &timeout); +#elif !defined(OPENMRN_FEATURE_SINGLE_THREADED) + #error no select implementation in multi threaded OS. +#else + // Single threaded OS: nothing to wake up. + int ret = 0; +#endif + { + AtomicHolder l(this); + pendingWakeup_ = false; + inSelect_ = false; + } + return ret; +} + #ifdef ESP32 #include #include diff --git a/src/os/OSSelectWakeup.hxx b/src/os/OSSelectWakeup.hxx index 2471ba8fa..5d284256e 100644 --- a/src/os/OSSelectWakeup.hxx +++ b/src/os/OSSelectWakeup.hxx @@ -181,67 +181,7 @@ public: * asynchronously */ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, - long long deadline_nsec) - { - { - AtomicHolder l(this); - inSelect_ = true; - if (pendingWakeup_) - { - deadline_nsec = 0; - } - else - { -#if OPENMRN_FEATURE_DEVICE_SELECT - Device::select_clear(); -#endif - } - } -#if OPENMRN_FEATURE_DEVICE_SELECT - int ret = - Device::select(nfds, readfds, writefds, exceptfds, deadline_nsec); - if (!ret && pendingWakeup_) - { - ret = -1; - errno = EINTR; - } -#elif OPENMRN_HAVE_PSELECT - struct timespec timeout; - timeout.tv_sec = deadline_nsec / 1000000000; - timeout.tv_nsec = deadline_nsec % 1000000000; - int ret = - ::pselect(nfds, readfds, writefds, exceptfds, &timeout, &origMask_); -#elif OPENMRN_HAVE_SELECT -#ifdef ESP32 - fd_set newexcept; - if (!exceptfds) - { - FD_ZERO(&newexcept); - exceptfds = &newexcept; - } - FD_SET(vfsFd_, exceptfds); - if (vfsFd_ >= nfds) { - nfds = vfsFd_ + 1; - } -#endif //ESP32 - struct timeval timeout; - timeout.tv_sec = deadline_nsec / 1000000000; - timeout.tv_usec = (deadline_nsec / 1000) % 1000000; - int ret = - ::select(nfds, readfds, writefds, exceptfds, &timeout); -#elif !defined(OPENMRN_FEATURE_SINGLE_THREADED) - #error no select implementation in multi threaded OS. -#else - // Single threaded OS: nothing to wake up. - int ret = 0; -#endif - { - AtomicHolder l(this); - pendingWakeup_ = false; - inSelect_ = false; - } - return ret; - } + long long deadline_nsec); private: #ifdef ESP32 diff --git a/src/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 ee3100836..bec4b181e 100644 --- a/src/os/TempFile.hxx +++ b/src/os/TempFile.hxx @@ -115,10 +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); - write(s); - } + void rewrite(const string& s); /// writes the given data to the temporary file. @param s is the data to /// write. diff --git a/src/os/os.c b/src/os/os.c index d4d04b6af..d51c51f60 100644 --- a/src/os/os.c +++ b/src/os/os.c @@ -421,17 +421,15 @@ int __attribute__((weak)) os_thread_create_helper(os_thread_t *thread, void *priv) { HASSERT(thread); -#if (configSUPPORT_STATIC_ALLOCATION == 1) +#if (configSUPPORT_DYNAMIC_ALLOCATION == 1) + xTaskCreate(os_thread_start, (const char *const)name, + stack_size/sizeof(portSTACK_TYPE), priv, priority, thread); +#elif (configSUPPORT_STATIC_ALLOCATION == 1) *thread = xTaskCreateStatic(os_thread_start, (const char *const)name, stack_size/sizeof(portSTACK_TYPE), priv, priority, (StackType_t *)stack_malloc(stack_size), (StaticTask_t *) malloc(sizeof(StaticTask_t))); - task_new->task = *thread; - task_new->name = (char*)pcTaskGetTaskName(*thread); -#elif (configSUPPORT_DYNAMIC_ALLOCATION == 1) - xTaskCreate(os_thread_start, (const char *const)name, - stack_size/sizeof(portSTACK_TYPE), priv, priority, thread); #else #error FREERTOS version v9.0.0 or later required #endif @@ -625,6 +623,7 @@ long long os_get_time_monotonic(void) time *= clockmul; time >>= 2; #else + struct timespec ts; #if defined (__nuttx__) clock_gettime(CLOCK_REALTIME, &ts); @@ -632,7 +631,15 @@ long long os_get_time_monotonic(void) clock_gettime(CLOCK_MONOTONIC, &ts); #endif time = ((long long)ts.tv_sec * 1000000000LL) + ts.tv_nsec; - + +#ifdef GTEST + long long fake_time = os_get_fake_time(); + if (fake_time >= 0) + { + return fake_time; + } +#endif // not GTEST + #endif /* This logic ensures that every successive call is one value larger * than the last. Each call returns a unique value. diff --git a/src/os/os.h b/src/os/os.h index cda7da346..fe608e145 100644 --- a/src/os/os.h +++ b/src/os/os.h @@ -164,6 +164,11 @@ typedef struct */ extern long long os_get_time_monotonic(void); +/** Get the fake time for a unit test. + * @return time in nanoseconds, <= 0 if there is no fake clock. + */ +extern long long os_get_fake_time(void); + #ifndef OPENMRN_FEATURE_MUTEX_PTHREAD /** @ref os_thread_once states. */ diff --git a/src/os/sleep.h b/src/os/sleep.h new file mode 100644 index 000000000..a61280f16 --- /dev/null +++ b/src/os/sleep.h @@ -0,0 +1,53 @@ +/** \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 + +#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). +/// @param microseconds how long to delay. +extern "C" void microdelay(uint32_t microseconds); + +#endif // _OS_SLEEP_H_ diff --git a/src/utils/ActivityLed.hxx b/src/utils/ActivityLed.hxx new file mode 100644 index 000000000..0e78cd145 --- /dev/null +++ b/src/utils/ActivityLed.hxx @@ -0,0 +1,88 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file ActivityLed.hxx + * + * State flow that controls an activity LED based on triggers of events. + * + * @author Balazs Racz + * @date 26 Sep 2020 + */ + +#ifndef _UTILS_ACTIVITYLED_HXX_ +#define _UTILS_ACTIVITYLED_HXX_ + +#include "executor/StateFlow.hxx" +#include "os/Gpio.hxx" + +/// Operates an LED to visually display some activity. When the activity() +/// function is called at least once within a period, we turn the LED on for +/// the next period, if no call was made, the LED turns off for the next +/// period. +class ActivityLed : private ::Timer +{ +public: + /// Constructor. + /// @param service defines which executor this timer should be running on. + /// @param pin the LED of the output. Will be high for activity, low for + /// inactivity. Use InvertedGPIO if needed. + /// @param period defines in nanosecond the time to spend between updates. + ActivityLed( + Service *service, const Gpio *pin, long long period = MSEC_TO_NSEC(33)) + : ::Timer(service->executor()->active_timers()) + , gpio_(pin) + { + start(period); + } + + /// Call this function when activity happens. + void activity() + { + triggerCount_++; + } + +private: + long long timeout() override + { + if (triggerCount_) + { + gpio_->write(true); + triggerCount_ = 0; + } + else + { + gpio_->write(false); + } + return RESTART; + } + + /// Output pin to blink for activity. + const Gpio *gpio_; + /// How many triggers happened since the last run of the timer. + unsigned triggerCount_ {0}; +}; + +#endif // _UTILS_ACTIVITYLED_HXX_ 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 diff --git a/src/utils/BandwidthMerger.cxxtest b/src/utils/BandwidthMerger.cxxtest new file mode 100644 index 000000000..a7fa83578 --- /dev/null +++ b/src/utils/BandwidthMerger.cxxtest @@ -0,0 +1,92 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BandwidthMerger.cxxtest + * + * Unit tests for the stride scheduler. + * + * @author Balazs Racz + * @date 10 Oct 2020 + */ + +#include "utils/BandwidthMerger.hxx" + +#include "utils/test_main.hxx" + +class BandwidthMergerTest : public ::testing::Test +{ +protected: + /// Runs a simulation with a certain number of steps with a bandwidth + /// merger. + void collect_stats(uint8_t percent, unsigned num_steps) + { + BandwidthMerger m {percent}; + results_[0] = results_[1] = 0; + for (unsigned i = 0; i < num_steps; ++i) + { + if (m.step()) + { + ++results_[1]; + } + else + { + ++results_[0]; + } + } + } + + /// Results of the simulation. [0] is the number of steps with false + /// output, [1] is the number of steps with true output. + unsigned results_[2]; +}; + +TEST_F(BandwidthMergerTest, fifty) +{ + collect_stats(50, 34); + EXPECT_EQ(17u, results_[0]); + EXPECT_EQ(17u, results_[1]); +} + +TEST_F(BandwidthMergerTest, hundred) +{ + collect_stats(100, 34); + EXPECT_EQ(0u, results_[0]); + EXPECT_EQ(34u, results_[1]); +} + +TEST_F(BandwidthMergerTest, zero) +{ + collect_stats(0, 34); + EXPECT_EQ(34u, results_[0]); + EXPECT_EQ(0u, results_[1]); +} + +TEST_F(BandwidthMergerTest, ten) +{ + collect_stats(10, 34); + EXPECT_EQ(31u, results_[0]); + EXPECT_EQ(3u, results_[1]); +} diff --git a/src/utils/BandwidthMerger.hxx b/src/utils/BandwidthMerger.hxx new file mode 100644 index 000000000..caf32a2d4 --- /dev/null +++ b/src/utils/BandwidthMerger.hxx @@ -0,0 +1,89 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BandwidthMerger.hxx + * + * Simple stride scheduler for splitting bandwidth. + * + * @author Balazs Racz + * @date 10 Oct 2020 + */ + +#ifndef _UTILS_BANDWIDTHMERGER_HXX_ +#define _UTILS_BANDWIDTHMERGER_HXX_ + +#include + +#include "utils/macros.h" + +/// Simple stride scheduler. Emulates a two-way split of bandwidth between a +/// first source and a second source. The percentage assigned to the first +/// source is a parameter. Over time the fraction of outputs selected from the +/// first source converges to this percentage. +struct BandwidthMerger +{ + /// Constructor. + /// @param percent is the percentage (0..100) of the bandwidth that + /// should be assigned to the first source. + BandwidthMerger(uint8_t percent) + : percentFirst_(percent) + { + HASSERT(percentFirst_ <= 100); + } + /// Runs one step. + /// @return true if the first source is selected in this step. + bool step() + { + currentState_ += percentFirst_; + if (currentState_ >= 100) + { + currentState_ -= 100; + return true; + } + return false; + } + + /// If the step function returned false, but still the first source was + /// taken (e.g. because the second source was empty), call this function to + /// clear the state. This will avoid selecting the first source twice in a + /// row for example. + void reset() + { + // Reduces the state by 100 but clips it to zero. Since the state is + // always < 100, the clipping will always win. + currentState_ = 0; + } + + /// State of the current stride. This is always between 0..99. It + /// represents the fractional steps that the first source has accumulated + /// but not paid out in the form of selections yet. + uint8_t currentState_ {0}; + /// Percentage of the bandwidth that should be assigned to the first + /// source. Range 0..100. + uint8_t percentFirst_; +}; + +#endif // _UTILS_BANDWIDTHMERGER_HXX_ diff --git a/src/utils/Blinker.cxx b/src/utils/Blinker.cxx new file mode 100644 index 000000000..f094ebb30 --- /dev/null +++ b/src/utils/Blinker.cxx @@ -0,0 +1,96 @@ +/** \copyright + * Copyright (c) 2021, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Blinker.cxx + * + * Implementations of blinker routines + * + * @author Balazs Racz + * @date 27 Feb 2021 + */ + +#include "utils/blinker.h" + +extern "C" +{ + +unsigned parseblink(uint32_t pattern) +{ + unsigned ret = 0; + if (!pattern) + { + return ret; + } + // Finds the top bit. + uint32_t tb = 1U << 31; + while ((tb & pattern) == 0) + { + tb >>= 1; + } + unsigned nibble_shift = 1; + unsigned last_len = 1; + unsigned curr_len = 1; + pattern &= ~tb; + while (true) + { + if (curr_len) + { + if (pattern & 1) + { + curr_len++; + } + else + { + // end of lit period + if (last_len != curr_len) + { + // new blink length + nibble_shift <<= 4; + last_len = curr_len; + } + ret += nibble_shift; + curr_len = 0; + } + } + else + { + // we are in unlit + if (pattern & 1) + { + // start of a new lit period. + curr_len = 1; + } + } + if (!pattern) + { + break; + } + pattern >>= 1; + } + return ret; +} + +} diff --git a/src/utils/Blinker.cxxtest b/src/utils/Blinker.cxxtest new file mode 100644 index 000000000..2831a4054 --- /dev/null +++ b/src/utils/Blinker.cxxtest @@ -0,0 +1,14 @@ +#include "utils/blinker.h" + +#include "utils/test_main.hxx" + +TEST(BlinkerTest, parse_test) +{ + EXPECT_EQ(0u, parseblink(0)); + EXPECT_EQ(0x33u, parseblink(BLINK_DIE_ABORT)); + EXPECT_EQ(0x213u, parseblink(BLINK_DIE_HARDFAULT)); + EXPECT_EQ(0x123u, parseblink(BLINK_DIE_OUTOFMEM)); + EXPECT_EQ(0x313u, parseblink(BLINK_DIE_NMI)); + EXPECT_EQ(0x223u, parseblink(BLINK_DIE_ASSERT)); + EXPECT_EQ(0x133u, parseblink(BLINK_DIE_WATCHDOG)); +}; diff --git a/src/utils/Buffer.cxx b/src/utils/Buffer.cxx index 8682ee647..436ecc7df 100644 --- a/src/utils/Buffer.cxx +++ b/src/utils/Buffer.cxx @@ -32,11 +32,17 @@ */ #include "utils/Buffer.hxx" +#include "utils/ByteBuffer.hxx" DynamicPool *mainBufferPool = nullptr; +Pool *rawBufferPool = nullptr; Pool* init_main_buffer_pool() { + if (!rawBufferPool) + { + rawBufferPool = new DynamicPool(Bucket::init(sizeof(RawBuffer), 0)); + } if (!mainBufferPool) { mainBufferPool = diff --git a/src/utils/BusMaster.cxxtest b/src/utils/BusMaster.cxxtest new file mode 100644 index 000000000..770719e4d --- /dev/null +++ b/src/utils/BusMaster.cxxtest @@ -0,0 +1,160 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BusMaster.cxxtest + * + * Unit tests for the bus master pattern. + * + * @author Balazs Racz + * @date 31 Dec 2020 + */ + +#include "utils/BusMaster.hxx" + +#include "utils/test_main.hxx" +#include + +/// Our imaginary bus will send these as packets. +struct PacketPayload +{ + /// Identifies the bus activity that generated this packet. + unsigned source; +}; + +/// Declares the bus type. +using FakeBus = Bus; + +/// Collects the outgoing packets into a vector. +class FakeSink : public FakeBus::PacketSink +{ +public: + void send(FakeBus::Packet *pkt, unsigned prio) override + { + packets_.emplace_back(pkt); + if (shutdown_) + { + packets_.clear(); + } + } + + /// Call this for test shutdown. + void shutdown() + { + shutdown_ = true; + packets_.clear(); + } + + /// If true, all packets will be immediately returned. + bool shutdown_ {false}; + /// Packets that arrived. Front is the oldest packet, back is the newest. + std::deque packets_; +}; + +class FakeActivity : public FakeBus::Activity +{ +public: + FakeActivity(unsigned num) + : num_(num) + { + } + + void fill_packet(Packet *packet) override + { + packet->data()->source = num_; + } + +private: + /// Identifies this activity. Will be stored in the packets. + unsigned num_; +}; + +class BusMasterTest : public ::testing::Test +{ +protected: + ~BusMasterTest() + { + sink_.shutdown(); + master_.shutdown(); + wait_for_main_executor(); + } + + /// Simulates completing a packet in the sink. Expects another packet to + /// arrive and returns the source ID for the newly arrived packet. + /// @return pkt->source for the newly arrived packet. + unsigned get_next_packet() + { + EXPECT_EQ(3u, sink_.packets_.size()); + // Simulate sink completing a packet. + sink_.packets_.pop_front(); + wait_for_main_executor(); + + EXPECT_EQ(3u, sink_.packets_.size()); + return sink_.packets_.back()->data()->source; + } + + static constexpr unsigned IDLE = 0xFFFu; + FakeSink sink_; + FakeActivity idleActivity_ {IDLE}; + FakeBus::Master master_ {&g_service, &sink_, &idleActivity_, 3}; +}; + +const unsigned BusMasterTest::IDLE; + +TEST_F(BusMasterTest, create) +{ +} + +TEST_F(BusMasterTest, roundrobin) +{ + FakeActivity a1 {1}; + FakeActivity a2 {2}; + FakeActivity a3 {3}; + constexpr Fixed16 policy[] = {{1}}; + master_.set_policy(1, policy); + wait_for_main_executor(); + // There should be three idles now in the queue. + ASSERT_EQ(3u, sink_.packets_.size()); + EXPECT_EQ(IDLE, sink_.packets_[0]->data()->source); + EXPECT_EQ(IDLE, sink_.packets_[1]->data()->source); + EXPECT_EQ(IDLE, sink_.packets_[2]->data()->source); + + master_.schedule_activity(&a1, 0); + master_.schedule_activity(&a2, 0); + + // Simulate sink completing a packet: arrival is from activity 1. + EXPECT_EQ(1u, get_next_packet()); + // next from 2 + EXPECT_EQ(2u, get_next_packet()); + // then idle. + EXPECT_EQ(IDLE, get_next_packet()); + + master_.schedule_activity(&a1, 0); + master_.schedule_activity(&a2, 0); + master_.schedule_activity(&a3, 0); + EXPECT_EQ(1u, get_next_packet()); + EXPECT_EQ(2u, get_next_packet()); + EXPECT_EQ(3u, get_next_packet()); +} diff --git a/src/utils/BusMaster.hxx b/src/utils/BusMaster.hxx new file mode 100644 index 000000000..48ea3fc41 --- /dev/null +++ b/src/utils/BusMaster.hxx @@ -0,0 +1,181 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file BusMaster.hxx + * + * An abstract controller to manage a shared bus which needs to be polled. + * + * @author Balazs Racz + * @date 30 Dec 2020 + */ + +#ifndef _UTILS_BUSMASTER_HXX_ +#define _UTILS_BUSMASTER_HXX_ + +#include "executor/StateFlow.hxx" +#include "utils/Buffer.hxx" +#include "utils/LimitedPool.hxx" +#include "utils/ScheduledQueue.hxx" + +/// This is a namespace class that contains a variety of related classes for +/// handling a bus that needs a polling loop. +template class Bus +{ +public: + /// The buffer type that is handed around to the different participants. + using Packet = Buffer; + + /// Self-owned buffer type. + using PacketPtr = BufferPtr; + + /// This is the consumer of the generated packets. Usually a device driver + /// or something like that. + using PacketSink = FlowInterface; + + /// Abstract class that gets scheduled for polling and when the master + /// decides to take it, gets a buffer to fill in with a packet. + class Activity : public QMember + { + public: + using Packet = Bus::Packet; + using PacketPtr = Bus::PacketPtr; + + /// Complete a packet to send to the bus. + /// @param packet must be filled in the by callee. Ownership is + /// retained by the caller. + virtual void fill_packet(Packet *packet) = 0; + }; + + /// The Bus Master class. This keeps the scheduler of activities and owns a + /// variety of auxiliary objects and memory management. + class Master : public StateFlowBase + { + public: + /// Constructor. + /// @param s the executor to run the service on. + /// @param sink this is where the generated packets will be sent out. + /// @param idle when no activity is scheduled, this activity will be + /// invoked with the packet. This activity must be ready to render a + /// packet at all times. + /// @param num_enqueued this is how many packets we fill in and enqueue + /// for the sink. It is a tradeoff between the sink being out of work + /// vs the what is the lowest latency between enqueueing a high + /// priority activity and that getting the next packet. Recommended + /// values are 2 or 3. + Master( + Service *s, PacketSink *sink, Activity *idle, unsigned num_enqueued) + : StateFlowBase(s) + , idle_(idle) + , sink_(sink) + , pool_(sizeof(Packet), num_enqueued) + , numPacketsInPool_(num_enqueued) + , needShutdown_(false) + { + } + +#ifdef GTEST + /// Used in unittests to cleanly shutdown the bus master. + void request_shutdown() + { + needShutdown_ = true; + } + + /// Used in unittests to cleanly shutdown the bus master. + void shutdown() + { + needShutdown_ = true; + while (!is_terminated() && (pool_.free_items() < numPacketsInPool_)) + { + usleep(200); + } + } +#endif + + /// Sets the scheduling policy. This must be called exactly once after + /// construction before scheduling any bus activity. + void set_policy(unsigned num_prio, const Fixed16 *strides) + { + queue_.emplace(num_prio, strides); + start_flow(STATE(get_buffer)); + } + + /// Adds an activity to the bus master's scheduler. The activity must + /// be ready to fill in a packet right now. + /// @param a the activity to schedule. The caller retains ownership. + /// @param prio the priority band to schedule in. + void schedule_activity(Activity *a, unsigned prio) + { + queue_->insert(a, prio); + } + + private: + /// Start of scheduling flow. + Action get_buffer() + { + return allocate_and_call(sink_, STATE(fill_pkt), &pool_); + } + + /// Executes the scheduling decision, fills in the packet by the + /// selected activity and sends it to the bus sink. + Action fill_pkt() + { + auto buf = get_buffer_deleter(get_allocation_result(sink_)); + if (needShutdown_) + { + return exit(); + } + // Picks the next activity to do on the bus. + Activity *a = (Activity *)queue_->next().item; + if (!a) + { + a = idle_; + } + a->fill_packet(buf.get()); + sink_->send(buf.release()); + return call_immediately(STATE(get_buffer)); + } + + /// Handles the policy of polling. + uninitialized queue_; + /// If we have no activity to do, this activity gets invoked. It must + /// always be able to fill in a packet. + Activity *idle_; + /// Where to send the generated packets. + PacketSink *sink_; + /// Source of the buffers that we fill and hand out. + LimitedPool pool_; + /// Total number of packets that the pool can generate. + uint16_t numPacketsInPool_; + /// True if shutdown was requested. + uint16_t needShutdown_ : 1; + }; // class Master + +private: + /// This class cannot be instantiated. + Bus(); +}; + +#endif // _UTILS_BUSMASTER_HXX_ diff --git a/src/utils/ByteBuffer.hxx b/src/utils/ByteBuffer.hxx new file mode 100644 index 000000000..71602c1dc --- /dev/null +++ b/src/utils/ByteBuffer.hxx @@ -0,0 +1,96 @@ +/** \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 ByteBuffer.hxx + * + * Specialization of the Buffer / Pool infrastructure for untyped data + * stream. See { \link doc/byte_buffer.md } + * + * @author Balazs Racz + * @date 17 Apr 2022 + */ + +#ifndef _UTILS_BYTEBUFFER_HXX_ +#define _UTILS_BYTEBUFFER_HXX_ + +#include "utils/Buffer.hxx" + +/// This is how many bytes we have in each raw buffer allocation. +static constexpr unsigned RAWBUFFER_SIZE = 1024; + +/// Use this BufferPool to allocate raw buffers. +extern Pool* rawBufferPool; + +/// Container for holding an arbitrary untyped data stream. +struct RawData +{ + uint8_t payload[RAWBUFFER_SIZE]; +}; + +/// Buffers of this type will be allocated from the rawBufferPool to hold the +/// payloads of untyped data streams. These buffers are never enqueued into Q +/// or QList objects. +using RawBuffer = Buffer; + +/// Holds a reference to a raw buffer, with the start and size information. +struct ByteChunk +{ + /// Owns a ref for a RawData buffer. If this is nullptr, then the data + /// references by this chunk is externally owned. + BufferPtr ownedData_; + + /// Points to the first byte of the useful data. + uint8_t* data_ {nullptr}; + + /// How many bytes from data_ does this chunk represent. + size_t size_ {0}; + + /// @return number of bytes pointed to by this chunk. + size_t size() const + { + return size_; + } + + /// Moves forward the data beginning pointer by a given number of + /// bytes. Represents that some number of bytes were consumed by the sink. + /// @param num_bytes how much data was consumed. Must be <= size(). + void advance(size_t num_bytes) + { + HASSERT(num_bytes <= size_); + data_ += num_bytes; + size_ -= num_bytes; + } +}; + +/// Buffer type of references. These are enqueued for byte sinks. +using ByteBuffer = Buffer; + +template class FlowInterface; + +/// Interface for sending a stream of data from a source to a sink. +using ByteSink = FlowInterface; + +#endif // _UTILS_BYTEBUFFER_HXX_ diff --git a/src/utils/ClientConnection.hxx b/src/utils/ClientConnection.hxx index 60973ddde..35121c2bd 100644 --- a/src/utils/ClientConnection.hxx +++ b/src/utils/ClientConnection.hxx @@ -35,11 +35,13 @@ #ifndef _UTILS_CLIENTCONNECTION_HXX_ #define _UTILS_CLIENTCONNECTION_HXX_ -#include "utils/GridConnectHub.hxx" #include #include /* tc* functions */ #include +#include "utils/GridConnectHub.hxx" +#include "utils/socket_listener.hxx" + /// Abstract base class for the Hub's connections. class ConnectionClient { diff --git a/src/utils/Crc.cxx b/src/utils/Crc.cxx index 10af6db5c..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); } } @@ -169,3 +193,51 @@ void crc3_crc16_ibm(const void* data, size_t length_bytes, uint16_t* checksum) { checksum[1] = crc_16_ibm_finish(state2); checksum[2] = crc_16_ibm_finish(state3); } + +// static +uint8_t Crc8DallasMaxim::table256[256] = { + 0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, + 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41, + 0x9d, 0xc3, 0x21, 0x7f, 0xfc, 0xa2, 0x40, 0x1e, + 0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60, 0x82, 0xdc, + 0x23, 0x7d, 0x9f, 0xc1, 0x42, 0x1c, 0xfe, 0xa0, + 0xe1, 0xbf, 0x5d, 0x03, 0x80, 0xde, 0x3c, 0x62, + 0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d, + 0x7c, 0x22, 0xc0, 0x9e, 0x1d, 0x43, 0xa1, 0xff, + 0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5, + 0x84, 0xda, 0x38, 0x66, 0xe5, 0xbb, 0x59, 0x07, + 0xdb, 0x85, 0x67, 0x39, 0xba, 0xe4, 0x06, 0x58, + 0x19, 0x47, 0xa5, 0xfb, 0x78, 0x26, 0xc4, 0x9a, + 0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6, + 0xa7, 0xf9, 0x1b, 0x45, 0xc6, 0x98, 0x7a, 0x24, + 0xf8, 0xa6, 0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b, + 0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9, + 0x8c, 0xd2, 0x30, 0x6e, 0xed, 0xb3, 0x51, 0x0f, + 0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd, + 0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e, 0xcc, 0x92, + 0xd3, 0x8d, 0x6f, 0x31, 0xb2, 0xec, 0x0e, 0x50, + 0xaf, 0xf1, 0x13, 0x4d, 0xce, 0x90, 0x72, 0x2c, + 0x6d, 0x33, 0xd1, 0x8f, 0x0c, 0x52, 0xb0, 0xee, + 0x32, 0x6c, 0x8e, 0xd0, 0x53, 0x0d, 0xef, 0xb1, + 0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf, 0x2d, 0x73, + 0xca, 0x94, 0x76, 0x28, 0xab, 0xf5, 0x17, 0x49, + 0x08, 0x56, 0xb4, 0xea, 0x69, 0x37, 0xd5, 0x8b, + 0x57, 0x09, 0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4, + 0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16, + 0xe9, 0xb7, 0x55, 0x0b, 0x88, 0xd6, 0x34, 0x6a, + 0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8, + 0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9, 0xf7, + 0xb6, 0xe8, 0x0a, 0x54, 0xd7, 0x89, 0x6b, 0x35, +}; + +// static +uint8_t Crc8DallasMaxim::tableLo16[16] = { + 0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, + 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41, +}; + +// static +uint8_t Crc8DallasMaxim::tableHi16[16] = { + 0x00, 0x9d, 0x23, 0xbe, 0x46, 0xdb, 0x65, 0xf8, + 0x8c, 0x11, 0xaf, 0x32, 0xca, 0x57, 0xe9, 0x74 +}; diff --git a/src/utils/Crc.cxxtest b/src/utils/Crc.cxxtest index 3946b204a..2c5db06df 100644 --- a/src/utils/Crc.cxxtest +++ b/src/utils/Crc.cxxtest @@ -1,6 +1,9 @@ -#include "utils/test_main.hxx" #include "utils/Crc.hxx" +#include "utils/format_utils.hxx" +#include "utils/test_main.hxx" +#include + extern uint8_t reverse(uint8_t data); @@ -30,11 +33,218 @@ TEST(CrcIbmTest, Example3) { EXPECT_EQ("0459", actual); } +TEST(Crc3Test, Example) +{ + uint16_t data[3]; + crc3_crc16_ibm("12345678", 8, data); + EXPECT_EQ(0x3c9d, data[0]); + EXPECT_EQ(0x75a8, data[1]); + 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 + // the same as an example message from the BiDiB examples page: + // http://www.bidib.org/support/example1_e.html + static const uint8_t sample_message[] = { + 0x0B, 0x0A, 0x00, 0x00, 0x8E, 0x40, 0x00, 0x0D, 0x67, 0x00, 0x01, 0x00}; + + Crc8DallasMaxim c0; + Crc8DallasMaxim c16; + Crc8DallasMaxim c256; + + for (unsigned i = 0; i < ARRAYSIZE(sample_message); i++) { + c0.update0(sample_message[i]); + c16.update16(sample_message[i]); + c256.update256(sample_message[i]); + + EXPECT_EQ(c0.get(), c16.get()); + EXPECT_EQ(c0.get(), c256.get()); + } + EXPECT_EQ(0x4Cu, c0.get()); + + EXPECT_TRUE(c0.check_ok(0x4C)); + EXPECT_TRUE(c16.check_ok(0x4C)); + EXPECT_TRUE(c256.check_ok(0x4C)); + + // Consumes the CRC byte + c0.update0(0x4C); + c16.update16(0x4C); + c256.update256(0x4C); + // The message should check to zero now. + EXPECT_TRUE(c0.check_ok()); + EXPECT_TRUE(c16.check_ok()); + EXPECT_TRUE(c256.check_ok()); +} + +TEST(Crc8Test, Fuzz) +{ + // In the fuzz test we test the three different implementations of CRC8 + // against each other on random data. + unsigned int seed = 42; + + Crc8DallasMaxim c0; + Crc8DallasMaxim c16; + Crc8DallasMaxim c256; + + for (unsigned i = 0; i < 100000; i++) + { + int r1 = rand_r(&seed); + if (r1 % 100 == 0) + { + c0.init(); + c16.init(); + c256.init(); + } + uint8_t m = rand_r(&seed) & 0xFF; + c0.update0(m); + c16.update16(m); + c256.update256(m); + + EXPECT_EQ(c0.get(), c16.get()); + EXPECT_EQ(c0.get(), c256.get()); + } +} + +string get_sample(unsigned suffix) +{ + const uint8_t prefixbytes[] = {0, 0x99, 0x11, 0x22, 0x33}; + string s((const char *)prefixbytes, 5); + s.push_back(suffix & 0xff); + s[4] ^= (suffix >> 8); + return s; +} + +// Shows the collision behavior of CRC8. +TEST(Crc8Test, Collision) +{ + std::map rev_lookup; + unsigned c1, c2; + for (unsigned i = 0; i < 2 * 256; i++) + { + string s = get_sample(i); + Crc8DallasMaxim m; + for (unsigned j = 0; j < 6; j++) + { + m.update256(s[j]); + } + uint8_t crc = m.get(); + if (rev_lookup.find(crc) != rev_lookup.end()) + { + c1 = i; + c2 = rev_lookup[crc]; + LOG(INFO, "collision: %d %d on %02x", c1, c2, crc); + break; + } + rev_lookup[crc] = i; + } + string s1 = get_sample(c1), s2 = get_sample(c2); + Crc8DallasMaxim m1, m2; + for (unsigned i = 0; i < 6; i++) + { + m1.update256(s1[i]); + m2.update256(s2[i]); + } + LOG(INFO, "1 %02x %02x %d", m1.get(), m2.get(), s1 == s2); + for (unsigned i = 0; i < 6; i++) + { + m1.update256(s1[i]); + m2.update256(s2[i]); + } + LOG(INFO, "2 %02x %02x", m1.get(), m2.get()); + for (unsigned i = 0; i < 6; i++) + { + m1.update256(s1[i]); + m2.update256(s2[i]); + } + LOG(INFO, "3 %02x %02x", m1.get(), m2.get()); + for (unsigned cc = 0; cc < 256; cc++) + { + m1.init(); + m1.update256(cc); + m2.init(); + m2.update256(cc); + for (unsigned i = 0; i < 6; i++) + { + m1.update256(s1[i]); + m2.update256(s2[i]); + } + LOG(VERBOSE, "start %02x one %02x two %02x", cc, m1.get(), m2.get()); + } + LOG(INFO, "s1=%s s2=%s", string_to_hex(s1).c_str(), + string_to_hex(s2).c_str()); +} + +// These examples contain specific messages from other unittests and print +// their CRC. This pattern can be used for developing other tests. There are no +// expectations in these tests. +TEST(Crc8Test, LogonExample) +{ + Crc8DallasMaxim m; + m.update256(0x93); + m.update256(0x82); + m.update256(0x55); + m.update256(0xa7); + m.update256(0x03); + LOG(INFO, "example: 0x%02x", m.get()); +} + +TEST(Crc8Test, LogonExample2) +{ + Crc8DallasMaxim m; + m.update256(0xD5); + m.update256(0xA3); + m.update256(0x27); + m.update256(0x18); + m.update256(0x22); + LOG(INFO, "example: 0x%02x", m.get()); +} + +TEST(Crc8Test, LogonExample3) +{ + Crc8DallasMaxim m; + m.update256(253); + m.update256(0xaa); + m.update256(0xaa); + m.update256(0xaa); + m.update256(0xaa); + m.update256(0xaa); + LOG(INFO, "example: 0x%02x", m.get()); +} -TEST(Crc3Test, Example) { - uint16_t data[3]; - crc3_crc16_ibm("12345678", 8, data); - EXPECT_EQ(0x3c9d, data[0]); - EXPECT_EQ(0x75a8, data[1]); - EXPECT_EQ(0x0459, data[2]); +TEST(Crc8Test, LogonExample4) +{ + Crc8DallasMaxim m; + m.update256(254); + m.update256(0xaa); + m.update256(0xaa); + m.update256(0xaa); + m.update256(0xaa); + m.update256(0xaa); + LOG(INFO, "example: 0x%02x", m.get()); } diff --git a/src/utils/Crc.hxx b/src/utils/Crc.hxx index cb5f3402f..549e57b72 100644 --- a/src/utils/Crc.hxx +++ b/src/utils/Crc.hxx @@ -32,6 +32,9 @@ * @date 16 Dec 2014 */ +#ifndef _UTILS_CRC_HXX_ +#define _UTILS_CRC_HXX_ + #include #include @@ -59,3 +62,132 @@ uint16_t crc_16_ibm(const void* data, size_t length_bytes); * @param checksum is the output buffer where to store the 48-bit checksum. */ void crc3_crc16_ibm(const void* data, size_t length_bytes, uint16_t* checksum); + + +/// Helper class for computing CRC-8 according to Dallas/Maxim specification +/// for 1-wire protocol. This specification is used for BiDiB, RCN-218 and +/// S-9.2.1.1. +/// +/// This class can incrementally compute CRC byte by byte. There are three +/// implementations available, with different code space requirements. +class Crc8DallasMaxim { +public: + Crc8DallasMaxim() + : state_(0) + { + } + + /// Re-sets the state machine for checksumming a new message. + void init() + { + state_ = 0; + } + + /// @return the checksum of the currently consumed message. + uint8_t get() + { + return state_; + } + + /// Checks that the message has a correct CRC. This function assumes that + /// the CRC byte has already been consumed. + /// @return true if the message checksum is correct. + bool check_ok() + { + return (state_ == 0); + } + + /// Checks that the message has a correct CRC. This function assumes that + /// the CRC byte was not part of the message. + /// @param checksum_byte the CRC8 byte of the received message. + /// @return true if the message checksum is correct. + bool check_ok(uint8_t checksum_byte) + { + return (state_ == checksum_byte); + } + + /// Processes one byte of the incoming message. No lookup table will be + /// used. + /// @param message_byte next byte in the message. + void update0(uint8_t message_byte) + { + uint8_t data = state_ ^ message_byte; + uint8_t crc = 0; + if (data & 1) + { + crc ^= 0x5e; + } + if (data & 2) + { + crc ^= 0xbc; + } + if (data & 4) + { + crc ^= 0x61; + } + if (data & 8) + { + crc ^= 0xc2; + } + if (data & 0x10) + { + crc ^= 0x9d; + } + if (data & 0x20) + { + crc ^= 0x23; + } + if (data & 0x40) + { + crc ^= 0x46; + } + if (data & 0x80) + { + crc ^= 0x8c; + } + state_ = crc; + } + + /// Processes one byte of the incoming message. A small lookup table will be + /// used. + /// @param message_byte next byte in the message. + void update16(uint8_t message_byte) + { + uint8_t data = state_ ^ message_byte; + state_ = tableLo16[data & 0xf] ^ tableHi16[data >> 4]; + } + + /// Processes one byte of the incoming message. A 256-byte lookup table + /// will be used. + /// @param message_byte next byte in the message. + void update256(uint8_t message_byte) + { + state_ = table256[state_ ^ message_byte]; + } + +#ifdef CRC8_TABLE_SIZE + /// Processes one byte of the incoming message. + /// @param message_byte next byte in the message. + void update(uint8_t message_byte) + { + update##CRC8_TABLE_SIZE(message_byte); + } +#endif + +private: + // Of the static tables here only those will be linked into a binary which + // have been used there. + + /// 256-entry lookup table for the update256 function. + static uint8_t table256[256]; + + /// 16-entry lookup table for the update16 function. + static uint8_t tableHi16[16]; + /// 16-entry lookup table for the update16 function. + static uint8_t tableLo16[16]; + + /// Current value of the state register for the CRC computation. + uint8_t state_; +}; + +#endif // _UTILS_CRC_HXX_ diff --git a/src/utils/EEPROMEmuTest.hxx b/src/utils/EEPROMEmuTest.hxx index 5f04de8f0..bd5288b2b 100644 --- a/src/utils/EEPROMEmuTest.hxx +++ b/src/utils/EEPROMEmuTest.hxx @@ -59,6 +59,10 @@ static const char FILENAME[] = "/tmp/eeprom"; #define EELEN 32768 +void EEPROMEmulation::updated_notification() +{ +} + // We need to jump through some hoops to define a linker symbol // "__eeprom_start" in a place that is not actually constant. namespace foo { diff --git a/src/utils/EntryModel.cxxtest b/src/utils/EntryModel.cxxtest new file mode 100644 index 000000000..62d7ff409 --- /dev/null +++ b/src/utils/EntryModel.cxxtest @@ -0,0 +1,678 @@ +#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, 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()); + uem.pop_back(); + 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, SetLeadingZero) +{ + EntryModel em; + + // 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()); + EXPECT_FALSE(em.has_leading_zeros()); + + em.set_leading_zeros(1); + EXPECT_EQ(24, em.get_value()); + EXPECT_EQ("024", em.get_string()); + EXPECT_EQ(" 024", em.get_string(true)); + EXPECT_TRUE(em.has_leading_zeros()); + + em.set_leading_zeros(2); + EXPECT_EQ(24, em.get_value()); + EXPECT_EQ("0024", em.get_string()); + EXPECT_EQ("0024", em.get_string(true)); + EXPECT_TRUE(em.has_leading_zeros()); + + em.set_leading_zeros(0); + EXPECT_EQ("24", em.get_string()); + EXPECT_EQ(" 24", em.get_string(true)); + EXPECT_FALSE(em.has_leading_zeros()); + + // signed + // zero init + em.init(4, 10, 0); + EXPECT_EQ(4U, em.max_size()); + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("0", em.get_string()); + EXPECT_EQ(" 0", em.get_string(true)); + EXPECT_FALSE(em.has_leading_zeros()); + + em.set_leading_zeros(1); + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("00", em.get_string()); + EXPECT_EQ(" 00", em.get_string(true)); + EXPECT_TRUE(em.has_leading_zeros()); + + em.set_leading_zeros(0); + EXPECT_EQ(0, em.get_value()); + EXPECT_EQ("0", em.get_string()); + EXPECT_EQ(" 0", em.get_string(true)); + EXPECT_FALSE(em.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) +{ + 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()); +} + +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 b6c834f48..c1fdad299 100644 --- a/src/utils/EntryModel.hxx +++ b/src/utils/EntryModel.hxx @@ -38,393 +38,512 @@ #include #include #include +#include #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); // this will call 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; + } + if (isAtInitialValue_) + { + // 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) + { + value_ -= val; } - strncpy(data_, str.c_str(), sizeof(data_) - 1); - data_[sizeof(data_) - 1] = '\0'; - hasInitial_ = true; + 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: + push_back(c - '0'); + break; + case 16: + c = toupper(c); + 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_) + value_ /= base_; + if (value_ == 0 && numLeadingZeros_) { - refresh = true; - clear(); + --numLeadingZeros_; } - else + if (size_) { - if (hasInitial_) - { - hasInitial_ = false; - refresh = true; - clear(); - } - data_[index_++] = transform_ ? toupper(c) : c; + --size_; } - clamp(); - return refresh; - } - - /** Delete a character off the end. - */ - void backspace() - { - if (index_ == 0) + if (isAtInitialValue_) { - index_ = parsed().size(); + isAtInitialValue_ = false; + clamp(); + // need to compute the size now that the initial value is false + calculate_size(); + } + if (size_ == 0) + { + // 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_boundaries(); } - /** Set the value, keep the digits and base the same. - * @param value value to initialize with - */ + /// 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. + /// @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 max size (in digits). + /// @return max size in number of digits + size_t max_size() + { + return maxSize_; + } + + /// Test if the entry is "empty". Having an initial value is not empty. + /// @return true if empty, else false + bool empty() + { + 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 C style string representing the menu entry. - * @return the string data representing the menu entry - */ - const char *c_str() + /// Sets the number of leading zeros without changing the value. + void set_leading_zeros(unsigned num) { - return data_; + numLeadingZeros_ = num; + } + + /// 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) + { + clamp(true); + } + return value_; } - /** 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) + /// 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) { - const char *parse = data_; - string result; - result.reserve(N); - if (strip_leading) + string str; + if (isAtInitialValue_ || !empty_) { - while (*parse == '0' || *parse == ' ') + 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') + + if (numLeadingZeros_) + { + str.insert(value_ < 0 ? 1 : 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. 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) { - memmove(data_ + 1, data_, digits_ - 1); - data_[0] = '-'; + // skip clamping if we have space for more leading zeros + return; + } + if (force || !empty_) + { + 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()) + { + ++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 ? 1 : 0; + for (T tmp = value_ < 0 ? -value_ : value_; tmp != 0; tmp /= base_) { - clampCallback_(); + ++size_; } + numLeadingZeros_ = std::min(static_cast(numLeadingZeros_), + static_cast(maxSize_ - size_)); + size_ += numLeadingZeros_; } -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)) + /// Constructor. + EntryModelBounded() + : EntryModel() { } - /** 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) + /// 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) { - min_ = min; - max_ = max; - default_ = default_val; - EntryModel::init(digits, base, value); - } - - /// Set the value to the minimum. - void set_min() - { - EntryModel::set_value(min_); - } - - /// Set the value to the maximum. - void set_max() - { - 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. 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 { - 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_ diff --git a/src/utils/ExecutorWatchdog.hxx b/src/utils/ExecutorWatchdog.hxx new file mode 100644 index 000000000..4715ba8e9 --- /dev/null +++ b/src/utils/ExecutorWatchdog.hxx @@ -0,0 +1,87 @@ +/** \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 ExecutorWatchdog.hxx + * + * Watches an executor and prints a message if it was blocked. + * + * @author Balazs Racz + * @date 5 April 2021 + */ + +#ifndef _UTILS_EXECUTORWATCHDOG_HXX_ +#define _UTILS_EXECUTORWATCHDOG_HXX_ + +#include "executor/StateFlow.hxx" +#include "os/os.h" +#include "utils/logging.h" + +/// This stateflow checks an executor every 50 msec. If the latency of a wakeup +/// is more than 50 msec, then prints a warning of how long the executor was +/// blocked. +class ExecutorWatchdog : public StateFlowBase +{ +public: + /// Constructor. + /// @param service defines which executor to watch. + ExecutorWatchdog(Service *service) + : StateFlowBase(service) + { + start_flow(STATE(take_stamp)); + } + +private: + Action take_stamp() + { + lastTimeMsec_ = NSEC_TO_MSEC(os_get_time_monotonic()); + return sleep_and_call(&timer_, MSEC_TO_NSEC(50), STATE(woken)); + } + + Action woken() + { + uint32_t new_time_msec = NSEC_TO_MSEC(os_get_time_monotonic()); + auto diff = new_time_msec - lastTimeMsec_; + if (diff > 100) + { + LOG(WARNING, "[WARN] Executor was blocked for %d msec", + (int)(diff - 50)); + } + if (++count_ > (5000 / 50)) + { + count_ = 0; + LOG(INFO, "Watchdog alive."); + } + return call_immediately(STATE(take_stamp)); + } + + StateFlowTimer timer_ {this}; + /// Timestamp when we last went to sleep. + uint32_t lastTimeMsec_ {0}; + /// Counter that controls printing a heartbeat message that we are fine. + int count_ {0}; +}; + +#endif // _UTILS_EXECUTORWATCHDOG_HXX_ diff --git a/src/utils/FileUtils.cxx b/src/utils/FileUtils.cxx index c72b38011..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 @@ -106,10 +116,15 @@ void write_string_to_file(const string &filename, const string &data) while ((nr = fwrite(data.data() + offset, 1, data.size() - offset, f)) > 0) { offset += nr; - if (offset >= data.size()) break; + if (offset >= data.size()) + { + break; + } } - if (nr < 0) { - fprintf(stderr, "error writing: %s\n", strerror(errno)); + if (offset != data.size()) + { + fprintf(stderr, "error writing: %s, offset: %zu, size: %zu\n", + strerror(errno), offset, data.size()); } fclose(f); } diff --git a/src/utils/Fixed16.cxxtest b/src/utils/Fixed16.cxxtest index b46e550a5..14866a06b 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); @@ -170,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); @@ -213,3 +290,108 @@ TEST(Fixed16Test, SignedZero) // Adding and subtracting does not preserve the sign. 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. +/// @param base left operand of mulpow2 +/// @param shift right operand of mulpow2 +/// @param precision relative precisoin of how close the FIxed16 computation +/// should be to the real result. Example 0.01 for 1% precision. +void mulpow2_test(double base, double shift, double precision) +{ + double result = base * pow(2.0, shift); + string expl = StringPrintf("while computing %lg mulpow2 %lg", base, shift); + SCOPED_TRACE(expl); + Fixed16 fbase {Fixed16::FROM_DOUBLE, base}; + Fixed16 fshift {Fixed16::FROM_DOUBLE, shift}; + fbase.mulpow2(fshift); + EXPECT_NEAR(result, fbase.to_float(), result * precision); +} + +TEST(Fixed16Test, MulPow2) +{ + // General case is within 1% precision. + mulpow2_test(13, 1.385, 0.01); + mulpow2_test(13, -2.442, 0.01); + + // Integer shifts should work very well. + mulpow2_test(1, 4, 0.00001); + mulpow2_test(1.77429, 4, 0.00001); + mulpow2_test(1, -4, 0.00001); + mulpow2_test(1.77429, -4, 0.0001); + + // When the fractional part does not have a lot of binary digits, the + // result should be pretty good too. + mulpow2_test(30481, -12.5, 0.00001); + mulpow2_test(4377, 2.375, 0.0001); +} + +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 71e0d08fa..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,10 +135,90 @@ 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. + /// + /// Modifies *this. + /// @param o number of "bits" to shift with. May be positive or negative. + /// @return *this = *this * pow(2, o); + Fixed16 &mulpow2(Fixed16 o) + { + const Fixed16* coeffs; + uint16_t f; + if (o.is_positive()) + { + // multiplying + value_ <<= o.trunc(); + f = o.frac(); + static constexpr const Fixed16 pown[6] = { + {FROM_DOUBLE, 1.4142135623730951}, // 2^(1/2) + {FROM_DOUBLE, 1.1892071150027210}, // 2^(1/4) + {FROM_DOUBLE, 1.0905077326652577}, // 2^(1/8) + {FROM_DOUBLE, 1.0442737824274138}, // 2^(1/16) + {FROM_DOUBLE, 1.0218971486541166}, // 2^(1/32) + {FROM_DOUBLE, 1.0108892860517005}}; // 2^(1/64) + coeffs = pown; + } + else + { + // dividing + o.negate(); + value_ >>= o.trunc(); + f = o.frac(); + static constexpr const Fixed16 pown[6] = { + {FROM_DOUBLE, 0.7071067811865476}, // 2^(-1/2) + {FROM_DOUBLE, 0.8408964152537145}, // 2^(-1/4) + {FROM_DOUBLE, 0.9170040432046712}, // 2^(-1/8) + {FROM_DOUBLE, 0.9576032806985737}, // 2^(-1/16) + {FROM_DOUBLE, 0.9785720620877001}, // 2^(-1/32) + {FROM_DOUBLE, 0.9892280131939755}}; // 2^(-1/64) + coeffs = pown; + } + for (unsigned idx = 0, bit = 0x8000; idx < 6; ++idx, bit >>= 1) + { + if (f & bit) + { + *this *= coeffs[idx]; + } + } + return *this; } /// @return the value rounded to nearest integer. @@ -139,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; @@ -148,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; @@ -195,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. @@ -221,7 +318,7 @@ private: } value_ = v & 0x7fffffffu; } - + uint32_t value_ : 31; uint32_t sign_ : 1; }; diff --git a/src/utils/GcTcpHub.cxx b/src/utils/GcTcpHub.cxx index 6f94a510a..99ec6e1fe 100644 --- a/src/utils/GcTcpHub.cxx +++ b/src/utils/GcTcpHub.cxx @@ -38,17 +38,30 @@ #include "nmranet_config.h" #include "utils/GridConnectHub.hxx" -void GcTcpHub::OnNewConnection(int fd) +void GcTcpHub::on_new_connection(int fd) { const bool use_select = (config_gridconnect_tcp_use_select() == CONSTANT_TRUE); - create_gc_port_for_can_hub(canHub_, fd, nullptr, use_select); + { + AtomicHolder h(this); + numClients_++; + } + create_gc_port_for_can_hub(canHub_, fd, this, use_select); +} + +void GcTcpHub::notify() +{ + AtomicHolder h(this); + if (numClients_) + { + numClients_--; + } } GcTcpHub::GcTcpHub(CanHubFlow *can_hub, int port) : canHub_(can_hub) - , tcpListener_(port, std::bind(&GcTcpHub::OnNewConnection, this, - std::placeholders::_1)) + , tcpListener_(port, + std::bind(&GcTcpHub::on_new_connection, this, std::placeholders::_1)) { } diff --git a/src/utils/GcTcpHub.cxxtest b/src/utils/GcTcpHub.cxxtest index e8a1f6ba5..d24c6a8f3 100644 --- a/src/utils/GcTcpHub.cxxtest +++ b/src/utils/GcTcpHub.cxxtest @@ -64,6 +64,7 @@ protected: fprintf(stderr, "waiting for exiting.\r"); usleep(100000); } + EXPECT_EQ(0U, tcpHub_.get_num_clients()); } struct Client @@ -154,6 +155,7 @@ protected: TEST_F(GcTcpHubTest, CreateDestroy) { + EXPECT_EQ(0u, tcpHub_.get_num_clients()); } TEST_F(GcTcpHubTest, TwoClientsPingPong) @@ -165,6 +167,9 @@ TEST_F(GcTcpHubTest, TwoClientsPingPong) writeline(b.fd_, ":S001N01;"); EXPECT_EQ(":S001N01;", readline(a.fd_, ';')); EXPECT_EQ(3U, can_hub0.size()); + + EXPECT_EQ(2u, tcpHub_.get_num_clients()); + // Test writing outwards. send_packet(":S002N0102;"); EXPECT_EQ(":S002N0102;", readline(a.fd_, ';')); @@ -177,6 +182,7 @@ TEST_F(GcTcpHubTest, ClientCloseExpect) unsigned can_hub_size = can_hub0.size(); LOG(INFO, "can hub: %p ", &can_hub0); EXPECT_EQ(1U, can_hub_size); + EXPECT_EQ(0U, tcpHub_.get_num_clients()); { Client a; Client b; @@ -184,6 +190,7 @@ TEST_F(GcTcpHubTest, ClientCloseExpect) writeline(b.fd_, ":S001N01;"); EXPECT_EQ(":S001N01;", readline(a.fd_, ';')); EXPECT_EQ(can_hub_size + 2, can_hub0.size()); + EXPECT_EQ(2U, tcpHub_.get_num_clients()); wait(); } // Test writing outwards. diff --git a/src/utils/GcTcpHub.hxx b/src/utils/GcTcpHub.hxx index f81d55bbf..04888ed1f 100644 --- a/src/utils/GcTcpHub.hxx +++ b/src/utils/GcTcpHub.hxx @@ -43,7 +43,7 @@ class ExecutorBase; * format. Any new incoming connection will be wired into the same virtual CAN * hub. All packets will be forwarded to every participant, without * loopback. */ -class GcTcpHub +class GcTcpHub : private Notifiable, private Atomic { public: /// Constructor. @@ -60,16 +60,28 @@ public: return tcpListener_.is_started(); } + /// @return currently connected client count. + unsigned get_num_clients() + { + return numClients_; + } + private: /// Callback when a new connection arrives. /// /// @param fd filedes of the freshly established incoming connection. /// - void OnNewConnection(int fd); + void on_new_connection(int fd); + + /// Error callback from the gridconnect socket. This is invoked when a + /// client disconnects. + void notify() override; /// @param can_hub Which CAN-hub should we attach the TCP gridconnect hub /// onto. CanHubFlow *canHub_; + /// How many clients are connected right now. + unsigned numClients_ {0}; /// Helper object representing the listening on the socket. SocketListener tcpListener_; }; 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"); } } 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/JSHubPort.hxx b/src/utils/JSHubPort.hxx index 752d315c4..55c08fbd8 100644 --- a/src/utils/JSHubPort.hxx +++ b/src/utils/JSHubPort.hxx @@ -44,11 +44,29 @@ #include "utils/Hub.hxx" #include "utils/GridConnectHub.hxx" +class JSHubFeedback { +public: + /// Callback executed when the port successfully opened. + virtual void on_open() {} + + /// Callback executed when the port is closed. + virtual void on_close() {} + + /// Callback executed when the port encounters an error. + virtual void on_error(string error) {} + + static void call_on_error(unsigned long p, string error) + { + ((JSHubFeedback *)p)->on_error(error); + } +}; + class JSHubPort : public HubPortInterface { public: - JSHubPort(unsigned long parent, emscripten::val send_fn) + JSHubPort(unsigned long parent, emscripten::val send_fn, unsigned long feedback = 0) : parent_(reinterpret_cast(parent)) + , feedback_(reinterpret_cast(feedback)) , sendFn_(send_fn) , gcHub_(parent_->service()) , gcAdapter_( @@ -59,6 +77,10 @@ public: HASSERT(sendFn_.typeof().as() == "function"); gcHub_.register_port(this); active_ = true; + if (feedback_) + { + feedback_->on_open(); + } } ~JSHubPort() @@ -66,6 +88,22 @@ public: pause(); } + void fb_close() + { + if (feedback_) + { + feedback_->on_close(); + } + } + + void fb_error(string e) + { + if (feedback_) + { + feedback_->on_error(e); + } + } + void abandon() { pause(); @@ -120,6 +158,7 @@ public: private: CanHubFlow *parent_; + JSHubFeedback* feedback_; emscripten::val sendFn_; HubFlow gcHub_; std::unique_ptr gcAdapter_; @@ -130,12 +169,16 @@ private: EMSCRIPTEN_BINDINGS(js_hub_module) { emscripten::class_("JSHubPort") - .constructor() + // .constructor() + .constructor() .function("recv", &JSHubPort::recv) .function("pause", &JSHubPort::pause) .function("abandon", &JSHubPort::abandon) .function("get_port_num", &JSHubPort::get_port_num) .function("resume", &JSHubPort::resume); + + emscripten::class_("JSHubFeedback") + .class_function("call_on_error", &JSHubFeedback::call_on_error); } #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..42ff5016a 100644 --- a/src/utils/JSTcpClient.hxx +++ b/src/utils/JSTcpClient.hxx @@ -44,14 +44,29 @@ #include "utils/Hub.hxx" #include "utils/JSHubPort.hxx" -class JSTcpClient +class JSTcpClient : private JSHubFeedback { public: - JSTcpClient(CanHubFlow *hflow, string host, int port) + /// Argument to the connection callback. + enum ConnectionFeedback { + CONNECTION_UP, + CONNECTION_DOWN, + CONNECTION_ERROR + }; + + /// 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 on connection events (up/down/error) + JSTcpClient(CanHubFlow *hflow, string host, int port, + std::function cb = nullptr) : canHub_(hflow) + , callback_(std::move(cb)) { string script = "Module.remote_server = '" + host + "';\n"; emscripten_run_script(script.c_str()); + EM_ASM_( { var net = require('net'); @@ -63,23 +78,71 @@ public: c.setTimeout(0); c.setKeepAlive(true); var client_port = new Module.JSHubPort( - $1, function(data) { c.write(data); }); + $1, function(data) { c.write(data); }, $2); c.on('close', function() { console.log('connection lost'); + client_port.fb_close(); client_port.abandon(); }); - c.on('error', function() { + c.on('error', function(err) { console.log('connection error -- disconnected'); + client_port.fb_error(err.toString()); client_port.abandon(); }); c.on('data', function(data) { client_port.recv(data); }); }); + c.on('error', function(err) { + console.log('Failed to connect.'); + Module.JSHubFeedback.call_on_error($2, err.toString()); + }); }, - port, (unsigned long)canHub_); + port, (unsigned long)canHub_, (unsigned long)((JSHubFeedback*)this)); + } + + /// @return true if the connection is established. + bool is_connected() + { + return connected_; } private: + + /// Callback executed when the port successfully opened. + void on_open() override + { + connected_ = true; + LOG(INFO, "connected"); + if (callback_) + { + callback_(CONNECTION_UP); + } + } + + /// Callback executed when the port is closed. + void on_close() override + { + connected_ = false; + LOG(INFO, "closed"); + if (callback_) + { + callback_(CONNECTION_DOWN); + } + } + + /// Callback executed when the port encounters an error. + void on_error(string error) override + { + LOG(INFO, "%s", error.c_str()); + connected_ = false; + if (callback_) + { + callback_(CONNECTION_ERROR); + } + } + CanHubFlow *canHub_; + bool connected_ = false; + std::function callback_; }; #endif // __EMSCRIPTEN__ diff --git a/src/utils/LimitTimer.cxxtest b/src/utils/LimitTimer.cxxtest new file mode 100644 index 000000000..4e1e9ae1f --- /dev/null +++ b/src/utils/LimitTimer.cxxtest @@ -0,0 +1,136 @@ +/** \copyright + * Copyright (c) 2021, Stuart Baker + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @file LimitTimer.cxxtest + * + * Unit tests for LimitTimer. + * + * @author Stuart Baker + * @date 10 January 2021 + */ + +#include "utils/test_main.hxx" + +#include "utils/LimitTimer.hxx" +#include "os/FakeClock.hxx" + +class MockCallback +{ +public: + MOCK_METHOD0(callback, void()); +}; + +class MyLimitTimer +{ +public: + /// Constructor. + /// @param update_delay_msec cooldown time delay in milliseconds + /// @param max_tokens number of available tokens + MyLimitTimer(uint16_t update_delay_msec, + uint8_t max_tokens) + : mockCallback_() + , limitTimer_(&g_executor, update_delay_msec, max_tokens, + std::bind(&MockCallback::callback, &mockCallback_)) + { + } + + /// Destructor. + ~MyLimitTimer() + { + wait_for_main_executor(); + while (!g_executor.active_timers()->empty()) + { + sleep_helper(20); + } + } + + /// Helper function for sleeping. + /// @param clk fake clock or nullptr if no fake clock exists + /// @param len_msec how long to sleep + /// @param step_msec what granularity to use for sleeping wiht fake clock. + void sleep_helper(unsigned len_msec, unsigned step_msec = 50) + { + for (unsigned i = 0; i < len_msec; i += step_msec) + { + clk_.advance(MSEC_TO_NSEC(step_msec)); + wait_for_main_executor(); + } + } + + FakeClock clk_; + + ::testing::StrictMock mockCallback_; + LimitTimer limitTimer_; +}; + +TEST(LimitTimerTest, Create) +{ + MyLimitTimer(200, 3); +} + +TEST(LimitTimerTest, TryTake) +{ + MyLimitTimer lt(200, 3); + + ::testing::MockFunction check; + + ::testing::InSequence s; + + // flush out the tokens + EXPECT_CALL(lt.mockCallback_, callback()).Times(0); + EXPECT_TRUE(lt.limitTimer_.try_take()); + EXPECT_TRUE(lt.limitTimer_.try_take()); + EXPECT_TRUE(lt.limitTimer_.try_take()); + EXPECT_CALL(check, Call("1")); + check.Call("1"); + + // try to pull one more token out (that is not available) + EXPECT_CALL(lt.mockCallback_, callback()).Times(0); + EXPECT_FALSE(lt.limitTimer_.try_take()); + EXPECT_CALL(check, Call("2")); + check.Call("2"); + + // verify the callback after timeout + EXPECT_CALL(lt.mockCallback_, callback()).Times(1); + EXPECT_CALL(check, Call("3")); + lt.sleep_helper(200); + check.Call("3"); + + // timer should still be running as bucket gets refilled + lt.sleep_helper(200); + EXPECT_FALSE(g_executor.active_timers()->empty()); + + // pull a token from the bucket + lt.limitTimer_.take_no_callback(); + + // timer should still be running as bucket gets refilled + lt.sleep_helper(200); + EXPECT_FALSE(g_executor.active_timers()->empty()); + + // timer should stop running once bucket gets refilled + lt.sleep_helper(200); + EXPECT_TRUE(g_executor.active_timers()->empty()); +} diff --git a/src/utils/LimitTimer.hxx b/src/utils/LimitTimer.hxx new file mode 100644 index 000000000..a42ba6f1e --- /dev/null +++ b/src/utils/LimitTimer.hxx @@ -0,0 +1,141 @@ +/** @copyright + * Copyright (c) 2020, Balazs Racz; 2021 Stuart Baker + * All rights reserved + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @file LimitTimer.hxx + * + * Limits the number of updates per unit time. Initial version written by + * Balazs Racz, modified for generalization by Stuart Baker + * + * @author Balazs Racz, Stuart Baker + * @date 9 January 2021 + */ + +#ifndef _UTILS_LIMITTIMER_HXX_ +#define _UTILS_LIMITTIMER_HXX_ + +#include + +#include "executor/Timer.hxx" + +/// This timer takes care of limiting the number of speed updates we send +/// out in a second. It is a token bucket filter. +class LimitTimer : public Timer +{ +public: + /// Constructor. + /// @param ex executor to run on + /// @param update_delay_msec cooldown time delay in milliseconds + /// @param max_tokens number of available tokens, <= 127 max + /// @param callback callback called once after cooldown time delay + LimitTimer(ExecutorBase *ex, uint16_t update_delay_msec, uint8_t max_tokens, + std::function callback) + : Timer(ex->active_timers()) + , updateDelayMsec_(update_delay_msec) + , bucket_(std::min(static_cast(127), max_tokens)) + , bucketMax_(max_tokens) + , needUpdate_(false) + , callback_(callback) + { + HASSERT(callback); + } + + /// Destructor. + ~LimitTimer() + { + cancel(); + } + + /// Attempts to take a token out of the bucket. Must be called from the + /// same executor that was passed in the object construction. + /// @return true if the take is successful, false if there are no available + /// tokens, in which case there will be a callback generated when + /// tokens become available. + bool try_take() + { + if (bucket_ == bucketMax_) + { + start(MSEC_TO_NSEC(updateDelayMsec_)); + } + if (bucket_ > 0) + { + --bucket_; + return true; + } + else + { + needUpdate_ = true; + return false; + } + } + + /// Takes one entry from the bucket, and does not give a callback if + /// there is no entry left. Must be called from the + /// same executor that was passed in the object construction. + void take_no_callback() + { + if (bucket_ > 0) + { + --bucket_; + } + } + +private: + /// Callback from the timer infrastructure. Called periodically. + long long timeout() override + { + ++bucket_; + if (needUpdate_) + { + needUpdate_ = false; + callback_(); + } + if (bucket_ >= bucketMax_) + { + return NONE; + } + else + { + return RESTART; + } + } + + /// cooldown delay in msec + uint16_t updateDelayMsec_; + + /// number of available tokens + uint8_t bucket_ ; + + /// maximum number of tokens in the bucket + uint8_t bucketMax_ : 7; + + /// if non-zero, wake up parent when token is available. + uint8_t needUpdate_ : 1; + + /// callback after cooldown period. + std::function callback_; +}; + +#endif // _UTILS_LIMITTIMER_HXX_ diff --git a/src/utils/LinkedObject.hxx b/src/utils/LinkedObject.hxx index 93020fa52..463c03cf3 100644 --- a/src/utils/LinkedObject.hxx +++ b/src/utils/LinkedObject.hxx @@ -90,6 +90,12 @@ public: return static_cast(link_); } + /// @return the subclass pointer of the beginning of the list. + static T *link_head() + { + return static_cast(head_); + } + /// Locks the list for modification (at any entry!). static Atomic* head_mu() { return LinkedObjectHeadMutex::headMu_.get(); diff --git a/src/utils/LruCounter.cxxtest b/src/utils/LruCounter.cxxtest new file mode 100644 index 000000000..cbecefadd --- /dev/null +++ b/src/utils/LruCounter.cxxtest @@ -0,0 +1,293 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file LruCounter.cxxtest + * + * Unit tests for LruCounter. + * + * @author Balazs Racz + * @date 18 Sep 2020 + */ + +#include "utils/LruCounter.hxx" + +#include "utils/test_main.hxx" + +class LruCounterTest : public ::testing::Test +{ +protected: + GlobalLruCounter global_; + + void tick_n(unsigned n) + { + for (unsigned i = 0; i < n; ++i) + { + global_.tick(); + cb1.tick(global_); + cb2.tick(global_); + cb3.tick(global_); + cb4.tick(global_); + cs1.tick(global_); + cs2.tick(global_); + cs3.tick(global_); + cs4.tick(global_); + } + } + + void set_bits_per_bit(unsigned bpb) + { + new (&global_) GlobalLruCounter(bpb); + } + + /// Runs the sequence test on a given set of counters. + /// @param entries the counters + /// @param num_tick how much to wait between resetting each counter. + template + void sequence_test(std::initializer_list entries, unsigned num_tick) + { + for (T *e : entries) + { + EXPECT_EQ(0u, e->value()); + } + for (unsigned i = 1; i < entries.size(); i++) + { + tick_n(num_tick); + entries.begin()[i]->touch(); + } + tick_n(num_tick); + + for (unsigned i = 1; i < entries.size(); i++) + { + EXPECT_GT( + entries.begin()[i - 1]->value(), entries.begin()[i]->value()); + } + } + + /// Expects that an entry is going to flip forward to the next value in + /// num_tick counts. + template void next_increment(T *entry, unsigned num_tick) + { + LOG(INFO, "Next increment from %u", entry->value()); + unsigned current = entry->value(); + tick_n(num_tick - 1); + EXPECT_EQ(current, entry->value()); + tick_n(1); + EXPECT_EQ(current + 1, entry->value()); + } + + /// Byte sized LRU counters for testing. + LruCounter cb1, cb2, cb3, cb4; + /// Short sized LRU counters for testing. + LruCounter cs1, cs2, cs3, cs4; +}; + +TEST_F(LruCounterTest, create) +{ +} + +/// Tests that the initial value is zero and the reset value is zero. +TEST_F(LruCounterTest, initial) +{ + EXPECT_EQ(0u, cb1.value()); + EXPECT_EQ(0u, cs1.value()); + + cb1.touch(); + cs1.touch(); + + EXPECT_EQ(0u, cb1.value()); + EXPECT_EQ(0u, cs1.value()); +} + +/// Increments a counter through the first few values, which take exponentially +/// increasing tick count. +TEST_F(LruCounterTest, simple_increment) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cb1.value()); + tick_n(1); // 1 + EXPECT_EQ(1u, cb1.value()); + tick_n(1); // 2 + EXPECT_EQ(2u, cb1.value()); + tick_n(2); // 4 + EXPECT_EQ(3u, cb1.value()); + tick_n(4); // 8 + EXPECT_EQ(4u, cb1.value()); + tick_n(8); // 16 + EXPECT_EQ(5u, cb1.value()); +} + +/// Increments a 16-bit counter through the first few values, which take +/// exponentially increasing tick count. +TEST_F(LruCounterTest, simple_increment_short) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cs1.value()); + tick_n(1); // 1 + EXPECT_EQ(1u, cs1.value()); + tick_n(1); // 2 + EXPECT_EQ(2u, cs1.value()); + tick_n(2); // 4 + EXPECT_EQ(3u, cs1.value()); + tick_n(4); // 8 + EXPECT_EQ(4u, cs1.value()); + tick_n(8); // 16 + EXPECT_EQ(5u, cs1.value()); +} + +/// Increments a 2 bit/bit counter through the first few values, which take +/// exponentially increasing tick count. +TEST_F(LruCounterTest, simple_increment_2bit) +{ + EXPECT_EQ(0u, cb1.value()); + next_increment(&cb1, 1); // old value = 0, next tick = 1 + next_increment(&cb1, 3); // old value = 1, next tick = 4 + next_increment(&cb1, 12); // old value = 2, next tick = 16 + next_increment(&cb1, 16); // old value = 3, next tick = 32 + next_increment(&cb1, 32); // old value = 4, next tick = 64 + next_increment(&cb1, 64); // old value = 5, next tick = 128 + next_increment(&cb1, 64); // old value = 6, next tick = 192 + next_increment(&cb1, 64); // old value = 7, next tick = 256 + next_increment(&cb1, 256); // old value = 8, next tick = 512 +} + +/// Increments a 16-bit 2 bit/bit counter through the first few values, which +/// take exponentially increasing tick count. +TEST_F(LruCounterTest, simple_increment_short_2bit) +{ + EXPECT_EQ(0u, cs1.value()); + next_increment(&cs1, 1); // old value = 0, next tick = 1 + next_increment(&cs1, 3); // old value = 1, next tick = 4 + next_increment(&cs1, 12); // old value = 2, next tick = 16 + next_increment(&cs1, 16); // old value = 3, next tick = 32 + next_increment(&cs1, 32); // old value = 4, next tick = 64 + next_increment(&cs1, 64); // old value = 5, next tick = 128 + next_increment(&cs1, 64); // old value = 6, next tick = 192 + next_increment(&cs1, 64); // old value = 7, next tick = 256 + next_increment(&cs1, 256); // old value = 8, next tick = 512 +} + +/// Saturates a byte sized counter and expects that no overflow has happened. +TEST_F(LruCounterTest, no_overflow) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cb1.value()); + tick_n(100000); + EXPECT_EQ(255u, cb1.value()); + tick_n(1); + EXPECT_EQ(255u, cb1.value()); + tick_n(100000); + EXPECT_EQ(255u, cb1.value()); +} + +/// Checks that a 2 bit/bit exponent bytes sized counter can count more than a +/// few 100k ticks. +TEST_F(LruCounterTest, byte_range) +{ + set_bits_per_bit(2); + EXPECT_EQ(0u, cb1.value()); + tick_n(100000); + EXPECT_EQ(52u, cb1.value()); + tick_n(100000); + EXPECT_EQ(67u, cb1.value()); +} + +/// Tests resetting the counter, then incrementing. +TEST_F(LruCounterTest, reset) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cb1.value()); + tick_n(16); + EXPECT_EQ(5u, cb1.value()); + + cb1.touch(); + EXPECT_EQ(0u, cb1.value()); + tick_n(1); // 1 + EXPECT_EQ(1u, cb1.value()); + tick_n(1); // 2 + EXPECT_EQ(2u, cb1.value()); + tick_n(2); // 4 + EXPECT_EQ(3u, cb1.value()); + tick_n(4); // 8 + EXPECT_EQ(4u, cb1.value()); + tick_n(8); // 16 + EXPECT_EQ(5u, cb1.value()); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. +TEST_F(LruCounterTest, sequence) +{ + set_bits_per_bit(1); + EXPECT_EQ(0u, cb1.value()); + EXPECT_EQ(0u, cb2.value()); + EXPECT_EQ(0u, cb3.value()); + EXPECT_EQ(0u, cb4.value()); + + cb1.touch(); + tick_n(50); + cb2.touch(); + tick_n(50); + cb3.touch(); + tick_n(50); + cb4.touch(); + tick_n(50); + + EXPECT_GT(cb1.value(), cb2.value()); + EXPECT_GT(cb2.value(), cb3.value()); + EXPECT_GT(cb3.value(), cb4.value()); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. 1-byte, 1-bit-per-bit exponent +TEST_F(LruCounterTest, sequence_byte_1) +{ + set_bits_per_bit(1); + sequence_test({&cb1, &cb2, &cb3, &cb4}, 50); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. 2-byte, 1-bit-per-bit exponent +TEST_F(LruCounterTest, sequence_short_1) +{ + set_bits_per_bit(1); + sequence_test({&cs1, &cs2, &cs3, &cs4}, 50); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. 1-byte 2-bit-per-bit exponent +TEST_F(LruCounterTest, sequence_byte_2) +{ + set_bits_per_bit(2); + sequence_test({&cb1, &cb2, &cb3, &cb4}, 400); +} + +/// Tests several counters that were reset at different times. Their values +/// should be monotonic from their reset time. 2-byte 2-bit-per-bit exponent +TEST_F(LruCounterTest, sequence_short_2) +{ + set_bits_per_bit(2); + sequence_test({&cs1, &cs2, &cs3, &cs4}, 400); +} diff --git a/src/utils/LruCounter.hxx b/src/utils/LruCounter.hxx new file mode 100644 index 000000000..aec479ff0 --- /dev/null +++ b/src/utils/LruCounter.hxx @@ -0,0 +1,169 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file LruCounter.hxx + * + * A monotonic counter that is usable for approximate LRU age determination. + * + * @author Balazs Racz + * @date 18 Sep 2020 + */ + +#ifndef _UTILS_LRUCOUNTER_HXX_ +#define _UTILS_LRUCOUNTER_HXX_ + +#include + +template class LruCounter; + +/// The GlobalLruCounter and a set of LruCounter<> objects cooperate in order +/// to create an approximate LRU order over a set of objects. The particular +/// optimization criterion is that the memory storage per object should be very +/// low. (Target is one byte.) Touching an object is constant time, but there +/// is linear time background maintenance operations that need to run +/// regularly. Picking the oldest object is linear time. The oldest concept is +/// an approximation in that the older an object becomes the less time +/// granularity is available to distinguish exact age. This is generally fine +/// in applications. +/// +/// How to use: +/// +/// Create one GlobalLruCounter. Create for each tracked object an +/// LruCounter or LruCounter. +/// +/// Periodically call the tick() function once on the GlobalLruCounter, then +/// for each live object the tick(global) function. This is linear cost in the +/// number of tracked objects, so do it rather rarely (e.g. once per second). +/// +/// When a specific object is used, call the touch() function on it. +/// +/// When the oldest object needs to be selected, pick the one which has the +/// highest returned value() from its LruCounter<>. +/// +/// Theory of operation: +/// +/// The GlobalLruCounter maintains a global tick count. It gets incremented by +/// one in each tick. In the per-object local counter we only increment the +/// counter for a subset of the global ticks. How many global ticks we skip +/// between two local counter increments depends on the age of the object. The +/// older an object becomes the more rarely we increment the object's counter. +/// +/// Specifically, if the object counter has reached to be k bits long, then we +/// only increment it, when the global counter's bottom k bits are all +/// zero. Example: if the object counter is 35 (6 bits long), then we increment +/// it to 36 when the global counter is divisible by 64 (all 6 bottom bits are +/// zero). In a variant we double the zero-bits requirement, needing that the +/// bottom 12 bits are all zero. +/// +/// Example calculations, assuming 1 tick per second: +/// +/// +-------------------------------------------------------------------+ +/// | Exponent 1 bit/bit 2 bits/bit | +/// +------------+------------------------------------------------------+ +/// | data type: | | +/// | | | +/// | uint8_t | max count: ~43k max count: 9.5M | +/// | | (0.5 days) (110 days) | +/// | | end granularity: 256 end granularity: 64k | +/// | | (4 min) (0.5 days) | +/// +------------+------------------------------------------------------+ +/// | uint16_t | max count: ~2.8B max count: 161T | +/// | | (100 years) (5M years) | +/// | | end granularity: 64k end granularity: 4B | +/// | | (0.5 days) (136 years) | +/// +------------+------------------------------------------------------+ +class GlobalLruCounter +{ +public: + /// Constructor. + /// @param bits_per_bit How aggressive the exponential downsampling should + /// be. Meaningful values are 1 and 2. + GlobalLruCounter(unsigned bits_per_bit = 2) + : bitsPerBit_(bits_per_bit) + { + } + void tick() + { + ++tick_; + } + +private: + template friend class LruCounter; + /// Setting defining the exponent. + unsigned bitsPerBit_; + /// Rolling counter of global ticks. This is used by the local counters to + /// synchronize their increments. + unsigned tick_ {0}; +}; + +/// Create an instance of this type for each object whose age needs to be +/// measured with the GlobalLruCounter. For further details, see +/// { \link GlobalLruCounter }. +/// @param T is the storage type, typically uint8_t or uint16_t. +template class LruCounter +{ +public: + /// @return A value monotonic in the age of the current counter. + unsigned value() + { + return counter_; + } + + /// Increments the local counter. + /// @param global reference to the global tick counter. All calls must use + /// the same global counter. + void tick(const GlobalLruCounter &global) + { + if (!counter_) + { + ++counter_; + return; + } + if (counter_ == std::numeric_limits::max()) + { + // Counter is saturated. + return; + } + int nlz = __builtin_clz((unsigned)counter_); + int needzero = (32 - nlz) * global.bitsPerBit_; + if ((global.tick_ & ((1U << needzero) - 1)) == 0) + { + ++counter_; + } + } + + /// Signals that the object has been used now. + void touch() + { + counter_ = 0; + } + +private: + /// Internal counter. + T counter_ {0}; +}; + +#endif // _UTILS_LRUCOUNTER_HXX_ diff --git a/src/utils/MakeUnique.hxx b/src/utils/MakeUnique.hxx new file mode 100644 index 000000000..2e3b8e7ad --- /dev/null +++ b/src/utils/MakeUnique.hxx @@ -0,0 +1,50 @@ +/** + * \file MakeUnique.hxx + * + * C++11 version of std::make_unique which is only available from c++14 or + * later. + * + * This is based on https://isocpp.org/files/papers/N3656.txt. + * + * The __cplusplus constant reference is from: + * http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2014/n3938.html + * + */ + +// Check if we are building with less than C++14 and if so we need to define +// the std::make_unique() API. +#if __cplusplus < 201402L + +#include +#include +#include + +namespace std +{ + +template +unique_ptr make_unique_helper(false_type, Args&&... args) +{ + return unique_ptr(new T(forward(args)...)); +} + +template +unique_ptr make_unique_helper(true_type, Args&&... args) +{ + static_assert(extent::value == 0, + "make_unique() is forbidden, please use make_unique()."); + + typedef typename remove_extent::type U; + return unique_ptr(new U[sizeof...(Args)]{forward(args)...}); +} + +template +unique_ptr make_unique(Args&&... args) +{ + return make_unique_helper(is_array(), forward(args)...); +} + +} + +#endif // __cplusplus < 201402L + diff --git a/src/utils/Queue.hxx b/src/utils/Queue.hxx index 3a81d5f8e..bf7819c5d 100644 --- a/src/utils/Queue.hxx +++ b/src/utils/Queue.hxx @@ -38,6 +38,7 @@ #include #include +#include "openmrn_features.h" #include "executor/Executable.hxx" #include "executor/Notifiable.hxx" #include "os/OS.hxx" @@ -868,7 +869,7 @@ public: return result; } -#if !(defined(ESP_NONOS) || defined(ARDUINO)) +#if OPENMRN_FEATURE_SEM_TIMEDWAIT /** Wait for an item from the front of the queue. * @param timeout time to wait in nanoseconds * @return item retrieved from queue, else NULL with errno set: diff --git a/src/utils/ScheduledQueue.cxxtest b/src/utils/ScheduledQueue.cxxtest new file mode 100644 index 000000000..aaea7db7e --- /dev/null +++ b/src/utils/ScheduledQueue.cxxtest @@ -0,0 +1,382 @@ +#include "utils/ScheduledQueue.hxx" + +#include "utils/test_main.hxx" + +class ScheduledQueueTest : public ::testing::Test +{ +protected: + ScheduledQueueTest() + { + entries_.resize(100); + for (unsigned i = 0; i < entries_.size(); ++i) + { + entries_[i].idx = i; + } + } + + ~ScheduledQueueTest() + { + if (!q_) + return; + while (!q_->empty()) + { + take(); + } + for (unsigned i = 0; i < nextEntry_; ++i) + { + EXPECT_TRUE(entries_[i].queued); + EXPECT_TRUE(entries_[i].done); + } + } + + struct Entry : QMember + { + /// Which entry this is in the vector. + unsigned idx; + /// True if this entry was added to the queue. + bool queued {false}; + /// True if this entry was returned from the queue. + bool done {false}; + }; + + /// Adds a new entry to the queue. + /// @param prio which priority band to add to + /// @return the index of the new member. + unsigned add_empty(unsigned prio) + { + HASSERT(nextEntry_ < entries_.size()); + Entry *e = &entries_[nextEntry_++]; + e->queued = true; + q_->insert(e, prio); + return e->idx; + } + + /// Takes the next entry from the queue. Fills in lastIdx_ and lastPrio_. + void take() + { + auto ret = q_->next(); + if (!ret.item) + { + lastIdx_ = EMPTY; + lastPrio_ = EMPTY; + } + else + { + lastPrio_ = ret.index; + if (frequency_.size() <= lastPrio_) + { + frequency_.resize(lastPrio_ + 1); + } + frequency_[lastPrio_]++; + auto *e = (Entry *)ret.item; + lastIdx_ = e->idx; + ASSERT_TRUE(e->queued); + ASSERT_FALSE(e->done); + e->done = true; + } + } + + /// Takes an entry from the queue and returns its index. + unsigned take_idx() + { + take(); + return lastIdx_; + } + + /// Takes an entry from the queue and returns its priority. + unsigned take_prio() + { + take(); + return lastPrio_; + } + + /// Runs a statistical test with always full priority bands and returns the + /// percentage bandwidth allocated. + /// @param count how many iterations. + std::vector run_stat_test(unsigned count) + { + // Fills each priority band with 10 entries. + for (unsigned p = 0; p < q_->num_prio(); ++p) + { + for (unsigned i = 0; i < 2; i++) + { + add_empty(p); + } + } + + EXPECT_EQ(2 * q_->num_prio(), q_->pending()); + + for (unsigned i = 0; i < count; i++) + { + take(); + // re-add the same entry. + entries_[lastIdx_].done = false; + q_->insert(&entries_[lastIdx_], lastPrio_); + } + + std::vector v(frequency_.begin(), frequency_.end()); + for (unsigned p = 0; p < q_->num_prio(); ++p) + { + v[p] /= count; + } + return v; + } + + /// Pre-allocated entries. + vector entries_; + /// Index where to take next entry from. + unsigned nextEntry_ {0}; + /// The queue object under test. + std::unique_ptr q_; + /// Frequency of removals. index: priority. value: count. + vector frequency_; + + /// Index of the last returned entry. + unsigned lastIdx_ {0}; + /// Priority of the last returned entry. + unsigned lastPrio_ {0}; + + static constexpr unsigned EMPTY = 0xffffu; +}; + +TEST_F(ScheduledQueueTest, create) +{ +} + +TEST_F(ScheduledQueueTest, empty) +{ + constexpr Fixed16 ps[] = {{1, 0}}; + q_.reset(new ScheduledQueue(1, ps)); + + EXPECT_TRUE(q_->empty()); +} + +TEST_F(ScheduledQueueTest, fifo) +{ + constexpr Fixed16 ps[] = {{1, 0}}; + q_.reset(new ScheduledQueue(1, ps)); + add_empty(0); + add_empty(0); + add_empty(0); + EXPECT_EQ(0u, take_idx()); + EXPECT_EQ(1u, take_idx()); + add_empty(0); + EXPECT_EQ(2u, take_idx()); + add_empty(0); + add_empty(0); + EXPECT_EQ(3u, take_idx()); + EXPECT_EQ(4u, take_idx()); + + EXPECT_FALSE(q_->empty()); +} + +TEST_F(ScheduledQueueTest, strict_prio) +{ + constexpr Fixed16 ps[] = {{1, 0}, {1, 0}}; + q_.reset(new ScheduledQueue(2, ps)); + add_empty(0); + add_empty(1); + add_empty(0); + add_empty(1); + add_empty(0); + add_empty(1); + // We get back first the entries from the zero priority. + EXPECT_EQ(0u, take_idx()); + EXPECT_EQ(0u, lastPrio_); + EXPECT_EQ(2u, take_idx()); + EXPECT_EQ(0u, lastPrio_); + EXPECT_EQ(4u, take_idx()); + EXPECT_EQ(0u, lastPrio_); + + // Then the entries from the zero priority. + EXPECT_EQ(1u, take_idx()); + EXPECT_EQ(1u, lastPrio_); + EXPECT_EQ(3u, take_idx()); + EXPECT_EQ(1u, lastPrio_); +} + +TEST_F(ScheduledQueueTest, prio_pending_empty) +{ + constexpr Fixed16 ps[] = {{1, 0}, {1, 0}, {1, 0}, {1, 0}}; + q_.reset(new ScheduledQueue(4, ps)); + add_empty(0); + add_empty(1); + add_empty(2); + add_empty(2); + + // Checks empty and num pending by band + EXPECT_FALSE(q_->empty()); + EXPECT_EQ(4u, q_->pending()); + EXPECT_EQ(1u, q_->pending(0)); + EXPECT_EQ(1u, q_->pending(1)); + EXPECT_EQ(2u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + + take(); + + EXPECT_EQ(3u, q_->pending()); + EXPECT_EQ(0u, q_->pending(0)); + EXPECT_EQ(1u, q_->pending(1)); + EXPECT_EQ(2u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + EXPECT_FALSE(q_->empty()); + + take(); + + EXPECT_EQ(2u, q_->pending()); + EXPECT_EQ(0u, q_->pending(0)); + EXPECT_EQ(0u, q_->pending(1)); + EXPECT_EQ(2u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + EXPECT_FALSE(q_->empty()); + + take(); + + EXPECT_EQ(1u, q_->pending()); + EXPECT_EQ(0u, q_->pending(0)); + EXPECT_EQ(0u, q_->pending(1)); + EXPECT_EQ(1u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + EXPECT_FALSE(q_->empty()); + + take(); + + EXPECT_EQ(0u, q_->pending()); + EXPECT_EQ(0u, q_->pending(0)); + EXPECT_EQ(0u, q_->pending(1)); + EXPECT_EQ(0u, q_->pending(2)); + EXPECT_EQ(0u, q_->pending(3)); + EXPECT_TRUE(q_->empty()); +} + +TEST_F(ScheduledQueueTest, schedule_full) +{ + constexpr Fixed16 ps[] = {{Fixed16::FROM_DOUBLE, 0.5}, + {Fixed16::FROM_DOUBLE, 0.5}, {Fixed16::FROM_DOUBLE, 0.5}, + {Fixed16::FROM_DOUBLE, 0.5}}; + q_.reset(new ScheduledQueue(4, ps)); + // Fills each priority band with 10 entries. + for (unsigned p = 0; p < q_->num_prio(); ++p) + { + for (unsigned i = 0; i < 10; i++) + { + add_empty(p); + } + } + + EXPECT_EQ(40u, q_->pending()); + + // Every second comes from p0 + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(1u, take_prio()); + EXPECT_EQ(0u, take_prio()); + // every fourth from p2 + EXPECT_EQ(2u, take_prio()); + + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(1u, take_prio()); + EXPECT_EQ(0u, take_prio()); + + // every eight from p3 + EXPECT_EQ(3u, take_prio()); + + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(1u, take_prio()); + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(2u, take_prio()); + + EXPECT_EQ(0u, take_prio()); + EXPECT_EQ(1u, take_prio()); + EXPECT_EQ(0u, take_prio()); + + // There is no p4, so p3 will repeat here. + EXPECT_EQ(3u, take_prio()); +} + +TEST_F(ScheduledQueueTest, statistical) +{ + constexpr Fixed16 ps[] = {{Fixed16::FROM_DOUBLE, 0.2}, + {Fixed16::FROM_DOUBLE, 0.2}, {Fixed16::FROM_DOUBLE, 0.5}, + {Fixed16::FROM_DOUBLE, 0.5}}; + q_.reset(new ScheduledQueue(4, ps)); + + std::vector bw_frac = run_stat_test(10000); + + // 20% of bandwidth to p0 + EXPECT_NEAR(0.2, bw_frac[0], 0.01); + // 80% * 20% = 16% of bandwidth to p1 + EXPECT_NEAR(0.16, bw_frac[1], 0.01); + // 80% * 80% * 50% = 32% of bandwidth to p2 + EXPECT_NEAR(0.32, bw_frac[2], 0.01); + // same to p3 + EXPECT_NEAR(0.32, bw_frac[3], 0.01); +} + +TEST_F(ScheduledQueueTest, statistical_skewed) +{ + constexpr Fixed16 ps[] = {{Fixed16::FROM_DOUBLE, 0.8}, + {Fixed16::FROM_DOUBLE, 0.8}, {Fixed16::FROM_DOUBLE, 0.8}, + {Fixed16::FROM_DOUBLE, 0.5}}; + q_.reset(new ScheduledQueue(4, ps)); + + std::vector bw_frac = run_stat_test(10000); + + // 80% of bandwidth to p0 + EXPECT_NEAR(0.8, bw_frac[0], 0.01); + // 20% * 80% = 16% of bandwidth to p1 + EXPECT_NEAR(0.16, bw_frac[1], 0.01); + // 20% * 20% * 80% = 3.2% of bandwidth to p2 + EXPECT_NEAR(0.032, bw_frac[2], 0.001); + // 20% * 20% * 20% = 0.8% of bandwidth to p2 + EXPECT_NEAR(0.008, bw_frac[3], 0.001); +} + +TEST_F(ScheduledQueueTest, schedule_with_empties) +{ + constexpr Fixed16 ps[] = {{Fixed16::FROM_DOUBLE, 0.4}, + {Fixed16::FROM_DOUBLE, 0.22}, {Fixed16::FROM_DOUBLE, 0.1}, + {Fixed16::FROM_DOUBLE, 1}}; + q_.reset(new ScheduledQueue(4, ps)); + + // We leave p0 empty for now + add_empty(1); + add_empty(1); + add_empty(1); + add_empty(2); + add_empty(2); + + // First nonempty is found on p1. + EXPECT_EQ(1u, take_prio()); + // The next will be sent down, finds the first nonempty on p2. + EXPECT_EQ(2u, take_prio()); + // The next token will skip p2, but find nothing more so traces back to + // take p2 again. + EXPECT_EQ(2u, take_prio()); + // Now p1 is still not scheduled to arrive but that's the only nonempty. + EXPECT_EQ(1u, take_prio()); + + // Now stocking up lower priorities will cause p1 to skip quite a few. + add_empty(2); + add_empty(2); + add_empty(2); + add_empty(2); + add_empty(2); + add_empty(2); + add_empty(2); + + // p1 is not scheduled to send + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + // now p1 exceeded the token threshold + EXPECT_EQ(1u, take_prio()); + + // remaining entries + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + EXPECT_EQ(2u, take_prio()); + + // Empty + EXPECT_EQ((unsigned)EMPTY, take_prio()); +} diff --git a/src/utils/ScheduledQueue.hxx b/src/utils/ScheduledQueue.hxx new file mode 100644 index 000000000..723d4b37a --- /dev/null +++ b/src/utils/ScheduledQueue.hxx @@ -0,0 +1,223 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file ScheduledQueue.hxx + * + * A Queue implementation that has a priority based scheduler on the different + * bands. + * + * @author Balazs Racz + * @date 30 Dec 2020 + */ + +#ifndef _UTILS_SCHEDULEDQUEUE_HXX_ +#define _UTILS_SCHEDULEDQUEUE_HXX_ + +#include "os/OS.hxx" +#include "utils/Fixed16.hxx" +#include "utils/Queue.hxx" +#include "utils/logging.h" + +/// ScheduledQueue is a queue with multiple priorities, where each priority is +/// a FIFO list. The different priorities are polled according to a weighted +/// stride scheduler instead of in strict numerical priority order. +class ScheduledQueue +{ +public: + /// Constructor. + /// @param num_bands now many priority bands should there be. + /// @param strides is an array of the size num_bands. It contains the + /// stride coefficients (they should all be numbers <= 1. A value of 1 + /// degrades to strict priority order). This array is not used anymore + /// after the constructor returns. A stride of 0.2 means that 20% of all + /// tokens at this level will be allocated to this band, and 80% of the + /// tokens are passed down to lower priorities (bands with higher index). + ScheduledQueue(unsigned num_bands, const Fixed16 *strides) + : numBands_(num_bands) + , bands_(new Band[num_bands]) + { + for (unsigned i = 0; i < numBands_; i++) + { + bands_[i].stride_ = strides[i]; + bands_[i].currentToken_ -= strides[i]; + } + } + + /// Destructor. + ~ScheduledQueue() + { + delete[] bands_; + } + + /// Get an item from the queue. The returned item will be according to the + /// priority scheduler. + /// @return the member and the priority from which it came. + Result next() + { + OSMutexLock h(lock()); + return next_locked(); + } + + /// Get an item from the queue. The returned item will be according to the + /// priority scheduler. The caller must acquire the lock() first. + /// @return the member and the priority from which it came. + Result next_locked() + { + if (!numPending_) + { + // Empty queue. + return Result(0, 0); + } + // Execute the priority based scheduling algorithm. + for (unsigned i = 0; i < numBands_; ++i) + { + bands_[i].currentToken_ += bands_[i].stride_; + if (bands_[i].currentToken_.trunc() >= 1) + { + Result ret = bands_[i].queue_.next_locked(); + if (ret.item) + { + ret.index = i; + --numPending_; + bands_[i].currentToken_ -= 1; + return ret; + } + else + { + // This queue has a token but is empty. We remove + // fractional tokens and keep searching onwards in the + // priorities. + bands_[i].currentToken_ = 1; + } + } + } + // Fallen off at the end. We go backwards to find any queue with + // nonempty members. + for (int i = numBands_ - 1; i >= 0; --i) + { + if (!bands_[i].queue_.empty()) + { + Result ret = bands_[i].queue_.next_locked(); + bands_[i].currentToken_ = 0; + ret.index = i; + --numPending_; + return ret; + } + } + DIE("Unexpected nonempty queue"); + return Result(0, 0); + } + + /// The caller must acquire this lock before using any of the _locked() + /// functions. If the caller needs to do many operations in quick + /// succession, it might be faster to do them under a single lock, + /// i.e. acquire lock() first, then call xxx_locked() repeatedly, then + /// unlock. + /// @return the lock to use for the _locked() functions. + OSMutex *lock() + { + return &lock_; + } + + /// Adds an entry to the queue. It will be added to the end of the given + /// priority band. + /// @param item the entry to be added to the queue. + /// @param prio which priority band to add to. 0 = highest priority. Must + /// be within 0 and numBands_ - 1. + void insert(QMember *item, unsigned prio) + { + OSMutexLock h(lock()); + return insert_locked(item, prio); + } + + /// Adds an entry to the queue. It will be added to the end of the given + /// priority band. The caller must hold lock(). + /// @param item the entry to be added to the queue. + /// @param prio which priority band to add to. 0 = highest priority. Must + /// be within 0 and numBands_ - 1. + void insert_locked(QMember *item, unsigned prio) + { + HASSERT(prio < numBands_); + ++numPending_; + bands_[prio].queue_.insert_locked(item); + } + + /// Get the number of pending items in the queue. + /// @param prio in the list to operate on + /// @return number of pending items in that priority band in the queue + size_t pending(unsigned prio) + { + HASSERT(prio < numBands_); + return bands_[prio].queue_.pending(); + }; + + /// Get the number of pending items in the queue (all bands total) + /// @return number of pending items + size_t pending() const + { + return numPending_; + } + + /// @return true if the queue is empty (on all priority bands). + bool empty() const + { + return numPending_ == 0; + } + + /// @return the number of available priority bands. + unsigned num_prio() const + { + return numBands_; + } + +private: + /// This structure contains information about one priority band. + struct Band + { + /// Holds the queue for this priority band. + Q queue_; + /// How many tokens we add each call. + Fixed16 stride_ {0}; + /// How many tokens we have right now. If this is > 1 then we will emit + /// the front of this queue, if it is < 1 then we move on to the next + /// priority item. + Fixed16 currentToken_ {1, 0}; + }; + + /// Protects insert and next operations. + OSMutex lock_; + + /// How many priority bands we have. + unsigned numBands_; + + /// How many queue entries are pending. + unsigned numPending_ {0}; + + /// The actual priority bands. + Band *bands_; +}; + +#endif // _UTILS_SCHEDULEDQUEUE_HXX_ diff --git a/src/utils/SocketCan.cxx b/src/utils/SocketCan.cxx new file mode 100644 index 000000000..791b46c8a --- /dev/null +++ b/src/utils/SocketCan.cxx @@ -0,0 +1,101 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SocketCan.cxx + * + * Helper functions to connect to CAN devices via SocketCan. + * + * @author Balazs Racz + * @date 1 Sep 2020 + */ + +#include "utils/SocketCan.hxx" +#include "can_frame.h" +#include + +#if defined(__linux__) + +#include +#include +#include +#include +#include + +/// This macro executes an OS call, and if it returns negative result, then +/// prints the errno to stderr, and terminates the current function with -1 +/// return value. +/// @param where textual description of what function was called +/// (e.g. "socket") +/// @param x... the function call. +#define ERRNOLOG(where, x...) \ + do \ + { \ + if ((x) < 0) \ + { \ + perror(where); \ + return -1; \ + } \ + } while (0) + +int socketcan_open(const char *device, int loopback) +{ + int s; + struct sockaddr_can addr; + struct ifreq ifr; + + s = socket(PF_CAN, SOCK_RAW, CAN_RAW); + ERRNOLOG("socket", s); + + // Set the blocking limit to the minimum allowed, typically 1024 in Linux + int sndbuf = 0; + ERRNOLOG("setsockopt(sndbuf)", + setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf))); + + // turn on/off loopback + ERRNOLOG("setsockopt(loopback)", + setsockopt( + s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback))); + + // setup error notifications + can_err_mask_t err_mask = CAN_ERR_TX_TIMEOUT | CAN_ERR_LOSTARB | + CAN_ERR_CRTL | CAN_ERR_PROT | CAN_ERR_TRX | CAN_ERR_ACK | + CAN_ERR_BUSOFF | CAN_ERR_BUSERROR | CAN_ERR_RESTARTED; + ERRNOLOG("setsockopt(filter)", + setsockopt( + s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &err_mask, sizeof(err_mask))); + strcpy(ifr.ifr_name, device); + + ERRNOLOG("interface set", ::ioctl(s, SIOCGIFINDEX, &ifr)); + + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + + ERRNOLOG("bind", bind(s, (struct sockaddr *)&addr, sizeof(addr))); + + return s; +} + +#endif diff --git a/src/utils/SocketCan.hxx b/src/utils/SocketCan.hxx new file mode 100644 index 000000000..9e14354b4 --- /dev/null +++ b/src/utils/SocketCan.hxx @@ -0,0 +1,49 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file SocketCan.hxx + * + * Helper functions to connect to CAN devices via SocketCan. + * + * @author Balazs Racz + * @date 1 Sep 2020 + */ + +#ifndef _UTILS_SOCKETCAN_HXX_ +#define _UTILS_SOCKETCAN_HXX_ + +#if defined(__linux__) + +/// Opens a SocketCan socket. +/// @param device the name of the CAN device, e.g. can0 +/// @param loopback 1 to enable loopback locally to other open references, +/// 0 to disable loopback locally to other open references. +/// @return an open socket file descriptor, or -1 if there was an error. +int socketcan_open(const char *device, int loopback); + +#endif + +#endif // _UTILS_SOCKETCAN_HXX_ diff --git a/src/utils/SocketClient.cxxtest b/src/utils/SocketClient.cxxtest index 199bfd560..1277fb50c 100644 --- a/src/utils/SocketClient.cxxtest +++ b/src/utils/SocketClient.cxxtest @@ -151,9 +151,9 @@ TEST_F(SocketClientTest, connect_mdns) std::bind(&SocketClientTest::connect_callback, this, _1, _2))); EXPECT_CALL(*this, connect_callback(_, sc_.get())).Times(1); - usleep(10000); + usleep(20000); wait(); - usleep(10000); + usleep(20000); wait(); } diff --git a/src/utils/SocketClient.hxx b/src/utils/SocketClient.hxx index f649e9ad0..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 @@ -47,10 +42,12 @@ #endif #include #include +#include #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" @@ -169,7 +166,7 @@ public: } while (!is_terminated()) { - usleep(1000); + microsleep(1000); } } diff --git a/src/utils/SortedListMap.hxx b/src/utils/SortedListMap.hxx index 293727d58..7a65bc45d 100644 --- a/src/utils/SortedListMap.hxx +++ b/src/utils/SortedListMap.hxx @@ -59,10 +59,19 @@ public: /// Const Iterator type. typedef typename container_type::const_iterator const_iterator; - SortedListSet() + template + SortedListSet(Args &&...args) + : cmp_(std::forward(args)...) { } + /// Ensures that a given size can be reached without memory allocation. + /// @param sz the number of entries to prepare for. + void reserve(size_t sz) + { + container_.reserve(sz); + } + /// @return first iterator. iterator begin() { @@ -86,8 +95,8 @@ public: iterator lower_bound(key_type key) { lazy_init(); - return std::lower_bound(container_.begin(), container_.end(), key, - CMP()); + return std::lower_bound( + container_.begin(), container_.end(), key, cmp_); } /// @param key what to search for @return iterator, see std::upper_bound. @@ -95,8 +104,8 @@ public: iterator upper_bound(key_type key) { lazy_init(); - return std::upper_bound(container_.begin(), container_.end(), key, - CMP()); + return std::upper_bound( + container_.begin(), container_.end(), key, cmp_); } /// Searches for a single entry. @param key is what to search for. @return @@ -116,8 +125,8 @@ public: std::pair equal_range(key_type key) { lazy_init(); - return std::equal_range(container_.begin(), container_.end(), key, - CMP()); + return std::equal_range( + container_.begin(), container_.end(), key, cmp_); } /// Adds new entry to the vector. @@ -143,7 +152,8 @@ public: } /// Removes all entries. - void clear() { + void clear() + { container_.clear(); sortedCount_ = 0; } @@ -154,7 +164,7 @@ private: { if (sortedCount_ != container_.size()) { - sort(container_.begin(), container_.end(), CMP()); + sort(container_.begin(), container_.end(), cmp_); sortedCount_ = container_.size(); } } @@ -162,6 +172,9 @@ private: /// Holds the actual data elements. container_type container_; + /// Comparator instance. + CMP cmp_; + /// The first this many elements in the container are already sorted. size_t sortedCount_{0}; }; diff --git a/src/utils/StoredBitSet.cxxtest b/src/utils/StoredBitSet.cxxtest index f0cf54efe..cd67e6212 100644 --- a/src/utils/StoredBitSet.cxxtest +++ b/src/utils/StoredBitSet.cxxtest @@ -54,7 +54,7 @@ TEST(ShadowedBitSetSingleTest, size) } class BitSetMultiTest - : public ::testing::TestWithParam> + : public ::testing::TestWithParam> { protected: BitSetMultiTest() @@ -63,11 +63,11 @@ protected: unsigned get_size() { - return std::tr1::get<0>(GetParam()); + return std::get<0>(GetParam()); } uint8_t get_granularity() { - return std::tr1::get<1>(GetParam()); + return std::get<1>(GetParam()); } #define expect_all_zero(x...) \ diff --git a/src/utils/async_if_test_helper.hxx b/src/utils/async_if_test_helper.hxx index 2355a9b0b..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 @@ -378,19 +398,19 @@ protected: * alias. */ void create_allocated_alias() { - inject_allocated_alias(0x33A, true); + inject_allocated_alias(0x33A); aliasSeed_ = 0x44C; pendingAliasAllocation_ = false; } - void inject_allocated_alias(NodeAlias alias, bool repeat = false) + void inject_allocated_alias(NodeAlias alias) { if (!ifCan_->alias_allocator()) { ifCan_->set_alias_allocator( new AliasAllocator(TEST_NODE_ID, ifCan_.get())); } - run_x([this, alias, repeat]() { - ifCan_->alias_allocator()->TEST_add_allocated_alias(alias, repeat); + run_x([this, alias]() { + ifCan_->alias_allocator()->TEST_add_allocated_alias(alias); }); } diff --git a/src/utils/async_traction_test_helper.hxx b/src/utils/async_traction_test_helper.hxx index 32d1a15ee..a49518da8 100644 --- a/src/utils/async_traction_test_helper.hxx +++ b/src/utils/async_traction_test_helper.hxx @@ -3,6 +3,7 @@ #include "utils/async_if_test_helper.hxx" +#include "dcc/TrackIf.hxx" #include "openlcb/TractionDefs.hxx" #include "openlcb/TractionTrain.hxx" #include "utils/MockTrain.hxx" @@ -23,7 +24,26 @@ protected: StrictMock m1_, m2_; }; - } // namespace openlcb +namespace dcc +{ + +class MockTrackIf : public dcc::TrackIf +{ +public: + MOCK_METHOD2( + packet, void(const vector &payload, uintptr_t feedback_key)); + void send(Buffer *b, unsigned prio) OVERRIDE + { + vector payload; + payload.assign( + b->data()->payload, b->data()->payload + b->data()->dlc - 1); + this->packet(payload, b->data()->feedback_key); + b->unref(); + } +}; + +} + #endif // _UTILS_ASYNC_TRACTION_TEST_HELPER_HXX_ diff --git a/src/utils/blinker.h b/src/utils/blinker.h index 7151f3bbe..b61cfe243 100644 --- a/src/utils/blinker.h +++ b/src/utils/blinker.h @@ -90,6 +90,14 @@ extern uint32_t blinker_pattern; /** Sets a blinking pattern and never returns. */ extern void diewith(uint32_t); +/** Turns a blinker pattern into an error code in BCD. + * @param pattern a blinker pattern from the above examples. + * @return a nonnegative integer with each 4-bit nibble representing the number + * of repeats in the block of blinks, in LSB-first order. So a blink 3-1-2 will + * be represented as 0x213. + */ +extern unsigned parseblink(uint32_t pattern); + #ifdef __cplusplus } #endif // cplusplus diff --git a/src/utils/constants.cxx b/src/utils/constants.cxx index 6f654a2d0..d25f90ca1 100644 --- a/src/utils/constants.cxx +++ b/src/utils/constants.cxx @@ -152,3 +152,15 @@ DEFAULT_CONST(gridconnect_bridge_max_incoming_packets, 1); DEFAULT_CONST(gridconnect_bridge_max_outgoing_packets, 1); DEFAULT_CONST_FALSE(gridconnect_tcp_use_select); + +#ifdef ESP32 +/// Use a stack size of 3kb for SocketListener tasks. +DEFAULT_CONST(socket_listener_stack_size, 3072); +/// Allow one socket to be pending for accept() in SocketListener. +DEFAULT_CONST(socket_listener_backlog, 1); +#else +/// Use a stack size of 1000 for SocketListener tasks. +DEFAULT_CONST(socket_listener_stack_size, 1000); +/// Allow up to five sockets to be pending for accept() in SocketListener. +DEFAULT_CONST(socket_listener_backlog, 5); +#endif \ No newline at end of file diff --git a/src/utils/format_utils.cxx b/src/utils/format_utils.cxx index dd96df507..d763e46a4 100644 --- a/src/utils/format_utils.cxx +++ b/src/utils/format_utils.cxx @@ -35,6 +35,14 @@ #include "utils/macros.h" #include "utils/format_utils.hxx" +/// Translates a number 0..15 to a hex character. +/// @param nibble input number +/// @return character in 0-9a-f +static char nibble_to_hex(unsigned nibble) +{ + return nibble <= 9 ? '0' + nibble : 'a' + (nibble - 10); +} + char* unsigned_integer_to_buffer_hex(unsigned int value, char* buffer) { int num_digits = 0; @@ -50,16 +58,8 @@ char* unsigned_integer_to_buffer_hex(unsigned int value, char* buffer) do { HASSERT(num_digits >= 0); - unsigned int tmp2 = tmp % 16; - if (tmp2 <= 9) - { - buffer[num_digits--] = '0' + tmp2; - } - else - { - buffer[num_digits--] = 'a' + (tmp2 - 10); - } - tmp /= 16; + buffer[num_digits--] = nibble_to_hex(tmp & 0xf); + tmp >>= 4; } while (tmp); HASSERT(num_digits == -1); return ret; @@ -80,16 +80,8 @@ char* uint64_integer_to_buffer_hex(uint64_t value, char* buffer) do { HASSERT(num_digits >= 0); - uint64_t tmp2 = tmp % 16; - if (tmp2 <= 9) - { - buffer[num_digits--] = '0' + tmp2; - } - else - { - buffer[num_digits--] = 'a' + (tmp2 - 10); - } - tmp /= 16; + buffer[num_digits--] = nibble_to_hex(tmp & 0xf); + tmp >>= 4; } while (tmp); HASSERT(num_digits == -1); return ret; @@ -237,6 +229,19 @@ string int64_to_string_hex(int64_t value, unsigned padding) return ret; } +string string_to_hex(const string &arg) +{ + string ret; + ret.reserve(arg.size() * 2); + for (char c : arg) + { + uint8_t cc = static_cast(c); + ret.push_back(nibble_to_hex((cc >> 4) & 0xf)); + ret.push_back(nibble_to_hex(cc & 0xf)); + } + return ret; +} + string mac_to_string(uint8_t mac[6], bool colon) { string ret; diff --git a/src/utils/format_utils.hxx b/src/utils/format_utils.hxx index d1b8890b8..b35c151ca 100644 --- a/src/utils/format_utils.hxx +++ b/src/utils/format_utils.hxx @@ -36,6 +36,9 @@ #define _UTILS_FORMAT_UTILS_HXX_ #include +#include + +using std::string; /** Renders an integer to string, left-justified. @param buffer must be an at * @param buffer must be an at least 10 character long array. @@ -121,6 +124,12 @@ string uint64_to_string_hex(uint64_t value, unsigned padding = 0); */ string int64_to_string_hex(int64_t value, unsigned padding = 0); +/// Converts a (binary) string into a sequence of hex digits. +/// @param arg input string +/// @return string twice the length of arg with hex digits representing the +/// original data. +string string_to_hex(const string& arg); + /// Formats a MAC address to string. Works both for Ethernet addresses as well /// as for OpenLCB node IDs. /// @@ -157,4 +166,17 @@ inline string ipv4_to_string(uint32_t ip) return ipv4_to_string((uint8_t*)&ip); } +/// Populates a character array with a C string. Copies the C string, +/// appropriately truncating if it is too long and filling the remaining space +/// with zeroes. Ensures that at least one null terminator character is +/// present. +/// @param dst a character array of fixed length, declared as char sdata[N] +/// @param src a C string to fill it with. +template +inline void str_populate(char (&dst)[N], const char *src) +{ + strncpy(dst, src, N - 1); + dst[N - 1] = 0; +} + #endif // _UTILS_FORMAT_UTILS_HXX_ diff --git a/src/utils/macros.h b/src/utils/macros.h index 7cb4670f7..142b7f9c5 100644 --- a/src/utils/macros.h +++ b/src/utils/macros.h @@ -104,7 +104,7 @@ extern const char* g_death_file; #include #ifdef NDEBUG -#define HASSERT(x) do { if (!(x)) { fprintf(stderr, "Assertion failed in file " __FILE__ " line %d: assert(" #x ")", __LINE__); g_death_file = __FILE__; g_death_lineno = __LINE__; abort();} } while(0) +#define HASSERT(x) do { if (!(x)) { fprintf(stderr, "Assertion failed in file " __FILE__ " line %d: assert(" #x ")\n", __LINE__); g_death_file = __FILE__; g_death_lineno = __LINE__; abort();} } while(0) #else /// Checks that the value of expression x is true, else terminates the current /// process. @@ -114,7 +114,7 @@ extern const char* g_death_file; /// Unconditionally terminates the current process with a message. /// @param MSG is the message to print as cause of death. -#define DIE(MSG) do { fprintf(stderr, "Crashed in file " __FILE__ " line %d: " MSG, __LINE__); g_death_file = __FILE__; g_death_lineno = __LINE__; abort(); } while(0) +#define DIE(MSG) do { fprintf(stderr, "Crashed in file " __FILE__ " line %d: " MSG "\n", __LINE__); g_death_file = __FILE__; g_death_lineno = __LINE__; abort(); } while(0) #endif diff --git a/src/utils/socket_listener.cxx b/src/utils/socket_listener.cxx index bd2f45872..5dfa241f4 100644 --- a/src/utils/socket_listener.cxx +++ b/src/utils/socket_listener.cxx @@ -40,6 +40,12 @@ #define _DEFAULT_SOURCE #endif +#include "utils/socket_listener.hxx" + +#include "nmranet_config.h" +#include "utils/logging.h" +#include "utils/macros.h" + #ifndef ESP32 // these don't exist on the ESP32 with LWiP #include #include @@ -51,34 +57,22 @@ #include #include -#include "utils/socket_listener.hxx" - -#include "utils/macros.h" -#include "utils/logging.h" - - -static void* accept_thread_start(void* arg) { +static void* accept_thread_start(void* arg) +{ SocketListener* l = static_cast(arg); l->AcceptThreadBody(); return NULL; } -#ifdef ESP32 -/// Stack size to use for the accept_thread_. -static constexpr size_t listener_stack_size = 2048; -#else -/// Stack size to use for the accept_thread_. -static constexpr size_t listener_stack_size = 1000; -#endif // ESP32 - -SocketListener::SocketListener(int port, connection_callback_t callback) +SocketListener::SocketListener(int port, connection_callback_t callback, + const char *thread_name) : startupComplete_(0), shutdownRequested_(0), shutdownComplete_(0), port_(port), callback_(callback), - accept_thread_("accept_thread", 0, listener_stack_size, - accept_thread_start, this) + accept_thread_(thread_name, 0, config_socket_listener_stack_size(), + accept_thread_start, this) { #if OPENMRN_FEATURE_BSD_SOCKETS_IGNORE_SIGPIPE // We expect write failures to occur but we want to handle them where the @@ -87,8 +81,10 @@ SocketListener::SocketListener(int port, connection_callback_t callback) #endif // OPENMRN_FEATURE_BSD_SOCKETS_IGNORE_SIGPIPE } -SocketListener::~SocketListener() { - if (!shutdownComplete_) { +SocketListener::~SocketListener() +{ + if (!shutdownComplete_) + { shutdown(); } } @@ -96,12 +92,14 @@ SocketListener::~SocketListener() { void SocketListener::shutdown() { shutdownRequested_ = 1; - while (!shutdownComplete_) { + while (!shutdownComplete_) + { usleep(1000); } } -void SocketListener::AcceptThreadBody() { +void SocketListener::AcceptThreadBody() +{ socklen_t namelen; struct sockaddr_in addr; int listenfd; @@ -129,7 +127,7 @@ void SocketListener::AcceptThreadBody() { // FreeRTOS+TCP uses the parameter to listen to set the maximum number of // connections to the given socket, so allow some room - ERRNOCHECK("listen", listen(listenfd, 5)); + ERRNOCHECK("listen", listen(listenfd, config_socket_listener_backlog())); LOG(INFO, "Listening on port %d, fd %d", ntohs(addr.sin_port), listenfd); @@ -147,16 +145,20 @@ void SocketListener::AcceptThreadBody() { startupComplete_ = 1; - while (!shutdownRequested_) { + while (!shutdownRequested_) + { namelen = sizeof(addr); connfd = accept(listenfd, (struct sockaddr *)&addr, &namelen); - if (connfd < 0) { - if (errno == EINTR || errno == EAGAIN || errno == EMFILE) { + if (connfd < 0) + { + if (errno == EINTR || errno == EAGAIN || errno == EMFILE) + { continue; } - else if (errno == ECONNABORTED) { + else if (errno == ECONNABORTED) + { break; } print_errno_and_exit("accept"); diff --git a/src/utils/socket_listener.hxx b/src/utils/socket_listener.hxx index 5ff45e26d..667406702 100644 --- a/src/utils/socket_listener.hxx +++ b/src/utils/socket_listener.hxx @@ -60,7 +60,9 @@ public: /// /// @param port which TCP port number to listen upon. /// @param callback will be called on each incoming connection. - SocketListener(int port, connection_callback_t callback); + /// @param thread_name name to assign to the OSThread. + SocketListener(int port, connection_callback_t callback, + const char *thread_name = "accept_thread"); ~SocketListener(); /// Implementation of the accept thread. diff --git a/src/utils/sources b/src/utils/sources index 4dc7e26c7..8e7a00a40 100644 --- a/src/utils/sources +++ b/src/utils/sources @@ -5,6 +5,7 @@ CSRCS += errno_exit.c \ CXXSRCS += \ Base64.cxx \ + Blinker.cxx \ CanIf.cxx \ Crc.cxx \ StringPrintf.cxx \ @@ -22,6 +23,7 @@ CXXSRCS += \ Queue.cxx \ JSHubPort.cxx \ ReflashBootloader.cxx \ + SocketCan.cxx \ constants.cxx \ gc_format.cxx \ logging.cxx \ diff --git a/src/utils/test_main.hxx b/src/utils/test_main.hxx index 104e03ef2..cfbd77840 100644 --- a/src/utils/test_main.hxx +++ b/src/utils/test_main.hxx @@ -44,16 +44,25 @@ #include #include #include +#include #include "gtest/gtest.h" #include "gmock/gmock.h" #include "can_frame.h" +#include "executor/CallableFlow.hxx" #include "executor/Executor.hxx" #include "executor/Service.hxx" #include "os/TempFile.hxx" #include "os/os.h" #include "utils/StringPrintf.hxx" +#ifdef WITHGPERFTOOLS +#include + +std::function profiler_enable{&ProfilerEnable}; +std::function profiler_disable{&ProfilerDisable}; +#endif + int appl_main(int argc, char *argv[]) { testing::InitGoogleMock(&argc, argv); @@ -166,9 +175,75 @@ private: /// Synchronously runs a function in the main executor. void run_x(std::function fn) { - FnExecutable e(std::move(fn)); - g_executor.add(&e); - e.n.wait_for_notification(); + g_executor.sync_run(std::move(fn)); +} + +/// Runs some code in the constructor. Useful if custom code needs to be +/// injected into the constructor initialization order. +class RunInConstruct +{ +public: + RunInConstruct(std::function f) + { + f(); + } +}; + +/// Runs some code in the constructor on the main executor. +class RunInConstructOnMain +{ +public: + RunInConstructOnMain(std::function f) + { + run_x(f); + } +}; + +/// Helper macro to make running certain test commands run on the main executor +/// simpler. +#define RX(statement) run_x([&](){ statement; }) + +/// Structure holding returned objects for an invoke_flow_nowait command. +template struct PendingInvocation +{ + /// Buffer sent to the flow. + BufferPtr b; + /// Notifiable to wait for. + SyncNotifiable notifiable; + /// Barrier notifiable given to the buffer. + BarrierNotifiable barrier {¬ifiable}; + /// True if wait has been invoked. + bool isWaited {false}; + + ~PendingInvocation() + { + wait(); + } + + void wait() + { + if (isWaited) + { + return; + } + notifiable.wait_for_notification(); + isWaited = true; + } +}; + +/// Executes a callable flow similar to invoke_flow(...) but does not wait for +/// the result to come back. Instead, returns a PendingInvocation object, where +/// there is a wait() method to be called. +template +std::unique_ptr> invoke_flow_nowait( + FlowInterface> *flow, Args &&...args) +{ + auto ret = std::make_unique>(); + ret->b.reset(flow->alloc()); + ret->b->data()->reset(std::forward(args)...); + ret->b->data()->done.reset(&ret->barrier); + flow->send(ret->b->ref()); + return ret; } /** Utility class to block an executor for a while. diff --git a/targets/Makefile b/targets/Makefile index 41b678b59..82b88cf30 100644 --- a/targets/Makefile +++ b/targets/Makefile @@ -6,7 +6,7 @@ SUBDIRS = linux.x86 \ freertos.armv6m \ freertos.armv4t \ freertos.mips4k.pic32mx \ - mach.x86 mach.x86_64 \ + mach.x86_64 \ bare.armv7m \ js.emscripten \ mingw.x86 \ diff --git a/targets/bare.armv7m/freertos_drivers/stm32cubel431xx/Makefile b/targets/bare.armv7m/freertos_drivers/stm32cubel431xx/Makefile new file mode 100644 index 000000000..72e52af15 --- /dev/null +++ b/targets/bare.armv7m/freertos_drivers/stm32cubel431xx/Makefile @@ -0,0 +1,2 @@ +OPENMRNPATH ?= $(realpath ../../../..) +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/bare.armv7m/freertos_drivers/stm32cubel431xx/sources b/targets/bare.armv7m/freertos_drivers/stm32cubel431xx/sources new file mode 100644 index 000000000..ddd6657d8 --- /dev/null +++ b/targets/bare.armv7m/freertos_drivers/stm32cubel431xx/sources @@ -0,0 +1,94 @@ +include $(OPENMRNPATH)/etc/stm32cubel4.mk +VPATH = $(OPENMRNPATH)/src/freertos_drivers/st \ + $(STM32CUBEL4PATH)/Drivers/STM32L4xx_HAL_Driver/Src + +CFLAGS += -DSTM32L431xx +CXXFLAGS += -DSTM32L431xx + +CSRCS += \ + stm32l4xx_hal_adc.c \ + stm32l4xx_hal_adc_ex.c \ + stm32l4xx_hal.c \ + stm32l4xx_hal_can.c \ + stm32l4xx_hal_comp.c \ + stm32l4xx_hal_cortex.c \ + stm32l4xx_hal_crc.c \ + stm32l4xx_hal_crc_ex.c \ + stm32l4xx_hal_cryp.c \ + stm32l4xx_hal_cryp_ex.c \ + stm32l4xx_hal_dac.c \ + stm32l4xx_hal_dac_ex.c \ + stm32l4xx_hal_dcmi.c \ + stm32l4xx_hal_dfsdm.c \ + stm32l4xx_hal_dfsdm_ex.c \ + stm32l4xx_hal_dma2d.c \ + stm32l4xx_hal_dma.c \ + stm32l4xx_hal_dma_ex.c \ + stm32l4xx_hal_dsi.c \ + stm32l4xx_hal_exti.c \ + stm32l4xx_hal_firewall.c \ + stm32l4xx_hal_flash.c \ + stm32l4xx_hal_flash_ex.c \ + stm32l4xx_hal_flash_ramfunc.c \ + stm32l4xx_hal_gfxmmu.c \ + stm32l4xx_hal_gpio.c \ + stm32l4xx_hal_hash.c \ + stm32l4xx_hal_hash_ex.c \ + stm32l4xx_hal_hcd.c \ + stm32l4xx_hal_i2c.c \ + stm32l4xx_hal_i2c_ex.c \ + stm32l4xx_hal_irda.c \ + stm32l4xx_hal_iwdg.c \ + stm32l4xx_hal_lcd.c \ + stm32l4xx_hal_lptim.c \ + stm32l4xx_hal_ltdc.c \ + stm32l4xx_hal_ltdc_ex.c \ + stm32l4xx_hal_mmc.c \ + stm32l4xx_hal_mmc_ex.c \ + stm32l4xx_hal_msp_template.c \ + stm32l4xx_hal_nand.c \ + stm32l4xx_hal_nor.c \ + stm32l4xx_hal_opamp.c \ + stm32l4xx_hal_opamp_ex.c \ + stm32l4xx_hal_ospi.c \ + stm32l4xx_hal_pcd.c \ + stm32l4xx_hal_pcd_ex.c \ + stm32l4xx_hal_pka.c \ + stm32l4xx_hal_pssi.c \ + stm32l4xx_hal_pwr.c \ + stm32l4xx_hal_pwr_ex.c \ + stm32l4xx_hal_qspi.c \ + stm32l4xx_hal_rcc.c \ + stm32l4xx_hal_rcc_ex.c \ + stm32l4xx_hal_rng.c \ + stm32l4xx_hal_rng_ex.c \ + stm32l4xx_hal_rtc.c \ + stm32l4xx_hal_rtc_ex.c \ + stm32l4xx_hal_sai.c \ + stm32l4xx_hal_sai_ex.c \ + stm32l4xx_hal_sd.c \ + stm32l4xx_hal_sd_ex.c \ + stm32l4xx_hal_smartcard.c \ + stm32l4xx_hal_smartcard_ex.c \ + stm32l4xx_hal_smbus.c \ + stm32l4xx_hal_spi.c \ + stm32l4xx_hal_spi_ex.c \ + stm32l4xx_hal_sram.c \ + stm32l4xx_hal_swpmi.c \ + stm32l4xx_hal_tim.c \ + stm32l4xx_hal_tim_ex.c \ + stm32l4xx_hal_tsc.c \ + stm32l4xx_hal_uart.c \ + stm32l4xx_hal_uart_ex.c \ + stm32l4xx_hal_usart.c \ + stm32l4xx_hal_usart_ex.c \ + stm32l4xx_hal_wwdg.c \ + + +CXXSRCS += Stm32Can.cxx \ + Stm32Uart.cxx \ + Stm32SPI.cxx \ + Stm32I2C.cxx \ + Stm32EEPROMEmulation.cxx \ + + diff --git a/targets/bare.armv7m/freertos_drivers/stm32cubel432xx/Makefile b/targets/bare.armv7m/freertos_drivers/stm32cubel432xx/Makefile new file mode 100644 index 000000000..72e52af15 --- /dev/null +++ b/targets/bare.armv7m/freertos_drivers/stm32cubel432xx/Makefile @@ -0,0 +1,2 @@ +OPENMRNPATH ?= $(realpath ../../../..) +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/bare.armv7m/freertos_drivers/stm32cubel432xx/sources b/targets/bare.armv7m/freertos_drivers/stm32cubel432xx/sources new file mode 100644 index 000000000..2209fe22a --- /dev/null +++ b/targets/bare.armv7m/freertos_drivers/stm32cubel432xx/sources @@ -0,0 +1,93 @@ +include $(OPENMRNPATH)/etc/stm32cubel4.mk +VPATH = $(OPENMRNPATH)/src/freertos_drivers/st \ + $(STM32CUBEL4PATH)/Drivers/STM32L4xx_HAL_Driver/Src + +CFLAGS += -DSTM32L432xx +CXXFLAGS += -DSTM32L432xx + +CSRCS += \ + stm32l4xx_hal_adc.c \ + stm32l4xx_hal_adc_ex.c \ + stm32l4xx_hal.c \ + stm32l4xx_hal_can.c \ + stm32l4xx_hal_comp.c \ + stm32l4xx_hal_cortex.c \ + stm32l4xx_hal_crc.c \ + stm32l4xx_hal_crc_ex.c \ + stm32l4xx_hal_cryp.c \ + stm32l4xx_hal_cryp_ex.c \ + stm32l4xx_hal_dac.c \ + stm32l4xx_hal_dac_ex.c \ + stm32l4xx_hal_dcmi.c \ + stm32l4xx_hal_dfsdm.c \ + stm32l4xx_hal_dfsdm_ex.c \ + stm32l4xx_hal_dma2d.c \ + stm32l4xx_hal_dma.c \ + stm32l4xx_hal_dma_ex.c \ + stm32l4xx_hal_dsi.c \ + stm32l4xx_hal_exti.c \ + stm32l4xx_hal_firewall.c \ + stm32l4xx_hal_flash.c \ + stm32l4xx_hal_flash_ex.c \ + stm32l4xx_hal_flash_ramfunc.c \ + stm32l4xx_hal_gfxmmu.c \ + stm32l4xx_hal_gpio.c \ + stm32l4xx_hal_hash.c \ + stm32l4xx_hal_hash_ex.c \ + stm32l4xx_hal_hcd.c \ + stm32l4xx_hal_i2c.c \ + stm32l4xx_hal_i2c_ex.c \ + stm32l4xx_hal_irda.c \ + stm32l4xx_hal_iwdg.c \ + stm32l4xx_hal_lcd.c \ + stm32l4xx_hal_lptim.c \ + stm32l4xx_hal_ltdc.c \ + stm32l4xx_hal_ltdc_ex.c \ + stm32l4xx_hal_mmc.c \ + stm32l4xx_hal_mmc_ex.c \ + stm32l4xx_hal_msp_template.c \ + stm32l4xx_hal_nand.c \ + stm32l4xx_hal_nor.c \ + stm32l4xx_hal_opamp.c \ + stm32l4xx_hal_opamp_ex.c \ + stm32l4xx_hal_ospi.c \ + stm32l4xx_hal_pcd.c \ + stm32l4xx_hal_pcd_ex.c \ + stm32l4xx_hal_pka.c \ + stm32l4xx_hal_pssi.c \ + stm32l4xx_hal_pwr.c \ + stm32l4xx_hal_pwr_ex.c \ + stm32l4xx_hal_qspi.c \ + stm32l4xx_hal_rcc.c \ + stm32l4xx_hal_rcc_ex.c \ + stm32l4xx_hal_rng.c \ + stm32l4xx_hal_rng_ex.c \ + stm32l4xx_hal_rtc.c \ + stm32l4xx_hal_rtc_ex.c \ + stm32l4xx_hal_sai.c \ + stm32l4xx_hal_sai_ex.c \ + stm32l4xx_hal_sd.c \ + stm32l4xx_hal_sd_ex.c \ + stm32l4xx_hal_smartcard.c \ + stm32l4xx_hal_smartcard_ex.c \ + stm32l4xx_hal_smbus.c \ + stm32l4xx_hal_spi.c \ + stm32l4xx_hal_spi_ex.c \ + stm32l4xx_hal_sram.c \ + stm32l4xx_hal_swpmi.c \ + stm32l4xx_hal_tim.c \ + stm32l4xx_hal_tim_ex.c \ + stm32l4xx_hal_tsc.c \ + stm32l4xx_hal_uart.c \ + stm32l4xx_hal_uart_ex.c \ + stm32l4xx_hal_usart.c \ + stm32l4xx_hal_usart_ex.c \ + stm32l4xx_hal_wwdg.c \ + + +CXXSRCS += Stm32Can.cxx \ + Stm32Uart.cxx \ + Stm32SPI.cxx \ + Stm32I2C.cxx \ + Stm32EEPROMEmulation.cxx \ + diff --git a/targets/bare.armv7m/freertos_drivers/tivadriverlib/Makefile b/targets/bare.armv7m/freertos_drivers/tivadriverlib/Makefile new file mode 100644 index 000000000..23673c838 --- /dev/null +++ b/targets/bare.armv7m/freertos_drivers/tivadriverlib/Makefile @@ -0,0 +1,11 @@ +OPENMRNPATH ?= $(realpath ../../../..) + +DEPS += TIVAWAREPATH +include $(OPENMRNPATH)/etc/tivaware.mk +VPATH := $(TIVAWAREPATH)/driverlib + +include $(OPENMRNPATH)/etc/lib.mk + +CFLAGS += -Dgcc + +emac.o : CFLAGS += -Wno-address-of-packed-member diff --git a/targets/bare.armv7m/freertos_drivers/tivausblib/Makefile b/targets/bare.armv7m/freertos_drivers/tivausblib/Makefile new file mode 100644 index 000000000..65ddb2bbd --- /dev/null +++ b/targets/bare.armv7m/freertos_drivers/tivausblib/Makefile @@ -0,0 +1,12 @@ +OPENMRNPATH ?= $(realpath ../../../..) + +DEPS += TIVAWAREPATH +include $(OPENMRNPATH)/etc/tivaware.mk +VPATH := $(TIVAWAREPATH)/usblib + +include $(OPENMRNPATH)/etc/lib.mk +CFLAGS += -Dcodered + +SUBDIRS = device host +include $(OPENMRNPATH)/etc/recurse.mk + diff --git a/targets/bare.armv7m/freertos_drivers/tivausblib/device/Makefile b/targets/bare.armv7m/freertos_drivers/tivausblib/device/Makefile new file mode 100644 index 000000000..3c65be4ce --- /dev/null +++ b/targets/bare.armv7m/freertos_drivers/tivausblib/device/Makefile @@ -0,0 +1,9 @@ +OPENMRNPATH ?= $(realpath ../../../../..) + +DEPS += TIVAWAREPATH +include $(OPENMRNPATH)/etc/tivaware.mk +VPATH := $(TIVAWAREPATH)/usblib/device + +include $(OPENMRNPATH)/etc/lib.mk +CFLAGS += -Dcodered + diff --git a/targets/bare.armv7m/freertos_drivers/tivausblib/host/Makefile b/targets/bare.armv7m/freertos_drivers/tivausblib/host/Makefile new file mode 100644 index 000000000..c29be4ec5 --- /dev/null +++ b/targets/bare.armv7m/freertos_drivers/tivausblib/host/Makefile @@ -0,0 +1,9 @@ +OPENMRNPATH ?= $(realpath ../../../../..) + +DEPS += TIVAWAREPATH +include $(OPENMRNPATH)/etc/tivaware.mk +VPATH := $(TIVAWAREPATH)/usblib/host + +include $(OPENMRNPATH)/etc/lib.mk +CFLAGS += -Dcodered + diff --git a/targets/freertos.armv6m/freertos_drivers/stm32cubef091xc/sources b/targets/freertos.armv6m/freertos_drivers/stm32cubef091xc/sources index e7308f35f..6f2189d68 100644 --- a/targets/freertos.armv6m/freertos_drivers/stm32cubef091xc/sources +++ b/targets/freertos.armv6m/freertos_drivers/stm32cubef091xc/sources @@ -49,7 +49,8 @@ CSRCS += stm32f0xx_hal.c \ CXXSRCS += Stm32Can.cxx \ Stm32Uart.cxx \ Stm32SPI.cxx \ - Stm32EEPROMEmulation.cxx + Stm32EEPROMEmulation.cxx \ + Stm32RailcomSender.cxx # for some reason, -0s changes the behavior of HAL_FLASHEx_Erase() and # doesn't perform the erase. 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 += + diff --git a/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c123/Makefile b/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c123/Makefile new file mode 100644 index 000000000..72e52af15 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c123/Makefile @@ -0,0 +1,2 @@ +OPENMRNPATH ?= $(realpath ../../../..) +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c123/sources b/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c123/sources new file mode 100644 index 000000000..96f098066 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/spiffs_tm4c123/sources @@ -0,0 +1,28 @@ +DEPS += SPIFFSPATH +DEPS += TICC3220SDKPATH + +VPATH := $(SPIFFSPATH)/src: \ + $(OPENMRNPATH)/src/freertos_drivers/spiffs: \ + $(OPENMRNPATH)/src/freertos_drivers/spiffs/cc32x0sf + +CSRCS += spiffs_cache.c \ + spiffs_check.c \ + spiffs_gc.c \ + spiffs_hydrogen.c \ + spiffs_nucleus.c \ + spiffs_nucleus.c \ + +CXXSRCS += SPIFFS.cxx \ + CC32x0SFSPIFFS.cxx + + +INCLUDES += -I$(SPIFFSPATH)/src \ + -I$(OPENMRNPATH)/src/freertos_drivers/spiffs \ + -I$(OPENMRNPATH)/src/freertos_drivers/spiffs/cc32x0sf \ + -I$(TICC3220SDKPATH)/source/ti/devices/cc32xx \ + -I$(TICC3220SDKPATH)/source/ti/devices/cc32xx/driverlib \ + +CFLAGS += -DNO_TEST + +CXXFLAGS += + 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 += + diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile new file mode 100644 index 000000000..72e52af15 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/Makefile @@ -0,0 +1,2 @@ +OPENMRNPATH ?= $(realpath ../../../..) +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources new file mode 100644 index 000000000..ddd6657d8 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel431xx/sources @@ -0,0 +1,94 @@ +include $(OPENMRNPATH)/etc/stm32cubel4.mk +VPATH = $(OPENMRNPATH)/src/freertos_drivers/st \ + $(STM32CUBEL4PATH)/Drivers/STM32L4xx_HAL_Driver/Src + +CFLAGS += -DSTM32L431xx +CXXFLAGS += -DSTM32L431xx + +CSRCS += \ + stm32l4xx_hal_adc.c \ + stm32l4xx_hal_adc_ex.c \ + stm32l4xx_hal.c \ + stm32l4xx_hal_can.c \ + stm32l4xx_hal_comp.c \ + stm32l4xx_hal_cortex.c \ + stm32l4xx_hal_crc.c \ + stm32l4xx_hal_crc_ex.c \ + stm32l4xx_hal_cryp.c \ + stm32l4xx_hal_cryp_ex.c \ + stm32l4xx_hal_dac.c \ + stm32l4xx_hal_dac_ex.c \ + stm32l4xx_hal_dcmi.c \ + stm32l4xx_hal_dfsdm.c \ + stm32l4xx_hal_dfsdm_ex.c \ + stm32l4xx_hal_dma2d.c \ + stm32l4xx_hal_dma.c \ + stm32l4xx_hal_dma_ex.c \ + stm32l4xx_hal_dsi.c \ + stm32l4xx_hal_exti.c \ + stm32l4xx_hal_firewall.c \ + stm32l4xx_hal_flash.c \ + stm32l4xx_hal_flash_ex.c \ + stm32l4xx_hal_flash_ramfunc.c \ + stm32l4xx_hal_gfxmmu.c \ + stm32l4xx_hal_gpio.c \ + stm32l4xx_hal_hash.c \ + stm32l4xx_hal_hash_ex.c \ + stm32l4xx_hal_hcd.c \ + stm32l4xx_hal_i2c.c \ + stm32l4xx_hal_i2c_ex.c \ + stm32l4xx_hal_irda.c \ + stm32l4xx_hal_iwdg.c \ + stm32l4xx_hal_lcd.c \ + stm32l4xx_hal_lptim.c \ + stm32l4xx_hal_ltdc.c \ + stm32l4xx_hal_ltdc_ex.c \ + stm32l4xx_hal_mmc.c \ + stm32l4xx_hal_mmc_ex.c \ + stm32l4xx_hal_msp_template.c \ + stm32l4xx_hal_nand.c \ + stm32l4xx_hal_nor.c \ + stm32l4xx_hal_opamp.c \ + stm32l4xx_hal_opamp_ex.c \ + stm32l4xx_hal_ospi.c \ + stm32l4xx_hal_pcd.c \ + stm32l4xx_hal_pcd_ex.c \ + stm32l4xx_hal_pka.c \ + stm32l4xx_hal_pssi.c \ + stm32l4xx_hal_pwr.c \ + stm32l4xx_hal_pwr_ex.c \ + stm32l4xx_hal_qspi.c \ + stm32l4xx_hal_rcc.c \ + stm32l4xx_hal_rcc_ex.c \ + stm32l4xx_hal_rng.c \ + stm32l4xx_hal_rng_ex.c \ + stm32l4xx_hal_rtc.c \ + stm32l4xx_hal_rtc_ex.c \ + stm32l4xx_hal_sai.c \ + stm32l4xx_hal_sai_ex.c \ + stm32l4xx_hal_sd.c \ + stm32l4xx_hal_sd_ex.c \ + stm32l4xx_hal_smartcard.c \ + stm32l4xx_hal_smartcard_ex.c \ + stm32l4xx_hal_smbus.c \ + stm32l4xx_hal_spi.c \ + stm32l4xx_hal_spi_ex.c \ + stm32l4xx_hal_sram.c \ + stm32l4xx_hal_swpmi.c \ + stm32l4xx_hal_tim.c \ + stm32l4xx_hal_tim_ex.c \ + stm32l4xx_hal_tsc.c \ + stm32l4xx_hal_uart.c \ + stm32l4xx_hal_uart_ex.c \ + stm32l4xx_hal_usart.c \ + stm32l4xx_hal_usart_ex.c \ + stm32l4xx_hal_wwdg.c \ + + +CXXSRCS += Stm32Can.cxx \ + Stm32Uart.cxx \ + Stm32SPI.cxx \ + Stm32I2C.cxx \ + Stm32EEPROMEmulation.cxx \ + + diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile new file mode 100644 index 000000000..72e52af15 --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/Makefile @@ -0,0 +1,2 @@ +OPENMRNPATH ?= $(realpath ../../../..) +include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources new file mode 100644 index 000000000..2209fe22a --- /dev/null +++ b/targets/freertos.armv7m/freertos_drivers/stm32cubel432xx/sources @@ -0,0 +1,93 @@ +include $(OPENMRNPATH)/etc/stm32cubel4.mk +VPATH = $(OPENMRNPATH)/src/freertos_drivers/st \ + $(STM32CUBEL4PATH)/Drivers/STM32L4xx_HAL_Driver/Src + +CFLAGS += -DSTM32L432xx +CXXFLAGS += -DSTM32L432xx + +CSRCS += \ + stm32l4xx_hal_adc.c \ + stm32l4xx_hal_adc_ex.c \ + stm32l4xx_hal.c \ + stm32l4xx_hal_can.c \ + stm32l4xx_hal_comp.c \ + stm32l4xx_hal_cortex.c \ + stm32l4xx_hal_crc.c \ + stm32l4xx_hal_crc_ex.c \ + stm32l4xx_hal_cryp.c \ + stm32l4xx_hal_cryp_ex.c \ + stm32l4xx_hal_dac.c \ + stm32l4xx_hal_dac_ex.c \ + stm32l4xx_hal_dcmi.c \ + stm32l4xx_hal_dfsdm.c \ + stm32l4xx_hal_dfsdm_ex.c \ + stm32l4xx_hal_dma2d.c \ + stm32l4xx_hal_dma.c \ + stm32l4xx_hal_dma_ex.c \ + stm32l4xx_hal_dsi.c \ + stm32l4xx_hal_exti.c \ + stm32l4xx_hal_firewall.c \ + stm32l4xx_hal_flash.c \ + stm32l4xx_hal_flash_ex.c \ + stm32l4xx_hal_flash_ramfunc.c \ + stm32l4xx_hal_gfxmmu.c \ + stm32l4xx_hal_gpio.c \ + stm32l4xx_hal_hash.c \ + stm32l4xx_hal_hash_ex.c \ + stm32l4xx_hal_hcd.c \ + stm32l4xx_hal_i2c.c \ + stm32l4xx_hal_i2c_ex.c \ + stm32l4xx_hal_irda.c \ + stm32l4xx_hal_iwdg.c \ + stm32l4xx_hal_lcd.c \ + stm32l4xx_hal_lptim.c \ + stm32l4xx_hal_ltdc.c \ + stm32l4xx_hal_ltdc_ex.c \ + stm32l4xx_hal_mmc.c \ + stm32l4xx_hal_mmc_ex.c \ + stm32l4xx_hal_msp_template.c \ + stm32l4xx_hal_nand.c \ + stm32l4xx_hal_nor.c \ + stm32l4xx_hal_opamp.c \ + stm32l4xx_hal_opamp_ex.c \ + stm32l4xx_hal_ospi.c \ + stm32l4xx_hal_pcd.c \ + stm32l4xx_hal_pcd_ex.c \ + stm32l4xx_hal_pka.c \ + stm32l4xx_hal_pssi.c \ + stm32l4xx_hal_pwr.c \ + stm32l4xx_hal_pwr_ex.c \ + stm32l4xx_hal_qspi.c \ + stm32l4xx_hal_rcc.c \ + stm32l4xx_hal_rcc_ex.c \ + stm32l4xx_hal_rng.c \ + stm32l4xx_hal_rng_ex.c \ + stm32l4xx_hal_rtc.c \ + stm32l4xx_hal_rtc_ex.c \ + stm32l4xx_hal_sai.c \ + stm32l4xx_hal_sai_ex.c \ + stm32l4xx_hal_sd.c \ + stm32l4xx_hal_sd_ex.c \ + stm32l4xx_hal_smartcard.c \ + stm32l4xx_hal_smartcard_ex.c \ + stm32l4xx_hal_smbus.c \ + stm32l4xx_hal_spi.c \ + stm32l4xx_hal_spi_ex.c \ + stm32l4xx_hal_sram.c \ + stm32l4xx_hal_swpmi.c \ + stm32l4xx_hal_tim.c \ + stm32l4xx_hal_tim_ex.c \ + stm32l4xx_hal_tsc.c \ + stm32l4xx_hal_uart.c \ + stm32l4xx_hal_uart_ex.c \ + stm32l4xx_hal_usart.c \ + stm32l4xx_hal_usart_ex.c \ + stm32l4xx_hal_wwdg.c \ + + +CXXSRCS += Stm32Can.cxx \ + Stm32Uart.cxx \ + Stm32SPI.cxx \ + Stm32I2C.cxx \ + Stm32EEPROMEmulation.cxx \ + 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 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 \ diff --git a/targets/mach.x86/Makefile b/targets/linux.aarch64/Makefile similarity index 100% rename from targets/mach.x86/Makefile rename to targets/linux.aarch64/Makefile diff --git a/targets/mach.x86/os/Makefile b/targets/linux.aarch64/console/Makefile similarity index 100% rename from targets/mach.x86/os/Makefile rename to targets/linux.aarch64/console/Makefile diff --git a/targets/mach.x86/console/Makefile b/targets/linux.aarch64/cue/Makefile similarity index 100% rename from targets/mach.x86/console/Makefile rename to targets/linux.aarch64/cue/Makefile diff --git a/targets/mach.x86/cue/Makefile b/targets/linux.aarch64/dcc/Makefile similarity index 100% rename from targets/mach.x86/cue/Makefile rename to targets/linux.aarch64/dcc/Makefile diff --git a/targets/mach.x86/dcc/Makefile b/targets/linux.aarch64/executor/Makefile similarity index 100% rename from targets/mach.x86/dcc/Makefile rename to targets/linux.aarch64/executor/Makefile diff --git a/targets/mach.x86/lib/Makefile b/targets/linux.aarch64/lib/Makefile similarity index 100% rename from targets/mach.x86/lib/Makefile rename to targets/linux.aarch64/lib/Makefile diff --git a/targets/mach.x86/executor/Makefile b/targets/linux.aarch64/openlcb/Makefile similarity index 100% rename from targets/mach.x86/executor/Makefile rename to targets/linux.aarch64/openlcb/Makefile diff --git a/targets/linux.aarch64/os/Makefile b/targets/linux.aarch64/os/Makefile new file mode 100644 index 000000000..0c54f81dd --- /dev/null +++ b/targets/linux.aarch64/os/Makefile @@ -0,0 +1,3 @@ +OPENMRNPATH ?= $(realpath ../../..) +include $(OPENMRNPATH)/etc/lib.mk + diff --git a/targets/mach.x86/openlcb/Makefile b/targets/linux.aarch64/utils/Makefile similarity index 100% rename from targets/mach.x86/openlcb/Makefile rename to targets/linux.aarch64/utils/Makefile diff --git a/targets/mach.x86/utils/Makefile b/targets/linux.aarch64/withrottle/Makefile similarity index 100% rename from targets/mach.x86/utils/Makefile rename to targets/linux.aarch64/withrottle/Makefile diff --git a/targets/mach.x86/withrottle/Makefile b/targets/mach.x86/withrottle/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/withrottle/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk