linux0.11块设备驱动及访问请求管理程序阅读注释笔记
linux-dash
A beautiful web dashboard for Linux
项目地址:https://gitcode.com/gh_mirrors/li/linux-dash
免费下载资源
·
[ 1] linux0.11引导程序阅读注释。
[ 2] linux0.11由实模式进入保护模式程序阅读注释 。
[ 3] linux0.11护模式初始化程序阅读注释。
[ 4] linux0.11主存管理程序阅读注释。
[ 5] linux0.11中断/异常机制初始设置相关程序阅读注释。
[ 6] linux0.11缓冲区管理程序阅读注释。
[ 7] linux0.11文件系统管理程序阅读注释。
篇幅较长,可通过浏览器的搜索功能(Ctrl + f)搜索函数名了解相应函数的实现机制,如 lock_buffer。
[8] linux0.11块设备驱动及访问请求管理程序阅读注释
/* 粗略总结块设备请求管理。
* |--------------|
* | file request |
* |--------------|
* ∧
* | |------------------------------------|
* | |filesystem structure buffer(mapping)|
* | |------------------------------------|
* V
* |----------------------------|
* |block device request(manage)|
* |----------------------------|
* ∧
* | |------------------------|
* | |file data buffer(manage)|
* | |------------------------|
* V
* ..............................
* . logical filesystem mapping .
* ..............................
* ∧
* | I/O指令和中断机制
* V
* |——————————|
* | physical |
* | device |
* |——————————| */
main.c
/* struct drive_info结构体类型用于描述setup.s中获取并存储的硬盘参数,
* drive_info保存这些信息, 在main开始处初始化。*/
struct drive_info { char dummy[32]; } drive_info;
void main(void) /* This really IS void, no error here. */
{
/* 在bootsect.s偏移508处设置了根文件系统的逻辑设备分区号。
* 获取bootsect.s所设置的根文件设备号到全局变量ROOT_DEV。
* ROOT_DEV为定义在fs/supper.c中的全局变量,
* 声明在include/fs.h中。*/
ROOT_DEV = ORIG_ROOT_DEV;
/* 将setup.s通过BIOS所获取的硬盘参数信息
* 存于全局变量drive_info中。*/
drive_info = DRIVE_INFO;
/* ... */
#ifdef RAMDISK /* 初始化虚拟硬盘 $$$ */
main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
/* ... */
/* 块设备访问请求管理初始化 */
blk_dev_init();
/* ... */
/* 设置硬盘和软盘的读写请求函数,
* 设置硬盘和软盘中断处理函数,使能硬盘和软盘中断。*/
hd_init();
floppy_init();
}
ramdisk.c
/*
* linux/kernel/blk_drv/ramdisk.c
*
* Written by Theodore Ts'o, 12/2/91
*/
#include <string.h>
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/memory.h>
#define MAJOR_NR 1 /* 虚拟硬盘主设备号 */
#include "blk.h"
/* 用于记录虚拟硬盘内存首地址和长度的全局变量 */
char *rd_start;
int rd_length = 0;
/* do_rd_request,
* 虚拟硬盘读写请求函数。*/
void do_rd_request(void)
{
int len;
char *addr;
/* 检查当前请求是否合理 */
INIT_REQUEST;
/* 将扇区号换算为内存地址 */
addr = rd_start + (CURRENT->sector << 9);
len = CURRENT->nr_sectors << 9;
/* 检查次设备号和所读内存地址,若不在范围内则结束本次请求并调度下一个请求 */
if ((MINOR(CURRENT->dev) != 1) || (addr+len > rd_start+rd_length)) {
end_request(0);
goto repeat;
}
/* 若请求为写虚拟硬盘,则将请求缓冲区中的数据拷贝到虚拟硬盘相应内存段中 */
if (CURRENT-> cmd == WRITE) {
(void ) memcpy(addr,
CURRENT->buffer,
len);
/* 若请求为读虚拟硬盘,则将虚拟硬盘相应内存段数据拷贝到请求缓冲区中 */
} else if (CURRENT->cmd == READ) {
(void) memcpy(CURRENT->buffer,
addr,
len);
} else
panic("unknown ramdisk-command");
/* 正常结束本次虚拟硬盘请求并调度虚拟硬盘下一个请求,
* 同时回到INIT_REQUEST中repeat处检查该请求的合理性。*/
end_request(1);
goto repeat;
}
/*
* Returns amount of memory which needs to be reserved.
*/
/* [1] rd_init,
* 初始化与虚拟硬盘内存段[mem_start, mem_start+length)相关信息。*/
long rd_init(long mem_start, int length)
{
int i;
char *cp;
/* 设置虚拟硬盘设备的请求函数 */
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
/* 用全局变量记录虚拟硬盘内存段信息并将该段内存初始化为0 */
rd_start = (char *) mem_start;
rd_length = length;
cp = rd_start;
for (i=0; i < length; i++)
*cp++ = '\0';
return(length);
}
/*
* If the root device is the ram disk, try to load it.
* In order to do this, the root device is originally set to the
* floppy, and we later change it to be ram disk.
*/
/* [2] rd_load,
* */
void rd_load(void)
{
struct buffer_head *bh;
struct super_block s;
int block = 256; /* Start at block 256 */
int i = 1;
int nblocks;
char *cp; /* Move pointer */
if (!rd_length)
return; /* 若虚拟硬盘内存长度为0则返回 */
printk("Ram disk: %d bytes, starting at 0x%x\n", rd_length,
(int) rd_start);
if (MAJOR(ROOT_DEV) != 2)
return; /* 若根文件系统主设备号不为软盘则返回 */
/* 读取软盘根文件系统的超级块到s中 */
bh = breada(ROOT_DEV,block+1,block,block+2,-1);
if (!bh) {
printk("Disk error while looking for ramdisk!\n");
return;
}
*((struct d_super_block *) &s) = *((struct d_super_block *) bh->b_data);
brelse(bh);
if (s.s_magic != SUPER_MAGIC)
/* No ram disk image present, assume normal floppy boot */
return; /* 文件系统非MINIX1.0则返回 */
/* 计算文件系统数据逻辑块扇区数,若大于虚拟硬盘内存长度则返回 */
nblocks = s.s_nzones << s.s_log_zone_size;
if (nblocks > (rd_length >> BLOCK_SIZE_BITS)) {
printk("Ram disk image too big! (%d blocks, %d avail)\n",
nblocks, rd_length >> BLOCK_SIZE_BITS);
return;
}
printk("Loading %d bytes into ram disk... 0000k",
nblocks << BLOCK_SIZE_BITS);
/* 将软盘上的文件系统拷贝到虚拟硬盘内存段中 */
cp = rd_start; /* 虚拟硬盘内存段首地址 */
while (nblocks) {
if (nblocks > 2) /* 剩余扇区大于2则以预读的方式读取软盘 */
bh = breada(ROOT_DEV, block, block+1, block+2, -1);
else /* 读软盘 */
bh = bread(ROOT_DEV, block);
if (!bh) {
printk("I/O error on block %d, aborting load\n",
block);
return;
}
/* 将缓冲区块中的软盘数据拷贝到虚拟硬盘内存段中 */
(void) memcpy(cp, bh->b_data, BLOCK_SIZE);
brelse(bh);
printk("\010\010\010\010\010%4dk",i);
cp += BLOCK_SIZE;
block++;
nblocks--;
i++;
}
printk("\010\010\010\010\010done \n");
ROOT_DEV=0x0101; /* 设置根文件系统设备为虚拟硬盘 */
}
/* 粗略从不同层次总结块设备请求管理程序,
* 以文件概念封装处的系统调用(如open,read,write)。
* |
* v
* 将文件相关参数转换为设备逻辑层面相关参数(如block_read,block_write)。
* |
* V
* 将对设备逻辑块号相关参数转换为
* "请求"相关参数的概念组织(如ll_rw_block,make_request,add_request),
* 以电梯升梯算法调度各请求,不同设备具有不同的请求函数。
* |
* V
* 块设备请求函数将向设备控制器下发(如do_*_request)实际的请求命令(读写命令,磁道扇
* 区号等),块设备收到命令就绪后不断向PIC输出中断让CPU执行相应的块设备请求中断函数
* do_hd读写块设备准备好的数据,直到完成当前设备请求。块设备(硬盘,软盘,虚拟硬盘)相
* 应请求函数调度一个请求后将调度自动调度下一请求。*/
ll_rw_blk.c
/*
* linux/kernel/blk_dev/ll_rw.c
*
* (C) 1991 Linus Torvalds
*/
/*
* This handles all read/write requests to block devices
*/
/* 本文件包含处理块设备读/写请求代码 */
#include <errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include "blk.h"
/*
* The request-struct contains all necessary data
* to load a nr of sectors into memory
*/
/* 管理块设备读写请求结构体类型的全局数组,struct request
* 结构体类型中包含了加载 nr 扇区到内存的必要成员。*/
struct request request[NR_REQUEST];
/*
* used to wait on when there are no free requests
*/
/* 用于进程等待空闲的 request 结构体元素 */
/* 在块设备请求数组request无空闲元素时,
* 用于等待直到request中出现空闲元素。*/
struct task_struct * wait_for_request = NULL;
/* blk_dev_struct is:
* do_request-address
* next-request
*/
/* 块设备(读写)请求结构体数组,
* 目前只将软盘(2)和硬盘(3)当做块设备管理。
* 他们分别在floppy.c和hd.c中被赋值。*/
struct blk_dev_struct blk_dev[NR_BLK_DEV] = {
{ NULL, NULL }, /* no_dev */
{ NULL, NULL }, /* dev mem */
{ NULL, NULL }, /* dev fd */
{ NULL, NULL }, /* dev hd */
{ NULL, NULL }, /* dev ttyx */
{ NULL, NULL }, /* dev tty */
{ NULL, NULL } /* dev lp */
};
/* lock_buffer,
* 为bh所指管理缓冲区块的节点置位锁状态,
* 间接为bh所管理的缓冲区块上锁。*/
static inline void lock_buffer(struct buffer_head * bh)
{
/* 原理同lock_super,除之前描述,再额外粗略理解一点吧^_^。
*
* 在为某共享内存上锁过程中禁止CPU处理中断是为了防止中断程序
* 与当前进程冲突访问bh指向节点的b_lock成员。一旦确保b_lock
* 成员的互斥访问后,只要在访问bh所指节点其他成员前"判断该节点
* 锁状态是否已置位,若置位则等待"的约定,就可以实现共享内存段的
* 互斥访问了。这是只有在写b_lock成员时才禁止CPU处理中断的原因。*/
cli();
while (bh->b_lock)
sleep_on(&bh->b_wait);
bh->b_lock=1;
sti();
}
/* unlock_buffer,
* 为bh所指管理缓冲区块的节点复位锁状态。*/
static inline void unlock_buffer(struct buffer_head * bh)
{
if (!bh->b_lock)
printk("ll_rw_block.c: buffer not locked\n\r");
/* 按照锁状态访问共享内存的约定,
* 在本进程获取到锁后其余进程或中断就不会写访问bh中的成员,
* 所以此处不用再禁止CPU处理本进程中断。*/
bh->b_lock = 0;
/* 唤醒调用sleep_on(&bh->b_wait)进入睡眠的进程们 */
wake_up(&bh->b_wait);
}
/*
* add-request adds a request to the linked list.
* It disables interrupts so that it can muck with the
* request-lists in peace.
*/
/* add_request,添加一个请求到请求链表中。
* 该函数失能中断是为了能正确地添加请求(避免中断程序的竞争)。*/
/* add_request,
* 以电梯升梯调度算法管理调度某个块设备的读写请求。
* 若设备当前无其他请求,则直接用当前设备读写请求函数(dev->request_fn)读写设备。
* 若设备已有其他请求,则以电梯升梯算法将当前读写请求加入到该设备已有请求的链表
* 中,以待调度执行。*/
static void add_request(struct blk_dev_struct * dev, struct request * req)
{
struct request * tmp;
req->next = NULL;
cli(); /* 禁止中断 */
/* 无中断+内核无抢占模式 将使得从此处到sti()之间的程序会一直执行 */
if (req->bh)
req->bh->b_dirt = 0;
if (!(tmp = dev->current_request)) {
/* 若设备无其他请求则将当前请求设置为req指向的请求,并调
* 用挂载在request_fn上的回调函数读写设备。该函数将会向
* 对应的设备下发读写命令,对应设备收到读写命令后,在准备
* 好被读或被写的相关状态时会就向CPU申请被读写的中断,从
* 而让CPU指向对应的中断函数以完成设备读写。*/
dev->current_request = req;
sti();
(dev->request_fn)();
return;
}
/* 若设备有其他请求,则按照电梯升梯算
* 法将req指向的请求加入到该设备的所
* 有请求中形成一个各楼层等待乘梯上楼
* 式的链表。*/
for ( ; tmp->next ; tmp=tmp->next)
/* 以IN_ORDER指定优先级判定,若新
* 加入的req请求的优先级比tmp请求
* 优先级低,则以优先级降序顺序寻找
* req指向请求的位置,即满足优先级
* tmp > req > tmp->next。
*
* 若新加入的req请求优先级高于或等于
* tmp请求,则req会加入到优先级都比tmp
* 请求高的链表序列中,在这个序列中仍
* 以IN_ORDER所指定优先级的降序方式将
* req插入其中,待tmp所在序列中的所有序
* 列都调度完毕后再调度比tmp优先级高的
* 序列。这就像电梯到了3楼(tmp),所有高
* 于3楼的要乘梯继续往上的都可以乘梯,所
* 有低于3楼的要乘梯往上的只有当电梯重新
* 回到有人往上的最低楼层后才又逐层往上走。
*
* 使用电梯调度算法是为了在调度各读写磁盘的
* 请求时,每次都尽可能少地移动磁盘(磁头等)。
* linux0.11只使用了电梯升梯调度算法。*/
if ((IN_ORDER(tmp,req) ||
!IN_ORDER(tmp,tmp->next)) &&
IN_ORDER(req,tmp->next))
break;
/* 将新请求插入到链表中的合适位置以待调度请求读写设备 */
req->next=tmp->next;
tmp->next=req;
sti();
}
/* make_request,
* 用描述各类请求的结构体数组元素request管理major对应块设备的读写请求。
* 即将bh中携带的块设备读写信息拷贝到request元素中,好专门管理。
*
* rw=读或写请求时,make_request会不惜睡眠等待可用的请求数组元素来记录
* 读写请求;rw=欲读或预写时,遇到需睡眠等待的情形则放弃本次请求。*/
static void make_request(int major,int rw, struct buffer_head * bh)
{
struct request * req;
int rw_ahead;
/* WRITEA/READA is special case - it is not really needed, so if the */
/* buffer is locked, we just forget about it, else it's a normal read */
/* 当参数rw为预写/预读标志时,若此时缓冲区锁状态置位,则放弃写/读,
* 否则按照正常的写/读操作来写/读对应设备。*/
if (rw_ahead = (rw == READA || rw == WRITEA)) {
if (bh->b_lock)
return;
if (rw == READA)
rw = READ;
else
rw = WRITE;
}
if (rw!=READ && rw!=WRITE)
panic("Bad block dev command, must be R/W/RA/WA");
/* 置位bh所指缓冲区块的锁状态;
* 写设备时,若缓冲区块中的数据还未准备好则复位缓冲区块锁状态并返回;
* 读设备时,若数据已读入缓冲区块中则复位缓冲区块锁状态并返回。*/
lock_buffer(bh);
if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) {
unlock_buffer(bh);
return;
}
repeat:
/* we don't allow the write-requests to fill up the queue completely:
* we want some room for reads: they take precedence. The last third
* of the requests are only for reads.
*/
/* 对于设备读写请求的设计是,读请求的优先级会更高一些,全局数组requests
* 最后三分之一专用于读请求,剩余三分之二用于读写请求。*/
if (rw == READ)
req = request+NR_REQUEST;
else
req = request+((NR_REQUEST*2)/3);
/* find an empty request */
while (--req >= request)
if (req->dev<0)
break;
/* if none found, sleep on new requests: check for rw_ahead */
/* 如果管理设备读写请求的数组中已无空闲元素时,若是预读则
* 直接返回,否则睡眠等待直到有某个元素被释放时重新遍历。*/
if (req < request) {
if (rw_ahead) {
unlock_buffer(bh);
return;
}
sleep_on(&wait_for_request);
goto repeat;
}
/* fill up the request-info, and add it to the queue */
/* 在遍历到请求全局数组元素后,从bh所指管理缓冲区块的节点
* 中将请求设备的详细信息拷贝到所遍历到的请求数组元素中,
* 以完成缓冲区块节点结构体向请求结构体类型的过渡。*/
req->dev = bh->b_dev;
req->cmd = rw;
req->errors=0;
req->sector = bh->b_blocknr<<1; /* 2扇区为一逻辑块 */
req->nr_sectors = 2; /* 读写两扇区即一个逻辑块 */
req->buffer = bh->b_data;
req->waiting = NULL;
req->bh = bh;
req->next = NULL;
add_request(major+blk_dev,req);
}
/* ll_rw_block,
* 请求读或写块设备的底层函数。
* rw参数为读(预读)或写(预写)设备标识,
* bh所指管理缓冲区块的节点中包含了欲读写设备的详细信
* 息,如设备分区号,欲读写逻辑块号,欲读扇区数等。参见
* struct buffer_head结构体类型。*/
void ll_rw_block(int rw, struct buffer_head * bh)
{
unsigned int major;
/* 从bh所指管理缓冲区块的节点中获取主设备号,
* 并判断该主设备号和其对应的读写设备函数是否存在。*/
if ((major=MAJOR(bh->b_dev)) >= NR_BLK_DEV ||
!(blk_dev[major].request_fn)) {
printk("Trying to read nonexistent block-device\n\r");
return;
}
/* 请求读写块设备,主设备号major用于映射其对应的读写函数 */
make_request(major,rw,bh);
}
/* blk_dev_init,
* 初始化管理块设备读写请求的全局数组。*/
void blk_dev_init(void)
{
int i;
for (i=0 ; i<NR_REQUEST ; i++) {
request[i].dev = -1;
request[i].next = NULL;
}
}
/* 粗略总结块设备请求管理。
* |--------------|
* | file request |
* |--------------|
* ∧
* | |------------------------------------|
* | |filesystem structure buffer(mapping)|
* | |------------------------------------|
* V
* |----------------------------|
* |block device request(manage)|
* |----------------------------|
* ∧
* | |------------------------|
* | |file data buffer(manage)|
* | |------------------------|
* V
* ..............................
* . logical filesystem mapping .
* ..............................
* ∧
* | I/O指令和中断机制
* V
* |----------|
* | controler|
* |——————————|
* | physical |
* | device |
* |——————————| */
hd.c
/*
* linux/kernel/hd.c
*
* (C) 1991 Linus Torvalds
*/
/*
* This is the low-level hd interrupt support. It traverses the
* request-list, using interrupts to jump between functions. As
* all the functions are called within interrupts, we may not
* sleep. Special care is recommended.
*
* modified by Drew Eckhardt to check nr of hd's from the CMOS.
*/
/* 此文包含了硬盘中断的底层代码。此文件函数接口将遍历请求列表,根据
* 中断类型调用相应函数。因为所有的函数都将在中断中调用,所以这些函
* 数中都不包含睡眠机制。在编写这些代码时,应考虑全面,特别谨慎。*/
#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 /* 硬盘主设备号 */
#include "blk.h"
/* 70h端口用于接收CMOS RAM的内存地址,
* 71h端口用于读写70h对应的内存单元。*/
#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})
/* Max read/write errors/sector */
#define MAX_ERRORS 7 /* 访问硬盘允许的最大次数 */
#define MAX_HD 2 /* 硬盘数 */
static void recal_intr(void);
/* 硬盘校正和复位标志 */
static int recalibrate = 1;
static int reset = 1;
/*
* This struct defines the HD's and their types.
*/
/* 各字段分别是磁头数、每磁道扇区数、柱面数(磁道数)、
* 写前预补偿柱面号、磁头着陆区柱面号、控制字节。*/
/* struct hd_i_struct,
* 硬盘参数结构体类型。*/
struct hd_i_struct {
/* 磁头数;每磁道扇区数;磁道数;
* 预补偿柱面号,..........................s*/
int head,sect,cyl,wpcom,lzone,ctl;
};
/* 硬盘参数结构体数组 */
#ifdef HD_TYPE /* 程序指定具体的硬盘参数 */
struct hd_i_struct hd_info[] = { HD_TYPE };
#define NR_HD ((sizeof (hd_info))/(sizeof (struct hd_i_struct)))
#else /* 使用BIOS获取到的硬盘参数 */
struct hd_i_struct hd_info[] = { {0,0,0,0,0,0},{0,0,0,0,0,0} };
static int NR_HD = 0;
#endif
/* struct hd_struct,
* 硬盘分区信息结构体类型及硬盘分区信息全局数组。*/
static struct hd_struct {
long start_sect; /* 分区起始扇区号 */
long nr_sects; /* 分区总扇区数 */
} hd[5*MAX_HD]={{0,0},};
/* hd[0..4]用于第一个硬盘,
* h[0]用于记录整个硬盘起始扇区和总扇区数,
* h[1..4]用于记录硬盘各个分区的起始扇区和总扇区数;
*
* hd[5..9]用于第二个硬盘,
* h[5]用于记录整个硬盘起始扇区和总扇区数,
* h[6..9]用于记录硬盘各个分区的起始扇区和总扇区数。*/
/* port_read(port, buf, nr),
* 从端口地址port处读取nr*2字节内容到buf内存段。
*
* 内联汇编输入。
* "d" (port), edx = port;
* "D" (buf), edi = buf;
* "c" (nr), ecx = nr;
*
* 内联汇编指令。
* cld;rep;insw 相当于
* while (ecx--)
* inw es:edi,edx
* edi += 2
* 即从端口地址port处读取ecx*2字节到buf内存段。*/
#define port_read(port,buf,nr) \
__asm__("cld;rep;insw"::"d" (port),"D" (buf),"c" (nr):"cx","di")
/* port_write(port,buf,nr),
* 将buf内存段中的nr*2字节内容写往端口port。
*
* 内联输入。
* "d" (port)-端口存于edx寄存器中。
* "S" (buf) - 将buf赋给esi寄存器。
* "c" (nr) - 将2字节数存入cdx寄存器中。
*
* cld;rep;outsw指令:
* while (ecx--)
* outw ds:si, edx
* si += 2 */
#define port_write(port,buf,nr) \
__asm__("cld;rep;outsw"::"d" (port),"S" (buf),"c" (nr):"cx","si")
extern void hd_interrupt(void);
extern void rd_load(void);
/* This may be used only once, enforced by 'static int callable' */
/* [1] sys_setup,
* 将硬盘参数信息保存到全局变量hd_info中,
* 硬盘参数信息来自BIOS所指内存段或程序中额外指定(HD_TYPE)。
*
* 通过硬盘参数信息读取硬盘分区表信息于hd数组中,
* 然后挂载根文件系统并加载虚拟硬盘。
*
* 根文件系统位于第二个硬盘的第一个分区中,分区设备号为0x306。*/
int sys_setup(void * BIOS)
{
static int callable = 1;
int i,drive;
unsigned char cmos_disks;
struct partition *p;
struct buffer_head * bh;
/* sys_setup只供调用一次即可 */
if (!callable)
return -1;
callable = 0;
/* 将硬盘参数信息保存到全局变量hd_info中 */
#ifndef 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;
}
/* bootsect.s曾检查第2个硬盘是否存在,
* 不存在时其参数信息会被清0.*/
if (hd_info[1].cyl)
NR_HD=2;
else
NR_HD=1;
#endif
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;
}
/*
We querry CMOS about hard disks : it could be that
we have a SCSI/ESDI/etc controller that is BIOS
compatable with ST-506, and thus showing up in our
BIOS table, but not register compatable, and therefore
not present in CMOS.
Furthurmore, we will assume that our ST-506 drives
<if any> are the primary drives in the system, and
the ones reflected as drive 1 or 2.
The first drive is stored in the high nibble of CMOS
byte 0x12, the second in the low nibble. This will be
either a 4 bit drive type or 0xf indicating use byte 0x19
for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS.
Needless to say, a non-zero value means we have
an AT controller hard disk for that drive.
*/
/* 从CMOS中获取硬盘驱动器类型的信息。
* CMOS RAM 0x12地址处的bit[7..4]不为0表含驱动器0,
* CMOS RAM 0x12地址处的bit[3..0]不为0表含驱动器1。*/
if ((cmos_disks = CMOS_READ(0x12)) & 0xf0)
if (cmos_disks & 0x0f)
NR_HD = 2; /* 在定义了HD_TYPE的情况下有编译错误吧 */
else
NR_HD = 1;
else
NR_HD = 0;
/* 将驱动器不支持的硬盘的总信息清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++) {
if (!(bh = bread(0x300 + drive*5,0))) {
printk("Unable to read partition table of drive %d\n\r",
drive);
panic("");
}
if (bh->b_data[510] != 0x55 || (unsigned char)
bh->b_data[511] != 0xAA) {
printk("Bad partition table on drive %d\n\r",drive);
panic("");
}
/* 硬盘分区信息开始于引导区的0x1BE偏移处,
* 将硬盘各个分区信息依次读取到hd中。*/
p = 0x1BE + (void *)bh->b_data;
for (i=1;i<5;i++,p++) {
hd[i+5*drive].start_sect = p->start_sect;
hd[i+5*drive].nr_sects = p->nr_sects;
}
brelse(bh);
}
if (NR_HD)
printk("Partition table%s ok.\n\r",(NR_HD>1)?"s":"");
/* 加载软盘文件系统到虚拟硬盘内存中; 挂载根文件系统。*/
rd_load();
mount_root();
return (0);
}
/* controller_redy,
* 等待硬盘驱动器就绪。
* 若硬盘驱动器就绪则返回非0值,否则返回0.*/
static int controller_ready(void)
{
int retries=10000;
/* 最多读10000次硬盘状态寄存器,若驱动器就绪则立即退出循环 */
while (--retries && (inb_p(HD_STATUS)&0xc0)!=0x40);
return (retries);
}
/* win_result,
* 读硬盘主状态寄存器,看其是否处于就绪或寻道结束状态,
* 若是则返回0,否则读取硬盘错误码后返回1。*/
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); /* ok */
if (i&1) i=inb(HD_ERROR);
return (1);
}
/* [4] hd_out,
* 给drive对应硬盘下发读写命令并挂硬盘读写回调函数。
*
* drive=0,硬盘1;drive=1,硬盘2。
* nsect,欲读写扇区数;sect,读写的起始扇区;head,读写的起始磁头;
* cyl,读写的起始柱面;cmd,读写命令;intr_addr,硬盘读写中断处理函数。*/
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");
/* 硬盘只有2个(0,1);硬盘的柱面数为15;
* 检查硬盘控制器是否就绪。*/
if (drive>1 || head>15)
panic("Trying to write bad sector");
if (!controller_ready())
panic("HD controller not ready");
/* 将硬盘读或写中断的C处理函数赋给do_hd,
* 疑惑:在linux0.11中并没有看到do_hd的定义。*/
do_hd = intr_addr;
/* AT硬盘控制器命令编程方法。
* |------------------------------------------
* |0|1f7h|读检测控制器空闲状态controller_ready|
* |------------------------------------------
* |1|3f6h|写硬盘控制寄存器********************|
* |------------------------------------------|
* |2|1f1h|写预补偿起始柱面号*******************|
* |------------------------------------------|
* |3|1f2h|写扇区数量**************************|
* |------------------------------------------|
* |4|1f3h|写(起始)扇区号**********************|
* |------------------------------------------|
* |5|1f4h|写柱面号低8位***********************|
* |------------------------------------------|
* |6|1f5h|写柱面号高2位***********************|
* |------------------------------------------|
* |7|1f6h|写驱动号 磁头号*********************|
* |------------------------------------------|
* |8|1f7h|写HDC命令码*************************|
* |------------------------------------------|
* 写硬盘控制器和写预补偿起始柱面号用硬盘
* 参数信息中对应字段即可。page420. */
* outb_p(hd_info[drive].ctl,HD_CMD);
port=HD_DATA; /* 0x1f0 */
outb_p(hd_info[drive].wpcom>>2,++port);
outb_p(nsect,++port);
outb_p(sect,++port);
outb_p(cyl,++port);
outb_p(cyl>>8,++port);
outb_p(0xA0|(drive<<4)|head,++port);
outb(cmd,++port); /* 如cmd=30h,写 */
}
/* drive_busy,
* 多次获取硬盘主状态控制器的状态,
* 若在规定次数中主状态控制器状态
* 就绪则返回0,否则返回1表硬盘驱动器忙。*/
static int drive_busy(void)
{
unsigned int i;
/* 见读0x1f7时硬盘主状态控制器位(hdreg.h中) */
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);
}
/* reset_controller,
* 复位硬盘驱动器。*/
static void reset_controller(void)
{
int i;
/* 置位硬盘复位并稍作等待 */
outb(4,HD_CMD);
for(i = 0; i < 100; i++) nop();
/* 将硬盘1参数控制字节写入0x3f6指定硬盘运作模式 */
outb(hd_info[0].ctl & 0x0f ,HD_CMD);
/* 若硬盘忙或复位硬盘失败则提示 */
if (drive_busy())
printk("HD-controller still busy\n\r");
if ((i = inb(HD_ERROR)) != 1)
printk("HD-controller reset failed: %02x\n\r",i);
}
/* reset_hd,
* 以指定硬盘参数表设置硬盘。*/
static void reset_hd(int nr)
{
/* 复位硬盘驱动器,下发以硬盘参数表信息重新建立控制器参数HDC命令 */
reset_controller();
hd_out(nr,hd_info[nr].sect,hd_info[nr].sect,hd_info[nr].head-1,
hd_info[nr].cyl,WIN_SPECIFY,&recal_intr);
}
void unexpected_hd_interrupt(void)
{
printk("Unexpected HD interrupt\n\r");
}
/* bad_rw_intr,
* 访问硬盘设备失败处理。
* 当访问失败次数超最大允许次数时,则放弃当前请求并置位复位硬盘标志;
* 若访问超过所允许最大次数一半时,则置位复位硬盘标志。*/
static void bad_rw_intr(void)
{
if (++CURRENT->errors >= MAX_ERRORS)
end_request(0);
if (CURRENT->errors > MAX_ERRORS/2)
reset = 1;
}
/* read_intr,
* 读硬盘中断C处理函数。
* 当前读块设备请求完成时调度下一请求,
* 各请求以电梯升梯顺序形成链表,见add_request。
*
* 读硬盘请求->向硬盘下发读硬盘命令并设置do_hd指向写硬盘中断处理read_intr;
* 硬盘可读时输出IRQ14中断从而让CPU执行IDT[0x2e]中的处理函数_hd_interrupt,
* _hd_interrupt->do_hd即调用此处的read_intr。*/
static void read_intr(void)
{
/* 检查硬盘状态,若硬盘不正常则记录失败次数
* 并进行相应设置,然后继续调度硬盘访问请求。*/
if (win_result()) {
bad_rw_intr();
do_hd_request();
return;
}
/* 从硬盘数据寄存器中读取一扇区内容到buffer中,
* 读成功后复位读设备失败次数,存储硬盘数据缓冲
* 区后移一扇区大小,更新当前所在扇区以及未读扇
* 区数。当未读扇区数不为0时,中断请求函数仍设置
* 为硬盘中断读函数并返回。*/
port_read(HD_DATA,CURRENT->buffer,256);
CURRENT->errors = 0;
CURRENT->buffer += 512;
CURRENT->sector++;
if (--CURRENT->nr_sectors) {
do_hd = &read_intr;
return;
}
/* 执行到这里时,当前请求未读扇区已为0,
* 所以正常结束当前请求,并设置当前块设备
* 请求指向下一请求并调用do_hd_request调
* 度块设备下一请求。*/
end_request(1);
do_hd_request();
}
/* write_intr,
* 写硬盘中断C处理函数。
* 当前写块设备请求完成时调度下一请求,
* 各请求以电梯升梯顺序形成链表,见add_request。
*
* 写硬盘请求->向硬盘下发写硬盘命令并设置do_hd指向写硬盘中断处理write_intr;
* 硬盘可写时输出IRQ14中断从而让CPU执行IDT[0x2e]中的处理函数_hd_interrupt,
* _hd_interrupt->do_hd即调用此处的write_intr完成一次写操作。*/
static void write_intr(void)
{
/* 检查硬盘状态,若硬盘不正常则记录失败次数
* 并进行相应设置,然后继续调度硬盘访问请求。*/
if (win_result()) {
bad_rw_intr();
do_hd_request();
return;
}
/* 因为在do_hd_request中已写过一扇区,所以此处
* 先将未写扇区数减1后再判断是否为0。若不为0则
* 更新已读硬盘当前所在扇区号,更新缓冲区块位置,
* 继续设置do_hd为写中断处理函数write_intr,并再
* 写块设备一扇区内容并返回。*/
if (--CURRENT->nr_sectors) {
CURRENT->sector++;
CURRENT->buffer += 512;
do_hd = &write_intr;
port_write(HD_DATA,CURRENT->buffer,256);
return;
}
/* 执行到此处表明当前写硬盘请求已完成,
* 所以正常结束当前写块设备请求并调整当
* 前硬盘请求指向下一请求;然后调用do_hd_request继续调度。*/
end_request(1);
do_hd_request();
}
/* recal_intr,
* 判断硬盘状态并记录硬盘状态非
* 就绪次数,若没有超过设定值则重
* 新调度当前请求,若超过设定次数
* 则复位硬盘或放弃当前请求继续调
* 度硬盘下一请求。*/
static void recal_intr(void)
{
/* 判断硬盘状态,若状态不可访问
* 则进入bad_rw_intr中记录,若记
* 录次数超过指定数bad_rw_intr会
* 调用end_request(0)放弃当前请求
* 而设置当前请求指向下一请求或置
* 位硬盘复位标志。*/
if (win_result())
bad_rw_intr();
/* 调度行为取决于bad_rw_intr中的设
* 置,若bad_rw_intr置位硬盘复位标志,
* 则do_hd_request将执行硬盘复位函数等,
* 否则调度当前请求继续请求硬盘。*/
do_hd_request();
}
/* [3] do_hd_request,
* 硬盘读写请求函数,向硬盘下发读写命令及相关信息。
* 硬盘成功收到该命令并准备好后就会向PIC输出中断
* 信息,从而让CPU执行IDT[2eh]中的硬盘中断处理函数
* _hd_interrupt,该函数会调用通过hd_out挂载在do_hd
* 上的硬盘中断C处理函数,如read_intr, write_intr,
* recal_intr。_hd_interrupt定义在kernel/system_call.s中。*/
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;
}
/* 获取基于整个硬盘的分区号,
* dev/5计算得到硬盘0还是硬盘1。*/
block += hd[dev].start_sect;
dev /= 5;
/* 计算逻辑扇区号block对应的磁道和在该磁道上的扇区号,
* 计算结果为block=磁道号,sect=在block磁道上的扇区号。
*
* 计算磁道号block对应的柱面号和磁头号,
* 计算结果为cyl=柱面号,head=磁头号。*/
__asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0),
"r" (hd_info[dev].sect));
__asm__("divl %4":"=a" (cyl),"=d" (head):"0" (block),"1" (0),
"r" (hd_info[dev].head));
sec++; /* 扇区号从1开始 */
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) {
/* 则向硬盘下发写命令块并传入写硬盘中断的C处理回调函数write_intr */
hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr);
/* 检查向硬盘下发的写命令是否成功,若失败则回到INIT_REQUEST中repeat处 */
for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++)
/* nothing */ ;
if (!r) {
bad_rw_intr();
goto repeat;
}
/* 若向硬盘下发的写命令成功,则先写入一扇区数据 */
port_write(HD_DATA,CURRENT->buffer,256);
/* 若当前请求是请求读设备, */
} else if (CURRENT->cmd == READ) {
/* 则向硬盘下发读命令块并传入读硬盘中断的C处理函数read_intr */
hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr);
} else
panic("unknown hd-command");
}
/* [2] hd_init,
* 设置硬盘的读写请求函数,
* 设置硬盘中断处理函数,使能硬盘中断。*/
void hd_init(void)
{
/* MAJOR_NR, 硬盘主设备号为3,
* 为块设备硬盘挂载(读写)请求函数DEVICE_REQUEST。
* 在块设备硬盘中,读写硬盘的请求函数DEVICE_REQUEST
* 为do_hd_request函数。*/
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
/* 在IDT[0x2E]中设定硬盘中断处理函数,
* 8259A-2 IRQ7对应硬盘中断,IRQ7中断
* 号在setup.s中被设置为0x2e。*/
set_intr_gate(0x2E,&hd_interrupt);
/* 设置主片8259A-1接收从片8259A-2中断,
* 设置使能从片8259A-2 IRQ7即硬盘中断。*/
outb_p(inb_p(0x21)&0xfb,0x21);
outb(inb_p(0xA1)&0xbf,0xA1);
}
/* 粗略总结读写硬盘请求的大体流程,
* 文件内容转换到硬盘逻辑块,
* 硬盘逻辑块内容转换为硬盘设备请求,
* 根据硬盘设备请求向硬盘发起请求命令,
* 硬盘收到命令并就绪后向PIC输出中断,
* CPU执行硬盘中断处理函数完成硬盘访问请求并调度硬盘设备下一请求。
*
* 以函数描述这个过程的大体流程
* 文件操作系统调用(如open,read,write,sys_read,sys_write),
* 文件内容到硬盘逻辑块的转换(如block_read,breada),
* 硬盘逻辑块访问转换为一个硬盘请求(make_request,add_request),
* 调度硬盘访问请求向硬盘发起访问请求并设置硬盘中断函数
* (如do_hd_request,hd_out,do_hd),
* 硬盘收到访问命令并就绪后则发起硬盘访问中断从而让CPU执行硬盘
* 中断函数do_hd以完成硬盘访问。
*
* read -> sys_read -> block_read -> make_request -> add_request
* -> do_hd_request -> hd_out... -> do_hd(do_hd_request) */
hdreg.h
/*
* This file contains some defines for the AT-hd-controller.
* Various sources. Check out some definitions (see comments with
* a ques).
*/
#ifndef _HDREG_H
#define _HDREG_H
/* Hd controller regs. Ref: IBM AT Bios-listing */
/* 硬盘端口地址0x1f0-0x1f7,不同访问操作呈现不同功能,
* 此处宏定义用作之后注释作用。*/
#define HD_DATA 0x1f0 /* 硬盘数据寄存器(扇区读/写) */
#define HD_ERROR 0x1f1 /* see err-bits */
#define HD_NSECTOR 0x1f2 /* nr of sectors to read/write */
#define HD_SECTOR 0x1f3 /* starting sector */
#define HD_LCYL 0x1f4 /* starting cylinder */
#define HD_HCYL 0x1f5 /* high byte of starting cyl */
#define HD_CURRENT 0x1f6 /* 101dhhhh , d=drive, hhhh=head */
#define HD_STATUS 0x1f7 /* see status-bits */
#define HD_PRECOMP HD_ERROR /* same io address, read=error, write=precomp */
#define HD_COMMAND HD_STATUS /* same io address, read=status, write=cmd */
/* 写端口地址0x3f6-写硬盘控制寄存器位,其各位含义。
* |7 0
* ---------------------------------
* |*1*|**1*|**|**|**1**|**1*|**|**|
* ---------------------------------
* |禁止|禁止|未|未|多磁头|复位|未|未|
* |重试|重读|用|用| 选择 |允许|用|用|
* --------------------------------- */
#define HD_CMD 0x3f6
/* Bits of HD_STATUS */
/* 读端口地址0x1f7-读硬盘主状态控制器,其各位含义。
* ------------------------------------------------
* |控制器|驱动器|驱动器|寻道|请求| ECC |收到| 命令 |
* | 忙碌 | 就绪 | 故障 |结束|服务|检验错|索引|执行错|
* | =1 | =1 | =1 | =1 | =1 | =1 | =1 | =1 |
* ------------------------------------------------ */
#define ERR_STAT 0x01
#define INDEX_STAT 0x02
#define ECC_STAT 0x04 /* Corrected error */
#define DRQ_STAT 0x08
#define SEEK_STAT 0x10
#define WRERR_STAT 0x20
#define READY_STAT 0x40
#define BUSY_STAT 0x80
/* Values for HD_COMMAND */
#define WIN_RESTORE 0x10 /* 驱动器重新校准 */
#define WIN_READ 0x20 /* 扇区读 */
#define WIN_WRITE 0x30 /* 扇区写 */
#define WIN_VERIFY 0x40 /* 扇区检验 */
#define WIN_FORMAT 0x50 /* 格式化磁道 */
#define WIN_INIT 0x60 /* ?? */
#define WIN_SEEK 0x70 /* 寻道 */
#define WIN_DIAGNOSE 0x90 /* 控制器诊断 */
#define WIN_SPECIFY 0x91 /* 建立驱动器参数 */
/* Bits for HD_ERROR */
/* 读0x1f1-读错误寄存器,其各位含义。
*
* 诊断模式下
* 01h无错误;02h控制器错;03h扇区缓冲器错;04hECC部件错;05h控制处理器错;
*
* 操作模式下
* 01h数据标志丢失;02h磁道0错;04h命令放弃;10hID未找到;40hECC错误;80h坏扇区。*/
#define MARK_ERR 0x01 /* Bad address mark ? */
#define TRK0_ERR 0x02 /* couldn't find track 0 */
#define ABRT_ERR 0x04 /* ? */
#define ID_ERR 0x10 /* ? */
#define ECC_ERR 0x40 /* ? */
#define BBD_ERR 0x80 /* ? */
/* struct partition,
* 硬盘分区表结构体类型,
* 用于描述硬盘引导区中偏移[0x1BE, 0x1FD]的分区表信息。*/
struct partition {
unsigned char boot_ind; /* 引导标志,0x80-从该分区开始引导 */
unsigned char head; /* 分区起始磁头号 */
unsigned char sector; /* 分区起始扇区号(bit[5..0]),bit[7..6]为柱面号高2位 */
unsigned char cyl; /* 分区起始柱面号低8位 */
unsigned char sys_ind; /* 分区类型字节;0x80-MINIX */
unsigned char end_head; /* 分区结束磁头号 */
unsigned char end_sector; /* 分区扇区结束号(bit[5..0]),bit[7..0]结束柱面号高2位 */
unsigned char end_cyl; /* 结束柱面号低8位 */
unsigned int start_sect; /* 基于分区的起始扇区号,从0开始 */
unsigned int nr_sects; /* 分区占用扇区数 */
};
#endif
floppy.c
/*
* linux/kernel/floppy.c
*
* (C) 1991 Linus Torvalds
*/
/*
* 02.12.91 - Changed to static variables to indicate need for reset
* and recalibrate. This makes some things easier (output_byte reset
* checking etc), and means less interrupt jumping in case of errors,
* so the code is hopefully easier to understand.
*/
/* 增加静态变量用于记录重置和校准。使用静态变量可以让编码稍简单一些,
* 能减少错误发生时的跳转,能让代码的阅读性好一点。*/
/*
* This file is certainly a mess. I've tried my best to get it working,
* but I don't like programming floppies, and I have only one anyway.
* Urgel. I should check for more errors, and do more graceful error
* recovery. Seems there are problems with several drives. I've tried to
* correct them. No promises.
*/
/* 本文件有些乱。此文已经尽最大努力让其正确运行了,此文承认不太喜欢编写软盘
* 模块的程序,但此文别无选择,额......此文应该再增加些对错误的检查并做相应
* 错误修复的功能。在多驱动器下,似乎还有些问题,此文已经尽力修复了他们,但并
* 不保证没有问题。*/
/*
* As with hd.c, all routines within this file can (and will) be called
* by interrupts, so extreme caution is needed. A hardware interrupt
* handler may not sleep, or a kernel panic will happen. Thus I cannot
* call "floppy-on" directly, but have to set a special timer interrupt
* etc.
*
* Also, I'm not certain this works on more than 1 floppy. Bugs may
* abund.
*/
/* 同hd.c,本文件中的程序也需能被中断处理程序调用,所以在编写此文件中函数时
* 需额外谨慎。如中断处理程序可能不适合睡眠,内核可能会调用 painc 。所以此
* 文不能直接编写 "floppy-on* 似的函数,而需要为软盘设置一个定时器及回调函
* 数。另外,在超过1个软盘时,此文不确保是否还能正常工作,可能会有潜在bug。*/
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/fdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>
#define MAJOR_NR 2 /* 软盘主设备号 */
#include "blk.h"
static int recalibrate = 0;
static int reset = 0;
static int seek = 0;
extern unsigned char current_DOR;
/* immoutb_p(val, port),
* 将val低字节内容写往端口port,并用jmp指令适当延时。*/
#define immoutb_p(val,port) \
__asm__("outb %0,%1\n\tjmp 1f\n1:\tjmp 1f\n1:"::"a" ((char) (val)),"i" (port))
#define TYPE(x) ((x)>>2)
#define DRIVE(x) ((x)&0x03)
/*
* Note that MAX_ERRORS=8 doesn't imply that we retry every bad read
* max 8 times - some types of errors increase the errorcount by 2,
* so we might actually retry only 5-6 times before giving up.
*/
/* 注,MAX_ERRORS=8并不意味着所有操作在出错时都会操作8次,因为有些操作
* 出错时错误计数步长为2,所以在放弃操作前可能会重试5-6次。*/
#define MAX_ERRORS 8
/*
* globals used by 'result()'
*/
#define MAX_REPLIES 7
static unsigned char reply_buffer[MAX_REPLIES];
#define ST0 (reply_buffer[0])
#define ST1 (reply_buffer[1])
#define ST2 (reply_buffer[2])
#define ST3 (reply_buffer[3])
/*
* This struct defines the different floppy types. Unlike minix
* linux doesn't have a "search for right type"-type, as the code
* for that is convoluted and weird. I've got enough problems with
* this driver as it is.
*
* The 'stretch' tells if the tracks need to be boubled for some
* types (ie 360kB diskette in 1.2MB drive etc). Others should
* be self-explanatory.
*/
/* struct floppy_struct,
* 软盘信息结构体类型。*/
static struct floppy_struct {
/* size,扇区总数;sect,每磁道扇区数;head,磁头数;
* track,磁道数;stretch,磁道特殊处理标志;
* gap,扇区间隙长度;rate,数据传输速率;
*/
unsigned int size, sect, head, track, stretch;
unsigned char gap,rate,spec1;
} floppy_type[] = {
{ 0, 0,0, 0,0,0x00,0x00,0x00 }, /* no testing */
{ 720, 9,2,40,0,0x2A,0x02,0xDF }, /* 360kB PC diskettes */
{ 2400,15,2,80,0,0x1B,0x00,0xDF }, /* 1.2 MB AT-diskettes */
{ 720, 9,2,40,1,0x2A,0x02,0xDF }, /* 360kB in 720kB drive */
{ 1440, 9,2,80,0,0x2A,0x02,0xDF }, /* 3.5" 720kB diskette */
{ 720, 9,2,40,1,0x23,0x01,0xDF }, /* 360kB in 1.2MB drive */
{ 1440, 9,2,80,0,0x23,0x01,0xDF }, /* 720kB in 1.2MB drive */
{ 2880,18,2,80,0,0x1B,0x00,0xCF }, /* 1.44MB diskette */
};
/*
* Rate is 0 for 500kb/s, 2 for 300kbps, 1 for 250kbps
* Spec1 is 0xSH, where S is stepping rate (F=1ms, E=2ms, D=3ms etc),
* H is head unload time (1=16ms, 2=32ms, etc)
*
* Spec2 is (HLD<<1 | ND), where HLD is head load time (1=2ms, 2=4 ms etc)
* and ND is set means no DMA. Hardcoded to 6 (HLD=6ms, use DMA).
*/
extern void floppy_interrupt(void);
extern char tmp_floppy_area[1024];
/*
* These are global variables, as that's the easiest way to give
* information to interrupts. They are the data used for the current
* request.
*/
/* 软盘请求相关参数 */
static int cur_spec1 = -1; /* 软盘参数 */
static int cur_rate = -1; /* 软盘转速 */
static struct floppy_struct * floppy = floppy_type; /* 软盘类型数组地址 */
static unsigned char current_drive = 0; /* 当前软盘驱动号 */
static unsigned char sector = 0; /* 当前扇区号 */
static unsigned char head = 0; /* 当前磁头号 */
static unsigned char track = 0; /* 当前磁道号 */
static unsigned char seek_track = 0; /* 寻道磁道号 */
static unsigned char current_track = 255; /* 当前磁头所在磁道号 */
static unsigned char command = 0; /* 当前访问软盘的操作命令 */
unsigned char selected = 0; /* 软驱是否已选择的标志 */
struct task_struct * wait_on_floppy_select = NULL; /* 用于进程等待某软驱的任务指针 */
/* floppy_deselect,
* 复位软驱已选择标志,唤醒等在当前软驱上的进程。*/
void floppy_deselect(unsigned int nr)
{
if (nr != (current_DOR & 3))
printk("floppy_deselect: drive not selected\n\r");
selected = 0;
wake_up(&wait_on_floppy_select);
}
/*
* floppy-change is never called from an interrupt, so we can relax a bit
* here, sleep etc. Note that floppy-on tries to set current_DOR to point
* to the desired drive, but it will probably not survive the sleep if
* several floppies are used at the same time: thus the loop.
*/
/* floppy_change,
* 读取软盘控制器数字输入寄存器,判断该软
* 驱动器下是否更换过软盘,若更换过则返回
* 1,否则返回0。*/
int floppy_change(unsigned int nr)
{
repeat:
floppy_on(nr);/* 等待nr对应软驱下软盘转速正常 */
/* 等待当前软驱为nr对应的软驱 */
while ((current_DOR & 3) != nr && selected)
interruptible_sleep_on(&wait_on_floppy_select);
if ((current_DOR & 3) != nr)
goto repeat;
/* 读输入寄存器,bit[7]=1表示nr软驱下的软盘已更换 */
if (inb(FD_DIR) & 0x80) {
floppy_off(nr);
return 1;
}
floppy_off(nr);
return 0;
}
/* copy_buffer(from, to),
* 从from内存段拷贝1024字节内容到to所指内存段。
*
* 内联汇编输入。
* "c" (BLOCK_SIZE/4), ecx = BLOCK_SIZE / 4;
* "S" ((long)(from)), ESI = form;
* "D" ((long)(to)), EDI = to;
*
* 内联汇编指令。
* cld; rep; movsl 相当于
* while (ecx--)
* movl ds:esi, es:edi
* esi += 4
* edi += 4
* 即从from内存段拷贝BLOCK_SIZE字节内容到to内存段。*/
#define copy_buffer(from,to) \
__asm__("cld ; rep ; movsl" \
::"c" (BLOCK_SIZE/4),"S" ((long)(from)),"D" ((long)(to)) \
:"cx","di","si")
/* setup_DMA,
* 设置DMA芯片用于软驱传输数据的通道2,
* 以使得软盘数据的读写以DMA方式进行。*/
static void setup_DMA(void)
{
/* 当前请求中用于存储数据的缓冲区首地址 */
long addr = (long) CURRENT->buffer;
/* 因为DMA芯片8237A只能寻址1Mb以内内存地址空间,
* 所以当addr地址超过1Mb时则使用软盘临时缓冲区
* 承载访问软盘中的数据。*/
cli();
if (addr >= 0x100000) {
addr = (long) tmp_floppy_area;
if (command == FD_WRITE)
copy_buffer(CURRENT->buffer,tmp_floppy_area);
}
/* mask DMA 2 */
/* DMA单通道屏蔽端口地址为0x0A,
* bit[1..0]用于指定通道x,bit[2]=1则屏蔽通道x。*/
immoutb_p(4|2,10);
/* output command byte. I don't know why, but everyone (minix, */
/* sanches & canton) output this twice, first to 12 then to 11 */
/* 根据当前命令command向DMA端口0x0B和0x0C写方式字(读或写) */
__asm__("outb %%al,$12\n\tjmp 1f\n1:\tjmp 1f\n1:\t"
"outb %%al,$11\n\tjmp 1f\n1:\tjmp 1f\n1:"::
"a" ((char) ((command == FD_READ)?DMA_READ:DMA_WRITE)));
/* 向DMA端口地址0x04中写入CPU侧访问数据内存首地址addr */
/* 8 low bits of addr */
immoutb_p(addr,4);
addr >>= 8;
/* bits 8-15 of addr */
immoutb_p(addr,4);
addr >>= 8;
/* bits 16-19 of addr */
immoutb_p(addr,0x81);
/* 向DMA端口地址0x05写入需传输的字节数 */
/* low 8 bits of count-1 (1024-1=0x3ff) */
immoutb_p(0xff,5);
/* high 8 bits of count-1 */
immoutb_p(3,5);
/* activate DMA 2 */
/* 使能DMA通道2 */
immoutb_p(0|2,10);
sti();
}
/* output_byte,
* 向软盘输出指定指定字节byte。*/
static void output_byte(char byte)
{
int counter;
unsigned char status;
if (reset)
return;
/* 获取软盘主状态控制器0x3f4状态,若已就绪且方向位置位(CPU->FDC)则
* 向数据端口输出指定字节数据byte。*/
for(counter = 0 ; counter < 10000 ; counter++) {
status = inb_p(FD_STATUS) & (STATUS_READY | STATUS_DIR);
if (status == STATUS_READY) {
outb(byte,FD_DATA);
return;
}
}
/* 若10000次后还未写成功则置位软盘复位标志 */
reset = 1;
printk("Unable to send byte to FDC\n\r");
}
/* result,
* 读取FDC执行结果于全局数组reply_buffer中。*/
static int result(void)
{
int i = 0, counter, status;
if (reset)
return -1;
for (counter = 0 ; counter < 10000 ; counter++) {
status = inb_p(FD_STATUS)&(STATUS_DIR|STATUS_READY|STATUS_BUSY);
if (status == STATUS_READY)
return i; /* 若控制器状态为READY则表示数据已读完 */
/* 若控制器状态方向标志置位(CPU<-FDC),已准备好,忙则表示有数据 */
if (status == (STATUS_DIR|STATUS_READY|STATUS_BUSY)) {
if (i >= MAX_REPLIES)
break;
reply_buffer[i++] = inb_p(FD_DATA);
}
}
/* 若10000次还未获取完成则表超时,则置位复位软盘 */
reset = 1;
printk("Getstatus times out\n\r");
return -1;
}
/* bad_flp_intr,
* 记录软盘访问失败次数,并根据失败次数做一些处理,
* 若访问次数超过最大允许次数则放弃当前请求,若当前
* 访问失败次数超过最大失败次数的一半则置软盘复位标
* 志。若访问失败还未超过最大失败数一半则置软盘重置
* 标志。*/
static void bad_flp_intr(void)
{
CURRENT->errors++;
if (CURRENT->errors > MAX_ERRORS) {
floppy_deselect(current_drive);
end_request(0);
}
if (CURRENT->errors > MAX_ERRORS/2)
reset = 1;
else
recalibrate = 1;
}
/*
* Ok, this interrupt is called after a DMA read/write has succeeded,
* so we check the results, and copy any buffers.
*/
/* rw_interrupt,
* 用于检查DMA读写请求结果并拷贝所读数据到缓冲区块中。*/
static void rw_interrupt(void)
{
/* 获取FDC并检查相关位状态,若出错则做些处理后重新请求 */
if (result() != 7 || (ST0 & 0xf8) || (ST1 & 0xbf) || (ST2 & 0x73)) {
if (ST1 & 0x02) {
printk("Drive %d is write protected\n\r",current_drive);
floppy_deselect(current_drive);
end_request(0);
} else
bad_flp_intr();
do_fd_request();
return;
}
/* 若当前读软盘且原缓冲区块在1Mb以外则拷贝软盘临时缓冲区块的数据到目的
* 缓冲区块中并结束本次软盘请求,并继续调度下一个软盘请求。*/
if (command == FD_READ && (unsigned long)(CURRENT->buffer) >= 0x100000)
copy_buffer(tmp_floppy_area,CURRENT->buffer);
floppy_deselect(current_drive);
end_request(1);
do_fd_request();
}
/* setup_rw_floppy,
* 设置DMA通道2用于传输软盘数据,并向软盘控制器
* 下发读写相关命令和参数。*/
inline void setup_rw_floppy(void)
{
/* 设置DMA 2通道用于当前软盘传输数据 */
setup_DMA();
/* 设置完成DMA数据传输后的中断函数指针 */
do_floppy = rw_interrupt;
output_byte(command); /* 向软盘发送访问的命令字节 */
output_byte(head<<2 | current_drive); /* 磁头和驱动器号 */
output_byte(track); /* 磁道号 */
output_byte(head); /* 磁头号 */
output_byte(sector); /* 起始扇区号 */
output_byte(2); /* sector size = 512 */
output_byte(floppy->sect); /* 每磁道扇区数 */
output_byte(floppy->gap); /* 扇区间隔长度 */
output_byte(0xFF); /* sector size (0xff when n!=0 ?) */
if (reset) /* 若以上设置有出错则调用do_fd_request复位软盘 */
do_fd_request();
}
/*
* This is the routine called after every seek (or recalibrate) interrupt
* from the floppy controller. Note that the "unexpected interrupt" routine
* also does a recalibrate, but doesn't come here.
*/
/* seek_interrupt,
* 在软盘校正后调用该函数检查软盘校正执行结果。*/
static void seek_interrupt(void)
{
/* sense drive status */
/* 获取寻道操作执行结果,若发生错误则记录访问软盘出错次数 */
output_byte(FD_SENSEI);
if (result() != 2 || (ST0 & 0xF8) != 0x20 || ST1 != seek_track) {
bad_flp_intr();
do_fd_request();
return;
}
/* 若寻道操作成功则继续当前软盘访问请求,
* 向软盘发送访问命令和参数。*/
current_track = ST1;
setup_rw_floppy();
}
/*
* This routine is called when everything should be correctly set up
* for the transfer (ie floppy motor is on and the correct floppy is
* selected).
*/
/* transfer,
* 处理软盘数据传输。*/
static void transfer(void)
{
/* 检查当前软驱是否为指定驱动器参数,若
* 不是则设置当前软驱参数。*/
if (cur_spec1 != floppy->spec1) {
cur_spec1 = floppy->spec1;
output_byte(FD_SPECIFY);
output_byte(cur_spec1); /* hut etc */
output_byte(6); /* Head load time =6ms, DMA */
}
if (cur_rate != floppy->rate)
outb_p(cur_rate = floppy->rate,FD_DCR);
if (reset) {
do_fd_request();
return;
}
/* 不需要寻道则设置DMA并下发对应命令和参数 */
if (!seek) {
setup_rw_floppy();
return;
}
/* 执行寻道处理,下发相关的寻道函数 */
do_floppy = seek_interrupt;
if (seek_track) {
output_byte(FD_SEEK);
output_byte(head<<2 | current_drive);
output_byte(seek_track);
} else {
output_byte(FD_RECALIBRATE);
output_byte(head<<2 | current_drive);
}
/* 若下发命令失败导致置位软盘复位标志则立即执行软盘复位 */
if (reset)
do_fd_request();
}
/*
* Special case - used after a unexpected interrupt (or reset)
*/
/* recal_interrupt,
* 软驱校正中断C处理函数。*/
static void recal_interrupt(void)
{
/* 检测中断状态,若异常则置软盘复位标志;
* 最后调度软盘下一请求。*/
output_byte(FD_SENSEI);
if (result()!=2 || (ST0 & 0xE0) == 0x60)
reset = 1;
else
recalibrate = 0;
do_fd_request();
}
/* unexpected_floppy_interrupt,
* 检测中断状态,若异常则置位软盘复位标志。*/
void unexpected_floppy_interrupt(void)
{
output_byte(FD_SENSEI);
if (result()!=2 || (ST0 & 0xE0) == 0x60)
reset = 1;
else
recalibrate = 1;
}
/* recalibrate_floppy,
* 向软驱下发校正命令和参数进行软盘校正,
* 复位校正相关标志,设置软驱校正中断函数。*/
static void recalibrate_floppy(void)
{
recalibrate = 0;
current_track = 0;
do_floppy = recal_interrupt;
output_byte(FD_RECALIBRATE);
output_byte(head<<2 | current_drive);
if (reset)
do_fd_request();
}
/* reset_interrupt,
* 复位软盘中断C处理函数,在向软盘下发复位命令后,
* 软盘接收命令并作复位操作后将引发软件中断从而
* 调用本函数。*/
static void reset_interrupt(void)
{
output_byte(FD_SENSEI);
(void) result();
output_byte(FD_SPECIFY);
output_byte(cur_spec1); /* hut etc */
output_byte(6); /* Head load time =6ms, DMA */
do_fd_request();
}
/*
* reset is done by pulling bit 2 of DOR low for a while.
*/
/* reset_floppy,
* 下发软盘复位命令和参数到软盘以复位软盘控制器。
* 软盘复位后将输出软盘中断。*/
static void reset_floppy(void)
{
int i;
reset = 0;
cur_spec1 = -1;
cur_rate = -1;
recalibrate = 1;
printk("Reset-floppy called\n\r");
cli();
do_floppy = reset_interrupt;
outb_p(current_DOR & ~0x04,FD_DOR);
for (i=0 ; i<100 ; i++)
__asm__("nop");
outb(current_DOR,FD_DOR);
sti();
}
/* floppy_on_interrupt,
* 软盘定时超时回调函数。*/
static void floppy_on_interrupt(void)
{
/* We cannot do a floppy-select, as that might sleep. We just force it */
selected = 1;
if (current_drive != (current_DOR & 3)) {
current_DOR &= 0xFC;
current_DOR |= current_drive;
outb(current_DOR,FD_DOR);
add_timer(2,&transfer);
} else
transfer();
}
/* do_fd_request,
* 软盘(读写)请求函数。
* 重置软盘或校正软盘,或启用定时器定时读写软盘。*/
void do_fd_request(void)
{
unsigned int block;
seek = 0;
/* 软盘重置或校正标志置位则重置或校正软盘 */
if (reset) {
reset_floppy();
return;
}
if (recalibrate) {
recalibrate_floppy();
return;
}
/* 检查当前对硬盘的请求是否合理 */
INIT_REQUEST;
/* 根据软盘次设备号映射到具体类型的软盘,
* 若请求不合理则放弃当前请求并调度下一请
* 求。若合理则将请求换算为软盘磁头,磁道,
* 柱面,扇区等参数。*/
floppy = (MINOR(CURRENT->dev)>>2) + floppy_type;
if (current_drive != CURRENT_DEV)
seek = 1;
current_drive = CURRENT_DEV;
block = CURRENT->sector;
if (block+2 > floppy->size) {
end_request(0);
goto repeat;
}
sector = block % floppy->sect;
block /= floppy->sect;
head = block % floppy->head;
track = block / floppy->head;
seek_track = track << floppy->stretch;
if (seek_track != current_track)
seek = 1; /* 若当前磁道和欲访问磁道不同则置寻道标志 */
sector++;
if (CURRENT->cmd == READ)
command = FD_READ;
else if (CURRENT->cmd == WRITE)
command = FD_WRITE;
else
panic("do_fd_request: unknown command");
/* 访问软驱时,软驱启动并达设定转速需一定时间。ticks_to_floppy_on
* 函数计算该事件,当定时器超时该事件时则调用floppy_on_interrupt
* 调度软盘请求。add_timer和ticks_to_floppy_on在sched.c中,届时阅读。*/
add_timer(ticks_to_floppy_on(current_drive),&floppy_on_interrupt);
}
/* floppy_init,
* 设置软盘读写函数,
* 在IDT[0x26]中设置软盘中断处理函数,设置PIC允许软盘中断IRQ6。*/
void floppy_init(void)
{
blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
set_trap_gate(0x26,&floppy_interrupt);
outb(inb_p(0x21)&~0x40,0x21);
}
/* floppy.c读得比hd.c粗糙哦... ^_^ */
fdreg.h
/*
* This file contains some defines for the floppy disk controller.
* Various sources. Mostly "IBM Microcomputers: A Programmers
* Handbook", Sanches and Canton.
*/
#ifndef _FDREG_H
#define _FDREG_H
extern int ticks_to_floppy_on(unsigned int nr);
extern void floppy_on(unsigned int nr);
extern void floppy_off(unsigned int nr);
extern void floppy_select(unsigned int nr);
extern void floppy_deselect(unsigned int nr);
/* Fd controller regs. S&C, about page 340 */
#define FD_STATUS 0x3f4
#define FD_DATA 0x3f5
#define FD_DOR 0x3f2 /* Digital Output Register */
#define FD_DIR 0x3f7 /* Digital Input Register (read) */
#define FD_DCR 0x3f7 /* Diskette Control Register (write)*/
/* Bits of main status register */
#define STATUS_BUSYMASK 0x0F /* drive busy mask */
#define STATUS_BUSY 0x10 /* FDC busy */
#define STATUS_DMA 0x20 /* 0- DMA mode */
#define STATUS_DIR 0x40 /* 0- cpu->fdc */
#define STATUS_READY 0x80 /* Data reg ready */
/* Bits of FD_ST0 */
#define ST0_DS 0x03 /* drive select mask */
#define ST0_HA 0x04 /* Head (Address) */
#define ST0_NR 0x08 /* Not Ready */
#define ST0_ECE 0x10 /* Equipment chech error */
#define ST0_SE 0x20 /* Seek end */
#define ST0_INTR 0xC0 /* Interrupt code mask */
/* Bits of FD_ST1 */
#define ST1_MAM 0x01 /* Missing Address Mark */
#define ST1_WP 0x02 /* Write Protect */
#define ST1_ND 0x04 /* No Data - unreadable */
#define ST1_OR 0x10 /* OverRun */
#define ST1_CRC 0x20 /* CRC error in data or addr */
#define ST1_EOC 0x80 /* End Of Cylinder */
/* Bits of FD_ST2 */
#define ST2_MAM 0x01 /* Missing Addess Mark (again) */
#define ST2_BC 0x02 /* Bad Cylinder */
#define ST2_SNS 0x04 /* Scan Not Satisfied */
#define ST2_SEH 0x08 /* Scan Equal Hit */
#define ST2_WC 0x10 /* Wrong Cylinder */
#define ST2_CRC 0x20 /* CRC error in data field */
#define ST2_CM 0x40 /* Control Mark = deleted */
/* Bits of FD_ST3 */
#define ST3_HA 0x04 /* Head (Address) */
#define ST3_TZ 0x10 /* Track Zero signal (1=track 0) */
#define ST3_WP 0x40 /* Write Protect */
/* Values for FD_COMMAND */
#define FD_RECALIBRATE 0x07 /* move to track 0 */
#define FD_SEEK 0x0F /* seek track */
#define FD_READ 0xE6 /* read with MT, MFM, SKip deleted */
#define FD_WRITE 0xC5 /* write with MT, MFM */
#define FD_SENSEI 0x08 /* Sense Interrupt Status */
#define FD_SPECIFY 0x03 /* specify HUT etc */
/* DMA commands */
#define DMA_READ 0x46
#define DMA_WRITE 0x4A
#endif
system_call.s
/* ...*/
/* _hd_interrupt,
* 设置在IDT[0x2e]中的硬盘中断入口程序。
* 当通过hd_out函数向硬盘下发读写等命令后,
* 在硬盘准备好被读写后就会向PIC IRQ14输出
* 相应的硬盘中断,从而让CPU执行IDT[0x2e]中
* 的处理函数即此处的_hd_interrupt。
*
* CPU跳转执行_hd_interrupt时,在栈中依次保存
* 了以下寄存器(即中断现场保护) ss esp flag cs eip。
*
* 在_hd_interrupt中继续依次入栈的寄存器有
* eax ecx edx ds es fs。*/
_hd_interrupt:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax # ds和es切换到内核数据段GDT[1]
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax # 加载用户数据段LDT[2]到fs
mov %ax,%fs
movb $0x20,%al # 见setup.s中对8259A的设置
outb %al,$0xA0 # EOI to interrupt controller #1
jmp 1f # give port chance to breathe
1: jmp 1f
1: xorl %edx,%edx # 异或操作将edx清0
xchgl _do_hd,%edx # 交换do_hd和edx的内容,do_hd的赋值见hd_out函数
testl %edx,%edx
jne 1f # 若hd_out非空则向前跳转到标号1处
movl $_unexpected_hd_interrupt,%edx # 若do_hd函数指针为NULL,则赋值此函数给do_hd。
1: outb %al,$0x20
call *%edx # "interesting" way of handling intr,如read_intr, write_intr etc.
# 中断函数执行完毕后, 从栈中恢复寄存器的值, 同时执行iret恢复中断现场。
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
/* _floppy_interrupt,
* 设置在IDT[26h]中软盘中断入口处理程序。
*
* 在向软盘下发复位,校正,DMA读写命令并在软盘完成
* 这些命令后,软盘将引发软盘中断即会让CPU直行本函数,
* 本函数将调用do_floppy函数,do_floppy函数挂载了复位,
* 校正,DMA读写等中断处理C函数。
*
* 其余一些信息同_hd_interrupt。*/
_floppy_interrupt:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
movb $0x20,%al
outb %al,$0x20 # EOI to interrupt controller #1
xorl %eax,%eax
xchgl _do_floppy,%eax
testl %eax,%eax
jne 1f
movl $_unexpected_floppy_interrupt,%eax
1: call *%eax # "interesting" way of handling intr.
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
/* ... */
blk.h
#ifndef _BLK_H
#define _BLK_H
#define NR_BLK_DEV 7
/*
* NR_REQUEST is the number of entries in the request-queue.
* NOTE that writes may use only the low 2/3 of these: reads
* take precedence.
*
* 32 seems to be a reasonable number: enough to get some benefit
* from the elevator-mechanism, but not so much as to lock a lot of
* buffers when they are in the queue. 64 seems to be too many (easily
* long pauses in reading when heavy writing/syncing is going on)
*/
/* NR_REQUEST 是块设备请求队列大小。为保证读优先级,只使用队列前2/3用作
* 写(和读)请求。
*
* 32是一个合理的值:大小适合电梯调度算法,只是在队列中的缓冲区块被锁住时
* 显得有些不太够数。64似乎太大了一点,当有许多写/同步操作时容易造成读请
* 求的暂停感。*/
#define NR_REQUEST 32
/*
* Ok, this is an expanded form so that we can use the same
* request for paging requests when that is implemented. In
* paging, 'bh' is NULL, and 'waiting' is used to wait for
* read/write completion.
*/
/* 注,为了在实现页请求功能后也能用该结构体进行页请求,此文对该
* 结构体类型做了扩展。当用于页请求时, 成员 bh 赋值为NULL, 成
* 员 waiting 用于等待读/写操作完成。*/
struct request {
int dev; /* -1 if no request */
int cmd; /* READ or WRITE */
int errors;
unsigned long sector; /* 当前设备分区中的当前扇区号 */
unsigned long nr_sectors; /* 欲读/写扇区数 */
char * buffer; /* 用于缓存访问设备数据的内存段 */
struct task_struct * waiting; /* 用于进程等待当前请求元素 */
struct buffer_head * bh; /* 管理缓冲区块的节点 */
struct request * next; /* 同一设备上的下一个请求 */
};
/*
* This is used in the elevator algorithm: Note that
* reads always go before writes. This is natural: reads
* are much more time-critical than writes.
*/
/* 该宏由电梯算法调用。注,读操作比写操作优先级更高。
* 读操作所体现出来的实时性(如需显示)比写操作高很多。*/
/* 在C语言中,算术运算符优先级高于逻辑运算符,
* 逻辑与运算符的优先级高于逻辑或的优先级。
*
* IN_ORDER(s1, s2)宏代表的表达式相当于
* ( (s1)->cmd < (s2)->cmd ) ||
* ( (s1)->cmd==(s2)->cmd && (...) )
* (...)中内容同外层。
*
* 在IN_ORDER(s1,s2)代表的表达式中,
* 若已表达式最终值来代表s1和s2的优
* 先级,如1代表s1的优先级大于s2优先级,
* 则
* cmd值(请求访问设备的操作:读/写..)
* 越小优先级越高;
* 在cmd相同时(如都为读),dev值(设备分
* 区号)越小优先级越高;
* 在dev相同时(如都为第2硬盘第2分区号)
* sector(访问的起始扇区号)越小优先级越高。
*
* 在add_request中安排设备的请求调度时,并
* 不是严格按照这个优先级安排的调度,而是按
* 照电梯(升梯)算法安排设备请求调度,以减少
* 磁盘在相邻请求间的大范围移动,从而节约一
* 些磁盘请求时间。*/
#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,
* 块设备当前(读写)请求结构体类型。*/
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
/*
* Add entries as needed. Currently the only block devices
* supported are hard-disks and floppies.
*/
#if (MAJOR_NR == 1)
/* ram disk */
#define DEVICE_NAME "ramdisk"
#define DEVICE_REQUEST do_rd_request
#define DEVICE_NR(device) ((device) & 7)
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
#elif (MAJOR_NR == 2)
/* floppy */
#define DEVICE_NAME "floppy"
#define DEVICE_INTR do_floppy
#define DEVICE_REQUEST do_fd_request
#define DEVICE_NR(device) ((device) & 3)
#define DEVICE_ON(device) floppy_on(DEVICE_NR(device))
#define DEVICE_OFF(device) floppy_off(DEVICE_NR(device))
#elif (MAJOR_NR == 3)
/* harddisk */
#define DEVICE_NAME "harddisk"
#define DEVICE_INTR do_hd
#define DEVICE_REQUEST do_hd_request
#define DEVICE_NR(device) (MINOR(device)/5)
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
#elif
/* unknown blk device */
#error "unknown blk device"
#endif
/* 主设备号MAJOR_NR块设备的当前请求及请求的设备分区号 */
#define CURRENT (blk_dev[MAJOR_NR].current_request)
#define CURRENT_DEV DEVICE_NR(CURRENT->dev)
#ifdef DEVICE_INTR
void (*DEVICE_INTR)(void) = NULL;
#endif
static void (DEVICE_REQUEST)(void);
/* unlock_buffer,
* 复位bh所指缓冲区块节点并唤醒等在该缓冲区块节点的进程。*/
extern 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);
}
/* end_request,
* 结束块设备当前请求,调用当前请求的下一个请求。
*
* uptodate=0,块设备请求失败;
* uptodate=1,块设备请求成功。
*
* 结束块设备当前请求时,会复位缓冲区块锁状态,
* 会置位缓冲区块读数据的状态;会唤醒等在当前请
* 求请求元素的进程;会唤醒在等空闲请求元素的进程。*/
extern inline void end_request(int uptodate)
{
/* 置缓冲区块数据是否已读标志,复位缓冲区块锁状态 */
DEVICE_OFF(CURRENT->dev);
if (CURRENT->bh) {
CURRENT->bh->b_uptodate = uptodate;
unlock_buffer(CURRENT->bh);
}
/* uptodate=0时,表示请求设备失败则提示 */
if (!uptodate) {
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;
}
/* 检查当前请求是否为NULL,
* 检查设备分区号对应的主设备号,
* 请求读写设备前对应缓冲区块是否存在并已置位锁状态。*/
#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
GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:22 天前 )
186a802e
added ecosystem file for PM2 4 年前
5def40a3
Add host customization support for the NodeJS version 4 年前
更多推荐
已为社区贡献9条内容
所有评论(0)