1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! Guest reset and virtual interrupt register controls.
use super::{Exception, GuestBinding, GuestPrivilege, Vcpu};
/// Virtual interrupt bit in HIE/HVIP, independent of host IRQ routing.
#[repr(usize)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum GuestInterrupt {
/// Virtual supervisor software interrupt.
Software = 2,
/// Virtual supervisor timer interrupt.
Timer = 6,
/// Virtual supervisor external interrupt.
External = 10,
}
impl Vcpu {
/// Installs supervisor reset controls without changing guest GPRs or memory.
pub fn initialize_supervisor(&mut self) {
// SPP=Supervisor, FS=Initial; global interrupt enables remain clear.
self.guest_regs.sstatus = (1 << 8) | (1 << 13);
// SPV, SPVP, VSXL=64; guest privileged instructions are not intercepted.
self.guest_regs.hstatus = (1 << 7) | (1 << 8) | (2 << 32);
self.virtual_hs_csrs.hie = (1 << 2) | (1 << 6) | (1 << 10);
self.virtual_hs_csrs.hvip = 0;
self.virtual_hs_csrs.hgeie = 0;
#[cfg(feature = "riscv-sstc")]
{
self.vs_csrs.vstimecmp = usize::MAX;
}
}
/// Updates one pending virtual interrupt in the uninstalled register image.
/// The active binding must explicitly synchronize the image after this call.
pub fn set_interrupt_pending(&mut self, interrupt: GuestInterrupt, pending: bool) {
let bit = 1usize << interrupt as usize;
if pending {
self.virtual_hs_csrs.hvip |= bit;
} else {
self.virtual_hs_csrs.hvip &= !bit;
}
}
/// Captures host-raised virtual interrupt bits for subsequent guest binding.
///
/// # Safety
/// The caller owns the current hart's HVIP bank and has established that
/// every pending bit belongs to this guest. It must exclude migration and
/// concurrent changes while attributing and capturing the pending state.
pub unsafe fn latch_interrupts(&mut self) {
let pending: usize;
// SAFETY: caller owns this hart's guest interrupt attribution.
unsafe {
core::arch::asm!("csrr {}, hvip", out(reg) pending, options(nostack));
}
self.virtual_hs_csrs.hvip |= pending & ((1 << 2) | (1 << 6) | (1 << 10));
}
}
impl GuestBinding {
/// Synchronizes one pending bit without overwriting other hardware events.
/// The binding's CPU pin and exclusive ownership must remain active.
pub fn sync_interrupt(&mut self, registers: &Vcpu, interrupt: GuestInterrupt) {
let bit = 1usize << interrupt as usize;
// SAFETY: this live binding owns the current hart. CSR set/clear touches
// only the selected source, preserving independently raised pending bits.
unsafe {
if registers.virtual_hs_csrs.hvip & bit != 0 {
core::arch::asm!("csrs hvip, {}", in(reg) bit, options(nostack));
} else {
core::arch::asm!("csrc hvip, {}", in(reg) bit, options(nostack));
}
}
}
/// Programs the bound virtual supervisor comparator and its saved image.
#[cfg(feature = "riscv-sstc")]
pub fn set_timer_compare(&mut self, registers: &mut Vcpu, deadline: usize) {
let _irqs = super::binding::LocalIrqs::disable();
registers.vs_csrs.vstimecmp = deadline;
// SAFETY: successful binding validated STCE and owns this hart's timer.
unsafe {
core::arch::asm!("csrw vstimecmp, {}", in(reg) deadline, options(nostack));
}
}
}
impl Vcpu {
/// Returns the privilege captured on the most recent guest exit.
pub const fn guest_privilege(&self) -> GuestPrivilege {
if self.guest_regs.hstatus & (1 << 8) != 0 {
GuestPrivilege::Supervisor
} else {
GuestPrivilege::User
}
}
}
impl GuestBinding {
/// Delivers a synchronous exception through the guest's current VS vector.
/// Updates the installed bank and saved image together before reentry.
pub fn inject_exception(
&mut self,
registers: &mut Vcpu,
exception: Exception,
address: crate::virtualization::GuestVirtAddr,
) {
let _irqs = super::binding::LocalIrqs::disable();
let mut status: usize;
let vector: usize;
// SAFETY: this binding owns the current hart and VS register bank.
unsafe {
core::arch::asm!("csrr {}, vsstatus", "csrr {}, vstvec", out(reg) status, out(reg) vector, options(nostack));
}
let saved_ie = (status >> 1) & 1;
status = (status & !((1 << 1) | (1 << 5) | (1 << 8)))
| (saved_ie << 5)
| (registers.guest_regs.hstatus & (1 << 8));
registers.vs_csrs.vstvec = vector;
registers.vs_csrs.vsepc = registers.guest_regs.sepc;
registers.vs_csrs.vscause = exception as usize;
registers.vs_csrs.vstval = address.as_usize();
registers.vs_csrs.vsstatus = status;
registers.guest_regs.sepc = vector & !3;
// SAFETY: values are the architectural VS synchronous trap transition;
// host SSTATUS and host vector are not modified.
unsafe {
core::arch::asm!("csrw vsstatus, {status}", "csrw vscause, {cause}",
"csrw vstval, {address}", "csrw vsepc, {pc}",
status = in(reg) status, cause = in(reg) exception as usize,
address = in(reg) address.as_usize(), pc = in(reg) registers.vs_csrs.vsepc,
options(nostack));
}
}
}