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