DTS实验

发布时间 2023-10-07 17:10:30作者: CodeTrap

DTS实验

qemu的dumpdtb参数可以解析出qemu virt设备平台使用的默认dtb配置。

qemu-system-riscv64 -M virt,dumpdtb=qemu.dtb

成功解析出目标dtb文件,但此文件无法直接进行修改,必须将其修改为dts文件。

dtc -I dtb -O dts qemu.dtb -o qemu.dts

(dtc为设备树的编译工具,可以做dts文件与dtb文件的相互转化。该工具可以在发行版下直接安装device-tree-compiler,或者在Linux源码scripts\dtc下:

编译过内核源码,此目录下会有dtc的二进制文件)

解析出的dts文件内容(部分):

/dts-v1/;

/ {
	#address-cells = <0x02>;
	#size-cells = <0x02>;
	compatible = "riscv-virtio";
	model = "riscv-virtio,qemu";

	fw-cfg@10100000 {
		dma-coherent;
		reg = <0x00 0x10100000 0x00 0x18>;
		compatible = "qemu,fw-cfg-mmio";
	};

	flash@20000000 {
		bank-width = <0x04>;
		reg = <0x00 0x20000000 0x00 0x2000000 0x00 0x22000000 0x00 0x2000000>;
		compatible = "cfi-flash";
	};

	chosen {
		rng-seed = <0xc7e0aad9 0x9eef51cb 0xbdae5ad1 0x2e4ef955 0x6fb40bee 0xb0a597a1 0x60790b61 0x5ac3b84b>;
		stdout-path = "/soc/serial@10000000";
	};

	poweroff {
		value = <0x5555>;
		offset = <0x00>;
		regmap = <0x04>;
		compatible = "syscon-poweroff";
	};

dts文件用于描述设备的属性(其中自然包括了设备的起始地址以及使用地址的大小),涉及到地址的部分使用u32进行描述。

而#address-cells和#size-cells分别表示设备的起始地址以及使用地址的大小用几个u32进行表示。

每一个{}包括的地方都是一个所谓的设备节点,其中用=来表示设备节点的各个属性以及属性值(相当于map的key和value)。

使用fdtdump工具可以看到fdt文件的内容:

fdtdump -sd qemu.dtb > qemu.txt
/dts-v1/;
// magic:		0xd00dfeed
// totalsize:		0x10de (4318)
// off_dt_struct:	0x38
// off_dt_strings:	0xf2c
// off_mem_rsvmap:	0x28
// version:		17
// last_comp_version:	16
// boot_cpuid_phys:	0x0
// size_dt_strings:	0x1b2
// size_dt_struct:	0xef4

// 0038: tag: 0x00000001 (FDT_BEGIN_NODE)
/ {
// 0040: tag: 0x00000003 (FDT_PROP)
// 0f49: string: #address-cells
// 004c: value
    #address-cells = <0x00000002>;
// 0050: tag: 0x00000003 (FDT_PROP)
// 0f3d: string: #size-cells
// 005c: value
    #size-cells = <0x00000002>;
// 0060: tag: 0x00000003 (FDT_PROP)
// 0f32: string: compatible
// 006c: value
    compatible = "riscv-virtio";
// 007c: tag: 0x00000003 (FDT_PROP)
// 0f2c: string: model
// 0088: value
    model = "riscv-virtio,qemu";
// 009c: tag: 0x00000001 (FDT_BEGIN_NODE)
    fw-cfg@10100000 {
// 00b0: tag: 0x00000003 (FDT_PROP)
// 1050: string: dma-coherent
// 00bc: value
        dma-coherent;
// 00bc: tag: 0x00000003 (FDT_PROP)
// 0fb8: string: reg
// 00c8: value
        reg = <0x00000000 0x10100000 0x00000000 0x00000018>;
// 00d8: tag: 0x00000003 (FDT_PROP)
// 0f32: string: compatible
// 00e4: value
        compatible = "qemu,fw-cfg-mmio";
// 00f8: tag: 0x00000002 (FDT_END_NODE)
    };

dtb文件的最前面是一些文件本身的信息。

在每个设备节点之前会有一个tag:FDT_BEGIN_NODE,而在设备节点之后会有一个tag:FDT_END_NODE。

而设备节点的每个属性之前,会有一个tag:FDT_PROP,之后依次是string和value(即map的key和value)。

观察string的值,可以发现,string的值在开头标明的off_dt_strings之后,这是dtc编译时,将所有的string放置在一块区域,而对应string的key只用表明其在文件的所属位置即可。

接下来在dts文件中添加自己的设备节点。

在linux源码的arch/riscv/boot/dts目录下新建一个dts_demo目录,将qemu.dts复制到此为qemu.dtsi文件(dtsi文件相当于C语言中的.h文件。)

之后,新建一个dts_demo.dts:

#include "qemu.dtsi"

/ {
	dts-demo {
		compatible = "dts-demo, zyz test";
		status = "okay";
		a-string-property = "xxxx xxxx";
		sub-dts-demo {
			a-string-list-property = "yyyy yyyy";
		};
	};
};

这里新建了一个dts-demo设备节点,设置了它的一些属性,并在其中包含另一个子设备节点,也设置了它的属性。

之后,在当前目录下设置其Makefile:

# SPDX-License-Identifier: GPL-2.0
dtb-$(CONFIG_SOC_DTS_DEMO) += dts_demo.dtb
obj-$(CONFIG_BUILTIN_DTB) += $(addsuffix .o, $(dtb-y))

在上一级目录(/arch/riscv/boot/dts)修改Makefile为:

# SPDX-License-Identifier: GPL-2.0
subdir-y += sifive
subdir-$(CONFIG_SOC_CANAAN_K210_DTB_BUILTIN) += canaan
subdir-y += microchip
subdir-y += dts_demo

obj-$(CONFIG_BUILTIN_DTB) := $(addsuffix /, $(subdir-y))

修改arch/riscv目录下的Kconfig.socs文件,增加如下内容:

config SOC_DTS_DEMO
	bool "Dts Demo"
	help
	  This demo for increase dts file in arch

之后make ARCH=riscv CROSS_COMPILE=riscv64-linux- menuconfig时,可以看到以下编译选项:

直接选定后编译。

在arch/riscv/boot/dts/dts_demo下生成了对应的dtb文件:

将dtb文件拷贝到工作区,在qemu的启动参数中增加-dtb选项dtb dts_demo.dtb。

启动虚拟机,在/proc目录下可以看到device-tree目录:

进入该目录下可以通过文件读取到设备节点的信息。

那么,如何在驱动代码中使驱动与设备对应呢?这里可以使用platform的框架。

dts_test.c:

#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/of.h>

#define DTS_DEMO_DRIVER		"dts_demo_driver"

static int dts_demo_probe(struct platform_device *pdev)
{
	printk("enter %s \n", __func__);
    printk("node name: %s, node full name: %s\n", pdev->dev.of_node->name, pdev->dev.of_node->full_name);
    dump_stack();

	return 0;
}

static int dts_demo_remove(struct platform_device *pdev)
{
	printk("exit %s \n", __func__);
    dump_stack();
	return 0;
}

static const struct of_device_id dts_demo_of_match_table[] = {
	{ .compatible = "dts-demo, zyz test", },
	{ },
};

MODULE_DEVICE_TABLE(of, dts_demo_of_match_table);

static struct platform_driver dts_demo_driver = {
	.probe = dts_demo_probe,
	.remove = dts_demo_remove,
	.driver = {
		.name = DTS_DEMO_DRIVER,
		.owner = THIS_MODULE,
	    .of_match_table = dts_demo_of_match_table,
	},
};

module_platform_driver(dts_demo_driver);
MODULE_LICENSE("GPL v2");

这里主要是静态化定义了一个struct platform_driver结构体,其中关键的是of_match_table定义了匹配的字符串。

对应交叉编译的Makefile:

obj-m+=dts_test.o

CORSS_COMPILE:=riscv64-linux-

ARCH:=riscv

PWD:=$(pwd)

KDIR:=/home/zyz/Code/riscv64-linux/linux

all:
	make ARCH=$(ARCH) CORSS_COMPILE=$(CORSS_COMPILE) -C $(KDIR) M=$(PWD) modules
clean:
	make -C $(KDIR) M=$(PWD) clean

编译时出错:

不熟悉riscv的交叉编译这里的出错原因是什么,哥们儿直接放进内核里编译。

在drivers下新建dts_test目录,拷贝dts_test.c至该目录下,并增加Makefile:

# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DTS_TEST)	+= dts_test.o

以及Kconfig:

# SPDX-License-Identifier: GPL-2.0-only
menuconfig DTS_TEST
	tristate "Test DTS Function"
    depends on OF
	help
      This test is a LKM for test dts function

修改drivers目录中的Makefile,在最后增加一句:

obj-$(CONFIG_DTS_TEST) += dts_test/

以及在Kconfig中最后增加一句:

source "drivers/dts_test/Kconfig"

至此,重新make ARCH=riscv CROSS_COMPILE=riscv64-linux- menuconfig,选择编译选项:

这里勾选为按模块编译。

编译后,drivers/dts_test目录下生成dts_test.ko文件。

将该dts_test.ko文件拷贝到虚拟机和主机的共享区域,之后insmod该内核模块。

这里打印调用栈,是因为我在dts_demo_probe函数中增加了dump_stack函数。