Skip to content

Commit

Permalink
feat(threads): xtensa support
Browse files Browse the repository at this point in the history
  • Loading branch information
elenaf9 committed Sep 10, 2024
1 parent c575ab9 commit e46f357
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 1 deletion.
3 changes: 3 additions & 0 deletions src/riot-rs-threads/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ esp-hal = { workspace = true, features = ["esp32c3"] }
[target.'cfg(context = "esp32c6")'.dependencies]
esp-hal = { workspace = true, features = ["esp32c6"] }

[target.'cfg(context = "esp32s3")'.dependencies]
esp-hal = { workspace = true, features = ["esp32s3"] }

[target.'cfg(context = "cortex-m")'.dependencies]
# cortex-m specifics
cortex-m.workspace = true
Expand Down
4 changes: 4 additions & 0 deletions src/riot-rs-threads/src/arch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ cfg_if::cfg_if! {
mod riscv;
pub use riscv::Cpu;
}
else if #[cfg(context = "esp32s3")] {
mod xtensa;
pub use xtensa::Cpu;
}
else {
pub struct Cpu;
impl Arch for Cpu {
Expand Down
3 changes: 2 additions & 1 deletion src/riot-rs-threads/src/arch/riscv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ impl Arch for Cpu {
fn start_threading() {
interrupt::disable(EspHalCpu::ProCpu, Interrupt::FROM_CPU_INTR1);
Self::schedule();
// TODO: handle unwrap error?
// Panics if `FROM_CPU_INTR1` is among `esp_hal::interrupt::RESERVED_INTERRUPTS`,
// which isn't the case.
interrupt::enable(Interrupt::FROM_CPU_INTR1, interrupt::Priority::min()).unwrap();
}
}
Expand Down
141 changes: 141 additions & 0 deletions src/riot-rs-threads/src/arch/xtensa.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use esp_hal::{
interrupt,
peripherals::{Interrupt, SYSTEM},
trapframe::TrapFrame,
};

use crate::{cleanup, Arch, THREADS};

pub struct Cpu;

impl Arch for Cpu {
type ThreadData = TrapFrame;
const DEFAULT_THREAD_DATA: Self::ThreadData = default_trap_frame();

fn schedule() {
unsafe {
(&*SYSTEM::PTR)
.cpu_intr_from_cpu_1()
.modify(|_, w| w.cpu_intr_from_cpu_1().set_bit());
}
}

fn setup_stack(thread: &mut crate::thread::Thread, stack: &mut [u8], func: usize, arg: usize) {
let stack_start = stack.as_ptr() as usize;
let task_stack_ptr = stack_start + stack.len();
// 16 byte alignment.
let stack_pos = task_stack_ptr - (task_stack_ptr % 0x10);

thread.sp = stack_pos;
thread.data.A1 = stack_pos as u32;
thread.data.A6 = arg as u32;
// Usually A0 holds the return address.
// However, xtensa features so-called Windowed registers, which allow
// to shift the used registers when calling procedure.
// The xtensa-lx-rt does this when calling the exception handler using
// call4, which shifts the window by 4.
// See `xtensa_lx_rt::exception::asm::__default_naked_exception`.
// (At least that's what I assume is happening)
thread.data.A4 = cleanup as u32;
thread.data.PC = func as u32;

// Copied from esp-wifi::preempt::preempt_xtensa

// For windowed ABI set WOE and CALLINC (pretend task was 'call4'd).
thread.data.PS = 0x00040000 | (1 & 3) << 16;
}

fn start_threading() {
interrupt::disable(esp_hal::Cpu::ProCpu, Interrupt::FROM_CPU_INTR1);
Self::schedule();
// Panics if `FROM_CPU_INTR1` is among `esp_hal::interrupt::RESERVED_INTERRUPTS`,
// which isn't the case.
interrupt::enable(Interrupt::FROM_CPU_INTR1, interrupt::Priority::min()).unwrap();
}
}

const fn default_trap_frame() -> TrapFrame {
TrapFrame {
PC: 0,
PS: 0,
A0: 0,
A1: 0,
A2: 0,
A3: 0,
A4: 0,
A5: 0,
A6: 0,
A7: 0,
A8: 0,
A9: 0,
A10: 0,
A11: 0,
A12: 0,
A13: 0,
A14: 0,
A15: 0,
SAR: 0,
EXCCAUSE: 0,
EXCVADDR: 0,
LBEG: 0,
LEND: 0,
LCOUNT: 0,
THREADPTR: 0,
SCOMPARE1: 0,
BR: 0,
ACCLO: 0,
ACCHI: 0,
M0: 0,
M1: 0,
M2: 0,
M3: 0,
}
}

/// Handler for software interrupt 0, which we use for context switching.
#[allow(non_snake_case)]
#[no_mangle]
extern "C" fn FROM_CPU_INTR1(trap_frame: &mut TrapFrame) {
unsafe {
// clear FROM_CPU_INTR1
(&*SYSTEM::PTR)
.cpu_intr_from_cpu_1()
.modify(|_, w| w.cpu_intr_from_cpu_1().clear_bit());

sched(trap_frame)
}
}

/// Probes the runqueue for the next thread and switches context if needed.
///
/// # Safety
///
/// This method might switch the current register state that is saved in the
/// `trap_frame`.
/// It should only be called from inside the trap handler that is responsible for
/// context switching.
unsafe fn sched(trap_frame: &mut TrapFrame) {
loop {
if THREADS.with_mut(|mut threads| {
let next_pid = match threads.runqueue.get_next() {
Some(pid) => pid,
None => {
unsafe { core::arch::asm!("waiti 0") };
return false;
}
};

if let Some(current_pid) = threads.current_pid() {
if next_pid == current_pid {
return true;
}
threads.threads[usize::from(current_pid)].data = *trap_frame;
}
threads.current_thread = Some(next_pid);
*trap_frame = threads.threads[usize::from(next_pid)].data;
true
}) {
break;
}
}
}
1 change: 1 addition & 0 deletions src/riot-rs-threads/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![feature(naked_functions)]
#![feature(used_with_arg)]
#![feature(lint_reasons)]
#![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))]
// Disable indexing lints for now, possible panics are documented or rely on internally-enforced
// invariants
#![allow(clippy::indexing_slicing)]
Expand Down

0 comments on commit e46f357

Please sign in to comment.