lib/sys/src/kvm/exit.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const abi = @import("abi.zig");
3
4 pub const DecodeError = error{
5 InvalidDirection,
6 InvalidPayload,
7 MappingTooSmall,
8 };
9
10 pub const Direction = enum(u8) {
11 input = 0,
12 output = 1,
13 };
14
15 pub const Reason = enum(u32) {
16 unknown = 0,
17 exception = 1,
18 io = 2,
19 hypercall = 3,
20 debug = 4,
21 halted = 5,
22 mmio = 6,
23 interrupt_window_open = 7,
24 shutdown = 8,
25 fail_entry = 9,
26 interrupted = 10,
27 set_task_priority = 11,
28 task_priority_access = 12,
29 s390_intercept = 13,
30 s390_reset = 14,
31 dcr = 15,
32 non_maskable_interrupt = 16,
33 internal_error = 17,
34 osi = 18,
35 papr_hypercall = 19,
36 s390_ucontrol = 20,
37 watchdog = 21,
38 s390_tsch = 22,
39 epr = 23,
40 system_event = 24,
41 s390_stsi = 25,
42 ioapic_eoi = 26,
43 hyperv = 27,
44 arm_nisv = 28,
45 msr_read = 29,
46 msr_write = 30,
47 dirty_ring_full = 31,
48 application_processor_reset_hold = 32,
49 bus_lock = 33,
50 xen = 34,
51 riscv_sbi = 35,
52 riscv_csr = 36,
53 notify = 37,
54 loongarch_iocsr = 38,
55 memory_fault = 39,
56 tdx = 40,
57 arm_sea = 41,
58 arm_ldst64b = 42,
59 snp_request_certificates = 43,
60 _,
61 };
62
63 pub const SystemEventKind = enum(u32) {
64 shutdown = 1,
65 reset = 2,
66 crash = 3,
67 wakeup = 4,
68 @"suspend" = 5,
69 encrypted_guest_termination = 6,
70 confidential_guest_fatal = 7,
71 _,
72 };
73
74 pub const Unknown = struct {
75 hardware_reason: u64,
76 };
77
78 pub const Exception = struct {
79 number: u32,
80 error_code: u32,
81 };
82
83 pub const Io = struct {
84 direction: Direction,
85 size: u8,
86 port: u16,
87 count: u32,
88 data: []u8,
89 };
90
91 pub const Debug = struct {
92 exception: u32,
93 program_counter: u64,
94 dr6: u64,
95 dr7: u64,
96 };
97
98 pub const Mmio = struct {
99 physical_address: u64,
100 data: []u8,
101 write: bool,
102 };
103
104 pub const Hypercall = struct {
105 number: u64,
106 arguments: [6]u64,
107 result: *u64,
108 flags: *u64,
109 };
110
111 pub const FailEntry = struct {
112 hardware_reason: u64,
113 cpu: u32,
114 };
115
116 pub const TaskPriorityAccess = struct {
117 instruction_pointer: u64,
118 write: bool,
119 };
120
121 pub const InternalError = struct {
122 kind: u32,
123 data: []const u64,
124 };
125
126 pub const SystemEvent = struct {
127 kind: SystemEventKind,
128 data: []u64,
129 };
130
131 pub const Msr = struct {
132 error_value: *u8,
133 reason: u32,
134 index: u32,
135 data: *u64,
136 };
137
138 pub const Notify = struct {
139 flags: u32,
140 };
141
142 pub const MemoryFault = struct {
143 flags: u64,
144 guest_physical_address: u64,
145 size: u64,
146 };
147
148 pub const Exit = union(enum) {
149 unknown: Unknown,
150 exception: Exception,
151 io: Io,
152 hypercall: Hypercall,
153 debug: Debug,
154 halted,
155 mmio: Mmio,
156 interrupt_window_open,
157 shutdown,
158 fail_entry: FailEntry,
159 interrupted,
160 set_task_priority,
161 task_priority_access: TaskPriorityAccess,
162 non_maskable_interrupt,
163 internal_error: InternalError,
164 watchdog,
165 system_event: SystemEvent,
166 ioapic_eoi: u8,
167 msr_read: Msr,
168 msr_write: Msr,
169 dirty_ring_full,
170 application_processor_reset_hold,
171 bus_lock,
172 notify: Notify,
173 memory_fault: MemoryFault,
174 unhandled: Reason,
175 };
176
177 const UnknownRaw = extern struct {
178 hardware_reason: u64,
179 };
180
181 const ExceptionRaw = extern struct {
182 number: u32,
183 error_code: u32,
184 };
185
186 const IoRaw = extern struct {
187 direction: u8,
188 size: u8,
189 port: u16,
190 count: u32,
191 data_offset: u64,
192 };
193
194 const DebugRaw = extern struct {
195 exception: u32,
196 padding: u32,
197 program_counter: u64,
198 dr6: u64,
199 dr7: u64,
200 };
201
202 const MmioRaw = extern struct {
203 physical_address: u64,
204 data: [8]u8,
205 length: u32,
206 write: u8,
207 };
208
209 const HypercallRaw = extern struct {
210 number: u64,
211 arguments: [6]u64,
212 result: u64,
213 flags: u64,
214 };
215
216 const FailEntryRaw = extern struct {
217 hardware_reason: u64,
218 cpu: u32,
219 };
220
221 const TaskPriorityAccessRaw = extern struct {
222 instruction_pointer: u64,
223 write: u32,
224 padding: u32,
225 };
226
227 const InternalErrorRaw = extern struct {
228 kind: u32,
229 count: u32,
230 data: [abi.maximum_exit_data]u64,
231 };
232
233 const SystemEventRaw = extern struct {
234 kind: u32,
235 count: u32,
236 data: [abi.maximum_exit_data]u64,
237 };
238
239 const MsrRaw = extern struct {
240 error_value: u8,
241 padding: [7]u8,
242 reason: u32,
243 index: u32,
244 data: u64,
245 };
246
247 const NotifyRaw = extern struct {
248 flags: u32,
249 };
250
251 const MemoryFaultRaw = extern struct {
252 flags: u64,
253 guest_physical_address: u64,
254 size: u64,
255 };
256
257 pub fn decode(mapping: []align(std.heap.page_size_min) u8) DecodeError!Exit {
258 if (mapping.len < abi.minimum_run_bytes) return error.MappingTooSmall;
259 const header = view(abi.RunHeader, mapping);
260 const reason: Reason = @fromBackingInt(@intCast(header.exit_reason));
261 return switch (reason) {
262 .unknown => decodeUnknown(mapping),
263 .exception => decodeException(mapping),
264 .io => decodeIo(mapping),
265 .hypercall => decodeHypercall(mapping),
266 .debug => decodeDebug(mapping),
267 .halted => .halted,
268 .mmio => decodeMmio(mapping),
269 .interrupt_window_open => .interrupt_window_open,
270 .shutdown => .shutdown,
271 .fail_entry => decodeFailEntry(mapping),
272 .interrupted => .interrupted,
273 .set_task_priority => .set_task_priority,
274 .task_priority_access => decodeTaskPriorityAccess(mapping),
275 .non_maskable_interrupt => .non_maskable_interrupt,
276 .internal_error => decodeInternalError(mapping),
277 .watchdog => .watchdog,
278 .system_event => decodeSystemEvent(mapping),
279 .ioapic_eoi => decodeIoapicEoi(mapping),
280 .msr_read => .{ .msr_read = decodeMsr(mapping) },
281 .msr_write => .{ .msr_write = decodeMsr(mapping) },
282 .dirty_ring_full => .dirty_ring_full,
283 .application_processor_reset_hold => .application_processor_reset_hold,
284 .bus_lock => .bus_lock,
285 .notify => decodeNotify(mapping),
286 .memory_fault => decodeMemoryFault(mapping),
287 else => .{ .unhandled = reason },
288 };
289 }
290
291 fn decodeUnknown(mapping: []align(std.heap.page_size_min) u8) Exit {
292 const raw = payload(UnknownRaw, mapping);
293 return .{ .unknown = .{ .hardware_reason = raw.hardware_reason } };
294 }
295
296 fn decodeException(mapping: []align(std.heap.page_size_min) u8) Exit {
297 const raw = payload(ExceptionRaw, mapping);
298 return .{ .exception = .{ .number = raw.number, .error_code = raw.error_code } };
299 }
300
301 fn decodeIo(mapping: []align(std.heap.page_size_min) u8) DecodeError!Exit {
302 const raw = payload(IoRaw, mapping);
303 const direction: Direction = switch (raw.direction) {
304 0 => .input,
305 1 => .output,
306 else => return error.InvalidDirection,
307 };
308 if (raw.size == 0 or raw.size > 8 or !std.math.isPowerOfTwo(raw.size)) {
309 return error.InvalidPayload;
310 }
311 const byte_count = std.math.mul(usize, raw.size, raw.count) catch
312 return error.InvalidPayload;
313 const offset = std.math.cast(usize, raw.data_offset) orelse
314 return error.InvalidPayload;
315 const end = std.math.add(usize, offset, byte_count) catch
316 return error.InvalidPayload;
317 if (end > mapping.len) return error.InvalidPayload;
318 return .{ .io = .{
319 .direction = direction,
320 .size = raw.size,
321 .port = raw.port,
322 .count = raw.count,
323 .data = mapping[offset..end],
324 } };
325 }
326
327 fn decodeDebug(mapping: []align(std.heap.page_size_min) u8) Exit {
328 const raw = payload(DebugRaw, mapping);
329 return .{ .debug = .{
330 .exception = raw.exception,
331 .program_counter = raw.program_counter,
332 .dr6 = raw.dr6,
333 .dr7 = raw.dr7,
334 } };
335 }
336
337 fn decodeMmio(mapping: []align(std.heap.page_size_min) u8) DecodeError!Exit {
338 const raw = payload(MmioRaw, mapping);
339 if (raw.length > raw.data.len) return error.InvalidPayload;
340 return .{ .mmio = .{
341 .physical_address = raw.physical_address,
342 .data = raw.data[0..raw.length],
343 .write = raw.write != 0,
344 } };
345 }
346
347 fn decodeHypercall(mapping: []align(std.heap.page_size_min) u8) Exit {
348 const raw = payload(HypercallRaw, mapping);
349 return .{ .hypercall = .{
350 .number = raw.number,
351 .arguments = raw.arguments,
352 .result = &raw.result,
353 .flags = &raw.flags,
354 } };
355 }
356
357 fn decodeFailEntry(mapping: []align(std.heap.page_size_min) u8) Exit {
358 const raw = payload(FailEntryRaw, mapping);
359 return .{ .fail_entry = .{
360 .hardware_reason = raw.hardware_reason,
361 .cpu = raw.cpu,
362 } };
363 }
364
365 fn decodeTaskPriorityAccess(mapping: []align(std.heap.page_size_min) u8) Exit {
366 const raw = payload(TaskPriorityAccessRaw, mapping);
367 return .{ .task_priority_access = .{
368 .instruction_pointer = raw.instruction_pointer,
369 .write = raw.write != 0,
370 } };
371 }
372
373 fn decodeIoapicEoi(mapping: []align(std.heap.page_size_min) u8) Exit {
374 return .{ .ioapic_eoi = payload(u8, mapping).* };
375 }
376
377 fn decodeInternalError(mapping: []align(std.heap.page_size_min) u8) DecodeError!Exit {
378 const raw = payload(InternalErrorRaw, mapping);
379 if (raw.count > raw.data.len) return error.InvalidPayload;
380 return .{ .internal_error = .{
381 .kind = raw.kind,
382 .data = raw.data[0..raw.count],
383 } };
384 }
385
386 fn decodeSystemEvent(mapping: []align(std.heap.page_size_min) u8) DecodeError!Exit {
387 const raw = payload(SystemEventRaw, mapping);
388 if (raw.count > raw.data.len) return error.InvalidPayload;
389 return .{ .system_event = .{
390 .kind = @fromBackingInt(@intCast(raw.kind)),
391 .data = raw.data[0..raw.count],
392 } };
393 }
394
395 fn decodeMsr(mapping: []align(std.heap.page_size_min) u8) Msr {
396 const raw = payload(MsrRaw, mapping);
397 return .{
398 .error_value = &raw.error_value,
399 .reason = raw.reason,
400 .index = raw.index,
401 .data = &raw.data,
402 };
403 }
404
405 fn decodeNotify(mapping: []align(std.heap.page_size_min) u8) Exit {
406 const raw = payload(NotifyRaw, mapping);
407 return .{ .notify = .{ .flags = raw.flags } };
408 }
409
410 fn decodeMemoryFault(mapping: []align(std.heap.page_size_min) u8) Exit {
411 const raw = payload(MemoryFaultRaw, mapping);
412 return .{ .memory_fault = .{
413 .flags = raw.flags,
414 .guest_physical_address = raw.guest_physical_address,
415 .size = raw.size,
416 } };
417 }
418
419 fn view(comptime T: type, mapping: []align(std.heap.page_size_min) u8) *T {
420 std.debug.assert(mapping.len >= @sizeOf(T));
421 return @ptrCast(@alignCast(mapping.ptr));
422 }
423
424 fn payload(comptime T: type, mapping: []align(std.heap.page_size_min) u8) *T {
425 std.debug.assert(abi.run_union_offset + @sizeOf(T) <= mapping.len);
426 return @ptrCast(@alignCast(mapping.ptr + abi.run_union_offset));
427 }
428
429 test "run exit decoder exposes bounded port data" {
430 var storage: [4096]u8 align(std.heap.page_size_min) = @splat(0);
431 const mapping = storage[0..];
432 view(abi.RunHeader, mapping).exit_reason = @backingInt(Reason.io);
433 const raw = payload(IoRaw, mapping);
434 raw.* = .{ .direction = 1, .size = 2, .port = 0x34, .count = 2, .data_offset = 256 };
435 @memcpy(mapping[256..260], &[_]u8{ 1, 2, 3, 4 });
436
437 const decoded = try decode(mapping);
438 try std.testing.expectEqual(Direction.output, decoded.io.direction);
439 try std.testing.expectEqual(@as(u16, 0x34), decoded.io.port);
440 try std.testing.expectEqualSlices(u8, &.{ 1, 2, 3, 4 }, decoded.io.data);
441 }
442
443 test "run exit decoder rejects data beyond the run mapping" {
444 var storage: [4096]u8 align(std.heap.page_size_min) = @splat(0);
445 const mapping = storage[0..];
446 view(abi.RunHeader, mapping).exit_reason = @backingInt(Reason.io);
447 payload(IoRaw, mapping).* = .{
448 .direction = 0,
449 .size = 8,
450 .port = 0,
451 .count = 2,
452 .data_offset = mapping.len - 8,
453 };
454 try std.testing.expectError(error.InvalidPayload, decode(mapping));
455 }
456
457 test "run exit decoder exposes bounded MMIO data for completion" {
458 var storage: [4096]u8 align(std.heap.page_size_min) = @splat(0);
459 const mapping = storage[0..];
460 view(abi.RunHeader, mapping).exit_reason = @backingInt(Reason.mmio);
461 const raw = payload(MmioRaw, mapping);
462 raw.* = .{
463 .physical_address = 0x4000,
464 .data = .{ 1, 2, 3, 4, 0, 0, 0, 0 },
465 .length = 4,
466 .write = 0,
467 };
468
469 const decoded = try decode(mapping);
470 try std.testing.expectEqual(@as(u64, 0x4000), decoded.mmio.physical_address);
471 try std.testing.expect(!decoded.mmio.write);
472 decoded.mmio.data[0] = 9;
473 try std.testing.expectEqual(@as(u8, 9), raw.data[0]);
474 }
475
476 test "run exit decoder bounds internal and system event arrays" {
477 var storage: [4096]u8 align(std.heap.page_size_min) = @splat(0);
478 const mapping = storage[0..];
479 view(abi.RunHeader, mapping).exit_reason = @backingInt(Reason.internal_error);
480 payload(InternalErrorRaw, mapping).count = abi.maximum_exit_data + 1;
481 try std.testing.expectError(error.InvalidPayload, decode(mapping));
482
483 view(abi.RunHeader, mapping).exit_reason = @backingInt(Reason.system_event);
484 const event = payload(SystemEventRaw, mapping);
485 event.kind = @backingInt(SystemEventKind.reset);
486 event.count = 2;
487 event.data[0] = 7;
488 event.data[1] = 8;
489 const decoded = try decode(mapping);
490 try std.testing.expectEqual(SystemEventKind.reset, decoded.system_event.kind);
491 try std.testing.expectEqualSlices(u64, &.{ 7, 8 }, decoded.system_event.data);
492 }