[PATCH v6 03/10] x86/virt/tdx: Use auto-generated code to read global metadata

Kai Huang posted 10 patches 3 weeks, 6 days ago
There is a newer version of this series
[PATCH v6 03/10] x86/virt/tdx: Use auto-generated code to read global metadata
Posted by Kai Huang 3 weeks, 6 days ago
From: Paolo Bonzini <pbonzini@redhat.com>

The TDX module provides a set of "Global Metadata Fields".  Currently
the kernel only reads "TD Memory Region" (TDMR) related fields for
module initialization.  There are needs to read more global metadata
fields including TDX module version [1], supported features [2] and
"Convertible Memory Regions" (CMRs) to fix a module initialization
failure [3].  Future changes to support KVM TDX and other features like
TDX Connect will need to read more.

The current global metadata reading code has limitations (e.g., it only
has a primitive helper to read metadata field with 16-bit element size,
while TDX supports 8/16/32/64 bits metadata element sizes).  It needs
tweaks in order to read more metadata fields.

But even with the tweaks, when new code is added to read a new field,
the reviewers will still need to review against the spec to make sure
the new code doesn't screw up things like using the wrong metadata
field ID (each metadata field is associated with a unique field ID,
which is a TDX-defined u64 constant) etc.

TDX documents all global metadata fields in a 'global_metadata.json'
file as part of TDX spec [4].  JSON format is machine readable.  Instead
of tweaking the metadata reading code, use a script [5] to generate the
code so that:

  1) Using the generated C is simple.
  2) Adding a field is dirty simple, e.g., the script just pulls the
     field ID out of the JSON for a given field thus no manual review is
     needed.

Specifically, to match the layout of the 'struct tdx_sys_info' and its
sub-structures, the script uses a table with each entry containing the
the name of the sub-structures (which reflects the "Class") and the
"Field Name" of all its fields, and auto-generate:

  1) The 'struct tdx_sys_info' and all 'struct tdx_sys_info_xx'
     sub-structures in 'tdx_global_metadata.h'

  2) The main function 'get_tdx_sys_info()' which reads all metadata to
     'struct tdx_sys_info' and the 'get_tdx_sys_info_xx()' functions
     which read 'struct tdx_sys_info_xx()' in 'tdx_global_metadata.c'.

Using the generated C is simple: 1) include "tdx_global_metadata.h" to
the local "tdx.h"; 2) explicitly include "tdx_global_metadata.c" to the
local "tdx.c" after the read_sys_metadata_field() primitive (which is a
wrapper of TDH.SYS.RD SEAMCALL to read global metadata).

Adding a field is also simple: 1) just add the new field to an existing
structure, or add it with a new structure; 2) re-run the script to
generate the new code; 3) update the existing tdx_global_metadata.{hc}
with the new ones.

For now, use the auto-generated code to read the aforesaid metadata
fields: 1) TDX module version; 2) supported features; 3) CMRs.

Reading CMRs is more complicated than reading a simple field, since
there are two arrays containing the "CMR_BASE" and "CMR_SIZE" for each
CMR respectively.

TDX spec [3] section "Metadata Access Interface", sub-section "Arrays of
Metadata Fields" defines the way to read metadata fields in an array.
There's a "Base field ID" (say, X) for the array and the field ID for
entry array[i] is X + i.

For CMRs, the field "NUM_CMRS" reports the number of CMR entries that
can be read, and the code needs to use the value reported via "NUM_CMRS"
to loop despite the JSON file says the "Num Fields" of both "CMR_BASE"
and "CMR_SIZE" are 32.

The tdx_global_metadata.{hc} can be generated by running below:

 #python tdx.py global_metadata.json tdx_global_metadata.h \
	tdx_global_metadata.c

.. where tdx.py can be found in [5] and global_metadata.json can be
fetched from [4].

Link: https://lore.kernel.org/lkml/4b3adb59-50ea-419e-ad02-e19e8ca20dee@intel.com/ [1]
Link: https://lore.kernel.org/all/fc0e8ab7-86d4-4428-be31-82e1ece6dd21@intel.com/ [2]
Link: https://github.com/canonical/tdx/issues/135 [3]
Link: https://cdrdv2.intel.com/v1/dl/getContent/795381 [4]
Link: https://lore.kernel.org/kvm/0853b155ec9aac09c594caa60914ed6ea4dc0a71.camel@intel.com/ [5]
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Co-developed-by: Kai Huang <kai.huang@intel.com>
Signed-off-by: Kai Huang <kai.huang@intel.com>
---
 arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 89 +++++++++++++++++++++
 arch/x86/virt/vmx/tdx/tdx_global_metadata.h | 42 ++++++++++
 2 files changed, 131 insertions(+)
 create mode 100644 arch/x86/virt/vmx/tdx/tdx_global_metadata.c
 create mode 100644 arch/x86/virt/vmx/tdx/tdx_global_metadata.h

diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
new file mode 100644
index 000000000000..2fe57e084453
--- /dev/null
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Automatically generated functions to read TDX global metadata.
+ *
+ * This file doesn't compile on its own as it lacks of inclusion
+ * of SEAMCALL wrapper primitive which reads global metadata.
+ * Include this file to other C file instead.
+ */
+
+static int get_tdx_sys_info_version(struct tdx_sys_info_version *sysinfo_version)
+{
+	int ret = 0;
+	u64 val;
+
+	if (!ret && !(ret = read_sys_metadata_field(0x8800000200000001, &val)))
+		sysinfo_version->build_date = val;
+	if (!ret && !(ret = read_sys_metadata_field(0x8800000100000002, &val)))
+		sysinfo_version->build_num = val;
+	if (!ret && !(ret = read_sys_metadata_field(0x0800000100000003, &val)))
+		sysinfo_version->minor_version = val;
+	if (!ret && !(ret = read_sys_metadata_field(0x0800000100000004, &val)))
+		sysinfo_version->major_version = val;
+	if (!ret && !(ret = read_sys_metadata_field(0x0800000100000005, &val)))
+		sysinfo_version->update_version = val;
+	if (!ret && !(ret = read_sys_metadata_field(0x0800000100000006, &val)))
+		sysinfo_version->internal_version = val;
+
+	return ret;
+}
+
+static int get_tdx_sys_info_features(struct tdx_sys_info_features *sysinfo_features)
+{
+	int ret = 0;
+	u64 val;
+
+	if (!ret && !(ret = read_sys_metadata_field(0x0A00000300000008, &val)))
+		sysinfo_features->tdx_features0 = val;
+
+	return ret;
+}
+
+static int get_tdx_sys_info_tdmr(struct tdx_sys_info_tdmr *sysinfo_tdmr)
+{
+	int ret = 0;
+	u64 val;
+
+	if (!ret && !(ret = read_sys_metadata_field(0x9100000100000008, &val)))
+		sysinfo_tdmr->max_tdmrs = val;
+	if (!ret && !(ret = read_sys_metadata_field(0x9100000100000009, &val)))
+		sysinfo_tdmr->max_reserved_per_tdmr = val;
+	if (!ret && !(ret = read_sys_metadata_field(0x9100000100000010, &val)))
+		sysinfo_tdmr->pamt_4k_entry_size = val;
+	if (!ret && !(ret = read_sys_metadata_field(0x9100000100000011, &val)))
+		sysinfo_tdmr->pamt_2m_entry_size = val;
+	if (!ret && !(ret = read_sys_metadata_field(0x9100000100000012, &val)))
+		sysinfo_tdmr->pamt_1g_entry_size = val;
+
+	return ret;
+}
+
+static int get_tdx_sys_info_cmr(struct tdx_sys_info_cmr *sysinfo_cmr)
+{
+	int ret = 0;
+	u64 val;
+	int i;
+
+	if (!ret && !(ret = read_sys_metadata_field(0x9000000100000000, &val)))
+		sysinfo_cmr->num_cmrs = val;
+	for (i = 0; i < sysinfo_cmr->num_cmrs; i++)
+		if (!ret && !(ret = read_sys_metadata_field(0x9000000300000080 + i, &val)))
+			sysinfo_cmr->cmr_base[i] = val;
+	for (i = 0; i < sysinfo_cmr->num_cmrs; i++)
+		if (!ret && !(ret = read_sys_metadata_field(0x9000000300000100 + i, &val)))
+			sysinfo_cmr->cmr_size[i] = val;
+
+	return ret;
+}
+
+static int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
+{
+	int ret = 0;
+
+	ret = ret ?: get_tdx_sys_info_version(&sysinfo->version);
+	ret = ret ?: get_tdx_sys_info_features(&sysinfo->features);
+	ret = ret ?: get_tdx_sys_info_tdmr(&sysinfo->tdmr);
+	ret = ret ?: get_tdx_sys_info_cmr(&sysinfo->cmr);
+
+	return ret;
+}
diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.h b/arch/x86/virt/vmx/tdx/tdx_global_metadata.h
new file mode 100644
index 000000000000..fde370b855f1
--- /dev/null
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Automatically generated TDX global metadata structures. */
+#ifndef _X86_VIRT_TDX_AUTO_GENERATED_TDX_GLOBAL_METADATA_H
+#define _X86_VIRT_TDX_AUTO_GENERATED_TDX_GLOBAL_METADATA_H
+
+#include <linux/types.h>
+
+struct tdx_sys_info_version {
+	u32 build_date;
+	u16 build_num;
+	u16 minor_version;
+	u16 major_version;
+	u16 update_version;
+	u16 internal_version;
+};
+
+struct tdx_sys_info_features {
+	u64 tdx_features0;
+};
+
+struct tdx_sys_info_tdmr {
+	u16 max_tdmrs;
+	u16 max_reserved_per_tdmr;
+	u16 pamt_4k_entry_size;
+	u16 pamt_2m_entry_size;
+	u16 pamt_1g_entry_size;
+};
+
+struct tdx_sys_info_cmr {
+	u16 num_cmrs;
+	u64 cmr_base[32];
+	u64 cmr_size[32];
+};
+
+struct tdx_sys_info {
+	struct tdx_sys_info_version version;
+	struct tdx_sys_info_features features;
+	struct tdx_sys_info_tdmr tdmr;
+	struct tdx_sys_info_cmr cmr;
+};
+
+#endif
-- 
2.46.2
Re: [PATCH v6 03/10] x86/virt/tdx: Use auto-generated code to read global metadata
Posted by Dan Williams 3 weeks, 6 days ago
Kai Huang wrote:
> From: Paolo Bonzini <pbonzini@redhat.com>
> 
> The TDX module provides a set of "Global Metadata Fields".  Currently
> the kernel only reads "TD Memory Region" (TDMR) related fields for
> module initialization.  There are needs to read more global metadata
> fields including TDX module version [1], supported features [2] and
> "Convertible Memory Regions" (CMRs) to fix a module initialization
> failure [3].  Future changes to support KVM TDX and other features like
> TDX Connect will need to read more.
> 
> The current global metadata reading code has limitations (e.g., it only
> has a primitive helper to read metadata field with 16-bit element size,
> while TDX supports 8/16/32/64 bits metadata element sizes).  It needs
> tweaks in order to read more metadata fields.
> 
> But even with the tweaks, when new code is added to read a new field,
> the reviewers will still need to review against the spec to make sure
> the new code doesn't screw up things like using the wrong metadata
> field ID (each metadata field is associated with a unique field ID,
> which is a TDX-defined u64 constant) etc.
> 
> TDX documents all global metadata fields in a 'global_metadata.json'
> file as part of TDX spec [4].  JSON format is machine readable.  Instead
> of tweaking the metadata reading code, use a script [5] to generate the
> code so that:
> 
>   1) Using the generated C is simple.
>   2) Adding a field is dirty simple, e.g., the script just pulls the

