#include "asm/page_types.h"
#define pr_fmt(fmt) "virt/tdx: " fmt
#include <linux/types.h>
#include <linux/cache.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/printk.h>
#include <linux/cpu.h>
#include <linux/spinlock.h>
#include <linux/percpu-defs.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/memblock.h>
#include <linux/memory.h>
#include <linux/minmax.h>
#include <linux/sizes.h>
#include <linux/pfn.h>
#include <linux/align.h>
#include <linux/sort.h>
#include <linux/log2.h>
#include <linux/acpi.h>
#include <linux/suspend.h>
#include <linux/idr.h>
#include <asm/page.h>
#include <asm/special_insns.h>
#include <asm/msr-index.h>
#include <asm/msr.h>
#include <asm/cpufeature.h>
#include <asm/tdx.h>
#include <asm/cpu_device_id.h>
#include <asm/processor.h>
#include <asm/mce.h>
#include "tdx.h"
static u32 tdx_global_keyid __ro_after_init;
static u32 tdx_guest_keyid_start __ro_after_init;
static u32 tdx_nr_guest_keyids __ro_after_init;
static DEFINE_IDA(tdx_guest_keyid_pool);
static DEFINE_PER_CPU(bool, tdx_lp_initialized);
static struct tdmr_info_list tdx_tdmr_list;
static enum tdx_module_status_t tdx_module_status;
static DEFINE_MUTEX(tdx_module_lock);
static LIST_HEAD(tdx_memlist);
static struct tdx_sys_info tdx_sysinfo;
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))
static int try_init_module_global(void)
{
struct tdx_module_args args = {};
static DEFINE_RAW_SPINLOCK(sysinit_lock);
static bool sysinit_done;
static int sysinit_ret;
lockdep_assert_irqs_disabled();
raw_spin_lock(&sysinit_lock);
if (sysinit_done)
goto out;
args.rcx = 0;
sysinit_ret = seamcall_prerr(TDH_SYS_INIT, &args);
if (sysinit_ret == -ENODEV)
pr_err("module not loaded\n");
sysinit_done = true;
out:
raw_spin_unlock(&sysinit_lock);
return sysinit_ret;
}
int tdx_cpu_enable(void)
{
struct tdx_module_args args = {};
int ret;
if (!boot_cpu_has(X86_FEATURE_TDX_HOST_PLATFORM))
return -ENODEV;
lockdep_assert_irqs_disabled();
if (__this_cpu_read(tdx_lp_initialized))
return 0;
ret = try_init_module_global();
if (ret)
return ret;
ret = seamcall_prerr(TDH_SYS_LP_INIT, &args);
if (ret)
return ret;
__this_cpu_write(tdx_lp_initialized, true);
return 0;
}
EXPORT_SYMBOL_GPL(tdx_cpu_enable);
static int add_tdx_memblock(struct list_head *tmb_list, unsigned long start_pfn,
unsigned long end_pfn, int nid)
{
struct tdx_memblock *tmb;
tmb = kmalloc(sizeof(*tmb), GFP_KERNEL);
if (!tmb)
return -ENOMEM;
INIT_LIST_HEAD(&tmb->list);
tmb->start_pfn = start_pfn;
tmb->end_pfn = end_pfn;
tmb->nid = nid;
list_add_tail(&tmb->list, tmb_list);
return 0;
}
static void free_tdx_memlist(struct list_head *tmb_list)
{
while (!list_empty(tmb_list)) {
struct tdx_memblock *tmb = list_first_entry(tmb_list,
struct tdx_memblock, list);
list_del(&tmb->list);
kfree(tmb);
}
}
static int build_tdx_memlist(struct list_head *tmb_list)
{
unsigned long start_pfn, end_pfn;
int i, nid, ret;
for_each_mem_pfn_range(i, MAX_NUMNODES, &start_pfn, &end_pfn, &nid) {
start_pfn = max(start_pfn, PHYS_PFN(SZ_1M));
if (start_pfn >= end_pfn)
continue;
ret = add_tdx_memblock(tmb_list, start_pfn, end_pfn, nid);
if (ret)
goto err;
}
return 0;
err:
free_tdx_memlist(tmb_list);
return ret;
}
static int read_sys_metadata_field(u64 field_id, u64 *data)
{
struct tdx_module_args args = {};
int ret;
args.rdx = field_id;
ret = seamcall_prerr_ret(TDH_SYS_RD, &args);
if (ret)
return ret;
*data = args.r8;
return 0;
}
#include "tdx_global_metadata.c"
static int check_features(struct tdx_sys_info *sysinfo)
{
u64 tdx_features0 = sysinfo->features.tdx_features0;
if (!(tdx_features0 & TDX_FEATURES0_NO_RBP_MOD)) {
pr_err("frame pointer (RBP) clobber bug present, upgrade TDX module\n");
return -EINVAL;
}
return 0;
}
static int tdmr_size_single(u16 max_reserved_per_tdmr)
{
int tdmr_sz;
tdmr_sz = sizeof(struct tdmr_info);
tdmr_sz += sizeof(struct tdmr_reserved_area) * max_reserved_per_tdmr;
return ALIGN(tdmr_sz, TDMR_INFO_ALIGNMENT);
}
static int alloc_tdmr_list(struct tdmr_info_list *tdmr_list,
struct tdx_sys_info_tdmr *sysinfo_tdmr)
{
size_t tdmr_sz, tdmr_array_sz;
void *tdmr_array;
tdmr_sz = tdmr_size_single(sysinfo_tdmr->max_reserved_per_tdmr);
tdmr_array_sz = tdmr_sz * sysinfo_tdmr->max_tdmrs;
tdmr_array = alloc_pages_exact(tdmr_array_sz,
GFP_KERNEL | __GFP_ZERO);
if (!tdmr_array)
return -ENOMEM;
tdmr_list->tdmrs = tdmr_array;
tdmr_list->tdmr_sz = tdmr_sz;
tdmr_list->max_tdmrs = sysinfo_tdmr->max_tdmrs;
tdmr_list->nr_consumed_tdmrs = 0;
return 0;
}
static void free_tdmr_list(struct tdmr_info_list *tdmr_list)
{
free_pages_exact(tdmr_list->tdmrs,
tdmr_list->max_tdmrs * tdmr_list->tdmr_sz);
}
static struct tdmr_info *tdmr_entry(struct tdmr_info_list *tdmr_list,
int idx)
{
int tdmr_info_offset = tdmr_list->tdmr_sz * idx;
return (void *)tdmr_list->tdmrs + tdmr_info_offset;
}
#define TDMR_ALIGNMENT SZ_1G
#define TDMR_ALIGN_DOWN(_addr) ALIGN_DOWN((_addr), TDMR_ALIGNMENT)
#define TDMR_ALIGN_UP(_addr) ALIGN((_addr), TDMR_ALIGNMENT)
static inline u64 tdmr_end(struct tdmr_info *tdmr)
{
return tdmr->base + tdmr->size;
}
static int fill_out_tdmrs(struct list_head *tmb_list,
struct tdmr_info_list *tdmr_list)
{
struct tdx_memblock *tmb;
int tdmr_idx = 0;
list_for_each_entry(tmb, tmb_list, list) {
struct tdmr_info *tdmr = tdmr_entry(tdmr_list, tdmr_idx);
u64 start, end;
start = TDMR_ALIGN_DOWN(PFN_PHYS(tmb->start_pfn));
end = TDMR_ALIGN_UP(PFN_PHYS(tmb->end_pfn));
if (tdmr->size) {
if (end <= tdmr_end(tdmr))
continue;
if (start < tdmr_end(tdmr))
start = tdmr_end(tdmr);
tdmr_idx++;
if (tdmr_idx >= tdmr_list->max_tdmrs) {
pr_warn("initialization failed: TDMRs exhausted.\n");
return -ENOSPC;
}
tdmr = tdmr_entry(tdmr_list, tdmr_idx);
}
tdmr->base = start;
tdmr->size = end - start;
}
tdmr_list->nr_consumed_tdmrs = tdmr_idx + 1;
if (tdmr_list->max_tdmrs - tdmr_list->nr_consumed_tdmrs < TDMR_NR_WARN)
pr_warn("consumed TDMRs reaching limit: %d used out of %d\n",
tdmr_list->nr_consumed_tdmrs,
tdmr_list->max_tdmrs);
return 0;
}
static unsigned long tdmr_get_pamt_sz(struct tdmr_info *tdmr, int pgsz,
u16 pamt_entry_size)
{
unsigned long pamt_sz, nr_pamt_entries;
switch (pgsz) {
case TDX_PS_4K:
nr_pamt_entries = tdmr->size >> PAGE_SHIFT;
break;
case TDX_PS_2M:
nr_pamt_entries = tdmr->size >> PMD_SHIFT;
break;
case TDX_PS_1G:
nr_pamt_entries = tdmr->size >> PUD_SHIFT;
break;
default:
WARN_ON_ONCE(1);
return 0;
}
pamt_sz = nr_pamt_entries * pamt_entry_size;
pamt_sz = ALIGN(pamt_sz, PAGE_SIZE);
return pamt_sz;
}
static int tdmr_get_nid(struct tdmr_info *tdmr, struct list_head *tmb_list)
{
struct tdx_memblock *tmb;
list_for_each_entry(tmb, tmb_list, list) {
if (tmb->end_pfn > PHYS_PFN(tdmr->base))
return tmb->nid;
}
pr_warn("TDMR [0x%llx, 0x%llx): unable to find local NUMA node for PAMT allocation, fallback to use node 0.\n",
tdmr->base, tdmr_end(tdmr));
return 0;
}
static int tdmr_set_up_pamt(struct tdmr_info *tdmr,
struct list_head *tmb_list,
u16 pamt_entry_size[])
{
unsigned long pamt_base[TDX_PS_NR];
unsigned long pamt_size[TDX_PS_NR];
unsigned long tdmr_pamt_base;
unsigned long tdmr_pamt_size;
struct page *pamt;
int pgsz, nid;
nid = tdmr_get_nid(tdmr, tmb_list);
tdmr_pamt_size = 0;
for (pgsz = TDX_PS_4K; pgsz < TDX_PS_NR; pgsz++) {
pamt_size[pgsz] = tdmr_get_pamt_sz(tdmr, pgsz,
pamt_entry_size[pgsz]);
tdmr_pamt_size += pamt_size[pgsz];
}
pamt = alloc_contig_pages(tdmr_pamt_size >> PAGE_SHIFT, GFP_KERNEL,
nid, &node_online_map);
if (!pamt)
return -ENOMEM;
tdmr_pamt_base = page_to_pfn(pamt) << PAGE_SHIFT;
for (pgsz = TDX_PS_4K; pgsz < TDX_PS_NR; pgsz++) {
pamt_base[pgsz] = tdmr_pamt_base;
tdmr_pamt_base += pamt_size[pgsz];
}
tdmr->pamt_4k_base = pamt_base[TDX_PS_4K];
tdmr->pamt_4k_size = pamt_size[TDX_PS_4K];
tdmr->pamt_2m_base = pamt_base[TDX_PS_2M];
tdmr->pamt_2m_size = pamt_size[TDX_PS_2M];
tdmr->pamt_1g_base = pamt_base[TDX_PS_1G];
tdmr->pamt_1g_size = pamt_size[TDX_PS_1G];
return 0;
}
static void tdmr_get_pamt(struct tdmr_info *tdmr, unsigned long *pamt_base,
unsigned long *pamt_size)
{
unsigned long pamt_bs, pamt_sz;
pamt_bs = tdmr->pamt_4k_base;
pamt_sz = tdmr->pamt_4k_size + tdmr->pamt_2m_size + tdmr->pamt_1g_size;
WARN_ON_ONCE((pamt_bs & ~PAGE_MASK) || (pamt_sz & ~PAGE_MASK));
*pamt_base = pamt_bs;
*pamt_size = pamt_sz;
}
static void tdmr_do_pamt_func(struct tdmr_info *tdmr,
void (*pamt_func)(unsigned long base, unsigned long size))
{
unsigned long pamt_base, pamt_size;
tdmr_get_pamt(tdmr, &pamt_base, &pamt_size);
if (!pamt_size)
return;
if (WARN_ON_ONCE(!pamt_base))
return;
pamt_func(pamt_base, pamt_size);
}
static void free_pamt(unsigned long pamt_base, unsigned long pamt_size)
{
free_contig_range(pamt_base >> PAGE_SHIFT, pamt_size >> PAGE_SHIFT);
}
static void tdmr_free_pamt(struct tdmr_info *tdmr)
{
tdmr_do_pamt_func(tdmr, free_pamt);
}
static void tdmrs_free_pamt_all(struct tdmr_info_list *tdmr_list)
{
int i;
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++)
tdmr_free_pamt(tdmr_entry(tdmr_list, i));
}
static int tdmrs_set_up_pamt_all(struct tdmr_info_list *tdmr_list,
struct list_head *tmb_list,
u16 pamt_entry_size[])
{
int i, ret = 0;
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++) {
ret = tdmr_set_up_pamt(tdmr_entry(tdmr_list, i), tmb_list,
pamt_entry_size);
if (ret)
goto err;
}
return 0;
err:
tdmrs_free_pamt_all(tdmr_list);
return ret;
}
static void reset_tdx_pages(unsigned long base, unsigned long size)
{
const void *zero_page = (const void *)page_address(ZERO_PAGE(0));
unsigned long phys, end;
end = base + size;
for (phys = base; phys < end; phys += 64)
movdir64b(__va(phys), zero_page);
mb();
}
static void tdmr_reset_pamt(struct tdmr_info *tdmr)
{
tdmr_do_pamt_func(tdmr, reset_tdx_pages);
}
static void tdmrs_reset_pamt_all(struct tdmr_info_list *tdmr_list)
{
int i;
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++)
tdmr_reset_pamt(tdmr_entry(tdmr_list, i));
}
static unsigned long tdmrs_count_pamt_kb(struct tdmr_info_list *tdmr_list)
{
unsigned long pamt_size = 0;
int i;
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++) {
unsigned long base, size;
tdmr_get_pamt(tdmr_entry(tdmr_list, i), &base, &size);
pamt_size += size;
}
return pamt_size / 1024;
}
static int tdmr_add_rsvd_area(struct tdmr_info *tdmr, int *p_idx, u64 addr,
u64 size, u16 max_reserved_per_tdmr)
{
struct tdmr_reserved_area *rsvd_areas = tdmr->reserved_areas;
int idx = *p_idx;
if (WARN_ON(addr & ~PAGE_MASK || size & ~PAGE_MASK))
return -EINVAL;
if (idx >= max_reserved_per_tdmr) {
pr_warn("initialization failed: TDMR [0x%llx, 0x%llx): reserved areas exhausted.\n",
tdmr->base, tdmr_end(tdmr));
return -ENOSPC;
}
rsvd_areas[idx].offset = addr - tdmr->base;
rsvd_areas[idx].size = size;
*p_idx = idx + 1;
return 0;
}
static int tdmr_populate_rsvd_holes(struct list_head *tmb_list,
struct tdmr_info *tdmr,
int *rsvd_idx,
u16 max_reserved_per_tdmr)
{
struct tdx_memblock *tmb;
u64 prev_end;
int ret;
prev_end = tdmr->base;
list_for_each_entry(tmb, tmb_list, list) {
u64 start, end;
start = PFN_PHYS(tmb->start_pfn);
end = PFN_PHYS(tmb->end_pfn);
if (start >= tdmr_end(tdmr))
break;
if (end < tdmr->base)
continue;
if (start <= prev_end) {
prev_end = end;
continue;
}
ret = tdmr_add_rsvd_area(tdmr, rsvd_idx, prev_end,
start - prev_end,
max_reserved_per_tdmr);
if (ret)
return ret;
prev_end = end;
}
if (prev_end < tdmr_end(tdmr)) {
ret = tdmr_add_rsvd_area(tdmr, rsvd_idx, prev_end,
tdmr_end(tdmr) - prev_end,
max_reserved_per_tdmr);
if (ret)
return ret;
}
return 0;
}
static int tdmr_populate_rsvd_pamts(struct tdmr_info_list *tdmr_list,
struct tdmr_info *tdmr,
int *rsvd_idx,
u16 max_reserved_per_tdmr)
{
int i, ret;
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++) {
struct tdmr_info *tmp = tdmr_entry(tdmr_list, i);
unsigned long pamt_base, pamt_size, pamt_end;
tdmr_get_pamt(tmp, &pamt_base, &pamt_size);
WARN_ON_ONCE(!pamt_size || !pamt_base);
pamt_end = pamt_base + pamt_size;
if ((pamt_end <= tdmr->base) ||
(pamt_base >= tdmr_end(tdmr)))
continue;
if (pamt_base < tdmr->base)
pamt_base = tdmr->base;
if (pamt_end > tdmr_end(tdmr))
pamt_end = tdmr_end(tdmr);
ret = tdmr_add_rsvd_area(tdmr, rsvd_idx, pamt_base,
pamt_end - pamt_base,
max_reserved_per_tdmr);
if (ret)
return ret;
}
return 0;
}
static int rsvd_area_cmp_func(const void *a, const void *b)
{
struct tdmr_reserved_area *r1 = (struct tdmr_reserved_area *)a;
struct tdmr_reserved_area *r2 = (struct tdmr_reserved_area *)b;
if (r1->offset + r1->size <= r2->offset)
return -1;
if (r1->offset >= r2->offset + r2->size)
return 1;
WARN_ON_ONCE(1);
return -1;
}
static int tdmr_populate_rsvd_areas(struct tdmr_info *tdmr,
struct list_head *tmb_list,
struct tdmr_info_list *tdmr_list,
u16 max_reserved_per_tdmr)
{
int ret, rsvd_idx = 0;
ret = tdmr_populate_rsvd_holes(tmb_list, tdmr, &rsvd_idx,
max_reserved_per_tdmr);
if (ret)
return ret;
ret = tdmr_populate_rsvd_pamts(tdmr_list, tdmr, &rsvd_idx,
max_reserved_per_tdmr);
if (ret)
return ret;
sort(tdmr->reserved_areas, rsvd_idx, sizeof(struct tdmr_reserved_area),
rsvd_area_cmp_func, NULL);
return 0;
}
static int tdmrs_populate_rsvd_areas_all(struct tdmr_info_list *tdmr_list,
struct list_head *tmb_list,
u16 max_reserved_per_tdmr)
{
int i;
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++) {
int ret;
ret = tdmr_populate_rsvd_areas(tdmr_entry(tdmr_list, i),
tmb_list, tdmr_list, max_reserved_per_tdmr);
if (ret)
return ret;
}
return 0;
}
static int construct_tdmrs(struct list_head *tmb_list,
struct tdmr_info_list *tdmr_list,
struct tdx_sys_info_tdmr *sysinfo_tdmr)
{
u16 pamt_entry_size[TDX_PS_NR] = {
sysinfo_tdmr->pamt_4k_entry_size,
sysinfo_tdmr->pamt_2m_entry_size,
sysinfo_tdmr->pamt_1g_entry_size,
};
int ret;
ret = fill_out_tdmrs(tmb_list, tdmr_list);
if (ret)
return ret;
ret = tdmrs_set_up_pamt_all(tdmr_list, tmb_list, pamt_entry_size);
if (ret)
return ret;
ret = tdmrs_populate_rsvd_areas_all(tdmr_list, tmb_list,
sysinfo_tdmr->max_reserved_per_tdmr);
if (ret)
tdmrs_free_pamt_all(tdmr_list);
smp_wmb();
return ret;
}
static int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid)
{
struct tdx_module_args args = {};
u64 *tdmr_pa_array;
size_t array_sz;
int i, ret;
array_sz = tdmr_list->nr_consumed_tdmrs * sizeof(u64);
array_sz = roundup_pow_of_two(array_sz);
if (array_sz < TDMR_INFO_PA_ARRAY_ALIGNMENT)
array_sz = TDMR_INFO_PA_ARRAY_ALIGNMENT;
tdmr_pa_array = kzalloc(array_sz, GFP_KERNEL);
if (!tdmr_pa_array)
return -ENOMEM;
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++)
tdmr_pa_array[i] = __pa(tdmr_entry(tdmr_list, i));
args.rcx = __pa(tdmr_pa_array);
args.rdx = tdmr_list->nr_consumed_tdmrs;
args.r8 = global_keyid;
ret = seamcall_prerr(TDH_SYS_CONFIG, &args);
kfree(tdmr_pa_array);
return ret;
}
static int do_global_key_config(void *unused)
{
struct tdx_module_args args = {};
return seamcall_prerr(TDH_SYS_KEY_CONFIG, &args);
}
static int config_global_keyid(void)
{
cpumask_var_t packages;
int cpu, ret = -EINVAL;
if (!zalloc_cpumask_var(&packages, GFP_KERNEL))
return -ENOMEM;
wbinvd_on_all_cpus();
for_each_online_cpu(cpu) {
if (cpumask_test_and_set_cpu(topology_physical_package_id(cpu),
packages))
continue;
ret = smp_call_on_cpu(cpu, do_global_key_config, NULL, true);
if (ret)
break;
}
free_cpumask_var(packages);
return ret;
}
static int init_tdmr(struct tdmr_info *tdmr)
{
u64 next;
do {
struct tdx_module_args args = {
.rcx = tdmr->base,
};
int ret;
ret = seamcall_prerr_ret(TDH_SYS_TDMR_INIT, &args);
if (ret)
return ret;
next = args.rdx;
cond_resched();
} while (next < tdmr->base + tdmr->size);
return 0;
}
static int init_tdmrs(struct tdmr_info_list *tdmr_list)
{
int i;
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++) {
int ret;
ret = init_tdmr(tdmr_entry(tdmr_list, i));
if (ret)
return ret;
}
return 0;
}
static int init_tdx_module(void)
{
int ret;
ret = get_tdx_sys_info(&tdx_sysinfo);
if (ret)
return ret;
ret = check_features(&tdx_sysinfo);
if (ret)
return ret;
get_online_mems();
ret = build_tdx_memlist(&tdx_memlist);
if (ret)
goto out_put_tdxmem;
ret = alloc_tdmr_list(&tdx_tdmr_list, &tdx_sysinfo.tdmr);
if (ret)
goto err_free_tdxmem;
ret = construct_tdmrs(&tdx_memlist, &tdx_tdmr_list, &tdx_sysinfo.tdmr);
if (ret)
goto err_free_tdmrs;
ret = config_tdx_module(&tdx_tdmr_list, tdx_global_keyid);
if (ret)
goto err_free_pamts;
ret = config_global_keyid();
if (ret)
goto err_reset_pamts;
ret = init_tdmrs(&tdx_tdmr_list);
if (ret)
goto err_reset_pamts;
pr_info("%lu KB allocated for PAMT\n", tdmrs_count_pamt_kb(&tdx_tdmr_list));
out_put_tdxmem:
put_online_mems();
return ret;
err_reset_pamts:
wbinvd_on_all_cpus();
tdmrs_reset_pamt_all(&tdx_tdmr_list);
err_free_pamts:
tdmrs_free_pamt_all(&tdx_tdmr_list);
err_free_tdmrs:
free_tdmr_list(&tdx_tdmr_list);
err_free_tdxmem:
free_tdx_memlist(&tdx_memlist);
goto out_put_tdxmem;
}
static int __tdx_enable(void)
{
int ret;
ret = init_tdx_module();
if (ret) {
pr_err("module initialization failed (%d)\n", ret);
tdx_module_status = TDX_MODULE_ERROR;
return ret;
}
pr_info("module initialized\n");
tdx_module_status = TDX_MODULE_INITIALIZED;
return 0;
}
int tdx_enable(void)
{
int ret;
if (!boot_cpu_has(X86_FEATURE_TDX_HOST_PLATFORM))
return -ENODEV;
lockdep_assert_cpus_held();
mutex_lock(&tdx_module_lock);
switch (tdx_module_status) {
case TDX_MODULE_UNINITIALIZED:
ret = __tdx_enable();
break;
case TDX_MODULE_INITIALIZED:
ret = 0;
break;
default:
ret = -EINVAL;
break;
}
mutex_unlock(&tdx_module_lock);
return ret;
}
EXPORT_SYMBOL_GPL(tdx_enable);
static bool is_pamt_page(unsigned long phys)
{
struct tdmr_info_list *tdmr_list = &tdx_tdmr_list;
int i;
smp_rmb();
for (i = 0; i < tdmr_list->nr_consumed_tdmrs; i++) {
unsigned long base, size;
tdmr_get_pamt(tdmr_entry(tdmr_list, i), &base, &size);
if (phys >= base && phys < (base + size))
return true;
}
return false;
}
static bool paddr_is_tdx_private(unsigned long phys)
{
struct tdx_module_args args = {
.rcx = phys & PAGE_MASK,
};
u64 sret;
if (!boot_cpu_has(X86_FEATURE_TDX_HOST_PLATFORM))
return false;
sret = __seamcall_ret(TDH_PHYMEM_PAGE_RDMD, &args);
if (sret)
return false;
switch (args.rcx) {
case PT_NDA:
return false;
case PT_RSVD:
return is_pamt_page(phys);
default:
return true;
}
}
const char *tdx_dump_mce_info(struct mce *m)
{
if (!m || !mce_is_memory_error(m) || !mce_usable_address(m))
return NULL;
if (!paddr_is_tdx_private(m->addr))
return NULL;
return "TDX private memory error. Possible kernel bug.";
}
static __init int record_keyid_partitioning(u32 *tdx_keyid_start,
u32 *nr_tdx_keyids)
{
u32 _nr_mktme_keyids, _tdx_keyid_start, _nr_tdx_keyids;
int ret;
ret = rdmsr_safe(MSR_IA32_MKTME_KEYID_PARTITIONING, &_nr_mktme_keyids,
&_nr_tdx_keyids);
if (ret || !_nr_tdx_keyids)
return -EINVAL;
_tdx_keyid_start = _nr_mktme_keyids + 1;
*tdx_keyid_start = _tdx_keyid_start;
*nr_tdx_keyids = _nr_tdx_keyids;
return 0;
}
static bool is_tdx_memory(unsigned long start_pfn, unsigned long end_pfn)
{
struct tdx_memblock *tmb;
list_for_each_entry(tmb, &tdx_memlist, list) {
if (start_pfn >= tmb->start_pfn && end_pfn <= tmb->end_pfn)
return true;
}
return false;
}
static int tdx_memory_notifier(struct notifier_block *nb, unsigned long action,
void *v)
{
struct memory_notify *mn = v;
if (action != MEM_GOING_ONLINE)
return NOTIFY_OK;
if (list_empty(&tdx_memlist))
return NOTIFY_OK;
if (is_tdx_memory(mn->start_pfn, mn->start_pfn + mn->nr_pages))
return NOTIFY_OK;
return NOTIFY_BAD;
}
static struct notifier_block tdx_memory_nb = {
.notifier_call = tdx_memory_notifier,
};
static void __init check_tdx_erratum(void)
{
switch (boot_cpu_data.x86_vfm) {
case INTEL_SAPPHIRERAPIDS_X:
case INTEL_EMERALDRAPIDS_X:
setup_force_cpu_bug(X86_BUG_TDX_PW_MCE);
}
}
void __init tdx_init(void)
{
u32 tdx_keyid_start, nr_tdx_keyids;
int err;
err = record_keyid_partitioning(&tdx_keyid_start, &nr_tdx_keyids);
if (err)
return;
pr_info("BIOS enabled: private KeyID range [%u, %u)\n",
tdx_keyid_start, tdx_keyid_start + nr_tdx_keyids);
if (nr_tdx_keyids < 2) {
pr_err("initialization failed: too few private KeyIDs available.\n");
return;
}
if (hibernation_available()) {
pr_err("initialization failed: Hibernation support is enabled\n");
return;
}
err = register_memory_notifier(&tdx_memory_nb);
if (err) {
pr_err("initialization failed: register_memory_notifier() failed (%d)\n",
err);
return;
}
#if defined(CONFIG_ACPI) && defined(CONFIG_SUSPEND)
pr_info("Disable ACPI S3. Turn off TDX in the BIOS to use ACPI S3.\n");
acpi_suspend_lowlevel = NULL;
#endif
tdx_global_keyid = tdx_keyid_start;
tdx_guest_keyid_start = tdx_keyid_start + 1;
tdx_nr_guest_keyids = nr_tdx_keyids - 1;
setup_force_cpu_cap(X86_FEATURE_TDX_HOST_PLATFORM);
check_tdx_erratum();
}
const struct tdx_sys_info *tdx_get_sysinfo(void)
{
const struct tdx_sys_info *p = NULL;
mutex_lock(&tdx_module_lock);
if (tdx_module_status == TDX_MODULE_INITIALIZED)
p = (const struct tdx_sys_info *)&tdx_sysinfo;
mutex_unlock(&tdx_module_lock);
return p;
}
EXPORT_SYMBOL_GPL(tdx_get_sysinfo);
u32 tdx_get_nr_guest_keyids(void)
{
return tdx_nr_guest_keyids;
}
EXPORT_SYMBOL_GPL(tdx_get_nr_guest_keyids);
int tdx_guest_keyid_alloc(void)
{
return ida_alloc_range(&tdx_guest_keyid_pool, tdx_guest_keyid_start,
tdx_guest_keyid_start + tdx_nr_guest_keyids - 1,
GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(tdx_guest_keyid_alloc);
void tdx_guest_keyid_free(unsigned int keyid)
{
ida_free(&tdx_guest_keyid_pool, keyid);
}
EXPORT_SYMBOL_GPL(tdx_guest_keyid_free);
static inline u64 tdx_tdr_pa(struct tdx_td *td)
{
return page_to_phys(td->tdr_page);
}
static inline u64 tdx_tdvpr_pa(struct tdx_vp *td)
{
return page_to_phys(td->tdvpr_page);
}
static void tdx_clflush_page(struct page *page)
{
clflush_cache_range(page_to_virt(page), PAGE_SIZE);
}
noinstr __flatten u64 tdh_vp_enter(struct tdx_vp *td, struct tdx_module_args *args)
{
args->rcx = tdx_tdvpr_pa(td);
return __seamcall_saved_ret(TDH_VP_ENTER, args);
}
EXPORT_SYMBOL_GPL(tdh_vp_enter);
u64 tdh_mng_addcx(struct tdx_td *td, struct page *tdcs_page)
{
struct tdx_module_args args = {
.rcx = page_to_phys(tdcs_page),
.rdx = tdx_tdr_pa(td),
};
tdx_clflush_page(tdcs_page);
return seamcall(TDH_MNG_ADDCX, &args);
}
EXPORT_SYMBOL_GPL(tdh_mng_addcx);
u64 tdh_mem_page_add(struct tdx_td *td, u64 gpa, struct page *page, struct page *source, u64 *ext_err1, u64 *ext_err2)
{
struct tdx_module_args args = {
.rcx = gpa,
.rdx = tdx_tdr_pa(td),
.r8 = page_to_phys(page),
.r9 = page_to_phys(source),
};
u64 ret;
tdx_clflush_page(page);
ret = seamcall_ret(TDH_MEM_PAGE_ADD, &args);
*ext_err1 = args.rcx;
*ext_err2 = args.rdx;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_mem_page_add);
u64 tdh_mem_sept_add(struct tdx_td *td, u64 gpa, int level, struct page *page, u64 *ext_err1, u64 *ext_err2)
{
struct tdx_module_args args = {
.rcx = gpa | level,
.rdx = tdx_tdr_pa(td),
.r8 = page_to_phys(page),
};
u64 ret;
tdx_clflush_page(page);
ret = seamcall_ret(TDH_MEM_SEPT_ADD, &args);
*ext_err1 = args.rcx;
*ext_err2 = args.rdx;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_mem_sept_add);
u64 tdh_vp_addcx(struct tdx_vp *vp, struct page *tdcx_page)
{
struct tdx_module_args args = {
.rcx = page_to_phys(tdcx_page),
.rdx = tdx_tdvpr_pa(vp),
};
tdx_clflush_page(tdcx_page);
return seamcall(TDH_VP_ADDCX, &args);
}
EXPORT_SYMBOL_GPL(tdh_vp_addcx);
u64 tdh_mem_page_aug(struct tdx_td *td, u64 gpa, int level, struct page *page, u64 *ext_err1, u64 *ext_err2)
{
struct tdx_module_args args = {
.rcx = gpa | level,
.rdx = tdx_tdr_pa(td),
.r8 = page_to_phys(page),
};
u64 ret;
tdx_clflush_page(page);
ret = seamcall_ret(TDH_MEM_PAGE_AUG, &args);
*ext_err1 = args.rcx;
*ext_err2 = args.rdx;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_mem_page_aug);
u64 tdh_mem_range_block(struct tdx_td *td, u64 gpa, int level, u64 *ext_err1, u64 *ext_err2)
{
struct tdx_module_args args = {
.rcx = gpa | level,
.rdx = tdx_tdr_pa(td),
};
u64 ret;
ret = seamcall_ret(TDH_MEM_RANGE_BLOCK, &args);
*ext_err1 = args.rcx;
*ext_err2 = args.rdx;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_mem_range_block);
u64 tdh_mng_key_config(struct tdx_td *td)
{
struct tdx_module_args args = {
.rcx = tdx_tdr_pa(td),
};
return seamcall(TDH_MNG_KEY_CONFIG, &args);
}
EXPORT_SYMBOL_GPL(tdh_mng_key_config);
u64 tdh_mng_create(struct tdx_td *td, u16 hkid)
{
struct tdx_module_args args = {
.rcx = tdx_tdr_pa(td),
.rdx = hkid,
};
tdx_clflush_page(td->tdr_page);
return seamcall(TDH_MNG_CREATE, &args);
}
EXPORT_SYMBOL_GPL(tdh_mng_create);
u64 tdh_vp_create(struct tdx_td *td, struct tdx_vp *vp)
{
struct tdx_module_args args = {
.rcx = tdx_tdvpr_pa(vp),
.rdx = tdx_tdr_pa(td),
};
tdx_clflush_page(vp->tdvpr_page);
return seamcall(TDH_VP_CREATE, &args);
}
EXPORT_SYMBOL_GPL(tdh_vp_create);
u64 tdh_mng_rd(struct tdx_td *td, u64 field, u64 *data)
{
struct tdx_module_args args = {
.rcx = tdx_tdr_pa(td),
.rdx = field,
};
u64 ret;
ret = seamcall_ret(TDH_MNG_RD, &args);
*data = args.r8;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_mng_rd);
u64 tdh_mr_extend(struct tdx_td *td, u64 gpa, u64 *ext_err1, u64 *ext_err2)
{
struct tdx_module_args args = {
.rcx = gpa,
.rdx = tdx_tdr_pa(td),
};
u64 ret;
ret = seamcall_ret(TDH_MR_EXTEND, &args);
*ext_err1 = args.rcx;
*ext_err2 = args.rdx;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_mr_extend);
u64 tdh_mr_finalize(struct tdx_td *td)
{
struct tdx_module_args args = {
.rcx = tdx_tdr_pa(td),
};
return seamcall(TDH_MR_FINALIZE, &args);
}
EXPORT_SYMBOL_GPL(tdh_mr_finalize);
u64 tdh_vp_flush(struct tdx_vp *vp)
{
struct tdx_module_args args = {
.rcx = tdx_tdvpr_pa(vp),
};
return seamcall(TDH_VP_FLUSH, &args);
}
EXPORT_SYMBOL_GPL(tdh_vp_flush);
u64 tdh_mng_vpflushdone(struct tdx_td *td)
{
struct tdx_module_args args = {
.rcx = tdx_tdr_pa(td),
};
return seamcall(TDH_MNG_VPFLUSHDONE, &args);
}
EXPORT_SYMBOL_GPL(tdh_mng_vpflushdone);
u64 tdh_mng_key_freeid(struct tdx_td *td)
{
struct tdx_module_args args = {
.rcx = tdx_tdr_pa(td),
};
return seamcall(TDH_MNG_KEY_FREEID, &args);
}
EXPORT_SYMBOL_GPL(tdh_mng_key_freeid);
u64 tdh_mng_init(struct tdx_td *td, u64 td_params, u64 *extended_err)
{
struct tdx_module_args args = {
.rcx = tdx_tdr_pa(td),
.rdx = td_params,
};
u64 ret;
ret = seamcall_ret(TDH_MNG_INIT, &args);
*extended_err = args.rcx;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_mng_init);
u64 tdh_vp_rd(struct tdx_vp *vp, u64 field, u64 *data)
{
struct tdx_module_args args = {
.rcx = tdx_tdvpr_pa(vp),
.rdx = field,
};
u64 ret;
ret = seamcall_ret(TDH_VP_RD, &args);
*data = args.r8;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_vp_rd);
u64 tdh_vp_wr(struct tdx_vp *vp, u64 field, u64 data, u64 mask)
{
struct tdx_module_args args = {
.rcx = tdx_tdvpr_pa(vp),
.rdx = field,
.r8 = data,
.r9 = mask,
};
return seamcall(TDH_VP_WR, &args);
}
EXPORT_SYMBOL_GPL(tdh_vp_wr);
u64 tdh_vp_init(struct tdx_vp *vp, u64 initial_rcx, u32 x2apicid)
{
struct tdx_module_args args = {
.rcx = tdx_tdvpr_pa(vp),
.rdx = initial_rcx,
.r8 = x2apicid,
};
return seamcall(TDH_VP_INIT | (1ULL << TDX_VERSION_SHIFT), &args);
}
EXPORT_SYMBOL_GPL(tdh_vp_init);
u64 tdh_phymem_page_reclaim(struct page *page, u64 *tdx_pt, u64 *tdx_owner, u64 *tdx_size)
{
struct tdx_module_args args = {
.rcx = page_to_phys(page),
};
u64 ret;
ret = seamcall_ret(TDH_PHYMEM_PAGE_RECLAIM, &args);
*tdx_pt = args.rcx;
*tdx_owner = args.rdx;
*tdx_size = args.r8;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_phymem_page_reclaim);
u64 tdh_mem_track(struct tdx_td *td)
{
struct tdx_module_args args = {
.rcx = tdx_tdr_pa(td),
};
return seamcall(TDH_MEM_TRACK, &args);
}
EXPORT_SYMBOL_GPL(tdh_mem_track);
u64 tdh_mem_page_remove(struct tdx_td *td, u64 gpa, u64 level, u64 *ext_err1, u64 *ext_err2)
{
struct tdx_module_args args = {
.rcx = gpa | level,
.rdx = tdx_tdr_pa(td),
};
u64 ret;
ret = seamcall_ret(TDH_MEM_PAGE_REMOVE, &args);
*ext_err1 = args.rcx;
*ext_err2 = args.rdx;
return ret;
}
EXPORT_SYMBOL_GPL(tdh_mem_page_remove);
u64 tdh_phymem_cache_wb(bool resume)
{
struct tdx_module_args args = {
.rcx = resume ? 1 : 0,
};
return seamcall(TDH_PHYMEM_CACHE_WB, &args);
}
EXPORT_SYMBOL_GPL(tdh_phymem_cache_wb);
u64 tdh_phymem_page_wbinvd_tdr(struct tdx_td *td)
{
struct tdx_module_args args = {};
args.rcx = mk_keyed_paddr(tdx_global_keyid, td->tdr_page);
return seamcall(TDH_PHYMEM_PAGE_WBINVD, &args);
}
EXPORT_SYMBOL_GPL(tdh_phymem_page_wbinvd_tdr);
u64 tdh_phymem_page_wbinvd_hkid(u64 hkid, struct page *page)
{
struct tdx_module_args args = {};
args.rcx = mk_keyed_paddr(hkid, page);
return seamcall(TDH_PHYMEM_PAGE_WBINVD, &args);
}
EXPORT_SYMBOL_GPL(tdh_phymem_page_wbinvd_hkid);