5.2.3. Linux 驱动模型 —— linux2.6 版本注册

特点:

  • 首先需要申请一个设备号然后在注册设备
  • 设备号 32 位
    前12位主设备号 (2^12=4K 0~4095) 后 20 位为次设备号 (2^20 = 1M)

MKDEV(ma,mi) //已知次设备号合成完整设备号
MAJOR(dev) //从完整设备号提取主设备号
MINOR(dev) //从完整设备号提取次设备号

流程:

  1. 申请完整设备号

动态申请(动态分配)
静态申请(直接指定)

  1. 注册添加设备 cdev_init – cdev_add
  2. 自动/手动创建节点

重要数据结构: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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值