设备树相关

chosen节点也位于根节点下,该节点用来给内核传递参数(不代表实际硬件)。对于Linux内核,该节点下最有用的属性是bootargs,该属性的类型是字符串,用来向Linux内核传递cmdline。

memory和chosen节点在内核初始化的代码都位于start_kernel()->setup_arch()->setup_machine_fdt()->early_init_dt_scan_nodes()函数中(位于drivers/of/fdt.c),复制代码如下(本节所有代码都来自官方内核4.4-rc7版本):

1078 void __init early_init_dt_scan_nodes(void)

1079 {      

1080     /* Retrieve various information from the /chosen node */

1081     of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

1082

1083     /* Initialize {size,address}-cells info */

1084     of_scan_flat_dt(early_init_dt_scan_root, NULL);

1085

1086     /* Setup memory, calling early_init_dt_add_memory_arch */

1087     of_scan_flat_dt(early_init_dt_scan_memory, NULL);

1088 }

of_scan_flat_dt函数扫描整个设备树,实际的动作是在回调函数中完成的。第1081行是对chosen节点操作,该行代码的作用是将节点下的bootargs属性的字符串拷贝到boot_command_line指向的内存中。boot_command_line是内核的一个全局变量,在内核的多处都会用到。第1084行是根据根节点的#address-cells属性和#size-cells属性初始化全局变量dt_root_size_cells和dt_root_addr_cells,还记得前边说过如果没有设置属性的话就用默认值,这些都在early_init_dt_scan_root函数中实现。

驱动自动加载
节点中的第一个赋值语句compatible = “xlnx,xillybus-1.00.a”是最重要的一句:它连接硬件和驱动。当内核在总线上扫描设备时(设备节点在设备树里挂在一个总线节点下),内核检索"compatible"字段,然后将其字符串与一些已知的字符串比较。这个过程会在启动时自动发生两次:
. 内核启动时,编译进内核的驱动与设备树中某个"compatible"条目匹配
. 之后加载内核模块时,再触发一次匹配操作

内核驱动和"compatible"条目的连接由驱动代码中的一小段完成:
static struct of_device_id xillybus_of_match[] __devinitdata = {
{ .compatible = "xlnx,xillybus-1.00.a", },
{}
};

MODULE_DEVICE_TABLE(of,xillybus_of_match);

这段代码使得驱动与某一个"compatible"条目匹配。注意上面的id表中有一个空结构,用这个空意绪标志id表的结束。

在上段代码之后,一定有类似如下的一段代码:
static struct platform_driver xillybus_platform_driver = {
.probe = xilly_drv_probe,
.remove = xilly_drv_remove,
.driver = {
.name = "xillybus",
.owner = THIS_MODULE,
.of_match_table = xillybus_of_match,
},
};

platform_driver_register(&xillybus_platform_driver)在模块初始化里被调用。这个结构告诉内核,当驱动与某个硬件匹配时,xilly_drv_probe 被调用。

对内核来说,"compatible"字串需要与某个驱动名相同。”xlnx"前缀用于防止名字冲突。

另外,一个设备可以有多个"compatible"。因为一个设备可以有多个模块对应多个驱动。

可能会需要匹配硬件的名字和类型,但这不常用。

写内核模块时需要特别注意,自动加载机制依赖于/lib/modules/{kernel version}/modules.ofmap文件中的"compatible"字串,其他定义文件也在这个目录下。正确的方式是把*.ko文件复制到/lib/modules/{kernelversion}/kernel/drivers/下的相关目录中,然后:
depmod -a

获取资源信息

内核模块驱动加载之后,就开始把硬件资源管理起来,如读写寄存器、接收中断。

来看看设备树里的一条:
xillybus_0: xillybus@50000000 {
compatible = "xlnx,xillybus-1.00.a";
reg = < 0x50000000 0x1000 >;
interrupts = < 0 59 1 >;
interrupt-parent = <&gic>;

xlnx,max-burst-len =<0x10>;
xlnx,native-data-width = <0x20>;
xlnx,slv-awidth = <0x20>;
xlnx,slv-dwidth = <0x20>;
xlnx,use-wstrb = <0x1>;
} ;

驱动一般在探测函数里就取得了硬件内存段的所有权(探测函数就是probe指针指向的函数)。

来看看一个典型探测函数的框架:
static int __devinit xilly_drv_probe(struct platform_device *op)
{
const struct of_device_id *match;

match =of_match_device(xillybus_of_match, &op->dev);

if (!match)
return -EINVAL;

第一个操作就是检查probe是否作用在相关硬件上。

访问寄存器

下一步,分配一段内存并映射到虚拟内存中。
int rc = 0;
struct resource res;
void *registers;

rc = of_address_to_resource(&op->dev.of_node,0, &res);
if (rc) {
/* Fail */
}

if(!request_mem_region(res.start, resource_size(&res), "xillybus")){
/* Fail */
}

registers =of_iomap(op->dev.of_node, 0);

if (!registers) {
/* Fail */
}

of_address_to_resource() 在设备树中找到第一个"reg",并将解析到的信息填充在"res"结构体里。这个例子里"reg = <0x50000000 0x1000 >”, 指的是分配一块起始物理地址是0x50000000,长度为0x1000字节的空间。of_address_to_resource()会设置res.start =0x50000000, res.end = 0x50000fff。

调用request_mem_region()是为了注册特殊的内存段。目的是避免两个驱动访问同一段寄存器空间而造成的冲突。resource_size()是个内联函数,返回segment的大小(此处是0x1000)。

of_iomap()函数是of_address_to_resource()和ioremap()的组合,本质上等效于ioremap(re.start, resource_size(&res)).确保物理段已经映射到虚拟内存中,函数返回内存段的虚拟地址空间起始地址。

显然,当模块卸载或某个错误发生时,这些操作都需要有恢复动作。

访问硬件寄存器请使用iowrite32(),ioread32()以及其他的函数和宏,而不要直接使用上面的"register"指针。

中断处理

这部分的驱动很简单,类似如下:
irq = irq_of_parse_and_map(op->dev.of_node, 0);

rc = request_irq(irq,xillybus_isr, 0, "xillybus", op->dev);

irq_of_parse_and_map()在设备树里查找中断的描述项,然后返回中断号,request_irq()将使用这个中断号来注册。第二个参数是0,表示使用设备树中的第一个中断。

设备树里面描述是:
interrupts = < 0 59 1 >;
interrupt-parent = <&gic>;

那么使用了这三个数据中的哪一个呢?

第一个0是一个标志,用于指示中断是否是SPI(共享中断,shared peripheral interrupt)。非0值表示它是SPI。事实上在Zynq硬件上,这些中断都是共享的,这里是为了方便才写0, 软件上认为它不共享。

第二个数据表示中断号。

第三个数字是中断类型,可以有如下值:

0 - 内核不改变它,开机或uboot设置它是什么样就什么样。
1 - 上升沿触发
4 - 电平触发,高电平表示来中断。
不允许有其他值,下降沿触发和低电平中断目前不支持,因为硬件不支持那些模式。如果需要这样的触发方式,就得在硬件上加一个非门。

值得注意的是第三个数字在设备树里通常都是0, 所以Linux内核不去改变中断模式。这通常意味着高电平触发。这也让驱动依赖于bootloader里的设置。

interrupt-parent 这一句,必须指向中断控制器&gic。如果反编译一个DTB文件,这里的&gic会被一个数字代替,通常是0x1。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值