Linux驱动开发 15 块设备驱动框架

CAN

        I.MX6ULL 带有两个 CAN 控制器: FlexCAN1 FlexCAN2 NXP 官方的 EVK 开发板这两个 CAN 接口都用到了,因此 NXP 官方的设备树将这两个 CAN 接口都使能了
使能 Linux 内核自带的 FlexCAN 驱动         

 

=========================================================================

块设备驱动,不同的设备类型协议不同,但基本通用,不需要写驱动

块设备驱动相比字符设备驱动的主要区别如下:
        ①、块设备只能以块为单位进行读写访问,块是 linux 虚拟文件系统 (VFS) 基本的数据传输单位。字符设备是以字节为单位进行数据传输的,不需要缓冲。
        ②、块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中。这么做的目的为了提高块设备寿命,大家如果仔细观察的话就会发现有些硬盘或者 NAND Flash就会标明擦除次数(flash 的特性,写之前要先擦除 ) ,比如擦除 100000 次等。因此,为了提高块设备寿命而引入了缓冲区,数据先写入到缓冲区中,等满足一定条件后再一次性写入到真正的物理存储设备中,这样就减少了对块设备的擦除次数,提高了块设备寿命。
        字符设备是顺序的数据流设备,字符设备是按照字节进行读写访问的。字符设备不需要缓冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问。

block_device 结构体       

        linux 内核使用 block_device 表示块设备 block_device 为 一 个 结 构 体 , 定 义 在 include/linux/fs.h 文件中,结构体内容如下:

1 、注册块设备
和字符设备驱动一样,我们需要向内核注册新的块设备、申请设备号,块设备注册函数为
register_blkdev ,函数原型如下:
int register_blkdev(unsigned int major, const char *name)
2 、注销块设备
和字符设备驱动一样,如果不使用某个块设备了,那么就需要注销掉,函数为
unregister_blkdev ,函数原型如下:
void unregister_blkdev(unsigned int major, const char *name)

 

gendisk 结构体

         linux 内核使用 gendisk 来描述一个磁盘设备 ,这是一个结构体,定义在 include/linux/genhd.h 中,内容如下

 

1 、申请 gendisk
        使用 gendisk 之前要先申请, alloc_disk 函数用于申请一个 gendisk ,函数原型如下:
struct gendisk *alloc_disk(int minors)
2 、删除 gendisk
        如果要删除 gendisk 的话可以使用函数 del_gendisk ,函数原型如下:
void del_gendisk(struct gendisk *gp)
3 、将 gendisk 添加到内核
        使用 alloc_disk 申请到 gendisk 以后系统还不能使用,必须使用 add_disk 函数将申请到的
gendisk 添加到内核中, add_disk 函数原型如下:
void add_disk(struct gendisk *disk)
4 、设置 gendisk 容量
        每一个磁盘都有容量,所以在初始化 gendisk 的时候也需要设置其容量,使用函数
set_capacity ,函数原型如下:
void set_capacity(struct gendisk *disk, sector_t size)
5 、调整 gendisk 引用计数
        内核会通过 get_disk put_disk 这两个函数来调整 gendisk 的引用计数,根据名字就可以知道,get_disk 是增加 gendisk 的引用计数, put_disk 是减少 gendisk 的引用计数,这两个函数原型如下所示:
truct kobject *get_disk(struct gendisk *disk)
void put_disk(struct gendisk *disk)

block_device_operations 结构体(块设备操作集)

        和字符设备的 file _operations 一样,块设备也有操作集,为结构体 block_device_operations, 此结构体定义在 include/linux/blkdev.h

2 行,open 函数用于打开指定的块设备。

第 3 行,release 函数用于关闭(释放)指定的块设备。

第 4 行,rw_page 函数用于读写指定的页。

第 5 行,ioctl 函数用于块设备的 I/O 控制。

第 6 行,compat_ioctl 函数和 ioctl 函数一样,都是用于块设备的 I/O 控制。区别在于在 64

位系统上,32 位应用程序的 ioctl 会调用 compat_iotl 函数。在 32 位系统上运行的 32 位应用程 序调用的就是 ioctl 函数。

第 15 行,getgeo 函数用于获取磁盘信息,包括磁头、柱面和扇区等信息。

第 18 行,owner 表示此结构体属于哪个模块,一般直接设置为 THIS_MODULE

块设备读写请求过程

