public inbox for dmaengine@vger.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/2] dma: DMA slave device bringup tool
@ 2026-02-21 13:22 Alexander Gordeev
  2026-02-21 13:22 ` [RFC PATCH 1/2] dmaengine/dma-slave: DMA slave device xfer passthrough driver Alexander Gordeev
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Alexander Gordeev @ 2026-02-21 13:22 UTC (permalink / raw)
  To: Vinod Koul; +Cc: dmaengine, linux-kernel

Hi All,

This is a custom tool that can be used to bring up DMA slave devices.
It consists of a user-level utility and a companion device driver that
communicate via IOCTL.

The tool is likely need some polishing, but I would like first get some
feedback to ensure there is interest.

I also tested it only on x86 and have little idea on how channel names
on other architectures look like. That could especially impact the way
dma_request_channel() treats user-provided target DMA channel names, as
exposed via /sys/class/dma.

Thanks!

Alexander Gordeev (2):
  dmaengine/dma-slave: DMA slave device xfer passthrough driver
  tools/dma-slave: DMA slave device transfer utility

 drivers/dma/Kconfig            |   7 +
 drivers/dma/Makefile           |   1 +
 drivers/dma/dma-slave.c        | 246 +++++++++++++++++++++++++
 include/uapi/linux/dma-slave.h |  30 +++
 tools/Makefile                 |  11 +-
 tools/dma/Makefile             |  20 ++
 tools/dma/dma-slave.c          | 321 +++++++++++++++++++++++++++++++++
 7 files changed, 631 insertions(+), 5 deletions(-)
 create mode 100644 drivers/dma/dma-slave.c
 create mode 100644 include/uapi/linux/dma-slave.h
 create mode 100644 tools/dma/Makefile
 create mode 100644 tools/dma/dma-slave.c

-- 
2.51.0


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [RFC PATCH 1/2] dmaengine/dma-slave: DMA slave device xfer passthrough driver
  2026-02-21 13:22 [RFC PATCH 0/2] dma: DMA slave device bringup tool Alexander Gordeev
@ 2026-02-21 13:22 ` Alexander Gordeev
  2026-02-21 13:22 ` [RFC PATCH 2/2] tools/dma-slave: DMA slave device transfer utility Alexander Gordeev
  2026-02-24 22:34 ` [RFC PATCH 0/2] dma: DMA slave device bringup tool Frank Li
  2 siblings, 0 replies; 8+ messages in thread
From: Alexander Gordeev @ 2026-02-21 13:22 UTC (permalink / raw)
  To: Vinod Koul; +Cc: dmaengine, linux-kernel

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 <a.gordeev.box@gmail.com>
---
 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 <a.gordeev.box@gmail.com>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/miscdevice.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-direction.h>
