linux核心原始碼閱讀-塊裝置驅動

redrobot發表於2024-07-23

來自:

https://in1t.top/2020/06/04/linux%E5%86%85%E6%A0%B8%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB-%E5%9D%97%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8/

開始 fs 模組之前,我發現如果對塊裝置/字元裝置的驅動程式不瞭解的話,讀 fs 程式碼時會困難重重。為了簡化問題,本文及之後的 fs 模組都將只記錄關於塊裝置(特指硬碟)的程式碼,先弄懂一個,剩下的讀起來就輕鬆了。閱讀本文或許會一頭霧水,但和下篇文章聯絡起來看就會清楚許多了(x

塊裝置操作方式(以讀資料為例)

提到 I/O 先來看一張圖:

塊裝置操作方式

當程式需要從硬碟中讀取資料(read 系統呼叫)時,緩衝區管理程式會先查詢該資料塊是否已經讀入到緩衝區中。如果是,則直接將該緩衝頭(涉及高速緩衝的管理方式,下篇文章將會記錄)返回並喚醒等待此資料塊的程序;否則呼叫 ll_rw_block 函式,告訴塊裝置驅動程式(核心程式碼)現在需要讀資料塊,該函式就會為其建立一個請求項,並掛入相應裝置的請求佇列,同時發出請求的程序會被掛起(不可中斷睡眠態)。

當請求被處理時,裝置控制器根據請求項中的引數,向硬碟驅動器傳送讀指令,硬碟驅動器就會將資料讀取到裝置控制器的緩衝區中(注意此時原發出讀盤請求的程序已被掛起,CPU 正在被其他程序佔用)。當裝置控制器檢測到資料讀取完畢,就會產生一箇中斷請求訊號發往 CPU,CPU 在硬碟中斷處理程式 hd_interrupt 中呼叫 read_intr 函式將資料從裝置控制器的緩衝區搬到記憶體的高速緩衝區中,並讓裝置控制器開始處理下一個請求(如果有的話)。最後核心將高速緩衝中的資料複製到呼叫 read 函式時第二個引數指向的地址中去。用一張圖來總結:

塊裝置操作方式

請求項與請求佇列

請求項

請求項的資料結構如下:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// kernel/blk_drv/blk.h Line 23
struct request {
int dev; // 使用的裝置號,為 -1 表示該請求項空閒
int cmd; // 表示該請求項的操作是讀還是寫
int errors; // 操作時產生的錯誤次數
unsigned long sector; // 開始扇區
unsigned long nr_sectors; // 讀/寫 扇區數量
char * buffer; // 指向高速緩衝區,資料會從裝置控制器的緩衝區搬到這裡來
struct task_struct * waiting; // 等待該請求完成的任務
struct buffer_head * bh; // 緩衝區頭指標
struct request * next; // 指向下一個請求項
};

// ll_rw_blk.c Line 21
struct request request[NR_REQUEST]; // 請求項陣列,NR_REQUEST 值為 32

為什麼請求項已經可以透過 next 指標構成單項鍊表了,還需要一個陣列來維護呢?採用陣列加連結串列結構其實是為了滿足兩個目的:

  • 陣列結構使得在搜尋空閒請求項的時候可以進行迴圈操作,搜尋訪問時間複雜度為常數
  • 連結串列結構是為了滿足電梯演算法插入請求項的操作

請求佇列

對於各種塊裝置,核心使用塊裝置表 blk_dev 來管理,每種塊裝置在塊裝置表中佔有一項,相關資料結構如下:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// blk.h Line 45
// 塊裝置表項
struct blk_dev_struct {
void (*request_fn)(void); // 處理請求項的函式指標
struct request * current_request; // 該裝置當前請求佇列頭指標
};

// ll_rw_blk.c Line 32
// 初始化塊裝置表,NR_BLK_DEV 值為 7,以裝置的主裝置號為索引
struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
{ NULL, NULL }, // 無
{ NULL, NULL }, // 虛擬盤,對應的請求處理函式為 do_rd_request
{ NULL, NULL }, // 軟盤,對應的請求處理函式為 do_fd_request
{ NULL, NULL }, // 硬碟,對應的請求處理函式為 do_hd_request
{ NULL, NULL }, // 無用
{ NULL, NULL }, // 無用
{ NULL, NULL } // 無用
};

