Linux Confidential Computing Development
 help / color / mirror / Atom feed
* [RFC PATCH v3 05/10] coco: host: arm64: Add helper to stop and tear down an RMM pdev
From: Aneesh Kumar K.V (Arm) @ 2026-03-12  8:01 UTC (permalink / raw)
  To: linux-coco, kvmarm, linux-arm-kernel
  Cc: linux-kernel, Aneesh Kumar K.V (Arm), Marc Zyngier,
	Catalin Marinas, Will Deacon, Jonathan Cameron, Jason Gunthorpe,
	Dan Williams, Alexey Kardashevskiy, Samuel Ortiz, Xu Yilun,
	Suzuki K Poulose, Steven Price, Jonathan Cameron
In-Reply-To: <20260312080129.3483585-1-aneesh.kumar@kernel.org>

Add helper to stop and tear down an RMM pdev
- describe the RMI_PDEV_STOP/RMI_PDEV_DESTROY SMC IDs and provide
  wrappers in rmi_cmds.h
- implement pdev_stop_and_destroy() so the host driver stops the pdev,
  waits for it to reach RMI_PDEV_STOPPED, destroys it, frees auxiliary
  granules, and drops the delegated page

Cc: Marc Zyngier <maz@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Samuel Ortiz <sameo@rivosinc.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: Suzuki K Poulose <Suzuki.Poulose@arm.com>
Cc: Steven Price <steven.price@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 arch/arm64/include/asm/rmi_cmds.h       | 18 +++++++++++++++
 arch/arm64/include/asm/rmi_smc.h        |  2 ++
 drivers/virt/coco/arm-cca-host/rmi-da.c | 30 +++++++++++++++++++++++++
 drivers/virt/coco/arm-cca-host/rmi-da.h |  1 +
 4 files changed, 51 insertions(+)

diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index b86bf15afcda..f10a0dcaa308 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -556,4 +556,22 @@ static inline unsigned long rmi_pdev_abort(unsigned long pdev_phys)
 	return res.a0;
 }
 
+static inline unsigned long rmi_pdev_stop(unsigned long pdev_phys)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_1_1_invoke(SMC_RMI_PDEV_STOP, pdev_phys, &res);
+
+	return res.a0;
+}
+
+static inline unsigned long rmi_pdev_destroy(unsigned long pdev_phys)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_1_1_invoke(SMC_RMI_PDEV_DESTROY, pdev_phys, &res);
+
+	return res.a0;
+}
+
 #endif /* __ASM_RMI_CMDS_H */
diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
index c91cd0e389a9..3a57c7245029 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -50,7 +50,9 @@
 #define SMC_RMI_PDEV_ABORT		SMC_RMI_CALL(0x0174)
 #define SMC_RMI_PDEV_COMMUNICATE        SMC_RMI_CALL(0x0175)
 #define SMC_RMI_PDEV_CREATE             SMC_RMI_CALL(0x0176)
+#define SMC_RMI_PDEV_DESTROY		SMC_RMI_CALL(0x0177)
 #define SMC_RMI_PDEV_GET_STATE		SMC_RMI_CALL(0x0178)
+#define SMC_RMI_PDEV_STOP		SMC_RMI_CALL(0x017c)
 
 #define RMI_ABI_MAJOR_VERSION	1
 #define RMI_ABI_MINOR_VERSION	0
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
index 93512f7e73d5..ba6d67e5f54e 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.c
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -435,3 +435,33 @@ int cca_pdev_ide_setup(struct pci_dev *pdev)
 {
 	return submit_pdev_state_transition_work(pdev, RMI_PDEV_NEEDS_KEY);
 }
+
+void cca_pdev_stop_and_destroy(struct pci_dev *pdev)
+{
+	int ret;
+	struct cca_host_pf0_dsc *pf0_dsc = to_cca_pf0_dsc(pdev);
+	phys_addr_t rmm_pdev_phys = virt_to_phys(pf0_dsc->rmm_pdev);
+
+	if (WARN_ON(rmi_pdev_stop(rmm_pdev_phys)))
+		return;
+
+	ret = submit_pdev_state_transition_work(pdev, RMI_PDEV_STOPPED);
+	if (ret)
+		return;
+
+	if (WARN_ON(rmi_pdev_destroy(rmm_pdev_phys)))
+		return;
+
+	kfree(pf0_dsc->cert_chain.public_key);
+	kvfree(pf0_dsc->cert_chain.cache);
+	kvfree(pf0_dsc->vca);
+	pf0_dsc->cert_chain.cache = NULL;
+	pf0_dsc->vca = NULL;
+
+	/* Free the aux granules */
+	free_aux_pages(pf0_dsc->num_aux, pf0_dsc->aux);
+	pf0_dsc->num_aux = 0;
+	if (!rmi_granule_undelegate(rmm_pdev_phys))
+		free_page((unsigned long)pf0_dsc->rmm_pdev);
+	pf0_dsc->rmm_pdev = NULL;
+}
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index db4bf893f596..fbfbcd40beb4 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -116,4 +116,5 @@ static inline struct cca_host_comm_data *to_cca_comm_data(struct pci_dev *pdev)
 
 int cca_pdev_create(struct pci_dev *pdev);
 int cca_pdev_ide_setup(struct pci_dev *pdev);
+void cca_pdev_stop_and_destroy(struct pci_dev *pdev);
 #endif
-- 
2.43.0


^ permalink raw reply related

* [RFC PATCH v3 04/10] coco: host: arm64: Add RMM device communication helpers
From: Aneesh Kumar K.V (Arm) @ 2026-03-12  8:01 UTC (permalink / raw)
  To: linux-coco, kvmarm, linux-arm-kernel
  Cc: linux-kernel, Aneesh Kumar K.V (Arm), Marc Zyngier,
	Catalin Marinas, Will Deacon, Jonathan Cameron, Jason Gunthorpe,
	Dan Williams, Alexey Kardashevskiy, Samuel Ortiz, Xu Yilun,
	Suzuki K Poulose, Steven Price
In-Reply-To: <20260312080129.3483585-1-aneesh.kumar@kernel.org>

- add SMCCC IDs/wrappers for RMI_PDEV_COMMUNICATE/RMI_PDEV_ABORT
- describe the RMM device-communication ABI (struct rmi_dev_comm_*,
  cache flags, protocol/object IDs, busy error code)
- track per-PF0 communication state (buffers, workqueue, cache metadata) and
  serialize access behind object_lock
- plumb a DOE/SPDM worker (pdev_communicate_work) plus shared helpers that
  submit the SMCCC call, cache multi-part responses, and handle retries/abort
- hook the new helpers into the physical function connect path so IDE
  setup can drive the device to the expected state

Cc: Marc Zyngier <maz@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Samuel Ortiz <sameo@rivosinc.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: Suzuki K Poulose <Suzuki.Poulose@arm.com>
Cc: Steven Price <steven.price@arm.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 arch/arm64/include/asm/rmi_cmds.h        |  20 ++
 arch/arm64/include/asm/rmi_smc.h         |  63 +++++
 drivers/virt/coco/arm-cca-host/arm-cca.c |  50 ++++
 drivers/virt/coco/arm-cca-host/rmi-da.c  | 281 +++++++++++++++++++++++
 drivers/virt/coco/arm-cca-host/rmi-da.h  |  66 ++++++
 5 files changed, 480 insertions(+)

diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index 4547ce0901a6..b86bf15afcda 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -536,4 +536,24 @@ static inline unsigned long rmi_pdev_get_state(unsigned long pdev_phys, enum rmi
 	return res.a0;
 }
 
+static inline unsigned long rmi_pdev_communicate(unsigned long pdev_phys,
+						 unsigned long pdev_comm_data_phys)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_1_1_invoke(SMC_RMI_PDEV_COMMUNICATE,
+			     pdev_phys, pdev_comm_data_phys, &res);
+
+	return res.a0;
+}
+
+static inline unsigned long rmi_pdev_abort(unsigned long pdev_phys)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_1_1_invoke(SMC_RMI_PDEV_ABORT, pdev_phys, &res);
+
+	return res.a0;
+}
+
 #endif /* __ASM_RMI_CMDS_H */
diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
index 58106740c1f7..c91cd0e389a9 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -47,6 +47,8 @@
 #define SMC_RMI_RTT_INIT_RIPAS		SMC_RMI_CALL(0x0168)
 #define SMC_RMI_RTT_SET_RIPAS		SMC_RMI_CALL(0x0169)
 
+#define SMC_RMI_PDEV_ABORT		SMC_RMI_CALL(0x0174)
+#define SMC_RMI_PDEV_COMMUNICATE        SMC_RMI_CALL(0x0175)
 #define SMC_RMI_PDEV_CREATE             SMC_RMI_CALL(0x0176)
 #define SMC_RMI_PDEV_GET_STATE		SMC_RMI_CALL(0x0178)
 
@@ -69,6 +71,7 @@
 #define RMI_ERROR_REALM		2
 #define RMI_ERROR_REC		3
 #define RMI_ERROR_RTT		4
+#define RMI_BUSY		10
 
 enum rmi_ripas {
 	RMI_EMPTY = 0,
@@ -362,4 +365,64 @@ struct rmi_pdev_params {
 	};
 };
 
+#define RMI_DEV_COMM_EXIT_CACHE_REQ	BIT(0)
+#define RMI_DEV_COMM_EXIT_CACHE_RSP	BIT(1)
+#define RMI_DEV_COMM_EXIT_SEND		BIT(2)
+#define RMI_DEV_COMM_EXIT_WAIT		BIT(3)
+#define RMI_DEV_COMM_EXIT_RSP_RESET	BIT(4)
+#define RMI_DEV_COMM_EXIT_MULTI		BIT(5)
+
+#define RMI_DEV_COMM_NONE	0
+#define RMI_DEV_COMM_RESPONSE	1
+#define RMI_DEV_COMM_ERROR	2
+
+#define RMI_PROTOCOL_SPDM		0
+#define RMI_PROTOCOL_SECURE_SPDM	1
+
+#define RMI_DEV_VCA			0
+#define RMI_DEV_CERTIFICATE		1
+#define RMI_DEV_MEASUREMENTS		2
+#define RMI_DEV_INTERFACE_REPORT	3
+
+struct rmi_dev_comm_enter {
+	union {
+		u8 status;
+		u64 padding0;
+	};
+	u64 req_addr;
+	u64 resp_addr;
+	u64 resp_len;
+};
+
+struct rmi_dev_comm_exit {
+	u64 flags;
+	u64 req_cache_offset;
+	u64 req_cache_len;
+	u64 rsp_cache_offset;
+	u64 rsp_cache_len;
+	union {
+		u8 cache_obj_id;
+		u64 padding0;
+	};
+
+	union {
+		u8 protocol;
+		u64 padding1;
+	};
+	u64 req_delay;
+	u64 req_len;
+	u64 rsp_timeout;
+};
+
+struct rmi_dev_comm_data {
+	union { /* 0x0 */
+		struct rmi_dev_comm_enter enter;
+		u8 padding0[0x800];
+	};
+	union { /* 0x800 */
+		struct rmi_dev_comm_exit exit;
+		u8 padding1[0x800];
+	};
+};
+
 #endif /* __ASM_RMI_SMC_H */
diff --git a/drivers/virt/coco/arm-cca-host/arm-cca.c b/drivers/virt/coco/arm-cca-host/arm-cca.c
index 639ebd82978a..4ed5e8ec9e91 100644
--- a/drivers/virt/coco/arm-cca-host/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-host/arm-cca.c
@@ -47,6 +47,7 @@ static struct pci_tsm *cca_tsm_pci_probe(struct tsm_dev *tsm_dev, struct pci_dev
 	rc = pci_tsm_pf0_constructor(pdev, &pf0_dsc->pci, tsm_dev);
 	if (rc)
 		return NULL;
+	mutex_init(&pf0_dsc->object_lock);
 
 	pci_dbg(pdev, "tsm enabled\n");
 	return &no_free_ptr(pf0_dsc)->pci.base_tsm;
@@ -66,6 +67,55 @@ static void cca_tsm_pci_remove(struct pci_tsm *tsm)
 	}
 }
 
+static __maybe_unused int init_dev_communication_buffers(struct pci_dev *pdev,
+							 struct cca_host_comm_data *comm_data)
+{
+	int ret = -ENOMEM;
+
+	comm_data->io_params = (struct rmi_dev_comm_data *)get_zeroed_page(GFP_KERNEL);
+	if (!comm_data->io_params)
+		goto err_out;
+
+	comm_data->rsp_buff = (void *)__get_free_page(GFP_KERNEL);
+	if (!comm_data->rsp_buff)
+		goto err_res_buff;
+
+	comm_data->req_buff = (void *)__get_free_page(GFP_KERNEL);
+	if (!comm_data->req_buff)
+		goto err_req_buff;
+
+	comm_data->work_queue = alloc_ordered_workqueue("%s %s DEV_COMM", 0,
+						dev_bus_name(&pdev->dev),
+						pci_name(pdev));
+	if (!comm_data->work_queue)
+		goto err_work_queue;
+
+	comm_data->io_params->enter.status = RMI_DEV_COMM_NONE;
+	comm_data->io_params->enter.resp_addr = virt_to_phys(comm_data->rsp_buff);
+	comm_data->io_params->enter.req_addr  = virt_to_phys(comm_data->req_buff);
+	comm_data->io_params->enter.resp_len = 0;
+
+	return 0;
+
+err_work_queue:
+	free_page((unsigned long)comm_data->req_buff);
+err_req_buff:
+	free_page((unsigned long)comm_data->rsp_buff);
+err_res_buff:
+	free_page((unsigned long)comm_data->io_params);
+err_out:
+	return ret;
+}
+
+static inline void free_dev_communication_buffers(struct cca_host_comm_data *comm_data)
+{
+	destroy_workqueue(comm_data->work_queue);
+
+	free_page((unsigned long)comm_data->req_buff);
+	free_page((unsigned long)comm_data->rsp_buff);
+	free_page((unsigned long)comm_data->io_params);
+}
+
 /* For now global for simplicity. Protected by pci_tsm_rwsem */
 static DECLARE_BITMAP(cca_stream_ids, MAX_STREAM_ID);
 static int alloc_stream_id(struct pci_host_bridge *hb)
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
index 89b61ad5bc00..93512f7e73d5 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.c
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -5,6 +5,8 @@
 
 #include <linux/pci.h>
 #include <linux/pci-ecam.h>
+#include <linux/pci-doe.h>
+#include <linux/delay.h>
 #include <asm/rmi_cmds.h>
 
 #include "rmi-da.h"
@@ -154,3 +156,282 @@ int cca_pdev_create(struct pci_dev *pci_dev)
 		free_page((unsigned long)rmm_pdev);
 	return ret;
 }
