您的位置:首页 > 移动开发 > IOS开发

bios x86保护模式下的软盘操作floppy

2016-08-23 11:04 871 查看
Cpu启动时,如果在bios中设置了从软盘启动,则bios会自动把软盘的第一个扇区(512字节)搬移到0x7c00,然后会从0x7c00开始运行,我们需要在这512字节的程序中实现把boot从软盘中搬移到内存中的功能。

此时x86系列的cpu还处在实模式中,可以利用bios提供的中断完成软盘读写,也就是著名的int 13。

1   软盘操作
1.1      软盘结构

以1.44M软盘为例,软盘有上、下两个盘面,每个盘面被划分为80个track,每个track被划分为18个Sector,每个Sector大小为512
BYTES。

2×80×18×512=1440×1024=1.44MB

如果是单纯的进行软盘读写操作,知道软盘的这些知识就够了。

1.2      软盘读写
中断号

寄存器

作用

13h

ah=00h      al=驱动器号(0表示A盘)

复位软驱

ah=02h      al=要读扇区数

ch=磁道号   cl=起始扇区号

dh=磁头号   dl=驱动器号

es:bx=数据缓冲区

从cl/ch/dh/dl指向的扇区开始读取al个扇区的数据到es:bx指向的缓冲区

ah=03h      al=要写扇区数

ch=磁道号   cl=起始扇区号

dh=磁头号   dl=驱动器号

es:bx=数据缓冲区

从es:bx指向的缓冲区读取al个扇区的数据写入cl/ch/dh/dl指向的al个扇区

对于软盘来说, DL=0(0表示A盘)

CL的取值范围是:1—18

CH的取值范围是:0—79

DH 的取值范围是:0—1

如果软盘的CL,CH,DH取值超过了范围,中断调用出错。





使用int13时对应扇区数的计算方法如下:



 

1.3      软盘编程
用软盘存放操作系统时,bios只会把前512个字节(即第一个扇区,引导区)读入内存的0x7c00处执行。程序的长度也被限制在了512字节大小,一个出入保护模式的程序就会超出。

为了突破512字节的限制,需要在这个扇区内放置一段引导程序,用于把后面的程序加载到内存中。这也是第一个扇区被称为引导区的原因。

下面的就是一段简单的加载程序,reset软驱后从LABEL_SECTOR2开始读入17个扇区。加上0号扇区(引导区),一共是18个扇区,也就是第一个磁道。这是因为13号中断不能跨磁道读取,所以这里只读入第一个磁道剩下的17个扇区。

    ; reset floppy

    xor ah, ah

    xor dl,  dl

    int 13h

   

    ; read sector 2 to memory

    mov ah, 03h

    mov al, 11h

    mov ch, 00h

    mov cl, 03h

    mov dh, 00h

    mov dl, 00h

    mov bx, 0100h

    int 13h

下面的程序用于保证除引导程序外的所有程序都在引导区之外,完成把磁盘内容拷贝入memory的操作后,就会跳入LABEL_SECTOR2。

       jmp LABEL_SECTOR2

 

; fill boot sector

    times 510 - ($ - $$) db 1

    dw 0xaa55

 

; Sector 2

LABEL_SECTOR2:

下面是完整的程序代码

       org 07c00h

    jmp LABEL_BEGIN

 

LABEL_BEGIN:

    mov ax, cs

    mov ds, ax

    mov ss, ax

    mov es, ax

   

    ; reset floppy

    xor ah, ah

    xor dl,  dl

    int 13h

   

    ; read sector 2

    mov ah, 02h

    mov al, 01h

    mov ch, 00h

    mov cl, 02h

    mov dh, 00h

    mov dl, 00h

    mov bx, LABEL_SECTOR2

    int 13h

 

       jmp LABEL_SECTOR2

 

; fill boot sector

    uutimes 510 - ($ - $$) db 0

    dw 0xaa55

 

; Sector 2 jmp into loader

;      org 0100h

LABEL_SECTOR2:

       mov ax, cs

       mov ds, ax

       mov es, ax

       call  DispStr                 ;
调用显示字符串例程

       jmp  $                   ;
无限循环

DispStr:

       mov ax, BootMessage

       mov bp, ax                   ; ES:BP =
串地址

       mov cx, 16                   ; CX =
串长度

       mov ax, 01301h            ; AH = 13,  AL = 01h

       mov bx, 000ch              ;
页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)

       mov dl, 0

       int   10h                ; 10h
号中断

       ret

BootMessage:         db    "Hello, OS world!"

 

; fill floppy

    times (2 * 18 * 80 * 512 - ($ - $$)) db 0

 

 

软盘,INT 13H和IMG文件

1:软盘

 软盘是以扇区为基本单位来进行操作的,每扇区512字节,共2880个扇区,空间大小为1.44M.

 这2880个扇区又可以用(磁头、磁道、扇区)这三个参数来描述。我们简单记(磁头、磁道、扇区)为(x,y,z),那么

  X的取值范围是:0—1

  Y的取值范围是:0--79

  Z的取值范围是:1—18

也就是说软盘有2磁头、每磁头有80磁道、每磁道有18扇区,共2880个扇区(2880=2*80*18)。

如果把2880个扇区从0开始编号,一直到2879结束,那么

编号为i的扇区和(X,Y,Z)的换算公式为:i=80*18*x+18*y+z-1

