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/build/{chunk-V6I6APCK.mjs → chunk-H6WI4NRK.mjs} +122 -123
- package/build/cli.js +590 -610
- package/build/index.d.ts +5 -5
- package/build/index.js +117 -118
- package/build/index.mjs +4 -4
- package/build/{riscv_vm-QIJGD3E2.mjs → riscv_vm-MAZK3LFN.mjs} +1 -1
- package/package.json +12 -5
- package/.yarn/install-state.gz +0 -0
- package/.yarnrc.yml +0 -1
- package/Cargo.toml +0 -49
- package/build.sh +0 -25
- package/cli.ts +0 -268
- package/index.ts +0 -16
- package/src/bus.rs +0 -558
- package/src/clint.rs +0 -132
- package/src/console.rs +0 -83
- package/src/cpu.rs +0 -1913
- package/src/csr.rs +0 -67
- package/src/decoder.rs +0 -789
- package/src/dram.rs +0 -146
- package/src/emulator.rs +0 -603
- package/src/lib.rs +0 -249
- package/src/main.rs +0 -449
- package/src/mmu.rs +0 -331
- package/src/net.rs +0 -121
- package/src/net_webtransport.rs +0 -446
- package/src/plic.rs +0 -261
- package/src/uart.rs +0 -231
- package/src/virtio.rs +0 -1074
- package/tsconfig.json +0 -19
- package/tsup/index.ts +0 -79
- package/tsup/tsup.cli.ts +0 -8
- package/tsup/tsup.core.cjs.ts +0 -7
- package/tsup/tsup.core.esm.ts +0 -8
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 {}
|