程序 6‑2 linux/boot/setup.S


  1 !

  2 !       setup.s         (C) 1991 Linus Torvalds

  3 !

  4 ! setup.s is responsible for getting the system data from the BIOS,

  5 ! and putting them into the appropriate places in system memory.

  6 ! both setup.s and system has been loaded by the bootblock.

  7 !

  8 ! This code asks the bios for memory/disk/other parameters, and

  9 ! puts them in a "safe" place: 0x90000-0x901FF, ie where the

 10 ! boot-block used to be. It is then up to the protected mode

 11 ! system to read them from there before the area is overwritten

 12 ! for buffer-blocks.

 13 !

    ! setup.s负责从BIOS中获取系统数据,并将这些数据放到系统内存的适当

    ! 地方。此时setup.ssystem已经由bootsect引导块加载到内存中。

    !

    ! 这段代码询问bios有关内存/磁盘/其他参数,并将这些参数放到一个

    ! “安全的”地方:0x90000-0x901FF,也即原来bootsect代码块曾经在

    ! 的地方,然后在被缓冲块覆盖掉之前由保护模式的system读取。

 14

 15 ! NOTE! These had better be the same as in bootsect.s!

    ! 以下这些参数最好和bootsect.s中的相同!

 16 #include <linux/config.h>

    ! config.h中定义了DEF_INITSEG = 0x9000DEF_SYSSEG = 0x1000DEF_SETUPSEG = 0x9020

 17

 18 INITSEG  = DEF_INITSEG  ! we move boot here - out of the way ! 原来bootsect所处的段。

 19 SYSSEG   = DEF_SYSSEG   ! system loaded at 0x10000 (65536).  ! system0x10000处。

 20 SETUPSEG = DEF_SETUPSEG ! this is the current segment        ! 本程序所在的段地址。

 21

 22 .globl begtext, begdata, begbss, endtext, enddata, endbss

 23 .text

 24 begtext:

 25 .data

 26 begdata:

 27 .bss

 28 begbss:

 29 .text

 30

 31 entry start

 32 start:

 33

 34 ! ok, the read went well so we get current cursor position and save it for

 35 ! posterity.

    ! ok,整个读磁盘过程都正常,现在将光标位置保存以备今后使用(相关代码在59--62行)。

 36

    ! 下句将ds置成INITSEG(0x9000)。这已经在bootsect程序中设置过,但是现在是setup程序,

    ! Linus觉得需要再重新设置一下。

 37         mov     ax,#INITSEG

 38         mov     ds,ax

 39

 40 ! Get memory size (extended mem, kB)

    ! 取扩展内存的大小值(KB)。

    ! 利用BIOS中断0x15 功能号ah = 0x88 取系统所含扩展内存大小并保存在内存0x90002处。

    ! 返回:ax = 0x1000001M)处开始的扩展内存大小(KB)。若出错则CF置位,ax = 出错码。

 41

 42         mov     ah,#0x88

 43         int     0x15

 44         mov     [2],ax             ! 将扩展内存数值存在0x90002处(1个字)。

 45

 46 ! check for EGA/VGA and some config parameters

    ! 检查显示方式(EGA/VGA)并取参数。

    ! 调用BIOS中断0x10,附加功能选择方式信息。功能号:ah = 0x12bl = 0x10

    ! 返回:bh =显示状态。0x00 -彩色模式,I/O端口=0x3dX0x01 -单色模式,I/O端口=0x3bX

    ! bl = 安装的显示内存。0x00 - 64k0x01 - 128k0x02 - 192k0x03 = 256k

    ! cx = 显示卡特性参数(参见程序后对BIOS视频中断0x10的说明)

 47

 48         mov     ah,#0x12

 49         mov     bl,#0x10

 50         int     0x10

 51         mov     [8],ax           ! 0x90008 = ??

 52         mov     [10],bx          ! 0x9000A =安装的显示内存;0x9000B=显示状态(/单色)

 53         mov     [12],cx          ! 0x9000C =显示卡特性参数。

    ! 检测屏幕当前行列值。若显示卡是VGA卡时则请求用户选择显示行列值,并保存到0x9000E处。

 54         mov     ax,#0x5019       ! ax中预置屏幕默认行列值(ah = 80列;al=25行)。

 55         cmp     bl,#0x10         ! 若中断返回bl值为0x10,则表示不是VGA显示卡,跳转。

 56         je      novga

 57         call    chsvga           ! 检测显示卡厂家和类型,修改显示行列值(第215行)。

 58 novga:  mov     [14],ax          ! 保存屏幕当前行列值(0x9000E0x9000F)。

 

    ! 这段代码使用BIOS中断取屏幕当前光标位置(列、行),并保存在内存0x90000处(2字节)。

    ! 控制台初始化程序会到此处读取该值。

    ! BIOS中断0x10功能号 ah = 0x03,读光标位置。

    ! 输入:bh = 页号

    ! 返回:ch = 扫描开始线;cl = 扫描结束线;dh = 行号(0x00顶端)dl = 列号(0x00最左边)

 59         mov     ah,#0x03         ! read cursor pos

 60         xor     bh,bh

 61         int     0x10             ! save it in known place, con_init fetches

 62         mov     [0],dx           ! it from 0x90000.

 63        

 64 ! Get video-card data:

    ! 下面这段用于取显示卡当前显示模式。

    ! 调用BIOS中断0x10,功能号 ah = 0x0f

    ! 返回:ah = 字符列数;al = 显示模式;bh = 当前显示页。

    ! 0x90004(1)存放当前页;0x90006存放显示模式;0x90007存放字符列数。

 65        

 66         mov     ah,#0x0f

 67         int     0x10

 68         mov     [4],bx          ! bh = display page

 69         mov     [6],ax          ! al = video mode, ah = window width

 70

 71 ! Get hd0 data

    ! 取第一个硬盘的信息(复制硬盘参数表)。

    ! 1个硬盘参数表的首地址竟然是中断向量0x41的向量值!而第2个硬盘参数表紧接在第1

    ! 表的后面,中断向量0x46的向量值也指向第2个硬盘的参数表首址。表的长度是16个字节。

    ! 下面两段程序分别复制ROM BIOS中有关两个硬盘的参数表,0x90080处存放第1个硬盘的表,

    ! 0x90090处存放第2个硬盘的表。

 72

    ! 75行语句从内存指定位置处读取一个长指针值并放入dssi寄存器中。ds中放段地址,

    ! si是段内偏移地址。这里是把内存地址4 * 0x41= 0x104)处保存的4个字节读出。这4

    ! 节即是硬盘参数表所处位置的段和偏移值。

 73         mov     ax,#0x0000

 74         mov     ds,ax

 75         lds     si,[4*0x41]        ! 取中断向量0x41的值,即hd0参数表的地址èds:si

 76         mov     ax,#INITSEG

 77         mov     es,ax

 78         mov     di,#0x0080         ! 传输的目的地址: 0x9000:0x0080 è es:di

 79         mov     cx,#0x10           ! 共传输16字节。

 80         rep

 81         movsb

 82

 83 ! Get hd1 data

 84

 85         mov     ax,#0x0000

 86         mov     ds,ax

 87         lds     si,[4*0x46]        ! 取中断向量0x46的值,即hd1参数表的地址èds:si

 88         mov     ax,#INITSEG

 89         mov     es,ax

 90         mov     di,#0x0090         ! 传输的目的地址: 0x9000:0x0090 è es:di

 91         mov     cx,#0x10

 92         rep

 93         movsb

 94

 95 ! Check that there IS a hd1 :-)

    ! 检查系统是否有第2个硬盘。如果没有则把第2个表清零。

    ! 利用BIOS中断调用0x13的取盘类型功能,功能号 ah = 0x15

    ! 输入:dl = 驱动器号(0x8X是硬盘:0x80指第1个硬盘,0x812个硬盘)

    ! 输出:ah = 类型码;00 - 没有这个盘,CF置位;01 - 是软驱,没有change-line支持;

    !                    02 - 是软驱(或其他可移动设备),有change-line支持; 03 - 是硬盘。

 96

 97         mov     ax,#0x01500

 98         mov     dl,#0x81

 99         int     0x13

