[RFC PATCH 44/45] iommu/arm-smmu-v3-kvm: Support power management with SCMI SMC

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

Discover SCMI parameters for the SMMUv3 power domain, and pass them to
the hypervisor. Power management must use a method based on SMC, so the
hypervisor driver can catch them and keep the software state in sync
with the hardware.

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

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 930d78f6e29f..198e41d808b0 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
@@ -6,6 +6,7 @@
*/
#include <asm/kvm_mmu.h>
#include <linux/local_lock.h>
+#include <linux/of_address.h>
#include <linux/of_platform.h>

#include <kvm/arm_smmu_v3.h>
@@ -495,6 +496,75 @@ static int kvm_arm_smmu_device_reset(struct host_arm_smmu_device *host_smmu)
return 0;
}

+static int kvm_arm_probe_scmi_pd(struct device_node *scmi_node,
+ struct kvm_power_domain *pd)
+{
+ int ret;
+ struct resource res;
+ struct of_phandle_args args;
+
+ pd->type = KVM_POWER_DOMAIN_ARM_SCMI;
+
+ ret = of_parse_phandle_with_args(scmi_node, "shmem", NULL, 0, &args);
+ if (ret)
+ return ret;
+
+ ret = of_address_to_resource(args.np, 0, &res);
+ if (ret)
+ goto out_put_nodes;
+
+ ret = of_property_read_u32(scmi_node, "arm,smc-id",
+ &pd->arm_scmi.smc_id);
+ if (ret)
+ goto out_put_nodes;
+
+ /*
+ * The shared buffer is unmapped from the host while a request is in
+ * flight, so it has to be on its own page.
+ */
+ if (!IS_ALIGNED(res.start, SZ_64K) || resource_size(&res) < SZ_64K) {
+ ret = -EINVAL;
+ goto out_put_nodes;
+ }
+
+ pd->arm_scmi.shmem_base = res.start;
+ pd->arm_scmi.shmem_size = resource_size(&res);
+
+out_put_nodes:
+ of_node_put(args.np);
+ return ret;
+}
+
+/* TODO: Move this. None of it is specific to SMMU */
+static int kvm_arm_probe_power_domain(struct device *dev,
+ struct kvm_power_domain *pd)
+{
+ int ret;
+ struct device_node *parent;
+ struct of_phandle_args args;
+
+ if (!of_get_property(dev->of_node, "power-domains", NULL))
+ return 0;
+
+ ret = of_parse_phandle_with_args(dev->of_node, "power-domains",
+ "#power-domain-cells", 0, &args);
+ if (ret)
+ return ret;
+
+ parent = of_get_parent(args.np);
+ if (parent && of_device_is_compatible(parent, "arm,scmi-smc") &&
+ args.args_count > 0) {
+ pd->arm_scmi.domain_id = args.args[0];
+ ret = kvm_arm_probe_scmi_pd(parent, pd);
+ } else {
+ dev_err(dev, "Unsupported PM method for %pOF\n", args.np);
+ ret = -EINVAL;
+ }
+ of_node_put(parent);
+ of_node_put(args.np);
+ return ret;
+}
+
static void *kvm_arm_smmu_alloc_domains(struct arm_smmu_device *smmu)
{
return (void *)devm_get_free_pages(smmu->dev, GFP_KERNEL | __GFP_ZERO,
@@ -513,6 +583,7 @@ static int kvm_arm_smmu_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct host_arm_smmu_device *host_smmu;
struct hyp_arm_smmu_v3_device *hyp_smmu;
+ struct kvm_power_domain power_domain = {};

if (kvm_arm_smmu_cur >= kvm_arm_smmu_count)
return -ENOSPC;
@@ -530,6 +601,10 @@ static int kvm_arm_smmu_probe(struct platform_device *pdev)
if (ret || bypass)
return ret ?: -EINVAL;

+ ret = kvm_arm_probe_power_domain(dev, &power_domain);
+ if (ret)
+ return ret;
+
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
size = resource_size(res);
if (size < SZ_128K) {
@@ -606,6 +681,7 @@ static int kvm_arm_smmu_probe(struct platform_device *pdev)
hyp_smmu->mmio_size = size;
hyp_smmu->features = smmu->features;
hyp_smmu->iommu.pgtable_cfg = cfg;
+ hyp_smmu->iommu.power_domain = power_domain;

kvm_arm_smmu_cur++;

--
2.39.0