特点:
- 首先需要申请一个设备号然后在注册设备
- 设备号 32 位
前12位主设备号 (2^12=4K 0~4095) 后 20 位为次设备号 (2^20 = 1M)
MKDEV(ma,mi) //已知次设备号合成完整设备号
MAJOR(dev) //从完整设备号提取主设备号
MINOR(dev) //从完整设备号提取次设备号
流程:
- 申请完整设备号
动态申请(动态分配)
静态申请(直接指定)
- 注册添加设备 cdev_init – cdev_add
- 自动/手动创建节点
重要数据结构:struct cdev 结构体
linux-4.4.240\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;
};
struct module *owner;//指定为THIS_MODULE,表示这个模块
const struct file_operations *ops;//设备节点管理的文件指针集
struct list_head list;//链表上下一个设备
dev_t dev;//完整设备号
unsigned int count;//申请设备号的数量
释放cdev结构空间函数 kfree( ) 头文件 #include <slab.h>
一、关于设备号
1. 动态设备号申请:
#include <linux/fs.h>
int alloc_chrdev_region(dev_t* dev, unsigned baseminor, unsigned count, const char* name)
- 功能:申请一个设备号范围
- 参数:
dev:存放分配到的第一个设备(包括主次设备号)
baseminon:要分配起始次设备号(次设备号的起始值)
count:连续的次设备号数量
name:设备名,不需要和/dev的设备文件名相同
- 返回值:成功返回0 失败返回负数
2. 静态设备号申请:
#include <linux/fs.h>
int register_chrdev_region(dev_t from, unsigned count, const char* name)
- 功能:申请一个设备号范围
- 参数:
from:起始设备号(主,次)
count:连续的次设备号数量
参数name:设备名,不需要和/dev的设备文件名相同
- 返回值:成功返回0 失败返回负数
3. 设备号释放函数:
#include <linux/fs.h>
void unregister_chrdev_region(dev_t from, unsigned int count)
- 功能:释放一个设备号范围
- 参数:
from:起始设备号(主,次)(包含主次设备号)
count:连续的次设备号数量
二、关于设备
1. 核心结构初始化
#include <linux/cdev.h>
void cdev_init(struct cdev* cdev, const struct file_operations* fops)
- 功能:初始化核心结构,具体做的是清零核心结构,初始化核心结构的list,kobj,ops成员
- 参数
cdev:需要初始化的核心结构指针
fops:文件操作方法结构指针集
说明:写这种驱动模型的时候,不需要在定义 struct cdev 结构变量初始化,因为调用 cdev_init 函数的时候会把它清零 0,定义时候的初始无效
2. 设备注册函数
int cdev_add(struct cdev* p, dev_t dev, unsigned count)
- 功能注册一个 cdev 结构
- 参数:
p:已经初始化的核心结构指针
参数dev:起始设备号(包含主次设备号)
参数count:连续次设备号的数量
- 返回值:成功: 返回 0, 失败:返回负数
3. 设备注销函数
include <linux/cdev.h>
void cdev_del(struct cdev* p)
- 功能:注销一个cdev结构
- 参数p:前面注册的struct cdev结构指针
4. 核心结构分配函数:
#include <linux/cdev.h>
struct cdev* cdev_alloc(void)
- 功能:在堆空间中分配一个核心结构,注意,不使用的时候要用 kfree 函数释放
- 返回值:返回分配到struct cdev 结构空间首地址
- 说明:用完记得释放,否则会造成内存泄漏
5. 定义核心结构体两种方式:
*第一种方式: struct cdev cdev; //定义变量,定义后已经有struct cdev 结构内存空间
第二中方式: struct cdev p; //这样写法只是定义了指针 p =cdev_alloc(); //写在模块加载函数中
三、 linux2.6 字符设备驱动编程步骤
- 模块加载函数中:
(1). 定义核心结构体,两种方法,第二中要使用 cdev_alloc(void) 分配结构空间
(2). 申请设备号静态或者是动态 alloc_chrdev_region
(3). 初始化 cdev 结构 cdev_init
(4). 注册已经初始化好的 cdev 结构 cdev_add
- 模块卸载函数中:
(1). 注销设备
(2).释放设备号
四、自动创建设备节点
1. 节点描述结构体 struct device *device_create
linux-4.4.240\include\linux\device.h
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
struct device *device_create(
struct class *cls, // 设备的类 类指针 让那个类来管理
struct device *parent, // NULL
dev_t devt, // 主设备号和次设备号的组合 32 位,主 12 位 次 20 位
void *drvdata, // NULL,设备私有数据
const char *fmt, … /可以格式化的 fmt:/dev/下的节点名/
);
2. 创建 class 类
#include <linux/device.h>
class_create(ower,name)
owner:类的所有者,固定是THIS_MODULE
name;类名,随便,能有含义最好,不是 /dev/ 下设备的名字,这个名字决定了
/sys/class/name。 当创建一个 class 后会在 /sys/class/ 目录生成一个名为 name 的目录
查看内核已经存在的类:ls /sys/class
3. 创建设备节点
#include <linux/device.h>
struct device* device_create(struct class* class, struct device* parent, dev_t devt, void* drvdata, const char* fmt, .....)
参数 class:已经创建好的类指针
参数parent:指向父设备的指针,如果没有可以为 NULL
参数devt: 32 位的设备号、
参数drvdata: 驱动数据,也可以是 NULL,一般为 NULL
参数:fmt,…可变参数,用来生成/dev/目录下的设备文件名
返回值 : 返回的指针是否合法使用 IS_ERR( ) 宏判断, 成功 有效 struct device* 指针;失败返回错误指针,可以使用 PTR_ERR( ) 宏转换成错误代码
4. 摧毁 class 类
void class_destroy(struct class *cls)
5. 摧毁设备节点
void device_destroy(struct class *class,dev_t devt)
6. linux2.6 字符设备自动创建驱动编程步骤
- 模块加载函数中:
(1). 静态或者是动态申请设备号
(2). 合成设备号 MKDEV(ma,mi)
(3). 创建一个类 class_create(ower,name)
(4). 创建设备节点 struct device *device_create(struct class *class,struct device *parent,dev_t devt,void *drvdata,const char *fmt,…)
(5). 初始化 cdev 结构空间 cdev_init(struct cdev *cdev,const struct file_operations *fops)
(6). 注册设备 int cdev_add(struct cdev *p,dev_t dev,unsigned count)
- 模块卸载函数中:
(1). 摧毁设备节点 void device_destroy(struct class *class,dev_t devt)
(2). 摧毁类 void class_destroy(struct class *cls)
(3). 注销cdev结构空间 void cdev_del(struct cdev *p)
(4). 释放设备号 void unregister_chrdev_region(dev_t from, unsigned int count)
7. 映射地址操作
驱动操作的物理地址都是通过ioremap映射的虚拟地址
映射:
#include <linux/io.h>
ioremap(cookie,size)
参数 cookie:实际的物理地址
参数 size:IO的大小返回值:返回可操作的虚拟地址
解除映射:
iounmap
参数:映射成功后的虚拟地址
GPIO驱动
映射地址
配置寄存器,填充框架
编写应用层
五、示例代码
驱动程序:cdev_device_driver.c
/* Linux 驱动模型 - linux2.6 版本注册 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#define CDEV_BASEMION 0 /* 要分配起始次设备号(次设备号的起始值) */
#define CDEV_BASEMION_CNT 3 /* 连续的次设备号数量 */
#define CDEV_NAME "cdev_device"
/* 定义 dev 变量,存放分配到的第一个设备(包括主次设备号) */
static dev_t dev;
/* 核心结构体 */
struct cdev cdev;
/* 打开设备 */
int cdev_device_open(struct inode* inode, struct file* file)
{
printk("cdev_device_open: open\n");
return 0;
}
/* 关闭设备 */
int cdev_device_release(struct inode* inode, struct file* file)
{
printk("cdev_device_release: release\n");
return 0;
}
/* 从设备中同步读取数据 */
ssize_t cdev_device_read(struct file* file, char __user* data, size_t len, loff_t* ppos)
{
printk("cdev_device_read: read\n");
return len;
}
/* 向设备发送数据 */
ssize_t cdev_device_write(struct file* file, const char __user* data, size_t len, loff_t* ppos)
{
printk("cdev_device_write: write\n");
return len;
}
/* 设备文件操作函数指针集 */
struct file_operations cdev_device_fops = {
.owner = THIS_MODULE,
.read = cdev_device_read,
.write = cdev_device_write,
.open = cdev_device_open,
.release = cdev_device_release,
};
/* 设备模块加载函数 */
static int __init cdev_device_modules_init(void)
{
/* 1. 动态申请设备号:申请一个设备号范围 */
int ret = alloc_chrdev_region(&dev, CDEV_BASEMION, CDEV_BASEMION_CNT, CDEV_NAME);
if (ret < 0)
{
printk("alloc_chrdev_region: alloc chrdev region error\n");
return -EFAULT;
}
printk("dev = %d\n", dev);
printk("major = %d\n", MAJOR(dev)); /* 从完整设备号提取主设备号 */
printk("minor = %d\n", MINOR(dev)); /* 从完整设备号提取次设备号 */
/* 2. 初始化核心结构: cdev */
cdev_init(&cdev, &cdev_device_fops);
/* 3. 注册设备 */
ret = 0;
ret = cdev_add(&cdev, dev, CDEV_BASEMION_CNT);
if (ret < 0)
{
printk("cdev_add: cdev add error\n");
return -EFAULT;
}
printk("cdev_device_modules_init: cdev device modules init ok\n");
return 0;
}
/* 设备模块卸载函数 */
static void __exit cdev_device_modules_exit(void)
{
/* 1. 注销设备 */
cdev_del(&cdev);
/* 2.释放设备号 */
unregister_chrdev_region(dev, CDEV_BASEMION_CNT);
/* 以上两步顺序不能变 */
printk("cdev_device_modules_exit: cdev device modules exit done\n");
}
module_init(cdev_device_modules_init);
module_exit(cdev_device_modules_exit);
MODULE_LICENSE("GPL");
应用程序:cdev_device_app.c
/* linux2.6 版本注册 - 应用层程序 */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUF_SIZE 128
char buf[BUF_SIZE] = { 0 };
int main(int argc, char* argv[])
{
if (argc < 2)
{
printf("No find device\n");
return -1;
}
int fp = open(argv[1], O_RDWR);
if (fp < 0)
{
printf("open error\n");
return -1;
}
read(fp, buf, 1024);
int ret = write(fp, buf, sizeof(buf));
if (ret < 0)
{
printf("write error\n");
return -1;
}
close(fp);
return 0;
}
Makefile 文件
# 驱动理论 - linux2.6 版本注册
# 内核路径
KERNNEL_DIR = /home/share/linux-3.5
all:
make -C $(KERNNEL_DIR) M=$(PWD) modules
arm-linux-gcc -Wall -g -std=c99 cdev_device_app.c -o cdev_device_app
clean:
rm -rf *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.order
obj-m += cdev_device_driver.o