100         jc      no_disk1

101         cmp     ah,#3              ! 是硬盘吗?(类型 = 3 )

102         je      is_disk1

103 no_disk1:

104         mov     ax,#INITSEG        ! 2个硬盘不存在,则对第2个硬盘表清零。

105         mov     es,ax

106         mov     di,#0x0090

107         mov     cx,#0x10

108         mov     ax,#0x00

109         rep

110         stosb

111 is_disk1:

112

113 ! now we want to move to protected mode ...

    ! 现在我们要进入保护模式中了...

114

115         cli                     ! no interrupts allowed !     ! 从此开始不允许中断。

116

117 ! first we move the system to it's rightful place

    ! 首先我们将system模块移到正确的位置。

    ! bootsect引导程序会把 system 模块读入到内存 0x1000064KB)开始的位置。由于当时假设

    ! system模块最大长度不会超过0x80000512KB),即其末端不会超过内存地址0x90000,所以

    ! bootsect会把自己移动到0x90000开始的地方,并把setup加载到它的后面。下面这段程序的

    ! 用途是再把整个system模块移动到 0x00000位置,即把从 0x100000x8ffff 的内存数据块

    ! 512KB)整块地向内存低端移动了0x1000064KB)的位置。

118

119         mov     ax,#0x0000

120         cld                     ! 'direction'=0, movs moves forward

121 do_move:

122         mov     es,ax           ! destination segment ! es:di是目的地址(初始为0x0:0x0)

123         add     ax,#0x1000

124         cmp     ax,#0x9000      ! 已经把最后一段(从0x8000段开始的64KB)代码移动完?

125         jz      end_move        ! 是,则跳转。

126         mov     ds,ax           ! source segment  ! ds:si是源地址(初始为0x1000:0x0)

127         sub     di,di

128         sub     si,si

129         mov     cx,#0x8000      ! 移动0x8000字(64KB字节)。

130         rep

131         movsw

132         jmp     do_move

133

134 ! then we load the segment descriptors

    ! 此后,我们加载段描述符。

    ! 从这里开始会遇到32位保护模式的操作,因此需要Intel 32位保护模式编程方面的知识了,

    ! 有关这方面的信息请查阅列表后的简单介绍或附录中的详细说明。这里仅作概要说明。在进入

    ! 保护模式中运行之前,我们需要首先设置好需要使用的段描述符表。这里需要设置全局描述符

    ! 表和中断描述符表。

    !

    ! 下面指令lidt用于加载中断描述符表(IDT)寄存器。它的操作数(idt_48)有6字节。前2

    ! 字节(字节0-1)是描述符表的字节长度值;后4字节(字节2-5)是描述符表的32位线性基

    ! 地址,其形式参见下面218--220行和222--224行说明。中断描述符表中的每一个8字节表项

    ! 指出发生中断时需要调用的代码信息。与中断向量有些相似,但要包含更多的信息。

    !

    ! lgdt指令用于加载全局描述符表(GDT)寄存器,其操作数格式与lidt指令的相同。全局描述

    ! 符表中的每个描述符项(8字节)描述了保护模式下数据段和代码段(块)的信息。 其中包括

    ! 段的最大长度限制(16位)、段的线性地址基址(32位)、段的特权级、段是否在内存、读写

    ! 许可权以及其他一些保护模式运行的标志。参见后面205--216行。

135

136 end_move:

137         mov     ax,#SETUPSEG    ! right, forgot this at first. didn't work :-)

138         mov     ds,ax           ! ds指向本程序(setup)段。

139         lidt    idt_48          ! load idt with 0,0                  ! 加载IDT寄存器。

140         lgdt    gdt_48          ! load gdt with whatever appropriate ! 加载GDT寄存器。

141

142 ! that was painless, now we enable A20

    ! 以上的操作很简单,现在我们开启A20地址线。

    ! 为了能够访问和使用1MB以上的物理内存,我们需要首先开启A20地址线。参见本程序列表后

    ! 有关A20信号线的说明。关于所涉及的一些端口和命令,可参考kernel/chr_drv/keyboard.S

    ! 程序后对键盘接口的说明。至于机器是否真正开启了A20地址线,我们还需要在进入保护模式

    ! 之后(能访问1MB以上内存之后)在测试一下。这个工作放在了head.S程序中(32--36行)。

143

144         call    empty_8042              ! 测试8042状态寄存器,等待输入缓冲器空。

                                            ! 只有当输入缓冲器为空时才可以对其执行写命令。

145         mov     al,#0xD1                ! command write ! 0xD1命令码-表示要写数据到

146         out     #0x64,al                ! 8042P2端口。P2端口位1用于A20线的选通。

147         call    empty_8042              ! 等待输入缓冲器空,看命令是否被接受。

148         mov     al,#0xDF                ! A20 on        ! 选通A20地址线的参数。

149         out     #0x60,al                ! 数据要写到0x60口。

150         call    empty_8042              ! 若此时输入缓冲器为空,则表示A20线已经选通。

151

