virtual-machine 0.0.3 → 0.0.10
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-GZ343GYI.mjs → chunk-MELUIZWO.mjs} +6 -104
- package/build/cli.js +591 -709
- package/build/index.d.ts +2 -11
- package/build/index.js +9 -107
- package/build/index.mjs +4 -4
- package/build/{riscv_vm-3CIEJ5K7.mjs → riscv_vm-JTPPWIT3.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/chunk-LJUNPJTY.mjs +0 -670
- package/build/chunk-Q7TFYQ5G.mjs +0 -670
- package/build/riscv_vm-MHIWEZQY.mjs +0 -12
- package/build/riscv_vm-VNML57ES.mjs +0 -12
- 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 -270
- package/src/main.rs +0 -373
- package/src/mmu.rs +0 -331
- package/src/net.rs +0 -121
- package/src/net_tap.rs +0 -164
- package/src/net_webtransport.rs +0 -446
- package/src/net_ws.rs +0 -396
- package/src/plic.rs +0 -261
- package/src/uart.rs +0 -233
- 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/net_ws.rs
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
//! WebSocket network backend for cross-platform networking.
|
|
2
|
-
//!
|
|
3
|
-
//! This backend tunnels Ethernet frames over WebSocket, enabling
|
|
4
|
-
//! networking on platforms without TAP support (macOS, WASM/browser).
|
|
5
|
-
|
|
6
|
-
use crate::net::NetworkBackend;
|
|
7
|
-
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
|
|
8
|
-
use std::sync::{Arc, Mutex};
|
|
9
|
-
|
|
10
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
11
|
-
mod native {
|
|
12
|
-
use super::*;
|
|
13
|
-
use std::thread;
|
|
14
|
-
use tungstenite::{connect, Message, WebSocket};
|
|
15
|
-
use tungstenite::stream::MaybeTlsStream;
|
|
16
|
-
use std::net::TcpStream;
|
|
17
|
-
|
|
18
|
-
/// WebSocket backend for native platforms (macOS, Linux, Windows).
|
|
19
|
-
pub struct WsBackend {
|
|
20
|
-
url: String,
|
|
21
|
-
mac: [u8; 6],
|
|
22
|
-
tx_to_ws: Option<Sender<Vec<u8>>>,
|
|
23
|
-
rx_from_ws: Option<Receiver<Vec<u8>>>,
|
|
24
|
-
connected: Arc<Mutex<bool>>,
|
|
25
|
-
error_message: Arc<Mutex<Option<String>>>,
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
impl WsBackend {
|
|
29
|
-
pub fn new(url: &str) -> Self {
|
|
30
|
-
// Generate a random-ish MAC based on URL hash
|
|
31
|
-
let mut mac = [0x52, 0x54, 0x00, 0x00, 0x00, 0x00];
|
|
32
|
-
let hash: u32 = url.bytes().fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
|
|
33
|
-
mac[3] = ((hash >> 16) & 0xff) as u8;
|
|
34
|
-
mac[4] = ((hash >> 8) & 0xff) as u8;
|
|
35
|
-
mac[5] = (hash & 0xff) as u8;
|
|
36
|
-
|
|
37
|
-
Self {
|
|
38
|
-
url: url.to_string(),
|
|
39
|
-
mac,
|
|
40
|
-
tx_to_ws: None,
|
|
41
|
-
rx_from_ws: None,
|
|
42
|
-
connected: Arc::new(Mutex::new(false)),
|
|
43
|
-
error_message: Arc::new(Mutex::new(None)),
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/// Check if the backend is currently connected.
|
|
48
|
-
pub fn is_connected(&self) -> bool {
|
|
49
|
-
*self.connected.lock().unwrap()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/// Get any error message from the connection.
|
|
53
|
-
pub fn error_message(&self) -> Option<String> {
|
|
54
|
-
self.error_message.lock().unwrap().clone()
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/// Reader thread - reads from WebSocket and sends to channel
|
|
58
|
-
fn reader_thread(
|
|
59
|
-
mut socket: WebSocket<MaybeTlsStream<TcpStream>>,
|
|
60
|
-
tx_received: Sender<Vec<u8>>,
|
|
61
|
-
rx_to_send: Receiver<Vec<u8>>,
|
|
62
|
-
connected: Arc<Mutex<bool>>,
|
|
63
|
-
) {
|
|
64
|
-
// Set socket to blocking mode for reliable reads
|
|
65
|
-
if let MaybeTlsStream::Plain(ref stream) = *socket.get_ref() {
|
|
66
|
-
let _ = stream.set_nonblocking(false);
|
|
67
|
-
// Set a read timeout so we can also check for outgoing messages
|
|
68
|
-
let _ = stream.set_read_timeout(Some(std::time::Duration::from_millis(10)));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
loop {
|
|
72
|
-
// First, check if we have outgoing messages to send
|
|
73
|
-
loop {
|
|
74
|
-
match rx_to_send.try_recv() {
|
|
75
|
-
Ok(data) => {
|
|
76
|
-
log::trace!("[WsBackend] Sending {} bytes", data.len());
|
|
77
|
-
if let Err(e) = socket.send(Message::Binary(data.into())) {
|
|
78
|
-
log::warn!("[WsBackend] Send error: {}", e);
|
|
79
|
-
*connected.lock().unwrap() = false;
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
Err(TryRecvError::Empty) => break,
|
|
84
|
-
Err(TryRecvError::Disconnected) => {
|
|
85
|
-
log::info!("[WsBackend] Send channel closed, exiting");
|
|
86
|
-
*connected.lock().unwrap() = false;
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Flush any pending writes
|
|
93
|
-
if let Err(e) = socket.flush() {
|
|
94
|
-
if !matches!(e, tungstenite::Error::Io(ref io) if io.kind() == std::io::ErrorKind::WouldBlock) {
|
|
95
|
-
log::warn!("[WsBackend] Flush error: {}", e);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Try to read from WebSocket
|
|
100
|
-
match socket.read() {
|
|
101
|
-
Ok(Message::Binary(data)) => {
|
|
102
|
-
log::debug!("[WsBackend] Received {} bytes from relay", data.len());
|
|
103
|
-
if tx_received.send(data.into()).is_err() {
|
|
104
|
-
log::info!("[WsBackend] Receiver dropped, exiting");
|
|
105
|
-
*connected.lock().unwrap() = false;
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
Ok(Message::Close(_)) => {
|
|
110
|
-
log::info!("[WsBackend] WebSocket closed by server");
|
|
111
|
-
*connected.lock().unwrap() = false;
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
Ok(Message::Ping(data)) => {
|
|
115
|
-
// Respond to ping with pong
|
|
116
|
-
let _ = socket.send(Message::Pong(data));
|
|
117
|
-
}
|
|
118
|
-
Ok(_) => {} // Ignore text, pong
|
|
119
|
-
Err(tungstenite::Error::Io(ref e)) if e.kind() == std::io::ErrorKind::WouldBlock => {
|
|
120
|
-
// Timeout - no data available, continue loop
|
|
121
|
-
}
|
|
122
|
-
Err(tungstenite::Error::Io(ref e)) if e.kind() == std::io::ErrorKind::TimedOut => {
|
|
123
|
-
// Timeout - no data available, continue loop
|
|
124
|
-
}
|
|
125
|
-
Err(e) => {
|
|
126
|
-
log::warn!("[WsBackend] Read error: {}", e);
|
|
127
|
-
*connected.lock().unwrap() = false;
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
impl NetworkBackend for WsBackend {
|
|
136
|
-
fn init(&mut self) -> Result<(), String> {
|
|
137
|
-
log::info!("[WsBackend] Connecting to {}", self.url);
|
|
138
|
-
|
|
139
|
-
let (socket, _response) = connect(&self.url)
|
|
140
|
-
.map_err(|e| {
|
|
141
|
-
let msg = format!("Failed to connect: {}", e);
|
|
142
|
-
log::error!("[WsBackend] {}", msg);
|
|
143
|
-
*self.error_message.lock().unwrap() = Some(msg.clone());
|
|
144
|
-
msg
|
|
145
|
-
})?;
|
|
146
|
-
|
|
147
|
-
log::info!("[WsBackend] Connected successfully!");
|
|
148
|
-
*self.connected.lock().unwrap() = true;
|
|
149
|
-
*self.error_message.lock().unwrap() = None;
|
|
150
|
-
|
|
151
|
-
let (tx_to_ws, rx_to_send) = channel();
|
|
152
|
-
let (tx_received, rx_from_ws) = channel();
|
|
153
|
-
|
|
154
|
-
self.tx_to_ws = Some(tx_to_ws);
|
|
155
|
-
self.rx_from_ws = Some(rx_from_ws);
|
|
156
|
-
|
|
157
|
-
let connected = self.connected.clone();
|
|
158
|
-
|
|
159
|
-
thread::spawn(move || {
|
|
160
|
-
Self::reader_thread(socket, tx_received, rx_to_send, connected);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
log::info!("[WsBackend] Initialized successfully");
|
|
164
|
-
Ok(())
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
|
|
168
|
-
if let Some(ref rx) = self.rx_from_ws {
|
|
169
|
-
match rx.try_recv() {
|
|
170
|
-
Ok(data) => {
|
|
171
|
-
log::trace!("[WsBackend] recv() returning {} bytes", data.len());
|
|
172
|
-
Ok(Some(data))
|
|
173
|
-
}
|
|
174
|
-
Err(TryRecvError::Empty) => Ok(None),
|
|
175
|
-
Err(TryRecvError::Disconnected) => {
|
|
176
|
-
Err("WebSocket disconnected".to_string())
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
Ok(None)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
fn send(&self, buf: &[u8]) -> Result<(), String> {
|
|
185
|
-
if !*self.connected.lock().unwrap() {
|
|
186
|
-
return Err("Not connected".to_string());
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
if let Some(ref tx) = self.tx_to_ws {
|
|
190
|
-
tx.send(buf.to_vec()).map_err(|e| format!("Send failed: {}", e))
|
|
191
|
-
} else {
|
|
192
|
-
Err("WebSocket not initialized".to_string())
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
fn mac_address(&self) -> [u8; 6] {
|
|
197
|
-
self.mac
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
#[cfg(target_arch = "wasm32")]
|
|
203
|
-
mod wasm {
|
|
204
|
-
use super::*;
|
|
205
|
-
use wasm_bindgen::prelude::*;
|
|
206
|
-
use wasm_bindgen::JsCast;
|
|
207
|
-
use web_sys::{WebSocket, MessageEvent, ErrorEvent, CloseEvent, BinaryType};
|
|
208
|
-
use js_sys::{ArrayBuffer, Uint8Array};
|
|
209
|
-
use std::cell::RefCell;
|
|
210
|
-
use std::rc::Rc;
|
|
211
|
-
use std::collections::VecDeque;
|
|
212
|
-
|
|
213
|
-
/// Maximum number of packets to buffer before dropping
|
|
214
|
-
const MAX_RX_QUEUE_SIZE: usize = 256;
|
|
215
|
-
|
|
216
|
-
/// Connection state for tracking
|
|
217
|
-
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
218
|
-
pub enum ConnectionState {
|
|
219
|
-
Disconnected,
|
|
220
|
-
Connecting,
|
|
221
|
-
Connected,
|
|
222
|
-
Error,
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/// WebSocket backend for WASM/browser.
|
|
226
|
-
pub struct WsBackend {
|
|
227
|
-
url: String,
|
|
228
|
-
mac: [u8; 6],
|
|
229
|
-
ws: Option<WebSocket>,
|
|
230
|
-
rx_queue: Rc<RefCell<VecDeque<Vec<u8>>>>,
|
|
231
|
-
state: Rc<RefCell<ConnectionState>>,
|
|
232
|
-
error_message: Rc<RefCell<Option<String>>>,
|
|
233
|
-
packets_dropped: Rc<RefCell<u64>>,
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// WASM types are !Send by default, but we need Send for NetworkBackend.
|
|
237
|
-
// This is safe because WASM is single-threaded.
|
|
238
|
-
unsafe impl Send for WsBackend {}
|
|
239
|
-
|
|
240
|
-
impl WsBackend {
|
|
241
|
-
pub fn new(url: &str) -> Self {
|
|
242
|
-
let mut mac = [0x52, 0x54, 0x00, 0x00, 0x00, 0x00];
|
|
243
|
-
let hash: u32 = url.bytes().fold(0u32, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u32));
|
|
244
|
-
mac[3] = ((hash >> 16) & 0xff) as u8;
|
|
245
|
-
mac[4] = ((hash >> 8) & 0xff) as u8;
|
|
246
|
-
mac[5] = (hash & 0xff) as u8;
|
|
247
|
-
|
|
248
|
-
Self {
|
|
249
|
-
url: url.to_string(),
|
|
250
|
-
mac,
|
|
251
|
-
ws: None,
|
|
252
|
-
rx_queue: Rc::new(RefCell::new(VecDeque::with_capacity(MAX_RX_QUEUE_SIZE))),
|
|
253
|
-
state: Rc::new(RefCell::new(ConnectionState::Disconnected)),
|
|
254
|
-
error_message: Rc::new(RefCell::new(None)),
|
|
255
|
-
packets_dropped: Rc::new(RefCell::new(0)),
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/// Check if the backend is currently connected.
|
|
260
|
-
pub fn is_connected(&self) -> bool {
|
|
261
|
-
*self.state.borrow() == ConnectionState::Connected
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/// Get the current connection state.
|
|
265
|
-
pub fn connection_state(&self) -> ConnectionState {
|
|
266
|
-
*self.state.borrow()
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/// Get any error message.
|
|
270
|
-
pub fn error_message(&self) -> Option<String> {
|
|
271
|
-
self.error_message.borrow().clone()
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/// Get the number of packets dropped due to buffer overflow.
|
|
275
|
-
pub fn packets_dropped(&self) -> u64 {
|
|
276
|
-
*self.packets_dropped.borrow()
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
impl NetworkBackend for WsBackend {
|
|
281
|
-
fn init(&mut self) -> Result<(), String> {
|
|
282
|
-
*self.state.borrow_mut() = ConnectionState::Connecting;
|
|
283
|
-
*self.error_message.borrow_mut() = None;
|
|
284
|
-
|
|
285
|
-
let ws = WebSocket::new(&self.url)
|
|
286
|
-
.map_err(|e| {
|
|
287
|
-
*self.state.borrow_mut() = ConnectionState::Error;
|
|
288
|
-
let msg = format!("Failed to create WebSocket: {:?}", e);
|
|
289
|
-
*self.error_message.borrow_mut() = Some(msg.clone());
|
|
290
|
-
msg
|
|
291
|
-
})?;
|
|
292
|
-
|
|
293
|
-
ws.set_binary_type(BinaryType::Arraybuffer);
|
|
294
|
-
|
|
295
|
-
// Set up message handler with buffer overflow protection
|
|
296
|
-
let rx_queue = self.rx_queue.clone();
|
|
297
|
-
let packets_dropped = self.packets_dropped.clone();
|
|
298
|
-
let onmessage_callback = Closure::<dyn FnMut(_)>::new(move |e: MessageEvent| {
|
|
299
|
-
if let Ok(abuf) = e.data().dyn_into::<ArrayBuffer>() {
|
|
300
|
-
let array = Uint8Array::new(&abuf);
|
|
301
|
-
let mut data = vec![0u8; array.length() as usize];
|
|
302
|
-
array.copy_to(&mut data);
|
|
303
|
-
|
|
304
|
-
let mut queue = rx_queue.borrow_mut();
|
|
305
|
-
// Handle buffer overflow - drop oldest packets if queue is full
|
|
306
|
-
while queue.len() >= MAX_RX_QUEUE_SIZE {
|
|
307
|
-
queue.pop_front();
|
|
308
|
-
*packets_dropped.borrow_mut() += 1;
|
|
309
|
-
}
|
|
310
|
-
queue.push_back(data);
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
|
|
314
|
-
onmessage_callback.forget();
|
|
315
|
-
|
|
316
|
-
// Set up open handler
|
|
317
|
-
let state_open = self.state.clone();
|
|
318
|
-
let error_clear = self.error_message.clone();
|
|
319
|
-
let onopen_callback = Closure::<dyn FnMut()>::new(move || {
|
|
320
|
-
*state_open.borrow_mut() = ConnectionState::Connected;
|
|
321
|
-
*error_clear.borrow_mut() = None;
|
|
322
|
-
log::info!("[WsBackend] WebSocket connected!");
|
|
323
|
-
});
|
|
324
|
-
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
|
|
325
|
-
onopen_callback.forget();
|
|
326
|
-
|
|
327
|
-
// Set up error handler
|
|
328
|
-
let state_error = self.state.clone();
|
|
329
|
-
let error_message = self.error_message.clone();
|
|
330
|
-
let onerror_callback = Closure::<dyn FnMut(_)>::new(move |e: ErrorEvent| {
|
|
331
|
-
*state_error.borrow_mut() = ConnectionState::Error;
|
|
332
|
-
let msg = format!("WebSocket error: {}", e.message());
|
|
333
|
-
*error_message.borrow_mut() = Some(msg.clone());
|
|
334
|
-
log::error!("[WsBackend] {}", msg);
|
|
335
|
-
});
|
|
336
|
-
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
|
|
337
|
-
onerror_callback.forget();
|
|
338
|
-
|
|
339
|
-
// Set up close handler
|
|
340
|
-
let state_close = self.state.clone();
|
|
341
|
-
let onclose_callback = Closure::<dyn FnMut(_)>::new(move |e: CloseEvent| {
|
|
342
|
-
*state_close.borrow_mut() = ConnectionState::Disconnected;
|
|
343
|
-
log::info!("[WsBackend] WebSocket closed (code: {}, reason: {})",
|
|
344
|
-
e.code(), e.reason());
|
|
345
|
-
});
|
|
346
|
-
ws.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
|
347
|
-
onclose_callback.forget();
|
|
348
|
-
|
|
349
|
-
self.ws = Some(ws);
|
|
350
|
-
|
|
351
|
-
log::info!("[WsBackend] Initialized, connecting to {}", self.url);
|
|
352
|
-
Ok(())
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
fn recv(&mut self) -> Result<Option<Vec<u8>>, String> {
|
|
356
|
-
// Check connection state
|
|
357
|
-
let state = *self.state.borrow();
|
|
358
|
-
if state == ConnectionState::Error {
|
|
359
|
-
if let Some(msg) = self.error_message.borrow().clone() {
|
|
360
|
-
return Err(msg);
|
|
361
|
-
}
|
|
362
|
-
return Err("WebSocket in error state".to_string());
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
Ok(self.rx_queue.borrow_mut().pop_front())
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
fn send(&self, buf: &[u8]) -> Result<(), String> {
|
|
369
|
-
let state = *self.state.borrow();
|
|
370
|
-
|
|
371
|
-
if state != ConnectionState::Connected {
|
|
372
|
-
// Silently drop if not connected (don't spam errors)
|
|
373
|
-
return Ok(());
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if let Some(ref ws) = self.ws {
|
|
377
|
-
let array = Uint8Array::from(buf);
|
|
378
|
-
ws.send_with_array_buffer(&array.buffer())
|
|
379
|
-
.map_err(|e| format!("Send failed: {:?}", e))
|
|
380
|
-
} else {
|
|
381
|
-
Err("WebSocket not initialized".to_string())
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
fn mac_address(&self) -> [u8; 6] {
|
|
386
|
-
self.mac
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Re-export the appropriate backend based on target
|
|
392
|
-
#[cfg(not(target_arch = "wasm32"))]
|
|
393
|
-
pub use native::WsBackend;
|
|
394
|
-
|
|
395
|
-
#[cfg(target_arch = "wasm32")]
|
|
396
|
-
pub use wasm::WsBackend;
|
package/src/plic.rs
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
use crate::dram::MemoryError;
|
|
2
|
-
|
|
3
|
-
pub const PLIC_BASE: u64 = 0x0C00_0000;
|
|
4
|
-
pub const PLIC_SIZE: u64 = 0x400_0000;
|
|
5
|
-
|
|
6
|
-
pub const UART_IRQ: u32 = 10;
|
|
7
|
-
pub const VIRTIO0_IRQ: u32 = 1;
|
|
8
|
-
|
|
9
|
-
const NUM_SOURCES: usize = 32;
|
|
10
|
-
const NUM_CONTEXTS: usize = 2; // 0 = M-mode hart0, 1 = S-mode hart0
|
|
11
|
-
|
|
12
|
-
pub struct Plic {
|
|
13
|
-
pub priority: [u32; NUM_SOURCES],
|
|
14
|
-
pub pending: u32, // Level-triggered mirror of device IRQ lines (bit per source)
|
|
15
|
-
pub enable: [u32; NUM_CONTEXTS],
|
|
16
|
-
pub threshold: [u32; NUM_CONTEXTS],
|
|
17
|
-
pub active: [u32; NUM_CONTEXTS], // Per-context in-flight IRQs (claimed but not completed)
|
|
18
|
-
pub debug: bool,
|
|
19
|
-
// Multi-context arrays enable SMP readiness while preserving single-hart behavior.
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
impl Plic {
|
|
23
|
-
pub fn new() -> Self {
|
|
24
|
-
Self {
|
|
25
|
-
priority: [0; NUM_SOURCES],
|
|
26
|
-
pending: 0,
|
|
27
|
-
enable: [0; NUM_CONTEXTS],
|
|
28
|
-
threshold: [0; NUM_CONTEXTS],
|
|
29
|
-
active: [0; NUM_CONTEXTS],
|
|
30
|
-
debug: false,
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
pub fn update_pending(&mut self, source: u32) {
|
|
35
|
-
// Backward compatibility helper: set as pending (edge → level).
|
|
36
|
-
// Bus.refresh_irqs() may later clear this if device line is low.
|
|
37
|
-
if source < 32 {
|
|
38
|
-
if self.debug {
|
|
39
|
-
eprintln!("[PLIC] Update Pending source={}", source);
|
|
40
|
-
}
|
|
41
|
-
self.pending |= 1 << source;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// New: level-triggered source line setter
|
|
46
|
-
pub fn set_source_level(&mut self, source: u32, level: bool) {
|
|
47
|
-
if source >= 32 {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
let was_pending = (self.pending & (1 << source)) != 0;
|
|
51
|
-
if level {
|
|
52
|
-
if self.debug && !was_pending {
|
|
53
|
-
eprintln!("[PLIC] IRQ Line High: source={} enable[0]=0x{:x} enable[1]=0x{:x} prio={}",
|
|
54
|
-
source, self.enable[0], self.enable[1], self.priority[source as usize]);
|
|
55
|
-
}
|
|
56
|
-
self.pending |= 1 << source;
|
|
57
|
-
} else {
|
|
58
|
-
self.pending &= !(1 << source);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
pub fn load(&mut self, offset: u64, size: u64) -> Result<u64, MemoryError> {
|
|
63
|
-
if size != 4 {
|
|
64
|
-
return Ok(0);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Priority registers: 0x000000 .. 0x0000FC (4 bytes each)
|
|
68
|
-
if offset < 0x001000 {
|
|
69
|
-
let idx = (offset >> 2) as usize;
|
|
70
|
-
if idx < NUM_SOURCES {
|
|
71
|
-
return Ok(self.priority[idx] as u64);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// Pending bits: 0x001000
|
|
75
|
-
if offset == 0x001000 {
|
|
76
|
-
return Ok(self.pending as u64);
|
|
77
|
-
}
|
|
78
|
-
// Enable per context: 0x002000 + 0x80 * context
|
|
79
|
-
if offset >= 0x002000 && offset < 0x002000 + 0x80 * (NUM_CONTEXTS as u64) {
|
|
80
|
-
let ctx = ((offset - 0x002000) / 0x80) as usize;
|
|
81
|
-
let inner = (offset - 0x002000) % 0x80;
|
|
82
|
-
if ctx < NUM_CONTEXTS && inner == 0 {
|
|
83
|
-
return Ok(self.enable[ctx] as u64);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Context registers: threshold @ 0x200000 + 0x1000 * ctx, claim @ +4
|
|
87
|
-
if offset >= 0x200000 {
|
|
88
|
-
let ctx = ((offset - 0x200000) / 0x1000) as usize;
|
|
89
|
-
if ctx < NUM_CONTEXTS {
|
|
90
|
-
let base = 0x200000 + (0x1000 * ctx as u64);
|
|
91
|
-
if offset == base {
|
|
92
|
-
return Ok(self.threshold[ctx] as u64);
|
|
93
|
-
}
|
|
94
|
-
if offset == base + 4 {
|
|
95
|
-
let claim = self.claim_interrupt_for(ctx);
|
|
96
|
-
if crate::plic::Plic::debug_trace() {
|
|
97
|
-
eprintln!("[PLIC] SCLAIM ctx={} -> {}", ctx, claim);
|
|
98
|
-
}
|
|
99
|
-
return Ok(claim as u64);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
Ok(0)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
fn debug_trace() -> bool {
|
|
108
|
-
// Helper to check if trace logging is enabled without importing log everywhere if not needed
|
|
109
|
-
// or just use std::env
|
|
110
|
-
std::env::var("RUST_LOG").map(|s| s.contains("trace")).unwrap_or(false)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
pub fn store(&mut self, offset: u64, size: u64, value: u64) -> Result<(), MemoryError> {
|
|
114
|
-
if size != 4 {
|
|
115
|
-
return Ok(());
|
|
116
|
-
}
|
|
117
|
-
let val = value as u32;
|
|
118
|
-
|
|
119
|
-
// Priority
|
|
120
|
-
if offset < 0x001000 {
|
|
121
|
-
let idx = (offset >> 2) as usize;
|
|
122
|
-
if idx < NUM_SOURCES {
|
|
123
|
-
self.priority[idx] = val;
|
|
124
|
-
}
|
|
125
|
-
return Ok(());
|
|
126
|
-
}
|
|
127
|
-
// Pending is read-only to software
|
|
128
|
-
if offset == 0x001000 {
|
|
129
|
-
return Ok(());
|
|
130
|
-
}
|
|
131
|
-
// Enable per context
|
|
132
|
-
if offset >= 0x002000 && offset < 0x002000 + 0x80 * (NUM_CONTEXTS as u64) {
|
|
133
|
-
let ctx = ((offset - 0x002000) / 0x80) as usize;
|
|
134
|
-
let inner = (offset - 0x002000) % 0x80;
|
|
135
|
-
if ctx < NUM_CONTEXTS && inner == 0 {
|
|
136
|
-
self.enable[ctx] = val;
|
|
137
|
-
}
|
|
138
|
-
return Ok(());
|
|
139
|
-
}
|
|
140
|
-
// Threshold / Claim-Complete per context
|
|
141
|
-
if offset >= 0x200000 {
|
|
142
|
-
let ctx = ((offset - 0x200000) / 0x1000) as usize;
|
|
143
|
-
if ctx < NUM_CONTEXTS {
|
|
144
|
-
let base = 0x200000 + (0x1000 * ctx as u64);
|
|
145
|
-
if offset == base {
|
|
146
|
-
self.threshold[ctx] = val;
|
|
147
|
-
return Ok(());
|
|
148
|
-
}
|
|
149
|
-
if offset == base + 4 {
|
|
150
|
-
// Completion: value is the source ID to complete
|
|
151
|
-
let id = (val & 0xffff) as u32;
|
|
152
|
-
if id > 0 && (id as usize) < NUM_SOURCES {
|
|
153
|
-
// eprintln!("[PLIC] Completed IRQ {} for context {}", id, ctx);
|
|
154
|
-
self.active[ctx] &= !(1 << id);
|
|
155
|
-
}
|
|
156
|
-
return Ok(());
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
return Ok(());
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
Ok(())
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
fn eligible_for_context(&self, source: usize, ctx: usize) -> bool {
|
|
166
|
-
let pending = ((self.pending >> source) & 1) == 1;
|
|
167
|
-
let enabled = ((self.enable[ctx] >> source) & 1) == 1;
|
|
168
|
-
let over_threshold = self.priority[source] > self.threshold[ctx];
|
|
169
|
-
let not_active = ((self.active[ctx] >> source) & 1) == 0;
|
|
170
|
-
pending && enabled && over_threshold && not_active
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
pub fn claim_interrupt_for(&mut self, ctx: usize) -> u32 {
|
|
174
|
-
let mut max_prio = 0;
|
|
175
|
-
let mut max_id = 0;
|
|
176
|
-
|
|
177
|
-
for i in 1..NUM_SOURCES {
|
|
178
|
-
if self.eligible_for_context(i, ctx) {
|
|
179
|
-
let prio = self.priority[i];
|
|
180
|
-
if prio > max_prio {
|
|
181
|
-
max_prio = prio;
|
|
182
|
-
max_id = i as u32;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if max_id != 0 {
|
|
188
|
-
// eprintln!("[PLIC] Claimed IRQ {} for context {} (prio {})", max_id, ctx, max_prio);
|
|
189
|
-
// Mark in-flight for this context until completed.
|
|
190
|
-
self.active[ctx] |= 1 << max_id;
|
|
191
|
-
}
|
|
192
|
-
max_id
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
pub fn is_interrupt_pending(&self) -> bool {
|
|
196
|
-
// For current single-hart flow, report S-mode context (1) if available, else context 0.
|
|
197
|
-
let ctx = if NUM_CONTEXTS > 1 { 1 } else { 0 };
|
|
198
|
-
self.is_interrupt_pending_for(ctx)
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
pub fn is_interrupt_pending_for(&self, ctx: usize) -> bool {
|
|
202
|
-
if ctx >= NUM_CONTEXTS {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
for i in 1..NUM_SOURCES {
|
|
206
|
-
if self.eligible_for_context(i, ctx) {
|
|
207
|
-
if self.debug {
|
|
208
|
-
eprintln!("[PLIC] Interrupt pending for ctx={} source={}", ctx, i);
|
|
209
|
-
}
|
|
210
|
-
return true;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
// Debug: show why no interrupt
|
|
214
|
-
if self.debug && self.pending != 0 {
|
|
215
|
-
for i in 1..NUM_SOURCES {
|
|
216
|
-
let pending = ((self.pending >> i) & 1) == 1;
|
|
217
|
-
let enabled = ((self.enable[ctx] >> i) & 1) == 1;
|
|
218
|
-
let over_threshold = self.priority[i] > self.threshold[ctx];
|
|
219
|
-
let not_active = ((self.active[ctx] >> i) & 1) == 0;
|
|
220
|
-
if pending {
|
|
221
|
-
eprintln!("[PLIC] Source {} pending but not eligible for ctx={}: enabled={} over_threshold={} (prio={} > thresh={}) not_active={}",
|
|
222
|
-
i, ctx, enabled, over_threshold, self.priority[i], self.threshold[ctx], not_active);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
false
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
#[cfg(test)]
|
|
231
|
-
mod tests {
|
|
232
|
-
use super::*;
|
|
233
|
-
|
|
234
|
-
#[test]
|
|
235
|
-
fn test_plic_claim_complete_context1() {
|
|
236
|
-
let mut plic = Plic::new();
|
|
237
|
-
// Priorities
|
|
238
|
-
plic.priority[1] = 5;
|
|
239
|
-
plic.priority[10] = 3;
|
|
240
|
-
// Enable sources 1 and 10 for context 1 (S-mode)
|
|
241
|
-
let enable_val = (1u32 << 1) | (1u32 << 10);
|
|
242
|
-
let _ = plic.store(0x002000 + 0x80 * 1, 4, enable_val as u64);
|
|
243
|
-
// Threshold 0 for context 1
|
|
244
|
-
let _ = plic.store(0x200000 + 0x1000 * 1, 4, 0);
|
|
245
|
-
// Assert device lines
|
|
246
|
-
plic.set_source_level(1, true);
|
|
247
|
-
plic.set_source_level(10, true);
|
|
248
|
-
|
|
249
|
-
// Claim highest priority first (source 1)
|
|
250
|
-
let id1 = plic.claim_interrupt_for(1);
|
|
251
|
-
assert_eq!(id1, 1);
|
|
252
|
-
// Next claim should return source 10 (since 1 is active)
|
|
253
|
-
let id2 = plic.claim_interrupt_for(1);
|
|
254
|
-
assert_eq!(id2, 10);
|
|
255
|
-
// Complete source 1
|
|
256
|
-
let _ = plic.store(0x200004 + 0x1000 * 1, 4, 1);
|
|
257
|
-
// Claim again should allow source 1
|
|
258
|
-
let id3 = plic.claim_interrupt_for(1);
|
|
259
|
-
assert_eq!(id3, 1);
|
|
260
|
-
}
|
|
261
|
-
}
|