Skip to content

Riscv port basic thread#137

Merged
ventZl merged 7 commits intoventZl:masterfrom
tobiaguiar08:riscv_port_basic_thread
Apr 15, 2026
Merged

Riscv port basic thread#137
ventZl merged 7 commits intoventZl:masterfrom
tobiaguiar08:riscv_port_basic_thread

Conversation

@tobiaguiar08
Copy link
Copy Markdown
Contributor

@tobiaguiar08 tobiaguiar08 commented Feb 1, 2026

Summary

This PR is my first attempt at scaffolding RISC-V support for CMRX thread switching, targeting to use RP2350 (Pico 2) with Pico SDK as the validation platform. The goal is to get a minimal context switcher + syscall/trap path wired enough to start doing on-target debugging and validation.

What’s included

  • RISC-V syscall entry (ecall) and handler wiring so kernel syscalls can be dispatched.
  • Minimal RISC-V scheduler/boot primitives needed to start the first thread and perform basic switching.
  • Stubs for memory protection (PMP/MPU not implemented) to keep scope focused on switching bring-up.
  • Static init linker symbols/sections to support CMRX’s static thread/application tables.
  • Pico SDK integration (“quirk”) for RP2350 RISC-V, including startup/exception integration points.

Current status

  • Latest changes in this code used with a small validation app it currently builds
  • On-target validation is now working with latest changes.

I’d appreciate your review/feedback

@tobiaguiar08 tobiaguiar08 mentioned this pull request Feb 1, 2026
Copy link
Copy Markdown
Owner

@ventZl ventZl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your submission!

Except of the requirement to move RISC-V-related tests into riscv subtree, all remaining comments are just general non-binding stuff and can be resolved later.

Comment thread src/os/kernel/tests/CMakeLists.txt Outdated
Comment thread src/os/arch/riscv/hal/arch/sysenter.h Outdated

/* Syscall/trap entry is platform-specific. */
#define __SYSCALL
#define __SVC(x) return 0;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to return E_NOTAVAIL here rather than 0. It will make whatever code that calls it fail early and serve as a reminder that there is something waiting to be implemented.

Comment thread include/cmrx/arch/riscv/context_switch.h Outdated
uint32_t *new_saved_sp = NULL;
uint32_t *new_sp_after = NULL;

riscv_setup_switch(&old_sp, &new_saved_sp, &new_sp_after);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use CTEST_DATA / CTEST_SETUP / CTEST2 macros to automate this. Many kernel tests use this pattern.

CTEST_DATA serves purpose of defining per-group specific test data. In most cases this is not used, so you keep this empty.

CTEST_SETUP is executed automatically before every test starts, so you can reinitialize system into clean state there without having to call anything explicitly.

Use of CTEST2 macro rather than CTEST one is needed to trigger this semantics.

CTEST_DATA/CTEST_SETUP are grouped per test group (1st argument). You can have as many you need.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to use CTEST_DATA/CTEST_SETUP, not sure it was done right. Please recheck.

* - mepc points to ecall instruction, must be incremented by 4 to continue
*/

#define FRAME_OFFSET_A0 16
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this be replaced by something like type ExceptionFrame in arch/arm/cmsis/arch/cortex.h ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created an exception frame adapter for rp2350 for now. SHould I try to make it generic now or we leave it for another PR?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't bother with extensive cleanup while the port is not done. It may easily be found out things will have to be reworked while implementing later stages of support. Thus effort spent by making things generic may easily be thrown out.

@@ -0,0 +1,34 @@
# CMRX quirk for Pico SDK RISC-V (RP2350)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rpi-sdk code was sent to exile here because it was modifying some of pico-sdk targets but I don't see anything similar here. All changes are to the CMRX's own os target.

Thus the code here seems to be pretty generic and not specific to RPI SDK. Of course, various SDKs can call these handlers various names which will cause us to fail to bind to the vectors, but this can be addressed later I guess.

Which is a good news because bunch of the code can be moved into arch/riscv rather than live here.

I hope that in long run, the we could get rid of the whole quirks subtree as portability will be recognized as an advantage in the world of embedded SW development.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I didn't make the changes geenric. Do you want me to leave it for this first PR or should I try to move the generic bits to the os subdirectory still in this PR?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets keep it as is for now, when some other RISC-V microcontroller is available, this can get revisited and we can find some way to make it generic.

