【问题标题】:Linux Kernel Device Driver Write Callback is writing data across the entire allocated memory space for the device driverLinux Kernel Device Driver Write Callback 正在为设备驱动程序分配的整个内存空间写入数据
【发布时间】:2021-03-03 20:46:33
【问题描述】:

我有一个 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位。

因此,从设备驱动程序的角度来看,特别是在 write 回调函数中,我正在将一个 32 位数字写入从“ioremap(.)”函数检索的虚拟内存地址中。

已进行完整性检查以查看虚拟内存映射到物理地址,并且似乎正确完成且没有错误(驱动程序中的一些与内存相关的代码 sn-ps 如下所示):

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 ......

write-callback 函数如下所示:

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找到

【问题讨论】:

  • 所以读取回调是您实际观察到令人惊讶的结果的地方?你有没有考虑到错误可能存在?
  • 你为什么使用sscanf [inside a driver] 来获取端口地址? sscanf 是 slow 并且永远不会以您使用它的方式在驱动程序中使用。 operand_2 [你不用于任何事情]是什么?添加printk 以显示所有相关值[写之前]。许多需要用户空间写入端口的驱动程序可能不会将其映射到设备write 回调,但会创建一个[自定义] ioctl 和您设计的struct。拥有一个全球性的lp 是坏事。 lp 应该来自 platform_device 中的每设备私有指针
  • @JohnBollinger 你好约翰,读取回调(截至目前)基本上只是一个 ioread32((unsigned int __force)lp->base_addr+0x20);我已经测试了一段时间,所以现在有点乱。但基本上,我的问题是,无论偏移量是多少(当前为 0x20),读取值都是相同的。从命令行运行 devmem 时会发生类似的问题。
  • 您好@CraigEstey,感谢您的指点。你认为这就是问题所在吗(请记住 devmem 会导致同样的奇怪问题)。
  • 我会尝试为他们提供的标准设备找到 Xilinx 提供的设备驱动程序,以用作 DTB/DTC 条目的参考设计以及如何使用它。 [此处] 缺少扫描 DTB 并提取数据的代码。我会添加一些诊断代码[从probe 或open 调用] 来验证简单的操作:2*2 == 4、17 * 37 == 629 等带有硬连线调用 [vs.等待来自用户空间的数据]。您甚至可以让它转储周围的记忆词以寻找 [已知] 结果。乘数是标准宏 [由 Xilinx 提供] 还是您自己的定制设计?

标签: c linux linux-kernel driver xilinx


【解决方案1】:

警告:这与其说是一个解决方案,不如说是一些观察和尝试的事情[无特定顺序]。

目前,您有多个潜在的错误来源:硬件逻辑错误、设备驱动程序不正确。

从链接的驱动程序代码中,大多数 return 语句返回错误代码(例如 -ENOMEM),但有些会返回 return -1。这是不一致的。

正如我在我的 cmets 中提到的,你有一堆全局变量。没有线程间锁定。所以,你可能会有竞争条件。

我猜你正在启动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。

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

这两个函数都传递了一个struct file 指针。但是,这个值会被忽略,取而代之的是您已经设置的全局变量。正如我在顶级 cmets 中提到的,使用这些全局变量是有问题的。您应该使用传递的指针找到合适的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* 可能会减轻痛苦[减少内存/总线损坏的可能性]。由于您没有写入地址空间,因此它不太可能破坏其他内存单元,并且系统可能会更长时间地保持[未损坏]。这只会给你最初结果 reg 中的任何值。

此外,您可以在 ioread32 上编写操作数并循环以获取偏移量(例如)0x0-0x40 和 printk 这些值。

【讨论】:

  • 感谢克雷格的帮助。我现在将浏览这些 cmets 并将它们应用到项目中!非常感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-07-29
  • 1970-01-01
  • 2012-10-16
  • 2011-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多