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::PciConfigSpace;
30use chipset_device::pio::PortIoIntercept;
31use framebuffer::FramebufferLocalControl;
32use guestmem::MapRom;
33use inspect::InspectMut;
34use render::Renderer;
35use thiserror::Error;
36use video_core::FramebufferFormat;
37use vmcore::device_state::ChangeDeviceState;
38use vmcore::save_restore::SaveRestore;
39use vmcore::save_restore::SavedStateNotSupported;
40use vmcore::vm_task::VmTaskDriver;
41use vmcore::vmtime::VmTimeSource;
42
43#[derive(InspectMut)]
44pub struct VgaDevice {
45    #[inspect(flatten)]
46    emu: emu::Emulator,
47    renderer: Renderer,
48}
49
50#[derive(Debug, Error)]
51pub enum Error {
52    #[error("failed to map framebuffer")]
53    Framebuffer(#[source] std::io::Error),
54}
55
56impl VgaDevice {
57    pub fn new(
58        driver: &VmTaskDriver,
59        vmtime: &VmTimeSource,
60        mut control: FramebufferLocalControl,
61        rom: Option<Box<dyn MapRom>>,
62    ) -> Result<Self, Error> {
63        control.set_format(FramebufferFormat {
64            width: 800,
65            height: 600,
66            bytes_per_line: 800 * 4,
67            offset: 0,
68        });
69
70        let vram = control.memory().map_err(Error::Framebuffer)?;
71        let renderer = Renderer::new(driver, control.clone(), vram.clone());
72        let emu = emu::Emulator::new(control, vram, vmtime, rom, renderer.control());
73        Ok(Self { emu, renderer })
74    }
75}
76
77impl ChangeDeviceState for VgaDevice {
78    fn start(&mut self) {
79        self.renderer.start();
80    }
81
82    async fn stop(&mut self) {
83        self.renderer.stop().await;
84    }
85
86    async fn reset(&mut self) {
87        self.emu.reset();
88    }
89}
90
91impl ChipsetDevice for VgaDevice {
92    fn supports_pio(&mut self) -> Option<&mut dyn PortIoIntercept> {
93        Some(self)
94    }
95
96    fn supports_mmio(&mut self) -> Option<&mut dyn MmioIntercept> {
97        Some(self)
98    }
99
100    fn supports_pci(&mut self) -> Option<&mut dyn PciConfigSpace> {
101        Some(self)
102    }
103}
104
105impl PciConfigSpace for VgaDevice {
106    fn pci_cfg_read(&mut self, offset: u16, value: &mut u32) -> IoResult {
107        self.emu.notify_pci_config_access_read(offset, value)
108    }
109
110    fn pci_cfg_write(&mut self, offset: u16, value: u32) -> IoResult {
111        self.emu.notify_pci_config_access_write(offset, value)
112    }
113
114    fn suggested_bdf(&mut self) -> Option<(u8, u8, u8)> {
115        Some((0, 8, 0)) // to match legacy Hyper-V behavior
116    }
117}
118
119impl MmioIntercept for VgaDevice {
120    fn mmio_read(&mut self, addr: u64, data: &mut [u8]) -> IoResult {
121        self.emu.notify_mmio_read(addr, data);
122        IoResult::Ok
123    }
124
125    fn mmio_write(&mut self, addr: u64, data: &[u8]) -> IoResult {
126        self.emu.notify_mmio_write(addr, data);
127        IoResult::Ok
128    }
129
130    fn get_static_regions(&mut self) -> &[(&str, std::ops::RangeInclusive<u64>)] {
131        // N.B. The VM's RAM must be configured as unmapped in this region.
132        &[("vga", 0xa0000..=0xbffff)]
133    }
134}
135
136impl PortIoIntercept for VgaDevice {
137    fn io_read(&mut self, io_port: u16, data: &mut [u8]) -> IoResult {
138        let v = self.emu.io_port_read(io_port, data.len() as u16);
139        data.copy_from_slice(&v.to_ne_bytes()[..data.len()]);
140        IoResult::Ok
141    }
142
143    fn io_write(&mut self, io_port: u16, data: &[u8]) -> IoResult {
144        let mut v = [0; 4];
145        v[..data.len()].copy_from_slice(data);
146        self.emu
147            .io_port_write(io_port, data.len() as u16, u32::from_ne_bytes(v));
148        IoResult::Ok
149    }
150
151    fn get_static_regions(&mut self) -> &[(&str, std::ops::RangeInclusive<u16>)] {
152        &[
153            ("mda", 0x3b0..=0x3bf),
154            ("vga", 0x3c0..=0x3cf),
155            ("cga", 0x3d0..=0x3df),
156            ("s3", 0x4ae8..=0x4ae8),
157        ]
158    }
159}
160
161impl SaveRestore for VgaDevice {
162    type SavedState = SavedStateNotSupported;
163
164    fn save(&mut self) -> Result<Self::SavedState, vmcore::save_restore::SaveError> {
165        todo!()
166    }
167
168    fn restore(
169        &mut self,
170        state: Self::SavedState,
171    ) -> Result<(), vmcore::save_restore::RestoreError> {
172        match state {}
173    }
174}