Add xdma/linux-kernel.

This commit is contained in:
Colin 2025-05-02 19:01:03 +08:00
parent 16de3ee458
commit 18096812ca
98 changed files with 27130 additions and 5 deletions

View File

@ -1,6 +1,7 @@
module fpga_top (
output wire o_led0,
output wire o_led_blink,
input wire i_pcie_rstn,
input wire i_pcie_refclkp, i_pcie_refclkn,
input wire [0:0] i_pcie_rxp, i_pcie_rxn,
@ -12,6 +13,11 @@ localparam AXI_IDWIDTH = 4;
localparam AXI_AWIDTH = 64;
localparam AXI_DWIDTH = 64;
reg [31:0] delay_cnt;
always@(posedge clk) begin
delay_cnt <= delay_cnt + 1'b1;
end
assign o_led_blink = delay_cnt[26];
wire pcie_rstn;
wire pcie_refclk;
@ -20,7 +26,6 @@ wire rstn;
wire clk;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// PCIe XDMA's AXI interface
///////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -4,8 +4,8 @@
1. 执行安装驱动的脚本要sudo用户不能用sudo命令
2. 安装完成驱动可能要重启电脑
3. 直接插在PCIEx16槽上不行使用x1转接线可以pass test case
1. 可能是采用转接板重启电脑PCIE不断电
1. 确保主板完全断电,把 FPGA 插到该 Linux Host-PC 的 PCIe 插槽中。
@ -20,6 +20,9 @@
cd xdma && sudo make # 编译驱动
./dma_ip_drivers/XDMA/linux-kernel/tests/load_driver.sh # 安装驱动
./dma_ip_drivers/XDMA/linux-kernel/tests/run_test.sh # 跑测试
modinfo xdma # 查看驱动安装情况
ls /dev/xdma*
rmmod -s xdma # 卸载驱动

View File

@ -1,6 +1,7 @@
set_property -dict { PACKAGE_PIN P30 IOSTANDARD LVCMOS18 } [get_ports { o_led0 }];
#set_property -dict { PACKAGE_PIN AR23 IOSTANDARD LVCMOS15 } [get_ports { o_led1 }];
set_property -dict { PACKAGE_PIN M30 IOSTANDARD LVCMOS18 } [get_ports { o_led_blink }];
set_property -dict { PACKAGE_PIN Y26 IOSTANDARD LVCMOS33 PULLUP true } [get_ports i_pcie_rstn]

340
xdma/linux-kernel/COPYING Normal file
View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

30
xdma/linux-kernel/LICENSE Normal file
View File

@ -0,0 +1,30 @@
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.

43
xdma/linux-kernel/RELEASE Normal file
View File

@ -0,0 +1,43 @@
v2024.1.0
========
- added support for AlmaOS 9.4 and Ubuntu 24.04
v2020.2.0
=========
- renamed module parameter "enable_credit_mp" to "enable_st_c2h_credit" to
accurately describe its usage.
- turn off the ST C2H credit as default (i.e., enable_st_c2h_credit = 0)
- fixed c2h streaming credit issue when data buffers uses >= 1K descriptors.
v2020.1.08
===============
- replaced module parameter "sgdma_timeout" to "h2c_timeout" and "c2h_timeout"
for H2C and C2H channels.
value of 0 means no timeout: wait forever for the dma completion.
- added new "-e" option to dma_from_device
this is for streaming mode only, when -e is set, the driver will end the dma
and return the data when an EOP (end-of-packet) is received or the
specified bytes of data is received.
without "-e" option, the driver will end the dma when the specified bytes of
data is received.
- added gen4 device ids
- fixed next adjacent descriptors when dma_alloc_coherent doesn't return a
page-aligned address
v2020.1.06
===============
- added memory aperture support (-k) option in dma_from_device and dma_to_device.
- fixed holding spinlock while doing wait_event_interruptible_xxx
- kernel 5.0 support
- fixed next adjacent descriptors crossing the 4K boundary
Release: 2019.2
===============
Change list:
- Updated data rate for performance run. Now it will print data rate based on the size.
- remove BUG_ON, return proper error code instead
- Streaming mode: enable credit mechanism by default
- Streaming mode: Do not read more than user supplied buffer size on C2H
- Streaming mode: Added support for Async-IO for both streaming and MM transfers
- fixed performance appliaction crash

View File

@ -0,0 +1,150 @@
/*
* 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);
ssize_t xdma_xfer_submit_nowait(void *cb_hndl, void *dev_hndl, int channel, bool write, u64 ep_addr,
struct sg_table *sgt, bool dma_mapped, int timeout_ms);
ssize_t xdma_xfer_completion(void *cb_hndl, 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

View File

@ -0,0 +1,178 @@
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.
Operating System Support:
=========================
Operating System Architecture: x86_64
Linux kernel: 3.10, 4.18.0, 5.14.0, 5.15, 6.8
RHEL/CentOS: 7.6, 7.7, 7.8, 7.9,
8.1, 8.2
ALMA OS: 8.10, 9.4
Ubuntu: 16.04.5 LTS, 16.04.6 LTS
18.04.1 LTS, 18.04.2 LTS, 18.04.4 LTS3, 18.04.5 LTS
20.04 LTS, 20.04.1 LTS, 22.04 LTS, 24.04 LTS
HW Requirement:
===============
The PCIe DMA supports UltraScale+, UltraScale, Virtex-7 XT and 7 Series Gen2
devices.
Please refer to the Xilinx documentation "PG195 DMA/Bridge Subsystem for PCI
Express" for details of the IP.
Directory and file description:
===============================
- xdma/: This directory contains the Xilinx PCIe DMA kernel module
driver files.
- include/: This directory contains all include files that are needed for
compiling driver.
- 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:
- dma_memory_mapped_test.sh, dma_streaming_test.sh:
- data/:
run_test.sh runs sample tests on a Xilinx PCIe DMA target and
returns a pass (0) or fail (1) result.
This script calls 2 other scripts in the same directory:
- dma_memory_mapped_test.sh for memory-mapped dma mode
- dma_streaming_test.sh for streaming dma mode
The data/ directory contains binary data files that are used
for DMA data transfers to the Xilinx FPGA PCIe endpoint device
with the run_test.sh.
!NOTE!:
=======
These scripts are intended for use with the PCIe DMA EXAMPLE
DESIGN ONLY.
- 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.
- scripts_mm/
This directory contains a set of scripts to check basic driver
loading/unloading and perform dma operations in memory-mapped
mode.
Compare with dma_memory_mapped_test.sh, the test is more
extensive with more dma size and it also utilizes fio tool in
addition to dma_from/to_device tools.
- xdma_mm.sh
top level script.
- io_sweep.sh, io.sh, unaligned
dma test via dma_from/to_device
- fio_test.sh fio_parse_result.sh
dma test via fio tool
- scripts_mm/ dependency
Some test in script_mm/ requires fio tool and python extension
Install fio:
- Centos/RHEL: yum install fio
- Ubuntu: apt install fio
Install python extension openpyxl, xlrd(version 1.2.0)
python --version
pip2 install openpyxl
pip2 install xlrd=1.2.0
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
- 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
For more extensive memory mapped test:
assume the XDMA FGPA is at pci slot 0000:01:00.0
cd scripts_mm
./xdma_mm.sh 0000:01:00.0 | tee /tmp/xdma_mm.log
- 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
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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,115 @@
#!/bin/bash
display_help() {
echo "$0 <xdma id> <io size> <io count> <h2c #> <c2h #>"
echo -e "xdma id:\txdma[N] "
echo -e "io size:\tdma transfer size in byte"
echo -e "io count:\tdma transfer count"
echo -e "h2c #:\tnumber of h2c channels"
echo -e "c2h #:\tnumber of c2h channels"
echo
exit 1
}
if [ $# -eq 0 ]; then
display_help
fi
xid=$1
transferSz=$2
transferCount=$3
h2cChannels=$4
c2hChannels=$5
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 -e "\ttransfer size: $transferSz, count: $transferCount"
# Write to all enabled h2cChannels in parallel
if [ $h2cChannels -gt 0 ]; then
# Loop over four blocks of size $transferSz and write to them
for ((i=0; i<=3; i++)); do
addrOffset=$(($transferSz * $i))
curChannel=$(($i % $h2cChannels))
echo "Info: Writing to h2c channel $curChannel at address" \
"offset $addrOffset."
$tool_path/dma_to_device -d /dev/${xid}_h2c_${curChannel} \
-f data/datafile${i}_4K.bin -s $transferSz \
-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 $transferSz and read from them
for ((i=0; i<=3; i++)); do
addrOffset=$(($transferSz * $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/${xid}_c2h_${curChannel} \
-f data/output_datafile${i}_4K.bin -s $transferSz \
-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 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 $transferSz
returnVal=$?
if [ ! $returnVal == 0 ]; then
echo "Error: The data written did not match the data" \
" that was read."
echo -e "\taddress range: " \
"$(($i*$transferSz)) - $((($i+1)*$transferSz))"
echo -e "\twrite data file: data/datafile${i}_4K.bin"
echo -e "\tread data file: data/output_datafile${i}_4K.bin"
testError=1
else
echo "Info: Data check passed for address range " \
"$(($i*$transferSz)) - $((($i+1)*$transferSz))"
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

View File

@ -0,0 +1,65 @@
#!/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

View File

@ -0,0 +1,100 @@
#!/bin/bash
# set -x
display_help() {
echo "$0 [interrupt mode]"
echo "interrupt mode: optional"
echo "0: auto"
echo "1: MSI"
echo "2: Legacy"
echo "3: MSIx"
echo "4: do not use interrupt, poll mode only"
exit;
}
if [ "$1" == "help" ]; then
display_help
fi;
interrupt_selection=$1
echo "interrupt_selection $interrupt_selection."
device_id=903f
# 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
if [ $? -ne 0 ]; then
echo "rmmod xdma failed: $?"
exit 1
fi
fi
# 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.
echo -n "Loading driver..."
case $interrupt_selection in
"0")
echo "insmod xdma.ko interrupt_mode=1 ..."
ret=`insmod ../xdma/xdma.ko interrupt_mode=0`
;;
"1")
echo "insmod xdma.ko interrupt_mode=2 ..."
ret=`insmod ../xdma/xdma.ko interrupt_mode=1`
;;
"2")
echo "insmod xdma.ko interrupt_mode=3 ..."
ret=`insmod ../xdma/xdma.ko interrupt_mode=2`
;;
"3")
echo "insmod xdma.ko interrupt_mode=4 ..."
ret=`insmod ../xdma/xdma.ko interrupt_mode=3`
;;
"4")
echo "insmod xdma.ko poll_mode=1 ..."
ret=`insmod ../xdma/xdma.ko poll_mode=1`
;;
*)
intp=`sudo lspci -d :${device_id} -v | grep -o -E "MSI-X"`
intp1=`sudo lspci -d :${device_id} -v | grep -o -E "MSI:"`
if [[ ( -n $intp ) && ( $intp == "MSI-X" ) ]]; then
echo "insmod xdma.ko interrupt_mode=0 ..."
ret=`insmod ../xdma/xdma.ko interrupt_mode=0`
elif [[ ( -n $intp1 ) && ( $intp1 == "MSI:" ) ]]; then
echo "insmod xdma.ko interrupt_mode=1 ..."
ret=`insmod ../xdma/xdma.ko interrupt_mode=1`
else
echo "insmod xdma.ko interrupt_mode=2 ..."
ret=`insmod ../xdma/xdma.ko interrupt_mode=2`
fi
;;
esac
if [ ! $ret == 0 ]; then
echo "Error: xdma driver 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"

View File

@ -0,0 +1,38 @@
#!/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

View File

@ -0,0 +1,113 @@
#!/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
v=`$tool_path/reg_rw /dev/xdma0_control 0x0${i}00 w`
returnVal=$?
if [ $returnVal -ne 0 ]; then
break;
fi
#v=`echo $v | grep -o '): 0x[0-9a-f]*'`
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
v=`$tool_path/reg_rw /dev/xdma0_control 0x1${i}00 w`
returnVal=$?
if [ $returnVal -ne 0 ]; then
break;
fi
$tool_path/reg_rw /dev/xdma0_control 0x1${i}00 w | grep "Read.*: 0x1fc" > /dev/null
statusRegVal=`$tool_path/reg_rw /dev/xdma0_control 0x1${i}00 w | grep "Read.*:" | sed 's/Read.*: 0x\([a-z0-9]*\)/\1/'`
channelId=${statusRegVal:0:3}
# there will NOT be a mix of MM & ST channels, so no need to check
# for streaming enabled
if [ $channelId == "1fc" ]; then
c2hChannels=$((c2hChannels + 1))
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 xdma0 $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

View File

@ -0,0 +1,180 @@
#!/bin/bash
##############################################################
#
# parse the fio result directory generated by the fio_test.sh
#
##############################################################
function parse_iops() {
eval str="$1"
value=$(echo $str | awk -F "," '{print $1}' | awk -F "=" '{print $2}')
unit=$(echo $value | awk -F '[0-9,.]*' '{print $2}')
value=$(echo $value | sed 's/[^0-9,.]*//g')
# echo -n " iops: ${value}${unit}"
if [ -z "$unit" ];then
value=$(echo "scale=4; $value/1000" | bc -l)
elif [[ "$unit" == "k" ]];then
value=$(echo "scale=4; $value" | bc -l)
elif [[ "$unit" == "m" ]];then
value=$(echo "scale=4; $value*1000" | bc -l)
else
echo "iops: $value$unit, unknown unit $unit."
fi
}
function parse_bw() {
eval str="$1"
value=$(echo $str | awk -F "," '{print $2}')
value=$(echo $value | awk -F "[(,)]" '{print $2}')
unit=$(echo $value | awk -F '[0-9,.]*' '{print $2}')
value=$(echo $value | sed 's/[^0-9,.]*//g')
# echo -n " bw: ${value}${unit}"
if [[ "$unit" == "kB/s" ]];then
value=$(echo "scale=4; $value" | bc -l)
elif [[ "$unit" == "MB/s" ]];then
value=$(echo "scale=4; $value*1024" | bc -l)
elif [[ "$unit" == "gB/s" ]];then
value=$(echo "scale=4; $value*1024*1024" | bc -l)
else
echo "bw: $value$unit, unknown unit $unit."
fi
}
function parse_latency() {
eval str="$1"
value=$(echo $str | awk -F "," '{print $3}' | awk -F "=" '{print $2}')
unit=$(echo $str | awk -F "[(,)]" '{print $2}')
# echo -n " latency: ${value}${unit}"
if [[ "$unit" == "usec" ]];then
value=$(echo "scale=4; $value" | bc -l)
elif [[ "$unit" == "sec" ]];then
value=$(echo "scale=6; $value*1000000" | bc -l)
elif [[ "$unit" == "msec" ]];then
value=$(echo "scale=6; $value*1000" | bc -l)
elif [[ "$unit" == "nsec" ]];then
value=$(echo "scale=4; $value/1000" | bc -l)
else
echo "latency: $value$unit, unknown unit $unit."
fi
}
##############
# Main body
##############
if [ $# -lt 1 ];then
echo "$0 <result directory>"
exit 1
fi
dir=$1
if [[ ! -d $dir ]];then
echo "$dir does NOT exist."
exit 1
fi
declare -a lat_array
resfname=result.csv
rm -f $dir/$resfname
channel_list=`ls $dir`
for channels in $channel_list; do
cd $dir/$channels
rm -f $dir/$channels/$resfname
iodir_list=`ls`
for iodir in $iodir_list; do
cd $dir/$channels/$iodir
rm -f $resfname
echo > $resfname
fio_list=`ls fio*.log`
for fname in $fio_list; do
# fio result file format fio_<io size>_t<# threads>.log
sz=$(echo $fname | cut -d. -f1 | cut -d_ -f2)
thread=$(echo $fname | cut -d. -f1 | cut -d_ -f3)
thread=$(echo $thread | sed 's/[^0-9]*//')
#echo "$dir/$channels/$iodir/$fname:"
value=0;
unit=0
if [ "$iodir" == "h2c" ]; then
#echo -n "$channels h2c:io $sz thread $thread "
ln=$(grep "write:" $fname)
parse_iops "\${ln}"
echo -n $sz,$thread,$value, >> $resfname
parse_bw "\${ln}"
echo -n $value, >> $resfname
ln=$(grep clat $fname | grep avg)
parse_latency "\${ln}"
echo "$value,,,," >> $resfname
elif [ "$iodir" == "c2h" ]; then
#echo -n "$channels c2h:io $sz thread $thread "
ln=$(grep "read:" $fname)
parse_iops "\${ln}"
echo -n $sz,$thread,,,,$value, >> $resfname
parse_bw "\${ln}"
echo -n $value, >> $resfname
ln=$(grep clat $fname | grep avg)
parse_latency "\${ln}"
echo "$value," >> $resfname
elif [ "$iodir" == "bi" ]; then
#echo -n "$channels bidir:io $sz thread $thread "
readarray lat_array < <(grep clat $fname | \
grep avg)
# h2c
#echo -n "h2c "
ln=$(grep "write:" $fname)
parse_iops "\${ln}"
echo -n $sz,$thread,$value, >> $resfname
parse_bw "\${ln}"
echo -n $value, >> $resfname
parse_latency "\${lat_array[1]}"
echo -n $value, >> $resfname
#c2h
#echo -n " c2h "
ln=$(grep "read:" $fname)
parse_iops "\${ln}"
echo -n $value, >> $resfname
parse_bw "\${ln}"
echo -n $value, >> $resfname
parse_latency "\${lat_array[0]}"
echo $value >> $resfname
fi
done
done
done
echo
cd $dir
for channels in $channel_list; do
cd $dir/$channels
echo -n "iosize(B)","Thread #", > $resfname
echo -n "H2C IOPS(K)","H2C BW(KB/s)","H2C Latency(usec)," >> $resfname
echo "C2H IOPS(K)","C2H BW(KB/s)","C2H Latency(usec)," >> $resfname
for iodir in $iodir_list; do
cat $iodir/$resfname | sort -t, -k1,1n >> $resfname
done
echo "$channels channel results: $dir/$channels/$resfname"
done

View File

@ -0,0 +1,70 @@
#!/bin/bash
display_help() {
echo -n "$0 <xdma id> <# ch> <io sz> < runtime> <iodir> <thread #> "
echo "<logdir>"
echo -e "\t<xdma id>: xdmaN"
echo -e "\t<dir>: io direction <h2c|c2h|bi>"
echo -e "\t<# ch>: fio --num_ch"
echo -e "\t<io sz>: fio --io_size"
echo -e "\t<runtime>: fio --runtime"
echo -e "\t<thread #>: fio --threads"
echo -e "\t<logdir>: log directory"
exit;
}
####################
#
# main body
#
####################
if [ $# -ne 7 ]; then
display_help
fi
xid=$1
iodir=$2
num_ch=$3
io_size=$4
runtime=$5
threads=$6
logdir=$7
outfile="${logdir}/fio_${io_size}_t${threads}.log"
exec_cmd=
op_cmd=
engine=sync
cmd_common="fio --allow_file_create=0 --ioengine=${engine} --zero_buffers"
cmd_common="$cmd_common --mem=mmap --runtime=${runtime} --time_based"
for ((i = 0; i < num_ch; i++)); do
if [ ${iodir} == bi ]; then
op_cmd="${op_cmd} --name=write${i} --bs=${io_size}"
op_cmd="${op_cmd} --size=${io_size} --offset=0 --rw=write"
op_cmd="${op_cmd} --filename=/dev/xdma0_h2c_${i}"
op_cmd="${op_cmd} --numjobs=${threads} --group_reporting"
op_cmd="${op_cmd} --name=read${i} --bs=${io_size}"
op_cmd="${op_cmd} --size=${io_size} --offset=0 --rw=read "
op_cmd="${op_cmd} --filename=/dev/xdma0_c2h_${i}"
op_cmd="${op_cmd} --numjobs=${threads} --group_reporting"
elif [ ${iodir} == h2c ]; then
op_cmd="${op_cmd} --name=write${i} --bs=${io_size}"
op_cmd="${op_cmd} --size=${io_size} --offset=0 --rw=write"
op_cmd="${op_cmd} --filename=/dev/xdma0_${iodir}_${i}"
op_cmd="${op_cmd} --numjobs=${threads} --group_reporting"
else
op_cmd="${op_cmd} --name=read${i} --bs=${io_size}"
op_cmd="${op_cmd} --size=${io_size} --offset=0 --rw=read"
op_cmd="${op_cmd} --filename=/dev/xdma0_${iodir}_${i}"
op_cmd="${op_cmd} --numjobs=${threads} --group_reporting"
fi
done
exec_cmd="${cmd_common}${op_cmd}"
echo -e "${exec_cmd}\n\n" > ${outfile}
${exec_cmd} >> ${outfile} &
pid=$!
wait $pid

View File

@ -0,0 +1,130 @@
#!/bin/sh
tool_path=../../tools
logdir=/tmp
if [ $# -lt 9 ]; then
echo -ne "$0 <dmesg log 0|1> <data check 0|1> <sz> <address> <offset> "
echo "<xid> <h2c channel> <c2h channel> <log dir> [data file]"
echo -e "\t<dmesg log>: log test into dmesg"
echo -e "\t<data check 0|1>: read data back and compare"
echo -e "\t<sz>: dma transfer size"
echo -e "\t<address>: "
echo -e "\t<offset>: "
echo -e "\t<xdma id>: xdma<N>"
echo -e "\t<h2c channel>: dma h2c channel #, 0-based"
echo -e "\t\tif >= 4, no traffic will be ran"
echo -e "\t<c2h channel>: dma c2h channel #, 0-based"
echo -e "\t if channel # >= 4 NO dma will be performed"
echo -e "\t\tif >= 4, no traffic will be ran"
echo -e "\t<log dir>: temp. log directory"
echo -e "\t[data file]: data file, size >= io size, "
echo -e "\t optional if <data check>=0"
exit
fi
dmesg=$1
data_check=$2
sz=$3
address=$4
offset=$5
xid=$6
h2cno=$7
c2hno=$8
logdir=$9
if [ $# -gt 9 ]; then
datafile=${10}
fi
if [ ! -d "$logdir" ]; then
mkdir -p $logdir
fi
echo -en "\n===>$0 $xid, channel $h2cno:$c2hno, io $sz, addr $address, "
echo "off $offset, data: $datafile, integrity $data_check, dmesg $dmesg."
if [ "$h2cno" -ge 4 ] && [ "$c2hno" -ge 4 ]; then
echo "$0: NO valid dma channel $h2cno:$c2hno"
exit 1
fi
h2c_cmd="$tool_path/dma_to_device -d /dev/${xid}_h2c_${h2cno}"
c2h_cmd="$tool_path/dma_from_device -d /dev/${xid}_c2h_${c2hno}"
if [ "$address" -ne "0" ]; then
h2c_cmd="$h2c_cmd -a $address"
c2h_cmd="$c2h_cmd -a $address"
fi
if [ "$offset" -ne "0" ]; then
h2c_cmd="$h2c_cmd -o $offset"
c2h_cmd="$c2h_cmd -o $offset"
fi
if [ "$data_check" -ne 0 ]; then
if [ -z "$datafile" ]; then
echo "no datafile specified"
exit 2
fi
if [ ! -s "$datafile" ]; then
echo "missing datafile: $datafile ..."
exit 3
fi
h2c_fname="$logdir/$xid-h2c${h2cno}-io$sz-o$offset-a$address.bin"
rm -f $h2c_fname
h2c_cmd="$h2c_cmd -f $datafile -w $h2c_fname"
c2h_fname="$logdir/$xid-c2h${c2hno}-io$sz-o$offset-a$address.bin"
rm -f $c2h_fname
c2h_cmd="$c2h_cmd -f $c2h_fname"
fi
if [ "$h2cno" -lt 4 ]; then
if [ "$dmesg" -ne "0" ]; then
echo "$h2c_cmd -s $sz -c 1" > /dev/kmsg
fi
echo "$h2c_cmd -s $sz -c 1 ..." > \
${logdir}/h2c-io${sz}-o${offset}-a${address}.log
out=`$h2c_cmd -s $sz -c 1`
echo $out >> ${logdir}/h2c-io${sz}-o${offset}-a${address}.log
if [ "$?" -ne "0" ]; then
echo -e "\tH2C${h2cno}: io $sz, ERROR $?."
exit 4
fi
fi
if [ "$c2hno" -lt 4 ]; then
if [ "$dmesg" -ne "0" ]; then
echo "$c2h_cmd -s $sz -c 1 ..." > /dev/kmsg
fi
echo "$c2h_cmd -s $sz -c 1 ..." > \
${logdir}/c2h-io${sz}-o${offset}-a${address}.log
out=`./$c2h_cmd -s $sz -c 1`
echo $out >> ${logdir}/c2h-io${sz}-o${offset}-a${address}.log
if [ "$?" -ne "0" ]; then
echo -e "\tC2H$channel: io $sz, ERROR $?."
exit 5
fi
fi
if [ "$data_check" -eq 0 ]; then
# no data integrity check needs to be done
exit 0
fi
#md5sum $c2h_fname
#md5sum $h2c_fname
diff -q $c2h_fname $h2c_fname > /dev/null
if [ "$?" -eq "1" ]; then
echo -e "\t$xid $h2cno:$c2hno: io $sz, addr $address, off $offset," \
"data integrity FAILED!."
exit 6
fi
echo -e "\t$xid $h2cno:$c2hno: io $sz, addr $address, off $offset, data match."
rm -f $c2h_fname $h2c_fname
exit 0

View File

@ -0,0 +1,87 @@
#!/bin/sh
delay=5
if [ $# -lt 9 ]; then
echo -ne "$0: <xid> <h2c channel> <c2h channel> <address> <offset> "
echo "<io min> <io max> <data check> <dmesg log> [log dir]"
echo -e "\t<xdma id>: xdma<N>"
echo -e "\th2c channel: H2C channel #, 0-based"
echo -e "\tc2h channel: C2H channel #, 0-based"
echo -e "\t<address>: "
echo -e "\t<offset>: "
echo -e "\t<io min>,<io max>: dma size in byte, io size start from"
echo -e "\t\tio_min, double each time until reaches io_max"
echo -e "\t<data check>: read back the data and compare, 0|1"
echo -e "\t<dmesg log>: log test info. into dmesg, 0|1"
exit 1
fi
xid=$1
h2cno=$2
c2hno=$3
addr=$4
off=$5
io_min=$6
io_max=$7
data_check=$8
dmesg=$9
tmpdir="/tmp/${xid}_h2c${h2cno}c2h${c2hno}"
echo "====>$0 $xid $h2cno:$c2hno, $io_min~$io_max @$addr $off, $data_check, $tmpdir"
if [ "$dmesg" -ne 0 ]; then
echo "$0 $xid $h2cno:$c2hno, $io_min~$io_max @$addr $off, $tmpdir..." \
>> /dev/kmsg
fi
if [ ! -d "$tmpdir" ]; then
mkdir -p $tmpdir
fi
rm -rf ${tmpdir}/*
if [ "$data_check" -ne 0 ]; then
cnt=$(($io_max / 1024))
if [ "$cnt" -eq 0 ]; then
cnt=1
fi
datafile="$tmpdir/datafile-$cnt-K"
cnt=$(($cnt / 65536))
if [ "$cnt" -eq "0" ]; then
cnt=1
fi
if [ ! -f "$datafile" ]; then
echo "creating datafile: $datafile ..."
let cnt=cnt+1
dd if=/dev/urandom of=$datafile bs=64M count=$cnt \
iflag=fullblock
fi
fi
date
sz=$io_min
while [ "$sz" -le "$io_max" ]; do
./io.sh $dmesg $data_check $sz $addr $off $xid $h2cno $c2hno $tmpdir \
$datafile
if [ "$?" -ne "0" ]; then
#echo -e "\t$xid $h2cno:$c2hno, $sz FAILED"
exit 2
fi
if [ "$sz" -eq "$io_max" ]; then
break
fi
sz=$(($sz * 2))
if [ "$sz" -gt "$io_max" ]; then
sz=$io_max
fi
if [ "$delay" -ne "0" ]; then
sleep $delay
fi
done
echo "====>$0 $xid $h2cno:$c2hno, $io_min~$io_max @$addr $off COMPLETED!"
exit 0

View File

@ -0,0 +1,187 @@
tool_path=../../tools
ERR=255
############################
#
# utility functions
#
############################
# Make sure only root can run the script
function check_if_root() {
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" 1>&2
exit $ERR
fi
}
function check_cmd_exist() {
which $1 > /dev/null 2>&1
if [[ $? -ne 0 ]]; then
echo "$1 NOT found on the system"
return $ERR
fi
return 0
}
# check_rc <error code> <string> <exit if error 0|1>
function check_rc() {
local rc=$1
if [ $rc -ne 0 ]; then
echo "ERR! $2 failed $rc."
if [ $3 -gt 0 ]; then
exit $rc
fi
fi
}
# check_dma_dir <h2c|c2h|bi>
function check_dma_dir() {
if [ "$1" != h2c ] && [ "$1" != c2h ] && [ "$1" != bi ]; then
echo "bad dma direction: $1."
exit $ERR
fi
}
# check_driver_loaded <bdf>
function check_driver_loaded() {
local bdf=$1
lspci -s $bdf -v | grep driver | grep xdma | wc -l
}
# bdf_to_xdmaid <bdf>
function bdf_to_xdmaid() {
cd /sys/bus/pci/devices/$1/
if [ -d "xdma" ]; then
cd xdma/
ls | grep control | cut -d'_' -f1
fi
}
# cfg_reg_read <xid> <reg addr>
function cfg_reg_read() {
local v=`$tool_path/reg_rw /dev/$1_control $2 w | grep "Read.*:" | sed 's/Read.*: 0x\([a-z0-9]*\)/\1/'`
if [ -z "$v" ]; then
return $ERR
else
echo $v
return 0
fi
}
# cfg_reg_write <xid> <reg addr> <reg value>
function cfg_reg_write() {
local v=`$tool_path/reg_rw /dev/$1_control $2 w $3`
return $?
}
# get_streaming_enabled <xid>
function get_streaming_enabled() {
local v=`cfg_reg_read $1 0`
local rc=$?
if [ $rc -ne 0 ]; then
return $rc
fi
local id=${v:0:3}
local stream=${v:4:1}
local st=0
if [ "$id" == "1fc" ]; then
if [ "$stream" == "8" ]; then
st=1
fi
else
echo "$1 reg 0, $v, bad id $id, $stream."
return $ERR
fi
echo $st
return 0
}
# get_h2c_channel_count <xid>
function get_h2c_channel_count() {
local cnt=0
for ((i=0; i<=3; i++)); do
local regval=`cfg_reg_read $1 0x0${i}00`
if [ $? -ne 0 ]; then
break
fi
local id=${regval:0:3}
if [ "$id" == "1fc" ]; then
cnt=$((cnt + 1))
fi
done
echo $cnt
return 0
}
# get_c2h_channel_count <xid>
function get_c2h_channel_count() {
local cnt=0
for ((i=0; i<=3; i++)); do
local regval=`cfg_reg_read $1 0x1${i}00`
if [ $? -ne 0 ]; then
break
fi
local id=${regval:0:3}
if [ "$id" == "1fc" ]; then
cnt=$((cnt + 1))
fi
done
echo $cnt
return 0
}
#############################################################################
#
# test cases
#
#############################################################################
# xdma config bar access
# TC_cfg_reg_rw <xid>
function TC_cfg_reg_rw() {
local reg=0x301c
local val=0
cfg_reg_write $reg $val
local v=$(cfg_reg_read $reg)
let "v = $v + 0"
if [ $v -eq $val ]; then
echo "ERR ${FUNCNAME[0]} reg value mismatch $v, exp $val"
exit $ERR
fi
cfg_reg_write $reg 1
}
# TC_dma_chrdev_open_close <xid> <h2c count> <c2h count>
function TC_dma_chrdev_open_close() {
local xid=$1
local h2c_count=$2
local c2h_count=$3
local err=0
for ((i=0; i<$h2c_count; i++)); do
$tool_path/test_chrdev /dev/${xid}_h2c_$i > /dev/null 2>&1
if [ $? -ne 0 ];then
echo "${FUNCNAME[0]} ${xid}_h2c_$i FAILED"
exit 1
fi
done
for ((i=0; i<$c2h_count; i++)); do
$tool_path/test_chrdev /dev/${xid}_c2h_$i > /dev/null 2>&1
if [ $? -ne 0 ];then
echo "${FUNCNAME[0]} ${xid}_c2h_$i FAILED"
exit 2
fi
done
}

View File

@ -0,0 +1,93 @@
#!/bin/sh
##########################
#
# preset test parameters:
#
##########################
# dma io size
io_list="1 10 127 128 1021 1022 1023 1024 4095 4096 4097 4098 4099 8189 8190 8191 8192"
io_max=8192
# offset
offset_list="1 2 3 4 2045 2046 2047 2048 2049 4091 4092 4093 4094 4095"
# starting address
address=0
# delay(sleep) before moving on to the next channel, if applicable
delay=2
##########################
#
# main
#
##########################
if [ $# -lt 5 ]; then
echo "$0: <xid> <h2c channel> <c2h channel> <data check> <dmesg log>"
echo -e "\t<xid>: xdma<N>"
echo -e "\t<h2c channel>: H2C channel #, 0-based"
echo -e "\t<c2h channel>: C2H channel #, 0-based"
echo -e "\t<data check>: read back the data and compare, 0|1"
echo -e "\t<dmesg log>: log test info. into dmesg, 0|1"
exit
fi
xid=$1
h2cno=$2
c2hno=$3
data_check=$4
dmesg=$5
tmpdir="/tmp/${xid}_h2c${h2cno}_c2h${c2hno}_unaligned"
echo "====>$0 $xid $h2cno:$c2hno, $data_check,$dmesg, $tmpdir"
if [ "$dmesg" -ne 0 ]; then
echo "$0 $xid $h2cno:$c2hno, $tmpdir..." >> /dev/kmsg
fi
if [ ! -d "$tmpdir" ]; then
mkdir -p $tmpdir
fi
rm -rf $tmpdir/*
# generate data file, minimum 64MB
cnt=$(($io_max / 1024))
if [ "$cnt" -eq "0" ]; then
cnt=1
fi
datafile="$tmpdir/datafile-$cnt-K"
cnt=$(($cnt / 65536))
if [ "$cnt" -eq "0" ]; then
cnt=1
fi
if [ ! -f "$datafile" ]; then
echo "creating datafile: $datafile ..."
let cnt=cnt+1
dd if=/dev/urandom of=$datafile bs=64M count=$cnt iflag=fullblock
fi
echo
date
echo "====>$0: $xid $h2cno:$c2hno, addr $address ..." > /dev/kmsg
echo "$0: $xid $h2cno:$c2hno, addr $address ..." > /dev/kmsg
for io in $io_list; do
for offset in $offset_list; do
./io.sh $dmesg $data_check $io $address $offset $xid \
$h2cno $c2hno $tmpdir $datafile
if [ "$?" -ne "0" ]; then
echo -e "\t$xid $h2cno:$c2hno, $io, off $offset FAILED!"
exit 2
fi
done
if [ "$delay" -ne "0" ]; then
sleep $delay
fi
done
date
echo "====>$0: $xid $h2cno:$c2hno, addr $address COMPLETED!"
exit 0

View File

@ -0,0 +1,164 @@
#!/bin/bash
####################
#
# test settings
#
####################
outdir="/tmp"
driver_modes="0 4" ;# driver mode
address=0
offset=0
io_min=64
io_max=$((1 << 30)) ;# 1GB
delay=5 ;# delay between each test
fio_time=30
fio_thread_list="4 8"
fio_iodir_list="h2c c2h bi"
####################
#
# main body
#
####################
display_help() {
echo "$0 <xdma BDF> [log dir]"
echo -e "xdma BDF:\tfpga pci device specified in the format of "
echo -e "\t\t\t<domain>:<bus>:<device>.<func>"
echo -e "log dir:\toptional, default to /tmp"
echo
exit;
}
if [ $# -eq 0 ]; then
display_help
fi
bdf=$1
if [ $# -gt 1 ]; then
outdir=$2
fi
echo "xdma bdf:$bdf, outdir: $outdir"
source ./libtest.sh
check_if_root
curdir=$PWD
for dm in $driver_modes; do
echo -e "\n\n====> xdma mode $dm ...\n"
cd ../../tests
./load_driver.sh $dm
if [ $? -ne 0 ]; then
echo "load_driver.sh failed: $?"
exit 1
fi
cd $curdir
xid=$(bdf_to_xdmaid $bdf)
if [ ! -n "$xid" ]; then
echo "$bdf, no correponding xdma found, driver mode $dm."
exit 1
fi
echo "xdma id: $xid."
h2c_channels=$(get_h2c_channel_count $xid)
check_rc $? get_h2c_channel_count 1
c2h_channels=$(get_c2h_channel_count $xid)
check_rc $? get_c2h_channel_count 1
channel_pairs=$(($h2c_channels < $c2h_channels ? \
$h2c_channels : $c2h_channels))
echo "channels: $h2c_channels,$c2h_channels, pair $channel_pairs"
if [ "$channel_pairs" -eq 0 ]; then
echo "Error: 0 DMA channel pair: $h2c_channels,$c2h_channels."
exit 1
fi
# test cdev
TC_dma_chrdev_open_close $xid $h2c_channels $c2h_channels
#
# run 1 channel at a time
#
for i in {1..80}; do echo -n =; done
echo -e "\nSingle H2C Channel $h2c_channels io test ...\n"
for ((i=0; i<$h2c_channels; i++)); do
# aligned: no data integrity check
./io_sweep.sh $xid $i 4 $address $offset \
$io_min $io_max 0 1
check_rc $? "h2c-$i" 1
./unaligned.sh $xid $i 4 0 1
check_rc $? "h2c-$i-unaligned" 1
done
for i in {1..80}; do echo -n =; done
echo -e "\nSingle C2H Channel $c2h_channels io test ...\n"
for ((i=0; i<$c2h_channels; i++)); do
./io_sweep.sh $xid 4 $i $address $offset \
$io_min $io_max 0 1
check_rc $? "c2h-$i" 1
./unaligned.sh $xid 4 $i 0 1
check_rc $? "c2h-$i-unaligned" 1
done
for i in {1..80}; do echo -n =; done
echo -e "\nh2c/c2h pair $channel_pairs io test with data check ...\n"
for ((i=0; i<$channel_pairs; i++)); do
./io_sweep.sh $xid $i $i $address $offset $io_min $io_max 1 1
check_rc $? "pair-$i" 1
./unaligned.sh $xid $i $i 1 1
check_rc $? "pair-$i-unaligned" 1
done
#
# fio test
#
check_cmd_exist fio
if [ "$?" -ne 0 ]; then
echo "fio test skipped"
continue
fi
for i in {1..80}; do echo -n =; done
echo -e "\nfio test ...\n"
#
# result directory structure:
# - <outdir/fio>
# - <number of channels>
# - <direction: h2c c2h bi>
#
for ((i=1; i<=$channel_pairs; i++)); do
for iodir in $fio_iodir_list; do
out=${outdir}/fio_d${dm}/${i}/${iodir}
mkdir -p ${out}
rm -rf ${out}/*
for (( sz=$io_min; sz<=$io_max; sz=$(($sz*2)) )); do
for thread in $fio_thread_list; do
name=${sz}_t${thread}
echo "$iodir $i: $name ..."
./fio_test.sh $xid $iodir $i ${sz} \
${fio_time} ${thread} ${out}
done
done
done
done
./fio_parse_result.sh ${outdir}/fio_d${dm}
echo -e "\n\n====> xdma mode $dm COMPLETED.\n"
done
echo "$0: COMPLETED."

View File

@ -0,0 +1,24 @@
CC ?= gcc
all: reg_rw dma_to_device dma_from_device performance test_chrdev
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 $@ $<
test_chrdev: test_chrdev.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 test_chrdev

Binary file not shown.

View File

@ -0,0 +1,304 @@
/*
* 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)
*/
#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 <sys/ioctl.h>
#include "../xdma/cdev_sgdma.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'},
{"aperture", required_argument, NULL, 'k'},
{"size", required_argument, NULL, 's'},
{"offset", required_argument, NULL, 'o'},
{"count", required_argument, NULL, 'c'},
{"file", required_argument, NULL, 'f'},
{"eop_flush", no_argument, NULL, 'e'},
{"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 aperture,
uint64_t size, uint64_t offset, uint64_t count,
char *ofname);
static int eop_flush = 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) memory address aperture\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) end dma when ST end-of-packet(eop) is rcved\n",
long_opts[i].val, long_opts[i].name);
fprintf(stdout,
"\t\t* streaming only, ignored for memory-mapped channels\n");
fprintf(stdout,
"\t\t* acutal # of bytes dma'ed could be smaller than specified\n");
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++;
fprintf(stdout, "\nReturn code:\n");
fprintf(stdout, " 0: all bytes were dma'ed successfully\n");
fprintf(stdout, " * with -e set, the bytes dma'ed could be smaller\n");
fprintf(stdout, " < 0: error\n\n");
}
int main(int argc, char *argv[])
{
int cmd_opt;
char *device = DEVICE_NAME_DEFAULT;
uint64_t address = 0;
uint64_t aperture = 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, "vhec:f:d:a:k: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;
case 'k':
/* memory aperture windows size */
aperture = getopt_integer(optarg);
break;
case 's':
/* RAM 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':
ofname = strdup(optarg);
break;
/* print usage help and exit */
case 'v':
verbose = 1;
break;
case 'e':
eop_flush = 1;
break;
case 'h':
default:
usage(argv[0]);
exit(0);
break;
}
}
if (verbose)
fprintf(stdout,
"dev %s, addr 0x%lx, aperture 0x%lx, size 0x%lx, offset 0x%lx, "
"count %lu\n",
device, address, aperture, size, offset, count);
return test_dma(device, address, aperture, size, offset, count, ofname);
}
static int test_dma(char *devname, uint64_t addr, uint64_t aperture,
uint64_t size, uint64_t offset, uint64_t count,
char *ofname)
{
ssize_t rc = 0;
size_t out_offset = 0;
size_t bytes_done = 0;
uint64_t i;
char *buffer = NULL;
char *allocated = NULL;
struct timespec ts_start, ts_end;
int out_fd = -1;
int fpga_fd;
long total_time = 0;
float result;
float avg_time = 0;
int underflow = 0;
/*
* use O_TRUNC to indicate to the driver to flush the data up based on
* EOP (end-of-packet), streaming mode only
*/
if (eop_flush)
fpga_fd = open(devname, O_RDWR | O_TRUNC);
else
fpga_fd = open(devname, O_RDWR);
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);
if (aperture) {
struct xdma_aperture_ioctl io;
io.buffer = (unsigned long)buffer;
io.len = size;
io.ep_addr = addr;
io.aperture = aperture;
io.done = 0UL;
rc = ioctl(fpga_fd, IOCTL_XDMA_APERTURE_R, &io);
if (rc < 0 || io.error) {
fprintf(stderr,
"#%d: aperture R failed %d,%d.\n",
i, rc, io.error);
goto out;
}
bytes_done = io.done;
} else {
rc = read_to_buffer(devname, fpga_fd, buffer, size, addr);
if (rc < 0)
goto out;
bytes_done = rc;
}
clock_gettime(CLOCK_MONOTONIC, &ts_end);
if (bytes_done < size) {
fprintf(stderr, "#%d: underflow %ld/%ld.\n",
i, bytes_done, size);
underflow = 1;
}
/* 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/%ld bytes\n",
i, ts_end.tv_sec, ts_end.tv_nsec, bytes_done, size);
/* file argument given? */
if (out_fd >= 0) {
rc = write_from_buffer(ofname, out_fd, buffer,
bytes_done, out_offset);
if (rc < 0 || rc < bytes_done)
goto out;
out_offset += bytes_done;
}
}
if (!underflow) {
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("%s ** Average BW = %lu, %f\n", devname, size, result);
rc = 0;
} else if (eop_flush) {
/* allow underflow with -e option */
rc = 0;
} else
rc = -EIO;
out:
close(fpga_fd);
if (out_fd >= 0)
close(out_fd);
free(allocated);
return rc;
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,318 @@
/*
* 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 <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 <sys/ioctl.h>
#include "../xdma/cdev_sgdma.h"
#include "dma_utils.c"
static struct option const long_opts[] = {
{"device", required_argument, NULL, 'd'},
{"address", required_argument, NULL, 'a'},
{"aperture", required_argument, NULL, 'k'},
{"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 aperture,
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) memory address aperture\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++;
fprintf(stdout, "\nReturn code:\n");
fprintf(stdout, " 0: all bytes were dma'ed successfully\n");
fprintf(stdout, " < 0: error\n\n");
}
int main(int argc, char *argv[])
{
int cmd_opt;
char *device = DEVICE_NAME_DEFAULT;
uint64_t address = 0;
uint64_t aperture = 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:k: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 'k':
/* memory aperture windows size */
aperture = 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, addr 0x%lx, aperture 0x%lx, size 0x%lx, offset 0x%lx, "
"count %lu\n",
device, address, aperture, size, offset, count);
return test_dma(device, address, aperture, size, offset, count,
infname, ofname);
}
static int test_dma(char *devname, uint64_t addr, uint64_t aperture,
uint64_t size, uint64_t offset, uint64_t count,
char *infname, char *ofname)
{
uint64_t i;
ssize_t rc;
size_t bytes_done = 0;
size_t out_offset = 0;
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;
int underflow = 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 || rc < size)
goto out;
}
for (i = 0; i < count; i++) {
/* write buffer to AXI MM address using SGDMA */
rc = clock_gettime(CLOCK_MONOTONIC, &ts_start);
if (aperture) {
struct xdma_aperture_ioctl io;
io.buffer = (unsigned long)buffer;
io.len = size;
io.ep_addr = addr;
io.aperture = aperture;
io.done = 0UL;
rc = ioctl(fpga_fd, IOCTL_XDMA_APERTURE_W, &io);
if (rc < 0 || io.error) {
fprintf(stdout,
"#%d: aperture W ioctl failed %d,%d.\n",
i, rc, io.error);
goto out;
}
bytes_done = io.done;
} else {
rc = write_from_buffer(devname, fpga_fd, buffer, size,
addr);
if (rc < 0)
goto out;
bytes_done = rc;
}
rc = clock_gettime(CLOCK_MONOTONIC, &ts_end);
if (bytes_done < size) {
printf("#%d: underflow %ld/%ld.\n",
i, bytes_done, size);
underflow = 1;
}
/* 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,
bytes_done, out_offset);
if (rc < 0 || rc < bytes_done)
goto out;
out_offset += bytes_done;
}
}
if (!underflow) {
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("%s ** Average BW = %lu, %f\n", devname, size, result);
}
out:
close(fpga_fd);
if (infile_fd >= 0)
close(infile_fd);
if (outfile_fd >= 0)
close(outfile_fd);
free(allocated);
if (rc < 0)
return rc;
/* treat underflow as error */
return underflow ? -EIO : 0;
}

