1、 驱动背景
Ubuntu 操作系统、 学习 ARM 裸机、 学习系统移植 就是为了学习 Linux驱动开发
驱动分为 字符设备驱动 块设备驱动 网络设备驱动。
字符设备
最多,从最简单的
点灯
到
I2C
、
SPI、LCD
.
音频
等都属于
字符设备驱动
的类型。
就是因为其复杂所以
半导体厂商
一般都给我们编写好了,大多数情况下都是
直接可以使
用的
。
在
Linux
中一切皆为文件,
驱动加载成功
以后会在 “
/dev
” 目录下生成一个相应的文件,应
用程序通过对这个名为
“/dev/xxx
”
(xxx
是具体的驱动文件名字
)
的文件进行
相应的操作
即可实
现对硬件的操作。
每一 个统调用
,在驱动中都有与之对应的一个
驱动函数
,在 Linux
内核文件
include/linux/fs.h
中
有个叫做
file_operations
的结构体,此结构体就是
Linux
内核驱动操作函数集合.
在 Linux 驱动开发中 肯定也是要初始化相应的外设寄存器
,这个是毫无疑问的。
在 Linux 驱动开发中 肯定也是要初始化相应的外设寄存器
,这个是毫无疑问的。
在 Linux 驱动开发中 肯定也是要初始化相应的外设寄存器
,这个是毫无疑问的。
重要的事情说3遍! 重要的事情说3遍! 重要的事情说3遍!
只是在
Linux 驱动开发中
我们需要按照
kernel
规定的
框架
来编写
驱动 (设备文件操作 函数指针)
,所以说学 Linux
驱动开发
重点
是学习
其驱动框架
。
2. 驱动 的加载和卸载
第一种就是将驱动编译进
Linux 内核中 Linux 内核启动的时候就会自动运行驱动程序
第二种就是将驱动编译成模块
(Linux
下模块扩展名为
.ko)
,在 Linux 内核启动以后使用“
insmod
”命令加载驱动模块。
在调试驱动的时候一般都选择将其编译 为模块,这样我们修改驱动以后只需要
编译一下驱动代码
即可,
不需要编译整个 Linux 代码
。
module_init(xxx_init);
//
注册模块加载函数
module_exit(xxx_exit);
//
注册模块卸载函数
当使用
“insmod”
命令加载驱动的时候,
xxx_init
这个函数就会被调用。
rmmod”命令
卸载具体驱动的时候
xxx_exit
函数就会被调用。
3. 字符设备注册 和 注销
当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模 块的时候也需要注销掉字符设备。
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)
4. 传统的驱动编程框架
static struct
file_operations test_fops
;
static int
__init xxx_init
(
void
) {
int
retvalue
=
0
;
retvalue
=
register_chrdev
(
200
,
"chrtest"
, &
test_fops
);
if
(
retvalue
<
0
){ }
return 0;
}
static void
__exit xxx_exit
(
void
) {
unregister_chrdev
(
200
,
"chrtest"
);
}
module_init(xxx_init);
module_exit(xxx_exit);
MODULE_LICENSE(“GPL”)
//
添加模块
LICENSE
信息
MODULE_AUTHOR("zhengyang")
//
添加模块作者信息
register_chrdev 接口调用后 可以在cat proc/devices 可以看到注册的设备
5. 终极核心 就是 实现 file_operations 这个结构体
file_operations
结构体就是设备的具体操作函数
open
、
release
、
read
和
write
等具体的设备操作函数。
static int
chrtest_open
(
struct
inode
*
inode
,
struct
file
*
filp
) {}
static
ssize_t chrtest_read
(
struct
file
*
filp
,
char
__user
*
buf
,
size_t
cnt
,
loff_t
*
offt
){}
static
ssize_t chrtest_write
(
struct
file
*
filp
,
const char
__user
*
buf
,
size_t
cnt
,
loff_t
*
offt
){}
static int
chrtest_release
(
struct
inode
*
inode
,
struct
file
*
filp
) {}
static struct
file_operations test_fops
= {
.
owner
=
THIS_MODULE
,
.
open
=
chrtest_open
,
.
read
=
chrtest_read
,
.
write
=
chrtest_write
,
.
release
=
chrtest_release
,
};
6.Linux设备号
为了 方便管理,
Linux 中每个设备
都有一个设备号,设备号由
主设备号
和
次设备
号两部分 组成,
主设备号
表示
某一个具体的驱动
,次设备号表示
使用这个驱动的各个设备
。
typedef
__u32
dev_t
定义在文件
include/linux/types.h
里面
高 12 位为
主设备号,
低 20 位为
次设备号
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
直接注册设备号register_chrdev
动态分配注册设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
7. 驱动 Makefile 编写
KERNELDIR
:=
/home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx
rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH
:=
$(shell pwd)
obj-m
:=
chrdevbase.o
build
:
kernel_modules
kernel_modules
:
$(MAKE)
-C
$(KERNELDIR) M
=
$(CURRENT_PATH)
modules
clean
:
$(MAKE)
-C
$(KERNELDIR) M
=
$(CURRENT_PATH)
clean
KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径
8.创建设备节点文件
驱动加载成功需要在
/dev
目录下创建一个与之对应的设备节点文件,应用程序就是通过操
作这个设备节点文件来完成对具体设备的操作。
mknod /dev/chrdevbase c 200 0
insmod rmmod
“
ls /dev/chrdevbase -l
”
9. 驱动程序的测试手法
应用层app程序: open close read write 等函数应用。
./chrdevbaseApp /dev/chrdevbase 1
./chrdevbaseApp /dev/chrdevbase 2
字符设备驱动的开发框架以及测试方法