+#include <linux/dma-mapping.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/dma-slave.h>
+
+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 <a.gordeev.box@gmail.com>");
+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 <a.gordeev.box@gmail.com>
+ */
+#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


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [RFC PATCH 2/2] tools/dma-slave: DMA slave device transfer utility
  2026-02-21 13:22 [RFC PATCH 0/2] dma: DMA slave device bringup tool Alexander Gordeev
  2026-02-21 13:22 ` [RFC PATCH 1/2] dmaengine/dma-slave: DMA slave device xfer passthrough driver Alexander Gordeev
@ 2026-02-21 13:22 ` Alexander Gordeev
  2026-02-24 22:34 ` [RFC PATCH 0/2] dma: DMA slave device bringup tool Frank Li
  2 siblings, 0 replies; 8+ messages in thread
From: Alexander Gordeev @ 2026-02-21 13:22 UTC (permalink / raw)
  To: Vinod Koul; +Cc: dmaengine, linux-kernel

A utility to pass user data through a DMA slave device. It is
intended for bringing up and debugging DMA devices.

The tool writes the contents of a binary file to, or reads from,
the companion dma-slave device driver, which must be loaded.

The contents of an input file to be transferred can be prepared
to trigger specific target device behavior on writes.

For read operations, the output file contents can be examined using
user-level tools to detect transfer integrity issues.

The DMA transfer parameters are provided via command-line arguments
and are not sanitized in any way. These parameters are used as-is to
initialize a dma_slave_config instance passed to dmaengine_slave_config().

An additional file may be provided as peripheral configuration data for
the DMA transfer. In this case, dma_slave_config::peripheral_config and
dma_slave_config::peripheral_size members are populated from the file
contents and its size.

Signed-off-by: Alexander Gordeev <a.gordeev.box@gmail.com>
---
 tools/Makefile        |  11 +-
 tools/dma/Makefile    |  20 +++
 tools/dma/dma-slave.c | 321 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 347 insertions(+), 5 deletions(-)
 create mode 100644 tools/dma/Makefile
 create mode 100644 tools/dma/dma-slave.c

diff --git a/tools/Makefile b/tools/Makefile
index c31cbbd12c45..2c52bf7bc899 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -14,6 +14,7 @@ help:
 	@echo '  counter                - counter tools'
 	@echo '  cpupower               - a tool for all things x86 CPU power'
 	@echo '  debugging              - tools for debugging'
+	@echo '  dma                    - DMA tools'
 	@echo '  firewire               - the userspace part of nosy, an IEEE-1394 traffic sniffer'
 	@echo '  firmware               - Firmware tools'
 	@echo '  freefall               - laptop accelerometer program for disk protection'
@@ -69,7 +70,7 @@ acpi: FORCE
 cpupower: FORCE
 	$(call descend,power/$@)
 
-counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi firmware debugging tracing: FORCE
+counter firewire hv guest bootconfig spi usb virtio mm bpf iio gpio objtool leds wmi firmware debugging tracing dma: FORCE
 	$(call descend,$@)
 
 bpf/%: FORCE
@@ -122,7 +123,7 @@ kvm_stat: FORCE
 ynl: FORCE
 	$(call descend,net/ynl)
 
-all: acpi counter cpupower gpio hv firewire \
+all: acpi counter cpupower dma gpio hv firewire \
 		perf selftests bootconfig spi turbostat usb \
 		virtio mm bpf x86_energy_perf_policy \
 		tmon freefall iio objtool kvm_stat wmi \
@@ -134,7 +135,7 @@ acpi_install:
 cpupower_install:
 	$(call descend,power/$(@:_install=),install)
 
-counter_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install debugging_install tracing_install:
+counter_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install mm_install bpf_install objtool_install wmi_install debugging_install tracing_install dma_install:
 	$(call descend,$(@:_install=),install)
 
 selftests_install:
@@ -165,7 +166,7 @@ ynl_install:
 	$(call descend,net/$(@:_install=),install)
 
 install: acpi_install counter_install cpupower_install gpio_install \
-		hv_install firewire_install iio_install \
+		hv_install firewire_install iio_install dma_install \
 		perf_install selftests_install turbostat_install usb_install \
 		virtio_install mm_install bpf_install x86_energy_perf_policy_install \
 		tmon_install freefall_install objtool_install kvm_stat_install \
@@ -178,7 +179,7 @@ acpi_clean:
 cpupower_clean:
 	$(call descend,power/cpupower,clean)
 
-counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean firmware_clean debugging_clean tracing_clean:
+counter_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean mm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean firmware_clean debugging_clean tracing_clean dma_clean:
 	$(call descend,$(@:_clean=),clean)
 
 libapi_clean:
diff --git a/tools/dma/Makefile b/tools/dma/Makefile
new file mode 100644
index 000000000000..c92a260ccf36
--- /dev/null
+++ b/tools/dma/Makefile
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0
+PREFIX ?= /usr
+SBINDIR ?= sbin
+INSTALL ?= install
+CFLAGS += -Wall -Wextra -I../../include/uapi -D__EXPORTED_HEADERS__
+
+TARGET = dma-slave
+
+all: $(TARGET)
+
+%: %.c
+	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<
+
+clean:
+	$(RM) $(TARGET)
+
+install: $(TARGET)
+	$(INSTALL) -D -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/$(SBINDIR)/$(TARGET)
+
+.PHONY: all clean install
diff --git a/tools/dma/dma-slave.c b/tools/dma/dma-slave.c
new file mode 100644
index 000000000000..e1256779ccd3
--- /dev/null
+++ b/tools/dma/dma-slave.c
@@ -0,0 +1,321 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2026 Alexander Gordeev <a.gordeev.box@gmail.com>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/mman.h>
+#include <linux/types.h>
+#include <linux/dma-slave.h>
+
+static void dump_mem(const void *mem, size_t mem_len)
+{
+	unsigned int i;
+
+	if (mem_len > 16)
+		mem_len = 16;
+	printf("[ ");
+	for (i = 0; i < mem_len; i++)
+		printf("%02X ", ((const unsigned char *)mem)[i]);
+	printf("]\n");
+}
+
+static bool check_args(unsigned int cmd, const char *file, struct dma_slave_config_uapi *ucfg)
+{
+	if (cmd == IOCTL_DMA_SLAVE_READ) {
+		if (!ucfg->data.iov_len)
+			return false;
+		if (!ucfg->src_addr)
+			return false;
+	} else if (cmd == IOCTL_DMA_SLAVE_WRITE) {
+		if (!ucfg->dst_addr)
+			return false;
+	} else {
+		return false;
+	}
+	if (!file)
+		return false;
+	return true;
+}
+
+static int read_peripheral_config(const char *conf, struct iovec *peripheral_config)
+{
+	struct stat stat;
+	void *buf;
+	int fd;
+	int ret;
+
+	if (!conf)
+		return 0;
+
+	fd = open(conf, O_RDONLY);
+	if (fd < 0) {
+		ret = errno;
+		goto err_ret;
+	}
+	if (fstat(fd, &stat) < 0) {
+		ret = errno;
+		goto err_close_fd;
+	}
+	buf = malloc(stat.st_size);
+	if (!buf) {
+		ret = errno;
+		goto err_close_fd;
+	}
+	if (read(fd, buf, stat.st_size) < 0) {
+		ret = errno;
+		goto err_free;
+	}
+
+	peripheral_config->iov_base = buf;
+	peripheral_config->iov_len = stat.st_size;
+	ret = 0;
+
+err_free:
+	free(buf);
+err_close_fd:
+	close(fd);
+err_ret:
+	return ret;
+}
+
+static void print_usage(void)
+{
+	printf("A utility to transfer the contents of a file to a DMA slave device.\n");
+	printf("Requires the companion 'dma-slave' device driver to be loaded.\n\n");
+
+	printf("Usage:\n");
+	printf("  dma-slave [OPTIONS]\n\n");
+
+	printf("Transfer direction (required, select one):\n");
+	printf("  -r, --read                     Perform DMA read (device to file)\n");
+	printf("  -w, --write                    Perform DMA write (file to device)\n\n");
+
+	printf("Transfer address (required):\n");
+	printf("  -S, --src-addr <addr>          Source address (hex,dec,oct)\n");
+	printf("  -D, --dst-addr <addr>          Destination address (hex,dec,oct)\n\n");
+
+	printf("Transfer size (required on read, optional on write):\n");
+	printf("  -s, --size <bytes>             Transfer size in bytes\n\n");
+
+	printf("Transfer parameters (optional):\n");
+	printf("      --src-addr-width <n>       Source address width\n");
+	printf("      --dst-addr-width <n>       Destination address width\n");
+	printf("      --src-maxburst <n>         Source max burst size\n");
+	printf("      --dst-maxburst <n>         Destination max burst size\n");
+	printf("      --src-port-window-size <n> Source port window size\n");
+	printf("      --dst-port-window-size <n> Destination port window size\n");
+	printf("      --device-fc                Enable device flow control\n");
+	printf("  -p, --peripheral-config <file> Peripheral configuration file (raw)\n\n");
+
+	printf("User stored data (recreated on read, must exist on write):\n");
+	printf("  -f, --file <file>              Transfer contents (raw)\n\n");
+
+	printf("Target DMA channel (optional, auto-selected if not provided):\n");
+	printf("  -c, --channel-name <name>      DMA channel name (in /sys/class/dma)\n");
+
+	printf("Advanced parameters (optional):\n");
+	printf("  -d, --dump                     Dump transferred data (16 bytes at most)\n\n");
+
+	printf("Examples:\n");
+	printf("  dma-slave --read  -c dma0chan0 -S 0x20000000 --file output.bin -s 4096\n");
+	printf("  dma-slave --write -c dma0chan0 -D 0x10000000 --file input.bin\n");
+}
+
+enum {
+	OPT_SRC_ADDR_WIDTH,
+	OPT_DST_ADDR_WIDTH,
+	OPT_SRC_MAXBURST,
+	OPT_DST_MAXBURST,
+	OPT_SRC_PORT_WINDOW_SIZE,
+	OPT_DST_PORT_WINDOW_SIZE,
+	OPT_DEVICE_FC,
+};
+
+static const struct option long_opts[] = {
+	{ "help",			no_argument,		NULL, 'h' },
+	{ "read",			no_argument,		NULL, 'r' },
+	{ "write",			no_argument,		NULL, 'w' },
+	{ "dump",			no_argument,		NULL, 'd' },
+	{ "channel-name",		required_argument,	NULL, 'c' },
+	{ "peripheral-config",		required_argument,	NULL, 'p' },
+	{ "file",			required_argument,	NULL, 'f' },
+	{ "size",			required_argument,	NULL, 's' },
+	{ "src-addr",			required_argument,	NULL, 'S' },
+	{ "dst-addr",			required_argument,	NULL, 'D' },
+	{ "src-addr-width",		required_argument,	NULL, OPT_SRC_ADDR_WIDTH },
+	{ "dst-addr-width",		required_argument,	NULL, OPT_DST_ADDR_WIDTH },
+	{ "src-maxburst",		required_argument,	NULL, OPT_SRC_MAXBURST },
+	{ "dst-maxburst",		required_argument,	NULL, OPT_DST_MAXBURST },
+	{ "src-port-window-size",	required_argument,	NULL, OPT_SRC_PORT_WINDOW_SIZE },
+	{ "dst-port-window-size",	required_argument,	NULL, OPT_DST_PORT_WINDOW_SIZE },
+	{ "device-fc",			no_argument,		NULL, OPT_DEVICE_FC },
+	{ NULL,				0,			NULL, 0  }
+};
+
+int main(int argc, char **argv)
+{
+	char *file = NULL, *conf = NULL, *endptr;
+	struct dma_slave_config_uapi ucfg = {};
+	int fd, fd_dev;
+	unsigned int cmd = 0;
+	bool dump = false;
+	struct stat stat;
+	char opt;
+	int ret;
+
+	if (argc == 1) {
+print_usage:
+		print_usage();
+		return 0;
+	}
+
+	while ((opt = getopt_long(argc, argv, "hrwdc:s:p:f:S:D:", long_opts, NULL)) != -1) {
+		switch (opt) {
+		case 'h':
+			goto print_usage;
+		case 'r':
+			if (cmd == IOCTL_DMA_SLAVE_WRITE)
+				goto err_args;
+			cmd = IOCTL_DMA_SLAVE_READ;
+			break;
+		case 'w':
+			if (cmd == IOCTL_DMA_SLAVE_READ)
+				goto err_args;
+			cmd = IOCTL_DMA_SLAVE_WRITE;
+			break;
+		case 'd':
+			dump = true;
+			break;
+		case 'c':
+			strncpy(ucfg.channel_name, optarg, sizeof(ucfg.channel_name) - 1);
+			break;
+		case 'p':
+			conf = optarg;
+			break;
+		case 'f':
+			file = optarg;
+			break;
+		case 's':
+			ucfg.data.iov_len = strtoull(optarg, &endptr, 0);
+			if (endptr[0])
+				goto err_args;
+			break;
+		case 'S':
+			ucfg.src_addr = strtoull(optarg, &endptr, 0);
+			if (endptr[0])
+				goto err_args;
+			break;
+		case 'D':
+			ucfg.dst_addr = strtoull(optarg, &endptr, 0);
+			if (endptr[0])
+				goto err_args;
+			break;
+		case OPT_SRC_ADDR_WIDTH:
+			ucfg.src_addr_width = strtoul(optarg, &endptr, 10);
+			if (endptr[0])
+				goto err_args;
+			break;
+		case OPT_DST_ADDR_WIDTH:
+			ucfg.dst_addr_width = strtoul(optarg, &endptr, 10);
+			if (endptr[0])
+				goto err_args;
+			break;
+		case OPT_SRC_MAXBURST:
+			ucfg.src_maxburst = strtoul(optarg, &endptr, 10);
+			if (endptr[0])
+				goto err_args;
+			break;
+		case OPT_DST_MAXBURST:
+			ucfg.dst_maxburst = strtoul(optarg, &endptr, 10);
+			if (endptr[0])
+				goto err_args;
+			break;
+		case OPT_SRC_PORT_WINDOW_SIZE:
+			ucfg.src_port_window_size = strtoul(optarg, &endptr, 10);
+			if (endptr[0])
+				goto err_args;
+			break;
+		case OPT_DST_PORT_WINDOW_SIZE:
+			ucfg.dst_port_window_size = strtoul(optarg, &endptr, 10);
+			if (endptr[0])
+				goto err_args;
+			break;
+		case OPT_DEVICE_FC:
+			ucfg.device_fc = true;
+			break;
+		default:
+			goto err_args;
+		}
+	}
+
+	ret = read_peripheral_config(conf, &ucfg.peripheral_config);
+	if (ret)
+		return ret;
+
+	if (!check_args(cmd, file, &ucfg)) {
+err_args:
+		print_usage();
+		return EINVAL;
+	}
+
+	fd_dev = open("/dev/" DMA_SLAVE_DEVICE, O_RDWR);
+	if (fd_dev < 0)
+		return errno;
+
+	switch (cmd) {
+	case IOCTL_DMA_SLAVE_READ:
+		fd = open(file, O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+		if (fd < 0)
+			return errno;
+		if (ftruncate(fd, ucfg.data.iov_len) < 0)
+			return errno;
+		ucfg.data.iov_base = mmap(NULL, ucfg.data.iov_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+		if (ucfg.data.iov_base == MAP_FAILED) {
+			ret = errno;
+			unlink(file);
+			return ret;
+		}
+		close(fd);
+		break;
+
+	case IOCTL_DMA_SLAVE_WRITE:
+		fd = open(file, O_RDONLY);
+		if (fd < 0)
+			return errno;
+		if (fstat(fd, &stat) < 0)
+			return errno;
+		if (!stat.st_size)
+			return EINVAL;
+		if (!ucfg.data.iov_len || (size_t)stat.st_size < ucfg.data.iov_len)
+			ucfg.data.iov_len = stat.st_size;
+		ucfg.data.iov_base = mmap(NULL, ucfg.data.iov_len, PROT_READ, MAP_SHARED, fd, 0);
+		if (ucfg.data.iov_base == MAP_FAILED)
+			return errno;
+		close(fd);
+		if (dump)
+			dump_mem(ucfg.data.iov_base, ucfg.data.iov_len);
+		break;
+	}
+
+	if (ioctl(fd_dev, cmd, &ucfg) < 0) {
+		ret = errno;
+		if (cmd == IOCTL_DMA_SLAVE_READ)
+			unlink(file);
+		return ret;
+	}
+
+	if (cmd == IOCTL_DMA_SLAVE_READ && dump)
+		dump_mem(ucfg.data.iov_base, ucfg.data.iov_len);
+
+	munmap(ucfg.data.iov_base, ucfg.data.iov_len);
+	close(fd_dev);
+
+	return 0;
+}
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 0/2] dma: DMA slave device bringup tool
  2026-02-21 13:22 [RFC PATCH 0/2] dma: DMA slave device bringup tool Alexander Gordeev
  2026-02-21 13:22 ` [RFC PATCH 1/2] dmaengine/dma-slave: DMA slave device xfer passthrough driver Alexander Gordeev
  2026-02-21 13:22 ` [RFC PATCH 2/2] tools/dma-slave: DMA slave device transfer utility Alexander Gordeev
@ 2026-02-24 22:34 ` Frank Li
  2026-02-25  9:37   ` Vinod Koul
  2 siblings, 1 reply; 8+ messages in thread
From: Frank Li @ 2026-02-24 22:34 UTC (permalink / raw)
  To: Alexander Gordeev; +Cc: Vinod Koul, dmaengine, linux-kernel

On Sat, Feb 21, 2026 at 02:22:46PM +0100, Alexander Gordeev wrote:
> Hi All,
>
> This is a custom tool that can be used to bring up DMA slave devices.
> It consists of a user-level utility and a companion device driver that
> communicate via IOCTL.
>
> The tool is likely need some polishing, but I would like first get some
> feedback to ensure there is interest.
>
> I also tested it only on x86 and have little idea on how channel names
> on other architectures look like. That could especially impact the way
> dma_request_channel() treats user-provided target DMA channel names, as
> exposed via /sys/class/dma.

I am not sure if it can work for general dma engine because it slave setting
is tight coupling with FIFO settings and timing, some periphal require
start dma firstly, then enable DMA. some perphial require enable DMA first
then queue dma transfer.

burst len is also related with FIFO 's watermark settings.

Frank

>
> Thanks!
>
> Alexander Gordeev (2):
>   dmaengine/dma-slave: DMA slave device xfer passthrough driver
>   tools/dma-slave: DMA slave device transfer utility
>
>  drivers/dma/Kconfig            |   7 +
>  drivers/dma/Makefile           |   1 +
>  drivers/dma/dma-slave.c        | 246 +++++++++++++++++++++++++
>  include/uapi/linux/dma-slave.h |  30 +++
>  tools/Makefile                 |  11 +-
>  tools/dma/Makefile             |  20 ++
>  tools/dma/dma-slave.c          | 321 +++++++++++++++++++++++++++++++++
>  7 files changed, 631 insertions(+), 5 deletions(-)
>  create mode 100644 drivers/dma/dma-slave.c
>  create mode 100644 include/uapi/linux/dma-slave.h
>  create mode 100644 tools/dma/Makefile
>  create mode 100644 tools/dma/dma-slave.c
>
> --
> 2.51.0
>

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 0/2] dma: DMA slave device bringup tool
  2026-02-24 22:34 ` [RFC PATCH 0/2] dma: DMA slave device bringup tool Frank Li
