Dániel Buga 321ae5ef1b
Rewrite Xtensa startup code in assembly (#4117)
* Initialize rtc RAM in xtensa-lx-rt

* Rewrite CPU reset handler in ASM
2025-09-17 07:57:23 +00:00

265 lines
7.5 KiB
Rust

//! Minimal startup/runtime for Xtensa LX CPUs.
//!
//! ## Feature Flags
#![doc = document_features::document_features!()]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
#![allow(asm_sub_register, named_asm_labels)]
#![feature(asm_experimental_arch)]
#![no_std]
use core::arch::global_asm;
pub use macros::{entry, exception, interrupt, pre_init};
pub use xtensa_lx;
pub mod exception;
pub mod interrupt;
#[doc(hidden)]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn no_init_hook() {}
unsafe extern "C" {
fn __pre_init();
fn __post_init();
fn __zero_bss() -> bool;
fn __init_data() -> bool;
fn main() -> !;
static _bss_start: u32;
static _bss_end: u32;
static _data_start: u32;
static _data_end: u32;
static _sidata: u32;
static _init_start: u32;
static _stack_start_cpu0: u32;
}
global_asm!(
"
.section .rwtext,\"ax\",@progbits
.literal sym__pre_init, {__pre_init}
.literal sym__post_init, {__post_init}
.literal sym__zero_bss, {__zero_bss}
.literal sym_main, {main}
.literal sym_stack_start_cpu0, {_stack_start_cpu0}
.literal sym_init_start, {_init_start}
.literal sym_bss_end, {_bss_end}
.literal sym_bss_start, {_bss_start}
.literal sym__init_data, {__init_data}
.literal sym_data_start, {_data_start}
.literal sym_data_end, {_data_end}
.literal sym_sidata, {_sidata}
",
__pre_init = sym __pre_init,
__post_init = sym __post_init,
__zero_bss = sym __zero_bss,
_stack_start_cpu0 = sym _stack_start_cpu0,
_bss_end = sym _bss_end,
_bss_start = sym _bss_start,
__init_data = sym __init_data,
_data_start = sym _data_start,
_data_end = sym _data_end,
_sidata = sym _sidata,
_init_start = sym _init_start,
main = sym main,
);
global_asm!(
"
// _xtensa_lx_rt_zero_fill
//
// Input arguments:
// a2: start address (used as a cursor)
// a3: end address
.section .rwtext,\"ax\",@progbits
.global _xtensa_lx_rt_zero_fill
.p2align 2
.type _xtensa_lx_rt_zero_fill,@function
_xtensa_lx_rt_zero_fill:
entry a1, 0
bgeu a2, a3, .Lfill_done // If start >= end, skip zeroing
movi.n a5, 0
.Lfill_loop:
s32i.n a5, a2, 0 // Store the zero at the current cursor
addi.n a2, a2, 4 // Increment the cursor by 4 bytes
bltu a2, a3, .Lfill_loop // If cursor < end, repeat
.Lfill_done:
retw.n
// _xtensa_lx_rt_copy
//
// Input arguments:
// a2: source address
// a3: destination start address (used as a cursor)
// a4: destination end address
.section .rwtext,\"ax\",@progbits
.global _xtensa_lx_rt_copy
.p2align 2
.type _xtensa_lx_rt_copy,@function
_xtensa_lx_rt_copy:
entry a1, 0
bgeu a3, a4, .Lcopy_done // If start >= end, skip copying
.Lcopy_loop:
l32i.n a5, a2, 0 // Load word from source pointer
s32i.n a5, a3, 0 // Store word at destination pointer
addi.n a3, a3, 4 // Increment destination pointer by 4 bytes
addi.n a2, a2, 4 // Increment source pointer by 4 bytes
bltu a3, a4, .Lcopy_loop // If cursor < end, repeat
.Lcopy_done:
retw.n
.section .rwtext,\"ax\",@progbits
.global Reset
.p2align 2
.type Reset,@function
Reset:
entry a1, 0
movi a0, 0 // Trash the return address. Debuggers may use this to stop unwinding.
l32r a5, sym_stack_start_cpu0 // a5 is our temporary value register
mov sp, a5 // Set the stack pointer.
l32r a5, sym__pre_init
callx8 a5 // Call the pre-initialization function.
.Linit_bss:
l32r a5, sym__zero_bss // Do we need to zero-initialize memory?
callx8 a5
beqz a10, .Linit_data // No -> skip to copying initialized data
l32r a10, sym_bss_start // Set input range to .bss
l32r a11, sym_bss_end //
call8 _xtensa_lx_rt_zero_fill // Zero-fill
.Linit_data:
l32r a5, sym__init_data // Do we need to initialize data sections?
callx8 a5
beqz a10, .Linit_data_done // If not, skip initialization
l32r a10, sym_sidata // Arguments - source data pointer
l32r a11, sym_data_start // - destination pointer
l32r a12, sym_data_end // - destination end pointer
call8 _xtensa_lx_rt_copy // Copy .data section
.Linit_data_done:
memw // Make sure all writes are completed before proceeding. At this point, all static variables have been initialized.
"
);
// According to 4.4.7.2 of the xtensa isa, ccount and compare are undefined on
// reset, set all values to zero to disable. ("timer interupts are cleared by writing CCOMPARE[i]")
#[cfg(any(
XCHAL_HAVE_TIMER0,
XCHAL_HAVE_TIMER1,
XCHAL_HAVE_TIMER2,
XCHAL_HAVE_TIMER3
))]
cfg_global_asm!(
#[cfg(XCHAL_HAVE_TIMER0)]
"wsr.ccompare0 a0",
#[cfg(XCHAL_HAVE_TIMER1)]
"wsr.ccompare1 a0",
#[cfg(XCHAL_HAVE_TIMER2)]
"wsr.ccompare2 a0",
#[cfg(XCHAL_HAVE_TIMER3)]
"wsr.ccompare3 a0",
"isync",
);
global_asm!(
"
l32r a5, sym_init_start // vector table address
wsr.vecbase a5
l32r a5, sym__post_init
callx8 a5
l32r a5, sym_main // program entry point
callx8 a5
",
);
// We redefine these functions to avoid pulling in `xtensa-lx` as a dependency:
// CPU Interrupts
unsafe extern "C" {
#[cfg(XCHAL_HAVE_TIMER0)]
pub fn Timer0(save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_TIMER1)]
pub fn Timer1(save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_TIMER2)]
pub fn Timer2(save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_TIMER3)]
pub fn Timer3(save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_PROFILING)]
pub fn Profiling(save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_SOFTWARE0)]
pub fn Software0(save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_SOFTWARE1)]
pub fn Software1(save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_NMI)]
pub fn NMI(save_frame: &mut crate::exception::Context);
}
#[doc(hidden)]
#[unsafe(no_mangle)]
pub extern "C" fn default_mem_hook() -> bool {
true // default to zeroing bss & initializing data
}
#[doc(hidden)]
#[macro_export]
macro_rules! cfg_asm {
(@inner, [$($x:tt)*], [$($opts:tt)*], ) => {
asm!($($x)* $($opts)*)
};
(@inner, [$($x:tt)*], [$($opts:tt)*], #[cfg($meta:meta)] $asm:literal, $($rest:tt)*) => {
#[cfg($meta)]
cfg_asm!(@inner, [$($x)* $asm,], [$($opts)*], $($rest)*);
#[cfg(not($meta))]
cfg_asm!(@inner, [$($x)*], [$($opts)*], $($rest)*)
};
(@inner, [$($x:tt)*], [$($opts:tt)*], $asm:literal, $($rest:tt)*) => {
cfg_asm!(@inner, [$($x)* $asm,], [$($opts)*], $($rest)*)
};
({$($asms:tt)*}, $($opts:tt)*) => {
cfg_asm!(@inner, [], [$($opts)*], $($asms)*)
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! cfg_global_asm {
{@inner, [$($x:tt)*], } => {
global_asm!{$($x)*}
};
(@inner, [$($x:tt)*], #[cfg($meta:meta)] $asm:literal, $($rest:tt)*) => {
#[cfg($meta)]
cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*}
#[cfg(not($meta))]
cfg_global_asm!{@inner, [$($x)*], $($rest)*}
};
{@inner, [$($x:tt)*], $asm:literal, $($rest:tt)*} => {
cfg_global_asm!{@inner, [$($x)* $asm,], $($rest)*}
};
{$($asms:tt)*} => {
cfg_global_asm!{@inner, [], $($asms)*}
};
}