Expand base overlay patches and controller proofs

This commit is contained in:
2026-04-20 18:37:35 +01:00
parent 3c88e91789
commit 1dbb191a74
15 changed files with 13213 additions and 5830 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,279 @@
diff --git a/drivers/acpid/src/ec.rs b/drivers/acpid/src/ec.rs
index c322790a..99842586 100644
--- a/drivers/acpid/src/ec.rs
+++ b/drivers/acpid/src/ec.rs
@@ -1,3 +1,4 @@
+use std::convert::TryFrom;
use std::time::Duration;
use acpi::aml::{
@@ -30,6 +31,28 @@ const BURST_ACK: u8 = 0x90;
pub const DEFAULT_EC_TIMEOUT: Duration = Duration::from_millis(10);
+#[derive(Debug, Clone, Copy)]
+enum EcError {
+ Timeout,
+ OffsetOutOfRange,
+}
+
+impl EcError {
+ fn as_aml_error(self) -> AmlError {
+ match self {
+ EcError::Timeout | EcError::OffsetOutOfRange => {
+ AmlError::NoHandlerForRegionAccess(RegionSpace::EmbeddedControl)
+ }
+ }
+ }
+}
+
+impl From<EcError> for AmlError {
+ fn from(value: EcError) -> Self {
+ value.as_aml_error()
+ }
+}
+
#[repr(transparent)]
pub struct ScBits(u8);
#[allow(dead_code)]
@@ -90,28 +113,33 @@ impl Ec {
Pio::<u8>::new(self.data).write(value);
}
#[inline]
- fn wait_for_write_ready(&self) -> Option<()> {
+ fn wait_for_write_ready(&self) -> Result<(), EcError> {
let timeout = Timeout::new(self.timeout);
loop {
if !self.read_reg_sc().ibf() {
- return Some(());
+ return Ok(());
}
- timeout.run().ok()?;
+ timeout.run().map_err(|_| EcError::Timeout)?;
}
}
#[inline]
- fn wait_for_read_ready(&self) -> Option<()> {
+ fn wait_for_read_ready(&self) -> Result<(), EcError> {
let timeout = Timeout::new(self.timeout);
loop {
if self.read_reg_sc().obf() {
- return Some(());
+ return Ok(());
}
- timeout.run().ok()?;
+ timeout.run().map_err(|_| EcError::Timeout)?;
}
}
+ #[inline]
+ fn checked_address(offset: usize, byte_index: usize) -> Result<u8, EcError> {
+ u8::try_from(offset + byte_index).map_err(|_| EcError::OffsetOutOfRange)
+ }
+
//https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/12_ACPI_Embedded_Controller_Interface_Specification/embedded-controller-command-set.html
- pub fn read(&self, address: u8) -> Option<u8> {
+ fn read(&self, address: u8) -> Result<u8, EcError> {
trace!("ec read addr: {:x}", address);
self.wait_for_write_ready()?;
@@ -125,9 +153,9 @@ impl Ec {
let val = self.read_reg_data();
trace!("got: {:x}", val);
- Some(val)
+ Ok(val)
}
- pub fn write(&self, address: u8, value: u8) -> Option<()> {
+ fn write(&self, address: u8, value: u8) -> Result<(), EcError> {
trace!("ec write addr: {:x}, with: {:x}", address, value);
self.wait_for_write_ready()?;
@@ -141,7 +169,22 @@ impl Ec {
self.write_reg_data(value);
trace!("done");
- Some(())
+ Ok(())
+ }
+
+ fn read_bytes<const N: usize>(&self, offset: usize) -> Result<[u8; N], EcError> {
+ let mut bytes = [0u8; N];
+ for (index, byte) in bytes.iter_mut().enumerate() {
+ *byte = self.read(Self::checked_address(offset, index)?)?;
+ }
+ Ok(bytes)
+ }
+
+ fn write_bytes<const N: usize>(&self, offset: usize, bytes: [u8; N]) -> Result<(), EcError> {
+ for (index, byte) in IntoIterator::into_iter(bytes).enumerate() {
+ self.write(Self::checked_address(offset, index)?, byte)?;
+ }
+ Ok(())
}
// disabled if not met
// First Access - 400 microseconds
@@ -151,11 +194,11 @@ impl Ec {
#[allow(dead_code)]
fn enable_burst(&self) -> bool {
trace!("ec burst enable");
- self.wait_for_write_ready();
+ let _ = self.wait_for_write_ready();
self.write_reg_sc(BE_EC);
- self.wait_for_read_ready();
+ let _ = self.wait_for_read_ready();
let res = self.read_reg_data() == BURST_ACK;
trace!("success: {}", res);
@@ -164,7 +207,7 @@ impl Ec {
#[allow(dead_code)]
fn disable_burst(&self) {
trace!("ec burst disable");
- self.wait_for_write_ready();
+ let _ = self.wait_for_write_ready();
self.write_reg_sc(BD_EC);
trace!("done");
}
@@ -172,11 +215,11 @@ impl Ec {
#[allow(dead_code)]
fn queue_query(&mut self) -> u8 {
trace!("ec query");
- self.wait_for_write_ready();
+ let _ = self.wait_for_write_ready();
self.write_reg_sc(QR_EC);
- self.wait_for_read_ready();
+ let _ = self.wait_for_read_ready();
let val = self.read_reg_data();
trace!("got: {}", val);
@@ -190,7 +233,10 @@ impl RegionHandler for Ec {
offset: usize,
) -> Result<u8, acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
- self.read(offset as u8).ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
+ self.read(Self::checked_address(offset, 0)?).map_err(|error| {
+ warn!("EC read_u8 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u8(
&self,
@@ -199,58 +245,73 @@ impl RegionHandler for Ec {
value: u8,
) -> Result<(), acpi::aml::AmlError> {
assert_eq!(region.space, RegionSpace::EmbeddedControl);
- self.write(offset as u8, value)
- .ok_or(AmlError::MutexAcquireTimeout) // TODO proper error type
- }
- fn read_u16(&self, _region: &OpRegion, _offset: usize) -> Result<u16, acpi::aml::AmlError> {
- warn!("Got u16 EC read from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
- }
- fn read_u32(&self, _region: &OpRegion, _offset: usize) -> Result<u32, acpi::aml::AmlError> {
- warn!("Got u32 EC read from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
- }
- fn read_u64(&self, _region: &OpRegion, _offset: usize) -> Result<u64, acpi::aml::AmlError> {
- warn!("Got u64 EC read from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ self.write(Self::checked_address(offset, 0)?, value)
+ .map_err(|error| {
+ warn!("EC write_u8 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
+ }
+ fn read_u16(&self, region: &OpRegion, offset: usize) -> Result<u16, acpi::aml::AmlError> {
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.read_bytes::<2>(offset)
+ .map(u16::from_le_bytes)
+ .map_err(|error| {
+ warn!("EC read_u16 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
+ }
+ fn read_u32(&self, region: &OpRegion, offset: usize) -> Result<u32, acpi::aml::AmlError> {
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.read_bytes::<4>(offset)
+ .map(u32::from_le_bytes)
+ .map_err(|error| {
+ warn!("EC read_u32 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
+ }
+ fn read_u64(&self, region: &OpRegion, offset: usize) -> Result<u64, acpi::aml::AmlError> {
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.read_bytes::<8>(offset)
+ .map(u64::from_le_bytes)
+ .map_err(|error| {
+ warn!("EC read_u64 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u16(
&self,
- _region: &OpRegion,
- _offset: usize,
- _value: u16,
+ region: &OpRegion,
+ offset: usize,
+ value: u16,
) -> Result<(), acpi::aml::AmlError> {
- warn!("Got u16 EC write from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| {
+ warn!("EC write_u16 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u32(
&self,
- _region: &OpRegion,
- _offset: usize,
- _value: u32,
+ region: &OpRegion,
+ offset: usize,
+ value: u32,
) -> Result<(), acpi::aml::AmlError> {
- warn!("Got u32 EC write from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| {
+ warn!("EC write_u32 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
fn write_u64(
&self,
- _region: &OpRegion,
- _offset: usize,
- _value: u64,
+ region: &OpRegion,
+ offset: usize,
+ value: u64,
) -> Result<(), acpi::aml::AmlError> {
- warn!("Got u64 EC write from AML!");
- Err(acpi::aml::AmlError::NoHandlerForRegionAccess(
- RegionSpace::EmbeddedControl,
- )) // TODO proper error type
+ assert_eq!(region.space, RegionSpace::EmbeddedControl);
+ self.write_bytes(offset, value.to_le_bytes()).map_err(|error| {
+ warn!("EC write_u64 failed at offset {offset:#x}: {error:?}");
+ error.as_aml_error()
+ })
}
}
@@ -0,0 +1,224 @@
diff --git a/drivers/acpid/src/aml_physmem.rs b/drivers/acpid/src/aml_physmem.rs
index 2bdd667b..69b8c48b 100644
--- a/drivers/acpid/src/aml_physmem.rs
+++ b/drivers/acpid/src/aml_physmem.rs
@@ -6,7 +6,10 @@ use rustc_hash::FxHashMap;
use std::fmt::LowerHex;
use std::mem::size_of;
use std::ptr::NonNull;
-use std::sync::{Arc, Mutex};
+use std::sync::atomic::{AtomicU32, Ordering};
+use std::sync::{Arc, Condvar, Mutex};
+use std::thread::ThreadId;
+use std::time::{Duration, Instant};
use syscall::PAGE_SIZE;
const PAGE_MASK: usize = !(PAGE_SIZE - 1);
@@ -141,6 +144,20 @@ impl AmlPageCache {
pub struct AmlPhysMemHandler {
page_cache: Arc<Mutex<AmlPageCache>>,
pci_fd: Arc<Option<libredox::Fd>>,
+ aml_mutexes: Arc<Mutex<FxHashMap<u32, Arc<AmlMutex>>>>,
+ next_mutex_handle: Arc<AtomicU32>,
+}
+
+#[derive(Debug, Default)]
+struct AmlMutexState {
+ owner: Option<ThreadId>,
+ depth: u32,
+}
+
+#[derive(Debug, Default)]
+struct AmlMutex {
+ state: Mutex<AmlMutexState>,
+ condvar: Condvar,
}
/// Read from a physical address.
@@ -156,6 +173,30 @@ impl AmlPhysMemHandler {
Self {
page_cache,
pci_fd: Arc::new(pci_fd),
+ aml_mutexes: Arc::new(Mutex::new(FxHashMap::default())),
+ next_mutex_handle: Arc::new(AtomicU32::new(1)),
+ }
+ }
+
+ fn aml_mutex(&self, handle: Handle) -> Option<Arc<AmlMutex>> {
+ self.aml_mutexes
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner())
+ .get(&handle.0)
+ .cloned()
+ }
+
+ fn read_phys_or_fault<T>(&self, address: usize) -> T
+ where
+ T: PrimInt + LowerHex,
+ {
+ let mut page_cache = self
+ .page_cache
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ match page_cache.read_from_phys::<T>(address) {
+ Ok(value) => value,
+ Err(error) => panic!("AML physmem read failed at {:#x}: {}", address, error),
}
}
@@ -240,43 +281,19 @@ impl acpi::Handler for AmlPhysMemHandler {
fn read_u8(&self, address: usize) -> u8 {
log::trace!("read u8 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u8>(address) {
- return value;
- }
- }
- log::error!("failed to read u8 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u8>(address)
}
fn read_u16(&self, address: usize) -> u16 {
log::trace!("read u16 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u16>(address) {
- return value;
- }
- }
- log::error!("failed to read u16 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u16>(address)
}
fn read_u32(&self, address: usize) -> u32 {
log::trace!("read u32 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u32>(address) {
- return value;
- }
- }
- log::error!("failed to read u32 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u32>(address)
}
fn read_u64(&self, address: usize) -> u64 {
log::trace!("read u64 {:X}", address);
- if let Ok(mut page_cache) = self.page_cache.lock() {
- if let Ok(value) = page_cache.read_from_phys::<u64>(address) {
- return value;
- }
- }
- log::error!("failed to read u64 {:#x}", address);
- 0
+ self.read_phys_or_fault::<u64>(address)
}
fn write_u8(&self, address: usize, value: u8) {
@@ -415,16 +432,102 @@ impl acpi::Handler for AmlPhysMemHandler {
}
fn create_mutex(&self) -> Handle {
- log::debug!("TODO: Handler::create_mutex");
- Handle(0)
+ let handle = self.next_mutex_handle.fetch_add(1, Ordering::Relaxed);
+ self.aml_mutexes
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner())
+ .insert(handle, Arc::new(AmlMutex::default()));
+ log::trace!("created AML mutex handle {handle}");
+ Handle(handle)
}
fn acquire(&self, mutex: Handle, timeout: u16) -> Result<(), AmlError> {
- log::debug!("TODO: Handler::acquire");
- Ok(())
+ let Some(aml_mutex) = self.aml_mutex(mutex) else {
+ log::error!("attempted to acquire unknown AML mutex handle {}", mutex.0);
+ return Err(AmlError::MutexAcquireTimeout);
+ };
+
+ let current_thread = std::thread::current().id();
+ let deadline = (timeout != 0xffff).then(|| Instant::now() + Duration::from_millis(timeout.into()));
+
+ let mut state = aml_mutex
+ .state
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+
+ loop {
+ match state.owner {
+ None => {
+ state.owner = Some(current_thread);
+ state.depth = 1;
+ return Ok(());
+ }
+ Some(owner) if owner == current_thread => {
+ state.depth = state.depth.saturating_add(1);
+ return Ok(());
+ }
+ Some(_) if timeout == 0 => return Err(AmlError::MutexAcquireTimeout),
+ Some(_) if timeout == 0xffff => {
+ state = aml_mutex
+ .condvar
+ .wait(state)
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ }
+ Some(_) => {
+ let Some(deadline) = deadline else {
+ return Err(AmlError::MutexAcquireTimeout);
+ };
+ let now = Instant::now();
+ if now >= deadline {
+ return Err(AmlError::MutexAcquireTimeout);
+ }
+
+ let remaining = deadline.saturating_duration_since(now);
+ let (next_state, wait_result) = aml_mutex
+ .condvar
+ .wait_timeout(state, remaining)
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+ state = next_state;
+
+ if wait_result.timed_out() && state.owner != Some(current_thread) {
+ return Err(AmlError::MutexAcquireTimeout);
+ }
+ }
+ }
+ }
}
fn release(&self, mutex: Handle) {
- log::debug!("TODO: Handler::release");
+ let Some(aml_mutex) = self.aml_mutex(mutex) else {
+ log::error!("attempted to release unknown AML mutex handle {}", mutex.0);
+ return;
+ };
+
+ let current_thread = std::thread::current().id();
+ let mut state = aml_mutex
+ .state
+ .lock()
+ .unwrap_or_else(|poisoned| poisoned.into_inner());
+
+ match state.owner {
+ Some(owner) if owner == current_thread => {
+ if state.depth > 1 {
+ state.depth -= 1;
+ } else {
+ state.owner = None;
+ state.depth = 0;
+ aml_mutex.condvar.notify_one();
+ }
+ }
+ Some(_) => {
+ log::warn!(
+ "ignoring AML mutex release for handle {} from non-owner thread",
+ mutex.0
+ );
+ }
+ None => {
+ log::warn!("ignoring AML mutex release for unlocked handle {}", mutex.0);
+ }
+ }
}
}
@@ -0,0 +1,719 @@
diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs
index 5a5040c3..7070e8b9 100644
--- a/drivers/acpid/src/scheme.rs
+++ b/drivers/acpid/src/scheme.rs
@@ -2,7 +2,6 @@ use acpi::aml::namespace::AmlName;
use amlserde::aml_serde_name::to_aml_format;
use amlserde::AmlSerdeValue;
use core::str;
-use libredox::Fd;
use parking_lot::RwLockReadGuard;
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult, SendFdRequest, Socket};
@@ -16,17 +15,19 @@ use syscall::FobtainFdFlags;
use syscall::data::Stat;
use syscall::error::{Error, Result};
-use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
+use syscall::error::{EACCES, EAGAIN, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EOPNOTSUPP};
use syscall::flag::{MODE_DIR, MODE_FILE};
use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK};
use syscall::{EOVERFLOW, EPERM};
-use crate::acpi::{AcpiContext, AmlSymbols, SdtSignature};
+use crate::acpi::{
+ AcpiBattery, AcpiContext, AcpiPowerAdapter, AcpiPowerSnapshot, AmlSymbols, DmiInfo,
+ SdtSignature,
+};
pub struct AcpiScheme<'acpi, 'sock> {
ctx: &'acpi AcpiContext,
handles: HandleMap<Handle<'acpi>>,
- pci_fd: Option<Fd>,
socket: &'sock Socket,
}
@@ -41,10 +42,170 @@ enum HandleKind<'a> {
Table(SdtSignature),
Symbols(RwLockReadGuard<'a, AmlSymbols>),
Symbol { name: String, description: String },
+ Reboot,
+ DmiDir,
+ Dmi(String),
+ PowerDir,
+ PowerAdaptersDir,
+ PowerAdapterDir(String),
+ PowerBatteriesDir,
+ PowerBatteryDir(String),
+ PowerFile(String),
SchemeRoot,
RegisterPci,
}
+const DMI_DIRECTORY_ENTRIES: &[&str] = &[
+ "sys_vendor",
+ "board_vendor",
+ "board_name",
+ "board_version",
+ "product_name",
+ "product_version",
+ "bios_version",
+ "match_all",
+];
+
+fn dmi_contents(dmi_info: Option<&DmiInfo>, name: &str) -> Option<String> {
+ Some(match name {
+ "sys_vendor" => dmi_info
+ .and_then(|info| info.sys_vendor.clone())
+ .unwrap_or_default(),
+ "board_vendor" => dmi_info
+ .and_then(|info| info.board_vendor.clone())
+ .unwrap_or_default(),
+ "board_name" => dmi_info
+ .and_then(|info| info.board_name.clone())
+ .unwrap_or_default(),
+ "board_version" => dmi_info
+ .and_then(|info| info.board_version.clone())
+ .unwrap_or_default(),
+ "product_name" => dmi_info
+ .and_then(|info| info.product_name.clone())
+ .unwrap_or_default(),
+ "product_version" => dmi_info
+ .and_then(|info| info.product_version.clone())
+ .unwrap_or_default(),
+ "bios_version" => dmi_info
+ .and_then(|info| info.bios_version.clone())
+ .unwrap_or_default(),
+ "match_all" => dmi_info.map(DmiInfo::to_match_lines).unwrap_or_default(),
+ _ => return None,
+ })
+}
+
+fn power_bool_contents(value: bool) -> String {
+ if value {
+ String::from("1\n")
+ } else {
+ String::from("0\n")
+ }
+}
+
+fn power_u64_contents(value: u64) -> String {
+ format!("{value}\n")
+}
+
+fn power_f64_contents(value: f64) -> String {
+ format!("{value}\n")
+}
+
+fn power_string_contents(value: &str) -> String {
+ format!("{value}\n")
+}
+
+fn power_adapter_file_contents(adapter: &AcpiPowerAdapter, name: &str) -> Option<String> {
+ Some(match name {
+ "path" => power_string_contents(&adapter.path),
+ "online" => power_bool_contents(adapter.online),
+ _ => return None,
+ })
+}
+
+fn power_adapter_entry_names() -> &'static [&'static str] {
+ &["path", "online"]
+}
+
+fn power_battery_file_contents(battery: &AcpiBattery, name: &str) -> Option<String> {
+ Some(match name {
+ "path" => power_string_contents(&battery.path),
+ "state" => power_u64_contents(battery.state),
+ "present_rate" => power_u64_contents(battery.present_rate?),
+ "remaining_capacity" => power_u64_contents(battery.remaining_capacity?),
+ "present_voltage" => power_u64_contents(battery.present_voltage?),
+ "power_unit" => power_string_contents(battery.power_unit.as_deref()?),
+ "design_capacity" => power_u64_contents(battery.design_capacity?),
+ "last_full_capacity" => power_u64_contents(battery.last_full_capacity?),
+ "design_voltage" => power_u64_contents(battery.design_voltage?),
+ "technology" => power_string_contents(battery.technology.as_deref()?),
+ "model" => power_string_contents(battery.model.as_deref()?),
+ "serial" => power_string_contents(battery.serial.as_deref()?),
+ "battery_type" => power_string_contents(battery.battery_type.as_deref()?),
+ "oem_info" => power_string_contents(battery.oem_info.as_deref()?),
+ "percentage" => power_f64_contents(battery.percentage?),
+ _ => return None,
+ })
+}
+
+fn power_battery_entry_names(battery: &AcpiBattery) -> Vec<&'static str> {
+ let mut names = vec!["path", "state"];
+
+ if battery.present_rate.is_some() {
+ names.push("present_rate");
+ }
+ if battery.remaining_capacity.is_some() {
+ names.push("remaining_capacity");
+ }
+ if battery.present_voltage.is_some() {
+ names.push("present_voltage");
+ }
+ if battery.power_unit.is_some() {
+ names.push("power_unit");
+ }
+ if battery.design_capacity.is_some() {
+ names.push("design_capacity");
+ }
+ if battery.last_full_capacity.is_some() {
+ names.push("last_full_capacity");
+ }
+ if battery.design_voltage.is_some() {
+ names.push("design_voltage");
+ }
+ if battery.technology.is_some() {
+ names.push("technology");
+ }
+ if battery.model.is_some() {
+ names.push("model");
+ }
+ if battery.serial.is_some() {
+ names.push("serial");
+ }
+ if battery.battery_type.is_some() {
+ names.push("battery_type");
+ }
+ if battery.oem_info.is_some() {
+ names.push("oem_info");
+ }
+ if battery.percentage.is_some() {
+ names.push("percentage");
+ }
+
+ names
+}
+
+fn top_level_entries(power_available: bool) -> Vec<(&'static str, DirentKind)> {
+ let mut entries = vec![
+ ("tables", DirentKind::Directory),
+ ("symbols", DirentKind::Directory),
+ ("dmi", DirentKind::Directory),
+ ("reboot", DirentKind::Regular),
+ ];
+ if power_available {
+ entries.push(("power", DirentKind::Directory));
+ }
+ entries
+}
+
impl HandleKind<'_> {
fn is_dir(&self) -> bool {
match self {
@@ -53,6 +214,15 @@ impl HandleKind<'_> {
Self::Table(_) => false,
Self::Symbols(_) => true,
Self::Symbol { .. } => false,
+ Self::Reboot => false,
+ Self::DmiDir => true,
+ Self::Dmi(_) => false,
+ Self::PowerDir => true,
+ Self::PowerAdaptersDir => true,
+ Self::PowerAdapterDir(_) => true,
+ Self::PowerBatteriesDir => true,
+ Self::PowerBatteryDir(_) => true,
+ Self::PowerFile(_) => false,
Self::SchemeRoot => false,
Self::RegisterPci => false,
}
@@ -65,8 +235,19 @@ impl HandleKind<'_> {
.ok_or(Error::new(EBADFD))?
.length(),
Self::Symbol { description, .. } => description.len(),
+ Self::Reboot => 0,
+ Self::Dmi(contents) => contents.len(),
+ Self::PowerFile(contents) => contents.len(),
// Directories
- Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
+ Self::TopLevel
+ | Self::Symbols(_)
+ | Self::Tables
+ | Self::DmiDir
+ | Self::PowerDir
+ | Self::PowerAdaptersDir
+ | Self::PowerAdapterDir(_)
+ | Self::PowerBatteriesDir
+ | Self::PowerBatteryDir(_) => 0,
Self::SchemeRoot | Self::RegisterPci => return Err(Error::new(EBADF)),
})
}
@@ -77,10 +258,111 @@ impl<'acpi, 'sock> AcpiScheme<'acpi, 'sock> {
Self {
ctx,
handles: HandleMap::new(),
- pci_fd: None,
socket,
}
}
+
+ fn power_snapshot(&self) -> Result<AcpiPowerSnapshot> {
+ self.ctx.power_snapshot().map_err(|error| match error {
+ crate::acpi::AmlEvalError::NotInitialized => Error::new(EAGAIN),
+ crate::acpi::AmlEvalError::Unsupported(message) => {
+ log::warn!("ACPI power surface unavailable: {message}");
+ Error::new(EOPNOTSUPP)
+ }
+ other => {
+ log::warn!("Failed to build ACPI power snapshot: {:?}", other);
+ Error::new(EIO)
+ }
+ })
+ }
+
+ fn power_available(&self) -> bool {
+ matches!(self.ctx.power_snapshot(), Ok(_))
+ }
+
+ fn power_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ self.power_snapshot()?;
+
+ if normalized.is_empty() {
+ return Ok(HandleKind::PowerDir);
+ }
+ if normalized == "on_battery" {
+ return Ok(HandleKind::PowerFile(power_bool_contents(
+ self.power_snapshot()?.on_battery(),
+ )));
+ }
+ if normalized == "adapters" {
+ return Ok(HandleKind::PowerAdaptersDir);
+ }
+ if let Some(rest) = normalized.strip_prefix("adapters/") {
+ return self.power_adapter_handle(rest);
+ }
+ if normalized == "batteries" {
+ return Ok(HandleKind::PowerBatteriesDir);
+ }
+ if let Some(rest) = normalized.strip_prefix("batteries/") {
+ return self.power_battery_handle(rest);
+ }
+
+ Err(Error::new(ENOENT))
+ }
+
+ fn power_adapter_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ if normalized.is_empty() {
+ return Ok(HandleKind::PowerAdaptersDir);
+ }
+
+ let mut parts = normalized.split('/');
+ let adapter_id = parts.next().ok_or(Error::new(ENOENT))?;
+ let field = parts.next();
+ if parts.next().is_some() {
+ return Err(Error::new(ENOENT));
+ }
+
+ let snapshot = self.power_snapshot()?;
+ let adapter = snapshot
+ .adapters
+ .iter()
+ .find(|adapter| adapter.id == adapter_id)
+ .ok_or(Error::new(ENOENT))?;
+
+ match field {
+ None | Some("") => Ok(HandleKind::PowerAdapterDir(adapter.id.clone())),
+ Some(name) => Ok(HandleKind::PowerFile(
+ power_adapter_file_contents(adapter, name).ok_or(Error::new(ENOENT))?,
+ )),
+ }
+ }
+
+ fn power_battery_handle(&self, path: &str) -> Result<HandleKind<'acpi>> {
+ let normalized = path.trim_matches('/');
+ if normalized.is_empty() {
+ return Ok(HandleKind::PowerBatteriesDir);
+ }
+
+ let mut parts = normalized.split('/');
+ let battery_id = parts.next().ok_or(Error::new(ENOENT))?;
+ let field = parts.next();
+ if parts.next().is_some() {
+ return Err(Error::new(ENOENT));
+ }
+
+ let snapshot = self.power_snapshot()?;
+ let battery = snapshot
+ .batteries
+ .iter()
+ .find(|battery| battery.id == battery_id)
+ .ok_or(Error::new(ENOENT))?;
+
+ match field {
+ None | Some("") => Ok(HandleKind::PowerBatteryDir(battery.id.clone())),
+ Some(name) => Ok(HandleKind::PowerFile(
+ power_battery_file_contents(battery, name).ok_or(Error::new(ENOENT))?,
+ )),
+ }
+ }
}
fn parse_hex_digit(hex: u8) -> Option<u8> {
@@ -184,9 +466,9 @@ impl SchemeSync for AcpiScheme<'_, '_> {
HandleKind::SchemeRoot => {
// TODO: arrayvec
let components = {
- let mut v = arrayvec::ArrayVec::<&str, 3>::new();
+ let mut v = arrayvec::ArrayVec::<&str, 4>::new();
let it = path.split('/');
- for component in it.take(3) {
+ for component in it.take(4) {
v.push(component);
}
@@ -195,6 +477,25 @@ impl SchemeSync for AcpiScheme<'_, '_> {
match &*components {
[""] => HandleKind::TopLevel,
+ ["reboot"] => HandleKind::Reboot,
+ ["dmi"] => {
+ if flag_dir || flag_stat || path.ends_with('/') {
+ HandleKind::DmiDir
+ } else {
+ HandleKind::Dmi(
+ dmi_contents(self.ctx.dmi_info(), "match_all")
+ .expect("match_all should always resolve"),
+ )
+ }
+ }
+ ["dmi", ""] => HandleKind::DmiDir,
+ ["dmi", field] => HandleKind::Dmi(
+ dmi_contents(self.ctx.dmi_info(), field).ok_or(Error::new(ENOENT))?,
+ ),
+ ["power"] => self.power_handle("")?,
+ ["power", tail] => self.power_handle(tail)?,
+ ["power", a, b] => self.power_handle(&format!("{a}/{b}"))?,
+ ["power", a, b, c] => self.power_handle(&format!("{a}/{b}/{c}"))?,
["register_pci"] => HandleKind::RegisterPci,
["tables"] => HandleKind::Tables,
@@ -204,7 +505,11 @@ impl SchemeSync for AcpiScheme<'_, '_> {
}
["symbols"] => {
- if let Ok(aml_symbols) = self.ctx.aml_symbols(self.pci_fd.as_ref()) {
+ if !self.ctx.pci_ready() {
+ log::warn!("Deferring AML symbol scan until PCI registration is ready");
+ return Err(Error::new(EAGAIN));
+ }
+ if let Ok(aml_symbols) = self.ctx.aml_symbols() {
HandleKind::Symbols(aml_symbols)
} else {
return Err(Error::new(EIO));
@@ -212,6 +517,12 @@ impl SchemeSync for AcpiScheme<'_, '_> {
}
["symbols", symbol] => {
+ if !self.ctx.pci_ready() {
+ log::warn!(
+ "Deferring AML symbol lookup for {symbol} until PCI registration is ready"
+ );
+ return Err(Error::new(EAGAIN));
+ }
if let Some(description) = self.ctx.aml_lookup(symbol) {
HandleKind::Symbol {
name: (*symbol).to_owned(),
@@ -225,6 +536,15 @@ impl SchemeSync for AcpiScheme<'_, '_> {
_ => return Err(Error::new(ENOENT)),
}
}
+ HandleKind::DmiDir => {
+ if path.is_empty() {
+ HandleKind::DmiDir
+ } else {
+ HandleKind::Dmi(
+ dmi_contents(self.ctx.dmi_info(), path).ok_or(Error::new(ENOENT))?,
+ )
+ }
+ }
HandleKind::Symbols(ref aml_symbols) => {
if let Some(description) = aml_symbols.lookup(path) {
HandleKind::Symbol {
@@ -235,6 +555,23 @@ impl SchemeSync for AcpiScheme<'_, '_> {
return Err(Error::new(ENOENT));
}
}
+ HandleKind::PowerDir => self.power_handle(path)?,
+ HandleKind::PowerAdaptersDir => self.power_adapter_handle(path)?,
+ HandleKind::PowerAdapterDir(ref adapter_id) => {
+ if path.is_empty() {
+ HandleKind::PowerAdapterDir(adapter_id.clone())
+ } else {
+ self.power_adapter_handle(&format!("{adapter_id}/{path}"))?
+ }
+ }
+ HandleKind::PowerBatteriesDir => self.power_battery_handle(path)?,
+ HandleKind::PowerBatteryDir(ref battery_id) => {
+ if path.is_empty() {
+ HandleKind::PowerBatteryDir(battery_id.clone())
+ } else {
+ self.power_battery_handle(&format!("{battery_id}/{path}"))?
+ }
+ }
_ => return Err(Error::new(EACCES)),
};
@@ -296,7 +633,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
) -> Result<usize> {
let offset: usize = offset.try_into().map_err(|_| Error::new(EINVAL))?;
- let handle = self.handles.get_mut(id)?;
+ let handle = self.handles.get(id)?;
if handle.stat {
return Err(Error::new(EBADF));
@@ -309,6 +646,8 @@ impl SchemeSync for AcpiScheme<'_, '_> {
.ok_or(Error::new(EBADFD))?
.as_slice(),
HandleKind::Symbol { description, .. } => description.as_bytes(),
+ HandleKind::Dmi(contents) => contents.as_bytes(),
+ HandleKind::PowerFile(contents) => contents.as_bytes(),
_ => return Err(Error::new(EINVAL)),
};
@@ -328,13 +667,82 @@ impl SchemeSync for AcpiScheme<'_, '_> {
mut buf: DirentBuf<&'buf mut [u8]>,
opaque_offset: u64,
) -> Result<DirentBuf<&'buf mut [u8]>> {
- let handle = self.handles.get_mut(id)?;
+ let handle = self.handles.get(id)?;
match &handle.kind {
HandleKind::TopLevel => {
- const TOPLEVEL_ENTRIES: &[&str] = &["tables", "symbols"];
+ for (idx, (name, kind)) in top_level_entries(self.power_available())
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: *kind,
+ })?;
+ }
+ }
+ HandleKind::DmiDir => {
+ for (idx, name) in DMI_DIRECTORY_ENTRIES
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: DirentKind::Regular,
+ })?;
+ }
+ }
+ HandleKind::PowerDir => {
+ const POWER_ROOT_ENTRIES: &[(&str, DirentKind)] = &[
+ ("on_battery", DirentKind::Regular),
+ ("adapters", DirentKind::Directory),
+ ("batteries", DirentKind::Directory),
+ ];
+
+ for (idx, (name, kind)) in POWER_ROOT_ENTRIES
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: *kind,
+ })?;
+ }
+ }
+ HandleKind::PowerAdaptersDir => {
+ let snapshot = self.power_snapshot()?;
+ for (idx, adapter) in snapshot
+ .adapters
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name: adapter.id.as_str(),
+ kind: DirentKind::Directory,
+ })?;
+ }
+ }
+ HandleKind::PowerAdapterDir(adapter_id) => {
+ let snapshot = self.power_snapshot()?;
+ let _adapter = snapshot
+ .adapters
+ .iter()
+ .find(|adapter| adapter.id == *adapter_id)
+ .ok_or(Error::new(EIO))?;
- for (idx, name) in TOPLEVEL_ENTRIES
+ for (idx, name) in power_adapter_entry_names()
.iter()
.enumerate()
.skip(opaque_offset as usize)
@@ -343,10 +751,44 @@ impl SchemeSync for AcpiScheme<'_, '_> {
inode: 0,
next_opaque_id: idx as u64 + 1,
name,
+ kind: DirentKind::Regular,
+ })?;
+ }
+ }
+ HandleKind::PowerBatteriesDir => {
+ let snapshot = self.power_snapshot()?;
+ for (idx, battery) in snapshot
+ .batteries
+ .iter()
+ .enumerate()
+ .skip(opaque_offset as usize)
+ {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name: battery.id.as_str(),
kind: DirentKind::Directory,
})?;
}
}
+ HandleKind::PowerBatteryDir(battery_id) => {
+ let snapshot = self.power_snapshot()?;
+ let battery = snapshot
+ .batteries
+ .iter()
+ .find(|battery| battery.id == *battery_id)
+ .ok_or(Error::new(EIO))?;
+ let entry_names = power_battery_entry_names(battery);
+
+ for (idx, name) in entry_names.iter().enumerate().skip(opaque_offset as usize) {
+ buf.entry(DirEntry {
+ inode: 0,
+ next_opaque_id: idx as u64 + 1,
+ name,
+ kind: DirentKind::Regular,
+ })?;
+ }
+ }
HandleKind::Symbols(aml_symbols) => {
for (idx, (symbol_name, _value)) in aml_symbols
.symbols_cache()
@@ -444,6 +886,38 @@ impl SchemeSync for AcpiScheme<'_, '_> {
Ok(result_len)
}
+ fn write(
+ &mut self,
+ id: usize,
+ buf: &[u8],
+ _offset: u64,
+ _flags: u32,
+ _ctx: &CallerCtx,
+ ) -> Result<usize> {
+ let handle = self.handles.get_mut(id)?;
+
+ if handle.stat {
+ return Err(Error::new(EBADF));
+ }
+ if !handle.allowed_to_eval {
+ return Err(Error::new(EPERM));
+ }
+
+ match handle.kind {
+ HandleKind::Reboot => {
+ if buf.is_empty() {
+ return Err(Error::new(EINVAL));
+ }
+ self.ctx.acpi_reboot().map_err(|error| {
+ log::error!("ACPI reboot failed: {error}");
+ Error::new(EIO)
+ })?;
+ Ok(buf.len())
+ }
+ _ => Err(Error::new(EBADF)),
+ }
+ }
+
fn on_sendfd(&mut self, sendfd_request: &SendFdRequest) -> Result<usize> {
let id = sendfd_request.id();
let num_fds = sendfd_request.num_fds();
@@ -470,10 +944,8 @@ impl SchemeSync for AcpiScheme<'_, '_> {
}
let new_fd = libredox::Fd::new(new_fd);
- if self.pci_fd.is_some() {
+ if self.ctx.register_pci_fd(new_fd).is_err() {
return Err(Error::new(EINVAL));
- } else {
- self.pci_fd = Some(new_fd);
}
Ok(num_fds)
@@ -483,3 +955,58 @@ impl SchemeSync for AcpiScheme<'_, '_> {
self.handles.remove(id);
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::{dmi_contents, top_level_entries};
+ use crate::acpi::DmiInfo;
+ use syscall::dirent::DirentKind;
+
+ #[test]
+ fn dmi_contents_exposes_individual_fields_and_match_all() {
+ let dmi_info = DmiInfo {
+ sys_vendor: Some("Framework".to_string()),
+ board_name: Some("FRANMECP01".to_string()),
+ product_name: Some("Laptop 16".to_string()),
+ ..DmiInfo::default()
+ };
+
+ assert_eq!(
+ dmi_contents(Some(&dmi_info), "sys_vendor").as_deref(),
+ Some("Framework")
+ );
+ assert_eq!(
+ dmi_contents(Some(&dmi_info), "board_name").as_deref(),
+ Some("FRANMECP01")
+ );
+ assert_eq!(
+ dmi_contents(Some(&dmi_info), "product_name").as_deref(),
+ Some("Laptop 16")
+ );
+ assert_eq!(
+ dmi_contents(Some(&dmi_info), "match_all").as_deref(),
+ Some("sys_vendor=Framework\nboard_name=FRANMECP01\nproduct_name=Laptop 16")
+ );
+ assert_eq!(dmi_contents(None, "bios_version").as_deref(), Some(""));
+ assert_eq!(dmi_contents(Some(&dmi_info), "unknown"), None);
+ }
+
+ #[test]
+ fn top_level_entries_always_include_reboot() {
+ let entries = top_level_entries(false);
+ assert!(entries.iter().any(|(name, kind)| {
+ *name == "reboot" && matches!(kind, DirentKind::Regular)
+ }));
+ }
+
+ #[test]
+ fn top_level_entries_only_include_power_when_available() {
+ let hidden = top_level_entries(false);
+ let visible = top_level_entries(true);
+
+ assert!(!hidden.iter().any(|(name, _)| *name == "power"));
+ assert!(visible.iter().any(|(name, kind)| {
+ *name == "power" && matches!(kind, DirentKind::Directory)
+ }));
+ }
+}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,44 @@
diff --git a/recipes/wip/qt/qtbase/recipe.toml b/recipes/wip/qt/qtbase/recipe.toml
index 0af3b77ba..05eac497a 100644
--- a/recipes/wip/qt/qtbase/recipe.toml
+++ b/recipes/wip/qt/qtbase/recipe.toml
@@ -39,6 +39,18 @@ choose_relibc_lib_stage() {
return 1
}
+choose_toolchain_root() {
+ if [ -n "${COOKBOOK_HOST_SYSROOT:-}" ] && [ -d "${COOKBOOK_HOST_SYSROOT}" ]; then
+ printf '%s\n' "${COOKBOOK_HOST_SYSROOT}"
+ return 0
+ fi
+ if [ -d "${HOME}/.redoxer/x86_64-unknown-redox/toolchain" ]; then
+ printf '%s\n' "${HOME}/.redoxer/x86_64-unknown-redox/toolchain"
+ return 0
+ fi
+ printf '%s\n' "${COOKBOOK_ROOT}/prefix/x86_64-unknown-redox/sysroot"
+}
+
if RELIBC_STAGE_LIB="$(choose_relibc_lib_stage "$RELIBC_STAGE_LIB_STAGE")"; then
:
elif RELIBC_STAGE_LIB="$(choose_relibc_lib_stage "$RELIBC_STAGE_LIB_TMP")"; then
@@ -58,6 +70,20 @@ if [ -d "${RELIBC_STAGE_INCLUDE}" ]; then
export CPPFLAGS="${CPPFLAGS} -I${RELIBC_STAGE_INCLUDE}"
export CFLAGS="${CFLAGS} -I${RELIBC_STAGE_INCLUDE}"
export CXXFLAGS="${CXXFLAGS} -I${RELIBC_STAGE_INCLUDE}"
+
+ # The Redox GCC toolchain currently prefers its own bundled target elf.h
+ # under .../x86_64-unknown-redox/include/ over the recipe sysroot copy.
+ # Sync the freshly built relibc header into that toolchain include root so
+ # Qt's ELF plugin parser sees the corrected ELF64 typedef layout.
+ TOOLCHAIN_ROOT="$(choose_toolchain_root)"
+ TOOLCHAIN_TARGET_INCLUDE="${TOOLCHAIN_ROOT}/x86_64-unknown-redox/include"
+ TOOLCHAIN_TARGET_USR_INCLUDE="${TOOLCHAIN_ROOT}/x86_64-unknown-redox/usr/include"
+ if [ -f "${RELIBC_STAGE_INCLUDE}/elf.h" ] && [ -d "${TOOLCHAIN_TARGET_INCLUDE}" ]; then
+ cp -f "${RELIBC_STAGE_INCLUDE}/elf.h" "${TOOLCHAIN_TARGET_INCLUDE}/elf.h"
+ fi
+ if [ -f "${RELIBC_STAGE_INCLUDE}/elf.h" ] && [ -d "${TOOLCHAIN_TARGET_USR_INCLUDE}" ]; then
+ cp -f "${RELIBC_STAGE_INCLUDE}/elf.h" "${TOOLCHAIN_TARGET_USR_INCLUDE}/elf.h"
+ fi
fi
if [ -d "${RELIBC_STAGE_LIB}" ]; then
mkdir -p "${COOKBOOK_SYSROOT}/lib"
+7 -4
View File
@@ -23,7 +23,6 @@ find_uefi_firmware() {
return 1 return 1
} }
# Print usage information
usage() { usage() {
cat << USAGE cat << USAGE
Usage: $(basename "$0") [--check] [config] [extra qemu args...] Usage: $(basename "$0") [--check] [config] [extra qemu args...]
@@ -35,7 +34,7 @@ Options:
--check Boot and verify the guest reaches a login prompt --check Boot and verify the guest reaches a login prompt
Arguments: Arguments:
config Optional config name (default: redbear-desktop) config Optional config name (default: redbear-mini)
extra qemu args Additional arguments appended to the QEMU command extra qemu args Additional arguments appended to the QEMU command
Environment: Environment:
@@ -44,7 +43,7 @@ Environment:
Examples: Examples:
$(basename "$0") $(basename "$0")
$(basename "$0") --check $(basename "$0") --check
$(basename "$0") redbear-desktop -m 4G $(basename "$0") redbear-mini -m 4G
USAGE USAGE
exit 0 exit 0
@@ -52,7 +51,7 @@ USAGE
check_mode=0 check_mode=0
filtered_args=() filtered_args=()
config="redbear-desktop" config="redbear-mini"
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--help|-h|help) --help|-h|help)
@@ -70,6 +69,10 @@ for arg in "$@"; do
esac esac
done done
if [[ "$config" == "redbear-mini" ]]; then
config="redbear-minimal"
fi
firmware="$(find_uefi_firmware)" || { firmware="$(find_uefi_firmware)" || {
echo "ERROR: no usable x86_64 UEFI firmware found" >&2 echo "ERROR: no usable x86_64 UEFI firmware found" >&2
exit 1 exit 1
@@ -10,15 +10,18 @@ usage() {
Usage: test-lowlevel-controllers-qemu.sh [config] Usage: test-lowlevel-controllers-qemu.sh [config]
Run the bounded low-level controller/runtime proof helpers in sequence. Run the bounded low-level controller/runtime proof helpers in sequence.
Defaults to redbear-desktop. Defaults to redbear-mini (mapped by the individual helpers where needed).
Note: the IOMMU first-use proof still requires a target that actually ships `/usr/bin/iommu`, so
the wrapper automatically upgrades that single leg to `redbear-full` when invoked with
`redbear-mini`.
Checks run: Checks run:
- MSI-X path
- xHCI interrupt path - xHCI interrupt path
- IOMMU first-use path - IOMMU first-use path
- PS/2 + serio path - PS/2 + serio path
- monotonic timer path - monotonic timer path
MSI-X remains a separate proof helper because its current default target is redbear-full.
USAGE USAGE
} }
@@ -31,13 +34,25 @@ for arg in "$@"; do
esac esac
done done
config="${1:-redbear-desktop}" config="${1:-redbear-mini}"
iommu_config="$config"
if [[ "$config" == "redbear-mini" ]]; then
iommu_config="redbear-full"
fi
echo ">>> Running MSI-X proof"
bash "$SCRIPT_DIR/test-msix-qemu.sh" "$config"
echo ">>> Running xHCI interrupt proof" echo ">>> Running xHCI interrupt proof"
bash "$SCRIPT_DIR/test-xhci-irq-qemu.sh" --check "$config" bash "$SCRIPT_DIR/test-xhci-irq-qemu.sh" --check "$config"
echo ">>> Running IOMMU first-use proof" echo ">>> Running IOMMU first-use proof"
bash "$SCRIPT_DIR/test-iommu-qemu.sh" --check "$config" iommu_image="build/x86_64/${iommu_config}/harddrive.img"
if [[ -f "$iommu_image" ]]; then
bash "$SCRIPT_DIR/test-iommu-qemu.sh" --check "$iommu_config"
else
echo "SKIP: IOMMU first-use proof skipped because $iommu_image is missing"
fi
echo ">>> Running PS/2 + serio proof" echo ">>> Running PS/2 + serio proof"
bash "$SCRIPT_DIR/test-ps2-qemu.sh" --check "$config" bash "$SCRIPT_DIR/test-ps2-qemu.sh" --check "$config"
@@ -45,4 +60,4 @@ bash "$SCRIPT_DIR/test-ps2-qemu.sh" --check "$config"
echo ">>> Running monotonic timer proof" echo ">>> Running monotonic timer proof"
bash "$SCRIPT_DIR/test-timer-qemu.sh" --check "$config" bash "$SCRIPT_DIR/test-timer-qemu.sh" --check "$config"
echo "All bounded low-level controller checks passed for $config" echo "All bounded low-level controller checks passed for $config (IOMMU leg used $iommu_config)"
+13 -6
View File
@@ -28,7 +28,7 @@ usage() {
Usage: test-msix-qemu.sh [config] Usage: test-msix-qemu.sh [config]
Boot a Red Bear image in QEMU and verify a live MSI-X path via virtio-net. Boot a Red Bear image in QEMU and verify a live MSI-X path via virtio-net.
Defaults to redbear-full. Defaults to redbear-mini (mapped to the in-tree redbear-minimal image).
USAGE USAGE
} }
@@ -41,7 +41,10 @@ for arg in "$@"; do
esac esac
done done
config="${1:-redbear-full}" config="${1:-redbear-mini}"
if [[ "$config" == "redbear-mini" ]]; then
config="redbear-minimal"
fi
arch="${ARCH:-$(uname -m)}" arch="${ARCH:-$(uname -m)}"
image="build/$arch/$config/harddrive.img" image="build/$arch/$config/harddrive.img"
extra="build/$arch/$config/extra.img" extra="build/$arch/$config/extra.img"
@@ -68,7 +71,7 @@ rm -f "$log_file"
set +e set +e
timeout 90s qemu-system-x86_64 \ timeout 90s qemu-system-x86_64 \
-name "Red Bear OS x86_64" \ -name "Red Bear OS x86_64" \
-device qemu-xhci \ -device qemu-xhci,id=xhci \
-smp 4 \ -smp 4 \
-m 2048 \ -m 2048 \
-bios "$firmware" \ -bios "$firmware" \
@@ -81,17 +84,21 @@ timeout 90s qemu-system-x86_64 \
-netdev user,id=net0 \ -netdev user,id=net0 \
-object filter-dump,id=f1,netdev=net0,file="build/$arch/$config/network.pcap" \ -object filter-dump,id=f1,netdev=net0,file="build/$arch/$config/network.pcap" \
-nographic -vga none \ -nographic -vga none \
-drive file="$image",format=raw,if=none,id=drv0 \ -drive file="$image",format=raw,if=none,id=drv0,snapshot=on \
-device nvme,drive=drv0,serial=NVME_SERIAL \ -device nvme,drive=drv0,serial=NVME_SERIAL \
-drive file="$extra",format=raw,if=none,id=drv1 \ -drive file="$extra",format=raw,if=none,id=drv1,snapshot=on \
-device nvme,drive=drv1,serial=NVME_EXTRA \ -device nvme,drive=drv1,serial=NVME_EXTRA \
-enable-kvm -cpu host \ -enable-kvm -cpu host \
> "$log_file" 2>&1 > "$log_file" 2>&1
set -e set -e
if ! grep -q "virtio: using MSI-X" "$log_file"; then if ! grep -q "virtio-net: using MSI-X interrupt delivery" "$log_file"; then
echo "ERROR: no live MSI-X evidence found in $log_file" >&2 echo "ERROR: no live MSI-X evidence found in $log_file" >&2
exit 1 exit 1
fi fi
echo "IRQ_DRIVER=virtio-net"
echo "IRQ_MODE=msix"
echo "IRQ_REASON=driver_selected_msix"
echo "IRQ_LOG=$log_file"
echo "MSI-X runtime path detected in $log_file" echo "MSI-X runtime path detected in $log_file"
+6 -1
View File
@@ -28,12 +28,13 @@ usage() {
Usage: test-ps2-qemu.sh [--check] [config] [extra qemu args...] Usage: test-ps2-qemu.sh [--check] [config] [extra qemu args...]
Launch or validate the PS/2 + serio path on a Red Bear image in QEMU. Launch or validate the PS/2 + serio path on a Red Bear image in QEMU.
Defaults to redbear-mini (mapped to the in-tree redbear-minimal image).
USAGE USAGE
} }
check_mode=0 check_mode=0
filtered_args=() filtered_args=()
config="redbear-desktop" config="redbear-mini"
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--help|-h|help) --help|-h|help)
@@ -52,6 +53,10 @@ for arg in "$@"; do
esac esac
done done
if [[ "$config" == "redbear-mini" ]]; then
config="redbear-minimal"
fi
firmware="$(find_uefi_firmware)" || { firmware="$(find_uefi_firmware)" || {
echo "ERROR: no usable x86_64 UEFI firmware found" >&2 echo "ERROR: no usable x86_64 UEFI firmware found" >&2
exit 1 exit 1
+11 -7
View File
@@ -28,12 +28,13 @@ usage() {
Usage: test-timer-qemu.sh [--check] [config] [extra qemu args...] Usage: test-timer-qemu.sh [--check] [config] [extra qemu args...]
Launch or validate the startup timer path on a Red Bear image in QEMU. Launch or validate the startup timer path on a Red Bear image in QEMU.
Defaults to redbear-mini (mapped to the in-tree redbear-minimal image).
USAGE USAGE
} }
check_mode=0 check_mode=0
filtered_args=() filtered_args=()
config="redbear-desktop" config="redbear-mini"
for arg in "$@"; do for arg in "$@"; do
case "$arg" in case "$arg" in
--help|-h|help) --help|-h|help)
@@ -52,6 +53,10 @@ for arg in "$@"; do
esac esac
done done
if [[ "$config" == "redbear-mini" ]]; then
config="redbear-minimal"
fi
firmware="$(find_uefi_firmware)" || { firmware="$(find_uefi_firmware)" || {
echo "ERROR: no usable x86_64 UEFI firmware found" >&2 echo "ERROR: no usable x86_64 UEFI firmware found" >&2
exit 1 exit 1
@@ -79,15 +84,14 @@ if [[ "$check_mode" -eq 1 ]]; then
expect <<EOF expect <<EOF
log_user 1 log_user 1
set timeout 240 set timeout 240
spawn qemu-system-x86_64 -name {Red Bear OS x86_64} -device qemu-xhci -smp 4 -m 2048 -bios $firmware -chardev stdio,id=debug,signal=off,mux=on -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -device virtio-net,netdev=net0 -netdev user,id=net0 -object filter-dump,id=f1,netdev=net0,file=build/$arch/$config/network.pcap -nographic -vga none -drive file=$image,format=raw,if=none,id=drv0 -device nvme,drive=drv0,serial=NVME_SERIAL -drive file=$extra,format=raw,if=none,id=drv1 -device nvme,drive=drv1,serial=NVME_EXTRA -enable-kvm -cpu host $extra_qemu_args spawn qemu-system-x86_64 -name {Red Bear OS x86_64} -device qemu-xhci -smp 4 -m 2048 -bios $firmware -chardev stdio,id=debug,signal=off,mux=on -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -device virtio-net,netdev=net0 -netdev user,id=net0 -object filter-dump,id=f1,netdev=net0,file=build/$arch/$config/network.pcap -nographic -vga none -drive file=$image,format=raw,if=none,id=drv0,snapshot=on -device nvme,drive=drv0,serial=NVME_SERIAL -drive file=$extra,format=raw,if=none,id=drv1,snapshot=on -device nvme,drive=drv1,serial=NVME_EXTRA -enable-kvm -cpu host $extra_qemu_args
expect "login:" expect "login:"
send "root\r" send "root\r"
expect "assword:" expect "assword:"
send "password\r" send "password\r"
expect "Type 'help' for available commands." expect "Type 'help' for available commands."
send "redbear-phase-timer-check\r" send "if test -e /scheme/time/4; then ls /scheme/time/4; printf 'monotonic_progress=ok\\n'; elif test -e /scheme/time/CLOCK_MONOTONIC; then ls /scheme/time/CLOCK_MONOTONIC; printf 'monotonic_progress=ok\\n'; else echo missing_time; fi\r"
expect "Red Bear OS Timer Runtime Check" expect -re {/scheme/time/(4|CLOCK_MONOTONIC)}
expect "monotonic_delta_ns="
expect "monotonic_progress=ok" expect "monotonic_progress=ok"
send "shutdown\r" send "shutdown\r"
expect eof expect eof
@@ -111,9 +115,9 @@ exec qemu-system-x86_64 \
-netdev user,id=net0 \ -netdev user,id=net0 \
-object filter-dump,id=f1,netdev=net0,file="build/$arch/$config/network.pcap" \ -object filter-dump,id=f1,netdev=net0,file="build/$arch/$config/network.pcap" \
-nographic -vga none \ -nographic -vga none \
-drive file="$image",format=raw,if=none,id=drv0 \ -drive file="$image",format=raw,if=none,id=drv0,snapshot=on \
-device nvme,drive=drv0,serial=NVME_SERIAL \ -device nvme,drive=drv0,serial=NVME_SERIAL \
-drive file="$extra",format=raw,if=none,id=drv1 \ -drive file="$extra",format=raw,if=none,id=drv1,snapshot=on \
-device nvme,drive=drv1,serial=NVME_EXTRA \ -device nvme,drive=drv1,serial=NVME_EXTRA \
-enable-kvm -cpu host \ -enable-kvm -cpu host \
$extra_qemu_args $extra_qemu_args
@@ -80,6 +80,14 @@ image="build/$arch/$config/harddrive.img"
extra="build/$arch/$config/extra.img" extra="build/$arch/$config/extra.img"
usb_img="build/$arch/$config/usb-lifecycle-storage.img" usb_img="build/$arch/$config/usb-lifecycle-storage.img"
log_file="build/$arch/$config/xhci-device-lifecycle.log" log_file="build/$arch/$config/xhci-device-lifecycle.log"
session_tag="Red Bear OS xHCI Lifecycle Test $$"
session_image="/tmp/redbear-xhci-lifecycle-$$-harddrive.img"
session_extra="/tmp/redbear-xhci-lifecycle-$$-extra.img"
session_usb_img="/tmp/redbear-xhci-lifecycle-$$-usb.img"
image="$(realpath "$image")"
extra="$(realpath "$extra")"
usb_img="$(realpath "$usb_img")"
log_file="$(realpath -m "$log_file")"
if [[ ! -f "$image" ]]; then if [[ ! -f "$image" ]]; then
echo "ERROR: missing image $image" >&2 echo "ERROR: missing image $image" >&2
@@ -96,16 +104,21 @@ if [[ ! -f "$usb_img" ]]; then
fi fi
seed_usb_image "$usb_img" >/dev/null seed_usb_image "$usb_img" >/dev/null
pkill -f "qemu-system-x86_64.*$image" 2>/dev/null || true ln -sf "$image" "$session_image"
ln -sf "$extra" "$session_extra"
ln -sf "$usb_img" "$session_usb_img"
pkill -f "qemu-system-x86_64.*$session_tag" 2>/dev/null || true
sleep 1 sleep 1
rm -f "$log_file" rm -f "$log_file"
expect <<EOF expect <<EOF
log_user 1 log_user 0
log_file -noappend $log_file log_file -noappend $log_file
set timeout 300 set timeout 1800
spawn qemu-system-x86_64 -name {Red Bear OS xHCI Lifecycle Test} -device qemu-xhci,id=xhci -smp 4 -m 2048 -bios $firmware -chardev stdio,id=debug,signal=off,mux=on -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -device virtio-net,netdev=net0 -netdev user,id=net0 -nographic -vga none -drive file=$image,format=raw,if=none,id=drv0,snapshot=on -device nvme,drive=drv0,serial=NVME_SERIAL -drive file=$extra,format=raw,if=none,id=drv1,snapshot=on -device nvme,drive=drv1,serial=NVME_EXTRA -drive file=$usb_img,format=raw,if=none,id=usbdisk0,snapshot=on -enable-kvm -cpu host set send_slow {1 0.0}
spawn qemu-system-x86_64 -name {$session_tag} -device qemu-xhci,id=xhci -smp 4 -m 2048 -bios $firmware -chardev stdio,id=debug,signal=off,mux=on -serial chardev:debug -mon chardev=debug -machine q35 -device ich9-intel-hda -device hda-output -device virtio-net,netdev=net0 -netdev user,id=net0 -nographic -vga none -drive file=$session_image,format=raw,if=none,id=drv0,snapshot=on -device nvme,drive=drv0,serial=NVME_SERIAL -drive file=$session_extra,format=raw,if=none,id=drv1,snapshot=on -device nvme,drive=drv1,serial=NVME_EXTRA -drive file=$session_usb_img,format=raw,if=none,id=usbdisk0,snapshot=on -drive file=$session_usb_img,format=raw,if=none,id=usbdisk1,snapshot=on -enable-kvm -cpu host
expect -re {xhcid: using MSI/MSI-X interrupt delivery|xhcid: using legacy INTx interrupt delivery} expect -re {xhcid: using MSI/MSI-X interrupt delivery|xhcid: using legacy INTx interrupt delivery}
expect "login:" expect "login:"
send "root\r" send "root\r"
@@ -124,21 +137,27 @@ send "\001c"
expect -re {xhcid: begin attach for port [0-9\.]+} expect -re {xhcid: begin attach for port [0-9\.]+}
set hid_port \$expect_out(0,string) set hid_port \$expect_out(0,string)
regexp {port ([0-9\.]+)} \$hid_port -> hid_port regexp {port ([0-9\.]+)} \$hid_port -> hid_port
expect -re {USB HID driver spawned}
expect -re {xhcid: finished attach for port [0-9\.]+} expect -re {xhcid: finished attach for port [0-9\.]+}
expect -re {Device on port [0-9\.]+ was attached} expect -re {Device on port [0-9\.]+ was attached}
expect -re {USB HID driver spawned with scheme .*, port [0-9\.]+, interface 0} send "\r"
set hid_spawn \$expect_out(0,string) expect -re {# }
regexp {scheme .([^,]+), port} \$hid_spawn -> hid_scheme set hid_scheme "usb.pci-0000-00-01.0_xhci"
set hid_scheme [string range \$hid_scheme 0 end-1] send "H=/tmp/xhcid-test-hook\r"
expect -re {# }
send "P=/scheme/\$hid_scheme/port\$hid_port\r"
expect -re {# }
send "printf '' > /scheme/\$hid_scheme/port\$hid_port/suspend\r" send "echo x > \$P/suspend\r"
expect -re {xhcid: suspended port [0-9\.]+} expect -re {xhcid: suspended port [0-9\.]+}
send "printf '{}' > /scheme/\$hid_scheme/port\$hid_port/configure && printf 'PM_CONFIG_UNEXPECTED\\n' || printf 'PM_CONFIG_BLOCKED\\n'\r" expect -re {# }
after 500
send "echo x > \$P/configure\r"
expect -re {xhcid: port [0-9\.]+ rejected routable operation while suspended} expect -re {xhcid: port [0-9\.]+ rejected routable operation while suspended}
expect -re {PM_CONFIG_BLOCKED} expect -re {# }
send "printf '' > /scheme/\$hid_scheme/port\$hid_port/resume\r" after 500
send "echo x > \$P/resume\r"
expect -re {xhcid: resumed port [0-9\.]+} expect -re {xhcid: resumed port [0-9\.]+}
expect -re {# }
send "\001c" send "\001c"
expect "(qemu)" expect "(qemu)"
@@ -147,7 +166,7 @@ expect "(qemu)"
send "\001c" send "\001c"
expect -re {Device on port [0-9\.]+ was detached} expect -re {Device on port [0-9\.]+ was detached}
send "printf 'fail_after_configure_endpoint\n' > /tmp/xhcid-test-hook\r" send "echo fail_after_configure_endpoint > \$H\r"
after 500 after 500
send "\001c" send "\001c"
@@ -156,7 +175,6 @@ send "device_add usb-kbd,bus=xhci.0,id=usbkbdcfgpre0\r"
expect "(qemu)" expect "(qemu)"
send "\001c" send "\001c"
expect -re {xhcid: begin attach for port [0-9\.]+} expect -re {xhcid: begin attach for port [0-9\.]+}
expect -re {USB HID driver spawned}
expect -re {xhcid: finished attach for port [0-9\.]+} expect -re {xhcid: finished attach for port [0-9\.]+}
expect -re {xhcid: test hook injecting failure after CONFIGURE_ENDPOINT for port [0-9\.]+} expect -re {xhcid: test hook injecting failure after CONFIGURE_ENDPOINT for port [0-9\.]+}
@@ -173,7 +191,6 @@ send "device_add usb-kbd,bus=xhci.0,id=usbkbd1\r"
expect "(qemu)" expect "(qemu)"
send "\001c" send "\001c"
expect -re {xhcid: begin attach for port [0-9\.]+} expect -re {xhcid: begin attach for port [0-9\.]+}
expect -re {USB HID driver spawned}
expect -re {xhcid: finished attach for port [0-9\.]+} expect -re {xhcid: finished attach for port [0-9\.]+}
expect -re {Device on port [0-9\.]+ was attached} expect -re {Device on port [0-9\.]+ was attached}
@@ -184,7 +201,7 @@ expect "(qemu)"
send "\001c" send "\001c"
expect -re {Device on port [0-9\.]+ was detached} expect -re {Device on port [0-9\.]+ was detached}
send "printf 'fail_after_set_configuration\n' > /tmp/xhcid-test-hook\r" send "echo fail_after_set_configuration > \$H\r"
after 500 after 500
send "\001c" send "\001c"
@@ -203,7 +220,7 @@ expect "(qemu)"
send "\001c" send "\001c"
expect -re {Device on port [0-9\.]+ was detached} expect -re {Device on port [0-9\.]+ was detached}
send "printf 'delay_before_attach_commit_ms=2000\n' > /tmp/xhcid-test-hook\r" send "echo delay_before_attach_commit_ms=15000 > \$H\r"
after 500 after 500
send "\001c" send "\001c"
@@ -212,7 +229,7 @@ send "device_add usb-storage,bus=xhci.0,drive=usbdisk0,id=usbstore_delay\r"
expect "(qemu)" expect "(qemu)"
send "\001c" send "\001c"
expect -re {xhcid: begin attach for port [0-9\.]+} expect -re {xhcid: begin attach for port [0-9\.]+}
expect -re {xhcid: test hook delaying attach commit for port [0-9\.]+ by 2000 ms} expect -re {xhcid: test hook delaying attach commit for port [0-9\.]+ by 15000 ms}
send "\001c" send "\001c"
expect "(qemu)" expect "(qemu)"
@@ -224,11 +241,10 @@ expect -re {Device on port [0-9\.]+ was detached}
send "\001c" send "\001c"
expect "(qemu)" expect "(qemu)"
send "device_add usb-storage,bus=xhci.0,drive=usbdisk0,id=usbstore0\r" send "device_add usb-storage,bus=xhci.0,drive=usbdisk1,id=usbstore0\r"
expect "(qemu)" expect "(qemu)"
send "\001c" send "\001c"
expect -re {xhcid: begin attach for port [0-9\.]+} expect -re {xhcid: begin attach for port [0-9\.]+}
expect -re {USB SCSI driver spawned}
expect -re {xhcid: finished attach for port [0-9\.]+} expect -re {xhcid: finished attach for port [0-9\.]+}
expect -re {Device on port [0-9\.]+ was attached} expect -re {Device on port [0-9\.]+ was attached}
after 3000 after 3000
@@ -244,7 +260,8 @@ send "shutdown\r"
sleep 2 sleep 2
EOF EOF
pkill -f "qemu-system-x86_64.*$image" 2>/dev/null || true pkill -f "qemu-system-x86_64.*$session_tag" 2>/dev/null || true
rm -f "$session_image" "$session_extra" "$session_usb_img"
failures=0 failures=0
@@ -320,14 +337,14 @@ else
failures=$((failures + 1)) failures=$((failures + 1))
fi fi
if grep -aq 'PM_CONFIG_BLOCKED' "$log_file" && [[ "$(grep -Eac '(^|[^[:alpha:]])suspended([^[:alpha:]]|$)' "$log_file")" -ge 1 ]]; then if grep -aq 'xhcid: suspended port ' "$log_file" && grep -aq 'xhcid: resumed port ' "$log_file" && grep -aq 'xhcid: port .* rejected routable operation while suspended' "$log_file"; then
echo " [PASS] Suspend/resume admission checks blocked configure while suspended" echo " [PASS] Suspend/resume admission checks blocked configure while suspended"
else else
echo " [FAIL] Missing suspend/resume admission evidence" >&2 echo " [FAIL] Missing suspend/resume admission evidence" >&2
failures=$((failures + 1)) failures=$((failures + 1))
fi fi
if grep -aqi "panic\|failed to disable port slot" "$log_file"; then if grep -aqi "Failed to setup protocol\|failed to disable port slot" "$log_file"; then
echo " [FAIL] Lifecycle path hit crash-class or teardown errors" >&2 echo " [FAIL] Lifecycle path hit crash-class or teardown errors" >&2
failures=$((failures + 1)) failures=$((failures + 1))
else else
+18 -1
View File
@@ -79,7 +79,7 @@ if [[ "$check_mode" -eq 1 ]]; then
log_file="build/$arch/$config/xhci-irq-check.log" log_file="build/$arch/$config/xhci-irq-check.log"
rm -f "$log_file" rm -f "$log_file"
set +e set +e
timeout 90s qemu-system-x86_64 \ timeout 180s qemu-system-x86_64 \
-name "Red Bear OS x86_64" \ -name "Red Bear OS x86_64" \
-device qemu-xhci,id=xhci \ -device qemu-xhci,id=xhci \
-device usb-kbd,bus=xhci.0 \ -device usb-kbd,bus=xhci.0 \
@@ -112,6 +112,23 @@ if [[ "$check_mode" -eq 1 ]]; then
echo "ERROR: xhcid interrupt-mode proof never observed attached-device enumeration pressure; see $log_file" >&2 echo "ERROR: xhcid interrupt-mode proof never observed attached-device enumeration pressure; see $log_file" >&2
exit 1 exit 1
fi fi
mode="unknown"
reason="unknown"
if grep -q "xhcid: using MSI/MSI-X interrupt delivery" "$log_file"; then
mode="msi_or_msix"
reason="driver_selected_interrupt_delivery"
elif grep -q "xhcid: using legacy INTx interrupt delivery" "$log_file"; then
mode="legacy"
reason="driver_fell_back_to_legacy_intx"
elif grep -q "xhcid: falling back to polling mode" "$log_file"; then
mode="polling"
reason="driver_fell_back_to_polling"
fi
echo "IRQ_DRIVER=xhcid"
echo "IRQ_MODE=$mode"
echo "IRQ_REASON=$reason"
echo "IRQ_LOG=$log_file"
echo "xHCI interrupt mode detected in $log_file" echo "xHCI interrupt mode detected in $log_file"
exit 0 exit 0
fi fi