2:INT 13H

  INT13H是磁盘的BOIS中断,对于读写扇区操作,中断的完整调用参数如下:

  AL=扇区数    

  AH=中断子功能号     ;2=读扇区,3=写扇区

  CL的6,7位,CH = 磁道号  ;每磁头最多可以有2^10=1024个磁道

  CL的低6位   =  扇区号  ;每磁道最多可以有2^6=64个扇区

  DH = 磁头号     ;最多可以有256个磁头

  DL = 驱动器号     ;0=软盘,80H=硬盘

  ES:BX=数据缓冲区的地址

其他:

1).

  对于软盘来说,实际的INT 13H中DL=0,而CL,CH,DH的取值范围也不可能取到上面的数值,根据1:中的数据,有

CL的取值范围是:1--18

CH的取值范围是:0-79

DH 的取值范围是:0—1

如果软盘的CL,CH,DH取值超过了范围,中断调用出错。

AL的取值也不是任意的,一次调用INT13H进行读写扇区的只可以在一个磁道内的扇区进行。如果超出了一个磁道,必须要更新INT13H的中寄存器,重复调用INT13H。

对于软盘来说 AL必须要小于19-CL。

下面是一个从软盘的(x,y,z)扇区中连续读出n个扇区的内容到缓冲区BUFFER中的代码片段( 代码使用了一些80386的指令和MASM6.0才支持的伪指令):

 MOV  AX,DS

 MOV  ES,AX

MOV  BX,OFFSET

MOV   BP,  n 

MOV CL,z

MOV CH,y

MOV DL,0 

MOV DH,x

.WHILE  BP  ;bp记录的是还没有进行读操作的扇区数量

;WHILE伪指令,和高级语言的WHILE一样理解就可以了,下面的。IF也一样

 ;对AL的取值进行计算

 MOV AL,19

    SUB AL,CL ;最多可以读19-CL个扇区

    XOR AH,AH

 .IF AX>BP  

      MOV AX,BP

   XOR BP,BP 

    .ELSE  

  SUB BP,AX ;更新BP

       .ENDIF    

  MOV AH,2

INT 13H   ;读扇区

 ;更新CL,CH,DH

MOV CL,1  

.IF  CH==79 

   INC DH

   XOR CH,CH

  .ELSE

      INC CH

  .ENDIF

  XOR AH,AH

  SHL AL,9 ;AX=AL*512,等于已经处理的字节数

     ;SHL AL,9和SHL AX,4都是80386+才支持的指令

        SHR AX,4   ;AX=AX/16

  ADD ES,AX  ;更新ES:BX,这里是更新了ES,也可以更新BX。

 .endw

2).

  INT13H只理论上最多处理 2^24个扇区*512字节/扇区=8G的磁盘空间,这对现在的硬盘来说,是远远不够的,于是后来对INT13H进行了扩展,用AH=42H、AH=43H分别对大硬盘进行操作,这里就不详细讨论了。

3:IMG文件

 IMG文件是软盘的镜像文件,文件大小也是1.44M,它和软盘是一种线性的对应关系。

软盘上一个编号为(x,y,z)的512字节的扇区,对应IMG文件中的以2400H*x+4800H*y+(z-1)*512为基址的512个字节(注意,2400H,4800H是16进制数)。

IMG文件的字节和软盘的扇区对应关系也可以如下图所示(注意那些是十进制,那些是16进制)。

 

对于扩展INT 13中断,参数如下:

中断号 功能                       调用寄存器 返回寄存器       备注

INT 13

AH=41H 检测扩展中断功能是否安装 AH = 41h

                 BX=55AAh

                DL = 驱动器号(80h到FFH)

                 失败:AH=1

                 CF置位

                 成功:AH=版本号

                 CF=0 BX=AA55H 

INT 13

AH=42H 磁盘扩展读操作              AH = 42H

                    DL = 驱动器号

                    DS:SI=指向LBA地址包的指针 失败:AH=错误号

                    CF置位

                    成功:AH=0

                    CF=0

  地址包定义:

                    偏移 大小 描述

                    00H 字节 地址包大小

                    01H 字节 保留(为0)

                    02H 字     传输包个数

                    04H 双字 指向数据指针

                    08H 4字     起始地址

                    其中LBA=((柱面*磁头/柱面+磁头)*扇区/柱面)+扇区-1

INT 13

AH=43H 磁盘扩展写操作               AH=43H

                         AL=写标志

                      DL = 驱动器号

                      DS:SI=指向LBA地址包的指针 失败:AH=错误号

                                     CF置位

                               成功:AH=0

                                                     CF =0 同上

INT 13

AH=48H 获取磁盘参数 AH=48H

                                     DL=驱动器号

                                      DS:SI=指向保存参数缓冲区的指针

                                      
失败:AH=错误号

                           CF置位

                                      
成功:AH=0

                           CF=0

 参数缓冲区定义:

                    偏移 大小 描述

                    00H 字 缓冲区大小

                    02H 字 信息标志位

                    04H 双字 物理柱面数

                    08H 双字 物理磁头数

                    0CH 双字 物理每柱扇区数

                    10H 4字 扇区总数

                    18H 字 每扇区字节数

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控制器手册进行设置。不看也可以,注释写得很清楚,拿过来用就是了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: