目录
2、给定了设备号register_chrdev_region
一、分配和释放设备号
使用 register_chrdev 函数注册字符设备的时候只需要给定一个主设备号即可,但是这样会带来两个问题:
①、需要我们事先确定好哪些主设备号没有使用。
②、会将一个主设备号下的所有次设备号都使用掉,比如现在设置 LED 这个主设备号为200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被 LED 一个设备分走了。太浪费次设备号了
解决这两个问题最好的方法就是要使用设备号的时候向 Linux 内核申请,需要几个就申请几个,由 Linux 内核分配设备可以使用的设备号,需要头文件是<linux/fs.h>
1、没有指定设备号alloc_chrdev_region
如果没有指定设备号的话就使用如下函数来申请设备号:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
参数dev是指向 dev_t 类型数据的指针变量,用于存放分配到的设备编号的起始值;
参数baseminor是次设备号的起始值,通常情况下,设置为 0;
参数count是指定需要分配的次设备编号的个数;
参数name是表示设备名称
2、给定了设备号register_chrdev_region
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数 from 是要申请的起始设备号,也就是给定的设备号;
参数 count 是要申请的次设备号数量,一般都是一个;
参数 name 是设备名字
3、释 放 掉 设 备 号
注 销 字 符 设 备 之 后 要 释 放 掉 设 备 号 , 不 管 是 通 过 alloc_chrdev_region 函 数 还 是
register_chrdev_region 函数申请的设备号,统一使用如下释放函数:
void unregister_chrdev_region(dev_t from, unsigned count)
参数 from 是设备号;参数 count 是要释放的数量
构建或获取设备号
4、MKDEV
用MKDEV来构建dev_t主设备号和次设备号 ,宏如下:
MKDEV(MAJOR, MINOR);
MAJOR 主设备号 ;MINOR 次设备号,一般为0
5、MAJOR和MINOR
用 MAJOR和MINOR获取分配的主次设备号
MAJOR(devid);
MINOR(devid);
devid是分配好的设备号
二、目录环境
创建一个新目录“3_newchrled.c”
把上一章中的实验“2_led"里面的所有内容复制到“3_newchrled.c”
进入目录“3_newchrled.c”,把2_led.code-workspace和ledAPP删除,保存当前工作区
使用make clean清除编译出文件,把led.c改为newchrled.c
打开newchrled.c,保留内容如下,其他删掉
三、代码编写
1、注册和卸载驱动,许可和作者信息
2、入口和出口函数
3、LED设备
4、入口函数
定义一个设备名,在分配设备号函数中用到设备名
定义一个数量1,这里只需要申请一个次设备号
入口函数如下
39行判断是指定设备号还是不指定,分别执行代码
41行就是构建主次设备号,指定设备号注册设备
44行是注册通过内核分配主次设备号的设备
48行判断设备号是否注册成功
53行,查看主次设备号
5、出口函数
添加注销设备号
6、makefile
修改第五行为newchrdev.o即可
7、编译
先把下面这段没使用到的代码屏蔽
目前总体代码如下 (方便复制)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define NEWCHRLED_NAME "newchrled"
#define NEWCHRLED_COUNT 1
#if 0
/*寄存器物理地址*/
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/*地址映射后的虚拟地址指针*/
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#define LEDOFF 0 /*关闭*/
#define LEDON 1 /*打开*/
#endif
/*LED设备结构体*/
struct newchrled_dev{
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
}newchrled;/*led设备*/
/*入口*/
static int __init newchrled_init(void)
{
int ret = 0;
if(newchrled.major) /*给定主设备号*/
{
newchrled.devid = MKDEV(newchrled.major,0);
ret = register_chrdev_region(newchrled.devid, NEWCHRLED_COUNT, NEWCHRLED_NAME);
}else{ /*没有给定主设备号*/
ret = alloc_chrdev_region(&newchrled.devid , 0 , NEWCHRLED_COUNT ,NEWCHRLED_NAME);
newchrled.major = MAJOR(newchrled.devid);/* 获取分配好的主设备号 */
newchrled.minor = MINOR(newchrled.devid);/* 获取分配好的次设备号 */
}
if(ret < 0)/*检查是否分配到设备号*/
{
printk("newchrled chrdev_region error!\r\n");
return -1;
}
printk("newchrled.major = %d , newchrled.minor = %d\r\n",newchrled.major,newchrled.minor);
return 0;
}
/*出口*/
static void __exit newchrled_exit(void)
{
/*注销设备号*/
unregister_chrdev_region(newchrled.devid,NEWCHRLED_COUNT);
printk("newchrled_exit\r\n");
}
/*注册和卸载驱动*/
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");
好的没有问题,继续编写
四、新的字符设备注册注销方法
1、字符设备结构
编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个变量就表示一个字符设备
添加cdev,需要头文件#include <linux/cdev.h>
2、cdev_init 函数
定义好 cdev 变量以后就要使用 cdev_init 函数对其进行初始化, cdev_init 函数原型如下:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合
3、 cdev_add 函数
cdev_add 函数用于向 Linux 系统添加字符设备(cdev 结构体变量),首先使用 cdev_init 函数完成对 cdev 结构体变量的初始化,然后使用 cdev_add 函数向 Linux 系统添加这个字符设备,cdev_add 函数原型如下:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数 p 指向要添加的字符设备(cdev 结构体变量),参数 dev 就是设备所使用的设备号,
参数 count 是要添加的设备数量
分配设备号的程序加上这三个函数一起实现的就是函数 register_chrdev 的功能
4、注册字符设备
在函数入口后面添加注册字符设备
5、字符设备文件操作fops函数
这里面用到newchrled_fops并没有定义,需要先定义一下newchrled_fops结构体
6、操作文件函数
在这里面能看到也没有定义初始化函数,下面定义一下文件操作函数
7、注销字符设备
卸载驱动的时候一定要使用 cdev_del 函数从 Linux 内核中删除相应的字符设备, cdev_del
函数原型如下:
void cdev_del(struct cdev *p)
参数 p 就是要删除的字符设备
在函数出口添加注销字符设备
基本的驱动框架已经完成,测试一下
8、测试
编译后复制到根文件系统lib/modules/4.1.15/目录下
开发板测试
可以看到加载模块成功并获取到主次设备号
目前总体代码如下
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#define NEWCHRLED_NAME "newchrled"
#define NEWCHRLED_COUNT 1
#if 0
/*寄存器物理地址*/
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/*地址映射后的虚拟地址指针*/
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#define LEDOFF 0 /*关闭*/
#define LEDON 1 /*打开*/
#endif
/*LED设备结构体*/
struct newchrled_dev{
struct cdev cdev; /*字符设备*/
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
}newchrled;/*led设备*/
static int newchrled_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t newchrled_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static int newchrled_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*字符设备文件操作函数集合*/
static const struct file_operations newchrled_fops ={
.owner = THIS_MODULE,
.write = newchrled_write,
.open = newchrled_open,
.release = newchrled_release,
};
/*入口*/
static int __init newchrled_init(void)
{
int ret = 0;
if(newchrled.major) /*给定主设备号*/
{
newchrled.devid = MKDEV(newchrled.major,0);
ret = register_chrdev_region(newchrled.devid, NEWCHRLED_COUNT, NEWCHRLED_NAME);
}else{ /*没有给定主设备号*/
ret = alloc_chrdev_region(&newchrled.devid , 0 , NEWCHRLED_COUNT ,NEWCHRLED_NAME);
newchrled.major = MAJOR(newchrled.devid);/* 获取分配好的主设备号 */
newchrled.minor = MINOR(newchrled.devid);/* 获取分配好的次设备号 */
}
if(ret < 0)/*检查是否分配到设备号*/
{
printk("newchrled chrdev_region error!\r\n");
return -1;
}
printk("newchrled.major = %d , newchrled.minor = %d\r\n",newchrled.major,newchrled.minor);
/*注册字符设备*/
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev , &newchrled_fops);
ret = cdev_add(&newchrled.cdev,newchrled.devid,NEWCHRLED_COUNT);
return 0;
}
/*出口*/
static void __exit newchrled_exit(void)
{
/*注销字符设备*/
cdev_del(&newchrled.cdev);
/*注销设备号*/
unregister_chrdev_region(newchrled.devid,NEWCHRLED_COUNT);
printk("newchrled_exit\r\n");
}
/*注册和卸载驱动*/
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");
五、用新驱动点灯
先把注释了的代码释放出来
1、添加开关灯模块
2、添加模块化开关灯
写入函数添加获取开关数据(这里把参数cun改成了count,只是方便看,也可以不改)
3、初始化LED
在写入函数开头添加初始化LED
4、取消地址映射
在出口函数添加关灯和取消地址映射
六、用APP进行点灯测试
1、编译、复制
2、 加载驱动
查看设备号为249
3、创建设备节点
4、APP测试开灯
APP继续用之前的即可,都是点灯代码,内容是一样的
5、 APP测试关灯
本章代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#define NEWCHRLED_NAME "newchrled"
#define NEWCHRLED_COUNT 1
/*寄存器物理地址*/
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/*地址映射后的虚拟地址指针*/
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#define LEDOFF 0 /*关闭*/
#define LEDON 1 /*打开*/
/*LED设备结构体*/
struct newchrled_dev{
struct cdev cdev; /*字符设备*/
dev_t devid; /*设备号*/
int major; /*主设备号*/
int minor; /*次设备号*/
}newchrled;/*led设备*/
/*LED灯开关控制*/
static void newchrled_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON)
{
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val,GPIO1_DR);
}else if(sta == LEDOFF){
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val,GPIO1_DR);
}
}
static int newchrled_open(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t newchrled_write(struct file *filp, const char __user *buf, size_t count, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
retvalue = copy_from_user(databuf,buf,count);
if(retvalue < 0){
printk("kernel write failed!\r\n");
return -EFAULT;
}
/*判断开关灯*/
newchrled_switch(databuf[0]);
return 0;
}
static int newchrled_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*字符设备文件操作函数集合*/
static const struct file_operations newchrled_fops ={
.owner = THIS_MODULE,
.write = newchrled_write,
.open = newchrled_open,
.release = newchrled_release,
};
/*入口*/
static int __init newchrled_init(void)
{
unsigned int val = 0;
int ret = 0;
/* 初始化LED */
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE , 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE , 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE , 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE , 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE , 4);
/* 2、使能GPIO1时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26);/*清除以前的配置*/
val |= 3 << 26;/*bit26-27置1*/
writel(val ,IMX6U_CCM_CCGR1);/*把数据写到虚拟地址上*/
/* 3、设置GPIO1_IO03的复用功能,将其复用为
* GPIO1_IO03,最后设置IO属性。
*/
writel(0x5 , SW_MUX_GPIO1_IO03);/*设置复用*/
/*寄存器SW_PAD_GPIO1_IO03设置IO属性
*bit 16:0 HYS关闭
*bit [15:14]: 00 默认下拉
*bit [13]: 0 kepper功能
*bit [12]: 1 pull/keeper使能
*bit [11]: 0 关闭开路输出
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 110 R0/6驱动能力
*bit [0]: 0 低转换率
*/
writel(0x10B0 , SW_PAD_GPIO1_IO03);/*设置电气属性*/
/* 4、设置GPIO1_IO03为输出功能 */
val = readl(GPIO1_GDIR);
val |= 1 << 3;/*设置为输出*/
writel(val ,GPIO1_GDIR);
/* 5、默认关闭LED */
val = readl(GPIO1_DR);
val |= (1 << 3);/*bit3清零,关闭LED灯*/
writel(val ,GPIO1_DR);
newchrled.major = 0; /*设置为0,表示由系统申请设备号*/
/*设备号*/
if(newchrled.major) /*给定主设备号*/
{
newchrled.devid = MKDEV(newchrled.major,0);
ret = register_chrdev_region(newchrled.devid, NEWCHRLED_COUNT, NEWCHRLED_NAME);
}else{ /*没有给定主设备号*/
ret = alloc_chrdev_region(&newchrled.devid , 0 , NEWCHRLED_COUNT ,NEWCHRLED_NAME);
newchrled.major = MAJOR(newchrled.devid);/* 获取分配好的主设备号 */
newchrled.minor = MINOR(newchrled.devid);/* 获取分配好的次设备号 */
}
if(ret < 0)/*检查是否分配到设备号*/
{
printk("newchrled chrdev_region error!\r\n");
return -1;
}
printk("newchrled_init\r\nnewchrled.major = %d , newchrled.minor = %d\r\n",newchrled.major,newchrled.minor);
/*注册字符设备*/
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev , &newchrled_fops);
ret = cdev_add(&newchrled.cdev,newchrled.devid,NEWCHRLED_COUNT);
return 0;
}
/*出口*/
static void __exit newchrled_exit(void)
{
unsigned int val = 0;
val = readl(GPIO1_DR);
val |= (1 << 3);/*bit3置1,关闭LED灯*/
writel(val ,GPIO1_DR);
/*取消地址映射*/
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/*注销字符设备*/
cdev_del(&newchrled.cdev);
/*注销设备号*/
unregister_chrdev_region(newchrled.devid,NEWCHRLED_COUNT);
printk("newchrled_exit\r\n");
}
/*注册和卸载驱动*/
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ba che kai qi lai");