本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接,严禁用于任何商业用途。 作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net 1.1 保护模式下实现软盘编程
要发挥x86芯片的功能,必须要进入保护模式。
系统启动时所加载的512字节的MBR区为bootloader区,用于加载真正的boot程序。在bootloader区中,cpu还运行于实模式,因此bootloader通过bios中断加载boot。进入boot区后,一般来说已经完成了切换入保护模式的动作。
在保护模式下,不能使用bios中断,需要通过读写软盘控制器芯片8237来完成。 1.1.1 软盘控制器
I/O address |
Read or Write |
Register |
0x3f2 |
Write |
DOR: Digital Output Register |
0x3f4 |
Read |
FDC Status: Floppy Disk Status Register |
0x3f5 |
Read/Write |
FDC Data: Floppy Disk Data Register |
0x3f7 |
Read |
DIR: Digital Input Register |
Write |
DCR: Disk Control Register |
注:FDC为软盘控制器 1.1.1.1 DOR数字输出寄存器
DOR是一个8为寄存器,他控制驱动器马达的开启、驱动器选择、启动/复位FDC以及允许/禁止DMA请求
位 |
Name |
Description |
7 |
MOT_EN3 |
Driver D motor:1-start;0-stop |
6 |
MOT_EN2 |
Driver C motor:1-start;0-stop |
5 |
MOT_EN1 |
Driver B motor:1-start;0-stop |
4 |
MOT_EN0 |
Driver A motor:1-start;0-stop |
3 |
DMA_INT |
DMA interrupt; 1 enable; 0-disable |
2 |
RESET |
FDC Reset |
1 |
DRV_SEL1 |
Select driver |
0 |
DRV_SEL0 |
1.1.1.2 FDC Status:FDC状态寄存器
FDC status用于反映软盘驱动器FDC的基本状态。通常,在CPU想FDC发送命令或从FDC获取结果前,都要读取FDC的状态为,以判断当前的FDC data寄存器是否就需,以及确定数据传输方向。
位 |
Name |
Description |
7 |
RQM |
Data ready: FDD ready |
6 |
DIO |
Direction: 1 - FDD to CPU; 0 – CPU to FDD |
5 |
NDM |
DMA set: 1-not DMA; 0-DMA |
4 |
CB |
Controller busy |
3 |
DDB |
Driver D busy |
2 |
DCB |
Driver C busy |
1 |
DBB |
Driver B busy |
0 |
DAB |
Driver A busy |
1.1.1.3 FDC Data:FDC数据寄存器
FDC Data寄存器用于向FDC发送控制命令或从FDC读取状态,实现数据读写等。FDC的使用比较复杂,可支持多种命令。每个命令都通过一个命令序列实现:命令阶段、执行阶段和结果阶段。
1) 重新校正命令(FD_RECALIBRATE)
软盘启动时调用
阶段 |
序 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
说明 |
cmd |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0x07 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
US1 |
US2 |
Drive no. |
执行 |
|
|
磁头移动到track0 |
结果 |
|
无 |
无 |
2) 磁头寻道命令(FD_SEEK)
把磁头定位到制定位置,在读写前执行
阶段 |
序 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
说明 |
cmd |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0x0F |
1 |
0 |
0 |
0 |
0 |
0 |
HD |
US1 |
US2 |
磁头号、驱动器号 |
2 |
C |
磁道号 |
执行 |
|
|
磁头移动到制定磁道 |
结果 |
|
无 |
无 |
3) 读扇区数据命令(FD_READ)
阶段 |
序 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
说明 |
cmd |
0 |
MT |
MF |
SK |
0 |
0 |
1 |
1 |
0 |
0xE6(MT=MF=SK=1) |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
US1 |
US2 |
驱动器号 |
2 |
C |
磁道号track |
3 |
H |
磁头号head |
4 |
R |
起始扇区号start sector |
5 |
N |
扇区字节数 |
6 |
EOT |
磁道最大扇区号 |
7 |
GPL |
扇区建间隔长度(3) |
8 |
DTL |
N=0时,制定扇区字节书 |
执行 |
|
|
从软盘读取扇区 |
结果 |
1 |
ST0 |
状态字节0 |
2 |
ST1 |
状态字节1 |
3 |
ST2 |
状态字节2 |
4 |
C |
磁道号track |
5 |
H |
磁头号head |
6 |
R |
起始扇区号 |
7 |
N |
扇区字节数 |
注:
MT:多磁道操作。MT=1表示允许多磁道操作
MF:记录方式。MF=1表示选用MFM记录方式,否则是FM记录方式d
SK:是否跳过有删除标志的扇区。SK=1表示跳过。
返回的返回的状态ST0、ST1和ST2的含义如下:
ST0:
位 |
名称 |
说明 |
7 |
ST0_INTR |
中断原因。00-正常结束;01-异常结束;10-命令无效;11-软盘驱动器状态改变 |
6 |
5 |
ST0_SE |
寻道操作或重新校正操作结束(seek end) |
4 |
ST0_ECE |
设备检查错误(0磁道校正错误)(Equip. Check Error) |
3 |
ST0_NR |
软盘未就绪(Not Ready) |
2 |
ST0_HA |
磁头地址。中断时磁头地址(Head Address) |
1 |
ST0_DS |
驱动器号(Driver Select) |
0 |
ST1:
位 |
名称 |
说明 |
7 |
ST1_EOC |
范文超过磁道最大扇区号(End of Cylinder) |
6 |
|
Reserve |
5 |
ST1_CRC |
CRC校验出错 |
4 |
ST1_OR |
数据传输超时(Over Run) |
3 |
|
Reserve |
2 |
ST1_ND |
未找到制定扇区(No Data) |
1 |
ST1_WP |
写保护(Write Protect) |
0 |
ST1_MAM |
未找到扇区地址标志ID(Miss Address Mask) |
ST2:
位 |
名称 |
说明 |
7 |
|
Reserve |
6 |
ST2_CM |
SK=0时,读数据遇到删除标志(Control Mark) |
5 |
ST2_CRC |
CRC校验出错 |
4 |
ST2_WC |
扇区ID信息的磁道号C不不符(Wrong Cylinder) |
3 |
ST2_SEH |
检索条件满足(Scan Equal Hit) |
2 |
ST2_SNS |
检索条件不满足(Scan Not Satisfied) |
1 |
ST2_BC |
磁道坏(Bad Cylinder) |
0 |
ST2_MAM |
未找到扇区地址标志ID(Miss Address Mask) |
4) 写扇区数据命令(FD_WRITE)
阶段 |
序 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
说明 |
cmd |
0 |
MT |
MF |
0 |
0 |
0 |
1 |
0 |
1 |
0xC5(MT=MF=1) |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
US1 |
US2 |
磁头号、驱动器号 |
2 |
C |
磁道号track |
3 |
H |
磁头号head |
4 |
R |
起始扇区号start sector |
5 |
N |
扇区字节数 |
6 |
EOT |
磁道最大扇区号 |
7 |
GPL |
扇区建间隔长度(3) |
8 |
DTL |
N=0时,制定扇区字节书 |
执行 |
|
|
向软盘写入扇区 |
结果 |
1 |
ST0 |
状态字节0 |
2 |
ST1 |
状态字节1 |
3 |
ST2 |
状态字节2 |
5) 检测中断状态命令(FD_SENSEI)
阶段 |
序 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
说明 |
cmd |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0x08 |
执行 |
|
|
|
结果 |
1 |
ST0 |
状态字节0 |
2 |
|
磁头所在磁道号 |
6) 设定驱动器参数命令(FD_SPECIFY)
阶段 |
序 |
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
说明 |
cmd |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0x03 |
1 |
SRT(单位2ms) |
HUT(单位32ms) |
马达速度、磁头卸载时间 |
2 |
HLT(单位4ms) |
ND |
磁头加载时间,非DMA模式 |
执行 |
|
|
设置控制器 |
结果 |
|
无 |
无 |
1.1.1.4 DIR:数字输入寄存器
DIR寄存器只有D7位有效,用于表示软盘更换状态,其余用于硬盘控制器。 1.1.1.5 DCR:磁盘控制寄存器
DCR仅是用户D0与D1位,用于表示数据传输率。
00-500kpbs, 01-300kpbs, 10-250kpbs。 1.1.2 保护模式下代码实现 1.1.2.1 初始化
1) Reset
- outb(FLOPPY_REG_DOR, 0x08); // 重启
- for (i=0 ; i<100 ; i++)
- __asm__("nop"); // 延时,保证重启完成
- outb(FLOPPY_REG_DOR, 0xc); // 选择DMA模式,选择软驱A
1) 设置磁盘数据传输速度
outb(FD_DCR, 0); // 500kpbs
2) Output_byte函数
用于FDC命令的输出,FDC的每条命令需要确保上条命令已经完成
- static void output_byte(char byte)
- {
- int counter;
- unsigned char status;
- for(counter = 0 ; counter < 10000 ; counter++) {
- status = inb(FD_STATUS) & (STATUS_READY | STATUS_DIR);
- if (status == STATUS_READY) {
- outb(FD_DATA, byte);
- return;
- }
- }
- printf("Unable to send byte to FDC\r");
- }
3) 设置驱动器参数
- output_byte(FD_SPECIFY);
- output_byte(0xCF); /* 马达步进速度、磁头卸载时间=32ms */
- output_byte(6); /* Head load time =6ms, DMA */
1.1.2.2 读扇区
- typedef struct {
- unsigned int size, sect, head, track, stretch;
- unsigned char gap,rate,spec1;
- }floppy_struct;
- static floppy_struct floppy_type =
- {2880,18,2,80,0,0x1B,0x00,0xCF }; /* 1.44MB diskette */
- static u32 current_dev = 0;
- /* (2 * 18 * 80 * 512) */
- void FloppyReadSector(u32 sectNo, u8 *buf)
- {
- u32 head, track, block, sector, seek_track;
- if (NULL == buf)
- {
- printf("FloppyReadSector para error.\r");
- return;
- }
- if (sectNo >= (floppy_type.head * floppy_type.track * floppy_type.sect))
- {
- printf("FloppyReadSector sectNo error: %x.\r", sectNo);
- return;
- }
- /* 计算参数 */
- sector = sectNo % floppy_type.sect + 1;
- block = sectNo / floppy_type.sect;
- track = block / floppy_type.head;
- head = block % floppy_type.head;
- seek_track = track << floppy_type.stretch;
-
- /* 软盘重新校正 */
- output_byte(FD_RECALIBRATE);
- output_byte(current_dev);
-
- /* 寻找磁道 */
- output_byte(FD_SEEK);
- output_byte(current_dev);
- output_byte(seek_track);
-
- /* 设置DMA,准备传送数据 */
- SetDMA(buf, FD_READ);
- /* 发送读扇区命令 */
- output_byte(FD_READ); /* command */
- output_byte(current_dev); /* driver no. */
- output_byte(track); /* track no. */
- output_byte(head); /* head */
- output_byte(sector); /* start sector */
- output_byte(2); /* sector size = 512 */
- output_byte(floppy_type.sect); /* Max sector */
- output_byte(floppy_type.gap); /* sector gap */
- output_byte(0xFF); /* sector size (0xff when n!=0 ?) */
- }
程序很清楚,不再多说,写命令于此类似。
唯一不清楚的是SetDMA函数。
我们在设置DOR时设置的DMA工作方式为enable,也就是说数据会通过DMA方式传送,因此必须设置DMA控制器。
1.1.2.3 DMA传输
- /* DMA commands */
- #define DMA_READ 0x46
- #define DMA_WRITE 0x4A
- #define immoutb_p(val,port) \
- __asm__("outb %0,%1\n\tjmp 1f\n1:\tjmp 1f\n1:"::"a" ((char) (val)),"i" (port))
- void SetDMA(u8 *buf, u8 cmd)
- {
- long addr = (long)buf;
- Cli();
- /* mask DMA 2 */
- 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 */
- __asm__("outb %%al,$12\n\tjmp 1f\n1:\tjmp 1f\n1:\t"
- "outb %%al,$11\n\tjmp 1f\n1:\tjmp 1f\n1:"::
- "a" ((char) ((cmd == FD_READ)?DMA_READ:DMA_WRITE)));
- /* 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);
- /* 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 */
- immoutb_p(0|2,10);
- Sti();
- }
该函数由linux0.11移植而来,可参照DMA控制器手册进行设置。不看也可以,注释写得很清楚,拿过来用就是了。
本文乃fireaxe原创,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,并注明原作者及原链接,严禁用于任何商业用途。 作者:fireaxe_hq@hotmail.com
博客:fireaxe.blog.chinaunix.net |