+
+static int doe_send_req_resp(struct pci_tsm *tsm)
+{
+	int data_obj_type;
+	struct cca_host_comm_data *comm_data = to_cca_comm_data(tsm->pdev);
+	struct rmi_dev_comm_exit *io_exit = &comm_data->io_params->exit;
+	u8 protocol = io_exit->protocol;
+
+	if (protocol == RMI_PROTOCOL_SPDM)
+		data_obj_type = PCI_DOE_FEATURE_CMA;
+	else if (protocol == RMI_PROTOCOL_SECURE_SPDM)
+		data_obj_type = PCI_DOE_FEATURE_SSESSION;
+	else
+		return -EINVAL;
+
+	/* delay the send */
+	if (io_exit->req_delay)
+		fsleep(io_exit->req_delay);
+
+	return pci_tsm_doe_transfer(tsm->dsm_dev, data_obj_type,
+				    comm_data->req_buff, io_exit->req_len,
+				    comm_data->rsp_buff, PAGE_SIZE);
+}
+
+static inline bool pending_dev_communicate(struct rmi_dev_comm_exit *io_exit)
+{
+	bool pending = io_exit->flags & (RMI_DEV_COMM_EXIT_CACHE_REQ |
+					 RMI_DEV_COMM_EXIT_CACHE_RSP |
+					 RMI_DEV_COMM_EXIT_SEND |
+					 RMI_DEV_COMM_EXIT_WAIT |
+					 RMI_DEV_COMM_EXIT_MULTI);
+	return pending;
+}
+
+static inline gfp_t cache_obj_id_to_gfp_flags(u8 cache_obj_id)
+{
+	/* These two cache objects are system objects. */
+	if (cache_obj_id == RMI_DEV_VCA || cache_obj_id == RMI_DEV_CERTIFICATE)
+		return GFP_KERNEL;
+	/* rest are per TDI which is associated to a VM */
+	return GFP_KERNEL_ACCOUNT;
+}
+
+static int _do_dev_communicate(enum dev_comm_type type, struct pci_tsm *tsm)
+{
+	unsigned long rmi_ret;
+	gfp_t cache_alloc_flags;
+	int nbytes, cp_len;
+	struct cache_object **cache_objp, *cache_obj;
+	struct cca_host_pf0_dsc *pf0_dsc = to_cca_pf0_dsc(tsm->dsm_dev);
+	struct cca_host_comm_data *comm_data = to_cca_comm_data(tsm->pdev);
+	struct rmi_dev_comm_enter *io_enter = &comm_data->io_params->enter;
+	struct rmi_dev_comm_exit *io_exit = &comm_data->io_params->exit;
+
+redo_communicate:
+
+	if (type == PDEV_COMMUNICATE)
+		rmi_ret = rmi_pdev_communicate(virt_to_phys(pf0_dsc->rmm_pdev),
+					       virt_to_phys(comm_data->io_params));
+	else
+		rmi_ret = RMI_ERROR_INPUT;
+	if (rmi_ret != RMI_SUCCESS) {
+		if (rmi_ret == RMI_BUSY)
+			return -EBUSY;
+		return -EIO;
+	}
+
+	if (io_exit->flags & RMI_DEV_COMM_EXIT_CACHE_REQ ||
+	    io_exit->flags & RMI_DEV_COMM_EXIT_CACHE_RSP) {
+
+		switch (io_exit->cache_obj_id) {
+		case RMI_DEV_VCA:
+			cache_objp = &pf0_dsc->vca;
+			break;
+		case RMI_DEV_CERTIFICATE:
+			cache_objp = &pf0_dsc->cert_chain.cache;
+			break;
+		default:
+			return -EINVAL;
+		}
+		cache_obj = *cache_objp;
+		cache_alloc_flags = cache_obj_id_to_gfp_flags(io_exit->cache_obj_id);
+	}
+
+	if (io_exit->flags & RMI_DEV_COMM_EXIT_CACHE_REQ)
+		cp_len = io_exit->req_cache_len;
+	else
+		cp_len = io_exit->rsp_cache_len;
+
+	/* response and request len should be <= SZ_4k */
+	if (cp_len > CACHE_CHUNK_SIZE)
+		return -EINVAL;
+
+	if (io_exit->flags & RMI_DEV_COMM_EXIT_CACHE_REQ ||
+	    io_exit->flags & RMI_DEV_COMM_EXIT_CACHE_RSP) {
+		int cache_remaining;
+
+		/* new allocation */
+		if (!cache_obj) {
+			int obj_size = struct_size(cache_obj, buf,
+						   CACHE_CHUNK_SIZE);
+
+			cache_obj = kvmalloc(obj_size, cache_alloc_flags);
+			if (!cache_obj)
+				return -ENOMEM;
+
+			cache_obj->size = CACHE_CHUNK_SIZE;
+			cache_obj->offset = 0;
+			*cache_objp = cache_obj;
+		}
+
+		cache_remaining = cache_obj->size - cache_obj->offset;
+		if (cp_len > cache_remaining) {
+			struct cache_object *new_obj;
+			int new_size = struct_size(cache_obj, buf,
+						   cache_obj->size +
+						   CACHE_CHUNK_SIZE);
+
+			if (cache_obj->size + CACHE_CHUNK_SIZE > MAX_CACHE_OBJ_SIZE)
+				return -EINVAL;
+
+			new_obj = kvrealloc(cache_obj, new_size, cache_alloc_flags);
+			if (!new_obj)
+				return -ENOMEM;
+			new_obj->size = cache_obj->size + CACHE_CHUNK_SIZE;
+			*cache_objp = new_obj;
+		}
+
+		/* cache object can change above. */
+		cache_obj = *cache_objp;
+	}
+
+
+	if (io_exit->flags & RMI_DEV_COMM_EXIT_CACHE_REQ) {
+		memcpy(cache_obj->buf + cache_obj->offset,
+		       (comm_data->req_buff + io_exit->req_cache_offset), io_exit->req_cache_len);
+		cache_obj->offset += io_exit->req_cache_len;
+	}
+
+	if (io_exit->flags & RMI_DEV_COMM_EXIT_CACHE_RSP) {
+		memcpy(cache_obj->buf + cache_obj->offset,
+		       (comm_data->rsp_buff + io_exit->rsp_cache_offset), io_exit->rsp_cache_len);
+		cache_obj->offset += io_exit->rsp_cache_len;
+	}
+
+	/*
+	 * wait for last packet request from RMM.
+	 * We should not find this because our device communication is synchronous
+	 */
+	if (io_exit->flags & RMI_DEV_COMM_EXIT_WAIT)
+		return -EIO;
+
+	/* next packet to send */
+	if (io_exit->flags & RMI_DEV_COMM_EXIT_SEND) {
+		nbytes = doe_send_req_resp(tsm);
+		if (nbytes < 0) {
+			/* report error back to RMM */
+			io_enter->status = RMI_DEV_COMM_ERROR;
+		} else {
+			/* send response back to RMM */
+			io_enter->resp_len = nbytes;
+			io_enter->status = RMI_DEV_COMM_RESPONSE;
+		}
+	} else {
+		/* no data transmitted => no data received */
+		io_enter->resp_len = 0;
+		io_enter->status = RMI_DEV_COMM_NONE;
+	}
+
+	if (pending_dev_communicate(io_exit))
+		goto redo_communicate;
+
+	return 0;
+}
+
+static int do_dev_communicate(enum dev_comm_type type,
+				struct pci_tsm *tsm, unsigned long error_state)
+{
+	int ret, state = error_state;
+	struct rmi_dev_comm_enter *io_enter;
+	struct cca_host_pf0_dsc *pf0_dsc = to_cca_pf0_dsc(tsm->dsm_dev);
+
+	io_enter = &pf0_dsc->comm_data.io_params->enter;
+	io_enter->resp_len = 0;
+	io_enter->status = RMI_DEV_COMM_NONE;
+
+	ret = _do_dev_communicate(type, tsm);
+	if (ret) {
+		if (type == PDEV_COMMUNICATE)
+			rmi_pdev_abort(virt_to_phys(pf0_dsc->rmm_pdev));
+	} else {
+		/*
+		 * Some device communication error will transition the
+		 * device to error state. Report that.
+		 */
+		if (type == PDEV_COMMUNICATE) {
+			if (rmi_pdev_get_state(virt_to_phys(pf0_dsc->rmm_pdev),
+					       (enum rmi_pdev_state *)&state))
+				state = error_state;
+		}
+	}
+
+	if (state == error_state)
+		pci_err(tsm->pdev, "device communication error\n");
+
+	return state;
+}
+
+static int wait_for_dev_state(enum dev_comm_type type, struct pci_tsm *tsm,
+			      unsigned long target_state,
+			      unsigned long error_state)
+{
+	int state;
+
+	do {
+		state = do_dev_communicate(type, tsm, error_state);
+
+		if (state == target_state || state == error_state)
+			return state;
+	} while (1);
+
+	/* can't reach */
+	return error_state;
+}
+
+static int wait_for_pdev_state(struct pci_tsm *tsm, enum rmi_pdev_state target_state)
+{
+	return wait_for_dev_state(PDEV_COMMUNICATE, tsm, target_state, RMI_PDEV_ERROR);
+}
+
+static void pdev_state_transition_workfn(struct work_struct *work)
+{
+	unsigned long state;
+	struct pci_tsm *tsm;
+	struct dev_comm_work *setup_work;
+	struct cca_host_pf0_dsc *pf0_dsc;
+
+	setup_work = container_of(work, struct dev_comm_work, work);
+	tsm = setup_work->tsm;
+	pf0_dsc = to_cca_pf0_dsc(tsm->dsm_dev);
+
+	guard(mutex)(&pf0_dsc->object_lock);
+	state = wait_for_pdev_state(tsm, setup_work->target_state);
+	WARN_ON(state != setup_work->target_state);
+
+	complete(&setup_work->complete);
+}
+
+static int submit_pdev_state_transition_work(struct pci_dev *pdev, int target_state)
+{
+	enum rmi_pdev_state state;
+	struct dev_comm_work comm_work;
+	struct cca_host_pf0_dsc *pf0_dsc = to_cca_pf0_dsc(pdev);
+	struct cca_host_comm_data *comm_data = to_cca_comm_data(pdev);
+
+	INIT_WORK_ONSTACK(&comm_work.work, pdev_state_transition_workfn);
+	init_completion(&comm_work.complete);
+	comm_work.tsm = pdev->tsm;
+	comm_work.target_state = target_state;
+
+	queue_work(comm_data->work_queue, &comm_work.work);
+
+	wait_for_completion(&comm_work.complete);
+	destroy_work_on_stack(&comm_work.work);
+
+	/* check if we reached target state */
+	if (rmi_pdev_get_state(virt_to_phys(pf0_dsc->rmm_pdev), &state))
+		return -EIO;
+
+	if (state != target_state)
+		/* no specific error for this */
+		return -1;
+	return 0;
+}
+
+int cca_pdev_ide_setup(struct pci_dev *pdev)
+{
+	return submit_pdev_state_transition_work(pdev, RMI_PDEV_NEEDS_KEY);
+}
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index 229f3ff6dc6f..db4bf893f596 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -9,29 +9,79 @@
 #include <linux/pci.h>
 #include <linux/pci-ide.h>
 #include <linux/pci-tsm.h>
+#include <linux/sizes.h>
 #include <asm/rmi_smc.h>
 
+#define MAX_CACHE_OBJ_SIZE	SZ_16M
+#define CACHE_CHUNK_SIZE	SZ_4K
+struct cache_object {
+	int size;
+	int offset;
+	u8 buf[] __counted_by(size);
+};
+
+struct dev_comm_work {
+	struct pci_tsm *tsm;
+	int target_state;
+	struct work_struct work;
+	struct completion complete;
+};
+
+struct cca_host_comm_data {
+	void *rsp_buff;
+	void *req_buff;
+	struct rmi_dev_comm_data *io_params;
+	/*
+	 * Only one device communication request can be active at
+	 * a time. This limitation comes from using the DOE mailbox
+	 * at the pdev level. Requests such as get_measurements may
+	 * span multiple mailbox messages, which must not be
+	 * interleaved with other SPDM requests.
+	 */
+	struct workqueue_struct *work_queue;
+};
+
 /**
  * struct cca_host_pf0_dsc - Device Security Context for physical function 0.
+ * @comm_data: Device communication context
  * @pci: Physical Function 0 TDISP link context
  * @sel_stream: Selective IDE Stream descriptor
  * @rmm_pdev: Delegated granule address of rmm pdev object
  * @num_ax: Number of auxiliary granules allocated for pdev
  * @aux: Delegated auxiliary granules
+ * @object_lock: lock used to protect access to cached obects in PF0 and TDIs
+ * @cert_chain: cetrificate chain
+ * @vca: SPDM's Version-Capabilities-Algorithms cache object
  */
 struct cca_host_pf0_dsc {
+	struct cca_host_comm_data comm_data;
 	struct pci_tsm_pf0 pci;
 	struct pci_ide *sel_stream;
 
 	void *rmm_pdev;
 	int num_aux;
 	void *aux[MAX_PDEV_AUX_GRANULES];
+
+	struct mutex object_lock;
+	struct {
+		struct cache_object *cache;
+
+		void *public_key;
+		size_t public_key_size;
+
+		bool valid;
+	} cert_chain;
+	struct cache_object *vca;
 };
 
 struct cca_host_fn_dsc {
 	struct pci_tsm pci;
 };
 
+enum dev_comm_type {
+	PDEV_COMMUNICATE = 0x1,
+};
+
 static inline struct cca_host_pf0_dsc *to_cca_pf0_dsc(struct pci_dev *pdev)
 {
 	struct pci_tsm *tsm = pdev->tsm;
@@ -49,5 +99,21 @@ static inline struct cca_host_fn_dsc *to_cca_fn_dsc(struct pci_dev *pdev)
 	return container_of(tsm, struct cca_host_fn_dsc, pci);
 }
 
+static inline struct cca_host_comm_data *to_cca_comm_data(struct pci_dev *pdev)
+{
+	struct cca_host_pf0_dsc *pf0_dsc;
+
+	pf0_dsc = to_cca_pf0_dsc(pdev);
+	if (pf0_dsc)
+		return &pf0_dsc->comm_data;
+
+	pf0_dsc = to_cca_pf0_dsc(pdev->tsm->dsm_dev);
+	if (pf0_dsc)
+		return &pf0_dsc->comm_data;
+
+	return NULL;
+}
+
 int cca_pdev_create(struct pci_dev *pdev);
+int cca_pdev_ide_setup(struct pci_dev *pdev);
 #endif
-- 
2.43.0


^ permalink raw reply related

* [RFC PATCH v3 03/10] coco: host: arm64: Build and register RMM pdev descriptors
From: Aneesh Kumar K.V (Arm) @ 2026-03-12  8:01 UTC (permalink / raw)
  To: linux-coco, kvmarm, linux-arm-kernel
  Cc: linux-kernel, Aneesh Kumar K.V (Arm), Marc Zyngier,
	Catalin Marinas, Will Deacon, Jonathan Cameron, Jason Gunthorpe,
	Dan Williams, Alexey Kardashevskiy, Samuel Ortiz, Xu Yilun,
	Suzuki K Poulose, Steven Price
In-Reply-To: <20260312080129.3483585-1-aneesh.kumar@kernel.org>

Add the SMCCC plumbing for RMI_PDEV_AUX_COUNT, RMI_PDEV_CREATE, and
RMI_PDEV_GET_STATE, describe the pdev state enum/flags in rmi_smc.h,
and extend the PF0 descriptor so we can hold the RMM-side pdev handle
plus its auxiliary granules.

Implement pdev_create() to delegate backing pages, populate the pdev
parameters from the device's RID, ECAM window, IDE stream, and
non-coherent address ranges, and invoke RMI_PDEV_CREATE. The helper
keeps track of the allocated/assigned granules and unwinds them on
failure, so the host driver can reliably establish the pdev channel
before kicking off further IDE/TSM setup.

Cc: Marc Zyngier <maz@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Samuel Ortiz <sameo@rivosinc.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: Suzuki K Poulose <Suzuki.Poulose@arm.com>
Cc: Steven Price <steven.price@arm.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 arch/arm64/include/asm/rmi_cmds.h       |  31 +++++
 arch/arm64/include/asm/rmi_smc.h        |  95 ++++++++++++++-
 drivers/virt/coco/arm-cca-host/Makefile |   2 +-
 drivers/virt/coco/arm-cca-host/rmi-da.c | 156 ++++++++++++++++++++++++
 drivers/virt/coco/arm-cca-host/rmi-da.h |   8 ++
 5 files changed, 290 insertions(+), 2 deletions(-)
 create mode 100644 drivers/virt/coco/arm-cca-host/rmi-da.c

diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index ef53147c1984..4547ce0901a6 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -505,4 +505,35 @@ static inline int rmi_rtt_unmap_unprotected(unsigned long rd,
 	return res.a0;
 }
 
+static inline unsigned long rmi_pdev_aux_count(unsigned long flags, u64 *aux_count)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_1_1_invoke(SMC_RMI_PDEV_AUX_COUNT, flags, &res);
+
+	*aux_count = res.a1;
+	return res.a0;
+}
+
+static inline unsigned long rmi_pdev_create(unsigned long pdev_phys,
+					    unsigned long pdev_params_phys)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_1_1_invoke(SMC_RMI_PDEV_CREATE,
+			     pdev_phys, pdev_params_phys, &res);
+
+	return res.a0;
+}
+
+static inline unsigned long rmi_pdev_get_state(unsigned long pdev_phys, enum rmi_pdev_state *state)
+{
+	struct arm_smccc_res res;
+
+	arm_smccc_1_1_invoke(SMC_RMI_PDEV_GET_STATE, pdev_phys, &res);
+
+	*state = res.a1;
+	return res.a0;
+}
+
 #endif /* __ASM_RMI_CMDS_H */
diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
index 9f8b6aa5ea06..58106740c1f7 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -26,7 +26,7 @@
 #define SMC_RMI_DATA_CREATE		SMC_RMI_CALL(0x0153)
 #define SMC_RMI_DATA_CREATE_UNKNOWN	SMC_RMI_CALL(0x0154)
 #define SMC_RMI_DATA_DESTROY		SMC_RMI_CALL(0x0155)
-
+#define SMC_RMI_PDEV_AUX_COUNT		SMC_RMI_CALL(0x0156)
 #define SMC_RMI_REALM_ACTIVATE		SMC_RMI_CALL(0x0157)
 #define SMC_RMI_REALM_CREATE		SMC_RMI_CALL(0x0158)
 #define SMC_RMI_REALM_DESTROY		SMC_RMI_CALL(0x0159)
@@ -47,6 +47,9 @@
 #define SMC_RMI_RTT_INIT_RIPAS		SMC_RMI_CALL(0x0168)
 #define SMC_RMI_RTT_SET_RIPAS		SMC_RMI_CALL(0x0169)
 
+#define SMC_RMI_PDEV_CREATE             SMC_RMI_CALL(0x0176)
+#define SMC_RMI_PDEV_GET_STATE		SMC_RMI_CALL(0x0178)
+
 #define RMI_ABI_MAJOR_VERSION	1
 #define RMI_ABI_MINOR_VERSION	0
 
@@ -269,4 +272,94 @@ struct rec_run {
 	struct rec_exit exit;
 };
 
+enum rmi_pdev_state {
+	RMI_PDEV_NEW,
+	RMI_PDEV_NEEDS_KEY,
+	RMI_PDEV_HAS_KEY,
+	RMI_PDEV_READY,
+	RMI_PDEV_IDE_RESETTING,
+	RMI_PDEV_COMMUNICATING,
+	RMI_PDEV_STOPPING,
+	RMI_PDEV_STOPPED,
+	RMI_PDEV_ERROR,
+};
+
+#define MAX_PDEV_AUX_GRANULES	32
+#define MAX_IOCOH_ADDR_RANGE	16
+#define MAX_FCOH_ADDR_RANGE	4
+
+#define RMI_PDEV_FLAGS_SPDM		BIT(0)
+#define RMI_PDEV_FLAGS_NCOH_IDE		BIT(1)
+#define RMI_PDEV_FLAGS_NCOH_ADDR	BIT(2)
+#define RMI_PDEV_FLAGS_COH_IDE		BIT(3)
+#define RMI_PDEV_FLAGS_COH_ADDR		BIT(4)
+#define RMI_PDEV_FLAGS_P2P		BIT(5)
+#define RMI_PDEV_FLAGS_COMP_TRUST	BIT(6)
+#define RMI_PDEV_FLAGS_CATEGORY_MASK	GENMASK(8, 7)
+#define RMI_PDEV_FLAGS_CATEGORY_SHIFT	7
+
+#define RMI_PDEV_FLAGS_CATEGORY_CMEM_CXL	0x1
+
+#define RMI_HASH_SHA_256	0
+#define RMI_HASH_SHA_512	1
+
+struct rmi_pdev_addr_range {
+	u64 base;
+	u64 top;
+};
+
+struct rmi_pdev_params {
+	union {
+		struct {
+			u64 flags;
+			u64 pdev_id;
+			union {
+				u8 segment_id;
+				u64 padding0;
+			};
+			u64 ecam_addr;
+			union {
+				u16 root_id;
+				u64 padding1;
+			};
+			u64 cert_id;
+			union {
+				u16 rid_base;
+				u64 padding2;
+			};
+			union {
+				u16 rid_top;
+				u64 padding3;
+			};
+			union {
+				u8 hash_algo;
+				u64 padding4;
+			};
+			u64 num_aux;
+			u64 ncoh_ide_sid;
+			u64 ncoh_num_addr_range;
+			u64 coh_num_addr_range;
+		};
+		u8 padding5[0x100];
+	};
+
+	union { /* 0x100 */
+		u64 aux_granule[MAX_PDEV_AUX_GRANULES];
+		u8 padding6[0x100];
+	};
+
+	union { /* 0x200 */
+		struct {
+			struct rmi_pdev_addr_range ncoh_addr_range[MAX_IOCOH_ADDR_RANGE];
+		};
+		u8 padding7[0x100];
+	};
+	union { /* 0x300 */
+		struct {
+			struct rmi_pdev_addr_range coh_addr_range[MAX_FCOH_ADDR_RANGE];
+		};
+		u8 padding8[0x100];
+	};
+};
+
 #endif /* __ASM_RMI_SMC_H */
diff --git a/drivers/virt/coco/arm-cca-host/Makefile b/drivers/virt/coco/arm-cca-host/Makefile
index c236827f002c..d48e8940af46 100644
--- a/drivers/virt/coco/arm-cca-host/Makefile
+++ b/drivers/virt/coco/arm-cca-host/Makefile
@@ -2,4 +2,4 @@
 #
 obj-$(CONFIG_ARM_CCA_HOST) += arm-cca-host.o
 