152 ! well, that went ok, I hope. Now we have to reprogram the interrupts :-(

153 ! we put them right after the intel-reserved hardware interrupts, at

154 ! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really

155 ! messed this up with the original PC, and they haven't been able to

156 ! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,

157 ! which is used for the internal hardware interrupts as well. We just

158 ! have to reprogram the 8259's, and it isn't fun.

    !

    ! 希望以上一切正常。现在我们必须重新对中断进行编程 :-( 我们将它们放在正好

    ! 处于Intel保留的硬件中断后面,即int 0x20--0x2F。在那里它们不会引起冲突。

    ! 不幸的是IBM在原PC机中搞糟了,以后也没有纠正过来。所以PCBIOS把中断

    ! 放在了0x08--0x0f,这些中断也被用于内部硬件中断。所以我们就必须重新对8259

    ! 中断控制器进行编程,这一点都没意思。

    !

    ! PC机使用28259A芯片,关于对可编程控制器8259A芯片的编程方法请参见本程序后的介绍。

    ! 162行上定义的两个字(0x00eb)是直接使用机器码表示的两条相对跳转指令,起延时作用。

    ! 0xeb是直接近跳转指令的操作码,带1个字节的相对位移值。因此跳转范围是-127127CPU

    ! 通过把这个相对位移值加到EIP寄存器中就形成一个新的有效地址。此时EIP指向下一条被执行

    ! 的指令。执行时所花费的CPU时钟周期数是710个。0x00eb 表示跳转值是0的一条指令,因

    ! 此还是直接执行下一条指令。这两条指令共可提供14--20CPU时钟周期的延迟时间。在as86

    ! 中没有表示相应指令的助记符,因此Linussetup.s等一些汇编程序中就直接使用机器码来表

    ! 示这种指令。另外,每个空操作指令NOP的时钟周期数是3个,因此若要达到相同的延迟效果就

    ! 需要67NOP指令。

159

    ! 8259芯片主片端口是0x20-0x21,从片端口是0xA0-0xA1。输出值0x11表示初始化命令开始,

    ! 它是ICW1命令字,表示边沿触发、多片8259级连、最后要发送ICW4命令字。

160         mov     al,#0x11                ! initialization sequence

161         out     #0x20,al                ! send it to 8259A-1  ! 发送到8259A主芯片。

162         .word   0x00eb,0x00eb           ! jmp $+2, jmp $+2    ! '$'表示当前指令的地址,

163         out     #0xA0,al                ! and to 8259A-2      ! 再发送到8259A从芯片。

164         .word   0x00eb,0x00eb

    ! Linux系统硬件中断号被设置成从0x20开始。参见表3-2:硬件中断请求信号与中断号对应表。

165         mov     al,#0x20                ! start of hardware int's (0x20)

166         out     #0x21,al                ! 送主芯片ICW2命令字,设置起始中断号,要送奇端口。

167         .word   0x00eb,0x00eb

168         mov     al,#0x28                ! start of hardware int's 2 (0x28)

169         out     #0xA1,al                ! 送从芯片ICW2命令字,从芯片的起始中断号。

170         .word   0x00eb,0x00eb

171         mov     al,#0x04                ! 8259-1 is master

172         out     #0x21,al                ! 送主芯片ICW3命令字,主芯片的IR2连从芯片INT

                                            !参见代码列表后的说明。

173         .word   0x00eb,0x00eb

174         mov     al,#0x02                ! 8259-2 is slave

175         out     #0xA1,al                ! 送从芯片ICW3命令字,表示从芯片的INT连到主芯

                                            ! 片的IR2引脚上。

176         .word   0x00eb,0x00eb

177         mov     al,#0x01                ! 8086 mode for both

178         out     #0x21,al                ! 送主芯片ICW4命令字。8086模式;普通EOI、非缓冲

                                            ! 方式,需发送指令来复位。初始化结束,芯片就绪。

179         .word   0x00eb,0x00eb

180         out     #0xA1,al                !送从芯片ICW4命令字,内容同上。

181         .word   0x00eb,0x00eb

182         mov     al,#0xFF                ! mask off all interrupts for now

183         out     #0x21,al                ! 屏蔽主芯片所有中断请求。

184         .word   0x00eb,0x00eb

185         out     #0xA1,al                !屏蔽从芯片所有中断请求。

186

187 ! well, that certainly wasn't fun :-(. Hopefully it works, and we don't

188 ! need no steenking BIOS anyway (except for the initial loading :-).

189 ! The BIOS-routine wants lots of unnecessary data, and it's less

190 ! "interesting" anyway. This is how REAL programmers do it.

191 !

192 ! Well, now's the time to actually move into protected mode. To make

193 ! things as simple as possible, we do no register set-up or anything,

194 ! we let the gnu-compiled 32-bit programs do that. We just jump to

195 ! absolute address 0x00000, in 32-bit protected mode.

    !

    ! 哼,上面这段编程当然没劲:-(,但希望这样能工作,而且我们也不再需要乏味的BIOS

    ! 了(除了初始加载:-)BIOS子程序要求很多不必要的数据,而且它一点都没趣。那是

    ! “真正”的程序员所做的事。

    !

    ! 好了,现在是真正开始进入保护模式的时候了。为了把事情做得尽量简单,我们并不对

    ! 寄存器内容进行任何设置。我们让gnu编译的32位程序去处理这些事。在进入32位保

    ! 护模式时我们仅是简单地跳转到绝对地址0x00000处。

196

    ! 下面设置并进入32位保护模式运行。首先加载机器状态字(lmsw-Load Machine Status Word)

    ! 也称控制寄存器CR0,其比特位01将导致CPU切换到保护模式,并且运行在特权级0中,即

    ! 当前特权级CPL=0。此时段寄存器仍然指向与实地址模式中相同的线性地址处(在实地址模式下

    ! 线性地址与物理内存地址相同)。在设置该比特位后,随后一条指令必须是一条段间跳转指令以

    ! 用于刷新CPU当前指令队列。因为CPU是在执行一条指令之前就已从内存读取该指令并对其进行

    ! 解码。然而在进入保护模式以后那些属于实模式的预先取得的指令信息就变得不再有效。而一条

    ! 段间跳转指令就会刷新CPU的当前指令队列,即丢弃这些无效信息。另外,在Intel公司的手册

    ! 上建议80386或以上CPU应该使用指令“mov cr0,ax”切换到保护模式。lmsw指令仅用于兼容以

    ! 前的286 CPU

 

197         mov     ax,#0x0001      ! protected mode (PE) bit        ! 保护模式比特位(PE)

198         lmsw    ax              ! This is it!                    ! 就这样加载机器状态字!

199         jmpi    0,8             ! jmp offset 0 of segment 8 (cs) ! 跳转至cs段偏移0处。

    ! 我们已经将system模块移动到0x00000开始的地方,所以上句中的偏移地址是0。而段值8已经

    ! 是保护模式下的段选择符了,用于选择描述符表和描述符表项以及所要求的特权级。段选择符长

    ! 度为16位(2字节);位0-1表示请求的特权级0--3,但Linux操作系统只用到两级:0级(内

    ! 核级)和3级(用户级);位2用于选择全局描述符表(0)还是局部描述符表(1);位3-15是描

    ! 述符表项的索引,指出选择第几项描述符。所以段选择符80b0000,0000,0000,1000)表示请求

    ! 特权级0、使用全局描述符表GDT中第2个段描述符项,该项指出代码的基地址是0(参见571行),

    ! 因此这里的跳转指令就会去执行system中的代码。另外,

200

201 ! This routine checks that the keyboard command queue is empty

202 ! No timeout is used - if this hangs there is something wrong with

203 ! the machine, and we probably couldn't proceed anyway.

    ! 下面这个子程序检查键盘命令队列是否为空。这里不使用超时方法 -

    ! 如果这里死机,则说明PC机有问题,我们就没有办法再处理下去了。

    !

    ! 只有当输入缓冲器为空时(键盘控制器状态寄存器位1 = 0)才可以对其执行写命令。

204 empty_8042:

205         .word   0x00eb,0x00eb

206         in      al,#0x64        ! 8042 status port         ! AT键盘控制器状态寄存器。

207         test    al,#2           ! is input buffer full?    ! 测试位1,输入缓冲器满?

208         jnz     empty_8042      ! yes - loop

209         ret

210

211 ! Routine trying to recognize type of SVGA-board present (if any)

212 ! and if it recognize one gives the choices of resolution it offers.

213 ! If one is found the resolution chosen is given by al,ah (rows,cols).

    ! 下面是用于识别SVGA显示卡(若有的话)的子程序。若识别出一块就向用户

    ! 提供选择分辨率的机会,并把分辨率放入寄存器alah(行、列)中返回。

    !

    ! 注意下面215--566行代码牵涉到众多显示卡端口信息,因此比较复杂。但由于这段代码与内核

    ! 运行关系不大,因此可以跳过不看。

    ! 下面首先显示588行上的msg1字符串("<回车键>查看存在的SVGA模式,或按任意键继续"),

    ! 然后循环读取键盘控制器输出缓冲器,等待用户按键。如果用户按下回车键就去检查系统具有

    ! SVGA模式,并在ALAH中返回最大行列值,否则设置默认值AL=25行、AH=80列并返回。

214

215 chsvga: cld

216         push    ds               ! 保存ds值。将在231行(或490492行)弹出。

217         push    cs               ! 把默认数据段设置成和代码段同一个段。

218         pop     ds

219         mov     ax,#0xc000

220         mov     es,ax            ! es 指向0xc000段。此处是VGA卡上的ROM BIOS区。

221         lea     si,msg1          ! ds:si指向msg1字符串。

222         call    prtstr           ! 显示以NULL结尾的msg1字符串。

223 nokey:  in      al,#0x60         ! 读取键盘控制器输出缓冲器(来自键盘的扫描码或命令)。

224         cmp     al,#0x82         ! 如果收到比0x82小的扫描码则是接通扫描码,因为0x82

225         jb      nokey            ! 最小断开扫描码值。小于0x82表示还没有按键松开。

226         cmp     al,#0xe0         ! 如果扫描码大于0xe0,表示收到的是扩展扫描码前缀。

227         ja      nokey

228         cmp     al,#0x9c         ! 如果断开扫描码是0x9c,表示用户按下/松开了回车键,

229         je      svga             ! 于是程序跳转去检查系统是否具有SVGA模式。

230         mov     ax,#0x5019       ! 否则把AX中返回行列值默认设置为AL=25行、AH=80列。

231         pop     ds

232         ret

    ! 下面根据VGA显示卡上的ROM BIOS指定位置处的特征数据串或者支持的特别功能来判断机器上

    ! 安装的是什么牌子的显示卡。本程序共支持10种显示卡的扩展功能。注意,此时程序已经在第

    ! 220行把es指向VGA卡上ROM BIOS所在的段0xc000(参见第2章)。

    ! 首先判断是不是ATI显示卡。我们把 ds:si指向595行上ATI显示卡特征数据串,并把es:si

    ! VGA BIOS中指定位置(偏移0x31)处。因为该特征串共有9个字符("761295520"),因此我

    ! 们循环比较这个特征串。如果相同则表示机器中的VGA卡是ATI牌子的,于是让ds:si指向该显

    ! 示卡可以设置的行列模式值dscati(第615行),让di指向ATI卡可设置的行列个数和模式,

    ! 并跳转到标号selmod438行)处进一步进行设置。

233 svga:   lea     si,idati         ! Check ATI 'clues'  ! 检查判断 ATI显示卡的数据。

234         mov     di,#0x31         ! 特征串从0xc000:0x0031开始。

235         mov     cx,#0x09         ! 特征串有9个字节。

236         repe

237         cmpsb

238         jne     noati            ! 若特征串不同则表示不是ATI显示卡。跳转继续检测卡。

239         lea     si,dscati        ! 如果9个字节都相同,表示系统中有一块ATI牌显示卡。

240         lea     di,moati         ! 于是si指向ATI卡具有的可选行列值,di指向可选个数

241         lea     cx,selmod        ! 和模式列表,然后跳转到selmod438行)处继续处理。

