[PATCH v4 20/23] x86/slaunch: support AMD CPUs

Sergii Dmytruk <[email protected]> Sun, 2 Aug 2026 16:09:36 +0300
Newsgroups org.xenproject.lists.xen-devel
Message-ID <280c41d0b12c3b218d8e70d830bafdfb64229468.1785668458.git.sergii.dmytruk@3mdeb.com>
Handle the state after secure-kernel-loader (SKL) in boot/head.S.

Use slr_entry_amd_info::boot_params_base on AMD with SKINIT to get MBI
location.

Locate SLRT which is bootloader's data after SKL on AMD.

Measure AMD-specific data in slaunch_measure_slrt().

Find Intel-compatible TPM event log structure within vendor data of
TCG-compliant event logs.

Signed-off-by: Krystian Hebel <[email protected]>
Signed-off-by: Sergii Dmytruk <[email protected]>
---

Notes:
    v4: squashed "x86/slaunch: support AMD SKINIT" and "x86/boot/slaunch-early: find MBI and SLRT on AMD"
    v4: use CONFIG_SLAUNCH
    v4: update large comment in head.S
    v4: define slaunch_is_amd_drtm() in a header and use twice to avoid duplication
    v4: %#04x => %#x in panic("SLRT is for unexpected architecture ...")
    v4: use container_of()
    v4: don't drop `const` from the result of `slr_next_entry_by_tag()`

 xen/arch/x86/boot/head.S           | 42 ++++++++++++---
 xen/arch/x86/boot/slaunch-early.c  | 52 ++++++++++++++++++
 xen/arch/x86/e820.c                |  2 +-
 xen/arch/x86/include/asm/slaunch.h | 30 +++++++++++
 xen/arch/x86/include/asm/tpm1.h    | 15 ++++++
 xen/arch/x86/slaunch-tpm.c         | 26 +++++++++
 xen/arch/x86/slaunch.c             | 87 ++++++++++++++++++++++++------
 7 files changed, 231 insertions(+), 23 deletions(-)

diff --git a/xen/arch/x86/boot/head.S b/xen/arch/x86/boot/head.S
index ff46579904..bf38aef21c 100644
--- a/xen/arch/x86/boot/head.S
+++ b/xen/arch/x86/boot/head.S
@@ -358,10 +358,14 @@ cs32_switch:
 
 #if CONFIG_SLAUNCH
         /*
-         * Entry point for TrenchBoot Secure Launch on Intel TXT platforms.
+         * Entry point for TrenchBoot Secure Launch, common for Intel TXT and
+         * AMD Secure Startup, but state is slightly different.
+         *
+         * On Intel
+         * --------
          *
          * CPU is in 32b protected mode with paging disabled. On entry:
-         * - %ebx = %eip = MLE entry point,
+         * - %ebx = %eip = this entry point,
          * - stack pointer is undefined,
          * - CS is flat 4GB code segment,
          * - DS, ES, SS, FS and GS are undefined according to TXT SDG, but this
@@ -382,13 +386,36 @@ cs32_switch:
          *   writing a non-zero value at a MONITORed address or via
          *   GETSEC[WAKEUP] instruction, depending on which is supported by a
          *   given SINIT ACM
+         *
+         * On AMD (as implemented by TrenchBoot's secure-kernel-loader or SKL)
+         * -------------------------------------------------------------------
+         *
+         * CPU is in 32b protected mode with paging disabled. On entry:
+         * - %ebx = %eip = this entry point,
+         * - %ebp holds base address of SKL
+         * - stack pointer is treated as undefined for parity with TXT,
+         * - CS is flat 4GB code segment,
+         * - DS, ES, SS are flat 4GB data segments, but treated as undefined for
+         *   parity with TXT.
+         *
+         * Additional restrictions:
+         * - interrupts (including NMIs and SMIs) are disabled and must be
+         *   enabled later
+         * - APs must be brought up by SIPI without an INIT
          */
 slaunch_stub_entry:
         /* Calculate the load base address. */
         mov     %ebx, %esi
         sub     $sym_offs(slaunch_stub_entry), %esi
 
