virtual-machine 0.0.4 → 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/src/bus.rs DELETED
@@ -1,558 +0,0 @@
1
- use crate::clint::{Clint, CLINT_BASE, CLINT_SIZE};
2
- use crate::plic::{Plic, PLIC_BASE, PLIC_SIZE};
3
- use crate::uart::{Uart, UART_BASE, UART_SIZE};
4
- use crate::virtio::VirtioDevice;
5
- use crate::Trap;
6
- use crate::dram::Dram;
7
-
8
- /// Default DRAM base for the virt platform.
9
- pub const DRAM_BASE: u64 = 0x8000_0000;
10
-
11
- /// Base address of the RISC-V test finisher MMIO region.
12
- pub const TEST_FINISHER_BASE: u64 = 0x0010_0000;
13
- pub const TEST_FINISHER_SIZE: u64 = 0x1000;
14
-
15
- /// VirtIO MMIO base address (for the first device).
16
- pub const VIRTIO_BASE: u64 = 0x1000_1000;
17
- /// Size of each VirtIO MMIO region.
18
- pub const VIRTIO_STRIDE: u64 = 0x1000;
19
-
20
- pub trait Bus {
21
- fn read8(&mut self, addr: u64) -> Result<u8, Trap>;
22
- fn read16(&mut self, addr: u64) -> Result<u16, Trap>;
23
- fn read32(&mut self, addr: u64) -> Result<u32, Trap>;
24
- fn read64(&mut self, addr: u64) -> Result<u64, Trap>;
25
-
26
- fn write8(&mut self, addr: u64, val: u8) -> Result<(), Trap>;
27
- fn write16(&mut self, addr: u64, val: u16) -> Result<(), Trap>;
28
- fn write32(&mut self, addr: u64, val: u32) -> Result<(), Trap>;
29
- fn write64(&mut self, addr: u64, val: u64) -> Result<(), Trap>;
30
-
31
- /// Generic load helper used by the MMU for page-table walks.
32
- fn load(&mut self, addr: u64, size: u64) -> Result<u64, Trap> {
33
- match size {
34
- 1 => self.read8(addr).map(|v| v as u64),
35
- 2 => self.read16(addr).map(|v| v as u64),
36
- 4 => self.read32(addr).map(|v| v as u64),
37
- 8 => self.read64(addr),
38
- _ => Err(Trap::Fatal(format!("Unsupported bus load size: {}", size))),
39
- }
40
- }
41
-
42
- /// Generic store helper used by the MMU for page-table A/D updates.
43
- fn store(&mut self, addr: u64, size: u64, value: u64) -> Result<(), Trap> {
44
- match size {
45
- 1 => self.write8(addr, value as u8),
46
- 2 => self.write16(addr, value as u16),
47
- 4 => self.write32(addr, value as u32),
48
- 8 => self.write64(addr, value),
49
- _ => Err(Trap::Fatal(format!("Unsupported bus store size: {}", size))),
50
- }
51
- }
52
-
53
- fn fetch_u32(&mut self, addr: u64) -> Result<u32, Trap> {
54
- if addr % 4 != 0 {
55
- return Err(Trap::InstructionAddressMisaligned(addr));
56
- }
57
- // Map LoadAccessFault to InstructionAccessFault for fetch
58
- self.read32(addr).map_err(|e| match e {
59
- Trap::LoadAccessFault(a) => Trap::InstructionAccessFault(a),
60
- Trap::LoadAddressMisaligned(a) => Trap::InstructionAddressMisaligned(a),
61
- _ => e,
62
- })
63
- }
64
-
65
- fn poll_interrupts(&mut self) -> u64 {
66
- 0
67
- }
68
- }
69
-
70
- // A simple system bus that just wraps DRAM for now (Phase 1)
71
- pub struct SystemBus {
72
- pub dram: Dram,
73
- pub clint: Clint,
74
- pub plic: Plic,
75
- pub uart: Uart,
76
- pub virtio_devices: Vec<Box<dyn VirtioDevice>>,
77
- }
78
-
79
- impl SystemBus {
80
- pub fn new(dram_base: u64, dram_size: usize) -> Self {
81
- Self {
82
- dram: Dram::new(dram_base, dram_size),
83
- clint: Clint::new(),
84
- plic: Plic::new(),
85
- uart: Uart::new(),
86
- virtio_devices: Vec::new(),
87
- }
88
- }
89
-
90
- pub fn dram_base(&self) -> u64 {
91
- self.dram.base
92
- }
93
-
94
- pub fn dram_size(&self) -> usize {
95
- self.dram.data.len()
96
- }
97
-
98
- pub fn check_interrupts(&mut self) -> u64 {
99
- // 0. Advance CLINT timer each step
100
- self.clint.tick();
101
-
102
- // 1. Update PLIC with UART status
103
- let uart_irq = self.uart.interrupting;
104
- self.plic.set_source_level(crate::plic::UART_IRQ, uart_irq);
105
-
106
- // 1b. Update PLIC with VirtIO interrupts
107
- // We map VirtIO devices to IRQs starting at VIRTIO0_IRQ (1).
108
- // Device 0 -> IRQ 1
109
- // Device 1 -> IRQ 2
110
- // ...
111
- // Device 7 -> IRQ 8
112
- // Note: xv6 expects VIRTIO0 at IRQ 1.
113
- for (i, dev) in self.virtio_devices.iter().enumerate() {
114
- let irq = crate::plic::VIRTIO0_IRQ + i as u32;
115
- if irq < 32 { // PLIC limit
116
- let intr = dev.is_interrupting();
117
- if intr && log::log_enabled!(log::Level::Trace) {
118
- log::trace!("[Bus] VirtIO dev {} interrupting (irq {})", i, irq);
119
- }
120
- self.plic.set_source_level(irq, intr);
121
- }
122
- }
123
-
124
- // 2. Calculate MIP bits
125
- let mut mip = 0;
126
-
127
- // MSIP (Machine Software Interrupt) - Bit 3
128
- if self.clint.msip[0] & 1 != 0 {
129
- mip |= 1 << 3;
130
- }
131
-
132
- // MTIP (Machine Timer Interrupt) - Bit 7
133
- if self.clint.mtime >= self.clint.mtimecmp[0] {
134
- mip |= 1 << 7;
135
- }
136
-
137
- // SEIP (Supervisor External Interrupt) - Bit 9
138
- if self.plic.is_interrupt_pending_for(1) {
139
- mip |= 1 << 9;
140
- }
141
-
142
- // MEIP (Machine External Interrupt) - Bit 11
143
- if self.plic.is_interrupt_pending_for(0) {
144
- mip |= 1 << 11;
145
- }
146
-
147
- mip
148
- }
149
-
150
- fn get_virtio_device(&mut self, addr: u64) -> Option<(usize, u64)> {
151
- if addr >= VIRTIO_BASE {
152
- let offset = addr - VIRTIO_BASE;
153
- let idx = (offset / VIRTIO_STRIDE) as usize;
154
- if idx < self.virtio_devices.len() {
155
- return Some((idx, offset % VIRTIO_STRIDE));
156
- }
157
- }
158
- None
159
- }
160
-
161
- /// Check if an address is in the VirtIO MMIO region (even if no device present).
162
- /// Returns the offset within the device region if in range.
163
- fn is_virtio_region(&self, addr: u64) -> Option<u64> {
164
- if addr >= VIRTIO_BASE && addr < VIRTIO_BASE + VIRTIO_STRIDE * 8 {
165
- Some((addr - VIRTIO_BASE) % VIRTIO_STRIDE)
166
- } else {
167
- None
168
- }
169
- }
170
-
171
- /// Poll all VirtIO devices for pending work (e.g., incoming network packets).
172
- /// Should be called periodically from the main emulation loop.
173
- pub fn poll_virtio(&mut self) {
174
- for device in &mut self.virtio_devices {
175
- if let Err(e) = device.poll(&mut self.dram) {
176
- log::warn!("[Bus] VirtIO poll error: {:?}", e);
177
- }
178
- }
179
- }
180
- }
181
-
182
- impl Bus for SystemBus {
183
- fn poll_interrupts(&mut self) -> u64 {
184
- self.check_interrupts()
185
- }
186
-
187
- fn read8(&mut self, addr: u64) -> Result<u8, Trap> {
188
- // Test finisher region: reads are harmless and return zero.
189
- if addr >= TEST_FINISHER_BASE && addr < TEST_FINISHER_BASE + TEST_FINISHER_SIZE {
190
- return Ok(0);
191
- }
192
-
193
- if let Some(off) = self.dram.offset(addr) {
194
- return Ok(self.dram.data[off]);
195
- }
196
-
197
- if addr >= CLINT_BASE && addr < CLINT_BASE + CLINT_SIZE {
198
- let offset = addr - CLINT_BASE;
199
- let val = self.clint.load(offset, 1);
200
- return Ok(val as u8);
201
- }
202
-
203
- if addr >= PLIC_BASE && addr < PLIC_BASE + PLIC_SIZE {
204
- let offset = addr - PLIC_BASE;
205
- let val = self.plic.load(offset, 1).map_err(|_| Trap::LoadAccessFault(addr))?;
206
- return Ok(val as u8);
207
- }
208
-
209
- if addr >= UART_BASE && addr < UART_BASE + UART_SIZE {
210
- let offset = addr - UART_BASE;
211
- let val = self.uart.load(offset, 1).map_err(|_| Trap::LoadAccessFault(addr))?;
212
- return Ok(val as u8);
213
- }
214
-
215
- if let Some((idx, offset)) = self.get_virtio_device(addr) {
216
- // Emulate narrow MMIO reads by extracting from the 32-bit register value
217
- let aligned = offset & !3;
218
- let word = self.virtio_devices[idx].read(aligned).map_err(|_| Trap::LoadAccessFault(addr))?;
219
- let shift = ((offset & 3) * 8) as u64;
220
- return Ok(((word >> shift) & 0xff) as u8);
221
- }
222
-
223
- // Unmapped VirtIO slots return 0 (allows safe probing)
224
- if self.is_virtio_region(addr).is_some() {
225
- return Ok(0);
226
- }
227
-
228
- Err(Trap::LoadAccessFault(addr))
229
- }
230
-
231
- fn read16(&mut self, addr: u64) -> Result<u16, Trap> {
232
- if addr % 2 != 0 {
233
- return Err(Trap::LoadAddressMisaligned(addr));
234
- }
235
-
236
- if addr >= TEST_FINISHER_BASE && addr < TEST_FINISHER_BASE + TEST_FINISHER_SIZE {
237
- return Ok(0);
238
- }
239
-
240
- if let Some(off) = self.dram.offset(addr) {
241
- if off + 2 > self.dram.data.len() {
242
- return Err(Trap::LoadAccessFault(addr));
243
- }
244
- let bytes = &self.dram.data[off..off + 2];
245
- return Ok(u16::from_le_bytes(bytes.try_into().unwrap()));
246
- }
247
-
248
- if addr >= CLINT_BASE && addr < CLINT_BASE + CLINT_SIZE {
249
- let offset = addr - CLINT_BASE;
250
- let val = self.clint.load(offset, 2);
251
- return Ok(val as u16);
252
- }
253
-
254
- if addr >= PLIC_BASE && addr < PLIC_BASE + PLIC_SIZE {
255
- let offset = addr - PLIC_BASE;
256
- let val = self.plic.load(offset, 2).map_err(|_| Trap::LoadAccessFault(addr))?;
257
- return Ok(val as u16);
258
- }
259
-
260
- if addr >= UART_BASE && addr < UART_BASE + UART_SIZE {
261
- let offset = addr - UART_BASE;
262
- let val = self.uart.load(offset, 2).map_err(|_| Trap::LoadAccessFault(addr))?;
263
- return Ok(val as u16);
264
- }
265
-
266
- if let Some((idx, offset)) = self.get_virtio_device(addr) {
267
- let aligned = offset & !3;
268
- let word = self.virtio_devices[idx].read(aligned).map_err(|_| Trap::LoadAccessFault(addr))?;
269
- let shift = ((offset & 3) * 8) as u64;
270
- return Ok(((word >> shift) & 0xffff) as u16);
271
- }
272
-
273
- // Unmapped VirtIO slots return 0 (allows safe probing)
274
- if self.is_virtio_region(addr).is_some() {
275
- return Ok(0);
276
- }
277
-
278
- Err(Trap::LoadAccessFault(addr))
279
- }
280
-
281
- fn read32(&mut self, addr: u64) -> Result<u32, Trap> {
282
- if addr % 4 != 0 {
283
- return Err(Trap::LoadAddressMisaligned(addr));
284
- }
285
-
286
- if addr >= TEST_FINISHER_BASE && addr < TEST_FINISHER_BASE + TEST_FINISHER_SIZE {
287
- return Ok(0);
288
- }
289
-
290
- if let Some(off) = self.dram.offset(addr) {
291
- if off + 4 > self.dram.data.len() {
292
- return Err(Trap::LoadAccessFault(addr));
293
- }
294
- let bytes = &self.dram.data[off..off + 4];
295
- return Ok(u32::from_le_bytes(bytes.try_into().unwrap()));
296
- }
297
-
298
- if addr >= CLINT_BASE && addr < CLINT_BASE + CLINT_SIZE {
299
- let offset = addr - CLINT_BASE;
300
- let val = self.clint.load(offset, 4);
301
- return Ok(val as u32);
302
- }
303
-
304
- if addr >= PLIC_BASE && addr < PLIC_BASE + PLIC_SIZE {
305
- let offset = addr - PLIC_BASE;
306
- let val = self.plic.load(offset, 4).map_err(|_| Trap::LoadAccessFault(addr))?;
307
- return Ok(val as u32);
308
- }
309
-
310
- if addr >= UART_BASE && addr < UART_BASE + UART_SIZE {
311
- let offset = addr - UART_BASE;
312
- let val = self.uart.load(offset, 4).map_err(|_| Trap::LoadAccessFault(addr))?;
313
- return Ok(val as u32);
314
- }
315
-
316
- if let Some((idx, offset)) = self.get_virtio_device(addr) {
317
- let val = self.virtio_devices[idx].read(offset).map_err(|_| Trap::LoadAccessFault(addr))?;
318
- return Ok(val as u32);
319
- }
320
-
321
- // Unmapped VirtIO slots return 0 (allows safe probing)
322
- if self.is_virtio_region(addr).is_some() {
323
- return Ok(0);
324
- }
325
-
326
- Err(Trap::LoadAccessFault(addr))
327
- }
328
-
329
- fn read64(&mut self, addr: u64) -> Result<u64, Trap> {
330
- if addr % 8 != 0 {
331
- return Err(Trap::LoadAddressMisaligned(addr));
332
- }
333
-
334
- if addr >= TEST_FINISHER_BASE && addr < TEST_FINISHER_BASE + TEST_FINISHER_SIZE {
335
- return Ok(0);
336
- }
337
-
338
- if let Some(off) = self.dram.offset(addr) {
339
- if off + 8 > self.dram.data.len() {
340
- return Err(Trap::LoadAccessFault(addr));
341
- }
342
- let bytes = &self.dram.data[off..off + 8];
343
- return Ok(u64::from_le_bytes(bytes.try_into().unwrap()));
344
- }
345
-
346
- if addr >= CLINT_BASE && addr < CLINT_BASE + CLINT_SIZE {
347
- let offset = addr - CLINT_BASE;
348
- let val = self.clint.load(offset, 8);
349
- return Ok(val);
350
- }
351
-
352
- if addr >= PLIC_BASE && addr < PLIC_BASE + PLIC_SIZE {
353
- let offset = addr - PLIC_BASE;
354
- let val = self.plic.load(offset, 8).map_err(|_| Trap::LoadAccessFault(addr))?;
355
- return Ok(val);
356
- }
357
-
358
- if addr >= UART_BASE && addr < UART_BASE + UART_SIZE {
359
- let offset = addr - UART_BASE;
360
- let val = self.uart.load(offset, 8).map_err(|_| Trap::LoadAccessFault(addr))?;
361
- return Ok(val);
362
- }
363
-
364
- if let Some((idx, offset)) = self.get_virtio_device(addr) {
365
- let low = self.virtio_devices[idx].read(offset).map_err(|_| Trap::LoadAccessFault(addr))?;
366
- let high = self.virtio_devices[idx].read(offset + 4).map_err(|_| Trap::LoadAccessFault(addr + 4))?;
367
- return Ok((low as u64) | ((high as u64) << 32));
368
- }
369
-
370
- // Unmapped VirtIO slots return 0 (allows safe probing)
371
- if self.is_virtio_region(addr).is_some() {
372
- return Ok(0);
373
- }
374
-
375
- Err(Trap::LoadAccessFault(addr))
376
- }
377
-
378
- fn write8(&mut self, addr: u64, val: u8) -> Result<(), Trap> {
379
- // Any write in the test finisher region signals a requested trap to the host.
380
- if addr >= TEST_FINISHER_BASE && addr < TEST_FINISHER_BASE + TEST_FINISHER_SIZE {
381
- return Err(Trap::RequestedTrap(val as u64));
382
- }
383
-
384
- if let Some(off) = self.dram.offset(addr) {
385
- self.dram.data[off] = val;
386
- return Ok(());
387
- }
388
-
389
- if addr >= CLINT_BASE && addr < CLINT_BASE + CLINT_SIZE {
390
- let offset = addr - CLINT_BASE;
391
- self.clint.store(offset, 1, val as u64);
392
- return Ok(());
393
- }
394
-
395
- if addr >= PLIC_BASE && addr < PLIC_BASE + PLIC_SIZE {
396
- let offset = addr - PLIC_BASE;
397
- self.plic.store(offset, 1, val as u64).map_err(|_| Trap::StoreAccessFault(addr))?;
398
- return Ok(());
399
- }
400
-
401
- if addr >= UART_BASE && addr < UART_BASE + UART_SIZE {
402
- let offset = addr - UART_BASE;
403
- self.uart.store(offset, 1, val as u64).map_err(|_| Trap::StoreAccessFault(addr))?;
404
- return Ok(());
405
- }
406
-
407
- if let Some((_idx, _offset)) = self.get_virtio_device(addr) {
408
- // VirtIO registers are 32-bit. Byte writes are not strictly supported by the spec for all registers.
409
- // We ignore them for now to be safe.
410
- return Ok(());
411
- }
412
-
413
- Err(Trap::StoreAccessFault(addr))
414
- }
415
-
416
- fn write16(&mut self, addr: u64, val: u16) -> Result<(), Trap> {
417
- if addr % 2 != 0 {
418
- return Err(Trap::StoreAddressMisaligned(addr));
419
- }
420
-
421
- if addr >= TEST_FINISHER_BASE && addr < TEST_FINISHER_BASE + TEST_FINISHER_SIZE {
422
- return Err(Trap::RequestedTrap(val as u64));
423
- }
424
-
425
- if let Some(off) = self.dram.offset(addr) {
426
- if off + 2 > self.dram.data.len() {
427
- return Err(Trap::StoreAccessFault(addr));
428
- }
429
- let bytes = val.to_le_bytes();
430
- self.dram.data[off..off + 2].copy_from_slice(&bytes);
431
- return Ok(());
432
- }
433
-
434
- if addr >= CLINT_BASE && addr < CLINT_BASE + CLINT_SIZE {
435
- let offset = addr - CLINT_BASE;
436
- self.clint.store(offset, 2, val as u64);
437
- return Ok(());
438
- }
439
-
440
- if addr >= PLIC_BASE && addr < PLIC_BASE + PLIC_SIZE {
441
- let offset = addr - PLIC_BASE;
442
- self.plic.store(offset, 2, val as u64).map_err(|_| Trap::StoreAccessFault(addr))?;
443
- return Ok(());
444
- }
445
-
446
- if addr >= UART_BASE && addr < UART_BASE + UART_SIZE {
447
- let offset = addr - UART_BASE;
448
- self.uart.store(offset, 2, val as u64).map_err(|_| Trap::StoreAccessFault(addr))?;
449
- return Ok(());
450
- }
451
-
452
- if let Some((_idx, _offset)) = self.get_virtio_device(addr) {
453
- return Ok(());
454
- }
455
-
456
- Err(Trap::StoreAccessFault(addr))
457
- }
458
-
459
- fn write32(&mut self, addr: u64, val: u32) -> Result<(), Trap> {
460
- if addr % 4 != 0 {
461
- return Err(Trap::StoreAddressMisaligned(addr));
462
- }
463
-
464
- if addr >= TEST_FINISHER_BASE && addr < TEST_FINISHER_BASE + TEST_FINISHER_SIZE {
465
- return Err(Trap::RequestedTrap(val as u64));
466
- }
467
-
468
- if let Some(off) = self.dram.offset(addr) {
469
- if off + 4 > self.dram.data.len() {
470
- return Err(Trap::StoreAccessFault(addr));
471
- }
472
- let bytes = val.to_le_bytes();
473
- self.dram.data[off..off + 4].copy_from_slice(&bytes);
474
- return Ok(());
475
- }
476
-
477
- if addr >= CLINT_BASE && addr < CLINT_BASE + CLINT_SIZE {
478
- let offset = addr - CLINT_BASE;
479
- self.clint.store(offset, 4, val as u64);
480
- return Ok(());
481
- }
482
-
483
- if addr >= PLIC_BASE && addr < PLIC_BASE + PLIC_SIZE {
484
- let offset = addr - PLIC_BASE;
485
- self.plic.store(offset, 4, val as u64).map_err(|_| Trap::StoreAccessFault(addr))?;
486
- return Ok(());
487
- }
488
-
489
- if addr >= UART_BASE && addr < UART_BASE + UART_SIZE {
490
- let offset = addr - UART_BASE;
491
- self.uart.store(offset, 4, val as u64).map_err(|_| Trap::StoreAccessFault(addr))?;
492
- return Ok(());
493
- }
494
-
495
- if let Some((idx, offset)) = self.get_virtio_device(addr) {
496
- self.virtio_devices[idx].write(offset, val as u64, &mut self.dram)
497
- .map_err(|_| Trap::StoreAccessFault(addr))?;
498
- return Ok(());
499
- }
500
-
501
- // Writes to unmapped VirtIO slots are silently ignored (allows safe probing)
502
- if self.is_virtio_region(addr).is_some() {
503
- return Ok(());
504
- }
505
-
506
- Err(Trap::StoreAccessFault(addr))
507
- }
508
-
509
- fn write64(&mut self, addr: u64, val: u64) -> Result<(), Trap> {
510
- if addr % 8 != 0 {
511
- return Err(Trap::StoreAddressMisaligned(addr));
512
- }
513
-
514
- if addr >= TEST_FINISHER_BASE && addr < TEST_FINISHER_BASE + TEST_FINISHER_SIZE {
515
- return Err(Trap::RequestedTrap(val));
516
- }
517
-
518
- if let Some(off) = self.dram.offset(addr) {
519
- if off + 8 > self.dram.data.len() {
520
- return Err(Trap::StoreAccessFault(addr));
521
- }
522
- let bytes = val.to_le_bytes();
523
- self.dram.data[off..off + 8].copy_from_slice(&bytes);
524
- return Ok(());
525
- }
526
-
527
- if addr >= CLINT_BASE && addr < CLINT_BASE + CLINT_SIZE {
528
- let offset = addr - CLINT_BASE;
529
- self.clint.store(offset, 8, val);
530
- return Ok(());
531
- }
532
-
533
- if addr >= PLIC_BASE && addr < PLIC_BASE + PLIC_SIZE {
534
- let offset = addr - PLIC_BASE;
535
- self.plic.store(offset, 8, val).map_err(|_| Trap::StoreAccessFault(addr))?;
536
- return Ok(());
537
- }
538
-
539
- if addr >= UART_BASE && addr < UART_BASE + UART_SIZE {
540
- let offset = addr - UART_BASE;
541
- self.uart.store(offset, 8, val).map_err(|_| Trap::StoreAccessFault(addr))?;
542
- return Ok(());
543
- }
544
-
545
- if let Some((_idx, _offset)) = self.get_virtio_device(addr) {
546
- // VirtIO registers are 32-bit. 64-bit writes are not typically supported directly via MMIO
547
- // except for legacy queue PFN which is 32-bit anyway.
548
- return Ok(());
549
- }
550
-
551
- // Writes to unmapped VirtIO slots are silently ignored (allows safe probing)
552
- if self.is_virtio_region(addr).is_some() {
553
- return Ok(());
554
- }
555
-
556
- Err(Trap::StoreAccessFault(addr))
557
- }
558
- }
package/src/clint.rs DELETED
@@ -1,132 +0,0 @@
1
- pub const CLINT_BASE: u64 = 0x0200_0000;
2
- pub const CLINT_SIZE: u64 = 0x10000;
3
-
4
- pub const MSIP_OFFSET: u64 = 0x0000;
5
- pub const MTIME_OFFSET: u64 = 0xbff8;
6
- pub const MTIMECMP_OFFSET: u64 = 0x4000;
7
-
8
- pub const MAX_HARTS: usize = 8;
9
-
10
- /// Time increment per CPU step (in timer ticks).
11
- /// At 10MHz and ~1 instruction per cycle at ~10MHz CPU, this gives roughly real-time.
12
- /// Adjust for desired timer granularity.
13
- const MTIME_INCREMENT: u64 = 1;
14
-
15
- pub struct Clint {
16
- pub msip: [u32; MAX_HARTS],
17
- pub mtimecmp: [u64; MAX_HARTS],
18
- /// Machine timer counter. Incremented by `tick()` each CPU step.
19
- pub mtime: u64,
20
- pub debug: bool,
21
- }
22
-
23
- impl Clint {
24
- pub fn new() -> Self {
25
- Self {
26
- msip: [0; MAX_HARTS],
27
- mtimecmp: [u64::MAX; MAX_HARTS],
28
- mtime: 0,
29
- debug: false,
30
- }
31
- }
32
-
33
- /// Returns the current mtime value.
34
- pub fn mtime(&self) -> u64 {
35
- self.mtime
36
- }
37
-
38
- /// Sets mtime to a specific value (used for snapshot restore).
39
- pub fn set_mtime(&mut self, val: u64) {
40
- self.mtime = val;
41
- }
42
-
43
- /// Advance mtime by one tick. Called once per CPU step.
44
- pub fn tick(&mut self) {
45
- self.mtime = self.mtime.wrapping_add(MTIME_INCREMENT);
46
- }
47
-
48
- /// Backward compatibility: increment is now tick()
49
- pub fn increment(&mut self) {
50
- self.tick();
51
- }
52
-
53
- pub fn sync_time_micros(&mut self, _micros: u64) {
54
- // No-op for deterministic timer
55
- }
56
-
57
- /// Load from the CLINT register space.
58
- ///
59
- /// Offsets are relative to `CLINT_BASE`. Only naturally aligned 4- and
60
- /// 8-byte accesses are architecturally meaningful; other sizes return 0.
61
- pub fn load(&self, offset: u64, size: u64) -> u64 {
62
- match (offset, size) {
63
- // MSIP[hart], 32-bit
64
- (o, 4) if o >= MSIP_OFFSET && o < MSIP_OFFSET + (MAX_HARTS as u64 * 4) => {
65
- let hart_idx = ((o - MSIP_OFFSET) / 4) as usize;
66
- self.msip[hart_idx] as u64
67
- }
68
-
69
- // MTIME, 64-bit
70
- (MTIME_OFFSET, 8) => self.mtime(),
71
- // MTIME, low/high 32-bit words
72
- (MTIME_OFFSET, 4) => self.mtime() & 0xffff_ffff,
73
- (o, 4) if o == MTIME_OFFSET + 4 => self.mtime() >> 32,
74
-
75
- // MTIMECMP[hart], 64-bit and split 32-bit accesses
76
- (o, 8) if o >= MTIMECMP_OFFSET && o < MTIMECMP_OFFSET + (MAX_HARTS as u64 * 8) => {
77
- let hart_idx = ((o - MTIMECMP_OFFSET) / 8) as usize;
78
- self.mtimecmp[hart_idx]
79
- }
80
- (o, 4) if o >= MTIMECMP_OFFSET && o < MTIMECMP_OFFSET + (MAX_HARTS as u64 * 8) => {
81
- let hart_idx = ((o - MTIMECMP_OFFSET) / 8) as usize;
82
- let sub = (o - MTIMECMP_OFFSET) % 8;
83
- let val = self.mtimecmp[hart_idx];
84
- match sub {
85
- 0 => val & 0xffff_ffff,
86
- 4 => val >> 32,
87
- _ => 0,
88
- }
89
- }
90
-
91
- // Other offsets/sizes are reserved -> read as zero.
92
- _ => 0,
93
- }
94
- }
95
-
96
- /// Store into the CLINT register space.
97
- ///
98
- /// Offsets are relative to `CLINT_BASE`. Mis-sized or strange offsets are
99
- /// ignored to keep the device side-effect free for unsupported accesses.
100
- pub fn store(&mut self, offset: u64, size: u64, value: u64) {
101
- match (offset, size) {
102
- // MSIP[hart], 32-bit
103
- (o, 4) if o >= MSIP_OFFSET && o < MSIP_OFFSET + (MAX_HARTS as u64 * 4) => {
104
- let hart_idx = ((o - MSIP_OFFSET) / 4) as usize;
105
- // Only the LSB matters for MSIP
106
- self.msip[hart_idx] = (value & 1) as u32;
107
- }
108
-
109
- // MTIME is read-only in this implementation (driven by wall clock)
110
- (MTIME_OFFSET, _) => {}
111
- (o, 4) if o == MTIME_OFFSET + 4 => {}
112
-
113
- // MTIMECMP[hart], 64-bit and split 32-bit writes
114
- (o, 8) if o >= MTIMECMP_OFFSET && o < MTIMECMP_OFFSET + (MAX_HARTS as u64 * 8) => {
115
- let hart_idx = ((o - MTIMECMP_OFFSET) / 8) as usize;
116
- self.mtimecmp[hart_idx] = value;
117
- }
118
- (o, 4) if o >= MTIMECMP_OFFSET && o < MTIMECMP_OFFSET + (MAX_HARTS as u64 * 8) => {
119
- let hart_idx = ((o - MTIMECMP_OFFSET) / 8) as usize;
120
- let sub = (o - MTIMECMP_OFFSET) % 8;
121
- let current = self.mtimecmp[hart_idx];
122
- self.mtimecmp[hart_idx] = match sub {
123
- 0 => (current & 0xffff_ffff_0000_0000) | (value & 0xffff_ffff),
124
- 4 => (current & 0x0000_0000_ffff_ffff) | (value << 32),
125
- _ => current,
126
- };
127
- }
128
-
129
- _ => {}
130
- }
131
- }
132
- }