Linux驱动开发: 块设备驱动开发

简介: Linux驱动开发: 块设备驱动开发

Linux内核版本: 3.5


一、块设备介绍

        块是一种具有一定结构的随机存取设备,对这种设备的读写是按块进行的,他使用缓冲区来存放暂时的数据,待条件成熟后,从缓存一次性写入设备或者从设备一次性读到缓冲区。

块设备是与字符设备并列的概念, 这两类设备在 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结构体。


    编写块设备驱动时,使用的一些单位介绍:


1. 扇区(Sectors):任何块设备硬件对数据处理的基本单位。通常,1个扇区的大小为512字节。(对设备而言)


2. 块 (Blocks):由Linux制定对内核或文件系统等数据处理的基本单位。通常,1个块由1个或多个扇区组成。(对Linux操作系统而言)


3. 段(Segments):由若干个相邻的块组成。是Linux内存管理机制中一个内存页或者内存页的一部分。

image.png

IO调度器

         就是电梯算法。我们知道,磁盘是的读写是通过机械性的移动磁头来实现读写的,理论上磁盘设备满足块设备的随机读写的要求,但是出于节约磁盘,提高效率的考虑,我们希望当磁头处于某一个位置的时候,一起将最近需要写在附近的数据写入,而不是这写一下,那写一下然后再回来,IO调度就是将上层发下来的IO请求的顺序进行重新排序以及对多个请求进行合并,这样就可以实现上述的提高效率、节约磁盘的目的。这种解决问题的思路使用电梯算法,一个运行中的电梯,一个人20楼->1楼,另外一个人15->5楼,电梯不会先将第一个人送到1楼再去15楼接第二个人将其送到5楼,而是从20楼下来,到15楼的时候停下接人,到5楼将第二个放下,最后到达1楼,一句话,电梯算法最终服务的优先顺序并不按照按按钮的先后顺序。


Linux内核中提供了下面的几种电梯算法来实现IO调度:


1.    No-op I/O scheduler只实现了简单的FIFO的,只进行最简单的合并,比较适合基于Flash的存储


2.    Anticipatory I/O scheduler推迟IO请求(大约几个微秒),以期能对他们进行排序,获得更高效率


3.    Deadline I/O scheduler试图把每次请求的延迟降到最低,同时也会对BIO重新排序,特别适用于读取较多的场合,比如数据库


4.    CFQ I/O scheduler为系统内所有的任务分配均匀的IO带宽,提供一个公平的工作环境,在多媒体环境中,能保证音视频及时从磁盘中读取数据,是当前内核默认的调度器

我们可以通过内核传参的方式指定使用的调度算法:  kernel elevator=deadline

或者,使用如下命令改变内核调度算法:

echo SCHEDULER > /sys/block/DEVICE/queue/scheduler

二、块设备结构介绍

2.1 内核自带可参考的块设备驱动源码

1.drivers\block\z2ram.c
drivers\block\xd.c
\drivers\mmc\host\sdhci-s3c.c

2.2 块设备注册与注销函数

1. 注册函数

image.png

函数功能介绍:  注册一个新的块设备


函数参数介绍:


@major:块设备的主设备号[1..255]。 如果major = 0,表示尝试分配未使用的主设备号,返回值就表示分配成功的主设备号。


@name:新块设备的名称。 注意: 名称必须保证在系统中是唯一的(不能与设备名称重名)。


注册示例:

image.png

2. 注销函数

image.png

函数工程介绍: 注销已注册的块设备。

函数参数介绍:

@major: 主设备号

@name: 设备名称

注销示例:

image.png

2.3 动态分配请求队列

image.png

函数功能介绍: 分配一个默认的请求队列,用该函数生成的请求队列没有设置默认的IO调度器,如果编写的块设备是内存模拟块设备或者是SD卡、Flash等设备,就可以用此函数分配请求队列。


函数参数介绍:


@ gfp_mask : 内存分配的方式。 GFP_KERNEL和GFP_ATOMIC,


 GFP_ATOMIC: 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠


GFP_KERNEL: 内核内存的正常分配. 可能睡眠


分配请求队列示例:

image.png

卸载驱动时,可以通过kfree释放空间。

如果需要访问外部硬件,比如: 光盘、磁盘等外部物理设备时,要设置默认的调度器,可以调用blk_init_queue函数分配请求队列。

image.png

blk_init_queue()必须与blk_cleanup_queue()调用配对。


函数参数介绍:


@ rfn 是一个函数指针,类型为 typedef void (request_fn_proc) (struct request_queue *q);


@ lock 自旋锁


2.4 绑定请求队列

image.png

函数功能介绍:  绑定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函数指针。


