块设备介绍
块是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。
块设备是与字符设备并列的概念,这两类设备在 Linux 中驱动的结构有较大差异,总体而言, 块设备驱动比字符设备驱动要复杂得多,在 I/O 操作上表现出极大的不同,缓冲、 I/O 调度、请求队列等都是与块设备驱动相关的概念。
在Linux中,驱动对块设备的输入或输出(I/O)操作,都会向块设备发出一个请求,在驱动中用request结构体描述。但对于一些磁盘设备而言请求的速度很慢,这时候内核就提供一种队列的机制把这些I/O请求添加到队列中(即:请求队列),在驱动中用request_queue结构体描述。在向块设备提交这些请求前内核会先执行请求的合并和排序预操作,以提高访问的效率,然后再由内核中的I/O调度程序子系统来负责提交 I/O 请求, 调度程序将磁盘资源分配给系统中所有挂起的块 I/O 请求,其工作是管理块设备的请求队列,决定队列中的请求的排列顺序以及什么时候派发请求到设备。
由通用块层(Generic Block Layer)负责维持一个I/O请求在上层文件系统与底层物理磁盘之间的关系。在通用块层中,通常用一个bio结构体来对应一个I/O请求。
Linux提供了一个gendisk数据结构体,用来表示一个独立的磁盘设备或分区,用于对底层物理磁盘进行访问。在gendisk中有一个类似字符设备中file_operations的硬件操作结构指针,是block_device_operations结构体。
- 编写块设备驱动时,使用的一些单位介绍:n 1. 扇区(Sectors):任何块设备硬件对数据处理的基本单位。通常,1个扇区的大小为512字节。(对设备而言)
2. 块 (Blocks):由Linux制定对内核或文件系统等数据处理的基本单位。通常,1个块由1个或多个扇区组成。(对Linux操作系统而言)
n 3. 段(Segments):由若干个相邻的块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。 - IO调度器就是电梯算法。我们知道,磁盘是的读写是通过机械性的移动磁头来实现读写的,理论上磁盘设备满足块设备的随机读写的要求,但是出于节约磁盘,提高效率的考虑,我们希望当磁头处于某一个位置的时候,一起将最近需要写在附近的数据写入,而不是这写一下,那写一下然后再回来,IO调度就是将上层发下来的IO请求的顺序进行重新排序以及对多个请求进行合并,这样就可以实现上述的提高效率、节约磁盘的目的。这种解决问题的思路使用电梯算法,一个运行中的电梯,一个人20楼->1楼,另外一个人15->5楼,电梯不会先将第一个人送到1楼再去15楼接第二个人将其送到5楼,而是从20楼下来,到15楼的时候停下接人,到5楼将第二个放下,最后到达1楼,一句话,电梯算法最终服务的优先顺序并不按照按按钮的先后顺序。
Linux内核中提供了下面的几种电梯算法来实现IO调度:
- No-op I/O scheduler只实现了简单的FIFO的,只进行最简单的合并,比较适合基于Flash的存储
- Anticipatory I/O scheduler推迟IO请求(大约几个微秒),以期能对他们进行排序,获得更高效率
- Deadline I/O scheduler试图把每次请求的延迟降到最低,同时也会对BIO重新排序,特别适用于读取较多的场合,比如数据库
- CFQ I/O scheduler为系统内所有的任务分配均匀的IO带宽,提供一个公平的工作环境,在多媒体环境中,能保证音视频及时从磁盘中读取数据,是当前内核默认的调度器
我们可以通过内核传参的方式指定使用的调度算法: kernel elevator=deadline
或者,使用如下命令改变内核调度算法: echo SCHEDULER > /sys/block/DEVICE/queue/scheduler
1.2 块设备结构介绍
1.2.1 内核自带可参考的块设备驱动源码
drivers\block\z2ram.c
drivers\block\xd.c
\drivers\mmc\host\sdhci-s3c.c
1.2.2 块设备注册与注销函数
1. 注册函数
int register_blkdev(unsigned int major, const char *name) |
函数功能介绍: 注册一个新的块设备
函数参数介绍:
@major:块设备的主设备号[1..255]。 如果major = 0,表示尝试分配未使用的主设备号,返回值就表示分配成功的主设备号。
@name:新块设备的名称。 注意: 该名称必须保证在系统中是唯一的(不能与设备名称重名)。
注册示例:
int Tiny4412_block_major = register_blkdev(0, "Tiny4412_block"); |
2. 注销函数
void unregister_blkdev(unsigned int major, const char *name) |
函数功能介绍: 注销已注册的块设备。
函数参数介绍:
@major: 主设备号
@name: 设备名称
注销示例:
unregister_blkdev(Tiny4412_block_major, "Tiny4412_block"); |
1.2.3 动态分配请求队列
struct request_queue *blk_alloc_queue(gfp_t gfp_mask) |
函数功能介绍: 分配一个默认的请求队列,用该函数生成的请求队列没有设置默认的IO调度器,如果编写的块设备是内存模拟块设备或者是SD卡、Flash等设备,就可以用此函数分配请求队列。
函数参数介绍:
@ gfp_mask : 内存分配的方式。 GFP_KERNEL和GFP_ATOMIC,
GFP_ATOMIC: 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠
GFP_KERNEL: 内核内存的正常分配. 可能睡眠
分配请求队列示例:
struct request_queue *queue= =blk_alloc_queue(GFP_KERNEL); |
卸载驱动时,可以通过kfree释放空间。
如果需要访问外部硬件,比如: 光盘、磁盘等外部物理设备时,要设置默认的调度器,可以调用blk_init_queue函数分配请求队列。
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) |
blk_init_queue()必须与blk_cleanup_queue()调用配对。
函数参数介绍:
@ rfn 是一个函数指针,类型为 typedef void (request_fn_proc) (struct request_queue *q);
@ lock 自旋锁
1.2.4 绑定请求队列
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn) |
函数功能介绍: 绑定blk_alloc_queue函数到请求队列。
上一步介绍的blk_alloc_queue函数分配的请求队列,由于不会使用默认的IO调度器,其中的make_request_fn是没有赋值的,因为上层代码向请求队列发生请求时都是通过make_request_fn这个函数来完成的。对于上层代码发出的请求,可以直接用make_request_fn函数来完成请求并直接将结果返回给上层的代码。
函数参数介绍:
struct request_queue *q :请求队列指针。
make_request_fn *mfn : make_request_fn函数指针。
函数指针的原型如下:
typedef void (make_request_fn) (struct request_queue *q, struct bio *bio); |
该函数指针在Blkdev.h定义。
绑定请求队列示例:
blk_queue_make_request(queue, Tiny4412_block_make_request); |
1.2.5 make_request_fn处理函数编写
//直接提交请求,队列处理 static void Tiny4412_block_make_request(struct request_queue *q, struct bio *bio) { int i; struct bio_vec *bvec; sector_t sector = bio->bi_sector; /*通过for循环遍历一个bio中所有的segment请求*/ bio_for_each_segment(bvec, bio, i) { char *buffer = __bio_kmap_atomic(bio, i, KM_USER0); /*映射内存空间(申请空间)*/ Tiny4412_block_dev_sector_read_write(sector, bio_cur_bytes(bio)>>9 ,buffer, bio_data_dir(bio) == WRITE); /* sector: 当前扇区位置 bio_cur_bytes(bio)>>9: 扇区读写数量 buffer :读写的缓冲区指针首地址 bio_data_dir(bio): 判断是读还是写 */ sector += bio_cur_bytes(bio)>>9; /*偏移扇区*/ __bio_kunmap_atomic(bio, KM_USER0); /*取消映射(释放空间)*/ } bio_endio(bio, 0); /*结束处理*/ return; } |
make_request_fn函数指针传入的参数介绍:
struct bio *bio: 描述块数据传送时怎样完成填充或读取块给driver
struct request_queue *q :传入的请求队列
1.2.6 扇区读写函数实现
代码示例:
unsigned long sector: 当前扇区位置 unsigned long nsect : 扇区读写数量 char *buffer : 读写的缓冲区指针 int write : 是读还是写 */ static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write) { /*块设备最小单位是一个扇区,一个扇区的字节数是512字节*/ unsigned long offset = sector*512; unsigned long nbytes = nsect*512; if((offset + nbytes)>TINY4412_BLKDEV_BYTES) { printk(KERN_NOTICE "写超出范围,强制结束(%ld %ld)\n", offset, nbytes); return; } if(write) /*为真,表示是写*/ memcpy(tiny4412_blkdev_data + offset, buffer, nbytes); else /*读操作*/ memcpy(buffer,tiny4412_blkdev_data + offset, nbytes); } |
1.2.7 分配一个gendisk结构
struct gendisk *alloc_disk(int minors) //动态分配gendisk void del_gendisk(struct gendisk *disk) //注销gendisk |
函数功能介绍:
每个块设备都对应一个gendisk结构,函数alloc_disk用于分配一个gendisk结构。
函数参数介绍:
@minors: 数量
- 给分配的结构填充参数:
/*动态分配次设备号结构*/ gd=alloc_disk(1);/*分配一个gendisk,1表示不能进行分区,只能固定一个分区。 >1表示支持分区的数量 分区可以通过fdsik命令进行操作*/ gd->major=Tiny4412_block_major; 主设备号*/ gd->first_minor=0;次设备号*/ gd->fops=&Tiny4412_block_ops; /*文件操作集合*/ gd->queue=queue; /*将请求队列关联到gendisk结构*/ snprintf(gd->disk_name, 32, "Tiny4412_block_%c",'a'); //设置磁盘名称,在/dev下可以查看该名称 //块设备基本都是使用文件系统函数进行操作,该文件操作集合可以不用自己实现 static struct block_device_operations Tiny4412_block_ops= { .owner = THIS_MODULE, }; |
驱动安装之后,查看的节点信息:
- 设置磁盘的容量
/*注意: 块设备的大小使用扇区作为单位设置,而扇区的大小默认是512字节。 可以查看到设置的大小 512,或者右移9位 */ set_capacity(gd,TINY4412_BLKDEV_BYTES>>9); |
1.2.8 添加磁盘分区信息到内核
void add_disk(struct gendisk *disk) |
函数功能介绍: 将分区信息添加到内核。
函数参数: 填充好gendisk结构。
示例:
add_disk(gd); |
1.2.9 初始化一个请求队列
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) |
示例:
tiny4412_blockdev_queue = blk_init_queue(do_tiny4412_blockdev_request, &tiny4412_blockdev_lock); |
该函数里调用了默认的IO调度器。代码可以参考内核文件: drivers\block\z2ram.c
1.3 块设备示例代码
图1.3.1 块设备框架简图(了解整体框架)
1.3.1 内存模拟块设备(不使用IO调度器)
内存空间采用vmalloc函数进行分配。
#include <linux/module.h> #include <linux/blkdev.h> #include <linux/hdreg.h> #include <linux/version.h> /* * insmod tiny4412_blkdev.ko * # or insmod tiny4412_blkdev.ko size=numK/M/G/T * fdisk /dev/tiny4412_blkdev # create 2 patitions * mkfs.ext2 /dev/tiny4412_blkdev1 * mkfs.ext2 /dev/tiny4412_blkdev2 * mount /dev/tiny4412_blkdev1 /mnt/temp1/ * mount /dev/tiny4412_blkdev2 /mnt/temp2/ * # play in /mnt/temp1/ and /mnt/temp2/ * umount /mnt/temp1/ * umount /mnt/temp2/ * rmmod tiny4412_blkdev.ko * */ static int Tiny4412_block_major=0; static struct request_queue *tiny4412_blkdev_queue; static struct gendisk *tiny4412_blkdev_disk; static unsigned long long tiny4412_blkdev_bytes=1024*1024*10;//10M--空间容量 #define TINY4412_BLKDEV_BYTES_1 (1024*1024*10) /*设置块设备的大小*/ static unsigned char tiny4412_blkdev_data_1[TINY4412_BLKDEV_BYTES_1]; /*用于测试块设备的数组大小*/ /* * Handle an I/O request. * 实现扇区的读写 unsigned long sector: 当前扇区位置 unsigned long nsect : 扇区读写数量 char *buffer : 读写的缓冲区指针 int write : 是读还是写 */ static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write) { /*块设备最小单位是一个扇区,一个扇区的字节数是512字节*/ unsigned long offset = sector; /*写入数据的位置*/ unsigned long nbytes = nsect; /*写入的长度*/ if((offset + nbytes)>TINY4412_BLKDEV_BYTES_1) { printk("写超出范围,强制结束(%ld %ld)\n", offset, nbytes); return; } if(write) /*为真,表示是写*/ memcpy(tiny4412_blkdev_data_1 + offset, buffer, nbytes); else /*读操作*/ memcpy(buffer,tiny4412_blkdev_data_1 + offset, nbytes); } /* 处理请求 */ static int tiny4412_blkdev_make_request(struct request_queue *q, struct bio *bio) { int dir; unsigned long long dsk_offset; struct bio_vec *bvec; int i; void *iovec_mem; /*判断读写方向*/ if(bio_data_dir(bio) == WRITE) dir = 1; else dir = 0; dsk_offset = bio->bi_sector << 9; bio_for_each_segment(bvec, bio, i) { iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; //起始位置,长度,源数据,方向 Tiny4412_block_dev_sector_read_write(dsk_offset,bvec->bv_len,iovec_mem,dir); kunmap(bvec->bv_page); dsk_offset += bvec->bv_len; } bio_endio(bio, 0); return 0; } static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 容量=heads*cylinders*sectors*512 存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数 */ geo->heads = 2; /*磁头(一般一个盘面有两个磁头,正面一个/反面一个)*/ geo->cylinders = 32; /*柱面(一般一个盘面上有32个柱面)每个盘片32个磁道)*/ geo->sectors = TINY4412_BLKDEV_BYTES_1/2/32/512; /*扇区,一般每个磁道上有12个扇区,这里需要根据前面柱面和磁头进行计算,不能乱填*/ return 0; } struct block_device_operations tiny4412_blkdev_fops = { .owner= THIS_MODULE, 命令分区时需要调用该函数,用于读取磁头、柱面、扇区等信息*/ .getgeo= tiny4412_blockdev_getgeo, }; static int __init tiny4412_blkdev_init(void) { /*动态分配请求队列*/ tiny4412_blkdev_queue = blk_alloc_queue(GFP_KERNEL); /*绑定请求队列*/ blk_queue_make_request(tiny4412_blkdev_queue,tiny4412_blkdev_make_request); /*动态分配次设备号结构*/ /*每一个磁盘(分区)都是使用一个gendisk结构保存*/ tiny4412_blkdev_disk = alloc_disk(64); /*磁盘名称赋值*/ strcpy(tiny4412_blkdev_disk->disk_name, "tiny4412_blkdev"); /*注册一个块设备,自动分配主设备号*/ Tiny4412_block_major = register_blkdev(0,"Tiny4412_block"); printk("Tiny4412_block_major=%d\n",Tiny4412_block_major); tiny4412_blkdev_disk->major=Tiny4412_block_major; 主设备号*/ tiny4412_blkdev_disk->first_minor = 0; 次设备号*/ tiny4412_blkdev_disk->fops = &tiny4412_blkdev_fops; /*文件操作结合*/ tiny4412_blkdev_disk->queue = tiny4412_blkdev_queue; /*处理数据请求的队列*/ /*设置磁盘结构 capacity 的容量*/ /*注意: 块设备的大小使用扇区作为单位设置,而扇区的大小默认是512字节。 可以查看到设置的大小 512,或者右移9位 */ set_capacity(tiny4412_blkdev_disk,tiny4412_blkdev_bytes>>9); //添加磁盘信息到内核 add_disk(tiny4412_blkdev_disk); return 0; } static void __exit tiny4412_blkdev_exit(void) { //删除磁盘 del_gendisk(tiny4412_blkdev_disk); put_disk(tiny4412_blkdev_disk); //清除队列 blk_cleanup_queue(tiny4412_blkdev_queue); /*注销块设备*/ unregister_blkdev(Tiny4412_block_major, "Tiny4412_block"); } module_init(tiny4412_blkdev_init); module_exit(tiny4412_blkdev_exit); MODULE_LICENSE("GPL"); |
- 块设备操作过程:
[root@wbyq code]#ls tiny4412_block_device.ko [root@wbyq code]#insmod tiny4412_block_device.ko [ 154.950000] Tiny4412_block_major=253 [ 154.955000] tiny4412_blkdev: unknown partition table (因为使用的内存设备模拟块设备,数组里没有分区表,所以第一次安装设备时,读取不到设备分区表,这个提示是正常的) [root@wbyq code]#fdisk /dev/tiny4412_blkdev Device contains neither a valid DOS partition table, nor Sun, SGI, OSF or GPT disklabel Building a new DOS disklabel. Changes will remain in memory only, until you decide to write them. After that the previous content won't be recoverable. Command (m for help): n(n表示新建分区表) Command action e extended(表示扩展分区) p primary partition (1-4)(表示主分区) p (选择p创建主分区) Partition number (1-4): 1 (创建的主分区编号为1) First cylinder (1-160, default 1): 1(柱面起始位置设置为1, 1-160表示当前磁盘剩余的未分区的柱面范围为1-160) Last cylinder or +size or +sizeM or +sizeK (1-160, default 160): 100 (设置柱面的结束位置) Command (m for help): n (表示主分区) Command action e extended p primary partition (1-4) p (选择p创建主分区) Partition number (1-4): 2 (创建的主分区编号为2) First cylinder (101-160, default 101): 101(柱面起始位置设置为101, 101-160表示当前磁盘剩余的未分区的柱面范围为101-160) Last cylinder or +size or +sizeM or +sizeK (101-160, default 160): 160 (设置柱面的结束位置) Command (m for help): p (打印当前的分区情况) Disk /dev/tiny4412_blkdev: 10 MB, 10485760 bytes (磁盘的总容量: M单位,字节单位) 2 heads, 64 sectors/track, 160 cylinders (一个共有2个磁头,每个柱面有64个扇区,160个柱面) (提示: 存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数) Units = cylinders of 128 * 512 = 65536 bytes Device Boot Start End Blocks Id System /dev/tiny4412_blkdev11100 6368 83 Linux (分区1的信息) Partition 1 has different physical/logical endings: phys=(355, 1, 0) logical=(99, 1, 64) Partition 1 does not end on cylinder boundary /dev/tiny4412_blkdev2101160 3840 83 Linux (分区2的信息) Partition 2 has different physical/logical endings: phys=(415, 1, 0) logical=(159, 1, 64) Partition 2 does not end on cylinder boundary Command (m for help): w (w表示保存分区表,写入磁盘,并退出fdisk命令行) The partition table has been altered. (提示,分区表示已经更改) Calling ioctl() to re-read partition table (调用ioctl()重新读取分区表) [ 218.905000] tiny4412_blkdev: tiny4412_blkdev1 tiny4412_blkdev2(提示分区之后创建成功的设备节点) [root@wbyq code]#ls /dev/tiny4412_blkdev* -l(查看/dev下分区成功的设备节点) brw-rw---- 1 root root 253, 0 Nov 24 2018 /dev/tiny4412_blkdev brw-rw---- 1 root root 253, 1 Nov 24 2018 /dev/tiny4412_blkdev1 brw-rw---- 1 root root 253, 2 Nov 24 2018 /dev/tiny4412_blkdev2 [root@wbyq code]#mkfs.ext2 /dev/tiny4412_blkdev1(给第一个设备分区进行格式化文件系统) Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 1592 inodes, 6368 blocks 318 blocks (5%) reserved for the super user First data block=1 Maximum filesystem blocks=262144 1 block groups 8192 blocks per group, 8192 fragments per group 1592 inodes per group [root@wbyq code]#mkfs.ext2 /dev/tiny4412_blkdev2(给第二个设备分区进行格式化文件系统) Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 960 inodes, 3840 blocks 192 blocks (5%) reserved for the super user First data block=1 Maximum filesystem blocks=262144 1 block groups 8192 blocks per group, 8192 fragments per group 960 inodes per group [root@wbyq code]#mount /dev/tiny4412_blkdev1 /mnt/ (将第一个分区挂载到/mnt目录下) [root@wbyq code]#mount /dev/tiny4412_blkdev2 /tmp/ (将第二个分区挂载到/mnt目录下) [root@wbyq code]#df -h (查看当前系统磁盘的信息) Filesystem Size Used Available Use% Mounted on 192.168.10.11:/work/rootfs/ 48.1G 16.5G 29.1G 36% / /dev/tiny4412_blkdev1 6.0M 13.0K 5.7M 0% /mnt /dev/tiny4412_blkdev2 3.6M 13.0K 3.4M 0% /tmp /*下面就是分别进入到挂载的目录下进行文件拷贝操作,测试块设备是否正常,最后在取消挂载退出*/ [root@wbyq code]#cd /mnt/ [root@wbyq mnt]#ls lost+found [root@wbyq mnt]#cp /123.mp3 ./ [root@wbyq mnt]# [root@wbyq mnt]#cd /tmp/ [root@wbyq tmp]#cp /123.mp3 ./ [root@wbyq tmp]# [root@wbyq tmp]#ls 123.mp3 lost+found [root@wbyq tmp]#cd / [root@wbyq ]#umount /tmp/ [root@wbyq ]#umount /mnt/ |
1.3.2 使用SD卡编写块设备(不使用IO调度器)
SD卡采用SPI协议通信,底层采用模拟的SPI系统,没有使用SPI子系统。
- 示例代码:
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/fs.h> /* everything... */ #include <linux/errno.h>/* error codes */ #include <linux/timer.h> #include <linux/types.h>/* size_t */ #include <linux/fcntl.h>/* O_ACCMODE */ #include <linux/hdreg.h>/* HDIO_GETGEO */ #include <linux/kdev_t.h> #include <linux/vmalloc.h> #include <linux/genhd.h> #include <linux/blkdev.h> #include <linux/bio.h> #include <linux/init.h> #include <linux/module.h> #include <linux/ioctl.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/err.h> #include <linux/list.h> #include <linux/errno.h> #include <linux/mutex.h> #include <linux/slab.h> #include <linux/compat.h> #include <linux/spi/spi.h> #include <linux/spi/spidev.h> #include <asm/uaccess.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/mutex.h> #include <linux/miscdevice.h> /*杂项字符设备头文件*/ /*--------------------------------SD相关操作代码---------------------------------------------*/ /*定义指针,用于接收虚拟地址*/ volatile unsigned int *SD_GPBCON; volatile unsigned int *SD_GPBDAT; /* 函数功能:SD初始化 Tiny4412硬件连接: DO--MISO :GPB_2 DI--MOSI :GPB_3 CLK-SCLK :GPB_0 CS--CS :GPB_1 */ void SDCardSpiInit(void) { /*1. 初始化GPIO*/ /*映射物理地址*/ SD_GPBCON=ioremap(0x11400040,4); SD_GPBDAT=ioremap(0x11400044,4); *SD_GPBCON &= ~(0xf << 0 * 4);*SD_GPBCON |= (0x1 << 0 * 4); *SD_GPBCON &= ~(0xf << 1 * 4);*SD_GPBCON |= (0x1 << 1 * 4); *SD_GPBCON &= ~(0xf << 2 * 4); *SD_GPBCON &= ~(0xf << 3 * 4);*SD_GPBCON |= (0x1 << 3 * 4); /*2. 上拉GPIO口*/ //*SD_GPBDAT &= ~(1 << 4);//输出0 *SD_GPBDAT |= (1 << 0); //输出1 *SD_GPBDAT |= (1 << 1); //输出1 *SD_GPBDAT |= (1 << 3); //输出1 } // SD卡类型定义 #define SDCard_TYPE_ERR 0X00 //卡类型错误 #define SDCard_TYPE_MMC 0X01 //MMC卡 #define SDCard_TYPE_V1 0X02 #define SDCard_TYPE_V2 0X04 #define SDCard_TYPE_V2HC 0X06 // SD卡指令表 #define SDCard_CMD0 0 //卡复位 #define SDCard_CMD1 1 #define SDCard_CMD8 8 //命令8 ,SEND_IF_COND #define SDCard_CMD9 9 //命令9 ,读CSD数据 #define SDCard_CMD10 10 //命令10,读CID数据 #define SDCard_CMD12 12 //命令12,停止数据传输 #define SDCard_CMD13 16 //命令16,设置扇区大小 应返回0x00 #define SDCard_CMD17 17 //命令17,读扇区 #define SDCard_CMD18 18 //命令18,读Multi 扇区 #define SDCard_CMD23 23 //命令23,设置多扇区写入前预先擦除N个block #define SDCard_CMD24 24 //命令24,写扇区 #define SDCard_CMD25 25 //命令25,写多个扇区 #define SDCard_CMD41 41 //命令41,应返回0x00 #define SDCard_CMD55 55 //命令55,应返回0x01 #define SDCard_CMD58 58 //命令58,读OCR信息 #define SDCard_CMD59 59 //命令59,使能/禁止CRC,应返回0x00、 /*SD卡回应标记字*/ #define SDCard_RESPONSE_NO_ERROR 0x00 //正确回应 #define SDCard_SD_IN_IDLE_STATE 0x01 //闲置状态 #define SDCard_SD_ERASE_RESET 0x02 //擦除复位 #define SDCard_RESPONSE_FAILURE 0xFF //响应失败 //函数声明 unsigned char SDCardReadWriteOneByte(unsigned char data); //底层接口,SPI读写字节函数 unsigned char SDCardWaitBusy(void);//等待SD卡准备 unsigned char SDCardGetAck(unsigned char Response);//获得应答 unsigned char SDCardDeviceInit(void);初始化 unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt);读块(扇区) unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt);写块(扇区) unsigned int GetSDCardSectorCount(void); 读扇区数 unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data); //读SD卡CID unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data); //读SD卡CSD static unsigned char SD_Type=0; //存放SD卡的类型 /* 函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节 函数参数:data是要写入的数据 返 回 值:读到的数据 说明:时序是第二个上升沿采集数据 */ unsigned char SDCardReadWriteOneByte(unsigned char data_tx) { u8 data_rx=0; u8 i; for(i=0;i<8;i++) { *SD_GPBDAT &= ~(1 << 0);//输出0 if(data_tx&0x80)*SD_GPBDAT |= (1 << 3); //输出1 else *SD_GPBDAT &= ~(1 << 3);//输出0 data_tx<<=1; //继续发送下一个数据 *SD_GPBDAT |= (1 << 0); //输出1 data_rx<<=1; if((*SD_GPBDAT & (1 << 2)))data_rx|=0x01; } return data_rx; } /* 函数功能:取消选择,释放SPI总线 */ void SDCardCancelCS(void) { *SD_GPBDAT |= (1 << 1); SDCardReadWriteOneByte(0xff);//提供额外的8个时钟 } /* 函数 功 能:选择sd卡,并且等待卡准备OK 函数返回值:0,成功;1,失败; */ unsigned char SDCardSelectCS(void) { *SD_GPBDAT &= ~(1 << 1); if(SDCardWaitBusy()==0)return 0;//等待成功 SDCardCancelCS(); return 1;//等待失败 } /* 函数 功 能:等待卡准备好 函数返回值:0,准备好了;其他,错误代码 */ unsigned char SDCardWaitBusy(void) { unsigned int t=0; do { if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK t++; }while(t<0xFFFFFF);//等待 return 1; } /* 函数功能:等待SD卡回应 函数参数: Response:要得到的回应值 返 回 值: 0,成功得到了该回应值 其他,得到回应值失败 */ unsigned char SDCardGetAck(unsigned char Response) { u16 Count=0xFFFF;//等待次数 while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应 if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回应失败 else return SDCard_RESPONSE_NO_ERROR;//正确回应 } /* 函数功能:从sd卡读取一个数据包的内容 函数参数: buf:数据缓存区 len:要读取的数据长度. 返回值: 0,成功;其他,失败; */ unsigned char SDCardRecvData(unsigned char*buf,u16 len) { if(SDCardGetAck(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE 开始接收数据 { *buf=SDCardReadWriteOneByte(0xFF); buf++; } 下面是2个伪CRC(dummy CRC) SDCardReadWriteOneByte(0xFF); SDCardReadWriteOneByte(0xFF); 读取成功 } /* 函数功能:向sd卡写入一个数据包的内容 512字节 函数参数: buf 数据缓存区 cmd 指令 返 回 值:0表示成功;其他值表示失败; */ unsigned char SDCardSendData(unsigned char*buf,unsigned char cmd) { u16 t; if(SDCardWaitBusy())return 1; //等待准备失效 SDCardReadWriteOneByte(cmd); if(cmd!=0XFD)//不是结束指令 { for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,减少函数传参时间 忽略crc SDCardReadWriteOneByte(0xFF); 接收响应 if((t&0x1F)!=0x05)return 2; //响应错误 } 写入成功 } /* 函数功能:向SD卡发送一个命令 函数参数: unsigned char cmd 命令 unsigned int arg 命令参数 unsigned char crc crc校验值 返回值:SD卡返回的响应 */ unsigned char SendSDCardCmd(unsigned char cmd, unsigned int arg, unsigned char crc) { unsigned char r1; unsigned char Retry=0; SDCardCancelCS(); //取消上次片选 if(SDCardSelectCS())return 0XFF;//片选失效 //发送数据 SDCardReadWriteOneByte(cmd | 0x40);//分别写入命令 SDCardReadWriteOneByte(arg >> 24); SDCardReadWriteOneByte(arg >> 16); SDCardReadWriteOneByte(arg >> 8); SDCardReadWriteOneByte(arg); SDCardReadWriteOneByte(crc); if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading Retry=0X1F; do { r1=SDCardReadWriteOneByte(0xFF); }while((r1&0X80) && Retry--);等待响应,或超时退出 return r1;//返回状态值 } /* 函数功能:获取SD卡的CID信息,包括制造商信息 函数参数:unsigned char *cid_data(存放CID的内存,至少16Byte) 返 回 值: 0:成功,1:错误 */ unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data) { unsigned char r1; 发SDCard_CMD10命令,读CID r1=SendSDCardCmd(SDCard_CMD10,0,0x01); if(r1==0x00) { r1=SDCardRecvData(cid_data,16);//接收16个字节的数据 } SDCardCancelCS();//取消片选 if(r1)return 1; else return 0; } /* 函数说明: 获取SD卡的CSD信息,包括容量和速度信息 函数参数: unsigned char *cid_data(存放CID的内存,至少16Byte) 返 回 值: 0:成功,1:错误 */ unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data) { unsigned char r1; r1=SendSDCardCmd(SDCard_CMD9,0,0x01); //发SDCard_CMD9命令,读CSD if(r1==0) { r1=SDCardRecvData(csd_data, 16);//接收16个字节的数据 } SDCardCancelCS();//取消片选 if(r1)return 1; else return 0; } /* 函数功能:获取SD卡的总扇区数(扇区数) 返 回 值: 0表示容量检测出错,其他值表示SD卡的容量(扇区数/512字节) 说 明: 每扇区的字节数必为512字节,如果不是512字节,则初始化不能通过. */ unsigned int GetSDCardSectorCount(void) { unsigned char csd[16]; unsigned int Capacity; unsigned char n; u16 csize; if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0;//取CSD信息,如果期间出错,返回0 if((csd[0]&0xC0)==0x40)的卡,如果为SDHC卡,按照下面方式计算 { csize = csd[9] + ((u16)csd[8] << 8) + 1; Capacity = (unsigned int)csize << 10;//得到扇区数 } else//V1.XX的卡 { n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1; Capacity= (unsigned int)csize << (n - 9);//得到扇区数 } return Capacity; } /* 函数功能: 初始化SD卡 返 回 值: 非0表示初始化失败! */ unsigned char SDCardDeviceInit(void) { 存放SD卡的返回值 用来进行超时计数 unsigned char buf[4]; u16 i; SDCardSpiInit();//初始化底层IO口 for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //发送最少74个脉冲 retry=20; do { r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//进入IDLE状态 闲置 }while((r1!=0X01) && retry--); SD_Type=0; //默认无卡 if(r1==0X01) { if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1) //SD V2.0 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//Get trailing return value of R7 resp if(buf[2]==0X01&&buf[3]==0XAA) //卡是否支持2.7~3.6V { retry=0XFFFE; do { SendSDCardCmd(SDCard_CMD55,0,0X01);发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//发送SDCard_CMD41 }while(r1&&retry--); if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值 if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC; //检查CCS else SD_Type=SDCard_TYPE_V2; } } } else//SD V1.x/ MMCV3 { SendSDCardCmd(SDCard_CMD55,0,0X01);//发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//发送SDCard_CMD41 if(r1<=1) { SD_Type=SDCard_TYPE_V1; retry=0XFFFE; do //等待退出IDLE模式 { SendSDCardCmd(SDCard_CMD55,0,0X01);//发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//发送SDCard_CMD41 }while(r1&&retry--); } else//MMC卡不支持SDCard_CMD55+SDCard_CMD41识别 { SD_Type=SDCard_TYPE_MMC;//MMC V3 retry=0XFFFE; do //等待退出IDLE模式 { r1=SendSDCardCmd(SDCard_CMD1,0,0X01);//发送SDCard_CMD1 }while(r1&&retry--); } if(retry==0||SendSDCardCmd(SDCard_CMD13,512,0X01)!=0)SD_Type=SDCard_TYPE_ERR;//错误的卡 } } SDCardCancelCS(); //取消片选 if(SD_Type)return 0; //初始化成功返回0 else if(r1)return r1; //返回值错误值 return 0xaa; //其他错误 } /* 函数功能:读SD卡 函数参数: buf:数据缓存区 sector:扇区 cnt:扇区数 返回值: 0,ok;其他,失败. 说 明: SD卡一个扇区大小512字节 */ unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt) { unsigned char r1; if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//转换为字节地址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//读命令 if(r1==0)指令发送成功 { r1=SDCardRecvData(buf,512);//接收512个字节 } }else { r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//连续读命令 do { r1=SDCardRecvData(buf,512);//接收512个字节 buf+=512; }while(--cnt && r1==0); SendSDCardCmd(SDCard_CMD12,0,0X01);//发送停止命令 } SDCardCancelCS();//取消片选 return r1;// } /* 函数功能:向SD卡写数据 函数参数: buf:数据缓存区 sector:起始扇区 cnt:扇区数 返回值: 0,ok;其他,失败. 说 明: SD卡一个扇区大小512字节 */ unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt) { unsigned char r1; if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//转换为字节地址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//读命令 if(r1==0)//指令发送成功 { r1=SDCardSendData(buf,0xFE);//写512个字节 } } else { if(SD_Type!=SDCard_TYPE_MMC) { SendSDCardCmd(SDCard_CMD55,0,0X01); SendSDCardCmd(SDCard_CMD23,cnt,0X01);//发送指令 } r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//连续读命令 if(r1==0) { do { r1=SDCardSendData(buf,0xFC);//接收512个字节 buf+=512; }while(--cnt && r1==0); r1=SDCardSendData(0,0xFD);//接收512个字节 } } SDCardCancelCS();//取消片选 return r1;// } /* 功能说明: 1. 支持文件系统格式化: #mkfs.ext2 /dev/tiny4412_block_a 2. 支持mount挂载: #mount /dev/tiny4412_block_a /mnt/ 3. 支持磁盘大小查看: #cat /sys/block/Tiny4412_block_a/size #df -h */ static struct request_queue *queue=NULL; /* 设备请求队列 */ static struct gendisk *gd; 结构 */ static unsigned int sd_size=0; 存放SD卡返回的容量扇区数量单位(512字节) static int Tiny4412_block_major = 0; /*存放主设备号*/ static DEFINE_MUTEX(sd_mutex); /*静态定义互斥锁*/ /* * Handle an I/O request. * 实现扇区的读写 */ static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write) { /*互斥锁,上锁*/ mutex_lock(&sd_mutex); sector>>=9; nsect>>=9; /*块设备最小单位是一个扇区,一个扇区的字节数是512字节*/ if(write) { if(SDCardWriteData(buffer,sector,nsect)) { printk(KERN_ERR"write error!\r\n"); printk("write ---> nsect=%ld,sector=%ld\r\n",nsect,sector); } } else { if(SDCardReadData(buffer,sector,nsect)) { printk(KERN_ERR"read error!\r\n"); printk("read ---> nsect=%ld,sector=%ld\r\n",nsect,sector); } } /*互斥锁解锁*/ mutex_unlock(&sd_mutex); } /* 处理请求 */ static void Tiny4412_block_make_request(struct request_queue *q, struct bio *bio) { int dir; unsigned long long dsk_offset; struct bio_vec *bvec; int i; void *iovec_mem; /*判断读写方向*/ if(bio_data_dir(bio) == WRITE) dir = 1; else dir = 0; dsk_offset = bio->bi_sector << 9; bio_for_each_segment(bvec, bio, i) { iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; //起始位置,长度,源数据,方向 Tiny4412_block_dev_sector_read_write(dsk_offset,bvec->bv_len,iovec_mem,dir); kunmap(bvec->bv_page); dsk_offset += bvec->bv_len; } bio_endio(bio, 0); } static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 容量=heads*cylinders*sectors*512 存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数 */ geo->heads = 2; /*磁头(一般一个盘面有两个磁头,正面一个/反面一个)*/ geo->cylinders = 32; /*柱面(一般一个盘面上有32个柱面)每个盘片32个磁道)*/ geo->sectors = sd_size/2/32; /*扇区,一般每个磁道上有12个扇区,这里需要根据前面柱面和磁头进行计算,不能乱填*/ /*geo->sectors =存储容量/磁头数/柱面/每扇区字节数*/ return 0; } /* * 块设备文件操作集合接口 */ static struct block_device_operations Tiny4412_block_ops= { .owner = THIS_MODULE, 命令分区时需要调用该函数,用于读取磁头、柱面、扇区等信息*/ .getgeo= tiny4412_blockdev_getgeo, }; /* 驱动入口 */ static int __init Tiny4412_block_init(void) { /*1. 初始化SD口*/ if(SDCardDeviceInit()) { 卡初始化失败!\r\n"); return -1; } /*2. 检测SD卡大小*/ sd_size=GetSDCardSectorCount();//检测SD卡大小,返回值右移11位得到以M为单位的容量 printk("SD卡Sizeof:%dM secnt=%d\r\n",sd_size>>11,sd_size); /*注册一个块设备,自动分配主设备号*/ Tiny4412_block_major = register_blkdev(0, "Tiny4412_SDdrv"); /*动态分配请求队列*/ queue=blk_alloc_queue(GFP_KERNEL); /*绑定请求队列*/ blk_queue_make_request(queue, Tiny4412_block_make_request); /*动态分配次设备号结构*/ gd=alloc_disk(16);/*分配一个gendisk,分区是一个*/ gd->major=Tiny4412_block_major; 主设备号*/ gd->first_minor=0;次设备号*/ gd->fops=&Tiny4412_block_ops; gd->queue=queue; 将请求队列关联到gendisk结构*/ snprintf(gd->disk_name, 32, "tiny4412_sd%c",'a'); /*设置磁盘结构 capacity 的容量*/ /*注意: 块设备的大小使用扇区作为单位设置,而扇区的大小默认是512字节。 可以查看到设置的大小 512,或者右移9位 */ set_capacity(gd,sd_size); /*注册磁盘设备*/ add_disk(gd); printk("块设备注册成功!\r\n"); return 0; } /*驱动出口*/ static void Tiny4412_block_exit(void) { /*释放虚拟地址*/ iounmap(SD_GPBCON); iounmap(SD_GPBDAT); /*注销磁盘设备*/ if(gd)del_gendisk(gd); /*注销块设备*/ if(Tiny4412_block_major!=0)unregister_blkdev(Tiny4412_block_major, "Tiny4412_SDdrv"); printk("块设备注消成功!\r\n"); } module_init(Tiny4412_block_init); module_exit(Tiny4412_block_exit); MODULE_LICENSE("GPL"); |
1.3.3 内存模拟块设备(使用默认的IO调度器)
- 示例代码:
/* 参考:搜索注册函数即可 * drivers\block\xd.c * drivers\block\z2ram.c */ #include <linux/module.h> #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/timer.h> #include <linux/genhd.h> #include <linux/hdreg.h> #include <linux/ioport.h> #include <linux/init.h> #include <linux/wait.h> #include <linux/blkdev.h> #include <linux/blkpg.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/vmalloc.h> #include <linux/hdreg.h> #include <linux/version.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/dma.h> static struct gendisk *tiny4412_blockdev_disk; static struct request_queue *tiny4412_blockdev_queue; static int major; static DEFINE_SPINLOCK(tiny4412_blockdev_lock); #define RAMBLOCK_SIZE (1024*1024*10) /*10M空间*/ static unsigned char *tiny4412_blockdev_buf=NULL; static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 容量=heads*cylinders*sectors*512 存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数 */ geo->heads = 2; /*磁头(一般一个盘面有两个磁头,正面一个/反面一个)*/ geo->cylinders = 32; /*柱面(一般一个盘面上有32个柱面)每个盘片32个磁道)*/ geo->sectors = RAMBLOCK_SIZE/2/32/512; /*扇区,一般每个磁道上有12个扇区,这里需要根据前面柱面和磁头进行计算,不能乱填*/ return 0; } static struct block_device_operations tiny4412_blockdev_fops = { .owner= THIS_MODULE, .getgeo= tiny4412_blockdev_getgeo, }; static void do_tiny4412_blockdev_request(struct request_queue * q) { struct request *req; req = blk_fetch_request(q); while (req) { unsigned long start = blk_rq_pos(req) *512; /*转为字节单位(起始位置)*/ unsigned long len = blk_rq_cur_bytes(req); /*当前操作的字节数量*/ int err = 0; if (rq_data_dir(req) == READ) /*如果是读*/ memcpy(req->buffer, tiny4412_blockdev_buf+start, len); else memcpy(tiny4412_blockdev_buf+start, req->buffer,len); if (!__blk_end_request_cur(req, err)) /*判断是否处理完毕请求*/ req = blk_fetch_request(q); /*继续处理下一个请求*/ } } static int tiny4412_blockdev_init(void) { /* 1. 分配一个gendisk结构体 */ tiny4412_blockdev_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */ /* 2. 设置 */ /* 2.1 分配/设置队列: 提供读写能力 */ tiny4412_blockdev_queue = blk_init_queue(do_tiny4412_blockdev_request, &tiny4412_blockdev_lock); tiny4412_blockdev_disk->queue = tiny4412_blockdev_queue; /* 2.2 设置其他属性: 比如容量 */ major = register_blkdev(0, "tiny4412_blockdev"); /* cat /proc/devices */ tiny4412_blockdev_disk->major = major; tiny4412_blockdev_disk->first_minor = 0; sprintf(tiny4412_blockdev_disk->disk_name, "tiny4412_blockdev"); tiny4412_blockdev_disk->fops = &tiny4412_blockdev_fops; set_capacity(tiny4412_blockdev_disk, RAMBLOCK_SIZE / 512); /* 3. 硬件相关操作 */ tiny4412_blockdev_buf = vmalloc(RAMBLOCK_SIZE); if(tiny4412_blockdev_buf==NULL) { printk("空间申请失败!\n"); return -1; } /* 4. 注册 */ add_disk(tiny4412_blockdev_disk); return 0; } static void tiny4412_blockdev_exit(void) { unregister_blkdev(major, "tiny4412_blockdev"); del_gendisk(tiny4412_blockdev_disk); put_disk(tiny4412_blockdev_disk); blk_cleanup_queue(tiny4412_blockdev_queue); vfree(tiny4412_blockdev_buf); } module_init(tiny4412_blockdev_init); module_exit(tiny4412_blockdev_exit); MODULE_LICENSE("GPL"); |
1.3.4 使用SD卡编写块设备(使用默认的IO调度器)
/* 参考: * drivers\block\xd.c * drivers\block\z2ram.c */ #include <linux/module.h> #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/timer.h> #include <linux/genhd.h> #include <linux/hdreg.h> #include <linux/ioport.h> #include <linux/init.h> #include <linux/wait.h> #include <linux/blkdev.h> #include <linux/blkpg.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/vmalloc.h> #include <linux/hdreg.h> #include <linux/version.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/dma.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/delay.h> #include <linux/io.h> /*--------------------------------SD相关操作代码---------------------------------------------*/ /*定义指针,用于接收虚拟地址*/ volatile unsigned int *SD_GPBCON; volatile unsigned int *SD_GPBDAT; /* 函数功能:SD初始化 Tiny4412硬件连接: DO--MISO :GPB_2 DI--MOSI :GPB_3 CLK-SCLK :GPB_0 CS--CS :GPB_1 */ void SDCardSpiInit(void) { /*1. 初始化GPIO*/ /*映射物理地址*/ SD_GPBCON=ioremap(0x11400040,4); SD_GPBDAT=ioremap(0x11400044,4); *SD_GPBCON &= ~(0xf << 0 * 4);*SD_GPBCON |= (0x1 << 0 * 4); *SD_GPBCON &= ~(0xf << 1 * 4);*SD_GPBCON |= (0x1 << 1 * 4); *SD_GPBCON &= ~(0xf << 2 * 4); *SD_GPBCON &= ~(0xf << 3 * 4);*SD_GPBCON |= (0x1 << 3 * 4); /*2. 上拉GPIO口*/ //*SD_GPBDAT &= ~(1 << 4);//输出0 *SD_GPBDAT |= (1 << 0); //输出1 *SD_GPBDAT |= (1 << 1); //输出1 *SD_GPBDAT |= (1 << 3); //输出1 } // SD卡类型定义 #define SDCard_TYPE_ERR 0X00 //卡类型错误 #define SDCard_TYPE_MMC 0X01 //MMC卡 #define SDCard_TYPE_V1 0X02 #define SDCard_TYPE_V2 0X04 #define SDCard_TYPE_V2HC 0X06 // SD卡指令表 #define SDCard_CMD0 0 //卡复位 #define SDCard_CMD1 1 #define SDCard_CMD8 8 //命令8 ,SEND_IF_COND #define SDCard_CMD9 9 //命令9 ,读CSD数据 #define SDCard_CMD10 10 //命令10,读CID数据 #define SDCard_CMD12 12 //命令12,停止数据传输 #define SDCard_CMD13 16 //命令16,设置扇区大小 应返回0x00 #define SDCard_CMD17 17 //命令17,读扇区 #define SDCard_CMD18 18 //命令18,读Multi 扇区 #define SDCard_CMD23 23 //命令23,设置多扇区写入前预先擦除N个block #define SDCard_CMD24 24 //命令24,写扇区 #define SDCard_CMD25 25 //命令25,写多个扇区 #define SDCard_CMD41 41 //命令41,应返回0x00 #define SDCard_CMD55 55 //命令55,应返回0x01 #define SDCard_CMD58 58 //命令58,读OCR信息 #define SDCard_CMD59 59 //命令59,使能/禁止CRC,应返回0x00、 /*SD卡回应标记字*/ #define SDCard_RESPONSE_NO_ERROR 0x00 //正确回应 #define SDCard_SD_IN_IDLE_STATE 0x01 //闲置状态 #define SDCard_SD_ERASE_RESET 0x02 //擦除复位 #define SDCard_RESPONSE_FAILURE 0xFF //响应失败 //函数声明 unsigned char SDCardReadWriteOneByte(unsigned char data); //底层接口,SPI读写字节函数 unsigned char SDCardWaitBusy(void);//等待SD卡准备 unsigned char SDCardGetAck(unsigned char Response);//获得应答 unsigned char SDCardDeviceInit(void);初始化 unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt);读块(扇区) unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt);写块(扇区) unsigned int GetSDCardSectorCount(void); 读扇区数 unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data); //读SD卡CID unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data); //读SD卡CSD static unsigned char SD_Type=0; //存放SD卡的类型 /* 函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节 函数参数:data是要写入的数据 返 回 值:读到的数据 说明:时序是第二个上升沿采集数据 */ unsigned char SDCardReadWriteOneByte(unsigned char data_tx) { u8 data_rx=0; u8 i; for(i=0;i<8;i++) { *SD_GPBDAT &= ~(1 << 0);//输出0 if(data_tx&0x80)*SD_GPBDAT |= (1 << 3); //输出1 else *SD_GPBDAT &= ~(1 << 3);//输出0 data_tx<<=1; //继续发送下一个数据 *SD_GPBDAT |= (1 << 0); //输出1 data_rx<<=1; if((*SD_GPBDAT & (1 << 2)))data_rx|=0x01; } return data_rx; } /* 函数功能:取消选择,释放SPI总线 */ void SDCardCancelCS(void) { *SD_GPBDAT |= (1 << 1); SDCardReadWriteOneByte(0xff);//提供额外的8个时钟 } /* 函数 功 能:选择sd卡,并且等待卡准备OK 函数返回值:0,成功;1,失败; */ unsigned char SDCardSelectCS(void) { *SD_GPBDAT &= ~(1 << 1); if(SDCardWaitBusy()==0)return 0;//等待成功 SDCardCancelCS(); return 1;//等待失败 } /* 函数 功 能:等待卡准备好 函数返回值:0,准备好了;其他,错误代码 */ unsigned char SDCardWaitBusy(void) { unsigned int t=0; do { if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK t++; }while(t<0xFFFFFF);//等待 return 1; } /* 函数功能:等待SD卡回应 函数参数: Response:要得到的回应值 返 回 值: 0,成功得到了该回应值 其他,得到回应值失败 */ unsigned char SDCardGetAck(unsigned char Response) { u16 Count=0xFFFF;//等待次数 while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应 if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回应失败 else return SDCard_RESPONSE_NO_ERROR;//正确回应 } /* 函数功能:从sd卡读取一个数据包的内容 函数参数: buf:数据缓存区 len:要读取的数据长度. 返回值: 0,成功;其他,失败; */ unsigned char SDCardRecvData(unsigned char*buf,u16 len) { if(SDCardGetAck(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE 开始接收数据 { *buf=SDCardReadWriteOneByte(0xFF); buf++; } 下面是2个伪CRC(dummy CRC) SDCardReadWriteOneByte(0xFF); SDCardReadWriteOneByte(0xFF); 读取成功 } /* 函数功能:向sd卡写入一个数据包的内容 512字节 函数参数: buf 数据缓存区 cmd 指令 返 回 值:0表示成功;其他值表示失败; */ unsigned char SDCardSendData(unsigned char*buf,unsigned char cmd) { u16 t; if(SDCardWaitBusy())return 1; //等待准备失效 SDCardReadWriteOneByte(cmd); if(cmd!=0XFD)//不是结束指令 { for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,减少函数传参时间 忽略crc SDCardReadWriteOneByte(0xFF); 接收响应 if((t&0x1F)!=0x05)return 2; //响应错误 } 写入成功 } /* 函数功能:向SD卡发送一个命令 函数参数: unsigned char cmd 命令 unsigned int arg 命令参数 unsigned char crc crc校验值 返回值:SD卡返回的响应 */ unsigned char SendSDCardCmd(unsigned char cmd, unsigned int arg, unsigned char crc) { unsigned char r1; unsigned char Retry=0; SDCardCancelCS(); //取消上次片选 if(SDCardSelectCS())return 0XFF;//片选失效 //发送数据 SDCardReadWriteOneByte(cmd | 0x40);//分别写入命令 SDCardReadWriteOneByte(arg >> 24); SDCardReadWriteOneByte(arg >> 16); SDCardReadWriteOneByte(arg >> 8); SDCardReadWriteOneByte(arg); SDCardReadWriteOneByte(crc); if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading Retry=0X1F; do { r1=SDCardReadWriteOneByte(0xFF); }while((r1&0X80) && Retry--);等待响应,或超时退出 return r1;//返回状态值 } /* 函数功能:获取SD卡的CID信息,包括制造商信息 函数参数:unsigned char *cid_data(存放CID的内存,至少16Byte) 返 回 值: 0:成功,1:错误 */ unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data) { unsigned char r1; 发SDCard_CMD10命令,读CID r1=SendSDCardCmd(SDCard_CMD10,0,0x01); if(r1==0x00) { r1=SDCardRecvData(cid_data,16);//接收16个字节的数据 } SDCardCancelCS();//取消片选 if(r1)return 1; else return 0; } /* 函数说明: 获取SD卡的CSD信息,包括容量和速度信息 函数参数: unsigned char *cid_data(存放CID的内存,至少16Byte) 返 回 值: 0:成功,1:错误 */ unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data) { unsigned char r1; r1=SendSDCardCmd(SDCard_CMD9,0,0x01); //发SDCard_CMD9命令,读CSD if(r1==0) { r1=SDCardRecvData(csd_data, 16);//接收16个字节的数据 } SDCardCancelCS();//取消片选 if(r1)return 1; else return 0; } /* 函数功能:获取SD卡的总扇区数(扇区数) 返 回 值: 0表示容量检测出错,其他值表示SD卡的容量(扇区数/512字节) 说 明: 每扇区的字节数必为512字节,如果不是512字节,则初始化不能通过. */ unsigned int GetSDCardSectorCount(void) { unsigned char csd[16]; unsigned int Capacity; unsigned char n; u16 csize; if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0;//取CSD信息,如果期间出错,返回0 if((csd[0]&0xC0)==0x40)的卡,如果为SDHC卡,按照下面方式计算 { csize = csd[9] + ((u16)csd[8] << 8) + 1; Capacity = (unsigned int)csize << 10;//得到扇区数 } else//V1.XX的卡 { n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1; Capacity= (unsigned int)csize << (n - 9);//得到扇区数 } return Capacity; } /* 函数功能: 初始化SD卡 返 回 值: 非0表示初始化失败! */ unsigned char SDCardDeviceInit(void) { 存放SD卡的返回值 用来进行超时计数 unsigned char buf[4]; u16 i; SDCardSpiInit();//初始化底层IO口 for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //发送最少74个脉冲 retry=20; do { r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//进入IDLE状态 闲置 }while((r1!=0X01) && retry--); SD_Type=0; //默认无卡 if(r1==0X01) { if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1) //SD V2.0 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//Get trailing return value of R7 resp if(buf[2]==0X01&&buf[3]==0XAA) //卡是否支持2.7~3.6V { retry=0XFFFE; do { SendSDCardCmd(SDCard_CMD55,0,0X01);发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//发送SDCard_CMD41 }while(r1&&retry--); if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值 if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC; //检查CCS else SD_Type=SDCard_TYPE_V2; } } } else//SD V1.x/ MMCV3 { SendSDCardCmd(SDCard_CMD55,0,0X01);//发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//发送SDCard_CMD41 if(r1<=1) { SD_Type=SDCard_TYPE_V1; retry=0XFFFE; do //等待退出IDLE模式 { SendSDCardCmd(SDCard_CMD55,0,0X01);//发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//发送SDCard_CMD41 }while(r1&&retry--); } else//MMC卡不支持SDCard_CMD55+SDCard_CMD41识别 { SD_Type=SDCard_TYPE_MMC;//MMC V3 retry=0XFFFE; do //等待退出IDLE模式 { r1=SendSDCardCmd(SDCard_CMD1,0,0X01);//发送SDCard_CMD1 }while(r1&&retry--); } if(retry==0||SendSDCardCmd(SDCard_CMD13,512,0X01)!=0)SD_Type=SDCard_TYPE_ERR;//错误的卡 } } SDCardCancelCS(); //取消片选 if(SD_Type)return 0; //初始化成功返回0 else if(r1)return r1; //返回值错误值 return 0xaa; //其他错误 } /* 函数功能:读SD卡 函数参数: buf:数据缓存区 sector:扇区 cnt:扇区数 返回值: 0,ok;其他,失败. 说 明: SD卡一个扇区大小512字节 */ unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt) { unsigned char r1; if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//转换为字节地址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//读命令 if(r1==0)指令发送成功 { r1=SDCardRecvData(buf,512);//接收512个字节 } }else { r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//连续读命令 do { r1=SDCardRecvData(buf,512);//接收512个字节 buf+=512; }while(--cnt && r1==0); SendSDCardCmd(SDCard_CMD12,0,0X01);//发送停止命令 } SDCardCancelCS();//取消片选 return r1;// } /* 函数功能:向SD卡写数据 函数参数: buf:数据缓存区 sector:起始扇区 cnt:扇区数 返回值: 0,ok;其他,失败. 说 明: SD卡一个扇区大小512字节 */ unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt) { unsigned char r1; if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//转换为字节地址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//读命令 if(r1==0)//指令发送成功 { r1=SDCardSendData(buf,0xFE);//写512个字节 } } else { if(SD_Type!=SDCard_TYPE_MMC) { SendSDCardCmd(SDCard_CMD55,0,0X01); SendSDCardCmd(SDCard_CMD23,cnt,0X01);//发送指令 } r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//连续读命令 if(r1==0) { do { r1=SDCardSendData(buf,0xFC);//接收512个字节 buf+=512; }while(--cnt && r1==0); r1=SDCardSendData(0,0xFD);//接收512个字节 } } SDCardCancelCS();//取消片选 return r1;// } static struct gendisk *tiny4412_blockdev_disk; static struct request_queue *tiny4412_blockdev_queue; static int major; //主设备号 static DEFINE_SPINLOCK(tiny4412_blockdev_lock); //自旋锁 static unsigned int sd_size=0; 存放SD卡返回的容量扇区数量单位(512字节) static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 容量=heads*cylinders*sectors*512 存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数 */ geo->heads = 200; /*磁头(一般一个盘面有两个磁头,正面一个/反面一个)*/ geo->cylinders = 32; /*柱面(一般一个盘面上有32个柱面)每个盘片32个磁道)*/ geo->sectors = sd_size/200/32; /*扇区,一般每个磁道上有12个扇区,这里需要根据前面柱面和磁头进行计算,不能乱填*/ return 0; } static struct block_device_operations tiny4412_blockdev_fops = { .owner= THIS_MODULE, .getgeo= tiny4412_blockdev_getgeo, }; static void do_tiny4412_blockdev_request(struct request_queue * q) { struct request *req; req = blk_fetch_request(q); while (req) { unsigned long start = blk_rq_pos(req); /*起始扇区位置*/ unsigned long len = (blk_rq_cur_bytes(req)>>9); /*当前操作的扇区数量*/ int err = 0; if(rq_data_dir(req) == READ) /*如果是读*/ { if(SDCardReadData(req->buffer,start,len)) { printk(KERN_ERR"read error!\r\n"); printk("read ---> nsect=%ld,sector=%ld\r\n",len,start); } } else { if(SDCardWriteData(req->buffer,start,len)) { printk(KERN_ERR"write error!\r\n"); printk("write ---> nsect=%ld,sector=%ld\r\n",len,start); } } if (!__blk_end_request_cur(req, err)) /*判断是否处理完毕请求*/ req = blk_fetch_request(q); /*继续处理下一个请求*/ } } static int tiny4412_blockdev_init(void) { /*初始化SD卡*/ if(SDCardDeviceInit()) { 卡初始化失败!\r\n"); return -1; } /*检测SD卡大小*/ sd_size=GetSDCardSectorCount();//检测SD卡大小,返回值右移11位得到以M为单位的容量 printk("SD卡Sizeof:%dM secnt=%d\r\n",sd_size>>11,sd_size); /* 1. 分配一个gendisk结构体 */ tiny4412_blockdev_disk = alloc_disk(16); /* 次设备号个数: 分区个数+1 */ /* 2. 设置 */ /* 2.1 分配/设置队列: 提供读写能力 */ tiny4412_blockdev_queue = blk_init_queue(do_tiny4412_blockdev_request, &tiny4412_blockdev_lock); tiny4412_blockdev_disk->queue = tiny4412_blockdev_queue; /* 2.2 设置其他属性: 比如容量 */ major = register_blkdev(0,"blockdev"); /* cat /proc/devices */ printk("注册major=%d\n",major); tiny4412_blockdev_disk->major = major; tiny4412_blockdev_disk->first_minor = 0; sprintf(tiny4412_blockdev_disk->disk_name, "tiny4412_blockdev"); tiny4412_blockdev_disk->fops = &tiny4412_blockdev_fops; set_capacity(tiny4412_blockdev_disk, sd_size); //设置磁盘容量 /* 3. 硬件相关操作 */ /* 4. 注册 */ add_disk(tiny4412_blockdev_disk); return 0; } static void tiny4412_blockdev_exit(void) { printk("注销major=%d\n",major); unregister_blkdev(major,"blockdev"); del_gendisk(tiny4412_blockdev_disk); put_disk(tiny4412_blockdev_disk); blk_cleanup_queue(tiny4412_blockdev_queue); /*释放虚拟地址*/ iounmap(SD_GPBCON); iounmap(SD_GPBDAT); } module_init(tiny4412_blockdev_init); module_exit(tiny4412_blockdev_exit); MODULE_LICENSE("GPL"); |
1.4 磁盘的构造分析
硬盘是电脑主要的存储媒介之一,由一个或者多个铝制或者玻璃制的碟片组成。碟片外覆盖有铁磁性材料。
硬盘有固态硬盘(SSD 盘,新式硬盘)、机械硬盘(HDD 传统硬盘)、混合硬盘(HHD 一块基于传统机械硬盘诞生出来的新硬盘)。SSD采用闪存颗粒来存储,HDD采用磁性碟片来存储,混合硬盘(HHD: Hybrid Hard Disk)是把磁性硬盘和闪存集成到一起的一种硬盘。绝大多数硬盘都是固定硬盘,被永久性地密封固定在硬盘驱动器中。
图4.1 机械硬盘
硬盘是集精密机械、微电子电路、电磁转换为一体的电脑存储设备,它存储着电脑系统资源和重要的信息及数据,这些因素使硬盘在PC机中成为最为重要的一个硬件设备
- 最精密的部分--磁头:由于磁头工作的性质,对磁感应的要求非常高。磁头是在高速旋转的盘片上悬浮的,悬浮力来自盘片旋转带动的气流,磁头必须悬浮而不是接触盘面,避免盘面和磁头发生相互接触的磨损。
- 硬盘存储的介质--盘片:盘片是以坚固耐用的材料为盘基,将磁粉附着在平滑的铝合金或玻璃圆盘基上。这些磁粉被划分成称为磁道的若干个同心圆,每个同心圆就好像有无数的小磁铁,它们分别代表着0和1状态。当小磁铁受到来自磁头的磁力影响时,其排列方向会随之改变。
下面主要讲解机械硬盘的构造,机械硬盘是由一个个盘片组成的,我们先从个盘片结构讲起。下图图中的一圈圈灰色同心圆为一条条磁道,从圆心向外画直线,可以将磁道划分为若干个弧段,每个磁道上一个弧段被称之为一个扇区(图践绿色部分)。扇区是磁盘的最小组成单元,通常是512字节。 -
图4.2 盘面构造 - 磁盘的常见参数如下:
- 磁头(head)
- 磁道(track)
- 柱面(cylinder)
- 扇区(sector)
- 圆盘(platter)
上图2中磁盘是一个有 3个盘面6个磁头(一个盘面有正反面两个磁头,两面都可以独立读写),7个柱面(每个盘片7个磁道) 的磁盘,每条磁道有12个扇区,所以此磁盘的容量为6*7*12*512字节。
计算方法:存储容量=磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数