242         jmp     cx

 

    ! 现在来判断是不是Ahead牌子的显示卡。首先向EGA/VGA 图形索引寄存器0x3ce写入想访问的

    ! 主允许寄存器索引号0x0f,同时向0x3cf端口(此时对应主允许寄存器)写入开启扩展寄存器

    ! 标志值0x20。然后通过0x3cf端口读取主允许寄存器值,以检查是否可以设置开启扩展寄存器

    ! 标志。如果可以则说明是Ahead牌子的显示卡。注意word输出时alè端口nahè端口n+1

243 noati:  mov     ax,#0x200f       ! Check Ahead 'clues'

244         mov     dx,#0x3ce        ! 数据端口指向主允许寄存器(0x0fè0x3ce端口),

245         out     dx,ax            ! 并设置开启扩展寄存器标志(0x20è0x3cf端口)。

246         inc     dx               ! 然后再读取该寄存器,检查该标志是否被设置上。

247         in      al,dx

248         cmp     al,#0x20         ! 如果读取值是0x20,则表示是Ahead A显示卡。

249         je      isahed           ! 如果读取值是0x21,则表示是Ahead B显示卡。

250         cmp     al,#0x21         ! 否则说明不是Ahead显示卡,于是跳转继续检测其余卡。

251         jne     noahed

252 isahed: lea     si,dscahead      ! si 指向Ahead显示卡可选行列值表,di指向扩展模式个

253         lea     di,moahead       ! 数和扩展模式号列表。然后跳转到selmod438行)处继

254         lea     cx,selmod        ! 续处理。

255         jmp     cx

 

    ! 现在来检查是不是Chips & Tech生产的显示卡。通过端口0x3c30x940x46e8)设置VGA允许

    ! 寄存器的进入设置模式标志(位4),然后从端口0x104读取显示卡芯片集标识值。如果该标识值

    ! 0xA5,则说明是Chips & Tech生产的显示卡。

256 noahed: mov     dx,#0x3c3        ! Check Chips & Tech. 'clues'

257         in      al,dx            ! 0x3c3端口读取VGA允许寄存器值,添加上进入设置模式

258         or      al,#0x10         ! 标志(位4)后再写回。

259         out     dx,al

260         mov     dx,#0x104        ! 在设置模式时从全局标识端口0x104读取显示卡芯片标识值,

261         in      al,dx            ! 并暂时存放在bl寄存器中。

262         mov     bl,al

263         mov     dx,#0x3c3        ! 然后把0x3c3端口中的进入设置模式标志复位。

264         in      al,dx

265         and     al,#0xef

266         out     dx,al

267         cmp     bl,[idcandt]     ! 再把bl中标识值与位于idcandt处(第596行)的Chips &

268         jne     nocant           ! Tech的标识值0xA5作比较。如果不同则跳转比较下一种显卡。

269         lea     si,dsccandt      ! si指向这种显示卡的可选行列值表,di指向扩展模式个数

270         lea     di,mocandt       ! 和扩展模式号列表。然后跳转到selmod438行)进行设置

271         lea     cx,selmod        ! 显示模式的操作。

272         jmp     cx

 

    ! 现在检查是不是Cirrus显示卡。方法是使用CRT控制器索引号0x1f寄存器的内容来尝试禁止扩展

    ! 功能。该寄存器被称为鹰标(Eagle ID)寄存器,将其值高低半字节交换一下后写入端口0x3c4

    ! 引的6号(定序/扩展)寄存器应该会禁止Cirrus显示卡的扩展功能。如果不会则说明不是Cirrus

    ! 显示卡。因为从端口0x3d4索引的0x1f鹰标寄存器中读取的内容是鹰标值与0x0c索引号对应的显

    ! 存起始地址高字节寄存器内容异或操作之后的值,因此在读0x1f中内容之前我们需要先把显存起始

    ! 高字节寄存器内容保存后清零,并在检查后恢复之。另外,将没有交换过的Eagle ID值写到0x3c4

    ! 端口索引的6号定序/扩展寄存器会重新开启扩展功能。

