瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。
【公众号】迅为电子
【粉丝群】824412014(加群获取驱动文档+例程)
【视频观看】嵌入式学习之Linux驱动(第二篇 字符设备基础_全新升级)_基于RK3568
【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板
第9章 申请字符设备号实验
经过前面章节的学习,相信大家已经对驱动模块的基本框架、驱动模块传参等知识有了自己的认识,本章节开始就要进入字符设备的世界了。 字符设备是指在I/O传输过程中以字符为单位进行传输的设备,可以使用与普通文件相同的文件操作命令(打开、关闭、读、写等)对字符设备进行操作,是Linux驱动中最基本的一类设备驱动,例如最常见的LED、按键、IIC、SPI,LCD等都属于字符设备的范畴。要想对字符设备进行操作,需要通过设备号来对相应的设备进行查找,在本章节将对设备号相关知识进行讲解。
9.1 申请驱动设备号
9.1.1 设备号申请
在Linux系统中每一个设备都有相应的设备号,通过该设备号查找对应的设备,从而进行之后的文件操作。设备号有主设备号与次设备号之分,主设备号用来表示一个特定的驱动,次设备号用来管理下面的设备。
在Linux驱动中可以使用以下两种方法进行设备号的申请:
1.通过register_chrdev_region(dev_t from, unsigned count, const char *name)函数进行静态申请设备号。
2.通过alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)函数进行动态申请设备号。
两个函数在“内核源码/include/linux/fs.h”文件中引用(在编写驱动程序的时候要加入该文件的引用),如下(图9-1)所示:
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
extern int register_chrdev_region(dev_t, unsigned, const char *);
- 静态申请设备号:
函数原型:
register_chrdev_region(dev_t from, unsigned count, const char *name)
函数作用:
静态申请设备号,对指定好的设备号进行申请。
参数含义:
from: 自定义的dev_t类型设备号
count: 申请设备的数量
name: 申请的设备名称
函数返回值:
申请成功返回0,申请失败返回负数
2.动态申请设备号:
函数原型:
alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned
count,const char *name)函数作用:
动态申请设备号,内核会自动分配一个未使用的设备号,相较于静态申请设备号,动态申请会避免注册设备号相同引发冲突的问题。
参数含义:
dev *: 会将申请完成的设备号保存在dev变量中
baseminor: 次设备号可申请的最小值
count: 申请设备的数量
name: 申请的设备名称
函数返回值:
申请成功返回0,申请失败返回负数
对于申请设备号所用到的函数就讲解完成了,会在之后的测试小节对两个函数进行实际运用。
9.1.2 设备号类型
申请的设备号类型为dev_t ,在“内核源码/include/linux/types.h” 文件中定义如下(图9-2)所示:
typedef u32 __kernel_dev_t;
....
typedef __kernel_dev_t dev_t;
dev_t为u32类型,而u32 定义在文件 “内核源码/include/uapi/asm-generic/int-ll64.h”文件中,定义如下(图9-3):
typedef unsigned int __u32;
__u32为unsigned int类型,所以dev_t是一个无符号的32位整形类型。其中高12位表示主设备号,低20位表示次设备号。在“内核源码/include/linux/kdev_t.h”中提供了设备号相关的宏定义,如下(图9-4)所示:
#define MINORBITS 20 /*次设备号位数*/
#define MINORMASK ((1U << MINORBITS) - 1) /*次设备号掩码*/
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))/*dev右移20位得到主设备号*/
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) /*与次设备掩码与,得到次设备号*/
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))/*MKDEV宏将主设备号(ma)左移20位,然后与次设备号(mi)相与,得到设备号*/
在稍后的实验中不论是静态申请设备号还是动态申请设备号都会用到上述宏,例如在静态申请设备号时需要将指定的主设备号和从设备号通过MKDEV(ma,mi)宏进行设备号的转换,在动态申请设备号时可以用MAJOR(dev) 和MINOR(dev)宏将动态申请的设备号转化为主设备号和从设备号。
至此,关于设备号相关的知识就结束了,在下一小节中将对申请设备号实验代码进行编写。
9.2 实验程序的编写
本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\04。
本章节实验将编写Linux下申请字符设备号实例代码,如果在进行驱动模块加载时传入了major主设备号,则通过静态的方式进行设备号的申请,如果不传入任何参数进行驱动模块加载,则通过动态的方式进行设备号申请。
编写完成的dev_t.c代码如下(图9-5)所示
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
static int major;//定义静态加载方式时的主设备号参数major
static int minor;//定义静态加载方式时的次设备号参数minor
module_param(major,int,S_IRUGO);//通过驱动模块传参的方式传递主设备号参数major
module_param(minor,int,S_IRUGO);//通过驱动模块传参的方式传递次设备号参数minor
static dev_t dev_num;//定义dev_t类型(32位大小)的变量dev_num
static int __init dev_t_init(void)//驱动入口函数
{
int ret;//定义int类型的变量ret,用来判断函数返回值
/*以主设备号进行条件判断,即如果通过驱动传入了major参数则条件成立,进入以下分支*/
if(major){
dev_num = MKDEV(major,minor);//通过MKDEV函数将驱动传参的主设备号和次设备号转换成dev_t类型的设备号
printk("major is %d\n",major);
printk("minor is %d\n",minor);
ret = register_chrdev_region(dev_num,1,"chrdev_name");//通过静态方式进行设备号册
if(ret < 0){
printk("register_chrdev_region is error\n");
}
printk("register_chrdev_region is ok\n");
}
/*如果没有通过驱动传入major参数,则条件成立,进入以下分支*/
else{
ret = alloc_chrdev_region(&dev_num,0,1,"chrdev_num");//通过动态方式进行设备号注册
if(ret < 0){
printk("alloc_chrdev_region is error\n");
}
printk("alloc_chrdev_region is ok\n");
major=MAJOR(dev_num);//通过MAJOR()函数进行主设备号获取
minor=MINOR(dev_num);//通过MINOR()函数进行次设备号获取
printk("major is %d\n",major);
printk("minor is %d\n",minor);
}
return 0;
}
static void __exit dev_t_exit(void)//驱动出口函数
{
unregister_chrdev_region(dev_num,1);//释放字符驱动设备号
printk("module exit \n");
}
module_init(dev_t_init);//注册入口函数
module_exit(dev_t_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
MODULE_AUTHOR("topeet"); //作者信息
以上代码通过对传入参数的判断,从而进行设备号申请方式的选择,会在下一小节进行相应的驱动加载测试。
9.3 运行测试
9.3.1 编译驱动程序
在上一小节中的dev_t.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下(图9-6)所示:
export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += dev_c.o #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules #make操作
clean:
make -C $(KDIR) M=$(PWD) clean #make clean操作
对于Makefile的内容注释已在上图添加,保存退出之后,来到存放dev_t.c和Makefile文件目录下,如下图(图9-7)所示:
然后使用命令“make”进行驱动的编译,编译完成如下图(图9-8)所示:
编译完生成dev_t.ko目标文件,如下图(图9-9)所示:
至此我们的驱动模块就编译成功了,下面对驱动进行加载测试。
9.3.2 运行测试
开发板上电启动之后,使用以下命令加载dev_t.ko驱动,加载完成之后的打印信息如下图图(9-10)所示:
insmod dev_t.ko major=200 minor=0
可以看到传入的主设备号和次设备号都被打印了出来,“register_chrdev_region is ok”也被成功打印了证明设备注册成功了,然后使用以下命令进行注册设备号的查看,如下图(图9-11)所示:
cat /proc/devices
可以看到主设备号200的设备名为chrdev_name,和驱动程序中设置的相同,证明我们的设备号注册成功了,然后使用以下命令进行驱动的卸载,如下图(图9-12)所示:
rmmod dev_t.ko
下面进行动态申请设备号实验,使用以下命令进行驱动模块的加载,如下图(图9-13)所示:
insmod dev_t.ko
可以看到动态申请设备号成功了,主设备号为236,次设备号为0,然后使用以下命令进行注册设备号的查看,如下图(图9-14)所示:
cat /proc/devices
可以看到主设备号236的设备名为chrdev_name,和驱动程序中设置的相同,证明我们的设备号注册成功了,最后可以输入以下命令对驱动进行卸载,卸载完成如下图(图9-15)所示:
rmmod dev_t.ko