chipset_legacy/
i440bx_host_pci_bridge.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! 440BX Host to PCI Bridge
5
6pub use pam::GpaState;
7
8use chipset_device::ChipsetDevice;
9use chipset_device::io::IoError;
10use chipset_device::io::IoResult;
11use chipset_device::pci::PciConfigSpace;
12use inspect::Inspect;
13use inspect::InspectMut;
14use memory_range::MemoryRange;
15use open_enum::open_enum;
16use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
17use pci_core::cfg_space_emu::DeviceBars;
18use pci_core::spec::hwid::ClassCode;
19use pci_core::spec::hwid::HardwareIds;
20use pci_core::spec::hwid::ProgrammingInterface;
21use pci_core::spec::hwid::Subclass;
22use vmcore::device_state::ChangeDeviceState;
23
24/// A trait to create GPA alias ranges.
25pub trait AdjustGpaRange: Send {
26    /// Adjusts a memory range's mapping state.
27    ///
28    /// This will only be called for memory ranges supported by the i440BX PAM
29    /// registers, or for VGA memory.
30    fn adjust_gpa_range(&mut self, range: MemoryRange, state: GpaState);
31}
32
33struct HostPciBridgeRuntime {
34    adjust_gpa_range: Box<dyn AdjustGpaRange>,
35}
36
37/// 440BX Host to PCI Bridge
38///
39/// See section 3.3 in the 440BX data sheet.
40#[derive(InspectMut)]
41pub struct HostPciBridge {
42    // Runtime glue
43    #[inspect(skip)]
44    rt: HostPciBridgeRuntime,
45
46    // Sub-emulators
47    cfg_space: ConfigSpaceType0Emulator,
48
49    // Volatile state
50    state: HostPciBridgeState,
51}
52
53#[derive(Debug, Inspect)]
54struct HostPciBridgeState {
55    host_pci_dram1: u32,
56    host_pci_dram2: u32,
57    pam_reg1: u32,
58    pam_reg2: u32,
59    bios_scratch1: u32,
60    bios_scratch2: u32,
61    smm_config_word: u16,
62}
63
64// All unmapped.
65const INITIAL_PAM_REG1: u32 = 0x00000003;
66const INITIAL_PAM_REG2: u32 = 0;
67
68impl HostPciBridgeState {
69    fn new() -> Self {
70        Self {
71            // magic numbers lifted straight from Hyper-V source code
72            host_pci_dram1: 0x02020202,
73            host_pci_dram2: 0x00000002,
74            pam_reg1: INITIAL_PAM_REG1,
75            pam_reg2: INITIAL_PAM_REG2,
76            bios_scratch1: 0,
77            bios_scratch2: 0,
78            smm_config_word: 0x3802,
79        }
80    }
81}
82
83impl HostPciBridge {
84    pub fn new(adjust_gpa_range: Box<dyn AdjustGpaRange>, is_restoring: bool) -> Self {
85        let cfg_space = ConfigSpaceType0Emulator::new(
86            HardwareIds {
87                vendor_id: 0x8086,
88                device_id: 0x7192,
89                revision_id: 0x03,
90                prog_if: ProgrammingInterface::NONE,
91                sub_class: Subclass::BRIDGE_HOST,
92                base_class: ClassCode::BRIDGE,
93                type0_sub_vendor_id: 0,
94                type0_sub_system_id: 0,
95            },
96            Vec::new(),
97            DeviceBars::new(),
98        );
99
100        let mut dev = Self {
101            rt: HostPciBridgeRuntime { adjust_gpa_range },
102
103            cfg_space,
104
105            state: HostPciBridgeState::new(),
106        };
107
108        if !is_restoring {
109            // Hard code VGA decoding to on. We don't support the register used to
110            // control this, and the BIOS doesn't try to set it.
111            dev.rt
112                .adjust_gpa_range
113                .adjust_gpa_range(MemoryRange::new(0xa0000..0xc0000), GpaState::Mmio);
114
115            dev.adjust_bios_override_ranges(dev.state.pam_reg1, dev.state.pam_reg2, true);
116        }
117
118        dev
119    }
120}
121
122impl HostPciBridge {
123    // This routine is called when the PAM (physical address management) PCI
124    // configuration registers are modified.
125    //
126    // It gives us a chance to adjust the physical mappings for the addresses
127    // corresponding to the system BIOS (E0000-FFFFF).
128    fn adjust_bios_override_ranges(&mut self, new_reg1: u32, new_reg2: u32, force: bool) {
129        tracing::trace!(?self.state.pam_reg1, ?self.state.pam_reg2, new_reg1, new_reg2, "updating PAM registers");
130
131        let old = pam::parse_pam_registers(self.state.pam_reg1, self.state.pam_reg2);
132        let new = pam::parse_pam_registers(new_reg1, new_reg2);
133
134        for ((range, old_state), (_, new_state)) in old.zip(new) {
135            if old_state != new_state || force {
136                self.rt.adjust_gpa_range.adjust_gpa_range(range, new_state);
137            }
138        }
139
140        self.state.pam_reg1 = new_reg1;
141        self.state.pam_reg2 = new_reg2;
142    }
143}
144
145impl ChangeDeviceState for HostPciBridge {
146    fn start(&mut self) {}
147
148    async fn stop(&mut self) {}
149
150    async fn reset(&mut self) {
151        self.cfg_space.reset();
152        self.state = HostPciBridgeState::new();
153
154        self.adjust_bios_override_ranges(INITIAL_PAM_REG1, INITIAL_PAM_REG2, true);
155    }
156}
157
158impl ChipsetDevice for HostPciBridge {
159    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
160        Some(self)
161    }
162}
163
164impl PciConfigSpace for HostPciBridge {
165    fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult {
166        *value = match ConfigSpace(offset) {
167            // for bug-for-bug compat with the hyper-v implementation: return
168            // hardcoded status register instead of letting the config space
169            // emulator take care of it
170            _ if offset == pci_core::spec::cfg_space::HeaderType00::STATUS_COMMAND.0 => 0x02000006,
171            _ if offset < 0x40 => return self.cfg_space.read_u32(offset, value),
172            ConfigSpace::PAM1 => self.state.pam_reg1,
173            ConfigSpace::PAM2 => self.state.pam_reg2,
174            ConfigSpace::DRAM1 => self.state.host_pci_dram1,
175            ConfigSpace::DRAM2 => self.state.host_pci_dram2,
176            // Specify the default value: No AGP, fast CPU startup
177            ConfigSpace::PAGING_POLICY => 0x380A0000,
178            ConfigSpace::BIOS_SCRATCH1 => self.state.bios_scratch1,
179            ConfigSpace::BIOS_SCRATCH2 => self.state.bios_scratch2,
180            ConfigSpace::SYS_MNG => {
181                // Bits 7 and 2, 0 are always clear.
182                // Bit 13-12, 1 are always set.
183                (self.state.smm_config_word as u32 & 0xC77C | 0x3802) << 16
184            }
185            ConfigSpace::MANUFACTURER_ID => 0x00000F20,
186            ConfigSpace::BUFFER_CONTROL
187            | ConfigSpace::SDRAM_CONTROL
188            | ConfigSpace::CACHE
189            | ConfigSpace::DRAM_C
190            | ConfigSpace::DRAM_RT1
191            | ConfigSpace::UNKNOWN_F4 => 0, // Hyper-V always returns 0, so do we.
192            _ => {
193                tracing::debug!(?offset, "unimplemented config space read");
194                return IoResult::Err(IoError::InvalidRegister);
195            }
196        };
197
198        IoResult::Ok
199    }
200
201    fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult {
202        match ConfigSpace(offset) {
203            _ if offset < 0x40 => return self.cfg_space.write_u32(offset, value),
204            ConfigSpace::DRAM1 => self.state.host_pci_dram1 = value,
205            ConfigSpace::DRAM2 => self.state.host_pci_dram2 = value,
206            ConfigSpace::PAM1 => {
207                self.adjust_bios_override_ranges(value, self.state.pam_reg2, false);
208            }
209            ConfigSpace::PAM2 => {
210                self.adjust_bios_override_ranges(self.state.pam_reg1, value, false);
211            }
212            ConfigSpace::BIOS_SCRATCH1 => self.state.bios_scratch1 = value,
213            ConfigSpace::BIOS_SCRATCH2 => self.state.bios_scratch2 = value,
214            ConfigSpace::SYS_MNG => {
215                // Configuration registers 70-71 are reserved. Only 72-73 (the top 16
216                // bits of this four-byte range) are defined. We'll therefore shift
217                // off the bottom portion.
218                let mut new_smm_word = (value >> 16) as u16;
219
220                // If the register is "locked" (i.e. bit 4 has been set), then
221                // all of the other bits become read-only.
222                if self.state.smm_config_word & 0x10 == 0 {
223                    // Make sure they aren't enabling features we don't currently support.
224                    if new_smm_word & 0x8700 != 0 {
225                        tracelimit::warn_ratelimited!(bits = ?new_smm_word & !0x8700, "guest set unsupported feature bits");
226                    }
227
228                    new_smm_word &= !0x8700;
229                    // Bits 7 and 2, 0 are always clear.
230                    new_smm_word &= 0xC77C;
231                    // Bit 13-12, 1 are always set.
232                    new_smm_word |= 0x3802;
233                    // We never set bit 14 that indicates that SMM memory was accessed
234                    // by the CPU when not in SMM mode.
235                    new_smm_word &= !0x4000;
236
237                    // Make sure no one is trying to enable SMM RAM.
238                    if new_smm_word & 0x0040 != 0 {
239                        tracelimit::warn_ratelimited!("guest attempted to enable SMM RAM");
240                    }
241                    new_smm_word &= !0x0040;
242
243                    self.state.smm_config_word = new_smm_word;
244                }
245            }
246            ConfigSpace::BUFFER_CONTROL
247            | ConfigSpace::SDRAM_CONTROL
248            | ConfigSpace::CACHE
249            | ConfigSpace::DRAM_C
250            | ConfigSpace::DRAM_RT1
251            | ConfigSpace::PAGING_POLICY
252            | ConfigSpace::UNKNOWN_F4 => {} // Hyper-V ignores these, so do we.
253            _ => {
254                tracing::debug!(?offset, ?value, "unimplemented config space write");
255                return IoResult::Err(IoError::InvalidRegister);
256            }
257        }
258
259        IoResult::Ok
260    }
261
262    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
263        Some((0, 0, 0)) // as per i440bx spec
264    }
265}
266
267open_enum! {
268    enum ConfigSpace: u16 {
269        CACHE           = 0x50,
270        DRAM_C          = 0x54,
271        TIMING          = 0x58,
272        PAM1            = 0x58,
273        PAM2            = 0x5C,
274        DRAM1           = 0x60,
275        DRAM2           = 0x64,
276        DRAM_RT1        = 0x68,
277        DRAM_RT2        = 0x6C,
278        SYS_MNG         = 0x70,
279        SDRAM_CONTROL   = 0x74,
280        PAGING_POLICY   = 0x78,
281        SUSPEND_CBR     = 0x7C, // Register spans 7B-7C
282        MEM_BUFF_FREQ   = 0xCC,
283        BIOS_SCRATCH1   = 0xD0,
284        BIOS_SCRATCH2   = 0xD4,
285        BUFFER_CONTROL  = 0xF0,
286        UNKNOWN_F4      = 0xF4,
287        MANUFACTURER_ID = 0xF8,
288    }
289}
290
291mod pam {
292    use memory_range::MemoryRange;
293
294    #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
295    pub enum GpaState {
296        /// Reads and writes go to RAM.
297        #[default]
298        Writable,
299        /// Reads go to RAM, writes go to MMIO.
300        WriteProtected,
301        /// Reads go to ROM, writes go to RAM.
302        WriteOnly,
303        /// Reads and writes go to MMIO.
304        Mmio,
305    }
306
307    pub const PAM_RANGES: &[MemoryRange; 13] = &[
308        MemoryRange::new(0xf0000..0x100000),
309        MemoryRange::new(0xc0000..0xc4000),
310        MemoryRange::new(0xc4000..0xc8000),
311        MemoryRange::new(0xc8000..0xcc000),
312        MemoryRange::new(0xcc000..0xd0000),
313        MemoryRange::new(0xd0000..0xd4000),
314        MemoryRange::new(0xd4000..0xd8000),
315        MemoryRange::new(0xd8000..0xdc000),
316        MemoryRange::new(0xdc000..0xe0000),
317        MemoryRange::new(0xe0000..0xe4000),
318        MemoryRange::new(0xe4000..0xe8000),
319        MemoryRange::new(0xe8000..0xec000),
320        MemoryRange::new(0xec000..0xf0000),
321    ];
322
323    pub fn parse_pam_registers(
324        reg1: u32,
325        reg2: u32,
326    ) -> impl Iterator<Item = (MemoryRange, GpaState)> {
327        // Grab the two PAM (physical address management) registers which
328        // consist of 16 four-bit fields. We never look at the first two bits
329        // of these fields. The second two bits encode the following:
330        //    xx00    => Rom only mapping (shadow RAM is inaccessible)
331        //    xx01    => Read-only RAM (writes go to Rom and are ignored)
332        //    xx10    => Write-only RAM (reads come from Rom - not supported by us)
333        //    xx11    => RAM-only (Rom is inaccessible)
334        let reg = ((reg2 as u64) << 32) | reg1 as u64;
335        PAM_RANGES.iter().enumerate().map(move |(i, range)| {
336            let state = match (reg >> ((i + 3) * 4)) & 3 {
337                0b00 => GpaState::Mmio,
338                0b01 => GpaState::WriteProtected,
339                0b10 => GpaState::WriteOnly,
340                0b11 => GpaState::Writable,
341                _ => unreachable!(),
342            };
343            (*range, state)
344        })
345    }
346}
347
348mod save_restore {
349    use super::*;
350    use vmcore::save_restore::RestoreError;
351    use vmcore::save_restore::SaveError;
352    use vmcore::save_restore::SaveRestore;
353
354    mod state {
355        use mesh::payload::Protobuf;
356        use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
357        use vmcore::save_restore::SaveRestore;
358        use vmcore::save_restore::SavedStateRoot;
359
360        #[derive(Protobuf, SavedStateRoot)]
361        #[mesh(package = "chipset.i440bx.host_pci_bridge")]
362        pub struct SavedState {
363            #[mesh(1)]
364            pub host_pci_dram1: u32,
365            #[mesh(2)]
366            pub host_pci_dram2: u32,
367            #[mesh(3)]
368            pub pam_reg1: u32,
369            #[mesh(4)]
370            pub pam_reg2: u32,
371            #[mesh(5)]
372            pub bios_scratch1: u32,
373            #[mesh(6)]
374            pub bios_scratch2: u32,
375            #[mesh(7)]
376            pub smm_config_word: u16,
377            #[mesh(8)]
378            pub cfg_space: <ConfigSpaceType0Emulator as SaveRestore>::SavedState,
379        }
380    }
381
382    impl SaveRestore for HostPciBridge {
383        type SavedState = state::SavedState;
384
385        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
386            let HostPciBridgeState {
387                host_pci_dram1,
388                host_pci_dram2,
389                pam_reg1,
390                pam_reg2,
391                bios_scratch1,
392                bios_scratch2,
393                smm_config_word,
394            } = self.state;
395
396            Ok(state::SavedState {
397                host_pci_dram1,
398                host_pci_dram2,
399                pam_reg1,
400                pam_reg2,
401                bios_scratch1,
402                bios_scratch2,
403                smm_config_word,
404                cfg_space: self.cfg_space.save()?,
405            })
406        }
407
408        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
409            let state::SavedState {
410                host_pci_dram1,
411                host_pci_dram2,
412                pam_reg1,
413                pam_reg2,
414                bios_scratch1,
415                bios_scratch2,
416                smm_config_word,
417                cfg_space,
418            } = state;
419
420            self.state = HostPciBridgeState {
421                host_pci_dram1,
422                host_pci_dram2,
423                pam_reg1,
424                pam_reg2,
425                bios_scratch1,
426                bios_scratch2,
427                smm_config_word,
428            };
429
430            self.adjust_bios_override_ranges(pam_reg1, pam_reg2, true);
431
432            self.cfg_space.restore(cfg_space)?;
433
434            Ok(())
435        }
436    }
437}