To perform TD-Preserving updates, the kernel must stop invoking any TDX module SEAMCALLs. Currently, these SEAMCALLs can be invoked in various contexts and in parallel across CPUs. Additionally, considering the need to force all vCPUs out of guest mode, no single lock primitive, except for stop_machine(), can meet this requirement. A failed attempt is to lock all KVM entry points and kick all vCPUs. But it cannot be done within KVM TDX code. And it needs to introduce new infrastructure and maintenance burden outside of tdx for questionable benefits. Perform TD-Preserving updates within stop_machine() as it achieves the seamldr requirements and is an existing well understood mechanism. TD-Preserving updates consist of several steps: shutting down the old module, installing the new module, and initializing the new one and etc. Some steps must be executed on a single CPU, others serially across all CPUs, and some can be performed concurrently on all CPUs and there are ordering requirements between steps. So, all CPUs need to perform the work in a step-locked manner. In preparation for adding concrete steps for TD-Preserving updates, establish the framework by mimicking multi_cpu_stop(). Specifically, use a global state machine to control the work done on each CPU and require all CPUs to acknowledge completion before proceeding to the next stage. Signed-off-by: Chao Gao <chao.gao@xxxxxxxxx> Tested-by: Farrah Chen <farrah.chen@xxxxxxxxx> --- instead of copy-pasting multi_cpu_stop(), would it be better to abstract a common function and adapt it for TD-Preserving updates? --- arch/x86/virt/vmx/tdx/seamldr.c | 63 +++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c index cdf85dff6d69..01dc2b0bc4a5 100644 --- a/arch/x86/virt/vmx/tdx/seamldr.c +++ b/arch/x86/virt/vmx/tdx/seamldr.c @@ -12,7 +12,9 @@ #include <linux/gfp.h> #include <linux/kobject.h> #include <linux/mm.h> +#include <linux/nmi.h> #include <linux/slab.h> +#include <linux/stop_machine.h> #include <linux/sysfs.h> #include "tdx.h" @@ -237,6 +239,62 @@ static struct seamldr_params *init_seamldr_params(const u8 *data, u32 size) return alloc_seamldr_params(module, module_size, sig, sig_size); } +enum tdp_state { + TDP_START, + TDP_DONE, +}; + +static struct { + enum tdp_state state; + atomic_t thread_ack; +} tdp_data; + +static void set_state(enum tdp_state state) +{ + /* Reset ack counter. */ + atomic_set(&tdp_data.thread_ack, num_online_cpus()); + /* Ensure thread_ack is updated before the new state */ + smp_wmb(); + WRITE_ONCE(tdp_data.state, state); +} + +/* Last one to ack a state moves to the next state. */ +static void ack_state(void) +{ + if (atomic_dec_and_test(&tdp_data.thread_ack)) + set_state(tdp_data.state + 1); +} + +/* + * See multi_cpu_stop() from where this multi-cpu state-machine was + * adopted, and the rationale for touch_nmi_watchdog() + */ +static int do_seamldr_install_module(void *params) +{ + enum tdp_state newstate, curstate = TDP_START; + int ret = 0; + + do { + /* Chill out and ensure we re-read tdp_data. */ + cpu_relax(); + newstate = READ_ONCE(tdp_data.state); + + if (newstate != curstate) { + curstate = newstate; + switch (curstate) { + default: + break; + } + ack_state(); + } else { + touch_nmi_watchdog(); + } + rcu_momentary_eqs(); + } while (curstate != TDP_DONE); + + return ret; +} + /* * Temporary flag to guard TD-Preserving updates. This will be removed once * all necessary components for its support are integrated. @@ -256,9 +314,8 @@ static int seamldr_install_module(const u8 *data, u32 size) if (IS_ERR(params)) return PTR_ERR(params); - /* TODO: Install and initialize the new TDX module */ - - return 0; + set_state(TDP_START + 1); + return stop_machine(do_seamldr_install_module, params, cpu_online_mask); } static enum fw_upload_err tdx_fw_prepare(struct fw_upload *fwl, -- 2.47.1