From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wr1-f65.google.com (mail-wr1-f65.google.com [209.85.221.65]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8D15617A2FA for ; Sat, 21 Feb 2026 13:22:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.65 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771680174; cv=none; b=UPqFXtxZhvmm1Ach9qneK5TQlYT4IHeuKYDZ+QST/OKm7d+m6k3CWXv64WeMMVp6/ujBFJ/6vk/SXpOG4TYGiYV7BxDskiReX+Rz5v6IZCyeF9qluNirdhmCmo+B/iDB9BuuggtKl/j8RfujA8U/IFIKdxlnfDWkzIs4akJooog= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1771680174; c=relaxed/simple; bh=4WaT+SLTA5E2qENtPUwbbJ6yGcs0q8qkWKWbbfS7YHo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BW0uHyhevpb6Nkk3lHAGTTswOgdT1B8KEEiPWGK8y39mjuTNriyFPe0lA/Ew/Wr84/Ur30voWlRRqcQg/lTEOqwQzsiNgXBC0N8y8cSFKcUVT1bWVd5y3cJCkRl8x+jeIqnB7YNbvVZlNlMdgwIGBxtkGQeVmbQesSpFLl4frfg= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=eZkVA2x+; arc=none smtp.client-ip=209.85.221.65 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="eZkVA2x+" Received: by mail-wr1-f65.google.com with SMTP id ffacd0b85a97d-4359249bbacso2340045f8f.0 for ; Sat, 21 Feb 2026 05:22:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1771680171; x=1772284971; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=mW2mqb4iYYLGxYXz3pBe0gQX7nYgSCJZQBErENVSksc=; b=eZkVA2x+u/39+yidrbcztofQubsI561elfwtzMCEUDNAbUykmRYz0vegb6oQ+o9phD +R3ydDf+MIGnhq+zw731E+xO9bZf6RawkSq/YIsHeEx7n+qRpukIvc6TNlGte5sfzL3I IYGRkz4904Ob32WJ4pwD+HfxH0j3+z6ump3E8iB+o9tL5BEDl8PS8aVvzyhQ+aa/cA/h jHRyrWrQetpt/26+rN7tYvRFAo/UyKqoYEWZjyM/RzTpAb59I5c6n3nlo9Ey/gqraMJv 4xqWEiSzsB5G5fcprRjYGqUaakS7IB2O8mAuIyH1Rlqf2qhA1mfinrveJWah9OAh2Olr zRtg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1771680171; x=1772284971; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=mW2mqb4iYYLGxYXz3pBe0gQX7nYgSCJZQBErENVSksc=; b=hWE9bHVFuAVaQjphKxt7WanNNVSGu4wRinBW09X/ROPf6Dd0GKQbZbPIBhGhM+WsI2 IRFJrFohk/a90ky0fgSoDDfI8BvUlexgJJvOdV0lP+XupHJptDQtzorz6e23OVlSOeHh YUaPsJqsB3xlsUihHwHMSDm698ucvCRyjglQFGlFPSU0aMNOoQ9CToLco7969KD0VuEb 42kU2JlVrfQxcsYNOjfqJl6HPJAnu0DrixP4sSN5m9CZEkm4/zmG0E7PWPnCwLJjBWlM b9mEKPw5x4LwxTZZVbi3Upm8PiIb+dWH0w0KhNPwRanF+e+6kyS2xLCa0x27D3fI+wdi nTDA== X-Forwarded-Encrypted: i=1; AJvYcCWWyNatKWynXAKxwYhufQeCwgMUt54utQchGanVZPG0aKLtxu6fgNe/n4vpkbw+ILr7YrZcEUh9fmEzQh4=@vger.kernel.org X-Gm-Message-State: AOJu0YzxHARMIKJWE87ocqRcHXibCjmCrlDkkVphkjdycKSqIdiCA7ih Hkq9NlIWJmy9CcWKUUdock66/+Q11uj8Xip4lxq3npT7ZFtH2aqZ70D1 X-Gm-Gg: AZuq6aJpo2iEuTB2kwt9kXf/GkpxY3GFcIDHSlXwMU6NuT5jvtWBtRECpZyuRF8Rx29 Skkaou5uZlrk14+djXxvPodfCNQJZqd7Fg4u07N5xmq55zCeQfgkB4XwLPlmgvaFuiaNLkT6xge yGUzczW3JmY8IXr5bOlV7zO4c2kEBVOoqmrPpnUQTmqVh52O/3cFOpbeABaEMtz5MIl7SKZboty xbZZ6kL8Ln2nMcxfNS0ZPE+0bhn+08QZMBsTUNVrOF3rWQ8R5z2PDz7DlV8ZOkE9K44QeZ0n3QI H8oBK5F8CIjk7n6pmiDJ3ftrynJauUxYz1PxH0owWOs/GEe75eClb4cVekakWua/nYWeQ+u7jPb nYKdM5XyXS6XHNMgm5gLBkzit06V6TWMszacacQmQABITwzkoCoT6bLdUei4cK6r/TG/nrna89I Yya/fdDMkgAvqXb8Mv4e4qtBJ0cPY= X-Received: by 2002:a05:6000:26ce:b0:437:7719:ca82 with SMTP id ffacd0b85a97d-4396fd9bda4mr5318534f8f.3.1771680170611; Sat, 21 Feb 2026 05:22:50 -0800 (PST) Received: from ideapad ([2a02:8070:a483:bca0:8c22:565c:f0a8:cd41]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-43970bfa1bdsm4645071f8f.3.2026.02.21.05.22.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 21 Feb 2026 05:22:50 -0800 (PST) From: Alexander Gordeev To: Vinod Koul Cc: dmaengine@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [RFC PATCH 1/2] dmaengine/dma-slave: DMA slave device xfer passthrough driver Date: Sat, 21 Feb 2026 14:22:47 +0100 Message-ID: <20260221132248.17721-2-a.gordeev.box@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260221132248.17721-1-a.gordeev.box@gmail.com> References: <20260221132248.17721-1-a.gordeev.box@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit This is a driver to help bringing up or debug DMA slave devices. A target device should expose itself as DMA_SLAVE to be able get found by dma_request_channel() service. It is located by the name as exposed in /sys/class/dma. The ioctl() caller is expected to map a file in user space and provide the mapping address along with DMA transfer parameters. The driver sets up and triggers a single the DMA transfer using zero-copy approach. The DMA transfer parameters are not sanitized in any way and the ioctl() caller is expected to be the device-aware. In other words, considering the DMA transfer parameters and the data itself this introduces a DMA passthrough driver. Signed-off-by: Alexander Gordeev --- drivers/dma/Kconfig | 7 + drivers/dma/Makefile | 1 + drivers/dma/dma-slave.c | 246 +++++++++++++++++++++++++++++++++ include/uapi/linux/dma-slave.h | 30 ++++ 4 files changed, 284 insertions(+) create mode 100644 drivers/dma/dma-slave.c create mode 100644 include/uapi/linux/dma-slave.h diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 4d0946f92edf..03808df52f4b 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig @@ -801,6 +801,13 @@ config DMATEST Simple DMA test client. Say N unless you're debugging a DMA Device driver. +config DMA_SLAVE + tristate "DMA Slave Test client" + depends on DMA_ENGINE + help + Simple DMA Passthrough Slave test client. Say N unless you're + debugging a DMA Device driver. + config DMA_ENGINE_RAID bool diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index a6535b2310bb..f52a2d525e8b 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_DMA_OF) += of-dma.o #dmatest obj-$(CONFIG_DMATEST) += dmatest.o +obj-$(CONFIG_DMA_SLAVE) += dma-slave.o #devices obj-$(CONFIG_ALTERA_MSGDMA) += altera-msgdma.o diff --git a/drivers/dma/dma-slave.c b/drivers/dma/dma-slave.c new file mode 100644 index 000000000000..c8671ade2ef6 --- /dev/null +++ b/drivers/dma/dma-slave.c @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2026 Alexander Gordeev +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool filter(struct dma_chan *chan, void *filter_param) +{ + const char *name = dma_chan_name(chan); + + if (!name) + return false; + return !strcmp(name, (char *)filter_param); +} + +static struct dma_chan *dma_slave_request_chan(char *channel_name) +{ + dma_filter_fn filter_fn = NULL; + struct dma_chan *chan; + dma_cap_mask_t mask; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + if (channel_name[0]) + filter_fn = filter; + chan = dma_request_channel(mask, filter_fn, channel_name); + if (!chan) + return ERR_PTR(-ENODEV); + + return chan; +} + +static void dma_slave_release_chan(struct dma_chan *chan) +{ + dma_release_channel(chan); +} + +static int dma_slave_setup_config(unsigned int cmd, + struct dma_slave_config *cfg, + struct dma_slave_config_uapi *ucfg, + enum dma_data_direction *dir) +{ + if (!IS_ALIGNED((unsigned long)ucfg->data.iov_base, PAGE_SIZE)) + return -EINVAL; + if (!ucfg->data.iov_len) + return -EINVAL; + if (strnlen(ucfg->channel_name, sizeof(ucfg->channel_name)) >= sizeof(ucfg->channel_name)) + return -EINVAL; + + if (ucfg->peripheral_config.iov_len) { + cfg->peripheral_config = kmalloc(ucfg->peripheral_config.iov_len, GFP_KERNEL); + if (!cfg->peripheral_config) + return -ENOMEM; + if (copy_from_user(cfg->peripheral_config, + (void __user *)ucfg->peripheral_config.iov_base, + ucfg->peripheral_config.iov_len)) { + kfree(cfg->peripheral_config); + return -EFAULT; + } + cfg->peripheral_size = ucfg->peripheral_config.iov_len; + } + if (cmd == IOCTL_DMA_SLAVE_READ) { + cfg->direction = DMA_DEV_TO_MEM; + *dir = DMA_FROM_DEVICE; + } else { + cfg->direction = DMA_MEM_TO_DEV; + *dir = DMA_TO_DEVICE; + } + cfg->src_addr = ucfg->src_addr; + cfg->dst_addr = ucfg->dst_addr; + cfg->src_addr_width = ucfg->src_addr_width; + cfg->dst_addr_width = ucfg->dst_addr_width; + cfg->src_maxburst = ucfg->src_maxburst; + cfg->dst_maxburst = ucfg->dst_maxburst; + cfg->src_port_window_size = ucfg->src_port_window_size; + cfg->dst_port_window_size = ucfg->dst_port_window_size; + cfg->device_fc = ucfg->device_fc; + + return 0; +} + +static void dma_slave_teardown_config(struct dma_slave_config *cfg) +{ + kfree(cfg->peripheral_config); +} + +static void dma_slave_callback(void *callback_param) +{ + complete((struct completion *)callback_param); +} + +static long dma_slave_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct dma_async_tx_descriptor *desc; + struct dma_slave_config_uapi ucfg; + struct dma_slave_config cfg = {}; + enum dma_data_direction dir; + struct page **pages; + unsigned long nr_pages; + struct dma_chan *chan; + struct sg_table sgt; + unsigned int foll; + dma_cookie_t tx; + struct completion completion; + long ret; + int i; + + switch (cmd) { + case IOCTL_DMA_SLAVE_READ: + case IOCTL_DMA_SLAVE_WRITE: + if (copy_from_user(&ucfg, (void __user *)arg, sizeof(ucfg))) + return -EFAULT; + + ret = dma_slave_setup_config(cmd, &cfg, &ucfg, &dir); + if (ret) + return ret; + break; + default: + return -EINVAL; + }; + + chan = dma_slave_request_chan(ucfg.channel_name); + if (IS_ERR(chan)) { + ret = PTR_ERR(chan); + goto err_teardown_config; + } + + ret = dmaengine_slave_config(chan, &cfg); + if (ret) + goto err_release_chan; + + nr_pages = DIV_ROUND_UP(ucfg.data.iov_len, PAGE_SIZE); + pages = kmalloc_array(nr_pages, sizeof(pages[0]), GFP_KERNEL); + if (!pages) { + ret = -ENOMEM; + goto err_release_chan; + } + + foll = 0; + if (cmd == IOCTL_DMA_SLAVE_READ) + foll |= FOLL_WRITE; + mmap_read_lock(current->mm); + ret = pin_user_pages((unsigned long)ucfg.data.iov_base, nr_pages, foll, pages); + if (ret < 0) + goto err_mmap_unlock; + if (ret != nr_pages) { + nr_pages = ret; + ret = -EFAULT; + goto err_unpin_pages; + } + + ret = sg_alloc_table_from_pages(&sgt, pages, nr_pages, 0, ucfg.data.iov_len, GFP_KERNEL); + if (ret) + goto err_unpin_pages; + + ret = dma_map_sgtable(chan->device->dev, &sgt, dir, 0); + if (ret) + goto err_free_sgt; + + desc = dmaengine_prep_slave_sg(chan, sgt.sgl, sgt.nents, cfg.direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + ret = -ENOMEM; + goto err_unmap_sgt; + } + init_completion(&completion); + desc->callback = dma_slave_callback; + desc->callback_param = &completion; + + tx = dmaengine_submit(desc); + ret = dma_submit_error(tx); + if (ret < 0) + goto err_unmap_sgt; + + dma_async_issue_pending(chan); + + ret = wait_for_completion_interruptible(&completion); + if (ret) + goto err_term_sync; + + if (dma_async_is_tx_complete(chan, tx, NULL, NULL) != DMA_COMPLETE) { + ret = -EIO; + goto err_term_sync; + } + + if (cmd == IOCTL_DMA_SLAVE_READ) { + for (i = 0; i < nr_pages; i++) + set_page_dirty_lock(pages[i]); + } + + goto err_unmap_sgt; + +err_term_sync: + dmaengine_terminate_sync(chan); +err_unmap_sgt: + dma_unmap_sgtable(chan->device->dev, &sgt, dir, 0); +err_free_sgt: + sg_free_table(&sgt); +err_unpin_pages: + unpin_user_pages(pages, nr_pages); +err_mmap_unlock: + mmap_read_unlock(current->mm); + kfree(pages); +err_release_chan: + dma_slave_release_chan(chan); +err_teardown_config: + dma_slave_teardown_config(&cfg); + + return ret; +} + +static const struct file_operations dma_slave_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = dma_slave_ioctl, +}; + +struct miscdevice misc_dev = { + .minor = MISC_DYNAMIC_MINOR, + .name = DMA_SLAVE_DEVICE, + .nodename = DMA_SLAVE_DEVICE, + .fops = &dma_slave_fops, + .mode = 0600, +}; + +static int __init dma_slave_init(void) +{ + return misc_register(&misc_dev); +} +late_initcall(dma_slave_init); + +static void __exit dma_slave_exit(void) +{ + misc_deregister(&misc_dev); +} +module_exit(dma_slave_exit); + +MODULE_AUTHOR("Alexander Gordeev "); +MODULE_DESCRIPTION("DMA slave passthrough driver"); +MODULE_LICENSE("GPL"); diff --git a/include/uapi/linux/dma-slave.h b/include/uapi/linux/dma-slave.h new file mode 100644 index 000000000000..e3da14d4224e --- /dev/null +++ b/include/uapi/linux/dma-slave.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2026 Alexander Gordeev + */ +#ifndef _UAPI_LINUX_DMA_SLAVE_H__ +#define _UAPI_LINUX_DMA_SLAVE_H__ + +#define DMA_SLAVE_DEVICE "dma-slave" + +struct dma_slave_config_uapi { + struct iovec data; + struct iovec peripheral_config; + __u64 src_addr; + __u64 dst_addr; + __u32 src_addr_width; + __u32 dst_addr_width; + __u32 src_maxburst; + __u32 dst_maxburst; + __u32 src_port_window_size; + __u32 dst_port_window_size; + bool device_fc; + char channel_name[32]; +}; + +#define DMA_SLAVE_SIG 'S' + +#define IOCTL_DMA_SLAVE_READ _IOR(DMA_SLAVE_SIG, 0, struct dma_slave_config_uapi) +#define IOCTL_DMA_SLAVE_WRITE _IOW(DMA_SLAVE_SIG, 1, struct dma_slave_config_uapi) + +#endif -- 2.51.0