Linux 設備樹學習——基於i2c總線分析

博客說明

撰寫日期 2019.11.18
完稿日期 2019.11.20
最近維護 暫無
本文作者 multimicro
聯繫方式 [email protected]
資料鏈接 本文無附件資料
GitHub https://github.com/wifialan/drivers/tree/master/device_tree_i2c
原文鏈接 https://blog.csdn.net/multimicro/article/details/103129546

開發環境

環境說明 詳細信息 備註信息
操作系統 Ubunut 18.04
開發板 JZ2440-V3
u-boot uboot-2012.04.01
busybox busybox-1.22.1
u-boot和busybox編譯器 arm-linux-gcc (4.4.3)
Linux內核 linux-4.19-rc3
Linux內核編譯器 arm-linux-gnueabi-gcc (4.9.4)

1. 如何使用設備樹

設備樹,顧名思義,是描述物理設備的文件,裏面的節點包含特定設備下的硬件信息。使用設備樹,需要做兩方面工作:其一,開啓u-boot支持設備樹的功能;其二:使用支持設備樹的內核版本。

1.1 u-boot支持設備樹

  • 在u-boot工程中,增添一行命令在板級配置文件中:
    #define CONFIG_OF_LIBFDT
    我用的是S3C2440,所以應該在include/configs/smdk2440.h中增添上述命令行,然後重新編譯即可得到支持設備樹的u-boot。

  • 在u-boot啓動過程中,在把控制權交給內核前,會對內存進行分區,那麼因此,也需要讓u-boot啓動時,爲設備樹分配一定的內存空間,具體操作是:
    繼續修改include/configs/smdk2440.h文件,在裏面這個位置增加圖示代碼:

在這裏插入圖片描述
重新編譯,燒錄,在u-boot命令行中輸入mtdparts即可查看分區情況

在這裏插入圖片描述
這就在之前的u-boot基礎上得到了一個支持設備樹的u-boot,可以在對應的內存空間中燒寫相應的文件。

1.2 Linux內核支持設備樹

我使用的是韋東山提供的Linux內核以及補丁包,採用Linux-4.19.3-rc內核版本。

打補丁:
patch -p1<../linux-4.19-rc3_device_tree_for_jz2440.patch
編內核:
cp config_ok .config
make ARCH=arm CROSS_COMPILE=/opt/FriendlyARM/toolschain/4.9.4/bin/arm-linux-gnueabi- menuconfig
make ARCH=arm CROSS_COMPILE=/opt/FriendlyARM/toolschain/4.9.4/bin/arm-linux-gnueabi- uImage -j8
make ARCH=arm CROSS_COMPILE=/opt/FriendlyARM/toolschain/4.9.4/bin/arm-linux-gnueabi- dtbs -j8

執行完後,會在arch/arm/boot/文件夾下得到uImage,在arch/arm/boot/dts/文件夾下得到*.dtb

1.3 如何在開發板中使用設備樹

爲了減少學習時對開發板的FLASH擦寫,這裏一直使用tftp服務將內核和設備樹文件的直接傳遞給內存中運行,而u-boot和文件系統busybox則固化在FLASH中,從Linux-3.4.2過渡過來的busybox可以直接使用,但需要提醒一下,之前的busybox是在第三分區,而增加設備樹分區後的busybox被挪到了第四分區:
在這裏插入圖片描述
因此,在bootargs命令中,應該修改root掛載到/dev/mtdblock4分區下

set bootargs console=ttySAC0,115200 root=/dev/mtdblock4 rw init=/linuxrc

現在開始通過tftp服務加載內核和設備樹,tftp服務器搭建參考:在Linux系統下通過TFTP或NFS燒寫內核
u-boot命令行中輸入:

tftp 31000000 uImage; tftp 32000000 jz2440_irq.dtb; bootm 31000000 - 32000000

bootm 命令介紹,若ramdisk沒有,則用"-"號替代
bootm + uImage地址 + ramdisk地址 + 設備樹鏡像地址