函数指针的原型如下:

image.png

该函数指针在Blkdev.h定义。

image.png

绑定请求队列示例:

image.png

2.5 make_request_fn处理函数编写

image.png

make_request_fn函数指针传入的参数介绍:

struct bio *bio: 描述块数据传送时怎样完成填充或读取块给driver

struct request_queue *q :传入的请求队列

2.6 扇区读写函数实现

代码示例:

image.png

2.7 分配一个gendisk结构

image.png

函数功能介绍:每个块设备都对应一个gendisk结构,函数alloc_disk用于分配一个gendisk结构。

函数参数介绍:

@minors: 数量

给分配的结构填充参数:

image.png

驱动安装之后,查看的节点信息:

image.png

 设置磁盘的容量

image.png

2.8 添加磁盘分区信息到内核

image.png

函数功能介绍: 将分区信息添加到内核。

函数参数: 填充好gendisk结构。

示例:

image.png

12.9 初始化一个请求队列

image.png

示例:

image.png

该函数里调用了默认的IO调度器。 代码可以参考内核文件: drivers\block\z2ram.c

三、块设备示例代码

image.png

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, 
   /*fdisk命令分区时需要调用该函数,用于读取磁头、柱面、扇区等信息*/
  .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字节。
    cat /sys/block/xxxx/size 可以查看到设置的大小
    把字节为单位的大小转换为以扇区为单位时,我们需要除以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");

块设备操作过程:

image.png

image.png

3.2 使用SD卡编写块设备(不使用IO调度器)

SD卡采用SPI协议通信,底层采用模拟的SPI系统,没有使用SPI子系统。

image.png

