vmm/arch/x86_64/
interrupts.rs

1// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
5// Use of this source code is governed by a BSD-style license that can be
6// found in the THIRD-PARTY file.
7
8use kvm_bindings::kvm_lapic_state;
9use kvm_ioctls::VcpuFd;
10use zerocopy::IntoBytes;
11
12use crate::utils::byte_order;
13
14/// Errors thrown while configuring the LAPIC.
15#[derive(Debug, thiserror::Error, displaydoc::Display, PartialEq, Eq)]
16pub enum InterruptError {
17    /// Failure in getting the LAPIC configuration: {0}
18    GetLapic(kvm_ioctls::Error),
19    /// Failure in setting the LAPIC configuration: {0}
20    SetLapic(kvm_ioctls::Error),
21}
22
23// Defines poached from apicdef.h kernel header.
24const APIC_LVT0: usize = 0x350;
25const APIC_LVT1: usize = 0x360;
26const APIC_MODE_NMI: u32 = 0x4;
27const APIC_MODE_EXTINT: u32 = 0x7;
28
29fn get_klapic_reg(klapic: &kvm_lapic_state, reg_offset: usize) -> u32 {
30    let range = reg_offset..reg_offset + 4;
31    let reg = klapic.regs.get(range).expect("get_klapic_reg range");
32    byte_order::read_le_u32(reg.as_bytes())
33}
34
35fn set_klapic_reg(klapic: &mut kvm_lapic_state, reg_offset: usize, value: u32) {
36    let range = reg_offset..reg_offset + 4;
37    let reg = klapic.regs.get_mut(range).expect("set_klapic_reg range");
38    byte_order::write_le_u32(reg.as_mut_bytes(), value);
39}
40
41fn set_apic_delivery_mode(reg: u32, mode: u32) -> u32 {
42    ((reg) & !0x700) | ((mode) << 8)
43}
44
45/// Configures LAPICs.  LAPIC0 is set for external interrupts, LAPIC1 is set for NMI.
46///
47/// # Arguments
48/// * `vcpu` - The VCPU object to configure.
49pub fn set_lint(vcpu: &VcpuFd) -> Result<(), InterruptError> {
50    let mut klapic = vcpu.get_lapic().map_err(InterruptError::GetLapic)?;
51
52    let lvt_lint0 = get_klapic_reg(&klapic, APIC_LVT0);
53    set_klapic_reg(
54        &mut klapic,
55        APIC_LVT0,
56        set_apic_delivery_mode(lvt_lint0, APIC_MODE_EXTINT),
57    );
58    let lvt_lint1 = get_klapic_reg(&klapic, APIC_LVT1);
59    set_klapic_reg(
60        &mut klapic,
61        APIC_LVT1,
62        set_apic_delivery_mode(lvt_lint1, APIC_MODE_NMI),
63    );
64
65    vcpu.set_lapic(&klapic).map_err(InterruptError::SetLapic)
66}
67
68#[cfg(test)]
69mod tests {
70    use kvm_ioctls::Kvm;
71
72    use super::*;
73
74    const KVM_APIC_REG_SIZE: usize = 0x400;
75
76    #[test]
77    fn test_set_and_get_klapic_reg() {
78        let reg_offset = 0x340;
79        let mut klapic = kvm_lapic_state::default();
80        set_klapic_reg(&mut klapic, reg_offset, 3);
81        let value = get_klapic_reg(&klapic, reg_offset);
82        assert_eq!(value, 3);
83    }
84
85    #[test]
86    fn test_set_and_get_klapic_reg_overflow() {
87        let reg_offset = 0x340;
88        let mut klapic = kvm_lapic_state::default();
89        set_klapic_reg(
90            &mut klapic,
91            reg_offset,
92            u32::try_from(i32::MAX).unwrap() + 1u32,
93        );
94        let value = get_klapic_reg(&klapic, reg_offset);
95        assert_eq!(value, u32::try_from(i32::MAX).unwrap() + 1u32);
96    }
97
98    #[test]
99    #[should_panic]
100    fn test_set_and_get_klapic_out_of_bounds() {
101        let reg_offset = KVM_APIC_REG_SIZE + 10;
102        let mut klapic = kvm_lapic_state::default();
103        set_klapic_reg(&mut klapic, reg_offset, 3);
104    }
105
106    #[test]
107    fn test_apic_delivery_mode() {
108        let mut v: Vec<u32> = (0..20)
109            .map(|_| vmm_sys_util::rand::xor_pseudo_rng_u32())
110            .collect();
111
112        v.iter_mut()
113            .for_each(|x| *x = set_apic_delivery_mode(*x, 2));
114        let after: Vec<u32> = v.iter().map(|x| ((*x & !0x700) | ((2) << 8))).collect();
115        assert_eq!(v, after);
116    }
117
118    #[test]
119    fn test_setlint() {
120        let kvm = Kvm::new().unwrap();
121        assert!(kvm.check_extension(kvm_ioctls::Cap::Irqchip));
122        let vm = kvm.create_vm().unwrap();
123        // the get_lapic ioctl will fail if there is no irqchip created beforehand.
124        vm.create_irq_chip().unwrap();
125        let vcpu = vm.create_vcpu(0).unwrap();
126        let klapic_before: kvm_lapic_state = vcpu.get_lapic().unwrap();
127
128        // Compute the value that is expected to represent LVT0 and LVT1.
129        let lint0 = get_klapic_reg(&klapic_before, APIC_LVT0);
130        let lint1 = get_klapic_reg(&klapic_before, APIC_LVT1);
131        let lint0_mode_expected = set_apic_delivery_mode(lint0, APIC_MODE_EXTINT);
132        let lint1_mode_expected = set_apic_delivery_mode(lint1, APIC_MODE_NMI);
133
134        set_lint(&vcpu).unwrap();
135
136        // Compute the value that represents LVT0 and LVT1 after set_lint.
137        let klapic_actual: kvm_lapic_state = vcpu.get_lapic().unwrap();
138        let lint0_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT0);
139        let lint1_mode_actual = get_klapic_reg(&klapic_actual, APIC_LVT1);
140        assert_eq!(lint0_mode_expected, lint0_mode_actual);
141        assert_eq!(lint1_mode_expected, lint1_mode_actual);
142    }
143
144    #[test]
145    fn test_setlint_fails() {
146        let kvm = Kvm::new().unwrap();
147        let vm = kvm.create_vm().unwrap();
148        let vcpu = vm.create_vcpu(0).unwrap();
149        // 'get_lapic' ioctl triggered by the 'set_lint' function will fail if there is no
150        // irqchip created beforehand.
151        set_lint(&vcpu).unwrap_err();
152    }
153}