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