kernel/0340-WIN2030-16412-feat-DMA-memcopy-kernel-driver.patch

773 lines
23 KiB
Diff
Raw Normal View History

2025-02-24 11:26:50 -05:00
From 7ab1830e7299a15d2c97f88ee9da3a1e566109ef Mon Sep 17 00:00:00 2001
2025-02-23 12:25:17 -05:00
From: congyuyang <congyuyang@eswincomputing.com>
Date: Mon, 2 Dec 2024 17:13:54 +0800
2025-02-24 11:26:50 -05:00
Subject: [PATCH 340/415] WIN2030-16412:feat: DMA memcopy kernel driver.
2025-02-23 12:25:17 -05:00
Changelogs:
1. DMA memcopy kernel driver
Change-Id: I4c6b889030f3a9b5a0dffbb540387d0728f4cc51
Signed-off-by: congyuyang <congyuyang@eswincomputing.com>
---
arch/riscv/configs/eic7700_defconfig | 1 +
arch/riscv/configs/win2030_defconfig | 1 +
drivers/memory/eswin/Kconfig | 1 +
drivers/memory/eswin/Makefile | 1 +
drivers/memory/eswin/es_dma_memcp/Kconfig | 6 +
drivers/memory/eswin/es_dma_memcp/Makefile | 1 +
.../memory/eswin/es_dma_memcp/es_dma_memcp.c | 603 ++++++++++++++++++
include/uapi/linux/dma_memcp.h | 62 ++
8 files changed, 676 insertions(+)
create mode 100644 drivers/memory/eswin/es_dma_memcp/Kconfig
create mode 100644 drivers/memory/eswin/es_dma_memcp/Makefile
create mode 100644 drivers/memory/eswin/es_dma_memcp/es_dma_memcp.c
create mode 100644 include/uapi/linux/dma_memcp.h
diff --git a/arch/riscv/configs/eic7700_defconfig b/arch/riscv/configs/eic7700_defconfig
index 353c29d85325..52c6b13f0804 100644
--- a/arch/riscv/configs/eic7700_defconfig
+++ b/arch/riscv/configs/eic7700_defconfig
@@ -766,6 +766,7 @@ CONFIG_ESWIN_RSVMEM_HEAP=y
CONFIG_ESWIN_MMZ_VB=y
CONFIG_ESWIN_DEV_DMA_BUF=y
CONFIG_ESWIN_IOMMU_RSV=y
+CONFIG_ESWIN_DMA_MEMCP=y
CONFIG_PWM=y
CONFIG_PWM_ESWIN=y
CONFIG_RESET_ESWIN_WIN2030=y
diff --git a/arch/riscv/configs/win2030_defconfig b/arch/riscv/configs/win2030_defconfig
index 477f2406826e..7601a3cf4932 100644
--- a/arch/riscv/configs/win2030_defconfig
+++ b/arch/riscv/configs/win2030_defconfig
@@ -795,6 +795,7 @@ CONFIG_ESWIN_RSVMEM_HEAP=y
CONFIG_ESWIN_MMZ_VB=y
CONFIG_ESWIN_DEV_DMA_BUF=y
CONFIG_ESWIN_IOMMU_RSV=y
+CONFIG_ESWIN_DMA_MEMCP=y
CONFIG_PWM=y
CONFIG_PWM_ESWIN=y
CONFIG_RESET_ESWIN_WIN2030=y
diff --git a/drivers/memory/eswin/Kconfig b/drivers/memory/eswin/Kconfig
index 3078936326e4..c187f1c0ac0a 100644
--- a/drivers/memory/eswin/Kconfig
+++ b/drivers/memory/eswin/Kconfig
@@ -31,5 +31,6 @@ source "drivers/memory/eswin/es_rsvmem_heap/Kconfig"
source "drivers/memory/eswin/es_mmz_vb/Kconfig"
source "drivers/memory/eswin/es_dev_buf/Kconfig"
source "drivers/memory/eswin/es_iommu_rsv/Kconfig"
+source "drivers/memory/eswin/es_dma_memcp/Kconfig"
endif
diff --git a/drivers/memory/eswin/Makefile b/drivers/memory/eswin/Makefile
index d8b6f2868e09..1d33ac236716 100644
--- a/drivers/memory/eswin/Makefile
+++ b/drivers/memory/eswin/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_ESWIN_RSVMEM_HEAP) += es_rsvmem_heap/
obj-$(CONFIG_ESWIN_RSVMEM_HEAP) += es_mmz_vb/
obj-$(CONFIG_ESWIN_DEV_DMA_BUF) += es_dev_buf/
obj-$(CONFIG_ESWIN_IOMMU_RSV) += es_iommu_rsv/
+obj-$(CONFIG_ESWIN_DMA_MEMCP) += es_dma_memcp/
ES_MEM_HEADER := $(srctree)/drivers/memory/eswin/
diff --git a/drivers/memory/eswin/es_dma_memcp/Kconfig b/drivers/memory/eswin/es_dma_memcp/Kconfig
new file mode 100644
index 000000000000..deff4abf37de
--- /dev/null
+++ b/drivers/memory/eswin/es_dma_memcp/Kconfig
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config ESWIN_DMA_MEMCP
+ tristate "ESWIN DMA memory copy"
+ help
+ ESWIN DMA memory copy device.
diff --git a/drivers/memory/eswin/es_dma_memcp/Makefile b/drivers/memory/eswin/es_dma_memcp/Makefile
new file mode 100644
index 000000000000..615375e6b476
--- /dev/null
+++ b/drivers/memory/eswin/es_dma_memcp/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_ESWIN_DMA_MEMCP) += es_dma_memcp.o
diff --git a/drivers/memory/eswin/es_dma_memcp/es_dma_memcp.c b/drivers/memory/eswin/es_dma_memcp/es_dma_memcp.c
new file mode 100644
index 000000000000..2901a5e8525e
--- /dev/null
+++ b/drivers/memory/eswin/es_dma_memcp/es_dma_memcp.c
@@ -0,0 +1,603 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ESWIN DMA MEMCP Driver
+ *
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * Authors: Zonglin Geng <gengzonglin@eswincomputing.com>
+ * Yuyang Cong <congyuyang@eswincomputing.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/miscdevice.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <asm/uaccess.h>
+#include <linux/version.h>
+#include <linux/dmaengine.h>
+#include <linux/uaccess.h>
+#include <linux/dma-mapping.h>
+#include <linux/dma-buf.h>
+#include <linux/workqueue.h>
+#include <uapi/linux/dma_memcp.h>
+
+#define DRIVER_NAME "es_memcp"
+
+#define MAX_NUM_USED_DMA_CH (4)
+
+static struct device *esw_memcp_dev;
+
+struct esw_memcp_dma_buf_info {
+ struct dma_buf *dma_buf;
+ int mem_nid;
+ struct dma_buf_attachment *attach;
+ struct sg_table *sgt;
+};
+
+struct cmdq {
+ struct workqueue_struct *wq;
+ struct mutex lock;
+ atomic_t total_tasks;
+ atomic_t completed_tasks;
+};
+
+struct esw_cmdq_task {
+ struct cmdq *cmdq;
+ struct esw_memcp_f2f_cmd f2f_cmd;
+ struct esw_memcp_dma_buf_info src_buf;
+ struct esw_memcp_dma_buf_info dst_buf;
+ struct work_struct work;
+ struct dma_chan *dma_ch; /* pointer to the dma channel. */
+ struct completion dma_finished;
+};
+
+#ifdef CONFIG_NUMA
+static int memcp_attach_dma_buf(struct device *dma_dev, struct esw_memcp_dma_buf_info *buf_info);
+static int memcp_detach_dma_buf(struct dma_buf_attachment *attach, struct sg_table *sgt);
+#define PAGE_IN_SPRAM_DIE0(page) ((page_to_phys(page)>=0x59000000) && (page_to_phys(page)<0x59400000))
+#define PAGE_IN_SPRAM_DIE1(page) ((page_to_phys(page)>=0x79000000) && (page_to_phys(page)<0x79400000))
+static int esw_memcp_get_mem_nid(struct esw_memcp_dma_buf_info *buf_info)
+{
+ int ret = 0;
+ struct page *page = NULL;
+ int nid = -1;
+
+ ret = memcp_attach_dma_buf(esw_memcp_dev, buf_info);
+ if(ret) {
+ pr_err("Failed to attach DMA buffer, ret=%d\n", ret);
+ return ret;
+ }
+
+ page = sg_page(buf_info->sgt->sgl);
+ if (unlikely(PAGE_IN_SPRAM_DIE0(page))) {
+ nid = 0;
+ }
+ else if(unlikely(PAGE_IN_SPRAM_DIE1(page))) {
+ nid = 1;
+ }
+ else {
+ nid = page_to_nid(page);
+ }
+
+ ret = memcp_detach_dma_buf(buf_info->attach, buf_info->sgt);
+ if(ret) {
+ pr_err("Failed to detach DMA buffer, , ret=%d\n", ret);
+ return ret;
+ }
+
+ buf_info->mem_nid = nid;
+
+ return ret;
+}
+#else
+static int esw_memcp_get_mem_nid(struct esw_memcp_dma_buf_info *buf_info)
+{
+ buf_info->mem_nid = 0;
+ return 0;
+}
+#endif
+
+static bool filter(struct dma_chan *chan, void *param)
+{
+#ifdef CONFIG_NUMA
+ if((*(int *)param) == 2)
+ return true;
+ else
+ return (*(int *)param) == dev_to_node(chan->device->dev);
+#else
+ return true;
+#endif
+}
+
+
+int esw_memcp_alloc_dma(struct esw_cmdq_task *task)
+{
+ int ret = 0;
+ dma_cap_mask_t mask;
+ struct dma_chan *dma_ch = NULL;
+
+ ret = esw_memcp_get_mem_nid(&task->src_buf);
+ if(ret) {
+ return ret;
+ }
+
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_MEMCPY, mask);
+
+ dma_ch = dma_request_channel(mask, filter, &task->src_buf.mem_nid);
+ if (IS_ERR(dma_ch)) {
+ pr_warn("dma request channel failed, Try using any of them.\n");
+ dma_ch = dma_request_channel(mask, NULL, NULL);
+ }
+
+ if (IS_ERR(dma_ch)) {
+ pr_err("dma request channel failed\n");
+ return -ENODEV;
+ }
+
+ task->dma_ch = dma_ch;
+ return 0;
+}
+
+static int memcp_attach_dma_buf(struct device *dma_dev, struct esw_memcp_dma_buf_info *buf_info)
+{
+ int ret = 0;
+ struct dma_buf *dma_buf;
+ struct dma_buf_attachment *attach;
+ struct sg_table *sgt;
+
+ if (!buf_info || !dma_dev) {
+ pr_err("Invalid parameters: buf_info or dma_dev is NULL\n");
+ return -EINVAL;
+ }
+
+ dma_buf = buf_info->dma_buf;
+ if (IS_ERR(dma_buf)) {
+ return PTR_ERR(dma_buf);
+ }
+ /* Ref + 1 */
+ attach = dma_buf_attach(dma_buf, dma_dev);
+ if (IS_ERR(attach)) {
+ ret = PTR_ERR(attach);
+ return ret;
+ }
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
+ sgt = dma_buf_map_attachment_unlocked(attach, DMA_BIDIRECTIONAL);
+#else
+ sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
+#endif
+ if (IS_ERR(sgt)) {
+ ret = PTR_ERR(sgt);
+ dma_buf_detach(dma_buf, attach);
+ return ret;
+ }
+#ifdef DMA_MEMCP_DEBUG_EN
+ struct scatterlist *sg = NULL;
+ u64 addr;
+ int len;
+ int i = 0;
+ for_each_sgtable_dma_sg(sgt, sg, i) {
+ addr = sg_dma_address(sg);
+ len = sg_dma_len(sg);
+ }
+#endif
+
+ buf_info->attach = attach;
+ buf_info->sgt = sgt;
+ return ret;
+}
+
+static int memcp_detach_dma_buf(struct dma_buf_attachment *attach, struct sg_table *sgt)
+{
+ int ret = 0;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0)
+ dma_buf_unmap_attachment_unlocked(attach, sgt, DMA_BIDIRECTIONAL);
+#else
+ dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL);
+#endif
+ /* detach attach->dma_buf*/
+ dma_buf_detach(attach->dmabuf, attach);
+ return ret;
+}
+
+static int memcp_detach_dma_buf_check(struct esw_memcp_dma_buf_info *buf_info)
+{
+ int ret = 0;
+ if(buf_info->attach) {
+ ret = memcp_detach_dma_buf(buf_info->attach, buf_info->sgt);
+ buf_info->attach = NULL;
+ }
+ return ret;
+}
+
+static int esw_memcp_release_cmdq_task(struct esw_cmdq_task *cmdq_task)
+{
+ memcp_detach_dma_buf_check(&cmdq_task->src_buf);
+ memcp_detach_dma_buf_check(&cmdq_task->dst_buf);
+
+ if (cmdq_task->src_buf.dma_buf) {
+ dma_buf_put(cmdq_task->src_buf.dma_buf);
+ cmdq_task->src_buf.dma_buf = NULL;
+ }
+ if (cmdq_task->dst_buf.dma_buf) {
+ dma_buf_put(cmdq_task->dst_buf.dma_buf);
+ cmdq_task->dst_buf.dma_buf = NULL;
+ }
+
+ if (cmdq_task->dma_ch) {
+ dma_release_channel(cmdq_task->dma_ch);
+ cmdq_task->dma_ch = NULL;
+ }
+
+ kfree(cmdq_task);
+ return 0;
+}
+
+
+static void esw_memcp_complete_func(void *cb_param)
+{
+ struct esw_cmdq_task *cmdq_task = (struct esw_cmdq_task *)cb_param;
+
+ complete(&cmdq_task->dma_finished);
+}
+
+static int esw_memcp_start_dma_f2f_trans(struct esw_cmdq_task *cmdq_task)
+{
+ int ret = 0;
+ struct esw_memcp_f2f_cmd *f2f_cmd;
+ struct dma_buf_attachment *attach;
+ struct sg_table *sgt;
+ struct dma_buf *dma_buf;
+ struct dma_chan *dma_ch;
+ struct device *dma_dev;
+ enum dma_ctrl_flags flags;
+ dma_cookie_t cookie; //used for judge whether trans has been completed.
+ struct dma_async_tx_descriptor *tx = NULL;
+ dma_addr_t src_buf_addr, dst_buf_addr;
+ size_t src_size, dst_size;
+
+ ret = esw_memcp_alloc_dma(cmdq_task);
+ if(ret) {
+ goto release_cmdq_task;
+ }
+
+ f2f_cmd = &cmdq_task->f2f_cmd;
+ dma_ch = cmdq_task->dma_ch;
+ dma_dev = dmaengine_get_dma_device(dma_ch);
+
+ ret = memcp_attach_dma_buf(dma_dev, &cmdq_task->src_buf);
+ if(ret) {
+ goto release_cmdq_task;
+ }
+ attach = cmdq_task->src_buf.attach;
+ sgt = cmdq_task->src_buf.sgt;
+ src_buf_addr = sg_dma_address(sgt->sgl) + f2f_cmd->src_offset;
+ dma_buf = attach->dmabuf;
+ src_size = dma_buf->size;
+
+ ret = memcp_attach_dma_buf(dma_dev, &cmdq_task->dst_buf);
+ if(ret) {
+ goto release_cmdq_task;
+ }
+ attach = cmdq_task->dst_buf.attach;
+ sgt = cmdq_task->dst_buf.sgt;
+ dst_buf_addr = sg_dma_address(sgt->sgl) + f2f_cmd->dst_offset;
+ dma_buf = attach->dmabuf;
+ dst_size = dma_buf->size;
+
+ init_completion(&cmdq_task->dma_finished);
+ dma_ch = cmdq_task->dma_ch;
+ flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
+ tx = dmaengine_prep_dma_memcpy(dma_ch, dst_buf_addr, src_buf_addr, f2f_cmd->len, flags);
+ if(!tx){
+ pr_err("Failed to prepare DMA memcp\n");
+ ret = -EFAULT;
+ goto release_cmdq_task;
+ }
+
+ tx->callback = esw_memcp_complete_func;
+ tx->callback_param = cmdq_task;
+
+ cookie = dmaengine_submit(tx);
+ if(dma_submit_error(cookie)){
+ pr_err("Failed to dma tx_submit\n");
+ ret = -EFAULT;
+ goto release_cmdq_task;
+ }
+
+ dma_async_issue_pending(dma_ch);
+ if (wait_for_completion_interruptible_timeout(&cmdq_task->dma_finished,
+ msecs_to_jiffies(cmdq_task->f2f_cmd.timeout)) == 0) {
+ pr_err("DMA transfer timeout.\n");
+ ret = -ETIMEDOUT;
+ dmaengine_terminate_sync(dma_ch);
+ goto release_cmdq_task;
+ }
+
+release_cmdq_task:
+ esw_memcp_release_cmdq_task(cmdq_task);
+ return ret;
+}
+
+
+static void cmdq_task_worker(struct work_struct *work) {
+ struct esw_cmdq_task *task = container_of(work, struct esw_cmdq_task, work);
+ struct cmdq *q = task->cmdq;
+ int ret;
+
+ if (!task) {
+ pr_err("cmdq_task_worker: Invalid task pointer\n");
+ return;
+ }
+
+ // Start DMA Transfer
+ ret = esw_memcp_start_dma_f2f_trans(task);
+
+ if (ret) {
+ pr_err("cmdq_task_worker: DMA transfer failed with error code %d\n", ret);
+ }
+ atomic_inc(&q->completed_tasks);
+
+}
+
+static int esw_cmdq_add_task(struct file *filp, void __user *user_arg) {
+ struct cmdq *q = (struct cmdq *)filp->private_data;
+ struct esw_cmdq_task *cmdq_task;
+ struct esw_memcp_f2f_cmd memcp_f2f_cmd;
+ int ret;
+
+ if (!q || !q->wq) {
+ pr_err("CMDQ or workqueue is NULL\n");
+ return -EINVAL;
+ }
+
+ cmdq_task = kzalloc(sizeof(struct esw_cmdq_task), GFP_KERNEL);
+ if (!cmdq_task) {
+ pr_err("Failed to allocate cmdq_task\n");
+ return -ENOMEM;
+ }
+
+ if (copy_from_user(&memcp_f2f_cmd, user_arg, sizeof(struct esw_memcp_f2f_cmd))) {
+ pr_err("Failed to copy data from user space\n");
+ kfree(cmdq_task);
+ return -EFAULT;
+ }
+
+ cmdq_task->cmdq = q;
+ cmdq_task->f2f_cmd = memcp_f2f_cmd;
+
+ cmdq_task->src_buf.dma_buf = dma_buf_get(memcp_f2f_cmd.src_fd);
+ if (IS_ERR(cmdq_task->src_buf.dma_buf)) {
+ pr_err("Failed to get src dma_buf, src_fd=%d, err=%ld\n",
+ memcp_f2f_cmd.src_fd, PTR_ERR(cmdq_task->src_buf.dma_buf));
+ kfree(cmdq_task);
+ return PTR_ERR(cmdq_task->src_buf.dma_buf);
+ }
+
+ if ((memcp_f2f_cmd.src_offset + memcp_f2f_cmd.len) > cmdq_task->src_buf.dma_buf->size) {
+ pr_err("Source buffer overflow: src_offset=%d, len=%lu, buf_size=%lu\n",
+ memcp_f2f_cmd.src_offset, memcp_f2f_cmd.len, cmdq_task->src_buf.dma_buf->size);
+ dma_buf_put(cmdq_task->src_buf.dma_buf);
+ kfree(cmdq_task);
+ return -EINVAL;
+ }
+
+ cmdq_task->dst_buf.dma_buf = dma_buf_get(memcp_f2f_cmd.dst_fd);
+ if (IS_ERR(cmdq_task->dst_buf.dma_buf)) {
+ pr_err("Failed to get dst dma_buf, dst_fd=%d, err=%ld\n",
+ memcp_f2f_cmd.dst_fd, PTR_ERR(cmdq_task->dst_buf.dma_buf));
+ dma_buf_put(cmdq_task->src_buf.dma_buf);
+ kfree(cmdq_task);
+ return PTR_ERR(cmdq_task->dst_buf.dma_buf);
+ }
+
+ if ((memcp_f2f_cmd.dst_offset + memcp_f2f_cmd.len) > cmdq_task->dst_buf.dma_buf->size) {
+ pr_err("Destination buffer overflow: dst_offset=%d, len=%lu, buf_size=%lu\n",
+ memcp_f2f_cmd.dst_offset, memcp_f2f_cmd.len, cmdq_task->dst_buf.dma_buf->size);
+ dma_buf_put(cmdq_task->src_buf.dma_buf);
+ dma_buf_put(cmdq_task->dst_buf.dma_buf);
+ kfree(cmdq_task);
+ return -EINVAL;
+ }
+
+ INIT_WORK(&cmdq_task->work, cmdq_task_worker);
+ ret = queue_work(cmdq_task->cmdq->wq, &cmdq_task->work);
+ if (!ret) {
+ pr_err("Failed to queue work\n");
+ dma_buf_put(cmdq_task->src_buf.dma_buf);
+ dma_buf_put(cmdq_task->dst_buf.dma_buf);
+ kfree(cmdq_task);
+ return -EFAULT;
+ }
+
+ atomic_inc(&q->total_tasks);
+
+ return 0;
+}
+
+static int esw_cmdq_sync(struct file *filp) {
+ struct cmdq *q = (struct cmdq *)filp->private_data;
+
+ if (!q) {
+ pr_err("esw_cmdq_sync: Invalid cmdq\n");
+ return -EINVAL;
+ }
+
+ flush_workqueue(q->wq);
+
+ return 0;
+}
+
+
+
+static int esw_cmdq_query(struct file *file, void __user *user_arg)
+{
+ struct cmdq *q;
+ struct esw_cmdq_query query;
+
+ if (!file || !user_arg) {
+ pr_err("esw_cmdq_query: Invalid arguments\n");
+ return -EINVAL;
+ }
+
+ q = file->private_data;
+ if (!q) {
+ pr_err("esw_cmdq_query: No cmdq associated with this file descriptor\n");
+ return -EINVAL;
+ }
+
+ int total = atomic_read(&q->total_tasks);
+ int completed = atomic_read(&q->completed_tasks);
+
+ query.status = (total == completed) ? 0 : 1; // 0 FREE1 BUSY
+ query.task_count = total - completed;
+
+ if (copy_to_user(user_arg, &query, sizeof(query))) {
+ pr_err("esw_cmdq_query: Failed to copy data to user space\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+static long esw_memcp_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+
+ switch (cmd) {
+ case (ESW_CMDQ_ADD_TASK):
+ ret = esw_cmdq_add_task(filp, (void *)arg);
+ break;
+
+ case (ESW_CMDQ_SYNC):
+ ret = esw_cmdq_sync(filp);
+ break;
+
+ case (ESW_CMDQ_QUERY):
+ ret = esw_cmdq_query(filp, (void *)arg);
+ break;
+
+ default:
+ dev_err(esw_memcp_dev, "invalid cmd! cmd=0x%x, arg=0x%lx\n", cmd, arg);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static ssize_t esw_memcp_write(struct file *file, const char __user *data, size_t len, loff_t *ppos)
+{
+ dev_info(esw_memcp_dev, "Write: operation not supported\n");
+ return -EINVAL;
+}
+
+static int esw_memcp_open(struct inode *inode, struct file *filp)
+{
+ struct cmdq *q;
+
+ q = kzalloc(sizeof(struct cmdq), GFP_KERNEL);
+ if (!q) {
+ pr_err("Failed to allocate cmdq\n");
+ return -ENOMEM;
+ }
+
+ q->wq = alloc_workqueue("cmdq_wq", WQ_UNBOUND | WQ_FREEZABLE | WQ_MEM_RECLAIM, 0);
+ if (!q->wq) {
+ pr_err("Failed to allocate workqueue\n");
+ kfree(q);
+ return -ENOMEM;
+ }
+
+ atomic_set(&q->total_tasks, 0);
+ atomic_set(&q->completed_tasks, 0);
+
+ filp->private_data = q;
+
+ return 0;
+}
+
+
+
+static int esw_memcp_release(struct inode *inode, struct file *filp)
+{
+ struct cmdq *q = (struct cmdq *)filp->private_data;
+
+ if (q) {
+ if (q->wq) {
+ destroy_workqueue(q->wq);
+ }
+
+ kfree(q);
+ }
+
+ return 0;
+}
+
+
+static struct file_operations esw_memcp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .write = esw_memcp_write,
+ .unlocked_ioctl = esw_memcp_ioctl,
+ .open = esw_memcp_open,
+ .release = esw_memcp_release,
+};
+
+static struct miscdevice esw_memcp_miscdev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = DRIVER_NAME,
+ .fops = &esw_memcp_fops,
+};
+
+static int __init esw_memcp_init(void)
+{
+ int ret = 0;
+
+ ret = misc_register(&esw_memcp_miscdev);
+ if (ret) {
+ pr_err("%s: Failed to register misc device, err=%d\n", __func__, ret);
+ return ret;
+ }
+
+ esw_memcp_dev = esw_memcp_miscdev.this_device;
+
+ if (!esw_memcp_dev->dma_mask) {
+ esw_memcp_dev->dma_mask = &esw_memcp_dev->coherent_dma_mask;
+ }
+ ret = dma_set_mask_and_coherent(esw_memcp_dev, DMA_BIT_MASK(64));
+ if (ret)
+ dev_err(esw_memcp_dev, "Failed to set coherent mask.\n");
+ return ret;
+}
+
+static void __exit esw_memcp_exit(void)
+{
+ misc_deregister(&esw_memcp_miscdev);
+}
+module_init(esw_memcp_init);
+module_exit(esw_memcp_exit);
+
+MODULE_AUTHOR("Geng Zonglin <gengzonglin@eswincomputing.com>");
+MODULE_AUTHOR("Yuyang Cong <congyuyang@eswincomputing.com>");
+MODULE_DESCRIPTION("ESW DMA MEMCP Driver");
+MODULE_LICENSE("GPL v2");
\ No newline at end of file
diff --git a/include/uapi/linux/dma_memcp.h b/include/uapi/linux/dma_memcp.h
new file mode 100644
index 000000000000..3cb75a348ba8
--- /dev/null
+++ b/include/uapi/linux/dma_memcp.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * ESWIN DMA MEMCP Driver
+ *
+ * Copyright 2024, Beijing ESWIN Computing Technology Co., Ltd.. All rights reserved.
+ * SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * Authors: Zonglin Geng <gengzonglin@eswincomputing.com>
+ * Yuyang Cong <congyuyang@eswincomputing.com>
+ */
+
+#ifndef ES_DMA_MEMCP_H
+#define ES_DMA_MEMCP_H
+
+/**
+ * struct esw_memcp_f2f_cmd - Represents a memory copy command from source to destination.
+ *
+ * @src_fd: File descriptor of the source buffer.
+ * @src_offset: Offset in the source buffer from which data copy starts.
+ * @dst_fd: File descriptor of the destination buffer.
+ * @dst_offset: Offset in the destination buffer where data will be copied to.
+ * @len: Length of the data to be copied, in bytes.
+ * @timeout: Timeout for the memory copy operation, in milliseconds.
+ */
+struct esw_memcp_f2f_cmd {
+ int src_fd;
+ int src_offset;
+ int dst_fd;
+ int dst_offset;
+ size_t len;
+ int timeout;
+};
+
+/**
+ * struct esw_cmdq_query - Represents the command queue status structure.
+ *
+ * @status: Status of the queue, 0 indicates idle, 1 indicates busy.
+ * @task_count: Current number of tasks in the queue.
+ */
+struct esw_cmdq_query {
+ int status;
+ int task_count;
+};
+
+#define ESW_MEMCP_MAGIC 'M'
+#define ESW_CMDQ_ADD_TASK _IOW(ESW_MEMCP_MAGIC, 1, struct esw_memcp_f2f_cmd)
+#define ESW_CMDQ_SYNC _IO(ESW_MEMCP_MAGIC, 2)
+#define ESW_CMDQ_QUERY _IOR(ESW_MEMCP_MAGIC, 3, struct esw_cmdq_query)
+
+#endif
--
2.47.0