273 nocant: mov     dx,#0x3d4        ! Check Cirrus 'clues'

274         mov     al,#0x0c         ! 首先向CRT控制寄存器的索引寄存器端口0x3d4写入要访问

275         out     dx,al            ! 的寄存器索引号0x0c(对应显存起始地址高字节寄存器),

276         inc     dx               ! 然后从0x3d5端口读入显存起始地址高字节并暂存在bl中,

277         in      al,dx            ! 再把显存起始地址高字节寄存器清零。

278         mov     bl,al

279         xor     al,al

280         out     dx,al

281         dec     dx               ! 接着向0x3d4端口输出索引0x1f,指出我们要在0x3d5端口

282         mov     al,#0x1f         ! 访问读取“Eagle ID”寄存器内容。

283         out     dx,al

284         inc     dx

285         in      al,dx            ! 0x3d5端口读取“Eagle ID”寄存器值,并暂存在bh中。

286         mov     bh,al            ! 然后把该值高低4比特互换位置存放到cl中。再左移8

287         xor     ah,ah            ! 后放入ch中,而cl中放入数值6

288         shl     al,#4

289         mov     cx,ax

290         mov     al,bh

291         shr     al,#4

292         add     cx,ax

293         shl     cx,#8

294         add     cx,#6            ! 最后把cx值存放入ax中。此时ah中是换位后的“Eagle

295         mov     ax,cx            ! ID”值,al中是索引号6,对应定序/扩展寄存器。把ah

296         mov     dx,#0x3c4        ! 写到0x3c4端口索引的定序/扩展寄存器应该会导致Cirrus

297         out     dx,ax            ! 显示卡禁止扩展功能。

298         inc     dx

299         in      al,dx            ! 如果扩展功能真的被禁止,那么此时读入的值应该为0

300         and     al,al            ! 如果不为0则表示不是Cirrus显示卡,跳转继续检查其他卡。

301         jnz     nocirr

302         mov     al,bh            ! Cirrus显示卡,则利用第286行保存在bh中的“Eagle

303         out     dx,al            ! ID”原值再重新开启Cirrus卡扩展功能。此时读取的返回

304         in      al,dx            ! 值应该为1。若不是,则仍然说明不是Cirrus显示卡。

305         cmp     al,#0x01

306         jne     nocirr

307         call    rst3d4           ! 恢复CRT控制器的显示起始地址高字节寄存器内容。

308         lea     si,dsccirrus     ! si指向Cirrus显示卡的可选行列值,di指向扩展模式个数

309         lea     di,mocirrus      ! 和对应模式号。然后跳转到selmod处去选择显示模式。

310         lea     cx,selmod

311         jmp     cx

    ! 该子程序利用保存在bl中的值(第278行)恢复CRT控制器的显示起始地址高字节寄存器内容。

312 rst3d4: mov     dx,#0x3d4

313         mov     al,bl

314         xor     ah,ah

315         shl     ax,#8

316         add     ax,#0x0c

317         out     dx,ax            ! 注意,这是word输出!! al è0x3d4ah è0x3d5

318         ret    

 

    ! 现在检查系统中是不是Everex显示卡。方法是利用中断int 0x10功能0x70ax =0x7000

    ! bx=0x0000)调用Everex的扩展视频BIOS功能。对于Everes类型显示卡,该中断调用应该

    ! 会返回模拟状态,即有以下返回信息:

    ! al = 0x70,若是基于TridentEverex显示卡;

    ! cl = 显示器类型:00-单色;01-CGA02-EGA03-数字多频;04-PS/205-IBM 851406-SVGA

    ! ch = 属性:位7-600-256K01-512K10-1MB11-2MB;位4-开启VGA保护;位0-6845模拟。

    ! dx = 板卡型号:位15-4:板类型标识号;位3-0:板修正标识号。

    !      0x2360-Ultragraphics II0x6200-Vision VGA0x6730-EVGA0x6780-Viewpoint

    ! di = BCD码表示的视频BIOS版本号。

319 nocirr: call    rst3d4           ! Check Everex 'clues'

320         mov     ax,#0x7000       ! 设置ax = 0x7000, bx=0x0000,调用int 0x10

321         xor     bx,bx

322         int     0x10

323         cmp     al,#0x70         ! 对于Everes显示卡,al中应该返回值0x70

324         jne     noevrx

325         shr     dx,#4            ! 忽律板修正号(位3-0)。

326         cmp     dx,#0x678        ! 板类型号是0x678表示是一块Trident显示卡,则跳转。

327         je      istrid

328         cmp     dx,#0x236        ! 板类型号是0x236表示是一块Trident显示卡,则跳转。

329         je      istrid

330         lea     si,dsceverex     ! si指向Everex显示卡的可选行列值表,让di指向扩展

331         lea     di,moeverex      ! 模式个数和模式号列表。然后跳转到selmod去执行选择

332         lea     cx,selmod        ! 显示模式的操作。

333         jmp     cx

334 istrid: lea     cx,ev2tri        ! Trident类型的Everex显示卡,则跳转到ev2tri处理。

335         jmp     cx

 

    ! 现在检查是不是Genoa显示卡。方式是检查其视频BIOS中的特征数字串(0x770x000x66

    ! 0x99)。注意,此时es已经在第220行被设置成指向VGA卡上ROM BIOS所在的段0xc000

336 noevrx: lea     si,idgenoa       ! Check Genoa 'clues'

337         xor     ax,ax            ! ds:si指向第597行上的特征数字串。

338         seg es

339         mov     al,[0x37]        ! VGA卡上BIOS0x37处的指针(它指向特征串)。

340         mov     di,ax            ! 因此此时es:di指向特征数字串开始处。

341         mov     cx,#0x04

342         dec     si

343         dec     di

344 l1:     inc     si               ! 然后循环比较这4个字节的特征数字串。

345         inc     di

346         mov     al,(si)

347         seg es

348         and     al,(di)

349         cmp     al,(si)

350         loope   l1

351         cmp     cx,#0x00         ! 如果特征数字串完全相同,则表示是Genoa显示卡,

352         jne     nogen            ! 否则跳转去检查其他类型的显示卡。

353         lea     si,dscgenoa      ! si指向Genoa显示卡的可选行列值表,让di指向扩展

354         lea     di,mogenoa       ! 模式个数和模式号列表。然后跳转到selmod去执行选择

355         lea     cx,selmod        ! 显示模式的操作。

356         jmp     cx

 

    ! 现在检查是不是Paradise显示卡。同样是采用比较显示卡上BIOS中特征串(“VGA=”)的方式。

357 nogen:  lea     si,idparadise    ! Check Paradise 'clues'

358         mov     di,#0x7d         ! es:di指向VGA ROM BIOS0xc000:0x007d处,该处应该有

359         mov     cx,#0x04         ! 4个字符“VGA=”。

360         repe

361         cmpsb

362         jne     nopara           ! 若有不同的字符,表示不是Paradise显示卡,于是跳转。