在這裏插入圖片描述
可以看到,設備樹已經成功被uImage加載。

2. 設備樹介紹

  • Linux uses DT data for three major purposes:
    1) platform identification,
    2) runtime configuration, and
    3) device population.

  • 我用設備樹主要是方便其加載硬件設備信息,不需要在內核中增加比如spi_register_board_info這樣的設備註冊函數,通過重新編譯內核來實現device的註冊。現在在利用設備樹時,若增加某個設備,那麼只需要修改設備樹中的相關信息,那麼重新編譯設備樹讓內核重新加載,即可便捷的註冊某device。非設備樹註冊device可參考spi_device的註冊過程: 2.2 spi_device註冊

  • 在根文件系統中查看設備樹(有助於調試)
    a. /sys/firmware/fdt // 原始dtb文件
    hexdump -C /sys/firmware/fdt//查看dtb文件內容
    b. /sys/firmware/devicetree // 以目錄結構程現的dtb文件, 根節點對應base目錄, 每一個節點對應一個目錄, 每一個屬性對應一個文件
    c. /sys/devices/platform // 系統中所有的platform_device, 有來自設備樹的, 也有來有.c文件中註冊的
     對於來自設備樹的platform_device, 可以進入 /sys/devices/platform/<設備名>/of_node 查看它的設備樹屬性
    d. /proc/device-tree是鏈接文件, 指向 /sys/firmware/devicetree/base

2.1 設備樹中的設備驅動節點

  • sys/devices/platform/
    sys/devices/platform/文件夾下面可以看到通過設備樹註冊進來的根設備(子設備都在根設備文件夾內的of_node文件夾下):

在這裏插入圖片描述
進入其中一個文件夾,如54000000.i2c可以看到一個文件夾of_node,裏面包含了i2c節點下的子節點設備信息:

在這裏插入圖片描述
紅框中of_node被鏈接到了/sys/firmware/devicetree/base/i2c@54000000/文件夾下(由此也驗證,設備樹中的所有信息都在/sys/firmware/devicetree/base文件夾裏面),那麼該文件夾中信息有那些呢?

  • sys/firmware/devicetree/base

tree命令: 將tree源碼交叉編譯後的文件放到busybox下的/bin/文件夾下方可使用

在這裏插入圖片描述
設備樹中,i2c節點下的設備子節點信息如下:

在這裏插入圖片描述
通過上述兩張圖,可以看出eeprom設備節點被成功加載到了內核中去,其依附於i2c設備。現在想想,樹狀關係是不是有點雛形了,一個根設備(i2c)下面伸出了一個枝幹(eeprom)。

  • sys/bus/i2c/

設備樹註冊進來的總線設備,可以在sys/bus/文件夾下找到,比如i2c、spi總線等

sys/bus/i2c/目錄下面可以看到i2c總線下面的devicedriver信息

在這裏插入圖片描述
若內核中註冊了相應的i2c drivers,那麼通過名字匹配成功後(具體的匹配方式下面會寫),會調用相應的probe函數將driver和device連在一起

在這裏插入圖片描述

2.2 設備樹匹配流程

通過設備樹註冊進來的device,是如何匹配到對應的driver呢,本節仍然以i2c總線的匹配爲例進行介紹。

spi總線platform總線也是類似的,本節結尾我會貼出這兩個總線的匹配程序供參考,會發現,這玩意兒,就是一通百通。

2.2.1 以i2c匹配爲例

i2c中的匹配函數在如下文件內

drivers/i2c/i2c-core-base.c : line 101

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);
	struct i2c_driver	*driver;

	/* Attempt an OF style match */
	if (i2c_of_match_device(drv->of_match_table, client))
		return 1;

	/* Then ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);

	/* Finally an I2C match */
	if (i2c_match_id(driver->id_table, client))
		return 1;

	return 0;
}

從上述流程中可以得知,通過調用i2c_device_match函數實現i2c設備和驅動的匹配,匹配流程爲:

