use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::sync::{Arc, Weak};
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{Device, DeviceClass, RealizeCtx, ResetKind, SinkPin};
use crate::core::error::{BusError, Error, Result};
use crate::core::props::Props;
use crate::core::space::{AccessConstraints, MemAttrs, MemOps, MemResult, Region, RegionRef};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{LockRank, Mutex};
use crate::core::value::{Endian, Width};
use crate::core::wire::{
FanIn, IntAck, IntAckCycle, IntAckHandlers, IntAckResponse, Level, Resolve, WireId, WireSink,
WireSource,
};
use crate::machine::realize::Instance;
use crate::machine::validate::ClassSchema;
pub const CLASS_NAME: &str = "pc.imcr";
const STATE_VERSION: u32 = 1;
pub const REGISTER_WINDOW_LEN: u64 = 2;
pub const IMCR_INDEX: u8 = 0x70;
const IMCR_VIA_APIC: u8 = 0x01;
const LINE_INT: u32 = 0;
#[derive(Debug, Clone, Copy, Default)]
struct State {
index: u8,
imcr: u8,
asserted: bool,
}
struct Registers {
state: Mutex<State>,
intr: Mutex<Option<WireSource>>,
lint0: Mutex<Option<WireSource>>,
upstream: IntAckHandlers,
}
impl fmt::Debug for Registers {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = f.debug_struct("Registers");
match self.state.try_lock() {
Some(state) => s.field("state", &*state),
None => s.field("state", &"<in use>"),
};
s.field("upstream", &self.upstream.len()).finish()
}
}
fn drive(holder: &Mutex<Option<WireSource>>, level: Level) {
let out = holder.lock().clone();
if let Some(out) = out {
out.set(level);
}
}
impl Registers {
fn new() -> Registers {
Registers {
state: Mutex::with_rank(LockRank::DEVICE, State::default()),
intr: Mutex::with_rank(LockRank::LEAF, None),
lint0: Mutex::with_rank(LockRank::LEAF, None),
upstream: IntAckHandlers::new(),
}
}
fn via_apic(&self) -> bool {
self.state.lock().imcr & IMCR_VIA_APIC != 0
}
fn drive_outputs(&self) {
let (intr, lint0) = {
let s = self.state.lock();
let via_apic = s.imcr & IMCR_VIA_APIC != 0;
(
Level::from_bool(s.asserted && !via_apic),
Level::from_bool(s.asserted && via_apic),
)
};
drive(&self.intr, intr);
drive(&self.lint0, lint0);
}
fn input_level(&self, level: Level) {
{
let mut s = self.state.lock();
let asserted = level == Level::High;
if s.asserted == asserted {
return;
}
s.asserted = asserted;
}
self.drive_outputs();
}
fn read(&self, offset: u64) -> u8 {
let s = self.state.lock();
match offset {
0 => s.index,
_ if s.index == IMCR_INDEX => s.imcr,
_ => 0xff,
}
}
fn write(&self, offset: u64, value: u8) -> bool {
let mut s = self.state.lock();
if offset == 0 {
s.index = value;
return false;
}
if s.index != IMCR_INDEX {
return false;
}
let imcr = value & IMCR_VIA_APIC;
let moved = imcr != s.imcr;
s.imcr = imcr;
moved
}
}
#[derive(Debug)]
struct AckPath {
regs: Arc<Registers>,
when_via_apic: bool,
}
impl IntAck for AckPath {
fn acknowledge(&self, cycle: IntAckCycle) -> IntAckResponse {
if self.regs.via_apic() != self.when_via_apic {
return IntAckResponse::Declined;
}
self.regs.upstream.run(cycle)
}
}
#[derive(Debug)]
struct Ports(Arc<Registers>);
impl MemOps for Ports {
fn read(&self, offset: u64, dst: &mut [u8], _attrs: MemAttrs) -> MemResult {
let [byte] = dst else {
return Err(BusError::BadAccess);
};
*byte = self.0.read(offset);
Ok(())
}
fn write(&self, offset: u64, src: &[u8], attrs: MemAttrs) -> MemResult {
let [value] = src else {
return Err(BusError::BadAccess);
};
if attrs.debug {
return Err(BusError::BadAccess);
}
if self.0.write(offset, *value) {
self.0.drive_outputs();
}
Ok(())
}
fn constraints(&self) -> AccessConstraints {
AccessConstraints::word(Width::U8, Endian::Little)
}
}
#[derive(Debug)]
struct InputPin {
regs: Arc<Registers>,
inputs: FanIn,
}
impl WireSink for InputPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
self.regs.input_level(self.inputs.resolve(Resolve::Or));
}
}
#[derive(Debug)]
pub struct Imcr {
regs: Arc<Registers>,
ports: RegionRef,
intr_ack: Arc<AckPath>,
lint0_ack: Arc<AckPath>,
pins: Mutex<Vec<Arc<InputPin>>>,
}
impl Imcr {
pub fn new(props: &Props) -> Result<Imcr> {
props.reader().finish()?;
Ok(Imcr::build())
}
fn build() -> Imcr {
let regs = Arc::new(Registers::new());
let ports: RegionRef = Arc::new(Region::io(
"pc.imcr.regs",
REGISTER_WINDOW_LEN,
Arc::new(Ports(Arc::clone(®s))) as Arc<dyn MemOps>,
));
Imcr {
intr_ack: Arc::new(AckPath {
regs: Arc::clone(®s),
when_via_apic: false,
}),
lint0_ack: Arc::new(AckPath {
regs: Arc::clone(®s),
when_via_apic: true,
}),
regs,
ports,
pins: Mutex::with_rank(LockRank::LEAF, Vec::new()),
}
}
#[must_use]
pub fn via_apic(&self) -> bool {
self.regs.via_apic()
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: CLASS_NAME,
version: STATE_VERSION,
summary: "the MP specification's interrupt mode configuration register, at 0x22/0x23",
properties: &[],
construct: |props| Ok(Box::new(Imcr::new(props)?)),
};
fn unknown_pin(port: &str) -> Error {
Error::Config {
at: port.to_string(),
message: String::from("the IMCR takes `int` in, and drives `intr` and `lint0` out"),
}
}
impl Device for Imcr {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, _ctx: &mut RealizeCtx<'_>) -> Result<()> {
Ok(())
}
fn reset(&self, _kind: ResetKind) {
{
let mut s = self.regs.state.lock();
*s = State {
asserted: s.asserted,
..State::default()
};
}
self.regs.drive_outputs();
}
fn region(&self, name: &str) -> Option<RegionRef> {
match name {
"" | "regs" => Some(Arc::clone(&self.ports)),
_ => None,
}
}
fn sink(&self, port: &str, sources: &[WireId]) -> Option<SinkPin> {
if port != "int" {
return None;
}
let pin = Arc::new(InputPin {
regs: Arc::clone(&self.regs),
inputs: FanIn::new(sources),
});
self.pins.lock().push(Arc::clone(&pin));
Some(SinkPin {
sink: pin,
line: LINE_INT,
})
}
fn connect(&self, port: &str, source: WireSource) -> Result<()> {
let pin = match port {
"intr" => &self.regs.intr,
"lint0" => &self.regs.lint0,
_ => return Err(unknown_pin(port)),
};
*pin.lock() = Some(source);
Ok(())
}
fn announce(&self, port: &str) {
if port == "intr" || port == "lint0" {
self.regs.drive_outputs();
}
}
fn int_ack(&self, port: &str) -> Option<Arc<dyn IntAck>> {
match port {
"intr" => Some(Arc::clone(&self.intr_ack) as Arc<dyn IntAck>),
"lint0" => Some(Arc::clone(&self.lint0_ack) as Arc<dyn IntAck>),
_ => None,
}
}
fn attach_int_ack(&self, port: &str, ack: Weak<dyn IntAck>) {
if port == "int" {
self.regs.upstream.attach(ack);
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let s = *self.regs.state.lock();
w.write_u8(s.index)?;
w.write_u8(s.imcr)?;
w.write_bool(s.asserted)
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let state = State {
index: r.read_u8()?,
imcr: r.read_u8()? & IMCR_VIA_APIC,
asserted: r.read_bool()?,
};
*self.regs.state.lock() = state;
self.regs.drive_outputs();
Ok(())
}
}
impl Instance for Imcr {}
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(Imcr::new(props)?)))
}
#[must_use]
pub fn schema() -> ClassSchema {
use crate::machine::validate::PortDir;
ClassSchema::new(CLASS_NAME)
.region("")
.region("regs")
.port("int", PortDir::In)
.port("intr", PortDir::Out)
.port("lint0", PortDir::Out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::state::{MachineShape, Migrations, StateReader, StateWriter};
use crate::core::wire::{Wire, WireIdAllocator};
use core::sync::atomic::{AtomicU32, Ordering};
#[derive(Debug, Default)]
struct Probe {
level: AtomicU32,
}
impl WireSink for Probe {
fn set_level(&self, _src: WireId, _line: u32, level: Level) {
self.level
.store(u32::from(level.is_high()), Ordering::Relaxed);
}
}
impl Probe {
fn high(&self) -> bool {
self.level.load(Ordering::Relaxed) == 1
}
}
#[derive(Debug)]
struct Stub8259(u32);
impl IntAck for Stub8259 {
fn acknowledge(&self, _cycle: IntAckCycle) -> IntAckResponse {
IntAckResponse::Vector(self.0)
}
}
struct Wired {
imcr: Imcr,
intr: Arc<Probe>,
lint0: Arc<Probe>,
pin: Arc<dyn WireSink>,
src: WireId,
}
fn wired() -> Wired {
let imcr = Imcr::build();
let ids = WireIdAllocator::new();
let mut probes = Vec::new();
for port in ["intr", "lint0"] {
let id = ids.alloc();
let probe = Arc::new(Probe::default());
let wire = Wire::builder()
.source(id)
.sink(Arc::clone(&probe) as Arc<dyn WireSink>, 0)
.build_shared();
Device::connect(&imcr, port, WireSource::new(wire, id)).expect("both outputs exist");
Device::announce(&imcr, port);
probes.push(probe);
}
let src = ids.alloc();
let pin = Device::sink(&imcr, "int", &[src]).expect("the input pin exists");
let lint0 = probes.pop().expect("two");
let intr = probes.pop().expect("two");
Wired {
imcr,
intr,
lint0,
pin: pin.sink,
src,
}
}
impl Wired {
fn drive_int(&self, level: Level) {
self.pin.set_level(self.src, LINE_INT, level);
}
}
fn read(imcr: &Imcr, offset: u64) -> u8 {
let mut byte = [0u8; 1];
Ports(Arc::clone(&imcr.regs))
.read(offset, &mut byte, MemAttrs::DEFAULT)
.expect("a byte read is legal");
byte[0]
}
fn write(imcr: &Imcr, offset: u64, value: u8) {
Ports(Arc::clone(&imcr.regs))
.write(offset, &[value], MemAttrs::DEFAULT)
.expect("a byte write is legal");
}
fn set_imcr(imcr: &Imcr, value: u8) {
write(imcr, 0, IMCR_INDEX);
write(imcr, 1, value);
}
#[test]
fn the_power_on_default_is_pic_mode() {
let w = wired();
assert!(!w.imcr.via_apic(), "MP spec 3.6.2.1: the default is zero");
w.drive_int(Level::High);
assert!(w.intr.high(), "the 8259A reaches the processor directly");
assert!(!w.lint0.high(), "and the APIC is bypassed");
}
#[test]
fn writing_one_moves_the_line_to_the_apic_and_writing_zero_moves_it_back() {
let w = wired();
w.drive_int(Level::High);
set_imcr(&w.imcr, 0x01);
assert!(w.imcr.via_apic());
assert!(!w.intr.high(), "the direct path is released");
assert!(w.lint0.high(), "and LINT0 picks the pending interrupt up");
set_imcr(&w.imcr, 0x00);
assert!(w.intr.high(), "and back, with the interrupt still pending");
assert!(!w.lint0.high());
}
#[test]
fn the_register_answers_only_when_it_is_selected() {
let w = wired();
set_imcr(&w.imcr, 0x01);
write(&w.imcr, 0, IMCR_INDEX);
assert_eq!(read(&w.imcr, 1), 0x01);
assert_eq!(read(&w.imcr, 0), IMCR_INDEX, "the index reads back");
write(&w.imcr, 0, 0x71);
assert_eq!(read(&w.imcr, 1), 0xff);
write(&w.imcr, 1, 0x00);
assert!(
w.imcr.via_apic(),
"a write to an unselected register moved the mode"
);
}
#[test]
fn only_bit_zero_of_the_register_exists() {
let w = wired();
set_imcr(&w.imcr, 0xfe);
assert!(
!w.imcr.via_apic(),
"bit 0 clear is PIC mode whatever else is set"
);
write(&w.imcr, 0, IMCR_INDEX);
assert_eq!(read(&w.imcr, 1), 0x00, "and the rest did not latch");
}
#[test]
fn the_acknowledge_reaches_the_8259a_through_whichever_output_is_selected() {
let w = wired();
let pic: Arc<dyn IntAck> = Arc::new(Stub8259(0x08));
Device::attach_int_ack(&w.imcr, "int", Arc::downgrade(&pic));
let intr = Device::int_ack(&w.imcr, "intr").expect("the direct path vectors");
let lint0 = Device::int_ack(&w.imcr, "lint0").expect("the APIC path vectors");
assert_eq!(
intr.acknowledge(IntAckCycle::vector_only()),
IntAckResponse::Vector(0x08)
);
assert_eq!(
lint0.acknowledge(IntAckCycle::vector_only()),
IntAckResponse::Declined
);
set_imcr(&w.imcr, 0x01);
assert_eq!(
intr.acknowledge(IntAckCycle::vector_only()),
IntAckResponse::Declined,
"in APIC mode this device is not driving the processor's pin"
);
assert_eq!(
lint0.acknowledge(IntAckCycle::vector_only()),
IntAckResponse::Vector(0x08)
);
assert!(
Device::int_ack(&w.imcr, "int").is_none(),
"an input pin offers nothing"
);
}
#[test]
fn a_debug_write_cannot_move_the_interrupt_path() {
let w = wired();
let ops = Ports(Arc::clone(&w.imcr.regs));
assert!(ops.write(0, &[IMCR_INDEX], MemAttrs::DEBUG).is_err());
assert!(ops.write(1, &[0x01], MemAttrs::DEBUG).is_err());
assert!(!w.imcr.via_apic(), "and nothing moved");
let mut byte = [0u8; 1];
ops.read(0, &mut byte, MemAttrs::DEBUG).expect("legal");
assert_eq!(byte[0], 0x00, "nothing has been selected");
}
#[test]
fn a_reset_returns_to_pic_mode_without_forgetting_the_pending_interrupt() {
let w = wired();
set_imcr(&w.imcr, 0x01);
w.drive_int(Level::High);
assert!(w.lint0.high());
Device::reset(&w.imcr, ResetKind::Warm);
assert!(
!w.imcr.via_apic(),
"a warm reset is a power-on for this latch"
);
assert!(
w.intr.high(),
"and the pending interrupt found the new path"
);
assert!(!w.lint0.high());
}
#[test]
fn an_access_that_is_not_a_single_byte_is_refused() {
let w = wired();
let ops = Ports(Arc::clone(&w.imcr.regs));
let mut two = [0u8; 2];
assert!(ops.read(0, &mut two, MemAttrs::DEFAULT).is_err());
assert!(ops.write(0, &[0, 0], MemAttrs::DEFAULT).is_err());
}
#[test]
fn a_pin_this_device_does_not_drive_is_a_configuration_error() {
let imcr = Imcr::build();
let id = WireIdAllocator::new().alloc();
let wire = Wire::builder().source(id).build_shared();
assert!(Device::connect(&imcr, "nmi", WireSource::new(wire, id)).is_err());
assert!(Device::sink(&imcr, "nmi", &[]).is_none());
assert!(Device::region(&imcr, "nmi").is_none());
assert!(Device::region(&imcr, "regs").is_some());
}
#[test]
fn a_snapshot_round_trips_every_bit_of_architectural_state() {
let w = wired();
write(&w.imcr, 0, 0x71);
set_imcr(&w.imcr, 0x01);
w.drive_int(Level::High);
let image = |dev: &Imcr| {
let mut shape = MachineShape::new();
shape.add_device("imcr", CLASS.name).unwrap();
let mut out = StateWriter::new(shape);
{
let mut chunk = out.chunk("imcr", CLASS.name, CLASS.version).unwrap();
Device::save(dev, &mut chunk).unwrap();
}
out.to_vec().unwrap()
};
let first = image(&w.imcr);
let r = wired();
let reader = StateReader::new(&first).unwrap();
let chunk = reader
.load("imcr", CLASS.name, CLASS.version, &Migrations::new())
.unwrap();
Device::load(&r.imcr, &mut chunk.reader()).unwrap();
assert_eq!(image(&r.imcr), first, "the two images are identical");
assert!(r.imcr.via_apic(), "the mode came back");
assert!(r.lint0.high(), "and so did the interrupt it was routing");
assert!(!r.intr.high());
}
#[test]
fn properties_are_checked_rather_than_ignored() {
assert!(Imcr::new(&Props::new()).is_ok());
assert!(Imcr::new(&Props::new().with("mode", "apic")).is_err());
}
}