所学来自百问网
目录
1.驱动设计的思想:面向对象/分层/分离
1.1 面向对象
1.2 分层
1.3 分离
2.总线驱动设备模型
2.1 相关函数和结构体
2.1.1 platform_device
2.1.2 platform_driver
2.1.3 相关函数
2.2 platfrom_driver和platfrom_device的注册过程
2.3 匹配规则
2.3.1 先注册驱动
2.3.2 先注册设备
2.3.3 比较顺序
2.4 driver获取device数据
2.5 总线驱动设备示例代码
2.5.1 led.drv.c
2.5.2 led_dev.c
2.5.3 led.dev2.c
1.驱动设计的思想:面向对象/分层/分离
1.1 面向对象
面向对象就是指的根据某一类事物的属性,进行抽象。
字符设备驱动程序抽象出一个file_operations结构体;
file_operations:对我们驱动程序经常需要用到的open、read、write这些公共的函数或属性封装成一个结构体,由于不同的硬件有不同的操作方法,故对这部分函数或属性进行抽象,在编写字符设备驱动程序时,只需对该结构体进行实现即可
1.2 分层
上层实现硬件无关的操作,比如注册字符设备驱动:leddrv.c
下层实现硬件相关的操作,比如board_A.c实现单板A的LED操作
如:
1.3 分离
当我们想对分层后的代码进行修改时,比如驱动不同的引脚的led灯,我们就要重新编写初始化和控制代码,这样显然很麻烦;由于每一款芯片的GPIO的操作都是类似的,故我们可以针对该芯片书写一个硬件通用代码
2.总线驱动设备模型
platform_driver结构体负责对硬件的寄存器代码的编写、初始化file_operation、映射寄存器等对硬件设备的初始化操作,即驱动
platform_device结构体负责对硬件功能的实现,即资源
在linux内核中,platform_bus_type
是platform总线的数据结构,它是一个虚拟的总线,总线负责设备和驱动的匹配和绑定,用于将platform_device
和platform_driver
连接起来
2.1 相关函数和结构体
2.1.1 platform_device
platform_device包含了对描述设备所需的各种信息,如设备的名称、ID、资源(如 IO 端口、内存地址、中断号等)以及指向设备特定数据的指针。
部分字段的含义:
struct platform_device {
const char *name; // 设备的名称,用于与驱动程序进行匹配
int id; // 设备的 ID 号,用于区分具有相同名称的不同设备实例。
struct device dev; // 一个 device 结构体,表示设备在内核设备模型中的抽象。
u32 num_resources; // 设备使用的资源数量
struct resource *resource; // 资源描述符数组
char *driver_override; // 一个指向字符数组的指针,用于指定要强制匹配的驱动程序名称
};
2.1.2 platform_driver
platform_driver提供了注册和注销设备驱动程序的接口,负责对硬件驱动程序的书写
部分字段的含义:
struct platform_driver {
int (*probe)(struct platform_device *); //函数指针,指向驱动程序的 probe 函数 用于初始化设备并分配必要的资源
int (*remove)(struct platform_device *); //函数指针,指向驱动程序的 remove 函数 释放之前分配的资源并执行清理操作
struct device_driver driver; //device_driver 结构体,包含了驱动程序的通用信息,如驱动程序的名称、所属模块等
const struct platform_device_id *id_table; //指向 platform_device_id 结构体数组的指针,用于基于设备 ID 的匹配
};
2.1.3 相关函数
-
用于向内核注册一个平台驱动程序
-
int platform_driver_register(struct platform_driver *drv);
-
drv
是一个指向platform_driver
结构体的指针,该结构体包含了驱动程序的信息和函数指针 -
返回值:注册成功,返回 0;如果失败,返回非零错误码
-
-
从内核中注销一个已经注册的平台驱动程序
-
void platform_driver_unregister(struct platform_driver *drv);
-
drv
是一个指向要注销的平台驱动程序结构体的指针
-
-
用于向内核注册一个平台设备
-
struct platform_device *platform_device_register(struct platform_device *pdev);
-
pdev
是一个指向platform_device
结构体的指针,该结构体包含了设备的信息和资源 -
返回值:注册成功,返回
pdev
指针本身;如果失败,返回NULL
。
-
-
用于从内核中注销一个已经注册的平台设备
-
void platform_device_unregister(struct platform_device *pdev);
-
pdev
是一个指向要注销的平台设备结构体的指针。
-
-
从给定的平台设备(
platform_device
)中获取指定类型的资源-
struct resource *platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
-
dev
:指向要查询资源的平台设备的指针。 -
type
:要查询的资源类型,如IORESOURCE_MEM
(内存资源)、IORESOURCE_IRQ
(中断资源)等。 -
num
:如果同一类型有多个资源,此参数指定要查询的资源的编号(通常从0开始) -
返回值:函数返回一个指向
struct resource
结构体的指针,该结构体包含了资源的详细信息,如开始地址、结束地址、资源名称等。如果找不到指定的资源,则返回NULL
-
-
将私有数据与平台设备(
platform_device
)相关联-
void platform_set_drvdata(struct platform_device *pdev, void *data);
-
pdev
:指向要设置私有数据的平台设备的指针。 -
data
:指向要与平台设备关联的私有数据的指针。这个数据可以是任何类型,但通常是一个指向设备特定数据结构的指针。
-
-
用于获取与平台设备(
platform_device
)相关联的私有数据-
void *platform_get_drvdata(const struct platform_device *pdev);
-
pdev
:指向要获取私有数据的平台设备的指针。 -
返回值:函数返回一个指向之前通过
platform_set_drvdata
设置的私有数据的指针。如果没有设置私有数据,则返回NULL。
-
2.2 platfrom_driver和platfrom_device的注册过程
图解:即将注册的设备或驱动添加到总线的链表中,依次遍历链表,去匹配对应的驱动或设备,若匹配则调用相应的函数
2.3 匹配规则
2.3.1 先注册驱动
图解:总线的drvier链表已经构造并注册了hello_drv,系统会对device链表中的设备一一比较,匹配得到则直接调用,若无,系统会直接返回,则需要去构造并注册hello_dev,构造完成后,系统会对drvier链表进行一一比较,找到则直接调用
2.3.2 先注册设备
图解:总线的device链表已经构造并注册了hello_dev,系统会对drvier链表中的设备一一比较,匹配得到则直接调用,若无,系统会直接返回,则需要去构造并注册hello_drv,构造完成后,系统会对device链表进行一一比较,找到则直接调用
2.3.3 比较顺序
最先比较
platform_device.driver_override 和 platform_driver.driver.name 可以设置platform_device 的driver_override,强制选择某个 platform_driver。
然后比较
platform_device.name和platform_driver.id_table[i].name platform_driver.id_table 是“platform_device_id”指针,表示该 drv 支持若干个device,它里面列出了各个device的{.name, .driver_data},其中的“name”表示该 drv 支持的设备的名字,driver_data是些提供给该device的私有数据。
最后比较
platform_device.name 和 platform_driver.driver.name platform_driver.id_table 可能为空,这时可以根据platform_driver.driver.name来寻找同名的platform_device。
2.4 driver获取device数据
通过调用
resource结构体下的参数获得数据
例如
2.5 总线驱动设备示例代码
2.5.1 led.drv.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>
#include <linux/mod_devicetable.h>
#define LED_MAX_CNT 10
// 结构体记录led信息
struct led_desc {
int pin;
int minor;
};
/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
static int g_ledcnt = 0;
static struct led_desc leds_desc[LED_MAX_CNT];
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
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);
/* 根据次设备号和status控制LED */
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__);
/* 根据次设备号初始化LED */
printk("init led pin 0x%x as output\n", leds_desc[minor].pin);
return 0;
}
/* 2. 定义自己的file_operations结构体 */
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.write = led_drv_write,
};
/* B.1 实现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;
leds_desc[minor].minor = minor;
/* 7.2 辅助信息 */
/* 创建设备节点 */
device_create(led_class, NULL, MKDEV(major, minor), NULL, "100ask_led%d", minor); /* /dev/100ask_led0,1,... */
platform_set_drvdata(pdev, &leds_desc[minor]);
g_ledcnt++;
return 0;
}
/* B.2 实现platform_driver的remove函数 */
static int led_remove(struct platform_device *pdev)
{
struct led_desc *led = platform_get_drvdata(pdev);
device_destroy(led_class, MKDEV(major, led->minor)); /* /dev/100ask_led0,1,... */
return 0;
}
// 记录可与driver相匹配的device设备
static const struct platform_device_id led_id_table[] = {
{"100ask_led", 1},
{"100ask_led_3", 2},
{"100ask_led_4", 3},
{ },
};
/* A. 实现platform_driver */
static struct platform_driver led_driver = {
.probe = led_probe,
.remove = led_remove,
.driver = {
.name = "100ask_led",
},
.id_table = led_id_table, // 获取匹配的device
};
/* 4. 把file_operations结构体告诉内核:注册驱动程序register_chrdev */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
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); /* /dev/led */
/* 7.1 辅助信息 */
led_class = class_create(THIS_MODULE, "100ask_led_class");
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;
}
/* C. 注册platform_driver */
err = platform_driver_register(&led_driver);
return err;
}
/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* C. 反注册platform_driver */
platform_driver_unregister(&led_driver);
class_destroy(led_class);
unregister_chrdev(major, "100ask_led");
}
/* 7. 其他完善:提供设备信息,自动创建设备节点 */
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
2.5.2 led_dev.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>
// 设置硬件资源 用于driver获取
static struct resource resources[] = {
{
.start = (3<<8)|(1),
.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 = "100ask_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");
2.5.3 led.dev2.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>
// 设置硬件资源 用于driver获取
static struct resource resources[] = {
{
.start = (3<<8)|(2),
.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 = "100ask_led_second",
.num_resources = ARRAY_SIZE(resources),
.resource = resources,
.dev = {
.release = led_dev_release,
},
.driver_override = "100ask_led", // 强制匹配
};
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");