声明
以下都是我刚开始看驱动视频的个人强行解读,如果有误请指出,共同进步。
本节目标
- 注册驱动
正文
上一篇我们注册了设备并编译进了内核,接下来我们再注册一个驱动试试
设备有一个结构体,同样的,驱动也有个结构体
首先我们打开头文件,看一下驱动注册的结构体长啥样
vim include/linux/platform_device.h
打开看到的就是上一节用到的注册设备的结构体,然而本节是注册驱动,所以我们搜索platform_driver并查看
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;
};
extern int platform_driver_register(struct platform_driver *);
extern void platform_driver_unregister(struct platform_driver *);
对于驱动,有几种常见状态:初始化、移除、休眠、复位,所以结构体内定义了很多的函数(英文翻译很直白了…)
我们注册了设备,也注册了驱动,那么linux会调用platform_match进行匹配,匹配完成之后会调用probe函数进行初始化,这些下面配置结构体再说。
除了结构体,还发现下面有两行,看名字就能看出来是注册和卸载驱动的函数,也很直白了…
和注册设备不一样的地方在于,注册设备的结构体里就是关于设备的一些信息的参数。
但是注册驱动的结构体里包含的是关于驱动的函数,而参数则又封装了一个结构体device_driver,这个结构体内包含了驱动里面的参数。
这些参数也有name这样的参数,目的是根据name让设备和驱动匹配。
我们打开路径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 */
enum probe_type probe_type;
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;
void (*coredump) (struct device *dev);
struct driver_private *p;
};
这个结构体内我们主要关注的是name和owner,name用于匹配设备,owner一般填THIS_MODULE,表示应用于此模块。
看到device设备里也有类似platform_device的函数,比如关闭,休眠。因为我们注册的是platform的平台字符设备,所以直接再platform里定义就行了。
因为设备只有一个,所以id上一节是-1,那么驱动通过name就可以直接找到设备,从而匹配。
对于注册驱动的步骤
- 编写注册驱动用到的结构体
- 把结构体传入注册驱动的函数进行注册
下面开始注册驱动
1. 先掏出模块的模板
本节是以模块的方式去注册,所以程序框架先写一个模块。
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");
static int mryang_init(void)
{
printk(KERN_EMERG "HELLO MrYang\n");
return 0;
}
static void mryang_exit(void)
{
printk(KERN_EMERG "Bye MrYang\n");
}
module_init(mryang_init);
module_exit(mryang_exit);
因为插入模块会调用mryang_init(),所以我们直接在此函数里去注册驱动就行了。
2.编写注册驱动用到的结构体
我们先要包含platform平台驱动的头文件
#include <linux/platform_device.h>
接下来就是定义驱动的结构体:
上面我们查看了linux是如何定义驱动的结构体,里面有probe,remove函数等等,我们只是学习+体验,所以只需要先编写加载和卸载两个结构体成员函数就可以了。
// 下面是结构体内的原型
// int (*probe)(struct platform_device *);
// int (*remove)(struct platform_device *);
// 这是我们编写的函数
int mryang_probe(struct platform_device *pdv)
{
return 0;
}
int mryang_remove(struct platform_device *pdv)
{
return 0;
}
结构体里的参数probe,remove函数已经定义完了,然后我们定义结构体时,把参数指向我们定义的probe和remove函数即可。
struct platform_driver mryang_driver = {
.probe = mryang_probe,
.remove = mryang_remove,
.driver = {
.name = "mryang_ctl", // 像这种变量最好定义一个宏定义在开头,别问我为啥
.owner = THIS_MODULE,
}
};
3. 把结构体传入注册驱动的函数进行注册
现在我们驱动结构体也写好了,那么就可以注册了,咋注册,肯定是加载模块的时候注册,卸载模块的时候卸载啊。
所以我们在mryang_init和mryang_exit里分别添加注册和卸载驱动的函数(要是不知道函数叫什么,可以上翻查看结构体的时候有说),参数自然就是我们定义的结构体mryang_driver。
// 写在mryang_init里
platform_driver_register(&mryang_driver);
// 写在mryang_exit里
platform_driver_unregister(&mryang_driver);
完整的代码
#include <linux/init.h>
#include <linux/module.h>
// platform相关头文件(设备、驱动的结构体和函数)
#include <linux/platform_device.h>
// 驱动名(与设备名一致)
#define DRIVER_NAME "mryang_ctl"
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");
/* 加载驱动,设备、驱动匹配成功,则调用probe()函数 */
int mryang_probe(struct platform_device *pdv)
{
printk(KERN_EMERG "probe!\n");
return 0;
}
/* 卸载驱动调用remove()函数 */
int mryang_remove(struct platform_device *pdv)
{
printk(KERN_EMERG "remove!\n");
return 0;
}
/* 驱动结构体 */
struct platform_driver mryang_driver = {
.probe = mryang_probe,
.remove = mryang_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
}
};
/* 加载模块 */
static int mryang_init(void)
{
printk(KERN_EMERG "HELLO MrYang\n");
platform_driver_register(&mryang_driver);
return 0;
}
/* 卸载模块 */
static void mryang_exit(void)
{
printk(KERN_EMERG "Bye MrYang\n");
platform_driver_unregister(&mryang_driver);
}
module_init(mryang_init);
module_exit(mryang_exit);
Makefile文件
#!/bin/bash
obj-m += probe_linux_module.o
KDIR := /root/iTop4412_Kernel_3.0
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o
其实看到Makefile基本上没变,只是名字改成了probe_linux_module。以后就不再叙述Makefile了。
编译驱动!
make
并非编译linux内核,只是编译驱动,不知道咋整详见第一节…
加载、卸载驱动
[root@iTOP-4412]# insmod probe_linux_module.ko
[ 295.708430] HELLO MrYang
[ 295.709815] probe!
[root@iTOP-4412]# rmmod probe_linux_module
[ 297.924409] Bye MrYang
[ 297.925330] remove!
总结
其实也是融合了之前的所有内容
- 加载模块调用了module_init(mryang_init);
其中的mryang_init是我们自定义的函数,里面输出了HELLO MrYang
- 紧接着还调用了注册驱动的函数platform_driver_register(&mryang_driver);
mryang_driver就是我们定义的驱动的结构体,里面包含了
probe和remove函数。
- 当我们注册驱动的时候,linux去匹配设备,发现设备也注册了,就调用probe函数
结构体内的probe指向了我们定义的mryang_probe(struct platform_device *pdv)函数
函数的内容就是输出probe。
这就是完整的流程,你可能还会问,我们啥时候注册设备了?傻呀,看上一节注册设备的笔记,我们在编译linux内核的时候已经勾选把注册设备也编译进内核了。
至此,其实已经可以好好思考整个流程了。