Binary file not shown.

View File

@ -0,0 +1,183 @@
/*
* 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;
int loop = 0;
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 < 0) {
fprintf(stderr, "%s, read 0x%lx @ 0x%lx failed %ld.\n",
fname, bytes, offset, rc);
perror("read file");
return -EIO;
}
count += rc;
if (rc != bytes) {
fprintf(stderr, "%s, read underflow 0x%lx/0x%lx @ 0x%lx.\n",
fname, rc, bytes, offset);
break;
}
buf += bytes;
offset += bytes;
loop++;
}
if (count != size && loop)
fprintf(stderr, "%s, read underflow 0x%lx/0x%lx.\n",
fname, count, size);
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;
int loop = 0;
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 < 0) {
fprintf(stderr, "%s, write 0x%lx @ 0x%lx failed %ld.\n",
fname, bytes, offset, rc);
perror("write file");
return -EIO;
}
count += rc;
if (rc != bytes) {
fprintf(stderr, "%s, write underflow 0x%lx/0x%lx @ 0x%lx.\n",
fname, rc, bytes, offset);
break;
}
buf += bytes;
offset += bytes;
loop++;
}
if (count != size && loop)
fprintf(stderr, "%s, write underflow 0x%lx/0x%lx.\n",
fname, count, size);
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;
}
}

Binary file not shown.

View File

@ -0,0 +1,174 @@
/*
* 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>
#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);
}

Binary file not shown.

BIN
xdma/linux-kernel/tools/reg_rw Executable file

Binary file not shown.

View File

@ -0,0 +1,179 @@
/*
* 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 <fcntl.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/mman.h>
/* ltoh: little endian to host */
/* htol: host to little endian */
#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
int main(int argc, char **argv)
{
int fd;
int err = 0;
void *map;
uint32_t read_result, writeval;
off_t target;
off_t pgsz, target_aligned, offset;
/* access width */
char 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);
}
device = strdup(argv[1]);
target = strtoul(argv[2], 0, 0);
/* check for target page alignment */
pgsz = sysconf(_SC_PAGESIZE);
offset = target & (pgsz - 1);
target_aligned = target & (~(pgsz - 1));
printf("device: %s, address: 0x%lx (0x%lx+0x%lx), access %s.\n",
device, target, target_aligned, offset,
argc >= 4 ? "write" : "read");
/* data given? */
if (argc >= 4)
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("default to word (32-bits)\n");
access_width = 'w';
}
if ((fd = open(argv[1], O_RDWR | O_SYNC)) == -1) {
printf("character device %s opened failed: %s.\n",
argv[1], strerror(errno));
return -errno;
}
printf("character device %s opened.\n", argv[1]);
map = mmap(NULL, offset + 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
target_aligned);
if (map == (void *)-1) {
printf("Memory 0x%lx mapped failed: %s.\n",
target, strerror(errno));
err = 1;
goto close;
}
printf("Memory 0x%lx mapped at address %p.\n", target_aligned, map);
map += offset;
/* read only */
if (argc <= 4) {
switch (access_width) {
case 'b':
read_result = *((uint8_t *) map);
printf
("Read 8-bits value at address 0x%lx (%p): 0x%02x\n",
target, map, (unsigned int)read_result);
break;
case 'h':
read_result = *((uint16_t *) map);
/* swap 16-bit endianess if host is not little-endian */
read_result = ltohs(read_result);
printf
("Read 16-bit value at address 0x%lx (%p): 0x%04x\n",
target, map, (unsigned int)read_result);
break;
case 'w':
read_result = *((uint32_t *) map);
/* swap 32-bit endianess if host is not little-endian */
read_result = ltohl(read_result);
printf
("Read 32-bit value at address 0x%lx (%p): 0x%08x\n",
target, map, (unsigned int)read_result);
break;
default:
fprintf(stderr, "Illegal data type '%c'.\n",
access_width);
err = 1;
goto unmap;
}
}
/* 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%lx (0x%p)\n",
(unsigned int)writeval, target, map);
*((uint8_t *) map) = writeval;
break;
case 'h':
printf("Write 16-bits value 0x%04x to 0x%lx (0x%p)\n",
(unsigned int)writeval, target, map);
/* swap 16-bit endianess if host is not little-endian */
writeval = htols(writeval);
*((uint16_t *) map) = writeval;
break;
case 'w':
printf("Write 32-bits value 0x%08x to 0x%lx (0x%p)\n",
(unsigned int)writeval, target, map);
/* swap 32-bit endianess if host is not little-endian */
writeval = htoll(writeval);
*((uint32_t *) map) = writeval;
break;
default:
fprintf(stderr, "Illegal data type '%c'.\n",
access_width);
err = 1;
goto unmap;
}
}
unmap:
map -= offset;
if (munmap(map, offset + 4) == -1) {
printf("Memory 0x%lx mapped failed: %s.\n",
target, strerror(errno));
}
close:
close(fd);
return err;
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,46 @@
/*
* 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 <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd;
char *filename;
if ( (argc < 2) || (argc >= 3))
{
printf("usage %s <device file>\n",argv[0]);
return -1;
}
filename = argv[1];
fd = open(filename,O_RDWR);
if (fd < 0)
{
perror("Device open Failed");
return fd;
}
printf("%s Device open successfull\n",argv[1]);
if ( close(fd) )
{
perror("Device Close Failed");
return -1;
}
printf("%s Device close successfull\n",argv[1]);
return 0;
}

Binary file not shown.

View File

@ -0,0 +1 @@
savedcmd_/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/Module.symvers := scripts/mod/modpost -M -m -a -o /home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/Module.symvers -T /home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/modules.order -i Module.symvers -e

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
savedcmd_/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/modules.order := { echo /home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.o; :; } > /home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/modules.order

View File

@ -0,0 +1 @@
savedcmd_/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.ko := ld -r -m elf_x86_64 -z noexecstack --build-id=sha1 -T scripts/module.lds -o /home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.ko /home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.o /home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.mod.o

View File

@ -0,0 +1 @@
savedcmd_/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.mod := printf '%s\n' libxdma.o xdma_cdev.o cdev_ctrl.o cdev_events.o cdev_sgdma.o cdev_xvc.o cdev_bypass.o xdma_mod.o xdma_thread.o | awk '!x[$$0]++ { print("/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/"$$0) }' > /home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.mod

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
savedcmd_/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.o := ld -m elf_x86_64 -z noexecstack -r -o /home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.o @/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.mod

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
SHELL = /bin/bash
#
# optional makefile parameters:
# - DEBUG=<0|1>, enable verbose debug print-out in the driver
# - config_bar_num=, xdma pci config bar number
# - xvc_bar_num=, xvc pci bar #
# - xvc_bar_offset=, xvc register base offset
#
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)
ifeq ($(DEBUG),1)
EXTRA_CFLAGS += -D__LIBXDMA_DEBUG__
endif
ifneq ($(config_bar_num),)
EXTRA_CFLAGS += -DXDMA_CONFIG_BAR_NUM=$(config_bar_num)
endif
#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 xdma_thread.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
@/bin/rm -f *.ko modules.order *.mod.c *.o *.o.ur-safe .*.o.cmd
install: all
@rm -f /lib/modules/5.15.0-67-generic/extra/xdma.ko
@echo "installing kernel modules to /lib/modules/$(shell uname -r)/xdma ..."
@mkdir -p -m 755 /lib/modules/$(shell uname -r)/xdma
@install -v -m 644 *.ko /lib/modules/$(shell uname -r)/xdma
@depmod -a || true
uninstall:
@echo "Un-installing /lib/modules/$(shell uname -r)/xdma ..."
@/bin/rm -rf /lib/modules/$(shell uname -r)/xdma
@depmod -a
endif

View File

View File

@ -0,0 +1,198 @@
/*
* 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 "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;
if (!buf) {
pr_err("Invalid user buffer\n");
return -EINVAL;
}
if (!buf_offset) {
pr_err("Invalid user buffer offset\n");
return -EINVAL;
}
/* 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 %s()\n", __func__);
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;
void __iomem *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 %s()\n", __func__);
spin_lock(&engine->lock);
/* Write descriptor data to the bypass BAR */
bypass_addr = xdev->bar[xdev->bypass_bar_idx];
bypass_addr = (void __iomem *)(
(u32 __iomem *)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);
}

