Add reading of the global metadata for TDX Module Extensions. TDX Module Extensions is an add-on feature enumerated by TDX_FEATURES0. But for the Module's integrity, Linux requires that all features that a Module advertises must have a complete, valid set of metadata, and the validation must succeed at core TDX initialization time. Check TDX_FEATURES0 before reading these metadata. If a feature is advertised, a failure in reading associated metadata causes the entire TDX initialization to fail, otherwise skip. Signed-off-by: Xu Yilun --- arch/x86/include/asm/tdx_global_metadata.h | 6 ++++++ arch/x86/virt/vmx/tdx/tdx.h | 1 + arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 16 ++++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h index 40689c8dc67e..533afe50a3f1 100644 --- a/arch/x86/include/asm/tdx_global_metadata.h +++ b/arch/x86/include/asm/tdx_global_metadata.h @@ -40,12 +40,18 @@ struct tdx_sys_info_td_conf { u64 cpuid_config_values[128][2]; }; +struct tdx_sys_info_ext { + u16 memory_pool_required_pages; + u8 ext_required; +}; + 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_td_ctrl td_ctrl; struct tdx_sys_info_td_conf td_conf; + struct tdx_sys_info_ext ext; }; #endif diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index e2cf2dd48755..a5eec8e3cc71 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -87,6 +87,7 @@ struct tdmr_info { /* Bit definitions of TDX_FEATURES0 metadata field */ #define TDX_FEATURES0_NO_RBP_MOD BIT(18) +#define TDX_FEATURES0_EXT BIT_ULL(39) /* * Do not put any hardware-defined TDX structure representations below diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c index c7db393a9cfb..3d3b56ef3d2f 100644 --- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c +++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c @@ -100,6 +100,19 @@ static __init int get_tdx_sys_info_td_conf(struct tdx_sys_info_td_conf *sysinfo_ return ret; } +static __init int get_tdx_sys_info_ext(struct tdx_sys_info_ext *sysinfo_ext) +{ + int ret = 0; + u64 val; + + if (!ret && !(ret = read_sys_metadata_field(0x3100000100000000, &val))) + sysinfo_ext->memory_pool_required_pages = val; + if (!ret && !(ret = read_sys_metadata_field(0x3100000000000001, &val))) + sysinfo_ext->ext_required = val; + + return ret; +} + static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo) { int ret = 0; @@ -116,5 +129,8 @@ static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo) ret = ret ?: get_tdx_sys_info_td_ctrl(&sysinfo->td_ctrl); ret = ret ?: get_tdx_sys_info_td_conf(&sysinfo->td_conf); + if (sysinfo->features.tdx_features0 & TDX_FEATURES0_EXT) + ret = ret ?: get_tdx_sys_info_ext(&sysinfo->ext); + return ret; } -- 2.25.1 TDX Module introduces a new concept called "TDX Module Extensions" to support long running / hard-irq preemptible flows inside. This makes TDX Module capable of handling complex tasks through "Extension SEAMCALLs". Adding more memory to TDX Module is the first step to enable Extensions. Currently, TDX Module memory use is relatively static. But, the Extensions need to use memory more dynamically. While 'static' here means the kernel provides necessary amount of memory to TDX Module for its basic functionalities, 'dynamic' means extra memory is needed only if new add-on features are to be enabled. So add a new memory feeding process backed by a new SEAMCALL TDH.EXT.MEM.ADD. The process is mostly the same as adding PAMT. The kernel queries TDX Module how much memory needed, allocates it, hands it over, and never gets it back. TDH.EXT.MEM.ADD uses a new parameter type HPA_LIST_INFO to provide control (private) pages to TDX Module. This type represents a list of pages for TDX Module to access. It needs a 'root page' which contains the list of HPAs of the pages. It collapses the HPA of the root page and the number of valid HPAs into a 64 bit raw value for SEAMCALL parameters. The root page is always a medium, TDX Module never keeps the root page. Introduce a tdx_clflush_hpa_list() helper to flush shared cache before SEAMCALL, to avoid shared cache writeback damaging these private pages. For now, TDX Module Extensions consumes relatively large amount of memory (~50MB). Use contiguous page allocation to avoid permanently fragment too much memory. Print the allocation amount on TDX Module Extensions initialization for visibility. Co-developed-by: Zhenzhong Duan Signed-off-by: Zhenzhong Duan Signed-off-by: Xu Yilun --- arch/x86/virt/vmx/tdx/tdx.h | 1 + arch/x86/virt/vmx/tdx/tdx.c | 118 ++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index a5eec8e3cc71..2335f88bbb10 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -46,6 +46,7 @@ #define TDH_PHYMEM_PAGE_WBINVD 41 #define TDH_VP_WR 43 #define TDH_SYS_CONFIG 45 +#define TDH_EXT_MEM_ADD 61 #define TDH_SYS_DISABLE 69 /* diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index c0c6281b08a5..622399d8da68 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -1179,6 +1180,123 @@ static __init int init_tdmrs(struct tdmr_info_list *tdmr_list) return 0; } +static void tdx_clflush_hpa_list(struct page *root, unsigned int nr_pages) +{ + u64 *entries = page_to_virt(root); + int i; + + for (i = 0; i < nr_pages; i++) + clflush_cache_range(__va(entries[i]), PAGE_SIZE); +} + +#define HPA_LIST_INFO_FIRST_ENTRY GENMASK_U64(11, 3) +#define HPA_LIST_INFO_PFN GENMASK_U64(51, 12) +#define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55) + +static u64 to_hpa_list_info(struct page *root, unsigned int nr_pages) +{ + return FIELD_PREP(HPA_LIST_INFO_FIRST_ENTRY, 0) | + FIELD_PREP(HPA_LIST_INFO_PFN, page_to_pfn(root)) | + FIELD_PREP(HPA_LIST_INFO_LAST_ENTRY, nr_pages - 1); +} + +static int tdx_ext_mem_add(struct page *root, unsigned int nr_pages) +{ + struct tdx_module_args args = { + .rcx = to_hpa_list_info(root, nr_pages), + }; + u64 r; + + tdx_clflush_hpa_list(root, nr_pages); + + do { + /* + * TDH_EXT_MEM_ADD is designed to use output parameter RCX to + * override/update input parameter RCX, so the caller doesn't + * have to do manual parameter update on retry call. + */ + r = seamcall_ret(TDH_EXT_MEM_ADD, &args); + } while (r == TDX_INTERRUPTED_RESUMABLE); + + if (r != TDX_SUCCESS) + return -EFAULT; + + return 0; +} + +static int tdx_ext_mem_setup(void) +{ + unsigned int nr_pages; + struct page *page; + u64 *root; + unsigned int i; + int ret; + + nr_pages = tdx_sysinfo.ext.memory_pool_required_pages; + /* + * memory_pool_required_pages == 0 means no need to add pages, + * skip the memory setup. + */ + if (!nr_pages) + return 0; + + root = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!root) + return -ENOMEM; + + page = alloc_contig_pages(nr_pages, GFP_KERNEL, numa_mem_id(), + &node_online_map); + if (!page) { + ret = -ENOMEM; + goto out_free_root; + } + + for (i = 0; i < nr_pages;) { + unsigned int nents = min(nr_pages - i, + PAGE_SIZE / sizeof(*root)); + int j; + + for (j = 0; j < nents; j++) + root[j] = page_to_phys(page + i + j); + + ret = tdx_ext_mem_add(virt_to_page(root), nents); + /* + * No SEAMCALLs to reclaim the added pages. For simple error + * handling, leak all pages. + */ + WARN_ON_ONCE(ret); + if (ret) + break; + + i += nents; + } + + /* + * Extensions memory can't be reclaimed once added, print out the + * amount, stop tracking it and free the root page, no matter success + * or failure. + */ + pr_info("%lu KB allocated for TDX Module Extensions\n", + nr_pages * PAGE_SIZE / 1024); + +out_free_root: + kfree(root); + + return ret; +} + +static int __maybe_unused init_tdx_ext(void) +{ + if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT)) + return 0; + + /* No feature requires TDX Module Extensions. */ + if (!tdx_sysinfo.ext.ext_required) + return 0; + + return tdx_ext_mem_setup(); +} + static __init int init_tdx_module(void) { int ret; -- 2.25.1 After providing all required memory to TDX Module, initialize TDX Module Extensions via TDH.EXT.INIT, so Extension-SEAMCALLs can be used. Co-developed-by: Zhenzhong Duan Signed-off-by: Zhenzhong Duan Signed-off-by: Xu Yilun --- arch/x86/virt/vmx/tdx/tdx.h | 1 + arch/x86/virt/vmx/tdx/tdx.c | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index 2335f88bbb10..c5bffd118145 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -46,6 +46,7 @@ #define TDH_PHYMEM_PAGE_WBINVD 41 #define TDH_VP_WR 43 #define TDH_SYS_CONFIG 45 +#define TDH_EXT_INIT 60 #define TDH_EXT_MEM_ADD 61 #define TDH_SYS_DISABLE 69 diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 622399d8da68..ff2b96c20d2b 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -1200,6 +1200,22 @@ static u64 to_hpa_list_info(struct page *root, unsigned int nr_pages) FIELD_PREP(HPA_LIST_INFO_LAST_ENTRY, nr_pages - 1); } +/* Initialize the TDX Module Extensions then Extension-SEAMCALLs can be used */ +static int tdx_ext_init(void) +{ + struct tdx_module_args args = {}; + u64 r; + + do { + r = seamcall(TDH_EXT_INIT, &args); + } while (r == TDX_INTERRUPTED_RESUMABLE); + + if (r != TDX_SUCCESS) + return -EFAULT; + + return 0; +} + static int tdx_ext_mem_add(struct page *root, unsigned int nr_pages) { struct tdx_module_args args = { @@ -1287,6 +1303,8 @@ static int tdx_ext_mem_setup(void) static int __maybe_unused init_tdx_ext(void) { + int ret; + if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_EXT)) return 0; @@ -1294,7 +1312,11 @@ static int __maybe_unused init_tdx_ext(void) if (!tdx_sysinfo.ext.ext_required) return 0; - return tdx_ext_mem_setup(); + ret = tdx_ext_mem_setup(); + if (ret) + return ret; + + return tdx_ext_init(); } static __init int init_tdx_module(void) -- 2.25.1 The detailed initialization flow for TDX Module Extensions has been fully implemented. Enable the flow after basic TDX Module initialization. Theoretically, the Extensions doesn't need to be enabled right after basic TDX initialization. It could be enabled right before the first Extension SEAMCALL is issued. That would save or postpone memory usage. But it isn't worth the complexity, the needs for the Extensions are vast but the savings are little for a typical TDX capable system (about 0.001% of memory). So the Linux decision is to just enable it along with the basic TDX. Note that the Extensions initialization flow will still not start if no add-on features require Extensions. The enabling of add-on features will be in later patches. Until then, the system hasn't consumed extra memory. Signed-off-by: Xu Yilun --- arch/x86/virt/vmx/tdx/tdx.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index ff2b96c20d2b..dad5ec642723 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -1180,7 +1180,7 @@ static __init int init_tdmrs(struct tdmr_info_list *tdmr_list) return 0; } -static void tdx_clflush_hpa_list(struct page *root, unsigned int nr_pages) +static __init void tdx_clflush_hpa_list(struct page *root, unsigned int nr_pages) { u64 *entries = page_to_virt(root); int i; @@ -1193,7 +1193,7 @@ static void tdx_clflush_hpa_list(struct page *root, unsigned int nr_pages) #define HPA_LIST_INFO_PFN GENMASK_U64(51, 12) #define HPA_LIST_INFO_LAST_ENTRY GENMASK_U64(63, 55) -static u64 to_hpa_list_info(struct page *root, unsigned int nr_pages) +static __init u64 to_hpa_list_info(struct page *root, unsigned int nr_pages) { return FIELD_PREP(HPA_LIST_INFO_FIRST_ENTRY, 0) | FIELD_PREP(HPA_LIST_INFO_PFN, page_to_pfn(root)) | @@ -1201,7 +1201,7 @@ static u64 to_hpa_list_info(struct page *root, unsigned int nr_pages) } /* Initialize the TDX Module Extensions then Extension-SEAMCALLs can be used */ -static int tdx_ext_init(void) +static __init int tdx_ext_init(void) { struct tdx_module_args args = {}; u64 r; @@ -1216,7 +1216,7 @@ static int tdx_ext_init(void) return 0; } -static int tdx_ext_mem_add(struct page *root, unsigned int nr_pages) +static __init int tdx_ext_mem_add(struct page *root, unsigned int nr_pages) { struct tdx_module_args args = { .rcx = to_hpa_list_info(root, nr_pages), @@ -1240,7 +1240,7 @@ static int tdx_ext_mem_add(struct page *root, unsigned int nr_pages) return 0; } -static int tdx_ext_mem_setup(void) +static __init int tdx_ext_mem_setup(void) { unsigned int nr_pages; struct page *page; @@ -1301,7 +1301,7 @@ static int tdx_ext_mem_setup(void) return ret; } -static int __maybe_unused init_tdx_ext(void) +static __init int init_tdx_ext(void) { int ret; @@ -1373,6 +1373,10 @@ static __init int init_tdx_module(void) if (ret) goto err_reset_pamts; + ret = init_tdx_ext(); + if (ret) + goto err_reset_pamts; + pr_info("%lu KB allocated for PAMT\n", tdmrs_count_pamt_kb(&tdx_tdmr_list)); out_put_tdxmem: -- 2.25.1 From: Peter Fang Move the tdx_tdr_pa() in preparation for upcoming changes to use them during TDX bringup. No functional change intended. Signed-off-by: Peter Fang Signed-off-by: Xu Yilun --- arch/x86/virt/vmx/tdx/tdx.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index dad5ec642723..67758adefb4a 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -1200,6 +1200,11 @@ static __init u64 to_hpa_list_info(struct page *root, unsigned int nr_pages) FIELD_PREP(HPA_LIST_INFO_LAST_ENTRY, nr_pages - 1); } +static inline u64 tdx_tdr_pa(struct tdx_td *td) +{ + return page_to_phys(td->tdr_page); +} + /* Initialize the TDX Module Extensions then Extension-SEAMCALLs can be used */ static __init int tdx_ext_init(void) { @@ -1725,11 +1730,6 @@ void tdx_guest_keyid_free(unsigned int keyid) } EXPORT_SYMBOL_FOR_KVM(tdx_guest_keyid_free); -static inline u64 tdx_tdr_pa(struct tdx_td *td) -{ - return page_to_phys(td->tdr_page); -} - /* * The TDX module exposes a CLFLUSH_BEFORE_ALLOC bit to specify whether * a CLFLUSH of pages is required before handing them to the TDX module. -- 2.25.1 From: Peter Fang Initialize the Quoting extension and fetch its metadata during TDX bringup. Because Quoting is an optional TDX feature, do not let its initialization failures cause TDX bringup to fail. This patch does not include the opt-in portion of the initialization. It mainly lays the groundwork for TDX Quoting support. Opt-in will be added in a follow-up patch once the feature can be properly used by the system. Signed-off-by: Peter Fang Signed-off-by: Xu Yilun --- arch/x86/include/asm/tdx_global_metadata.h | 5 ++++ arch/x86/virt/vmx/tdx/tdx.h | 1 + arch/x86/virt/vmx/tdx/tdx.c | 29 ++++++++++++++++++++- arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 11 ++++++++ 4 files changed, 45 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h index 533afe50a3f1..04f515cd4c1d 100644 --- a/arch/x86/include/asm/tdx_global_metadata.h +++ b/arch/x86/include/asm/tdx_global_metadata.h @@ -45,6 +45,10 @@ struct tdx_sys_info_ext { u8 ext_required; }; +struct tdx_sys_info_quote { + u32 max_quote_size; +}; + struct tdx_sys_info { struct tdx_sys_info_version version; struct tdx_sys_info_features features; @@ -52,6 +56,7 @@ struct tdx_sys_info { struct tdx_sys_info_td_ctrl td_ctrl; struct tdx_sys_info_td_conf td_conf; struct tdx_sys_info_ext ext; + struct tdx_sys_info_quote quote; }; #endif diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index c5bffd118145..3849f4f9cc78 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -49,6 +49,7 @@ #define TDH_EXT_INIT 60 #define TDH_EXT_MEM_ADD 61 #define TDH_SYS_DISABLE 69 +#define TDH_QUOTE_INIT 100 /* * SEAMCALL leaf: diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 67758adefb4a..fb84fb6d952b 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -1205,6 +1205,22 @@ static inline u64 tdx_tdr_pa(struct tdx_td *td) return page_to_phys(td->tdr_page); } +static void tdx_quote_init(void) +{ + struct tdx_module_args args = {}; + u64 r; + + do { + r = seamcall(TDH_QUOTE_INIT, &args); + } while (r == TDX_INTERRUPTED_RESUMABLE); + + if (r) + return; + + /* Quoting metadata is valid only after initialization */ + get_tdx_sys_info_quote(&tdx_sysinfo.quote); +} + /* Initialize the TDX Module Extensions then Extension-SEAMCALLs can be used */ static __init int tdx_ext_init(void) { @@ -1306,6 +1322,13 @@ static __init int tdx_ext_mem_setup(void) return ret; } +static int init_tdx_ext_features(void) +{ + tdx_quote_init(); + + return 0; +} + static __init int init_tdx_ext(void) { int ret; @@ -1321,7 +1344,11 @@ static __init int init_tdx_ext(void) if (ret) return ret; - return tdx_ext_init(); + ret = tdx_ext_init(); + if (ret) + return ret; + + return init_tdx_ext_features(); } static __init int init_tdx_module(void) diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c index 3d3b56ef3d2f..f9cc2dd02caf 100644 --- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c +++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c @@ -113,6 +113,17 @@ static __init int get_tdx_sys_info_ext(struct tdx_sys_info_ext *sysinfo_ext) return ret; } +static int get_tdx_sys_info_quote(struct tdx_sys_info_quote *sysinfo_quote) +{ + int ret = 0; + u64 val; + + if (!ret && !(ret = read_sys_metadata_field(0x2300000200000002, &val))) + sysinfo_quote->max_quote_size = val; + + return ret; +} + static __init int get_tdx_sys_info(struct tdx_sys_info *sysinfo) { int ret = 0; -- 2.25.1 From: Peter Fang The host uses a Quote buffer to communicate with the TDX module when generating Quotes. Because the Quote buffer is shared with TDX guests, prepare the required metadata during Quoting extension bringup. This mostly involves determining the physical addresses of the Quote buffer pages and arranging them in the HPA_LINKED_LIST format defined by the Intel TDX Module ABI specification. Signed-off-by: Peter Fang Signed-off-by: Xu Yilun --- arch/x86/virt/vmx/tdx/tdx.c | 85 ++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index fb84fb6d952b..9d04293394d7 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +62,13 @@ static LIST_HEAD(tdx_memlist); static struct tdx_sys_info tdx_sysinfo __ro_after_init; static bool tdx_module_initialized __ro_after_init; +static struct quote_data { + void *buf; + u64 buf_len; + u64 *hpa_list; + phys_addr_t hpa_list_pa; +} quote_data; + typedef void (*sc_err_func_t)(u64 fn, u64 err, struct tdx_module_args *args); static inline void seamcall_err(u64 fn, u64 err, struct tdx_module_args *args) @@ -1205,9 +1213,78 @@ static inline u64 tdx_tdr_pa(struct tdx_td *td) return page_to_phys(td->tdr_page); } +#define HPAS_PER_PAGE (PAGE_SIZE / sizeof(u64)) + +static int tdx_quote_create_buf(unsigned int nr_pages, struct quote_data *qdata) +{ + unsigned long pfn; + u64 qlist_npages; + int err, i, j; + u64 *qlist; + void *qbuf; + + if (!nr_pages) + return -EINVAL; + + /* The last entry of a linked list page points to the next page */ + qlist_npages = (u64)DIV_ROUND_UP(nr_pages, HPAS_PER_PAGE - 1); + + qlist = vmalloc_array(qlist_npages, PAGE_SIZE); + if (!qlist) { + err = -ENOMEM; + goto out_err; + } + + /* + * Make sure unfilled entries are always -1, which means NULL in TDX. + * Only the last page needs to be filled. All the other pages will be + * fully populated. + */ + memset((u8 *)qlist + (qlist_npages - 1) * PAGE_SIZE, 0xff, PAGE_SIZE); + + qbuf = vcalloc(nr_pages, PAGE_SIZE); + if (!qbuf) { + err = -ENOMEM; + goto out_err; + } + + /* Populate HPA_LINKED_LIST as per TDX ABI spec */ + for (i = 0, j = 0; j < nr_pages; i++) { + if ((i % HPAS_PER_PAGE) == HPAS_PER_PAGE - 1) { + /* + * The last entry always points to the next page. The + * address of the following entry must be on next page's + * boundary. + */ + pfn = vmalloc_to_pfn(&qlist[i + 1]); + qlist[i] = PFN_PHYS(pfn); + continue; + } + + pfn = vmalloc_to_pfn((u8 *)qbuf + j * PAGE_SIZE); + qlist[i] = PFN_PHYS(pfn); + j++; + } + + qdata->buf = qbuf; + qdata->buf_len = (u64)nr_pages * PAGE_SIZE; + qdata->hpa_list = qlist; + + pfn = vmalloc_to_pfn(qlist); + qdata->hpa_list_pa = PFN_PHYS(pfn); + + return 0; + +out_err: + vfree(qlist); + + return err; +} + static void tdx_quote_init(void) { struct tdx_module_args args = {}; + unsigned int nr_quote_pages; u64 r; do { @@ -1218,7 +1295,13 @@ static void tdx_quote_init(void) return; /* Quoting metadata is valid only after initialization */ - get_tdx_sys_info_quote(&tdx_sysinfo.quote); + if (get_tdx_sys_info_quote(&tdx_sysinfo.quote)) + return; + + nr_quote_pages = PAGE_ALIGN(tdx_sysinfo.quote.max_quote_size) / + PAGE_SIZE; + if (tdx_quote_create_buf(nr_quote_pages, "e_data)) + pr_err("Failed to create quote buffer\n"); } /* Initialize the TDX Module Extensions then Extension-SEAMCALLs can be used */ -- 2.25.1 From: Peter Fang KVM needs to know if the Quoting extension is available to determine whether userspace must be involved in Quote generation. Since the Quote buffer is always created during Quoting extension bringup, checking whether the buffer exists is sufficient. Signed-off-by: Peter Fang Signed-off-by: Xu Yilun --- arch/x86/include/asm/tdx.h | 2 ++ arch/x86/virt/vmx/tdx/tdx.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index 15eac89b0afb..7b257088aa1e 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -176,6 +176,8 @@ struct tdx_vp { struct page **tdcx_pages; }; +bool tdx_quote_enabled(void); + static inline u64 mk_keyed_paddr(u16 hkid, struct page *page) { u64 ret; diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 9d04293394d7..b305fa5aab5c 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -1213,6 +1213,21 @@ static inline u64 tdx_tdr_pa(struct tdx_td *td) return page_to_phys(td->tdr_page); } +/** + * tdx_quote_enabled() - Check whether TDX Quoting extension is available + * + * Return: %true if the Quoting extension is available, otherwise %false. + */ +bool tdx_quote_enabled(void) +{ + /* + * No need for locking here. The quote buffer is initialized as part of + * core TDX bringup, which comes before KVM is ready for userspace. + */ + return !!quote_data.buf; +} +EXPORT_SYMBOL_FOR_KVM(tdx_quote_enabled); + #define HPAS_PER_PAGE (PAGE_SIZE / sizeof(u64)) static int tdx_quote_create_buf(unsigned int nr_pages, struct quote_data *qdata) -- 2.25.1 From: Peter Fang Use the TDX Quoting extension's TDH.QUOTE.GET SEAMCALL to generate a Quote. Since the interface is shared across all KVM instances, serialize access to the SEAMCALL buffer with a mutex. Allocate and return a per-call buffer containing the generated Quote so callers don't need to size the Quote buffer themselves. The caller is responsible for freeing the returned buffer. Signed-off-by: Peter Fang Signed-off-by: Xu Yilun --- arch/x86/include/asm/tdx.h | 2 + arch/x86/virt/vmx/tdx/tdx.h | 1 + arch/x86/virt/vmx/tdx/tdx.c | 82 +++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index 7b257088aa1e..bc512a00a0d0 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -177,6 +177,8 @@ struct tdx_vp { }; bool tdx_quote_enabled(void); +void *tdx_quote_generate(struct tdx_td *td, void *in_data, u32 in_data_len, + u32 *quote_len); static inline u64 mk_keyed_paddr(u16 hkid, struct page *page) { diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index 3849f4f9cc78..01a7d7d8ada9 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -49,6 +49,7 @@ #define TDH_EXT_INIT 60 #define TDH_EXT_MEM_ADD 61 #define TDH_SYS_DISABLE 69 +#define TDH_QUOTE_GET 98 #define TDH_QUOTE_INIT 100 /* diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index b305fa5aab5c..821f677e9a86 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -62,6 +62,8 @@ static LIST_HEAD(tdx_memlist); static struct tdx_sys_info tdx_sysinfo __ro_after_init; static bool tdx_module_initialized __ro_after_init; +static DEFINE_MUTEX(tdx_quote_lock); + static struct quote_data { void *buf; u64 buf_len; @@ -1228,6 +1230,86 @@ bool tdx_quote_enabled(void) } EXPORT_SYMBOL_FOR_KVM(tdx_quote_enabled); +#define QUOTE_ID_MASK GENMASK_U64(47, 32) + +static u64 tdx_quote_get(struct tdx_td *td, u64 in_data_pa, u64 in_data_len, + u64 hpa_list_pa, u64 total_len, u64 *quote_len) +{ + struct tdx_module_args args = { + .rcx = tdx_tdr_pa(td), + /* Don't bother specifying the quote id */ + .rdx = QUOTE_ID_MASK & (u64)-1, + .r8 = in_data_pa, + .r9 = in_data_len, + .r10 = hpa_list_pa, + .r11 = total_len, + }; + u64 r; + + do { + r = seamcall_ret(TDH_QUOTE_GET, &args); + } while (r == TDX_INTERRUPTED_RESUMABLE); + + *quote_len = args.rcx; + + return r; +} + +/** + * tdx_quote_generate() - Generate a quote for a TD + * @td: The TD to generate the quote for. + * @in_data: Input data for the quote request. + * @in_data_len: Size of the input data in bytes. + * @quote_len: Returned size of the generated quote in bytes. + * + * Use the TDX Quoting extension to generate a TD quote. Pass the input data + * through the shared quote buffer and return the quote. + * + * Return: Newly allocated quote buffer or %NULL on failure. + * The caller must free the returned buffer with kvfree(). + */ +void *tdx_quote_generate(struct tdx_td *td, void *in_data, u32 in_data_len, + u32 *quote_len) +{ + void *quote_dup = NULL; + u64 r, out_len; + + if (!tdx_quote_enabled()) + return NULL; + + /* TDH.QUOTE.GET expects the input data to fit in a page */ + if (in_data_len > PAGE_SIZE) + return NULL; + + mutex_lock(&tdx_quote_lock); + + /* + * Use the first page of the quote buffer for input data. The buffer + * must be at least one page in size. @in_data may not be page-aligned, + * but TDH.QUOTE.GET expects page-aligned addresses. + */ + memcpy(quote_data.buf, in_data, (size_t)in_data_len); + + r = tdx_quote_get(td, quote_data.hpa_list[0], (u64)in_data_len, + quote_data.hpa_list_pa, quote_data.buf_len, &out_len); + if (r || !out_len || out_len > quote_data.buf_len) + goto out; + + /* + * The quote buffer is a shared resource, so use it only for the + * SEAMCALL and copy the data out as soon as possible. + */ + quote_dup = kvmemdup(quote_data.buf, out_len, GFP_KERNEL); + +out: + mutex_unlock(&tdx_quote_lock); + + *quote_len = (u32)out_len; + + return quote_dup; +} +EXPORT_SYMBOL_FOR_KVM(tdx_quote_generate); + #define HPAS_PER_PAGE (PAGE_SIZE / sizeof(u64)) static int tdx_quote_create_buf(unsigned int nr_pages, struct quote_data *qdata) -- 2.25.1 From: Peter Fang struct tdx_quote_buf is currently used only by the guest, but the Quote buffer format will also be needed by the host for in-kernel Quote generation. Move the definition to tdx.h so it can be shared by both. Rename the struct to tdx_quote_req to better reflect its purpose. Signed-off-by: Peter Fang Signed-off-by: Xu Yilun --- arch/x86/include/asm/tdx.h | 21 +++++++++++++++++++++ drivers/virt/coco/tdx-guest/tdx-guest.c | 25 +++---------------------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index bc512a00a0d0..945e6817abb2 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -96,6 +96,27 @@ static inline long tdx_kvm_hypercall(unsigned int nr, unsigned long p1, } #endif /* CONFIG_INTEL_TDX_GUEST && CONFIG_KVM_GUEST */ +#if defined(CONFIG_INTEL_TDX_GUEST) || defined(CONFIG_KVM_INTEL_TDX) +/* struct tdx_quote_req: Format of Quote request message. + * @version: Quote format version, filled by TD. + * @status: Status code of Quote request, filled by VMM. + * @in_len: Length of TDREPORT, filled by TD. + * @out_len: Length of Quote data, filled by VMM. + * @data: Quote data on output or TDREPORT on input. + * + * More details of Quote request message can be found in TDX + * Guest-Host Communication Interface (GHCI) for Intel TDX 1.0, + * section titled "TDG.VP.VMCALL" + */ +struct tdx_quote_req { + u64 version; + u64 status; + u32 in_len; + u32 out_len; + u8 data[]; +}; +#endif /* CONFIG_INTEL_TDX_GUEST || CONFIG_KVM_INTEL_TDX */ + #ifdef CONFIG_INTEL_TDX_HOST u64 __seamcall(u64 fn, struct tdx_module_args *args); u64 __seamcall_ret(u64 fn, struct tdx_module_args *args); diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c index a9ecc46df187..d0ddbbc98fb8 100644 --- a/drivers/virt/coco/tdx-guest/tdx-guest.c +++ b/drivers/virt/coco/tdx-guest/tdx-guest.c @@ -171,26 +171,7 @@ static void tdx_mr_deinit(const struct attribute_group *mr_grp) #define GET_QUOTE_SUCCESS 0 #define GET_QUOTE_IN_FLIGHT 0xffffffffffffffff -#define TDX_QUOTE_MAX_LEN (GET_QUOTE_BUF_SIZE - sizeof(struct tdx_quote_buf)) - -/* struct tdx_quote_buf: Format of Quote request buffer. - * @version: Quote format version, filled by TD. - * @status: Status code of Quote request, filled by VMM. - * @in_len: Length of TDREPORT, filled by TD. - * @out_len: Length of Quote data, filled by VMM. - * @data: Quote data on output or TDREPORT on input. - * - * More details of Quote request buffer can be found in TDX - * Guest-Host Communication Interface (GHCI) for Intel TDX 1.0, - * section titled "TDG.VP.VMCALL" - */ -struct tdx_quote_buf { - u64 version; - u64 status; - u32 in_len; - u32 out_len; - u8 data[]; -}; +#define TDX_QUOTE_MAX_LEN (GET_QUOTE_BUF_SIZE - sizeof(struct tdx_quote_req)) /* Quote data buffer */ static void *quote_data; @@ -250,7 +231,7 @@ static void *alloc_quote_buf(void) * or error code after processing is complete. So wait till the status * changes from GET_QUOTE_IN_FLIGHT or the request being timed out. */ -static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeout) +static int wait_for_quote_completion(struct tdx_quote_req *quote_buf, u32 timeout) { int i = 0; @@ -269,7 +250,7 @@ static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeou static int tdx_report_new_locked(struct tsm_report *report, void *data) { u8 *buf; - struct tdx_quote_buf *quote_buf = quote_data; + struct tdx_quote_req *quote_buf = quote_data; struct tsm_report_desc *desc = &report->desc; u32 out_len; int ret; -- 2.25.1 From: Peter Fang Separate the logic that returns GetQuote to userspace so that tdx_get_quote() can be extended to support in-kernel quote generation. No functional change intended. Signed-off-by: Peter Fang Signed-off-by: Xu Yilun --- arch/x86/kvm/vmx/tdx.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index ed12805bbb44..9f7c39e0d4b5 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -1524,6 +1524,20 @@ static int tdx_complete_simple(struct kvm_vcpu *vcpu) return 1; } +static int tdx_get_quote_user(struct kvm_vcpu *vcpu, u64 gpa, u64 size) +{ + vcpu->run->exit_reason = KVM_EXIT_TDX; + vcpu->run->tdx.flags = 0; + vcpu->run->tdx.nr = TDVMCALL_GET_QUOTE; + vcpu->run->tdx.get_quote.ret = TDVMCALL_STATUS_SUBFUNC_UNSUPPORTED; + vcpu->run->tdx.get_quote.gpa = gpa; + vcpu->run->tdx.get_quote.size = size; + + vcpu->arch.complete_userspace_io = tdx_complete_simple; + + return 0; +} + static int tdx_get_quote(struct kvm_vcpu *vcpu) { struct vcpu_tdx *tdx = to_tdx(vcpu); @@ -1536,16 +1550,9 @@ static int tdx_get_quote(struct kvm_vcpu *vcpu) return 1; } - vcpu->run->exit_reason = KVM_EXIT_TDX; - vcpu->run->tdx.flags = 0; - vcpu->run->tdx.nr = TDVMCALL_GET_QUOTE; - vcpu->run->tdx.get_quote.ret = TDVMCALL_STATUS_SUBFUNC_UNSUPPORTED; - vcpu->run->tdx.get_quote.gpa = gpa & ~gfn_to_gpa(kvm_gfn_direct_bits(tdx->vcpu.kvm)); - vcpu->run->tdx.get_quote.size = size; - - vcpu->arch.complete_userspace_io = tdx_complete_simple; + gpa &= ~gfn_to_gpa(kvm_gfn_direct_bits(vcpu->kvm)); - return 0; + return tdx_get_quote_user(vcpu, gpa, size); } static int tdx_setup_event_notify_interrupt(struct kvm_vcpu *vcpu) -- 2.25.1 From: Peter Fang Provide an in-kernel path for TDX Quote generation when handling TDG.VP.VMCALL, without requiring an exit to userspace. Use the core TDX API when the TDX Quoting extension is available. For simplicity, each KVM guest checks for availability only once during initialization. KVM does not handle Quoting service disruptions. Signed-off-by: Peter Fang Signed-off-by: Xu Yilun --- arch/x86/include/asm/tdx.h | 9 +++ arch/x86/kvm/vmx/tdx.h | 6 ++ arch/x86/kvm/vmx/tdx.c | 135 ++++++++++++++++++++++++++++++++++++- virt/kvm/kvm_main.c | 1 + 4 files changed, 150 insertions(+), 1 deletion(-) diff --git a/arch/x86/include/asm/tdx.h b/arch/x86/include/asm/tdx.h index 945e6817abb2..5863d6748100 100644 --- a/arch/x86/include/asm/tdx.h +++ b/arch/x86/include/asm/tdx.h @@ -115,6 +115,15 @@ struct tdx_quote_req { u32 out_len; u8 data[]; }; + +#define TDX_QUOTE_REQ_HDR_SIZE (offsetof(struct tdx_quote_req, data)) + +/* + * TDG.VP.VMCALL Status Codes + */ +#define TDX_QUOTE_STATUS_SUCCESS 0x0000000000000000ULL +#define TDX_QUOTE_STATUS_ERROR 0x8000000000000000ULL +#define TDX_QUOTE_STATUS_UNAVAILABLE 0x8000000000000001ULL #endif /* CONFIG_INTEL_TDX_GUEST || CONFIG_KVM_INTEL_TDX */ #ifdef CONFIG_INTEL_TDX_HOST diff --git a/arch/x86/kvm/vmx/tdx.h b/arch/x86/kvm/vmx/tdx.h index ac8323a68b16..18c93e80c0ec 100644 --- a/arch/x86/kvm/vmx/tdx.h +++ b/arch/x86/kvm/vmx/tdx.h @@ -47,6 +47,12 @@ struct kvm_tdx { * Set/unset is protected with kvm->mmu_lock. */ bool wait_for_sept_zap; + + /* + * Whether to get TDX quote directly in kernel, without exiting to + * userspace. + */ + bool get_quote_in_kernel; }; /* TDX module vCPU states */ diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index 9f7c39e0d4b5..bade046da5a1 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -1538,11 +1538,133 @@ static int tdx_get_quote_user(struct kvm_vcpu *vcpu, u64 gpa, u64 size) return 0; } +static bool write_quote_status_to_guest(struct kvm_vcpu *vcpu, u64 status, + gpa_t gpa) +{ + if (kvm_vcpu_write_guest(vcpu, + gpa + offsetof(struct tdx_quote_req, status), + &status, sizeof(status))) + return false; + + return true; +} + +static bool write_quote_to_guest(struct kvm_vcpu *vcpu, void *quote_data, + u32 quote_len, gpa_t gpa) +{ + if (kvm_vcpu_write_guest(vcpu, + gpa + TDX_QUOTE_REQ_HDR_SIZE, + quote_data, quote_len)) + return false; + + if (kvm_vcpu_write_guest(vcpu, + gpa + offsetof(struct tdx_quote_req, out_len), + "e_len, sizeof(quote_len))) + return false; + + return true; +} + +static u64 __get_quote_kernel(struct kvm_vcpu *vcpu, struct tdx_quote_req *req, + size_t req_len, gpa_t req_gpa, size_t total_len) +{ + struct tdx_td *td = &to_kvm_tdx(vcpu->kvm)->td; + + /* Only support version 1 as defined in the GHCI spec */ + if (req->version != 1) + return TDX_QUOTE_STATUS_ERROR; + + if ((size_t)req->in_len + TDX_QUOTE_REQ_HDR_SIZE > req_len) + return TDX_QUOTE_STATUS_ERROR; + + /* The caller frees the quote data */ + void *quote_data __free(kvfree) = + tdx_quote_generate(td, req->data, req->in_len, &req->out_len); + + if (!quote_data) + return TDX_QUOTE_STATUS_UNAVAILABLE; + + if ((size_t)req->out_len + TDX_QUOTE_REQ_HDR_SIZE > total_len) + return TDX_QUOTE_STATUS_ERROR; + + if (!write_quote_to_guest(vcpu, quote_data, req->out_len, req_gpa)) + return TDX_QUOTE_STATUS_ERROR; + + return TDX_QUOTE_STATUS_SUCCESS; +} + +static u64 tdx_get_quote_check_args(struct kvm_vcpu *vcpu, u64 gpa, u64 size) +{ + gfn_t gfn_start, gfn_end; + u64 end; + + if (!size) + return TDVMCALL_STATUS_INVALID_OPERAND; + + if (!PAGE_ALIGNED(gpa) || !PAGE_ALIGNED(size)) + return TDVMCALL_STATUS_ALIGN_ERROR; + + if (check_add_overflow(gpa, size, &end)) + return TDVMCALL_STATUS_INVALID_OPERAND; + + gfn_start = gpa_to_gfn(gpa); + gfn_end = gpa_to_gfn(end); + + /* + * Reject if the guest didn't explicitly convert its quote pages to + * shared. + */ + if (!kvm_range_has_memory_attributes(vcpu->kvm, gfn_start, gfn_end, + KVM_MEMORY_ATTRIBUTE_PRIVATE, 0)) + return TDVMCALL_STATUS_INVALID_OPERAND; + + return TDVMCALL_STATUS_SUCCESS; +} + +static int tdx_get_quote_kernel(struct kvm_vcpu *vcpu, u64 gpa, u64 size) +{ + void *first_page = NULL; + u64 err, qerr; + + err = tdx_get_quote_check_args(vcpu, gpa, size); + if (err != TDVMCALL_STATUS_SUCCESS) + goto out; + + err = TDVMCALL_STATUS_INVALID_OPERAND; + + first_page = kmalloc(PAGE_SIZE, GFP_KERNEL); + if (!first_page) + goto out; + + /* + * Read the first GetQuote page for its header + in_data. The check + * above ensures that this GetQuote message is at least one page in + * size. in_data spanning more than a page is not supported. + */ + if (kvm_vcpu_read_guest(vcpu, gpa, first_page, PAGE_SIZE)) + goto out; + + qerr = __get_quote_kernel(vcpu, first_page, PAGE_SIZE, + (gpa_t)gpa, size); + + if (write_quote_status_to_guest(vcpu, qerr, (gpa_t)gpa) && + qerr == TDX_QUOTE_STATUS_SUCCESS) + err = TDVMCALL_STATUS_SUCCESS; + +out: + kfree(first_page); + tdvmcall_set_return_code(vcpu, err); + + return 1; +} + static int tdx_get_quote(struct kvm_vcpu *vcpu) { + struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm); struct vcpu_tdx *tdx = to_tdx(vcpu); u64 gpa = tdx->vp_enter_args.r12; u64 size = tdx->vp_enter_args.r13; + int ret; /* The gpa of buffer must have shared bit set. */ if (vt_is_tdx_private_gpa(vcpu->kvm, gpa)) { @@ -1552,7 +1674,12 @@ static int tdx_get_quote(struct kvm_vcpu *vcpu) gpa &= ~gfn_to_gpa(kvm_gfn_direct_bits(vcpu->kvm)); - return tdx_get_quote_user(vcpu, gpa, size); + if (kvm_tdx->get_quote_in_kernel) + ret = tdx_get_quote_kernel(vcpu, gpa, size); + else + ret = tdx_get_quote_user(vcpu, gpa, size); + + return ret; } static int tdx_setup_event_notify_interrupt(struct kvm_vcpu *vcpu) @@ -2751,6 +2878,12 @@ static int tdx_td_init(struct kvm *kvm, struct kvm_tdx_cmd *cmd) else kvm->arch.gfn_direct_bits = TDX_SHARED_BIT_PWL_4; + /* + * Check only once at TD creation. If the quoting service gets disrupted + * during TD runtime, let the user handle it. + */ + kvm_tdx->get_quote_in_kernel = tdx_quote_enabled(); + kvm_tdx->state = TD_STATE_INITIALIZED; out: /* kfree() accepts NULL. */ diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c index 89489996fbc1..599f88a13071 100644 --- a/virt/kvm/kvm_main.c +++ b/virt/kvm/kvm_main.c @@ -2461,6 +2461,7 @@ bool kvm_range_has_memory_attributes(struct kvm *kvm, gfn_t start, gfn_t end, return true; } +EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_range_has_memory_attributes); static __always_inline void kvm_handle_gfn_range(struct kvm *kvm, struct kvm_mmu_notifier_range *range) -- 2.25.1 From: Peter Fang Tie userspace SetupEventNotifyInterrupt support to userspace Quote generation. Delivering event-notify interrupts via userspace breaks if KVM never exits to userspace in the first place. No known guest currently requires event-notify interrupt support, so defer adding in-kernel support for now. Linux TDX guests use polling only. Update the KVM API Documentation to reflect the change. Signed-off-by: Peter Fang Signed-off-by: Xu Yilun --- Documentation/virt/kvm/api.rst | 8 +++++++- arch/x86/kvm/vmx/tdx.c | 20 +++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst index 52bbbb553ce1..8a02745a36ee 100644 --- a/Documentation/virt/kvm/api.rst +++ b/Documentation/virt/kvm/api.rst @@ -7335,6 +7335,9 @@ inputs and outputs of the TDVMCALL. Currently the following values of queued successfully, the TDX guest can poll the status field in the shared-memory area to check whether the Quote generation is completed or not. When completed, the generated Quote is returned via the same buffer. + If the host kernel generates Quotes through the TDX Quoting service provided + by the TDX module, KVM processes the GetQuote request and it will not appear + in userspace. KVM only supports version 1 of the GetQuote request. * ``TDVMCALL_GET_TD_VM_CALL_INFO``: the guest has requested the support status of TDVMCALLs. The output values for the given leaf should be @@ -7342,7 +7345,10 @@ inputs and outputs of the TDVMCALL. Currently the following values of field of the union. * ``TDVMCALL_SETUP_EVENT_NOTIFY_INTERRUPT``: the guest has requested to - set up a notification interrupt for vector ``vector``. + set up a notification interrupt for vector ``vector``. Since this TDVMCALL + is used to optimize ``TDVMCALL_GET_QUOTE``, KVM disables this support in + userspace VMM if ``TDVMCALL_GET_QUOTE`` is completely handled in the kernel. + KVM may add kernel support for this in the future. KVM may add support for more values in the future that may cause a userspace exit, even without calls to ``KVM_ENABLE_CAP`` or similar. In this case, diff --git a/arch/x86/kvm/vmx/tdx.c b/arch/x86/kvm/vmx/tdx.c index bade046da5a1..5aebbec7fa6e 100644 --- a/arch/x86/kvm/vmx/tdx.c +++ b/arch/x86/kvm/vmx/tdx.c @@ -185,7 +185,7 @@ static void td_init_cpuid_entry2(struct kvm_cpuid_entry2 *entry, unsigned char i tdx_clear_unsupported_cpuid(entry); } -#define TDVMCALLINFO_SETUP_EVENT_NOTIFY_INTERRUPT BIT(1) +#define TDVMCALLINFO_SETUP_EVENT_NOTIFY_INTERRUPT BIT_ULL(1) static int init_kvm_tdx_caps(const struct tdx_sys_info_td_conf *td_conf, struct kvm_tdx_capabilities *caps) @@ -202,8 +202,15 @@ static int init_kvm_tdx_caps(const struct tdx_sys_info_td_conf *td_conf, caps->cpuid.nent = td_conf->num_cpuid_config; - caps->user_tdvmcallinfo_1_r11 = - TDVMCALLINFO_SETUP_EVENT_NOTIFY_INTERRUPT; + /* + * Don't advertise userspace event-notify interrupt support if TDX + * quoting service is enabled, as quote generation will be done entirely + * in the kernel. Support in the kernel can be added later if needed. + */ + if (!tdx_quote_enabled()) { + caps->user_tdvmcallinfo_1_r11 |= + TDVMCALLINFO_SETUP_EVENT_NOTIFY_INTERRUPT; + } for (i = 0; i < td_conf->num_cpuid_config; i++) td_init_cpuid_entry2(&caps->cpuid.entries[i], i); @@ -1684,9 +1691,16 @@ static int tdx_get_quote(struct kvm_vcpu *vcpu) static int tdx_setup_event_notify_interrupt(struct kvm_vcpu *vcpu) { + struct kvm_tdx *kvm_tdx = to_kvm_tdx(vcpu->kvm); struct vcpu_tdx *tdx = to_tdx(vcpu); u64 vector = tdx->vp_enter_args.r12; + /* See init_kvm_tdx_caps() for comments */ + if (kvm_tdx->get_quote_in_kernel) { + tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_SUBFUNC_UNSUPPORTED); + return 1; + } + if (vector < 32 || vector > 255) { tdvmcall_set_return_code(vcpu, TDVMCALL_STATUS_INVALID_OPERAND); return 1; -- 2.25.1 Embed version information in SEAMCALL leaf function definitions rather than let the caller open code them. For now, only TDH.VP.INIT is involved. Don't bother the caller to choose the SEAMCALL version if unnecessary. New version SEAMCALLs are guaranteed to be backward compatible, so ideally kernel doesn't need to keep version history and only uses the latest version SEAMCALLs. The concern is some old TDX Modules don't recognize new version SEAMCALLs. Multiple SEAMCALL versions co-exist when kernel should support these old Modules. As time goes by, the old Modules deprecate and old version SEAMCALL definitions should disappear. The old TDX Modules that only support TDH.VP.INIT v0 are all deprecated, so only provide the latest (v1) definition. Signed-off-by: Xu Yilun --- arch/x86/virt/vmx/tdx/tdx.h | 23 ++++++++++++++--------- arch/x86/virt/vmx/tdx/tdx.c | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index 01a7d7d8ada9..10aff23cd01f 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -2,6 +2,7 @@ #ifndef _X86_VIRT_TDX_H #define _X86_VIRT_TDX_H +#include #include /* @@ -11,6 +12,18 @@ * architectural definitions come first. */ +/* + * SEAMCALL leaf: + * + * Bit 15:0 Leaf number + * Bit 23:16 Version number + */ +#define SEAMCALL_LEAF GENMASK(15, 0) +#define SEAMCALL_VER GENMASK(23, 16) + +#define SEAMCALL_LEAF_VER(l, v) (FIELD_PREP(SEAMCALL_LEAF, l) | \ + FIELD_PREP(SEAMCALL_VER, v)) + /* * TDX module SEAMCALL leaf functions */ @@ -31,7 +44,7 @@ #define TDH_VP_CREATE 10 #define TDH_MNG_KEY_FREEID 20 #define TDH_MNG_INIT 21 -#define TDH_VP_INIT 22 +#define TDH_VP_INIT SEAMCALL_LEAF_VER(22, 1) #define TDH_PHYMEM_PAGE_RDMD 24 #define TDH_VP_RD 26 #define TDH_PHYMEM_PAGE_RECLAIM 28 @@ -52,14 +65,6 @@ #define TDH_QUOTE_GET 98 #define TDH_QUOTE_INIT 100 -/* - * SEAMCALL leaf: - * - * Bit 15:0 Leaf number - * Bit 23:16 Version number - */ -#define TDX_VERSION_SHIFT 16 - /* TDX page types */ #define PT_NDA 0x0 #define PT_RSVD 0x1 diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index 821f677e9a86..f7600f930c6e 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -2217,8 +2217,8 @@ u64 tdh_vp_init(struct tdx_vp *vp, u64 initial_rcx, u32 x2apicid) .r8 = x2apicid, }; - /* apicid requires version == 1. */ - return seamcall(TDH_VP_INIT | (1ULL << TDX_VERSION_SHIFT), &args); + /* apicid requires version == 1. See TDH_VP_INIT definition.*/ + return seamcall(TDH_VP_INIT, &args); } EXPORT_SYMBOL_FOR_KVM(tdh_vp_init); -- 2.25.1 From: Peter Fang Enable the TDX Quoting feature via TDH.SYS.CONFIG when supported by the TDX module. The TDX Quoting extension generates TDX attestation Quotes via a SEAMCALL, without using a discrete Quoting engine. TDX Module supports add-on TDX features (e.g. TDX Quoting & TDX Module Extensions) that should be manually enabled by host. It extends TDH.SYS.CONFIG for host to choose to enable them on bootup. Call TDH.SYS.CONFIG with a new bitmap input parameter to specify which features to enable. The bitmap uses the same definitions as TDX_FEATURES0. But note not all bits in TDX_FEATURES0 are valid for configuration, e.g. TDX Module Extensions is a service that supports TDX Quoting, it is implicitly enabled when TDX Quoting is enabled. Setting TDX_FEATURES0_EXT in the bitmap has no effect. TDX Module advances the version of TDH.SYS.CONFIG for the change, so use the latest version (v1) for add-on feature enabling. But supporting existing Modules which only support v0 is still necessary until they are deprecated. In fact, it is unlikely that TDH.SYS.CONFIG ever needs to change again and the code would stay in v1. So there is little value in worrying about deprecating v0 to save a couple lines of code in 5-7 years when these original TDX platforms sunset. TDX Module updates global metadata when add-on features are enabled. Host should update the cached tdx_sysinfo to reflect these changes. Co-developed-by: Xu Yilun Signed-off-by: Xu Yilun Signed-off-by: Peter Fang --- arch/x86/virt/vmx/tdx/tdx.h | 4 +++- arch/x86/virt/vmx/tdx/tdx.c | 24 ++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h index 10aff23cd01f..524a14c01aa6 100644 --- a/arch/x86/virt/vmx/tdx/tdx.h +++ b/arch/x86/virt/vmx/tdx/tdx.h @@ -58,7 +58,8 @@ #define TDH_PHYMEM_CACHE_WB 40 #define TDH_PHYMEM_PAGE_WBINVD 41 #define TDH_VP_WR 43 -#define TDH_SYS_CONFIG 45 +#define TDH_SYS_CONFIG_V0 45 +#define TDH_SYS_CONFIG SEAMCALL_LEAF_VER(TDH_SYS_CONFIG_V0, 1) #define TDH_EXT_INIT 60 #define TDH_EXT_MEM_ADD 61 #define TDH_SYS_DISABLE 69 @@ -97,6 +98,7 @@ struct tdmr_info { /* Bit definitions of TDX_FEATURES0 metadata field */ #define TDX_FEATURES0_NO_RBP_MOD BIT(18) #define TDX_FEATURES0_EXT BIT_ULL(39) +#define TDX_FEATURES0_QUOTE BIT_ULL(50) /* * Do not put any hardware-defined TDX structure representations below diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c index f7600f930c6e..86e5b7ad19b3 100644 --- a/arch/x86/virt/vmx/tdx/tdx.c +++ b/arch/x86/virt/vmx/tdx/tdx.c @@ -1049,6 +1049,7 @@ static __init int construct_tdmrs(struct list_head *tmb_list, static __init int config_tdx_module(struct tdmr_info_list *tdmr_list, u64 global_keyid) { + u64 seamcall_fn = TDH_SYS_CONFIG_V0; struct tdx_module_args args = {}; u64 *tdmr_pa_array; size_t array_sz; @@ -1074,8 +1075,22 @@ static __init int config_tdx_module(struct tdmr_info_list *tdmr_list, args.rcx = __pa(tdmr_pa_array); args.rdx = tdmr_list->nr_consumed_tdmrs; args.r8 = global_keyid; - ret = seamcall_prerr(TDH_SYS_CONFIG, &args); + if (tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_QUOTE) { + args.r9 |= TDX_FEATURES0_QUOTE; + /* These parameters require version >= 1 */ + seamcall_fn = TDH_SYS_CONFIG; + } + + ret = seamcall_prerr(seamcall_fn, &args); + if (ret) + goto free_tdmr; + + /* enabling TDX Quoting may change tdx_sysinfo, update it */ + if (tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_QUOTE) + ret = get_tdx_sys_info(&tdx_sysinfo); + +free_tdmr: /* Free the array as it is not required anymore. */ kfree(tdmr_pa_array); @@ -1384,12 +1399,17 @@ static void tdx_quote_init(void) unsigned int nr_quote_pages; u64 r; + if (!(tdx_sysinfo.features.tdx_features0 & TDX_FEATURES0_QUOTE)) + return; + do { r = seamcall(TDH_QUOTE_INIT, &args); } while (r == TDX_INTERRUPTED_RESUMABLE); - if (r) + if (r) { + pr_err("Failed to enable quoting extension: 0x%llx\n", r); return; + } /* Quoting metadata is valid only after initialization */ if (get_tdx_sys_info_quote(&tdx_sysinfo.quote)) -- 2.25.1