1
+/*
2
+ * MSHV in-kernel APIC support
3
+ *
4
+ * Copyright Microsoft, Corp. 2026
5
+ *
6
+ * Authors: Magnus Kulke <magnuskulke@microsoft.com>
7
+ *
8
+ * SPDX-License-Identifier: GPL-2.0-or-later
9
+ */
10
+
11
+#include "qemu/osdep.h"
12
+#include "qemu/module.h"
13
+#include "qemu/memalign.h"
14
+#include "qemu/error-report.h"
15
+#include "hw/i386/apic_internal.h"
16
+#include "hw/i386/apic-msidef.h"
17
+#include "hw/pci/msi.h"
18
+#include "migration/vmstate.h"
19
+#include "qemu/typedefs.h"
20
+#include "system/hw_accel.h"
21
+#include "system/mshv.h"
22
+#include "system/mshv_int.h"
23
+
24
+typedef struct hv_local_interrupt_controller_state
25
+ hv_local_interrupt_controller_state;
26
+
27
+#define TYPE_MSHV_APIC "mshv-apic"
28
+OBJECT_DECLARE_SIMPLE_TYPE(MshvAPICState, MSHV_APIC)
29
+
30
+struct MshvAPICState {
31
+ APICCommonState parent_obj;
32
+
33
+ uint32_t apic_version;
34
+ uint32_t apic_lvt_cmci;
35
+ uint32_t apic_error_status;
36
+ uint32_t apic_counter_value;
37
+ uint32_t apic_remote_read;
38
+};
39
+
40
+static int get_lapic(int cpu_fd,
41
+ struct hv_local_interrupt_controller_state *state)
42
+{
43
+ int ret;
44
+ size_t size = 4096;
45
+ /* buffer aligned to 4k, as *state requires that */
46
+ void *buffer = qemu_memalign(size, size);
47
+ struct mshv_get_set_vp_state mshv_state = { 0 };
48
+
49
+ mshv_state.buf_ptr = (uint64_t) buffer;
50
+ mshv_state.buf_sz = size;
51
+ mshv_state.type = MSHV_VP_STATE_LAPIC;
52
+
53
+ ret = mshv_get_vp_state(cpu_fd, &mshv_state);
54
+ if (ret == 0) {
55
+ memcpy(state, buffer, sizeof(*state));
56
+ }
57
+ qemu_vfree(buffer);
58
+ if (ret < 0) {
59
+ error_report("failed to get lapic");
60
+ return -1;
61
+ }
62
+
63
+ return 0;
64
+}
65
+
66
+static int set_lapic(int cpu_fd,
67
+ const struct hv_local_interrupt_controller_state *state)
68
+{
69
+ int ret;
70
+ size_t size = 4096;
71
+ /* buffer aligned to 4k, as *state requires that */
72
+ void *buffer = qemu_memalign(size, size);
73
+ struct mshv_get_set_vp_state mshv_state = { 0 };
74
+
75
+ if (!state) {
76
+ error_report("lapic state is NULL");
77
+ return -1;
78
+ }
79
+ memcpy(buffer, state, sizeof(*state));
80
+
81
+ mshv_state.buf_ptr = (uint64_t) buffer;
82
+ mshv_state.buf_sz = size;
83
+ mshv_state.type = MSHV_VP_STATE_LAPIC;
84
+
85
+ ret = mshv_set_vp_state(cpu_fd, &mshv_state);
86
+ qemu_vfree(buffer);
87
+ if (ret < 0) {
88
+ error_report("failed to set lapic: %s", strerror(errno));
89
+ return -1;
90
+ }
91
+
92
+ return 0;
93
+}
94
+
95
+static void populate_apic_state(CPUState *cpu,
96
+ const hv_local_interrupt_controller_state *hv)
97
+{
98
+ X86CPU *x86cpu = X86_CPU(cpu);
99
+ MshvAPICState *ms = MSHV_APIC(x86cpu->apic_state);
100
+ APICCommonState *s = &ms->parent_obj;
101
+ size_t i;
102
+
103
+ /*
104
+ * x2APIC:
105
+ * - APIC ID is the full 32-bit initial_apic_id
106
+ * - LDR is read-only, architecturally derived from the ID
107
+ * - DFR does not exist in x2APIC mode
108
+ */
109
+ if (is_x2apic_mode(s)) {
110
+ s->initial_apic_id = hv->apic_id;
111
+ } else {
112
+ s->id = hv->apic_id >> 24;
113
+ s->log_dest = hv->apic_ldr >> 24;
114
+ s->dest_mode = hv->apic_dfr >> 28;
115
+ }
116
+ ms->apic_version = hv->apic_version;
117
+ s->spurious_vec = hv->apic_spurious;
118
+ for (i = 0; i < 8; i++) {
119
+ s->isr[i] = hv->apic_isr[i];
120
+ s->tmr[i] = hv->apic_tmr[i];
121
+ s->irr[i] = hv->apic_irr[i];
122
+ }
123
+ s->esr = hv->apic_esr;
124
+ s->icr[1] = hv->apic_icr_high;
125
+ s->icr[0] = hv->apic_icr_low;
126
+
127
+ s->lvt[APIC_LVT_TIMER] = hv->apic_lvt_timer;
128
+ s->lvt[APIC_LVT_THERMAL] = hv->apic_lvt_thermal;
129
+ s->lvt[APIC_LVT_PERFORM] = hv->apic_lvt_perfmon;
130
+ s->lvt[APIC_LVT_LINT0] = hv->apic_lvt_lint0;
131
+ s->lvt[APIC_LVT_LINT1] = hv->apic_lvt_lint1;
132
+ s->lvt[APIC_LVT_ERROR] = hv->apic_lvt_error;
133
+ ms->apic_lvt_cmci = hv->apic_lvt_cmci;
134
+
135
+ ms->apic_error_status = hv->apic_error_status;
136
+ s->initial_count = hv->apic_initial_count;
137
+ ms->apic_counter_value = hv->apic_counter_value;
138
+ s->divide_conf = hv->apic_divide_configuration;
139
+ ms->apic_remote_read = hv->apic_remote_read;
140
+}
141
+
142
+static uint32_t set_apic_delivery_mode(uint32_t reg, uint32_t mode)
143
+{
144
+ return ((reg) & ~0x700) | ((mode) << 8);
145
+}
146
+
147
+int mshv_init_lint(CPUState *cpu)
148
+{
149
+ uint32_t *lvt_lint0, *lvt_lint1;
150
+ int cpu_fd = mshv_vcpufd(cpu);
151
+ int ret;
152
+ struct hv_local_interrupt_controller_state lapic_state = { 0 };
153
+
154
+ ret = get_lapic(cpu_fd, &lapic_state);
155
+ if (ret < 0) {
156
+ return ret;
157
+ }
158
+
159
+ lvt_lint0 = &lapic_state.apic_lvt_lint0;
160
+ *lvt_lint0 = set_apic_delivery_mode(*lvt_lint0, APIC_DM_EXTINT);
161
+
162
+ lvt_lint1 = &lapic_state.apic_lvt_lint1;
163
+ *lvt_lint1 = set_apic_delivery_mode(*lvt_lint1, APIC_DM_NMI);
164
+
165
+ /* TODO: should we skip setting lapic if the values are the same? */
166
+
167
+ ret = set_lapic(cpu_fd, &lapic_state);
168
+ if (ret < 0) {
169
+ return -1;
170
+ }
171
+
172
+ populate_apic_state(cpu, &lapic_state);
173
+
174
+ return 0;
175
+}
176
+
177
+static void populate_hv_lapic_state(hv_local_interrupt_controller_state *hv,
178
+ const CPUState *cpu)
179
+{
180
+ uint32_t x2apic_id;
181
+ X86CPU *x86cpu = X86_CPU(cpu);
182
+ MshvAPICState *ms = MSHV_APIC(x86cpu->apic_state);
183
+ APICCommonState *s = &ms->parent_obj;
184
+ size_t i;
185
+
186
+ /*
187
+ * x2APIC:
188
+ * - APIC ID is the full 32-bit initial_apic_id
189
+ * - LDR is read-only, architecturally derived from the ID
190
+ * - DFR does not exist in x2APIC mode
191
+ */
192
+ if (is_x2apic_mode(s)) {
193
+ x2apic_id = s->initial_apic_id;
194
+
195
+ hv->apic_id = x2apic_id;
196
+ hv->apic_ldr = ((x2apic_id >> 4) << 16) | (1 << (x2apic_id & 0xf));
197
+ hv->apic_dfr = 0;
198
+ } else {
199
+ hv->apic_id = s->id << 24;
200
+ hv->apic_ldr = s->log_dest << 24;
201
+ hv->apic_dfr = s->dest_mode << 28 | 0x0fffffff;
202
+ }
203
+ hv->apic_version = ms->apic_version;
204
+ hv->apic_spurious = s->spurious_vec;
205
+ for (i = 0; i < 8; i++) {
206
+ hv->apic_isr[i] = s->isr[i];
207
+ hv->apic_tmr[i] = s->tmr[i];
208
+ hv->apic_irr[i] = s->irr[i];
209
+ }
210
+ hv->apic_esr = s->esr;
211
+ hv->apic_icr_high = s->icr[1];
212
+ hv->apic_icr_low = s->icr[0];
213
+
214
+ hv->apic_lvt_timer = s->lvt[APIC_LVT_TIMER];
215
+ hv->apic_lvt_thermal = s->lvt[APIC_LVT_THERMAL];
216
+ hv->apic_lvt_perfmon = s->lvt[APIC_LVT_PERFORM];
217
+ hv->apic_lvt_lint0 = s->lvt[APIC_LVT_LINT0];
218
+ hv->apic_lvt_lint1 = s->lvt[APIC_LVT_LINT1];
219
+ hv->apic_lvt_error = s->lvt[APIC_LVT_ERROR];
220
+ hv->apic_lvt_cmci = ms->apic_lvt_cmci;
221
+
222
+ hv->apic_error_status = ms->apic_error_status;
223
+ hv->apic_initial_count = s->initial_count;
224
+ hv->apic_counter_value = ms->apic_counter_value;
225
+ hv->apic_divide_configuration = s->divide_conf;
226
+ hv->apic_remote_read = ms->apic_remote_read;
227
+}
228
+
229
+int mshv_set_lapic(const CPUState *cpu)
230
+{
231
+ int cpu_fd = mshv_vcpufd(cpu);
232
+ struct hv_local_interrupt_controller_state lapic_state = { 0 };
233
+
234
+ populate_hv_lapic_state(&lapic_state, cpu);
235
+
236
+ return set_lapic(cpu_fd, &lapic_state);
237
+}
238
+
239
+int mshv_get_lapic(CPUState *cpu)
240
+{
241
+ int cpu_fd = mshv_vcpufd(cpu);
242
+ int ret;
243
+ struct hv_local_interrupt_controller_state lapic_state = { 0 };
244
+
245
+ ret = get_lapic(cpu_fd, &lapic_state);
246
+ if (ret < 0) {
247
+ return -1;
248
+ }
249
+
250
+ populate_apic_state(cpu, &lapic_state);
251
+
252
+ return 0;
253
+}
254
+
255
+static int mshv_apic_set_base(APICCommonState *s, uint64_t val)
256
+{
257
+ s->apicbase = val;
258
+
259
+ return 0;
260
+}
261
+
262
+static void mshv_apic_set_tpr(APICCommonState *s, uint8_t val)
263
+{
264
+ s->tpr = (val & APIC_PR_SUB_CLASS) << APIC_PR_CLASS_SHIFT;
265
+}
266
+
267
+static uint8_t mshv_apic_get_tpr(APICCommonState *s)
268
+{
269
+ return s->tpr >> APIC_PR_CLASS_SHIFT;
270
+}
271
+
272
+static void mshv_apic_external_nmi(APICCommonState *s)
273
+{
274
+}
275
+
276
+static void mshv_apic_vapic_base_update(APICCommonState *s)
277
+{
278
+}
279
+
280
+static void mshv_send_msi(MSIMessage *msi)
281
+{
282
+ uint64_t addr;
283
+ uint32_t data, dest;
284
+ uint8_t vector, dest_mode, trigger_mode, delivery;
285
+
286
+ addr = msi->address;
287
+ data = msi->data;
288
+ dest = (addr & MSI_ADDR_DEST_ID_MASK) >> MSI_ADDR_DEST_ID_SHIFT |
289
+ (addr >> 32);
290
+ vector = (data & MSI_DATA_VECTOR_MASK) >> MSI_DATA_VECTOR_SHIFT;
291
+ dest_mode = (addr >> MSI_ADDR_DEST_MODE_SHIFT) & 0x1;
292
+ trigger_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1;
293
+ delivery = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) &
294
+ MSI_DATA_DELIVERY_MODE_MASK;
295
+
296
+ mshv_request_interrupt(mshv_state, delivery, vector, dest, dest_mode,
297
+ trigger_mode);
298
+}
299
+
300
+static uint64_t mshv_apic_mem_read(void *opaque, hwaddr addr,
301
+ unsigned size)
302
+{
303
+ return UINT64_MAX;
304
+}
305
+
306
+static void mshv_apic_mem_write(void *opaque, hwaddr addr,
307
+ uint64_t data, unsigned size)
308
+{
309
+ MSIMessage msg = { .address = addr, .data = data };
310
+
311
+ mshv_send_msi(&msg);
312
+}
313
+
314
+static const MemoryRegionOps mshv_apic_io_ops = {
315
+ .read = mshv_apic_mem_read,
316
+ .write = mshv_apic_mem_write,
317
+ .endianness = DEVICE_LITTLE_ENDIAN,
318
+};
319
+
320
+static void mshv_apic_reset(APICCommonState *s)
321
+{
322
+ s->wait_for_sipi = 0;
323
+}
324
+
325
+static const VMStateDescription vmstate_mshv_apic = {
326
+ .name = "mshv-apic",
327
+ .version_id = 1,
328
+ .minimum_version_id = 1,
329
+ .fields = (const VMStateField[]) {
330
+ VMSTATE_UINT32(apic_version, MshvAPICState),
331
+ VMSTATE_UINT32(apic_lvt_cmci, MshvAPICState),
332
+ VMSTATE_UINT32(apic_error_status, MshvAPICState),
333
+ VMSTATE_UINT32(apic_counter_value, MshvAPICState),
334
+ VMSTATE_UINT32(apic_remote_read, MshvAPICState),
335
+ VMSTATE_END_OF_LIST()
336
+ }
337
+};
338
+
339
+static void mshv_apic_realize(DeviceState *dev, Error **errp)
340
+{
341
+ APICCommonState *s = APIC_COMMON(dev);
342
+ MshvAPICState *ms = MSHV_APIC(dev);
343
+
344
+ memory_region_init_io(&s->io_memory, OBJECT(s), &mshv_apic_io_ops, s,
345
+ "mshv-apic-msi", APIC_SPACE_SIZE);
346
+
347
+ msi_nonbroken = true;
348
+
349
+ /*
350
+ * We register this state explicity, rather than going via dc->vmsd.
351
+ * The auto-wiring would register the state with
352
+ * instance_id == VMSTATE_INSTANCE_ID_ANY, which for the APIC doesn't
353
+ * work, b/c the ID carries semantic meaning for restoring the state
354
+ * on the destination (which vcpu it belongs to).
355
+ */
356
+ vmstate_register_with_alias_id(NULL,
357
+ s->initial_apic_id, &vmstate_mshv_apic, ms,
358
+ -1, 0, NULL);
359
+}
360
+
361
+static void mshv_apic_unrealize(DeviceState *dev)
362
+{
363
+ MshvAPICState *ms = MSHV_APIC(dev);
364
+
365
+ vmstate_unregister(NULL, &vmstate_mshv_apic, ms);
366
+}
367
+
368
+static void mshv_apic_class_init(ObjectClass *klass, const void *data)
369
+{
370
+ APICCommonClass *k = APIC_COMMON_CLASS(klass);
371
+
372
+ k->realize = mshv_apic_realize;
373
+ k->unrealize = mshv_apic_unrealize;
374
+ k->reset = mshv_apic_reset;
375
+ k->set_base = mshv_apic_set_base;
376
+ k->set_tpr = mshv_apic_set_tpr;
377
+ k->get_tpr = mshv_apic_get_tpr;
378
+ k->external_nmi = mshv_apic_external_nmi;
379
+ k->vapic_base_update = mshv_apic_vapic_base_update;
380
+ k->send_msi = mshv_send_msi;
381
+}
382
+
383
+static const TypeInfo mshv_apic_info = {
384
+ .name = TYPE_MSHV_APIC,
385
+ .parent = TYPE_APIC_COMMON,
386
+ .instance_size = sizeof(MshvAPICState),
387
+ .class_init = mshv_apic_class_init,
388
+};
389
+
390
+static void mshv_apic_register_types(void)
391
+{
392
+ type_register_static(&mshv_apic_info);
393
+}
394
+
395
+type_init(mshv_apic_register_types)