Binary file not shown.

View File

@ -0,0 +1,274 @@
/*
* 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"
#if ACCESS_OK_2_ARGS
#define xlx_access_ok(X, Y, Z) access_ok(Y, Z)
#else
#define xlx_access_ok(X, Y, Z) access_ok(X, Y, Z)
#endif
/*
* 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 __iomem *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("%s(@%p, count=%ld, pos=%d) value = 0x%08x\n",
__func__, 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 __iomem *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("%s(0x%08x @%p, count=%ld, pos=%d)\n",
__func__, 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 = !xlx_access_ok(VERIFY_WRITE, (void __user *)arg,
_IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
result = !xlx_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 __user *) 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, vsize 0x%lu, psize 0x%lu.\n", off, vsize, psize);
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
*/
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0)
vm_flags_set(vma, VMEM_FLAGS);
#elif defined(RHEL_RELEASE_CODE)
#if (RHEL_RELEASE_CODE > RHEL_RELEASE_VERSION(9, 4))
vm_flags_set(vma, VMEM_FLAGS);
#else
vma->vm_flags |= VMEM_FLAGS;
#endif
#else
vma->vm_flags |= VMEM_FLAGS;
#endif
/* 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);
}

View File

@ -0,0 +1,87 @@
/*
* 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_ */

Binary file not shown.

View File

