前言:
- 字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI, LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。
- 在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。
- 应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。
- 应用程序使用到的函数在具体驱动程序中都有与之对应的函数。
字符设备驱动开发步骤
1. 驱动模块的加载和卸载以及驱动入口函数的进入
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数
1 /* 驱动入口函数 */
2 static int __init xxx_init(void) 3 { 4 /* 入口函数具体内容 */
5 return 0; 6 } 7
8 /* 驱动出口函数 */
9 static void __exit xxx_exit(void)
10 {
11 /* 出口函数具体内容 */
12 }
2 字符设备注册与注销
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
3 实现设备的具体操作函数
主要就是实现file结构体的操作函数
29 static struct file_operations test_fops = {
30 .owner = THIS_MODULE,
31 .open = chrtest_open,
32 .read = chrtest_read,
33 .write = chrtest_write,
34 .release = chrtest_release,
35 };
4 添加 LICENSE 和作者信息
- 其中 LICENSE 是必须添加的
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息
Linux 设备号
- 主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备
第 6 行,宏 MINORBITS 表示次设备号位数,一共是 20 位。
第 7 行,宏 MINORMASK 表示次设备号掩码。
第 9 行,宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。
第 10 行,宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。
第 11 行,宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。
printk 运行在内核态
- 8 个消息级别定义在文件 include/linux/kern_levels.h 里面
#define KERN_SOH "\001"
#define KERN_EMERG KERN_SOH "0" /* 紧急事件,一般是内核崩溃 */
#define KERN_ALERT KERN_SOH "1" /* 必须立即采取行动 */
#define KERN_CRIT KERN_SOH "2" /* 临界条件,比如严重的软件或硬件错误*/
#define KERN_ERR KERN_SOH "3" /* 错误状态,一般设备驱动程序中使用
KERN_ERR 报告硬件错误 */
#define KERN_WARNING KERN_SOH "4" /* 警告信息,不会对系统造成严重影响 */
#define KERN_NOTICE KERN_SOH "5" /* 有必要进行提示的一些信息 */
#define KERN_INFO KERN_SOH "6" /* 提示性的信息 */
#define KERN_DEBUG KERN_SOH "7" /* 调试信息 */
编写测试 APP
- 使用open、read、write、relase这些系统编程的函数打开/dev/xxx下的设备文件。
filename = argv[1];
/* 打开驱动文件 */
fd = open(filename, O_RDWR);
//打开自己创建的设备文件返回给应用层使用
if(fd < 0){
printf("Can't open file %s\r\n", filename);
return -1;
}
if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
retvalue = read(fd, readbuf, 50);
if(retvalue < 0){
printf("read file %s failed!\r\n", filename);
}else{
/* 读取成功,打印出读取成功的数据 */
printf("read data:%s\r\n",readbuf);
}
}
if(atoi(argv[2]) == 2){
/* 向设备驱动写数据 */
memcpy(writebuf, usrdata, sizeof(usrdata));
retvalue = write(fd, writebuf, 50);
if(retvalue < 0){
printf("write file %s failed!\r\n", filename);
}
}
/* 关闭设备 */
retvalue = close(fd);
if(retvalue < 0){
printf("Can't close file %s\r\n", filename);
return -1;
}