Files
RedBear-OS/local/patches/base/P5-i2c-hidd-estale-retry.patch
T
vasilito f6c2eb2a8e feat: ACPI Wave 1 boot-critical hardening (P19) + robust patch generation
- P19-init-startup-hardening: Replace panic-grade expect/unwrap in init
  startup paths (getns, register_scheme_to_ns, setrens, filename parsing)
  with graceful error handling and logging
- P19-acpid-startup-hardening: Replace panic-grade calls in acpid with
  graceful degradation (rxsdt read failure → warn + exit 0, SDT parse →
  error + exit 1, I/O privilege → fatal, scheme registration → fatal,
  setrens → warn + continue, event loop errors → log + continue)
- P18-9-msi-allocation-resilience: Regenerate with git diff -U0 -w format
  for maximum context resilience
- fetch.rs: Change --fuzz=0 to --fuzz=3 for resilient patch application
- AGENTS.md: Document robust patch generation technique as mandatory
- Add P4/P5/P6/P7 patches (estale, dmi, i2c, ps2d hardening)
- Add P21 kernel x2apic SMP fix patch
- Multiple local recipe source improvements (redox-drm, driver-manager,
  driver-acpi, thermald)
- Config updates for redbear-mini and redbear-device-services
- Subsystem assessment document
2026-05-18 14:07:42 +03:00

409 lines
14 KiB
Diff