@ 2026-02-25  9:37   ` Vinod Koul
  2026-02-25 21:10     ` Alexander Gordeev
  0 siblings, 1 reply; 8+ messages in thread
From: Vinod Koul @ 2026-02-25  9:37 UTC (permalink / raw)
  To: Frank Li; +Cc: Alexander Gordeev, dmaengine, linux-kernel

On 24-02-26, 17:34, Frank Li wrote:
> On Sat, Feb 21, 2026 at 02:22:46PM +0100, Alexander Gordeev wrote:
> > Hi All,
> >
> > This is a custom tool that can be used to bring up DMA slave devices.
> > It consists of a user-level utility and a companion device driver that
> > communicate via IOCTL.
> >
> > The tool is likely need some polishing, but I would like first get some
> > feedback to ensure there is interest.
> >
> > I also tested it only on x86 and have little idea on how channel names
> > on other architectures look like. That could especially impact the way
> > dma_request_channel() treats user-provided target DMA channel names, as
> > exposed via /sys/class/dma.
> 
> I am not sure if it can work for general dma engine because it slave setting
> is tight coupling with FIFO settings and timing, some periphal require
> start dma firstly, then enable DMA. some perphial require enable DMA first
> then queue dma transfer.
> 
> burst len is also related with FIFO 's watermark settings.

