驱动分离思想:
在传统的字符设备驱动思想中一个驱动程序对应一个硬件资源,在驱动入口函数中对资源进行配置,在file_operation中对各个硬件资源进行操作。这种思想使得内核中驱动代码变得庞大,为了节约内核空间驱动分离思想被提出,同时提高程序可移植性。
总线设备驱动模型包含的就是总线、设备、驱动三个部分。驱动分离是将驱动和设备代码进行分离。将模块拆分为device和driver。
在Linux内核中总线由bus_type结构体表示,在platform.c中有bus_type型platform_bus_type结构体。总线管理两个链表分别为platform_device和platform_driver链表用于管理设备和驱动。
struct bus_type:
struct bus_type platform_bus_type
platform_driver结构体以及结构体内重要元素
其中struct platform_ driver结构体如下:
在struct platform_driver中结构体*id_table表示该驱动能支持的设备,一个platform_driver可能支持一个或多个设备。
还有struct device_driver结构体,该结构体内通常将设备名等相关自由度较高的信息放置在该结构体中,该结构体如下:
platform_device结构体
struct platform_device结构体如下:
总线驱动模型原理详解
总线会管理两个链表,一个是驱动链表platform_driver,一个是设备链表platfoem_devie。
假设当前注册一个设备进入到设备链表中,注册完成之后就会与驱动链表中的驱动进行比较。根据platform_match函数源码
其中platform_device_id和函数platform_match_id如下,通过遍历比较驱动中的id_table中的各个结构体name和设备中的name看是否匹配
通常为了实现一个驱动和多个设备匹配,比较时采用platform_device中的override或者platform_driver中的id_tatble来实现:
platform_device中定义了driver_override字符串,比较优先级为driver_override > name。
1.首先是根据platform_device中driver_override这个字符串所表示的设备名与platform_driver中的device_driver中的设备名进行比较。
2.如果在platform_device中没有定义driver_override,则直接将platform_device中的name与platform_driver中的device_driver中的设备 名进行比较。
platform_device中未定义了driver_override字符串
1.platform_device中的name首先与platform_driver中结构体platform_device_id *id-table中的能支持的设备进行比较。
2.如果id_table为空或者各相与platform_device中的name不匹配则会与platform_driver中的device_driver driver中的name进行比较。
匹配成功就会调用驱动结构体platform_driver中的probe函数
可以看到probe函数的参数为设备结构体platform_device,在设备结构体中通常包含所有的资源的详细信息,因此总线设备驱动的一些对于硬件寄存器的相关操作通常在驱动结构体的probe函数指针所指函数中进行,该函数调用后通常会分配设置注册fileoperation结构体,并根据platform_device确定硬件相关寄存器使用ioreamp来映射寄存器等。反之,如果先对驱动进行注册,当驱动被注册进总线中的驱动链表中之后,就会对设备链表中的设备进行比较,后面的操作同上。
这样一来就实现了驱动和设备分离。将传统驱动框架中的file_operation中对于硬件寄存器的一些操作通过驱动结构体中probe函数指针指向的函数中进行probe函数将与之相匹配的设备结点作为参数。设备结构体中通常包含一些硬件资源相关的信息。
需要了解的是,当注册一个platform_drv之后就会与设备链表上的所有设备进行一一比较,但是当注册platform_driver时,通过__device_attach_driver去匹配驱动时,匹配成功之后就立即调用probe函数。
platform_device.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
static struct resource resources[] = {
{
.start = (3<<8)|(1), //引脚信息GPIO3 pin1
.flags = IORESOURCE_IRQ,
},
};
static void led_dev_release(struct device *dev)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
}
static struct platform_device led_dev = {
.name = "led",
.num_resources = ARRAY_SIZE(resources),
.resource = resources,
.dev = {
.release = led_dev_release,
},
};
static int __init led_dev_init(void)
{
int err;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_device_register(&led_dev); //注册设备放入链表中
return err;
}
static void __exit led_dev_exit(void)
{
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_device_unregister(&led_dev);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
pllatform_driver.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/platform_device.h>
#define LED_MAX_CNT 10
struct led_desc {
int pin; //引脚
int minor; //次设备号
};
static int major = 0;
static struct class *led_class;
static int g_ledcnt = 0;
static struct led_desc leds_desc[LED_MAX_CNT]; //放置设备资源的结构体数组
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
struct inode *inode = file_inode(file);
int minor = iminor(inode);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);
//对寄存器进行设置,控制硬件,相关的硬件信息置于全局变量中,可以通过全局变量进行获取
printk("set led pin 0x%x as %d\n", leds_desc[minor].pin, status);
return 1;
}
static int led_drv_open (struct inode *node, struct file *file)
{
int minor = iminor(node);
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
//初始化硬件寄存器(方向寄存器,置位寄存器,复位寄存器)
printk("init led pin 0x%x as output\n", leds_desc[minor].pin);
return 0;
}
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.write = led_drv_write,
};
//platform_driver 中的probe函数
static int led_probe(struct platform_device *pdev)
{
int minor;
int i = 0;
struct resource *res;
printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
res = platform_get_resource(pdev, IORESOURCE_IRQ, i++); //匹配成功之后利用定义的全局变量获得平台设备资源信息,以便于系统调用时使用相关的硬件寄存器引脚信息。
if (!res)
return -EINVAL;
//记录引脚
minor = g_ledcnt;
leds_desc[minor].pin = res->start; //将参数中的platform_pdev得到的硬件资源赋值到设备数组中
// 创建设备节点
device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor);
platform_set_drvdata(pdev, &leds_desc[minor]);
g_ledcnt++;
return 0;
}
static int led_remove(struct platform_device *pdev)
{
struct led_desc *led = platform_get_drvdata(pdev);
device_destroy(led_class, MKDEV(major, led->minor));
return 0;
}
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "led",
},
};
static int __init led_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
major = register_chrdev(0, "100ask_led", &led_drv); //注册驱动,也可以在probe函数中进行注册
led_class = class_create(THIS_MODULE, "100ask_led_class"); //device_create在probe函数中,卸载设备放在platform_driver中的remove中
err = PTR_ERR(led_class);
if (IS_ERR(led_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "led");
return -1;
}
err = platform_driver_register(&led_driver);
return err;
}
static void __exit led_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&led_driver);
class_destroy(led_class);
unregister_chrdev(major, "100ask_led");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
learned from :韦东山