diff --git a/drivers/pcid/src/scheme.rs b/drivers/pcid/src/scheme.rs index bb9f39a3..b6f8711e 100644 --- a/drivers/pcid/src/scheme.rs +++ b/drivers/pcid/src/scheme.rs @@ -1,28 +1,100 @@ -use std::collections::{BTreeMap, VecDeque}; +use std::collections::{BTreeMap, HashMap, VecDeque}; +use std::fmt::Write; use pci_types::{ConfigRegionAccess, PciAddress}; use redox_scheme::scheme::SchemeSync; 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::error::{ + Error, Result, EACCES, EALREADY, EBADF, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR, EROFS, +}; use syscall::flag::{MODE_CHR, MODE_DIR, O_DIRECTORY, O_STAT}; use syscall::schemev2::NewFdFlags; use syscall::ENOLCK; use crate::cfg_access::Pcie; +const PCIE_EXTENDED_CAPABILITY_AER: u16 = 0x0001; + +#[derive(Clone, Copy)] +enum AerRegisterName { + UncorStatus, + UncorMask, + UncorSeverity, + CorStatus, + CorMask, + Cap, + HeaderLog, +} + +impl AerRegisterName { + fn from_path(path: &str) -> Option { + Some(match path { + "uncor_status" => Self::UncorStatus, + "uncor_mask" => Self::UncorMask, + "uncor_severity" => Self::UncorSeverity, + "cor_status" => Self::CorStatus, + "cor_mask" => Self::CorMask, + "cap" => Self::Cap, + "header_log" => Self::HeaderLog, + _ => return None, + }) + } + + const fn offset(self) -> u16 { + match self { + Self::UncorStatus => 0x00, + Self::UncorMask => 0x04, + Self::UncorSeverity => 0x08, + Self::CorStatus => 0x0C, + Self::CorMask => 0x10, + Self::Cap => 0x14, + Self::HeaderLog => 0x18, + } + } + + const fn len(self) -> usize { + match self { + Self::HeaderLog => 16, + _ => 4, + } + } +} + pub struct PciScheme { handles: HandleMap, pub pcie: Pcie, pub tree: BTreeMap, + /// Maps device address string (e.g. "0000:00:14.0") to owning PID + binds: HashMap, } enum Handle { - TopLevel { entries: Vec }, + TopLevel { + entries: Vec, + }, Access, - Device, - Channel { addr: PciAddress, st: ChannelState }, + Device { + addr: PciAddress, + }, + Channel { + addr: PciAddress, + st: ChannelState, + }, SchemeRoot, + /// Represents an open handle to a device's bind endpoint + Bind { + addr: PciAddress, + }, + AerDir, + Aer { + addr: PciAddress, + register: AerRegisterName, + }, + /// Uevent surface for hotplug consumers. Opening uevent returns an object + /// from which device add/remove events can be read. Since pcid currently + /// only scans at startup, this surface is ready for hotplug polling consumers. + Uevent, } struct HandleWrapper { inner: Handle, @@ -30,14 +102,23 @@ struct HandleWrapper { } impl Handle { fn is_file(&self) -> bool { - matches!(self, Self::Access | Self::Channel { .. }) + matches!( + self, + Self::Access + | Self::Channel { .. } + | Self::Bind { .. } + | Self::Aer { .. } + | Self::Uevent + ) } fn is_dir(&self) -> bool { !self.is_file() } - // TODO: capability rather than root fn requires_root(&self) -> bool { - matches!(self, Self::Access | Self::Channel { .. }) + matches!( + self, + Self::Access | Self::Channel { .. } | Self::Bind { .. } + ) } fn is_scheme_root(&self) -> bool { matches!(self, Self::SchemeRoot) @@ -49,7 +130,17 @@ enum ChannelState { AwaitingResponseRead(VecDeque), } -const DEVICE_CONTENTS: &[&str] = &["channel"]; +const DEVICE_CONTENTS: &[&str] = &["channel", "bind"]; +const DEVICE_AER_CONTENTS: &[&str] = &["channel", "bind", "aer"]; +const AER_CONTENTS: &[&str] = &[ + "uncor_status", + "uncor_mask", + "uncor_severity", + "cor_status", + "cor_mask", + "cap", + "header_log", +]; impl PciScheme { pub fn access(&mut self) -> usize { @@ -88,22 +179,25 @@ 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(String::from("uevent")); + entries.push(String::from("access")); + Handle::TopLevel { entries } } else if path == "access" { Handle::Access + } else if path == "uevent" { + Handle::Uevent } else { let idx = path.find('/').unwrap_or(path.len()); let (addr_str, after) = path.split_at(idx); let addr = parse_pci_addr(addr_str).ok_or(Error::new(ENOENT))?; - self.parse_after_pci_addr(addr, after)? + self.parse_after_pci_addr(addr, after, ctx)? }; let stat = flags & O_STAT != 0; @@ -131,8 +225,14 @@ impl SchemeSync for PciScheme { let (len, mode) = match handle.inner { Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755), - Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), - Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600), + Handle::Device { addr } => ( + Self::device_entries(&self.pcie, addr).len(), + MODE_DIR | 0o755, + ), + Handle::AerDir => (AER_CONTENTS.len(), MODE_DIR | 0o755), + Handle::Aer { register, .. } => (register.len(), MODE_CHR | 0o444), + Handle::Access | Handle::Channel { .. } | Handle::Bind { .. } => (0, MODE_CHR | 0o600), + Handle::Uevent => (0, MODE_CHR | 0o644), Handle::SchemeRoot => return Err(Error::new(EBADF)), }; stat.st_size = len as u64; @@ -143,7 +243,7 @@ impl SchemeSync for PciScheme { &mut self, id: usize, buf: &mut [u8], - _offset: u64, + offset: u64, _fcntl_flags: u32, _ctx: &CallerCtx, ) -> Result { @@ -155,12 +255,45 @@ impl SchemeSync for PciScheme { match handle.inner { Handle::TopLevel { .. } => Err(Error::new(EISDIR)), - Handle::Device => Err(Error::new(EISDIR)), + Handle::Device { .. } | Handle::AerDir => Err(Error::new(EISDIR)), Handle::Channel { addr: _, ref mut st, } => Self::read_channel(st, buf), - Handle::SchemeRoot => Err(Error::new(EBADF)), + Handle::Aer { addr, register } => { + Self::read_aer_register(&self.pcie, addr, register, buf, offset) + } + Handle::Uevent => { + // Uevent surface for hotplug polling consumers. + // pcid currently only scans at startup, so return the current + // device tree as "add" events. Consumers can poll and re-read + // to check for new events. + let mut o = String::new(); + for (a, f) in &self.tree { + let _ = write!( + o, + "add device {:02x}:{:02x}.{:x}.{:x} vendor=0x{:04x} device=0x{:04x} class=0x{:02x}.{:02x}\n", + a.segment(), + a.bus(), + a.device(), + a.function(), + f.inner.full_device_id.vendor_id, + f.inner.full_device_id.device_id, + f.inner.full_device_id.class, + f.inner.full_device_id.subclass + ); + } + let b = o.as_bytes(); + let s = offset as usize; + if s < b.len() { + let n = (b.len() - s).min(buf.len()); + buf[..n].copy_from_slice(&b[s..s + n]); + Ok(n) + } else { + Ok(0) + } + } + Handle::SchemeRoot | Handle::Bind { .. } => Err(Error::new(EBADF)), _ => Err(Error::new(EBADF)), } } @@ -192,8 +325,15 @@ impl SchemeSync for PciScheme { } return Ok(buf); } - Handle::Device => DEVICE_CONTENTS, - Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)), + Handle::Device { addr } => Self::device_entries(&self.pcie, addr), + Handle::AerDir => AER_CONTENTS, + Handle::Access + | Handle::Channel { .. } + | Handle::Bind { .. } + | Handle::Aer { .. } + | Handle::Uevent => { + return Err(Error::new(ENOTDIR)); + } Handle::SchemeRoot => return Err(Error::new(EBADF)), }; @@ -226,6 +366,7 @@ impl SchemeSync for PciScheme { Handle::Channel { addr, ref mut st } => { Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) } + Handle::Aer { .. } => Err(Error::new(EROFS)), _ => Err(Error::new(EBADF)), } @@ -316,6 +457,16 @@ impl SchemeSync for PciScheme { func.enabled = false; } } + Some(HandleWrapper { + inner: Handle::Bind { addr }, + .. + }) => { + let addr_str = format!("{}", addr); + if let Some(&owner_pid) = self.binds.get(&addr_str) { + log::info!("pcid: device {} unbound by pid {}", addr_str, owner_pid); + } + self.binds.remove(&addr_str); + } _ => {} } } @@ -327,36 +478,154 @@ impl PciScheme { handles: HandleMap::new(), pcie, tree: BTreeMap::new(), + binds: HashMap::new(), + } + } + fn device_entries(pcie: &Pcie, addr: PciAddress) -> &'static [&'static str] { + if Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER).is_some() { + DEVICE_AER_CONTENTS + } else { + DEVICE_CONTENTS } } - fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result { + fn find_pcie_extended_capability( + pcie: &Pcie, + addr: PciAddress, + capability_id: u16, + ) -> Option { + if !pcie.has_extended_config(addr) { + return None; + } + + let mut offset = 0x100_u16; + + while offset <= 0xFFC { + let header = unsafe { pcie.read(addr, offset) }; + if header == 0 || header == u32::MAX { + return None; + } + + if (header & 0xFFFF) as u16 == capability_id { + return Some(offset); + } + + let next = ((header >> 20) & 0xFFF) as u16; + if next < 0x100 || next <= offset || next > 0xFFC || next % 4 != 0 { + return None; + } + offset = next; + } + + None + } + fn read_file_bytes(data: &[u8], buf: &mut [u8], offset: u64) -> Result { + let Ok(offset) = usize::try_from(offset) else { + return Ok(0); + }; + if offset >= data.len() { + return Ok(0); + } + + let count = std::cmp::min(buf.len(), data.len() - offset); + buf[..count].copy_from_slice(&data[offset..offset + count]); + Ok(count) + } + fn read_aer_register( + pcie: &Pcie, + addr: PciAddress, + register: AerRegisterName, + buf: &mut [u8], + offset: u64, + ) -> Result { + let Some(aer_base) = + Self::find_pcie_extended_capability(pcie, addr, PCIE_EXTENDED_CAPABILITY_AER) + else { + return Err(Error::new(ENOENT)); + }; + + let mut data = [0_u8; 16]; + for (index, chunk) in data[..register.len()].chunks_exact_mut(4).enumerate() { + let index = u16::try_from(index).map_err(|_| Error::new(EIO))?; + let value = unsafe { pcie.read(addr, aer_base + register.offset() + index * 4) }; + chunk.copy_from_slice(&value.to_le_bytes()); + } + + Self::read_file_bytes(&data[..register.len()], buf, offset) + } + fn parse_after_pci_addr( + &mut self, + addr: PciAddress, + after: &str, + ctx: &CallerCtx, + ) -> Result { if after.chars().next().map_or(false, |c| c != '/') { return Err(Error::new(ENOENT)); } let func = self.tree.get_mut(&addr).ok_or(Error::new(ENOENT))?; Ok(if after.is_empty() { - Handle::Device + Handle::Device { addr } } else { let path = &after[1..]; - match path { - "channel" => { - if func.enabled { - return Err(Error::new(ENOLCK)); + if path == "aer" { + if Self::find_pcie_extended_capability( + &self.pcie, + addr, + PCIE_EXTENDED_CAPABILITY_AER, + ) + .is_none() + { + return Err(Error::new(ENOENT)); + } + Handle::AerDir + } else if let Some(register_name) = path.strip_prefix("aer/") { + let register = + AerRegisterName::from_path(register_name).ok_or(Error::new(ENOENT))?; + if Self::find_pcie_extended_capability( + &self.pcie, + addr, + PCIE_EXTENDED_CAPABILITY_AER, + ) + .is_none() + { + return Err(Error::new(ENOENT)); + } + Handle::Aer { addr, register } + } else { + match path { + "channel" => { + if func.enabled { + return Err(Error::new(ENOLCK)); + } + func.inner.legacy_interrupt_line = crate::enable_function( + &self.pcie, + &mut func.endpoint_header, + &mut func.capabilities, + ); + func.enabled = true; + Handle::Channel { + addr, + st: ChannelState::AwaitingData, + } } - func.inner.legacy_interrupt_line = crate::enable_function( - &self.pcie, - &mut func.endpoint_header, - &mut func.capabilities, - ); - func.enabled = true; - Handle::Channel { - addr, - st: ChannelState::AwaitingData, + "bind" => { + let addr_str = format!("{}", addr); + if let Some(&owner_pid) = self.binds.get(&addr_str) { + log::info!( + "pcid: device {} already bound by pid {}", + addr_str, + owner_pid + ); + return Err(Error::new(EALREADY)); + } + let caller_pid = u32::try_from(ctx.pid).map_err(|_| Error::new(EINVAL))?; + self.binds.insert(addr_str.clone(), caller_pid); + log::info!("pcid: device {} bound by pid {}", addr_str, caller_pid); + Handle::Bind { addr } } + _ => return Err(Error::new(ENOENT)), } - _ => return Err(Error::new(ENOENT)), } }) }