www.pudn.com > linux011.rar > floppy.c


/* passed
* linux/kernel/floppy.c
*
* (C) 1991 Linus Torvalds
*/
#include  

/*
* 02.12.91 - 修改成静态变量,以适应复位和重新校正操作。这使得某些事情
* 做起来较为方便(output_byte 复位检查等),并且意味着在出错时中断跳转
* 要少一些,所以希望代码能更容易被理解。
*/

/*
* 这个文件当然比较混乱。我已经尽我所能使其能够工作,但我不喜欢软驱编程,
* 而且我也只有一个软驱。另外,我应该做更多的查错工作,以及改正更多的错误。
* 对于某些软盘驱动器好象还存在一些问题。我已经尝试着进行纠正了,但不能保证
* 问题已消失。
*/

/*
* 如同hd.c 文件一样,该文件中的所有子程序都能够被中断调用,所以需要特别
* 地小心。硬件中断处理程序是不能睡眠的,否则内核就会傻掉(死机)?。因此不能
* 直接调用"floppy-on",而只能设置一个特殊的时间中断等。
*
* 另外,我不能保证该程序能在多于1 个软驱的系统上工作,有可能存在错误。
*/

#include 	// 调度程序头文件,定义了任务结构task_struct、初始任务0 的数据,
// 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。
#include 		// 文件系统头文件。定义文件表结构(file,buffer_head,m_inode 等)。
#include 	// 内核头文件。含有一些内核常用函数的原形定义。
#include 	// 软驱头文件。含有软盘控制器参数的一些定义。
#include 		// 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。
#include 		// io 头文件。定义硬件端口输入/输出宏汇编语句。
#include 	// 段操作头文件。定义了有关段寄存器操作的嵌入式汇编函数。

#define MAJOR_NR 2		// 软驱的主设备号是2。
#include "blk.h"		// 块设备头文件。定义请求数据结构、块设备数据结构和宏函数等信息。

static int recalibrate = 0;	// 标志:需要重新校正。
static int reset = 0;		// 标志:需要进行复位操作。
static int seek = 0;		// 寻道。

extern unsigned char current_DOR;	// 当前数字输出寄存器(Digital Output Register)。 
 
// 字节直接输出(嵌入汇编语言宏)。
//#define immoutb_p(val,port) \
//__asm__ ("outb %0,%1\n\tjmp 1f\n1:\tjmp 1f\n1:"::"a" ((char) (val)),"i" (port)) 
void _inline immoutb_p(unsigned char val,unsigned short port) 
{_asm{ 
	mov al,val 
	mov dx,port 
	out dx,al 
	jmp l1 
l1: jmp l2 
l2: 
}}
// 这两个定义用于计算软驱的设备号。次设备号 = TYPE*4 + DRIVE。计算方法参见列表后。
#define TYPE(x) ((x)>>2)	// 软驱类型(2--1.2Mb,7--1.44Mb)。
#define DRIVE(x) ((x)&0x03)	// 软驱序号(0--3 对应A--D)。
/*
* 注意,下面定义MAX_ERRORS=8 并不表示对每次读错误尝试最多8 次 - 有些类型
* 的错误将把出错计数值乘2,所以我们实际上在放弃操作之前只需尝试5-6 遍即可。
*/
#define MAX_ERRORS 8
/*
* globals used by 'result()'
*/
/* 下面是函数'result()'使用的全局变量 */
// 这些状态字节中各比特位的含义请参见include/linux/fdreg.h 头文件。
#define MAX_REPLIES 7		// FDC 最多返回7 字节的结果信息。 

static unsigned char reply_buffer[MAX_REPLIES] = {0};	// 存放FDC 返回的结果信息。 

#define ST0 (reply_buffer[0])	// 返回结果状态字节0。
#define ST1 (reply_buffer[1])	// 返回结果状态字节1。
#define ST2 (reply_buffer[2])	// 返回结果状态字节2。
#define ST3 (reply_buffer[3])	// 返回结果状态字节3。

