-
zynq dma linux 配置
2017-04-02 18:56:52该资源是博客中附带的资源下载链接 -
Zynq Axidma linux下驱动axidmatest.c 驱动分析
2020-03-09 16:23:01本文的部分内容可能来源于网络,该内容归原作者所有,如果侵犯到您的权益,请及时通知我,我将立即删除,...欢迎加入zynq-arm-linux提高交流群:788265722 文档错误可能很多,大家多包涵,主要理解文件的目的就好...本文的部分内容可能来源于网络,该内容归原作者所有,如果侵犯到您的权益,请及时通知我,我将立即删除,原创内容copyleft归tingkman@163.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。欢迎加入zynq-arm-linux提高交流群:788265722
文档错误可能很多,大家多包涵,主要理解文件的目的就好。可留言
Zynq-axidma是大家常用的功能,所以,很多同学都用到,但是有很大一部分同学比较熟悉裸跑下axidma使用,网络上也有很多这方面的教程,像米联客AXI_DMA_LOOP 环路测试,但是linux下使用axidma的使用方法却很少,所以才有了今天这个博文,
阅读这篇博文之前,大家可以参考以下博客内容,为理解linux下axi dma使用做铺垫。
Axidma 裸跑例子(更多的可以百度)
AXI_DMA_LOOP 环路测
https://www.cnblogs.com/milinker/p/6484011.html
https://blog.csdn.net/long_fly/article/details/79702222
AXI_DMA_LOOP,在viviado图里有两部分,一个是axidma模块,一个是数据回环模块,首先,用户通过写dma下发一个数据,比方1234,然后用户通过读dma读取数据读到1234,表示测试完成,这里面其实有两个dma通道一个写一个读做了两次dma操作,读写都是由用户发起的,搬运都是dma控制器执行的如下图:
在linux下驱动主要牵涉的文件有3个,一个是设备文件描述dtb,一个是axidma控制器驱动xilinx_dma.c,一个是axidmatest.c,我们今天的主角就是axidmatest.c
Xilinx官网给的axidma驱动说明在这里大家可以看看
https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842337/Linux+Soft+DMA+Driver
各个文件的位置如下:
内核目录
https://github.com/Xilinx/linux-xlnx/blob/master/arch/arm/boot/dts/zynq-zc706.c,这个是官网评估板的dtb描述,原版的dtb里面没有dma的描述,要把dma的描述加进来
https://github.com/Xilinx/linux-xlnx/blob/master/drivers/dma/xilinx/xilinx_dma.c
https://github.com/Xilinx/linux-xlnx/blob/master/drivers/dma/xilinx/axidmatest.c
在设备树里表示dma读写通道,用以下表示
dma-channel@40400000 axi-dma-mm2s-channel 写通道,是ddr网datafifo写
dma-channel@40400030 axi-dma-s2mm-channel 读通道 是datafifo往ddr搬
这个描述对应的是控制器驱动xilinx_dma.c,有这个
描述xilinx_dma.c这个驱动才会加载。
axi_dma_1: dma@40400000 {
#dma-cells = <1>;
clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";
clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>, <&clkc 15>;
compatible = "xlnx,axi-dma-1.00.a";
interrupt-parent = <&intc>;
interrupts = <0 29 4 0 30 4>;
reg = <0x40400000 0x10000>;
xlnx,addrwidth = <0x20>;
xlnx,include-sg ;
dma-channel@40400000 {
compatible = "xlnx,axi-dma-mm2s-channel";
dma-channels = <0x1>;
interrupts = <0 29 4>;
xlnx,datawidth = <0x20>;
xlnx,device-id = <0x0>;
xlnx,include-dre ;
};
dma-channel@40400030 {
compatible = "xlnx,axi-dma-s2mm-channel";
dma-channels = <0x1>;
interrupts = <0 30 4>;
xlnx,datawidth = <0x20>;
xlnx,device-id = <0x0>;
xlnx,include-dre ;
};
};
在linux里驱动是分层的,一般像spi i2c dma这类的驱动都是分控制器驱动,和下面的设备驱动,控制器是单独的驱动,下面挂的设备是设备的驱动,设备的驱动都是去想控制器去申请的。
那么今天的主题设备驱动axidmatest.c也在设备树里面有自己的描述,我们看到它用了两个dma通道,就是dma控制器的两个读写通道。
axidmatest_1: axidmatest@1 {
compatible ="xlnx,axi-dma-test-1.00.a";
dmas = <&axi_dma_1 0
&axi_dma_1 1>;
dma-names = "axidma0", "axidma1";
} ;
这里设备树和驱动是怎么对应的那,linux采用的是compatible里面的描述
控制器 compatible = "xlnx,axi-dma-1.00.a";
测试程序驱动compatible ="xlnx,axi-dma-test-1.00.a";
驱动程序表示如下:
这样写好,还要把内核配置也配置好,这些驱动才都能加载
在写dma驱动我们不用关注xilinx_dma.c驱动的实现,只需要理解axidmatest.c的实现,下面就分析一下:axidmatest.c这个驱动。
那么从哪里开始分析那,我们知道一般程序都有个入口,比方说main函数,那在linux下axidmatest.c入口就是以下函数。
late_initcall这个意思大概意思就是后面初始化,实际上dma控制器xilinx_dma.c实在测试驱动axidmatest.c之前运行的,因为测试驱动要去向控制器申请通道,所以控制器要先初始化好,才能给测试驱动提供通道。
static int __init axidma_init(void)
{
return platform_driver_register(&xilinx_axidmatest_driver);
}
late_initcall(axidma_init);
static const struct of_device_id xilinx_axidmatest_of_ids[] = {
{ .compatible = "xlnx,axi-dma-test-1.00.a",},
{}
};
static struct platform_driver xilinx_axidmatest_driver = {
.driver = {
.name = "xilinx_axidmatest",
.owner = THIS_MODULE,
.of_match_table = xilinx_axidmatest_of_ids,
},
.probe = xilinx_axidmatest_probe,
.remove = xilinx_axidmatest_remove,
};
axidma_init其实使用platform_driver_register,加载平台设备驱动xilinx_axidmatest_driver,
平台设备驱动有几个描述driver和probe,remove函数,remove函数是在驱动程序卸载才会执行,这里一般内核启动后不会卸载。当xilinx_axidmatest_of_ids里面的compatible和设备树描述文件dtb里面的对应后probe就会运行
axidmatest_1: axidmatest@1 {
compatible ="xlnx,axi-dma-test-1.00.a";
dmas = <&axi_dma_1 0
&axi_dma_1 1>;
dma-names = "axidma0", "axidma1";
} ;
当probe函数xilinx_axidmatest_probe才开始测试驱动的初始化。
dma_request_slave_channel是linux驱动编写的标准函数,dma测试程序申请dma通道要用这个函数,具体实现我们可以不管,只需要用这个申请就可以了,当然了,控制器驱动已经准备好了,才能申请成功。
static int xilinx_axidmatest_probe(struct platform_device *pdev)
{
struct dma_chan *chan, *rx_chan;
int err;
chan = dma_request_slave_channel(&pdev->dev, "axidma0");
//---------------------向控制器申请dma发送通道
if (IS_ERR(chan)) {
pr_err("xilinx_dmatest: No Tx channel\n");
return PTR_ERR(chan);
}
rx_chan = dma_request_slave_channel(&pdev->dev, "axidma1");
//---------------------向控制器申请dma接收通道
if (IS_ERR(rx_chan)) {
err = PTR_ERR(rx_chan);
pr_err("xilinx_dmatest: No Rx channel\n");
goto free_tx;
}
err = dmatest_add_slave_channels(chan, rx_chan);
if (err) {
pr_err("xilinx_dmatest: Unable to add channels\n");
goto free_rx;
}
return 0;
free_rx:
dma_release_channel(rx_chan);
free_tx:
dma_release_channel(chan);
return err;
}
dmatest_add_slave_channels(chan, rx_chan)函数进一步初始化。
定义两个dmatest_chan类型的数据结构tx_dtc,rx_dtc用来保存之前申请的两个通道,后面就用这两个数据接口来访问通道。
static int dmatest_add_slave_channels(struct dma_chan *tx_chan,
struct dma_chan *rx_chan)
{
struct dmatest_chan *tx_dtc;
struct dmatest_chan *rx_dtc;
unsigned int thread_count = 0;
tx_dtc = kmalloc(sizeof(struct dmatest_chan), GFP_KERNEL);
if (!tx_dtc) {
pr_warn("dmatest: No memory for tx %s\n",
dma_chan_name(tx_chan));
return -ENOMEM;
}
rx_dtc = kmalloc(sizeof(struct dmatest_chan), GFP_KERNEL);
if (!rx_dtc) {
pr_warn("dmatest: No memory for rx %s\n",
dma_chan_name(rx_chan));
return -ENOMEM;
}
tx_dtc->chan = tx_chan;
rx_dtc->chan = rx_chan;
INIT_LIST_HEAD(&tx_dtc->threads);
INIT_LIST_HEAD(&rx_dtc->threads);
dmatest_add_slave_threads(tx_dtc, rx_dtc);
thread_count += 1;
pr_info("dmatest: Started %u threads using %s %s\n",
thread_count, dma_chan_name(tx_chan), dma_chan_name(rx_chan));
list_add_tail(&tx_dtc->node, &dmatest_channels);
list_add_tail(&rx_dtc->node, &dmatest_channels);
nr_channels += 2;
if (iterations)
wait_event(thread_wait, !is_threaded_test_run(tx_dtc, rx_dtc));
return 0;
}
dmatest_add_slave_threads实际上是创建了一个内核任务,这个任务才是发起dma操作的,
在linux-dma测试程序中发起dma请求,都是由标准的函数,我们写的时候也要用到这些函数
这里要了解一些linuxdma的相关知识,这里找几篇博客参考
http://www.wowotech.net/linux_kenrel/dma_engine_overview.html
http://www.wowotech.net/linux_kenrel/dma_engine_api.html
https://www.cnblogs.com/xiaojiang1025/archive/2017/02/11/6389194.html
Dma驱动一般分两种,一种是一致性dma一种是流式dma,本文用的dma是流式的。
相关函数如下:
dma_map_single
sg_init_table(tx_sg, bd_cnt);
sg_dma_address
sg_dma_len
device_prep_slave_sg
rxd->tx_submit
dmatest_slave_func函数基本流程我先概况一下:
先通过准备数据通过写通道dma发送给datafifo,然后在通过读通道dma从datafifo读出来,对比发送的数据是否和接收的数据一致,一致则认为测试通过,循环做几次测试
static int dmatest_slave_func(void *data)函数分析
static int dmatest_slave_func(void *data)
{
struct dmatest_slave_thread *thread = data;
struct dma_chan *tx_chan;
struct dma_chan *rx_chan;
const char *thread_name;
unsigned int src_off, dst_off, len;
unsigned int error_count;
unsigned int failed_tests = 0;
unsigned int total_tests = 0;
dma_cookie_t tx_cookie;
dma_cookie_t rx_cookie;
enum dma_status status;
enum dma_ctrl_flags flags;
int ret;
int src_cnt;
int dst_cnt;
int bd_cnt = 11;
int i;
ktime_t ktime, start, diff;
ktime_t filltime = 0;
ktime_t comparetime = 0;
s64 runtime = 0;
unsigned long long total_len = 0;
thread_name = current->comm;
ret = -ENOMEM;
/* Ensure that all previous reads are complete */
smp_rmb();
tx_chan = thread->tx_chan;
rx_chan = thread->rx_chan;
dst_cnt = bd_cnt; //这里为11,实际上是创建11个sg,sg支持多个分块dma传输,这里可以改成1,就一块
src_cnt = bd_cnt;
//申请发送缓冲区数据结构,
thread->srcs = kcalloc(src_cnt + 1, sizeof(u8 *), GFP_KERNEL);
if (!thread->srcs)
goto err_srcs;
//申请发送缓冲区,这里面放发送的数据,往datafifo发送的数据
for (i = 0; i < src_cnt; i++) {
thread->srcs[i] = kmalloc(test_buf_size, GFP_KERNEL);
if (!thread->srcs[i])
goto err_srcbuf;
}
thread->srcs[i] = NULL;
//申请接收缓冲区数据结构,
thread->dsts = kcalloc(dst_cnt + 1, sizeof(u8 *), GFP_KERNEL);
if (!thread->dsts)
goto err_dsts;
//申请接收缓冲区,这里是dma从datafifo搬运的数据放到这里,
for (i = 0; i < dst_cnt; i++) {
thread->dsts[i] = kmalloc(test_buf_size, GFP_KERNEL);
if (!thread->dsts[i])
goto err_dstbuf;
}
thread->dsts[i] = NULL;
set_user_nice(current, 10);
flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
ktime = ktime_get();
while (!kthread_should_stop() &&
!(iterations && total_tests >= iterations)) {
struct dma_device *tx_dev = tx_chan->device;
struct dma_device *rx_dev = rx_chan->device;
struct dma_async_tx_descriptor *txd = NULL;
struct dma_async_tx_descriptor *rxd = NULL;
dma_addr_t dma_srcs[src_cnt];
dma_addr_t dma_dsts[dst_cnt];
struct completion rx_cmp;
struct completion tx_cmp;
unsigned long rx_tmo =
msecs_to_jiffies(300000); /* RX takes longer */
unsigned long tx_tmo = msecs_to_jiffies(30000);
u8 align = 0;
struct scatterlist tx_sg[bd_cnt]; //定义发送dma数据结构,
struct scatterlist rx_sg[bd_cnt]; //定义接收dma数据结构,
total_tests++;
//----------------字节对齐,dma传输要求字节对齐
/* honor larger alignment restrictions */
align = tx_dev->copy_align;
if (rx_dev->copy_align > align)
align = rx_dev->copy_align;
if (1 << align > test_buf_size) {
pr_err("%u-byte buffer too small for %d-byte alignment\n",
test_buf_size, 1 << align);
break;
}
len = dmatest_random() % test_buf_size + 1;
len = (len >> align) << align;
if (!len)
len = 1 << align;
total_len += len;
src_off = dmatest_random() % (test_buf_size - len + 1);
dst_off = dmatest_random() % (test_buf_size - len + 1);
src_off = (src_off >> align) << align;
dst_off = (dst_off >> align) << align;
start = ktime_get();
dmatest_init_srcs(thread->srcs, src_off, len);
dmatest_init_dsts(thread->dsts, dst_off, len);
//----------------字节对齐,dma传输要求字节对齐
diff = ktime_sub(ktime_get(), start);
filltime = ktime_add(filltime, diff);
for (i = 0; i < src_cnt; i++) {
u8 *buf = thread->srcs[i] + src_off;
//dma映射后获取的物理地址,实际上dma初始化用的是物理地址,thread->srcs[i] 这个是虚拟地址,是程序可以直接使用的,但是dma用的是物理地址
dma_srcs[i] = dma_map_single(tx_dev->dev, buf, len,
DMA_MEM_TO_DEV);
}
for (i = 0; i < dst_cnt; i++) {
//dma映射后获取的物理地址,实际上dma初始化用的是物理地址,thread->srcs[i] 这个是虚拟地址,是程序可以直接使用的,但是dma用的是物理地址
dma_dsts[i] = dma_map_single(rx_dev->dev,
thread->dsts[i],
test_buf_size,
DMA_BIDIRECTIONAL);
}
//初始化发送接收数据结构----------------------
sg_init_table(tx_sg, bd_cnt);
sg_init_table(rx_sg, bd_cnt);
for (i = 0; i < bd_cnt; i++) {
sg_dma_address(&tx_sg[i]) = dma_srcs[i]; //物理地址
sg_dma_address(&rx_sg[i]) = dma_dsts[i] + dst_off;
sg_dma_len(&tx_sg[i]) = len; //dma一次传输的长度
sg_dma_len(&rx_sg[i]) = len;
}
//准备发送 接收sg
rxd = rx_dev->device_prep_slave_sg(rx_chan, rx_sg, bd_cnt,
DMA_DEV_TO_MEM, flags, NULL);
txd = tx_dev->device_prep_slave_sg(tx_chan, tx_sg, bd_cnt,
DMA_MEM_TO_DEV, flags, NULL);
if (!rxd || !txd) {
for (i = 0; i < src_cnt; i++)
dma_unmap_single(tx_dev->dev, dma_srcs[i], len,
DMA_MEM_TO_DEV);
for (i = 0; i < dst_cnt; i++)
dma_unmap_single(rx_dev->dev, dma_dsts[i],
test_buf_size,
DMA_BIDIRECTIONAL);
pr_warn("%s: #%u: prep error with src_off=0x%x ",
thread_name, total_tests - 1, src_off);
pr_warn("dst_off=0x%x len=0x%x\n",
dst_off, len);
msleep(100);
failed_tests++;
continue;
}
//接收的dma,先准备接收的
init_completion(&rx_cmp);
rxd->callback = dmatest_slave_rx_callback;//dma搬运完这个函数会运行
rxd->callback_param = &rx_cmp;
rx_cookie = rxd->tx_submit(rxd);
//发送的dma,
init_completion(&tx_cmp);
txd->callback = dmatest_slave_tx_callback;//dma搬运完这个函数会运行
txd->callback_param = &tx_cmp;
tx_cookie = txd->tx_submit(txd);
if (dma_submit_error(rx_cookie) ||
dma_submit_error(tx_cookie)) {
pr_warn("%s: #%u: submit error %d/%d with src_off=0x%x ",
thread_name, total_tests - 1,
rx_cookie, tx_cookie, src_off);
pr_warn("dst_off=0x%x len=0x%x\n",
dst_off, len);
msleep(100);
failed_tests++;
continue;
}
dma_async_issue_pending(rx_chan);
dma_async_issue_pending(tx_chan);
//--dma_async_issue_pending这个执行后就相当于给dma控制器发命令了,dma控制器就会根据我们提供的参数来自己搬运数据了
tx_tmo = wait_for_completion_timeout(&tx_cmp, tx_tmo);
//等待发送dma传输完 等completion
status = dma_async_is_tx_complete(tx_chan, tx_cookie,
NULL, NULL);
if (tx_tmo == 0) {
pr_warn("%s: #%u: tx test timed out\n",
thread_name, total_tests - 1);
failed_tests++;
continue;
} else if (status != DMA_COMPLETE) {
pr_warn("%s: #%u: tx got completion callback, ",
thread_name, total_tests - 1);
pr_warn("but status is \'%s\'\n",
status == DMA_ERROR ? "error" :
"in progress");
failed_tests++;
continue;
}
rx_tmo = wait_for_completion_timeout(&rx_cmp, rx_tmo);
//等待接收传输完
status = dma_async_is_tx_complete(rx_chan, rx_cookie,
NULL, NULL);
if (rx_tmo == 0) {
pr_warn("%s: #%u: rx test timed out\n",
thread_name, total_tests - 1);
failed_tests++;
continue;
} else if (status != DMA_COMPLETE) {
pr_warn("%s: #%u: rx got completion callback, ",
thread_name, total_tests - 1);
pr_warn("but status is \'%s\'\n",
status == DMA_ERROR ? "error" :
"in progress");
failed_tests++;
continue;
}
//前面是先映射,这里是解除映射,这样缓冲区的数据才是正确的,不执行这个操作,缓冲区数据不对。
/* Unmap by myself */
for (i = 0; i < dst_cnt; i++)
dma_unmap_single(rx_dev->dev, dma_dsts[i],
test_buf_size, DMA_BIDIRECTIONAL);
error_count = 0;
start = ktime_get();
pr_debug("%s: verifying source buffer...\n", thread_name);
error_count += dmatest_verify(thread->srcs, 0, src_off,
0, PATTERN_SRC, true);
error_count += dmatest_verify(thread->srcs, src_off,
src_off + len, src_off,
PATTERN_SRC | PATTERN_COPY, true);
error_count += dmatest_verify(thread->srcs, src_off + len,
test_buf_size, src_off + len,
PATTERN_SRC, true);
pr_debug("%s: verifying dest buffer...\n",
thread->task->comm);
//--校验接收的数据和发送的数据,
error_count += dmatest_verify(thread->dsts, 0, dst_off,
0, PATTERN_DST, false);
error_count += dmatest_verify(thread->dsts, dst_off,
dst_off + len, src_off,
PATTERN_SRC | PATTERN_COPY, false);
error_count += dmatest_verify(thread->dsts, dst_off + len,
test_buf_size, dst_off + len,
PATTERN_DST, false);
diff = ktime_sub(ktime_get(), start);
comparetime = ktime_add(comparetime, diff);
if (error_count) {
pr_warn("%s: #%u: %u errors with ",
thread_name, total_tests - 1, error_count);
pr_warn("src_off=0x%x dst_off=0x%x len=0x%x\n",
src_off, dst_off, len);
failed_tests++;
} else {
pr_debug("%s: #%u: No errors with ",
thread_name, total_tests - 1);
pr_debug("src_off=0x%x dst_off=0x%x len=0x%x\n",
src_off, dst_off, len);
}
}
ktime = ktime_sub(ktime_get(), ktime);
ktime = ktime_sub(ktime, comparetime);
ktime = ktime_sub(ktime, filltime);
runtime = ktime_to_us(ktime);
ret = 0;
for (i = 0; thread->dsts[i]; i++)
kfree(thread->dsts[i]);
err_dstbuf:
kfree(thread->dsts);
err_dsts:
for (i = 0; thread->srcs[i]; i++)
kfree(thread->srcs[i]);
err_srcbuf:
kfree(thread->srcs);
err_srcs:
pr_notice("%s: terminating after %u tests, %u failures %llu iops %llu KB/s (status %d)\n",
thread_name, total_tests, failed_tests,
dmatest_persec(runtime, total_tests),
dmatest_KBs(runtime, total_len), ret);
thread->done = true;
wake_up(&thread_wait);
return ret;
}
至此,axidmatest.c主要部分就是这个任务,所以,把这个搞明白就行,还有一点数据准备这块有个字节对齐,有点麻烦,容易搞不清楚,可以使用__get_free_pages申请缓冲区,这种函数本身就是对齐的,还有,这种流式的dma要先dma_map_single传输完后还要dma_unmap_single后才能去读传输的数据,这种函数我测试数据量小的话好用,如果很大的话,就比较花时间,一致性dma就不用这样。后面有时间吧这个改造一下,做一致性的dma,写的尽量简单能用,能理解大概就行。
之前在闲鱼上买的一个fmc 测试nvme-ssd硬盘的测试板,测试过不怎么用了,现在出手,
Fmc nvme sata pinggu评估 测试板,可以在具有fmc接口的开发板测试 比如zynq zc706上测试nvme协议的固态硬盘ssd,提供技术支持,比如怎么在vivado建立工程,linux下驱动指导等
-
Zynq-Linux-DMA-master
2018-01-23 17:25:32DMA enabled Zynq PS-PL communication to implement high throughput data transfer between Linux applications and user IP core. (based on Xilinx UG873 chapter 6) This is a simple loop-back project in ... -
ZYNQ AXI DMA 学习总结
2019-02-21 09:32:06学习参考资料传送门: 【原创】ZYNQ AXIDMA详解(一) ...zynq linux AXI DMA传输步骤教程详解 【ZYNQ-7000——开发之五】:AXI DMA读写FIFO ZYNQ跑系统 系列(四) AXI-DMA的linux下运行 ... -
Zynq MPSoC 官方Linux DMA驱动调试
2020-05-09 23:29:07Zynq MPSoC Linux官方DMA驱动调试 前言 Zynq平台下DMA驱动主要有官方在用户层控制的和某大神写的axi_dma驱动,今天主要用官方的进行测试。 环境 petalinux 19.1 vivado 19.1 开始 首先搭建逻辑,注意这里DMA用64地址...Zynq MPSoC Linux官方DMA驱动调试
前言
Zynq平台下DMA驱动主要有官方在用户层控制的和某大神写的axi_dma驱动,今天主要用官方的进行测试。
环境
petalinux 19.1
vivado 19.1开始
首先搭建逻辑,注意这里DMA用64地址线,不然4GB以上的DDR访问不到,然后输入输出就挂到FIFO上就行。
然后FIFO这样挂上去就行
然后编译,这是一个漫长的过程,主要是我有个自己的IP以及VCU,所以特别慢。Linux驱动
1、首先下载驱动,地址如下:添加链接描述
这个也是我参考的
2、然后根据他的进行编译,注意这里编译完了之后,启动的时候会出现一个问题,驱动挂载失败,然后报错,这个是petalinux_19.1的问题,然后官方给的解决方案是打补丁,连接如下:添加链接描述
3、然后呢,还有个问题就是DMA传输一遍后time_out,这里的解决方案也比较简单,如下:/* Close the file and there's nothing to do for it */ static int release(struct inode *ino, struct file *file) { struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data; struct dma_device *dma_device = pchannel_p->channel_p->device; /* Stop all the activity when the channel is closed assuming this * may help if the application is aborted without normal closure */ //注释掉这里就ok //dma_device->device_terminate_all(pchannel_p->channel_p); return 0; }
4、测试,测试也用官方的就行
自定义自己驱动
1、这里就是等待DMA读取完成,然后我们用来读取数据进行编码,如果超过5s,自行跳出
static ssize_t read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { int ret_val; struct dma_proxy_channel *pchannel_p = (struct dma_proxy_channel *)file->private_data; //char* data = share_mem_dev.base_addr; unsigned int cnt = size; unsigned int p = *ppos; u64 phy_addr = pchannel_p->dma_handle; u64 off = phy_addr - pchannel_p->reva_mem_start; //physcicl addr offset char* data = pchannel_p->reva_mem_vaddr + off; //ensure read size right if(p >= MEM_SIZE) return cnt ? -ENXIO : 0; if(cnt > MEM_SIZE - p) cnt = MEM_SIZE - p; #define WAIT_TIMEOUT_DURATION (HZ * 5) int err = wait_event_interruptible_timeout(pchannel_p->queue, pchannel_p->dma_rx_finish_flag, WAIT_TIMEOUT_DURATION); if(err = 0) { printk("[%s] read time out\r\n", DRIVER_NAME); return -EINVAL; } pchannel_p->dma_rx_finish_flag = 0; //start next transmit , and the transmit and encode can be run at the same time ret_val = copy_to_user(buf, data + p, cnt); if(ret_val < 0) { printk("[%s] err copy data to user\r\n", DRIVER_NAME); return -1; }else{ *ppos += cnt; } return cnt; }
2、然后在DMA回调函数那里修改:
static void sync_callback(void *pchannel_p) { /* Indicate the DMA transaction completed to allow the other * thread of control to finish processing */ struct dma_proxy_channel *chan = pchannel_p; chan->dma_rx_finish_flag = 1; //to tell read func i read finsh complete(&chan->cmp); }
3、测试
就直接改的官方测试程序,然后我这个用fifo测试成功了,后面该到了自己的IP上,然后效果如下root@RS-IPU:/mnt/bin# ./dma_test 1 1 DMA proxy test [ 26.797779] start ip 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 DMA proxy test complete
其实上面的结果就是一幅图像的ROI图,有1代表该区域为ROI,哈哈,成功了,开森,就是下面这个图了
END
最后,我这里开源一下我封装好的驱动,用C++写的,因为在上层,所以比较方便调试:
#ifndef _DMA_BSP_H_ #define _DMA_BSP_H_ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/mman.h> #include <fcntl.h> #include <sys/ioctl.h> #include <pthread.h> #define DMA_ALLOC_SIZE (3840 * 2160 * 5) #define IO_TRANSFRER_TEST 0 #define IO_START_IP 1 #define IO_SET_IMG_PARAM 2 enum { TYPE_VEDIO = 0x01, TYPE_IMG = 0x11 }; struct usr_param { unsigned char type; unsigned char fps; unsigned char Qp; unsigned char resv; unsigned int width; unsigned int height; }; struct dma_proxy_channel_interface { unsigned char buffer[DMA_ALLOC_SIZE]; enum proxy_status { PROXY_NO_ERROR = 0, PROXY_BUSY = 1, PROXY_TIMEOUT = 2, PROXY_ERROR = 3 } status; unsigned int length; }; class dma_ctrl { private: struct dma_proxy_channel_interface* dma_proxy_interface_p; struct usr_param dev_ctrl_param; int dev_fd; public: dma_ctrl(char* dev_name); ~dma_ctrl(); public: void dma_set_transmit_data(unsigned char* buff, unsigned int size, unsigned int off); void dma_get_transmit_data(unsigned char* buff, unsigned int size, unsigned int off); int dev_set_param(struct usr_param* param); void dma_set_size(unsigned int size); int dma_start_transmit(void); int dma_test_transmit(void); }; #endif
主程序:
#include "dma_bsp.h" #include <cstring> dma_ctrl::dma_ctrl(char* dev_name) { dev_fd = open(dev_name, O_RDWR); if (dev_fd < 1) { printf("Unable to open device file, name: %s\r\n", dev_name); exit(EXIT_FAILURE); } dma_proxy_interface_p = (struct dma_proxy_channel_interface *)mmap(NULL, sizeof(struct dma_proxy_channel_interface), PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd, 0); if (dma_proxy_interface_p == MAP_FAILED) { printf("Unable to mmap device file, name: %s\r\n", dev_name); exit(EXIT_FAILURE); } } dma_ctrl::~dma_ctrl() { if(dev_fd) { close(dev_fd); } } void dma_ctrl::dma_set_transmit_data(unsigned char* buff, unsigned int size, unsigned int off) { if(!dma_proxy_interface_p) { printf("err init mmap\r\n"); } memcpy(dma_proxy_interface_p->buffer + off, buff, size); } void dma_ctrl::dma_set_size(unsigned int size) { dma_proxy_interface_p->length = size; } void dma_ctrl::dma_get_transmit_data(unsigned char* buff, unsigned int size, unsigned int off) { if(!dma_proxy_interface_p) { printf("err init mmap\r\n"); } memcpy(buff, dma_proxy_interface_p->buffer + off, size); //dma_proxy_interface_p->length = size; } int dma_ctrl::dma_start_transmit() { return ioctl(dev_fd, IO_START_IP, NULL); } int dma_ctrl::dev_set_param(struct usr_param* param) { if(!param) return NULL; return ioctl(dev_fd, IO_SET_IMG_PARAM, param); } int dma_ctrl::dma_test_transmit() { return ioctl(dev_fd, IO_TRANSFRER_TEST, NULL); }
测试用例:
void safe_main_thread(int argc, char* argv[]) { dma_ctrl rs_ipu_img_rx_ctrl(RS_IPU_IMG_RX_DEV); dma_ctrl rs_ipu_img_tx_ctrl(RS_IPU_IMG_TX_DEV); dma_ctrl rs_ipu_bitstream_rx_ctrl(RS_IPU_BIT_RX_DEV); dma_ctrl rs_ipu_bitstream_tx_ctrl(RS_IPU_BIT_TX_DEV); unsigned char rx_data[128]; #define TEST_SIZE (DMA_ALLOC_SIZE * 4 / 5) unsigned char* malloc_data = (unsigned char*)malloc(TEST_SIZE); int index = 0; unsigned char gray = 0; for(int i = 0; i < 5; i++){ for(int j = 0; j < 2160; j++) { for(int j = 0; j < 3840; j++) { malloc_data[index++] = gray++; if (gray >= 255) { gray = 0; } } } } std::cout << "ipu_test" <<endl; rs_ipu_img_tx_ctrl.dma_set_transmit_data(malloc_data, 128, 0); rs_ipu_img_tx_ctrl.dma_set_size(128); rs_ipu_img_tx_ctrl.dma_test_transmit(); std::cout << "ipu_test endl" <<endl; rs_ipu_bitstream_tx_ctrl.dma_set_transmit_data(malloc_data, 128, 0); rs_ipu_bitstream_tx_ctrl.dma_set_size(128); rs_ipu_bitstream_tx_ctrl.dma_test_transmit(); std::cout << "bitstream_test endl" <<endl; rs_ipu_img_rx_ctrl.dma_set_size(128); rs_ipu_img_rx_ctrl.dma_test_transmit(); rs_ipu_img_rx_ctrl.dma_get_transmit_data(rx_data, 128, 0); std::cout << "rx data : " << endl; for(int i = 0; i < 128; i++) { std::cout << (int)(rx_data[i]) << " "; } std::cout << endl; free(malloc_data); }
tx过程就是先设置传输的data,然后设置大小,最后dma_test_transmit()
rx过程先设置大小,然后transmit,然后get_data -
Zynq-linux PL与PS通过DMA数据交互
2019-10-04 10:39:55在米尔科技的z-turn板上,采用AXI DMA 实现zynq的PS与PL数据交互。 二、分析 ①PS数据传PL 驱动中的测试程序中给出一堆数据,通过DMA传输到AXI4-Stream Data FIFO ,PL端从DATA FIFO中把数据读出来。 ②PL数据传...一、目标
在米尔科技的z-turn板上,采用AXI DMA 实现zynq的PS与PL数据交互。
二、分析
①PS数据传PL
驱动中的测试程序中给出一堆数据,通过DMA传输到AXI4-Stream Data FIFO ,PL端从DATA FIFO中把数据读出来。
②PL数据传PS
将PS传入PL的数据回传,在PS端显示出数据,最后将数据乘2再送入DMA。
③PL端代码思路
1)读数据
在加上DATA FIFO的情况下,PL从DATA FIFO中读取数据。将DATA -FIFO的M_AXIS端引出,得到下面的信号。信号名称 作用 方向 m_axis_tvalid 主机向从机发数据的信号声明,置1表示主机有数据要发向从机。 输出 m_axis_tready 主机判断从机是否准备好接收数据,当为1时表示从机准备好接收数据。 输入 m_axis_tdata 主机将要发送的数据。 输出 m_axis_tkeep 主机发送数据时需拉高。 输出 m_axis_tlast 表示主机发送是否是最后一个数据,置1时表示主机正在发送最后一个数据。 输出 从上表可以看出,DATA FIFO接收完数据后,想要从FIFO中读取数据,靠的就是上面5根线,FIFO作主机,PL端作从机。所以FIFO会自动将m_axis_tvalid置1,表明可以从主机中读取数据。PL只需要给出回应m_axis_tready置1,便可以在时钟上升沿来临时读取数据。
同时,AXI-DMA将PS数据传输完成后有完成标志mm2s_introut置1。通过这个标准来确定是否从FIFO中读取数据。所以这个信号用于PL中断的触发信号。
2)写数据
将PL读取出来的数据进行乘2,再传入PS,PS再将数据打印出来。PL端接的是AXI-DMA的S_AXIS_S2MM端口。其信号如下:信号名称 作用 方向 s_axis_s2mm_tdata 从机接收数据线 输入 s_axis_s2mm_tkeep 数据有效信号线 输入 s_axis_s2mm_tlast 是否最后一个数据 输入 s_axis_s2mm_tready 从机准备是否准备好接收数据 输出 s_axis_s2mm_tvalid 主机是否有数据发向从机 输入 从信号列表可以看出,此时DMA端口是作从机,PL端口作主机向DMA端口发送数据。PL端想发送数据,通过s_axis_s2mm_tvalid表明有数据发往从机。等待从机响应s_axis_s2mm_tready信号,响应过后便可以发送数据。发送数据时需要将s_axis_s2mm_tkeep拉高,同时当传到最后一个数据时,需要将s_axis_s2mm_tlast置1。
3)整体架构
局部放大图:
三、代码实现
①pl_read.vmodule pl_read( clk, rst, m_axis_tvalid, m_axis_tdata, m_axis_tkeep, m_axis_tlast, m_axis_tready, m_ready, m_data, m_datavalid, m_datalast ); input clk; input rst; input m_axis_tvalid; input [31:0]m_axis_tdata; input [3:0]m_axis_tkeep; input m_axis_tlast; output m_axis_tready; input m_ready; output [31:0]m_data; output m_datavalid; output m_datalast; reg m_axis_tready; reg [31:0]m_data; reg m_datavalid; reg m_datalast; always@(posedge clk or negedge rst) begin if(!rst) begin m_axis_tready <= 0; m_data <= 0; m_datavalid <= 0; m_datalast <= 0; end else begin if(m_ready==1) begin m_axis_tready <= 1; if(m_axis_tvalid==1&&m_axis_tkeep==4'b1111) begin m_data <= m_axis_tdata; m_datavalid <= 1; if(m_axis_tlast==1) m_datalast <= 1; else m_datalast <= 0; end else begin m_data <= m_data; m_datavalid <= 0; m_datalast <= 0; end end else begin m_axis_tready <= 0; m_data <= m_data; m_datavalid <= 0; m_datalast <= 0; end end end endmodule
②pl_write.v
module pl_write( clk, rst, s_data, s_datavalid, s_datalast, s_ready, s_axis_tdata, s_axis_tkeep, s_axis_tlast, s_axis_tready, s_axis_tvalid ); input clk; input rst; input[31:0] s_data;//data stream input s_datalast;//data last flag input s_datavalid;//input data valid flag output s_ready;//数据可以写入标志 input s_axis_tready; output[31:0] s_axis_tdata; output[3:0]s_axis_tkeep; output s_axis_tlast; output s_axis_tvalid; reg [31:0] s_axis_tdata; reg [3:0]s_axis_tkeep; reg s_axis_tlast; reg s_axis_tvalid; reg s_ready; always@(posedge clk or negedge rst) begin if(!rst) begin s_axis_tkeep <= 4'b0000; s_axis_tvalid <= 0; s_axis_tdata <= 0; s_axis_tlast<=0; s_ready <= 0; end else begin if(s_axis_tready==1) begin if(s_datavalid==1) begin s_axis_tvalid <= 1; s_axis_tkeep <= 4'b1111; s_axis_tdata <= s_data; if(s_datalast)s_axis_tlast <= 1; else s_axis_tlast <= 0; end else begin s_axis_tvalid <= 0; s_axis_tkeep <= 4'b0000; s_axis_tdata <= s_axis_tdata; s_axis_tlast<=0; end s_ready <= 1; end else begin s_axis_tkeep <= 4'b0000; s_axis_tvalid <= 0; s_axis_tdata <= s_axis_tdata; s_axis_tlast<=0; s_ready <= 0; end end end endmodule
③top.v
module top( ); wire clk; wire rst; wire m_axis_tvalid; wire [31:0]m_axis_tdata; wire [3:0]m_axis_tkeep; wire m_axis_tlast; wire m_axis_tready; wire m_ready; wire [31:0]m_data; wire m_datavalid; wire m_datalast; wire s_axis_tready; wire[31:0] s_axis_tdata; wire[3:0]s_axis_tkeep; wire s_axis_tlast; wire s_axis_tvalid; pl_write u1( .clk(clk), .rst(rst), .s_data(m_data), .s_datalast(m_datalast), .s_datavalid(m_datavalid), .s_ready(m_ready), .s_axis_tready(s_axis_tready), .s_axis_tdata(s_axis_tdata), .s_axis_tkeep(s_axis_tkeep), .s_axis_tlast(s_axis_tlast), .s_axis_tvalid(s_axis_tvalid) ); pl_read u2( .clk(clk), .rst(rst), .m_data(m_data), .m_datalast(m_datalast), .m_datavalid(m_datavalid), .m_ready(m_ready), .m_axis_tready(m_axis_tready), .m_axis_tdata(m_axis_tdata), .m_axis_tkeep(m_axis_tkeep), .m_axis_tlast(m_axis_tlast), .m_axis_tvalid(m_axis_tvalid) ); system_wrapper u3( .FCLK_CLK0(clk), .peripheral_aresetn(rst), .s_axis_aclk(clk), .s_axis_aclk_1(clk), .s_axis_aresetn(rst), .s_axis_aresetn_1(rst), .s_axis_tready(s_axis_tready), .s_axis_tdata(s_axis_tdata), .s_axis_tkeep(s_axis_tkeep), .s_axis_tlast(s_axis_tlast), .s_axis_tvalid(s_axis_tvalid), .m_axis_tready(m_axis_tready), .m_axis_tdata(m_axis_tdata), .m_axis_tkeep(m_axis_tkeep), .m_axis_tlast(m_axis_tlast), .m_axis_tvalid(m_axis_tvalid) ); endmodule
④dma驱动代码
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/io.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/miscdevice.h> #include <linux/ioport.h> #include <linux/of.h> #include <linux/uaccess.h> #include <linux/interrupt.h> #include <asm/irq.h> #include <linux/irq.h> #include <asm/uaccess.h> #include <linux/dma-mapping.h> /** *DMA驱动程序 * * * * * * *` * * **/ //DMA 基地址 #define DMA_S2MM_ADDR 0X40400000 #define DMA_MM2S_ADDR 0X40410000 //DMA MM2S控制寄存器 volatile unsigned int * mm2s_cr; #define MM2S_DMACR 0X00000000 //DMA MM2S状态控制寄存器 volatile unsigned int * mm2s_sr; #define MM2S_DMASR 0X00000004 //DMA MM2S源地址低32位 volatile unsigned int * mm2s_sa; #define MM2S_SA 0X00000018 //DMA MM2S传输长度(字节) volatile unsigned int * mm2s_len; #define MM2S_LENGTH 0X00000028 //DMA S2MM控制寄存器 volatile unsigned int * s2mm_cr; #define S2MM_DMACR 0X00000030 //DMA S2MM状态控制寄存器 volatile unsigned int * s2mm_sr; #define S2MM_DMASR 0X00000034 //DMA S2MM目标地址低32位 volatile unsigned int * s2mm_da; #define S2MM_DA 0X00000048 //DMA S2MM传输长度(字节) volatile unsigned int * s2mm_len; #define S2MM_LENGTH 0X00000058 #define DMA_LENGTH 524288 dma_addr_t axidma_handle; volatile unsigned int * axidma_addr; static irqreturn_t dma_mm2s_irq(int irq,void *dev_id) { printk("\nPs write data to fifo is over! irq=%d\n",irq); iowrite32(0x00001000,mm2s_sr); return IRQ_HANDLED; } static irqreturn_t dma_s2mm_irq(int irq,void *dev_id) { iowrite32(0x00001000,s2mm_sr); printk("\nps read data from fifo is over! irq=%d\n",irq);//读出了FIFO里的数据触发中断 return IRQ_HANDLED; } int major; static struct class *dma_class = NULL; static int dma_init(void); static int dma_exit(void); static int dma_open(struct inode *inode,struct file *file); static int dma_write(struct file *file,const char __user *buf, size_t count,loff_t *ppos); static int dma_read(struct file *file,char __user *buf,size_t size,loff_t *ppos); /* *file_operations 结构数据,沟通内核与操作系统桥梁 * * */ static struct file_operations dma_lops= { .owner = THIS_MODULE, .read = dma_read, .open = dma_open, .write = dma_write, }; /* * 初始化,用于module init * * */ static int dma_init(void) { major=register_chrdev(0,"dma_dev",&dma_lops); dma_class= class_create(THIS_MODULE,"dma_dev"); device_create(dma_class,NULL,MKDEV(major,0),NULL,"dma_dev"); printk("major dev number= %d",major); mm2s_cr = ioremap(DMA_MM2S_ADDR+MM2S_DMACR, 4); mm2s_sr = ioremap(DMA_MM2S_ADDR+MM2S_DMASR, 4); mm2s_sa = ioremap(DMA_MM2S_ADDR+MM2S_SA, 4); mm2s_len = ioremap(DMA_MM2S_ADDR+MM2S_LENGTH,4); s2mm_cr = ioremap(DMA_S2MM_ADDR+S2MM_DMACR, 4); s2mm_sr = ioremap(DMA_S2MM_ADDR+S2MM_DMASR, 4); s2mm_da = ioremap(DMA_S2MM_ADDR+S2MM_DA, 4); s2mm_len = ioremap(DMA_S2MM_ADDR+S2MM_LENGTH,4); return 0; } /* *退出 用于 module exit * * */ static int dma_exit(void) { unregister_chrdev(major,"dma_dev"); device_destroy(dma_class,MKDEV(major,0)); class_destroy(dma_class); free_irq(dma_mm2s_irq,NULL); free_irq(dma_s2mm_irq,NULL); dma_free_coherent(NULL,DMA_LENGTH,axidma_addr,axidma_handle); iounmap(mm2s_cr); iounmap(mm2s_sr); iounmap(mm2s_sa); iounmap(mm2s_len); iounmap(s2mm_cr); iounmap(s2mm_sr); iounmap(s2mm_da); iounmap(s2mm_len); return 0; } /* *open 接口函数 * * */ static int dma_open(struct inode *inode,struct file *file) { int err; printk("DMA open\n"); axidma_addr = dma_alloc_coherent(NULL,DMA_LENGTH,&axidma_handle,GFP_KERNEL); err = request_irq(61,dma_mm2s_irq,IRQF_TRIGGER_RISING,"dma_dev",NULL); printk("err=%d\n",err); err = request_irq(62,dma_s2mm_irq,IRQF_TRIGGER_RISING,"dma_dev",NULL); printk("err=%d\n",err); return 0; } /* * write 接口函数 * * */ static int dma_write(struct file *file,const char __user *buf, size_t count,loff_t *ppos) { unsigned int mm2s_status = 0; printk("dma write start !\n"); if(count>DMA_LENGTH) { printk("the number of data is too large!\n"); return 0; } memcpy(axidma_addr,buf,count); iowrite32(0x00001001,mm2s_cr); iowrite32(axidma_handle,mm2s_sa); iowrite32(count,mm2s_len); mm2s_status = ioread32(mm2s_sr); while((mm2s_status&(1<<1))==0) { mm2s_status = ioread32(mm2s_sr); } printk("mm2s_status =0x%x\n",mm2s_status); printk("dma write is over!\n"); return 0; } /* * read 接口函数 *DMA读取数据是按照32bit读取的 * */ static int dma_read(struct file *file,char __user *buf,size_t size,loff_t *ppos) { unsigned int s2mm_status=0; printk("dma read start!\n"); if(size>DMA_LENGTH) { printk("the number of data is not enough!\n"); return 1; } iowrite32(0x00001001,s2mm_cr); iowrite32(axidma_handle,s2mm_da); iowrite32(size,s2mm_len); s2mm_status=ioread32(s2mm_sr); while((s2mm_status&(1<<1))==0) { s2mm_status=ioread32(s2mm_sr); } printk("s2mm_sr=0x%x\n",s2mm_status); memcpy(buf,axidma_addr,size); printk("\ndma read is over!\n"); return 0; } module_init(dma_init); module_exit(dma_exit); MODULE_AUTHOR("TEST@dma"); MODULE_DESCRIPTION("dma driver"); MODULE_ALIAS("dma linux driver"); MODULE_LICENSE("GPL");
⑤测试代码
#include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> void delay(void) { int i,j; for(i=0;i<20000;i++) for(j=0;j<10000;j++); } unsigned int readarray[10001]; int main(int argc , char ** argv) { int fd; int i=0; fd = open("/dev/dma_dev",O_RDWR); if(fd<0) {printf("can not open file\n");while(1);} else printf("open file sucuss\n"); delay(); for(i=0;i<4000;i++) { readarray[i]=i+1; } while(1) { write(fd,readarray,4000*4); if(read(fd,readarray,4000*4)==0) { for(i=0;i<4000;i++) { printf(" %d",readarray[i]); readarray[i]=readarray[i]*2; } printf("\n=====================================\n"); printf("======================================\n"); } delay();delay(); } return 0; }
⑥Makefile文件
KDIR = /home/python/Hard_disk_21G/04-Linux_Source/Kernel/linux-xlnx PWD := $(shell pwd) CC = $(CROSS_COMPILE)gcc ARCH =arm MAKE =make obj-m:=dma_driver.o modules: $(MAKE) -C $(KDIR) ARCH=$(ARCH) CROSS_COMPLE=$(CROSS_COMPLE) M=$(PWD) modules clean: make -C $(KDIR) ARCH=$(ARCH) CROSS_COMPLE=$(CROSS_COMPLE) M=$(PWD) clean
⑦运行结果
可以看见程序运行结果,符合预期值。
四、调试
在调试数据交互的过程中,遇到了很多坑。第一个大坑就是在DATA-FIFO中。数据位宽设置的8bit,PL将测试数据写入DMA,PS端读出数据,发现数据变得大而且只有前面部分数据不为0,后面数据全是0,后来发现是PS端读DMA是按照字节进行读取的,而PS端读取的数据放在整形数组里面,结果导致是PL端的4个数据合成了PS端一个数据,最后导致PL端发送200个数据,结果PS端只有前50个数据有值,后面全是0。后面将PL的位宽设置为32bit,PS端读出数据就正缺了。 第二大坑就是忽略了tlast信号。这块问题找了很久,最后通过慢慢测试发现了。当时在PL端发送10万个数据,PS端一次性读取10万个数据没问题,然后想测试读取少一点,结果读取一千,两千,一万个数据都可以,同时数据是正确的。到了第二次循环是就出现问题了,第二次读取的数据还是同第一次读取的数据一样。通过读取DMA状态寄存器的值,打印出来是0x4011,判断出DMA内部错误。后来调试出问题是:在传输期间没有tlast信号,所以报出DMA内部错误。再次调试,设置一万个数据给个tlast,读取2万个数据,最后发现只能读取出前面1万个数据,后面全是0,第二轮还是一样,只有前面1万个有数据,后面全是0。最后明白了DMA传输中在一次传输过程中,只能且必须有一次tlast信号,也不能读出tlast信号后的数据。 第三个坑就是在模块设计时,报DMA的S_AXIS_S2MM与FIFO的M_AXIS时钟不匹配,还以为是哪儿没弄对,看别人的都没问题。后来把器件全删了,先把fifo与DMA联接起来,在进行自动连线,问题就解决了。 第四个坑就在PL端的例化,连线上。在仿真fifo的时候,先编写一个模块与fifo对应,同时在模块里面对fifo的S_AXIS端连线,在simulation文件中将fifo的M_AXIS连线,功能仿真想观察波形,结果咋调,波形都是xxxx,AXI时序修改了又修改,还是出问题。后来发现是出现了两个fifo,一个是模块里连线的fifo,另一个是simulation里的fifo,所以就出现了波形是xxxx的情况。后面改成了,增加一个top.v在top里面进行各个模块的连线,时钟也不要在单独例化在底层模块里,全部在top里面连线,例化。
五、总结
①每次进行DMA传输是,读取DMA状态寄存器,根据状态寄存器的值,判断是否发生DMA错误。如果发生DMA内部错误,则是由于DMA传输过程中没有收到tlast信号。
②PS是按字节进行读取或写入DMA的,要注意存放数据的数组的变量类型,即整形数组和字符型数组和PL端的位宽匹配好。 -
ZYNQ PCIe EP实现DMA+Linux交互,非常简洁的程序
2019-06-19 15:17:46ZYNQ PCIe-DMA源码 例程 PS-PL交互 linux/裸机 verilog C/C++ZYNQ PCIe-DMA的实现过程一、概述二、基础知识三、系统总框架四、工作原理与工作模式五、接口时序六、资源使用情况七、PS-PL交互以及测试程序 ZYNQ PCIe-... -
ZYNQ Linux应用层 利用 AXI DMA 进行数据传输
2021-03-02 15:27:17ZYNQ Linux应用层 利用 AXI DMA 进行数据传输 软件版本: VIVADO2018.2 操作系统: Debian 9 硬件平台: ZYNQ-MZ7100FA 嵌入式操作系统:Debian(4.19.0-xilinx) AXI-DMA驱动:GitHub:bperez77/xilinx_axidma 一、... -
米尔科技ZYNQ -Linux下的DMA驱动
2019-08-08 19:15:19在米尔科技的z-turn板上实现linux下的DMA驱动,同时对DMA中断进行测试。 二.分析 ZYNQ的AXIDMA有Direct Register Mode和Scatter/Gather Mode,本文使用的是Direct Register Mode。 Vivado上PL端的构造如下图所示... -
ZYNQ跑系统 系列(四) AXI-DMA的linux下运行
2018-05-30 10:47:42AXI-DMA的linux驱动 一、搭建硬件环境 vivado版本2017.4,芯片为7010,不过不管什么版本和芯片大致步骤是一样的 硬件平台PL的搭建同ZYNQ基础系列(六) DMA基本用法,在这个工程的基础上添加SD卡(根据自己的... -
2019.6.17 Xilinx FPGA Zynq DMA驱动 Linux测试
2019-06-17 20:25:01http://www.fpgadeveloper.com/2014/08/using-the-axi-dma-in-vivado.html https://blog.csdn.net/qq_20091945/article/details/70194026 github: *使用 vivado2018.2 ,linaro15.4,设备树卡一半内存,设置一半... -
从零开始zynq linux AXI DMA传输
2017-04-02 19:24:33gsc@gsc-250:~/zynq7000/ZedBoard/projects/linux_kernel/scripts/dtc$ dtc -O dtb -I dts -o devicetree.dtb /home/gsc/zynq7000/ZedBoard/projects/hardware_design/axidma_user/axidma_user.sdk/device_tree_bsp_... -
zynq 嵌入式Linux下的DMA测试(PS+PL)
2018-05-15 13:44:27ZedBorad 嵌入式Linux下的DMA测试(PS+PL), 包含VIVADO工程代码,Linux下DMA测试APP源代码,Makefile文件,亲测可用。 -
zynq DMA分析
2014-12-08 21:40:30Zynq Linux pl330 DMA Edit 0 10 … 3 Tags DMAlinuxZynq Notify RSS Backlinks Source Print Export (PDF) IMPORTANT NOTE: The reference implementation contained ... -
AXI DMA linux驱动
2019-10-09 18:50:28PL端设计包括四个AXI DMA IP,它们分别和zynq处理IP的HP口相连接。 这个设计是基于Avnet-Digilent-ZedBoard-v2016.1-final.bsp,由于其它的ip都是xilinx开发环境开发环境就有,所以这里就不详细每一步设计过程了。 ... -
-Wignored-attributes in drivers/dma/xilinx/zynqmp_dma.c
2020-12-09 00:33:06CC drivers/dma/xilinx/zynqmp_dma.o drivers/dma/xilinx/zynqmp_dma.c:166:4: warning: attribute 'aligned' is ignored, place it after "struct" to apply attribute to type declaration [-... -
zynq操作系统: Linux驱动开发AXIDMA篇
2021-04-12 13:27:37先不谈如何实现用户空间的零拷贝DMA传输,光是Linux环境下的DMA传输就已经感觉比较棘手,一方面是对Linux了解不够深入,另一方面则是Linux在相关的使用说明方面的确没有比较好的官方支持。 -
zynq操作系统:Linux驱动开发AXIDMA补充篇 多路DMA
2021-04-14 17:15:28上一篇的一路双通道DMA的正常收发已经成功实现了,但是实际使用的时候大概率会挂载多路dma,那么我们调用的这个模块能不能支持多路的dma便是第一个要解决的问题 首先阅读初始化部分的代码,自然有了第一个...