Correct!

I like the idea but it is not practical. Every dmaengine is tied to the
peripheral for setting up the transfer. It is not a memcpy! How did you
test it, which controller was used ..?

-- 
~Vinod

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 0/2] dma: DMA slave device bringup tool
  2026-02-25  9:37   ` Vinod Koul
@ 2026-02-25 21:10     ` Alexander Gordeev
  2026-02-26  7:07       ` Vinod Koul
  0 siblings, 1 reply; 8+ messages in thread
From: Alexander Gordeev @ 2026-02-25 21:10 UTC (permalink / raw)
  To: Vinod Koul; +Cc: Frank Li, dmaengine, linux-kernel

On Wed, Feb 25, 2026 at 03:07:06PM +0530, Vinod Koul wrote:

Hi Vinod, Frank,

> > I am not sure if it can work for general dma engine because it slave setting
> > is tight coupling with FIFO settings and timing, some periphal require
> > start dma firstly, then enable DMA. some perphial require enable DMA first
> > then queue dma transfer.
> > 
> > burst len is also related with FIFO 's watermark settings.
> 
> Correct!
> 
> I like the idea but it is not practical. Every dmaengine is tied to the
> peripheral for setting up the transfer. It is not a memcpy! How did you
> test it, which controller was used ..?

I likely missing something, but how this differs from dmatest, which also
lacks any controller-specific setup?

I tested it on Avalon-MM Interface on Arria 10 FPGA and found it super-
useful - thus an attempt to share.

> ~Vinod

Thanks!

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 0/2] dma: DMA slave device bringup tool
  2026-02-25 21:10     ` Alexander Gordeev
