[英]Linux Kernel Device Driver Write Callback is writing data across the entire allocated memory space for the device driver
我有一个 Xilinx SoC,并通过 verilog 在可编程逻辑上创建了一个简单的乘法器。 乘法器接受两个 16 位输入,将它们相乘并返回一个 32 位 output。 数字设计已通过 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”)的物理memory地址为0xa0000000,地址宽度为0x4,数据宽度为32位。
因此,从设备驱动程序的角度来看,特别是在写回调 function 中,我正在将一个 32 位数字写入从“ioremap(.)”检索到的虚拟 memory 地址 ZC1C4245268E67A94D11
进行了完整性检查以查看虚拟 memory 映射到物理地址,并且它似乎正确完成,没有错误(驱动程序中的一些与内存相关的代码片段如下所示):
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);
....
}
写回调 function(截至目前)只是取一个 32 位数字并将其放入 memory。 但是,即使我从 base_address+0x20_offset 读取,读取回调 function 也只是读取完全相同的数字。 我尝试更改偏移值,但无论如何,它一直在读取相同的数字。
我的直觉告诉我,如果从不同的 memory 地址读取该值应该是垃圾值或零,但它不太可能读取写入基地址的相同值。 为什么要在整个分配的 memory 空间中复制写入的数据?
即使执行 devmem 命令 <devmem 0xa0000000 w 52> 也会在执行 <devmem 0xa0000020 w> 或 <devmem 0xa0000040 w> 或......
写回调 function 如下所示:
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;
}
完整的项目代码(稍作改动)可在https://forums.xilinx.com/t5/Embedded-Linux/Memory-Replications-during-write-call-back-function-in-Linux/mp/1212405上找到
警告:这与其说是一个解决方案,不如说是一些观察和尝试的事情[无特定顺序]。
目前,您有多个潜在的错误来源:硬件逻辑错误、设备驱动程序错误。
从链接的驱动程序代码中,大多数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
kernel。
我从未见过写入值并正确读取它,但在整个 memory 中复制了该数据。
这意味着您设备中的地址匹配逻辑不仅响应其分配的地址范围,而且响应更多不应该的地址。 可能与其他设备重叠,它们可能正在竞争/竞赛。
我不是Vivado 专家,但是...
从您的链接中,查看 Vivado windows 之一的.png
,它说AXI BASEADDR
是0xFFFFFFFF
而AXI HIGHADDR
是0x00000000
。 两者都有一个蓝色的i
。
这些对我来说非常可疑,因为我认为这些值应该与 DTB 条目中的值匹配。 而且, BASEADDR
值对我来说毫无意义。
我想知道是否可以将 DTB 生成到某个健全的地址,但生成的实际硬件逻辑是不同的。
这很容易导致您看到的所有症状。
可能有帮助的一件事是将chipscope
添加到硬件设计中,以便您可以调试硬件逻辑和/或观察对给定端口/地址范围的任何访问。
您正在使用copy_to_user
等。 人。 但是,这可能会失败,并且您没有检查错误代码。 我还会对正在传递的 arguments 进行printk
。
无法保证传递给dev_read/dev_write
的len
值足以包含传输大小。 在dev_read
中,您执行ioread32
。 但是,你这样做: int n = sprintf(ker_buf, "%d\n", read_val);
您没有检查n
与len
以确保有足够的空间。 而且,您不是在检查/尊重loff_t
这两个函数都传递了一个struct file
指针。 但是,这个值会被忽略,取而代之的是您已经设置的全局变量。 正如我在顶级评论中提到的那样,使用这些全局变量是有问题的。 您应该使用传递的指针来找到适当的struct
指针和 [最终] 您的私有设备 struct simpmod_local
。
您的dev_write
应该将来自用户空间的值存储到私有结构中。 dev_read
应该从那里得到它们。
这是一个总的猜测:我见过的大多数设计都使用完整的AXI
而不是AXI
lite。 我对“AXI 线程 ID”的构成一无所知,所以我不知道您的访问代码在内核之间弹跳的含义可能是什么 [如果有的话]。
像你一样使用dev_write/dev_read
不是原子的。 我认为,目前,你有更根本的问题。 但是,从长远来看,我会用一个带有struct
的ioctl
调用来替换它,例如:
struct mymult_user {
u32 operand_1;
u32 operand_2;
u32 result;
};
ioctl
调用对此进行了copy_from_user
。 将这些值发送到 H/W,取回结果。 并且,将结果返回给 ioctl 调用者。 或者,它可以在struct
的result
字段上执行copy_to_user
。
总体而言,您更有可能在 Xilinx 的论坛页面上获得 [有用的] 响应 [因为一直在做这些事情的人经常光顾它]。
更新:
我注意到了别的东西。
DTB 条目将 AXI 数据宽度指定为 0x20。 这是32字节?? 它是自动生成的,所以它必须是正确的;-) 但是,这对我来说似乎太过分了。 它可能只是与 AXI 数据总线的宽度有关,所以,也许不是问题......
但是,查看驱动程序,基地址的偏移量似乎不匹配。
operand_1
是偏移量0x10, operand_2
是偏移量0x20,结果是偏移量0x30。 那么,偏移量 0x0 是什么?
AXI 总线的宽度和寄存器的宽度可能没有严格的关系。
一种查看方式是偏移量应与总线宽度对齐:0x0、0x20、0x40。
但是,通常情况下,我希望事情会更加紧凑。 (例如)分别偏移 0x0、0x2、0x4。
在调试时只做ioread*
可能不会那么痛苦[减少内存/总线损坏的机会]。 由于您没有写入地址空间,因此它不太可能损坏其他 memory 单元,并且系统可能会更长时间地保持 [未损坏]。 这只会给你最初结果 reg 中的任何值。
此外,您可以在ioread32
上编写操作数和循环以获取偏移量(例如) printk
并打印这些值。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.