#![no_std]
#![feature(asm_experimental_arch)]
use core::cell::UnsafeCell;
use xtensa_lx_rt::exception::Context;
pub mod timer;
const MAX_PTASKS: usize = rivet::preempt::tcb::MAX_PTASKS;
const BOOTSTRAP_HEADROOM: usize = 4;
const BOOTSTRAP_CAPACITY: usize = MAX_PTASKS + BOOTSTRAP_HEADROOM;
struct ContextCell(UnsafeCell<Context>);
unsafe impl Sync for ContextCell {}
static CONTEXTS: [ContextCell; MAX_PTASKS] =
[const { ContextCell(UnsafeCell::new(Context::new())) }; MAX_PTASKS];
#[derive(Clone, Copy)]
struct Bootstrap {
stack_base: usize,
stack_len: usize,
entry_fn: usize,
arg: usize,
}
struct BootstrapCell(UnsafeCell<Bootstrap>);
unsafe impl Sync for BootstrapCell {}
static BOOTSTRAPS: [BootstrapCell; BOOTSTRAP_CAPACITY] = [const {
BootstrapCell(UnsafeCell::new(Bootstrap {
stack_base: 0,
stack_len: 0,
entry_fn: 0,
arg: 0,
}))
}; BOOTSTRAP_CAPACITY];
struct BootstrapAlloc {
next: usize,
free: [usize; BOOTSTRAP_CAPACITY],
free_len: usize,
}
struct BootstrapAllocCell(UnsafeCell<BootstrapAlloc>);
unsafe impl Sync for BootstrapAllocCell {}
static BOOTSTRAP_ALLOC: BootstrapAllocCell = BootstrapAllocCell(UnsafeCell::new(BootstrapAlloc {
next: 0,
free: [0; BOOTSTRAP_CAPACITY],
free_len: 0,
}));
static BOOTSTRAP_LOCK: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
#[inline(always)]
fn with_bootstrap_alloc<R>(f: impl FnOnce(&mut BootstrapAlloc) -> R) -> R {
while BOOTSTRAP_LOCK
.compare_exchange_weak(
false,
true,
core::sync::atomic::Ordering::Acquire,
core::sync::atomic::Ordering::Relaxed,
)
.is_err()
{
core::hint::spin_loop();
}
let r = f(unsafe { &mut *BOOTSTRAP_ALLOC.0.get() });
BOOTSTRAP_LOCK.store(false, core::sync::atomic::Ordering::Release);
r
}
fn alloc_bootstrap_index() -> usize {
with_bootstrap_alloc(|a| {
if a.free_len > 0 {
a.free_len -= 1;
a.free[a.free_len]
} else {
let i = a.next;
assert!(
i < BOOTSTRAP_CAPACITY,
"rivet-arch-xtensa: more tasks spawned ({}) than the bootstrap table's capacity \
({}, MAX_PTASKS {} + headroom {}) will ever fit — every task must be dispatched \
(interrupted) at least once to free its slot, or BOOTSTRAP_HEADROOM must be raised",
i + 1,
BOOTSTRAP_CAPACITY,
MAX_PTASKS,
BOOTSTRAP_HEADROOM
);
a.next = i + 1;
i
}
})
}
fn free_bootstrap_index(index: usize) {
with_bootstrap_alloc(|a| {
debug_assert!(a.free_len < BOOTSTRAP_CAPACITY, "double-free of a bootstrap index");
a.free[a.free_len] = index;
a.free_len += 1;
})
}
fn encode_bootstrap(index: usize) -> usize {
MAX_PTASKS + index
}
fn decode_bootstrap(sp: usize) -> Option<usize> {
sp.checked_sub(MAX_PTASKS)
}
fn fresh_task_context(b: &Bootstrap) -> Context {
let mut ctx = Context::new();
let top = (b.stack_base + b.stack_len) & !0xF;
ctx.PC = rivet_ptask_trampoline_impl as *const () as usize as u32;
ctx.PS = timer::PS_WOE;
ctx.A1 = top as u32;
ctx.A2 = b.arg as u32;
ctx.A3 = b.entry_fn as u32;
ctx
}
#[no_mangle]
extern "Rust" fn __rivet_arch_irq_save() -> usize {
xtensa_lx::interrupt::disable() as usize
}
#[no_mangle]
extern "Rust" fn __rivet_arch_irq_restore(token: usize) {
unsafe {
xtensa_lx::interrupt::set_mask(token as u32);
}
}
#[no_mangle]
extern "Rust" fn __rivet_arch_init() {
unsafe extern "C" {
static _init_start: u32;
}
unsafe {
xtensa_lx::set_vecbase(core::ptr::addr_of!(_init_start) as *const u32);
}
unsafe {
if __rivet_arch_hart_id() == 0 {
(*esp32s3::INTERRUPT_CORE0::ptr())
.core_0_intr_map(ipi::FROM_APP_CPU_SOURCE)
.write(|w| w.bits(ipi::IPI_CPU_LEVEL as u32));
} else {
(*esp32s3::INTERRUPT_CORE1::ptr())
.core_1_intr_map(ipi::FROM_PRO_CPU_SOURCE)
.write(|w| w.bits(ipi::IPI_CPU_LEVEL as u32));
}
}
unsafe {
xtensa_lx::interrupt::enable_mask(timer::SOFTWARE1_MASK | ipi::IPI_MASK);
}
}
#[no_mangle]
extern "Rust" fn __rivet_arch_idle() {
unsafe {
core::arch::asm!("waiti 0", options(nomem, nostack));
}
}
#[no_mangle]
extern "Rust" fn __rivet_arch_min_task_stack() -> usize {
256
}
#[no_mangle]
extern "Rust" fn __rivet_arch_min_guard_size() -> usize {
64
}
#[no_mangle]
extern "Rust" fn __rivet_arch_cycle_count() -> u64 {
xtensa_lx::timer::get_cycle_count() as u64
}
#[no_mangle]
extern "Rust" fn __rivet_arch_hart_id() -> usize {
if xtensa_lx::get_processor_id() & 0x2000 != 0 {
1
} else {
0
}
}
mod ipi {
pub const FROM_PRO_CPU_SOURCE: usize = 79;
pub const FROM_APP_CPU_SOURCE: usize = 80;
pub const IPI_CPU_LEVEL: u8 = 23;
pub const IPI_MASK: u32 = 1 << (IPI_CPU_LEVEL as u32);
}
mod periph_irq {
pub const LINE: u32 = 27;
pub const MASK: u32 = 1 << LINE;
pub const DISABLED_TARGET: u32 = 16;
pub static CURRENT_SOURCE: [core::sync::atomic::AtomicU32; rivet::config::MAX_HARTS] =
[const { core::sync::atomic::AtomicU32::new(u32::MAX) }; rivet::config::MAX_HARTS];
}
#[cfg(feature = "latency-histograms")]
static RESCHEDULE_REQUESTED_AT: [core::sync::atomic::AtomicU32; rivet::config::MAX_HARTS] =
[const { core::sync::atomic::AtomicU32::new(0) }; rivet::config::MAX_HARTS];
#[cfg(feature = "latency-histograms")]
fn stamp_reschedule_requested(hart: usize) {
RESCHEDULE_REQUESTED_AT[hart].store(
rivet::port::arch::cycle_count() as u32,
core::sync::atomic::Ordering::Relaxed,
);
}
#[no_mangle]
extern "Rust" fn __rivet_arch_request_reschedule_on(hart: usize) {
#[cfg(feature = "latency-histograms")]
stamp_reschedule_requested(hart);
if hart == __rivet_arch_hart_id() {
unsafe {
xtensa_lx::interrupt::set(timer::SOFTWARE1_MASK);
}
return;
}
unsafe {
let system = &*esp32s3::SYSTEM::ptr();
let source = if hart == 0 {
ipi::FROM_APP_CPU_SOURCE
} else {
ipi::FROM_PRO_CPU_SOURCE
};
system
.cpu_intr_from_cpu(source - ipi::FROM_PRO_CPU_SOURCE)
.write(|w| w.cpu_intr().set_bit());
}
}
#[no_mangle]
extern "Rust" fn __rivet_arch_irq_enable(irq_num: u32) {
let hart = __rivet_arch_hart_id();
periph_irq::CURRENT_SOURCE[hart].store(irq_num, core::sync::atomic::Ordering::Release);
unsafe {
if hart == 0 {
(*esp32s3::INTERRUPT_CORE0::ptr())
.core_0_intr_map(irq_num as usize)
.write(|w| w.bits(periph_irq::LINE));
} else {
(*esp32s3::INTERRUPT_CORE1::ptr())
.core_1_intr_map(irq_num as usize)
.write(|w| w.bits(periph_irq::LINE));
}
xtensa_lx::interrupt::enable_mask(periph_irq::MASK);
}
}
#[no_mangle]
extern "Rust" fn __rivet_arch_irq_disable(irq_num: u32) {
let hart = __rivet_arch_hart_id();
let current = periph_irq::CURRENT_SOURCE[hart].load(core::sync::atomic::Ordering::Acquire);
if current != irq_num {
return;
}
unsafe {
if hart == 0 {
(*esp32s3::INTERRUPT_CORE0::ptr())
.core_0_intr_map(irq_num as usize)
.write(|w| w.bits(periph_irq::DISABLED_TARGET));
} else {
(*esp32s3::INTERRUPT_CORE1::ptr())
.core_1_intr_map(irq_num as usize)
.write(|w| w.bits(periph_irq::DISABLED_TARGET));
}
xtensa_lx::interrupt::disable_mask(periph_irq::MASK);
}
}
#[no_mangle]
extern "Rust" fn __rivet_arch_irq_set_priority(_irq_num: u32, _priority: u8) {
}
#[no_mangle]
extern "Rust" fn __rivet_arch_request_reschedule() {
#[cfg(feature = "latency-histograms")]
stamp_reschedule_requested(__rivet_arch_hart_id());
unsafe {
xtensa_lx::interrupt::set(timer::SOFTWARE1_MASK);
}
}
core::arch::global_asm!(
".section .text",
".align 4",
".literal .Lappcpu_stack_lit, _appcpu_stack_top",
".global rivet_appcpu_entry",
"rivet_appcpu_entry:",
" l32r a1, .Lappcpu_stack_lit",
" call4 rivet_appcpu_rust_entry",
"1:",
" j 1b",
);
#[no_mangle]
extern "C" fn rivet_appcpu_rust_entry() -> ! {
unsafe {
xtensa_lx::interrupt::set_mask(0);
xtensa_lx::timer::set_ccompare0(0);
xtensa_lx::timer::set_ccompare1(0);
xtensa_lx::timer::set_ccompare2(0);
}
while !rivet::kernel_ready() {
core::hint::spin_loop();
}
rivet::run_secondary_hart();
}
#[no_mangle]
extern "Rust" fn __rivet_arch_guard_register(_guard_base: usize, _slot: usize) {}
#[no_mangle]
extern "Rust" fn __rivet_arch_scratch_open(_base: usize, _size: usize) {}
#[no_mangle]
extern "Rust" fn __rivet_arch_scratch_close() {}
#[no_mangle]
extern "Rust" fn __rivet_arch_on_switch_to(_stack_base: usize, _stack_size: usize) {
}
core::arch::global_asm!(
".section .text",
".align 4",
".literal .Lexit_core_lit, rivet_task_exit_core",
".global rivet_ptask_trampoline_impl",
"rivet_ptask_trampoline_impl:",
" entry a1, 32",
" mov a6, a2",
" callx4 a3",
" l32r a4, .Lexit_core_lit",
" callx4 a4",
"1:",
" j 1b",
);
extern "C" {
#[allow(dead_code)]
fn rivet_task_exit_core(lo: usize, hi: usize) -> !;
fn rivet_ptask_trampoline_impl();
}
#[no_mangle]
unsafe extern "Rust" fn __rivet_arch_init_task_stack(
stack_ptr: *mut u8,
stack_len: usize,
entry_fn: usize,
arg: usize,
) -> usize {
let index = alloc_bootstrap_index();
unsafe {
*BOOTSTRAPS[index].0.get() = Bootstrap {
stack_base: stack_ptr as usize,
stack_len,
entry_fn,
arg,
};
}
encode_bootstrap(index)
}
#[no_mangle]
unsafe extern "Rust" fn __rivet_arch_start_first_task(sp: usize) -> ! {
xtensa_lx::interrupt::disable();
let index = decode_bootstrap(sp)
.expect("rivet-arch-xtensa: start_first_task given a non-bootstrap sp");
let b = unsafe { *BOOTSTRAPS[index].0.get() };
let top = ((b.stack_base + b.stack_len) & !0xF) as u32;
unsafe {
core::arch::asm!(
"mov a1, {top}",
"wsr.intenable {mask}",
"rsync",
"call4 {trampoline}",
top = in(reg) top,
mask = in(reg) (timer::TIMER1_MASK | timer::SOFTWARE1_MASK | ipi::IPI_MASK),
in("a6") b.arg as u32,
in("a7") b.entry_fn as u32,
trampoline = sym rivet_ptask_trampoline_impl,
options(noreturn),
);
}
}
#[no_mangle]
extern "Rust" fn __level_3_interrupt(save_frame: &mut Context) {
#[cfg(feature = "latency-histograms")]
let __entry_now = rivet::port::arch::cycle_count() as u32;
let pending = xtensa_lx::interrupt::get();
if pending & timer::TIMER1_MASK != 0 {
unsafe { timer::on_timer_irq() };
rivet::timer::poll_timers(rivet::port::board::now_us());
}
if pending & timer::SOFTWARE1_MASK != 0 {
unsafe { xtensa_lx::interrupt::clear(timer::SOFTWARE1_MASK) };
#[cfg(feature = "latency-histograms")]
{
let requested_at = RESCHEDULE_REQUESTED_AT[__rivet_arch_hart_id()]
.load(core::sync::atomic::Ordering::Relaxed);
rivet::latency::record(
rivet::latency::Kind::IrqEntry,
__entry_now.wrapping_sub(requested_at) as u64,
);
}
}
if pending & ipi::IPI_MASK != 0 {
unsafe {
let system = &*esp32s3::SYSTEM::ptr();
let reg = if __rivet_arch_hart_id() == 0 {
ipi::FROM_APP_CPU_SOURCE
} else {
ipi::FROM_PRO_CPU_SOURCE
} - ipi::FROM_PRO_CPU_SOURCE;
system.cpu_intr_from_cpu(reg).write(|w| w.cpu_intr().clear_bit());
}
}
if pending & periph_irq::MASK != 0 {
let hart = __rivet_arch_hart_id();
let source = periph_irq::CURRENT_SOURCE[hart].load(core::sync::atomic::Ordering::Acquire);
if source != u32::MAX {
rivet::irq::dispatch(source);
}
return;
}
if pending & (timer::TIMER1_MASK | timer::SOFTWARE1_MASK | ipi::IPI_MASK) == 0 {
return;
}
let Some(tid) = rivet::preempt::sched::current() else {
return;
};
rivet::critical::enter(|| unsafe {
*CONTEXTS[tid].0.get() = *save_frame;
});
let resume = rivet::preempt::on_tick(tid);
if resume == tid {
return; }
if resume < MAX_PTASKS {
rivet::critical::enter(|| unsafe {
*save_frame = *CONTEXTS[resume].0.get();
});
} else {
let index = decode_bootstrap(resume)
.expect("rivet-arch-xtensa: on_tick returned an sp that is neither a task id nor a bootstrap marker");
let b = unsafe { *BOOTSTRAPS[index].0.get() };
free_bootstrap_index(index);
*save_frame = fresh_task_context(&b);
}
}