如何从Linux驱动程序中访问和调试FDT/DTS设备树(段错误)

11
我编写了一个Linux内核模块,作为一款基于Freescale P2020RDB的自定义板子的FPGA驱动程序。该驱动程序包含几个#define用于指定不同的地址、大小、总线宽度等信息。我想从驱动程序中访问板子的Flattened Device Tree (FDT)以配置这些地址,以便该驱动程序可用于其他板子,其中FPGA具有不同的大小或位于不同的地址。
我已经在我的模块初始化函数中添加了以下简单代码,我在Linux内核源代码树上找到了这段代码:
...
#include <linux/of_device.h>
#include <linux/of_platform.h>

static int __init fpga_init(void) {

  struct device_node *dt_node;
  const u8 *property;
  int len;

  printk(KERN_INFO "(I) FPGA module loaded at 0x%p\n", fpga_init);
  dt_node = of_find_node_by_path("/fpga_dt@c0000000");
  if (!dt_node) {
    printk(KERN_ERR "(E) Failed to find device-tree node: /fpga_dt@c0000000\n");
    return -ENODEV;
  }
  printk(KERN_INFO "(I) Found device-tree node.  Now retrieving property.\n");

  property = of_get_property(dt_node, "reg", &len);

  printk(KERN_INFO "(I) reg=0x%08lX\n", (unsigned long) property[0]);
  ...
  return 0;
}

很不幸,插入模块时仅尝试查找设备节点就会导致分段错误。