@ -0,0 +1,120 @@
/*
* 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);
}

Binary file not shown.

View File

@ -0,0 +1,954 @@
/*
* 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/types.h>
#include <asm/cacheflush.h>
#include <linux/slab.h>
#include <linux/aio.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/version.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
#include <linux/uio.h>
#endif
#include "libxdma_api.h"
#include "xdma_cdev.h"
#include "cdev_sgdma.h"
#include "xdma_thread.h"
/* Module Parameters */
unsigned int h2c_timeout = 10;
module_param(h2c_timeout, uint, 0644);
MODULE_PARM_DESC(h2c_timeout, "H2C sgdma timeout in seconds, default is 10 sec.");
unsigned int c2h_timeout = 10;
module_param(c2h_timeout, uint, 0644);
MODULE_PARM_DESC(c2h_timeout, "C2H sgdma timeout in seconds, default is 10 sec.");
extern struct kmem_cache *cdev_cache;
static void char_sgdma_unmap_user_buf(struct xdma_io_cb *cb, bool write);
static void async_io_handler(unsigned long cb_hndl, int err)
{
struct xdma_cdev *xcdev;
struct xdma_engine *engine;
struct xdma_dev *xdev;
struct xdma_io_cb *cb = (struct xdma_io_cb *)cb_hndl;
struct cdev_async_io *caio = (struct cdev_async_io *)cb->private;
ssize_t numbytes = 0;
ssize_t res, res2;
int lock_stat;
int rv;
if (caio == NULL) {
pr_err("Invalid work struct\n");
return;
}
xcdev = (struct xdma_cdev *)caio->iocb->ki_filp->private_data;
rv = xcdev_check(__func__, xcdev, 1);
if (rv < 0)
return;
/* Safeguarding for cancel requests */
lock_stat = spin_trylock(&caio->lock);
if (!lock_stat) {
pr_err("caio lock not acquired\n");
goto skip_dev_lock;
}
if (false != caio->cancel) {
pr_err("skipping aio\n");
goto skip_tran;
}
engine = xcdev->engine;
xdev = xcdev->xdev;
if (!err)
numbytes = xdma_xfer_completion((void *)cb, xdev,
engine->channel, cb->write, cb->ep_addr,
&cb->sgt, 0,
cb->write ? h2c_timeout * 1000 :
c2h_timeout * 1000);
char_sgdma_unmap_user_buf(cb, cb->write);
caio->res2 |= (err < 0) ? err : 0;
if (caio->res2)
caio->err_cnt++;
caio->cmpl_cnt++;
caio->res += numbytes;
if (caio->cmpl_cnt == caio->req_cnt) {
res = caio->res;
res2 = caio->res2;
#if defined(RHEL_RELEASE_CODE)
#if (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(9, 4))
caio->iocb->ki_complete(caio->iocb, caio->err_cnt ? res2 : res);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0)
caio->iocb->ki_complete(caio->iocb, caio->err_cnt ? res2 : res);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
caio->iocb->ki_complete(caio->iocb, res, res2);
#else
aio_complete(caio->iocb, res, res2);
#endif
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0)
caio->iocb->ki_complete(caio->iocb, caio->err_cnt ? res2 : res);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
caio->iocb->ki_complete(caio->iocb, res, res2);
#else
aio_complete(caio->iocb, res, res2);
#endif
skip_tran:
spin_unlock(&caio->lock);
kmem_cache_free(cdev_cache, caio);
kfree(cb);
return;
}
spin_unlock(&caio->lock);
return;
skip_dev_lock:
#if defined(RHEL_RELEASE_CODE)
#if (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(9, 4))
caio->iocb->ki_complete(caio->iocb, caio->err_cnt ? res2 : res);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0)
caio->iocb->ki_complete(caio->iocb, -EBUSY);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
caio->iocb->ki_complete(caio->iocb, numbytes, -EBUSY);
#else
aio_complete(caio->iocb, numbytes, -EBUSY);
#endif
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0)
caio->iocb->ki_complete(caio->iocb, -EBUSY);
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
caio->iocb->ki_complete(caio->iocb, numbytes, -EBUSY);
#else
aio_complete(caio->iocb, numbytes, -EBUSY);
#endif
kmem_cache_free(cdev_cache, caio);
}
/*
* character device file operations for SG DMA engine
*/
static loff_t char_sgdma_llseek(struct file *file, loff_t off, int whence)
{
loff_t newpos = 0;
switch (whence) {
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = file->f_pos + off;
break;
case 2: /* SEEK_END, @TODO should work from end of address space */
newpos = UINT_MAX + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0)
return -EINVAL;
file->f_pos = newpos;
dbg_fops("%s: pos=%lld\n", __func__, (signed long long)newpos);
#if 0
pr_err("0x%p, off %lld, whence %d -> pos %lld.\n",
file, (signed long long)off, whence, (signed long long)off);
#endif
return newpos;
}
/* char_sgdma_read_write() -- Read from or write to the device
*
* @buf userspace buffer
* @count number of bytes in the userspace buffer
* @pos byte-address in device
* @dir_to_device If !0, a write to the device is performed
*
* Iterate over the userspace buffer, taking at most 255 * PAGE_SIZE bytes for
* each DMA transfer.
*
* For each transfer, get the user pages, build a sglist, map, build a
* descriptor table. submit the transfer. wait for the interrupt handler
* to wake us on completion.
*/
static int check_transfer_align(struct xdma_engine *engine,
const char __user *buf, size_t count, loff_t pos, int sync)
{
if (!engine) {
pr_err("Invalid DMA engine\n");
return -EINVAL;
}
/* AXI ST or AXI MM non-incremental addressing mode? */
if (engine->non_incr_addr) {
int buf_lsb = (int)((uintptr_t)buf) & (engine->addr_align - 1);
size_t len_lsb = count & ((size_t)engine->len_granularity - 1);
int pos_lsb = (int)pos & (engine->addr_align - 1);
dbg_tfr("AXI ST or MM non-incremental\n");
dbg_tfr("buf_lsb = %d, pos_lsb = %d, len_lsb = %ld\n", buf_lsb,
pos_lsb, len_lsb);
if (buf_lsb != 0) {
dbg_tfr("FAIL: non-aligned buffer address %p\n", buf);
return -EINVAL;
}
if ((pos_lsb != 0) && (sync)) {
dbg_tfr("FAIL: non-aligned AXI MM FPGA addr 0x%llx\n",
(unsigned long long)pos);
return -EINVAL;
}
if (len_lsb != 0) {
dbg_tfr("FAIL: len %d is not a multiple of %d\n",
(int)count,
(int)engine->len_granularity);
return -EINVAL;
}
/* AXI MM incremental addressing mode */
} else {
int buf_lsb = (int)((uintptr_t)buf) & (engine->addr_align - 1);
int pos_lsb = (int)pos & (engine->addr_align - 1);
if (buf_lsb != pos_lsb) {
dbg_tfr("FAIL: Misalignment error\n");
dbg_tfr("host addr %p, FPGA addr 0x%llx\n", buf, pos);
return -EINVAL;
}
}
return 0;
}
/*
* Map a user memory range into a scatterlist
* Returns the number of scatterlist entries used or -errno on error.
*/
static inline void xdma_io_cb_release(struct xdma_io_cb *cb)
{
int i;
for (i = 0; i < cb->pages_nr; i++)
put_page(cb->pages[i]);
sg_free_table(&cb->sgt);
kfree(cb->pages);
memset(cb, 0, sizeof(*cb));
}
static void char_sgdma_unmap_user_buf(struct xdma_io_cb *cb, bool write)
{
int i;
sg_free_table(&cb->sgt);
if (!cb->pages || !cb->pages_nr)
return;
for (i = 0; i < cb->pages_nr; i++) {
if (cb->pages[i]) {
if (!write)
set_page_dirty_lock(cb->pages[i]);
put_page(cb->pages[i]);
} else
break;
}
if (i != cb->pages_nr)
pr_info("sgl pages %d/%u.\n", i, cb->pages_nr);
kfree(cb->pages);
cb->pages = NULL;
}
static int char_sgdma_map_user_buf_to_sgl(struct xdma_io_cb *cb, bool write)
{
struct sg_table *sgt = &cb->sgt;
unsigned long len = cb->len;
void __user *buf = cb->buf;
struct scatterlist *sg;
unsigned int pages_nr = (((unsigned long)buf + len + PAGE_SIZE - 1) -
((unsigned long)buf & PAGE_MASK))
>> PAGE_SHIFT;
int i;
int rv;
if (pages_nr == 0)
return -EINVAL;
if (sg_alloc_table(sgt, pages_nr, GFP_KERNEL)) {
pr_err("sgl OOM.\n");
return -ENOMEM;
}
cb->pages = kcalloc(pages_nr, sizeof(struct page *), GFP_KERNEL);
if (!cb->pages) {
pr_err("pages OOM.\n");
rv = -ENOMEM;
goto err_out;
}
rv = get_user_pages_fast((unsigned long)buf, pages_nr, 1/* write */,
cb->pages);
/* No pages were pinned */
if (rv < 0) {
pr_err("unable to pin down %u user pages, %d.\n",
pages_nr, rv);
goto err_out;
}
/* Less pages pinned than wanted */
if (rv != pages_nr) {
pr_err("unable to pin down all %u user pages, %d.\n",
pages_nr, rv);
cb->pages_nr = rv;
rv = -EFAULT;
goto err_out;
}
for (i = 1; i < pages_nr; i++) {
if (cb->pages[i - 1] == cb->pages[i]) {
pr_err("duplicate pages, %d, %d.\n",
i - 1, i);
rv = -EFAULT;
cb->pages_nr = pages_nr;
goto err_out;
}
}
sg = sgt->sgl;
for (i = 0; i < pages_nr; i++, sg = sg_next(sg)) {
unsigned int offset = offset_in_page(buf);
unsigned int nbytes =
min_t(unsigned int, PAGE_SIZE - offset, len);
flush_dcache_page(cb->pages[i]);
sg_set_page(sg, cb->pages[i], nbytes, offset);
buf += nbytes;
len -= nbytes;
}
if (len) {
pr_err("Invalid user buffer length. Cannot map to sgl\n");
return -EINVAL;
}
cb->pages_nr = pages_nr;
return 0;
err_out:
char_sgdma_unmap_user_buf(cb, write);
return rv;
}
static ssize_t char_sgdma_read_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos, bool write)
{
int rv;
ssize_t res = 0;
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
struct xdma_dev *xdev;
struct xdma_engine *engine;
struct xdma_io_cb cb;
rv = xcdev_check(__func__, xcdev, 1);
if (rv < 0)
return rv;
xdev = xcdev->xdev;
engine = xcdev->engine;
dbg_tfr("file 0x%p, priv 0x%p, buf 0x%p,%llu, pos %llu, W %d, %s.\n",
file, file->private_data, buf, (u64)count, (u64)*pos, write,
engine->name);
if ((write && engine->dir != DMA_TO_DEVICE) ||
(!write && engine->dir != DMA_FROM_DEVICE)) {
pr_err("r/w mismatch. W %d, dir %d.\n",
write, engine->dir);
return -EINVAL;
}
rv = check_transfer_align(engine, buf, count, *pos, 1);
if (rv) {
pr_info("Invalid transfer alignment detected\n");
return rv;
}
memset(&cb, 0, sizeof(struct xdma_io_cb));
cb.buf = (char __user *)buf;
cb.len = count;
cb.ep_addr = (u64)*pos;
cb.write = write;
rv = char_sgdma_map_user_buf_to_sgl(&cb, write);
if (rv < 0)
return rv;
res = xdma_xfer_submit(xdev, engine->channel, write, *pos, &cb.sgt,
0, write ? h2c_timeout * 1000 :
c2h_timeout * 1000);
char_sgdma_unmap_user_buf(&cb, write);
return res;
}
static ssize_t char_sgdma_write(struct file *file, const char __user *buf,
size_t count, loff_t *pos)
{
return char_sgdma_read_write(file, buf, count, pos, 1);
}
static ssize_t char_sgdma_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
return char_sgdma_read_write(file, buf, count, pos, 0);
}
static ssize_t cdev_aio_write(struct kiocb *iocb, const struct iovec *io,
unsigned long count, loff_t pos)
{
struct xdma_cdev *xcdev = (struct xdma_cdev *)
iocb->ki_filp->private_data;
struct cdev_async_io *caio;
struct xdma_engine *engine;
struct xdma_dev *xdev;
int rv;
unsigned long i;
if (!xcdev) {
pr_info("file 0x%p, xcdev NULL, %llu, pos %llu, W %d.\n",
iocb->ki_filp, (u64)count, (u64)pos, 1);
return -EINVAL;
}
engine = xcdev->engine;
xdev = xcdev->xdev;
if (engine->dir != DMA_TO_DEVICE) {
pr_err("r/w mismatch. WRITE, dir %d.\n",
engine->dir);
return -EINVAL;
}
caio = kmem_cache_alloc(cdev_cache, GFP_KERNEL);
memset(caio, 0, sizeof(struct cdev_async_io));
caio->cb = kzalloc(count * (sizeof(struct xdma_io_cb)), GFP_KERNEL);
spin_lock_init(&caio->lock);
iocb->private = caio;
caio->iocb = iocb;
caio->write = true;
caio->cancel = false;
caio->req_cnt = count;
for (i = 0; i < count; i++) {
memset(&(caio->cb[i]), 0, sizeof(struct xdma_io_cb));
caio->cb[i].buf = io[i].iov_base;
caio->cb[i].len = io[i].iov_len;
caio->cb[i].ep_addr = (u64)pos;
caio->cb[i].write = true;
caio->cb[i].private = caio;
caio->cb[i].io_done = async_io_handler;
rv = check_transfer_align(engine, caio->cb[i].buf,
caio->cb[i].len, pos, 1);
if (rv) {
pr_info("Invalid transfer alignment detected\n");
kmem_cache_free(cdev_cache, caio);
return rv;
}
rv = char_sgdma_map_user_buf_to_sgl(&caio->cb[i], true);
if (rv < 0)
return rv;
rv = xdma_xfer_submit_nowait((void *)&caio->cb[i], xdev,
engine->channel, caio->cb[i].write,
caio->cb[i].ep_addr, &caio->cb[i].sgt,
0, h2c_timeout * 1000);
}
if (engine->cmplthp)
xdma_kthread_wakeup(engine->cmplthp);
return -EIOCBQUEUED;
}
static ssize_t cdev_aio_read(struct kiocb *iocb, const struct iovec *io,
unsigned long count, loff_t pos)
{
struct xdma_cdev *xcdev = (struct xdma_cdev *)
iocb->ki_filp->private_data;
struct cdev_async_io *caio;
struct xdma_engine *engine;
struct xdma_dev *xdev;
int rv;
unsigned long i;
if (!xcdev) {
pr_info("file 0x%p, xcdev NULL, %llu, pos %llu, W %d.\n",
iocb->ki_filp, (u64)count, (u64)pos, 1);
return -EINVAL;
}
engine = xcdev->engine;
xdev = xcdev->xdev;
if (engine->dir != DMA_FROM_DEVICE) {
pr_err("r/w mismatch. READ, dir %d.\n",
engine->dir);
return -EINVAL;
}
caio = kmem_cache_alloc(cdev_cache, GFP_KERNEL);
memset(caio, 0, sizeof(struct cdev_async_io));
caio->cb = kzalloc(count * (sizeof(struct xdma_io_cb)), GFP_KERNEL);
spin_lock_init(&caio->lock);
iocb->private = caio;
caio->iocb = iocb;
caio->write = false;
caio->cancel = false;
caio->req_cnt = count;
for (i = 0; i < count; i++) {
memset(&(caio->cb[i]), 0, sizeof(struct xdma_io_cb));
caio->cb[i].buf = io[i].iov_base;
caio->cb[i].len = io[i].iov_len;
caio->cb[i].ep_addr = (u64)pos;
caio->cb[i].write = false;
caio->cb[i].private = caio;
caio->cb[i].io_done = async_io_handler;
rv = check_transfer_align(engine, caio->cb[i].buf,
caio->cb[i].len, pos, 1);
if (rv) {
pr_info("Invalid transfer alignment detected\n");
kmem_cache_free(cdev_cache, caio);
return rv;
}
rv = char_sgdma_map_user_buf_to_sgl(&caio->cb[i], true);
if (rv < 0)
return rv;
rv = xdma_xfer_submit_nowait((void *)&caio->cb[i], xdev,
engine->channel, caio->cb[i].write,
caio->cb[i].ep_addr, &caio->cb[i].sgt,
0, c2h_timeout * 1000);
}
if (engine->cmplthp)
xdma_kthread_wakeup(engine->cmplthp);
return -EIOCBQUEUED;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
static ssize_t cdev_write_iter(struct kiocb *iocb, struct iov_iter *io)
{
#if defined(RHEL_RELEASE_CODE)
#if (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(9, 4))
return cdev_aio_write(iocb, iter_iov(io), io->nr_segs, io->iov_offset);
#else
return cdev_aio_write(iocb, io->iov, io->nr_segs, io->iov_offset);
#endif
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
return cdev_aio_write(iocb, iter_iov(io), io->nr_segs, io->iov_offset);
#else
return cdev_aio_write(iocb, io->iov, io->nr_segs, io->iov_offset);
#endif
}
static ssize_t cdev_read_iter(struct kiocb *iocb, struct iov_iter *io)
{
#if defined(RHEL_RELEASE_CODE)
#if (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(9, 4))
return cdev_aio_read(iocb, iter_iov(io), io->nr_segs, io->iov_offset);
#else
return cdev_aio_read(iocb, io->iov, io->nr_segs, io->iov_offset);
#endif
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
return cdev_aio_read(iocb, iter_iov(io), io->nr_segs, io->iov_offset);
#else
return cdev_aio_read(iocb, io->iov, io->nr_segs, io->iov_offset);
#endif
}
#endif
static int ioctl_do_perf_start(struct xdma_engine *engine, unsigned long arg)
{
int rv;
struct xdma_dev *xdev;
if (!engine) {
pr_err("Invalid DMA engine\n");
return -EINVAL;
}
xdev = engine->xdev;
if (!xdev) {
pr_err("Invalid xdev\n");
return -EINVAL;
}
/* performance measurement already running on this engine? */
if (engine->xdma_perf) {
dbg_perf("IOCTL_XDMA_PERF_START failed!\n");
dbg_perf("Perf measurement already seems to be running!\n");
return -EBUSY;
}
engine->xdma_perf = kzalloc(sizeof(struct xdma_performance_ioctl),
GFP_KERNEL);
if (!engine->xdma_perf)
return -ENOMEM;
rv = copy_from_user(engine->xdma_perf,
(struct xdma_performance_ioctl __user *)arg,
sizeof(struct xdma_performance_ioctl));
if (rv < 0) {
dbg_perf("Failed to copy from user space 0x%lx\n", arg);
return -EINVAL;
}
if (engine->xdma_perf->version != IOCTL_XDMA_PERF_V1) {
dbg_perf("Unsupported IOCTL version %d\n",
engine->xdma_perf->version);
return -EINVAL;
}
enable_perf(engine);
dbg_perf("transfer_size = %d\n", engine->xdma_perf->transfer_size);
/* initialize wait queue */
#if HAS_SWAKE_UP
init_swait_queue_head(&engine->xdma_perf_wq);
#else
init_waitqueue_head(&engine->xdma_perf_wq);
#endif
rv = xdma_performance_submit(xdev, engine);
if (rv < 0)
pr_err("Failed to submit dma performance\n");
return rv;
}
static int ioctl_do_perf_stop(struct xdma_engine *engine, unsigned long arg)
{
struct xdma_transfer *transfer = NULL;
int rv;
if (!engine) {
pr_err("Invalid DMA engine\n");
return -EINVAL;
}
dbg_perf("IOCTL_XDMA_PERF_STOP\n");
/* no performance measurement running on this engine? */
if (!engine->xdma_perf) {
dbg_perf("No measurement in progress\n");
return -EINVAL;
}
/* stop measurement */
transfer = engine_cyclic_stop(engine);
if (!transfer) {
pr_err("Failed to stop cyclic transfer\n");
return -EINVAL;
}
dbg_perf("Waiting for measurement to stop\n");
get_perf_stats(engine);
rv = copy_to_user((void __user *)arg, engine->xdma_perf,
sizeof(struct xdma_performance_ioctl));
if (rv) {
dbg_perf("Error copying result to user\n");
return rv;
}
kfree(transfer);
kfree(engine->xdma_perf);
engine->xdma_perf = NULL;
return 0;
}
static int ioctl_do_perf_get(struct xdma_engine *engine, unsigned long arg)
{
int rc;
if (!engine) {
pr_err("Invalid DMA engine\n");
return -EINVAL;
}
dbg_perf("IOCTL_XDMA_PERF_GET\n");
if (engine->xdma_perf) {
get_perf_stats(engine);
rc = copy_to_user((void __user *)arg, engine->xdma_perf,
sizeof(struct xdma_performance_ioctl));
if (rc) {
dbg_perf("Error copying result to user\n");
return rc;
}
} else {
dbg_perf("engine->xdma_perf == NULL?\n");
return -EPROTO;
}
return 0;
}
static int ioctl_do_addrmode_set(struct xdma_engine *engine, unsigned long arg)
{
return engine_addrmode_set(engine, arg);
}
static int ioctl_do_addrmode_get(struct xdma_engine *engine, unsigned long arg)
{
int rv;
unsigned long src;
if (!engine) {
pr_err("Invalid DMA engine\n");
return -EINVAL;
}
src = !!engine->non_incr_addr;
dbg_perf("IOCTL_XDMA_ADDRMODE_GET\n");
rv = put_user(src, (int __user *)arg);
return rv;
}
static int ioctl_do_align_get(struct xdma_engine *engine, unsigned long arg)
{
if (!engine) {
pr_err("Invalid DMA engine\n");
return -EINVAL;
}
dbg_perf("IOCTL_XDMA_ALIGN_GET\n");
return put_user(engine->addr_align, (int __user *)arg);
}
static int ioctl_do_aperture_dma(struct xdma_engine *engine, unsigned long arg,
bool write)
{
struct xdma_aperture_ioctl io;
struct xdma_io_cb cb;
ssize_t res;
int rv;
rv = copy_from_user(&io, (struct xdma_aperture_ioctl __user *)arg,
sizeof(struct xdma_aperture_ioctl));
if (rv < 0) {
dbg_tfr("%s failed to copy from user space 0x%lx\n",
engine->name, arg);
return -EINVAL;
}
dbg_tfr("%s, W %d, buf 0x%lx,%lu, ep %llu, aperture %u.\n",
engine->name, write, io.buffer, io.len, io.ep_addr,
io.aperture);
if ((write && engine->dir != DMA_TO_DEVICE) ||
(!write && engine->dir != DMA_FROM_DEVICE)) {
pr_err("r/w mismatch. W %d, dir %d.\n", write, engine->dir);
return -EINVAL;
}
rv = check_transfer_align(engine, (char *)io.buffer, io.len,
io.ep_addr, 1);
if (rv) {
pr_info("Invalid transfer alignment detected\n");
return rv;
}
memset(&cb, 0, sizeof(struct xdma_io_cb));
cb.buf = (char __user *)io.buffer;
cb.len = io.len;
cb.ep_addr = io.ep_addr;
cb.write = write;
rv = char_sgdma_map_user_buf_to_sgl(&cb, write);
if (rv < 0)
return rv;
io.error = 0;
res = xdma_xfer_aperture(engine, write, io.ep_addr, io.aperture,
&cb.sgt, 0, write ? h2c_timeout * 1000 :
c2h_timeout * 1000);
char_sgdma_unmap_user_buf(&cb, write);
if (res < 0)
io.error = res;
else
io.done = res;
rv = copy_to_user((struct xdma_aperture_ioctl __user *)arg, &io,
sizeof(struct xdma_aperture_ioctl));
if (rv < 0) {
dbg_tfr("%s failed to copy to user space 0x%lx, %ld\n",
engine->name, arg, res);
return -EINVAL;
}
return io.error;
}
static long char_sgdma_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
struct xdma_dev *xdev;
struct xdma_engine *engine;
int rv = 0;
rv = xcdev_check(__func__, xcdev, 1);
if (rv < 0)
return rv;
xdev = xcdev->xdev;
engine = xcdev->engine;
switch (cmd) {
case IOCTL_XDMA_PERF_START:
rv = ioctl_do_perf_start(engine, arg);
break;
case IOCTL_XDMA_PERF_STOP:
rv = ioctl_do_perf_stop(engine, arg);
break;
case IOCTL_XDMA_PERF_GET:
rv = ioctl_do_perf_get(engine, arg);
break;
case IOCTL_XDMA_ADDRMODE_SET:
rv = ioctl_do_addrmode_set(engine, arg);
break;
case IOCTL_XDMA_ADDRMODE_GET:
rv = ioctl_do_addrmode_get(engine, arg);
break;
case IOCTL_XDMA_ALIGN_GET:
rv = ioctl_do_align_get(engine, arg);
break;
case IOCTL_XDMA_APERTURE_R:
rv = ioctl_do_aperture_dma(engine, arg, 0);
break;
case IOCTL_XDMA_APERTURE_W:
rv = ioctl_do_aperture_dma(engine, arg, 1);
break;
default:
dbg_perf("Unsupported operation\n");
rv = -EINVAL;
break;
}
return rv;
}
static int char_sgdma_open(struct inode *inode, struct file *file)
{
struct xdma_cdev *xcdev;
struct xdma_engine *engine;
char_open(inode, file);
xcdev = (struct xdma_cdev *)file->private_data;
engine = xcdev->engine;
if (engine->streaming && engine->dir == DMA_FROM_DEVICE) {
if (engine->device_open == 1)
return -EBUSY;
engine->device_open = 1;
engine->eop_flush = (file->f_flags & O_TRUNC) ? 1 : 0;
}
return 0;
}
static int char_sgdma_close(struct inode *inode, struct file *file)
{
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
struct xdma_engine *engine;
int rv;
rv = xcdev_check(__func__, xcdev, 1);
if (rv < 0)
return rv;
engine = xcdev->engine;
if (engine->streaming && engine->dir == DMA_FROM_DEVICE)
engine->device_open = 0;
return 0;
}
static const struct file_operations sgdma_fops = {
.owner = THIS_MODULE,
.open = char_sgdma_open,
.release = char_sgdma_close,
.write = char_sgdma_write,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
.write_iter = cdev_write_iter,
#else
.aio_write = cdev_aio_write,
#endif
.read = char_sgdma_read,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 16, 0)
.read_iter = cdev_read_iter,
#else
.aio_read = cdev_aio_read,
#endif
.unlocked_ioctl = char_sgdma_ioctl,
.llseek = char_sgdma_llseek,
};
void cdev_sgdma_init(struct xdma_cdev *xcdev)
{
cdev_init(&xcdev->cdev, &sgdma_fops);
}

View File

@ -0,0 +1,82 @@
/*
* 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;
};
struct xdma_aperture_ioctl {
uint64_t ep_addr;
unsigned int aperture;
unsigned long buffer;
unsigned long len;
int error;
unsigned long done;
};
/* 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)
#define IOCTL_XDMA_APERTURE_R _IOW('q', 7, struct xdma_aperture_ioctl *)
#define IOCTL_XDMA_APERTURE_W _IOW('q', 8, struct xdma_aperture_ioctl *)
#endif /* _XDMA_IOCALLS_POSIX_H_ */

