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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
|
// SPDX-License-Identifier: GPL-2.0
/*
* vgic_irq.c - Test userspace injection of IRQs
*
* This test validates the injection of IRQs from userspace using various
* methods (e.g., KVM_IRQ_LINE) and modes (e.g., EOI). The guest "asks" the
* host to inject a specific intid via a GUEST_SYNC call, and then checks that
* it received it.
*/
#include <asm/kvm.h>
#include <asm/kvm_para.h>
#include <linux/sizes.h>
#include "processor.h"
#include "test_util.h"
#include "kvm_util.h"
#include "gic.h"
#include "gic_v3.h"
#include "vgic.h"
#define GICD_BASE_GPA 0x08000000ULL
#define GICR_BASE_GPA 0x080A0000ULL
#define VCPU_ID 0
/*
* KVM implements 32 priority levels:
* 0x00 (highest priority) - 0xF8 (lowest priority), in steps of 8
*
* Note that these macros will still be correct in the case that KVM implements
* more priority levels. Also note that 32 is the minimum for GICv3 and GICv2.
*/
#define KVM_NUM_PRIOS 32
#define KVM_PRIO_SHIFT 3 /* steps of 8 = 1 << 3 */
#define LOWEST_PRIO (KVM_NUM_PRIOS - 1)
#define CPU_PRIO_MASK (LOWEST_PRIO << KVM_PRIO_SHIFT) /* 0xf8 */
#define IRQ_DEFAULT_PRIO (LOWEST_PRIO - 1)
#define IRQ_DEFAULT_PRIO_REG (IRQ_DEFAULT_PRIO << KVM_PRIO_SHIFT) /* 0xf0 */
static void *dist = (void *)GICD_BASE_GPA;
static void *redist = (void *)GICR_BASE_GPA;
/*
* The kvm_inject_* utilities are used by the guest to ask the host to inject
* interrupts (e.g., using the KVM_IRQ_LINE ioctl).
*/
typedef enum {
KVM_INJECT_EDGE_IRQ_LINE = 1,
} kvm_inject_cmd;
struct kvm_inject_args {
kvm_inject_cmd cmd;
uint32_t intid;
};
/* Used on the guest side to perform the hypercall. */
static void kvm_inject_call(kvm_inject_cmd cmd, uint32_t intid);
/* Used on the host side to get the hypercall info. */
static void kvm_inject_get_call(struct kvm_vm *vm, struct ucall *uc,
struct kvm_inject_args *args);
/* Shared between the guest main thread and the IRQ handlers. */
volatile uint64_t irq_handled;
volatile uint32_t irqnr_received[MAX_SPI + 1];
static void reset_stats(void)
{
int i;
irq_handled = 0;
for (i = 0; i <= MAX_SPI; i++)
irqnr_received[i] = 0;
}
static uint64_t gic_read_ap1r0(void)
{
uint64_t reg = read_sysreg_s(SYS_ICV_AP1R0_EL1);
dsb(sy);
return reg;
}
static void guest_irq_handler(struct ex_regs *regs)
{
uint32_t intid = gic_get_and_ack_irq();
if (intid == IAR_SPURIOUS)
return;
GUEST_ASSERT(gic_irq_get_active(intid));
GUEST_ASSERT(!gic_irq_get_pending(intid));
GUEST_ASSERT(intid < MAX_SPI);
irqnr_received[intid] += 1;
irq_handled += 1;
gic_set_eoi(intid);
GUEST_ASSERT_EQ(gic_read_ap1r0(), 0);
GUEST_ASSERT(!gic_irq_get_active(intid));
GUEST_ASSERT(!gic_irq_get_pending(intid));
}
static void kvm_inject_call(kvm_inject_cmd cmd, uint32_t intid)
{
struct kvm_inject_args args = {
.cmd = cmd,
.intid = intid,
};
GUEST_SYNC(&args);
}
#define GUEST_ASSERT_IAR_EMPTY() \
do { \
uint32_t _intid; \
_intid = gic_get_and_ack_irq(); \
GUEST_ASSERT(_intid == 0 || _intid == IAR_SPURIOUS); \
} while (0)
static void test_kvm_irq_line(uint32_t intid)
{
reset_stats();
asm volatile("msr daifset, #2" : : : "memory");
kvm_inject_call(KVM_INJECT_EDGE_IRQ_LINE, intid);
while (irq_handled < 1) {
asm volatile("wfi\n"
"msr daifclr, #2\n"
/* handle IRQ */
"msr daifset, #2\n"
: : : "memory");
}
asm volatile("msr daifclr, #2" : : : "memory");
GUEST_ASSERT_EQ(irq_handled, 1);
GUEST_ASSERT_EQ(irqnr_received[intid], 1);
GUEST_ASSERT_IAR_EMPTY();
}
static void guest_code(void)
{
uint32_t i;
uint32_t nr_irqs = 64; /* absolute minimum number of IRQs supported. */
gic_init(GIC_V3, 1, dist, redist);
for (i = 0; i < nr_irqs; i++) {
gic_irq_enable(i);
gic_set_priority(i, IRQ_DEFAULT_PRIO_REG);
}
gic_set_priority_mask(CPU_PRIO_MASK);
local_irq_enable();
test_kvm_irq_line(MIN_SPI);
GUEST_DONE();
}
static void run_guest_cmd(struct kvm_vm *vm, int gic_fd,
struct kvm_inject_args *inject_args)
{
kvm_inject_cmd cmd = inject_args->cmd;
uint32_t intid = inject_args->intid;
switch (cmd) {
case KVM_INJECT_EDGE_IRQ_LINE:
kvm_arm_irq_line(vm, intid, 1);
kvm_arm_irq_line(vm, intid, 0);
break;
default:
break;
}
}
static void kvm_inject_get_call(struct kvm_vm *vm, struct ucall *uc,
struct kvm_inject_args *args)
{
struct kvm_inject_args *kvm_args_hva;
vm_vaddr_t kvm_args_gva;
kvm_args_gva = uc->args[1];
kvm_args_hva = (struct kvm_inject_args *)addr_gva2hva(vm, kvm_args_gva);
memcpy(args, kvm_args_hva, sizeof(struct kvm_inject_args));
}
static void test_vgic(void)
{
struct ucall uc;
int gic_fd;
struct kvm_vm *vm;
struct kvm_inject_args inject_args;
vm = vm_create_default(VCPU_ID, 0, guest_code);
ucall_init(vm, NULL);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(vm, VCPU_ID);
gic_fd = vgic_v3_setup(vm, 1, GICD_BASE_GPA, GICR_BASE_GPA);
vm_install_exception_handler(vm, VECTOR_IRQ_CURRENT,
guest_irq_handler);
while (1) {
vcpu_run(vm, VCPU_ID);
switch (get_ucall(vm, VCPU_ID, &uc)) {
case UCALL_SYNC:
kvm_inject_get_call(vm, &uc, &inject_args);
run_guest_cmd(vm, gic_fd, &inject_args);
break;
case UCALL_ABORT:
TEST_FAIL("%s at %s:%ld\n\tvalues: %#lx, %#lx",
(const char *)uc.args[0],
__FILE__, uc.args[1], uc.args[2], uc.args[3]);
break;
case UCALL_DONE:
goto done;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
}
}
done:
close(gic_fd);
kvm_vm_free(vm);
}
int main(int ac, char **av)
{
/* Tell stdout not to buffer its content */
setbuf(stdout, NULL);
test_vgic();
return 0;
}
|