| From 27aa9f97887f599267c345075e61979de785c770 Mon Sep 17 00:00:00 2001 |
| From: Peng Ma <peng.ma@nxp.com> |
| Date: Thu, 11 Oct 2018 16:49:41 +0800 |
| Subject: [PATCH] dma: caam: add dma memcpy driver |
| MIME-Version: 1.0 |
| Content-Type: text/plain; charset=UTF-8 |
| Content-Transfer-Encoding: 8bit |
| |
| This module introduces a memcpy DMA driver based on the DMA capabilities |
| of the CAAM hardware block. CAAM DMA is a platform driver that is only |
| probed if the device is defined in the device tree. The driver creates |
| a DMA channel for each JR of the CAAM. This introduces a dependency on |
| the JR driver. Therefore a defering mechanism was used to ensure that |
| the CAAM DMA driver is probed only after the JR driver. |
| |
| Signed-off-by: Radu Alexe <radu.alexe@nxp.com> |
| Signed-off-by: Tudor Ambarus <tudor-dan.ambarus@nxp.com> |
| Signed-off-by: Rajiv Vishwakarma <rajiv.vishwakarma@nxp.com> |
| Signed-off-by: Horia Geantă <horia.geanta@nxp.com> |
| [rebase] |
| Signed-off-by: Yangbo Lu <yangbo.lu@nxp.com> |
| --- |
| drivers/dma/Kconfig | 19 +- |
| drivers/dma/Makefile | 1 + |
| drivers/dma/caam_dma.c | 462 +++++++++++++++++++++++++++++++++++++++++++++++++ |
| 3 files changed, 481 insertions(+), 1 deletion(-) |
| create mode 100644 drivers/dma/caam_dma.c |
| |
| --- a/drivers/dma/Kconfig |
| +++ b/drivers/dma/Kconfig |
| @@ -132,6 +132,24 @@ config COH901318 |
| help |
| Enable support for ST-Ericsson COH 901 318 DMA. |
| |
| +config CRYPTO_DEV_FSL_CAAM_DMA |
| + tristate "CAAM DMA engine support" |
| + depends on CRYPTO_DEV_FSL_CAAM_JR |
| + default n |
| + select DMA_ENGINE |
| + select ASYNC_CORE |
| + select ASYNC_TX_ENABLE_CHANNEL_SWITCH |
| + help |
| + Selecting this will offload the DMA operations for users of |
| + the scatter gather memcopy API to the CAAM via job rings. The |
| + CAAM is a hardware module that provides hardware acceleration to |
| + cryptographic operations. It has a built-in DMA controller that can |
| + be programmed to read/write cryptographic data. This module defines |
| + a DMA driver that uses the DMA capabilities of the CAAM. |
| + |
| + To compile this as a module, choose M here: the module |
| + will be called caam_dma. |
| + |
| config DMA_BCM2835 |
| tristate "BCM2835 DMA engine support" |
| depends on ARCH_BCM2835 |
| @@ -663,7 +681,6 @@ config ZX_DMA |
| help |
| Support the DMA engine for ZTE ZX family platform devices. |
| |
| - |
| # driver files |
| source "drivers/dma/bestcomm/Kconfig" |
| |
| --- a/drivers/dma/Makefile |
| +++ b/drivers/dma/Makefile |
| @@ -77,6 +77,7 @@ obj-$(CONFIG_XGENE_DMA) += xgene-dma.o |
| obj-$(CONFIG_ZX_DMA) += zx_dma.o |
| obj-$(CONFIG_ST_FDMA) += st_fdma.o |
| obj-$(CONFIG_FSL_DPAA2_QDMA) += fsl-dpaa2-qdma/ |
| +obj-$(CONFIG_CRYPTO_DEV_FSL_CAAM_DMA) += caam_dma.o |
| |
| obj-y += mediatek/ |
| obj-y += qcom/ |
| --- /dev/null |
| +++ b/drivers/dma/caam_dma.c |
| @@ -0,0 +1,462 @@ |
| +/* |
| + * caam support for SG DMA |
| + * |
| + * Copyright 2016 Freescale Semiconductor, Inc |
| + * Copyright 2017 NXP |
| + * |
| + * Redistribution and use in source and binary forms, with or without |
| + * modification, are permitted provided that the following conditions are met: |
| + * * Redistributions of source code must retain the above copyright |
| + * notice, this list of conditions and the following disclaimer. |
| + * * Redistributions in binary form must reproduce the above copyright |
| + * notice, this list of conditions and the following disclaimer in the |
| + * documentation and/or other materials provided with the distribution. |
| + * * Neither the names of the above-listed copyright holders nor the |
| + * names of any contributors may be used to endorse or promote products |
| + * derived from this software without specific prior written permission. |
| + * |
| + * |
| + * ALTERNATIVELY, this software may be distributed under the terms of the |
| + * GNU General Public License ("GPL") as published by the Free Software |
| + * Foundation, either version 2 of that License or (at your option) any |
| + * later version. |
| + * |
| + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE |
| + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| + * POSSIBILITY OF SUCH DAMAGE. |
| + */ |
| + |
| +#include <linux/dma-mapping.h> |
| +#include <linux/dmaengine.h> |
| +#include <linux/module.h> |
| +#include <linux/platform_device.h> |
| +#include <linux/slab.h> |
| + |
| +#include "dmaengine.h" |
| + |
| +#include "../crypto/caam/regs.h" |
| +#include "../crypto/caam/jr.h" |
| +#include "../crypto/caam/error.h" |
| +#include "../crypto/caam/desc_constr.h" |
| + |
| +#define DESC_DMA_MEMCPY_LEN ((CAAM_DESC_BYTES_MAX - DESC_JOB_IO_LEN) / \ |
| + CAAM_CMD_SZ) |
| + |
| +/* |
| + * This is max chunk size of a DMA transfer. If a buffer is larger than this |
| + * value it is internally broken into chunks of max CAAM_DMA_CHUNK_SIZE bytes |
| + * and for each chunk a DMA transfer request is issued. |
| + * This value is the largest number on 16 bits that is a multiple of 256 bytes |
| + * (the largest configurable CAAM DMA burst size). |
| + */ |
| +#define CAAM_DMA_CHUNK_SIZE 65280 |
| + |
| +struct caam_dma_sh_desc { |
| + u32 desc[DESC_DMA_MEMCPY_LEN] ____cacheline_aligned; |
| + dma_addr_t desc_dma; |
| +}; |
| + |
| +/* caam dma extended descriptor */ |
| +struct caam_dma_edesc { |
| + struct dma_async_tx_descriptor async_tx; |
| + struct list_head node; |
| + struct caam_dma_ctx *ctx; |
| + dma_addr_t src_dma; |
| + dma_addr_t dst_dma; |
| + unsigned int src_len; |
| + unsigned int dst_len; |
| + u32 jd[] ____cacheline_aligned; |
| +}; |
| + |
| +/* |
| + * caam_dma_ctx - per jr/channel context |
| + * @chan: dma channel used by async_tx API |
| + * @node: list_head used to attach to the global dma_ctx_list |
| + * @jrdev: Job Ring device |
| + * @pending_q: queue of pending (submitted, but not enqueued) jobs |
| + * @done_not_acked: jobs that have been completed by jr, but maybe not acked |
| + * @edesc_lock: protects extended descriptor |
| + */ |
| +struct caam_dma_ctx { |
| + struct dma_chan chan; |
| + struct list_head node; |
| + struct device *jrdev; |
| + struct list_head pending_q; |
| + struct list_head done_not_acked; |
| + spinlock_t edesc_lock; |
| +}; |
| + |
| +static struct dma_device *dma_dev; |
| +static struct caam_dma_sh_desc *dma_sh_desc; |
| +static LIST_HEAD(dma_ctx_list); |
| + |
| +static dma_cookie_t caam_dma_tx_submit(struct dma_async_tx_descriptor *tx) |
| +{ |
| + struct caam_dma_edesc *edesc = NULL; |
| + struct caam_dma_ctx *ctx = NULL; |
| + dma_cookie_t cookie; |
| + |
| + edesc = container_of(tx, struct caam_dma_edesc, async_tx); |
| + ctx = container_of(tx->chan, struct caam_dma_ctx, chan); |
| + |
| + spin_lock_bh(&ctx->edesc_lock); |
| + |
| + cookie = dma_cookie_assign(tx); |
| + list_add_tail(&edesc->node, &ctx->pending_q); |
| + |
| + spin_unlock_bh(&ctx->edesc_lock); |
| + |
| + return cookie; |
| +} |
| + |
| +static void caam_jr_chan_free_edesc(struct caam_dma_edesc *edesc) |
| +{ |
| + struct caam_dma_ctx *ctx = edesc->ctx; |
| + struct caam_dma_edesc *_edesc = NULL; |
| + |
| + spin_lock_bh(&ctx->edesc_lock); |
| + |
| + list_add_tail(&edesc->node, &ctx->done_not_acked); |
| + list_for_each_entry_safe(edesc, _edesc, &ctx->done_not_acked, node) { |
| + if (async_tx_test_ack(&edesc->async_tx)) { |
| + list_del(&edesc->node); |
| + kfree(edesc); |
| + } |
| + } |
| + |
| + spin_unlock_bh(&ctx->edesc_lock); |
| +} |
| + |
| +static void caam_dma_done(struct device *dev, u32 *hwdesc, u32 err, |
| + void *context) |
| +{ |
| + struct caam_dma_edesc *edesc = context; |
| + struct caam_dma_ctx *ctx = edesc->ctx; |
| + dma_async_tx_callback callback; |
| + void *callback_param; |
| + |
| + if (err) |
| + caam_jr_strstatus(ctx->jrdev, err); |
| + |
| + dma_run_dependencies(&edesc->async_tx); |
| + |
| + spin_lock_bh(&ctx->edesc_lock); |
| + dma_cookie_complete(&edesc->async_tx); |
| + spin_unlock_bh(&ctx->edesc_lock); |
| + |
| + callback = edesc->async_tx.callback; |
| + callback_param = edesc->async_tx.callback_param; |
| + |
| + dma_descriptor_unmap(&edesc->async_tx); |
| + |
| + caam_jr_chan_free_edesc(edesc); |
| + |
| + if (callback) |
| + callback(callback_param); |
| +} |
| + |
| +static void caam_dma_memcpy_init_job_desc(struct caam_dma_edesc *edesc) |
| +{ |
| + u32 *jd = edesc->jd; |
| + u32 *sh_desc = dma_sh_desc->desc; |
| + dma_addr_t desc_dma = dma_sh_desc->desc_dma; |
| + |
| + /* init the job descriptor */ |
| + init_job_desc_shared(jd, desc_dma, desc_len(sh_desc), HDR_REVERSE); |
| + |
| + /* set SEQIN PTR */ |
| + append_seq_in_ptr(jd, edesc->src_dma, edesc->src_len, 0); |
| + |
| + /* set SEQOUT PTR */ |
| + append_seq_out_ptr(jd, edesc->dst_dma, edesc->dst_len, 0); |
| + |
| + print_hex_dump_debug("caam dma desc@" __stringify(__LINE__) ": ", |
| + DUMP_PREFIX_ADDRESS, 16, 4, jd, desc_bytes(jd), 1); |
| +} |
| + |
| +static struct dma_async_tx_descriptor * |
| +caam_dma_prep_memcpy(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src, |
| + size_t len, unsigned long flags) |
| +{ |
| + struct caam_dma_edesc *edesc; |
| + struct caam_dma_ctx *ctx = container_of(chan, struct caam_dma_ctx, |
| + chan); |
| + |
| + edesc = kzalloc(sizeof(*edesc) + DESC_JOB_IO_LEN, GFP_DMA | GFP_NOWAIT); |
| + if (!edesc) |
| + return ERR_PTR(-ENOMEM); |
| + |
| + dma_async_tx_descriptor_init(&edesc->async_tx, chan); |
| + edesc->async_tx.tx_submit = caam_dma_tx_submit; |
| + edesc->async_tx.flags = flags; |
| + edesc->async_tx.cookie = -EBUSY; |
| + |
| + edesc->src_dma = src; |
| + edesc->src_len = len; |
| + edesc->dst_dma = dst; |
| + edesc->dst_len = len; |
| + edesc->ctx = ctx; |
| + |
| + caam_dma_memcpy_init_job_desc(edesc); |
| + |
| + return &edesc->async_tx; |
| +} |
| + |
| +/* This function can be called in an interrupt context */ |
| +static void caam_dma_issue_pending(struct dma_chan *chan) |
| +{ |
| + struct caam_dma_ctx *ctx = container_of(chan, struct caam_dma_ctx, |
| + chan); |
| + struct caam_dma_edesc *edesc, *_edesc; |
| + |
| + spin_lock_bh(&ctx->edesc_lock); |
| + list_for_each_entry_safe(edesc, _edesc, &ctx->pending_q, node) { |
| + if (caam_jr_enqueue(ctx->jrdev, edesc->jd, |
| + caam_dma_done, edesc) < 0) |
| + break; |
| + list_del(&edesc->node); |
| + } |
| + spin_unlock_bh(&ctx->edesc_lock); |
| +} |
| + |
| +static void caam_dma_free_chan_resources(struct dma_chan *chan) |
| +{ |
| + struct caam_dma_ctx *ctx = container_of(chan, struct caam_dma_ctx, |
| + chan); |
| + struct caam_dma_edesc *edesc, *_edesc; |
| + |
| + spin_lock_bh(&ctx->edesc_lock); |
| + list_for_each_entry_safe(edesc, _edesc, &ctx->pending_q, node) { |
| + list_del(&edesc->node); |
| + kfree(edesc); |
| + } |
| + list_for_each_entry_safe(edesc, _edesc, &ctx->done_not_acked, node) { |
| + list_del(&edesc->node); |
| + kfree(edesc); |
| + } |
| + spin_unlock_bh(&ctx->edesc_lock); |
| +} |
| + |
| +static int caam_dma_jr_chan_bind(void) |
| +{ |
| + struct device *jrdev; |
| + struct caam_dma_ctx *ctx; |
| + int bonds = 0; |
| + int i; |
| + |
| + for (i = 0; i < caam_jr_driver_probed(); i++) { |
| + jrdev = caam_jridx_alloc(i); |
| + if (IS_ERR(jrdev)) { |
| + pr_err("job ring device %d allocation failed\n", i); |
| + continue; |
| + } |
| + |
| + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); |
| + if (!ctx) { |
| + caam_jr_free(jrdev); |
| + continue; |
| + } |
| + |
| + ctx->chan.device = dma_dev; |
| + ctx->chan.private = ctx; |
| + |
| + ctx->jrdev = jrdev; |
| + |
| + INIT_LIST_HEAD(&ctx->pending_q); |
| + INIT_LIST_HEAD(&ctx->done_not_acked); |
| + INIT_LIST_HEAD(&ctx->node); |
| + spin_lock_init(&ctx->edesc_lock); |
| + |
| + dma_cookie_init(&ctx->chan); |
| + |
| + /* add the context of this channel to the context list */ |
| + list_add_tail(&ctx->node, &dma_ctx_list); |
| + |
| + /* add this channel to the device chan list */ |
| + list_add_tail(&ctx->chan.device_node, &dma_dev->channels); |
| + |
| + bonds++; |
| + } |
| + |
| + return bonds; |
| +} |
| + |
| +static inline void caam_jr_dma_free(struct dma_chan *chan) |
| +{ |
| + struct caam_dma_ctx *ctx = container_of(chan, struct caam_dma_ctx, |
| + chan); |
| + |
| + list_del(&ctx->node); |
| + list_del(&chan->device_node); |
| + caam_jr_free(ctx->jrdev); |
| + kfree(ctx); |
| +} |
| + |
| +static void set_caam_dma_desc(u32 *desc) |
| +{ |
| + u32 *jmp_cmd; |
| + |
| + /* dma shared descriptor */ |
| + init_sh_desc(desc, HDR_SHARE_NEVER | (1 << HDR_START_IDX_SHIFT)); |
| + |
| + /* REG1 = CAAM_DMA_CHUNK_SIZE */ |
| + append_math_add_imm_u32(desc, REG1, ZERO, IMM, CAAM_DMA_CHUNK_SIZE); |
| + |
| + /* REG0 = SEQINLEN - CAAM_DMA_CHUNK_SIZE */ |
| + append_math_sub_imm_u32(desc, REG0, SEQINLEN, IMM, CAAM_DMA_CHUNK_SIZE); |
| + |
| + /* |
| + * if (REG0 > 0) |
| + * jmp to LABEL1 |
| + */ |
| + jmp_cmd = append_jump(desc, JUMP_TEST_INVALL | JUMP_COND_MATH_N | |
| + JUMP_COND_MATH_Z); |
| + |
| + /* REG1 = SEQINLEN */ |
| + append_math_sub(desc, REG1, SEQINLEN, ZERO, CAAM_CMD_SZ); |
| + |
| + /* LABEL1 */ |
| + set_jump_tgt_here(desc, jmp_cmd); |
| + |
| + /* VARSEQINLEN = REG1 */ |
| + append_math_add(desc, VARSEQINLEN, REG1, ZERO, CAAM_CMD_SZ); |
| + |
| + /* VARSEQOUTLEN = REG1 */ |
| + append_math_add(desc, VARSEQOUTLEN, REG1, ZERO, CAAM_CMD_SZ); |
| + |
| + /* do FIFO STORE */ |
| + append_seq_fifo_store(desc, 0, FIFOST_TYPE_METADATA | LDST_VLF); |
| + |
| + /* do FIFO LOAD */ |
| + append_seq_fifo_load(desc, 0, FIFOLD_CLASS_CLASS1 | |
| + FIFOLD_TYPE_IFIFO | LDST_VLF); |
| + |
| + /* |
| + * if (REG0 > 0) |
| + * jmp 0xF8 (after shared desc header) |
| + */ |
| + append_jump(desc, JUMP_TEST_INVALL | JUMP_COND_MATH_N | |
| + JUMP_COND_MATH_Z | 0xF8); |
| + |
| + print_hex_dump_debug("caam dma shdesc@" __stringify(__LINE__) ": ", |
| + DUMP_PREFIX_ADDRESS, 16, 4, desc, desc_bytes(desc), |
| + 1); |
| +} |
| + |
| +static int caam_dma_probe(struct platform_device *pdev) |
| +{ |
| + struct device *dev = &pdev->dev; |
| + struct device *ctrldev = dev->parent; |
| + struct dma_chan *chan, *_chan; |
| + u32 *sh_desc; |
| + int err = -ENOMEM; |
| + int bonds; |
| + |
| + if (!caam_jr_driver_probed()) { |
| + dev_info(dev, "Defer probing after JR driver probing\n"); |
| + return -EPROBE_DEFER; |
| + } |
| + |
| + dma_dev = kzalloc(sizeof(*dma_dev), GFP_KERNEL); |
| + if (!dma_dev) |
| + return -ENOMEM; |
| + |
| + dma_sh_desc = kzalloc(sizeof(*dma_sh_desc), GFP_KERNEL | GFP_DMA); |
| + if (!dma_sh_desc) |
| + goto desc_err; |
| + |
| + sh_desc = dma_sh_desc->desc; |
| + set_caam_dma_desc(sh_desc); |
| + dma_sh_desc->desc_dma = dma_map_single(ctrldev, sh_desc, |
| + desc_bytes(sh_desc), |
| + DMA_TO_DEVICE); |
| + if (dma_mapping_error(ctrldev, dma_sh_desc->desc_dma)) { |
| + dev_err(dev, "unable to map dma descriptor\n"); |
| + goto map_err; |
| + } |
| + |
| + INIT_LIST_HEAD(&dma_dev->channels); |
| + |
| + bonds = caam_dma_jr_chan_bind(); |
| + if (!bonds) { |
| + err = -ENODEV; |
| + goto jr_bind_err; |
| + } |
| + |
| + dma_dev->dev = dev; |
| + dma_dev->residue_granularity = DMA_RESIDUE_GRANULARITY_DESCRIPTOR; |
| + dma_cap_set(DMA_MEMCPY, dma_dev->cap_mask); |
| + dma_cap_set(DMA_PRIVATE, dma_dev->cap_mask); |
| + dma_dev->device_tx_status = dma_cookie_status; |
| + dma_dev->device_issue_pending = caam_dma_issue_pending; |
| + dma_dev->device_prep_dma_memcpy = caam_dma_prep_memcpy; |
| + dma_dev->device_free_chan_resources = caam_dma_free_chan_resources; |
| + |
| + err = dma_async_device_register(dma_dev); |
| + if (err) { |
| + dev_err(dev, "Failed to register CAAM DMA engine\n"); |
| + goto jr_bind_err; |
| + } |
| + |
| + dev_info(dev, "caam dma support with %d job rings\n", bonds); |
| + |
| + return err; |
| + |
| +jr_bind_err: |
| + list_for_each_entry_safe(chan, _chan, &dma_dev->channels, device_node) |
| + caam_jr_dma_free(chan); |
| + |
| + dma_unmap_single(ctrldev, dma_sh_desc->desc_dma, desc_bytes(sh_desc), |
| + DMA_TO_DEVICE); |
| +map_err: |
| + kfree(dma_sh_desc); |
| +desc_err: |
| + kfree(dma_dev); |
| + return err; |
| +} |
| + |
| +static int caam_dma_remove(struct platform_device *pdev) |
| +{ |
| + struct device *dev = &pdev->dev; |
| + struct device *ctrldev = dev->parent; |
| + struct caam_dma_ctx *ctx, *_ctx; |
| + |
| + dma_async_device_unregister(dma_dev); |
| + |
| + list_for_each_entry_safe(ctx, _ctx, &dma_ctx_list, node) { |
| + list_del(&ctx->node); |
| + caam_jr_free(ctx->jrdev); |
| + kfree(ctx); |
| + } |
| + |
| + dma_unmap_single(ctrldev, dma_sh_desc->desc_dma, |
| + desc_bytes(dma_sh_desc->desc), DMA_TO_DEVICE); |
| + |
| + kfree(dma_sh_desc); |
| + kfree(dma_dev); |
| + |
| + dev_info(dev, "caam dma support disabled\n"); |
| + return 0; |
| +} |
| + |
| +static struct platform_driver caam_dma_driver = { |
| + .driver = { |
| + .name = "caam-dma", |
| + }, |
| + .probe = caam_dma_probe, |
| + .remove = caam_dma_remove, |
| +}; |
| +module_platform_driver(caam_dma_driver); |
| + |
| +MODULE_LICENSE("Dual BSD/GPL"); |
| +MODULE_DESCRIPTION("NXP CAAM support for DMA engine"); |
| +MODULE_AUTHOR("NXP Semiconductors"); |
| +MODULE_ALIAS("platform:caam-dma"); |