chipset_legacy/
piix4_pci_bus.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! PIIX4 - PCI bus
5
6use chipset_device::ChipsetDevice;
7use chipset_device::io::IoResult;
8use chipset_device::pio::PortIoIntercept;
9use chipset_device::pio::RegisterPortIoIntercept;
10use chipset_device::poll_device::PollDevice;
11use inspect::InspectMut;
12use pci_bus::GenericPciBus;
13use vmcore::device_state::ChangeDeviceState;
14
15/// IO ports as specified by the PIIX4 data sheet
16mod io_ports {
17    pub const PCI_ADDR_START: u16 = 0xCF8;
18    pub const RESET_CF9: u16 = 0xCF9; // it's just sandwiched in there
19    pub const PCI_DATA_START: u16 = 0xCFC;
20}
21
22/// The PCI bus as implemented on the PIIX4 chipset.
23///
24/// Identical to a standard PCI bus, aside from the addition of the `RESET_CF9`
25/// register, because for _some reason_, _someone_ thought it'd be a great idea
26/// to throw a one-byte register that performs a machine reset *right in the
27/// middle of the PCI addr register* >:(
28#[derive(InspectMut)]
29pub struct Piix4PciBus {
30    // Runtime glue
31    #[inspect(skip)]
32    reset_evt: Box<dyn Fn() + Send + Sync>,
33
34    // Sub-emulator
35    #[inspect(mut)]
36    bus: GenericPciBus,
37}
38
39impl Piix4PciBus {
40    /// Create a new [`Piix4PciBus`]
41    pub fn new(
42        register_pio: &mut dyn RegisterPortIoIntercept,
43        reset_evt: Box<dyn Fn() + Send + Sync>,
44    ) -> Self {
45        Piix4PciBus {
46            reset_evt,
47            bus: GenericPciBus::new(
48                register_pio,
49                io_ports::PCI_ADDR_START,
50                io_ports::PCI_DATA_START,
51            ),
52        }
53    }
54
55    /// bypass the PIIX4 specific stuff, and get a handle to the underlying PCI
56    /// bus implementation
57    pub fn as_pci_bus(&mut self) -> &mut GenericPciBus {
58        &mut self.bus
59    }
60
61    fn handle_reset_cf9_read(&mut self, data: &mut [u8]) {
62        if data.len() != 1 {
63            tracelimit::warn_ratelimited!(len = ?data.len(), "unexpected RESET_CF9 read len");
64            return;
65        }
66
67        tracelimit::warn_ratelimited!("read from the RESET_CF9 io port");
68        data[0] = 0;
69    }
70
71    fn handle_reset_cf9_write(&mut self, data: &[u8]) {
72        if data.len() != 1 {
73            tracelimit::warn_ratelimited!(len = ?data.len(), "unexpected RESET_CF9 write len");
74            return;
75        }
76
77        if (data[0] & 0x6) != 0 {
78            tracing::info!("initiating guest reset via RESET_CF9");
79            (self.reset_evt)();
80        }
81    }
82}
83
84impl ChangeDeviceState for Piix4PciBus {
85    fn start(&mut self) {}
86
87    async fn stop(&mut self) {}
88
89    async fn reset(&mut self) {
90        self.bus.reset().await;
91    }
92}
93
94impl ChipsetDevice for Piix4PciBus {
95    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
96        Some(self)
97    }
98
99    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
100        Some(self.as_pci_bus())
101    }
102}
103
104impl PortIoIntercept for Piix4PciBus {
105    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
106        if data.len() == 1 && io_port == io_ports::RESET_CF9 {
107            self.handle_reset_cf9_read(data);
108            return IoResult::Ok;
109        }
110
111        self.bus.io_read(io_port, data)
112    }
113
114    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
115        if data.len() == 1 && io_port == io_ports::RESET_CF9 {
116            self.handle_reset_cf9_write(data);
117            return IoResult::Ok;
118        }
119
120        self.bus.io_write(io_port, data)
121    }
122}
123
124mod save_restore {
125    use super::*;
126    use vmcore::save_restore::RestoreError;
127    use vmcore::save_restore::SaveError;
128    use vmcore::save_restore::SaveRestore;
129
130    mod state {
131        use mesh::payload::Protobuf;
132        use pci_bus::GenericPciBus;
133        use vmcore::save_restore::SaveRestore;
134        use vmcore::save_restore::SavedStateRoot;
135
136        #[derive(Protobuf, SavedStateRoot)]
137        #[mesh(package = "chipset.piix4.pci_bus")]
138        pub struct SavedState {
139            #[mesh(1)]
140            pub bus: <GenericPciBus as SaveRestore>::SavedState,
141        }
142    }
143
144    impl SaveRestore for Piix4PciBus {
145        type SavedState = state::SavedState;
146
147        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
148            let Piix4PciBus { reset_evt: _, bus } = self;
149
150            let saved_state = state::SavedState { bus: bus.save()? };
151
152            Ok(saved_state)
153        }
154
155        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
156            let state::SavedState { bus } = state;
157
158            self.bus.restore(bus)?;
159
160            Ok(())
161        }
162    }
163}