[RFC PATCH kernel 14/17] x86/sev: Add GHCB calls for SEV-TIO
From: Alexey Kardashevskiy
Date: Wed Sep 16 2026 - 08:17:03 EST
SEV-TIO is a PSP protocol allowing PCI pass through of trusted devices
(TDI == TEE Device Interface) to an SNP VM. The VMM advertises
the support via HV_FEATURES and implements new calls:
1) TDI operation: the host executes on the guest behalf:
- TDI bind/unbind to/from the SNP VM in the PSP;
- TDI start/stop which translates to TDISP interface start/stop.
2) TIO guest request for an encrypted communication channel between
the SNP VM and the PSP, it follows the existing Extended Guest Request
pattern to read device evidence:
- get TDI status;
- validate MMIO ranges (a variant of the PVALIDATE instruction for
MMIO RMPs);
- configure sDTE (a secure IOMMU descriptor).
Extend snp_req_data to pass more parameters.
Signed-off-by: Alexey Kardashevskiy <aik@xxxxxxx>
---
arch/x86/include/asm/sev-common.h | 1 +
arch/x86/include/asm/sev.h | 5 +++
arch/x86/include/uapi/asm/svm.h | 40 +++++++++++++++++++
arch/x86/coco/sev/core.c | 42 ++++++++++++++++++++
4 files changed, 88 insertions(+)
diff --git a/arch/x86/include/asm/sev-common.h b/arch/x86/include/asm/sev-common.h
index 01a6e4dbe423..ff763c3c5d63 100644
--- a/arch/x86/include/asm/sev-common.h
+++ b/arch/x86/include/asm/sev-common.h
@@ -137,6 +137,7 @@ enum psc_op {
#define GHCB_HV_FT_SNP BIT_ULL(0)
#define GHCB_HV_FT_SNP_AP_CREATION BIT_ULL(1)
#define GHCB_HV_FT_SNP_MULTI_VMPL BIT_ULL(5)
+#define GHCB_HV_FT_SNP_SEV_TIO BIT_ULL(7)
/*
* SNP Page State Change NAE event
diff --git a/arch/x86/include/asm/sev.h b/arch/x86/include/asm/sev.h
index 9e7a077c445d..89aaccb053ba 100644
--- a/arch/x86/include/asm/sev.h
+++ b/arch/x86/include/asm/sev.h
@@ -149,6 +149,9 @@ struct snp_req_data {
unsigned long resp_gpa;
unsigned long data_gpa;
unsigned int data_npages;
+ unsigned int guest_rid;
+ unsigned long npages;
+ unsigned long param;
};
#define MAX_AUTHTAG_LEN 32
@@ -597,6 +600,8 @@ static inline void sev_evict_cache(void *va, int npages)
}
}
+int sev_tio_op(u32 guest_rid, unsigned int op, u64 *fw_err, u64 *tdi_id);
+
#else /* !CONFIG_AMD_MEM_ENCRYPT */
#define snp_vmpl 0
diff --git a/arch/x86/include/uapi/asm/svm.h b/arch/x86/include/uapi/asm/svm.h
index 010a45c9f614..93597ad492bf 100644
--- a/arch/x86/include/uapi/asm/svm.h
+++ b/arch/x86/include/uapi/asm/svm.h
@@ -122,6 +122,44 @@
#define SVM_VMGEXIT_SAVIC_REGISTER_GPA 0
#define SVM_VMGEXIT_SAVIC_UNREGISTER_GPA 1
#define SVM_VMGEXIT_SAVIC_SELF_GPA ~0ULL
+#define SVM_VMGEXIT_SEV_TIO_GR 0x80000020ull
+#define SVM_VMGEXIT_SEV_TIO_GR_INFO_STATE BIT(0)
+#define SVM_VMGEXIT_SEV_TIO_GR_INFO_CERTS BIT(1)
+#define SVM_VMGEXIT_SEV_TIO_GR_INFO_MEAS BIT(2)
+#define SVM_VMGEXIT_SEV_TIO_GR_INFO_REPORT BIT(3)
+
+#define SVM_VMGEXIT_SEV_TIO_GR_SDTE_VALIDATE BIT(0)
+#define SVM_VMGEXIT_SEV_TIO_GR_SDTE_VTOM GENMASK_ULL(51, 21)
+
+/*
+ * TIO_GUEST_REQUEST's MMIO_VALIDATE_REQ/MMIO_CONFIG_REQ encoding for MMIO in RDX:
+ *
+ * T....... ....GGGG GGGGGGGG GGGGGGGG GGGGGGGG GGGGGGGG GGGG.... ........
+ * Where:
+ * G - guest physical address
+ * T - TEE
+ */
+#define SVM_VMGEXIT_SEV_TIO_GR_MMIO_GFN(r) (((r) & 0x000FFFFFFFFFF000ULL) >> PAGE_SHIFT)
+#define SVM_VMGEXIT_SEV_TIO_GR_MMIO_RESERVED(r) ((r) & 0x7FF0000000000FFFULL)
+#define SVM_VMGEXIT_SEV_TIO_GR_MMIO_PRIVATE(r) (!!((r) & BIT(63)))
+
+#define SVM_VMGEXIT_SEV_TIO_GR_MMIO_NUM(r) ((uint32_t)((r) >> 32))
+#define SVM_VMGEXIT_SEV_TIO_GR_MMIO_ADDR(r) ((uint32_t)((r) & 0xFFFFFFFF))
+
+#define SVM_VMGEXIT_SEV_TIO_GR_MMIO_MK_VALIDATE(start, private) \
+ ((SVM_VMGEXIT_SEV_TIO_GR_MMIO_GFN(start) << PAGE_SHIFT) | \
+ ((private) ? BIT(63) : 0))
+
+#define SVM_VMGEXIT_SEV_TIO_OP 0x80000021ull
+#define SVM_VMGEXIT_SEV_TIO_GR_MMIO_MK_NUM_BDFN(n, bdfn) (((uint64_t)(n) << 32) | (bdfn))
+
+#define SVM_VMGEXIT_SEV_TIO_OP_PARAM(guest_id, action) ((u64)(action)<<32|(guest_id))
+#define SVM_VMGEXIT_SEV_TIO_OP_ACTION(exitinfo1) ((exitinfo1)>>32)
+#define SVM_VMGEXIT_SEV_TIO_OP_GUEST_ID(exitinfo1) ((exitinfo1) & 0xFFFFFFFF)
+#define SVM_VMGEXIT_SEV_TIO_OP_BIND 0
+#define SVM_VMGEXIT_SEV_TIO_OP_UNBIND 1
+#define SVM_VMGEXIT_SEV_TIO_OP_RUN 2
+#define SVM_VMGEXIT_SEV_TIO_OP_STOP 3
#define SVM_VMGEXIT_HV_FEATURES 0x8000fffdull
#define SVM_VMGEXIT_TERM_REQUEST 0x8000fffeull
#define SVM_VMGEXIT_TERM_REASON(reason_set, reason_code) \
@@ -245,6 +283,8 @@
{ SVM_VMGEXIT_GUEST_REQUEST, "vmgexit_guest_request" }, \
{ SVM_VMGEXIT_EXT_GUEST_REQUEST, "vmgexit_ext_guest_request" }, \
{ SVM_VMGEXIT_AP_CREATION, "vmgexit_ap_creation" }, \
+ { SVM_VMGEXIT_SEV_TIO_GR, "vmgexit_sev_tio_guest_request" }, \
+ { SVM_VMGEXIT_SEV_TIO_OP, "vmgexit_sev_tio_op" }, \
{ SVM_VMGEXIT_HV_FEATURES, "vmgexit_hypervisor_feature" }, \
{ SVM_EXIT_ERR, "invalid_guest_state" }
diff --git a/arch/x86/coco/sev/core.c b/arch/x86/coco/sev/core.c
index 5b912f42f493..ed0e4546d5e5 100644
--- a/arch/x86/coco/sev/core.c
+++ b/arch/x86/coco/sev/core.c
@@ -104,6 +104,37 @@ static unsigned long snp_tsc_freq_khz __ro_after_init;
DEFINE_PER_CPU(struct sev_es_runtime_data*, runtime_data);
DEFINE_PER_CPU(struct sev_es_save_area *, sev_vmsa);
+int sev_tio_op(u32 guest_rid, unsigned int op, u64 *fw_err, u64 *tdi_id)
+{
+ struct ghcb_state state;
+ struct es_em_ctxt ctxt;
+ struct ghcb *ghcb;
+ int ret;
+
+ /* __sev_get_ghcb() needs IRQs disabled because it uses per-CPU GHCB. */
+ guard(irqsave)();
+
+ ghcb = __sev_get_ghcb(&state);
+ if (!ghcb)
+ return -EIO;
+
+ vc_ghcb_invalidate(ghcb);
+ ret = sev_es_ghcb_hv_call(ghcb, &ctxt, SVM_VMGEXIT_SEV_TIO_OP,
+ SVM_VMGEXIT_SEV_TIO_OP_PARAM(guest_rid, op), 0);
+
+ *fw_err = ghcb->save.sw_exit_info_2;
+ if (*fw_err)
+ ret = -EIO;
+
+ if (!ret && op == SVM_VMGEXIT_SEV_TIO_OP_BIND && tdi_id)
+ *tdi_id = ghcb_get_rcx(ghcb);
+
+ __sev_put_ghcb(&state);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(sev_tio_op);
+
/*
* SVSM related information:
* When running under an SVSM, the VMPL that Linux is executing at must be
@@ -1345,6 +1376,11 @@ static int snp_issue_guest_request(struct snp_guest_req *req)
if (req->exit_code == SVM_VMGEXIT_EXT_GUEST_REQUEST) {
ghcb_set_rax(ghcb, input->data_gpa);
ghcb_set_rbx(ghcb, input->data_npages);
+ } else if (req->exit_code == SVM_VMGEXIT_SEV_TIO_GR) {
+ ghcb_set_rax(ghcb, input->data_gpa);
+ ghcb_set_rbx(ghcb, input->data_npages);
+ ghcb_set_rcx(ghcb, ((uint64_t)input->npages << 32) | input->guest_rid);
+ ghcb_set_rdx(ghcb, input->param);
}
ret = sev_es_ghcb_hv_call(ghcb, &ctxt, req->exit_code, input->req_gpa, input->resp_gpa);
@@ -1354,6 +1390,8 @@ static int snp_issue_guest_request(struct snp_guest_req *req)
req->exitinfo2 = ghcb->save.sw_exit_info_2;
switch (req->exitinfo2) {
case 0:
+ if (req->exit_code == SVM_VMGEXIT_SEV_TIO_GR)
+ input->param = ghcb_get_rdx(ghcb);
break;
case SNP_GUEST_VMM_ERR(SNP_GUEST_VMM_ERR_BUSY):
@@ -1366,6 +1404,10 @@ static int snp_issue_guest_request(struct snp_guest_req *req)
input->data_npages = ghcb_get_rbx(ghcb);
ret = -ENOSPC;
break;
+ } else if (req->exit_code == SVM_VMGEXIT_SEV_TIO_GR) {
+ input->data_npages = ghcb_get_rbx(ghcb);
+ ret = -ENOSPC;
+ break;
}
fallthrough;
default:
--
2.55.0