vexide_startup/lib.rs
1//! Startup routine and behavior in `vexide`.
2//!
3//! - User code begins at an assembly routine called `_boot`, which sets up the stack
4//!   section before jumping to a user-provided `_start` symbol, which should be your
5//!   rust entrypoint.
6//!
7//! - From there, the Rust entrypoint may call the [`startup`] function to finish the
8//!   startup process by clearing the `.bss` section (intended for uninitialized data)
9//!   and initializing vexide's heap allocator.
10//!
11//! This crate is NOT a crt0 implementation. No global constructors are called.
12
13#![no_std]
14#![allow(clippy::needless_doctest_main)]
15
16use banner::themes::BannerTheme;
17use bitflags::bitflags;
18
19pub mod banner;
20mod patcher;
21
22/// Load address of user programs.
23const USER_MEMORY_START: u32 = 0x0380_0000;
24
25/// Identifies the type of binary to VEXos.
26#[repr(u32)]
27#[non_exhaustive]
28pub enum ProgramType {
29    /// User program binary.
30    User = 0,
31}
32
33/// The owner (originator) of the user program
34#[repr(u32)]
35pub enum ProgramOwner {
36    /// Program is a system binary.
37    System = 0,
38
39    /// Program originated from VEX.
40    Vex = 1,
41
42    /// Program originated from a partner developer.
43    Partner = 2,
44}
45
46bitflags! {
47    /// Program Flags
48    ///
49    /// These bitflags are part of the [`CodeSignature`] that determine some small
50    /// aspects of program behavior when running under VEXos. This struct contains
51    /// the flags with publicly documented behavior.
52    #[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
53    pub struct ProgramFlags: u32 {
54        /// Inverts the background color to pure white.
55        const INVERT_DEFAULT_GRAPHICS = 1 << 0;
56
57        /// VEXos scheduler simple tasks will be killed when the program requests exit.
58        const KILL_TASKS_ON_EXIT = 1 << 1;
59
60        /// If VEXos is using the Light theme, inverts the background color to pure white.
61        const THEMED_DEFAULT_GRAPHICS = 1 << 2;
62    }
63}
64
65/// Program Code Signature
66///
67/// The first 16 bytes of a VEX user code binary contain a user code signature,
68/// containing some basic metadata and startup flags about the program. This
69/// signature must be at the start of the binary for booting to occur.
70#[derive(Debug, Clone, Copy, Eq, PartialEq)]
71pub struct CodeSignature(vex_sdk::vcodesig, [u32; 4]);
72
73impl CodeSignature {
74    /// Creates a new signature given a program type, owner, and flags.
75    #[must_use]
76    pub const fn new(program_type: ProgramType, owner: ProgramOwner, flags: ProgramFlags) -> Self {
77        Self(
78            vex_sdk::vcodesig {
79                magic: vex_sdk::V5_SIG_MAGIC,
80                r#type: program_type as _,
81                owner: owner as _,
82                options: flags.bits(),
83            },
84            [0; 4],
85        )
86    }
87}
88
89unsafe extern "C" {
90    static mut __heap_start: u8;
91    static mut __heap_end: u8;
92
93    static mut __patcher_ram_start: u8;
94    static mut __patcher_ram_end: u8;
95
96    // These symbols don't have real types, so this is a little bit of a hack.
97    static mut __bss_start: u32;
98    static mut __bss_end: u32;
99}
100
101// This is the true entrypoint of vexide, containing the first two
102// instructions of user code executed before anything else.
103//
104// This function loads the stack pointer to the stack region specified
105// in our linkerscript, then immediately branches to the Rust entrypoint
106// created by our macro.
107core::arch::global_asm!(
108    r#"
109.section .boot, "ax"
110.global _boot
111
112_boot:
113    @ Load the user program stack.
114    @
115    @ This technically isn't required, as VEXos already sets up a stack for CPU1,
116    @ but that stack is relatively small and we have more than enough memory
117    @ available to us for this.
118    ldr sp, =__stack_top
119
120    @ Before any Rust code runs, we need to memcpy the currently running binary to
121    @ 0x07C00000 if a patch file is loaded into memory. See the documentation in
122    @ `patcher/mod.rs` for why we want to do this.
123
124    @ Check for patch magic at 0x07A00000.
125    mov r0, #0x07A00000
126    ldr r0, [r0]
127    ldr r1, =0xB1DF
128    cmp r0, r1
129
130    @ Prepare to memcpy binary to 0x07C00000
131    mov r0, #0x07C00000 @ memcpy dest -> r0
132    mov r1, #0x03800000 @ memcpy src -> r1
133    ldr r2, =0x07A0000C @ the length of the binary is stored at 0x07A0000C
134    ldr r2, [r2] @ memcpy size -> r2
135
136    @ Do the memcpy if patch magic is present (we checked this in our `cmp` instruction).
137    bleq __overwriter_aeabi_memcpy
138
139    @ Jump to the Rust entrypoint.
140    b _start
141"#
142);
143
144/// Zeroes the `.bss` section
145///
146/// # Arguments
147///
148/// - `sbss`. Pointer to the start of the `.bss` section.
149/// - `ebss`. Pointer to the open/non-inclusive end of the `.bss` section.
150///   (The value behind this pointer will not be modified)
151/// - Use `T` to indicate the alignment of the `.bss` section.
152///
153/// # Safety
154///
155/// - Must be called exactly once
156/// - `mem::size_of::<T>()` must be non-zero
157/// - `ebss >= sbss`
158/// - `sbss` and `ebss` must be `T` aligned.
159#[inline]
160#[allow(clippy::similar_names)]
161#[cfg(target_vendor = "vex")]
162unsafe fn zero_bss<T>(mut sbss: *mut T, ebss: *mut T)
163where
164    T: Copy,
165{
166    while sbss < ebss {
167        // NOTE: volatile to prevent this from being transformed into `memclr`
168        unsafe {
169            core::ptr::write_volatile(sbss, core::mem::zeroed());
170            sbss = sbss.offset(1);
171        }
172    }
173}
174
175/// Startup Routine
176///
177/// - Sets up the heap allocator if necessary.
178/// - Zeroes the `.bss` section if necessary.
179/// - Prints the startup banner with a specified theme, if enabled.
180///
181/// # Safety
182///
183/// Must be called once at the start of program execution after the stack has been setup.
184#[inline]
185#[allow(clippy::too_many_lines)]
186pub unsafe fn startup<const BANNER: bool>(theme: BannerTheme) {
187    #[cfg(target_vendor = "vex")]
188    unsafe {
189        // Fill the `.bss` section of our program's memory with zeroes to ensure that
190        // uninitialized data is allocated properly.
191        zero_bss(&raw mut __bss_start, &raw mut __bss_end);
192
193        // Initialize the heap allocator using normal bounds
194        #[cfg(feature = "allocator")]
195        vexide_core::allocator::claim(&raw mut __heap_start, &raw mut __heap_end);
196
197        // If this link address is 0x03800000, this implies we were uploaded using
198        // differential uploads by cargo-v5 and may have a patch to apply.
199        if vex_sdk::vexSystemLinkAddrGet() == USER_MEMORY_START {
200            patcher::patch();
201        }
202
203        // Reclaim 6mb memory region occupied by patches and program copies as heap space.
204        #[cfg(feature = "allocator")]
205        vexide_core::allocator::claim(&raw mut __patcher_ram_start, &raw mut __patcher_ram_end);
206    }
207
208    // Print the banner
209    if BANNER {
210        banner::print(theme);
211    }
212}