Skip to main content

starry_kernel/
entry.rs

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