Contributors: 4
Author Tokens Token Proportion Commits Commit Proportion
Chao Gao 1087 97.14% 11 73.33%
Dave Hansen 21 1.88% 1 6.67%
Kai Huang 8 0.71% 2 13.33%
Jacob Xu 3 0.27% 1 6.67%
Total 1119 15


// 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");