有了Device Tree后,大量的板级信息都不再需要,譬如过去经常在arch/arm/plat-xxx和arch/arm/mach-xxx实施的如下事情:
- 注册platform_device,绑定resource,即内存、IRQ等板级信息。
透过Device Tree后,形如
static struct resource xxx_resources[] = {
[0] = {
.start = …,
.end = …,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = …,
.end = …,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device xxx_device = {
.name = "xxx",
.id = -1,
.dev = {
.platform_data = &xxx_data,
},
.resource = xxx_resources,
.num_resources = ARRAY_SIZE(xxx_resources),
};
之类的platform_device代码都不再需要,其中platform_device会由kernel自动展开。而这些resource实际来源于.dts中设备结点的reg、interrupts属性。典型地,大多数总线都与“simple_bus”兼容,而在SoC对应的machine的.init_machine成员函数中,调用of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);即可自动展开所有的platform_device。譬如,假设我们有个XXX SoC,则可在arch/arm/mach-xxx/的板文件中透过如下方式展开.dts中的设备结点对应的platform_device:
static struct of_device_id xxx_of_bus_ids[] __initdata = {
{ .compatible = "simple-bus", },
{},
};
void __init xxx_mach_init(void)
{
of_platform_bus_probe(NULL, xxx_of_bus_ids, NULL);
}
#ifdef CONFIG_ARCH_XXX
DT_MACHINE_START(XXX_DT, "Generic XXX (Flattened Device Tree)")
…
.init_machine = xxx_mach_init,
…
MACHINE_END
#endif
- 注册i2c_board_info,指定IRQ等板级信息。
形如
static struct i2c_board_info __initdata afeb9260_i2c_devices[] = {
{
I2C_BOARD_INFO("tlv320aic23", 0x1a),
}, {
I2C_BOARD_INFO("fm3130", 0x68),
}, {
I2C_BOARD_INFO("24c64", 0x50),
},
};
之类的i2c_board_info代码,目前不再需要出现,现在只需要把tlv320aic23、fm3130、24c64这些设备结点填充作为相应的I2C controller结点的子结点即可,类似于前面的
i2c@1,0 {
compatible = "acme,a1234-i2c-bus";
…
rtc@58 {
compatible = "maxim,ds1338";
reg = <58>;
interrupts = < 7 3 >;
};
};
Device Tree中的I2C client会透过I2C host驱动的probe()函数中调用of_i2c_register_devices(&i2c_dev->adapter);被自动展开。
- 注册spi_board_info,指定IRQ等板级信息。
形如
static struct spi_board_info afeb9260_spi_devices[] = {
{ /* DataFlash chip */
.modalias = "mtd_dataflash",
.chip_select = 1,
.max_speed_hz = 15 * 1000 * 1000,
.bus_num = 0,
},
};
之类的spi_board_info代码,目前不再需要出现,与I2C类似,现在只需要把mtd_dataflash之类的结点,作为SPI控制器的子结点即可,SPI host驱动的probe函数透过spi_register_master()注册master的时候,会自动展开依附于它的slave。
- 多个针对不同电路板的machine,以及相关的callback。
过去,ARM Linux针对不同的电路板会建立由MACHINE_START和MACHINE_END包围起来的针对这个machine的一系列callback,譬如:
MACHINE_START(VEXPRESS, "ARM-Versatile Express")
.atag_offset = 0x100,
.smp = smp_ops(vexpress_smp_ops),
.map_io = v2m_map_io,
.init_early = v2m_init_early,
.init_irq = v2m_init_irq,
.timer = &v2m_timer,
.handle_irq = gic_handle_irq,
.init_machine = v2m_init,
.restart = vexpress_restart,
MACHINE_END
这些不同的machine会有不同的MACHINE ID,Uboot在启动Linux内核时会将MACHINE ID存放在r1寄存器,Linux启动时会匹配Bootloader传递的MACHINE ID和MACHINE_START声明的MACHINE ID,然后执行相应machine的一系列初始化函数。
引入Device Tree之后,MACHINE_START变更为DT_MACHINE_START,其中含有一个.dt_compat成员,用于表明相关的machine与.dts中root结点的compatible属性兼容关系。如果Bootloader传递给内核的Device Tree中root结点的compatible属性出现在某machine的.dt_compat表中,相关的machine就与对应的Device Tree匹配,从而引发这一machine的一系列初始化函数被执行。
static const char * const v2m_dt_match[] __initconst = {
"arm,vexpress",
"xen,xenvm",
NULL,
};
DT_MACHINE_START(VEXPRESS_DT, "ARM-Versatile Express")
.dt_compat = v2m_dt_match,
.smp = smp_ops(vexpress_smp_ops),
.map_io = v2m_dt_map_io,
.init_early = v2m_dt_init_early,
.init_irq = v2m_dt_init_irq,
.timer = &v2m_dt_timer,
.init_machine = v2m_dt_init,
.handle_irq = gic_handle_irq,
.restart = vexpress_restart,
MACHINE_END
Linux倡导针对多个SoC、多个电路板的通用DT machine,即一个DT machine的.dt_compat表含多个电路板.dts文件的root结点compatible属性字符串。之后,如果的电路板的初始化序列不一样,可以透过int of_machine_is_compatible(const char *compat) API判断具体的电路板是什么。
譬如arch/arm/mach-exynos/mach-exynos5-dt.c的EXYNOS5_DT machine同时兼容"samsung,exynos5250"和"samsung,exynos5440":
static char const *exynos5_dt_compat[] __initdata = {
"samsung,exynos5250",
"samsung,exynos5440",
NULL
};
DT_MACHINE_START(EXYNOS5_DT, "SAMSUNG EXYNOS5 (Flattened Device Tree)")
/* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
.init_irq = exynos5_init_irq,
.smp = smp_ops(exynos_smp_ops),
.map_io = exynos5_dt_map_io,
.handle_irq = gic_handle_irq,
.init_machine = exynos5_dt_machine_init,
.init_late = exynos_init_late,
.timer = &exynos4_timer,
.dt_compat = exynos5_dt_compat,
.restart = exynos5_restart,
.reserve = exynos5_reserve,
MACHINE_END
它的.init_machine成员函数就针对不同的machine进行了不同的分支处理:
static void __init exynos5_dt_machine_init(void)
{
…
if (of_machine_is_compatible("samsung,exynos5250"))
of_platform_populate(NULL, of_default_bus_match_table,
exynos5250_auxdata_lookup, NULL);
else if (of_machine_is_compatible("samsung,exynos5440"))
of_platform_populate(NULL, of_default_bus_match_table,
exynos5440_auxdata_lookup, NULL);
}
使用Device Tree后,驱动需要与.dts中描述的设备结点进行匹配,从而引发驱动的probe()函数执行。对于platform_driver而言,需要添加一个OF匹配表,如前文的.dts文件的"acme,a1234-i2c-bus"兼容I2C控制器结点的OF匹配表可以是:
static const struct of_device_id a1234_i2c_of_match[] = {
{ .compatible = "acme,a1234-i2c-bus ", },
{},
};
MODULE_DEVICE_TABLE(of, a1234_i2c_of_match);
static struct platform_driver i2c_a1234_driver = {
.driver = {
.name = "a1234-i2c-bus ",
.owner = THIS_MODULE,
.of_match_table = a1234_i2c_of_match,
},
.probe = i2c_a1234_probe,
.remove = i2c_a1234_remove,
};
module_platform_driver(i2c_a1234_driver);
对于I2C和SPI从设备而言,同样也可以透过of_match_table添加匹配的.dts中的相关结点的compatible属性,如sound/soc/codecs/wm8753.c中的:
.............
1533 static const struct of_device_id wm8753_of_match[] = {
1534 { .compatible = "wlf,wm8753", },
1535 { }
1536 };
1537 MODULE_DEVICE_TABLE(of, wm8753_of_match);
1587 static struct spi_driver wm8753_spi_driver = {
1588 .driver = {
1589 .name = "wm8753",
1590 .owner = THIS_MODULE,
1591 .of_match_table = wm8753_of_match,
1592 },
1593 .probe = wm8753_spi_probe,
1594 .remove = wm8753_spi_remove,
1595 };
1640 static struct i2c_driver wm8753_i2c_driver = {
1641 .driver = {
1642 .name = "wm8753",
1643 .owner = THIS_MODULE,
1644 .of_match_table = wm8753_of_match,
1645 },
1646 .probe = wm8753_i2c_probe,
1647 .remove = wm8753_i2c_remove,
1648 .id_table = wm8753_i2c_id,
1649 };
不过这边有一点需要提醒的是,I2C和SPI外设驱动和Device Tree中设备结点的compatible 属性还有一种弱式匹配方法,就是别名匹配。compatible 属性的组织形式为,,别名其实就是去掉compatible 属性中逗号前的manufacturer前缀。关于这一点,可查看drivers/spi/spi.c的源代码,函数spi_match_device()暴露了更多的细节,如果别名出现在设备spi_driver的id_table里面,或者别名与spi_driver的name字段相同,SPI设备和驱动都可以匹配上:
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;
}
static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
const struct spi_device *sdev)
{
while (id->name[0]) {
if (!strcmp(sdev->modalias, id->name))
return id;
id++;
}
return NULL;
}