驱动的分离与分层思想
分离:硬件信息分离;
在编写硬件驱动的时候,需要操作许多硬件寄存器。比如gpio 驱动,你需要知道gpio控制器 寄存器的地址,你想要哪个gpio输出?或是输入? 这些操作最终都是靠设置寄存器完成,如果你想要操作的gpio变了、方向变了,这些硬件信息的都要修改驱动。
或者说同一个系列的soc 它们的寄存器地址不同、soc 更新换代寄存器地址改变等等,但是寄存器的设置方法相同,驱动无法兼容多个硬件。
这样,明明可以只用一份代码通,却因为硬件不同要写无数份,代码重用性很低,内核中也会多许多这种垃圾代码。
这种要不断修改、重用性低的驱动是不理想的,有没有可能做出一份通用且不需要经常修改代码的驱动呢?
可以,硬件信息分离。把控制器的寄存器地址、要输出的引脚、方向等信息从驱动分离出来另外保存,原本的驱动就变成一些纯软件代码与操作寄存器的代码,这样就不需要因为硬件的改变而不断修改代码。
分离的思想就是把硬件信息从驱动中分离出来,做出一套通用的驱动。
Linux 驱动如何实现分离:内核3.x 以前使用 .c(结构体) 文件描写硬件信息,有设备树后用设备描写硬件信息。
分层:代码分层。
使用过内核通用的gpio驱动可以知道,内核gpio驱动分为两部分:
- gpio核心层 (与硬件无关的软件层,负责管理下层的各种驱动,调用它们提供的硬件操作函数(下层一般会把操作函数设成回调函数)进行封装,实现各个厂家的驱动统一的操作方法)
- chip_xxx_gpio.c 某个soc 厂家为自己芯片编写的gpio硬件驱动。(实现gpio功能,各个厂家的操作方式可能不同,所以操作函数也不同)
为什么要分层呢?
因为linux内核是要兼容多个平台的,不同的平台可能寄存器的设计不同导致方法不同,那又会衍生出很多驱动。
所以内核提出了分层思想,由与硬件无关的软件层作为上层(核心层)来管理下层驱动,而各个厂家则根据自己的硬件编写驱动代码作为下层(硬件驱动层)(不管你硬件怎么变,暴露给上层的接口必须一致,按照上层的管理方法来编写)。
加上硬件信息,一个驱动一共就是三部分:通用的驱动框架、硬件驱动和硬件信息(硬件信息+硬件驱动=硬件驱动层)。
Linux 驱动-总线-设备 模型
分离是把硬件信息和驱动代码分离开来,那么在Linux内核中如何实现分离。
Linux 内核建立了设备-总线-驱动 这样的模型,用struct device
来表示设备,用struct device_driver
来表示驱动。(它们都定义于include\linux\device.h)
(struct device 和struct device_driver 只是两个基类,根据不同的总线会衍生出很多派生类,比如platform_device(它可以描述设备信息)、platform_driver (可以描述驱动))
struct device {
struct device *parent; //父设备
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */ //名字
const struct device_type *type;
struct mutex mutex; /* mutex to synchronize calls to
* its driver.
*/
struct bus_type *bus; /* type of bus device is on */ //总线类型
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device //platform 数据
core doesn't touch it */
void *driver_data; /* Driver data, set and get with //驱动数据,一般可以在驱动代码中设置为比较常用的结构体变量
dev_set/get_drvdata */
struct dev_pm_info power;
struct dev_pm_domain *pm_domain;
#ifdef CONFIG_PINCTRL
struct dev_pin_info *pins;
#endif
#ifdef CONFIG_NUMA
int numa_node; /* NUMA node this device is close to */
#endif
u64 *dma_mask; /* dma mask (if dma'able device) */
u64 coherent_dma_mask;/* Like dma_mask, but for
alloc_coherent mappings as
not all hardware supports
64 bit addresses for consistent
allocations such descriptors. */
unsigned long dma_pfn_offset;
struct device_dma_parameters *dma_parms;
struct list_head dma_pools; /* dma pools (if dma'ble) */
struct dma_coherent_mem *dma_mem; /* internal for coherent mem
override */
#ifdef CONFIG_DMA_CMA
struct cma *cma_area; /* contiguous memory area for dma
allocations */
#endif
/* arch specific additions */
struct dev_archdata archdata;
struct device_node *of_node; /* associated device tree node */ //设备树节点
struct fwnode_handle *fwnode; /* firmware device node */
dev_t devt; /* dev_t, creates the sysfs "dev" */ //设备号
u32 id; /* device instance */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class; //device 所属的类
const struct attribute_group **groups; /* optional groups */ //属性文件,组
void (*release)(struct device *dev); //用于释放device 的回调函数
struct iommu_group *iommu_group;
bool offline_disabled:1;
bool offline:1;
};
struct device_driver {
const char *name; //驱动名字
struct bus_type *bus; //总线类型
struct module *owner; //一般设置为THIS_MODULES
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
const struct of_device_id *of_match_table; //用于设备树匹配的id
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev); //当匹配成功后会调用probe(在probe中我们可以自由发挥,做任何想做的事,比如硬件的设置)
int (*remove) (struct device *dev); //一方想要卸载时会调用remove(匹配成功的状态下)
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
const struct attribute_group **groups; //属性文件,组
const struct dev_pm_ops *pm;
struct driver_private *p;
};
现在有了设备信息、驱动代码,但是要把它们合在一起使用才能算是完整可用的驱动代码,内核里有那么多个驱动,就会产生无数个device 和device_driver,如何把它们匹配在一起?
这时候总线就出来了,它能把相互对应的device 和device_driver 匹配起来形成完整的驱动。(此总线并非硬件总线,而是Linux内核中虚拟出来的,用来匹配设备信息和驱动代码)
总线用一个struct bus_type
类型的数据结构来描述:
include\linux\device.h
struct bus_type {
const char *name; //总线名
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv); //负责判断设备与驱动之间是否匹配的函数,至关重要!!!
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
};
查看device 与 device_driver 匹配过程:
原理分析:
注册device 时会将它添加到bus->p->klist_devices链表,然后获取bus->p->klist_drivers 链表,将device 与链表中的所有device_driver 一一比较,如果匹配成功就会调用device_driver->probe。
同样注册注册device_driver 时也会将它添加到bus->p->klist_drivers链表,然后获取bus->p->klist_devices 链表,将device_driver 与链表中的所有device 一一比较,如果匹配成功就会调用device_driver->probe。
struct device 使用device_register()
向内核注册,device_driver 使用driver_register()
向内核注册。
device_register
device_add
->bus_add_device
bus_add_device 中将dev->p->knode_bus(struct klist_node) 添加到bus->p->klist_devices(struct klist) 链表中 (bus根据设备所属的总线来定,可能是platform、i2c、spi… 任何总线)。
device_add
->bus_probe_device
->device_attach (attach 表示附加的意思,此函数将device 附加到某个device_driver 上)
device_attach 中调用bus_for_each_drv 遍历bus 下的所有device_driver。
klist_iter_init_node 初始化了一个klist_iter (i)
i->i_klist = k; //被设置为了bus->p->klist_drivers(它就是device_driver 的klist 链表)
i->i_cur = n; //i_cur 是struct klist_node 类型的(传入的start为NULL,所以i_cur == NULL)
循环的调用next_driver(&i) 来遍历bus->p->klist_drivers 所有节点,获取与节点对应的device_driver
(依靠刚刚构建的klist_iter 来找到下一个链表节点,由于klist_iter->i_cur 为空,所以可能是从头开始获取节点之类的)
拿到了device_driver,就可以与新注册device 进行比对了,调用fn 也就是传入的_device_attach 进行比较,如果匹配上就会绑定device 和device_driver(device->driver = driver),并且调用device_driver->probe。
device_add
->bus_probe_device
->device_attach
->bus_for_each_drv
->__device_attach //判断device 与device_driver 是否匹配
比较的方法由具体的总线提供,直接调用device_driver->bus->match 回调函数。
假如总线是platform 的话,那么bus->match 就是platform_match。
platform_match 中有三种匹配方式,在有设备树的时我们一般使用设备树的匹配方式(比较compatible 的值),没有设备树则使用方法3 和4。
of_driver_match_device 是通过platform_driver->driver->of_match_table->compatible 与platform_device->device->of_node 中读取compatible 属性值来比较。
of_driver_match_device
->of_match_device
platform_match_id 是通过比较platform_device 的名字和platform_driver->id_table->name 来判断是否匹配。
如果匹配则调用 driver_probe_device 绑定device 与device_driver 的关系,并且调用bus->probe 或device_driver->probe
driver_probe_device
->really_probe
优先调用device->bus->probe,如果没提供则调用deivce_driver->probe。
driver_register
driver_register 调用bus_add_driver 向总线添加device_driver
bus_add_driver 将device_driver->p->knode_bus (klist_node)节点添加到bus->p->klist_drivers (klist)链表。
然后调用driver_attach,将新注册的device_driver 与bus 下的所有device 进行比对。
driver_attach 调用bus_for_each_dev 遍历总线下的所有device,调用_driver_attach 将新注册的device_driver 与device 一一比对。
bus_for_each_dev 循环遍历bus 下的所有device,调用__driver_attach 进行device 与device_driver比较
__driver_attach 调用driver_match_device 来判断device 和device_driver 是否匹配(这一步和__device_attach 中是一样的调用bus->match 来对比)
如果两者匹配的话调用driver_probe_device->really_probe 绑定device 和device_driver的关系,然后调用bus->probe 或device_driver->probe (这一步和__device_attach 中也是一模一样的)
根据不同外设的特点,内核定义了多条常用的总线,比如platform总线、i2c 总线、spi总线… 等等。
定义如下:
//drivers/base/platform.c
//drivers/i2c/i2c-core.c
//driver/spi/spi.c
可以在命令行查看内核具体定义了哪些总线,如:
为什么要定义这么多总线类型,以i2c 和platform 来举例:
i2c 总线:
挂在i2c总线(硬件)下的从设备,比如加密芯片、rtc芯片、触摸屏芯片等等,这些从设备它们也需要驱动,它们自然也要按照分离思想来设计。
那么内核中的i2c 总线就是用来帮助i2c从设备的设备信息 和驱动,互相匹配的。
platform 总线:
像i2c、spi这样硬件有实体总线的,从设备驱动可以用总线来管理。
那么没有总线的硬件外设怎么办,比如gpio、uart、i2c控制器、spi 控制器…等等,这些通通用platform 总线来管理。
如何注册一个总线(常用的总线内核中已经注册好了,通常情况下无需自己创建一条总线):
可以用bus_register
函数注册一条总线:
int bus_register(struct bus_type *bus)
bus_unregister
注销一条总线:
void bus_unregister(struct bus_type *bus)
如platform 总线的注册代码如下:
//drivers\base\platform.c
platform 总线
根据platform 总线和外设的特点,为了更好的描述设备信息和驱动代码,在device
和device_driver
基础上衍生了platform_device
和platform_driver
两个派生类。
//include/linux/platform_device.h
struct platform_device {
const char *name; //设备名
int id;
bool id_auto;
struct device dev; //派生类仍然包含基础的device,因为里面有不可或缺的重要信息
u32 num_resources; //资源数量
struct resource *resource; //描述硬件设备的资源,资源包括寄存器地址范围、irq 等等。
const struct platform_device_id *id_entry; //用于匹配的id
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
在kernel v3.x 版本以上,内核引入设备树,所以硬件信息往往在设备树中描述。内核代码会解析设备树,把它转化为一个platform_device。
例如这是一个描述gpio控制器的设备树节点:
reg 第一个字段表示gpio控制器寄存器的起始地址,第二个字段表示长度。(物理地址)
interrupts 表示中断号。(共享中断 中断号 中断触发类型)
//include/linux/platform_device.h
struct platform_driver {
//当platform_driver与platform_device 匹配时就会调用probe(在probe函数中可以自由发挥,写任何东西,比如驱动代码)
int (*probe)(struct platform_device *);
//当platform_driver 或是platform_device 一方卸载时就会调用remove (匹配完成的情况下)
//(在remove中通常做与probe 相反的操作,比如probe 中申请了内存,那么remove 就要释放内存)
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; //派生类任然包含基类,这是很重要的(device_driver 中包含决定双方是否匹配的重要信息)
const struct platform_device_id *id_table; //用于匹配的id
bool prevent_deferred_probe;
};
在platform_device 中有一个resource 成员,它对于描述硬件信息是非常重要的。
它用来描述一个资源,资源包括寄存器地址、中断等等。(可描述的资源很多,参考宏定义)
//include/linux/ioport.h
/*
如果所描述的资源是寄存器地址(IORESOURCE_MEM 类型),那么:
start 保存着reg 的起始地址,end 保存着reg的末尾地址。start-end 就描述出了一段地址范围。
如果描述的是irq,那么:start 保存着irq 的值。
*/
struct resource {
resource_size_t start;
resource_size_t end;
const char *name; //资源的名字
unsigned long flags;
struct resource *parent, *sibling, *child;
};
//include/linux/ioport.h
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */ //io内存地址资源类型
#define IORESOURCE_MEM 0x00000200 //mem内存地址资源类型
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400 //irq 资源类型
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
......
那么platform_device
和platform_driver
这两个结构体究竟该如何使用它呢,以imx6ull 的gpio驱动为例:
首先硬件信息会用设备树节点描述,由内核解析成一个platform_device 注册到内核中。
(没有设备树的版本,驱动需要自己创建一个platform_device 结构体,设置里面的内容,然后向内核注册。使用platform_device_register
函数注册)
int platform_device_register(struct platform_device *);
在驱动中,需要构建一个platform_driver,初始化内容,并调用platform_driver_register
向内核注册。
细看platform_driver 中设置的内容:
device_driver->name :是驱动的名字,它也可以用来判断platform_device 与platform_driver 是否匹配。(非设备树情况下)
device_driver->of_match_table :设备树特有的匹配方式。在dtb节点 和of_match_table 中都有一个compatible 属性,它用来判断驱动与设备是否兼容,如果compatible的值是相同的那么就就判断他两是匹配的。data 是与硬件平台相关的数据。
id_table:不使用设备树情况下的一种匹配方式,通过对比id_table->name 与platform_device->name 来判断两者是否兼容。
probe:当匹配完成之后,就会自动调用probe 函数了,在probe函数中我们可以做任何事情,比如硬件的设置等等。
参考mxc_gpio_probe 来看看它做了什么:
(传入的参数就是由设备树节点转化而来的platform_device)
除了这些,在probe 中还可以做任何事:注册irq、设置driver_data 等等。
这个函数可以设置platform_device->dev.drver_data,可以把它设置成任何驱动中常用的变量地址。
platform_device->dev.of_node 指向的就是与驱动所匹配的设备树节点,可以用它来获取设备树节点。
platform_driver 与platform_device 注册流程
总结前面的内容:为了实现硬件信息的分离,Linux 内核用一个platform_device 来描述硬件信息,用platform_driver 来描述驱动代码;由platform 总线来管理,帮助它们相互匹配。
使用platform总线管理的设备都要调用platform_device_register
注册platform_device,驱动要调用platform_driver_regster
注册platform_driver。每当platform_device 和platform_driver 匹配就会调用platform_driver.probe 来执行驱动代码。
#define platform_driver_register(drv) \ __platform_driver_register(drv, THIS_MODULE) extern int __platform_driver_register(struct platform_driver *, struct module *);
int platform_device_register(struct platform_device *);
那么双方究竟是如何完成匹配的,又是如何调用到probe 函数的?这就要看看platform_device_register
和platform_driver_regster
是如何注册的。
代码解析
调用platform_device_register 注册platform_device
platform_device_register
->platform_device_add
将platform_device->dev 的父设备设为platform_bus (在注册platform 总线时创建的)
platform_device->dev.bus 设为platform_bus_type。
调用device_add 添加platform_device->dev (struct device)。
(从device_add 开始就是device 的注册过程,参考前面的device与device_driver 匹配过程)
调用platform_driver_register 注册platform_driver
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
__platform_driver_register 中将platform_driver->driver.probe 设置为platform_drv_probe 那么在device 和device_driver 匹配成功后就会调用platform_drv_probe 。
调用driver_register 注册platform_driver->driver (参考前面的的device_driver 注册过程,device 与device_driver 匹配过程)
在device 与device_driver 匹配成功后,最终将调用device_driver->probe 对于platfrom 总线来说,它就是platform_drv_probe。
在platform_drv_probe 中会调用platform_driver->probe(也就是各自驱动中实现的真正的驱动代码了)