aboutsummaryrefslogtreecommitdiffstats
path: root/hw/i386/sgx.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/i386/sgx.c')
-rw-r--r--hw/i386/sgx.c243
1 files changed, 243 insertions, 0 deletions
diff --git a/hw/i386/sgx.c b/hw/i386/sgx.c
new file mode 100644
index 000000000..8fef3dd8f
--- /dev/null
+++ b/hw/i386/sgx.c
@@ -0,0 +1,243 @@
+/*
+ * SGX common code
+ *
+ * Copyright (C) 2021 Intel Corporation
+ *
+ * Authors:
+ * Yang Zhong<yang.zhong@intel.com>
+ * Sean Christopherson <sean.j.christopherson@intel.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qemu/osdep.h"
+#include "hw/i386/pc.h"
+#include "hw/i386/sgx-epc.h"
+#include "hw/mem/memory-device.h"
+#include "monitor/qdev.h"
+#include "monitor/monitor.h"
+#include "monitor/hmp-target.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-misc-target.h"
+#include "exec/address-spaces.h"
+#include "sysemu/hw_accel.h"
+#include "sysemu/reset.h"
+#include <sys/ioctl.h>
+
+#define SGX_MAX_EPC_SECTIONS 8
+#define SGX_CPUID_EPC_INVALID 0x0
+
+/* A valid EPC section. */
+#define SGX_CPUID_EPC_SECTION 0x1
+#define SGX_CPUID_EPC_MASK 0xF
+
+#define SGX_MAGIC 0xA4
+#define SGX_IOC_VEPC_REMOVE_ALL _IO(SGX_MAGIC, 0x04)
+
+#define RETRY_NUM 2
+
+static uint64_t sgx_calc_section_metric(uint64_t low, uint64_t high)
+{
+ return (low & MAKE_64BIT_MASK(12, 20)) +
+ ((high & MAKE_64BIT_MASK(0, 20)) << 32);
+}
+
+static uint64_t sgx_calc_host_epc_section_size(void)
+{
+ uint32_t i, type;
+ uint32_t eax, ebx, ecx, edx;
+ uint64_t size = 0;
+
+ for (i = 0; i < SGX_MAX_EPC_SECTIONS; i++) {
+ host_cpuid(0x12, i + 2, &eax, &ebx, &ecx, &edx);
+
+ type = eax & SGX_CPUID_EPC_MASK;
+ if (type == SGX_CPUID_EPC_INVALID) {
+ break;
+ }
+
+ if (type != SGX_CPUID_EPC_SECTION) {
+ break;
+ }
+
+ size += sgx_calc_section_metric(ecx, edx);
+ }
+
+ return size;
+}
+
+static void sgx_epc_reset(void *opaque)
+{
+ PCMachineState *pcms = PC_MACHINE(qdev_get_machine());
+ HostMemoryBackend *hostmem;
+ SGXEPCDevice *epc;
+ int failures;
+ int fd, i, j, r;
+ static bool warned = false;
+
+ /*
+ * The second pass is needed to remove SECS pages that could not
+ * be removed during the first.
+ */
+ for (i = 0; i < RETRY_NUM; i++) {
+ failures = 0;
+ for (j = 0; j < pcms->sgx_epc.nr_sections; j++) {
+ epc = pcms->sgx_epc.sections[j];
+ hostmem = MEMORY_BACKEND(epc->hostmem);
+ fd = memory_region_get_fd(host_memory_backend_get_memory(hostmem));
+
+ r = ioctl(fd, SGX_IOC_VEPC_REMOVE_ALL);
+ if (r == -ENOTTY && !warned) {
+ warned = true;
+ warn_report("kernel does not support SGX_IOC_VEPC_REMOVE_ALL");
+ warn_report("SGX might operate incorrectly in the guest after reset");
+ break;
+ } else if (r > 0) {
+ /* SECS pages remain */
+ failures++;
+ if (i == 1) {
+ error_report("cannot reset vEPC section %d", j);
+ }
+ }
+ }
+ if (!failures) {
+ break;
+ }
+ }
+}
+
+SGXInfo *qmp_query_sgx_capabilities(Error **errp)
+{
+ SGXInfo *info = NULL;
+ uint32_t eax, ebx, ecx, edx;
+
+ int fd = qemu_open_old("/dev/sgx_vepc", O_RDWR);
+ if (fd < 0) {
+ error_setg(errp, "SGX is not enabled in KVM");
+ return NULL;
+ }
+
+ info = g_new0(SGXInfo, 1);
+ host_cpuid(0x7, 0, &eax, &ebx, &ecx, &edx);
+
+ info->sgx = ebx & (1U << 2) ? true : false;
+ info->flc = ecx & (1U << 30) ? true : false;
+
+ host_cpuid(0x12, 0, &eax, &ebx, &ecx, &edx);
+ info->sgx1 = eax & (1U << 0) ? true : false;
+ info->sgx2 = eax & (1U << 1) ? true : false;
+
+ info->section_size = sgx_calc_host_epc_section_size();
+
+ close(fd);
+
+ return info;
+}
+
+SGXInfo *qmp_query_sgx(Error **errp)
+{
+ SGXInfo *info = NULL;
+ X86MachineState *x86ms;
+ PCMachineState *pcms =
+ (PCMachineState *)object_dynamic_cast(qdev_get_machine(),
+ TYPE_PC_MACHINE);
+ if (!pcms) {
+ error_setg(errp, "SGX is only supported on PC machines");
+ return NULL;
+ }
+
+ x86ms = X86_MACHINE(pcms);
+ if (!x86ms->sgx_epc_list) {
+ error_setg(errp, "No EPC regions defined, SGX not available");
+ return NULL;
+ }
+
+ SGXEPCState *sgx_epc = &pcms->sgx_epc;
+ info = g_new0(SGXInfo, 1);
+
+ info->sgx = true;
+ info->sgx1 = true;
+ info->sgx2 = true;
+ info->flc = true;
+ info->section_size = sgx_epc->size;
+
+ return info;
+}
+
+void hmp_info_sgx(Monitor *mon, const QDict *qdict)
+{
+ Error *err = NULL;
+ g_autoptr(SGXInfo) info = qmp_query_sgx(&err);
+
+ if (err) {
+ error_report_err(err);
+ return;
+ }
+ monitor_printf(mon, "SGX support: %s\n",
+ info->sgx ? "enabled" : "disabled");
+ monitor_printf(mon, "SGX1 support: %s\n",
+ info->sgx1 ? "enabled" : "disabled");
+ monitor_printf(mon, "SGX2 support: %s\n",
+ info->sgx2 ? "enabled" : "disabled");
+ monitor_printf(mon, "FLC support: %s\n",
+ info->flc ? "enabled" : "disabled");
+ monitor_printf(mon, "size: %" PRIu64 "\n",
+ info->section_size);
+}
+
+bool sgx_epc_get_section(int section_nr, uint64_t *addr, uint64_t *size)
+{
+ PCMachineState *pcms = PC_MACHINE(qdev_get_machine());
+ SGXEPCDevice *epc;
+
+ if (pcms->sgx_epc.size == 0 || pcms->sgx_epc.nr_sections <= section_nr) {
+ return true;
+ }
+
+ epc = pcms->sgx_epc.sections[section_nr];
+
+ *addr = epc->addr;
+ *size = memory_device_get_region_size(MEMORY_DEVICE(epc), &error_fatal);
+
+ return false;
+}
+
+void pc_machine_init_sgx_epc(PCMachineState *pcms)
+{
+ SGXEPCState *sgx_epc = &pcms->sgx_epc;
+ X86MachineState *x86ms = X86_MACHINE(pcms);
+ SgxEPCList *list = NULL;
+ Object *obj;
+
+ memset(sgx_epc, 0, sizeof(SGXEPCState));
+ if (!x86ms->sgx_epc_list) {
+ return;
+ }
+
+ sgx_epc->base = 0x100000000ULL + x86ms->above_4g_mem_size;
+
+ memory_region_init(&sgx_epc->mr, OBJECT(pcms), "sgx-epc", UINT64_MAX);
+ memory_region_add_subregion(get_system_memory(), sgx_epc->base,
+ &sgx_epc->mr);
+
+ for (list = x86ms->sgx_epc_list; list; list = list->next) {
+ obj = object_new("sgx-epc");
+
+ /* set the memdev link with memory backend */
+ object_property_parse(obj, SGX_EPC_MEMDEV_PROP, list->value->memdev,
+ &error_fatal);
+ object_property_set_bool(obj, "realized", true, &error_fatal);
+ object_unref(obj);
+ }
+
+ if ((sgx_epc->base + sgx_epc->size) < sgx_epc->base) {
+ error_report("Size of all 'sgx-epc' =0x%"PRIu64" causes EPC to wrap",
+ sgx_epc->size);
+ exit(EXIT_FAILURE);
+ }
+
+ memory_region_set_size(&sgx_epc->mr, sgx_epc->size);
+
+ /* register the reset callback for sgx epc */
+ qemu_register_reset(sgx_epc_reset, NULL);
+}