主頁 >  其他 > Linux驅動開發: 塊設備驅動開發

Linux驅動開發: 塊設備驅動開發

2021-09-01 14:00:36 其他

Linux內核版本: 3.5

測驗設備: 友善之臂Tiny4412開發板 (CPU型號: 三星Exynos4412)

2019年三星發布的Galaxy S IIMX采用的就是這款CPU,

一、塊設備介紹

塊是一種具有一定結構的隨機存取設備,對這種設備的讀寫是按塊進行的,他使用緩沖區來存放暫時的資料,待條件成熟后,從快取一次性寫入設備或者從設備一次性讀到緩沖區,
塊設備是與字符設備并列的概念, 這兩類設備在 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記憶體管理機制中一個記憶體頁或者記憶體頁的一部分,


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 內核自帶可參考的塊設備驅動原始碼

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

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");

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 自旋鎖

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);

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 :傳入的請求佇列

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);

}

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);/*分配一個gendisk1表示不能進行磁區,只能固定一個磁區, >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位元組,

cat /sys/block/xxxx/size 可以查看到設定的大小

把位元組為單位的大小轉換為以扇區為單位時,我們需要除以512,或者右移9位

*/

set_capacity(gd,TINY4412_BLKDEV_BYTES>>9);

2.8 添加磁盤磁區資訊到內核

void add_disk(struct gendisk *disk)

函式功能介紹: 將磁區資訊添加到內核,

函式引數: 填充好gendisk結構,

示例:

add_disk(gd);

12.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

三、塊設備示例代碼

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");

塊設備操作程序:

[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_blkdev1 1 100 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_blkdev2 101 160 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/

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
    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調度器)

示例代碼:

/* 參考:  搜索注冊函式即可
 * 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)是把磁性硬碟和閃存集成到一起的一種硬碟,絕大多數硬碟都是固定硬碟,被永久性地密封固定在硬碟驅動器中,

硬碟是集精密機械、微電子電路、電磁轉換為一體的電腦存盤設備,它存盤著電腦系統資源和重要的資訊及資料,這些因素使硬碟在PC機中成為最為重要的一個硬體設備

最精密的部分--磁頭:

由于磁頭作業的性質,對磁感應的要求非常高,磁頭是在高速旋轉的盤片上懸浮的,懸浮力來自盤片旋轉帶動的氣流,磁頭必須懸浮而不是接觸盤面,避免盤面和磁頭發生相互接觸的磨損,

硬碟存盤的介質--盤片:

盤片是以堅固耐用的材料為盤基,將磁粉附著在平滑的鋁合金或玻璃圓盤基上,這些磁粉被劃分成稱為磁道的若干個同心圓,每個同心圓就好像有無數的小磁鐵,它們分別代表著0和1狀態,當小磁鐵受到來自磁頭的磁力影響時,其排列方向會隨之改變,

下面主要講解機械硬碟的構造,機械硬碟是由一個個盤片組成的,我們先從個盤片結構講起,下圖圖中的一圈圈灰色同心圓為一條條磁道,從圓心向外畫直線,可以將磁道劃分為若干個弧段,每個磁道上一個弧段被稱之為一個扇區(圖踐綠色部分),扇區是磁盤的最小組成單元,通常是512位元組,

磁盤的常見引數如下:

  1. 磁頭(head)
  2. 磁道(track)
  3. 柱面(cylinder)
  4. 扇區(sector)
  5. 圓盤(platter)

上圖2中磁盤是一個有 3個盤面6個磁頭(一個盤面有正反面兩個磁頭,兩面都可以獨立讀寫),7個柱面(每個盤片7個磁道) 的磁盤,每條磁道有12個扇區,所以此磁盤的容量為6*7*12*512位元組,

計算方法: 存盤容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/296522.html

標籤:其他

上一篇:超強!!! Kafka高質量專欄學習大全,持續連載中

下一篇:TeaChain全球化布局 從每一個TEA人做起

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more