From 5a43628d58ab2c32fad0f560efbd2241597f1947 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Fri, 24 Jul 2026 12:32:05 +0900 Subject: [PATCH] pcid: AER + pciehp event producers (P2-1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New events module: a 500ms poller thread scans every enumerated device and produces two read-only event streams: - /scheme/pci/aer — uncorrectable (NonFatal) and correctable AER status from the PCIe AER extended capability, emitted as 'severity= device= raw=...' lines and W1C-cleared - /scheme/pci/pciehp — Slot Status events from hot-plug-capable ports (presence detect, DLL state, attention button, MRL sensor, power fault), emitted as 'kind= device=' lines and W1C-cleared Events accumulate in capped in-memory logs (64 lines) served by the scheme; device strings use the scheme dir-name format (0000--00--1f.2) so driver-manager's route_to_driver matches bound paths. The Pcie handle is now Arc-shared with the poller. Extended capabilities need ECAM; on PCI 3.0 fallback machines the scanners gracefully produce nothing. This wires driver-manager's previously inert unified event listener to real producers. --- drivers/pcid/src/events.rs | 217 +++++++++++++++++++++++++++++++++++++ drivers/pcid/src/main.rs | 11 +- drivers/pcid/src/scheme.rs | 71 +++++++++--- 3 files changed, 284 insertions(+), 15 deletions(-) create mode 100644 drivers/pcid/src/events.rs diff --git a/drivers/pcid/src/events.rs b/drivers/pcid/src/events.rs new file mode 100644 index 0000000000..706c9875ff --- /dev/null +++ b/drivers/pcid/src/events.rs @@ -0,0 +1,217 @@ +//! AER and PCIe-native-hotplug event producers. +//! +//! A poller thread scans every enumerated device every 500 ms: +//! - AER: reads the PCIe AER extended capability's uncorrectable and +//! correctable error status registers, emits one line per event, and +//! write-1-to-clears the bits (the registers are W1C by spec). +//! - pciehp: reads the Slot Status register of hot-plug-capable ports and +//! emits one line per event bit (presence detect, DLL state, attention +//! button, MRL sensor, power fault), then W1C-clears them. +//! +//! Events accumulate in capped in-memory logs served read-only as +//! `/scheme/pci/aer` and `/scheme/pci/pciehp`. Consumers (driver-manager's +//! unified listener) hash lines to deduplicate across polls, matching the +//! polling-fallback model in the migration plan. Proper MSI-based delivery +//! is the follow-up once pcid grows an IRQ path for AER/hotplug. + +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::Duration; + +use log::{debug, info}; +use pci_types::{ConfigRegionAccess, PciAddress}; + +use crate::cfg_access::Pcie; + +const MAX_EVENTS: usize = 64; +const POLL_INTERVAL_MS: u64 = 500; + +const AER_CAP_ID: u16 = 0x0001; +const AER_UNCORRECTABLE_STATUS: u16 = 0x04; +const AER_CORRECTABLE_STATUS: u16 = 0x10; + +const PCIE_CAP_ID: u8 = 0x10; +const SLOT_CAPABILITIES: u16 = 0x0C; +const SLOT_CONTROL_STATUS: u16 = 0x18; +const SLOT_CAP_HOT_PLUG_CAPABLE: u32 = 1 << 6; + +const SLTSTA_ATTENTION_BUTTON: u32 = 1 << 0; +const SLTSTA_POWER_FAULT: u32 = 1 << 1; +const SLTSTA_MRL_SENSOR: u32 = 1 << 2; +const SLTSTA_PRESENCE_DETECT: u32 = 1 << 3; +const SLTSTA_DLL_STATE: u32 = 1 << 5; + + +fn scheme_name(addr: &PciAddress) -> String { + format!("{}", addr).replace(':', "--") +} + +pub struct EventLog { + aer: Mutex>, + pciehp: Mutex>, +} + +impl EventLog { + pub fn new() -> Self { + Self { + aer: Mutex::new(VecDeque::new()), + pciehp: Mutex::new(VecDeque::new()), + } + } + + fn push(log: &Mutex>, line: String) { + let mut queue = log.lock().unwrap_or_else(|e| e.into_inner()); + if queue.len() >= MAX_EVENTS { + queue.pop_front(); + } + queue.push_back(line); + } + + fn snapshot(log: &Mutex>) -> String { + let queue = log.lock().unwrap_or_else(|e| e.into_inner()); + queue.iter().cloned().collect() + } + + pub fn push_aer(&self, line: String) { + Self::push(&self.aer, line); + } + + pub fn push_pciehp(&self, line: String) { + Self::push(&self.pciehp, line); + } + + pub fn aer_snapshot(&self) -> String { + Self::snapshot(&self.aer) + } + + pub fn pciehp_snapshot(&self) -> String { + Self::snapshot(&self.pciehp) + } +} + +pub fn spawn_event_poller( + pcie: Arc, + devices: Vec, + log: Arc, +) -> thread::JoinHandle<()> { + thread::Builder::new() + .name("pcid-events".to_string()) + .spawn(move || { + info!( + "pcid-events: AER/pciehp poller started ({} devices, {} ms poll)", + devices.len(), + POLL_INTERVAL_MS + ); + loop { + poll_aer(&pcie, &devices, &log); + poll_pciehp(&pcie, &devices, &log); + thread::sleep(Duration::from_millis(POLL_INTERVAL_MS)); + } + }) + .expect("spawn pcid event poller") +} + +fn poll_aer(pcie: &Pcie, devices: &[PciAddress], log: &EventLog) { + for addr in devices { + let Some(cap_off) = find_extended_capability(pcie, *addr, AER_CAP_ID) else { + continue; + }; + + let uncorrectable = unsafe { pcie.read(*addr, cap_off + AER_UNCORRECTABLE_STATUS) }; + if uncorrectable != 0 && uncorrectable != u32::MAX { + debug!("AER: uncorrectable error on {addr}: 0x{uncorrectable:08x}"); + log.push_aer(format!( + "severity=NonFatal device={} raw=uncorrectable_status=0x{uncorrectable:08x}\n", scheme_name(addr) + )); + unsafe { pcie.write(*addr, cap_off + AER_UNCORRECTABLE_STATUS, uncorrectable) }; + } + + let correctable = unsafe { pcie.read(*addr, cap_off + AER_CORRECTABLE_STATUS) }; + if correctable != 0 && correctable != u32::MAX { + debug!("AER: correctable error on {addr}: 0x{correctable:08x}"); + log.push_aer(format!( + "severity=Correctable device={} raw=correctable_status=0x{correctable:08x}\n", scheme_name(addr) + )); + unsafe { pcie.write(*addr, cap_off + AER_CORRECTABLE_STATUS, correctable) }; + } + } +} + +fn poll_pciehp(pcie: &Pcie, devices: &[PciAddress], log: &EventLog) { + for addr in devices { + let Some(cap_off) = find_standard_capability(pcie, *addr, PCIE_CAP_ID) else { + continue; + }; + + let slot_capabilities = unsafe { pcie.read(*addr, cap_off + SLOT_CAPABILITIES) }; + if slot_capabilities == u32::MAX || slot_capabilities & SLOT_CAP_HOT_PLUG_CAPABLE == 0 { + continue; + } + + let status = unsafe { pcie.read(*addr, cap_off + SLOT_CONTROL_STATUS) } >> 16; + if status == 0 || status == 0xFFFF { + continue; + } + + if status & SLTSTA_ATTENTION_BUTTON != 0 { + log.push_pciehp(format!("kind=attention_button device={}\n", scheme_name(addr))); + } + if status & SLTSTA_POWER_FAULT != 0 { + log.push_pciehp(format!("kind=power_fault device={}\n", scheme_name(addr))); + } + if status & SLTSTA_MRL_SENSOR != 0 { + log.push_pciehp(format!("kind=mrl_sensor device={}\n", scheme_name(addr))); + } + if status & SLTSTA_PRESENCE_DETECT != 0 { + log.push_pciehp(format!("kind=presence_detect device={}\n", scheme_name(addr))); + } + if status & SLTSTA_DLL_STATE != 0 { + log.push_pciehp(format!("kind=dll_state device={}\n", scheme_name(addr))); + } + + // Write-1-to-clear the event bits in Slot Status (upper word of the + // Slot Control/Status dword), preserving the current Slot Control + // value in the lower word. + let control_status = unsafe { pcie.read(*addr, cap_off + SLOT_CONTROL_STATUS) }; + let clear_value = (control_status & 0x0000_FFFF) | (status << 16); + unsafe { pcie.write(*addr, cap_off + SLOT_CONTROL_STATUS, clear_value) }; + } +} + +fn find_extended_capability(pcie: &Pcie, addr: PciAddress, cap_id: u16) -> Option { + let mut offset = 0x100u16; + for _ in 0..48 { + if offset == 0 { + return None; + } + let header = unsafe { pcie.read(addr, offset) }; + if header == u32::MAX || header == 0 { + return None; + } + if (header & 0xFFFF) as u16 == cap_id { + return Some(offset); + } + offset = ((header >> 20) & 0xFFF) as u16; + } + None +} + +fn find_standard_capability(pcie: &Pcie, addr: PciAddress, cap_id: u8) -> Option { + let command_status = unsafe { pcie.read(addr, 0x04) }; + if (command_status >> 16) & 0x10 == 0 { + return None; + } + let mut ptr = (unsafe { pcie.read(addr, 0x34) } & 0xFC) as u16; + for _ in 0..48 { + if ptr == 0 { + return None; + } + let entry = unsafe { pcie.read(addr, ptr) }; + if (entry & 0xFF) as u8 == cap_id { + return Some(ptr); + } + ptr = ((entry >> 8) & 0xFC) as u16; + } + None +} diff --git a/drivers/pcid/src/main.rs b/drivers/pcid/src/main.rs index d489328713..dff3eb0258 100644 --- a/drivers/pcid/src/main.rs +++ b/drivers/pcid/src/main.rs @@ -3,6 +3,7 @@ #![feature(non_exhaustive_omitted_patterns_lint)] use std::collections::BTreeMap; +use std::sync::Arc; use log::{debug, info, trace, warn}; use pci_types::capability::PciCapability; @@ -18,6 +19,7 @@ use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, Pci mod cfg_access; mod driver_handler; +mod events; mod scheme; pub struct Func { @@ -284,11 +286,11 @@ fn daemon(daemon: daemon::Daemon) -> ! { common::file_level(), ); - let pcie = Pcie::new(); + let pcie = Arc::new(Pcie::new()); info!("PCI SG-BS:DV.F VEND:DEVI CL.SC.IN.RV"); - let mut scheme = scheme::PciScheme::new(pcie); + let mut scheme = scheme::PciScheme::new(Arc::clone(&pcie)); let socket = redox_scheme::Socket::create().expect("failed to open pci scheme socket"); let handler = Blocking::new(&socket, 16); @@ -342,6 +344,11 @@ fn daemon(daemon: daemon::Daemon) -> ! { } debug!("Enumeration complete, now starting pci scheme"); + let event_log = Arc::new(events::EventLog::new()); + let devices: Vec = scheme.tree.keys().copied().collect(); + scheme.set_event_log(Arc::clone(&event_log)); + let _events_thread = events::spawn_event_poller(Arc::clone(&pcie), devices, event_log); + register_sync_scheme(&socket, "pci", &mut scheme) .expect("failed to register pci scheme to namespace"); diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs index 8dc7136e70..e615d9b4c8 100644 --- a/drivers/pcid/src/scheme.rs +++ b/drivers/pcid/src/scheme.rs @@ -6,7 +6,7 @@ use redox_scheme::{CallerCtx, OpenResult}; use scheme_utils::HandleMap; use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; -use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT}; +use syscall::flag::{MODE_CHR, MODE_DIR, MODE_FILE, O_DIRECTORY, O_STAT}; use syscall::schemev2::NewFdFlags; use syscall::ENOLCK; @@ -14,8 +14,9 @@ use crate::cfg_access::Pcie; pub struct PciScheme { handles: HandleMap, - pub pcie: Pcie, + pub pcie: std::sync::Arc, pub tree: BTreeMap, + events: std::sync::Arc, } enum Handle { TopLevel { entries: Vec }, @@ -23,6 +24,8 @@ enum Handle { Device, Config { addr: PciAddress }, Channel { addr: PciAddress, st: ChannelState }, + Aer, + Pciehp, SchemeRoot, } struct HandleWrapper { @@ -33,7 +36,11 @@ impl Handle { fn is_file(&self) -> bool { matches!( self, - Self::Access | Self::Config { .. } | Self::Channel { .. } + Self::Access + | Self::Config { .. } + | Self::Channel { .. } + | Self::Aer + | Self::Pciehp ) } fn is_dir(&self) -> bool { @@ -95,16 +102,21 @@ impl SchemeSync for PciScheme { let path = path.trim_matches('/'); let handle = if path.is_empty() { - Handle::TopLevel { - entries: self - .tree - .iter() - // FIXME remove replacement of : once the old scheme format is no longer supported. - .map(|(addr, _)| format!("{}", addr).replace(':', "--")) - .collect::>(), - } + let mut entries: Vec = self + .tree + .iter() + // FIXME remove replacement of : once the old scheme format is no longer supported. + .map(|(addr, _)| format!("{}", addr).replace(':', "--")) + .collect::>(); + entries.push("aer".to_string()); + entries.push("pciehp".to_string()); + Handle::TopLevel { entries } } else if path == "access" { Handle::Access + } else if path == "aer" { + Handle::Aer + } else if path == "pciehp" { + Handle::Pciehp } else { let idx = path.find('/').unwrap_or(path.len()); let (addr_str, after) = path.split_at(idx); @@ -142,6 +154,8 @@ impl SchemeSync for PciScheme { Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), Handle::Config { .. } => (config_size, MODE_CHR | 0o600), Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), + Handle::Aer => (self.events.aer_snapshot().len(), MODE_FILE | 0o444), + Handle::Pciehp => (self.events.pciehp_snapshot().len(), MODE_FILE | 0o444), Handle::SchemeRoot => return Err(Error::new(EBADF)), }; stat.st_size = len as u64; @@ -186,6 +200,28 @@ impl SchemeSync for PciScheme { addr: _, ref mut st, } => Self::read_channel(st, buf), + Handle::Aer => { + let data = self.events.aer_snapshot(); + let bytes = data.as_bytes(); + let offset = usize::try_from(_offset).map_err(|_| Error::new(EINVAL))?; + if offset >= bytes.len() { + return Ok(0); + } + let count = (bytes.len() - offset).min(buf.len()); + buf[..count].copy_from_slice(&bytes[offset..offset + count]); + Ok(count) + } + Handle::Pciehp => { + let data = self.events.pciehp_snapshot(); + let bytes = data.as_bytes(); + let offset = usize::try_from(_offset).map_err(|_| Error::new(EINVAL))?; + if offset >= bytes.len() { + return Ok(0); + } + let count = (bytes.len() - offset).min(buf.len()); + buf[..count].copy_from_slice(&bytes[offset..offset + count]); + Ok(count) + } Handle::SchemeRoot => Err(Error::new(EBADF)), _ => Err(Error::new(EBADF)), } @@ -219,7 +255,11 @@ impl SchemeSync for PciScheme { return Ok(buf); } Handle::Device => DEVICE_CONTENTS, - Handle::Access | Handle::Config { .. } | Handle::Channel { .. } => { + Handle::Access + | Handle::Config { .. } + | Handle::Channel { .. } + | Handle::Aer + | Handle::Pciehp => { return Err(Error::new(ENOTDIR)); } Handle::SchemeRoot => return Err(Error::new(EBADF)), @@ -383,11 +423,16 @@ impl PciScheme { if self.pcie.has_ecam() { 4096 } else { 256 } } - pub fn new(pcie: Pcie) -> Self { + pub fn set_event_log(&mut self, events: std::sync::Arc) { + self.events = events; + } + + pub fn new(pcie: std::sync::Arc) -> Self { Self { handles: HandleMap::new(), pcie, tree: BTreeMap::new(), + events: std::sync::Arc::new(crate::events::EventLog::new()), } } fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result {