Skip to main content

chipset_legacy/i440bx_host_pci_bridge/
mod.rs

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