use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::bus::pci::{
Bdf, CONFIG_PORT_WINDOW_LEN, ConfigPorts, ConfigSpace, PciBus, PciFunction, buses, config,
};
use crate::core::device::{Device, DeviceClass, ExportId, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::{AddressSpace, MappingId, MemAttrs, Perms, RamStore, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::machine::realize::{BindCtx, Instance};
use crate::machine::validate::ClassSchema;
pub const CLASS_NAME: &str = "pc.pmc";
const STATE_VERSION: u32 = 1;
const DEVICE_ID: u16 = 0x1237;
pub const SHADOW_BASE: u64 = 0x000c_0000;
pub const SHADOW_LEN: u64 = 0x0004_0000;
const SHADOW_PRIORITY: i32 = 1;
const PAM0: u16 = 0x59;
const PAM_COUNT: u16 = 7;
struct Window {
base: u64,
len: u64,
reg: u16,
shift: u32,
}
const WINDOWS: [Window; 13] = [
Window {
base: 0xc_0000,
len: 0x4000,
reg: PAM0 + 1,
shift: 0,
},
Window {
base: 0xc_4000,
len: 0x4000,
reg: PAM0 + 1,
shift: 4,
},
Window {
base: 0xc_8000,
len: 0x4000,
reg: PAM0 + 2,
shift: 0,
},
Window {
base: 0xc_c000,
len: 0x4000,
reg: PAM0 + 2,
shift: 4,
},
Window {
base: 0xd_0000,
len: 0x4000,
reg: PAM0 + 3,
shift: 0,
},
Window {
base: 0xd_4000,
len: 0x4000,
reg: PAM0 + 3,
shift: 4,
},
Window {
base: 0xd_8000,
len: 0x4000,
reg: PAM0 + 4,
shift: 0,
},
Window {
base: 0xd_c000,
len: 0x4000,
reg: PAM0 + 4,
shift: 4,
},
Window {
base: 0xe_0000,
len: 0x4000,
reg: PAM0 + 5,
shift: 0,
},
Window {
base: 0xe_4000,
len: 0x4000,
reg: PAM0 + 5,
shift: 4,
},
Window {
base: 0xe_8000,
len: 0x4000,
reg: PAM0 + 6,
shift: 0,
},
Window {
base: 0xe_c000,
len: 0x4000,
reg: PAM0 + 6,
shift: 4,
},
Window {
base: 0xf_0000,
len: 0x1_0000,
reg: PAM0,
shift: 4,
},
];
const N: usize = WINDOWS.len();
const RE: u8 = 0x1;
const WE: u8 = 0x2;
fn perms_of(nibble: u8) -> Perms {
let mut p = Perms::NONE;
if nibble & RE != 0 {
p = p.union(Perms::READ).union(Perms::EXEC);
}
if nibble & WE != 0 {
p = p.union(Perms::WRITE);
}
p
}
struct Registers {
config: Mutex<ConfigSpace>,
ports: Arc<ConfigPorts>,
dram: Arc<RamStore>,
windows: Vec<RegionRef>,
mapped: Mutex<Option<Mapped>>,
stale: Mutex<bool>,
}
#[derive(Debug, Clone)]
struct Mapped {
space: Arc<AddressSpace>,
ids: Vec<Option<MappingId>>,
}
impl fmt::Debug for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Registers");
match self.config.try_lock() {
Some(c) => s.field(
"pam",
&[
c.byte(PAM0),
c.byte(PAM0 + 1),
c.byte(PAM0 + 2),
c.byte(PAM0 + 3),
c.byte(PAM0 + 4),
c.byte(PAM0 + 5),
c.byte(PAM0 + 6),
],
),
None => s.field("pam", &"<in use>"),
};
s.field("mapped", &self.mapped.try_lock().map(|m| m.is_some()))
.finish()
}
}
impl Registers {
fn fresh_config(revision: u8) -> ConfigSpace {
let mut c = ConfigSpace::new();
c.hardwire(config::VENDOR_ID, u32::from(config::VENDOR_INTEL), 2);
c.hardwire(config::DEVICE_ID, u32::from(DEVICE_ID), 2);
c.hardwire(config::COMMAND, 0x0006, 2);
c.hardwire(config::STATUS, 0x0280, 2);
c.hardwire(config::REVISION_ID, u32::from(revision), 1);
c.hardwire(config::CLASS_CODE, 0x00, 1);
c.hardwire(
config::CLASS_CODE + 1,
u32::from(config::SUBCLASS_HOST_BRIDGE),
1,
);
c.hardwire(config::CLASS_CODE + 2, u32::from(config::CLASS_BRIDGE), 1);
c.hardwire(config::HEADER_TYPE, 0x00, 1);
c.allow(config::COMMAND, 2);
c.allow(config::LATENCY_TIMER, 1);
c.allow(config::BIST, 1);
c.allow(PAM0, PAM_COUNT);
c
}
fn perms(&self) -> Vec<Perms> {
let c = self.config.lock();
WINDOWS
.iter()
.map(|w| perms_of((c.byte(w.reg) >> w.shift) & 0xf))
.collect()
}
fn retopo(&self, perms: &[Perms], blocking: bool) -> bool {
let Some(mapped) = self.mapped.lock().clone() else {
return true;
};
let guard = if blocking {
Some(mapped.space.topology())
} else {
mapped.space.try_topology()
};
let Some(mut topo) = guard else {
*self.stale.lock() = true;
return false;
};
let mut ids = Vec::with_capacity(N);
for ((id, p), w) in mapped.ids.iter().zip(perms).zip(&WINDOWS) {
let want = *p != Perms::NONE;
ids.push(match (*id, want) {
(Some(id), true) => {
let _ = topo.reprotect(id, *p);
Some(id)
}
(None, true) => topo
.map_with(
crate::core::space::Mapping::new(
Arc::clone(&self.windows[ids.len()]),
w.base,
)
.with_priority(SHADOW_PRIORITY)
.with_perms(*p),
)
.ok(),
(Some(id), false) => {
let _ = topo.unmap(id);
None
}
(None, false) => None,
});
}
drop(topo);
*self.mapped.lock() = Some(Mapped {
space: Arc::clone(&mapped.space),
ids,
});
*self.stale.lock() = false;
true
}
fn sync(&self, blocking: bool) -> bool {
let perms = self.perms();
self.retopo(&perms, blocking)
}
fn install(&self, space: &Arc<AddressSpace>) -> Result<()> {
*self.mapped.lock() = Some(Mapped {
space: Arc::clone(space),
ids: alloc::vec![None; N],
});
self.sync(true);
Ok(())
}
}
impl PciFunction for Registers {
fn config_read(&self, offset: u16, dst: &mut [u8], _attrs: MemAttrs) {
self.config.lock().read(offset, dst);
if *self.stale.lock() {
self.sync(false);
}
}
fn config_write(&self, offset: u16, src: &[u8], attrs: MemAttrs) {
if attrs.debug {
return;
}
let (changed, perms) = {
let mut c = self.config.lock();
let changed = c.write(offset, src);
let perms = WINDOWS
.iter()
.map(|w| perms_of((c.byte(w.reg) >> w.shift) & 0xf))
.collect::<Vec<_>>();
(changed, perms)
};
let touches_pam =
offset < PAM0 + PAM_COUNT && offset.saturating_add(src.len() as u16) > PAM0;
if (changed && touches_pam) || *self.stale.lock() {
self.retopo(&perms, false);
}
}
}
#[derive(Debug)]
pub struct Pmc {
regs: Arc<Registers>,
bus: Arc<PciBus>,
at: Bdf,
config_region: RegionRef,
revision: u8,
passthrough: Mutex<Option<String>>,
}
impl Pmc {
pub fn new(props: &Props) -> Result<Pmc> {
let mut r = props.reader();
let bus_name = r.or_str("bus", "pci0")?.to_string();
let device = r.or_range("device", 0u64, 0..=u64::from(crate::bus::pci::MAX_DEVICE))?;
let revision = r.or_range("revision", 0u64, 0..=255)?;
let passthrough = r
.optional_link("passthrough")?
.map(|l| String::from(l.as_str()));
r.finish()?;
let bus = buses::attach(props, &bus_name)?;
let at = Bdf::new(0, device as u8, 0)?;
let pmc = Pmc::with_bus(bus, at, revision as u8)?;
*pmc.passthrough.lock() = passthrough;
Ok(pmc)
}
pub fn with_bus(bus: Arc<PciBus>, at: Bdf, revision: u8) -> Result<Pmc> {
let dram = Arc::new(RamStore::new(SHADOW_LEN));
let whole: RegionRef = Arc::new(Region::ram("pc.pmc.dram", Arc::clone(&dram)));
let mut windows = Vec::with_capacity(N);
for w in &WINDOWS {
windows.push(Arc::new(Region::alias(
alloc::format!("pc.pmc.shadow.{:05x}", w.base),
Arc::clone(&whole),
w.base - SHADOW_BASE,
w.len,
)?) as RegionRef);
}
let ports = Arc::new(ConfigPorts::new(Arc::clone(&bus)));
let config_region: RegionRef = Arc::new(Region::io(
"pc.pmc.config",
CONFIG_PORT_WINDOW_LEN,
Arc::clone(&ports) as Arc<dyn crate::core::space::MemOps>,
));
Ok(Pmc {
regs: Arc::new(Registers {
config: Mutex::with_rank(LockRank::DEVICE, Registers::fresh_config(revision)),
ports,
dram,
windows,
mapped: Mutex::with_rank(LockRank::LEAF, None),
stale: Mutex::with_rank(LockRank::LEAF, false),
}),
bus,
at,
config_region,
revision,
passthrough: Mutex::with_rank(LockRank::LEAF, None),
})
}
#[must_use]
pub fn dram(&self) -> &Arc<RamStore> {
&self.regs.dram
}
#[must_use]
pub fn address(&self) -> Bdf {
self.at
}
#[must_use]
pub fn pam(&self, index: u16) -> Option<u8> {
(index < PAM_COUNT).then(|| self.regs.config.lock().byte(PAM0 + index))
}
pub fn attach_space(&self, space: &Arc<AddressSpace>) -> Result<()> {
self.regs.install(space)
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "an Intel 82441FX PCI host bridge, with the PAM registers that shadow the BIOS",
properties: &[
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the PCI fabric this bridge is the root of (default `pci0`)",
},
PropertySpec {
name: "device",
kind: ValueKind::Uint,
required: false,
summary: "the device number it answers at on bus 0 (default 0, which is the part's own)",
},
PropertySpec {
name: "revision",
kind: ValueKind::Uint,
required: false,
summary: "the revision identification byte (default 0)",
},
PropertySpec {
name: "passthrough",
kind: ValueKind::Link,
required: false,
summary: "the sibling whose own decode lives inside CONFADD's four bytes at 0xcf8",
},
],
construct: |props| Ok(Box::new(Pmc::new(props)?)),
};
impl Device for Pmc {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
self.bus
.attach(self.at, Arc::clone(&self.regs) as Arc<dyn PciFunction>)
}
fn reset(&self, kind: ResetKind) {
*self.regs.config.lock() = Registers::fresh_config(self.revision);
self.regs.ports.reset();
if kind == ResetKind::Cold {
let _ = self.regs.dram.fill(0, SHADOW_LEN, 0);
}
self.regs.sync(true);
}
fn region(&self, name: &str) -> Option<RegionRef> {
match name {
"" | "config" => Some(Arc::clone(&self.config_region)),
_ => None,
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
w.write_bytes(self.regs.config.lock().bytes())?;
w.write_u32(self.regs.ports.address())?;
let len = usize::try_from(SHADOW_LEN)
.map_err(|_| Error::State(String::from("shadow larger than this host")))?;
let mut bytes = alloc::vec![0u8; len];
self.regs.dram.read_at(0, &mut bytes)?;
w.write_bytes(&bytes)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let config: &[u8] = r.read_bytes()?;
let address = r.read_u32()?;
let dram: &[u8] = r.read_bytes()?;
if dram.len() as u64 != SHADOW_LEN {
return Err(Error::State(alloc::format!(
"snapshot has {} byte(s) of shadow DRAM, this bridge has {SHADOW_LEN}",
dram.len()
)));
}
{
let mut c = self.regs.config.lock();
*c = Registers::fresh_config(self.revision);
c.restore(config);
}
self.regs.ports.set_address(address);
self.regs.dram.write_at(0, dram)?;
self.regs.sync(true);
Ok(())
}
}
impl Instance for Pmc {
fn bind(&self, ctx: &BindCtx<'_>) -> Result<()> {
let space = ctx.space().ok_or_else(|| Error::Config {
at: String::from(ctx.path()),
message: String::from(
"a host bridge decides what is decoded in 0xc0000-0xfffff, so it needs the \
space it decides for: add `space = mem` to the object that declares it",
),
})?;
self.attach_space(space)?;
let wanted = self.passthrough.lock().clone();
if let Some(path) = wanted {
let handle =
ctx.export_as::<super::PortPassthrough>(&path, ExportId::PORT_PASSTHROUGH)?;
self.regs.ports.set_passthrough(Arc::clone(handle.ops()));
}
Ok(())
}
}
pub fn register(registry: &mut crate::core::Registry) -> Result<()> {
registry.add(&CLASS)
}
pub fn bind(bindings: &mut crate::machine::Bindings) -> Result<()> {
bindings.bind(CLASS_NAME, |props| Ok(Arc::new(Pmc::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::PropSchema;
ClassSchema::new(CLASS_NAME)
.prop(PropSchema::new("bus", ValueKind::Str))
.prop(
PropSchema::new("device", ValueKind::Uint)
.range(0, u64::from(crate::bus::pci::MAX_DEVICE)),
)
.prop(PropSchema::new("revision", ValueKind::Uint).range(0, 255))
.prop(PropSchema::new("passthrough", ValueKind::Link))
.region("")
.region("config")
}
#[cfg(test)]
mod tests;