Probably meant "dirt simple", but if this is fixed up on apply I'd drop
the idiom and just say "simple".

...don't spin the patch just for this nit.

>      field ID out of the JSON for a given field thus no manual review is
>      needed.
> 
> Specifically, to match the layout of the 'struct tdx_sys_info' and its
> sub-structures, the script uses a table with each entry containing the
> the name of the sub-structures (which reflects the "Class") and the
> "Field Name" of all its fields, and auto-generate:
> 
>   1) The 'struct tdx_sys_info' and all 'struct tdx_sys_info_xx'
>      sub-structures in 'tdx_global_metadata.h'
> 
>   2) The main function 'get_tdx_sys_info()' which reads all metadata to
>      'struct tdx_sys_info' and the 'get_tdx_sys_info_xx()' functions
>      which read 'struct tdx_sys_info_xx()' in 'tdx_global_metadata.c'.
> 
> Using the generated C is simple: 1) include "tdx_global_metadata.h" to
> the local "tdx.h"; 2) explicitly include "tdx_global_metadata.c" to the
> local "tdx.c" after the read_sys_metadata_field() primitive (which is a
> wrapper of TDH.SYS.RD SEAMCALL to read global metadata).
> 
> Adding a field is also simple: 1) just add the new field to an existing
> structure, or add it with a new structure; 2) re-run the script to
> generate the new code; 3) update the existing tdx_global_metadata.{hc}
> with the new ones.
> 
> For now, use the auto-generated code to read the aforesaid metadata
> fields: 1) TDX module version; 2) supported features; 3) CMRs.
> 
> Reading CMRs is more complicated than reading a simple field, since
> there are two arrays containing the "CMR_BASE" and "CMR_SIZE" for each
> CMR respectively.
> 
> TDX spec [3] section "Metadata Access Interface", sub-section "Arrays of
> Metadata Fields" defines the way to read metadata fields in an array.
> There's a "Base field ID" (say, X) for the array and the field ID for
> entry array[i] is X + i.
> 
> For CMRs, the field "NUM_CMRS" reports the number of CMR entries that
> can be read, and the code needs to use the value reported via "NUM_CMRS"
> to loop despite the JSON file says the "Num Fields" of both "CMR_BASE"
> and "CMR_SIZE" are 32.
> 
> The tdx_global_metadata.{hc} can be generated by running below:
> 
>  #python tdx.py global_metadata.json tdx_global_metadata.h \
> 	tdx_global_metadata.c
> 
> .. where tdx.py can be found in [5] and global_metadata.json can be
> fetched from [4].
> 
> Link: https://lore.kernel.org/lkml/4b3adb59-50ea-419e-ad02-e19e8ca20dee@intel.com/ [1]
> Link: https://lore.kernel.org/all/fc0e8ab7-86d4-4428-be31-82e1ece6dd21@intel.com/ [2]
> Link: https://lore.kernel.org/kvm/0853b155ec9aac09c594caa60914ed6ea4dc0a71.camel@intel.com/ [5]

