use kvm_bindings::kvm_lapic_state;
use kvm_ioctls::VcpuFd;
#[derive(Debug)]
pub enum Error {
GetLapic(kvm_ioctls::Error),
SetLapic(kvm_ioctls::Error),
}
type Result<T> = std::result::Result<T, Error>;
const APIC_LVT0: usize = 0x350;
const APIC_LVT1: usize = 0x360;
const APIC_MODE_NMI: u32 = 0x4;
const APIC_MODE_EXTINT: u32 = 0x7;
fn get_klapic_reg(klapic: &kvm_lapic_state, reg_offset: usize) -> u32 {
let range = reg_offset..reg_offset + 4;
let reg = klapic.regs.get(range).expect("get_klapic_reg range");
let mut reg_bytes = [0u8; 4];
for (byte, read) in reg_bytes.iter_mut().zip(reg.iter().cloned()) {
*byte = read as u8;
}
u32::from_le_bytes(reg_bytes)
}
fn set_klapic_reg(klapic: &mut kvm_lapic_state, reg_offset: usize, value: u32) {
let range = reg_offset..reg_offset + 4;
let reg = klapic.regs.get_mut(range).expect("set_klapic_reg range");
let value = u32::to_le_bytes(value);
for (byte, read) in reg.iter_mut().zip(value.iter().cloned()) {
*byte = read as i8;
}
}
#[allow(unused_parens)]
fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
(((reg) & !0x700) | ((mode) << 8))
}
pub fn set_lint(vcpu: &VcpuFd) -> Result<()> {
let mut klapic = vcpu.get_lapic().map_err(Error::GetLapic)?;
let lvt_lint0 = get_klapic_reg(&klapic, APIC_LVT0);
set_klapic_reg(
&mut klapic,
APIC_LVT0,
set_apic_delivery_mode(lvt_lint0, APIC_MODE_EXTINT),
);
let lvt_lint1 = get_klapic_reg(&klapic, APIC_LVT1);
set_klapic_reg(
&mut klapic,
APIC_LVT1,
set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI),
);
vcpu.set_lapic(&klapic).map_err(Error::SetLapic)
}
#[cfg(test)]
mod tests {
use super::*;
use kvm_ioctls::Kvm;
const KVM_APIC_REG_SIZE: usize = 0x400;
#[test]
fn test_set_and_get_klapic_reg() {
let reg_offset = 0x340;
let mut klapic = kvm_lapic_state::default();
set_klapic_reg(&mut klapic, reg_offset, 3);
let value = get_klapic_reg(&klapic, reg_offset);
assert_eq!(value, 3);
}
#[test]
#[should_panic]
fn test_set_and_get_klapic_out_of_bounds() {
let reg_offset = KVM_APIC_REG_SIZE + 10;
let mut klapic = kvm_lapic_state::default();
set_klapic_reg(&mut klapic, reg_offset, 3);
}
#[test]
fn test_setlint() {
let kvm = Kvm::new().unwrap();
assert!(kvm.check_extension(kvm_ioctls::Cap::Irqchip));
let vm = kvm.create_vm().unwrap();
assert!(vm.create_irq_chip().is_ok());
let vcpu = vm.create_vcpu(0).unwrap();
let klapic_before: kvm_lapic_state = vcpu.get_lapic().unwrap();
let lint0 = get_klapic_reg(&klapic_before, APIC_LVT0);
let lint1 = get_klapic_reg(&klapic_before, APIC_LVT1);
let lint0_mode_expected = set_apic_delivery_mode(lint0, APIC_MODE_EXTINT);
let lint1_mode_expected = set_apic_delivery_mode(lint1, APIC_MODE_NMI);
set_lint(&vcpu).unwrap();
let klapic_actual: kvm_lapic_state = vcpu.get_lapic().unwrap();
let lint0_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT0);
let lint1_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT1);
assert_eq!(lint0_mode_expected, lint0_mode_actual);
assert_eq!(lint1_mode_expected, lint1_mode_actual);
}
#[test]
fn test_setlint_fails() {
let kvm = Kvm::new().unwrap();
let vm = kvm.create_vm().unwrap();
let vcpu = vm.create_vcpu(0).unwrap();
assert!(set_lint(&vcpu).is_err());
}
}