use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use core::fmt;
use crate::bus::pci::{Bar, Bars, Bdf, ConfigSpace, PciBus, PciFunction, buses, config};
use crate::core::device::{Device, DeviceClass, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::{Error, Result};
use crate::core::props::{Props, ValueKind};
use crate::core::space::{AddressSpace, MemAttrs, Region, RegionRef, RomStore, RomWrite};
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.vga-pci";
const STATE_VERSION: u32 = 1;
const MIN_ROM_LEN: u64 = 2048;
const MAX_ROM_LEN: u64 = 16 * 1024 * 1024;
const ERASED: u8 = 0xff;
const COMMAND_IMPLEMENTED: u16 =
config::COMMAND_IO | config::COMMAND_MEMORY | config::COMMAND_MASTER | 0x0020;
struct Registers {
config: Mutex<ConfigSpace>,
bars: Bars,
}
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(
"command",
&(u16::from(c.byte(config::COMMAND)) | u16::from(c.byte(config::COMMAND + 1)) << 8),
),
None => s.field("command", &"<in use>"),
};
s.field("bars", &self.bars).finish()
}
}
impl Registers {
fn fresh_config(vendor: u16, device: u16, revision: u8) -> ConfigSpace {
let mut c = ConfigSpace::new();
c.hardwire(config::VENDOR_ID, u32::from(vendor), 2);
c.hardwire(config::DEVICE_ID, u32::from(device), 2);
c.hardwire(config::COMMAND, 0x0000, 2);
c.hardwire(config::STATUS, 0x0200, 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_VGA), 1);
c.hardwire(config::CLASS_CODE + 2, u32::from(config::CLASS_DISPLAY), 1);
c.hardwire(config::HEADER_TYPE, 0x00, 1);
c.hardwire(config::INTERRUPT_PIN, 0x00, 1);
c.allow(config::COMMAND, 2);
c.allow(config::CACHE_LINE_SIZE, 1);
c.allow(config::LATENCY_TIMER, 1);
c.allow(config::INTERRUPT_LINE, 1);
c
}
fn command(&self) -> u16 {
let c = self.config.lock();
u16::from(c.byte(config::COMMAND)) | u16::from(c.byte(config::COMMAND + 1)) << 8
}
}
impl PciFunction for Registers {
fn config_read(&self, offset: u16, dst: &mut [u8], _attrs: MemAttrs) {
self.config.lock().read(offset, dst);
self.bars.config_read(offset, dst);
if self.bars.is_stale() {
self.bars.sync(self.command(), false);
}
}
fn config_write(&self, offset: u16, src: &[u8], attrs: MemAttrs) {
if attrs.debug {
return;
}
let bars_moved = self.bars.config_write(offset, src);
let (command_moved, command) = {
let mut c = self.config.lock();
let moved = c.write(offset, src);
let raw =
u16::from(c.byte(config::COMMAND)) | u16::from(c.byte(config::COMMAND + 1)) << 8;
let kept = raw & COMMAND_IMPLEMENTED;
if kept != raw {
c.set_byte(config::COMMAND, kept as u8);
c.set_byte(config::COMMAND + 1, (kept >> 8) as u8);
}
(
moved
&& offset < config::COMMAND + 2
&& offset.saturating_add(src.len() as u16) > config::COMMAND,
kept,
)
};
if bars_moved || command_moved || self.bars.is_stale() {
self.bars.sync(command, false);
}
}
}
#[derive(Debug)]
pub struct VgaPci {
regs: Arc<Registers>,
bus: Arc<PciBus>,
at: Bdf,
vendor: u16,
device: u16,
revision: u8,
rom: Option<Arc<RomStore>>,
}
impl VgaPci {
pub fn new(props: &Props) -> Result<VgaPci> {
let mut r = props.reader();
let bus_name = r.or_str("bus", "pci0")?.to_string();
let device_no = r.or_range("device", 2u64, 0..=u64::from(crate::bus::pci::MAX_DEVICE))?;
let function_no = r.or_range(
"function",
0u64,
0..=u64::from(crate::bus::pci::MAX_FUNCTION),
)?;
let vendor = r.or_range("vendor-id", 0x1234u64, 0..=0xffff)?;
let device = r.or_range("device-id", 0x1111u64, 0..=0xffff)?;
let revision = r.or_range("revision", 0u64, 0..=255)?;
let image = r.require_media("image")?.to_bytes();
r.finish()?;
let bus = buses::attach(props, &bus_name)?;
let at = Bdf::new(0, device_no as u8, function_no as u8)?;
VgaPci::with_bus(
bus,
at,
vendor as u16,
device as u16,
revision as u8,
&image,
)
}
pub fn with_bus(
bus: Arc<PciBus>,
at: Bdf,
vendor: u16,
device: u16,
revision: u8,
image: &[u8],
) -> Result<VgaPci> {
let mut bars = Bars::new();
let mut rom = None;
if !image.is_empty() {
let len = image.len() as u64;
if len > MAX_ROM_LEN {
return Err(Error::Property(alloc::format!(
"property `image`: an expansion ROM here holds at most {MAX_ROM_LEN} bytes \
and this image is {len}"
)));
}
let window = len.next_power_of_two().max(MIN_ROM_LEN);
let mut bytes = alloc::vec![ERASED; window as usize];
bytes[..image.len()].copy_from_slice(image);
let store = Arc::new(RomStore::new(bytes));
let region: RegionRef = Arc::new(Region::rom(
"pc.vga-pci.rom",
Arc::clone(&store),
RomWrite::Ignore,
));
bars = bars.with(
Bars::ROM,
Bar::rom(window).decoding(region, crate::core::space::Perms::RX),
)?;
rom = Some(store);
}
Ok(VgaPci {
regs: Arc::new(Registers {
config: Mutex::with_rank(
LockRank::DEVICE,
Registers::fresh_config(vendor, device, revision),
),
bars,
}),
bus,
at,
vendor,
device,
revision,
rom,
})
}
#[must_use]
pub fn address(&self) -> Bdf {
self.at
}
#[must_use]
pub fn rom(&self) -> Option<&Arc<RomStore>> {
self.rom.as_ref()
}
#[must_use]
pub fn bars(&self) -> &Bars {
&self.regs.bars
}
#[must_use]
pub fn command(&self) -> u16 {
self.regs.command()
}
pub fn attach_space(&self, space: &Arc<AddressSpace>) -> Result<()> {
self.regs.bars.install(space, self.regs.command())
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "a PCI display adapter's configuration header and its video BIOS's expansion ROM",
properties: &[
PropertySpec {
name: "bus",
kind: ValueKind::Str,
required: false,
summary: "the PCI fabric this card is on (default `pci0`)",
},
PropertySpec {
name: "device",
kind: ValueKind::Uint,
required: false,
summary: "the device number it answers at on bus 0 (default 2)",
},
PropertySpec {
name: "function",
kind: ValueKind::Uint,
required: false,
summary: "the function number, 0-7 (default 0)",
},
PropertySpec {
name: "vendor-id",
kind: ValueKind::Uint,
required: false,
summary: "the vendor identification the video BIOS expects to find (default 0x1234)",
},
PropertySpec {
name: "device-id",
kind: ValueKind::Uint,
required: false,
summary: "the device identification the video BIOS expects to find (default 0x1111)",
},
PropertySpec {
name: "revision",
kind: ValueKind::Uint,
required: false,
summary: "the revision identification byte (default 0)",
},
PropertySpec {
name: "image",
kind: ValueKind::Media,
required: true,
summary: "the media slot the video BIOS is bound to; empty fits no expansion ROM",
},
],
construct: |props| Ok(Box::new(VgaPci::new(props)?)),
};
impl Device for VgaPci {
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.vendor, self.device, self.revision);
self.regs.bars.reset();
self.regs.bars.sync(self.regs.command(), true);
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
w.write_bytes(self.regs.config.lock().bytes())?;
for value in self.regs.bars.latches() {
w.write_u32(value)?;
}
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let config: &[u8] = r.read_bytes()?;
let mut latches = [0u32; Bars::COUNT as usize];
for slot in &mut latches {
*slot = r.read_u32()?;
}
{
let mut c = self.regs.config.lock();
*c = Registers::fresh_config(self.vendor, self.device, self.revision);
c.restore(config);
}
self.regs.bars.set_latches(&latches);
self.regs.bars.sync(self.regs.command(), true);
Ok(())
}
}
impl Instance for VgaPci {
fn bind(&self, ctx: &BindCtx<'_>) -> Result<()> {
let space = ctx.space().ok_or_else(|| Error::Config {
at: String::from(ctx.path()),
message: String::from(
"a card's base address registers place windows in a memory space, so it needs \
the space they are placed in: add `space = mem` to the object that declares it",
),
})?;
self.attach_space(space)
}
}
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(VgaPci::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("function", ValueKind::Uint)
.range(0, u64::from(crate::bus::pci::MAX_FUNCTION)),
)
.prop(PropSchema::new("vendor-id", ValueKind::Uint).range(0, 0xffff))
.prop(PropSchema::new("device-id", ValueKind::Uint).range(0, 0xffff))
.prop(PropSchema::new("revision", ValueKind::Uint).range(0, 255))
.prop(PropSchema::new("image", ValueKind::Media).required())
}
#[cfg(test)]
mod tests;