363         lea     si,dscparadise   ! 否则让si指向Paradise显示卡的可选行列值表,让di

364         lea     di,moparadise    ! 向扩展模式个数和模式号列表。然后跳转到selmod处去选

365         lea     cx,selmod        ! 择想要使用的显示模式。

366         jmp     cx

 

    ! 现在检查是不是TridentTVGA)显示卡。TVGA显示卡扩充的模式控制寄存器10x3c4端口索引

    ! 0x0e)的位3--064K内存页面个数值。这个字段值有一个特性:当写入时,我们需要首先把

    ! 值与0x02进行异或操作后再写入;当读取该值时则不需要执行异或操作,即异或前的值应该与写

    ! 入后再读取的值相同。下面代码就利用这个特性来检查是不是Trident显示卡。

367 nopara: mov     dx,#0x3c4        ! Check Trident 'clues'

368         mov     al,#0x0e         ! 首先在端口0x3c4输出索引号0x0e,索引模式控制寄存器1

369         out     dx,al            ! 然后从0x3c5数据端口读入该寄存器原值,并暂存在ah中。

370         inc     dx

371         in      al,dx

372         xchg    ah,al

373         mov     al,#0x00         ! 然后我们向该寄存器写入0x00,再读取其值èal

374         out     dx,al            ! 写入0x00就相当于“原值”0x02异或0x02后的写入值,

375         in      al,dx            ! 因此若是Trident显示卡,则此后读入的值应该是0x02

376         xchg    al,ah            ! 交换后,al=原模式控制寄存器1的值,ah=最后读取的值。

    ! 下面语句右则英文注释是“真奇怪...书中并没有要求这样操作,但是这对我的Trident显示卡

    ! 起作用。如果不这样做,屏幕就会变模糊...”。这几行附带有英文注释的语句执行如下操作:

    ! 如果bl中原模式控制寄存器1的位1在置位状态的话就将其复位,否则就将位1置位。

    ! 实际上这几条语句就是对原模式控制寄存器1的值执行异或 0x02的操作,然后用结果值去设置

    ! (恢复)原寄存器值。

377         mov     bl,al            ! Strange thing ... in the book this wasn't

378         and     bl,#0x02         ! necessary but it worked on my card which

379         jz      setb2            ! is a trident. Without it the screen goes

380         and     al,#0xfd         ! blurred ...

381         jmp     clrb2            !

382 setb2:  or      al,#0x02         !

383 clrb2:  out     dx,al

384         and     ah,#0x0f         ! 375行最后读入值的页面个数字段(位3--0),如果

385         cmp     ah,#0x02         ! 该字段值等于0x02,则表示是Trident显示卡。

386         jne     notrid

387 ev2tri: lea     si,dsctrident    ! Trident显示卡,于是让si指向该显示卡的可选行列

388         lea     di,motrident     ! 值列表,让di指向对应扩展模式个数和模式号列表,然

389         lea     cx,selmod        ! 后跳转到selmod去执行模式选择操作。

390         jmp     cx

 

    ! 现在检查是不是Tseng显示卡(ET4000AXET4000/W32类)。方法是对0x3cd端口对应的段

    ! 选择(Segment Select)寄存器执行读写操作。该寄存器高4位(位7--4)是要进行读操作的

    ! 64KB段号(Bank number),低4位(位3--0)是指定要写的段号。如果指定段选择寄存器的

    ! 的值是 0x55(表示读、写第664KB段),那么对于Tseng显示卡来说,把该值写入寄存器

    ! 后再读出应该还是0x55

391 notrid: mov     dx,#0x3cd        ! Check Tseng 'clues'

392         in      al,dx            ! Could things be this simple ! :-)

393         mov     bl,al            ! 先从0x3cd端口读取段选择寄存器原值,并保存在bl中。

394         mov     al,#0x55         ! 然后我们向该寄存器中写入0x55。再读入并放在ah中。

395         out     dx,al

396         in      al,dx

397         mov     ah,al

398         mov     al,bl            ! 接着恢复该寄存器的原值。

399         out     dx,al

400         cmp     ah,#0x55         ! 如果读取的就是我们写入的值,则表明是Tseng显示卡。

401         jne     notsen

402         lea     si,dsctseng      ! 于是让si指向Tseng显示卡的可选行列值的列表,让di

403         lea     di,motseng       ! 指向对应扩展模式个数和模式号列表,然后跳转到selmod

404         lea     cx,selmod        ! 去执行模式选择操作。

405         jmp     cx

 

    ! 下面检查是不是Video7显示卡。端口0x3c2是混合输出寄存器写端口,而0x3cc是混合输出寄存

    ! 器读端口。该寄存器的位0是单色/彩色标志。如果为0则表示是单色,否则是彩色。判断是不是

    ! Video7显示卡的方式是利用这种显示卡的CRT控制扩展标识寄存器(索引号是0x1f)。该寄存器

    ! 的值实际上就是显存起始地址高字节寄存器(索引号0x0c)的内容和0xea进行异或操作后的值。

    ! 因此我们只要向显存起始地址高字节寄存器中写入一个特定值,然后从标识寄存器中读取标识值

    ! 进行判断即可。

    ! 通过对以上显示卡和这里Video7显示卡的检查分析,我们可知检查过程通常分为三个基本步骤。

    ! 首先读取并保存测试需要用到的寄存器原值,然后使用特定测试值进行写入和读出操作,最后恢

    ! 复原寄存器值并对检查结果作出判断。

406 notsen: mov     dx,#0x3cc        ! Check Video7 'clues'

407         in      al,dx

408         mov     dx,#0x3b4        ! 先设置dx为单色显示CRT控制索引寄存器端口号0x3b4

409         and     al,#0x01         ! 如果混合输出寄存器的位0等于0(单色)则直接跳转,

410         jz      even7            ! 否则dx设置为彩色显示CRT控制索引寄存器端口号0x3d4

411         mov     dx,#0x3d4

412 even7:  mov     al,#0x0c         ! 设置寄存器索引号为0x0c,对应显存起始地址高字节寄存器。

413         out     dx,al

414         inc     dx

415         in      al,dx            ! 读取显示内存起始地址高字节寄存器内容,并保存在bl中。

416         mov     bl,al

417         mov     al,#0x55         ! 然后在显存起始地址高字节寄存器中写入值0x55,再读取出来。

418         out     dx,al

419         in      al,dx

420         dec     dx               ! 然后通过CRTC索引寄存器端口0x3b40x3d4选择索引号是

421         mov     al,#0x1f         ! 0x1fVideo7显示卡标识寄存器。该寄存器内容实际上就是

422         out     dx,al            ! 显存起始地址高字节和0xea进行异或操作后的结果值。

423         inc     dx

424         in      al,dx            ! 读取Video7显示卡标识寄存器值,并保存在bh中。

425         mov     bh,al

426         dec     dx               ! 然后再选择显存起始地址高字节寄存器,恢复其原值。

427         mov     al,#0x0c

428         out     dx,al

429         inc     dx

430         mov     al,bl

431         out     dx,al

432         mov     al,#0x55         ! 随后我们来验证“Video7显示卡标识寄存器值就是显存起始

433         xor     al,#0xea         ! 地址高字节和0xea进行异或操作后的结果值。因此0x55

