前面通过学习总线、设备、驱动模型知识后,知道了设备和驱动之间都是通过总线进行绑定而匹配的;然后通过设备树的深入探究,知道了设备树的出现大大增加了驱动的通用性;接着我们一起看了 Linux 的启动流程和设备在内核里一层一层的展开。
通过前面的课程学习,相信内核初学者对内核和驱动有了一个大概的感性认识,有内核经验的小伙伴相信也有了系统化的梳理。都说“实践是检验真理的唯一标准”,为了检验前面的理论性,更为了加深理解,这里我们手把手一起从设备树入手,模拟一个电路板,上面有中断控制器、GPIO 控制器、I2C 控制器、SPI 控制器、以太网控制器等,并根据这个电路板从头到尾构建一个 DTS 文件,使内核支持我们自己定制的开发板。
添加一个新的 SOC
我们假设 csdn 是一家芯片厂商,研发了一块叫做 gitchat 的芯片,对应的开发板叫做 evb。需要做什么工作才能定制一套开发板并且使 Linux 支持我们这块电路板呢?
首先我们在 arch/arm 目录下创建一个 mach-gitchat 的目录,然后创建 arch/arm/machgitchat/Kconfig,Makefile,common.c。
其中,Kconfig 里做了一个叫 ARCH_GITCHAT 的假芯片,common.c 是对应的驱动,Makefile 里对这个驱动做了编译。我们来简单的看一下这个驱动:
就像前几节课讲的那样,我们是通过 DT_MACHINE_START
来定义一个电路板的,里面有很多回调函数,这里简单的写了 init_late
和 dt_compat
,其中 dt_compat 会根据设备树里的字段进行匹配,如果字段一致就说明匹配正确,这里的字段是“csdn, gitchat”。
添加对应的 DTS
现在已经在 Linux 里添加了我们定义的 soc,接下来需要添加 soc 对应的设备树,即具体的板级文件信息。
在 arch/arm/boot/dts 下创建 csdn-gitchat.dtsi 和 csdn-gitchat-evb.dts。csdn-gitchat.dtsi 是对芯片的描述,csdn-gitchat-evb.dts 是针对这个芯片的 evb 板子描述,其内容分别如下:
/* * DTS file for demo platform for Dec20,2017 CSDN/Gitchat course * * Copyright (c) 2017 xxx * * Licensed under GPLv2. *// { compatible = "csdn,gitchat"; #address-cells = <1>; #size-cells = <1>; interrupt-parent = <&intc>; aliases { serial0 = &uart0; }; cpus { #address-cells = <1>; #size-cells = <0>; cpu@0 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <0x0>; }; cpu@1 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <0x1>; }; }; axi@40000000 { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; ranges = <0x40000000 0x40000000 0x80000000>; l2-cache-controller@80040000 { compatible = "arm,pl310-cache"; reg = <0x80040000 0x1000>; interrupts = <59>; arm,tag-latency = <1 1 1>; arm,data-latency = <1 1 1>; arm,filter-ranges = <0 0x40000000>; }; intc: interrupt-controller@80020000 { #interrupt-cells = <1>; interrupt-controller; compatible = "csdn,gitchat"; reg = <0x80020000 0x1000>; }; peri-iobg@b0000000 { compatible = "simple-bus"; #address-cells = <1>; #size-cells = <1>; ranges = <0x0 0xb0000000 0x180000>; ethernet@10000 { compatible = "davicom,dm9000"; reg = <0x10000 0x2 0x10004 0x2>; interrupts = <7>; davicom,no-eeprom; }; timer@20000 { compatible = "csdn,gitchat-tick"; reg = <0x20000 0x1000>; interrupts = <0>; clocks = <&clks 11>; }; clks: clock-controller@30000 { compatible = "csdn,gitchat-clkc"; reg = <0x30000 0x1000>; #clock-cells = <1>; }; gpio0: goio@40000 { #gpio-cells = <2>; #interrupt-cells = <2>; compatible = "csdn,gitchat-gpio"; reg = <0x40000 0x200>; interrupts = <43>; gpio-controller; interrupt-controller; }; uart0: uart@50000 { cell-index = <0>; compatible = "csdn,gitchat-uart"; reg = <0x50000 0x1000>; interrupts = <17>; status = "disabled"; }; spi0: spi@d0000 { cell-index = <0>; compatible = "csdn,gitchat-spi"; reg = <0xd0000 0x10000>; interrupts = <15>; #address-cells = <1>; #size-cells = <0>; status = "okay"; }; i2c0: i2c@e0000 { cell-index = <0>; compatible = "csdn,gitchat-i2c"; reg = <0xe0000 0x10000>; interrupts = <24>; #address-cells = <1>; #size-cells = <0>; status = "okay"; }; }; };};
/dts-v1/;/include/ "csdn-gitchat.dtsi"/ { model = "Csdn Gitchat EVB Board"; compatible = "csdn,gitchat-evb", "csdn,gitchat"; memory { device_type = "memory"; reg = <0x00000000 0x20000000>; }; chosen { bootargs = "console=ttyS0,115200 root=/dev/mtdblock5 rw rootfstype=ubifs"; }; axi@40000000 { peri-iobg@b0000000 { spi@d0000 { status = "disabled"; }; i2c@e0000 { pixcir_ts@5c { compatible = "pixcir,pixcir_tangoc"; reg = <0x5c>; interrupt-parent = <&gpio0>; interrupts = <17 0>; attb-gpio = <&gpio0 22 0>; touchscreen-size-x = <1024>; touchscreen-size-y = <600>; }; }; }; };};&uart0 { status = "okay";};
由于篇幅限制,这里我们只写了部分代码。
DTS 的编译
在 arch/arm/boot/dts/Makefile 里添加我们的板子:
刚刚平台目录下我们已经对 MACH_GITCHAT 进行了定义,这里就会把设备树编译成 dtb 文件。我们也可以通过命令手动来编译生成 csdn-gitchat-evb.dtb 文件:
$ dtc -I dts -O dtb csdn-gitchat-evb.dts -o csdn-gitchat-evb.dtb
至此,一个自己定制的开发板就已经搞定,这里虚拟了一个开发板,如果定制一个真实的电路板的话需要根据芯片手册把设备树里的信息完善起来。但是通过这节课的课程完全可以理解从零是如何定制一个开发板的。
关于内核的知识我们先到这里,在接下来的课程里会和大家一起学习项目开发中经常遇到的问题——调试技巧。
【推荐阅读】
Linux 总线、设备、驱动模型的探究
Linux 设备树(DTS)的深入理解
Linux 设备和驱动的相遇
添加极客助手微信,加入技术交流群
长按,扫码,关注公众号