[PATCH v2 4/5] firmware: stratix10-svc: add DMA coherent memory allocation for SMMU-enabled platforms
From: Adrian Ng Ho Yin
Date: Wed Jul 22 2026 - 05:56:16 EST
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 <adrian.ho.yin.ng@xxxxxxxxxx>
---
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