-arm-cca-host-y	+=  arm-cca.o
+arm-cca-host-y	+=  arm-cca.o rmi-da.o
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
new file mode 100644
index 000000000000..89b61ad5bc00
--- /dev/null
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/pci.h>
+#include <linux/pci-ecam.h>
+#include <asm/rmi_cmds.h>
+
+#include "rmi-da.h"
+
+static int pci_dev_addr_range(struct pci_dev *pdev,
+			      struct rmi_pdev_addr_range *pdev_addr,
+			      struct pci_ide_partner *partner)
+{
+	int naddr = 0;
+	struct pci_dev *br;
+	struct resource *mem, *pref;
+
+	br = pci_upstream_bridge(pdev);
+	if (!br)
+		return 0;
+
+	mem = pci_resource_n(br, PCI_BRIDGE_MEM_WINDOW);
+	pref = pci_resource_n(br, PCI_BRIDGE_PREF_MEM_WINDOW);
+	if (resource_assigned(mem)) {
+		pdev_addr[naddr].base = mem->start;
+		pdev_addr[naddr].top  = mem->end + 1;
+		naddr++;
+	}
+	if (resource_assigned(pref)) {
+		pdev_addr[naddr].base = pref->start;
+		pdev_addr[naddr].top  = pref->end + 1;
+		naddr++;
+	}
+	return naddr;
+}
+
+static void free_aux_pages(int cnt, void *aux[])
+{
+	int ret;
+
+	while (cnt--) {
+		ret = rmi_granule_undelegate(virt_to_phys(aux[cnt]));
+		if (!ret)
+			free_page((unsigned long)aux[cnt]);
+	}
+}
+
+static int init_pdev_params(struct pci_dev *pdev, struct rmi_pdev_params *params)
+{
+	int rid, ret, i;
+	phys_addr_t aux_phys;
+	struct pci_config_window *cfg = pdev->bus->sysdata;
+	struct cca_host_pf0_dsc *pf0_dsc = to_cca_pf0_dsc(pdev);
+	struct pci_ide *ide = pf0_dsc->sel_stream;
+
+	/* assign the ep device with RMM */
+	rid = pci_dev_id(pdev);
+	params->pdev_id = rid;
+	/* slot number for certificate chain */
+	params->cert_id = 0;
+	/* io coherent spdm/ide and non p2p */
+	params->flags = RMI_PDEV_FLAGS_SPDM | RMI_PDEV_FLAGS_NCOH_IDE |
+			RMI_PDEV_FLAGS_NCOH_ADDR;
+	params->ncoh_ide_sid = ide->stream_id;
+	params->hash_algo = RMI_HASH_SHA_256;
+	/* use the rid and MMIO resources of the end point pdev */
+	params->rid_base = rid;
+	params->rid_top = params->rid_base + 1;
+	params->ecam_addr = cfg->res.start;
+	params->root_id = pci_dev_id(pcie_find_root_port(pdev));
+
+	params->ncoh_num_addr_range = pci_dev_addr_range(pdev,
+						params->ncoh_addr_range,
+						&ide->partner[PCI_IDE_RP]);
+
+	ret = rmi_pdev_aux_count(params->flags, &params->num_aux);
+	if (ret)
+		return -EIO;
+
+	pf0_dsc->num_aux = params->num_aux;
+	for (i = 0; i < params->num_aux; i++) {
+		void *aux = (void *)__get_free_page(GFP_KERNEL);
+
+		if (!aux) {
+			ret = -ENOMEM;
+			goto err_free_aux;
+		}
+
+		aux_phys = virt_to_phys(aux);
+		if (rmi_granule_delegate(aux_phys)) {
+			ret = -EIO;
+			free_page((unsigned long)aux);
+			goto err_free_aux;
+		}
+		params->aux_granule[i] = aux_phys;
+		pf0_dsc->aux[i] = aux;
+	}
+	return 0;
+
+err_free_aux:
+	free_aux_pages(i, pf0_dsc->aux);
+	return ret;
+}
+
+int cca_pdev_create(struct pci_dev *pci_dev)
+{
+	int ret;
+	void *rmm_pdev;
+	bool should_free = true;
+	phys_addr_t rmm_pdev_phys;
+	struct rmi_pdev_params *params;
+	struct cca_host_pf0_dsc *pf0_dsc = to_cca_pf0_dsc(pci_dev);
+
+	rmm_pdev = (void *)get_zeroed_page(GFP_KERNEL);
+	if (!rmm_pdev)
+		return -ENOMEM;
+
+	rmm_pdev_phys = virt_to_phys(rmm_pdev);
+	if (rmi_granule_delegate(rmm_pdev_phys)) {
+		ret = -EIO;
+		goto err_granule_delegate;
+	}
+
+	params = (struct rmi_pdev_params *)get_zeroed_page(GFP_KERNEL);
+	if (!params) {
+		ret = -ENOMEM;
+		goto err_param_alloc;
+	}
+
+	ret = init_pdev_params(pci_dev, params);
+	if (ret)
+		goto err_init_pdev_params;
+
+	if (rmi_pdev_create(rmm_pdev_phys, virt_to_phys(params))) {
+		ret = -EIO;
+		goto err_pdev_create;
+	}
+
+	pf0_dsc->rmm_pdev = rmm_pdev;
+	free_page((unsigned long)params);
+	return 0;
+
+err_pdev_create:
+	free_aux_pages(pf0_dsc->num_aux, pf0_dsc->aux);
+err_init_pdev_params:
+	free_page((unsigned long)params);
+err_param_alloc:
+	if (rmi_granule_undelegate(rmm_pdev_phys))
+		should_free = false;
+err_granule_delegate:
+	if (should_free)
+		free_page((unsigned long)rmm_pdev);
+	return ret;
+}
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index a23955f84e4f..229f3ff6dc6f 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -15,10 +15,17 @@
  * struct cca_host_pf0_dsc - Device Security Context for physical function 0.
  * @pci: Physical Function 0 TDISP link context
  * @sel_stream: Selective IDE Stream descriptor
+ * @rmm_pdev: Delegated granule address of rmm pdev object
+ * @num_ax: Number of auxiliary granules allocated for pdev
+ * @aux: Delegated auxiliary granules
  */
 struct cca_host_pf0_dsc {
 	struct pci_tsm_pf0 pci;
 	struct pci_ide *sel_stream;
+
+	void *rmm_pdev;
+	int num_aux;
+	void *aux[MAX_PDEV_AUX_GRANULES];
 };
 
 struct cca_host_fn_dsc {
@@ -42,4 +49,5 @@ static inline struct cca_host_fn_dsc *to_cca_fn_dsc(struct pci_dev *pdev)
 	return container_of(tsm, struct cca_host_fn_dsc, pci);
 }
 
+int cca_pdev_create(struct pci_dev *pdev);
 #endif
-- 
2.43.0


^ permalink raw reply related

* [RFC PATCH v3 02/10] coco: host: arm64: Add host TSM callback and IDE stream allocation support
From: Aneesh Kumar K.V (Arm) @ 2026-03-12  8:01 UTC (permalink / raw)
  To: linux-coco, kvmarm, linux-arm-kernel
  Cc: linux-kernel, Aneesh Kumar K.V (Arm), Marc Zyngier,
	Catalin Marinas, Will Deacon, Jonathan Cameron, Jason Gunthorpe,
	Dan Williams, Alexey Kardashevskiy, Samuel Ortiz, Xu Yilun,
	Suzuki K Poulose, Steven Price
In-Reply-To: <20260312080129.3483585-1-aneesh.kumar@kernel.org>

Register the TSM callback when the DA feature is supported by KVM.

This driver handles IDE stream setup for both the root port and PCIe
endpoints. Root port IDE stream enablement itself is managed by RMM.

In addition, the driver registers `pci_tsm_ops` with the TSM subsystem.

Cc: Marc Zyngier <maz@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Samuel Ortiz <sameo@rivosinc.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: Suzuki K Poulose <Suzuki.Poulose@arm.com>
Cc: Steven Price <steven.price@arm.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 arch/arm64/include/asm/rmi_smc.h         |   2 +
 drivers/firmware/smccc/rmm.c             |  12 ++
 drivers/firmware/smccc/rmm.h             |   8 +
 drivers/firmware/smccc/smccc.c           |   1 +
 drivers/virt/coco/Kconfig                |   2 +
 drivers/virt/coco/Makefile               |   1 +
 drivers/virt/coco/arm-cca-host/Kconfig   |  19 +++
 drivers/virt/coco/arm-cca-host/Makefile  |   5 +
 drivers/virt/coco/arm-cca-host/arm-cca.c | 205 +++++++++++++++++++++++
 drivers/virt/coco/arm-cca-host/rmi-da.h  |  45 +++++
 10 files changed, 300 insertions(+)
 create mode 100644 drivers/virt/coco/arm-cca-host/Kconfig
 create mode 100644 drivers/virt/coco/arm-cca-host/Makefile
 create mode 100644 drivers/virt/coco/arm-cca-host/arm-cca.c
 create mode 100644 drivers/virt/coco/arm-cca-host/rmi-da.h

diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
index f6f786bfb9c9..9f8b6aa5ea06 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -12,6 +12,8 @@
 
 #include <linux/arm-smccc.h>
 
+#define RMI_DEV_NAME "arm-rmi-dev"
+
 #define SMC_RMI_CALL(func)				\
 	ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL,		\
 			   ARM_SMCCC_SMC_64,		\
diff --git a/drivers/firmware/smccc/rmm.c b/drivers/firmware/smccc/rmm.c
index 03496330630f..7435996dcb90 100644
--- a/drivers/firmware/smccc/rmm.c
+++ b/drivers/firmware/smccc/rmm.c
@@ -23,3 +23,15 @@ void __init register_rsi_device(struct platform_device *pdev)
 	__devm_auxiliary_device_create(&pdev->dev,
 				       "arm_cca_guest", RSI_DEV_NAME, NULL, 0);
 }
+
+void __init register_rmi_device(struct platform_device *pdev)
+{
+	struct arm_smccc_res res;
+	unsigned long host_version = RMI_ABI_VERSION(RMI_ABI_MAJOR_VERSION,
+						     RMI_ABI_MINOR_VERSION);
+
+	arm_smccc_1_1_invoke(SMC_RMI_VERSION, host_version, &res);
+	if (res.a0 == RMI_SUCCESS)
+		__devm_auxiliary_device_create(&pdev->dev,
+					"arm_cca_host", RMI_DEV_NAME, NULL, 0);
+}
diff --git a/drivers/firmware/smccc/rmm.h b/drivers/firmware/smccc/rmm.h
index a47a650d4f51..37d0d95a099e 100644
--- a/drivers/firmware/smccc/rmm.h
+++ b/drivers/firmware/smccc/rmm.h
@@ -6,12 +6,20 @@
 
 #ifdef CONFIG_ARM64
 #include <asm/rsi_cmds.h>
+#include <asm/rmi_smc.h>
+
 void __init register_rsi_device(struct platform_device *pdev);
+void __init register_rmi_device(struct platform_device *pdev);
 #else
 
 static void __init register_rsi_device(struct platform_device *pdev)
 {
 
+}
+
+static void __init register_rmi_device(struct platform_device *pdev)
+{
+
 }
 #endif
 #endif
diff --git a/drivers/firmware/smccc/smccc.c b/drivers/firmware/smccc/smccc.c
index fc9b44b7c687..2bf2d59e686d 100644
--- a/drivers/firmware/smccc/smccc.c
+++ b/drivers/firmware/smccc/smccc.c
@@ -97,6 +97,7 @@ static int __init smccc_devices_init(void)
 		 * the required SMCCC function IDs at a supported revision.
 		 */
 		register_rsi_device(pdev);
+		register_rmi_device(pdev);
 	}
 
 	if (smccc_trng_available) {
diff --git a/drivers/virt/coco/Kconfig b/drivers/virt/coco/Kconfig
index f7691f64fbe3..1cbc2134f9ea 100644
--- a/drivers/virt/coco/Kconfig
+++ b/drivers/virt/coco/Kconfig
@@ -19,5 +19,7 @@ endif
 
 source "drivers/virt/coco/tdx-host/Kconfig"
 
+source "drivers/virt/coco/arm-cca-host/Kconfig"
+
 config TSM
 	bool
diff --git a/drivers/virt/coco/Makefile b/drivers/virt/coco/Makefile
index b323b0ae4f82..f2310c34daf9 100644
--- a/drivers/virt/coco/Makefile
+++ b/drivers/virt/coco/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_INTEL_TDX_HOST)	+= tdx-host/
 obj-$(CONFIG_ARM_CCA_GUEST)	+= arm-cca-guest/
 obj-$(CONFIG_TSM) 		+= tsm-core.o
 obj-$(CONFIG_TSM_GUEST)		+= guest/