下面也是匹配的優先順序

  • 1st: /* Attempt an OF style match */

    設備樹中i2c子設備節點下的compatible屬性值內核驅動程序中所定義的of_device_id結構體中的compatible屬性值 是否完全相同,在linux-4.19-rc3內核版本中,採用of_compat_cmp函數(原型爲strcasecmp函數,由drivers/of/base.c :line454: __of_device_is_compatible調用)來匹配兩個值是否相同

  • 2nd: /* Then ACPI style match */

    設備樹不涉及這種匹配方式,忽略

  • 3rd: /* Finally an I2C match */

    設備樹不涉及這種匹配方式,但此種方式是在沒有采用設備樹時的匹配方式:platform_device結構體定義的.name 字段(至於怎麼傳入client先留個疑問)和 驅動程序中所定義的i2c_device_id結構體中的.name字段屬性值是否完全相同,在linux-4.19-rc3內核版本中,採用i2c_match_id函數(drivers/i2c/i2c-core-base.c :line86)來匹配兩個值是否相同


下面舉例介紹一下設備樹中驅動的匹配

  • /* Attempt an OF style match */

如下將at24_of_match定義爲了一個of_device_id結構體,裏面包含compatible屬性,和設備樹中子設備節點下的compatible屬性值進行匹配。倘若設備樹中子設備節點eeprom@50compatible = "atmel,24c256",那麼就會匹配成功,調用probe函數

driver端    內核程序

  static const struct of_device_id at24_of_match[] = {
	{ .compatible = "atmel,24c00",		.data = &at24_data_24c00 },
	{ .compatible = "atmel,24c01",		.data = &at24_data_24c01 },
	{ .compatible = "atmel,24cs01",		.data = &at24_data_24cs01 },
 		...	//此處省略了一部分
	{ .compatible = "atmel,24c256",		.data = &at24_data_24c256 },
	{ .compatible = "atmel,24c512",		.data = &at24_data_24c512 },
	{ .compatible = "atmel,24c1024",	.data = &at24_data_24c1024 },
	{ /* END OF LIST */ },
};

device端    設備樹程序

在這裏插入圖片描述


  • /* Finally an I2C match */

    對比通過內核註冊i2c device,匹配方式

driver端    內核程序

static const struct i2c_device_id at24_ids[] = {
	{ "24c00",	(kernel_ulong_t)&at24_data_24c00 },
	{ "24c01",	(kernel_ulong_t)&at24_data_24c01 },
	{ "24cs01",	(kernel_ulong_t)&at24_data_24cs01 },
		...	//此處省略了一部分
	{ "24c256",	(kernel_ulong_t)&at24_data_24c256 },
	{ "24c512",	(kernel_ulong_t)&at24_data_24c512 },
	{ "24c1024",	(kernel_ulong_t)&at24_data_24c1024 },
	{ "at24",	0 },
	{ /* END OF LIST */ }
};

device端    內核程序

static struct i2c_board_info __initdata da830_evm_i2c_devices[] = {
	{
		I2C_BOARD_INFO("24c256", 0x50),
		.platform_data	= &da830_evm_i2c_eeprom_info,
	},
};

  • spi總線platform總線的匹配程序
  1. spi總線  drivers/spi/spi.c
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
	const struct spi_device	*spi = to_spi_device(dev);
	const struct spi_driver	*sdrv = to_spi_driver(drv);

/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
	return 1;

/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
	return 1;

if (sdrv->id_table)
	return !!spi_match_id(sdrv->id_table, spi);

return strcmp(spi->modalias, drv->name) == 0;

}
  1. platform總線  drivers/base/platform.c