/*
* 下面的软盘结构定义了不同的软盘类型。与minix 不同的是,linux 没有
* "搜索正确的类型"-类型,因为对其处理的代码令人费解且怪怪的。本程序
* 已经让我遇到了许多的问题了。
*
* 对某些类型的软盘(例如在1.2MB 驱动器中的360kB 软盘等),'stretch'用于
* 检测磁道是否需要特殊处理。其它参数应该是自明的。
*/
// 软盘参数有:
// size 大小(扇区数);
// sect 每磁道扇区数;
// head 磁头数;
// track 磁道数;
// stretch 对磁道是否要特殊处理(标志);
// gap 扇区间隙长度(字节数);
// rate 数据传输速率;
// spec1 参数(高4 位步进速率,低四位磁头卸载时间)。
static struct floppy_struct
{
	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:0 表示500kb/s,1 表示300kbps,2 表示250kbps。
* 参数spec1 是0xSH,其中S 是步进速率(F-1 毫秒,E-2ms,D=3ms 等),
* H 是磁头卸载时间(1=16ms,2=32ms 等)
*
* spec2 是(HLD<<1 | ND),其中HLD 是磁头加载时间(1=2ms,2=4ms 等)
* ND 置位表示不使用DMA(No DMA),在程序中硬编码成6(HLD=6ms,使用DMA)。
*/

extern void floppy_interrupt (void);
extern char tmp_floppy_area[1024];

/*
* 下面是一些全局变量,因为这是将信息传给中断程序最简单的方式。它们是
* 用于当前请求的数据。
*/
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;

//// 释放(取消选定的)软盘(软驱)。
// 数字输出寄存器(DOR)的低2 位用于指定选择的软驱(0-3 对应A-D)。
void
floppy_deselect (unsigned int nr)
{
	if (nr != (unsigned int)(current_DOR & 3))
		printk ("floppy_deselect: drive not selected\n\r");
	selected = 0;
	wake_up (&wait_on_floppy_select);
}

/*
* floppy-change()不是从中断程序中调用的,所以这里我们可以轻松一下,睡觉等。
* 注意floppy-on()会尝试设置current_DOR 指向所需的驱动器,但当同时使用几个
* 软盘时不能睡眠:因此此时只能使用循环方式。
*/
//// 检测指定软驱中软盘更换情况。如果软盘更换了则返回1,否则返回0。
int
floppy_change (unsigned int nr)
{
repeat:
	floppy_on (nr);		// 开启指定软驱nr(kernel/sched.c,251)。
// 如果当前选择的软驱不是指定的软驱nr,并且已经选择其它了软驱,则让当前任务进入可中断
// 等待状态。
	while ((unsigned int)(current_DOR & 3) != nr && selected)
		interruptible_sleep_on (&wait_on_floppy_select);
// 如果当前没有选择其它软驱或者当前任务被唤醒时,当前软驱仍然不是指定的软驱nr,则循环等待。
	if ((unsigned int)(current_DOR & 3) != nr)
		goto repeat;
// 取数字输入寄存器值,如果最高位(位7)置位,则表示软盘已更换,此时关闭马达并退出返回1。
// 否则关闭马达退出返回0。
	if (inb (FD_DIR) & 0x80)
	{
		floppy_off (nr);
		return 1;
	}
	floppy_off (nr);
	return 0;
}

//// 复制内存块。
//#define copy_buffer(from,to) \
// __asm__( "cld ; rep ; movsl" \
// :: "c" (BLOCK_SIZE/4), "S" ((long)(from)), "D" ((long)(to)) \
// : "cx", "di", "si")
void _inline copy_buffer(void* from, void* to) 
{_asm{
	pushf 
	mov cx,BLOCK_SIZE/4 
	mov esi,from 
	mov edi,to 
	cld 
	rep movsd
	popf 
}} 

//// 设置(初始化)软盘DMA 通道。
static void
setup_DMA (void)
{
	long addr = (long) CURRENT->buffer;	// 当前请求项缓冲区所处内存中位置(地址)。

	cli ();
// 如果缓冲区处于内存1M 以上的地方,则将DMA 缓冲区设在临时缓冲区域(tmp_floppy_area 数组)
// (因为8237A 芯片只能在1M 地址范围内寻址)。如果是写盘命令,则还需将数据复制到该临时区域。
	if (addr >= 0x100000)
	{
		addr = (long) tmp_floppy_area;
		if (command == FD_WRITE)
			copy_buffer (CURRENT->buffer, tmp_floppy_area);
	}
/* mask DMA 2 *//* 屏蔽DMA 通道2 */
// 单通道屏蔽寄存器端口为0x10。位0-1 指定DMA 通道(0--3),位2:1 表示屏蔽,0 表示允许请求。
	immoutb_p (4 | 2, 10);
/* 输出命令字节。我是不知道为什么,但是每个人(minix,*/
/* sanches 和canton)都输出两次,首先是12 口,然后是11 口 */
// 下面嵌入汇编代码向DMA 控制器端口12 和11 写方式字(读盘0x46,写盘0x4A)。 
	if (command == FD_READ) 
		_asm mov al,DMA_READ; 
	else 
		_asm mov al,DMA_WRITE; 
	_asm { 
		out 12,al 
		jmp l1 
	l1: jmp l2 
	l2: out 11,al 
		jmp l3 
	l3: jmp l4 
	l4: 
	}
//  __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)));
/* 8 low bits of addr *//* 地址低0-7 位 */
// 向DMA 通道2 写入基/当前地址寄存器(端口4)。
	immoutb_p ((unsigned char)addr, 4);
	addr >>= 8;
/* bits 8-15 of addr *//* 地址高8-15 位 */
	immoutb_p ((unsigned char)addr, 4);
	addr >>= 8;
/* bits 16-19 of addr *//* 地址16-19 位 */
// DMA 只可以在1M 内存空间内寻址,其高16-19 位地址需放入页面寄存器(端口0x81)。
	immoutb_p ((unsigned char)addr, 0x81);
/* low 8 bits of count-1 (1024-1=0x3ff) *//* 计数器低8 位(1024-1=0x3ff) */
// 向DMA 通道2 写入基/当前字节计数器值(端口5)。
	immoutb_p (0xff, 5);
/* high 8 bits of count-1 *//* 计数器高8 位 */
// 一次共传输1024 字节(两个扇区)。
	immoutb_p (3, 5);
/* activate DMA 2 *//* 开启DMA 通道2 的请求 */
// 复位对DMA 通道2 的屏蔽,开放DMA2 请求DREQ 信号。
	immoutb_p (0 | 2, 10);
	sti ();
}

//// 向软盘控制器输出一个字节数据(命令或参数)。
static void
output_byte (char byte)
{
	int counter;
	unsigned char status;

	if (reset)
		return;
// 循环读取主状态控制器FD_STATUS(0x3f4)的状态。如果状态是STATUS_READY 并且STATUS_DIR=0
// (CPU??FDC),则向数据端口输出指定字节。
	for (counter = 0; counter < 10000; counter++)
	{
		status = inb_p (FD_STATUS) & (STATUS_READY | STATUS_DIR);
		if (status == STATUS_READY)
		{
			outb (byte, FD_DATA);
			return;
		}
	}
// 如果到循环1 万次结束还不能发送,则置复位标志,并打印出错信息。
	reset = 1;
	printk ("Unable to send byte to FDC\n\r");
}

//// 读取FDC 执行的结果信息。
// 结果信息最多7 个字节,存放在reply_buffer[]中。返回读入的结果字节数,若返回值=-1
// 表示出错。
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;
		if (status == (STATUS_DIR | STATUS_READY | STATUS_BUSY))
		{
			if (i >= MAX_REPLIES)
				break;
			reply_buffer[i++] = inb_p (FD_DATA);
		}
	}
	reset = 1;
	printk ("Getstatus times out\n\r");
	return -1;
}

