From 2effad1f5359417fcb4497cbc2dd53c750e72a6d Mon Sep 17 00:00:00 2001 From: mikee47 Date: Thu, 13 Jan 2022 09:39:04 +0000 Subject: [PATCH 1/8] Fix flashmem read, must claim DMA channel --- Sming/Arch/Rp2040/Components/spi_flash/flashmem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sming/Arch/Rp2040/Components/spi_flash/flashmem.cpp b/Sming/Arch/Rp2040/Components/spi_flash/flashmem.cpp index 641d1fde4d..54d7f4a599 100644 --- a/Sming/Arch/Rp2040/Components/spi_flash/flashmem.cpp +++ b/Sming/Arch/Rp2040/Components/spi_flash/flashmem.cpp @@ -139,7 +139,7 @@ uint32_t readAligned(void* to, uint32_t fromaddr, uint32_t size) * Use the auxiliary bus slave for the DMA<-FIFO accesses, to avoid stalling * the DMA against general XIP traffic. */ - constexpr unsigned dma_chan{0}; + auto dma_chan = dma_claim_unused_channel(true); dma_channel_config cfg = dma_channel_get_default_config(dma_chan); channel_config_set_transfer_data_size(&cfg, dmaTransferSize); channel_config_set_read_increment(&cfg, false); @@ -153,6 +153,7 @@ uint32_t readAligned(void* to, uint32_t fromaddr, uint32_t size) ); dma_channel_wait_for_finish_blocking(dma_chan); + dma_channel_unclaim(dma_chan); return size; } From 137120d83f2a2959d7629dce9cdf1d607b117f62 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Mon, 17 Jan 2022 13:15:57 +0000 Subject: [PATCH 2/8] Make `host_printf`, etc. threadsafe. --- Sming/Arch/Host/Components/hostlib/hostmsg.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sming/Arch/Host/Components/hostlib/hostmsg.c b/Sming/Arch/Host/Components/hostlib/hostmsg.c index af26d9df5a..0c561b0fd4 100644 --- a/Sming/Arch/Host/Components/hostlib/hostmsg.c +++ b/Sming/Arch/Host/Components/hostlib/hostmsg.c @@ -18,6 +18,7 @@ ****/ #include +#include #include #include #include "include/hostlib/hostmsg.h" @@ -72,10 +73,10 @@ void host_printfp(const char* fmt, const char* pretty_function, ...) size_t host_nputs(const char* str, size_t length) { - return fwrite(str, 1, length, stderr); + return write(STDERR_FILENO, str, length); } void host_puts(const char* str) { - fputs(str, stderr); + host_nputs(str, strlen(str)); } From 25de08b9cf6ec1f0e19f131e0d8da96c70bafbfe Mon Sep 17 00:00:00 2001 From: mikee47 Date: Sat, 15 Jan 2022 10:22:41 +0000 Subject: [PATCH 3/8] Improve CThread interrupt handling Honour interrupt levels, masking out lower-level threads during interrupt Main thread synchronisation fixed Mitigate signal deadlock using timer --- .../Arch/Host/Components/driver/hw_timer.cpp | 27 +- .../driver/include/driver/hw_timer.h | 4 +- .../Arch/Host/Components/driver/os_timer.cpp | 4 + .../Host/Components/driver/uart_server.cpp | 2 +- Sming/Arch/Host/Components/gdbstub/gdbcmds | 3 +- Sming/Arch/Host/Components/hostlib/README.rst | 11 +- .../Arch/Host/Components/hostlib/component.mk | 2 + .../Arch/Host/Components/hostlib/threads.cpp | 222 +++++++++++++--- Sming/Arch/Host/Components/hostlib/threads.h | 247 ++++++++++++------ 9 files changed, 384 insertions(+), 138 deletions(-) diff --git a/Sming/Arch/Host/Components/driver/hw_timer.cpp b/Sming/Arch/Host/Components/driver/hw_timer.cpp index c5d045355f..c7afa8f8f1 100644 --- a/Sming/Arch/Host/Components/driver/hw_timer.cpp +++ b/Sming/Arch/Host/Components/driver/hw_timer.cpp @@ -13,11 +13,12 @@ #include #include #include -#include +#include #include #include #include #include +#include /* Timer 1 */ @@ -207,41 +208,49 @@ void* CTimerThread::thread_routine() return nullptr; } -static CTimerThread timer1("Timer1"); +static std::unique_ptr timer1; + +void hw_timer_init(void) +{ + timer1.reset(new CTimerThread("Timer1")); +} void hw_timer_cleanup() { - timer1.terminate(); + if(timer1) { + timer1->terminate(); + timer1.reset(); + } } void hw_timer1_attach_interrupt(hw_timer_source_type_t source_type, hw_timer_callback_t callback, void* arg) { - timer1.attach_interrupt(source_type, callback, arg); + timer1->attach_interrupt(source_type, callback, arg); } void hw_timer1_enable(hw_timer_clkdiv_t div, hw_timer_intr_type_t intr_type, bool auto_load) { - timer1.enable(div, intr_type, auto_load); + timer1->enable(div, intr_type, auto_load); } void hw_timer1_write(uint32_t ticks) { - timer1.write(ticks); + timer1->write(ticks); } void hw_timer1_disable() { - timer1.stop(); + timer1->stop(); } void hw_timer1_detach_interrupt() { - timer1.detach_interrupt(); + timer1->detach_interrupt(); } uint32_t hw_timer1_read() { - return timer1.read(); + return timer1->read(); } uint32_t hw_timer2_read() diff --git a/Sming/Arch/Host/Components/driver/include/driver/hw_timer.h b/Sming/Arch/Host/Components/driver/include/driver/hw_timer.h index f2c24c46c8..314f625416 100644 --- a/Sming/Arch/Host/Components/driver/include/driver/hw_timer.h +++ b/Sming/Arch/Host/Components/driver/include/driver/hw_timer.h @@ -79,9 +79,7 @@ inline uint32_t NOW() return hw_timer2_read(); } -inline void hw_timer_init(void) -{ -} +void hw_timer_init(void); void hw_timer_cleanup(); diff --git a/Sming/Arch/Host/Components/driver/os_timer.cpp b/Sming/Arch/Host/Components/driver/os_timer.cpp index 5eddbb2900..6039f2431e 100644 --- a/Sming/Arch/Host/Components/driver/os_timer.cpp +++ b/Sming/Arch/Host/Components/driver/os_timer.cpp @@ -57,6 +57,10 @@ void os_timer_disarm(struct os_timer_t* ptimer) { assert(ptimer != nullptr); + if(int(ptimer->timer_next) == -1) { + return; + } + mutex.lock(); if(timer_list != nullptr) { // Remove timer from list diff --git a/Sming/Arch/Host/Components/driver/uart_server.cpp b/Sming/Arch/Host/Components/driver/uart_server.cpp index 552216b298..994658a3f6 100644 --- a/Sming/Arch/Host/Components/driver/uart_server.cpp +++ b/Sming/Arch/Host/Components/driver/uart_server.cpp @@ -36,7 +36,7 @@ std::unique_ptr servers[UART_COUNT]; class KeyboardThread : public CThread { public: - KeyboardThread() : CThread("keyboard", 0) + KeyboardThread() : CThread("keyboard", 1) { } diff --git a/Sming/Arch/Host/Components/gdbstub/gdbcmds b/Sming/Arch/Host/Components/gdbstub/gdbcmds index ce3dfcdbb9..9b184ee836 100644 --- a/Sming/Arch/Host/Components/gdbstub/gdbcmds +++ b/Sming/Arch/Host/Components/gdbstub/gdbcmds @@ -1,4 +1,5 @@ -handle SIGUSR1 nostop noprint +# Used for interrupt emulation +handle SIG34 SIG35 nostop noprint # Enable this if you want to log all traffic between GDB and the stub #set remotelogfile gdb_rsp_logfile.txt diff --git a/Sming/Arch/Host/Components/hostlib/README.rst b/Sming/Arch/Host/Components/hostlib/README.rst index 93f296b454..5b4e0ba5f2 100644 --- a/Sming/Arch/Host/Components/hostlib/README.rst +++ b/Sming/Arch/Host/Components/hostlib/README.rst @@ -33,13 +33,16 @@ Ideally we'd use SCHED_FIFO to disable time-slicing and more closely resemble ho on a single-core CPU. However, this mode isn't supported in Windows, and Linux requires privileged access and can potentially crash the system. Best avoided, I think. -All ESP code runs at a specific interrupt level, where 0 represents regular code. When an interrupt +All ESP code runs at a specific interrupt level, where 0 represents regular code. +Interrupts are triggered from a separate thread (a CThread instance) which calls :cpp:func:` +When an interrupt occurs, the level is raised according to the priority of that interrupt. Until that code has finished, only interrupts of a higher priority will preempt it. -The ``set_interrupt_level`` function is used to ensure that threads running at different interrupt -levels do not pre-empty each other, as this would introduce problems that do not exist on real hardware. -The main thread is also suspended during interrupt execution. +Thread 'interrupt' code is sandwiched between calls to `interrupt_begin()` and `interrupt_end()`, +which blocks interrupts from other threads at the same or lower level. +The threads aren't suspended but will block if they call `interrupt_begin()`. +However, the main thread (level 0) is halted to reflect normal interrupt behaviour. .. envvar:: LWIP_SERVICE_INTERVAL diff --git a/Sming/Arch/Host/Components/hostlib/component.mk b/Sming/Arch/Host/Components/hostlib/component.mk index 36956894c0..f2cd881212 100644 --- a/Sming/Arch/Host/Components/hostlib/component.mk +++ b/Sming/Arch/Host/Components/hostlib/component.mk @@ -2,6 +2,8 @@ EXTRA_LIBS := pthread ifeq ($(UNAME),Windows) EXTRA_LIBS += wsock32 +else + EXTRA_LIBS += rt endif COMPONENT_DEPENDS := \ diff --git a/Sming/Arch/Host/Components/hostlib/threads.cpp b/Sming/Arch/Host/Components/hostlib/threads.cpp index b93160adfb..3f05802935 100644 --- a/Sming/Arch/Host/Components/hostlib/threads.cpp +++ b/Sming/Arch/Host/Components/hostlib/threads.cpp @@ -18,72 +18,226 @@ ****/ #include "threads.h" +#include +#include #include -CMutex CThread::interrupt; +CThread::List CThread::list; +unsigned CThread::interrupt_mask; +CBasicMutex* interrupt; -#ifdef __WIN32 +namespace +{ +pthread_t mainThread; -static HANDLE mainThread; +#ifndef __WIN32 -#else +volatile bool mainThreadSignalled; +timer_t signalTimer; +int pauseSignal; +int resumeSignal; -static pthread_t mainThread; -static CSemaphore mainThreadSemaphore; - -static void signal_handler(int sig) +void signal_handler(int sig) { - if(sig == SIGUSR1) { - mainThreadSemaphore.wait(); + if(sig == pauseSignal) { + mainThreadSignalled = true; + /* + * A resumeSignal here results in deadlock as we're waiting for a signal which will never arrive. + * Possibly useful async-safe functions: + * + * - sem_post() + * - alarm(secs) Delivers SIGALRM after a delay. + * - timer_settime() as for alarm() but with smaller interval + * + */ + struct timespec ts = {0, long(0.1e9)}; + struct itimerspec its = {ts, ts}; + timer_settime(signalTimer, 0, &its, nullptr); + while(mainThreadSignalled) { + pause(); + } + its = {}; + timer_settime(signalTimer, 0, &its, nullptr); + } else if(sig == resumeSignal) { + mainThreadSignalled = false; + } else if(sig == SIGALRM) { + } else { + assert(false); } } #endif -void CThread::startup() +bool isMainThread() +{ + return pthread_equal(pthread_self(), mainThread); +} + +void suspend_main_thread() { + assert(!isMainThread()); + #ifdef __WIN32 - mainThread = OpenThread(THREAD_ALL_ACCESS, FALSE, GetCurrentThreadId()); + SuspendThread(pthread_getw32threadhandle_np(mainThread)); #else - mainThread = pthread_self(); - signal(SIGUSR1, signal_handler); + + assert(!mainThreadSignalled); + assert(pthread_kill(mainThread, pauseSignal) == 0); + while(!mainThreadSignalled) { + sched_yield(); + } #endif } -static void suspend_main_thread(bool suspend) +void resume_main_thread() { + assert(!isMainThread()); + #ifdef __WIN32 - if(suspend) { - SuspendThread(mainThread); - } else { - ResumeThread(mainThread); - } + ResumeThread(pthread_getw32threadhandle_np(mainThread)); #else - if(suspend) { - pthread_kill(mainThread, SIGUSR1); - } else { - mainThreadSemaphore.post(); + assert(mainThreadSignalled); + assert(pthread_kill(mainThread, resumeSignal) == 0); + while(mainThreadSignalled) { + sched_yield(); } #endif } -void CThread::set_interrupt_level(unsigned new_level) +} // namespace + +void CMutex::lock() { - if(new_level > 0) { - interrupt.lock(); - suspend_main_thread(true); - } else { - suspend_main_thread(false); - interrupt.unlock(); - } + interrupt->lock(); + CBasicMutex::lock(); +} + +void CMutex::unlock() +{ + CBasicMutex::unlock(); + interrupt->unlock(); +} + +void CThread::startup() +{ + mainThread = pthread_self(); + interrupt = new CBasicMutex; +#ifndef __WIN32 + pauseSignal = SIGRTMIN + 0; + resumeSignal = SIGRTMIN + 1; + signal(pauseSignal, signal_handler); + signal(resumeSignal, signal_handler); + signal(SIGALRM, signal_handler); + timer_create(CLOCK_MONOTONIC, nullptr, &signalTimer); +#endif +} + +CThread::CThread(const char* name, unsigned interrupt_level) : name(name), interrupt_level(interrupt_level) +{ + assert(interrupt_level > 0); + interrupt->lock(); + list.add(this); + interrupt->unlock(); +} + +CThread::~CThread() +{ + HOST_THREAD_DEBUG("Thread '%s' destroyed", name); + interrupt->lock(); + list.remove(this); + interrupt->unlock(); } void CThread::interrupt_lock() { - interrupt.lock(); + assert(interrupt_mask == 0); + interrupt->lock(); } void CThread::interrupt_unlock() { - interrupt.unlock(); + assert(interrupt_mask == 0); + interrupt->unlock(); +} + +void CThread::suspend() +{ + assert(!isCurrent()); + suspendMutex.lock(); + ++suspended; + suspendMutex.unlock(); +} + +void CThread::resume() +{ + assert(!isCurrent()); + suspendMutex.lock(); + --suspended; + if(suspended == 0) { + pthread_cond_signal(&resumeCond); + } + suspendMutex.unlock(); +} + +void CThread::interrupt_begin() +{ + assert(isCurrent()); + assert(interrupt_level > interrupt_mask); + + // Are we suspended by another thread? + suspendMutex.lock(); + while(suspended != 0) { + suspendMutex.wait(resumeCond); + } + suspendMutex.unlock(); + + interrupt->lock(); + + if(interrupt_mask == 0) { + suspend_main_thread(); + } + + for(auto& thread : list) { + if(&thread != this && thread.interrupt_level <= interrupt_level) { + thread.suspend(); + } + } + + previous_mask = interrupt_mask; + interrupt_mask = interrupt_level; + + interrupt->unlock(); +} + +void CThread::interrupt_end() +{ + assert(isCurrent()); + + interrupt->lock(); + + interrupt_mask = previous_mask; + + for(auto& thread : list) { + if(&thread != this && thread.interrupt_level <= interrupt_level) { + thread.resume(); + } + } + + if(interrupt_mask == 0) { + resume_main_thread(); + } + + interrupt->unlock(); +} + +const char* CThread::getCurrentName() +{ + auto cur = pthread_self(); + for(auto& t : list) { + if(t == cur) { + return t.name; + } + } + + return ""; } diff --git a/Sming/Arch/Host/Components/hostlib/threads.h b/Sming/Arch/Host/Components/hostlib/threads.h index 5ed12fbc82..124eac25b8 100644 --- a/Sming/Arch/Host/Components/hostlib/threads.h +++ b/Sming/Arch/Host/Components/hostlib/threads.h @@ -21,8 +21,10 @@ #include "include/hostlib/hostlib.h" #include +#include #include #include +#include #if defined(DEBUG_VERBOSE_LEVEL) && (DEBUG_VERBOSE_LEVEL == 3) #define HOST_THREAD_DEBUG(fmt, ...) host_printf(fmt "\n", ##__VA_ARGS__) @@ -30,107 +32,37 @@ #define HOST_THREAD_DEBUG(fmt, ...) #endif -class CMutex; - -class CThread +/** + * @brief Wrapper for posix thread mutex + * + * Note: Don't use this in application code, use `CMutex` as it guards against interrupt deadlocks. + */ +class CBasicMutex { public: - static void startup(); - - // Sets interrupt level for current thread - static void set_interrupt_level(unsigned new_level); - - CThread(const char* name, unsigned interrupt_level) : name(name), interrupt_level(interrupt_level) - { - } - - virtual ~CThread() - { - HOST_THREAD_DEBUG("Thread '%s' destroyed", name); - } - - bool execute() - { - return pthread_create(&m_thread, NULL, thread_start, this) == 0; - } - - bool detach() - { - return pthread_detach(m_thread) == 0; - } - - bool cancel() - { - return pthread_cancel(m_thread) == 0; - } - - void join() - { - pthread_join(m_thread, nullptr); - HOST_THREAD_DEBUG("Thread '%s' complete", name); - } - - bool isCurrent() const + ~CBasicMutex() { - return pthread_equal(pthread_self(), m_thread) != 0; - } - - /* - * Called at the start of any code which affects framework variables. - * Will block if any another thread is running in interrupt context. - * - * @todo Only block if another thread is running at the same or higher level - * i.e. high-priority interrupts can pre-empty lower-priority ones. - */ - void interrupt_begin() - { - set_interrupt_level(interrupt_level); + pthread_mutex_destroy(&m_priv); } - /* - * Allows other waiting threads to resume. - */ - void interrupt_end() + void lock() { - set_interrupt_level(0); + pthread_mutex_lock(&m_priv); } - static void interrupt_lock(); - static void interrupt_unlock(); - -protected: - virtual void* thread_routine() = 0; - -private: - static void* thread_start(void* param) + bool tryLock() { - auto thread = static_cast(param); - HOST_THREAD_DEBUG("Thread '%s' running", thread->name); - return thread->thread_routine(); + return pthread_mutex_trylock(&m_priv) == 0; } -private: - pthread_t m_thread = {0}; - const char* name; // Helps to identify purpose for debugging - unsigned interrupt_level; // Interrupt level associated with this thread - static CMutex interrupt; -}; - -class CMutex -{ -public: - ~CMutex() + void unlock() { - pthread_mutex_destroy(&m_priv); + pthread_mutex_unlock(&m_priv); } - void lock() + int wait(pthread_cond_t& cond) { - pthread_mutex_lock(&m_priv); - } - void unlock() - { - pthread_mutex_unlock(&m_priv); + return pthread_cond_wait(&cond, &m_priv); } private: @@ -141,6 +73,24 @@ class CMutex pthread_mutex_t m_priv = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; }; +/** + * @brief Wrapper for posix thread mutex with interrupt deadlock protection + * + * To simulate interrupts the main thread is suspended, but if this happens whilst it owns + * a mutex then interrupt code can deadlock the application. + * + * This object uses an additional mutex shared with CThread to guard against this. + */ +class CMutex : public CBasicMutex +{ +public: + void lock(); + void unlock(); +}; + +/** + * @brief Wrapper for posix semaphore + */ class CSemaphore { public: @@ -182,6 +132,131 @@ class CSemaphore return timedwait(&to); } + int value() const + { + int n{0}; + sem_getvalue(const_cast(&m_sem), &n); + return n; + } + private: sem_t m_sem; }; + +/** + * @brief Class to manage separate execution thread for simulating hardware and interrupts. + */ +class CThread : public LinkedObjectTemplate +{ +public: + using List = LinkedObjectListTemplate; + using OwnedList = OwnedLinkedObjectListTemplate; + + static void startup(); + + /** + * @brief Construct a new CThread object + * @param name Name of thread (for debugging) + * @param interrupt_level Must be > 0. Higher values can interrupt threads of lower levels. + */ + CThread(const char* name, unsigned interrupt_level); + + virtual ~CThread(); + + bool execute() + { + return pthread_create(&m_thread, NULL, thread_start, this) == 0; + } + + bool detach() + { + return pthread_detach(m_thread) == 0; + } + + bool cancel() + { + return pthread_cancel(m_thread) == 0; + } + + void join() + { + pthread_join(m_thread, nullptr); + HOST_THREAD_DEBUG("Thread '%s' complete", name); + } + + /** + * @brief Determine if running in the context of this thread + */ + bool isCurrent() const + { + return pthread_equal(pthread_self(), m_thread) != 0; + } + + /** + * @brief A thread calls this method before issuing an 'interrupt'. + * + * Will block if any another thread is running interrupt code at the same or higher level. + * i.e. high-priority interrupts can pre-empty lower-priority ones. + */ + void interrupt_begin(); + + /** + * @brief Signals end of interrupt code and allows other waiting threads to issue interrupts + */ + void interrupt_end(); + + /** + * @brief Prevent all interrupts + */ + static void interrupt_lock(); + + /** + * @brief Resume interrupts + */ + static void interrupt_unlock(); + + /** + * @brief Suspend interrupts for this thread. + */ + void suspend(); + + /** + * @brief Resume interrupts for this thread. + */ + void resume(); + + bool operator==(pthread_t other) const + { + return pthread_equal(other, m_thread); + } + + /** + * @brief Get name of the currently executing thread + */ + static const char* getCurrentName(); + +protected: + /** + * @brief Inherited classes must implement this method + */ + virtual void* thread_routine() = 0; + +private: + static void* thread_start(void* param) + { + auto thread = static_cast(param); + HOST_THREAD_DEBUG("Thread '%s' running", thread->name); + return thread->thread_routine(); + } + +private: + pthread_t m_thread = {0}; + const char* name; ///< Helps to identify purpose for debugging + unsigned interrupt_level; ///< Interrupt level associated with this thread + unsigned previous_mask{0};///< Used to restore previous interrupt mask when interrupt ends + unsigned suspended{0}; ///< Non-zero when thread interrupts are suspended + CBasicMutex suspendMutex; ///< Synchronises suspend + pthread_cond_t resumeCond = PTHREAD_COND_INITIALIZER; ///< Synchronnises resume + static List list; ///< All running threads + static unsigned interrupt_mask; ///< Current interrupt level +}; From 92abe49d9ae0a7977c24677b11dcda45e0b7c1c5 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Mon, 17 Jan 2022 20:24:26 +0000 Subject: [PATCH 4/8] Update Host README --- Sming/Arch/Host/README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sming/Arch/Host/README.rst b/Sming/Arch/Host/README.rst index 9326a585eb..8e8f3540d3 100644 --- a/Sming/Arch/Host/README.rst +++ b/Sming/Arch/Host/README.rst @@ -33,8 +33,6 @@ Building Build the framework and application as usual, specifying :envvar:`SMING_ARCH` =Host. For example:: - cd $SMING_HOME - make SMING_ARCH=Host cd $SMING_HOME/../samples/Basic_Serial make SMING_ARCH=Host From 3b3620b690644c13d0b091c8a3947cc0b8b3f20f Mon Sep 17 00:00:00 2001 From: mikee47 Date: Wed, 19 Jan 2022 14:43:01 +0000 Subject: [PATCH 5/8] Add CI test notification support --- Tools/ci/testnotify.sh | 47 ++++++++++++++++++++++++++++++++++++++++++ Tools/ci/util.mk | 24 +++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100755 Tools/ci/testnotify.sh create mode 100644 Tools/ci/util.mk diff --git a/Tools/ci/testnotify.sh b/Tools/ci/testnotify.sh new file mode 100755 index 0000000000..c681e46964 --- /dev/null +++ b/Tools/ci/testnotify.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# +# Send test notification to CI framework +# +# Don't call directly; use makefile functions in `util.mk` +# + +set -e + +testname="$1" +status="$2" + +testfile=/tmp/$MODULE.test +mkdir -p "$(dirname "$testfile")" +curNanos=$(date +%s%N) +if [ -f "$testfile" ]; then + prevNanos=$(<"$testfile") + elapsedMillis=$(( (curNanos - prevNanos) / 1000000 )) +fi + +case "$status" in + "start") + cmd=AddTest + status=Running + elapsedMillis=0 + ;; + "success") + cmd=UpdateTest + status=Passed + ;; + "fail") + cmd=UpdateTest + status=Failed + ;; + *) + echo "Invalid status: $status" + exit 1 + ;; +esac + +if [ -n "$APPVEYOR" ]; then + appveyor $cmd "$testname" -Framework Sming -Filename "$MODULE" -Outcome $status -Duration $elapsedMillis +else + echo "TestNotify: $cmd $testname -Framework Sming -Filename $MODULE -Outcome $status -Duration $elapsedMillis" +fi + +echo "$curNanos" > "$testfile" diff --git a/Tools/ci/util.mk b/Tools/ci/util.mk new file mode 100644 index 0000000000..b19337edb8 --- /dev/null +++ b/Tools/ci/util.mk @@ -0,0 +1,24 @@ +# +# CI utilities +# + +include $(SMING_HOME)/util.mk + +SMING_HOME := $(patsubst %/,%,$(call FixPath,$(SMING_HOME))) +CI_TOOLS_DIR := $(abspath $(SMING_HOME)/../Tools/ci) + +$(info CI_TOOLS_DIR = $(CI_TOOLS_DIR)) + +ifndef MODULE +MODULE := $(patsubst $(SMING_HOME)/%,%,$(shell pwd)) +endif + +export MODULE + +# Send CI test framework notification +# $1 -> Name of test +# $2 -> Status (start, success, fail) +# $MODULE -> Test module name +define TestNotify + $(CI_TOOLS_DIR)/testnotify.sh $1 $2 +endef From 953ab1e60561de34f4e7258552a4764f471d711f Mon Sep 17 00:00:00 2001 From: mikee47 Date: Fri, 14 Jan 2022 14:42:31 +0000 Subject: [PATCH 6/8] Update Graphics & HardwareSPI libraries --- Sming/Libraries/Graphics | 2 +- Sming/Libraries/HardwareSPI | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sming/Libraries/Graphics b/Sming/Libraries/Graphics index d65d6066ab..999ebaeb17 160000 --- a/Sming/Libraries/Graphics +++ b/Sming/Libraries/Graphics @@ -1 +1 @@ -Subproject commit d65d6066ab6aa9357fb93b0ca2f64112e5356614 +Subproject commit 999ebaeb177e14a2a592fb1fd70bcfe421c2019f diff --git a/Sming/Libraries/HardwareSPI b/Sming/Libraries/HardwareSPI index ae5efe4d99..b206f9348e 160000 --- a/Sming/Libraries/HardwareSPI +++ b/Sming/Libraries/HardwareSPI @@ -1 +1 @@ -Subproject commit ae5efe4d990340654baa399dba0c9e54203ec5eb +Subproject commit b206f9348e79a2aa95440b793cdb42cde3f6d90d From a73796a97fa77365e6d90c37fb518864f3961eb6 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Thu, 20 Jan 2022 12:06:13 +0000 Subject: [PATCH 7/8] Use 64-bit python (32-bit wheels not found for freetype) --- Tools/ci/setenv.ps1 | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Tools/ci/setenv.ps1 b/Tools/ci/setenv.ps1 index 9113108898..5e50e39b86 100644 --- a/Tools/ci/setenv.ps1 +++ b/Tools/ci/setenv.ps1 @@ -26,9 +26,14 @@ if (Test-Path "$env:PICO_TOOLCHAIN_PATH" ) { if ($IsWindows) { $env:PATH = "C:\MinGW\msys\1.0\bin;C:\MinGW\bin;$env:PATH" - $env:PATH = "C:\Python39;C:\Python39\Scripts;$env:PATH" - $env:PYTHON = "C:\Python39\python" - $env:ESP32_PYTHON_PATH = "C:\Python39" + $env:PYTHON_PATH = "C:\Python39-x64" + if ( -not (Test-Path "$env:PYTHON_PATH") ) { + $env:PYTHON_PATH = "C:\Python39" + } + + $env:PATH = "$env:PYTHON_PATH;$env:PYTHON_PATH\Scripts;$env:PATH" + $env:PYTHON = "$env:PYTHON_PATH\python" + $env:ESP32_PYTHON_PATH = "$env:PYTHON_PATH" $env:PATH = "$env:PROGRAMFILES\CMake\bin;$env:PATH" From e2c751658ba4b3ae3036cf93483f83e12eda6b98 Mon Sep 17 00:00:00 2001 From: mikee47 Date: Thu, 20 Jan 2022 16:34:23 +0000 Subject: [PATCH 8/8] HardwareSPI fixes Fix `Device::isSupported` method Handle full/half duplex transfers for Rp2040 Run `testCombos` for all supported IO modes --- Sming/Libraries/HardwareSPI | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/Libraries/HardwareSPI b/Sming/Libraries/HardwareSPI index b206f9348e..7717c018cc 160000 --- a/Sming/Libraries/HardwareSPI +++ b/Sming/Libraries/HardwareSPI @@ -1 +1 @@ -Subproject commit b206f9348e79a2aa95440b793cdb42cde3f6d90d +Subproject commit 7717c018cc9d5b057fa80965daaac024ca767aed