Skip to main content

chipset_legacy/piix4_pci_isa_bridge/
mod.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! PIIX4 - PCI to ISA Bridge
5
6pub mod resolver;
7
8use chipset_device::ChipsetDevice;
9use chipset_device::io::IoError;
10use chipset_device::io::IoResult;
11use chipset_device::pci::ByteEnabledDwordRead;
12use chipset_device::pci::ByteEnabledDwordWrite;
13use chipset_device::pci::PciConfigSpace;
14use chipset_device::pio::PortIoIntercept;
15use inspect::Inspect;
16use inspect::InspectMut;
17use open_enum::open_enum;
18use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
19use pci_core::cfg_space_emu::DeviceBars;
20use pci_core::spec::hwid::ClassCode;
21use pci_core::spec::hwid::HardwareIds;
22use pci_core::spec::hwid::ProgrammingInterface;
23use pci_core::spec::hwid::Subclass;
24use vmcore::device_state::ChangeDeviceState;
25
26/// IO ports as specified by the PIIX4 data sheet
27mod io_ports {
28    pub const FAST_A20_GATE: u16 = 0x92;
29    pub const MATH_COPROC0: u16 = 0xF0;
30    pub const MATH_COPROC1: u16 = 0xF1;
31}
32
33struct PciIsaBridgeRuntime {
34    reset_evt: Box<dyn Fn() + Send + Sync>,
35    set_a20_signal: Box<dyn FnMut(bool) + Send + Sync>,
36}
37
38/// PIIX4 (PCI device function 0) - PCI to ISA Bridge
39///
40/// See section 3.1 in the PIIX4 data sheet.
41#[derive(InspectMut)]
42pub struct PciIsaBridge {
43    // Runtime glue
44    #[inspect(skip)]
45    rt: PciIsaBridgeRuntime,
46
47    // Sub-emulators
48    cfg_space: ConfigSpaceType0Emulator,
49
50    // Volatile state
51    state: PciIsaBridgeState,
52}
53
54#[derive(Inspect)]
55struct PciIsaBridgeState {
56    pci_irq_routing: u32,
57    smi_control: u32,
58    smi_request: u32,
59    system_event: u32,
60    clock_scale: u32,
61    apic_base: u32,
62    a20_gate_enabled: bool,
63}
64
65impl PciIsaBridgeState {
66    fn new() -> Self {
67        Self {
68            // hard-code PCI lane A to IRQ 11, disabling all other PCI IRQ lines
69            pci_irq_routing: 0x80808000 | 11,
70            smi_control: 0x00000008,
71            smi_request: 0x0000000F,
72            system_event: 0,
73            clock_scale: 0,
74            apic_base: 0,
75            a20_gate_enabled: true,
76        }
77    }
78}
79
80impl PciIsaBridge {
81    pub fn new(
82        reset_evt: Box<dyn Fn() + Send + Sync>,
83        set_a20_signal: Box<dyn FnMut(bool) + Send + Sync>,
84    ) -> Self {
85        let cfg_space = ConfigSpaceType0Emulator::new(
86            HardwareIds {
87                vendor_id: 0x8086,
88                device_id: 0x7110,
89                revision_id: 0x03,
90                prog_if: ProgrammingInterface::NONE,
91                sub_class: Subclass::BRIDGE_ISA,
92                base_class: ClassCode::BRIDGE,
93                type0_sub_vendor_id: pci_core::microsoft::VENDOR_ID,
94                type0_sub_system_id: 0,
95            },
96            Vec::new(),
97            Vec::new(),
98            DeviceBars::new(),
99        )
100        .with_multi_function_bit(true);
101
102        Self {
103            rt: PciIsaBridgeRuntime {
104                reset_evt,
105                set_a20_signal,
106            },
107
108            cfg_space,
109            state: PciIsaBridgeState::new(),
110        }
111    }
112
113    fn handle_math_coproc_read(&mut self, max_access_size: usize, data: &mut [u8]) {
114        if data.len() > max_access_size {
115            tracelimit::warn_ratelimited!(?max_access_size, len = ?data.len(), "unexpected MATH_COPROC read len");
116            data.fill(0xff);
117            return;
118        }
119
120        // ..but also, on a valid read, we still just return all Fs
121        data.fill(0xff)
122    }
123
124    fn handle_math_coproc_write(&mut self, max_access_size: usize, data: &[u8]) {
125        if data.len() > max_access_size {
126            tracelimit::warn_ratelimited!(?max_access_size, len = ?data.len(), "unexpected MATH_COPROC write len");
127            return;
128        }
129
130        // the legacy stack would deassert IRQ number 13 here, but AFAIK,
131        // nothing ever actually asserted that IRQ in the first place?
132        let _ = data;
133    }
134
135    fn handle_fast_a20_read(&mut self, data: &mut [u8]) {
136        if data.len() != 1 {
137            tracelimit::warn_ratelimited!(len = ?data.len(), "unexpected FAST_A20_GATE read len");
138            return;
139        }
140
141        // Clear bit 1 if the fast A20 gate is enabled.
142        data[0] = if self.state.a20_gate_enabled {
143            0x00
144        } else {
145            0x02
146        };
147    }
148
149    fn handle_fast_a20_write(&mut self, data: &[u8]) {
150        if data.len() != 1 {
151            tracelimit::warn_ratelimited!(len = ?data.len(), "unexpected FAST_A20_GATE write len");
152            return;
153        }
154
155        let v = data[0];
156
157        if v & 0x01 != 0 {
158            tracing::info!("initiating guest reset via FAST_A20_GATE");
159            (self.rt.reset_evt)();
160            return;
161        }
162
163        self.state.a20_gate_enabled = v & 0x02 == 0;
164        (self.rt.set_a20_signal)(self.state.a20_gate_enabled);
165    }
166}
167
168impl ChangeDeviceState for PciIsaBridge {
169    fn start(&mut self) {}
170
171    async fn stop(&mut self) {}
172
173    async fn reset(&mut self) {
174        // Assume the caller will reset the A20 state to its initial state.
175        self.state = PciIsaBridgeState::new();
176        self.cfg_space.reset();
177    }
178}
179
180impl ChipsetDevice for PciIsaBridge {
181    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
182        Some(self)
183    }
184
185    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
186        Some(self)
187    }
188}
189
190impl PortIoIntercept for PciIsaBridge {
191    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
192        use self::io_ports::*;
193        match io_port {
194            FAST_A20_GATE => self.handle_fast_a20_read(data),
195            MATH_COPROC0 => self.handle_math_coproc_read(2, data),
196            MATH_COPROC1 => self.handle_math_coproc_read(1, data),
197            _ => return IoResult::Err(IoError::InvalidRegister),
198        }
199        IoResult::Ok
200    }
201
202    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
203        use self::io_ports::*;
204        match io_port {
205            FAST_A20_GATE => self.handle_fast_a20_write(data),
206            MATH_COPROC0 => self.handle_math_coproc_write(2, data),
207            MATH_COPROC1 => self.handle_math_coproc_write(1, data),
208            _ => return IoResult::Err(IoError::InvalidRegister),
209        }
210        IoResult::Ok
211    }
212
213    fn get_static_regions(&mut self) -> &[(&str, std::ops::RangeInclusive<u16>)] {
214        use self::io_ports::*;
215
216        &[
217            ("fast_a20_gate", FAST_A20_GATE..=FAST_A20_GATE),
218            ("math_coproc", MATH_COPROC0..=MATH_COPROC1),
219            // NOTE: we don't explicitly claim RESET_CF9 port here, since that
220            // would result in a conflict with the PCI bus's ADDR/DATA IO ports.
221            // ("reset_cf9", RESET_CF9..=RESET_CF9),
222        ]
223    }
224}
225
226impl PciConfigSpace for PciIsaBridge {
227    fn pci_cfg_read(&mut self, offset: u16, mut value: ByteEnabledDwordRead<'_>) -> IoResult {
228        value.set(match ConfigSpace(offset) {
229            // for bug-for-bug compat with the hyper-v implementation: return
230            // hardcoded status register instead of letting the config space
231            // emulator take care of it
232            _ if offset == pci_core::spec::cfg_space::HeaderType00::STATUS_COMMAND.0 => 0x02000007,
233            _ if offset < 0x40 => return self.cfg_space.read_byte_enabled(offset, value),
234            // these magic values mainly consist of default values pulled from
235            // the PIIX4 documentation
236            ConfigSpace::TOP => 0x00000200,
237            ConfigSpace::IO_REC => 0x0003004D,
238            ConfigSpace::PIRQ => self.state.pci_irq_routing,
239            ConfigSpace::SER_IRQ => 0x0000000D0,
240            ConfigSpace::SMI => self.state.smi_control,
241            ConfigSpace::SEE => self.state.system_event,
242            ConfigSpace::FTM => self.state.smi_request,
243            ConfigSpace::CTL_TMR => self.state.clock_scale,
244            ConfigSpace::RTC_CONFIG => 0x25000000,
245            ConfigSpace::MANUF_ID => 0x00000F30,
246            ConfigSpace::APIC_BASE => self.state.apic_base,
247            ConfigSpace::DMA_CFG1
248            | ConfigSpace::DMA_CFG2
249            | ConfigSpace::IRQ_RT
250            | ConfigSpace::DMA
251            | ConfigSpace::PCSC
252            | ConfigSpace::GEN_CONFIG => 0,
253            _ => {
254                tracing::debug!(?offset, "unimplemented config space read");
255                return IoResult::Err(IoError::InvalidRegister);
256            }
257        });
258
259        IoResult::Ok
260    }
261
262    fn pci_cfg_write(&mut self, offset: u16, value: ByteEnabledDwordWrite) -> IoResult {
263        match ConfigSpace(offset) {
264            _ if offset < 0x40 => return self.cfg_space.write_byte_enabled(offset, value),
265            ConfigSpace::PIRQ => {
266                let value = value.merge(self.state.pci_irq_routing);
267                if self.state.pci_irq_routing != value {
268                    tracelimit::info_ratelimited!(new_pci_irq_routing = ?value, "custom PCI IRQ routing is not implemented!");
269                }
270
271                self.state.pci_irq_routing = value;
272            }
273            ConfigSpace::SER_IRQ => {
274                let value = value.extract();
275                if !(value == 0x0000000D0 || value == 0x000000010) {
276                    tracelimit::warn_ratelimited!(
277                        ?value,
278                        "set invalid serial IRQ control register value"
279                    );
280                }
281            }
282            ConfigSpace::TOP => {
283                // Make sure ISA/DMA 512-640K Region Forwarding Enable is never cleared.
284                // This controls whether addresses 512K to 640K are forwarded to RAM
285                // (which we always want to do).
286                let value = value.extract();
287                if (value & 0x00000200) == 0 {
288                    tracing::debug!("ISA/DMA 512-640K Region Forwarding Enable was cleared!");
289                }
290            }
291            ConfigSpace::SMI => {
292                self.state.smi_control = value.merge(self.state.smi_control) & 0x00FF001F;
293            }
294            ConfigSpace::SEE => value.merge_into(&mut self.state.system_event),
295            ConfigSpace::FTM => value.merge_into(&mut self.state.smi_request),
296            ConfigSpace::CTL_TMR => value.merge_into(&mut self.state.clock_scale),
297            ConfigSpace::RTC_CONFIG => {
298                // For now, the code assumes the default value. We don't support
299                // disabling extended CMOS (upper 128 bytes).
300                let value = value.extract();
301                if (value & 0x04000000) == 0 {
302                    tracing::debug!("Trying to disable extended CMOS - not supported")
303                }
304
305                if value != 0x25000000 {
306                    tracelimit::warn_ratelimited!(?value, "unexpected value for RTC_CONFIG write")
307                }
308            }
309            ConfigSpace::APIC_BASE => {
310                // If any of bits 0..5 have changed, then we need to change the base of
311                // the IoApic.
312                let value = value.merge(self.state.apic_base);
313                if (value & 0x3F) != (self.state.apic_base & 0x3F) {
314                    // DEVNOTE: this shouldn't actually be all that difficult to
315                    // implement, but until there is definitive proof that there
316                    // is a supported guest OS that makes use of this bit of
317                    // functionality, its best to just keep things simple and
318                    // cross-deps minimal.
319                    tracelimit::error_ratelimited!(
320                        ?value,
321                        "changing the IOAPIC base is not implemented!"
322                    );
323                }
324
325                self.state.apic_base = value;
326            }
327            ConfigSpace::GEN_CONFIG
328            | ConfigSpace::DMA_CFG1
329            | ConfigSpace::DMA_CFG2
330            | ConfigSpace::IO_REC
331            | ConfigSpace::IRQ_RT
332            | ConfigSpace::DMA
333            | ConfigSpace::PCSC => {
334                // always ignored
335            }
336            _ => {
337                tracelimit::warn_ratelimited!(?offset, ?value, "unimplemented config space write");
338                return IoResult::Err(IoError::InvalidRegister);
339            }
340        }
341
342        IoResult::Ok
343    }
344
345    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
346        Some(chipset_resources::piix4_pci_isa_bridge::PIIX4_PCI_ISA_BRIDGE_BDF)
347    }
348}
349
350open_enum! {
351    enum ConfigSpace: u16 {
352        IO_REC     = 0x4C,
353        PIRQ       = 0x60,
354        SER_IRQ    = 0x64,
355        TOP        = 0x68,
356        IRQ_RT     = 0x70,
357        DMA        = 0x74,
358        PCSC       = 0x78,
359        APIC_BASE  = 0x80,
360        DMA_CFG1   = 0x90,
361        DMA_CFG2   = 0x94,
362        SMI        = 0xA0,
363        SEE        = 0xA4,
364        FTM        = 0xA8,
365        CTL_TMR    = 0xAC,
366        GEN_CONFIG = 0xB0,
367        RTC_CONFIG = 0xC8,
368        MANUF_ID   = 0xF8,
369    }
370}
371
372mod save_restore {
373    use super::*;
374    use vmcore::save_restore::RestoreError;
375    use vmcore::save_restore::SaveError;
376    use vmcore::save_restore::SaveRestore;
377
378    mod state {
379        use mesh::payload::Protobuf;
380        use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
381        use vmcore::save_restore::SaveRestore;
382        use vmcore::save_restore::SavedStateRoot;
383
384        #[derive(Protobuf, SavedStateRoot)]
385        #[mesh(package = "chipset.piix4.pci_isa_bridge")]
386        pub struct SavedState {
387            #[mesh(1)]
388            pub pci_irq_routing: u32,
389            #[mesh(2)]
390            pub smi_control: u32,
391            #[mesh(3)]
392            pub smi_request: u32,
393            #[mesh(4)]
394            pub system_event: u32,
395            #[mesh(5)]
396            pub clock_scale: u32,
397            #[mesh(6)]
398            pub apic_base: u32,
399            #[mesh(7)]
400            pub a20_gate_enabled: bool,
401            #[mesh(8)]
402            pub cfg_space: <ConfigSpaceType0Emulator as SaveRestore>::SavedState,
403        }
404    }
405
406    impl SaveRestore for PciIsaBridge {
407        type SavedState = state::SavedState;
408
409        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
410            let PciIsaBridgeState {
411                pci_irq_routing,
412                smi_control,
413                smi_request,
414                system_event,
415                clock_scale,
416                apic_base,
417                a20_gate_enabled,
418            } = self.state;
419
420            let saved_state = state::SavedState {
421                pci_irq_routing,
422                smi_control,
423                smi_request,
424                system_event,
425                clock_scale,
426                apic_base,
427                a20_gate_enabled,
428                cfg_space: self.cfg_space.save()?,
429            };
430
431            Ok(saved_state)
432        }
433
434        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
435            let state::SavedState {
436                pci_irq_routing,
437                smi_control,
438                smi_request,
439                system_event,
440                clock_scale,
441                apic_base,
442                a20_gate_enabled,
443                cfg_space,
444            } = state;
445
446            let state = PciIsaBridgeState {
447                pci_irq_routing,
448                smi_control,
449                smi_request,
450                system_event,
451                clock_scale,
452                apic_base,
453                a20_gate_enabled,
454            };
455
456            self.state = state;
457
458            // sync a20 signal
459            (self.rt.set_a20_signal)(self.state.a20_gate_enabled);
460
461            self.cfg_space.restore(cfg_space)?;
462
463            Ok(())
464        }
465    }
466}