//// 软盘操作出错中断调用函数。由软驱中断处理程序调用。
static void
bad_flp_intr (void)
{
	CURRENT->errors++;		// 当前请求项出错次数增1。
// 如果当前请求项出错次数大于最大允许出错次数,则取消选定当前软驱,并结束该请求项(不更新)。
	if (CURRENT->errors > MAX_ERRORS)
	{
		floppy_deselect (current_drive);
		end_request (0);
	}
// 如果当前请求项出错次数大于最大允许出错次数的一半,则置复位标志,需对软驱进行复位操作,
// 然后再试。否则软驱需重新校正一下,再试。
	if (CURRENT->errors > MAX_ERRORS / 2)
		reset = 1;
	else
		recalibrate = 1;
}

/*
* OK,下面该中断处理函数是在DMA 读/写成功后调用的,这样我们就可以检查执行结果,
* 并复制缓冲区中的数据。
*/
//// 软盘读写操作成功中断调用函数。。
static void
rw_interrupt (void)
{
// 如果返回结果字节数不等于7,或者状态字节0、1 或2 中存在出错标志,则若是写保护
// 就显示出错信息,释放当前驱动器,并结束当前请求项。否则就执行出错计数处理。
// 然后继续执行软盘请求操作。
// ( 0xf8 = ST0_INTR | ST0_SE | ST0_ECE | ST0_NR )
// ( 0xbf = ST1_EOC | ST1_CRC | ST1_OR | ST1_ND | ST1_WP | ST1_MAM,应该是0xb7)
// ( 0x73 = ST2_CM | ST2_CRC | ST2_WC | ST2_BC | ST2_MAM )
	if (result () != 7 || (ST0 & 0xf8) || (ST1 & 0xbf) || (ST2 & 0x73))
	{
		if (ST1 & 0x02)
		{			// 0x02 = ST1_WP - Write Protected。
			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;
	}
// 如果当前请求项的缓冲区位于1M 地址以上,则说明此次软盘读操作的内容还放在临时缓冲区内,
// 需要复制到请求项的缓冲区中(因为DMA 只能在1M 地址范围寻址)。
	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 ();
}

//// 设置DMA 并输出软盘操作命令和参数(输出1 字节命令+ 0~7 字节参数)。
_inline void
setup_rw_floppy (void)
{
	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 */// 发送参数(字节数(N=2)512 字节)。
	output_byte (floppy->sect);	// 发送参数(每磁道扇区数)。
	output_byte (floppy->gap);	// 发送参数(扇区间隔长度)。
	output_byte ((char)0xFF);		/* sector size (0xff when n!=0 ?) */
// 发送参数(当N=0 时,扇区定义的字节长度),这里无用。
// 若在发送命令和参数时发生错误,则继续执行下一软盘操作请求。
	if (reset)
		do_fd_request ();
}

/*
* 该子程序是在每次软盘控制器寻道(或重新校正)中断后被调用的。注意
* "unexpected interrupt"(意外中断)子程序也会执行重新校正操作,但不在此地。
*/
//// 寻道处理中断调用函数。
// 首先发送检测中断状态命令,获得状态信息ST0 和磁头所在磁道信息。若出错则执行错误计数
// 检测处理或取消本次软盘操作请求项。否则根据状态信息设置当前磁道变量,然后调用函数
// setup_rw_floppy()设置DMA 并输出软盘读写命令和参数。
static void
seek_interrupt (void)
{
/* sense drive status *//* 检测中断状态 */
// 发送检测中断状态命令,该命令不带参数。返回结果信息两个字节:ST0 和磁头当前磁道号。
	output_byte (FD_SENSEI);
// 如果返回结果字节数不等于2,或者ST0 不为寻道结束,或者磁头所在磁道(ST1)不等于设定磁道,
// 则说明发生了错误,于是执行检测错误计数处理,然后继续执行软盘请求项,并退出。
	if (result () != 2 || (ST0 & 0xF8) != 0x20 || ST1 != seek_track)
	{
		bad_flp_intr ();
		do_fd_request ();
		return;
	}
	current_track = ST1;		// 设置当前磁道。
	setup_rw_floppy ();		// 设置DMA 并输出软盘操作命令和参数。
}

/*
* 该函数是在传输操作的所有信息都正确设置好后被调用的(也即软驱马达已开启
* 并且已选择了正确的软盘(软驱)。
*/
//// 读写数据传输函数。
static void
transfer (void)
{
// 首先看当前驱动器参数是否就是指定驱动器的参数,若不是就发送设置驱动器参数命令及相应
// 参数(参数1:高4 位步进速率,低四位磁头卸载时间;参数2:磁头加载时间)。
	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 */
	}
// 判断当前数据传输速率是否与指定驱动器的一致,若不是就发送指定软驱的速率值到数据传输
// 速率控制寄存器(FD_DCR)。
	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 ();
}

