设备驱动
Linux设备驱动分为三个实体总线、设备(device)、驱动(driver), 平台总线将设备和驱动匹配。
2.6 内核加入了platform虚拟总线,platform机制将设备本身的资源注册进内核,由内核统一管理。
- 在系统注册任意一个驱动时, 都会寻找对应的设备。
- 当系统注册设备时,系统也会寻找对应的驱动进行匹配。
Linux设备
Linux将设备分为三大类: 字符设备、块设备、网络设备。
- 字符设备, 字符设备是能够像字节流一样被访问的设备。常见led,蜂鸣器,串口,键盘等等。
- 块设备, 块设备通过内存缓冲区访问,可以随机存取的设备。通过传输固定大小的数据(一般为512或1k)来访问设备。一般性的理解就是存储介质类的设备,常见字符设备有u盘,TF卡,eMMC,电脑硬盘,光盘等
- 网络设备,可以和其他主机交换数据的设备。常见的以太网设备,wifi,蓝牙等
cat /proc/devices
可以看到不同的设备都有编号。
主设备号 256, 从设备号 256。 故理论上就有256 * 256
个设备号。
主设备号、从设备号
在设备管理中,除了设备类型外,内核还需要一对称为主从设备号的参数, 才能唯一标识一个设备。
主设备号
用于标识驱动程序, 相同的主设备号使用相同的驱动程序。
从设备号
用于标识同一驱动程序的不同硬件。
Linux 驱动 和 设备 注册过程
Linux内核会要求每出现一个设备就要向总线汇报, 或者 出现一个驱动,也要向总线汇报, 或者叫做注册。
系统初始化的时候,会扫描链接了哪些设备,并为每一个设备建立一个struct platform_device的变量,然后将设备的变量插入到devices链表中。如下图所示:
系统初始化任意一个驱动程序的时候,也要准备一个struct platform_driver 结构变量, 然后将驱动的变量插入到drivers链表,如下图所示:
Linux总线为了将设备 与 驱动绑定,方便管理。 在注册设备,或者注册驱动时,都会寻找与之匹配的设备,而匹配由总线platform_match
函数完成。
注册设备的结构体platform_device, 注册驱动的结构体为platform_driver。设备和驱动的结构体成员name字段,相同则匹配。 并且匹配成功,则会调用platform_driver中的probe函数,注册驱动。
platform_match() 函数
1 | /** |
驱动 platform_driver
头文件:
结构体: platform_driver
位于 include/linux/platform_device.h 头文件
1
2
3
4
5
6
7
8
9
10
11
12
13extern int platform_driver_register(struct platform_driver *)
extern void platform_driver_unregister(struct 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;
}
驱动常见状态:
初始化
移除
休眠
复位
probe函数:
platform_match 函数匹配之后, 驱动调用的初始化函数
remove函数:
移除驱动函数
suspend函数
悬挂(休眠)驱动函数
resume函数:
休眠后恢复驱动
shudown 函数:
device_driver 数据结构
name 和 注册设备name 一致
owner 一般赋值THIS_MODULE
设备 platform_device
头文件:
结构体: platform_device
位于 include/linux/platform_device.h 头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15struct platform_device {
const char * name; //设备名称, 在 /sys/devices 会显示。
int id; //设备id,用于插入总线并且具有相同name的设备编号,如果只有一个设备则为-1
struct device dev; //结构体中内嵌的device结构体
u32 num_resources;//设备使用资源的数量
struct resource * resource;//设备使用的资源数组
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};第二个参数”id” 表示子设备编号, 一个设备如果有多个子设备,则需要写入子设备号数量, 只有一个则为 -1
定义于:
$kernel/drivers/base/platform.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);
/**
* platform_device_unregister - unregister a platform-level device
* @pdev: platform device we're unregistering
*
* Unregistration is done in 2 steps. First we release all resources
* and remove it from the subsystem, then we drop reference count by
* calling platform_device_put().
*/
void platform_device_unregister(struct platform_device *pdev)
{
platform_device_del(pdev);
platform_device_put(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_unregister);
注册设备到平台总线:
以三星itop-4412为例:
方法适用于源码:(静态注册)
1 | vi $itop-kernel/arch/arm/mach-exynos/mach-itop4412.c |
注: s3c_device_hello_ctl 即注册的platform_device 设备变量信息.
按照其他模块方式编写。 hello模块即可, 同时确保 宏定义 ,变量未重复。
对应在 $itop-kernel/drivers/char/Kconfig
添加对应模块选择配置
1 | config HEOLL_CTL |
即make menuconfig
所示如下图:
即保存退出, 重新编译 make zImage
, 烧写至开发板。
ls -ls /sys/devices/platform
即可看到新注册的hello驱动。
ls /sys/class/misc/
方法二:(动态注册)
1 | static void hello_dev_release(struct device *dev) |
方法三:(mknod)
1 | 创建特殊文件。 |
示例:
1 | mknod /dev/input/mouse0 c 12 32 |
注册驱动到平台总线
platform_driver_register函数
和platform_driver_unregister函数
用于注册 和 卸载驱动。platform_driver结构体
定义宏变量
DRIVER_NAME "hello_ctl"
(注: 与注册hello设备时的名称相同。)编写模块
module_init
module_exit
分别在其中注册 和 卸载驱动platform_driver_register
platform_driver_unregister
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27static int __init hello_init(void)
{
int DriverState = 0;
printk(KERN_WARNING "Welcome hello-module init\n");
DriverState = platform_driver_register(&hello_driver);
printk(DRIVER_NAME "state = \t", DriverState);
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_WARNING "byebye hello-module exit\n");
platform_driver_unregister(&hello_driver);
}
//注册 并初始化模块
module_init(hello_init)
//模块退出, 并关闭内存;
module_exit(hello_exit)
其中注册 和 卸载驱动函数的传入值为
platform_driver
类型, 即我们编写,将要实现的驱动程序。1
2
3
4
5
6
7
8
9
10
11
12
13struct platform_driver hello_driver =
{
.probe = &hello_probe,
.remove = &hello_remove,
.shutdown = &hello_shutdown,
.suspend = &hello_suspend,
.resume = &hello_resume,
.driver =
{
.name = DRIVER_NAME, //设备注册- 查看 $itop_kernel/
.owner = THIS_MODULE,
}
};其次定义并且实现 hello_driver; 此类型可查看 驱动 platform_driver
问题: probe_hello: disagrees about version of symbol module_layout
解决办法:
uname -r
查看系统内核版本, 同时查看 makefile 提供内核源码版本, 提供与系统内核版本对应一致的版本源码。
注意:
- 先注册platform_driver结构体, 使用probe 注册杂项设备, 设置两个 name.
或者
- 在 module_init 中直接使用 misc_register 注册杂项设备, 其效果相同。
如下:
设备节点
杂项设备的主设备号是10, 在任何Linux系统中它都是固定的。
杂项设备注册函数
1 | include/linux/miscdevice.h |
1 | extern int misc_register(struct miscdevice *misc); |
杂项设备注册函数; 一般在probe中调用,参数为struct miscdevice
。
1 | extern int misc_deregister(struct miscdevice *misc); |
杂项设备卸载函数;一般是在remove函数中用于卸载驱动。
1 | static int __init hello_init(void) |
ls /dev/
即可看到我们创建的杂项设备节点:
1 | 0 crw------- 1 root root 10, 55 Dec 20 00:00 hello_device_name |
即: 我们便可理解:设备节点,驱动名,设备名其实并无任何关系。
Linux file_operations结构体
file_operations 结构体的成员函数属于驱动设计的主体内容, 里面的函数和Linux系统给应用程序提供系统接口一一对应。
file_operations 结构体位于 include/linux/fs.h
, 定义为:
1 | /* |
1 |
|
导出模块 以及符号的相互作用
Linux2.6 内核的/proc/kallsyms
文件对应内核符号表,它记录了符号以及符号所在的内存地址,模块可以使用下列宏导到内核符号表中。
1 | EXPORT_SYMBOL(符号名) 任意模块均可 |
示例:
1 | #include <linux/module.h> /*module_init()*/ |
执行cat /proc/kallsyms | grep test
即可找到以下信息,表示模块确实加载到内核表中。
1 | f88c9008 r __ksymtab_sub_integar [export_symb] |
在其它模块中可以引用此符号
1 |
|
platform 机制:
总线注册机制
Kernel_init()
do_basic_setupo()
driver_init()
platform_bus_init()
bus_register()
注册系统platform总线. 内核启动初始化,自动维护。
添加设备阶段:
- Platform_device_register()
- Platform_device_add()
- pdev->dev.bus = &platform_bus_type && device_add()
此步骤操作具体可以查看 $kernel/arch/arm/mach-exynos/mach-itop4412.c
驱动注册阶段:
- Platform_driver_register()
- driver_register()
- bus_add_driver()
- driver_attach()
- bus_for_each_dev()
对在每个挂载虚拟的platform bus的设备做 driver_attach()-> driver_probe_device()
判断drv->bus->match() 是否执行成功, 此时通过指针执行platform_match() 对比name。
知识点:
- proc 目录是虚拟文件系统, 可以为Linux用户空间和内核空间提供交互, 它存在于内存中, 而不占用实际的flash或者硬盘空间。
- /proc/devices/ 里面的设备是加载驱动程序生成的, 即现有驱动创建信息。
- /dev/ 下的设备是通过创建设备节点生成的。用户通过此设备节点访问内核驱动。 即系统挂载的实际设备。
- /sys/devices/ 里面的设备都是系统初始化,
make zImage
编译后注册的新设备。 - /proc/misc 查看pc机Ubuntu系统的杂项设备。
- /sys/module 查看模块信息, 即lsmod信息