chipset_legacy/
piix4_pci_isa_bridge.rs

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