static int platform_match(struct device *dev, struct device_driver *drv)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *pdrv = to_platform_driver(drv);


	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	/* Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;

	/* Then try ACPI style match */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	/* Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;

	/* fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

韋東山在設備樹視頻裏面講解的匹配流程就是基於platform總線的,見下圖(單擊放大後觀看),而i2c總線spi總線的匹配優先級方式可以根據各自對應的源碼分析。現在再看這張圖,理解的會更上一層樓。i2c總線spi總線的匹配結構也可以仿照這張圖繪製出來
在這裏插入圖片描述

2.2.2 設備樹匹配下的程序模板

下面梳理一下i2c總線device註冊driver註冊以及匹配程序流程,spi總線platform總線流程也基本都是一個套路,學會一個,其他的就會很好理解了。

  • 設備樹端

    代碼在git上:jz2440_irq.dts

    在設備樹dts文件中的i2c總線節點下面,創建一個新的子設備節點(eeprom@50),需要注意的是,通過設備樹註冊的i2c設備會轉換爲一個i2c client,不像spi設備會轉換爲一個spi device

  1. compatible 的值是用於和driver匹配的
  2. reg 的值是相應的寄存器
  3. status = "okay"是啓用該設備

dts文件:

	&i2c {
    status = "okay";
    samsung,i2c-max-bus-freq = <200000>;
    eeprom@50 {
        compatible = "atmel,24c256";
        reg = <0x50>;
        pagesize = <32>;
        status = "okay";
    };  
};

下面是上述dts文件中所包含的dtsi文件裏面的i2c設備節點信息,之所以可以用&i2c,就是因爲在dtsi文件(見下)中將i2c@54000000這個i2c節點前記了一個標識符i2c,這種標識符的格式爲:i2c: i2c@54000000採用此標識符下的屬性信息可以覆蓋原有的屬性,並可以增加新的屬性信息在其中

dtsi文件

i2c: i2c@54000000 {
        compatible = "samsung,s3c2440-i2c";
        clocks = <&clocks PCLK_I2C>;
        clock-names = "i2c";
        pinctrl-names = "default";
        pinctrl-0 = <&i2c0_bus>;
    }; 
  • 內核driver端
static const struct of_device_id xxx_of_match[] = {
	{ .compatible = "atmel,24c256",		.data = &xxx_data },
	...		//還可以增加其他設備信息	.data字段暫時不知道作用,匹配是用不到這個屬性了
	{ /* END OF LIST */ },
};
MODULE_DEVICE_TABLE(of, xxx_of_match);
static int xxx_probe(struct i2c_client * client, const struct i2c_device_id *id)
{
	...	//在這裏可以將i2c設備註冊爲字符設備或者misc設備使用
}

static int xxx_remove(struct i2c_client *client)
{
    ...
}

static struct i2c_driver xxx_driver = {
    .driver     = {
        .name   = "at24c256",
        .owner  = THIS_MODULE,
        .of_match_table = xxx_of_match,	//設備樹註冊i2c device匹配方式選用
    },
    .probe      = xxx_probe,
    .remove     = xxx_remove,
    .id_table   = xxx_ids,		//內核註冊i2c device匹配方式選用
};

static int __init xxx_init(void)
{
    return i2c_add_driver(&xxx_driver);
}

static void __exit xxx_exit(void)
{
    i2c_del_driver(&xxx_driver);c
}

module_init(xxx_init);
module_exit(xxx_exit);

2.3 設備樹中的設備節點轉換

文本內容較多,移步至github: device_tree_node_transfer

i2c節點和spi節點是如何轉換爲相應的i2c clientspi device的呢?

/i2c節點一般表示i2c控制器, 它會被轉換爲platform_device, 在內核中有對應的platform_driver;
/i2c/at24c02節點不會被轉換爲platform_device, 它被如何處理完全由父節點的platform_driver決定, 一般是被創建爲一個i2c_client。

類似的也有/spi節點, 它一般也是用來表示SPI控制器, 它會被轉換爲platform_device, 在內核中有對應的platform_driver;
/spi/flash@0節點不會被轉換爲platform_device, 它被如何處理完全由父節點的platform_driver決定, 一般是被創建爲一個spi_device。

詳細流程參考github上文本內容

附錄

  1. GitHub:device_tree_i2c

參考資料

  1. 讓u-boot支持內核設備樹dts
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章