Binary file not shown.

View File

@ -0,0 +1,250 @@
/*
* 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 __iomem *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 __iomem *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 __iomem *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 = 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,
(const char __user *)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,
(const char __user *)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)
break;
memcpy(tdo_buf + bytes, &tdo_store, shift_bytes);
}
if (rv < 0)
goto unlock;
/* if testing bar access swap tdi and tdo bufferes to "loopback" */
if (opcode == 0x2) {
unsigned char *tmp = tdo_buf;
tdo_buf = tdi_buf;
tdi_buf = tmp;
}
rv = copy_to_user(xvc_obj.tdo_buf, (const void *)tdo_buf, total_bytes);
if (rv)
pr_info("copy back tdo_buf failed: %d/%u.\n", rv, total_bytes);
unlock:
#if HAS_MMIOWB
mmiowb();
#endif
spin_unlock(&xcdev->lock);
cleanup:
kfree(buffer);
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);
}

View File

@ -0,0 +1,42 @@
/*
* 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>
/*
* the bar offset can be changed at compile time
*/
#define XVC_BAR_OFFSET_DFLT 0x40000 /* DSA 4.0 */
#define XVC_MAGIC 0x58564344 // "XVCD"
struct xvc_ioc {
unsigned int opcode;
unsigned int length;
const char __user *tms_buf;
const char __user *tdi_buf;
void __user *tdo_buf;
};
#define XDMA_IOCXVC _IOWR(XVC_MAGIC, 1, struct xvc_ioc)
#endif /* __XVC_IOCTL_H__ */

Binary file not shown.

File diff suppressed because it is too large Load Diff

699
xdma/linux-kernel/xdma/libxdma.h Executable file
View File

@ -0,0 +1,699 @@
/*
* 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_LIB_H
#define XDMA_LIB_H
#include <linux/version.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/dma-mapping.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/workqueue.h>
/* Add compatibility checking for RHEL versions */
#if defined(RHEL_RELEASE_CODE)
# define ACCESS_OK_2_ARGS (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(8, 0))
#else
# define ACCESS_OK_2_ARGS (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 0, 0))
#endif
#if defined(RHEL_RELEASE_CODE)
# define HAS_MMIOWB (RHEL_RELEASE_CODE <= RHEL_RELEASE_VERSION(8, 0))
#else
# define HAS_MMIOWB (LINUX_VERSION_CODE <= KERNEL_VERSION(5, 1, 0))
#endif
#if defined(RHEL_RELEASE_CODE)
# define HAS_SWAKE_UP_ONE (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(8, 0))
# define HAS_SWAKE_UP (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(8, 0))
#else
# define HAS_SWAKE_UP_ONE (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0))
# define HAS_SWAKE_UP (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0))
#endif
#if defined(RHEL_RELEASE_CODE)
# define PCI_AER_NAMECHANGE (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(8, 3))
#else
# define PCI_AER_NAMECHANGE (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0))
#endif
#if HAS_SWAKE_UP
#include <linux/swait.h>
#endif
/*
* if the config bar is fixed, the driver does not neeed to search through
* all of the bars
*/
//#define XDMA_CONFIG_BAR_NUM 1
/* SECTION: Preprocessor macros/constants */
#define XDMA_BAR_NUM (6)
/* maximum amount of register space to map */
#define XDMA_BAR_SIZE (0x8000UL)
/* Use this definition to poll several times between calls to schedule */
#define NUM_POLLS_PER_SCHED 100
#define XDMA_CHANNEL_NUM_MAX (4)
/*
* interrupts per engine, rad2_vul.sv:237
* .REG_IRQ_OUT (reg_irq_from_ch[(channel*2) +: 2]),
*/
#define XDMA_ENG_IRQ_NUM (1)
#define XDMA_MAX_ADJ_BLOCK_SIZE 0x40
#define XDMA_PAGE_SIZE 0x1000
#define RX_STATUS_EOP (1)
/* Target internal components on XDMA control BAR */
#define XDMA_OFS_INT_CTRL (0x2000UL)
#define XDMA_OFS_CONFIG (0x3000UL)
/* maximum number of desc per transfer request */
#define XDMA_ENGINE_XFER_MAX_DESC 0x800
#define XDMA_ENGINE_CREDIT_XFER_MAX_DESC 0x3FF
/* maximum size of a single DMA transfer descriptor */
#define XDMA_DESC_BLEN_BITS 28
#define XDMA_DESC_BLEN_MAX ((1 << (XDMA_DESC_BLEN_BITS)) - 1)
/* bits of the SG DMA control register */
#define XDMA_CTRL_RUN_STOP (1UL << 0)
#define XDMA_CTRL_IE_DESC_STOPPED (1UL << 1)
#define XDMA_CTRL_IE_DESC_COMPLETED (1UL << 2)
#define XDMA_CTRL_IE_DESC_ALIGN_MISMATCH (1UL << 3)
#define XDMA_CTRL_IE_MAGIC_STOPPED (1UL << 4)
#define XDMA_CTRL_IE_IDLE_STOPPED (1UL << 6)
#define XDMA_CTRL_IE_READ_ERROR (0x1FUL << 9)
#define XDMA_CTRL_IE_DESC_ERROR (0x1FUL << 19)
#define XDMA_CTRL_NON_INCR_ADDR (1UL << 25)
#define XDMA_CTRL_POLL_MODE_WB (1UL << 26)
#define XDMA_CTRL_STM_MODE_WB (1UL << 27)
/* bits of the SG DMA status register */
#define XDMA_STAT_BUSY (1UL << 0)
#define XDMA_STAT_DESC_STOPPED (1UL << 1)
#define XDMA_STAT_DESC_COMPLETED (1UL << 2)
#define XDMA_STAT_ALIGN_MISMATCH (1UL << 3)
#define XDMA_STAT_MAGIC_STOPPED (1UL << 4)
#define XDMA_STAT_INVALID_LEN (1UL << 5)
#define XDMA_STAT_IDLE_STOPPED (1UL << 6)
#define XDMA_STAT_COMMON_ERR_MASK \
(XDMA_STAT_ALIGN_MISMATCH | XDMA_STAT_MAGIC_STOPPED | \
XDMA_STAT_INVALID_LEN)
/* desc_error, C2H & H2C */
#define XDMA_STAT_DESC_UNSUPP_REQ (1UL << 19)
#define XDMA_STAT_DESC_COMPL_ABORT (1UL << 20)
#define XDMA_STAT_DESC_PARITY_ERR (1UL << 21)
#define XDMA_STAT_DESC_HEADER_EP (1UL << 22)
#define XDMA_STAT_DESC_UNEXP_COMPL (1UL << 23)
#define XDMA_STAT_DESC_ERR_MASK \
(XDMA_STAT_DESC_UNSUPP_REQ | XDMA_STAT_DESC_COMPL_ABORT | \
XDMA_STAT_DESC_PARITY_ERR | XDMA_STAT_DESC_HEADER_EP | \
XDMA_STAT_DESC_UNEXP_COMPL)
/* read error: H2C */
#define XDMA_STAT_H2C_R_UNSUPP_REQ (1UL << 9)
#define XDMA_STAT_H2C_R_COMPL_ABORT (1UL << 10)
#define XDMA_STAT_H2C_R_PARITY_ERR (1UL << 11)
#define XDMA_STAT_H2C_R_HEADER_EP (1UL << 12)
#define XDMA_STAT_H2C_R_UNEXP_COMPL (1UL << 13)
#define XDMA_STAT_H2C_R_ERR_MASK \
(XDMA_STAT_H2C_R_UNSUPP_REQ | XDMA_STAT_H2C_R_COMPL_ABORT | \
XDMA_STAT_H2C_R_PARITY_ERR | XDMA_STAT_H2C_R_HEADER_EP | \
XDMA_STAT_H2C_R_UNEXP_COMPL)
/* write error, H2C only */
#define XDMA_STAT_H2C_W_DECODE_ERR (1UL << 14)
#define XDMA_STAT_H2C_W_SLAVE_ERR (1UL << 15)
#define XDMA_STAT_H2C_W_ERR_MASK \
(XDMA_STAT_H2C_W_DECODE_ERR | XDMA_STAT_H2C_W_SLAVE_ERR)
/* read error: C2H */
#define XDMA_STAT_C2H_R_DECODE_ERR (1UL << 9)
#define XDMA_STAT_C2H_R_SLAVE_ERR (1UL << 10)
#define XDMA_STAT_C2H_R_ERR_MASK \
(XDMA_STAT_C2H_R_DECODE_ERR | XDMA_STAT_C2H_R_SLAVE_ERR)
/* all combined */
#define XDMA_STAT_H2C_ERR_MASK \
(XDMA_STAT_COMMON_ERR_MASK | XDMA_STAT_DESC_ERR_MASK | \
XDMA_STAT_H2C_R_ERR_MASK | XDMA_STAT_H2C_W_ERR_MASK)
#define XDMA_STAT_C2H_ERR_MASK \
(XDMA_STAT_COMMON_ERR_MASK | XDMA_STAT_DESC_ERR_MASK | \
XDMA_STAT_C2H_R_ERR_MASK)
/* bits of the SGDMA descriptor control field */
#define XDMA_DESC_STOPPED (1UL << 0)
#define XDMA_DESC_COMPLETED (1UL << 1)
#define XDMA_DESC_EOP (1UL << 4)
#define XDMA_PERF_RUN (1UL << 0)
#define XDMA_PERF_CLEAR (1UL << 1)
#define XDMA_PERF_AUTO (1UL << 2)
#define MAGIC_ENGINE 0xEEEEEEEEUL
#define MAGIC_DEVICE 0xDDDDDDDDUL
/* upper 16-bits of engine identifier register */
#define XDMA_ID_H2C 0x1fc0U
#define XDMA_ID_C2H 0x1fc1U
/* for C2H AXI-ST mode */
#define CYCLIC_RX_PAGES_MAX 256
#define LS_BYTE_MASK 0x000000FFUL
#define BLOCK_ID_MASK 0xFFF00000
#define BLOCK_ID_HEAD 0x1FC00000
#define IRQ_BLOCK_ID 0x1fc20000UL
#define CONFIG_BLOCK_ID 0x1fc30000UL
#define WB_COUNT_MASK 0x00ffffffUL
#define WB_ERR_MASK (1UL << 31)
#define POLL_TIMEOUT_SECONDS 10
#define MAX_USER_IRQ 16
#define MAX_DESC_BUS_ADDR (0xffffffffULL)
#define DESC_MAGIC 0xAD4B0000UL
#define C2H_WB 0x52B4UL
#define MAX_NUM_ENGINES (XDMA_CHANNEL_NUM_MAX * 2)
#define H2C_CHANNEL_OFFSET 0x1000
#define SGDMA_OFFSET_FROM_CHANNEL 0x4000
#define CHANNEL_SPACING 0x100
#define TARGET_SPACING 0x1000
#define BYPASS_MODE_SPACING 0x0100
/* obtain the 32 most significant (high) bits of a 32-bit or 64-bit address */
#define PCI_DMA_H(addr) ((addr >> 16) >> 16)
/* obtain the 32 least significant (low) bits of a 32-bit or 64-bit address */
#define PCI_DMA_L(addr) (addr & 0xffffffffUL)
#ifndef VM_RESERVED
#define VMEM_FLAGS (VM_IO | VM_DONTEXPAND | VM_DONTDUMP)
#else
#define VMEM_FLAGS (VM_IO | VM_RESERVED)
#endif
#ifdef __LIBXDMA_DEBUG__
#define dbg_io pr_err
#define dbg_fops pr_err
#define dbg_perf pr_err
#define dbg_sg pr_err
#define dbg_tfr pr_err
#define dbg_irq pr_err
#define dbg_init pr_err
#define dbg_desc pr_err
#else
/* disable debugging */
#define dbg_io(...)
#define dbg_fops(...)
#define dbg_perf(...)
#define dbg_sg(...)
#define dbg_tfr(...)
#define dbg_irq(...)
#define dbg_init(...)
#define dbg_desc(...)
#endif
/* SECTION: Enum definitions */
enum transfer_state {
TRANSFER_STATE_NEW = 0,
TRANSFER_STATE_SUBMITTED,
TRANSFER_STATE_COMPLETED,
TRANSFER_STATE_FAILED,
TRANSFER_STATE_ABORTED
};
enum shutdown_state {
ENGINE_SHUTDOWN_NONE = 0, /* No shutdown in progress */
ENGINE_SHUTDOWN_REQUEST = 1, /* engine requested to shutdown */
ENGINE_SHUTDOWN_IDLE = 2 /* engine has shutdown and is idle */
};
enum dev_capabilities {
CAP_64BIT_DMA = 2,
CAP_64BIT_DESC = 4,
CAP_ENGINE_WRITE = 8,
CAP_ENGINE_READ = 16
};
/* SECTION: Structure definitions */
struct xdma_io_cb {
void __user *buf;
size_t len;
void *private;
unsigned int pages_nr;
struct sg_table sgt;
struct page **pages;
/** total data size */
unsigned int count;
/** MM only, DDR/BRAM memory addr */
u64 ep_addr;
/** write: if write to the device */
struct xdma_request_cb *req;
u8 write:1;
void (*io_done)(unsigned long cb_hndl, int err);
};
struct config_regs {
u32 identifier;
u32 reserved_1[4];
u32 msi_enable;
};
/**
* SG DMA Controller status and control registers
*
* These registers make the control interface for DMA transfers.
*
* It sits in End Point (FPGA) memory BAR[0] for 32-bit or BAR[0:1] for 64-bit.
* It references the first descriptor which exists in Root Complex (PC) memory.
*
* @note The registers must be accessed using 32-bit (PCI DWORD) read/writes,
* and their values are in little-endian byte ordering.
*/
struct engine_regs {
u32 identifier;
u32 control;
u32 control_w1s;
u32 control_w1c;
u32 reserved_1[12]; /* padding */
u32 status;
u32 status_rc;
u32 completed_desc_count;
u32 alignments;
u32 reserved_2[14]; /* padding */
u32 poll_mode_wb_lo;
u32 poll_mode_wb_hi;
u32 interrupt_enable_mask;
u32 interrupt_enable_mask_w1s;
u32 interrupt_enable_mask_w1c;
u32 reserved_3[9]; /* padding */
u32 perf_ctrl;
u32 perf_cyc_lo;
u32 perf_cyc_hi;
u32 perf_dat_lo;
u32 perf_dat_hi;
u32 perf_pnd_lo;
u32 perf_pnd_hi;
} __packed;
struct engine_sgdma_regs {
u32 identifier;
u32 reserved_1[31]; /* padding */
/* bus address to first descriptor in Root Complex Memory */
u32 first_desc_lo;
u32 first_desc_hi;
/* number of adjacent descriptors at first_desc */
u32 first_desc_adjacent;
u32 credits;
} __packed;
struct msix_vec_table_entry {
u32 msi_vec_addr_lo;
u32 msi_vec_addr_hi;
u32 msi_vec_data_lo;
u32 msi_vec_data_hi;
} __packed;
struct msix_vec_table {
struct msix_vec_table_entry entry_list[32];
} __packed;
struct interrupt_regs {
u32 identifier;
u32 user_int_enable;
u32 user_int_enable_w1s;
u32 user_int_enable_w1c;
u32 channel_int_enable;
u32 channel_int_enable_w1s;
u32 channel_int_enable_w1c;
u32 reserved_1[9]; /* padding */
u32 user_int_request;
u32 channel_int_request;
u32 user_int_pending;
u32 channel_int_pending;
u32 reserved_2[12]; /* padding */
u32 user_msi_vector[8];
u32 channel_msi_vector[8];
} __packed;
struct sgdma_common_regs {
u32 padding[8];
u32 credit_mode_enable;
u32 credit_mode_enable_w1s;
u32 credit_mode_enable_w1c;
} __packed;
/* Structure for polled mode descriptor writeback */
struct xdma_poll_wb {
u32 completed_desc_count;
u32 reserved_1[7];
} __packed;
/**
* Descriptor for a single contiguous memory block transfer.
*
* Multiple descriptors are linked by means of the next pointer. An additional
* extra adjacent number gives the amount of extra contiguous descriptors.
*
* The descriptors are in root complex memory, and the bytes in the 32-bit
* words must be in little-endian byte ordering.
*/
struct xdma_desc {
u32 control;
u32 bytes; /* transfer length in bytes */
u32 src_addr_lo; /* source address (low 32-bit) */
u32 src_addr_hi; /* source address (high 32-bit) */
u32 dst_addr_lo; /* destination address (low 32-bit) */
u32 dst_addr_hi; /* destination address (high 32-bit) */
/*
* next descriptor in the single-linked list of descriptors;
* this is the PCIe (bus) address of the next descriptor in the
* root complex memory
*/
u32 next_lo; /* next desc address (low 32-bit) */
u32 next_hi; /* next desc address (high 32-bit) */
} __packed;
/* 32 bytes (four 32-bit words) or 64 bytes (eight 32-bit words) */
struct xdma_result {
u32 status;
u32 length;
u32 reserved_1[6]; /* padding */
} __packed;
struct sw_desc {
dma_addr_t addr;
unsigned int len;
};
/* Describes a (SG DMA) single transfer for the engine */
#define XFER_FLAG_NEED_UNMAP 0x1
#define XFER_FLAG_ST_C2H_EOP_RCVED 0x2 /* ST c2h only */
struct xdma_transfer {
struct list_head entry; /* queue of non-completed transfers */
struct xdma_desc *desc_virt; /* virt addr of the 1st descriptor */
struct xdma_result *res_virt; /* virt addr of result, c2h streaming */
dma_addr_t res_bus; /* bus addr for result descriptors */
dma_addr_t desc_bus; /* bus addr of the first descriptor */
int desc_adjacent; /* adjacent descriptors at desc_bus */
int desc_num; /* number of descriptors in transfer */
int desc_index; /* index for 1st desc. in transfer */
int desc_cmpl; /* completed descriptors */
int desc_cmpl_th; /* completed descriptor threshold */
enum dma_data_direction dir;
#if HAS_SWAKE_UP
struct swait_queue_head wq;
#else
wait_queue_head_t wq; /* wait queue for transfer completion */
#endif
enum transfer_state state; /* state of the transfer */
unsigned int flags;
int cyclic; /* flag if transfer is cyclic */
int last_in_request; /* flag if last within request */
unsigned int len;
struct sg_table *sgt;
struct xdma_io_cb *cb;
};
struct xdma_request_cb {
struct sg_table *sgt;
u64 ep_addr;
unsigned int aperture;
unsigned int total_len;
unsigned int offset;
struct scatterlist *sg;
unsigned int sg_idx;
unsigned int sg_offset;
/* Use two transfers in case single request needs to be split */
struct xdma_transfer tfer[2];
struct xdma_io_cb *cb;
unsigned int sw_desc_idx;
unsigned int sw_desc_cnt;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 8, 0)
struct sw_desc sdesc[];
#else
struct sw_desc sdesc[0];
#endif
};
struct xdma_engine {
unsigned long magic; /* structure ID for sanity checks */
struct xdma_dev *xdev; /* parent device */
char name[16]; /* name of this engine */
int version; /* version of this engine */
/* HW register address offsets */
struct engine_regs *regs; /* Control reg BAR offset */
struct engine_sgdma_regs *sgdma_regs; /* SGDAM reg BAR offset */
u32 bypass_offset; /* Bypass mode BAR offset */
/* Engine state, configuration and flags */
enum shutdown_state shutdown; /* engine shutdown mode */
enum dma_data_direction dir;
u8 addr_align; /* source/dest alignment in bytes */
u8 len_granularity; /* transfer length multiple */
u8 addr_bits; /* HW datapath address width */
u8 channel:2; /* engine indices */
u8 streaming:1;
u8 device_open:1; /* flag if engine node open, ST mode only */
u8 running:1; /* flag if the driver started engine */
u8 non_incr_addr:1; /* flag if non-incremental addressing used */
u8 eop_flush:1; /* st c2h only, flush up the data with eop */
u8 filler:1;
int max_extra_adj; /* descriptor prefetch capability */
int desc_dequeued; /* num descriptors of completed transfers */
u32 desc_max; /* max # descriptors per xfer */
u32 status; /* last known status of device */
/* only used for MSIX mode to store per-engine interrupt mask value */
u32 interrupt_enable_mask_value;
/* Transfer list management */
struct list_head transfer_list; /* queue of transfers */
/* Members applicable to AXI-ST C2H (cyclic) transfers */
struct xdma_result *cyclic_result;
dma_addr_t cyclic_result_bus; /* bus addr for transfer */
u8 *perf_buf_virt;
dma_addr_t perf_buf_bus; /* bus address */
/* Members associated with polled mode support */
u8 *poll_mode_addr_virt; /* virt addr for descriptor writeback */
dma_addr_t poll_mode_bus; /* bus addr for descriptor writeback */
/* Members associated with interrupt mode support */
#if HAS_SWAKE_UP
struct swait_queue_head shutdown_wq;
#else
wait_queue_head_t shutdown_wq; /* wait queue for shutdown sync */
#endif
spinlock_t lock; /* protects concurrent access */
int prev_cpu; /* remember CPU# of (last) locker */
int msix_irq_line; /* MSI-X vector for this engine */
u32 irq_bitmask; /* IRQ bit mask for this engine */
struct work_struct work; /* Work queue for interrupt handling */
struct mutex desc_lock; /* protects concurrent access */
dma_addr_t desc_bus;
struct xdma_desc *desc;
int desc_idx; /* current descriptor index */
int desc_used; /* total descriptors used */
/* for performance test support */
struct xdma_performance_ioctl *xdma_perf; /* perf test control */
#if HAS_SWAKE_UP
struct swait_queue_head xdma_perf_wq;
#else
wait_queue_head_t xdma_perf_wq; /* Perf test sync */
#endif
struct xdma_kthread *cmplthp;
/* completion status thread list for the queue */
struct list_head cmplthp_list;
/* pending work thread list */
/* cpu attached to intr_work */
unsigned int intr_work_cpu;
};
struct xdma_user_irq {
struct xdma_dev *xdev; /* parent device */
u8 user_idx; /* 0 ~ 15 */
u8 events_irq; /* accumulated IRQs */
spinlock_t events_lock; /* lock to safely update events_irq */
wait_queue_head_t events_wq; /* wait queue to sync waiting threads */
irq_handler_t handler;
void *dev;
};
/* XDMA PCIe device specific book-keeping */
#define XDEV_FLAG_OFFLINE 0x1
struct xdma_dev {
struct list_head list_head;
struct list_head rcu_node;
unsigned long magic; /* structure ID for sanity checks */
struct pci_dev *pdev; /* pci device struct from probe() */
int idx; /* dev index */
const char *mod_name; /* name of module owning the dev */
spinlock_t lock; /* protects concurrent access */
unsigned int flags;
/* PCIe BAR management */
void __iomem *bar[XDMA_BAR_NUM]; /* addresses for mapped BARs */
int user_bar_idx; /* BAR index of user logic */
int config_bar_idx; /* BAR index of XDMA config logic */
int bypass_bar_idx; /* BAR index of XDMA bypass logic */
int regions_in_use; /* flag if dev was in use during probe() */
int got_regions; /* flag if probe() obtained the regions */
int user_max;
int c2h_channel_max;
int h2c_channel_max;
/* Interrupt management */
int irq_count; /* interrupt counter */
int irq_line; /* flag if irq allocated successfully */
int msi_enabled; /* flag if msi was enabled for the device */
int msix_enabled; /* flag if msi-x was enabled for the device */
#if KERNEL_VERSION(4, 12, 0) > LINUX_VERSION_CODE
struct msix_entry entry[32]; /* msi-x vector/entry table */
#endif
struct xdma_user_irq user_irq[16]; /* user IRQ management */
unsigned int mask_irq_user;
/* XDMA engine management */
int engines_num; /* Total engine count */
u32 mask_irq_h2c;
u32 mask_irq_c2h;
struct xdma_engine engine_h2c[XDMA_CHANNEL_NUM_MAX];
struct xdma_engine engine_c2h[XDMA_CHANNEL_NUM_MAX];
/* SD_Accel specific */
enum dev_capabilities capabilities;
u64 feature_id;
};
static inline int xdma_device_flag_check(struct xdma_dev *xdev, unsigned int f)
{
unsigned long flags;
spin_lock_irqsave(&xdev->lock, flags);
if (xdev->flags & f) {
spin_unlock_irqrestore(&xdev->lock, flags);
return 1;
}
spin_unlock_irqrestore(&xdev->lock, flags);
return 0;
}
static inline int xdma_device_flag_test_n_set(struct xdma_dev *xdev,
unsigned int f)
{
unsigned long flags;
int rv = 0;
spin_lock_irqsave(&xdev->lock, flags);
if (xdev->flags & f) {
spin_unlock_irqrestore(&xdev->lock, flags);
rv = 1;
} else
xdev->flags |= f;
spin_unlock_irqrestore(&xdev->lock, flags);
return rv;
}
static inline void xdma_device_flag_set(struct xdma_dev *xdev, unsigned int f)
{
unsigned long flags;
spin_lock_irqsave(&xdev->lock, flags);
xdev->flags |= f;
spin_unlock_irqrestore(&xdev->lock, flags);
}
static inline void xdma_device_flag_clear(struct xdma_dev *xdev, unsigned int f)
{
unsigned long flags;
spin_lock_irqsave(&xdev->lock, flags);
xdev->flags &= ~f;
spin_unlock_irqrestore(&xdev->lock, flags);
}
void write_register(u32 value, void *iomem);
u32 read_register(void *iomem);
struct xdma_dev *xdev_find_by_pdev(struct pci_dev *pdev);
void xdma_device_offline(struct pci_dev *pdev, void *dev_handle);
void xdma_device_online(struct pci_dev *pdev, void *dev_handle);
int xdma_performance_submit(struct xdma_dev *xdev, struct xdma_engine *engine);
struct xdma_transfer *engine_cyclic_stop(struct xdma_engine *engine);
void enable_perf(struct xdma_engine *engine);
void get_perf_stats(struct xdma_engine *engine);
int engine_addrmode_set(struct xdma_engine *engine, unsigned long arg);
int engine_service_poll(struct xdma_engine *engine, u32 expected_desc_count);
ssize_t xdma_xfer_aperture(struct xdma_engine *engine, bool write, u64 ep_addr,
unsigned int aperture, struct sg_table *sgt,
bool dma_mapped, int timeout_ms);
#endif /* XDMA_LIB_H */

