Skip to main content

axruntime/
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 axlog;
38
39#[cfg(all(target_arch = "x86_64", feature = "driver-dyn"))]
40extern crate axplat_x86_qemu_q35;
41
42#[cfg(all(target_arch = "aarch64", feature = "driver-dyn"))]
43extern crate axplat_dyn;
44
45#[cfg(all(target_arch = "aarch64", feature = "driver-dyn"))]
46extern crate somehal;
47
48#[cfg(all(target_os = "none", not(test)))]
49mod lang_items;
50
51#[cfg(feature = "smp")]
52mod mp;
53
54#[cfg(feature = "smp")]
55pub use self::mp::rust_main_secondary;
56
57const LOGO: &str = r#"
58       d8888                            .d88888b.   .d8888b.
59      d88888                           d88P" "Y88b d88P  Y88b
60     d88P888                           888     888 Y88b.
61    d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
62   d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
63  d88P   888 888     888      88888888 888     888       "888
64 d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
65d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
66"#;
67
68unsafe extern "C" {
69    /// Application's entry point.
70    fn main();
71}
72
73struct LogIfImpl;
74
75#[crate_interface::impl_interface]
76impl axlog::LogIf for LogIfImpl {
77    fn console_write_str(s: &str) {
78        axhal::console::write_bytes(s.as_bytes());
79    }
80
81    fn current_time() -> core::time::Duration {
82        axhal::time::monotonic_time()
83    }
84
85    fn current_cpu_id() -> Option<usize> {
86        #[cfg(feature = "smp")]
87        if is_init_ok() {
88            Some(axhal::percpu::this_cpu_id())
89        } else {
90            None
91        }
92        #[cfg(not(feature = "smp"))]
93        Some(0)
94    }
95
96    fn current_task_id() -> Option<u64> {
97        if is_init_ok() {
98            #[cfg(feature = "multitask")]
99            {
100                axtask::current_may_uninit().map(|curr| curr.id().as_u64())
101            }
102            #[cfg(not(feature = "multitask"))]
103            None
104        } else {
105            None
106        }
107    }
108}
109
110use core::sync::atomic::{AtomicUsize, Ordering};
111
112static INITED_CPUS: AtomicUsize = AtomicUsize::new(0);
113
114#[cfg(feature = "driver-dyn")]
115fn is_init_ok() -> bool {
116    let cpu_num = cpu_count();
117    INITED_CPUS.load(Ordering::Acquire) == cpu_num
118}
119
120#[cfg(not(feature = "driver-dyn"))]
121fn is_init_ok() -> bool {
122    let cpu_num = axconfig::plat::CPU_NUM;
123    INITED_CPUS.load(Ordering::Acquire) == cpu_num
124}
125
126/// The main entry point of the ArceOS runtime.
127///
128/// It is called from the bootstrapping code in the specific platform crate (see
129/// [`axplat::main`]).
130///
131/// `cpu_id` is the logic ID of the current CPU, and `arg` is passed from the
132/// bootloader (typically the device tree blob address).
133///
134/// In multi-core environment, this function is called on the primary core, and
135/// secondary cores call [`rust_main_secondary`].
136#[cfg_attr(not(test), axplat::main)]
137pub fn rust_main(cpu_id: usize, arg: usize) -> ! {
138    unsafe { axhal::mem::clear_bss() };
139    axhal::percpu::init_primary(cpu_id);
140    axhal::init_early(cpu_id, arg);
141
142    ax_println!("{}", LOGO);
143    #[cfg(feature = "driver-dyn")]
144    ax_println!("smp = {}", cpu_count());
145
146    #[cfg(not(feature = "driver-dyn"))]
147    ax_println!(
148        indoc::indoc! {"
149            arch = {}
150            platform = {}
151            target = {}
152            build_mode = {}
153            log_level = {}
154            backtrace = {}
155            smp = {}
156        "},
157        axconfig::ARCH,
158        axconfig::PLATFORM,
159        option_env!("AX_TARGET").unwrap_or(""),
160        option_env!("AX_MODE").unwrap_or(""),
161        option_env!("AX_LOG").unwrap_or(""),
162        axbacktrace::is_enabled(),
163        axconfig::plat::CPU_NUM,
164    );
165
166    #[cfg(feature = "rtc")]
167    ax_println!(
168        "Boot at {}\n",
169        chrono::DateTime::from_timestamp_nanos(axhal::time::wall_time_nanos() as _),
170    );
171
172    axlog::init();
173    log::set_max_level(log::LevelFilter::Trace);
174    // axlog::set_max_level("info"); // no effect if set `log-level-*` features
175    info!("Logging is enabled.");
176    info!("Primary CPU {cpu_id} started, arg = {arg:#x}.");
177
178    axhal::mem::init();
179    info!("Found physcial memory regions:");
180    for r in axhal::mem::memory_regions() {
181        info!(
182            "  [{:x?}, {:x?}) {} ({:?})",
183            r.paddr,
184            r.paddr + r.size,
185            r.name,
186            r.flags
187        );
188    }
189
190    #[cfg(feature = "alloc")]
191    init_allocator();
192
193    let (kernel_space_start, kernel_space_size) = axhal::mem::kernel_aspace();
194
195    info!(
196        "kernel aspace: [{:#x?}, {:#x?})",
197        kernel_space_start,
198        kernel_space_start + kernel_space_size,
199    );
200
201    #[cfg(feature = "paging")]
202    axmm::init_memory_management();
203
204    #[cfg(feature = "driver-dyn")]
205    axdriver::setup(arg);
206
207    info!("Initialize platform devices...");
208    axhal::init_later(cpu_id, arg);
209
210    #[cfg(feature = "multitask")]
211    axtask::init_scheduler();
212
213    #[cfg(any(feature = "fs", feature = "net", feature = "display"))]
214    {
215        #[allow(unused_variables)]
216        let all_devices = axdriver::init_drivers();
217
218        #[cfg(feature = "fs")]
219        axfs::init_filesystems(all_devices.block, axhal::dtb::get_chosen_bootargs());
220
221        #[cfg(feature = "net")]
222        axnet::init_network(all_devices.net);
223
224        #[cfg(feature = "display")]
225        axdisplay::init_display(all_devices.display);
226    }
227
228    #[cfg(feature = "smp")]
229    self::mp::start_secondary_cpus(cpu_id);
230
231    #[cfg(feature = "irq")]
232    {
233        info!("Initialize interrupt handlers...");
234        init_interrupt();
235    }
236
237    #[cfg(all(feature = "tls", not(feature = "multitask")))]
238    {
239        info!("Initialize thread local storage...");
240        init_tls();
241    }
242
243    ctor_bare::call_ctors();
244
245    info!("Primary CPU {cpu_id} init OK.");
246    INITED_CPUS.fetch_add(1, Ordering::Release);
247
248    while !is_init_ok() {
249        core::hint::spin_loop();
250    }
251
252    unsafe { main() };
253
254    #[cfg(feature = "multitask")]
255    axtask::exit(0);
256    #[cfg(not(feature = "multitask"))]
257    {
258        debug!("main task exited: exit_code={}", 0);
259        axhal::power::system_off();
260    }
261}
262
263#[cfg(feature = "alloc")]
264fn init_allocator() {
265    use axhal::mem::{MemRegionFlags, memory_regions, phys_to_virt, virt_to_phys};
266
267    info!("Initialize global memory allocator...");
268    info!("  use {} allocator.", axalloc::global_allocator().name());
269
270    let mut max_region_size = 0;
271    let mut max_region_paddr = 0.into();
272    let mut use_next_free = false;
273
274    for r in memory_regions() {
275        if r.name == ".bss" {
276            use_next_free = true;
277        } else if r.flags.contains(MemRegionFlags::FREE) {
278            if use_next_free {
279                max_region_paddr = r.paddr;
280                break;
281            } else if r.size > max_region_size {
282                max_region_size = r.size;
283                max_region_paddr = r.paddr;
284            }
285        }
286    }
287
288    #[cfg(feature = "hv")]
289    {
290        struct AddrTranslatorImpl;
291        impl axalloc::AddrTranslator for AddrTranslatorImpl {
292            fn virt_to_phys(&self, va: usize) -> Option<usize> {
293                Some(virt_to_phys(va.into()).as_usize())
294            }
295        }
296
297        static TRANSLATOR: AddrTranslatorImpl = AddrTranslatorImpl;
298
299        for r in memory_regions() {
300            if r.flags.contains(MemRegionFlags::FREE) && r.paddr == max_region_paddr {
301                axalloc::global_init(phys_to_virt(r.paddr).as_usize(), r.size, &TRANSLATOR);
302                break;
303            }
304        }
305    }
306
307    #[cfg(not(feature = "hv"))]
308    {
309        for r in memory_regions() {
310            if r.flags.contains(MemRegionFlags::FREE) && r.paddr == max_region_paddr {
311                axalloc::global_init(phys_to_virt(r.paddr).as_usize(), r.size);
312                break;
313            }
314        }
315    }
316
317    for r in memory_regions() {
318        if r.flags.contains(MemRegionFlags::FREE) && r.paddr != max_region_paddr {
319            axalloc::global_add_memory(phys_to_virt(r.paddr).as_usize(), r.size)
320                .expect("add heap memory region failed");
321        }
322    }
323}
324
325#[cfg(feature = "irq")]
326fn init_interrupt() {
327    // Setup timer interrupt handler
328    const PERIODIC_INTERVAL_NANOS: u64 =
329        axhal::time::NANOS_PER_SEC / axconfig::TICKS_PER_SEC as u64;
330
331    #[percpu::def_percpu]
332    static NEXT_DEADLINE: u64 = 0;
333
334    fn update_timer() {
335        let now_ns = axhal::time::monotonic_time_nanos();
336        // Safety: we have disabled preemption in IRQ handler.
337        let mut deadline = unsafe { NEXT_DEADLINE.read_current_raw() };
338        if now_ns >= deadline {
339            deadline = now_ns + PERIODIC_INTERVAL_NANOS;
340        }
341        unsafe { NEXT_DEADLINE.write_current_raw(deadline + PERIODIC_INTERVAL_NANOS) };
342        axhal::time::set_oneshot_timer(deadline);
343    }
344
345    // axhal::irq::register(axconfig::devices::TIMER_IRQ, || {
346    axhal::irq::register(axhal::time::irq_num(), || {
347        update_timer();
348        #[cfg(feature = "multitask")]
349        axtask::on_timer_tick();
350    });
351
352    #[cfg(feature = "ipi")]
353    axhal::irq::register(axhal::irq::IPI_IRQ, || {
354        axipi::ipi_handler();
355    });
356
357    // Enable IRQs before starting app
358    axhal::asm::enable_irqs();
359}
360
361#[cfg(all(feature = "tls", not(feature = "multitask")))]
362fn init_tls() {
363    let main_tls = axhal::tls::TlsArea::alloc();
364    unsafe { axhal::asm::write_thread_pointer(main_tls.tls_ptr() as usize) };
365    core::mem::forget(main_tls);
366}
367
368#[cfg(feature = "driver-dyn")]
369fn smp() -> Option<usize> {
370    let mut smp = None;
371    let s = option_env!("AXVISOR_SMP");
372    if let Some(s) = s
373        && let Ok(n) = s.parse::<usize>()
374    {
375        smp = Some(n);
376    }
377    smp
378}
379
380/// Returns the number of CPUs available on the system
381#[cfg(feature = "driver-dyn")]
382pub fn cpu_count() -> usize {
383    let mut cpu_count;
384
385    cfg_if::cfg_if! {
386        if #[cfg(all(target_arch = "x86_64", target_os = "none"))] {
387            cpu_count = axplat_x86_qemu_q35::cpu_count()
388        } else if #[cfg(target_arch = "aarch64")] {
389            cpu_count = somehal::mem::cpu_id_list().count()
390        } else {
391            cpu_count = 1;
392        }
393    }
394
395    if let Some(smp) = smp() {
396        cpu_count = smp.min(cpu_count);
397    }
398
399    cpu_count
400}