再來透過一張圖直觀地感受這些資料結構之間的關係:

請求項與請求佇列

透過之前的描述不難看出,硬碟裝置有 4 個請求,軟盤裝置有 1 個請求,虛擬盤暫無請求。下面正式開始塊裝置(僅硬碟)驅動程式部分原始碼的閱讀

blk.h

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// Line 1
#ifndef _BLK_H
#define _BLK_H

#define NR_BLK_DEV 7 // 塊裝置型別數量
#define NR_REQUEST 32 // 請求項陣列長度

struct request { // 請求項結構體,上面已經提及
int dev;
int cmd;
int errors;
unsigned long sector;
unsigned long nr_sectors;
char * buffer;
struct task_struct * waiting;
struct buffer_head * bh;
struct request * next;
};

// 單項電梯演算法對插入請求佇列的請求項進行排序,將請求項插入到磁頭移動距離最小的位置處
#define IN_ORDER(s1,s2) \
((s1)->cmd<(s2)->cmd || (s1)->cmd==(s2)->cmd && \
((s1)->dev < (s2)->dev || ((s1)->dev == (s2)->dev && \
(s1)->sector < (s2)->sector)))

struct blk_dev_struct { // 裝置表項結構體,上面已經提及
void (*request_fn)(void);
struct request * current_request;
};

extern struct blk_dev_struct blk_dev[NR_BLK_DEV]; // 裝置表
extern struct request request[NR_REQUEST]; // 請求項陣列
extern struct task_struct * wait_for_request; // 等待空閒請求項的程序佇列頭指標

#ifdef MAJOR_NR
...
#elif (MAJOR_NR == 3) // 如果主裝置號是 3,即硬碟
#define DEVICE_NAME "harddisk" // 裝置名稱
#define DEVICE_INTR do_hd // 裝置中斷處理函式
#define DEVICE_REQUEST do_hd_request // 裝置請求項處理函式
#define DEVICE_NR(device) (MINOR(device)/5) // 硬碟裝置號(0 - 1)
#define DEVICE_ON(device)
#define DEVICE_OFF(device) // 開機後硬碟總是運轉
#elif (MAJOR_NR > 3) // 主裝置號大於 3
#error "unknown blk device" // 未知塊裝置
#endif

#define CURRENT (blk_dev[MAJOR_NR].current_request) // 指定裝置號的當前請求項指標
#define CURRENT_DEV DEVICE_NR(CURRENT->dev) // 當前請求項的裝置號

#ifdef DEVICE_INTR // 如果定義了裝置中斷處理符號常數
void (*DEVICE_INTR)(void) = NULL; // 將其宣告為一個值為 NULL 的函式指標
#endif
static void (DEVICE_REQUEST)(void); // 將符號常數 DEVICE_REQUEST 宣告為一個靜態函式指標

// 解鎖指定緩衝塊,引數是該緩衝塊的緩衝塊頭指標
static inline void unlock_buffer(struct buffer_head * bh)
{
if (!bh->b_lock) // 如果已經處於解鎖狀態,發出警告
printk(DEVICE_NAME ": free buffer being unlocked\n");
bh->b_lock=0; // 解鎖
wake_up(&bh->b_wait); // 喚醒等待該緩衝塊的程序
}

// 結束請求,引數用於設定緩衝塊資料更新標誌
static inline void end_request(int uptodate)
{
DEVICE_OFF(CURRENT->dev); // 關閉裝置
if (CURRENT->bh) { // 如果當前請求項的緩衝塊頭指標不為空
CURRENT->bh->b_uptodate = uptodate; // 設定資料更新標誌
unlock_buffer(CURRENT->bh); // 解鎖緩衝塊
}
if (!uptodate) { // 如果引數給定的更新標誌為 0,則顯示出錯資訊
printk(DEVICE_NAME " I/O error\n\r");
printk("dev %04x, block %d\n\r",CURRENT->dev,
CURRENT->bh->b_blocknr);
}
wake_up(&CURRENT->waiting); // 喚醒等待該請求項完成的程序
wake_up(&wait_for_request); // 喚醒等待空閒請求項的程序
CURRENT->dev = -1; // 設定當前請求項空閒
CURRENT = CURRENT->next; // 指向下一請求項
}

