全系列传送门
Linux嵌入式驱动开发01——第一个驱动Hello World(附源码)
Linux嵌入式驱动开发06——第一个相对完整的驱动实践编写
Linux嵌入式驱动开发07——GPIO驱动过程记录(飞凌开发板)
Linux嵌入式驱动开发11——平台总线模型修改为设备树实例
Linux嵌入式驱动开发12——pinctl和gpio子系统实践操作
Linux嵌入式驱动开发13——ioctl接口(gpio控制使用)
Linux嵌入式驱动开发14——中断的原理以及按键中断的实现(tasklet中断下文)
驱动模块传参
传递普通的参数 char int
使用的函数
module_param(name,type,perm);
param_test.c 代码
#include <linux/init.h>
#include <linux/module.h>
static int my_param;
/* module_param(name,type,perm);
* name:要传递进去的参数的名称
* type:类型
* perm:参数读写的权限 S_IRUSR 400
*/
module_param(my_param, int, S_IRUSR);
static int hello_init(void)
{
printk("my_param = %d\n", my_param); // 在内核中无法使用c语言库,所以不用printf
return 0;
}
static void hello_exit(void)
{
printk("see you again!!!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
核心就是这个module_param
函数的使用
static int my_param;
/* module_param(name,type,perm);
* name:要传递进去的参数的名称
* type:类型
* perm:参数读写的权限 S_IRUSR 400
*/
module_param(my_param, int, S_IRUSR);
编译之后,发送到开发板,然后安装,并且传递参数my_param到驱动模块中
模块安装在位置我们可以通过
/sys/module
可以看到我们的param_test模块
然后查看里面的文件权限,就可以和我们之前的S_IRUSR 400对应上了
传递数组
函数
module_param_array(name, type, &nump, perm);
param_test.c 代码
#include <linux/init.h>
#include <linux/module.h>
static int my_param;
static int my_list[5];
static int count;
/* module_param(name,type,perm);
* name:要传递进去的参数的名称
* type:类型
* perm:参数读写的权限 S_IRUSR 400
*/
module_param(my_param, int, S_IRUSR);
/* module_param_array(name, type, &nump, perm);
* name:要传递进去的参数的名称
* type:类型
* nump:实际传入的个数
* perm:参数读写的权限
*/
module_param_array(my_list, int, &count, S_IRUSR);
static int hello_init(void)
{
int i;
for(i=0; i<count; i++){
printk("my_list[ %d ] = %d\n", i, my_list[i]);
}
printk("count = %d\n", count);
printk("my_param = %d\n", my_param); // 在内核中无法使用c语言库,所以不用printf
return 0;
}
static void hello_exit(void)
{
printk("see you again!!!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
可以看到运行结果,符合我们的设计
在开发板上运行
insmod param_test.ko my_list=1,2,3,4,5
如果传入数据多出5个,那么报错
字符设备和杂项设备的区别
杂项设备的主设备号是固定的,固定为10,那么我们要学习的字符类设备就需要自己或者系统来给我们分配了。
杂项设备可以自动生成设备节点,字符设备需要我们自己生成设备节点
申请字符类设备号
第一种 静态分配设备号
linux-4.1.15/include/linux
打开fs.h
文件
使用的就是这个函数
register_chrdev_region(dev_t, unsigned, const char *);
在使用函数中,一定要注意使用的设备号的没有被使用过的
参数:
第一个:设备号的起始值
第二个:次设备号的个数
第三个:设备的名称
返回值:成功返回0,失败返回负数
cat /proc/devices
dev_t类型
dev_t是用来保存设备号的,是一个32位的数
typeded _u32_kernel_dev_t;
typeded _kernel_dev_t dev_t;
其中
高 12位 保存主设备号
低 12位 保存次设备号
linux提供了一些宏定义来操作设备号
第一个MINORBITS
是次设备号的位数,一共二十位
第二个MINORMASK
是次设备号的掩码
第三个MAJOR(dev)
是在dev_t里面获取我们的主设备号
第四个MINOR(dev)
是在dev_t里面获取我们的次设备号
第五个MKDEV(ma, mi)
是将我们的主设备号和次设备号组成一个dev_t类型,第一个参数是主设备号,第二个参数是次设备号
第二种 动态分配
alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
参数
第一个dev_t *:保存生成的设备号
第二个unsigned:我们请求的第一个此设备号,通常是0
第三个unsigned:连续申请的设备号个数
第四个const char *:设备名称
返回值:成功返回0,失败返回负数
使用动态分配会优先使用255到234
注销设备号
使用的是
unregister_chrdev_region(dev_t from, unsigned count)
参数
第一个dev_t from:分配设备号的起始地址
第二个unsigned count:申请的连续设备号数量
代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#define DEVICE_NUMBER 1 // 申请设备号的数量
#define DEVICE_SNAME "schrdev" // 静态注册 名称
#define DEVICE_ANAME "achrdev" // 动态注册 名称
#define DEVICE_MINOR_NUMBER 0 // 次设备号起始地址,通常是0
static int major_num,minor_num;
/* module_param(name,type,perm);
* name:要传递进去的参数的名称
* type:类型
* perm:参数读写的权限 S_IRUSR 400
*/
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);
static int hello_init(void)
{
dev_t dev_num; // 保存设备号
int ret;
if(major_num){ // 如果major_num被手动设置,就使用静态注册设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
dev_num = MKDEV(major_num, minor_num);
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if(ret < 0){ // ret<0 注册失败
printk("register_chrdev_region ERROR!!!\n");
}else{
printk("register_chrdev_region OK!!!\n");
}
}else{ // major_num没有传入,所以动态申请设备号
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if(ret < 0){
printk("alloc_chrdev_region ERROR!!!\n");
}else{
printk("alloc_chrdev_region OK!!!\n");
}
major_num = MAJOR(dev_num); // 取出来主设备号
minor_num = MINOR(dev_num); // 取出来次设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
// printk("my_param = %d\n", my_param); // 在内核中无法使用c语言库,所以不用printf
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
printk("see you again!!!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
验证静态分配代码
需要手动分配设备号,所以我们要先查看哪些使用过,哪些没用过
cat /proc/devices
现在我们假定使用9作为我们自己程序的主设备号
根据图片结果,可以看到主设备号是9,次设备号因为没有设置,默认是0
然后,我们程序中这样的设置,所以设备名称应该是schrdev
我们可以看到这里生成了我们定义的设备号,以及对应的名字schrdev
验证动态分配代码
直接加载模块
查看
小结
建议使用动态分配
注册字符类设备
注册杂项设备
注册杂项设备
misc_register(&misc_dev)
注销杂项设备
misc_deregister(&misc_dev)
cdev结构体
cdev结构体,是一个描述字符设备的结构体
include/linux/cdev.h
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
步骤一:定义一个cdev结构体
步骤二:使用cdev_init函数初始化cdev结构体成员变量
void cdev_init(struct cdev *, const struct file_operations *);
参数
struct cdev *:要初始化的cdev
const struct file_operations *:文件操作集
cdev->ops = fops; // 把文件集写给成员变量ops
步骤三:使用cdev_add函数注册到内核
int cdev_add(struct cdev *, dev_t, unsigned);
参数
struct cdev *:cdev的结构体指针
dev_t:设备号
unsigned:次设备号的数量
步骤四:使用cdev_del函数注销
void cdev_del(struct cdev *);
代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#define DEVICE_NUMBER 1 // 申请设备号的数量
#define DEVICE_SNAME "schrdev" // 静态注册 名称
#define DEVICE_ANAME "achrdev" // 动态注册 名称
#define DEVICE_MINOR_NUMBER 0 // 次设备号起始地址,通常是0
static int major_num,minor_num;
struct cdev cdev;
/* module_param(name,type,perm);
* name:要传递进去的参数的名称
* type:类型
* perm:参数读写的权限 S_IRUSR 400
*/
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);
int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open OK\n"); //chrdev_open正常
return 0;
}
struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open, // file_operations文件集操作,要定义函数chrdev_open
};
static int hello_init(void)
{
dev_t dev_num; // 保存设备号
int ret;
if(major_num){ // 如果major_num被手动设置,就使用静态注册设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
dev_num = MKDEV(major_num, minor_num);
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if(ret < 0){ // ret<0 注册失败
printk("register_chrdev_region ERROR!!!\n");
}else{
printk("register_chrdev_region OK!!!\n");
}
}else{ // major_num没有传入,所以动态申请设备号
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if(ret < 0){
printk("alloc_chrdev_region ERROR!!!\n");
}else{
printk("alloc_chrdev_region OK!!!\n");
}
major_num = MAJOR(dev_num); // 取出来主设备号
minor_num = MINOR(dev_num); // 取出来次设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops);
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
cdev_del(&cdev);
printk("see you again!!!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
设备节点
没有设备节点的名字,只有设备的名字,也就是cat看到的
要先创建一个设备节点
字符设备注册完之后,不会自动生成设备节点
需要使用mknod命令创建一个节点
格式
mknod 名称 类型 主设备号 次设备号
举例
mknod /dev/test c 242 0
运行app
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char buff[64] = {0};
fd = open("/dev/test", O_RDWR);
if(fd < 0){
perror("open error\n"); // 在应用中打印
return fd;
}
return 0;
}
自动创建设备节点
怎么自动创建一个设备节点?
在嵌入式Linux中使用mdev来实现设备节点文件的自动创建和删除
什么是mdev?
mdev 是 udev 的简化版本,是 busybox 中所带的程序,最适合用在嵌入式系统
步骤一:使用class_create函数创建一个class的类
class结构体在
/include/linux/device.h
class类
struct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
const struct attribute_group **dev_groups;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
步骤二:使用devic_create函数在我们创建的类下面创建一个设备
创建
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
删除
extern void class_destroy(struct class *cls);
代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define DEVICE_NUMBER 1 // 申请设备号的数量
#define DEVICE_SNAME "schrdev" // 静态注册 名称
#define DEVICE_ANAME "achrdev" // 动态注册 名称
#define DEVICE_MINOR_NUMBER 0 // 次设备号起始地址,通常是0
#define DEVICE_CLASS_NAME "chrdev_class"
static int major_num,minor_num;
struct cdev cdev;
struct class *class;
/* module_param(name,type,perm);
* name:要传递进去的参数的名称
* type:类型
* perm:参数读写的权限 S_IRUSR 400
*/
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);
int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open OK\n"); //chrdev_open正常
return 0;
}
struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open, // file_operations文件集操作,要定义函数chrdev_open
};
static int hello_init(void)
{
dev_t dev_num; // 保存设备号
int ret;
if(major_num){ // 如果major_num被手动设置,就使用静态注册设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
dev_num = MKDEV(major_num, minor_num);
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if(ret < 0){ // ret<0 注册失败
printk("register_chrdev_region ERROR!!!\n");
}else{
printk("register_chrdev_region OK!!!\n");
}
}else{ // major_num没有传入,所以动态申请设备号
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if(ret < 0){
printk("alloc_chrdev_region ERROR!!!\n");
}else{
printk("alloc_chrdev_region OK!!!\n");
}
major_num = MAJOR(dev_num); // 取出来主设备号
minor_num = MINOR(dev_num); // 取出来次设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops);
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
cdev_del(&cdev);
class_destroy(class);
printk("see you again!!!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
验证
代码运行前,可以看到class中的注册信息
加载模块
insmod chrdev.ko
查看class
ls /sys/class/
看到了我们注册的class类
在设备节点下创建设备函数
当使用上面的函数创建完成一个类后,使用device_create函数在这个类下创建一个设备
/include/linux/device.h
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
参数class:设备要创建哪个类下面
参数parent:父设备
代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define DEVICE_NUMBER 1 // 申请设备号的数量
#define DEVICE_SNAME "schrdev" // 静态注册 名称
#define DEVICE_ANAME "achrdev" // 动态注册 名称
#define DEVICE_MINOR_NUMBER 0 // 次设备号起始地址,通常是0
#define DEVICE_CLASS_NAME "chrdev_class"
#define DEVICE_NODE_NAME "chrdev_inode_test"
static int major_num,minor_num;
dev_t dev_num; // 保存设备号
struct cdev cdev;
struct class *class;
struct device *device;
/* module_param(name,type,perm);
* name:要传递进去的参数的名称
* type:类型
* perm:参数读写的权限 S_IRUSR 400
*/
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);
int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open OK\n"); //chrdev_open正常
return 0;
}
struct file_operations chrdev_ops = {
.owner = THIS_MODULE,
.open = chrdev_open, // file_operations文件集操作,要定义函数chrdev_open
};
static int hello_init(void)
{
int ret;
if(major_num){ // 如果major_num被手动设置,就使用静态注册设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
dev_num = MKDEV(major_num, minor_num);
ret = register_chrdev_region(dev_num, DEVICE_NUMBER, DEVICE_SNAME);
if(ret < 0){ // ret<0 注册失败
printk("register_chrdev_region ERROR!!!\n");
}else{
printk("register_chrdev_region OK!!!\n");
}
}else{ // major_num没有传入,所以动态申请设备号
ret = alloc_chrdev_region(&dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);
if(ret < 0){
printk("alloc_chrdev_region ERROR!!!\n");
}else{
printk("alloc_chrdev_region OK!!!\n");
}
major_num = MAJOR(dev_num); // 取出来主设备号
minor_num = MINOR(dev_num); // 取出来次设备号
printk("major_num = %d\n", major_num);
printk("minor_num = %d\n", minor_num);
}
cdev.owner = THIS_MODULE;
cdev_init(&cdev, &chrdev_ops);
cdev_add(&cdev, dev_num, DEVICE_NUMBER);
class = class_create(THIS_MODULE, DEVICE_CLASS_NAME);
device = device_create(class, NULL, dev_num, NULL, DEVICE_NODE_NAME);
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
cdev_del(&cdev);
device_destroy(class, dev_num);
class_destroy(class);
printk("see you again!!!\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL"); //声明模块拥有开源许可
结果
insmod chrdev.ko
ls /sys/class/
ls /dev/
现在设备节点创建成功
rmmod chrdev.ko
测试卸载功能
使用
app代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
char buff[64] = {0};
fd = open("/dev/chrdev_inode_test", O_RDWR);
if(fd < 0){
perror("open error\n"); // 在应用中打印
return fd;
}
// read(fd, buff, sizeof(buff));
// write(fd, buff,sizeof(buff));
// close(fd);
return 0;
}
编译
$CC app.c -o app
发送
scp app root@192.168.0.232:/tmp
运行
运行app可执行文件,可以执行,触发驱动open中的printk内容