linux0.11块设备驱动及访问请求管理程序阅读注释笔记

[ 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

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页