#define INIT_REQUEST \ // 初始化請求項
repeat: \
if (!CURRENT) \ // 如果沒有請求項,則返回
return; \
if (MAJOR(CURRENT->dev) != MAJOR_NR) \ // 如果當前裝置主裝置號不對則停機
panic(DEVICE_NAME ": request list destroyed"); \
if (CURRENT->bh) { \ // 如果緩衝塊頭指標不為空
if (!CURRENT->bh->b_lock) \ // 且沒有被鎖定
panic(DEVICE_NAME ": block not locked"); \ // 停機
}

#endif
#endif

hd.c

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Line 16
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/hdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#define MAJOR_NR 3 // 定義主裝置號為 3,即硬碟
#include "blk.h"

#define CMOS_READ(addr) ({ \ // 讀 CMOS 引數
outb_p(0x80|addr,0x70); \ // 0x80 | addr 是要讀的 CMOS 記憶體地址,0x70 是寫埠號
inb_p(0x71); \ // 0x71 是讀埠號
})

#define MAX_ERRORS 7 // 讀/寫 一個扇區時允許的最多出錯次數
#define MAX_HD 2 // 系統支援的最多硬碟數

static void recal_intr(void); // 重新校正處理函式

static int recalibrate = 0; // 重新校正標誌,設定為 1 時,程式會呼叫 recal_intr
static int reset = 0; // 復位標誌,當發生讀寫錯誤時會設定該標誌並呼叫相關復位函式

// 硬碟資訊結構體
// 包括磁頭數、每磁軌扇區數、柱面數、寫前預補償柱面號,磁頭著陸區柱面號、控制位元組
struct hd_i_struct {
int head,sect,cyl,wpcom,lzone,ctl;
};

// 如果在 include/linux/config.h 中已經定義了 HD_TYPE,就取定義好的引數
// 作為硬碟資訊陣列 hd_info 中的資料,否則填充為 0
#ifdef HD_TYPE
struct hd_i_struct hd_info[] = { HD_TYPE };
#define NR_HD ((sizeof (hd_info))/(sizeof (struct hd_i_struct)))
#else
struct hd_i_struct hd_info[] = { {0,0,0,0,0,0},{0,0,0,0,0,0} };
static int NR_HD = 0;
#endif

// 硬碟分割槽結構體
static struct hd_struct {
long start_sect; // 分割槽在硬碟中的起始(絕對)扇區
long nr_sects; // 分割槽所佔的扇區總數
} hd[5*MAX_HD]={{0,0},};

// 讀埠宏定義,從埠 port 讀 nr 字,儲存在 buf 中
#define port_read(port,buf,nr) \
__asm__("cld;rep;insw"::"d" (port),"D" (buf),"c" (nr))

// 寫埠宏定義,從 buf 中取資料,向埠 port 寫 nr 字
#define port_write(port,buf,nr) \
__asm__("cld;rep;outsw"::"d" (port),"S" (buf),"c" (nr))

extern void hd_interrupt(void);
extern void rd_load(void);

