Skip to main content

vga/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! This is a port of the PCI VGA adapter from Hyper-V.
5//!
6//! It, in turn, was originally an emulator of an S3 Trio, but over time it was
7//! defeatured to be a generic SVGA card. It still uses the S3 Trio registers
8//! for SVGA mode switching, and it has some extra Hyper-V enlightenments, so it
9//! must be paired with the proprietary Hyper-V SVGA BIOS.
10//!
11//! This code needs a lot of cleanup, and various features need to be completed
12//! (especially around supporting different graphics modes). Ultimately, the
13//! standard core VGA portion should be split out so that alternate SVGA mode
14//! switching can be layered on top (e.g. to support the bochs mode switching
15//! interface that SeaVGABios uses).
16
17#![expect(missing_docs)]
18#![forbid(unsafe_code)]
19
20mod emu;
21mod non_linear;
22mod render;
23mod spec;
24mod text_mode;
25
26use chipset_device::ChipsetDevice;
27use chipset_device::io::IoResult;
28use chipset_device::mmio::MmioIntercept;
29use chipset_device::pci::ByteEnabledDwordRead;
30use chipset_device::pci::ByteEnabledDwordWrite;
31use chipset_device::pci::PciConfigSpace;
32use chipset_device::pio::PortIoIntercept;
33use framebuffer::FramebufferLocalControl;
34use guestmem::MapRom;
35use inspect::InspectMut;
36use render::Renderer;
37use thiserror::Error;
38use video_core::FramebufferFormat;
39use vmcore::device_state::ChangeDeviceState;
40use vmcore::save_restore::SaveRestore;
41use vmcore::save_restore::SavedStateNotSupported;
42use vmcore::vm_task::VmTaskDriver;
43use vmcore::vmtime::VmTimeSource;
44
45#[derive(InspectMut)]
46pub struct VgaDevice {
47    #[inspect(flatten)]
48    emu: emu::Emulator,
49    renderer: Renderer,
50}
51
52#[derive(Debug, Error)]
53pub enum Error {
54    #[error("failed to map framebuffer")]
55    Framebuffer(#[source] std::io::Error),
56}
57
58impl VgaDevice {
59    pub fn new(
60        driver: &VmTaskDriver,
61        vmtime: &VmTimeSource,
62        mut control: FramebufferLocalControl,
63        rom: Option<Box<dyn MapRom>>,
64    ) -> Result<Self, Error> {
65        control.set_format(FramebufferFormat {
66            width: 800,
67            height: 600,
68            bytes_per_line: 800 * 4,
69            offset: 0,
70        });
71
72        let vram = control.memory().map_err(Error::Framebuffer)?;
73        let renderer = Renderer::new(driver, control.clone(), vram.clone());
74        let emu = emu::Emulator::new(control, vram, vmtime, rom, renderer.control());
75        Ok(Self { emu, renderer })
76    }
77}
78
79impl ChangeDeviceState for VgaDevice {
80    fn start(&mut self) {
81        self.renderer.start();
82    }
83
84    async fn stop(&mut self) {
85        self.renderer.stop().await;
86    }
87
88    async fn reset(&mut self) {
89        self.emu.reset();
90    }
91}
92
93impl ChipsetDevice for VgaDevice {
94    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
95        Some(self)
96    }
97
98    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
99        Some(self)
100    }
101
102    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
103        Some(self)
104    }
105}
106
107impl PciConfigSpace for VgaDevice {
108    fn pci_cfg_read(&mut self, offset: u16, value: ByteEnabledDwordRead<'_>) -> IoResult {
109        self.emu.notify_pci_config_access_read(offset, value)
110    }
111
112    fn pci_cfg_write(&mut self, offset: u16, value: ByteEnabledDwordWrite) -> IoResult {
113        self.emu.notify_pci_config_access_write(offset, value)
114    }
115
116    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
117        Some((0, 8, 0)) // to match legacy Hyper-V behavior
118    }
119}
120
121impl MmioIntercept for VgaDevice {
122    fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
123        self.emu.notify_mmio_read(addr, data);
124        IoResult::Ok
125    }
126
127    fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
128        self.emu.notify_mmio_write(addr, data);
129        IoResult::Ok
130    }
131
132    fn get_static_regions(&mut self) -> &[(&str, std::ops::RangeInclusive<u64>)] {
133        // N.B. The VM's RAM must be configured as unmapped in this region.
134        &[("vga", 0xa0000..=0xbffff)]
135    }
136}
137
138impl PortIoIntercept for VgaDevice {
139    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
140        let v = self.emu.io_port_read(io_port, data.len() as u16);
141        data.copy_from_slice(&v.to_ne_bytes()[..data.len()]);
142        IoResult::Ok
143    }
144
145    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
146        let mut v = [0; 4];
147        v[..data.len()].copy_from_slice(data);
148        self.emu
149            .io_port_write(io_port, data.len() as u16, u32::from_ne_bytes(v));
150        IoResult::Ok
151    }
152
153    fn get_static_regions(&mut self) -> &[(&str, std::ops::RangeInclusive<u16>)] {
154        &[
155            ("mda", 0x3b0..=0x3bf),
156            ("vga", 0x3c0..=0x3cf),
157            ("cga", 0x3d0..=0x3df),
158            ("s3", 0x4ae8..=0x4ae8),
159        ]
160    }
161}
162
163impl SaveRestore for VgaDevice {
164    type SavedState = SavedStateNotSupported;
165
166    fn save(&mut self) -> Result<Self::SavedState, vmcore::save_restore::SaveError> {
167        todo!()
168    }
169
170    fn restore(
171        &mut self,
172        state: Self::SavedState,
173    ) -> Result<(), vmcore::save_restore::RestoreError> {
174        match state {}
175    }
176}