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::DRB_1 => self.state.host_pci_dram1,
175            ConfigSpace::DRB_2 => self.state.host_pci_dram2,
176            // Specify the default value: No AGP, fast CPU startup,
177            // and default low byte of SCRR in our top byte.
178            ConfigSpace::PGPOL => 0x380A0000,
179            ConfigSpace::BSPAD_1 => self.state.bios_scratch1,
180            ConfigSpace::BSPAD_2 => self.state.bios_scratch2,
181            ConfigSpace::SMRAM => {
182                // Bits 7, 2 & 0 are always clear.
183                // Bit 13-11 & 1 are always set.
184                ((self.state.smm_config_word & 0b01111010 | 0b00111000_00000010) as u32) << 16
185            }
186            ConfigSpace::MANUFACTURER_ID => 0x00000F20,
187            ConfigSpace::BUFFC
188            | ConfigSpace::SDRAMC
189            | ConfigSpace::NBXCFG
190            | ConfigSpace::DRAMC
191            | ConfigSpace::MBSC_1
192            | ConfigSpace::SCRR_2
193            | ConfigSpace::ERR
194            | ConfigSpace::ACAPID
195            | ConfigSpace::AGPSTAT
196            | ConfigSpace::AGPCMD
197            | ConfigSpace::AGPCTRL
198            | ConfigSpace::APSIZE
199            | ConfigSpace::ATTBASE
200            | ConfigSpace::UNKNOWN_BC
201            | ConfigSpace::UNKNOWN_F4 => 0, // Hyper-V always returns 0, so do we.
202            _ => {
203                tracing::debug!(?offset, "unimplemented config space read");
204                return IoResult::Err(IoError::InvalidRegister);
205            }
206        };
207
208        IoResult::Ok
209    }
210
211    fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult {
212        match ConfigSpace(offset) {
213            _ if offset < 0x40 => return self.cfg_space.write_u32(offset, value),
214            ConfigSpace::DRB_1 => self.state.host_pci_dram1 = value,
215            ConfigSpace::DRB_2 => self.state.host_pci_dram2 = value,
216            ConfigSpace::PAM1 => {
217                self.adjust_bios_override_ranges(value, self.state.pam_reg2, false);
218            }
219            ConfigSpace::PAM2 => {
220                self.adjust_bios_override_ranges(self.state.pam_reg1, value, false);
221            }
222            ConfigSpace::BSPAD_1 => self.state.bios_scratch1 = value,
223            ConfigSpace::BSPAD_2 => self.state.bios_scratch2 = value,
224            ConfigSpace::SMRAM => {
225                // Configuration registers 70-71 are reserved. Only 72-73 (the top 16
226                // bits of this four-byte range) are defined. We'll therefore shift
227                // off the bottom portion.
228                let mut new_smm_word = (value >> 16) as u16;
229
230                // If the register is "locked" (i.e. bit 4 has been set), then
231                // all of the other bits become read-only.
232                if self.state.smm_config_word & 0x10 == 0 {
233                    // Make sure they aren't enabling features we don't currently support.
234                    const UNSUPPORTED_BITS: u16 = 0b10000111_00000000;
235                    if new_smm_word & UNSUPPORTED_BITS != 0 {
236                        tracelimit::warn_ratelimited!(
237                            bits = new_smm_word & !UNSUPPORTED_BITS,
238                            "guest set unsupported feature bits"
239                        );
240                    }
241
242                    new_smm_word &= !UNSUPPORTED_BITS;
243                    // Bits 7, 2 & 0 are always clear.
244                    new_smm_word &= 0b01111010;
245                    // Bit 13-11 & 1 are always set.
246                    new_smm_word |= 0b00111000_00000010;
247                    // We never set bit 14 that indicates that SMM memory was accessed
248                    // by the CPU when not in SMM mode.
249                    new_smm_word &= !0b01000000_00000000;
250
251                    // Make sure no one is trying to enable SMM RAM.
252                    if new_smm_word & 0b01000000 != 0 {
253                        tracelimit::warn_ratelimited!("guest attempted to enable SMM RAM");
254                    }
255                    new_smm_word &= !0b01000000;
256
257                    self.state.smm_config_word = new_smm_word;
258                }
259            }
260            ConfigSpace::BUFFC
261            | ConfigSpace::SDRAMC
262            | ConfigSpace::NBXCFG
263            | ConfigSpace::DRAMC
264            | ConfigSpace::MBSC_1
265            | ConfigSpace::PGPOL
266            | ConfigSpace::SCRR_2
267            | ConfigSpace::ERR
268            | ConfigSpace::ACAPID
269            | ConfigSpace::AGPSTAT
270            | ConfigSpace::AGPCMD
271            | ConfigSpace::AGPCTRL
272            | ConfigSpace::APSIZE
273            | ConfigSpace::ATTBASE
274            | ConfigSpace::UNKNOWN_BC
275            | ConfigSpace::UNKNOWN_F4 => {} // Hyper-V ignores these, so do we.
276            _ => {
277                tracing::debug!(?offset, ?value, "unimplemented config space write");
278                return IoResult::Err(IoError::InvalidRegister);
279            }
280        }
281
282        IoResult::Ok
283    }
284
285    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
286        Some((0, 0, 0)) // as per i440bx spec
287    }
288}
289
290open_enum! {
291    /// Note that all accesses will be 4-byte aligned, so this enum sets values
292    /// to the expected offsets we will receive. When the actual register is not
293    /// a full 4 bytes aligned to 4 bytes it is documented here.
294    enum ConfigSpace: u16 {
295        NBXCFG          = 0x50,
296        /// Only comprises offset 0x57.
297        DRAMC           = 0x54,
298        /// Only comprises offset 0x58.
299        DRAMT           = 0x58,
300        /// Comprises offsets 0x59-0x5B.
301        PAM1            = 0x58,
302        PAM2            = 0x5C,
303        DRB_1           = 0x60,
304        DRB_2           = 0x64,
305        /// Only comprises offset 0x68.
306        FDHC            = 0x68,
307        /// Comprises offsets 0x69-0x6B.
308        MBSC_1          = 0x68,
309        /// Comprises offsets 0x6C-0x6E.
310        MBSC_2          = 0x6C,
311        /// Comprises offsets 0x72-0x73.
312        SMRAM           = 0x70,
313        SDRAMC          = 0x74,
314        /// Comprises offsets 0x78-0x7A.
315        PGPOL           = 0x78,
316        /// Only comprises offset 0x7B.
317        SCRR_1          = 0x78,
318        /// Only comprises offset 0x7C.
319        SCRR_2          = 0x7C,
320        /// Comprises offsets 0x90-0x92.
321        ERR             = 0x90,
322        ACAPID          = 0xA0,
323        AGPSTAT         = 0xA4,
324        AGPCMD          = 0xA8,
325        AGPCTRL         = 0xB0,
326        /// Only comprises offset 0xB4.
327        APSIZE          = 0xB4,
328        ATTBASE         = 0xB8,
329        /// Documented as Reserved.
330        UNKNOWN_BC      = 0xBC,
331        MBFS            = 0xCC,
332        BSPAD_1         = 0xD0,
333        BSPAD_2         = 0xD4,
334        /// Comprises offsets 0xF0-0xF1.
335        BUFFC           = 0xF0,
336        /// Documented as Intel Reserved.
337        UNKNOWN_F4      = 0xF4,
338        MANUFACTURER_ID = 0xF8,
339    }
340}
341
342mod pam {
343    use memory_range::MemoryRange;
344
345    #[derive(Default, Copy, Clone, Debug, PartialEq, Eq)]
346    pub enum GpaState {
347        /// Reads and writes go to RAM.
348        #[default]
349        Writable,
350        /// Reads go to RAM, writes go to MMIO.
351        WriteProtected,
352        /// Reads go to ROM, writes go to RAM.
353        WriteOnly,
354        /// Reads and writes go to MMIO.
355        Mmio,
356    }
357
358    pub const PAM_RANGES: &[MemoryRange; 13] = &[
359        MemoryRange::new(0xf0000..0x100000),
360        MemoryRange::new(0xc0000..0xc4000),
361        MemoryRange::new(0xc4000..0xc8000),
362        MemoryRange::new(0xc8000..0xcc000),
363        MemoryRange::new(0xcc000..0xd0000),
364        MemoryRange::new(0xd0000..0xd4000),
365        MemoryRange::new(0xd4000..0xd8000),
366        MemoryRange::new(0xd8000..0xdc000),
367        MemoryRange::new(0xdc000..0xe0000),
368        MemoryRange::new(0xe0000..0xe4000),
369        MemoryRange::new(0xe4000..0xe8000),
370        MemoryRange::new(0xe8000..0xec000),
371        MemoryRange::new(0xec000..0xf0000),
372    ];
373
374    pub fn parse_pam_registers(
375        reg1: u32,
376        reg2: u32,
377    ) -> impl Iterator<Item = (MemoryRange, GpaState)> {
378        // Grab the two PAM (physical address management) registers which
379        // consist of 16 four-bit fields. We never look at the first two bits
380        // of these fields. The second two bits encode the following:
381        //    xx00    => Rom only mapping (shadow RAM is inaccessible)
382        //    xx01    => Read-only RAM (writes go to Rom and are ignored)
383        //    xx10    => Write-only RAM (reads come from Rom - not supported by us)
384        //    xx11    => RAM-only (Rom is inaccessible)
385        let reg = ((reg2 as u64) << 32) | reg1 as u64;
386        PAM_RANGES.iter().enumerate().map(move |(i, range)| {
387            let state = match (reg >> ((i + 3) * 4)) & 3 {
388                0b00 => GpaState::Mmio,
389                0b01 => GpaState::WriteProtected,
390                0b10 => GpaState::WriteOnly,
391                0b11 => GpaState::Writable,
392                _ => unreachable!(),
393            };
394            (*range, state)
395        })
396    }
397}
398
399mod save_restore {
400    use super::*;
401    use vmcore::save_restore::RestoreError;
402    use vmcore::save_restore::SaveError;
403    use vmcore::save_restore::SaveRestore;
404
405    mod state {
406        use mesh::payload::Protobuf;
407        use pci_core::cfg_space_emu::ConfigSpaceType0Emulator;
408        use vmcore::save_restore::SaveRestore;
409        use vmcore::save_restore::SavedStateRoot;
410
411        #[derive(Protobuf, SavedStateRoot)]
412        #[mesh(package = "chipset.i440bx.host_pci_bridge")]
413        pub struct SavedState {
414            #[mesh(1)]
415            pub host_pci_dram1: u32,
416            #[mesh(2)]
417            pub host_pci_dram2: u32,
418            #[mesh(3)]
419            pub pam_reg1: u32,
420            #[mesh(4)]
421            pub pam_reg2: u32,
422            #[mesh(5)]
423            pub bios_scratch1: u32,
424            #[mesh(6)]
425            pub bios_scratch2: u32,
426            #[mesh(7)]
427            pub smm_config_word: u16,
428            #[mesh(8)]
429            pub cfg_space: <ConfigSpaceType0Emulator as SaveRestore>::SavedState,
430        }
431    }
432
433    impl SaveRestore for HostPciBridge {
434        type SavedState = state::SavedState;
435
436        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
437            let HostPciBridgeState {
438                host_pci_dram1,
439                host_pci_dram2,
440                pam_reg1,
441                pam_reg2,
442                bios_scratch1,
443                bios_scratch2,
444                smm_config_word,
445            } = self.state;
446
447            Ok(state::SavedState {
448                host_pci_dram1,
449                host_pci_dram2,
450                pam_reg1,
451                pam_reg2,
452                bios_scratch1,
453                bios_scratch2,
454                smm_config_word,
455                cfg_space: self.cfg_space.save()?,
456            })
457        }
458
459        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
460            let state::SavedState {
461                host_pci_dram1,
462                host_pci_dram2,
463                pam_reg1,
464                pam_reg2,
465                bios_scratch1,
466                bios_scratch2,
467                smm_config_word,
468                cfg_space,
469            } = state;
470
471            self.state = HostPciBridgeState {
472                host_pci_dram1,
473                host_pci_dram2,
474                pam_reg1,
475                pam_reg2,
476                bios_scratch1,
477                bios_scratch2,
478                smm_config_word,
479            };
480
481            self.adjust_bios_override_ranges(pam_reg1, pam_reg2, true);
482
483            self.cfg_space.restore(cfg_space)?;
484
485            Ok(())
486        }
487    }
488}