From 844b4671de91616cc579bb3cff598762961961e3 Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sun, 9 Aug 2020 13:24:33 -0500 Subject: [PATCH 1/5] Bakerstu macos cleanup (#409) * Remove 32-bit Mac OS X build. * Fix Mac OS X build. * Fixup toolpath discovery. * Add support for and use HOSTCLANGPPPATH. Co-authored-by: Stuart Baker --- applications/async_blink/targets/Makefile | 1 - .../async_blink/targets/mach.x86/.gitignore | 2 - .../async_blink/targets/mach.x86/Makefile | 1 - .../async_blink/targets/mach.x86/NodeId.cxx | 4 - .../async_blink/targets/mach.x86_64/Makefile | 1 + applications/clinic_app/targets/Makefile | 10 +- .../{mach.x86 => mach.x86_64}/.gitignore | 0 .../{mach.x86 => mach.x86_64}/Makefile | 0 .../{mach.x86 => mach.x86_64}/config.hxx | 0 .../{mach.x86 => mach.x86_64}/main.cxx | 0 applications/hub/targets/Makefile | 2 +- .../hub/targets/mach.x86/lib/Makefile | 1 - .../{mach.x86 => mach.x86_64}/.gitignore | 0 .../{mach.x86 => mach.x86_64}/Makefile | 0 .../targets/mach.x86_64}/lib/Makefile | 0 applications/simple_client/targets/Makefile | 2 +- .../targets/mach.x86_64/.gitignore | 1 + .../{mach.x86 => mach.x86_64}/Makefile | 0 .../{mach.x86 => mach.x86_64}/hardware.mk | 0 etc/mach.x86.mk | 30 ------ etc/mach.x86_64.mk | 20 ++-- etc/path.mk | 12 +++ src/os/OSSelectWakeup.cxx | 100 ++++++++++++++++++ src/os/OSSelectWakeup.hxx | 62 +---------- src/utils/SocketClient.hxx | 1 + targets/Makefile | 2 +- targets/mach.x86/Makefile | 1 - targets/mach.x86/console/Makefile | 1 - targets/mach.x86/cue/Makefile | 1 - targets/mach.x86/dcc/Makefile | 1 - targets/mach.x86/executor/Makefile | 1 - targets/mach.x86/lib/Makefile | 1 - targets/mach.x86/openlcb/Makefile | 1 - targets/mach.x86/os/Makefile | 3 - targets/mach.x86/utils/Makefile | 1 - targets/mach.x86/withrottle/Makefile | 1 - 36 files changed, 134 insertions(+), 130 deletions(-) delete mode 100644 applications/async_blink/targets/mach.x86/.gitignore delete mode 100644 applications/async_blink/targets/mach.x86/Makefile delete mode 100644 applications/async_blink/targets/mach.x86/NodeId.cxx rename applications/clinic_app/targets/{mach.x86 => mach.x86_64}/.gitignore (100%) rename applications/clinic_app/targets/{mach.x86 => mach.x86_64}/Makefile (100%) rename applications/clinic_app/targets/{mach.x86 => mach.x86_64}/config.hxx (100%) rename applications/clinic_app/targets/{mach.x86 => mach.x86_64}/main.cxx (100%) delete mode 100644 applications/hub/targets/mach.x86/lib/Makefile rename applications/hub/targets/{mach.x86 => mach.x86_64}/.gitignore (100%) rename applications/hub/targets/{mach.x86 => mach.x86_64}/Makefile (100%) rename applications/{async_blink/targets/mach.x86 => hub/targets/mach.x86_64}/lib/Makefile (100%) create mode 100644 applications/simple_client/targets/mach.x86_64/.gitignore rename applications/simple_client/targets/{mach.x86 => mach.x86_64}/Makefile (100%) rename applications/simple_client/targets/{mach.x86 => mach.x86_64}/hardware.mk (100%) delete mode 100644 etc/mach.x86.mk delete mode 100644 targets/mach.x86/Makefile delete mode 100644 targets/mach.x86/console/Makefile delete mode 100644 targets/mach.x86/cue/Makefile delete mode 100644 targets/mach.x86/dcc/Makefile delete mode 100644 targets/mach.x86/executor/Makefile delete mode 100644 targets/mach.x86/lib/Makefile delete mode 100644 targets/mach.x86/openlcb/Makefile delete mode 100644 targets/mach.x86/os/Makefile delete mode 100644 targets/mach.x86/utils/Makefile delete mode 100644 targets/mach.x86/withrottle/Makefile diff --git a/applications/async_blink/targets/Makefile b/applications/async_blink/targets/Makefile index 228c445ca..dd450c0a7 100644 --- a/applications/async_blink/targets/Makefile +++ b/applications/async_blink/targets/Makefile @@ -1,7 +1,6 @@ SUBDIRS = linux.x86 \ linux.armv7a \ linux.llvm \ - mach.x86 \ mach.x86_64 \ freertos.armv7m.ek-lm4f120xl \ freertos.armv7m.ek-tm4c123gxl \ diff --git a/applications/async_blink/targets/mach.x86/.gitignore b/applications/async_blink/targets/mach.x86/.gitignore deleted file mode 100644 index 43148b79f..000000000 --- a/applications/async_blink/targets/mach.x86/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -async_blink -*_test diff --git a/applications/async_blink/targets/mach.x86/Makefile b/applications/async_blink/targets/mach.x86/Makefile deleted file mode 100644 index 3bb6034b2..000000000 --- a/applications/async_blink/targets/mach.x86/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/prog.mk diff --git a/applications/async_blink/targets/mach.x86/NodeId.cxx b/applications/async_blink/targets/mach.x86/NodeId.cxx deleted file mode 100644 index 3cd6d1db7..000000000 --- a/applications/async_blink/targets/mach.x86/NodeId.cxx +++ /dev/null @@ -1,4 +0,0 @@ -#include "openlcb/If.hxx" - -extern const openlcb::NodeID NODE_ID; -const openlcb::NodeID NODE_ID = 0x050101011410ULL; diff --git a/applications/async_blink/targets/mach.x86_64/Makefile b/applications/async_blink/targets/mach.x86_64/Makefile index 3bb6034b2..c6386dac3 100644 --- a/applications/async_blink/targets/mach.x86_64/Makefile +++ b/applications/async_blink/targets/mach.x86_64/Makefile @@ -1 +1,2 @@ +-include ../../config.mk include $(OPENMRNPATH)/etc/prog.mk diff --git a/applications/clinic_app/targets/Makefile b/applications/clinic_app/targets/Makefile index f999fd393..8b3bed0a4 100644 --- a/applications/clinic_app/targets/Makefile +++ b/applications/clinic_app/targets/Makefile @@ -1,11 +1,11 @@ SUBDIRS = \ - freertos.armv7m.ek-tm4c123gxl \ - freertos.armv7m.ek-tm4c1294xl \ - freertos.armv6m.st-stm32f072b-discovery \ - freertos.armv7m.st-stm32f303-discovery \ + freertos.armv7m.ek-tm4c123gxl \ + freertos.armv7m.ek-tm4c1294xl \ + freertos.armv6m.st-stm32f072b-discovery \ + freertos.armv7m.st-stm32f303-discovery \ linux.armv7a \ linux.x86 \ - mach.x86 + mach.x86_64 # freertos.armv7m.lpc1768-mbed \ diff --git a/applications/clinic_app/targets/mach.x86/.gitignore b/applications/clinic_app/targets/mach.x86_64/.gitignore similarity index 100% rename from applications/clinic_app/targets/mach.x86/.gitignore rename to applications/clinic_app/targets/mach.x86_64/.gitignore diff --git a/applications/clinic_app/targets/mach.x86/Makefile b/applications/clinic_app/targets/mach.x86_64/Makefile similarity index 100% rename from applications/clinic_app/targets/mach.x86/Makefile rename to applications/clinic_app/targets/mach.x86_64/Makefile diff --git a/applications/clinic_app/targets/mach.x86/config.hxx b/applications/clinic_app/targets/mach.x86_64/config.hxx similarity index 100% rename from applications/clinic_app/targets/mach.x86/config.hxx rename to applications/clinic_app/targets/mach.x86_64/config.hxx diff --git a/applications/clinic_app/targets/mach.x86/main.cxx b/applications/clinic_app/targets/mach.x86_64/main.cxx similarity index 100% rename from applications/clinic_app/targets/mach.x86/main.cxx rename to applications/clinic_app/targets/mach.x86_64/main.cxx diff --git a/applications/hub/targets/Makefile b/applications/hub/targets/Makefile index 3d075d4b8..5ff3034dd 100644 --- a/applications/hub/targets/Makefile +++ b/applications/hub/targets/Makefile @@ -1,5 +1,5 @@ SUBDIRS = linux.x86 \ linux.armv7a \ - mach.x86 + mach.x86_64 include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/hub/targets/mach.x86/lib/Makefile b/applications/hub/targets/mach.x86/lib/Makefile deleted file mode 100644 index a414ed98e..000000000 --- a/applications/hub/targets/mach.x86/lib/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/app_target_lib.mk diff --git a/applications/hub/targets/mach.x86/.gitignore b/applications/hub/targets/mach.x86_64/.gitignore similarity index 100% rename from applications/hub/targets/mach.x86/.gitignore rename to applications/hub/targets/mach.x86_64/.gitignore diff --git a/applications/hub/targets/mach.x86/Makefile b/applications/hub/targets/mach.x86_64/Makefile similarity index 100% rename from applications/hub/targets/mach.x86/Makefile rename to applications/hub/targets/mach.x86_64/Makefile diff --git a/applications/async_blink/targets/mach.x86/lib/Makefile b/applications/hub/targets/mach.x86_64/lib/Makefile similarity index 100% rename from applications/async_blink/targets/mach.x86/lib/Makefile rename to applications/hub/targets/mach.x86_64/lib/Makefile diff --git a/applications/simple_client/targets/Makefile b/applications/simple_client/targets/Makefile index 722e7127c..090bf476c 100644 --- a/applications/simple_client/targets/Makefile +++ b/applications/simple_client/targets/Makefile @@ -1,3 +1,3 @@ -SUBDIRS = linux.x86 mach.x86 +SUBDIRS = linux.x86 mach.x86_64 -include config.mk include $(OPENMRNPATH)/etc/recurse.mk diff --git a/applications/simple_client/targets/mach.x86_64/.gitignore b/applications/simple_client/targets/mach.x86_64/.gitignore new file mode 100644 index 000000000..7d1a13c95 --- /dev/null +++ b/applications/simple_client/targets/mach.x86_64/.gitignore @@ -0,0 +1 @@ +simple_client diff --git a/applications/simple_client/targets/mach.x86/Makefile b/applications/simple_client/targets/mach.x86_64/Makefile similarity index 100% rename from applications/simple_client/targets/mach.x86/Makefile rename to applications/simple_client/targets/mach.x86_64/Makefile diff --git a/applications/simple_client/targets/mach.x86/hardware.mk b/applications/simple_client/targets/mach.x86_64/hardware.mk similarity index 100% rename from applications/simple_client/targets/mach.x86/hardware.mk rename to applications/simple_client/targets/mach.x86_64/hardware.mk diff --git a/etc/mach.x86.mk b/etc/mach.x86.mk deleted file mode 100644 index b94225e19..000000000 --- a/etc/mach.x86.mk +++ /dev/null @@ -1,30 +0,0 @@ -ifndef TOOLPATH -TOOLPATH := $(shell \ -sh -c "if [ -d /usr/include/mach ]; then echo /usr/bin; \ - else echo; fi" \ -) -endif - -# Get the $(CFLAGSENV), $(CXXFLAGSENV), $(LDFLAGSENV) -include $(OPENMRNPATH)/etc/env.mk - -CC = gcc -CXX = g++ -AR = ar -LD = g++ - -STARTGROUP := -ENDGROUP := - -INCLUDES += -I$(OPENMRNPATH)/include/mach - -CFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=gnu99 -m32 -fno-stack-protector \ - -D_GNU_SOURCE -CXXFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c++0x -m32 -fno-stack-protector \ - -D_GNU_SOURCE -D__STDC_FORMAT_MACROS - -LDFLAGS = -g -m32 -SYSLIBRARIES = -lpthread - -EXTENTION = - diff --git a/etc/mach.x86_64.mk b/etc/mach.x86_64.mk index 5842b51db..72ef97bb1 100644 --- a/etc/mach.x86_64.mk +++ b/etc/mach.x86_64.mk @@ -1,8 +1,8 @@ -ifndef TOOLPATH -TOOLPATH := $(shell \ -sh -c "if [ -d /usr/include/mach ]; then echo /usr/bin; \ - else echo; fi" \ -) +# Get the toolchain path +include $(OPENMRNPATH)/etc/path.mk + +ifeq ($(shell uname -sm),Darwin x86_64) +TOOLPATH := $(HOSTCLANGPPPATH) endif $(info mach toolpath '$(TOOLPATH)') @@ -10,19 +10,19 @@ $(info mach toolpath '$(TOOLPATH)') # Get the $(CFLAGSENV), $(CXXFLAGSENV), $(LDFLAGSENV) include $(OPENMRNPATH)/etc/env.mk -CC = gcc -CXX = g++ +CC = clang +CXX = clang++ AR = ar -LD = g++ +LD = clang++ STARTGROUP := ENDGROUP := INCLUDES += -I$(OPENMRNPATH)/include/mach -CFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=gnu99 -fno-stack-protector \ +CFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c99 -fno-stack-protector \ -D_GNU_SOURCE -CXXFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c++0x -fno-stack-protector \ +CXXFLAGS = -c -g -O0 -Wall -Werror -MD -MP -std=c++14 -fno-stack-protector \ -D_GNU_SOURCE LDFLAGS = -g diff --git a/etc/path.mk b/etc/path.mk index 5bb3a6f2f..261a95eff 100644 --- a/etc/path.mk +++ b/etc/path.mk @@ -582,6 +582,18 @@ CLANGPPPATH:=$(TRYPATH) endif endif #CLANGPPPATH +##################### HOSTCLANGPP ###################### +ifndef HOSTCLANGPPPATH +SEARCHPATH := \ + /usr/bin \ + + +TRYPATH:=$(call findfirst,clang++,$(SEARCHPATH)) +ifneq ($(TRYPATH),) +HOSTCLANGPPPATH:=$(TRYPATH) +endif +endif #HOSTCLANGPPPATH + ##################### NODEJS ###################### ifndef NODEJSPATH SEARCHPATH := \ diff --git a/src/os/OSSelectWakeup.cxx b/src/os/OSSelectWakeup.cxx index b040906e9..2fa97bf57 100644 --- a/src/os/OSSelectWakeup.cxx +++ b/src/os/OSSelectWakeup.cxx @@ -1,10 +1,110 @@ +/** \copyright +* Copyright (c) 2015, Balazs Racz +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* - Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* - Redistributions in binary form must reproduce the above copyright notice, +* this list of conditions and the following disclaimer in the documentation +* and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +* \file OSSelectWakeup.cxx +* Helper class for portable wakeup of a thread blocked in a select call. +* +* @author Balazs Racz +* @date 10 Apr 2015 +*/ + #include "os/OSSelectWakeup.hxx" #include "utils/logging.h" +#if defined(__MACH__) +#define _DARWIN_C_SOURCE // pselect +#endif void empty_signal_handler(int) { } +int OSSelectWakeup::select(int nfds, fd_set *readfds, + fd_set *writefds, fd_set *exceptfds, + long long deadline_nsec) +{ + { + AtomicHolder l(this); + inSelect_ = true; + if (pendingWakeup_) + { + deadline_nsec = 0; + } + else + { +#if OPENMRN_FEATURE_DEVICE_SELECT + Device::select_clear(); +#endif + } + } +#if OPENMRN_FEATURE_DEVICE_SELECT + int ret = + Device::select(nfds, readfds, writefds, exceptfds, deadline_nsec); + if (!ret && pendingWakeup_) + { + ret = -1; + errno = EINTR; + } +#elif OPENMRN_HAVE_PSELECT + struct timespec timeout; + timeout.tv_sec = deadline_nsec / 1000000000; + timeout.tv_nsec = deadline_nsec % 1000000000; + int ret = + ::pselect(nfds, readfds, writefds, exceptfds, &timeout, &origMask_); +#elif OPENMRN_HAVE_SELECT +#ifdef ESP32 + fd_set newexcept; + if (!exceptfds) + { + FD_ZERO(&newexcept); + exceptfds = &newexcept; + } + FD_SET(vfsFd_, exceptfds); + if (vfsFd_ >= nfds) { + nfds = vfsFd_ + 1; + } +#endif //ESP32 + struct timeval timeout; + timeout.tv_sec = deadline_nsec / 1000000000; + timeout.tv_usec = (deadline_nsec / 1000) % 1000000; + int ret = + ::select(nfds, readfds, writefds, exceptfds, &timeout); +#elif !defined(OPENMRN_FEATURE_SINGLE_THREADED) + #error no select implementation in multi threaded OS. +#else + // Single threaded OS: nothing to wake up. + int ret = 0; +#endif + { + AtomicHolder l(this); + pendingWakeup_ = false; + inSelect_ = false; + } + return ret; +} + #ifdef ESP32 #include #include diff --git a/src/os/OSSelectWakeup.hxx b/src/os/OSSelectWakeup.hxx index 2471ba8fa..5d284256e 100644 --- a/src/os/OSSelectWakeup.hxx +++ b/src/os/OSSelectWakeup.hxx @@ -181,67 +181,7 @@ public: * asynchronously */ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, - long long deadline_nsec) - { - { - AtomicHolder l(this); - inSelect_ = true; - if (pendingWakeup_) - { - deadline_nsec = 0; - } - else - { -#if OPENMRN_FEATURE_DEVICE_SELECT - Device::select_clear(); -#endif - } - } -#if OPENMRN_FEATURE_DEVICE_SELECT - int ret = - Device::select(nfds, readfds, writefds, exceptfds, deadline_nsec); - if (!ret && pendingWakeup_) - { - ret = -1; - errno = EINTR; - } -#elif OPENMRN_HAVE_PSELECT - struct timespec timeout; - timeout.tv_sec = deadline_nsec / 1000000000; - timeout.tv_nsec = deadline_nsec % 1000000000; - int ret = - ::pselect(nfds, readfds, writefds, exceptfds, &timeout, &origMask_); -#elif OPENMRN_HAVE_SELECT -#ifdef ESP32 - fd_set newexcept; - if (!exceptfds) - { - FD_ZERO(&newexcept); - exceptfds = &newexcept; - } - FD_SET(vfsFd_, exceptfds); - if (vfsFd_ >= nfds) { - nfds = vfsFd_ + 1; - } -#endif //ESP32 - struct timeval timeout; - timeout.tv_sec = deadline_nsec / 1000000000; - timeout.tv_usec = (deadline_nsec / 1000) % 1000000; - int ret = - ::select(nfds, readfds, writefds, exceptfds, &timeout); -#elif !defined(OPENMRN_FEATURE_SINGLE_THREADED) - #error no select implementation in multi threaded OS. -#else - // Single threaded OS: nothing to wake up. - int ret = 0; -#endif - { - AtomicHolder l(this); - pendingWakeup_ = false; - inSelect_ = false; - } - return ret; - } + long long deadline_nsec); private: #ifdef ESP32 diff --git a/src/utils/SocketClient.hxx b/src/utils/SocketClient.hxx index f649e9ad0..4a0b33eed 100644 --- a/src/utils/SocketClient.hxx +++ b/src/utils/SocketClient.hxx @@ -47,6 +47,7 @@ #endif #include #include +#include #include "executor/StateFlow.hxx" #include "executor/Timer.hxx" diff --git a/targets/Makefile b/targets/Makefile index 41b678b59..82b88cf30 100644 --- a/targets/Makefile +++ b/targets/Makefile @@ -6,7 +6,7 @@ SUBDIRS = linux.x86 \ freertos.armv6m \ freertos.armv4t \ freertos.mips4k.pic32mx \ - mach.x86 mach.x86_64 \ + mach.x86_64 \ bare.armv7m \ js.emscripten \ mingw.x86 \ diff --git a/targets/mach.x86/Makefile b/targets/mach.x86/Makefile deleted file mode 100644 index 95c3e21d6..000000000 --- a/targets/mach.x86/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/core_target.mk diff --git a/targets/mach.x86/console/Makefile b/targets/mach.x86/console/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/console/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/cue/Makefile b/targets/mach.x86/cue/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/cue/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/dcc/Makefile b/targets/mach.x86/dcc/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/dcc/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/executor/Makefile b/targets/mach.x86/executor/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/executor/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/lib/Makefile b/targets/mach.x86/lib/Makefile deleted file mode 100644 index a8d2e7abf..000000000 --- a/targets/mach.x86/lib/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/target_lib.mk diff --git a/targets/mach.x86/openlcb/Makefile b/targets/mach.x86/openlcb/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/openlcb/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/os/Makefile b/targets/mach.x86/os/Makefile deleted file mode 100644 index 0c54f81dd..000000000 --- a/targets/mach.x86/os/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -OPENMRNPATH ?= $(realpath ../../..) -include $(OPENMRNPATH)/etc/lib.mk - diff --git a/targets/mach.x86/utils/Makefile b/targets/mach.x86/utils/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/utils/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk diff --git a/targets/mach.x86/withrottle/Makefile b/targets/mach.x86/withrottle/Makefile deleted file mode 100644 index 5e5bf973e..000000000 --- a/targets/mach.x86/withrottle/Makefile +++ /dev/null @@ -1 +0,0 @@ -include $(OPENMRNPATH)/etc/lib.mk From 5c2b3a087efdf638b6b2804af21be45276052d8c Mon Sep 17 00:00:00 2001 From: Stuart W Baker Date: Sun, 9 Aug 2020 13:51:55 -0500 Subject: [PATCH 2/5] Make dead code into less dead code. (#417) * Make dead code into less dead code. * fix code style. * remove superfluous space. --- src/utils/FileUtils.cxx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/utils/FileUtils.cxx b/src/utils/FileUtils.cxx index c72b38011..979769910 100644 --- a/src/utils/FileUtils.cxx +++ b/src/utils/FileUtils.cxx @@ -106,10 +106,15 @@ void write_string_to_file(const string &filename, const string &data) while ((nr = fwrite(data.data() + offset, 1, data.size() - offset, f)) > 0) { offset += nr; - if (offset >= data.size()) break; + if (offset >= data.size()) + { + break; + } } - if (nr < 0) { - fprintf(stderr, "error writing: %s\n", strerror(errno)); + if (offset != data.size()) + { + fprintf(stderr, "error writing: %s, offset: %zu, size: %zu\n", + strerror(errno), offset, data.size()); } fclose(f); } From 95b30e235976c1687bb514f42e0b8eda1b366d61 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 9 Aug 2020 23:56:09 +0200 Subject: [PATCH 3/5] Adds a railcom debug flow that sends a pulse to a GPIO when it sees a valid packet. (#415) --- src/dcc/RailcomPortDebug.hxx | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/dcc/RailcomPortDebug.hxx b/src/dcc/RailcomPortDebug.hxx index dc2e61eb2..c41d224bd 100644 --- a/src/dcc/RailcomPortDebug.hxx +++ b/src/dcc/RailcomPortDebug.hxx @@ -129,6 +129,72 @@ private: dcc::RailcomHubFlow *parent_; }; +/// This flow listens to Railcom packets coming from the hub, and if they are +/// correctly decoded, pulses the given GPIO output. Correctly decoded is +/// defined as having every single byte be a correct 4/8 codepoint. +class RailcomToGpioFlow : public dcc::RailcomHubPortInterface +{ +public: + /// Constructor. + /// @param source is the railcom hub to listen to. + /// @param output + RailcomToGpioFlow(dcc::RailcomHubFlow *source, const Gpio *output) + : parent_(source) + , output_(output) + { + source->register_port(this); + } + + ~RailcomToGpioFlow() + { + parent_->unregister_port(this); + } + +private: + /// Incoming railcom data. + /// + /// @param d railcom buffer. + /// @param prio priority + void send(Buffer *d, unsigned prio) OVERRIDE + { + AutoReleaseBuffer rb(d); + dcc::Feedback &fb = *d->data(); + if (fb.channel >= 0xfe) + { + // Occupancy feedback, not railcom data. + return; + } + unsigned correct = 0; + unsigned total = 0; + for (unsigned i = 0; i < fb.ch1Size; i++) + { + ++total; + correct += (dcc::railcom_decode[fb.ch1Data[i]] != RailcomDefs::INV) + ? 1 + : 0; + } + for (unsigned i = 0; i < fb.ch2Size; i++) + { + ++total; + correct += (dcc::railcom_decode[fb.ch2Data[i]] != RailcomDefs::INV) + ? 1 + : 0; + } + if (total > 0 && correct == total) + { + // Produces a short pulse on the output + output_->write(true); + for (volatile int i = 0; i < 3000; i++) { } + output_->write(false); + } + } + + /// Flow to which we are registered. + dcc::RailcomHubFlow *parent_; + /// Output gpio to toggle. + const Gpio *output_; +}; // RailcomToGpioFlow + } // namespace dcc namespace openlcb From 6920d7e3604a53272b50ca027de14abee9f9a266 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 9 Aug 2020 23:56:37 +0200 Subject: [PATCH 4/5] Adds the ability to run tests under performance profiling. (#416) * Adds the ability to run tests under performance profiling. For this apt-get install libgoogle-perftools-dev needs to be installed. * removes unnecessary whitespace --- etc/cov.mk | 15 +++++++++++++-- etc/test.mk | 8 +++++--- src/utils/test_main.hxx | 8 ++++++++ 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/etc/cov.mk b/etc/cov.mk index db1f5d37c..12082186f 100644 --- a/etc/cov.mk +++ b/etc/cov.mk @@ -18,10 +18,12 @@ HOST_TARGET := 1 STARTGROUP := -Wl,--start-group ENDGROUP := -Wl,--end-group +TESTOPTIMIZATION=-O0 + ifdef SKIP_COVERAGE -ARCHOPTIMIZATION = -g -O0 +ARCHOPTIMIZATION = -g $(TESTOPTIMIZATION) else -ARCHOPTIMIZATION = -g -O0 -fprofile-arcs -ftest-coverage +ARCHOPTIMIZATION = -g $(TESTOPTIMIZATION) -fprofile-arcs -ftest-coverage endif CSHAREDFLAGS = -c -frandom-seed=$(shell echo $(abspath $<) | md5sum | sed 's/\(.*\) .*/\1/') $(ARCHOPTIMIZATION) $(INCLUDES) -Wall -Werror -Wno-unknown-pragmas -MD -MP -fno-stack-protector -D_GNU_SOURCE -DGTEST @@ -36,6 +38,15 @@ LDFLAGS = $(ARCHOPTIMIZATION) -Wl,-Map="$(@:%=%.map)" SYSLIB_SUBDIRS += SYSLIBRARIES = -lrt -lpthread -lavahi-client -lavahi-common $(SYSLIBRARIESEXTRA) +ifdef RUN_GPERF +CXXFLAGS += -DWITHGPERFTOOLS +LDFLAGS += -DWITHGPERFTOOLS +SYSLIBRARIES += -lprofiler +TESTOPTIMIZATION = -O3 +SKIP_COVERAGE = 1 +endif + + ifndef SKIP_COVERAGE LDFLAGS += -pg SYSLIBRARIES += -lgcov diff --git a/etc/test.mk b/etc/test.mk index 7ca2fe721..e60514e6c 100644 --- a/etc/test.mk +++ b/etc/test.mk @@ -45,11 +45,13 @@ LIBS = $(STARTGROUP) \ $(ENDGROUP) \ $(LINKCORELIBS) +TESTOPTIMIZATION=-O0 + INCLUDES += -I$(GTESTPATH)/include -I$(GMOCKPATH)/include -I$(GMOCKPATH) \ -I$(OPENMRNPATH)/src -I$(OPENMRNPATH)/include -CFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage -O0 -CXXFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage -O0 -SYSLIBRARIES += -lgcov -fprofile-arcs -ftest-coverage -O0 +CFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage $(TESTOPTIMIZATION) +CXXFLAGS += -DGTEST $(INCLUDES) -Wno-unused-but-set-variable -fprofile-arcs -ftest-coverage $(TESTOPTIMIZATION) +SYSLIBRARIES += -lgcov -fprofile-arcs -ftest-coverage $(TESTOPTIMIZATION) LDFLAGS += -L$(LIBDIR) .SUFFIXES: diff --git a/src/utils/test_main.hxx b/src/utils/test_main.hxx index 104e03ef2..2504ec543 100644 --- a/src/utils/test_main.hxx +++ b/src/utils/test_main.hxx @@ -44,6 +44,7 @@ #include #include #include +#include #include "gtest/gtest.h" #include "gmock/gmock.h" @@ -54,6 +55,13 @@ #include "os/os.h" #include "utils/StringPrintf.hxx" +#ifdef WITHGPERFTOOLS +#include + +std::function profiler_enable{&ProfilerEnable}; +std::function profiler_disable{&ProfilerDisable}; +#endif + int appl_main(int argc, char *argv[]) { testing::InitGoogleMock(&argc, argv); From 1225b67642dec553fbcd016beae9d482a8ffe097 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 27 Aug 2020 23:21:51 +0200 Subject: [PATCH 5/5] Adds a partial implementation of VirtualMemorySpace (#418) The VirtualMemorySpace is an implementation of a MemorySpace interface which does not have a linear storage space behind. Instead, each variable is registered with a callback to write and read. This allows various advanced features, such as remapping address space, creating holes, really huge repetitions, etc. This is the first cut of an implementation that is not feature complete, only one data type is supported, and has todos left. === * Adds VirtualMemorySpace a memory space where the values are not stored in a contiguous storage area but are read and written via callbacks. * Adds comments. * Adds more tests. * Adds another test. * Fix copyright date. * fix typos. * Adds unit tests for asynchronous execution on the memory space. Fixes a respective bug in the implementation. * fully parenthesizes complex expressions. --- src/openlcb/VirtualMemorySpace.cxxtest | 370 +++++++++++++++++++++++++ src/openlcb/VirtualMemorySpace.hxx | 329 ++++++++++++++++++++++ 2 files changed, 699 insertions(+) create mode 100644 src/openlcb/VirtualMemorySpace.cxxtest create mode 100644 src/openlcb/VirtualMemorySpace.hxx diff --git a/src/openlcb/VirtualMemorySpace.cxxtest b/src/openlcb/VirtualMemorySpace.cxxtest new file mode 100644 index 000000000..ba84e044c --- /dev/null +++ b/src/openlcb/VirtualMemorySpace.cxxtest @@ -0,0 +1,370 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file VirtualMemorySpace.cxxtest + * + * Unit tests for the virtual memory space. + * + * @author Balazs Racz + * @date 10 Aug 2020 + */ + +#include "openlcb/VirtualMemorySpace.hxx" + +#include "openlcb/ConfigRepresentation.hxx" +#include "openlcb/MemoryConfigClient.hxx" +#include "utils/async_datagram_test_helper.hxx" + +namespace openlcb +{ + +string arg1; +string arg2; + +CDI_GROUP(ExampleMemorySpace); +CDI_GROUP_ENTRY(skipped, EmptyGroup<5>); +CDI_GROUP_ENTRY(first, StringConfigEntry<13>); +CDI_GROUP_ENTRY(skipped2, EmptyGroup<8>); +CDI_GROUP_ENTRY(second, StringConfigEntry<20>); +CDI_GROUP_END(); + +ExampleMemorySpace cfg(44); + +const unsigned arg1_ofs = 44 + 5; +const unsigned arg2_ofs = 44 + 5 + 13 + 8; + +class VirtualMemorySpaceTest : public AsyncDatagramTest +{ +protected: + ~VirtualMemorySpaceTest() + { + wait(); + } + + MemoryConfigHandler memCfg_ {&datagram_support_, node_, 3}; + std::unique_ptr space_; + MemoryConfigClient client_ {node_, &memCfg_}; +}; + +class TestSpace : public VirtualMemorySpace +{ +public: + TestSpace() + { + arg1.clear(); + arg2.clear(); + register_string( + cfg.first(), string_reader(&arg1), string_writer(&arg1)); + register_string( + cfg.second(), string_reader(&arg2), string_writer(&arg2)); + } + + /// Creates a ReaderFunction that just returns a string from a given + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_reader(string *ptr) + { + return + [ptr](unsigned repeat, string *contents, BarrierNotifiable *done) { + *contents = *ptr; + done->notify(); + return true; + }; + } + + /// Creates a WriterFunction that just stores the data in a given string + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_writer(string *ptr) + { + return + [ptr](unsigned repeat, string contents, BarrierNotifiable *done) { + *ptr = std::move(contents); + done->notify(); + }; + } +}; + +class TestSpaceTest : public VirtualMemorySpaceTest +{ +protected: + TestSpaceTest() + { + space_.reset(new TestSpace); + memCfg_.registry()->insert(node_, SPACE, space_.get()); + } + + /// Memory space number where the test space is registered. + const uint8_t SPACE = 0x52; +}; + +TEST_F(TestSpaceTest, create) +{ +} + +/// Basic tests reading variables from the exact offset including partial +/// reads. +TEST_F(TestSpaceTest, read_payload) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("hello", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, 20); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("", b->data()->payload.c_str()); + EXPECT_EQ(20u, b->data()->payload.size()); + + // prefix read + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, 3); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("hel", b->data()->payload); +} + +/// Test reading a variable from an imprecise offset (too early). +TEST_F(TestSpaceTest, read_early) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 2, 10); + ASSERT_EQ(0, b->data()->resultCode); + string exp("\0\0hello\0\0\0", 10); + EXPECT_EQ(exp, b->data()->payload); + EXPECT_EQ(10u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 4, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp2("\0\0\0", 3); + EXPECT_EQ(exp2, b->data()->payload); + EXPECT_EQ(3u, b->data()->payload.size()); +} + +/// Test writing a variable to a offset that is not covered at all. +TEST_F(TestSpaceTest, read_hole) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 5, 3); + ASSERT_EQ(0, b->data()->resultCode); + string exp("\0\0\0", 3); + EXPECT_EQ(exp, b->data()->payload); + + /// @todo this return seems to be wrong, although this address is out of + /// the memory space limits. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 5, 2); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string(), b->data()->payload); + + /// @todo this return seems to be wrong, although this address is out of + /// the memory space limits. + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 100, 4); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ(string(), b->data()->payload); +} + +/// Basic tests writing variables from the exact offset but not including +/// partial writes. +TEST_F(TestSpaceTest, write_payload) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, "xyzw"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("xyzw", arg1); + EXPECT_EQ(4u, arg1.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, "abcde"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("abcde", arg2); + EXPECT_EQ(5u, arg2.size()); +} + +/// Test writing a variable to a offset that is too early. +TEST_F(TestSpaceTest, write_early) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 2, "xyzw"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("zw", arg1); + EXPECT_EQ(2u, arg1.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 1, "qwert"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("wert", arg2); + EXPECT_EQ(4u, arg2.size()); +} + +/// Test writing a variable to a offset that is not covered at all. +TEST_F(TestSpaceTest, write_hole) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs - 5, "xyz"); + ASSERT_EQ(0, b->data()->resultCode); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs - 5, "qw"); + ASSERT_EQ(0, b->data()->resultCode); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs + 100, "qw"); + ASSERT_EQ(0, b->data()->resultCode); +} + +class TestSpaceAsync : public VirtualMemorySpace +{ +public: + TestSpaceAsync() + { + arg1.clear(); + arg2.clear(); + register_string( + cfg.first(), string_reader(&arg1), string_writer(&arg1)); + register_string( + cfg.second(), string_reader(&arg2), string_writer(&arg2)); + } + + /// Creates a ReaderFunction that just returns a string from a given + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_reader(string *ptr) + { + return [this, ptr]( + unsigned repeat, string *contents, BarrierNotifiable *done) { + attempt++; + if ((attempt & 1) == 0) + { + *contents = *ptr; + done->notify(); + return true; + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + return false; + } + }; + } + + /// Creates a WriterFunction that just stores the data in a given string + /// variable. + /// @param ptr the string whose contents to return as read value. Must stay + /// alive as long as the function is in use. + /// @return the ReaderFunction. + std::function + string_writer(string *ptr) + { + return [this, ptr]( + unsigned repeat, string contents, BarrierNotifiable *done) { + attempt++; + if ((attempt & 1) == 0) + { + *ptr = std::move(contents); + done->notify(); + return true; + } + else + { + g_executor.add( + new CallbackExecutable([done]() { done->notify(); })); + return false; + } + }; + } + +private: + size_t attempt = 0; +}; + +class TestSpaceAsyncTest : public VirtualMemorySpaceTest +{ +protected: + TestSpaceAsyncTest() + { + space_.reset(new TestSpaceAsync); + memCfg_.registry()->insert(node_, SPACE, space_.get()); + } + + /// Memory space number where the test space is registered. + const uint8_t SPACE = 0x52; +}; + +/// Basic tests reading variables from async space. +TEST_F(TestSpaceAsyncTest, read_payload_async) +{ + arg1 = "hello"; + auto b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, 13); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("hello", b->data()->payload.c_str()); + EXPECT_EQ(13u, b->data()->payload.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::READ_PART, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, 20); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_STREQ("", b->data()->payload.c_str()); + EXPECT_EQ(20u, b->data()->payload.size()); +} + +/// Basic tests writing variables from the exact offset but not including +/// partial writes. +TEST_F(TestSpaceAsyncTest, write_payload_async) +{ + auto b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg1_ofs, "xyzw"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("xyzw", arg1); + EXPECT_EQ(4u, arg1.size()); + + b = invoke_flow(&client_, MemoryConfigClientRequest::WRITE, + NodeHandle(node_->node_id()), SPACE, arg2_ofs, "abcde"); + ASSERT_EQ(0, b->data()->resultCode); + EXPECT_EQ("abcde", arg2); + EXPECT_EQ(5u, arg2.size()); +} + +} // namespace openlcb diff --git a/src/openlcb/VirtualMemorySpace.hxx b/src/openlcb/VirtualMemorySpace.hxx new file mode 100644 index 000000000..fc6e48f30 --- /dev/null +++ b/src/openlcb/VirtualMemorySpace.hxx @@ -0,0 +1,329 @@ +/** \copyright + * Copyright (c) 2020, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file VirtualMemorySpace.hxx + * + * Implementation of a memory space where the values are not stored in a + * contiguous storage area but are read and written via callbacks. + * + * @author Balazs Racz + * @date 10 Aug 2020 + */ + +#ifndef _OPENLCB_VIRTUALMEMORYSPACE_HXX +#define _OPENLCB_VIRTUALMEMORYSPACE_HXX + +#include "openlcb/ConfigEntry.hxx" +#include "openlcb/MemoryConfig.hxx" +#include "utils/SortedListMap.hxx" + +namespace openlcb +{ + +/// Implementation of a memory space where the values are not stored in a +/// contiguous storage area but are read and written via callbacks. +class VirtualMemorySpace : public MemorySpace +{ +public: + /// @returns whether the memory space does not accept writes. + bool read_only() override + { + return isReadOnly_; + } + /// @returns the lowest address that's valid for this block. + address_t min_address() override + { + return minAddress_; + } + /// @returns the largest valid address for this block. A read of 1 from + /// this address should succeed in returning the last byte. + address_t max_address() override + { + return maxAddress_; + } + + /// @return the number of bytes successfully written (before hitting end + /// of space). + /// @param destination address to write to + /// @param data to write + /// @param len how many bytes to write + /// @param error if set to non-null, then the operation has failed. If the + /// operation needs to be continued, then sets error to + /// MemorySpace::ERROR_AGAIN, and calls the Notifiable + /// @param again when a re-try makes sense. The caller should call write + /// once more, with the offset adjusted with the previously returned + /// bytes. + size_t write(address_t destination, const uint8_t *data, size_t len, + errorcode_t *error, Notifiable *again) override + { + if ((destination > maxAddress_) || ((destination + len) <= minAddress_)) + { + *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS; + return 0; + } + *error = 0; + unsigned repeat; + const DataElement *element = nullptr; + ssize_t skip = find_data_element(destination, len, &element, &repeat); + string payload; + if (skip > 0) + { + // Will cause a new call be delivered with adjusted data and len. + return skip; + } + else if (skip < 0) + { + // We have some missing bytes that we need to read out first, then + // can perform the write. + DIE("unimplemented"); + // if (!element->readImpl_(repeat, &payload, + } + HASSERT(element); + payload.assign( + (const char *)data, std::min((size_t)len, (size_t)element->size_)); + size_t written_len = payload.size(); + bn_.reset(again); + element->writeImpl_(repeat, std::move(payload), bn_.new_child()); + if (bn_.abort_if_almost_done()) + { + return written_len; + } + else + { + // did not succeed synchronously. + bn_.notify(); // our slice + *error = MemorySpace::ERROR_AGAIN; + return 0; + } + } + + /** @returns the number of bytes successfully read (before hitting end of + * space). If *error is set to non-null, then the operation has failed. If + * the operation needs to be continued, then sets error to ERROR_AGAIN, and + * calls the Notifiable @param again when a re-try makes sense. The caller + * should call read once more, with the offset adjusted with the previously + * returned bytes. */ + size_t read(address_t source, uint8_t *dst, size_t len, errorcode_t *error, + Notifiable *again) override + { + if ((source > maxAddress_) || ((source + len) <= minAddress_)) + { + *error = MemoryConfigDefs::ERROR_OUT_OF_BOUNDS; + return 0; + } + *error = 0; + unsigned repeat; + const DataElement *element = nullptr; + ssize_t skip = find_data_element(source, len, &element, &repeat); + if (skip > 0) + { + memset(dst, 0, skip); + return skip; + } + else if (skip < 0) + { + DIE("unimplemented"); + } + HASSERT(element); + string payload; + bn_.reset(again); + element->readImpl_(repeat, &payload, bn_.new_child()); + if (!bn_.abort_if_almost_done()) + { + // did not succeed synchronously. + bn_.notify(); // our slice + *error = MemorySpace::ERROR_AGAIN; + return 0; + } + payload.resize(element->size_); // pads with zeroes + size_t data_len = std::min(payload.size(), len); + memcpy(dst, payload.data(), data_len); + return data_len; + } + +protected: + /// Function that will be called for writes. + /// @param repeat 0 to number of repeats if this is in a repeated + /// group. Always 0 if not repeated group. + /// @param contents data payload that needs to be written. The data + /// bytes of this container start at the address_. + /// @param done must be notified when the write is complete (possibly + /// inline). + using WriteFunction = std::function; + /// Function that will be called for reads. + /// @param repeat 0 to number of repeats if this is in a repeated + /// group. Always 0 if not repeated group. + /// @param contents the payload to be returned from this variable shall + /// be written here. Will be zero-padded to size_ bytes if shorter. + /// @param done must be notified when the read values are ready. The + /// call will be re-tried in this case. + /// @return true if the read was successful, false if the read needs to be + /// re-tried later, + using ReadFunction = std::function; + + /// Setup the address bounds from a single CDI group declaration. + /// @param group is an instance of a group, for example a segment. + template void set_bounds_from_group(const G &group) + { + minAddress_ = group.offset(); + maxAddress_ = group.offset() + group.size() - 1; + } + + /// Expand the address bounds from a single CDI group declaration. + /// @param group is an instance of a group, for example a segment. + template void expand_bounds_from_group(const G &group) + { + minAddress_ = std::min(minAddress_, (address_t)group.offset()); + maxAddress_ = std::max( + maxAddress_, (address_t)(group.offset() + group.size() - 1)); + } + + /// Register an untyped element. + /// @param address the address in the memory space + /// @param size how many bytes this elements occupes + /// @param read_f will be called to read this data + /// @param write_f will be called to write this data + void register_element(address_t address, address_t size, + ReadFunction read_f, WriteFunction write_f) + { + elements_.insert(DataElement(address, size, read_f, write_f)); + } + + /// Registers a string typed element. + /// @param entry is the CDI ConfigRepresentation. + /// @param read_f will be called to read this data + /// @param write_f will be called to write this data + template + void register_string(const StringConfigEntry &entry, + ReadFunction read_f, WriteFunction write_f) + { + expand_bounds_from_group(entry); + register_element(entry.offset(), SIZE, read_f, write_f); + } + + /// Bounds for valid addresses. + address_t minAddress_ = 0xFFFFFFFFu; + /// Bounds for valid addresses. A read of length 1 from this address + /// should succeed in returning the last byte. + address_t maxAddress_ = 0; + /// Whether the space should report as RO. + bool isReadOnly_ = false; + +private: + /// We keep one of these for each variable that was declared. + struct DataElement + { + DataElement(address_t address, address_t size, ReadFunction read_f, + WriteFunction write_f) + : address_(address) + , size_(size) + , writeImpl_(write_f) + , readImpl_(read_f) + { + } + /// Base offset of this variable (first repeat only). + address_t address_; + /// Size of this variable. This is how many bytes of address space this + /// variable occupies. + address_t size_; + /// Function that will be called for writes. + WriteFunction writeImpl_; + /// Function that will be called for reads. + ReadFunction readImpl_; + }; + + struct Comparator + { + /// Sorting operator by address. + bool operator()(const DataElement &a, const DataElement &b) const + { + return a.address_ < b.address_; + } + /// Sorting operator by address. + bool operator()(unsigned a, const DataElement &b) const + { + return a < b.address_; + } + }; + + /// Look up the first matching data element given an address in the virtual + /// memory space. + /// @param address byte offset to look up. + /// @param len how many bytes long range to search from address. + /// @param ptr will be filled with a pointer to the data element when + /// found, or filled with nullptr if no data element overlaps with the + /// given range. + /// @param repeat output argument, filled with zero or the repetition + /// number. + /// @return 0 if an exact match is found. -N if a data element is found, + /// but N first bytes of this element are not covered. A number N in + /// [1..len-1] if a data element is found, but this many bytes need to be + /// skipped from address to arrive at the given data element's offset. len + /// if there was no data element found (in which case also set ptr to + /// null). + ssize_t find_data_element(address_t address, address_t len, + const DataElement **ptr, unsigned *repeat) + { + *repeat = 0; + *ptr = nullptr; + auto it = elements_.upper_bound(address); + if (it != elements_.begin()) + { + auto pit = it - 1; + // now: pit->address_ <= address + if (pit->address_ + pit->size_ > address) + { + // found overlap + *ptr = &*pit; + return (ssize_t)pit->address_ - + (ssize_t)address; // may be negative! + } + // else: no overlap, look at the next item + } + // now: it->address_ > address + if (address + len > it->address_) + { + // found overlap, but some data needs to be discarded. + *ptr = &*it; + return it->address_ - address; + } + /// @todo try repeated fields here first. + + // now: no overlap either before or after. + return len; + } + + /// Stores all the registered variables. + SortedListSet elements_; + /// Helper object in the function calls. + BarrierNotifiable bn_; +}; // class VirtualMemorySpace + +} // namespace openlcb + +#endif // _OPENLCB_VIRTUALMEMORYSPACE_HXX