Skip to main content

ax_runtime/
lib.rs

1// Copyright 2025 The Axvisor Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Runtime library of [ArceOS](https://github.com/arceos-org/arceos).
16//!
17//! Any application uses ArceOS should link this library. It does some
18//! initialization work before entering the application's `main` function.
19//!
20//! # Cargo Features
21//!
22//! - `alloc`: Enable global memory allocator.
23//! - `paging`: Enable page table manipulation support.
24//! - `irq`: Enable interrupt handling support.
25//! - `multitask`: Enable multi-threading support.
26//! - `smp`: Enable SMP (symmetric multiprocessing) support.
27//! - `fs`: Enable filesystem support.
28//! - `net`: Enable networking support.
29//! - `display`: Enable graphics support.
30//!
31//! All the features are optional and disabled by default.
32
33#![cfg_attr(not(test), no_std)]
34#![allow(missing_abi)]
35
36#[macro_use]
37extern crate ax_log;
38
39#[cfg(all(target_os = "none", not(test)))]
40mod lang_items;
41
42#[cfg(feature = "smp")]
43mod mp;
44
45#[cfg(feature = "paging")]
46mod klib;
47
48#[cfg(feature = "buddy-slab")]
49use ax_alloc::eii::ax_alloc_virt_to_phys_impl;
50
51#[cfg(feature = "smp")]
52pub use self::mp::rust_main_secondary;
53
54const LOGO: &str = r#"
55       d8888                            .d88888b.   .d8888b.
56      d88888                           d88P" "Y88b d88P  Y88b
57     d88P888                           888     888 Y88b.
58    d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
59   d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
60  d88P   888 888     888      88888888 888     888       "888
61 d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
62d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
63"#;
64
65unsafe extern "C" {
66    /// Application's entry point.
67    fn main();
68}
69
70#[cfg(feature = "buddy-slab")]
71#[ax_alloc_virt_to_phys_impl]
72fn ax_alloc_virt_to_phys(vaddr: usize) -> usize {
73    ax_hal::mem::virt_to_phys(vaddr.into()).as_usize()
74}
75
76struct LogIfImpl;
77
78#[ax_crate_interface::impl_interface]
79impl ax_log::LogIf for LogIfImpl {
80    fn console_write_str(s: &str) {
81        ax_hal::console::write_bytes(s.as_bytes());
82    }
83
84    fn current_time() -> core::time::Duration {
85        ax_hal::time::monotonic_time()
86    }
87
88    fn current_cpu_id() -> Option<usize> {
89        #[cfg(feature = "smp")]
90        if is_init_ok() {
91            Some(ax_hal::percpu::this_cpu_id())
92        } else {
93            None
94        }
95        #[cfg(not(feature = "smp"))]
96        Some(0)
97    }
98
99    fn current_task_id() -> Option<u64> {
100        if is_init_ok() {
101            #[cfg(feature = "multitask")]
102            {
103                ax_task::current_may_uninit().map(|curr| curr.id().as_u64())
104            }
105            #[cfg(not(feature = "multitask"))]
106            None
107        } else {
108            None
109        }
110    }
111}
112
113use core::sync::atomic::{AtomicUsize, Ordering};
114
115/// Number of CPUs that have completed initialization.
116static INITED_CPUS: AtomicUsize = AtomicUsize::new(0);
117
118fn is_init_ok() -> bool {
119    INITED_CPUS.load(Ordering::Acquire) == ax_hal::cpu_num()
120}
121
122/// The main entry point of the ArceOS runtime.
123///
124/// It is called from the bootstrapping code in the specific platform crate (see
125/// [`ax_plat::main`]).
126///
127/// `cpu_id` is the logic ID of the current CPU, and `arg` is passed from the
128/// bootloader (typically the device tree blob address).
129///
130/// In multi-core environment, this function is called on the primary core, and
131/// secondary cores call [`rust_main_secondary`].
132#[cfg_attr(not(test), ax_plat::main)]
133pub fn rust_main(cpu_id: usize, arg: usize) -> ! {
134    #[cfg(not(feature = "plat-dyn"))]
135    unsafe {
136        ax_hal::mem::clear_bss()
137    };
138    ax_hal::percpu::init_primary(cpu_id);
139    #[cfg(all(feature = "alloc", feature = "buddy-slab"))]
140    ax_alloc::init_precpu_slab(cpu_id);
141    ax_hal::init_early(cpu_id, arg);
142    let log_level = option_env!("AX_LOG").unwrap_or("info");
143
144    ax_println!("{}", LOGO);
145    ax_println!(
146        indoc::indoc! {"
147            arch = {}
148            platform = {}
149            target = {}
150            build_mode = {}
151            log_level = {}
152            backtrace = {}
153            smp = {}
154        "},
155        ax_config::ARCH,
156        ax_config::PLATFORM,
157        option_env!("AX_TARGET").unwrap_or(""),
158        option_env!("AX_MODE").unwrap_or(""),
159        log_level,
160        axbacktrace::is_enabled(),
161        ax_hal::cpu_num()
162    );
163
164    #[cfg(feature = "rtc")]
165    ax_println!(
166        "Boot at {}\n",
167        chrono::DateTime::from_timestamp_nanos(ax_hal::time::wall_time_nanos() as _),
168    );
169
170    ax_log::init();
171    ax_log::set_max_level(log_level); // no effect if set `log-level-*` features
172    info!("Logging is enabled.");
173    info!("Primary CPU {cpu_id} started, arg = {arg:#x}.");
174
175    info!("Found physcial memory regions:");
176    for r in ax_hal::mem::memory_regions() {
177        info!(
178            "  [{:x?}, {:x?}) {} ({:?})",
179            r.paddr,
180            r.paddr + r.size,
181            r.name,
182            r.flags
183        );
184    }
185
186    #[cfg(feature = "alloc")]
187    init_allocator();
188
189    {
190        use core::ops::Range;
191
192        unsafe extern "C" {
193            safe static _stext: [u8; 0];
194            safe static _etext: [u8; 0];
195            safe static _edata: [u8; 0];
196        }
197
198        axbacktrace::init(
199            Range {
200                start: _stext.as_ptr() as usize,
201                end: _etext.as_ptr() as usize,
202            },
203            Range {
204                start: _edata.as_ptr() as usize,
205                end: usize::MAX,
206            },
207        );
208    }
209
210    let (kernel_space_start, kernel_space_size) = ax_hal::mem::kernel_aspace();
211
212    info!(
213        "kernel aspace: [{:#x?}, {:#x?})",
214        kernel_space_start,
215        kernel_space_start + kernel_space_size,
216    );
217
218    #[cfg(feature = "paging")]
219    ax_mm::init_memory_management();
220
221    // #[cfg(feature = "plat-dyn")]
222    // ax_driver::setup(arg);
223
224    info!("Initialize platform devices...");
225    ax_hal::init_later(cpu_id, arg);
226
227    #[cfg(feature = "multitask")]
228    ax_task::init_scheduler();
229
230    #[cfg(feature = "ax-driver")]
231    {
232        #[allow(unused_variables)]
233        let all_devices = ax_driver::init_drivers();
234
235        cfg_if::cfg_if! {
236            if #[cfg(feature = "fs-ng")] {
237                ax_fs_ng::init_filesystems(all_devices.block);
238            } else
239            if #[cfg(feature = "fs")] {
240                ax_fs::init_filesystems(all_devices.block, ax_hal::dtb::get_chosen_bootargs());
241            }
242        }
243
244        cfg_if::cfg_if! {
245            if #[cfg(feature = "net-ng")] {
246                ax_net_ng::init_network(all_devices.net);
247
248                #[cfg(feature = "vsock")]
249                ax_net_ng::init_vsock(all_devices.vsock);
250            } else if #[cfg(feature = "net")] {
251                ax_net::init_network(all_devices.net);
252            }
253        }
254
255        #[cfg(feature = "display")]
256        ax_display::init_display(all_devices.display);
257
258        #[cfg(feature = "input")]
259        ax_input::init_input(all_devices.input);
260    }
261
262    #[cfg(feature = "smp")]
263    self::mp::start_secondary_cpus(cpu_id);
264
265    #[cfg(feature = "irq")]
266    {
267        info!("Initialize interrupt handlers...");
268        init_interrupt();
269    }
270
271    #[cfg(all(feature = "tls", not(feature = "multitask")))]
272    {
273        info!("Initialize thread local storage...");
274        init_tls();
275    }
276
277    ax_ctor_bare::call_ctors();
278
279    info!("Primary CPU {cpu_id} init OK.");
280    INITED_CPUS.fetch_add(1, Ordering::Release);
281
282    while !is_init_ok() {
283        core::hint::spin_loop();
284    }
285
286    unsafe { main() };
287
288    #[cfg(feature = "multitask")]
289    ax_task::exit(0);
290    #[cfg(not(feature = "multitask"))]
291    {
292        debug!("main task exited: exit_code={}", 0);
293        ax_hal::power::system_off();
294    }
295}
296
297#[cfg(feature = "alloc")]
298fn init_allocator() {
299    use ax_hal::mem::{MemRegionFlags, memory_regions, phys_to_virt};
300
301    info!("Initialize global memory allocator...");
302    info!("  use {} allocator.", ax_alloc::global_allocator().name());
303
304    let mut max_region_size = 0;
305    let mut max_region_paddr = 0.into();
306    let mut use_next_free = false;
307
308    for r in memory_regions() {
309        if r.name == ".bss" {
310            use_next_free = true;
311        } else if r.flags.contains(MemRegionFlags::FREE) {
312            if use_next_free {
313                max_region_paddr = r.paddr;
314                break;
315            } else if r.size > max_region_size {
316                max_region_size = r.size;
317                max_region_paddr = r.paddr;
318            }
319        }
320    }
321
322    for r in memory_regions() {
323        if r.flags.contains(MemRegionFlags::FREE) && r.paddr == max_region_paddr {
324            ax_alloc::global_init(phys_to_virt(r.paddr).as_usize(), r.size)
325                .expect("initialize global allocator failed");
326            break;
327        }
328    }
329
330    for r in memory_regions() {
331        if r.flags.contains(MemRegionFlags::FREE) && r.paddr != max_region_paddr {
332            ax_alloc::global_add_memory(phys_to_virt(r.paddr).as_usize(), r.size)
333                .expect("add heap memory region failed");
334        }
335    }
336}
337
338#[cfg(feature = "irq")]
339fn init_interrupt() {
340    // Setup timer interrupt handler
341    const PERIODIC_INTERVAL_NANOS: u64 =
342        ax_hal::time::NANOS_PER_SEC / ax_config::TICKS_PER_SEC as u64;
343
344    #[ax_percpu::def_percpu]
345    static NEXT_DEADLINE: u64 = 0;
346
347    fn update_timer() {
348        let now_ns = ax_hal::time::monotonic_time_nanos();
349        // Safety: we have disabled preemption in IRQ handler.
350        let mut deadline = unsafe { NEXT_DEADLINE.read_current_raw() };
351        if now_ns >= deadline {
352            deadline = now_ns + PERIODIC_INTERVAL_NANOS;
353        }
354        unsafe { NEXT_DEADLINE.write_current_raw(deadline + PERIODIC_INTERVAL_NANOS) };
355        ax_hal::time::set_oneshot_timer(deadline);
356    }
357
358    ax_hal::irq::register(ax_hal::time::irq_num(), || {
359        update_timer();
360        #[cfg(feature = "multitask")]
361        ax_task::on_timer_tick();
362    });
363
364    #[cfg(feature = "ipi")]
365    ax_hal::irq::register(ax_hal::irq::IPI_IRQ, || {
366        ax_ipi::ipi_handler();
367    });
368
369    // Arm the first one-shot timer on the primary CPU. Otherwise the timer
370    // handler may never get the first chance to re-program subsequent ticks.
371    update_timer();
372
373    // Enable IRQs before starting app
374    ax_hal::asm::enable_irqs();
375}
376
377#[cfg(all(feature = "tls", not(feature = "multitask")))]
378fn init_tls() {
379    let main_tls = ax_hal::tls::TlsArea::alloc();
380    unsafe { ax_hal::asm::write_thread_pointer(main_tls.tls_ptr() as usize) };
381    core::mem::forget(main_tls);
382}