接下來是在 init 函式中呼叫的 setup 系統呼叫,引數 BIOS 是 setup.s 程式取得並放置在 0x90080 處的包含兩個硬碟引數的硬碟參數列指標(大小為 32 位元組)

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// Line 71
int sys_setup(void * BIOS)
{
static int callable = 1; // 限制本函式只能被呼叫 1 次
int i,drive;
unsigned char cmos_disks;
struct partition *p;
struct buffer_head * bh;

if (!callable) // 判斷是否是第一次呼叫
return -1;
callable = 0; // 呼叫一次該標誌就設定為 0
#ifndef HD_TYPE // 如果沒有定義 HD_TYPE,則讀取硬碟引數
for (drive=0 ; drive<2 ; drive++) { // 迴圈讀取兩個硬碟的引數
hd_info[drive].cyl = *(unsigned short *) BIOS; // 柱面數
hd_info[drive].head = *(unsigned char *) (2+BIOS); // 磁頭數
hd_info[drive].wpcom = *(unsigned short *) (5+BIOS); // 寫前預補償柱面號
hd_info[drive].ctl = *(unsigned char *) (8+BIOS); // 控制位元組
hd_info[drive].lzone = *(unsigned short *) (12+BIOS); // 磁頭著陸區柱面號
hd_info[drive].sect = *(unsigned char *) (14+BIOS); // 每磁軌扇區數
BIOS += 16;
}
if (hd_info[1].cyl) // 判斷第二個硬碟柱面數是否為 0,為零表示只有一個硬碟
NR_HD=2; // 設定硬碟數
else
NR_HD=1;
#endif
// hd 陣列的第 0 項和 第 5 項分別表示兩個硬碟的整體引數
// 第 1 ~ 4,6 ~ 9 項分別表示兩個硬碟各自 4 個分割槽的引數
// 這裡先只設定第 0 和第 5 項
for (i=0 ; i<NR_HD ; i++) {
hd[i*5].start_sect = 0; // 起始扇區
hd[i*5].nr_sects = hd_info[i].head* // 總扇區數
hd_info[i].sect*hd_info[i].cyl;
}

// 檢測硬碟是否是 AT 控制器相容的
// 從 CMOS 偏移地址 0x12 處讀出硬碟型別位元組,如果高半個位元組不為 0,表示系統至少有一個相容硬碟
if ((cmos_disks = CMOS_READ(0x12)) & 0xf0)
// 如果低半個位元組不為 0,表示系統有兩個相容硬碟
if (cmos_disks & 0x0f)
NR_HD = 2;
else
NR_HD = 1;
// 否則(高半個位元組為 0)系統沒有相容硬碟
else
NR_HD = 0;
// 將不相容的硬碟資訊清空
for (i = NR_HD ; i < 2 ; i++) {
hd[i*5].start_sect = 0;
hd[i*5].nr_sects = 0;
}
// 讀取每個硬碟第一個盤塊中的分割槽表資訊,設定 hd 陣列
for (drive=0 ; drive<NR_HD ; drive++) {
// 第一個引數 0x300 和 0x305 為兩個硬碟的裝置號,第二個引數 0 是取的塊號
if (!(bh = bread(0x300 + drive*5,0))) { // bh 為空則停機
printk("Unable to read partition table of drive %d\n\r",
drive);
panic("");
}
// 判斷第一個扇區最後兩個位元組是否為 0xAA55(硬碟的有效性)
if (bh->b_data[510] != 0x55 || (unsigned char)
bh->b_data[511] != 0xAA) {
printk("Bad partition table on drive %d\n\r",drive);
panic("");
}
p = 0x1BE + (void *)bh->b_data; // 獲取分割槽表地址
for (i=1;i<5;i++,p++) { // 初始化 hd 陣列 1 ~ 4,6 ~ 9 項
hd[i+5*drive].start_sect = p->start_sect;
hd[i+5*drive].nr_sects = p->nr_sects;
}
brelse(bh); // 釋放 bh 緩衝塊
}
if (NR_HD) // 列印初始化分割槽表完成的資訊
printk("Partition table%s ok.\n\r",(NR_HD>1)?"s":"");
rd_load(); // 嘗試在系統記憶體虛擬盤中載入啟動盤中包含的根檔案系統映像(如果有的話)
mount_root(); // 安裝根檔案系統
return (0);
}

然後是 main 函式中呼叫的 hd_init 函式

C
1
2
3
4
5
6
7
8
9
// Line 345
void hd_init(void)
{
// 設定硬碟裝置請求處理函式為 do_hd_request
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
set_intr_gate(0x2E,&hd_interrupt); // 設定硬碟中斷服務程式入口
outb_p(inb_p(0x21)&0xfb,0x21); // 允許從片發出中斷請求訊號
outb(inb_p(0xA1)&0xbf,0xA1); // 允許硬碟控制器傳送中斷請求訊號
}