+obj-$(CONFIG_ARM_CCA_HOST)	+= arm-cca-host/
diff --git a/drivers/virt/coco/arm-cca-host/Kconfig b/drivers/virt/coco/arm-cca-host/Kconfig
new file mode 100644
index 000000000000..efe40d61d5d8
--- /dev/null
+++ b/drivers/virt/coco/arm-cca-host/Kconfig
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# TSM (TEE Security Manager) host drivers
+#
+config ARM_CCA_HOST
+	tristate "Arm CCA Host driver"
+	depends on ARM64
+	depends on PCI
+	depends on KVM
+	select PCI_TSM
+	select AUXILIARY_BUS
+
+	help
+	  ARM CCA RMM firmware is the trusted runtime that enforces memory
+	  isolation and security for confidential computing on ARM. This driver
+	  provides the interface for communicating with RMM to support secure
+	  device assignment.
+
+	  If you choose 'M' here, this module will be called arm-cca-host.
diff --git a/drivers/virt/coco/arm-cca-host/Makefile b/drivers/virt/coco/arm-cca-host/Makefile
new file mode 100644
index 000000000000..c236827f002c
--- /dev/null
+++ b/drivers/virt/coco/arm-cca-host/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+obj-$(CONFIG_ARM_CCA_HOST) += arm-cca-host.o
+
+arm-cca-host-y	+=  arm-cca.o
diff --git a/drivers/virt/coco/arm-cca-host/arm-cca.c b/drivers/virt/coco/arm-cca-host/arm-cca.c
new file mode 100644
index 000000000000..639ebd82978a
--- /dev/null
+++ b/drivers/virt/coco/arm-cca-host/arm-cca.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/pci-tsm.h>
+#include <linux/pci-ide.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/tsm.h>
+#include <linux/vmalloc.h>
+#include <linux/cleanup.h>
+#include <linux/kvm_host.h>
+
+#include "rmi-da.h"
+
+/* Total number of stream id supported at root port level */
+#define MAX_STREAM_ID	256
+
+static struct pci_tsm *cca_tsm_pci_probe(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
+{
+	int rc;
+
+	if (!is_pci_tsm_pf0(pdev)) {
+		struct cca_host_fn_dsc *fn_dsc __free(kfree) =
+			kzalloc(sizeof(*fn_dsc), GFP_KERNEL);
+
+		if (!fn_dsc)
+			return NULL;
+
+		rc = pci_tsm_link_constructor(pdev, &fn_dsc->pci, tsm_dev);
+		if (rc)
+			return NULL;
+
+		return &no_free_ptr(fn_dsc)->pci;
+	}
+
+	if (!pdev->ide_cap)
+		return NULL;
+
+	struct cca_host_pf0_dsc *pf0_dsc __free(kfree) =
+					kzalloc(sizeof(*pf0_dsc), GFP_KERNEL);
+	if (!pf0_dsc)
+		return NULL;
+
+	rc = pci_tsm_pf0_constructor(pdev, &pf0_dsc->pci, tsm_dev);
+	if (rc)
+		return NULL;
+
+	pci_dbg(pdev, "tsm enabled\n");
+	return &no_free_ptr(pf0_dsc)->pci.base_tsm;
+}
+
+static void cca_tsm_pci_remove(struct pci_tsm *tsm)
+{
+	struct pci_dev *pdev = tsm->pdev;
+
+	if (is_pci_tsm_pf0(pdev)) {
+		struct cca_host_pf0_dsc *pf0_dsc = to_cca_pf0_dsc(pdev);
+
+		pci_tsm_pf0_destructor(&pf0_dsc->pci);
+		kfree(pf0_dsc);
+	} else {
+		kfree(to_cca_fn_dsc(pdev));
+	}
+}
+
+/* For now global for simplicity. Protected by pci_tsm_rwsem */
+static DECLARE_BITMAP(cca_stream_ids, MAX_STREAM_ID);
+static int alloc_stream_id(struct pci_host_bridge *hb)
+{
+	int stream_id;
+
+redo_alloc:
+	stream_id = find_first_zero_bit(cca_stream_ids, MAX_STREAM_ID);
+	if (stream_id == MAX_STREAM_ID)
+		return stream_id;
+
+	if (ida_exists(&hb->ide_stream_ids_ida, stream_id)) {
+		/* mark the stream allocated in the global bitmap. */
+		set_bit(stream_id, cca_stream_ids);
+		goto redo_alloc;
+	}
+	return stream_id;
+}
+
+static int cca_tsm_connect(struct pci_dev *pdev)
+{
+	struct pci_dev *rp = pcie_find_root_port(pdev);
+	struct cca_host_pf0_dsc *pf0_dsc;
+	struct pci_ide *ide;
+	int rc, stream_id;
+
+	/* Only function 0 supports connect in host */
+	if (WARN_ON(!is_pci_tsm_pf0(pdev)))
+		return -EIO;
+
+	pf0_dsc = to_cca_pf0_dsc(pdev);
+	/* Allocate stream id */
+	stream_id = alloc_stream_id(pci_find_host_bridge(pdev->bus));
+	if (stream_id == MAX_STREAM_ID)
+		return -EBUSY;
+	set_bit(stream_id, cca_stream_ids);
+
+	ide = pci_ide_stream_alloc(pdev);
+	if (!ide) {
+		rc = -ENOMEM;
+		goto err_stream_alloc;
+	}
+
+	pf0_dsc->sel_stream = ide;
+	ide->stream_id = stream_id;
+	rc = pci_ide_stream_register(ide);
+	if (rc)
+		goto err_stream;
+
+	pci_ide_stream_setup(pdev, ide);
+	pci_ide_stream_setup(rp, ide);
+
+	rc = tsm_ide_stream_register(ide);
+	if (rc)
+		goto err_tsm;
+
+	/*
+	 * Once ide is setup, enable the stream at the endpoint
+	 * Root port will be done by RMM
+	 */
+	pci_ide_stream_enable(pdev, ide);
+	return 0;
+
+err_tsm:
+	pci_ide_stream_teardown(rp, ide);
+	pci_ide_stream_teardown(pdev, ide);
+	pci_ide_stream_unregister(ide);
+err_stream:
+	pci_ide_stream_free(ide);
+	pf0_dsc->sel_stream = NULL;
+err_stream_alloc:
+	clear_bit(stream_id, cca_stream_ids);
+
+	return rc;
+}
+
+static void cca_tsm_disconnect(struct pci_dev *pdev)
+{
+	int stream_id;
+	struct pci_ide *ide;
+	struct cca_host_pf0_dsc *pf0_dsc;
+
+	pf0_dsc = to_cca_pf0_dsc(pdev);
+	if (!pf0_dsc)
+		return;
+
+	ide = pf0_dsc->sel_stream;
+	stream_id = ide->stream_id;
+
+	pci_ide_stream_release(ide);
+	pf0_dsc->sel_stream = NULL;
+	clear_bit(stream_id, cca_stream_ids);
+}
+
+static struct pci_tsm_ops cca_link_pci_ops = {
+	.probe = cca_tsm_pci_probe,
+	.remove = cca_tsm_pci_remove,
+	.connect = cca_tsm_connect,
+	.disconnect = cca_tsm_disconnect,
+};
+
+static void cca_link_tsm_remove(void *tsm_dev)
+{
+	tsm_unregister(tsm_dev);
+}
+
+static int cca_link_tsm_probe(struct auxiliary_device *adev,
+			      const struct auxiliary_device_id *id)
+{
+	struct tsm_dev *tsm_dev;
+
+	if (!kvm_has_da_feature())
+		return -ENODEV;
+
+	tsm_dev = tsm_register(&adev->dev, &cca_link_pci_ops);
+	if (IS_ERR(tsm_dev))
+		return PTR_ERR(tsm_dev);
+
+	return devm_add_action_or_reset(&adev->dev, cca_link_tsm_remove,
+					tsm_dev);
+}
+
+static const struct auxiliary_device_id cca_link_tsm_id_table[] = {
+	{ .name =  KBUILD_MODNAME "." RMI_DEV_NAME },
+	{}
+};
+MODULE_DEVICE_TABLE(auxiliary, cca_link_tsm_id_table);
+
+static struct auxiliary_driver cca_link_tsm_driver = {
+	.probe = cca_link_tsm_probe,
+	.id_table = cca_link_tsm_id_table,
+};
+module_auxiliary_driver(cca_link_tsm_driver);
+MODULE_IMPORT_NS("PCI_IDE");
+MODULE_AUTHOR("Aneesh Kumar <aneesh.kumar@kernel.org>");
+MODULE_DESCRIPTION("ARM CCA Host TSM driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
new file mode 100644
index 000000000000..a23955f84e4f
--- /dev/null
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#ifndef _VIRT_COCO_RMM_DA_H_
+#define _VIRT_COCO_RMM_DA_H_
+
+#include <linux/pci.h>
+#include <linux/pci-ide.h>
+#include <linux/pci-tsm.h>
+#include <asm/rmi_smc.h>
+
+/**
+ * struct cca_host_pf0_dsc - Device Security Context for physical function 0.
+ * @pci: Physical Function 0 TDISP link context
+ * @sel_stream: Selective IDE Stream descriptor
+ */
+struct cca_host_pf0_dsc {
+	struct pci_tsm_pf0 pci;
+	struct pci_ide *sel_stream;
+};
+
+struct cca_host_fn_dsc {
+	struct pci_tsm pci;
+};
+
+static inline struct cca_host_pf0_dsc *to_cca_pf0_dsc(struct pci_dev *pdev)
+{
+	struct pci_tsm *tsm = pdev->tsm;
+
+	if (!tsm || !is_pci_tsm_pf0(pdev))
+		return NULL;
+
+	return container_of(tsm, struct cca_host_pf0_dsc, pci.base_tsm);
+}
+
+static inline struct cca_host_fn_dsc *to_cca_fn_dsc(struct pci_dev *pdev)
+{
+	struct pci_tsm *tsm = pdev->tsm;
+
+	return container_of(tsm, struct cca_host_fn_dsc, pci);
+}
+
+#endif
-- 
2.43.0


^ permalink raw reply related

* [RFC PATCH v3 01/10] KVM: arm64: RMI: Add and export kvm_has_da_feature helper
From: Aneesh Kumar K.V (Arm) @ 2026-03-12  8:01 UTC (permalink / raw)
  To: linux-coco, kvmarm, linux-arm-kernel
  Cc: linux-kernel, Aneesh Kumar K.V (Arm), Marc Zyngier,
	Catalin Marinas, Will Deacon, Jonathan Cameron, Jason Gunthorpe,
	Dan Williams, Alexey Kardashevskiy, Samuel Ortiz, Xu Yilun,
	Suzuki K Poulose, Steven Price
In-Reply-To: <20260312080129.3483585-1-aneesh.kumar@kernel.org>

Add kvm_has_da_feature() helper for use in later patches.
Update reserved fields based on Alp17 spec.

Cc: Marc Zyngier <maz@kernel.org>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Samuel Ortiz <sameo@rivosinc.com>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Cc: Suzuki K Poulose <Suzuki.Poulose@arm.com>
Cc: Steven Price <steven.price@arm.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
 arch/arm64/include/asm/kvm_rmi.h | 1 +
 arch/arm64/include/asm/rmi_smc.h | 3 ++-
 arch/arm64/kvm/rmi.c             | 6 ++++++
 3 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/include/asm/kvm_rmi.h b/arch/arm64/include/asm/kvm_rmi.h
index 1b2cdaac6c50..a967061af6ed 100644
--- a/arch/arm64/include/asm/kvm_rmi.h
+++ b/arch/arm64/include/asm/kvm_rmi.h
@@ -90,6 +90,7 @@ u32 kvm_realm_ipa_limit(void);
 u32 kvm_realm_vgic_nr_lr(void);
 u8 kvm_realm_max_pmu_counters(void);
 unsigned int kvm_realm_sve_max_vl(void);
+bool kvm_has_da_feature(void);
 
 u64 kvm_realm_reset_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val);
 
diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
index 1000368f1bca..f6f786bfb9c9 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -86,7 +86,8 @@ enum rmi_ripas {
 #define RMI_FEATURE_REGISTER_0_HASH_SHA_512	BIT(33)
 #define RMI_FEATURE_REGISTER_0_GICV3_NUM_LRS	GENMASK(37, 34)
 #define RMI_FEATURE_REGISTER_0_MAX_RECS_ORDER	GENMASK(41, 38)
-#define RMI_FEATURE_REGISTER_0_Reserved		GENMASK(63, 42)
+#define RMI_FEATURE_REGISTER_0_DA		BIT(42)
+#define RMI_FEATURE_REGISTER_0_Reserved		GENMASK(63, 61)
 
 #define RMI_REALM_PARAM_FLAG_LPA2		BIT(0)
 #define RMI_REALM_PARAM_FLAG_SVE		BIT(1)
diff --git a/arch/arm64/kvm/rmi.c b/arch/arm64/kvm/rmi.c
index 478a73e0b35a..08f3d2362dfd 100644
--- a/arch/arm64/kvm/rmi.c
+++ b/arch/arm64/kvm/rmi.c
@@ -1738,6 +1738,12 @@ int kvm_init_realm_vm(struct kvm *kvm)
 	return 0;
 }
 
+bool kvm_has_da_feature(void)
+{
+	return rmi_has_feature(RMI_FEATURE_REGISTER_0_DA);
+}
+EXPORT_SYMBOL_GPL(kvm_has_da_feature);
+
 void kvm_init_rmi(void)
 {
 	/* Only 4k page size on the host is supported */
-- 
2.43.0


^ permalink raw reply related

* [RFC PATCH v3 00/10] coco/TSM: Host-side Arm CCA IDE setup via connect/disconnect callbacks
From: Aneesh Kumar K.V (Arm) @ 2026-03-12  8:01 UTC (permalink / raw)
  To: linux-coco, kvmarm, linux-arm-kernel; +Cc: linux-kernel, Aneesh Kumar K.V (Arm)

This patch series implements the TSM ->connect() and ->disconnect() callbacks
required for the Arm CCA IDE setup as per the RMM ALP17 specification [1].

This patchset includes the host-side flow needed by connect/disconnect,
including:
- DA feature detection helpers
- host TSM callback wiring and IDE stream allocation support
- creation/registration of RMM pdev descriptors
- RMM pdev communication helpers
- pdev stop and teardown helpers for disconnect
- pdev instantiation from the connect path
- public key registration with RMM

To support public-key handling from the device certificate chain, the series
also includes the required X.509 parser updates.

The series builds upon the TSM framework patches posted at [2] and depends on
the KVM CCA patchset [3]. A git repository containing all the related changes is
available at [4].

Testing / Usage

To initiate the IDE setup:
	echo tsm0 > /sys/bus/pci/devices/$DEVICE/tsm/connect

To disconnect:
	echo tsm0 > /sys/bus/pci/devices/$DEVICE/tsm/disconnect

Previous posting:
rfc-v1 https://lore.kernel.org/all/20250728135216.48084-1-aneesh.kumar@kernel.org
rfc-v2 https://lore.kernel.org/all/20251027095602.1154418-1-aneesh.kumar@kernel.org

Changes from v2:
* rebase to latest kernel and core TSM changes
* Address review feedback.

[1] https://developer.arm.com/-/cdn-downloads/permalink/Architectures/Armv9/DEN0137_1.1-alp17.zip
[2] https://lore.kernel.org/all/20260303000207.1836586-1-dan.j.williams@intel.com
[3] https://lore.kernel.org/all/461fa23f-9add-40e5-a0d0-759030e7c70b@arm.com
[4] https://gitlab.arm.com/linux-arm/linux-cca.git cca/topics/cca-tdisp-upstream-rfc-v3


Aneesh Kumar K.V (Arm) (7):
  KVM: arm64: RMI: Add and export kvm_has_da_feature helper
  coco: host: arm64: Add host TSM callback and IDE stream allocation
    support
  coco: host: arm64: Build and register RMM pdev descriptors
  coco: host: arm64: Add RMM device communication helpers
  coco: host: arm64: Add helper to stop and tear down an RMM pdev
  coco: host: arm64: Instantiate RMM pdev during device connect
  coco: host: arm64: Register device public key with RMM

Lukas Wunner (3):
  X.509: Make certificate parser public
  X.509: Parse Subject Alternative Name in certificates
  X.509: Move certificate length retrieval into new helper

 arch/arm64/include/asm/kvm_rmi.h          |   1 +
 arch/arm64/include/asm/rmi_cmds.h         |  78 +++
 arch/arm64/include/asm/rmi_smc.h          | 183 ++++++-
 arch/arm64/kvm/rmi.c                      |   6 +
 crypto/asymmetric_keys/x509_cert_parser.c |   9 +
 crypto/asymmetric_keys/x509_loader.c      |  38 +-
 crypto/asymmetric_keys/x509_parser.h      |  42 +-
 drivers/firmware/smccc/rmm.c              |  12 +
 drivers/firmware/smccc/rmm.h              |   8 +
 drivers/firmware/smccc/smccc.c            |   1 +
 drivers/virt/coco/Kconfig                 |   2 +
 drivers/virt/coco/Makefile                |   1 +
 drivers/virt/coco/arm-cca-host/Kconfig    |  23 +
 drivers/virt/coco/arm-cca-host/Makefile   |   5 +
 drivers/virt/coco/arm-cca-host/arm-cca.c  | 274 ++++++++++
 drivers/virt/coco/arm-cca-host/rmi-da.c   | 639 ++++++++++++++++++++++
 drivers/virt/coco/arm-cca-host/rmi-da.h   | 122 +++++
 include/keys/asymmetric-type.h            |   2 +
 include/keys/x509-parser.h                |  57 ++
 19 files changed, 1448 insertions(+), 55 deletions(-)
 create mode 100644 drivers/virt/coco/arm-cca-host/Kconfig
 create mode 100644 drivers/virt/coco/arm-cca-host/Makefile
 create mode 100644 drivers/virt/coco/arm-cca-host/arm-cca.c
 create mode 100644 drivers/virt/coco/arm-cca-host/rmi-da.c
 create mode 100644 drivers/virt/coco/arm-cca-host/rmi-da.h
 create mode 100644 include/keys/x509-parser.h

-- 
2.43.0


^ permalink raw reply

* Re: [PATCH v2 1/3] cpu/bugs: Allow forcing Automatic IBRS with SNP enabled using spectre_v2=eibrs
From: kernel test robot @ 2026-03-12  3:41 UTC (permalink / raw)
  To: Kim Phillips, linux-kernel, kvm, linux-coco, x86
  Cc: oe-kbuild-all, Sean Christopherson, Paolo Bonzini,
	K Prateek Nayak, Nikunj A Dadhania, Tom Lendacky, Michael Roth,
	Borislav Petkov, Naveen Rao, David Kaplan, Pawan Gupta,
	Kim Phillips, stable
In-Reply-To: <20260311130611.2201214-2-kim.phillips@amd.com>

Hi Kim,

kernel test robot noticed the following build warnings:

[auto build test WARNING on 7726ce2287804e70b2bf2fc00f104530b603d3f3]

url:    https://github.com/intel-lab-lkp/linux/commits/Kim-Phillips/cpu-bugs-Allow-forcing-Automatic-IBRS-with-SNP-enabled-using-spectre_v2-eibrs/20260311-211730
base:   7726ce2287804e70b2bf2fc00f104530b603d3f3
patch link:    https://lore.kernel.org/r/20260311130611.2201214-2-kim.phillips%40amd.com
patch subject: [PATCH v2 1/3] cpu/bugs: Allow forcing Automatic IBRS with SNP enabled using spectre_v2=eibrs
config: x86_64-randconfig-101-20260312 (https://download.01.org/0day-ci/archive/20260312/202603121136.bc8zNsHS-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202603121136.bc8zNsHS-lkp@intel.com/

cocci warnings: (new ones prefixed by >>)
>> arch/x86/kernel/cpu/bugs.c:2190:42-44: WARNING !A || A && B is equivalent to !A || B

vim +2190 arch/x86/kernel/cpu/bugs.c

  2122	
  2123	static void __init spectre_v2_select_mitigation(void)
  2124	{
  2125		if ((spectre_v2_cmd == SPECTRE_V2_CMD_RETPOLINE ||
  2126		     spectre_v2_cmd == SPECTRE_V2_CMD_RETPOLINE_LFENCE ||
  2127		     spectre_v2_cmd == SPECTRE_V2_CMD_RETPOLINE_GENERIC ||
  2128		     spectre_v2_cmd == SPECTRE_V2_CMD_EIBRS_LFENCE ||
  2129		     spectre_v2_cmd == SPECTRE_V2_CMD_EIBRS_RETPOLINE) &&
  2130		    !IS_ENABLED(CONFIG_MITIGATION_RETPOLINE)) {
  2131			pr_err("RETPOLINE selected but not compiled in. Switching to AUTO select\n");
  2132			spectre_v2_cmd = SPECTRE_V2_CMD_AUTO;
  2133		}
  2134	
  2135		if ((spectre_v2_cmd == SPECTRE_V2_CMD_EIBRS ||
  2136		     spectre_v2_cmd == SPECTRE_V2_CMD_EIBRS_LFENCE ||
  2137		     spectre_v2_cmd == SPECTRE_V2_CMD_EIBRS_RETPOLINE) &&
  2138		    !boot_cpu_has(X86_FEATURE_IBRS_ENHANCED)) {
  2139			pr_err("EIBRS selected but CPU doesn't have Enhanced or Automatic IBRS. Switching to AUTO select\n");
  2140			spectre_v2_cmd = SPECTRE_V2_CMD_AUTO;
  2141		}
  2142	
  2143		if ((spectre_v2_cmd == SPECTRE_V2_CMD_RETPOLINE_LFENCE ||
  2144		     spectre_v2_cmd == SPECTRE_V2_CMD_EIBRS_LFENCE) &&
  2145		    !boot_cpu_has(X86_FEATURE_LFENCE_RDTSC)) {
  2146			pr_err("LFENCE selected, but CPU doesn't have a serializing LFENCE. Switching to AUTO select\n");
  2147			spectre_v2_cmd = SPECTRE_V2_CMD_AUTO;
  2148		}
  2149	
  2150		if (spectre_v2_cmd == SPECTRE_V2_CMD_IBRS && !IS_ENABLED(CONFIG_MITIGATION_IBRS_ENTRY)) {
  2151			pr_err("IBRS selected but not compiled in. Switching to AUTO select\n");
  2152			spectre_v2_cmd = SPECTRE_V2_CMD_AUTO;
  2153		}
  2154	
  2155		if (spectre_v2_cmd == SPECTRE_V2_CMD_IBRS && boot_cpu_data.x86_vendor != X86_VENDOR_INTEL) {
  2156			pr_err("IBRS selected but not Intel CPU. Switching to AUTO select\n");
  2157			spectre_v2_cmd = SPECTRE_V2_CMD_AUTO;
  2158		}
  2159	
  2160		if (spectre_v2_cmd == SPECTRE_V2_CMD_IBRS && !boot_cpu_has(X86_FEATURE_IBRS)) {
  2161			pr_err("IBRS selected but CPU doesn't have IBRS. Switching to AUTO select\n");
  2162			spectre_v2_cmd = SPECTRE_V2_CMD_AUTO;
  2163		}
  2164	
  2165		if (spectre_v2_cmd == SPECTRE_V2_CMD_IBRS && cpu_feature_enabled(X86_FEATURE_XENPV)) {
  2166			pr_err("IBRS selected but running as XenPV guest. Switching to AUTO select\n");
  2167			spectre_v2_cmd = SPECTRE_V2_CMD_AUTO;
  2168		}
  2169	
  2170		if (!boot_cpu_has_bug(X86_BUG_SPECTRE_V2)) {
  2171			spectre_v2_cmd = SPECTRE_V2_CMD_NONE;
  2172			return;
  2173		}
  2174	
  2175		switch (spectre_v2_cmd) {
  2176		case SPECTRE_V2_CMD_NONE:
  2177			return;
  2178	
  2179		case SPECTRE_V2_CMD_AUTO:
  2180			if (!should_mitigate_vuln(X86_BUG_SPECTRE_V2))
  2181				break;
  2182			fallthrough;
  2183		case SPECTRE_V2_CMD_FORCE:
  2184			/*
  2185			 * Unless forced, don't use AutoIBRS when SNP is enabled
  2186			 * because it degrades host userspace indirect branch performance.
  2187			 */
  2188			if (boot_cpu_has(X86_FEATURE_IBRS_ENHANCED) &&
  2189			    (!boot_cpu_has(X86_FEATURE_SEV_SNP) ||
> 2190			     (boot_cpu_has(X86_FEATURE_SEV_SNP) &&
  2191			      spectre_v2_cmd == SPECTRE_V2_CMD_FORCE))) {
  2192				spectre_v2_enabled = SPECTRE_V2_EIBRS;
  2193				break;
  2194			}
  2195	
  2196			spectre_v2_enabled = spectre_v2_select_retpoline();
  2197			break;
  2198	
  2199		case SPECTRE_V2_CMD_RETPOLINE_LFENCE:
  2200			pr_err(SPECTRE_V2_LFENCE_MSG);
  2201			spectre_v2_enabled = SPECTRE_V2_LFENCE;
  2202			break;
  2203	
  2204		case SPECTRE_V2_CMD_RETPOLINE_GENERIC:
  2205			spectre_v2_enabled = SPECTRE_V2_RETPOLINE;
  2206			break;
  2207	
  2208		case SPECTRE_V2_CMD_RETPOLINE:
  2209			spectre_v2_enabled = spectre_v2_select_retpoline();
  2210			break;
  2211	
  2212		case SPECTRE_V2_CMD_IBRS:
  2213			spectre_v2_enabled = SPECTRE_V2_IBRS;
  2214			break;
  2215	
  2216		case SPECTRE_V2_CMD_EIBRS:
  2217			spectre_v2_enabled = SPECTRE_V2_EIBRS;
  2218			break;
  2219	
  2220		case SPECTRE_V2_CMD_EIBRS_LFENCE:
  2221			spectre_v2_enabled = SPECTRE_V2_EIBRS_LFENCE;
  2222			break;
  2223	
  2224		case SPECTRE_V2_CMD_EIBRS_RETPOLINE:
  2225			spectre_v2_enabled = SPECTRE_V2_EIBRS_RETPOLINE;
  2226			break;
  2227		}
  2228	}
  2229	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply

* Re: [PATCH v4 13/24] x86/virt/seamldr: Shut down the current TDX module
From: Chao Gao @ 2026-03-12  2:57 UTC (permalink / raw)
  To: Edgecombe, Rick P
  Cc: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
	linux-kernel@vger.kernel.org, x86@kernel.org, Huang, Kai,
	dave.hansen@linux.intel.com, tony.lindgren@linux.intel.com,
	binbin.wu@linux.intel.com, seanjc@google.com, Weiny, Ira,
	Chatre, Reinette, Verma, Vishal L, nik.borisov@suse.com,
	mingo@redhat.com, kas@kernel.org, Annapurve, Vishal,
	sagis@google.com, Duan, Zhenzhong, tglx@kernel.org,
	paulmck@kernel.org, hpa@zytor.com, bp@alien8.de,
	yilun.xu@linux.intel.com, Williams, Dan J
In-Reply-To: <c8b83471fbc98666c5980bf9d4073e956bc895da.camel@intel.com>

>> +static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff)
>> +{
>> +	int ret = 0;
>> +	u64 val;
>> +
>> +	if (!tdx_supports_runtime_update(&tdx_sysinfo))
>> +		return 0;
>
>DPAMT has a similar need to conditionally fetch metadata. The thing that is ugly
>about this is it refers to the global copy while populating the tdx_sys_info
>passed as a pointer. That is how DPAMT worked previously. I was going to change
>it to something like this for DPAMT:

Looks good. I will follow this approach.

<snip>

>Wait, looking at the later patches, in the post update caller it will refer to
>the old sysinfo instead of the new one? It assumes a new module will not lose
>runtime update ability?

Yes, no features should be removed during an update to avoid compatibility
issues. TDX module releases must guarantee this, and users should verify
compatibility before an update. If users load incompatible modules, that's
user error: the kernel doesn't prevent users from shooting themselves in
the foot.

^ permalink raw reply

* Re: [PATCH v4 23/24] x86/virt/tdx: Document TDX Module updates
From: Edgecombe, Rick P @ 2026-03-12  2:42 UTC (permalink / raw)
  To: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	Gao, Chao, x86@kernel.org
  Cc: corbet@lwn.net, Huang, Kai, dave.hansen@linux.intel.com,
	tony.lindgren@linux.intel.com, binbin.wu@linux.intel.com,
	seanjc@google.com, Weiny, Ira, Chatre, Reinette, Verma, Vishal L,
	nik.borisov@suse.com, mingo@redhat.com, kas@kernel.org,
	Annapurve, Vishal, sagis@google.com, Duan, Zhenzhong,
	tglx@kernel.org, paulmck@kernel.org, hpa@zytor.com, bp@alien8.de,
	yilun.xu@linux.intel.com, Williams, Dan J
In-Reply-To: <20260212143606.534586-24-chao.gao@intel.com>

On Thu, 2026-02-12 at 06:35 -0800, Chao Gao wrote:
> +
> +Given the risk of losing existing TDs, userspace should verify that the update
> +is compatible with the current system and properly validated before applying it.

Maybe a new line here.

> +A reference userspace tool that implements necessary checks is available at:
> +
> +  https://github.com/intel/confidential-computing.tdx.tdx-module.binaries
> +

It looks good in general. My only question is if we know what kind of
persistence this repo will have. These things can move around unfortunately.

^ permalink raw reply

* Re: [PATCH v4 09/24] x86/virt/seamldr: Check update limit before TDX Module updates
From: Yan Zhao @ 2026-03-12  2:35 UTC (permalink / raw)
  To: Chao Gao
  Cc: linux-coco, linux-kernel, kvm, x86, reinette.chatre, ira.weiny,
	kai.huang, dan.j.williams, yilun.xu, sagis, vannapurve, paulmck,
	nik.borisov, zhenzhong.duan, seanjc, rick.p.edgecombe, kas,
	dave.hansen, vishal.l.verma, binbin.wu, tony.lindgren,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260212143606.534586-10-chao.gao@intel.com>

On Thu, Feb 12, 2026 at 06:35:12AM -0800, Chao Gao wrote:
> TDX maintains a log about each TDX Module which has been loaded. This
> log has a finite size which limits the number of TDX Module updates
> which can be performed.
> 
> After each successful update, the remaining updates reduces by one. Once
> it reaches zero, further updates will fail until next reboot.
> 
> Before updating the TDX Module, verify that the update limit has not been
> exceeded. Otherwise, P-SEAMLDR will detect this violation after the old TDX
> Module is gone and all TDs will be killed.
> 
> Note that userspace should perform this check before updates. Perform this
> check in kernel as well to make the update process more robust.
> 
> Signed-off-by: Chao Gao <chao.gao@intel.com>
> Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
> ---
>  arch/x86/virt/vmx/tdx/seamldr.c | 10 ++++++++++
>  1 file changed, 10 insertions(+)
> 
> diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
> index 694243f1f220..733b13215691 100644
> --- a/arch/x86/virt/vmx/tdx/seamldr.c
> +++ b/arch/x86/virt/vmx/tdx/seamldr.c
> @@ -52,6 +52,16 @@ EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
>   */
>  int seamldr_install_module(const u8 *data, u32 size)
>  {
> +	struct seamldr_info info;
> +	int ret;
> +
> +	ret = seamldr_get_info(&info);
> +	if (ret)
> +		return ret;
> +
> +	if (!info.num_remaining_updates)
> +		return -ENOSPC;
seamldr_install_module() is invoked by tdx_fw_write().
Why don't we put the check of info.num_remaining_updates in tdx_fw_prepare()?

>  	if (WARN_ON_ONCE(!is_vmalloc_addr(data)))
>  		return -EINVAL;
>  
> -- 
> 2.47.3
> 
> 

^ permalink raw reply

* Re: [PATCH v4 10/24] x86/virt/seamldr: Allocate and populate a module update request
From: Yan Zhao @ 2026-03-12  2:32 UTC (permalink / raw)
  To: Chao Gao
  Cc: linux-coco, linux-kernel, kvm, x86, reinette.chatre, ira.weiny,
	kai.huang, dan.j.williams, yilun.xu, sagis, vannapurve, paulmck,
	nik.borisov, zhenzhong.duan, seanjc, rick.p.edgecombe, kas,
	dave.hansen, vishal.l.verma, binbin.wu, tony.lindgren,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, H. Peter Anvin
In-Reply-To: <20260212143606.534586-11-chao.gao@intel.com>

On Thu, Feb 12, 2026 at 06:35:13AM -0800, Chao Gao wrote:
> P-SEAMLDR uses the SEAMLDR_PARAMS structure to describe TDX Module
> update requests. This structure contains physical addresses pointing to
> the module binary and its signature file (or sigstruct), along with an
> update scenario field.
> 
> TDX Modules are distributed in the tdx_blob format defined at [1]. A
> tdx_blob contains a header, sigstruct, and module binary. This is also
> the format supplied by the userspace to the kernel.
> 
> Parse the tdx_blob format and populate a SEAMLDR_PARAMS structure
> accordingly. This structure will be passed to P-SEAMLDR to initiate the
> update.
> 
> Note that the sigstruct_pa field in SEAMLDR_PARAMS has been extended to
> a 4-element array. The updated "SEAM Loader (SEAMLDR) Interface
> Specification" will be published separately. The kernel does not
> validate P-SEAMLDR compatibility (for example, whether it supports 4KB
> or 16KB sigstruct); userspace must ensure the P-SEAMLDR version is
> compatible with the selected TDX Module by checking the minimum
> P-SEAMLDR version requirements at [2].
> 
> Signed-off-by: Chao Gao <chao.gao@intel.com>
> Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
> Link: https://github.com/intel/confidential-computing.tdx.tdx-module.binaries/blob/main/blob_structure.txt # [1]
> Link: https://github.com/intel/confidential-computing.tdx.tdx-module.binaries/blob/main/mapping_file.json # [2]
> ---
> v4:
>  - Remove checksum verification as it is optional
>  - Convert comments to is_vmalloc_addr() checks [Kai]
>  - Explain size/alignment checks in alloc_seamldr_params() [Kai]
> 
> v3:
>  - Print tdx_blob version in hex [Binbin]
>  - Drop redundant sigstruct alignment check [Yilun]
>  - Note buffers passed from firmware upload infrastructure are
>    vmalloc()'d above alloc_seamldr_params()
> ---
>  arch/x86/virt/vmx/tdx/seamldr.c | 152 ++++++++++++++++++++++++++++++++
>  1 file changed, 152 insertions(+)
> 
> diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
> index 733b13215691..718cb8396057 100644
> --- a/arch/x86/virt/vmx/tdx/seamldr.c
> +++ b/arch/x86/virt/vmx/tdx/seamldr.c
> @@ -6,9 +6,11 @@
>   */
>  #define pr_fmt(fmt)	"seamldr: " fmt
>  
> +#include <linux/cleanup.h>
>  #include <linux/cpuhplock.h>
>  #include <linux/cpumask.h>
>  #include <linux/mm.h>
> +#include <linux/slab.h>
>  #include <linux/spinlock.h>
>  
>  #include <asm/seamldr.h>
> @@ -18,6 +20,33 @@
>  /* P-SEAMLDR SEAMCALL leaf function */
>  #define P_SEAMLDR_INFO			0x8000000000000000
>  
> +#define SEAMLDR_MAX_NR_MODULE_4KB_PAGES	496
> +#define SEAMLDR_MAX_NR_SIG_4KB_PAGES	4
> +
> +/*
> + * The seamldr_params "scenario" field specifies the operation mode:
> + * 0: Install TDX Module from scratch (not used by kernel)
> + * 1: Update existing TDX Module to a compatible version
> + */
> +#define SEAMLDR_SCENARIO_UPDATE		1
> +
> +/*
> + * This is called the "SEAMLDR_PARAMS" data structure and is defined
> + * in "SEAM Loader (SEAMLDR) Interface Specification".
> + *
> + * It describes the TDX Module that will be installed.
> + */
> +struct seamldr_params {
> +	u32	version;
> +	u32	scenario;
> +	u64	sigstruct_pa[SEAMLDR_MAX_NR_SIG_4KB_PAGES];
> +	u8	reserved[80];
Calculate this size (i.e., 80) from 4096 - xxx ?

> +	u64	num_module_pages;
> +	u64	mod_pages_pa_list[SEAMLDR_MAX_NR_MODULE_4KB_PAGES];
> +} __packed;
> +
> +static_assert(sizeof(struct seamldr_params) == 4096);
> +
>  /*
>   * Serialize P-SEAMLDR calls since the hardware only allows a single CPU to
>   * interact with P-SEAMLDR simultaneously.
> @@ -42,6 +71,124 @@ int seamldr_get_info(struct seamldr_info *seamldr_info)
>  }
>  EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host");
>  
> +static void free_seamldr_params(struct seamldr_params *params)
> +{
> +	free_page((unsigned long)params);
> +}
> +
> +static struct seamldr_params *alloc_seamldr_params(const void *module, unsigned int module_size,
> +						   const void *sig, unsigned int sig_size)
> +{
> +	struct seamldr_params *params;
> +	const u8 *ptr;
> +	int i;
> +
> +	if (WARN_ON_ONCE(!is_vmalloc_addr(module) || !is_vmalloc_addr(sig)))
> +		return ERR_PTR(-EINVAL);
> +
> +	if (module_size > SEAMLDR_MAX_NR_MODULE_4KB_PAGES * SZ_4K)
> +		return ERR_PTR(-EINVAL);
> +
> +	if (sig_size > SEAMLDR_MAX_NR_SIG_4KB_PAGES * SZ_4K)
> +		return ERR_PTR(-EINVAL);
> +
> +	/*
> +	 * Check that input buffers satisfy P-SEAMLDR's size and alignment
> +	 * constraints so they can be passed directly to P-SEAMLDR without
> +	 * relocation or copy.
> +	 */
> +	if (!IS_ALIGNED(module_size, SZ_4K) || !IS_ALIGNED(sig_size, SZ_4K) ||
> +	    !IS_ALIGNED((unsigned long)module, SZ_4K) ||
> +	    !IS_ALIGNED((unsigned long)sig, SZ_4K))
> +		return ERR_PTR(-EINVAL);
> +
> +	params = (struct seamldr_params *)get_zeroed_page(GFP_KERNEL);
> +	if (!params)
> +		return ERR_PTR(-ENOMEM);
> +
> +	params->scenario = SEAMLDR_SCENARIO_UPDATE;

Add a comment for why params->version isn't initialized explicitly?

> +	ptr = sig;
> +	for (i = 0; i < sig_size / SZ_4K; i++) {
> +		/*
> +		 * Don't assume @sig is page-aligned although it is 4KB-aligned.
> +		 * Always add the in-page offset to get the physical address.
> +		 */
> +		params->sigstruct_pa[i] = (vmalloc_to_pfn(ptr) << PAGE_SHIFT) +
> +					  ((unsigned long)ptr & ~PAGE_MASK);
> +		ptr += SZ_4K;
> +	}
> +
> +	params->num_module_pages = module_size / SZ_4K;
> +
> +	ptr = module;
> +	for (i = 0; i < params->num_module_pages; i++) {
> +		params->mod_pages_pa_list[i] = (vmalloc_to_pfn(ptr) << PAGE_SHIFT) +
> +					       ((unsigned long)ptr & ~PAGE_MASK);
> +		ptr += SZ_4K;
> +	}
> +
> +	return params;
> +}
> +
> +/*
> + * Intel TDX Module blob. Its format is defined at:
> + * https://github.com/intel/tdx-module-binaries/blob/main/blob_structure.txt
> + *
> + * Note this structure differs from the reference above: the two variable-length
> + * fields "@sigstruct" and "@module" are represented as a single "@data" field
> + * here and split programmatically using the offset_of_module value.
> + */
> +struct tdx_blob {
> +	u16	version;
> +	u16	checksum;
> +	u32	offset_of_module;
> +	u8	signature[8];
> +	u32	length;
> +	u32	resv0;
> +	u64	resv1[509];
> +	u8	data[];
> +} __packed;
> +
> +static struct seamldr_params *init_seamldr_params(const u8 *data, u32 size)
> +{
> +	const struct tdx_blob *blob = (const void *)data;
> +	int module_size, sig_size;
> +	const void *sig, *module;
> +
> +	if (size < sizeof(struct tdx_blob) || blob->offset_of_module >= size)
> +		return ERR_PTR(-EINVAL);
> +
> +	if (blob->version != 0x100) {
Do we need a macro for this 0x100?

> +		pr_err("unsupported blob version: %x\n", blob->version);
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	if (blob->resv0 || memchr_inv(blob->resv1, 0, sizeof(blob->resv1))) {
> +		pr_err("non-zero reserved fields\n");
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	/* Split the blob into a sigstruct and a module */
> +	sig		= blob->data;
> +	sig_size	= blob->offset_of_module - sizeof(struct tdx_blob);
> +	module		= data + blob->offset_of_module;
> +	module_size	= size - blob->offset_of_module;
> +
> +	if (sig_size <= 0 || module_size <= 0 || blob->length != size)
> +		return ERR_PTR(-EINVAL);
> +
> +	if (memcmp(blob->signature, "TDX-BLOB", 8)) {
> +		pr_err("invalid signature\n");
> +		return ERR_PTR(-EINVAL);
> +	}
> +
> +	return alloc_seamldr_params(module, module_size, sig, sig_size);
> +}
> +
> +DEFINE_FREE(free_seamldr_params, struct seamldr_params *,
> +	    if (!IS_ERR_OR_NULL(_T)) free_seamldr_params(_T))
> +
>  /**
>   * seamldr_install_module - Install a new TDX module
>   * @data: Pointer to the TDX module update blob. It should be vmalloc'd
> @@ -65,6 +212,11 @@ int seamldr_install_module(const u8 *data, u32 size)
>  	if (WARN_ON_ONCE(!is_vmalloc_addr(data)))
>  		return -EINVAL;
>  
> +	struct seamldr_params *params __free(free_seamldr_params) =
> +						init_seamldr_params(data, size);
> +	if (IS_ERR(params))
> +		return PTR_ERR(params);
> +
>  	guard(cpus_read_lock)();
>  	if (!cpumask_equal(cpu_online_mask, cpu_present_mask)) {
>  		pr_err("Cannot update the TDX Module if any CPU is offline\n");
> -- 
> 2.47.3
> 
> 

^ permalink raw reply

* Re: [PATCH v4 13/24] x86/virt/seamldr: Shut down the current TDX module
From: Edgecombe, Rick P @ 2026-03-12  2:34 UTC (permalink / raw)
  To: Gao, Chao, Huang, Kai
  Cc: tony.lindgren@linux.intel.com, linux-coco@lists.linux.dev,
	kvm@vger.kernel.org, dave.hansen@linux.intel.com, kas@kernel.org,
	mingo@redhat.com, Chatre, Reinette, Weiny, Ira, seanjc@google.com,
	Verma, Vishal L, nik.borisov@suse.com, binbin.wu@linux.intel.com,
	hpa@zytor.com, Annapurve, Vishal, sagis@google.com,
	Duan, Zhenzhong, tglx@kernel.org, linux-kernel@vger.kernel.org,
	paulmck@kernel.org, bp@alien8.de, yilun.xu@linux.intel.com,
	x86@kernel.org, Williams, Dan J
In-Reply-To: <aaqM+eFfQ9qmpzyT@intel.com>

On Fri, 2026-03-06 at 16:14 +0800, Chao Gao wrote:
> > This (and future patches) makes couple of tdx_xx() functions visible out of
> > tdx.c.  The alternative is to move the main "module update" function out of
> > seamldr.c to tdx.c, but that would require making couple of seamldr_xx()s
> > (and data structures probably) visible to tdx.c too.
> 
> Yes. I'll keep this organization unless someone strongly prefers moving the
> main "module update" function and related data structures to tdx.c.
> 
> If neither approach is acceptable, a third option would be to remove seamldr.c
> entirely and merge it into tdx.c. This would mean adding ~360 LoC to an
> existing file that already has ~1900 LoC.

tdx.c will only get bigger, but the breakdown between these files in this series
is not super clear to me. I think the headers are not a problem. But the fact
that seamldr.c is making seamcalls indirectly is a bit strange.

I'd maybe vote to put it all into tdx.c at this stage of the enabling, but
leaving it seems ok to me too. Someday when TDX is more implemented we can see
what borders make more sense.

^ permalink raw reply

* Re: [PATCH v4 13/24] x86/virt/seamldr: Shut down the current TDX module
From: Edgecombe, Rick P @ 2026-03-12  2:17 UTC (permalink / raw)
  To: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
	linux-kernel@vger.kernel.org, Gao, Chao, x86@kernel.org
  Cc: Huang, Kai, dave.hansen@linux.intel.com,
	tony.lindgren@linux.intel.com, binbin.wu@linux.intel.com,
	seanjc@google.com, Weiny, Ira, Chatre, Reinette, Verma, Vishal L,
	nik.borisov@suse.com, mingo@redhat.com, kas@kernel.org,
	Annapurve, Vishal, sagis@google.com, Duan, Zhenzhong,
	tglx@kernel.org, paulmck@kernel.org, hpa@zytor.com, bp@alien8.de,
	yilun.xu@linux.intel.com, Williams, Dan J
In-Reply-To: <20260212143606.534586-14-chao.gao@intel.com>

On Thu, 2026-02-12 at 06:35 -0800, Chao Gao wrote:
> The first step of TDX Module updates is shutting down the current TDX
> Module. This step also packs state information that needs to be
> preserved across updates as handoff data, which will be consumed by the
> updated module. The handoff data is stored internally in the SEAM range
> and is hidden from the kernel.
> 
> To ensure a successful update, the new module must be able to consume
> the handoff data generated by the old module. Since handoff data layout
> may change between modules, the handoff data is versioned. Each module
> has a native handoff version and provides backward support for several
> older versions.
> 
> The complete handoff versioning protocol is complex as it supports both
> module upgrades and downgrades. See details in Intel® Trust Domain
> Extensions (Intel® TDX) Module Base Architecture Specification, Revision
> 348549-007, Chapter 4.5.3 "Handoff Versioning".
> 
> Ideally, the kernel needs to retrieve the handoff versions supported by
> the current module and the new module and select a version supported by
> both. But, since the Linux kernel only supports module upgrades, simply
> request the current module to generate handoff data using its highest
> supported version, expecting that the new module will likely support it.
> 
> Note that only one CPU needs to call the TDX Module's shutdown API.
> 
> Signed-off-by: Chao Gao <chao.gao@intel.com>
> Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
> ---
> v4:
>  - skip the whole handoff metadata if runtime updates are not supported
>    [Yilun]
> v3:
>  - remove autogeneration stuff in the changelog
> v2:
>  - add a comment about how handoff version is chosen.
>  - remove the first !ret in get_tdx_sys_info_handoff() as we edited the
>    auto-generated code anyway
>  - remove !! when determining whether a CPU is the primary one
>  - remove unnecessary if-break nesting in TDP_SHUTDOWN
> ---
>  arch/x86/include/asm/tdx_global_metadata.h  |  5 +++++
>  arch/x86/virt/vmx/tdx/seamldr.c             | 10 ++++++++++
>  arch/x86/virt/vmx/tdx/tdx.c                 | 15 +++++++++++++++
>  arch/x86/virt/vmx/tdx/tdx.h                 |  3 +++
>  arch/x86/virt/vmx/tdx/tdx_global_metadata.c | 15 +++++++++++++++
>  5 files changed, 48 insertions(+)
> 
> diff --git a/arch/x86/include/asm/tdx_global_metadata.h b/arch/x86/include/asm/tdx_global_metadata.h
> index 40689c8dc67e..8a9ebd895e70 100644
> --- a/arch/x86/include/asm/tdx_global_metadata.h
> +++ b/arch/x86/include/asm/tdx_global_metadata.h
> @@ -40,12 +40,17 @@ struct tdx_sys_info_td_conf {
>  	u64 cpuid_config_values[128][2];
>  };
>  
> +struct tdx_sys_info_handoff {
> +	u16 module_hv;
> +};
> +
>  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_handoff handoff;
>  };
>  
>  #endif
> diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
> index 70bc577e5957..c59cdd5b1fe4 100644
> --- a/arch/x86/virt/vmx/tdx/seamldr.c
> +++ b/arch/x86/virt/vmx/tdx/seamldr.c
> @@ -18,6 +18,7 @@
>  #include <asm/seamldr.h>
>  
>  #include "seamcall_internal.h"
> +#include "tdx.h"
>  
>  /* P-SEAMLDR SEAMCALL leaf function */
>  #define P_SEAMLDR_INFO			0x8000000000000000
> @@ -196,6 +197,7 @@ static struct seamldr_params *init_seamldr_params(const u8 *data, u32 size)
>   */
>  enum tdp_state {
>  	TDP_START,
> +	TDP_SHUTDOWN,
>  	TDP_DONE,
>  };
>  
> @@ -228,8 +230,12 @@ static void ack_state(void)
>  static int do_seamldr_install_module(void *params)
>  {
>  	enum tdp_state newstate, curstate = TDP_START;
> +	int cpu = smp_processor_id();
> +	bool primary;
>  	int ret = 0;
>  
> +	primary = cpumask_first(cpu_online_mask) == cpu;
> +
>  	do {
>  		/* Chill out and re-read tdp_data */
>  		cpu_relax();
> @@ -238,6 +244,10 @@ static int do_seamldr_install_module(void *params)
>  		if (newstate != curstate) {
>  			curstate = newstate;
>  			switch (curstate) {
> +			case TDP_SHUTDOWN:
> +				if (primary)
> +					ret = tdx_module_shutdown();
> +				break;
>  			default:
>  				break;
>  			}
> diff --git a/arch/x86/virt/vmx/tdx/tdx.c b/arch/x86/virt/vmx/tdx/tdx.c
> index b65b2a609e81..f911c8c63800 100644
> --- a/arch/x86/virt/vmx/tdx/tdx.c
> +++ b/arch/x86/virt/vmx/tdx/tdx.c
> @@ -1176,6 +1176,21 @@ int tdx_enable(void)
>  }
>  EXPORT_SYMBOL_FOR_KVM(tdx_enable);
>  
> +int tdx_module_shutdown(void)
> +{
> +	struct tdx_module_args args = {};
> +
> +	/*
> +	 * Shut down the TDX Module and prepare handoff data for the next
> +	 * TDX Module. This SEAMCALL requires a handoff version. Use the
> +	 * module's handoff version, as it is the highest version the
> +	 * module can produce and is more likely to be supported by new
> +	 * modules as new modules likely have higher handoff version.
> +	 */
> +	args.rcx = tdx_sysinfo.handoff.module_hv;
> +	return seamcall_prerr(TDH_SYS_SHUTDOWN, &args);
> +}
> +
>  static bool is_pamt_page(unsigned long phys)
>  {
>  	struct tdmr_info_list *tdmr_list = &tdx_tdmr_list;
> diff --git a/arch/x86/virt/vmx/tdx/tdx.h b/arch/x86/virt/vmx/tdx/tdx.h
> index 82bb82be8567..1c4da9540ae0 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_SYS_SHUTDOWN		52
>  
>  /*
>   * SEAMCALL leaf:
> @@ -118,4 +119,6 @@ struct tdmr_info_list {
>  	int max_tdmrs;	/* How many 'tdmr_info's are allocated */
>  };
>  
> +int tdx_module_shutdown(void);
> +
>  #endif
> diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
> index 4c9917a9c2c3..6aee10c36489 100644
> --- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
> +++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
> @@ -100,6 +100,20 @@ static int get_tdx_sys_info_td_conf(struct tdx_sys_info_td_conf *sysinfo_td_conf
>  	return ret;
>  }
>  
> +static int get_tdx_sys_info_handoff(struct tdx_sys_info_handoff *sysinfo_handoff)
> +{
> +	int ret = 0;
> +	u64 val;
> +
> +	if (!tdx_supports_runtime_update(&tdx_sysinfo))
> +		return 0;

DPAMT has a similar need to conditionally fetch metadata. The thing that is ugly
about this is it refers to the global copy while populating the tdx_sys_info
passed as a pointer. That is how DPAMT worked previously. I was going to change
it to something like this for DPAMT:

diff --git a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
index 13ad2663488b..13e68d375065 100644
--- a/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
+++ b/arch/x86/virt/vmx/tdx/tdx_global_metadata.c
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 /*
- * Automatically generated functions to read TDX global metadata.
+ * 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.
@@ -18,6 +18,17 @@ static int get_tdx_sys_info_features(struct
tdx_sys_info_features *sysinfo_featu
        return ret;
 }
 
+static int get_tdx_sys_info_tdmr_dpamt(struct tdx_sys_info_tdmr *sysinfo_tdmr)
+{
+       int ret = 0;
+       u64 val;
+
+       if (!ret && !(ret = read_sys_metadata_field(0x9100000100000013, &val)))
+               sysinfo_tdmr->pamt_page_bitmap_entry_bits = val;
+
+       return ret;
+}
+
 static int get_tdx_sys_info_tdmr(struct tdx_sys_info_tdmr *sysinfo_tdmr)
 {
        int ret = 0;
@@ -94,5 +105,12 @@ static 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);
 
+       /*
+        * Don't treat a module that doesn't support Dynamic PAMT
+        * as a failure. Only read the metadata optionally.
+        */
+       if (tdx_supports_dynamic_pamt(sysinfo))
+               ret = ret ?: get_tdx_sys_info_tdmr_dpamt(&sysinfo->tdmr);
+
        return ret;
 }


Wait, looking at the later patches, in the post update caller it will refer to
the old sysinfo instead of the new one? It assumes a new module will not lose
runtime update ability?

Rest of the patch LGTM.

> +
> +	if (!ret && !(ret = read_sys_metadata_field(0x8900000100000000, &val)))
> +		sysinfo_handoff->module_hv = val;
> +
> +	return ret;
> +}
> +
>  static int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
>  {
>  	int ret = 0;
> @@ -115,6 +129,7 @@ static int get_tdx_sys_info(struct tdx_sys_info *sysinfo)
>  	ret = ret ?: get_tdx_sys_info_tdmr(&sysinfo->tdmr);
>  	ret = ret ?: get_tdx_sys_info_td_ctrl(&sysinfo->td_ctrl);
>  	ret = ret ?: get_tdx_sys_info_td_conf(&sysinfo->td_conf);
> +	ret = ret ?: get_tdx_sys_info_handoff(&sysinfo->handoff);
>  
>  	return ret;
>  }


^ permalink raw reply related

* Re: [PATCH v4 11/24] x86/virt/seamldr: Introduce skeleton for TDX Module updates
From: Edgecombe, Rick P @ 2026-03-12  2:00 UTC (permalink / raw)
  To: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
	linux-kernel@vger.kernel.org, Gao, Chao, x86@kernel.org
  Cc: Huang, Kai, dave.hansen@linux.intel.com,
	tony.lindgren@linux.intel.com, binbin.wu@linux.intel.com,
	seanjc@google.com, Weiny, Ira, Chatre, Reinette, Verma, Vishal L,
	nik.borisov@suse.com, mingo@redhat.com, kas@kernel.org,
	Annapurve, Vishal, sagis@google.com, Duan, Zhenzhong,
	tglx@kernel.org, paulmck@kernel.org, hpa@zytor.com, bp@alien8.de,
	yilun.xu@linux.intel.com, Williams, Dan J
In-Reply-To: <20260212143606.534586-12-chao.gao@intel.com>

On Thu, 2026-02-12 at 06:35 -0800, Chao Gao wrote:
> TDX Module updates require careful synchronization with other TDX
> operations on the host. During updates, only update-related SEAMCALLs are
> permitted; all other SEAMCALLs must be blocked.
> 
> However, SEAMCALLs can be invoked from different contexts (normal and IRQ
> context) and run in parallel across CPUs. And, all TD vCPUs must remain
> out of guest mode during updates.
> 

Above it says only update-related SEAMCALLs are permitted. Does that not already
exclude SEAMCALLs that might allow entering the TD?

>  No single lock primitive can satisfy
> all these synchronization requirements, so stop_machine() is used as the
> only well-understood mechanism that can meet them all.
> 
> The TDX Module update process consists of several steps as described in
> Intel® Trust Domain Extensions (Intel® TDX) Module Base Architecture
> Specification, Revision 348549-007, Chapter 4.5 "TD-Preserving TDX Module
> Update"
> 
>   - shut down the old module
>   - install the new module
>   - global and per-CPU initialization
>   - restore state information
> 
> Some steps must execute on a single CPU, others must run serially across
> all CPUs, and some can run concurrently on all CPUs. There are also
> ordering requirements between steps, so all CPUs must work in a step-locked
> manner.

Does the fact that they can run on other CPUs add any synchronization
requirements? If not I'd leave it off.

> 
> In summary, TDX Module updates create two requirements:

The stop_machine() part seems more like a solution then a requirement.

> 
> 1. The entire update process must use stop_machine() to synchronize with
>    other TDX workloads
> 2. Update steps must be performed in a step-locked manner
> 
> To prepare for implementing concrete TDX Module update steps, establish
> the framework by mimicking multi_cpu_stop(), which is a good example of
> performing a multi-step task in step-locked manner.
> 

Offline Chao pointed that Paul suggested this after considering refactoring out
the common code. I think it might still be worth mentioning why you can't use
multi_cpu_stop() directly. I guess there are some differences. what are they.

>  Specifically, use a
> global state machine to control each CPU's work and require all CPUs to
> acknowledge completion before proceeding to the next step.

Maybe add a bit more about the reasoning for requiring the other steps to ack.
Tie it back to the lockstep part.

> 
> Potential alternative to stop_machine()
> =======================================
> An alternative approach is to lock all KVM entry points and kick all
> vCPUs. Here, KVM entry points refer to KVM VM/vCPU ioctl entry points,
> implemented in KVM common code (virt/kvm). Adding a locking mechanism
> there would affect all architectures KVM supports. And to lock only TDX
> vCPUs, new logic would be needed to identify TDX vCPUs, which the KVM
> common code currently lacks. This would add significant complexity and
> maintenance overhead to KVM for this TDX-specific use case.
> 
> Signed-off-by: Chao Gao <chao.gao@intel.com>
> Reviewed-by: Xu Yilun <yilun.xu@linux.intel.com>
> Reviewed-by: Tony Lindgren <tony.lindgren@linux.intel.com>
> ---
> v2:
>  - refine the changlog to follow context-problem-solution structure
>  - move alternative discussions at the end of the changelog
>  - add a comment about state machine transition
>  - Move rcu_momentary_eqs() call to the else branch.
> ---
>  arch/x86/virt/vmx/tdx/seamldr.c | 70 ++++++++++++++++++++++++++++++++-
>  1 file changed, 69 insertions(+), 1 deletion(-)
> 
> diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamldr.c
> index 718cb8396057..21d572d75769 100644
> --- a/arch/x86/virt/vmx/tdx/seamldr.c
> +++ b/arch/x86/virt/vmx/tdx/seamldr.c
> @@ -10,8 +10,10 @@
>  #include <linux/cpuhplock.h>
>  #include <linux/cpumask.h>
>  #include <linux/mm.h>
> +#include <linux/nmi.h>
>  #include <linux/slab.h>
>  #include <linux/spinlock.h>
> +#include <linux/stop_machine.h>
>  
>  #include <asm/seamldr.h>
>  
> @@ -186,6 +188,68 @@ static struct seamldr_params *init_seamldr_params(const u8 *data, u32 size)
>  	return alloc_seamldr_params(module, module_size, sig, sig_size);
>  }
>  
> +/*
> + * During a TDX Module update, all CPUs start from TDP_START and progress
> + * to TDP_DONE. Each state is associated with certain work. For some
> + * states, just one CPU needs to perform the work, while other CPUs just
> + * wait during those states.
> + */
> +enum tdp_state {
> +	TDP_START,
> +	TDP_DONE,
> +};
> +
> +static struct {
> +	enum tdp_state state;
> +	atomic_t thread_ack;
> +} tdp_data;
> +
> +static void set_target_state(enum tdp_state state)
> +{
> +	/* Reset ack counter. */
> +	atomic_set(&tdp_data.thread_ack, num_online_cpus());
> +	/* Ensure thread_ack is updated before the new state */
> +	smp_wmb();
> +	WRITE_ONCE(tdp_data.state, state);
> +}
> +
> +/* Last one to ack a state moves to the next state. */
> +static void ack_state(void)
> +{
> +	if (atomic_dec_and_test(&tdp_data.thread_ack))
> +		set_target_state(tdp_data.state + 1);
> +}
> +
> +/*
> + * See multi_cpu_stop() from where this multi-cpu state-machine was
> + * adopted, and the rationale for touch_nmi_watchdog()
> + */
> +static int do_seamldr_install_module(void *params)
> +{
> +	enum tdp_state newstate, curstate = TDP_START;
> +	int ret = 0;
> +
> +	do {
> +		/* Chill out and re-read tdp_data */
> +		cpu_relax();
> +		newstate = READ_ONCE(tdp_data.state);
> +
> +		if (newstate != curstate) {
> +			curstate = newstate;
> +			switch (curstate) {

Maybe a little comment here like "todo add the steps".

> +			default:
> +				break;
> +			}
> +			ack_state();
> +		} else {
> +			touch_nmi_watchdog();
> +			rcu_momentary_eqs();
> +		}
> +	} while (curstate != TDP_DONE);
> +
> +	return ret;
> +}
> +
>  DEFINE_FREE(free_seamldr_params, struct seamldr_params *,
>  	    if (!IS_ERR_OR_NULL(_T)) free_seamldr_params(_T))
>  
> @@ -223,7 +287,11 @@ int seamldr_install_module(const u8 *data, u32 size)
>  		return -EBUSY;
>  	}
>  
> -	/* TODO: Update TDX Module here */
> +	set_target_state(TDP_START + 1);
> +	ret = stop_machine_cpuslocked(do_seamldr_install_module, params, cpu_online_mask);
> +	if (ret)
> +		return ret;
> +
>  	return 0;
>  }
>  EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host");


^ permalink raw reply

* Re: [PATCH net-next v3 1/2] dma-mapping: introduce DMA_ATTR_CC_DECRYPTED for pre-decrypted memory
From: Jason Gunthorpe @ 2026-03-12  0:34 UTC (permalink / raw)
  To: Jiri Pirko
  Cc: Leon Romanovsky, dri-devel, linaro-mm-sig, iommu, linux-media,
	sumit.semwal, benjamin.gaignard, Brian.Starkey, jstultz,
	tjmercier, christian.koenig, m.szyprowski, robin.murphy,
	sean.anderson, ptesarik, catalin.marinas, aneesh.kumar,
	suzuki.poulose, steven.price, thomas.lendacky, john.allen,
	ashish.kalra, suravee.suthikulpanit, linux-coco
In-Reply-To: <phry3e2dtgxzxdqvrnqfuskangp4al64f2auithwme5kwkgepe@7qtftrhgv4l7>

On Mon, Mar 09, 2026 at 06:51:21PM +0100, Jiri Pirko wrote:
> Mon, Mar 09, 2026 at 04:18:57PM +0100, jgg@ziepe.ca wrote:
> >On Mon, Mar 09, 2026 at 04:02:33PM +0200, Leon Romanovsky wrote:
> >> On Mon, Mar 09, 2026 at 10:15:30AM -0300, Jason Gunthorpe wrote:
> >> > On Sun, Mar 08, 2026 at 12:19:48PM +0200, Leon Romanovsky wrote:
> >> > 
> >> > > > +/*
> >> > > > + * DMA_ATTR_CC_DECRYPTED: Indicates memory that has been explicitly decrypted
> >> > > > + * (shared) for confidential computing guests. The caller must have
> >> > > > + * called set_memory_decrypted(). A struct page is required.
> >> > > > + */
> >> > > > +#define DMA_ATTR_CC_DECRYPTED	(1UL << 12)
> >> > > 
> >> > > While adding the new attribute is fine, I would expect additional checks in
> >> > > dma_map_phys() to ensure the attribute cannot be misused. For example,
> >> > > WARN_ON(attrs & (DMA_ATTR_CC_DECRYPTED | DMA_ATTR_MMIO)), along with a check
> >> > > that we are taking the direct path only.
> >> > 
> >> > DECRYPYED and MMIO is something that needs to work, VFIO (inside a
> >> > TVM) should be using that combination.
> >> 
> >> So this sentence "A struct page is required" from the comment above is
> >> not accurate.
> >
> >It would be clearer to say "Unless DMA_ATTR_MMIO is provided a struct
> >page is required"
> >
> >We need to audit if that works properly, IIRC it does, but I don't
> >remember.. Jiri?
> 
> How can you do set_memory_decrypted if you don't have page/folio ?

Alot of device MMIO is decrypted by nature and can't be encrypted, so
you'd have to use both flags. eg in VFIO we'd want to do this.

Jason


^ permalink raw reply

* Re: [PATCH v2 3/7] x86/sev: add support for RMPOPT instruction
From: Dave Hansen @ 2026-03-11 22:20 UTC (permalink / raw)
  To: Kalra, Ashish, Sean Christopherson
  Cc: tglx, mingo, bp, dave.hansen, x86, hpa, peterz, thomas.lendacky,
	herbert, davem, ardb, pbonzini, aik, Michael.Roth, KPrateek.Nayak,
	Tycho.Andersen, Nathan.Fontenot, jackyli, pgonda, rientjes,
	jacobhxu, xin, pawan.kumar.gupta, babu.moger, dyoung, nikunj,
	john.allen, darwi, linux-kernel, linux-crypto, kvm, linux-coco
In-Reply-To: <cc9bf918-a14b-4619-a084-3f424fa16ea1@amd.com>

On 3/11/26 14:24, Kalra, Ashish wrote:
...
> There are 2 active SNP VMs here, with one SNP VM being terminated, the other SNP VM is still running, both VMs are configured with 100GB guest RAM: 
> 
> When this loop is executed when the SNP guest terminates:
> 
> [  232.789187] SEV-SNP: RMPOPT execution time 391609638 ns for physical address range 0x0000000000000000 - 0x0000020000000000 on all cpus -> ~391 ms
> 
> [  234.647462] SEV-SNP: RMPOPT execution time 457933019 ns for physical address range 0x0000000000000000 - 0x0000020000000000 on all cpus -> ~457 ms

That's better, but it's not quite what am looking for.

The most important case (IMNHO) is when RMPOPT falls flat on its face:
it tries to optimize the full 2TB of memory and manages to optimize nothing.

I doubt that two 100GB VMs will get close to that case. It's
theoretically possible, but unlikely.

You also didn't mention 4k vs. 2M vs. 1G mappings.

> Now, there are a couple of additional RMPOPT optimizations which can be applied to this loop : 
> 
> 1). RMPOPT can skip the bulk of its work if another CPU has already optimized that region.
> The optimal thing may be to optimize all memory on one CPU first, and then let all the others
> run RMPOPT in parallel.

Ahh, so the RMP table itself caches the result of the RMPOPT in its 1G
metadata, then the CPUs can just copy it into their core-local
optimization table at RMPOPT time?

That's handy.

*But*, for the purposes of finding pathological behavior, it's actually
contrary to what I think I was asking for which was having all 1G pages
filled with some private memory. If the system was in the state I want
to see tested, that optimization won't function.

> [  363.926595] SEV-SNP: RMPOPT execution time 317016656 ns for physical address range 0x0000000000000000 - 0x0000020000000000 on all cpus -> ~317 ms
> 
> [  365.415243] SEV-SNP: RMPOPT execution time 369659769 ns for physical address range 0x0000000000000000 - 0x0000020000000000 on all cpus -> ~369 ms.
> 
> So, with these two optimizations applied, there is like a ~16-20% performance improvement (when SNP guest terminates) in the execution of this loop
> which is executing RMPOPT on upto 2TB of RAM on all CPUs.
> 
> Any thoughts, feedback on the performance numbers ? 

16-20% isn't horrible, but it isn't really a fundamental change.

It would also be nice to see elapsed time for each CPU. Having one
pegged CPU for 400ms and 99 mostly idle ones is way different than
having 100 pegged CPUs for 400ms.

That's why I was interested in "how long it takes per-cpu".

But you could get some pretty good info with your new optimized loop:

                start = ktime_get();

                for (pa = pa_start; pa < pa_end; pa += PUD_SIZE)
                        rmpopt() // current CPU

                middle = ktime_get();

                for (pa = pa_start; pa < pa_end; pa += PUD_SIZE)
                        on_each_cpu_mask(...) // remote CPUs

                end = ktime_get();

If you do that ^ with a system:

	1. full of private memory
	2. empty of private memory
	3. empty again

You'll hopefully see:

	1. RMPOPT fall on its face. Worst case scenario (what I want to
	   see most)
	2. RMPOPT sees great success, but has to scan the RMP at least
	   once. Remote CPUs get a free ride on the first CPU's scan.
	   Largest (middle-start) vs. (end-middle)/nr_cpus delta.
	3. RMPOPT best case. Everything is already optimized.

> Ideally we should be issuing RMPOPTs to only optimize the 1G regions that contained memory associated with that guest and that should be 
> significantly less than the whole 2TB RAM range. 
> 
> But that is something we planned for 1GB hugetlb guest_memfd support getting merged and which i believe has dependency on:
> 1). in-place conversion for guest_memfd, 
> 2). 2M hugepage support for guest_memfd and finally 
> 3). 1GB hugeTLB support for guest_memfd.

