Skip to main content

starnix_core/task/
kernel.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::bpf::EbpfState;
6use crate::device::remote_block_device::RemoteBlockDeviceRegistry;
7use crate::device::{DeviceMode, DeviceRegistry};
8use crate::execution::CrashReporter;
9use crate::mm::{FutexTable, MappingSummary, MlockPinFlavor};
10use crate::power::{SuspendResumeManagerHandle, create_watcher_for_wake_events};
11use crate::ptrace::StopState;
12use crate::security::{self, AuditLogger};
13use crate::signals::SignalInfo;
14use crate::task::container_namespace::ContainerNamespace;
15use crate::task::limits::SystemLimits;
16use crate::task::memory_attribution::MemoryAttributionManager;
17use crate::task::net::NetstackDevices;
18use crate::task::tracing::TracePerformanceEventManager;
19use crate::task::{
20    AbstractUnixSocketNamespace, AbstractVsockSocketNamespace, CurrentTask, DelayedReleaser,
21    ExitStatus, IpTables, KernelCgroups, KernelStats, KernelThreads, PidTable, SchedulerManager,
22    Syslog, Task, ThreadGroup, UtsNamespace, UtsNamespaceHandle,
23};
24use crate::time::{HrTimerManager, HrTimerManagerHandle};
25use crate::vdso::vdso_loader::Vdso;
26use crate::vfs::fs_args::MountParams;
27use crate::vfs::socket::{
28    GenericMessage, GenericNetlink, NetlinkAccessControl, NetlinkContextImpl,
29    NetlinkToClientSender, SocketAddress, SocketTokensStore,
30};
31use crate::vfs::{CacheConfig, FileOps, FsNodeHandle, FsString, Mounts, NamespaceNode};
32use bstr::{BString, ByteSlice};
33use devicetree::types::Devicetree;
34use expando::Expando;
35use fidl::endpoints::{ClientEnd, DiscoverableProtocolMarker, ProtocolMarker, create_endpoints};
36use fidl_fuchsia_component_runner::{ComponentControllerControlHandle, ComponentStopInfo};
37use fidl_fuchsia_feedback::CrashReporterProxy;
38use fidl_fuchsia_io as fio;
39use fidl_fuchsia_memory_attribution as fattribution;
40use fidl_fuchsia_net_power as fnet_power;
41use fidl_fuchsia_net_resources as fnet_resources;
42use fidl_fuchsia_time_external::AdjustSynchronousProxy;
43use fuchsia_async as fasync;
44use fuchsia_inspect::ArrayProperty;
45use fuchsia_rcu::RcuReadScope;
46use futures::FutureExt;
47use netlink::interfaces::InterfacesHandler;
48use netlink::{NETLINK_LOG_TAG, Netlink};
49use once_cell::sync::OnceCell;
50use scopeguard::ScopeGuard;
51use smallvec::SmallVec;
52use starnix_lifecycle::AtomicCounter;
53use starnix_logging::{SyscallLogFilter, log_debug, log_error, log_info, log_warn};
54use starnix_sync::{
55    ComponentControllerLock, KernelSwapFiles, LockDepGuard, LockDepMutex, MountsLevel, RwSeqLock,
56    RwSeqLockGuard, SyscallLogFiltersLock,
57};
58use starnix_uapi::device_id::DeviceId;
59use starnix_uapi::errors::{Errno, errno};
60use starnix_uapi::open_flags::OpenFlags;
61use starnix_uapi::signals::SIGKILL;
62use starnix_uapi::{VMADDR_CID_HOST, from_status_like_fdio};
63use std::borrow::Cow;
64use std::cell::RefCell;
65use std::collections::{HashMap, HashSet};
66use std::num::NonZeroU64;
67use std::ops::Deref;
68use std::path::PathBuf;
69use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU8, AtomicU16, Ordering};
70use std::sync::{Arc, OnceLock, Weak};
71use zx::{CpuFeatureFlags, CpuSveFeatureFlags};
72
73/// Host kernel free-memory watermarks (in MiB) configured via Product Assembly
74/// (`platform.kernel.oom`) and routed to `starnix_kernel` as structured configuration.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub struct OomWatermarksMb {
77    pub out_of_memory_mb: u32,
78    pub critical_mb: u32,
79    pub warning_mb: u32,
80}
81
82/// Kernel features are specified in the component manifest of the starnix container
83/// or explicitly provided to the kernel constructor in tests.
84#[derive(Debug, Default, Clone)]
85pub struct KernelFeatures {
86    pub bpf_v2: bool,
87
88    /// Whether the `devtmpfs` filesystem and automatic device node creation are enabled.
89    pub devtmpfs: bool,
90
91    /// Whether the kernel supports the S_ISUID and S_ISGID bits.
92    ///
93    /// For example, these bits are used by `sudo`.
94    ///
95    /// Enabling this feature is potentially a security risk because they allow privilege
96    /// escalation.
97    pub enable_suid: bool,
98
99    /// Whether io_uring is enabled.
100    ///
101    /// TODO(https://fxbug.dev/297431387): Enabled by default once the feature is completed.
102    pub io_uring: bool,
103
104    /// Whether the kernel should return an error to userspace, rather than panicking, if `reboot()`
105    /// is requested but cannot be enacted because the kernel lacks the relevant capabilities.
106    pub error_on_failed_reboot: bool,
107
108    /// The default seclabel that is applied to components that are run in this kernel.
109    ///
110    /// Components can override this by setting the `seclabel` field in their program block.
111    pub default_seclabel: Option<String>,
112
113    /// Whether the kernel is being used to run the SELinux Test Suite.
114    ///
115    /// TODO: https://fxbug.dev/388077431 - remove this once we no longer need workarounds for the
116    /// SELinux Test Suite.
117    pub selinux_test_suite: bool,
118
119    /// The default mount options to use when mounting directories from a component's namespace.
120    ///
121    /// The key is the path in the component's namespace, and the value is the mount options
122    /// string.
123    pub default_ns_mount_options: Option<HashMap<String, String>>,
124
125    /// The default uid that is applied to components that are run in this kernel.
126    ///
127    /// Components can override this by setting the `uid` field in their program block.
128    pub default_uid: u32,
129
130    /// mlock() never prefaults pages.
131    pub mlock_always_onfault: bool,
132
133    /// Implementation of mlock() to use for this kernel instance.
134    pub mlock_pin_flavor: MlockPinFlavor,
135
136    /// Whether excessive crash reports should be throttled.
137    pub crash_report_throttling: bool,
138
139    /// Whether or not to serve wifi support to Android.
140    pub wifi: bool,
141
142    /// The number of bytes to cache in pages for reading zx::MapInfo from VMARs.
143    pub cached_zx_map_info_bytes: u32,
144
145    /// The size of the Dirent LRU cache.
146    pub dirent_cache_size: u32,
147
148    /// Whether to expose a stub '/dev/ion' node, as a temporary workaround for compatibility.
149    // TODO(https://fxbug.dev/485370648) remove when unnecessary
150    pub fake_ion: bool,
151
152    /// Whether `remotevol` mounts the volume in-process rather than over FIDL.
153    pub remotevol_in_process: bool,
154
155    /// Whether to log unexpected lookup misses in `sysfs` via `track_stub_log!`.
156    pub log_sysfs_lookup_misses: bool,
157
158    /// Free-memory watermarks (in MiB) for `/proc/zoneinfo`.
159    pub oom_watermarks_mb: Option<OomWatermarksMb>,
160
161    /// Paths of executables whose processes are stopped only after all other processes except init
162    /// have exited when the kernel shuts down, e.g. one that writes crash dumps for processes that
163    /// crash during shutdown. See `Kernel::run_shutdown()`.
164    pub stop_last_on_shutdown: Vec<FsString>,
165}
166
167impl KernelFeatures {
168    /// Returns the `MountParams` to use when mounting the specified path from a component's
169    /// namespace.  This mechanism is also used to specified options for mounts created via
170    /// container features, by specifying a pseudo-path e.g. "#container".
171    pub fn ns_mount_options(&self, ns_path: &str) -> Result<MountParams, Errno> {
172        if let Some(all_options) = &self.default_ns_mount_options {
173            if let Some(options) = all_options.get(ns_path) {
174                return MountParams::parse(options.as_bytes().into());
175            }
176        }
177        Ok(MountParams::default())
178    }
179}
180
181/// Kernel command line or module argument structure (`name` or `name=value`).
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub struct ArgNameAndValue<'a> {
184    pub name: &'a str,
185    pub value: Option<&'a str>,
186}
187
188impl<'a> From<&'a str> for ArgNameAndValue<'a> {
189    fn from(arg: &'a str) -> Self {
190        match arg.split_once('=') {
191            Some((name, value)) => Self { name, value: Some(value) },
192            None => Self { name: arg, value: None },
193        }
194    }
195}
196
197type DeferredDropCallback = Box<dyn FnOnce() + Send + Sync>;
198
199/// A proof token representing the global lock over the namespace mount topology.
200///
201/// Functions that take `&MountsWriteToken` require the caller to hold the
202/// `Kernel::mounts_lock` to ensure safe modification of the global mount tree.
203pub struct MountsWriteToken {
204    deferred_drops: RefCell<SmallVec<[DeferredDropCallback; 8]>>,
205}
206
207impl MountsWriteToken {
208    fn new() -> Self {
209        Self { deferred_drops: RefCell::new(SmallVec::new()) }
210    }
211
212    fn defer_drop_internal<T: Send + Sync + 'static>(&self, value: T) {
213        self.deferred_drops.borrow_mut().push(Box::new(move || {
214            drop(value);
215        }));
216    }
217
218    /// We cannot block while holding a [MountsWriteGuard] as it is a spinlock. If a Drop requires
219    /// a blocking operation, use [Self::defer_drop] to defer the blocking operation until
220    /// after the [MountsWriteGuard] is released.
221    pub fn defer_drop<T: Send + Sync + 'static>(
222        &self,
223        value: T,
224    ) -> ScopeGuard<T, impl FnOnce(T) + '_> {
225        scopeguard::guard(value, |value| {
226            self.defer_drop_internal(value);
227        })
228    }
229
230    /// Helper method to [Self::defer_drop] specifically for [Arc<T>]s.
231    pub fn retain<T: Send + Sync + 'static>(&self, value: &Arc<T>) -> Arc<T> {
232        self.defer_drop_internal(value.clone());
233        value.clone()
234    }
235
236    fn take_deferred_drops(&mut self) -> SmallVec<[DeferredDropCallback; 8]> {
237        std::mem::take(self.deferred_drops.get_mut())
238    }
239}
240
241/// A guard special cased for mount operations to defer destroying mounts until after all
242/// modifications of the global mount tree are completed.
243pub struct MountsWriteGuard<'a> {
244    guard: Option<RwSeqLockGuard<'a, LockDepGuard<'a, MountsWriteToken>>>,
245}
246
247impl<'a> MountsWriteGuard<'a> {
248    fn new(guard: RwSeqLockGuard<'a, LockDepGuard<'a, MountsWriteToken>>) -> Self {
249        Self { guard: Some(guard) }
250    }
251}
252
253impl<'a> Deref for MountsWriteGuard<'a> {
254    type Target = MountsWriteToken;
255    fn deref(&self) -> &Self::Target {
256        self.guard.as_ref().unwrap()
257    }
258}
259
260impl<'a> Drop for MountsWriteGuard<'a> {
261    fn drop(&mut self) {
262        if let Some(mut guard) = self.guard.take() {
263            let drops = guard.take_deferred_drops();
264            drop(guard);
265            for callback in drops {
266                callback();
267            }
268        }
269    }
270}
271
272/// Maximum time after shutdown starts before deferred-shutdown processes receive SIGKILL.
273///
274/// Deferred-shutdown processes are processes that are still handling a core-dump signal
275/// when their normal shutdown SIGKILL timeout expires (e.g., while writing a crash dump),
276/// along with any child processes they spawn. See `ThreadGroup::shut_down()`.
277///
278/// `component_manager` forcibly kills the container if it does not stop within 5s by
279/// default, which skips filesystem unmounts and `on_stop`. Setting this timeout to 3s
280/// gives crash dumps time to finish (typically 1-2.5s in testing) while leaving 2s for
281/// the remaining shutdown steps (`stop_last_on_shutdown` processes, `init`, unmounts,
282/// and `on_stop`, which took up to 0.6s in testing).
283// TODO(https://fxbug.dev/574471394): Remove once non-forced shutdown lets the container
284// run its own shutdown sequence.
285const DEFERRED_SHUTDOWN_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(3);
286
287/// The shared, mutable state for the entire Starnix kernel.
288///
289/// The `Kernel` object holds all kernel threads, userspace tasks, and file system resources for a
290/// single instance of the Starnix kernel. In production, there is one instance of this object for
291/// the entire Starnix kernel. However, multiple instances of this object can be created in one
292/// process during unit testing.
293///
294/// The structure of this object will likely need to evolve as we implement more namespacing and
295/// isolation mechanisms, such as `namespaces(7)` and `pid_namespaces(7)`.
296pub struct Kernel {
297    /// Weak reference to self. Allows to not have to pass &Arc<Kernel> in apis.
298    pub weak_self: Weak<Kernel>,
299
300    /// The kernel threads running on behalf of this kernel.
301    pub kthreads: KernelThreads,
302
303    /// The features enabled for this kernel.
304    pub features: KernelFeatures,
305
306    /// The processes and threads running in this kernel, organized by pid_t.
307    pub pids: PidTable,
308
309    /// A weak reference to the init task (PID 1).
310    pub init_task: OnceLock<Weak<Task>>,
311
312    /// Shared manager of pid/tid to koid mappings for tracing and profiling clients.
313    /// A direct field (rather than an expando entry) so the task-creation fast path is a
314    /// single relaxed atomic load with no locks when nothing is recording.
315    pub trace_event_manager: Arc<TracePerformanceEventManager>,
316
317    /// Subsystem-specific properties that hang off the Kernel object.
318    ///
319    /// Instead of adding yet another property to the Kernel object, consider storing the property
320    /// in an expando if that property is only used by one part of the system, such as a module.
321    pub expando: Expando,
322
323    /// The default namespace for abstract AF_UNIX sockets in this kernel.
324    ///
325    /// Rather than use this default namespace, abstract socket addresses
326    /// should be looked up in the AbstractSocketNamespace on each Task
327    /// object because some Task objects might have a non-default namespace.
328    pub default_abstract_socket_namespace: Arc<AbstractUnixSocketNamespace>,
329
330    /// The default namespace for abstract AF_VSOCK sockets in this kernel.
331    pub default_abstract_vsock_namespace: Arc<AbstractVsockSocketNamespace>,
332
333    /// The kernel command line. Shows up in /proc/cmdline.
334    pub cmdline: BString,
335
336    pub device_tree: Option<Devicetree>,
337
338    // Global state held by the Linux Security Modules subsystem.
339    pub security_state: security::KernelState,
340
341    /// The registry of device drivers.
342    pub device_registry: DeviceRegistry,
343
344    /// Mapping of top-level namespace entries to an associated proxy.
345    /// For example, "/svc" to the respective proxy. Only the namespace entries
346    /// which were known at component startup will be available by the kernel.
347    pub container_namespace: ContainerNamespace,
348
349    /// The global lock for the mount tree.
350    ///
351    /// This lock protects against concurrent modifications to the mount topology. It uses
352    /// an `RwSeqLock` to allow readers (like path walking traversing mount points) to get a
353    /// consistent, lock-free snapshot of the RCU-protected mount table using `read_seq`.
354    /// Writers must acquire the lock before mutating filesystems, moving mounts, or
355    /// propagating peer groups. The returned `MountsWriteToken` is used as a proof token
356    /// throughout the `namespace` module to statically enforce exclusive write access.
357    pub mounts_lock: RwSeqLock<LockDepMutex<MountsWriteToken, MountsLevel>>,
358
359    /// The registry of block devices backed by a remote fuchsia.io file.
360    pub remote_block_device_registry: Arc<RemoteBlockDeviceRegistry>,
361
362    /// The iptables used for filtering network packets.
363    iptables: OnceLock<IpTables>,
364
365    /// The futexes across all processes in the kernel.
366    pub shared_futexes: FutexTable,
367
368    /// The default UTS namespace for all tasks.
369    ///
370    /// Because each task can have its own UTS namespace, you probably want to use
371    /// the UTS namespace handle of the task, which may/may not point to this one.
372    pub root_uts_ns: UtsNamespaceHandle,
373
374    /// A struct containing a VMO with a vDSO implementation, if implemented for a given architecture, and possibly an offset for a sigreturn function.
375    pub vdso: Vdso,
376
377    /// A struct containing a VMO with a arch32-vDSO implementation, if implemented for a given architecture.
378    // TODO(https://fxbug.dev/380431743) This could be made less clunky -- maybe a Vec<Vdso> above or
379    // something else
380    pub vdso_arch32: Option<Vdso>,
381
382    /// The table of devices installed on the netstack and their associated
383    /// state local to this `Kernel`.
384    pub netstack_devices: Arc<NetstackDevices>,
385
386    /// Files that are currently available for swapping.
387    /// Note: Starnix never actually swaps memory to these files. We just need to track them
388    /// to pass conformance tests.
389    pub swap_files: LockDepMutex<Vec<FsNodeHandle>, KernelSwapFiles>,
390
391    /// The implementation of generic Netlink protocol families.
392    generic_netlink: OnceLock<GenericNetlink<NetlinkToClientSender<GenericMessage>>>,
393
394    /// The implementation of networking-related Netlink protocol families.
395    network_netlink: OnceLock<Netlink<NetlinkContextImpl>>,
396
397    /// Inspect instrumentation for this kernel instance.
398    pub inspect_node: fuchsia_inspect::Node,
399
400    /// The kinds of seccomp action that gets logged, stored as a bit vector.
401    /// Each potential SeccompAction gets a bit in the vector, as specified by
402    /// SeccompAction::logged_bit_offset.  If the bit is set, that means the
403    /// action should be logged when it is taken, subject to the caveats
404    /// described in seccomp(2).  The value of the bit vector is exposed to users
405    /// in a text form in the file /proc/sys/kernel/seccomp/actions_logged.
406    pub actions_logged: AtomicU16,
407
408    /// The manager for suspend/resume.
409    pub suspend_resume_manager: SuspendResumeManagerHandle,
410
411    /// Unique IDs for new mounts and mount namespaces.
412    pub next_mount_id: AtomicCounter<u64>,
413    pub next_peer_group_id: AtomicCounter<u64>,
414    pub next_namespace_id: AtomicCounter<u64>,
415
416    /// Unique IDs for file objects.
417    pub next_file_object_id: AtomicCounter<u64>,
418
419    /// Controls which processes a process is allowed to ptrace.  See Documentation/security/Yama.txt
420    pub ptrace_scope: AtomicU8,
421
422    // The Fuchsia build version returned by `fuchsia.buildinfo.Provider`.
423    pub build_version: OnceCell<String>,
424
425    pub stats: Arc<KernelStats>,
426
427    /// Resource limits that are exposed, for example, via sysctl.
428    pub system_limits: SystemLimits,
429
430    // The service to handle delayed releases. This is required for elements that requires to
431    // execute some code when released and requires a known context (both in term of lock context,
432    // as well as `CurrentTask`).
433    pub delayed_releaser: DelayedReleaser,
434
435    /// Manages task priorities.
436    pub scheduler: SchedulerManager,
437
438    /// The syslog manager.
439    pub syslog: Syslog,
440
441    /// All mounts.
442    pub mounts: Mounts,
443
444    /// The manager for creating and managing high-resolution timers.
445    pub hrtimer_manager: HrTimerManagerHandle,
446
447    /// The manager for monitoring and reporting resources used by the kernel.
448    pub memory_attribution_manager: MemoryAttributionManager,
449
450    /// Handler for crashing Linux processes.
451    pub crash_reporter: CrashReporter,
452
453    /// When this kernel started shutting down, if it has. See `run_shutdown()`.
454    shutdown_start: OnceLock<zx::MonotonicInstant>,
455
456    /// True to disable syslog access to unprivileged callers.  This also controls whether read
457    /// access to /dev/kmsg requires privileged capabilities.
458    pub restrict_dmesg: AtomicBool,
459
460    /// Determines whether unprivileged BPF is permitted, or can be re-enabled.
461    ///   0 - Unprivileged BPF is permitted.
462    ///   1 - Unprivileged BPF is not permitted, and cannot be enabled.
463    ///   2 - Unprivileged BPF is not permitted, but can be enabled by a privileged task.
464    pub disable_unprivileged_bpf: AtomicU8,
465
466    /// Controls use of perf_event_open(2) by tasks without CAP_PERFMON (or CAP_SYS_ADMIN):
467    ///   -1 - No capability required.
468    ///    0 - Capability required for tracepoint events.
469    ///    1 - Capability additionally required for CPU-wide events.
470    ///    2 - Capability additionally required for kernel profiling (default).
471    pub perf_event_paranoid: AtomicI32,
472
473    /// Control handle to the running container's ComponentController.
474    pub container_control_handle:
475        LockDepMutex<Option<ComponentControllerControlHandle>, ComponentControllerLock>,
476
477    /// eBPF state: loaded programs, eBPF maps, etc.
478    pub ebpf_state: EbpfState,
479
480    /// Cgroups of the kernel.
481    pub cgroups: KernelCgroups,
482
483    /// Used to communicate requests to adjust system time from within a Starnix
484    /// container. Used from syscalls.
485    pub time_adjustment_proxy: Option<AdjustSynchronousProxy>,
486
487    /// A token for the wake group we have registered with the netstack to
488    /// receive wakeup notifications on incoming data while suspended.
489    ///
490    /// A value of `None` means we were unable to communicate with the netstack
491    /// to create the wake group. In that case, we assume something has gone
492    /// wrong with the netstack (failed to start, crash, etc) and won't retry.
493    pub netstack_wake_group: OnceLock<Option<fnet_resources::WakeGroupToken>>,
494
495    /// Used to store tokens for sockets, particularly per-uid sharing domain sockets.
496    pub socket_tokens_store: SocketTokensStore,
497
498    /// Hardware capabilities to push onto stack when loading an ELF binary.
499    pub hwcaps: HwCaps,
500
501    /// Filters for syscall logging. Processes with names matching these filters will have syscalls
502    /// logged at INFO level.
503    pub syscall_log_filters: LockDepMutex<Vec<SyscallLogFilter>, SyscallLogFiltersLock>,
504}
505
506/// Hardware capabilities.
507#[derive(Debug, Clone, Copy, Default)]
508pub struct HwCap {
509    /// The value for `AT_HWCAP`.
510    pub hwcap: u32,
511    /// The value for `AT_HWCAP2`.
512    pub hwcap2: u32,
513}
514
515/// Hardware capabilities for both 32-bit and 64-bit ELF binaries.
516#[derive(Debug, Clone, Copy, Default)]
517pub struct HwCaps {
518    /// For 32-bit binaries.
519    #[cfg(target_arch = "aarch64")]
520    pub arch32: HwCap,
521    /// For 64-bit binaries.
522    pub arch64: HwCap,
523}
524
525/// An implementation of [`InterfacesHandler`].
526///
527/// This holds a `Weak<Kernel>` because it is held within a [`Netlink`] which
528/// is itself held within an `Arc<Kernel>`. Holding an `Arc<T>` within an
529/// `Arc<T>` prevents the `Arc`'s ref count from ever reaching 0, causing a
530/// leak.
531struct InterfacesHandlerImpl(Weak<Kernel>);
532
533impl InterfacesHandlerImpl {
534    fn kernel(&self) -> Option<Arc<Kernel>> {
535        self.0.upgrade()
536    }
537}
538
539impl InterfacesHandler for InterfacesHandlerImpl {
540    fn handle_new_link(&mut self, name: &str, interface_id: NonZeroU64) {
541        if let Some(kernel) = self.kernel() {
542            kernel.netstack_devices.add_device(&kernel, name.into(), interface_id);
543        }
544    }
545
546    fn handle_deleted_link(&mut self, name: &str) {
547        if let Some(kernel) = self.kernel() {
548            kernel.netstack_devices.remove_device(kernel.kthreads.system_task(), name.into());
549        }
550    }
551
552    fn handle_idle_event(&mut self) {
553        let Some(kernel) = self.kernel() else {
554            log_error!("kernel went away while netlink is initializing");
555            return;
556        };
557        let (initialized, wq) = &kernel.netstack_devices.initialized_and_wq;
558        if initialized.swap(true, Ordering::SeqCst) {
559            log_error!("netlink initial devices should only be reported once");
560            return;
561        }
562        wq.notify_all()
563    }
564}
565
566impl Kernel {
567    pub fn new(
568        cmdline: BString,
569        features: KernelFeatures,
570        system_limits: SystemLimits,
571        container_namespace: ContainerNamespace,
572        scheduler: SchedulerManager,
573        crash_reporter_proxy: Option<CrashReporterProxy>,
574        inspect_node: fuchsia_inspect::Node,
575        security_state: security::KernelState,
576        time_adjustment_proxy: Option<AdjustSynchronousProxy>,
577        device_tree: Option<Devicetree>,
578    ) -> Result<Arc<Kernel>, zx::Status> {
579        let unix_address_maker =
580            Box::new(|x: FsString| -> SocketAddress { SocketAddress::Unix(x) });
581        let vsock_address_maker = Box::new(|x: u32| -> SocketAddress {
582            SocketAddress::Vsock { port: x, cid: VMADDR_CID_HOST }
583        });
584
585        let crash_reporter = CrashReporter::new(
586            &inspect_node,
587            crash_reporter_proxy,
588            zx::Duration::from_minutes(8),
589            features.crash_report_throttling,
590        );
591        let hrtimer_manager = HrTimerManager::new(&inspect_node);
592
593        let cpu_feature_flags =
594            zx::system_get_feature_flags::<CpuFeatureFlags>().unwrap_or_else(|e| {
595                log_debug!("CPU feature flags are only supported on ARM64: {}, reporting 0", e);
596                CpuFeatureFlags::empty()
597            });
598        let cpu_sve_feature_flags = zx::system_get_feature_flags::<CpuSveFeatureFlags>()
599            .unwrap_or_else(|e| {
600                log_debug!("CPU SVE feature flags failed: {}, reporting 0", e);
601                CpuSveFeatureFlags::empty()
602            });
603        let hwcaps = HwCaps::from_cpu_feature_flags(cpu_feature_flags, cpu_sve_feature_flags);
604
605        let this = Arc::new_cyclic(|kernel| Kernel {
606            weak_self: kernel.clone(),
607            kthreads: KernelThreads::new(kernel.clone()),
608            features,
609            pids: Default::default(),
610            init_task: OnceLock::new(),
611            trace_event_manager: Arc::new(TracePerformanceEventManager::new(kernel.clone())),
612            expando: Default::default(),
613            default_abstract_socket_namespace: AbstractUnixSocketNamespace::new(unix_address_maker),
614            default_abstract_vsock_namespace: AbstractVsockSocketNamespace::new(
615                vsock_address_maker,
616            ),
617            cmdline,
618            device_tree,
619            security_state,
620            device_registry: Default::default(),
621            container_namespace,
622            mounts_lock: RwSeqLock::new(MountsWriteToken::new().into()),
623            remote_block_device_registry: Default::default(),
624            iptables: OnceLock::new(),
625            shared_futexes: FutexTable::default(),
626            root_uts_ns: Arc::new(UtsNamespace::default().into()),
627            vdso: Vdso::new(),
628            vdso_arch32: Vdso::new_arch32(),
629            netstack_devices: Arc::default(),
630            swap_files: Default::default(),
631            generic_netlink: OnceLock::new(),
632            network_netlink: OnceLock::new(),
633            inspect_node,
634            actions_logged: AtomicU16::new(0),
635            suspend_resume_manager: Default::default(),
636            next_mount_id: AtomicCounter::<u64>::new(1),
637            next_peer_group_id: AtomicCounter::<u64>::new(1),
638            next_namespace_id: AtomicCounter::<u64>::new(1),
639            next_file_object_id: Default::default(),
640            system_limits,
641            ptrace_scope: AtomicU8::new(0), // Disable YAMA checks by default.
642            restrict_dmesg: AtomicBool::new(false),
643            disable_unprivileged_bpf: AtomicU8::new(0), // Enable unprivileged BPF by default.
644            perf_event_paranoid: AtomicI32::new(2),
645            build_version: OnceCell::new(),
646            stats: Arc::new(KernelStats::default()),
647            delayed_releaser: Default::default(),
648            scheduler,
649            syslog: Default::default(),
650            mounts: Mounts::new(),
651            hrtimer_manager,
652            memory_attribution_manager: MemoryAttributionManager::new(kernel.clone()),
653            crash_reporter,
654            shutdown_start: OnceLock::new(),
655            container_control_handle: Default::default(),
656            ebpf_state: Default::default(),
657            cgroups: Default::default(),
658            time_adjustment_proxy,
659            netstack_wake_group: OnceLock::new(),
660            socket_tokens_store: Default::default(),
661            hwcaps,
662            syscall_log_filters: Default::default(),
663        });
664
665        // Initialize the device registry before registering any devices.
666        //
667        // We will create sysfs recursively within this function.
668        this.device_registry.objects.init(&this);
669
670        // Make a copy of this Arc for the inspect lazy node to use but don't create an Arc cycle
671        // because the inspect node that owns this reference is owned by the kernel.
672        let kernel = Arc::downgrade(&this);
673        this.inspect_node.record_lazy_child("thread_groups", move || {
674            if let Some(kernel) = kernel.upgrade() {
675                let inspector = kernel.get_thread_groups_inspect();
676                async move { Ok(inspector) }.boxed()
677            } else {
678                async move { Err(anyhow::format_err!("kernel was dropped")) }.boxed()
679            }
680        });
681
682        let kernel = Arc::downgrade(&this);
683        this.inspect_node.record_lazy_child("cgroupv2", move || {
684            if let Some(kernel) = kernel.upgrade() {
685                async move { Ok(kernel.cgroups.cgroup2.get_cgroup_inspect()) }.boxed()
686            } else {
687                async move { Err(anyhow::format_err!("kernel was dropped")) }.boxed()
688            }
689        });
690
691        Ok(this)
692    }
693
694    /// Returns the init task for this kernel.
695    pub fn get_init_task(&self) -> Result<Arc<Task>, Errno> {
696        self.init_task.get().and_then(|t| t.upgrade()).ok_or_else(|| errno!(EINVAL))
697    }
698
699    /// Shuts down userspace and the kernel in an orderly fashion, eventually terminating the root
700    /// kernel process.
701    pub fn shut_down(self: &Arc<Self>) {
702        // Run shutdown code on a kthread in the main process so that it can be the last process
703        // alive.
704        self.kthreads.spawn_future(
705            {
706                let kernel = self.clone();
707                move || async move {
708                    kernel.run_shutdown().await;
709                }
710            },
711            "run_shutdown",
712        );
713    }
714
715    /// Starts shutting down the Starnix kernel and any running container. Only one thread can drive
716    /// shutdown at a time. This function will return immediately if shut down is already under way.
717    ///
718    /// Shutdown happens in several phases:
719    ///
720    /// 1. Stop processes from being created, except by deferred shutdown processes until the
721    ///    deferred shutdown deadline
722    /// 2. Shut down individual ThreadGroups until only the init and system tasks remain, stopping
723    ///    the `stop_last_on_shutdown` ones last
724    /// 3. Repeat the above for the init task
725    /// 4. Clean up kernel-internal structures that can hold processes alive
726    /// 5. Ensure this process is the only one running in the kernel job.
727    /// 6. Unmounts the kernel's mounts' FileSystems.
728    /// 7. Tell CF the container component has stopped
729    /// 8. Exit this process
730    ///
731    /// If a ThreadGroup does not shut down on its own (including after SIGKILL), that phase of
732    /// shutdown will hang. To gracefully shut down any further we need the other kernel processes
733    /// to do controlled exits that properly release access to shared state. If our orderly shutdown
734    /// does hang, eventually CF will kill the container component which will lead to the job of
735    /// this process being killed and shutdown will still complete.
736    async fn run_shutdown(&self) {
737        const INIT_PID: i32 = 1;
738        const SYSTEM_TASK_PID: i32 = 2;
739
740        // Step 1: From here on, only deferred shutdown processes may create processes, and only
741        // until the deferred shutdown deadline (see `create_zircon_process()`). This keeps Step 2
742        // finite: each round waits for its thread groups to exit, and after the deadline no new
743        // ones can appear.
744        if self.shutdown_start.set(zx::MonotonicInstant::get()).is_err() {
745            log_info!("Additional thread tried to initiate shutdown while already in-progress.");
746            return;
747        }
748        let deferred_shutdown_deadline =
749            self.deferred_shutdown_deadline().expect("shutdown has started");
750
751        log_info!("Shutting down Starnix kernel.");
752
753        // Step 2: Shut down thread groups in a loop until init and the system task are all that
754        // remain. Processes running a `stop_last_on_shutdown` executable are kept until everything
755        // else has exited, e.g. so that processes that crash during shutdown can still have their
756        // crash dumps written.
757        // TODO(https://fxbug.dev/574471394): Remove once a shutdown that isn't forced lets the
758        // container run its own shutdown sequence.
759        let stop_last = &self.features.stop_last_on_shutdown;
760        let is_stop_last = |tg: &ThreadGroup| {
761            let exe = tg.read().first_task().and_then(|task| task.mm().ok());
762            let exe = exe.and_then(|mm| mm.executable_node());
763            exe.is_some_and(|exe| stop_last.contains(&exe.path_escaping_chroot()))
764        };
765        let mut keep_stop_last = !stop_last.is_empty();
766        loop {
767            // Collect under the pid table lock so that no process is caught while it's being
768            // created. `is_stop_last()` runs after the lock is released.
769            let tgs = {
770                let pids = self.pids.lock();
771                pids.get_thread_groups(&RcuReadScope::new())
772                    .filter(|tg| tg.leader.id != SYSTEM_TASK_PID && tg.leader.id != INIT_PID)
773                    .collect::<Vec<_>>()
774            };
775            let tgs = tgs
776                .into_iter()
777                .filter(|tg| !(keep_stop_last && is_stop_last(tg)))
778                .collect::<Vec<_>>();
779            if tgs.is_empty() {
780                if keep_stop_last {
781                    keep_stop_last = false;
782                    continue;
783                }
784                log_info!("pid table is empty except init and system task");
785                break;
786            }
787
788            log_info!(tgs:?; "shutting down thread groups");
789            let mut tasks = vec![];
790            for tg in tgs {
791                let task = fasync::Task::local(ThreadGroup::shut_down(
792                    Arc::downgrade(&tg),
793                    deferred_shutdown_deadline,
794                ));
795                tasks.push(task);
796            }
797            futures::future::join_all(tasks).await;
798        }
799
800        // Step 3: Terminate the init process.
801        let maybe_init = self.get_init_task().ok().map(|t| Arc::downgrade(&t.thread_group));
802        if let Some(init) = maybe_init {
803            log_info!("shutting down init");
804            ThreadGroup::shut_down(init, deferred_shutdown_deadline).await;
805            // If init forked while handling a core-dump signal, its children can outlive it.
806            self.kill_processes_created_during_shutdown();
807        } else {
808            log_info!("init already terminated");
809        }
810
811        // Step 4: Clean up any structures that can keep non-Linux processes live in our job.
812        log_info!("cleaning up pinned memory");
813        self.expando.remove::<crate::mm::InfoCacheShadowProcess>();
814        self.expando.remove::<crate::mm::MlockShadowProcess>();
815
816        // Step 5: Make sure this is the only process running in the job. We already should have
817        // cleared up all processes other than the system task at this point, but wait on any that
818        // might be around for good measure.
819        //
820        // Use unwrap liberally since we're shutting down anyway and errors will still tear down the
821        // kernel.
822        let kernel_job = fuchsia_runtime::job_default();
823        assert_eq!(kernel_job.children().unwrap(), &[], "starnix does not create any child jobs");
824        let own_koid = fuchsia_runtime::process_self().koid().unwrap();
825
826        log_info!("waiting for this to be the only process in the job");
827        loop {
828            let mut remaining_processes = kernel_job
829                .processes()
830                .unwrap()
831                .into_iter()
832                // Don't wait for ourselves to exit.
833                .filter(|pid| pid != &own_koid)
834                .peekable();
835            if remaining_processes.peek().is_none() {
836                log_info!("No stray Zircon processes.");
837                break;
838            }
839
840            let mut terminated_signals = vec![];
841            for pid in remaining_processes {
842                let handle = match kernel_job
843                    .get_child(&pid, zx::Rights::BASIC | zx::Rights::PROPERTY | zx::Rights::DESTROY)
844                {
845                    Ok(h) => h,
846                    Err(e) => {
847                        log_info!(pid:?, e:?; "failed to get child process from job");
848                        continue;
849                    }
850                };
851                log_info!(
852                    pid:?,
853                    name:? = handle.get_name();
854                    "waiting on process terminated signal"
855                );
856                terminated_signals
857                    .push(fuchsia_async::OnSignals::new(handle, zx::Signals::PROCESS_TERMINATED));
858            }
859            log_info!("waiting on process terminated signals");
860            futures::future::join_all(terminated_signals).await;
861        }
862
863        // Step 6: Forcibly unmounts the mounts' FileSystems.
864        log_info!("clearing mounts");
865        self.mounts.clear();
866
867        // Step 7: Tell CF the container stopped.
868        log_info!("all non-root processes killed, notifying CF container is stopped");
869        if let Some(control_handle) = self.container_control_handle.lock().take() {
870            log_info!("Notifying CF that the container has stopped.");
871            control_handle
872                .send_on_stop(ComponentStopInfo {
873                    termination_status: Some(zx::sys::ZX_OK),
874                    exit_code: Some(0),
875                    ..ComponentStopInfo::default()
876                })
877                .unwrap();
878            control_handle.shutdown_with_epitaph(Ok(()));
879        } else {
880            log_warn!("Shutdown invoked without a container controller control handle.");
881        }
882
883        // Step 8: exiting this process.
884        log_info!("All tasks killed, exiting Starnix kernel root process.");
885        // Normally a Rust program exits its process by calling `std::process::exit()` which goes
886        // through libc to exit the program. This runs drop impls on any thread-local variables
887        // which can cause issues during Starnix shutdown when we haven't yet integrated every
888        // subsystem with the shutdown flow. While those issues are indicative of underlying
889        // problems, we can't solve them without finishing the implementation of graceful shutdown.
890        // Instead, ask Zircon to exit our process directly, bypassing any libc atexit handlers.
891        // TODO(https://fxbug.dev/295073633) return from main instead of avoiding atexit handlers
892        zx::Process::exit(0);
893    }
894
895    pub fn is_shutting_down(&self) -> bool {
896        self.shutdown_start.get().is_some()
897    }
898
899    /// Until when deferred shutdown processes may keep running and create processes, or `None` if
900    /// the kernel is not shutting down. See `ThreadGroup::shut_down()`.
901    pub fn deferred_shutdown_deadline(&self) -> Option<zx::MonotonicInstant> {
902        self.shutdown_start.get().map(|start| *start + DEFERRED_SHUTDOWN_TIMEOUT)
903    }
904
905    /// SIGKILLs every process created during shutdown, all of which deferred shutdown processes
906    /// created (e.g. crash dumpers). See `ThreadGroup::shut_down()`.
907    ///
908    /// A dumper that outlives the process it dumps fails, gets reported as a crash and can keep
909    /// that process stopped under ptrace while it does. Processes still handling a core-dump signal
910    /// aren't killed here: after their dumper exits, their handler re-raises the signal so Starnix
911    /// files a crash report, the only record of a crash whose dump missed the deadline. Their own
912    /// `ThreadGroup::shut_down()` gives them a moment to do so before sending SIGKILL.
913    pub fn kill_processes_created_during_shutdown(&self) {
914        // Collect under the pid table lock so that a process being created (which holds it from
915        // the deadline check until its first task is added) is either fully added or not created.
916        let created = {
917            let pids = self.pids.lock();
918            pids.get_thread_groups(&RcuReadScope::new())
919                .filter(|tg| tg.created_during_shutdown.load(Ordering::Relaxed))
920                .collect::<Vec<_>>()
921        };
922        for tg in created {
923            tg.kill(ExitStatus::Kill(SignalInfo::kernel(SIGKILL)), None);
924        }
925    }
926
927    pub fn allow_unprivileged_bpf(&self) -> bool {
928        self.disable_unprivileged_bpf.load(Ordering::Relaxed) == 0
929    }
930
931    /// Opens a device file (driver) identified by `dev`.
932    pub fn open_device(
933        &self,
934        current_task: &CurrentTask,
935        node: &NamespaceNode,
936        flags: OpenFlags,
937        dev: DeviceId,
938        mode: DeviceMode,
939    ) -> Result<Box<dyn FileOps>, Errno> {
940        self.device_registry.open_device(current_task, node, flags, dev, mode)
941    }
942
943    /// Return a reference to the Audit Framework
944    ///
945    /// This function follows the lazy initialization pattern.
946    pub fn audit_logger(&self) -> Arc<AuditLogger> {
947        self.expando.get_or_init(|| AuditLogger::new(self))
948    }
949
950    /// Return a reference to the GenericNetlink implementation.
951    ///
952    /// This function follows the lazy initialization pattern, where the first
953    /// call will instantiate the Generic Netlink server in a separate kthread.
954    pub fn generic_netlink(&self) -> &GenericNetlink<NetlinkToClientSender<GenericMessage>> {
955        self.generic_netlink.get_or_init(|| {
956            let (generic_netlink, worker_params) = GenericNetlink::new();
957            let enable_nl80211 = self.features.wifi;
958            self.kthreads.spawn_future(
959                move || async move {
960                    crate::vfs::socket::run_generic_netlink_worker(worker_params, enable_nl80211)
961                        .await;
962                    log_error!("Generic Netlink future unexpectedly exited");
963                },
964                "generic_netlink_worker",
965            );
966            generic_netlink
967        })
968    }
969
970    /// Return a reference to the [`netlink::Netlink`] implementation.
971    ///
972    /// This function follows the lazy initialization pattern, where the first
973    /// call will instantiate the Netlink implementation.
974    pub fn network_netlink(self: &Arc<Self>) -> &Netlink<NetlinkContextImpl> {
975        self.network_netlink.get_or_init(|| {
976            let (network_netlink, worker_params) =
977                Netlink::new(InterfacesHandlerImpl(self.weak_self.clone()));
978
979            let kernel = self.clone();
980            self.kthreads.spawn_future(
981                move || async move {
982                    netlink::run_netlink_worker(
983                        worker_params,
984                        NetlinkAccessControl::new(kernel.kthreads.system_task()),
985                    )
986                    .await;
987                    log_error!(tag = NETLINK_LOG_TAG; "Netlink async worker unexpectedly exited");
988                },
989                "network_netlink_worker",
990            );
991            network_netlink
992        })
993    }
994
995    /// Return a reference to the token representing our wake group registered
996    /// with the netstack.
997    ///
998    /// This function lazily initializes the wake group with the netstack and
999    /// the wake group as a wake source with the Starnix runner.
1000    pub(crate) fn netstack_wake_group(&self) -> Option<&fnet_resources::WakeGroupToken> {
1001        self.netstack_wake_group
1002            .get_or_init(|| {
1003                // The signal the netstack raises when it wants the container to
1004                // wake up.
1005                const GROUP_WAKEUP_SIGNAL: zx::Signals =
1006                    zx::Signals::from_bits(fnet_power::GROUP_WAKEUP_SIGNAL).unwrap();
1007
1008                let provider = fuchsia_component::client::connect_to_protocol_sync::<
1009                    fnet_power::WakeGroupProviderMarker,
1010                >()
1011                .expect("connect to WakeGroupProvider");
1012
1013                let (wake_watcher_waiter, wake_watcher_signaller) = zx::EventPair::create();
1014                create_watcher_for_wake_events(wake_watcher_signaller);
1015
1016                let token = match provider.create_wake_group(
1017                    &fnet_power::WakeGroupOptions {
1018                        debug_name: Some(String::from("starnix")),
1019                        ..Default::default()
1020                    },
1021                    wake_watcher_waiter,
1022                    zx::Instant::INFINITE,
1023                ) {
1024                    Ok(fnet_power::CreateWakeGroupResponse { token, .. }) => token,
1025                    Err(e) => {
1026                        log_error!("failed to create wake group with the netstack: {e}");
1027                        return None;
1028                    }
1029                };
1030
1031                let token = token.expect("netstack provides a wake group token");
1032                let wake_source = token
1033                    .token
1034                    .duplicate_handle(zx::Rights::SAME_RIGHTS)
1035                    .expect("duplicate handle to token");
1036                self.suspend_resume_manager
1037                    .add_external_wake_source(
1038                        wake_source.into(),
1039                        GROUP_WAKEUP_SIGNAL,
1040                        "netstack-wake-group".into(),
1041                    )
1042                    .expect("add wake group as wake source");
1043
1044                Some(token)
1045            })
1046            .as_ref()
1047    }
1048
1049    pub fn iptables(&self) -> &IpTables {
1050        self.iptables.get_or_init(|| IpTables::new())
1051    }
1052
1053    /// Returns a Proxy to the service used by the container at `filename`.
1054    #[allow(unused)]
1055    pub fn connect_to_named_protocol_at_container_svc<P: ProtocolMarker>(
1056        &self,
1057        filename: &str,
1058    ) -> Result<ClientEnd<P>, Errno> {
1059        match self.container_namespace.get_namespace_channel("/svc") {
1060            Ok(channel) => {
1061                let (client_end, server_end) = create_endpoints::<P>();
1062                fdio::service_connect_at(channel.as_ref(), filename, server_end.into_channel())
1063                    .map_err(|status| from_status_like_fdio!(status))?;
1064                Ok(client_end)
1065            }
1066            Err(err) => {
1067                log_error!("Unable to get /svc namespace channel! {}", err);
1068                Err(errno!(ENOENT))
1069            }
1070        }
1071    }
1072
1073    /// Returns a Proxy to the service `P` used by the container.
1074    pub fn connect_to_protocol_at_container_svc<P: DiscoverableProtocolMarker>(
1075        &self,
1076    ) -> Result<ClientEnd<P>, Errno> {
1077        self.connect_to_named_protocol_at_container_svc::<P>(P::PROTOCOL_NAME)
1078    }
1079
1080    pub fn add_syscall_log_filter(&self, name: &str) {
1081        let filter = SyscallLogFilter::new(name.to_string());
1082        {
1083            let mut filters = self.syscall_log_filters.lock();
1084            if filters.contains(&filter) {
1085                return;
1086            }
1087            filters.push(filter);
1088        }
1089        for headers in self.pids.get_thread_groups(&RcuReadScope::new()) {
1090            headers.sync_syscall_log_level();
1091        }
1092    }
1093
1094    pub fn clear_syscall_log_filters(&self) {
1095        {
1096            let mut filters = self.syscall_log_filters.lock();
1097            if filters.is_empty() {
1098                return;
1099            }
1100            filters.clear();
1101        }
1102        for headers in self.pids.get_thread_groups(&RcuReadScope::new()) {
1103            headers.sync_syscall_log_level();
1104        }
1105    }
1106
1107    fn get_thread_groups_inspect(&self) -> fuchsia_inspect::Inspector {
1108        let inspector = fuchsia_inspect::Inspector::default();
1109
1110        let thread_groups = inspector.root();
1111        let mut mm_summary = MappingSummary::default();
1112        let mut mms_summarized = HashSet::new();
1113
1114        for thread_group in self.pids.get_thread_groups(&RcuReadScope::new()) {
1115            // Avoid holding the state lock while summarizing.
1116            let (ppid, tasks) = {
1117                let tg = thread_group.read();
1118                (tg.get_ppid() as i64, tg.tasks())
1119            };
1120
1121            let tg_node = thread_groups.create_child(format!("{}", thread_group.leader));
1122            if let Ok(koid) = thread_group.process.koid() {
1123                tg_node.record_int("koid", koid.raw_koid() as i64);
1124            }
1125            tg_node.record_int("pid", thread_group.leader.id as i64);
1126            tg_node.record_int("ppid", ppid);
1127            tg_node.record_bool("stopped", thread_group.load_stopped() == StopState::GroupStopped);
1128
1129            let tasks_node = tg_node.create_child("tasks");
1130            for task in tasks {
1131                if let Ok(mm) = task.mm() {
1132                    if mms_summarized.insert(Arc::as_ptr(&mm) as usize) {
1133                        mm.summarize(&mut mm_summary);
1134                    }
1135                }
1136                let set_properties = |node: &fuchsia_inspect::Node| {
1137                    node.record_string("command", task.command().to_string());
1138
1139                    let scheduler_state = task.scheduler_state.lock().clone();
1140                    if !scheduler_state.is_default() {
1141                        node.record_child("sched", |node| {
1142                            node.record_string(
1143                                "role_name",
1144                                self.scheduler
1145                                    .role_name(&task, &scheduler_state)
1146                                    .map(|n| Cow::Borrowed(n))
1147                                    .unwrap_or_else(|e| Cow::Owned(e.to_string())),
1148                            );
1149                            node.record_string("state", format!("{scheduler_state:?}"));
1150                        });
1151                    }
1152                };
1153                if task.tid == thread_group.leader {
1154                    let mut argv = task.read_argv(256).unwrap_or_default();
1155
1156                    // Any runtime that overwrites argv is likely to leave a lot of trailing
1157                    // nulls, no need to print those in inspect.
1158                    argv.retain(|arg| !arg.is_empty());
1159
1160                    let inspect_argv = tg_node.create_string_array("argv", argv.len());
1161                    for (i, arg) in argv.iter().enumerate() {
1162                        inspect_argv.set(i, arg.to_string());
1163                    }
1164                    tg_node.record(inspect_argv);
1165
1166                    set_properties(&tg_node);
1167                } else {
1168                    tasks_node.record_child(task.tid.to_string(), |task_node| {
1169                        set_properties(task_node);
1170                    });
1171                };
1172            }
1173            tg_node.record(tasks_node);
1174            thread_groups.record(tg_node);
1175        }
1176
1177        thread_groups.record_child("memory_managers", |node| mm_summary.record(node));
1178
1179        inspector
1180    }
1181
1182    pub fn new_memory_attribution_observer(
1183        &self,
1184        control_handle: fattribution::ProviderControlHandle,
1185    ) -> attribution_server::Observer {
1186        self.memory_attribution_manager.new_observer(control_handle)
1187    }
1188
1189    /// Opens and returns a directory proxy from the container's namespace, at
1190    /// the requested path, using the provided flags. This method will open the
1191    /// closest existing path from the namespace hierarchy, and then attempt
1192    /// initialize an open on the remaining subdirectory path, using the given open_flags.
1193    ///
1194    /// For example, given the parameter provided is `/path/to/foo/bar` and there
1195    /// are namespace entries already for `/path/to/foo` and `/path/to`. The entry
1196    /// for /path/to/foo will be opened, and then the /bar will attempt to be opened
1197    /// underneath that directory with the given open_flags. The returned value
1198    /// will be the proxy to the parent (/path/to/foo) and the string to the child
1199    /// path (/bar). The caller of this method can expect /bar to be initialized.
1200    pub fn open_ns_dir(
1201        &self,
1202        path: &str,
1203        open_flags: fio::Flags,
1204    ) -> Result<(fio::DirectorySynchronousProxy, String), Errno> {
1205        let ns_path = PathBuf::from(path);
1206        match self.container_namespace.find_closest_channel(&ns_path) {
1207            Ok((root_channel, remaining_subdir)) => {
1208                let (_, server_end) = create_endpoints::<fio::DirectoryMarker>();
1209                fdio::open_at(
1210                    &root_channel,
1211                    &remaining_subdir,
1212                    open_flags,
1213                    server_end.into_channel(),
1214                )
1215                .map_err(|e| {
1216                    log_error!("Failed to intialize the subdirs: {}", e);
1217                    errno!(EIO)
1218                })?;
1219
1220                Ok((fio::DirectorySynchronousProxy::new(root_channel), remaining_subdir))
1221            }
1222            Err(err) => {
1223                log_error!(
1224                    "Unable to find a channel for {}. Received error: {}",
1225                    ns_path.display(),
1226                    err
1227                );
1228                Err(errno!(ENOENT))
1229            }
1230        }
1231    }
1232
1233    /// Returns an iterator of the command line arguments.
1234    pub fn cmdline_args_iter(&self) -> impl Iterator<Item = ArgNameAndValue<'_>> {
1235        parse_cmdline(self.cmdline.to_str().unwrap_or_default()).map(ArgNameAndValue::from)
1236    }
1237
1238    /// Returns the container-configured CacheConfig.
1239    pub fn fs_cache_config(&self) -> CacheConfig {
1240        CacheConfig { capacity: self.features.dirent_cache_size as usize }
1241    }
1242
1243    pub fn mounts_lock(&self) -> MountsWriteGuard<'_> {
1244        MountsWriteGuard::new(self.mounts_lock.lock())
1245    }
1246}
1247
1248pub fn parse_cmdline(cmdline: &str) -> impl Iterator<Item = &str> {
1249    let mut args = Vec::new();
1250    let mut arg_start: Option<usize> = None;
1251    let mut in_quotes = false;
1252    let mut previous_char = ' ';
1253
1254    for (i, c) in cmdline.char_indices() {
1255        if let Some(start) = arg_start {
1256            match c {
1257                ' ' if !in_quotes => {
1258                    args.push(&cmdline[start..i]);
1259                    arg_start = None;
1260                }
1261                '"' if previous_char != '\\' => {
1262                    in_quotes = !in_quotes;
1263                }
1264                _ => {}
1265            }
1266        } else if c != ' ' {
1267            arg_start = Some(i);
1268            if c == '"' {
1269                in_quotes = true;
1270            }
1271        }
1272        previous_char = c;
1273    }
1274    if let Some(start) = arg_start {
1275        args.push(&cmdline[start..]);
1276    }
1277    args.into_iter()
1278}
1279
1280impl std::fmt::Debug for Kernel {
1281    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1282        f.debug_struct("Kernel").finish()
1283    }
1284}
1285
1286// TODO(https://fxbug.dev/380427153): move arch dependent code to `kernel/core/arch/*`.
1287#[cfg(target_arch = "aarch64")]
1288fn arm32_hwcap(cpu_feature_flags: CpuFeatureFlags) -> HwCap {
1289    use starnix_uapi::arch32;
1290    const COMPAT_ARM32_ELF_HWCAP: u32 = arch32::HWCAP_HALF
1291        | arch32::HWCAP_THUMB
1292        | arch32::HWCAP_FAST_MULT
1293        | arch32::HWCAP_EDSP
1294        | arch32::HWCAP_TLS
1295        | arch32::HWCAP_IDIV // == IDIVA | IDIVT.
1296        | arch32::HWCAP_LPAE
1297        | arch32::HWCAP_EVTSTRM;
1298
1299    let mut hwcap = COMPAT_ARM32_ELF_HWCAP;
1300    let mut hwcap2 = 0;
1301    for feature in cpu_feature_flags.iter() {
1302        match feature {
1303            CpuFeatureFlags::ARM64_FEATURE_ISA_ASIMD => hwcap |= arch32::HWCAP_NEON,
1304            CpuFeatureFlags::ARM64_FEATURE_ISA_AES => hwcap2 |= arch32::HWCAP2_AES,
1305            CpuFeatureFlags::ARM64_FEATURE_ISA_PMULL => hwcap2 |= arch32::HWCAP2_PMULL,
1306            CpuFeatureFlags::ARM64_FEATURE_ISA_SHA1 => hwcap2 |= arch32::HWCAP2_SHA1,
1307            CpuFeatureFlags::ARM64_FEATURE_ISA_SHA256 => hwcap2 |= arch32::HWCAP2_SHA2,
1308            CpuFeatureFlags::ARM64_FEATURE_ISA_CRC32 => hwcap2 |= arch32::HWCAP2_CRC32,
1309            CpuFeatureFlags::ARM64_FEATURE_ISA_I8MM => hwcap |= arch32::HWCAP_I8MM,
1310            CpuFeatureFlags::ARM64_FEATURE_ISA_FHM => hwcap |= arch32::HWCAP_ASIMDFHM,
1311            CpuFeatureFlags::ARM64_FEATURE_ISA_DP => hwcap |= arch32::HWCAP_ASIMDDP,
1312            CpuFeatureFlags::ARM64_FEATURE_ISA_FP => {
1313                hwcap |= arch32::HWCAP_VFP | arch32::HWCAP_VFPv3 | arch32::HWCAP_VFPv4
1314            }
1315            _ => {}
1316        }
1317    }
1318    // Both FEAT_FHM and FEAT_SVE architecturally require FEAT_FP16 (which is defined for both
1319    // AArch64 and AArch32 execution states).
1320    // TODO(https://fxbug.dev/42081850): Expose a dedicated `ZX_ARM64_FEATURE_ISA_FP16` flag from
1321    // Zircon so standalone Armv8.2 FEAT_FP16 cores without FHM or SVE also report FP16 hwcaps.
1322    if cpu_feature_flags
1323        .intersects(CpuFeatureFlags::ARM64_FEATURE_ISA_FHM | CpuFeatureFlags::ARM64_FEATURE_ISA_SVE)
1324    {
1325        hwcap |= arch32::HWCAP_FPHP | arch32::HWCAP_ASIMDHP;
1326    }
1327    HwCap { hwcap, hwcap2 }
1328}
1329
1330#[cfg(target_arch = "aarch64")]
1331fn arm64_hwcap(
1332    cpu_feature_flags: CpuFeatureFlags,
1333    cpu_sve_feature_flags: CpuSveFeatureFlags,
1334) -> HwCap {
1335    // See https://docs.kernel.org/arch/arm64/elf_hwcaps.html for details.
1336    use starnix_uapi;
1337    let mut hwcap = 0;
1338    let mut hwcap2 = 0;
1339
1340    for feature in cpu_feature_flags.iter() {
1341        match feature {
1342            CpuFeatureFlags::ARM64_FEATURE_ISA_FP => hwcap |= starnix_uapi::HWCAP_FP,
1343            CpuFeatureFlags::ARM64_FEATURE_ISA_ASIMD => hwcap |= starnix_uapi::HWCAP_ASIMD,
1344            CpuFeatureFlags::ARM64_FEATURE_ISA_AES => hwcap |= starnix_uapi::HWCAP_AES,
1345            CpuFeatureFlags::ARM64_FEATURE_ISA_PMULL => hwcap |= starnix_uapi::HWCAP_PMULL,
1346            CpuFeatureFlags::ARM64_FEATURE_ISA_SHA1 => hwcap |= starnix_uapi::HWCAP_SHA1,
1347            CpuFeatureFlags::ARM64_FEATURE_ISA_SHA256 => hwcap |= starnix_uapi::HWCAP_SHA2,
1348            CpuFeatureFlags::ARM64_FEATURE_ISA_CRC32 => hwcap |= starnix_uapi::HWCAP_CRC32,
1349            CpuFeatureFlags::ARM64_FEATURE_ISA_I8MM => hwcap2 |= starnix_uapi::HWCAP2_I8MM,
1350            CpuFeatureFlags::ARM64_FEATURE_ISA_FHM => hwcap |= starnix_uapi::HWCAP_ASIMDFHM,
1351            CpuFeatureFlags::ARM64_FEATURE_ISA_DP => hwcap |= starnix_uapi::HWCAP_ASIMDDP,
1352            CpuFeatureFlags::ARM64_FEATURE_ISA_SM3 => hwcap |= starnix_uapi::HWCAP_SM3,
1353            CpuFeatureFlags::ARM64_FEATURE_ISA_SM4 => hwcap |= starnix_uapi::HWCAP_SM4,
1354            CpuFeatureFlags::ARM64_FEATURE_ISA_SHA3 => hwcap |= starnix_uapi::HWCAP_SHA3,
1355            CpuFeatureFlags::ARM64_FEATURE_ISA_SHA512 => hwcap |= starnix_uapi::HWCAP_SHA512,
1356            CpuFeatureFlags::ARM64_FEATURE_ISA_ATOMICS => hwcap |= starnix_uapi::HWCAP_ATOMICS,
1357            CpuFeatureFlags::ARM64_FEATURE_ISA_RDM => hwcap |= starnix_uapi::HWCAP_ASIMDRDM,
1358            CpuFeatureFlags::ARM64_FEATURE_ISA_TS => hwcap |= starnix_uapi::HWCAP_FLAGM,
1359            CpuFeatureFlags::ARM64_FEATURE_ISA_DPB => hwcap |= starnix_uapi::HWCAP_DCPOP,
1360            CpuFeatureFlags::ARM64_FEATURE_ISA_RNDR => hwcap2 |= starnix_uapi::HWCAP2_RNG,
1361            CpuFeatureFlags::ARM64_FEATURE_ISA_SVE => hwcap |= starnix_uapi::HWCAP_SVE,
1362            _ => {}
1363        }
1364    }
1365    // Both FEAT_FHM and FEAT_SVE architecturally require FEAT_FP16 (HWCAP_FPHP and HWCAP_ASIMDHP).
1366    // Per ACLE Function Multi-Versioning (FMV) dependency rules (used by LLVM/compiler-rt), "sve"
1367    // and "sve2" targets transitively require FEAT_FP | FEAT_FP16 | FEAT_SVE, where compiler-rt
1368    // derives FEAT_FP16 from HWCAP_FPHP.
1369    // TODO(https://fxbug.dev/42081850): Expose a dedicated `ZX_ARM64_FEATURE_ISA_FP16` flag from
1370    // Zircon so standalone Armv8.2 FEAT_FP16 cores without FHM or SVE also report FP16 hwcaps.
1371    if cpu_feature_flags
1372        .intersects(CpuFeatureFlags::ARM64_FEATURE_ISA_FHM | CpuFeatureFlags::ARM64_FEATURE_ISA_SVE)
1373    {
1374        hwcap |= starnix_uapi::HWCAP_FPHP | starnix_uapi::HWCAP_ASIMDHP;
1375    }
1376    if cpu_feature_flags.contains(CpuFeatureFlags::ARM64_FEATURE_ISA_SVE) {
1377        for feature in cpu_sve_feature_flags.iter() {
1378            match feature {
1379                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEI8MM => {
1380                    hwcap2 |= starnix_uapi::HWCAP2_SVEI8MM
1381                }
1382                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEF32MM => {
1383                    hwcap2 |= starnix_uapi::HWCAP2_SVEF32MM
1384                }
1385                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEF64MM => {
1386                    hwcap2 |= starnix_uapi::HWCAP2_SVEF64MM
1387                }
1388                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEBF16 => {
1389                    hwcap2 |= starnix_uapi::HWCAP2_SVEBF16
1390                }
1391                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVE2 => hwcap2 |= starnix_uapi::HWCAP2_SVE2,
1392                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEAES => {
1393                    hwcap2 |= starnix_uapi::HWCAP2_SVEAES
1394                }
1395                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEPMULL => {
1396                    hwcap2 |= starnix_uapi::HWCAP2_SVEPMULL
1397                }
1398                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEBITPERM => {
1399                    hwcap2 |= starnix_uapi::HWCAP2_SVEBITPERM
1400                }
1401                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVESHA3 => {
1402                    hwcap2 |= starnix_uapi::HWCAP2_SVESHA3
1403                }
1404                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVESM4 => {
1405                    hwcap2 |= starnix_uapi::HWCAP2_SVESM4
1406                }
1407                _ => {}
1408            }
1409        }
1410    }
1411    HwCap { hwcap, hwcap2 }
1412}
1413
1414impl HwCaps {
1415    #[cfg(target_arch = "aarch64")]
1416    pub fn from_cpu_feature_flags(
1417        cpu_feature_flags: CpuFeatureFlags,
1418        cpu_sve_feature_flags: CpuSveFeatureFlags,
1419    ) -> Self {
1420        Self {
1421            arch32: arm32_hwcap(cpu_feature_flags),
1422            arch64: arm64_hwcap(cpu_feature_flags, cpu_sve_feature_flags),
1423        }
1424    }
1425
1426    #[cfg(not(target_arch = "aarch64"))]
1427    pub fn from_cpu_feature_flags(
1428        _cpu_feature_flags: CpuFeatureFlags,
1429        _cpu_sve_feature_flags: CpuSveFeatureFlags,
1430    ) -> Self {
1431        Self { arch64: HwCap::default() }
1432    }
1433}
1434
1435#[cfg(test)]
1436mod test {
1437    use super::*;
1438
1439    #[test]
1440    fn test_parse_cmdline() {
1441        let cmdline =
1442            r#"first second=third "fourth fifth" sixth="seventh eighth" "ninth\" tenth" eleventh"#;
1443        let expected = vec![
1444            "first",
1445            "second=third",
1446            "\"fourth fifth\"",
1447            "sixth=\"seventh eighth\"",
1448            "\"ninth\\\" tenth\"",
1449            "eleventh",
1450        ];
1451        assert_eq!(parse_cmdline(cmdline).collect::<Vec<_>>(), expected);
1452    }
1453
1454    #[cfg(target_arch = "aarch64")]
1455    #[test]
1456    fn test_arm64_hwcap_sve() {
1457        let empty =
1458            HwCaps::from_cpu_feature_flags(CpuFeatureFlags::empty(), CpuSveFeatureFlags::empty());
1459        assert_eq!(empty.arch64.hwcap, 0);
1460        assert_eq!(empty.arch64.hwcap2, 0);
1461
1462        let expected_sve_hwcap =
1463            starnix_uapi::HWCAP_SVE | starnix_uapi::HWCAP_FPHP | starnix_uapi::HWCAP_ASIMDHP;
1464        let sve_only = HwCaps::from_cpu_feature_flags(
1465            CpuFeatureFlags::ARM64_FEATURE_ISA_SVE,
1466            CpuSveFeatureFlags::empty(),
1467        );
1468        assert_eq!(sve_only.arch64.hwcap, expected_sve_hwcap);
1469        assert_eq!(sve_only.arch64.hwcap2, 0);
1470        assert_eq!(
1471            sve_only.arch32.hwcap,
1472            empty.arch32.hwcap
1473                | starnix_uapi::arch32::HWCAP_FPHP
1474                | starnix_uapi::arch32::HWCAP_ASIMDHP
1475        );
1476        assert_eq!(sve_only.arch32.hwcap2, 0);
1477
1478        let fhm_only = HwCaps::from_cpu_feature_flags(
1479            CpuFeatureFlags::ARM64_FEATURE_ISA_FHM,
1480            CpuSveFeatureFlags::empty(),
1481        );
1482        assert_eq!(
1483            fhm_only.arch64.hwcap,
1484            starnix_uapi::HWCAP_ASIMDFHM | starnix_uapi::HWCAP_FPHP | starnix_uapi::HWCAP_ASIMDHP
1485        );
1486        assert_eq!(fhm_only.arch64.hwcap2, 0);
1487        assert_eq!(
1488            fhm_only.arch32.hwcap,
1489            empty.arch32.hwcap
1490                | starnix_uapi::arch32::HWCAP_ASIMDFHM
1491                | starnix_uapi::arch32::HWCAP_FPHP
1492                | starnix_uapi::arch32::HWCAP_ASIMDHP
1493        );
1494        assert_eq!(fhm_only.arch32.hwcap2, 0);
1495
1496        let cases = [
1497            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEI8MM, starnix_uapi::HWCAP2_SVEI8MM),
1498            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEF32MM, starnix_uapi::HWCAP2_SVEF32MM),
1499            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEF64MM, starnix_uapi::HWCAP2_SVEF64MM),
1500            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEBF16, starnix_uapi::HWCAP2_SVEBF16),
1501            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVE2, starnix_uapi::HWCAP2_SVE2),
1502            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEAES, starnix_uapi::HWCAP2_SVEAES),
1503            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEPMULL, starnix_uapi::HWCAP2_SVEPMULL),
1504            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEBITPERM, starnix_uapi::HWCAP2_SVEBITPERM),
1505            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVESHA3, starnix_uapi::HWCAP2_SVESHA3),
1506            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVESM4, starnix_uapi::HWCAP2_SVESM4),
1507        ];
1508
1509        let mut all_sve_flags = CpuSveFeatureFlags::empty();
1510        let mut expected_hwcap2 = 0;
1511        for (flag, hwcap2_bit) in cases {
1512            let without_sve = HwCaps::from_cpu_feature_flags(CpuFeatureFlags::empty(), flag);
1513            assert_eq!(without_sve.arch64.hwcap, 0);
1514            assert_eq!(without_sve.arch64.hwcap2, 0);
1515
1516            let caps = HwCaps::from_cpu_feature_flags(CpuFeatureFlags::ARM64_FEATURE_ISA_SVE, flag);
1517            assert_eq!(caps.arch64.hwcap, expected_sve_hwcap);
1518            assert_eq!(caps.arch64.hwcap2, hwcap2_bit);
1519            all_sve_flags |= flag;
1520            expected_hwcap2 |= hwcap2_bit;
1521        }
1522
1523        let all_caps =
1524            HwCaps::from_cpu_feature_flags(CpuFeatureFlags::ARM64_FEATURE_ISA_SVE, all_sve_flags);
1525        assert_eq!(all_caps.arch64.hwcap, expected_sve_hwcap);
1526        assert_eq!(all_caps.arch64.hwcap2, expected_hwcap2);
1527    }
1528
1529    #[cfg(target_arch = "aarch64")]
1530    #[test]
1531    fn test_arm64_hwcap_sve_gating_and_linux_abi() {
1532        // The HWCAP bit positions are part of the Linux ABI
1533        // (`arch/arm64/include/uapi/asm/hwcap.h` and `arch/arm/include/uapi/asm/hwcap.h`). Pin
1534        // them, so that a wrong uapi binding or a transposed mapping in `arm64_hwcap` /
1535        // `arm32_hwcap` is caught immediately.
1536        assert_eq!(starnix_uapi::HWCAP_FP, 1 << 0);
1537        assert_eq!(starnix_uapi::HWCAP_ASIMD, 1 << 1);
1538        assert_eq!(starnix_uapi::HWCAP_FPHP, 1 << 9);
1539        assert_eq!(starnix_uapi::HWCAP_ASIMDHP, 1 << 10);
1540        assert_eq!(starnix_uapi::HWCAP_SVE, 1 << 22);
1541        assert_eq!(starnix_uapi::HWCAP_ASIMDFHM, 1 << 23);
1542        assert_eq!(starnix_uapi::arch32::HWCAP_FPHP, 1 << 22);
1543        assert_eq!(starnix_uapi::arch32::HWCAP_ASIMDHP, 1 << 23);
1544        assert_eq!(starnix_uapi::arch32::HWCAP_ASIMDFHM, 1 << 25);
1545        let expected_sve_hwcap =
1546            starnix_uapi::HWCAP_SVE | starnix_uapi::HWCAP_FPHP | starnix_uapi::HWCAP_ASIMDHP;
1547        let sve_mappings = [
1548            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVE2, starnix_uapi::HWCAP2_SVE2, 1 << 1),
1549            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEAES, starnix_uapi::HWCAP2_SVEAES, 1 << 2),
1550            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEPMULL, starnix_uapi::HWCAP2_SVEPMULL, 1 << 3),
1551            (
1552                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEBITPERM,
1553                starnix_uapi::HWCAP2_SVEBITPERM,
1554                1 << 4,
1555            ),
1556            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVESHA3, starnix_uapi::HWCAP2_SVESHA3, 1 << 5),
1557            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVESM4, starnix_uapi::HWCAP2_SVESM4, 1 << 6),
1558            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEI8MM, starnix_uapi::HWCAP2_SVEI8MM, 1 << 9),
1559            (
1560                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEF32MM,
1561                starnix_uapi::HWCAP2_SVEF32MM,
1562                1 << 10,
1563            ),
1564            (
1565                CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEF64MM,
1566                starnix_uapi::HWCAP2_SVEF64MM,
1567                1 << 11,
1568            ),
1569            (CpuSveFeatureFlags::ARM64_FEATURE_ISA_SVEBF16, starnix_uapi::HWCAP2_SVEBF16, 1 << 12),
1570        ];
1571        let mut all_sve_flags = CpuSveFeatureFlags::empty();
1572        let mut all_sve_hwcap2 = 0u32;
1573        for (flag, hwcap2_bit, linux_bit) in sve_mappings {
1574            assert_eq!(hwcap2_bit, linux_bit, "{flag:?}");
1575            assert_eq!(all_sve_hwcap2 & hwcap2_bit, 0, "{flag:?} HWCAP2 bit is not unique");
1576            all_sve_flags |= flag;
1577            all_sve_hwcap2 |= hwcap2_bit;
1578        }
1579        // Every named SVE subfeature reported by Zircon has a mapping.
1580        let all_named_sve_flags = <CpuSveFeatureFlags as bitflags::Flags>::FLAGS
1581            .iter()
1582            .filter(|f| f.is_named())
1583            .fold(CpuSveFeatureFlags::empty(), |acc, f| acc | *f.value());
1584        assert_eq!(all_sve_flags, all_named_sve_flags);
1585
1586        // Linux never reports HWCAP2_SVE* without HWCAP_SVE. Without ZX_ARM64_FEATURE_ISA_SVE, the
1587        // SVE subfeatures (all of them at once, or even unknown bits in ZX_FEATURE_KIND_CPU_SVE)
1588        // must be ignored, while unrelated hwcaps are unaffected.
1589        let non_sve_cpu_flags = CpuFeatureFlags::ARM64_FEATURE_ISA_FP
1590            | CpuFeatureFlags::ARM64_FEATURE_ISA_ASIMD
1591            | CpuFeatureFlags::ARM64_FEATURE_ISA_I8MM
1592            | CpuFeatureFlags::ARM64_FEATURE_ISA_RNDR;
1593        let non_sve_hwcap = starnix_uapi::HWCAP_FP | starnix_uapi::HWCAP_ASIMD;
1594        let non_sve_hwcap2 = starnix_uapi::HWCAP2_I8MM | starnix_uapi::HWCAP2_RNG;
1595        let baseline =
1596            HwCaps::from_cpu_feature_flags(non_sve_cpu_flags, CpuSveFeatureFlags::empty());
1597        assert_eq!(baseline.arch64.hwcap, non_sve_hwcap);
1598        assert_eq!(baseline.arch64.hwcap2, non_sve_hwcap2);
1599        for sve_flags in [all_sve_flags, CpuSveFeatureFlags::from_bits_retain(!0)] {
1600            let gated = HwCaps::from_cpu_feature_flags(CpuFeatureFlags::empty(), sve_flags);
1601            assert_eq!(gated.arch64.hwcap, 0, "{sve_flags:?}");
1602            assert_eq!(gated.arch64.hwcap2, 0, "{sve_flags:?}");
1603
1604            let gated = HwCaps::from_cpu_feature_flags(non_sve_cpu_flags, sve_flags);
1605            assert_eq!(gated.arch64.hwcap, non_sve_hwcap, "{sve_flags:?}");
1606            assert_eq!(gated.arch64.hwcap & starnix_uapi::HWCAP_SVE, 0, "{sve_flags:?}");
1607            assert_eq!(gated.arch64.hwcap2, non_sve_hwcap2, "{sve_flags:?}");
1608            assert_eq!(gated.arch64.hwcap2 & all_sve_hwcap2, 0, "{sve_flags:?}");
1609            // The 32-bit (compat) hwcaps never include SVE.
1610            assert_eq!(gated.arch32.hwcap, baseline.arch32.hwcap);
1611            assert_eq!(gated.arch32.hwcap2, baseline.arch32.hwcap2);
1612
1613            // With ZX_ARM64_FEATURE_ISA_SVE, exactly the known subfeature bits are added on top of
1614            // the unrelated hwcaps, and unknown bits in ZX_FEATURE_KIND_CPU_SVE are still ignored.
1615            let enabled = HwCaps::from_cpu_feature_flags(
1616                non_sve_cpu_flags | CpuFeatureFlags::ARM64_FEATURE_ISA_SVE,
1617                sve_flags,
1618            );
1619            assert_eq!(enabled.arch64.hwcap, non_sve_hwcap | expected_sve_hwcap);
1620            assert_eq!(enabled.arch64.hwcap2, non_sve_hwcap2 | all_sve_hwcap2);
1621            assert_eq!(
1622                enabled.arch32.hwcap,
1623                baseline.arch32.hwcap
1624                    | starnix_uapi::arch32::HWCAP_FPHP
1625                    | starnix_uapi::arch32::HWCAP_ASIMDHP
1626            );
1627            assert_eq!(enabled.arch32.hwcap2, baseline.arch32.hwcap2);
1628        }
1629
1630        // Leaving out a single subfeature clears exactly its HWCAP2 bit, which catches duplicated
1631        // or transposed mappings that `test_arm64_hwcap_sve`'s single-flag checks would not.
1632        for (flag, hwcap2_bit, _) in sve_mappings {
1633            let all_but_one = HwCaps::from_cpu_feature_flags(
1634                CpuFeatureFlags::ARM64_FEATURE_ISA_SVE,
1635                all_sve_flags.difference(flag),
1636            );
1637            assert_eq!(all_but_one.arch64.hwcap, expected_sve_hwcap, "{flag:?}");
1638            assert_eq!(all_but_one.arch64.hwcap2, all_sve_hwcap2 & !hwcap2_bit, "{flag:?}");
1639        }
1640    }
1641}