Binary file not shown.

View File

@ -0,0 +1 @@
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma.o

View File

@ -0,0 +1,35 @@
/*
* 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 2020
#define DRV_MOD_MINOR 2
#define DRV_MOD_PATCHLEVEL 2
#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__ */

Binary file not shown.

View File

@ -0,0 +1,9 @@
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/libxdma.o
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma_cdev.o
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/cdev_ctrl.o
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/cdev_events.o
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/cdev_sgdma.o
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/cdev_xvc.o
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/cdev_bypass.o
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma_mod.o
/home/colin/develop/xc7k480t/xdma/linux-kernel/xdma/xdma_thread.o

View File

@ -0,0 +1,229 @@
#include <linux/module.h>
#define INCLUDE_VERMAGIC
#include <linux/build-salt.h>
#include <linux/elfnote-lto.h>
#include <linux/export-internal.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>
#ifdef CONFIG_UNWINDER_ORC
#include <asm/orc_header.h>
ORC_HEADER;
#endif
BUILD_SALT;
BUILD_LTO_INFO;
MODULE_INFO(vermagic, VERMAGIC_STRING);
MODULE_INFO(name, KBUILD_MODNAME);
__visible struct module __this_module
__section(".gnu.linkonce.this_module") = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
#ifdef CONFIG_RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif
static const struct modversion_info ____versions[]
__used __section("__versions") = {
{ 0x587f22d7, "devmap_managed_key" },
{ 0x521dc434, "pci_save_state" },
{ 0xc1514a3b, "free_irq" },
{ 0xc80ab559, "swake_up_one" },
{ 0xc54884b6, "get_user_pages_fast" },
{ 0xa78af5f3, "ioread32" },
{ 0xe3ec2f2b, "alloc_chrdev_region" },
{ 0x88db9f48, "__check_object_size" },
{ 0x89973ae6, "param_ops_uint" },
{ 0x13c49cc2, "_copy_from_user" },
{ 0x64b40666, "pci_enable_device" },
{ 0x4a453f53, "iowrite32" },
{ 0x7f02188f, "__msecs_to_jiffies" },
{ 0x736f7fc9, "pci_enable_device_mem" },
{ 0xb29a5edc, "pci_iomap" },
{ 0xac4208d, "pci_alloc_irq_vectors" },
{ 0x656e4a6e, "snprintf" },
{ 0xc5b6f236, "queue_work_on" },
{ 0xc8c85086, "sg_free_table" },
{ 0x48d88a2c, "__SCT__preempt_schedule" },
{ 0x608741b5, "__init_swait_queue_head" },
{ 0x92540fbf, "finish_wait" },
{ 0x226c7554, "class_destroy" },
{ 0xc2e05886, "__pci_register_driver" },
{ 0xb8d1db87, "pci_disable_msi" },
{ 0x6df1aaf1, "kernel_sigaction" },
{ 0xb832d8db, "pci_request_regions" },
{ 0xe82c4c9f, "remap_pfn_range" },
{ 0x37a0cba, "kfree" },
{ 0xe8efec2e, "pcpu_hot" },
{ 0xc3f9cced, "__put_devmap_managed_page_refs" },
{ 0x8c26d495, "prepare_to_wait_event" },
{ 0xb3f7646e, "kthread_should_stop" },
{ 0xe2964344, "__wake_up" },
{ 0x83a6332b, "pci_irq_vector" },
{ 0x3e9bf5b0, "kmem_cache_create" },
{ 0x34db050b, "_raw_spin_lock_irqsave" },
{ 0xb19a5453, "__per_cpu_offset" },
{ 0xba8fbd64, "_raw_spin_lock" },
{ 0xd19158ca, "pci_unregister_driver" },
{ 0xcbd4898c, "fortify_panic" },
{ 0xbdfb6dbb, "__fentry__" },
{ 0x497449f1, "wake_up_process" },
{ 0x65487097, "__x86_indirect_thunk_rax" },
{ 0x122c3a7e, "_printk" },
{ 0x8bb21f8, "prepare_to_swait_event" },
{ 0x1d24c881, "___ratelimit" },
{ 0x8ddd8aad, "schedule_timeout" },
{ 0x1000e51, "schedule" },
{ 0x9cb986f2, "vmalloc_base" },
{ 0xf0fdf6cb, "__stack_chk_fail" },
{ 0xb2fd5ceb, "__put_user_4" },
{ 0x618911fc, "numa_node" },
{ 0x482d32e6, "kmem_cache_alloc" },
{ 0x87a21cb3, "__ubsan_handle_out_of_bounds" },
{ 0xb3f985a8, "sg_alloc_table" },
{ 0x1289fbda, "cdev_add" },
{ 0xbcb36fe4, "hugetlb_optimize_vmemmap_key" },
{ 0x7810d9b7, "pci_find_capability" },
{ 0xfe487975, "init_wait_entry" },
{ 0x98378a1d, "cc_mkdec" },
{ 0x4ca00502, "pci_enable_msi" },
{ 0x57bc19d2, "down_write" },
{ 0xce807a25, "up_write" },
{ 0x6091797f, "synchronize_rcu" },
{ 0x92d5838e, "request_threaded_irq" },
{ 0x397ec880, "device_create" },
{ 0x133353bd, "class_create" },
{ 0x4c03a563, "random_kmalloc_seed" },
{ 0x9d8e61c6, "finish_swait" },
{ 0x4dfa8d4b, "mutex_lock" },
{ 0x8b606a2, "kmem_cache_free" },
{ 0x9f38c153, "dma_alloc_attrs" },
{ 0x1b4dbcbc, "pci_read_config_word" },
{ 0x16986a45, "pci_aer_clear_nonfatal_status" },
{ 0x53a1e8d9, "_find_next_bit" },
{ 0x5a5a2271, "__cpu_online_mask" },
{ 0xd071fc87, "kthread_stop" },
{ 0xfef216eb, "_raw_spin_trylock" },
{ 0xcefb0c9f, "__mutex_init" },
{ 0xd35cce70, "_raw_spin_unlock_irqrestore" },
{ 0x1dd71947, "pci_iounmap" },
{ 0xb11b57f1, "pci_restore_state" },
{ 0xfb578fc5, "memset" },
{ 0xb3eafc7a, "pci_set_master" },
{ 0x5b8239ca, "__x86_return_thunk" },
{ 0x17de3d5, "nr_cpu_ids" },
{ 0x6b10bee1, "_copy_to_user" },
{ 0xd9a5ea54, "__init_waitqueue_head" },
{ 0x50467724, "kthread_bind" },
{ 0x1ed42f7b, "pcie_capability_clear_and_set_word_unlocked" },
{ 0x15ba50a6, "jiffies" },
{ 0x692d8599, "kthread_create_on_node" },
{ 0x49cd2686, "dma_set_coherent_mask" },
{ 0x3c3ff9fd, "sprintf" },
{ 0xa648e561, "__ubsan_handle_shift_out_of_bounds" },
{ 0x770930da, "dma_free_attrs" },
{ 0x999e8297, "vfree" },
{ 0x6091b333, "unregister_chrdev_region" },
{ 0x3213f038, "mutex_unlock" },
{ 0x3f77c96b, "pci_release_regions" },
{ 0xfbe215e4, "sg_next" },
{ 0x9172c975, "__folio_put" },
{ 0x6729d3df, "__get_user_4" },
{ 0xa8f6c39, "kobject_set_name" },
{ 0x1ec4740d, "device_destroy" },
{ 0x2cf56265, "__dynamic_pr_debug" },
{ 0x3a73c6d6, "set_page_dirty_lock" },
{ 0xe11ff268, "pci_disable_msix" },
{ 0xed2ada1c, "pci_disable_device" },
{ 0xe16cab4a, "boot_cpu_data" },
{ 0x5895035, "pcie_set_readrq" },
{ 0xa7b4b0e5, "dma_set_mask" },
{ 0x8e93c30e, "dma_unmap_sg_attrs" },
{ 0xd8dcb679, "kmalloc_trace" },
{ 0x46cf10eb, "cachemode2protval" },
{ 0xbb111df2, "pci_read_config_byte" },
{ 0x54b1fac6, "__ubsan_handle_load_invalid_value" },
{ 0xd6ee688f, "vmalloc" },
{ 0x3e8e968a, "pci_write_config_word" },
{ 0xb5b54b34, "_raw_spin_unlock" },
{ 0xd8287c5f, "cdev_init" },
{ 0xeb233a45, "__kmalloc" },
{ 0xe2c17b5d, "__SCT__might_resched" },
{ 0xb22ef59e, "kmalloc_caches" },
{ 0x7c127ea2, "cdev_del" },
{ 0xd5d30195, "kmem_cache_destroy" },
{ 0xcf876b96, "dma_map_sg_attrs" },
{ 0x2d3385d3, "system_wq" },
{ 0x2273f01b, "module_layout" },
};
MODULE_INFO(depends, "");
MODULE_ALIAS("pci:v000010EEd00009048sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009044sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009042sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009041sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd0000903Fsv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009038sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009028sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009018sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009034sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009024sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009014sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009032sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009022sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009012sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009031sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009021sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00009011sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008011sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008012sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008014sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008018sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008021sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008022sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008024sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008028sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008031sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008032sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008034sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00008038sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007011sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007012sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007014sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007018sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007021sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007022sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007024sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007028sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007031sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007032sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007034sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00007038sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00006828sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00006830sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00006928sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00006930sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00006A28sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00006A30sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00006D30sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00004808sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00004828sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00004908sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00004A28sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00004B28sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v000010EEd00002808sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v00001D0Fd0000F000sv*sd*bc*sc*i*");
MODULE_ALIAS("pci:v00001D0Fd0000F001sv*sd*bc*sc*i*");
MODULE_INFO(srcversion, "2EEB4A2EC40A3FC02ABA554");

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,642 @@
/*
* 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"
static struct class *g_xdma_class;
struct kmem_cache *cdev_cache;
enum cdev_type {
CHAR_USER,
CHAR_CTRL,
CHAR_XVC,
CHAR_EVENTS,
CHAR_XDMA_H2C,
CHAR_XDMA_C2H,
CHAR_BYPASS_H2C,
CHAR_BYPASS_C2H,
CHAR_BYPASS,
};
static const char * const devnode_names[] = {
XDMA_NODE_NAME "%d_user",
XDMA_NODE_NAME "%d_control",
XDMA_NODE_NAME "%d_xvc",
XDMA_NODE_NAME "%d_events_%d",
XDMA_NODE_NAME "%d_h2c_%d",
XDMA_NODE_NAME "%d_c2h_%d",
XDMA_NODE_NAME "%d_bypass_h2c_%d",
XDMA_NODE_NAME "%d_bypass_c2h_%d",
XDMA_NODE_NAME "%d_bypass",
};
enum xpdev_flags_bits {
XDF_CDEV_USER,
XDF_CDEV_CTRL,
XDF_CDEV_XVC,
XDF_CDEV_EVENT,
XDF_CDEV_SG,
XDF_CDEV_BYPASS,
};
static inline void xpdev_flag_set(struct xdma_pci_dev *xpdev,
enum xpdev_flags_bits fbit)
{
xpdev->flags |= 1 << fbit;
}
static inline void xcdev_flag_clear(struct xdma_pci_dev *xpdev,
enum xpdev_flags_bits fbit)
{
xpdev->flags &= ~(1 << fbit);
}
static inline int xpdev_flag_test(struct xdma_pci_dev *xpdev,
enum xpdev_flags_bits fbit)
{
return xpdev->flags & (1 << fbit);
}
#ifdef __XDMA_SYSFS__
ssize_t xdma_dev_instance_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct xdma_pci_dev *xpdev =
(struct xdma_pci_dev *)dev_get_drvdata(dev);
return snprintf(buf, PAGE_SIZE, "%d\t%d\n",
xpdev->major, xpdev->xdev->idx);
}
static DEVICE_ATTR_RO(xdma_dev_instance);
#endif
static int config_kobject(struct xdma_cdev *xcdev, enum cdev_type type)
{
int rv = -EINVAL;
struct xdma_dev *xdev = xcdev->xdev;
struct xdma_engine *engine = xcdev->engine;
switch (type) {
case CHAR_XDMA_H2C:
case CHAR_XDMA_C2H:
case CHAR_BYPASS_H2C:
case CHAR_BYPASS_C2H:
if (!engine) {
pr_err("Invalid DMA engine\n");
return rv;
}
rv = kobject_set_name(&xcdev->cdev.kobj, devnode_names[type],
xdev->idx, engine->channel);
break;
case CHAR_BYPASS:
case CHAR_USER:
case CHAR_CTRL:
case CHAR_XVC:
rv = kobject_set_name(&xcdev->cdev.kobj, devnode_names[type],
xdev->idx);
break;
case CHAR_EVENTS:
rv = kobject_set_name(&xcdev->cdev.kobj, devnode_names[type],
xdev->idx, xcdev->bar);
break;
default:
pr_warn("%s: UNKNOWN type 0x%x.\n", __func__, type);
break;
}
if (rv)
pr_err("%s: type 0x%x, failed %d.\n", __func__, type, rv);
return rv;
}
int xcdev_check(const char *fname, struct xdma_cdev *xcdev, bool check_engine)
{
struct xdma_dev *xdev;
if (!xcdev || xcdev->magic != MAGIC_CHAR) {
pr_info("%s, xcdev 0x%p, magic 0x%lx.\n",
fname, xcdev, xcdev ? xcdev->magic : 0xFFFFFFFF);
return -EINVAL;
}
xdev = xcdev->xdev;
if (!xdev || xdev->magic != MAGIC_DEVICE) {
pr_info("%s, xdev 0x%p, magic 0x%lx.\n",
fname, xdev, xdev ? xdev->magic : 0xFFFFFFFF);
return -EINVAL;
}
if (check_engine) {
struct xdma_engine *engine = xcdev->engine;
if (!engine || engine->magic != MAGIC_ENGINE) {
pr_info("%s, engine 0x%p, magic 0x%lx.\n", fname,
engine, engine ? engine->magic : 0xFFFFFFFF);
return -EINVAL;
}
}
return 0;
}
int char_open(struct inode *inode, struct file *file)
{
struct xdma_cdev *xcdev;
/* pointer to containing structure of the character device inode */
xcdev = container_of(inode->i_cdev, struct xdma_cdev, cdev);
if (xcdev->magic != MAGIC_CHAR) {
pr_err("xcdev 0x%p inode 0x%lx magic mismatch 0x%lx\n",
xcdev, inode->i_ino, xcdev->magic);
return -EINVAL;
}
/* create a reference to our char device in the opened file */
file->private_data = xcdev;
return 0;
}
/*
* Called when the device goes from used to unused.
*/
int char_close(struct inode *inode, struct file *file)
{
struct xdma_dev *xdev;
struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data;
if (!xcdev) {
pr_err("char device with inode 0x%lx xcdev NULL\n",
inode->i_ino);
return -EINVAL;
}
if (xcdev->magic != MAGIC_CHAR) {
pr_err("xcdev 0x%p magic mismatch 0x%lx\n",
xcdev, xcdev->magic);
return -EINVAL;
}
/* fetch device specific data stored earlier during open */
xdev = xcdev->xdev;
if (!xdev) {
pr_err("char device with inode 0x%lx xdev NULL\n",
inode->i_ino);
return -EINVAL;
}
if (xdev->magic != MAGIC_DEVICE) {
pr_err("xdev 0x%p magic mismatch 0x%lx\n", xdev, xdev->magic);
return -EINVAL;
}
return 0;
}
/* create_xcdev() -- create a character device interface to data or control bus
*
* If at least one SG DMA engine is specified, the character device interface
* is coupled to the SG DMA file operations which operate on the data bus. If
* no engines are specified, the interface is coupled with the control bus.
*/
static int create_sys_device(struct xdma_cdev *xcdev, enum cdev_type type)
{
struct xdma_dev *xdev = xcdev->xdev;
struct xdma_engine *engine = xcdev->engine;
int last_param;
if (type == CHAR_EVENTS)
last_param = xcdev->bar;
else
last_param = engine ? engine->channel : 0;
xcdev->sys_device = device_create(g_xdma_class, &xdev->pdev->dev,
xcdev->cdevno, NULL, devnode_names[type], xdev->idx,
last_param);
if (!xcdev->sys_device) {
pr_err("device_create(%s) failed\n", devnode_names[type]);
return -1;
}
return 0;
}
static int destroy_xcdev(struct xdma_cdev *cdev)
{
if (!cdev) {
pr_warn("cdev NULL.\n");
return -EINVAL;
}
if (cdev->magic != MAGIC_CHAR) {
pr_warn("cdev 0x%p magic mismatch 0x%lx\n", cdev, cdev->magic);
return -EINVAL;
}
if (!cdev->xdev) {
pr_err("xdev NULL\n");
return -EINVAL;
}
if (!g_xdma_class) {
pr_err("g_xdma_class NULL\n");
return -EINVAL;
}
if (!cdev->sys_device) {
pr_err("cdev sys_device NULL\n");
return -EINVAL;
}
if (cdev->sys_device)
device_destroy(g_xdma_class, cdev->cdevno);
cdev_del(&cdev->cdev);
return 0;
}
static int create_xcdev(struct xdma_pci_dev *xpdev, struct xdma_cdev *xcdev,
int bar, struct xdma_engine *engine,
enum cdev_type type)
{
int rv;
int minor;
struct xdma_dev *xdev = xpdev->xdev;
dev_t dev;
spin_lock_init(&xcdev->lock);
/* new instance? */
if (!xpdev->major) {
/* allocate a dynamically allocated char device node */
int rv = alloc_chrdev_region(&dev, XDMA_MINOR_BASE,
XDMA_MINOR_COUNT, XDMA_NODE_NAME);
if (rv) {
pr_err("unable to allocate cdev region %d.\n", rv);
return rv;
}
xpdev->major = MAJOR(dev);
}
/*
* do not register yet, create kobjects and name them,
*/
xcdev->magic = MAGIC_CHAR;
xcdev->cdev.owner = THIS_MODULE;
xcdev->xpdev = xpdev;
xcdev->xdev = xdev;
xcdev->engine = engine;
xcdev->bar = bar;
rv = config_kobject(xcdev, type);
if (rv < 0)
return rv;
switch (type) {
case CHAR_USER:
case CHAR_CTRL:
/* minor number is type index for non-SGDMA interfaces */
minor = type;
cdev_ctrl_init(xcdev);
break;
case CHAR_XVC:
/* minor number is type index for non-SGDMA interfaces */
minor = type;
cdev_xvc_init(xcdev);
break;
case CHAR_XDMA_H2C:
minor = 32 + engine->channel;
cdev_sgdma_init(xcdev);
break;
case CHAR_XDMA_C2H:
minor = 36 + engine->channel;
cdev_sgdma_init(xcdev);
break;
case CHAR_EVENTS:
minor = 10 + bar;
cdev_event_init(xcdev);
break;
case CHAR_BYPASS_H2C:
minor = 64 + engine->channel;
cdev_bypass_init(xcdev);
break;
case CHAR_BYPASS_C2H:
minor = 68 + engine->channel;
cdev_bypass_init(xcdev);
break;
case CHAR_BYPASS:
minor = 100;
cdev_bypass_init(xcdev);
break;
default:
pr_info("type 0x%x NOT supported.\n", type);
return -EINVAL;
}
xcdev->cdevno = MKDEV(xpdev->major, minor);
/* bring character device live */
rv = cdev_add(&xcdev->cdev, xcdev->cdevno, 1);
if (rv < 0) {
pr_err("cdev_add failed %d, type 0x%x.\n", rv, type);
goto unregister_region;
}
dbg_init("xcdev 0x%p, %u:%u, %s, type 0x%x.\n",
xcdev, xpdev->major, minor, xcdev->cdev.kobj.name, type);
/* create device on our class */
if (g_xdma_class) {
rv = create_sys_device(xcdev, type);
if (rv < 0)
goto del_cdev;
}
return 0;
del_cdev:
cdev_del(&xcdev->cdev);
unregister_region:
unregister_chrdev_region(xcdev->cdevno, XDMA_MINOR_COUNT);
return rv;
}
void xpdev_destroy_interfaces(struct xdma_pci_dev *xpdev)
{
int i = 0;
int rv;
#ifdef __XDMA_SYSFS__
device_remove_file(&xpdev->pdev->dev, &dev_attr_xdma_dev_instance);
#endif
if (xpdev_flag_test(xpdev, XDF_CDEV_SG)) {
/* iterate over channels */
for (i = 0; i < xpdev->h2c_channel_max; i++) {
/* remove SG DMA character device */
rv = destroy_xcdev(&xpdev->sgdma_h2c_cdev[i]);
if (rv < 0)
pr_err("Failed to destroy h2c xcdev %d error :0x%x\n",
i, rv);
}
for (i = 0; i < xpdev->c2h_channel_max; i++) {
rv = destroy_xcdev(&xpdev->sgdma_c2h_cdev[i]);
if (rv < 0)
pr_err("Failed to destroy c2h xcdev %d error 0x%x\n",
i, rv);
}
}
if (xpdev_flag_test(xpdev, XDF_CDEV_EVENT)) {
for (i = 0; i < xpdev->user_max; i++) {
rv = destroy_xcdev(&xpdev->events_cdev[i]);
if (rv < 0)
pr_err("Failed to destroy cdev event %d error 0x%x\n",
i, rv);
}
}
/* remove control character device */
if (xpdev_flag_test(xpdev, XDF_CDEV_CTRL)) {
rv = destroy_xcdev(&xpdev->ctrl_cdev);
if (rv < 0)
pr_err("Failed to destroy cdev ctrl event %d error 0x%x\n",
i, rv);
}
/* remove user character device */
if (xpdev_flag_test(xpdev, XDF_CDEV_USER)) {
rv = destroy_xcdev(&xpdev->user_cdev);
if (rv < 0)
pr_err("Failed to destroy user cdev %d error 0x%x\n",
i, rv);
}
if (xpdev_flag_test(xpdev, XDF_CDEV_XVC)) {
rv = destroy_xcdev(&xpdev->xvc_cdev);
if (rv < 0)
pr_err("Failed to destroy xvc cdev %d error 0x%x\n",
i, rv);
}
if (xpdev_flag_test(xpdev, XDF_CDEV_BYPASS)) {
/* iterate over channels */
for (i = 0; i < xpdev->h2c_channel_max; i++) {
/* remove DMA Bypass character device */
rv = destroy_xcdev(&xpdev->bypass_h2c_cdev[i]);
if (rv < 0)
pr_err("Failed to destroy bypass h2c cdev %d error 0x%x\n",
i, rv);
}
for (i = 0; i < xpdev->c2h_channel_max; i++) {
rv = destroy_xcdev(&xpdev->bypass_c2h_cdev[i]);
if (rv < 0)
pr_err("Failed to destroy bypass c2h %d error 0x%x\n",
i, rv);
}
rv = destroy_xcdev(&xpdev->bypass_cdev_base);
if (rv < 0)
pr_err("Failed to destroy base cdev\n");
}
if (xpdev->major)
unregister_chrdev_region(
MKDEV(xpdev->major, XDMA_MINOR_BASE),
XDMA_MINOR_COUNT);
}
int xpdev_create_interfaces(struct xdma_pci_dev *xpdev)
{
struct xdma_dev *xdev = xpdev->xdev;
struct xdma_engine *engine;
int i;
int rv = 0;
/* initialize control character device */
rv = create_xcdev(xpdev, &xpdev->ctrl_cdev, xdev->config_bar_idx,
NULL, CHAR_CTRL);
if (rv < 0) {
pr_err("create_char(ctrl_cdev) failed\n");
goto fail;
}
xpdev_flag_set(xpdev, XDF_CDEV_CTRL);
/* initialize events character device */
for (i = 0; i < xpdev->user_max; i++) {
rv = create_xcdev(xpdev, &xpdev->events_cdev[i], i, NULL,
CHAR_EVENTS);
if (rv < 0) {
pr_err("create char event %d failed, %d.\n", i, rv);
goto fail;
}
}
xpdev_flag_set(xpdev, XDF_CDEV_EVENT);
/* iterate over channels */
for (i = 0; i < xpdev->h2c_channel_max; i++) {
engine = &xdev->engine_h2c[i];
if (engine->magic != MAGIC_ENGINE)
continue;
rv = create_xcdev(xpdev, &xpdev->sgdma_h2c_cdev[i], i, engine,
CHAR_XDMA_H2C);
if (rv < 0) {
pr_err("create char h2c %d failed, %d.\n", i, rv);
goto fail;
}
}
for (i = 0; i < xpdev->c2h_channel_max; i++) {
engine = &xdev->engine_c2h[i];
if (engine->magic != MAGIC_ENGINE)
continue;
rv = create_xcdev(xpdev, &xpdev->sgdma_c2h_cdev[i], i, engine,
CHAR_XDMA_C2H);
if (rv < 0) {
pr_err("create char c2h %d failed, %d.\n", i, rv);
goto fail;
}
}
xpdev_flag_set(xpdev, XDF_CDEV_SG);
/* Initialize Bypass Character Device */
if (xdev->bypass_bar_idx > 0) {
for (i = 0; i < xpdev->h2c_channel_max; i++) {
engine = &xdev->engine_h2c[i];
if (engine->magic != MAGIC_ENGINE)
continue;
rv = create_xcdev(xpdev, &xpdev->bypass_h2c_cdev[i], i,
engine, CHAR_BYPASS_H2C);
if (rv < 0) {
pr_err("create h2c %d bypass I/F failed, %d.\n",
i, rv);
goto fail;
}
}
for (i = 0; i < xpdev->c2h_channel_max; i++) {
engine = &xdev->engine_c2h[i];
if (engine->magic != MAGIC_ENGINE)
continue;
rv = create_xcdev(xpdev, &xpdev->bypass_c2h_cdev[i], i,
engine, CHAR_BYPASS_C2H);
if (rv < 0) {
pr_err("create c2h %d bypass I/F failed, %d.\n",
i, rv);
goto fail;
}
}
rv = create_xcdev(xpdev, &xpdev->bypass_cdev_base,
xdev->bypass_bar_idx, NULL, CHAR_BYPASS);
if (rv < 0) {
pr_err("create bypass failed %d.\n", rv);
goto fail;
}
xpdev_flag_set(xpdev, XDF_CDEV_BYPASS);
}
/* initialize user character device */
if (xdev->user_bar_idx >= 0) {
rv = create_xcdev(xpdev, &xpdev->user_cdev, xdev->user_bar_idx,
NULL, CHAR_USER);
if (rv < 0) {
pr_err("create_char(user_cdev) failed\n");
goto fail;
}
xpdev_flag_set(xpdev, XDF_CDEV_USER);
/* xvc */
rv = create_xcdev(xpdev, &xpdev->xvc_cdev, xdev->user_bar_idx,
NULL, CHAR_XVC);
if (rv < 0) {
pr_err("create xvc failed, %d.\n", rv);
goto fail;
}
xpdev_flag_set(xpdev, XDF_CDEV_XVC);
}
#ifdef __XDMA_SYSFS__
/* sys file */
rv = device_create_file(&xpdev->pdev->dev,
&dev_attr_xdma_dev_instance);
if (rv) {
pr_err("Failed to create device file\n");
goto fail;
}
#endif
return 0;
fail:
rv = -1;
xpdev_destroy_interfaces(xpdev);
return rv;
}
int xdma_cdev_init(void)
{
#if defined(RHEL_RELEASE_CODE)
#if (RHEL_RELEASE_CODE >= RHEL_RELEASE_VERSION(9, 4))
g_xdma_class = class_create(XDMA_NODE_NAME);
#else
g_xdma_class = class_create(THIS_MODULE, XDMA_NODE_NAME);
#endif
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
g_xdma_class = class_create(XDMA_NODE_NAME);
#else
g_xdma_class = class_create(THIS_MODULE, XDMA_NODE_NAME);
#endif
if (IS_ERR(g_xdma_class)) {
dbg_init(XDMA_NODE_NAME ": failed to create class");
return -EINVAL;
}
/* using kmem_cache_create to enable sequential cleanup */
cdev_cache = kmem_cache_create("cdev_cache",
sizeof(struct cdev_async_io), 0,
SLAB_HWCACHE_ALIGN, NULL);
if (!cdev_cache) {
pr_info("memory allocation for cdev_cache failed. OOM\n");
return -ENOMEM;
}
return 0;
}
void xdma_cdev_cleanup(void)
{
if (cdev_cache)
kmem_cache_destroy(cdev_cache);
if (g_xdma_class)
class_destroy(g_xdma_class);
}