It's a no-brainer to do RMPOPT when you have 1GB pages around. You'll
see zero argument from me.

Doing things per-guest and for smaller pages gets a little bit harder to
reason about. In the end, this is all about trying to optimize against
the RMP table which is a global resource. It's going to get wonky if
RMPOPT is driven purely by guest-local data. There are lots of potential
pitfalls.

For now, let's just do it as simply as possible. Get maximum bang for
our buck with minimal data structures and see how that works out. It
might end up being a:

	queue_delayed_work()

to do some cleanup a few seconds out after each SNP guest terminates. If
a bunch of guests terminate all at once it'll at least only do a single
set of IPIs.

^ permalink raw reply

* Re: [PATCH v4 24/24] [NOT-FOR-REVIEW] x86/virt/seamldr: Save and restore current VMCS
From: Huang, Kai @ 2026-03-11 22:06 UTC (permalink / raw)
  To: kvm@vger.kernel.org, linux-coco@lists.linux.dev,
	linux-kernel@vger.kernel.org, Gao, Chao, x86@kernel.org
  Cc: dave.hansen@linux.intel.com, tony.lindgren@linux.intel.com,
	binbin.wu@linux.intel.com, seanjc@google.com, kas@kernel.org,
	Chatre, Reinette, Verma, Vishal L, nik.borisov@suse.com,
	mingo@redhat.com, Weiny, Ira, hpa@zytor.com, Annapurve, Vishal,
	sagis@google.com, Duan, Zhenzhong, Edgecombe, Rick P,
	paulmck@kernel.org, tglx@kernel.org, yilun.xu@linux.intel.com,
	Williams, Dan J, bp@alien8.de
