Skip to content

Commit

Permalink
ezdma: Initial commit for public release.
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremytrimble committed Apr 25, 2015
1 parent 5ff1393 commit a23d3f4
Show file tree
Hide file tree
Showing 5 changed files with 1,177 additions and 0 deletions.
47 changes: 47 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#
# NOTE! Don't add files that are generated in specific
# subdirectories here. Add them in the ".gitignore" file
# in that subdirectory instead.
#
# NOTE! Please use 'git ls-files -i --exclude-standard'
# command after changing this file, to see if there are
# any tracked files which get ignored after the change.
#
# Normal rules
#
.*
*.o
*.o.*
*.a
*.s
*.ko
*.so
*.so.dbg
*.mod.c
*.i
*.lst
*.symtypes
*.order
*.elf
*.bin
*.gz
*.bz2
*.lzma
*.xz
*.lz4
*.lzo
*.patch
*.gcno
modules.builtin
Module.symvers
*.dwo

#
# git files that we don't want to ignore even it they are dot-files
#
!.gitignore

*.orig
*~
\#*#

65 changes: 65 additions & 0 deletions Documentation/devicetree/bindings/dma/ezdma.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"ezdma" module: Simple zero-copy DMA direct from userspace.

TODO: clean up this documentation a little bit.

You insert a simple "ezdma" entry in your device tree like so:

/* DMA loopback in PL: mm2s -> AXI FIFO -> s2mm */
loopback_dma: axidma@40410000 {
#dma-cells = <1>;
compatible = "xlnx,axi-dma";
reg = < 0x40410000 0x10000 >;

xlnx,include-sg;
loopback_dma_mm2s_chan: dma-channel@40410000 {
compatible = "xlnx,axi-dma-mm2s-channel";
interrupt-parent = <&gic>;
interrupts = <0 31 4>; // concat port 2
// IRQ_F2P[15:0] == [91:84],[68:61]
// 2 -> 63 -> 63 - 32 = 31

xlnx,datawidth = <0x20>; // 32-bit output
xlnx,sg-length-width = <14>; // Width of Buffer Length Register (configured for 20 bits)

xlnx,device-id = <0x1>; // what's this for?
};

loopback_dma_s2mm_chan: dma-channel@40410030 {
compatible = "xlnx,axi-dma-s2mm-channel";
interrupt-parent = <&gic>;
interrupts = <0 32 4>; // concat port 3
// IRQ_F2P[15:0] == [91:84],[68:61]
// 3 -> 64 -> 64 - 32 = 32

xlnx,datawidth = <0x20>; // 32-bit output
xlnx,sg-length-width = <14>; // Width of Buffer Length Register (configured for 20 bits)

xlnx,device-id = <0x1>; // what's this for?
};
};

ezdma0 {
compatible = "ezdma";

dmas = <&loopback_dma 0 &loopback_dma 1>;
dma-names = "loop_tx", "loop_rx"; // used when obtaining reference to above DMA core using dma_request_slave_channel()
ezdma,dirs = <2 1>; // direction of DMA channel: 1 = RX (dev->cpu), 2 = TX (cpu->dev)
};

This will cause two devices "/dev/loop_tx" and "/dev/loop_rx" to show up on the
system when the "ezdma" module is loaded.

You can send an AXI stream packet by doing:
int fd = open("/dev/loop_tx", O_WRONLY);
write(fd, tx_buf, packet_size_in_bytes);

And you can receive an AXI stream packet by doing:
int fd = open("/dev/loop_rx", O_RDONLY);
read(fd, rx_buf, packet_size_in_bytes);