-        /* Mark Secure Launch boot protocol and jump to common entry. */
+        /* On AMD, %ebp holds the base address of SLB, save it for later. */
+        mov     %ebp, %ebx
+
+        /*
+         * Mark Secure Launch boot protocol and jump to common entry. Note that
+         * all general purpose registers except %ebx and %esi are clobbered
+         * between here and .Lslaunch_proto.
+         */
         mov     $SLAUNCH_BOOTLOADER_MAGIC, %eax
         jmp     .Lset_stack
 #endif /* CONFIG_SLAUNCH */
@@ -524,15 +551,18 @@ __start:
         sub     $SL_EIR_size, %esp
 
         push    %esp                              /* pointer to output structure */
+        push    %ebx                              /* Slaunch parameter on AMD */
         mov     $sym_offs(__2M_rwdata_end), %ecx  /* end of target image */
         mov     $sym_offs(_start), %edx           /* target base address */
         mov     %esi, %eax                        /* load base address */
         /*
-         * slaunch_early_init(load/eax, tgt/edx, tgt_end/ecx, ret/stk) using
-         * fastcall calling convention.
+         * slaunch_early_init(load/eax, tgt/edx, tgt_end/ecx,
+         *                    slaunch/stk, ret/stk)
+         *
+         * Uses fastcall calling convention.
          */
         call    slaunch_early_init
