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