@tobiaguiar08 tobiaguiar08 force-pushed the riscv_port_basic_thread branch 2 times, most recently from 09da34c to 6be04d0 Compare February 15, 2026 15:49
Comment thread cmake/arch/riscv/hal/CMRX.cmake Outdated
add_library(${NAME} STATIC EXCLUDE_FROM_ALL ${ARGN})
set_property(TARGET ${NAME} PROPERTY CMRX_IS_APPLICATION 1)
target_compile_definitions(${NAME} PRIVATE -D APPLICATION_NAME=${NAME})
target_include_directories(${NAME} PRIVATE
Copy link
Copy Markdown
Contributor Author

@tobiaguiar08 tobiaguiar08 Feb 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure this is actually needed.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${CMRX_ROOT_DIR}/include should be injected by stdlib, latter shouldn't be needed at all I guess.

Copy link
Copy Markdown
Owner

@ventZl ventZl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is functional state of this code?

I think that for initial stage of porting effort it is OK as long as it builds. Based on the code I see I'd expect it is capable of switching threads and maybe calling system calls?

If clang-tidy won't complain, it is probably good to go.

RISC-V specific unit tests opened question of organization of unit tests, maybe these need to be shuffled around. Testing is already messy anyway with unit-tests, testing platform, hil tests and clang-tidy builds all living in different parts of tree.

Comment thread src/lib/arch/riscv/mutex.c Outdated

int __futex_fast_lock(futex_t *futex, uint8_t thread_id, unsigned max_depth)
{
uint32_t saved = irq_save();
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Can you disable interrupts from userspace on RISC-V? I'd expect you can't. This code runs in userspace (note the src/lib path). That's why CMRX is using load/store conditional to implement them.

Disabling interrupts from userspace is a big fat no.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember why I let AI write this. So should I just remove this file ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REmoved interrupt handling, and implemented lock and unlock using riscv Atomic instructions (load reserved and store conditional)

Comment thread cmake/arch/riscv/hal/CMRX.cmake Outdated
add_library(${NAME} STATIC EXCLUDE_FROM_ALL ${ARGN})
set_property(TARGET ${NAME} PROPERTY CMRX_IS_APPLICATION 1)
target_compile_definitions(${NAME} PRIVATE -D APPLICATION_NAME=${NAME})
target_include_directories(${NAME} PRIVATE
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

${CMRX_ROOT_DIR}/include should be injected by stdlib, latter shouldn't be needed at all I guess.

@@ -0,0 +1,34 @@
# CMRX quirk for Pico SDK RISC-V (RP2350)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets keep it as is for now, when some other RISC-V microcontroller is available, this can get revisited and we can find some way to make it generic.

)
target_sources(os PRIVATE ${cmrx_testing_SRCS})
target_link_libraries(os PRIVATE ctest)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably included this directory conditionally from riscv/hal/CMakeLists.txt.

Neither is totally clean but adding add_subdirectory() to direct parent directory at least makes things a bit more obvious.

edit: OK, now I understand. If unit testing is activated, arch directory won't be included at all.

When ARM platform support was developed there was no idea on writing tests for it so this case wasn't considered. Lets keep these tests here for now and we'll see what to do with it later.

@tobiaguiar08
Copy link
Copy Markdown
Contributor Author

What is functional state of this code?

I think that for initial stage of porting effort it is OK as long as it builds. Based on the code I see I'd expect it is capable of switching threads and maybe calling system calls?

If clang-tidy won't complain, it is probably good to go.

RISC-V specific unit tests opened question of organization of unit tests, maybe these need to be shuffled around. Testing is already messy anyway with unit-tests, testing platform, hil tests and clang-tidy builds all living in different parts of tree.

Currently I have a small validation app that has 2 threads, one that turn off the led and another that turns on. Both threads print some variable count to uart.

It looks like this the main c file :
#include <cmrx/application.h>
#include <cmrx/ipc/timer.h>
#include <pico/stdlib.h>
#include <stdio.h>

#define LED_PIN 25

static volatile uint32_t thread_a_count = 0;
static volatile uint32_t thread_b_count = 0;

/*

  • Thread A: blinks LED on, increments counter, sleeps
    */
    static int thread_a_main(void *data)
    {
    (void)data;

    gpio_init(LED_PIN);
    gpio_set_dir(LED_PIN, GPIO_OUT);

    while (1) {
    thread_a_count++;
    gpio_put(LED_PIN, 1);
    printf("[A] count=%lu\n", (unsigned long)thread_a_count);
    usleep(500000); /* 500ms */
    }
    return 0;
    }

/*

  • Thread B: blinks LED off, increments counter, sleeps
    */
    static int thread_b_main(void *data)
    {
    (void)data;

    while (1) {
    thread_b_count++;
    gpio_put(LED_PIN, 0);
    printf("[B] count=%lu\n", (unsigned long)thread_b_count);
    usleep(500000); /* 500ms */
    }
    return 0;
    }

/*

  • Grant application access to GPIO and SIO peripherals.
  • RP2350 memory map:
    • 0x40000000-0x50000000: APB peripherals (includes GPIO)
    • 0xd0000000-0xe0000000: SIO (single-cycle IO)
      */
      OS_APPLICATION_MMIO_RANGES(validation_app,
      0x40000000, 0x50000000,
      0xd0000000, 0xe0000000);

/* Declare the application */
OS_APPLICATION(validation_app);

/* Auto-start two threads with priority 32 */
OS_THREAD_CREATE(validation_app, thread_a_main, NULL, 32);
OS_THREAD_CREATE(validation_app, thread_b_main, NULL, 32);

I also have a main source file that initializes the os like demonstrated in other cmrx examples :

#include <cmrx/cmrx.h>
#include "timing_provider.h"

#include "pico/stdlib.h"
#include "hardware/clocks.h"

long timing_get_current_cpu_freq(void)
{
return (long)clock_get_hz(clk_sys);
}

int main(void)
{
stdio_init_all();

timing_provider_setup(1);
os_start();

/* Should not reach here */
return 0;

}

With the latest code changes, it builds and when I try to debug the target I see that unfortunately it is not wiring my pico-sdk modifications in quirks/, but the pico-sdk default ones, so it is hanging :

Thread 1 "rp2350.rv0" hit Temporary breakpoint 1, main ()
at /path/to/cmrx-riscv-port-workspace/sandbox/pico2_riscv_cmrx/src/main.c:22
22 stdio_init_all();
(gdb) continue
Continuing.

Thread 1 "rp2350.rv0" received signal SIGTRAP, Trace/breakpoint trap.
0x20002c60 in isr_riscv_machine_timer ()
(gdb) bt
#0 0x20002c60 in isr_riscv_machine_timer ()
#1 0x10004944 in os_boot_thread (boot_thread=)
at /home/tobias-aguiar/Desktop/testing/cmrx/cmrx-riscv-port-workspace/cmrx/src/os/arch/riscv/sched.c:122
#2 0x1b0f0041 in ?? ()

do you think it is something wrong with my app or there might be something in cmrx code that I am missing?

Comment thread src/os/arch/riscv/hal/arch/runtime.h Outdated

/* No special handling yet; platform integration will define this as needed. */
#define os_thread_initialize_arch(x)
#define os_thread_initialize_arch(...)
Copy link
Copy Markdown
Contributor Author

@tobiaguiar08 tobiaguiar08 Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this function here is wrong... ? I should have put it in riscv subdirecty under the sched.c with the actual implementation that calls os_thread_populate_stack that configures the thread state to be executed after the os boots .... ?

Copy link
Copy Markdown
Owner

@ventZl ventZl Feb 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for late reply, I somehow missed e-mail notification about reply.

Yes, I see you did it already. And your assumption is right. The purpose of this function is to make stack suitable for "returning" from interrupt handler into thread context. That's how threads are both launched and resumed. Upon resume, stack is in such state automatically due to work done on its suspension. Before first launch, this state has to be forged.

@tobiaguiar08 tobiaguiar08 force-pushed the riscv_port_basic_thread branch from 6be04d0 to 3ce1f74 Compare February 25, 2026 17:17
@ventZl
Copy link
Copy Markdown
Owner

ventZl commented Feb 27, 2026

With the latest code changes, it builds and when I try to debug the target I see that unfortunately it is not wiring my pico-sdk modifications in quirks/, but the pico-sdk default ones, so it is hanging :

Thread 1 "rp2350.rv0" hit Temporary breakpoint 1, main () at /path/to/cmrx-riscv-port-workspace/sandbox/pico2_riscv_cmrx/src/main.c:22 22 stdio_init_all(); (gdb) continue Continuing.

Thread 1 "rp2350.rv0" received signal SIGTRAP, Trace/breakpoint trap. 0x20002c60 in isr_riscv_machine_timer () (gdb) bt #0 0x20002c60 in isr_riscv_machine_timer () #1 0x10004944 in os_boot_thread (boot_thread=) at /home/tobias-aguiar/Desktop/testing/cmrx/cmrx-riscv-port-workspace/cmrx/src/os/arch/riscv/sched.c:122 #2 0x1b0f0041 in ?? ()

do you think it is something wrong with my app or there might be something in cmrx code that I am missing?

Major problem CMRX has is that it is compiled as a static library. SDKs usually provide fallback weak no-op symbols for stuff like ISR handlers (even in case of ARM and other platforms) so that stuff will compile even if integrator doesn't provide / need their own version. This way you don't have to provide dozens of ISR handlers for peripherals you are not using.

These symbols are weak so if you provide strong version of the symbol in any other object file entering linking, it will override the weak symbol. If you don't provide strong symbol, weak symbol will be used as fallback.

The problem is that this changes in numerous ways, if your code isn't coming directly from object file, rather from static library. Static library is "just a bunch of object files" but linker treats it differently. Linker tries to use objects from libraries as least as possible.

One of linker behavior changes is that weak symbols in main object are not overridable by strong symbols from library object.

That's why pico-SDK quirk hacked pico-sdk to provide crt0.s which was missing few symbols which were then provided by quirk addition to OS library. This way this weak symbol in main object wasn't a problem anymore. PicoSDK "fixed" the issue by providing a small static library which hosts these files so this quirk is actually not needed anymore for RP2040 (and probably also RP2350 in ARM mode).

@tobiaguiar08 tobiaguiar08 marked this pull request as ready for review March 3, 2026 03:22
@tobiaguiar08 tobiaguiar08 force-pushed the riscv_port_basic_thread branch 2 times, most recently from 639adea to 1b758f5 Compare March 3, 2026 05:43
* The context switch safe-point is handled by the exception dispatcher,
* not here.
*
* SPDX-License-Identifier: BSD-3-Clause
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're using MIT here, at least for now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to MIT

Comment thread src/lib/arch/riscv/mutex.c
@tobiaguiar08 tobiaguiar08 force-pushed the riscv_port_basic_thread branch from 1b758f5 to 58d88d7 Compare March 7, 2026 10:27
@tobiaguiar08
Copy link
Copy Markdown
Contributor Author

With the latest code changes, it builds and when I try to debug the target I see that unfortunately it is not wiring my pico-sdk modifications in quirks/, but the pico-sdk default ones, so it is hanging :
Thread 1 "rp2350.rv0" hit Temporary breakpoint 1, main () at /path/to/cmrx-riscv-port-workspace/sandbox/pico2_riscv_cmrx/src/main.c:22 22 stdio_init_all(); (gdb) continue Continuing.
Thread 1 "rp2350.rv0" received signal SIGTRAP, Trace/breakpoint trap. 0x20002c60 in isr_riscv_machine_timer () (gdb) bt #0 0x20002c60 in isr_riscv_machine_timer () #1 0x10004944 in os_boot_thread (boot_thread=) at /home/tobias-aguiar/Desktop/testing/cmrx/cmrx-riscv-port-workspace/cmrx/src/os/arch/riscv/sched.c:122 #2 0x1b0f0041 in ?? ()
do you think it is something wrong with my app or there might be something in cmrx code that I am missing?

Major problem CMRX has is that it is compiled as a static library. SDKs usually provide fallback weak no-op symbols for stuff like ISR handlers (even in case of ARM and other platforms) so that stuff will compile even if integrator doesn't provide / need their own version. This way you don't have to provide dozens of ISR handlers for peripherals you are not using.

These symbols are weak so if you provide strong version of the symbol in any other object file entering linking, it will override the weak symbol. If you don't provide strong symbol, weak symbol will be used as fallback.

The problem is that this changes in numerous ways, if your code isn't coming directly from object file, rather from static library. Static library is "just a bunch of object files" but linker treats it differently. Linker tries to use objects from libraries as least as possible.

One of linker behavior changes is that weak symbols in main object are not overridable by strong symbols from library object.

That's why pico-SDK quirk hacked pico-sdk to provide crt0.s which was missing few symbols which were then provided by quirk addition to OS library. This way this weak symbol in main object wasn't a problem anymore. PicoSDK "fixed" the issue by providing a small static library which hosts these files so this quirk is actually not needed anymore for RP2040 (and probably also RP2350 in ARM mode).

@tobiaguiar08 tobiaguiar08 reopened this Mar 7, 2026
@tobiaguiar08
Copy link
Copy Markdown
Contributor Author

Close PR unintentionally, sorry about that

Copy link
Copy Markdown
Owner

@ventZl ventZl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. There are several things that may turn out to be loose ends but dealing with them is not critical for now.

*
* This file is derived from pico-sdk exception_table_riscv.S.
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
* SPDX-License-Identifier: BSD-3-Clause
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that in some files license identifier is already MIT, here BSD leaked

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to add bsd license there because it was a derived file from the pico-sdk repo. Not sure I could simply put MIT.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, it seems that there is no way around this. It would probably be best to make the whole pico-sdk-riscv component BSD-licensed, not just this file.

Then it should be compliant and clear as BSD-licensed component will link against MIT-licensed kernel with no dependencies from kernel back to the quirk component.

Sorry for inconvenience caused by shifting licenses back and forth. To comply with BSD license its copy probably has to be put into this folder into LICENSE.txt. This will also aid license inferring tools to figure out that the content of this directory is licensed under BSD while remainder is licensed as MIT.

For me, there will be a homework to add license identifiers into all files as this will become a multi-license project once this gets merged into master.


#else

#error "RISC-V A extension (atomics) is required for the LR.W/SC.W-based mutex. \
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good solution for now. In the future, maybe mutex implementation provided for Cortex-M0+ (which also doesn't support atomics) may be used, if sufficient large pool of MCUs with MPU but without atomics appears.

No action needed.

Comment thread src/lib/arch/riscv/mutex.c
Add RISC-V architecture support for running on RP2350 with Pico SDK:

- quirks/pico-sdk-riscv: Pico SDK RISC-V integration specifics
  - Custom CRT0 wrapper for IRQ exit context switch hook
  - ecall exception handler for CMRX syscalls
  - CMake integration for firmware builds

- src/os/arch/riscv: Architecture-specific kernel components
  - sched.c: Thread stack setup, boot, and sleep primitives
  - mpu.c: Memory protection stubs (PMP not implemented)
  - static.c: Static thread/application table accessors
  - corelocal.h: Single-core lock/unlock via IRQ disable
  - sysenter.h: ecall-based syscall implementation

- cmake/arch/riscv/hal: Build system support
  - CMRX.cmake: add_firmware() function
  - cmrx_sections.ld: Linker symbols for static init tables

- src/lib/arch/riscv: Library support
  - mutex.c: Mutex implementation using HAL primitives
Add arch-level ExceptionFrame (32 words / 128 bytes) covering all GP
registers, mepc, and mstatus.  Simplify context switch to a pure SP
swap; full register save/restore is now the trap handler's responsibility.

Update os_thread_populate_stack to build an ExceptionFrame, os_boot_thread
to use mret for consistent trap-return entry, and os_set_syscall_return_value
to write a0 via the unified frame.  Adjust unit tests accordingly.
Add SAVE_CONTEXT() and LOAD_CONTEXT() inline asm macros to
exception_frame.h, mirroring ARM's cortex.h pattern.  Rewrite the
Pico SDK quirk handlers to use these macros, reducing each to a thin
naked wrapper with only SDK-specific wiring (symbol override, mscratch
protocol, exception table dispatch).

Simplify the ecall handler to plain C receiving ExceptionFrame*.
Delete the RP2350-specific exception frame type, superseded by the
arch-level ExceptionFrame.
@tobiaguiar08 tobiaguiar08 force-pushed the riscv_port_basic_thread branch from 58d88d7 to 9e88ab2 Compare April 12, 2026 16:10
@ventZl ventZl merged commit 8c8046b into ventZl:master Apr 15, 2026
6 checks passed
@ventZl
Copy link
Copy Markdown
Owner

ventZl commented Apr 15, 2026

Thanks for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants