Skip to content

Commit

Permalink
Upgrade HardwareSPI library with Rp2040 support (#2470)
Browse files Browse the repository at this point in the history
This PR adds support for Rp2040 to the HardwareSPI and Graphics libraries.

Host support is also improved to allow integration testing.
This includes revisions to thread/interrupt handling to more closely emulate real hardware behaviour.

## HardwareSPI

- Refactor using architecture-specific `ControllerBase` class to simplify/clarify Controller.h.

- Replace `maxTransactionSize` with `sizeAlign`

    Required only where requests have to be split up due to size.

    For example, transferring 4K of display pixel data for ESP8266 will require splitting into transactions of 64 bytes or less.
    The data must be transferred strictly in groups of 3 bytes (R, G, B) so `sizeAlign=3` ensures this happens.

    ESP32 has ~4K DMA capability, so still must be considered.
    RP2040 has no DMA size restriction so can be simplified.

- Fix Host Controller sequencing

    A complex application (e.g. Basic_Graphics) should run without stalling, race conditions or exceptions.

### Improvements

- Add version header

- Add loopback method to Controller

- Copy pin setup from SPI library

    Provides some consistency between the two libraries.

- Supported device IO mode also depends on controller capability

    e.g. Device may support quad modes but controller may not,
    so `Device::isSupported` method returns intersection of device/controller capabilities.

- Add Rp2040 implementation

    DMA transaction sizes are unrestricted which simplifies code, offset by lack of cmd/address/dummy
    phase support which complicates it.

    Hardware CS behaviour is weird. It gets asserted for every word in mode 0 or 2 (but not 1 or 3).
    This breaks operation with some devices, e.g. ILI9341.
    Also, there's no 'request complete' interrupt available.

    This is solved by using standard GPIO for CS control and completing request on RX DMA interrupt.

- Add test application

    Provides a vehicle for testing basic operation and performing integration testing.

    Improved Host interrupt support in framework allows inclusion in CI testing,
    inserting calculated delay (based on number of bits being transferred) before firing interrupt.


## Graphics

- Add Rp2040 support

- Remove extraneous file added in error - duplicate of file in resource/fonts/Linux/

- Fix issue with `showFonts` in Basic_Graphics sample

    Gets stuck second time around the loop

- Update Basic_Graphics sample, use for integration tests

    Simplify font functions.
    Add build variables to configure application to run more quickly so it can be used in CI testing.

    Host HardwareSPI may not do much but it allows the control flow to be verified and checked with valgrind, etc.

- Fix access violation rendering fonts

    Detected in Windows CI run.

- Don't assume location of Graphics library

    Running CI on standalone library means it won't be in the Sming repo.


## Framework

- Fix flashmem read

    Must claim DMA channel so it doesn't get given to HardwareSPI.

- Improve CThread interrupt handling

    Make `host_printf`, etc. threadsafe using `write` instead of buffered `printf` calls.

    Honour interrupt levels, masking out lower-level threads during interrupt

    Main thread synchronisation fixed. Mitigate signal deadlock using timer.

    Note: Running in valgrind may encounter 'undefined syscall' errors (407, 409)
    due to missing wrappers for 64-bit time functions.
    Valgrind version 3.17 has the wrappers (available in Ubuntu 21.04).

- Add CI test notification support

    Graphics library test uses a sample application rather than SmingTest,
    so notifications are handled in a makefile.

- Update Graphics & HardwareSPI libraries

    RP2040 support added.

- Use 64-bit python

    Freetype python library doesn't work on Windows as DLL isn't installed for 32-bit python.
    This only happens with standard appveyor system as `C:\Python39` contains 32-bit python installation.
  • Loading branch information
mikee47 authored Jan 21, 2022
1 parent 95dc764 commit 98d189c
Show file tree
Hide file tree
Showing 17 changed files with 470 additions and 148 deletions.
27 changes: 18 additions & 9 deletions Sming/Arch/Host/Components/driver/hw_timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
#include <driver/hw_timer.h>
#include <hostlib/threads.h>
#include <sys/time.h>
#include <errno.h>
#include <cerrno>
#include <hostlib/hostmsg.h>
#include <cassert>
#include <muldiv.h>
#include <esp_system.h>
#include <memory>

/* Timer 1 */

Expand Down Expand Up @@ -207,41 +208,49 @@ void* CTimerThread::thread_routine()
return nullptr;
}

static CTimerThread timer1("Timer1");
static std::unique_ptr<CTimerThread> 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()
Expand Down
4 changes: 1 addition & 3 deletions Sming/Arch/Host/Components/driver/include/driver/hw_timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
4 changes: 4 additions & 0 deletions Sming/Arch/Host/Components/driver/os_timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Sming/Arch/Host/Components/driver/uart_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ std::unique_ptr<CUart> servers[UART_COUNT];
class KeyboardThread : public CThread
{
public:
KeyboardThread() : CThread("keyboard", 0)
KeyboardThread() : CThread("keyboard", 1)
{
}

Expand Down
3 changes: 2 additions & 1 deletion Sming/Arch/Host/Components/gdbstub/gdbcmds
Original file line number Diff line number Diff line change
@@ -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
Expand Down
11 changes: 7 additions & 4 deletions Sming/Arch/Host/Components/hostlib/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Sming/Arch/Host/Components/hostlib/component.mk
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ EXTRA_LIBS := pthread

ifeq ($(UNAME),Windows)
EXTRA_LIBS += wsock32
else
EXTRA_LIBS += rt
endif

COMPONENT_DEPENDS := \
Expand Down
5 changes: 3 additions & 2 deletions Sming/Arch/Host/Components/hostlib/hostmsg.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
****/

#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include "include/hostlib/hostmsg.h"
Expand Down Expand Up @@ -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));
}
Loading

0 comments on commit 98d189c

Please sign in to comment.