* [RFC PATCH v4 07/16] coco: host: arm64: Add pdev stream key refresh and purge helpers
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:53 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Joerg Roedel, Jonathan Cameron,
Marc Zyngier, Nicolin Chen, Pranjal Shrivastava, Robin Murphy,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427085344.941627-1-aneesh.kumar@kernel.org>
Add RMI command wrappers for PDEV stream key refresh and key purge,
and plumb them into arm-cca host helper functions.
The new helpers follow the existing stream operation pattern: issue the
RMI command for the local and optional peer pdev, then run the shared
stream synchronization work before returning.
This prepares the arm-cca host code to refresh or purge stream keys
during later vdev and stream state transitions.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rmi_cmds.h | 24 +++++++++++++++++
drivers/virt/coco/arm-cca-host/rmi-da.c | 35 +++++++++++++++++++++++++
drivers/virt/coco/arm-cca-host/rmi-da.h | 4 +++
3 files changed, 63 insertions(+)
diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index 242ce2fac14e..03dffba763e1 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -925,4 +925,28 @@ static inline unsigned long rmi_vdev_lock(unsigned long rd,
return res.a0;
}
+static inline unsigned long rmi_pdev_stream_key_refresh(unsigned long pdev1_phys,
+ unsigned long pdev2_phys, unsigned long stream_handle)
+{
+
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_PDEV_STREAM_KEY_REFRESH, pdev1_phys,
+ pdev2_phys, stream_handle, &res);
+
+ return res.a0;
+}
+
+static inline unsigned long rmi_pdev_stream_key_purge(unsigned long pdev1_phys,
+ unsigned long pdev2_phys, unsigned long stream_handle)
+{
+
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_PDEV_STREAM_KEY_PURGE, pdev1_phys,
+ pdev2_phys, stream_handle, &res);
+
+ return res.a0;
+}
+
#endif /* __ASM_RMI_CMDS_H */
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
index 84f0b2211cd1..128079d5b993 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.c
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -1038,3 +1038,38 @@ void *cca_vdev_create(struct realm *realm, struct pci_dev *pdev,
err_out:
return ERR_PTR(ret);
}
+
+int cca_pdev_refresh_stream_key(struct pci_dev *pdev1,
+ struct pci_dev *pdev2, unsigned long stream_handle)
+{
+
+ phys_addr_t rmm_pdev2_phys = 0;
+ struct cca_host_pdev_dsc *pdev_dsc1 = to_cca_pdev_dsc(pdev1);
+
+ if (pdev2)
+ rmm_pdev2_phys = virt_to_phys(to_cca_pdev_dsc(pdev2)->rmm_pdev);
+
+ if (rmi_pdev_stream_key_refresh(virt_to_phys(pdev_dsc1->rmm_pdev),
+ rmm_pdev2_phys, stream_handle))
+ return -EIO;
+
+ return submit_stream_work(pdev1, pdev2, stream_handle);
+}
+
+
+int cca_pdev_purge_stream_key(struct pci_dev *pdev1,
+ struct pci_dev *pdev2, unsigned long stream_handle)
+{
+
+ phys_addr_t rmm_pdev2_phys = 0;
+ struct cca_host_pdev_dsc *pdev_dsc1 = to_cca_pdev_dsc(pdev1);
+
+ if (pdev2)
+ rmm_pdev2_phys = virt_to_phys(to_cca_pdev_dsc(pdev2)->rmm_pdev);
+
+ if (rmi_pdev_stream_key_purge(virt_to_phys(pdev_dsc1->rmm_pdev),
+ rmm_pdev2_phys, stream_handle))
+ return -EIO;
+
+ return submit_stream_work(pdev1, pdev2, stream_handle);
+}
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index cd13cbf650d5..d6cdbc638d6d 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -235,5 +235,9 @@ int cca_pdev_disconnect_stream(struct pci_dev *pdev1,
struct pci_dev *pdev2, unsigned long stream_handle);
void *cca_vdev_create(struct realm *realm, struct pci_dev *pdev,
struct pci_dev *pf0_dev, u32 guest_rid);
+int cca_pdev_refresh_stream_key(struct pci_dev *pdev1,
+ struct pci_dev *pdev2, unsigned long stream_handle);
+int cca_pdev_purge_stream_key(struct pci_dev *pdev1,
+ struct pci_dev *pdev2, unsigned long stream_handle);
#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 06/16] coco: host: arm64: Add support for RMM vdev objects
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:53 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Joerg Roedel, Jonathan Cameron,
Marc Zyngier, Nicolin Chen, Pranjal Shrivastava, Robin Murphy,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427085344.941627-1-aneesh.kumar@kernel.org>
An RMM vdev object represents the binding between a device function and
a Realm. For example, a vdev can represent a physical function of a PCIe
device or a virtual function of a multi-function PCIe device. Each vdev
is associated with one pdev.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rmi_cmds.h | 22 +++++
arch/arm64/include/asm/rmi_smc.h | 22 +++++
drivers/virt/coco/arm-cca-host/arm-cca.c | 27 +++++-
drivers/virt/coco/arm-cca-host/rmi-da.c | 104 +++++++++++++++++++++++
drivers/virt/coco/arm-cca-host/rmi-da.h | 2 +
5 files changed, 176 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index 2925abde3882..242ce2fac14e 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -903,4 +903,26 @@ static inline unsigned long rmi_vdev_abort(unsigned long vdev_phys)
return res.a0;
}
+static inline unsigned long rmi_vdev_create(unsigned long rd,
+ unsigned long pdev_phys, unsigned long vdev_phys,
+ unsigned long vdev_params_phys)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_VDEV_CREATE, rd, pdev_phys,
+ vdev_phys, vdev_params_phys, &res);
+
+ return res.a0;
+}
+
+static inline unsigned long rmi_vdev_lock(unsigned long rd,
+ unsigned long pdev_phys, unsigned long vdev_phys)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_VDEV_LOCK, rd, pdev_phys, vdev_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 72e4a53b74b0..d14d13a9f169 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -650,4 +650,26 @@ enum rmi_vdev_state {
RMI_VDEV_STARTED,
RMI_VDEV_ERROR,
};
+
+#define MAX_VDEV_ADDR_RANGE 8
+
+struct rmi_vdev_params {
+ union {
+ struct {
+ u64 flags;
+ u64 vdev_id;
+ u64 tdi_id;
+ u64 padding1;
+ u64 vsmmu_addr;
+ u64 vsid;
+ u64 num_addr_range;
+ };
+ u8 padding2[0x200];
+ };
+ union { /* 0x200 */
+ struct rmi_addr_range addr_range[MAX_VDEV_ADDR_RANGE];
+ u8 padding3[0x1000 - 0x200];
+ };
+};
+
#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 8b1182620872..5930a30dd16f 100644
--- a/drivers/virt/coco/arm-cca-host/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-host/arm-cca.c
@@ -12,7 +12,8 @@
#include <linux/vmalloc.h>
#include <linux/cleanup.h>
#include <linux/pci-doe.h>
-
+#include <linux/pci.h>
+#include <linux/kvm_host.h>
#include "rmi-da.h"
@@ -449,11 +450,35 @@ static void cca_tsm_disconnect(struct pci_dev *pdev)
}
}
+static struct pci_tdi *cca_tsm_bind(struct pci_dev *pdev, struct kvm *kvm, u32 tdi_id)
+{
+ void *rmm_vdev;
+ struct pci_dev *dsm_dev = pdev->tsm->dsm_dev;
+ struct realm *realm = &kvm->arch.realm;
+
+ struct cca_host_tdi *host_tdi __free(kfree) =
+ kzalloc(sizeof(struct cca_host_tdi), GFP_KERNEL);
+ if (!host_tdi)
+ return ERR_PTR(-ENOMEM);
+
+ pci_tsm_tdi_constructor(pdev, &host_tdi->tdi, kvm, tdi_id);
+ /* Assign the tdi such that vdev_create can use that to lookup */
+ pdev->tsm->tdi = &host_tdi->tdi;
+ rmm_vdev = cca_vdev_create(realm, pdev, dsm_dev, tdi_id);
+ if (IS_ERR_OR_NULL(rmm_vdev)) {
+ pdev->tsm->tdi = NULL;
+ return rmm_vdev;
+ }
+
+ return &no_free_ptr(host_tdi)->tdi;
+}
+
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,
+ .bind = cca_tsm_bind,
};
static void cca_link_tsm_remove(void *tsm_dev)
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
index d61c3191c038..84f0b2211cd1 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.c
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -934,3 +934,107 @@ int cca_pdev_disconnect_stream(struct pci_dev *pdev1,
return submit_stream_work(pdev1, pdev2, stream_handle);
}
+
+static unsigned long pci_get_tdi_id(struct pci_dev *pdev)
+{
+ /* requester segment is marked reserved. */
+ return pci_dev_id(pdev);
+}
+
+static void init_vdev_params_mmio_range(struct pci_dev *pdev,
+ struct rmi_vdev_params *params)
+{
+ int index = 0;
+
+ for (int i = 0; i < PCI_STD_NUM_BARS; i++) {
+ struct resource *res = &pdev->resource[i];
+
+ if (!(res->flags & IORESOURCE_MEM))
+ continue;
+
+ if (resource_size(res) == 0)
+ continue;
+
+ index = insert_addr_range_sorted(params->addr_range, index,
+ res->start, res->end + 1);
+ }
+
+ params->num_addr_range = index;
+}
+
+
+void *cca_vdev_create(struct realm *realm, struct pci_dev *pdev,
+ struct pci_dev *pf0_dev, u32 guest_rid)
+{
+ phys_addr_t rd_phys = virt_to_phys(realm->rd);
+ struct rmi_vdev_params *params = NULL;
+ struct cca_host_pdev_dsc *pdev_dsc;
+ struct cca_host_tdi *host_tdi;
+ phys_addr_t rmm_pdev_phys;
+ phys_addr_t rmm_vdev_phys;
+ bool should_free = true;
+ void *rmm_vdev;
+ int ret;
+
+ pdev_dsc = to_cca_pdev_dsc(pf0_dev);
+ if (!pdev_dsc->rmm_pdev) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ rmm_vdev = (void *)get_zeroed_page(GFP_KERNEL);
+ if (!rmm_vdev) {
+ ret = -ENOMEM;
+ goto err_out;
+ }
+
+ rmm_vdev_phys = virt_to_phys(rmm_vdev);
+ if (rmi_delegate_page(rmm_vdev_phys)) {
+ ret = -ENXIO;
+ goto err_granule_delegate;
+ }
+
+ params = (struct rmi_vdev_params *)get_zeroed_page(GFP_KERNEL);
+ if (!params) {
+ ret = -ENOMEM;
+ goto err_params_alloc;
+ }
+
+ params->flags = 0;
+ params->vdev_id = guest_rid;
+ params->tdi_id = pci_get_tdi_id(pdev);
+
+ init_vdev_params_mmio_range(pdev, params);
+
+ rmm_pdev_phys = virt_to_phys(pdev_dsc->rmm_pdev);
+ if (rmi_vdev_create(rd_phys, rmm_pdev_phys,
+ rmm_vdev_phys, virt_to_phys(params))) {
+ ret = -ENXIO;
+ goto err_vdev_create;
+ }
+
+ /* setup host_tdi before call to device communicate */
+ host_tdi = to_cca_host_tdi(pdev);
+ host_tdi->rmm_vdev = rmm_vdev;
+ host_tdi->realm = realm;
+
+ submit_vdev_state_transition_work(pdev, RMI_VDEV_UNLOCKED);
+
+ ret = rmi_vdev_lock(rd_phys, rmm_pdev_phys, rmm_vdev_phys);
+
+ submit_vdev_state_transition_work(pdev, RMI_VDEV_LOCKED);
+
+ free_page((unsigned long)params);
+ return rmm_vdev;
+
+err_vdev_create:
+ free_page((unsigned long)params);
+err_params_alloc:
+ if (rmi_undelegate_page(rmm_vdev_phys))
+ should_free = false;
+err_granule_delegate:
+ if (should_free)
+ free_page((unsigned long)rmm_vdev);
+err_out:
+ return ERR_PTR(ret);
+}
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index 88fa428f788e..cd13cbf650d5 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -233,5 +233,7 @@ int cca_pdev_stream_connect(struct pci_dev *pdev1, struct pci_dev *pdev2,
unsigned long *stream_handle);
int cca_pdev_disconnect_stream(struct pci_dev *pdev1,
struct pci_dev *pdev2, unsigned long stream_handle);
+void *cca_vdev_create(struct realm *realm, struct pci_dev *pdev,
+ struct pci_dev *pf0_dev, u32 guest_rid);
#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 05/16] coco: host: arm64: Add support for virtual device communication
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:53 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Joerg Roedel, Jonathan Cameron,
Marc Zyngier, Nicolin Chen, Pranjal Shrivastava, Robin Murphy,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427085344.941627-1-aneesh.kumar@kernel.org>
Add support for vdev_communicate with RMM.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rmi_cmds.h | 32 ++++++++++
arch/arm64/include/asm/rmi_smc.h | 7 +++
drivers/virt/coco/arm-cca-host/rmi-da.c | 83 ++++++++++++++++++++++---
drivers/virt/coco/arm-cca-host/rmi-da.h | 20 ++++++
4 files changed, 135 insertions(+), 7 deletions(-)
diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index 205fc200d1db..2925abde3882 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -871,4 +871,36 @@ int rmi_psmmu_st_l2_create(unsigned long psmmu_phys,
int rmi_psmmu_st_l2_destroy(unsigned long psmmu_phys,
unsigned long stream_id, unsigned long *rmi_ret);
+static inline unsigned long rmi_vdev_communicate(unsigned long rd_phys,
+ unsigned long pdev_phys, unsigned long vdev_phys,
+ unsigned long vdev_comm_data_phys)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_VDEV_COMMUNICATE, rd_phys, pdev_phys,
+ vdev_phys, vdev_comm_data_phys, &res);
+
+ return res.a0;
+}
+
+static inline unsigned long rmi_vdev_get_state(unsigned long vdev_phys,
+ enum rmi_vdev_state *state)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_VDEV_GET_STATE, vdev_phys, &res);
+
+ *state = res.a1;
+ return res.a0;
+}
+
+static inline unsigned long rmi_vdev_abort(unsigned long vdev_phys)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_VDEV_ABORT, vdev_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 5b540d25914e..72e4a53b74b0 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -643,4 +643,11 @@ struct rmi_psmmu_params {
};
};
+enum rmi_vdev_state {
+ RMI_VDEV_NEW,
+ RMI_VDEV_UNLOCKED,
+ RMI_VDEV_LOCKED,
+ RMI_VDEV_STARTED,
+ RMI_VDEV_ERROR,
+};
#endif /* __ASM_RMI_SMC_H */
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
index 33a2551fd09f..d61c3191c038 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.c
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -11,6 +11,8 @@
#include <crypto/internal/rsa.h>
#include <keys/asymmetric-type.h>
#include <keys/x509-parser.h>
+#include <linux/kvm_types.h>
+#include <asm/kvm_rmi.h>
#include "rmi-da.h"
@@ -217,6 +219,7 @@ static int _do_dev_communicate(enum dev_comm_type type, struct pci_tsm *tsm, int
gfp_t cache_alloc_flags;
int nbytes, cp_len;
struct cache_object **cache_objp, *cache_obj;
+ struct cca_host_tdi *host_tdi = to_cca_host_tdi(tsm->pdev);
struct cca_host_pdev_dsc *pdev_dsc = to_cca_pdev_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;
@@ -228,7 +231,10 @@ static int _do_dev_communicate(enum dev_comm_type type, struct pci_tsm *tsm, int
rmi_ret = rmi_pdev_communicate(virt_to_phys(pdev_dsc->rmm_pdev),
virt_to_phys(comm_data->io_params));
else
- rmi_ret = RMI_ERROR_INPUT;
+ rmi_ret = rmi_vdev_communicate(virt_to_phys(host_tdi->realm->rd),
+ virt_to_phys(pdev_dsc->rmm_pdev),
+ virt_to_phys(host_tdi->rmm_vdev),
+ virt_to_phys(comm_data->io_params));
if (rmi_ret != RMI_SUCCESS) {
if (rmi_ret == RMI_BUSY)
return -EBUSY;
@@ -252,6 +258,12 @@ static int _do_dev_communicate(enum dev_comm_type type, struct pci_tsm *tsm, int
case RMI_DEV_CERTIFICATE:
cache_objp = &pf0_ep_dsc->cert_chain.cache;
break;
+ case RMI_DEV_INTERFACE_REPORT:
+ cache_objp = &host_tdi->interface_report;
+ break;
+ case RMI_DEV_MEASUREMENTS:
+ cache_objp = &host_tdi->measurements;
+ break;
default:
return -EINVAL;
}
@@ -355,9 +367,11 @@ static int _do_dev_communicate(enum dev_comm_type type, struct pci_tsm *tsm, int
static int do_dev_communicate(enum dev_comm_type type,
struct pci_tsm *tsm, unsigned long error_state, int *stream_wait)
{
- int ret, state = error_state;
+ int ret, state;
+ unsigned long rmi_ret;
struct rmi_dev_comm_enter *io_enter;
struct cca_host_pdev_dsc *pdev_dsc = to_cca_pdev_dsc(tsm->dsm_dev);
+ struct cca_host_tdi *host_tdi = to_cca_host_tdi(tsm->pdev);
io_enter = &pdev_dsc->comm_data.io_params->enter;
io_enter->resp_len = 0;
@@ -369,16 +383,23 @@ static int do_dev_communicate(enum dev_comm_type type,
if (ret) {
if (type == PDEV_COMMUNICATE)
rmi_pdev_abort(virt_to_phys(pdev_dsc->rmm_pdev));
+ else
+ rmi_vdev_abort(virt_to_phys(host_tdi->rmm_vdev));
+
+ state = error_state;
} 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(pdev_dsc->rmm_pdev),
- (enum rmi_pdev_state *)&state))
- state = error_state;
- }
+ if (type == PDEV_COMMUNICATE)
+ rmi_ret = rmi_pdev_get_state(virt_to_phys(pdev_dsc->rmm_pdev),
+ (enum rmi_pdev_state *)&state);
+ else
+ rmi_ret = rmi_vdev_get_state(virt_to_phys(host_tdi->rmm_vdev),
+ (enum rmi_vdev_state *)&state);
+ if (rmi_ret)
+ state = error_state;
}
if (state == error_state)
@@ -408,6 +429,11 @@ static int wait_for_pdev_state(struct pci_tsm *tsm, enum rmi_pdev_state target_s
return wait_for_dev_state(PDEV_COMMUNICATE, tsm, target_state, RMI_PDEV_ERROR);
}
+static int wait_for_vdev_state(struct pci_tsm *tsm, enum rmi_vdev_state target_state)
+{
+ return wait_for_dev_state(VDEV_COMMUNICATE, tsm, target_state, RMI_VDEV_ERROR);
+}
+
static int parse_certificate_chain(struct pci_tsm *tsm)
{
struct cca_host_pf0_ep_dsc *pf0_ep_dsc;
@@ -603,6 +629,49 @@ static int submit_pdev_state_transition_work(struct pci_dev *pdev,
return 0;
}
+static void vdev_state_transition_workfn(struct work_struct *work)
+{
+ unsigned long state;
+ struct pci_tsm *tsm;
+ struct dev_comm_work *setup_work;
+ struct cca_host_pdev_dsc *pdev_dsc;
+
+ setup_work = container_of(work, struct dev_comm_work, work);
+ tsm = setup_work->tsm;
+
+ pdev_dsc = to_cca_pdev_dsc(tsm->dsm_dev);
+ guard(mutex)(&pdev_dsc->object_lock);
+
+ state = wait_for_vdev_state(tsm, setup_work->target_state);
+ WARN_ON(state != setup_work->target_state);
+}
+
+static int __maybe_unused submit_vdev_state_transition_work(struct pci_dev *pdev, int target_state)
+{
+ enum rmi_vdev_state state;
+ struct dev_comm_work comm_work;
+ struct cca_host_comm_data *comm_data = to_cca_comm_data(pdev);
+ struct cca_host_tdi *host_tdi = to_cca_host_tdi(pdev);
+
+ INIT_WORK_ONSTACK(&comm_work.work, vdev_state_transition_workfn);
+ comm_work.tsm = pdev->tsm;
+ comm_work.target_state = target_state;
+
+ queue_work(comm_data->work_queue, &comm_work.work);
+
+ flush_work(&comm_work.work);
+ destroy_work_on_stack(&comm_work.work);
+
+ /* check if we reached target state */
+ if (rmi_vdev_get_state(virt_to_phys(host_tdi->rmm_vdev), &state))
+ return -ENXIO;
+
+ if (state != target_state)
+ /* Protocol didn't take it to expected target state */
+ return -EPROTO;
+ return 0;
+}
+
static void pdev_collect_identity_workfn(struct work_struct *work)
{
struct pci_tsm *tsm;
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index 798a8ed7505f..88fa428f788e 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -116,6 +116,16 @@ struct cca_host_fn_dsc {
enum dev_comm_type {
PDEV_COMMUNICATE = 0x1,
+ VDEV_COMMUNICATE = 0x2,
+};
+
+struct cca_host_tdi {
+ struct pci_tdi tdi;
+ struct realm *realm;
+ void *rmm_vdev;
+ /* protected by cca_host_pdev_dsc.object_lock */
+ struct cache_object *interface_report;
+ struct cache_object *measurements;
};
static inline int insert_addr_range_sorted(struct rmi_addr_range *addr_range,
@@ -203,6 +213,16 @@ static inline struct cca_host_comm_data *to_cca_comm_data(struct pci_dev *pdev)
return NULL;
}
+static inline struct cca_host_tdi *to_cca_host_tdi(struct pci_dev *pdev)
+{
+ struct pci_tsm *tsm = pdev->tsm;
+
+ if (!tsm || !tsm->tdi)
+ return NULL;
+
+ return container_of(tsm->tdi, struct cca_host_tdi, tdi);
+}
+
int cca_pdev_create(struct pci_dev *pdev);
int cca_pdev_collect_identity(struct pci_dev *pdev);
bool cca_pdev_needs_key(struct pci_dev *pdev);
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 04/16] iommu/arm-smmu-v3: Track realm pSMMU users with refcount_t
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:53 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Joerg Roedel, Jonathan Cameron,
Marc Zyngier, Nicolin Chen, Pranjal Shrivastava, Robin Murphy,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427085344.941627-1-aneesh.kumar@kernel.org>
Replace the realm pSMMU active-state boolean with a refcount so
activation/deactivation is tied to actual Realm vIOMMU lifetime.
- add realm_mutex and realm_users (refcount_t) to struct arm_smmu_device
- on first Realm init, activate pSMMU and set realm_users to 1
- on subsequent Realm inits, increment realm_users
- on Realm viommu destroy, decrement realm_users and call
rmi_psmmu_deactivate() when the last user drops the count to 0
This removes duplicated state tracking and ensures pSMMU is deactivated only
after the last Realm user is gone.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rmi_cmds.h | 1 +
arch/arm64/kernel/rmi.c | 12 ++++++
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c | 39 +++++++++++++++----
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 3 +-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 4 +-
5 files changed, 50 insertions(+), 9 deletions(-)
diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index 659d68ad5f1d..205fc200d1db 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -865,6 +865,7 @@ static inline unsigned long rmi_psmmu_event_consume(unsigned long psmmu_phys,
int rmi_psmmu_activate(unsigned long psmmu_phys,
unsigned long psmmu_params_phys, unsigned long *rmi_ret);
+int rmi_psmmu_deactivate(unsigned long psmmu_phys, unsigned long *rmi_ret);
int rmi_psmmu_st_l2_create(unsigned long psmmu_phys,
unsigned long stream_id, unsigned long *rmi_ret);
int rmi_psmmu_st_l2_destroy(unsigned long psmmu_phys,
diff --git a/arch/arm64/kernel/rmi.c b/arch/arm64/kernel/rmi.c
index cc4050db5a6a..884ab3f99f2f 100644
--- a/arch/arm64/kernel/rmi.c
+++ b/arch/arm64/kernel/rmi.c
@@ -436,6 +436,18 @@ int rmi_psmmu_activate(unsigned long psmmu_phys,
return 0;
}
+int rmi_psmmu_deactivate(unsigned long psmmu_phys, unsigned long *rmi_ret)
+{
+ struct rmi_sro_state *sro __free(sro) =
+ rmi_sro_init(SMC_RMI_PSMMU_DEACTIVATE, psmmu_phys);
+ if (!sro)
+ return -ENOMEM;
+
+ *rmi_ret = rmi_sro_execute(sro);
+
+ return 0;
+}
+
int rmi_psmmu_st_l2_create(unsigned long psmmu_phys,
unsigned long stream_id, unsigned long *rmi_ret)
{
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
index 6f8de7cead9d..dfff493f96d0 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
@@ -124,9 +124,25 @@ void arm_smmu_setup_realm_irqs(struct arm_smmu_device *smmu)
}
}
+static bool arm_realm_smmu_active(struct arm_smmu_device *smmu)
+{
+ lockdep_assert_held(&smmu->realm_mutex);
+ return refcount_read(&smmu->realm_users) > 0;
+}
+
static void arm_realm_smmu_v3_destroy(struct iommufd_viommu *viommu)
{
- /* When we add refcount psmmu deactivate here. */
+ unsigned long rmi_ret;
+ struct arm_smmu_device *smmu =
+ container_of(viommu->iommu_dev, struct arm_smmu_device, iommu);
+
+ guard(mutex)(&smmu->realm_mutex);
+ if (WARN_ON(!arm_realm_smmu_active(smmu)))
+ return;
+
+ if (refcount_dec_and_test(&smmu->realm_users) &&
+ (rmi_psmmu_deactivate(smmu->base_phys, &rmi_ret) || rmi_ret))
+ dev_warn(smmu->dev, "failed to deactivate realm pSMMU\n");
}
static void arm_realm_smmu_v3_vdevice_destroy(struct iommufd_vdevice *vdev)
@@ -140,7 +156,8 @@ static void arm_realm_smmu_v3_vdevice_destroy(struct iommufd_vdevice *vdev)
unsigned long rmi_ret = 0;
int ret;
- if (!smmu->realm_initialized)
+ guard(mutex)(&smmu->realm_mutex);
+ if (!arm_realm_smmu_active(smmu))
return;
ret = rmi_psmmu_st_l2_destroy(smmu->base_phys,
@@ -168,7 +185,8 @@ static int arm_realm_smmu_v3_vdevice_init(struct iommufd_vdevice *vdev)
unsigned long rmi_ret = 0;
int ret;
- if (!smmu->realm_initialized)
+ guard(mutex)(&smmu->realm_mutex);
+ if (!arm_realm_smmu_active(smmu))
return -EINVAL;
ret = rmi_psmmu_st_l2_create(smmu->base_phys,
@@ -229,12 +247,17 @@ int arm_realm_smmu_v3_init(struct iommufd_viommu *viommu,
if (!(smmu->features & ARM_SMMU_FEAT_RME))
return -EOPNOTSUPP;
- if (smmu->realm_initialized)
+ mutex_lock(&smmu->realm_mutex);
+ if (arm_realm_smmu_active(smmu)) {
+ refcount_inc(&smmu->realm_users);
goto psmmu_already_active;
+ }
params = (struct rmi_psmmu_params *)get_zeroed_page(GFP_KERNEL);
- if (!params)
- return -ENOMEM;
+ if (!params) {
+ ret = -ENOMEM;
+ goto out_unlock;
+ }
/* No ATS and PRI support */
if (!(smmu->features & ARM_SMMU_FEAT_MSI))
@@ -261,12 +284,14 @@ int arm_realm_smmu_v3_init(struct iommufd_viommu *viommu,
dev_warn(smmu->dev, "failed to activate realm pSMMU\n");
ret = -EIO;
} else {
- smmu->realm_initialized = true;
+ refcount_set(&smmu->realm_users, 1);
}
out_free:
free_page((unsigned long)params);
psmmu_already_active:
if (!ret)
viommu->ops = &arm_realm_smmu_v3_ops;
+out_unlock:
+ mutex_unlock(&smmu->realm_mutex);
return ret;
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 1e3d4d682e32..e458c3818c34 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -3946,6 +3946,8 @@ static int arm_smmu_init_structures(struct arm_smmu_device *smmu)
int ret;
mutex_init(&smmu->streams_mutex);
+ mutex_init(&smmu->realm_mutex);
+ refcount_set(&smmu->realm_users, 0);
smmu->streams = RB_ROOT;
ret = arm_smmu_init_queues(smmu);
@@ -3956,7 +3958,6 @@ static int arm_smmu_init_structures(struct arm_smmu_device *smmu)
if (ret)
return ret;
- smmu->realm_initialized = false;
if (smmu->impl_ops && smmu->impl_ops->init_structures)
return smmu->impl_ops->init_structures(smmu);
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index d528b3212d38..b5d0e1341236 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -13,6 +13,7 @@
#include <linux/iommufd.h>
#include <linux/kernel.h>
#include <linux/mmzone.h>
+#include <linux/refcount.h>
#include <linux/sizes.h>
struct arm_smmu_device;
@@ -812,7 +813,8 @@ struct arm_smmu_device {
struct rb_root streams;
struct mutex streams_mutex;
- bool realm_initialized;
+ struct mutex realm_mutex;
+ refcount_t realm_users;
};
struct arm_smmu_stream {
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 03/16] iommu/arm-smmu-v3: Add initial pSMMU realm viommu plumbing
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:53 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Joerg Roedel, Jonathan Cameron,
Marc Zyngier, Nicolin Chen, Pranjal Shrivastava, Robin Murphy,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427085344.941627-1-aneesh.kumar@kernel.org>
Add initial plumbing for Realm pSMMU integration in the arm-smmu-v3 iommufd
path.
Changes include:
- add RMI SMC IDs and helper wrappers for pSMMU activate and ST_L2 create/destroy
- add RMI pSMMU parameter structure definitions
- add IOMMU_VIOMMU_TYPE_ARM_REALM_SMMUV3 UAPI/internal type support
- add arm-smmu-v3 realm viommu init/vdevice hooks
- store SMMU MMIO physical base and realm initialization state in arm_smmu_device
This enables basic realm pSMMU setup and vdevice stream-table operations.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rmi_cmds.h | 7 +
arch/arm64/include/asm/rmi_smc.h | 18 +++
arch/arm64/kernel/rmi.c | 39 +++++
.../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 7 +
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c | 148 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 1 +
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 4 +
include/uapi/linux/iommufd.h | 1 +
8 files changed, 225 insertions(+)
diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index 75eb59d4fa84..659d68ad5f1d 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -863,4 +863,11 @@ static inline unsigned long rmi_psmmu_event_consume(unsigned long psmmu_phys,
return res.a0;
}
+int rmi_psmmu_activate(unsigned long psmmu_phys,
+ unsigned long psmmu_params_phys, unsigned long *rmi_ret);
+int rmi_psmmu_st_l2_create(unsigned long psmmu_phys,
+ unsigned long stream_id, unsigned long *rmi_ret);
+int rmi_psmmu_st_l2_destroy(unsigned long psmmu_phys,
+ unsigned long stream_id, unsigned long *rmi_ret);
+
#endif /* __ASM_RMI_CMDS_H */
diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
index be1b1e95a937..5b540d25914e 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -625,4 +625,22 @@ struct rmi_psmmu_info {
};
};
+#define RMI_PSMMU_FLAG_MSI BIT(0)
+#define RMI_PSMMU_FLAG_ATS BIT(1)
+#define RMI_PSMMU_FLAG_PRI BIT(2)
+struct rmi_psmmu_params {
+ union {
+ struct {
+ u64 flags;
+ u64 grr_addr;
+ u64 grr_data;
+ u64 eventq_addr;
+ u64 eventq_data;
+ u64 priq_addr;
+ u64 priq_data;
+ };
+ u8 padding5[0x1000];
+ };
+};
+
#endif /* __ASM_RMI_SMC_H */
diff --git a/arch/arm64/kernel/rmi.c b/arch/arm64/kernel/rmi.c
index da4707981548..cc4050db5a6a 100644
--- a/arch/arm64/kernel/rmi.c
+++ b/arch/arm64/kernel/rmi.c
@@ -423,6 +423,45 @@ unsigned long rmi_sro_execute(struct rmi_sro_state *sro)
return regs.a0;
}
+int rmi_psmmu_activate(unsigned long psmmu_phys,
+ unsigned long psmmu_params_phys, unsigned long *rmi_ret)
+{
+ struct rmi_sro_state *sro __free(sro) =
+ rmi_sro_init(SMC_RMI_PSMMU_ACTIVATE, psmmu_phys, psmmu_params_phys);
+ if (!sro)
+ return -ENOMEM;
+
+ *rmi_ret = rmi_sro_execute(sro);
+
+ return 0;
+}
+
+int rmi_psmmu_st_l2_create(unsigned long psmmu_phys,
+ unsigned long stream_id, unsigned long *rmi_ret)
+{
+ struct rmi_sro_state *sro __free(sro) =
+ rmi_sro_init(SMC_RMI_PSMMU_ST_L2_CREATE, psmmu_phys, stream_id);
+ if (!sro)
+ return -ENOMEM;
+
+ *rmi_ret = rmi_sro_execute(sro);
+
+ return 0;
+}
+
+int rmi_psmmu_st_l2_destroy(unsigned long psmmu_phys,
+ unsigned long stream_id, unsigned long *rmi_ret)
+{
+ struct rmi_sro_state *sro __free(sro) =
+ rmi_sro_init(SMC_RMI_PSMMU_ST_L2_DESTROY, psmmu_phys, stream_id);
+ if (!sro)
+ return -ENOMEM;
+
+ *rmi_ret = rmi_sro_execute(sro);
+
+ return 0;
+}
+
static int rmi_configure(void)
{
struct rmm_config *config __free(free_page) = NULL;
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
index ddae0b07c76b..c98e91b3ca13 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
@@ -437,6 +437,9 @@ size_t arm_smmu_get_viommu_size(struct device *dev,
if (viommu_type == IOMMU_VIOMMU_TYPE_ARM_SMMUV3)
return VIOMMU_STRUCT_SIZE(struct arm_vsmmu, core);
+ if (viommu_type == IOMMU_VIOMMU_TYPE_ARM_REALM_SMMUV3)
+ return VIOMMU_STRUCT_SIZE(struct arm_vsmmu, core);
+
if (!smmu->impl_ops || !smmu->impl_ops->get_viommu_size)
return 0;
return smmu->impl_ops->get_viommu_size(viommu_type);
@@ -464,6 +467,10 @@ int arm_vsmmu_init(struct iommufd_viommu *viommu,
return 0;
}
+ if (viommu->type == IOMMU_VIOMMU_TYPE_ARM_REALM_SMMUV3)
+ return arm_realm_smmu_v3_init(viommu, user_data);
+
+
return smmu->impl_ops->vsmmu_init(vsmmu, user_data);
}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
index fec1a32de53c..6f8de7cead9d 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
@@ -6,6 +6,7 @@
#include <linux/interrupt.h>
#include <asm/rmi_smc.h>
#include <asm/rmi_cmds.h>
+#include <asm/kvm_emulate.h>
#include "arm-smmu-v3.h"
@@ -122,3 +123,150 @@ void arm_smmu_setup_realm_irqs(struct arm_smmu_device *smmu)
}
}
}
+
+static void arm_realm_smmu_v3_destroy(struct iommufd_viommu *viommu)
+{
+ /* When we add refcount psmmu deactivate here. */
+}
+
+static void arm_realm_smmu_v3_vdevice_destroy(struct iommufd_vdevice *vdev)
+{
+ struct device *dev = iommufd_vdevice_to_device(vdev);
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ /* FIXME which stream to pick */
+ /* At this moment, iommufd only supports PCI device that has one SID */
+ struct arm_smmu_stream *stream = &master->streams[0];
+ struct arm_smmu_device *smmu = master->smmu;
+ unsigned long rmi_ret = 0;
+ int ret;
+
+ if (!smmu->realm_initialized)
+ return;
+
+ ret = rmi_psmmu_st_l2_destroy(smmu->base_phys,
+ ALIGN_DOWN(stream->id, STRTAB_NUM_L2_STES),
+ &rmi_ret);
+ if (ret || rmi_ret) {
+
+ /* Table in use */
+ if (RMI_RETURN_STATUS(rmi_ret) == RMI_ERROR_PSMMU_ST &&
+ RMI_RETURN_INDEX(rmi_ret) == 2)
+ return;
+
+ dev_warn(dev, "failed to destroy realm stream mapping\n");
+ }
+}
+
+static int arm_realm_smmu_v3_vdevice_init(struct iommufd_vdevice *vdev)
+{
+ struct device *dev = iommufd_vdevice_to_device(vdev);
+ struct arm_smmu_master *master = dev_iommu_priv_get(dev);
+ // fixme which stream to pick
+ /* At this moment, iommufd only supports PCI device that has one SID */
+ struct arm_smmu_stream *stream = &master->streams[0];
+ struct arm_smmu_device *smmu = master->smmu;
+ unsigned long rmi_ret = 0;
+ int ret;
+
+ if (!smmu->realm_initialized)
+ return -EINVAL;
+
+ ret = rmi_psmmu_st_l2_create(smmu->base_phys,
+ ALIGN_DOWN(stream->id, STRTAB_NUM_L2_STES),
+ &rmi_ret);
+ if (ret || rmi_ret) {
+ if (!ret)
+ return -EIO;
+ if (RMI_RETURN_STATUS(rmi_ret) == RMI_ERROR_PSMMU_ST &&
+ RMI_RETURN_INDEX(rmi_ret) == 2) {
+ /* table already exist */
+ vdev->destroy = arm_realm_smmu_v3_vdevice_destroy;
+ return 0;
+ }
+ dev_warn(dev, "failed to create realm stream mapping\n");
+ return -EIO;
+ }
+ vdev->destroy = arm_realm_smmu_v3_vdevice_destroy;
+ return 0;
+}
+
+static const struct iommufd_viommu_ops arm_realm_smmu_v3_ops = {
+ .destroy = arm_realm_smmu_v3_destroy,
+ .alloc_domain_nested = arm_vsmmu_alloc_domain_nested,
+ .cache_invalidate = arm_vsmmu_cache_invalidate,
+ .vdevice_init = arm_realm_smmu_v3_vdevice_init,
+};
+
+static int get_irq_data(int irq, u64 *msi_addr, u64 *msi_data)
+{
+ struct msi_desc *desc;
+
+ desc = irq_get_msi_desc(irq);
+ if (!desc)
+ return -EINVAL;
+
+ *msi_addr = (((u64)desc->msg.address_hi) << 32) | desc->msg.address_lo;
+ *msi_data = desc->msg.data;
+ return 0;
+}
+
+int arm_realm_smmu_v3_init(struct iommufd_viommu *viommu,
+ const struct iommu_user_data *user_data)
+{
+ int ret = 0;
+ struct kvm *kvm = viommu->kvm;
+ struct rmi_psmmu_params *params;
+ struct arm_smmu_device *smmu =
+ container_of(viommu->iommu_dev, struct arm_smmu_device, iommu);
+ unsigned long rmi_ret;
+
+ if (!kvm)
+ return -EINVAL;
+
+ if (!kvm_is_realm(kvm))
+ return -EINVAL;
+
+ if (!(smmu->features & ARM_SMMU_FEAT_RME))
+ return -EOPNOTSUPP;
+
+ if (smmu->realm_initialized)
+ goto psmmu_already_active;
+
+ params = (struct rmi_psmmu_params *)get_zeroed_page(GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ /* No ATS and PRI support */
+ if (!(smmu->features & ARM_SMMU_FEAT_MSI))
+ goto psmmu_activate;
+
+ params->flags = RMI_PSMMU_FLAG_MSI;
+ if (get_irq_data(smmu->realm_gerr_irq,
+ ¶ms->grr_addr, ¶ms->grr_data)) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+ if (get_irq_data(smmu->realm_evtq_irq,
+ ¶ms->eventq_addr, ¶ms->eventq_data)) {
+ ret = -EINVAL;
+ goto out_free;
+ }
+
+psmmu_activate:
+ ret = rmi_psmmu_activate(smmu->base_phys, virt_to_phys(params),
+ &rmi_ret);
+ if (ret || rmi_ret) {
+ if (!ret)
+ ret = -EIO;
+ dev_warn(smmu->dev, "failed to activate realm pSMMU\n");
+ ret = -EIO;
+ } else {
+ smmu->realm_initialized = true;
+ }
+out_free:
+ free_page((unsigned long)params);
+psmmu_already_active:
+ if (!ret)
+ viommu->ops = &arm_realm_smmu_v3_ops;
+ return ret;
+}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 17fd99887aab..1e3d4d682e32 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -3956,6 +3956,7 @@ static int arm_smmu_init_structures(struct arm_smmu_device *smmu)
if (ret)
return ret;
+ smmu->realm_initialized = false;
if (smmu->impl_ops && smmu->impl_ops->init_structures)
return smmu->impl_ops->init_structures(smmu);
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 6680516b571b..d528b3212d38 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -811,6 +811,8 @@ struct arm_smmu_device {
struct rb_root streams;
struct mutex streams_mutex;
+
+ bool realm_initialized;
};
struct arm_smmu_stream {
@@ -1073,6 +1075,8 @@ arm_vsmmu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,
const struct iommu_user_data *user_data);
int arm_vsmmu_cache_invalidate(struct iommufd_viommu *viommu,
struct iommu_user_data_array *array);
+int arm_realm_smmu_v3_init(struct iommufd_viommu *viommu,
+ const struct iommu_user_data *user_data);
#else
#define arm_smmu_get_viommu_size NULL
#define arm_smmu_hw_info NULL
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 47213663c0c1..74afc9967c3e 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -1055,6 +1055,7 @@ enum iommu_viommu_type {
IOMMU_VIOMMU_TYPE_DEFAULT = 0,
IOMMU_VIOMMU_TYPE_ARM_SMMUV3 = 1,
IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV = 2,
+ IOMMU_VIOMMU_TYPE_ARM_REALM_SMMUV3 = 3,
};
/**
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 02/16] iommu/arm-smmu-v3: Save the programmed MSI message in msi_desc
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:53 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Joerg Roedel, Jonathan Cameron,
Marc Zyngier, Nicolin Chen, Pranjal Shrivastava, Robin Murphy,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427085344.941627-1-aneesh.kumar@kernel.org>
Cache the MSI message in desc->msg from arm_smmu_write_msi_msg(). The
realm support code later reads the MSI address and data through
irq_get_msi_desc(), so it needs the descriptor to reflect the last
programmed message.
This matches the caching done by __pci_write_msi_msg().
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index d5b9ab95beea..17fd99887aab 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -4012,6 +4012,9 @@ static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
if (smmu->features & ARM_SMMU_FEAT_PRI)
max_config_index = PRIQ_MSI_INDEX;
+ /* save the programmed msi message details */
+ desc->msg = *msg;
+
/* Don't try to config for Realm interrupts. */
if (desc->msi_index > max_config_index)
return;
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 01/16] iommu/arm-smmu-v3: Discover RME support and realm IRQ topology
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:53 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Joerg Roedel, Jonathan Cameron,
Marc Zyngier, Nicolin Chen, Pranjal Shrivastava, Robin Murphy,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427085344.941627-1-aneesh.kumar@kernel.org>
Detect RME-capable SMMUv3 instances from IDR0.RME_IMPL and record the
capability in arm_smmu_device.
When RMM is active, query RMI_PSMMU_INFO to discover how realm-side
notifications are delivered. For pSMMUs that expose realm interrupts, store
the reported evtq/gerror/priq IRQs, reserve extra MSI vectors when RMM uses
MSI delivery, and register threaded handlers that acknowledge notifications
through RMI_PSMMU_IRQ_NOTIFY / RMI_PSMMU_EVENT_CONSUME.
Also add the RMI command/structure definitions needed for PSMMU_INFO and
interrupt notification, along with arm_smmu_device state for the physical
base address and realm IRQs.
If RMM reports a CMDQ sync interrupt requirement, keep the IRQ plumbing
but leave ARM_SMMU_FEAT_RME disabled.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rmi_cmds.h | 52 ++++++++
arch/arm64/include/asm/rmi_smc.h | 32 ++++-
drivers/iommu/arm/arm-smmu-v3/Makefile | 2 +-
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c | 124 ++++++++++++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 81 +++++++++++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 10 ++
6 files changed, 297 insertions(+), 4 deletions(-)
create mode 100644 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index c82d4d9cbc06..75eb59d4fa84 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -811,4 +811,56 @@ static inline unsigned long rmi_pdev_stream_disconnect(unsigned long pdev1_phys,
return res.a0;
}
+static inline unsigned long rmi_psmmu_info(unsigned long psmmu_phys,
+ unsigned long psmmu_info_phys)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_PSMMU_INFO,
+ psmmu_phys, psmmu_info_phys, &res);
+
+ return res.a0;
+}
+
+struct rmi_psmmu_event_details {
+ u64 flags;
+ u64 event_num;
+ u64 stream_id; /* valid only if we have VSMMU event */
+ u64 fetch_addr;
+ u64 input_addr;
+ u64 syndrome;
+};
+
+static inline unsigned long rmi_psmmu_irq_notify(unsigned long psmmu_phys,
+ unsigned long irqs, struct rmi_psmmu_event_details *event)
+{
+ struct arm_smccc_1_2_regs regs = {
+ .a0 = SMC_RMI_PSMMU_IRQ_NOTIFY,
+ .a1 = psmmu_phys,
+ .a2 = irqs,
+ };
+
+ arm_smccc_1_2_invoke(®s, ®s);
+
+ event->flags = regs.a1;
+ event->event_num = regs.a2;
+ event->stream_id = regs.a3;
+ event->fetch_addr = regs.a4;
+ event->input_addr = regs.a5;
+ event->syndrome = regs.a6;
+
+ return regs.a0;
+}
+
+static inline unsigned long rmi_psmmu_event_consume(unsigned long psmmu_phys,
+ unsigned long irqs)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_PSMMU_EVENT_CONSUME,
+ psmmu_phys, irqs, &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 7b16f1540a0e..be1b1e95a937 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -101,7 +101,7 @@
#define SMC_RMI_PDEV_MEC_UPDATE SMC_RMI_CALL(0x01ed)
#define SMC_RMI_VSMMU_EVENT_COMPLETE SMC_RMI_CALL(0x01ee)
-#define SMC_RMI_PSMMU_EVENT_DISCARD SMC_RMI_CALL(0x01f0)
+#define SMC_RMI_PSMMU_EVENT_CONSUME SMC_RMI_CALL(0x01f0)
#define SMC_RMI_GRANULE_RANGE_DELEGATE SMC_RMI_CALL(0x01f1)
#define SMC_RMI_GRANULE_RANGE_UNDELEGATE SMC_RMI_CALL(0x01f2)
#define SMC_RMI_GPT_L1_CREATE SMC_RMI_CALL(0x01f3)
@@ -129,6 +129,7 @@
#define SMC_RMI_OP_MEM_RECLAIM SMC_RMI_CALL(0x0209)
#define SMC_RMI_OP_CANCEL SMC_RMI_CALL(0x020a)
#define SMC_RMI_PDEV_SET_PROT SMC_RMI_CALL(0x020b)
+#define SMC_RMI_PSMMU_INFO SMC_RMI_CALL(0x020e)
#define RMI_ABI_MAJOR_VERSION 2
#define RMI_ABI_MINOR_VERSION 0
@@ -595,4 +596,33 @@ struct rmi_pdev_stream_params {
};
};
+#define RMI_PSMMU_IRQCFG_IRQ_DISABLED 0x0
+#define RMI_PSMMU_IRQCFG_IRQ_WIRED 0x1
+#define RMI_PSMMU_IRQCFG_IRQ_MSI 0x2
+#define RMI_PSMMU_IRQCFG_MASK GENMASK(1, 0)
+struct rmi_psmmu_info {
+ union {
+ struct {
+ u64 flags;
+ union {
+ u32 gerror_intr_num;
+ u8 padding1[8];
+ };
+ union {
+ u32 eventq_intr_num;
+ u8 padding2[8];
+ };
+ union {
+ u32 priq_intr_num;
+ u8 padding3[8];
+ };
+ union {
+ u32 cmdq_sync_intr_num;
+ u8 padding4[8];
+ };
+ };
+ u8 padding5[0x1000];
+ };
+};
+
#endif /* __ASM_RMI_SMC_H */
diff --git a/drivers/iommu/arm/arm-smmu-v3/Makefile b/drivers/iommu/arm/arm-smmu-v3/Makefile
index 493a659cc66b..23bd794ebeda 100644
--- a/drivers/iommu/arm/arm-smmu-v3/Makefile
+++ b/drivers/iommu/arm/arm-smmu-v3/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ARM_SMMU_V3) += arm_smmu_v3.o
-arm_smmu_v3-y := arm-smmu-v3.o
+arm_smmu_v3-y := arm-smmu-v3.o arm-smmu-v3-realm.o
arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_IOMMUFD) += arm-smmu-v3-iommufd.o
arm_smmu_v3-$(CONFIG_ARM_SMMU_V3_SVA) += arm-smmu-v3-sva.o
arm_smmu_v3-$(CONFIG_TEGRA241_CMDQV) += tegra241-cmdqv.o
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
new file mode 100644
index 000000000000..fec1a32de53c
--- /dev/null
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include <linux/interrupt.h>
+#include <asm/rmi_smc.h>
+#include <asm/rmi_cmds.h>
+
+#include "arm-smmu-v3.h"
+
+#define RMI_PSMMU_IRQ_GERROR BIT(0)
+#define RMI_PSMMU_IRQ_EVENTQ BIT(1)
+#define RMI_PSMMU_IRQ_PRIQ BIT(2)
+#define RMI_PSMMU_IRQ_CMDQ BIT(3)
+
+#define RMI_PSMMU_IRQ_EVENT_NONE 0
+#define RMI_PSMMU_IRQ_EVENT_ERROR 1
+#define RMI_PSMMU_IRQ_EVENT_PSMMU 2
+#define RMI_PSMMU_IRQ_EVENT_VSMMU 3
+#define RMI_PSMMU_IRQ_EVENT_MASK GENMASK(2, 1)
+#define RMI_PSMMU_IRQ_EVENT_SHIFT 1
+#define RMI_PSMMU_IRQ_EVENT_PENDING 0x1
+
+static irqreturn_t arm_smmu_realm_notify_thread(int irq, void *dev)
+{
+ int rmi_psmmu_event;
+ unsigned long notify_flags;
+ struct arm_smmu_device *smmu = dev;
+ struct rmi_psmmu_event_details event;
+
+ if (irq == smmu->realm_evtq_irq)
+ notify_flags = RMI_PSMMU_IRQ_EVENTQ;
+ else if (irq == smmu->realm_gerr_irq)
+ notify_flags = RMI_PSMMU_IRQ_GERROR;
+ else if (irq == smmu->realm_pri_irq)
+ notify_flags = RMI_PSMMU_IRQ_PRIQ;
+ else
+ return IRQ_HANDLED;
+
+ do {
+ if (rmi_psmmu_irq_notify(smmu->base_phys,
+ notify_flags, &event)) {
+ dev_warn(smmu->dev,
+ "failed to notify RMM of a SMMU event\n");
+ /* there is nothing much we could do. Mark it handled. */
+ return IRQ_HANDLED;
+ }
+ rmi_psmmu_event = (event.flags & RMI_PSMMU_IRQ_EVENT_MASK) >>
+ RMI_PSMMU_IRQ_EVENT_SHIFT;
+ switch (rmi_psmmu_event) {
+ case RMI_PSMMU_IRQ_EVENT_NONE:
+ break;
+ case RMI_PSMMU_IRQ_EVENT_ERROR:
+ dev_warn(smmu->dev, "SMMU Error reported\n");
+ rmi_psmmu_event_consume(smmu->base_phys, notify_flags);
+ break;
+ case RMI_PSMMU_IRQ_EVENT_PSMMU:
+ dev_warn(smmu->dev,
+ "SMMU event (event num: 0x%llx syndrome 0x%llx "
+ "fetch_addr 0x%llx input_addr 0x%llx) reported\n",
+ event.event_num, event.syndrome,
+ event.fetch_addr, event.input_addr);
+ rmi_psmmu_event_consume(smmu->base_phys, notify_flags);
+ break;
+ case RMI_PSMMU_IRQ_EVENT_VSMMU:
+ dev_warn(smmu->dev, "Wrong VSMMU event on stream 0x%llx, ignoring\n",
+ event.stream_id);
+ rmi_psmmu_event_consume(smmu->base_phys, notify_flags);
+ break;
+ }
+
+ } while (event.flags & RMI_PSMMU_IRQ_EVENT_PENDING);
+
+ return IRQ_HANDLED;
+}
+
+void arm_smmu_setup_realm_irqs(struct arm_smmu_device *smmu)
+{
+ int irq, ret;
+
+ irq = smmu->realm_evtq_irq;
+ if (irq) {
+ ret = devm_request_threaded_irq(smmu->dev, irq, NULL,
+ arm_smmu_realm_notify_thread,
+ IRQF_ONESHOT,
+ "arm-smmu-v3-realm-evtq",
+ smmu);
+ if (ret < 0)
+ dev_warn(smmu->dev, "failed to enable realm evtq irq\n");
+ } else {
+ dev_warn(smmu->dev, "no realm evtq irq - events will not be reported!\n");
+ }
+
+ irq = smmu->realm_gerr_irq;
+ if (irq) {
+ ret = devm_request_threaded_irq(smmu->dev, irq, NULL,
+ arm_smmu_realm_notify_thread,
+ IRQF_ONESHOT,
+ "arm-smmu-v3-realm-gerror",
+ smmu);
+ if (ret < 0)
+ dev_warn(smmu->dev, "failed to enable realm gerror irq\n");
+ } else {
+ dev_warn(smmu->dev, "no realm gerr irq - errors will not be reported!\n");
+ }
+
+ if (smmu->features & ARM_SMMU_FEAT_PRI) {
+ irq = smmu->realm_pri_irq;
+ if (irq) {
+
+ ret = devm_request_threaded_irq(smmu->dev, irq, NULL,
+ arm_smmu_realm_notify_thread,
+ IRQF_ONESHOT,
+ "arm-smmu-v3-realm-priq",
+ smmu);
+ if (ret < 0)
+ dev_warn(smmu->dev,
+ "failed to enable realm priq irq\n");
+ } else {
+ dev_warn(smmu->dev, "no realm priq irq - PRI will be broken\n");
+ }
+ }
+}
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
index 4d00d796f078..d5b9ab95beea 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c
@@ -29,6 +29,9 @@
#include <linux/string_choices.h>
#include <kunit/visibility.h>
#include <uapi/linux/iommufd.h>
+#include <linux/irq.h>
+#include <linux/msi.h>
+#include <asm/rmi_cmds.h>
#include "arm-smmu-v3.h"
#include "../../dma-iommu.h"
@@ -4000,10 +4003,20 @@ static void arm_smmu_free_msis(void *data)
static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
{
+ int max_config_index = GERROR_MSI_INDEX;
phys_addr_t doorbell;
struct device *dev = msi_desc_to_dev(desc);
struct arm_smmu_device *smmu = dev_get_drvdata(dev);
- phys_addr_t *cfg = arm_smmu_msi_cfg[desc->msi_index];
+ phys_addr_t *cfg;
+
+ if (smmu->features & ARM_SMMU_FEAT_PRI)
+ max_config_index = PRIQ_MSI_INDEX;
+
+ /* Don't try to config for Realm interrupts. */
+ if (desc->msi_index > max_config_index)
+ return;
+
+ cfg = arm_smmu_msi_cfg[desc->msi_index];
doorbell = (((u64)msg->address_hi) << 32) | msg->address_lo;
doorbell &= MSI_CFG0_ADDR_MASK;
@@ -4015,6 +4028,7 @@ static void arm_smmu_write_msi_msg(struct msi_desc *desc, struct msi_msg *msg)
static void arm_smmu_setup_msis(struct arm_smmu_device *smmu)
{
+ int irq_index;
int ret, nvec = ARM_SMMU_MAX_MSIS;
struct device *dev = smmu->dev;
@@ -4035,6 +4049,13 @@ static void arm_smmu_setup_msis(struct arm_smmu_device *smmu)
return;
}
+ /*
+ * Request for realm side non secure interrupts too. Should this be condition
+ * on non-secure gic?
+ */
+ if (smmu->features & ARM_SMMU_FEAT_RME_MSI)
+ nvec = nvec * 2;
+
/* Allocate MSIs for evtq, gerror and priq. Ignore cmdq */
ret = platform_device_msi_init_and_alloc_irqs(dev, nvec, arm_smmu_write_msi_msg);
if (ret) {
@@ -4044,7 +4065,19 @@ static void arm_smmu_setup_msis(struct arm_smmu_device *smmu)
smmu->evtq.q.irq = msi_get_virq(dev, EVTQ_MSI_INDEX);
smmu->gerr_irq = msi_get_virq(dev, GERROR_MSI_INDEX);
- smmu->priq.q.irq = msi_get_virq(dev, PRIQ_MSI_INDEX);
+ irq_index = 2;
+ if (smmu->features & ARM_SMMU_FEAT_PRI) {
+ smmu->priq.q.irq = msi_get_virq(dev, PRIQ_MSI_INDEX);
+ irq_index++;
+ }
+
+ if (smmu->features & ARM_SMMU_FEAT_RME_MSI) {
+ smmu->realm_evtq_irq = msi_get_virq(dev, irq_index++);
+ smmu->realm_gerr_irq = msi_get_virq(dev, irq_index++);
+ // fixme, we should check for pri rmm capability
+ if (smmu->features & ARM_SMMU_FEAT_PRI)
+ smmu->realm_pri_irq = msi_get_virq(dev, irq_index++);
+ }
/* Add callback to free MSIs on teardown */
devm_add_action_or_reset(dev, arm_smmu_free_msis, dev);
@@ -4094,6 +4127,9 @@ static void arm_smmu_setup_unique_irqs(struct arm_smmu_device *smmu)
dev_warn(smmu->dev, "no priq irq - PRI will be broken\n");
}
}
+
+ if (smmu->features & ARM_SMMU_FEAT_RME_IRQ)
+ arm_smmu_setup_realm_irqs(smmu);
}
static int arm_smmu_setup_irqs(struct arm_smmu_device *smmu)
@@ -4464,6 +4500,9 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)
smmu->asid_bits = reg & IDR0_ASID16 ? 16 : 8;
smmu->vmid_bits = reg & IDR0_VMID16 ? 16 : 8;
+ if (reg & IDR0_RME_IMPL)
+ smmu->features |= ARM_SMMU_FEAT_RME;
+
/* IDR1 */
reg = readl_relaxed(smmu->base + ARM_SMMU_IDR1);
if (reg & (IDR1_TABLES_PRESET | IDR1_QUEUES_PRESET | IDR1_REL)) {
@@ -4852,6 +4891,7 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
return -EINVAL;
}
ioaddr = res->start;
+ smmu->base_phys = ioaddr;
/*
* Don't map the IMPLEMENTATION DEFINED regions, since they may contain
@@ -4893,6 +4933,43 @@ static int arm_smmu_device_probe(struct platform_device *pdev)
if (ret)
return ret;
+ if (rmm_is_active()) {
+ struct rmi_psmmu_info *psmmu_info;
+
+ psmmu_info = (struct rmi_psmmu_info *)get_zeroed_page(GFP_KERNEL);
+ if (!psmmu_info)
+ goto skip_rmm_config;
+
+ if (rmi_psmmu_info(smmu->base_phys, virt_to_phys(psmmu_info)))
+ smmu->features &= ~ARM_SMMU_FEAT_RME;
+
+ if ((psmmu_info->flags & RMI_PSMMU_IRQCFG_MASK) ==
+ RMI_PSMMU_IRQCFG_IRQ_DISABLED) {
+ free_page((unsigned long)psmmu_info);
+ goto skip_rmm_config;
+ }
+
+ smmu->features |= ARM_SMMU_FEAT_RME_IRQ;
+
+ if ((psmmu_info->flags & RMI_PSMMU_IRQCFG_MASK) ==
+ RMI_PSMMU_IRQCFG_IRQ_WIRED) {
+ smmu->realm_gerr_irq = psmmu_info->gerror_intr_num;
+ smmu->realm_evtq_irq = psmmu_info->eventq_intr_num;
+ smmu->realm_pri_irq = psmmu_info->priq_intr_num;
+
+ /* Disable RME FEAT because RMM need cmdq sync interrupt*/
+ if (psmmu_info->cmdq_sync_intr_num)
+ smmu->features &= ~ARM_SMMU_FEAT_RME;
+
+ } else if ((psmmu_info->flags & RMI_PSMMU_IRQCFG_MASK) ==
+ RMI_PSMMU_IRQCFG_IRQ_MSI) {
+ smmu->features |= ARM_SMMU_FEAT_RME_MSI;
+ }
+
+ free_page((unsigned long)psmmu_info);
+ }
+skip_rmm_config:
+
/* Initialise in-memory data structures */
ret = arm_smmu_init_structures(smmu);
if (ret)
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
index 3c6d65d36164..6680516b571b 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h
@@ -20,6 +20,7 @@ struct arm_vsmmu;
/* MMIO registers */
#define ARM_SMMU_IDR0 0x0
+#define IDR0_RME_IMPL (1 << 30)
#define IDR0_ST_LVL GENMASK(28, 27)
#define IDR0_ST_LVL_2LVL 1
#define IDR0_STALL_MODEL GENMASK(25, 24)
@@ -739,6 +740,7 @@ struct arm_smmu_device {
struct device *impl_dev;
const struct arm_smmu_impl_ops *impl_ops;
+ phys_addr_t base_phys;
void __iomem *base;
void __iomem *page1;
@@ -767,6 +769,9 @@ struct arm_smmu_device {
#define ARM_SMMU_FEAT_HD (1 << 22)
#define ARM_SMMU_FEAT_S2FWB (1 << 23)
#define ARM_SMMU_FEAT_BBML2 (1 << 24)
+#define ARM_SMMU_FEAT_RME (1 << 25)
+#define ARM_SMMU_FEAT_RME_IRQ (1 << 26)
+#define ARM_SMMU_FEAT_RME_MSI (1 << 27)
u32 features;
#define ARM_SMMU_OPT_SKIP_PREFETCH (1 << 0)
@@ -782,6 +787,9 @@ struct arm_smmu_device {
int gerr_irq;
int combined_irq;
+ int realm_gerr_irq;
+ int realm_evtq_irq;
+ int realm_pri_irq;
unsigned long oas; /* PA */
unsigned long pgsize_bitmap;
@@ -1096,4 +1104,6 @@ static inline int arm_vmaster_report_event(struct arm_smmu_vmaster *vmaster,
}
#endif /* CONFIG_ARM_SMMU_V3_IOMMUFD */
+void arm_smmu_setup_realm_irqs(struct arm_smmu_device *smmu);
+
#endif /* _ARM_SMMU_V3_H */
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 00/16] coco/TSM: Implement host-side support for Arm CCA TDISP setup
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:53 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Joerg Roedel, Jonathan Cameron,
Marc Zyngier, Nicolin Chen, Pranjal Shrivastava, Robin Murphy,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
This patch series implements the host-side changes needed for end-to-end
Arm CCA TDISP setup. It adds the RMI/RHI plumbing required to create and
manage Realm vdev objects, service device-attestation object requests, and
complete the KVM/RMM flows needed for device run-time transitions.
The series is based on the RMM 2.0bet1 specification [1] and the
RHI v1.0 BET1 specification [5].
At a high level, the series adds support for:
- host-side vdev communication and lifecycle management
- host handling of RHI DA object read/size requests
- host-side fetching and caching of interface reports and measurements
- KVM handling of vdev request/complete exits
- KVM handling of map/validation exits and teardown on granule destroy
- vdev transition to TDISP RUN state
- enabling DA in Realm create parameters
The series builds upon the TSM framework patches posted at [2] and depends on
the KVM CCA patchset [3]. A git repository containing all related changes is
available at [4]. kvmtool repo is at [6]
Previous posting:
Changes from v3:
https://lore.kernel.org/all/20260312080743.3487326-1-aneesh.kumar@kernel.org
* updated the patches to follow the RMM 2.0bet1 specification
* moved vdev request-exit handling to the last patch in the series. This is
expected to be dropped once the corresponding spec update lands
* dropped the vdev and pdev arguments from rmi_rtt_dev_validate(). The spec
update for that change is still pending. The validation call is now made
from the REC enter path
* added a response field to KVM_EXIT_ARM64_TIO so the VMM can pass the ioctl
return status back to the exit handler
* dropped vcpu_fd from arm64_vdev_device_memmap_guest_req. Once vdev request
handling is removed from the series, the ioctl path will no longer need
vcpu_fd
* reworked the host-side vdev lifecycle to better match the RMM 2.0bet1 flow,
* updated the vdev flows to match the revised interfaces: populate MMIO BAR
ranges in rmi_vdev_create(), rename the interface-report and measurement
update commands, and drop vcpu_fd from the guest MMIO map request path
Changes from v1:
* rebase to latest kernel and core TSM changes
* address review feedback
rfc-v1: https://lore.kernel.org/all/20250728135216.48084-1-aneesh.kumar@kernel.org
There is no rfc-v2 posting. This series is marked rfc-v3 to stay aligned
with the rest of the CCA patchsets that are being posted as v3.
[1] https://developer.arm.com/documentation/den0137/2-0bet1/
[2] https://lore.kernel.org/all/20260303000207.1836586-1-dan.j.williams@intel.com
[3] https://lore.kernel.org/all/20260318155413.793430-1-steven.price@arm.com
[4] https://gitlab.arm.com/linux-arm/linux-cca.git cca/topics/cca-tdisp-upstream-rfc-v4
[5] https://developer.arm.com/documentation/den0148/latest/
[6] https://gitlab.arm.com/linux-arm/kvmtool-cca.git cca/topics/cca-tdisp-upstream-rfc-v4
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Joerg Roedel <joro@8bytes.org>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Nicolin Chen <nicolinc@nvidia.com>
Cc: Pranjal Shrivastava <praan@google.com>
Cc: Robin Murphy <robin.murphy@arm.com>
Cc: Samuel Ortiz <sameo@rivosinc.com>
Cc: Steven Price <steven.price@arm.com>
Cc: Suzuki K Poulose <Suzuki.Poulose@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Aneesh Kumar K.V (Arm) (16):
iommu/arm-smmu-v3: Discover RME support and realm IRQ topology
iommu/arm-smmu-v3: Save the programmed MSI message in msi_desc
iommu/arm-smmu-v3: Add initial pSMMU realm viommu plumbing
iommu/arm-smmu-v3: Track realm pSMMU users with refcount_t
coco: host: arm64: Add support for virtual device communication
coco: host: arm64: Add support for RMM vdev objects
coco: host: arm64: Add pdev stream key refresh and purge helpers
coco: host: arm64: Add helpers to unlock and destroy RMM vdev
coco: host: arm64: Add support for da object read RHI handling
coco: host: arm64: Add helper for cached object fetches
coco: host: arm64: Fetch interface report via RMI
coco: host: arm64: Fetch device measurements via RMI
coco: host: KVM: arm64: Handle vdev validate-mapping exits
KVM: arm64: Unmap device mappings when a private granule is destroyed
coco: host: arm64: Transition vdevs to TDISP RUN state
KVM: arm64: CCA: enable DA in realm create parameters
Documentation/virt/kvm/api.rst | 20 +
arch/arm64/include/asm/kvm_rmi.h | 4 +
arch/arm64/include/asm/rmi_cmds.h | 193 ++++++
arch/arm64/include/asm/rmi_smc.h | 98 ++-
arch/arm64/include/uapi/asm/rmi-da.h | 47 ++
arch/arm64/kernel/rmi.c | 51 ++
arch/arm64/kvm/rmi-exit.c | 37 ++
arch/arm64/kvm/rmi.c | 279 ++++++++-
drivers/iommu/arm/arm-smmu-v3/Makefile | 2 +-
.../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 7 +
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c | 297 +++++++++
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 86 ++-
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 16 +
drivers/virt/coco/arm-cca-host/arm-cca.c | 188 +++++-
drivers/virt/coco/arm-cca-host/rmi-da.c | 569 +++++++++++++++++-
drivers/virt/coco/arm-cca-host/rmi-da.h | 40 ++
include/uapi/linux/iommufd.h | 1 +
include/uapi/linux/kvm.h | 11 +
18 files changed, 1929 insertions(+), 17 deletions(-)
create mode 100644 arch/arm64/include/uapi/asm/rmi-da.h
create mode 100644 drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-realm.c
--
2.43.0
^ permalink raw reply
* [RFC PATCH v4 11/11] coco: guest: arm64: Enable vdev DMA after attestation
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:28 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Jonathan Cameron
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
- define SMC_RSI_VDEV_DMA_ENABLE and add wrapper in rsi_cmds.h
- invoke the new helper from the guest accept path once the device
passes attestation, rolling back to TDI_LOCKED on failure
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rsi_cmds.h | 16 ++++++++++++++++
arch/arm64/include/asm/rsi_smc.h | 2 ++
drivers/virt/coco/arm-cca-guest/rsi-da.c | 14 ++++++++++++++
3 files changed, 32 insertions(+)
diff --git a/arch/arm64/include/asm/rsi_cmds.h b/arch/arm64/include/asm/rsi_cmds.h
index f72d8e0cd422..1e0d1cd8841a 100644
--- a/arch/arm64/include/asm/rsi_cmds.h
+++ b/arch/arm64/include/asm/rsi_cmds.h
@@ -226,4 +226,20 @@ static inline unsigned long rsi_vdev_get_info(unsigned long vdev_id,
return res.a0;
}
+static inline unsigned long __rsi_vdev_dma_enable(unsigned long vdev_id,
+ unsigned long flags,
+ unsigned long non_ats_plane,
+ unsigned long lock_nonce,
+ unsigned long meas_nonce,
+ unsigned long report_nonce)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RSI_VDEV_DMA_ENABLE, vdev_id, flags,
+ non_ats_plane, lock_nonce,
+ meas_nonce, report_nonce, &res);
+
+ return res.a0;
+}
+
#endif /* __ASM_RSI_CMDS_H */
diff --git a/arch/arm64/include/asm/rsi_smc.h b/arch/arm64/include/asm/rsi_smc.h
index 99b34b37b693..1d98a3b47c89 100644
--- a/arch/arm64/include/asm/rsi_smc.h
+++ b/arch/arm64/include/asm/rsi_smc.h
@@ -186,6 +186,8 @@ struct realm_config {
*/
#define SMC_RSI_IPA_STATE_GET SMC_RSI_FID(0x198)
+#define SMC_RSI_VDEV_DMA_ENABLE SMC_RSI_FID(0x19C)
+
#define RSI_VDEV_REPORT_FORMAT_TDISP 0x1
struct rsi_vdevice_info {
union {
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.c b/drivers/virt/coco/arm-cca-guest/rsi-da.c
index 7c2b28fa43a1..77267479df19 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.c
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.c
@@ -231,9 +231,17 @@ int cca_verify_digests(u64 hash_algo,
return 0;
}
+static inline int rsi_vdev_enable_dma(int vdev_id, struct dsm_device_info *dev_info)
+{
+ /* No ATS support */
+ return __rsi_vdev_dma_enable(vdev_id, 0, 0, dev_info->lock_nonce,
+ dev_info->meas_nonce, dev_info->report_nonce);
+}
+
int cca_device_accept(struct pci_dev *pdev, unsigned long lock_nonce)
{
int ret;
+ int vdev_id = rsi_vdev_id(pdev);
struct cca_guest_dsc *dsc = to_cca_guest_dsc(pdev);
if (lock_nonce != dsc->dev_info.lock_nonce) {
@@ -270,6 +278,12 @@ int cca_device_accept(struct pci_dev *pdev, unsigned long lock_nonce)
return ret;
}
+ if (rsi_vdev_enable_dma(vdev_id, &dsc->dev_info)) {
+ rhi_vdev_set_tdi_state(pdev, RHI_DA_TDI_CONFIG_LOCKED);
+ pci_err(pdev, "failed to enable DMA from the device\n");
+ return -EIO;
+ }
+
dsc->pci.mmio = no_free_ptr(tsm_mmio);
return 0;
}
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 10/11] coco: arm64: dma: Update force_dma_unencrypted for accepted devices
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:28 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
This change updates the DMA behavior for accepted devices by assuming
they access only private memory. Currently, the DMA API does not provide
a mechanism for allocating shared memory that can be accessed by both
the secure realm and the non-secure host. Accepted devices are therefore
expected to operate entirely within the private memory space.
If future use cases require accepted devices to interact with shared
memory— for example, for host-device communication, we will need to
extend the DMA interface to support such allocation semantics. This
commit lays the groundwork for that by clearly defining the current
assumption and isolating the enforcement to force_dma_unencrypted.
Treat swiotlb and decrypted DMA pools as shared-memory paths and avoid them
for accepted devices by:
- returning false from is_swiotlb_for_alloc() for accepted devices
- returning false from is_swiotlb_active() for accepted devices
- bypassing dma-direct atomic pool usage for accepted devices
This is based on the current assumption that accepted devices operate on private
Realm memory only, and prevents accidental fallback to shared/decrypted DMA
backends.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/mem_encrypt.h | 6 +-----
arch/arm64/mm/mem_encrypt.c | 10 ++++++++++
include/linux/swiotlb.h | 3 +++
kernel/dma/direct.c | 8 ++++++++
kernel/dma/swiotlb.c | 3 +++
5 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/include/asm/mem_encrypt.h b/arch/arm64/include/asm/mem_encrypt.h
index 5541911eb028..ae0b0cac0900 100644
--- a/arch/arm64/include/asm/mem_encrypt.h
+++ b/arch/arm64/include/asm/mem_encrypt.h
@@ -15,17 +15,13 @@ int arm64_mem_crypt_ops_register(const struct arm64_mem_crypt_ops *ops);
int set_memory_encrypted(unsigned long addr, int numpages);
int set_memory_decrypted(unsigned long addr, int numpages);
+bool force_dma_unencrypted(struct device *dev);
#define mem_decrypt_granule_size mem_decrypt_granule_size
size_t mem_decrypt_granule_size(void);
int realm_register_memory_enc_ops(void);
-static inline bool force_dma_unencrypted(struct device *dev)
-{
- return is_realm_world();
-}
-
/*
* For Arm CCA guests, canonical addresses are "encrypted", so no changes
* required for dma_addr_encrypted().
diff --git a/arch/arm64/mm/mem_encrypt.c b/arch/arm64/mm/mem_encrypt.c
index f5d64bc29c20..18dea5d879b8 100644
--- a/arch/arm64/mm/mem_encrypt.c
+++ b/arch/arm64/mm/mem_encrypt.c
@@ -18,6 +18,7 @@
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/mem_encrypt.h>
+#include <linux/device.h>
static const struct arm64_mem_crypt_ops *crypt_ops;
@@ -67,3 +68,12 @@ size_t mem_decrypt_granule_size(void)
return PAGE_SIZE;
}
EXPORT_SYMBOL_GPL(mem_decrypt_granule_size);
+
+bool force_dma_unencrypted(struct device *dev)
+{
+ if (device_cc_accepted(dev))
+ return false;
+
+ return is_realm_world();
+}
+EXPORT_SYMBOL_GPL(force_dma_unencrypted);
diff --git a/include/linux/swiotlb.h b/include/linux/swiotlb.h
index 0efb9b8e5dd0..224dcec6a58f 100644
--- a/include/linux/swiotlb.h
+++ b/include/linux/swiotlb.h
@@ -296,6 +296,9 @@ bool swiotlb_free(struct device *dev, struct page *page, size_t size);
static inline bool is_swiotlb_for_alloc(struct device *dev)
{
+ if (device_cc_accepted(dev))
+ return false;
+
return dev->dma_io_tlb_mem->for_alloc;
}
#else
diff --git a/kernel/dma/direct.c b/kernel/dma/direct.c
index 34eccd047e9b..a7a9984db342 100644
--- a/kernel/dma/direct.c
+++ b/kernel/dma/direct.c
@@ -158,6 +158,14 @@ static struct page *__dma_direct_alloc_pages(struct device *dev, size_t size,
*/
static bool dma_direct_use_pool(struct device *dev, gfp_t gfp)
{
+ /*
+ * Atomic pools are marked decrypted and are used if we require
+ * updation of pfn mem encryption attributes or for DMA non-coherent
+ * device allocation. Both is not true for trusted device.
+ */
+ if (device_cc_accepted(dev))
+ return false;
+
return !gfpflags_allow_blocking(gfp) && !is_swiotlb_for_alloc(dev);
}
diff --git a/kernel/dma/swiotlb.c b/kernel/dma/swiotlb.c
index c94abc2dcae3..f0d4b9f799bf 100644
--- a/kernel/dma/swiotlb.c
+++ b/kernel/dma/swiotlb.c
@@ -1651,6 +1651,9 @@ bool is_swiotlb_active(struct device *dev)
{
struct io_tlb_mem *mem = dev->dma_io_tlb_mem;
+ if (device_cc_accepted(dev))
+ return false;
+
return mem && mem->nslabs;
}
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 09/11] coco: guest: arm64: Hook TSM accept to Realm TDISP RUN transition
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:28 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun, Jonathan Cameron
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
Add an accept callback in pci_tsm_ops and implement cca_device_accept() to:
- verify evidence generation (lock_nonce)
- allocate and register protected MMIO ranges
- transition TDI state to RUN
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/virt/coco/arm-cca-guest/arm-cca.c | 20 +++++++++++
drivers/virt/coco/arm-cca-guest/rsi-da.c | 43 +++++++++++++++++++++++
drivers/virt/coco/arm-cca-guest/rsi-da.h | 1 +
3 files changed, 64 insertions(+)
diff --git a/drivers/virt/coco/arm-cca-guest/arm-cca.c b/drivers/virt/coco/arm-cca-guest/arm-cca.c
index 320dd5aa7b9a..b29b3948a454 100644
--- a/drivers/virt/coco/arm-cca-guest/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-guest/arm-cca.c
@@ -499,9 +499,29 @@ static void cca_tsm_unlock(struct pci_tsm *tsm)
kfree(cca_dsc);
}
+static int __cca_tsm_accept(struct pci_dev *pdev, unsigned long lock_nonce)
+{
+ int ret;
+
+ ret = cca_device_accept(pdev, lock_nonce);
+ if (ret) {
+ pci_err(pdev, "failed to transition the device to run state (%d)\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int cca_tsm_accept(struct pci_dev *pdev)
+{
+ struct cca_guest_dsc *dsc = to_cca_guest_dsc(pdev);
+
+ return __cca_tsm_accept(pdev, dsc->dev_info.lock_nonce);
+}
+
static struct pci_tsm_ops cca_devsec_pci_ops = {
.lock = cca_tsm_lock,
.unlock = cca_tsm_unlock,
+ .accept = cca_tsm_accept,
};
static void cca_devsec_tsm_remove(void *tsm_dev)
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.c b/drivers/virt/coco/arm-cca-guest/rsi-da.c
index 039138768f8f..7c2b28fa43a1 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.c
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.c
@@ -230,3 +230,46 @@ int cca_verify_digests(u64 hash_algo,
}
return 0;
}
+
+int cca_device_accept(struct pci_dev *pdev, unsigned long lock_nonce)
+{
+ int ret;
+ struct cca_guest_dsc *dsc = to_cca_guest_dsc(pdev);
+
+ if (lock_nonce != dsc->dev_info.lock_nonce) {
+ pci_err(pdev, "Device evidence generation mismatch\n");
+ return -EIO;
+ }
+
+ /* Allocation private mmio range based on interface report. */
+ struct pci_tsm_mmio *tsm_mmio __free(kfree) = pci_tsm_mmio_alloc(pdev);
+ if (!tsm_mmio) {
+ pci_err(pdev, "Protected mmio range allocation failure\n");
+ return -ENOMEM;
+ }
+
+ /*
+ * Present the private mmio range in the resource hierarchy.
+ * We don't use this for ioremap, ioremap check the RIPAS value.
+ */
+ ret = pci_tsm_mmio_setup(pdev, tsm_mmio);
+ if (ret) {
+ pci_err(pdev, "Protected mmio setup failure\n");
+ return ret;
+ }
+
+ ret = cca_map_evidence_report_range(pdev, tsm_mmio);
+ if (ret) {
+ pci_err(pdev, "failed to validate the interface report\n");
+ return ret;
+ }
+
+ ret = rhi_vdev_set_tdi_state(pdev, RHI_DA_TDI_CONFIG_RUN);
+ if (ret) {
+ pci_err(pdev, "failed to switch the device (%u) to RUN state\n", ret);
+ return ret;
+ }
+
+ dsc->pci.mmio = no_free_ptr(tsm_mmio);
+ return 0;
+}
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.h b/drivers/virt/coco/arm-cca-guest/rsi-da.h
index 07a044d3e335..eeb049d374c5 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.h
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.h
@@ -61,5 +61,6 @@ int cca_verify_digests(u64 hash_algo,
uint8_t *interface_report, size_t interface_report_size,
uint8_t *measurements, size_t measurements_size,
struct rsi_vdevice_info *dev_info);
+int cca_device_accept(struct pci_dev *pdev, unsigned long lock_nonce);
#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 08/11] coco: guest: arm64: Verify DA evidence with RSI_VDEV_GET_INFO digests
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:28 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
Add guest-side evidence verification based on RSI_VDEV_GET_INFO and use the
verified TDISP interface report to validate Realm MMIO mappings.
During lock:
- refresh host caches from device
- read certificate/VCA/interface-report/measurement objects from host cache
- fetch trusted digest metadata from RSI_VDEV_GET_INFO
- verify host-provided objects against RSI digests
- initialize and populate PCI TSM evidence objects
- preserve lock/meas/report nonces and digests in guest state
Add mapping helpers to walk MMIO entries from the TDISP report and perform
RSI_VDEV_VALIDATE_MAPPING on map, or RIPAS destroy on unmap. Reject malformed
range progress while validating.
During unlock:
- invalidate mappings derived from the evidence report
- unlock the device and tear down MMIO bookkeeping
This ensures host-cached DA objects are cryptographically verified before
being trusted for mapping and attestation state transitions.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rsi_cmds.h | 40 +++
arch/arm64/include/asm/rsi_smc.h | 60 +++++
drivers/virt/coco/arm-cca-guest/Kconfig | 2 +
drivers/virt/coco/arm-cca-guest/arm-cca.c | 287 +++++++++++++++++++++-
drivers/virt/coco/arm-cca-guest/rsi-da.c | 145 +++++++++++
drivers/virt/coco/arm-cca-guest/rsi-da.h | 22 ++
6 files changed, 545 insertions(+), 11 deletions(-)
diff --git a/arch/arm64/include/asm/rsi_cmds.h b/arch/arm64/include/asm/rsi_cmds.h
index 596bdc356f1a..f72d8e0cd422 100644
--- a/arch/arm64/include/asm/rsi_cmds.h
+++ b/arch/arm64/include/asm/rsi_cmds.h
@@ -186,4 +186,44 @@ static inline unsigned long rsi_features(unsigned long index, u64 *out)
return res.a0;
}
+static inline long
+rsi_vdev_validate_mapping(unsigned long vdev_id,
+ phys_addr_t ipa_base, phys_addr_t ipa_top,
+ phys_addr_t pa_base, phys_addr_t *next_ipa,
+ unsigned long flags, unsigned long lock_nonce,
+ unsigned long meas_nonce, unsigned long report_nonce)
+{
+ struct arm_smccc_1_2_regs res;
+ struct arm_smccc_1_2_regs regs = {
+ .a0 = SMC_RSI_VDEV_VALIDATE_MAPPING,
+ .a1 = vdev_id,
+ .a2 = ipa_base,
+ .a3 = ipa_top,
+ .a4 = pa_base,
+ .a5 = flags,
+ .a6 = lock_nonce,
+ .a7 = meas_nonce,
+ .a8 = report_nonce,
+ };
+
+ arm_smccc_1_2_invoke(®s, &res);
+ *next_ipa = res.a1;
+
+ if (res.a2 != RSI_ACCEPT)
+ return -EPERM;
+
+ return res.a0;
+}
+
+static inline unsigned long rsi_vdev_get_info(unsigned long vdev_id,
+ unsigned long digest_phys)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RSI_VDEV_GET_INFO,
+ vdev_id, digest_phys, &res);
+
+ return res.a0;
+}
+
#endif /* __ASM_RSI_CMDS_H */
diff --git a/arch/arm64/include/asm/rsi_smc.h b/arch/arm64/include/asm/rsi_smc.h
index 4af4638fdd49..99b34b37b693 100644
--- a/arch/arm64/include/asm/rsi_smc.h
+++ b/arch/arm64/include/asm/rsi_smc.h
@@ -125,6 +125,9 @@
#ifndef __ASSEMBLER__
+#define RSI_HASH_SHA_256 0
+#define RSI_HASH_SHA_512 1
+
struct realm_config {
union {
struct {
@@ -183,6 +186,63 @@ struct realm_config {
*/
#define SMC_RSI_IPA_STATE_GET SMC_RSI_FID(0x198)
+#define RSI_VDEV_REPORT_FORMAT_TDISP 0x1
+struct rsi_vdevice_info {
+ union {
+ struct {
+ u64 flags;
+ u64 id_index;
+ union {
+ u8 hash_algo;
+ u64 padding0;
+ };
+ u64 lock_nonce;
+ u64 meas_nonce;
+ u64 report_nonce;
+ union {
+ u8 format_type;
+ u64 padding1;
+ };
+ u64 format_version;
+ union {
+ u8 state;
+ u64 padding2;
+ };
+
+ };
+ u8 padding3[0x80];
+ };
+ union { /* 0x80 */
+ struct {
+ u8 protocol_data_digest[0x40];
+ u8 identity_digest[0x40];
+ u8 pubkey_digest[0x40];
+ u8 meas_digest[0x40];
+ u8 report_digest[0x40];
+ };
+ u8 padding4[0x1c0 - 0x80];
+ };
+ union { /* 0x1c0 */
+ struct {
+ u64 vsmmu_addr;
+ u64 vsmu_vsid;
+ };
+ u8 padding5[0x200 - 0x1c0];
+ };
+};
+
+/*
+ * Get information for a device.
+ * arg1 == Realm device identifier (vdev id)
+ * arg2 == IPA to which configuration data will be written
+ * ret0 == Status / error
+ */
+#define SMC_RSI_VDEV_GET_INFO SMC_RSI_FID(0x19D)
+
+#define RSI_DEV_MEM_COHERENT BIT(0)
+#define RSI_DEV_MEM_LIMITED_ORDER BIT(1)
+#define SMC_RSI_VDEV_VALIDATE_MAPPING SMC_RSI_FID(0x19F)
+
struct rsi_host_call {
union {
u16 imm;
diff --git a/drivers/virt/coco/arm-cca-guest/Kconfig b/drivers/virt/coco/arm-cca-guest/Kconfig
index d295146bd92a..8ed4b95df5e4 100644
--- a/drivers/virt/coco/arm-cca-guest/Kconfig
+++ b/drivers/virt/coco/arm-cca-guest/Kconfig
@@ -5,6 +5,8 @@ config ARM_CCA_GUEST
tristate "Arm CCA Guest driver"
depends on ARM64
select PCI_TSM if PCI
+ select CRYPTO_LIB_SHA256
+ select CRYPTO_LIB_SHA512
select TSM_REPORTS
select AUXILIARY_BUS
help
diff --git a/drivers/virt/coco/arm-cca-guest/arm-cca.c b/drivers/virt/coco/arm-cca-guest/arm-cca.c
index 0f9cfb329a06..320dd5aa7b9a 100644
--- a/drivers/virt/coco/arm-cca-guest/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-guest/arm-cca.c
@@ -198,39 +198,304 @@ static void unregister_cca_tsm_report(void *data)
}
#ifdef CONFIG_PCI_TSM
+
+static int __maybe_unused
+cca_update_dev_measurements(struct pci_dev *pdev, const u8 *nonce)
+{
+ int ret;
+ void *measurements;
+ int measurements_size;
+ int vdev_id = rsi_vdev_id(pdev);
+ struct pci_tsm_evidence *evidence;
+ struct rsi_vdevice_info *dev_info;
+ struct pci_tsm_evidence_object *obj;
+ struct cca_guest_dsc *dsc = to_cca_guest_dsc(pdev);
+
+ /* Regenerate the measurement from the device */
+ ret = rhi_update_vdev_measurements_cache(pdev, nonce);
+ if (ret) {
+ pci_err(pdev, "failed to update device measurements from device (%d)\n", ret);
+ return ret;
+ }
+
+ ret = rhi_read_cached_object(vdev_id, RHI_DA_OBJECT_MEASUREMENT,
+ &measurements, &measurements_size);
+ if (ret) {
+ pci_err(pdev, "failed to get device measurements from the host (%d)\n", ret);
+ return ret;
+ }
+
+ dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
+ if (!dev_info) {
+ ret = -ENOMEM;
+ goto free_measurements;
+ }
+
+ if (rsi_vdev_get_info(vdev_id, virt_to_phys(dev_info))) {
+ pci_err(pdev, "failed to get device digests (%d)\n", ret);
+ ret = -EIO;
+ goto free_dev_info;
+ }
+
+ /* Make sure no unexpected lock/unlock operation happened from guest */
+ if (dsc->dev_info.lock_nonce != dev_info->lock_nonce) {
+ pci_err(pdev, "Unexpected lock/unlock operation from host (%d)\n", ret);
+ ret = -EIO;
+ goto free_dev_info;
+ }
+
+ /*
+ * Verify that the digests of the provided reports match with the
+ * digests from RMM
+ */
+ ret = cca_verify_digest(dev_info->hash_algo, measurements,
+ measurements_size, dev_info->meas_digest);
+ if (ret) {
+ pci_err(pdev, "RMM provided digest mismatch (%d)\n", ret);
+ goto free_dev_info;
+ }
+
+ /* fill evidence details */
+ evidence = &dsc->pci.base_tsm.evidence;
+
+ /* Now update the evidence under lock. */
+ down_write(&evidence->lock);
+ evidence->generation = dev_info->meas_nonce;
+
+ obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS];
+ if (obj->data)
+ kvfree(obj->data);
+ obj->data = measurements;
+ obj->len = measurements_size;
+
+ dsc->dev_info.meas_nonce = dev_info->meas_nonce;
+ memcpy(dsc->dev_info.meas_digest, dev_info->meas_digest, SHA512_DIGEST_SIZE);
+ up_write(&evidence->lock);
+
+ kfree(dev_info);
+ return 0;
+
+free_dev_info:
+ kfree(dev_info);
+free_measurements:
+ kvfree(measurements);
+ return ret;
+}
+
+static int cca_collect_dev_evidence(struct pci_dev *pdev, struct cca_guest_dsc *dsc)
+{
+ int ret;
+ int vdev_id = rsi_vdev_id(pdev);
+ struct pci_tsm_evidence *evidence;
+ struct rsi_vdevice_info *dev_info;
+ struct pci_tsm_evidence_object *obj;
+ void *certificate, *vca, *interface_report, *measurements;
+ int certificate_size, vca_size, interface_report_size, measurements_size;
+
+ /* Regenerate interface report and measurement from the device */
+ ret = cca_update_device_object_cache(pdev, NULL);
+ if (ret) {
+ pci_err(pdev, "failed to update device objects from device (%d)\n", ret);
+ return ret;
+ }
+
+ ret = rhi_read_cached_object(vdev_id, RHI_DA_OBJECT_CERTIFICATE,
+ &certificate, &certificate_size);
+ if (ret) {
+ pci_err(pdev, "failed to get device certificate from the host (%d)\n", ret);
+ return ret;
+ }
+
+ ret = rhi_read_cached_object(vdev_id, RHI_DA_OBJECT_VCA, &vca, &vca_size);
+ if (ret) {
+ pci_err(pdev, "failed to get device VCA from the host (%d)\n", ret);
+ goto free_certificate;
+ }
+
+ ret = rhi_read_cached_object(vdev_id, RHI_DA_OBJECT_INTERFACE_REPORT,
+ &interface_report, &interface_report_size);
+ if (ret) {
+ pci_err(pdev, "failed to get interface report from the host (%d)\n", ret);
+ goto free_vca;
+ }
+
+ ret = rhi_read_cached_object(vdev_id, RHI_DA_OBJECT_MEASUREMENT,
+ &measurements, &measurements_size);
+ if (ret) {
+ pci_err(pdev, "failed to get device certificate from the host (%d)\n", ret);
+ goto free_interface_report;
+ }
+
+ dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
+ if (!dev_info) {
+ ret = -ENOMEM;
+ goto free_measurements;
+ }
+
+ if (rsi_vdev_get_info(vdev_id, virt_to_phys(dev_info))) {
+ pci_err(pdev, "failed to get device digests (%d)\n", ret);
+ ret = -EIO;
+ goto free_dev_info;
+ }
+
+ /* Make sure no unexpected lock/unlock operation happened from guest */
+ if (dsc->dev_info.lock_nonce != dev_info->lock_nonce) {
+ pci_err(pdev, "Unexpected lock/unlock operation from host (%d)\n", ret);
+ ret = -EIO;
+ goto free_dev_info;
+ }
+
+ /*
+ * Verify that the digests of the provided reports match with the
+ * digests from RMM
+ */
+ ret = cca_verify_digests(dev_info->hash_algo, certificate,
+ certificate_size, vca, vca_size,
+ interface_report, interface_report_size,
+ measurements, measurements_size, dev_info);
+ if (ret) {
+ pci_err(pdev, "RMM provided digest mismatch (%d)\n", ret);
+ goto free_dev_info;
+ }
+
+ /* fill evidence details */
+ evidence = &dsc->pci.base_tsm.evidence;
+
+ /* Now update the evidence under lock. */
+ down_write(&evidence->lock);
+ evidence->generation = dev_info->meas_nonce;
+
+ /* we default to slot 0 in pdev_create */
+ obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
+ WARN_ON(obj->data);
+ obj->data = certificate;
+ obj->len = certificate_size;
+
+ obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_VCA];
+ WARN_ON(obj->data);
+ obj->data = vca;
+ obj->len = vca_size;
+
+ obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_REPORT];
+ WARN_ON(obj->data);
+ obj->data = interface_report;
+ obj->len = interface_report_size;
+
+ obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS];
+ WARN_ON(obj->data);
+ obj->data = measurements;
+ obj->len = measurements_size;
+
+ dsc->dev_info.meas_nonce = dev_info->meas_nonce;
+ dsc->dev_info.report_nonce = dev_info->report_nonce;
+ memcpy(dsc->dev_info.cert_digest, dev_info->identity_digest, SHA512_DIGEST_SIZE);
+ memcpy(dsc->dev_info.vca_digest, dev_info->protocol_data_digest, SHA512_DIGEST_SIZE);
+ memcpy(dsc->dev_info.meas_digest, dev_info->meas_digest, SHA512_DIGEST_SIZE);
+ memcpy(dsc->dev_info.report_digest, dev_info->report_digest, SHA512_DIGEST_SIZE);
+ up_write(&evidence->lock);
+
+ kfree(dev_info);
+ return 0;
+
+free_dev_info:
+ kfree(dev_info);
+free_measurements:
+ kvfree(measurements);
+free_interface_report:
+ kvfree(interface_report);
+free_vca:
+ kvfree(vca);
+free_certificate:
+ kvfree(certificate);
+ return ret;
+}
+
static struct pci_tsm *cca_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
{
int ret;
+ enum hash_algo digest_algo;
+ struct cca_guest_dsc *cca_dsc;
+ int vdev_id = rsi_vdev_id(pdev);
+ struct rsi_vdevice_info *dev_info;
- struct cca_guest_dsc *cca_dsc __free(kfree) =
- kzalloc_obj(struct cca_guest_dsc);
+ cca_dsc = kzalloc_obj(struct cca_guest_dsc);
if (!cca_dsc)
return ERR_PTR(-ENOMEM);
ret = pci_tsm_devsec_constructor(pdev, &cca_dsc->pci, tsm_dev);
if (ret)
- return ERR_PTR(ret);
+ goto free_cca_dsc;
ret = cca_device_lock(pdev);
if (ret)
- return ERR_PTR(ret);
+ goto free_cca_dsc;
- /* collect evidence without nonce */
- ret = cca_update_device_object_cache(pdev, NULL);
- if (ret) {
- cca_device_unlock(pdev);
- return ERR_PTR(ret);
+ dev_info = kmalloc_obj(struct rsi_vdevice_info);
+ if (!dev_info) {
+ ret = -ENOMEM;
+ goto dev_unlock;
+ }
+
+ if (rsi_vdev_get_info(vdev_id, virt_to_phys(dev_info))) {
+ ret = -EIO;
+ goto free_dev_info;
+ }
+
+ /* collect the lock nonce */
+ cca_dsc->dev_info.lock_nonce = dev_info->lock_nonce;
+
+ switch (dev_info->hash_algo) {
+ case RSI_HASH_SHA_256:
+ digest_algo = HASH_ALGO_SHA256;
+ break;
+ case RSI_HASH_SHA_512:
+ digest_algo = HASH_ALGO_SHA512;
+ break;
+ default:
+ ret = -EIO;
+ goto free_dev_info;
}
+ pci_tsm_init_evidence(&cca_dsc->pci.base_tsm.evidence,
+ dev_info->id_index, digest_algo);
- return &no_free_ptr(cca_dsc)->pci.base_tsm;
+ /* collect evidence without nonce */
+ ret = cca_collect_dev_evidence(pdev, cca_dsc);
+ if (ret)
+ goto free_dev_info;
+
+ kfree(dev_info);
+ return &cca_dsc->pci.base_tsm;
+
+free_dev_info:
+ kfree(dev_info);
+dev_unlock:
+ cca_device_unlock(pdev);
+free_cca_dsc:
+ kfree(cca_dsc);
+ return ERR_PTR(ret);
}
static void cca_tsm_unlock(struct pci_tsm *tsm)
{
- struct cca_guest_dsc *cca_dsc = to_cca_guest_dsc(tsm->pdev);
+ long ret;
+ struct pci_dev *pdev = tsm->pdev;
+ struct cca_guest_dsc *cca_dsc = to_cca_guest_dsc(pdev);
+
+ /* invalidate dev mapping based on interface report */
+ ret = cca_unmap_evidence_report_range(tsm->pdev);
+ if (ret) {
+ pci_err(tsm->pdev, "failed to invalidate the interface report\n");
+ goto err_out;
+ }
cca_device_unlock(tsm->pdev);
+ pci_tsm_mmio_teardown(cca_dsc->pci.mmio);
+err_out:
+ /*
+ * No error handling from this function. Leave the device locked
+ */
+ pci_tsm_mmio_free(tsm->pdev, cca_dsc->pci.mmio);
kfree(cca_dsc);
}
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.c b/drivers/virt/coco/arm-cca-guest/rsi-da.c
index 9f9e54174813..039138768f8f 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.c
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.c
@@ -6,6 +6,7 @@
#include <linux/pci.h>
#include <linux/mem_encrypt.h>
#include <asm/rsi_cmds.h>
+#include <crypto/hash.h>
#include "rsi-da.h"
#include "rhi-da.h"
@@ -85,3 +86,147 @@ int cca_update_device_object_cache(struct pci_dev *pdev, const u8 *nonce)
return rhi_update_vdev_measurements_cache(pdev, nonce);
}
+
+static inline int
+rsi_validate_dev_mapping(unsigned long vdev_id, phys_addr_t start_ipa,
+ phys_addr_t end_ipa, phys_addr_t io_pa,
+ unsigned long flags, unsigned long lock_nonce,
+ unsigned long meas_nonce, unsigned long report_nonce)
+{
+ unsigned long ret;
+ phys_addr_t next_ipa;
+
+ while (start_ipa < end_ipa) {
+ ret = rsi_vdev_validate_mapping(vdev_id, start_ipa, end_ipa,
+ io_pa, &next_ipa, flags,
+ lock_nonce, meas_nonce, report_nonce);
+ if (ret || next_ipa <= start_ipa || next_ipa > end_ipa)
+ return -EINVAL;
+ io_pa += next_ipa - start_ipa;
+ start_ipa = next_ipa;
+ }
+ return 0;
+}
+
+static inline int rsi_invalidate_dev_mapping(phys_addr_t start_ipa, phys_addr_t end_ipa)
+{
+ return rsi_set_memory_range(start_ipa, end_ipa, RSI_RIPAS_EMPTY,
+ RSI_CHANGE_DESTROYED);
+}
+
+static int cca_apply_evidence_report_range(struct pci_dev *pdev,
+ struct pci_tsm_mmio *mmio, bool map)
+{
+ int i, ret;
+ struct resource *res;
+ unsigned long mmio_flags = 0; /* non coherent, not limited order */
+ int vdev_id = rsi_vdev_id(pdev);
+ struct pci_tsm_mmio_entry *entry;
+ struct cca_guest_dsc *dsc = to_cca_guest_dsc(pdev);
+
+ for (i = 0; i < mmio->nr; i++) {
+ entry = pci_tsm_mmio_entry(mmio, i);
+ res = &entry->res;
+
+ if (res->desc != IORES_DESC_ENCRYPTED)
+ continue;
+
+ if (map)
+ ret = rsi_validate_dev_mapping(vdev_id, res->start,
+ res->end + 1, entry->tsm_offset,
+ mmio_flags,
+ dsc->dev_info.lock_nonce,
+ dsc->dev_info.meas_nonce,
+ dsc->dev_info.report_nonce);
+ else
+ ret = rsi_invalidate_dev_mapping(res->start, res->end + 1);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+int cca_map_evidence_report_range(struct pci_dev *pdev, struct pci_tsm_mmio *mmio)
+{
+ return cca_apply_evidence_report_range(pdev, mmio, true);
+}
+
+int cca_unmap_evidence_report_range(struct pci_dev *pdev)
+{
+ struct cca_guest_dsc *dsc = to_cca_guest_dsc(pdev);
+ struct pci_tsm_mmio *tsm_mmio = dsc->pci.mmio;
+
+ return cca_apply_evidence_report_range(pdev, tsm_mmio, false);
+}
+
+int cca_verify_digest(u64 hash_algo, uint8_t *report,
+ size_t report_size, uint8_t *report_digest)
+{
+ u8 digest[SHA512_DIGEST_SIZE];
+ size_t digest_size;
+ void (*digest_func)(const u8 *data, size_t len, u8 *out);
+
+ switch (hash_algo) {
+ case RSI_HASH_SHA_256:
+ digest_func = sha256;
+ digest_size = SHA256_DIGEST_SIZE;
+ break;
+ case RSI_HASH_SHA_512:
+ digest_func = sha512;
+ digest_size = SHA512_DIGEST_SIZE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ digest_func(report, report_size, digest);
+ if (memcmp(report_digest, digest, digest_size))
+ return -EINVAL;
+
+ return 0;
+}
+
+int cca_verify_digests(u64 hash_algo,
+ uint8_t *certificate, size_t certificate_size,
+ uint8_t *vca, size_t vca_size,
+ uint8_t *interface_report, size_t interface_report_size,
+ uint8_t *measurements, size_t measurements_size,
+ struct rsi_vdevice_info *dev_info)
+{
+ int ret;
+ struct {
+ uint8_t *report;
+ size_t size;
+ uint8_t *digest;
+ } reports[] = {
+ {
+ certificate,
+ certificate_size,
+ dev_info->identity_digest
+ },
+ {
+ vca,
+ vca_size,
+ dev_info->protocol_data_digest
+ },
+ {
+ interface_report,
+ interface_report_size,
+ dev_info->report_digest
+ },
+ {
+ measurements,
+ measurements_size,
+ dev_info->meas_digest
+ }
+
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(reports); i++) {
+ ret = cca_verify_digest(hash_algo, reports[i].report,
+ reports[i].size, reports[i].digest);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.h b/drivers/virt/coco/arm-cca-guest/rsi-da.h
index 88067d2230ab..07a044d3e335 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.h
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.h
@@ -9,11 +9,23 @@
#include <linux/pci.h>
#include <linux/pci-tsm.h>
#include <asm/rsi_smc.h>
+#include <crypto/sha2.h>
#define MAX_CACHE_OBJ_SIZE SZ_16M
+struct dsm_device_info {
+ u64 lock_nonce;
+ u64 meas_nonce;
+ u64 report_nonce;
+ u8 cert_digest[SHA512_DIGEST_SIZE];
+ u8 vca_digest[SHA512_DIGEST_SIZE];
+ u8 meas_digest[SHA512_DIGEST_SIZE];
+ u8 report_digest[SHA512_DIGEST_SIZE];
+};
+
struct cca_guest_dsc {
struct pci_tsm_devsec pci;
+ struct dsm_device_info dev_info;
};
static inline struct cca_guest_dsc *to_cca_guest_dsc(struct pci_dev *pdev)
@@ -39,5 +51,15 @@ int cca_device_unlock(struct pci_dev *pdev);
int cca_update_device_object_cache(struct pci_dev *pdev, const u8 *nonce);
struct page *alloc_shared_pages(int nid, gfp_t gfp_mask, unsigned long min_size);
int free_shared_pages(struct page *page, unsigned long min_size);
+int cca_map_evidence_report_range(struct pci_dev *pdev, struct pci_tsm_mmio *mmio);
+int cca_unmap_evidence_report_range(struct pci_dev *pdev);
+int cca_verify_digest(u64 hash_algo, uint8_t *report,
+ size_t report_size, uint8_t *report_digest);
+int cca_verify_digests(u64 hash_algo,
+ uint8_t *certificate, size_t certificate_size,
+ uint8_t *vca, size_t vca_size,
+ uint8_t *interface_report, size_t interface_report_size,
+ uint8_t *measurements, size_t measurements_size,
+ struct rsi_vdevice_info *dev_info);
#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 07/11] coco: guest: arm64: Add guest APIs to read host-cached DA objects
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:28 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
Introduce guest-side helpers to read host-cached DA objects
(certificate, VCA, interface report, and measurements).
Add RHI_DA_OBJECT_SIZE and RHI_DA_OBJECT_READ definitions, then implement
rhi_read_cached_object() that:
- queries object size from host
- validates size against MAX_CACHE_OBJ_SIZE
- allocates a shared buffer
- issues OBJECT_READ into shared memory
- copies data into private memory and frees shared pages
Export the helper for later evidence-collection and verification code.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rhi.h | 8 +++
drivers/virt/coco/arm-cca-guest/rhi-da.c | 71 ++++++++++++++++++++++++
drivers/virt/coco/arm-cca-guest/rhi-da.h | 1 +
drivers/virt/coco/arm-cca-guest/rsi-da.h | 2 +
4 files changed, 82 insertions(+)
diff --git a/arch/arm64/include/asm/rhi.h b/arch/arm64/include/asm/rhi.h
index 2b56a7760904..dc7a57370945 100644
--- a/arch/arm64/include/asm/rhi.h
+++ b/arch/arm64/include/asm/rhi.h
@@ -48,6 +48,14 @@ unsigned long rhi_get_ipa_change_alignment(void);
RHI_DA_FEATURE_VDEV_SET_TDI_STATE)
#define RHI_DA_FEATURES SMC_RHI_CALL(0x004B)
+#define RHI_DA_OBJECT_VCA 0x0
+#define RHI_DA_OBJECT_CERTIFICATE 0x1
+#define RHI_DA_OBJECT_MEASUREMENT 0x2
+#define RHI_DA_OBJECT_INTERFACE_REPORT 0x3
+#define RHI_DA_OBJECT_EXTENSION_EVIDENCE 0x4
+#define RHI_DA_OBJECT_SIZE SMC_RHI_CALL(0x004C)
+#define RHI_DA_OBJECT_READ SMC_RHI_CALL(0x004D)
+
#define RHI_DA_VDEV_CONTINUE SMC_RHI_CALL(0x0051)
#define RHI_VDEV_MEASURE_HASH 0x0
diff --git a/drivers/virt/coco/arm-cca-guest/rhi-da.c b/drivers/virt/coco/arm-cca-guest/rhi-da.c
index d0f5ae320f83..73c599802a93 100644
--- a/drivers/virt/coco/arm-cca-guest/rhi-da.c
+++ b/drivers/virt/coco/arm-cca-guest/rhi-da.c
@@ -3,6 +3,8 @@
* Copyright (C) 2026 ARM Ltd.
*/
+#include <linux/string.h>
+
#include "rsi-da.h"
#include "rhi-da.h"
@@ -283,3 +285,72 @@ int rhi_update_vdev_measurements_cache(struct pci_dev *pdev, const u8 *nonce)
pci_err(pdev, "failed to get device measurement (%d)\n", ret);
return ret;
}
+
+int rhi_read_cached_object(int vdev_id, int da_object_type, void **object, int *object_size)
+{
+ int ret;
+ int data_size;
+ void *data_buf_shared;
+ struct page *shared_pages;
+
+ *object_size = 0;
+ *object = NULL;
+
+ struct rsi_host_call *rhicall __free(kfree) =
+ kmalloc(sizeof(struct rsi_host_call), GFP_KERNEL);
+ if (!rhicall)
+ return -ENOMEM;
+
+ rhicall->imm = 0;
+ rhicall->gprs[0] = RHI_DA_OBJECT_SIZE;
+ rhicall->gprs[1] = vdev_id;
+ rhicall->gprs[2] = da_object_type;
+
+ ret = rsi_host_call(rhicall);
+ if (ret != RSI_SUCCESS)
+ return -EIO;
+
+ if (rhicall->gprs[0] != RHI_DA_SUCCESS)
+ return -EIO;
+
+ /* validate against the max cache object size used on host. */
+ data_size = rhicall->gprs[1];
+ if (data_size > MAX_CACHE_OBJ_SIZE || data_size == 0)
+ return -EIO;
+
+ shared_pages = alloc_shared_pages(NUMA_NO_NODE, GFP_KERNEL, data_size);
+ if (!shared_pages)
+ return -ENOMEM;
+
+ data_buf_shared = page_address(shared_pages);
+
+ rhicall->imm = 0;
+ rhicall->gprs[0] = RHI_DA_OBJECT_READ;
+ rhicall->gprs[1] = vdev_id;
+ rhicall->gprs[2] = da_object_type;
+ rhicall->gprs[3] = virt_to_phys(data_buf_shared);
+ rhicall->gprs[4] = data_size;
+ rhicall->gprs[5] = 0; /* offset to read from */
+ ret = rsi_host_call(rhicall);
+ if (ret != RSI_SUCCESS || rhicall->gprs[0] != RHI_DA_SUCCESS) {
+ free_shared_pages(shared_pages, data_size);
+ return -EIO;
+ }
+
+ if (data_size != rhicall->gprs[1]) {
+ /* Short read */
+ free_shared_pages(shared_pages, data_size);
+ return -EIO;
+ }
+
+ void *data_buf_private = kvmemdup(data_buf_shared,
+ data_size, GFP_KERNEL);
+ /* free the shared pages irrespective of error condition */
+ free_shared_pages(shared_pages, data_size);
+ if (!data_buf_private)
+ return -ENOMEM;
+
+ *object = data_buf_private;
+ *object_size = data_size;
+ return 0;
+}
diff --git a/drivers/virt/coco/arm-cca-guest/rhi-da.h b/drivers/virt/coco/arm-cca-guest/rhi-da.h
index d32ccc48c0d0..f7655d7ecf18 100644
--- a/drivers/virt/coco/arm-cca-guest/rhi-da.h
+++ b/drivers/virt/coco/arm-cca-guest/rhi-da.h
@@ -13,4 +13,5 @@ bool rhi_has_da_support(void);
int rhi_vdev_set_tdi_state(struct pci_dev *pdev, enum rhi_tdi_state target_state);
int rhi_update_vdev_interface_report_cache(struct pci_dev *pdev);
int rhi_update_vdev_measurements_cache(struct pci_dev *pdev, const u8 *nonce);
+int rhi_read_cached_object(int vdev_id, int da_object_type, void **object, int *object_size);
#endif
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.h b/drivers/virt/coco/arm-cca-guest/rsi-da.h
index 297cb800edc0..88067d2230ab 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.h
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.h
@@ -10,6 +10,8 @@
#include <linux/pci-tsm.h>
#include <asm/rsi_smc.h>
+#define MAX_CACHE_OBJ_SIZE SZ_16M
+
struct cca_guest_dsc {
struct pci_tsm_devsec pci;
};
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 06/11] coco: guest: arm64: Add measurement refresh via RHI_DA_VDEV_GET_MEASUREMENTS
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:28 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
Add guest support to request fresh device measurements using
RHI_DA_VDEV_GET_MEASUREMENTS.
Define measurement request parameters (flags + nonce), request RAW
measurements, and implement cookie-based continuation for incomplete DA
operations. Extend cca_update_device_object_cache() to refresh both
interface report and measurements.
Because RHI buffers are shared with the host, add shared-page allocation
helpers that convert pages to decrypted/shared memory before use and restore
them to encrypted/private state on free.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rhi.h | 11 ++++
drivers/virt/coco/arm-cca-guest/arm-cca.c | 1 +
drivers/virt/coco/arm-cca-guest/rhi-da.c | 78 +++++++++++++++++++++++
drivers/virt/coco/arm-cca-guest/rhi-da.h | 1 +
drivers/virt/coco/arm-cca-guest/rsi-da.c | 42 +++++++++++-
drivers/virt/coco/arm-cca-guest/rsi-da.h | 2 +
6 files changed, 134 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/rhi.h b/arch/arm64/include/asm/rhi.h
index 15946fe64484..2b56a7760904 100644
--- a/arch/arm64/include/asm/rhi.h
+++ b/arch/arm64/include/asm/rhi.h
@@ -50,6 +50,17 @@ unsigned long rhi_get_ipa_change_alignment(void);
#define RHI_DA_VDEV_CONTINUE SMC_RHI_CALL(0x0051)
+#define RHI_VDEV_MEASURE_HASH 0x0
+#define RHI_VDEV_MEASURE_RAW 0x1
+struct rhi_vdev_measurement_params {
+ union {
+ u64 flags;
+ u8 padding0[0x100];
+ };
+ u8 nonce[32];
+};
+#define RHI_DA_VDEV_GET_MEASUREMENTS SMC_RHI_CALL(0x0052)
+
#define RHI_DA_VDEV_GET_INTERFACE_REPORT SMC_RHI_CALL(0x0053)
enum rhi_tdi_state {
diff --git a/drivers/virt/coco/arm-cca-guest/arm-cca.c b/drivers/virt/coco/arm-cca-guest/arm-cca.c
index 411cbbaa5d26..0f9cfb329a06 100644
--- a/drivers/virt/coco/arm-cca-guest/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-guest/arm-cca.c
@@ -215,6 +215,7 @@ static struct pci_tsm *cca_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pde
if (ret)
return ERR_PTR(ret);
+ /* collect evidence without nonce */
ret = cca_update_device_object_cache(pdev, NULL);
if (ret) {
cca_device_unlock(pdev);
diff --git a/drivers/virt/coco/arm-cca-guest/rhi-da.c b/drivers/virt/coco/arm-cca-guest/rhi-da.c
index 5078136a4cc6..d0f5ae320f83 100644
--- a/drivers/virt/coco/arm-cca-guest/rhi-da.c
+++ b/drivers/virt/coco/arm-cca-guest/rhi-da.c
@@ -205,3 +205,81 @@ int rhi_update_vdev_interface_report_cache(struct pci_dev *pdev)
return ret;
}
+
+static inline int rhi_vdev_get_measurements(unsigned long vdev_id,
+ phys_addr_t vdev_meas_phys, unsigned long *cookie)
+{
+ unsigned long ret;
+
+ struct rsi_host_call *rhi_call __free(kfree) =
+ kmalloc(sizeof(*rhi_call), GFP_KERNEL);
+ if (!rhi_call)
+ return -ENOMEM;
+
+ rhi_call->imm = 0;
+ rhi_call->gprs[0] = RHI_DA_VDEV_GET_MEASUREMENTS;
+ rhi_call->gprs[1] = vdev_id;
+ rhi_call->gprs[2] = vdev_meas_phys;
+
+ ret = rsi_host_call(rhi_call);
+ if (ret != RSI_SUCCESS)
+ return -EIO;
+
+ *cookie = rhi_call->gprs[1];
+ return map_rhi_da_error(rhi_call->gprs[0]);
+}
+
+static inline struct rhi_vdev_measurement_params *alloc_vdev_meas_params(void)
+{
+ struct page *pages;
+
+ pages = alloc_shared_pages(NUMA_NO_NODE, GFP_KERNEL,
+ sizeof(struct rhi_vdev_measurement_params));
+ if (!pages)
+ return NULL;
+ return page_address(pages);
+}
+
+static inline void vdev_meas_params_free(struct rhi_vdev_measurement_params *params)
+{
+ struct page *pages = virt_to_page(params);
+
+ free_shared_pages(pages, sizeof(struct rhi_vdev_measurement_params));
+}
+
+DEFINE_FREE(vdev_meas_params_free, struct rhi_vdev_measurement_params *, if (_T) vdev_meas_params_free(_T))
+int rhi_update_vdev_measurements_cache(struct pci_dev *pdev, const u8 *nonce)
+{
+ int ret;
+ unsigned long cookie;
+ int vdev_id = rsi_vdev_id(pdev);
+ phys_addr_t vdev_meas_phys;
+
+ struct rhi_vdev_measurement_params *dev_meas __free(vdev_meas_params_free) =
+ alloc_vdev_meas_params();
+ if (!dev_meas)
+ return -ENOMEM;
+
+ vdev_meas_phys = virt_to_phys(dev_meas);
+ /* request for raw bitstream */
+ dev_meas->flags = RHI_VDEV_MEASURE_RAW;
+ if (nonce)
+ memcpy(dev_meas->nonce, nonce, 32);
+
+ for (;;) {
+ ret = rhi_vdev_get_measurements(vdev_id, vdev_meas_phys, &cookie);
+ if (ret != -EBUSY)
+ break;
+ cond_resched();
+ }
+
+ while (ret == RHI_DA_INCOMPLETE) {
+ if (should_abort_rhi_call_loop(vdev_id))
+ return -EINTR;
+ ret = rhi_vdev_continue(vdev_id, cookie);
+ }
+
+ if (ret)
+ pci_err(pdev, "failed to get device measurement (%d)\n", ret);
+ return ret;
+}
diff --git a/drivers/virt/coco/arm-cca-guest/rhi-da.h b/drivers/virt/coco/arm-cca-guest/rhi-da.h
index 8b7faf4d1c8a..d32ccc48c0d0 100644
--- a/drivers/virt/coco/arm-cca-guest/rhi-da.h
+++ b/drivers/virt/coco/arm-cca-guest/rhi-da.h
@@ -12,4 +12,5 @@ struct pci_dev;
bool rhi_has_da_support(void);
int rhi_vdev_set_tdi_state(struct pci_dev *pdev, enum rhi_tdi_state target_state);
int rhi_update_vdev_interface_report_cache(struct pci_dev *pdev);
+int rhi_update_vdev_measurements_cache(struct pci_dev *pdev, const u8 *nonce);
#endif
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.c b/drivers/virt/coco/arm-cca-guest/rsi-da.c
index 6c78f0e2f3a1..9f9e54174813 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.c
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.c
@@ -4,6 +4,7 @@
*/
#include <linux/pci.h>
+#include <linux/mem_encrypt.h>
#include <asm/rsi_cmds.h>
#include "rsi-da.h"
@@ -33,6 +34,45 @@ int cca_device_unlock(struct pci_dev *pdev)
return 0;
}
+struct page *alloc_shared_pages(int nid, gfp_t gfp_mask, unsigned long min_size)
+{
+ int ret;
+ struct page *page;
+ /* We should normalize the size based on hypervisor page size */
+ int page_order = get_order(min_size);
+
+ page = alloc_pages_node(nid, gfp_mask | __GFP_ZERO, page_order);
+ if (!page)
+ return NULL;
+
+ ret = set_memory_decrypted((unsigned long)page_address(page),
+ 1 << page_order);
+ /*
+ * If set_memory_decrypted() fails then we don't know what state the
+ * page is in, so we can't free it. Instead we leak it.
+ * set_memory_decrypted() will already have WARNed.
+ */
+ if (ret)
+ return NULL;
+
+ return page;
+}
+
+int free_shared_pages(struct page *page, unsigned long size)
+{
+ int ret;
+ /* We should normalize the size based on hypervisor page size */
+ int page_order = get_order(size);
+
+ ret = set_memory_encrypted((unsigned long)page_address(page), 1 << page_order);
+ /* If we fail to mark it encrypted don't free it back */
+ if (ret)
+ return ret;
+
+ __free_pages(page, page_order);
+ return 0;
+}
+
int cca_update_device_object_cache(struct pci_dev *pdev, const u8 *nonce)
{
int ret;
@@ -43,5 +83,5 @@ int cca_update_device_object_cache(struct pci_dev *pdev, const u8 *nonce)
return ret;
}
- return 0;
+ return rhi_update_vdev_measurements_cache(pdev, nonce);
}
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.h b/drivers/virt/coco/arm-cca-guest/rsi-da.h
index dda8026a1c3f..297cb800edc0 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.h
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.h
@@ -35,5 +35,7 @@ static inline int rsi_vdev_id(struct pci_dev *pdev)
int cca_device_lock(struct pci_dev *pdev);
int cca_device_unlock(struct pci_dev *pdev);
int cca_update_device_object_cache(struct pci_dev *pdev, const u8 *nonce);
+struct page *alloc_shared_pages(int nid, gfp_t gfp_mask, unsigned long min_size);
+int free_shared_pages(struct page *page, unsigned long min_size);
#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 05/11] coco: guest: arm64: Refresh interface-report cache during device lock
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:27 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
Add support for RHI_DA_VDEV_GET_INTERFACE_REPORT and use it to refresh the
host-side cached interface report when a device is locked.
Implement rhi_update_vdev_interface_report_cache() with busy retry and
cookie-based CONTINUE handling for incomplete operations. Surface the flow
through cca_update_device_object_cache(), and call it from the lock path so
the interface report is fetched before lock succeeds.
On refresh failure, unwind by unlocking the device and returning the error.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rhi.h | 2 ++
drivers/virt/coco/arm-cca-guest/arm-cca.c | 6 ++++
drivers/virt/coco/arm-cca-guest/rhi-da.c | 44 +++++++++++++++++++++++
drivers/virt/coco/arm-cca-guest/rhi-da.h | 1 +
drivers/virt/coco/arm-cca-guest/rsi-da.c | 13 +++++++
drivers/virt/coco/arm-cca-guest/rsi-da.h | 1 +
6 files changed, 67 insertions(+)
diff --git a/arch/arm64/include/asm/rhi.h b/arch/arm64/include/asm/rhi.h
index 88de2d9b34d1..15946fe64484 100644
--- a/arch/arm64/include/asm/rhi.h
+++ b/arch/arm64/include/asm/rhi.h
@@ -50,6 +50,8 @@ unsigned long rhi_get_ipa_change_alignment(void);
#define RHI_DA_VDEV_CONTINUE SMC_RHI_CALL(0x0051)
+#define RHI_DA_VDEV_GET_INTERFACE_REPORT SMC_RHI_CALL(0x0053)
+
enum rhi_tdi_state {
RHI_DA_TDI_CONFIG_UNLOCKED,
RHI_DA_TDI_CONFIG_LOCKED,
diff --git a/drivers/virt/coco/arm-cca-guest/arm-cca.c b/drivers/virt/coco/arm-cca-guest/arm-cca.c
index d4880ca59fc5..411cbbaa5d26 100644
--- a/drivers/virt/coco/arm-cca-guest/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-guest/arm-cca.c
@@ -215,6 +215,12 @@ static struct pci_tsm *cca_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pde
if (ret)
return ERR_PTR(ret);
+ ret = cca_update_device_object_cache(pdev, NULL);
+ if (ret) {
+ cca_device_unlock(pdev);
+ return ERR_PTR(ret);
+ }
+
return &no_free_ptr(cca_dsc)->pci.base_tsm;
}
diff --git a/drivers/virt/coco/arm-cca-guest/rhi-da.c b/drivers/virt/coco/arm-cca-guest/rhi-da.c
index 5b48c8d6ebe3..5078136a4cc6 100644
--- a/drivers/virt/coco/arm-cca-guest/rhi-da.c
+++ b/drivers/virt/coco/arm-cca-guest/rhi-da.c
@@ -161,3 +161,47 @@ int rhi_vdev_set_tdi_state(struct pci_dev *pdev, enum rhi_tdi_state target_state
return ret;
}
+
+static inline int rhi_vdev_get_interface_report(unsigned long vdev_id,
+ unsigned long *cookie)
+{
+ unsigned long ret;
+
+ struct rsi_host_call *rhi_call __free(kfree) =
+ kmalloc(sizeof(struct rsi_host_call), GFP_KERNEL);
+ if (!rhi_call)
+ return -ENOMEM;
+
+ rhi_call->imm = 0;
+ rhi_call->gprs[0] = RHI_DA_VDEV_GET_INTERFACE_REPORT;
+ rhi_call->gprs[1] = vdev_id;
+
+ ret = rsi_host_call(rhi_call);
+ if (ret != RSI_SUCCESS)
+ return -EIO;
+
+ *cookie = rhi_call->gprs[1];
+ return map_rhi_da_error(rhi_call->gprs[0]);
+}
+
+int rhi_update_vdev_interface_report_cache(struct pci_dev *pdev)
+{
+ int ret;
+ unsigned long cookie;
+ int vdev_id = rsi_vdev_id(pdev);
+
+ for (;;) {
+ ret = rhi_vdev_get_interface_report(vdev_id, &cookie);
+ if (ret != -EBUSY)
+ break;
+ cond_resched();
+ }
+
+ while (ret == RHI_DA_INCOMPLETE) {
+ if (should_abort_rhi_call_loop(vdev_id))
+ return -EINTR;
+ ret = rhi_vdev_continue(vdev_id, cookie);
+ }
+
+ return ret;
+}
diff --git a/drivers/virt/coco/arm-cca-guest/rhi-da.h b/drivers/virt/coco/arm-cca-guest/rhi-da.h
index 43c1cda8738d..8b7faf4d1c8a 100644
--- a/drivers/virt/coco/arm-cca-guest/rhi-da.h
+++ b/drivers/virt/coco/arm-cca-guest/rhi-da.h
@@ -11,4 +11,5 @@
struct pci_dev;
bool rhi_has_da_support(void);
int rhi_vdev_set_tdi_state(struct pci_dev *pdev, enum rhi_tdi_state target_state);
+int rhi_update_vdev_interface_report_cache(struct pci_dev *pdev);
#endif
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.c b/drivers/virt/coco/arm-cca-guest/rsi-da.c
index 2c3017933fb0..6c78f0e2f3a1 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.c
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.c
@@ -32,3 +32,16 @@ int cca_device_unlock(struct pci_dev *pdev)
}
return 0;
}
+
+int cca_update_device_object_cache(struct pci_dev *pdev, const u8 *nonce)
+{
+ int ret;
+
+ ret = rhi_update_vdev_interface_report_cache(pdev);
+ if (ret) {
+ pci_err(pdev, "failed to get interface report (%d)\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.h b/drivers/virt/coco/arm-cca-guest/rsi-da.h
index 06fcea95d888..dda8026a1c3f 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.h
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.h
@@ -34,5 +34,6 @@ static inline int rsi_vdev_id(struct pci_dev *pdev)
int cca_device_lock(struct pci_dev *pdev);
int cca_device_unlock(struct pci_dev *pdev);
+int cca_update_device_object_cache(struct pci_dev *pdev, const u8 *nonce);
#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 04/11] coco: guest: arm64: Support guest-initiated TDI lock/unlock transitions
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:27 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
Add guest helpers to drive TDI state transitions through RHI:
- cca_device_lock() -> RHI_DA_TDI_CONFIG_LOCKED
- cca_device_unlock() -> RHI_DA_TDI_CONFIG_UNLOCKED
Use these helpers in the PCI TSM lock/unlock callbacks so a successful
lock path returns a live pci_tsm handle and unlock transitions the device
back to unlocked state.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/virt/coco/arm-cca-guest/Makefile | 2 +-
drivers/virt/coco/arm-cca-guest/arm-cca.c | 9 ++++--
drivers/virt/coco/arm-cca-guest/rsi-da.c | 34 +++++++++++++++++++++++
drivers/virt/coco/arm-cca-guest/rsi-da.h | 3 ++
4 files changed, 45 insertions(+), 3 deletions(-)
create mode 100644 drivers/virt/coco/arm-cca-guest/rsi-da.c
diff --git a/drivers/virt/coco/arm-cca-guest/Makefile b/drivers/virt/coco/arm-cca-guest/Makefile
index 65c4cc52c154..11db7af095c9 100644
--- a/drivers/virt/coco/arm-cca-guest/Makefile
+++ b/drivers/virt/coco/arm-cca-guest/Makefile
@@ -2,4 +2,4 @@
obj-$(CONFIG_ARM_CCA_GUEST) += arm-cca-guest.o
arm-cca-guest-y += arm-cca.o
-arm-cca-guest-$(CONFIG_PCI_TSM) += rhi-da.o
+arm-cca-guest-$(CONFIG_PCI_TSM) += rhi-da.o rsi-da.o
diff --git a/drivers/virt/coco/arm-cca-guest/arm-cca.c b/drivers/virt/coco/arm-cca-guest/arm-cca.c
index 0c12aae85e6d..d4880ca59fc5 100644
--- a/drivers/virt/coco/arm-cca-guest/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-guest/arm-cca.c
@@ -211,14 +211,19 @@ static struct pci_tsm *cca_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pde
if (ret)
return ERR_PTR(ret);
- /* For now always return an error */
- return ERR_PTR(-EIO);
+ ret = cca_device_lock(pdev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return &no_free_ptr(cca_dsc)->pci.base_tsm;
}
static void cca_tsm_unlock(struct pci_tsm *tsm)
{
struct cca_guest_dsc *cca_dsc = to_cca_guest_dsc(tsm->pdev);
+ cca_device_unlock(tsm->pdev);
+
kfree(cca_dsc);
}
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.c b/drivers/virt/coco/arm-cca-guest/rsi-da.c
new file mode 100644
index 000000000000..2c3017933fb0
--- /dev/null
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 ARM Ltd.
+ */
+
+#include <linux/pci.h>
+#include <asm/rsi_cmds.h>
+
+#include "rsi-da.h"
+#include "rhi-da.h"
+
+int cca_device_lock(struct pci_dev *pdev)
+{
+ int ret;
+
+ ret = rhi_vdev_set_tdi_state(pdev, RHI_DA_TDI_CONFIG_LOCKED);
+ if (ret) {
+ pci_err(pdev, "failed to lock the device (%d)\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+int cca_device_unlock(struct pci_dev *pdev)
+{
+ int ret;
+
+ ret = rhi_vdev_set_tdi_state(pdev, RHI_DA_TDI_CONFIG_UNLOCKED);
+ if (ret) {
+ pci_err(pdev, "failed to unlock the device (%d)\n", ret);
+ return ret;
+ }
+ return 0;
+}
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.h b/drivers/virt/coco/arm-cca-guest/rsi-da.h
index 858bfdaf59c9..06fcea95d888 100644
--- a/drivers/virt/coco/arm-cca-guest/rsi-da.h
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.h
@@ -32,4 +32,7 @@ static inline int rsi_vdev_id(struct pci_dev *pdev)
PCI_DEVID(pdev->bus->number, pdev->devfn);
}
+int cca_device_lock(struct pci_dev *pdev);
+int cca_device_unlock(struct pci_dev *pdev);
+
#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 03/11] coco: guest: arm64: Add Realm Host Interface and guest DA helper
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:27 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
- Add guest-side rhi-da helper that drives the vdev TDI state machine
via RHI host calls and translates the firmware status codes
This provides the basic RHI plumbing that later DA features rely on.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rhi.h | 38 +++++
drivers/virt/coco/arm-cca-guest/Makefile | 1 +
drivers/virt/coco/arm-cca-guest/arm-cca.c | 3 +-
drivers/virt/coco/arm-cca-guest/rhi-da.c | 163 ++++++++++++++++++++++
drivers/virt/coco/arm-cca-guest/rhi-da.h | 14 ++
5 files changed, 218 insertions(+), 1 deletion(-)
create mode 100644 drivers/virt/coco/arm-cca-guest/rhi-da.c
create mode 100644 drivers/virt/coco/arm-cca-guest/rhi-da.h
diff --git a/arch/arm64/include/asm/rhi.h b/arch/arm64/include/asm/rhi.h
index 0895dd92ea1d..88de2d9b34d1 100644
--- a/arch/arm64/include/asm/rhi.h
+++ b/arch/arm64/include/asm/rhi.h
@@ -21,4 +21,42 @@ unsigned long rhi_get_ipa_change_alignment(void);
#define __RHI_HOSTCONF_GET_IPA_CHANGE_ALIGNMENT BIT(0)
#define RHI_HOSTCONF_FEATURES SMC_RHI_CALL(0x004F)
#define RHI_HOSTCONF_GET_IPA_CHANGE_ALIGNMENT SMC_RHI_CALL(0x0050)
+
+#define RHI_DA_SUCCESS 0x0
+#define RHI_DA_INCOMPLETE 0x1
+#define RHI_DA_ERROR_DATA_NOT_AVAILABLE 0x2
+#define RHI_DA_ERROR_INVALID_VDEV_ID 0x3
+#define RHI_DA_ERROR_INVALID_OBJECT 0x4
+#define RHI_DA_ERROR_INPUT 0x5
+#define RHI_DA_ERROR_DEVICE 0x6
+#define RHI_DA_ERROR_INVALID_OFFSET 0x7
+#define RHI_DA_ERROR_ACCESS_FAILED 0x8
+#define RHI_DA_ERROR_BUSY 0x9
+#define RHI_DA_ABORTED_OPERATION_HAD_COMPLETED 0xA
+
+#define RHI_DA_FEATURE_OBJECT_SIZE BIT(0)
+#define RHI_DA_FEATURE_OBJECT_READ BIT(1)
+#define RHI_DA_FEATURE_VDEV_CONTINUE BIT(2)
+#define RHI_DA_FEATURE_VDEV_GET_MEASUREMENT BIT(3)
+#define RHI_DA_FEATURE_VDEV_GET_INTF_REPORT BIT(4)
+#define RHI_DA_FEATURE_VDEV_SET_TDI_STATE BIT(5)
+
+#define RHI_DA_BASE_FEATURE (RHI_DA_FEATURE_OBJECT_SIZE | \
+ RHI_DA_FEATURE_OBJECT_READ | \
+ RHI_DA_FEATURE_VDEV_GET_INTF_REPORT | \
+ RHI_DA_FEATURE_VDEV_GET_MEASUREMENT | \
+ RHI_DA_FEATURE_VDEV_SET_TDI_STATE)
+#define RHI_DA_FEATURES SMC_RHI_CALL(0x004B)
+
+#define RHI_DA_VDEV_CONTINUE SMC_RHI_CALL(0x0051)
+
+enum rhi_tdi_state {
+ RHI_DA_TDI_CONFIG_UNLOCKED,
+ RHI_DA_TDI_CONFIG_LOCKED,
+ RHI_DA_TDI_CONFIG_RUN,
+};
+#define RHI_DA_VDEV_SET_TDI_STATE SMC_RHI_CALL(0x0054)
+
+#define RHI_DA_VDEV_ABORT SMC_RHI_CALL(0x0056)
+
#endif
diff --git a/drivers/virt/coco/arm-cca-guest/Makefile b/drivers/virt/coco/arm-cca-guest/Makefile
index 75a120e24fda..65c4cc52c154 100644
--- a/drivers/virt/coco/arm-cca-guest/Makefile
+++ b/drivers/virt/coco/arm-cca-guest/Makefile
@@ -2,3 +2,4 @@
obj-$(CONFIG_ARM_CCA_GUEST) += arm-cca-guest.o
arm-cca-guest-y += arm-cca.o
+arm-cca-guest-$(CONFIG_PCI_TSM) += rhi-da.o
diff --git a/drivers/virt/coco/arm-cca-guest/arm-cca.c b/drivers/virt/coco/arm-cca-guest/arm-cca.c
index 6f13c54fab73..0c12aae85e6d 100644
--- a/drivers/virt/coco/arm-cca-guest/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-guest/arm-cca.c
@@ -17,6 +17,7 @@
#ifdef CONFIG_PCI_TSM
#include "rsi-da.h"
+#include "rhi-da.h"
#endif
/**
@@ -265,7 +266,7 @@ static int cca_devsec_tsm_probe(struct auxiliary_device *adev,
#ifdef CONFIG_PCI_TSM
/* Allow tsm report even if tsm_register fails */
- if (rsi_has_da_feature())
+ if (rsi_has_da_feature() && rhi_has_da_support())
cca_devsec_tsm_register(adev);
#endif
diff --git a/drivers/virt/coco/arm-cca-guest/rhi-da.c b/drivers/virt/coco/arm-cca-guest/rhi-da.c
new file mode 100644
index 000000000000..5b48c8d6ebe3
--- /dev/null
+++ b/drivers/virt/coco/arm-cca-guest/rhi-da.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#include "rsi-da.h"
+#include "rhi-da.h"
+
+/**
+ * map_rhi_da_error - Map an RHI DA status to Linux errno
+ * @rhi_da_error: RHI DA status value to translate
+ *
+ * Return: 0 for %RHI_DA_SUCCESS, %RHI_DA_INCOMPLETE when the caller must
+ * continue the operation with rhi_vdev_continue(), or a negative errno for
+ * all other failures. Unknown status codes are mapped to -EIO.
+ */
+static inline int map_rhi_da_error(unsigned long rhi_da_error)
+{
+ switch (rhi_da_error) {
+ case RHI_DA_SUCCESS:
+ return 0;
+ case RHI_DA_INCOMPLETE:
+ return RHI_DA_INCOMPLETE;
+ case RHI_DA_ERROR_BUSY:
+ return -EBUSY;
+ case RHI_DA_ERROR_INPUT:
+ case RHI_DA_ERROR_INVALID_VDEV_ID:
+ return -EINVAL;
+ case RHI_DA_ERROR_ACCESS_FAILED:
+ return -EFAULT;
+ case RHI_DA_ERROR_DEVICE:
+ return -EIO;
+ case RHI_DA_ERROR_INVALID_OBJECT:
+ return -EINVAL;
+ default:
+ return -EIO;
+ }
+}
+
+bool rhi_has_da_support(void)
+{
+ int ret;
+
+ struct rsi_host_call *rhi_call __free(kfree) =
+ kmalloc(sizeof(*rhi_call), GFP_KERNEL);
+ if (!rhi_call)
+ return -ENOMEM;
+
+ rhi_call->imm = 0;
+ rhi_call->gprs[0] = RHI_DA_FEATURES;
+
+ ret = rsi_host_call(rhi_call);
+ if (ret != RSI_SUCCESS || rhi_call->gprs[0] == SMCCC_RET_NOT_SUPPORTED)
+ return false;
+
+ /* For base DA to work we need these to be supported */
+ if ((rhi_call->gprs[0] & RHI_DA_BASE_FEATURE) == RHI_DA_BASE_FEATURE)
+ return true;
+
+ return false;
+}
+
+static inline int rhi_vdev_continue(unsigned long vdev_id, unsigned long cookie)
+{
+ unsigned long ret;
+
+ struct rsi_host_call *rhi_call __free(kfree) =
+ kmalloc(sizeof(*rhi_call), GFP_KERNEL);
+ if (!rhi_call)
+ return -ENOMEM;
+
+ rhi_call->imm = 0;
+ rhi_call->gprs[0] = RHI_DA_VDEV_CONTINUE;
+ rhi_call->gprs[1] = vdev_id;
+ rhi_call->gprs[2] = cookie;
+
+ ret = rsi_host_call(rhi_call);
+ if (ret != RSI_SUCCESS)
+ return -EIO;
+
+ return map_rhi_da_error(rhi_call->gprs[0]);
+}
+
+static int __rhi_vdev_abort(unsigned long vdev_id, unsigned long *da_error)
+{
+ unsigned long ret;
+ struct rsi_host_call *rhi_call __free(kfree) =
+ kmalloc(sizeof(struct rsi_host_call), GFP_KERNEL);
+ if (!rhi_call)
+ return -ENOMEM;
+
+ rhi_call->imm = 0;
+ rhi_call->gprs[0] = RHI_DA_VDEV_ABORT;
+ rhi_call->gprs[1] = vdev_id;
+
+ ret = rsi_host_call(rhi_call);
+ if (ret != RSI_SUCCESS)
+ return -EIO;
+
+ *da_error = rhi_call->gprs[0];
+ return 0;
+}
+
+static bool should_abort_rhi_call_loop(unsigned long vdev_id)
+{
+ int ret;
+
+ cond_resched();
+ if (signal_pending(current)) {
+ unsigned long da_error;
+
+ ret = __rhi_vdev_abort(vdev_id, &da_error);
+ /* consider all kind of error as not aborted */
+ if (!ret && (da_error == RHI_DA_SUCCESS))
+ return true;
+ }
+ return false;
+}
+
+static int __rhi_vdev_set_tdi_state(unsigned long vdev_id,
+ enum rhi_tdi_state target_state, unsigned long *cookie)
+{
+ unsigned long ret;
+
+ struct rsi_host_call *rhi_call __free(kfree) =
+ kmalloc(sizeof(struct rsi_host_call), GFP_KERNEL);
+ if (!rhi_call)
+ return -ENOMEM;
+
+ rhi_call->imm = 0;
+ rhi_call->gprs[0] = RHI_DA_VDEV_SET_TDI_STATE;
+ rhi_call->gprs[1] = vdev_id;
+ rhi_call->gprs[2] = target_state;
+
+ ret = rsi_host_call(rhi_call);
+ if (ret != RSI_SUCCESS)
+ return -EIO;
+
+ *cookie = rhi_call->gprs[1];
+ return map_rhi_da_error(rhi_call->gprs[0]);
+}
+
+int rhi_vdev_set_tdi_state(struct pci_dev *pdev, enum rhi_tdi_state target_state)
+{
+ int ret;
+ unsigned long cookie;
+ int vdev_id = rsi_vdev_id(pdev);
+
+ for (;;) {
+ ret = __rhi_vdev_set_tdi_state(vdev_id, target_state, &cookie);
+ if (ret != -EBUSY)
+ break;
+ cond_resched();
+ }
+
+ while (ret == RHI_DA_INCOMPLETE) {
+ if (should_abort_rhi_call_loop(vdev_id))
+ return -EINTR;
+ ret = rhi_vdev_continue(vdev_id, cookie);
+ }
+
+ return ret;
+}
diff --git a/drivers/virt/coco/arm-cca-guest/rhi-da.h b/drivers/virt/coco/arm-cca-guest/rhi-da.h
new file mode 100644
index 000000000000..43c1cda8738d
--- /dev/null
+++ b/drivers/virt/coco/arm-cca-guest/rhi-da.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#ifndef _VIRT_COCO_RHI_DA_H_
+#define _VIRT_COCO_RHI_DA_H_
+
+#include <asm/rhi.h>
+
+struct pci_dev;
+bool rhi_has_da_support(void);
+int rhi_vdev_set_tdi_state(struct pci_dev *pdev, enum rhi_tdi_state target_state);
+#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 02/11] coco: guest: arm64: Fix a typo in the ARM_CCA_GUEST Kconfig help string ("and" -> "an").
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:27 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
Fix a typo in Kconfig file.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/virt/coco/arm-cca-guest/Kconfig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/virt/coco/arm-cca-guest/Kconfig b/drivers/virt/coco/arm-cca-guest/Kconfig
index 5f7f284dae1a..d295146bd92a 100644
--- a/drivers/virt/coco/arm-cca-guest/Kconfig
+++ b/drivers/virt/coco/arm-cca-guest/Kconfig
@@ -8,7 +8,7 @@ config ARM_CCA_GUEST
select TSM_REPORTS
select AUXILIARY_BUS
help
- The driver provides userspace interface to request and
+ The driver provides userspace interface to request an
attestation report from the Realm Management Monitor(RMM).
If the DA feature is supported, it also register with TSM framework.
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 01/11] coco: guest: arm64: Guest TSM callback and realm device lock support
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:27 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427082805.931832-1-aneesh.kumar@kernel.org>
Register the TSM callback when the DA feature is supported by RSI. The
build order is also adjusted so that the TSM class is created before the
arm-cca-guest driver is initialized.
In addition, add support for the TDISP lock sequence. Writing a TSM
(TEE Security Manager) device name from `/sys/class/tsm` into `tsm/lock`
triggers the realm device lock operation.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rsi.h | 1 +
arch/arm64/include/asm/rsi_cmds.h | 17 +++++++
arch/arm64/include/asm/rsi_smc.h | 1 +
arch/arm64/kernel/rsi.c | 10 ++++
drivers/virt/coco/Makefile | 2 +-
drivers/virt/coco/arm-cca-guest/Kconfig | 5 ++
drivers/virt/coco/arm-cca-guest/arm-cca.c | 60 ++++++++++++++++++++++-
drivers/virt/coco/arm-cca-guest/rsi-da.h | 35 +++++++++++++
8 files changed, 129 insertions(+), 2 deletions(-)
create mode 100644 drivers/virt/coco/arm-cca-guest/rsi-da.h
diff --git a/arch/arm64/include/asm/rsi.h b/arch/arm64/include/asm/rsi.h
index 34c8f649fe48..f5288551ae77 100644
--- a/arch/arm64/include/asm/rsi.h
+++ b/arch/arm64/include/asm/rsi.h
@@ -68,5 +68,6 @@ static inline int rsi_set_memory_range_shared(phys_addr_t start,
RSI_CHANGE_DESTROYED);
}
+bool rsi_has_da_feature(void);
unsigned long realm_get_hyp_pagesize(void);
#endif /* __ASM_RSI_H_ */
diff --git a/arch/arm64/include/asm/rsi_cmds.h b/arch/arm64/include/asm/rsi_cmds.h
index a341ce0eeda1..596bdc356f1a 100644
--- a/arch/arm64/include/asm/rsi_cmds.h
+++ b/arch/arm64/include/asm/rsi_cmds.h
@@ -169,4 +169,21 @@ static inline unsigned long rsi_host_call(struct rsi_host_call *rhi_call)
return res.a0;
}
+/**
+ * rsi_features() - Read feature register
+ * @index: Feature register index
+ * @out: Feature register value is written to this pointer
+ *
+ * Return: RSI return code
+ */
+static inline unsigned long rsi_features(unsigned long index, u64 *out)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RSI_FEATURES, index, &res);
+
+ *out = res.a1;
+ return res.a0;
+}
+
#endif /* __ASM_RSI_CMDS_H */
diff --git a/arch/arm64/include/asm/rsi_smc.h b/arch/arm64/include/asm/rsi_smc.h
index 9ee8b5c7612e..4af4638fdd49 100644
--- a/arch/arm64/include/asm/rsi_smc.h
+++ b/arch/arm64/include/asm/rsi_smc.h
@@ -53,6 +53,7 @@
*/
#define SMC_RSI_ABI_VERSION SMC_RSI_FID(0x190)
+#define RSI_FEATURE_REGISTER_0_DA BIT(0)
/*
* Read feature register.
*
diff --git a/arch/arm64/kernel/rsi.c b/arch/arm64/kernel/rsi.c
index e49318469632..a3cd27bf82e8 100644
--- a/arch/arm64/kernel/rsi.c
+++ b/arch/arm64/kernel/rsi.c
@@ -17,6 +17,7 @@
#include <asm/rhi.h>
static struct realm_config config;
+static u64 rsi_feat_reg0;
static unsigned long ipa_change_alignment = PAGE_SIZE;
unsigned long prot_ns_shared;
@@ -25,6 +26,12 @@ EXPORT_SYMBOL(prot_ns_shared);
DEFINE_STATIC_KEY_FALSE_RO(rsi_present);
EXPORT_SYMBOL(rsi_present);
+bool rsi_has_da_feature(void)
+{
+ return u64_get_bits(rsi_feat_reg0, RSI_FEATURE_REGISTER_0_DA);
+}
+EXPORT_SYMBOL_GPL(rsi_has_da_feature);
+
bool cc_platform_has(enum cc_attr attr)
{
switch (attr) {
@@ -160,6 +167,9 @@ void __init arm64_rsi_init(void)
if (!ipa_change_alignment)
return;
+ if (WARN_ON(rsi_features(0, &rsi_feat_reg0)))
+ return;
+
prot_ns_shared = __phys_to_pte_val(BIT(config.ipa_bits - 1));
if (arm64_ioremap_prot_hook_register(realm_ioremap_hook))
diff --git a/drivers/virt/coco/Makefile b/drivers/virt/coco/Makefile
index b323b0ae4f82..4f7e30f5aeb8 100644
--- a/drivers/virt/coco/Makefile
+++ b/drivers/virt/coco/Makefile
@@ -7,6 +7,6 @@ obj-$(CONFIG_ARM_PKVM_GUEST) += pkvm-guest/
obj-$(CONFIG_SEV_GUEST) += sev-guest/
obj-$(CONFIG_INTEL_TDX_GUEST) += tdx-guest/
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_GUEST) += arm-cca-guest/
diff --git a/drivers/virt/coco/arm-cca-guest/Kconfig b/drivers/virt/coco/arm-cca-guest/Kconfig
index a42359a90558..5f7f284dae1a 100644
--- a/drivers/virt/coco/arm-cca-guest/Kconfig
+++ b/drivers/virt/coco/arm-cca-guest/Kconfig
@@ -1,11 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+
config ARM_CCA_GUEST
tristate "Arm CCA Guest driver"
depends on ARM64
+ select PCI_TSM if PCI
select TSM_REPORTS
select AUXILIARY_BUS
help
The driver provides userspace interface to request and
attestation report from the Realm Management Monitor(RMM).
+ If the DA feature is supported, it also register with TSM framework.
If you choose 'M' here, this module will be called
arm-cca-guest.
diff --git a/drivers/virt/coco/arm-cca-guest/arm-cca.c b/drivers/virt/coco/arm-cca-guest/arm-cca.c
index 7daada072cc0..6f13c54fab73 100644
--- a/drivers/virt/coco/arm-cca-guest/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-guest/arm-cca.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Copyright (C) 2023 ARM Ltd.
+ * Copyright (C) 2023-2026 ARM Ltd.
*/
#include <linux/auxiliary_bus.h>
@@ -15,6 +15,10 @@
#include <asm/rsi.h>
+#ifdef CONFIG_PCI_TSM
+#include "rsi-da.h"
+#endif
+
/**
* struct arm_cca_token_info - a descriptor for the token buffer.
* @challenge: Pointer to the challenge data
@@ -192,6 +196,53 @@ static void unregister_cca_tsm_report(void *data)
tsm_report_unregister(&arm_cca_tsm_report_ops);
}
+#ifdef CONFIG_PCI_TSM
+static struct pci_tsm *cca_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pdev)
+{
+ int ret;
+
+ struct cca_guest_dsc *cca_dsc __free(kfree) =
+ kzalloc_obj(struct cca_guest_dsc);
+ if (!cca_dsc)
+ return ERR_PTR(-ENOMEM);
+
+ ret = pci_tsm_devsec_constructor(pdev, &cca_dsc->pci, tsm_dev);
+ if (ret)
+ return ERR_PTR(ret);
+
+ /* For now always return an error */
+ return ERR_PTR(-EIO);
+}
+
+static void cca_tsm_unlock(struct pci_tsm *tsm)
+{
+ struct cca_guest_dsc *cca_dsc = to_cca_guest_dsc(tsm->pdev);
+
+ kfree(cca_dsc);
+}
+
+static struct pci_tsm_ops cca_devsec_pci_ops = {
+ .lock = cca_tsm_lock,
+ .unlock = cca_tsm_unlock,
+};
+
+static void cca_devsec_tsm_remove(void *tsm_dev)
+{
+ tsm_unregister(tsm_dev);
+}
+
+static int cca_devsec_tsm_register(struct auxiliary_device *adev)
+{
+ struct tsm_dev *tsm_dev;
+
+ tsm_dev = tsm_register(&adev->dev, &cca_devsec_pci_ops);
+ if (IS_ERR(tsm_dev))
+ return PTR_ERR(tsm_dev);
+
+ return devm_add_action_or_reset(&adev->dev, cca_devsec_tsm_remove, tsm_dev);
+}
+#endif /* CONFIG_PCI_TSM */
+
static int cca_devsec_tsm_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
@@ -212,6 +263,12 @@ static int cca_devsec_tsm_probe(struct auxiliary_device *adev,
return ret;
}
+#ifdef CONFIG_PCI_TSM
+ /* Allow tsm report even if tsm_register fails */
+ if (rsi_has_da_feature())
+ cca_devsec_tsm_register(adev);
+#endif
+
return 0;
}
@@ -227,5 +284,6 @@ static struct auxiliary_driver cca_devsec_tsm_driver = {
};
module_auxiliary_driver(cca_devsec_tsm_driver);
MODULE_AUTHOR("Sami Mujawar <sami.mujawar@arm.com>");
+MODULE_AUTHOR("Aneesh Kumar <aneesh.kumar@kernel.org>");
MODULE_DESCRIPTION("Arm CCA Guest TSM Driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/virt/coco/arm-cca-guest/rsi-da.h b/drivers/virt/coco/arm-cca-guest/rsi-da.h
new file mode 100644
index 000000000000..858bfdaf59c9
--- /dev/null
+++ b/drivers/virt/coco/arm-cca-guest/rsi-da.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2026 ARM Ltd.
+ */
+
+#ifndef _VIRT_COCO_RSI_DA_H_
+#define _VIRT_COCO_RSI_DA_H_
+
+#include <linux/pci.h>
+#include <linux/pci-tsm.h>
+#include <asm/rsi_smc.h>
+
+struct cca_guest_dsc {
+ struct pci_tsm_devsec pci;
+};
+
+static inline struct cca_guest_dsc *to_cca_guest_dsc(struct pci_dev *pdev)
+{
+ struct pci_tsm *tsm = pdev->tsm;
+
+ if (!tsm)
+ return NULL;
+ return container_of(tsm, struct cca_guest_dsc, pci.base_tsm);
+}
+
+/*
+ * Linux use device requester id as the vdev id.
+ */
+static inline int rsi_vdev_id(struct pci_dev *pdev)
+{
+ return (pci_domain_nr(pdev->bus) << 16) |
+ PCI_DEVID(pdev->bus->number, pdev->devfn);
+}
+
+#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 00/11] coco/TSM: Arm CCA guest TDISP lock/accept flow with verification and DMA enable
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 8:27 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
This patch series implements the TSM ->lock(), ->unlock(), and ->accept()
callbacks required for the TDISP setup with Arm CCA as per as per the RMM
2.0bet1 specification [1].
The series adds the guest-side DA plumbing needed to transition a device
through TDI LOCK and RUN states, verify host-provided evidence against
RMM-provided digests, validate interface-report MMIO mappings, and enable
DMA only after attestation succeeds.
At a high level, the series includes:
- guest TSM callback registration and lock/unlock/accept hooks
- RHI DA helper support for TDI state transitions and object refresh
- host-cached DA object fetch APIs in guest
- RSI_VDEV_GET_INFO digest verification of certificate/VCA/report/measurement
- mapping validation for interface-report ranges and teardown on unlock
- DMA behavior updates for accepted devices (including swiotlb restrictions)
- vdev DMA enable after successful attestation
The series builds upon the TSM framework patches posted at [2]. A git repository
containing all the related changes is available at [3].
Testing / Usage
echo ${DEVICE} > /sys/bus/pci/devices/${DEVICE}/driver/unbind
To transition the device to TDISP LOCK state:
echo tsm0 > /sys/bus/pci/devices/${DEVICE}/tsm/lock
To transition the device to TDISP RUN state:
echo 1 > /sys/bus/pci/devices/${DEVICE}/tsm/accept
echo ${DEVICE} > /sys/bus/pci/drivers_probe
Changes from v3:
https://lore.kernel.org/all/20260312080442.3485633-1-aneesh.kumar@kernel.org
* updated the patches to follow the RMM 2.0bet1 specification
* updated the guest-side DA code to use the renamed identity/protocol digest
fields and id_index
Changes from v2:
rfc-v2: https://lore.kernel.org/all/20251117140007.122062-1-aneesh.kumar@kernel.org
* rebase to latest kernel and core TSM changes
* Address review feedback.
* Interface report is now collected using core TSM framework
* swiotlb is now considered shared-memory pool and is not allowed to be used by accepted devices.
v1:
rfc-v1: https://lore.kernel.org/all/20250728135216.48084-1-aneesh.kumar@kernel.org
[1] https://developer.arm.com/documentation/den0137/2-0bet1/
[2] https://lore.kernel.org/all/20260303000207.1836586-1-dan.j.williams@intel.com
[3] https://gitlab.arm.com/linux-arm/linux-cca.git cca/topics/cca-tdisp-upstream-rfc-v4
Cc: Alexey Kardashevskiy <aik@amd.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Dan Williams <dan.j.williams@intel.com>
Cc: Jason Gunthorpe <jgg@ziepe.ca>
Cc: Jonathan Cameron <jic23@kernel.org>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Samuel Ortiz <sameo@rivosinc.com>
Cc: Steven Price <steven.price@arm.com>
Cc: Suzuki K Poulose <Suzuki.Poulose@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Xu Yilun <yilun.xu@linux.intel.com>
Aneesh Kumar K.V (Arm) (11):
coco: guest: arm64: Guest TSM callback and realm device lock support
coco: guest: arm64: Fix a typo in the ARM_CCA_GUEST Kconfig help
string ("and" -> "an").
coco: guest: arm64: Add Realm Host Interface and guest DA helper
coco: guest: arm64: Support guest-initiated TDI lock/unlock
transitions
coco: guest: arm64: Refresh interface-report cache during device lock
coco: guest: arm64: Add measurement refresh via
RHI_DA_VDEV_GET_MEASUREMENTS
coco: guest: arm64: Add guest APIs to read host-cached DA objects
coco: guest: arm64: Verify DA evidence with RSI_VDEV_GET_INFO digests
coco: guest: arm64: Hook TSM accept to Realm TDISP RUN transition
coco: arm64: dma: Update force_dma_unencrypted for accepted devices
coco: guest: arm64: Enable vdev DMA after attestation
arch/arm64/include/asm/mem_encrypt.h | 6 +-
arch/arm64/include/asm/rhi.h | 59 ++++
arch/arm64/include/asm/rsi.h | 1 +
arch/arm64/include/asm/rsi_cmds.h | 73 +++++
arch/arm64/include/asm/rsi_smc.h | 63 ++++
arch/arm64/kernel/rsi.c | 10 +
arch/arm64/mm/mem_encrypt.c | 10 +
drivers/virt/coco/Makefile | 2 +-
drivers/virt/coco/arm-cca-guest/Kconfig | 9 +-
drivers/virt/coco/arm-cca-guest/Makefile | 1 +
drivers/virt/coco/arm-cca-guest/arm-cca.c | 358 +++++++++++++++++++++-
drivers/virt/coco/arm-cca-guest/rhi-da.c | 356 +++++++++++++++++++++
drivers/virt/coco/arm-cca-guest/rhi-da.h | 17 +
drivers/virt/coco/arm-cca-guest/rsi-da.c | 289 +++++++++++++++++
drivers/virt/coco/arm-cca-guest/rsi-da.h | 66 ++++
include/linux/swiotlb.h | 3 +
kernel/dma/direct.c | 8 +
kernel/dma/swiotlb.c | 3 +
18 files changed, 1326 insertions(+), 8 deletions(-)
create mode 100644 drivers/virt/coco/arm-cca-guest/rhi-da.c
create mode 100644 drivers/virt/coco/arm-cca-guest/rhi-da.h
create mode 100644 drivers/virt/coco/arm-cca-guest/rsi-da.c
create mode 100644 drivers/virt/coco/arm-cca-guest/rsi-da.h
--
2.43.0
^ permalink raw reply
* [RFC PATCH v4 14/14] coco: host: arm64: Add NCOH_SYS stream support for RC endpoints
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 6:51 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427065121.916615-1-aneesh.kumar@kernel.org>
Teach the host CCA pdev setup to handle PCI_EXP_TYPE_RC_END devices.
Classify RC integrated endpoints as RMI_PDEV_FLAGS_CATEGORY_ON_CHIP_EP when
building the RMM pdev parameters, and only advertise SPDM support when a
DOE mailbox is present.
Also add the stream setup path for these devices by creating an
RMI_PDEV_STREAM_NCOH_SYS stream using the endpoint pdev and its bridge
address windows. This allows RC endpoints to participate in the TDISP flow
without requiring a separate root-port pdev.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/virt/coco/arm-cca-host/arm-cca.c | 28 ++++++++++++++++++++++++
drivers/virt/coco/arm-cca-host/rmi-da.c | 10 +++++++++
2 files changed, 38 insertions(+)
diff --git a/drivers/virt/coco/arm-cca-host/arm-cca.c b/drivers/virt/coco/arm-cca-host/arm-cca.c
index 265aa0cb612a..8b1182620872 100644
--- a/drivers/virt/coco/arm-cca-host/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-host/arm-cca.c
@@ -277,11 +277,39 @@ static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long strea
return ret;
}
+static int cca_pdev_create_ncoh_sys_stream(struct pci_dev *pdev)
+{
+ int ret;
+ long stream_handle;
+ struct rmi_pdev_stream_params *params;
+ struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
+
+ params = (struct rmi_pdev_stream_params *)get_zeroed_page(GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ params->flags = 0;
+ params->type = RMI_PDEV_STREAM_NCOH_SYS;
+ params->pdev_1 = virt_to_phys(pf0_ep_dsc->pdev.rmm_pdev);
+ params->pdev_2 = 0; /* ignored */
+ params->ide_sid = 0; /* ignored */
+ params->num_addr_range = pci_dev_addr_range(pdev, params->addr_range);
+
+ ret = cca_pdev_stream_connect(pdev, NULL, params, &stream_handle);
+ if (!ret)
+ pf0_ep_dsc->stream_handle = stream_handle;
+
+ free_page((unsigned long)params);
+ return ret;
+}
+
static int cca_pdev_create_streams(struct pci_dev *pdev, unsigned long stream_id)
{
switch (pci_pcie_type(pdev)) {
case PCI_EXP_TYPE_ENDPOINT:
return cca_pdev_create_ncoh_stream(pdev, stream_id);
+ case PCI_EXP_TYPE_RC_END:
+ return cca_pdev_create_ncoh_sys_stream(pdev);
default:
return -EINVAL;
}
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
index a10ac6ff03d1..33a2551fd09f 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.c
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -66,6 +66,16 @@ static int init_pdev_params(struct pci_dev *pdev, struct rmi_pdev_params *params
category = RMI_PDEV_FLAGS_CATEGORY_ROOT_PORT;
break;
}
+ case PCI_EXP_TYPE_RC_END: {
+ struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
+
+ /* Use SPDM if present */
+ if (pf0_ep_dsc->pci.doe_mb)
+ params->flags = RMI_PDEV_FLAGS_SPDM;
+
+ category = RMI_PDEV_FLAGS_CATEGORY_ON_CHIP_EP;
+ break;
+ }
default:
return -EINVAL;
}
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 13/14] PCI/TSM: Move CMA DOE mailbox discovery out of pci_tsm_pf0_constructor()
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 6:51 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427065121.916615-1-aneesh.kumar@kernel.org>
pci_tsm_pf0_constructor() currently looks up a CMA DOE mailbox and
fails PF0 initialization when one is not present. That is too strict
for all link TSM drivers.
Move CMA DOE mailbox discovery into the low-level PF0 probe callbacks
so each driver can decide whether a mailbox is mandatory.
Keep SEV-TIO and TDX requiring a CMA mailbox, while allowing the
arm-cca host path to proceed on PF0 devices that do not support IDE
and therefore have no DOE-based SPDM path.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/crypto/ccp/sev-dev-tsm.c | 13 +++++++++++++
drivers/pci/tsm/core.c | 14 ++++++++------
drivers/virt/coco/arm-cca-host/arm-cca.c | 16 +++++++++++++---
drivers/virt/coco/tdx-host/tdx-host.c | 13 +++++++++++++
4 files changed, 47 insertions(+), 9 deletions(-)
diff --git a/drivers/crypto/ccp/sev-dev-tsm.c b/drivers/crypto/ccp/sev-dev-tsm.c
index b07ae529b591..a7506cbbe392 100644
--- a/drivers/crypto/ccp/sev-dev-tsm.c
+++ b/drivers/crypto/ccp/sev-dev-tsm.c
@@ -217,6 +217,19 @@ static struct pci_tsm *tio_pf0_probe(struct pci_dev *pdev, struct sev_device *se
if (rc)
return NULL;
+ /* if device have ide cap, setup doe mailbox */
+ if (pdev->ide_cap) {
+ struct pci_doe_mb *doe_mb;
+
+ doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
+ PCI_DOE_FEATURE_CMA);
+ if (!doe_mb)
+ return NULL;
+ dsm->tsm.doe_mb = doe_mb;
+ } else {
+ return NULL;
+ }
+
pci_dbg(pdev, "TSM enabled\n");
dsm->sev = sev;
return &no_free_ptr(dsm)->tsm.base_tsm;
diff --git a/drivers/pci/tsm/core.c b/drivers/pci/tsm/core.c
index bb440135b8f7..900306a43161 100644
--- a/drivers/pci/tsm/core.c
+++ b/drivers/pci/tsm/core.c
@@ -1236,12 +1236,14 @@ int pci_tsm_pf0_constructor(struct pci_dev *pdev, struct pci_tsm_pf0 *tsm,
struct tsm_dev *tsm_dev)
{
mutex_init(&tsm->lock);
- tsm->doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
- PCI_DOE_FEATURE_CMA);
- if (!tsm->doe_mb) {
- pci_warn(pdev, "TSM init failure, no CMA mailbox\n");
- return -ENODEV;
- }
+
+ /*
+ * Note, low-level TSM driver responsible for determining if it wants to
+ * proceed with a device that has no DOE mailbox. TSM may have an
+ * alternate method for coordinating TDISP.
+ */
+ if (!tsm->doe_mb)
+ pci_dbg(pdev, "no CMA mailbox\n");
return pci_tsm_link_constructor(pdev, &tsm->base_tsm, tsm_dev);
}
diff --git a/drivers/virt/coco/arm-cca-host/arm-cca.c b/drivers/virt/coco/arm-cca-host/arm-cca.c
index 0b1200f591ab..265aa0cb612a 100644
--- a/drivers/virt/coco/arm-cca-host/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-host/arm-cca.c
@@ -11,6 +11,8 @@
#include <linux/tsm.h>
#include <linux/vmalloc.h>
#include <linux/cleanup.h>
+#include <linux/pci-doe.h>
+
#include "rmi-da.h"
@@ -35,14 +37,22 @@ static struct pci_tsm *cca_tsm_pci_probe(struct tsm_dev *tsm_dev, struct pci_dev
return &no_free_ptr(fn_dsc)->pci;
}
- if (!pdev->ide_cap)
- return NULL;
-
struct cca_host_pf0_ep_dsc *pf0_ep_dsc __free(kfree) =
kzalloc(sizeof(*pf0_ep_dsc), GFP_KERNEL);
if (!pf0_ep_dsc)
return NULL;
+ /* if device have ide cap, setup doe mailbox */
+ if (pdev->ide_cap) {
+ struct pci_doe_mb *doe_mb;
+
+ doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
+ PCI_DOE_FEATURE_CMA);
+ if (!doe_mb)
+ return NULL;
+ pf0_ep_dsc->pci.doe_mb = doe_mb;
+ }
+
ret = pci_tsm_pf0_constructor(pdev, &pf0_ep_dsc->pci, tsm_dev);
if (ret)
return NULL;
diff --git a/drivers/virt/coco/tdx-host/tdx-host.c b/drivers/virt/coco/tdx-host/tdx-host.c
index ea7c2167660f..4947b9bc2359 100644
--- a/drivers/virt/coco/tdx-host/tdx-host.c
+++ b/drivers/virt/coco/tdx-host/tdx-host.c
@@ -634,6 +634,19 @@ static struct pci_tsm *tdx_link_pf0_probe(struct tsm_dev *tsm_dev,
spdm_conf->vmm_spdm_cap = SPDM_CAP_KEY_UPD;
spdm_conf->certificate_slot_mask = 0xff;
+ /* if device have ide cap, setup doe mailbox */
+ if (pdev->ide_cap) {
+ struct pci_doe_mb *doe_mb;
+
+ doe_mb = pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
+ PCI_DOE_FEATURE_CMA);
+ if (!doe_mb)
+ return NULL;
+ tlink->pci.doe_mb = doe_mb;
+ } else {
+ return NULL;
+ }
+
tlink->in_msg = no_free_ptr(in_msg);
tlink->out_msg = no_free_ptr(out_msg);
tlink->spdm_conf = no_free_ptr(spdm_conf);
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 12/14] coco: host: arm64: Refcount root-port pdevs used by IDE streams
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 6:51 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427065121.916615-1-aneesh.kumar@kernel.org>
Keep the root-port RMM pdev alive while endpoint IDE streams are attached
to it.
Add a kref to the root-port descriptor, take a reference when reusing an
existing root-port pdev for stream setup, and drop it when the endpoint
disconnects. Release the root-port pdev once the final reference is
dropped, tearing down the RMM object and its communication buffers at that
point.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
drivers/virt/coco/arm-cca-host/arm-cca.c | 31 +++++++++++++++++++++---
drivers/virt/coco/arm-cca-host/rmi-da.h | 4 +++
2 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/drivers/virt/coco/arm-cca-host/arm-cca.c b/drivers/virt/coco/arm-cca-host/arm-cca.c
index de7a2e156549..0b1200f591ab 100644
--- a/drivers/virt/coco/arm-cca-host/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-host/arm-cca.c
@@ -134,6 +134,23 @@ static int alloc_stream_id(struct pci_host_bridge *hb)
return stream_id;
}
+static void cca_root_port_pdev_release(struct kref *kref)
+{
+ struct cca_host_rp_dsc *rp_dsc = container_of(kref, struct cca_host_rp_dsc,
+ tsm_ref);
+ struct pci_dev *rp = rp_dsc->pci.pdev;
+
+ cca_pdev_stop_and_destroy(rp);
+ free_dev_communication_buffers(&rp_dsc->pdev.comm_data);
+ rp->tsm = NULL;
+ kfree(rp_dsc);
+}
+
+static inline void cca_root_port_pdev_put(struct cca_host_rp_dsc *rp_dsc)
+{
+ kref_put(&rp_dsc->tsm_ref, cca_root_port_pdev_release);
+}
+
static int cca_root_port_pdev_create(struct pci_dev *rp, struct tsm_dev *tsm_dev)
{
int ret;
@@ -151,6 +168,7 @@ static int cca_root_port_pdev_create(struct pci_dev *rp, struct tsm_dev *tsm_dev
rp->tsm->dsm_dev = rp;
rp->tsm->pdev = rp;
rp->tsm->tsm_dev = tsm_dev;
+ kref_init(&rp_dsc->tsm_ref);
mutex_init(&rp_dsc->pdev.object_lock);
ret = init_dev_communication_buffers(rp, &rp_dsc->pdev.comm_data);
@@ -222,12 +240,15 @@ static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long strea
/* Make sure they use the same TSM */
if (rp->tsm->tsm_dev != pf0_ep_dsc->pci.base_tsm.tsm_dev)
return -EINVAL;
- }
+ kref_get(&rp_dsc->tsm_ref);
+ }
params = (struct rmi_pdev_stream_params *)get_zeroed_page(GFP_KERNEL);
- if (!params)
+ if (!params) {
+ cca_root_port_pdev_put(rp_dsc);
return -ENOMEM;
+ }
params->flags = 0;
params->type = RMI_PDEV_STREAM_NCOH;
@@ -237,7 +258,9 @@ static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long strea
params->num_addr_range = pci_dev_addr_range(pdev, params->addr_range);
ret = cca_pdev_stream_connect(pdev, rp, params, &stream_handle);
- if (!ret)
+ if (ret)
+ cca_root_port_pdev_put(rp_dsc);
+ else
pf0_ep_dsc->stream_handle = stream_handle;
free_page((unsigned long)params);
@@ -375,6 +398,8 @@ static void cca_tsm_disconnect(struct pci_dev *pdev)
}
cca_pdev_disconnect_stream(pdev, rp, pf0_ep_dsc->stream_handle);
+ if (rp)
+ cca_root_port_pdev_put(to_cca_rp_dsc(rp));
cca_pdev_stop_and_destroy(pdev);
free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index ea5f7df3541f..798a8ed7505f 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -10,6 +10,9 @@
#include <linux/pci-ide.h>
#include <linux/pci-tsm.h>
#include <linux/sizes.h>
+#include <linux/atomic.h>
+#include <linux/kref.h>
+#include <linux/wait.h>
#include <asm/rmi_cmds.h>
#include <asm/rmi_smc.h>
@@ -104,6 +107,7 @@ struct cca_host_pf0_ep_dsc {
struct cca_host_rp_dsc {
struct pci_tsm pci;
struct cca_host_pdev_dsc pdev;
+ struct kref tsm_ref;
};
struct cca_host_fn_dsc {
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 11/14] coco: host: arm64: Connect RMM pdev streams for IDE devices
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 6:51 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427065121.916615-1-aneesh.kumar@kernel.org>
Add the RMI definitions for pdev stream management, including the stream
parameter layout and helpers for RMI_PDEV_STREAM_CONNECT,
RMI_PDEV_STREAM_COMPLETE, and RMI_PDEV_STREAM_DISCONNECT.
Create an RMM pdev for the endpoint's root port when needed, build the
non-coherent stream parameters from the endpoint/root-port pdevs, IDE
stream ID, and bridge address windows, and issue the RMM stream connect
before enabling IDE on the endpoint.
Store the returned stream handle in the PF0 descriptor
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rmi_cmds.h | 37 +++++++
arch/arm64/include/asm/rmi_smc.h | 39 +++++++
drivers/virt/coco/arm-cca-host/arm-cca.c | 127 +++++++++++++++++++++++
drivers/virt/coco/arm-cca-host/rmi-da.c | 40 ++++++-
drivers/virt/coco/arm-cca-host/rmi-da.h | 57 ++++++++++
5 files changed, 299 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/include/asm/rmi_cmds.h b/arch/arm64/include/asm/rmi_cmds.h
index 00e0a08e17a6..c82d4d9cbc06 100644
--- a/arch/arm64/include/asm/rmi_cmds.h
+++ b/arch/arm64/include/asm/rmi_cmds.h
@@ -774,4 +774,41 @@ static inline unsigned long rmi_pdev_set_pubkey(unsigned long pdev_phys, unsigne
return res.a0;
}
+
+static inline unsigned long rmi_pdev_stream_connect(unsigned long stream_params_phys,
+ unsigned long *stream_handle)
+{
+
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_PDEV_STREAM_CONNECT, stream_params_phys, &res);
+
+ *stream_handle = res.a1;
+ return res.a0;
+}
+
+static inline unsigned long rmi_pdev_stream_complete(unsigned long pdev1_phys,
+ unsigned long pdev2_phys, unsigned long stream_handle)
+{
+
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_PDEV_STREAM_COMPLETE, pdev1_phys,
+ pdev2_phys, stream_handle, &res);
+
+ return res.a0;
+}
+
+static inline unsigned long rmi_pdev_stream_disconnect(unsigned long pdev1_phys,
+ unsigned long pdev2_phys, unsigned long stream_handle)
+{
+
+ struct arm_smccc_res res;
+
+ arm_smccc_1_1_invoke(SMC_RMI_PDEV_STREAM_DISCONNECT, pdev1_phys,
+ pdev2_phys, stream_handle, &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 e9437d56996a..7b16f1540a0e 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -556,4 +556,43 @@ struct rmi_public_key_params {
};
};
+#define MAX_STREAM_ADDR_RANGE 16
+
+enum rmi_pdev_stream_type {
+ RMI_PDEV_STREAM_NON_TEE,
+ RMI_PDEV_STREAM_NCOH,
+ RMI_PDEV_STREAM_COH,
+ RMI_PDEV_STREAM_NCOH_SYS,
+ RMI_PDEV_STREAM_COH_SYS,
+ RMI_PDEV_STREAM_NCOH_P2P,
+ RMI_PDEV_STREAM_COH_CMEM,
+};
+
+struct rmi_addr_range {
+ u64 base; /* inclusive */
+ u64 top; /* exclusive */
+};
+
+struct rmi_pdev_stream_params {
+ union {
+ struct {
+ u64 flags;
+ union {
+ u8 type;
+ u8 padding1[8];
+ };
+ u64 pdev_1;
+ u64 pdev_2;
+ u64 ide_sid;
+ u64 num_addr_range;
+ };
+ u8 padding2[0x100];
+ };
+
+ union { /* 0x100 */
+ struct rmi_addr_range addr_range[MAX_STREAM_ADDR_RANGE];
+ u8 padding3[0xF00];
+ };
+};
+
#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 f0aa4e46e96c..de7a2e156549 100644
--- a/drivers/virt/coco/arm-cca-host/arm-cca.c
+++ b/drivers/virt/coco/arm-cca-host/arm-cca.c
@@ -134,6 +134,126 @@ static int alloc_stream_id(struct pci_host_bridge *hb)
return stream_id;
}
+static int cca_root_port_pdev_create(struct pci_dev *rp, struct tsm_dev *tsm_dev)
+{
+ int ret;
+ struct cca_host_rp_dsc *rp_dsc;
+
+ /* We are under pci_tsm_rwsem. */
+ lockdep_assert_held_write(&pci_tsm_rwsem);
+
+ rp_dsc = kzalloc_obj(*rp_dsc);
+ if (!rp_dsc)
+ return -ENOMEM;
+
+ /* we expect this to be asigned early */
+ rp->tsm = &rp_dsc->pci;
+ rp->tsm->dsm_dev = rp;
+ rp->tsm->pdev = rp;
+ rp->tsm->tsm_dev = tsm_dev;
+ mutex_init(&rp_dsc->pdev.object_lock);
+
+ ret = init_dev_communication_buffers(rp, &rp_dsc->pdev.comm_data);
+ if (ret)
+ goto err_comm_buff;
+
+ ret = cca_pdev_create(rp);
+ if (ret)
+ goto err_pdev_create;
+
+ /*
+ * device communication is still required even though
+ * there is not identity collection
+ */
+ ret = cca_pdev_collect_identity(rp);
+ if (ret)
+ goto pdev_destroy;
+
+ return 0;
+
+pdev_destroy:
+ cca_pdev_stop_and_destroy(rp);
+err_pdev_create:
+ free_dev_communication_buffers(&rp_dsc->pdev.comm_data);
+err_comm_buff:
+ kfree(rp_dsc);
+ rp->tsm = NULL;
+ return ret;
+}
+
+static int pci_dev_addr_range(struct pci_dev *pdev, struct rmi_addr_range *pdev_addr)
+{
+ 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))
+ naddr = insert_addr_range_sorted(pdev_addr, naddr,
+ mem->start, mem->end + 1);
+ if (resource_assigned(pref))
+ naddr = insert_addr_range_sorted(pdev_addr, naddr,
+ pref->start, pref->end + 1);
+
+ return naddr;
+}
+
+static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long stream_id)
+{
+ int ret;
+ long stream_handle;
+ struct cca_host_rp_dsc *rp_dsc;
+ struct rmi_pdev_stream_params *params;
+ struct pci_dev *rp = pcie_find_root_port(pdev);
+ struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
+
+ if (!rp->tsm) {
+ ret = cca_root_port_pdev_create(rp, pf0_ep_dsc->pci.base_tsm.tsm_dev);
+ if (ret)
+ return ret;
+ rp_dsc = to_cca_rp_dsc(rp);
+ } else {
+ rp_dsc = to_cca_rp_dsc(rp);
+ /* Make sure they use the same TSM */
+ if (rp->tsm->tsm_dev != pf0_ep_dsc->pci.base_tsm.tsm_dev)
+ return -EINVAL;
+ }
+
+
+ params = (struct rmi_pdev_stream_params *)get_zeroed_page(GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ params->flags = 0;
+ params->type = RMI_PDEV_STREAM_NCOH;
+ params->pdev_1 = virt_to_phys(pf0_ep_dsc->pdev.rmm_pdev);
+ params->pdev_2 = virt_to_phys(rp_dsc->pdev.rmm_pdev);
+ params->ide_sid = stream_id;
+ params->num_addr_range = pci_dev_addr_range(pdev, params->addr_range);
+
+ ret = cca_pdev_stream_connect(pdev, rp, params, &stream_handle);
+ if (!ret)
+ pf0_ep_dsc->stream_handle = stream_handle;
+
+ free_page((unsigned long)params);
+ return ret;
+}
+
+static int cca_pdev_create_streams(struct pci_dev *pdev, unsigned long stream_id)
+{
+ switch (pci_pcie_type(pdev)) {
+ case PCI_EXP_TYPE_ENDPOINT:
+ return cca_pdev_create_ncoh_stream(pdev, stream_id);
+ default:
+ return -EINVAL;
+ }
+}
+
static inline bool cca_pdev_need_sel_ide_streams(struct pci_dev *pdev)
{
return pci_pcie_type(pdev) == PCI_EXP_TYPE_ENDPOINT;
@@ -202,6 +322,10 @@ static int cca_tsm_connect(struct pci_dev *pdev)
if (ret)
goto pdev_destroy;
}
+ /* Create IDE streams */
+ ret = cca_pdev_create_streams(pdev, stream_id);
+ if (ret)
+ goto pdev_destroy;
/*
* Once ide is setup, enable the stream at the endpoint
* Root port will be done by RMM
@@ -239,6 +363,7 @@ static void cca_tsm_disconnect(struct pci_dev *pdev)
int stream_id;
struct pci_ide *ide;
struct cca_host_pf0_ep_dsc *pf0_ep_dsc;
+ struct pci_dev *rp = pcie_find_root_port(pdev);
pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
if (!pf0_ep_dsc)
@@ -249,6 +374,8 @@ static void cca_tsm_disconnect(struct pci_dev *pdev)
stream_id = ide->stream_id;
}
+ cca_pdev_disconnect_stream(pdev, rp, pf0_ep_dsc->stream_handle);
+
cca_pdev_stop_and_destroy(pdev);
free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
index 28f450e2db27..a10ac6ff03d1 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.c
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -62,6 +62,10 @@ static int init_pdev_params(struct pci_dev *pdev, struct rmi_pdev_params *params
category = RMI_PDEV_FLAGS_CATEGORY_OFF_CHIP_EP;
break;
}
+ case PCI_EXP_TYPE_ROOT_PORT: {
+ category = RMI_PDEV_FLAGS_CATEGORY_ROOT_PORT;
+ break;
+ }
default:
return -EINVAL;
}
@@ -770,7 +774,7 @@ static void stream_connect_workfn(struct work_struct *work)
mutex_unlock(&pdev_dsc->object_lock);
}
-static int __maybe_unused submit_stream_work(struct pci_dev *pdev1, struct pci_dev *pdev2,
+static int submit_stream_work(struct pci_dev *pdev1, struct pci_dev *pdev2,
unsigned long stream_handle)
{
phys_addr_t rmm_pdev1_phys, rmm_pdev2_phys = 0;
@@ -814,6 +818,40 @@ static int __maybe_unused submit_stream_work(struct pci_dev *pdev1, struct pci_d
rmm_pdev1_phys = virt_to_phys(pdev_dsc1->rmm_pdev);
if (pdev2)
rmm_pdev2_phys = virt_to_phys(pdev_dsc2->rmm_pdev);
+ /*
+ * If we had device communication error, this will error out.
+ */
+ if (rmi_pdev_stream_complete(rmm_pdev1_phys, rmm_pdev2_phys, stream_handle))
+ return -EIO;
return 0;
}
+
+int cca_pdev_stream_connect(struct pci_dev *pdev1, struct pci_dev *pdev2,
+ struct rmi_pdev_stream_params *stream_params,
+ unsigned long *stream_handle)
+{
+ phys_addr_t stream_params_phys = virt_to_phys(stream_params);
+
+ if (rmi_pdev_stream_connect(stream_params_phys, stream_handle))
+ return -EIO;
+
+ return submit_stream_work(pdev1, pdev2, *stream_handle);
+}
+
+int cca_pdev_disconnect_stream(struct pci_dev *pdev1,
+ struct pci_dev *pdev2, unsigned long stream_handle)
+{
+
+ phys_addr_t rmm_pdev2_phys = 0;
+ struct cca_host_pdev_dsc *pdev_dsc1 = to_cca_pdev_dsc(pdev1);
+
+ if (pdev2)
+ rmm_pdev2_phys = virt_to_phys(to_cca_pdev_dsc(pdev2)->rmm_pdev);
+
+ if (rmi_pdev_stream_disconnect(virt_to_phys(pdev_dsc1->rmm_pdev),
+ rmm_pdev2_phys, stream_handle))
+ return -EIO;
+
+ return submit_stream_work(pdev1, pdev2, stream_handle);
+}
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index 5b0f43493485..ea5f7df3541f 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -72,6 +72,7 @@ struct cca_host_pdev_dsc {
* @pci: Physical Function 0 TDISP link context
* @pdev: pdev communication context
* @sel_stream: Selective IDE Stream descriptor
+ * @stream_handle: Stream handle returned by stream connect
* @rmi_signature_algorithm: Signature algorithm used for public key
* @cert_chain: cetrificate chain
* @vca: SPDM's Version-Capabilities-Algorithms cache object
@@ -80,6 +81,7 @@ struct cca_host_pf0_ep_dsc {
struct pci_tsm_pf0 pci;
struct cca_host_pdev_dsc pdev;
struct pci_ide *sel_stream;
+ unsigned long stream_handle;
uint8_t rmi_signature_algorithm;
struct {
@@ -93,6 +95,17 @@ struct cca_host_pf0_ep_dsc {
struct cache_object *vca;
};
+/**
+ * struct cca_host_rp_dsc - Root-port pdev context for stream coordination.
+ * @pci: Root-port TSM link context
+ * @pdev: Common pdev communication context
+ * @tsm_ref: Reference count held by connected endpoint streams
+ */
+struct cca_host_rp_dsc {
+ struct pci_tsm pci;
+ struct cca_host_pdev_dsc pdev;
+};
+
struct cca_host_fn_dsc {
struct pci_tsm pci;
};
@@ -101,6 +114,30 @@ enum dev_comm_type {
PDEV_COMMUNICATE = 0x1,
};
+static inline int insert_addr_range_sorted(struct rmi_addr_range *addr_range,
+ int nr_addr_range, resource_size_t start, resource_size_t top)
+{
+ int index = nr_addr_range;
+
+ while (index > 0) {
+ struct rmi_addr_range *prev = &addr_range[index - 1];
+
+ if (prev->base < start)
+ break;
+
+ if (prev->base == start && prev->top <= top)
+ break;
+
+ addr_range[index] = *prev;
+ index--;
+ }
+
+ addr_range[index].base = start;
+ addr_range[index].top = top;
+
+ return nr_addr_range + 1;
+}
+
static inline struct cca_host_pf0_ep_dsc *to_cca_pf0_ep_dsc(struct pci_dev *pdev)
{
struct pci_tsm *tsm = pdev->tsm;
@@ -118,14 +155,29 @@ 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_rp_dsc *to_cca_rp_dsc(struct pci_dev *pdev)
+{
+ struct pci_tsm *tsm = pdev->tsm;
+
+ if (!tsm || pci_pcie_type(pdev) != PCI_EXP_TYPE_ROOT_PORT)
+ return NULL;
+
+ return container_of(tsm, struct cca_host_rp_dsc, pci);
+}
+
static inline struct cca_host_pdev_dsc *to_cca_pdev_dsc(struct pci_dev *pdev)
{
struct cca_host_pf0_ep_dsc *pf0_ep_dsc;
+ struct cca_host_rp_dsc *rp_dsc;
pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
if (pf0_ep_dsc)
return &pf0_ep_dsc->pdev;
+ rp_dsc = to_cca_rp_dsc(pdev);
+ if (rp_dsc)
+ return &rp_dsc->pdev;
+
return NULL;
}
@@ -152,5 +204,10 @@ int cca_pdev_collect_identity(struct pci_dev *pdev);
bool cca_pdev_needs_key(struct pci_dev *pdev);
int cca_pdev_set_public_key(struct pci_dev *pdev);
void cca_pdev_stop_and_destroy(struct pci_dev *pdev);
+int cca_pdev_stream_connect(struct pci_dev *pdev1, struct pci_dev *pdev2,
+ struct rmi_pdev_stream_params *stream_params,
+ unsigned long *stream_handle);
+int cca_pdev_disconnect_stream(struct pci_dev *pdev1,
+ struct pci_dev *pdev2, unsigned long stream_handle);
#endif
--
2.43.0
^ permalink raw reply related
* [RFC PATCH v4 10/14] coco: host: arm64: Coordinate peer stream waits during pdev communication
From: Aneesh Kumar K.V (Arm) @ 2026-04-27 6:51 UTC (permalink / raw)
To: linux-coco, kvmarm, linux-arm-kernel, linux-kernel
Cc: Aneesh Kumar K.V (Arm), Alexey Kardashevskiy, Catalin Marinas,
Dan Williams, Jason Gunthorpe, Jonathan Cameron, Marc Zyngier,
Samuel Ortiz, Steven Price, Suzuki K Poulose, Will Deacon,
Xu Yilun
In-Reply-To: <20260427065121.916615-1-aneesh.kumar@kernel.org>
RMM stream operations can return RMI_DEV_COMM_EXIT_STREAM_WAIT while
one side waits for the peer stream to reach the matching point in the
protocol.
Teach arm-cca host device communication to detect STREAM_WAIT and add
a helper that runs pdev communication for both sides in parallel until
each side has made enough progress, then issue rmi_pdev_stream_complete().
This provides the synchronization needed for stream connect,
disconnect, key refresh, and key purge operations.
Signed-off-by: Aneesh Kumar K.V (Arm) <aneesh.kumar@kernel.org>
---
arch/arm64/include/asm/rmi_smc.h | 1 +
drivers/virt/coco/arm-cca-host/rmi-da.c | 116 +++++++++++++++++++++++-
drivers/virt/coco/arm-cca-host/rmi-da.h | 13 +++
3 files changed, 125 insertions(+), 5 deletions(-)
diff --git a/arch/arm64/include/asm/rmi_smc.h b/arch/arm64/include/asm/rmi_smc.h
index 7a5d57a8be7a..e9437d56996a 100644
--- a/arch/arm64/include/asm/rmi_smc.h
+++ b/arch/arm64/include/asm/rmi_smc.h
@@ -484,6 +484,7 @@ struct rmi_pdev_params {
#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_EXIT_STREAM_WAIT BIT(6)
#define RMI_DEV_COMM_NONE 0
#define RMI_DEV_COMM_RESPONSE 1
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.c b/drivers/virt/coco/arm-cca-host/rmi-da.c
index cb654d1b2eb3..28f450e2db27 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.c
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.c
@@ -197,7 +197,7 @@ static inline gfp_t cache_obj_id_to_gfp_flags(u8 cache_obj_id)
return GFP_KERNEL_ACCOUNT;
}
-static int _do_dev_communicate(enum dev_comm_type type, struct pci_tsm *tsm)
+static int _do_dev_communicate(enum dev_comm_type type, struct pci_tsm *tsm, int *stream_wait)
{
unsigned long rmi_ret;
gfp_t cache_alloc_flags;
@@ -329,11 +329,17 @@ static int _do_dev_communicate(enum dev_comm_type type, struct pci_tsm *tsm)
if (pending_dev_communicate(io_exit))
goto redo_communicate;
+ if (io_exit->flags & RMI_DEV_COMM_EXIT_STREAM_WAIT) {
+ if (stream_wait)
+ *stream_wait = 1;
+ else
+ WARN(1, "Unexpected Stream wait status\n");
+ }
return 0;
}
static int do_dev_communicate(enum dev_comm_type type,
- struct pci_tsm *tsm, unsigned long error_state)
+ struct pci_tsm *tsm, unsigned long error_state, int *stream_wait)
{
int ret, state = error_state;
struct rmi_dev_comm_enter *io_enter;
@@ -342,8 +348,10 @@ static int do_dev_communicate(enum dev_comm_type type,
io_enter = &pdev_dsc->comm_data.io_params->enter;
io_enter->resp_len = 0;
io_enter->status = RMI_DEV_COMM_NONE;
+ if (stream_wait)
+ *stream_wait = 0;
- ret = _do_dev_communicate(type, tsm);
+ ret = _do_dev_communicate(type, tsm, stream_wait);
if (ret) {
if (type == PDEV_COMMUNICATE)
rmi_pdev_abort(virt_to_phys(pdev_dsc->rmm_pdev));
@@ -371,7 +379,7 @@ static int wait_for_dev_state(enum dev_comm_type type, struct pci_tsm *tsm,
int state;
do {
- state = do_dev_communicate(type, tsm, error_state);
+ state = do_dev_communicate(type, tsm, error_state, NULL);
if (state == target_state || state == error_state)
return state;
@@ -593,7 +601,7 @@ static void pdev_collect_identity_workfn(struct work_struct *work)
guard(mutex)(&pdev_dsc->object_lock);
- do_dev_communicate(PDEV_COMMUNICATE, tsm, RMI_PDEV_ERROR);
+ do_dev_communicate(PDEV_COMMUNICATE, tsm, RMI_PDEV_ERROR, NULL);
/*
* Don't worry about communication error. The caller will look at
@@ -711,3 +719,101 @@ void cca_pdev_stop_and_destroy(struct pci_dev *pdev)
free_page((unsigned long)pdev_dsc->rmm_pdev);
pdev_dsc->rmm_pdev = NULL;
}
+
+static void stream_connect_workfn(struct work_struct *work)
+{
+ int state;
+ int peer_wait = 0;
+ struct pci_tsm *tsm;
+ int my_index, peer_index, target;
+ struct stream_connect_work *stream_work;
+ struct cca_host_pdev_dsc *pdev_dsc;
+
+ stream_work = container_of(work, struct stream_connect_work, work);
+ tsm = stream_work->tsm;
+ pdev_dsc = to_cca_pdev_dsc(tsm->dsm_dev);
+
+ my_index = stream_work->my_index;
+ peer_index = my_index ^ 0x1;
+
+redo_communicate:
+ mutex_lock(&pdev_dsc->object_lock);
+
+ state = do_dev_communicate(PDEV_COMMUNICATE, tsm, RMI_PDEV_ERROR, &peer_wait);
+ if (state != RMI_PDEV_ERROR && peer_wait) {
+
+ if (!stream_work->has_peer) {
+ WARN(1, "Unexpected STREAM_WAIT without peer stream\n");
+ mutex_unlock(&pdev_dsc->object_lock);
+ return;
+ }
+ /*
+ * Record a fresh target val for this side, then wait until
+ * peer reaches at least the same target.
+ */
+ target = atomic_inc_return(&stream_work->sync->val[my_index]);
+
+ wake_up_all(&stream_work->sync->wq);
+
+ mutex_unlock(&pdev_dsc->object_lock);
+
+ /* Wait for peer to make matching progress */
+ wait_event(stream_work->sync->wq,
+ atomic_read(&stream_work->sync->val[peer_index]) >= target);
+ goto redo_communicate;
+ }
+
+ /* Signal peer if it is waiting on me */
+ atomic_inc_return(&stream_work->sync->val[my_index]);
+ wake_up_all(&stream_work->sync->wq);
+
+ mutex_unlock(&pdev_dsc->object_lock);
+}
+
+static int __maybe_unused submit_stream_work(struct pci_dev *pdev1, struct pci_dev *pdev2,
+ unsigned long stream_handle)
+{
+ phys_addr_t rmm_pdev1_phys, rmm_pdev2_phys = 0;
+ struct cca_host_comm_data *comm_data_pdev1, *comm_data_pdev2;
+ struct cca_host_pdev_dsc *pdev_dsc1, *pdev_dsc2 = NULL;
+ struct stream_sync sync;
+ struct stream_connect_work stream_work_pdev1, stream_work_pdev2;
+
+ comm_data_pdev1 = to_cca_comm_data(pdev1);
+ init_waitqueue_head(&sync.wq);
+ atomic_set(&sync.val[0], 0);
+ atomic_set(&sync.val[1], 0);
+
+ pdev_dsc1 = to_cca_pdev_dsc(pdev1);
+ INIT_WORK_ONSTACK(&stream_work_pdev1.work, stream_connect_workfn);
+ stream_work_pdev1.tsm = pdev1->tsm;
+ stream_work_pdev1.sync = &sync;
+ stream_work_pdev1.my_index = 0;
+ stream_work_pdev1.has_peer = !!pdev2;
+ queue_work(comm_data_pdev1->work_queue, &stream_work_pdev1.work);
+
+ if (pdev2) {
+ comm_data_pdev2 = to_cca_comm_data(pdev2);
+ pdev_dsc2 = to_cca_pdev_dsc(pdev2);
+ INIT_WORK_ONSTACK(&stream_work_pdev2.work, stream_connect_workfn);
+ stream_work_pdev2.tsm = pdev2->tsm;
+ stream_work_pdev2.sync = &sync;
+ stream_work_pdev2.my_index = 1;
+ stream_work_pdev2.has_peer = true;
+ queue_work(comm_data_pdev2->work_queue, &stream_work_pdev2.work);
+ }
+
+ flush_work(&stream_work_pdev1.work);
+ if (pdev2) {
+ flush_work(&stream_work_pdev2.work);
+ destroy_work_on_stack(&stream_work_pdev2.work);
+ }
+
+ destroy_work_on_stack(&stream_work_pdev1.work);
+
+ rmm_pdev1_phys = virt_to_phys(pdev_dsc1->rmm_pdev);
+ if (pdev2)
+ rmm_pdev2_phys = virt_to_phys(pdev_dsc2->rmm_pdev);
+
+ return 0;
+}
diff --git a/drivers/virt/coco/arm-cca-host/rmi-da.h b/drivers/virt/coco/arm-cca-host/rmi-da.h
index 240b2993ae53..5b0f43493485 100644
--- a/drivers/virt/coco/arm-cca-host/rmi-da.h
+++ b/drivers/virt/coco/arm-cca-host/rmi-da.h
@@ -27,6 +27,19 @@ struct dev_comm_work {
struct work_struct work;
};
+struct stream_sync {
+ wait_queue_head_t wq;
+ atomic_t val[2];
+};
+
+struct stream_connect_work {
+ struct pci_tsm *tsm;
+ struct work_struct work;
+ struct stream_sync *sync;
+ u8 my_index;
+ bool has_peer;
+};
+
struct cca_host_comm_data {
void *rsp_buff;
void *req_buff;
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox