blob: de9624428bb0095cf1836d518650547cfe221c10 [file]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::ContainerStartInfo;
use anyhow::{Context, Error, anyhow};
use fidl_fuchsia_ui_composition as fuicomposition;
use fidl_fuchsia_ui_input3 as fuiinput;
use fidl_fuchsia_ui_policy as fuipolicy;
use fidl_fuchsia_ui_views as fuiviews;
use starnix_consent_sync::init as consent_sync_init;
use starnix_container_structured_config::Config as ContainerStructuredConfig;
use starnix_core::device::block::add_mmc_block_device;
use starnix_core::mm::MlockPinFlavor;
use starnix_core::task::{Kernel, KernelFeatures, SystemLimits, ThreadLockupDetector};
use starnix_core::vfs::FsString;
use starnix_features::Feature;
use starnix_logging::log_error;
use starnix_modules_android_usb::usb_device_init;
use starnix_modules_ashmem::ashmem_device_init;
use starnix_modules_boot::booted_device_init;
use starnix_modules_fastrpc::fastrpc_device_init;
use starnix_modules_framebuffer::{AspectRatio, Framebuffer};
use starnix_modules_gpu::gpu_device_init;
use starnix_modules_gralloc::gralloc_device_init;
use starnix_modules_hvdcp_opti::hvdcp_opti_init;
use starnix_modules_input::uinput::register_uinput_device;
use starnix_modules_input::{
DEFAULT_KEYBOARD_DEVICE_ID, DEFAULT_MOUSE_DEVICE_ID, DEFAULT_TOUCH_DEVICE_ID, EventProxyMode,
InputDevice, new_input_relay,
};
use starnix_modules_kgsl::kgsl_device_init;
use starnix_modules_magma::magma_device_init;
use starnix_modules_nanohub::nanohub_device_init;
use starnix_modules_nmfs::nmfs_init;
use starnix_modules_perfetto_consumer::start_perfetto_consumer_thread;
use starnix_modules_thermal::{cooling_device_init, thermal_device_init};
use starnix_modules_touch_power_policy::TouchPowerPolicyDevice;
use starnix_modules_wakeup_test::register_wakeup_test_device;
use std::sync::Arc;
use starnix_uapi::error;
use starnix_uapi::errors::Errno;
/// A collection of parsed features, and their arguments.
#[derive(Default, Debug)]
pub struct Features {
/// Features that must be available to the kernel after initialization.
pub kernel: KernelFeatures,
/// SELinux configuration.
pub selinux: SELinuxFeature,
/// Limits value passed to the kernel during the initialization.
pub system_limits: SystemLimits,
/// Whether to enable ashmem.
pub ashmem: bool,
/// Whether to enable a boot notifier device.
pub boot_notifier: bool,
/// Whether to enable consent sync.
pub data_collection_consent_sync: bool,
/// Whether to boost CPU for a fixed duration.
pub boot_notifier_cpu_boost: Option<zx::MonotonicDuration>,
/// Whether to enable a framebuffer device.
pub framebuffer: bool,
/// Display aspect ratio.
pub aspect_ratio: Option<AspectRatio>,
/// This controls whether or not the default framebuffer background is black or colorful, to
/// aid debugging.
pub enable_visual_debugging: bool,
/// The value of the initial view ID annotation.
pub initial_view_id_annotation: String,
/// Whether to enable gralloc.
pub gralloc: bool,
/// Whether to enable the Kernel Graphics Support Layer (kgsl) device used by Adreno GPUs.
pub kgsl: bool,
/// Supported magma vendor IDs.
pub magma_supported_vendors: Option<Vec<u16>>,
/// Whether to enable gfxstream.
pub gfxstream: bool,
/// Include the /container directory in the root file system.
pub container: bool,
/// Include the /test_data directory in the root file system.
pub test_data: bool,
/// Include the /custom_artifacts directory in the root file system.
pub custom_artifacts: bool,
/// Whether to provide android with a serial number.
pub android_serialno: bool,
/// Optional perfetto configuration.
pub perfetto: Option<FsString>,
/// Whether to allow the root filesystem to be read/write.
pub rootfs_rw: bool,
/// Whether to enable network manager and its filesystem.
pub network_manager: bool,
/// Whether to enable the nanohub module.
pub nanohub: bool,
/// Whether to enable the fastrpc module.
pub fastrpc: bool,
pub enable_utc_time_adjustment: bool,
pub thermal: bool,
/// Optional cooling features to enable. See [`cooling_device_init`] for a list of possible
/// values and their parameters.
pub cooling: Option<Vec<String>>,
/// Whether to add android bootreason to kernel cmdline.
pub android_bootreason: bool,
pub hvdcp_opti: bool,
pub additional_mounts: Option<Vec<String>>,
/// Support kernel level suspend/resume tests.
pub wakeup_test: bool,
/// Add a stub block device entry for mmcblk0. This is required for certain containers that
/// query certain functionality based on block device naming.
pub mmcblk_stub: bool,
/// Whether to initialize Android-compatible USB monitoring sysfs logic.
pub android_usb: bool,
}
#[derive(Default, Debug, PartialEq)]
pub struct SELinuxFeature {
/// True if SELinux should be enabled in the container.
pub enabled: bool,
/// Optional set of options to pass to the SELinux module.
pub options: String,
/// Optional set of access-check exceptions to pass to the SELinux module.
pub exceptions: Vec<String>,
}
impl Features {
pub fn record_inspect(&self, parent_node: &fuchsia_inspect::Node) {
parent_node.record_child("features", |inspect_node| match self {
Features {
kernel:
KernelFeatures {
bpf_v2,
enable_suid,
io_uring,
error_on_failed_reboot,
default_uid,
default_seclabel,
selinux_test_suite,
default_ns_mount_options,
mlock_always_onfault,
mlock_pin_flavor,
crash_report_throttling,
wifi,
cached_zx_map_info_bytes,
dirent_cache_size,
fake_ion,
},
system_limits,
selinux,
ashmem,
boot_notifier,
boot_notifier_cpu_boost,
data_collection_consent_sync,
framebuffer,
aspect_ratio,
enable_visual_debugging,
gralloc,
kgsl,
magma_supported_vendors,
gfxstream,
container,
test_data,
custom_artifacts,
android_serialno,
perfetto,
rootfs_rw,
network_manager,
nanohub,
fastrpc,
enable_utc_time_adjustment,
thermal,
cooling,
android_bootreason,
hvdcp_opti,
additional_mounts,
wakeup_test,
mmcblk_stub,
android_usb,
initial_view_id_annotation,
} => {
inspect_node.record_bool("selinux", selinux.enabled);
inspect_node.record_bool("ashmem", *ashmem);
inspect_node.record_bool("boot_notifier", *boot_notifier);
inspect_node.record_string(
"initial_view_id_annotation",
initial_view_id_annotation.as_str(),
);
inspect_node
.record_bool("data_collection_consent_sync", *data_collection_consent_sync);
inspect_node.record_string(
"boot_notifier_cpu_boost",
boot_notifier_cpu_boost
.map(|d| format!("{}s", d.into_seconds()))
.unwrap_or_else(|| "none".to_string()),
);
inspect_node.record_bool("framebuffer", *framebuffer);
inspect_node.record_bool("gralloc", *gralloc);
inspect_node.record_bool("kgsl", *kgsl);
inspect_node.record_string(
"magma_supported_vendors",
match magma_supported_vendors {
Some(vendors) => vendors
.iter()
.map(|vendor| format!("0x{:x}", vendor))
.collect::<Vec<String>>()
.join(","),
None => "".to_string(),
},
);
inspect_node.record_bool("gfxstream", *gfxstream);
inspect_node.record_bool("container", *container);
inspect_node.record_bool("test_data", *test_data);
inspect_node.record_bool("custom_artifacts", *custom_artifacts);
inspect_node.record_bool("android_serialno", *android_serialno);
inspect_node.record_string(
"aspect_ratio",
aspect_ratio
.as_ref()
.map(|aspect_ratio| {
format!("width: {} height: {}", aspect_ratio.width, aspect_ratio.height)
})
.unwrap_or_default(),
);
inspect_node.record_string(
"perfetto",
perfetto.as_ref().map(|p| p.to_string()).unwrap_or_default(),
);
inspect_node.record_bool("rootfs_rw", *rootfs_rw);
inspect_node.record_bool("network_manager", *network_manager);
inspect_node.record_bool("nanohub", *nanohub);
inspect_node.record_bool("fastrpc", *fastrpc);
inspect_node.record_bool("thermal", *thermal);
inspect_node.record_string(
"cooling",
match cooling {
Some(devices) => devices.join(","),
None => "".to_string(),
},
);
inspect_node.record_bool("android_bootreason", *android_bootreason);
inspect_node.record_bool("hvdcp_opti", *hvdcp_opti);
inspect_node.record_string("ping_group_range", {
let range = system_limits.socket.icmp_ping_gids.lock();
std::format!("{},{}", range.start, range.end - 1)
});
inspect_node.record_child("kernel", |kernel_node| {
kernel_node.record_bool("bpf_v2", *bpf_v2);
kernel_node.record_bool("enable_suid", *enable_suid);
kernel_node.record_bool("io_uring", *io_uring);
kernel_node.record_bool("error_on_failed_reboot", *error_on_failed_reboot);
kernel_node.record_bool("enable_visual_debugging", *enable_visual_debugging);
kernel_node.record_int("default_uid", (*default_uid).into());
kernel_node.record_string(
"default_seclabel",
default_seclabel.as_deref().unwrap_or_default(),
);
kernel_node.record_bool("selinux_test_suite", *selinux_test_suite);
kernel_node.record_bool("crash_report_throttling", *crash_report_throttling);
kernel_node
.record_uint("cached_zx_map_info_bytes", *cached_zx_map_info_bytes as u64);
inspect_node.record_string(
"default_ns_mount_options",
format!("{:?}", default_ns_mount_options),
);
inspect_node
.record_bool("enable_utc_time_adjustment", *enable_utc_time_adjustment);
inspect_node.record_bool("mlock_always_onfault", *mlock_always_onfault);
inspect_node
.record_string("mlock_pin_flavor", format!("{:?}", mlock_pin_flavor));
inspect_node.record_bool("wifi", *wifi);
inspect_node
.record_string("additional_mounts", format!("{:?}", additional_mounts));
inspect_node.record_uint("dirent_cache_size", *dirent_cache_size as u64);
inspect_node.record_bool("wakeup_test", *wakeup_test);
inspect_node.record_bool("mmcblk_stub", *mmcblk_stub);
inspect_node.record_bool("android_usb", *android_usb);
inspect_node.record_bool("fake_ion", *fake_ion);
});
}
});
}
}
/// Parses all the featurse in `entries`.
///
/// Returns an error if parsing fails, or if an unsupported feature is present in `features`.
pub fn parse_features(
start_info: &ContainerStartInfo,
kernel_extra_features: &[String],
) -> Result<Features, Error> {
let ContainerStructuredConfig {
crash_report_throttling,
enable_utc_time_adjustment,
extra_features,
cached_zx_map_info_bytes,
mlock_always_onfault,
mlock_pin_flavor,
selinux_exceptions,
ui_visual_debugging_level,
additional_mounts,
dirent_cache_size,
initial_view_id_annotation,
} = &start_info.config;
let mut features = Features::default();
for entry in start_info
.program
.features
.iter()
.chain(kernel_extra_features.iter())
.chain(extra_features.iter())
{
let (feature, raw_args) = Feature::try_parse_feature_and_args(entry)?;
match (feature, raw_args) {
(Feature::AndroidSerialno, _) => features.android_serialno = true,
(Feature::AndroidBootreason, _) => features.android_bootreason = true,
(Feature::AspectRatio, Some(args)) => {
let e = anyhow!("Invalid aspect_ratio: {:?}", args);
let components: Vec<_> = args.split(':').collect();
if components.len() != 2 {
return Err(e);
}
let width: u32 =
components[0].parse().map_err(|_| anyhow!("Invalid aspect ratio width"))?;
let height: u32 =
components[1].parse().map_err(|_| anyhow!("Invalid aspect ratio height"))?;
features.aspect_ratio = Some(AspectRatio { width, height });
}
(Feature::AspectRatio, None) => {
return Err(anyhow!(
"Aspect ratio feature must contain the aspect ratio in the format: aspect_ratio:w:h"
));
}
(Feature::Container, _) => features.container = true,
(Feature::CustomArtifacts, _) => features.custom_artifacts = true,
(Feature::Ashmem, _) => features.ashmem = true,
(Feature::BootNotifier, _) => features.boot_notifier = true,
(Feature::DataCollectionConsentSync, _) => features.data_collection_consent_sync = true,
(Feature::BootNotifierCpuBoost, Some(arg)) => {
let duration = zx::MonotonicDuration::from_seconds(arg.parse::<i64>()?);
features.boot_notifier_cpu_boost = Some(duration);
}
(Feature::BootNotifierCpuBoost, None) => {
return Err(anyhow!(
"boot_notifier_cpu_boost feature must have an argument (e.g. \"boot_notifier_cpu_boost:60\")"
));
}
(Feature::Framebuffer, _) => features.framebuffer = true,
(Feature::Gralloc, _) => features.gralloc = true,
(Feature::Kgsl, _) => features.kgsl = true,
(Feature::Magma, _) => {
if features.magma_supported_vendors.is_none() {
const VENDOR_ARM: u16 = 0x13B5;
const VENDOR_INTEL: u16 = 0x8086;
features.magma_supported_vendors = Some(vec![VENDOR_ARM, VENDOR_INTEL])
}
}
(Feature::MagmaSupportedVendors, Some(arg)) => {
features.magma_supported_vendors = Some(
arg.split(',')
.map(|s| {
let err = anyhow!(
"Feature format must be: magma_supported_vendors:0x1234[,0xabcd]"
);
let trimmed = s.trim_start_matches("0x");
u16::from_str_radix(trimmed, 16).map_err(|_| err)
})
.collect::<Result<Vec<u16>, Error>>()?,
);
}
(Feature::MagmaSupportedVendors, None) => {
return Err(anyhow!(
"Feature format must be: magma_supported_vendors:0x1234[,0xabcd]"
));
}
(Feature::Nanohub, _) => features.nanohub = true,
(Feature::Fastrpc, _) => features.fastrpc = true,
(Feature::NetworkManager, _) => features.network_manager = true,
(Feature::Gfxstream, _) => features.gfxstream = true,
(Feature::Bpf, Some(version)) => features.kernel.bpf_v2 = version == "v2",
(Feature::Bpf, None) => {
return Err(anyhow!("bpf feature must have an argument (e.g. \"bpf:v2\")"));
}
(Feature::EnableSuid, _) => features.kernel.enable_suid = true,
(Feature::IoUring, _) => features.kernel.io_uring = true,
(Feature::ErrorOnFailedReboot, _) => features.kernel.error_on_failed_reboot = true,
(Feature::Perfetto, Some(socket_path)) => {
features.perfetto = Some(socket_path.into());
}
(Feature::Perfetto, None) => {
return Err(anyhow!("Perfetto feature must contain a socket path"));
}
(Feature::PingGroupRange, Some(arg)) => {
let mut args = arg.split(',');
let (min, max) = (|| {
let min = args.next()?.trim_ascii().parse::<u32>().ok()?;
let max = args.next()?.trim_ascii().parse::<u32>().ok()?.checked_add(1)?;
if args.next().is_some() {
return None;
}
Some((min, max))
})()
.ok_or_else(|| anyhow!("Feature format must be: ping_group_range:0,100"))?;
*features.system_limits.socket.icmp_ping_gids.lock() = min..max;
}
(Feature::PingGroupRange, None) => {
return Err(anyhow!("Feature format must be: ping_group_range:0,100"));
}
(Feature::RootfsRw, _) => features.rootfs_rw = true,
(Feature::Selinux, arg) => {
features.selinux = SELinuxFeature {
enabled: true,
options: arg.unwrap_or_default(),
exceptions: selinux_exceptions.clone(),
};
}
(Feature::SelinuxTestSuite, _) => features.kernel.selinux_test_suite = true,
(Feature::TestData, _) => features.test_data = true,
(Feature::Thermal, _) => features.thermal = true,
(Feature::Cooling, Some(arg)) => {
features.cooling = Some(arg.split(',').map(String::from).collect::<Vec<String>>())
}
(Feature::Cooling, None) => {
return Err(anyhow!("cooling feature must have an argument"));
}
(Feature::HvdcpOpti, _) => features.hvdcp_opti = true,
(Feature::Wifi, _) => features.kernel.wifi = true,
(Feature::AdditionalMounts, _) => {
features.additional_mounts = Some(additional_mounts.clone())
}
(Feature::WakeupTest, _) => features.wakeup_test = true,
(Feature::MmcblkStub, _) => features.mmcblk_stub = true,
(Feature::FakeIon, _) => features.kernel.fake_ion = true,
(Feature::AndroidUsb, _) => features.android_usb = true,
};
}
if features.boot_notifier_cpu_boost.is_some() && !features.boot_notifier {
return Err(anyhow!("boot_notifier_cpu_boost feature requires boot_notifier"));
}
if *ui_visual_debugging_level > 0 {
features.enable_visual_debugging = true;
}
features.enable_utc_time_adjustment = *enable_utc_time_adjustment;
features.kernel.default_uid = start_info.program.default_uid.0;
features.kernel.default_seclabel = start_info.program.default_seclabel.clone();
features.kernel.default_ns_mount_options =
if let Some(mount_options) = &start_info.program.default_ns_mount_options {
let options = mount_options
.iter()
.map(|item| {
let mut splitter = item.splitn(2, ":");
let key = splitter.next().expect("Failed to parse mount options");
let value = splitter.next().expect("Failed to parse mount options");
(key.to_string(), value.to_string())
})
.collect();
Some(options)
} else {
None
};
features.kernel.mlock_always_onfault = *mlock_always_onfault;
features.kernel.mlock_pin_flavor = MlockPinFlavor::parse(mlock_pin_flavor.as_str())?;
features.kernel.crash_report_throttling = *crash_report_throttling;
features.kernel.cached_zx_map_info_bytes = *cached_zx_map_info_bytes;
features.kernel.dirent_cache_size = *dirent_cache_size;
features.initial_view_id_annotation = initial_view_id_annotation.clone();
Ok(features)
}
/// Runs all the features that are enabled in `system_task.kernel()`.
pub fn run_container_features(kernel: &Arc<Kernel>, features: &Features) -> Result<(), Error> {
if features.framebuffer {
let framebuffer = Framebuffer::device_init(
kernel,
features.aspect_ratio,
features.enable_visual_debugging,
features.initial_view_id_annotation.clone(),
)
.context("initializing framebuffer")?;
let (touch_source_client, touch_source_server) = fidl::endpoints::create_endpoints();
let (mouse_source_client, mouse_source_server) = fidl::endpoints::create_endpoints();
let view_bound_protocols = fuicomposition::ViewBoundProtocols {
touch_source: Some(touch_source_server),
mouse_source: Some(mouse_source_server),
..Default::default()
};
let view_identity = fuiviews::ViewIdentityOnCreation::from(
fuchsia_scenic::ViewRefPair::new().expect("Failed to create ViewRefPair"),
);
let view_ref = fuchsia_scenic::duplicate_view_ref(&view_identity.view_ref)
.expect("Failed to dup view ref.");
let keyboard =
fuchsia_component::client::connect_to_protocol_sync::<fuiinput::KeyboardMarker>()
.expect("Failed to connect to keyboard");
let registry_proxy = fuchsia_component::client::connect_to_protocol_sync::<
fuipolicy::DeviceListenerRegistryMarker,
>()
.expect("Failed to connect to device listener registry");
// These need to be set before `Framebuffer::start_server` is called.
// `Framebuffer::start_server` is only called when the `framebuffer` component feature is
// enabled. The container is the runner for said components, and `run_container_features`
// is performed before the Container is fully initialized. Therefore, it's safe to set
// these values at this point.
//
// In the future, we would like to avoid initializing a framebuffer unconditionally on the
// Kernel, at which point this logic will need to change.
*framebuffer.view_identity.lock() = Some(view_identity);
*framebuffer.view_bound_protocols.lock() = Some(view_bound_protocols);
let (display_width, display_height) = {
let framebuffer_info = framebuffer.info.read();
(framebuffer_info.xres as i32, framebuffer_info.yres as i32)
};
let touch_device =
InputDevice::new_touch(display_width, display_height, &kernel.inspect_node);
let keyboard_device = InputDevice::new_keyboard(&kernel.inspect_node);
let mouse_device = InputDevice::new_mouse(&kernel.inspect_node);
touch_device.clone().register(kernel, DEFAULT_TOUCH_DEVICE_ID)?;
keyboard_device.clone().register(kernel, DEFAULT_KEYBOARD_DEVICE_ID)?;
mouse_device.clone().register(kernel, DEFAULT_MOUSE_DEVICE_ID)?;
let (input_events_relay, input_events_relay_handle) = new_input_relay();
input_events_relay.start_relays(
&kernel,
EventProxyMode::WakeContainer,
touch_source_client,
keyboard,
mouse_source_client,
view_ref,
registry_proxy,
touch_device.open_files.clone(),
keyboard_device.open_files.clone(),
mouse_device.open_files.clone(),
Some(touch_device.inspect_status),
Some(keyboard_device.inspect_status),
Some(mouse_device.inspect_status),
);
register_uinput_device(kernel, input_events_relay_handle)?;
// Channel we use to inform the relay of changes to `touch_standby`
let (touch_standby_sender, touch_standby_receiver) =
ThreadLockupDetector::tracked_channel::<bool>();
let touch_policy_device = TouchPowerPolicyDevice::new(touch_standby_sender);
touch_policy_device.clone().register(kernel);
touch_policy_device.start_relay(&kernel, touch_standby_receiver);
framebuffer.start_server(kernel, None);
}
if features.gralloc {
// The virtgralloc0 device allows vulkan_selector to indicate to gralloc
// whether swiftshader or magma will be used. This is separate from the
// magma feature because the policy choice whether to use magma or
// swiftshader is in vulkan_selector, and it can potentially choose
// switfshader for testing purposes even when magma0 is present. Also,
// it's nice to indicate swiftshader the same way regardless of whether
// the magma feature is enabled or disabled. If a call to gralloc AIDL
// IAllocator allocate2 occurs with this feature disabled, the call will
// fail.
gralloc_device_init(kernel);
}
if features.kgsl {
kgsl_device_init(kernel);
}
if let Some(supported_vendors) = &features.magma_supported_vendors {
magma_device_init(kernel, supported_vendors.clone());
}
if features.gfxstream {
gpu_device_init(kernel);
}
if let Some(socket_path) = features.perfetto.clone() {
start_perfetto_consumer_thread(kernel, socket_path)
.context("Failed to start perfetto consumer thread")?;
}
if features.ashmem {
ashmem_device_init(kernel);
}
if features.boot_notifier {
booted_device_init(kernel, features.boot_notifier_cpu_boost);
}
if features.data_collection_consent_sync {
consent_sync_init(kernel);
}
if features.network_manager {
if let Err(e) = nmfs_init(kernel) {
log_error!("Network manager initialization failed: ({e:?})");
}
}
if features.nanohub {
nanohub_device_init(kernel);
}
if features.thermal {
thermal_device_init(kernel)?;
}
if let Some(devices) = &features.cooling {
cooling_device_init(kernel, devices.clone())?;
}
if features.hvdcp_opti {
hvdcp_opti_init(kernel)?;
}
if features.fastrpc {
fastrpc_device_init(kernel);
}
if features.wakeup_test {
register_wakeup_test_device(kernel)?;
}
if features.mmcblk_stub {
let _device = add_mmc_block_device(kernel).context("Failed to add stub mmcblk0 device")?;
}
if features.android_usb {
usb_device_init(kernel).context("Failed to add android usb device nodes")?;
}
Ok(())
}
/// Runs features requested by individual components inside the container.
pub fn run_component_features(
kernel: &Kernel,
entries: &Vec<String>,
mut incoming_dir: Option<fidl_fuchsia_io::DirectoryProxy>,
) -> Result<(), Errno> {
for entry in entries {
match entry.as_str() {
"framebuffer" => {
Framebuffer::get(kernel)?.start_server(kernel, incoming_dir.take());
}
feature => {
return error!(ENOSYS, format!("Unsupported feature: {}", feature));
}
}
}
Ok(())
}