驱动的分离和分层
- 为了方便开发,将主机驱动和设备驱动分离开来,主机驱动和设备驱动通过统一的接口访问。
- 主机驱动由半导体厂商编写,设备驱动由设备器件厂商编写,用户只需提供设备信息。
- 驱动的分层是为了在不同的层处理不同的内容。
- Linux采用驱动-总线-设备模型,通过总线匹配驱动和设备。
- 向系统注册一个驱动时,总线在所以设备中查找与驱动匹配的设备。
- 向系统注册一个设备时,总线在所有驱动中查找与设备匹配的驱动。
- 总线由Linux内核提供,无需编写;用户需编写驱动和设备程序。
- 驱动是具体的设备驱动;设备是设备属性信息包括地址、速度等。
platform
Linux提供platform虚拟总线,可以使LCD、RTC等没有总线概念的外设中使用驱动-总线-设备模型。
platform总线
Linux定义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;
};
bus_type结构体中,match函数用于驱动和设备之间的匹配,每一条总线都必须实现match函数。match函数的dev和drv参数分别为设备和驱动的类型。 驱动和设备的匹配方法有四种,常用设备树OF类型匹配和字段匹配方法,platform_match匹配函数在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);
}
- 第一种匹配方法,OF类型匹配,设备树采用的匹配方法,使用of_driver_match_device函数。在device_driver设备驱动结构体中of_match_table成员变量保存compatible匹配表,设备树中的每个设备节点的compatible属性都会和of_match_table匹配表中成员比较,如果相同表示匹配,probe函数执行。
- 第二种匹配方法,ACPI匹配。
- 第三种匹配方法,id_table匹配,根据platform_driver结构体id_table成员变量中id信息进行匹配。
- 第四章匹配方法,name字段匹配,比较驱动和设备中的name字段,相同表示匹配成功。
platform驱动
platform只是为了驱动分离和分层提出的一种框架,platform驱动的具体实现还需要字符设备驱动、块设备驱动和网络设备驱动。
Linux定义platform_driver结构体表示platform驱动,include/linux/platform_device.h。
/* include/linux/platform_device.h */
struct platform_driver {
int (*probe)(struct platform_device *);
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;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
/* include/linux/device.h */
struct device_driver {
const char *name;
struct bus_type *bus;
struct module *owner;
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;
const struct acpi_device_id *acpi_match_table;
int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
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;
};
/* include/linux/mod_devicetable.h */
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
- ?驱动和设备匹配成功后,执行probe函数,用户编写probe函数。
- driver成员变量是device_driver结构体变量,platform_driver继承device_driver这个“基类”。
- id_table是个数组,每个元素类型为platform_device_id。
- device_driver结构体中of_match_table是个数组,是使用设备树时驱动的匹配表,每个元素类型为of_device_id。
- of_device_id结构体中的compatible变量,设备树通过设备节点的compatible属性和of_match_table中的每个项目的compatible成员变量比较,相同表示匹配成功。
platform驱动首先定义一个platform_driver结构体变量,然后使用platform_driver_register函数注册platform驱动。
int platform_driver_register(struct platform_driver *driver);
- driver:要注册的platform驱动。
- 返回值:0,成功;负值,失败。?
使用platform_driver_unregister函数卸载platform驱动。
void platform_driver_unregister(struct platform_driver *drv);
- driver:要卸载的platform驱动。
- 返回值:无。
platform驱动框架
/* 设备结构体 */
struct xxx_dev{
struct cdev cdev;
/* ... */
};
struct xxx_dev xxxdev;
/* 字符设备操作集合 */
static struct file_operation xxx_fops = {
.owner = THIS_MODULE;
/* ... */
};
/* probe函数在驱动和设备匹配后执行 */
static int xxx_probe(struct platform_device *dev){
/* 注册字符设备驱动代码*/
/* ... */
cdev_init(&xxxdev.cdev, &xxx_fops);
return 0;
}
/* 关闭platform驱动后执行 */
static int xxx_remove(struct platform_device *dev){
/* 卸载字符设备驱动代码*/
/* ... */
cdev_del(&xxxdev.cdev);
return 0;
}
/* 匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{.compatible = "xxx-gpio"},
{ /* Sentinel */ }, /* 最后一个匹配项为空 */
};
/* platform驱动结构体 */
static struct platform_driver xxx_driver = {
.driver = {
.name = "xxx", /* 无设备树匹配 */
.of_match_table = xxx_of_match, /* 设备树匹配 */
},
.probe = xxx_probe,
.remove = xxx_remove,
};
/* 驱动模块加载 */
static int __init xxx_driver_init(void){
return platform_driver_register(&xxx_driver); /* 注册platform驱动 */
}
/* 驱动模块卸载 */
static void __exit xxx_driver_exit(void){
platform_driver_unregister(&xxx_driver); /* 卸载platform驱动 */
}
module_init(xxx_driver_init);
module_exit(xxx_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XXXUUUXXX");
- xxx_probe:将放在模块加载init函数中的代码如注册字符设备驱动、添加cdev、创建类和设备等都放在probe函数内,当驱动和设备匹配成功后执行probe函数。
- xxx_remove:将放在模块卸载exit函数中的代码如注销设备号、删除cdev等都放在remove函数内。
- xxx_of_match:使用设备树使,匹配节点的compatible属性,of_device_id最后一个匹配项必须为空。
- xxx_driver:name属性用于无设备树匹配方法,匹配驱动和设备的name字段是否相同。of_match_table属性用于设备树匹配方法。
platform设备
- 使用设备树表示设备,如何Linux内核支持设备树,使用设备树描述设备。
- Linux定义platform_device结构体表示platform设备。include/linux/platform_device.h。
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
- name:设备名字,与驱动的name字段进行匹配。
- num_resources:资源数量。
- resource:资源设备信息,包括外设寄存器等。
resource结构体表示资源,include/linux/ioport.h。
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
- start:资源起始信息(地址)。
- end:资源终止信息(地址)。
- name:资源名字。
- flag:资源类型。
resource资源类型flag,在include/linux/ioport.h中用宏定义表示。
/*
* IO resources have these defined flags.
*/
#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
#define IORESOURCE_PREFETCH 0x00002000 /* No side effects */
#define IORESOURCE_READONLY 0x00004000
#define IORESOURCE_CACHEABLE 0x00008000
#define IORESOURCE_RANGELENGTH 0x00010000
#define IORESOURCE_SHADOWABLE 0x00020000
#define IORESOURCE_SIZEALIGN 0x00040000 /* size indicates alignment */
#define IORESOURCE_STARTALIGN 0x00080000 /* start field is alignment */
#define IORESOURCE_MEM_64 0x00100000
#define IORESOURCE_WINDOW 0x00200000 /* forwarded by bridge */
#define IORESOURCE_MUXED 0x00400000 /* Resource is software muxed */
#define IORESOURCE_EXCLUSIVE 0x08000000 /* Userland may not map this resource */
#define IORESOURCE_DISABLED 0x10000000
#define IORESOURCE_UNSET 0x20000000 /* No address assigned yet */
#define IORESOURCE_AUTO 0x40000000
#define IORESOURCE_BUSY 0x80000000 /* Driver has marked this resource busy */
/* PnP IRQ specific bits (IORESOURCE_BITS) */
#define IORESOURCE_IRQ_HIGHEDGE (1<<0)
#define IORESOURCE_IRQ_LOWEDGE (1<<1)
#define IORESOURCE_IRQ_HIGHLEVEL (1<<2)
#define IORESOURCE_IRQ_LOWLEVEL (1<<3)
#define IORESOURCE_IRQ_SHAREABLE (1<<4)
#define IORESOURCE_IRQ_OPTIONAL (1<<5)
/* PnP DMA specific bits (IORESOURCE_BITS) */
#define IORESOURCE_DMA_TYPE_MASK (3<<0)
#define IORESOURCE_DMA_8BIT (0<<0)
#define IORESOURCE_DMA_8AND16BIT (1<<0)
#define IORESOURCE_DMA_16BIT (2<<0)
#define IORESOURCE_DMA_MASTER (1<<2)
#define IORESOURCE_DMA_BYTE (1<<3)
#define IORESOURCE_DMA_WORD (1<<4)
#define IORESOURCE_DMA_SPEED_MASK (3<<6)
#define IORESOURCE_DMA_COMPATIBLE (0<<6)
#define IORESOURCE_DMA_TYPEA (1<<6)
#define IORESOURCE_DMA_TYPEB (2<<6)
#define IORESOURCE_DMA_TYPEF (3<<6)
/* PnP memory I/O specific bits (IORESOURCE_BITS) */
#define IORESOURCE_MEM_WRITEABLE (1<<0) /* dup: IORESOURCE_READONLY */
#define IORESOURCE_MEM_CACHEABLE (1<<1) /* dup: IORESOURCE_CACHEABLE */
#define IORESOURCE_MEM_RANGELENGTH (1<<2) /* dup: IORESOURCE_RANGELENGTH */
#define IORESOURCE_MEM_TYPE_MASK (3<<3)
#define IORESOURCE_MEM_8BIT (0<<3)
#define IORESOURCE_MEM_16BIT (1<<3)
#define IORESOURCE_MEM_8AND16BIT (2<<3)
#define IORESOURCE_MEM_32BIT (3<<3)
#define IORESOURCE_MEM_SHADOWABLE (1<<5) /* dup: IORESOURCE_SHADOWABLE */
#define IORESOURCE_MEM_EXPANSIONROM (1<<6)
/* PnP I/O specific bits (IORESOURCE_BITS) */
#define IORESOURCE_IO_16BIT_ADDR (1<<0)
#define IORESOURCE_IO_FIXED (1<<1)
/* PCI ROM control bits (IORESOURCE_BITS) */
#define IORESOURCE_ROM_ENABLE (1<<0) /* ROM is enabled, same as PCI_ROM_ADDRESS_ENABLE */
#define IORESOURCE_ROM_SHADOW (1<<1) /* ROM is copy at C000:0 */
#define IORESOURCE_ROM_COPY (1<<2) /* ROM is alloc'd copy, resource field overlaid */
#define IORESOURCE_ROM_BIOS_COPY (1<<3) /* ROM is BIOS copy, resource field overlaid */
/* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
#define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */
在不支持设备树的Linux版本,使用platform_device表示设备信息,使用platform_device_register函数将platform设备信息注册到Linux内核。
int platform_device_register(struct platform_device *pdev);
- pdev:要注册的platform设备。
- 返回值:0,成功;负值,失败。
使用platform_device_unregister函数注销platform设备。
void platform_device_unregister(struct platform_device *pdev);
- pdev:要注销的platform设备。
- 返回值:无。
platform设备框架—无设备树
/* 寄存器地址定义外设寄存器基地址 */
#define PERIPH1_REGISTER_BASE (0x20000000)
#define PERIPH2_REGISTER_BASE (0x020e0068)
#define REGISTER_LEN 4
/* 资源 */
static struct resource xxx_resources[] = {
[0] = {
.start = PERIPH1_REGISTER_BASE,
.end = (PERIPH1_REGISTER_BASE + REGISTER_LEN - 1),
.flag = IORESOURCE_MEM,
},
[1] = {
.start = PERIPH2_REGISTER_BASE,
.end = (PERIPH2_REGISTER_BASE + REGISTER_LEN - 1),
.flag = IORESOURCE_MEM,
},
};
/* platform设备 */
static struct platform_device xxx_device = {
.name = "xxx-gpio",
.id = -1,
.num_resources = ARRAY_SIZE(xxx_resources),
.resource = xxx_resources,
};
/* 设备模块加载 */
static int __init xxx_device_init(void){
return platform_device_register(&xxx_device);
}
/* 设备模块卸载 */
static void __exit xxx_device_exit(void){
platform_device_unregister(&xxx_device);
}
module_init(xxx_device_init);
module_exit(xxx_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XXXUUUXXX");
- xxx_resources:设备资源,包括外设起始信息、终止信息、资源类型,此处为内存类型。资源获取使用platform_get_resource函数。
- xxx_device:platform设备结构体变量,name字段和驱动中的name字段相同表示匹配成功,使用ARRAY_SIZE获取数组元素个数。
|