Most Qualcomm platforms feature a proprietary hypervisor (Gunyah, QHEE), which normally takes care of the IOMMU configuration for remote processors by intercepting qcom_scm_pas_auth_and_reset() calls.
When the aforementioned hypervisor is absent, the OS must perform the configuration instead. To do so, it must first retrieve a resource table from the secure world, to ensure the settings are in sync with TZ's expectations and then it should map the resources before it calls qcom_scm_pas_auth_and_reset(). Add helper function to IOMMU map and unmap devmem resources for a given remote processor. Signed-off-by: Mukesh Ojha <mukesh.o...@oss.qualcomm.com> --- drivers/soc/qcom/mdt_loader.c | 174 ++++++++++++++++++++++++++++ include/linux/soc/qcom/mdt_loader.h | 17 +++ 2 files changed, 191 insertions(+) diff --git a/drivers/soc/qcom/mdt_loader.c b/drivers/soc/qcom/mdt_loader.c index a1718db91b3e..ea7034c4b996 100644 --- a/drivers/soc/qcom/mdt_loader.c +++ b/drivers/soc/qcom/mdt_loader.c @@ -11,13 +11,34 @@ #include <linux/device.h> #include <linux/elf.h> #include <linux/firmware.h> +#include <linux/iommu.h> #include <linux/kernel.h> +#include <linux/hashtable.h> +#include <linux/list.h> #include <linux/module.h> #include <linux/firmware/qcom/qcom_scm.h> +#include <linux/rsc_table.h> #include <linux/sizes.h> #include <linux/slab.h> #include <linux/soc/qcom/mdt_loader.h> +#define MAX_RSCTABLE_SIZE SZ_16K +#define RSC_TABLE_HASH_BITS 5 // 32 buckets + +DEFINE_HASHTABLE(qcom_pas_rsc_table_map, RSC_TABLE_HASH_BITS); + +struct qcom_pas_devmem_rsc { + struct fw_rsc_devmem *devmem; + struct list_head node; +}; + +struct qcom_pas_rsc_table_info { + struct resource_table *rsc_table; + struct list_head devmem_list; + struct hlist_node hnode; + int pas_id; +}; + static bool mdt_header_valid(const struct firmware *fw) { const struct elf32_hdr *ehdr; @@ -500,5 +521,158 @@ int qcom_mdt_pas_load(struct qcom_scm_pas_ctx *ctx, const struct firmware *fw, } EXPORT_SYMBOL_GPL(qcom_mdt_pas_load); +static void __qcom_mdt_unmap_devmem_rscs(struct qcom_pas_rsc_table_info *info, + struct iommu_domain *domain) +{ + struct qcom_pas_devmem_rsc *entry, *tmp; + + list_for_each_entry_safe(entry, tmp, &info->devmem_list, node) { + iommu_unmap(domain, entry->devmem->da, entry->devmem->len); + list_del(&entry->node); + kfree(entry); + } +} + +void qcom_mdt_pas_unmap_devmem_rscs(struct qcom_scm_pas_ctx *ctx, struct iommu_domain *domain) +{ + struct qcom_pas_rsc_table_info *info; + + if (!ctx || !domain) + return; + + if (!ctx->has_iommu) + return; + + hash_for_each_possible(qcom_pas_rsc_table_map, info, hnode, ctx->peripheral) { + if (info->pas_id == ctx->peripheral) + __qcom_mdt_unmap_devmem_rscs(info, domain); + + hash_del(&info->hnode); + kfree(info->rsc_table); + } + + return; +} +EXPORT_SYMBOL_GPL(qcom_mdt_pas_unmap_devmem_rscs); + +static int __qcom_mdt_map_devmem_rscs(struct device *dev, void *ptr, int avail, + struct iommu_domain *domain, + struct qcom_pas_rsc_table_info *info) +{ + struct qcom_pas_devmem_rsc *devmem_info; + struct fw_rsc_devmem *rsc = ptr; + int ret; + + if (sizeof(*rsc) > avail) { + dev_err(dev, "devmem rsc is truncated\n"); + return -EINVAL; + } + + if (rsc->reserved) { + dev_err(dev, "devmem rsc has non zero reserved bytes\n"); + return -EINVAL; + } + + devmem_info = kzalloc(sizeof(*devmem_info), GFP_KERNEL); + if (!devmem_info) + return -ENOMEM; + + ret = iommu_map(domain, rsc->da, rsc->pa, rsc->len, rsc->flags, GFP_KERNEL); + if (ret) { + dev_err(dev, "failed to map devmem: %d\n", ret); + kfree(devmem_info); + return ret; + } + + devmem_info->devmem = rsc; + list_add_tail(&devmem_info->node, &info->devmem_list); + + dev_dbg(dev, "mapped devmem pa 0x%x, da 0x%x, len 0x%x\n", + rsc->pa, rsc->da, rsc->len); + + return ret; +} + +/** + * qcom_mdt_pas_map_devmem_rscs() - IOMMU map device memory resources for + * a given Peripheral + * + * This routine should be called when it is known that the SoC is running + * with Linux as hypervisor at EL2 where it is in control of the IOMMU map + * of the resources for the remote processors. + * + * @ctx: pas context data structure + * @domain: IOMMU domain + * @input_rt: input resource table buffer when resource table is part of firmware + * binary, if not, pass NULL + * @input_rt_size: input resource table size, if input_rt is NULL, then pass zero. + * + * Returns 0 on success, negative errno otherwise. + * + */ +int qcom_mdt_pas_map_devmem_rscs(struct qcom_scm_pas_ctx *ctx, struct iommu_domain *domain, + void *input_rt, size_t input_rt_size) +{ + size_t output_rt_size = MAX_RSCTABLE_SIZE; + struct resource_table *rsc_table; + struct qcom_pas_rsc_table_info *info; + void *output_rt; + int ret; + int i; + + if (!ctx || !domain) + return -EINVAL; + + if (!ctx->has_iommu) + return 0; + + ret = qcom_scm_pas_get_rsc_table(ctx, input_rt, input_rt_size, &output_rt, + &output_rt_size); + if (ret) { + dev_err(ctx->dev, "error %d getting resource_table\n", ret); + return ret; + } + + rsc_table = output_rt; + info = devm_kzalloc(ctx->dev, sizeof(*info), GFP_KERNEL); + if (!info) + goto free_output_rt; + + info->pas_id = ctx->peripheral; + info->rsc_table = output_rt; + INIT_LIST_HEAD(&info->devmem_list); + for (i = 0; i < rsc_table->num; i++) { + int offset = rsc_table->offset[i]; + struct fw_rsc_hdr *hdr = (void *)rsc_table + offset; + int avail = output_rt_size - offset - sizeof(*hdr); + void *ptr = (void *)hdr + sizeof(*hdr); + + if (avail < 0) { + dev_err(ctx->dev, "rsc table is truncated\n"); + ret = -EINVAL; + goto undo_mapping; + } + + if (hdr->type == RSC_DEVMEM) { + ret = __qcom_mdt_map_devmem_rscs(ctx->dev, ptr, avail, domain, info); + if (ret) + goto undo_mapping; + } + } + + hash_add(qcom_pas_rsc_table_map, &info->hnode, ctx->peripheral); + + return 0; + +undo_mapping: + __qcom_mdt_unmap_devmem_rscs(info, domain); + +free_output_rt: + devm_kfree(ctx->dev, info->rsc_table); + + return ret; +} +EXPORT_SYMBOL_GPL(qcom_mdt_pas_map_devmem_rscs); + MODULE_DESCRIPTION("Firmware parser for Qualcomm MDT format"); MODULE_LICENSE("GPL v2"); diff --git a/include/linux/soc/qcom/mdt_loader.h b/include/linux/soc/qcom/mdt_loader.h index 450fa0be2af0..62f239f64dfb 100644 --- a/include/linux/soc/qcom/mdt_loader.h +++ b/include/linux/soc/qcom/mdt_loader.h @@ -11,6 +11,8 @@ struct device; struct firmware; struct qcom_scm_pas_ctx; +struct resource_table; +struct iommu_domain; #if IS_ENABLED(CONFIG_QCOM_MDT_LOADER) @@ -31,6 +33,11 @@ int qcom_mdt_load_no_init(struct device *dev, const struct firmware *fw, void *qcom_mdt_read_metadata(const struct firmware *fw, size_t *data_len, const char *fw_name, struct device *dev); +int qcom_mdt_pas_map_devmem_rscs(struct qcom_scm_pas_ctx *ctx, struct iommu_domain *domain, + void *rsc_table, size_t rsc_size); + +void qcom_mdt_pas_unmap_devmem_rscs(struct qcom_scm_pas_ctx *ctx, struct iommu_domain *domain); + #else /* !IS_ENABLED(CONFIG_QCOM_MDT_LOADER) */ static inline ssize_t qcom_mdt_get_size(const struct firmware *fw) @@ -68,6 +75,16 @@ static inline void *qcom_mdt_read_metadata(const struct firmware *fw, return ERR_PTR(-ENODEV); } +static inline int qcom_mdt_pas_map_devmem_rscs(struct device *dev, bool has_iommu, + struct iommu_domain *domain, int pas_id, + phys_addr_t rsc_table, size_t rsc_size) +{ + return -ENODEV; +} + +void qcom_mdt_pas_unmap_devmem_rscs(bool has_iommu, int pas_id, struct iommu_domain *domain) +{ +} #endif /* !IS_ENABLED(CONFIG_QCOM_MDT_LOADER) */ #endif -- 2.50.1