This makes using DMA really easy. It works great from Python too (that's how
I'm using it). Unfortunately, you can't just "cat datafile > /dev/loop_tx" to
send data, since the size of the packet is passed as an argument to
write()/read() and "cat" just sets that to whatever it likes. I have an
enhancement in mind for the future that would remove this restriction, but no
time to implement it now.
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,81 @@
# ezdma
Simple, zero-copy DMA to/from userspace.

## Usage

1. Specify which dmaengine-compatible DMA channels you'd like to create userspace-accessible device files for in your device tree:

```
ezdma0 {
compatible = "ezdma";
dmas = <&loopback_dma 0 &loopback_dma 1>;
dma-names = "loop_tx", "loop_rx";
ezdma,dirs = <2 1>; // direction of DMA channel:
// 1 = RX (dev->cpu), 2 = TX (cpu->dev)
};
```
2. After inserting the ezdma module, two devices, as named in your `dma-names` above, will become available:
```
/dev/loop_tx
/dev/loop_rx
```
3. Sending data is as simple as:
```
int tx_fd = open("/dev/loop_tx", O_WRONLY);
int rx_fd = open("/dev/loop_rx", O_RDONLY);
write(tx_fd, tx_buf, xfer_size); // send a DMA transaction
read (rx_fd, rx_buf, xfer_size);
```
See [Documentation/devicetree/bindings/dma/ezdma.txt](../blob/master/Documentation/devicetree/bindings/dma/ezdma.txt) for additional example info.
## Compiling
A Makefile for out-of-tree building is supplied. You just need to point it to the top-level directory of a kernel tree that you've already compiled.
make KERNELDIR=/path/to/your/kernel
Currently the Makefile assumes you want to cross-compile for ARM by default, but you're free to override the `ARCH` and/or `CROSS_COMPILE` variables on the command line or on in your environment. (I'd be interested to hear how it works on non-ARM platforms, as well!)
## Other info
Tested with:
- Xilinx AXI DMA on Zynq-7000 SoC.
Future enhancements:
* Allow a forced transaction size to be specified in sysfs (such that DMA transfers are always performed in increments of the given size).
* This would be useful when sending AXI stream packets of a particular size and would allow simple usages like `cat packet_file > /dev/my_tx` or `cat /dev/my_rx > packet_file`.
* Add user-space scatter-gather `readv()`/`writev()` support.
In the future, I hope to refine and contribute ezdma to the mainline kernel.
## Resources
[Linux Device Tree Background]( http://devicetree.org/Device_Tree_Usage )
[Xilinx AXI DMA Driver]( https://github.com/Xilinx/linux-xlnx/blob/master/drivers/dma/xilinx/xilinx_axidma.c )
## License (GPL)
Copyright (C) 2015 Jeremy Trimble
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; either version 2 of the License, or
(at your option) any later version.
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 <http://www.gnu.org/licenses/>.
45 changes: 45 additions & 0 deletions drivers/dma/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# Out-of-tree Makefile for building ezdma driver.
#
# This Makefile assumes that you've defined the "KERNELDIR" variable to point
# to the top-level directory of a linux source tree which you'd like to build
# ezdma against. You can specify KERNELDIR either on the make command line, or
# in your environment, or optionally uncomment the KERNELDIR?= line below and
# set the default value within this file.
#
# This Makefile is parsed twice:
#
# - In the first pass, it make -C's into your KERNELDIR.
#
# - In the second pass, it sets up make variables that cause the ezdma module
# to be built against the kernel at your KERNELDIR.
#
#

# Cross-compile for ARM by default.
ARCH?=arm
CROSS_COMPILE?=arm-linux-gnueabi-

# If desired, you can specify a default KERNELDIR by uncommenting the line
# below and placing your desired directory after the ?=
#KERNELDIR?=

LC_ALL?=C

export LC_ALL ARCH CROSS_COMPILE

ifeq ($(KERNELRELEASE),)
# First Pass

all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
rm -rf -- *~ *.ko *.o *.mod.c modules.order Module.symvers .adc* .tmp_versions .*.tmp .*.cmd

else
# Second Pass

obj-m += ezdma.o

endif
Loading

0 comments on commit a23d3f4

Please sign in to comment.