向硬碟控制器傳送命令的函式 hd_out,引數 drive 是驅動器號,nsect 是讀寫扇區數,sect 是起始盤區,head 是磁頭號,cyl 是柱面號,cmd 是命令碼,intr_addr 型別為函式指標,呼叫時此處需要傳一個函式名,該函式將在硬碟中斷處理程式中被呼叫(類似 hook)

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Line 182
static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,
unsigned int head,unsigned int cyl,unsigned int cmd,
void (*intr_addr)(void))
{
register int port asm("dx");

if (drive>1 || head>15) // 驅動器號只能為 0/1,磁頭號不能大於 15
panic("Trying to write bad sector");
if (!controller_ready()) // 等待硬碟控制器就緒,如果等待一段時間後還沒有就緒,當機
panic("HD controller not ready");
do_hd = intr_addr; // 設定硬碟中斷處理時呼叫的函式
outb_p(hd_info[drive].ctl,HD_CMD); // 向控制暫存器輸出控制位元組,建立指定硬碟的控制方式
port=HD_DATA; // 之後幾行向控制器埠 0x1f1 ~ 0x1f7 傳送 7 位元組的引數命令塊
outb_p(hd_info[drive].wpcom>>2,++port); // 寫預補償柱面號
outb_p(nsect,++port); // 讀/寫 扇區總數
outb_p(sect,++port); // 起始扇區
outb_p(cyl,++port); // 柱面號低 8 位
outb_p(cyl>>8,++port); // 柱面號高 8 位
outb_p(0xA0|(drive<<4)|head,++port); // 驅動器號 + 磁頭號
outb(cmd,++port); // 硬碟控制命令(如 WRITE/READ)
}

處理硬碟當前請求項的函式 do_hd_request

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Line 296
void do_hd_request(void)
{
int i,r;
unsigned int block,dev;
unsigned int sec,head,cyl;
unsigned int nsect;

INIT_REQUEST; // 一個宏定義,上面寫過,檢查請求佇列中是否有請求項及請求項的合法性
dev = MINOR(CURRENT->dev); // 取得子裝置號
block = CURRENT->sector; // 請求的起始扇區
// 如果子裝置號超出裝置號範圍或請求的扇區號大於分割槽中倒數第二個扇區號
if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects) {
end_request(0); // 結束請求
goto repeat; // repeat 標號在 INIT_REQUEST 宏定義中
}
block += hd[dev].start_sect; // 加上子裝置號對應分割槽的起始扇區號
dev /= 5; // 被 5 整除得到對應的硬碟號
__asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0),
"r" (hd_info[dev].sect)); // 計算扇區號(sec)
__asm__("divl %4":"=a" (cyl),"=d" (head):"0" (block),"1" (0),
"r" (hd_info[dev].head)); // 計算柱面號(cyl)
sec++;
nsect = CURRENT->nr_sectors; // 讀/寫 扇區數
if (reset) { // 若復位標誌置位
reset = 0; // 復位
recalibrate = 1; // 重新校正標誌
reset_hd(CURRENT_DEV); // 重新校正硬碟
return;
}
if (recalibrate) { // 如果重新校正標誌置位
recalibrate = 0; //復位
hd_out(dev,hd_info[CURRENT_DEV].sect,0,0,0,
WIN_RESTORE,&recal_intr); // 向硬碟控制器傳送重新校正命令
return;
}
if (CURRENT->cmd == WRITE) { // 如果是寫操作
hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr); // 傳送寫命令
for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++)
/* nothing */ ; // 迴圈讀取狀態暫存器資訊,判斷請求標誌 DRQ_STAT 是否置位
if (!r) { // 如果等待結束還沒有置位,表示寫命令失敗
bad_rw_intr(); // 處理出現的問題,reset 標誌置 1(硬碟復位),判斷是否允許重試
goto repeat; // 繼續處理請求
}
port_write(HD_DATA,CURRENT->buffer,256); // 沒出問題直接寫
} else if (CURRENT->cmd == READ) { // 如果是寫操作
hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr); // 傳送讀命令
} else
panic("unknown hd-command"); // 不允許其他操作(cmd)
}

另一類函式是硬碟中斷處理過程中可被呼叫的函式,它們有 read_intr、write_intr、bad_rw_intr、recal_intr

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// Line 252
// 讀盤請求完成後在硬碟中斷中被呼叫,
// 將一個扇區的資料從硬碟控制器的緩衝區讀到記憶體的高速緩衝
static void read_intr(void)
{
if (win_result()) { // 檢查此次讀命令操作是否出錯
bad_rw_intr(); // 出錯則處理問題,reset 標誌置 1(硬碟復位),判斷是否允許重試
do_hd_request(); // 繼續處理請求
return;
}
port_read(HD_DATA,CURRENT->buffer,256); // 從資料暫存器埠將 1 各扇區讀到高速緩衝中
CURRENT->errors = 0; // 設定出錯次數為 0
CURRENT->buffer += 512; // 請求項的緩衝指標向後移動 1 個扇區
CURRENT->sector++; // 請求項的起始扇區遞增
if (--CURRENT->nr_sectors) { // 讀/寫 操作的扇區數遞減,並判斷是否還有資料沒讀完
do_hd = &read_intr; // 如果是的話再次設定硬碟中斷中呼叫的函式為 read_intr
return;
}
end_request(1); // 否則正常結束請求
do_hd_request(); // 接著處理下一個請求(如果有)
}

// 在硬碟寫命令結束時引發的硬碟中斷中被呼叫,與 read_intr 邏輯相似
static void write_intr(void)
{
if (win_result()) {
bad_rw_intr();
do_hd_request();
return;
}
if (--CURRENT->nr_sectors) { // 如果資料沒寫完
CURRENT->sector++;
CURRENT->buffer += 512;
do_hd = &write_intr; // 再次設定硬碟中斷中呼叫的函式為 write_intr
port_write(HD_DATA,CURRENT->buffer,256);
return;
}
end_request(1);
do_hd_request();
}

// Line 244
// 讀寫硬碟失敗呼叫的函式
static void bad_rw_intr(void)
{
if (++CURRENT->errors >= MAX_ERRORS) // 如果出錯次數 >= 7
end_request(0); // 結束當前請求項
if (CURRENT->errors > MAX_ERRORS/2) // 如果出錯次數 > 3
reset = 1; // 置位 reset 標誌,復位硬碟控制器
}

// Line 289
// 重新校正函式
static void recal_intr(void)
{
if (win_result()) // 判斷上次命令結束後的狀態
bad_rw_intr(); // 如果出錯,處理出錯
do_hd_request(); // 接著處理請求
}

剩下的函式為操作硬碟控制器的輔助函式

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// Line 161
// 等待硬碟控制器就緒
static int controller_ready(void)
{
int retries=100000;

// 迴圈檢測驅動器狀態暫存器忙位(位 7)是否為 1,判斷控制器是否處於忙狀態
while (--retries && (inb_p(HD_STATUS)&0x80));
return (retries);
}

// 檢測上次命令的結束狀態
static int win_result(void)
{
int i=inb_p(HD_STATUS); // 讀取狀態暫存器中的命令執行結束狀態

if ((i & (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT))
== (READY_STAT | SEEK_STAT)) // 正常
return(0);
if (i&1) i=inb(HD_ERROR); // 如果 ERR_STAT 置位,讀取錯誤暫存器
return (1);
}

// Line 204
// 等待硬碟就緒
static int drive_busy(void)
{
unsigned int i;

for (i = 0; i < 10000; i++)
// 如果就緒或尋道結束標誌位置位表示硬碟就緒,跳出迴圈
if (READY_STAT == (inb_p(HD_STATUS) & (BUSY_STAT|READY_STAT)))
break;
i = inb(HD_STATUS); // 讀取狀態暫存器中的命令執行結束狀態
i &= BUSY_STAT | READY_STAT | SEEK_STAT;
if (i == READY_STAT | SEEK_STAT) // 正常
return(0);
printk("HD controller times out\n\r"); // 等待超時
return(1);
}

// 重新校正硬碟控制器
static void reset_controller(void)
{
int i;

outb(4,HD_CMD); // 向控制暫存器埠傳送復位控制位元組
for(i = 0; i < 100; i++) nop(); // 等待一段時間
outb(hd_info[0].ctl & 0x0f ,HD_CMD); // 傳送正常控制位元組(不禁止重試、重讀)
if (drive_busy()) // 等待硬碟就緒
printk("HD-controller still busy\n\r");
if ((i = inb(HD_ERROR)) != 1) // 讀取錯誤暫存器內容,若不為 1 表示復位失敗
printk("HD-controller reset failed: %02x\n\r",i);
}

相關文章