virtual-machine 0.0.4 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/main.rs DELETED
@@ -1,449 +0,0 @@
1
- use clap::Parser;
2
- use goblin::elf::{program_header::PT_LOAD, Elf};
3
- use riscv_vm::bus::{Bus, SystemBus};
4
- use riscv_vm::cpu::Cpu;
5
- use riscv_vm::Trap;
6
- use riscv_vm::csr::{CSR_MCAUSE, CSR_MEPC, CSR_MTVAL, CSR_MTVEC, CSR_SCAUSE, CSR_SEPC, CSR_STVAL, CSR_STVEC};
7
- use std::fs::File;
8
- use std::io::Read;
9
- use std::path::PathBuf;
10
-
11
- use riscv_vm::console::Console;
12
-
13
- #[derive(Parser, Debug)]
14
- #[command(author, version, about, long_about = None)]
15
- struct Args {
16
- /// Path or URL to binary to load
17
- #[arg(short, long)]
18
- kernel: String,
19
-
20
- /// Address to load kernel at (default 0x8000_0000)
21
- #[arg(long, default_value_t = 0x8000_0000)]
22
- load_addr: u64,
23
-
24
- /// DRAM size in MiB
25
- #[arg(long, default_value_t = 512)]
26
- mem_mib: usize,
27
-
28
- /// Optional path to a VirtIO Block disk image (e.g. xv6 fs.img)
29
- #[arg(long)]
30
- disk: Option<PathBuf>,
31
-
32
- /// Optional TAP interface name for VirtIO network device (e.g. tap0)
33
- /// Requires the interface to exist: sudo ip tuntap add dev tap0 mode tap
34
- #[arg(long)]
35
- net_tap: Option<String>,
36
-
37
- /// Enable VirtIO network device with a dummy backend (for testing, no actual packets)
38
- #[arg(long)]
39
- net_dummy: bool,
40
-
41
- /// Connect to a WebSocket server for networking (e.g. ws://localhost:8765)
42
- /// Works on macOS and in browser/WASM
43
- #[arg(long)]
44
- net_ws: Option<String>,
45
-
46
- /// Connect to a WebTransport relay for networking (e.g. https://127.0.0.1:4433)
47
- /// Supports NAT traversal and peer-to-peer connections
48
- #[arg(long)]
49
- net_webtransport: Option<String>,
50
-
51
- /// Certificate hash for WebTransport (hex string)
52
- /// Required for self-signed certificates
53
- #[arg(long)]
54
- net_cert_hash: Option<String>,
55
- }
56
-
57
- // Debug helper: dump VirtIO MMIO identity registers expected by xv6.
58
- fn dump_virtio_id(bus: &mut SystemBus) {
59
- const VIRTIO0_BASE: u64 = 0x1000_1000;
60
- fn r32(bus: &mut SystemBus, off: u64) -> u32 {
61
- bus.read32(VIRTIO0_BASE + off).unwrap_or(0)
62
- }
63
- let magic = r32(bus, 0x000);
64
- let ver = r32(bus, 0x004);
65
- let devid = r32(bus, 0x008);
66
- let vendor = r32(bus, 0x00c);
67
- eprintln!(
68
- "VirtIO ID: MAGIC=0x{:08x} VERSION={} DEVICE_ID={} VENDOR=0x{:08x}",
69
- magic, ver, devid, vendor
70
- );
71
- }
72
-
73
- fn print_vm_banner() {
74
- const BANNER: &str = r#"
75
- ┌─────────────────────────────────────────────────────────────────────────┐
76
- │ │
77
- │ ██████╗ ██╗███████╗██╗ ██╗ ██╗ ██╗ │
78
- │ ██╔══██╗██║██╔════╝██║ ██╔╝ ██║ ██║ │
79
- │ ██████╔╝██║███████╗█████╔╝ ██║ ██║ │
80
- │ ██╔══██╗██║╚════██║██╔═██╗ ╚██╗ ██╔╝ │
81
- │ ██║ ██║██║███████║██║ ██╗ ╚████╔╝ │
82
- │ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═╝ ╚═══╝ │
83
- │ │
84
- │ RISC-V Virtual Machine Hypervisor v0.1.0 │
85
- │ 64-bit RISC-V Emulator with VirtIO Support │
86
- │ │
87
- └─────────────────────────────────────────────────────────────────────────┘
88
- "#;
89
- println!("{}", BANNER);
90
- }
91
-
92
- fn print_section(title: &str) {
93
- println!("\n\x1b[1;36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
94
- println!("\x1b[1;33m ▸ {}\x1b[0m", title);
95
- println!("\x1b[1;36m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\x1b[0m");
96
- }
97
-
98
- fn print_status(component: &str, status: &str, ok: bool) {
99
- let status_color = if ok { "\x1b[1;32m" } else { "\x1b[1;31m" };
100
- let check = if ok { "✓" } else { "✗" };
101
- println!(" \x1b[0;37m{:<40}\x1b[0m {}[{}] {}\x1b[0m", component, status_color, check, status);
102
- }
103
-
104
- fn print_info(key: &str, value: &str) {
105
- println!(" \x1b[0;90m├─\x1b[0m \x1b[0;37m{:<20}\x1b[0m \x1b[1;97m{}\x1b[0m", key, value);
106
- }
107
-
108
- fn main() -> Result<(), Box<dyn std::error::Error>> {
109
- env_logger::init();
110
- print_vm_banner();
111
-
112
- let args = Args::parse();
113
-
114
- // ─── CPU INITIALIZATION ───────────────────────────────────────────────────
115
- print_section("CPU INITIALIZATION");
116
- print_info("Architecture", "RISC-V 64-bit (RV64GC)");
117
- print_info("Extensions", "I, M, A, F, D, C, Zicsr, Zifencei");
118
- print_info("Privilege Modes", "Machine, Supervisor, User");
119
- print_status("CPU Core", "INITIALIZED", true);
120
-
121
- // ─── MEMORY SUBSYSTEM ─────────────────────────────────────────────────────
122
- print_section("MEMORY SUBSYSTEM");
123
- let dram_size_bytes = args
124
- .mem_mib
125
- .checked_mul(1024 * 1024)
126
- .ok_or("Requested memory size is too large")?;
127
- let dram_base = 0x8000_0000u64;
128
-
129
- print_info("DRAM Base", &format!("0x{:08X}", dram_base));
130
- print_info("DRAM Size", &format!("{} MiB ({} bytes)", args.mem_mib, dram_size_bytes));
131
- print_info("Address Range", &format!("0x{:08X} - 0x{:08X}", dram_base, dram_base + dram_size_bytes as u64));
132
-
133
- let mut bus = SystemBus::new(dram_base, dram_size_bytes);
134
- print_status("DRAM Controller", "ONLINE", true);
135
- print_status("MMU (Sv39)", "READY", true);
136
-
137
- // ─── KERNEL LOADING ───────────────────────────────────────────────────────
138
- print_section("KERNEL LOADING");
139
- let buffer = if args.kernel.starts_with("http://") || args.kernel.starts_with("https://") {
140
- print_info("Source", "Remote (HTTP/HTTPS)");
141
- print_info("URL", &args.kernel);
142
- println!(" \x1b[0;90m├─\x1b[0m \x1b[0;33mDownloading...\x1b[0m");
143
- let response = reqwest::blocking::get(&args.kernel)?;
144
- if !response.status().is_success() {
145
- print_status("Download", "FAILED", false);
146
- return Err(format!("Failed to download kernel: {}", response.status()).into());
147
- }
148
- let bytes = response.bytes()?.to_vec();
149
- print_status("Download", "COMPLETE", true);
150
- bytes
151
- } else {
152
- print_info("Source", "Local filesystem");
153
- print_info("Path", &args.kernel);
154
- let mut file = File::open(&args.kernel)?;
155
- let mut buffer = Vec::new();
156
- file.read_to_end(&mut buffer)?;
157
- buffer
158
- };
159
- print_info("Kernel Size", &format!("{} bytes ({:.2} KiB)", buffer.len(), buffer.len() as f64 / 1024.0));
160
-
161
-
162
- // ─── VIRTIO DEVICE BUS ─────────────────────────────────────────────────────
163
- print_section("VIRTIO DEVICE BUS");
164
- print_info("Bus Type", "VirtIO MMIO v2");
165
- print_info("Base Address", "0x10001000");
166
- print_info("Device Spacing", "0x1000 (4 KiB)");
167
-
168
- // If a disk image is provided, wire up VirtIO Block at 0x1000_1000
169
- if let Some(disk_path) = &args.disk {
170
- let mut disk_file = File::open(disk_path)?;
171
- let mut disk_buf = Vec::new();
172
- disk_file.read_to_end(&mut disk_buf)?;
173
- let disk_size_mib = disk_buf.len() / (1024 * 1024);
174
- let vblk = riscv_vm::virtio::VirtioBlock::new(disk_buf);
175
- bus.virtio_devices.push(Box::new(vblk));
176
- println!();
177
- println!(" \x1b[1;35m┌─ VirtIO Block Device ─────────────────────────────────┐\x1b[0m");
178
- println!(" \x1b[1;35m│\x1b[0m Address: \x1b[1;97m0x10001000\x1b[0m \x1b[1;35m│\x1b[0m");
179
- println!(" \x1b[1;35m│\x1b[0m IRQ: \x1b[1;97m1\x1b[0m \x1b[1;35m│\x1b[0m");
180
- println!(" \x1b[1;35m│\x1b[0m Disk Size: \x1b[1;97m{} MiB\x1b[0m \x1b[1;35m│\x1b[0m", disk_size_mib);
181
- println!(" \x1b[1;35m│\x1b[0m Image: \x1b[0;90m{}\x1b[0m", disk_path.display());
182
- println!(" \x1b[1;35m└────────────────────────────────────────────────────────┘\x1b[0m");
183
- print_status("VirtIO Block", "ATTACHED", true);
184
- }
185
-
186
- // If WebTransport is provided, wire up VirtIO Net
187
- if let Some(wt_url) = &args.net_webtransport {
188
- let wt_backend = riscv_vm::net_webtransport::WebTransportBackend::new(wt_url, args.net_cert_hash.clone());
189
- let vnet = riscv_vm::virtio::VirtioNet::new(Box::new(wt_backend));
190
- let device_idx = bus.virtio_devices.len();
191
- let irq = 1 + device_idx;
192
- bus.virtio_devices.push(Box::new(vnet));
193
- let base_addr = 0x1000_1000 + (device_idx as u64) * 0x1000;
194
- println!();
195
- println!(" \x1b[1;34m┌─ VirtIO Network Device ───────────────────────────────┐\x1b[0m");
196
- println!(" \x1b[1;34m│\x1b[0m Address: \x1b[1;97m0x{:08X}\x1b[0m \x1b[1;34m│\x1b[0m", base_addr);
197
- println!(" \x1b[1;34m│\x1b[0m IRQ: \x1b[1;97m{}\x1b[0m \x1b[1;34m│\x1b[0m", irq);
198
- println!(" \x1b[1;34m│\x1b[0m Backend: \x1b[1;97mWebTransport\x1b[0m \x1b[1;34m│\x1b[0m");
199
- println!(" \x1b[1;34m│\x1b[0m Relay: \x1b[0;90m{}\x1b[0m", wt_url);
200
- println!(" \x1b[1;34m└────────────────────────────────────────────────────────┘\x1b[0m");
201
- print_status("VirtIO Network", "ATTACHED", true);
202
- }
203
-
204
- let entry_pc = if buffer.starts_with(b"\x7FELF") {
205
- print_info("Format", "ELF64 Executable");
206
- load_elf_into_dram(&buffer, &mut bus)?
207
- } else {
208
- if args.load_addr < dram_base {
209
- print_status("Load Address", "INVALID (below DRAM)", false);
210
- return Ok(());
211
- }
212
- let offset = args.load_addr - dram_base;
213
- print_info("Format", "Raw Binary");
214
- print_info("Load Address", &format!("0x{:08X}", args.load_addr));
215
- bus.dram
216
- .load(&buffer, offset)
217
- .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
218
- args.load_addr
219
- };
220
- print_info("Entry Point", &format!("0x{:08X}", entry_pc));
221
- print_status("Kernel Image", "LOADED", true);
222
-
223
- // ─── SYSTEM PERIPHERALS ───────────────────────────────────────────────────
224
- print_section("SYSTEM PERIPHERALS");
225
- print_info("UART 16550", "0x10000000 (IRQ 10)");
226
- print_info("CLINT", "0x02000000 (Machine Timer)");
227
- print_info("PLIC", "0x0C000000 (IRQ Controller)");
228
- print_status("UART Console", "READY", true);
229
- print_status("Interrupt Controller", "CONFIGURED", true);
230
-
231
- // Early probe dump (harmless if device absent)
232
- dump_virtio_id(&mut bus);
233
-
234
- let mut cpu = Cpu::new(entry_pc);
235
-
236
- // ─── BOOT SEQUENCE COMPLETE ───────────────────────────────────────────────
237
- print_section("BOOT SEQUENCE COMPLETE");
238
- println!();
239
- println!(" \x1b[1;32m╔══════════════════════════════════════════════════════════════════════╗\x1b[0m");
240
- println!(" \x1b[1;32m║\x1b[0m \x1b[1;32m║\x1b[0m");
241
- println!(" \x1b[1;32m║\x1b[0m \x1b[1;97mStarting RISC-V Kernel at 0x{:08X}\x1b[0m \x1b[1;32m║\x1b[0m", entry_pc);
242
- println!(" \x1b[1;32m║\x1b[0m \x1b[0;90mPress Ctrl-A then 'x' to terminate\x1b[0m \x1b[1;32m║\x1b[0m");
243
- println!(" \x1b[1;32m║\x1b[0m \x1b[1;32m║\x1b[0m");
244
- println!(" \x1b[1;32m╚══════════════════════════════════════════════════════════════════════╝\x1b[0m");
245
- println!();
246
- println!("\x1b[1;36m══════════════════════════════════════════════════════════════════════════════\x1b[0m");
247
- println!("\x1b[1;33m KERNEL OUTPUT BEGINS\x1b[0m");
248
- println!("\x1b[1;36m══════════════════════════════════════════════════════════════════════════════\x1b[0m");
249
- println!();
250
-
251
- let mut step_count = 0u64;
252
- let mut last_report_step = 0u64;
253
-
254
- // Initialize console for host input
255
- let console = Console::new();
256
- let mut escaped = false;
257
-
258
- loop {
259
- // Poll console input
260
- if let Some(b) = console.poll() {
261
- if escaped {
262
- if b == b'x' {
263
- println!("\nTerminated by user.");
264
- break;
265
- } else if b == 1 {
266
- // Ctrl-A twice -> send Ctrl-A to guest
267
- bus.uart.push_input(1);
268
- } else {
269
- // Ctrl-A then something else -> send that something else
270
- // (Ctrl-A is swallowed)
271
- bus.uart.push_input(b);
272
- }
273
- escaped = false;
274
- } else {
275
- if b == 1 { // Ctrl-A
276
- escaped = true;
277
- } else {
278
- bus.uart.push_input(b);
279
- }
280
- }
281
- }
282
-
283
- let step_result = cpu.step(&mut bus);
284
- step_count += 1;
285
-
286
- // Poll VirtIO devices for incoming network packets every 100 instructions
287
- // More frequent polling improves network responsiveness for interactive protocols
288
- if step_count % 100 == 0 {
289
- bus.poll_virtio();
290
- }
291
-
292
- // Progress report every 10M instructions (not every instruction!)
293
- if step_count - last_report_step >= 10_000_000 {
294
- // eprinteln!("[{} M insns] pc=0x{:x} mode={:?}", step_count / 1_000_000, cpu.pc, cpu.mode);
295
- last_report_step = step_count;
296
- }
297
-
298
-
299
- if let Err(trap) = step_result {
300
- match trap {
301
- // Test finisher / explicit host stop requested by the guest.
302
- Trap::RequestedTrap(_) => {
303
- break;
304
- }
305
- // Non-recoverable emulator error: dump state and exit.
306
- Trap::Fatal(msg) => {
307
- eprintln!("Fatal emulator error: {msg}");
308
- println!("PC: 0x{:x}", cpu.pc);
309
- for i in 0..32 {
310
- if i % 4 == 0 {
311
- println!();
312
- }
313
- print!("x{:<2}: 0x{:<16x} ", i, cpu.regs[i]);
314
- }
315
- println!();
316
- break;
317
- }
318
- // Architectural traps (interrupts, page faults, ecalls, etc.)
319
- // are fully handled inside Cpu::handle_trap by updating CSRs
320
- // and redirecting PC to mtvec/stvec. We simply continue
321
- // stepping so that the guest handler can run.
322
- _other => {
323
- // Traps are handled inside cpu.step() - just continue execution.
324
- // Use RUST_LOG=debug to see trap details.
325
- if log::log_enabled!(log::Level::Debug) {
326
- let mepc = cpu.read_csr(CSR_MEPC).unwrap_or(0);
327
- let mcause = cpu.read_csr(CSR_MCAUSE).unwrap_or(0);
328
- let mtval = cpu.read_csr(CSR_MTVAL).unwrap_or(0);
329
- let mtvec = cpu.read_csr(CSR_MTVEC).unwrap_or(0);
330
- log::debug!(
331
- "Trap: {:?} pc=0x{:x} mepc=0x{:x} mcause=0x{:x} mtval=0x{:x} mtvec=0x{:x}",
332
- _other, cpu.pc, mepc, mcause, mtval, mtvec
333
- );
334
- }
335
- }
336
- }
337
- }
338
-
339
- // Check UART output - handle raw mode by converting \n to \r\n
340
- use std::io::Write;
341
- let stdout = std::io::stdout();
342
- let mut stdout_lock = stdout.lock();
343
- while let Some(byte) = bus.uart.pop_output() {
344
- // In raw terminal mode, \n alone doesn't return cursor to column 0.
345
- // We need to emit \r\n for proper line breaks.
346
- if byte == b'\n' {
347
- let _ = stdout_lock.write_all(b"\r\n");
348
- } else if byte == b'\r' {
349
- // Carriage return - just emit it
350
- let _ = stdout_lock.write_all(b"\r");
351
- } else {
352
- let _ = stdout_lock.write_all(&[byte]);
353
- }
354
- }
355
- let _ = stdout_lock.flush();
356
-
357
- // Stop if PC is 0 in Machine/Supervisor mode (likely trap to unmapped vector).
358
- // User mode PC=0 is valid (xv6 initcode).
359
- if cpu.pc == 0 && cpu.mode != riscv_vm::csr::Mode::User {
360
- let mepc = cpu.read_csr(CSR_MEPC).unwrap_or(0);
361
- let mcause = cpu.read_csr(CSR_MCAUSE).unwrap_or(0);
362
- let mtval = cpu.read_csr(CSR_MTVAL).unwrap_or(0);
363
- let mtvec = cpu.read_csr(CSR_MTVEC).unwrap_or(0);
364
- let sepc = cpu.read_csr(CSR_SEPC).unwrap_or(0);
365
- let scause = cpu.read_csr(CSR_SCAUSE).unwrap_or(0);
366
- let stval = cpu.read_csr(CSR_STVAL).unwrap_or(0);
367
- let stvec = cpu.read_csr(CSR_STVEC).unwrap_or(0);
368
- println!("PC reached 0, stopping.");
369
- println!(
370
- "Final state:\n pc=0x{:016x} mode={:?}\n M: mepc=0x{:016x} mcause=0x{:016x} mtval=0x{:016x} mtvec=0x{:016x}\n S: sepc=0x{:016x} scause=0x{:016x} stval=0x{:016x} stvec=0x{:016x}",
371
- cpu.pc, cpu.mode, mepc, mcause, mtval, mtvec, sepc, scause, stval, stvec
372
- );
373
- break;
374
- }
375
- }
376
-
377
- Ok(())
378
- }
379
-
380
- fn load_elf_into_dram(
381
- buffer: &[u8],
382
- bus: &mut SystemBus,
383
- ) -> Result<u64, Box<dyn std::error::Error>> {
384
- let elf = Elf::parse(buffer)?;
385
- let base = bus.dram_base();
386
- let dram_end = base + bus.dram_size() as u64;
387
-
388
- for ph in &elf.program_headers {
389
- if ph.p_type != PT_LOAD || ph.p_memsz == 0 {
390
- continue;
391
- }
392
-
393
- let file_size = ph.p_filesz as usize;
394
- let mem_size = ph.p_memsz as usize;
395
- let file_offset = ph.p_offset as usize;
396
- if file_offset + file_size > buffer.len() {
397
- return Err(format!(
398
- "ELF segment exceeds file bounds (offset 0x{:x})",
399
- file_offset
400
- )
401
- .into());
402
- }
403
-
404
- let target_addr = if ph.p_paddr != 0 {
405
- ph.p_paddr
406
- } else {
407
- ph.p_vaddr
408
- };
409
- if target_addr < base {
410
- return Err(format!(
411
- "Segment start 0x{:x} lies below DRAM base 0x{:x}",
412
- target_addr, base
413
- )
414
- .into());
415
- }
416
- let seg_end = target_addr
417
- .checked_add(mem_size as u64)
418
- .ok_or_else(|| "Segment end overflow".to_string())?;
419
- if seg_end > dram_end {
420
- return Err(format!(
421
- "Segment 0x{:x}-0x{:x} exceeds DRAM (end 0x{:x})",
422
- target_addr, seg_end, dram_end
423
- )
424
- .into());
425
- }
426
-
427
- let dram_offset = (target_addr - base) as u64;
428
- if file_size > 0 {
429
- let end = file_offset + file_size;
430
- bus.dram
431
- .load(&buffer[file_offset..end], dram_offset)
432
- .map_err(|e| format!("Failed to load segment: {}", e))?;
433
- }
434
- if mem_size > file_size {
435
- let zero_start = dram_offset as usize + file_size;
436
- bus.dram
437
- .zero_range(zero_start, mem_size - file_size)
438
- .map_err(|e| format!("Failed to zero bss: {}", e))?;
439
- }
440
- log::debug!(
441
- "Loaded segment: addr=0x{:x}, filesz=0x{:x}, memsz=0x{:x}",
442
- target_addr,
443
- file_size,
444
- mem_size
445
- );
446
- }
447
-
448
- Ok(elf.entry)
449
- }