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 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 fn vga_framebuffer_rgb(&self) -> Option<(u32, u32, Vec<u8>)> {
126 None
127 }
128
129 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}