# insmod fpga_drv.ko 
(I) FPGA module loaded at 0xe112d000
Unable to handle kernel paging request for data at address 0x00000000
Faulting instruction address: 0xe112d07c
Oops: Kernel access of bad area, sig: 11 [#1]
SMP NR_CPUS=2 P2020 RDB
Modules linked in: fpga_drv(P+)
NIP: e112d07c LR: e112d078 CTR: c03ed6a4
REGS: df043e10 TRAP: 0300   Tainted: P            (2.6.32.13)
MSR: 00029000 <EE,ME,CE>  CR: 24000222  XER: 20000000
DEAR: 00000000, ESR: 00000000
TASK = dfb85300[1167] 'insmod' THREAD: df042000 CPU: 1
GPR00: e112d078 df043ec0 dfb85300 00000000 e11761f4 c05838c4 00000000 dfffc650 
GPR08: 00000020 00000000 00000012 c03ed6a4 24000282 10098374 1ff92100 10081fc8 
GPR16: 1007a3e0 1007a434 00000000 00000002 00000000 00000000 bfbe6364 4801f468 
GPR24: 10094009 1007ca88 c064d07c 00000000 e112d000 c0690000 e1170000 e1170000 
NIP [e112d07c] fpga_init+0x7c/0x460 [fpga_drv]
LR [e112d078] fpga_init+0x78/0x460 [fpga_drv]
Call Trace:
[df043ec0] [e112d078] fpga_init+0x78/0x460 [fpga_drv] (unreliable)
[df043ef0] [c0001d94] do_one_initcall+0x3c/0x1e8
[df043f20] [c0077720] sys_init_module+0xf8/0x220
[df043f40] [c0010644] ret_from_syscall+0x0/0x3c
Instruction dump:
3860ffed 80010034 bb410018 38210030 7c0803a6 4e800020 3c80e117 38a10008 
388461f4 3fe0e117 4800038d 3fc0e117 <80830000> 3c60e117 386361f8 4cc63182 
---[ end trace 40317dd8a9588d98 ]---
Segmentation fault
这表示什么?有没有办法验证设备树 blob 已经被正确加载并可用?我需要更多的“设置”代码来准备这样的查询吗?还是说我正在尝试使用螺丝刀当作锤子?谢谢!顺便说一下,这是我的 FDT(DTS)源代码:
/dts-v1/;
/ {
    model = "fsl,P2020";
    compatible = "fsl,P2020RDB";
    #address-cells = <2>;
    #size-cells = <2>;
    ...
    fpga_dt@c0000000 {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "xilinx,xc6vlx240t", "virtex6";
        model = "xilinx,XC6VLX240T";
        reg = <0xc0000000 1 0xc8000000 0x08000000>;
        label = "Xilinx FPGA XC6VLX240T for My Custom Board";
    };
};

你能展示一下 fpga_init 的反汇编吗? - ninjalj
2个回答

12

我将具体回答这个问题:

有没有一种方法可以验证设备树blob已正确加载并可用?

有两种验证FDT是否正确的方法。

首先,在u-boot中,您可以转储FDT。
例如,如果您的FDT驻留在NV存储器中,请先将其复制到RAM。

cp.b 0xFFF70000 0x800000 0x200


设置fdt,然后转储fdt树(如u-boot所看到的)

fdt addr 800000
fdt print


这应该有效,因为你的节点是静态的。此时,您很容易看到设备树是否不正确。

第二个在内核中,但您需要重新编译并启用调试!

您必须启用CONFIG_DEBUG_FS=y并在arch/powerpc/kernel/prom.c中定义#define DEBUG。 这将导出设备树在/proc中 :-)

第三种方法是倾倒设备树,因为内核在引导期间非常早就解析它。确切的方法我现在想不起来了。 我非常确定您需要重新编译内核并添加一个bootarg。

祝好运。


谢谢,sessyargc.jp! 我能够使用您的建议在u-boot中验证FDT是否良好。(我不得不稍微修改它,因为我从TFTP服务器拉取FDT,但这只是一个小差异。) 我还按照您的建议在/proc/device-tree中找到了设备树,这使我能够验证内核已正确加载该树。 - Trevor
感谢 @sessyargc.jp,它帮了我很大的忙。 - Amit Singh Tomar

4

sessyargc.jp的回答已经足够了。但是,为了完整起见,我想提供一些C代码,用于从驱动程序内部打印出一些基本的树形信息:

#include <linux/of_device.h>
#include <linux/of_platform.h>

...
print_device_tree_node(of_find_node_by_path("/"), 0);
...

static void print_device_tree_node(struct device_node *node, int depth) {
  int i = 0;
  struct device_node *child;
  struct property    *properties;
  char                indent[255] = "";

  for(i = 0; i < depth * 3; i++) {
    indent[i] = ' ';
  }
  indent[i] = '\0';
  ++depth;

  for_each_child_of_node(node, child) {
    printk(KERN_INFO "%s{ name = %s\n", indent, child->name);
    printk(KERN_INFO "%s  type = %s\n", indent, child->type);
    for (properties = child->properties; properties != NULL; properties = properties->next) {
      printk(KERN_INFO "%s  %s (%d)\n", indent, properties->name, properties->length);
    }
    print_device_tree_node(child, depth);
    printk(KERN_INFO "%s}\n", indent);
  }
}

我希望知道如何确定每个属性的类型,这样我就可以正确地格式化值并输出它。 有什么建议吗?

最后,这是稍微修改过的原始片段:

char *path = "/fpga_dt@c0000000";
struct device_node *dt_node;
const u32 *property;
int len;

dt_node = of_find_node_by_path(path);
if (!dt_node) {
  printk(KERN_ERR "(E) Failed to find device-tree node: %s\n", path);
  return -ENODEV;
}
printk(KERN_INFO "(I) Found device-tree node.  Now retrieving property.\n");

property = of_get_property(dt_node, "reg", &len);

printk(KERN_INFO "(I) len=%d\n", len);
printk(KERN_INFO "(I) reg[0]=0x%08lX\n", (unsigned long) property[0]);
printk(KERN_INFO "(I) reg[1]=0x%08lX\n", (unsigned long) property[1]);
printk(KERN_INFO "(I) reg[2]=0x%08lX\n", (unsigned long) property[2]);
printk(KERN_INFO "(I) reg[3]=0x%08lX\n", (unsigned long) property[3]);

一些错误的设备树路径导致了段错误。显然,存在拼写错误。最终,我通过探测根路径以及其他基本节点(如/cpu0, /memory等)来解决问题,最终成功探测到我的 FPGA。我不确定真正发生了什么变化,但现在使用上述代码时,我能够正确地引用我的 FPGA 的设备树节点。

感谢所有的帮助!:)


你也可以从/proc/device-tree/中获取路径,并将其传递给of_find_node_by_path()函数。 - SD.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接