lib/sys/src/kvm/system.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const abi = @import("abi.zig");
  3 const linux = @import("linux.zig");
  4 const types = @import("types.zig");
  5 const vm = @import("vm.zig");
  6 
  7 pub const supported_cpuid_entries_max: usize = 256;
  8 pub const OpenAttempt = union(enum) {
  9     ready: System,
 10     rejected: types.CapabilityReceipt,
 11 };
 12 
 13 pub const System = struct {
 14     pub const descriptor_capacity: usize = 1;
 15 
 16     operations: types.Operations,
 17     descriptor: types.Descriptor,
 18     receipt: types.CapabilityReceipt,
 19     active: bool = true,
 20 
 21     pub fn deinit(self: *@This()) void {
 22         if (!self.active) return;
 23         std.debug.assert(self.descriptor >= 0);
 24         self.operations.close(self.descriptor);
 25         self.active = false;
 26     }
 27 
 28     pub fn createVm(self: *@This(), machine_type: u32) types.CreateError!vm.Vm {
 29         if (!self.active) return error.Closed;
 30         const result = types.controlRetry(
 31             self.operations,
 32             self.descriptor,
 33             abi.request.create_vm,
 34             machine_type,
 35         );
 36         const descriptor = try types.createDescriptor(result);
 37         return .{
 38             .operations = self.operations,
 39             .descriptor = descriptor,
 40             .run_mapping_bytes = self.receipt.run_mapping_bytes,
 41             .extensions = self.receipt.extensions,
 42         };
 43     }
 44 
 45     pub fn memorySlotCapacity(self: *const @This()) u32 {
 46         std.debug.assert(self.active);
 47         return self.receipt.extensions.memory_slots;
 48     }
 49 
 50     pub fn supportsReadOnlyMemory(self: *const @This()) bool {
 51         std.debug.assert(self.active);
 52         return self.receipt.extensions.read_only_memory != 0;
 53     }
 54 
 55     pub fn supportedCpuid(self: *@This(), buffer: anytype) types.StateError!void {
 56         if (!self.active) return error.Closed;
 57         if (self.receipt.extensions.extended_cpuid == 0) {
 58             return error.UnsupportedState;
 59         }
 60         validateCpuidBuffer(@TypeOf(buffer.*));
 61         if (buffer.count > buffer.entries.len) return error.CapacityExceeded;
 62         try types.stateControl(
 63             self.operations,
 64             self.descriptor,
 65             abi.request.get_supported_cpuid,
 66             @intFromPtr(buffer),
 67         );
 68     }
 69 };
 70 
 71 const ExtensionField = struct {
 72     extension: abi.Extension,
 73     name: []const u8,
 74 };
 75 
 76 const extension_fields = [_]ExtensionField{
 77     .{ .extension = .user_memory, .name = "user_memory" },
 78     .{ .extension = .extended_cpuid, .name = "extended_cpuid" },
 79     .{ .extension = .recommended_vcpus, .name = "recommended_vcpus" },
 80     .{ .extension = .memory_slots, .name = "memory_slots" },
 81     .{ .extension = .mp_state, .name = "multiprocessor_state" },
 82     .{ .extension = .vcpu_events, .name = "vcpu_events" },
 83     .{ .extension = .debug_registers, .name = "debug_registers" },
 84     .{ .extension = .xsave, .name = "xsave" },
 85     .{ .extension = .xcrs, .name = "xcrs" },
 86     .{ .extension = .maximum_vcpus, .name = "maximum_vcpus" },
 87     .{ .extension = .read_only_memory, .name = "read_only_memory" },
 88     .{ .extension = .maximum_vcpu_id, .name = "maximum_vcpu_id" },
 89     .{ .extension = .immediate_exit, .name = "immediate_exit" },
 90     .{ .extension = .special_registers_2, .name = "special_registers_2" },
 91     .{ .extension = .xsave_2, .name = "xsave_2" },
 92 };
 93 
 94 pub fn open() OpenAttempt {
 95     return acquire(linux.operations());
 96 }
 97 
 98 pub fn probe() types.CapabilityReceipt {
 99     return switch (open()) {
100         .ready => |value| result: {
101             var system = value;
102             const receipt = system.receipt;
103             system.deinit();
104             break :result receipt;
105         },
106         .rejected => |receipt| receipt,
107     };
108 }
109 
110 pub fn acquire(operations: types.Operations) OpenAttempt {
111     var receipt: types.CapabilityReceipt = .{};
112     if (!operations.platformSupported()) {
113         receipt.availability = .unsupported;
114         return .{ .rejected = receipt };
115     }
116     receipt.stage = .device;
117     const descriptor = openDescriptor(operations, &receipt) orelse {
118         return .{ .rejected = receipt };
119     };
120     if (!readApiVersion(operations, descriptor, &receipt)) {
121         operations.close(descriptor);
122         return .{ .rejected = receipt };
123     }
124     if (!readRunMappingSize(operations, descriptor, &receipt)) {
125         operations.close(descriptor);
126         return .{ .rejected = receipt };
127     }
128     if (!readExtensions(operations, descriptor, &receipt)) {
129         operations.close(descriptor);
130         return .{ .rejected = receipt };
131     }
132     receipt.availability = .available;
133     receipt.stage = .complete;
134     return .{ .ready = .{
135         .operations = operations,
136         .descriptor = descriptor,
137         .receipt = receipt,
138     } };
139 }
140 
141 fn openDescriptor(
142     operations: types.Operations,
143     receipt: *types.CapabilityReceipt,
144 ) ?types.Descriptor {
145     for (0..types.maximum_interrupt_retries) |_| switch (operations.openDevice()) {
146         .value => |value| return std.math.cast(types.Descriptor, value) orelse {
147             receipt.availability = .failed;
148             return null;
149         },
150         .failure => |code| {
151             if (code == .INTR) continue;
152             reject(receipt, .device, code);
153             return null;
154         },
155     };
156     reject(receipt, .device, .INTR);
157     return null;
158 }
159 
160 fn readApiVersion(
161     operations: types.Operations,
162     descriptor: types.Descriptor,
163     receipt: *types.CapabilityReceipt,
164 ) bool {
165     receipt.stage = .api_version;
166     const result = types.controlRetry(
167         operations,
168         descriptor,
169         abi.request.get_api_version,
170         0,
171     );
172     const value = receiptValue(receipt, .api_version, result) orelse return false;
173     receipt.api_version = std.math.cast(u32, value) orelse {
174         receipt.availability = .failed;
175         return false;
176     };
177     if (receipt.api_version != abi.api_version) {
178         receipt.availability = .unsupported;
179         return false;
180     }
181     return true;
182 }
183 
184 fn readRunMappingSize(
185     operations: types.Operations,
186     descriptor: types.Descriptor,
187     receipt: *types.CapabilityReceipt,
188 ) bool {
189     receipt.stage = .run_mapping;
190     const result = types.controlRetry(
191         operations,
192         descriptor,
193         abi.request.get_vcpu_mmap_size,
194         0,
195     );
196     const value = receiptValue(receipt, .run_mapping, result) orelse return false;
197     receipt.run_mapping_bytes = std.math.cast(u32, value) orelse {
198         receipt.availability = .failed;
199         return false;
200     };
201     if (receipt.run_mapping_bytes < abi.minimum_run_bytes or
202         receipt.run_mapping_bytes % types.page_size != 0)
203     {
204         receipt.availability = .unsupported;
205         return false;
206     }
207     return true;
208 }
209 
210 fn readExtensions(
211     operations: types.Operations,
212     descriptor: types.Descriptor,
213     receipt: *types.CapabilityReceipt,
214 ) bool {
215     receipt.stage = .extensions;
216     inline for (extension_fields) |field| {
217         const result = types.controlRetry(
218             operations,
219             descriptor,
220             abi.request.check_extension,
221             @backingInt(field.extension),
222         );
223         const value = receiptValue(receipt, .extensions, result) orelse return false;
224         @field(receipt.extensions, field.name) = std.math.cast(u32, value) orelse {
225             receipt.availability = .failed;
226             return false;
227         };
228     }
229     if (receipt.extensions.user_memory == 0) {
230         receipt.availability = .unsupported;
231         return false;
232     }
233     return true;
234 }
235 
236 fn receiptValue(
237     receipt: *types.CapabilityReceipt,
238     stage: types.CapabilityStage,
239     result: types.RawResult,
240 ) ?usize {
241     return switch (result) {
242         .value => |value| value,
243         .failure => |code| {
244             reject(receipt, stage, code);
245             return null;
246         },
247     };
248 }
249 
250 fn reject(
251     receipt: *types.CapabilityReceipt,
252     stage: types.CapabilityStage,
253     code: types.Errno,
254 ) void {
255     receipt.availability = classify(code);
256     receipt.stage = stage;
257     receipt.errno_code = @intCast(@backingInt(code));
258 }
259 
260 fn classify(code: types.Errno) types.Availability {
261     return switch (code) {
262         .ACCES, .PERM => .denied,
263         .NOENT, .NODEV, .NXIO => .unavailable,
264         .NOSYS, .NOTTY, .OPNOTSUPP => .unsupported,
265         else => .failed,
266     };
267 }
268 
269 fn validateCpuidBuffer(comptime Buffer: type) void {
270     comptime {
271         if (!@hasField(Buffer, "count") or !@hasField(Buffer, "entries")) {
272             @compileError("expected abi.CpuidBuffer");
273         }
274         if (@FieldType(Buffer, "count") != u32) @compileError("invalid CPUID count type");
275         if (@typeInfo(@FieldType(Buffer, "entries")).array.child != abi.CpuidEntry) {
276             @compileError("invalid CPUID entry type");
277         }
278         if (@offsetOf(Buffer, "entries") != @sizeOf(abi.CpuidHeader)) {
279             @compileError("invalid CPUID buffer layout");
280         }
281     }
282 }