pcid: AER + pciehp event producers (P2-1)

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=<level> device=<bdf> 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=<event> device=<bdf>' 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.
This commit is contained in:
Red Bear OS
2026-07-24 12:32:05 +09:00
parent 9cd43d8d8c
commit 5a43628d58
3 changed files with 284 additions and 15 deletions
+217
View File
@@ -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<VecDeque<String>>,
pciehp: Mutex<VecDeque<String>>,
}
impl EventLog {
pub fn new() -> Self {
Self {
aer: Mutex::new(VecDeque::new()),
pciehp: Mutex::new(VecDeque::new()),
}
}
fn push(log: &Mutex<VecDeque<String>>, 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<VecDeque<String>>) -> 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<Pcie>,
devices: Vec<PciAddress>,
log: Arc<EventLog>,
) -> 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<u16> {
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<u16> {
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
}
+9 -2
View File
@@ -3,6 +3,7 @@
#![feature(non_exhaustive_omitted_patterns_lint)] #![feature(non_exhaustive_omitted_patterns_lint)]
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::sync::Arc;
use log::{debug, info, trace, warn}; use log::{debug, info, trace, warn};
use pci_types::capability::PciCapability; use pci_types::capability::PciCapability;
@@ -18,6 +19,7 @@ use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction, Pci
mod cfg_access; mod cfg_access;
mod driver_handler; mod driver_handler;
mod events;
mod scheme; mod scheme;
pub struct Func { pub struct Func {
@@ -284,11 +286,11 @@ fn daemon(daemon: daemon::Daemon) -> ! {
common::file_level(), 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"); 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 socket = redox_scheme::Socket::create().expect("failed to open pci scheme socket");
let handler = Blocking::new(&socket, 16); let handler = Blocking::new(&socket, 16);
@@ -342,6 +344,11 @@ fn daemon(daemon: daemon::Daemon) -> ! {
} }
debug!("Enumeration complete, now starting pci scheme"); debug!("Enumeration complete, now starting pci scheme");
let event_log = Arc::new(events::EventLog::new());
let devices: Vec<PciAddress> = 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) register_sync_scheme(&socket, "pci", &mut scheme)
.expect("failed to register pci scheme to namespace"); .expect("failed to register pci scheme to namespace");
+58 -13
View File
@@ -6,7 +6,7 @@ use redox_scheme::{CallerCtx, OpenResult};
use scheme_utils::HandleMap; use scheme_utils::HandleMap;
use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; use syscall::dirent::{DirEntry, DirentBuf, DirentKind};
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR}; 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::schemev2::NewFdFlags;
use syscall::ENOLCK; use syscall::ENOLCK;
@@ -14,8 +14,9 @@ use crate::cfg_access::Pcie;
pub struct PciScheme { pub struct PciScheme {
handles: HandleMap<HandleWrapper>, handles: HandleMap<HandleWrapper>,
pub pcie: Pcie, pub pcie: std::sync::Arc<Pcie>,
pub tree: BTreeMap<PciAddress, crate::Func>, pub tree: BTreeMap<PciAddress, crate::Func>,
events: std::sync::Arc<crate::events::EventLog>,
} }
enum Handle { enum Handle {
TopLevel { entries: Vec<String> }, TopLevel { entries: Vec<String> },
@@ -23,6 +24,8 @@ enum Handle {
Device, Device,
Config { addr: PciAddress }, Config { addr: PciAddress },
Channel { addr: PciAddress, st: ChannelState }, Channel { addr: PciAddress, st: ChannelState },
Aer,
Pciehp,
SchemeRoot, SchemeRoot,
} }
struct HandleWrapper { struct HandleWrapper {
@@ -33,7 +36,11 @@ impl Handle {
fn is_file(&self) -> bool { fn is_file(&self) -> bool {
matches!( matches!(
self, self,
Self::Access | Self::Config { .. } | Self::Channel { .. } Self::Access
| Self::Config { .. }
| Self::Channel { .. }
| Self::Aer
| Self::Pciehp
) )
} }
fn is_dir(&self) -> bool { fn is_dir(&self) -> bool {
@@ -95,16 +102,21 @@ impl SchemeSync for PciScheme {
let path = path.trim_matches('/'); let path = path.trim_matches('/');
let handle = if path.is_empty() { let handle = if path.is_empty() {
Handle::TopLevel { let mut entries: Vec<String> = self
entries: self .tree
.tree .iter()
.iter() // FIXME remove replacement of : once the old scheme format is no longer supported.
// FIXME remove replacement of : once the old scheme format is no longer supported. .map(|(addr, _)| format!("{}", addr).replace(':', "--"))
.map(|(addr, _)| format!("{}", addr).replace(':', "--")) .collect::<Vec<_>>();
.collect::<Vec<_>>(), entries.push("aer".to_string());
} entries.push("pciehp".to_string());
Handle::TopLevel { entries }
} else if path == "access" { } else if path == "access" {
Handle::Access Handle::Access
} else if path == "aer" {
Handle::Aer
} else if path == "pciehp" {
Handle::Pciehp
} else { } else {
let idx = path.find('/').unwrap_or(path.len()); let idx = path.find('/').unwrap_or(path.len());
let (addr_str, after) = path.split_at(idx); 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::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
Handle::Config { .. } => (config_size, MODE_CHR | 0o600), Handle::Config { .. } => (config_size, MODE_CHR | 0o600),
Handle::Access | Handle::Channel { .. } => (0, 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)), Handle::SchemeRoot => return Err(Error::new(EBADF)),
}; };
stat.st_size = len as u64; stat.st_size = len as u64;
@@ -186,6 +200,28 @@ impl SchemeSync for PciScheme {
addr: _, addr: _,
ref mut st, ref mut st,
} => Self::read_channel(st, buf), } => 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)), Handle::SchemeRoot => Err(Error::new(EBADF)),
_ => Err(Error::new(EBADF)), _ => Err(Error::new(EBADF)),
} }
@@ -219,7 +255,11 @@ impl SchemeSync for PciScheme {
return Ok(buf); return Ok(buf);
} }
Handle::Device => DEVICE_CONTENTS, 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)); return Err(Error::new(ENOTDIR));
} }
Handle::SchemeRoot => return Err(Error::new(EBADF)), Handle::SchemeRoot => return Err(Error::new(EBADF)),
@@ -383,11 +423,16 @@ impl PciScheme {
if self.pcie.has_ecam() { 4096 } else { 256 } 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<crate::events::EventLog>) {
self.events = events;
}
pub fn new(pcie: std::sync::Arc<Pcie>) -> Self {
Self { Self {
handles: HandleMap::new(), handles: HandleMap::new(),
pcie, pcie,
tree: BTreeMap::new(), tree: BTreeMap::new(),
events: std::sync::Arc::new(crate::events::EventLog::new()),
} }
} }
fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result<Handle> { fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result<Handle> {