diff --git a/arch/arm64/include/asm/kvm_s2mpu.h b/arch/arm64/include/asm/kvm_s2mpu.h index d513c56b2c9c..d1ed76c5f066 100644 --- a/arch/arm64/include/asm/kvm_s2mpu.h +++ b/arch/arm64/include/asm/kvm_s2mpu.h @@ -57,9 +57,24 @@ enum s2mpu_version { }; enum s2mpu_power_state { - S2MPU_POWER_ALWAYS_ON, + S2MPU_POWER_ALWAYS_ON = 0, S2MPU_POWER_ON, S2MPU_POWER_OFF, }; +struct s2mpu { + phys_addr_t pa; + void __iomem *va; + u32 version; + enum s2mpu_power_state power_state; + u32 power_domain_id; + u32 context_cfg_valid_vid; +}; + +extern size_t kvm_nvhe_sym(kvm_hyp_nr_s2mpus); +#define kvm_hyp_nr_s2mpus kvm_nvhe_sym(kvm_hyp_nr_s2mpus) + +extern struct s2mpu *kvm_nvhe_sym(kvm_hyp_s2mpus); +#define kvm_hyp_s2mpus kvm_nvhe_sym(kvm_hyp_s2mpus) + #endif /* __ARM64_KVM_S2MPU_H__ */ diff --git a/arch/arm64/kvm/hyp/nvhe/iommu/s2mpu.c b/arch/arm64/kvm/hyp/nvhe/iommu/s2mpu.c index 1731300e0bec..43fc1e693aef 100644 --- a/arch/arm64/kvm/hyp/nvhe/iommu/s2mpu.c +++ b/arch/arm64/kvm/hyp/nvhe/iommu/s2mpu.c @@ -8,4 +8,7 @@ #include +size_t __ro_after_init kvm_hyp_nr_s2mpus; +struct s2mpu __ro_after_init *kvm_hyp_s2mpus; + const struct kvm_iommu_ops kvm_s2mpu_ops = (struct kvm_iommu_ops){}; diff --git a/arch/arm64/kvm/iommu/s2mpu.c b/arch/arm64/kvm/iommu/s2mpu.c index 5ac5544a30c9..6d990c47146c 100644 --- a/arch/arm64/kvm/iommu/s2mpu.c +++ b/arch/arm64/kvm/iommu/s2mpu.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -22,6 +23,14 @@ struct s2mpu_irq_info { void __iomem *va; }; +struct s2mpu_list_entry { + struct list_head list; + struct device *dev; + struct s2mpu info; +}; + +static LIST_HEAD(s2mpu_list); + static irqreturn_t s2mpu_irq_handler(int irq, void *data) { struct s2mpu_irq_info *info = data; @@ -109,15 +118,16 @@ static u32 gen_ctx_cfg_valid_vid(struct platform_device *pdev, | CTX_CFG_ENTRY(7, ctx, ctx_vid[7]); } -static int s2mpu_probe_v9(struct platform_device *pdev, void __iomem *kaddr) +static int s2mpu_probe_v9(struct platform_device *pdev, void __iomem *kaddr, + struct s2mpu *info) { unsigned int num_ctx; - u32 ssmt_valid_vid_bmap, ctx_cfg_valid_vid; + u32 ssmt_valid_vid_bmap; ssmt_valid_vid_bmap = ALL_VIDS_BITMAP; num_ctx = readl_relaxed(kaddr + REG_NS_NUM_CONTEXT) & NUM_CONTEXT_MASK; - ctx_cfg_valid_vid = gen_ctx_cfg_valid_vid(pdev, num_ctx, ssmt_valid_vid_bmap); - if (!ctx_cfg_valid_vid) { + info->context_cfg_valid_vid = gen_ctx_cfg_valid_vid(pdev, num_ctx, ssmt_valid_vid_bmap); + if (!info->context_cfg_valid_vid) { dev_err(&pdev->dev, "failed to allocate context IDs"); return -EINVAL; } @@ -168,10 +178,16 @@ static int s2mpu_probe(struct platform_device *pdev) struct resource *res; void __iomem *kaddr; size_t res_size; - enum s2mpu_power_state power_state = S2MPU_POWER_ALWAYS_ON; - u32 version, power_domain_id = 0; + struct s2mpu_list_entry *entry; + struct s2mpu *info; int ret; + entry = devm_kzalloc(&pdev->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + entry->dev = &pdev->dev; + info = &entry->info; + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "failed to parse 'reg'"); @@ -191,6 +207,7 @@ static int s2mpu_probe(struct platform_device *pdev) res->start); return -EINVAL; } + info->pa = res->start; res_size = resource_size(res); if (res_size != S2MPU_MMIO_SIZE) { @@ -201,9 +218,9 @@ static int s2mpu_probe(struct platform_device *pdev) } ret = of_property_read_u32(pdev->dev.of_node, "power-domain-id", - &power_domain_id); + &info->power_domain_id); if (!ret) { - power_state = S2MPU_POWER_ON; + info->power_state = S2MPU_POWER_ON; } else if (ret != -EINVAL) { dev_err(&pdev->dev, "failed to parse power-domain-id: %d", ret); return ret; @@ -216,20 +233,23 @@ static int s2mpu_probe(struct platform_device *pdev) */ s2mpu_probe_irq(pdev, kaddr); - version = readl_relaxed(kaddr + REG_NS_VERSION); - switch (version & VERSION_CHECK_MASK) { + info->version = readl_relaxed(kaddr + REG_NS_VERSION); + switch (info->version & VERSION_CHECK_MASK) { case S2MPU_VERSION_8: break; case S2MPU_VERSION_9: - ret = s2mpu_probe_v9(pdev, kaddr); + ret = s2mpu_probe_v9(pdev, kaddr, info); if (ret) return ret; break; default: - dev_err(&pdev->dev, "unexpected version 0x%08x", version); + dev_err(&pdev->dev, "unexpected version 0x%08x", info->version); return -EINVAL; } + /* Insert successfully parsed devices to a list later copied to hyp. */ + list_add_tail(&entry->list, &s2mpu_list); + kvm_hyp_nr_s2mpus++; return 0; } @@ -245,16 +265,61 @@ static struct platform_driver of_driver = { }, }; +static struct s2mpu *alloc_s2mpu_array(void) +{ + unsigned int order; + + order = get_order(kvm_hyp_nr_s2mpus * sizeof(struct s2mpu)); + return (struct s2mpu *)__get_free_pages(GFP_KERNEL, order); +} + +static void free_s2mpu_array(struct s2mpu *array) +{ + unsigned int order; + + order = get_order(kvm_hyp_nr_s2mpus * sizeof(struct s2mpu)); + free_pages((unsigned long)array, order); +} + +static int create_s2mpu_array(struct s2mpu **array) +{ + struct s2mpu_list_entry *entry, *tmp; + size_t i; + + *array = alloc_s2mpu_array(); + if (!*array) + return -ENOMEM; + + /* Copy list to hyp array and destroy the list in the process. */ + i = 0; + list_for_each_entry_safe(entry, tmp, &s2mpu_list, list) { + (*array)[i++] = entry->info; + list_del(&entry->list); + devm_kfree(entry->dev, entry); + } + WARN_ON(i != kvm_hyp_nr_s2mpus); + + kvm_hyp_s2mpus = kern_hyp_va(*array); + return 0; +} + int kvm_s2mpu_init(void) { + struct s2mpu *s2mpus = NULL; int ret; ret = platform_driver_probe(&of_driver, s2mpu_probe); if (ret) goto out; + ret = create_s2mpu_array(&s2mpus); + if (ret) + goto out; + kvm_info("S2MPU driver initialized\n"); out: + if (ret) + free_s2mpu_array(s2mpus); return ret; }