/*
* 特殊情况 - 用于意外中断(或复位)处理后。
*/
//// 软驱重新校正中断调用函数。
// 首先发送检测中断状态命令(无参数),如果返回结果表明出错,则置复位标志,否则复位重新
// 校正标志。然后再次执行软盘请求。
static void
recal_interrupt (void)
{
	output_byte (FD_SENSEI);	// 发送检测中断状态命令。
	if (result () != 2 || (ST0 & 0xE0) == 0x60)	// 如果返回结果字节数不等于2 或命令
		reset = 1;			// 异常结束,则置复位标志。
	else				// 否则复位重新校正标志。
		recalibrate = 0;
	do_fd_request ();		// 执行软盘请求项。
}

//// 意外软盘中断请求中断调用函数。
// 首先发送检测中断状态命令(无参数),如果返回结果表明出错,则置复位标志,否则置重新
// 校正标志。
void
unexpected_floppy_interrupt (void)
{
	output_byte (FD_SENSEI);	// 发送检测中断状态命令。
	if (result () != 2 || (ST0 & 0xE0) == 0x60)	// 如果返回结果字节数不等于2 或命令
		reset = 1;			// 异常结束,则置复位标志。
	else				// 否则置重新校正标志。
		recalibrate = 1;
}

//// 软盘重新校正处理函数。
// 向软盘控制器FDC 发送重新校正命令和参数,并复位重新校正标志。
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 ();
}

//// 软盘控制器FDC 复位中断调用函数。在软盘中断处理程序中调用。
// 首先发送检测中断状态命令(无参数),然后读出返回的结果字节。接着发送设定软驱参数命令
// 和相关参数,最后再次调用执行软盘请求。
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 ();		// 调用执行软盘请求。
}

