Re: [PATCH v15 09/23] x86/virt/tdx: Get module global metadata for module initialization

From: Isaku Yamahata
Date: Wed Nov 15 2023 - 14:35:58 EST


On Fri, Nov 10, 2023 at 12:55:46AM +1300,
Kai Huang <kai.huang@xxxxxxxxx> wrote:

> The TDX module global metadata provides system-wide information about
> the module. The TDX module provides SEAMCALls to allow the kernel to
> query one specific global metadata field (entry) or all fields.
>
> TL;DR:
>
> Use the TDH.SYS.RD SEAMCALL to read the essential global metadata for
> module initialization, and at the same time, to only initialize TDX
> module with version 1.5 and later.
>
> Long Version:
>
> 1) Only initialize TDX module with version 1.5 and later
>
> TDX module 1.0 has some compatibility issues with the later versions of
> module, as documented in the "Intel TDX module ABI incompatibilities
> between TDX1.0 and TDX1.5" spec. Basically there's no value to use TDX
> module 1.0 when TDX module 1.5 and later versions are already available.
> To keep things simple, just support initializing the TDX module 1.5 and
> later.
>
> 2) Get the essential global metadata for module initialization
>
> TDX reports a list of "Convertible Memory Region" (CMR) to tell the
> kernel which memory is TDX compatible. The kernel needs to build a list
> of memory regions (out of CMRs) as "TDX-usable" memory and pass them to
> the TDX module. The kernel does this by constructing a list of "TD
> Memory Regions" (TDMRs) to cover all these memory regions and passing
> them to the TDX module.
>
> Each TDMR is a TDX architectural data structure containing the memory
> region that the TDMR covers, plus the information to track (within this
> TDMR): a) the "Physical Address Metadata Table" (PAMT) to track each TDX
> memory page's status (such as which TDX guest "owns" a given page, and
> b) the "reserved areas" to tell memory holes that cannot be used as TDX
> memory.
>
> The kernel needs to get below metadata from the TDX module to build the
> list of TDMRs: a) the maximum number of supported TDMRs, b) the maximum
> number of supported reserved areas per TDMR and, c) the PAMT entry size
> for each TDX-supported page size.
>
> Note the TDX module internally checks whether the "TDX-usable" memory
> regions passed via TDMRs are truly convertible. Just skipping reading
> the CMRs and manually checking memory regions against them, but let the
> TDX module do the check.
>
> == Implementation ==
>
> TDX module 1.0 uses TDH.SYS.INFO SEAMCALL to report the global metadata
> in a fixed-size (1024-bytes) structure 'TDSYSINFO_STRUCT'. TDX module
> 1.5 adds more metadata fields, and introduces the new TDH.SYS.{RD|RDALL}
> SEAMCALLs for reading the metadata. The new metadata mechanism removes
> the fixed-size limitation of the structure 'TDSYSINFO_STRUCT' and allows
> the TDX module to support unlimited number of metadata fields.
>
> TDX module 1.5 and later versions still support the TDH.SYS.INFO for
> compatibility to the TDX module 1.0, but it may only report part of
> metadata via the 'TDSYSINFO_STRUCT'. For any new metadata the kernel
> must use TDH.SYS.{RD|RDALL} to read.
>
> To achieve the above two goals mentioned in 1) and 2), just use the
> TDH.SYS.RD to read the essential metadata fields related to the TDMRs.
>
> TDH.SYS.RD returns *one* metadata field at a given "Metadata Field ID".
> It is enough for getting these few fields for module initialization.
> On the other hand, TDH.SYS.RDALL reports all metadata fields to a 4KB
> buffer provided by the kernel which is a little bit overkill here.
>
> It may be beneficial to get all metadata fields at once here so they can
> also be used by KVM (some are essential for creating basic TDX guests),
> but technically it's unknown how many 4K pages are needed to fill all
> the metadata. Thus it's better to read metadata when needed.
>
> Signed-off-by: Kai Huang <kai.huang@xxxxxxxxx>
> ---
>
> v14 -> v15:
> - New patch to use TDH.SYS.RD to read TDX module global metadata for
> module initialization and stop initializing 1.0 module.
>
> ---
> arch/x86/include/asm/shared/tdx.h | 1 +
> arch/x86/virt/vmx/tdx/tdx.c | 75 ++++++++++++++++++++++++++++++-
> arch/x86/virt/vmx/tdx/tdx.h | 39 ++++++++++++++++
> 3 files changed, 114 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/include/asm/shared/tdx.h b/arch/x86/include/asm/shared/tdx.h
> index a4036149c484..fdfd41511b02 100644
> --- a/arch/x86/include/asm/shared/tdx.h
> +++ b/arch/x86/include/asm/shared/tdx.h
> @@ -59,6 +59,7 @@
> #define TDX_PS_4K 0
> #define TDX_PS_2M 1
> #define TDX_PS_1G 2
> +#define TDX_PS_NR (TDX_PS_1G + 1)
>
> #ifndef __ASSEMBLY__
>
> diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
> index d1affb30f74d..d24027993983 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.c
> +++ b/arch/x86/virt/vmx/tdx/tdx.c
> @@ -235,8 +235,75 @@ static int build_tdx_memlist(struct list_head *tmb_list)
> return ret;
> }
>
> +static int read_sys_metadata_field(u64 field_id, u64 *data)
> +{
> + struct tdx_module_args args = {};
> + int ret;
> +
> + /*
> + * TDH.SYS.RD -- reads one global metadata field
> + * - RDX (in): the field to read
> + * - R8 (out): the field data
> + */
> + args.rdx = field_id;
> + ret = seamcall_prerr_ret(TDH_SYS_RD, &args);
> + if (ret)
> + return ret;
> +
> + *data = args.r8;
> +
> + return 0;
> +}
> +
> +static int read_sys_metadata_field16(u64 field_id, u16 *data)
> +{
> + u64 _data;
> + int ret;
> +
> + if (WARN_ON_ONCE(MD_FIELD_ID_ELE_SIZE_CODE(field_id) !=
> + MD_FIELD_ID_ELE_SIZE_16BIT))
> + return -EINVAL;
> +
> + ret = read_sys_metadata_field(field_id, &_data);
> + if (ret)
> + return ret;
> +
> + *data = (u16)_data;
> +
> + return 0;
> +}
> +
> +static int get_tdx_tdmr_sysinfo(struct tdx_tdmr_sysinfo *tdmr_sysinfo)
> +{
> + int ret;
> +
> + ret = read_sys_metadata_field16(MD_FIELD_ID_MAX_TDMRS,
> + &tdmr_sysinfo->max_tdmrs);
> + if (ret)
> + return ret;
> +
> + ret = read_sys_metadata_field16(MD_FIELD_ID_MAX_RESERVED_PER_TDMR,
> + &tdmr_sysinfo->max_reserved_per_tdmr);
> + if (ret)
> + return ret;
> +
> + ret = read_sys_metadata_field16(MD_FIELD_ID_PAMT_4K_ENTRY_SIZE,
> + &tdmr_sysinfo->pamt_entry_size[TDX_PS_4K]);
> + if (ret)
> + return ret;
> +
> + ret = read_sys_metadata_field16(MD_FIELD_ID_PAMT_2M_ENTRY_SIZE,
> + &tdmr_sysinfo->pamt_entry_size[TDX_PS_2M]);
> + if (ret)
> + return ret;
> +
> + return read_sys_metadata_field16(MD_FIELD_ID_PAMT_1G_ENTRY_SIZE,
> + &tdmr_sysinfo->pamt_entry_size[TDX_PS_1G]);
> +}
> +

Now we don't query the versions, build info, attributes, and etc. Because it's
important to know its version/attributes, can we query and print them
as before? Maybe with another path.
In long term, those info would be exported via sysfs, though.
--
Isaku Yamahata <isaku.yamahata@xxxxxxxxxxxxxxx>