diff --git a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml index ffdb308352c3f..5fafbc9f3de17 100644 --- a/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml +++ b/Documentation/devicetree/bindings/dma/qcom,bam-dma.yaml @@ -12,6 +12,15 @@ maintainers: allOf: - $ref: dma-controller.yaml# + - if: + not: + properties: + compatible: + contains: + const: qcom,shikra-bam-dma + then: + properties: + qcom,vmid: false properties: compatible: @@ -27,6 +36,8 @@ properties: - enum: # SDM845, SM6115, SM8150, SM8250 and QCM2290 - qcom,bam-v1.7.4 + # Shikra + - qcom,shikra-bam-dma - const: qcom,bam-v1.7.0 clocks: @@ -79,6 +90,21 @@ properties: Indicates that the bam is powered up by a remote processor but must be initialized by the local processor. + qcom,vmid: + $ref: /schemas/types.yaml#/definitions/uint32-array + minItems: 1 + maxItems: 8 + items: + minimum: 1 + maximum: 63 + description: + Destination VMIDs of the remote processor(s) that read the per-channel + descriptor FIFOs as an AXI master. When present, the driver SCM-assigns + each FIFO to these VMIDs so the remote access does not trigger an XPU + violation. HLOS is always retained as the source owner and must not be + listed. Optional even when the qcom,shikra-bam-dma compatible is + present; not valid on any other compatible in this schema. + reg: maxItems: 1 diff --git a/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml b/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml index b30544410d092..12f1b2bc46d6e 100644 --- a/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml +++ b/Documentation/devicetree/bindings/net/qcom,bam-dmux.yaml @@ -22,7 +22,13 @@ description: | properties: compatible: - const: qcom,bam-dmux + oneOf: + - const: qcom,bam-dmux + - items: + - enum: + # Shikra + - qcom,shikra-bam-dmux + - const: qcom,bam-dmux interrupts: description: diff --git a/Documentation/devicetree/bindings/remoteproc/qcom,shikra-pas.yaml b/Documentation/devicetree/bindings/remoteproc/qcom,shikra-pas.yaml index 253b14eb2b598..17461d92165b2 100644 --- a/Documentation/devicetree/bindings/remoteproc/qcom,shikra-pas.yaml +++ b/Documentation/devicetree/bindings/remoteproc/qcom,shikra-pas.yaml @@ -44,6 +44,11 @@ properties: - description: Firmware name of the Hexagon core - description: Firmware name of the Hexagon Devicetree + bam-dmux: + $ref: /schemas/net/qcom,bam-dmux.yaml# + description: + Qualcomm BAM Data Multiplexer (provides network interface to the modem) + glink-edge: $ref: /schemas/remoteproc/qcom,glink-edge.yaml# description: diff --git a/arch/arm64/boot/dts/qcom/shikra.dtsi b/arch/arm64/boot/dts/qcom/shikra.dtsi index 812ad89a649e2..132fa16399a14 100644 --- a/arch/arm64/boot/dts/qcom/shikra.dtsi +++ b/arch/arm64/boot/dts/qcom/shikra.dtsi @@ -27,22 +27,6 @@ #address-cells = <2>; #size-cells = <2>; - bam_dmux: bam-dmux { - compatible = "qcom,bam-dmux"; - - interrupts-extended = <&modem_smsm 1 IRQ_TYPE_EDGE_BOTH>, - <&modem_smsm 11 IRQ_TYPE_EDGE_BOTH>; - interrupt-names = "pc", - "pc-ack"; - - qcom,smem-states = <&apps_smsm 1>, <&apps_smsm 11>; - qcom,smem-state-names = "pc", - "pc-ack"; - - dmas = <&bam_dmux_dma 4>, <&bam_dmux_dma 5>; - dma-names = "tx", "rx"; - }; - clocks { xo_board: xo-board { compatible = "fixed-clock"; @@ -2587,18 +2571,6 @@ }; }; - bam_dmux_dma: dma-controller@6044000 { - compatible = "qcom,bam-v1.7.0"; - reg = <0x0 0x06044000 0x0 0x19000>; - interrupts = ; - #dma-cells = <1>; - qcom,ee = <0>; - - num-channels = <6>; - qcom,num-ees = <1>; - qcom,powered-remotely; - }; - remoteproc_mpss: remoteproc@6080000 { compatible = "qcom,shikra-mpss-pas"; reg = <0x0 0x06080000 0x0 0x100>; @@ -2631,6 +2603,23 @@ status = "disabled"; + bam_dmux: bam-dmux { + compatible = "qcom,shikra-bam-dmux", "qcom,bam-dmux"; + + interrupts-extended = <&modem_smsm 1 IRQ_TYPE_EDGE_BOTH>, + <&modem_smsm 11 IRQ_TYPE_EDGE_BOTH>; + interrupt-names = "pc", + "pc-ack"; + + qcom,smem-states = <&apps_smsm 1>, + <&apps_smsm 11>; + qcom,smem-state-names = "pc", + "pc-ack"; + + dmas = <&bam_dmux_dma 4>, <&bam_dmux_dma 5>; + dma-names = "tx", "rx"; + }; + glink-edge { interrupts = ; mboxes = <&apcs_glb 12>; @@ -3470,6 +3459,19 @@ }; }; + bam_dmux_dma: dma-controller@6044000 { + compatible = "qcom,shikra-bam-dma", "qcom,bam-v1.7.0"; + reg = <0x0 0x06044000 0x0 0x19000>; + interrupts = ; + #dma-cells = <1>; + qcom,ee = <0>; + + num-channels = <6>; + qcom,num-ees = <1>; + qcom,powered-remotely; + qcom,vmid = ; + }; + sram@c11e000 { compatible = "qcom,shikra-imem", "mmio-sram"; reg = <0x0 0x0c11e000 0x0 0x1000>; diff --git a/drivers/dma/qcom/Kconfig b/drivers/dma/qcom/Kconfig index ace75d7b835a2..63105b8cc1276 100644 --- a/drivers/dma/qcom/Kconfig +++ b/drivers/dma/qcom/Kconfig @@ -15,6 +15,7 @@ config QCOM_BAM_DMA depends on ARCH_QCOM || (COMPILE_TEST && OF && ARM) select DMA_ENGINE select DMA_VIRTUAL_CHANNELS + select QCOM_SCM help Enable support for the QCOM BAM DMA controller. This controller provides DMA capabilities for a variety of on-chip devices. diff --git a/drivers/dma/qcom/bam_dma.c b/drivers/dma/qcom/bam_dma.c index cf1b2e512921b..d1b60a34cc877 100644 --- a/drivers/dma/qcom/bam_dma.c +++ b/drivers/dma/qcom/bam_dma.c @@ -23,24 +23,26 @@ * indication of where the hardware is currently working. */ -#include -#include +#include +#include +#include +#include +#include +#include #include -#include -#include #include -#include -#include -#include -#include -#include +#include +#include +#include #include -#include #include -#include -#include -#include +#include +#include +#include #include +#include +#include +#include #include "../dmaengine.h" #include "../virt-dma.h" @@ -380,6 +382,9 @@ struct bam_chan { struct bam_desc_hw *fifo_virt; dma_addr_t fifo_phys; + /* SCM source-VMID bitmask of the FIFO, 0 if not SCM-assigned */ + u64 fifo_src_perms; + /* fifo markers */ unsigned short head; /* start of active descriptor entries */ unsigned short tail; /* end of active descriptor entries */ @@ -417,6 +422,10 @@ struct bam_device { const struct bam_device_data *dev_data; + /* destination VMIDs for SCM assignment of descriptor FIFOs */ + u32 *vmids; + int num_vmids; + struct clk *bamclk; int irq; @@ -553,6 +562,126 @@ static void bam_chan_init_hw(struct bam_chan *bchan, bchan->tail = 0; } +/** + * bam_parse_vmids - Parse the optional qcom,vmid property + * @bdev: bam device + * + * Reads the list of destination VMIDs from qcom,vmid, if present. HLOS is + * always the source owner and must not be listed. Absent property leaves + * num_vmids 0. + */ +static int bam_parse_vmids(struct bam_device *bdev) +{ + struct device *dev = bdev->dev; + int n, i, ret; + + n = of_property_count_u32_elems(dev->of_node, "qcom,vmid"); + if (n == -EINVAL) + return 0; + + if (n < 0) + return n; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + bdev->vmids = devm_kcalloc(dev, n, sizeof(*bdev->vmids), GFP_KERNEL); + if (!bdev->vmids) + return -ENOMEM; + + ret = of_property_read_u32_array(dev->of_node, "qcom,vmid", + bdev->vmids, n); + if (ret) + return ret; + + for (i = 0; i < n; i++) { + if (bdev->vmids[i] == QCOM_SCM_VMID_HLOS) { + dev_err(dev, "qcom,vmid must not include HLOS (%u)\n", + QCOM_SCM_VMID_HLOS); + return -EINVAL; + } + } + + bdev->num_vmids = n; + + return 0; +} + +/** + * bam_assign_fifo - SCM-assign a channel's descriptor FIFO to the remote VMIDs + * @bdev: bam device + * @bchan: bam channel + * + * Grants HLOS plus the configured qcom,vmid VMIDs access to the FIFO, so + * the remote EE can read it. The updated source-VMID bitmask is stored in + * bchan->fifo_src_perms for bam_fifo_can_free() to reverse. + */ +static int bam_assign_fifo(struct bam_device *bdev, struct bam_chan *bchan) +{ + struct qcom_scm_vmperm *dst __free(kfree) = NULL; + u64 src = BIT_ULL(QCOM_SCM_VMID_HLOS); + int i, ret; + + if (!bdev->num_vmids) + return 0; + + dst = kcalloc(bdev->num_vmids + 1, sizeof(*dst), GFP_KERNEL); + if (!dst) + return -ENOMEM; + + dst[0].vmid = QCOM_SCM_VMID_HLOS; + dst[0].perm = QCOM_SCM_PERM_RW; + + for (i = 0; i < bdev->num_vmids; i++) { + dst[i + 1].vmid = bdev->vmids[i]; + dst[i + 1].perm = QCOM_SCM_PERM_RW; + } + + ret = qcom_scm_assign_mem(bchan->fifo_phys, BAM_DESC_FIFO_SIZE, + &src, dst, bdev->num_vmids + 1); + if (ret) { + dev_err(bdev->dev, "SCM assign fifo chan %u failed: %d\n", + bchan->id, ret); + return ret; + } + + bchan->fifo_src_perms = src; + + return 0; +} + +/** + * bam_fifo_can_free - Reclaim a channel's descriptor FIFO to HLOS + * @bdev: bam device + * @bchan: bam channel + * + * Returns true if the FIFO may now be freed. On SCM failure the remote VMID + * still has access, so the caller must leak the buffer instead of freeing it. + */ +static bool bam_fifo_can_free(struct bam_device *bdev, struct bam_chan *bchan) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + int ret; + + if (!bchan->fifo_src_perms) + return true; + + ret = qcom_scm_assign_mem(bchan->fifo_phys, BAM_DESC_FIFO_SIZE, + &bchan->fifo_src_perms, &hlos, 1); + if (ret) { + dev_err(bdev->dev, "SCM reclaim fifo chan %u failed: %d; leaking\n", + bchan->id, ret); + return false; + } + + bchan->fifo_src_perms = 0; + + return true; +} + /** * bam_alloc_chan - Allocate channel resources for DMA channel. * @chan: specified channel @@ -564,16 +693,29 @@ static int bam_alloc_chan(struct dma_chan *chan) struct bam_chan *bchan = to_bam_chan(chan); struct bam_device *bdev = bchan->bdev; - if (bchan->fifo_virt) - return 0; - - /* allocate FIFO descriptor space, but only if necessary */ - bchan->fifo_virt = dma_alloc_wc(bdev->dev, BAM_DESC_FIFO_SIZE, - &bchan->fifo_phys, GFP_KERNEL); - + /* + * Remote-owned BAMs keep the FIFO allocated and SCM-assigned to the + * remote VMID across power cycles (see bam_free_chan), so allocate and + * assign it only once; the block and pipe are still re-initialised on + * every power-on below. + */ if (!bchan->fifo_virt) { - dev_err(bdev->dev, "Failed to allocate desc fifo\n"); - return -ENOMEM; + /* allocate FIFO descriptor space, but only if necessary */ + bchan->fifo_virt = dma_alloc_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + &bchan->fifo_phys, GFP_KERNEL); + if (!bchan->fifo_virt) { + dev_err(bdev->dev, "Failed to allocate desc fifo\n"); + return -ENOMEM; + } + + if (bam_assign_fifo(bdev, bchan)) { + dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + bchan->fifo_virt, bchan->fifo_phys); + bchan->fifo_virt = NULL; + return -EIO; + } + } else if (!bdev->num_vmids) { + return 0; } if (bdev->active_channels++ == 0 && bdev->powered_remotely) @@ -594,7 +736,6 @@ static void bam_free_chan(struct dma_chan *chan) struct bam_chan *bchan = to_bam_chan(chan); struct bam_device *bdev = bchan->bdev; u32 val; - unsigned long flags; int ret; ret = pm_runtime_get_sync(bdev->dev); @@ -608,13 +749,29 @@ static void bam_free_chan(struct dma_chan *chan) goto err; } - spin_lock_irqsave(&bchan->vc.lock, flags); - bam_reset_channel(bchan); - spin_unlock_irqrestore(&bchan->vc.lock, flags); + /* + * Remote-owned BAMs (qcom,vmid) keep the descriptor FIFO allocated and + * SCM-assigned across power cycles: the remote may already have cut + * power, so pipe-register access would fault, and TZ still holds the + * grant for the next restart (the FIFO is reclaimed and freed once in + * bam_dma_remove). Only drop local channel state here so the block and + * pipe are re-initialised on the next power-on; skip all MMIO. + */ + if (bdev->num_vmids) { + scoped_guard(spinlock_irqsave, &bchan->vc.lock) + bchan->initialized = 0; + bdev->active_channels--; + goto err; + } + + scoped_guard(spinlock_irqsave, &bchan->vc.lock) + bam_reset_channel(bchan); - dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, bchan->fifo_virt, - bchan->fifo_phys); + if (bam_fifo_can_free(bdev, bchan)) + dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + bchan->fifo_virt, bchan->fifo_phys); bchan->fifo_virt = NULL; + bdev->active_channels--; /* mask irq for pipe/channel */ val = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_SRCS_MSK_EE)); @@ -624,7 +781,7 @@ static void bam_free_chan(struct dma_chan *chan) /* disable irq */ writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_IRQ_EN)); - if (--bdev->active_channels == 0 && bdev->powered_remotely) { + if (bdev->active_channels == 0 && bdev->powered_remotely) { /* s/w reset bam */ val = readl_relaxed(bam_addr(bdev, 0, BAM_CTRL)); val |= BAM_SW_RST; @@ -648,12 +805,11 @@ static int bam_slave_config(struct dma_chan *chan, struct dma_slave_config *cfg) { struct bam_chan *bchan = to_bam_chan(chan); - unsigned long flag; - spin_lock_irqsave(&bchan->vc.lock, flag); + guard(spinlock_irqsave)(&bchan->vc.lock); + memcpy(&bchan->slave, cfg, sizeof(*cfg)); bchan->reconfigure = 1; - spin_unlock_irqrestore(&bchan->vc.lock, flag); return 0; } @@ -680,22 +836,17 @@ static struct dma_async_tx_descriptor *bam_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sg; u32 i; struct bam_desc_hw *desc; - unsigned int num_alloc = 0; - + unsigned int num_alloc; if (!is_slave_direction(direction)) { dev_err(bdev->dev, "invalid dma direction\n"); return NULL; } - /* calculate number of required entries */ - for_each_sg(sgl, sg, sg_len, i) - num_alloc += DIV_ROUND_UP(sg_dma_len(sg), BAM_FIFO_SIZE); - /* allocate enough room to accommodate the number of entries */ + num_alloc = sg_nents_for_dma(sgl, sg_len, BAM_FIFO_SIZE); async_desc = kzalloc(struct_size(async_desc, desc, num_alloc), GFP_NOWAIT); - if (!async_desc) return NULL; @@ -776,38 +927,39 @@ static int bam_dma_terminate_all(struct dma_chan *chan) { struct bam_chan *bchan = to_bam_chan(chan); struct bam_async_desc *async_desc, *tmp; - unsigned long flag; LIST_HEAD(head); /* remove all transactions, including active transaction */ - spin_lock_irqsave(&bchan->vc.lock, flag); - /* - * If we have transactions queued, then some might be committed to the - * hardware in the desc fifo. The only way to reset the desc fifo is - * to do a hardware reset (either by pipe or the entire block). - * bam_chan_init_hw() will trigger a pipe reset, and also reinit the - * pipe. If the pipe is left disabled (default state after pipe reset) - * and is accessed by a connected hardware engine, a fatal error in - * the BAM will occur. There is a small window where this could happen - * with bam_chan_init_hw(), but it is assumed that the caller has - * stopped activity on any attached hardware engine. Make sure to do - * this first so that the BAM hardware doesn't cause memory corruption - * by accessing freed resources. - */ - if (!list_empty(&bchan->desc_list)) { - async_desc = list_first_entry(&bchan->desc_list, - struct bam_async_desc, desc_node); - bam_chan_init_hw(bchan, async_desc->dir); - } + scoped_guard(spinlock_irqsave, &bchan->vc.lock) { + /* + * If we have transactions queued, then some might be committed to the + * hardware in the desc fifo. The only way to reset the desc fifo is + * to do a hardware reset (either by pipe or the entire block). + * bam_chan_init_hw() will trigger a pipe reset, and also reinit the + * pipe. If the pipe is left disabled (default state after pipe reset) + * and is accessed by a connected hardware engine, a fatal error in + * the BAM will occur. There is a small window where this could happen + * with bam_chan_init_hw(), but it is assumed that the caller has + * stopped activity on any attached hardware engine. Make sure to do + * this first so that the BAM hardware doesn't cause memory corruption + * by accessing freed resources. + */ + if (!list_empty(&bchan->desc_list)) { + async_desc = list_first_entry(&bchan->desc_list, + struct bam_async_desc, desc_node); + /* Remote-owned BAM: pipe reset may fault, skip it. */ + if (!bchan->bdev->num_vmids) + bam_chan_init_hw(bchan, async_desc->dir); + } - list_for_each_entry_safe(async_desc, tmp, - &bchan->desc_list, desc_node) { - list_add(&async_desc->vd.node, &bchan->vc.desc_issued); - list_del(&async_desc->desc_node); - } + list_for_each_entry_safe(async_desc, tmp, + &bchan->desc_list, desc_node) { + list_add(&async_desc->vd.node, &bchan->vc.desc_issued); + list_del(&async_desc->desc_node); + } - vchan_get_all_descriptors(&bchan->vc, &head); - spin_unlock_irqrestore(&bchan->vc.lock, flag); + vchan_get_all_descriptors(&bchan->vc, &head); + } vchan_dma_desc_free_list(&bchan->vc, &head); @@ -823,17 +975,16 @@ static int bam_pause(struct dma_chan *chan) { struct bam_chan *bchan = to_bam_chan(chan); struct bam_device *bdev = bchan->bdev; - unsigned long flag; int ret; ret = pm_runtime_get_sync(bdev->dev); if (ret < 0) return ret; - spin_lock_irqsave(&bchan->vc.lock, flag); - writel_relaxed(1, bam_addr(bdev, bchan->id, BAM_P_HALT)); - bchan->paused = 1; - spin_unlock_irqrestore(&bchan->vc.lock, flag); + scoped_guard(spinlock_irqsave, &bchan->vc.lock) { + writel_relaxed(1, bam_addr(bdev, bchan->id, BAM_P_HALT)); + bchan->paused = 1; + } pm_runtime_mark_last_busy(bdev->dev); pm_runtime_put_autosuspend(bdev->dev); @@ -849,17 +1000,16 @@ static int bam_resume(struct dma_chan *chan) { struct bam_chan *bchan = to_bam_chan(chan); struct bam_device *bdev = bchan->bdev; - unsigned long flag; int ret; ret = pm_runtime_get_sync(bdev->dev); if (ret < 0) return ret; - spin_lock_irqsave(&bchan->vc.lock, flag); - writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_HALT)); - bchan->paused = 0; - spin_unlock_irqrestore(&bchan->vc.lock, flag); + scoped_guard(spinlock_irqsave, &bchan->vc.lock) { + writel_relaxed(0, bam_addr(bdev, bchan->id, BAM_P_HALT)); + bchan->paused = 0; + } pm_runtime_mark_last_busy(bdev->dev); pm_runtime_put_autosuspend(bdev->dev); @@ -876,7 +1026,6 @@ static int bam_resume(struct dma_chan *chan) static u32 process_channel_irqs(struct bam_device *bdev) { u32 i, srcs, pipe_stts, offset, avail; - unsigned long flags; struct bam_async_desc *async_desc, *tmp; srcs = readl_relaxed(bam_addr(bdev, 0, BAM_IRQ_SRCS_EE)); @@ -896,7 +1045,7 @@ static u32 process_channel_irqs(struct bam_device *bdev) writel_relaxed(pipe_stts, bam_addr(bdev, i, BAM_P_IRQ_CLR)); - spin_lock_irqsave(&bchan->vc.lock, flags); + guard(spinlock_irqsave)(&bchan->vc.lock); offset = readl_relaxed(bam_addr(bdev, i, BAM_P_SW_OFSTS)) & P_SW_OFSTS_MASK; @@ -935,8 +1084,6 @@ static u32 process_channel_irqs(struct bam_device *bdev) } list_del(&async_desc->desc_node); } - - spin_unlock_irqrestore(&bchan->vc.lock, flags); } return srcs; @@ -1000,7 +1147,6 @@ static enum dma_status bam_tx_status(struct dma_chan *chan, dma_cookie_t cookie, int ret; size_t residue = 0; unsigned int i; - unsigned long flags; ret = dma_cookie_status(chan, cookie, txstate); if (ret == DMA_COMPLETE) @@ -1009,23 +1155,22 @@ static enum dma_status bam_tx_status(struct dma_chan *chan, dma_cookie_t cookie, if (!txstate) return bchan->paused ? DMA_PAUSED : ret; - spin_lock_irqsave(&bchan->vc.lock, flags); - vd = vchan_find_desc(&bchan->vc, cookie); - if (vd) { - residue = container_of(vd, struct bam_async_desc, vd)->length; - } else { - list_for_each_entry(async_desc, &bchan->desc_list, desc_node) { - if (async_desc->vd.tx.cookie != cookie) - continue; - - for (i = 0; i < async_desc->num_desc; i++) - residue += le16_to_cpu( - async_desc->curr_desc[i].size); + scoped_guard(spinlock_irqsave, &bchan->vc.lock) { + vd = vchan_find_desc(&bchan->vc, cookie); + if (vd) { + residue = container_of(vd, struct bam_async_desc, vd)->length; + } else { + list_for_each_entry(async_desc, &bchan->desc_list, desc_node) { + if (async_desc->vd.tx.cookie != cookie) + continue; + + for (i = 0; i < async_desc->num_desc; i++) + residue += le16_to_cpu( + async_desc->curr_desc[i].size); + } } } - spin_unlock_irqrestore(&bchan->vc.lock, flags); - dma_set_residue(txstate, residue); if (ret == DMA_IN_PROGRESS && bchan->paused) @@ -1166,17 +1311,16 @@ static void dma_tasklet(struct tasklet_struct *t) { struct bam_device *bdev = from_tasklet(bdev, t, task); struct bam_chan *bchan; - unsigned long flags; unsigned int i; /* go through the channels and kick off transactions */ for (i = 0; i < bdev->num_channels; i++) { bchan = &bdev->channels[i]; - spin_lock_irqsave(&bchan->vc.lock, flags); + + guard(spinlock_irqsave)(&bchan->vc.lock); if (!list_empty(&bchan->vc.desc_issued) && !IS_BUSY(bchan)) bam_start_dma(bchan); - spin_unlock_irqrestore(&bchan->vc.lock, flags); } } @@ -1190,15 +1334,12 @@ static void dma_tasklet(struct tasklet_struct *t) static void bam_issue_pending(struct dma_chan *chan) { struct bam_chan *bchan = to_bam_chan(chan); - unsigned long flags; - spin_lock_irqsave(&bchan->vc.lock, flags); + guard(spinlock_irqsave)(&bchan->vc.lock); /* if work pending and idle, start a transaction */ if (vchan_issue_pending(&bchan->vc) && !IS_BUSY(bchan)) bam_start_dma(bchan); - - spin_unlock_irqrestore(&bchan->vc.lock, flags); } /** @@ -1322,6 +1463,10 @@ static int bam_dma_probe(struct platform_device *pdev) bdev->powered_remotely = of_property_read_bool(pdev->dev.of_node, "qcom,powered-remotely"); + ret = bam_parse_vmids(bdev); + if (ret) + return ret; + if (bdev->controlled_remotely || bdev->powered_remotely) bdev->bamclk = devm_clk_get_optional(bdev->dev, "bam_clk"); else @@ -1458,9 +1603,10 @@ static void bam_dma_remove(struct platform_device *pdev) if (!bdev->channels[i].fifo_virt) continue; - dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, - bdev->channels[i].fifo_virt, - bdev->channels[i].fifo_phys); + if (bam_fifo_can_free(bdev, &bdev->channels[i])) + dma_free_wc(bdev->dev, BAM_DESC_FIFO_SIZE, + bdev->channels[i].fifo_virt, + bdev->channels[i].fifo_phys); } tasklet_kill(&bdev->task); diff --git a/drivers/net/wwan/Kconfig b/drivers/net/wwan/Kconfig index 410b0245114e8..18f761165cdc0 100644 --- a/drivers/net/wwan/Kconfig +++ b/drivers/net/wwan/Kconfig @@ -64,6 +64,7 @@ config MHI_WWAN_MBIM config QCOM_BAM_DMUX tristate "Qualcomm BAM-DMUX WWAN network driver" depends on (DMA_ENGINE && PM && QCOM_SMEM_STATE) || COMPILE_TEST + select QCOM_SCM help The BAM Data Multiplexer provides access to the network data channels of modems integrated into many older Qualcomm SoCs, e.g. Qualcomm diff --git a/drivers/net/wwan/qcom_bam_dmux.c b/drivers/net/wwan/qcom_bam_dmux.c index 64dab8b57611c..8993346989527 100644 --- a/drivers/net/wwan/qcom_bam_dmux.c +++ b/drivers/net/wwan/qcom_bam_dmux.c @@ -9,11 +9,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include @@ -63,6 +65,7 @@ struct bam_dmux_skb_dma { struct bam_dmux *dmux; struct sk_buff *skb; dma_addr_t addr; + void *rx_virt; /* non-NULL: slot in the coherent RX block */ }; struct bam_dmux { @@ -76,6 +79,10 @@ struct bam_dmux { struct completion pc_ack_completion; struct dma_chan *rx, *tx; + /* Single coherent block backing all RX buffers, NULL if unused */ + void *rx_buf; + dma_addr_t rx_buf_dma; + u64 rx_buf_perms; /* SCM source-VMID bitmask of rx_buf */ struct bam_dmux_skb_dma rx_skbs[BAM_DMUX_NUM_SKB]; struct bam_dmux_skb_dma tx_skbs[BAM_DMUX_NUM_SKB]; spinlock_t tx_lock; /* Protect tx_skbs, tx_next_skb */ @@ -93,6 +100,10 @@ struct bam_dmux_netdev { u8 ch; }; +struct bam_dmux_data { + u32 vmid; +}; + static void bam_dmux_pc_vote(struct bam_dmux *dmux, bool enable) { reinit_completion(&dmux->pc_ack_completion); @@ -112,6 +123,9 @@ static bool bam_dmux_skb_dma_map(struct bam_dmux_skb_dma *skb_dma, { struct device *dev = skb_dma->dmux->dev; + if (skb_dma->rx_virt) /* coherent RX slot: addr pre-assigned */ + return true; + skb_dma->addr = dma_map_single(dev, skb_dma->skb->data, skb_dma->skb->len, dir); if (dma_mapping_error(dev, skb_dma->addr)) { dev_err(dev, "Failed to DMA map buffer\n"); @@ -125,6 +139,9 @@ static bool bam_dmux_skb_dma_map(struct bam_dmux_skb_dma *skb_dma, static void bam_dmux_skb_dma_unmap(struct bam_dmux_skb_dma *skb_dma, enum dma_data_direction dir) { + if (skb_dma->rx_virt) /* coherent RX slot: nothing to unmap */ + return; + dma_unmap_single(skb_dma->dmux->dev, skb_dma->addr, skb_dma->skb->len, dir); skb_dma->addr = 0; } @@ -471,9 +488,10 @@ static bool bam_dmux_skb_dma_submit_rx(struct bam_dmux_skb_dma *skb_dma) { struct bam_dmux *dmux = skb_dma->dmux; struct dma_async_tx_descriptor *desc; + size_t len = skb_dma->rx_virt ? BAM_DMUX_BUFFER_SIZE : skb_dma->skb->len; desc = dmaengine_prep_slave_single(dmux->rx, skb_dma->addr, - skb_dma->skb->len, DMA_DEV_TO_MEM, + len, DMA_DEV_TO_MEM, DMA_PREP_INTERRUPT); if (!desc) { dev_err(dmux->dev, "Failed to prepare RX DMA buffer\n"); @@ -488,6 +506,10 @@ static bool bam_dmux_skb_dma_submit_rx(struct bam_dmux_skb_dma *skb_dma) static bool bam_dmux_skb_dma_queue_rx(struct bam_dmux_skb_dma *skb_dma, gfp_t gfp) { + /* Coherent RX slots have rx_virt and addr pre-assigned at probe. */ + if (skb_dma->rx_virt) + return bam_dmux_skb_dma_submit_rx(skb_dma); + if (!skb_dma->skb) { skb_dma->skb = __netdev_alloc_skb(NULL, BAM_DMUX_BUFFER_SIZE, gfp); if (!skb_dma->skb) @@ -502,9 +524,10 @@ static bool bam_dmux_skb_dma_queue_rx(struct bam_dmux_skb_dma *skb_dma, gfp_t gf static void bam_dmux_cmd_data(struct bam_dmux_skb_dma *skb_dma) { struct bam_dmux *dmux = skb_dma->dmux; - struct sk_buff *skb = skb_dma->skb; - struct bam_dmux_hdr *hdr = (struct bam_dmux_hdr *)skb->data; + struct bam_dmux_hdr *hdr = skb_dma->rx_virt ? skb_dma->rx_virt : + (struct bam_dmux_hdr *)skb_dma->skb->data; struct net_device *netdev = dmux->netdevs[hdr->ch]; + struct sk_buff *skb; if (!netdev || !netif_running(netdev)) { dev_warn(dmux->dev, "Data for inactive channel %u\n", hdr->ch); @@ -517,10 +540,18 @@ static void bam_dmux_cmd_data(struct bam_dmux_skb_dma *skb_dma) return; } - skb_dma->skb = NULL; /* Hand over to network stack */ - - skb_pull(skb, sizeof(*hdr)); - skb_trim(skb, hdr->len); + if (skb_dma->rx_virt) { + /* Coherent block is not page-backed: copy out to a real skb */ + skb = netdev_alloc_skb(netdev, hdr->len); + if (!skb) + return; + skb_put_data(skb, (u8 *)skb_dma->rx_virt + sizeof(*hdr), hdr->len); + } else { + skb = skb_dma->skb; + skb_dma->skb = NULL; /* Hand over to network stack */ + skb_pull(skb, sizeof(*hdr)); + skb_trim(skb, hdr->len); + } skb->dev = netdev; /* Only Raw-IP/QMAP is supported by this driver */ @@ -577,10 +608,14 @@ static void bam_dmux_rx_callback(void *data) { struct bam_dmux_skb_dma *skb_dma = data; struct bam_dmux *dmux = skb_dma->dmux; - struct sk_buff *skb = skb_dma->skb; - struct bam_dmux_hdr *hdr = (struct bam_dmux_hdr *)skb->data; + struct bam_dmux_hdr *hdr; - bam_dmux_skb_dma_unmap(skb_dma, DMA_FROM_DEVICE); + if (skb_dma->rx_virt) { + hdr = skb_dma->rx_virt; /* coherent RX: no skb to unmap */ + } else { + bam_dmux_skb_dma_unmap(skb_dma, DMA_FROM_DEVICE); + hdr = (struct bam_dmux_hdr *)skb_dma->skb->data; + } if (hdr->magic != BAM_DMUX_HDR_MAGIC) { dev_err(dmux->dev, "Invalid magic in header: %#x\n", hdr->magic); @@ -647,6 +682,9 @@ static void bam_dmux_free_skbs(struct bam_dmux_skb_dma skbs[], for (i = 0; i < BAM_DMUX_NUM_SKB; i++) { struct bam_dmux_skb_dma *skb_dma = &skbs[i]; + if (skb_dma->rx_virt) /* coherent block freed at remove */ + continue; + if (skb_dma->addr) bam_dmux_skb_dma_unmap(skb_dma, dir); if (skb_dma->skb) { @@ -765,6 +803,71 @@ static int __maybe_unused bam_dmux_runtime_resume(struct device *dev) return 0; } +static int bam_dmux_alloc_coherent_rx(struct bam_dmux *dmux) +{ + struct device *dev = dmux->dev; + const struct bam_dmux_data *data = of_device_get_match_data(dev); + size_t size = BAM_DMUX_NUM_SKB * BAM_DMUX_BUFFER_SIZE; + u64 src = BIT_ULL(QCOM_SCM_VMID_HLOS); + struct qcom_scm_vmperm dst[2]; + int i, ret; + + if (!data) + return 0; + + if (!qcom_scm_is_available()) + return -EPROBE_DEFER; + + dst[0].vmid = QCOM_SCM_VMID_HLOS; + dst[0].perm = QCOM_SCM_PERM_RW; + dst[1].vmid = data->vmid; + dst[1].perm = QCOM_SCM_PERM_RW; + + dmux->rx_buf = dma_alloc_coherent(dev, size, &dmux->rx_buf_dma, GFP_KERNEL); + if (!dmux->rx_buf) + return -ENOMEM; + + for (i = 0; i < BAM_DMUX_NUM_SKB; i++) { + dmux->rx_skbs[i].rx_virt = dmux->rx_buf + i * BAM_DMUX_BUFFER_SIZE; + dmux->rx_skbs[i].addr = dmux->rx_buf_dma + i * BAM_DMUX_BUFFER_SIZE; + } + + ret = qcom_scm_assign_mem(dmux->rx_buf_dma, size, &src, dst, ARRAY_SIZE(dst)); + if (ret) { + dev_err(dev, "SCM assign RX block failed: %d\n", ret); + dma_free_coherent(dev, size, dmux->rx_buf, dmux->rx_buf_dma); + dmux->rx_buf = NULL; + return ret; + } + dmux->rx_buf_perms = src; + + return 0; +} + +static void bam_dmux_free_coherent_rx(struct bam_dmux *dmux) +{ + struct qcom_scm_vmperm hlos = { + .vmid = QCOM_SCM_VMID_HLOS, + .perm = QCOM_SCM_PERM_RW, + }; + size_t size = BAM_DMUX_NUM_SKB * BAM_DMUX_BUFFER_SIZE; + + if (!dmux->rx_buf) + return; + + if (dmux->rx_buf_perms) { + if (qcom_scm_assign_mem(dmux->rx_buf_dma, size, &dmux->rx_buf_perms, + &hlos, 1)) { + dev_err(dmux->dev, "SCM reclaim RX block failed; leaking\n"); + return; + } + dmux->rx_buf_perms = 0; + } + + dma_free_coherent(dmux->dev, size, dmux->rx_buf, dmux->rx_buf_dma); + dmux->rx_buf = NULL; +} + static int bam_dmux_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -812,6 +915,10 @@ static int bam_dmux_probe(struct platform_device *pdev) dmux->tx_skbs[i].dmux = dmux; } + ret = bam_dmux_alloc_coherent_rx(dmux); + if (ret) + return ret; + /* Runtime PM manages our own power vote. * Note that the RX path may be active even if we are runtime suspended, * since it is controlled by the remote side. @@ -848,6 +955,7 @@ static int bam_dmux_probe(struct platform_device *pdev) err_disable_pm: pm_runtime_disable(dev); pm_runtime_dont_use_autosuspend(dev); + bam_dmux_free_coherent_rx(dmux); return ret; } @@ -882,13 +990,19 @@ static void bam_dmux_remove(struct platform_device *pdev) disable_irq(dmux->pc_irq); bam_dmux_power_off(dmux); bam_dmux_free_skbs(dmux->tx_skbs, DMA_TO_DEVICE); + bam_dmux_free_coherent_rx(dmux); } static const struct dev_pm_ops bam_dmux_pm_ops = { SET_RUNTIME_PM_OPS(bam_dmux_runtime_suspend, bam_dmux_runtime_resume, NULL) }; +static const struct bam_dmux_data shikra_data = { + .vmid = QCOM_SCM_VMID_NAV, +}; + static const struct of_device_id bam_dmux_of_match[] = { + { .compatible = "qcom,shikra-bam-dmux", .data = &shikra_data }, { .compatible = "qcom,bam-dmux" }, { /* sentinel */ } }; diff --git a/drivers/remoteproc/qcom_q6v5_pas.c b/drivers/remoteproc/qcom_q6v5_pas.c index d0c3d041bf4d6..ec56e6638a442 100644 --- a/drivers/remoteproc/qcom_q6v5_pas.c +++ b/drivers/remoteproc/qcom_q6v5_pas.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +119,7 @@ struct qcom_pas { struct qcom_rproc_pdm pdm_subdev; struct qcom_rproc_ssr ssr_subdev; struct qcom_sysmon *sysmon; + struct platform_device *bam_dmux; struct qcom_scm_pas_context *pas_ctx; struct qcom_scm_pas_context *dtb_pas_ctx; @@ -799,6 +801,7 @@ static int qcom_pas_probe(struct platform_device *pdev) const struct qcom_pas_data *desc; struct qcom_pas *pas; struct rproc *rproc; + struct device_node *node; const char *fw_name, *dtb_fw_name = NULL; const struct rproc_ops *ops = &qcom_pas_ops; int ret; @@ -903,6 +906,10 @@ static int qcom_pas_probe(struct platform_device *pdev) if (ret) goto remove_ssr_sysmon; + node = of_get_compatible_child(pdev->dev.of_node, "qcom,bam-dmux"); + pas->bam_dmux = of_platform_device_create(node, NULL, &pdev->dev); + of_node_put(node); + return 0; remove_ssr_sysmon: @@ -927,6 +934,9 @@ static void qcom_pas_remove(struct platform_device *pdev) { struct qcom_pas *pas = platform_get_drvdata(pdev); + if (pas->bam_dmux) + of_platform_device_destroy(&pas->bam_dmux->dev, NULL); + rproc_del(pas->rproc); qcom_q6v5_deinit(&pas->q6v5); diff --git a/include/linux/scatterlist.h b/include/linux/scatterlist.h index 29f6ceb98d74b..6de1a24342990 100644 --- a/include/linux/scatterlist.h +++ b/include/linux/scatterlist.h @@ -441,6 +441,8 @@ static inline void sg_init_marker(struct scatterlist *sgl, int sg_nents(struct scatterlist *sg); int sg_nents_for_len(struct scatterlist *sg, u64 len); +int sg_nents_for_dma(struct scatterlist *sgl, unsigned int sglen, size_t len); + struct scatterlist *sg_last(struct scatterlist *s, unsigned int); void sg_init_table(struct scatterlist *, unsigned int); void sg_init_one(struct scatterlist *, const void *, unsigned int); diff --git a/lib/scatterlist.c b/lib/scatterlist.c index 91fe08b7e8869..a04cfa7a4533f 100644 --- a/lib/scatterlist.c +++ b/lib/scatterlist.c @@ -64,6 +64,32 @@ int sg_nents_for_len(struct scatterlist *sg, u64 len) } EXPORT_SYMBOL(sg_nents_for_len); +/** + * sg_nents_for_dma - return the count of DMA-capable entries in scatterlist + * @sgl: The scatterlist + * @sglen: The current number of entries + * @len: The maximum length of DMA-capable block + * + * Description: + * Determines the number of entries in @sgl which would be permitted in + * DMA-capable transfer if list had been split accordingly, taking into + * account chaining as well. + * + * Returns: + * the number of sgl entries needed + * + **/ +int sg_nents_for_dma(struct scatterlist *sgl, unsigned int sglen, size_t len) +{ + struct scatterlist *sg; + int i, nents = 0; + + for_each_sg(sgl, sg, sglen, i) + nents += DIV_ROUND_UP(sg_dma_len(sg), len); + return nents; +} +EXPORT_SYMBOL(sg_nents_for_dma); + /** * sg_last - return the last scatterlist entry in a list * @sgl: First entry in the scatterlist