diff --git a/drivers/input/i2c-hidd/src/main.rs b/drivers/input/i2c-hidd/src/main.rs
new file mode 100644
index 00000000..88270e37
--- /dev/null
+++ b/drivers/input/i2c-hidd/src/main.rs
@@ -0,0 +1,114 @@
+use std::process;
+use std::thread;
+use std::time::Duration;
+
+use anyhow::{Context, Result};
+
+mod acpi;
+mod hid;
+mod input;
+mod quirks;
+
+use acpi::{
+ hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device,
+ scan_acpi_i2c_hid_devices,
+};
+use hid::{fetch_hid_descriptor, fetch_report_descriptor, stream_input_reports, I2cAdapterClient};
+use input::InputForwarder;
+use quirks::match_probe_failure_quirk;
+
+fn main() {
+ daemon::Daemon::new(daemon);
+}
+
+fn daemon(daemon: daemon::Daemon) -> ! {
+ common::setup_logging(
+ "input",
+ "i2c-hid",
+ "i2c-hidd",
+ common::output_level(),
+ common::file_level(),
+ );
+
+ if let Err(err) = run(daemon) {
+ log::error!("RB_I2C_HIDD_BLOCKER stage=startup error={err:#}");
+ process::exit(1);
+ }
+
+ process::exit(0);
+}
+
+fn run(daemon: daemon::Daemon) -> Result<()> {
+ log::info!("RB_I2C_HIDD_SCHEMA version=1");
+
+ let devices = scan_acpi_i2c_hid_devices().context("failed to scan ACPI I2C HID devices")?;
+ if devices.is_empty() {
+ log::warn!("RB_I2C_HIDD_BLOCKER stage=scan error=no PNP0C50/ACPI0C50 devices found");
+ }
+
+ let mut workers = Vec::new();
+ for device in devices {
+ log::info!("RB_I2C_HIDD_SNAPSHOT device={device}");
+ workers.push(thread::spawn(move || {
+ if let Err(err) = bind_device(&device) {
+ log::error!("RB_I2C_HIDD_BLOCKER device={} error={:#}", device, err);
+ }
+ }));
+ }
+
+ daemon.ready();
+
+ if workers.is_empty() {
+ loop {
+ thread::sleep(Duration::from_secs(5));
+ }
+ }
+
+ for worker in workers {
+ let _ = worker.join();
+ }
+ Ok(())
+}
+
+pub fn bind_device(device_path: &str) -> Result<()> {
+ prepare_acpi_device(device_path)
+ .with_context(|| format!("failed to prepare ACPI device {device_path}"))?;
+
+ let resources = read_decoded_resources(device_path)
+ .with_context(|| format!("failed to decode _CRS for {device_path}"))?;
+ log::info!(
+ "RB_I2C_HIDD_SNAPSHOT device={} adapter={} addr={:04x} irq={:?} gpio_int={} gpio_io={}",
+ device_path,
+ resources.i2c.adapter,
+ resources.i2c.address,
+ resources.irq,
+ resources.gpio_int.len(),
+ resources.gpio_io.len()
+ );
+
+ let hid_desc_addr = hid_descriptor_address(device_path)
+ .with_context(|| format!("failed to evaluate _DSM for {device_path}"))?;
+ let adapter = I2cAdapterClient::new(resources.i2c.clone());
+ let hid_desc = fetch_hid_descriptor(&adapter, resources.i2c.address, hid_desc_addr)
+ .with_context(|| format!("failed to fetch HID descriptor for {device_path}"))?;
+ let report_desc = fetch_report_descriptor(&adapter, resources.i2c.address, &hid_desc)
+ .with_context(|| format!("failed to fetch report descriptor for {device_path}"))?;
+ let mut forwarder = InputForwarder::new().context("failed to connect to inputd producer")?;
+
+ match stream_input_reports(
+ &adapter,
+ resources.i2c.address,
+ &hid_desc,
+ &report_desc,
+ &mut forwarder,
+ ) {
+ Ok(()) => Ok(()),
+ Err(err) => {
+ let quirk =
+ match_probe_failure_quirk().context("failed to evaluate DMI recovery quirks")?;
+ recover_acpi_device(device_path, &resources, quirk.as_ref())
+ .with_context(|| format!("failed ACPI recovery for {device_path}"))?;
+ Err(err).with_context(|| format!("streaming input reports failed for {device_path}"))
+ }
+ }
+}
diff --git a/drivers/input/intel-thc-hidd/src/main.rs b/drivers/input/intel-thc-hidd/src/main.rs
new file mode 100644
index 00000000..c5cda29e
--- /dev/null
+++ b/drivers/input/intel-thc-hidd/src/main.rs
@@ -0,0 +1,282 @@
+use std::collections::BTreeSet;
+use std::fs::{self, OpenOptions};
+use std::io::Read;
+use std::process;
+use std::thread;
+use std::time::Duration;
+
+use acpi_resource::ResourceDescriptor;
+use amlserde::{AmlSerde, AmlSerdeValue};
+use anyhow::{bail, Context, Result};
+use libredox::flag::{O_CLOEXEC, O_RDWR};
+use pcid_interface::PciFunctionHandle;
+
+mod quicki2c;
+mod thc;
+
+use quicki2c::QuickI2cTransport;
+use thc::{ThcController, SUPPORTED_PCI_IDS};
+
+fn main() {
+ pcid_interface::pci_daemon(daemon);
+}
+
+fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
+ common::setup_logging(
+ "input",
+ "intel-thc",
+ "intel-thc-hidd",
+ common::output_level(),
+ common::file_level(),
+ );
+
+ if let Err(err) = run(daemon, &mut pcid_handle) {
+ log::error!("RB_THC_HIDD_FATAL error={err:#}");
+ process::exit(1);
+ }
+
+ process::exit(0);
+}
+
+fn run(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> {
+ log::info!("RB_THC_HIDD_SCHEMA version=1");
+
+ let pci_config = pcid_handle.config();
+ let id = (
+ pci_config.func.full_device_id.vendor_id,
+ pci_config.func.full_device_id.device_id,
+ );
+ if !SUPPORTED_PCI_IDS.contains(&id) {
+ bail!("unsupported Intel THC PCI device {:04x}:{:04x}", id.0, id.1);
+ }
+
+ pcid_handle.enable_device();
+ let bar = unsafe { pcid_handle.map_bar(0) };
+ let controller = ThcController::new(bar.ptr.as_ptr(), bar.bar_size)
+ .context("failed to create THC controller")?;
+
+ let companion = resolve_acpi_companion(&pci_config.func.addr)
+ .context("failed to resolve ACPI companion for THC device")?;
+ let override_address = companion
+ .as_deref()
+ .map(companion_slave_address_override)
+ .transpose()
+ .context("failed to evaluate THC slave-address override")?
+ .flatten();
+ let hid_devices = scan_bound_i2c_hid_devices(companion.as_deref())
+ .context("failed to scan PNP0C50 devices for THC controller")?;
+
+ let effective_address = override_address.unwrap_or(0x0015);
+ let transport = QuickI2cTransport::new(controller, effective_address);
+ transport.prime_controller();
+ transport.emulate_transfer(&[]);
+ log::debug!("RB_THC_HIDD status={:#x}", transport.status());
+
+ match transport.register_with_i2cd(companion.as_deref(), override_address) {
+ Ok(()) => {}
+ Err(err) => {
+ log::warn!("RB_THC_HIDD registration error={err:#}");
+ }
+ }
+
+ log::info!(
+ "RB_THC_HIDD pci={} companion={:?} override={:?} hid_devices={}",
+ pci_config.func.name(),
+ companion,
+ override_address,
+ hid_devices.len()
+ );
+
+ daemon.ready();
+
+ loop {
+ thread::sleep(Duration::from_secs(5));
+ }
+}
+
+fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result<Option<String>> {
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
+ Ok(entries) => entries,
+ Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
+ log::debug!("intel-thc-hidd: ACPI symbols are not ready yet");
+ return Ok(None);
+ }
+ // ESTALE (116): filesystem handle stale during initfs-to-rootfs transition
+ // ENOENT (2): /scheme/acpi not mounted yet (e.g., acpid not started)
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), skipping companion resolution", err);
+ return Ok(None);
+ }
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
+ };
+ let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function());
+
+ for entry in entries {
+ let entry = entry.context("failed to enumerate ACPI symbol entry")?;
+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
+ continue;
+ };
+ if !file_name.ends_with("._ADR") {
+ continue;
+ }
+
+ let symbol = read_aml_symbol(&file_name)?;
+ if !matches!(symbol.value, AmlSerdeValue::Integer(value) if value == expected_adr) {
+ continue;
+ }
+
+ let device = symbol
+ .name
+ .strip_suffix("._ADR")
+ .unwrap_or(&symbol.name)
+ .trim_start_matches('\\')
+ .replace('/', ".");
+ return Ok(Some(device));
+ }
+
+ Ok(None)
+}
+
+fn companion_slave_address_override(path: &str) -> Result<Option<u16>> {
+ let icrs = evaluate_integer_method(path, "ICRS").ok();
+ let isub = evaluate_integer_method(path, "ISUB").ok();
+ Ok(icrs
+ .or(isub)
+ .map(|value| u16::try_from(value))
+ .transpose()
+ .context("THC ACPI override out of range")?)
+}
+
+fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result<Vec<String>> {
+ let entries = match fs::read_dir("/scheme/acpi/symbols") {
+ Ok(entries) => entries,
+ Err(err) if err.kind() == std::io::ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
+ log::debug!("intel-thc-hidd: ACPI symbols are not ready yet");
+ return Ok(Vec::new());
+ }
+ Err(err) if err.raw_os_error() == Some(116) || err.kind() == std::io::ErrorKind::NotFound => {
+ log::info!("intel-thc-hidd: ACPI symbols unavailable ({}), running with no HID devices", err);
+ return Ok(Vec::new());
+ }
+ Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
+ };
+ let mut devices = BTreeSet::new();
+
+ for entry in entries {
+ let entry = entry.context("failed to enumerate ACPI HID entry")?;
+ let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
+ continue;
+ };
+ if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
+ continue;
+ }
+
+ let symbol = read_aml_symbol(&file_name)?;
+ let is_hid = matches!(
+ decode_hardware_id(&symbol.value).as_deref(),
+ Some("PNP0C50" | "ACPI0C50")
+ );
+ if !is_hid {
+ continue;
+ }
+
+ let device = symbol
+ .name
+ .strip_suffix("._HID")
+ .or_else(|| symbol.name.strip_suffix("._CID"))
+ .unwrap_or(&symbol.name)
+ .trim_start_matches('\\')
+ .replace('/', ".");
+ if let Some(companion) = companion {
+ if !is_bound_to_companion(&device, companion)? {
+ continue;
+ }
+ }
+ devices.insert(device);
+ }
+
+ Ok(devices.into_iter().collect())
+}
+
+fn is_bound_to_companion(device: &str, companion: &str) -> Result<bool> {
+ let resource_path = format!("/scheme/acpi/resources/{device}");
+ let serialized = match fs::read_to_string(&resource_path) {
+ Ok(serialized) => serialized,
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
+ Err(err) => return Err(err).with_context(|| format!("failed to read {resource_path}")),
+ };
+
+ let resources: Vec<ResourceDescriptor> =
+ ron::from_str(&serialized).with_context(|| format!("failed to decode {resource_path}"))?;
+ Ok(resources.into_iter().any(|resource| match resource {
+ ResourceDescriptor::I2cSerialBus(bus) => bus
+ .resource_source
+ .as_ref()
+ .map(|source| source.source == companion)
+ .unwrap_or(false),
+ _ => false,
+ }))
+}
+
+fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
+ let symbol_name = format!("{}.{}", normalize_device_path(path), method);
+ let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
+ let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
+ .with_context(|| format!("failed to open {symbol_path}"))?;
+
+ let mut payload = ron::to_string(&Vec::<AmlSerdeValue>::new())
+ .context("failed to serialize ACPI call arguments")?
+ .into_bytes();
+ payload.resize(payload.len() + 2048, 0);
+ let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
+ .with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
+ let response = std::str::from_utf8(&payload[..used])
+ .with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
+ match ron::from_str::<AmlSerdeValue>(response)
+ .with_context(|| format!("failed to decode ACPI response for {symbol_name}"))?
+ {
+ AmlSerdeValue::Integer(value) => Ok(value),
+ other => bail!("{}.{} returned non-integer value {other:?}", path, method),
+ }
+}
+
+fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
+ let path = format!("/scheme/acpi/symbols/{file_name}");
+ let mut file = OpenOptions::new()
+ .read(true)
+ .open(&path)
+ .with_context(|| format!("failed to open {path}"))?;
+ let mut ron_text = String::new();
+ file.read_to_string(&mut ron_text)
+ .with_context(|| format!("failed to read {path}"))?;
+ ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
+}
+
+fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
+ match value {
+ AmlSerdeValue::String(value) => Some(value.clone()),
+ AmlSerdeValue::Integer(integer) => {
+ let vendor = integer & 0xFFFF;
+ let device = (integer >> 16) & 0xFFFF;
+ let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
+ let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
+ let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
+ let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
+ let device_1 = (device >> 4) & 0xF;
+ let device_2 = (device >> 0) & 0xF;
+ let device_3 = (device >> 12) & 0xF;
+ let device_4 = (device >> 8) & 0xF;
+ Some(format!(
+ "{}{}{}{:01X}{:01X}{:01X}{:01X}",
+ vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
+ ))
+ }
+ _ => None,
+ }
+}
+
+fn normalize_device_path(path: &str) -> String {
+ path.trim_start_matches('\\')
+ .trim_matches('/')
+ .replace('/', ".")
+}