/* FDC 复位是通过将数字输出寄存器(DOR)位2 置0 一会儿实现的 */
//// 复位软盘控制器。
static void
reset_floppy (void)
{
	int i;

	reset = 0;			// 复位标志置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);	// 对软盘控制器FDC 执行复位操作。
	for (i = 0; i < 100; i++)	// 空操作,延迟。
		_asm nop;
	outb (current_DOR, FD_DOR);	// 再启动软盘控制器。
	sti ();			// 开中断。
}

//// 软驱启动定时中断调用函数。
// 首先检查数字输出寄存器(DOR),使其选择当前指定的驱动器。然后调用执行软盘读写传输
// 函数transfer()。
static void
floppy_on_interrupt (void)
{
/* 我们不能任意设置选择的软驱,因为这样做可能会引起进程睡眠。我们只是迫使它自己选择 */
  selected = 1;			// 置已选择当前驱动器标志。
// 如果当前驱动器号与数字输出寄存器DOR 中的不同,则重新设置DOR 为当前驱动器current_drive。
// 定时延迟2 个滴答时间,然后调用软盘读写传输函数transfer()。否则直接调用软盘读写传输函数。
	if (current_drive != (current_DOR & 3))
	{
		current_DOR &= 0xFC;
		current_DOR |= current_drive;
		outb (current_DOR, FD_DOR);	// 向数字输出寄存器输出当前DOR。
		add_timer (2, &transfer);	// 添加定时器并执行传输函数。
	}
	else
		transfer ();		// 执行软盘读写传输函数。
}

//// 软盘读写请求项处理函数。
void
do_fd_request (void)
{
	unsigned int block;

	seek = 0;
// 如果复位标志已置位,则执行软盘复位操作,并返回。
	if (reset)
	{
		reset_floppy ();
		return;
	}
// 如果重新校正标志已置位,则执行软盘重新校正操作,并返回。
	if (recalibrate)
	{
		recalibrate_floppy ();
		return;
	}
// 检测请求项的合法性(参见kernel/blk_drv/blk.h,127)。
	INIT_REQUEST;
// 将请求项结构中软盘设备号中的软盘类型(MINOR(CURRENT->dev)>>2)作为索引取得软盘参数块。
	floppy = (MINOR (CURRENT->dev) >> 2) + floppy_type;
// 如果当前驱动器不是请求项中指定的驱动器,则置标志seek,表示需要进行寻道操作。
// 然后置请求项设备为当前驱动器。
	if (current_drive != CURRENT_DEV)
		seek = 1;
	current_drive = CURRENT_DEV;
// 设置读写起始扇区。因为每次读写是以块为单位(1 块2 个扇区),所以起始扇区需要起码比
// 磁盘总扇区数小2 个扇区。否则结束该次软盘请求项,执行下一个请求项。
	block = CURRENT->sector;	// 取当前软盘请求项中起始扇区号??block。
	if (block + 2 > floppy->size)
	{				// 如果block+2 大于磁盘扇区总数,则
		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;	// 相应于驱动器中盘类型进行调整,得寻道号。
// 如果寻道号与当前磁头所在磁道不同,则置需要寻道标志seek。
	if (seek_track != current_track)
		seek = 1;
	sector++;			// 磁盘上实际扇区计数是从1 算起。
	if (CURRENT->cmd == READ)	// 如果请求项中是读操作,则置软盘读命令码。
		command = FD_READ;
	else if (CURRENT->cmd == WRITE)	// 如果请求项中是写操作,则置软盘写命令码。
		command = FD_WRITE;
	else
		panic ("do_fd_request: unknown command");
// 添加定时器,用于指定驱动器到能正常运行所需延迟的时间(滴答数),当定时时间到时就调用
// 函数floppy_on_interrupt(),
	add_timer (ticks_to_floppy_on (current_drive), &floppy_on_interrupt);
}

//// 软盘系统初始化。
// 设置软盘块设备的请求处理函数(do_fd_request()),并设置软盘中断门(int 0x26,对应硬件
// 中断请求信号IRQ6),然后取消对该中断信号的屏蔽,允许软盘控制器FDC 发送中断请求信号。
void
floppy_init (void)
{
	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;	// = do_fd_request()。
	set_trap_gate (0x26, &floppy_interrupt);	//设置软盘中断门 int 0x26(38)。
	outb (inb_p (0x21) & ~0x40, 0x21);	// 复位软盘的中断请求屏蔽位,允许
										// 软盘控制器发送中断请求信号。
}