Linux驱动学习记录-新字符设备
Posted 不良高须
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动学习记录-新字符设备相关的知识,希望对你有一定的参考价值。
第一节介绍了字符驱动设备,开发重点是register_chrdev注册字符设备,unregister_chrdev注销字符设备。驱动加载需要mknod命令创建节点。这些都是老版本,现在学习新字符设备驱动开发。第一节提了一点点,申请设备号是用自动分配的方式就是新设备开发。
1.新字符设备注册
Linux中cdev结构体表示一个字符设备,在include/linux/cdev.h中,定义如下
//字符结构体 重点是 dev和ops
struct cdev {
struct kobject kobj; //内嵌的内核对象
struct module *owner; //该字符设备所在的内核模块的对象指针.
const struct file_operations *ops; //操作函数结构体,描述所实现的方法,极为重要的结构体
struct list_head list; //用来将已经向内核注册的所有字符设备形成链表
dev_t dev; //设备号结构体,主号和次号
unsigned int count; //隶属于同一主设备号的次设备号的个数
};
//相关操作函数
void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
void cd_forget(struct inode *);
一个简单例子介绍其结构体和函数的用法:
struct cdev devtest; //定义字符设备结构体
dev_t dev; //定义设备号结构体
struct file_operations opstest = {
.owner = THIS_MODULE,
}; //定义操作函数结构体
cdev_init (&devtest, &opstest); //初始化字符设备,加入操作函数
cdev_add (&devtest, dev, 1); //添加字符设备,dev是设备号,添加1个
cdev_del (&devtest); //删除字符设备结构体
2.自动创建设备节点
以前我们通过命令mknod手动创建设备节点,一个设备节点其实就是设备文件,Linux中成为设备文件。在Linux中,所有设备的访问都是文件访问。接下来介绍自动创建设备节点,在驱动中实现后,通过modprobe加载驱动后,会自动在/dev目录下创建对应的设备文件。
Linux下通过udev来实现设备文件的创建和删除,使用busybox构建根文件系统的时候,busybox会创建一个udev的简化版本-mdev,嵌入式linux中我们使用mdev来实现设备节点的安装删除。
首先要创建class类结构体,然后创建设备结构体。他们的定义都在include/linux/device.h中,在353行和723行,可自行查看其结构体内成员及其作用。
//480行,定义class结构体创建的别名
extern struct class * __must_check __class_create(struct module *owner,
const char *name,
struct lock_class_key *key);
extern void class_destroy(struct class *cls);
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
#define class_create(owner, name) \\
({ \\
static struct lock_class_key __key; \\
__class_create(owner, name, &__key); \\
})
//因此输入参数只有两个
struct class *class_create (struct module *owner, const char *name)
- owner:一般为THIS_MODULE;
- name:类的名字。
- return:返回值是一个指向name(class结构体)的指针。
创建好类以后,还不能实现自动创建设备节点,还需要在这个类下面创建设备(相当于mknod),使用device_create命令来创建。
struct device *device_create(struct class *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)
- class:这个设备创建在哪个类下面,class指针指向刚刚创建的“name”。
- parent:这个设备的父设备,一般是NULL。
- devt:设备号结构体,由主号和次号构成。
- drvdata:设备可能会使用的一些数据,一般是NULL。
- fmt:是设备名字,如果fmt=XXX,就会生成/dev/XXX这个设备
- 。。。:还有其他参数略。
- return:创建好的设备结构体指针。
参考例子如下:
struct class *led_class; //led类的结构体
struct device *led_device; //led设备结构体
dev_t devid; //led设备号结构体
/*驱动入口函数*/
static int __init led_init(void)
{
led_class = class_creat(THIS_MODULE, "led"); //创建led类
led_device = device_cerat(led_class, NULL, devid, NULL, "led"); //创建led设备
return 0;
}
/*驱动出口函数*/
static void __exit led_exit(void)
{
device_destroy(led_class, devid); //注销led设备
class_destroy(led_class); //注销led类
}
module_init(led_init);
module_exit(led_exit);
3.设置设备文件私有数据
列举一下前两小节涉及的变量类型:
- struct cdev //cdev设备结构体
- dev_t //设备号结构体
- struct class //类结构体
- struct device //设备结构体
现在我们把这些变量设置为结构体属性,用来描述一个驱动设备。
/* 设备结构体 */
struct test_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct test_dev testdev;
/* open 函数 */
/* 注意这里的驱动open函数与测试app的open函数的区别! */
static int test_open(struct inode *inode, struct file *filp)
{
filp->private_data = &testdev; /* 设置私有数据 */
return 0;
}
这里做出补充:struct inode结构体用于描述设备文件的静态属性,包含但不限于dev_t 、cdev等变量描述。mknod每创建一个节点,都会对应一个唯一的inode结构体。struct file是文件结构体代表打开的文件,系统中每个打开的文件在内核空间中都会对应一个file结构体,在文件关闭后,内核会释放这个数据结构。详见:Linux--struct file结构体 - Sophie_h - 博客园。
下图展示各个部分的调用关系:
4.程序编写
下面摘取正点原子的部分程序/03_newchrled。
#define NEWCHRLED_CNT 1 /* 设备号个数 */
#define NEWCHRLED_NAME "newchrled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* 寄存器物理地址 */
/* 映射后的寄存器虚拟地址指针 */
/* newchrled设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct newchrled_dev newchrled; /* led设备 */
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled; /* 设置私有数据 */
return 0;
}
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init led_init(void)
{
u32 val = 0;
/* 初始化LED */
/* 1、寄存器地址映射 */
/* 2、使能GPIO1时钟 */
/* 3、设置GPIO1_IO03的复用功能,将其复用为 GPIO1_IO03,最后设置IO属性。*/
/* 4、设置GPIO1_IO03为输出功能 */
/* 5、默认关闭LED */
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (newchrled.major) { /* 定义了设备号 */
newchrled.devid = MKDEV(newchrled.major, 0);
register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */
newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */
newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */
}
printk("newcheled major=%d,minor=%d\\r\\n",newchrled.major, newchrled.minor);
/* 2、初始化cdev */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
/* 3、添加一个cdev */
cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
/* 4、创建类 */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if (IS_ERR(newchrled.class)) {
return PTR_ERR(newchrled.class);
}
/* 5、创建设备 */
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
if (IS_ERR(newchrled.device)) {
return PTR_ERR(newchrled.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit led_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注销字符设备驱动 */
cdev_del(&newchrled.cdev);/* 删除cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
5.测试
与第三小节一样。
6.附录结构图
以上是关于Linux驱动学习记录-新字符设备的主要内容,如果未能解决你的问题,请参考以下文章