-        add     $4, %esp                         /* pop the fourth parameter */
+        add     $8, %esp                         /* pop last two parameters */
 
         /* Move outputs of slaunch_early_init() from the stack. */
         pop     %ebx                  /* store physical MBI address in EBX where
diff --git a/xen/arch/x86/boot/slaunch-early.c b/xen/arch/x86/boot/slaunch-early.c
index 00c772cfdf..9b16602ac8 100644
--- a/xen/arch/x86/boot/slaunch-early.c
+++ b/xen/arch/x86/boot/slaunch-early.c
@@ -13,9 +13,23 @@
 #include <asm/intel-txt.h>
 #include <asm/slaunch.h>
 
+/*
+ * The AMD-defined structure layout for the SLB. The last two fields are
+ * SL-specific.
+ */
+struct skinit_sl_header
+{
+    uint16_t skl_entry_point;
+    uint16_t length;
+    uint8_t reserved[62];
+    uint16_t skl_info_offset;
+    uint16_t bootloader_data_offset;
+} __packed;
+
 void asmlinkage slaunch_early_init(uint32_t load_base_addr,
                                    uint32_t tgt_base_addr,
                                    uint32_t tgt_end_addr,
+                                   uint32_t slaunch_param,
                                    struct slaunch_early_init_results *result)
 {
     void *txt_heap;
@@ -26,6 +40,44 @@ void asmlinkage slaunch_early_init(uint32_t load_base_addr,
     const struct slr_entry_intel_info *intel_info;
     uint32_t size = tgt_end_addr - tgt_base_addr;
 
+    if ( slaunch_is_amd_drtm() )
+    {
+        /*
+         * Not an Intel CPU. Currently the only other option is AMD with SKINIT
+         * and secure-kernel-loader (SKL).
+         */
+        const struct slr_entry_amd_info *amd_info;
+        const struct skinit_sl_header *sl_header = (void *)slaunch_param;
+
+        /*
+         * slaunch_param holds a physical address of SLB.
+         * Bootloader's data is SLRT.
+         */
+        result->slrt_pa = slaunch_param + sl_header->bootloader_data_offset;
+
+        slrt = (struct slr_table *)(uintptr_t)result->slrt_pa;
+
+        entry = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_AMD_INFO);
+        if ( entry == NULL )
+        {
+            /* No reset mechanism or an error register on AMD. */
+            asm volatile ("ud2");
+            unreachable();
+        }
+
+        amd_info = container_of(entry, const struct slr_entry_amd_info, hdr);
+        /* Basic checks only, SKL checked and consumed the rest. */
+        if ( amd_info->hdr.size != sizeof(*amd_info) )
+        {
+            /* No reset mechanism or an error register on AMD. */
+            asm volatile ("ud2");
+            unreachable();
+        }
+
+        result->mbi_pa = amd_info->boot_params_base;
+        return;
+    }
+
     txt_heap = txt_init();
     os_mle = txt_start(txt_heap, TXT_OS2MLE);
     os_sinit = txt_start(txt_heap, TXT_OS2SINIT);
diff --git a/xen/arch/x86/e820.c b/xen/arch/x86/e820.c
index c63b0b12cc..964a02384d 100644
--- a/xen/arch/x86/e820.c
+++ b/xen/arch/x86/e820.c
@@ -501,7 +501,7 @@ static void __init machine_specific_memory_setup(struct e820map *raw)
     uint64_t top_of_ram, size;
     unsigned int i;
 
-    if ( slaunch_active )
+    if ( slaunch_active && boot_cpu_data.x86_vendor == X86_VENDOR_INTEL )
         txt_restore_mtrrs(e820_verbose);
 
     sanitize_e820_map(raw->map, &raw->nr_map);
diff --git a/xen/arch/x86/include/asm/slaunch.h b/xen/arch/x86/include/asm/slaunch.h
index 1b2c5957e4..a9c009fa96 100644
--- a/xen/arch/x86/include/asm/slaunch.h
+++ b/xen/arch/x86/include/asm/slaunch.h
@@ -17,6 +17,8 @@
 #include <xen/slr-table.h>
 #include <xen/types.h>
 
+#include <asm/x86-vendors.h>
+
 #define DRTM_LOC                   2
 #define DRTM_CODE_PCR              17
 #define DRTM_DATA_PCR              18
@@ -56,6 +58,34 @@ static bool slaunch_active = false;
  */
 extern uint32_t slaunch_slrt;
 
+#ifdef __EARLY_SLAUNCH__
+
+static inline bool slaunch_is_amd_drtm(void)
+{
+    /*
+     * asm/processor.h can't be included in early code, which means neither
+     * cpuid() function nor boot_cpu_data can be used here.
+     */
+    uint32_t eax, ebx, ecx, edx;
+    asm volatile ( "cpuid"
+          : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
+          : "0" (0), "c" (0) );
+    return ebx == X86_VENDOR_AMD_EBX
+        && ecx == X86_VENDOR_AMD_ECX
+        && edx == X86_VENDOR_AMD_EDX;
+}
+
+#else   /* __EARLY_SLAUNCH__ */
+
+#include <asm/cpufeature.h>
+
+static inline bool slaunch_is_amd_drtm(void)
+{
+    return boot_cpu_data.x86_vendor == X86_VENDOR_AMD;
+}
+
+#endif  /* __EARLY_SLAUNCH__ */
+
 /*
  * Retrieves pointer to SLRT.  Checks table's validity and maps it as necessary.
  */
diff --git a/xen/arch/x86/include/asm/tpm1.h b/xen/arch/x86/include/asm/tpm1.h
index d1cb2cc041..57a60223ba 100644
--- a/xen/arch/x86/include/asm/tpm1.h
+++ b/xen/arch/x86/include/asm/tpm1.h
@@ -76,4 +76,19 @@ struct TPM12_PCREvent {
     uint8_t Data[];
 };
 
+struct tpm1_spec_id_event {
+    uint32_t pcrIndex;
+    uint32_t eventType;
+    uint8_t digest[20];
+    uint32_t eventSize;
+    uint8_t signature[16];
+    uint32_t platformClass;
+    uint8_t specVersionMinor;
+    uint8_t specVersionMajor;
+    uint8_t specErrata;
+    uint8_t uintnSize;
+    uint8_t vendorInfoSize;
+    uint8_t vendorInfo[0];  /* variable number of members */
+} __packed;
+
 #endif /* X86_TPM1_H */
diff --git a/xen/arch/x86/slaunch-tpm.c b/xen/arch/x86/slaunch-tpm.c
index e3b7341cc5..2f8e598706 100644
--- a/xen/arch/x86/slaunch-tpm.c
+++ b/xen/arch/x86/slaunch-tpm.c
@@ -79,6 +79,16 @@ create_log_event12(struct txt_ev_log_container_12 *evt_log,
     if (evt_log == NULL)
         return log_hashes;
 
+    if ( slaunch_is_amd_drtm() )
+    {
+        /*
+         * On AMD, TXT-compatible structure is stored as vendor data of
+         * TCG-defined event log header.
+         */
+        struct tpm1_spec_id_event *spec_id = (void *)evt_log;
+        evt_log = (struct txt_ev_log_container_12 *)&spec_id->vendorInfo[0];
+    }
+
     new_entry = (void *)evt_log + evt_log->NextEventOffset;
 
     /*
@@ -114,6 +124,22 @@ find_evt_log_ext_data(struct tpm2_spec_id_event *evt_log)
     struct txt_os_sinit_data *os_sinit;
     struct txt_ext_data_element *ext_data;
 
+    if ( slaunch_is_amd_drtm() )
+    {
+        /*
+         * Event log pointer is defined by TXT specification, but
+         * secure-kernel-loader provides a compatible structure in vendor data
+         * of the log.
+         */
+        uint8_t *data_size =
+            (uint8_t *)&evt_log->digestSizes[evt_log->digestCount];
+        if ( *data_size != sizeof(struct heap_event_log_pointer_element2_1) )
+            return NULL;
+
+        /* Vendor data directly follows a single-byte size. */
+        return (struct heap_event_log_pointer_element2_1 *)(data_size + 1);
+    }
+
     os_sinit = txt_start(__va(txt_read(TXTCR_HEAP_BASE)), TXT_OS2SINIT);
     ext_data = txt_find_ext_data_element(os_sinit,
                                          TXT_HEAP_EXTDATA_TYPE_EVENT_LOG_POINTER2_1);
diff --git a/xen/arch/x86/slaunch.c b/xen/arch/x86/slaunch.c
index ac62301f93..af88ca9caa 100644
--- a/xen/arch/x86/slaunch.c
+++ b/xen/arch/x86/slaunch.c
@@ -23,6 +23,10 @@
 #include <asm/slaunch-tpm.h>
 #include <asm/tpm.h>
 
+/* SLB is 64k, 64k-aligned */
+#define SKINIT_SLB_SIZE   0x10000
+#define SKINIT_SLB_ALIGN  0x10000
+
 /*
  * These variables are assigned to by the code near Xen's entry point.
  *
@@ -48,6 +52,8 @@ struct slr_table *__init slaunch_get_slrt(void)
     if ( slrt == NULL )
     {
         int rc;
+        bool intel_cpu = (boot_cpu_data.x86_vendor == X86_VENDOR_INTEL);
+        uint16_t slrt_architecture = intel_cpu ? SLR_INTEL_TXT : SLR_AMD_SKINIT;
 
         slrt = __va(slaunch_slrt);
 
@@ -59,9 +65,9 @@ struct slr_table *__init slaunch_get_slrt(void)
         /* XXX: are newer revisions allowed? */
         if ( slrt->revision != SLR_TABLE_REVISION )
             panic("SLRT is of unsupported revision: %#x!\n", slrt->revision);
-        if ( slrt->architecture != SLR_INTEL_TXT )
-            panic("SLRT is for unexpected architecture: %#x!\n",
-                  slrt->architecture);
+        if ( slrt->architecture != slrt_architecture )
+            panic("SLRT is for unexpected architecture: %#x != %#x!\n",
+                  slrt->architecture, slrt_architecture);
         if ( slrt->size > slrt->max_size )
             panic("SLRT is larger than its max size: %#x > %#x!\n",
                   slrt->size, slrt->max_size);
@@ -76,6 +82,23 @@ struct slr_table *__init slaunch_get_slrt(void)
     return slrt;
 }
 
