From 7ffc62ab8613b097485b2c664093e649907dc2a1 Mon Sep 17 00:00:00 2001 From: Ian McIntyre Date: Mon, 3 Aug 2026 07:46:10 -0400 Subject: First commit --- crates/cortex-m/Cargo.toml | 7 + crates/cortex-m/build.rs | 14 ++ crates/cortex-m/src/lib.rs | 1 + crates/imxrt1170evk/Cargo.toml | 17 +++ crates/imxrt1170evk/build.rs | 17 +++ crates/imxrt1170evk/src/lib.rs | 117 ++++++++++++++ crates/imxrt1170evk/src/main.rs | 10 ++ crates/qemu/Cargo.toml | 15 ++ crates/qemu/src/lib.rs | 37 +++++ crates/qemu/src/main.rs | 72 +++++++++ crates/stdio-imxrt-usbd/Cargo.toml | 13 ++ crates/stdio-imxrt-usbd/src/lib.rs | 297 ++++++++++++++++++++++++++++++++++++ crates/stdio-semihosting/.gitignore | 1 + crates/stdio-semihosting/Cargo.toml | 8 + crates/stdio-semihosting/src/lib.rs | 52 +++++++ crates/teensy4/Cargo.toml | 12 ++ crates/teensy4/src/lib.rs | 67 ++++++++ crates/teensy4/src/main.rs | 13 ++ 18 files changed, 770 insertions(+) create mode 100644 crates/cortex-m/Cargo.toml create mode 100644 crates/cortex-m/build.rs create mode 100644 crates/cortex-m/src/lib.rs create mode 100644 crates/imxrt1170evk/Cargo.toml create mode 100644 crates/imxrt1170evk/build.rs create mode 100644 crates/imxrt1170evk/src/lib.rs create mode 100644 crates/imxrt1170evk/src/main.rs create mode 100644 crates/qemu/Cargo.toml create mode 100644 crates/qemu/src/lib.rs create mode 100644 crates/qemu/src/main.rs create mode 100644 crates/stdio-imxrt-usbd/Cargo.toml create mode 100644 crates/stdio-imxrt-usbd/src/lib.rs create mode 100644 crates/stdio-semihosting/.gitignore create mode 100644 crates/stdio-semihosting/Cargo.toml create mode 100644 crates/stdio-semihosting/src/lib.rs create mode 100644 crates/teensy4/Cargo.toml create mode 100644 crates/teensy4/src/lib.rs create mode 100644 crates/teensy4/src/main.rs (limited to 'crates') diff --git a/crates/cortex-m/Cargo.toml b/crates/cortex-m/Cargo.toml new file mode 100644 index 0000000..9f18dd2 --- /dev/null +++ b/crates/cortex-m/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rust-threadx-cortex-m" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/crates/cortex-m/build.rs b/crates/cortex-m/build.rs new file mode 100644 index 0000000..286e738 --- /dev/null +++ b/crates/cortex-m/build.rs @@ -0,0 +1,14 @@ +use std::{env, path}; + +fn main() { + let out_dir = path::PathBuf::from(env::var("OUT_DIR").unwrap()); + std::fs::write( + out_dir.join("weak-symbols.x"), + r#" +PROVIDE(PendSV = PendSV_Handler); +PROVIDE(SysTick = _tx_timer_interrupt); +"#, + ) + .unwrap(); + println!("cargo::rustc-link-search={}", out_dir.display()); +} diff --git a/crates/cortex-m/src/lib.rs b/crates/cortex-m/src/lib.rs new file mode 100644 index 0000000..0c9ac1a --- /dev/null +++ b/crates/cortex-m/src/lib.rs @@ -0,0 +1 @@ +#![no_std] diff --git a/crates/imxrt1170evk/Cargo.toml b/crates/imxrt1170evk/Cargo.toml new file mode 100644 index 0000000..995eff0 --- /dev/null +++ b/crates/imxrt1170evk/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rust-threadx-imxrt1170evk" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +imxrt-ral = { workspace = true, features = ["imxrt1176_cm7"] } +imxrt-rt = { workspace = true } +imxrt1170evk-fcb = { version = "0.2" } + +cortex-m = { workspace = true } +rust-threadx-cortex-m = { workspace = true } +rust-threadx-stdio-semihosting = { workspace = true } + +[build-dependencies] +imxrt-rt = { workspace = true } diff --git a/crates/imxrt1170evk/build.rs b/crates/imxrt1170evk/build.rs new file mode 100644 index 0000000..6d4b58e --- /dev/null +++ b/crates/imxrt1170evk/build.rs @@ -0,0 +1,17 @@ +use imxrt_rt::{Family, Memory, RuntimeBuilder}; + +fn main() { + RuntimeBuilder::from_flexspi(Family::Imxrt1170, 16 * 1024 * 1024) + .text(Memory::Itcm) + .vectors(Memory::Itcm) + .data(Memory::Dtcm) + .bss(Memory::Dtcm) + .uninit(Memory::Ocram) + .rodata(Memory::Ocram) + .stack(Memory::Dtcm) + .heap(Memory::Ocram) + .heap_size(8 * 1024) + .stack_size(4 * 1024) + .build() + .unwrap(); +} diff --git a/crates/imxrt1170evk/src/lib.rs b/crates/imxrt1170evk/src/lib.rs new file mode 100644 index 0000000..b626843 --- /dev/null +++ b/crates/imxrt1170evk/src/lib.rs @@ -0,0 +1,117 @@ +#![no_std] +#![feature(rustc_private)] +extern crate fusible; + +use core::pin::Pin; + +use imxrt_ral as ral; +use imxrt_rt as _; +use imxrt1170evk_fcb as _; +use rust_threadx_cortex_m as _; +use rust_threadx_stdio_semihosting as _; + +#[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::External); + SYST.set_reload(100_000 / 100 - 1); + SYST.clear_current(); + SYST.enable_interrupt(); + SYST.enable_counter(); + } + + unsafe { + use ral::ccm; + + let ccm = ccm::CCM::instance(); + let clock_root = &ccm.CLOCK_ROOT[8]; + ral::modify_reg!(ccm::clockroot, clock_root, CLOCK_ROOT_CONTROL, MUX: 1, DIV: 240 - 1); + } + + unsafe { + use ral::iomuxc; + let iomuxc = iomuxc::IOMUXC::instance(); + + ral::write_reg!(iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_AD_04, MUX_MODE: 5, SION: 0); + + ral::write_reg!(iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_AD_04, PUE: 0); + } + + unsafe { + use ral::gpio; + let gpio3 = gpio::GPIO3::instance(); + + const GPIO3_OUTPUTS: u32 = led::MASK; + + ral::write_reg!(gpio, gpio3, GDIR, GPIO3_OUTPUTS); + } +} + +/// Controls the LED. +pub mod led { + use crate::ral::gpio; + + pub(crate) const MASK: u32 = 1 << 3; + + /// Turn on the LED. + #[inline] + pub fn set() { + unsafe { crate::ral::write_reg!(gpio, gpio::GPIO3, DR_SET, MASK) }; + } + + /// Turn off the LED. + #[inline] + pub fn clear() { + unsafe { crate::ral::write_reg!(gpio, gpio::GPIO3, DR_CLEAR, MASK) }; + } + + /// Toggle the LED. + /// + /// This is achieved in hardware, without reading the current state + /// of the LED. It's usually more efficient. + #[inline] + pub fn toggle() { + unsafe { crate::ral::write_reg!(gpio, gpio::GPIO3, DR_TOGGLE, MASK) }; + } + + /// Drive the LED on or off. + #[inline] + pub fn drive(value: bool) { + if value { set() } else { clear() } + } +} + +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() { + Thread::create( + Pin::static_ref(&THREAD), + STACK.take().unwrap(), + &Default::default(), + || loop { + fusible::thread::sleep(25); + led::toggle(); + }, + ) + .unwrap(); +} diff --git a/crates/imxrt1170evk/src/main.rs b/crates/imxrt1170evk/src/main.rs new file mode 100644 index 0000000..737a6cc --- /dev/null +++ b/crates/imxrt1170evk/src/main.rs @@ -0,0 +1,10 @@ +use rust_threadx_imxrt1170evk as _; + +fn main() { + let mut count = 0_usize; + loop { + println!("Hello world! The count is {count}"); + count = count.wrapping_add(1); + std::thread::sleep(std::time::Duration::from_millis(500)) + } +} diff --git a/crates/qemu/Cargo.toml b/crates/qemu/Cargo.toml new file mode 100644 index 0000000..904f696 --- /dev/null +++ b/crates/qemu/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rust-threadx-qemu" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +cortex-m = { workspace = true } +cortex-m-rt = { workspace = true } +cortex-m-semihosting = { workspace = true } +lm3s6965 = { workspace = true } +panic-semihosting = { version = "0.6", features = ["exit"] } + +rust-threadx-cortex-m = { workspace = true } +rust-threadx-stdio-semihosting = { workspace = true } diff --git a/crates/qemu/src/lib.rs b/crates/qemu/src/lib.rs new file mode 100644 index 0000000..dc91465 --- /dev/null +++ b/crates/qemu/src/lib.rs @@ -0,0 +1,37 @@ +#![no_std] + +use cortex_m_rt as _; +use lm3s6965 as _; + +use rust_threadx_cortex_m as _; +use rust_threadx_stdio_semihosting as _; + +pub fn exit_success() -> ! { + use cortex_m_semihosting::debug::*; + exit(EXIT_SUCCESS); + unreachable!() +} + +// Designed for ARMv6-M. Just enough to get the example +// running in QEMU with a meaningless system tick. +#[unsafe(no_mangle)] +pub extern "C" fn _tx_initialize_low_level() { + use cortex_m::{Peripherals, peripheral::scb::SystemHandler}; + + cortex_m::interrupt::disable(); + + let Peripherals { + mut SYST, mut SCB, .. + } = Peripherals::take().unwrap(); + + SYST.set_reload(80000 - 1); + SYST.clear_current(); + SYST.enable_interrupt(); + SYST.enable_counter(); + + unsafe { + SCB.set_priority(SystemHandler::SysTick, 0x40); + SCB.set_priority(SystemHandler::PendSV, 0xff); + SCB.set_priority(SystemHandler::SVCall, 0xff); + } +} diff --git a/crates/qemu/src/main.rs b/crates/qemu/src/main.rs new file mode 100644 index 0000000..3a3df38 --- /dev/null +++ b/crates/qemu/src/main.rs @@ -0,0 +1,72 @@ +use rust_threadx_qemu::exit_success; +use std::{ + sync::{Arc, Condvar, Mutex}, + time::Duration, +}; + +fn main() { + println!("Hello, world!"); + println!("Testing allocations in 0.25 seconds..."); + std::thread::sleep(Duration::from_millis(250)); + test_heap_allocation(); + + let mtx = Arc::new(Mutex::new(0_u32)); + let cv = Arc::new(Condvar::new()); + + std::thread::scope(|s| { + s.spawn(|| { + println!("Hello from one thread!"); + let mut guard = mtx.lock().unwrap(); + *guard += 1; + cv.notify_all(); + }); + }); + + let (m, c) = (mtx.clone(), cv.clone()); + std::thread::spawn(move || { + println!("Hello from another thread!"); + + let guard = m.lock().unwrap(); + let mut guard = c.wait_while(guard, |c| *c != 1).unwrap(); + *guard += 1; + c.notify_all(); + }) + .join() + .unwrap(); + + let guard = mtx.lock().unwrap(); + let guard = cv.wait_while(guard, |c| *c != 2).unwrap(); + assert_eq!(*guard, 2); + + echo_stdin(); + exit_success(); +} + +fn echo_stdin() { + use std::io::{self, Write}; + + print!("Enter a message: "); + // `print!` does not flush, so make sure the prompt reaches the host before + // we block waiting on input. + io::stdout().flush().ok(); + + let mut message = String::new(); + match io::stdin().read_line(&mut message) { + Ok(0) => println!("No message received (EOF)"), + Ok(_) => println!("You said: {}", message.trim_end()), + Err(e) => println!("Failed to read message: {e}"), + } +} + +fn test_heap_allocation() { + use core::hint::black_box; + + for len in [32usize, 64, 128] { + let mut v: Vec = Vec::with_capacity(len); + for i in 0..len { + v.push(black_box(i)); + } + let v = black_box(v); + println!("Allocated vector with {} elements", v.len()); + } +} diff --git a/crates/stdio-imxrt-usbd/Cargo.toml b/crates/stdio-imxrt-usbd/Cargo.toml new file mode 100644 index 0000000..ce3012e --- /dev/null +++ b/crates/stdio-imxrt-usbd/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rust-threadx-stdio-imxrt-usbd" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +imxrt-usbd = "0.4" +usb-device = "0.3" +usbd-serial = "0.2" + +cortex-m = { workspace = true } +imxrt-ral = { workspace = true } diff --git a/crates/stdio-imxrt-usbd/src/lib.rs b/crates/stdio-imxrt-usbd/src/lib.rs new file mode 100644 index 0000000..dbd4f66 --- /dev/null +++ b/crates/stdio-imxrt-usbd/src/lib.rs @@ -0,0 +1,297 @@ +//! Route stdout through a USB serial interface +//! +//! You'll notice this looks pretty similar to imxrt-log's usbd backend. +//! I replace the bbqueue with a fusible Queue and BlockPool, and I run +//! the "backend" on a fusible Thread. +//! +//! You're responsible for registering the USB interrupt and calling +//! `on_interrupt` in that interrupt. You'll also need to call `start` +//! to set up the driver. +//! +//! This only supports stdout; I haven't gotten stdin working yet. + +#![no_std] +#![feature(rustc_private)] +extern crate fusible; + +use usb_device::device::UsbDeviceState; + +const VID_PID: usb_device::device::UsbVidPid = usb_device::device::UsbVidPid(0x5824, 0x27dd); +const PRODUCT: &str = "rust-threadx-usbd"; + +/// Provide some extra overhead for the interrupt endpoint. +/// +/// If you start noticing panics, check to make sure that this buffer +/// is large enough for all the max packet sizes for all the endpoints. +const ENDPOINT_BYTES: usize = MAX_PACKET_SIZE * 2 + EP0_CONTROL_PACKET_SIZE * 2 + 128; +static ENDPOINT_MEMORY: imxrt_usbd::EndpointMemory = + imxrt_usbd::EndpointMemory::new(); +static ENDPOINT_STATE: imxrt_usbd::EndpointState<6> = imxrt_usbd::EndpointState::new(); + +type Bus = imxrt_usbd::BusAdapter; +type Class<'a> = usbd_serial::CdcAcmClass<'a, Bus>; +type Device<'a> = usb_device::device::UsbDevice<'a, Bus>; + +/// High-speed bulk endpoint limit. +const MAX_PACKET_SIZE: usize = 512; +/// Size for control transfers on endpoint 0. +const EP0_CONTROL_PACKET_SIZE: usize = 64; +/// The USB GPT timer we use to (infrequently) check for data. +const GPT_INSTANCE: imxrt_usbd::gpt::Instance = imxrt_usbd::gpt::Instance::Gpt0; + +struct Backend<'a> { + class: Class<'a>, + device: Device<'a>, + configured: bool, + stdout_queue: &'static BlockQueue, +} + +impl Backend<'_> { + fn poll(&mut self) { + // Is there a CDC class event, like a completed transfer? If so, check + // the consumer immediately, even if a timer hasn't expired. + // + // Checking the consumer on class traffic lets the driver burst out data. + // Suppose the user wants to use the USB GPT timer, and they configure a very + // long interval. That interval expires, and we see tons of data in the consumer. + // We should write that out as fast as possible, even if the timer hasn't elapsed. + // That's the behavior provided by the class_event flag. + let class_event = self.device.poll(&mut [&mut self.class]); + let timer_event = self.device.bus().gpt_mut(GPT_INSTANCE, |gpt| { + let mut elapsed = false; + while gpt.is_elapsed() { + gpt.clear_elapsed(); + elapsed = true; + } + // Simulate a timer event if the timer is not running. + // + // If the timer is not running, its because the user disabled interrupts, + // and they're using their own timer / polling loop. There might not always + // be a class traffic (transfer complete) event when the user polls, so + // signaling true allows the poll to check the consumer for new data and + // send it. + // + // If the timer is running, checking the consumer depends on the elapsed + // timer. + elapsed || !gpt.is_running() + }); + let check_consumer = class_event || timer_event; + + if self.device.state() != UsbDeviceState::Configured { + if self.configured { + // Turn off the timer, but only if we were previously configured. + self.device.bus().gpt_mut(GPT_INSTANCE, |gpt| gpt.stop()); + } + self.configured = false; + // We can't use the class if we're not configured, + // so bail out here. + return; + } + + // We're now configured. Are we newly configured? + if !self.configured { + // Must call this when we transition into configured. + self.device.bus().configure(); + self.device.bus().gpt_mut(GPT_INSTANCE, |gpt| { + // There's no need for a timer if interrupts are disabled. + // If the user disabled USB interrupts and decided to poll this + // from another timer, this USB timer could unnecessarily block + // that timer from checking the consumer queue. + if gpt.is_interrupt_enabled() { + gpt.run() + } + }); + self.configured = true; + } + + self.class.read_packet(&mut []).ok(); + + if check_consumer && let Some(block) = self.stdout_queue.try_receive() { + // Ignoring errors here... and we're not really utilizing the full + // width of the endpoint buffer. Let's see how long we can get + // away with this... + let _ = self.class.write_packet(&block.byte_buffer[..block.len]); + } + } +} + +fn run( + peripherals: imxrt_usbd::Instances, + interrupt: imxrt_ral::Interrupt, + stdout_queue: &'static BlockQueue, +) -> ! { + let events = Semaphore::create(Pin::static_ref(&EVENTS), &Default::default()).unwrap(); + + let bus = { + // Safety: we ensure that the bus, class, and all other related USB objects + // are accessed in poll(). poll() is not reentrant, so there's no racing + // occuring across executing contexts. + let bus = unsafe { + imxrt_usbd::BusAdapter::without_critical_sections( + peripherals, + &ENDPOINT_MEMORY, + &ENDPOINT_STATE, + imxrt_usbd::Speed::High, + ) + }; + bus.set_interrupts(true); + bus.gpt_mut(GPT_INSTANCE, |gpt| { + gpt.stop(); + gpt.clear_elapsed(); + gpt.set_interrupt_enabled(true); + gpt.set_mode(imxrt_usbd::gpt::Mode::Repeat); + gpt.set_load(4_000); + gpt.reset(); + }); + usb_device::bus::UsbBusAllocator::new(bus) + }; + let class = usbd_serial::CdcAcmClass::new(&bus, MAX_PACKET_SIZE as u16); + + let device = usb_device::device::UsbDeviceBuilder::new(&bus, VID_PID) + .strings(&[usb_device::device::StringDescriptors::default().product(PRODUCT)]) + .unwrap() + .device_class(usbd_serial::USB_CLASS_CDC) + .max_packet_size_0(EP0_CONTROL_PACKET_SIZE as u8) + .unwrap() + .build(); + + // Not sure which endpoints the CDC ACM class will pick, + // so enable the setting for all non-zero endpoints. + for idx in 1..8 { + for dir in &[usb_device::UsbDirection::In, usb_device::UsbDirection::Out] { + let ep_addr = usb_device::endpoint::EndpointAddress::from_parts(idx, *dir); + // CDC class requires that we send the ZLP. + // Let the hardware do that for us. + device.bus().enable_zlt(ep_addr); + } + } + + let mut backend = Backend { + class, + device, + configured: false, + stdout_queue, + }; + + loop { + backend.poll(); + unsafe { cortex_m::peripheral::NVIC::unmask(interrupt) }; + events.get().unwrap(); + } +} + +use core::pin::Pin; +use fusible::semaphore::{Semaphore, SemaphoreContext}; + +static EVENTS: SemaphoreContext = Semaphore::context(); + +#[inline] +pub fn on_interrupt(intr: imxrt_ral::Interrupt) { + cortex_m::peripheral::NVIC::mask(intr); + if let Some(events) = Pin::static_ref(&EVENTS).try_created() { + events.put(); + } +} + +use fusible::thread::{StaticStack, Thread, ThreadContext}; + +static STACK: StaticStack<2048> = StaticStack::new(); +static THREAD: ThreadContext = Thread::context(); + +pub fn start(peripherals: imxrt_usbd::Instances, interrupt: imxrt_ral::Interrupt) { + BlockPool::create( + Pin::static_ref(&BLOCK_POOL), + BLOCK_STORAGE.take().unwrap(), + &Default::default(), + ) + .unwrap(); + + let stdout_queue = Queue::create( + Pin::static_ref(&STDOUT_QUEUE), + STDOUT_STORAGE.take().unwrap(), + &Default::default(), + ) + .unwrap(); + + Thread::create( + Pin::static_ref(&THREAD), + STACK.take().unwrap(), + &Default::default(), + move || run(peripherals, interrupt, stdout_queue), + ) + .unwrap(); +} + +use fusible::block_pool::{Block, BlockPool, BlockPoolContext, StaticBlocks}; + +type ByteBuffer = [u8; MAX_PACKET_SIZE]; + +struct BlockWithLen { + len: usize, + byte_buffer: ByteBuffer, +} + +impl BlockWithLen { + fn copy_of(buf: &[u8]) -> Self { + let mut byte_buffer: ByteBuffer = [0_u8; _]; + let len = byte_buffer.len().min(buf.len()); + byte_buffer[..len].copy_from_slice(&buf[..len]); + Self { len, byte_buffer } + } +} + +const BLOCK_COUNT: usize = 4; +static BLOCK_STORAGE: StaticBlocks = StaticBlocks::new(); +static BLOCK_POOL: BlockPoolContext = BlockPool::context(); + +type ByteBufferBlock = Block<'static, BlockWithLen>; + +use fusible::queue::{Queue, QueueContext, StaticQueueSlots}; + +static STDOUT_STORAGE: StaticQueueSlots = StaticQueueSlots::new(); +static STDOUT_QUEUE: QueueContext = Queue::context(); + +type BlockQueue = Queue; + +fn stdout_write(buf: &[u8]) -> usize { + let mut written = 0; + + let Some(block_pool) = Pin::static_ref(&BLOCK_POOL).try_created() else { + return written; + }; + + let Some(stdout_queue) = Pin::static_ref(&STDOUT_QUEUE).try_created() else { + return written; + }; + + for chunk in buf.chunks(MAX_PACKET_SIZE) { + let Some(block) = block_pool.try_allocate(|| { + let block = BlockWithLen::copy_of(&chunk); + written += block.len; + block + }) else { + break; + }; + + if stdout_queue.try_send(block).is_some() { + break; + } + } + + written +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stdout_write(buf: &[u8]) -> usize { + stdout_write(buf) +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stderr_write(buf: &[u8]) -> usize { + stdout_write(buf) +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stdin_read(_: &mut [u8]) -> usize { + 0 // Haven't gotten this working yet. +} diff --git a/crates/stdio-semihosting/.gitignore b/crates/stdio-semihosting/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/crates/stdio-semihosting/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/stdio-semihosting/Cargo.toml b/crates/stdio-semihosting/Cargo.toml new file mode 100644 index 0000000..3178f61 --- /dev/null +++ b/crates/stdio-semihosting/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rust-threadx-stdio-semihosting" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +cortex-m-semihosting = { workspace = true } diff --git a/crates/stdio-semihosting/src/lib.rs b/crates/stdio-semihosting/src/lib.rs new file mode 100644 index 0000000..1a18b84 --- /dev/null +++ b/crates/stdio-semihosting/src/lib.rs @@ -0,0 +1,52 @@ +//! ThreadX stdio implemented over semihosting + +#![no_std] + +#[unsafe(no_mangle)] +fn __rust_threadx_stdout_write(buf: &[u8]) -> usize { + use cortex_m_semihosting::hio; + // `write_all` loops until the whole buffer is sent or the host reports an + // error, so a success means every byte was written. + let Ok(mut out) = hio::hstdout() else { + return 0; + }; + match out.write_all(buf) { + Ok(()) => buf.len(), + Err(()) => 0, + } +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stderr_write(buf: &[u8]) -> usize { + use cortex_m_semihosting::hio; + let Ok(mut out) = hio::hstderr() else { + return 0; + }; + match out.write_all(buf) { + Ok(()) => buf.len(), + Err(()) => 0, + } +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stdin_read(buf: &mut [u8]) -> usize { + use cortex_m_semihosting::{nr, syscall}; + + if buf.is_empty() { + return 0; + } + + // `hio` only opens the host console for writing, so open it for reading + // directly. `:tt` is the special semihosting path for the console. + let name = b":tt\0"; + let fd = match unsafe { syscall!(OPEN, name.as_ptr(), nr::open::R, name.len() - 1) } as isize { + -1 => return 0, + fd => fd as usize, + }; + + // SYS_READ returns the number of bytes that were *not* read; on error it + // returns a value larger than the request, which `saturating_sub` maps to 0. + let not_read = unsafe { syscall!(READ, fd, buf.as_mut_ptr(), buf.len()) }; + let _ = unsafe { syscall!(CLOSE, fd) }; + buf.len().saturating_sub(not_read) +} diff --git a/crates/teensy4/Cargo.toml b/crates/teensy4/Cargo.toml new file mode 100644 index 0000000..4b196d5 --- /dev/null +++ b/crates/teensy4/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust-threadx-teensy4" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +cortex-m = { workspace = true } +teensy4-bsp = { version = "0.6", features = ["rt"] } + +rust-threadx-cortex-m = { workspace = true } +rust-threadx-stdio-imxrt-usbd = { workspace = true } diff --git a/crates/teensy4/src/lib.rs b/crates/teensy4/src/lib.rs new file mode 100644 index 0000000..35afb31 --- /dev/null +++ b/crates/teensy4/src/lib.rs @@ -0,0 +1,67 @@ +#![no_std] +#![feature(rustc_private)] +extern crate fusible; + +use core::pin::Pin; + +use rust_threadx_cortex_m as _; +use rust_threadx_stdio_imxrt_usbd as stdio; + +#[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::External); + SYST.set_reload(100_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 instances = teensy4_bsp::board::instances(); + let mut resources = teensy4_bsp::board::t40(instances); + + let led = teensy4_bsp::board::led(&mut resources.gpio2, resources.pins.p13); + Thread::create( + Pin::static_ref(&THREAD), + STACK.take().unwrap(), + &Default::default(), + move || loop { + fusible::thread::sleep(50); + led.toggle(); + }, + ) + .unwrap(); + + stdio::start(resources.usb, interrupt::USB_OTG1); +} + +use teensy4_bsp::{ral::interrupt, rt::interrupt}; + +#[interrupt] +fn USB_OTG1() { + stdio::on_interrupt(interrupt::USB_OTG1); +} diff --git a/crates/teensy4/src/main.rs b/crates/teensy4/src/main.rs new file mode 100644 index 0000000..16a1a1b --- /dev/null +++ b/crates/teensy4/src/main.rs @@ -0,0 +1,13 @@ +use std::thread; +use std::time::Duration; + +use rust_threadx_teensy4 as _; + +fn main() { + let mut count = 0_usize; + loop { + thread::sleep(Duration::from_millis(500)); + println!("Hello world! The count is {count}\r"); + count = count.wrapping_add(1); + } +} -- cgit v1.2.3