@ 2026-02-26  7:07       ` Vinod Koul
  2026-02-26 19:43         ` Alexander Gordeev
  0 siblings, 1 reply; 8+ messages in thread
From: Vinod Koul @ 2026-02-26  7:07 UTC (permalink / raw)
  To: Alexander Gordeev; +Cc: Frank Li, dmaengine, linux-kernel

On 25-02-26, 22:10, Alexander Gordeev wrote:
> On Wed, Feb 25, 2026 at 03:07:06PM +0530, Vinod Koul wrote:
> 
> Hi Vinod, Frank,
> 
> > > I am not sure if it can work for general dma engine because it slave setting
> > > is tight coupling with FIFO settings and timing, some periphal require
> > > start dma firstly, then enable DMA. some perphial require enable DMA first
> > > then queue dma transfer.
> > > 
> > > burst len is also related with FIFO 's watermark settings.
> > 
> > Correct!
> > 
> > I like the idea but it is not practical. Every dmaengine is tied to the
> > peripheral for setting up the transfer. It is not a memcpy! How did you
> > test it, which controller was used ..?
> 
> I likely missing something, but how this differs from dmatest, which also
> lacks any controller-specific setup?

slave dma needs a peripheral to test. For example a spi/i2c etc
dmaengine in slave mode will not work untill unless there is some
signalling for dmaengine from peripheral to push/pull data.

> I tested it on Avalon-MM Interface on Arria 10 FPGA and found it super-
> useful - thus an attempt to share.

Which driver is that? Seems more like a memcpy masked as slave to me

-- 
~Vinod

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [RFC PATCH 0/2] dma: DMA slave device bringup tool
  2026-02-26  7:07       ` Vinod Koul
@ 2026-02-26 19:43         ` Alexander Gordeev
  0 siblings, 0 replies; 8+ messages in thread
From: Alexander Gordeev @ 2026-02-26 19:43 UTC (permalink / raw)
  To: Vinod Koul; +Cc: Frank Li, dmaengine, linux-kernel

On Thu, Feb 26, 2026 at 12:37:47PM +0530, Vinod Koul wrote:
> > I likely missing something, but how this differs from dmatest, which also
> > lacks any controller-specific setup?
> 
> slave dma needs a peripheral to test. For example a spi/i2c etc
> dmaengine in slave mode will not work untill unless there is some
> signalling for dmaengine from peripheral to push/pull data.

Well, the idea is to trigger xfers using custom out-of-band tooling.

> > I tested it on Avalon-MM Interface on Arria 10 FPGA and found it super-
> > useful - thus an attempt to share.
> 
> Which driver is that? Seems more like a memcpy masked as slave to me

Yes, one could say so. It transfers off-CPU memory in one of FPGA
implementations, which does not need any xfer setup.

When attached to a camera however the xfer is triggered using a private
tool that uses i2c - exactly as you guys noticed.

The dmaengine driver for Avalon-MM Interface is not upstreamed yet.

> ~Vinod

Thanks!

^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-02-26 19:43 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-21 13:22 [RFC PATCH 0/2] dma: DMA slave device bringup tool Alexander Gordeev
2026-02-21 13:22 ` [RFC PATCH 1/2] dmaengine/dma-slave: DMA slave device xfer passthrough driver Alexander Gordeev
2026-02-21 13:22 ` [RFC PATCH 2/2] tools/dma-slave: DMA slave device transfer utility Alexander Gordeev
2026-02-24 22:34 ` [RFC PATCH 0/2] dma: DMA slave device bringup tool Frank Li
2026-02-25  9:37   ` Vinod Koul
2026-02-25 21:10     ` Alexander Gordeev
2026-02-26  7:07       ` Vinod Koul
2026-02-26 19:43         ` Alexander Gordeev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox