Linux 内核设备驱动程序写入回调正在为设备驱动程序在整个分配的内存空间中写入数据

问题描述

我有一个 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 命令 也会在执行 或 ......

时产生输出 52

写回调函数如下所示:

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/m-p/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 内核。

我从未见过写入值并正确读取它但在整个内存中复制该数据。

这意味着您设备中的地址匹配逻辑不仅响应其分配的地址范围,还响应更多不应该响应的地址。可能与其他设备重叠,它们可能会竞争/竞赛。

不是 Vivado 专家,但是......

从您的链接中,查看 Vivado 窗口之一的 .png,它说 AXI BASEADDR0xFFFFFFFFAXI HIGHADDR0x00000000 .两者都有一个蓝色的 i

这些对我来说非常可疑,因为我认为这些值应该与 DTB 条目中的值匹配。而且,BASEADDR 值对我来说毫无意义。

我想知道是否可以将 DTB 生成到某个合理的地址,但生成的实际硬件逻辑是不同的。

这很容易导致您看到的所有症状。

可能有帮助的一件事是将 chipscope 添加到硬件设计中,以便您可以调试硬件逻辑和/或观察对给定端口/地址范围的任何访问。

您正在使用 copy_to_user 等。阿尔。但是,这可能会失败并且您没有检查错误代码。我还会对传递的参数执行 printk

无法保证传递给 lendev_read/dev_write 值足以包含传输大小。在 dev_read 中,您执行 ioread32。但是,您需要这样做:int n = sprintf(ker_buf,"%d\n",read_val);没有检查 nlen 以确保有足够的空间。而且,您不是检查/尊重loff_t

这两个函数都传递了一个 struct file 指针。但是,该值会被忽略,以支持您已经设置的全局变量。正如我在最重要的评论中提到的那样,使用这些全局变量是有问题的。您应该使用传递的指针来查找适当的 struct 指针和 [最终] 您的私有设备结构 simpmod_local

您的 dev_write 应该将用户空间中的值存储到私有结构中。 dev_read 应该从那里获取它们。

这里有一个猜测:我见过的大多数设计都使用完整的 AXI 而不是 AXI lite。我对“AXI 线程 ID”的构成一无所知,因此我不知道您的访问代码在内核之间弹跳的含义 [如果有的话]。

像您一样使用 dev_write/dev_read 不是原子性的。我认为,目前,你有更基本的问题。但是,从长远来看,我会将其替换为带有 ioctlstruct 调用,例如:

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 这些值。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...