use chipset_device::io::IoError;
use chipset_device::io::IoResult;
use chipset_device::pci::PciConfigSpace;
use chipset_device::pio::PortIoIntercept;
use chipset_device::ChipsetDevice;
use inspect::Inspect;
use inspect::InspectMut;
use open_enum::open_enum;
use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
use pci_core::cfg_space_emu::DeviceBars;
use pci_core::spec::hwid::ClassCode;
use pci_core::spec::hwid::HardwareIds;
use pci_core::spec::hwid::ProgrammingInterface;
use pci_core::spec::hwid::Subclass;
use vmcore::device_state::ChangeDeviceState;
mod io_ports {
pub const FAST_A20_GATE: u16 = 0x92;
pub const MATH_COPROC0: u16 = 0xF0;
pub const MATH_COPROC1: u16 = 0xF1;
}
struct PciIsaBridgeRuntime {
reset_evt: Box<dyn Fn() + Send + Sync>,
set_a20_signal: Box<dyn FnMut(bool) + Send + Sync>,
}
#[derive(InspectMut)]
pub struct PciIsaBridge {
#[inspect(skip)]
rt: PciIsaBridgeRuntime,
cfg_space: ConfigSpaceType0Emulator,
state: PciIsaBridgeState,
}
#[derive(Inspect)]
struct PciIsaBridgeState {
pci_irq_routing: u32,
smi_control: u32,
smi_request: u32,
system_event: u32,
clock_scale: u32,
apic_base: u32,
a20_gate_enabled: bool,
}
impl PciIsaBridgeState {
fn new() -> Self {
Self {
pci_irq_routing: 0x80808000 | 11,
smi_control: 0x00000008,
smi_request: 0x0000000F,
system_event: 0,
clock_scale: 0,
apic_base: 0,
a20_gate_enabled: true,
}
}
}
impl PciIsaBridge {
pub fn new(
reset_evt: Box<dyn Fn() + Send + Sync>,
set_a20_signal: Box<dyn FnMut(bool) + Send + Sync>,
) -> Self {
let cfg_space = ConfigSpaceType0Emulator::new(
HardwareIds {
vendor_id: 0x8086,
device_id: 0x7110,
revision_id: 0x03,
prog_if: ProgrammingInterface::NONE,
sub_class: Subclass::BRIDGE_ISA,
base_class: ClassCode::BRIDGE,
type0_sub_vendor_id: 0x1414,
type0_sub_system_id: 0,
},
Vec::new(),
DeviceBars::new(),
)
.with_multi_function_bit(true);
Self {
rt: PciIsaBridgeRuntime {
reset_evt,
set_a20_signal,
},
cfg_space,
state: PciIsaBridgeState::new(),
}
}
fn handle_math_coproc_read(&mut self, max_access_size: usize, data: &mut [u8]) {
if data.len() > max_access_size {
tracelimit::warn_ratelimited!(?max_access_size, len = ?data.len(), "unexpected MATH_COPROC read len");
data.fill(0xff);
return;
}
data.fill(0xff)
}
fn handle_math_coproc_write(&mut self, max_access_size: usize, data: &[u8]) {
if data.len() > max_access_size {
tracelimit::warn_ratelimited!(?max_access_size, len = ?data.len(), "unexpected MATH_COPROC write len");
return;
}
let _ = data;
}
fn handle_fast_a20_read(&mut self, data: &mut [u8]) {
if data.len() != 1 {
tracelimit::warn_ratelimited!(len = ?data.len(), "unexpected FAST_A20_GATE read len");
return;
}
data[0] = if self.state.a20_gate_enabled {
0x00
} else {
0x02
};
}
fn handle_fast_a20_write(&mut self, data: &[u8]) {
if data.len() != 1 {
tracelimit::warn_ratelimited!(len = ?data.len(), "unexpected FAST_A20_GATE write len");
return;
}
let v = data[0];
if v & 0x01 != 0 {
tracing::info!("initiating guest reset via FAST_A20_GATE");
(self.rt.reset_evt)();
return;
}
self.state.a20_gate_enabled = v & 0x02 == 0;
(self.rt.set_a20_signal)(self.state.a20_gate_enabled);
}
}
impl ChangeDeviceState for PciIsaBridge {
fn start(&mut self) {}
async fn stop(&mut self) {}
async fn reset(&mut self) {
self.state = PciIsaBridgeState::new();
self.cfg_space.reset();
}
}
impl ChipsetDevice for PciIsaBridge {
fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
Some(self)
}
fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
Some(self)
}
}
impl PortIoIntercept for PciIsaBridge {
fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
use self::io_ports::*;
match io_port {
FAST_A20_GATE => self.handle_fast_a20_read(data),
MATH_COPROC0 => self.handle_math_coproc_read(2, data),
MATH_COPROC1 => self.handle_math_coproc_read(1, data),
_ => return IoResult::Err(IoError::InvalidRegister),
}
IoResult::Ok
}
fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
use self::io_ports::*;
match io_port {
FAST_A20_GATE => self.handle_fast_a20_write(data),
MATH_COPROC0 => self.handle_math_coproc_write(2, data),
MATH_COPROC1 => self.handle_math_coproc_write(1, data),
_ => return IoResult::Err(IoError::InvalidRegister),
}
IoResult::Ok
}
fn get_static_regions(&mut self) -> &[(&str, std::ops::RangeInclusive<u16>)] {
use self::io_ports::*;
&[
("fast_a20_gate", FAST_A20_GATE..=FAST_A20_GATE),
("math_coproc", MATH_COPROC0..=MATH_COPROC1),
]
}
}
impl PciConfigSpace for PciIsaBridge {
fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult {
*value = match ConfigSpace(offset) {
_ if offset == pci_core::spec::cfg_space::HeaderType00::STATUS_COMMAND.0 => 0x02000007,
_ if offset < 0x40 => return self.cfg_space.read_u32(offset, value),
ConfigSpace::TOP => 0x00000200,
ConfigSpace::IO_REC => 0x0003004D,
ConfigSpace::PIRQ => self.state.pci_irq_routing,
ConfigSpace::SER_IRQ => 0x0000000D0,
ConfigSpace::SMI => self.state.smi_control,
ConfigSpace::SEE => self.state.system_event,
ConfigSpace::FTM => self.state.smi_request,
ConfigSpace::CTL_TMR => self.state.clock_scale,
ConfigSpace::RTC_CONFIG => 0x25000000,
ConfigSpace::MANUF_ID => 0x00000F30,
ConfigSpace::APIC_BASE => self.state.apic_base,
ConfigSpace::DMA_CFG1
| ConfigSpace::DMA_CFG2
| ConfigSpace::IRQ_RT
| ConfigSpace::DMA
| ConfigSpace::PCSC
| ConfigSpace::GEN_CONFIG => 0,
_ => {
tracing::debug!(?offset, "unimplemented config space read");
return IoResult::Err(IoError::InvalidRegister);
}
};
IoResult::Ok
}
fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult {
match ConfigSpace(offset) {
_ if offset < 0x40 => return self.cfg_space.write_u32(offset, value),
ConfigSpace::PIRQ => {
if self.state.pci_irq_routing != value {
tracelimit::info_ratelimited!(new_pci_irq_routing = ?value, "custom PCI IRQ routing is not implemented!");
}
self.state.pci_irq_routing = value;
}
ConfigSpace::SER_IRQ => {
if !(value == 0x0000000D0 || value == 0x000000010) {
tracelimit::warn_ratelimited!(
?value,
"set invalid serial IRQ control register value"
);
}
}
ConfigSpace::TOP => {
if (value & 0x00000200) == 0 {
tracing::debug!("ISA/DMA 512-640K Region Forwarding Enable was cleared!");
}
}
ConfigSpace::SMI => self.state.smi_control = value & 0x00FF001F,
ConfigSpace::SEE => self.state.system_event = value,
ConfigSpace::FTM => self.state.smi_request = value,
ConfigSpace::CTL_TMR => self.state.clock_scale = value,
ConfigSpace::RTC_CONFIG => {
if (value & 0x04000000) == 0 {
tracing::debug!("Trying to disable extended CMOS - not supported")
}
if value != 0x25000000 {
tracelimit::warn_ratelimited!(?value, "unexpected value for RTC_CONFIG write")
}
}
ConfigSpace::APIC_BASE => {
if (value & 0x3F) != (self.state.apic_base & 0x3F) {
tracelimit::error_ratelimited!(
?value,
"changing the IOAPIC base is not implemented!"
);
}
self.state.apic_base = value;
}
ConfigSpace::GEN_CONFIG
| ConfigSpace::DMA_CFG1
| ConfigSpace::DMA_CFG2
| ConfigSpace::IO_REC
| ConfigSpace::IRQ_RT
| ConfigSpace::DMA
| ConfigSpace::PCSC => {
}
_ => {
tracelimit::warn_ratelimited!(?offset, ?value, "unimplemented config space write");
return IoResult::Err(IoError::InvalidRegister);
}
}
IoResult::Ok
}
fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
Some((0, 7, 0)) }
}
open_enum! {
enum ConfigSpace: u16 {
IO_REC = 0x4C,
PIRQ = 0x60,
SER_IRQ = 0x64,
TOP = 0x68,
IRQ_RT = 0x70,
DMA = 0x74,
PCSC = 0x78,
APIC_BASE = 0x80,
DMA_CFG1 = 0x90,
DMA_CFG2 = 0x94,
SMI = 0xA0,
SEE = 0xA4,
FTM = 0xA8,
CTL_TMR = 0xAC,
GEN_CONFIG = 0xB0,
RTC_CONFIG = 0xC8,
MANUF_ID = 0xF8,
}
}
mod save_restore {
use super::*;
use vmcore::save_restore::RestoreError;
use vmcore::save_restore::SaveError;
use vmcore::save_restore::SaveRestore;
mod state {
use mesh::payload::Protobuf;
use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
use vmcore::save_restore::SaveRestore;
use vmcore::save_restore::SavedStateRoot;
#[derive(Protobuf, SavedStateRoot)]
#[mesh(package = "chipset.piix4.pci_isa_bridge")]
pub struct SavedState {
#[mesh(1)]
pub pci_irq_routing: u32,
#[mesh(2)]
pub smi_control: u32,
#[mesh(3)]
pub smi_request: u32,
#[mesh(4)]
pub system_event: u32,
#[mesh(5)]
pub clock_scale: u32,
#[mesh(6)]
pub apic_base: u32,
#[mesh(7)]
pub a20_gate_enabled: bool,
#[mesh(8)]
pub cfg_space: <ConfigSpaceType0Emulator as SaveRestore>::SavedState,
}
}
impl SaveRestore for PciIsaBridge {
type SavedState = state::SavedState;
fn save(&mut self) -> Result<Self::SavedState, SaveError> {
let PciIsaBridgeState {
pci_irq_routing,
smi_control,
smi_request,
system_event,
clock_scale,
apic_base,
a20_gate_enabled,
} = self.state;
let saved_state = state::SavedState {
pci_irq_routing,
smi_control,
smi_request,
system_event,
clock_scale,
apic_base,
a20_gate_enabled,
cfg_space: self.cfg_space.save()?,
};
Ok(saved_state)
}
fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
let state::SavedState {
pci_irq_routing,
smi_control,
smi_request,
system_event,
clock_scale,
apic_base,
a20_gate_enabled,
cfg_space,
} = state;
let state = PciIsaBridgeState {
pci_irq_routing,
smi_control,
smi_request,
system_event,
clock_scale,
apic_base,
a20_gate_enabled,
};
self.state = state;
(self.rt.set_a20_signal)(self.state.a20_gate_enabled);
self.cfg_space.restore(cfg_space)?;
Ok(())
}
}
}