View File

@ -0,0 +1,51 @@
/*
* 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_CHRDEV_H__
#define __XDMA_CHRDEV_H__
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include "xdma_mod.h"
#define XDMA_NODE_NAME "xdma"
#define XDMA_MINOR_BASE (0)
#define XDMA_MINOR_COUNT (255)
void xdma_cdev_cleanup(void);
int xdma_cdev_init(void);
int char_open(struct inode *inode, struct file *file);
int char_close(struct inode *inode, struct file *file);
int xcdev_check(const char *fname, struct xdma_cdev *xcdev, bool check_engine);
void cdev_ctrl_init(struct xdma_cdev *xcdev);
void cdev_xvc_init(struct xdma_cdev *xcdev);
void cdev_event_init(struct xdma_cdev *xcdev);
void cdev_sgdma_init(struct xdma_cdev *xcdev);
void cdev_bypass_init(struct xdma_cdev *xcdev);
long char_ctrl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
void xpdev_destroy_interfaces(struct xdma_pci_dev *xpdev);
int xpdev_create_interfaces(struct xdma_pci_dev *xpdev);
int bridge_mmap(struct file *file, struct vm_area_struct *vma);
#endif /* __XDMA_CHRDEV_H__ */

Binary file not shown.

View File

@ -0,0 +1,382 @@
/*
* 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 <linux/types.h>
#include <linux/errno.h>
#include <linux/aer.h>
/* include early, to verify it depends only on the headers above */
#include "libxdma_api.h"
#include "libxdma.h"
#include "xdma_mod.h"
#include "xdma_cdev.h"
#include "version.h"
#define DRV_MODULE_NAME "xdma"
#define DRV_MODULE_DESC "Xilinx XDMA Reference Driver"
static char version[] =
DRV_MODULE_DESC " " DRV_MODULE_NAME " v" DRV_MODULE_VERSION "\n";
MODULE_AUTHOR("Xilinx, Inc.");
MODULE_DESCRIPTION(DRV_MODULE_DESC);
MODULE_VERSION(DRV_MODULE_VERSION);
MODULE_LICENSE("Dual BSD/GPL");
/* SECTION: Module global variables */
static int xpdev_cnt;
static const struct pci_device_id pci_ids[] = {
{ PCI_DEVICE(0x10ee, 0x9048), },
{ PCI_DEVICE(0x10ee, 0x9044), },
{ PCI_DEVICE(0x10ee, 0x9042), },
{ PCI_DEVICE(0x10ee, 0x9041), },
{ PCI_DEVICE(0x10ee, 0x903f), },
{ PCI_DEVICE(0x10ee, 0x9038), },
{ PCI_DEVICE(0x10ee, 0x9028), },
{ PCI_DEVICE(0x10ee, 0x9018), },
{ PCI_DEVICE(0x10ee, 0x9034), },
{ PCI_DEVICE(0x10ee, 0x9024), },
{ PCI_DEVICE(0x10ee, 0x9014), },
{ PCI_DEVICE(0x10ee, 0x9032), },
{ PCI_DEVICE(0x10ee, 0x9022), },
{ PCI_DEVICE(0x10ee, 0x9012), },
{ PCI_DEVICE(0x10ee, 0x9031), },
{ PCI_DEVICE(0x10ee, 0x9021), },
{ PCI_DEVICE(0x10ee, 0x9011), },
{ PCI_DEVICE(0x10ee, 0x8011), },
{ PCI_DEVICE(0x10ee, 0x8012), },
{ PCI_DEVICE(0x10ee, 0x8014), },
{ PCI_DEVICE(0x10ee, 0x8018), },
{ PCI_DEVICE(0x10ee, 0x8021), },
{ PCI_DEVICE(0x10ee, 0x8022), },
{ PCI_DEVICE(0x10ee, 0x8024), },
{ PCI_DEVICE(0x10ee, 0x8028), },
{ PCI_DEVICE(0x10ee, 0x8031), },
{ PCI_DEVICE(0x10ee, 0x8032), },
{ PCI_DEVICE(0x10ee, 0x8034), },
{ PCI_DEVICE(0x10ee, 0x8038), },
{ PCI_DEVICE(0x10ee, 0x7011), },
{ PCI_DEVICE(0x10ee, 0x7012), },
{ PCI_DEVICE(0x10ee, 0x7014), },
{ PCI_DEVICE(0x10ee, 0x7018), },
{ PCI_DEVICE(0x10ee, 0x7021), },
{ PCI_DEVICE(0x10ee, 0x7022), },
{ PCI_DEVICE(0x10ee, 0x7024), },
{ PCI_DEVICE(0x10ee, 0x7028), },
{ PCI_DEVICE(0x10ee, 0x7031), },
{ PCI_DEVICE(0x10ee, 0x7032), },
{ PCI_DEVICE(0x10ee, 0x7034), },
{ PCI_DEVICE(0x10ee, 0x7038), },
{ PCI_DEVICE(0x10ee, 0x6828), },
{ PCI_DEVICE(0x10ee, 0x6830), },
{ PCI_DEVICE(0x10ee, 0x6928), },
{ PCI_DEVICE(0x10ee, 0x6930), },
{ PCI_DEVICE(0x10ee, 0x6A28), },
{ PCI_DEVICE(0x10ee, 0x6A30), },
{ PCI_DEVICE(0x10ee, 0x6D30), },
{ PCI_DEVICE(0x10ee, 0x4808), },
{ PCI_DEVICE(0x10ee, 0x4828), },
{ PCI_DEVICE(0x10ee, 0x4908), },
{ PCI_DEVICE(0x10ee, 0x4A28), },
{ PCI_DEVICE(0x10ee, 0x4B28), },
{ PCI_DEVICE(0x10ee, 0x2808), },
#ifdef INTERNAL_TESTING
{ PCI_DEVICE(0x1d0f, 0x1042), 0},
#endif
/* aws */
{ PCI_DEVICE(0x1d0f, 0xf000), },
{ PCI_DEVICE(0x1d0f, 0xf001), },
{0,}
};
MODULE_DEVICE_TABLE(pci, pci_ids);
static void xpdev_free(struct xdma_pci_dev *xpdev)
{
struct xdma_dev *xdev = xpdev->xdev;
pr_info("xpdev 0x%p, destroy_interfaces, xdev 0x%p.\n", xpdev, xdev);
xpdev_destroy_interfaces(xpdev);
xpdev->xdev = NULL;
pr_info("xpdev 0x%p, xdev 0x%p xdma_device_close.\n", xpdev, xdev);
xdma_device_close(xpdev->pdev, xdev);
xpdev_cnt--;
kfree(xpdev);
}
static struct xdma_pci_dev *xpdev_alloc(struct pci_dev *pdev)
{
struct xdma_pci_dev *xpdev = kmalloc(sizeof(*xpdev), GFP_KERNEL);
if (!xpdev)
return NULL;
memset(xpdev, 0, sizeof(*xpdev));
xpdev->magic = MAGIC_DEVICE;
xpdev->pdev = pdev;
xpdev->user_max = MAX_USER_IRQ;
xpdev->h2c_channel_max = XDMA_CHANNEL_NUM_MAX;
xpdev->c2h_channel_max = XDMA_CHANNEL_NUM_MAX;
xpdev_cnt++;
return xpdev;
}
static int probe_one(struct pci_dev *pdev, const struct pci_device_id *id)
{
int rv = 0;
struct xdma_pci_dev *xpdev = NULL;
struct xdma_dev *xdev;
void *hndl;
xpdev = xpdev_alloc(pdev);
if (!xpdev)
return -ENOMEM;
hndl = xdma_device_open(DRV_MODULE_NAME, pdev, &xpdev->user_max,
&xpdev->h2c_channel_max, &xpdev->c2h_channel_max);
if (!hndl) {
rv = -EINVAL;
goto err_out;
}
if (xpdev->user_max > MAX_USER_IRQ) {
pr_err("Maximum users limit reached\n");
rv = -EINVAL;
goto err_out;
}
if (xpdev->h2c_channel_max > XDMA_CHANNEL_NUM_MAX) {
pr_err("Maximun H2C channel limit reached\n");
rv = -EINVAL;
goto err_out;
}
if (xpdev->c2h_channel_max > XDMA_CHANNEL_NUM_MAX) {
pr_err("Maximun C2H channel limit reached\n");
rv = -EINVAL;
goto err_out;
}
if (!xpdev->h2c_channel_max && !xpdev->c2h_channel_max)
pr_warn("NO engine found!\n");
if (xpdev->user_max) {
u32 mask = (1 << (xpdev->user_max + 1)) - 1;
rv = xdma_user_isr_enable(hndl, mask);
if (rv)
goto err_out;
}
/* make sure no duplicate */
xdev = xdev_find_by_pdev(pdev);
if (!xdev) {
pr_warn("NO xdev found!\n");
rv = -EINVAL;
goto err_out;
}
if (hndl != xdev) {
pr_err("xdev handle mismatch\n");
rv = -EINVAL;
goto err_out;
}
pr_info("%s xdma%d, pdev 0x%p, xdev 0x%p, 0x%p, usr %d, ch %d,%d.\n",
dev_name(&pdev->dev), xdev->idx, pdev, xpdev, xdev,
xpdev->user_max, xpdev->h2c_channel_max,
xpdev->c2h_channel_max);
xpdev->xdev = hndl;
rv = xpdev_create_interfaces(xpdev);
if (rv)
goto err_out;
dev_set_drvdata(&pdev->dev, xpdev);
return 0;
err_out:
pr_err("pdev 0x%p, err %d.\n", pdev, rv);
xpdev_free(xpdev);
return rv;
}
static void remove_one(struct pci_dev *pdev)
{
struct xdma_pci_dev *xpdev;
if (!pdev)
return;
xpdev = dev_get_drvdata(&pdev->dev);
if (!xpdev)
return;
pr_info("pdev 0x%p, xdev 0x%p, 0x%p.\n",
pdev, xpdev, xpdev->xdev);
xpdev_free(xpdev);
dev_set_drvdata(&pdev->dev, NULL);
}
static pci_ers_result_t xdma_error_detected(struct pci_dev *pdev,
pci_channel_state_t state)
{
struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);
switch (state) {
case pci_channel_io_normal:
return PCI_ERS_RESULT_CAN_RECOVER;
case pci_channel_io_frozen:
pr_warn("dev 0x%p,0x%p, frozen state error, reset controller\n",
pdev, xpdev);
xdma_device_offline(pdev, xpdev->xdev);
pci_disable_device(pdev);
return PCI_ERS_RESULT_NEED_RESET;
case pci_channel_io_perm_failure:
pr_warn("dev 0x%p,0x%p, failure state error, req. disconnect\n",
pdev, xpdev);
return PCI_ERS_RESULT_DISCONNECT;
}
return PCI_ERS_RESULT_NEED_RESET;
}
static pci_ers_result_t xdma_slot_reset(struct pci_dev *pdev)
{
struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);
pr_info("0x%p restart after slot reset\n", xpdev);
if (pci_enable_device_mem(pdev)) {
pr_info("0x%p failed to renable after slot reset\n", xpdev);
return PCI_ERS_RESULT_DISCONNECT;
}
pci_set_master(pdev);
pci_restore_state(pdev);
pci_save_state(pdev);
xdma_device_online(pdev, xpdev->xdev);
return PCI_ERS_RESULT_RECOVERED;
}
static void xdma_error_resume(struct pci_dev *pdev)
{
struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);
pr_info("dev 0x%p,0x%p.\n", pdev, xpdev);
#if PCI_AER_NAMECHANGE
pci_aer_clear_nonfatal_status(pdev);
#else
pci_cleanup_aer_uncorrect_error_status(pdev);
#endif
}
#if KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE
static void xdma_reset_prepare(struct pci_dev *pdev)
{
struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);
pr_info("dev 0x%p,0x%p.\n", pdev, xpdev);
xdma_device_offline(pdev, xpdev->xdev);
}
static void xdma_reset_done(struct pci_dev *pdev)
{
struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);
pr_info("dev 0x%p,0x%p.\n", pdev, xpdev);
xdma_device_online(pdev, xpdev->xdev);
}
#elif KERNEL_VERSION(3, 16, 0) <= LINUX_VERSION_CODE
static void xdma_reset_notify(struct pci_dev *pdev, bool prepare)
{
struct xdma_pci_dev *xpdev = dev_get_drvdata(&pdev->dev);
pr_info("dev 0x%p,0x%p, prepare %d.\n", pdev, xpdev, prepare);
if (prepare)
xdma_device_offline(pdev, xpdev->xdev);
else
xdma_device_online(pdev, xpdev->xdev);
}
#endif
static const struct pci_error_handlers xdma_err_handler = {
.error_detected = xdma_error_detected,
.slot_reset = xdma_slot_reset,
.resume = xdma_error_resume,
#if KERNEL_VERSION(4, 13, 0) <= LINUX_VERSION_CODE
.reset_prepare = xdma_reset_prepare,
.reset_done = xdma_reset_done,
#elif KERNEL_VERSION(3, 16, 0) <= LINUX_VERSION_CODE
.reset_notify = xdma_reset_notify,
#endif
};
static struct pci_driver pci_driver = {
.name = DRV_MODULE_NAME,
.id_table = pci_ids,
.probe = probe_one,
.remove = remove_one,
.err_handler = &xdma_err_handler,
};
static int xdma_mod_init(void)
{
int rv;
pr_info("%s", version);
if (desc_blen_max > XDMA_DESC_BLEN_MAX)
desc_blen_max = XDMA_DESC_BLEN_MAX;
pr_info("desc_blen_max: 0x%x/%u, timeout: h2c %u c2h %u sec.\n",
desc_blen_max, desc_blen_max, h2c_timeout, c2h_timeout);
rv = xdma_cdev_init();
if (rv < 0)
return rv;
return pci_register_driver(&pci_driver);
}
static void xdma_mod_exit(void)
{
/* unregister this driver from the PCI bus driver */
dbg_init("pci_unregister_driver.\n");
pci_unregister_driver(&pci_driver);
xdma_cdev_cleanup();
}
module_init(xdma_mod_init);
module_exit(xdma_mod_exit);