434         cmp     al,bh            ! 0xea进行异或操作的结果就应该等于标识寄存器的测试值。

435         jne     novid7           ! 若不是Video7显示卡,则设置默认显示行列值(492行)。

436         lea     si,dscvideo7     ! Video7显示卡,于是让si指向该显示卡行列值表,让di

437         lea     di,movideo7      ! 指向扩展模式个数和模式号列表。

 

    ! 下面根据上述代码判断出的显示卡类型以及取得的相关扩展模式信息(si指向的行列值列表;di

    ! 指向扩展模式个数和模式号列表),提示用户选择可用的显示模式,并设置成相应显示模式。最后

    ! 子程序返回系统当前设置的屏幕行列值(ah = 列数;al=行数)。例如,如果系统中是ATI显示卡,

    ! 那么屏幕上会显示以下信息:

    ! Mode:  COLSxROWS:

    ! 0.     132 x 25

    ! 1.     132 x 44

    ! Choose mode by pressing the corresponding number.

    !

    ! 这段程序首先在屏幕上显示NULL结尾的字符串信息“Mode:  COLSxROWS:”。

438 selmod: push    si

439         lea     si,msg2

440         call    prtstr

441         xor     cx,cx

442         mov     cl,(di)          ! 此时cl中是检查出的显示卡的扩展模式个数。

443         pop     si

444         push    si

445         push    cx

    ! 然后并在每一行上显示出当前显示卡可选择的扩展模式行列值,供用户选用。

446 tbl:    pop     bx               ! bx = 显示卡的扩展模式总个数。

447         push    bx

448         mov     al,bl

449         sub     al,cl

450         call    dprnt            ! 以十进制格式显示al中的值。

451         call    spcing           ! 显示一个点再空4个空格。

452         lodsw                    ! ax中加载si指向的行列值,随后si指向下一个word值。

453         xchg    al,ah            ! 交换位置后al = 列数。

454         call    dprnt            ! 显示列数;

455         xchg    ah,al            ! 此时al中是行数值。

456         push    ax

457         mov     al,#0x78         ! 显示一个小“x”,即乘号。

458         call    prnt1

459         pop     ax               ! 此时al中是行数值。

460         call    dprnt            ! 显示行数。

461         call    docr             ! 回车换行。

462         loop    tbl              ! 再显示下一个行列值。cx中扩展模式计数值递减1

    ! 在扩展模式行列值都显示之后,显示“Choose mode by pressing the corresponding number.”,

    ! 然后从键盘口读取用户按键的扫描码,根据该扫描码确定用户选择的行列值模式号,并利用ROM

    ! BIOS的显示中断int 0x10功能0x00来设置相应的显示模式。

    ! 468行的模式个数值+0x80”是所按数字键-1的松开扫描码。对于0--9数字键,它们的松开

    ! 扫描码分别是:0 - 0x8B1 - 0x822 - 0x833 - 0x844 - 0x85

    !               5 - 0x866 - 0x877 - 0x888 - 0x899 - 0x8A

    ! 因此,如果读取的键盘松开扫描码小于0x82就表示不是数字键;如果扫描码等于0x8B则表示用户

    ! 按下数字0键。

463         pop     cx               ! cl中是显示卡扩展模式总个数值。

464         call    docr

465         lea     si,msg3          ! 显示“请按相应数字键来选择模式。”

466         call    prtstr

467         pop     si               ! 弹出原行列值指针(指向显示卡行列值表开始处)。

468         add     cl,#0x80         ! cl + 0x80 = 对应“数字键-1”的松开扫描码。

469 nonum:  in      al,#0x60         ! Quick and dirty...

470         cmp     al,#0x82         ! 若键盘松开扫描码小于0x82则表示不是数字键,忽律该键。

471         jb      nonum

472         cmp     al,#0x8b         ! 若键盘松开扫描码等于0x8b,表示按下了数字键0

473         je      zero

474         cmp     al,cl            ! 若扫描码大于扩展模式个数值对应的最大扫描码值,表示

475         ja      nonum            ! 键入的值超过范围或不是数字键的松开扫描码。否则表示

476         jmp     nozero           ! 用户按下并松开了一个非0数字按键。

    ! 下面把松开扫描码转换成对应的数字按键值,然后利用该值从模式个数和模式号列表中选择对应的

    ! 的模式号。接着调用机器ROM BIOS中断int 0x10功能0把屏幕设置成模式号指定的模式。最后再

    ! 利用模式号从显示卡行列值表中选择并在ax中返回对应的行列值。

477 zero:   sub     al,#0x0a         ! al = 0x8b - 0x0a = 0x81

478 nozero: sub     al,#0x80         ! 再减去0x80就可以得到用户选择了第几个模式。

479         dec     al               ! 0起计数。

480         xor     ah,ah            ! int 0x10显示功能号=0(设置显示模式)。

481         add     di,ax

482         inc     di               ! di指向对应的模式号(跳过第1个模式个数字节值)。

483         push    ax

484         mov     al,(di)          ! 取模式号èal中,并调用系统BIOS显示中断功能0

485         int     0x10

486         pop     ax

487         shl     ax,#1            ! 模式号乘2,转换成为行列值表中对应值的指针。

488         add     si,ax

489         lodsw                    ! 取对应行列值到ax中(ah = 列数,al = 行数)。

490         pop     ds               ! 恢复第216行保存的ds原值。在ax中返回当前显示行列值。

491         ret

 

    ! 若都不是上面检测的显示卡,那么我们只好采用默认的80 x 25 的标准行列值。

492 novid7: pop     ds               ! Here could be code to support standard 80x50,80x30

493         mov     ax,#0x5019     

494         ret

495

496 ! Routine that 'tabs' to next col.

    ! 光标移动到下一制表位的子程序。

497

    ! 显示一个点字符'.'4个空格。

498 spcing: mov     al,#0x2e         ! 显示一个点字符'.'

499         call    prnt1

500         mov     al,#0x20

501         call    prnt1  

502         mov     al,#0x20

503         call    prnt1  

504         mov     al,#0x20

505         call    prnt1  

506         mov     al,#0x20

507         call    prnt1

508         ret    

509

510 ! Routine to print asciiz-string at DS:SI

    ! 显示位于DS:SI处以NULL0x00)结尾的字符串。

511

512 prtstr: lodsb

513         and     al,al

514         jz      fin

515         call    prnt1            ! 显示al中的一个字符。

516         jmp     prtstr

517 fin:    ret

518

519 ! Routine to print a decimal value on screen, the value to be

520 ! printed is put in al (i.e 0-255).

    ! 显示十进制数字的子程序。显示值放在寄存器al中(0--255)。

521

522 dprnt:  push    ax

523         push    cx

524         mov     ah,#0x00               

525         mov     cl,#0x0a

526         idiv    cl

527         cmp     al,#0x09

528         jbe     lt100

529         call    dprnt

530         jmp     skip10

531 lt100:  add     al,#0x30

532         call    prnt1

533 skip10: mov     al,ah

534         add     al,#0x30

535         call    prnt1  

536         pop     cx

537         pop     ax

538         ret

539

540 ! Part of above routine, this one just prints ascii al

    ! 上面子程序的一部分。显示al中的一个字符。

    ! 该子程序使用中断0x100x0E功能,以电传方式在屏幕上写一个字符。光标会自动移到下一个

    ! 位置处。如果写完一行光标就会移动到下一行开始处。如果已经写完一屏最后一行,则整个屏幕

    ! 会向上滚动一行。字符0x07BEL)、0x08BS)、0x0A(LF)0x0DCR)被作为命令不会显示。

    ! 输入:AL -- 欲写字符;BH -- 显示页号;BL -- 前景显示色(图形方式时)。

541

542 prnt1:  push    ax

543         push    cx

544         mov     bh,#0x00         ! 显示页面。

545         mov     cx,#0x01

546         mov     ah,#0x0e

547         int     0x10

548         pop     cx

549         pop     ax

550         ret

551

552 ! Prints <CR> + <LF>    ! 显示回车+换行。

553

554 docr:   push    ax

555         push    cx

556         mov     bh,#0x00

557         mov     ah,#0x0e

558         mov     al,#0x0a

559         mov     cx,#0x01

560         int     0x10

561         mov     al,#0x0d

562         int     0x10

563         pop     cx

564         pop     ax

565         ret    

566        

    ! 全局描述符表开始处。描述符表由多个8字节长的描述符项组成。这里给出了3个描述符项。

    ! 1项无用(568行),但须存在。第2项是系统代码段描述符(570-573行),第3项是系

    ! 统数据段描述符(575-578)

567 gdt:

568         .word   0,0,0,0         ! dummy   ! 1个描述符,不用。

569

    ! GDT表中这里的偏移量是0x08。它是内核代码段选择符的值。

570         .word   0x07FF          ! 8Mb - limit=2047  (0--2047,因此是2048*4096=8Mb)

571         .word   0x0000          ! base address=0

572         .word   0x9A00          ! code read/exec         ! 代码段为只读、可执行。

573         .word   0x00C0          ! granularity=4096, 386  ! 颗粒度为409632位模式。

574

    ! GDT表中这里的偏移量是0x10。它是内核数据段选择符的值。

575         .word   0x07FF          ! 8Mb - limit=2047 (2048*4096=8Mb)

576         .word   0x0000          ! base address=0

577         .word   0x9200          ! data read/write        ! 数据段为可读可写。

578         .word   0x00C0          ! granularity=4096, 386  ! 颗粒度为409632位模式。

579

    ! 下面是加载中断描述符表寄存器idtr的指令lidt要求的6字节操作数。前2字节是IDT表的

    ! 限长,后4字节是idt表在线性地址空间中的32位基地址。CPU要求在进入保护模式之前需设

    ! IDT表,因此这里先设置一个长度为0的空表。

580 idt_48:

581         .word   0               ! idt limit=0

582         .word   0,0             ! idt base=0L

583

    ! 这是加载全局描述符表寄存器gdtr的指令lgdt要求的6字节操作数。前2字节是gdt表的限

    ! 长,后4字节是 gdt表的线性基地址。这里全局表长度设置为 2KB0x7ff即可),因为每8

    ! 字节组成一个段描述符项,所以表中共可有 256项。4字节的线性基地址为 0x0009<<16 +

    ! 0x0200 + gdt,即0x90200 + gdt(符号gdt是全局表在本程序段中的偏移地址,见205)

584 gdt_48:

585         .word   0x800           ! gdt limit=2048, 256 GDT entries

586         .word   512+gdt,0x9     ! gdt base = 0X9xxxx

587

588 msg1:   .ascii  "Press <RETURN> to see SVGA-modes available or any other key to continue."

589                 db      0x0d, 0x0a, 0x0a, 0x00

590 msg2:           .ascii  "Mode:  COLSxROWS:"

591                 db      0x0d, 0x0a, 0x0a, 0x00

592 msg3:           .ascii  "Choose mode by pressing the corresponding number."

593                 db      0x0d, 0x0a, 0x00

594                

    ! 下面是4个显示卡的特征数据串。

595 idati:          .ascii  "761295520"

596 idcandt:        .byte   0xa5                   ! 标号idcandt意思是ID of Chip AND Tech.

597 idgenoa:        .byte   0x77, 0x00, 0x66, 0x99

598 idparadise:     .ascii  "VGA="

599

    ! 下面是各种显示卡可使用的扩展模式个数和对应的模式号列表。其中每一行第1个字节是模式个

    ! 数值,随后的一些值是中断0x10功能0AH=0)可使用的模式号。例如从602行可知,对于ATI

    ! 牌子的显示卡,除了标准模式以外还可使用两种扩展模式:0x230x33

600 ! Manufacturer:   Numofmodes:   Mode:

    ! 厂家:          模式数量:    模式列表:

601

602 moati:          .byte   0x02,   0x23, 0x33

603 moahead:        .byte   0x05,   0x22, 0x23, 0x24, 0x2f, 0x34

604 mocandt:        .byte   0x02,   0x60, 0x61

605 mocirrus:       .byte   0x04,   0x1f, 0x20, 0x22, 0x31

606 moeverex:       .byte   0x0a,   0x03, 0x04, 0x07, 0x08, 0x0a, 0x0b, 0x16, 0x18, 0x21, 0x40

607 mogenoa:        .byte   0x0a,   0x58, 0x5a, 0x60, 0x61, 0x62, 0x63, 0x64, 0x72, 0x74, 0x78

608 moparadise:     .byte   0x02,   0x55, 0x54

609 motrident:      .byte   0x07,   0x50, 0x51, 0x52, 0x57, 0x58, 0x59, 0x5a

610 motseng:        .byte   0x05,   0x26, 0x2a, 0x23, 0x24, 0x22

611 movideo7:       .byte   0x06,   0x40, 0x43, 0x44, 0x41, 0x42, 0x45

612

    ! 下面是各种牌子VGA显示卡可使用的模式对应的列、行值列表。例如第615行表示ATI显示卡两

    ! 种扩展模式的列、行值分别是 132 x 25132 x 44

613 !                       msb = Cols   lsb = Rows:

    !                       高字节=列数  低字节=行数:

614

615 dscati:         .word   0x8419, 0x842c                           ! ATI卡可设置列、行值。

616 dscahead:       .word   0x842c, 0x8419, 0x841c, 0xa032, 0x5042   ! Ahead卡可设置值。

617 dsccandt:       .word   0x8419, 0x8432

618 dsccirrus:      .word   0x8419, 0x842c, 0x841e, 0x6425

619 dsceverex:      .word   0x5022, 0x503c, 0x642b, 0x644b, 0x8419, 0x842c, 0x501e, 0x641b, 0xa040, 0x841e

620 dscgenoa:       .word   0x5020, 0x642a, 0x8419, 0x841d, 0x8420, 0x842c, 0x843c, 0x503c, 0x5042, 0x644b

621 dscparadise:    .word   0x8419, 0x842b

622 dsctrident:     .word   0x501e, 0x502b, 0x503c, 0x8419, 0x841e, 0x842b, 0x843c

623 dsctseng:       .word   0x503c, 0x6428, 0x8419, 0x841c, 0x842c

624 dscvideo7:      .word   0x502b, 0x503c, 0x643c, 0x8419, 0x842c, 0x841c

625        

626 .text

627 endtext:

628 .data

629 enddata:

630 .bss

631 endbss: