一、早期的注册方式
register_chrdev 函数族 + 创建设备类、文件的函数, 这种方式在 2.4版本的内核中使用,其优点是简单,缺点是没办法指定次设备号
registe_chrdev 函数族
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
/*
*函数作用: 为字符设备注册一个主号码
*参数1: 尝试指定的主设备号,传入0则动态分配
*参数2: 设备的标识名称
*参数3: 与此设备关联的文件操作
*/
int unregister_chrdev(const char* name, const struct file_operations *fops)
/*
*函数作用: 注销设备号
*参数1: 设备标识名
*参数2: 与此设备关联的文件操作
*/
二、cdev注册方式
2.6版本以后的内核中使用这种方式来注册设备驱动,优点是用户可以自行设置主次设备号,缺点是这种注册方式相比于早期的更加复杂,函数更多
使用一个结构体 cdev 来描述一个设备,结构体包含dev_t 类型的设备号和 与设备关联的文件操作集合 file_operations 结构体,file_operations和老版本相同
设备号: 主设备号表示一个特定的驱动程序,次设备号表示使用该驱动程序的不同设备
32位无符号整型,12位用来表示主设备号,20位用来表示次设备号
例如:LED驱动对应一个主设备号,两个想要单独控制的LED设备对应次设备号1和2
struct cdev {
struct kobject kobj;//内核对象,由内核管理
struct module *owner;
const struct file_operations *ops;//字符设备驱动方法结构体,需自己填充
struct list_head list;
dev_t dev;//设备号一般有自己指定和动态申请两种方式
unsigned int count;
};
主从设备号在内核中有一个u32类型的变量保存
<include/linux/types.h>
---------------------------------
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
cdev编写字符驱动框架
使用到的系统函数
//1. 注册设备号(静态, 用户指定)
int register_chrdev_region(dev_t from, unsigned int count, conster char* name)
/*
* from: 要分配的设备编号范围的初始值(主设备号,次设备号常设置为0)
* count: 连续编号范围
* name: 编号相关联的设备名
* 返回值:成功返回0
*/
//2. 注册设备号(动态)
int alloc_chrdev_region(dev_t* dev, unsigned int firstminor, unsigned int count, char * name)
/*
* dev:调用该函数申请下来的设备号
* firstminor: 次设备号的起始,请求使用的第一个次设备号
* count: 申请次设备号的总数
* name: 设备名
*分配失败返回0
*/
可以在驱动程序中通过MAJOR和MINOR 宏,把主次设备号打印出来,不然mknod的时候, 无法知道系统自动分配的设备号
MAKDEV(major, minor); //将主设备号和此设备号转换成一个主次设备号
MAJOR(devid); //获取主设备号
MINOR(devid); //获取次设备号
//3. 初始化cdev
cdev_init(struct cdev* cdev, const struct file_operations* fops)
/*
*cdev cdev结构体类型指针
*fops :与此设备关联的文件操作
*/
//4. 注册cdev
cdev_add(struct cdev* p, dev_t dev, unsigned count)
/*
*p: cdev结构体
*dev: 设备号
*count: 次设备的个数
*/
//5. 删除cdev
cdev_del(struct cdev *p)
//6. 注销设备号
void unregister_chrdev_region(dev_t from, unsigned int count)
/*
*from: 第一个设备号
*count:申请的设备数量
*/
这里我们就静态注册以及动态注册单独两个申请函数做分析
静态注册:
#define LED_MAJOR 520
#define LED_MINOR 0
static dev_t devno;
struct cdev cdev;
struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open
};
int __init led_init(void)
{
//主次设备号转换成设备号
devno = MKDEV(LED_MAJOR,LED_MINOR);
//申请设备号
int ret = register_chrdev_region(devno, 0, "led");
if(ret < 0){
printk("result devno error\n");
return -1;
}
//初始化cdev结构体
cdev_init(devno, &fops);
cdev.owner = THIS_MODULE;
cdev.ops = &fops
//注册字符设备
cdev_add(&cdev, devno, 1);
return 0;
}
void __exit led_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
}
动态注册:
static dev_t devno;
struct cdev cdev;
struct file_operations fops = {
.owner = THIS_MODULE,
.open = led_open
};
int __init led_init(void)
{
//申请设备号
int ret = alloc_chrdev_region(&devno, 0, 1, "led");
printk("Kernel info : LED major number is %d\n", MAJOR(devno));
printk("Kernel info : LED minor number is %d\n", MINOR(devno));
if(ret < 0){
printk("result devno error\n");
return -1;
}
//初始化cdev结构体
cdev_init(devno, &fops);
cdev.owner = THIS_MODULE;
cdev.ops = &fops
//注册字符设备
cdev_add(&cdev, devno, 1);
return 0;
}
void __exit led_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(devno, 1);
}
调用系统API,生成设备节点
class_create class_device_create
用动态申请设备号的做示例
static dev_t devno; struct cdev cdev; struct file_operations fops = { .owner = THIS_MODULE, .open = led_open }; int __init led_init(void) { //申请设备号 int ret = alloc_chrdev_region(&devno, 0, 1, "led"); printk("Kernel info : LED major number is %d\n", MAJOR(devno)); printk("Kernel info : LED minor number is %d\n", MINOR(devno)); if(ret < 0){ printk("result devno error\n"); return -1; } //初始化cdev结构体 cdev_init(devno, &fops); cdev.owner = THIS_MODULE; cdev.ops = &fops //注册字符设备 cdev_add(&cdev, devno, 1); struct class *led_class = class_create(THIS_MODULE, "led"); class_device_create(led_class, NULL, devno, NULL, "led0"); return 0; } void __exit led_exit(void) { class_device_destroy(led_class, devno); cdev_del(&cdev); unregister_chrdev_region(devno, 1); }
三、ioremap 映射物理地址
#define GPIO_CON_REG 0x12345 #define GPIO_DATA_REG 0x23456 unsigned long *led_con; unsigned long *led_data; static dev_t devno; struct cdev cdev; struct file_operations fops = { .owner = THIS_MODULE, .open = led_open }; int __init led_init(void) { //申请设备号 int ret = alloc_chrdev_region(&devno, 0, 1, "led"); printk("Kernel info : LED major number is %d\n", MAJOR(devno)); printk("Kernel info : LED minor number is %d\n", MINOR(devno)); if(ret < 0){ printk("result devno error\n"); return -1; } //初始化cdev结构体 cdev_init(devno, &fops); cdev.owner = THIS_MODULE; cdev.ops = &fops //注册字符设备 cdev_add(&cdev, devno, 1); struct class *led_class = class_create(THIS_MODULE, "led"); class_device_create(led_class, NULL, devno, NULL, "led0"); led_con = ioremap(GPIO_CON_REG, 8); led_data = ioremap(GPIO_DATA_REG, 8); return 0; } void __exit led_exit(void) { iounremap(led_con); iounremap(led_data); class_device_destroy(led_class, devno); cdev_del(&cdev); unregister_chrdev_region(devno, 1); }
原文:https://www.cnblogs.com/chenxinblog/p/14200268.html