#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"
#define P_SEAMLDR_INFO 0x8000000000000000
#define P_SEAMLDR_INSTALL 0x8000000000000001
#define SEAMLDR_MAX_NR_MODULE_PAGES 496
#define SEAMLDR_MAX_NR_SIG_PAGES 1
#define SEAMLDR_SCENARIO_UPDATE 1
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);
static DEFINE_RAW_SPINLOCK(seamldr_lock);
static int seamldr_call(u64 fn, struct tdx_module_args *args)
{
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 = {};
args.rcx = slow_virt_to_phys(seamldr_info);
return seamldr_call(P_SEAMLDR_INFO, &args);
}
EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
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
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);
struct tdx_image {
struct tdx_image_header header;
u8 payload[];
};
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);
}
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;
if (TDX_IMAGE_HEADER_SIZE + sigstruct_len + module_len != image_len)
return -EINVAL;
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;
}
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;
raw_spinlock_t lock;
} update_ctrl;
static void __set_target_state(struct update_ctrl *ctrl,
enum module_update_state newstate)
{
ctrl->num_ack = 0;
ctrl->state = newstate;
}
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;
}
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;
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;
}
int seamldr_install_module(const u8 *data, u32 data_len)
{
struct seamldr_params *params;
const struct tdx_image *image;
int ret;
if (data_len < TDX_IMAGE_HEADER_SIZE)
return -EINVAL;
image = (const struct tdx_image *)data;
params = kzalloc_obj(*params);
if (!params)
return -ENOMEM;
ret = init_seamldr_params(params, image, data_len);
if (ret)
goto out;
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");
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");