mirror of
https://github.com/torvalds/linux.git
synced 2026-07-30 11:11:26 +02:00
Merge 5b828263b1 ("Merge tag 'ras_core_for_v5.19_rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip") into android-mainline
Steps on the way to 5.19-rc1 Signed-off-by: Greg Kroah-Hartman <gregkh@google.com> Change-Id: I971fab4126a86d5db05517dd63e48e34ed0a7900
This commit is contained in:
commit
fbab598aa0
|
|
@ -5391,6 +5391,8 @@
|
|||
|
||||
serialnumber [BUGS=X86-32]
|
||||
|
||||
sev=option[,option...] [X86-64] See Documentation/x86/x86_64/boot-options.rst
|
||||
|
||||
shapers= [NET]
|
||||
Maximal number of shapers.
|
||||
|
||||
|
|
|
|||
|
|
@ -25,12 +25,6 @@ properties:
|
|||
- const: fsl,qoriq-memory-controller
|
||||
- enum:
|
||||
- fsl,bsc9132-memory-controller
|
||||
- fsl,8540-memory-controller
|
||||
- fsl,8541-memory-controller
|
||||
- fsl,8544-memory-controller
|
||||
- fsl,8548-memory-controller
|
||||
- fsl,8555-memory-controller
|
||||
- fsl,8568-memory-controller
|
||||
- fsl,mpc8536-memory-controller
|
||||
- fsl,mpc8540-memory-controller
|
||||
- fsl,mpc8541-memory-controller
|
||||
|
|
|
|||
|
|
@ -6,12 +6,6 @@ The cache bindings explained below are Devicetree Specification compliant
|
|||
Required Properties:
|
||||
|
||||
- compatible : Should include one of the following:
|
||||
"fsl,8540-l2-cache-controller"
|
||||
"fsl,8541-l2-cache-controller"
|
||||
"fsl,8544-l2-cache-controller"
|
||||
"fsl,8548-l2-cache-controller"
|
||||
"fsl,8555-l2-cache-controller"
|
||||
"fsl,8568-l2-cache-controller"
|
||||
"fsl,b4420-l2-cache-controller"
|
||||
"fsl,b4860-l2-cache-controller"
|
||||
"fsl,bsc9131-l2-cache-controller"
|
||||
|
|
|
|||
155
Documentation/virt/coco/sev-guest.rst
Normal file
155
Documentation/virt/coco/sev-guest.rst
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
===================================================================
|
||||
The Definitive SEV Guest API Documentation
|
||||
===================================================================
|
||||
|
||||
1. General description
|
||||
======================
|
||||
|
||||
The SEV API is a set of ioctls that are used by the guest or hypervisor
|
||||
to get or set a certain aspect of the SEV virtual machine. The ioctls belong
|
||||
to the following classes:
|
||||
|
||||
- Hypervisor ioctls: These query and set global attributes which affect the
|
||||
whole SEV firmware. These ioctl are used by platform provisioning tools.
|
||||
|
||||
- Guest ioctls: These query and set attributes of the SEV virtual machine.
|
||||
|
||||
2. API description
|
||||
==================
|
||||
|
||||
This section describes ioctls that is used for querying the SEV guest report
|
||||
from the SEV firmware. For each ioctl, the following information is provided
|
||||
along with a description:
|
||||
|
||||
Technology:
|
||||
which SEV technology provides this ioctl. SEV, SEV-ES, SEV-SNP or all.
|
||||
|
||||
Type:
|
||||
hypervisor or guest. The ioctl can be used inside the guest or the
|
||||
hypervisor.
|
||||
|
||||
Parameters:
|
||||
what parameters are accepted by the ioctl.
|
||||
|
||||
Returns:
|
||||
the return value. General error numbers (-ENOMEM, -EINVAL)
|
||||
are not detailed, but errors with specific meanings are.
|
||||
|
||||
The guest ioctl should be issued on a file descriptor of the /dev/sev-guest device.
|
||||
The ioctl accepts struct snp_user_guest_request. The input and output structure is
|
||||
specified through the req_data and resp_data field respectively. If the ioctl fails
|
||||
to execute due to a firmware error, then fw_err code will be set otherwise the
|
||||
fw_err will be set to 0x00000000000000ff.
|
||||
|
||||
The firmware checks that the message sequence counter is one greater than
|
||||
the guests message sequence counter. If guest driver fails to increment message
|
||||
counter (e.g. counter overflow), then -EIO will be returned.
|
||||
|
||||
::
|
||||
|
||||
struct snp_guest_request_ioctl {
|
||||
/* Message version number */
|
||||
__u32 msg_version;
|
||||
|
||||
/* Request and response structure address */
|
||||
__u64 req_data;
|
||||
__u64 resp_data;
|
||||
|
||||
/* firmware error code on failure (see psp-sev.h) */
|
||||
__u64 fw_err;
|
||||
};
|
||||
|
||||
2.1 SNP_GET_REPORT
|
||||
------------------
|
||||
|
||||
:Technology: sev-snp
|
||||
:Type: guest ioctl
|
||||
:Parameters (in): struct snp_report_req
|
||||
:Returns (out): struct snp_report_resp on success, -negative on error
|
||||
|
||||
The SNP_GET_REPORT ioctl can be used to query the attestation report from the
|
||||
SEV-SNP firmware. The ioctl uses the SNP_GUEST_REQUEST (MSG_REPORT_REQ) command
|
||||
provided by the SEV-SNP firmware to query the attestation report.
|
||||
|
||||
On success, the snp_report_resp.data will contains the report. The report
|
||||
contain the format described in the SEV-SNP specification. See the SEV-SNP
|
||||
specification for further details.
|
||||
|
||||
2.2 SNP_GET_DERIVED_KEY
|
||||
-----------------------
|
||||
:Technology: sev-snp
|
||||
:Type: guest ioctl
|
||||
:Parameters (in): struct snp_derived_key_req
|
||||
:Returns (out): struct snp_derived_key_resp on success, -negative on error
|
||||
|
||||
The SNP_GET_DERIVED_KEY ioctl can be used to get a key derive from a root key.
|
||||
The derived key can be used by the guest for any purpose, such as sealing keys
|
||||
or communicating with external entities.
|
||||
|
||||
The ioctl uses the SNP_GUEST_REQUEST (MSG_KEY_REQ) command provided by the
|
||||
SEV-SNP firmware to derive the key. See SEV-SNP specification for further details
|
||||
on the various fields passed in the key derivation request.
|
||||
|
||||
On success, the snp_derived_key_resp.data contains the derived key value. See
|
||||
the SEV-SNP specification for further details.
|
||||
|
||||
|
||||
2.3 SNP_GET_EXT_REPORT
|
||||
----------------------
|
||||
:Technology: sev-snp
|
||||
:Type: guest ioctl
|
||||
:Parameters (in/out): struct snp_ext_report_req
|
||||
:Returns (out): struct snp_report_resp on success, -negative on error
|
||||
|
||||
The SNP_GET_EXT_REPORT ioctl is similar to the SNP_GET_REPORT. The difference is
|
||||
related to the additional certificate data that is returned with the report.
|
||||
The certificate data returned is being provided by the hypervisor through the
|
||||
SNP_SET_EXT_CONFIG.
|
||||
|
||||
The ioctl uses the SNP_GUEST_REQUEST (MSG_REPORT_REQ) command provided by the SEV-SNP
|
||||
firmware to get the attestation report.
|
||||
|
||||
On success, the snp_ext_report_resp.data will contain the attestation report
|
||||
and snp_ext_report_req.certs_address will contain the certificate blob. If the
|
||||
length of the blob is smaller than expected then snp_ext_report_req.certs_len will
|
||||
be updated with the expected value.
|
||||
|
||||
See GHCB specification for further detail on how to parse the certificate blob.
|
||||
|
||||
3. SEV-SNP CPUID Enforcement
|
||||
============================
|
||||
|
||||
SEV-SNP guests can access a special page that contains a table of CPUID values
|
||||
that have been validated by the PSP as part of the SNP_LAUNCH_UPDATE firmware
|
||||
command. It provides the following assurances regarding the validity of CPUID
|
||||
values:
|
||||
|
||||
- Its address is obtained via bootloader/firmware (via CC blob), and those
|
||||
binaries will be measured as part of the SEV-SNP attestation report.
|
||||
- Its initial state will be encrypted/pvalidated, so attempts to modify
|
||||
it during run-time will result in garbage being written, or #VC exceptions
|
||||
being generated due to changes in validation state if the hypervisor tries
|
||||
to swap the backing page.
|
||||
- Attempts to bypass PSP checks by the hypervisor by using a normal page, or
|
||||
a non-CPUID encrypted page will change the measurement provided by the
|
||||
SEV-SNP attestation report.
|
||||
- The CPUID page contents are *not* measured, but attempts to modify the
|
||||
expected contents of a CPUID page as part of guest initialization will be
|
||||
gated by the PSP CPUID enforcement policy checks performed on the page
|
||||
during SNP_LAUNCH_UPDATE, and noticeable later if the guest owner
|
||||
implements their own checks of the CPUID values.
|
||||
|
||||
It is important to note that this last assurance is only useful if the kernel
|
||||
has taken care to make use of the SEV-SNP CPUID throughout all stages of boot.
|
||||
Otherwise, guest owner attestation provides no assurance that the kernel wasn't
|
||||
fed incorrect values at some point during boot.
|
||||
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
||||
SEV-SNP and GHCB specification: developer.amd.com/sev
|
||||
|
||||
The driver is based on SEV-SNP firmware spec 0.9 and GHCB spec version 2.0.
|
||||
|
|
@ -13,6 +13,7 @@ Linux Virtualization Support
|
|||
guest-halt-polling
|
||||
ne_overview
|
||||
acrn/index
|
||||
coco/sev-guest
|
||||
|
||||
.. only:: html and subproject
|
||||
|
||||
|
|
|
|||
|
|
@ -310,3 +310,17 @@ Miscellaneous
|
|||
Do not use GB pages for kernel direct mappings.
|
||||
gbpages
|
||||
Use GB pages for kernel direct mappings.
|
||||
|
||||
|
||||
AMD SEV (Secure Encrypted Virtualization)
|
||||
=========================================
|
||||
Options relating to AMD SEV, specified via the following format:
|
||||
|
||||
::
|
||||
|
||||
sev=option1[,option2]
|
||||
|
||||
The available options are:
|
||||
|
||||
debug
|
||||
Enable debug messages.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ Offset/Size Proto Name Meaning
|
|||
058/008 ALL tboot_addr Physical address of tboot shared page
|
||||
060/010 ALL ist_info Intel SpeedStep (IST) BIOS support information
|
||||
(struct ist_info)
|
||||
070/008 ALL acpi_rsdp_addr Physical address of ACPI RSDP table
|
||||
080/010 ALL hd0_info hd0 disk parameter, OBSOLETE!!
|
||||
090/010 ALL hd1_info hd1 disk parameter, OBSOLETE!!
|
||||
0A0/010 ALL sys_desc_table System description table (struct sys_desc_table),
|
||||
|
|
@ -27,6 +28,7 @@ Offset/Size Proto Name Meaning
|
|||
0C0/004 ALL ext_ramdisk_image ramdisk_image high 32bits
|
||||
0C4/004 ALL ext_ramdisk_size ramdisk_size high 32bits
|
||||
0C8/004 ALL ext_cmd_line_ptr cmd_line_ptr high 32bits
|
||||
13C/004 ALL cc_blob_address Physical address of Confidential Computing blob
|
||||
140/080 ALL edid_info Video mode setup (struct edid_info)
|
||||
1C0/020 ALL efi_info EFI 32 information (struct efi_info)
|
||||
1E0/004 ALL alt_mem_k Alternative mem check, in KB
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ endif
|
|||
vmlinux-objs-$(CONFIG_ACPI) += $(obj)/acpi.o
|
||||
|
||||
vmlinux-objs-$(CONFIG_EFI_MIXED) += $(obj)/efi_thunk_$(BITS).o
|
||||
vmlinux-objs-$(CONFIG_EFI) += $(obj)/efi.o
|
||||
efi-obj-$(CONFIG_EFI_STUB) = $(objtree)/drivers/firmware/efi/libstub/lib.a
|
||||
|
||||
$(obj)/vmlinux: $(vmlinux-objs-y) $(efi-obj-y) FORCE
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@
|
|||
#include "misc.h"
|
||||
#include "error.h"
|
||||
#include "../string.h"
|
||||
#include "efi.h"
|
||||
|
||||
#include <linux/numa.h>
|
||||
#include <linux/efi.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
/*
|
||||
* Longest parameter of 'acpi=' is 'copy_dsdt', plus an extra '\0'
|
||||
|
|
@ -20,153 +19,56 @@
|
|||
*/
|
||||
struct mem_vector immovable_mem[MAX_NUMNODES*2];
|
||||
|
||||
/*
|
||||
* Search EFI system tables for RSDP. If both ACPI_20_TABLE_GUID and
|
||||
* ACPI_TABLE_GUID are found, take the former, which has more features.
|
||||
*/
|
||||
static acpi_physical_address
|
||||
__efi_get_rsdp_addr(unsigned long config_tables, unsigned int nr_tables,
|
||||
bool efi_64)
|
||||
__efi_get_rsdp_addr(unsigned long cfg_tbl_pa, unsigned int cfg_tbl_len)
|
||||
{
|
||||
acpi_physical_address rsdp_addr = 0;
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
int i;
|
||||
unsigned long rsdp_addr;
|
||||
int ret;
|
||||
|
||||
/* Get EFI tables from systab. */
|
||||
for (i = 0; i < nr_tables; i++) {
|
||||
acpi_physical_address table;
|
||||
efi_guid_t guid;
|
||||
/*
|
||||
* Search EFI system tables for RSDP. Preferred is ACPI_20_TABLE_GUID to
|
||||
* ACPI_TABLE_GUID because it has more features.
|
||||
*/
|
||||
rsdp_addr = efi_find_vendor_table(boot_params, cfg_tbl_pa, cfg_tbl_len,
|
||||
ACPI_20_TABLE_GUID);
|
||||
if (rsdp_addr)
|
||||
return (acpi_physical_address)rsdp_addr;
|
||||
|
||||
if (efi_64) {
|
||||
efi_config_table_64_t *tbl = (efi_config_table_64_t *)config_tables + i;
|
||||
/* No ACPI_20_TABLE_GUID found, fallback to ACPI_TABLE_GUID. */
|
||||
rsdp_addr = efi_find_vendor_table(boot_params, cfg_tbl_pa, cfg_tbl_len,
|
||||
ACPI_TABLE_GUID);
|
||||
if (rsdp_addr)
|
||||
return (acpi_physical_address)rsdp_addr;
|
||||
|
||||
guid = tbl->guid;
|
||||
table = tbl->table;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_X86_64) && table >> 32) {
|
||||
debug_putstr("Error getting RSDP address: EFI config table located above 4GB.\n");
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
efi_config_table_32_t *tbl = (efi_config_table_32_t *)config_tables + i;
|
||||
|
||||
guid = tbl->guid;
|
||||
table = tbl->table;
|
||||
}
|
||||
|
||||
if (!(efi_guidcmp(guid, ACPI_TABLE_GUID)))
|
||||
rsdp_addr = table;
|
||||
else if (!(efi_guidcmp(guid, ACPI_20_TABLE_GUID)))
|
||||
return table;
|
||||
}
|
||||
debug_putstr("Error getting RSDP address.\n");
|
||||
#endif
|
||||
return rsdp_addr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* EFI/kexec support is 64-bit only. */
|
||||
#ifdef CONFIG_X86_64
|
||||
static struct efi_setup_data *get_kexec_setup_data_addr(void)
|
||||
{
|
||||
struct setup_data *data;
|
||||
u64 pa_data;
|
||||
|
||||
pa_data = boot_params->hdr.setup_data;
|
||||
while (pa_data) {
|
||||
data = (struct setup_data *)pa_data;
|
||||
if (data->type == SETUP_EFI)
|
||||
return (struct efi_setup_data *)(pa_data + sizeof(struct setup_data));
|
||||
|
||||
pa_data = data->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static acpi_physical_address kexec_get_rsdp_addr(void)
|
||||
{
|
||||
efi_system_table_64_t *systab;
|
||||
struct efi_setup_data *esd;
|
||||
struct efi_info *ei;
|
||||
char *sig;
|
||||
|
||||
esd = (struct efi_setup_data *)get_kexec_setup_data_addr();
|
||||
if (!esd)
|
||||
return 0;
|
||||
|
||||
if (!esd->tables) {
|
||||
debug_putstr("Wrong kexec SETUP_EFI data.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ei = &boot_params->efi_info;
|
||||
sig = (char *)&ei->efi_loader_signature;
|
||||
if (strncmp(sig, EFI64_LOADER_SIGNATURE, 4)) {
|
||||
debug_putstr("Wrong kexec EFI loader signature.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get systab from boot params. */
|
||||
systab = (efi_system_table_64_t *) (ei->efi_systab | ((__u64)ei->efi_systab_hi << 32));
|
||||
if (!systab)
|
||||
error("EFI system table not found in kexec boot_params.");
|
||||
|
||||
return __efi_get_rsdp_addr((unsigned long)esd->tables, systab->nr_tables, true);
|
||||
}
|
||||
#else
|
||||
static acpi_physical_address kexec_get_rsdp_addr(void) { return 0; }
|
||||
#endif /* CONFIG_X86_64 */
|
||||
|
||||
static acpi_physical_address efi_get_rsdp_addr(void)
|
||||
{
|
||||
#ifdef CONFIG_EFI
|
||||
unsigned long systab, config_tables;
|
||||
unsigned long cfg_tbl_pa = 0;
|
||||
unsigned int cfg_tbl_len;
|
||||
unsigned long systab_pa;
|
||||
unsigned int nr_tables;
|
||||
struct efi_info *ei;
|
||||
bool efi_64;
|
||||
char *sig;
|
||||
enum efi_type et;
|
||||
int ret;
|
||||
|
||||
ei = &boot_params->efi_info;
|
||||
sig = (char *)&ei->efi_loader_signature;
|
||||
|
||||
if (!strncmp(sig, EFI64_LOADER_SIGNATURE, 4)) {
|
||||
efi_64 = true;
|
||||
} else if (!strncmp(sig, EFI32_LOADER_SIGNATURE, 4)) {
|
||||
efi_64 = false;
|
||||
} else {
|
||||
debug_putstr("Wrong EFI loader signature.\n");
|
||||
et = efi_get_type(boot_params);
|
||||
if (et == EFI_TYPE_NONE)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get systab from boot params. */
|
||||
#ifdef CONFIG_X86_64
|
||||
systab = ei->efi_systab | ((__u64)ei->efi_systab_hi << 32);
|
||||
#else
|
||||
if (ei->efi_systab_hi || ei->efi_memmap_hi) {
|
||||
debug_putstr("Error getting RSDP address: EFI system table located above 4GB.\n");
|
||||
return 0;
|
||||
}
|
||||
systab = ei->efi_systab;
|
||||
#endif
|
||||
if (!systab)
|
||||
error("EFI system table not found.");
|
||||
systab_pa = efi_get_system_table(boot_params);
|
||||
if (!systab_pa)
|
||||
error("EFI support advertised, but unable to locate system table.");
|
||||
|
||||
/* Handle EFI bitness properly */
|
||||
if (efi_64) {
|
||||
efi_system_table_64_t *stbl = (efi_system_table_64_t *)systab;
|
||||
ret = efi_get_conf_table(boot_params, &cfg_tbl_pa, &cfg_tbl_len);
|
||||
if (ret || !cfg_tbl_pa)
|
||||
error("EFI config table not found.");
|
||||
|
||||
config_tables = stbl->tables;
|
||||
nr_tables = stbl->nr_tables;
|
||||
} else {
|
||||
efi_system_table_32_t *stbl = (efi_system_table_32_t *)systab;
|
||||
|
||||
config_tables = stbl->tables;
|
||||
nr_tables = stbl->nr_tables;
|
||||
}
|
||||
|
||||
if (!config_tables)
|
||||
error("EFI config tables not found.");
|
||||
|
||||
return __efi_get_rsdp_addr(config_tables, nr_tables, efi_64);
|
||||
return __efi_get_rsdp_addr(cfg_tbl_pa, cfg_tbl_len);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
|
|
@ -256,14 +158,6 @@ acpi_physical_address get_rsdp_addr(void)
|
|||
|
||||
pa = boot_params->acpi_rsdp_addr;
|
||||
|
||||
/*
|
||||
* Try to get EFI data from setup_data. This can happen when we're a
|
||||
* kexec'ed kernel and kexec(1) has passed all the required EFI info to
|
||||
* us.
|
||||
*/
|
||||
if (!pa)
|
||||
pa = kexec_get_rsdp_addr();
|
||||
|
||||
if (!pa)
|
||||
pa = efi_get_rsdp_addr();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "misc.h"
|
||||
|
||||
int early_serial_base;
|
||||
/* This might be accessed before .bss is cleared, so use .data instead. */
|
||||
int early_serial_base __section(".data");
|
||||
|
||||
#include "../early_serial_console.c"
|
||||
|
|
|
|||
234
arch/x86/boot/compressed/efi.c
Normal file
234
arch/x86/boot/compressed/efi.c
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Helpers for early access to EFI configuration table.
|
||||
*
|
||||
* Originally derived from arch/x86/boot/compressed/acpi.c
|
||||
*/
|
||||
|
||||
#include "misc.h"
|
||||
|
||||
/**
|
||||
* efi_get_type - Given a pointer to boot_params, determine the type of EFI environment.
|
||||
*
|
||||
* @bp: pointer to boot_params
|
||||
*
|
||||
* Return: EFI_TYPE_{32,64} for valid EFI environments, EFI_TYPE_NONE otherwise.
|
||||
*/
|
||||
enum efi_type efi_get_type(struct boot_params *bp)
|
||||
{
|
||||
struct efi_info *ei;
|
||||
enum efi_type et;
|
||||
const char *sig;
|
||||
|
||||
ei = &bp->efi_info;
|
||||
sig = (char *)&ei->efi_loader_signature;
|
||||
|
||||
if (!strncmp(sig, EFI64_LOADER_SIGNATURE, 4)) {
|
||||
et = EFI_TYPE_64;
|
||||
} else if (!strncmp(sig, EFI32_LOADER_SIGNATURE, 4)) {
|
||||
et = EFI_TYPE_32;
|
||||
} else {
|
||||
debug_putstr("No EFI environment detected.\n");
|
||||
et = EFI_TYPE_NONE;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_X86_64
|
||||
/*
|
||||
* Existing callers like acpi.c treat this case as an indicator to
|
||||
* fall-through to non-EFI, rather than an error, so maintain that
|
||||
* functionality here as well.
|
||||
*/
|
||||
if (ei->efi_systab_hi || ei->efi_memmap_hi) {
|
||||
debug_putstr("EFI system table is located above 4GB and cannot be accessed.\n");
|
||||
et = EFI_TYPE_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
return et;
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_get_system_table - Given a pointer to boot_params, retrieve the physical address
|
||||
* of the EFI system table.
|
||||
*
|
||||
* @bp: pointer to boot_params
|
||||
*
|
||||
* Return: EFI system table address on success. On error, return 0.
|
||||
*/
|
||||
unsigned long efi_get_system_table(struct boot_params *bp)
|
||||
{
|
||||
unsigned long sys_tbl_pa;
|
||||
struct efi_info *ei;
|
||||
enum efi_type et;
|
||||
|
||||
/* Get systab from boot params. */
|
||||
ei = &bp->efi_info;
|
||||
#ifdef CONFIG_X86_64
|
||||
sys_tbl_pa = ei->efi_systab | ((__u64)ei->efi_systab_hi << 32);
|
||||
#else
|
||||
sys_tbl_pa = ei->efi_systab;
|
||||
#endif
|
||||
if (!sys_tbl_pa) {
|
||||
debug_putstr("EFI system table not found.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sys_tbl_pa;
|
||||
}
|
||||
|
||||
/*
|
||||
* EFI config table address changes to virtual address after boot, which may
|
||||
* not be accessible for the kexec'd kernel. To address this, kexec provides
|
||||
* the initial physical address via a struct setup_data entry, which is
|
||||
* checked for here, along with some sanity checks.
|
||||
*/
|
||||
static struct efi_setup_data *get_kexec_setup_data(struct boot_params *bp,
|
||||
enum efi_type et)
|
||||
{
|
||||
#ifdef CONFIG_X86_64
|
||||
struct efi_setup_data *esd = NULL;
|
||||
struct setup_data *data;
|
||||
u64 pa_data;
|
||||
|
||||
pa_data = bp->hdr.setup_data;
|
||||
while (pa_data) {
|
||||
data = (struct setup_data *)pa_data;
|
||||
if (data->type == SETUP_EFI) {
|
||||
esd = (struct efi_setup_data *)(pa_data + sizeof(struct setup_data));
|
||||
break;
|
||||
}
|
||||
|
||||
pa_data = data->next;
|
||||
}
|
||||
|
||||
/*
|
||||
* Original ACPI code falls back to attempting normal EFI boot in these
|
||||
* cases, so maintain existing behavior by indicating non-kexec
|
||||
* environment to the caller, but print them for debugging.
|
||||
*/
|
||||
if (esd && !esd->tables) {
|
||||
debug_putstr("kexec EFI environment missing valid configuration table.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return esd;
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_get_conf_table - Given a pointer to boot_params, locate and return the physical
|
||||
* address of EFI configuration table.
|
||||
*
|
||||
* @bp: pointer to boot_params
|
||||
* @cfg_tbl_pa: location to store physical address of config table
|
||||
* @cfg_tbl_len: location to store number of config table entries
|
||||
*
|
||||
* Return: 0 on success. On error, return params are left unchanged.
|
||||
*/
|
||||
int efi_get_conf_table(struct boot_params *bp, unsigned long *cfg_tbl_pa,
|
||||
unsigned int *cfg_tbl_len)
|
||||
{
|
||||
unsigned long sys_tbl_pa;
|
||||
enum efi_type et;
|
||||
int ret;
|
||||
|
||||
if (!cfg_tbl_pa || !cfg_tbl_len)
|
||||
return -EINVAL;
|
||||
|
||||
sys_tbl_pa = efi_get_system_table(bp);
|
||||
if (!sys_tbl_pa)
|
||||
return -EINVAL;
|
||||
|
||||
/* Handle EFI bitness properly */
|
||||
et = efi_get_type(bp);
|
||||
if (et == EFI_TYPE_64) {
|
||||
efi_system_table_64_t *stbl = (efi_system_table_64_t *)sys_tbl_pa;
|
||||
struct efi_setup_data *esd;
|
||||
|
||||
/* kexec provides an alternative EFI conf table, check for it. */
|
||||
esd = get_kexec_setup_data(bp, et);
|
||||
|
||||
*cfg_tbl_pa = esd ? esd->tables : stbl->tables;
|
||||
*cfg_tbl_len = stbl->nr_tables;
|
||||
} else if (et == EFI_TYPE_32) {
|
||||
efi_system_table_32_t *stbl = (efi_system_table_32_t *)sys_tbl_pa;
|
||||
|
||||
*cfg_tbl_pa = stbl->tables;
|
||||
*cfg_tbl_len = stbl->nr_tables;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get vendor table address/guid from EFI config table at the given index */
|
||||
static int get_vendor_table(void *cfg_tbl, unsigned int idx,
|
||||
unsigned long *vendor_tbl_pa,
|
||||
efi_guid_t *vendor_tbl_guid,
|
||||
enum efi_type et)
|
||||
{
|
||||
if (et == EFI_TYPE_64) {
|
||||
efi_config_table_64_t *tbl_entry = (efi_config_table_64_t *)cfg_tbl + idx;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_X86_64) && tbl_entry->table >> 32) {
|
||||
debug_putstr("Error: EFI config table entry located above 4GB.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*vendor_tbl_pa = tbl_entry->table;
|
||||
*vendor_tbl_guid = tbl_entry->guid;
|
||||
|
||||
} else if (et == EFI_TYPE_32) {
|
||||
efi_config_table_32_t *tbl_entry = (efi_config_table_32_t *)cfg_tbl + idx;
|
||||
|
||||
*vendor_tbl_pa = tbl_entry->table;
|
||||
*vendor_tbl_guid = tbl_entry->guid;
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* efi_find_vendor_table - Given EFI config table, search it for the physical
|
||||
* address of the vendor table associated with GUID.
|
||||
*
|
||||
* @bp: pointer to boot_params
|
||||
* @cfg_tbl_pa: pointer to EFI configuration table
|
||||
* @cfg_tbl_len: number of entries in EFI configuration table
|
||||
* @guid: GUID of vendor table
|
||||
*
|
||||
* Return: vendor table address on success. On error, return 0.
|
||||
*/
|
||||
unsigned long efi_find_vendor_table(struct boot_params *bp,
|
||||
unsigned long cfg_tbl_pa,
|
||||
unsigned int cfg_tbl_len,
|
||||
efi_guid_t guid)
|
||||
{
|
||||
enum efi_type et;
|
||||
unsigned int i;
|
||||
|
||||
et = efi_get_type(bp);
|
||||
if (et == EFI_TYPE_NONE)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < cfg_tbl_len; i++) {
|
||||
unsigned long vendor_tbl_pa;
|
||||
efi_guid_t vendor_tbl_guid;
|
||||
int ret;
|
||||
|
||||
ret = get_vendor_table((void *)cfg_tbl_pa, i,
|
||||
&vendor_tbl_pa,
|
||||
&vendor_tbl_guid, et);
|
||||
if (ret)
|
||||
return 0;
|
||||
|
||||
if (!efi_guidcmp(guid, vendor_tbl_guid))
|
||||
return vendor_tbl_pa;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
126
arch/x86/boot/compressed/efi.h
Normal file
126
arch/x86/boot/compressed/efi.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef BOOT_COMPRESSED_EFI_H
|
||||
#define BOOT_COMPRESSED_EFI_H
|
||||
|
||||
#if defined(_LINUX_EFI_H) || defined(_ASM_X86_EFI_H)
|
||||
#error Please do not include kernel proper namespace headers
|
||||
#endif
|
||||
|
||||
typedef guid_t efi_guid_t __aligned(__alignof__(u32));
|
||||
|
||||
#define EFI_GUID(a, b, c, d...) (efi_guid_t){ { \
|
||||
(a) & 0xff, ((a) >> 8) & 0xff, ((a) >> 16) & 0xff, ((a) >> 24) & 0xff, \
|
||||
(b) & 0xff, ((b) >> 8) & 0xff, \
|
||||
(c) & 0xff, ((c) >> 8) & 0xff, d } }
|
||||
|
||||
#define ACPI_TABLE_GUID EFI_GUID(0xeb9d2d30, 0x2d88, 0x11d3, 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d)
|
||||
#define ACPI_20_TABLE_GUID EFI_GUID(0x8868e871, 0xe4f1, 0x11d3, 0xbc, 0x22, 0x00, 0x80, 0xc7, 0x3c, 0x88, 0x81)
|
||||
#define EFI_CC_BLOB_GUID EFI_GUID(0x067b1f5f, 0xcf26, 0x44c5, 0x85, 0x54, 0x93, 0xd7, 0x77, 0x91, 0x2d, 0x42)
|
||||
|
||||
#define EFI32_LOADER_SIGNATURE "EL32"
|
||||
#define EFI64_LOADER_SIGNATURE "EL64"
|
||||
|
||||
/*
|
||||
* Generic EFI table header
|
||||
*/
|
||||
typedef struct {
|
||||
u64 signature;
|
||||
u32 revision;
|
||||
u32 headersize;
|
||||
u32 crc32;
|
||||
u32 reserved;
|
||||
} efi_table_hdr_t;
|
||||
|
||||
#define EFI_CONVENTIONAL_MEMORY 7
|
||||
|
||||
#define EFI_MEMORY_MORE_RELIABLE \
|
||||
((u64)0x0000000000010000ULL) /* higher reliability */
|
||||
#define EFI_MEMORY_SP ((u64)0x0000000000040000ULL) /* soft reserved */
|
||||
|
||||
#define EFI_PAGE_SHIFT 12
|
||||
|
||||
typedef struct {
|
||||
u32 type;
|
||||
u32 pad;
|
||||
u64 phys_addr;
|
||||
u64 virt_addr;
|
||||
u64 num_pages;
|
||||
u64 attribute;
|
||||
} efi_memory_desc_t;
|
||||
|
||||
#define efi_early_memdesc_ptr(map, desc_size, n) \
|
||||
(efi_memory_desc_t *)((void *)(map) + ((n) * (desc_size)))
|
||||
|
||||
typedef struct {
|
||||
efi_guid_t guid;
|
||||
u64 table;
|
||||
} efi_config_table_64_t;
|
||||
|
||||
typedef struct {
|
||||
efi_guid_t guid;
|
||||
u32 table;
|
||||
} efi_config_table_32_t;
|
||||
|
||||
typedef struct {
|
||||
efi_table_hdr_t hdr;
|
||||
u64 fw_vendor; /* physical addr of CHAR16 vendor string */
|
||||
u32 fw_revision;
|
||||
u32 __pad1;
|
||||
u64 con_in_handle;
|
||||
u64 con_in;
|
||||
u64 con_out_handle;
|
||||
u64 con_out;
|
||||
u64 stderr_handle;
|
||||
u64 stderr;
|
||||
u64 runtime;
|
||||
u64 boottime;
|
||||
u32 nr_tables;
|
||||
u32 __pad2;
|
||||
u64 tables;
|
||||
} efi_system_table_64_t;
|
||||
|
||||
typedef struct {
|
||||
efi_table_hdr_t hdr;
|
||||
u32 fw_vendor; /* physical addr of CHAR16 vendor string */
|
||||
u32 fw_revision;
|
||||
u32 con_in_handle;
|
||||
u32 con_in;
|
||||
u32 con_out_handle;
|
||||
u32 con_out;
|
||||
u32 stderr_handle;
|
||||
u32 stderr;
|
||||
u32 runtime;
|
||||
u32 boottime;
|
||||
u32 nr_tables;
|
||||
u32 tables;
|
||||
} efi_system_table_32_t;
|
||||
|
||||
/* kexec external ABI */
|
||||
struct efi_setup_data {
|
||||
u64 fw_vendor;
|
||||
u64 __unused;
|
||||
u64 tables;
|
||||
u64 smbios;
|
||||
u64 reserved[8];
|
||||
};
|
||||
|
||||
static inline int efi_guidcmp (efi_guid_t left, efi_guid_t right)
|
||||
{
|
||||
return memcmp(&left, &right, sizeof (efi_guid_t));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
bool __pure __efi_soft_reserve_enabled(void);
|
||||
|
||||
static inline bool __pure efi_soft_reserve_enabled(void)
|
||||
{
|
||||
return IS_ENABLED(CONFIG_EFI_SOFT_RESERVE)
|
||||
&& __efi_soft_reserve_enabled();
|
||||
}
|
||||
#else
|
||||
static inline bool efi_soft_reserve_enabled(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* CONFIG_EFI */
|
||||
#endif /* BOOT_COMPRESSED_EFI_H */
|
||||
|
|
@ -189,11 +189,11 @@ SYM_FUNC_START(startup_32)
|
|||
subl $32, %eax /* Encryption bit is always above bit 31 */
|
||||
bts %eax, %edx /* Set encryption mask for page tables */
|
||||
/*
|
||||
* Mark SEV as active in sev_status so that startup32_check_sev_cbit()
|
||||
* will do a check. The sev_status memory will be fully initialized
|
||||
* with the contents of MSR_AMD_SEV_STATUS later in
|
||||
* set_sev_encryption_mask(). For now it is sufficient to know that SEV
|
||||
* is active.
|
||||
* Set MSR_AMD64_SEV_ENABLED_BIT in sev_status so that
|
||||
* startup32_check_sev_cbit() will do a check. sev_enable() will
|
||||
* initialize sev_status with all the bits reported by
|
||||
* MSR_AMD_SEV_STATUS later, but only MSR_AMD64_SEV_ENABLED_BIT
|
||||
* needs to be set for now.
|
||||
*/
|
||||
movl $1, rva(sev_status)(%ebp)
|
||||
1:
|
||||
|
|
@ -447,6 +447,23 @@ SYM_CODE_START(startup_64)
|
|||
call load_stage1_idt
|
||||
popq %rsi
|
||||
|
||||
#ifdef CONFIG_AMD_MEM_ENCRYPT
|
||||
/*
|
||||
* Now that the stage1 interrupt handlers are set up, #VC exceptions from
|
||||
* CPUID instructions can be properly handled for SEV-ES guests.
|
||||
*
|
||||
* For SEV-SNP, the CPUID table also needs to be set up in advance of any
|
||||
* CPUID instructions being issued, so go ahead and do that now via
|
||||
* sev_enable(), which will also handle the rest of the SEV-related
|
||||
* detection/setup to ensure that has been done in advance of any dependent
|
||||
* code.
|
||||
*/
|
||||
pushq %rsi
|
||||
movq %rsi, %rdi /* real mode address */
|
||||
call sev_enable
|
||||
popq %rsi
|
||||
#endif
|
||||
|
||||
/*
|
||||
* paging_prepare() sets up the trampoline and checks if we need to
|
||||
* enable 5-level paging.
|
||||
|
|
@ -558,17 +575,7 @@ SYM_FUNC_START_LOCAL_NOALIGN(.Lrelocated)
|
|||
shrq $3, %rcx
|
||||
rep stosq
|
||||
|
||||
/*
|
||||
* If running as an SEV guest, the encryption mask is required in the
|
||||
* page-table setup code below. When the guest also has SEV-ES enabled
|
||||
* set_sev_encryption_mask() will cause #VC exceptions, but the stage2
|
||||
* handler can't map its GHCB because the page-table is not set up yet.
|
||||
* So set up the encryption mask here while still on the stage1 #VC
|
||||
* handler. Then load stage2 IDT and switch to the kernel's own
|
||||
* page-table.
|
||||
*/
|
||||
pushq %rsi
|
||||
call set_sev_encryption_mask
|
||||
call load_stage2_idt
|
||||
|
||||
/* Pass boot_params to initialize_identity_maps() */
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ static struct x86_mapping_info mapping_info;
|
|||
/*
|
||||
* Adds the specified range to the identity mappings.
|
||||
*/
|
||||
static void add_identity_map(unsigned long start, unsigned long end)
|
||||
void kernel_add_identity_map(unsigned long start, unsigned long end)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
|
@ -157,14 +157,15 @@ void initialize_identity_maps(void *rmode)
|
|||
* explicitly here in case the compressed kernel does not touch them,
|
||||
* or does not touch all the pages covering them.
|
||||
*/
|
||||
add_identity_map((unsigned long)_head, (unsigned long)_end);
|
||||
kernel_add_identity_map((unsigned long)_head, (unsigned long)_end);
|
||||
boot_params = rmode;
|
||||
add_identity_map((unsigned long)boot_params, (unsigned long)(boot_params + 1));
|
||||
kernel_add_identity_map((unsigned long)boot_params, (unsigned long)(boot_params + 1));
|
||||
cmdline = get_cmd_line_ptr();
|
||||
add_identity_map(cmdline, cmdline + COMMAND_LINE_SIZE);
|
||||
kernel_add_identity_map(cmdline, cmdline + COMMAND_LINE_SIZE);
|
||||
|
||||
sev_prep_identity_maps(top_level_pgt);
|
||||
|
||||
/* Load the new page-table. */
|
||||
sev_verify_cbit(top_level_pgt);
|
||||
write_cr3(top_level_pgt);
|
||||
}
|
||||
|
||||
|
|
@ -246,10 +247,10 @@ static int set_clr_page_flags(struct x86_mapping_info *info,
|
|||
* It should already exist, but keep things generic.
|
||||
*
|
||||
* To map the page just read from it and fault it in if there is no
|
||||
* mapping yet. add_identity_map() can't be called here because that
|
||||
* would unconditionally map the address on PMD level, destroying any
|
||||
* PTE-level mappings that might already exist. Use assembly here so
|
||||
* the access won't be optimized away.
|
||||
* mapping yet. kernel_add_identity_map() can't be called here because
|
||||
* that would unconditionally map the address on PMD level, destroying
|
||||
* any PTE-level mappings that might already exist. Use assembly here
|
||||
* so the access won't be optimized away.
|
||||
*/
|
||||
asm volatile("mov %[address], %%r9"
|
||||
:: [address] "g" (*(unsigned long *)address)
|
||||
|
|
@ -275,15 +276,31 @@ static int set_clr_page_flags(struct x86_mapping_info *info,
|
|||
* Changing encryption attributes of a page requires to flush it from
|
||||
* the caches.
|
||||
*/
|
||||
if ((set | clr) & _PAGE_ENC)
|
||||
if ((set | clr) & _PAGE_ENC) {
|
||||
clflush_page(address);
|
||||
|
||||
/*
|
||||
* If the encryption attribute is being cleared, change the page state
|
||||
* to shared in the RMP table.
|
||||
*/
|
||||
if (clr)
|
||||
snp_set_page_shared(__pa(address & PAGE_MASK));
|
||||
}
|
||||
|
||||
/* Update PTE */
|
||||
pte = *ptep;
|
||||
pte = pte_set_flags(pte, set);
|
||||
pte = pte_clear_flags(pte, clr);
|
||||
set_pte(ptep, pte);
|
||||
|
||||
/*
|
||||
* If the encryption attribute is being set, then change the page state to
|
||||
* private in the RMP entry. The page state change must be done after the PTE
|
||||
* is updated.
|
||||
*/
|
||||
if (set & _PAGE_ENC)
|
||||
snp_set_page_private(__pa(address & PAGE_MASK));
|
||||
|
||||
/* Flush TLB after changing encryption attribute */
|
||||
write_cr3(top_level_pgt);
|
||||
|
||||
|
|
@ -347,5 +364,5 @@ void do_boot_page_fault(struct pt_regs *regs, unsigned long error_code)
|
|||
* Error code is sane - now identity map the 2M region around
|
||||
* the faulting address.
|
||||
*/
|
||||
add_identity_map(address, end);
|
||||
kernel_add_identity_map(address, end);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,23 @@ void load_stage1_idt(void)
|
|||
load_boot_idt(&boot_idt_desc);
|
||||
}
|
||||
|
||||
/* Setup IDT after kernel jumping to .Lrelocated */
|
||||
/*
|
||||
* Setup IDT after kernel jumping to .Lrelocated.
|
||||
*
|
||||
* initialize_identity_maps() needs a #PF handler to be setup
|
||||
* in order to be able to fault-in identity mapping ranges; see
|
||||
* do_boot_page_fault().
|
||||
*
|
||||
* This #PF handler setup needs to happen in load_stage2_idt() where the
|
||||
* IDT is loaded and there the #VC IDT entry gets setup too.
|
||||
*
|
||||
* In order to be able to handle #VCs, one needs a GHCB which
|
||||
* gets setup with an already set up pagetable, which is done in
|
||||
* initialize_identity_maps(). And there's the catch 22: the boot #VC
|
||||
* handler do_boot_stage2_vc() needs to call early_setup_ghcb() itself
|
||||
* (and, especially set_page_decrypted()) because the SEV-ES setup code
|
||||
* cannot initialize a GHCB as there's no #PF handler yet...
|
||||
*/
|
||||
void load_stage2_idt(void)
|
||||
{
|
||||
boot_idt_desc.address = (unsigned long)boot_idt;
|
||||
|
|
|
|||
|
|
@ -22,15 +22,14 @@
|
|||
#include "misc.h"
|
||||
#include "error.h"
|
||||
#include "../string.h"
|
||||
#include "efi.h"
|
||||
|
||||
#include <generated/compile.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/uts.h>
|
||||
#include <linux/utsname.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/efi.h>
|
||||
#include <generated/utsrelease.h>
|
||||
#include <asm/efi.h>
|
||||
|
||||
#define _SETUP
|
||||
#include <asm/setup.h> /* For COMMAND_LINE_SIZE */
|
||||
|
|
|
|||
|
|
@ -187,42 +187,6 @@ SYM_CODE_END(startup32_vc_handler)
|
|||
.code64
|
||||
|
||||
#include "../../kernel/sev_verify_cbit.S"
|
||||
SYM_FUNC_START(set_sev_encryption_mask)
|
||||
#ifdef CONFIG_AMD_MEM_ENCRYPT
|
||||
push %rbp
|
||||
push %rdx
|
||||
|
||||
movq %rsp, %rbp /* Save current stack pointer */
|
||||
|
||||
call get_sev_encryption_bit /* Get the encryption bit position */
|
||||
testl %eax, %eax
|
||||
jz .Lno_sev_mask
|
||||
|
||||
bts %rax, sme_me_mask(%rip) /* Create the encryption mask */
|
||||
|
||||
/*
|
||||
* Read MSR_AMD64_SEV again and store it to sev_status. Can't do this in
|
||||
* get_sev_encryption_bit() because this function is 32-bit code and
|
||||
* shared between 64-bit and 32-bit boot path.
|
||||
*/
|
||||
movl $MSR_AMD64_SEV, %ecx /* Read the SEV MSR */
|
||||
rdmsr
|
||||
|
||||
/* Store MSR value in sev_status */
|
||||
shlq $32, %rdx
|
||||
orq %rdx, %rax
|
||||
movq %rax, sev_status(%rip)
|
||||
|
||||
.Lno_sev_mask:
|
||||
movq %rbp, %rsp /* Restore original stack pointer */
|
||||
|
||||
pop %rdx
|
||||
pop %rbp
|
||||
#endif
|
||||
|
||||
xor %rax, %rax
|
||||
RET
|
||||
SYM_FUNC_END(set_sev_encryption_mask)
|
||||
|
||||
.data
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,10 @@ memptr free_mem_end_ptr;
|
|||
|
||||
static char *vidmem;
|
||||
static int vidport;
|
||||
static int lines, cols;
|
||||
|
||||
/* These might be accessed before .bss is cleared, so use .data instead. */
|
||||
static int lines __section(".data");
|
||||
static int cols __section(".data");
|
||||
|
||||
#ifdef CONFIG_KERNEL_GZIP
|
||||
#include "../../../../lib/decompress_inflate.c"
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@
|
|||
#define BOOT_BOOT_H
|
||||
#include "../ctype.h"
|
||||
|
||||
#include "efi.h"
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
#define memptr long
|
||||
#else
|
||||
|
|
@ -120,17 +122,23 @@ static inline void console_init(void)
|
|||
{ }
|
||||
#endif
|
||||
|
||||
void set_sev_encryption_mask(void);
|
||||
|
||||
#ifdef CONFIG_AMD_MEM_ENCRYPT
|
||||
void sev_enable(struct boot_params *bp);
|
||||
void sev_es_shutdown_ghcb(void);
|
||||
extern bool sev_es_check_ghcb_fault(unsigned long address);
|
||||
void snp_set_page_private(unsigned long paddr);
|
||||
void snp_set_page_shared(unsigned long paddr);
|
||||
void sev_prep_identity_maps(unsigned long top_level_pgt);
|
||||
#else
|
||||
static inline void sev_enable(struct boot_params *bp) { }
|
||||
static inline void sev_es_shutdown_ghcb(void) { }
|
||||
static inline bool sev_es_check_ghcb_fault(unsigned long address)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline void snp_set_page_private(unsigned long paddr) { }
|
||||
static inline void snp_set_page_shared(unsigned long paddr) { }
|
||||
static inline void sev_prep_identity_maps(unsigned long top_level_pgt) { }
|
||||
#endif
|
||||
|
||||
/* acpi.c */
|
||||
|
|
@ -151,6 +159,7 @@ static inline int count_immovable_mem_regions(void) { return 0; }
|
|||
#ifdef CONFIG_X86_5LEVEL
|
||||
extern unsigned int __pgtable_l5_enabled, pgdir_shift, ptrs_per_p4d;
|
||||
#endif
|
||||
extern void kernel_add_identity_map(unsigned long start, unsigned long end);
|
||||
|
||||
/* Used by PAGE_KERN* macros: */
|
||||
extern pteval_t __default_kernel_pte_mask;
|
||||
|
|
@ -172,4 +181,47 @@ void boot_stage2_vc(void);
|
|||
|
||||
unsigned long sev_verify_cbit(unsigned long cr3);
|
||||
|
||||
enum efi_type {
|
||||
EFI_TYPE_64,
|
||||
EFI_TYPE_32,
|
||||
EFI_TYPE_NONE,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_EFI
|
||||
/* helpers for early EFI config table access */
|
||||
enum efi_type efi_get_type(struct boot_params *bp);
|
||||
unsigned long efi_get_system_table(struct boot_params *bp);
|
||||
int efi_get_conf_table(struct boot_params *bp, unsigned long *cfg_tbl_pa,
|
||||
unsigned int *cfg_tbl_len);
|
||||
unsigned long efi_find_vendor_table(struct boot_params *bp,
|
||||
unsigned long cfg_tbl_pa,
|
||||
unsigned int cfg_tbl_len,
|
||||
efi_guid_t guid);
|
||||
#else
|
||||
static inline enum efi_type efi_get_type(struct boot_params *bp)
|
||||
{
|
||||
return EFI_TYPE_NONE;
|
||||
}
|
||||
|
||||
static inline unsigned long efi_get_system_table(struct boot_params *bp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int efi_get_conf_table(struct boot_params *bp,
|
||||
unsigned long *cfg_tbl_pa,
|
||||
unsigned int *cfg_tbl_len)
|
||||
{
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static inline unsigned long efi_find_vendor_table(struct boot_params *bp,
|
||||
unsigned long cfg_tbl_pa,
|
||||
unsigned int cfg_tbl_len,
|
||||
efi_guid_t guid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_EFI */
|
||||
|
||||
#endif /* BOOT_COMPRESSED_MISC_H */
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include "misc.h"
|
||||
#include <linux/efi.h>
|
||||
#include <asm/e820/types.h>
|
||||
#include <asm/processor.h>
|
||||
#include <asm/efi.h>
|
||||
#include "pgtable.h"
|
||||
#include "../string.h"
|
||||
#include "efi.h"
|
||||
|
||||
#define BIOS_START_MIN 0x20000U /* 128K, less than this is insane */
|
||||
#define BIOS_START_MAX 0x9f000U /* 640K, absolute maximum */
|
||||
|
|
|
|||
|
|
@ -20,8 +20,10 @@
|
|||
#include <asm/fpu/xcr.h>
|
||||
#include <asm/ptrace.h>
|
||||
#include <asm/svm.h>
|
||||
#include <asm/cpuid.h>
|
||||
|
||||
#include "error.h"
|
||||
#include "../msr.h"
|
||||
|
||||
struct ghcb boot_ghcb_page __aligned(PAGE_SIZE);
|
||||
struct ghcb *boot_ghcb;
|
||||
|
|
@ -56,23 +58,19 @@ static unsigned long insn_get_seg_base(struct pt_regs *regs, int seg_reg_idx)
|
|||
|
||||
static inline u64 sev_es_rd_ghcb_msr(void)
|
||||
{
|
||||
unsigned long low, high;
|
||||
struct msr m;
|
||||
|
||||
asm volatile("rdmsr" : "=a" (low), "=d" (high) :
|
||||
"c" (MSR_AMD64_SEV_ES_GHCB));
|
||||
boot_rdmsr(MSR_AMD64_SEV_ES_GHCB, &m);
|
||||
|
||||
return ((high << 32) | low);
|
||||
return m.q;
|
||||
}
|
||||
|
||||
static inline void sev_es_wr_ghcb_msr(u64 val)
|
||||
{
|
||||
u32 low, high;
|
||||
struct msr m;
|
||||
|
||||
low = val & 0xffffffffUL;
|
||||
high = val >> 32;
|
||||
|
||||
asm volatile("wrmsr" : : "c" (MSR_AMD64_SEV_ES_GHCB),
|
||||
"a"(low), "d" (high) : "memory");
|
||||
m.q = val;
|
||||
boot_wrmsr(MSR_AMD64_SEV_ES_GHCB, &m);
|
||||
}
|
||||
|
||||
static enum es_result vc_decode_insn(struct es_em_ctxt *ctxt)
|
||||
|
|
@ -119,11 +117,54 @@ static enum es_result vc_read_mem(struct es_em_ctxt *ctxt,
|
|||
/* Include code for early handlers */
|
||||
#include "../../kernel/sev-shared.c"
|
||||
|
||||
static bool early_setup_sev_es(void)
|
||||
static inline bool sev_snp_enabled(void)
|
||||
{
|
||||
if (!sev_es_negotiate_protocol())
|
||||
sev_es_terminate(GHCB_SEV_ES_PROT_UNSUPPORTED);
|
||||
return sev_status & MSR_AMD64_SEV_SNP_ENABLED;
|
||||
}
|
||||
|
||||
static void __page_state_change(unsigned long paddr, enum psc_op op)
|
||||
{
|
||||
u64 val;
|
||||
|
||||
if (!sev_snp_enabled())
|
||||
return;
|
||||
|
||||
/*
|
||||
* If private -> shared then invalidate the page before requesting the
|
||||
* state change in the RMP table.
|
||||
*/
|
||||
if (op == SNP_PAGE_STATE_SHARED && pvalidate(paddr, RMP_PG_SIZE_4K, 0))
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PVALIDATE);
|
||||
|
||||
/* Issue VMGEXIT to change the page state in RMP table. */
|
||||
sev_es_wr_ghcb_msr(GHCB_MSR_PSC_REQ_GFN(paddr >> PAGE_SHIFT, op));
|
||||
VMGEXIT();
|
||||
|
||||
/* Read the response of the VMGEXIT. */
|
||||
val = sev_es_rd_ghcb_msr();
|
||||
if ((GHCB_RESP_CODE(val) != GHCB_MSR_PSC_RESP) || GHCB_MSR_PSC_RESP_VAL(val))
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PSC);
|
||||
|
||||
/*
|
||||
* Now that page state is changed in the RMP table, validate it so that it is
|
||||
* consistent with the RMP entry.
|
||||
*/
|
||||
if (op == SNP_PAGE_STATE_PRIVATE && pvalidate(paddr, RMP_PG_SIZE_4K, 1))
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PVALIDATE);
|
||||
}
|
||||
|
||||
void snp_set_page_private(unsigned long paddr)
|
||||
{
|
||||
__page_state_change(paddr, SNP_PAGE_STATE_PRIVATE);
|
||||
}
|
||||
|
||||
void snp_set_page_shared(unsigned long paddr)
|
||||
{
|
||||
__page_state_change(paddr, SNP_PAGE_STATE_SHARED);
|
||||
}
|
||||
|
||||
static bool early_setup_ghcb(void)
|
||||
{
|
||||
if (set_page_decrypted((unsigned long)&boot_ghcb_page))
|
||||
return false;
|
||||
|
||||
|
|
@ -135,6 +176,10 @@ static bool early_setup_sev_es(void)
|
|||
/* Initialize lookup tables for the instruction decoder */
|
||||
inat_init_tables();
|
||||
|
||||
/* SNP guest requires the GHCB GPA must be registered */
|
||||
if (sev_snp_enabled())
|
||||
snp_register_ghcb_early(__pa(&boot_ghcb_page));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -174,8 +219,8 @@ void do_boot_stage2_vc(struct pt_regs *regs, unsigned long exit_code)
|
|||
struct es_em_ctxt ctxt;
|
||||
enum es_result result;
|
||||
|
||||
if (!boot_ghcb && !early_setup_sev_es())
|
||||
sev_es_terminate(GHCB_SEV_ES_GEN_REQ);
|
||||
if (!boot_ghcb && !early_setup_ghcb())
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SEV_ES_GEN_REQ);
|
||||
|
||||
vc_ghcb_invalidate(boot_ghcb);
|
||||
result = vc_init_em_ctxt(&ctxt, regs, exit_code);
|
||||
|
|
@ -202,5 +247,191 @@ void do_boot_stage2_vc(struct pt_regs *regs, unsigned long exit_code)
|
|||
if (result == ES_OK)
|
||||
vc_finish_insn(&ctxt);
|
||||
else if (result != ES_RETRY)
|
||||
sev_es_terminate(GHCB_SEV_ES_GEN_REQ);
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SEV_ES_GEN_REQ);
|
||||
}
|
||||
|
||||
static void enforce_vmpl0(void)
|
||||
{
|
||||
u64 attrs;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* RMPADJUST modifies RMP permissions of a lesser-privileged (numerically
|
||||
* higher) privilege level. Here, clear the VMPL1 permission mask of the
|
||||
* GHCB page. If the guest is not running at VMPL0, this will fail.
|
||||
*
|
||||
* If the guest is running at VMPL0, it will succeed. Even if that operation
|
||||
* modifies permission bits, it is still ok to do so currently because Linux
|
||||
* SNP guests are supported only on VMPL0 so VMPL1 or higher permission masks
|
||||
* changing is a don't-care.
|
||||
*/
|
||||
attrs = 1;
|
||||
if (rmpadjust((unsigned long)&boot_ghcb_page, RMP_PG_SIZE_4K, attrs))
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_NOT_VMPL0);
|
||||
}
|
||||
|
||||
void sev_enable(struct boot_params *bp)
|
||||
{
|
||||
unsigned int eax, ebx, ecx, edx;
|
||||
struct msr m;
|
||||
bool snp;
|
||||
|
||||
/*
|
||||
* Setup/preliminary detection of SNP. This will be sanity-checked
|
||||
* against CPUID/MSR values later.
|
||||
*/
|
||||
snp = snp_init(bp);
|
||||
|
||||
/* Check for the SME/SEV support leaf */
|
||||
eax = 0x80000000;
|
||||
ecx = 0;
|
||||
native_cpuid(&eax, &ebx, &ecx, &edx);
|
||||
if (eax < 0x8000001f)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Check for the SME/SEV feature:
|
||||
* CPUID Fn8000_001F[EAX]
|
||||
* - Bit 0 - Secure Memory Encryption support
|
||||
* - Bit 1 - Secure Encrypted Virtualization support
|
||||
* CPUID Fn8000_001F[EBX]
|
||||
* - Bits 5:0 - Pagetable bit position used to indicate encryption
|
||||
*/
|
||||
eax = 0x8000001f;
|
||||
ecx = 0;
|
||||
native_cpuid(&eax, &ebx, &ecx, &edx);
|
||||
/* Check whether SEV is supported */
|
||||
if (!(eax & BIT(1))) {
|
||||
if (snp)
|
||||
error("SEV-SNP support indicated by CC blob, but not CPUID.");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set the SME mask if this is an SEV guest. */
|
||||
boot_rdmsr(MSR_AMD64_SEV, &m);
|
||||
sev_status = m.q;
|
||||
if (!(sev_status & MSR_AMD64_SEV_ENABLED))
|
||||
return;
|
||||
|
||||
/* Negotiate the GHCB protocol version. */
|
||||
if (sev_status & MSR_AMD64_SEV_ES_ENABLED) {
|
||||
if (!sev_es_negotiate_protocol())
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SEV_ES_PROT_UNSUPPORTED);
|
||||
}
|
||||
|
||||
/*
|
||||
* SNP is supported in v2 of the GHCB spec which mandates support for HV
|
||||
* features.
|
||||
*/
|
||||
if (sev_status & MSR_AMD64_SEV_SNP_ENABLED) {
|
||||
if (!(get_hv_features() & GHCB_HV_FT_SNP))
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SNP_UNSUPPORTED);
|
||||
|
||||
enforce_vmpl0();
|
||||
}
|
||||
|
||||
if (snp && !(sev_status & MSR_AMD64_SEV_SNP_ENABLED))
|
||||
error("SEV-SNP supported indicated by CC blob, but not SEV status MSR.");
|
||||
|
||||
sme_me_mask = BIT_ULL(ebx & 0x3f);
|
||||
}
|
||||
|
||||
/* Search for Confidential Computing blob in the EFI config table. */
|
||||
static struct cc_blob_sev_info *find_cc_blob_efi(struct boot_params *bp)
|
||||
{
|
||||
unsigned long cfg_table_pa;
|
||||
unsigned int cfg_table_len;
|
||||
int ret;
|
||||
|
||||
ret = efi_get_conf_table(bp, &cfg_table_pa, &cfg_table_len);
|
||||
if (ret)
|
||||
return NULL;
|
||||
|
||||
return (struct cc_blob_sev_info *)efi_find_vendor_table(bp, cfg_table_pa,
|
||||
cfg_table_len,
|
||||
EFI_CC_BLOB_GUID);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initial set up of SNP relies on information provided by the
|
||||
* Confidential Computing blob, which can be passed to the boot kernel
|
||||
* by firmware/bootloader in the following ways:
|
||||
*
|
||||
* - via an entry in the EFI config table
|
||||
* - via a setup_data structure, as defined by the Linux Boot Protocol
|
||||
*
|
||||
* Scan for the blob in that order.
|
||||
*/
|
||||
static struct cc_blob_sev_info *find_cc_blob(struct boot_params *bp)
|
||||
{
|
||||
struct cc_blob_sev_info *cc_info;
|
||||
|
||||
cc_info = find_cc_blob_efi(bp);
|
||||
if (cc_info)
|
||||
goto found_cc_info;
|
||||
|
||||
cc_info = find_cc_blob_setup_data(bp);
|
||||
if (!cc_info)
|
||||
return NULL;
|
||||
|
||||
found_cc_info:
|
||||
if (cc_info->magic != CC_BLOB_SEV_HDR_MAGIC)
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SNP_UNSUPPORTED);
|
||||
|
||||
return cc_info;
|
||||
}
|
||||
|
||||
/*
|
||||
* Indicate SNP based on presence of SNP-specific CC blob. Subsequent checks
|
||||
* will verify the SNP CPUID/MSR bits.
|
||||
*/
|
||||
bool snp_init(struct boot_params *bp)
|
||||
{
|
||||
struct cc_blob_sev_info *cc_info;
|
||||
|
||||
if (!bp)
|
||||
return false;
|
||||
|
||||
cc_info = find_cc_blob(bp);
|
||||
if (!cc_info)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* If a SNP-specific Confidential Computing blob is present, then
|
||||
* firmware/bootloader have indicated SNP support. Verifying this
|
||||
* involves CPUID checks which will be more reliable if the SNP
|
||||
* CPUID table is used. See comments over snp_setup_cpuid_table() for
|
||||
* more details.
|
||||
*/
|
||||
setup_cpuid_table(cc_info);
|
||||
|
||||
/*
|
||||
* Pass run-time kernel a pointer to CC info via boot_params so EFI
|
||||
* config table doesn't need to be searched again during early startup
|
||||
* phase.
|
||||
*/
|
||||
bp->cc_blob_address = (u32)(unsigned long)cc_info;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void sev_prep_identity_maps(unsigned long top_level_pgt)
|
||||
{
|
||||
/*
|
||||
* The Confidential Computing blob is used very early in uncompressed
|
||||
* kernel to find the in-memory CPUID table to handle CPUID
|
||||
* instructions. Make sure an identity-mapping exists so it can be
|
||||
* accessed after switchover.
|
||||
*/
|
||||
if (sev_snp_enabled()) {
|
||||
unsigned long cc_info_pa = boot_params->cc_blob_address;
|
||||
struct cc_blob_sev_info *cc_info;
|
||||
|
||||
kernel_add_identity_map(cc_info_pa, cc_info_pa + sizeof(*cc_info));
|
||||
|
||||
cc_info = (struct cc_blob_sev_info *)cc_info_pa;
|
||||
kernel_add_identity_map(cc_info->cpuid_phys, cc_info->cpuid_phys + cc_info->cpuid_len);
|
||||
}
|
||||
|
||||
sev_verify_cbit(top_level_pgt);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <asm/required-features.h>
|
||||
#include <asm/msr-index.h>
|
||||
#include "string.h"
|
||||
#include "msr.h"
|
||||
|
||||
static u32 err_flags[NCAPINTS];
|
||||
|
||||
|
|
@ -130,12 +131,11 @@ int check_cpu(int *cpu_level_ptr, int *req_level_ptr, u32 **err_flags_ptr)
|
|||
/* If this is an AMD and we're only missing SSE+SSE2, try to
|
||||
turn them on */
|
||||
|
||||
u32 ecx = MSR_K7_HWCR;
|
||||
u32 eax, edx;
|
||||
struct msr m;
|
||||
|
||||
asm("rdmsr" : "=a" (eax), "=d" (edx) : "c" (ecx));
|
||||
eax &= ~(1 << 15);
|
||||
asm("wrmsr" : : "a" (eax), "d" (edx), "c" (ecx));
|
||||
boot_rdmsr(MSR_K7_HWCR, &m);
|
||||
m.l &= ~(1 << 15);
|
||||
boot_wrmsr(MSR_K7_HWCR, &m);
|
||||
|
||||
get_cpuflags(); /* Make sure it really did something */
|
||||
err = check_cpuflags();
|
||||
|
|
@ -145,28 +145,28 @@ int check_cpu(int *cpu_level_ptr, int *req_level_ptr, u32 **err_flags_ptr)
|
|||
/* If this is a VIA C3, we might have to enable CX8
|
||||
explicitly */
|
||||
|
||||
u32 ecx = MSR_VIA_FCR;
|
||||
u32 eax, edx;
|
||||
struct msr m;
|
||||
|
||||
asm("rdmsr" : "=a" (eax), "=d" (edx) : "c" (ecx));
|
||||
eax |= (1<<1)|(1<<7);
|
||||
asm("wrmsr" : : "a" (eax), "d" (edx), "c" (ecx));
|
||||
boot_rdmsr(MSR_VIA_FCR, &m);
|
||||
m.l |= (1 << 1) | (1 << 7);
|
||||
boot_wrmsr(MSR_VIA_FCR, &m);
|
||||
|
||||
set_bit(X86_FEATURE_CX8, cpu.flags);
|
||||
err = check_cpuflags();
|
||||
} else if (err == 0x01 && is_transmeta()) {
|
||||
/* Transmeta might have masked feature bits in word 0 */
|
||||
|
||||
u32 ecx = 0x80860004;
|
||||
u32 eax, edx;
|
||||
struct msr m, m_tmp;
|
||||
u32 level = 1;
|
||||
|
||||
asm("rdmsr" : "=a" (eax), "=d" (edx) : "c" (ecx));
|
||||
asm("wrmsr" : : "a" (~0), "d" (edx), "c" (ecx));
|
||||
boot_rdmsr(0x80860004, &m);
|
||||
m_tmp = m;
|
||||
m_tmp.l = ~0;
|
||||
boot_wrmsr(0x80860004, &m_tmp);
|
||||
asm("cpuid"
|
||||
: "+a" (level), "=d" (cpu.flags[0])
|
||||
: : "ecx", "ebx");
|
||||
asm("wrmsr" : : "a" (eax), "d" (edx), "c" (ecx));
|
||||
boot_wrmsr(0x80860004, &m);
|
||||
|
||||
err = check_cpuflags();
|
||||
} else if (err == 0x01 &&
|
||||
|
|
|
|||
26
arch/x86/boot/msr.h
Normal file
26
arch/x86/boot/msr.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Helpers/definitions related to MSR access.
|
||||
*/
|
||||
|
||||
#ifndef BOOT_MSR_H
|
||||
#define BOOT_MSR_H
|
||||
|
||||
#include <asm/shared/msr.h>
|
||||
|
||||
/*
|
||||
* The kernel proper already defines rdmsr()/wrmsr(), but they are not for the
|
||||
* boot kernel since they rely on tracepoint/exception handling infrastructure
|
||||
* that's not available here.
|
||||
*/
|
||||
static inline void boot_rdmsr(unsigned int reg, struct msr *m)
|
||||
{
|
||||
asm volatile("rdmsr" : "=a" (m->l), "=d" (m->h) : "c" (reg));
|
||||
}
|
||||
|
||||
static inline void boot_wrmsr(unsigned int reg, const struct msr *m)
|
||||
{
|
||||
asm volatile("wrmsr" : : "c" (reg), "a"(m->l), "d" (m->h) : "memory");
|
||||
}
|
||||
|
||||
#endif /* BOOT_MSR_H */
|
||||
|
|
@ -57,6 +57,9 @@ static bool amd_cc_platform_has(enum cc_attr attr)
|
|||
return (sev_status & MSR_AMD64_SEV_ENABLED) &&
|
||||
!(sev_status & MSR_AMD64_SEV_ES_ENABLED);
|
||||
|
||||
case CC_ATTR_GUEST_SEV_SNP:
|
||||
return sev_status & MSR_AMD64_SEV_SNP_ENABLED;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -215,8 +215,13 @@ syscall_return_via_sysret:
|
|||
|
||||
popq %rdi
|
||||
popq %rsp
|
||||
SYM_INNER_LABEL(entry_SYSRETQ_unsafe_stack, SYM_L_GLOBAL)
|
||||
ANNOTATE_NOENDBR
|
||||
swapgs
|
||||
sysretq
|
||||
SYM_INNER_LABEL(entry_SYSRETQ_end, SYM_L_GLOBAL)
|
||||
ANNOTATE_NOENDBR
|
||||
int3
|
||||
SYM_CODE_END(entry_SYSCALL_64)
|
||||
|
||||
/*
|
||||
|
|
@ -508,6 +513,7 @@ SYM_CODE_START(\asmsym)
|
|||
call vc_switch_off_ist
|
||||
movq %rax, %rsp /* Switch to new stack */
|
||||
|
||||
ENCODE_FRAME_POINTER
|
||||
UNWIND_HINT_REGS
|
||||
|
||||
/* Update pt_regs */
|
||||
|
|
|
|||
|
|
@ -297,6 +297,8 @@ sysret32_from_system_call:
|
|||
* code. We zero R8-R10 to avoid info leaks.
|
||||
*/
|
||||
movq RSP-ORIG_RAX(%rsp), %rsp
|
||||
SYM_INNER_LABEL(entry_SYSRETL_compat_unsafe_stack, SYM_L_GLOBAL)
|
||||
ANNOTATE_NOENDBR
|
||||
|
||||
/*
|
||||
* The original userspace %rsp (RSP-ORIG_RAX(%rsp)) is stored
|
||||
|
|
@ -314,6 +316,9 @@ sysret32_from_system_call:
|
|||
xorl %r10d, %r10d
|
||||
swapgs
|
||||
sysretl
|
||||
SYM_INNER_LABEL(entry_SYSRETL_compat_end, SYM_L_GLOBAL)
|
||||
ANNOTATE_NOENDBR
|
||||
int3
|
||||
SYM_CODE_END(entry_SYSCALL_compat)
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ static void sanitize_boot_params(struct boot_params *boot_params)
|
|||
BOOT_PARAM_PRESERVE(hdr),
|
||||
BOOT_PARAM_PRESERVE(e820_table),
|
||||
BOOT_PARAM_PRESERVE(eddbuf),
|
||||
BOOT_PARAM_PRESERVE(cc_blob_address),
|
||||
};
|
||||
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
|
|
|
|||
34
arch/x86/include/asm/cpuid.h
Normal file
34
arch/x86/include/asm/cpuid.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* CPUID-related helpers/definitions
|
||||
*
|
||||
* Derived from arch/x86/kvm/cpuid.c
|
||||
*/
|
||||
|
||||
#ifndef _ASM_X86_CPUID_H
|
||||
#define _ASM_X86_CPUID_H
|
||||
|
||||
static __always_inline bool cpuid_function_is_indexed(u32 function)
|
||||
{
|
||||
switch (function) {
|
||||
case 4:
|
||||
case 7:
|
||||
case 0xb:
|
||||
case 0xd:
|
||||
case 0xf:
|
||||
case 0x10:
|
||||
case 0x12:
|
||||
case 0x14:
|
||||
case 0x17:
|
||||
case 0x18:
|
||||
case 0x1d:
|
||||
case 0x1e:
|
||||
case 0x1f:
|
||||
case 0x8000001d:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif /* _ASM_X86_CPUID_H */
|
||||
|
|
@ -502,8 +502,10 @@
|
|||
#define MSR_AMD64_SEV 0xc0010131
|
||||
#define MSR_AMD64_SEV_ENABLED_BIT 0
|
||||
#define MSR_AMD64_SEV_ES_ENABLED_BIT 1
|
||||
#define MSR_AMD64_SEV_SNP_ENABLED_BIT 2
|
||||
#define MSR_AMD64_SEV_ENABLED BIT_ULL(MSR_AMD64_SEV_ENABLED_BIT)
|
||||
#define MSR_AMD64_SEV_ES_ENABLED BIT_ULL(MSR_AMD64_SEV_ES_ENABLED_BIT)
|
||||
#define MSR_AMD64_SEV_SNP_ENABLED BIT_ULL(MSR_AMD64_SEV_SNP_ENABLED_BIT)
|
||||
|
||||
#define MSR_AMD64_VIRT_SPEC_CTRL 0xc001011f
|
||||
|
||||
|
|
|
|||
|
|
@ -10,16 +10,7 @@
|
|||
#include <asm/errno.h>
|
||||
#include <asm/cpumask.h>
|
||||
#include <uapi/asm/msr.h>
|
||||
|
||||
struct msr {
|
||||
union {
|
||||
struct {
|
||||
u32 l;
|
||||
u32 h;
|
||||
};
|
||||
u64 q;
|
||||
};
|
||||
};
|
||||
#include <asm/shared/msr.h>
|
||||
|
||||
struct msr_info {
|
||||
u32 msr_no;
|
||||
|
|
|
|||
|
|
@ -93,6 +93,15 @@ struct irq_routing_table {
|
|||
struct irq_info slots[];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct irt_routing_table {
|
||||
u32 signature; /* IRT_SIGNATURE should be here */
|
||||
u8 size; /* Number of entries provided */
|
||||
u8 used; /* Number of entries actually used */
|
||||
u16 exclusive_irqs; /* IRQs devoted exclusively to
|
||||
PCI usage */
|
||||
struct irq_info slots[];
|
||||
} __attribute__((packed));
|
||||
|
||||
extern unsigned int pcibios_irq_mask;
|
||||
|
||||
extern raw_spinlock_t pci_config_lock;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ void syscall_init(void);
|
|||
#ifdef CONFIG_X86_64
|
||||
void entry_SYSCALL_64(void);
|
||||
void entry_SYSCALL_64_safe_stack(void);
|
||||
void entry_SYSRETQ_unsafe_stack(void);
|
||||
void entry_SYSRETQ_end(void);
|
||||
long do_arch_prctl_64(struct task_struct *task, int option, unsigned long arg2);
|
||||
#endif
|
||||
|
||||
|
|
@ -28,6 +30,8 @@ void entry_SYSENTER_compat(void);
|
|||
void __end_entry_SYSENTER_compat(void);
|
||||
void entry_SYSCALL_compat(void);
|
||||
void entry_SYSCALL_compat_safe_stack(void);
|
||||
void entry_SYSRETL_compat_unsafe_stack(void);
|
||||
void entry_SYSRETL_compat_end(void);
|
||||
void entry_INT80_compat(void);
|
||||
#ifdef CONFIG_XEN_PV
|
||||
void xen_entry_INT80_compat(void);
|
||||
|
|
|
|||
|
|
@ -186,9 +186,13 @@ static __always_inline bool ip_within_syscall_gap(struct pt_regs *regs)
|
|||
bool ret = (regs->ip >= (unsigned long)entry_SYSCALL_64 &&
|
||||
regs->ip < (unsigned long)entry_SYSCALL_64_safe_stack);
|
||||
|
||||
ret = ret || (regs->ip >= (unsigned long)entry_SYSRETQ_unsafe_stack &&
|
||||
regs->ip < (unsigned long)entry_SYSRETQ_end);
|
||||
#ifdef CONFIG_IA32_EMULATION
|
||||
ret = ret || (regs->ip >= (unsigned long)entry_SYSCALL_compat &&
|
||||
regs->ip < (unsigned long)entry_SYSCALL_compat_safe_stack);
|
||||
ret = ret || (regs->ip >= (unsigned long)entry_SYSRETL_compat_unsafe_stack &&
|
||||
regs->ip < (unsigned long)entry_SYSRETL_compat_end);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ extern unsigned long saved_video_mode;
|
|||
extern void reserve_standard_io_resources(void);
|
||||
extern void i386_reserve_resources(void);
|
||||
extern unsigned long __startup_64(unsigned long physaddr, struct boot_params *bp);
|
||||
extern unsigned long __startup_secondary_64(void);
|
||||
extern void startup_64_setup_env(unsigned long physbase);
|
||||
extern void early_setup_idt(void);
|
||||
extern void __init do_early_exception(struct pt_regs *regs, int trapnr);
|
||||
|
|
|
|||
|
|
@ -57,9 +57,79 @@
|
|||
#define GHCB_MSR_AP_RESET_HOLD_REQ 0x006
|
||||
#define GHCB_MSR_AP_RESET_HOLD_RESP 0x007
|
||||
|
||||
/* GHCB GPA Register */
|
||||
#define GHCB_MSR_REG_GPA_REQ 0x012
|
||||
#define GHCB_MSR_REG_GPA_REQ_VAL(v) \
|
||||
/* GHCBData[63:12] */ \
|
||||
(((u64)((v) & GENMASK_ULL(51, 0)) << 12) | \
|
||||
/* GHCBData[11:0] */ \
|
||||
GHCB_MSR_REG_GPA_REQ)
|
||||
|
||||
#define GHCB_MSR_REG_GPA_RESP 0x013
|
||||
#define GHCB_MSR_REG_GPA_RESP_VAL(v) \
|
||||
/* GHCBData[63:12] */ \
|
||||
(((u64)(v) & GENMASK_ULL(63, 12)) >> 12)
|
||||
|
||||
/*
|
||||
* SNP Page State Change Operation
|
||||
*
|
||||
* GHCBData[55:52] - Page operation:
|
||||
* 0x0001 Page assignment, Private
|
||||
* 0x0002 Page assignment, Shared
|
||||
*/
|
||||
enum psc_op {
|
||||
SNP_PAGE_STATE_PRIVATE = 1,
|
||||
SNP_PAGE_STATE_SHARED,
|
||||
};
|
||||
|
||||
#define GHCB_MSR_PSC_REQ 0x014
|
||||
#define GHCB_MSR_PSC_REQ_GFN(gfn, op) \
|
||||
/* GHCBData[55:52] */ \
|
||||
(((u64)((op) & 0xf) << 52) | \
|
||||
/* GHCBData[51:12] */ \
|
||||
((u64)((gfn) & GENMASK_ULL(39, 0)) << 12) | \
|
||||
/* GHCBData[11:0] */ \
|
||||
GHCB_MSR_PSC_REQ)
|
||||
|
||||
#define GHCB_MSR_PSC_RESP 0x015
|
||||
#define GHCB_MSR_PSC_RESP_VAL(val) \
|
||||
/* GHCBData[63:32] */ \
|
||||
(((u64)(val) & GENMASK_ULL(63, 32)) >> 32)
|
||||
|
||||
/* GHCB Hypervisor Feature Request/Response */
|
||||
#define GHCB_MSR_HV_FT_REQ 0x080
|
||||
#define GHCB_MSR_HV_FT_RESP 0x081
|
||||
#define GHCB_MSR_HV_FT_RESP_VAL(v) \
|
||||
/* GHCBData[63:12] */ \
|
||||
(((u64)(v) & GENMASK_ULL(63, 12)) >> 12)
|
||||
|
||||
#define GHCB_HV_FT_SNP BIT_ULL(0)
|
||||
#define GHCB_HV_FT_SNP_AP_CREATION BIT_ULL(1)
|
||||
|
||||
/* SNP Page State Change NAE event */
|
||||
#define VMGEXIT_PSC_MAX_ENTRY 253
|
||||
|
||||
struct psc_hdr {
|
||||
u16 cur_entry;
|
||||
u16 end_entry;
|
||||
u32 reserved;
|
||||
} __packed;
|
||||
|
||||
struct psc_entry {
|
||||
u64 cur_page : 12,
|
||||
gfn : 40,
|
||||
operation : 4,
|
||||
pagesize : 1,
|
||||
reserved : 7;
|
||||
} __packed;
|
||||
|
||||
struct snp_psc_desc {
|
||||
struct psc_hdr hdr;
|
||||
struct psc_entry entries[VMGEXIT_PSC_MAX_ENTRY];
|
||||
} __packed;
|
||||
|
||||
/* Guest message request error code */
|
||||
#define SNP_GUEST_REQ_INVALID_LEN BIT_ULL(32)
|
||||
|
||||
#define GHCB_MSR_TERM_REQ 0x100
|
||||
#define GHCB_MSR_TERM_REASON_SET_POS 12
|
||||
|
|
@ -73,8 +143,20 @@
|
|||
/* GHCBData[23:16] */ \
|
||||
((((u64)reason_val) & 0xff) << 16))
|
||||
|
||||
/* Error codes from reason set 0 */
|
||||
#define SEV_TERM_SET_GEN 0
|
||||
#define GHCB_SEV_ES_GEN_REQ 0
|
||||
#define GHCB_SEV_ES_PROT_UNSUPPORTED 1
|
||||
#define GHCB_SNP_UNSUPPORTED 2
|
||||
|
||||
/* Linux-specific reason codes (used with reason set 1) */
|
||||
#define SEV_TERM_SET_LINUX 1
|
||||
#define GHCB_TERM_REGISTER 0 /* GHCB GPA registration failure */
|
||||
#define GHCB_TERM_PSC 1 /* Page State Change failure */
|
||||
#define GHCB_TERM_PVALIDATE 2 /* Pvalidate failure */
|
||||
#define GHCB_TERM_NOT_VMPL0 3 /* SNP guest is not running at VMPL-0 */
|
||||
#define GHCB_TERM_CPUID 4 /* CPUID-validation failure */
|
||||
#define GHCB_TERM_CPUID_HV 5 /* CPUID failure during hypervisor fallback */
|
||||
|
||||
#define GHCB_RESP_CODE(v) ((v) & GHCB_MSR_INFO_MASK)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,10 @@
|
|||
#include <linux/types.h>
|
||||
#include <asm/insn.h>
|
||||
#include <asm/sev-common.h>
|
||||
#include <asm/bootparam.h>
|
||||
|
||||
#define GHCB_PROTO_OUR 0x0001UL
|
||||
#define GHCB_PROTOCOL_MAX 1ULL
|
||||
#define GHCB_PROTOCOL_MIN 1ULL
|
||||
#define GHCB_PROTOCOL_MAX 2ULL
|
||||
#define GHCB_DEFAULT_USAGE 0ULL
|
||||
|
||||
#define VMGEXIT() { asm volatile("rep; vmmcall\n\r"); }
|
||||
|
|
@ -42,6 +43,24 @@ struct es_em_ctxt {
|
|||
struct es_fault_info fi;
|
||||
};
|
||||
|
||||
/*
|
||||
* AMD SEV Confidential computing blob structure. The structure is
|
||||
* defined in OVMF UEFI firmware header:
|
||||
* https://github.com/tianocore/edk2/blob/master/OvmfPkg/Include/Guid/ConfidentialComputingSevSnpBlob.h
|
||||
*/
|
||||
#define CC_BLOB_SEV_HDR_MAGIC 0x45444d41
|
||||
struct cc_blob_sev_info {
|
||||
u32 magic;
|
||||
u16 version;
|
||||
u16 reserved;
|
||||
u64 secrets_phys;
|
||||
u32 secrets_len;
|
||||
u32 rsvd1;
|
||||
u64 cpuid_phys;
|
||||
u32 cpuid_len;
|
||||
u32 rsvd2;
|
||||
} __packed;
|
||||
|
||||
void do_vc_no_ghcb(struct pt_regs *regs, unsigned long exit_code);
|
||||
|
||||
static inline u64 lower_bits(u64 val, unsigned int bits)
|
||||
|
|
@ -60,6 +79,61 @@ extern void vc_no_ghcb(void);
|
|||
extern void vc_boot_ghcb(void);
|
||||
extern bool handle_vc_boot_ghcb(struct pt_regs *regs);
|
||||
|
||||
/* Software defined (when rFlags.CF = 1) */
|
||||
#define PVALIDATE_FAIL_NOUPDATE 255
|
||||
|
||||
/* RMP page size */
|
||||
#define RMP_PG_SIZE_4K 0
|
||||
|
||||
#define RMPADJUST_VMSA_PAGE_BIT BIT(16)
|
||||
|
||||
/* SNP Guest message request */
|
||||
struct snp_req_data {
|
||||
unsigned long req_gpa;
|
||||
unsigned long resp_gpa;
|
||||
unsigned long data_gpa;
|
||||
unsigned int data_npages;
|
||||
};
|
||||
|
||||
struct sev_guest_platform_data {
|
||||
u64 secrets_gpa;
|
||||
};
|
||||
|
||||
/*
|
||||
* The secrets page contains 96-bytes of reserved field that can be used by
|
||||
* the guest OS. The guest OS uses the area to save the message sequence
|
||||
* number for each VMPCK.
|
||||
*
|
||||
* See the GHCB spec section Secret page layout for the format for this area.
|
||||
*/
|
||||
struct secrets_os_area {
|
||||
u32 msg_seqno_0;
|
||||
u32 msg_seqno_1;
|
||||
u32 msg_seqno_2;
|
||||
u32 msg_seqno_3;
|
||||
u64 ap_jump_table_pa;
|
||||
u8 rsvd[40];
|
||||
u8 guest_usage[32];
|
||||
} __packed;
|
||||
|
||||
#define VMPCK_KEY_LEN 32
|
||||
|
||||
/* See the SNP spec version 0.9 for secrets page format */
|
||||
struct snp_secrets_page_layout {
|
||||
u32 version;
|
||||
u32 imien : 1,
|
||||
rsvd1 : 31;
|
||||
u32 fms;
|
||||
u32 rsvd2;
|
||||
u8 gosvw[16];
|
||||
u8 vmpck0[VMPCK_KEY_LEN];
|
||||
u8 vmpck1[VMPCK_KEY_LEN];
|
||||
u8 vmpck2[VMPCK_KEY_LEN];
|
||||
u8 vmpck3[VMPCK_KEY_LEN];
|
||||
struct secrets_os_area os_area;
|
||||
u8 rsvd3[3840];
|
||||
} __packed;
|
||||
|
||||
#ifdef CONFIG_AMD_MEM_ENCRYPT
|
||||
extern struct static_key_false sev_es_enable_key;
|
||||
extern void __sev_es_ist_enter(struct pt_regs *regs);
|
||||
|
|
@ -87,12 +161,71 @@ extern enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb,
|
|||
struct es_em_ctxt *ctxt,
|
||||
u64 exit_code, u64 exit_info_1,
|
||||
u64 exit_info_2);
|
||||
static inline int rmpadjust(unsigned long vaddr, bool rmp_psize, unsigned long attrs)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* "rmpadjust" mnemonic support in binutils 2.36 and newer */
|
||||
asm volatile(".byte 0xF3,0x0F,0x01,0xFE\n\t"
|
||||
: "=a"(rc)
|
||||
: "a"(vaddr), "c"(rmp_psize), "d"(attrs)
|
||||
: "memory", "cc");
|
||||
|
||||
return rc;
|
||||
}
|
||||
static inline int pvalidate(unsigned long vaddr, bool rmp_psize, bool validate)
|
||||
{
|
||||
bool no_rmpupdate;
|
||||
int rc;
|
||||
|
||||
/* "pvalidate" mnemonic support in binutils 2.36 and newer */
|
||||
asm volatile(".byte 0xF2, 0x0F, 0x01, 0xFF\n\t"
|
||||
CC_SET(c)
|
||||
: CC_OUT(c) (no_rmpupdate), "=a"(rc)
|
||||
: "a"(vaddr), "c"(rmp_psize), "d"(validate)
|
||||
: "memory", "cc");
|
||||
|
||||
if (no_rmpupdate)
|
||||
return PVALIDATE_FAIL_NOUPDATE;
|
||||
|
||||
return rc;
|
||||
}
|
||||
void setup_ghcb(void);
|
||||
void __init early_snp_set_memory_private(unsigned long vaddr, unsigned long paddr,
|
||||
unsigned int npages);
|
||||
void __init early_snp_set_memory_shared(unsigned long vaddr, unsigned long paddr,
|
||||
unsigned int npages);
|
||||
void __init snp_prep_memory(unsigned long paddr, unsigned int sz, enum psc_op op);
|
||||
void snp_set_memory_shared(unsigned long vaddr, unsigned int npages);
|
||||
void snp_set_memory_private(unsigned long vaddr, unsigned int npages);
|
||||
void snp_set_wakeup_secondary_cpu(void);
|
||||
bool snp_init(struct boot_params *bp);
|
||||
void snp_abort(void);
|
||||
int snp_issue_guest_request(u64 exit_code, struct snp_req_data *input, unsigned long *fw_err);
|
||||
#else
|
||||
static inline void sev_es_ist_enter(struct pt_regs *regs) { }
|
||||
static inline void sev_es_ist_exit(void) { }
|
||||
static inline int sev_es_setup_ap_jump_table(struct real_mode_header *rmh) { return 0; }
|
||||
static inline void sev_es_nmi_complete(void) { }
|
||||
static inline int sev_es_efi_map_ghcbs(pgd_t *pgd) { return 0; }
|
||||
static inline int pvalidate(unsigned long vaddr, bool rmp_psize, bool validate) { return 0; }
|
||||
static inline int rmpadjust(unsigned long vaddr, bool rmp_psize, unsigned long attrs) { return 0; }
|
||||
static inline void setup_ghcb(void) { }
|
||||
static inline void __init
|
||||
early_snp_set_memory_private(unsigned long vaddr, unsigned long paddr, unsigned int npages) { }
|
||||
static inline void __init
|
||||
early_snp_set_memory_shared(unsigned long vaddr, unsigned long paddr, unsigned int npages) { }
|
||||
static inline void __init snp_prep_memory(unsigned long paddr, unsigned int sz, enum psc_op op) { }
|
||||
static inline void snp_set_memory_shared(unsigned long vaddr, unsigned int npages) { }
|
||||
static inline void snp_set_memory_private(unsigned long vaddr, unsigned int npages) { }
|
||||
static inline void snp_set_wakeup_secondary_cpu(void) { }
|
||||
static inline bool snp_init(struct boot_params *bp) { return false; }
|
||||
static inline void snp_abort(void) { }
|
||||
static inline int snp_issue_guest_request(u64 exit_code, struct snp_req_data *input,
|
||||
unsigned long *fw_err)
|
||||
{
|
||||
return -ENOTTY;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
|||
15
arch/x86/include/asm/shared/msr.h
Normal file
15
arch/x86/include/asm/shared/msr.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _ASM_X86_SHARED_MSR_H
|
||||
#define _ASM_X86_SHARED_MSR_H
|
||||
|
||||
struct msr {
|
||||
union {
|
||||
struct {
|
||||
u32 l;
|
||||
u32 h;
|
||||
};
|
||||
u64 q;
|
||||
};
|
||||
};
|
||||
|
||||
#endif /* _ASM_X86_SHARED_MSR_H */
|
||||
|
|
@ -271,6 +271,7 @@ struct vmcb_seg {
|
|||
u64 base;
|
||||
} __packed;
|
||||
|
||||
/* Save area definition for legacy and SEV-MEM guests */
|
||||
struct vmcb_save_area {
|
||||
struct vmcb_seg es;
|
||||
struct vmcb_seg cs;
|
||||
|
|
@ -282,12 +283,12 @@ struct vmcb_save_area {
|
|||
struct vmcb_seg ldtr;
|
||||
struct vmcb_seg idtr;
|
||||
struct vmcb_seg tr;
|
||||
u8 reserved_1[43];
|
||||
u8 reserved_1[42];
|
||||
u8 vmpl;
|
||||
u8 cpl;
|
||||
u8 reserved_2[4];
|
||||
u64 efer;
|
||||
u8 reserved_3[104];
|
||||
u64 xss; /* Valid for SEV-ES only */
|
||||
u8 reserved_3[112];
|
||||
u64 cr4;
|
||||
u64 cr3;
|
||||
u64 cr0;
|
||||
|
|
@ -297,7 +298,9 @@ struct vmcb_save_area {
|
|||
u64 rip;
|
||||
u8 reserved_4[88];
|
||||
u64 rsp;
|
||||
u8 reserved_5[24];
|
||||
u64 s_cet;
|
||||
u64 ssp;
|
||||
u64 isst_addr;
|
||||
u64 rax;
|
||||
u64 star;
|
||||
u64 lstar;
|
||||
|
|
@ -308,29 +311,86 @@ struct vmcb_save_area {
|
|||
u64 sysenter_esp;
|
||||
u64 sysenter_eip;
|
||||
u64 cr2;
|
||||
u8 reserved_6[32];
|
||||
u8 reserved_5[32];
|
||||
u64 g_pat;
|
||||
u64 dbgctl;
|
||||
u64 br_from;
|
||||
u64 br_to;
|
||||
u64 last_excp_from;
|
||||
u64 last_excp_to;
|
||||
|
||||
/*
|
||||
* The following part of the save area is valid only for
|
||||
* SEV-ES guests when referenced through the GHCB or for
|
||||
* saving to the host save area.
|
||||
*/
|
||||
u8 reserved_7[72];
|
||||
u8 reserved_6[72];
|
||||
u32 spec_ctrl; /* Guest version of SPEC_CTRL at 0x2E0 */
|
||||
u8 reserved_7b[4];
|
||||
} __packed;
|
||||
|
||||
/* Save area definition for SEV-ES and SEV-SNP guests */
|
||||
struct sev_es_save_area {
|
||||
struct vmcb_seg es;
|
||||
struct vmcb_seg cs;
|
||||
struct vmcb_seg ss;
|
||||
struct vmcb_seg ds;
|
||||
struct vmcb_seg fs;
|
||||
struct vmcb_seg gs;
|
||||
struct vmcb_seg gdtr;
|
||||
struct vmcb_seg ldtr;
|
||||
struct vmcb_seg idtr;
|
||||
struct vmcb_seg tr;
|
||||
u64 vmpl0_ssp;
|
||||
u64 vmpl1_ssp;
|
||||
u64 vmpl2_ssp;
|
||||
u64 vmpl3_ssp;
|
||||
u64 u_cet;
|
||||
u8 reserved_1[2];
|
||||
u8 vmpl;
|
||||
u8 cpl;
|
||||
u8 reserved_2[4];
|
||||
u64 efer;
|
||||
u8 reserved_3[104];
|
||||
u64 xss;
|
||||
u64 cr4;
|
||||
u64 cr3;
|
||||
u64 cr0;
|
||||
u64 dr7;
|
||||
u64 dr6;
|
||||
u64 rflags;
|
||||
u64 rip;
|
||||
u64 dr0;
|
||||
u64 dr1;
|
||||
u64 dr2;
|
||||
u64 dr3;
|
||||
u64 dr0_addr_mask;
|
||||
u64 dr1_addr_mask;
|
||||
u64 dr2_addr_mask;
|
||||
u64 dr3_addr_mask;
|
||||
u8 reserved_4[24];
|
||||
u64 rsp;
|
||||
u64 s_cet;
|
||||
u64 ssp;
|
||||
u64 isst_addr;
|
||||
u64 rax;
|
||||
u64 star;
|
||||
u64 lstar;
|
||||
u64 cstar;
|
||||
u64 sfmask;
|
||||
u64 kernel_gs_base;
|
||||
u64 sysenter_cs;
|
||||
u64 sysenter_esp;
|
||||
u64 sysenter_eip;
|
||||
u64 cr2;
|
||||
u8 reserved_5[32];
|
||||
u64 g_pat;
|
||||
u64 dbgctl;
|
||||
u64 br_from;
|
||||
u64 br_to;
|
||||
u64 last_excp_from;
|
||||
u64 last_excp_to;
|
||||
u8 reserved_7[80];
|
||||
u32 pkru;
|
||||
u8 reserved_7a[20];
|
||||
u64 reserved_8; /* rax already available at 0x01f8 */
|
||||
u8 reserved_8[20];
|
||||
u64 reserved_9; /* rax already available at 0x01f8 */
|
||||
u64 rcx;
|
||||
u64 rdx;
|
||||
u64 rbx;
|
||||
u64 reserved_9; /* rsp already available at 0x01d8 */
|
||||
u64 reserved_10; /* rsp already available at 0x01d8 */
|
||||
u64 rbp;
|
||||
u64 rsi;
|
||||
u64 rdi;
|
||||
|
|
@ -342,22 +402,83 @@ struct vmcb_save_area {
|
|||
u64 r13;
|
||||
u64 r14;
|
||||
u64 r15;
|
||||
u8 reserved_10[16];
|
||||
u8 reserved_11[16];
|
||||
u64 guest_exit_info_1;
|
||||
u64 guest_exit_info_2;
|
||||
u64 guest_exit_int_info;
|
||||
u64 guest_nrip;
|
||||
u64 sev_features;
|
||||
u64 vintr_ctrl;
|
||||
u64 guest_exit_code;
|
||||
u64 virtual_tom;
|
||||
u64 tlb_id;
|
||||
u64 pcpu_id;
|
||||
u64 event_inj;
|
||||
u64 xcr0;
|
||||
u8 reserved_12[16];
|
||||
|
||||
/* Floating point area */
|
||||
u64 x87_dp;
|
||||
u32 mxcsr;
|
||||
u16 x87_ftw;
|
||||
u16 x87_fsw;
|
||||
u16 x87_fcw;
|
||||
u16 x87_fop;
|
||||
u16 x87_ds;
|
||||
u16 x87_cs;
|
||||
u64 x87_rip;
|
||||
u8 fpreg_x87[80];
|
||||
u8 fpreg_xmm[256];
|
||||
u8 fpreg_ymm[256];
|
||||
} __packed;
|
||||
|
||||
struct ghcb_save_area {
|
||||
u8 reserved_1[203];
|
||||
u8 cpl;
|
||||
u8 reserved_2[116];
|
||||
u64 xss;
|
||||
u8 reserved_3[24];
|
||||
u64 dr7;
|
||||
u8 reserved_4[16];
|
||||
u64 rip;
|
||||
u8 reserved_5[88];
|
||||
u64 rsp;
|
||||
u8 reserved_6[24];
|
||||
u64 rax;
|
||||
u8 reserved_7[264];
|
||||
u64 rcx;
|
||||
u64 rdx;
|
||||
u64 rbx;
|
||||
u8 reserved_8[8];
|
||||
u64 rbp;
|
||||
u64 rsi;
|
||||
u64 rdi;
|
||||
u64 r8;
|
||||
u64 r9;
|
||||
u64 r10;
|
||||
u64 r11;
|
||||
u64 r12;
|
||||
u64 r13;
|
||||
u64 r14;
|
||||
u64 r15;
|
||||
u8 reserved_9[16];
|
||||
u64 sw_exit_code;
|
||||
u64 sw_exit_info_1;
|
||||
u64 sw_exit_info_2;
|
||||
u64 sw_scratch;
|
||||
u8 reserved_11[56];
|
||||
u8 reserved_10[56];
|
||||
u64 xcr0;
|
||||
u8 valid_bitmap[16];
|
||||
u64 x87_state_gpa;
|
||||
} __packed;
|
||||
|
||||
struct ghcb {
|
||||
struct vmcb_save_area save;
|
||||
u8 reserved_save[2048 - sizeof(struct vmcb_save_area)];
|
||||
#define GHCB_SHARED_BUF_SIZE 2032
|
||||
|
||||
u8 shared_buffer[2032];
|
||||
struct ghcb {
|
||||
struct ghcb_save_area save;
|
||||
u8 reserved_save[2048 - sizeof(struct ghcb_save_area)];
|
||||
|
||||
u8 shared_buffer[GHCB_SHARED_BUF_SIZE];
|
||||
|
||||
u8 reserved_1[10];
|
||||
u16 protocol_version; /* negotiated SEV-ES/GHCB protocol version */
|
||||
|
|
@ -365,13 +486,17 @@ struct ghcb {
|
|||
} __packed;
|
||||
|
||||
|
||||
#define EXPECTED_VMCB_SAVE_AREA_SIZE 1032
|
||||
#define EXPECTED_VMCB_SAVE_AREA_SIZE 740
|
||||
#define EXPECTED_GHCB_SAVE_AREA_SIZE 1032
|
||||
#define EXPECTED_SEV_ES_SAVE_AREA_SIZE 1648
|
||||
#define EXPECTED_VMCB_CONTROL_AREA_SIZE 1024
|
||||
#define EXPECTED_GHCB_SIZE PAGE_SIZE
|
||||
|
||||
static inline void __unused_size_checks(void)
|
||||
{
|
||||
BUILD_BUG_ON(sizeof(struct vmcb_save_area) != EXPECTED_VMCB_SAVE_AREA_SIZE);
|
||||
BUILD_BUG_ON(sizeof(struct ghcb_save_area) != EXPECTED_GHCB_SAVE_AREA_SIZE);
|
||||
BUILD_BUG_ON(sizeof(struct sev_es_save_area) != EXPECTED_SEV_ES_SAVE_AREA_SIZE);
|
||||
BUILD_BUG_ON(sizeof(struct vmcb_control_area) != EXPECTED_VMCB_CONTROL_AREA_SIZE);
|
||||
BUILD_BUG_ON(sizeof(struct ghcb) != EXPECTED_GHCB_SIZE);
|
||||
}
|
||||
|
|
@ -441,7 +566,7 @@ struct vmcb {
|
|||
/* GHCB Accessor functions */
|
||||
|
||||
#define GHCB_BITMAP_IDX(field) \
|
||||
(offsetof(struct vmcb_save_area, field) / sizeof(u64))
|
||||
(offsetof(struct ghcb_save_area, field) / sizeof(u64))
|
||||
|
||||
#define DEFINE_GHCB_ACCESSORS(field) \
|
||||
static inline bool ghcb_##field##_is_valid(const struct ghcb *ghcb) \
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#define SETUP_EFI 4
|
||||
#define SETUP_APPLE_PROPERTIES 5
|
||||
#define SETUP_JAILHOUSE 6
|
||||
#define SETUP_CC_BLOB 7
|
||||
|
||||
#define SETUP_INDIRECT (1<<31)
|
||||
|
||||
|
|
@ -187,7 +188,8 @@ struct boot_params {
|
|||
__u32 ext_ramdisk_image; /* 0x0c0 */
|
||||
__u32 ext_ramdisk_size; /* 0x0c4 */
|
||||
__u32 ext_cmd_line_ptr; /* 0x0c8 */
|
||||
__u8 _pad4[116]; /* 0x0cc */
|
||||
__u8 _pad4[112]; /* 0x0cc */
|
||||
__u32 cc_blob_address; /* 0x13c */
|
||||
struct edid_info edid_info; /* 0x140 */
|
||||
struct efi_info efi_info; /* 0x1c0 */
|
||||
__u32 alt_mem_k; /* 0x1e0 */
|
||||
|
|
|
|||
|
|
@ -108,6 +108,14 @@
|
|||
#define SVM_VMGEXIT_AP_JUMP_TABLE 0x80000005
|
||||
#define SVM_VMGEXIT_SET_AP_JUMP_TABLE 0
|
||||
#define SVM_VMGEXIT_GET_AP_JUMP_TABLE 1
|
||||
#define SVM_VMGEXIT_PSC 0x80000010
|
||||
#define SVM_VMGEXIT_GUEST_REQUEST 0x80000011
|
||||
#define SVM_VMGEXIT_EXT_GUEST_REQUEST 0x80000012
|
||||
#define SVM_VMGEXIT_AP_CREATION 0x80000013
|
||||
#define SVM_VMGEXIT_AP_CREATE_ON_INIT 0
|
||||
#define SVM_VMGEXIT_AP_CREATE 1
|
||||
#define SVM_VMGEXIT_AP_DESTROY 2
|
||||
#define SVM_VMGEXIT_HV_FEATURES 0x8000fffd
|
||||
#define SVM_VMGEXIT_UNSUPPORTED_EVENT 0x8000ffff
|
||||
|
||||
/* Exit code reserved for hypervisor/software use */
|
||||
|
|
@ -218,6 +226,11 @@
|
|||
{ SVM_VMGEXIT_NMI_COMPLETE, "vmgexit_nmi_complete" }, \
|
||||
{ SVM_VMGEXIT_AP_HLT_LOOP, "vmgexit_ap_hlt_loop" }, \
|
||||
{ SVM_VMGEXIT_AP_JUMP_TABLE, "vmgexit_ap_jump_table" }, \
|
||||
{ SVM_VMGEXIT_PSC, "vmgexit_page_state_change" }, \
|
||||
{ SVM_VMGEXIT_GUEST_REQUEST, "vmgexit_guest_request" }, \
|
||||
{ SVM_VMGEXIT_EXT_GUEST_REQUEST, "vmgexit_ext_guest_request" }, \
|
||||
{ SVM_VMGEXIT_AP_CREATION, "vmgexit_ap_creation" }, \
|
||||
{ SVM_VMGEXIT_HV_FEATURES, "vmgexit_hypervisor_feature" }, \
|
||||
{ SVM_EXIT_ERR, "invalid_guest_state" }
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ endif
|
|||
# non-deterministic coverage.
|
||||
KCOV_INSTRUMENT := n
|
||||
|
||||
CFLAGS_head$(BITS).o += -fno-stack-protector
|
||||
|
||||
CFLAGS_irq.o := -I $(srctree)/$(src)/../include/asm/trace
|
||||
|
||||
obj-y := process_$(BITS).o signal.o
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
#include <asm/uv/uv.h>
|
||||
#include <asm/sigframe.h>
|
||||
#include <asm/traps.h>
|
||||
#include <asm/sev.h>
|
||||
|
||||
#include "cpu.h"
|
||||
|
||||
|
|
@ -2126,6 +2127,9 @@ void cpu_init_exception_handling(void)
|
|||
|
||||
load_TR_desc();
|
||||
|
||||
/* GHCB needs to be setup to handle #VC. */
|
||||
setup_ghcb();
|
||||
|
||||
/* Finally load the IDT */
|
||||
load_current_idt();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1294,10 +1294,23 @@ static void threshold_remove_bank(struct threshold_bank *bank)
|
|||
kfree(bank);
|
||||
}
|
||||
|
||||
static void __threshold_remove_device(struct threshold_bank **bp)
|
||||
{
|
||||
unsigned int bank, numbanks = this_cpu_read(mce_num_banks);
|
||||
|
||||
for (bank = 0; bank < numbanks; bank++) {
|
||||
if (!bp[bank])
|
||||
continue;
|
||||
|
||||
threshold_remove_bank(bp[bank]);
|
||||
bp[bank] = NULL;
|
||||
}
|
||||
kfree(bp);
|
||||
}
|
||||
|
||||
int mce_threshold_remove_device(unsigned int cpu)
|
||||
{
|
||||
struct threshold_bank **bp = this_cpu_read(threshold_banks);
|
||||
unsigned int bank, numbanks = this_cpu_read(mce_num_banks);
|
||||
|
||||
if (!bp)
|
||||
return 0;
|
||||
|
|
@ -1308,13 +1321,7 @@ int mce_threshold_remove_device(unsigned int cpu)
|
|||
*/
|
||||
this_cpu_write(threshold_banks, NULL);
|
||||
|
||||
for (bank = 0; bank < numbanks; bank++) {
|
||||
if (bp[bank]) {
|
||||
threshold_remove_bank(bp[bank]);
|
||||
bp[bank] = NULL;
|
||||
}
|
||||
}
|
||||
kfree(bp);
|
||||
__threshold_remove_device(bp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1351,15 +1358,14 @@ int mce_threshold_create_device(unsigned int cpu)
|
|||
if (!(this_cpu_read(bank_map) & (1 << bank)))
|
||||
continue;
|
||||
err = threshold_create_bank(bp, cpu, bank);
|
||||
if (err)
|
||||
goto out_err;
|
||||
if (err) {
|
||||
__threshold_remove_device(bp);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
this_cpu_write(threshold_banks, bp);
|
||||
|
||||
if (thresholding_irq_en)
|
||||
mce_threshold_vector = amd_threshold_interrupt;
|
||||
return 0;
|
||||
out_err:
|
||||
mce_threshold_remove_device(cpu);
|
||||
return err;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,9 @@ DEFINE_PER_CPU_READ_MOSTLY(unsigned int, mce_num_banks);
|
|||
|
||||
struct mce_bank {
|
||||
u64 ctl; /* subevents to enable */
|
||||
bool init; /* initialise bank? */
|
||||
|
||||
__u64 init : 1, /* initialise bank? */
|
||||
__reserved_1 : 63;
|
||||
};
|
||||
static DEFINE_PER_CPU_READ_MOSTLY(struct mce_bank[MAX_NR_BANKS], mce_banks_array);
|
||||
|
||||
|
|
|
|||
|
|
@ -301,85 +301,65 @@ static noinstr int error_context(struct mce *m, struct pt_regs *regs)
|
|||
}
|
||||
}
|
||||
|
||||
static __always_inline int mce_severity_amd_smca(struct mce *m, enum context err_ctx)
|
||||
{
|
||||
u64 mcx_cfg;
|
||||
|
||||
/*
|
||||
* We need to look at the following bits:
|
||||
* - "succor" bit (data poisoning support), and
|
||||
* - TCC bit (Task Context Corrupt)
|
||||
* in MCi_STATUS to determine error severity.
|
||||
*/
|
||||
if (!mce_flags.succor)
|
||||
return MCE_PANIC_SEVERITY;
|
||||
|
||||
mcx_cfg = mce_rdmsrl(MSR_AMD64_SMCA_MCx_CONFIG(m->bank));
|
||||
|
||||
/* TCC (Task context corrupt). If set and if IN_KERNEL, panic. */
|
||||
if ((mcx_cfg & MCI_CONFIG_MCAX) &&
|
||||
(m->status & MCI_STATUS_TCC) &&
|
||||
(err_ctx == IN_KERNEL))
|
||||
return MCE_PANIC_SEVERITY;
|
||||
|
||||
/* ...otherwise invoke hwpoison handler. */
|
||||
return MCE_AR_SEVERITY;
|
||||
}
|
||||
|
||||
/*
|
||||
* See AMD Error Scope Hierarchy table in a newer BKDG. For example
|
||||
* 49125_15h_Models_30h-3Fh_BKDG.pdf, section "RAS Features"
|
||||
*/
|
||||
/* See AMD PPR(s) section Machine Check Error Handling. */
|
||||
static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp)
|
||||
{
|
||||
enum context ctx = error_context(m, regs);
|
||||
char *panic_msg = NULL;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Default return value: Action required, the error must be handled
|
||||
* immediately.
|
||||
*/
|
||||
ret = MCE_AR_SEVERITY;
|
||||
|
||||
/* Processor Context Corrupt, no need to fumble too much, die! */
|
||||
if (m->status & MCI_STATUS_PCC)
|
||||
return MCE_PANIC_SEVERITY;
|
||||
if (m->status & MCI_STATUS_PCC) {
|
||||
panic_msg = "Processor Context Corrupt";
|
||||
ret = MCE_PANIC_SEVERITY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (m->status & MCI_STATUS_UC) {
|
||||
|
||||
if (ctx == IN_KERNEL)
|
||||
return MCE_PANIC_SEVERITY;
|
||||
|
||||
/*
|
||||
* On older systems where overflow_recov flag is not present, we
|
||||
* should simply panic if an error overflow occurs. If
|
||||
* overflow_recov flag is present and set, then software can try
|
||||
* to at least kill process to prolong system operation.
|
||||
*/
|
||||
if (mce_flags.overflow_recov) {
|
||||
if (mce_flags.smca)
|
||||
return mce_severity_amd_smca(m, ctx);
|
||||
|
||||
/* kill current process */
|
||||
return MCE_AR_SEVERITY;
|
||||
} else {
|
||||
/* at least one error was not logged */
|
||||
if (m->status & MCI_STATUS_OVER)
|
||||
return MCE_PANIC_SEVERITY;
|
||||
}
|
||||
|
||||
/*
|
||||
* For any other case, return MCE_UC_SEVERITY so that we log the
|
||||
* error and exit #MC handler.
|
||||
*/
|
||||
return MCE_UC_SEVERITY;
|
||||
if (m->status & MCI_STATUS_DEFERRED) {
|
||||
ret = MCE_DEFERRED_SEVERITY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* deferred error: poll handler catches these and adds to mce_ring so
|
||||
* memory-failure can take recovery actions.
|
||||
* If the UC bit is not set, the system either corrected or deferred
|
||||
* the error. No action will be required after logging the error.
|
||||
*/
|
||||
if (m->status & MCI_STATUS_DEFERRED)
|
||||
return MCE_DEFERRED_SEVERITY;
|
||||
if (!(m->status & MCI_STATUS_UC)) {
|
||||
ret = MCE_KEEP_SEVERITY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* corrected error: poll handler catches these and passes responsibility
|
||||
* of decoding the error to EDAC
|
||||
* On MCA overflow, without the MCA overflow recovery feature the
|
||||
* system will not be able to recover, panic.
|
||||
*/
|
||||
return MCE_KEEP_SEVERITY;
|
||||
if ((m->status & MCI_STATUS_OVER) && !mce_flags.overflow_recov) {
|
||||
panic_msg = "Overflowed uncorrected error without MCA Overflow Recovery";
|
||||
ret = MCE_PANIC_SEVERITY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!mce_flags.succor) {
|
||||
panic_msg = "Uncorrected error without MCA Recovery";
|
||||
ret = MCE_PANIC_SEVERITY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (error_context(m, regs) == IN_KERNEL) {
|
||||
panic_msg = "Uncorrected unrecoverable error in kernel context";
|
||||
ret = MCE_PANIC_SEVERITY;
|
||||
}
|
||||
|
||||
out:
|
||||
if (msg && panic_msg)
|
||||
*msg = panic_msg;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp)
|
||||
|
|
|
|||
|
|
@ -143,7 +143,20 @@ static unsigned long __head sme_postprocess_startup(struct boot_params *bp, pmdv
|
|||
if (sme_get_me_mask()) {
|
||||
vaddr = (unsigned long)__start_bss_decrypted;
|
||||
vaddr_end = (unsigned long)__end_bss_decrypted;
|
||||
|
||||
for (; vaddr < vaddr_end; vaddr += PMD_SIZE) {
|
||||
/*
|
||||
* On SNP, transition the page to shared in the RMP table so that
|
||||
* it is consistent with the page table attribute change.
|
||||
*
|
||||
* __start_bss_decrypted has a virtual address in the high range
|
||||
* mapping (kernel .text). PVALIDATE, by way of
|
||||
* early_snp_set_memory_shared(), requires a valid virtual
|
||||
* address but the kernel is currently running off of the identity
|
||||
* mapping so use __pa() to get a *currently* valid virtual address.
|
||||
*/
|
||||
early_snp_set_memory_shared(__pa(vaddr), __pa(vaddr), PTRS_PER_PMD);
|
||||
|
||||
i = pmd_index(vaddr);
|
||||
pmd[i] -= sme_get_me_mask();
|
||||
}
|
||||
|
|
@ -192,9 +205,6 @@ unsigned long __head __startup_64(unsigned long physaddr,
|
|||
if (load_delta & ~PMD_PAGE_MASK)
|
||||
for (;;);
|
||||
|
||||
/* Activate Secure Memory Encryption (SME) if supported and enabled */
|
||||
sme_enable(bp);
|
||||
|
||||
/* Include the SME encryption mask in the fixup value */
|
||||
load_delta += sme_get_me_mask();
|
||||
|
||||
|
|
@ -308,15 +318,6 @@ unsigned long __head __startup_64(unsigned long physaddr,
|
|||
return sme_postprocess_startup(bp, pmd);
|
||||
}
|
||||
|
||||
unsigned long __startup_secondary_64(void)
|
||||
{
|
||||
/*
|
||||
* Return the SME encryption mask (if SME is active) to be used as a
|
||||
* modifier for the initial pgdir entry programmed into CR3.
|
||||
*/
|
||||
return sme_get_me_mask();
|
||||
}
|
||||
|
||||
/* Wipe all early page tables except for the kernel symbol map */
|
||||
static void __init reset_early_page_tables(void)
|
||||
{
|
||||
|
|
@ -600,8 +601,10 @@ static void startup_64_load_idt(unsigned long physbase)
|
|||
void early_setup_idt(void)
|
||||
{
|
||||
/* VMM Communication Exception */
|
||||
if (IS_ENABLED(CONFIG_AMD_MEM_ENCRYPT))
|
||||
if (IS_ENABLED(CONFIG_AMD_MEM_ENCRYPT)) {
|
||||
setup_ghcb();
|
||||
set_bringup_idt_handler(bringup_idt_table, X86_TRAP_VC, vc_boot_ghcb);
|
||||
}
|
||||
|
||||
bringup_idt_descr.address = (unsigned long)bringup_idt_table;
|
||||
native_load_idt(&bringup_idt_descr);
|
||||
|
|
|
|||
|
|
@ -65,10 +65,39 @@ SYM_CODE_START_NOALIGN(startup_64)
|
|||
leaq (__end_init_task - FRAME_SIZE)(%rip), %rsp
|
||||
|
||||
leaq _text(%rip), %rdi
|
||||
|
||||
/*
|
||||
* initial_gs points to initial fixed_percpu_data struct with storage for
|
||||
* the stack protector canary. Global pointer fixups are needed at this
|
||||
* stage, so apply them as is done in fixup_pointer(), and initialize %gs
|
||||
* such that the canary can be accessed at %gs:40 for subsequent C calls.
|
||||
*/
|
||||
movl $MSR_GS_BASE, %ecx
|
||||
movq initial_gs(%rip), %rax
|
||||
movq $_text, %rdx
|
||||
subq %rdx, %rax
|
||||
addq %rdi, %rax
|
||||
movq %rax, %rdx
|
||||
shrq $32, %rdx
|
||||
wrmsr
|
||||
|
||||
pushq %rsi
|
||||
call startup_64_setup_env
|
||||
popq %rsi
|
||||
|
||||
#ifdef CONFIG_AMD_MEM_ENCRYPT
|
||||
/*
|
||||
* Activate SEV/SME memory encryption if supported/enabled. This needs to
|
||||
* be done now, since this also includes setup of the SEV-SNP CPUID table,
|
||||
* which needs to be done before any CPUID instructions are executed in
|
||||
* subsequent code.
|
||||
*/
|
||||
movq %rsi, %rdi
|
||||
pushq %rsi
|
||||
call sme_enable
|
||||
popq %rsi
|
||||
#endif
|
||||
|
||||
/* Now switch to __KERNEL_CS so IRET works reliably */
|
||||
pushq $__KERNEL_CS
|
||||
leaq .Lon_kernel_cs(%rip), %rax
|
||||
|
|
@ -134,9 +163,11 @@ SYM_INNER_LABEL(secondary_startup_64_no_verify, SYM_L_GLOBAL)
|
|||
* Retrieve the modifier (SME encryption mask if SME is active) to be
|
||||
* added to the initial pgdir entry that will be programmed into CR3.
|
||||
*/
|
||||
pushq %rsi
|
||||
call __startup_secondary_64
|
||||
popq %rsi
|
||||
#ifdef CONFIG_AMD_MEM_ENCRYPT
|
||||
movq sme_me_mask, %rax
|
||||
#else
|
||||
xorq %rax, %rax
|
||||
#endif
|
||||
|
||||
/* Form the CR3 value being sure to include the CR3 modifier */
|
||||
addq $(init_top_pgt - __START_KERNEL_map), %rax
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <asm/sections.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/setup_arch.h>
|
||||
#include <asm/sev.h>
|
||||
|
||||
static struct resource system_rom_resource = {
|
||||
.name = "System ROM",
|
||||
|
|
@ -197,11 +198,21 @@ static int __init romchecksum(const unsigned char *rom, unsigned long length)
|
|||
|
||||
void __init probe_roms(void)
|
||||
{
|
||||
const unsigned char *rom;
|
||||
unsigned long start, length, upper;
|
||||
const unsigned char *rom;
|
||||
unsigned char c;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* The ROM memory range is not part of the e820 table and is therefore not
|
||||
* pre-validated by BIOS. The kernel page table maps the ROM region as encrypted
|
||||
* memory, and SNP requires encrypted memory to be validated before access.
|
||||
* Do that here.
|
||||
*/
|
||||
snp_prep_memory(video_rom_resource.start,
|
||||
((system_rom_resource.end + 1) - video_rom_resource.start),
|
||||
SNP_PAGE_STATE_PRIVATE);
|
||||
|
||||
/* video rom */
|
||||
upper = adapter_rom_resources[0].start;
|
||||
for (start = video_rom_resource.start; start < upper; start += 2048) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,68 @@
|
|||
#define has_cpuflag(f) boot_cpu_has(f)
|
||||
#endif
|
||||
|
||||
/* I/O parameters for CPUID-related helpers */
|
||||
struct cpuid_leaf {
|
||||
u32 fn;
|
||||
u32 subfn;
|
||||
u32 eax;
|
||||
u32 ebx;
|
||||
u32 ecx;
|
||||
u32 edx;
|
||||
};
|
||||
|
||||
/*
|
||||
* Individual entries of the SNP CPUID table, as defined by the SNP
|
||||
* Firmware ABI, Revision 0.9, Section 7.1, Table 14.
|
||||
*/
|
||||
struct snp_cpuid_fn {
|
||||
u32 eax_in;
|
||||
u32 ecx_in;
|
||||
u64 xcr0_in;
|
||||
u64 xss_in;
|
||||
u32 eax;
|
||||
u32 ebx;
|
||||
u32 ecx;
|
||||
u32 edx;
|
||||
u64 __reserved;
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* SNP CPUID table, as defined by the SNP Firmware ABI, Revision 0.9,
|
||||
* Section 8.14.2.6. Also noted there is the SNP firmware-enforced limit
|
||||
* of 64 entries per CPUID table.
|
||||
*/
|
||||
#define SNP_CPUID_COUNT_MAX 64
|
||||
|
||||
struct snp_cpuid_table {
|
||||
u32 count;
|
||||
u32 __reserved1;
|
||||
u64 __reserved2;
|
||||
struct snp_cpuid_fn fn[SNP_CPUID_COUNT_MAX];
|
||||
} __packed;
|
||||
|
||||
/*
|
||||
* Since feature negotiation related variables are set early in the boot
|
||||
* process they must reside in the .data section so as not to be zeroed
|
||||
* out when the .bss section is later cleared.
|
||||
*
|
||||
* GHCB protocol version negotiated with the hypervisor.
|
||||
*/
|
||||
static u16 ghcb_version __ro_after_init;
|
||||
|
||||
/* Copy of the SNP firmware's CPUID page. */
|
||||
static struct snp_cpuid_table cpuid_table_copy __ro_after_init;
|
||||
|
||||
/*
|
||||
* These will be initialized based on CPUID table so that non-present
|
||||
* all-zero leaves (for sparse tables) can be differentiated from
|
||||
* invalid/out-of-range leaves. This is needed since all-zero leaves
|
||||
* still need to be post-processed.
|
||||
*/
|
||||
static u32 cpuid_std_range_max __ro_after_init;
|
||||
static u32 cpuid_hyp_range_max __ro_after_init;
|
||||
static u32 cpuid_ext_range_max __ro_after_init;
|
||||
|
||||
static bool __init sev_es_check_cpu_features(void)
|
||||
{
|
||||
if (!has_cpuflag(X86_FEATURE_RDRAND)) {
|
||||
|
|
@ -24,15 +86,12 @@ static bool __init sev_es_check_cpu_features(void)
|
|||
return true;
|
||||
}
|
||||
|
||||
static void __noreturn sev_es_terminate(unsigned int reason)
|
||||
static void __noreturn sev_es_terminate(unsigned int set, unsigned int reason)
|
||||
{
|
||||
u64 val = GHCB_MSR_TERM_REQ;
|
||||
|
||||
/*
|
||||
* Tell the hypervisor what went wrong - only reason-set 0 is
|
||||
* currently supported.
|
||||
*/
|
||||
val |= GHCB_SEV_TERM_REASON(0, reason);
|
||||
/* Tell the hypervisor what went wrong. */
|
||||
val |= GHCB_SEV_TERM_REASON(set, reason);
|
||||
|
||||
/* Request Guest Termination from Hypvervisor */
|
||||
sev_es_wr_ghcb_msr(val);
|
||||
|
|
@ -42,6 +101,42 @@ static void __noreturn sev_es_terminate(unsigned int reason)
|
|||
asm volatile("hlt\n" : : : "memory");
|
||||
}
|
||||
|
||||
/*
|
||||
* The hypervisor features are available from GHCB version 2 onward.
|
||||
*/
|
||||
static u64 get_hv_features(void)
|
||||
{
|
||||
u64 val;
|
||||
|
||||
if (ghcb_version < 2)
|
||||
return 0;
|
||||
|
||||
sev_es_wr_ghcb_msr(GHCB_MSR_HV_FT_REQ);
|
||||
VMGEXIT();
|
||||
|
||||
val = sev_es_rd_ghcb_msr();
|
||||
if (GHCB_RESP_CODE(val) != GHCB_MSR_HV_FT_RESP)
|
||||
return 0;
|
||||
|
||||
return GHCB_MSR_HV_FT_RESP_VAL(val);
|
||||
}
|
||||
|
||||
static void snp_register_ghcb_early(unsigned long paddr)
|
||||
{
|
||||
unsigned long pfn = paddr >> PAGE_SHIFT;
|
||||
u64 val;
|
||||
|
||||
sev_es_wr_ghcb_msr(GHCB_MSR_REG_GPA_REQ_VAL(pfn));
|
||||
VMGEXIT();
|
||||
|
||||
val = sev_es_rd_ghcb_msr();
|
||||
|
||||
/* If the response GPA is not ours then abort the guest */
|
||||
if ((GHCB_RESP_CODE(val) != GHCB_MSR_REG_GPA_RESP) ||
|
||||
(GHCB_MSR_REG_GPA_RESP_VAL(val) != pfn))
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_REGISTER);
|
||||
}
|
||||
|
||||
static bool sev_es_negotiate_protocol(void)
|
||||
{
|
||||
u64 val;
|
||||
|
|
@ -54,10 +149,12 @@ static bool sev_es_negotiate_protocol(void)
|
|||
if (GHCB_MSR_INFO(val) != GHCB_MSR_SEV_INFO_RESP)
|
||||
return false;
|
||||
|
||||
if (GHCB_MSR_PROTO_MAX(val) < GHCB_PROTO_OUR ||
|
||||
GHCB_MSR_PROTO_MIN(val) > GHCB_PROTO_OUR)
|
||||
if (GHCB_MSR_PROTO_MAX(val) < GHCB_PROTOCOL_MIN ||
|
||||
GHCB_MSR_PROTO_MIN(val) > GHCB_PROTOCOL_MAX)
|
||||
return false;
|
||||
|
||||
ghcb_version = min_t(size_t, GHCB_MSR_PROTO_MAX(val), GHCB_PROTOCOL_MAX);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -104,10 +201,7 @@ static enum es_result verify_exception_info(struct ghcb *ghcb, struct es_em_ctxt
|
|||
|
||||
if (ret == 1) {
|
||||
u64 info = ghcb->save.sw_exit_info_2;
|
||||
unsigned long v;
|
||||
|
||||
info = ghcb->save.sw_exit_info_2;
|
||||
v = info & SVM_EVTINJ_VEC_MASK;
|
||||
unsigned long v = info & SVM_EVTINJ_VEC_MASK;
|
||||
|
||||
/* Check if exception information from hypervisor is sane. */
|
||||
if ((info & SVM_EVTINJ_VALID) &&
|
||||
|
|
@ -130,7 +224,7 @@ enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb, bool set_ghcb_msr,
|
|||
u64 exit_info_1, u64 exit_info_2)
|
||||
{
|
||||
/* Fill in protocol and format specifiers */
|
||||
ghcb->protocol_version = GHCB_PROTOCOL_MAX;
|
||||
ghcb->protocol_version = ghcb_version;
|
||||
ghcb->ghcb_usage = GHCB_DEFAULT_USAGE;
|
||||
|
||||
ghcb_set_sw_exit_code(ghcb, exit_code);
|
||||
|
|
@ -150,6 +244,290 @@ enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb, bool set_ghcb_msr,
|
|||
return verify_exception_info(ghcb, ctxt);
|
||||
}
|
||||
|
||||
static int __sev_cpuid_hv(u32 fn, int reg_idx, u32 *reg)
|
||||
{
|
||||
u64 val;
|
||||
|
||||
sev_es_wr_ghcb_msr(GHCB_CPUID_REQ(fn, reg_idx));
|
||||
VMGEXIT();
|
||||
val = sev_es_rd_ghcb_msr();
|
||||
if (GHCB_RESP_CODE(val) != GHCB_MSR_CPUID_RESP)
|
||||
return -EIO;
|
||||
|
||||
*reg = (val >> 32);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sev_cpuid_hv(struct cpuid_leaf *leaf)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* MSR protocol does not support fetching non-zero subfunctions, but is
|
||||
* sufficient to handle current early-boot cases. Should that change,
|
||||
* make sure to report an error rather than ignoring the index and
|
||||
* grabbing random values. If this issue arises in the future, handling
|
||||
* can be added here to use GHCB-page protocol for cases that occur late
|
||||
* enough in boot that GHCB page is available.
|
||||
*/
|
||||
if (cpuid_function_is_indexed(leaf->fn) && leaf->subfn)
|
||||
return -EINVAL;
|
||||
|
||||
ret = __sev_cpuid_hv(leaf->fn, GHCB_CPUID_REQ_EAX, &leaf->eax);
|
||||
ret = ret ? : __sev_cpuid_hv(leaf->fn, GHCB_CPUID_REQ_EBX, &leaf->ebx);
|
||||
ret = ret ? : __sev_cpuid_hv(leaf->fn, GHCB_CPUID_REQ_ECX, &leaf->ecx);
|
||||
ret = ret ? : __sev_cpuid_hv(leaf->fn, GHCB_CPUID_REQ_EDX, &leaf->edx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This may be called early while still running on the initial identity
|
||||
* mapping. Use RIP-relative addressing to obtain the correct address
|
||||
* while running with the initial identity mapping as well as the
|
||||
* switch-over to kernel virtual addresses later.
|
||||
*/
|
||||
static const struct snp_cpuid_table *snp_cpuid_get_table(void)
|
||||
{
|
||||
void *ptr;
|
||||
|
||||
asm ("lea cpuid_table_copy(%%rip), %0"
|
||||
: "=r" (ptr)
|
||||
: "p" (&cpuid_table_copy));
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
/*
|
||||
* The SNP Firmware ABI, Revision 0.9, Section 7.1, details the use of
|
||||
* XCR0_IN and XSS_IN to encode multiple versions of 0xD subfunctions 0
|
||||
* and 1 based on the corresponding features enabled by a particular
|
||||
* combination of XCR0 and XSS registers so that a guest can look up the
|
||||
* version corresponding to the features currently enabled in its XCR0/XSS
|
||||
* registers. The only values that differ between these versions/table
|
||||
* entries is the enabled XSAVE area size advertised via EBX.
|
||||
*
|
||||
* While hypervisors may choose to make use of this support, it is more
|
||||
* robust/secure for a guest to simply find the entry corresponding to the
|
||||
* base/legacy XSAVE area size (XCR0=1 or XCR0=3), and then calculate the
|
||||
* XSAVE area size using subfunctions 2 through 64, as documented in APM
|
||||
* Volume 3, Rev 3.31, Appendix E.3.8, which is what is done here.
|
||||
*
|
||||
* Since base/legacy XSAVE area size is documented as 0x240, use that value
|
||||
* directly rather than relying on the base size in the CPUID table.
|
||||
*
|
||||
* Return: XSAVE area size on success, 0 otherwise.
|
||||
*/
|
||||
static u32 snp_cpuid_calc_xsave_size(u64 xfeatures_en, bool compacted)
|
||||
{
|
||||
const struct snp_cpuid_table *cpuid_table = snp_cpuid_get_table();
|
||||
u64 xfeatures_found = 0;
|
||||
u32 xsave_size = 0x240;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < cpuid_table->count; i++) {
|
||||
const struct snp_cpuid_fn *e = &cpuid_table->fn[i];
|
||||
|
||||
if (!(e->eax_in == 0xD && e->ecx_in > 1 && e->ecx_in < 64))
|
||||
continue;
|
||||
if (!(xfeatures_en & (BIT_ULL(e->ecx_in))))
|
||||
continue;
|
||||
if (xfeatures_found & (BIT_ULL(e->ecx_in)))
|
||||
continue;
|
||||
|
||||
xfeatures_found |= (BIT_ULL(e->ecx_in));
|
||||
|
||||
if (compacted)
|
||||
xsave_size += e->eax;
|
||||
else
|
||||
xsave_size = max(xsave_size, e->eax + e->ebx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Either the guest set unsupported XCR0/XSS bits, or the corresponding
|
||||
* entries in the CPUID table were not present. This is not a valid
|
||||
* state to be in.
|
||||
*/
|
||||
if (xfeatures_found != (xfeatures_en & GENMASK_ULL(63, 2)))
|
||||
return 0;
|
||||
|
||||
return xsave_size;
|
||||
}
|
||||
|
||||
static bool
|
||||
snp_cpuid_get_validated_func(struct cpuid_leaf *leaf)
|
||||
{
|
||||
const struct snp_cpuid_table *cpuid_table = snp_cpuid_get_table();
|
||||
int i;
|
||||
|
||||
for (i = 0; i < cpuid_table->count; i++) {
|
||||
const struct snp_cpuid_fn *e = &cpuid_table->fn[i];
|
||||
|
||||
if (e->eax_in != leaf->fn)
|
||||
continue;
|
||||
|
||||
if (cpuid_function_is_indexed(leaf->fn) && e->ecx_in != leaf->subfn)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* For 0xD subfunctions 0 and 1, only use the entry corresponding
|
||||
* to the base/legacy XSAVE area size (XCR0=1 or XCR0=3, XSS=0).
|
||||
* See the comments above snp_cpuid_calc_xsave_size() for more
|
||||
* details.
|
||||
*/
|
||||
if (e->eax_in == 0xD && (e->ecx_in == 0 || e->ecx_in == 1))
|
||||
if (!(e->xcr0_in == 1 || e->xcr0_in == 3) || e->xss_in)
|
||||
continue;
|
||||
|
||||
leaf->eax = e->eax;
|
||||
leaf->ebx = e->ebx;
|
||||
leaf->ecx = e->ecx;
|
||||
leaf->edx = e->edx;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void snp_cpuid_hv(struct cpuid_leaf *leaf)
|
||||
{
|
||||
if (sev_cpuid_hv(leaf))
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_CPUID_HV);
|
||||
}
|
||||
|
||||
static int snp_cpuid_postprocess(struct cpuid_leaf *leaf)
|
||||
{
|
||||
struct cpuid_leaf leaf_hv = *leaf;
|
||||
|
||||
switch (leaf->fn) {
|
||||
case 0x1:
|
||||
snp_cpuid_hv(&leaf_hv);
|
||||
|
||||
/* initial APIC ID */
|
||||
leaf->ebx = (leaf_hv.ebx & GENMASK(31, 24)) | (leaf->ebx & GENMASK(23, 0));
|
||||
/* APIC enabled bit */
|
||||
leaf->edx = (leaf_hv.edx & BIT(9)) | (leaf->edx & ~BIT(9));
|
||||
|
||||
/* OSXSAVE enabled bit */
|
||||
if (native_read_cr4() & X86_CR4_OSXSAVE)
|
||||
leaf->ecx |= BIT(27);
|
||||
break;
|
||||
case 0x7:
|
||||
/* OSPKE enabled bit */
|
||||
leaf->ecx &= ~BIT(4);
|
||||
if (native_read_cr4() & X86_CR4_PKE)
|
||||
leaf->ecx |= BIT(4);
|
||||
break;
|
||||
case 0xB:
|
||||
leaf_hv.subfn = 0;
|
||||
snp_cpuid_hv(&leaf_hv);
|
||||
|
||||
/* extended APIC ID */
|
||||
leaf->edx = leaf_hv.edx;
|
||||
break;
|
||||
case 0xD: {
|
||||
bool compacted = false;
|
||||
u64 xcr0 = 1, xss = 0;
|
||||
u32 xsave_size;
|
||||
|
||||
if (leaf->subfn != 0 && leaf->subfn != 1)
|
||||
return 0;
|
||||
|
||||
if (native_read_cr4() & X86_CR4_OSXSAVE)
|
||||
xcr0 = xgetbv(XCR_XFEATURE_ENABLED_MASK);
|
||||
if (leaf->subfn == 1) {
|
||||
/* Get XSS value if XSAVES is enabled. */
|
||||
if (leaf->eax & BIT(3)) {
|
||||
unsigned long lo, hi;
|
||||
|
||||
asm volatile("rdmsr" : "=a" (lo), "=d" (hi)
|
||||
: "c" (MSR_IA32_XSS));
|
||||
xss = (hi << 32) | lo;
|
||||
}
|
||||
|
||||
/*
|
||||
* The PPR and APM aren't clear on what size should be
|
||||
* encoded in 0xD:0x1:EBX when compaction is not enabled
|
||||
* by either XSAVEC (feature bit 1) or XSAVES (feature
|
||||
* bit 3) since SNP-capable hardware has these feature
|
||||
* bits fixed as 1. KVM sets it to 0 in this case, but
|
||||
* to avoid this becoming an issue it's safer to simply
|
||||
* treat this as unsupported for SNP guests.
|
||||
*/
|
||||
if (!(leaf->eax & (BIT(1) | BIT(3))))
|
||||
return -EINVAL;
|
||||
|
||||
compacted = true;
|
||||
}
|
||||
|
||||
xsave_size = snp_cpuid_calc_xsave_size(xcr0 | xss, compacted);
|
||||
if (!xsave_size)
|
||||
return -EINVAL;
|
||||
|
||||
leaf->ebx = xsave_size;
|
||||
}
|
||||
break;
|
||||
case 0x8000001E:
|
||||
snp_cpuid_hv(&leaf_hv);
|
||||
|
||||
/* extended APIC ID */
|
||||
leaf->eax = leaf_hv.eax;
|
||||
/* compute ID */
|
||||
leaf->ebx = (leaf->ebx & GENMASK(31, 8)) | (leaf_hv.ebx & GENMASK(7, 0));
|
||||
/* node ID */
|
||||
leaf->ecx = (leaf->ecx & GENMASK(31, 8)) | (leaf_hv.ecx & GENMASK(7, 0));
|
||||
break;
|
||||
default:
|
||||
/* No fix-ups needed, use values as-is. */
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns -EOPNOTSUPP if feature not enabled. Any other non-zero return value
|
||||
* should be treated as fatal by caller.
|
||||
*/
|
||||
static int snp_cpuid(struct cpuid_leaf *leaf)
|
||||
{
|
||||
const struct snp_cpuid_table *cpuid_table = snp_cpuid_get_table();
|
||||
|
||||
if (!cpuid_table->count)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (!snp_cpuid_get_validated_func(leaf)) {
|
||||
/*
|
||||
* Some hypervisors will avoid keeping track of CPUID entries
|
||||
* where all values are zero, since they can be handled the
|
||||
* same as out-of-range values (all-zero). This is useful here
|
||||
* as well as it allows virtually all guest configurations to
|
||||
* work using a single SNP CPUID table.
|
||||
*
|
||||
* To allow for this, there is a need to distinguish between
|
||||
* out-of-range entries and in-range zero entries, since the
|
||||
* CPUID table entries are only a template that may need to be
|
||||
* augmented with additional values for things like
|
||||
* CPU-specific information during post-processing. So if it's
|
||||
* not in the table, set the values to zero. Then, if they are
|
||||
* within a valid CPUID range, proceed with post-processing
|
||||
* using zeros as the initial values. Otherwise, skip
|
||||
* post-processing and just return zeros immediately.
|
||||
*/
|
||||
leaf->eax = leaf->ebx = leaf->ecx = leaf->edx = 0;
|
||||
|
||||
/* Skip post-processing for out-of-range zero leafs. */
|
||||
if (!(leaf->fn <= cpuid_std_range_max ||
|
||||
(leaf->fn >= 0x40000000 && leaf->fn <= cpuid_hyp_range_max) ||
|
||||
(leaf->fn >= 0x80000000 && leaf->fn <= cpuid_ext_range_max)))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return snp_cpuid_postprocess(leaf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Boot VC Handler - This is the first VC handler during boot, there is no GHCB
|
||||
* page yet, so it only supports the MSR based communication with the
|
||||
|
|
@ -157,40 +535,33 @@ enum es_result sev_es_ghcb_hv_call(struct ghcb *ghcb, bool set_ghcb_msr,
|
|||
*/
|
||||
void __init do_vc_no_ghcb(struct pt_regs *regs, unsigned long exit_code)
|
||||
{
|
||||
unsigned int subfn = lower_bits(regs->cx, 32);
|
||||
unsigned int fn = lower_bits(regs->ax, 32);
|
||||
unsigned long val;
|
||||
struct cpuid_leaf leaf;
|
||||
int ret;
|
||||
|
||||
/* Only CPUID is supported via MSR protocol */
|
||||
if (exit_code != SVM_EXIT_CPUID)
|
||||
goto fail;
|
||||
|
||||
sev_es_wr_ghcb_msr(GHCB_CPUID_REQ(fn, GHCB_CPUID_REQ_EAX));
|
||||
VMGEXIT();
|
||||
val = sev_es_rd_ghcb_msr();
|
||||
if (GHCB_RESP_CODE(val) != GHCB_MSR_CPUID_RESP)
|
||||
goto fail;
|
||||
regs->ax = val >> 32;
|
||||
leaf.fn = fn;
|
||||
leaf.subfn = subfn;
|
||||
|
||||
sev_es_wr_ghcb_msr(GHCB_CPUID_REQ(fn, GHCB_CPUID_REQ_EBX));
|
||||
VMGEXIT();
|
||||
val = sev_es_rd_ghcb_msr();
|
||||
if (GHCB_RESP_CODE(val) != GHCB_MSR_CPUID_RESP)
|
||||
goto fail;
|
||||
regs->bx = val >> 32;
|
||||
ret = snp_cpuid(&leaf);
|
||||
if (!ret)
|
||||
goto cpuid_done;
|
||||
|
||||
sev_es_wr_ghcb_msr(GHCB_CPUID_REQ(fn, GHCB_CPUID_REQ_ECX));
|
||||
VMGEXIT();
|
||||
val = sev_es_rd_ghcb_msr();
|
||||
if (GHCB_RESP_CODE(val) != GHCB_MSR_CPUID_RESP)
|
||||
if (ret != -EOPNOTSUPP)
|
||||
goto fail;
|
||||
regs->cx = val >> 32;
|
||||
|
||||
sev_es_wr_ghcb_msr(GHCB_CPUID_REQ(fn, GHCB_CPUID_REQ_EDX));
|
||||
VMGEXIT();
|
||||
val = sev_es_rd_ghcb_msr();
|
||||
if (GHCB_RESP_CODE(val) != GHCB_MSR_CPUID_RESP)
|
||||
if (sev_cpuid_hv(&leaf))
|
||||
goto fail;
|
||||
regs->dx = val >> 32;
|
||||
|
||||
cpuid_done:
|
||||
regs->ax = leaf.eax;
|
||||
regs->bx = leaf.ebx;
|
||||
regs->cx = leaf.ecx;
|
||||
regs->dx = leaf.edx;
|
||||
|
||||
/*
|
||||
* This is a VC handler and the #VC is only raised when SEV-ES is
|
||||
|
|
@ -221,7 +592,7 @@ void __init do_vc_no_ghcb(struct pt_regs *regs, unsigned long exit_code)
|
|||
|
||||
fail:
|
||||
/* Terminate the guest */
|
||||
sev_es_terminate(GHCB_SEV_ES_GEN_REQ);
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SEV_ES_GEN_REQ);
|
||||
}
|
||||
|
||||
static enum es_result vc_insn_string_read(struct es_em_ctxt *ctxt,
|
||||
|
|
@ -481,12 +852,37 @@ static enum es_result vc_handle_ioio(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int vc_handle_cpuid_snp(struct pt_regs *regs)
|
||||
{
|
||||
struct cpuid_leaf leaf;
|
||||
int ret;
|
||||
|
||||
leaf.fn = regs->ax;
|
||||
leaf.subfn = regs->cx;
|
||||
ret = snp_cpuid(&leaf);
|
||||
if (!ret) {
|
||||
regs->ax = leaf.eax;
|
||||
regs->bx = leaf.ebx;
|
||||
regs->cx = leaf.ecx;
|
||||
regs->dx = leaf.edx;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum es_result vc_handle_cpuid(struct ghcb *ghcb,
|
||||
struct es_em_ctxt *ctxt)
|
||||
{
|
||||
struct pt_regs *regs = ctxt->regs;
|
||||
u32 cr4 = native_read_cr4();
|
||||
enum es_result ret;
|
||||
int snp_cpuid_ret;
|
||||
|
||||
snp_cpuid_ret = vc_handle_cpuid_snp(regs);
|
||||
if (!snp_cpuid_ret)
|
||||
return ES_OK;
|
||||
if (snp_cpuid_ret != -EOPNOTSUPP)
|
||||
return ES_VMM_ERROR;
|
||||
|
||||
ghcb_set_rax(ghcb, regs->ax);
|
||||
ghcb_set_rcx(ghcb, regs->cx);
|
||||
|
|
@ -538,3 +934,67 @@ static enum es_result vc_handle_rdtsc(struct ghcb *ghcb,
|
|||
|
||||
return ES_OK;
|
||||
}
|
||||
|
||||
struct cc_setup_data {
|
||||
struct setup_data header;
|
||||
u32 cc_blob_address;
|
||||
};
|
||||
|
||||
/*
|
||||
* Search for a Confidential Computing blob passed in as a setup_data entry
|
||||
* via the Linux Boot Protocol.
|
||||
*/
|
||||
static struct cc_blob_sev_info *find_cc_blob_setup_data(struct boot_params *bp)
|
||||
{
|
||||
struct cc_setup_data *sd = NULL;
|
||||
struct setup_data *hdr;
|
||||
|
||||
hdr = (struct setup_data *)bp->hdr.setup_data;
|
||||
|
||||
while (hdr) {
|
||||
if (hdr->type == SETUP_CC_BLOB) {
|
||||
sd = (struct cc_setup_data *)hdr;
|
||||
return (struct cc_blob_sev_info *)(unsigned long)sd->cc_blob_address;
|
||||
}
|
||||
hdr = (struct setup_data *)hdr->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the kernel's copy of the SNP CPUID table, and set up the
|
||||
* pointer that will be used to access it.
|
||||
*
|
||||
* Maintaining a direct mapping of the SNP CPUID table used by firmware would
|
||||
* be possible as an alternative, but the approach is brittle since the
|
||||
* mapping needs to be updated in sync with all the changes to virtual memory
|
||||
* layout and related mapping facilities throughout the boot process.
|
||||
*/
|
||||
static void __init setup_cpuid_table(const struct cc_blob_sev_info *cc_info)
|
||||
{
|
||||
const struct snp_cpuid_table *cpuid_table_fw, *cpuid_table;
|
||||
int i;
|
||||
|
||||
if (!cc_info || !cc_info->cpuid_phys || cc_info->cpuid_len < PAGE_SIZE)
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_CPUID);
|
||||
|
||||
cpuid_table_fw = (const struct snp_cpuid_table *)cc_info->cpuid_phys;
|
||||
if (!cpuid_table_fw->count || cpuid_table_fw->count > SNP_CPUID_COUNT_MAX)
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_CPUID);
|
||||
|
||||
cpuid_table = snp_cpuid_get_table();
|
||||
memcpy((void *)cpuid_table, cpuid_table_fw, sizeof(*cpuid_table));
|
||||
|
||||
/* Initialize CPUID ranges for range-checking. */
|
||||
for (i = 0; i < cpuid_table->count; i++) {
|
||||
const struct snp_cpuid_fn *fn = &cpuid_table->fn[i];
|
||||
|
||||
if (fn->eax_in == 0x0)
|
||||
cpuid_std_range_max = fn->eax;
|
||||
else if (fn->eax_in == 0x40000000)
|
||||
cpuid_hyp_range_max = fn->eax;
|
||||
else if (fn->eax_in == 0x80000000)
|
||||
cpuid_ext_range_max = fn->eax;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@
|
|||
#include <linux/memblock.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/efi.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include <asm/cpu_entry_area.h>
|
||||
#include <asm/stacktrace.h>
|
||||
|
|
@ -31,9 +35,28 @@
|
|||
#include <asm/svm.h>
|
||||
#include <asm/smp.h>
|
||||
#include <asm/cpu.h>
|
||||
#include <asm/apic.h>
|
||||
#include <asm/cpuid.h>
|
||||
#include <asm/cmdline.h>
|
||||
|
||||
#define DR7_RESET_VALUE 0x400
|
||||
|
||||
/* AP INIT values as documented in the APM2 section "Processor Initialization State" */
|
||||
#define AP_INIT_CS_LIMIT 0xffff
|
||||
#define AP_INIT_DS_LIMIT 0xffff
|
||||
#define AP_INIT_LDTR_LIMIT 0xffff
|
||||
#define AP_INIT_GDTR_LIMIT 0xffff
|
||||
#define AP_INIT_IDTR_LIMIT 0xffff
|
||||
#define AP_INIT_TR_LIMIT 0xffff
|
||||
#define AP_INIT_RFLAGS_DEFAULT 0x2
|
||||
#define AP_INIT_DR6_DEFAULT 0xffff0ff0
|
||||
#define AP_INIT_GPAT_DEFAULT 0x0007040600070406ULL
|
||||
#define AP_INIT_XCR0_DEFAULT 0x1
|
||||
#define AP_INIT_X87_FTW_DEFAULT 0x5555
|
||||
#define AP_INIT_X87_FCW_DEFAULT 0x0040
|
||||
#define AP_INIT_CR0_DEFAULT 0x60000010
|
||||
#define AP_INIT_MXCSR_DEFAULT 0x1f80
|
||||
|
||||
/* For early boot hypervisor communication in SEV-ES enabled guests */
|
||||
static struct ghcb boot_ghcb_page __bss_decrypted __aligned(PAGE_SIZE);
|
||||
|
||||
|
|
@ -41,7 +64,10 @@ static struct ghcb boot_ghcb_page __bss_decrypted __aligned(PAGE_SIZE);
|
|||
* Needs to be in the .data section because we need it NULL before bss is
|
||||
* cleared
|
||||
*/
|
||||
static struct ghcb __initdata *boot_ghcb;
|
||||
static struct ghcb *boot_ghcb __section(".data");
|
||||
|
||||
/* Bitmap of SEV features supported by the hypervisor */
|
||||
static u64 sev_hv_features __ro_after_init;
|
||||
|
||||
/* #VC handler runtime per-CPU data */
|
||||
struct sev_es_runtime_data {
|
||||
|
|
@ -87,6 +113,15 @@ struct ghcb_state {
|
|||
static DEFINE_PER_CPU(struct sev_es_runtime_data*, runtime_data);
|
||||
DEFINE_STATIC_KEY_FALSE(sev_es_enable_key);
|
||||
|
||||
static DEFINE_PER_CPU(struct sev_es_save_area *, sev_vmsa);
|
||||
|
||||
struct sev_config {
|
||||
__u64 debug : 1,
|
||||
__reserved : 63;
|
||||
};
|
||||
|
||||
static struct sev_config sev_cfg __read_mostly;
|
||||
|
||||
static __always_inline bool on_vc_stack(struct pt_regs *regs)
|
||||
{
|
||||
unsigned long sp = regs->sp;
|
||||
|
|
@ -523,13 +558,68 @@ void noinstr __sev_es_nmi_complete(void)
|
|||
__sev_put_ghcb(&state);
|
||||
}
|
||||
|
||||
static u64 get_jump_table_addr(void)
|
||||
static u64 __init get_secrets_page(void)
|
||||
{
|
||||
u64 pa_data = boot_params.cc_blob_address;
|
||||
struct cc_blob_sev_info info;
|
||||
void *map;
|
||||
|
||||
/*
|
||||
* The CC blob contains the address of the secrets page, check if the
|
||||
* blob is present.
|
||||
*/
|
||||
if (!pa_data)
|
||||
return 0;
|
||||
|
||||
map = early_memremap(pa_data, sizeof(info));
|
||||
if (!map) {
|
||||
pr_err("Unable to locate SNP secrets page: failed to map the Confidential Computing blob.\n");
|
||||
return 0;
|
||||
}
|
||||
memcpy(&info, map, sizeof(info));
|
||||
early_memunmap(map, sizeof(info));
|
||||
|
||||
/* smoke-test the secrets page passed */
|
||||
if (!info.secrets_phys || info.secrets_len != PAGE_SIZE)
|
||||
return 0;
|
||||
|
||||
return info.secrets_phys;
|
||||
}
|
||||
|
||||
static u64 __init get_snp_jump_table_addr(void)
|
||||
{
|
||||
struct snp_secrets_page_layout *layout;
|
||||
void __iomem *mem;
|
||||
u64 pa, addr;
|
||||
|
||||
pa = get_secrets_page();
|
||||
if (!pa)
|
||||
return 0;
|
||||
|
||||
mem = ioremap_encrypted(pa, PAGE_SIZE);
|
||||
if (!mem) {
|
||||
pr_err("Unable to locate AP jump table address: failed to map the SNP secrets page.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
layout = (__force struct snp_secrets_page_layout *)mem;
|
||||
|
||||
addr = layout->os_area.ap_jump_table_pa;
|
||||
iounmap(mem);
|
||||
|
||||
return addr;
|
||||
}
|
||||
|
||||
static u64 __init get_jump_table_addr(void)
|
||||
{
|
||||
struct ghcb_state state;
|
||||
unsigned long flags;
|
||||
struct ghcb *ghcb;
|
||||
u64 ret = 0;
|
||||
|
||||
if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
return get_snp_jump_table_addr();
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
ghcb = __sev_get_ghcb(&state);
|
||||
|
|
@ -553,7 +643,496 @@ static u64 get_jump_table_addr(void)
|
|||
return ret;
|
||||
}
|
||||
|
||||
int sev_es_setup_ap_jump_table(struct real_mode_header *rmh)
|
||||
static void pvalidate_pages(unsigned long vaddr, unsigned int npages, bool validate)
|
||||
{
|
||||
unsigned long vaddr_end;
|
||||
int rc;
|
||||
|
||||
vaddr = vaddr & PAGE_MASK;
|
||||
vaddr_end = vaddr + (npages << PAGE_SHIFT);
|
||||
|
||||
while (vaddr < vaddr_end) {
|
||||
rc = pvalidate(vaddr, RMP_PG_SIZE_4K, validate);
|
||||
if (WARN(rc, "Failed to validate address 0x%lx ret %d", vaddr, rc))
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PVALIDATE);
|
||||
|
||||
vaddr = vaddr + PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
static void __init early_set_pages_state(unsigned long paddr, unsigned int npages, enum psc_op op)
|
||||
{
|
||||
unsigned long paddr_end;
|
||||
u64 val;
|
||||
|
||||
paddr = paddr & PAGE_MASK;
|
||||
paddr_end = paddr + (npages << PAGE_SHIFT);
|
||||
|
||||
while (paddr < paddr_end) {
|
||||
/*
|
||||
* Use the MSR protocol because this function can be called before
|
||||
* the GHCB is established.
|
||||
*/
|
||||
sev_es_wr_ghcb_msr(GHCB_MSR_PSC_REQ_GFN(paddr >> PAGE_SHIFT, op));
|
||||
VMGEXIT();
|
||||
|
||||
val = sev_es_rd_ghcb_msr();
|
||||
|
||||
if (WARN(GHCB_RESP_CODE(val) != GHCB_MSR_PSC_RESP,
|
||||
"Wrong PSC response code: 0x%x\n",
|
||||
(unsigned int)GHCB_RESP_CODE(val)))
|
||||
goto e_term;
|
||||
|
||||
if (WARN(GHCB_MSR_PSC_RESP_VAL(val),
|
||||
"Failed to change page state to '%s' paddr 0x%lx error 0x%llx\n",
|
||||
op == SNP_PAGE_STATE_PRIVATE ? "private" : "shared",
|
||||
paddr, GHCB_MSR_PSC_RESP_VAL(val)))
|
||||
goto e_term;
|
||||
|
||||
paddr = paddr + PAGE_SIZE;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
e_term:
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PSC);
|
||||
}
|
||||
|
||||
void __init early_snp_set_memory_private(unsigned long vaddr, unsigned long paddr,
|
||||
unsigned int npages)
|
||||
{
|
||||
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Ask the hypervisor to mark the memory pages as private in the RMP
|
||||
* table.
|
||||
*/
|
||||
early_set_pages_state(paddr, npages, SNP_PAGE_STATE_PRIVATE);
|
||||
|
||||
/* Validate the memory pages after they've been added in the RMP table. */
|
||||
pvalidate_pages(vaddr, npages, true);
|
||||
}
|
||||
|
||||
void __init early_snp_set_memory_shared(unsigned long vaddr, unsigned long paddr,
|
||||
unsigned int npages)
|
||||
{
|
||||
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
return;
|
||||
|
||||
/* Invalidate the memory pages before they are marked shared in the RMP table. */
|
||||
pvalidate_pages(vaddr, npages, false);
|
||||
|
||||
/* Ask hypervisor to mark the memory pages shared in the RMP table. */
|
||||
early_set_pages_state(paddr, npages, SNP_PAGE_STATE_SHARED);
|
||||
}
|
||||
|
||||
void __init snp_prep_memory(unsigned long paddr, unsigned int sz, enum psc_op op)
|
||||
{
|
||||
unsigned long vaddr, npages;
|
||||
|
||||
vaddr = (unsigned long)__va(paddr);
|
||||
npages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
|
||||
|
||||
if (op == SNP_PAGE_STATE_PRIVATE)
|
||||
early_snp_set_memory_private(vaddr, paddr, npages);
|
||||
else if (op == SNP_PAGE_STATE_SHARED)
|
||||
early_snp_set_memory_shared(vaddr, paddr, npages);
|
||||
else
|
||||
WARN(1, "invalid memory op %d\n", op);
|
||||
}
|
||||
|
||||
static int vmgexit_psc(struct snp_psc_desc *desc)
|
||||
{
|
||||
int cur_entry, end_entry, ret = 0;
|
||||
struct snp_psc_desc *data;
|
||||
struct ghcb_state state;
|
||||
struct es_em_ctxt ctxt;
|
||||
unsigned long flags;
|
||||
struct ghcb *ghcb;
|
||||
|
||||
/*
|
||||
* __sev_get_ghcb() needs to run with IRQs disabled because it is using
|
||||
* a per-CPU GHCB.
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
|
||||
ghcb = __sev_get_ghcb(&state);
|
||||
if (!ghcb) {
|
||||
ret = 1;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
/* Copy the input desc into GHCB shared buffer */
|
||||
data = (struct snp_psc_desc *)ghcb->shared_buffer;
|
||||
memcpy(ghcb->shared_buffer, desc, min_t(int, GHCB_SHARED_BUF_SIZE, sizeof(*desc)));
|
||||
|
||||
/*
|
||||
* As per the GHCB specification, the hypervisor can resume the guest
|
||||
* before processing all the entries. Check whether all the entries
|
||||
* are processed. If not, then keep retrying. Note, the hypervisor
|
||||
* will update the data memory directly to indicate the status, so
|
||||
* reference the data->hdr everywhere.
|
||||
*
|
||||
* The strategy here is to wait for the hypervisor to change the page
|
||||
* state in the RMP table before guest accesses the memory pages. If the
|
||||
* page state change was not successful, then later memory access will
|
||||
* result in a crash.
|
||||
*/
|
||||
cur_entry = data->hdr.cur_entry;
|
||||
end_entry = data->hdr.end_entry;
|
||||
|
||||
while (data->hdr.cur_entry <= data->hdr.end_entry) {
|
||||
ghcb_set_sw_scratch(ghcb, (u64)__pa(data));
|
||||
|
||||
/* This will advance the shared buffer data points to. */
|
||||
ret = sev_es_ghcb_hv_call(ghcb, true, &ctxt, SVM_VMGEXIT_PSC, 0, 0);
|
||||
|
||||
/*
|
||||
* Page State Change VMGEXIT can pass error code through
|
||||
* exit_info_2.
|
||||
*/
|
||||
if (WARN(ret || ghcb->save.sw_exit_info_2,
|
||||
"SNP: PSC failed ret=%d exit_info_2=%llx\n",
|
||||
ret, ghcb->save.sw_exit_info_2)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Verify that reserved bit is not set */
|
||||
if (WARN(data->hdr.reserved, "Reserved bit is set in the PSC header\n")) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity check that entry processing is not going backwards.
|
||||
* This will happen only if hypervisor is tricking us.
|
||||
*/
|
||||
if (WARN(data->hdr.end_entry > end_entry || cur_entry > data->hdr.cur_entry,
|
||||
"SNP: PSC processing going backward, end_entry %d (got %d) cur_entry %d (got %d)\n",
|
||||
end_entry, data->hdr.end_entry, cur_entry, data->hdr.cur_entry)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
__sev_put_ghcb(&state);
|
||||
|
||||
out_unlock:
|
||||
local_irq_restore(flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __set_pages_state(struct snp_psc_desc *data, unsigned long vaddr,
|
||||
unsigned long vaddr_end, int op)
|
||||
{
|
||||
struct psc_hdr *hdr;
|
||||
struct psc_entry *e;
|
||||
unsigned long pfn;
|
||||
int i;
|
||||
|
||||
hdr = &data->hdr;
|
||||
e = data->entries;
|
||||
|
||||
memset(data, 0, sizeof(*data));
|
||||
i = 0;
|
||||
|
||||
while (vaddr < vaddr_end) {
|
||||
if (is_vmalloc_addr((void *)vaddr))
|
||||
pfn = vmalloc_to_pfn((void *)vaddr);
|
||||
else
|
||||
pfn = __pa(vaddr) >> PAGE_SHIFT;
|
||||
|
||||
e->gfn = pfn;
|
||||
e->operation = op;
|
||||
hdr->end_entry = i;
|
||||
|
||||
/*
|
||||
* Current SNP implementation doesn't keep track of the RMP page
|
||||
* size so use 4K for simplicity.
|
||||
*/
|
||||
e->pagesize = RMP_PG_SIZE_4K;
|
||||
|
||||
vaddr = vaddr + PAGE_SIZE;
|
||||
e++;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (vmgexit_psc(data))
|
||||
sev_es_terminate(SEV_TERM_SET_LINUX, GHCB_TERM_PSC);
|
||||
}
|
||||
|
||||
static void set_pages_state(unsigned long vaddr, unsigned int npages, int op)
|
||||
{
|
||||
unsigned long vaddr_end, next_vaddr;
|
||||
struct snp_psc_desc *desc;
|
||||
|
||||
desc = kmalloc(sizeof(*desc), GFP_KERNEL_ACCOUNT);
|
||||
if (!desc)
|
||||
panic("SNP: failed to allocate memory for PSC descriptor\n");
|
||||
|
||||
vaddr = vaddr & PAGE_MASK;
|
||||
vaddr_end = vaddr + (npages << PAGE_SHIFT);
|
||||
|
||||
while (vaddr < vaddr_end) {
|
||||
/* Calculate the last vaddr that fits in one struct snp_psc_desc. */
|
||||
next_vaddr = min_t(unsigned long, vaddr_end,
|
||||
(VMGEXIT_PSC_MAX_ENTRY * PAGE_SIZE) + vaddr);
|
||||
|
||||
__set_pages_state(desc, vaddr, next_vaddr, op);
|
||||
|
||||
vaddr = next_vaddr;
|
||||
}
|
||||
|
||||
kfree(desc);
|
||||
}
|
||||
|
||||
void snp_set_memory_shared(unsigned long vaddr, unsigned int npages)
|
||||
{
|
||||
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
return;
|
||||
|
||||
pvalidate_pages(vaddr, npages, false);
|
||||
|
||||
set_pages_state(vaddr, npages, SNP_PAGE_STATE_SHARED);
|
||||
}
|
||||
|
||||
void snp_set_memory_private(unsigned long vaddr, unsigned int npages)
|
||||
{
|
||||
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
return;
|
||||
|
||||
set_pages_state(vaddr, npages, SNP_PAGE_STATE_PRIVATE);
|
||||
|
||||
pvalidate_pages(vaddr, npages, true);
|
||||
}
|
||||
|
||||
static int snp_set_vmsa(void *va, bool vmsa)
|
||||
{
|
||||
u64 attrs;
|
||||
|
||||
/*
|
||||
* Running at VMPL0 allows the kernel to change the VMSA bit for a page
|
||||
* using the RMPADJUST instruction. However, for the instruction to
|
||||
* succeed it must target the permissions of a lesser privileged
|
||||
* (higher numbered) VMPL level, so use VMPL1 (refer to the RMPADJUST
|
||||
* instruction in the AMD64 APM Volume 3).
|
||||
*/
|
||||
attrs = 1;
|
||||
if (vmsa)
|
||||
attrs |= RMPADJUST_VMSA_PAGE_BIT;
|
||||
|
||||
return rmpadjust((unsigned long)va, RMP_PG_SIZE_4K, attrs);
|
||||
}
|
||||
|
||||
#define __ATTR_BASE (SVM_SELECTOR_P_MASK | SVM_SELECTOR_S_MASK)
|
||||
#define INIT_CS_ATTRIBS (__ATTR_BASE | SVM_SELECTOR_READ_MASK | SVM_SELECTOR_CODE_MASK)
|
||||
#define INIT_DS_ATTRIBS (__ATTR_BASE | SVM_SELECTOR_WRITE_MASK)
|
||||
|
||||
#define INIT_LDTR_ATTRIBS (SVM_SELECTOR_P_MASK | 2)
|
||||
#define INIT_TR_ATTRIBS (SVM_SELECTOR_P_MASK | 3)
|
||||
|
||||
static void *snp_alloc_vmsa_page(void)
|
||||
{
|
||||
struct page *p;
|
||||
|
||||
/*
|
||||
* Allocate VMSA page to work around the SNP erratum where the CPU will
|
||||
* incorrectly signal an RMP violation #PF if a large page (2MB or 1GB)
|
||||
* collides with the RMP entry of VMSA page. The recommended workaround
|
||||
* is to not use a large page.
|
||||
*
|
||||
* Allocate an 8k page which is also 8k-aligned.
|
||||
*/
|
||||
p = alloc_pages(GFP_KERNEL_ACCOUNT | __GFP_ZERO, 1);
|
||||
if (!p)
|
||||
return NULL;
|
||||
|
||||
split_page(p, 1);
|
||||
|
||||
/* Free the first 4k. This page may be 2M/1G aligned and cannot be used. */
|
||||
__free_page(p);
|
||||
|
||||
return page_address(p + 1);
|
||||
}
|
||||
|
||||
static void snp_cleanup_vmsa(struct sev_es_save_area *vmsa)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = snp_set_vmsa(vmsa, false);
|
||||
if (err)
|
||||
pr_err("clear VMSA page failed (%u), leaking page\n", err);
|
||||
else
|
||||
free_page((unsigned long)vmsa);
|
||||
}
|
||||
|
||||
static int wakeup_cpu_via_vmgexit(int apic_id, unsigned long start_ip)
|
||||
{
|
||||
struct sev_es_save_area *cur_vmsa, *vmsa;
|
||||
struct ghcb_state state;
|
||||
unsigned long flags;
|
||||
struct ghcb *ghcb;
|
||||
u8 sipi_vector;
|
||||
int cpu, ret;
|
||||
u64 cr4;
|
||||
|
||||
/*
|
||||
* The hypervisor SNP feature support check has happened earlier, just check
|
||||
* the AP_CREATION one here.
|
||||
*/
|
||||
if (!(sev_hv_features & GHCB_HV_FT_SNP_AP_CREATION))
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/*
|
||||
* Verify the desired start IP against the known trampoline start IP
|
||||
* to catch any future new trampolines that may be introduced that
|
||||
* would require a new protected guest entry point.
|
||||
*/
|
||||
if (WARN_ONCE(start_ip != real_mode_header->trampoline_start,
|
||||
"Unsupported SNP start_ip: %lx\n", start_ip))
|
||||
return -EINVAL;
|
||||
|
||||
/* Override start_ip with known protected guest start IP */
|
||||
start_ip = real_mode_header->sev_es_trampoline_start;
|
||||
|
||||
/* Find the logical CPU for the APIC ID */
|
||||
for_each_present_cpu(cpu) {
|
||||
if (arch_match_cpu_phys_id(cpu, apic_id))
|
||||
break;
|
||||
}
|
||||
if (cpu >= nr_cpu_ids)
|
||||
return -EINVAL;
|
||||
|
||||
cur_vmsa = per_cpu(sev_vmsa, cpu);
|
||||
|
||||
/*
|
||||
* A new VMSA is created each time because there is no guarantee that
|
||||
* the current VMSA is the kernels or that the vCPU is not running. If
|
||||
* an attempt was done to use the current VMSA with a running vCPU, a
|
||||
* #VMEXIT of that vCPU would wipe out all of the settings being done
|
||||
* here.
|
||||
*/
|
||||
vmsa = (struct sev_es_save_area *)snp_alloc_vmsa_page();
|
||||
if (!vmsa)
|
||||
return -ENOMEM;
|
||||
|
||||
/* CR4 should maintain the MCE value */
|
||||
cr4 = native_read_cr4() & X86_CR4_MCE;
|
||||
|
||||
/* Set the CS value based on the start_ip converted to a SIPI vector */
|
||||
sipi_vector = (start_ip >> 12);
|
||||
vmsa->cs.base = sipi_vector << 12;
|
||||
vmsa->cs.limit = AP_INIT_CS_LIMIT;
|
||||
vmsa->cs.attrib = INIT_CS_ATTRIBS;
|
||||
vmsa->cs.selector = sipi_vector << 8;
|
||||
|
||||
/* Set the RIP value based on start_ip */
|
||||
vmsa->rip = start_ip & 0xfff;
|
||||
|
||||
/* Set AP INIT defaults as documented in the APM */
|
||||
vmsa->ds.limit = AP_INIT_DS_LIMIT;
|
||||
vmsa->ds.attrib = INIT_DS_ATTRIBS;
|
||||
vmsa->es = vmsa->ds;
|
||||
vmsa->fs = vmsa->ds;
|
||||
vmsa->gs = vmsa->ds;
|
||||
vmsa->ss = vmsa->ds;
|
||||
|
||||
vmsa->gdtr.limit = AP_INIT_GDTR_LIMIT;
|
||||
vmsa->ldtr.limit = AP_INIT_LDTR_LIMIT;
|
||||
vmsa->ldtr.attrib = INIT_LDTR_ATTRIBS;
|
||||
vmsa->idtr.limit = AP_INIT_IDTR_LIMIT;
|
||||
vmsa->tr.limit = AP_INIT_TR_LIMIT;
|
||||
vmsa->tr.attrib = INIT_TR_ATTRIBS;
|
||||
|
||||
vmsa->cr4 = cr4;
|
||||
vmsa->cr0 = AP_INIT_CR0_DEFAULT;
|
||||
vmsa->dr7 = DR7_RESET_VALUE;
|
||||
vmsa->dr6 = AP_INIT_DR6_DEFAULT;
|
||||
vmsa->rflags = AP_INIT_RFLAGS_DEFAULT;
|
||||
vmsa->g_pat = AP_INIT_GPAT_DEFAULT;
|
||||
vmsa->xcr0 = AP_INIT_XCR0_DEFAULT;
|
||||
vmsa->mxcsr = AP_INIT_MXCSR_DEFAULT;
|
||||
vmsa->x87_ftw = AP_INIT_X87_FTW_DEFAULT;
|
||||
vmsa->x87_fcw = AP_INIT_X87_FCW_DEFAULT;
|
||||
|
||||
/* SVME must be set. */
|
||||
vmsa->efer = EFER_SVME;
|
||||
|
||||
/*
|
||||
* Set the SNP-specific fields for this VMSA:
|
||||
* VMPL level
|
||||
* SEV_FEATURES (matches the SEV STATUS MSR right shifted 2 bits)
|
||||
*/
|
||||
vmsa->vmpl = 0;
|
||||
vmsa->sev_features = sev_status >> 2;
|
||||
|
||||
/* Switch the page over to a VMSA page now that it is initialized */
|
||||
ret = snp_set_vmsa(vmsa, true);
|
||||
if (ret) {
|
||||
pr_err("set VMSA page failed (%u)\n", ret);
|
||||
free_page((unsigned long)vmsa);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Issue VMGEXIT AP Creation NAE event */
|
||||
local_irq_save(flags);
|
||||
|
||||
ghcb = __sev_get_ghcb(&state);
|
||||
|
||||
vc_ghcb_invalidate(ghcb);
|
||||
ghcb_set_rax(ghcb, vmsa->sev_features);
|
||||
ghcb_set_sw_exit_code(ghcb, SVM_VMGEXIT_AP_CREATION);
|
||||
ghcb_set_sw_exit_info_1(ghcb, ((u64)apic_id << 32) | SVM_VMGEXIT_AP_CREATE);
|
||||
ghcb_set_sw_exit_info_2(ghcb, __pa(vmsa));
|
||||
|
||||
sev_es_wr_ghcb_msr(__pa(ghcb));
|
||||
VMGEXIT();
|
||||
|
||||
if (!ghcb_sw_exit_info_1_is_valid(ghcb) ||
|
||||
lower_32_bits(ghcb->save.sw_exit_info_1)) {
|
||||
pr_err("SNP AP Creation error\n");
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
__sev_put_ghcb(&state);
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
/* Perform cleanup if there was an error */
|
||||
if (ret) {
|
||||
snp_cleanup_vmsa(vmsa);
|
||||
vmsa = NULL;
|
||||
}
|
||||
|
||||
/* Free up any previous VMSA page */
|
||||
if (cur_vmsa)
|
||||
snp_cleanup_vmsa(cur_vmsa);
|
||||
|
||||
/* Record the current VMSA page */
|
||||
per_cpu(sev_vmsa, cpu) = vmsa;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void snp_set_wakeup_secondary_cpu(void)
|
||||
{
|
||||
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
return;
|
||||
|
||||
/*
|
||||
* Always set this override if SNP is enabled. This makes it the
|
||||
* required method to start APs under SNP. If the hypervisor does
|
||||
* not support AP creation, then no APs will be started.
|
||||
*/
|
||||
apic->wakeup_secondary_cpu = wakeup_cpu_via_vmgexit;
|
||||
}
|
||||
|
||||
int __init sev_es_setup_ap_jump_table(struct real_mode_header *rmh)
|
||||
{
|
||||
u16 startup_cs, startup_ip;
|
||||
phys_addr_t jump_table_pa;
|
||||
|
|
@ -644,15 +1223,39 @@ static enum es_result vc_handle_msr(struct ghcb *ghcb, struct es_em_ctxt *ctxt)
|
|||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function runs on the first #VC exception after the kernel
|
||||
* switched to virtual addresses.
|
||||
*/
|
||||
static bool __init sev_es_setup_ghcb(void)
|
||||
static void snp_register_per_cpu_ghcb(void)
|
||||
{
|
||||
struct sev_es_runtime_data *data;
|
||||
struct ghcb *ghcb;
|
||||
|
||||
data = this_cpu_read(runtime_data);
|
||||
ghcb = &data->ghcb_page;
|
||||
|
||||
snp_register_ghcb_early(__pa(ghcb));
|
||||
}
|
||||
|
||||
void setup_ghcb(void)
|
||||
{
|
||||
if (!cc_platform_has(CC_ATTR_GUEST_STATE_ENCRYPT))
|
||||
return;
|
||||
|
||||
/* First make sure the hypervisor talks a supported protocol. */
|
||||
if (!sev_es_negotiate_protocol())
|
||||
return false;
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SEV_ES_GEN_REQ);
|
||||
|
||||
/*
|
||||
* Check whether the runtime #VC exception handler is active. It uses
|
||||
* the per-CPU GHCB page which is set up by sev_es_init_vc_handling().
|
||||
*
|
||||
* If SNP is active, register the per-CPU GHCB page so that the runtime
|
||||
* exception handler can use it.
|
||||
*/
|
||||
if (initial_vc_handler == (unsigned long)kernel_exc_vmm_communication) {
|
||||
if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
snp_register_per_cpu_ghcb();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear the boot_ghcb. The first exception comes in before the bss
|
||||
|
|
@ -663,7 +1266,9 @@ static bool __init sev_es_setup_ghcb(void)
|
|||
/* Alright - Make the boot-ghcb public */
|
||||
boot_ghcb = &boot_ghcb_page;
|
||||
|
||||
return true;
|
||||
/* SNP guest requires that GHCB GPA must be registered. */
|
||||
if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
snp_register_ghcb_early(__pa(&boot_ghcb_page));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
|
|
@ -766,6 +1371,17 @@ void __init sev_es_init_vc_handling(void)
|
|||
if (!sev_es_check_cpu_features())
|
||||
panic("SEV-ES CPU Features missing");
|
||||
|
||||
/*
|
||||
* SNP is supported in v2 of the GHCB spec which mandates support for HV
|
||||
* features.
|
||||
*/
|
||||
if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP)) {
|
||||
sev_hv_features = get_hv_features();
|
||||
|
||||
if (!(sev_hv_features & GHCB_HV_FT_SNP))
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SNP_UNSUPPORTED);
|
||||
}
|
||||
|
||||
/* Enable SEV-ES special handling */
|
||||
static_branch_enable(&sev_es_enable_key);
|
||||
|
||||
|
|
@ -1337,7 +1953,7 @@ DEFINE_IDTENTRY_VC_KERNEL(exc_vmm_communication)
|
|||
show_regs(regs);
|
||||
|
||||
/* Ask hypervisor to sev_es_terminate */
|
||||
sev_es_terminate(GHCB_SEV_ES_GEN_REQ);
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SEV_ES_GEN_REQ);
|
||||
|
||||
/* If that fails and we get here - just panic */
|
||||
panic("Returned from Terminate-Request to Hypervisor\n");
|
||||
|
|
@ -1383,10 +1999,6 @@ bool __init handle_vc_boot_ghcb(struct pt_regs *regs)
|
|||
struct es_em_ctxt ctxt;
|
||||
enum es_result result;
|
||||
|
||||
/* Do initial setup or terminate the guest */
|
||||
if (unlikely(boot_ghcb == NULL && !sev_es_setup_ghcb()))
|
||||
sev_es_terminate(GHCB_SEV_ES_GEN_REQ);
|
||||
|
||||
vc_ghcb_invalidate(boot_ghcb);
|
||||
|
||||
result = vc_init_em_ctxt(&ctxt, regs, exit_code);
|
||||
|
|
@ -1425,6 +2037,215 @@ bool __init handle_vc_boot_ghcb(struct pt_regs *regs)
|
|||
fail:
|
||||
show_regs(regs);
|
||||
|
||||
while (true)
|
||||
halt();
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SEV_ES_GEN_REQ);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initial set up of SNP relies on information provided by the
|
||||
* Confidential Computing blob, which can be passed to the kernel
|
||||
* in the following ways, depending on how it is booted:
|
||||
*
|
||||
* - when booted via the boot/decompress kernel:
|
||||
* - via boot_params
|
||||
*
|
||||
* - when booted directly by firmware/bootloader (e.g. CONFIG_PVH):
|
||||
* - via a setup_data entry, as defined by the Linux Boot Protocol
|
||||
*
|
||||
* Scan for the blob in that order.
|
||||
*/
|
||||
static __init struct cc_blob_sev_info *find_cc_blob(struct boot_params *bp)
|
||||
{
|
||||
struct cc_blob_sev_info *cc_info;
|
||||
|
||||
/* Boot kernel would have passed the CC blob via boot_params. */
|
||||
if (bp->cc_blob_address) {
|
||||
cc_info = (struct cc_blob_sev_info *)(unsigned long)bp->cc_blob_address;
|
||||
goto found_cc_info;
|
||||
}
|
||||
|
||||
/*
|
||||
* If kernel was booted directly, without the use of the
|
||||
* boot/decompression kernel, the CC blob may have been passed via
|
||||
* setup_data instead.
|
||||
*/
|
||||
cc_info = find_cc_blob_setup_data(bp);
|
||||
if (!cc_info)
|
||||
return NULL;
|
||||
|
||||
found_cc_info:
|
||||
if (cc_info->magic != CC_BLOB_SEV_HDR_MAGIC)
|
||||
snp_abort();
|
||||
|
||||
return cc_info;
|
||||
}
|
||||
|
||||
bool __init snp_init(struct boot_params *bp)
|
||||
{
|
||||
struct cc_blob_sev_info *cc_info;
|
||||
|
||||
if (!bp)
|
||||
return false;
|
||||
|
||||
cc_info = find_cc_blob(bp);
|
||||
if (!cc_info)
|
||||
return false;
|
||||
|
||||
setup_cpuid_table(cc_info);
|
||||
|
||||
/*
|
||||
* The CC blob will be used later to access the secrets page. Cache
|
||||
* it here like the boot kernel does.
|
||||
*/
|
||||
bp->cc_blob_address = (u32)(unsigned long)cc_info;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void __init snp_abort(void)
|
||||
{
|
||||
sev_es_terminate(SEV_TERM_SET_GEN, GHCB_SNP_UNSUPPORTED);
|
||||
}
|
||||
|
||||
static void dump_cpuid_table(void)
|
||||
{
|
||||
const struct snp_cpuid_table *cpuid_table = snp_cpuid_get_table();
|
||||
int i = 0;
|
||||
|
||||
pr_info("count=%d reserved=0x%x reserved2=0x%llx\n",
|
||||
cpuid_table->count, cpuid_table->__reserved1, cpuid_table->__reserved2);
|
||||
|
||||
for (i = 0; i < SNP_CPUID_COUNT_MAX; i++) {
|
||||
const struct snp_cpuid_fn *fn = &cpuid_table->fn[i];
|
||||
|
||||
pr_info("index=%3d fn=0x%08x subfn=0x%08x: eax=0x%08x ebx=0x%08x ecx=0x%08x edx=0x%08x xcr0_in=0x%016llx xss_in=0x%016llx reserved=0x%016llx\n",
|
||||
i, fn->eax_in, fn->ecx_in, fn->eax, fn->ebx, fn->ecx,
|
||||
fn->edx, fn->xcr0_in, fn->xss_in, fn->__reserved);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* It is useful from an auditing/testing perspective to provide an easy way
|
||||
* for the guest owner to know that the CPUID table has been initialized as
|
||||
* expected, but that initialization happens too early in boot to print any
|
||||
* sort of indicator, and there's not really any other good place to do it,
|
||||
* so do it here.
|
||||
*/
|
||||
static int __init report_cpuid_table(void)
|
||||
{
|
||||
const struct snp_cpuid_table *cpuid_table = snp_cpuid_get_table();
|
||||
|
||||
if (!cpuid_table->count)
|
||||
return 0;
|
||||
|
||||
pr_info("Using SNP CPUID table, %d entries present.\n",
|
||||
cpuid_table->count);
|
||||
|
||||
if (sev_cfg.debug)
|
||||
dump_cpuid_table();
|
||||
|
||||
return 0;
|
||||
}
|
||||
arch_initcall(report_cpuid_table);
|
||||
|
||||
static int __init init_sev_config(char *str)
|
||||
{
|
||||
char *s;
|
||||
|
||||
while ((s = strsep(&str, ","))) {
|
||||
if (!strcmp(s, "debug")) {
|
||||
sev_cfg.debug = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
pr_info("SEV command-line option '%s' was not recognized\n", s);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
__setup("sev=", init_sev_config);
|
||||
|
||||
int snp_issue_guest_request(u64 exit_code, struct snp_req_data *input, unsigned long *fw_err)
|
||||
{
|
||||
struct ghcb_state state;
|
||||
struct es_em_ctxt ctxt;
|
||||
unsigned long flags;
|
||||
struct ghcb *ghcb;
|
||||
int ret;
|
||||
|
||||
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
return -ENODEV;
|
||||
|
||||
if (!fw_err)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* __sev_get_ghcb() needs to run with IRQs disabled because it is using
|
||||
* a per-CPU GHCB.
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
|
||||
ghcb = __sev_get_ghcb(&state);
|
||||
if (!ghcb) {
|
||||
ret = -EIO;
|
||||
goto e_restore_irq;
|
||||
}
|
||||
|
||||
vc_ghcb_invalidate(ghcb);
|
||||
|
||||
if (exit_code == SVM_VMGEXIT_EXT_GUEST_REQUEST) {
|
||||
ghcb_set_rax(ghcb, input->data_gpa);
|
||||
ghcb_set_rbx(ghcb, input->data_npages);
|
||||
}
|
||||
|
||||
ret = sev_es_ghcb_hv_call(ghcb, true, &ctxt, exit_code, input->req_gpa, input->resp_gpa);
|
||||
if (ret)
|
||||
goto e_put;
|
||||
|
||||
if (ghcb->save.sw_exit_info_2) {
|
||||
/* Number of expected pages are returned in RBX */
|
||||
if (exit_code == SVM_VMGEXIT_EXT_GUEST_REQUEST &&
|
||||
ghcb->save.sw_exit_info_2 == SNP_GUEST_REQ_INVALID_LEN)
|
||||
input->data_npages = ghcb_get_rbx(ghcb);
|
||||
|
||||
*fw_err = ghcb->save.sw_exit_info_2;
|
||||
|
||||
ret = -EIO;
|
||||
}
|
||||
|
||||
e_put:
|
||||
__sev_put_ghcb(&state);
|
||||
e_restore_irq:
|
||||
local_irq_restore(flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(snp_issue_guest_request);
|
||||
|
||||
static struct platform_device sev_guest_device = {
|
||||
.name = "sev-guest",
|
||||
.id = -1,
|
||||
};
|
||||
|
||||
static int __init snp_init_platform_device(void)
|
||||
{
|
||||
struct sev_guest_platform_data data;
|
||||
u64 gpa;
|
||||
|
||||
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
return -ENODEV;
|
||||
|
||||
gpa = get_secrets_page();
|
||||
if (!gpa)
|
||||
return -ENODEV;
|
||||
|
||||
data.secrets_gpa = gpa;
|
||||
if (platform_device_add_data(&sev_guest_device, &data, sizeof(data)))
|
||||
return -ENODEV;
|
||||
|
||||
if (platform_device_register(&sev_guest_device))
|
||||
return -ENODEV;
|
||||
|
||||
pr_info("SNP guest platform device initialized.\n");
|
||||
return 0;
|
||||
}
|
||||
device_initcall(snp_init_platform_device);
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
#include <asm/spec-ctrl.h>
|
||||
#include <asm/hw_irq.h>
|
||||
#include <asm/stackprotector.h>
|
||||
#include <asm/sev.h>
|
||||
|
||||
/* representing HT siblings of each logical CPU */
|
||||
DEFINE_PER_CPU_READ_MOSTLY(cpumask_var_t, cpu_sibling_map);
|
||||
|
|
@ -1430,6 +1431,8 @@ void __init native_smp_prepare_cpus(unsigned int max_cpus)
|
|||
smp_quirk_init_udelay();
|
||||
|
||||
speculative_store_bypass_ht_init();
|
||||
|
||||
snp_set_wakeup_secondary_cpu();
|
||||
}
|
||||
|
||||
void arch_thaw_secondary_cpus_begin(void)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <asm/user.h>
|
||||
#include <asm/fpu/xstate.h>
|
||||
#include <asm/sgx.h>
|
||||
#include <asm/cpuid.h>
|
||||
#include "cpuid.h"
|
||||
#include "lapic.h"
|
||||
#include "mmu.h"
|
||||
|
|
@ -744,24 +745,8 @@ static struct kvm_cpuid_entry2 *do_host_cpuid(struct kvm_cpuid_array *array,
|
|||
cpuid_count(entry->function, entry->index,
|
||||
&entry->eax, &entry->ebx, &entry->ecx, &entry->edx);
|
||||
|
||||
switch (function) {
|
||||
case 4:
|
||||
case 7:
|
||||
case 0xb:
|
||||
case 0xd:
|
||||
case 0xf:
|
||||
case 0x10:
|
||||
case 0x12:
|
||||
case 0x14:
|
||||
case 0x17:
|
||||
case 0x18:
|
||||
case 0x1d:
|
||||
case 0x1e:
|
||||
case 0x1f:
|
||||
case 0x8000001d:
|
||||
if (cpuid_function_is_indexed(function))
|
||||
entry->flags |= KVM_CPUID_FLAG_SIGNIFCANT_INDEX;
|
||||
break;
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -562,12 +562,20 @@ static int sev_launch_update_data(struct kvm *kvm, struct kvm_sev_cmd *argp)
|
|||
|
||||
static int sev_es_sync_vmsa(struct vcpu_svm *svm)
|
||||
{
|
||||
struct vmcb_save_area *save = &svm->vmcb->save;
|
||||
struct sev_es_save_area *save = svm->sev_es.vmsa;
|
||||
|
||||
/* Check some debug related fields before encrypting the VMSA */
|
||||
if (svm->vcpu.guest_debug || (save->dr7 & ~DR7_FIXED_1))
|
||||
if (svm->vcpu.guest_debug || (svm->vmcb->save.dr7 & ~DR7_FIXED_1))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* SEV-ES will use a VMSA that is pointed to by the VMCB, not
|
||||
* the traditional VMSA that is part of the VMCB. Copy the
|
||||
* traditional VMSA as it has been built so far (in prep
|
||||
* for LAUNCH_UPDATE_VMSA) to be the initial SEV-ES state.
|
||||
*/
|
||||
memcpy(save, &svm->vmcb->save, sizeof(svm->vmcb->save));
|
||||
|
||||
/* Sync registgers */
|
||||
save->rax = svm->vcpu.arch.regs[VCPU_REGS_RAX];
|
||||
save->rbx = svm->vcpu.arch.regs[VCPU_REGS_RBX];
|
||||
|
|
@ -595,14 +603,6 @@ static int sev_es_sync_vmsa(struct vcpu_svm *svm)
|
|||
save->xss = svm->vcpu.arch.ia32_xss;
|
||||
save->dr6 = svm->vcpu.arch.dr6;
|
||||
|
||||
/*
|
||||
* SEV-ES will use a VMSA that is pointed to by the VMCB, not
|
||||
* the traditional VMSA that is part of the VMCB. Copy the
|
||||
* traditional VMSA as it has been built so far (in prep
|
||||
* for LAUNCH_UPDATE_VMSA) to be the initial SEV-ES state.
|
||||
*/
|
||||
memcpy(svm->sev_es.vmsa, save, sizeof(*save));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -2966,7 +2966,7 @@ void sev_es_vcpu_reset(struct vcpu_svm *svm)
|
|||
sev_enc_bit));
|
||||
}
|
||||
|
||||
void sev_es_prepare_switch_to_guest(struct vmcb_save_area *hostsa)
|
||||
void sev_es_prepare_switch_to_guest(struct sev_es_save_area *hostsa)
|
||||
{
|
||||
/*
|
||||
* As an SEV-ES guest, hardware will restore the host state on VMEXIT,
|
||||
|
|
|
|||
|
|
@ -1270,8 +1270,8 @@ static void svm_prepare_switch_to_guest(struct kvm_vcpu *vcpu)
|
|||
*/
|
||||
vmsave(__sme_page_pa(sd->save_area));
|
||||
if (sev_es_guest(vcpu->kvm)) {
|
||||
struct vmcb_save_area *hostsa;
|
||||
hostsa = (struct vmcb_save_area *)(page_address(sd->save_area) + 0x400);
|
||||
struct sev_es_save_area *hostsa;
|
||||
hostsa = (struct sev_es_save_area *)(page_address(sd->save_area) + 0x400);
|
||||
|
||||
sev_es_prepare_switch_to_guest(hostsa);
|
||||
}
|
||||
|
|
@ -3117,8 +3117,8 @@ static void dump_vmcb(struct kvm_vcpu *vcpu)
|
|||
"tr:",
|
||||
save01->tr.selector, save01->tr.attrib,
|
||||
save01->tr.limit, save01->tr.base);
|
||||
pr_err("cpl: %d efer: %016llx\n",
|
||||
save->cpl, save->efer);
|
||||
pr_err("vmpl: %d cpl: %d efer: %016llx\n",
|
||||
save->vmpl, save->cpl, save->efer);
|
||||
pr_err("%-15s %016llx %-13s %016llx\n",
|
||||
"cr0:", save->cr0, "cr2:", save->cr2);
|
||||
pr_err("%-15s %016llx %-13s %016llx\n",
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ struct svm_nested_state {
|
|||
|
||||
struct vcpu_sev_es_state {
|
||||
/* SEV-ES support */
|
||||
struct vmcb_save_area *vmsa;
|
||||
struct sev_es_save_area *vmsa;
|
||||
struct ghcb *ghcb;
|
||||
struct kvm_host_map ghcb_map;
|
||||
bool received_first_sipi;
|
||||
|
|
@ -622,7 +622,7 @@ int sev_es_string_io(struct vcpu_svm *svm, int size, unsigned int port, int in);
|
|||
void sev_es_init_vmcb(struct vcpu_svm *svm);
|
||||
void sev_es_vcpu_reset(struct vcpu_svm *svm);
|
||||
void sev_vcpu_deliver_sipi_vector(struct kvm_vcpu *vcpu, u8 vector);
|
||||
void sev_es_prepare_switch_to_guest(struct vmcb_save_area *hostsa);
|
||||
void sev_es_prepare_switch_to_guest(struct sev_es_save_area *hostsa);
|
||||
void sev_es_unmap_ghcb(struct vcpu_svm *svm);
|
||||
|
||||
/* vmenter.S */
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ static void print_mem_encrypt_feature_info(void)
|
|||
if (cc_platform_has(CC_ATTR_GUEST_STATE_ENCRYPT))
|
||||
pr_cont(" SEV-ES");
|
||||
|
||||
/* Secure Nested Paging */
|
||||
if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
|
||||
pr_cont(" SEV-SNP");
|
||||
|
||||
pr_cont("\n");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include <asm/processor-flags.h>
|
||||
#include <asm/msr.h>
|
||||
#include <asm/cmdline.h>
|
||||
#include <asm/sev.h>
|
||||
|
||||
#include "mm_internal.h"
|
||||
|
||||
|
|
@ -47,6 +48,36 @@ EXPORT_SYMBOL(sme_me_mask);
|
|||
/* Buffer used for early in-place encryption by BSP, no locking needed */
|
||||
static char sme_early_buffer[PAGE_SIZE] __initdata __aligned(PAGE_SIZE);
|
||||
|
||||
/*
|
||||
* SNP-specific routine which needs to additionally change the page state from
|
||||
* private to shared before copying the data from the source to destination and
|
||||
* restore after the copy.
|
||||
*/
|
||||
static inline void __init snp_memcpy(void *dst, void *src, size_t sz,
|
||||
unsigned long paddr, bool decrypt)
|
||||
{
|
||||
unsigned long npages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
|
||||
|
||||
if (decrypt) {
|
||||
/*
|
||||
* @paddr needs to be accessed decrypted, mark the page shared in
|
||||
* the RMP table before copying it.
|
||||
*/
|
||||
early_snp_set_memory_shared((unsigned long)__va(paddr), paddr, npages);
|
||||
|
||||
memcpy(dst, src, sz);
|
||||
|
||||
/* Restore the page state after the memcpy. */
|
||||
early_snp_set_memory_private((unsigned long)__va(paddr), paddr, npages);
|
||||
} else {
|
||||
/*
|
||||
* @paddr need to be accessed encrypted, no need for the page state
|
||||
* change.
|
||||
*/
|
||||
memcpy(dst, src, sz);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This routine does not change the underlying encryption setting of the
|
||||
* page(s) that map this memory. It assumes that eventually the memory is
|
||||
|
|
@ -95,8 +126,13 @@ static void __init __sme_early_enc_dec(resource_size_t paddr,
|
|||
* Use a temporary buffer, of cache-line multiple size, to
|
||||
* avoid data corruption as documented in the APM.
|
||||
*/
|
||||
memcpy(sme_early_buffer, src, len);
|
||||
memcpy(dst, sme_early_buffer, len);
|
||||
if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP)) {
|
||||
snp_memcpy(sme_early_buffer, src, len, paddr, enc);
|
||||
snp_memcpy(dst, sme_early_buffer, len, paddr, !enc);
|
||||
} else {
|
||||
memcpy(sme_early_buffer, src, len);
|
||||
memcpy(dst, sme_early_buffer, len);
|
||||
}
|
||||
|
||||
early_memunmap(dst, len);
|
||||
early_memunmap(src, len);
|
||||
|
|
@ -280,11 +316,24 @@ static void enc_dec_hypercall(unsigned long vaddr, int npages, bool enc)
|
|||
|
||||
static void amd_enc_status_change_prepare(unsigned long vaddr, int npages, bool enc)
|
||||
{
|
||||
/*
|
||||
* To maintain the security guarantees of SEV-SNP guests, make sure
|
||||
* to invalidate the memory before encryption attribute is cleared.
|
||||
*/
|
||||
if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP) && !enc)
|
||||
snp_set_memory_shared(vaddr, npages);
|
||||
}
|
||||
|
||||
/* Return true unconditionally: return value doesn't matter for the SEV side */
|
||||
static bool amd_enc_status_change_finish(unsigned long vaddr, int npages, bool enc)
|
||||
{
|
||||
/*
|
||||
* After memory is mapped encrypted in the page table, validate it
|
||||
* so that it is consistent with the page table updates.
|
||||
*/
|
||||
if (cc_platform_has(CC_ATTR_GUEST_SEV_SNP) && enc)
|
||||
snp_set_memory_private(vaddr, npages);
|
||||
|
||||
if (!cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT))
|
||||
enc_dec_hypercall(vaddr, npages, enc);
|
||||
|
||||
|
|
@ -322,14 +371,28 @@ static void __init __set_clr_pte_enc(pte_t *kpte, int level, bool enc)
|
|||
clflush_cache_range(__va(pa), size);
|
||||
|
||||
/* Encrypt/decrypt the contents in-place */
|
||||
if (enc)
|
||||
if (enc) {
|
||||
sme_early_encrypt(pa, size);
|
||||
else
|
||||
} else {
|
||||
sme_early_decrypt(pa, size);
|
||||
|
||||
/*
|
||||
* ON SNP, the page state in the RMP table must happen
|
||||
* before the page table updates.
|
||||
*/
|
||||
early_snp_set_memory_shared((unsigned long)__va(pa), pa, 1);
|
||||
}
|
||||
|
||||
/* Change the page encryption mask. */
|
||||
new_pte = pfn_pte(pfn, new_prot);
|
||||
set_pte_atomic(kpte, new_pte);
|
||||
|
||||
/*
|
||||
* If page is set encrypted in the page table, then update the RMP table to
|
||||
* add this page as private.
|
||||
*/
|
||||
if (enc)
|
||||
early_snp_set_memory_private((unsigned long)__va(pa), pa, 1);
|
||||
}
|
||||
|
||||
static int __init early_set_memory_enc_dec(unsigned long vaddr,
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@
|
|||
#include <asm/sections.h>
|
||||
#include <asm/cmdline.h>
|
||||
#include <asm/coco.h>
|
||||
#include <asm/sev.h>
|
||||
|
||||
#include "mm_internal.h"
|
||||
|
||||
|
|
@ -509,8 +510,11 @@ void __init sme_enable(struct boot_params *bp)
|
|||
bool active_by_default;
|
||||
unsigned long me_mask;
|
||||
char buffer[16];
|
||||
bool snp;
|
||||
u64 msr;
|
||||
|
||||
snp = snp_init(bp);
|
||||
|
||||
/* Check for the SME/SEV support leaf */
|
||||
eax = 0x80000000;
|
||||
ecx = 0;
|
||||
|
|
@ -542,6 +546,10 @@ void __init sme_enable(struct boot_params *bp)
|
|||
sev_status = __rdmsr(MSR_AMD64_SEV);
|
||||
feature_mask = (sev_status & MSR_AMD64_SEV_ENABLED) ? AMD_SEV_BIT : AMD_SME_BIT;
|
||||
|
||||
/* The SEV-SNP CC blob should never be present unless SEV-SNP is enabled. */
|
||||
if (snp && !(sev_status & MSR_AMD64_SEV_SNP_ENABLED))
|
||||
snp_abort();
|
||||
|
||||
/* Check if memory encryption is enabled */
|
||||
if (feature_mask == AMD_SME_BIT) {
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
#define PIRQ_SIGNATURE (('$' << 0) + ('P' << 8) + ('I' << 16) + ('R' << 24))
|
||||
#define PIRQ_VERSION 0x0100
|
||||
|
||||
#define IRT_SIGNATURE (('$' << 0) + ('I' << 8) + ('R' << 16) + ('T' << 24))
|
||||
|
||||
static int broken_hp_bios_irq9;
|
||||
static int acer_tm360_irqrouting;
|
||||
|
||||
|
|
@ -68,30 +70,99 @@ void (*pcibios_disable_irq)(struct pci_dev *dev) = pirq_disable_irq;
|
|||
* and perform checksum verification.
|
||||
*/
|
||||
|
||||
static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr)
|
||||
static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr,
|
||||
u8 *limit)
|
||||
{
|
||||
struct irq_routing_table *rt;
|
||||
int i;
|
||||
u8 sum;
|
||||
|
||||
rt = (struct irq_routing_table *) addr;
|
||||
rt = (struct irq_routing_table *)addr;
|
||||
if (rt->signature != PIRQ_SIGNATURE ||
|
||||
rt->version != PIRQ_VERSION ||
|
||||
rt->size % 16 ||
|
||||
rt->size < sizeof(struct irq_routing_table))
|
||||
rt->size < sizeof(struct irq_routing_table) ||
|
||||
(limit && rt->size > limit - addr))
|
||||
return NULL;
|
||||
sum = 0;
|
||||
for (i = 0; i < rt->size; i++)
|
||||
sum += addr[i];
|
||||
if (!sum) {
|
||||
DBG(KERN_DEBUG "PCI: Interrupt Routing Table found at 0x%p\n",
|
||||
rt);
|
||||
DBG(KERN_DEBUG "PCI: Interrupt Routing Table found at 0x%lx\n",
|
||||
__pa(rt));
|
||||
return rt;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle the $IRT PCI IRQ Routing Table format used by AMI for its BCP
|
||||
* (BIOS Configuration Program) external tool meant for tweaking BIOS
|
||||
* structures without the need to rebuild it from sources. The $IRT
|
||||
* format has been invented by AMI before Microsoft has come up with its
|
||||
* $PIR format and a $IRT table is therefore there in some systems that
|
||||
* lack a $PIR table.
|
||||
*
|
||||
* It uses the same PCI BIOS 2.1 format for interrupt routing entries
|
||||
* themselves but has a different simpler header prepended instead,
|
||||
* occupying 8 bytes, where a `$IRT' signature is followed by one byte
|
||||
* specifying the total number of interrupt routing entries allocated in
|
||||
* the table, then one byte specifying the actual number of entries used
|
||||
* (which the BCP tool can take advantage of when modifying the table),
|
||||
* and finally a 16-bit word giving the IRQs devoted exclusively to PCI.
|
||||
* Unlike with the $PIR table there is no alignment guarantee.
|
||||
*
|
||||
* Given the similarity of the two formats the $IRT one is trivial to
|
||||
* convert to the $PIR one, which we do here, except that obviously we
|
||||
* have no information as to the router device to use, but we can handle
|
||||
* it by matching PCI device IDs actually seen on the bus against ones
|
||||
* that our individual routers recognise.
|
||||
*
|
||||
* Reportedly there is another $IRT table format where a 16-bit word
|
||||
* follows the header instead that points to interrupt routing entries
|
||||
* in a $PIR table provided elsewhere. In that case this code will not
|
||||
* be reached though as the $PIR table will have been chosen instead.
|
||||
*/
|
||||
static inline struct irq_routing_table *pirq_convert_irt_table(u8 *addr,
|
||||
u8 *limit)
|
||||
{
|
||||
struct irt_routing_table *ir;
|
||||
struct irq_routing_table *rt;
|
||||
u16 size;
|
||||
u8 sum;
|
||||
int i;
|
||||
|
||||
ir = (struct irt_routing_table *)addr;
|
||||
if (ir->signature != IRT_SIGNATURE || !ir->used || ir->size < ir->used)
|
||||
return NULL;
|
||||
|
||||
size = sizeof(*ir) + ir->used * sizeof(ir->slots[0]);
|
||||
if (size > limit - addr)
|
||||
return NULL;
|
||||
|
||||
DBG(KERN_DEBUG "PCI: $IRT Interrupt Routing Table found at 0x%lx\n",
|
||||
__pa(ir));
|
||||
|
||||
size = sizeof(*rt) + ir->used * sizeof(rt->slots[0]);
|
||||
rt = kzalloc(size, GFP_KERNEL);
|
||||
if (!rt)
|
||||
return NULL;
|
||||
|
||||
rt->signature = PIRQ_SIGNATURE;
|
||||
rt->version = PIRQ_VERSION;
|
||||
rt->size = size;
|
||||
rt->exclusive_irqs = ir->exclusive_irqs;
|
||||
for (i = 0; i < ir->used; i++)
|
||||
rt->slots[i] = ir->slots[i];
|
||||
|
||||
addr = (u8 *)rt;
|
||||
sum = 0;
|
||||
for (i = 0; i < size; i++)
|
||||
sum += addr[i];
|
||||
rt->checksum = -sum;
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Search 0xf0000 -- 0xfffff for the PCI IRQ Routing Table.
|
||||
|
|
@ -99,17 +170,29 @@ static inline struct irq_routing_table *pirq_check_routing_table(u8 *addr)
|
|||
|
||||
static struct irq_routing_table * __init pirq_find_routing_table(void)
|
||||
{
|
||||
u8 * const bios_start = (u8 *)__va(0xf0000);
|
||||
u8 * const bios_end = (u8 *)__va(0x100000);
|
||||
u8 *addr;
|
||||
struct irq_routing_table *rt;
|
||||
|
||||
if (pirq_table_addr) {
|
||||
rt = pirq_check_routing_table((u8 *) __va(pirq_table_addr));
|
||||
rt = pirq_check_routing_table((u8 *)__va(pirq_table_addr),
|
||||
NULL);
|
||||
if (rt)
|
||||
return rt;
|
||||
printk(KERN_WARNING "PCI: PIRQ table NOT found at pirqaddr\n");
|
||||
}
|
||||
for (addr = (u8 *) __va(0xf0000); addr < (u8 *) __va(0x100000); addr += 16) {
|
||||
rt = pirq_check_routing_table(addr);
|
||||
for (addr = bios_start;
|
||||
addr < bios_end - sizeof(struct irq_routing_table);
|
||||
addr += 16) {
|
||||
rt = pirq_check_routing_table(addr, bios_end);
|
||||
if (rt)
|
||||
return rt;
|
||||
}
|
||||
for (addr = bios_start;
|
||||
addr < bios_end - sizeof(struct irt_routing_table);
|
||||
addr++) {
|
||||
rt = pirq_convert_irt_table(addr, bios_end);
|
||||
if (rt)
|
||||
return rt;
|
||||
}
|
||||
|
|
@ -135,7 +218,8 @@ static void __init pirq_peer_trick(void)
|
|||
#ifdef DEBUG
|
||||
{
|
||||
int j;
|
||||
DBG(KERN_DEBUG "%02x:%02x slot=%02x", e->bus, e->devfn/8, e->slot);
|
||||
DBG(KERN_DEBUG "%02x:%02x.%x slot=%02x",
|
||||
e->bus, e->devfn / 8, e->devfn % 8, e->slot);
|
||||
for (j = 0; j < 4; j++)
|
||||
DBG(" %d:%02x/%04x", j, e->irq[j].link, e->irq[j].bitmap);
|
||||
DBG("\n");
|
||||
|
|
@ -253,6 +337,15 @@ static void write_pc_conf_nybble(u8 base, u8 index, u8 val)
|
|||
pc_conf_set(reg, x);
|
||||
}
|
||||
|
||||
/*
|
||||
* FinALi pirq rules are as follows:
|
||||
*
|
||||
* - bit 0 selects between INTx Routing Table Mapping Registers,
|
||||
*
|
||||
* - bit 3 selects the nibble within the INTx Routing Table Mapping Register,
|
||||
*
|
||||
* - bits 7:4 map to bits 3:0 of the PCI INTx Sensitivity Register.
|
||||
*/
|
||||
static int pirq_finali_get(struct pci_dev *router, struct pci_dev *dev,
|
||||
int pirq)
|
||||
{
|
||||
|
|
@ -260,11 +353,13 @@ static int pirq_finali_get(struct pci_dev *router, struct pci_dev *dev,
|
|||
0, 9, 3, 10, 4, 5, 7, 6, 0, 11, 0, 12, 0, 14, 0, 15
|
||||
};
|
||||
unsigned long flags;
|
||||
u8 index;
|
||||
u8 x;
|
||||
|
||||
index = (pirq & 1) << 1 | (pirq & 8) >> 3;
|
||||
raw_spin_lock_irqsave(&pc_conf_lock, flags);
|
||||
pc_conf_set(PC_CONF_FINALI_LOCK, PC_CONF_FINALI_LOCK_KEY);
|
||||
x = irqmap[read_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, pirq - 1)];
|
||||
x = irqmap[read_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, index)];
|
||||
pc_conf_set(PC_CONF_FINALI_LOCK, 0);
|
||||
raw_spin_unlock_irqrestore(&pc_conf_lock, flags);
|
||||
return x;
|
||||
|
|
@ -278,13 +373,15 @@ static int pirq_finali_set(struct pci_dev *router, struct pci_dev *dev,
|
|||
};
|
||||
u8 val = irqmap[irq];
|
||||
unsigned long flags;
|
||||
u8 index;
|
||||
|
||||
if (!val)
|
||||
return 0;
|
||||
|
||||
index = (pirq & 1) << 1 | (pirq & 8) >> 3;
|
||||
raw_spin_lock_irqsave(&pc_conf_lock, flags);
|
||||
pc_conf_set(PC_CONF_FINALI_LOCK, PC_CONF_FINALI_LOCK_KEY);
|
||||
write_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, pirq - 1, val);
|
||||
write_pc_conf_nybble(PC_CONF_FINALI_PCI_INTX_RT1, index, val);
|
||||
pc_conf_set(PC_CONF_FINALI_LOCK, 0);
|
||||
raw_spin_unlock_irqrestore(&pc_conf_lock, flags);
|
||||
return 1;
|
||||
|
|
@ -293,7 +390,7 @@ static int pirq_finali_set(struct pci_dev *router, struct pci_dev *dev,
|
|||
static int pirq_finali_lvl(struct pci_dev *router, struct pci_dev *dev,
|
||||
int pirq, int irq)
|
||||
{
|
||||
u8 mask = ~(1u << (pirq - 1));
|
||||
u8 mask = ~((pirq & 0xf0u) >> 4);
|
||||
unsigned long flags;
|
||||
u8 trig;
|
||||
|
||||
|
|
@ -579,6 +676,81 @@ static int pirq_cyrix_set(struct pci_dev *router, struct pci_dev *dev, int pirq,
|
|||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PIRQ routing for the SiS85C497 AT Bus Controller & Megacell (ATM)
|
||||
* ISA bridge used with the SiS 85C496/497 486 Green PC VESA/ISA/PCI
|
||||
* Chipset.
|
||||
*
|
||||
* There are four PCI INTx#-to-IRQ Link registers provided in the
|
||||
* SiS85C497 part of the peculiar combined 85C496/497 configuration
|
||||
* space decoded by the SiS85C496 PCI & CPU Memory Controller (PCM)
|
||||
* host bridge, at 0xc0/0xc1/0xc2/0xc3 respectively for the PCI INT
|
||||
* A/B/C/D lines. Bit 7 enables the respective link if set and bits
|
||||
* 3:0 select the 8259A IRQ line as follows:
|
||||
*
|
||||
* 0000 : Reserved
|
||||
* 0001 : Reserved
|
||||
* 0010 : Reserved
|
||||
* 0011 : IRQ3
|
||||
* 0100 : IRQ4
|
||||
* 0101 : IRQ5
|
||||
* 0110 : IRQ6
|
||||
* 0111 : IRQ7
|
||||
* 1000 : Reserved
|
||||
* 1001 : IRQ9
|
||||
* 1010 : IRQ10
|
||||
* 1011 : IRQ11
|
||||
* 1100 : IRQ12
|
||||
* 1101 : Reserved
|
||||
* 1110 : IRQ14
|
||||
* 1111 : IRQ15
|
||||
*
|
||||
* We avoid using a reserved value for disabled links, hence the
|
||||
* choice of IRQ15 for that case.
|
||||
*
|
||||
* References:
|
||||
*
|
||||
* "486 Green PC VESA/ISA/PCI Chipset, SiS 85C496/497", Rev 3.0,
|
||||
* Silicon Integrated Systems Corp., July 1995
|
||||
*/
|
||||
|
||||
#define PCI_SIS497_INTA_TO_IRQ_LINK 0xc0u
|
||||
|
||||
#define PIRQ_SIS497_IRQ_MASK 0x0fu
|
||||
#define PIRQ_SIS497_IRQ_ENABLE 0x80u
|
||||
|
||||
static int pirq_sis497_get(struct pci_dev *router, struct pci_dev *dev,
|
||||
int pirq)
|
||||
{
|
||||
int reg;
|
||||
u8 x;
|
||||
|
||||
reg = pirq;
|
||||
if (reg >= 1 && reg <= 4)
|
||||
reg += PCI_SIS497_INTA_TO_IRQ_LINK - 1;
|
||||
|
||||
pci_read_config_byte(router, reg, &x);
|
||||
return (x & PIRQ_SIS497_IRQ_ENABLE) ? (x & PIRQ_SIS497_IRQ_MASK) : 0;
|
||||
}
|
||||
|
||||
static int pirq_sis497_set(struct pci_dev *router, struct pci_dev *dev,
|
||||
int pirq, int irq)
|
||||
{
|
||||
int reg;
|
||||
u8 x;
|
||||
|
||||
reg = pirq;
|
||||
if (reg >= 1 && reg <= 4)
|
||||
reg += PCI_SIS497_INTA_TO_IRQ_LINK - 1;
|
||||
|
||||
pci_read_config_byte(router, reg, &x);
|
||||
x &= ~(PIRQ_SIS497_IRQ_MASK | PIRQ_SIS497_IRQ_ENABLE);
|
||||
x |= irq ? (PIRQ_SIS497_IRQ_ENABLE | irq) : PIRQ_SIS497_IRQ_MASK;
|
||||
pci_write_config_byte(router, reg, x);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* PIRQ routing for SiS 85C503 router used in several SiS chipsets.
|
||||
* We have to deal with the following issues here:
|
||||
|
|
@ -640,11 +812,12 @@ static int pirq_cyrix_set(struct pci_dev *router, struct pci_dev *dev, int pirq,
|
|||
* bit 6-4 are probably unused, not like 5595
|
||||
*/
|
||||
|
||||
#define PIRQ_SIS_IRQ_MASK 0x0f
|
||||
#define PIRQ_SIS_IRQ_DISABLE 0x80
|
||||
#define PIRQ_SIS_USB_ENABLE 0x40
|
||||
#define PIRQ_SIS503_IRQ_MASK 0x0f
|
||||
#define PIRQ_SIS503_IRQ_DISABLE 0x80
|
||||
#define PIRQ_SIS503_USB_ENABLE 0x40
|
||||
|
||||
static int pirq_sis_get(struct pci_dev *router, struct pci_dev *dev, int pirq)
|
||||
static int pirq_sis503_get(struct pci_dev *router, struct pci_dev *dev,
|
||||
int pirq)
|
||||
{
|
||||
u8 x;
|
||||
int reg;
|
||||
|
|
@ -653,10 +826,11 @@ static int pirq_sis_get(struct pci_dev *router, struct pci_dev *dev, int pirq)
|
|||
if (reg >= 0x01 && reg <= 0x04)
|
||||
reg += 0x40;
|
||||
pci_read_config_byte(router, reg, &x);
|
||||
return (x & PIRQ_SIS_IRQ_DISABLE) ? 0 : (x & PIRQ_SIS_IRQ_MASK);
|
||||
return (x & PIRQ_SIS503_IRQ_DISABLE) ? 0 : (x & PIRQ_SIS503_IRQ_MASK);
|
||||
}
|
||||
|
||||
static int pirq_sis_set(struct pci_dev *router, struct pci_dev *dev, int pirq, int irq)
|
||||
static int pirq_sis503_set(struct pci_dev *router, struct pci_dev *dev,
|
||||
int pirq, int irq)
|
||||
{
|
||||
u8 x;
|
||||
int reg;
|
||||
|
|
@ -665,8 +839,8 @@ static int pirq_sis_set(struct pci_dev *router, struct pci_dev *dev, int pirq, i
|
|||
if (reg >= 0x01 && reg <= 0x04)
|
||||
reg += 0x40;
|
||||
pci_read_config_byte(router, reg, &x);
|
||||
x &= ~(PIRQ_SIS_IRQ_MASK | PIRQ_SIS_IRQ_DISABLE);
|
||||
x |= irq ? irq: PIRQ_SIS_IRQ_DISABLE;
|
||||
x &= ~(PIRQ_SIS503_IRQ_MASK | PIRQ_SIS503_IRQ_DISABLE);
|
||||
x |= irq ? irq : PIRQ_SIS503_IRQ_DISABLE;
|
||||
pci_write_config_byte(router, reg, x);
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -958,13 +1132,19 @@ static __init int serverworks_router_probe(struct irq_router *r,
|
|||
|
||||
static __init int sis_router_probe(struct irq_router *r, struct pci_dev *router, u16 device)
|
||||
{
|
||||
if (device != PCI_DEVICE_ID_SI_503)
|
||||
return 0;
|
||||
|
||||
r->name = "SIS";
|
||||
r->get = pirq_sis_get;
|
||||
r->set = pirq_sis_set;
|
||||
return 1;
|
||||
switch (device) {
|
||||
case PCI_DEVICE_ID_SI_496:
|
||||
r->name = "SiS85C497";
|
||||
r->get = pirq_sis497_get;
|
||||
r->set = pirq_sis497_set;
|
||||
return 1;
|
||||
case PCI_DEVICE_ID_SI_503:
|
||||
r->name = "SiS85C503";
|
||||
r->get = pirq_sis503_get;
|
||||
r->set = pirq_sis503_set;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static __init int cyrix_router_probe(struct irq_router *r, struct pci_dev *router, u16 device)
|
||||
|
|
@ -1084,10 +1264,32 @@ static struct pci_dev *pirq_router_dev;
|
|||
* chipset" ?
|
||||
*/
|
||||
|
||||
static bool __init pirq_try_router(struct irq_router *r,
|
||||
struct irq_routing_table *rt,
|
||||
struct pci_dev *dev)
|
||||
{
|
||||
struct irq_router_handler *h;
|
||||
|
||||
DBG(KERN_DEBUG "PCI: Trying IRQ router for [%04x:%04x]\n",
|
||||
dev->vendor, dev->device);
|
||||
|
||||
for (h = pirq_routers; h->vendor; h++) {
|
||||
/* First look for a router match */
|
||||
if (rt->rtr_vendor == h->vendor &&
|
||||
h->probe(r, dev, rt->rtr_device))
|
||||
return true;
|
||||
/* Fall back to a device match */
|
||||
if (dev->vendor == h->vendor &&
|
||||
h->probe(r, dev, dev->device))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void __init pirq_find_router(struct irq_router *r)
|
||||
{
|
||||
struct irq_routing_table *rt = pirq_table;
|
||||
struct irq_router_handler *h;
|
||||
struct pci_dev *dev;
|
||||
|
||||
#ifdef CONFIG_PCI_BIOS
|
||||
if (!rt->signature) {
|
||||
|
|
@ -1106,50 +1308,94 @@ static void __init pirq_find_router(struct irq_router *r)
|
|||
DBG(KERN_DEBUG "PCI: Attempting to find IRQ router for [%04x:%04x]\n",
|
||||
rt->rtr_vendor, rt->rtr_device);
|
||||
|
||||
pirq_router_dev = pci_get_domain_bus_and_slot(0, rt->rtr_bus,
|
||||
rt->rtr_devfn);
|
||||
if (!pirq_router_dev) {
|
||||
DBG(KERN_DEBUG "PCI: Interrupt router not found at "
|
||||
"%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);
|
||||
return;
|
||||
/* Use any vendor:device provided by the routing table or try all. */
|
||||
if (rt->rtr_vendor) {
|
||||
dev = pci_get_domain_bus_and_slot(0, rt->rtr_bus,
|
||||
rt->rtr_devfn);
|
||||
if (dev && pirq_try_router(r, rt, dev))
|
||||
pirq_router_dev = dev;
|
||||
} else {
|
||||
dev = NULL;
|
||||
for_each_pci_dev(dev) {
|
||||
if (pirq_try_router(r, rt, dev)) {
|
||||
pirq_router_dev = dev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (h = pirq_routers; h->vendor; h++) {
|
||||
/* First look for a router match */
|
||||
if (rt->rtr_vendor == h->vendor &&
|
||||
h->probe(r, pirq_router_dev, rt->rtr_device))
|
||||
break;
|
||||
/* Fall back to a device match */
|
||||
if (pirq_router_dev->vendor == h->vendor &&
|
||||
h->probe(r, pirq_router_dev, pirq_router_dev->device))
|
||||
break;
|
||||
}
|
||||
dev_info(&pirq_router_dev->dev, "%s IRQ router [%04x:%04x]\n",
|
||||
pirq_router.name,
|
||||
pirq_router_dev->vendor, pirq_router_dev->device);
|
||||
if (pirq_router_dev)
|
||||
dev_info(&pirq_router_dev->dev, "%s IRQ router [%04x:%04x]\n",
|
||||
pirq_router.name,
|
||||
pirq_router_dev->vendor, pirq_router_dev->device);
|
||||
else
|
||||
DBG(KERN_DEBUG "PCI: Interrupt router not found at "
|
||||
"%02x:%02x\n", rt->rtr_bus, rt->rtr_devfn);
|
||||
|
||||
/* The device remains referenced for the kernel lifetime */
|
||||
}
|
||||
|
||||
static struct irq_info *pirq_get_info(struct pci_dev *dev)
|
||||
/*
|
||||
* We're supposed to match on the PCI device only and not the function,
|
||||
* but some BIOSes build their tables with the PCI function included
|
||||
* for motherboard devices, so if a complete match is found, then give
|
||||
* it precedence over a slot match.
|
||||
*/
|
||||
static struct irq_info *pirq_get_dev_info(struct pci_dev *dev)
|
||||
{
|
||||
struct irq_routing_table *rt = pirq_table;
|
||||
int entries = (rt->size - sizeof(struct irq_routing_table)) /
|
||||
sizeof(struct irq_info);
|
||||
struct irq_info *slotinfo = NULL;
|
||||
struct irq_info *info;
|
||||
|
||||
for (info = rt->slots; entries--; info++)
|
||||
if (info->bus == dev->bus->number &&
|
||||
PCI_SLOT(info->devfn) == PCI_SLOT(dev->devfn))
|
||||
return info;
|
||||
return NULL;
|
||||
if (info->bus == dev->bus->number) {
|
||||
if (info->devfn == dev->devfn)
|
||||
return info;
|
||||
if (!slotinfo &&
|
||||
PCI_SLOT(info->devfn) == PCI_SLOT(dev->devfn))
|
||||
slotinfo = info;
|
||||
}
|
||||
return slotinfo;
|
||||
}
|
||||
|
||||
/*
|
||||
* Buses behind bridges are typically not listed in the PIRQ routing table.
|
||||
* Do the usual dance then and walk the tree of bridges up adjusting the
|
||||
* pin number accordingly on the way until the originating root bus device
|
||||
* has been reached and then use its routing information.
|
||||
*/
|
||||
static struct irq_info *pirq_get_info(struct pci_dev *dev, u8 *pin)
|
||||
{
|
||||
struct pci_dev *temp_dev = dev;
|
||||
struct irq_info *info;
|
||||
u8 temp_pin = *pin;
|
||||
u8 dpin = temp_pin;
|
||||
|
||||
info = pirq_get_dev_info(dev);
|
||||
while (!info && temp_dev->bus->parent) {
|
||||
struct pci_dev *bridge = temp_dev->bus->self;
|
||||
|
||||
temp_pin = pci_swizzle_interrupt_pin(temp_dev, temp_pin);
|
||||
info = pirq_get_dev_info(bridge);
|
||||
if (info)
|
||||
dev_warn(&dev->dev,
|
||||
"using bridge %s INT %c to get INT %c\n",
|
||||
pci_name(bridge),
|
||||
'A' + temp_pin - 1, 'A' + dpin - 1);
|
||||
|
||||
temp_dev = bridge;
|
||||
}
|
||||
*pin = temp_pin;
|
||||
return info;
|
||||
}
|
||||
|
||||
static int pcibios_lookup_irq(struct pci_dev *dev, int assign)
|
||||
{
|
||||
u8 pin;
|
||||
struct irq_info *info;
|
||||
int i, pirq, newirq;
|
||||
u8 dpin, pin;
|
||||
int irq = 0;
|
||||
u32 mask;
|
||||
struct irq_router *r = &pirq_router;
|
||||
|
|
@ -1157,8 +1403,8 @@ static int pcibios_lookup_irq(struct pci_dev *dev, int assign)
|
|||
char *msg = NULL;
|
||||
|
||||
/* Find IRQ pin */
|
||||
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
|
||||
if (!pin) {
|
||||
pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &dpin);
|
||||
if (!dpin) {
|
||||
dev_dbg(&dev->dev, "no interrupt pin\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1171,20 +1417,21 @@ static int pcibios_lookup_irq(struct pci_dev *dev, int assign)
|
|||
if (!pirq_table)
|
||||
return 0;
|
||||
|
||||
info = pirq_get_info(dev);
|
||||
pin = dpin;
|
||||
info = pirq_get_info(dev, &pin);
|
||||
if (!info) {
|
||||
dev_dbg(&dev->dev, "PCI INT %c not found in routing table\n",
|
||||
'A' + pin - 1);
|
||||
'A' + dpin - 1);
|
||||
return 0;
|
||||
}
|
||||
pirq = info->irq[pin - 1].link;
|
||||
mask = info->irq[pin - 1].bitmap;
|
||||
if (!pirq) {
|
||||
dev_dbg(&dev->dev, "PCI INT %c not routed\n", 'A' + pin - 1);
|
||||
dev_dbg(&dev->dev, "PCI INT %c not routed\n", 'A' + dpin - 1);
|
||||
return 0;
|
||||
}
|
||||
dev_dbg(&dev->dev, "PCI INT %c -> PIRQ %02x, mask %04x, excl %04x",
|
||||
'A' + pin - 1, pirq, mask, pirq_table->exclusive_irqs);
|
||||
'A' + dpin - 1, pirq, mask, pirq_table->exclusive_irqs);
|
||||
mask &= pcibios_irq_mask;
|
||||
|
||||
/* Work around broken HP Pavilion Notebooks which assign USB to
|
||||
|
|
@ -1226,7 +1473,7 @@ static int pcibios_lookup_irq(struct pci_dev *dev, int assign)
|
|||
newirq = i;
|
||||
}
|
||||
}
|
||||
dev_dbg(&dev->dev, "PCI INT %c -> newirq %d", 'A' + pin - 1, newirq);
|
||||
dev_dbg(&dev->dev, "PCI INT %c -> newirq %d", 'A' + dpin - 1, newirq);
|
||||
|
||||
/* Check if it is hardcoded */
|
||||
if ((pirq & 0xf0) == 0xf0) {
|
||||
|
|
@ -1260,15 +1507,17 @@ static int pcibios_lookup_irq(struct pci_dev *dev, int assign)
|
|||
return 0;
|
||||
}
|
||||
}
|
||||
dev_info(&dev->dev, "%s PCI INT %c -> IRQ %d\n", msg, 'A' + pin - 1, irq);
|
||||
dev_info(&dev->dev, "%s PCI INT %c -> IRQ %d\n",
|
||||
msg, 'A' + dpin - 1, irq);
|
||||
|
||||
/* Update IRQ for all devices with the same pirq value */
|
||||
for_each_pci_dev(dev2) {
|
||||
pci_read_config_byte(dev2, PCI_INTERRUPT_PIN, &pin);
|
||||
if (!pin)
|
||||
pci_read_config_byte(dev2, PCI_INTERRUPT_PIN, &dpin);
|
||||
if (!dpin)
|
||||
continue;
|
||||
|
||||
info = pirq_get_info(dev2);
|
||||
pin = dpin;
|
||||
info = pirq_get_info(dev2, &pin);
|
||||
if (!info)
|
||||
continue;
|
||||
if (info->irq[pin - 1].link == pirq) {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ void __init reserve_real_mode(void)
|
|||
memblock_reserve(0, SZ_1M);
|
||||
}
|
||||
|
||||
static void sme_sev_setup_real_mode(struct trampoline_header *th)
|
||||
static void __init sme_sev_setup_real_mode(struct trampoline_header *th)
|
||||
{
|
||||
#ifdef CONFIG_AMD_MEM_ENCRYPT
|
||||
if (cc_platform_has(CC_ATTR_HOST_MEM_ENCRYPT))
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ config EDAC_DECODE_MCE
|
|||
config EDAC_GHES
|
||||
bool "Output ACPI APEI/GHES BIOS detected errors via EDAC"
|
||||
depends on ACPI_APEI_GHES && (EDAC=y)
|
||||
select UEFI_CPER
|
||||
help
|
||||
Not all machines support hardware-driven error report. Some of those
|
||||
provide a BIOS-driven error report mechanism via ACPI, using the
|
||||
|
|
@ -484,7 +485,7 @@ config EDAC_ARMADA_XP
|
|||
|
||||
config EDAC_SYNOPSYS
|
||||
tristate "Synopsys DDR Memory Controller"
|
||||
depends on ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA
|
||||
depends on ARCH_ZYNQ || ARCH_ZYNQMP || ARCH_INTEL_SOCFPGA || ARCH_MXC
|
||||
help
|
||||
Support for error detection and correction on the Synopsys DDR
|
||||
memory controller.
|
||||
|
|
|
|||
|
|
@ -286,17 +286,10 @@ static int axp_mc_probe(struct platform_device *pdev)
|
|||
struct edac_mc_layer layers[1];
|
||||
const struct of_device_id *id;
|
||||
struct mem_ctl_info *mci;
|
||||
struct resource *r;
|
||||
void __iomem *base;
|
||||
uint32_t config;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r) {
|
||||
dev_err(&pdev->dev, "Unable to get mem resource\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
base = devm_ioremap_resource(&pdev->dev, r);
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(base)) {
|
||||
dev_err(&pdev->dev, "Unable to map regs\n");
|
||||
return PTR_ERR(base);
|
||||
|
|
@ -516,15 +509,8 @@ static int aurora_l2_probe(struct platform_device *pdev)
|
|||
const struct of_device_id *id;
|
||||
uint32_t l2x0_aux_ctrl;
|
||||
void __iomem *base;
|
||||
struct resource *r;
|
||||
|
||||
r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!r) {
|
||||
dev_err(&pdev->dev, "Unable to get mem resource\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
base = devm_ioremap_resource(&pdev->dev, r);
|
||||
base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(base)) {
|
||||
dev_err(&pdev->dev, "Unable to map regs\n");
|
||||
return PTR_ERR(base);
|
||||
|
|
|
|||
|
|
@ -489,7 +489,7 @@ static int dmc520_edac_probe(struct platform_device *pdev)
|
|||
dev = &pdev->dev;
|
||||
|
||||
for (idx = 0; idx < NUMBER_OF_IRQS; idx++) {
|
||||
irq = platform_get_irq_byname(pdev, dmc520_irq_configs[idx].name);
|
||||
irq = platform_get_irq_byname_optional(pdev, dmc520_irq_configs[idx].name);
|
||||
irqs[idx] = irq;
|
||||
masks[idx] = dmc520_irq_configs[idx].mask;
|
||||
if (irq >= 0) {
|
||||
|
|
|
|||
|
|
@ -47,99 +47,67 @@ static void edac_device_dump_device(struct edac_device_ctl_info *edac_dev)
|
|||
}
|
||||
#endif /* CONFIG_EDAC_DEBUG */
|
||||
|
||||
struct edac_device_ctl_info *edac_device_alloc_ctl_info(
|
||||
unsigned sz_private,
|
||||
char *edac_device_name, unsigned nr_instances,
|
||||
char *edac_block_name, unsigned nr_blocks,
|
||||
unsigned offset_value, /* zero, 1, or other based offset */
|
||||
struct edac_dev_sysfs_block_attribute *attrib_spec, unsigned nr_attrib,
|
||||
int device_index)
|
||||
/*
|
||||
* @off_val: zero, 1, or other based offset
|
||||
*/
|
||||
struct edac_device_ctl_info *
|
||||
edac_device_alloc_ctl_info(unsigned pvt_sz, char *dev_name, unsigned nr_instances,
|
||||
char *blk_name, unsigned nr_blocks, unsigned off_val,
|
||||
struct edac_dev_sysfs_block_attribute *attrib_spec,
|
||||
unsigned nr_attrib, int device_index)
|
||||
{
|
||||
struct edac_device_ctl_info *dev_ctl;
|
||||
struct edac_device_instance *dev_inst, *inst;
|
||||
struct edac_device_block *dev_blk, *blk_p, *blk;
|
||||
struct edac_dev_sysfs_block_attribute *dev_attrib, *attrib_p, *attrib;
|
||||
unsigned total_size;
|
||||
unsigned count;
|
||||
struct edac_device_block *dev_blk, *blk_p, *blk;
|
||||
struct edac_device_instance *dev_inst, *inst;
|
||||
struct edac_device_ctl_info *dev_ctl;
|
||||
unsigned instance, block, attr;
|
||||
void *pvt, *p;
|
||||
void *pvt;
|
||||
int err;
|
||||
|
||||
edac_dbg(4, "instances=%d blocks=%d\n", nr_instances, nr_blocks);
|
||||
|
||||
/* Calculate the size of memory we need to allocate AND
|
||||
* determine the offsets of the various item arrays
|
||||
* (instance,block,attrib) from the start of an allocated structure.
|
||||
* We want the alignment of each item (instance,block,attrib)
|
||||
* to be at least as stringent as what the compiler would
|
||||
* provide if we could simply hardcode everything into a single struct.
|
||||
*/
|
||||
p = NULL;
|
||||
dev_ctl = edac_align_ptr(&p, sizeof(*dev_ctl), 1);
|
||||
|
||||
/* Calc the 'end' offset past end of ONE ctl_info structure
|
||||
* which will become the start of the 'instance' array
|
||||
*/
|
||||
dev_inst = edac_align_ptr(&p, sizeof(*dev_inst), nr_instances);
|
||||
|
||||
/* Calc the 'end' offset past the instance array within the ctl_info
|
||||
* which will become the start of the block array
|
||||
*/
|
||||
count = nr_instances * nr_blocks;
|
||||
dev_blk = edac_align_ptr(&p, sizeof(*dev_blk), count);
|
||||
|
||||
/* Calc the 'end' offset past the dev_blk array
|
||||
* which will become the start of the attrib array, if any.
|
||||
*/
|
||||
/* calc how many nr_attrib we need */
|
||||
if (nr_attrib > 0)
|
||||
count *= nr_attrib;
|
||||
dev_attrib = edac_align_ptr(&p, sizeof(*dev_attrib), count);
|
||||
|
||||
/* Calc the 'end' offset past the attributes array */
|
||||
pvt = edac_align_ptr(&p, sz_private, 1);
|
||||
|
||||
/* 'pvt' now points to where the private data area is.
|
||||
* At this point 'pvt' (like dev_inst,dev_blk and dev_attrib)
|
||||
* is baselined at ZERO
|
||||
*/
|
||||
total_size = ((unsigned long)pvt) + sz_private;
|
||||
|
||||
/* Allocate the amount of memory for the set of control structures */
|
||||
dev_ctl = kzalloc(total_size, GFP_KERNEL);
|
||||
if (dev_ctl == NULL)
|
||||
dev_ctl = kzalloc(sizeof(struct edac_device_ctl_info), GFP_KERNEL);
|
||||
if (!dev_ctl)
|
||||
return NULL;
|
||||
|
||||
/* Adjust pointers so they point within the actual memory we
|
||||
* just allocated rather than an imaginary chunk of memory
|
||||
* located at address 0.
|
||||
* 'dev_ctl' points to REAL memory, while the others are
|
||||
* ZERO based and thus need to be adjusted to point within
|
||||
* the allocated memory.
|
||||
*/
|
||||
dev_inst = (struct edac_device_instance *)
|
||||
(((char *)dev_ctl) + ((unsigned long)dev_inst));
|
||||
dev_blk = (struct edac_device_block *)
|
||||
(((char *)dev_ctl) + ((unsigned long)dev_blk));
|
||||
dev_attrib = (struct edac_dev_sysfs_block_attribute *)
|
||||
(((char *)dev_ctl) + ((unsigned long)dev_attrib));
|
||||
pvt = sz_private ? (((char *)dev_ctl) + ((unsigned long)pvt)) : NULL;
|
||||
dev_inst = kcalloc(nr_instances, sizeof(struct edac_device_instance), GFP_KERNEL);
|
||||
if (!dev_inst)
|
||||
goto free;
|
||||
|
||||
/* Begin storing the information into the control info structure */
|
||||
dev_ctl->dev_idx = device_index;
|
||||
dev_ctl->nr_instances = nr_instances;
|
||||
dev_ctl->instances = dev_inst;
|
||||
dev_ctl->pvt_info = pvt;
|
||||
|
||||
dev_blk = kcalloc(nr_instances * nr_blocks, sizeof(struct edac_device_block), GFP_KERNEL);
|
||||
if (!dev_blk)
|
||||
goto free;
|
||||
|
||||
dev_ctl->blocks = dev_blk;
|
||||
|
||||
if (nr_attrib) {
|
||||
dev_attrib = kcalloc(nr_attrib, sizeof(struct edac_dev_sysfs_block_attribute),
|
||||
GFP_KERNEL);
|
||||
if (!dev_attrib)
|
||||
goto free;
|
||||
|
||||
dev_ctl->attribs = dev_attrib;
|
||||
}
|
||||
|
||||
if (pvt_sz) {
|
||||
pvt = kzalloc(pvt_sz, GFP_KERNEL);
|
||||
if (!pvt)
|
||||
goto free;
|
||||
|
||||
dev_ctl->pvt_info = pvt;
|
||||
}
|
||||
|
||||
dev_ctl->dev_idx = device_index;
|
||||
dev_ctl->nr_instances = nr_instances;
|
||||
|
||||
/* Default logging of CEs and UEs */
|
||||
dev_ctl->log_ce = 1;
|
||||
dev_ctl->log_ue = 1;
|
||||
|
||||
/* Name of this edac device */
|
||||
snprintf(dev_ctl->name,sizeof(dev_ctl->name),"%s",edac_device_name);
|
||||
|
||||
edac_dbg(4, "edac_dev=%p next after end=%p\n",
|
||||
dev_ctl, pvt + sz_private);
|
||||
snprintf(dev_ctl->name, sizeof(dev_ctl->name),"%s", dev_name);
|
||||
|
||||
/* Initialize every Instance */
|
||||
for (instance = 0; instance < nr_instances; instance++) {
|
||||
|
|
@ -150,15 +118,14 @@ struct edac_device_ctl_info *edac_device_alloc_ctl_info(
|
|||
inst->blocks = blk_p;
|
||||
|
||||
/* name of this instance */
|
||||
snprintf(inst->name, sizeof(inst->name),
|
||||
"%s%u", edac_device_name, instance);
|
||||
snprintf(inst->name, sizeof(inst->name), "%s%u", dev_name, instance);
|
||||
|
||||
/* Initialize every block in each instance */
|
||||
for (block = 0; block < nr_blocks; block++) {
|
||||
blk = &blk_p[block];
|
||||
blk->instance = inst;
|
||||
snprintf(blk->name, sizeof(blk->name),
|
||||
"%s%d", edac_block_name, block+offset_value);
|
||||
"%s%d", blk_name, block + off_val);
|
||||
|
||||
edac_dbg(4, "instance=%d inst_p=%p block=#%d block_p=%p name='%s'\n",
|
||||
instance, inst, block, blk, blk->name);
|
||||
|
|
@ -210,10 +177,8 @@ struct edac_device_ctl_info *edac_device_alloc_ctl_info(
|
|||
* Initialize the 'root' kobj for the edac_device controller
|
||||
*/
|
||||
err = edac_device_register_sysfs_main_kobj(dev_ctl);
|
||||
if (err) {
|
||||
kfree(dev_ctl);
|
||||
return NULL;
|
||||
}
|
||||
if (err)
|
||||
goto free;
|
||||
|
||||
/* at this point, the root kobj is valid, and in order to
|
||||
* 'free' the object, then the function:
|
||||
|
|
@ -223,6 +188,11 @@ struct edac_device_ctl_info *edac_device_alloc_ctl_info(
|
|||
*/
|
||||
|
||||
return dev_ctl;
|
||||
|
||||
free:
|
||||
__edac_device_free_ctl_info(dev_ctl);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(edac_device_alloc_ctl_info);
|
||||
|
||||
|
|
|
|||
|
|
@ -216,6 +216,8 @@ struct edac_device_ctl_info {
|
|||
*/
|
||||
u32 nr_instances;
|
||||
struct edac_device_instance *instances;
|
||||
struct edac_device_block *blocks;
|
||||
struct edac_dev_sysfs_block_attribute *attribs;
|
||||
|
||||
/* Event counters for the this whole EDAC Device */
|
||||
struct edac_device_counter counters;
|
||||
|
|
@ -348,4 +350,16 @@ edac_device_handle_ue(struct edac_device_ctl_info *edac_dev, int inst_nr,
|
|||
*/
|
||||
extern int edac_device_alloc_index(void);
|
||||
extern const char *edac_layer_name[];
|
||||
|
||||
/* Free the actual struct */
|
||||
static inline void __edac_device_free_ctl_info(struct edac_device_ctl_info *ci)
|
||||
{
|
||||
if (ci) {
|
||||
kfree(ci->pvt_info);
|
||||
kfree(ci->attribs);
|
||||
kfree(ci->blocks);
|
||||
kfree(ci->instances);
|
||||
kfree(ci);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -208,10 +208,7 @@ static void edac_device_ctrl_master_release(struct kobject *kobj)
|
|||
/* decrement the EDAC CORE module ref count */
|
||||
module_put(edac_dev->owner);
|
||||
|
||||
/* free the control struct containing the 'main' kobj
|
||||
* passed in to this routine
|
||||
*/
|
||||
kfree(edac_dev);
|
||||
__edac_device_free_ctl_info(edac_dev);
|
||||
}
|
||||
|
||||
/* ktype for the main (master) kobject */
|
||||
|
|
|
|||
|
|
@ -170,61 +170,6 @@ const char * const edac_mem_types[] = {
|
|||
};
|
||||
EXPORT_SYMBOL_GPL(edac_mem_types);
|
||||
|
||||
/**
|
||||
* edac_align_ptr - Prepares the pointer offsets for a single-shot allocation
|
||||
* @p: pointer to a pointer with the memory offset to be used. At
|
||||
* return, this will be incremented to point to the next offset
|
||||
* @size: Size of the data structure to be reserved
|
||||
* @n_elems: Number of elements that should be reserved
|
||||
*
|
||||
* If 'size' is a constant, the compiler will optimize this whole function
|
||||
* down to either a no-op or the addition of a constant to the value of '*p'.
|
||||
*
|
||||
* The 'p' pointer is absolutely needed to keep the proper advancing
|
||||
* further in memory to the proper offsets when allocating the struct along
|
||||
* with its embedded structs, as edac_device_alloc_ctl_info() does it
|
||||
* above, for example.
|
||||
*
|
||||
* At return, the pointer 'p' will be incremented to be used on a next call
|
||||
* to this function.
|
||||
*/
|
||||
void *edac_align_ptr(void **p, unsigned int size, int n_elems)
|
||||
{
|
||||
unsigned int align, r;
|
||||
void *ptr = *p;
|
||||
|
||||
*p += size * n_elems;
|
||||
|
||||
/*
|
||||
* 'p' can possibly be an unaligned item X such that sizeof(X) is
|
||||
* 'size'. Adjust 'p' so that its alignment is at least as
|
||||
* stringent as what the compiler would provide for X and return
|
||||
* the aligned result.
|
||||
* Here we assume that the alignment of a "long long" is the most
|
||||
* stringent alignment that the compiler will ever provide by default.
|
||||
* As far as I know, this is a reasonable assumption.
|
||||
*/
|
||||
if (size > sizeof(long))
|
||||
align = sizeof(long long);
|
||||
else if (size > sizeof(int))
|
||||
align = sizeof(long);
|
||||
else if (size > sizeof(short))
|
||||
align = sizeof(int);
|
||||
else if (size > sizeof(char))
|
||||
align = sizeof(short);
|
||||
else
|
||||
return ptr;
|
||||
|
||||
r = (unsigned long)ptr % align;
|
||||
|
||||
if (r == 0)
|
||||
return ptr;
|
||||
|
||||
*p += align - r;
|
||||
|
||||
return (void *)(((unsigned long)ptr) + align - r);
|
||||
}
|
||||
|
||||
static void _edac_mc_free(struct mem_ctl_info *mci)
|
||||
{
|
||||
put_device(&mci->dev);
|
||||
|
|
@ -257,6 +202,8 @@ static void mci_release(struct device *dev)
|
|||
}
|
||||
kfree(mci->csrows);
|
||||
}
|
||||
kfree(mci->pvt_info);
|
||||
kfree(mci->layers);
|
||||
kfree(mci);
|
||||
}
|
||||
|
||||
|
|
@ -392,9 +339,8 @@ struct mem_ctl_info *edac_mc_alloc(unsigned int mc_num,
|
|||
{
|
||||
struct mem_ctl_info *mci;
|
||||
struct edac_mc_layer *layer;
|
||||
unsigned int idx, size, tot_dimms = 1;
|
||||
unsigned int idx, tot_dimms = 1;
|
||||
unsigned int tot_csrows = 1, tot_channels = 1;
|
||||
void *pvt, *ptr = NULL;
|
||||
bool per_rank = false;
|
||||
|
||||
if (WARN_ON(n_layers > EDAC_MAX_LAYERS || n_layers == 0))
|
||||
|
|
@ -416,41 +362,25 @@ struct mem_ctl_info *edac_mc_alloc(unsigned int mc_num,
|
|||
per_rank = true;
|
||||
}
|
||||
|
||||
/* Figure out the offsets of the various items from the start of an mc
|
||||
* structure. We want the alignment of each item to be at least as
|
||||
* stringent as what the compiler would provide if we could simply
|
||||
* hardcode everything into a single struct.
|
||||
*/
|
||||
mci = edac_align_ptr(&ptr, sizeof(*mci), 1);
|
||||
layer = edac_align_ptr(&ptr, sizeof(*layer), n_layers);
|
||||
pvt = edac_align_ptr(&ptr, sz_pvt, 1);
|
||||
size = ((unsigned long)pvt) + sz_pvt;
|
||||
|
||||
edac_dbg(1, "allocating %u bytes for mci data (%d %s, %d csrows/channels)\n",
|
||||
size,
|
||||
tot_dimms,
|
||||
per_rank ? "ranks" : "dimms",
|
||||
tot_csrows * tot_channels);
|
||||
|
||||
mci = kzalloc(size, GFP_KERNEL);
|
||||
if (mci == NULL)
|
||||
mci = kzalloc(sizeof(struct mem_ctl_info), GFP_KERNEL);
|
||||
if (!mci)
|
||||
return NULL;
|
||||
|
||||
mci->layers = kcalloc(n_layers, sizeof(struct edac_mc_layer), GFP_KERNEL);
|
||||
if (!mci->layers)
|
||||
goto error;
|
||||
|
||||
mci->pvt_info = kzalloc(sz_pvt, GFP_KERNEL);
|
||||
if (!mci->pvt_info)
|
||||
goto error;
|
||||
|
||||
mci->dev.release = mci_release;
|
||||
device_initialize(&mci->dev);
|
||||
|
||||
/* Adjust pointers so they point within the memory we just allocated
|
||||
* rather than an imaginary chunk of memory located at address 0.
|
||||
*/
|
||||
layer = (struct edac_mc_layer *)(((char *)mci) + ((unsigned long)layer));
|
||||
pvt = sz_pvt ? (((char *)mci) + ((unsigned long)pvt)) : NULL;
|
||||
|
||||
/* setup index and various internal pointers */
|
||||
mci->mc_idx = mc_num;
|
||||
mci->tot_dimms = tot_dimms;
|
||||
mci->pvt_info = pvt;
|
||||
mci->n_layers = n_layers;
|
||||
mci->layers = layer;
|
||||
memcpy(mci->layers, layers, sizeof(*layer) * n_layers);
|
||||
mci->nr_csrows = tot_csrows;
|
||||
mci->num_cschannel = tot_channels;
|
||||
|
|
|
|||
|
|
@ -59,8 +59,6 @@ extern void edac_device_reset_delay_period(struct edac_device_ctl_info
|
|||
*edac_dev, unsigned long value);
|
||||
extern void edac_mc_reset_delay_period(unsigned long value);
|
||||
|
||||
extern void *edac_align_ptr(void **p, unsigned size, int n_elems);
|
||||
|
||||
/*
|
||||
* EDAC debugfs functions
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -29,32 +29,31 @@ static LIST_HEAD(edac_pci_list);
|
|||
static atomic_t pci_indexes = ATOMIC_INIT(0);
|
||||
|
||||
struct edac_pci_ctl_info *edac_pci_alloc_ctl_info(unsigned int sz_pvt,
|
||||
const char *edac_pci_name)
|
||||
const char *edac_pci_name)
|
||||
{
|
||||
struct edac_pci_ctl_info *pci;
|
||||
void *p = NULL, *pvt;
|
||||
unsigned int size;
|
||||
|
||||
edac_dbg(1, "\n");
|
||||
|
||||
pci = edac_align_ptr(&p, sizeof(*pci), 1);
|
||||
pvt = edac_align_ptr(&p, 1, sz_pvt);
|
||||
size = ((unsigned long)pvt) + sz_pvt;
|
||||
|
||||
/* Alloc the needed control struct memory */
|
||||
pci = kzalloc(size, GFP_KERNEL);
|
||||
if (pci == NULL)
|
||||
pci = kzalloc(sizeof(struct edac_pci_ctl_info), GFP_KERNEL);
|
||||
if (!pci)
|
||||
return NULL;
|
||||
|
||||
/* Now much private space */
|
||||
pvt = sz_pvt ? ((char *)pci) + ((unsigned long)pvt) : NULL;
|
||||
if (sz_pvt) {
|
||||
pci->pvt_info = kzalloc(sz_pvt, GFP_KERNEL);
|
||||
if (!pci->pvt_info)
|
||||
goto free;
|
||||
}
|
||||
|
||||
pci->pvt_info = pvt;
|
||||
pci->op_state = OP_ALLOC;
|
||||
|
||||
snprintf(pci->name, strlen(edac_pci_name) + 1, "%s", edac_pci_name);
|
||||
|
||||
return pci;
|
||||
|
||||
free:
|
||||
kfree(pci);
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(edac_pci_alloc_ctl_info);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@
|
|||
#include "edac_module.h"
|
||||
#include <ras/ras_event.h>
|
||||
|
||||
#define OTHER_DETAIL_LEN 400
|
||||
|
||||
struct ghes_pvt {
|
||||
struct mem_ctl_info *mci;
|
||||
|
||||
/* Buffers for the error handling routine */
|
||||
char other_detail[400];
|
||||
char other_detail[OTHER_DETAIL_LEN];
|
||||
char msg[80];
|
||||
};
|
||||
|
||||
|
|
@ -36,7 +38,7 @@ static struct ghes_pvt *ghes_pvt;
|
|||
* This driver's representation of the system hardware, as collected
|
||||
* from DMI.
|
||||
*/
|
||||
struct ghes_hw_desc {
|
||||
static struct ghes_hw_desc {
|
||||
int num_dimms;
|
||||
struct dimm_info *dimms;
|
||||
} ghes_hw;
|
||||
|
|
@ -235,8 +237,34 @@ static void ghes_scan_system(void)
|
|||
system_scanned = true;
|
||||
}
|
||||
|
||||
static int print_mem_error_other_detail(const struct cper_sec_mem_err *mem, char *msg,
|
||||
const char *location, unsigned int len)
|
||||
{
|
||||
u32 n;
|
||||
|
||||
if (!msg)
|
||||
return 0;
|
||||
|
||||
n = 0;
|
||||
len -= 1;
|
||||
|
||||
n += scnprintf(msg + n, len - n, "APEI location: %s ", location);
|
||||
|
||||
if (!(mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS))
|
||||
goto out;
|
||||
|
||||
n += scnprintf(msg + n, len - n, "status(0x%016llx): ", mem->error_status);
|
||||
n += scnprintf(msg + n, len - n, "%s ", cper_mem_err_status_str(mem->error_status));
|
||||
|
||||
out:
|
||||
msg[n] = '\0';
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void ghes_edac_report_mem_error(int sev, struct cper_sec_mem_err *mem_err)
|
||||
{
|
||||
struct cper_mem_err_compact cmem;
|
||||
struct edac_raw_error_desc *e;
|
||||
struct mem_ctl_info *mci;
|
||||
struct ghes_pvt *pvt;
|
||||
|
|
@ -292,60 +320,10 @@ void ghes_edac_report_mem_error(int sev, struct cper_sec_mem_err *mem_err)
|
|||
|
||||
/* Error type, mapped on e->msg */
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_ERROR_TYPE) {
|
||||
u8 etype = mem_err->error_type;
|
||||
|
||||
p = pvt->msg;
|
||||
switch (mem_err->error_type) {
|
||||
case 0:
|
||||
p += sprintf(p, "Unknown");
|
||||
break;
|
||||
case 1:
|
||||
p += sprintf(p, "No error");
|
||||
break;
|
||||
case 2:
|
||||
p += sprintf(p, "Single-bit ECC");
|
||||
break;
|
||||
case 3:
|
||||
p += sprintf(p, "Multi-bit ECC");
|
||||
break;
|
||||
case 4:
|
||||
p += sprintf(p, "Single-symbol ChipKill ECC");
|
||||
break;
|
||||
case 5:
|
||||
p += sprintf(p, "Multi-symbol ChipKill ECC");
|
||||
break;
|
||||
case 6:
|
||||
p += sprintf(p, "Master abort");
|
||||
break;
|
||||
case 7:
|
||||
p += sprintf(p, "Target abort");
|
||||
break;
|
||||
case 8:
|
||||
p += sprintf(p, "Parity Error");
|
||||
break;
|
||||
case 9:
|
||||
p += sprintf(p, "Watchdog timeout");
|
||||
break;
|
||||
case 10:
|
||||
p += sprintf(p, "Invalid address");
|
||||
break;
|
||||
case 11:
|
||||
p += sprintf(p, "Mirror Broken");
|
||||
break;
|
||||
case 12:
|
||||
p += sprintf(p, "Memory Sparing");
|
||||
break;
|
||||
case 13:
|
||||
p += sprintf(p, "Scrub corrected error");
|
||||
break;
|
||||
case 14:
|
||||
p += sprintf(p, "Scrub uncorrected error");
|
||||
break;
|
||||
case 15:
|
||||
p += sprintf(p, "Physical Memory Map-out event");
|
||||
break;
|
||||
default:
|
||||
p += sprintf(p, "reserved error (%d)",
|
||||
mem_err->error_type);
|
||||
}
|
||||
p += snprintf(p, sizeof(pvt->msg), "%s", cper_mem_err_type_str(etype));
|
||||
} else {
|
||||
strcpy(pvt->msg, "unknown error");
|
||||
}
|
||||
|
|
@ -362,52 +340,19 @@ void ghes_edac_report_mem_error(int sev, struct cper_sec_mem_err *mem_err)
|
|||
|
||||
/* Memory error location, mapped on e->location */
|
||||
p = e->location;
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_NODE)
|
||||
p += sprintf(p, "node:%d ", mem_err->node);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_CARD)
|
||||
p += sprintf(p, "card:%d ", mem_err->card);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_MODULE)
|
||||
p += sprintf(p, "module:%d ", mem_err->module);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_RANK_NUMBER)
|
||||
p += sprintf(p, "rank:%d ", mem_err->rank);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_BANK)
|
||||
p += sprintf(p, "bank:%d ", mem_err->bank);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_BANK_GROUP)
|
||||
p += sprintf(p, "bank_group:%d ",
|
||||
mem_err->bank >> CPER_MEM_BANK_GROUP_SHIFT);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_BANK_ADDRESS)
|
||||
p += sprintf(p, "bank_address:%d ",
|
||||
mem_err->bank & CPER_MEM_BANK_ADDRESS_MASK);
|
||||
if (mem_err->validation_bits & (CPER_MEM_VALID_ROW | CPER_MEM_VALID_ROW_EXT)) {
|
||||
u32 row = mem_err->row;
|
||||
cper_mem_err_pack(mem_err, &cmem);
|
||||
p += cper_mem_err_location(&cmem, p);
|
||||
|
||||
row |= cper_get_mem_extension(mem_err->validation_bits, mem_err->extended);
|
||||
p += sprintf(p, "row:%d ", row);
|
||||
}
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_COLUMN)
|
||||
p += sprintf(p, "col:%d ", mem_err->column);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_BIT_POSITION)
|
||||
p += sprintf(p, "bit_pos:%d ", mem_err->bit_pos);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_MODULE_HANDLE) {
|
||||
const char *bank = NULL, *device = NULL;
|
||||
struct dimm_info *dimm;
|
||||
|
||||
dmi_memdev_name(mem_err->mem_dev_handle, &bank, &device);
|
||||
if (bank != NULL && device != NULL)
|
||||
p += sprintf(p, "DIMM location:%s %s ", bank, device);
|
||||
else
|
||||
p += sprintf(p, "DIMM DMI handle: 0x%.4x ",
|
||||
mem_err->mem_dev_handle);
|
||||
|
||||
p += cper_dimm_err_location(&cmem, p);
|
||||
dimm = find_dimm_by_handle(mci, mem_err->mem_dev_handle);
|
||||
if (dimm) {
|
||||
e->top_layer = dimm->idx;
|
||||
strcpy(e->label, dimm->label);
|
||||
}
|
||||
}
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_CHIP_ID)
|
||||
p += sprintf(p, "chipID: %d ",
|
||||
mem_err->extended >> CPER_MEM_CHIP_ID_SHIFT);
|
||||
if (p > e->location)
|
||||
*(p - 1) = '\0';
|
||||
|
||||
|
|
@ -416,78 +361,7 @@ void ghes_edac_report_mem_error(int sev, struct cper_sec_mem_err *mem_err)
|
|||
|
||||
/* All other fields are mapped on e->other_detail */
|
||||
p = pvt->other_detail;
|
||||
p += snprintf(p, sizeof(pvt->other_detail),
|
||||
"APEI location: %s ", e->location);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_ERROR_STATUS) {
|
||||
u64 status = mem_err->error_status;
|
||||
|
||||
p += sprintf(p, "status(0x%016llx): ", (long long)status);
|
||||
switch ((status >> 8) & 0xff) {
|
||||
case 1:
|
||||
p += sprintf(p, "Error detected internal to the component ");
|
||||
break;
|
||||
case 16:
|
||||
p += sprintf(p, "Error detected in the bus ");
|
||||
break;
|
||||
case 4:
|
||||
p += sprintf(p, "Storage error in DRAM memory ");
|
||||
break;
|
||||
case 5:
|
||||
p += sprintf(p, "Storage error in TLB ");
|
||||
break;
|
||||
case 6:
|
||||
p += sprintf(p, "Storage error in cache ");
|
||||
break;
|
||||
case 7:
|
||||
p += sprintf(p, "Error in one or more functional units ");
|
||||
break;
|
||||
case 8:
|
||||
p += sprintf(p, "component failed self test ");
|
||||
break;
|
||||
case 9:
|
||||
p += sprintf(p, "Overflow or undervalue of internal queue ");
|
||||
break;
|
||||
case 17:
|
||||
p += sprintf(p, "Virtual address not found on IO-TLB or IO-PDIR ");
|
||||
break;
|
||||
case 18:
|
||||
p += sprintf(p, "Improper access error ");
|
||||
break;
|
||||
case 19:
|
||||
p += sprintf(p, "Access to a memory address which is not mapped to any component ");
|
||||
break;
|
||||
case 20:
|
||||
p += sprintf(p, "Loss of Lockstep ");
|
||||
break;
|
||||
case 21:
|
||||
p += sprintf(p, "Response not associated with a request ");
|
||||
break;
|
||||
case 22:
|
||||
p += sprintf(p, "Bus parity error - must also set the A, C, or D Bits ");
|
||||
break;
|
||||
case 23:
|
||||
p += sprintf(p, "Detection of a PATH_ERROR ");
|
||||
break;
|
||||
case 25:
|
||||
p += sprintf(p, "Bus operation timeout ");
|
||||
break;
|
||||
case 26:
|
||||
p += sprintf(p, "A read was issued to data that has been poisoned ");
|
||||
break;
|
||||
default:
|
||||
p += sprintf(p, "reserved ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_REQUESTOR_ID)
|
||||
p += sprintf(p, "requestorID: 0x%016llx ",
|
||||
(long long)mem_err->requestor_id);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_RESPONDER_ID)
|
||||
p += sprintf(p, "responderID: 0x%016llx ",
|
||||
(long long)mem_err->responder_id);
|
||||
if (mem_err->validation_bits & CPER_MEM_VALID_TARGET_ID)
|
||||
p += sprintf(p, "targetID: 0x%016llx ",
|
||||
(long long)mem_err->responder_id);
|
||||
p += print_mem_error_other_detail(mem_err, p, e->location, OTHER_DETAIL_LEN);
|
||||
if (p > pvt->other_detail)
|
||||
*(p - 1) = '\0';
|
||||
|
||||
|
|
|
|||
|
|
@ -244,11 +244,6 @@ static inline u32 i5100_nrecmema_rank(u32 a)
|
|||
return a >> 8 & ((1 << 3) - 1);
|
||||
}
|
||||
|
||||
static inline u32 i5100_nrecmema_dm_buf_id(u32 a)
|
||||
{
|
||||
return a & ((1 << 8) - 1);
|
||||
}
|
||||
|
||||
static inline u32 i5100_nrecmemb_cas(u32 a)
|
||||
{
|
||||
return a >> 16 & ((1 << 13) - 1);
|
||||
|
|
|
|||
|
|
@ -609,13 +609,6 @@ static int mpc85xx_l2_err_remove(struct platform_device *op)
|
|||
}
|
||||
|
||||
static const struct of_device_id mpc85xx_l2_err_of_match[] = {
|
||||
/* deprecate the fsl,85.. forms in the future, 2.6.30? */
|
||||
{ .compatible = "fsl,8540-l2-cache-controller", },
|
||||
{ .compatible = "fsl,8541-l2-cache-controller", },
|
||||
{ .compatible = "fsl,8544-l2-cache-controller", },
|
||||
{ .compatible = "fsl,8548-l2-cache-controller", },
|
||||
{ .compatible = "fsl,8555-l2-cache-controller", },
|
||||
{ .compatible = "fsl,8568-l2-cache-controller", },
|
||||
{ .compatible = "fsl,mpc8536-l2-cache-controller", },
|
||||
{ .compatible = "fsl,mpc8540-l2-cache-controller", },
|
||||
{ .compatible = "fsl,mpc8541-l2-cache-controller", },
|
||||
|
|
@ -644,13 +637,6 @@ static struct platform_driver mpc85xx_l2_err_driver = {
|
|||
};
|
||||
|
||||
static const struct of_device_id mpc85xx_mc_err_of_match[] = {
|
||||
/* deprecate the fsl,85.. forms in the future, 2.6.30? */
|
||||
{ .compatible = "fsl,8540-memory-controller", },
|
||||
{ .compatible = "fsl,8541-memory-controller", },
|
||||
{ .compatible = "fsl,8544-memory-controller", },
|
||||
{ .compatible = "fsl,8548-memory-controller", },
|
||||
{ .compatible = "fsl,8555-memory-controller", },
|
||||
{ .compatible = "fsl,8568-memory-controller", },
|
||||
{ .compatible = "fsl,mpc8536-memory-controller", },
|
||||
{ .compatible = "fsl,mpc8540-memory-controller", },
|
||||
{ .compatible = "fsl,mpc8541-memory-controller", },
|
||||
|
|
|
|||
|
|
@ -1,22 +1,9 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Synopsys DDR ECC Driver
|
||||
* This driver is based on ppc4xx_edac.c drivers
|
||||
*
|
||||
* Copyright (C) 2012 - 2014 Xilinx, Inc.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General Public
|
||||
* License. See the file "COPYING" in the main directory of this archive
|
||||
* for more details
|
||||
*/
|
||||
|
||||
#include <linux/edac.h>
|
||||
|
|
|
|||
|
|
@ -501,7 +501,7 @@ static int xgene_edac_mc_remove(struct xgene_edac_mc_ctx *mcu)
|
|||
#define MEMERR_L2C_L2ESRA_PAGE_OFFSET 0x0804
|
||||
|
||||
/*
|
||||
* Processor Module Domain (PMD) context - Context for a pair of processsors.
|
||||
* Processor Module Domain (PMD) context - Context for a pair of processors.
|
||||
* Each PMD consists of 2 CPUs and a shared L2 cache. Each CPU consists of
|
||||
* its own L1 cache.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -211,7 +211,33 @@ const char *cper_mem_err_type_str(unsigned int etype)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(cper_mem_err_type_str);
|
||||
|
||||
static int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg)
|
||||
const char *cper_mem_err_status_str(u64 status)
|
||||
{
|
||||
switch ((status >> 8) & 0xff) {
|
||||
case 1: return "Error detected internal to the component";
|
||||
case 4: return "Storage error in DRAM memory";
|
||||
case 5: return "Storage error in TLB";
|
||||
case 6: return "Storage error in cache";
|
||||
case 7: return "Error in one or more functional units";
|
||||
case 8: return "Component failed self test";
|
||||
case 9: return "Overflow or undervalue of internal queue";
|
||||
case 16: return "Error detected in the bus";
|
||||
case 17: return "Virtual address not found on IO-TLB or IO-PDIR";
|
||||
case 18: return "Improper access error";
|
||||
case 19: return "Access to a memory address which is not mapped to any component";
|
||||
case 20: return "Loss of Lockstep";
|
||||
case 21: return "Response not associated with a request";
|
||||
case 22: return "Bus parity error - must also set the A, C, or D Bits";
|
||||
case 23: return "Detection of a protocol error";
|
||||
case 24: return "Detection of a PATH_ERROR";
|
||||
case 25: return "Bus operation timeout";
|
||||
case 26: return "A read was issued to data that has been poisoned";
|
||||
default: return "Reserved";
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cper_mem_err_status_str);
|
||||
|
||||
int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg)
|
||||
{
|
||||
u32 len, n;
|
||||
|
||||
|
|
@ -221,51 +247,51 @@ static int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg)
|
|||
n = 0;
|
||||
len = CPER_REC_LEN;
|
||||
if (mem->validation_bits & CPER_MEM_VALID_NODE)
|
||||
n += scnprintf(msg + n, len - n, "node: %d ", mem->node);
|
||||
n += scnprintf(msg + n, len - n, "node:%d ", mem->node);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_CARD)
|
||||
n += scnprintf(msg + n, len - n, "card: %d ", mem->card);
|
||||
n += scnprintf(msg + n, len - n, "card:%d ", mem->card);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_MODULE)
|
||||
n += scnprintf(msg + n, len - n, "module: %d ", mem->module);
|
||||
n += scnprintf(msg + n, len - n, "module:%d ", mem->module);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER)
|
||||
n += scnprintf(msg + n, len - n, "rank: %d ", mem->rank);
|
||||
n += scnprintf(msg + n, len - n, "rank:%d ", mem->rank);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_BANK)
|
||||
n += scnprintf(msg + n, len - n, "bank: %d ", mem->bank);
|
||||
n += scnprintf(msg + n, len - n, "bank:%d ", mem->bank);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_BANK_GROUP)
|
||||
n += scnprintf(msg + n, len - n, "bank_group: %d ",
|
||||
n += scnprintf(msg + n, len - n, "bank_group:%d ",
|
||||
mem->bank >> CPER_MEM_BANK_GROUP_SHIFT);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_BANK_ADDRESS)
|
||||
n += scnprintf(msg + n, len - n, "bank_address: %d ",
|
||||
n += scnprintf(msg + n, len - n, "bank_address:%d ",
|
||||
mem->bank & CPER_MEM_BANK_ADDRESS_MASK);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_DEVICE)
|
||||
n += scnprintf(msg + n, len - n, "device: %d ", mem->device);
|
||||
n += scnprintf(msg + n, len - n, "device:%d ", mem->device);
|
||||
if (mem->validation_bits & (CPER_MEM_VALID_ROW | CPER_MEM_VALID_ROW_EXT)) {
|
||||
u32 row = mem->row;
|
||||
|
||||
row |= cper_get_mem_extension(mem->validation_bits, mem->extended);
|
||||
n += scnprintf(msg + n, len - n, "row: %d ", row);
|
||||
n += scnprintf(msg + n, len - n, "row:%d ", row);
|
||||
}
|
||||
if (mem->validation_bits & CPER_MEM_VALID_COLUMN)
|
||||
n += scnprintf(msg + n, len - n, "column: %d ", mem->column);
|
||||
n += scnprintf(msg + n, len - n, "column:%d ", mem->column);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_BIT_POSITION)
|
||||
n += scnprintf(msg + n, len - n, "bit_position: %d ",
|
||||
n += scnprintf(msg + n, len - n, "bit_position:%d ",
|
||||
mem->bit_pos);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_REQUESTOR_ID)
|
||||
n += scnprintf(msg + n, len - n, "requestor_id: 0x%016llx ",
|
||||
n += scnprintf(msg + n, len - n, "requestor_id:0x%016llx ",
|
||||
mem->requestor_id);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_RESPONDER_ID)
|
||||
n += scnprintf(msg + n, len - n, "responder_id: 0x%016llx ",
|
||||
n += scnprintf(msg + n, len - n, "responder_id:0x%016llx ",
|
||||
mem->responder_id);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_TARGET_ID)
|
||||
n += scnprintf(msg + n, len - n, "target_id: 0x%016llx ",
|
||||
n += scnprintf(msg + n, len - n, "target_id:0x%016llx ",
|
||||
mem->target_id);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_CHIP_ID)
|
||||
n += scnprintf(msg + n, len - n, "chip_id: %d ",
|
||||
n += scnprintf(msg + n, len - n, "chip_id:%d ",
|
||||
mem->extended >> CPER_MEM_CHIP_ID_SHIFT);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
static int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg)
|
||||
int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg)
|
||||
{
|
||||
u32 len, n;
|
||||
const char *bank = NULL, *device = NULL;
|
||||
|
|
@ -334,7 +360,9 @@ static void cper_print_mem(const char *pfx, const struct cper_sec_mem_err *mem,
|
|||
return;
|
||||
}
|
||||
if (mem->validation_bits & CPER_MEM_VALID_ERROR_STATUS)
|
||||
printk("%s""error_status: 0x%016llx\n", pfx, mem->error_status);
|
||||
printk("%s error_status: %s (0x%016llx)\n",
|
||||
pfx, cper_mem_err_status_str(mem->error_status),
|
||||
mem->error_status);
|
||||
if (mem->validation_bits & CPER_MEM_VALID_PA)
|
||||
printk("%s""physical_address: 0x%016llx\n",
|
||||
pfx, mem->physical_addr);
|
||||
|
|
|
|||
|
|
@ -50,4 +50,6 @@ source "drivers/virt/acrn/Kconfig"
|
|||
|
||||
source "drivers/virt/coco/efi_secret/Kconfig"
|
||||
|
||||
source "drivers/virt/coco/sev-guest/Kconfig"
|
||||
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ obj-y += vboxguest/
|
|||
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
|
||||
obj-$(CONFIG_ACRN_HSM) += acrn/
|
||||
obj-$(CONFIG_EFI_SECRET) += coco/efi_secret/
|
||||
obj-$(CONFIG_SEV_GUEST) += coco/sev-guest/
|
||||
|
|
|
|||
14
drivers/virt/coco/sev-guest/Kconfig
Normal file
14
drivers/virt/coco/sev-guest/Kconfig
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
config SEV_GUEST
|
||||
tristate "AMD SEV Guest driver"
|
||||
default m
|
||||
depends on AMD_MEM_ENCRYPT
|
||||
select CRYPTO_AEAD2
|
||||
select CRYPTO_GCM
|
||||
help
|
||||
SEV-SNP firmware provides the guest a mechanism to communicate with
|
||||
the PSP without risk from a malicious hypervisor who wishes to read,
|
||||
alter, drop or replay the messages sent. The driver provides
|
||||
userspace interface to communicate with the PSP to request the
|
||||
attestation report and more.
|
||||
|
||||
If you choose 'M' here, this module will be called sev-guest.
|
||||
2
drivers/virt/coco/sev-guest/Makefile
Normal file
2
drivers/virt/coco/sev-guest/Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_SEV_GUEST) += sev-guest.o
|
||||
743
drivers/virt/coco/sev-guest/sev-guest.c
Normal file
743
drivers/virt/coco/sev-guest/sev-guest.c
Normal file
|
|
@ -0,0 +1,743 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* AMD Secure Encrypted Virtualization (SEV) guest driver interface
|
||||
*
|
||||
* Copyright (C) 2021 Advanced Micro Devices, Inc.
|
||||
*
|
||||
* Author: Brijesh Singh <brijesh.singh@amd.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/set_memory.h>
|
||||
#include <linux/fs.h>
|
||||
#include <crypto/aead.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/psp-sev.h>
|
||||
#include <uapi/linux/sev-guest.h>
|
||||
#include <uapi/linux/psp-sev.h>
|
||||
|
||||
#include <asm/svm.h>
|
||||
#include <asm/sev.h>
|
||||
|
||||
#include "sev-guest.h"
|
||||
|
||||
#define DEVICE_NAME "sev-guest"
|
||||
#define AAD_LEN 48
|
||||
#define MSG_HDR_VER 1
|
||||
|
||||
struct snp_guest_crypto {
|
||||
struct crypto_aead *tfm;
|
||||
u8 *iv, *authtag;
|
||||
int iv_len, a_len;
|
||||
};
|
||||
|
||||
struct snp_guest_dev {
|
||||
struct device *dev;
|
||||
struct miscdevice misc;
|
||||
|
||||
void *certs_data;
|
||||
struct snp_guest_crypto *crypto;
|
||||
struct snp_guest_msg *request, *response;
|
||||
struct snp_secrets_page_layout *layout;
|
||||
struct snp_req_data input;
|
||||
u32 *os_area_msg_seqno;
|
||||
u8 *vmpck;
|
||||
};
|
||||
|
||||
static u32 vmpck_id;
|
||||
module_param(vmpck_id, uint, 0444);
|
||||
MODULE_PARM_DESC(vmpck_id, "The VMPCK ID to use when communicating with the PSP.");
|
||||
|
||||
/* Mutex to serialize the shared buffer access and command handling. */
|
||||
static DEFINE_MUTEX(snp_cmd_mutex);
|
||||
|
||||
static bool is_vmpck_empty(struct snp_guest_dev *snp_dev)
|
||||
{
|
||||
char zero_key[VMPCK_KEY_LEN] = {0};
|
||||
|
||||
if (snp_dev->vmpck)
|
||||
return !memcmp(snp_dev->vmpck, zero_key, VMPCK_KEY_LEN);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void snp_disable_vmpck(struct snp_guest_dev *snp_dev)
|
||||
{
|
||||
memzero_explicit(snp_dev->vmpck, VMPCK_KEY_LEN);
|
||||
snp_dev->vmpck = NULL;
|
||||
}
|
||||
|
||||
static inline u64 __snp_get_msg_seqno(struct snp_guest_dev *snp_dev)
|
||||
{
|
||||
u64 count;
|
||||
|
||||
lockdep_assert_held(&snp_cmd_mutex);
|
||||
|
||||
/* Read the current message sequence counter from secrets pages */
|
||||
count = *snp_dev->os_area_msg_seqno;
|
||||
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
/* Return a non-zero on success */
|
||||
static u64 snp_get_msg_seqno(struct snp_guest_dev *snp_dev)
|
||||
{
|
||||
u64 count = __snp_get_msg_seqno(snp_dev);
|
||||
|
||||
/*
|
||||
* The message sequence counter for the SNP guest request is a 64-bit
|
||||
* value but the version 2 of GHCB specification defines a 32-bit storage
|
||||
* for it. If the counter exceeds the 32-bit value then return zero.
|
||||
* The caller should check the return value, but if the caller happens to
|
||||
* not check the value and use it, then the firmware treats zero as an
|
||||
* invalid number and will fail the message request.
|
||||
*/
|
||||
if (count >= UINT_MAX) {
|
||||
dev_err(snp_dev->dev, "request message sequence counter overflow\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void snp_inc_msg_seqno(struct snp_guest_dev *snp_dev)
|
||||
{
|
||||
/*
|
||||
* The counter is also incremented by the PSP, so increment it by 2
|
||||
* and save in secrets page.
|
||||
*/
|
||||
*snp_dev->os_area_msg_seqno += 2;
|
||||
}
|
||||
|
||||
static inline struct snp_guest_dev *to_snp_dev(struct file *file)
|
||||
{
|
||||
struct miscdevice *dev = file->private_data;
|
||||
|
||||
return container_of(dev, struct snp_guest_dev, misc);
|
||||
}
|
||||
|
||||
static struct snp_guest_crypto *init_crypto(struct snp_guest_dev *snp_dev, u8 *key, size_t keylen)
|
||||
{
|
||||
struct snp_guest_crypto *crypto;
|
||||
|
||||
crypto = kzalloc(sizeof(*crypto), GFP_KERNEL_ACCOUNT);
|
||||
if (!crypto)
|
||||
return NULL;
|
||||
|
||||
crypto->tfm = crypto_alloc_aead("gcm(aes)", 0, 0);
|
||||
if (IS_ERR(crypto->tfm))
|
||||
goto e_free;
|
||||
|
||||
if (crypto_aead_setkey(crypto->tfm, key, keylen))
|
||||
goto e_free_crypto;
|
||||
|
||||
crypto->iv_len = crypto_aead_ivsize(crypto->tfm);
|
||||
crypto->iv = kmalloc(crypto->iv_len, GFP_KERNEL_ACCOUNT);
|
||||
if (!crypto->iv)
|
||||
goto e_free_crypto;
|
||||
|
||||
if (crypto_aead_authsize(crypto->tfm) > MAX_AUTHTAG_LEN) {
|
||||
if (crypto_aead_setauthsize(crypto->tfm, MAX_AUTHTAG_LEN)) {
|
||||
dev_err(snp_dev->dev, "failed to set authsize to %d\n", MAX_AUTHTAG_LEN);
|
||||
goto e_free_iv;
|
||||
}
|
||||
}
|
||||
|
||||
crypto->a_len = crypto_aead_authsize(crypto->tfm);
|
||||
crypto->authtag = kmalloc(crypto->a_len, GFP_KERNEL_ACCOUNT);
|
||||
if (!crypto->authtag)
|
||||
goto e_free_auth;
|
||||
|
||||
return crypto;
|
||||
|
||||
e_free_auth:
|
||||
kfree(crypto->authtag);
|
||||
e_free_iv:
|
||||
kfree(crypto->iv);
|
||||
e_free_crypto:
|
||||
crypto_free_aead(crypto->tfm);
|
||||
e_free:
|
||||
kfree(crypto);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void deinit_crypto(struct snp_guest_crypto *crypto)
|
||||
{
|
||||
crypto_free_aead(crypto->tfm);
|
||||
kfree(crypto->iv);
|
||||
kfree(crypto->authtag);
|
||||
kfree(crypto);
|
||||
}
|
||||
|
||||
static int enc_dec_message(struct snp_guest_crypto *crypto, struct snp_guest_msg *msg,
|
||||
u8 *src_buf, u8 *dst_buf, size_t len, bool enc)
|
||||
{
|
||||
struct snp_guest_msg_hdr *hdr = &msg->hdr;
|
||||
struct scatterlist src[3], dst[3];
|
||||
DECLARE_CRYPTO_WAIT(wait);
|
||||
struct aead_request *req;
|
||||
int ret;
|
||||
|
||||
req = aead_request_alloc(crypto->tfm, GFP_KERNEL);
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* AEAD memory operations:
|
||||
* +------ AAD -------+------- DATA -----+---- AUTHTAG----+
|
||||
* | msg header | plaintext | hdr->authtag |
|
||||
* | bytes 30h - 5Fh | or | |
|
||||
* | | cipher | |
|
||||
* +------------------+------------------+----------------+
|
||||
*/
|
||||
sg_init_table(src, 3);
|
||||
sg_set_buf(&src[0], &hdr->algo, AAD_LEN);
|
||||
sg_set_buf(&src[1], src_buf, hdr->msg_sz);
|
||||
sg_set_buf(&src[2], hdr->authtag, crypto->a_len);
|
||||
|
||||
sg_init_table(dst, 3);
|
||||
sg_set_buf(&dst[0], &hdr->algo, AAD_LEN);
|
||||
sg_set_buf(&dst[1], dst_buf, hdr->msg_sz);
|
||||
sg_set_buf(&dst[2], hdr->authtag, crypto->a_len);
|
||||
|
||||
aead_request_set_ad(req, AAD_LEN);
|
||||
aead_request_set_tfm(req, crypto->tfm);
|
||||
aead_request_set_callback(req, 0, crypto_req_done, &wait);
|
||||
|
||||
aead_request_set_crypt(req, src, dst, len, crypto->iv);
|
||||
ret = crypto_wait_req(enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req), &wait);
|
||||
|
||||
aead_request_free(req);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __enc_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg,
|
||||
void *plaintext, size_t len)
|
||||
{
|
||||
struct snp_guest_crypto *crypto = snp_dev->crypto;
|
||||
struct snp_guest_msg_hdr *hdr = &msg->hdr;
|
||||
|
||||
memset(crypto->iv, 0, crypto->iv_len);
|
||||
memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno));
|
||||
|
||||
return enc_dec_message(crypto, msg, plaintext, msg->payload, len, true);
|
||||
}
|
||||
|
||||
static int dec_payload(struct snp_guest_dev *snp_dev, struct snp_guest_msg *msg,
|
||||
void *plaintext, size_t len)
|
||||
{
|
||||
struct snp_guest_crypto *crypto = snp_dev->crypto;
|
||||
struct snp_guest_msg_hdr *hdr = &msg->hdr;
|
||||
|
||||
/* Build IV with response buffer sequence number */
|
||||
memset(crypto->iv, 0, crypto->iv_len);
|
||||
memcpy(crypto->iv, &hdr->msg_seqno, sizeof(hdr->msg_seqno));
|
||||
|
||||
return enc_dec_message(crypto, msg, msg->payload, plaintext, len, false);
|
||||
}
|
||||
|
||||
static int verify_and_dec_payload(struct snp_guest_dev *snp_dev, void *payload, u32 sz)
|
||||
{
|
||||
struct snp_guest_crypto *crypto = snp_dev->crypto;
|
||||
struct snp_guest_msg *resp = snp_dev->response;
|
||||
struct snp_guest_msg *req = snp_dev->request;
|
||||
struct snp_guest_msg_hdr *req_hdr = &req->hdr;
|
||||
struct snp_guest_msg_hdr *resp_hdr = &resp->hdr;
|
||||
|
||||
dev_dbg(snp_dev->dev, "response [seqno %lld type %d version %d sz %d]\n",
|
||||
resp_hdr->msg_seqno, resp_hdr->msg_type, resp_hdr->msg_version, resp_hdr->msg_sz);
|
||||
|
||||
/* Verify that the sequence counter is incremented by 1 */
|
||||
if (unlikely(resp_hdr->msg_seqno != (req_hdr->msg_seqno + 1)))
|
||||
return -EBADMSG;
|
||||
|
||||
/* Verify response message type and version number. */
|
||||
if (resp_hdr->msg_type != (req_hdr->msg_type + 1) ||
|
||||
resp_hdr->msg_version != req_hdr->msg_version)
|
||||
return -EBADMSG;
|
||||
|
||||
/*
|
||||
* If the message size is greater than our buffer length then return
|
||||
* an error.
|
||||
*/
|
||||
if (unlikely((resp_hdr->msg_sz + crypto->a_len) > sz))
|
||||
return -EBADMSG;
|
||||
|
||||
/* Decrypt the payload */
|
||||
return dec_payload(snp_dev, resp, payload, resp_hdr->msg_sz + crypto->a_len);
|
||||
}
|
||||
|
||||
static int enc_payload(struct snp_guest_dev *snp_dev, u64 seqno, int version, u8 type,
|
||||
void *payload, size_t sz)
|
||||
{
|
||||
struct snp_guest_msg *req = snp_dev->request;
|
||||
struct snp_guest_msg_hdr *hdr = &req->hdr;
|
||||
|
||||
memset(req, 0, sizeof(*req));
|
||||
|
||||
hdr->algo = SNP_AEAD_AES_256_GCM;
|
||||
hdr->hdr_version = MSG_HDR_VER;
|
||||
hdr->hdr_sz = sizeof(*hdr);
|
||||
hdr->msg_type = type;
|
||||
hdr->msg_version = version;
|
||||
hdr->msg_seqno = seqno;
|
||||
hdr->msg_vmpck = vmpck_id;
|
||||
hdr->msg_sz = sz;
|
||||
|
||||
/* Verify the sequence number is non-zero */
|
||||
if (!hdr->msg_seqno)
|
||||
return -ENOSR;
|
||||
|
||||
dev_dbg(snp_dev->dev, "request [seqno %lld type %d version %d sz %d]\n",
|
||||
hdr->msg_seqno, hdr->msg_type, hdr->msg_version, hdr->msg_sz);
|
||||
|
||||
return __enc_payload(snp_dev, req, payload, sz);
|
||||
}
|
||||
|
||||
static int handle_guest_request(struct snp_guest_dev *snp_dev, u64 exit_code, int msg_ver,
|
||||
u8 type, void *req_buf, size_t req_sz, void *resp_buf,
|
||||
u32 resp_sz, __u64 *fw_err)
|
||||
{
|
||||
unsigned long err;
|
||||
u64 seqno;
|
||||
int rc;
|
||||
|
||||
/* Get message sequence and verify that its a non-zero */
|
||||
seqno = snp_get_msg_seqno(snp_dev);
|
||||
if (!seqno)
|
||||
return -EIO;
|
||||
|
||||
memset(snp_dev->response, 0, sizeof(struct snp_guest_msg));
|
||||
|
||||
/* Encrypt the userspace provided payload */
|
||||
rc = enc_payload(snp_dev, seqno, msg_ver, type, req_buf, req_sz);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/* Call firmware to process the request */
|
||||
rc = snp_issue_guest_request(exit_code, &snp_dev->input, &err);
|
||||
if (fw_err)
|
||||
*fw_err = err;
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
/*
|
||||
* The verify_and_dec_payload() will fail only if the hypervisor is
|
||||
* actively modifying the message header or corrupting the encrypted payload.
|
||||
* This hints that hypervisor is acting in a bad faith. Disable the VMPCK so that
|
||||
* the key cannot be used for any communication. The key is disabled to ensure
|
||||
* that AES-GCM does not use the same IV while encrypting the request payload.
|
||||
*/
|
||||
rc = verify_and_dec_payload(snp_dev, resp_buf, resp_sz);
|
||||
if (rc) {
|
||||
dev_alert(snp_dev->dev,
|
||||
"Detected unexpected decode failure, disabling the vmpck_id %d\n",
|
||||
vmpck_id);
|
||||
snp_disable_vmpck(snp_dev);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Increment to new message sequence after payload decryption was successful. */
|
||||
snp_inc_msg_seqno(snp_dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
|
||||
{
|
||||
struct snp_guest_crypto *crypto = snp_dev->crypto;
|
||||
struct snp_report_resp *resp;
|
||||
struct snp_report_req req;
|
||||
int rc, resp_len;
|
||||
|
||||
lockdep_assert_held(&snp_cmd_mutex);
|
||||
|
||||
if (!arg->req_data || !arg->resp_data)
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req)))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* The intermediate response buffer is used while decrypting the
|
||||
* response payload. Make sure that it has enough space to cover the
|
||||
* authtag.
|
||||
*/
|
||||
resp_len = sizeof(resp->data) + crypto->a_len;
|
||||
resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT);
|
||||
if (!resp)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version,
|
||||
SNP_MSG_REPORT_REQ, &req, sizeof(req), resp->data,
|
||||
resp_len, &arg->fw_err);
|
||||
if (rc)
|
||||
goto e_free;
|
||||
|
||||
if (copy_to_user((void __user *)arg->resp_data, resp, sizeof(*resp)))
|
||||
rc = -EFAULT;
|
||||
|
||||
e_free:
|
||||
kfree(resp);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
|
||||
{
|
||||
struct snp_guest_crypto *crypto = snp_dev->crypto;
|
||||
struct snp_derived_key_resp resp = {0};
|
||||
struct snp_derived_key_req req;
|
||||
int rc, resp_len;
|
||||
/* Response data is 64 bytes and max authsize for GCM is 16 bytes. */
|
||||
u8 buf[64 + 16];
|
||||
|
||||
lockdep_assert_held(&snp_cmd_mutex);
|
||||
|
||||
if (!arg->req_data || !arg->resp_data)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* The intermediate response buffer is used while decrypting the
|
||||
* response payload. Make sure that it has enough space to cover the
|
||||
* authtag.
|
||||
*/
|
||||
resp_len = sizeof(resp.data) + crypto->a_len;
|
||||
if (sizeof(buf) < resp_len)
|
||||
return -ENOMEM;
|
||||
|
||||
if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req)))
|
||||
return -EFAULT;
|
||||
|
||||
rc = handle_guest_request(snp_dev, SVM_VMGEXIT_GUEST_REQUEST, arg->msg_version,
|
||||
SNP_MSG_KEY_REQ, &req, sizeof(req), buf, resp_len,
|
||||
&arg->fw_err);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
memcpy(resp.data, buf, sizeof(resp.data));
|
||||
if (copy_to_user((void __user *)arg->resp_data, &resp, sizeof(resp)))
|
||||
rc = -EFAULT;
|
||||
|
||||
/* The response buffer contains the sensitive data, explicitly clear it. */
|
||||
memzero_explicit(buf, sizeof(buf));
|
||||
memzero_explicit(&resp, sizeof(resp));
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
|
||||
{
|
||||
struct snp_guest_crypto *crypto = snp_dev->crypto;
|
||||
struct snp_ext_report_req req;
|
||||
struct snp_report_resp *resp;
|
||||
int ret, npages = 0, resp_len;
|
||||
|
||||
lockdep_assert_held(&snp_cmd_mutex);
|
||||
|
||||
if (!arg->req_data || !arg->resp_data)
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(&req, (void __user *)arg->req_data, sizeof(req)))
|
||||
return -EFAULT;
|
||||
|
||||
/* userspace does not want certificate data */
|
||||
if (!req.certs_len || !req.certs_address)
|
||||
goto cmd;
|
||||
|
||||
if (req.certs_len > SEV_FW_BLOB_MAX_SIZE ||
|
||||
!IS_ALIGNED(req.certs_len, PAGE_SIZE))
|
||||
return -EINVAL;
|
||||
|
||||
if (!access_ok((const void __user *)req.certs_address, req.certs_len))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Initialize the intermediate buffer with all zeros. This buffer
|
||||
* is used in the guest request message to get the certs blob from
|
||||
* the host. If host does not supply any certs in it, then copy
|
||||
* zeros to indicate that certificate data was not provided.
|
||||
*/
|
||||
memset(snp_dev->certs_data, 0, req.certs_len);
|
||||
npages = req.certs_len >> PAGE_SHIFT;
|
||||
cmd:
|
||||
/*
|
||||
* The intermediate response buffer is used while decrypting the
|
||||
* response payload. Make sure that it has enough space to cover the
|
||||
* authtag.
|
||||
*/
|
||||
resp_len = sizeof(resp->data) + crypto->a_len;
|
||||
resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT);
|
||||
if (!resp)
|
||||
return -ENOMEM;
|
||||
|
||||
snp_dev->input.data_npages = npages;
|
||||
ret = handle_guest_request(snp_dev, SVM_VMGEXIT_EXT_GUEST_REQUEST, arg->msg_version,
|
||||
SNP_MSG_REPORT_REQ, &req.data,
|
||||
sizeof(req.data), resp->data, resp_len, &arg->fw_err);
|
||||
|
||||
/* If certs length is invalid then copy the returned length */
|
||||
if (arg->fw_err == SNP_GUEST_REQ_INVALID_LEN) {
|
||||
req.certs_len = snp_dev->input.data_npages << PAGE_SHIFT;
|
||||
|
||||
if (copy_to_user((void __user *)arg->req_data, &req, sizeof(req)))
|
||||
ret = -EFAULT;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
goto e_free;
|
||||
|
||||
if (npages &&
|
||||
copy_to_user((void __user *)req.certs_address, snp_dev->certs_data,
|
||||
req.certs_len)) {
|
||||
ret = -EFAULT;
|
||||
goto e_free;
|
||||
}
|
||||
|
||||
if (copy_to_user((void __user *)arg->resp_data, resp, sizeof(*resp)))
|
||||
ret = -EFAULT;
|
||||
|
||||
e_free:
|
||||
kfree(resp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)
|
||||
{
|
||||
struct snp_guest_dev *snp_dev = to_snp_dev(file);
|
||||
void __user *argp = (void __user *)arg;
|
||||
struct snp_guest_request_ioctl input;
|
||||
int ret = -ENOTTY;
|
||||
|
||||
if (copy_from_user(&input, argp, sizeof(input)))
|
||||
return -EFAULT;
|
||||
|
||||
input.fw_err = 0xff;
|
||||
|
||||
/* Message version must be non-zero */
|
||||
if (!input.msg_version)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&snp_cmd_mutex);
|
||||
|
||||
/* Check if the VMPCK is not empty */
|
||||
if (is_vmpck_empty(snp_dev)) {
|
||||
dev_err_ratelimited(snp_dev->dev, "VMPCK is disabled\n");
|
||||
mutex_unlock(&snp_cmd_mutex);
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
switch (ioctl) {
|
||||
case SNP_GET_REPORT:
|
||||
ret = get_report(snp_dev, &input);
|
||||
break;
|
||||
case SNP_GET_DERIVED_KEY:
|
||||
ret = get_derived_key(snp_dev, &input);
|
||||
break;
|
||||
case SNP_GET_EXT_REPORT:
|
||||
ret = get_ext_report(snp_dev, &input);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock(&snp_cmd_mutex);
|
||||
|
||||
if (input.fw_err && copy_to_user(argp, &input, sizeof(input)))
|
||||
return -EFAULT;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void free_shared_pages(void *buf, size_t sz)
|
||||
{
|
||||
unsigned int npages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
|
||||
int ret;
|
||||
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
ret = set_memory_encrypted((unsigned long)buf, npages);
|
||||
if (ret) {
|
||||
WARN_ONCE(ret, "failed to restore encryption mask (leak it)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
__free_pages(virt_to_page(buf), get_order(sz));
|
||||
}
|
||||
|
||||
static void *alloc_shared_pages(struct device *dev, size_t sz)
|
||||
{
|
||||
unsigned int npages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
|
||||
struct page *page;
|
||||
int ret;
|
||||
|
||||
page = alloc_pages(GFP_KERNEL_ACCOUNT, get_order(sz));
|
||||
if (!page)
|
||||
return NULL;
|
||||
|
||||
ret = set_memory_decrypted((unsigned long)page_address(page), npages);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to mark page shared, ret=%d\n", ret);
|
||||
__free_pages(page, get_order(sz));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return page_address(page);
|
||||
}
|
||||
|
||||
static const struct file_operations snp_guest_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = snp_guest_ioctl,
|
||||
};
|
||||
|
||||
static u8 *get_vmpck(int id, struct snp_secrets_page_layout *layout, u32 **seqno)
|
||||
{
|
||||
u8 *key = NULL;
|
||||
|
||||
switch (id) {
|
||||
case 0:
|
||||
*seqno = &layout->os_area.msg_seqno_0;
|
||||
key = layout->vmpck0;
|
||||
break;
|
||||
case 1:
|
||||
*seqno = &layout->os_area.msg_seqno_1;
|
||||
key = layout->vmpck1;
|
||||
break;
|
||||
case 2:
|
||||
*seqno = &layout->os_area.msg_seqno_2;
|
||||
key = layout->vmpck2;
|
||||
break;
|
||||
case 3:
|
||||
*seqno = &layout->os_area.msg_seqno_3;
|
||||
key = layout->vmpck3;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static int __init sev_guest_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct snp_secrets_page_layout *layout;
|
||||
struct sev_guest_platform_data *data;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct snp_guest_dev *snp_dev;
|
||||
struct miscdevice *misc;
|
||||
int ret;
|
||||
|
||||
if (!dev->platform_data)
|
||||
return -ENODEV;
|
||||
|
||||
data = (struct sev_guest_platform_data *)dev->platform_data;
|
||||
layout = (__force void *)ioremap_encrypted(data->secrets_gpa, PAGE_SIZE);
|
||||
if (!layout)
|
||||
return -ENODEV;
|
||||
|
||||
ret = -ENOMEM;
|
||||
snp_dev = devm_kzalloc(&pdev->dev, sizeof(struct snp_guest_dev), GFP_KERNEL);
|
||||
if (!snp_dev)
|
||||
goto e_unmap;
|
||||
|
||||
ret = -EINVAL;
|
||||
snp_dev->vmpck = get_vmpck(vmpck_id, layout, &snp_dev->os_area_msg_seqno);
|
||||
if (!snp_dev->vmpck) {
|
||||
dev_err(dev, "invalid vmpck id %d\n", vmpck_id);
|
||||
goto e_unmap;
|
||||
}
|
||||
|
||||
/* Verify that VMPCK is not zero. */
|
||||
if (is_vmpck_empty(snp_dev)) {
|
||||
dev_err(dev, "vmpck id %d is null\n", vmpck_id);
|
||||
goto e_unmap;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, snp_dev);
|
||||
snp_dev->dev = dev;
|
||||
snp_dev->layout = layout;
|
||||
|
||||
/* Allocate the shared page used for the request and response message. */
|
||||
snp_dev->request = alloc_shared_pages(dev, sizeof(struct snp_guest_msg));
|
||||
if (!snp_dev->request)
|
||||
goto e_unmap;
|
||||
|
||||
snp_dev->response = alloc_shared_pages(dev, sizeof(struct snp_guest_msg));
|
||||
if (!snp_dev->response)
|
||||
goto e_free_request;
|
||||
|
||||
snp_dev->certs_data = alloc_shared_pages(dev, SEV_FW_BLOB_MAX_SIZE);
|
||||
if (!snp_dev->certs_data)
|
||||
goto e_free_response;
|
||||
|
||||
ret = -EIO;
|
||||
snp_dev->crypto = init_crypto(snp_dev, snp_dev->vmpck, VMPCK_KEY_LEN);
|
||||
if (!snp_dev->crypto)
|
||||
goto e_free_cert_data;
|
||||
|
||||
misc = &snp_dev->misc;
|
||||
misc->minor = MISC_DYNAMIC_MINOR;
|
||||
misc->name = DEVICE_NAME;
|
||||
misc->fops = &snp_guest_fops;
|
||||
|
||||
/* initial the input address for guest request */
|
||||
snp_dev->input.req_gpa = __pa(snp_dev->request);
|
||||
snp_dev->input.resp_gpa = __pa(snp_dev->response);
|
||||
snp_dev->input.data_gpa = __pa(snp_dev->certs_data);
|
||||
|
||||
ret = misc_register(misc);
|
||||
if (ret)
|
||||
goto e_free_cert_data;
|
||||
|
||||
dev_info(dev, "Initialized SEV guest driver (using vmpck_id %d)\n", vmpck_id);
|
||||
return 0;
|
||||
|
||||
e_free_cert_data:
|
||||
free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE);
|
||||
e_free_response:
|
||||
free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg));
|
||||
e_free_request:
|
||||
free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg));
|
||||
e_unmap:
|
||||
iounmap(layout);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __exit sev_guest_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct snp_guest_dev *snp_dev = platform_get_drvdata(pdev);
|
||||
|
||||
free_shared_pages(snp_dev->certs_data, SEV_FW_BLOB_MAX_SIZE);
|
||||
free_shared_pages(snp_dev->response, sizeof(struct snp_guest_msg));
|
||||
free_shared_pages(snp_dev->request, sizeof(struct snp_guest_msg));
|
||||
deinit_crypto(snp_dev->crypto);
|
||||
misc_deregister(&snp_dev->misc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This driver is meant to be a common SEV guest interface driver and to
|
||||
* support any SEV guest API. As such, even though it has been introduced
|
||||
* with the SEV-SNP support, it is named "sev-guest".
|
||||
*/
|
||||
static struct platform_driver sev_guest_driver = {
|
||||
.remove = __exit_p(sev_guest_remove),
|
||||
.driver = {
|
||||
.name = "sev-guest",
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver_probe(sev_guest_driver, sev_guest_probe);
|
||||
|
||||
MODULE_AUTHOR("Brijesh Singh <brijesh.singh@amd.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_VERSION("1.0.0");
|
||||
MODULE_DESCRIPTION("AMD SEV Guest Driver");
|
||||
63
drivers/virt/coco/sev-guest/sev-guest.h
Normal file
63
drivers/virt/coco/sev-guest/sev-guest.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (C) 2021 Advanced Micro Devices, Inc.
|
||||
*
|
||||
* Author: Brijesh Singh <brijesh.singh@amd.com>
|
||||
*
|
||||
* SEV-SNP API spec is available at https://developer.amd.com/sev
|
||||
*/
|
||||
|
||||
#ifndef __VIRT_SEVGUEST_H__
|
||||
#define __VIRT_SEVGUEST_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
#define MAX_AUTHTAG_LEN 32
|
||||
|
||||
/* See SNP spec SNP_GUEST_REQUEST section for the structure */
|
||||
enum msg_type {
|
||||
SNP_MSG_TYPE_INVALID = 0,
|
||||
SNP_MSG_CPUID_REQ,
|
||||
SNP_MSG_CPUID_RSP,
|
||||
SNP_MSG_KEY_REQ,
|
||||
SNP_MSG_KEY_RSP,
|
||||
SNP_MSG_REPORT_REQ,
|
||||
SNP_MSG_REPORT_RSP,
|
||||
SNP_MSG_EXPORT_REQ,
|
||||
SNP_MSG_EXPORT_RSP,
|
||||
SNP_MSG_IMPORT_REQ,
|
||||
SNP_MSG_IMPORT_RSP,
|
||||
SNP_MSG_ABSORB_REQ,
|
||||
SNP_MSG_ABSORB_RSP,
|
||||
SNP_MSG_VMRK_REQ,
|
||||
SNP_MSG_VMRK_RSP,
|
||||
|
||||
SNP_MSG_TYPE_MAX
|
||||
};
|
||||
|
||||
enum aead_algo {
|
||||
SNP_AEAD_INVALID,
|
||||
SNP_AEAD_AES_256_GCM,
|
||||
};
|
||||
|
||||
struct snp_guest_msg_hdr {
|
||||
u8 authtag[MAX_AUTHTAG_LEN];
|
||||
u64 msg_seqno;
|
||||
u8 rsvd1[8];
|
||||
u8 algo;
|
||||
u8 hdr_version;
|
||||
u16 hdr_sz;
|
||||
u8 msg_type;
|
||||
u8 msg_version;
|
||||
u16 msg_sz;
|
||||
u32 rsvd2;
|
||||
u8 msg_vmpck;
|
||||
u8 rsvd3[35];
|
||||
} __packed;
|
||||
|
||||
struct snp_guest_msg {
|
||||
struct snp_guest_msg_hdr hdr;
|
||||
u8 payload[4000];
|
||||
} __packed;
|
||||
|
||||
#endif /* __VIRT_SEVGUEST_H__ */
|
||||
|
|
@ -72,6 +72,14 @@ enum cc_attr {
|
|||
* Examples include TDX guest & SEV.
|
||||
*/
|
||||
CC_ATTR_GUEST_UNROLL_STRING_IO,
|
||||
|
||||
/**
|
||||
* @CC_ATTR_SEV_SNP: Guest SNP is active.
|
||||
*
|
||||
* The platform/OS is running as a guest/virtual machine and actively
|
||||
* using AMD SEV-SNP features.
|
||||
*/
|
||||
CC_ATTR_GUEST_SEV_SNP,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ARCH_HAS_CC_PLATFORM
|
||||
|
|
|
|||
|
|
@ -558,6 +558,7 @@ extern const char *const cper_proc_error_type_strs[4];
|
|||
u64 cper_next_record_id(void);
|
||||
const char *cper_severity_str(unsigned int);
|
||||
const char *cper_mem_err_type_str(unsigned int);
|
||||
const char *cper_mem_err_status_str(u64 status);
|
||||
void cper_print_bits(const char *prefix, unsigned int bits,
|
||||
const char * const strs[], unsigned int strs_size);
|
||||
void cper_mem_err_pack(const struct cper_sec_mem_err *,
|
||||
|
|
@ -568,5 +569,7 @@ void cper_print_proc_arm(const char *pfx,
|
|||
const struct cper_sec_proc_arm *proc);
|
||||
void cper_print_proc_ia(const char *pfx,
|
||||
const struct cper_sec_proc_ia *proc);
|
||||
int cper_mem_err_location(struct cper_mem_err_compact *mem, char *msg);
|
||||
int cper_dimm_err_location(struct cper_mem_err_compact *mem, char *msg);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -393,6 +393,7 @@ void efi_native_runtime_setup(void);
|
|||
#define EFI_CERT_SHA256_GUID EFI_GUID(0xc1c41626, 0x504c, 0x4092, 0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28)
|
||||
#define EFI_CERT_X509_GUID EFI_GUID(0xa5c059a1, 0x94e4, 0x4aa7, 0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72)
|
||||
#define EFI_CERT_X509_SHA256_GUID EFI_GUID(0x3bd2a492, 0x96c0, 0x4079, 0xb4, 0x20, 0xfc, 0xf9, 0x8e, 0xf1, 0x03, 0xed)
|
||||
#define EFI_CC_BLOB_GUID EFI_GUID(0x067b1f5f, 0xcf26, 0x44c5, 0x85, 0x54, 0x93, 0xd7, 0x77, 0x91, 0x2d, 0x42)
|
||||
|
||||
/*
|
||||
* This GUID is used to pass to the kernel proper the struct screen_info
|
||||
|
|
|
|||
80
include/uapi/linux/sev-guest.h
Normal file
80
include/uapi/linux/sev-guest.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only WITH Linux-syscall-note */
|
||||
/*
|
||||
* Userspace interface for AMD SEV and SNP guest driver.
|
||||
*
|
||||
* Copyright (C) 2021 Advanced Micro Devices, Inc.
|
||||
*
|
||||
* Author: Brijesh Singh <brijesh.singh@amd.com>
|
||||
*
|
||||
* SEV API specification is available at: https://developer.amd.com/sev/
|
||||
*/
|
||||
|
||||
#ifndef __UAPI_LINUX_SEV_GUEST_H_
|
||||
#define __UAPI_LINUX_SEV_GUEST_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct snp_report_req {
|
||||
/* user data that should be included in the report */
|
||||
__u8 user_data[64];
|
||||
|
||||
/* The vmpl level to be included in the report */
|
||||
__u32 vmpl;
|
||||
|
||||
/* Must be zero filled */
|
||||
__u8 rsvd[28];
|
||||
};
|
||||
|
||||
struct snp_report_resp {
|
||||
/* response data, see SEV-SNP spec for the format */
|
||||
__u8 data[4000];
|
||||
};
|
||||
|
||||
struct snp_derived_key_req {
|
||||
__u32 root_key_select;
|
||||
__u32 rsvd;
|
||||
__u64 guest_field_select;
|
||||
__u32 vmpl;
|
||||
__u32 guest_svn;
|
||||
__u64 tcb_version;
|
||||
};
|
||||
|
||||
struct snp_derived_key_resp {
|
||||
/* response data, see SEV-SNP spec for the format */
|
||||
__u8 data[64];
|
||||
};
|
||||
|
||||
struct snp_guest_request_ioctl {
|
||||
/* message version number (must be non-zero) */
|
||||
__u8 msg_version;
|
||||
|
||||
/* Request and response structure address */
|
||||
__u64 req_data;
|
||||
__u64 resp_data;
|
||||
|
||||
/* firmware error code on failure (see psp-sev.h) */
|
||||
__u64 fw_err;
|
||||
};
|
||||
|
||||
struct snp_ext_report_req {
|
||||
struct snp_report_req data;
|
||||
|
||||
/* where to copy the certificate blob */
|
||||
__u64 certs_address;
|
||||
|
||||
/* length of the certificate blob */
|
||||
__u32 certs_len;
|
||||
};
|
||||
|
||||
#define SNP_GUEST_REQ_IOC_TYPE 'S'
|
||||
|
||||
/* Get SNP attestation report */
|
||||
#define SNP_GET_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x0, struct snp_guest_request_ioctl)
|
||||
|
||||
/* Get a derived key from the root */
|
||||
#define SNP_GET_DERIVED_KEY _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x1, struct snp_guest_request_ioctl)
|
||||
|
||||
/* Get SNP extended report as defined in the GHCB specification version 2. */
|
||||
#define SNP_GET_EXT_REPORT _IOWR(SNP_GUEST_REQ_IOC_TYPE, 0x2, struct snp_guest_request_ioctl)
|
||||
|
||||
#endif /* __UAPI_LINUX_SEV_GUEST_H_ */
|
||||
Loading…
Reference in New Issue
Block a user