繁体   English   中英

Linux Kernel 设备驱动写入回调是在整个分配给设备驱动的 memory 空间中写入数据

[英]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 BASEADDR0xFFFFFFFFAXI HIGHADDR0x00000000 两者都有一个蓝色的i

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

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

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

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

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

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

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

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

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

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

struct mymult_user {
    u32 operand_1;
    u32 operand_2;
    u32 result;
};

ioctl调用对此进行了copy_from_user 将这些值发送到 H/W,取回结果。 并且,将结果返回给 ioctl 调用者。 或者,它可以在structresult字段上执行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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM