use std::fs::File; use std::io::prelude::*; use std::os::fd::{FromRawFd, IntoRawFd, RawFd}; use std::path::Path; use std::ptr::NonNull; use std::{env, io}; use std::{fmt, process}; use daemon::Daemon; use serde::{de::DeserializeOwned, Deserialize, Serialize}; pub use bar::PciBar; pub use cap::VendorSpecificCapability; pub use id::FullDeviceId; pub use pci_types::PciAddress; mod bar; pub mod cap; pub mod config; mod id; pub mod irq_helpers; pub mod msi; #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct LegacyInterruptLine { #[doc(hidden)] pub irq: u8, pub phandled: Option<(u32, [u32; 3], usize)>, } impl LegacyInterruptLine { /// Get an IRQ handle for this interrupt line. pub fn irq_handle(self, driver: &str) -> File { if let Some((phandle, addr, cells)) = self.phandled { let path = match cells { 1 => format!("/scheme/irq/phandle-{}/{}", phandle, addr[0]), 2 => format!("/scheme/irq/phandle-{}/{},{}", phandle, addr[0], addr[1]), 3 => format!( "/scheme/irq/phandle-{}/{},{},{}", phandle, addr[0], addr[1], addr[2] ), _ => panic!( "unexpected number of IRQ description cells for phandle {phandle}: {cells}" ), }; File::create(path) .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}")) } else { File::open(format!("/scheme/irq/{}", self.irq)) .unwrap_or_else(|err| panic!("{driver}: failed to open IRQ file: {err}")) } } /// Non-panicking version of `irq_handle`. pub fn try_irq_handle(self, _driver: &str) -> io::Result { if let Some((phandle, addr, cells)) = self.phandled { let path = match cells { 1 => format!("/scheme/irq/phandle-{}/{}", phandle, addr[0]), 2 => format!("/scheme/irq/phandle-{}/{},{}", phandle, addr[0], addr[1]), 3 => format!( "/scheme/irq/phandle-{}/{},{},{}", phandle, addr[0], addr[1], addr[2] ), _ => { return Err(io::Error::new( io::ErrorKind::InvalidInput, format!("unexpected IRQ cells count: {cells}"), )) } }; File::create(path) } else { File::open(format!("/scheme/irq/{}", self.irq)) } } } impl fmt::Display for LegacyInterruptLine { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some((phandle, addr, cells)) = self.phandled { match cells { 1 => write!(f, "(phandle {}, {:?})", phandle, addr[0]), 2 => write!(f, "(phandle {}, {:?},{:?})", phandle, addr[0], addr[1]), 3 => write!(f, "(phandle {}, {:?})", phandle, addr), _ => panic!( "unexpected number of IRQ description cells for phandle {phandle}: {cells}" ), } } else { write!(f, "{}", self.irq) } } } #[derive(Serialize, Deserialize)] #[serde(remote = "PciAddress")] struct PciAddressDef { #[serde(getter = "PciAddress::segment")] segment: u16, #[serde(getter = "PciAddress::bus")] bus: u8, #[serde(getter = "PciAddress::device")] device: u8, #[serde(getter = "PciAddress::function")] function: u8, } impl From for PciAddress { fn from(value: PciAddressDef) -> Self { PciAddress::new(value.segment, value.bus, value.device, value.function) } } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct PciRom { pub addr: u32, pub size: u32, pub enabled: bool, } #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct PciFunction { /// Address of the PCI function. #[serde(with = "PciAddressDef")] pub addr: PciAddress, /// PCI Base Address Registers pub bars: [PciBar; 6], /// PCI Option ROM pub rom: Option, /// Legacy IRQ line: It's the responsibility of pcid to make sure that it be mapped in either /// the I/O APIC or the 8259 PIC, so that the subdriver can map the interrupt vector directly. /// The vector to map is always this field, plus 32. /// If INTx# interrupts aren't supported at all this is `None`. pub legacy_interrupt_line: Option, /// All identifying information of the PCI function. pub full_device_id: FullDeviceId, } impl PciFunction { pub fn name(&self) -> String { // FIXME stop replacing : with - once it is a valid character in scheme names format!("pci-{}", self.addr).replace(':', "-") } pub fn display(&self) -> String { let mut string = self.name(); let mut first = true; for (i, bar) in self.bars.iter().enumerate() { if !bar.is_none() { if first { first = false; string.push_str(" on:"); } string.push_str(&format!(" {i}={}", bar.display())); } } if let Some(irq) = self.legacy_interrupt_line { string.push_str(&format!(" IRQ: {irq}")); } string } } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SubdriverArguments { pub func: PciFunction, /// Full device ID (vendor, device, class, subclass, etc.) — pcid /// reads this from the kernel's PCIe config space and passes it at /// spawn time. Used by subdrivers to apply per-vendor quirks. pub device_id: Option, } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum FeatureStatus { Enabled, Disabled, } impl FeatureStatus { pub fn enabled(enabled: bool) -> Self { if enabled { Self::Enabled } else { Self::Disabled } } pub fn is_enabled(&self) -> bool { if let &Self::Enabled = self { true } else { false } } } #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum PciFeature { Msi, MsiX, } impl PciFeature { pub fn is_msi(self) -> bool { if let Self::Msi = self { true } else { false } } pub fn is_msix(self) -> bool { if let Self::MsiX = self { true } else { false } } } #[derive(Debug, Serialize, Deserialize)] pub enum PciFeatureInfo { Msi(msi::MsiInfo), MsiX(msi::MsixInfo), } // TODO: Remove these "features" and just go strait to the actual thing. #[derive(Debug, Default, Serialize, Deserialize)] pub struct MsiSetFeatureInfo { /// The Multi Message Enable field of the Message Control in the MSI Capability Structure, /// is the log2 of the interrupt vectors, minus one. Can only be 0b000..=0b101. pub multi_message_enable: Option, /// The system-specific message address and data. /// /// The message address contains things like the CPU that will be targeted, at least on /// x86_64. The message data contains the actual interrupt vector (lower 8 bits) and /// the kind of interrupt, at least on x86_64. pub message_address_and_data: Option, /// A bitmap of the vectors that are masked. This field is not guaranteed (and not likely, /// at least according to the feature flags I got from QEMU), to exist. pub mask_bits: Option, } /// Some flags that might be set simultaneously, but separately. #[derive(Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum SetFeatureInfo { Msi(MsiSetFeatureInfo), MsiX { /// Masks the entire function, and all of its vectors. function_mask: Option, }, } #[derive(Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum PcidClientRequest { EnableDevice, RequestConfig, RequestFeatures, RequestVendorCapabilities, EnableFeature(PciFeature), FeatureInfo(PciFeature), SetFeatureInfo(SetFeatureInfo), ReadConfig(u16), WriteConfig(u16, u32), } #[derive(Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum PcidServerResponseError { NonexistentFeature(PciFeature), InvalidBitPattern, } #[derive(Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum PcidClientResponse { EnabledDevice, Config(SubdriverArguments), AllFeatures(Vec), VendorCapabilities(Vec), FeatureEnabled(PciFeature), FeatureStatus(PciFeature, FeatureStatus), Error(PcidServerResponseError), FeatureInfo(PciFeature, PciFeatureInfo), SetFeatureInfo(PciFeature), ReadConfig(u32), WriteConfig, } pub struct MappedBar { pub ptr: NonNull, pub bar_size: usize, } /// A handle from a `pcid` client (e.g. `ahcid`) to `pcid`. pub struct PciFunctionHandle { channel: File, config: SubdriverArguments, mapped_bars: [Option; 6], irq_mode_hint: Option, disable_accel_hint: bool, } fn read_irq_mode_hint() -> Option { match env::var("REDBEAR_DRIVER_PCI_IRQ_MODE") { Ok(v) if !v.is_empty() => Some(v), Ok(_) => None, Err(_) => None, } } fn read_disable_accel_hint() -> bool { match env::var("REDBEAR_DRIVER_DISABLE_ACCEL") { Ok(v) if v == "1" || v.eq_ignore_ascii_case("true") || v == "yes" => true, _ => false, } } fn send(w: &mut File, message: &T) { let mut data = Vec::new(); bincode::serialize_into(&mut data, message).expect("couldn't serialize pcid message"); match w.write(&data) { Ok(len) => assert_eq!(len, data.len()), Err(err) => { log::error!("writing pcid request failed: {err}"); process::exit(1); } } } fn recv(r: &mut File) -> T { let mut length_bytes = [0u8; 8]; if let Err(err) = r.read_exact(&mut length_bytes) { log::error!("reading pcid response length failed: {err}"); process::exit(1); } let length = u64::from_le_bytes(length_bytes); if length > 0x100_000 { panic!("pcid_interface: buffer too large"); } let mut data = vec![0u8; length as usize]; if let Err(err) = r.read_exact(&mut data) { log::error!("reading pcid response failed: {err}"); process::exit(1); } bincode::deserialize_from(&data[..]).expect("couldn't deserialize pcid message") } impl PciFunctionHandle { pub fn connect_default() -> Self { let channel_fd = match env::var("PCID_CLIENT_CHANNEL") { Ok(channel_fd) => channel_fd, Err(err) => { log::error!("PCID_CLIENT_CHANNEL invalid: {err}"); process::exit(1); } }; let channel_fd = match channel_fd.parse::() { Ok(channel_fd) => channel_fd, Err(err) => { log::error!("PCID_CLIENT_CHANNEL invalid: {err}"); process::exit(1); } }; Self::connect_common(channel_fd) } pub fn connect_by_path(device_path: &Path) -> io::Result { let channel_fd = libredox::call::open( device_path.join("channel").to_str().unwrap(), libredox::flag::O_RDWR, 0, )?; Ok(Self::connect_common(channel_fd as RawFd)) } fn connect_common(channel_fd: i32) -> PciFunctionHandle { let mut channel = unsafe { File::from_raw_fd(channel_fd) }; send(&mut channel, &PcidClientRequest::RequestConfig); let config = match recv(&mut channel) { PcidClientResponse::Config(a) => a, other => { log::error!("received wrong pcid response: {other:?}"); process::exit(1); } }; Self { channel, config, mapped_bars: [const { None }; 6], irq_mode_hint: read_irq_mode_hint(), disable_accel_hint: read_disable_accel_hint(), } } /// Returns the IRQ-mode hint read from `REDBEAR_DRIVER_PCI_IRQ_MODE` at /// connect time, or `None` if the env var was unset. Drivers should /// honour this hint when allocating interrupt vectors. pub fn irq_mode_hint(&self) -> Option<&str> { self.irq_mode_hint.as_deref() } /// Returns the disable-acceleration hint read from /// `REDBEAR_DRIVER_DISABLE_ACCEL` at connect time. Drivers (notably /// GPU drivers) should suppress hardware acceleration paths when this /// returns `true`. pub fn disable_accel_hint(&self) -> bool { self.disable_accel_hint } pub fn into_inner_fd(self) -> RawFd { self.channel.into_raw_fd() } fn send(&mut self, req: &PcidClientRequest) { send(&mut self.channel, req) } fn recv(&mut self) -> PcidClientResponse { recv(&mut self.channel) } pub fn config(&self) -> SubdriverArguments { self.config.clone() } pub fn enable_device(&mut self) { self.send(&PcidClientRequest::EnableDevice); match self.recv() { PcidClientResponse::EnabledDevice => {} other => { log::error!("received wrong pcid response: {other:?}"); process::exit(1); } } } pub fn get_vendor_capabilities(&mut self) -> Vec { self.send(&PcidClientRequest::RequestVendorCapabilities); match self.recv() { PcidClientResponse::VendorCapabilities(a) => a, other => { log::error!("received wrong pcid response: {other:?}"); process::exit(1); } } } // FIXME turn into struct with bool fields pub fn fetch_all_features(&mut self) -> Vec { self.send(&PcidClientRequest::RequestFeatures); match self.recv() { PcidClientResponse::AllFeatures(a) => a, other => { log::error!("received wrong pcid response: {other:?}"); process::exit(1); } } } pub fn enable_feature(&mut self, feature: PciFeature) { self.send(&PcidClientRequest::EnableFeature(feature)); match self.recv() { PcidClientResponse::FeatureEnabled(feat) if feat == feature => {} other => { log::error!("received wrong pcid response: {other:?}"); process::exit(1); } } } pub fn feature_info(&mut self, feature: PciFeature) -> PciFeatureInfo { self.send(&PcidClientRequest::FeatureInfo(feature)); match self.recv() { PcidClientResponse::FeatureInfo(feat, info) if feat == feature => info, other => { log::error!("received wrong pcid response: {other:?}"); process::exit(1); } } } pub fn set_feature_info(&mut self, info: SetFeatureInfo) { self.send(&PcidClientRequest::SetFeatureInfo(info)); match self.recv() { PcidClientResponse::SetFeatureInfo(_) => {} other => { log::error!("received wrong pcid response: {other:?}"); process::exit(1); } } } pub unsafe fn read_config(&mut self, offset: u16) -> u32 { self.send(&PcidClientRequest::ReadConfig(offset)); match self.recv() { PcidClientResponse::ReadConfig(value) => value, other => { log::error!("received wrong pcid response: {other:?}"); process::exit(1); } } } pub unsafe fn write_config(&mut self, offset: u16, value: u32) { self.send(&PcidClientRequest::WriteConfig(offset, value)); match self.recv() { PcidClientResponse::WriteConfig => {} other => { log::error!("received wrong pcid response: {other:?}"); process::exit(1); } } } pub unsafe fn map_bar(&mut self, bir: u8) -> &MappedBar { let mapped_bar = &mut self.mapped_bars[bir as usize]; if let Some(mapped_bar) = mapped_bar { mapped_bar } else { let (bar, bar_size) = self.config.func.bars[bir as usize].expect_mem(); let ptr = match unsafe { common::physmap( bar, bar_size, common::Prot::RW, // FIXME once the kernel supports this use write-through for prefetchable BAR common::MemoryType::Uncacheable, ) } { Ok(ptr) => ptr, Err(err) => { log::error!("failed to map BAR at {bar:016X}: {err}"); process::exit(1); } }; mapped_bar.insert(MappedBar { ptr: NonNull::new(ptr.cast::()).expect("Mapping a BAR resulted in a nullptr"), bar_size, }) } } pub unsafe fn try_map_bar(&mut self, bir: u8) -> Result<&MappedBar, String> { let mapped_bar = &mut self.mapped_bars[bir as usize]; if mapped_bar.is_none() { let (bar, bar_size) = self.config.func.bars[bir as usize] .try_mem() .map_err(|e| e.to_string())?; let ptr = unsafe { common::physmap( bar, bar_size, common::Prot::RW, common::MemoryType::Uncacheable, ) } .map_err(|e| format!("physmap failed: {e}"))?; let ptr = NonNull::new(ptr.cast::()) .ok_or_else(|| "physmap returned null".to_string())?; mapped_bar.insert(MappedBar { ptr, bar_size, }); } Ok(mapped_bar.as_ref().expect("BAR was just mapped")) } } pub fn pci_daemon !>(f: F) -> ! { Daemon::new(|daemon| { common::init(); let pcid_handle = PciFunctionHandle::connect_default(); f(daemon, pcid_handle) }) } #[cfg(test)] mod hint_tests { use super::{read_disable_accel_hint, read_irq_mode_hint}; use std::sync::Mutex; static ENV_LOCK: Mutex<()> = Mutex::new(()); #[test] fn irq_mode_hint_returns_none_when_unset() { let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); unsafe { std::env::remove_var("REDBEAR_DRIVER_PCI_IRQ_MODE") }; assert!(read_irq_mode_hint().is_none()); } #[test] fn irq_mode_hint_returns_some_when_set() { let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); unsafe { std::env::set_var("REDBEAR_DRIVER_PCI_IRQ_MODE", "intx_or_msi") }; let v = read_irq_mode_hint(); unsafe { std::env::remove_var("REDBEAR_DRIVER_PCI_IRQ_MODE") }; assert_eq!(v.as_deref(), Some("intx_or_msi")); } #[test] fn irq_mode_hint_returns_none_for_empty_value() { let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); unsafe { std::env::set_var("REDBEAR_DRIVER_PCI_IRQ_MODE", "") }; let v = read_irq_mode_hint(); unsafe { std::env::remove_var("REDBEAR_DRIVER_PCI_IRQ_MODE") }; assert!(v.is_none()); } #[test] fn disable_accel_hint_defaults_false() { let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); unsafe { std::env::remove_var("REDBEAR_DRIVER_DISABLE_ACCEL") }; assert!(!read_disable_accel_hint()); } #[test] fn disable_accel_hint_accepts_truthy_values() { let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); for truthy in &["1", "true", "TRUE", "yes"] { unsafe { std::env::set_var("REDBEAR_DRIVER_DISABLE_ACCEL", truthy) }; let v = read_disable_accel_hint(); unsafe { std::env::remove_var("REDBEAR_DRIVER_DISABLE_ACCEL") }; assert!(v, "value {:?} should be truthy", truthy); } } #[test] fn disable_accel_hint_rejects_falsy_values() { let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner()); for falsy in &["0", "false", "no", ""] { unsafe { std::env::set_var("REDBEAR_DRIVER_DISABLE_ACCEL", falsy) }; let v = read_disable_accel_hint(); unsafe { std::env::remove_var("REDBEAR_DRIVER_DISABLE_ACCEL") }; assert!(!v, "value {:?} should be falsy", falsy); } } }