内核对设备树的处理
从源码dts文件开始:
- dts在PC机上被编译为dtb文件
- u-boot把dtb文件传给内核
- 内核解析dtb文件,把每一个节点都转换为device_node结构体
- 对于某些device_node结构体,会被转换为platform_device结构体
哪些设备树节点会被转化为platform_device
-
根节点下含有compatible属性的子节点。
-
含有特定compatible属性的节点的子节点。
如果一个节点属性有"simple-bus",“simpile-mfd”, “isa”, “arm,armba-bus”,
那么它的子节点(需要包含compatible属性)也可以转换成platform_device。 -
总线I2C、SPI节点下的子节点:不转换为platform_device。
某个总线下到子节点,应该交给对应的总线驱动程序来处理,他们呢不应该被转换为platform_device。
/ { //是否会转换成platform_device
mytest { //Yes(条件1)
compatile = "mytest", "simple-bus";
mytest@0 { //Yes(条件2)
compatile = "mytest_0";
};
};
i2c { //Yes(条件1)
compatile = "samsung,i2c";
at24c02 { //No(条件3)
compatile = "at24c02";
};
};
spi { //Yes(条件1)
compatile = "samsung,spi";
flash@0 { //No(条件3)
compatible = "winbond,w25q32dw";
spi-max-frequency = <25000000>;
reg = <0>;
};
};
};
如何转换成platform_device
- platform_device中含有resource数组,它来自device_node的reg,interrupts属性。
- platform_device.dev.of_node指向device_node,可以通过它获得其他属性。
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) //1.最先比较是否强制选择某个driver
return !strcmp(pdev->driver_override, drv->name); //可以设置driver_override强制选择某个platform_driver
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv)) //2.比较platform_device.dev.of_node和
return 1; //platfrom_driver.driver.of_match_table
/* 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) //3.接下来比较platform_device.name和platform_driver.id_table[i].name
return platform_match_id(pdrv->id_table, pdev) != NULL; //id_table可能有多个
/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0); //4.最后比较platfrom_device.name和platform_driver.driver.name
}
//如果一个platform_driver支持设备树,它的platform_driver.driver.of_match_table是一个数组。类型如下:
struct of_device_id {
char name[32]; //3.最后如果有,就和dev的name属性比较
char type[32]; //2.其次如果有,就比较type值,与dev的device_type属性比较
char compatible[128]; //1.如果of_match_table中含有compatible值,就跟dev的compatible属性比较。返回成功或失败
const void *data;
}; //设备树中不再建议使用device_type和name属性。
dtb中每个节点都会被转化为device_node结构体
struct device_node {
const char *name; //来自设备树节点的name属性
const char *type; //来自设备树节点的type属性
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;
struct property *properties; //含有compatible属性
struct property *deadprops; /* removed properties */
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
struct kobject kobj;
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
struct property {
bool deleted;
char *name;
struct data val;
struct property *next;
struct label *labels;
};
类型 | 头文件 | 说明 |
---|---|---|
处理DTB | of_fdt.h dtb | of_fdt.h dtb文件相关操作函数,一般用不到。内核中已经转换成device_node树 |
处理device_node | of.h | 提供设备树的一般处理函数, 比如 of_property_read_u32, of_get_child_count |
- | of_address.h | 地址相关的函数, 比如 of_get_address ,of_match_device |
- | of_dma.h | 设备树中 DMA 相关属性的函数 |
- | of_gpio.h | GPIO 相关的函数 |
- | of_graph.h | GPU 相关驱动中用到的函数, 从设备树中获得 GPU 信息 |
- | of_iommu.h | 很少用到 |
- | of_irq.h | 中断相关的函数 |
- | of_mdio.h | MDIO (Ethernet PHY) API |
- | of_net.h | OF helpers for network devices. |
- | of_pci.h | PCI 相关函数 |
- | of_pdt.h | 很少用到 |
- | of_reserved_mem.h | reserved_mem 的相关函数 |
处理platform_device | of_platform.h | 把 device_node 转换为 platform_device 时用到的函数, 比如 of_device_alloc, of_find_device_by_node |
- | of_device.h | 设备相关的函数, 比如 of_match_device |
platform_device相关函数
//of_platform.h
extern struct platform_device *of_find_device_by_node(struct device_node *np); //知道device_node找platform_device
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
//对于设备树节点中的 reg 属性,它对应 IORESOURCE_MEM 类型的资源;
//对于设备树节点中的 interrupts 属性,它对应 IORESOURCE_IRQ 类型的资源。
找节点
//incldue/linux/of.h
//根据路径找节点
static inline struct device_node *of_find_node_by_path(const char *path);
//根据name找节点
extern struct device_node *of_find_node_by_name(struct device_node *from, const char *name);
//根据type找节点
extern struct device_node *of_find_node_by_type(struct device_node *from,const char *type);
//根据compatible找节点,from开始节点,type可以为NULL,指定device_type值,compat指定compatible的值
extern struct device_node *of_find_compatible_node(struct device_node *from,const char *type, const char *compat);
//根据phandle找节点,每个节点都有一个数字id
extern struct device_node *of_find_node_by_phandle(phandle handle);
//根据节点找父节点
extern struct device_node *of_get_parent(const struct device_node *node);
//根据节点找父节点,多调用了of_node_put(node)
extern struct device_node *of_get_next_parent(struct device_node *node);
//取出下一个子节点,prev是上一个子节点如果为NULL就找第一个
extern struct device_node *of_get_next_child(const struct device_node *node,struct device_node *prev);
//取出下一个可用子节点,status是"disabled"就会被跳过
extern struct device_node *of_get_next_available_child(const struct device_node *node, struct device_node *prev);
//根据名字取出子节点
extern struct device_node *of_get_child_by_name(const struct device_node *node, const char *name);
找属性
//incldue/linux/of.h
//找到节点中的属性,np表示节点,name表示名字,lenp用来保存这个属性的长度值
//xxx_pp_name = "hello";那么它的属性值是6
extern struct property *of_find_property(const struct device_node *np, const char *name, int *lenp);
//根据名字找到节点的属性,np表示节点,name表示名字,lenp用来保存这个属性的长度值
extern const void *of_get_property(const struct device_node *node, const char *name, int *lenp);
//根据名字找到节点的属性,确定它有多少个元素
//np表示节点,找到propname的属性,返回pro->length/elem_size
// xxx_pp_name = <0x50000000 1024> <0x60000000 2048>
// 调用 of_property_count_elems_of_size(np, "xxx_pp_name", 8)时,返回值是 2
// 调用 of_property_count_elems_of_size(np, "xxx_pp_name", 4)时,返回值是 4
extern int of_property_count_elems_of_size(const struct device_node *np, const char *propname, int elem_size);
// 读整数u32/u64
// name1 = <0x50000000>;
// name2 = <0x50000000 0x60000000>;
// 调用 of_property_read_u32 (np, “name1”, &val)时,val 将得到值 0x50000000
// 调用 of_property_read_u64 (np, “name2”, &val)时,val 将得到值 0x0x6000000050000000
static inline int of_property_read_u32(const struct device_node *np, const char *propname, u32 *out_value);
extern int of_property_read_u64(const struct device_node *np, const char *propname, u64 *out_value);
// 读整数u32/u64,index为序号
// 调用 of_property_read_u32 (np, "name2", 1, &val)时,val 将得到值 0x0x60000000
extern int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value);
// 读数组
// name2 = <0x50000012 0x60000034>;
// 调用 of_property_read_variable_u8_array (np, “name2”, out_values, 1, 10)时,out_values 中将会保存这 8 个字节: 0x12,0x00,0x00,0x50,0x34,0x00,0x00,0x60
// 调用 of_property_read_variable_u16_array (np, “name2”, out_values, 1, 10)时,out_values中将会保存这 4 个 16 位数值: 0x0012, 0x5000,0x0034,0x6000
// 会返回全部数值,要么没有。长度介于sz_min和sz_max之间
int of_property_read_variable_u8_array(const struct device_node *np, const char *propname, u8 *out_values, size_t sz_min, size_t sz_max);
// 读字符串
// 返回np的propname的名字的值,(*out_string)指向这个值
extern int of_property_read_string(struct device_node *np, const char *propname, const char **out_string);
修改设备树文件方法
- 使用芯片厂家提供的工具
- 看绑定文档
- 参考同类型单板的设备树文件
- 网上搜索
- 实在没办法时, 只能去研究驱动源码