| /* |
| * 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 "qemu/error-report.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> |
| #include "hw/acpi/aml-build.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 int sgx_epc_device_list(Object *obj, void *opaque) |
| { |
| GSList **list = opaque; |
| |
| if (object_dynamic_cast(obj, TYPE_SGX_EPC)) { |
| *list = g_slist_append(*list, DEVICE(obj)); |
| } |
| |
| object_child_foreach(obj, sgx_epc_device_list, opaque); |
| return 0; |
| } |
| |
| static GSList *sgx_epc_get_device_list(void) |
| { |
| GSList *list = NULL; |
| |
| object_child_foreach(qdev_get_machine(), sgx_epc_device_list, &list); |
| return list; |
| } |
| |
| void sgx_epc_build_srat(GArray *table_data) |
| { |
| GSList *device_list = sgx_epc_get_device_list(); |
| |
| for (; device_list; device_list = device_list->next) { |
| DeviceState *dev = device_list->data; |
| Object *obj = OBJECT(dev); |
| uint64_t addr, size; |
| int node; |
| |
| node = object_property_get_uint(obj, SGX_EPC_NUMA_NODE_PROP, |
| &error_abort); |
| addr = object_property_get_uint(obj, SGX_EPC_ADDR_PROP, &error_abort); |
| size = object_property_get_uint(obj, SGX_EPC_SIZE_PROP, &error_abort); |
| |
| build_srat_memory(table_data, addr, size, node, MEM_AFFINITY_ENABLED); |
| } |
| g_slist_free(device_list); |
| } |
| |
| 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 SGXEPCSectionList *sgx_calc_host_epc_sections(void) |
| { |
| SGXEPCSectionList *head = NULL, **tail = &head; |
| SGXEPCSection *section; |
| uint32_t i, type; |
| uint32_t eax, ebx, ecx, edx; |
| uint32_t j = 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; |
| } |
| |
| section = g_new0(SGXEPCSection, 1); |
| section->node = j++; |
| section->size = sgx_calc_section_metric(ecx, edx); |
| QAPI_LIST_APPEND(tail, section); |
| } |
| |
| return head; |
| } |
| |
| 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; |
| Error *local_err = NULL; |
| |
| int fd = qemu_open("/dev/sgx_vepc", O_RDWR, &local_err); |
| if (fd < 0) { |
| error_append_hint(&local_err, "SGX is not enabled in KVM"); |
| error_propagate(errp, local_err); |
| 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->sections = sgx_calc_host_epc_sections(); |
| |
| close(fd); |
| |
| return info; |
| } |
| |
| static SGXEPCSectionList *sgx_get_epc_sections_list(void) |
| { |
| GSList *device_list = sgx_epc_get_device_list(); |
| SGXEPCSectionList *head = NULL, **tail = &head; |
| SGXEPCSection *section; |
| |
| for (; device_list; device_list = device_list->next) { |
| DeviceState *dev = device_list->data; |
| Object *obj = OBJECT(dev); |
| |
| section = g_new0(SGXEPCSection, 1); |
| section->node = object_property_get_uint(obj, SGX_EPC_NUMA_NODE_PROP, |
| &error_abort); |
| section->size = object_property_get_uint(obj, SGX_EPC_SIZE_PROP, |
| &error_abort); |
| QAPI_LIST_APPEND(tail, section); |
| } |
| g_slist_free(device_list); |
| |
| return head; |
| } |
| |
| 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; |
| } |
| |
| info = g_new0(SGXInfo, 1); |
| |
| info->sgx = true; |
| info->sgx1 = true; |
| info->sgx2 = true; |
| info->flc = true; |
| info->sections = sgx_get_epc_sections_list(); |
| |
| return info; |
| } |
| |
| void hmp_info_sgx(Monitor *mon, const QDict *qdict) |
| { |
| Error *err = NULL; |
| SGXEPCSectionList *section_list, *section; |
| g_autoptr(SGXInfo) info = qmp_query_sgx(&err); |
| uint64_t size = 0; |
| |
| 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"); |
| |
| section_list = info->sections; |
| for (section = section_list; section; section = section->next) { |
| monitor_printf(mon, "NUMA node #%" PRId64 ": ", |
| section->value->node); |
| monitor_printf(mon, "size=%" PRIu64 "\n", |
| section->value->size); |
| size += section->value->size; |
| } |
| monitor_printf(mon, "total size=%" PRIu64 "\n", |
| size); |
| } |
| |
| bool check_sgx_support(void) |
| { |
| if (!object_dynamic_cast(qdev_get_machine(), TYPE_PC_MACHINE)) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool sgx_epc_get_section(int section_nr, uint64_t *addr, uint64_t *size) |
| { |
| PCMachineState *pcms = |
| (PCMachineState *)object_dynamic_cast(qdev_get_machine(), |
| TYPE_PC_MACHINE); |
| SGXEPCDevice *epc; |
| |
| if (!pcms || 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; |
| |
| memset(sgx_epc, 0, sizeof(SGXEPCState)); |
| if (!x86ms->sgx_epc_list) { |
| return; |
| } |
| |
| sgx_epc->base = x86ms->above_4g_mem_start + 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) { |
| DeviceState *dev = qdev_new(TYPE_SGX_EPC); |
| |
| /* set the memdev link with memory backend */ |
| object_property_parse(OBJECT(dev), SGX_EPC_MEMDEV_PROP, |
| list->value->memdev, &error_fatal); |
| /* set the numa node property for sgx epc object */ |
| object_property_set_uint(OBJECT(dev), SGX_EPC_NUMA_NODE_PROP, |
| list->value->node, &error_fatal); |
| qdev_realize_and_unref(dev, NULL, &error_fatal); |
| } |
| |
| if ((sgx_epc->base + sgx_epc->size) < sgx_epc->base) { |
| error_report("Size of all 'sgx-epc' =0x%"PRIx64" 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); |
| } |