In-Reply-To: <abFlCkNd7tqaUyAP@intel.com>


>  static const struct x86_cpu_id tdx_host_ids[] = {
> 	X86_MATCH_FEATURE(X86_FEATURE_TDX_HOST_PLATFORM, NULL),
> @@ -175,6 +177,7 @@ static int seamldr_init(struct device *dev)
>  {
> 	const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
> 	struct fw_upload *tdx_fwl;
> +	u64 basic_msr;
>  
> 	if (WARN_ON_ONCE(!tdx_sysinfo))
> 		return -EIO;
> @@ -182,6 +185,15 @@ static int seamldr_init(struct device *dev)
> 	if (!tdx_supports_runtime_update(tdx_sysinfo))
> 		return 0;
>  
> +	/*
> +	 * Some TDX-capable CPUs have an erratum where the current VMCS may
> +	 * be cleared after calling into P-SEAMLDR. Ensure no such erratum
> +	 * exists before exposing any P-SEAMLDR functions.
> +	 */
> +	rdmsrq(MSR_IA32_VMX_BASIC, basic_msr);
> +	if (!(basic_msr & VMX_BASIC_PRESERVE_CURRENT_VMCS))
> +		return 0;
> +

IIUC this silently disables runtime update and user won't be able to have
any clue to tell what went wrong (while the user can see the module supports
this feature and apparently the kernel should support it)?

Since we already have a X86_BUG_TDX_PW_MCE which is detected during kernel
boot in tdx_init(), shouldn't we just follow so that the user can at least
see the CPU has this erratum?

Another advantage is, if in the future some other kernel code needs to know
this erratum, it can just consult this flag.

And btw,

Which code base was this patch generated?  If I read correctly, in this
series seamldr_init() is a void function but doesn't return anything.

^ permalink raw reply

* Re: [PATCH v2 5/7] KVM: guest_memfd: Add cleanup interface for guest teardown
From: Kalra, Ashish @ 2026-03-11 21:49 UTC (permalink / raw)
  To: Ackerley Tng, tglx, mingo, bp, dave.hansen, x86, hpa, seanjc,
	peterz, thomas.lendacky, herbert, davem, ardb
  Cc: pbonzini, aik, Michael.Roth, KPrateek.Nayak, Tycho.Andersen,
	Nathan.Fontenot, jackyli, pgonda, rientjes, jacobhxu, xin,
	pawan.kumar.gupta, babu.moger, dyoung, nikunj, john.allen, darwi,
	linux-kernel, linux-crypto, kvm, linux-coco
In-Reply-To: <CAEvNRgGdaA1ynF8jxQDPh9U0U8Q0RkE0=KJx4FNrh_=+dVRaLQ@mail.gmail.com>

Hello Ackerley,

On 3/11/2026 1:00 AM, Ackerley Tng wrote:
> "Kalra, Ashish" <ashish.kalra@amd.com> writes:
> 
>> Hello Ackerley,
>>
>> On 3/9/2026 4:01 AM, Ackerley Tng wrote:
>>> Ashish Kalra <Ashish.Kalra@amd.com> writes:
>>>
>>>> From: Ashish Kalra <ashish.kalra@amd.com>
>>>>
>>>> Introduce kvm_arch_gmem_cleanup() to perform architecture-specific
>>>> cleanups when the last file descriptor for the guest_memfd inode is
>>>> closed. This typically occurs during guest shutdown and termination
>>>> and allows for final resource release.
>>>>
>>>> Signed-off-by: Ashish Kalra <ashish.kalra@amd.com>
>>>> ---
>>>>
>>>> [...snip...]
>>>>
>>>> diff --git a/virt/kvm/guest_memfd.c b/virt/kvm/guest_memfd.c
>>>> index 017d84a7adf3..2724dd1099f2 100644
>>>> --- a/virt/kvm/guest_memfd.c
>>>> +++ b/virt/kvm/guest_memfd.c
>>>> @@ -955,6 +955,14 @@ static void kvm_gmem_destroy_inode(struct inode *inode)
>>>>
>>>>  static void kvm_gmem_free_inode(struct inode *inode)
>>>>  {
>>>> +#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CLEANUP
>>>> +	/*
>>>> +	 * Finalize cleanup for the inode once the last guest_memfd
>>>> +	 * reference is released. This usually occurs after guest
>>>> +	 * termination.
>>>> +	 */
>>>> +	kvm_arch_gmem_cleanup();
>>>> +#endif
>>>
>>> Folks have already talked about the performance implications of doing
>>> the scan and rmpopt, I just want to call out that one VM could have more
>>> than one associated guest_memfd too.
>>
>> Yes, i have observed that kvm_gmem_free_inode() gets invoked multiple times
>> at SNP guest shutdown.
>>
>> And the same is true for kvm_gmem_destroy_inode() too.
>>
>>>
>>> I think the cleanup function should be thought of as cleanup for the
>>> inode (even if it doesn't take an inode pointer since it's not (yet)
>>> required).
>>>
>>> So, the gmem cleanup function should not handle deduplicating cleanup
>>> requests, but the arch function should, if the cleanup needs
>>> deduplicating.
>>
>> I agree, the arch function will have to handle deduplicating,  and for that
>> the arch function will probably need to be passed the inode pointer,
>> to have a parameter to assist with deduplicating.
>>
> 
> By the time .free_folio() is called, folio->mapping may no longer exist,
> so if we definitely want to deduplicate using something in the inode,
> .free_folio() won't be the right callback to use.

Ok.

> 
> I was thinking that deduplicating using something in the folio would be
> better. Can rmpopt take a PFN range? Then there's really no
> deduplication, the cleanup would be nicely narrowed to whatever was just
> freed. Perhaps the PFNs could be aligned up to the nearest PMD or PUD
> size for rmpopt to do the right thing.
> 

It will really be ideal if the cleanup can be narrowed down to whatever was just freed.

RMPOPT takes a SPA which is GB aligned, so if the PFNs are aligned to the nearest
PUD, then RMPOPT will be perfectly aligned to optimize the 1G regions that contained
memory associated with that guest being freed.

This will also be the most optimal way to use RMPOPT, as we only optimize the 1G regions
that contains memory associated with that guest, which should be much smaller than
optimizing the whole 2TB RAM. 

And that's what the actual plans for RMPOPT are.

We had planned for a phased RMPOPT implementation. 

In the first phase, we were planning to do RMP re-optimizations for entire 2TB
RAM. 

Once 1GB hugetlb guest_memfd support is merged, we planned to support re-enabling
RMPOPT optimizations during 1GB page cleanup as a follow-on series.

But i believe this support is dependent on:
1). in-place conversion for guest_memfd, 
2). 2M hugepage support for guest_memfd.

Another alternative we are considering is implementing a bitmap of 1GB regions in guest_memfd
that tracks when they are being freed and then issue RMPOPT on those 1GB regions.
(and this will be independent of the 1GB hugeTLB support for guest_memfd).

> Or perhaps some more tracking is required to check that the entire
> aligned range is freed before doing the rmpopt.
> 
> I need to implement some of this tracking for guest_memfd HugeTLB
> support, so if the tracking is useful for you, we should discuss!

Yes, this tracking is going to be useful for RMPOPT. 

Is this going to be implemented as part of the 1GB hugeTLB support for guest_memfd ?

> 
>>>
>>> Also, .free_inode() is called through RCU, so it could be called after
>>> some delay. Could it be possible that .free_inode() ends up being called
>>> way after the associated VM gets torn down, or after KVM the module gets
>>> unloaded?  Does rmpopt still work fine if KVM the module got unloaded?
>>
>> Yes, .free_inode() can probably get called after the associated VM has
>> been torn down and which should be fine for issuing RMPOPT to do
>> RMP re-optimizations.
>>
>> As far as about KVM module getting unloaded, then as part of the forthcoming patch-series,
>> during KVM module unload, X86_SNP_SHUTDOWN would be issued which means SNP would get
>> disabled and therefore, RMP checks are also disabled.
>>
>> And as CC_ATTR_HOST_SEV_SNP would then be cleared, therefore, snp_perform_rmp_optimization()
>> will simply return.
>>
> 
> I think relying on CC_ATTR_HOST_SEV_SNP to skip optimization should be
> best as long as there are no races (like the .free_inode() will
> definitely not try to optimize when SNP is half shut down or something
> like that.

Yeah, i will have to take a look at such races.

> 
>> Another option is to add a new guest_memfd superblock operation, and then do the
>> final guest_memfd cleanup using the .evict_inode() callback. This will then ensure
>> that the cleanup is not called through RCU and avoids any kind of delays, as following:
>>
>> +static void kvm_gmem_evict_inode(struct inode *inode)
>> +{
>> +#ifdef CONFIG_HAVE_KVM_ARCH_GMEM_CLEANUP
>> +        kvm_arch_gmem_cleanup();
>> +#endif
>> +       truncate_inode_pages_final(&inode->i_data);
>> +       clear_inode(inode);
>> +}
>> +
>>
> 
> At the point of .evict_inode(), CoCo-shared guest_memfd pages could
> still be pinned (for DMA or whatever, accidentally or maliciously), can
> rmpopt work on shared pages that might still be used for DMA?
> 

Yes, RMPOPT should be safe to work here, as it checks the RMP table for assigned
or private pages in the 1GB range specified. For a 1GB range full of shared pages,
it will mark that range to be RMP optimized.

If all RMPUPDATE's for all private->shared pages conversion have been completed at
the point of .evict_inode(), then RMPOPT re-optimizations will work nicely.

> .invalidate_folio() and .free_folio() both actually happen on removal
> from guest_memfd ownership, though both are not exactly when the folio
> is completely not in use.
> 
> Is the best time to optimize when the pages are truly freed?
> 

Yes.

Thanks,
Ashish

>> @@ -971,6 +979,7 @@ static const struct super_operations kvm_gmem_super_operations = {
>>         .alloc_inode    = kvm_gmem_alloc_inode,
>>         .destroy_inode  = kvm_gmem_destroy_inode,
>>         .free_inode     = kvm_gmem_free_inode,
>> +       .evict_inode    = kvm_gmem_evict_inode,
>>  };
>>
>>
>> Thanks,
>> Ashish
>>
>>>
>>> IIUC the current kmem_cache_free(kvm_gmem_inode_cachep, GMEM_I(inode));
>>> is fine because in kvm_gmem_exit(), there is a rcu_barrier() before
>>> kmem_cache_destroy(kvm_gmem_inode_cachep);.
>>>
>>>>  	kmem_cache_free(kvm_gmem_inode_cachep, GMEM_I(inode));
>>>>  }
>>>>
>>>> --
>>>> 2.43.0

^ permalink raw reply

* Re: [PATCH 0/7] KVM: x86: APX reg prep work
From: Paolo Bonzini @ 2026-03-11 19:01 UTC (permalink / raw)
  To: Sean Christopherson, Kiryl Shutsemau
  Cc: kvm, x86, linux-coco, linux-kernel, Chang S . Bae
In-Reply-To: <20260311003346.2626238-1-seanjc@google.com>

On 3/11/26 01:33, Sean Christopherson wrote:
> Clean up KVM's register tracking and storage in preparation for landing APX,
> which expands the maximum number of GPRs from 16 to 32.
> 
> This is kinda sorta an RFC, as there are some very opinionated changes.  I.e.
> if you dislike something, please speak up.
> 
> My thought is to treat R16-R31 as much like other GPRs as possible (though
> maybe we don't need to expand regs[] as sketched out in the last patch?).

The cleanups in patches 1-4 are nice.

For APX specifically, in abstract it's nice to treat R16-R31 as much as 
possible as regular GPRs.  On the other hand, the extra 16 regs[] 
entries would be more or less unused, the ugly switch statements 
wouldn't go away.  In other words, most of your remarks to Changseok's 
patches would remain...

Paolo


^ permalink raw reply

* Re: [PATCH v2 3/7] x86/sev: add support for RMPOPT instruction
From: Kalra, Ashish @ 2026-03-11 21:24 UTC (permalink / raw)
  To: Dave Hansen, Sean Christopherson
  Cc: tglx, mingo, bp, dave.hansen, x86, hpa, peterz, thomas.lendacky,
	herbert, davem, ardb, pbonzini, aik, Michael.Roth, KPrateek.Nayak,
	Tycho.Andersen, Nathan.Fontenot, jackyli, pgonda, rientjes,
	jacobhxu, xin, pawan.kumar.gupta, babu.moger, dyoung, nikunj,
	john.allen, darwi, linux-kernel, linux-crypto, kvm, linux-coco
In-Reply-To: <d7ba3790-a959-4150-87e0-c87dea4d09c5@intel.com>

Hello Dave and Sean,

On 3/5/2026 1:40 PM, Dave Hansen wrote:
> On 3/5/26 11:22, Kalra, Ashish wrote:
>> But, these are the performance numbers you should be considering : 
>>
>> RMPOPT during boot: 
>>
>> [   49.913402] SEV-SNP: RMPOPT largest cycles 1143020
>> [   49.913407] SEV-SNP: RMPOPT smallest cycles 60
>> [   49.913408] SEV-SNP: RMPOPT average cycles 5226
>>
>> RMPOPT after SNP guest shutdown: 
>>
>> [  276.435091] SEV-SNP: RMPOPT largest cycles 83680
>> [  276.435096] SEV-SNP: RMPOPT smallest cycles 60
>> [  276.435097] SEV-SNP: RMPOPT average cycles 5658
> 
> First of all, I'd really appreciate wall clock measurements on these.
> It's just less math and guesswork. Cycles are easy to measure but hard
> to read. Please make these easier to read. Also, the per-RMPOPT numbers
> don't mean much. You have to scale it by the number of CPUs and memory
> (or 2TB) to get to a real, useful number.
> 
> The thing that matters is how long this loop takes:
> 
> 	for (pa = pa_start; pa < pa_end; pa += PUD_SIZE)
> 
> and *especially* how long it takes per-cpu and when the system has a
> full 2TB load of memory.
> 
> That will tell us how many resources this RMPOPT thing is going to take,
> which is the _real_ thing we need to know.
> 
> Also, to some degree, the thing we care about here the *most* is the
> worst case scenario. I think the worst possible case is that there's one
> 4k private page in each 1GB of memory, and that it's the last 4k page.
> I'd like to see numbers for something close to *that*, not when there
> are no private pages.
> 
> The two things you measured above are interesting, but they're only part
> of the story.
> 

Here is the concerned performance data:

All these measurements are done with 2TB RAM installed on the server:

$ free -h
               total        used        free      shared  buff/cache   available
Mem:           2.0Ti        13Gi       1.9Ti       8.8Mi       1.6Gi       1.9Ti
Swap:          2.0Gi          0B       2.0Gi


For the loop executing RMPOPT on up-to 2TB of RAM on all CPUs: 

                ..
                start = ktime_get();
               
                for (pa = pa_start; pa < pa_end; pa += PUD_SIZE) {
                        /* Bit zero passes the function to the RMPOPT instruction. */
                        on_each_cpu_mask(cpu_online_mask, rmpopt,
                                         (void *)(pa | RMPOPT_FUNC_VERIFY_AND_REPORT_STATUS),
                                         true);
                }
                end = ktime_get();

                elapsed_ns = ktime_to_ns(ktime_sub(end, start));
		...

There are 2 active SNP VMs here, with one SNP VM being terminated, the other SNP VM is still running, both VMs are configured with 100GB guest RAM: 

When this loop is executed when the SNP guest terminates:

[  232.789187] SEV-SNP: RMPOPT execution time 391609638 ns for physical address range 0x0000000000000000 - 0x0000020000000000 on all cpus -> ~391 ms

[  234.647462] SEV-SNP: RMPOPT execution time 457933019 ns for physical address range 0x0000000000000000 - 0x0000020000000000 on all cpus -> ~457 ms


Now, there are a couple of additional RMPOPT optimizations which can be applied to this loop : 

1). RMPOPT can skip the bulk of its work if another CPU has already optimized that region.
The optimal thing may be to optimize all memory on one CPU first, and then let all the others
run RMPOPT in parallel.

2). The other optimization being applied here is only executing RMPOPT on only thread per
core.

