vga_proxy/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! A fake VGA device that proxies all PCI accesses to the emulated host device.
5
6#![expect(missing_docs)]
7#![forbid(unsafe_code)]
8
9use chipset_device::ChipsetDevice;
10use chipset_device::io::IoError;
11use chipset_device::io::IoResult;
12use chipset_device::io::deferred::DeferredRead;
13use chipset_device::io::deferred::DeferredWrite;
14use chipset_device::io::deferred::defer_read;
15use chipset_device::io::deferred::defer_write;
16use chipset_device::pci::PciConfigSpace;
17use chipset_device::pio::PortIoIntercept;
18use chipset_device::poll_device::PollDevice;
19use inspect::InspectMut;
20use std::future::Future;
21use std::ops::RangeInclusive;
22use std::pin::Pin;
23use std::sync::Arc;
24use std::task::Poll;
25use std::task::Waker;
26use vmcore::device_state::ChangeDeviceState;
27
28pub struct VgaProxyDevice {
29    pci_cfg_proxy: Arc<dyn ProxyVgaPciCfgAccess>,
30    pending_action: Option<DeferredAction>,
31    waker: Option<Waker>,
32    _host_port_handles: Vec<Box<dyn Send>>,
33}
34
35enum DeferredAction {
36    Read(DeferredRead, Pin<Box<dyn Future<Output = u32> + Send>>),
37    Write(DeferredWrite, Pin<Box<dyn Future<Output = ()> + Send>>),
38}
39
40static PORTS: &[(&str, RangeInclusive<u16>)] = &[
41    ("s3", 0x4ae8..=0x4ae9),
42    ("mda", 0x3b0..=0x3bf),
43    ("vga", 0x3c0..=0x3cf),
44    ("cga", 0x3d0..=0x3df),
45];
46
47impl VgaProxyDevice {
48    pub fn new(
49        pci_cfg_proxy: Arc<dyn ProxyVgaPciCfgAccess>,
50        register: &dyn RegisterHostIoPortFastPath,
51    ) -> Self {
52        // Register the IO ports with the host so that the hypervisor sends the
53        // IOs directly there.
54        let host_port_handles = PORTS
55            .iter()
56            .map(|(_, range)| register.register(range.clone()))
57            .collect();
58
59        Self {
60            pci_cfg_proxy,
61            pending_action: None,
62            waker: None,
63            _host_port_handles: host_port_handles,
64        }
65    }
66}
67
68/// Trait for registering host IO ports.
69pub trait RegisterHostIoPortFastPath {
70    /// Registers ports in `range` to go directly to the host.
71    ///
72    /// When the return value is dropped, the ports will be unregistered.
73    #[must_use]
74    fn register(&self, range: RangeInclusive<u16>) -> Box<dyn Send>;
75}
76
77#[async_trait::async_trait]
78pub trait ProxyVgaPciCfgAccess: Send + Sync {
79    async fn vga_proxy_pci_read(&self, offset: u16) -> u32;
80    async fn vga_proxy_pci_write(&self, offset: u16, value: u32);
81}
82
83impl ChangeDeviceState for VgaProxyDevice {
84    fn start(&mut self) {}
85
86    async fn stop(&mut self) {}
87
88    async fn reset(&mut self) {
89        // No state is currently stored here, so no work is needed for reset.
90        // The host side device will reset its own state for us.
91    }
92}
93
94impl ChipsetDevice for VgaProxyDevice {
95    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
96        Some(self)
97    }
98
99    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
100        Some(self)
101    }
102
103    fn supports_poll_device(&mut self) -> Option<&mut dyn PollDevice> {
104        Some(self)
105    }
106}
107
108impl PciConfigSpace for VgaProxyDevice {
109    fn pci_cfg_read(&mut self, offset: u16, _value: &mut u32) -> IoResult {
110        tracing::trace!(?offset, "VGA proxy read");
111        let (read, token) = defer_read();
112        assert!(self.pending_action.is_none());
113
114        let fut = {
115            let proxy = self.pci_cfg_proxy.clone();
116            async move { proxy.vga_proxy_pci_read(offset).await }
117        };
118
119        self.pending_action = Some(DeferredAction::Read(read, Box::pin(fut)));
120        if let Some(waker) = self.waker.take() {
121            waker.wake();
122        }
123        IoResult::Defer(token)
124    }
125
126    fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult {
127        tracing::trace!(?offset, ?value, "VGA proxy write");
128        let (write, token) = defer_write();
129        assert!(self.pending_action.is_none());
130
131        let fut = {
132            let proxy = self.pci_cfg_proxy.clone();
133            async move { proxy.vga_proxy_pci_write(offset, value).await }
134        };
135
136        self.pending_action = Some(DeferredAction::Write(write, Box::pin(fut)));
137        if let Some(waker) = self.waker.take() {
138            waker.wake();
139        }
140        IoResult::Defer(token)
141    }
142
143    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
144        Some((0, 8, 0)) // to match legacy Hyper-V behavior
145    }
146}
147
148impl PortIoIntercept for VgaProxyDevice {
149    fn io_read(&mut self, io_port: u16, _data: &mut [u8]) -> IoResult {
150        // This access extends beyond the registered IO port and was trapped
151        // here instead of going to the host. Fail it.
152        tracelimit::warn_ratelimited!(io_port, "invalid straddling vga write");
153        IoResult::Err(IoError::InvalidAccessSize)
154    }
155
156    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
157        // This access extends beyond the registered IO port and was trapped
158        // here instead of going to the host. Fail it.
159        tracelimit::warn_ratelimited!(io_port, ?data, "invalid straddling vga write");
160        IoResult::Err(IoError::InvalidAccessSize)
161    }
162
163    fn get_static_regions(&mut self) -> &[(&str, RangeInclusive<u16>)] {
164        PORTS
165    }
166}
167
168impl PollDevice for VgaProxyDevice {
169    fn poll_device(&mut self, cx: &mut std::task::Context<'_>) {
170        self.waker = Some(cx.waker().clone());
171        if let Some(action) = self.pending_action.take() {
172            match action {
173                DeferredAction::Read(dr, mut fut) => {
174                    if let Poll::Ready(value) = fut.as_mut().poll(cx) {
175                        tracing::trace!(value, "VGA proxy read complete");
176                        dr.complete(&value.to_ne_bytes());
177                    } else {
178                        self.pending_action = Some(DeferredAction::Read(dr, fut));
179                    }
180                }
181                DeferredAction::Write(dw, mut fut) => {
182                    if let Poll::Ready(()) = fut.as_mut().poll(cx) {
183                        tracing::trace!("VGA proxy write complete");
184                        dw.complete();
185                    } else {
186                        self.pending_action = Some(DeferredAction::Write(dw, fut));
187                    }
188                }
189            }
190        };
191    }
192}
193
194impl InspectMut for VgaProxyDevice {
195    fn inspect_mut(&mut self, req: inspect::Request<'_>) {
196        req.respond();
197    }
198}
199
200mod save_restore {
201    use super::*;
202    use vmcore::save_restore::NoSavedState;
203    use vmcore::save_restore::RestoreError;
204    use vmcore::save_restore::SaveError;
205    use vmcore::save_restore::SaveRestore;
206
207    // No state is stored on the proxy device, and so we don't need to do
208    // anything here to enable save/restore.
209    //
210    // The host side device will save and restore its own state for us.
211
212    impl SaveRestore for VgaProxyDevice {
213        type SavedState = NoSavedState;
214
215        fn save(&mut self) -> Result<Self::SavedState, SaveError> {
216            Ok(NoSavedState)
217        }
218
219        fn restore(&mut self, NoSavedState: Self::SavedState) -> Result<(), RestoreError> {
220            Ok(())
221        }
222    }
223}