diff --git a/Cargo.lock b/Cargo.lock index 4c1f1c7833..484b372504 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,7 +32,7 @@ dependencies = [ "parking_lot 0.12.3", "plain", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_event", "redox_syscall", "ron", @@ -52,7 +52,7 @@ dependencies = [ "log", "pcid", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_event", "redox_syscall", ] @@ -180,7 +180,7 @@ dependencies = [ "fdt 0.2.0-alpha1", "libredox", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_event", "redox_syscall", ] @@ -405,7 +405,7 @@ dependencies = [ "inputd", "libredox", "log", - "redox-scheme", + "redox-scheme 0.3.0", "redox_syscall", ] @@ -414,7 +414,7 @@ name = "driver-network" version = "0.1.0" dependencies = [ "libredox", - "redox-scheme", + "redox-scheme 0.3.0", "redox_syscall", ] @@ -449,7 +449,7 @@ dependencies = [ "orbclient", "ransid", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_event", "redox_syscall", ] @@ -465,7 +465,7 @@ dependencies = [ "orbclient", "ransid", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_event", "redox_syscall", ] @@ -680,7 +680,7 @@ dependencies = [ "log", "pcid", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_event", "redox_syscall", ] @@ -720,7 +720,7 @@ dependencies = [ "log", "orbclient", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_syscall", ] @@ -783,7 +783,7 @@ dependencies = [ "anyhow", "libredox", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_event", "redox_syscall", ] @@ -857,7 +857,7 @@ dependencies = [ "partitionlib", "pcid", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_event", "redox_syscall", "smallvec 1.13.2", @@ -968,8 +968,23 @@ dependencies = [ "pico-args", "plain", "redox-daemon", + "redox-scheme 0.4.0", + "redox_syscall", "serde", "thiserror", +] + +[[package]] +name = "pcid-spawner" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "log", + "pcid", + "pico-args", + "redox_syscall", + "serde", "toml 0.5.11", ] @@ -1123,6 +1138,16 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "redox-scheme" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28d292981c8f3338cb772b6024b08bcc597d0dd81020de17080a9a7b470ebb2" +dependencies = [ + "libredox", + "redox_syscall", +] + [[package]] name = "redox_event" version = "0.4.1" @@ -1135,9 +1160,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" dependencies = [ "bitflags 2.6.0", ] @@ -1577,7 +1602,7 @@ dependencies = [ "libredox", "plain", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_syscall", "thiserror", "xhcid", @@ -1652,7 +1677,7 @@ dependencies = [ "partitionlib", "pcid", "redox-daemon", - "redox-scheme", + "redox-scheme 0.3.0", "redox_syscall", "spin", "static_assertions", diff --git a/Cargo.toml b/Cargo.toml index be9ace45b5..62d4865e02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "common", "hwd", "pcid", + "pcid-spawner", "vboxd", "xhcid", "usbctl", diff --git a/pcid-spawner/Cargo.toml b/pcid-spawner/Cargo.toml new file mode 100644 index 0000000000..77dd21063f --- /dev/null +++ b/pcid-spawner/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "pcid-spawner" +version = "0.1.0" +authors = ["4lDO2 <4lDO2@protonmail.com>"] +edition = "2021" +license = "MIT" + +[dependencies] +anyhow = "1" +log = "0.4" +pico-args = "0.5" +redox_syscall = "0.5.9" +serde = { version = "1", features = ["derive"] } +toml = "0.5" + +common = { path = "../common" } +pcid = { path = "../pcid" } diff --git a/pcid-spawner/src/main.rs b/pcid-spawner/src/main.rs new file mode 100644 index 0000000000..a8ac5d5e13 --- /dev/null +++ b/pcid-spawner/src/main.rs @@ -0,0 +1,102 @@ +use std::fs; +use std::process::Command; + +use anyhow::{anyhow, Context, Result}; + +use pcid_interface::config::Config; +use pcid_interface::PciFunctionHandle; + +fn main() -> Result<()> { + let mut args = pico_args::Arguments::from_env(); + let config_path = args + .free_from_str::() + .expect("failed to parse --config argument"); + + common::setup_logging( + "bus", + "pci", + "pci-spawner.log", + log::LevelFilter::Info, + log::LevelFilter::Trace, + ); + + let config_data = if fs::metadata(&config_path)?.is_file() { + fs::read_to_string(&config_path)? + } else { + let mut config_data = String::new(); + for path in fs::read_dir(&config_path)? { + if let Ok(tmp) = fs::read_to_string(path.unwrap().path()) { + config_data.push_str(&tmp); + } + } + config_data + }; + let config: Config = toml::from_str(&config_data)?; + + for entry in fs::read_dir("/scheme/pci")? { + let entry = entry.context("failed to get entry")?; + let device_path = entry.path(); + log::trace!("ENTRY: {}", device_path.to_string_lossy()); + + let mut handle = match PciFunctionHandle::connect_by_path(&device_path) { + Ok(handle) => handle, + Err(err) => { + // Either the device is gone or it is already in-use by a driver. + log::debug!( + "pcid-spawner: {} already in use: {err}", + device_path.display(), + ); + continue; + } + }; + + let full_device_id = handle.config().func.full_device_id; + + log::debug!( + "pcid-spawner enumerated: PCI {} {}", + handle.config().func.addr, + full_device_id.display() + ); + + let Some(driver) = config + .drivers + .iter() + .find(|driver| driver.match_function(&full_device_id)) + else { + log::debug!("no driver for {}, continuing", handle.config().func.addr); + continue; + }; + + let mut args = driver.command.iter(); + + let program = args + .next() + .ok_or_else(|| anyhow!("driver configuration entry did not have any command!"))?; + let program = if program.starts_with('/') { + program.to_owned() + } else { + "/usr/lib/drivers/".to_owned() + program + }; + + let mut command = Command::new(program); + command.args(args); + + log::info!("pcid-spawner: spawn {:?}", command); + + handle.enable_device()?; + + let channel_fd = handle.into_inner_fd(); + command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string()); + + match command.status() { + Ok(status) if !status.success() => { + log::error!("pcid-spawner: driver {command:?} failed with {status}"); + } + Ok(_) => {} + Err(err) => log::error!("pcid-spawner: failed to execute {command:?}: {err}"), + } + syscall::close(channel_fd as usize).unwrap(); + } + + Ok(()) +} diff --git a/pcid/Cargo.toml b/pcid/Cargo.toml index b78a994584..b2d6b51317 100644 --- a/pcid/Cargo.toml +++ b/pcid/Cargo.toml @@ -20,9 +20,10 @@ pci_types = "0.10" pico-args = { version = "0.5", features = ["combined-flags"] } plain = "0.2" redox-daemon = "0.1" +redox-scheme = "0.4" +redox_syscall = "0.5.9" serde = { version = "1", features = ["derive"] } thiserror = "1" -toml = "0.5" common = { path = "../common" } libredox = "0.1.3" diff --git a/pcid/src/driver_handler.rs b/pcid/src/driver_handler.rs index cbaf221e54..435c225937 100644 --- a/pcid/src/driver_handler.rs +++ b/pcid/src/driver_handler.rs @@ -1,121 +1,60 @@ -use std::fs::File; -use std::os::unix::io::{FromRawFd, RawFd}; -use std::process::Command; -use std::sync::Arc; -use std::thread; - -use log::{error, info}; use pci_types::capability::{MultipleMessageSupport, PciCapability}; -use pci_types::{ConfigRegionAccess, PciAddress}; +use pci_types::{ConfigRegionAccess, EndpointHeader}; +use pcid_interface::PciFunction; -use crate::State; +use crate::cfg_access::Pcie; -pub struct DriverHandler { - addr: PciAddress, +pub struct DriverHandler<'a> { + func: PciFunction, + endpoint_header: &'a mut EndpointHeader, capabilities: Vec, - state: Arc, + pcie: &'a Pcie, } -impl DriverHandler { - pub fn spawn( - state: Arc, - func: pcid_interface::PciFunction, +impl<'a> DriverHandler<'a> { + pub fn new( + func: PciFunction, + endpoint_header: &'a mut EndpointHeader, capabilities: Vec, - args: &[String], - ) { - let subdriver_args = pcid_interface::SubdriverArguments { func }; - - let mut args = args.iter(); - if let Some(program) = args.next() { - let program = if program.starts_with('/') { - program.to_owned() - } else { - "/usr/lib/drivers/".to_owned() + program - }; - let mut command = Command::new(program); - for arg in args { - if arg.starts_with("$") { - panic!("support for $VARIABLE has been removed. use pcid_interface instead"); - } - command.arg(arg); - } - - info!("PCID SPAWN {:?}", command); - - // TODO: libc wrapper? - let [fds1, fds2] = unsafe { - let mut fds1 = [0 as libc::c_int; 2]; - let mut fds2 = [0 as libc::c_int; 2]; - - assert_eq!( - libc::pipe(fds1.as_mut_ptr()), - 0, - "pcid: failed to create pcid->client pipe" - ); - assert_eq!( - libc::pipe(fds2.as_mut_ptr()), - 0, - "pcid: failed to create client->pcid pipe" - ); - - [fds1.map(|c| c as usize), fds2.map(|c| c as usize)] - }; - - let [pcid_to_client_read, pcid_to_client_write] = fds1; - let [pcid_from_client_read, pcid_from_client_write] = fds2; - - let envs = vec![ - ("PCID_TO_CLIENT_FD", format!("{}", pcid_to_client_read)), - ("PCID_FROM_CLIENT_FD", format!("{}", pcid_from_client_write)), - ]; - - match command.envs(envs).spawn() { - Ok(mut child) => { - let driver_handler = DriverHandler { - addr: func.addr, - state: state.clone(), - capabilities, - }; - let handle = thread::spawn(move || { - driver_handler.handle_spawn( - pcid_to_client_write, - pcid_from_client_read, - subdriver_args, - ); - }); - state.threads.lock().unwrap().push(handle); - match child.wait() { - Ok(_status) => (), - Err(err) => error!("pcid: failed to wait for {:?}: {}", command, err), - } - } - Err(err) => error!("pcid: failed to execute {:?}: {}", command, err), - } + pcie: &'a Pcie, + ) -> Self { + DriverHandler { + func, + endpoint_header, + capabilities, + pcie, } } - fn respond( + pub fn respond( &mut self, request: pcid_interface::PcidClientRequest, - args: &pcid_interface::SubdriverArguments, ) -> pcid_interface::PcidClientResponse { use pcid_interface::*; #[forbid(non_exhaustive_omitted_patterns)] match request { + PcidClientRequest::EnableDevice => { + self.func.legacy_interrupt_line = + crate::enable_function(&self.pcie, &mut self.endpoint_header); + + PcidClientResponse::EnabledDevice + } PcidClientRequest::RequestVendorCapabilities => PcidClientResponse::VendorCapabilities( self.capabilities .iter() .filter_map(|capability| match capability { PciCapability::Vendor(addr) => unsafe { - Some(VendorSpecificCapability::parse(*addr, &self.state.pcie)) + Some(VendorSpecificCapability::parse(*addr, self.pcie)) }, _ => None, }) .collect::>(), ), - PcidClientRequest::RequestConfig => PcidClientResponse::Config(args.clone()), + PcidClientRequest::RequestConfig => { + PcidClientResponse::Config(SubdriverArguments { func: self.func }) + } PcidClientRequest::RequestFeatures => PcidClientResponse::AllFeatures( self.capabilities .iter() @@ -139,7 +78,7 @@ impl DriverHandler { { // If MSI-X is supported disable it before enabling MSI as they can't be // active at the same time. - msix_capability.set_enabled(false, &self.state.pcie); + msix_capability.set_enabled(false, self.pcie); } let capability = match self.capabilities.iter_mut().find_map(|capability| { @@ -155,7 +94,7 @@ impl DriverHandler { ) } }; - capability.set_enabled(true, &self.state.pcie); + capability.set_enabled(true, self.pcie); PcidClientResponse::FeatureEnabled(feature) } PciFeature::MsiX => { @@ -169,7 +108,7 @@ impl DriverHandler { { // If MSI is supported disable it before enabling MSI-X as they can't be // active at the same time. - msi_capability.set_enabled(false, &self.state.pcie); + msi_capability.set_enabled(false, self.pcie); } let capability = match self.capabilities.iter_mut().find_map(|capability| { @@ -185,7 +124,7 @@ impl DriverHandler { ) } }; - capability.set_enabled(true, &self.state.pcie); + capability.set_enabled(true, self.pcie); PcidClientResponse::FeatureEnabled(feature) } } @@ -268,7 +207,7 @@ impl DriverHandler { ) } }, - &self.state.pcie, + self.pcie, ); } if let Some(message_addr_and_data) = info_to_set.message_address_and_data { @@ -279,7 +218,7 @@ impl DriverHandler { ); } if message_addr_and_data.data - & ((1 << info.multiple_message_enable(&self.state.pcie) as u8) - 1) + & ((1 << info.multiple_message_enable(self.pcie) as u8) - 1) != 0 { return PcidClientResponse::Error( @@ -292,11 +231,11 @@ impl DriverHandler { .data .try_into() .expect("pcid: MSI message data too big"), - &self.state.pcie, + self.pcie, ); } if let Some(mask_bits) = info_to_set.mask_bits { - info.set_message_mask(mask_bits, &self.state.pcie); + info.set_message_mask(mask_bits, self.pcie); } PcidClientResponse::SetFeatureInfo(PciFeature::Msi) } else { @@ -315,7 +254,7 @@ impl DriverHandler { }) { if let Some(mask) = function_mask { - info.set_function_mask(mask, &self.state.pcie); + info.set_function_mask(mask, self.pcie); } PcidClientResponse::SetFeatureInfo(PciFeature::MsiX) } else { @@ -327,32 +266,16 @@ impl DriverHandler { _ => unreachable!(), }, PcidClientRequest::ReadConfig(offset) => { - let value = unsafe { self.state.pcie.read(self.addr, offset) }; + let value = unsafe { self.pcie.read(self.func.addr, offset) }; return PcidClientResponse::ReadConfig(value); } PcidClientRequest::WriteConfig(offset, value) => { unsafe { - self.state.pcie.write(self.addr, offset, value); + self.pcie.write(self.func.addr, offset, value); } return PcidClientResponse::WriteConfig; } _ => unreachable!(), } } - fn handle_spawn( - mut self, - pcid_to_client_write: usize, - pcid_from_client_read: usize, - args: pcid_interface::SubdriverArguments, - ) { - use pcid_interface::*; - - let mut pcid_to_client = unsafe { File::from_raw_fd(pcid_to_client_write as RawFd) }; - let mut pcid_from_client = unsafe { File::from_raw_fd(pcid_from_client_read as RawFd) }; - - while let Ok(msg) = recv(&mut pcid_from_client) { - let response = self.respond(msg, &args); - send(&mut pcid_to_client, &response).unwrap(); - } - } } diff --git a/pcid/src/driver_interface/config.rs b/pcid/src/driver_interface/config.rs index 8278f33c3c..e148b26ce6 100644 --- a/pcid/src/driver_interface/config.rs +++ b/pcid/src/driver_interface/config.rs @@ -20,7 +20,7 @@ pub struct DriverConfig { pub vendor: Option, pub device: Option, pub device_id_range: Option>, - pub command: Option>, + pub command: Vec, } impl DriverConfig { diff --git a/pcid/src/driver_interface/mod.rs b/pcid/src/driver_interface/mod.rs index 2cf16e8bba..7365276dc9 100644 --- a/pcid/src/driver_interface/mod.rs +++ b/pcid/src/driver_interface/mod.rs @@ -1,11 +1,11 @@ use std::fmt; 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::os::unix::io::{FromRawFd, RawFd}; - use serde::{de::DeserializeOwned, Deserialize, Serialize}; use thiserror::Error; @@ -240,6 +240,7 @@ pub enum SetFeatureInfo { #[derive(Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum PcidClientRequest { + EnableDevice, RequestConfig, RequestFeatures, RequestVendorCapabilities, @@ -260,6 +261,7 @@ pub enum PcidServerResponseError { #[derive(Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum PcidClientResponse { + EnabledDevice, Config(SubdriverArguments), AllFeatures(Vec), VendorCapabilities(Vec), @@ -277,14 +279,9 @@ pub struct MappedBar { pub bar_size: usize, } -// TODO: Ideally, pcid might have its own scheme, like lots of other Redox drivers, where this kind of IPC is done. Otherwise, instead of writing serde messages over -// a channel, the communication could potentially be done via mmap, using a channel -// very similar to crossbeam-channel or libstd's mpsc (except the cycle, enqueue and dequeue fields -// are stored in the same buffer as the actual data). /// A handle from a `pcid` client (e.g. `ahcid`) to `pcid`. pub struct PciFunctionHandle { - pcid_to_client: File, - pcid_from_client: File, + channel: File, config: SubdriverArguments, mapped_bars: [Option; 6], } @@ -293,9 +290,7 @@ pub struct PciFunctionHandle { pub fn send(w: &mut W, message: &T) -> Result<()> { let mut data = Vec::new(); bincode::serialize_into(&mut data, message)?; - let length_bytes = u64::to_le_bytes(data.len() as u64); - w.write_all(&length_bytes)?; - w.write_all(&data)?; + assert_eq!(w.write(&data)?, data.len()); Ok(()) } #[doc(hidden)] @@ -314,42 +309,69 @@ pub fn recv(r: &mut R) -> Result { impl PciFunctionHandle { pub fn connect_default() -> Result { - let pcid_to_client_fd = env::var("PCID_TO_CLIENT_FD")? - .parse::() - .map_err(PcidClientHandleError::EnvValidityError)?; - let pcid_from_client_fd = env::var("PCID_FROM_CLIENT_FD")? + let channel_fd = env::var("PCID_CLIENT_CHANNEL")? .parse::() .map_err(PcidClientHandleError::EnvValidityError)?; + Self::connect_common(channel_fd) + } - let mut pcid_to_client = unsafe { File::from_raw_fd(pcid_to_client_fd) }; - let mut pcid_from_client = unsafe { File::from_raw_fd(pcid_from_client_fd) }; + pub fn connect_by_path(device_path: &Path) -> Result { + let channel_fd = syscall::open( + device_path.join("channel").to_str().unwrap(), + syscall::O_RDWR, + ) + .map_err(|err| { + PcidClientHandleError::IoError(io::Error::other(format!( + "failed to open pcid channel: {}", + err + ))) + })?; + Self::connect_common(channel_fd as RawFd) + } - send(&mut pcid_from_client, &PcidClientRequest::RequestConfig)?; - let config = match recv(&mut pcid_to_client)? { + fn connect_common( + channel_fd: i32, + ) -> std::result::Result { + 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 => return Err(PcidClientHandleError::InvalidResponse(other)), }; Ok(Self { - pcid_to_client, - pcid_from_client, + channel, config, mapped_bars: [const { None }; 6], }) } + + pub fn into_inner_fd(self) -> RawFd { + self.channel.into_raw_fd() + } + fn send(&mut self, req: &PcidClientRequest) -> Result<()> { - send(&mut self.pcid_from_client, req) + send(&mut self.channel, req) } fn recv(&mut self) -> Result { - recv(&mut self.pcid_to_client) + recv(&mut self.channel) } + pub fn config(&self) -> SubdriverArguments { self.config.clone() } + pub fn enable_device(&mut self) -> Result<()> { + self.send(&PcidClientRequest::EnableDevice)?; + match self.recv()? { + PcidClientResponse::EnabledDevice => Ok(()), + other => Err(PcidClientHandleError::InvalidResponse(other)), + } + } + pub fn get_vendor_capabilities(&mut self) -> Result> { self.send(&PcidClientRequest::RequestVendorCapabilities)?; - match self.recv()? { PcidClientResponse::VendorCapabilities(a) => Ok(a), other => Err(PcidClientHandleError::InvalidResponse(other)), diff --git a/pcid/src/main.rs b/pcid/src/main.rs index c432539fda..3545c800f9 100644 --- a/pcid/src/main.rs +++ b/pcid/src/main.rs @@ -1,127 +1,117 @@ +#![feature(array_chunks)] #![feature(non_exhaustive_omitted_patterns_lint)] #![feature(iter_next_chunk)] #![feature(if_let_guard)] -use std::fs::{metadata, read_dir, File}; -use std::io::prelude::*; -use std::path::PathBuf; -use std::sync::{Arc, Mutex}; -use std::thread; +use std::collections::BTreeMap; use log::{debug, info, trace, warn}; +use pci_types::capability::PciCapability; use pci_types::{ Bar as TyBar, CommandRegister, EndpointHeader, HeaderType, PciAddress, PciHeader as TyPciHeader, PciPciBridgeHeader, }; +use redox_scheme::{RequestKind, SignalBehavior}; use crate::cfg_access::Pcie; -use pcid_interface::{config::Config, FullDeviceId, LegacyInterruptLine, PciBar}; +use pcid_interface::{FullDeviceId, LegacyInterruptLine, PciBar, PciFunction}; mod cfg_access; mod driver_handler; +mod scheme; -pub struct State { - threads: Mutex>>, - pcie: Pcie, +pub struct Func { + inner: PciFunction, + + capabilities: Vec, + endpoint_header: EndpointHeader, + enabled: bool, } fn handle_parsed_header( - state: Arc, - config: &Config, - mut endpoint_header: EndpointHeader, + pcie: &Pcie, + tree: &mut BTreeMap, + endpoint_header: EndpointHeader, full_device_id: FullDeviceId, ) { - for driver in config.drivers.iter() { - if !driver.match_function(&full_device_id) { + let mut bars = [PciBar::None; 6]; + let mut skip = false; + for i in 0..6 { + if skip { + skip = false; continue; } - - let Some(ref args) = driver.command else { - continue; - }; - - let mut bars = [PciBar::None; 6]; - let mut skip = false; - for i in 0..6 { - if skip { - skip = false; - continue; - } - match endpoint_header.bar(i, &state.pcie) { - Some(TyBar::Io { port }) => { - bars[i as usize] = PciBar::Port(port.try_into().unwrap()) - } - Some(TyBar::Memory32 { - address, + match endpoint_header.bar(i, pcie) { + Some(TyBar::Io { port }) => bars[i as usize] = PciBar::Port(port.try_into().unwrap()), + Some(TyBar::Memory32 { + address, + size, + prefetchable: _, + }) => { + bars[i as usize] = PciBar::Memory32 { + addr: address, size, - prefetchable: _, - }) => { - bars[i as usize] = PciBar::Memory32 { - addr: address, - size, - } } - Some(TyBar::Memory64 { - address, + } + Some(TyBar::Memory64 { + address, + size, + prefetchable: _, + }) => { + bars[i as usize] = PciBar::Memory64 { + addr: address, size, - prefetchable: _, - }) => { - bars[i as usize] = PciBar::Memory64 { - addr: address, - size, - }; - skip = true; // Each 64bit memory BAR occupies two slots - } - None => bars[i as usize] = PciBar::None, + }; + skip = true; // Each 64bit memory BAR occupies two slots } + None => bars[i as usize] = PciBar::None, } + } - let mut string = String::new(); - for (i, bar) in bars.iter().enumerate() { - if !bar.is_none() { - string.push_str(&format!(" {i}={}", bar.display())); - } + let mut string = String::new(); + for (i, bar) in bars.iter().enumerate() { + if !bar.is_none() { + string.push_str(&format!(" {i}={}", bar.display())); } + } - if !string.is_empty() { - info!(" BAR{}", string); - } + if !string.is_empty() { + info!(" BAR{}", string); + } - let legacy_interrupt_line = enable_function(&state, &mut endpoint_header); + let capabilities = if endpoint_header.status(pcie).has_capability_list() { + endpoint_header.capabilities(pcie).collect::>() + } else { + Vec::new() + }; + debug!( + "PCI DEVICE CAPABILITIES for {}: {:?}", + endpoint_header.header().address(), + capabilities + ); - let capabilities = if endpoint_header.status(&state.pcie).has_capability_list() { - endpoint_header - .capabilities(&state.pcie) - .collect::>() - } else { - Vec::new() - }; - debug!( - "PCI DEVICE CAPABILITIES for {}: {:?}", - args.iter() - .map(|string| string.as_ref()) - .nth(0) - .unwrap_or("[unknown]"), - capabilities - ); - - let func = pcid_interface::PciFunction { + let func = Func { + inner: pcid_interface::PciFunction { bars, addr: endpoint_header.header().address(), - legacy_interrupt_line, + legacy_interrupt_line: None, // Will be filled in when enabling the device full_device_id: full_device_id.clone(), - }; + }, - driver_handler::DriverHandler::spawn(Arc::clone(&state), func, capabilities, args); - } + capabilities, + endpoint_header, + enabled: false, + }; + + tree.insert(func.inner.addr, func); } fn enable_function( - state: &Arc, + pcie: &Pcie, endpoint_header: &mut EndpointHeader, ) -> Option { // Enable bus mastering, memory space, and I/O space - endpoint_header.update_command(&state.pcie, |cmd| { + endpoint_header.update_command(pcie, |cmd| { cmd | CommandRegister::BUS_MASTER_ENABLE | CommandRegister::MEMORY_ENABLE | CommandRegister::IO_ENABLE @@ -131,7 +121,7 @@ fn enable_function( let mut irq = 0xFF; let mut interrupt_pin = 0xFF; - endpoint_header.update_interrupt(&state.pcie, |(pin, mut line)| { + endpoint_header.update_interrupt(pcie, |(pin, mut line)| { if line == 0xFF { line = 9; } @@ -156,13 +146,12 @@ fn enable_function( | ((pci_address.device() as u32) << 11) | ((pci_address.function() as u32) << 8); let addr = [ - dt_address & state.pcie.interrupt_map_mask[0], + dt_address & pcie.interrupt_map_mask[0], 0u32, 0u32, - interrupt_pin as u32 & state.pcie.interrupt_map_mask[3], + interrupt_pin as u32 & pcie.interrupt_map_mask[3], ]; - let mapping = state - .pcie + let mapping = pcie .interrupt_map .iter() .find(|x| x.addr == addr[0..3] && x.interrupt == addr[3]); @@ -187,34 +176,6 @@ fn enable_function( fn main() { let mut args = pico_args::Arguments::from_env(); - - let mut config = Config::default(); - - if let Ok(config_path) = args.free_from_str::() { - if metadata(&config_path).unwrap().is_file() { - if let Ok(mut config_file) = File::open(&config_path) { - let mut config_data = String::new(); - if let Ok(_) = config_file.read_to_string(&mut config_data) { - config = toml::from_str(&config_data).unwrap_or(Config::default()); - } - } - } else { - let paths = read_dir(&config_path).unwrap(); - - let mut config_data = String::new(); - - for path in paths { - if let Ok(mut config_file) = File::open(&path.unwrap().path()) { - let mut tmp = String::new(); - if let Ok(_) = config_file.read_to_string(&mut tmp) { - config_data.push_str(&tmp); - } - } - } - config = toml::from_str(&config_data).unwrap_or(Config::default()); - } - } - let verbosity = (0..).find(|_| !args.contains("-v")).unwrap_or(0); let log_level = match verbosity { 0 => log::LevelFilter::Info, @@ -224,14 +185,12 @@ fn main() { common::setup_logging("bus", "pci", "pcid", log_level, log::LevelFilter::Trace); - redox_daemon::Daemon::new(move |daemon| main_inner(config, daemon)).unwrap(); + redox_daemon::Daemon::new(move |daemon| main_inner(daemon)).unwrap(); } -fn main_inner(config: Config, daemon: redox_daemon::Daemon) -> ! { - let state = Arc::new(State { - pcie: Pcie::new(), - threads: Mutex::new(Vec::new()), - }); +fn main_inner(daemon: redox_daemon::Daemon) -> ! { + let pcie = Pcie::new(); + let mut tree = BTreeMap::new(); info!("PCI SG-BS:DV.F VEND:DEVI CL.SC.IN.RV"); @@ -245,22 +204,45 @@ fn main_inner(config: Config, daemon: redox_daemon::Daemon) -> ! { bus_i += 1; for dev_num in 0..32 { - scan_device(&config, &state, &mut bus_nums, bus_num, dev_num); + scan_device(&mut tree, &pcie, &mut bus_nums, bus_num, dev_num); + } + } + info!("Enumeration complete, now starting pci scheme"); + + let mut scheme = scheme::PciScheme::new(pcie, tree); + let socket = redox_scheme::Socket::create("pci").expect("failed to open pci scheme socket"); + + let _ = daemon.ready(); + + loop { + let Some(request) = socket + .next_request(SignalBehavior::Restart) + .expect("pcid: failed to get next scheme request") + else { + break; + }; + match request.kind() { + RequestKind::Call(call) => { + let response = call.handle_scheme(&mut scheme); + + socket + .write_responses(&[response], SignalBehavior::Restart) + .expect("pcid: failed to write next scheme response"); + } + RequestKind::OnClose { id } => { + scheme.on_close(id); + } + _ => (), } } - daemon.ready().unwrap(); - - for thread in state.threads.lock().unwrap().drain(..) { - thread.join().unwrap(); - } - + println!("pcid: exit"); std::process::exit(0); } fn scan_device( - config: &Config, - state: &Arc, + tree: &mut BTreeMap, + pcie: &Pcie, bus_nums: &mut Vec, bus_num: u8, dev_num: u8, @@ -268,7 +250,7 @@ fn scan_device( for func_num in 0..8 { let header = TyPciHeader::new(PciAddress::new(0, bus_num, dev_num, func_num)); - let (vendor_id, device_id) = header.id(&state.pcie); + let (vendor_id, device_id) = header.id(pcie); if vendor_id == 0xffff && device_id == 0xffff { if func_num == 0 { trace!("PCI {:>02X}:{:>02X}: no dev", bus_num, dev_num); @@ -278,7 +260,7 @@ fn scan_device( continue; } - let (revision, class, subclass, interface) = header.revision_and_class(&state.pcie); + let (revision, class, subclass, interface) = header.revision_and_class(pcie); let full_device_id = FullDeviceId { vendor_id, device_id, @@ -290,20 +272,20 @@ fn scan_device( info!("PCI {} {}", header.address(), full_device_id.display()); - let has_multiple_functions = header.has_multiple_functions(&state.pcie); + let has_multiple_functions = header.has_multiple_functions(pcie); - match header.header_type(&state.pcie) { + match header.header_type(pcie) { HeaderType::Endpoint => { handle_parsed_header( - Arc::clone(state), - config, - EndpointHeader::from_header(header, &state.pcie).unwrap(), + pcie, + tree, + EndpointHeader::from_header(header, pcie).unwrap(), full_device_id, ); } HeaderType::PciPciBridge => { - let bridge_header = PciPciBridgeHeader::from_header(header, &state.pcie).unwrap(); - bus_nums.push(bridge_header.secondary_bus_number(&state.pcie)); + let bridge_header = PciPciBridgeHeader::from_header(header, pcie).unwrap(); + bus_nums.push(bridge_header.secondary_bus_number(pcie)); } ty => { warn!("pcid: unknown header type: {ty:?}"); diff --git a/pcid/src/scheme.rs b/pcid/src/scheme.rs new file mode 100644 index 0000000000..cee1fc7a11 --- /dev/null +++ b/pcid/src/scheme.rs @@ -0,0 +1,312 @@ +use std::collections::{BTreeMap, VecDeque}; + +use pci_types::PciAddress; +use redox_scheme::{CallerCtx, OpenResult, Scheme}; +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::schemev2::NewFdFlags; +use syscall::ENOLCK; + +use crate::cfg_access::Pcie; + +pub struct PciScheme { + handles: BTreeMap, + next_id: usize, + pcie: Pcie, + tree: BTreeMap, +} +enum Handle { + TopLevel { entries: Vec }, + Device, + Channel { addr: PciAddress, st: ChannelState }, +} +struct HandleWrapper { + inner: Handle, + stat: bool, +} +impl Handle { + fn is_file(&self) -> bool { + matches!(self, Self::Channel { .. }) + } + fn is_dir(&self) -> bool { + !self.is_file() + } + // TODO: capability rather than root + fn requires_root(&self) -> bool { + matches!(self, Self::Channel { .. }) + } +} + +enum ChannelState { + AwaitingData, + AwaitingResponseRead(VecDeque), +} + +const DEVICE_CONTENTS: &[&str] = &["channel"]; + +impl Scheme for PciScheme { + fn xopen(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result { + log::trace!("OPEN `{}` flags {}", path, flags); + + // TODO: Check flags are correct + let expects_dir = path.ends_with('/') || flags & O_DIRECTORY != 0; + + 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::>(), + } + } 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)? + }; + + let stat = flags & O_STAT != 0; + if expects_dir && handle.is_file() && !stat { + return Err(Error::new(ENOTDIR)); + } + if !expects_dir && handle.is_dir() && !stat { + return Err(Error::new(EISDIR)); + } + if ctx.uid != 0 && handle.requires_root() && !stat { + return Err(Error::new(EACCES)); + } + + let id = self.next_id; + self.next_id += 1; + + self.handles.insert( + id, + HandleWrapper { + inner: handle, + stat, + }, + ); + Ok(OpenResult::ThisScheme { + number: id, + flags: NewFdFlags::POSITIONED, + }) + } + fn fstat(&mut self, id: usize, stat: &mut syscall::Stat) -> Result { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + let (len, mode) = match handle.inner { + Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755), + Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755), + Handle::Channel { .. } => (0, MODE_CHR | 0o600), + }; + stat.st_size = len as u64; + stat.st_mode = mode; + Ok(0) + } + fn read( + &mut self, + id: usize, + buf: &mut [u8], + _offset: u64, + _fcntl_flags: u32, + ) -> Result { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + if handle.stat { + return Err(Error::new(EBADF)); + } + + match handle.inner { + Handle::TopLevel { .. } => Err(Error::new(EISDIR)), + Handle::Device => Err(Error::new(EISDIR)), + Handle::Channel { + addr: _, + ref mut st, + } => Self::read_channel(st, buf), + } + } + fn getdents<'buf>( + &mut self, + id: usize, + mut buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> Result> { + let Ok(offset) = usize::try_from(opaque_offset) else { + return Ok(buf); + }; + + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + if handle.stat { + return Err(Error::new(EBADF)); + } + + let entries = match handle.inner { + Handle::TopLevel { ref entries } => { + for (i, dent_name) in entries.iter().enumerate().skip(offset) { + buf.entry(DirEntry { + inode: 0, + name: dent_name, + kind: DirentKind::Unspecified, + next_opaque_id: i as u64 + 1, + })?; + } + return Ok(buf); + } + Handle::Device => DEVICE_CONTENTS, + Handle::Channel { .. } => return Err(Error::new(ENOTDIR)), + }; + + for (i, dent_name) in entries.iter().enumerate().skip(offset) { + buf.entry(DirEntry { + inode: 0, + name: dent_name, + kind: DirentKind::Unspecified, + next_opaque_id: i as u64 + 1, + })?; + } + Ok(buf) + } + + fn write(&mut self, id: usize, buf: &[u8], _offset: u64, _fcntl_flags: u32) -> Result { + let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?; + + if handle.stat { + return Err(Error::new(EBADF)); + } + + match handle.inner { + Handle::Channel { addr, ref mut st } => { + Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf) + } + + _ => Err(Error::new(EBADF)), + } + } +} + +impl PciScheme { + pub fn on_close(&mut self, id: usize) { + match self.handles.remove(&id) { + Some(HandleWrapper { + inner: Handle::Channel { addr, .. }, + .. + }) => { + log::trace!("TODO: Support disabling device (called on {})", addr); + if let Some(func) = self.tree.get_mut(&addr) { + func.enabled = false; + } + } + _ => {} + } + } +} + +impl PciScheme { + pub fn new(pcie: Pcie, tree: BTreeMap) -> Self { + Self { + handles: BTreeMap::new(), + next_id: 0, + pcie, + tree, + } + } + fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> 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 + } else { + let path = &after[1..]; + + 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); + func.enabled = true; + Handle::Channel { + addr, + st: ChannelState::AwaitingData, + } + } + _ => return Err(Error::new(ENOENT)), + } + }) + } + + fn read_channel(state: &mut ChannelState, buf: &mut [u8]) -> Result { + match *state { + ChannelState::AwaitingResponseRead(ref mut queue) => { + let byte_count = std::cmp::min(queue.len(), buf.len()); + // XXX: Why can't VecDeque support dequeueing into slices? + for (idx, byte) in queue.drain(..byte_count).enumerate() { + buf[idx] = byte; + } + if queue.is_empty() { + *state = ChannelState::AwaitingData; + } + Ok(byte_count) + } + ChannelState::AwaitingData => Err(Error::new(EINVAL)), + } + } + fn write_channel( + pci_state: &Pcie, + tree: &mut BTreeMap, + addr: PciAddress, + state: &mut ChannelState, + buf: &[u8], + ) -> Result { + match *state { + ChannelState::AwaitingResponseRead(_) => return Err(Error::new(EINVAL)), + ChannelState::AwaitingData => { + let func = tree.get_mut(&addr).unwrap(); + + let request = bincode::deserialize_from(buf).map_err(|_| Error::new(EINVAL))?; + let response = crate::driver_handler::DriverHandler::new( + func.inner.clone(), + &mut func.endpoint_header, + func.capabilities.clone(), + &*pci_state, + ) + .respond(request); + + let mut output_bytes = vec![0_u8; 8]; + bincode::serialize_into(&mut output_bytes, &response) + .map_err(|_| Error::new(EIO))?; + let len = output_bytes.len() - 8; + output_bytes[..8].copy_from_slice(&u64::to_le_bytes(len as u64)); + *state = ChannelState::AwaitingResponseRead(output_bytes.into()); + + Ok(buf.len()) + } + } + } +} + +fn parse_pci_addr(addr: &str) -> Option { + let (segment, rest) = addr.split_once('-')?; + let segment = u16::from_str_radix(segment, 16).ok()?; + + // FIXME use : instead of -- as separator once the old scheme format is no longer supported. + let (bus, rest) = rest.split_once("--")?; + let bus = u8::from_str_radix(bus, 16).ok()?; + + let (device, function) = rest.split_once('.')?; + let device = u8::from_str_radix(device, 16).ok()?; + let function = u8::from_str_radix(function, 16).ok()?; + + Some(PciAddress::new(segment, bus, device, function)) +}