Skip to main content

starry_kernel/
entry.rs

1use alloc::{
2    string::{String, ToString},
3    sync::Arc,
4};
5
6use ax_kernel_guard::NoPreemptIrqSave;
7use ax_runtime::hal::cpu::uspace::UserContext;
8use ax_sync::Mutex;
9use ax_task::{AxTaskExt, spawn_task_with};
10use starry_process::{Pid, Process};
11
12use crate::{
13    file::FD_TABLE,
14    mm::{copy_from_kernel, load_user_app, new_user_aspace_empty},
15    pseudofs::{self, dev::tty},
16    task::{ProcessData, ProcessImage, Thread, add_task_to_table, new_user_task, spawn_alarm_task},
17    tracepoint::tracepoint_init,
18};
19
20/// Initialize and run initproc.
21pub fn init(args: &[String], envs: &[String]) {
22    static_keys::global_init();
23    crate::cgroup::init();
24
25    tracepoint_init().expect("Failed to initialize tracepoints");
26
27    crate::ebpf::init_ebpf();
28    crate::perf::perf_event_init();
29    crate::kmod::init_kmod();
30
31    pseudofs::mount_all().expect("Failed to mount pseudofs");
32    spawn_alarm_task();
33    // DVFS: a one-shot OPP-calibration boot runs the sweep and skips the governor;
34    // otherwise start the ondemand governor. Both run here (early init, before the
35    // console tty handoff) so their kernel logs reach the serial console.
36    if ax_driver::cpufreq::calibrate_wanted() {
37        run_opp_calibration();
38    } else {
39        spawn_cpufreq_governor();
40    }
41    pseudofs::usbfs::start_event_pump();
42
43    ax_alloc::register_page_reclaim_fn(ax_fs_ng::vfs::page_cache_reclaim);
44
45    let loc = ax_fs_ng::vfs::current_fs_context()
46        .lock()
47        .resolve(&args[0])
48        .expect("Failed to resolve executable path");
49    let path = loc
50        .absolute_path()
51        .expect("Failed to get executable absolute path");
52    let name = loc.name().into_owned();
53
54    let mut uspace = new_user_aspace_empty()
55        .and_then(|mut it| {
56            copy_from_kernel(&mut it)?;
57            Ok(it)
58        })
59        .expect("Failed to create user address space");
60
61    let (entry_vaddr, ustack_top, auxv) = load_user_app(&mut uspace, loc, &args[0], args, envs)
62        .unwrap_or_else(|e| panic!("Failed to load user app: {}", e));
63
64    let uctx = UserContext::new(entry_vaddr.into(), ustack_top, 0);
65    let mut task = new_user_task(&name, uctx, 0);
66    task.ctx_mut().set_page_table_root(uspace.page_table_root());
67
68    // PID 1 must really be 1: the init process is the root of the process
69    // hierarchy and userspace (e.g. systemd's `getpid() == 1` system-manager
70    // check) relies on it. The scheduler task id is an internal counter that is
71    // already past 1 by the time we spawn the user init (kernel helper tasks
72    // took the low ids), so we pin the user-visible pid/tid to 1 and leave the
73    // scheduler id untouched. `Thread::tid` is already decoupled from the
74    // scheduler id (see its field doc), so this only requires the table keys to
75    // follow the thread tid rather than `task.id()`.
76    const INIT_PID: Pid = 1;
77    let pid = INIT_PID;
78    let proc = Process::new_init(pid);
79    proc.add_thread(pid);
80
81    if let Err(err) = tty::bind_console_to(&proc) {
82        warn!("Failed to bind console tty: {err:?}");
83    }
84
85    let proc = ProcessData::new(
86        proc,
87        ProcessImage::new(
88            path.to_string(),
89            Arc::new(args.to_vec()),
90            Arc::new(envs.to_vec()),
91            auxv,
92            "/".to_string(),
93            "/".to_string(),
94        ),
95        Arc::new(Mutex::new(uspace)),
96        Arc::default(),
97        None,
98        pid,
99        false,
100    );
101    // SAFE-EXPECT: failing to attach init would violate the kernel's process accounting invariant.
102    crate::cgroup::attach_initial_process(pid)
103        .expect("Failed to attach init process to cgroup root");
104
105    let mut scope = scope_local::Scope::new();
106    crate::file::add_stdio(&mut FD_TABLE.scope_mut(&mut scope).write())
107        .expect("Failed to add stdio");
108
109    let thr = Thread::new(pid, proc, None, starry_signal::SignalSet::default(), scope);
110    *task.task_ext_mut() = Some(AxTaskExt::from_impl(thr));
111
112    let task = {
113        let _guard = NoPreemptIrqSave::new();
114        let task = spawn_task_with(task, add_task_to_table);
115        tty::arm_console_irq();
116        task
117    };
118
119    // TODO: wait for all processes to finish
120    let exit_code = task.join();
121    info!("Init process exited with code: {exit_code:?}");
122
123    let fs_context = ax_fs_ng::vfs::current_fs_context();
124    let cx = fs_context.lock();
125    // Best-effort teardown, matching Linux's shutdown path. A process that exited while
126    // holding a mount namespace (bind mounts, pivot_root) can leave the mount tree in a
127    // state `unmount_all` rejects; at shutdown that must be logged, not turned into a
128    // kernel panic that fails an otherwise clean run. The rootfs flush below is what
129    // matters for on-disk integrity.
130    if let Err(err) = cx.root_dir().unmount_all() {
131        warn!("shutdown: unmount_all failed (best-effort): {err:?}");
132    }
133    cx.root_dir()
134        .filesystem()
135        .flush()
136        .expect("Failed to flush rootfs");
137}
138
139/// Run the one-shot DVFS OPP calibration sweep (gated by the driver's `CALIBRATE`
140/// const). Each cluster's (voltage x ring) sweep must execute ON a core of that
141/// cluster to read that core's own PMU cycle counter, so we pin a task per cluster
142/// (cpu0=A55, cpu4=A76 big0, cpu6=A76 big1) via `set_current_affinity` and run
143/// them sequentially (the two A76 rails share one I2C bus). Synchronous: it blocks
144/// init briefly so the `CAL` log lines land before the console tty handoff.
145fn run_opp_calibration() {
146    info!("cpufreq: running OPP calibration sweep (governor disabled this boot)");
147    for &(cluster_idx, cpu) in &[(0usize, 0usize), (1, 4), (2, 6)] {
148        let task = ax_task::spawn_raw(
149            move || {
150                ax_task::set_current_affinity(ax_task::AxCpuMask::one_shot(cpu));
151                ax_driver::cpufreq::calibrate_cluster(cluster_idx, cpu);
152            },
153            String::from("cpufreq-cal"),
154            ax_task::default_task_stack_size(),
155        );
156        task.join();
157    }
158    info!("cpufreq: OPP calibration sweep complete");
159}
160
161/// Start the CPU DVFS ondemand governor.
162///
163/// The frequency/voltage policy and the SCMI+PMIC apply live in the cpufreq
164/// driver (`ax_driver::cpufreq`); this kernel task is only the driver's periodic
165/// *loop*. The loop must live here, not in the driver, because ax-driver sits
166/// below ax-task/ax-hal in the dependency graph (they pull ax-driver back in via
167/// axplat-dyn), so spawning a task inside the driver would be a cyclic dep. Each
168/// period we snapshot the per-CPU busy counters the scheduler tick maintains and
169/// hand them to `governor_poll`, which decides and applies any OPP change.
170///
171/// No-op unless the driver armed the governor (feature on and both CPU-rail PMIC
172/// buses up); otherwise every cluster stays on its boot OPP.
173fn spawn_cpufreq_governor() {
174    if !ax_driver::cpufreq::governor_wanted() {
175        return;
176    }
177    info!("Initialize cpufreq ondemand governor...");
178    ax_task::spawn_raw(
179        cpufreq_governor_loop,
180        String::from("cpufreq-gov"),
181        ax_task::default_task_stack_size(),
182    );
183}
184
185/// Periodic body of the DVFS governor task: sleep, sample every CPU's cumulative
186/// busy-tick counter, and let the driver scale each cluster to match load. The
187/// slow work (SCMI SMC + PMIC I2C/SPI voltage ramp) happens inside
188/// `governor_poll`, which is why this runs in a sleepable task rather than the
189/// scheduler tick.
190fn cpufreq_governor_loop() {
191    let period = core::time::Duration::from_millis(ax_driver::cpufreq::governor_period_ms());
192    loop {
193        ax_task::sleep(period);
194        // RK3588 has 8 CPUs; an offline core's counter never advances, so it
195        // simply reads as idle (conservative — never over-scales).
196        let mut busy = [0u64; 8];
197        for (cpu, slot) in busy.iter_mut().enumerate() {
198            *slot = ax_task::cpu_busy_ticks(cpu);
199        }
200        ax_driver::cpufreq::governor_poll(&busy);
201    }
202}