Re: [PATCH v8 17/21] x86/virt/seamldr: Abort updates on failure

Dave Hansen
2026-04-30T20:06:38Z

I don't like how this is being done.

1. Introduce this do{}while() loop
2. Do 20 other patches
3. Introduce a thing that can make it change
4. Change the fundamental flow of the loop, to fix #3

I'd much rather have:

1. Introduce this do{}while() loop
2. Tweak fundamental flow of the loop from the last patch when I can
remember it. Allude to future failures.
3. Do 20 other patches
4. Introduce a thing that uses #2

> diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
> index c81b26c4bac1..9b8f571eb03f 100644
> --- a/arch/x86/virt/vmx/tdx/seamldr.c
> +++ b/arch/x86/virt/vmx/tdx/seamldr.c
> @@ -220,6 +220,7 @@ enum module_update_state {
> static struct {
> enum module_update_state state;
> int thread_ack;
> + bool failed;
> /*
> * Protect update_data. Raw spinlock as it will be acquired from
> * interrupt-disabled contexts.
> @@ -284,12 +285,15 @@ static int do_seamldr_install_module(void *seamldr_params)
> break;
> }
>
> - ack_state();
> + if (ret)
> + WRITE_ONCE(update_data.failed, true);
> + else
> + ack_state();
> } else {
> touch_nmi_watchdog();
> rcu_momentary_eqs();
> }

I don't like how this is turning out either. I don't like all the nested
conditions or ack_state() that hides its mucking with update data while
its caller mucks with it directly. It's just all hacked together.

Defer all of the acking, and *failed* acking to the ack_state() helper.

Also, I'm kinda peeved that you copied and pasted the
touch_nmi_watchdog()/rcu_momentary_eqs() bits and none of the comments.
This is a rather subtle use of both. If you want this to be a normal
"spinning in stop machine" idiom, then create a helper and put the
comments there.

Also, this is a case where:

do {
cpu_relax();
newstate = READ_ONCE(update_data.state);

if (newstate == curstate) {
// can cpu_relax() just go in here??
touch_nmi_watchdog();
rcu_momentary_eqs();
continue;
}

switch() {
// state changing here
}
} while (...);

is a much more sane setup. You're not paying the if() indentation cost
for the entire state transition block. You're also putting the "shut up
the warnings" code out of the way where you can forget about it.

> - } while (curstate != MODULE_UPDATE_DONE);
> + } while (curstate != MODULE_UPDATE_DONE && !READ_ONCE(update_data.failed));
>
> return ret;
> }
> @@ -315,6 +319,7 @@ int seamldr_install_module(const u8 *data, u32 size)
>
> /* Ensure a stable set of online CPUs for the update process. */
> guard(cpus_read_lock)();
> + WRITE_ONCE(update_data.failed, false);
> set_target_state(MODULE_UPDATE_START + 1);
> ret = stop_machine_cpuslocked(do_seamldr_install_module, params, cpu_online_mask);
> if (ret)
I kinda wish this 'update_data.failed' set was named. This is trying to
bring 'update_data' into some initial state. Let's _call_ it that.
Honestly, I wouldn't hate if that function just also did the spinlock
init since it's so ugly do to statically.