7f0e870b76
And avoid hiding channel open errors in pcid-spawner. This would have made the location of the problem more obvious.
493 lines
15 KiB
Rust
493 lines
15 KiB
Rust
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}"))
|
|
}
|
|
}
|
|
}
|
|
|
|
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<PciAddressDef> 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<PciRom>,
|
|
|
|
/// 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<LegacyInterruptLine>,
|
|
|
|
/// 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,
|
|
}
|
|
|
|
#[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<u8>,
|
|
|
|
/// 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<msi::MsiAddrAndData>,
|
|
|
|
/// 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<u32>,
|
|
}
|
|
|
|
/// 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<bool>,
|
|
},
|
|
}
|
|
|
|
#[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<PciFeature>),
|
|
VendorCapabilities(Vec<VendorSpecificCapability>),
|
|
FeatureEnabled(PciFeature),
|
|
FeatureStatus(PciFeature, FeatureStatus),
|
|
Error(PcidServerResponseError),
|
|
FeatureInfo(PciFeature, PciFeatureInfo),
|
|
SetFeatureInfo(PciFeature),
|
|
ReadConfig(u32),
|
|
WriteConfig,
|
|
}
|
|
|
|
pub struct MappedBar {
|
|
pub ptr: NonNull<u8>,
|
|
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<MappedBar>; 6],
|
|
}
|
|
|
|
fn send<T: Serialize>(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<T: DeserializeOwned>(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 {
|
|
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::<RawFd>() {
|
|
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<Self> {
|
|
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],
|
|
}
|
|
}
|
|
|
|
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<VendorSpecificCapability> {
|
|
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<PciFeature> {
|
|
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::<u8>()).expect("Mapping a BAR resulted in a nullptr"),
|
|
bar_size,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn pci_daemon<F: FnOnce(Daemon, PciFunctionHandle) -> !>(f: F) -> ! {
|
|
Daemon::new(|daemon| {
|
|
common::init();
|
|
let pcid_handle = PciFunctionHandle::connect_default();
|
|
f(daemon, pcid_handle)
|
|
})
|
|
}
|