+static uint32_t __init get_slb_start(void)
+{
+    /*
+     * The runtime computation relies on size being a power of 2 and equal to
+     * alignment. Make sure these assumptions hold.
+     */
+    BUILD_BUG_ON(SKINIT_SLB_SIZE != SKINIT_SLB_ALIGN);
+    BUILD_BUG_ON(SKINIT_SLB_SIZE == 0);
+    BUILD_BUG_ON((SKINIT_SLB_SIZE & (SKINIT_SLB_SIZE - 1)) != 0);
+
+    /*
+     * Rounding any address within SLB down to alignment gives SLB base and
+     * SLRT is inside SLB on AMD.
+     */
+    return slaunch_slrt & ~(SKINIT_SLB_SIZE - 1);
+}
+
 void __init slaunch_map_mem_regions(void)
 {
     int rc;
@@ -86,7 +109,10 @@ void __init slaunch_map_mem_regions(void)
     BUG_ON(rc != 0);
 
     /* Vendor-specific part. */
-    txt_map_mem_regions();
+    if ( boot_cpu_data.x86_vendor == X86_VENDOR_INTEL )
+        txt_map_mem_regions();
+    else if ( boot_cpu_data.x86_vendor == X86_VENDOR_AMD )
+        slaunch_map_l2(get_slb_start(), SKINIT_SLB_SIZE);
 
     slaunch_find_log(slaunch_get_slrt(), &evt_log_addr, &evt_log_size);
     if ( evt_log_addr != 0 )
@@ -98,17 +124,27 @@ void __init slaunch_map_mem_regions(void)
 
 void __init slaunch_reserve_mem_regions(void)
 {
+    int ok;
     paddr_t evt_log_addr;
     uint32_t evt_log_size;
 
     /* Vendor-specific part. */
-    txt_reserve_mem_regions();
+    if ( boot_cpu_data.x86_vendor == X86_VENDOR_INTEL )
+    {
+        txt_reserve_mem_regions();
+    }
+    else if ( boot_cpu_data.x86_vendor == X86_VENDOR_AMD )
+    {
+        uint64_t slb_start = get_slb_start();
+        uint64_t slb_end = slb_start + SKINIT_SLB_SIZE;
+        printk("SLAUNCH: reserving SLB [%#lx, %#lx)\n", slb_start, slb_end);
+        ok = reserve_e820_ram(&e820_raw, slb_start, slb_end);
+        BUG_ON(!ok);
+    }
 
     slaunch_find_log(slaunch_get_slrt(), &evt_log_addr, &evt_log_size);
     if ( evt_log_addr != 0 )
     {
-        int ok;
-
         printk("SLAUNCH: reserving event log [%#lx, %#lx)\n", evt_log_addr,
                evt_log_addr + evt_log_size);
         ok = reserve_e820_ram(&e820_raw, evt_log_addr,
@@ -129,18 +165,37 @@ void __init slaunch_measure_slrt(void)
          * In revision one of the SLRT, only platform-specific info table is
          * measured.
          */
-        struct slr_entry_intel_info tmp;
+        if ( boot_cpu_data.x86_vendor == X86_VENDOR_INTEL )
+        {
+            struct slr_entry_intel_info tmp;
 
-        entry = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_INTEL_INFO);
-        if ( entry == NULL )
-            panic("SLRT is missing Intel-specific information!\n");
+            entry = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_INTEL_INFO);
+            if ( entry == NULL )
+                panic("SLRT is missing Intel-specific information!\n");
 
-        tmp = *container_of(entry, const struct slr_entry_intel_info, hdr);
-        tmp.boot_params_base = 0;
-        tmp.txt_heap = 0;
+            tmp = *container_of(entry, const struct slr_entry_intel_info, hdr);
+            tmp.boot_params_base = 0;
+            tmp.txt_heap = 0;
 
-        slaunch_hash_extend(DRTM_LOC, DRTM_DATA_PCR, (uint8_t *)&tmp,
-                            sizeof(tmp), DLE_EVTYPE_SLAUNCH, NULL, 0);
+            slaunch_hash_extend(DRTM_LOC, DRTM_DATA_PCR, (uint8_t *)&tmp,
+                                sizeof(tmp), DLE_EVTYPE_SLAUNCH, NULL, 0);
+        }
+        else if ( boot_cpu_data.x86_vendor == X86_VENDOR_AMD )
+        {
+            struct slr_entry_amd_info tmp;
+
+            entry = slr_next_entry_by_tag(slrt, NULL, SLR_ENTRY_AMD_INFO);
+            if ( entry == NULL )
+                panic("SLRT is missing AMD-specific information!\n");
+
+            tmp = *container_of(entry, const struct slr_entry_amd_info, hdr);
+            tmp.next = 0;
+            tmp.slrt_base = 0;
+            tmp.boot_params_base = 0;
+
+            slaunch_hash_extend(DRTM_LOC, DRTM_DATA_PCR, (uint8_t *)&tmp,
+                                sizeof(tmp), DLE_EVTYPE_SLAUNCH, NULL, 0);
+        }
     }
     else
     {
-- 
2.55.0