Commit 8b8c70b6 authored by Karen Xie's avatar Karen Xie

xdma driver

parent adab382b
This diff is collapsed.
BSD License
For Xilinx DMA IP software
Copyright (c) 2016-present, Xilinx, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Xilinx nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#ifndef __XDMA_BASE_API_H__
#define __XDMA_BASE_API_H__
#include <linux/types.h>
#include <linux/scatterlist.h>
#include <linux/interrupt.h>
/*
* functions exported by the xdma driver
*/
typedef struct {
u64 write_submitted;
u64 write_completed;
u64 read_requested;
u64 read_completed;
u64 restart;
u64 open;
u64 close;
u64 msix_trigger;
} xdma_statistics;
/*
* This struct should be constantly updated by XMDA using u64_stats_* APIs
* The front end will read the structure without locking (That's why updating atomically is a must)
* every time it prints the statistics.
*/
//static XDMA_Statistics stats;
/*
* xdma_device_open - read the pci bars and configure the fpga
* should be called from probe()
* NOTE:
* user interrupt will not enabled until xdma_user_isr_enable()
* is called
* @pdev: ptr to pci_dev
* @mod_name: the module name to be used for request_irq
* @user_max: max # of user/event (interrupts) to be configured
* @channel_max: max # of c2h and h2c channels to be configured
* NOTE: if the user/channel provisioned is less than the max specified,
* libxdma will update the user_max/channel_max
* returns
* a opaque handle (for libxdma to identify the device)
* NULL, in case of error
*/
void *xdma_device_open(const char *mod_name, struct pci_dev *pdev,
int *user_max, int *h2c_channel_max, int *c2h_channel_max);
/*
* xdma_device_close - prepare fpga for removal: disable all interrupts (users
* and xdma) and release all resources
* should called from remove()
* @pdev: ptr to struct pci_dev
* @tuples: from xdma_device_open()
*/
void xdma_device_close(struct pci_dev *pdev, void *dev_handle);
/*
* xdma_device_restart - restart the fpga
* @pdev: ptr to struct pci_dev
* TODO:
* may need more refining on the parameter list
* return < 0 in case of error
* TODO: exact error code will be defined later
*/
int xdma_device_restart(struct pci_dev *pdev, void *dev_handle);
/*
* xdma_user_isr_register - register a user ISR handler
* It is expected that the xdma will register the ISR, and for the user
* interrupt, it will call the corresponding handle if it is registered and
* enabled.
*
* @pdev: ptr to the the pci_dev struct
* @mask: bitmask of user interrupts (0 ~ 15)to be registered
* bit 0: user interrupt 0
* ...
* bit 15: user interrupt 15
* any bit above bit 15 will be ignored.
* @handler: the correspoinding handler
* a NULL handler will be treated as de-registeration
* @name: to be passed to the handler, ignored if handler is NULL`
* @dev: to be passed to the handler, ignored if handler is NULL`
* return < 0 in case of error
* TODO: exact error code will be defined later
*/
int xdma_user_isr_register(void *dev_hndl, unsigned int mask,
irq_handler_t handler, void *dev);
/*
* xdma_user_isr_enable/disable - enable or disable user interrupt
* @pdev: ptr to the the pci_dev struct
* @mask: bitmask of user interrupts (0 ~ 15)to be registered
* return < 0 in case of error
* TODO: exact error code will be defined later
*/
int xdma_user_isr_enable(void *dev_hndl, unsigned int mask);
int xdma_user_isr_disable(void *dev_hndl, unsigned int mask);
/*
* xdma_xfer_submit - submit data for dma operation (for both read and write)
* This is a blocking call
* @channel: channle number (< channel_max)
* == channel_max means libxdma can pick any channel available:q
* @dir: DMA_FROM/TO_DEVICE
* @offset: offset into the DDR/BRAM memory to read from or write to
* @sg_tbl: the scatter-gather list of data buffers
* @timeout: timeout in mili-seconds, *currently ignored
* return # of bytes transfered or
* < 0 in case of error
* TODO: exact error code will be defined later
*/
ssize_t xdma_xfer_submit(void *dev_hndl, int channel, bool write, u64 ep_addr,
struct sg_table *sgt, bool dma_mapped, int timeout_ms);
/////////////////////missing API////////////////////
//xdma_get_channle_state - if no interrupt on DMA hang is available
//xdma_channle_restart
#endif
SHELL = /bin/bash
topdir := $(shell cd $(src)/.. && pwd)
TARGET_MODULE:=libxdma
EXTRA_CFLAGS := -I$(topdir)/include
EXTRA_CFLAGS += -D__LIBXDMA_MOD__
ifneq ($(KERNELRELEASE),)
obj-m := $(TARGET_MODULE).o
# $(TARGET_MODULE)-objs := libxdma.o
else
BUILDSYSTEM_DIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
all :
$(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) modules
clean:
$(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) clean
install: all
$(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) modules_install
endif
This diff is collapsed.
This diff is collapsed.
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#ifndef __XDMA_VERSION_H__
#define __XDMA_VERSION_H__
#define DRV_MOD_MAJOR 2018
#define DRV_MOD_MINOR 3
#define DRV_MOD_PATCHLEVEL 41
#define DRV_MODULE_VERSION \
__stringify(DRV_MOD_MAJOR) "." \
__stringify(DRV_MOD_MINOR) "." \
__stringify(DRV_MOD_PATCHLEVEL)
#define DRV_MOD_VERSION_NUMBER \
((DRV_MOD_MAJOR)*1000 + (DRV_MOD_MINOR)*100 + DRV_MOD_PATCHLEVEL)
#endif /* ifndef __XDMA_VERSION_H__ */
The files in this directory provide Xilinx PCIe DMA drivers, example software,
and example test scripts that can be used to exercise the Xilinx PCIe DMA IP.
This software can be used directly or referenced to create drivers and software
for your Xilinx FPGA hardware design.
Directory and file description:
===============================
- xdma/: This directory contains the Xilinx PCIe DMA kernel module
driver files.
- libxdma/: This directory contains support files for the kernel driver module,
which interfaces directly with the XDMA IP.
- include/: This directory contains all include files that are needed for
compiling driver.
- etc/: This directory contains rules for the Xilinx PCIe DMA kernel module
and software. The files in this directory should be copied to the /etc/
directory on your linux system.
- tests/: This directory contains example application software to exercise the
provided kernel module driver and Xilinx PCIe DMA IP. This directory
also contains the following scripts and directories.
- load_driver.sh:
This script loads the kernel module and creates the necissary
kernel nodes used by the provided software.
The The kernel device nodes will be created under /dev/xdma*.
Additional device nodes are created under /dev/xdma/card* to
more easily differentiate between multiple PCIe DMA enabled
cards. Root permissions will be required to run this script.
- run_test.sh:
This script runs sample tests on a Xilinx PCIe DMA target and
returns a pass (0) or fail (1) result.
This script is intended for use with the PCIe DMA example
design.
- perform_hwcount.sh:
This script runs hardware performance for XDMA for both Host to
Card (H2C) and Card to Host (C2H). The result are copied to
'hw_log_h2c.txt' and hw_log_c2h.txt' text files.
For each direction the performance script loops from 64 bytes
to 4MBytes and generate performance numbers (byte size doubles
for each loop count).
You can grep for 'data rate' on those two files to see data
rate values.
Data rate values are in percentage of maximum throughput.
Maximum data rate for x8 Gen3 is 8Gbytes/s, so for a x8Gen3
design value of 0.81 data rate is 0.81*8 = 6.48Gbytes/s.
Maximum data rate for x16 Gen3 is 16Gbytes/s, so for a x16Gen3
design value of 0.78 data rate is 0.78*16 = 12.48Gbytes/s.
This program can be run on AXI-MM example design.
AXI-ST example design is a loopback design, both H2C and C2H
are connected. Running on AXI-ST example design will not
generate proper numbers.
If a AXI-ST design is independent of H2C and C2H, performance
number can be generated.
- data/:
This directory contains binary data files that are used for DMA
data transfers to the Xilinx FPGA PCIe endpoint device.
Usage:
- Change directory to the driver directory.
cd xdma
- Compile and install the kernel module driver.
make install
- Change directory to the tools directory.
cd tools
- Compile the provided example test tools.
make
- Copy the provided driver rules from the etc directory to the /etc/ directory
on your system.
cp ../etc/udev/rules.d/* /etc/udev/rules.d/
- Load the kernel module driver:
a. modprobe xdma
b. using the provided script.
cd tests
./load_driver.sh
- Run the provided test script to generate basic DMA traffic.
./run_test.sh
- Check driver Version number
modinfo xdma (or)
modinfo ../xdma/xdma.ko
Updates and Backward Compaitiblity:
- The following features were added to the PCIe DMA IP and driver in Vivado
2016.1. These features cannot be used with PCIe DMA IP if the IP was
generated using a Vivado build earlier than 2016.1.
- Poll Mode: Earlier versions of Vivado only support interrupt mode which
is the default behavior of the driver.
- Source/Destination Address: Earlier versions of Vivado PCIe DMA IP
required the low-order bits of the Source and Destination address to be
the same.
As of 2016.1 this restriction has been removed and the Source and
Destination addresses can be any arbitrary address that is valid for
your system.
Frequently asked questions:
Q: How do I uninstall the kernel module driver?
A: Use the following commands to uninstall the driver.
- Uninstall the kernel module.
rmmod -s xdma
- Delete the dma rules that were added.
rm -f /etc/udev/rules.d/60-xdma.rules
rm -f /etc/udev/rules.d/xdma-udev-command.sh
Q: How do I modify the PCIe Device IDs recognized by the kernel module driver?
A: The xdma/xdma_mod.c file constains the pci_device_id struct that identifies
the PCIe Device IDs that are recognized by the driver in the following
format:
{ PCI_DEVICE(0x10ee, 0x8038), },
Add, remove, or modify the PCIe Device IDs in this struct as desired. Then
uninstall the existing xdma kernel module, compile the driver again, and
re-install the driver using the load_driver.sh script.
Q: By default the driver uses interupts to signal when DMA transfers are
completed. How do I modify the driver to use polling rather than
interrupts to determine when DMA transactions are completed?
A: The driver can be changed from being interrupt driven (default) to being
polling driven (poll mode) when the kernel module is inserted. To do this
modify the load_driver.sh file as follows:
Change: insmod xdma/xdma.ko
To: insmod xdma/xdma.ko poll_mode=1
Note: Interrupt vs Poll mode will apply to all DMA channels. If desired the
driver can be modified such that some channels are interrupt driven while
others are polling driven. Refer to the poll mode section of PG195 for
additional information on using the PCIe DMA IP in poll mode.
#!/bin/bash
transferSize=$1
transferCount=$2
h2cChannels=$3
c2hChannels=$4
tool_path=../tools
testError=0
# Run the PCIe DMA memory mapped write read test
echo "Info: Running PCIe DMA memory mapped write read test"
echo " transfer size: $transferSize"
echo " transfer count: $transferCount"
# Write to all enabled h2cChannels in parallel
if [ $h2cChannels -gt 0 ]; then
# Loop over four blocks of size $transferSize and write to them (in parallel where possible)
for ((i=0; i<=3; i++))
do
addrOffset=$(($transferSize * $i))
curChannel=$(($i % $h2cChannels))
echo "Info: Writing to h2c channel $curChannel at address offset $addrOffset."
$tool_path/dma_to_device -d /dev/xdma0_h2c_${curChannel} -f data/datafile${i}_4K.bin -s $transferSize -a $addrOffset -c $transferCount &
# If all channels have active transactions we must wait for them to complete
if [ $(($curChannel+1)) -eq $h2cChannels ]; then
echo "Info: Wait for current transactions to complete."
wait
fi
done
fi
# Wait for the last transaction to complete.
wait
# Read from all enabled c2hChannels in parallel
if [ $c2hChannels -gt 0 ]; then
# Loop over four blocks of size $transferSize and read from them (in parallel where possible)
for ((i=0; i<=3; i++))
do
addrOffset=$(($transferSize * $i))
curChannel=$(($i % $c2hChannels))
rm -f data/output_datafile${i}_4K.bin
echo "Info: Reading from c2h channel $curChannel at address offset $addrOffset."
$tool_path/dma_from_device -d /dev/xdma0_c2h_${curChannel} -f data/output_datafile${i}_4K.bin -s $transferSize -a $addrOffset -c $transferCount &
# If all channels have active transactions we must wait for them to complete
if [ $(($curChannel+1)) -eq $c2hChannels ]; then
echo "Info: Wait for the current transactions to complete."
wait
fi
done
fi
# Wait for the last transaction to complete.
wait
# Verify that the written data matches the read data if possible.
if [ $h2cChannels -eq 0 ]; then
echo "Info: No data verification was performed because no h2c channels are enabled."
elif [ $c2hChannels -eq 0 ]; then
echo "Info: No data verification was performed because no c2h channels are enabled."
else
echo "Info: Checking data integrity."
for ((i=0; i<=3; i++))
do
cmp data/output_datafile${i}_4K.bin data/datafile${i}_4K.bin -n $transferSize
returnVal=$?
if [ ! $returnVal == 0 ]; then
echo "Error: The data written did not match the data that was read."
echo " address range: $(($i*$transferSize)) - $((($i+1)*$transferSize))"
echo " write data file: data/datafile${i}_4K.bin"
echo " read data file: data/output_datafile${i}_4K.bin"
testError=1
else
echo "Info: Data check passed for address range $(($i*$transferSize)) - $((($i+1)*$transferSize))."
fi
done
fi
# Exit with an error code if an error was found during testing
if [ $testError -eq 1 ]; then
echo "Error: Test completed with Errors."
exit 1
fi
# Report all tests passed and exit
echo "Info: All PCIe DMA memory mapped tests passed."
exit 0
#!/bin/bash
transferSize=$1
transferCount=$2
channelPairs=$3
tool_path=../tools
testError=0
# Run the PCIe DMA streaming test
echo "Info: Running PCIe DMA streaming test"
echo " transfer size: $transferSize"
echo " transfer count: $transferCount"
echo "Info: Only channels that have both h2c and c2h will be tested as the other"
echo " interfaces are left unconnected in the PCIe DMA example design. "
# Setup the DMA c2h channels to wait for incomming data from the h2c channels.
for ((i=0; i<$channelPairs; i++))
do
rm -f data/output_datafile${i}_4K.bin
echo "Info: DMA setup to read from c2h channel $i. Waiting on write data to channel $i."
$tool_path/dma_from_device -d /dev/xdma0_c2h_${i} -f data/output_datafile${i}_4K.bin -s $transferSize -c $transferCount &
done
# Wait to make sure the DMA is ready to receive data.
sleep 1s
# Setup the DMA to write to the h2c channels. Data will be push out the h2c channel
# and then read back through the c2h channel and written to the output data file.
for ((i=0; i<$channelPairs; i++))
do
echo "Info: Writing to h2c channel $i. This will also start reading data on c2h channel $i."
$tool_path/dma_to_device -d /dev/xdma0_h2c_${i} -f data/datafile${i}_4K.bin -s $transferSize -c $transferCount &
done
# Wait for the current transactions to complete
echo "Info: Wait the for current transactions to complete."
wait
# Verify that the written data matches the read data.
for ((i=0; i<$channelPairs; i++))
do
echo "Info: Checking data integrity."
cmp data/output_datafile${i}_4K.bin data/datafile${i}_4K.bin -n $transferSize
returnVal=$?
if [ ! $returnVal == 0 ]; then
echo "Error: The data written did not match the data that was read."
echo " write data file: data/datafile${i}_4K.bin"
echo " read data file: data/output_datafile${i}_4K.bin"
testError=1
else
echo "Info: Data check passed for c2h and h2c channel $i."
fi
done
# Exit with an error code if an error was found during testing
if [ $testError -eq 1 ]; then
echo "Error: Test completed with Errors."
exit 1
fi
# Report all tests passed and exit
echo "Info: All PCIe DMA streaming tests passed."
exit 0
#!/bin/bash
# Make sure only root can run our script
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" 1>&2
exit 1
fi
# Remove the existing xdma kernel module
lsmod | grep xdma
if [ $? -eq 0 ]; then
rmmod xdma
fi
echo -n "Loading xdma driver..."
# Use the following command to Load the driver in the default
# or interrupt drive mode. This will allow the driver to use
# interrupts to signal when DMA transfers are completed.
insmod ../xdma/xdma.ko
# Use the following command to Load the driver in Polling
# mode rather than than interrupt mode. This will allow the
# driver to use polling to determ when DMA transfers are
# completed.
#insmod ../xdma/xdma.ko poll_mode=1
if [ ! $? == 0 ]; then
echo "Error: Kernel module did not load properly."
echo " FAILED"
exit 1
fi
# Check to see if the xdma devices were recognized
echo ""
cat /proc/devices | grep xdma > /dev/null
returnVal=$?
if [ $returnVal == 0 ]; then
# Installed devices were recognized.
echo "The Kernel module installed correctly and the xmda devices were recognized."
else
# No devices were installed.
echo "Error: The Kernel module installed correctly, but no devices were recognized."
echo " FAILED"
exit 1
fi
echo " DONE"
#!/bin/bash
tool_path=../tools
h2cchannels=$1
c2hchannels=$2
if [ "$#" -ne 2 ];then
echo "usage $0 <no:of h2cchannels> <no:of c2hchannels>"
exit -1
fi
rm hw_log_h2c.txt
rm hw_log_c2h.txt
echo "h2cchannels $h2cchannels"
echo "c2hchannels $c2hchannels"
h2c=/dev/xdma0_h2c_0
c2h=/dev/xdma0_c2h_0
iter=1
out_h2c=hw_log_h2c.txt
out_c2h=hw_log_c2h.txt
for ((i=0;i<h2cchannels;i++))
do
h2c=/dev/xdma0_h2c_$i
c2h=/dev/xdma0_c2h_$i
byte=64
for ((j=0; j<=16; j++)) do
echo "** HW H2C = $h2c bytecount = $byte and iteration = $iter" | tee -a $out_h2c
$tool_path/performance -d $h2c -c $iter -s $byte | tee -a $out_h2c
byte=$(($byte*2))
done
wait
byte=64
for ((j=0; j<=16; j++)) do
echo "** HW C2H = $c2h bytecount = $byte and iteration = $iter" | tee -a $out_c2h
$tool_path/performance -d $c2h -c $iter -s $byte | tee -a $out_c2h
byte=$(($byte*2))
done
done
#!/bin/bash
#---------------------------------------------------------------------
# Script variables
#---------------------------------------------------------------------
tool_path=../tools
# Size of PCIe DMA transfers that will be used for this test.
# Make sure valid addresses exist in the FPGA when modifying this
# variable. Addresses in the range of 0 - (4 * transferSize) will
# be used for this test when the PCIe DMA core is setup for memory
# mapped transaction.
transferSize=1024
# Set the number of times each data transfer will be repeated.
# Increasing this number will allow transfers to accross multiple
# channels to over lap for a longer period of time.
transferCount=1
# Determine which Channels are enabled
# Determine if the core is Memory Mapped or Streaming
isStreaming=0
h2cChannels=0
for ((i=0; i<=3; i++))
do
statusRegVal=`$tool_path/reg_rw /dev/xdma0_control 0x0${i}00 w | grep "Read.*:" | sed 's/Read.*: 0x\([a-z0-9]*\)/\1/'`
channelId=${statusRegVal:0:3}
streamEnable=${statusRegVal:4:1}
if [ $channelId == "1fc" ]; then
h2cChannels=$((h2cChannels + 1))
if [ $streamEnable == "8" ]; then
isStreaming=1
fi
fi
done
echo "Info: Number of enabled h2c channels = $h2cChannels"
# Find enabled c2hChannels
c2hChannels=0
for ((i=0; i<=3; i++))
do
$tool_path/reg_rw /dev/xdma0_control 0x1${i}00 w | grep "Read.*: 0x1fc" > /dev/null
returnVal=$?
if [ $returnVal -eq 0 ]; then
c2hChannels=$((c2hChannels + 1))
if [ $streamEnable == "8" ]; then
isStreaming=1
fi
fi
done
echo "Info: Number of enabled c2h channels = $c2hChannels"
# Report if the PCIe DMA core is memory mapped or streaming
if [ $isStreaming -eq 0 ]; then
echo "Info: The PCIe DMA core is memory mapped."
else
echo "Info: The PCIe DMA core is streaming."
fi
# Check to make sure atleast one channel was identified
if [ $h2cChannels -eq 0 -a $c2hChannels -eq 0 ]; then
echo "Error: No PCIe DMA channels were identified."
exit 1
fi
# Perform testing on the PCIe DMA core.
testError=0
if [ $isStreaming -eq 0 ]; then
# Run the PCIe DMA memory mapped write read test
./dma_memory_mapped_test.sh $transferSize $transferCount $h2cChannels $c2hChannels
returnVal=$?
if [ $returnVal -eq 1 ]; then
testError=1
fi
else
# Run the PCIe DMA streaming test
channelPairs=$(($h2cChannels < $c2hChannels ? $h2cChannels : $c2hChannels))
if [ $channelPairs -gt 0 ]; then
./dma_streaming_test.sh $transferSize $transferCount $channelPairs
returnVal=$?
if [ $returnVal -eq 1 ]; then
testError=1
fi
else
echo "Info: No PCIe DMA stream channels were tested because no h2c/c2h pairs were found."
fi
fi
# Exit with an error code if an error was found during testing
if [ $testError -eq 1 ]; then
echo "Error: Test completed with Errors."
exit 1
fi
# Report all tests passed and exit
echo "Info: All tests in run_tests.sh passed."
exit 0
CC ?= gcc
all: reg_rw dma_to_device dma_from_device performance
dma_to_device: dma_to_device.o
$(CC) -lrt -o $@ $< -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D_LARGE_FILE_SOURCE
dma_from_device: dma_from_device.o
$(CC) -lrt -o $@ $< -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D_LARGE_FILE_SOURCE
performance: performance.o
$(CC) -o $@ $< -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D_LARGE_FILE_SOURCE
reg_rw: reg_rw.o
$(CC) -o $@ $<
%.o: %.c
$(CC) -c -std=c99 -o $@ $< -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -D_LARGE_FILE_SOURCE
clean:
rm -rf reg_rw *.o *.bin dma_to_device dma_from_device performance
/*
* This file is part of the Xilinx DMA IP Core driver tool for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is licensed under BSD-style license (found in the
* LICENSE file in the root directory of this source tree)
*/
#define _BSD_SOURCE
#define _XOPEN_SOURCE 500
#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "dma_utils.c"
#define DEVICE_NAME_DEFAULT "/dev/xdma0_c2h_0"
#define SIZE_DEFAULT (32)
#define COUNT_DEFAULT (1)
static struct option const long_opts[] = {
{"device", required_argument, NULL, 'd'},
{"address", required_argument, NULL, 'a'},
{"size", required_argument, NULL, 's'},
{"offset", required_argument, NULL, 'o'},
{"count", required_argument, NULL, 'c'},
{"file", required_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"verbose", no_argument, NULL, 'v'},
{0, 0, 0, 0}
};
static int test_dma(char *devname, uint64_t addr, uint64_t size,
uint64_t offset, uint64_t count, char *ofname);
static int no_write = 0;
static void usage(const char *name)
{
int i = 0;
fprintf(stdout, "%s\n\n", name);
fprintf(stdout, "usage: %s [OPTIONS]\n\n", name);
fprintf(stdout, "Read via SGDMA, optionally save output to a file\n\n");
fprintf(stdout, " -%c (--%s) device (defaults to %s)\n",
long_opts[i].val, long_opts[i].name, DEVICE_NAME_DEFAULT);
i++;
fprintf(stdout, " -%c (--%s) the start address on the AXI bus\n",
long_opts[i].val, long_opts[i].name);
i++;
fprintf(stdout,
" -%c (--%s) size of a single transfer in bytes, default %d.\n",
long_opts[i].val, long_opts[i].name, SIZE_DEFAULT);
i++;
fprintf(stdout, " -%c (--%s) page offset of transfer\n",
long_opts[i].val, long_opts[i].name);
i++;
fprintf(stdout, " -%c (--%s) number of transfers, default is %d.\n",
long_opts[i].val, long_opts[i].name, COUNT_DEFAULT);
i++;
fprintf(stdout,
" -%c (--%s) file to write the data of the transfers\n",
long_opts[i].val, long_opts[i].name);
i++;
fprintf(stdout, " -%c (--%s) print usage help and exit\n",
long_opts[i].val, long_opts[i].name);
i++;
fprintf(stdout, " -%c (--%s) verbose output\n",
long_opts[i].val, long_opts[i].name);
i++;
}
int main(int argc, char *argv[])
{
int cmd_opt;
char *device = DEVICE_NAME_DEFAULT;
uint64_t address = 0;
uint64_t size = SIZE_DEFAULT;
uint64_t offset = 0;
uint64_t count = COUNT_DEFAULT;
char *ofname = NULL;
while ((cmd_opt = getopt_long(argc, argv, "vhxc:f:d:a:s:o:", long_opts,
NULL)) != -1) {
switch (cmd_opt) {
case 0:
/* long option */
break;
case 'd':
/* device node name */
device = strdup(optarg);
break;
case 'a':
/* RAM address on the AXI bus in bytes */
address = getopt_integer(optarg);
break;
/* RAM size in bytes */
case 's':
size = getopt_integer(optarg);
break;
case 'o':
offset = getopt_integer(optarg) & 4095;
break;
/* count */
case 'c':
count = getopt_integer(optarg);
break;
/* count */
case 'f':
ofname = strdup(optarg);
break;
/* print usage help and exit */
case 'x':
no_write++;
break;
case 'v':
verbose = 1;
break;
case 'h':
default:
usage(argv[0]);
exit(0);
break;
}
}
if (verbose)
fprintf(stdout,
"dev %s, addr 0x%lx, size 0x%lx, offset 0x%lx, count %lu\n",
device, address, size, offset, count);
return test_dma(device, address, size, offset, count, ofname);
}
static int test_dma(char *devname, uint64_t addr, uint64_t size,
uint64_t offset, uint64_t count, char *ofname)
{
ssize_t rc;
uint64_t i;
char *buffer = NULL;
char *allocated = NULL;
struct timespec ts_start, ts_end;
int out_fd = -1;
int fpga_fd = open(devname, O_RDWR | O_NONBLOCK);
long total_time = 0;
float result;
float avg_time = 0;
if (fpga_fd < 0) {
fprintf(stderr, "unable to open device %s, %d.\n",
devname, fpga_fd);
perror("open device");
return -EINVAL;
}
/* create file to write data to */
if (ofname) {
out_fd = open(ofname, O_RDWR | O_CREAT | O_TRUNC | O_SYNC,
0666);
if (out_fd < 0) {
fprintf(stderr, "unable to open output file %s, %d.\n",
ofname, out_fd);
perror("open output file");
rc = -EINVAL;
goto out;
}
}
posix_memalign((void **)&allocated, 4096 /*alignment */ , size + 4096);
if (!allocated) {
fprintf(stderr, "OOM %lu.\n", size + 4096);
rc = -ENOMEM;
goto out;
}
buffer = allocated + offset;
if (verbose)
fprintf(stdout, "host buffer 0x%lx, %p.\n", size + 4096, buffer);
for (i = 0; i < count; i++) {
rc = clock_gettime(CLOCK_MONOTONIC, &ts_start);
/* lseek & read data from AXI MM into buffer using SGDMA */
rc = read_to_buffer(devname, fpga_fd, buffer, size, addr);
if (rc < 0)
goto out;
clock_gettime(CLOCK_MONOTONIC, &ts_end);
/* subtract the start time from the end time */
timespec_sub(&ts_end, &ts_start);
total_time += ts_end.tv_nsec;
/* a bit less accurate but side-effects are accounted for */
if (verbose)
fprintf(stdout,
"#%lu: CLOCK_MONOTONIC %ld.%09ld sec. read %ld bytes\n",
i, ts_end.tv_sec, ts_end.tv_nsec, size);
/* file argument given? */
if ((out_fd >= 0) & (no_write == 0)) {
rc = write_from_buffer(ofname, out_fd, buffer,
size, i*size);
if (rc < 0)
goto out;
}
}
avg_time = (float)total_time/(float)count;
result = ((float)size)*1000/avg_time;
if (verbose)
printf("** Avg time device %s, total time %ld nsec, avg_time = %f, size = %lu, BW = %f \n",
devname, total_time, avg_time, size, result);
printf("** Average BW = %lu, %f\n", size, result);
rc = 0;
out:
close(fpga_fd);
if (out_fd >= 0)
close(out_fd);
free(allocated);
return rc;
}
/*
* This file is part of the Xilinx DMA IP Core driver tools for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is licensed under BSD-style license (found in the
* LICENSE file in the root directory of this source tree)
*/
#define _BSD_SOURCE
#define _XOPEN_SOURCE 500
#include <assert.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include "dma_utils.c"
static struct option const long_opts[] = {
{"device", required_argument, NULL, 'd'},
{"address", required_argument, NULL, 'a'},
{"size", required_argument, NULL, 's'},
{"offset", required_argument, NULL, 'o'},
{"count", required_argument, NULL, 'c'},
{"data infile", required_argument, NULL, 'f'},
{"data outfile", required_argument, NULL, 'w'},
{"help", no_argument, NULL, 'h'},
{"verbose", no_argument, NULL, 'v'},
{0, 0, 0, 0}
};
#define DEVICE_NAME_DEFAULT "/dev/xdma0_h2c_0"
#define SIZE_DEFAULT (32)
#define COUNT_DEFAULT (1)
static int test_dma(char *devname, uint64_t addr, uint64_t size,
uint64_t offset, uint64_t count, char *filename, char *);
static void usage(const char *name)
{
int i = 0;
fprintf(stdout, "%s\n\n", name);
fprintf(stdout, "usage: %s [OPTIONS]\n\n", name);
fprintf(stdout,
"Write via SGDMA, optionally read input from a file.\n\n");
fprintf(stdout, " -%c (--%s) device (defaults to %s)\n",
long_opts[i].val, long_opts[i].name, DEVICE_NAME_DEFAULT);
i++;
fprintf(stdout, " -%c (--%s) the start address on the AXI bus\n",
long_opts[i].val, long_opts[i].name);
i++;
fprintf(stdout,
" -%c (--%s) size of a single transfer in bytes, default %d,\n",
long_opts[i].val, long_opts[i].name, SIZE_DEFAULT);
i++;
fprintf(stdout, " -%c (--%s) page offset of transfer\n",
long_opts[i].val, long_opts[i].name);
i++;
fprintf(stdout, " -%c (--%s) number of transfers, default %d\n",
long_opts[i].val, long_opts[i].name, COUNT_DEFAULT);
i++;
fprintf(stdout, " -%c (--%s) filename to read the data from.\n",
long_opts[i].val, long_opts[i].name);
i++;
fprintf(stdout,
" -%c (--%s) filename to write the data of the transfers\n",
long_opts[i].val, long_opts[i].name);
i++;
fprintf(stdout, " -%c (--%s) print usage help and exit\n",
long_opts[i].val, long_opts[i].name);
i++;
fprintf(stdout, " -%c (--%s) verbose output\n",
long_opts[i].val, long_opts[i].name);
i++;
}
int main(int argc, char *argv[])
{
int cmd_opt;
char *device = DEVICE_NAME_DEFAULT;
uint64_t address = 0;
uint64_t size = SIZE_DEFAULT;
uint64_t offset = 0;
uint64_t count = COUNT_DEFAULT;
char *infname = NULL;
char *ofname = NULL;
while ((cmd_opt =
getopt_long(argc, argv, "vhc:f:d:a:s:o:w:", long_opts,
NULL)) != -1) {
switch (cmd_opt) {
case 0:
/* long option */
break;
case 'd':
/* device node name */
//fprintf(stdout, "'%s'\n", optarg);
device = strdup(optarg);
break;
case 'a':
/* RAM address on the AXI bus in bytes */
address = getopt_integer(optarg);
break;
case 's':
/* size in bytes */
size = getopt_integer(optarg);
break;
case 'o':
offset = getopt_integer(optarg) & 4095;
break;
/* count */
case 'c':
count = getopt_integer(optarg);
break;
/* count */
case 'f':
infname = strdup(optarg);
break;
case 'w':
ofname = strdup(optarg);
break;
/* print usage help and exit */
case 'v':
verbose = 1;
break;
case 'h':
default:
usage(argv[0]);
exit(0);
break;
}
}
if (verbose)
fprintf(stdout,
"dev %s, address 0x%lx, size 0x%lx, offset 0x%lx, count %lu\n",
device, address, size, offset, count);
return test_dma(device, address, size, offset, count, infname, ofname);
}
static int test_dma(char *devname, uint64_t addr, uint64_t size,
uint64_t offset, uint64_t count, char *infname,
char *ofname)
{
uint64_t i;
ssize_t rc;
char *buffer = NULL;
char *allocated = NULL;
struct timespec ts_start, ts_end;
int infile_fd = -1;
int outfile_fd = -1;
int fpga_fd = open(devname, O_RDWR);
long total_time = 0;
float result;
float avg_time = 0;
if (fpga_fd < 0) {
fprintf(stderr, "unable to open device %s, %d.\n",
devname, fpga_fd);
perror("open device");
return -EINVAL;
}
if (infname) {
infile_fd = open(infname, O_RDONLY);
if (infile_fd < 0) {
fprintf(stderr, "unable to open input file %s, %d.\n",
infname, infile_fd);
perror("open input file");
rc = -EINVAL;
goto out;
}
}
if (ofname) {
outfile_fd =
open(ofname, O_RDWR | O_CREAT | O_TRUNC | O_SYNC,
0666);
if (outfile_fd < 0) {
fprintf(stderr, "unable to open output file %s, %d.\n",
ofname, outfile_fd);
perror("open output file");
rc = -EINVAL;
goto out;
}
}
posix_memalign((void **)&allocated, 4096 /*alignment */ , size + 4096);
if (!allocated) {
fprintf(stderr, "OOM %lu.\n", size + 4096);
rc = -ENOMEM;
goto out;
}
buffer = allocated + offset;
if (verbose)
fprintf(stdout, "host buffer 0x%lx = %p\n",
size + 4096, buffer);
if (infile_fd >= 0) {
rc = read_to_buffer(infname, infile_fd, buffer, size, 0);
if (rc < 0)
goto out;
}
for (i = 0; i < count; i++) {
/* write buffer to AXI MM address using SGDMA */
rc = clock_gettime(CLOCK_MONOTONIC, &ts_start);
rc = write_from_buffer(devname, fpga_fd, buffer, size, addr);
if (rc < 0)
goto out;
rc = clock_gettime(CLOCK_MONOTONIC, &ts_end);
/* subtract the start time from the end time */
timespec_sub(&ts_end, &ts_start);
total_time += ts_end.tv_nsec;
/* a bit less accurate but side-effects are accounted for */
if (verbose)
fprintf(stdout,
"#%lu: CLOCK_MONOTONIC %ld.%09ld sec. write %ld bytes\n",
i, ts_end.tv_sec, ts_end.tv_nsec, size);
if (outfile_fd >= 0) {
rc = write_from_buffer(ofname, outfile_fd, buffer,
size, i * size);
if (rc < 0)
goto out;
}
}
avg_time = (float)total_time/(float)count;
result = ((float)size)*1000/avg_time;
if (verbose)
printf("** Avg time device %s, total time %ld nsec, avg_time = %f, size = %lu, BW = %f \n",
devname, total_time, avg_time, size, result);
printf("** Average BW = %lu, %f\n",size, result);
rc = 0;
out:
close(fpga_fd);
if (infile_fd >= 0)
close(infile_fd);
if (outfile_fd >= 0)
close(outfile_fd);
free(allocated);
return rc;
}
/*
* This file is part of the Xilinx DMA IP Core driver tools for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is licensed under BSD-style license (found in the
* LICENSE file in the root directory of this source tree)
*/
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
/*
* man 2 write:
* On Linux, write() (and similar system calls) will transfer at most
* 0x7ffff000 (2,147,479,552) bytes, returning the number of bytes
* actually transferred. (This is true on both 32-bit and 64-bit
* systems.)
*/
#define RW_MAX_SIZE 0x7ffff000
int verbose = 0;
uint64_t getopt_integer(char *optarg)
{
int rc;
uint64_t value;
rc = sscanf(optarg, "0x%lx", &value);
if (rc <= 0)
rc = sscanf(optarg, "%lu", &value);
//printf("sscanf() = %d, value = 0x%lx\n", rc, value);
return value;
}
ssize_t read_to_buffer(char *fname, int fd, char *buffer, uint64_t size,
uint64_t base)
{
ssize_t rc;
uint64_t count = 0;
char *buf = buffer;
off_t offset = base;
while (count < size) {
uint64_t bytes = size - count;
if (bytes > RW_MAX_SIZE)
bytes = RW_MAX_SIZE;
if (offset) {
rc = lseek(fd, offset, SEEK_SET);
if (rc != offset) {
fprintf(stderr, "%s, seek off 0x%lx != 0x%lx.\n",
fname, rc, offset);
perror("seek file");
return -EIO;
}
}
/* read data from file into memory buffer */
rc = read(fd, buf, bytes);
if (rc != bytes) {
fprintf(stderr, "%s, R off 0x%lx, 0x%lx != 0x%lx.\n",
fname, count, rc, bytes);
perror("read file");
return -EIO;
}
count += bytes;
buf += bytes;
offset += bytes;
}
if (count != size) {
fprintf(stderr, "%s, R failed 0x%lx != 0x%lx.\n",
fname, count, size);
return -EIO;
}
return count;
}
ssize_t write_from_buffer(char *fname, int fd, char *buffer, uint64_t size,
uint64_t base)
{
ssize_t rc;
uint64_t count = 0;
char *buf = buffer;
off_t offset = base;
while (count < size) {
uint64_t bytes = size - count;
if (bytes > RW_MAX_SIZE)
bytes = RW_MAX_SIZE;
if (offset) {
rc = lseek(fd, offset, SEEK_SET);
if (rc != offset) {
fprintf(stderr, "%s, seek off 0x%lx != 0x%lx.\n",
fname, rc, offset);
perror("seek file");
return -EIO;
}
}
/* write data to file from memory buffer */
rc = write(fd, buf, bytes);
if (rc != bytes) {
fprintf(stderr, "%s, W off 0x%lx, 0x%lx != 0x%lx.\n",
fname, offset, rc, bytes);
perror("write file");
return -EIO;
}
count += bytes;
buf += bytes;
offset += bytes;
}
if (count != size) {
fprintf(stderr, "%s, R failed 0x%lx != 0x%lx.\n",
fname, count, size);
return -EIO;
}
return count;
}
/* Subtract timespec t2 from t1
*
* Both t1 and t2 must already be normalized
* i.e. 0 <= nsec < 1000000000
*/
static int timespec_check(struct timespec *t)
{
if ((t->tv_nsec < 0) || (t->tv_nsec >= 1000000000))
return -1;
return 0;
}
void timespec_sub(struct timespec *t1, struct timespec *t2)
{
if (timespec_check(t1) < 0) {
fprintf(stderr, "invalid time #1: %lld.%.9ld.\n",
(long long)t1->tv_sec, t1->tv_nsec);
return;
}
if (timespec_check(t2) < 0) {
fprintf(stderr, "invalid time #2: %lld.%.9ld.\n",
(long long)t2->tv_sec, t2->tv_nsec);
return;
}
t1->tv_sec -= t2->tv_sec;
t1->tv_nsec -= t2->tv_nsec;
if (t1->tv_nsec >= 1000000000) {
t1->tv_sec++;
t1->tv_nsec -= 1000000000;
} else if (t1->tv_nsec < 0) {
t1->tv_sec--;
t1->tv_nsec += 1000000000;
}
}
#!/bin/bash
#rm hw_log_h2c.txt
#rm hw_log_c2h.txt
h2c=/dev/xdma0_h2c_0
c2h=/dev/xdma0_c2h_0
iter=1
out_h2c=hw_log_h2c.txt
out_c2h=hw_log_c2h.txt
byte=64
for ((j=0; j<=16; j++)) do
echo "** HW H2C = $h2c bytecount = $byte and iteration = $iter" | tee -a $out_h2c
./performance -d $h2c -c $iter -s $byte | tee -a $out_h2c
byte=$(($byte*2))
done
byte=64
for ((j=0; j<=16; j++)) do
echo "** HW C2H = $c2h bytecount = $byte and iteration = $iter" | tee -a $out_c2h
./performance -d $c2h -c $iter -s $byte | tee -a $out_c2h
byte=$(($byte*2))
done
/*
* This file is part of the Xilinx DMA IP Core driver tools for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is licensed under BSD-style license (found in the
* LICENSE file in the root directory of this source tree)
*/
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
/* @TODO During kernel upstreaming, the IOCTL must move into the public user API of the kernel */
#include "../xdma/cdev_sgdma.h"
struct xdma_performance_ioctl perf;
static struct option const long_opts[] =
{
{"device", required_argument, NULL, 'd'},
{"count", required_argument, NULL, 'c'},
{"size", required_argument, NULL, 's'},
{"incremental", no_argument, NULL, 'i'},
{"non-incremental", no_argument, NULL, 'n'},
{"verbose", no_argument, NULL, 'v'},
{"help", no_argument, NULL, 'h'},
{0, 0, 0, 0}
};
static void usage(const char* name)
{
int i = 0;
printf("%s\n\n", name);
printf("usage: %s [OPTIONS]\n\n", name);
printf("Performance test for XDMA SGDMA engine.\n\n");
printf(" -%c (--%s) device\n", long_opts[i].val, long_opts[i].name); i++;
printf(" -%c (--%s) incremental\n", long_opts[i].val, long_opts[i].name); i++;
printf(" -%c (--%s) non-incremental\n", long_opts[i].val, long_opts[i].name); i++;
printf(" -%c (--%s) be more verbose during test\n", long_opts[i].val, long_opts[i].name); i++;
printf(" -%c (--%s) print usage help and exit\n", long_opts[i].val, long_opts[i].name); i++;
}
static uint32_t getopt_integer(char *optarg)
{
int rc;
uint32_t value;
rc = sscanf(optarg, "0x%x", &value);
if (rc <= 0)
rc = sscanf(optarg, "%ul", &value);
//printf("sscanf() = %d, value = 0x%08x\n", rc, (unsigned int)value);
return value;
}
int test_dma(char *device_name, int size, int count);
static int verbosity = 0;
int main(int argc, char *argv[])
{
int cmd_opt;
char *device = "/dev/xdma/card0/h2c0";
uint32_t size = 32768;
uint32_t count = 1;
char *filename = NULL;
while ((cmd_opt = getopt_long(argc, argv, "vhic:d:s:", long_opts, NULL)) != -1)
{
switch (cmd_opt)
{
case 0:
/* long option */
break;
case 'v':
verbosity++;
break;
/* device node name */
case 'd':
printf("'%s'\n", optarg);
device = strdup(optarg);
break;
/* transfer size in bytes */
case 's':
size = getopt_integer(optarg);
break;
/* count */
case 'c':
count = getopt_integer(optarg);
printf(" count = %d\n", count);
break;
/* print usage help and exit */
case 'h':
default:
usage(argv[0]);
exit(0);
break;
}
}
printf("device = %s, size = 0x%08x, count = %u\n", device, size, count);
test_dma(device, size, count);
}
int test_dma(char *device_name, int size, int count)
{
int rc = 0;
int fd = open(device_name, O_RDWR);
if (fd < 0) {
printf("FAILURE: Could not open %s. Make sure xdma device driver is loaded and you have access rights (maybe use sudo?).\n", device_name);
exit(1);
}
unsigned char status = 1;
perf.version = IOCTL_XDMA_PERF_V1;
perf.transfer_size = size;
rc = ioctl(fd, IOCTL_XDMA_PERF_START, &perf);
if (rc == 0) {
printf("IOCTL_XDMA_PERF_START succesful.\n");
} else {
printf("ioctl(..., IOCTL_XDMA_PERF_START) = %d\n", rc);
}
#if 1
while (count--) {
sleep(2);
rc = ioctl(fd, IOCTL_XDMA_PERF_GET, &perf);
if (rc == 0) {
printf("IOCTL_XDMA_PERF_GET succesful.\n");
} else {
printf("ioctl(..., IOCTL_XDMA_PERF_GET) = %d\n", rc);
}
printf("perf.transfer_size = %d\n", perf.transfer_size);
printf("perf.iterations = %d\n", perf.iterations);
printf("(data transferred = %lld bytes)\n", (long long)perf.transfer_size * (long long)perf.iterations);
printf("perf.clock_cycle_count = %lld\n", (long long)perf.clock_cycle_count);
printf("perf.data_cycle_count = %lld\n", (long long)perf.data_cycle_count);
if (perf.clock_cycle_count && perf.data_cycle_count) {
printf("(data duty cycle = %lld%%)\n", (long long)perf.data_cycle_count * 100 / (long long)perf.clock_cycle_count);
}
}
#endif
rc = ioctl(fd, IOCTL_XDMA_PERF_STOP, &perf);
if (rc == 0) {
printf("IOCTL_XDMA_PERF_STOP succesful.\n");
} else {
printf("ioctl(..., IOCTL_XDMA_PERF_STOP) = %d\n", rc);
}
printf("perf.transfer_size = %d bytes\n", perf.transfer_size);
printf("perf.iterations = %d\n", perf.iterations);
printf("(data transferred = %lld bytes)\n", (long long)perf.transfer_size * (long long)perf.iterations);
printf("perf.clock_cycle_count = %lld\n", (long long)perf.clock_cycle_count);
printf("perf.data_cycle_count = %lld\n", (long long)perf.data_cycle_count);
if (perf.clock_cycle_count && perf.data_cycle_count) {
printf("(data duty cycle = %lld%%)\n", (long long)perf.data_cycle_count * 100 / (long long)perf.clock_cycle_count);
printf (" data rate ***** bytes length = %d, rate = %f \n", perf.transfer_size, (double)(long long)perf.data_cycle_count/(long long)perf.clock_cycle_count);
}
printf("perf.pending_count = %lld\n", (long long)perf.pending_count);
close(fd);
}
/*
* This file is part of the Xilinx DMA IP Core driver tools for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is licensed under BSD-style license (found in the
* LICENSE file in the root directory of this source tree)
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <byteswap.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
/* ltoh: little to host */
/* htol: little to host */
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ltohl(x) (x)
#define ltohs(x) (x)
#define htoll(x) (x)
#define htols(x) (x)
#elif __BYTE_ORDER == __BIG_ENDIAN
#define ltohl(x) __bswap_32(x)
#define ltohs(x) __bswap_16(x)
#define htoll(x) __bswap_32(x)
#define htols(x) __bswap_16(x)
#endif
#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)
#define MAP_SIZE (32*1024UL)
#define MAP_MASK (MAP_SIZE - 1)
int main(int argc, char **argv)
{
int fd;
void *map_base, *virt_addr;
uint32_t read_result, writeval;
off_t target;
/* access width */
int access_width = 'w';
char *device;
/* not enough arguments given? */
if (argc < 3) {
fprintf(stderr,
"\nUsage:\t%s <device> <address> [[type] data]\n"
"\tdevice : character device to access\n"
"\taddress : memory address to access\n"
"\ttype : access operation type : [b]yte, [h]alfword, [w]ord\n"
"\tdata : data to be written for a write\n\n",
argv[0]);
exit(1);
}
printf("argc = %d\n", argc);
device = strdup(argv[1]);
printf("device: %s\n", device);
target = strtoul(argv[2], 0, 0);
printf("address: 0x%08x\n", (unsigned int)target);
printf("access type: %s\n", argc >= 4 ? "write" : "read");
/* data given? */
if (argc >= 4) {
printf("access width given.\n");
access_width = tolower(argv[3][0]);
}
printf("access width: ");
if (access_width == 'b')
printf("byte (8-bits)\n");
else if (access_width == 'h')
printf("half word (16-bits)\n");
else if (access_width == 'w')
printf("word (32-bits)\n");
else {
printf("word (32-bits)\n");
access_width = 'w';
}
if ((fd = open(argv[1], O_RDWR | O_SYNC)) == -1)
FATAL;
printf("character device %s opened.\n", argv[1]);
fflush(stdout);
/* map one page */
map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_base == (void *)-1)
FATAL;
printf("Memory mapped at address %p.\n", map_base);
fflush(stdout);
/* calculate the virtual address to be accessed */
virt_addr = map_base + target;
/* read only */
if (argc <= 4) {
//printf("Read from address %p.\n", virt_addr);
switch (access_width) {
case 'b':
read_result = *((uint8_t *) virt_addr);
printf
("Read 8-bits value at address 0x%08x (%p): 0x%02x\n",
(unsigned int)target, virt_addr,
(unsigned int)read_result);
break;
case 'h':
read_result = *((uint16_t *) virt_addr);
/* swap 16-bit endianess if host is not little-endian */
read_result = ltohs(read_result);
printf
("Read 16-bit value at address 0x%08x (%p): 0x%04x\n",
(unsigned int)target, virt_addr,
(unsigned int)read_result);
break;
case 'w':
read_result = *((uint32_t *) virt_addr);
/* swap 32-bit endianess if host is not little-endian */
read_result = ltohl(read_result);
printf
("Read 32-bit value at address 0x%08x (%p): 0x%08x\n",
(unsigned int)target, virt_addr,
(unsigned int)read_result);
return (int)read_result;
break;
default:
fprintf(stderr, "Illegal data type '%c'.\n",
access_width);
exit(2);
}
fflush(stdout);
}
/* data value given, i.e. writing? */
if (argc >= 5) {
writeval = strtoul(argv[4], 0, 0);
switch (access_width) {
case 'b':
printf("Write 8-bits value 0x%02x to 0x%08x (0x%p)\n",
(unsigned int)writeval, (unsigned int)target,
virt_addr);
*((uint8_t *) virt_addr) = writeval;
#if 0
if (argc > 4) {
read_result = *((uint8_t *) virt_addr);
printf("Written 0x%02x; readback 0x%02x\n",
writeval, read_result);
}
#endif
break;
case 'h':
printf("Write 16-bits value 0x%04x to 0x%08x (0x%p)\n",
(unsigned int)writeval, (unsigned int)target,
virt_addr);
/* swap 16-bit endianess if host is not little-endian */
writeval = htols(writeval);
*((uint16_t *) virt_addr) = writeval;
#if 0
if (argc > 4) {
read_result = *((uint16_t *) virt_addr);
printf("Written 0x%04x; readback 0x%04x\n",
writeval, read_result);
}
#endif
break;
case 'w':
printf("Write 32-bits value 0x%08x to 0x%08x (0x%p)\n",
(unsigned int)writeval, (unsigned int)target,
virt_addr);
/* swap 32-bit endianess if host is not little-endian */
writeval = htoll(writeval);
*((uint32_t *) virt_addr) = writeval;
#if 0
if (argc > 4) {
read_result = *((uint32_t *) virt_addr);
printf("Written 0x%08x; readback 0x%08x\n",
writeval, read_result);
}
#endif
break;
}
fflush(stdout);
}
if (munmap(map_base, MAP_SIZE) == -1)
FATAL;
close(fd);
return 0;
}
SHELL = /bin/bash
ifneq ($(xvc_bar_num),)
XVC_FLAGS += -D__XVC_BAR_NUM__=$(xvc_bar_num)
endif
ifneq ($(xvc_bar_offset),)
XVC_FLAGS += -D__XVC_BAR_OFFSET__=$(xvc_bar_offset)
endif
$(warning XVC_FLAGS: $(XVC_FLAGS).)
topdir := $(shell cd $(src)/.. && pwd)
TARGET_MODULE:=xdma
EXTRA_CFLAGS := -I$(topdir)/include $(XVC_FLAGS)
#EXTRA_CFLAGS += -D__LIBXDMA_DEBUG__
#EXTRA_CFLAGS += -DINTERNAL_TESTING
ifneq ($(KERNELRELEASE),)
$(TARGET_MODULE)-objs := libxdma.o xdma_cdev.o cdev_ctrl.o cdev_events.o cdev_sgdma.o cdev_xvc.o cdev_bypass.o xdma_mod.o
obj-m := $(TARGET_MODULE).o
else
BUILDSYSTEM_DIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
all :
$(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) modules
clean:
$(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) clean
install: all
$(MAKE) -C $(BUILDSYSTEM_DIR) M=$(PWD) modules_install
endif
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#include "libxdma_api.h"
#include "xdma_cdev.h"
#define write_register(v,mem,off) iowrite32(v, mem)
static int copy_desc_data(struct xdma_transfer *transfer, char __user *buf,
size_t *buf_offset, size_t buf_size)
{
int i;
int copy_err;
int rc = 0;
BUG_ON(!buf);
BUG_ON(!buf_offset);
/* Fill user buffer with descriptor data */
for (i = 0; i < transfer->desc_num; i++) {
if (*buf_offset + sizeof(struct xdma_desc) <= buf_size) {
copy_err = copy_to_user(&buf[*buf_offset],
transfer->desc_virt + i,
sizeof(struct xdma_desc));
if (copy_err) {
dbg_sg("Copy to user buffer failed\n");
*buf_offset = buf_size;
rc = -EINVAL;
} else {
*buf_offset += sizeof(struct xdma_desc);
}
} else {
rc = -ENOMEM;
}
}
return rc;
}
static ssize_t char_bypass_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
struct xdma_dev *xdev;
struct xdma_engine *engine;
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
struct xdma_transfer *transfer;
struct list_head *idx;
size_t buf_offset = 0;
int rc = 0;
rc = xcdev_check(__func__, xcdev, 1);
if (rc < 0)
return rc;
xdev = xcdev->xdev;
engine = xcdev->engine;
dbg_sg("In char_bypass_read()\n");
if (count & 3) {
dbg_sg("Buffer size must be a multiple of 4 bytes\n");
return -EINVAL;
}
if (!buf) {
dbg_sg("Caught NULL pointer\n");
return -EINVAL;
}
if (xdev->bypass_bar_idx < 0) {
dbg_sg("Bypass BAR not present - unsupported operation\n");
return -ENODEV;
}
spin_lock(&engine->lock);
if (!list_empty(&engine->transfer_list)) {
list_for_each(idx, &engine->transfer_list) {
transfer = list_entry(idx, struct xdma_transfer, entry);
rc = copy_desc_data(transfer, buf, &buf_offset, count);
}
}
spin_unlock(&engine->lock);
if (rc < 0)
return rc;
else
return buf_offset;
}
static ssize_t char_bypass_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos)
{
struct xdma_dev *xdev;
struct xdma_engine *engine;
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
u32 desc_data;
u32 *bypass_addr;
size_t buf_offset = 0;
int rc = 0;
int copy_err;
rc = xcdev_check(__func__, xcdev, 1);
if (rc < 0)
return rc;
xdev = xcdev->xdev;
engine = xcdev->engine;
if (count & 3) {
dbg_sg("Buffer size must be a multiple of 4 bytes\n");
return -EINVAL;
}
if (!buf) {
dbg_sg("Caught NULL pointer\n");
return -EINVAL;
}
if (xdev->bypass_bar_idx < 0) {
dbg_sg("Bypass BAR not present - unsupported operation\n");
return -ENODEV;
}
dbg_sg("In char_bypass_write()\n");
spin_lock(&engine->lock);
/* Write descriptor data to the bypass BAR */
bypass_addr = (u32 *)xdev->bar[xdev->bypass_bar_idx];
bypass_addr += engine->bypass_offset;
while (buf_offset < count) {
copy_err = copy_from_user(&desc_data, &buf[buf_offset],
sizeof(u32));
if (!copy_err) {
write_register(desc_data, bypass_addr, bypass_addr - engine->bypass_offset);
buf_offset += sizeof(u32);
rc = buf_offset;
} else {
dbg_sg("Error reading data from userspace buffer\n");
rc = -EINVAL;
break;
}
}
spin_unlock(&engine->lock);
return rc;
}
/*
* character device file operations for bypass operation
*/
static const struct file_operations bypass_fops = {
.owner = THIS_MODULE,
.open = char_open,
.release = char_close,
.read = char_bypass_read,
.write = char_bypass_write,
.mmap = bridge_mmap,
};
void cdev_bypass_init(struct xdma_cdev *xcdev)
{
cdev_init(&xcdev->cdev, &bypass_fops);
}
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include <linux/ioctl.h>
#include "version.h"
#include "xdma_cdev.h"
#include "cdev_ctrl.h"
/*
* character device file operations for control bus (through control bridge)
*/
static ssize_t char_ctrl_read(struct file *fp, char __user *buf, size_t count,
loff_t *pos)
{
struct xdma_cdev *xcdev = (struct xdma_cdev *)fp->private_data;
struct xdma_dev *xdev;
void *reg;
u32 w;
int rv;
rv = xcdev_check(__func__, xcdev, 0);
if (rv < 0)
return rv;
xdev = xcdev->xdev;
/* only 32-bit aligned and 32-bit multiples */
if (*pos & 3)
return -EPROTO;
/* first address is BAR base plus file position offset */
reg = xdev->bar[xcdev->bar] + *pos;
//w = read_register(reg);
w = ioread32(reg);
dbg_sg("char_ctrl_read(@%p, count=%ld, pos=%d) value = 0x%08x\n", reg,
(long)count, (int)*pos, w);
rv = copy_to_user(buf, &w, 4);
if (rv)
dbg_sg("Copy to userspace failed but continuing\n");
*pos += 4;
return 4;
}
static ssize_t char_ctrl_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos)
{
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
struct xdma_dev *xdev;
void *reg;
u32 w;
int rv;
rv = xcdev_check(__func__, xcdev, 0);
if (rv < 0)
return rv;
xdev = xcdev->xdev;
/* only 32-bit aligned and 32-bit multiples */
if (*pos & 3)
return -EPROTO;
/* first address is BAR base plus file position offset */
reg = xdev->bar[xcdev->bar] + *pos;
rv = copy_from_user(&w, buf, 4);
if (rv) {
pr_info("copy from user failed %d/4, but continuing.\n", rv);
}
dbg_sg("char_ctrl_write(0x%08x @%p, count=%ld, pos=%d)\n", w, reg,
(long)count, (int)*pos);
//write_register(w, reg);
iowrite32(w, reg);
*pos += 4;
return 4;
}
static long version_ioctl(struct xdma_cdev *xcdev, void __user *arg)
{
struct xdma_ioc_info obj;
struct xdma_dev *xdev = xcdev->xdev;
int rv;
rv = copy_from_user((void *)&obj, arg, sizeof(struct xdma_ioc_info));
if (rv) {
pr_info("copy from user failed %d/%ld.\n",
rv, sizeof(struct xdma_ioc_info));
return -EFAULT;
}
memset(&obj, 0, sizeof(obj));
obj.vendor = xdev->pdev->vendor;
obj.device = xdev->pdev->device;
obj.subsystem_vendor = xdev->pdev->subsystem_vendor;
obj.subsystem_device = xdev->pdev->subsystem_device;
obj.feature_id = xdev->feature_id;
obj.driver_version = DRV_MOD_VERSION_NUMBER;
obj.domain = 0;
obj.bus = PCI_BUS_NUM(xdev->pdev->devfn);
obj.dev = PCI_SLOT(xdev->pdev->devfn);
obj.func = PCI_FUNC(xdev->pdev->devfn);
if (copy_to_user(arg, &obj, sizeof(struct xdma_ioc_info)))
return -EFAULT;
return 0;
}
long char_ctrl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct xdma_cdev *xcdev = (struct xdma_cdev *)filp->private_data;
struct xdma_dev *xdev;
struct xdma_ioc_base ioctl_obj;
long result = 0;
int rv;
rv = xcdev_check(__func__, xcdev, 0);
if (rv < 0)
return rv;
xdev = xcdev->xdev;
if (!xdev) {
pr_info("cmd %u, xdev NULL.\n", cmd);
return -EINVAL;
}
pr_info("cmd 0x%x, xdev 0x%p, pdev 0x%p.\n", cmd, xdev, xdev->pdev);
if (_IOC_TYPE(cmd) != XDMA_IOC_MAGIC) {
pr_err("cmd %u, bad magic 0x%x/0x%x.\n",
cmd, _IOC_TYPE(cmd), XDMA_IOC_MAGIC);
return -ENOTTY;
}
if (_IOC_DIR(cmd) & _IOC_READ)
result = !access_ok(VERIFY_WRITE, (void __user *)arg,
_IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
result = !access_ok(VERIFY_READ, (void __user *)arg,
_IOC_SIZE(cmd));
if (result) {
pr_err("bad access %ld.\n", result);
return -EFAULT;
}
switch (cmd) {
case XDMA_IOCINFO:
if (copy_from_user((void *)&ioctl_obj, (void *) arg,
sizeof(struct xdma_ioc_base))) {
pr_err("copy_from_user failed.\n");
return -EFAULT;
}
if (ioctl_obj.magic != XDMA_XCL_MAGIC) {
pr_err("magic 0x%x != XDMA_XCL_MAGIC (0x%x).\n",
ioctl_obj.magic, XDMA_XCL_MAGIC);
return -ENOTTY;
}
return version_ioctl(xcdev, (void __user *)arg);
case XDMA_IOCOFFLINE:
xdma_device_offline(xdev->pdev, xdev);
break;
case XDMA_IOCONLINE:
xdma_device_online(xdev->pdev, xdev);
break;
default:
pr_err("UNKNOWN ioctl cmd 0x%x.\n", cmd);
return -ENOTTY;
}
return 0;
}
/* maps the PCIe BAR into user space for memory-like access using mmap() */
int bridge_mmap(struct file *file, struct vm_area_struct *vma)
{
struct xdma_dev *xdev;
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
unsigned long off;
unsigned long phys;
unsigned long vsize;
unsigned long psize;
int rv;
rv = xcdev_check(__func__, xcdev, 0);
if (rv < 0)
return rv;
xdev = xcdev->xdev;
off = vma->vm_pgoff << PAGE_SHIFT;
/* BAR physical address */
phys = pci_resource_start(xdev->pdev, xcdev->bar) + off;
vsize = vma->vm_end - vma->vm_start;
/* complete resource */
psize = pci_resource_end(xdev->pdev, xcdev->bar) -
pci_resource_start(xdev->pdev, xcdev->bar) + 1 - off;
dbg_sg("mmap(): xcdev = 0x%08lx\n", (unsigned long)xcdev);
dbg_sg("mmap(): cdev->bar = %d\n", xcdev->bar);
dbg_sg("mmap(): xdev = 0x%p\n", xdev);
dbg_sg("mmap(): pci_dev = 0x%08lx\n", (unsigned long)xdev->pdev);
dbg_sg("off = 0x%lx\n", off);
dbg_sg("start = 0x%llx\n",
(unsigned long long)pci_resource_start(xdev->pdev,
xcdev->bar));
dbg_sg("phys = 0x%lx\n", phys);
if (vsize > psize)
return -EINVAL;
/*
* pages must not be cached as this would result in cache line sized
* accesses to the end point
*/
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/*
* prevent touching the pages (byte access) for swap-in,
* and prevent the pages from being swapped out
*/
vma->vm_flags |= VMEM_FLAGS;
/* make MMIO accessible to user space */
rv = io_remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT,
vsize, vma->vm_page_prot);
dbg_sg("vma=0x%p, vma->vm_start=0x%lx, phys=0x%lx, size=%lu = %d\n",
vma, vma->vm_start, phys >> PAGE_SHIFT, vsize, rv);
if (rv)
return -EAGAIN;
return 0;
}
/*
* character device file operations for control bus (through control bridge)
*/
static const struct file_operations ctrl_fops = {
.owner = THIS_MODULE,
.open = char_open,
.release = char_close,
.read = char_ctrl_read,
.write = char_ctrl_write,
.mmap = bridge_mmap,
.unlocked_ioctl = char_ctrl_ioctl,
};
void cdev_ctrl_init(struct xdma_cdev *xcdev)
{
cdev_init(&xcdev->cdev, &ctrl_fops);
}
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#ifndef _XDMA_IOCALLS_POSIX_H_
#define _XDMA_IOCALLS_POSIX_H_
#include <linux/ioctl.h>
/* Use 'x' as magic number */
#define XDMA_IOC_MAGIC 'x'
/* XL OpenCL X->58(ASCII), L->6C(ASCII), O->0 C->C L->6C(ASCII); */
#define XDMA_XCL_MAGIC 0X586C0C6C
/*
* S means "Set" through a ptr,
* T means "Tell" directly with the argument value
* G means "Get": reply by setting through a pointer
* Q means "Query": response is on the return value
* X means "eXchange": switch G and S atomically
* H means "sHift": switch T and Q atomically
*
* _IO(type,nr) no arguments
* _IOR(type,nr,datatype) read data from driver
* _IOW(type,nr.datatype) write data to driver
* _IORW(type,nr,datatype) read/write data
*
* _IOC_DIR(nr) returns direction
* _IOC_TYPE(nr) returns magic
* _IOC_NR(nr) returns number
* _IOC_SIZE(nr) returns size
*/
enum XDMA_IOC_TYPES {
XDMA_IOC_NOP,
XDMA_IOC_INFO,
XDMA_IOC_OFFLINE,
XDMA_IOC_ONLINE,
XDMA_IOC_MAX
};
struct xdma_ioc_base {
unsigned int magic;
unsigned int command;
};
struct xdma_ioc_info {
struct xdma_ioc_base base;
unsigned short vendor;
unsigned short device;
unsigned short subsystem_vendor;
unsigned short subsystem_device;
unsigned int dma_engine_version;
unsigned int driver_version;
unsigned long long feature_id;
unsigned short domain;
unsigned char bus;
unsigned char dev;
unsigned char func;
};
/* IOCTL codes */
#define XDMA_IOCINFO _IOWR(XDMA_IOC_MAGIC, XDMA_IOC_INFO, \
struct xdma_ioc_info)
#define XDMA_IOCOFFLINE _IO(XDMA_IOC_MAGIC, XDMA_IOC_OFFLINE)
#define XDMA_IOCONLINE _IO(XDMA_IOC_MAGIC, XDMA_IOC_ONLINE)
#define IOCTL_XDMA_ADDRMODE_SET _IOW('q', 4, int)
#define IOCTL_XDMA_ADDRMODE_GET _IOR('q', 5, int)
#define IOCTL_XDMA_ALIGN_GET _IOR('q', 6, int)
#endif /* _XDMA_IOCALLS_POSIX_H_ */
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include "xdma_cdev.h"
/*
* character device file operations for events
*/
static ssize_t char_events_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
int rv;
struct xdma_user_irq *user_irq;
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
u32 events_user;
unsigned long flags;
rv = xcdev_check(__func__, xcdev, 0);
if (rv < 0)
return rv;
user_irq = xcdev->user_irq;
if (!user_irq) {
pr_info("xcdev 0x%p, user_irq NULL.\n", xcdev);
return -EINVAL;
}
if (count != 4)
return -EPROTO;
if (*pos & 3)
return -EPROTO;
/*
* sleep until any interrupt events have occurred,
* or a signal arrived
*/
rv = wait_event_interruptible(user_irq->events_wq,
user_irq->events_irq != 0);
if (rv)
dbg_sg("wait_event_interruptible=%d\n", rv);
/* wait_event_interruptible() was interrupted by a signal */
if (rv == -ERESTARTSYS)
return -ERESTARTSYS;
/* atomically decide which events are passed to the user */
spin_lock_irqsave(&user_irq->events_lock, flags);
events_user = user_irq->events_irq;
user_irq->events_irq = 0;
spin_unlock_irqrestore(&user_irq->events_lock, flags);
rv = copy_to_user(buf, &events_user, 4);
if (rv)
dbg_sg("Copy to user failed but continuing\n");
return 4;
}
static unsigned int char_events_poll(struct file *file, poll_table *wait)
{
struct xdma_user_irq *user_irq;
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
unsigned long flags;
unsigned int mask = 0;
int rv;
rv = xcdev_check(__func__, xcdev, 0);
if (rv < 0)
return rv;
user_irq = xcdev->user_irq;
if (!user_irq) {
pr_info("xcdev 0x%p, user_irq NULL.\n", xcdev);
return -EINVAL;
}
poll_wait(file, &user_irq->events_wq, wait);
spin_lock_irqsave(&user_irq->events_lock, flags);
if (user_irq->events_irq)
mask = POLLIN | POLLRDNORM; /* readable */
spin_unlock_irqrestore(&user_irq->events_lock, flags);
return mask;
}
/*
* character device file operations for the irq events
*/
static const struct file_operations events_fops = {
.owner = THIS_MODULE,
.open = char_open,
.release = char_close,
.read = char_events_read,
.poll = char_events_poll,
};
void cdev_event_init(struct xdma_cdev *xcdev)
{
xcdev->user_irq = &(xcdev->xdev->user_irq[xcdev->bar]);
cdev_init(&xcdev->cdev, &events_fops);
}
This diff is collapsed.
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#ifndef _XDMA_IOCALLS_POSIX_H_
#define _XDMA_IOCALLS_POSIX_H_
#include <linux/ioctl.h>
#define IOCTL_XDMA_PERF_V1 (1)
#define XDMA_ADDRMODE_MEMORY (0)
#define XDMA_ADDRMODE_FIXED (1)
/*
* S means "Set" through a ptr,
* T means "Tell" directly with the argument value
* G means "Get": reply by setting through a pointer
* Q means "Query": response is on the return value
* X means "eXchange": switch G and S atomically
* H means "sHift": switch T and Q atomically
*
* _IO(type,nr) no arguments
* _IOR(type,nr,datatype) read data from driver
* _IOW(type,nr.datatype) write data to driver
* _IORW(type,nr,datatype) read/write data
*
* _IOC_DIR(nr) returns direction
* _IOC_TYPE(nr) returns magic
* _IOC_NR(nr) returns number
* _IOC_SIZE(nr) returns size
*/
struct xdma_performance_ioctl
{
/* IOCTL_XDMA_IOCTL_Vx */
uint32_t version;
uint32_t transfer_size;
/* measurement */
uint32_t stopped;
uint32_t iterations;
uint64_t clock_cycle_count;
uint64_t data_cycle_count;
uint64_t pending_count;
};
/* IOCTL codes */
#define IOCTL_XDMA_PERF_START _IOW('q', 1, struct xdma_performance_ioctl *)
#define IOCTL_XDMA_PERF_STOP _IOW('q', 2, struct xdma_performance_ioctl *)
#define IOCTL_XDMA_PERF_GET _IOR('q', 3, struct xdma_performance_ioctl *)
#define IOCTL_XDMA_ADDRMODE_SET _IOW('q', 4, int)
#define IOCTL_XDMA_ADDRMODE_GET _IOR('q', 5, int)
#define IOCTL_XDMA_ALIGN_GET _IOR('q', 6, int)
#endif /* _XDMA_IOCALLS_POSIX_H_ */
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__
#include "xdma_cdev.h"
#include "cdev_xvc.h"
#define COMPLETION_LOOP_MAX 100
#define XVC_BAR_LENGTH_REG 0x0
#define XVC_BAR_TMS_REG 0x4
#define XVC_BAR_TDI_REG 0x8
#define XVC_BAR_TDO_REG 0xC
#define XVC_BAR_CTRL_REG 0x10
#ifdef __REG_DEBUG__
/* SECTION: Function definitions */
inline void __write_register(const char *fn, u32 value, void *base,
unsigned int off)
{
pr_info("%s: 0x%p, W reg 0x%lx, 0x%x.\n", fn, base, off, value);
iowrite32(value, base + off);
}
inline u32 __read_register(const char *fn, void *base, unsigned int off)
{
u32 v = ioread32(base + off);
pr_info("%s: 0x%p, R reg 0x%lx, 0x%x.\n", fn, base, off, v);
return v;
}
#define write_register(v,base,off) __write_register(__func__, v, base, off)
#define read_register(base,off) __read_register(__func__, base, off)
#else
#define write_register(v,base,off) iowrite32(v, (base) + (off))
#define read_register(base,off) ioread32((base) + (off))
#endif /* #ifdef __REG_DEBUG__ */
static int xvc_shift_bits(void *base, u32 tms_bits, u32 tdi_bits,
u32 *tdo_bits)
{
u32 control;
int count;
/* set tms bit */
write_register(tms_bits, base, XVC_BAR_TMS_REG);
/* set tdi bits and shift data out */
write_register(tdi_bits, base, XVC_BAR_TDI_REG);
/* enable shift operation */
write_register(0x1, base, XVC_BAR_CTRL_REG);
/* poll for completion */
count = COMPLETION_LOOP_MAX;
while (count) {
/* read control reg to check shift operation completion */
control = read_register(base, XVC_BAR_CTRL_REG);
if ((control & 0x01) == 0)
break;
count--;
}
if (!count) {
pr_warn("XVC bar transaction timed out (0x%0X)\n", control);
return -ETIMEDOUT;
}
/* read tdo bits back out */
*tdo_bits = read_register(base, XVC_BAR_TDO_REG);
return 0;
}
static long xvc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct xdma_cdev *xcdev = (struct xdma_cdev *)filp->private_data;
struct xdma_dev *xdev;
struct xvc_ioc xvc_obj;
unsigned int opcode;
unsigned int total_bits;
unsigned int total_bytes;
unsigned char *buffer = NULL;
unsigned char *tms_buf = NULL;
unsigned char *tdi_buf = NULL;
unsigned char *tdo_buf = NULL;
unsigned int bits, bits_left;
void __iomem *iobase;
int rv;
rv = xcdev_check(__func__, xcdev, 0);
if (rv < 0)
return rv;
xdev = xcdev->xdev;
if (cmd != XDMA_IOCXVC) {
pr_info("ioctl 0x%x, UNKNOWN cmd.\n", cmd);
return -ENOIOCTLCMD;
}
rv = copy_from_user((void *)&xvc_obj, (void __user *)arg,
sizeof(struct xvc_ioc));
/* anything not copied ? */
if (rv) {
pr_info("copy_from_user xvc_obj failed: %d.\n", rv);
goto cleanup;
}
opcode = xvc_obj.opcode;
/* Invalid operation type, no operation performed */
if (opcode != 0x01 && opcode != 0x02) {
pr_info("UNKNOWN opcode 0x%x.\n", opcode);
return -EINVAL;
}
total_bits = xvc_obj.length;
total_bytes = (total_bits + 7) >> 3;
buffer = (char *)kmalloc(total_bytes * 3, GFP_KERNEL);
if (!buffer) {
pr_info("OOM %u, op 0x%x, len %u bits, %u bytes.\n",
3 * total_bytes, opcode, total_bits, total_bytes);
rv = -ENOMEM;
goto cleanup;
}
tms_buf = buffer;
tdi_buf = tms_buf + total_bytes;
tdo_buf = tdi_buf + total_bytes;
rv = copy_from_user((void *)tms_buf, xvc_obj.tms_buf, total_bytes);
if (rv) {
pr_info("copy tmfs_buf failed: %d/%u.\n", rv, total_bytes);
goto cleanup;
}
rv = copy_from_user((void *)tdi_buf, xvc_obj.tdi_buf, total_bytes);
if (rv) {
pr_info("copy tdi_buf failed: %d/%u.\n", rv, total_bytes);
goto cleanup;
}
/* exclusive access */
spin_lock(&xcdev->lock);
iobase = xdev->bar[xcdev->bar] + xcdev->base;
/* set length register to 32 initially if more than one
* word-transaction is to be done */
if (total_bits >= 32)
write_register(0x20, iobase, XVC_BAR_LENGTH_REG);
for (bits = 0, bits_left = total_bits; bits < total_bits; bits += 32,
bits_left -= 32) {
unsigned int bytes = bits >> 3;
unsigned int shift_bytes = 4;
u32 tms_store = 0;
u32 tdi_store = 0;
u32 tdo_store = 0;
if (bits_left < 32) {
/* set number of bits to shift out */
write_register(bits_left, iobase, XVC_BAR_LENGTH_REG);
shift_bytes = (bits_left + 7) >> 3;
}
memcpy(&tms_store, tms_buf + bytes, shift_bytes);
memcpy(&tdi_store, tdi_buf + bytes, shift_bytes);
/* Shift data out and copy to output buffer */
rv = xvc_shift_bits(iobase, tms_store, tdi_store, &tdo_store);
if (rv < 0)
goto cleanup;
memcpy(tdo_buf + bytes, &tdo_store, shift_bytes);
}
/* if testing bar access swap tdi and tdo bufferes to "loopback" */
if (opcode == 0x2) {
char *tmp = tdo_buf;
tdo_buf = tdi_buf;
tdi_buf = tmp;
}
rv = copy_to_user((void *)xvc_obj.tdo_buf, tdo_buf, total_bytes);
if (rv) {
pr_info("copy back tdo_buf failed: %d/%u.\n", rv, total_bytes);
rv = -EFAULT;
goto cleanup;
}
cleanup:
if (buffer)
kfree(buffer);
mmiowb();
spin_unlock(&xcdev->lock);
return rv;
}
/*
* character device file operations for the XVC
*/
static const struct file_operations xvc_fops = {
.owner = THIS_MODULE,
.open = char_open,
.release = char_close,
.unlocked_ioctl = xvc_ioctl,
};
void cdev_xvc_init(struct xdma_cdev *xcdev)
{
#ifdef __XVC_BAR_NUM__
xcdev->bar = __XVC_BAR_NUM__;
#endif
#ifdef __XVC_BAR_OFFSET__
xcdev->base = __XVC_BAR_OFFSET__;
#else
xcdev->base = XVC_BAR_OFFSET_DFLT;
#endif
pr_info("xcdev 0x%p, bar %u, offset 0x%lx.\n",
xcdev, xcdev->bar, xcdev->base);
cdev_init(&xcdev->cdev, &xvc_fops);
}
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#ifndef __XVC_IOCTL_H__
#define __XVC_IOCTL_H__
#include <linux/ioctl.h>
/*
* !!! TODO !!!
* need a better way set the bar offset dynamicly
*/
#define XVC_BAR_OFFSET_DFLT 0x40000 /* DSA 4.0 */
#define XVC_MAGIC 0x58564344 // "XVCD"
struct xvc_ioc {
unsigned int opcode;
unsigned int length;
unsigned char *tms_buf;
unsigned char *tdi_buf;
unsigned char *tdo_buf;
};
#define XDMA_IOCXVC _IOWR(XVC_MAGIC, 1, struct xvc_ioc)
#endif /* __XVC_IOCTL_H__ */
../libxdma/libxdma.c
\ No newline at end of file
../libxdma/libxdma.h
\ No newline at end of file
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2016-present, Xilinx, Inc.
* All rights reserved.
*
* This source code is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* 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.
*
* The full GNU General Public License is included in this distribution in
* the file called "COPYING".
*/
#ifndef __XDMA_VERSION_H__
#define __XDMA_VERSION_H__
#define DRV_MOD_MAJOR 2018
#define DRV_MOD_MINOR 3
#define DRV_MOD_PATCHLEVEL 50
#define DRV_MODULE_VERSION \
__stringify(DRV_MOD_MAJOR) "." \
__stringify(DRV_MOD_MINOR) "." \
__stringify(DRV_MOD_PATCHLEVEL)
#define DRV_MOD_VERSION_NUMBER \
((DRV_MOD_MAJOR)*1000 + (DRV_MOD_MINOR)*100 + DRV_MOD_PATCHLEVEL)
#endif /* ifndef __XDMA_VERSION_H__ */
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment