1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! PIIX4 - PCI bus

use chipset_device::io::IoResult;
use chipset_device::pio::PortIoIntercept;
use chipset_device::pio::RegisterPortIoIntercept;
use chipset_device::poll_device::PollDevice;
use chipset_device::ChipsetDevice;
use inspect::InspectMut;
use pci_bus::GenericPciBus;
use vmcore::device_state::ChangeDeviceState;

/// IO ports as specified by the PIIX4 data sheet
mod io_ports {
    pub const PCI_ADDR_START: u16 = 0xCF8;
    pub const RESET_CF9: u16 = 0xCF9; // it's just sandwiched in there
    pub const PCI_DATA_START: u16 = 0xCFC;
}

/// The PCI bus as implemented on the PIIX4 chipset.
///
/// Identical to a standard PCI bus, aside from the addition of the `RESET_CF9`
/// register, because for _some reason_, _someone_ thought it'd be a great idea
/// to throw a one-byte register that performs a machine reset *right in the
/// middle of the PCI addr register* >:(
#[derive(InspectMut)]
pub struct Piix4PciBus {
    // Runtime glue
    #[inspect(skip)]
    reset_evt: Box<dyn Fn() + Send + Sync>,

    // Sub-emulator
    #[inspect(mut)]
    bus: GenericPciBus,
}

impl Piix4PciBus {
    /// Create a new [`Piix4PciBus`]
    pub fn new(
        register_pio: &mut dyn RegisterPortIoIntercept,
        reset_evt: Box<dyn Fn() + Send + Sync>,
    ) -> Self {
        Piix4PciBus {
            reset_evt,
            bus: GenericPciBus::new(
                register_pio,
                io_ports::PCI_ADDR_START,
                io_ports::PCI_DATA_START,
            ),
        }
    }

    /// bypass the PIIX4 specific stuff, and get a handle to the underlying PCI
    /// bus implementation
    pub fn as_pci_bus(&mut self) -> &mut GenericPciBus {
        &mut self.bus
    }

    fn handle_reset_cf9_read(&mut self, data: &mut [u8]) {
        if data.len() != 1 {
            tracelimit::warn_ratelimited!(len = ?data.len(), "unexpected RESET_CF9 read len");
            return;
        }

        tracelimit::warn_ratelimited!("read from the RESET_CF9 io port");
        data[0] = 0;
    }

    fn handle_reset_cf9_write(&mut self, data: &[u8]) {
        if data.len() != 1 {
            tracelimit::warn_ratelimited!(len = ?data.len(), "unexpected RESET_CF9 write len");
            return;
        }

        if (data[0] & 0x6) != 0 {
            tracing::info!("initiating guest reset via RESET_CF9");
            (self.reset_evt)();
        }
    }
}

impl ChangeDeviceState for Piix4PciBus {
    fn start(&mut self) {}

    async fn stop(&mut self) {}

    async fn reset(&mut self) {
        self.bus.reset().await;
    }
}

impl ChipsetDevice for Piix4PciBus {
    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
        Some(self)
    }

    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
        Some(self.as_pci_bus())
    }
}

impl PortIoIntercept for Piix4PciBus {
    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
        if data.len() == 1 && io_port == io_ports::RESET_CF9 {
            self.handle_reset_cf9_read(data);
            return IoResult::Ok;
        }

        self.bus.io_read(io_port, data)
    }

    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
        if data.len() == 1 && io_port == io_ports::RESET_CF9 {
            self.handle_reset_cf9_write(data);
            return IoResult::Ok;
        }

        self.bus.io_write(io_port, data)
    }
}

mod save_restore {
    use super::*;
    use vmcore::save_restore::RestoreError;
    use vmcore::save_restore::SaveError;
    use vmcore::save_restore::SaveRestore;

    mod state {
        use mesh::payload::Protobuf;
        use pci_bus::GenericPciBus;
        use vmcore::save_restore::SaveRestore;
        use vmcore::save_restore::SavedStateRoot;

        #[derive(Protobuf, SavedStateRoot)]
        #[mesh(package = "chipset.piix4.pci_bus")]
        pub struct SavedState {
            #[mesh(1)]
            pub bus: <GenericPciBus as SaveRestore>::SavedState,
        }
    }

    impl SaveRestore for Piix4PciBus {
        type SavedState = state::SavedState;

        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
            let Piix4PciBus { reset_evt: _, bus } = self;

            let saved_state = state::SavedState { bus: bus.save()? };

            Ok(saved_state)
        }

        fn restore(&mut self, state: Self::SavedState) -> Result<(), RestoreError> {
            let state::SavedState { bus } = state;

            self.bus.restore(bus)?;

            Ok(())
        }
    }
}