http://hi.baidu.com/xshaonan/blog/item/6a3fca976ec5e26755fb965d.html

A20地址线
2009年12月01日 星期二 22:55

;********************************************
; A20.inc
; --- Enable A20 address line
; 方法一: 系统控制端口A
; 方法二: BIOS中断例程
; 方法三: 键盘控制器
; 方法四: 键盘控制器输出端口

;********************************************

%ifndef __A20_INC_67343546FDCC56AAB872_INCLUDED__
%define __A20_INC_67343546FDCC56AAB872_INCLUDED__

bits 16
;---------------------------------------
; 方法一: (system control)系统控制IO端口0x92,可用来实现对A20的开启与关闭,但因为0x92端口的细节与功能
; 和制造商有很多关系,所以这种方法移植性比较差
; Bit 0 - Setting to 1 causes a fast reset (Used to switch back to real mode)
; Bit 1 - 0: disable A20; 1: enable A20
; Bit 2 - Manufacturer defined
; Bit 3 - power on password bytes (CMOS bytes 0x38-0x3f or 0x36-0x3f). 0: accessible, 1: inaccessible
; Bits 4-5 - Manufacturer defined
; Bits 6-7 - 00: HDD activity LED off; any other value is "on"
;-----------------------------------------

;----------------------
; 打开地址线
;----------------------
EnableA20_SysControlA:
push ax
in   al, 92h
or    al, 00000010b
out   92h, al
pop   ax
ret

;----------------------
; 关闭地址线
;----------------------
DisableA20_SysControlA:
push ax
in al, 92h
and al, 11111101b
out 92h, al
pop   ax
ret


;-----------------------------------------
;方法二: 使用BIOS INT 0x15
; ax=2400h --- 关闭A20地址线
; ax=2401h --- 开启A20地址线
; ax=2402h --- 获取A20当前状态
; ax=2403h --- 查询A20
;-----------------------------------------
;----------------------
; 打开地址线
;----------------------
EnableA20_Bios:
pusha
mov   ax, 2401h
int   0x15
popa
ret

;----------------------
; 关闭地址线
;----------------------
DisableA20_Bios:
pusha
mov ax, 2400h
int   0x15
popa
ret

;-----------------------------------------
;方法三: 使用键盘控制器(KBC)(这里指的都是8043 keyboard Controller), 这个方法用得比较好,可移植性比较高
; Note: 设备控制器位于CPU与设备之间,它既要与CPU通信,又要与设备通信,还具有按照CPU所发来的
;    命令去控制设备工作的功能,因此,现有的大多数控制器有以下三部分:
; 1)设备控制器与处理机的接口
; 2)设备控制器与设备的接口
; 3)I/O控制逻辑
;为了更好地理解控制器,必须先知道控制器内部IO端口所包含的寄存器及其端口映射地址
; *************************
; 端口 读/写 描述
; 0x60 读   读输入缓冲
; 0x60 写   写输出缓冲
; 0x64 读   读状态寄存器
; 0x64 写   发送命令到控制器
; **************************
; 通过写0x64端口命令字节来发生命令给控制器. 如果这命令带有参数,则被发送到0x60端口.
;-----------------------------------------
;-----------------------------------------
; 从0x64端口读取控制器的状态信息
;Bit 0: Output Buffer Status
; 0: Output buffer empty, dont read yet
; 1: Output buffer full, please read me :)
;Bit 1: Input Buffer Status
; 0: Input buffer empty, can be written
; 1: Input buffer full, dont write yet
;-----------------------------------------
; Note:在操作系统课程中,CPU与外设之间交换信息即I/O控制方式的宗旨就
; 是: 尽量减少主机对I/O控制的干预,把主机从繁杂的I/O控制事务中
; 解脱出来(因为CPU的运行速度比I/O设备的运行速度远快得多),以便
; 更多地去完成数据处理的任务.
wait_input:
in   al, 0x64   ;读取状态寄存器
test al, 0x10   ;测试输入缓冲
jnz   wait_input   ;如果输入缓冲非空,则继续等待
ret

wait_output:
in   al, 0x64
test al, 0x01   ;测试输出缓冲
jz   wait_output   ;如果输出缓冲非空,则继承等待
ret


;---------------------------
; 方法三: 通过控制器
;---------------------------
EnableA20_KKbrd:
cli
push ax
mov   al, 0xdd   ;comand 0xdd: 开启a20
out   0x64, al   ;发送command到控制器
pop   ax
ret

;---------------------------
; 方法四: 通过控制器的输出端口
;----
; 键盘控制器的命令
; 0xD0 Read Output Port
; 0xD1 Write Output Port
; 0xDD Enable A20 Address Line
; 0xDF Disable A20 Address Line

;Note: I/O控制方式有: 程序I/O方式(即程序查询); 中断驱动; DMA方式; I/O通道
;---------------------------
EnableA20_KKbrd_Out:
cli
pusha

call wait_input
mov   al, 0xad
out   0x64, al   ; CPU发送关闭键盘命令给I/O控制器
call wait_input
  
mov   al, 0xd0   ; CPU发送命令让控制器去读输出端口
out   0x64, al
call wait_output

in   al, 0x60
push eax     ;获取输出端口数据并存放在堆栈中
call wait_input

mov   al, 0xd1
out   0x64, al   ; 让控制器去写输出端口
call wait_input

pop   eax
or   al, 2
out   0x60, al

call wait_input
mov   al, 0xae   ; 开启键盘
out   0x64, al

call wait_input
popa
sti
ret

%endif