问题描述
我有一个 Xilinx SoC,并通过 verilog 在可编程逻辑上创建了一个简单的乘法器。乘法器接受两个 16 位输入,将它们相乘并返回一个 32 位输出。数字设计已通过 AXI-Lite 接口打包并链接到 SoC 内的处理器系统。 Xilinx 工具为这个数字设计自动生成了一个设备树实体,以便可以创建一个定制的 linux 设备驱动程序来与数字设计交互(即 PS 将把它当作一个连接到 ARM 处理器的外部硬件设备) .
生成的设备树如下所示:
/ {
amba_pl: amba_pl@0 {
#address-cells = <2>;
#size-cells = <2>;
compatible = "simple-bus";
ranges ;
multi2_0: multi2@a0000000 {
clock-names = "s00_axi_aclk";
clocks = <&zynqmp_clk 71>;
compatible = "xlnx,multi2-1.0";
reg = <0x0 0xa0000000 0x0 0x10000>;
xlnx,s00-axi-addr-width = <0x4>;
xlnx,s00-axi-data-width = <0x20>;
};
};
};
所以从设备树中我们可以看到乘法器(“multi2-1.0”)的物理内存地址为0xa0000000,地址宽度为0x4,数据宽度为32位。
因此,从设备驱动程序的角度来看,特别是在写入回调函数中,我将一个 32 位数字写入从“ioremap(.)”函数检索到的虚拟内存地址。
进行了健全性检查以查看虚拟内存映射到物理地址,它似乎正确无误地完成(下面显示了驱动程序中一些与内存相关的代码片段):
struct simpmod_local {
int irq;
unsigned long mem_start;
unsigned long mem_end;
void __iomem *base_addr;
};
struct simpmod_local *lp = NULL;
......
static int simpmod_probe(struct platform_device *pdev)
{ .....
lp->base_addr = ioremap(lp->mem_start,lp->mem_end - lp->mem_start + 1);
...
dev_info(dev,"simpmod at 0x%08x mapped to 0x%08x,irq=%d\n",(unsigned int __force)lp->mem_start,(unsigned int __force)lp->base_addr,lp->irq);
....
}
写回调函数(截至目前)只是将一个 32 位数字放入内存中。但是,即使我从 base_address+0x20_offset 读取,读取回调函数也只是读取完全相同的数字。我曾尝试更改偏移值,但无论如何,它始终读取相同的数字。
我的直觉告诉我,如果从不同的内存地址读取值应该是垃圾值或零,但它不太可能读取写入基地址的相同值。为什么要在整个分配的内存空间中复制写入的数据?
即使执行 devmem 命令
写回调函数如下所示:
static ssize_t dev_write(struct file *fil,const char *buf,size_t len,loff_t *off){
sscanf (buf,"%d,%d",&operand_1,&operand_2);
ker_buf[len] = 0 ;
iowrite32((unsigned int) operand_1,lp->base_addr);
return len;
}
上找到完整的项目代码(经过细微更改)解决方法
警告:这与其说是一个解决方案,不如说是一些观察和尝试[无特定顺序]。
目前,您有多种潜在的错误来源:硬件逻辑错误、设备驱动程序不正确。
从链接的驱动程序代码中,大多数 return
语句返回错误代码(例如 -ENOMEM
),但有些会返回 return -1
。这是不一致的。
正如我在评论中提到的,你有一堆全局变量。没有线程间锁定。因此,您可能会遇到竞争条件。
我猜你正在启动 petalinux
。而且,只要您不访问您的设备,它就可以正常工作。这是一个大交易[以一种好的方式]。
我假设您通过串行电缆从开发系统 [运行(例如)minicom
] 到板载 UART 与其通信。因此,您会收到登录提示和/或 shell。
这意味着 UART 驱动程序源 [和相应的 dtb/dts] 可用。您可以将其用作参考驱动程序。或者,诸如 GPIO 之类的其他东西。
我注意到您提到了 ZYNQ
[这是一种相当流行的 Xilinx FPGA 芯片]。我假设您还使用带有 ZYNQ
芯片的标准 SDK 板。因此,Vivado 已经了解电路板互连/布局。
而且,我假设 Vivado 能够将电路板定义传递给 Xilinx 的 S/W SDK/builder,以便它可以构建兼容的 petalinux
内核。
我从未见过写入值并正确读取它但在整个内存中复制该数据。
这意味着您设备中的地址匹配逻辑不仅响应其分配的地址范围,还响应更多不应该响应的地址。可能与其他设备重叠,它们可能会竞争/竞赛。
我不是 Vivado 专家,但是......
从您的链接中,查看 Vivado 窗口之一的 .png
,它说 AXI BASEADDR
是 0xFFFFFFFF
,AXI HIGHADDR
是 0x00000000
.两者都有一个蓝色的 i
。
这些对我来说非常可疑,因为我认为这些值应该与 DTB 条目中的值匹配。而且,BASEADDR
值对我来说毫无意义。
我想知道是否可以将 DTB 生成到某个合理的地址,但生成的实际硬件逻辑是不同的。
这很容易导致您看到的所有症状。
可能有帮助的一件事是将 chipscope
添加到硬件设计中,以便您可以调试硬件逻辑和/或观察对给定端口/地址范围的任何访问。
您正在使用 copy_to_user
等。阿尔。但是,这可能会失败并且您没有检查错误代码。我还会对传递的参数执行 printk
。
无法保证传递给 len
的 dev_read/dev_write
值足以包含传输大小。在 dev_read
中,您执行 ioread32
。但是,您需要这样做:int n = sprintf(ker_buf,"%d\n",read_val);
您没有检查 n
与 len
以确保有足够的空间。而且,您不是检查/尊重loff_t
这两个函数都传递了一个 struct file
指针。但是,该值会被忽略,以支持您已经设置的全局变量。正如我在最重要的评论中提到的那样,使用这些全局变量是有问题的。您应该使用传递的指针来查找适当的 struct
指针和 [最终] 您的私有设备结构 simpmod_local
。
您的 dev_write
应该将用户空间中的值存储到私有结构中。 dev_read
应该从那里获取它们。
这里有一个猜测:我见过的大多数设计都使用完整的 AXI
而不是 AXI
lite。我对“AXI 线程 ID”的构成一无所知,因此我不知道您的访问代码在内核之间弹跳的含义 [如果有的话]。
像您一样使用 dev_write/dev_read
不是原子性的。我认为,目前,你有更基本的问题。但是,从长远来看,我会将其替换为带有 ioctl
的 struct
调用,例如:
struct mymult_user {
u32 operand_1;
u32 operand_2;
u32 result;
};
ioctl
调用对此执行 copy_from_user
。将这些值发送到 H/W,返回结果。并且,将结果返回给 ioctl 调用者。或者,它可以对 copy_to_user
中的 result
字段执行 struct
。
总的来说,您更有可能在 Xilinx 的论坛页面上得到 [有用的] 回复[因为经常做这些事情的人经常光顾这里]。
更新:
我注意到的其他事情。
DTB 条目指定 AXI 数据宽度为 0x20。这是 32 字节!?它是自动生成的,所以它必须是正确的;-) 但是,这对我来说似乎太过分了。可能只是与 AXI 数据总线的宽度有关,所以,可能不是问题...
但是,从驱动程序来看,基地址的偏移量似乎不匹配。
operand_1
为偏移量 0x10,operand_2
为偏移量 0x20,结果为偏移量 0x30。那么,偏移量 0x0 处是什么???
AXI 总线的宽度和寄存器的宽度可能不严格相关。
一种查看方式是偏移量应与总线宽度对齐:0x0、0x20、0x40。
但是,通常情况下,我希望事情能够更紧密地打包。 (例如)分别偏移 0x0、0x2、0x4。
在调试时只是执行ioread*
可能会减少痛苦[减少内存/总线损坏的机会]。由于您没有写入地址空间,因此不太可能损坏其他内存单元,并且系统可能会更长时间地保持活动[未损坏]。这只会为您提供最初结果 reg 中的任何值。
此外,您可以编写操作数并在 ioread32
上循环以获取偏移量(例如)0x0-0x40 和 printk
这些值。