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}