声明
以下都是我刚开始看驱动视频的个人强行解读,如果有误请指出,共同进步。
本节目标
- 以模块方式注册设备
- 驱动获取设备信息
正文
注册设备,注册驱动,当linux匹配设备和驱动成功,则调用probe函数(调用的probe()函数是自己定义的,linux只是指定了匹配成功就执行某一个函数,就是一个流程)。
先回想一下编译进内核时,注册设备的流程:
-
我们先是在Kconfig里仿照LEDS_CTL编写了一个MRYANG_CTL(图形界面才会有选项)(笔记2的内容)。
-
因为定义了MRYANG_CTL,所以 .config文件才会出现宏定义CONFIG_MRYANG_CTL(笔记2的内容)。
-
CONFIG_MRYANG_CTL定义了,我们还用他做开关,决定是否启动我们写的platform_device类型的结构体,同样作为开关,还决定了是否把s3c_device_mryang_ctl这个结构体传参给linux。
-
这样注册的设备就被直接编译进linux了,最后编译出来的zImage文件,就直接包含了我们的设备。
再回想一下注册驱动的流程:
-
我们选择以模块module的方式进行注册、卸载驱动(笔记1的内容)
-
模块加载的时候我们用API函数去注册驱动。
-
模块卸载的时候我们用API函数去卸载驱动。
我们其实会发现,两者的区别其实在于,编译进内核了,则你一直在内核里。以模块的方式,可以想用就插入内核,不想用就卸载。后者的优势在于灵活。如果什么都编译进内核,那么内核的程序会非常大,对于内核而言很臃肿。除了必备的东西,其实都可以以模块的方式插入,既不影响内核,还很灵活。
1.以模块方式注册设备
注册驱动可以以模块的方式注册,注册设备也可以。
类似注册驱动的API函数,名字稍微一改就行了,注册驱动是platform_driver_register()
,那么注册设备就是platform_device_register()
,同理卸载也是,他们的函数都在 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;
};
// 注册设备的API函数
extern int platform_device_register(struct platform_device *);
// 卸载设备的API函数
extern void platform_device_unregister(struct platform_device *);
这些东西其实在注册设备的时候就有看过,翻出来看一下其实是一样的。所以我们直接上代码
// 头文件
#include <linux/platform_device.h>
// 结构体内需要调用的release函数
static void mryang_module_release(struct device *dev)
{
printk(KERN_EMERG "MrYang module release!\n");
}
// 结构体
struct platform_device mryang_device = {
.name = "mryang_module_ctl",
.id = -1,
.dev = {
.release = mryang_module_release, // 关闭调用的函数
}
};
// 注册设备的API函数,放入加载模块的函数里
platform_device_register(&mryang_device);
// 卸载设备的API函数,放入卸载模块的函数里
platform_device_unregister(&mryang_device);
相比编译进内核,结构体内为什么要有一个release参数?因为platform_device_unregister()函数在卸载时会调用release函数关闭设备(可以查看源代码,点击 讲解 可以看一下别人分析的源代码)。
完整的代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");
static void mryang_module_release(struct device *dev)
{
printk(KERN_EMERG "MrYang module release!\n");
}
struct platform_device mryang_device = {
.name = "mryang_module_ctl",
.id = -1,
.dev = {
.release = mryang_module_release,
}
};
static int mryang_init(void)
{
printk(KERN_EMERG "HELLO MrYang\n");
// platform_driver_register(&mryang_driver);
platform_device_register(&mryang_device);
return 0;
}
static void mryang_exit(void)
{
printk(KERN_EMERG "Bye MrYang\n");
// platform_driver_register(&mryang_driver);
platform_device_unregister(&mryang_device);
}
module_init(mryang_init);
module_exit(mryang_exit);
make编译出来的文件我们在板子上试试
[root@iTOP-4412]# insmod mryang_module_device.ko
[ 108.046209] HELLO MrYang
[root@iTOP-4412]# ls /sys/devices/platform/ | grep mr
mryang_ctl
mryang_module_ctl
[root@iTOP-4412]# rmmod mryang_module_device
[ 112.745444] Bye MrYang
[ 112.746882] MrYang module release!
可以看到 输出确实符合我们的预期,卸载设备的时候调用release函数,通过使用命令
ls /sys/devices/platform/
也确实看到了我们的模块,mryang_ctl是当初编译进内核的,mryang_module_ctl是我们刚刚insmod的。
以上就完成了我们这次的以模块的形式来注册设备(我们之前是直接把设备编译进内核)
2.驱动获取设备信息
我们无论是注册驱动(调用probe()函数)还是注册设备(调用release()函数),函数的参数都有一个结构体,但是我们在函数里从来都没有用过。
当设备和驱动匹配之后,他们各自有各自的参数,而交流的渠道则是通过结构体传参。
我们以模块加载的mryang_module_ctl这样一个设备为例。
我们的预期是打印 设备的 name,id,以及调用设备的release() 函数,前两个没啥问题,后者的话我们要注意到release()的函数原型
void (*release)(struct device *dev);
传入的参数是 结构体 device,是我们传入的结构体的子结构体,所以我们调用的话,也一定是传入结构体的结构体。
下面直接上全部代码,主要看probe()函数(注意驱动的名字,要对应哪个设备就改成哪个设备的名字)
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");
int mryang_probe(struct platform_device *pdv)
{
printk(KERN_EMERG "probe!\n");
// 打印 name
printk(KERN_EMERG "pdv->name = %s\n", pdv->name);
// 打印 id
printk(KERN_EMERG "pdv->id = %d\n", pdv->id);
// 调用设备的release函数,传入的参数是一个指针,以及参数是platform结构体的子结构体device
pdv->dev.release(&pdv->dev);
return 0;
}
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 = "mryang_module_ctl",
.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);
输出如下,注意我们刚刚设备的模块已经insmod了,所以我这里直接insmod驱动即可
[root@iTOP-4412]# insmod probe_linux_module.ko
[ 37.872810] HELLO MrYang
[ 37.874240] probe!
[ 37.876258] pdv->name = mryang_module_ctl
[ 37.880008] pdv->id = -1
[ 37.890154] MrYang module release!
输出符合预期
- 首先加载模块输出HELLO MrYang
- 调用probe打印 probe!
- 接着输出设备的name和id
- 最后调用设备的release()函数(我们之前写的)
以上就是全部内容
本节我们做了两个事
一个以模块的方式来注册设备(而非直接编译进内核)
一个是驱动通过结构体参数来读取设备的信息