The code sequence being used here:

	...
        /* Only one thread per core needs to issue RMPOPT instruction */
        for_each_online_cpu(cpu) {
                if (!topology_is_primary_thread(cpu))
                        continue;

                cpumask_set_cpu(cpu, cpus);
        }

         while (!kthread_should_stop()) {
         	...
                start = ktime_get();
               
                /*
                 * RMPOPT is optimized to skip the bulk of its work if another CPU has already
                 * optimized that region. Optimize all memory on one CPU first, and then let all
                 * the others run RMPOPT in parallel.
                 */
                cpumask_clear_cpu(smp_processor_id(), cpus);

                /* current CPU */
                for (pa = pa_start; pa < pa_end; pa += PUD_SIZE)
                        rmpopt((void *)(pa | RMPOPT_FUNC_VERIFY_AND_REPORT_STATUS));

                for (pa = pa_start; pa < pa_end; pa += PUD_SIZE) {
                        /* Bit zero passes the function to the RMPOPT instruction. */
                        on_each_cpu_mask(cpus, rmpopt,
                                         (void *)(pa | RMPOPT_FUNC_VERIFY_AND_REPORT_STATUS),
                                         true);                       
                }
                end = ktime_get();

                elapsed_ns = ktime_to_ns(ktime_sub(end, start));
		...

With these optimizations applied:

When this loop is executed when an SNP guest terminates, again with 2 active SNP VMs with 100GB guest RAM:

[  363.926595] SEV-SNP: RMPOPT execution time 317016656 ns for physical address range 0x0000000000000000 - 0x0000020000000000 on all cpus -> ~317 ms

[  365.415243] SEV-SNP: RMPOPT execution time 369659769 ns for physical address range 0x0000000000000000 - 0x0000020000000000 on all cpus -> ~369 ms.

So, with these two optimizations applied, there is like a ~16-20% performance improvement (when SNP guest terminates) in the execution of this loop
which is executing RMPOPT on upto 2TB of RAM on all CPUs.

Any thoughts, feedback on the performance numbers ? 

Ideally we should be issuing RMPOPTs to only optimize the 1G regions that contained memory associated with that guest and that should be 
significantly less than the whole 2TB RAM range. 

But that is something we planned for 1GB hugetlb guest_memfd support getting merged and which i believe has dependency on:
1). in-place conversion for guest_memfd, 
2). 2M hugepage support for guest_memfd and finally 
3). 1GB hugeTLB support for guest_memfd.

