Expand base overlay patches and controller proofs
This commit is contained in:
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
+5995
-5774
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"
|
||||||
@@ -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)"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,18 +84,17 @@ 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
|
|
||||||
EOF
|
EOF
|
||||||
echo "Timer runtime validation completed via guest runtime check"
|
echo "Timer runtime validation completed via guest runtime check"
|
||||||
exit 0
|
exit 0
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user