[RFC PATCH 35/45] iommu/arm-smmu-v3-kvm: Pass a list of SMMU devices to the hypervisor

Jean-Philippe Brucker
2023-02-01T14:26:53Z

Build a list of devices and donate the page to the hypervisor. At this
point the host is trusted and this would be a good opportunity to
provide more information about the system. For example, which devices
are owned by the host (perhaps via the VMID and SW bits in the stream
table, although we populate the stream table lazily at the moment.)

Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
---
.../iommu/arm/arm-smmu-v3/arm-smmu-v3-kvm.c | 123 +++++++++++++++++-
1 file changed, 120 insertions(+), 3 deletions(-)

diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kvm.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kvm.c
index 4092da8050ef..1e0daf9ea4ac 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kvm.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-kvm.c
@@ -4,15 +4,78 @@
*
* Copyright (C) 2022 Linaro Ltd.
*/
+#include <asm/kvm_mmu.h>
#include <linux/of_platform.h>

#include <kvm/arm_smmu_v3.h>

#include "arm-smmu-v3.h"

+struct host_arm_smmu_device {
+ struct arm_smmu_device smmu;
+ pkvm_handle_t id;
+};
+
+#define smmu_to_host(_smmu) \
+ container_of(_smmu, struct host_arm_smmu_device, smmu);
+
+static size_t kvm_arm_smmu_cur;
+static size_t kvm_arm_smmu_count;
+static struct hyp_arm_smmu_v3_device *kvm_arm_smmu_array;
+
static int kvm_arm_smmu_probe(struct platform_device *pdev)
{
- return -ENOSYS;
+ int ret;
+ bool bypass;
+ size_t size;
+ phys_addr_t ioaddr;
+ struct resource *res;
+ struct arm_smmu_device *smmu;
+ struct device *dev = &pdev->dev;
+ struct host_arm_smmu_device *host_smmu;
+ struct hyp_arm_smmu_v3_device *hyp_smmu;
+
+ if (kvm_arm_smmu_cur >= kvm_arm_smmu_count)
+ return -ENOSPC;
+
+ hyp_smmu = &kvm_arm_smmu_array[kvm_arm_smmu_cur];
+
+ host_smmu = devm_kzalloc(dev, sizeof(*host_smmu), GFP_KERNEL);
+ if (!host_smmu)
+ return -ENOMEM;
+
+ smmu = &host_smmu->smmu;
+ smmu->dev = dev;
+
+ ret = arm_smmu_fw_probe(pdev, smmu, &bypass);
+ if (ret || bypass)
+ return ret ?: -EINVAL;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ size = resource_size(res);
+ if (size < SZ_128K) {
+ dev_err(dev, "unsupported MMIO region size (%pr)\n", res);
+ return -EINVAL;
+ }
+ ioaddr = res->start;
+ host_smmu->id = kvm_arm_smmu_cur;
+
+ smmu->base = devm_ioremap_resource(dev, res);
+ if (IS_ERR(smmu->base))
+ return PTR_ERR(smmu->base);
+
+ ret = arm_smmu_device_hw_probe(smmu);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, host_smmu);
+
+ /* Hypervisor parameters */
+ hyp_smmu->mmio_addr = ioaddr;
+ hyp_smmu->mmio_size = size;
+ kvm_arm_smmu_cur++;
+
+ return 0;
}

static int kvm_arm_smmu_remove(struct platform_device *pdev)
@@ -33,6 +96,36 @@ static struct platform_driver kvm_arm_smmu_driver = {
.remove = kvm_arm_smmu_remove,
};

+static int kvm_arm_smmu_array_alloc(void)
+{
+ int smmu_order;
+ struct device_node *np;
+
+ kvm_arm_smmu_count = 0;
+ for_each_compatible_node(np, NULL, "arm,smmu-v3")
+ kvm_arm_smmu_count++;
+
+ if (!kvm_arm_smmu_count)
+ return 0;
+
+ /* Allocate the parameter list shared with the hypervisor */
+ smmu_order = get_order(kvm_arm_smmu_count * sizeof(*kvm_arm_smmu_array));
+ kvm_arm_smmu_array = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
+ smmu_order);
+ if (!kvm_arm_smmu_array)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static void kvm_arm_smmu_array_free(void)
+{
+ int order;
+
+ order = get_order(kvm_arm_smmu_count * sizeof(*kvm_arm_smmu_array));
+ free_pages((unsigned long)kvm_arm_smmu_array, order);
+}
+
/**
* kvm_arm_smmu_v3_init() - Reserve the SMMUv3 for KVM
* @count: on success, number of SMMUs successfully initialized
@@ -44,12 +137,36 @@ int kvm_arm_smmu_v3_init(unsigned int *count)
{
int ret;

+ /*
+ * Check whether any device owned by the host is behind an SMMU.
+ */
+ ret = kvm_arm_smmu_array_alloc();
+ *count = kvm_arm_smmu_count;
+ if (ret || !kvm_arm_smmu_count)
+ return ret;
+
ret = platform_driver_probe(&kvm_arm_smmu_driver, kvm_arm_smmu_probe);
if (ret)
- return ret;
+ goto err_free;

- *count = 0;
+ if (kvm_arm_smmu_cur != kvm_arm_smmu_count) {
+ /* A device exists but failed to probe */
+ ret = -EUNATCH;
+ goto err_free;
+ }
+
+ /*
+ * These variables are stored in the nVHE image, and won't be accessible
+ * after KVM initialization. Ownership of kvm_arm_smmu_array will be
+ * transferred to the hypervisor as well.
+ */
+ kvm_hyp_arm_smmu_v3_smmus = kern_hyp_va(kvm_arm_smmu_array);
+ kvm_hyp_arm_smmu_v3_count = kvm_arm_smmu_count;
return 0;
+
+err_free:
+ kvm_arm_smmu_array_free();
+ return ret;
}

void kvm_arm_smmu_v3_remove(void)
--
2.39.0