* [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