塊裝置介紹
塊是一種具有一定結構的随機存取裝置,對這種裝置的讀寫是按塊進行的,他使用緩沖區來存放暫時的資料,待條件成熟後,從緩存一次性寫入裝置或者從裝置一次性讀到緩沖區。
塊裝置是與字元裝置并列的概念,這兩類裝置在 Linux 中驅動的結構有較大差異,總體而言, 塊裝置驅動比字元裝置驅動要複雜得多,在 I/O 操作上表現出極大的不同,緩沖、 I/O 排程、請求隊列等都是與塊裝置驅動相關的概念。
在Linux中,驅動對塊裝置的輸入或輸出(I/O)操作,都會向塊裝置發出一個請求,在驅動中用request結構體描述。但對于一些磁盤裝置而言請求的速度很慢,這時候核心就提供一種隊列的機制把這些I/O請求添加到隊列中(即:請求隊列),在驅動中用request_queue結構體描述。在向塊裝置送出這些請求前核心會先執行請求的合并和排序預操作,以提高通路的效率,然後再由核心中的I/O排程程式子系統來負責送出 I/O 請求, 排程程式将磁盤資源配置設定給系統中所有挂起的塊 I/O 請求,其工作是管理塊裝置的請求隊列,決定隊列中的請求的排列順序以及什麼時候派發請求到裝置。
由通用塊層(Generic Block Layer)負責維持一個I/O請求在上層檔案系統與底層實體磁盤之間的關系。在通用塊層中,通常用一個bio結構體來對應一個I/O請求。
Linux提供了一個gendisk資料結構體,用來表示一個獨立的磁盤裝置或分區,用于對底層實體磁盤進行通路。在gendisk中有一個類似字元裝置中file_operations的硬體操作結構指針,是block_device_operations結構體。
-
編寫塊裝置驅動時,使用的一些機關介紹:n 1. 扇區(Sectors):任何塊裝置硬體對資料處理的基本機關。通常,1個扇區的大小為512位元組。(對裝置而言)
2. 塊 (Blocks):由Linux制定對核心或檔案系統等資料處理的基本機關。通常,1個塊由1個或多個扇區組成。(對Linux作業系統而言)
n 3. 段(Segments):由若幹個相鄰的塊組成。是Linux記憶體管理機制中一個記憶體頁或者記憶體頁的一部分。
-
IO排程器就是電梯算法。我們知道,磁盤是的讀寫是通過機械性的移動磁頭來實作讀寫的,理論上磁盤裝置滿足塊裝置的随機讀寫的要求,但是出于節約磁盤,提高效率的考慮,我們希望當磁頭處于某一個位置的時候,一起将最近需要寫在附近的資料寫入,而不是這寫一下,那寫一下然後再回來,IO排程就是将上層發下來的IO請求的順序進行重新排序以及對多個請求進行合并,這樣就可以實作上述的提高效率、節約磁盤的目的。這種解決問題的思路使用電梯算法,一個運作中的電梯,一個人20樓->1樓,另外一個人15->5樓,電梯不會先将第一個人送到1樓再去15樓接第二個人将其送到5樓,而是從20樓下來,到15樓的時候停下接人,到5樓将第二個放下,最後到達1樓,一句話,電梯算法最終服務的優先順序并不按照按按鈕的先後順序。
Linux核心中提供了下面的幾種電梯算法來實作IO排程:
- No-op I/O scheduler隻實作了簡單的FIFO的,隻進行最簡單的合并,比較适合基于Flash的存儲
- Anticipatory I/O scheduler推遲IO請求(大約幾個微秒),以期能對他們進行排序,獲得更高效率
- Deadline I/O scheduler試圖把每次請求的延遲降到最低,同時也會對BIO重新排序,特别适用于讀取較多的場合,比如資料庫
- CFQ I/O scheduler為系統内所有的任務配置設定均勻的IO帶寬,提供一個公平的工作環境,在多媒體環境中,能保證音視訊及時從磁盤中讀取資料,是目前核心預設的排程器
我們可以通過核心傳參的方式指定使用的排程算法: kernel elevator=deadline
或者,使用如下指令改變核心排程算法: echo SCHEDULER > /sys/block/DEVICE/queue/scheduler
1.2 塊裝置結構介紹
1.2.1 核心自帶可參考的塊裝置驅動源碼
drivers\block\z2ram.c
drivers\block\xd.c
\drivers\mmc\host\sdhci-s3c.c
1.2.2 塊裝置注冊與登出函數
1. 注冊函數
int register_blkdev(unsigned int major, const char *name) |
函數功能介紹: 注冊一個新的塊裝置
函數參數介紹:
@major:塊裝置的主裝置号[1..255]。 如果major = 0,表示嘗試配置設定未使用的主裝置号,傳回值就表示配置設定成功的主裝置号。
@name:新塊裝置的名稱。 注意: 該名稱必須保證在系統中是唯一的(不能與裝置名稱重名)。
注冊示例:
int Tiny4412_block_major = register_blkdev(0, "Tiny4412_block"); |
2. 登出函數
void unregister_blkdev(unsigned int major, const char *name) |
函數功能介紹: 登出已注冊的塊裝置。
函數參數介紹:
@major: 主裝置号
@name: 裝置名稱
登出示例:
unregister_blkdev(Tiny4412_block_major, "Tiny4412_block"); |
1.2.3 動态配置設定請求隊列
struct request_queue *blk_alloc_queue(gfp_t gfp_mask) |
函數功能介紹: 配置設定一個預設的請求隊列,用該函數生成的請求隊列沒有設定預設的IO排程器,如果編寫的塊裝置是記憶體模拟塊裝置或者是SD卡、Flash等裝置,就可以用此函數配置設定請求隊列。
函數參數介紹:
@ gfp_mask : 記憶體配置設定的方式。 GFP_KERNEL和GFP_ATOMIC,
GFP_ATOMIC: 用來從中斷處理和程序上下文之外的其他代碼中配置設定記憶體. 從不睡眠
GFP_KERNEL: 核心記憶體的正常配置設定. 可能睡眠
配置設定請求隊列示例:
struct request_queue *queue= =blk_alloc_queue(GFP_KERNEL); |
解除安裝驅動時,可以通過kfree釋放空間。
如果需要通路外部硬體,比如: CD光牒、磁盤等外部實體裝置時,要設定預設的排程器,可以調用blk_init_queue函數配置設定請求隊列。
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) |
blk_init_queue()必須與blk_cleanup_queue()調用配對。
函數參數介紹:
@ rfn 是一個函數指針,類型為 typedef void (request_fn_proc) (struct request_queue *q);
@ lock 自旋鎖
1.2.4 綁定請求隊列
void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn) |
函數功能介紹: 綁定blk_alloc_queue函數到請求隊列。
上一步介紹的blk_alloc_queue函數配置設定的請求隊列,由于不會使用預設的IO排程器,其中的make_request_fn是沒有指派的,因為上層代碼向請求隊列發生請求時都是通過make_request_fn這個函數來完成的。對于上層代碼發出的請求,可以直接用make_request_fn函數來完成請求并直接将結果傳回給上層的代碼。
函數參數介紹:
struct request_queue *q :請求隊列指針。
make_request_fn *mfn : make_request_fn函數指針。
函數指針的原型如下:
typedef void (make_request_fn) (struct request_queue *q, struct bio *bio); |
該函數指針在Blkdev.h定義。
綁定請求隊列示例:
blk_queue_make_request(queue, Tiny4412_block_make_request); |
1.2.5 make_request_fn處理函數編寫
//直接送出請求,隊列處理 static void Tiny4412_block_make_request(struct request_queue *q, struct bio *bio) { int i; struct bio_vec *bvec; sector_t sector = bio->bi_sector; /*通過for循環周遊一個bio中所有的segment請求*/ bio_for_each_segment(bvec, bio, i) { char *buffer = __bio_kmap_atomic(bio, i, KM_USER0); /*映射記憶體空間(申請空間)*/ Tiny4412_block_dev_sector_read_write(sector, bio_cur_bytes(bio)>>9 ,buffer, bio_data_dir(bio) == WRITE); /* sector: 目前扇區位置 bio_cur_bytes(bio)>>9: 扇區讀寫數量 buffer :讀寫的緩沖區指針首位址 bio_data_dir(bio): 判斷是讀還是寫 */ sector += bio_cur_bytes(bio)>>9; /*偏移扇區*/ __bio_kunmap_atomic(bio, KM_USER0); /*取消映射(釋放空間)*/ } bio_endio(bio, 0); /*結束處理*/ return; } |
make_request_fn函數指針傳入的參數介紹:
struct bio *bio: 描述塊資料傳送時怎樣完成填充或讀取塊給driver
struct request_queue *q :傳入的請求隊列
1.2.6 扇區讀寫函數實作
代碼示例:
unsigned long sector: 目前扇區位置 unsigned long nsect : 扇區讀寫數量 char *buffer : 讀寫的緩沖區指針 int write : 是讀還是寫 */ static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write) { /*塊裝置最小機關是一個扇區,一個扇區的位元組數是512位元組*/ unsigned long offset = sector*512; unsigned long nbytes = nsect*512; if((offset + nbytes)>TINY4412_BLKDEV_BYTES) { printk(KERN_NOTICE "寫超出範圍,強制結束(%ld %ld)\n", offset, nbytes); return; } if(write) /*為真,表示是寫*/ memcpy(tiny4412_blkdev_data + offset, buffer, nbytes); else /*讀操作*/ memcpy(buffer,tiny4412_blkdev_data + offset, nbytes); } |
1.2.7 配置設定一個gendisk結構
struct gendisk *alloc_disk(int minors) //動态配置設定gendisk void del_gendisk(struct gendisk *disk) //登出gendisk |
函數功能介紹:
每個塊裝置都對應一個gendisk結構,函數alloc_disk用于配置設定一個gendisk結構。
函數參數介紹:
@minors: 數量
- 給配置設定的結構填充參數:
/*動态配置設定次裝置号結構*/ gd=alloc_disk(1);/*配置設定一個gendisk,1表示不能進行分區,隻能固定一個分區。 >1表示支援分區的數量 分區可以通過fdsik指令進行操作*/ gd->major=Tiny4412_block_major; 主裝置号*/ gd->first_minor=0;次裝置号*/ gd->fops=&Tiny4412_block_ops; /*檔案操作集合*/ gd->queue=queue; /*将請求隊列關聯到gendisk結構*/ snprintf(gd->disk_name, 32, "Tiny4412_block_%c",'a'); //設定磁盤名稱,在/dev下可以檢視該名稱 //塊裝置基本都是使用檔案系統函數進行操作,該檔案操作集合可以不用自己實作 static struct block_device_operations Tiny4412_block_ops= { .owner = THIS_MODULE, }; |
驅動安裝之後,檢視的節點資訊:
- 設定磁盤的容量
/*注意: 塊裝置的大小使用扇區作為機關設定,而扇區的大小預設是512位元組。 可以檢視到設定的大小 512,或者右移9位 */ set_capacity(gd,TINY4412_BLKDEV_BYTES>>9); |
1.2.8 添加磁盤分區資訊到核心
void add_disk(struct gendisk *disk) |
函數功能介紹: 将分區資訊添加到核心。
函數參數: 填充好gendisk結構。
示例:
add_disk(gd); |
1.2.9 初始化一個請求隊列
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) |
示例:
tiny4412_blockdev_queue = blk_init_queue(do_tiny4412_blockdev_request, &tiny4412_blockdev_lock); |
該函數裡調用了預設的IO排程器。代碼可以參考核心檔案: drivers\block\z2ram.c
1.3 塊裝置示例代碼
圖1.3.1 塊裝置架構簡圖(了解整體架構)
1.3.1 記憶體模拟塊裝置(不使用IO排程器)
記憶體空間采用vmalloc函數進行配置設定。
#include <linux/module.h> #include <linux/blkdev.h> #include <linux/hdreg.h> #include <linux/version.h> /* * insmod tiny4412_blkdev.ko * # or insmod tiny4412_blkdev.ko size=numK/M/G/T * fdisk /dev/tiny4412_blkdev # create 2 patitions * mkfs.ext2 /dev/tiny4412_blkdev1 * mkfs.ext2 /dev/tiny4412_blkdev2 * mount /dev/tiny4412_blkdev1 /mnt/temp1/ * mount /dev/tiny4412_blkdev2 /mnt/temp2/ * # play in /mnt/temp1/ and /mnt/temp2/ * umount /mnt/temp1/ * umount /mnt/temp2/ * rmmod tiny4412_blkdev.ko * */ static int Tiny4412_block_major=0; static struct request_queue *tiny4412_blkdev_queue; static struct gendisk *tiny4412_blkdev_disk; static unsigned long long tiny4412_blkdev_bytes=1024*1024*10;//10M--空間容量 #define TINY4412_BLKDEV_BYTES_1 (1024*1024*10) /*設定塊裝置的大小*/ static unsigned char tiny4412_blkdev_data_1[TINY4412_BLKDEV_BYTES_1]; /*用于測試塊裝置的數組大小*/ /* * Handle an I/O request. * 實作扇區的讀寫 unsigned long sector: 目前扇區位置 unsigned long nsect : 扇區讀寫數量 char *buffer : 讀寫的緩沖區指針 int write : 是讀還是寫 */ static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write) { /*塊裝置最小機關是一個扇區,一個扇區的位元組數是512位元組*/ unsigned long offset = sector; /*寫入資料的位置*/ unsigned long nbytes = nsect; /*寫入的長度*/ if((offset + nbytes)>TINY4412_BLKDEV_BYTES_1) { printk("寫超出範圍,強制結束(%ld %ld)\n", offset, nbytes); return; } if(write) /*為真,表示是寫*/ memcpy(tiny4412_blkdev_data_1 + offset, buffer, nbytes); else /*讀操作*/ memcpy(buffer,tiny4412_blkdev_data_1 + offset, nbytes); } /* 處理請求 */ static int tiny4412_blkdev_make_request(struct request_queue *q, struct bio *bio) { int dir; unsigned long long dsk_offset; struct bio_vec *bvec; int i; void *iovec_mem; /*判斷讀寫方向*/ if(bio_data_dir(bio) == WRITE) dir = 1; else dir = 0; dsk_offset = bio->bi_sector << 9; bio_for_each_segment(bvec, bio, i) { iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; //起始位置,長度,源資料,方向 Tiny4412_block_dev_sector_read_write(dsk_offset,bvec->bv_len,iovec_mem,dir); kunmap(bvec->bv_page); dsk_offset += bvec->bv_len; } bio_endio(bio, 0); return 0; } static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 容量=heads*cylinders*sectors*512 存儲容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數 */ geo->heads = 2; /*磁頭(一般一個盤面有兩個磁頭,正面一個/反面一個)*/ geo->cylinders = 32; /*柱面(一般一個盤面上有32個柱面)每個盤片32個磁道)*/ geo->sectors = TINY4412_BLKDEV_BYTES_1/2/32/512; /*扇區,一般每個磁道上有12個扇區,這裡需要根據前面柱面和磁頭進行計算,不能亂填*/ return 0; } struct block_device_operations tiny4412_blkdev_fops = { .owner= THIS_MODULE, 指令分區時需要調用該函數,用于讀取磁頭、柱面、扇區等資訊*/ .getgeo= tiny4412_blockdev_getgeo, }; static int __init tiny4412_blkdev_init(void) { /*動态配置設定請求隊列*/ tiny4412_blkdev_queue = blk_alloc_queue(GFP_KERNEL); /*綁定請求隊列*/ blk_queue_make_request(tiny4412_blkdev_queue,tiny4412_blkdev_make_request); /*動态配置設定次裝置号結構*/ /*每一個磁盤(分區)都是使用一個gendisk結構儲存*/ tiny4412_blkdev_disk = alloc_disk(64); /*磁盤名稱指派*/ strcpy(tiny4412_blkdev_disk->disk_name, "tiny4412_blkdev"); /*注冊一個塊裝置,自動配置設定主裝置号*/ Tiny4412_block_major = register_blkdev(0,"Tiny4412_block"); printk("Tiny4412_block_major=%d\n",Tiny4412_block_major); tiny4412_blkdev_disk->major=Tiny4412_block_major; 主裝置号*/ tiny4412_blkdev_disk->first_minor = 0; 次裝置号*/ tiny4412_blkdev_disk->fops = &tiny4412_blkdev_fops; /*檔案操作結合*/ tiny4412_blkdev_disk->queue = tiny4412_blkdev_queue; /*處理資料請求的隊列*/ /*設定磁盤結構 capacity 的容量*/ /*注意: 塊裝置的大小使用扇區作為機關設定,而扇區的大小預設是512位元組。 可以檢視到設定的大小 512,或者右移9位 */ set_capacity(tiny4412_blkdev_disk,tiny4412_blkdev_bytes>>9); //添加磁盤資訊到核心 add_disk(tiny4412_blkdev_disk); return 0; } static void __exit tiny4412_blkdev_exit(void) { //删除磁盤 del_gendisk(tiny4412_blkdev_disk); put_disk(tiny4412_blkdev_disk); //清除隊列 blk_cleanup_queue(tiny4412_blkdev_queue); /*登出塊裝置*/ unregister_blkdev(Tiny4412_block_major, "Tiny4412_block"); } module_init(tiny4412_blkdev_init); module_exit(tiny4412_blkdev_exit); MODULE_LICENSE("GPL"); |
- 塊裝置操作過程:
[root@wbyq code]#ls tiny4412_block_device.ko [root@wbyq code]#insmod tiny4412_block_device.ko [ 154.950000] Tiny4412_block_major=253 [ 154.955000] tiny4412_blkdev: unknown partition table (因為使用的記憶體裝置模拟塊裝置,數組裡沒有分區表,是以第一次安裝裝置時,讀取不到裝置分區表,這個提示是正常的) [root@wbyq code]#fdisk /dev/tiny4412_blkdev Device contains neither a valid DOS partition table, nor Sun, SGI, OSF or GPT disklabel Building a new DOS disklabel. Changes will remain in memory only, until you decide to write them. After that the previous content won't be recoverable. Command (m for help): n(n表示建立分區表) Command action e extended(表示擴充分區) p primary partition (1-4)(表示主分區) p (選擇p建立主分區) Partition number (1-4): 1 (建立的主分區編号為1) First cylinder (1-160, default 1): 1(柱面起始位置設定為1, 1-160表示目前磁盤剩餘的未分區的柱面範圍為1-160) Last cylinder or +size or +sizeM or +sizeK (1-160, default 160): 100 (設定柱面的結束位置) Command (m for help): n (表示主分區) Command action e extended p primary partition (1-4) p (選擇p建立主分區) Partition number (1-4): 2 (建立的主分區編号為2) First cylinder (101-160, default 101): 101(柱面起始位置設定為101, 101-160表示目前磁盤剩餘的未分區的柱面範圍為101-160) Last cylinder or +size or +sizeM or +sizeK (101-160, default 160): 160 (設定柱面的結束位置) Command (m for help): p (列印目前的分區情況) Disk /dev/tiny4412_blkdev: 10 MB, 10485760 bytes (磁盤的總容量: M機關,位元組機關) 2 heads, 64 sectors/track, 160 cylinders (一個共有2個磁頭,每個柱面有64個扇區,160個柱面) (提示: 存儲容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數) Units = cylinders of 128 * 512 = 65536 bytes Device Boot Start End Blocks Id System /dev/tiny4412_blkdev11100 6368 83 Linux (分區1的資訊) Partition 1 has different physical/logical endings: phys=(355, 1, 0) logical=(99, 1, 64) Partition 1 does not end on cylinder boundary /dev/tiny4412_blkdev2101160 3840 83 Linux (分區2的資訊) Partition 2 has different physical/logical endings: phys=(415, 1, 0) logical=(159, 1, 64) Partition 2 does not end on cylinder boundary Command (m for help): w (w表示儲存分區表,寫入磁盤,并退出fdisk指令行) The partition table has been altered. (提示,分區表示已經更改) Calling ioctl() to re-read partition table (調用ioctl()重新讀取分區表) [ 218.905000] tiny4412_blkdev: tiny4412_blkdev1 tiny4412_blkdev2(提示分區之後建立成功的裝置節點) [root@wbyq code]#ls /dev/tiny4412_blkdev* -l(檢視/dev下分區成功的裝置節點) brw-rw---- 1 root root 253, 0 Nov 24 2018 /dev/tiny4412_blkdev brw-rw---- 1 root root 253, 1 Nov 24 2018 /dev/tiny4412_blkdev1 brw-rw---- 1 root root 253, 2 Nov 24 2018 /dev/tiny4412_blkdev2 [root@wbyq code]#mkfs.ext2 /dev/tiny4412_blkdev1(給第一個裝置分區進行格式化檔案系統) Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 1592 inodes, 6368 blocks 318 blocks (5%) reserved for the super user First data block=1 Maximum filesystem blocks=262144 1 block groups 8192 blocks per group, 8192 fragments per group 1592 inodes per group [root@wbyq code]#mkfs.ext2 /dev/tiny4412_blkdev2(給第二個裝置分區進行格式化檔案系統) Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 960 inodes, 3840 blocks 192 blocks (5%) reserved for the super user First data block=1 Maximum filesystem blocks=262144 1 block groups 8192 blocks per group, 8192 fragments per group 960 inodes per group [root@wbyq code]#mount /dev/tiny4412_blkdev1 /mnt/ (将第一個分區挂載到/mnt目錄下) [root@wbyq code]#mount /dev/tiny4412_blkdev2 /tmp/ (将第二個分區挂載到/mnt目錄下) [root@wbyq code]#df -h (檢視目前系統磁盤的資訊) Filesystem Size Used Available Use% Mounted on 192.168.10.11:/work/rootfs/ 48.1G 16.5G 29.1G 36% / /dev/tiny4412_blkdev1 6.0M 13.0K 5.7M 0% /mnt /dev/tiny4412_blkdev2 3.6M 13.0K 3.4M 0% /tmp /*下面就是分别進入到挂載的目錄下進行檔案拷貝操作,測試塊裝置是否正常,最後在取消挂載退出*/ [root@wbyq code]#cd /mnt/ [root@wbyq mnt]#ls lost+found [root@wbyq mnt]#cp /123.mp3 ./ [root@wbyq mnt]# [root@wbyq mnt]#cd /tmp/ [root@wbyq tmp]#cp /123.mp3 ./ [root@wbyq tmp]# [root@wbyq tmp]#ls 123.mp3 lost+found [root@wbyq tmp]#cd / [root@wbyq ]#umount /tmp/ [root@wbyq ]#umount /mnt/ |
1.3.2 使用SD卡編寫塊裝置(不使用IO排程器)
SD卡采用SPI協定通信,底層采用模拟的SPI系統,沒有使用SPI子系統。
- 示例代碼:
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/sched.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/fs.h> /* everything... */ #include <linux/errno.h>/* error codes */ #include <linux/timer.h> #include <linux/types.h>/* size_t */ #include <linux/fcntl.h>/* O_ACCMODE */ #include <linux/hdreg.h>/* HDIO_GETGEO */ #include <linux/kdev_t.h> #include <linux/vmalloc.h> #include <linux/genhd.h> #include <linux/blkdev.h> #include <linux/bio.h> #include <linux/init.h> #include <linux/module.h> #include <linux/ioctl.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/err.h> #include <linux/list.h> #include <linux/errno.h> #include <linux/mutex.h> #include <linux/slab.h> #include <linux/compat.h> #include <linux/spi/spi.h> #include <linux/spi/spidev.h> #include <asm/uaccess.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/mutex.h> #include <linux/miscdevice.h> /*雜項字元裝置頭檔案*/ /*--------------------------------SD相關操作代碼---------------------------------------------*/ /*定義指針,用于接收虛拟位址*/ volatile unsigned int *SD_GPBCON; volatile unsigned int *SD_GPBDAT; /* 函數功能:SD初始化 Tiny4412硬體連接配接: DO--MISO :GPB_2 DI--MOSI :GPB_3 CLK-SCLK :GPB_0 CS--CS :GPB_1 */ void SDCardSpiInit(void) { /*1. 初始化GPIO*/ /*映射實體位址*/ SD_GPBCON=ioremap(0x11400040,4); SD_GPBDAT=ioremap(0x11400044,4); *SD_GPBCON &= ~(0xf << 0 * 4);*SD_GPBCON |= (0x1 << 0 * 4); *SD_GPBCON &= ~(0xf << 1 * 4);*SD_GPBCON |= (0x1 << 1 * 4); *SD_GPBCON &= ~(0xf << 2 * 4); *SD_GPBCON &= ~(0xf << 3 * 4);*SD_GPBCON |= (0x1 << 3 * 4); /*2. 上拉GPIO口*/ //*SD_GPBDAT &= ~(1 << 4);//輸出0 *SD_GPBDAT |= (1 << 0); //輸出1 *SD_GPBDAT |= (1 << 1); //輸出1 *SD_GPBDAT |= (1 << 3); //輸出1 } // SD卡類型定義 #define SDCard_TYPE_ERR 0X00 //卡類型錯誤 #define SDCard_TYPE_MMC 0X01 //MMC卡 #define SDCard_TYPE_V1 0X02 #define SDCard_TYPE_V2 0X04 #define SDCard_TYPE_V2HC 0X06 // SD卡指令表 #define SDCard_CMD0 0 //卡複位 #define SDCard_CMD1 1 #define SDCard_CMD8 8 //指令8 ,SEND_IF_COND #define SDCard_CMD9 9 //指令9 ,讀CSD資料 #define SDCard_CMD10 10 //指令10,讀CID資料 #define SDCard_CMD12 12 //指令12,停止資料傳輸 #define SDCard_CMD13 16 //指令16,設定扇區大小 應傳回0x00 #define SDCard_CMD17 17 //指令17,讀扇區 #define SDCard_CMD18 18 //指令18,讀Multi 扇區 #define SDCard_CMD23 23 //指令23,設定多扇區寫入前預先擦除N個block #define SDCard_CMD24 24 //指令24,寫扇區 #define SDCard_CMD25 25 //指令25,寫多個扇區 #define SDCard_CMD41 41 //指令41,應傳回0x00 #define SDCard_CMD55 55 //指令55,應傳回0x01 #define SDCard_CMD58 58 //指令58,讀OCR資訊 #define SDCard_CMD59 59 //指令59,使能/禁止CRC,應傳回0x00、 /*SD卡回應标記字*/ #define SDCard_RESPONSE_NO_ERROR 0x00 //正确回應 #define SDCard_SD_IN_IDLE_STATE 0x01 //閑置狀态 #define SDCard_SD_ERASE_RESET 0x02 //擦除複位 #define SDCard_RESPONSE_FAILURE 0xFF //響應失敗 //函數聲明 unsigned char SDCardReadWriteOneByte(unsigned char data); //底層接口,SPI讀寫位元組函數 unsigned char SDCardWaitBusy(void);//等待SD卡準備 unsigned char SDCardGetAck(unsigned char Response);//獲得應答 unsigned char SDCardDeviceInit(void);初始化 unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt);讀塊(扇區) unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt);寫塊(扇區) unsigned int GetSDCardSectorCount(void); 讀扇區數 unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data); //讀SD卡CID unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data); //讀SD卡CSD static unsigned char SD_Type=0; //存放SD卡的類型 /* 函數功能:SD卡底層接口,通過SPI時序向SD卡讀寫一個位元組 函數參數:data是要寫入的資料 返 回 值:讀到的資料 說明:時序是第二個上升沿采集資料 */ unsigned char SDCardReadWriteOneByte(unsigned char data_tx) { u8 data_rx=0; u8 i; for(i=0;i<8;i++) { *SD_GPBDAT &= ~(1 << 0);//輸出0 if(data_tx&0x80)*SD_GPBDAT |= (1 << 3); //輸出1 else *SD_GPBDAT &= ~(1 << 3);//輸出0 data_tx<<=1; //繼續發送下一個資料 *SD_GPBDAT |= (1 << 0); //輸出1 data_rx<<=1; if((*SD_GPBDAT & (1 << 2)))data_rx|=0x01; } return data_rx; } /* 函數功能:取消選擇,釋放SPI總線 */ void SDCardCancelCS(void) { *SD_GPBDAT |= (1 << 1); SDCardReadWriteOneByte(0xff);//提供額外的8個時鐘 } /* 函數 功 能:選擇sd卡,并且等待卡準備OK 函數傳回值:0,成功;1,失敗; */ unsigned char SDCardSelectCS(void) { *SD_GPBDAT &= ~(1 << 1); if(SDCardWaitBusy()==0)return 0;//等待成功 SDCardCancelCS(); return 1;//等待失敗 } /* 函數 功 能:等待卡準備好 函數傳回值:0,準備好了;其他,錯誤代碼 */ unsigned char SDCardWaitBusy(void) { unsigned int t=0; do { if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK t++; }while(t<0xFFFFFF);//等待 return 1; } /* 函數功能:等待SD卡回應 函數參數: Response:要得到的回應值 返 回 值: 0,成功得到了該回應值 其他,得到回應值失敗 */ unsigned char SDCardGetAck(unsigned char Response) { u16 Count=0xFFFF;//等待次數 while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到準确的回應 if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回應失敗 else return SDCard_RESPONSE_NO_ERROR;//正确回應 } /* 函數功能:從sd卡讀取一個資料包的内容 函數參數: buf:資料緩存區 len:要讀取的資料長度. 傳回值: 0,成功;其他,失敗; */ unsigned char SDCardRecvData(unsigned char*buf,u16 len) { if(SDCardGetAck(0xFE))return 1;//等待SD卡發回資料起始令牌0xFE 開始接收資料 { *buf=SDCardReadWriteOneByte(0xFF); buf++; } 下面是2個僞CRC(dummy CRC) SDCardReadWriteOneByte(0xFF); SDCardReadWriteOneByte(0xFF); 讀取成功 } /* 函數功能:向sd卡寫入一個資料包的内容 512位元組 函數參數: buf 資料緩存區 cmd 指令 返 回 值:0表示成功;其他值表示失敗; */ unsigned char SDCardSendData(unsigned char*buf,unsigned char cmd) { u16 t; if(SDCardWaitBusy())return 1; //等待準備失效 SDCardReadWriteOneByte(cmd); if(cmd!=0XFD)//不是結束指令 { for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,減少函數傳參時間 忽略crc SDCardReadWriteOneByte(0xFF); 接收響應 if((t&0x1F)!=0x05)return 2; //響應錯誤 } 寫入成功 } /* 函數功能:向SD卡發送一個指令 函數參數: unsigned char cmd 指令 unsigned int arg 指令參數 unsigned char crc crc校驗值 傳回值:SD卡傳回的響應 */ unsigned char SendSDCardCmd(unsigned char cmd, unsigned int arg, unsigned char crc) { unsigned char r1; unsigned char Retry=0; SDCardCancelCS(); //取消上次片選 if(SDCardSelectCS())return 0XFF;//片選失效 //發送資料 SDCardReadWriteOneByte(cmd | 0x40);//分别寫入指令 SDCardReadWriteOneByte(arg >> 24); SDCardReadWriteOneByte(arg >> 16); SDCardReadWriteOneByte(arg >> 8); SDCardReadWriteOneByte(arg); SDCardReadWriteOneByte(crc); if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading Retry=0X1F; do { r1=SDCardReadWriteOneByte(0xFF); }while((r1&0X80) && Retry--);等待響應,或逾時退出 return r1;//傳回狀态值 } /* 函數功能:擷取SD卡的CID資訊,包括制造商資訊 函數參數:unsigned char *cid_data(存放CID的記憶體,至少16Byte) 返 回 值: 0:成功,1:錯誤 */ unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data) { unsigned char r1; 發SDCard_CMD10指令,讀CID r1=SendSDCardCmd(SDCard_CMD10,0,0x01); if(r1==0x00) { r1=SDCardRecvData(cid_data,16);//接收16個位元組的資料 } SDCardCancelCS();//取消片選 if(r1)return 1; else return 0; } /* 函數說明: 擷取SD卡的CSD資訊,包括容量和速度資訊 函數參數: unsigned char *cid_data(存放CID的記憶體,至少16Byte) 返 回 值: 0:成功,1:錯誤 */ unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data) { unsigned char r1; r1=SendSDCardCmd(SDCard_CMD9,0,0x01); //發SDCard_CMD9指令,讀CSD if(r1==0) { r1=SDCardRecvData(csd_data, 16);//接收16個位元組的資料 } SDCardCancelCS();//取消片選 if(r1)return 1; else return 0; } /* 函數功能:擷取SD卡的總扇區數(扇區數) 返 回 值: 0表示容量檢測出錯,其他值表示SD卡的容量(扇區數/512位元組) 說 明: 每扇區的位元組數必為512位元組,如果不是512位元組,則初始化不能通過. */ unsigned int GetSDCardSectorCount(void) { unsigned char csd[16]; unsigned int Capacity; unsigned char n; u16 csize; if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0;//取CSD資訊,如果期間出錯,傳回0 if((csd[0]&0xC0)==0x40)的卡,如果為SDHC卡,按照下面方式計算 { csize = csd[9] + ((u16)csd[8] << 8) + 1; Capacity = (unsigned int)csize << 10;//得到扇區數 } else//V1.XX的卡 { n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1; Capacity= (unsigned int)csize << (n - 9);//得到扇區數 } return Capacity; } /* 函數功能: 初始化SD卡 返 回 值: 非0表示初始化失敗! */ unsigned char SDCardDeviceInit(void) { 存放SD卡的傳回值 用來進行逾時計數 unsigned char buf[4]; u16 i; SDCardSpiInit();//初始化底層IO口 for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //發送最少74個脈沖 retry=20; do { r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//進入IDLE狀态 閑置 }while((r1!=0X01) && retry--); SD_Type=0; //預設無卡 if(r1==0X01) { if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1) //SD V2.0 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//Get trailing return value of R7 resp if(buf[2]==0X01&&buf[3]==0XAA) //卡是否支援2.7~3.6V { retry=0XFFFE; do { SendSDCardCmd(SDCard_CMD55,0,0X01);發送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//發送SDCard_CMD41 }while(r1&&retry--); if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鑒别SD2.0卡版本開始 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值 if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC; //檢查CCS else SD_Type=SDCard_TYPE_V2; } } } else//SD V1.x/ MMCV3 { SendSDCardCmd(SDCard_CMD55,0,0X01);//發送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//發送SDCard_CMD41 if(r1<=1) { SD_Type=SDCard_TYPE_V1; retry=0XFFFE; do //等待退出IDLE模式 { SendSDCardCmd(SDCard_CMD55,0,0X01);//發送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//發送SDCard_CMD41 }while(r1&&retry--); } else//MMC卡不支援SDCard_CMD55+SDCard_CMD41識别 { SD_Type=SDCard_TYPE_MMC;//MMC V3 retry=0XFFFE; do //等待退出IDLE模式 { r1=SendSDCardCmd(SDCard_CMD1,0,0X01);//發送SDCard_CMD1 }while(r1&&retry--); } if(retry==0||SendSDCardCmd(SDCard_CMD13,512,0X01)!=0)SD_Type=SDCard_TYPE_ERR;//錯誤的卡 } } SDCardCancelCS(); //取消片選 if(SD_Type)return 0; //初始化成功傳回0 else if(r1)return r1; //傳回值錯誤值 return 0xaa; //其他錯誤 } /* 函數功能:讀SD卡 函數參數: buf:資料緩存區 sector:扇區 cnt:扇區數 傳回值: 0,ok;其他,失敗. 說 明: SD卡一個扇區大小512位元組 */ unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt) { unsigned char r1; if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//轉換為位元組位址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//讀指令 if(r1==0)指令發送成功 { r1=SDCardRecvData(buf,512);//接收512個位元組 } }else { r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//連續讀指令 do { r1=SDCardRecvData(buf,512);//接收512個位元組 buf+=512; }while(--cnt && r1==0); SendSDCardCmd(SDCard_CMD12,0,0X01);//發送停止指令 } SDCardCancelCS();//取消片選 return r1;// } /* 函數功能:向SD卡寫資料 函數參數: buf:資料緩存區 sector:起始扇區 cnt:扇區數 傳回值: 0,ok;其他,失敗. 說 明: SD卡一個扇區大小512位元組 */ unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt) { unsigned char r1; if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//轉換為位元組位址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//讀指令 if(r1==0)//指令發送成功 { r1=SDCardSendData(buf,0xFE);//寫512個位元組 } } else { if(SD_Type!=SDCard_TYPE_MMC) { SendSDCardCmd(SDCard_CMD55,0,0X01); SendSDCardCmd(SDCard_CMD23,cnt,0X01);//發送指令 } r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//連續讀指令 if(r1==0) { do { r1=SDCardSendData(buf,0xFC);//接收512個位元組 buf+=512; }while(--cnt && r1==0); r1=SDCardSendData(0,0xFD);//接收512個位元組 } } SDCardCancelCS();//取消片選 return r1;// } /* 功能說明: 1. 支援檔案系統格式化: #mkfs.ext2 /dev/tiny4412_block_a 2. 支援mount挂載: #mount /dev/tiny4412_block_a /mnt/ 3. 支援磁盤大小檢視: #cat /sys/block/Tiny4412_block_a/size #df -h */ static struct request_queue *queue=NULL; /* 裝置請求隊列 */ static struct gendisk *gd; 結構 */ static unsigned int sd_size=0; 存放SD卡傳回的容量扇區數量機關(512位元組) static int Tiny4412_block_major = 0; /*存放主裝置号*/ static DEFINE_MUTEX(sd_mutex); /*靜态定義互斥鎖*/ /* * Handle an I/O request. * 實作扇區的讀寫 */ static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write) { /*互斥鎖,上鎖*/ mutex_lock(&sd_mutex); sector>>=9; nsect>>=9; /*塊裝置最小機關是一個扇區,一個扇區的位元組數是512位元組*/ if(write) { if(SDCardWriteData(buffer,sector,nsect)) { printk(KERN_ERR"write error!\r\n"); printk("write ---> nsect=%ld,sector=%ld\r\n",nsect,sector); } } else { if(SDCardReadData(buffer,sector,nsect)) { printk(KERN_ERR"read error!\r\n"); printk("read ---> nsect=%ld,sector=%ld\r\n",nsect,sector); } } /*互斥鎖解鎖*/ mutex_unlock(&sd_mutex); } /* 處理請求 */ static void Tiny4412_block_make_request(struct request_queue *q, struct bio *bio) { int dir; unsigned long long dsk_offset; struct bio_vec *bvec; int i; void *iovec_mem; /*判斷讀寫方向*/ if(bio_data_dir(bio) == WRITE) dir = 1; else dir = 0; dsk_offset = bio->bi_sector << 9; bio_for_each_segment(bvec, bio, i) { iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset; //起始位置,長度,源資料,方向 Tiny4412_block_dev_sector_read_write(dsk_offset,bvec->bv_len,iovec_mem,dir); kunmap(bvec->bv_page); dsk_offset += bvec->bv_len; } bio_endio(bio, 0); } static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 容量=heads*cylinders*sectors*512 存儲容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數 */ geo->heads = 2; /*磁頭(一般一個盤面有兩個磁頭,正面一個/反面一個)*/ geo->cylinders = 32; /*柱面(一般一個盤面上有32個柱面)每個盤片32個磁道)*/ geo->sectors = sd_size/2/32; /*扇區,一般每個磁道上有12個扇區,這裡需要根據前面柱面和磁頭進行計算,不能亂填*/ /*geo->sectors =存儲容量/磁頭數/柱面/每扇區位元組數*/ return 0; } /* * 塊裝置檔案操作集合接口 */ static struct block_device_operations Tiny4412_block_ops= { .owner = THIS_MODULE, 指令分區時需要調用該函數,用于讀取磁頭、柱面、扇區等資訊*/ .getgeo= tiny4412_blockdev_getgeo, }; /* 驅動入口 */ static int __init Tiny4412_block_init(void) { /*1. 初始化SD口*/ if(SDCardDeviceInit()) { 卡初始化失敗!\r\n"); return -1; } /*2. 檢測SD卡大小*/ sd_size=GetSDCardSectorCount();//檢測SD卡大小,傳回值右移11位得到以M為機關的容量 printk("SD卡Sizeof:%dM secnt=%d\r\n",sd_size>>11,sd_size); /*注冊一個塊裝置,自動配置設定主裝置号*/ Tiny4412_block_major = register_blkdev(0, "Tiny4412_SDdrv"); /*動态配置設定請求隊列*/ queue=blk_alloc_queue(GFP_KERNEL); /*綁定請求隊列*/ blk_queue_make_request(queue, Tiny4412_block_make_request); /*動态配置設定次裝置号結構*/ gd=alloc_disk(16);/*配置設定一個gendisk,分區是一個*/ gd->major=Tiny4412_block_major; 主裝置号*/ gd->first_minor=0;次裝置号*/ gd->fops=&Tiny4412_block_ops; gd->queue=queue; 将請求隊列關聯到gendisk結構*/ snprintf(gd->disk_name, 32, "tiny4412_sd%c",'a'); /*設定磁盤結構 capacity 的容量*/ /*注意: 塊裝置的大小使用扇區作為機關設定,而扇區的大小預設是512位元組。 可以檢視到設定的大小 512,或者右移9位 */ set_capacity(gd,sd_size); /*注冊磁盤裝置*/ add_disk(gd); printk("塊裝置注冊成功!\r\n"); return 0; } /*驅動出口*/ static void Tiny4412_block_exit(void) { /*釋放虛拟位址*/ iounmap(SD_GPBCON); iounmap(SD_GPBDAT); /*登出磁盤裝置*/ if(gd)del_gendisk(gd); /*登出塊裝置*/ if(Tiny4412_block_major!=0)unregister_blkdev(Tiny4412_block_major, "Tiny4412_SDdrv"); printk("塊裝置注消成功!\r\n"); } module_init(Tiny4412_block_init); module_exit(Tiny4412_block_exit); MODULE_LICENSE("GPL"); |
1.3.3 記憶體模拟塊裝置(使用預設的IO排程器)
- 示例代碼:
/* 參考:搜尋注冊函數即可 * drivers\block\xd.c * drivers\block\z2ram.c */ #include <linux/module.h> #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/timer.h> #include <linux/genhd.h> #include <linux/hdreg.h> #include <linux/ioport.h> #include <linux/init.h> #include <linux/wait.h> #include <linux/blkdev.h> #include <linux/blkpg.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/vmalloc.h> #include <linux/hdreg.h> #include <linux/version.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/dma.h> static struct gendisk *tiny4412_blockdev_disk; static struct request_queue *tiny4412_blockdev_queue; static int major; static DEFINE_SPINLOCK(tiny4412_blockdev_lock); #define RAMBLOCK_SIZE (1024*1024*10) /*10M空間*/ static unsigned char *tiny4412_blockdev_buf=NULL; static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 容量=heads*cylinders*sectors*512 存儲容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數 */ geo->heads = 2; /*磁頭(一般一個盤面有兩個磁頭,正面一個/反面一個)*/ geo->cylinders = 32; /*柱面(一般一個盤面上有32個柱面)每個盤片32個磁道)*/ geo->sectors = RAMBLOCK_SIZE/2/32/512; /*扇區,一般每個磁道上有12個扇區,這裡需要根據前面柱面和磁頭進行計算,不能亂填*/ return 0; } static struct block_device_operations tiny4412_blockdev_fops = { .owner= THIS_MODULE, .getgeo= tiny4412_blockdev_getgeo, }; static void do_tiny4412_blockdev_request(struct request_queue * q) { struct request *req; req = blk_fetch_request(q); while (req) { unsigned long start = blk_rq_pos(req) *512; /*轉為位元組機關(起始位置)*/ unsigned long len = blk_rq_cur_bytes(req); /*目前操作的位元組數量*/ int err = 0; if (rq_data_dir(req) == READ) /*如果是讀*/ memcpy(req->buffer, tiny4412_blockdev_buf+start, len); else memcpy(tiny4412_blockdev_buf+start, req->buffer,len); if (!__blk_end_request_cur(req, err)) /*判斷是否處理完畢請求*/ req = blk_fetch_request(q); /*繼續處理下一個請求*/ } } static int tiny4412_blockdev_init(void) { /* 1. 配置設定一個gendisk結構體 */ tiny4412_blockdev_disk = alloc_disk(16); /* 次裝置号個數: 分區個數+1 */ /* 2. 設定 */ /* 2.1 配置設定/設定隊列: 提供讀寫能力 */ tiny4412_blockdev_queue = blk_init_queue(do_tiny4412_blockdev_request, &tiny4412_blockdev_lock); tiny4412_blockdev_disk->queue = tiny4412_blockdev_queue; /* 2.2 設定其他屬性: 比如容量 */ major = register_blkdev(0, "tiny4412_blockdev"); /* cat /proc/devices */ tiny4412_blockdev_disk->major = major; tiny4412_blockdev_disk->first_minor = 0; sprintf(tiny4412_blockdev_disk->disk_name, "tiny4412_blockdev"); tiny4412_blockdev_disk->fops = &tiny4412_blockdev_fops; set_capacity(tiny4412_blockdev_disk, RAMBLOCK_SIZE / 512); /* 3. 硬體相關操作 */ tiny4412_blockdev_buf = vmalloc(RAMBLOCK_SIZE); if(tiny4412_blockdev_buf==NULL) { printk("空間申請失敗!\n"); return -1; } /* 4. 注冊 */ add_disk(tiny4412_blockdev_disk); return 0; } static void tiny4412_blockdev_exit(void) { unregister_blkdev(major, "tiny4412_blockdev"); del_gendisk(tiny4412_blockdev_disk); put_disk(tiny4412_blockdev_disk); blk_cleanup_queue(tiny4412_blockdev_queue); vfree(tiny4412_blockdev_buf); } module_init(tiny4412_blockdev_init); module_exit(tiny4412_blockdev_exit); MODULE_LICENSE("GPL"); |
1.3.4 使用SD卡編寫塊裝置(使用預設的IO排程器)
/* 參考: * drivers\block\xd.c * drivers\block\z2ram.c */ #include <linux/module.h> #include <linux/errno.h> #include <linux/interrupt.h> #include <linux/mm.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/timer.h> #include <linux/genhd.h> #include <linux/hdreg.h> #include <linux/ioport.h> #include <linux/init.h> #include <linux/wait.h> #include <linux/blkdev.h> #include <linux/blkpg.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/vmalloc.h> #include <linux/hdreg.h> #include <linux/version.h> #include <asm/system.h> #include <asm/uaccess.h> #include <asm/dma.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/delay.h> #include <linux/io.h> /*--------------------------------SD相關操作代碼---------------------------------------------*/ /*定義指針,用于接收虛拟位址*/ volatile unsigned int *SD_GPBCON; volatile unsigned int *SD_GPBDAT; /* 函數功能:SD初始化 Tiny4412硬體連接配接: DO--MISO :GPB_2 DI--MOSI :GPB_3 CLK-SCLK :GPB_0 CS--CS :GPB_1 */ void SDCardSpiInit(void) { /*1. 初始化GPIO*/ /*映射實體位址*/ SD_GPBCON=ioremap(0x11400040,4); SD_GPBDAT=ioremap(0x11400044,4); *SD_GPBCON &= ~(0xf << 0 * 4);*SD_GPBCON |= (0x1 << 0 * 4); *SD_GPBCON &= ~(0xf << 1 * 4);*SD_GPBCON |= (0x1 << 1 * 4); *SD_GPBCON &= ~(0xf << 2 * 4); *SD_GPBCON &= ~(0xf << 3 * 4);*SD_GPBCON |= (0x1 << 3 * 4); /*2. 上拉GPIO口*/ //*SD_GPBDAT &= ~(1 << 4);//輸出0 *SD_GPBDAT |= (1 << 0); //輸出1 *SD_GPBDAT |= (1 << 1); //輸出1 *SD_GPBDAT |= (1 << 3); //輸出1 } // SD卡類型定義 #define SDCard_TYPE_ERR 0X00 //卡類型錯誤 #define SDCard_TYPE_MMC 0X01 //MMC卡 #define SDCard_TYPE_V1 0X02 #define SDCard_TYPE_V2 0X04 #define SDCard_TYPE_V2HC 0X06 // SD卡指令表 #define SDCard_CMD0 0 //卡複位 #define SDCard_CMD1 1 #define SDCard_CMD8 8 //指令8 ,SEND_IF_COND #define SDCard_CMD9 9 //指令9 ,讀CSD資料 #define SDCard_CMD10 10 //指令10,讀CID資料 #define SDCard_CMD12 12 //指令12,停止資料傳輸 #define SDCard_CMD13 16 //指令16,設定扇區大小 應傳回0x00 #define SDCard_CMD17 17 //指令17,讀扇區 #define SDCard_CMD18 18 //指令18,讀Multi 扇區 #define SDCard_CMD23 23 //指令23,設定多扇區寫入前預先擦除N個block #define SDCard_CMD24 24 //指令24,寫扇區 #define SDCard_CMD25 25 //指令25,寫多個扇區 #define SDCard_CMD41 41 //指令41,應傳回0x00 #define SDCard_CMD55 55 //指令55,應傳回0x01 #define SDCard_CMD58 58 //指令58,讀OCR資訊 #define SDCard_CMD59 59 //指令59,使能/禁止CRC,應傳回0x00、 /*SD卡回應标記字*/ #define SDCard_RESPONSE_NO_ERROR 0x00 //正确回應 #define SDCard_SD_IN_IDLE_STATE 0x01 //閑置狀态 #define SDCard_SD_ERASE_RESET 0x02 //擦除複位 #define SDCard_RESPONSE_FAILURE 0xFF //響應失敗 //函數聲明 unsigned char SDCardReadWriteOneByte(unsigned char data); //底層接口,SPI讀寫位元組函數 unsigned char SDCardWaitBusy(void);//等待SD卡準備 unsigned char SDCardGetAck(unsigned char Response);//獲得應答 unsigned char SDCardDeviceInit(void);初始化 unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt);讀塊(扇區) unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt);寫塊(扇區) unsigned int GetSDCardSectorCount(void); 讀扇區數 unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data); //讀SD卡CID unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data); //讀SD卡CSD static unsigned char SD_Type=0; //存放SD卡的類型 /* 函數功能:SD卡底層接口,通過SPI時序向SD卡讀寫一個位元組 函數參數:data是要寫入的資料 返 回 值:讀到的資料 說明:時序是第二個上升沿采集資料 */ unsigned char SDCardReadWriteOneByte(unsigned char data_tx) { u8 data_rx=0; u8 i; for(i=0;i<8;i++) { *SD_GPBDAT &= ~(1 << 0);//輸出0 if(data_tx&0x80)*SD_GPBDAT |= (1 << 3); //輸出1 else *SD_GPBDAT &= ~(1 << 3);//輸出0 data_tx<<=1; //繼續發送下一個資料 *SD_GPBDAT |= (1 << 0); //輸出1 data_rx<<=1; if((*SD_GPBDAT & (1 << 2)))data_rx|=0x01; } return data_rx; } /* 函數功能:取消選擇,釋放SPI總線 */ void SDCardCancelCS(void) { *SD_GPBDAT |= (1 << 1); SDCardReadWriteOneByte(0xff);//提供額外的8個時鐘 } /* 函數 功 能:選擇sd卡,并且等待卡準備OK 函數傳回值:0,成功;1,失敗; */ unsigned char SDCardSelectCS(void) { *SD_GPBDAT &= ~(1 << 1); if(SDCardWaitBusy()==0)return 0;//等待成功 SDCardCancelCS(); return 1;//等待失敗 } /* 函數 功 能:等待卡準備好 函數傳回值:0,準備好了;其他,錯誤代碼 */ unsigned char SDCardWaitBusy(void) { unsigned int t=0; do { if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK t++; }while(t<0xFFFFFF);//等待 return 1; } /* 函數功能:等待SD卡回應 函數參數: Response:要得到的回應值 返 回 值: 0,成功得到了該回應值 其他,得到回應值失敗 */ unsigned char SDCardGetAck(unsigned char Response) { u16 Count=0xFFFF;//等待次數 while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到準确的回應 if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回應失敗 else return SDCard_RESPONSE_NO_ERROR;//正确回應 } /* 函數功能:從sd卡讀取一個資料包的内容 函數參數: buf:資料緩存區 len:要讀取的資料長度. 傳回值: 0,成功;其他,失敗; */ unsigned char SDCardRecvData(unsigned char*buf,u16 len) { if(SDCardGetAck(0xFE))return 1;//等待SD卡發回資料起始令牌0xFE 開始接收資料 { *buf=SDCardReadWriteOneByte(0xFF); buf++; } 下面是2個僞CRC(dummy CRC) SDCardReadWriteOneByte(0xFF); SDCardReadWriteOneByte(0xFF); 讀取成功 } /* 函數功能:向sd卡寫入一個資料包的内容 512位元組 函數參數: buf 資料緩存區 cmd 指令 返 回 值:0表示成功;其他值表示失敗; */ unsigned char SDCardSendData(unsigned char*buf,unsigned char cmd) { u16 t; if(SDCardWaitBusy())return 1; //等待準備失效 SDCardReadWriteOneByte(cmd); if(cmd!=0XFD)//不是結束指令 { for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,減少函數傳參時間 忽略crc SDCardReadWriteOneByte(0xFF); 接收響應 if((t&0x1F)!=0x05)return 2; //響應錯誤 } 寫入成功 } /* 函數功能:向SD卡發送一個指令 函數參數: unsigned char cmd 指令 unsigned int arg 指令參數 unsigned char crc crc校驗值 傳回值:SD卡傳回的響應 */ unsigned char SendSDCardCmd(unsigned char cmd, unsigned int arg, unsigned char crc) { unsigned char r1; unsigned char Retry=0; SDCardCancelCS(); //取消上次片選 if(SDCardSelectCS())return 0XFF;//片選失效 //發送資料 SDCardReadWriteOneByte(cmd | 0x40);//分别寫入指令 SDCardReadWriteOneByte(arg >> 24); SDCardReadWriteOneByte(arg >> 16); SDCardReadWriteOneByte(arg >> 8); SDCardReadWriteOneByte(arg); SDCardReadWriteOneByte(crc); if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading Retry=0X1F; do { r1=SDCardReadWriteOneByte(0xFF); }while((r1&0X80) && Retry--);等待響應,或逾時退出 return r1;//傳回狀态值 } /* 函數功能:擷取SD卡的CID資訊,包括制造商資訊 函數參數:unsigned char *cid_data(存放CID的記憶體,至少16Byte) 返 回 值: 0:成功,1:錯誤 */ unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data) { unsigned char r1; 發SDCard_CMD10指令,讀CID r1=SendSDCardCmd(SDCard_CMD10,0,0x01); if(r1==0x00) { r1=SDCardRecvData(cid_data,16);//接收16個位元組的資料 } SDCardCancelCS();//取消片選 if(r1)return 1; else return 0; } /* 函數說明: 擷取SD卡的CSD資訊,包括容量和速度資訊 函數參數: unsigned char *cid_data(存放CID的記憶體,至少16Byte) 返 回 值: 0:成功,1:錯誤 */ unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data) { unsigned char r1; r1=SendSDCardCmd(SDCard_CMD9,0,0x01); //發SDCard_CMD9指令,讀CSD if(r1==0) { r1=SDCardRecvData(csd_data, 16);//接收16個位元組的資料 } SDCardCancelCS();//取消片選 if(r1)return 1; else return 0; } /* 函數功能:擷取SD卡的總扇區數(扇區數) 返 回 值: 0表示容量檢測出錯,其他值表示SD卡的容量(扇區數/512位元組) 說 明: 每扇區的位元組數必為512位元組,如果不是512位元組,則初始化不能通過. */ unsigned int GetSDCardSectorCount(void) { unsigned char csd[16]; unsigned int Capacity; unsigned char n; u16 csize; if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0;//取CSD資訊,如果期間出錯,傳回0 if((csd[0]&0xC0)==0x40)的卡,如果為SDHC卡,按照下面方式計算 { csize = csd[9] + ((u16)csd[8] << 8) + 1; Capacity = (unsigned int)csize << 10;//得到扇區數 } else//V1.XX的卡 { n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1; Capacity= (unsigned int)csize << (n - 9);//得到扇區數 } return Capacity; } /* 函數功能: 初始化SD卡 返 回 值: 非0表示初始化失敗! */ unsigned char SDCardDeviceInit(void) { 存放SD卡的傳回值 用來進行逾時計數 unsigned char buf[4]; u16 i; SDCardSpiInit();//初始化底層IO口 for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //發送最少74個脈沖 retry=20; do { r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//進入IDLE狀态 閑置 }while((r1!=0X01) && retry--); SD_Type=0; //預設無卡 if(r1==0X01) { if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1) //SD V2.0 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//Get trailing return value of R7 resp if(buf[2]==0X01&&buf[3]==0XAA) //卡是否支援2.7~3.6V { retry=0XFFFE; do { SendSDCardCmd(SDCard_CMD55,0,0X01);發送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//發送SDCard_CMD41 }while(r1&&retry--); if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鑒别SD2.0卡版本開始 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值 if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC; //檢查CCS else SD_Type=SDCard_TYPE_V2; } } } else//SD V1.x/ MMCV3 { SendSDCardCmd(SDCard_CMD55,0,0X01);//發送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//發送SDCard_CMD41 if(r1<=1) { SD_Type=SDCard_TYPE_V1; retry=0XFFFE; do //等待退出IDLE模式 { SendSDCardCmd(SDCard_CMD55,0,0X01);//發送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//發送SDCard_CMD41 }while(r1&&retry--); } else//MMC卡不支援SDCard_CMD55+SDCard_CMD41識别 { SD_Type=SDCard_TYPE_MMC;//MMC V3 retry=0XFFFE; do //等待退出IDLE模式 { r1=SendSDCardCmd(SDCard_CMD1,0,0X01);//發送SDCard_CMD1 }while(r1&&retry--); } if(retry==0||SendSDCardCmd(SDCard_CMD13,512,0X01)!=0)SD_Type=SDCard_TYPE_ERR;//錯誤的卡 } } SDCardCancelCS(); //取消片選 if(SD_Type)return 0; //初始化成功傳回0 else if(r1)return r1; //傳回值錯誤值 return 0xaa; //其他錯誤 } /* 函數功能:讀SD卡 函數參數: buf:資料緩存區 sector:扇區 cnt:扇區數 傳回值: 0,ok;其他,失敗. 說 明: SD卡一個扇區大小512位元組 */ unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt) { unsigned char r1; if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//轉換為位元組位址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//讀指令 if(r1==0)指令發送成功 { r1=SDCardRecvData(buf,512);//接收512個位元組 } }else { r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//連續讀指令 do { r1=SDCardRecvData(buf,512);//接收512個位元組 buf+=512; }while(--cnt && r1==0); SendSDCardCmd(SDCard_CMD12,0,0X01);//發送停止指令 } SDCardCancelCS();//取消片選 return r1;// } /* 函數功能:向SD卡寫資料 函數參數: buf:資料緩存區 sector:起始扇區 cnt:扇區數 傳回值: 0,ok;其他,失敗. 說 明: SD卡一個扇區大小512位元組 */ unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt) { unsigned char r1; if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//轉換為位元組位址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//讀指令 if(r1==0)//指令發送成功 { r1=SDCardSendData(buf,0xFE);//寫512個位元組 } } else { if(SD_Type!=SDCard_TYPE_MMC) { SendSDCardCmd(SDCard_CMD55,0,0X01); SendSDCardCmd(SDCard_CMD23,cnt,0X01);//發送指令 } r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//連續讀指令 if(r1==0) { do { r1=SDCardSendData(buf,0xFC);//接收512個位元組 buf+=512; }while(--cnt && r1==0); r1=SDCardSendData(0,0xFD);//接收512個位元組 } } SDCardCancelCS();//取消片選 return r1;// } static struct gendisk *tiny4412_blockdev_disk; static struct request_queue *tiny4412_blockdev_queue; static int major; //主裝置号 static DEFINE_SPINLOCK(tiny4412_blockdev_lock); //自旋鎖 static unsigned int sd_size=0; 存放SD卡傳回的容量扇區數量機關(512位元組) static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo) { /* 容量=heads*cylinders*sectors*512 存儲容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數 */ geo->heads = 200; /*磁頭(一般一個盤面有兩個磁頭,正面一個/反面一個)*/ geo->cylinders = 32; /*柱面(一般一個盤面上有32個柱面)每個盤片32個磁道)*/ geo->sectors = sd_size/200/32; /*扇區,一般每個磁道上有12個扇區,這裡需要根據前面柱面和磁頭進行計算,不能亂填*/ return 0; } static struct block_device_operations tiny4412_blockdev_fops = { .owner= THIS_MODULE, .getgeo= tiny4412_blockdev_getgeo, }; static void do_tiny4412_blockdev_request(struct request_queue * q) { struct request *req; req = blk_fetch_request(q); while (req) { unsigned long start = blk_rq_pos(req); /*起始扇區位置*/ unsigned long len = (blk_rq_cur_bytes(req)>>9); /*目前操作的扇區數量*/ int err = 0; if(rq_data_dir(req) == READ) /*如果是讀*/ { if(SDCardReadData(req->buffer,start,len)) { printk(KERN_ERR"read error!\r\n"); printk("read ---> nsect=%ld,sector=%ld\r\n",len,start); } } else { if(SDCardWriteData(req->buffer,start,len)) { printk(KERN_ERR"write error!\r\n"); printk("write ---> nsect=%ld,sector=%ld\r\n",len,start); } } if (!__blk_end_request_cur(req, err)) /*判斷是否處理完畢請求*/ req = blk_fetch_request(q); /*繼續處理下一個請求*/ } } static int tiny4412_blockdev_init(void) { /*初始化SD卡*/ if(SDCardDeviceInit()) { 卡初始化失敗!\r\n"); return -1; } /*檢測SD卡大小*/ sd_size=GetSDCardSectorCount();//檢測SD卡大小,傳回值右移11位得到以M為機關的容量 printk("SD卡Sizeof:%dM secnt=%d\r\n",sd_size>>11,sd_size); /* 1. 配置設定一個gendisk結構體 */ tiny4412_blockdev_disk = alloc_disk(16); /* 次裝置号個數: 分區個數+1 */ /* 2. 設定 */ /* 2.1 配置設定/設定隊列: 提供讀寫能力 */ tiny4412_blockdev_queue = blk_init_queue(do_tiny4412_blockdev_request, &tiny4412_blockdev_lock); tiny4412_blockdev_disk->queue = tiny4412_blockdev_queue; /* 2.2 設定其他屬性: 比如容量 */ major = register_blkdev(0,"blockdev"); /* cat /proc/devices */ printk("注冊major=%d\n",major); tiny4412_blockdev_disk->major = major; tiny4412_blockdev_disk->first_minor = 0; sprintf(tiny4412_blockdev_disk->disk_name, "tiny4412_blockdev"); tiny4412_blockdev_disk->fops = &tiny4412_blockdev_fops; set_capacity(tiny4412_blockdev_disk, sd_size); //設定磁盤容量 /* 3. 硬體相關操作 */ /* 4. 注冊 */ add_disk(tiny4412_blockdev_disk); return 0; } static void tiny4412_blockdev_exit(void) { printk("登出major=%d\n",major); unregister_blkdev(major,"blockdev"); del_gendisk(tiny4412_blockdev_disk); put_disk(tiny4412_blockdev_disk); blk_cleanup_queue(tiny4412_blockdev_queue); /*釋放虛拟位址*/ iounmap(SD_GPBCON); iounmap(SD_GPBDAT); } module_init(tiny4412_blockdev_init); module_exit(tiny4412_blockdev_exit); MODULE_LICENSE("GPL"); |
1.4 磁盤的構造分析
硬碟是電腦主要的存儲媒介之一,由一個或者多個鋁制或者玻璃制的碟片組成。碟片外覆寫有鐵磁性材料。
硬碟有固态硬碟(SSD 盤,新式硬碟)、機械硬碟(HDD 傳統硬碟)、混合硬碟(HHD 一塊基于傳統機械硬碟誕生出來的新硬碟)。SSD采用閃存顆粒來存儲,HDD采用磁性碟片來存儲,混合硬碟(HHD: Hybrid Hard Disk)是把磁性硬碟和閃存內建到一起的一種硬碟。絕大多數硬碟都是固定硬碟,被永久性地密封固定在硬碟驅動器中。
圖4.1 機械硬碟
硬碟是集精密機械、微電子電路、電磁轉換為一體的電腦儲存設備,它存儲着電腦系統資源和重要的資訊及資料,這些因素使硬碟在PC機中成為最為重要的一個硬體裝置
- 最精密的部分--磁頭:由于磁頭工作的性質,對磁感應的要求非常高。磁頭是在高速旋轉的盤片上懸浮的,懸浮力來自盤片旋轉帶動的氣流,磁頭必須懸浮而不是接觸盤面,避免盤面和磁頭發生互相接觸的磨損。
-
硬碟存儲的媒體--盤片:盤片是以堅固耐用的材料為盤基,将磁粉附着在平滑的鋁合金或玻璃圓盤基上。這些磁粉被劃分成稱為磁道的若幹個同心圓,每個同心圓就好像有無數的小磁鐵,它們分别代表着0和1狀态。當小磁鐵受到來自磁頭的磁力影響時,其排列方向會随之改變。
下面主要講解機械硬碟的構造,機械硬碟是由一個個盤片組成的,我們先從個盤片結構講起。下圖圖中的一圈圈灰色同心圓為一條條磁道,從圓心向外畫直線,可以将磁道劃分為若幹個弧段,每個磁道上一個弧段被稱之為一個扇區(圖踐綠色部分)。扇區是磁盤的最小組成單元,通常是512位元組。
- 圖4.2 盤面構造
- 磁盤的常見參數如下:
- 磁頭(head)
- 磁道(track)
- 柱面(cylinder)
- 扇區(sector)
- 圓盤(platter)
上圖2中磁盤是一個有 3個盤面6個磁頭(一個盤面有正反面兩個磁頭,兩面都可以獨立讀寫),7個柱面(每個盤片7個磁道) 的磁盤,每條磁道有12個扇區,是以此磁盤的容量為6*7*12*512位元組。
計算方法:存儲容量=磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數