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