大家如果仔细观察的话会在 block_device_operations 结构体中并没有找到 read write 这样
的读写函数,那么块设备是怎么从物理块设备中读写数据?这里就引出了块设备驱动中非常重
要的 request_queue request bio
1 、请求队列 request_queue
        内核将对块设备的读写都发送到请求队列 request_queue
        ①、初始化请求队列
        ②、删除请求队列
        ③、分配请求队列并绑定制造请求函数

2、请求 request

        request_queue 中是大量的 request(请求结构体 )

        ①、获取请求

        ②、开启请求

        ③、一步到位处理请求

3 bio 结构
        而 request 又包含了 bio bio 保存了读写相关数据,bio 保存着最终要读写的数据、地址等信息。上层应用程序对于块设备的读写会被构造成一个或多个 bio 结构,bio 结构描述了要读写的起始扇区、要读写的扇区数量、是读取还是写入、页偏移、数据长度等等信息
        ①、遍历请求中的 bio
        ②、遍历 bio 中的所有段
        ③、通知 bio 处理结束

使用请求队列的块框架例子

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>

#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: ramdisk.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: 内存模拟硬盘,实现块设备驱动,本驱动使用请求队列。
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2020/5/22 左忠凯创建
***************************************************************/

/* 定义磁盘大小,内存模式  */
#define RAMDISK_SIZE	(2 * 1024 * 1024) 	/* 容量大小为2MB */
#define RAMDISK_NAME	"ramdisk"			/* 名字 */
#define RADMISK_MINOR	3					/* 表示有三个磁盘分区!不是次设备号为3! */

/* ramdisk设备结构体 */
struct ramdisk_dev{
	int major;					/* 主设备号 */
	unsigned char *ramdiskbuf;	/* ramdisk内存空间,用于模拟块设备 */
	spinlock_t lock;			/* 自旋锁 */
	struct gendisk *gendisk; 	/* gendisk */
	struct request_queue *queue;/* 请求队列 */

};

struct ramdisk_dev ramdisk;		/* ramdisk设备 */


//
/*
 * @description		: 打开块设备
 * @param - dev 	: 块设备
 * @param - mode 	: 打开模式
 * @return 			: 0 成功;其他 失败
 */
int ramdisk_open(struct block_device *dev, fmode_t mode)
{
	printk("ramdisk open\r\n");
	return 0;
}

/*
 * @description		: 释放块设备
 * @param - disk 	: gendisk
 * @param - mode 	: 模式
 * @return 			: 0 成功;其他 失败
 */
void ramdisk_release(struct gendisk *disk, fmode_t mode)
{
	printk("ramdisk release\r\n");
}

/*
 * @description		: 获取磁盘信息
 * @param - dev 	: 块设备
 * @param - geo 	: 模式
 * @return 			: 0 成功;其他 失败
 */
int ramdisk_getgeo(struct block_device *dev, struct hd_geometry *geo)
{
	/* 这是相对于机械硬盘的概念 */
	geo->heads = 2;			/* 磁头 */
	geo->cylinders = 32;	/* 柱面 */
	geo->sectors = RAMDISK_SIZE / (2 * 32 *512); /* 一个磁道上的扇区数量 */
	return 0;
}

/* 
 * 块设备操作函数block_device_operations 结构体 
 */
static struct block_device_operations ramdisk_fops =
{
	.owner	 = THIS_MODULE,//表示此结构体属于哪个模块
	.open	 = ramdisk_open,//用于打开指定的块设备
	.release = ramdisk_release,//用于关闭(释放)指定的块设备
	.getgeo  = ramdisk_getgeo,//用于获取磁盘信息,包括磁头,柱面和扇区等信息
};

//
/*
 * @description	: 处理传输过程
 * @param-req 	: 请求
 * @return 		: 无
 */
static void ramdisk_transfer(struct request *req)
{	
	/* blk_rq_pos获取到的是扇区地址,左移9位转换为字节地址 */
	/* 用左移9位代替`乘法运算 2^9 = 512  */
	unsigned long start = blk_rq_pos(req) << 9;  
	/* 大小   */	
	unsigned long len  = blk_rq_cur_bytes(req);		

	// 数据传输三要素 :源 目的 长度
	// 对于块设备而言三要素:内存 块设备 长度

	/* bio中的数据缓冲区
	 * 如果是读:从磁盘读取到的数据存放到buffer中
	 * 如果是写:buffer保存这要写入磁盘的数据
	 */
	void *buffer = bio_data(req->bio);		
	// 在这里判断 使用内核提供的 rq_data_dir 函数
	if(rq_data_dir(req) == READ) 		/* 读数据 */	
		//三个参数 缓冲区 地址 长度
		//这个地方实际上没有那么简单,因为是内存模拟的SD卡读写,所以简单
		memcpy(buffer, ramdisk.ramdiskbuf + start, len); 
	else if(rq_data_dir(req) == WRITE) 	/* 写数据 */
		memcpy(ramdisk.ramdiskbuf + start, buffer, len);

}

