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 }