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 }