/*
 * @description	: 请求处理函数
 * @param-q 	: 请求队列
 * @return 		: 无
 * 块框架中 重点就是这个请求函数
 */
void ramdisk_request_fn(struct request_queue *q)
{
	int err = 0;
	struct request *req;

	/* 循环处理请求队列中的每个请求 */
	// 取出一个request,判断是否有效,有效则处理 执行ramdisk_transfer
	req = blk_fetch_request(q);
	while(req != NULL) {

		/* 针对请求做具体的传输处理 */
		ramdisk_transfer(req);

		/* 判断是否为最后一个请求,如果不是的话就获取下一个请求
		 * 循环处理完请求队列中的所有请求。
		 */
		if (!__blk_end_request_cur(req, err))
			req = blk_fetch_request(q);
	}
}
//

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 * 
 * 本驱动使用请求队列。 典型块设备驱动框架
 */
static int __init ramdisk_init(void)
{
	int ret = 0;
	printk("ramdisk init\r\n");

	/* 1、申请用于ramdisk内存 kzalloc */
	ramdisk.ramdiskbuf = kzalloc(RAMDISK_SIZE, GFP_KERNEL);
	if(ramdisk.ramdiskbuf == NULL) {
		ret = -EINVAL;
		goto ram_fail;
	}

	/* 2、初始化自旋锁 */
	spin_lock_init(&ramdisk.lock);

	/* 3、注册块设备 register_blkdev*/
	ramdisk.major = register_blkdev(0, RAMDISK_NAME); /* 第一个参数为0 由系统自动分配主设备号 */
	if(ramdisk.major < 0) {
		goto register_blkdev_fail;
	}  
	// 打印主设备号
	printk("ramdisk major = %d\r\n", ramdisk.major);

	/* 4、分配并初始化gendisk 
		alloc_disk:申请gendisk
	*/
	ramdisk.gendisk = alloc_disk(RADMISK_MINOR);
	if(!ramdisk.gendisk) {
		ret = -EINVAL;
		goto gendisk_alloc_fail;
	}

	/* 5、分配并初始化请求队列 blk_init_queue*/
	ramdisk.queue = blk_init_queue(ramdisk_request_fn, &ramdisk.lock);
	if(!ramdisk.queue) {
		ret = EINVAL;
		goto blk_init_fail;
	}

	/* 6、添加(注册)disk */	
	/* 初始化gendisk(描述磁盘设备)  */
	ramdisk.gendisk->major = ramdisk.major;		/* 主设备号 */
	ramdisk.gendisk->first_minor = 0;			/* 第一个次设备号(起始次设备号) */
	ramdisk.gendisk->fops = &ramdisk_fops; 		/* 操作函数 */
	ramdisk.gendisk->private_data = &ramdisk;	/* 私有数据 */
	ramdisk.gendisk->queue = ramdisk.queue;		/* 请求队列 */
	sprintf(ramdisk.gendisk->disk_name, RAMDISK_NAME); /* 名字 */
	set_capacity(ramdisk.gendisk, RAMDISK_SIZE/512);	/* 设备容量(单位为扇区) */
	add_disk(ramdisk.gendisk);

	return 0;

blk_init_fail:
	put_disk(ramdisk.gendisk);
	//del_gendisk(ramdisk.gendisk);
gendisk_alloc_fail:
	unregister_blkdev(ramdisk.major, RAMDISK_NAME);
register_blkdev_fail:
	kfree(ramdisk.ramdiskbuf); /* 释放内存 */
ram_fail:
	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ramdisk_exit(void)
{
	printk("ramdisk exit\r\n");
	/* 释放gendisk */
	del_gendisk(ramdisk.gendisk);
	put_disk(ramdisk.gendisk);

	/* 清除请求队列 */
	blk_cleanup_queue(ramdisk.queue);

	/* 注销块设备 unregister_blkdev*/
	unregister_blkdev(ramdisk.major, RAMDISK_NAME);

	/* 释放内存 */
	kfree(ramdisk.ramdiskbuf); 
}

module_init(ramdisk_init);
module_exit(ramdisk_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("whz");

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值