总线设备驱动模型
总线设备驱动模型是基于驱动设计思想分离而实现的。其主要包括了三个部分:总线(bus)、设备(dev)、驱动(drv); 主要功能就是将dev与drv分离开来。
设备(dev)
在dev部分中会注册一个platform_device(设备平台)。可以在platform_device结构体里面定义使用哪个引脚也可以用来表示所有设备的资源。
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;
};
驱动(drv)
在drv部分中会注册一个platform_driver(驱动平台)。platform_driver结构体中的probe函数可以做任何你想做的事情,例如:寄存器硬件操作,实现引脚的寄存器操作,/分配/设置/注册file_operations,使用ioremap映射寄存器都能在该结构体中实现。
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;
};
总线
那怎么将这两个平台联系起来呢?
内核里面有一个总线,这个总线是bus_type,是一个虚拟总线。bus有两个链表,一个是设备链表,一个是驱动链表。当我们注册一个platform_device 时,就会放入设备链表;注册platform_driver时就会放入驱动链表。比如说,将platform_device注册进内核里面了,同时就会去对面的驱动链表里面,将里面的成员一个个取出来进行对比,如果对比成功,就会调用platform_driver的probe函数来处理device。
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函数,其函数就用来比较dev和drv是否匹配。
进行匹配的先后顺序:
1.如果platform_device结构体定义了driver_override,就首先通过driver_override定义的字符串与platform_driver里面的名字是否相等,如果相等,就说明匹配成功。
2.如果平台设备并没有定义driver_override的话,就通过第二种方法进行比较。如果数组id_table不是空的,那么设备平台中的名字就会与数组里面的每一项进行匹配。
3.如果id_table为空的话,或者设备平台的名字与id_table匹配失败,那么就会将设备平台的名字与驱动平台的名字进行匹配。
match函数demo:
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);
/* 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);
}
编写LED总线设备驱动模型
1、实现platform_device结构体
board_A.c 作为一个可加载模块,里面也有入口函数、出口函数。在入口函数中注册 platform_device结构体,在 platform_device 结构体中指定使用哪个 GPIO 引脚。
首先看入口函数,它调用 platform_device_register 函数,向内核注册 board_A_led_dev 结构体:
demo
static void led_dev_release(struct device *dev)
{
}
static struct resource resource[] = { //用来存放引脚资源
{
.start = GROUP_PIN(5, 3),
.name = "led2_pin",
.flags = IORESOURCE_IRQ,
},
{
.start = GROUP_PIN(3, 1),
.name = "led2_pin",
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device board_A_led_device = {
.name = "imx6ull_led", //平台设备(platform_device)名字
.num_resources = ARRAY_SIZE(resource), //ARRAY_SIZE计算资源数组的个数
.resource = resource,
.dev = {
.release = led_dev_release,
},
};
static int __init led_dev_init(void)
{
int err;
err = platform_device_register(&board_A_led_device);
return 0;
}
static void __exit led_dev_exit(void)
{
platform_device_unregister(&board_A_led_device);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
注意:提供了一个空函数 led_dev_release,它被赋给 board_A_led_device结构体,这个函数在卸载 platform_device 时会被调用,如果不提供的话内核会打印警告信息。
2、实现 platform_driver 结构体
chip_demo_gpio.c 中注 册 platform_driver 结构 体 , 它 使用 Bus/Dev/Drv 模型 , 当有 匹 配的platform_device 时,它的 probe 函数就会被调用。
在 probe 函数中做你想做的事情,例如分配、设置、注册file_operation、操作寄存器等。
在probe函数中调用platform_get_resource获取设备平台中的resource。
platform_get_resource函数原型
/**
* platform_get_resource - get a resource for a device
* @dev: platform device
* @type: resource type
* @num: resource index
*/
struct resource *platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
获取资源之后就需要将引脚信息记录下来。
并需要创建设备节点。由于device_create函数中led_class变量在上层文件中,不能够直接调用device_create。可以在上层文件中将device_create封装进函数中。
demo
/* 创建设备 */
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
struct resource *res;
int i = 0;
while(1){
/* platform_get_resource - get a resource for a device */
res = platform_get_resource(pdev, IORESOURCE_IRQ, i++); //搜索设备链表pdev中IORESOURCE_IRQ类型的资源
if(!res)
break;
/* 记录设备资源中的引脚信息 */
g_ledpins[g_ledcnt] = res->start;
/* device_create
* 由于device_create函数中led_class变量在上层文件中,不能够直接调用device_create
* 可以将device_create封装进函数中
*/
led_class_create_device(i);
g_ledcnt++;
}
return 0;
}
/* 销毁设备 */
static int chip_demo_gpio_remove(struct platform_device *pdev)
{
struct resource *res;
int i = 0;
while(1){
res = platform_get_resource(pdev, IORESOURCE_IRQ, i);
if(!res)
break;
led_class_destroy_device(i);
i++;
g_ledcnt--;
}
return 0;
}
static struct led_operations board_demo_led_opr = {
.init = board_demo_led_init,
.ctl = board_demo_led_ctl
};
struct led_operations *get_board_led_opr(void)
{
return &board_demo_led_opr;
}
static struct platform_driver chip_demo_gpio_driver = {
/* 当设备平台与驱动平台匹配成功之后,会调用probe函数 */
.probe = chip_demo_gpio_probe,
/* 当移除设备的时候会调用remove函数,与probe函数对应 */
.remove = chip_demo_gpio_remove,
.driver = {
.name = "imx6ull_led",/* 需要与platform_device .name一致,这样才能够匹配 */
},
};
static int __init chip_demo_gpio_init(void)
{
int err;
err = platform_driver_register(&chip_demo_gpio_driver);
register_led_operations(&board_demo_led_opr);
return 0;
}
static void __exit chip_demo_gpio_exit(void)
{
platform_driver_unregister(&chip_demo_gpio_driver);
}
module_init(chip_demo_gpio_init);
module_exit(chip_demo_gpio_exit);
MODULE_LICENSE("GPL");
程序现象:
其他
如果想要打印内核信息
输入指令echo 7 4 1 7 > /proc/sys/kernel/printk
关闭内核打印信息指令
# echo 1 4 1 7 > /proc/sys/kernel/printk
或者
# echo 0 4 0 7 > /proc/sys/kernel/printk