Linux驱动开发笔记(四)——自动分配设备号 & 创建设备节点
·
视频:第5.1讲 Linux新字符设备驱动实验-新驱动框架的搭建_哔哩哔哩_bilibili
内容详见《【正点原子】I.MX6U嵌入式Linux驱动开发指南》四十二章
开发板:imx6ull mini
虚拟机:VMware17
ubuntu:ubuntu20.04
之前一直使用register_chrdev函数来注册字符设备,这会将整个主设备号下的次设备号都用掉,浪费次设备号,还需要手动指定主设备号,还需要确定哪个主设备号没有被使用,还需要mknod创建设备节点文件 巴拉巴拉……
既然已经有系统了,最方便的方法就是直接由系统分配。
1、自动分配设备号
1.1 分配
#include <linux/cdev.h>
// 不指定主设备号,系统自动分配:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
// dev 设备号。分配的设备号通过这个指针返回
// baseminor 从几号开始连续分配次设备号。一般都是从0开始
// count 需要连续注册几个设备
// name 设备名。会显示在/proc/devices
// 指定主、次设备号:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
// from 起始设备号。包括主、次设备号
// count 需要连续注册几个设备
// name 设备名。会显示在/proc/devices
1.2 卸载
#include <linux/cdev.h>
unregister_chrdev_region(dev_t from, unsigned count);
// from 设备号(包括主、次设备号)
// count 卸载的设备数量
1.3 设备号操作函数
//(来自/include/linux/kdev_t.h)
#define MINORBITS 20 // 主设备号长度为 20 bits
#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)) // 拼接设备号
1.4 示例
int major = 0; // 主设备号
int minor; // 次设备号
dev_t devid; // 设备号
if(major){ // 如果已经给出设备号
devid = MKDEV(major,minor); // 组成设备号
register chrdev region(devid, 1, "test"); // 从devid开始分配1个设备号,设备名test
} else { // 如果未指定设备号
// 从次设备号0开始分配1个设备号,设备名test。分配的设备号赋值给devid
alloc_chrdev_region(&devid, 0, 1, "test");
major = MAJOR(devid); // 获取主设备号
minor = MINOR(devid); // 获取次设备号
}
unregister_chrdev_region(devid, 1); // 从devid开始,注销1个设备
2、自动创建设备节点
详见《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81.pdf》42.2
第5.3讲 Linux新字符设备驱动实验-自动创建设备节点_哔哩哔哩_bilibili
2.1 创建类
自动创建设备节点的工作在驱动程序的入口函数中完成。
使用class_create函数进行类创建,这是个宏定义,包括了两行代码:
//定义在include/linux/device.h
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key);\
})
// owner : 模块指针,一般都是THIS_MODULE
// name : 设备名
// 其中__class_create()定义为:
struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key)
2.2 删除类
一般写到设备驱动出口文件里。
void class_destroy(struct class *class)
// class: 要删除的类
2.3 创建设备
创建好类以后还不能自动创建设备。还需要在类下面创建设备。
struct device *device_create(struct class *class, // 创建到哪个类下面
struct device *parent, // 父设备。没有就填NULL
dev_t devt, // 设备号
void *drvdata, // 私有数据指针,用于向设备的回调函数传递数据。没有就写NULL
const char *fmt, ...) // 设备名
2.4 删除设备
void device_destroy(struct class *class, dev_t devt) // 指向类的指针,设备号
2.5 示例
#include <linux/device.h> // 要加这个头文件
//首先在设备结构体中加入class结构体指针
struct newchrled_dev{
struct class *class; /* 类 */
struct device *device; /* 设备 */
/* 其他内容 */
dev_t devid; /* 设备号 */
};
/* 创建 */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME); // 创建类
if(IS_ERR(newchrled.class)) return PTR_ERR(newchrled.class); // 错误处理
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME); // 创建设备
if(IS_ERR(newchrled.device)) return PTR_ERR(newchrled.device); // 错误处理
/* 注意 先删除设备 再删除类 */
device_destroy(newchrled.class,newchrled.devid);
class_destroy(newchrled.class);
3、字符设备注册方法
// 定义在/include/linux.cdev.h
struct cdev {
struct kobject kobj;
struct module *owner; // 指向该字符设备所属驱动模块的指针
const struct file_operations *ops;// 指向操作函数结构体file_operations
struct list_head list; // 链表节点,用于将多个 cdev 结构体链接成链表。
dev_t dev; // 设备号
unsigned int count; // 连续设备号数量
};
//初始化cdev
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
// cdev: 指向定义的cdev结构体
// fops: 指向定义的设备操作集合file_operations
// 向Linux内核添加字符设备
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
// p: 指向要添加的字符设备的cdev结构体变量
// dev: 设备号
// count: 要添加的设备数量
// 删除字符设备
void cdev_del(struct cdev *p)
// p: 指向要删除的设备的cdev结构体变量
示例:
struct my_dev {
struct cdev testcdev; /* 在设备结构体中增加cdev结构体 */
dev_t devid; /* 设备号 */
………… /* 其他参数 */
};
/* 设备操作函数 */
static struct file_operations test_fops = {
.owner = THIS_MODULE,
………… /* 其他操作 */
};
my_dev.testcdev.owner = THIS_MODULE;
cdev_init(&my_dev.testcdev, &test_fops); /* 初始化cdev结构体变量 */
cdev_add(&my_dev.testcdev, my_dev.devid, 1); /* 向linux内核添加设备 */
cdev_del(&my_dev.testcdev); /* 删除设备 */
4、代码
4.1 文件结构
和上个笔记一样,直接把以前的工作区复制过来改个名:
NEWCHRLED(工作区)
└── 3_newchrled
├── .vscode
│ ├── c_cpp_properties.json
│ └── settings.json
├── newchrled.c
├── newchrled.code-workspace
├── newchrledAPP.c
└── Makefile
4.2 修改Makefile
修改obj-m这一行为:
obj-m := newchrled.o
4.3 修改newchrled.c代码:
加载驱动后可以看到分配的设备号:
除了自动分配设备号、自动创建设备文件以外,其他功能和上一篇笔记的寄存器点灯功能一致。
newchrled.c:
#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>
#include <linux/kdev_t.h>
#include <linux/stat.h>
#include <linux/device.h>
#define NEWCHRLED_NAME "newchrled" // 设备名
#define NEWCHRLED_COUNT 1 // led设备数量
/* 寄存器物理地址 */
#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;
/* LED状态 */
#define LEDOFF 0
#define LEDON 1
///////////////////////////////////////////////////////////////////////////////////////////////////
/* 设备结构体 */
struct newchrled_dev{
struct cdev cdev;
dev_t devid; /* 设备号 需要#include <linux/stat.h>*/
int major; /* 主设备号 */
int minor; /* 此设备号 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
};
struct newchrled_dev newchrled; /* led设备 */
//字符设备操作/////////////////////////////////////////////////////////////////////////////////////////////////
/* LED状态翻转 */
static void led_swtich(u8 sta){
u32 val = 0;
if(sta == LEDOFF){
/* 关灯 */
printk("turn off\r\n");
val = readl(GPIO1_DR); // 32bit读取函数
val |= (1 << 3); // 将bit3置为高电平,关闭LED
writel(val, GPIO1_DR);
}
else if(sta == LEDON){
/* 开灯 */
val = readl(GPIO1_DR); // 32bit读取函数
val &= ~(1 << 3); // 将bit3置为低电平,打开LED
writel(val, GPIO1_DR);
printk("turn on\r\n");
}
}
static int newchrled_open(struct inode *inode, struct file *filp){
return 0;
}
static int newchrled_release(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 *ppos){
int retvalue;
unsigned char databuf[1];
retvalue = copy_from_user(databuf, buf, count);
if(retvalue < 0){
printk("kernel write fauled!\r\n");
return -EFAULT;
}
led_swtich(databuf[0]);
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){
printk("newchrled init\r\n");
int ret = 0;
int val = 0;
/* 0.地址映射 ----------------------------------------------- */
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);
/* 1.初始化 ------------------------------------------------- */
val = readl(IMX6U_CCM_CCGR1); // 32bit读取函数
val &= ~(3 << 26); // 清除26、27位的数据为0,其余位保持不变。 其中3的二进制为11,~为取反操作。
val |= 3 << 26; // 将26、27位置为1
writel(val, IMX6U_CCM_CCGR1); // 将 val 写入 IMX6U_CCM_CCGR1,使能CCM_CCGR1的gpio1时钟。
writel(0x5, SW_MUX_GPIO1_IO03); // 复用GPIO1——IO03
writel(0x10B0, SW_PAD_GPIO1_IO03); //设置GPIO1_IO03电气属性
val = readl(GPIO1_GDIR); // 32bit读取函数
val |= 1 << 3; // 设置PIO1_IO03为output。 将GPIO1_GDIR第3位 置为1
writel(val, GPIO1_GDIR);
// 关灯
val = readl(GPIO1_DR); // 32bit读取函数
val |= (1 << 3); // 将bit3置为高电平,关闭LED
writel(val, GPIO1_DR);
/* 2.分配设备号----------------------------------------------- */
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("devid = %d:%d\r\n", newchrled.major, newchrled.minor);
/* 3.注册字符设备----------------------------------------------- */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_COUNT);
/* 4.自动创建设备节点-------------------------------------------- */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME); // 创建类
if(IS_ERR(newchrled.class)){
return PTR_ERR(newchrled.class);
}
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME); // 创建设备
if(IS_ERR(newchrled.device)){
return PTR_ERR(newchrled.device);
}
return 0;
}
/* 出口 */
static int __exit newchrled_exit(void){
printk("newchrled exit\r\n");
/* 1.删除字符设备 */
cdev_del(&newchrled.cdev);
/* 2.注销设备号 */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_COUNT);
/* 3.删除类*/
device_destroy(newchrled.class,newchrled.devid);
class_destroy(newchrled.class);
return 0;
}
/* 驱动加载&卸载 */
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");
newchrledAPP.c:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功; else失败
* 调用 ./newchrledAPP <filename> <0:1> 0关灯,1开灯
* ./newchrledAPP /dev/newchrled 0 关灯
* ./newchrledAPP /dev/newchrled 1 开灯
*/
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[]){
if(argc != 3){ // 判断用法是否错误
printf("Error Usage!\r\n");
return -1;
}
char *filename;
int fd = 0;
unsigned char databuf[1];
int retvalue = 0;
filename = argv[1];
fd = open(filename, O_RDWR); // 读写模式打开驱动文件filename
if(fd <0){
printf("file %s open failed!\r\n");
return -1;
}
databuf[0] = atoi(argv[2]); // char 2 int
retvalue = write(fd, databuf, sizeof(databuf)); // 缓冲区数据写入fd
if(retvalue <0){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
close(fd);
return 0;
}
4.4 运行
# VSCODE终端
make
arm-linux-gnueabihf-gcc newchrledAPP.c -o newchrledAPP
sudo cp newchrled.ko newchrledAPP .../rootfs/lib/modules/4.1.15/
# 串口
cd /lib/modules/4.1.15
modprobe newchrled.ko # 此时lsmod和cat /prob/devices 应当都能看到设备了
/dev/newchrled -l # 应当有对应输出
./newchrledAPP /dev/newchrled 1 # 此时开发板红灯亮起
./newchrledAPP /dev/newchrled 0 # 此时开发板红灯熄灭
rmmod newchrled.ko

更多推荐

所有评论(0)