Merge branch 'pcid-scheme2' into 'master'

Introduce a pci scheme and move driver spawning to pcid-spawner

See merge request redox-os/drivers!240
This commit is contained in:
Jeremy Soller
2025-03-01 16:38:49 +00:00
10 changed files with 681 additions and 296 deletions
Generated
+40 -15
View File
@@ -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",
+1
View File
@@ -4,6 +4,7 @@ members = [
"common",
"hwd",
"pcid",
"pcid-spawner",
"vboxd",
"xhcid",
"usbctl",
+17
View File
@@ -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" }
+102
View File
@@ -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::<String>()
.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(())
}
+2 -1
View File
@@ -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"
+40 -117
View File
@@ -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<PciCapability>,
state: Arc<State>,
pcie: &'a Pcie,
}
impl DriverHandler {
pub fn spawn(
state: Arc<State>,
func: pcid_interface::PciFunction,
impl<'a> DriverHandler<'a> {
pub fn new(
func: PciFunction,
endpoint_header: &'a mut EndpointHeader,
capabilities: Vec<PciCapability>,
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::<Vec<_>>(),
),
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();
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ pub struct DriverConfig {
pub vendor: Option<u16>,
pub device: Option<u16>,
pub device_id_range: Option<Range<u16>>,
pub command: Option<Vec<String>>,
pub command: Vec<String>,
}
impl DriverConfig {
+46 -24
View File
@@ -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<PciFeature>),
VendorCapabilities(Vec<VendorSpecificCapability>),
@@ -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<MappedBar>; 6],
}
@@ -293,9 +290,7 @@ pub struct PciFunctionHandle {
pub fn send<W: Write, T: Serialize>(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: Read, T: DeserializeOwned>(r: &mut R) -> Result<T> {
impl PciFunctionHandle {
pub fn connect_default() -> Result<Self> {
let pcid_to_client_fd = env::var("PCID_TO_CLIENT_FD")?
.parse::<RawFd>()
.map_err(PcidClientHandleError::EnvValidityError)?;
let pcid_from_client_fd = env::var("PCID_FROM_CLIENT_FD")?
let channel_fd = env::var("PCID_CLIENT_CHANNEL")?
.parse::<RawFd>()
.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<Self> {
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<PciFunctionHandle, PcidClientHandleError> {
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<PcidClientResponse> {
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<Vec<VendorSpecificCapability>> {
self.send(&PcidClientRequest::RequestVendorCapabilities)?;
match self.recv()? {
PcidClientResponse::VendorCapabilities(a) => Ok(a),
other => Err(PcidClientHandleError::InvalidResponse(other)),
+120 -138
View File
@@ -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<Vec<thread::JoinHandle<()>>>,
pcie: Pcie,
pub struct Func {
inner: PciFunction,
capabilities: Vec<PciCapability>,
endpoint_header: EndpointHeader,
enabled: bool,
}
fn handle_parsed_header(
state: Arc<State>,
config: &Config,
mut endpoint_header: EndpointHeader,
pcie: &Pcie,
tree: &mut BTreeMap<PciAddress, Func>,
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::<Vec<_>>()
} 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::<Vec<_>>()
} 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<State>,
pcie: &Pcie,
endpoint_header: &mut EndpointHeader,
) -> Option<LegacyInterruptLine> {
// 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::<PathBuf>() {
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<State>,
tree: &mut BTreeMap<PciAddress, Func>,
pcie: &Pcie,
bus_nums: &mut Vec<u8>,
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:?}");
+312
View File
@@ -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<usize, HandleWrapper>,
next_id: usize,
pcie: Pcie,
tree: BTreeMap<PciAddress, crate::Func>,
}
enum Handle {
TopLevel { entries: Vec<String> },
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<u8>),
}
const DEVICE_CONTENTS: &[&str] = &["channel"];
impl Scheme for PciScheme {
fn xopen(&mut self, path: &str, flags: usize, ctx: &CallerCtx) -> Result<OpenResult> {
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::<Vec<_>>(),
}
} 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<usize> {
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<usize> {
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<DirentBuf<&'buf mut [u8]>> {
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<usize> {
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<PciAddress, crate::Func>) -> Self {
Self {
handles: BTreeMap::new(),
next_id: 0,
pcie,
tree,
}
}
fn parse_after_pci_addr(&mut self, addr: PciAddress, after: &str) -> Result<Handle> {
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<usize> {
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<PciAddress, crate::Func>,
addr: PciAddress,
state: &mut ChannelState,
buf: &[u8],
) -> Result<usize> {
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<PciAddress> {
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))
}