View File

@ -0,0 +1,118 @@
/*
* 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_MODULE_H__
#define __XDMA_MODULE_H__
#include <linux/types.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/dma-mapping.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/mm_types.h>
#include <linux/poll.h>
#include <linux/pci.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include <linux/aio.h>
#include <linux/splice.h>
#include <linux/version.h>
#include <linux/uio.h>
#include <linux/spinlock_types.h>
#include "libxdma.h"
#include "xdma_thread.h"
#define MAGIC_ENGINE 0xEEEEEEEEUL
#define MAGIC_DEVICE 0xDDDDDDDDUL
#define MAGIC_CHAR 0xCCCCCCCCUL
#define MAGIC_BITSTREAM 0xBBBBBBBBUL
extern unsigned int desc_blen_max;
extern unsigned int h2c_timeout;
extern unsigned int c2h_timeout;
struct xdma_cdev {
unsigned long magic; /* structure ID for sanity checks */
struct xdma_pci_dev *xpdev;
struct xdma_dev *xdev;
dev_t cdevno; /* character device major:minor */
struct cdev cdev; /* character device embedded struct */
int bar; /* PCIe BAR for HW access, if needed */
unsigned long base; /* bar access offset */
struct xdma_engine *engine; /* engine instance, if needed */
struct xdma_user_irq *user_irq; /* IRQ value, if needed */
struct device *sys_device; /* sysfs device */
spinlock_t lock;
};
/* XDMA PCIe device specific book-keeping */
struct xdma_pci_dev {
unsigned long magic; /* structure ID for sanity checks */
struct pci_dev *pdev; /* pci device struct from probe() */
struct xdma_dev *xdev;
int major; /* major number */
int instance; /* instance number */
int user_max;
int c2h_channel_max;
int h2c_channel_max;
unsigned int flags;
/* character device structures */
struct xdma_cdev ctrl_cdev;
struct xdma_cdev sgdma_c2h_cdev[XDMA_CHANNEL_NUM_MAX];
struct xdma_cdev sgdma_h2c_cdev[XDMA_CHANNEL_NUM_MAX];
struct xdma_cdev events_cdev[16];
struct xdma_cdev user_cdev;
struct xdma_cdev bypass_c2h_cdev[XDMA_CHANNEL_NUM_MAX];
struct xdma_cdev bypass_h2c_cdev[XDMA_CHANNEL_NUM_MAX];
struct xdma_cdev bypass_cdev_base;
struct xdma_cdev xvc_cdev;
void *data;
};
struct cdev_async_io {
struct kiocb *iocb;
struct xdma_io_cb *cb;
bool write;
bool cancel;
int cmpl_cnt;
int req_cnt;
spinlock_t lock;
struct work_struct wrk_itm;
struct cdev_async_io *next;
ssize_t res;
ssize_t res2;
int err_cnt;
};
#endif /* ifndef __XDMA_MODULE_H__ */

Binary file not shown.

View File

@ -0,0 +1,339 @@
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2017-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_thread.h"
#include <linux/kernel.h>
#include <linux/slab.h>
/* ********************* global variables *********************************** */
static struct xdma_kthread *cs_threads;
static unsigned int thread_cnt;
/* ********************* static function definitions ************************ */
static int xdma_thread_cmpl_status_pend(struct list_head *work_item)
{
struct xdma_engine *engine = list_entry(work_item, struct xdma_engine,
cmplthp_list);
int pend = 0;
unsigned long flags;
spin_lock_irqsave(&engine->lock, flags);
pend = !list_empty(&engine->transfer_list);
spin_unlock_irqrestore(&engine->lock, flags);
return pend;
}
static int xdma_thread_cmpl_status_proc(struct list_head *work_item)
{
struct xdma_engine *engine;
struct xdma_transfer * transfer;
engine = list_entry(work_item, struct xdma_engine, cmplthp_list);
transfer = list_entry(engine->transfer_list.next, struct xdma_transfer,
entry);
if (transfer)
engine_service_poll(engine, transfer->desc_cmpl_th);
return 0;
}
static inline int xthread_work_pending(struct xdma_kthread *thp)
{
struct list_head *work_item, *next;
/* any work items assigned to this thread? */
if (list_empty(&thp->work_list))
return 0;
/* any work item has pending work to do? */
list_for_each_safe(work_item, next, &thp->work_list) {
if (thp->fpending && thp->fpending(work_item))
return 1;
}
return 0;
}
static inline void xthread_reschedule(struct xdma_kthread *thp)
{
if (thp->timeout) {
pr_debug_thread("%s rescheduling for %u seconds",
thp->name, thp->timeout);
wait_event_interruptible_timeout(thp->waitq, thp->schedule,
msecs_to_jiffies(thp->timeout));
} else {
pr_debug_thread("%s rescheduling", thp->name);
wait_event_interruptible(thp->waitq, thp->schedule);
}
}
static int xthread_main(void *data)
{
struct xdma_kthread *thp = (struct xdma_kthread *)data;
pr_debug_thread("%s UP.\n", thp->name);
disallow_signal(SIGPIPE);
if (thp->finit)
thp->finit(thp);
while (!kthread_should_stop()) {
struct list_head *work_item, *next;
pr_debug_thread("%s interruptible\n", thp->name);
/* any work to do? */
lock_thread(thp);
if (!xthread_work_pending(thp)) {
unlock_thread(thp);
xthread_reschedule(thp);
lock_thread(thp);
}
thp->schedule = 0;
if (thp->work_cnt) {
pr_debug_thread("%s processing %u work items\n",
thp->name, thp->work_cnt);
/* do work */
list_for_each_safe(work_item, next, &thp->work_list) {
thp->fproc(work_item);
}
}
unlock_thread(thp);
schedule();
}
pr_debug_thread("%s, work done.\n", thp->name);
if (thp->fdone)
thp->fdone(thp);
pr_debug_thread("%s, exit.\n", thp->name);
return 0;
}
int xdma_kthread_start(struct xdma_kthread *thp, char *name, int id)
{
int len;
int node;
if (thp->task) {
pr_warn("kthread %s task already running?\n", thp->name);
return -EINVAL;
}
len = snprintf(thp->name, sizeof(thp->name), "%s%d", name, id);
if (len < 0) {
pr_err("thread %d, error in snprintf name %s.\n", id, name);
return -EINVAL;
}
thp->id = id;
spin_lock_init(&thp->lock);
INIT_LIST_HEAD(&thp->work_list);
init_waitqueue_head(&thp->waitq);
node = cpu_to_node(thp->cpu);
pr_debug("node : %d\n", node);
thp->task = kthread_create_on_node(xthread_main, (void *)thp,
node, "%s", thp->name);
if (IS_ERR(thp->task)) {
pr_err("kthread %s, create task failed: 0x%lx\n",
thp->name, (unsigned long)IS_ERR(thp->task));
thp->task = NULL;
return -EFAULT;
}
kthread_bind(thp->task, thp->cpu);
pr_debug_thread("kthread 0x%p, %s, cpu %u, task 0x%p.\n",
thp, thp->name, thp->cpu, thp->task);
wake_up_process(thp->task);
return 0;
}
int xdma_kthread_stop(struct xdma_kthread *thp)
{
int rv;
if (!thp->task) {
pr_debug_thread("kthread %s, already stopped.\n", thp->name);
return 0;
}
thp->schedule = 1;
rv = kthread_stop(thp->task);
if (rv < 0) {
pr_warn("kthread %s, stop err %d.\n", thp->name, rv);
return rv;
}
pr_debug_thread("kthread %s, 0x%p, stopped.\n", thp->name, thp->task);
thp->task = NULL;
return 0;
}
void xdma_thread_remove_work(struct xdma_engine *engine)
{
struct xdma_kthread *cmpl_thread;
unsigned long flags;
spin_lock_irqsave(&engine->lock, flags);
cmpl_thread = engine->cmplthp;
engine->cmplthp = NULL;
// pr_debug("%s removing from thread %s, %u.\n",
// descq->conf.name, cmpl_thread ? cmpl_thread->name : "?",
// cpu_idx);
spin_unlock_irqrestore(&engine->lock, flags);
#if 0
if (cpu_idx < cpu_count) {
spin_lock(&qcnt_lock);
per_cpu_qcnt[cpu_idx]--;
spin_unlock(&qcnt_lock);
}
#endif
if (cmpl_thread) {
lock_thread(cmpl_thread);
list_del(&engine->cmplthp_list);
cmpl_thread->work_cnt--;
unlock_thread(cmpl_thread);
}
}
void xdma_thread_add_work(struct xdma_engine *engine)
{
struct xdma_kthread *thp = cs_threads;
unsigned int v = 0;
int i, idx = thread_cnt;
unsigned long flags;
/* Polled mode only */
for (i = 0; i < thread_cnt; i++, thp++) {
lock_thread(thp);
if (idx == thread_cnt) {
v = thp->work_cnt;
idx = i;
} else if (!thp->work_cnt) {
idx = i;
unlock_thread(thp);
break;
} else if (thp->work_cnt < v)
idx = i;
unlock_thread(thp);
}
thp = cs_threads + idx;
lock_thread(thp);
list_add_tail(&engine->cmplthp_list, &thp->work_list);
engine->intr_work_cpu = idx;
thp->work_cnt++;
unlock_thread(thp);
pr_info("%s 0x%p assigned to cmpl status thread %s,%u.\n",
engine->name, engine, thp->name, thp->work_cnt);
spin_lock_irqsave(&engine->lock, flags);
engine->cmplthp = thp;
spin_unlock_irqrestore(&engine->lock, flags);
}
int xdma_threads_create(unsigned int num_threads)
{
struct xdma_kthread *thp;
int rv;
int cpu;
if (thread_cnt) {
pr_warn("threads already created!");
return 0;
}
cs_threads = kzalloc(num_threads * sizeof(struct xdma_kthread),
GFP_KERNEL);
if (!cs_threads) {
pr_err("OOM, # threads %u.\n", num_threads);
return -ENOMEM;
}
/* N dma writeback monitoring threads */
thp = cs_threads;
for_each_online_cpu(cpu) {
pr_debug("index %d cpu %d online\n", thread_cnt, cpu);
thp->cpu = cpu;
thp->timeout = 0;
thp->fproc = xdma_thread_cmpl_status_proc;
thp->fpending = xdma_thread_cmpl_status_pend;
rv = xdma_kthread_start(thp, "cmpl_status_th", thread_cnt);
if (rv < 0)
goto cleanup_threads;
thread_cnt++;
if (thread_cnt == num_threads)
break;
thp++;
}
return 0;
cleanup_threads:
kfree(cs_threads);
cs_threads = NULL;
thread_cnt = 0;
return rv;
}
void xdma_threads_destroy(void)
{
int i;
struct xdma_kthread *thp;
if (!thread_cnt)
return;
/* N dma writeback monitoring threads */
thp = cs_threads;
for (i = 0; i < thread_cnt; i++, thp++)
if (thp->fproc)
xdma_kthread_stop(thp);
kfree(cs_threads);
cs_threads = NULL;
thread_cnt = 0;
}

View File

@ -0,0 +1,149 @@
/*
* This file is part of the Xilinx DMA IP Core driver for Linux
*
* Copyright (c) 2017-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_KTHREAD_H__
#define __XDMA_KTHREAD_H__
/**
* @file
* @brief This file contains the declarations for xdma kernel threads
*
*/
#include <linux/version.h>
#include <linux/spinlock.h>
#include <linux/kthread.h>
#include <linux/cpuset.h>
#include <linux/signal.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include "libxdma.h"
#ifdef DEBUG_THREADS
#define lock_thread(thp) \
do { \
pr_debug("locking thp %s ...\n", (thp)->name); \
spin_lock(&(thp)->lock); \
} while (0)
#define unlock_thread(thp) \
do { \
pr_debug("unlock thp %s ...\n", (thp)->name); \
spin_unlock(&(thp)->lock); \
} while (0)
#define xdma_kthread_wakeup(thp) \
do { \
pr_info("signaling thp %s ...\n", (thp)->name); \
wake_up_process((thp)->task); \
} while (0)
#define pr_debug_thread(fmt, ...) pr_info(fmt, __VA_ARGS__)
#else
/** lock thread macro */
#define lock_thread(thp) spin_lock(&(thp)->lock)
/** un lock thread macro */
#define unlock_thread(thp) spin_unlock(&(thp)->lock)
#define xdma_kthread_wakeup(thp) \
do { \
thp->schedule = 1; \
wake_up_interruptible(&thp->waitq); \
} while (0)
/** pr_debug_thread */
#define pr_debug_thread(fmt, ...)
#endif
/**
* @struct - xdma_kthread
* @brief xdma thread book keeping parameters
*/
struct xdma_kthread {
/** thread lock*/
spinlock_t lock;
/** name of the thread */
char name[16];
/** cpu number for which the thread associated with */
unsigned short cpu;
/** thread id */
unsigned short id;
/** thread sleep timeout value */
unsigned int timeout;
/** flags for thread */
unsigned long flag;
/** thread wait queue */
wait_queue_head_t waitq;
/* flag to indicate scheduling of thread */
unsigned int schedule;
/** kernel task structure associated with thread*/
struct task_struct *task;
/** thread work list count */
unsigned int work_cnt;
/** thread work list count */
struct list_head work_list;
/** thread initialization handler */
int (*finit)(struct xdma_kthread *);
/** thread pending handler */
int (*fpending)(struct list_head *);
/** thread peocessing handler */
int (*fproc)(struct list_head *);
/** thread done handler */
int (*fdone)(struct xdma_kthread *);
};
/*****************************************************************************/
/**
* xdma_threads_create() - create xdma threads
*********/
int xdma_threads_create(unsigned int num_threads);
/*****************************************************************************/
/**
* xdma_threads_destroy() - destroy all the xdma threads created
* during system initialization
*
* @return none
*****************************************************************************/
void xdma_threads_destroy(void);
/*****************************************************************************/
/**
* xdma_thread_remove_work() - handler to remove the attached work thread
*
* @param[in] engine: pointer to xdma_engine
*
* @return none
*****************************************************************************/
void xdma_thread_remove_work(struct xdma_engine *engine);
/*****************************************************************************/
/**
* xdma_thread_add_work() - handler to add a work thread
*
* @param[in] engine: pointer to xdma_engine
*
* @return none
*****************************************************************************/
void xdma_thread_add_work(struct xdma_engine *engine);
int xdma_kthread_start(struct xdma_kthread *thp, char *name, int id);
int xdma_kthread_stop(struct xdma_kthread *thp);
#endif /* #ifndef __XDMA_KTHREAD_H__ */

Binary file not shown.

View File

@ -5,5 +5,5 @@ connect_hw_server
open_hw_target
puts [get_hw_devices]
set obj [lindex [get_hw_devices [current_hw_device]] 0]
set_property PROGRAM.FILE ./build/xdma480t.bit $obj
set_property PROGRAM.FILE ./xdma480t.bit $obj
program_hw_devices $obj