Skip to main content

x86/
machine.rs

1use crate::bootloader::{Bootloader, FetchOptions, Resource, load_resource};
2use crate::error::{Result, X86Error};
3use crate::image::{Image, ImageKind};
4use crate::state::SavedState;
5use serde::{Deserialize, Serialize};
6use std::time::Duration;
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
9pub enum ConsoleMode {
10    Text,
11    Headless,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ConsoleConfig {
16    pub mode: ConsoleMode,
17    pub echo_input: bool,
18    pub width: u16,
19    pub height: u16,
20}
21
22impl Default for ConsoleConfig {
23    fn default() -> Self {
24        Self {
25            mode: ConsoleMode::Text,
26            echo_input: true,
27            width: 80,
28            height: 25,
29        }
30    }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct MachineConfig {
35    pub ram_bytes: u64,
36    pub vga_memory_bytes: u64,
37    pub cpu_hz: u64,
38    pub command_line: Option<String>,
39    pub console: ConsoleConfig,
40}
41
42impl Default for MachineConfig {
43    fn default() -> Self {
44        Self {
45            ram_bytes: 128 * 1024 * 1024,
46            vga_memory_bytes: 8 * 1024 * 1024,
47            cpu_hz: 1_000_000,
48            command_line: None,
49            console: ConsoleConfig::default(),
50        }
51    }
52}
53
54impl MachineConfig {
55    pub fn with_ram_bytes(mut self, bytes: u64) -> Self {
56        self.ram_bytes = bytes;
57        self
58    }
59
60    pub fn with_vga_memory_bytes(mut self, bytes: u64) -> Self {
61        self.vga_memory_bytes = bytes;
62        self
63    }
64
65    pub fn with_cpu_hz(mut self, hz: u64) -> Self {
66        self.cpu_hz = hz;
67        self
68    }
69
70    pub fn with_command_line(mut self, command_line: impl Into<String>) -> Self {
71        self.command_line = Some(command_line.into());
72        self
73    }
74
75    pub fn with_console(mut self, console: ConsoleConfig) -> Self {
76        self.console = console;
77        self
78    }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum MachineStatus {
83    Created,
84    Ready,
85    Running,
86    Stopped,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct RunOptions {
91    pub max_steps: Option<u64>,
92    pub quantum: Duration,
93}
94
95impl Default for RunOptions {
96    fn default() -> Self {
97        Self {
98            max_steps: None,
99            quantum: Duration::from_millis(10),
100        }
101    }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct RunReport {
106    pub steps: u64,
107    pub halted: bool,
108}
109
110pub trait ExecutionBackend: Send {
111    fn reset(&mut self, config: &MachineConfig) -> Result<()>;
112
113    /// Restore an attached v86 saved state after reset. Backends that do not
114    /// support saved states may keep the default no-op implementation.
115    fn restore_state(&mut self, _state: &SavedState) -> Result<()> {
116        Ok(())
117    }
118
119    fn step(&mut self) -> Result<bool>;
120    fn read_memory(&self, address: u64, buffer: &mut [u8]) -> Result<()>;
121    fn write_memory(&mut self, address: u64, data: &[u8]) -> Result<()>;
122
123    /// Return the current guest framebuffer as packed RGB bytes when the
124    /// backend exposes a graphical VGA mode.
125    fn vga_framebuffer_rgb(&self) -> Option<(u32, u32, Vec<u8>)> {
126        None
127    }
128
129    /// Queue host text as guest keyboard input when supported by the backend.
130    fn inject_text(&mut self, _text: &str) -> Result<usize> {
131        Err(X86Error::BackendUnavailable(
132            "guest keyboard input is not supported by this backend".to_owned(),
133        ))
134    }
135}
136
137pub struct Machine {
138    config: MachineConfig,
139    status: MachineStatus,
140    bios: Option<Image>,
141    vga_bios: Option<Image>,
142    disk: Option<Image>,
143    cdrom: Option<Image>,
144    bootloader: Option<Bootloader>,
145    saved_state: Option<SavedState>,
146    backend: Option<Box<dyn ExecutionBackend>>,
147}
148
149impl Machine {
150    pub fn new(config: MachineConfig) -> Self {
151        Self {
152            config,
153            status: MachineStatus::Created,
154            bios: None,
155            vga_bios: None,
156            disk: None,
157            cdrom: None,
158            bootloader: None,
159            saved_state: None,
160            backend: None,
161        }
162    }
163
164    pub fn config(&self) -> &MachineConfig {
165        &self.config
166    }
167
168    pub fn status(&self) -> MachineStatus {
169        self.status
170    }
171
172    pub fn config_mut(&mut self) -> &mut MachineConfig {
173        &mut self.config
174    }
175
176    pub fn set_ram_bytes(&mut self, bytes: u64) {
177        self.config.ram_bytes = bytes;
178    }
179
180    pub fn set_vga_memory_bytes(&mut self, bytes: u64) {
181        self.config.vga_memory_bytes = bytes;
182    }
183
184    pub fn set_command_line(&mut self, command_line: impl Into<String>) {
185        self.config.command_line = Some(command_line.into());
186    }
187
188    pub fn attach_backend(&mut self, backend: impl ExecutionBackend + 'static) {
189        self.backend = Some(Box::new(backend));
190    }
191
192    pub fn set_bios(&mut self, image: Image) -> Result<()> {
193        require_kind(&image, ImageKind::Bios)?;
194        self.bios = Some(image);
195        Ok(())
196    }
197
198    pub fn set_vga_bios(&mut self, image: Image) -> Result<()> {
199        require_kind(&image, ImageKind::VgaBios)?;
200        self.vga_bios = Some(image);
201        Ok(())
202    }
203
204    pub fn set_disk(&mut self, image: Image) -> Result<()> {
205        self.disk = Some(image);
206        Ok(())
207    }
208
209    pub fn set_cdrom(&mut self, image: Image) -> Result<()> {
210        self.cdrom = Some(image);
211        Ok(())
212    }
213
214    pub fn set_bootloader(&mut self, bootloader: Bootloader) {
215        self.bootloader = Some(bootloader);
216    }
217
218    pub fn load_bootloader(&mut self, source: Resource) -> Result<()> {
219        self.bootloader = Some(Bootloader::load(source)?);
220        Ok(())
221    }
222
223    pub fn load_image(&mut self, kind: ImageKind, source: Resource) -> Result<()> {
224        let image = load_resource(&source, kind, &FetchOptions::default())?;
225        match kind {
226            ImageKind::Bios => self.set_bios(image),
227            ImageKind::VgaBios => self.set_vga_bios(image),
228            ImageKind::RawDisk => self.set_disk(image),
229            ImageKind::Iso9660 => self.set_cdrom(image),
230            _ => Err(X86Error::InvalidImage(format!(
231                "image kind {:?} cannot be attached as a machine device",
232                kind
233            ))),
234        }
235    }
236
237    pub fn set_saved_state(&mut self, state: SavedState) {
238        if let Some(memory_bytes) = state.memory_bytes() {
239            self.config.ram_bytes = memory_bytes;
240        }
241        self.saved_state = Some(state);
242    }
243
244    pub fn load_saved_state(&mut self, source: Resource) -> Result<()> {
245        let state_bytes = match source {
246            Resource::File(path) => {
247                std::fs::read(&path).map_err(|source| X86Error::Io { path, source })?
248            }
249            Resource::Bytes { bytes, .. } => bytes,
250            Resource::Url(url) => load_resource(
251                &Resource::Url(url),
252                ImageKind::SavedState,
253                &FetchOptions::default(),
254            )?
255            .bytes()
256            .to_vec(),
257        };
258        let state = SavedState::from_bytes(state_bytes)?;
259        if let Some(memory_bytes) = state.memory_bytes() {
260            self.config.ram_bytes = memory_bytes;
261        }
262        self.saved_state = Some(state);
263        Ok(())
264    }
265
266    pub fn bios(&self) -> Option<&Image> {
267        self.bios.as_ref()
268    }
269
270    pub fn vga_bios(&self) -> Option<&Image> {
271        self.vga_bios.as_ref()
272    }
273
274    pub fn disk(&self) -> Option<&Image> {
275        self.disk.as_ref()
276    }
277
278    pub fn cdrom(&self) -> Option<&Image> {
279        self.cdrom.as_ref()
280    }
281
282    pub fn bootloader(&self) -> Option<&Bootloader> {
283        self.bootloader.as_ref()
284    }
285
286    pub fn saved_state(&self) -> Option<&SavedState> {
287        self.saved_state.as_ref()
288    }
289
290    pub fn prepare(&mut self) -> Result<()> {
291        if self.backend.is_none() {
292            return Err(X86Error::BackendUnavailable(
293                "no ExecutionBackend attached; attach a native CPU/device backend before run"
294                    .to_owned(),
295            ));
296        }
297        let backend = self.backend.as_mut().unwrap();
298        backend.reset(&self.config)?;
299        if let Some(state) = self.saved_state.as_ref() {
300            backend.restore_state(state)?;
301        }
302        self.status = MachineStatus::Ready;
303        Ok(())
304    }
305
306    pub fn run(&mut self, options: RunOptions) -> Result<RunReport> {
307        if self.status == MachineStatus::Created {
308            self.prepare()?;
309        }
310        let backend = self.backend.as_mut().ok_or_else(|| {
311            X86Error::BackendUnavailable("no ExecutionBackend attached".to_owned())
312        })?;
313        self.status = MachineStatus::Running;
314        let mut steps = 0;
315        loop {
316            if options.max_steps.is_some_and(|max| steps >= max) {
317                break;
318            }
319            if backend.step()? {
320                self.status = MachineStatus::Stopped;
321                return Ok(RunReport {
322                    steps,
323                    halted: true,
324                });
325            }
326            steps += 1;
327        }
328        Ok(RunReport {
329            steps,
330            halted: false,
331        })
332    }
333
334    pub fn vga_framebuffer_rgb(&self) -> Option<(u32, u32, Vec<u8>)> {
335        self.backend.as_ref()?.vga_framebuffer_rgb()
336    }
337
338    pub fn inject_text(&mut self, text: &str) -> Result<usize> {
339        self.backend
340            .as_mut()
341            .ok_or_else(|| X86Error::BackendUnavailable("no ExecutionBackend attached".to_owned()))?
342            .inject_text(text)
343    }
344
345    pub fn stop(&mut self) {
346        self.status = MachineStatus::Stopped;
347    }
348}
349
350fn require_kind(image: &Image, expected: ImageKind) -> Result<()> {
351    if image.kind() != expected {
352        return Err(X86Error::InvalidImage(format!(
353            "expected {:?}, got {:?}",
354            expected,
355            image.kind()
356        )));
357    }
358    Ok(())
359}