[PATCH v2 RESEND v2 4/5] firmware: stratix10-svc: add DMA coherent memory allocation for SMMU-enabled platforms
Adrian Ng Ho Yin <[email protected]>
| Newsgroups | org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <dbae58b2d4f7cd4203be8b2ae3f340a71965b660.1786520346.git.adrian.ho.yin.ng@altera.com> |
On Agilex5, DDR starts at 0x8000_0000 which is outside the SDM's addressable range. When SMMU is active, the driver must allocate DMA-coherent buffers and pass IOVAs (not physical addresses) to ATF. Add SVC_SDM_DMA_ADDR_BITS (29) and SVC_SDM_DMA_ADDR_OFFSET (0x8000_0000) to constrain IOVAs to the 0-512 MB window the SDM can reach and to satisfy ATF's address range check respectively. Extend struct stratix10_svc_data_mem with dma_addr to hold the raw IOVA for teardown, and struct stratix10_svc_controller with use_dma_mem and dma_addr_offset to select the DMA path at runtime. Add svc_setup_dma_memory() to set the 29-bit DMA mask. Update stratix10_svc_allocate_memory() and stratix10_svc_free_memory() with a dma_alloc_coherent()/dma_free_coherent() branch, and adjust svc_thread_cmd_data_claim() to reapply dma_addr_offset when resolving ATF completion addresses back to virtual addresses. Both new controller fields default to zero so existing gen_pool platforms are unaffected. Signed-off-by: Adrian Ng Ho Yin <[email protected]> --- drivers/firmware/stratix10-svc.c | 164 +++++++++++++++++++++++++------ 1 file changed, 133 insertions(+), 31 deletions(-) diff --git a/drivers/firmware/stratix10-svc.c b/drivers/firmware/stratix10-svc.c index 20ab90a4584f..4a2071d57399 100644 --- a/drivers/firmware/stratix10-svc.c +++ b/drivers/firmware/stratix10-svc.c @@ -7,7 +7,9 @@ #include <linux/atomic.h> #include <linux/completion.h> #include <linux/delay.h> +#include <linux/dma-mapping.h> #include <linux/genalloc.h> +#include <linux/iommu.h> #include <linux/hashtable.h> #include <linux/idr.h> #include <linux/io.h> @@ -43,6 +45,23 @@ #define FPGA_CONFIG_STATUS_TIMEOUT_SEC 30 #define BYTE_TO_WORD_SIZE 4 +/* + * SVC_SDM_DMA_ADDR_BITS - constrains the IOVA allocated by + * dma_alloc_coherent() to 29 bits (0x0000_0000 - 0x1FFF_FFFF) + * when SMMU is active on Agilex5. The SDM accesses these buffers + * via the SMMU using IOVAs, so the 29-bit limit keeps IOVAs within + * the SDM's addressable window. + * + * SVC_SDM_DMA_ADDR_OFFSET - ATF on Agilex5 distinguishes + * SMMU-mapped buffers from direct physical addresses by the + * presence of this offset. The driver adds it to the IOVA before + * passing the address to ATF via SMC; ATF strips it, translates + * the remaining IOVA through the SMMU, and the SDM accesses the + * underlying physical memory. + */ +#define SVC_SDM_DMA_ADDR_BITS 29 +#define SVC_SDM_DMA_ADDR_OFFSET 0x80000000UL + /* stratix10 service layer clients */ #define STRATIX10_RSU "stratix10-rsu" @@ -133,18 +152,25 @@ struct stratix10_svc_sh_memory { /** * struct stratix10_svc_data_mem - service memory structure * @vaddr: virtual address - * @paddr: physical address + * @paddr: address passed to ATF via SMC and echoed back in completion + * notifications; used as the lookup key in svc_pa_to_va(). + * On the SMMU path this is (IOVA + %SVC_SDM_DMA_ADDR_OFFSET); + * on the gen_pool path this equals the raw physical address. * @size: size of memory + * @dma_addr: IOVA returned by dma_alloc_coherent(); used to free the + * mapping via dma_free_coherent() on the SMMU path. * @node: link list head node * * This struct is used in a list that keeps track of buffers which have * been allocated or freed from the memory pool. Service layer driver also - * uses this struct to transfer physical address to virtual address. + * uses this struct to map the address returned by ATF back to a virtual + * address. */ struct stratix10_svc_data_mem { void *vaddr; phys_addr_t paddr; size_t size; + dma_addr_t dma_addr; struct list_head node; }; @@ -277,6 +303,15 @@ struct stratix10_svc_chan { * @svc: manages the list of client svc drivers * @sdm_lock: only allows a single command single response to SDM * @actrl: async control structure + * @use_dma_mem: when true, buffers are allocated via dma_alloc_coherent() + * instead of the ATF reserved-memory gen_pool. + * @dma_addr_offset: value added to the DMA address (IOVA) before passing it + * to ATF via SMC. ATF uses this offset to distinguish + * SMMU-mapped buffers from direct physical addresses; it + * strips the offset, translates the remaining IOVA through + * the SMMU, and the SDM accesses the underlying memory. + * Set to %SVC_SDM_DMA_ADDR_OFFSET on Agilex5 when SMMU is + * active; zero otherwise. * @chans: array of service channels * * This struct is used to create communication channels for service clients, to @@ -293,6 +328,8 @@ struct stratix10_svc_controller { struct stratix10_svc *svc; struct mutex sdm_lock; struct stratix10_async_ctrl actrl; + bool use_dma_mem; + unsigned long dma_addr_offset; struct stratix10_svc_chan chans[] __counted_by(num_chans); }; @@ -356,12 +393,18 @@ static void svc_thread_cmd_data_claim(struct stratix10_svc_controller *ctrl, complete(&ctrl->complete_status); break; } - cb_data->status = BIT(SVC_STATUS_BUFFER_DONE); - cb_data->kaddr1 = svc_pa_to_va(res.a1); - cb_data->kaddr2 = (res.a2) ? - svc_pa_to_va(res.a2) : NULL; - cb_data->kaddr3 = (res.a3) ? - svc_pa_to_va(res.a3) : NULL; + cb_data->status = BIT(SVC_STATUS_BUFFER_DONE); + /* + * The firmware COMPLETED_WRITE response returns the + * raw IOVA (without dma_addr_offset). Add it back to + * match the key stored in pmem->paddr at allocation + * time. dma_addr_offset is zero on non-SMMU paths. + */ + cb_data->kaddr1 = svc_pa_to_va(res.a1 + ctrl->dma_addr_offset); + cb_data->kaddr2 = (res.a2) ? + svc_pa_to_va(res.a2 + ctrl->dma_addr_offset) : NULL; + cb_data->kaddr3 = (res.a3) ? + svc_pa_to_va(res.a3 + ctrl->dma_addr_offset) : NULL; p_data->chan->scl->receive_cb(p_data->chan->scl, cb_data); } else { @@ -982,6 +1025,38 @@ svc_create_memory_pool(struct platform_device *pdev, return genpool; } +/** + * svc_setup_dma_memory() - configure the device for dynamic DMA allocation + * @pdev: pointer to service layer device + * + * Called instead of svc_get_sh_memory() + svc_create_memory_pool() when + * the device is behind an SMMU. Sets a 29-bit coherent DMA mask so that + * every subsequent dma_alloc_coherent() call yields an IOVA within the + * first 512MB (0x0000_0000 - 0x1FFF_FFFF). The driver then adds + * %SVC_SDM_DMA_ADDR_OFFSET to the IOVA before passing it to ATF; ATF + * strips the offset and uses the SMMU to translate the IOVA to the + * underlying physical memory for SDM access. + * + * Return: 0 on success, or a negative error code on failure. + */ +static int svc_setup_dma_memory(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + int ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(SVC_SDM_DMA_ADDR_BITS)); + if (ret) { + dev_err(dev, + "failed to set %u-bit DMA mask: %d\n", + SVC_SDM_DMA_ADDR_BITS, ret); + return ret; + } + + dev_info(dev, + "SMMU enabled: using dynamic DMA allocation (IOVA range 0-512MB)\n"); + return 0; +} + /** * svc_smccc_smc() - secure monitor call between normal and secure world * @a0: argument passed in registers 0 @@ -1843,32 +1918,52 @@ EXPORT_SYMBOL_GPL(stratix10_svc_done); void *stratix10_svc_allocate_memory(struct stratix10_svc_chan *chan, size_t size) { + struct stratix10_svc_controller *ctrl = chan->ctrl; struct stratix10_svc_data_mem *pmem; - unsigned long va; - phys_addr_t pa; - struct gen_pool *genpool = chan->ctrl->genpool; - size_t s = roundup(size, 1 << genpool->min_alloc_order); + struct gen_pool *genpool; + dma_addr_t dma_addr; + size_t s; + void *va; - pmem = devm_kzalloc(chan->ctrl->dev, sizeof(*pmem), GFP_KERNEL); - if (!pmem) - return ERR_PTR(-ENOMEM); + if (ctrl->use_dma_mem) { + pmem = kzalloc_obj(*pmem, GFP_KERNEL); + if (!pmem) + return ERR_PTR(-ENOMEM); - guard(mutex)(&svc_mem_lock); - va = gen_pool_alloc(genpool, s); - if (!va) - return ERR_PTR(-ENOMEM); + va = dma_alloc_coherent(ctrl->dev, size, &dma_addr, GFP_KERNEL); + if (!va) { + kfree(pmem); + return ERR_PTR(-ENOMEM); + } + + pmem->vaddr = va; + pmem->paddr = dma_addr + ctrl->dma_addr_offset; + pmem->dma_addr = dma_addr; + pmem->size = size; + } else { + genpool = ctrl->genpool; + s = roundup(size, 1 << genpool->min_alloc_order); - memset((void *)va, 0, s); - pa = gen_pool_virt_to_phys(genpool, va); + pmem = devm_kzalloc(ctrl->dev, sizeof(*pmem), GFP_KERNEL); + if (!pmem) + return ERR_PTR(-ENOMEM); - pmem->vaddr = (void *)va; - pmem->paddr = pa; - pmem->size = s; + va = (void *)gen_pool_alloc(genpool, s); + if (!va) + return ERR_PTR(-ENOMEM); + + memset(va, 0, s); + pmem->vaddr = va; + pmem->paddr = gen_pool_virt_to_phys(genpool, (unsigned long)va); + pmem->size = s; + } + + guard(mutex)(&svc_mem_lock); list_add_tail(&pmem->node, &svc_data_mem); - pr_debug("%s: %s: va=%p, pa=0x%016x\n", __func__, - chan->name, pmem->vaddr, (unsigned int)pmem->paddr); + pr_debug("%s: %s: va=%p, addr=0x%016llx\n", __func__, + chan->name, pmem->vaddr, (unsigned long long)pmem->paddr); - return (void *)va; + return va; } EXPORT_SYMBOL_GPL(stratix10_svc_allocate_memory); @@ -1890,10 +1985,17 @@ void stratix10_svc_free_memory(struct stratix10_svc_chan *chan, void *kaddr) if (pmem->vaddr != kaddr) continue; - gen_pool_free(chan->ctrl->genpool, - (unsigned long)kaddr, pmem->size); - pmem->vaddr = NULL; - list_del(&pmem->node); + if (ctrl->use_dma_mem) { + dma_free_coherent(ctrl->dev, pmem->size, + pmem->vaddr, pmem->dma_addr); + list_del(&pmem->node); + kfree(pmem); + } else { + gen_pool_free(ctrl->genpool, + (unsigned long)kaddr, pmem->size); + pmem->vaddr = NULL; + list_del(&pmem->node); + } return; } -- 2.49.GIT