aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorIan McIntyre <me@mciantyre.dev>2026-08-30 17:35:22 -0400
committerIan McIntyre <me@mciantyre.dev>2026-09-05 13:37:16 -0400
commitbfa4501cd703149f4720a4ef6cdbe74ef6c3e361 (patch)
tree0bf7f586d2d363d7ae5a5599204378f8951a74e8 /crates
parentc4962c8b9207e9474659526e91a1cf387d2a7b8f (diff)
Add RP2040 support
Diffstat (limited to 'crates')
-rw-r--r--crates/rp-pico-2040/Cargo.toml13
-rw-r--r--crates/rp-pico-2040/build.rs7
-rw-r--r--crates/rp-pico-2040/memory.x15
-rw-r--r--crates/rp-pico-2040/src/lib.rs94
-rw-r--r--crates/rp-pico-2040/src/main.rs11
-rw-r--r--crates/sync-single-core/Cargo.toml6
-rw-r--r--crates/sync-single-core/src/lib.rs178
7 files changed, 324 insertions, 0 deletions
diff --git a/crates/rp-pico-2040/Cargo.toml b/crates/rp-pico-2040/Cargo.toml
new file mode 100644
index 0000000..6603bf4
--- /dev/null
+++ b/crates/rp-pico-2040/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "rust-threadx-rp-pico-2040"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+embedded-hal = "1.0"
+rp-pico = "0.9.0"
+
+rust-threadx-cortex-m.workspace = true
+rust-threadx-stdio-semihosting.workspace = true
+rust-threadx-sync-single-core.workspace = true
+cortex-m.workspace = true
diff --git a/crates/rp-pico-2040/build.rs b/crates/rp-pico-2040/build.rs
new file mode 100644
index 0000000..f8ac67a
--- /dev/null
+++ b/crates/rp-pico-2040/build.rs
@@ -0,0 +1,7 @@
+use std::path::PathBuf;
+
+fn main() {
+ let out_dir = std::env::var("OUT_DIR").map(PathBuf::from).unwrap();
+ std::fs::copy("memory.x", out_dir.join("memory.x")).unwrap();
+ println!("cargo::rustc-link-search={}", out_dir.display());
+}
diff --git a/crates/rp-pico-2040/memory.x b/crates/rp-pico-2040/memory.x
new file mode 100644
index 0000000..4077aab
--- /dev/null
+++ b/crates/rp-pico-2040/memory.x
@@ -0,0 +1,15 @@
+MEMORY {
+ BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
+ FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
+ RAM : ORIGIN = 0x20000000, LENGTH = 256K
+}
+
+EXTERN(BOOT2_FIRMWARE)
+
+SECTIONS {
+ /* ### Boot loader */
+ .boot2 ORIGIN(BOOT2) :
+ {
+ KEEP(*(.boot2));
+ } > BOOT2
+} INSERT BEFORE .text;
diff --git a/crates/rp-pico-2040/src/lib.rs b/crates/rp-pico-2040/src/lib.rs
new file mode 100644
index 0000000..8f877aa
--- /dev/null
+++ b/crates/rp-pico-2040/src/lib.rs
@@ -0,0 +1,94 @@
+//! Standard library support crate for an Raspberry Pi Pico with an RP2040.
+//!
+//! The blinky support example comes from the rp-hal-boards example, available
+//! <https://github.com/rp-rs/rp-hal-boards/blob/5135e3dafe3e69b112e6d2d72cfb1856a7679b82/boards/rp-pico/examples/pico_blinky.rs#L1>
+//! and licensed MIT or APACHE-2.0.
+
+#![no_std]
+#![feature(rustc_private)]
+extern crate fusible;
+
+use core::pin::Pin;
+
+use rust_threadx_cortex_m as _;
+use rust_threadx_stdio_semihosting as _;
+use rust_threadx_sync_single_core as _;
+
+use embedded_hal::digital::OutputPin;
+use rp_pico::hal;
+use rp_pico::hal::pac;
+
+#[doc(hidden)]
+#[unsafe(no_mangle)]
+pub unsafe extern "C" fn _tx_initialize_low_level() {
+ use cortex_m::{
+ Peripherals,
+ peripheral::{scb::SystemHandler, syst::SystClkSource},
+ };
+
+ cortex_m::interrupt::disable();
+
+ unsafe {
+ let Peripherals {
+ mut SYST, mut SCB, ..
+ } = Peripherals::steal();
+
+ SCB.set_priority(SystemHandler::SysTick, 0x40);
+ SCB.set_priority(SystemHandler::PendSV, 0xff);
+ SCB.set_priority(SystemHandler::SVCall, 0xff);
+
+ SYST.set_clock_source(SystClkSource::Core);
+ SYST.set_reload(125_000_000 / 100 - 1);
+ SYST.clear_current();
+ SYST.enable_interrupt();
+ SYST.enable_counter();
+ }
+}
+
+use fusible::thread::{StaticStack, Thread, ThreadContext};
+static STACK: StaticStack<512> = StaticStack::new();
+static THREAD: ThreadContext = Thread::context();
+
+#[unsafe(no_mangle)]
+#[doc(hidden)]
+pub extern "C" fn __rust_threadx_app_define() {
+ let mut pac = pac::Peripherals::take().unwrap();
+
+ let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
+
+ hal::clocks::init_clocks_and_plls(
+ rp_pico::XOSC_CRYSTAL_FREQ,
+ pac.XOSC,
+ pac.CLOCKS,
+ pac.PLL_SYS,
+ pac.PLL_USB,
+ &mut pac.RESETS,
+ &mut watchdog,
+ )
+ .ok()
+ .unwrap();
+
+ let sio = hal::Sio::new(pac.SIO);
+
+ let pins = rp_pico::Pins::new(
+ pac.IO_BANK0,
+ pac.PADS_BANK0,
+ sio.gpio_bank0,
+ &mut pac.RESETS,
+ );
+
+ let mut led_pin = pins.led.into_push_pull_output();
+
+ Thread::create(
+ Pin::static_ref(&THREAD),
+ STACK.take().unwrap(),
+ &Default::default(),
+ move || loop {
+ led_pin.set_high().unwrap();
+ fusible::thread::sleep(50);
+ led_pin.set_low().unwrap();
+ fusible::thread::sleep(50);
+ },
+ )
+ .unwrap();
+}
diff --git a/crates/rp-pico-2040/src/main.rs b/crates/rp-pico-2040/src/main.rs
new file mode 100644
index 0000000..b155870
--- /dev/null
+++ b/crates/rp-pico-2040/src/main.rs
@@ -0,0 +1,11 @@
+use rust_threadx_rp_pico_2040 as _;
+
+use std::{thread, time::Duration};
+
+fn main() {
+ // Nothing interesting yet. Nevertheless, the LED should
+ // be blinking on a background thread.
+ loop {
+ thread::sleep(Duration::from_millis(100));
+ }
+}
diff --git a/crates/sync-single-core/Cargo.toml b/crates/sync-single-core/Cargo.toml
new file mode 100644
index 0000000..6970bd3
--- /dev/null
+++ b/crates/sync-single-core/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "rust-threadx-sync-single-core"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/crates/sync-single-core/src/lib.rs b/crates/sync-single-core/src/lib.rs
new file mode 100644
index 0000000..08f7887
--- /dev/null
+++ b/crates/sync-single-core/src/lib.rs
@@ -0,0 +1,178 @@
+//! `__sync_*` atomic builtins for Armv6-M.
+//!
+//! Some Rust ThreadX targets, like `thumbv6m-threadx-eabi`, declares `atomic_cas: true`
+//! and emulate them with OS support. Right now, we handle this by disabling interrupts.
+//! This should be OK for single core support.
+//!
+//! <https://llvm.org/docs/Atomics.html>
+
+#![no_std]
+#![feature(rustc_private)]
+extern crate fusible;
+
+use fusible::interrupt_control::with_disabled;
+
+/// Atomically apply `op` to `*ptr`, returning the previous value.
+///
+/// # Safety
+///
+/// `ptr` must be valid for reads and writes and aligned to `size_of::<T>()`.
+unsafe fn rmw<T, O>(ptr: *mut T, op: O) -> T
+where
+ T: Copy,
+ O: FnOnce(T) -> T,
+{
+ // Safety: interrupt control sufficient for single-core access. Caller
+ // (LLVM) assumed to uphold safety contract.
+ with_disabled(|| unsafe {
+ let old = ptr.read();
+ ptr.write(op(old));
+ old
+ })
+}
+
+/// Atomically store `new` into `*ptr` if `*ptr` equals `old`, returning the
+/// previous value either way.
+///
+/// # Safety
+///
+/// `ptr` must be valid for reads and writes and aligned to `size_of::<T>()`.
+unsafe fn cas<T>(ptr: *mut T, old: T, new: T) -> T
+where
+ T: Copy + PartialEq,
+{
+ // Safety: Same as rmw.
+ with_disabled(|| unsafe {
+ let current = ptr.read();
+ if current == old {
+ ptr.write(new);
+ }
+ current
+ })
+}
+
+macro_rules! fetch_and_op {
+ ($name:ident, $ty:ty, $op:expr) => {
+ #[unsafe(no_mangle)]
+ pub unsafe extern "C" fn $name(ptr: *mut $ty, val: $ty) -> $ty {
+ unsafe { rmw(ptr, |cur| $op(cur, val)) }
+ }
+ };
+}
+
+macro_rules! val_compare_and_swap {
+ ($name:ident, $ty:ty) => {
+ #[unsafe(no_mangle)]
+ pub unsafe extern "C" fn $name(ptr: *mut $ty, oldval: $ty, newval: $ty) -> $ty {
+ unsafe { cas(ptr, oldval, newval) }
+ }
+ };
+}
+
+fetch_and_op!(__sync_fetch_and_add_1, u8, |a: u8, b: u8| a.wrapping_add(b));
+fetch_and_op!(__sync_fetch_and_add_2, u16, |a: u16, b: u16| a
+ .wrapping_add(b));
+fetch_and_op!(__sync_fetch_and_add_4, u32, |a: u32, b: u32| a
+ .wrapping_add(b));
+
+fetch_and_op!(__sync_fetch_and_sub_1, u8, |a: u8, b: u8| a.wrapping_sub(b));
+fetch_and_op!(__sync_fetch_and_sub_2, u16, |a: u16, b: u16| a
+ .wrapping_sub(b));
+fetch_and_op!(__sync_fetch_and_sub_4, u32, |a: u32, b: u32| a
+ .wrapping_sub(b));
+
+fetch_and_op!(__sync_fetch_and_and_1, u8, |a: u8, b: u8| a & b);
+fetch_and_op!(__sync_fetch_and_and_2, u16, |a: u16, b: u16| a & b);
+fetch_and_op!(__sync_fetch_and_and_4, u32, |a: u32, b: u32| a & b);
+
+fetch_and_op!(__sync_fetch_and_or_1, u8, |a: u8, b: u8| a | b);
+fetch_and_op!(__sync_fetch_and_or_2, u16, |a: u16, b: u16| a | b);
+fetch_and_op!(__sync_fetch_and_or_4, u32, |a: u32, b: u32| a | b);
+
+fetch_and_op!(__sync_fetch_and_xor_1, u8, |a: u8, b: u8| a ^ b);
+fetch_and_op!(__sync_fetch_and_xor_2, u16, |a: u16, b: u16| a ^ b);
+fetch_and_op!(__sync_fetch_and_xor_4, u32, |a: u32, b: u32| a ^ b);
+
+fetch_and_op!(__sync_fetch_and_nand_1, u8, |a: u8, b: u8| !(a & b));
+fetch_and_op!(__sync_fetch_and_nand_2, u16, |a: u16, b: u16| !(a & b));
+fetch_and_op!(__sync_fetch_and_nand_4, u32, |a: u32, b: u32| !(a & b));
+
+fetch_and_op!(__sync_fetch_and_max_1, i8, |a: i8, b: i8| if a > b {
+ a
+} else {
+ b
+});
+fetch_and_op!(__sync_fetch_and_max_2, i16, |a: i16, b: i16| if a > b {
+ a
+} else {
+ b
+});
+fetch_and_op!(__sync_fetch_and_max_4, i32, |a: i32, b: i32| if a > b {
+ a
+} else {
+ b
+});
+
+fetch_and_op!(__sync_fetch_and_umax_1, u8, |a: u8, b: u8| if a > b {
+ a
+} else {
+ b
+});
+fetch_and_op!(__sync_fetch_and_umax_2, u16, |a: u16, b: u16| if a > b {
+ a
+} else {
+ b
+});
+fetch_and_op!(__sync_fetch_and_umax_4, u32, |a: u32, b: u32| if a > b {
+ a
+} else {
+ b
+});
+
+fetch_and_op!(__sync_fetch_and_min_1, i8, |a: i8, b: i8| if a < b {
+ a
+} else {
+ b
+});
+fetch_and_op!(__sync_fetch_and_min_2, i16, |a: i16, b: i16| if a < b {
+ a
+} else {
+ b
+});
+fetch_and_op!(__sync_fetch_and_min_4, i32, |a: i32, b: i32| if a < b {
+ a
+} else {
+ b
+});
+
+fetch_and_op!(__sync_fetch_and_umin_1, u8, |a: u8, b: u8| if a < b {
+ a
+} else {
+ b
+});
+fetch_and_op!(__sync_fetch_and_umin_2, u16, |a: u16, b: u16| if a < b {
+ a
+} else {
+ b
+});
+fetch_and_op!(__sync_fetch_and_umin_4, u32, |a: u32, b: u32| if a < b {
+ a
+} else {
+ b
+});
+
+//
+// From the GCC docs, linked from the LLVM docs:
+//
+// > This built-in function, as described by Intel, is not a traditional
+// > test-and-set operation, but rather an atomic exchange operation. It
+// > writes value into *ptr, and returns the previous contents of *ptr.
+//
+
+fetch_and_op!(__sync_lock_test_and_set_1, u8, |_: u8, new: u8| new);
+fetch_and_op!(__sync_lock_test_and_set_2, u16, |_: u16, new: u16| new);
+fetch_and_op!(__sync_lock_test_and_set_4, u32, |_: u32, new: u32| new);
+
+val_compare_and_swap!(__sync_val_compare_and_swap_1, u8);
+val_compare_and_swap!(__sync_val_compare_and_swap_2, u16);
+val_compare_and_swap!(__sync_val_compare_and_swap_4, u32);