Just an fyi, that lore accepts the simple:

https://lore.kernel.org/$msg_id

...format, no need to record the list name in the URL (127734e23aed
("Documentation: best practices for using Link trailers"))

> Link: https://github.com/canonical/tdx/issues/135 [3]
> Link: https://cdrdv2.intel.com/v1/dl/getContent/795381 [4]
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> Co-developed-by: Kai Huang <kai.huang@intel.com>
> Signed-off-by: Kai Huang <kai.huang@intel.com>

Looks good to me, with or without the above nits addressed.

Reviewed-by: Dan Williams <dan.j.williams@intel.com>
Re: [PATCH v6 03/10] x86/virt/tdx: Use auto-generated code to read global metadata
Posted by Huang, Kai 3 weeks, 6 days ago
>>    1) Using the generated C is simple.
>>    2) Adding a field is dirty simple, e.g., the script just pulls the
> 
> Probably meant "dirt simple", but if this is fixed up on apply I'd drop
> the idiom and just say "simple".

Yeah "dirt simple".  I missed this.  Will just say "simple" if a new 
version is needed.

[...]

>>
>> Link: https://lore.kernel.org/lkml/4b3adb59-50ea-419e-ad02-e19e8ca20dee@intel.com/ [1]
>> Link: https://lore.kernel.org/all/fc0e8ab7-86d4-4428-be31-82e1ece6dd21@intel.com/ [2]
>> Link: https://lore.kernel.org/kvm/0853b155ec9aac09c594caa60914ed6ea4dc0a71.camel@intel.com/ [5]
> 
> Just an fyi, that lore accepts the simple:
> 
> https://lore.kernel.org/$msg_id
> 
> ...format, no need to record the list name in the URL (127734e23aed
> ("Documentation: best practices for using Link trailers"))

Thanks for the info.  I got those link by clicking the "permalink" on 
the webpage, and then I just paste.

Yeah "https://lore.kernel.org/$msg_id" works, but if I open the page 
using this format the like is changed to:

   "https://lore.kernel.org/all/$msg_id"

So I thought I just don't bother to remove the "all/lkml/kvm" in the 
link.  I'll change to use the simple format in the future since it is 
said in the Documentation.

> 
>> Link: https://github.com/canonical/tdx/issues/135 [3]
>> Link: https://cdrdv2.intel.com/v1/dl/getContent/795381 [4]
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>> Co-developed-by: Kai Huang <kai.huang@intel.com>
>> Signed-off-by: Kai Huang <kai.huang@intel.com>
> 
> Looks good to me, with or without the above nits addressed.
> 
> Reviewed-by: Dan Williams <dan.j.williams@intel.com>

Thanks.  I'll fixup if a new version is needed.