]>
Commit | Line | Data |
---|---|---|
a9b4942f BS |
1 | /* |
2 | * QEMU SEV support | |
3 | * | |
4 | * Copyright Advanced Micro Devices 2016-2018 | |
5 | * | |
6 | * Author: | |
7 | * Brijesh Singh <[email protected]> | |
8 | * | |
9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
10 | * See the COPYING file in the top-level directory. | |
11 | * | |
12 | */ | |
13 | ||
d8575c6c BS |
14 | #include <linux/kvm.h> |
15 | #include <linux/psp-sev.h> | |
16 | ||
17 | #include <sys/ioctl.h> | |
18 | ||
a9b4942f BS |
19 | #include "qemu/osdep.h" |
20 | #include "qapi/error.h" | |
21 | #include "qom/object_interfaces.h" | |
22 | #include "qemu/base64.h" | |
23 | #include "sysemu/kvm.h" | |
24 | #include "sev_i386.h" | |
25 | #include "sysemu/sysemu.h" | |
d8575c6c | 26 | #include "trace.h" |
a9b4942f BS |
27 | |
28 | #define DEFAULT_GUEST_POLICY 0x1 /* disable debug */ | |
29 | #define DEFAULT_SEV_DEVICE "/dev/sev" | |
30 | ||
d8575c6c BS |
31 | static SEVState *sev_state; |
32 | ||
33 | static const char *const sev_fw_errlist[] = { | |
34 | "", | |
35 | "Platform state is invalid", | |
36 | "Guest state is invalid", | |
37 | "Platform configuration is invalid", | |
38 | "Buffer too small", | |
39 | "Platform is already owned", | |
40 | "Certificate is invalid", | |
41 | "Policy is not allowed", | |
42 | "Guest is not active", | |
43 | "Invalid address", | |
44 | "Bad signature", | |
45 | "Bad measurement", | |
46 | "Asid is already owned", | |
47 | "Invalid ASID", | |
48 | "WBINVD is required", | |
49 | "DF_FLUSH is required", | |
50 | "Guest handle is invalid", | |
51 | "Invalid command", | |
52 | "Guest is active", | |
53 | "Hardware error", | |
54 | "Hardware unsafe", | |
55 | "Feature not supported", | |
56 | "Invalid parameter" | |
57 | }; | |
58 | ||
59 | #define SEV_FW_MAX_ERROR ARRAY_SIZE(sev_fw_errlist) | |
60 | ||
61 | static int | |
62 | sev_ioctl(int fd, int cmd, void *data, int *error) | |
63 | { | |
64 | int r; | |
65 | struct kvm_sev_cmd input; | |
66 | ||
67 | memset(&input, 0x0, sizeof(input)); | |
68 | ||
69 | input.id = cmd; | |
70 | input.sev_fd = fd; | |
71 | input.data = (__u64)(unsigned long)data; | |
72 | ||
73 | r = kvm_vm_ioctl(kvm_state, KVM_MEMORY_ENCRYPT_OP, &input); | |
74 | ||
75 | if (error) { | |
76 | *error = input.error; | |
77 | } | |
78 | ||
79 | return r; | |
80 | } | |
81 | ||
82 | static int | |
83 | sev_platform_ioctl(int fd, int cmd, void *data, int *error) | |
84 | { | |
85 | int r; | |
86 | struct sev_issue_cmd arg; | |
87 | ||
88 | arg.cmd = cmd; | |
89 | arg.data = (unsigned long)data; | |
90 | r = ioctl(fd, SEV_ISSUE_CMD, &arg); | |
91 | if (error) { | |
92 | *error = arg.error; | |
93 | } | |
94 | ||
95 | return r; | |
96 | } | |
97 | ||
98 | static const char * | |
99 | fw_error_to_str(int code) | |
100 | { | |
101 | if (code < 0 || code >= SEV_FW_MAX_ERROR) { | |
102 | return "unknown error"; | |
103 | } | |
104 | ||
105 | return sev_fw_errlist[code]; | |
106 | } | |
107 | ||
2b308e44 BS |
108 | static void |
109 | sev_ram_block_added(RAMBlockNotifier *n, void *host, size_t size) | |
110 | { | |
111 | int r; | |
112 | struct kvm_enc_region range; | |
113 | ||
114 | range.addr = (__u64)(unsigned long)host; | |
115 | range.size = size; | |
116 | ||
117 | trace_kvm_memcrypt_register_region(host, size); | |
118 | r = kvm_vm_ioctl(kvm_state, KVM_MEMORY_ENCRYPT_REG_REGION, &range); | |
119 | if (r) { | |
120 | error_report("%s: failed to register region (%p+%#zx) error '%s'", | |
121 | __func__, host, size, strerror(errno)); | |
122 | exit(1); | |
123 | } | |
124 | } | |
125 | ||
126 | static void | |
127 | sev_ram_block_removed(RAMBlockNotifier *n, void *host, size_t size) | |
128 | { | |
129 | int r; | |
130 | struct kvm_enc_region range; | |
131 | ||
132 | range.addr = (__u64)(unsigned long)host; | |
133 | range.size = size; | |
134 | ||
135 | trace_kvm_memcrypt_unregister_region(host, size); | |
136 | r = kvm_vm_ioctl(kvm_state, KVM_MEMORY_ENCRYPT_UNREG_REGION, &range); | |
137 | if (r) { | |
138 | error_report("%s: failed to unregister region (%p+%#zx)", | |
139 | __func__, host, size); | |
140 | } | |
141 | } | |
142 | ||
143 | static struct RAMBlockNotifier sev_ram_notifier = { | |
144 | .ram_block_added = sev_ram_block_added, | |
145 | .ram_block_removed = sev_ram_block_removed, | |
146 | }; | |
147 | ||
a9b4942f BS |
148 | static void |
149 | qsev_guest_finalize(Object *obj) | |
150 | { | |
151 | } | |
152 | ||
153 | static char * | |
154 | qsev_guest_get_session_file(Object *obj, Error **errp) | |
155 | { | |
156 | QSevGuestInfo *s = QSEV_GUEST_INFO(obj); | |
157 | ||
158 | return s->session_file ? g_strdup(s->session_file) : NULL; | |
159 | } | |
160 | ||
161 | static void | |
162 | qsev_guest_set_session_file(Object *obj, const char *value, Error **errp) | |
163 | { | |
164 | QSevGuestInfo *s = QSEV_GUEST_INFO(obj); | |
165 | ||
166 | s->session_file = g_strdup(value); | |
167 | } | |
168 | ||
169 | static char * | |
170 | qsev_guest_get_dh_cert_file(Object *obj, Error **errp) | |
171 | { | |
172 | QSevGuestInfo *s = QSEV_GUEST_INFO(obj); | |
173 | ||
174 | return g_strdup(s->dh_cert_file); | |
175 | } | |
176 | ||
177 | static void | |
178 | qsev_guest_set_dh_cert_file(Object *obj, const char *value, Error **errp) | |
179 | { | |
180 | QSevGuestInfo *s = QSEV_GUEST_INFO(obj); | |
181 | ||
182 | s->dh_cert_file = g_strdup(value); | |
183 | } | |
184 | ||
185 | static char * | |
186 | qsev_guest_get_sev_device(Object *obj, Error **errp) | |
187 | { | |
188 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
189 | ||
190 | return g_strdup(sev->sev_device); | |
191 | } | |
192 | ||
193 | static void | |
194 | qsev_guest_set_sev_device(Object *obj, const char *value, Error **errp) | |
195 | { | |
196 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
197 | ||
198 | sev->sev_device = g_strdup(value); | |
199 | } | |
200 | ||
201 | static void | |
202 | qsev_guest_class_init(ObjectClass *oc, void *data) | |
203 | { | |
204 | object_class_property_add_str(oc, "sev-device", | |
205 | qsev_guest_get_sev_device, | |
206 | qsev_guest_set_sev_device, | |
207 | NULL); | |
208 | object_class_property_set_description(oc, "sev-device", | |
209 | "SEV device to use", NULL); | |
210 | object_class_property_add_str(oc, "dh-cert-file", | |
211 | qsev_guest_get_dh_cert_file, | |
212 | qsev_guest_set_dh_cert_file, | |
213 | NULL); | |
214 | object_class_property_set_description(oc, "dh-cert-file", | |
215 | "guest owners DH certificate (encoded with base64)", NULL); | |
216 | object_class_property_add_str(oc, "session-file", | |
217 | qsev_guest_get_session_file, | |
218 | qsev_guest_set_session_file, | |
219 | NULL); | |
220 | object_class_property_set_description(oc, "session-file", | |
221 | "guest owners session parameters (encoded with base64)", NULL); | |
222 | } | |
223 | ||
224 | static void | |
225 | qsev_guest_set_handle(Object *obj, Visitor *v, const char *name, | |
226 | void *opaque, Error **errp) | |
227 | { | |
228 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
229 | uint32_t value; | |
230 | ||
231 | visit_type_uint32(v, name, &value, errp); | |
232 | sev->handle = value; | |
233 | } | |
234 | ||
235 | static void | |
236 | qsev_guest_set_policy(Object *obj, Visitor *v, const char *name, | |
237 | void *opaque, Error **errp) | |
238 | { | |
239 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
240 | uint32_t value; | |
241 | ||
242 | visit_type_uint32(v, name, &value, errp); | |
243 | sev->policy = value; | |
244 | } | |
245 | ||
246 | static void | |
247 | qsev_guest_set_cbitpos(Object *obj, Visitor *v, const char *name, | |
248 | void *opaque, Error **errp) | |
249 | { | |
250 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
251 | uint32_t value; | |
252 | ||
253 | visit_type_uint32(v, name, &value, errp); | |
254 | sev->cbitpos = value; | |
255 | } | |
256 | ||
257 | static void | |
258 | qsev_guest_set_reduced_phys_bits(Object *obj, Visitor *v, const char *name, | |
259 | void *opaque, Error **errp) | |
260 | { | |
261 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
262 | uint32_t value; | |
263 | ||
264 | visit_type_uint32(v, name, &value, errp); | |
265 | sev->reduced_phys_bits = value; | |
266 | } | |
267 | ||
268 | static void | |
269 | qsev_guest_get_policy(Object *obj, Visitor *v, const char *name, | |
270 | void *opaque, Error **errp) | |
271 | { | |
272 | uint32_t value; | |
273 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
274 | ||
275 | value = sev->policy; | |
276 | visit_type_uint32(v, name, &value, errp); | |
277 | } | |
278 | ||
279 | static void | |
280 | qsev_guest_get_handle(Object *obj, Visitor *v, const char *name, | |
281 | void *opaque, Error **errp) | |
282 | { | |
283 | uint32_t value; | |
284 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
285 | ||
286 | value = sev->handle; | |
287 | visit_type_uint32(v, name, &value, errp); | |
288 | } | |
289 | ||
290 | static void | |
291 | qsev_guest_get_cbitpos(Object *obj, Visitor *v, const char *name, | |
292 | void *opaque, Error **errp) | |
293 | { | |
294 | uint32_t value; | |
295 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
296 | ||
297 | value = sev->cbitpos; | |
298 | visit_type_uint32(v, name, &value, errp); | |
299 | } | |
300 | ||
301 | static void | |
302 | qsev_guest_get_reduced_phys_bits(Object *obj, Visitor *v, const char *name, | |
303 | void *opaque, Error **errp) | |
304 | { | |
305 | uint32_t value; | |
306 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
307 | ||
308 | value = sev->reduced_phys_bits; | |
309 | visit_type_uint32(v, name, &value, errp); | |
310 | } | |
311 | ||
312 | static void | |
313 | qsev_guest_init(Object *obj) | |
314 | { | |
315 | QSevGuestInfo *sev = QSEV_GUEST_INFO(obj); | |
316 | ||
317 | sev->sev_device = g_strdup(DEFAULT_SEV_DEVICE); | |
318 | sev->policy = DEFAULT_GUEST_POLICY; | |
319 | object_property_add(obj, "policy", "uint32", qsev_guest_get_policy, | |
320 | qsev_guest_set_policy, NULL, NULL, NULL); | |
321 | object_property_add(obj, "handle", "uint32", qsev_guest_get_handle, | |
322 | qsev_guest_set_handle, NULL, NULL, NULL); | |
323 | object_property_add(obj, "cbitpos", "uint32", qsev_guest_get_cbitpos, | |
324 | qsev_guest_set_cbitpos, NULL, NULL, NULL); | |
325 | object_property_add(obj, "reduced-phys-bits", "uint32", | |
326 | qsev_guest_get_reduced_phys_bits, | |
327 | qsev_guest_set_reduced_phys_bits, NULL, NULL, NULL); | |
328 | } | |
329 | ||
330 | /* sev guest info */ | |
331 | static const TypeInfo qsev_guest_info = { | |
332 | .parent = TYPE_OBJECT, | |
333 | .name = TYPE_QSEV_GUEST_INFO, | |
334 | .instance_size = sizeof(QSevGuestInfo), | |
335 | .instance_finalize = qsev_guest_finalize, | |
336 | .class_size = sizeof(QSevGuestInfoClass), | |
337 | .class_init = qsev_guest_class_init, | |
338 | .instance_init = qsev_guest_init, | |
339 | .interfaces = (InterfaceInfo[]) { | |
340 | { TYPE_USER_CREATABLE }, | |
341 | { } | |
342 | } | |
343 | }; | |
344 | ||
d8575c6c BS |
345 | static QSevGuestInfo * |
346 | lookup_sev_guest_info(const char *id) | |
347 | { | |
348 | Object *obj; | |
349 | QSevGuestInfo *info; | |
350 | ||
351 | obj = object_resolve_path_component(object_get_objects_root(), id); | |
352 | if (!obj) { | |
353 | return NULL; | |
354 | } | |
355 | ||
356 | info = (QSevGuestInfo *) | |
357 | object_dynamic_cast(obj, TYPE_QSEV_GUEST_INFO); | |
358 | if (!info) { | |
359 | return NULL; | |
360 | } | |
361 | ||
362 | return info; | |
363 | } | |
364 | ||
365 | bool | |
366 | sev_enabled(void) | |
367 | { | |
368 | return sev_state ? true : false; | |
369 | } | |
370 | ||
371 | uint64_t | |
372 | sev_get_me_mask(void) | |
373 | { | |
374 | return sev_state ? sev_state->me_mask : ~0; | |
375 | } | |
376 | ||
377 | uint32_t | |
378 | sev_get_cbit_position(void) | |
379 | { | |
380 | return sev_state ? sev_state->cbitpos : 0; | |
381 | } | |
382 | ||
383 | uint32_t | |
384 | sev_get_reduced_phys_bits(void) | |
385 | { | |
386 | return sev_state ? sev_state->reduced_phys_bits : 0; | |
387 | } | |
388 | ||
389 | SevInfo * | |
390 | sev_get_info(void) | |
391 | { | |
392 | SevInfo *info; | |
393 | ||
394 | info = g_new0(SevInfo, 1); | |
395 | info->enabled = sev_state ? true : false; | |
396 | ||
397 | if (info->enabled) { | |
398 | info->api_major = sev_state->api_major; | |
399 | info->api_minor = sev_state->api_minor; | |
400 | info->build_id = sev_state->build_id; | |
401 | info->policy = sev_state->policy; | |
402 | info->state = sev_state->state; | |
403 | info->handle = sev_state->handle; | |
404 | } | |
405 | ||
406 | return info; | |
407 | } | |
408 | ||
409 | void * | |
410 | sev_guest_init(const char *id) | |
411 | { | |
412 | SEVState *s; | |
413 | char *devname; | |
414 | int ret, fw_error; | |
415 | uint32_t ebx; | |
416 | uint32_t host_cbitpos; | |
417 | struct sev_user_data_status status = {}; | |
418 | ||
419 | s = g_new0(SEVState, 1); | |
420 | s->sev_info = lookup_sev_guest_info(id); | |
421 | if (!s->sev_info) { | |
422 | error_report("%s: '%s' is not a valid '%s' object", | |
423 | __func__, id, TYPE_QSEV_GUEST_INFO); | |
424 | goto err; | |
425 | } | |
426 | ||
427 | sev_state = s; | |
428 | s->state = SEV_STATE_UNINIT; | |
429 | ||
430 | host_cpuid(0x8000001F, 0, NULL, &ebx, NULL, NULL); | |
431 | host_cbitpos = ebx & 0x3f; | |
432 | ||
433 | s->cbitpos = object_property_get_int(OBJECT(s->sev_info), "cbitpos", NULL); | |
434 | if (host_cbitpos != s->cbitpos) { | |
435 | error_report("%s: cbitpos check failed, host '%d' requested '%d'", | |
436 | __func__, host_cbitpos, s->cbitpos); | |
437 | goto err; | |
438 | } | |
439 | ||
440 | s->reduced_phys_bits = object_property_get_int(OBJECT(s->sev_info), | |
441 | "reduced-phys-bits", NULL); | |
442 | if (s->reduced_phys_bits < 1) { | |
443 | error_report("%s: reduced_phys_bits check failed, it should be >=1," | |
444 | "' requested '%d'", __func__, s->reduced_phys_bits); | |
445 | goto err; | |
446 | } | |
447 | ||
448 | s->me_mask = ~(1UL << s->cbitpos); | |
449 | ||
450 | devname = object_property_get_str(OBJECT(s->sev_info), "sev-device", NULL); | |
451 | s->sev_fd = open(devname, O_RDWR); | |
452 | if (s->sev_fd < 0) { | |
453 | error_report("%s: Failed to open %s '%s'", __func__, | |
454 | devname, strerror(errno)); | |
455 | goto err; | |
456 | } | |
457 | g_free(devname); | |
458 | ||
459 | ret = sev_platform_ioctl(s->sev_fd, SEV_PLATFORM_STATUS, &status, | |
460 | &fw_error); | |
461 | if (ret) { | |
462 | error_report("%s: failed to get platform status ret=%d" | |
463 | "fw_error='%d: %s'", __func__, ret, fw_error, | |
464 | fw_error_to_str(fw_error)); | |
465 | goto err; | |
466 | } | |
467 | s->build_id = status.build; | |
468 | s->api_major = status.api_major; | |
469 | s->api_minor = status.api_minor; | |
470 | ||
471 | trace_kvm_sev_init(); | |
472 | ret = sev_ioctl(s->sev_fd, KVM_SEV_INIT, NULL, &fw_error); | |
473 | if (ret) { | |
474 | error_report("%s: failed to initialize ret=%d fw_error=%d '%s'", | |
475 | __func__, ret, fw_error, fw_error_to_str(fw_error)); | |
476 | goto err; | |
477 | } | |
478 | ||
2b308e44 BS |
479 | ram_block_notifier_add(&sev_ram_notifier); |
480 | ||
d8575c6c BS |
481 | return s; |
482 | err: | |
483 | g_free(sev_state); | |
484 | sev_state = NULL; | |
485 | return NULL; | |
486 | } | |
487 | ||
a9b4942f BS |
488 | static void |
489 | sev_register_types(void) | |
490 | { | |
491 | type_register_static(&qsev_guest_info); | |
492 | } | |
493 | ||
494 | type_init(sev_register_types); |