Re: [PATCH 2/2] x86/tdx: Accept hotplugged memory before online

Yan Zhao
2026-03-27T09:08:05Z

On Tue, Mar 24, 2026 at 07:21:48PM +0400, Marc-André Lureau wrote:
> In TDX guests, hotplugged memory (e.g., via virtio-mem) is never
> accepted before use. The first access triggers a fatal "SEPT entry in
> PENDING state" EPT violation and KVM terminates the guest.
>
> Fix this by registering a MEM_GOING_ONLINE memory hotplug notifier that
> calls tdx_accept_memory() for the range being onlined.
>
> The notifier returns NOTIFY_BAD on acceptance failure, preventing the
> memory from going online.
>
> Assisted-by: Claude:claude-opus-4-6
> Reported-by: Chenyi Qiang <chenyi.qiang@intel.com>
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> arch/x86/coco/tdx/tdx.c | 38 ++++++++++++++++++++++++++++++++++++++
> 1 file changed, 38 insertions(+)
>
> diff --git a/arch/x86/coco/tdx/tdx.c b/arch/x86/coco/tdx/tdx.c
> index 7b2833705d475..89f90bc303258 100644
> --- a/arch/x86/coco/tdx/tdx.c
> +++ b/arch/x86/coco/tdx/tdx.c
> @@ -8,6 +8,7 @@
> #include <linux/export.h>
> #include <linux/io.h>
> #include <linux/kexec.h>
> +#include <linux/memory.h>
> #include <asm/coco.h>
> #include <asm/tdx.h>
> #include <asm/vmx.h>
> @@ -1194,3 +1195,40 @@ void __init tdx_early_init(void)
>
> tdx_announce();
> }
> +
> +#ifdef CONFIG_MEMORY_HOTPLUG
> +static int tdx_guest_memory_notifier(struct notifier_block *nb,
> + unsigned long action, void *v)
> +{
> + struct memory_notify *mn = v;
> + phys_addr_t start, end;
> +
> + if (action != MEM_GOING_ONLINE)
> + return NOTIFY_OK;
> +
> + start = PFN_PHYS(mn->start_pfn);
> + end = start + PFN_PHYS(mn->nr_pages);
> +
> + if (!tdx_accept_memory(start, end)) {
> + pr_err("Failed to accept memory [0x%llx, 0x%llx)\n",
> + (unsigned long long)start,
> + (unsigned long long)end);
> + return NOTIFY_BAD;
> + }
> +
> + return NOTIFY_OK;
> +}
> +
> +static struct notifier_block tdx_guest_memory_nb = {
> + .notifier_call = tdx_guest_memory_notifier,
> +};
> +
> +static int __init tdx_guest_memory_init(void)
> +{
> + if (!cpu_feature_enabled(X86_FEATURE_TDX_GUEST))
> + return 0;
> +
> + return register_memory_notifier(&tdx_guest_memory_nb);
> +}
If I read the code correctly,

online_pages
1. memory_notify(MEM_GOING_ONLINE, &mem_arg);
2. online_pages_range(pfn, nr_pages);
(*online_page_callback)(page, order);
generic_online_page
__free_pages_core(page, order, MEMINIT_HOTPLUG);

In __free_pages_core(), there's accept_memory() already:

if (page_contains_unaccepted(page, order)) {
if (order == MAX_PAGE_ORDER && __free_unaccepted(page))
return;

accept_memory(page_to_phys(page), PAGE_SIZE << order);
}

__free_unaccepted() also adds the pages to the unaccepted_pages list, so
cond_accept_memory() will accept the memory later:

So, is it because the virtio mem sets online_page_callback to
virtio_mem_online_page_cb, which doesn't invoke __free_pages_core() properly?

Or am I missing something that makes the memory notifier approach necessary?

Thanks
Yan

> +core_initcall(tdx_guest_memory_init);
> +#endif
>
> --
> 2.53.0
>
>