blob: 00490cfa5e5e925656a5e9bcfc58fbd8b8ba2091 [file] [log] [blame]
/* 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;
int 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%08x\n", (int) resource_size);
if (resource_size == 0)
return -1; // INVALID_PARAMETER;
if (m_stm_resources_ptr == NULL) {
// 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 != NULL) {
// 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.
* Comsumed by SmmMpService when Init.
*
* @return BIOS STM resource
*/
void *get_stm_resource(void)
{
return m_stm_resources_ptr;
}