Files
RedBear-OS/drivers/pcid/src/driver_interface/mod.rs
T
Red Bear OS ddb40deac5 xhcid + pcid: P2-A — 51-quirk table ported from Linux 7.1
xhcid:
  - New module xhci/quirks.rs: 51-quirk XhciQuirks bitflags + per-vendor
    lookup table.  Ported from linux-7.1/drivers/usb/host/xhci.h:1587-1649
    (51 quirk flags) + xhci-pci.c (per-vendor lookup).
  - Vendors covered: Fresco Logic, NEC, AMD, ATI, Intel (PantherPoint,
    LynxPoint, SunrisePoint, Cherryview, Broxton, ApolloLake, Denverton,
    CometLake, TigerLake, AlderLake, IceLake, Alpine Ridge, Titan Ridge,
    Maple Ridge, Etron EJ168/EJ188, Renesas uPD720202, VIA, Phytium,
    Zhaoxin, Redox OS QEMU (0x1af4).
  - Tests for Intel/AMD/Etron/Renesas/unknown-vendor coverage.
  - Xhci struct gains a public quirks: XhciQuirks field.
  - main.rs detects vendor/device/class from pcid, applies quirks.

pcid:
  - SubdriverArguments gains device_id: Option<FullDeviceId> field.
  - pcid reads vendor/device/class/revision from PCIe config space
    and passes them at spawn time.  Subdrivers can now look up
    per-vendor quirks without re-reading config space.

Cross-reference: linux-7.1/drivers/usb/host/xhci.h:1587-1649 (51
quirk flags) + xhci-pci.c (per-vendor lookup table, 20+ entries).

Bitflags 2.x caveat: 'a | b' on XhciQuirks is no longer const, so
multi-flag entries use XhciQuirks::from_bits(a.bits() | b.bits()).unwrap()
in const context.

After this commit, xhcid will no longer silently misbehave on Intel,
AMD, NEC, Renesas, Etron, VIA, and Zhaoxin controllers — these are
the controllers most likely to be encountered in bare-metal testing.
2026-07-07 09:19:14 +03:00

497 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,
/// 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<crate::driver_interface::FullDeviceId>,
}
#[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)
})
}