Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += svm/ vmx/
obj-$(subst m,y,$(CONFIG_KVM_X86)) += hw.o
+373
View File
@@ -0,0 +1,373 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/cpu.h>
#include <linux/cpumask.h>
#include <linux/errno.h>
#include <linux/kvm_types.h>
#include <linux/list.h>
#include <linux/percpu.h>
#include <asm/perf_event.h>
#include <asm/processor.h>
#include <asm/virt.h>
#include <asm/vmx.h>
struct x86_virt_ops {
int feature;
int (*enable_virtualization_cpu)(void);
int (*disable_virtualization_cpu)(void);
void (*emergency_disable_virtualization_cpu)(void);
};
static struct x86_virt_ops virt_ops __ro_after_init;
__visible bool virt_rebooting;
EXPORT_SYMBOL_FOR_KVM(virt_rebooting);
static DEFINE_PER_CPU(int, virtualization_nr_users);
static cpu_emergency_virt_cb __rcu *kvm_emergency_callback;
void x86_virt_register_emergency_callback(cpu_emergency_virt_cb *callback)
{
if (WARN_ON_ONCE(rcu_access_pointer(kvm_emergency_callback)))
return;
rcu_assign_pointer(kvm_emergency_callback, callback);
}
EXPORT_SYMBOL_FOR_KVM(x86_virt_register_emergency_callback);
void x86_virt_unregister_emergency_callback(cpu_emergency_virt_cb *callback)
{
if (WARN_ON_ONCE(rcu_access_pointer(kvm_emergency_callback) != callback))
return;
rcu_assign_pointer(kvm_emergency_callback, NULL);
synchronize_rcu();
}
EXPORT_SYMBOL_FOR_KVM(x86_virt_unregister_emergency_callback);
static void x86_virt_invoke_kvm_emergency_callback(void)
{
cpu_emergency_virt_cb *kvm_callback;
/*
* RCU may not be watching the crashing CPU here, so rcu_dereference()
* triggers a suspicious-RCU-usage splat. In principle, a concurrent
* KVM module unload could race with this read; see commit 2baa33a8ddd6
* ("KVM: x86: Leave user-return notifier registered on reboot/shutdown")
* which notes that nothing prevents module unload during panic/reboot.
*
* However, taking a lock here would be riskier than the current race:
* the system is going down via NMI shootdown, and any lock could be
* held by an already-stopped CPU. Use rcu_dereference_raw() to silence
* the lockdep splat and accept the comically small remaining race;
* panic context inherently cannot guarantee complete correctness.
*/
kvm_callback = rcu_dereference_raw(kvm_emergency_callback);
if (kvm_callback)
kvm_callback();
}
#if IS_ENABLED(CONFIG_KVM_INTEL)
static DEFINE_PER_CPU(struct vmcs *, root_vmcs);
static int x86_virt_cpu_vmxon(void)
{
u64 vmxon_pointer = __pa(per_cpu(root_vmcs, raw_smp_processor_id()));
u64 msr;
cr4_set_bits(X86_CR4_VMXE);
asm goto("1: vmxon %[vmxon_pointer]\n\t"
_ASM_EXTABLE(1b, %l[fault])
: : [vmxon_pointer] "m"(vmxon_pointer)
: : fault);
return 0;
fault:
WARN_ONCE(1, "VMXON faulted, MSR_IA32_FEAT_CTL (0x3a) = 0x%llx\n",
rdmsrq_safe(MSR_IA32_FEAT_CTL, &msr) ? 0xdeadbeef : msr);
cr4_clear_bits(X86_CR4_VMXE);
return -EFAULT;
}
static int x86_vmx_enable_virtualization_cpu(void)
{
int r;
if (cr4_read_shadow() & X86_CR4_VMXE)
return -EBUSY;
intel_pt_handle_vmx(1);
r = x86_virt_cpu_vmxon();
if (r) {
intel_pt_handle_vmx(0);
return r;
}
return 0;
}
/*
* Disable VMX and clear CR4.VMXE (even if VMXOFF faults)
*
* Note, VMXOFF causes a #UD if the CPU is !post-VMXON, but it's impossible to
* atomically track post-VMXON state, e.g. this may be called in NMI context.
* Eat all faults as all other faults on VMXOFF faults are mode related, i.e.
* faults are guaranteed to be due to the !post-VMXON check unless the CPU is
* magically in RM, VM86, compat mode, or at CPL>0.
*/
static int x86_vmx_disable_virtualization_cpu(void)
{
int r = -EIO;
asm goto("1: vmxoff\n\t"
_ASM_EXTABLE(1b, %l[fault])
::: "cc", "memory" : fault);
r = 0;
fault:
cr4_clear_bits(X86_CR4_VMXE);
intel_pt_handle_vmx(0);
return r;
}
static void x86_vmx_emergency_disable_virtualization_cpu(void)
{
virt_rebooting = true;
/*
* Note, CR4.VMXE can be _cleared_ in NMI context, but it can only be
* set in task context. If this races with _another_ emergency call
* from NMI context, VMCLEAR (in KVM) and VMXOFF may #UD, but KVM and
* the kernel will eat those faults due to virt_rebooting being set by
* the interrupting NMI callback.
*/
if (!(__read_cr4() & X86_CR4_VMXE))
return;
x86_virt_invoke_kvm_emergency_callback();
x86_vmx_disable_virtualization_cpu();
}
static __init void x86_vmx_exit(void)
{
int cpu;
for_each_possible_cpu(cpu) {
free_page((unsigned long)per_cpu(root_vmcs, cpu));
per_cpu(root_vmcs, cpu) = NULL;
}
}
static __init int __x86_vmx_init(void)
{
const struct x86_virt_ops vmx_ops = {
.feature = X86_FEATURE_VMX,
.enable_virtualization_cpu = x86_vmx_enable_virtualization_cpu,
.disable_virtualization_cpu = x86_vmx_disable_virtualization_cpu,
.emergency_disable_virtualization_cpu = x86_vmx_emergency_disable_virtualization_cpu,
};
u64 basic_msr;
u32 rev_id;
int cpu;
if (!cpu_feature_enabled(X86_FEATURE_VMX))
return -EOPNOTSUPP;
rdmsrq(MSR_IA32_VMX_BASIC, basic_msr);
/* IA-32 SDM Vol 3B: VMCS size is never greater than 4kB. */
if (WARN_ON_ONCE(vmx_basic_vmcs_size(basic_msr) > PAGE_SIZE))
return -EIO;
/*
* Even if eVMCS is enabled (or will be enabled?), and even though not
* explicitly documented by TLFS, the root VMCS passed to VMXON should
* still be marked with the revision_id reported by the physical CPU.
*/
rev_id = vmx_basic_vmcs_revision_id(basic_msr);
for_each_possible_cpu(cpu) {
int node = cpu_to_node(cpu);
struct page *page;
struct vmcs *vmcs;
page = __alloc_pages_node(node, GFP_KERNEL | __GFP_ZERO, 0);
if (WARN_ON_ONCE(!page)) {
x86_vmx_exit();
return -ENOMEM;
}
vmcs = page_address(page);
vmcs->hdr.revision_id = rev_id;
per_cpu(root_vmcs, cpu) = vmcs;
}
memcpy(&virt_ops, &vmx_ops, sizeof(virt_ops));
return 0;
}
static __init int x86_vmx_init(void)
{
int r;
r = __x86_vmx_init();
if (r)
setup_clear_cpu_cap(X86_FEATURE_VMX);
return r;
}
#else
static __init int x86_vmx_init(void) { return -EOPNOTSUPP; }
static __init void x86_vmx_exit(void) { }
#endif
#if IS_ENABLED(CONFIG_KVM_AMD)
static int x86_svm_enable_virtualization_cpu(void)
{
u64 efer;
rdmsrq(MSR_EFER, efer);
if (efer & EFER_SVME)
return -EBUSY;
wrmsrq(MSR_EFER, efer | EFER_SVME);
return 0;
}
static int x86_svm_disable_virtualization_cpu(void)
{
int r = -EIO;
u64 efer;
/*
* Force GIF=1 prior to disabling SVM, e.g. to ensure INIT and
* NMI aren't blocked.
*/
asm goto("1: stgi\n\t"
_ASM_EXTABLE(1b, %l[fault])
::: "memory" : fault);
r = 0;
fault:
rdmsrq(MSR_EFER, efer);
wrmsrq(MSR_EFER, efer & ~EFER_SVME);
return r;
}
static void x86_svm_emergency_disable_virtualization_cpu(void)
{
u64 efer;
virt_rebooting = true;
rdmsrq(MSR_EFER, efer);
if (!(efer & EFER_SVME))
return;
x86_virt_invoke_kvm_emergency_callback();
x86_svm_disable_virtualization_cpu();
}
static __init int x86_svm_init(void)
{
const struct x86_virt_ops svm_ops = {
.feature = X86_FEATURE_SVM,
.enable_virtualization_cpu = x86_svm_enable_virtualization_cpu,
.disable_virtualization_cpu = x86_svm_disable_virtualization_cpu,
.emergency_disable_virtualization_cpu = x86_svm_emergency_disable_virtualization_cpu,
};
if (!cpu_feature_enabled(X86_FEATURE_SVM) ||
cc_platform_has(CC_ATTR_GUEST_MEM_ENCRYPT))
return -EOPNOTSUPP;
memcpy(&virt_ops, &svm_ops, sizeof(virt_ops));
return 0;
}
#else
static __init int x86_svm_init(void) { return -EOPNOTSUPP; }
#endif
int x86_virt_get_ref(int feat)
{
int r;
/* Ensure the !feature check can't get false positives. */
BUILD_BUG_ON(!X86_FEATURE_SVM || !X86_FEATURE_VMX);
if (!virt_ops.feature || virt_ops.feature != feat)
return -EOPNOTSUPP;
guard(preempt)();
if (this_cpu_inc_return(virtualization_nr_users) > 1)
return 0;
r = virt_ops.enable_virtualization_cpu();
if (r)
WARN_ON_ONCE(this_cpu_dec_return(virtualization_nr_users));
return r;
}
EXPORT_SYMBOL_FOR_KVM(x86_virt_get_ref);
void x86_virt_put_ref(int feat)
{
guard(preempt)();
if (WARN_ON_ONCE(!this_cpu_read(virtualization_nr_users)) ||
this_cpu_dec_return(virtualization_nr_users))
return;
BUG_ON(virt_ops.disable_virtualization_cpu() && !virt_rebooting);
}
EXPORT_SYMBOL_FOR_KVM(x86_virt_put_ref);
/*
* Disable virtualization, i.e. VMX or SVM, to ensure INIT is recognized during
* reboot. VMX blocks INIT if the CPU is post-VMXON, and SVM blocks INIT if
* GIF=0, i.e. if the crash occurred between CLGI and STGI.
*/
int x86_virt_emergency_disable_virtualization_cpu(void)
{
if (!virt_ops.feature)
return -EOPNOTSUPP;
/*
* IRQs must be disabled as virtualization is enabled in hardware via
* function call IPIs, i.e. IRQs need to be disabled to guarantee
* virtualization stays disabled.
*/
lockdep_assert_irqs_disabled();
/*
* Do the NMI shootdown even if virtualization is off on _this_ CPU, as
* other CPUs may have virtualization enabled.
*
* TODO: Track whether or not virtualization might be enabled on other
* CPUs? May not be worth avoiding the NMI shootdown...
*/
virt_ops.emergency_disable_virtualization_cpu();
return 0;
}
void __init x86_virt_init(void)
{
/*
* Attempt to initialize both SVM and VMX, and simply use whichever one
* is present. Rsefuse to enable/use SVM or VMX if both are somehow
* supported. No known CPU supports both SVM and VMX.
*/
bool has_vmx = !x86_vmx_init();
bool has_svm = !x86_svm_init();
if (WARN_ON_ONCE(has_vmx && has_svm)) {
x86_vmx_exit();
memset(&virt_ops, 0, sizeof(virt_ops));
}
}
+4
View File
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_KVM_AMD_SEV) += sev.o
obj-$(CONFIG_CPU_SUP_AMD) += cmdline.o
+45
View File
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD SVM-SEV command line parsing support
*
* Copyright (C) 2023 - 2024 Advanced Micro Devices, Inc.
*
* Author: Michael Roth <michael.roth@amd.com>
*/
#include <linux/string.h>
#include <linux/printk.h>
#include <linux/cache.h>
#include <linux/cpufeature.h>
#include <asm/sev-common.h>
struct sev_config sev_cfg __read_mostly;
static int __init init_sev_config(char *str)
{
char *s;
while ((s = strsep(&str, ","))) {
if (!strcmp(s, "debug")) {
sev_cfg.debug = true;
continue;
}
if (!strcmp(s, "nosnp")) {
if (!cpu_feature_enabled(X86_FEATURE_HYPERVISOR)) {
setup_clear_cpu_cap(X86_FEATURE_SEV_SNP);
cc_platform_clear(CC_ATTR_HOST_SEV_SNP);
continue;
} else {
goto warn;
}
}
warn:
pr_info("SEV command-line option '%s' was not recognized\n", s);
}
return 1;
}
__setup("sev=", init_sev_config);
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_INTEL_TDX_HOST) += tdx/
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += seamcall.o seamldr.o tdx.o
@@ -0,0 +1,64 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/linkage.h>
#include <asm/frame.h>
#include "tdxcall.S"
/*
* __seamcall() - Host-side interface functions to SEAM software
* (the P-SEAMLDR or the TDX module).
*
* __seamcall() function ABI:
*
* @fn (RDI) - SEAMCALL Leaf number, moved to RAX
* @args (RSI) - struct tdx_module_args for input
*
* Only RCX/RDX/R8-R11 are used as input registers.
*
* Return (via RAX) TDX_SEAMCALL_VMFAILINVALID if the SEAMCALL itself
* fails, or the completion status of the SEAMCALL leaf function.
*/
SYM_FUNC_START(__seamcall)
TDX_MODULE_CALL host=1
SYM_FUNC_END(__seamcall)
/*
* __seamcall_ret() - Host-side interface functions to SEAM software
* (the P-SEAMLDR or the TDX module), with saving output registers to
* the 'struct tdx_module_args' used as input.
*
* __seamcall_ret() function ABI:
*
* @fn (RDI) - SEAMCALL Leaf number, moved to RAX
* @args (RSI) - struct tdx_module_args for input and output
*
* Only RCX/RDX/R8-R11 are used as input/output registers.
*
* Return (via RAX) TDX_SEAMCALL_VMFAILINVALID if the SEAMCALL itself
* fails, or the completion status of the SEAMCALL leaf function.
*/
SYM_FUNC_START(__seamcall_ret)
TDX_MODULE_CALL host=1 ret=1
SYM_FUNC_END(__seamcall_ret)
/* KVM requires non-instrumentable __seamcall_saved_ret() for TDH.VP.ENTER */
.section .noinstr.text, "ax"
/*
* __seamcall_saved_ret() - Host-side interface functions to SEAM software
* (the P-SEAMLDR or the TDX module), with saving output registers to the
* 'struct tdx_module_args' used as input.
*
* __seamcall_saved_ret() function ABI:
*
* @fn (RDI) - SEAMCALL Leaf number, moved to RAX
* @args (RSI) - struct tdx_module_args for input and output
*
* All registers in @args are used as input/output registers.
*
* Return (via RAX) TDX_SEAMCALL_VMFAILINVALID if the SEAMCALL itself
* fails, or the completion status of the SEAMCALL leaf function.
*/
SYM_FUNC_START(__seamcall_saved_ret)
TDX_MODULE_CALL host=1 ret=1 saved=1
SYM_FUNC_END(__seamcall_saved_ret)
@@ -0,0 +1,109 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* SEAMCALL utilities for TDX host-side operations.
*
* Provides convenient wrappers around SEAMCALL assembly with retry logic,
* error reporting and cache coherency tracking.
*
* Copyright (C) 2021-2023 Intel Corporation
*/
#ifndef _X86_VIRT_SEAMCALL_INTERNAL_H
#define _X86_VIRT_SEAMCALL_INTERNAL_H
#include <linux/printk.h>
#include <linux/types.h>
#include <asm/archrandom.h>
#include <asm/processor.h>
#include <asm/tdx.h>
u64 __seamcall(u64 fn, struct tdx_module_args *args);
u64 __seamcall_ret(u64 fn, struct tdx_module_args *args);
u64 __seamcall_saved_ret(u64 fn, struct tdx_module_args *args);
typedef u64 (*sc_func_t)(u64 fn, struct tdx_module_args *args);
static __always_inline u64 __seamcall_dirty_cache(sc_func_t func, u64 fn,
struct tdx_module_args *args)
{
lockdep_assert_preemption_disabled();
/*
* SEAMCALLs are made to the TDX module and can generate dirty
* cachelines of TDX private memory. Mark cache state incoherent
* so that the cache can be flushed during kexec.
*
* This needs to be done before actually making the SEAMCALL,
* because kexec-ing CPU could send NMI to stop remote CPUs,
* in which case even disabling IRQ won't help here.
*/
this_cpu_write(cache_state_incoherent, true);
return func(fn, args);
}
static __always_inline u64 sc_retry(sc_func_t func, u64 fn,
struct tdx_module_args *args)
{
int retry = RDRAND_RETRY_LOOPS;
u64 ret;
do {
preempt_disable();
ret = __seamcall_dirty_cache(func, fn, args);
preempt_enable();
} while (ret == TDX_RND_NO_ENTROPY && --retry);
return ret;
}
#define seamcall(_fn, _args) sc_retry(__seamcall, (_fn), (_args))
#define seamcall_ret(_fn, _args) sc_retry(__seamcall_ret, (_fn), (_args))
#define seamcall_saved_ret(_fn, _args) sc_retry(__seamcall_saved_ret, (_fn), (_args))
typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args);
static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args)
{
pr_err("SEAMCALL (0x%016llx) failed: 0x%016llx\n", fn, err);
}
static inline void seamcall_err_ret(u64 fn, u64 err,
struct tdx_module_args *args)
{
seamcall_err(fn, err, args);
pr_err("RCX 0x%016llx RDX 0x%016llx R08 0x%016llx\n",
args->rcx, args->rdx, args->r8);
pr_err("R09 0x%016llx R10 0x%016llx R11 0x%016llx\n",
args->r9, args->r10, args->r11);
}
static __always_inline int sc_retry_prerr(sc_func_t func,
sc_err_func_t err_func,
u64 fn, struct tdx_module_args *args)
{
u64 sret = sc_retry(func, fn, args);
if (sret == TDX_SUCCESS)
return 0;
if (sret == TDX_SEAMCALL_VMFAILINVALID)
return -ENODEV;
if (sret == TDX_SEAMCALL_GP)
return -EOPNOTSUPP;
if (sret == TDX_SEAMCALL_UD)
return -EACCES;
err_func(fn, sret, args);
return -EIO;
}
#define seamcall_prerr(__fn, __args) \
sc_retry_prerr(__seamcall, seamcall_err, (__fn), (__args))
#define seamcall_prerr_ret(__fn, __args) \
sc_retry_prerr(__seamcall_ret, seamcall_err_ret, (__fn), (__args))
#endif /* _X86_VIRT_SEAMCALL_INTERNAL_H */
+368
View File
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0
/*
* P-SEAMLDR support for TDX module management features like runtime updates
*
* Copyright (C) 2025 Intel Corporation
*/
#define pr_fmt(fmt) "seamldr: " fmt
#include <linux/bug.h>
#include <linux/mm.h>
#include <linux/nmi.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/stop_machine.h>
#include <asm/cpufeature.h>
#include <asm/cpufeatures.h>
#include <asm/seamldr.h>
#include "seamcall_internal.h"
#include "tdx.h"
/* P-SEAMLDR SEAMCALL leaf function */
#define P_SEAMLDR_INFO 0x8000000000000000
#define P_SEAMLDR_INSTALL 0x8000000000000001
#define SEAMLDR_MAX_NR_MODULE_PAGES 496
#define SEAMLDR_MAX_NR_SIG_PAGES 1
/*
* The seamldr_params "scenario" field specifies the operation mode:
* 0: Install TDX module from scratch (not used by kernel)
* 1: Update existing TDX module to a compatible version
*/
#define SEAMLDR_SCENARIO_UPDATE 1
/*
* This is the "SEAMLDR_PARAMS" data structure defined in the
* "SEAM Loader (SEAMLDR) Interface Specification".
*
* It is the in-memory ABI that the kernel passes to the P-SEAMLDR
* to update the TDX module. It breaks the TDX module image up in
* page-size pieces.
*/
struct seamldr_params {
u32 version;
u32 scenario;
u64 sigstruct_pages_pa_list[SEAMLDR_MAX_NR_SIG_PAGES];
u8 reserved[104];
u64 module_nr_pages;
u64 module_pages_pa_list[SEAMLDR_MAX_NR_MODULE_PAGES];
} __packed;
static_assert(sizeof(struct seamldr_params) == 4096);
/*
* Serialize P-SEAMLDR calls since the hardware only allows a single CPU to
* interact with P-SEAMLDR simultaneously. Use raw version as the calls can
* be made with interrupts disabled, where plain spinlocks are prohibited in
* PREEMPT_RT kernels as they become sleeping locks.
*/
static DEFINE_RAW_SPINLOCK(seamldr_lock);
static int seamldr_call(u64 fn, struct tdx_module_args *args)
{
/*
* With this bug, P-SEAMLDR calls corrupt the VMCS
* pointer and must be avoided. This path should be
* unreachable since sysfs hides the ABIs.
*/
if (boot_cpu_has_bug(X86_BUG_SEAMRET_INVD_VMCS)) {
WARN_ON(1);
return -EINVAL;
}
guard(raw_spinlock)(&seamldr_lock);
return seamcall_prerr(fn, args);
}
int seamldr_get_info(struct seamldr_info *seamldr_info)
{
struct tdx_module_args args = {};
/*
* Use slow_virt_to_phys() since @seamldr_info may be allocated on
* the stack.
*/
args.rcx = slow_virt_to_phys(seamldr_info);
return seamldr_call(P_SEAMLDR_INFO, &args);
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
/* Call into P-SEAMLDR to install a TDX module update */
static int seamldr_install(const struct seamldr_params *params)
{
struct tdx_module_args args = {};
args.rcx = __pa(params);
return seamldr_call(P_SEAMLDR_INSTALL, &args);
}
#define TDX_IMAGE_VERSION_2 0x200
/* First page of the on-disk module update image: */
struct tdx_image_header {
u16 version;
u16 checksum;
u8 signature[8];
u32 sigstruct_nr_pages;
u32 module_nr_pages;
u8 reserved[4076];
} __packed;
#define TDX_IMAGE_HEADER_SIZE sizeof(struct tdx_image_header)
static_assert(TDX_IMAGE_HEADER_SIZE == 4096);
/*
* Intel TDX module update ABI structure. aka. "TDX module blob".
* This is the on-disk format that fw_upload lands in a kernel
* buffer.
*
* @payload contains sigstruct pages followed by module pages.
*/
struct tdx_image {
struct tdx_image_header header;
u8 payload[];
};
/*
* Given a vmalloc() allocation, write all of the backing physical
* addresses to pa_list[]. Caller guarantees that the array is big
* enough.
*/
static void populate_pa_list(u64 *pa_list, const u8 *vmalloc_addr, u32 vmalloc_len_pages)
{
int i;
for (i = 0; i < vmalloc_len_pages; i++) {
unsigned long offset = i * PAGE_SIZE;
unsigned long pfn = vmalloc_to_pfn(&vmalloc_addr[offset]);
pa_list[i] = pfn << PAGE_SHIFT;
}
}
static void populate_seamldr_params(struct seamldr_params *params,
const u8 *sig, u32 sig_nr_pages,
const u8 *mod, u32 mod_nr_pages)
{
params->version = 0;
params->scenario = SEAMLDR_SCENARIO_UPDATE;
params->module_nr_pages = mod_nr_pages;
populate_pa_list(params->sigstruct_pages_pa_list, sig, sig_nr_pages);
populate_pa_list(params->module_pages_pa_list, mod, mod_nr_pages);
}
/*
* @image points to a vmalloc()'d 'struct tdx_image'. Transform
* it into @params which is the P-SEAMLDR ABI format.
*/
static int init_seamldr_params(struct seamldr_params *params,
const struct tdx_image *image,
u32 image_len)
{
const struct tdx_image_header *header = &image->header;
u32 sigstruct_len = header->sigstruct_nr_pages * PAGE_SIZE;
u32 module_len = header->module_nr_pages * PAGE_SIZE;
u8 *header_start = (u8 *)header;
u8 *header_end = header_start + TDX_IMAGE_HEADER_SIZE;
u8 *sigstruct_start = header_end;
u8 *sigstruct_end = sigstruct_start + sigstruct_len;
u8 *module_start = sigstruct_end;
/* Check the calculated payload size against the image size. */
if (TDX_IMAGE_HEADER_SIZE + sigstruct_len + module_len != image_len)
return -EINVAL;
/* Reject unsupported tdx_image ABI versions. */
if (header->version != TDX_IMAGE_VERSION_2)
return -EINVAL;
if (header->sigstruct_nr_pages > SEAMLDR_MAX_NR_SIG_PAGES ||
header->module_nr_pages > SEAMLDR_MAX_NR_MODULE_PAGES)
return -EINVAL;
if (memcmp(header->signature, "TDX-BLOB", sizeof(header->signature)))
return -EINVAL;
if (memchr_inv(header->reserved, 0, sizeof(header->reserved)))
return -EINVAL;
populate_seamldr_params(params, sigstruct_start, header->sigstruct_nr_pages,
module_start, header->module_nr_pages);
return 0;
}
/*
* During a TDX module update, all CPUs start from MODULE_UPDATE_START and
* progress to MODULE_UPDATE_DONE. Each state is associated with certain
* work. For some states, just one CPU needs to perform the work, while
* other CPUs just wait during those states.
*/
enum module_update_state {
MODULE_UPDATE_START,
MODULE_UPDATE_SHUTDOWN,
MODULE_UPDATE_CPU_INSTALL,
MODULE_UPDATE_CPU_INIT,
MODULE_UPDATE_RUN_UPDATE,
MODULE_UPDATE_DONE,
};
static struct update_ctrl {
enum module_update_state state;
int num_ack;
int num_failed;
/*
* Protect update_ctrl. Raw spinlock as it will be acquired from
* interrupt-disabled contexts.
*/
raw_spinlock_t lock;
} update_ctrl;
/* Called with ctrl->lock held or during initialization. */
static void __set_target_state(struct update_ctrl *ctrl,
enum module_update_state newstate)
{
/* Reset ack counter. */
ctrl->num_ack = 0;
ctrl->state = newstate;
}
/* Last one to ack a state moves to the next state. */
static void ack_state(struct update_ctrl *ctrl, int result)
{
raw_spin_lock(&ctrl->lock);
ctrl->num_failed += !!result;
ctrl->num_ack++;
if (ctrl->num_ack == num_online_cpus() && !ctrl->num_failed)
__set_target_state(ctrl, ctrl->state + 1);
raw_spin_unlock(&ctrl->lock);
}
static void init_state(struct update_ctrl *ctrl)
{
raw_spin_lock_init(&ctrl->lock);
__set_target_state(ctrl, MODULE_UPDATE_START + 1);
ctrl->num_failed = 0;
}
/*
* See multi_cpu_stop() from where this multi-cpu state-machine was
* adopted.
*/
static int do_seamldr_install_module(void *seamldr_params)
{
enum module_update_state curstate = MODULE_UPDATE_START;
enum module_update_state newstate;
bool is_lead_cpu = false;
int ret = 0;
/*
* Some steps must be run on exactly one CPU. Pick a "lead" CPU to
* execute those steps. Use CPU 0 because it is always online.
*/
if (smp_processor_id() == 0)
is_lead_cpu = true;
do {
newstate = READ_ONCE(update_ctrl.state);
if (curstate == newstate) {
cpu_relax();
continue;
}
curstate = newstate;
switch (curstate) {
case MODULE_UPDATE_SHUTDOWN:
if (is_lead_cpu)
ret = tdx_module_shutdown();
break;
case MODULE_UPDATE_CPU_INSTALL:
ret = seamldr_install(seamldr_params);
break;
case MODULE_UPDATE_CPU_INIT:
ret = tdx_cpu_enable();
break;
case MODULE_UPDATE_RUN_UPDATE:
if (is_lead_cpu)
ret = tdx_module_run_update();
break;
default:
break;
}
ack_state(&update_ctrl, ret);
} while (curstate != MODULE_UPDATE_DONE &&
!READ_ONCE(update_ctrl.num_failed));
return ret;
}
/**
* seamldr_install_module - Install a new TDX module.
* @data: Pointer to the TDX module image.
* @data_len: Size of the TDX module image.
*
* Returns 0 on success, negative error code on failure.
*/
int seamldr_install_module(const u8 *data, u32 data_len)
{
struct seamldr_params *params;
const struct tdx_image *image;
int ret;
/*
* init_seamldr_params() reads the header early.
* Ensure there is enough data to do at least that.
*/
if (data_len < TDX_IMAGE_HEADER_SIZE)
return -EINVAL;
image = (const struct tdx_image *)data;
params = kzalloc_obj(*params);
if (!params)
return -ENOMEM;
/* Populate 'params' from 'image'. */
ret = init_seamldr_params(params, image, data_len);
if (ret)
goto out;
/* Ensure a stable set of online CPUs for the update process. */
cpus_read_lock();
init_state(&update_ctrl);
ret = stop_machine_cpuslocked(do_seamldr_install_module, params,
cpu_online_mask);
cpus_read_unlock();
out:
kfree(params);
return ret;
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host");
/*
* stop_machine() does not interrupt preemption-disabled regions.
* Simply disabling preempt prevents updates.
*/
void seamldr_lock_module_update(void)
{
preempt_disable();
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_lock_module_update, "tdx-host");
void seamldr_unlock_module_update(void)
{
preempt_enable();
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_unlock_module_update, "tdx-host");
File diff suppressed because it is too large Load Diff
+116
View File
@@ -0,0 +1,116 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _X86_VIRT_TDX_H
#define _X86_VIRT_TDX_H
#include <linux/bits.h>
/*
* This file contains both macros and data structures defined by the TDX
* architecture and Linux defined software data structures and functions.
* The two should not be mixed together for better readability. The
* architectural definitions come first.
*/
/*
* TDX module SEAMCALL leaf functions
*/
#define TDH_VP_ENTER 0
#define TDH_MNG_ADDCX 1
#define TDH_MEM_PAGE_ADD 2
#define TDH_MEM_SEPT_ADD 3
#define TDH_VP_ADDCX 4
#define TDH_MEM_PAGE_AUG 6
#define TDH_MEM_RANGE_BLOCK 7
#define TDH_MNG_KEY_CONFIG 8
#define TDH_MNG_CREATE 9
#define TDH_MNG_RD 11
#define TDH_MR_EXTEND 16
#define TDH_MR_FINALIZE 17
#define TDH_VP_FLUSH 18
#define TDH_MNG_VPFLUSHDONE 19
#define TDH_VP_CREATE 10
#define TDH_MNG_KEY_FREEID 20
#define TDH_MNG_INIT 21
#define TDH_VP_INIT 22
#define TDH_PHYMEM_PAGE_RDMD 24
#define TDH_VP_RD 26
#define TDH_PHYMEM_PAGE_RECLAIM 28
#define TDH_MEM_PAGE_REMOVE 29
#define TDH_SYS_KEY_CONFIG 31
#define TDH_SYS_INIT 33
#define TDH_SYS_RD 34
#define TDH_SYS_LP_INIT 35
#define TDH_SYS_TDMR_INIT 36
#define TDH_MEM_TRACK 38
#define TDH_PHYMEM_CACHE_WB 40
#define TDH_PHYMEM_PAGE_WBINVD 41
#define TDH_VP_WR 43
#define TDH_SYS_CONFIG 45
#define TDH_SYS_SHUTDOWN 52
#define TDH_SYS_UPDATE 53
#define TDH_SYS_DISABLE 69
/*
* SEAMCALL leaf:
*
* Bit 15:0 Leaf number
* Bit 23:16 Version number
*/
#define TDX_VERSION_SHIFT 16
/* TDX page types */
#define PT_NDA 0x0
#define PT_RSVD 0x1
struct tdmr_reserved_area {
u64 offset;
u64 size;
} __packed;
#define TDMR_INFO_ALIGNMENT 512
#define TDMR_INFO_PA_ARRAY_ALIGNMENT 512
struct tdmr_info {
u64 base;
u64 size;
u64 pamt_1g_base;
u64 pamt_1g_size;
u64 pamt_2m_base;
u64 pamt_2m_size;
u64 pamt_4k_base;
u64 pamt_4k_size;
/*
* The actual number of reserved areas depends on the value of
* field MD_FIELD_ID_MAX_RESERVED_PER_TDMR in the TDX module
* global metadata.
*/
DECLARE_FLEX_ARRAY(struct tdmr_reserved_area, reserved_areas);
} __packed __aligned(TDMR_INFO_ALIGNMENT);
/*
* Do not put any hardware-defined TDX structure representations below
* this comment!
*/
struct tdx_memblock {
struct list_head list;
unsigned long start_pfn;
unsigned long end_pfn;
int nid;
};
/* Warn if kernel has less than TDMR_NR_WARN TDMRs after allocation */
#define TDMR_NR_WARN 4
struct tdmr_info_list {
void *tdmrs; /* Flexible array to hold 'tdmr_info's */
int nr_consumed_tdmrs; /* How many 'tdmr_info's are in use */
/* Metadata for finding target 'tdmr_info' and freeing @tdmrs */
int tdmr_sz; /* Size of one 'tdmr_info' */
int max_tdmrs; /* How many 'tdmr_info's are allocated */
};
int tdx_module_shutdown(void);
int tdx_module_run_update(void);
#endif
@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Automatically generated functions to read TDX global metadata.
*
* This file doesn't compile on its own as it lacks of inclusion
* of SEAMCALL wrapper primitive which reads global metadata.
* Include this file to other C file instead.
*/
static int get_tdx_sys_info_version(struct tdx_sys_info_version *sysinfo_version)
{
int ret = 0;
u64 val;
if (!ret && !(ret = read_sys_metadata_field(0x0800000100000003, &val)))
sysinfo_version->minor_version = val;
if (!ret && !(ret = read_sys_metadata_field(0x0800000100000004, &val)))
sysinfo_version->major_version = val;
if (!ret && !(ret = read_sys_metadata_field(0x0800000100000005, &val)))
sysinfo_version->update_version = val;
return ret;
}
static __init int get_tdx_sys_info_features(struct tdx_sys_info_features *sysinfo_features)
{
int ret = 0;
u64 val;
if (!ret && !(ret = read_sys_metadata_field(0x0A00000300000008, &val)))
sysinfo_features->tdx_features0 = val;
return ret;
}
static __init int get_tdx_sys_info_tdmr(struct tdx_sys_info_tdmr *sysinfo_tdmr)
{
int ret = 0;
u64 val;
if (!ret && !(ret = read_sys_metadata_field(0x9100000100000008, &val)))
sysinfo_tdmr->max_tdmrs = val;
if (!ret && !(ret = read_sys_metadata_field(0x9100000100000009, &val)))
sysinfo_tdmr->max_reserved_per_tdmr = val;
if (!ret && !(ret = read_sys_metadata_field(0x9100000100000010, &val)))
sysinfo_tdmr->pamt_4k_entry_size = val;
if (!ret && !(ret = read_sys_metadata_field(0x9100000100000011, &val)))
sysinfo_tdmr->pamt_2m_entry_size = val;
if (!ret && !(ret = read_sys_metadata_field(0x9100000100000012, &val)))
sysinfo_tdmr->pamt_1g_entry_size = val;
return ret;
}
static __init int get_tdx_sys_info_td_ctrl(struct tdx_sys_info_td_ctrl *sysinfo_td_ctrl)
{
int ret = 0;
u64 val;
if (!ret && !(ret = read_sys_metadata_field(0x9800000100000000, &val)))
sysinfo_td_ctrl->tdr_base_size = val;
if (!ret && !(ret = read_sys_metadata_field(0x9800000100000100, &val)))
sysinfo_td_ctrl->tdcs_base_size = val;
if (!ret && !(ret = read_sys_metadata_field(0x9800000100000200, &val)))
sysinfo_td_ctrl->tdvps_base_size = val;
return ret;
}
static __init int get_tdx_sys_info_td_conf(struct tdx_sys_info_td_conf *sysinfo_td_conf)
{
int ret = 0;
u64 val;
int i, j;
if (!ret && !(ret = read_sys_metadata_field(0x1900000300000000, &val)))
sysinfo_td_conf->attributes_fixed0 = val;
if (!ret && !(ret = read_sys_metadata_field(0x1900000300000001, &val)))
sysinfo_td_conf->attributes_fixed1 = val;
if (!ret && !(ret = read_sys_metadata_field(0x1900000300000002, &val)))
sysinfo_td_conf->xfam_fixed0 = val;
if (!ret && !(ret = read_sys_metadata_field(0x1900000300000003, &val)))
sysinfo_td_conf->xfam_fixed1 = val;
if (!ret && !(ret = read_sys_metadata_field(0x9900000100000004, &val)))
sysinfo_td_conf->num_cpuid_config = val;
if (!ret && !(ret = read_sys_metadata_field(0x9900000100000008, &val)))
sysinfo_td_conf->max_vcpus_per_td = val;
if (sysinfo_td_conf->num_cpuid_config > ARRAY_SIZE(sysinfo_td_conf->cpuid_config_leaves))
return -EINVAL;
for (i = 0; i < sysinfo_td_conf->num_cpuid_config; i++)
if (!ret && !(ret = read_sys_metadata_field(0x9900000300000400 + i, &val)))
sysinfo_td_conf->cpuid_config_leaves[i] = val;
if (sysinfo_td_conf->num_cpuid_config > ARRAY_SIZE(sysinfo_td_conf->cpuid_config_values))
return -EINVAL;
for (i = 0; i < sysinfo_td_conf->num_cpuid_config; i++)
for (j = 0; j < 2; j++)
if (!ret && !(ret = read_sys_metadata_field(0x9900000300000500 + i * 2 + j, &val)))
sysinfo_td_conf->cpuid_config_values[i][j] = val;
return ret;
}
static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff)
{
int ret;
u64 val;
ret = read_sys_metadata_field(0x8900000100000000, &val);
if (ret)
return ret;
sysinfo_handoff->module_hv = val;
return 0;
}
static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
{
int ret = 0;
ret = ret ?: get_tdx_sys_info_version(&sysinfo->version);
pr_info("Module version: " TDX_VERSION_FMT "\n",
sysinfo->version.major_version,
sysinfo->version.minor_version,
sysinfo->version.update_version);
ret = ret ?: get_tdx_sys_info_features(&sysinfo->features);
ret = ret ?: get_tdx_sys_info_tdmr(&sysinfo->tdmr);
ret = ret ?: get_tdx_sys_info_td_ctrl(&sysinfo->td_ctrl);
ret = ret ?: get_tdx_sys_info_td_conf(&sysinfo->td_conf);
return ret;
}
+220
View File
@@ -0,0 +1,220 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <asm/asm-offsets.h>
#include <asm/frame.h>
#include <asm/asm.h>
#include <asm/tdx.h>
/*
* TDCALL and SEAMCALL are supported in Binutils >= 2.36.
*/
#define tdcall .byte 0x66,0x0f,0x01,0xcc
#define seamcall .byte 0x66,0x0f,0x01,0xcf
/*
* TDX_MODULE_CALL - common helper macro for both
* TDCALL and SEAMCALL instructions.
*
* TDCALL - used by TDX guests to make requests to the
* TDX module and hypercalls to the VMM.
* SEAMCALL - used by TDX hosts to make requests to the
* TDX module.
*
*-------------------------------------------------------------------------
* TDCALL/SEAMCALL ABI:
*-------------------------------------------------------------------------
* Input Registers:
*
* RAX - TDCALL/SEAMCALL Leaf number.
* RCX,RDX,RDI,RSI,RBX,R8-R15 - TDCALL/SEAMCALL Leaf specific input registers.
*
* Output Registers:
*
* RAX - TDCALL/SEAMCALL instruction error code.
* RCX,RDX,RDI,RSI,RBX,R8-R15 - TDCALL/SEAMCALL Leaf specific output registers.
*
*-------------------------------------------------------------------------
*
* So while the common core (RAX,RCX,RDX,R8-R11) fits nicely in the
* callee-clobbered registers and even leaves RDI,RSI free to act as a
* base pointer, some leafs (e.g., VP.ENTER) make a giant mess of things.
*
* For simplicity, assume that anything that needs the callee-saved regs
* also tramples on RDI,RSI. This isn't strictly true, see for example
* TDH.EXPORT.MEM.
*/
.macro TDX_MODULE_CALL host:req ret=0 saved=0
FRAME_BEGIN
/* Move Leaf ID to RAX */
mov %rdi, %rax
/* Move other input regs from 'struct tdx_module_args' */
movq TDX_MODULE_rcx(%rsi), %rcx
movq TDX_MODULE_rdx(%rsi), %rdx
movq TDX_MODULE_r8(%rsi), %r8
movq TDX_MODULE_r9(%rsi), %r9
movq TDX_MODULE_r10(%rsi), %r10
movq TDX_MODULE_r11(%rsi), %r11
.if \saved
/*
* Move additional input regs from the structure. For simplicity
* assume that anything needs the callee-saved regs also tramples
* on RDI/RSI (see VP.ENTER).
*/
/* Save those callee-saved GPRs as mandated by the x86_64 ABI */
pushq %rbx
pushq %r12
pushq %r13
pushq %r14
pushq %r15
movq TDX_MODULE_r12(%rsi), %r12
movq TDX_MODULE_r13(%rsi), %r13
movq TDX_MODULE_r14(%rsi), %r14
movq TDX_MODULE_r15(%rsi), %r15
movq TDX_MODULE_rbx(%rsi), %rbx
.if \ret
/* Save the structure pointer as RSI is about to be clobbered */
pushq %rsi
.endif
movq TDX_MODULE_rdi(%rsi), %rdi
/* RSI needs to be done at last */
movq TDX_MODULE_rsi(%rsi), %rsi
.endif /* \saved */
.if \host
.Lseamcall\@:
seamcall
/*
* SEAMCALL instruction is essentially a VMExit from VMX root
* mode to SEAM VMX root mode. VMfailInvalid (CF=1) indicates
* that the targeted SEAM firmware is not loaded or disabled,
* or P-SEAMLDR is busy with another SEAMCALL. %rax is not
* changed in this case.
*
* Set %rax to TDX_SEAMCALL_VMFAILINVALID for VMfailInvalid.
* This value will never be used as actual SEAMCALL error code as
* it is from the Reserved status code class.
*/
jc .Lseamcall_vmfailinvalid\@
.else
tdcall
.endif
.if \ret
.if \saved
/*
* Restore the structure from stack to save the output registers
*
* In case of VP.ENTER returns due to TDVMCALL, all registers are
* valid thus no register can be used as spare to restore the
* structure from the stack (see "TDH.VP.ENTER Output Operands
* Definition on TDCALL(TDG.VP.VMCALL) Following a TD Entry").
* For this case, need to make one register as spare by saving it
* to the stack and then manually load the structure pointer to
* the spare register.
*
* Note for other TDCALLs/SEAMCALLs there are spare registers
* thus no need for such hack but just use this for all.
*/
pushq %rax /* save the TDCALL/SEAMCALL return code */
movq 8(%rsp), %rax /* restore the structure pointer */
movq %rsi, TDX_MODULE_rsi(%rax) /* save RSI */
popq %rax /* restore the return code */
popq %rsi /* pop the structure pointer */
/* Copy additional output regs to the structure */
movq %r12, TDX_MODULE_r12(%rsi)
movq %r13, TDX_MODULE_r13(%rsi)
movq %r14, TDX_MODULE_r14(%rsi)
movq %r15, TDX_MODULE_r15(%rsi)
movq %rbx, TDX_MODULE_rbx(%rsi)
movq %rdi, TDX_MODULE_rdi(%rsi)
.endif /* \saved */
/* Copy output registers to the structure */
movq %rcx, TDX_MODULE_rcx(%rsi)
movq %rdx, TDX_MODULE_rdx(%rsi)
movq %r8, TDX_MODULE_r8(%rsi)
movq %r9, TDX_MODULE_r9(%rsi)
movq %r10, TDX_MODULE_r10(%rsi)
movq %r11, TDX_MODULE_r11(%rsi)
.endif /* \ret */
.if \saved && \ret
/*
* Clear registers shared by guest for VP.VMCALL/VP.ENTER to prevent
* speculative use of guest's/VMM's values, including those are
* restored from the stack.
*
* See arch/x86/kvm/vmx/vmenter.S:
*
* In theory, a L1 cache miss when restoring register from stack
* could lead to speculative execution with guest's values.
*
* Note: RBP/RSP are not used as shared register. RSI has been
* restored already.
*
* XOR is cheap, thus unconditionally do for all leafs.
*/
xorl %ecx, %ecx
xorl %edx, %edx
xorl %r8d, %r8d
xorl %r9d, %r9d
xorl %r10d, %r10d
xorl %r11d, %r11d
xorl %r12d, %r12d
xorl %r13d, %r13d
xorl %r14d, %r14d
xorl %r15d, %r15d
xorl %ebx, %ebx
xorl %edi, %edi
.endif /* \ret && \host */
.if \host
.Lout\@:
.endif
.if \saved
/* Restore callee-saved GPRs as mandated by the x86_64 ABI */
popq %r15
popq %r14
popq %r13
popq %r12
popq %rbx
.endif /* \saved */
FRAME_END
RET
.if \host
.Lseamcall_vmfailinvalid\@:
mov $TDX_SEAMCALL_VMFAILINVALID, %rax
jmp .Lseamcall_fail\@
.Lseamcall_trap\@:
/*
* SEAMCALL caused #GP or #UD. By reaching here RAX contains
* the trap number. Convert the trap number to the TDX error
* code by setting TDX_SW_ERROR to the high 32-bits of RAX.
*
* Note cannot OR TDX_SW_ERROR directly to RAX as OR instruction
* only accepts 32-bit immediate at most.
*/
movq $TDX_SW_ERROR, %rdi
orq %rdi, %rax
.Lseamcall_fail\@:
.if \ret && \saved
/* pop the unused structure pointer back to RSI */
popq %rsi
.endif
jmp .Lout\@
_ASM_EXTABLE_FAULT(.Lseamcall\@, .Lseamcall_trap\@)
.endif /* \host */
.endm