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    fn step(&mut self) -> Result<bool>;
113    fn read_memory(&self, address: u64, buffer: &mut [u8]) -> Result<()>;
114    fn write_memory(&mut self, address: u64, data: &[u8]) -> Result<()>;
115}
116
117pub struct Machine {
118    config: MachineConfig,
119    status: MachineStatus,
120    bios: Option<Image>,
121    vga_bios: Option<Image>,
122    disk: Option<Image>,
123    cdrom: Option<Image>,
124    bootloader: Option<Bootloader>,
125    saved_state: Option<SavedState>,
126    backend: Option<Box<dyn ExecutionBackend>>,
127}
128
129impl Machine {
130    pub fn new(config: MachineConfig) -> Self {
131        Self {
132            config,
133            status: MachineStatus::Created,
134            bios: None,
135            vga_bios: None,
136            disk: None,
137            cdrom: None,
138            bootloader: None,
139            saved_state: None,
140            backend: None,
141        }
142    }
143
144    pub fn config(&self) -> &MachineConfig {
145        &self.config
146    }
147
148    pub fn status(&self) -> MachineStatus {
149        self.status
150    }
151
152    pub fn config_mut(&mut self) -> &mut MachineConfig {
153        &mut self.config
154    }
155
156    pub fn set_ram_bytes(&mut self, bytes: u64) {
157        self.config.ram_bytes = bytes;
158    }
159
160    pub fn set_vga_memory_bytes(&mut self, bytes: u64) {
161        self.config.vga_memory_bytes = bytes;
162    }
163
164    pub fn set_command_line(&mut self, command_line: impl Into<String>) {
165        self.config.command_line = Some(command_line.into());
166    }
167
168    pub fn attach_backend(&mut self, backend: impl ExecutionBackend + 'static) {
169        self.backend = Some(Box::new(backend));
170    }
171
172    pub fn set_bios(&mut self, image: Image) -> Result<()> {
173        require_kind(&image, ImageKind::Bios)?;
174        self.bios = Some(image);
175        Ok(())
176    }
177
178    pub fn set_vga_bios(&mut self, image: Image) -> Result<()> {
179        require_kind(&image, ImageKind::VgaBios)?;
180        self.vga_bios = Some(image);
181        Ok(())
182    }
183
184    pub fn set_disk(&mut self, image: Image) -> Result<()> {
185        self.disk = Some(image);
186        Ok(())
187    }
188
189    pub fn set_cdrom(&mut self, image: Image) -> Result<()> {
190        self.cdrom = Some(image);
191        Ok(())
192    }
193
194    pub fn set_bootloader(&mut self, bootloader: Bootloader) {
195        self.bootloader = Some(bootloader);
196    }
197
198    pub fn load_bootloader(&mut self, source: Resource) -> Result<()> {
199        self.bootloader = Some(Bootloader::load(source)?);
200        Ok(())
201    }
202
203    pub fn load_image(&mut self, kind: ImageKind, source: Resource) -> Result<()> {
204        let image = load_resource(&source, kind, &FetchOptions::default())?;
205        match kind {
206            ImageKind::Bios => self.set_bios(image),
207            ImageKind::VgaBios => self.set_vga_bios(image),
208            ImageKind::RawDisk => self.set_disk(image),
209            ImageKind::Iso9660 => self.set_cdrom(image),
210            _ => Err(X86Error::InvalidImage(format!(
211                "image kind {:?} cannot be attached as a machine device",
212                kind
213            ))),
214        }
215    }
216
217    pub fn set_saved_state(&mut self, state: SavedState) {
218        self.saved_state = Some(state);
219    }
220
221    pub fn load_saved_state(&mut self, source: Resource) -> Result<()> {
222        let state_bytes = match source {
223            Resource::File(path) => {
224                std::fs::read(&path).map_err(|source| X86Error::Io { path, source })?
225            }
226            Resource::Bytes { bytes, .. } => bytes,
227            Resource::Url(url) => load_resource(
228                &Resource::Url(url),
229                ImageKind::SavedState,
230                &FetchOptions::default(),
231            )?
232            .bytes()
233            .to_vec(),
234        };
235        self.saved_state = Some(SavedState::from_bytes(state_bytes)?);
236        Ok(())
237    }
238
239    pub fn bios(&self) -> Option<&Image> {
240        self.bios.as_ref()
241    }
242
243    pub fn vga_bios(&self) -> Option<&Image> {
244        self.vga_bios.as_ref()
245    }
246
247    pub fn disk(&self) -> Option<&Image> {
248        self.disk.as_ref()
249    }
250
251    pub fn cdrom(&self) -> Option<&Image> {
252        self.cdrom.as_ref()
253    }
254
255    pub fn bootloader(&self) -> Option<&Bootloader> {
256        self.bootloader.as_ref()
257    }
258
259    pub fn saved_state(&self) -> Option<&SavedState> {
260        self.saved_state.as_ref()
261    }
262
263    pub fn prepare(&mut self) -> Result<()> {
264        if self.backend.is_none() {
265            return Err(X86Error::BackendUnavailable(
266                "no ExecutionBackend attached; attach a native CPU/device backend before run"
267                    .to_owned(),
268            ));
269        }
270        self.backend.as_mut().unwrap().reset(&self.config)?;
271        self.status = MachineStatus::Ready;
272        Ok(())
273    }
274
275    pub fn run(&mut self, options: RunOptions) -> Result<RunReport> {
276        if self.status == MachineStatus::Created {
277            self.prepare()?;
278        }
279        let backend = self.backend.as_mut().ok_or_else(|| {
280            X86Error::BackendUnavailable("no ExecutionBackend attached".to_owned())
281        })?;
282        self.status = MachineStatus::Running;
283        let mut steps = 0;
284        loop {
285            if options.max_steps.is_some_and(|max| steps >= max) {
286                break;
287            }
288            if backend.step()? {
289                self.status = MachineStatus::Stopped;
290                return Ok(RunReport {
291                    steps,
292                    halted: true,
293                });
294            }
295            steps += 1;
296        }
297        Ok(RunReport {
298            steps,
299            halted: false,
300        })
301    }
302
303    pub fn stop(&mut self) {
304        self.status = MachineStatus::Stopped;
305    }
306}
307
308fn require_kind(image: &Image, expected: ImageKind) -> Result<()> {
309    if image.kind() != expected {
310        return Err(X86Error::InvalidImage(format!(
311            "expected {:?}, got {:?}",
312            expected,
313            image.kind()
314        )));
315    }
316    Ok(())
317}