Skip to main content

starry_kernel/
entry.rs

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