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/lib.rs DELETED
@@ -1,249 +0,0 @@
1
- pub mod bus;
2
- pub mod cpu;
3
- pub mod decoder;
4
- pub mod csr;
5
- pub mod mmu;
6
- pub mod dram;
7
- pub mod clint;
8
- pub mod plic;
9
- pub mod uart;
10
- pub mod net;
11
- pub mod net_webtransport;
12
- pub mod virtio;
13
- pub mod emulator;
14
-
15
- #[cfg(not(target_arch = "wasm32"))]
16
- pub mod console;
17
-
18
-
19
- use serde::{Deserialize, Serialize};
20
-
21
- // WASM bindings
22
- #[cfg(target_arch = "wasm32")]
23
- use wasm_bindgen::prelude::*;
24
-
25
- #[cfg(target_arch = "wasm32")]
26
- use crate::bus::{SystemBus, DRAM_BASE};
27
-
28
- /// Network connection status for the WASM VM.
29
- #[cfg(target_arch = "wasm32")]
30
- #[wasm_bindgen]
31
- #[derive(Clone, Copy, PartialEq, Eq)]
32
- pub enum NetworkStatus {
33
- Disconnected = 0,
34
- Connecting = 1,
35
- Connected = 2,
36
- Error = 3,
37
- }
38
-
39
- /// WASM-exposed VM wrapper for running RISC-V kernels in the browser.
40
- #[cfg(target_arch = "wasm32")]
41
- #[wasm_bindgen]
42
- pub struct WasmVm {
43
- bus: SystemBus,
44
- cpu: cpu::Cpu,
45
- net_status: NetworkStatus,
46
- poll_counter: u32,
47
- }
48
-
49
- #[cfg(target_arch = "wasm32")]
50
- #[wasm_bindgen]
51
- impl WasmVm {
52
- /// Create a new VM instance and load a kernel (ELF or raw binary).
53
- #[wasm_bindgen(constructor)]
54
- pub fn new(kernel: &[u8]) -> Result<WasmVm, JsValue> {
55
- // Set up panic hook for better error messages in the browser console
56
- console_error_panic_hook::set_once();
57
-
58
- const DRAM_SIZE: usize = 512 * 1024 * 1024; // 512 MiB
59
- let mut bus = SystemBus::new(DRAM_BASE, DRAM_SIZE);
60
-
61
- // Check if it's an ELF file and load appropriately
62
- let entry_pc = if kernel.starts_with(b"\x7FELF") {
63
- // Parse and load ELF
64
- load_elf_wasm(kernel, &mut bus)
65
- .map_err(|e| JsValue::from_str(&format!("Failed to load ELF kernel: {}", e)))?
66
- } else {
67
- // Load raw binary at DRAM_BASE
68
- bus.dram
69
- .load(kernel, 0)
70
- .map_err(|e| JsValue::from_str(&format!("Failed to load kernel: {}", e)))?;
71
- DRAM_BASE
72
- };
73
-
74
- let cpu = cpu::Cpu::new(entry_pc);
75
-
76
- Ok(WasmVm {
77
- bus,
78
- cpu,
79
- net_status: NetworkStatus::Disconnected,
80
- poll_counter: 0,
81
- })
82
- }
83
-
84
- /// Load a disk image and attach it as a VirtIO block device.
85
- /// This should be called before starting execution if the kernel needs a filesystem.
86
- pub fn load_disk(&mut self, disk_image: &[u8]) {
87
- let vblk = virtio::VirtioBlock::new(disk_image.to_vec());
88
- self.bus.virtio_devices.push(Box::new(vblk));
89
- }
90
-
91
- /// Connect to a WebTransport relay server.
92
- pub fn connect_webtransport(&mut self, url: &str, cert_hash: Option<String>) -> Result<(), JsValue> {
93
- use crate::net_webtransport::WebTransportBackend;
94
- use crate::virtio::VirtioNet;
95
-
96
- self.net_status = NetworkStatus::Connecting;
97
-
98
- let backend = WebTransportBackend::new(url, cert_hash);
99
- // Note: WebTransport connect is async, so backend.init() will start connection
100
- // but actual connection happens in background.
101
- let mut vnet = VirtioNet::new(Box::new(backend));
102
- vnet.debug = false;
103
-
104
- self.bus.virtio_devices.push(Box::new(vnet));
105
- self.net_status = NetworkStatus::Connected;
106
-
107
- Ok(())
108
- }
109
-
110
- /// Disconnect from the network.
111
- pub fn disconnect_network(&mut self) {
112
- // Remove VirtioNet devices (device_id == 1)
113
- self.bus.virtio_devices.retain(|dev| dev.device_id() != 1);
114
- self.net_status = NetworkStatus::Disconnected;
115
- }
116
-
117
- /// Get the current network connection status.
118
- pub fn network_status(&self) -> NetworkStatus {
119
- self.net_status
120
- }
121
-
122
- /// Execute a single instruction.
123
- pub fn step(&mut self) {
124
- // Poll VirtIO devices periodically for incoming network packets
125
- // Poll every 100 instructions for good network responsiveness
126
- self.poll_counter = self.poll_counter.wrapping_add(1);
127
- if self.poll_counter % 100 == 0 {
128
- self.bus.poll_virtio();
129
- }
130
-
131
- // Ignore traps for now - the kernel handles them
132
- let _ = self.cpu.step(&mut self.bus);
133
- }
134
-
135
- /// Get a byte from the UART output buffer, if available.
136
- pub fn get_output(&mut self) -> Option<u8> {
137
- self.bus.uart.pop_output()
138
- }
139
-
140
- /// Push an input byte to the UART.
141
- pub fn input(&mut self, byte: u8) {
142
- self.bus.uart.push_input(byte);
143
- }
144
-
145
- /// Get current memory usage (DRAM size) in bytes.
146
- pub fn get_memory_usage(&self) -> u64 {
147
- self.bus.dram.data.len() as u64
148
- }
149
- }
150
-
151
- /// Load an ELF kernel into DRAM (WASM-compatible version).
152
- #[cfg(target_arch = "wasm32")]
153
- fn load_elf_wasm(buffer: &[u8], bus: &mut SystemBus) -> Result<u64, String> {
154
- use goblin::elf::{program_header::PT_LOAD, Elf};
155
-
156
- let elf = Elf::parse(buffer).map_err(|e| format!("ELF parse error: {}", e))?;
157
- let base = bus.dram_base();
158
- let dram_end = base + bus.dram_size() as u64;
159
-
160
- for ph in &elf.program_headers {
161
- if ph.p_type != PT_LOAD || ph.p_memsz == 0 {
162
- continue;
163
- }
164
-
165
- let file_size = ph.p_filesz as usize;
166
- let mem_size = ph.p_memsz as usize;
167
- let file_offset = ph.p_offset as usize;
168
- if file_offset + file_size > buffer.len() {
169
- return Err(format!(
170
- "ELF segment exceeds file bounds (offset 0x{:x})",
171
- file_offset
172
- ));
173
- }
174
-
175
- let target_addr = if ph.p_paddr != 0 {
176
- ph.p_paddr
177
- } else {
178
- ph.p_vaddr
179
- };
180
- if target_addr < base {
181
- return Err(format!(
182
- "Segment start 0x{:x} lies below DRAM base 0x{:x}",
183
- target_addr, base
184
- ));
185
- }
186
- let seg_end = target_addr
187
- .checked_add(mem_size as u64)
188
- .ok_or_else(|| "Segment end overflow".to_string())?;
189
- if seg_end > dram_end {
190
- return Err(format!(
191
- "Segment 0x{:x}-0x{:x} exceeds DRAM (end 0x{:x})",
192
- target_addr, seg_end, dram_end
193
- ));
194
- }
195
-
196
- let dram_offset = (target_addr - base) as u64;
197
- if file_size > 0 {
198
- let end = file_offset + file_size;
199
- bus.dram
200
- .load(&buffer[file_offset..end], dram_offset)
201
- .map_err(|e| format!("Failed to load segment: {}", e))?;
202
- }
203
- if mem_size > file_size {
204
- let zero_start = dram_offset as usize + file_size;
205
- bus.dram
206
- .zero_range(zero_start, mem_size - file_size)
207
- .map_err(|e| format!("Failed to zero bss: {}", e))?;
208
- }
209
- }
210
-
211
- Ok(elf.entry)
212
- }
213
-
214
- #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215
- pub enum Trap {
216
- InstructionAddressMisaligned(u64),
217
- InstructionAccessFault(u64),
218
- IllegalInstruction(u64),
219
- Breakpoint,
220
- LoadAddressMisaligned(u64),
221
- LoadAccessFault(u64),
222
- StoreAddressMisaligned(u64),
223
- StoreAccessFault(u64),
224
- EnvironmentCallFromU,
225
- EnvironmentCallFromS,
226
- EnvironmentCallFromM,
227
- InstructionPageFault(u64),
228
- LoadPageFault(u64),
229
- StorePageFault(u64),
230
-
231
- MachineSoftwareInterrupt,
232
- MachineTimerInterrupt,
233
- MachineExternalInterrupt,
234
- SupervisorSoftwareInterrupt,
235
- SupervisorTimerInterrupt,
236
- SupervisorExternalInterrupt,
237
-
238
- // Custom internal errors
239
- RequestedTrap(u64), // For testing (software interrupts, etc)
240
- Fatal(String),
241
- }
242
-
243
- impl std::fmt::Display for Trap {
244
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245
- write!(f, "{:?}", self)
246
- }
247
- }
248
-
249
- impl std::error::Error for Trap {}