| /* SPDX-License-Identifier: BSD-2-Clause */ |
| |
| #include <console/console.h> |
| #include <cpu/x86/cr.h> |
| #include <cpu/x86/mp.h> |
| #include <cpu/x86/msr.h> |
| #include <cpu/x86/cache.h> |
| #include <security/intel/stm/SmmStm.h> |
| #include <stdbool.h> |
| #include <string.h> |
| |
| #define TXT_EVTYPE_BASE 0x400 |
| #define TXT_EVTYPE_STM_HASH (TXT_EVTYPE_BASE + 14) |
| |
| #define RDWR_ACCS 3 |
| #define FULL_ACCS 7 |
| |
| #define SIZE_4KB 0x00001000 |
| #define SIZE_4MB 0x00400000 |
| |
| #define PTP_SIZE SIZE_4KB |
| |
| #define IA32_PG_P (1 << 0) |
| #define IA32_PG_RW (1 << 1) |
| #define IA32_PG_PS (1 << 7) |
| |
| #define STM_PAGE_SHIFT 12 |
| #define STM_PAGE_MASK 0xFFF |
| #define STM_SIZE_TO_PAGES(a) \ |
| (((a) >> STM_PAGE_SHIFT) + (((a)&STM_PAGE_MASK) ? 1 : 0)) |
| #define STM_PAGES_TO_SIZE(a) ((a) << STM_PAGE_SHIFT) |
| |
| #define STM_ACCESS_DENIED 15 |
| #define STM_UNSUPPORTED 3 |
| |
| #define STM_BUFFER_TOO_SMALL 1 |
| |
| #define STM_SM_MONITOR_STATE_ENABLED 1 |
| |
| typedef struct { |
| |
| uint64_t vmcs_revision_id : 31; |
| uint64_t always_zero : 1; |
| uint64_t vmcs_size : 13; |
| uint64_t reserved1 : 3; |
| uint64_t vmxon_add_width : 1; |
| uint64_t stm_supported : 1; |
| uint64_t vmcs_memory_type : 4; |
| uint64_t in_out_reporting : 1; |
| uint64_t may_clear_defaults : 1; |
| uint64_t reserved2 : 8; |
| } VMX_BASIC_MSR_BITS; |
| |
| typedef union { |
| VMX_BASIC_MSR_BITS bits; |
| uint64_t uint64; |
| msr_t msr; |
| } VMX_BASIC_MSR; |
| |
| typedef struct { |
| uint64_t valid : 1; |
| uint64_t reserved1 : 1; |
| uint64_t vmx_off_blockSmi : 1; |
| uint64_t reserved2 : 9; |
| uint64_t mseg_address : 20; |
| uint64_t reserved3 : 32; |
| } SMM_MONITOR_CTL_MSR_BITS; |
| |
| extern struct mp_state { |
| struct mp_ops ops; |
| int cpu_count; |
| uintptr_t perm_smbase; |
| size_t perm_smsize; |
| size_t smm_save_state_size; |
| bool do_smm; |
| } mp_state; |
| |
| typedef union { |
| SMM_MONITOR_CTL_MSR_BITS bits; |
| uint64_t uint64; |
| msr_t msr; |
| } SMM_MONITOR_CTL_MSR; |
| |
| // Template of STM_RSC_END structure for copying. |
| |
| STM_RSC_END m_rsc_end_node = { |
| {END_OF_RESOURCES, sizeof(STM_RSC_END)}, |
| }; |
| |
| uint8_t *m_stm_resources_ptr = NULL; |
| uint32_t m_stm_resource_total_size = 0x0; |
| uint32_t m_stm_resource_size_used = 0x0; |
| uint32_t m_stm_resource_size_available = 0x0; |
| |
| uint8_t *stm_resource_heap = NULL; |
| |
| uint32_t m_stm_state = 0; |
| |
| /* |
| * Handle single Resource to see if it can be merged into Record. |
| * |
| * @param resource A pointer to resource node to be added |
| * @param record A pointer to record node to be merged |
| * |
| * @retval true resource handled |
| * @retval false resource is not handled |
| */ |
| |
| static bool handle_single_resource(STM_RSC *resource, STM_RSC *record) |
| { |
| uint64_t resource_lo = 0; |
| uint64_t resource_hi = 0; |
| uint64_t record_lo = 0; |
| uint64_t record_hi = 0; |
| |
| // Calling code is responsible for making sure that |
| // Resource->Header.RscType == (*Record)->Header.RscType |
| // thus we use just one of them as switch variable. |
| |
| switch (resource->header.rsc_type) { |
| case MEM_RANGE: |
| case MMIO_RANGE: |
| resource_lo = resource->mem.base; |
| resource_hi = resource->mem.base + resource->mem.length; |
| record_lo = record->mem.base; |
| record_hi = record->mem.base + record->mem.length; |
| if (resource->mem.rwx_attributes |
| != record->mem.rwx_attributes) { |
| if ((resource_lo == record_lo) |
| && (resource_hi == record_hi)) { |
| record->mem.rwx_attributes = |
| resource->mem.rwx_attributes |
| | record->mem.rwx_attributes; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| break; |
| case IO_RANGE: |
| case TRAPPED_IO_RANGE: |
| resource_lo = (uint64_t)resource->io.base; |
| resource_hi = (uint64_t)resource->io.base |
| + (uint64_t)resource->io.length; |
| record_lo = (uint64_t)record->io.base; |
| record_hi = |
| (uint64_t)record->io.base + (uint64_t)record->io.length; |
| break; |
| case PCI_CFG_RANGE: |
| if ((resource->pci_cfg.originating_bus_number |
| != record->pci_cfg.originating_bus_number) |
| || (resource->pci_cfg.last_node_index |
| != record->pci_cfg.last_node_index)) |
| return false; |
| |
| if (memcmp(resource->pci_cfg.pci_device_path, |
| record->pci_cfg.pci_device_path, |
| sizeof(STM_PCI_DEVICE_PATH_NODE) |
| * (resource->pci_cfg.last_node_index + 1)) |
| != 0) { |
| return false; |
| } |
| resource_lo = (uint64_t)resource->pci_cfg.base; |
| resource_hi = (uint64_t)resource->pci_cfg.base |
| + (uint64_t)resource->pci_cfg.length; |
| record_lo = (uint64_t)record->pci_cfg.base; |
| record_hi = (uint64_t)record->pci_cfg.base |
| + (uint64_t)record->pci_cfg.length; |
| if (resource->pci_cfg.rw_attributes |
| != record->pci_cfg.rw_attributes) { |
| if ((resource_lo == record_lo) |
| && (resource_hi == record_hi)) { |
| record->pci_cfg.rw_attributes = |
| resource->pci_cfg.rw_attributes |
| | record->pci_cfg.rw_attributes; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| break; |
| case MACHINE_SPECIFIC_REG: |
| |
| // Special case - merge MSR masks in place. |
| if (resource->msr.msr_index != record->msr.msr_index) |
| return false; |
| record->msr.read_mask |= resource->msr.read_mask; |
| record->msr.write_mask |= resource->msr.write_mask; |
| return true; |
| default: |
| return false; |
| } |
| |
| // If resources are disjoint |
| if ((resource_hi < record_lo) || (resource_lo > record_hi)) |
| return false; |
| |
| // If resource is consumed by record. |
| if ((resource_lo >= record_lo) && (resource_hi <= record_hi)) |
| return true; |
| |
| // Resources are overlapping. |
| // Resource and record are merged. |
| resource_lo = (resource_lo < record_lo) ? resource_lo : record_lo; |
| resource_hi = (resource_hi > record_hi) ? resource_hi : record_hi; |
| |
| switch (resource->header.rsc_type) { |
| case MEM_RANGE: |
| case MMIO_RANGE: |
| record->mem.base = resource_lo; |
| record->mem.length = resource_hi - resource_lo; |
| break; |
| case IO_RANGE: |
| case TRAPPED_IO_RANGE: |
| record->io.base = (uint64_t)resource_lo; |
| record->io.length = (uint64_t)(resource_hi - resource_lo); |
| break; |
| case PCI_CFG_RANGE: |
| record->pci_cfg.base = (uint64_t)resource_lo; |
| record->pci_cfg.length = (uint64_t)(resource_hi - resource_lo); |
| break; |
| default: |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Add resource node. |
| * |
| * @param Resource A pointer to resource node to be added |
| */ |
| static void add_single_resource(STM_RSC *resource) |
| { |
| STM_RSC *record; |
| |
| record = (STM_RSC *)m_stm_resources_ptr; |
| |
| while (true) { |
| if (record->header.rsc_type == END_OF_RESOURCES) |
| break; |
| |
| // Go to next record if resource and record types don't match. |
| if (resource->header.rsc_type != record->header.rsc_type) { |
| record = (STM_RSC *)((void *)record |
| + record->header.length); |
| continue; |
| } |
| |
| // Record is handled inside of procedure - don't adjust. |
| if (handle_single_resource(resource, record)) |
| return; |
| record = (STM_RSC *)((void *)record + record->header.length); |
| } |
| |
| // Add resource to the end of area. |
| memcpy(m_stm_resources_ptr + m_stm_resource_size_used |
| - sizeof(m_rsc_end_node), |
| resource, resource->header.length); |
| memcpy(m_stm_resources_ptr + m_stm_resource_size_used |
| - sizeof(m_rsc_end_node) + resource->header.length, |
| &m_rsc_end_node, sizeof(m_rsc_end_node)); |
| m_stm_resource_size_used += resource->header.length; |
| m_stm_resource_size_available = |
| m_stm_resource_total_size - m_stm_resource_size_used; |
| } |
| |
| /* |
| * Add resource list. |
| * |
| * @param resource_list A pointer to resource list to be added |
| * @param num_entries Optional number of entries. |
| * If 0, list must be terminated by END_OF_RESOURCES. |
| */ |
| static void add_resource(STM_RSC *resource_list, uint32_t num_entries) |
| { |
| uint32_t count; |
| uint32_t index; |
| STM_RSC *resource; |
| |
| if (num_entries == 0) |
| count = 0xFFFFFFFF; |
| else |
| count = num_entries; |
| |
| resource = resource_list; |
| |
| for (index = 0; index < count; index++) { |
| if (resource->header.rsc_type == END_OF_RESOURCES) |
| return; |
| add_single_resource(resource); |
| resource = |
| (STM_RSC *)((void *)resource + resource->header.length); |
| } |
| } |
| |
| /* |
| * Validate resource list. |
| * |
| * @param resource_list A pointer to resource list to be added |
| * @param num_entries Optional number of entries. |
| * If 0, list must be terminated by END_OF_RESOURCES. |
| * |
| * @retval true resource valid |
| * @retval false resource invalid |
| */ |
| static bool validate_resource(STM_RSC *resource_list, uint32_t num_entries) |
| { |
| uint32_t count; |
| uint32_t index; |
| STM_RSC *resource; |
| uint32_t sub_index; |
| |
| // If NumEntries == 0 make it very big. Scan will be terminated by |
| // END_OF_RESOURCES. |
| if (num_entries == 0) |
| count = 0xFFFFFFFF; |
| else |
| count = num_entries; |
| |
| // Start from beginning of resource list. |
| resource = resource_list; |
| |
| for (index = 0; index < count; index++) { |
| printk(BIOS_DEBUG, "STM: %s (%u) - RscType(%x) length(0x%x)\n", |
| __func__, |
| index, |
| resource->header.rsc_type, |
| resource->header.length); |
| // Validate resource. |
| switch (resource->header.rsc_type) { |
| case END_OF_RESOURCES: |
| if (resource->header.length != sizeof(STM_RSC_END)) |
| return false; |
| |
| // If we are passed actual number of resources to add, |
| // END_OF_RESOURCES structure between them is considered |
| // an error. If NumEntries == 0 END_OF_RESOURCES is a |
| // termination. |
| if (num_entries != 0) |
| return false; |
| |
| // If NumEntries == 0 and list reached end - return |
| // success. |
| return true; |
| |
| case MEM_RANGE: |
| case MMIO_RANGE: |
| printk(BIOS_DEBUG, |
| "STM: %s - MEM (0x%0llx, 0x%0llx)\n", |
| __func__, |
| resource->mem.base, |
| resource->mem.length); |
| |
| if (resource->header.length != sizeof(STM_RSC_MEM_DESC)) |
| return false; |
| |
| if (resource->mem.rwx_attributes > FULL_ACCS) |
| return false; |
| break; |
| |
| case IO_RANGE: |
| case TRAPPED_IO_RANGE: |
| if (resource->header.length != sizeof(STM_RSC_IO_DESC)) |
| return false; |
| |
| if ((resource->io.base + resource->io.length) > 0xFFFF) |
| return false; |
| break; |
| |
| case PCI_CFG_RANGE: |
| printk(BIOS_DEBUG, |
| "STM: %s - PCI (0x%02x, 0x%08x, 0x%02x, 0x%02x)\n", |
| __func__, |
| resource->pci_cfg.originating_bus_number, |
| resource->pci_cfg.last_node_index, |
| resource->pci_cfg.pci_device_path[0].pci_device, |
| resource->pci_cfg.pci_device_path[0] |
| .pci_function); |
| if (resource->header.length |
| != sizeof(STM_RSC_PCI_CFG_DESC) |
| + (sizeof(STM_PCI_DEVICE_PATH_NODE) |
| * resource->pci_cfg.last_node_index)) |
| return false; |
| for (sub_index = 0; |
| sub_index <= resource->pci_cfg.last_node_index; |
| sub_index++) { |
| if ((resource->pci_cfg |
| .pci_device_path[sub_index] |
| .pci_device |
| > 0x1F) |
| || (resource->pci_cfg |
| .pci_device_path[sub_index] |
| .pci_function |
| > 7)) |
| return false; |
| } |
| if ((resource->pci_cfg.base + resource->pci_cfg.length) |
| > 0x1000) |
| return false; |
| break; |
| |
| case MACHINE_SPECIFIC_REG: |
| if (resource->header.length != sizeof(STM_RSC_MSR_DESC)) |
| return false; |
| break; |
| |
| default: |
| printk(BIOS_DEBUG, "STM: %s - Unknown RscType(%x)\n", |
| __func__, resource->header.rsc_type); |
| return false; |
| } |
| resource = |
| (STM_RSC *)((void *)resource + resource->header.length); |
| } |
| return true; |
| } |
| |
| /* |
| * Get resource list. |
| * EndResource is excluded. |
| * |
| * @param resou rce_list A pointer to resource list to be added |
| * @param num_entries Optional number of entries. |
| * If 0, list must be terminated by END_OF_RESOURCES. |
| * |
| * @retval true resource valid |
| * @retval false resource invalid |
| */ |
| static uint32_t get_resource_size(STM_RSC *resource_list, uint32_t num_entries) |
| { |
| uint32_t count; |
| uint32_t index; |
| STM_RSC *resource; |
| |
| resource = resource_list; |
| |
| // If NumEntries == 0 make it very big. Scan will be terminated by |
| // END_OF_RESOURCES. |
| if (num_entries == 0) |
| count = 0xFFFFFFFF; |
| else |
| count = num_entries; |
| |
| // Start from beginning of resource list. |
| resource = resource_list; |
| |
| for (index = 0; index < count; index++) { |
| if (resource->header.rsc_type == END_OF_RESOURCES) |
| break; |
| resource = |
| (STM_RSC *)((void *)resource + resource->header.length); |
| } |
| return (uint32_t)((uint32_t)resource - (uint32_t)resource_list); |
| } |
| |
| /* |
| * Add resources in list to database. Allocate new memory areas as needed. |
| * |
| * @param resource_list A pointer to resource list to be added |
| * @param num_entries Optional number of entries. |
| * If 0, list must be terminated by END_OF_RESOURCES. |
| * |
| * @retval SUCCESS If resources are added |
| * @retval INVALID_PARAMETER If nested procedure detected resource failure |
| * @retval OUT_OF_RESOURCES If nested procedure returned it and we cannot |
| * allocate more areas. |
| */ |
| int add_pi_resource(STM_RSC *resource_list, uint32_t num_entries) |
| { |
| size_t resource_size; |
| |
| printk(BIOS_DEBUG, "STM: %s - Enter\n", __func__); |
| |
| if (!validate_resource(resource_list, num_entries)) |
| return -1; // INVALID_PARAMETER; |
| |
| resource_size = get_resource_size(resource_list, num_entries); |
| printk(BIOS_DEBUG, "STM: ResourceSize - 0x%08zx\n", resource_size); |
| if (resource_size == 0) |
| return -1; // INVALID_PARAMETER; |
| |
| if (!m_stm_resources_ptr) { |
| |
| // Copy EndResource for initialization |
| m_stm_resources_ptr = stm_resource_heap; |
| m_stm_resource_total_size = CONFIG_BIOS_RESOURCE_LIST_SIZE; |
| memset(m_stm_resources_ptr, 0, CONFIG_BIOS_RESOURCE_LIST_SIZE); |
| |
| memcpy(m_stm_resources_ptr, &m_rsc_end_node, |
| sizeof(m_rsc_end_node)); |
| m_stm_resource_size_used = sizeof(m_rsc_end_node); |
| m_stm_resource_size_available = |
| m_stm_resource_total_size - sizeof(m_rsc_end_node); |
| wbinvd(); // force to memory |
| |
| } else { |
| if (m_stm_resource_size_available < resource_size) { |
| printk(BIOS_DEBUG, |
| "STM: ERROR - not enough space for SMM resource list\n"); |
| return -1; // OUT_OF_RESOURCES |
| } |
| } |
| |
| // Check duplication |
| add_resource(resource_list, num_entries); |
| |
| return 0; // SUCCESS; |
| } |
| |
| /* |
| * Delete resources in list to database. |
| * |
| * @param resource_list A pointer to resource list to be deleted |
| * NULL means delete all resources. |
| * @param num_entries Optional number of entries. |
| * If 0, list must be terminated by END_OF_RESOURCES. |
| * |
| * @retval SUCCESS If resources are deleted |
| * @retval INVALID_PARAMETER If nested procedure detected resource failure |
| */ |
| int32_t delete_pi_resource(STM_RSC *resource_list, uint32_t num_entries) |
| { |
| if (resource_list) { |
| // ASSERT (false); |
| return -1; // UNSUPPORTED; |
| } |
| |
| // Delete all |
| memcpy(m_stm_resources_ptr, &m_rsc_end_node, sizeof(m_rsc_end_node)); |
| m_stm_resource_size_used = sizeof(m_rsc_end_node); |
| m_stm_resource_size_available = |
| m_stm_resource_total_size - sizeof(m_rsc_end_node); |
| return 0; // SUCCESS; |
| } |
| |
| /* |
| * Get BIOS resources. |
| * |
| * @param resource_list A pointer to resource list to be filled |
| * @param resource_size On input it means size of resource list input. |
| * On output it means size of resource list filled, |
| * or the size of resource list to be filled if size is |
| * too small. |
| * |
| * @retval SUCCESS If resources are returned. |
| * @retval BUFFER_TOO_SMALL If resource list buffer is too small to hold |
| * the whole resource list. |
| */ |
| int32_t get_pi_resource(STM_RSC *resource_list, uint32_t *resource_size) |
| { |
| if (*resource_size < m_stm_resource_size_used) { |
| *resource_size = (uint32_t)m_stm_resource_size_used; |
| return -1; // BUFFER_TOO_SMALL; |
| } |
| |
| memcpy(resource_list, m_stm_resources_ptr, m_stm_resource_size_used); |
| *resource_size = (uint32_t)m_stm_resource_size_used; |
| return 0; // SUCCESS; |
| } |
| |
| /* |
| * Get 4K page aligned VMCS size. |
| * @return 4K page aligned VMCS size |
| */ |
| static uint32_t get_vmcs_size(void) |
| { |
| uint32_t this_vmcs_size; |
| VMX_BASIC_MSR msr_data64; |
| int stm_support; |
| |
| msr_data64.msr = rdmsr(IA32_VMX_BASIC_MSR); |
| |
| this_vmcs_size = msr_data64.bits.vmcs_size; |
| stm_support = msr_data64.bits.stm_supported; |
| printk(BIOS_DEBUG, "STM: %s: Size %d StmSupport %d\n", __func__, |
| this_vmcs_size, stm_support); |
| |
| // VMCS require 0x1000 alignment |
| this_vmcs_size = STM_PAGES_TO_SIZE(STM_SIZE_TO_PAGES(this_vmcs_size)); |
| |
| return this_vmcs_size; |
| } |
| |
| /* |
| * Create 4G page table for STM. |
| * 2M PTEs for x86_64 or 2M PTEs for x86_32. |
| * |
| * @param pageable_base The page table base in MSEG |
| */ |
| void stm_gen_4g_pagetable_x64(uint32_t pagetable_base) |
| { |
| uint32_t index; |
| uint32_t sub_index; |
| uint64_t *pde; |
| uint64_t *pte; |
| uint64_t *pml4; |
| |
| pml4 = (uint64_t *)(uint32_t)pagetable_base; |
| pagetable_base += PTP_SIZE; |
| *pml4 = pagetable_base | IA32_PG_RW | IA32_PG_P; |
| |
| pde = (uint64_t *)(uint32_t)pagetable_base; |
| pagetable_base += PTP_SIZE; |
| pte = (uint64_t *)(uint32_t)pagetable_base; |
| |
| for (index = 0; index < 4; index++) { |
| *pde = pagetable_base | IA32_PG_RW | IA32_PG_P; |
| pde++; |
| pagetable_base += PTP_SIZE; |
| |
| for (sub_index = 0; sub_index < SIZE_4KB / sizeof(*pte); |
| sub_index++) { |
| *pte = (((index << 9) + sub_index) << 21) | IA32_PG_PS |
| | IA32_PG_RW | IA32_PG_P; |
| pte++; |
| } |
| } |
| } |
| |
| /* |
| * Check STM image size. |
| * |
| * @param stm_image STM image |
| * @param stm_imageSize STM image size |
| * |
| * @retval true check pass |
| * @retval false check fail |
| */ |
| |
| bool stm_check_stm_image(void *stm_image, uint32_t stm_imagesize) |
| { |
| uint32_t min_mseg_size; |
| STM_HEADER *stm_header; |
| |
| stm_header = (STM_HEADER *)stm_image; |
| |
| // Get Minimal required Mseg size |
| min_mseg_size = (STM_PAGES_TO_SIZE(STM_SIZE_TO_PAGES( |
| stm_header->sw_stm_hdr.static_image_size)) |
| + stm_header->sw_stm_hdr.additional_dynamic_memory_size |
| + (stm_header->sw_stm_hdr.per_proc_dynamic_memory_size |
| + get_vmcs_size() * 2) |
| * mp_state.cpu_count); |
| if (min_mseg_size < stm_imagesize) |
| min_mseg_size = stm_imagesize; |
| |
| if (stm_header->hw_stm_hdr.cr3_offset |
| >= stm_header->sw_stm_hdr.static_image_size) { |
| |
| // We will create page table, just in case that SINIT does not |
| // create it. |
| if (min_mseg_size < stm_header->hw_stm_hdr.cr3_offset |
| + STM_PAGES_TO_SIZE(6)) { |
| min_mseg_size = stm_header->hw_stm_hdr.cr3_offset |
| + STM_PAGES_TO_SIZE(6); |
| } |
| } |
| |
| // Check if it exceeds MSEG size |
| if (min_mseg_size > CONFIG_MSEG_SIZE) |
| return false; |
| |
| return true; |
| } |
| |
| /* |
| * This function return BIOS STM resource. |
| * Produced by SmmStm. |
| * Consumed by SmmMpService when Init. |
| * |
| * @return BIOS STM resource |
| */ |
| void *get_stm_resource(void) |
| { |
| return m_stm_resources_ptr; |
| } |