#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
    while(len--)//开始接收数据
    {
        *buf=SDCardReadWriteOneByte(0xFF);
        buf++;
    }
    //下面是2个伪CRC(dummy CRC)
    SDCardReadWriteOneByte(0xFF);
    SDCardReadWriteOneByte(0xFF);                                 
    return 0;//读取成功
}
/*
函数功能:向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]);//提高速度,减少函数传参时间
      SDCardReadWriteOneByte(0xFF); //忽略crc
      SDCardReadWriteOneByte(0xFF);
      t=SDCardReadWriteOneByte(0xFF); //接收响应
    if((t&0x1F)!=0x05)return 2;   //响应错误                                  
  }                                             
    return 0;//写入成功
}
/*
函数功能:向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)         //V2.00的卡,如果为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)
{
  unsigned char r1;      // 存放SD卡的返回值
  u16 retry;  // 用来进行超时计数
  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/ MMC  V3
    {
      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;              /* gendisk结构 */
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,
   /*fdisk命令分区时需要调用该函数,用于读取磁头、柱面、扇区等信息*/
  .getgeo = tiny4412_blockdev_getgeo,
};
/*
驱动入口
*/
static int __init Tiny4412_block_init(void)
{
  /*1. 初始化SD口*/
  if(SDCardDeviceInit()) 
  {
     printk("SD卡初始化失败!\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字节。
    cat /sys/block/xxxx/size 可以查看到设置的大小
    把字节为单位的大小转换为以扇区为单位时,我们需要除以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");

 3.3 内存模拟块设备(使用默认的IO调度器)

示例代码:

1./* 参考:  搜索注册函数即可
 * 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");

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
    while(len--)//开始接收数据
    {
        *buf=SDCardReadWriteOneByte(0xFF);
        buf++;
    }
    //下面是2个伪CRC(dummy CRC)
    SDCardReadWriteOneByte(0xFF);
    SDCardReadWriteOneByte(0xFF);                                 
    return 0;//读取成功
}
/*
函数功能:向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]);//提高速度,减少函数传参时间
      SDCardReadWriteOneByte(0xFF); //忽略crc
      SDCardReadWriteOneByte(0xFF);
      t=SDCardReadWriteOneByte(0xFF); //接收响应
    if((t&0x1F)!=0x05)return 2;   //响应错误                                  
  }                                             
    return 0;//写入成功
}
/*
函数功能:向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)         //V2.00的卡,如果为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)
{
  unsigned char r1;      // 存放SD卡的返回值
  u16 retry;  // 用来进行超时计数
  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/ MMC  V3
    {
      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()) 
  {
     printk("SD卡初始化失败!\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");

四、 磁盘的构造分析

     硬盘是电脑主要的存储媒介之一,由一个或者多个铝制或者玻璃制的碟片组成。碟片外覆盖有铁磁性材料。


     硬盘有固态硬盘(SSD 盘,新式硬盘)、机械硬盘(HDD 传统硬盘)、混合硬盘(HHD 一块基于传统机械硬盘诞生出来的新硬盘)。SSD采用闪存颗粒来存储,HDD采用磁性碟片来存储,混合硬盘(HHD: Hybrid Hard Disk)是把磁性硬盘和闪存集成到一起的一种硬盘。绝大多数硬盘都是固定硬盘,被永久性地密封固定在硬盘驱动器中。

image.png

  硬盘是集精密机械、微电子电路、电磁转换为一体的电脑存储设备,它存储着电脑系统资源和重要的信息及数据,这些因素使硬盘在PC机中成为最为重要的一个硬件设备


最精密的部分--磁头:


     由于磁头工作的性质,对磁感应的要求非常高。磁头是在高速旋转的盘片上悬浮的,悬浮力来自盘片旋转带动的气流,磁头必须悬浮而不是接触盘面,避免盘面和磁头发生相互接触的磨损。


硬盘存储的介质--盘片:


    盘片是以坚固耐用的材料为盘基,将磁粉附着在平滑的铝合金或玻璃圆盘基上。这些磁粉被划分成称为磁道的若干个同心圆,每个同心圆就好像有无数的小磁铁,它们分别代表着0和1状态。当小磁铁受到来自磁头的磁力影响时,其排列方向会随之改变。


    下面主要讲解机械硬盘的构造,机械硬盘是由一个个盘片组成的,我们先从个盘片结构讲起。下图图中的一圈圈灰色同心圆为一条条磁道,从圆心向外画直线,可以将磁道划分为若干个弧段,每个磁道上一个弧段被称之为一个扇区(图践绿色部分)。扇区是磁盘的最小组成单元,通常是512字节。

image.png

磁盘的常见参数如下:


磁头(head)

磁道(track)

柱面(cylinder)

扇区(sector)

圆盘(platter)

上图2中磁盘是一个有 3个盘面6个磁头(一个盘面有正反面两个磁头,两面都可以独立读写),7个柱面(每个盘片7个磁道) 的磁盘,每条磁道有12个扇区,所以此磁盘的容量为6*7*12*512字节。


计算方法: 存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数。

image.png

目录
相关文章
|
5天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
1月前
|
Linux API 调度
Linux系统驱动跟裸机驱动的区别
Linux系统驱动跟裸机驱动的区别
29 0
|
1月前
|
存储 缓存 Linux
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
36 0
|
11天前
|
Linux Go
Linux命令Top 100驱动人生! 面试必备
探索Linux命令不再迷茫!本文分10部分详解20个基础命令,带你由浅入深掌握文件、目录管理和文本处理。 [1]: <https://cloud.tencent.com/developer/article/2396114> [2]: <https://pan.quark.cn/s/865a0bbd5720> [3]: <https://yv4kfv1n3j.feishu.cn/docx/MRyxdaqz8ow5RjxyL1ucrvOYnnH>
64 0
|
19天前
|
存储 缓存 固态存储
Linux设备全览:从字符到块,揭秘每种设备的秘密
在Linux的世界里,设备是构成系统的基础,它们使得计算机能够与外界互动。Linux设备可以大致分为几种类型,每种类型都有其独特的特性和用途。🌌让我们一起探索这些设备类型及其特性。
|
21天前
|
Linux API C语言
FFmpeg开发笔记(一)搭建Linux系统的开发环境
本文指导初学者如何在Linux上搭建FFmpeg开发环境。首先,由于FFmpeg依赖第三方库,可以免去编译源码的复杂过程,直接安装预编译的FFmpeg动态库。推荐网站<https://github.com/BtbN/FFmpeg-Builds/releases>提供适用于不同系统的FFmpeg包。但在安装前,需确保系统有不低于2.22版本的glibc库。详细步骤包括下载glibc-2.23源码,配置、编译和安装。接着,下载Linux版FFmpeg安装包,解压至/usr/local/ffmpeg,并设置环境变量。最后编写和编译简单的C或C++测试程序验证FFmpeg环境是否正确配置。
37 8
FFmpeg开发笔记(一)搭建Linux系统的开发环境
|
24天前
|
Linux
Linux驱动运行灯 Heartbeat
Linux驱动运行灯 Heartbeat
12 0
|
1月前
|
存储 缓存 Linux
探秘Linux块设备驱动程序:成为内核开发大师的第一步
探秘Linux块设备驱动程序:成为内核开发大师的第一步
93 0
|
1月前
|
Linux
linux驱动开发-点亮第一个led灯
linux驱动开发-点亮第一个led灯
23 0
|
1月前
|
网络协议 Java Linux
Java 开发常用的 Linux 命令知识积累
Java 开发常用的 Linux 命令知识积累
38 0