The other alternative probably will be to use Dave's suggestions to loosely mirror the RMPOPT bitmap and
keep our own bitmap of 1GB regions that _need_ RMPOPT run on them and probably this bitmap lives in
guest_memfd and we track when they are being freed and then issue RMPOPT on those 1GB regions
(and this will be independent of the 1GB hugeTLB support for guest_memfd).

Thanks,
Ashish

^ permalink raw reply

* Re: [PATCH v12 06/46] arm64: RMI: Define the user ABI
From: Marc Zyngier @ 2026-03-11 19:10 UTC (permalink / raw)
  To: Steven Price
  Cc: kvm, kvmarm, Catalin Marinas, Will Deacon, James Morse,
	Oliver Upton, Suzuki K Poulose, Zenghui Yu, linux-arm-kernel,
	linux-kernel, Joey Gouly, Alexandru Elisei, Christoffer Dall,
	Fuad Tabba, linux-coco, Ganapatrao Kulkarni, Gavin Shan,
	Shanker Donthineni, Alper Gun, Aneesh Kumar K . V, Emi Kisanuki,
	Vishal Annapurve
In-Reply-To: <33053e22-6cc6-4d55-bc7f-01f873a15d28@arm.com>

On Mon, 02 Mar 2026 15:23:44 +0000,
Steven Price <steven.price@arm.com> wrote:
> 
> >> +  struct kvm_arm_rmi_populate {
> >> +	__u64 base;
> >> +	__u64 size;
> >> +	__u64 source_uaddr;
> >> +	__u32 flags;
> >> +	__u32 reserved;
> >> +  };
> >> +
> >> +Populate a region of protected address space by copying the data from the user
> >> +space pointer provided. This is only valid before any VCPUs have been run.
> >> +The ioctl might not populate the entire region and user space may have to
> >> +repeatedly call it (with updated pointers) to populate the entire region.
> > 
> > size as a __u64 is odd, as the return value from the ioctl is a signed
> > int. This implies that you can't really report how many bytes you have
> > copied.  Some form of consistency wouldn't hurt.
> 
> Good spot. In practice this works because >2GB in one operation is
> highly unlikely to be processed in one go. But I guess I'll change this
> to have an output size argument. I guess I could make the kernel update
> all of base,size,source_uaddr which would simplify user space.

In a conversation with Suzuki, I suggested that splice(2) could be a
nicer way to express this, and allow asynchronous use with io-uring.

After all, having a guestmem backend for CCA is not exactly
outlandish, and having a splice implementation realistic enough.

Thoughts?

	M.

-- 
Jazz isn't dead. It just smells funny.

^ permalink raw reply

* Re: [PATCH 4/7] KVM: x86: Add wrapper APIs to reset dirty/available register masks
From: Paolo Bonzini @ 2026-03-11 18:50 UTC (permalink / raw)
  To: Sean Christopherson, Yosry Ahmed
  Cc: Kiryl Shutsemau, kvm, x86, linux-coco, linux-kernel,
	Chang S . Bae
In-Reply-To: <abFulxXuRziXj039@google.com>

On 3/11/26 14:31, Sean Christopherson wrote:
>> Not closely following this series and don't know this code well, but
>> this API is very confusing for me tbh. Especially in comparison with
>> kvm_reset_dirty_registers().
>>
>> Maybe rename this to kvm_clear_available_registers(), and pass in a
>> "clear_mask", then reverse the polarity:
>>
>> vcpu->arch.regs_avail &= ~clear_mask;
> Oh, yeah, I can do something like that.  I originally misread the TDX code and
> thought it was explicitly setting regs_avail, and so came up with a roundabout
> name.  I didn't revisit the naming or the polarity of the param once I realized
> all callers could use the same scheme.
> 
> No small part of me is tempted to turn it into a straigh "set" though, unless I'm
> missing something, the whole &= business is an implementation quirk.

I like kvm_clear_available_registers() for this + removing the second 
argument completely for kvm_reset_dirty_registers().

Paolo


^ permalink raw reply

* Re: [PATCH 2/7] KVM: x86: Drop the "EX" part of "EXREG" to avoid collision with APX
From: Paolo Bonzini @ 2026-03-11 18:46 UTC (permalink / raw)
  To: Sean Christopherson, Kiryl Shutsemau
  Cc: kvm, x86, linux-coco, linux-kernel, Chang S . Bae
In-Reply-To: <20260311003346.2626238-3-seanjc@google.com>

On 3/11/26 01:33, Sean Christopherson wrote:
> Now that NR_VCPU_REGS is no longer a thing, drop the "EX" is for
> extended (or maybe extra?") prefix from non-GRP registers to avoid a
> collision with APX (Advanced Performance Extensions), which adds:
> 
>    16 additional general-purpose registers (GPRs) R16–R31, also referred
>    to as Extended GPRs (EGPRs)  in this document;

And also, now that RIP is effectively an EXREG.

Paolo


^ permalink raw reply

* Re: [PATCH 4/7] KVM: x86: Add wrapper APIs to reset dirty/available register masks
From: Yosry Ahmed @ 2026-03-11 18:28 UTC (permalink / raw)
  To: Sean Christopherson
  Cc: Paolo Bonzini, Kiryl Shutsemau, kvm, x86, linux-coco,
	linux-kernel, Chang S . Bae
In-Reply-To: <abFulxXuRziXj039@google.com>

On Wed, Mar 11, 2026 at 6:31 AM Sean Christopherson <seanjc@google.com> wrote:
>
> On Tue, Mar 10, 2026, Yosry Ahmed wrote:
> > On Tue, Mar 10, 2026 at 5:34 PM Sean Christopherson <seanjc@google.com> wrote:
> > >
> > > Add wrappers for setting regs_{avail,dirty} in anticipation of turning the
> > > fields into proper bitmaps, at which point direct writes won't work so
> > > well.
> > >
> > > Deliberately leave the initialization in kvm_arch_vcpu_create() as-is,
> > > because the regs_avail logic in particular is special in that it's the one
> > > and only place where KVM marks eagerly synchronized registers as available.
> > >
> > > No functional change intended.
> > >
> > > Signed-off-by: Sean Christopherson <seanjc@google.com>
> > > ---
> > >  arch/x86/kvm/kvm_cache_regs.h | 19 +++++++++++++++++++
> > >  arch/x86/kvm/svm/svm.c        |  4 ++--
> > >  arch/x86/kvm/vmx/nested.c     |  4 ++--
> > >  arch/x86/kvm/vmx/tdx.c        |  2 +-
> > >  arch/x86/kvm/vmx/vmx.c        |  4 ++--
> > >  5 files changed, 26 insertions(+), 7 deletions(-)
> > >
> > > diff --git a/arch/x86/kvm/kvm_cache_regs.h b/arch/x86/kvm/kvm_cache_regs.h
> > > index ac1f9867a234..94e31cf38cb8 100644
> > > --- a/arch/x86/kvm/kvm_cache_regs.h
> > > +++ b/arch/x86/kvm/kvm_cache_regs.h
> > > @@ -105,6 +105,25 @@ static __always_inline bool kvm_register_test_and_mark_available(struct kvm_vcpu
> > >         return arch___test_and_set_bit(reg, (unsigned long *)&vcpu->arch.regs_avail);
> > >  }
> > >
> > > +static __always_inline void kvm_reset_available_registers(struct kvm_vcpu *vcpu,
> > > +                                                         u32 available_mask)
> >
> > Not closely following this series and don't know this code well, but
> > this API is very confusing for me tbh. Especially in comparison with
> > kvm_reset_dirty_registers().
> >
> > Maybe rename this to kvm_clear_available_registers(), and pass in a
> > "clear_mask", then reverse the polarity:
> >
> > vcpu->arch.regs_avail &= ~clear_mask;
>
> Oh, yeah, I can do something like that.  I originally misread the TDX code and
> thought it was explicitly setting regs_avail, and so came up with a roundabout
> name.  I didn't revisit the naming or the polarity of the param once I realized
> all callers could use the same scheme.
>
> No small part of me is tempted to turn it into a straigh "set" though, unless I'm
> missing something, the whole &= business is an implementation quirk.

Not sure what you mean here, this (for example)?

vcpu->arch.regs_avail = ~SVM_REGS_LAZY_LOAD_SET;

Does this mean all other bits in regs_avail should already be set for
all users so the &= is unnecessary? Or it doesn't matter if they're
set or not?

^ permalink raw reply

* Re: [PATCH net-next v3 1/2] dma-mapping: introduce DMA_ATTR_CC_DECRYPTED for pre-decrypted memory
From: Jiri Pirko @ 2026-03-11 14:19 UTC (permalink / raw)
  To: Jason Gunthorpe
  Cc: Petr Tesarik, dri-devel, linaro-mm-sig, iommu, linux-media,
	sumit.semwal, benjamin.gaignard, Brian.Starkey, jstultz,
	tjmercier, christian.koenig, m.szyprowski, robin.murphy, leon,
	sean.anderson, catalin.marinas, aneesh.kumar, suzuki.poulose,
	steven.price, thomas.lendacky, john.allen, ashish.kalra,
	suravee.suthikulpanit, linux-coco
In-Reply-To: <20260309131736.GK1687929@ziepe.ca>

Mon, Mar 09, 2026 at 02:17:36PM +0100, jgg@ziepe.ca wrote:
>On Mon, Mar 09, 2026 at 01:56:10PM +0100, Petr Tesarik wrote:
>> I don't want to start a bikeshedding discussion, so if everyone else
>> likes this name, let's keep it. But maybe the "_CC" (meaning
>> Confidential Comptuing) is not necessary. IIUC it's the same concept as
>> set_page_encrypted(), set_page_decrypted(), which does not refer to
>> CoCo either.
>
>Frankly I hate that AMD got their "encrypted" "decrypted" naming baked
>into the CC related APIs.
>
>I'm not at all convinced that they "do not refer to CoCo" in the way
>Linux uses them and other arches absolutely make them 100% tied to coco.
>
>If we are going to bikeshed the name it should be DMA_ATTR_CC_SHARED

On the other hand, the encrypted/decrypted helpers could be always
renamed if it makes sense. Better to perhaps have DMA_ATTR_DECRYPTED to
have things consistently named now? If someone renames them all in the
future, so be it.

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox