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//! - `paging`: Enable page table manipulation support.
23//! - `irq`: Enable interrupt handling support.
24//! - `multitask`: Enable multi-threading support.
25//! - `smp`: Enable SMP (symmetric multiprocessing) support.
26//! - `fs`: Enable filesystem support.
27//! - `net`: Enable networking support.
28//! - `display`: Enable graphics support.
29//!
30//! All the features are optional and disabled by default.
31
32#![feature(extern_item_impls)]
33#![cfg_attr(not(test), no_std)]
34#![allow(missing_abi)]
35
36#[macro_use]
37extern crate ax_log;
38
39extern crate ax_driver as _;
40
41#[cfg(all(target_os = "none", not(feature = "std-compat"), not(test)))]
42mod lang_items;
43#[cfg(all(
44    feature = "stack-protector",
45    any(target_os = "none", target_env = "musl"),
46    not(test)
47))]
48mod stack_protector;
49
50#[cfg(feature = "smp")]
51mod mp;
52
53mod klib;
54
55mod devices;
56mod fs;
57#[cfg(feature = "irq")]
58pub mod irq;
59mod registers;
60#[cfg(feature = "serial")]
61pub mod serial;
62
63#[cfg(all(feature = "net", feature = "fs"))]
64mod unix_ns;
65
66#[cfg(feature = "aic8800-wifi")]
67mod wifi_glue;
68
69pub use ax_hal as hal;
70
71pub(crate) mod build_info {
72    include!(concat!(env!("OUT_DIR"), "/build_info.rs"));
73}
74
75#[cfg(feature = "smp")]
76pub use self::mp::rust_main_secondary;
77
78extern crate alloc;
79
80#[cfg(feature = "fs")]
81pub(crate) fn runtime_default_task_stack_size() -> usize {
82    build_info::TASK_STACK_SIZE
83}
84
85#[cfg(feature = "irq")]
86fn ticks_per_sec() -> u64 {
87    build_info::TICKS_PER_SEC as u64
88}
89
90const LOGO: &str = r#"
91       d8888                            .d88888b.   .d8888b.
92      d88888                           d88P" "Y88b d88P  Y88b
93     d88P888                           888     888 Y88b.
94    d88P 888 888d888  .d8888b  .d88b.  888     888  "Y888b.
95   d88P  888 888P"   d88P"    d8P  Y8b 888     888     "Y88b.
96  d88P   888 888     888      88888888 888     888       "888
97 d8888888888 888     Y88b.    Y8b.     Y88b. .d88P Y88b  d88P
98d88P     888 888      "Y8888P  "Y8888   "Y88888P"   "Y8888P"
99"#;
100
101#[eii]
102fn ax_app_entry() {
103    #[cfg(not(test))]
104    unsafe extern "C" {
105        /// Legacy application's entry point.
106        safe fn main();
107    }
108    // Default implementation
109    #[cfg(not(test))]
110    main();
111}
112
113struct LogIfImpl;
114
115#[cfg(feature = "paging")]
116fn runtime_page_fault_handler(
117    addr: ax_memory_addr::VirtAddr,
118    flags: ax_hal::trap::PageFaultFlags,
119) -> bool {
120    #[cfg(feature = "stack-guard-page")]
121    if ax_task::diagnose_current_stack_guard_page_fault(addr) {
122        return false;
123    }
124
125    ax_mm::kernel_aspace().lock().handle_page_fault(addr, flags)
126}
127
128#[ax_crate_interface::impl_interface]
129impl ax_log::LogIf for LogIfImpl {
130    fn console_write_str(s: &str) {
131        #[cfg(feature = "serial")]
132        if serial::route_console_bytes(s.as_bytes()).is_some() {
133            return;
134        }
135        ax_hal::console::write_text_bytes(s.as_bytes());
136    }
137
138    fn current_time() -> core::time::Duration {
139        ax_hal::time::monotonic_time()
140    }
141
142    fn current_cpu_id() -> Option<usize> {
143        #[cfg(feature = "smp")]
144        if is_init_ok() {
145            Some(ax_hal::percpu::this_cpu_id())
146        } else {
147            None
148        }
149        #[cfg(not(feature = "smp"))]
150        Some(0)
151    }
152
153    fn current_task_id() -> Option<u64> {
154        if is_init_ok() {
155            #[cfg(feature = "multitask")]
156            {
157                ax_task::current_may_uninit().map(|curr| curr.id().as_u64())
158            }
159            #[cfg(not(feature = "multitask"))]
160            None
161        } else {
162            None
163        }
164    }
165}
166
167use core::sync::atomic::{AtomicUsize, Ordering};
168
169/// Number of CPUs that have completed initialization.
170static INITED_CPUS: AtomicUsize = AtomicUsize::new(0);
171
172fn is_init_ok() -> bool {
173    INITED_CPUS.load(Ordering::Acquire) == ax_hal::cpu_num()
174}
175
176/// The main entry point of the ArceOS runtime.
177///
178/// It is called from the bootstrapping code in the specific platform crate (see
179/// [`ax_plat::main`]).
180///
181/// `cpu_id` is the logic ID of the current CPU, and `arg` is passed from the
182/// bootloader (typically the device tree blob address).
183///
184/// In multi-core environment, this function is called on the primary core, and
185/// secondary cores call [`rust_main_secondary`].
186#[cfg_attr(not(test), ax_plat::main)]
187pub fn rust_main(cpu_id: usize, arg: usize) -> ! {
188    ax_hal::percpu::init_primary(cpu_id);
189    // After per-CPU init, before scheduler/IPI/IRQ paths can allocate.
190    // This is a no-op for allocator backends that do not need per-CPU state.
191    ax_alloc::init_percpu_slab(cpu_id);
192    ax_hal::init_early(cpu_id, arg);
193    let log_level = option_env!("AX_LOG").unwrap_or("info");
194
195    ax_println!("{}", LOGO);
196    ax_println!(
197        indoc::indoc! {"
198            arch = {}
199            platform = {}
200            target = {}
201            build_mode = {}
202            log_level = {}
203            backtrace = {}
204            smp = {}
205        "},
206        build_info::ARCH,
207        hal::platform_name(),
208        build_info::TARGET,
209        build_info::MODE,
210        log_level,
211        axbacktrace::is_enabled(),
212        ax_hal::cpu_num()
213    );
214
215    ax_log::init();
216    ax_log::set_max_level(log_level); // no effect if set `log-level-*` features
217    info!("Logging is enabled.");
218    info!("Primary CPU {cpu_id} started, arg = {arg:#x}.");
219
220    info!("Found physcial memory regions:");
221    for r in ax_hal::mem::memory_regions() {
222        info!(
223            "  [{:x?}, {:x?}) {} ({:?})",
224            r.paddr,
225            r.paddr + r.size,
226            r.name,
227            r.flags
228        );
229    }
230
231    init_allocator();
232
233    let (kernel_space_start, kernel_space_size) = ax_hal::mem::kernel_aspace();
234
235    {
236        use core::ops::Range;
237
238        unsafe extern "C" {
239            safe static _stext: [u8; 0];
240            safe static _etext: [u8; 0];
241        }
242
243        let fp_range_start = kernel_space_start.as_usize();
244        let fp_range_end = fp_range_start.saturating_add(kernel_space_size);
245        axbacktrace::init(
246            Range {
247                start: _stext.as_ptr() as usize,
248                end: _etext.as_ptr() as usize,
249            },
250            Range {
251                start: fp_range_start,
252                end: fp_range_end,
253            },
254        );
255    }
256
257    info!(
258        "kernel aspace: [{:#x?}, {:#x?})",
259        kernel_space_start,
260        kernel_space_start + kernel_space_size,
261    );
262
263    #[cfg(feature = "paging")]
264    {
265        ax_mm::init_memory_management();
266        ax_hal::trap::set_page_fault_handler(runtime_page_fault_handler);
267    }
268
269    info!("Initialize platform devices...");
270    ax_hal::init_later(cpu_id, arg);
271    if rdrive::is_initialized() {
272        registers::append_linker_registers();
273        #[cfg(feature = "irq")]
274        ax_hal::irq::init_boot_irqs(cpu_id)
275            .unwrap_or_else(|err| panic!("failed to initialize boot IRQs: {err:?}"));
276        #[cfg(not(feature = "irq"))]
277        rdrive::probe_pre_kernel()
278            .unwrap_or_else(|err| panic!("failed to run pre-kernel driver probes: {err:?}"));
279    } else {
280        warn!("rdrive is not initialized; skip pre-kernel driver probe");
281    }
282
283    #[cfg(feature = "multitask")]
284    ax_task::init_scheduler();
285
286    #[cfg(feature = "ipi")]
287    {
288        ax_ipi::init();
289        #[cfg(feature = "irq")]
290        ax_hal::irq::set_run_on_cpu_sync(ax_ipi_run_on_cpu_sync);
291    }
292
293    #[cfg(feature = "irq")]
294    {
295        info!("Initialize interrupt handlers...");
296        init_interrupt();
297    }
298
299    // Install the ArceOS runtime glue into the OS-independent Wi-Fi driver
300    // cores (aic8800 / sdhci-cv1800) *before* probing, since the FDT probe
301    // brings the chip up and that needs timing/task capabilities. The cores
302    // declare no ArceOS dependency themselves; this is the adapter layer (see
303    // `wifi_glue`).
304    #[cfg(feature = "aic8800-wifi")]
305    wifi_glue::install_runtime();
306
307    devices::probe_all_devices();
308
309    #[cfg(feature = "serial")]
310    serial::init(cpu_id);
311
312    #[cfg(feature = "rtc")]
313    ax_println!(
314        "Boot at {}\n",
315        chrono::DateTime::from_timestamp_nanos(ax_hal::time::wall_time_nanos() as _),
316    );
317
318    fs::init(ax_hal::boot::bootargs());
319
320    #[cfg(feature = "display")]
321    devices::init_display();
322
323    #[cfg(feature = "input")]
324    devices::init_input();
325
326    #[cfg(feature = "net")]
327    devices::init_net();
328
329    #[cfg(feature = "vsock")]
330    devices::init_vsock();
331
332    #[cfg(feature = "smp")]
333    self::mp::start_secondary_cpus(cpu_id);
334
335    #[cfg(all(feature = "tls", not(feature = "multitask")))]
336    {
337        info!("Initialize thread local storage...");
338        init_tls();
339    }
340
341    ax_ctor_bare::call_ctors();
342
343    info!("Primary CPU {cpu_id} init OK.");
344    INITED_CPUS.fetch_add(1, Ordering::Release);
345
346    while !is_init_ok() {
347        core::hint::spin_loop();
348    }
349
350    #[cfg(all(feature = "irq", feature = "ipi"))]
351    ax_ipi::wait_for_all_cpus_ready();
352
353    ax_app_entry();
354
355    #[cfg(feature = "multitask")]
356    ax_task::exit(0);
357    #[cfg(not(feature = "multitask"))]
358    {
359        debug!("main task exited: exit_code={}", 0);
360        ax_hal::power::system_off();
361    }
362}
363
364fn init_allocator() {
365    use ax_hal::mem::{MemRegionFlags, memory_regions, phys_to_virt};
366
367    info!("Initialize global memory allocator...");
368    info!("  use {} allocator.", ax_alloc::global_allocator().name());
369
370    // The page allocator (which backs user-space page population via
371    // `alloc_pages`) is initialized from a single contiguous region by
372    // `global_init`; every other free region is handed to the byte/heap
373    // allocator by `global_add_memory` (the bitmap page allocator does not
374    // support `add_memory`). So the region chosen for `global_init` *is* the
375    // entire pool available for user memory.
376    //
377    // Pick the LARGEST free region for the page allocator. Platforms with a
378    // single contiguous RAM region (x86/aarch64/riscv64 qemu-virt) are
379    // unaffected (largest == the only region). Platforms with disjoint regions
380    // (loongarch64 qemu-virt: a small ~248 MB low region below the MMIO hole
381    // plus the multi-GB high region at 0x8000_0000) previously picked the small
382    // low region — the "first free region after .bss" heuristic — which capped
383    // all user allocations at ~248 MB regardless of total RAM, OOM'ing large
384    // workloads (e.g. the gradle build JVM) even with gigabytes free.
385    let mut max_region_size = 0;
386    let mut max_region_paddr = 0.into();
387
388    for r in memory_regions() {
389        if r.flags.contains(MemRegionFlags::FREE) && r.size > max_region_size {
390            max_region_size = r.size;
391            max_region_paddr = r.paddr;
392        }
393    }
394
395    for r in memory_regions() {
396        if r.flags.contains(MemRegionFlags::FREE) && r.paddr == max_region_paddr {
397            ax_alloc::global_init(phys_to_virt(r.paddr).as_usize(), r.size)
398                .expect("initialize global allocator failed");
399            break;
400        }
401    }
402
403    for r in memory_regions() {
404        if r.flags.contains(MemRegionFlags::FREE) && r.paddr != max_region_paddr {
405            ax_alloc::global_add_memory(phys_to_virt(r.paddr).as_usize(), r.size)
406                .expect("add heap memory region failed");
407        }
408    }
409}
410
411#[cfg(feature = "irq")]
412fn init_interrupt() {
413    init_percpu_irq(ax_hal::percpu::this_cpu_id());
414
415    // Enable IRQs before starting app
416    ax_hal::asm::enable_irqs();
417
418    #[cfg(feature = "ipi")]
419    ax_ipi::mark_current_cpu_ready();
420}
421
422#[cfg(feature = "irq")]
423pub(crate) fn init_percpu_irq(cpu_id: usize) {
424    ax_hal::irq::cpu_online(cpu_id).expect("failed to mark CPU online for IRQ framework");
425    ax_hal::irq::init_common_irq_handler();
426
427    if ax_hal::percpu::this_cpu_is_bsp() {
428        let cpus = ax_hal::irq::CpuMask::first_n(ax_hal::cpu_num());
429        ax_hal::irq::request_percpu_irq(ax_hal::time::irq_num(), cpus, timer_irq_handler)
430            .expect("failed to register timer IRQ handler");
431
432        #[cfg(any(feature = "ipi", feature = "wake-ipi"))]
433        ax_hal::irq::request_percpu_irq(ax_hal::irq::ipi_irq(), cpus, ipi_irq_handler)
434            .expect("failed to register IPI IRQ handler");
435    }
436
437    init_timer();
438}
439
440#[cfg(all(feature = "irq", feature = "ipi"))]
441unsafe fn ax_ipi_run_on_cpu_sync(
442    cpu: usize,
443    f: unsafe fn(*mut ()),
444    arg: *mut (),
445) -> Result<(), ax_hal::irq::IrqError> {
446    unsafe { ax_ipi::run_on_cpu_sync_raw(cpu, f, arg) }
447}
448
449#[cfg(feature = "irq")]
450fn periodic_interval_nanos() -> u64 {
451    ax_hal::time::NANOS_PER_SEC / ticks_per_sec()
452}
453
454#[cfg(feature = "irq")]
455#[ax_percpu::def_percpu]
456static NEXT_PERIODIC_DEADLINE_NANOS: u64 = 0;
457
458#[cfg(feature = "irq")]
459fn with_periodic_deadline<R>(
460    operation: impl for<'scope> FnOnce(&ax_percpu::CpuPin<'scope>) -> R,
461) -> R {
462    // SAFETY: every caller runs either during offline CPU initialization or in
463    // the local timer IRQ path. Both contexts prevent migration for the whole
464    // callback, and the CPU-local area was installed before runtime entry.
465    unsafe { ax_percpu::with_cpu_pin(operation) }
466        .unwrap_or_else(|error| panic!("timer CPU-local state is invalid: {error}"))
467}
468
469#[cfg(feature = "irq")]
470fn init_timer() {
471    ax_hal::time::enable_timer_irq();
472    let now_ns = ax_hal::time::monotonic_time_nanos();
473    with_periodic_deadline(|pin| {
474        NEXT_PERIODIC_DEADLINE_NANOS
475            .write_current(pin, now_ns.saturating_add(periodic_interval_nanos()));
476    });
477    program_next_timer();
478}
479
480#[cfg(feature = "irq")]
481fn advance_periodic_timer(now_ns: u64) -> bool {
482    let mut deadline = with_periodic_deadline(|pin| NEXT_PERIODIC_DEADLINE_NANOS.read_current(pin));
483    if deadline == 0 {
484        with_periodic_deadline(|pin| {
485            NEXT_PERIODIC_DEADLINE_NANOS
486                .write_current(pin, now_ns.saturating_add(periodic_interval_nanos()));
487        });
488        return false;
489    }
490    if now_ns < deadline {
491        return false;
492    }
493
494    while deadline <= now_ns {
495        deadline = deadline.saturating_add(periodic_interval_nanos());
496        if deadline == u64::MAX {
497            break;
498        }
499    }
500    with_periodic_deadline(|pin| NEXT_PERIODIC_DEADLINE_NANOS.write_current(pin, deadline));
501    true
502}
503
504#[cfg(feature = "irq")]
505fn program_next_timer() {
506    let mut deadline = with_periodic_deadline(|pin| NEXT_PERIODIC_DEADLINE_NANOS.read_current(pin));
507    if deadline == 0 {
508        let now_ns = ax_hal::time::monotonic_time_nanos();
509        deadline = now_ns.saturating_add(periodic_interval_nanos());
510        with_periodic_deadline(|pin| NEXT_PERIODIC_DEADLINE_NANOS.write_current(pin, deadline));
511    }
512    #[cfg(feature = "multitask")]
513    if let Some(task_deadline) = ax_task::next_timer_deadline_nanos() {
514        deadline = core::cmp::min(deadline, task_deadline);
515    }
516
517    ax_hal::time::set_oneshot_timer(deadline);
518    #[cfg(feature = "multitask")]
519    ax_task::note_programmed_timer_deadline_nanos(deadline);
520}
521
522#[cfg(feature = "irq")]
523fn timer_irq_handler(ctx: ax_hal::irq::IrqContext) -> ax_hal::irq::IrqReturn {
524    let _ = ctx;
525    #[cfg(feature = "multitask")]
526    let scheduler_tick = advance_periodic_timer(ax_hal::time::monotonic_time_nanos());
527    #[cfg(not(feature = "multitask"))]
528    let _ = advance_periodic_timer(ax_hal::time::monotonic_time_nanos());
529    #[cfg(feature = "multitask")]
530    ax_task::on_timer_irq(scheduler_tick);
531    program_next_timer();
532    ax_hal::irq::IrqReturn::Handled
533}
534
535#[cfg(all(feature = "irq", feature = "ipi"))]
536fn ipi_irq_handler(_ctx: ax_hal::irq::IrqContext) -> ax_hal::irq::IrqReturn {
537    ax_ipi::ipi_handler();
538    ax_hal::irq::IrqReturn::Handled
539}
540
541#[cfg(all(feature = "irq", feature = "wake-ipi", not(feature = "ipi")))]
542fn ipi_irq_handler(_ctx: ax_hal::irq::IrqContext) -> ax_hal::irq::IrqReturn {
543    ax_hal::irq::IrqReturn::Handled
544}
545
546#[cfg(all(feature = "tls", not(feature = "multitask")))]
547fn init_tls() {
548    let main_tls = ax_hal::tls::TlsArea::alloc();
549    let kernel_tls = ax_hal::context::KernelTlsBase::new(main_tls.tls_ptr() as usize);
550    unsafe { ax_hal::asm::write_thread_pointer(kernel_tls) };
551    core::mem::forget(main_tls);
552}
553
554#[cfg(test)]
555mod tests {
556    #[test]
557    fn fs_init_accepts_bootargs_without_fs_feature() {
558        crate::fs::init(Some("root=/dev/vda"));
559    }
560}