| /* | 
 |  * 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 "system/hw_accel.h" | 
 | #include "system/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); | 
 | } |