CAN
I.MX6ULL 带有两个
CAN
控制器:
FlexCAN1
和
FlexCAN2
,
NXP
官方的
EVK 开发板这两个
CAN
接口都用到了,因此
NXP
官方的设备树将这两个
CAN
接口都使能了
使能
Linux
内核自带的
FlexCAN
驱动
![](https://i-blog.csdnimg.cn/blog_migrate/f847e4048bf580052c55fdc6d41e4146.png)
=========================================================================
块设备驱动,不同的设备类型协议不同,但基本通用,不需要写驱动
块设备驱动相比字符设备驱动的主要区别如下:
①、块设备只能以块为单位进行读写访问,块是 linux
虚拟文件系统
(VFS)
基本的数据传输单位。字符设备是以字节为单位进行数据传输的,不需要缓冲。
②、块设备在结构上是可以进行随机访问的,对于这些设备的读写都是按块进行的,块设备使用缓冲区来暂时存放数据,等到条件成熟以后在一次性将缓冲区中的数据写入块设备中。这么做的目的为了提高块设备寿命,大家如果仔细观察的话就会发现有些硬盘或者 NAND Flash就会标明擦除次数(flash
的特性,写之前要先擦除
)
,比如擦除
100000
次等。因此,为了提高块设备寿命而引入了缓冲区,数据先写入到缓冲区中,等满足一定条件后再一次性写入到真正的物理存储设备中,这样就减少了对块设备的擦除次数,提高了块设备寿命。
字符设备是顺序的数据流设备,字符设备是按照字节进行读写访问的。字符设备不需要缓冲区,对于字符设备的访问都是实时的,而且也不需要按照固定的块大小进行访问。
block_device 结构体
linux 内核使用 block_device 表示块设备
,
block_device
为 一 个 结 构 体 , 定 义 在 include/linux/fs.h 文件中,结构体内容如下:
![](https://i-blog.csdnimg.cn/blog_migrate/0f7af3049ef78726d40cf75235badbca.png)
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 中,内容如下
![](https://i-blog.csdnimg.cn/blog_migrate/bdd55a07aaa9e67bca92327d64d6010b.png)
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");