키보드 컨트롤러에서 키보드 디바이스를 사용 가능하게 하려면, 커맨드 포트로 키보드 디바이스 활성화 커맨드인 0xAE를 보내면 된다.
(But, 해당 과정은 키보드 컨트롤러를 활성화한 것이지, 실제 키보드를 활성화한 것은 아니다.)
키보드 컨트롤러와 키보드는 PS/2 방식의 케이블로 연결되어 있으며 PC외부에 존재하기 때문에, 키보드에도 활성화 커맨드를 보내줄 필요가 있다.
키보드는 커맨드나 데이터에 대한 응답이 전송되며, 정상적으로 처리한 경우 ACK(0xFA)를 전송하며, ACK가 수신되지 않으면 수행 도중 에러가 발생한 것이다.
키보드 컨트롤러와 키보드를 활성화하는 코드
// 출력 버퍼(포트 0x60)에 수신된 데이터가 있는지 여부를 반환
BOOL kIsOutputBufferFull(void){
// 상태 레지스터(포트 0x64)에서 읽은 값에 출력 버퍼 상태 비트(비트0)가
// 1로 설정되어 있으면, 출력 버퍼에 키보드가 전송한 데이터가 존재함
if( kInPortByte( 0x64 ) & 0x01 ){
return TRUE;
}
return FALSE;
}
// 입력 버퍼(포트 0x64)에 프로세서가 쓴 데이터가 남아있는지 여부를 반환
BOOL kIsInputBufferFull(void){
// 상태 레지스터(포트 0x64)에서 읽은 값에 입력 버퍼 상태 비트(비트 1)가
// 1로 설정되어 있으면 아직 키보드가 데이터를 가져가지 않았음
if( kInPortByte(0x64) & 0x02 ){
return TRUE;
}
return FALSE;
}
// 키보드 활성화
BOOL kActivateKeyboard(void){
int i;
int j;
//컨트롤 레지스터(포트 0x64)에 키보드 활성화 커맨드(0xAE)를 전달하여 키보드 디바이스 활성화
kOutPortByte(0x64, 0xAE);
//입력 버퍼(포트 0x60)가 빌 때까지 기다렸다가, 키보드에 활성화 커맨드 전송
//0xFFFF만큼 루프를 수행할 시간이면, 충분히 커맨드가 전송될 수 있음
//0xFFFF 루프를 수행한 이후에도 입력 버퍼(포트 0x60)가 비지 않으면 무시하고 전송
for(i=0; i<0xFFFF; i++){
//입력 버퍼(포트 0x60)가 비어있으면, 키보드 커맨드 전송가능
if(kIsInputBufferFull() == FALSE){
break;
}
}
// 입력 버퍼(포트 0x60)로 키보드 활성화(0xF4) 커맨드를 전달하여 키보드로 전송
kOutPortByte(0x60, 0xF4);
// ACK가 올 때까지 대기함
// ACK가 오기 전에 키보드 출력 버퍼(포트 0x60)에 키 데이터가 저장될 수 있으므로
// 키보드에서 전달된 데이터를 최대 100개까지 수신하여 ACK를 확인
for(j=0; j<100; j++){
// 0xFFFF만큼 루프를 수행할 시간이면, 충분한 커맨드의 응답이 올 수 있음
// 0xFFFF 루프를 수행한 이후에도 출력 버퍼(포트 0x60)가 차있지 않으면 무시하고 읽음
for(i=0; i<0xFFFF; i++){
//출력 버퍼(포트 0x60)가 차있으면, 데이터를 읽을 수 있음
if(kIsOutputBufferFull()==TRUE){
break;
}
}
// 출력 버퍼(포트 0x60)에서 읽은 데이터가 ACK(0xFA)이면 성공
if(kInPortByte(0x60) == 0xFA){
return TRUE;
}
}
return FALSE;
}
kInPortByte( )함수와 kOutPortByte( )함수의 코드
; 포트로부터 1바이트를 읽음
; PARAM: 포트 번호
kInPortByte:
push rdx ; 함수에서 임시로 사용하는 레지스터를 스택에 저장
; 함수의 마지막 부분에서 스택에 삽입된 값을 꺼내 복원
mov rdx, rdi ; RDX 레지스터에 파라미터 1(포트 번호)를 저장
mov rax, 0 ; RAX 레지스터를 초기화
in al, dx ; DX 레지스터에 저장된 포트 어드레스에서 한 바이트를 읽어
; AL 레지스터에 저장, AL 레지스터는 함수의 반환 값으로 사용됨
pop rdx ; 함수에서 사용이 끝난 레지스터를 복원
ret ; 함수를 호출한 다음 코드의 위치로 복귀
; 포트로부터 1바이트를 씀
; PARAM: 포트 번호
kOutPortByte:
push rdx ; 함수에서 임시로 사용하는 레지스터를 스택에 저장
push rax ; 함수의 마지막 부분에서 스택에 삽입된 값을 꺼내 복원
mov rdx, rdi ; RDX 레지스터에 파라미터 1(포트 번호)를 저장
mov rax, rsi ; RAX 레지스터에 파라미터 2(데이터)를 저장
out dx, al ; DX 레지스터에 저장된 포트 어드레스에 AL 레지스터에 저장된
; 한 바이트를 씀
pop rax ; 함수에서 사용이 끝난 레지스터를 복원
pop rdx
ret ; 함수를 호출한 다음 코드의 위치로 복귀
키보드 컨트롤에서 키 값(스캔 코드)를 읽는 코드
BYTE kGetKeyboardScanCode(void){
// 출력 버퍼(포트0x60)에 데이터가 있을 때까지 대기
while(kIsOutputBufferFull() == FALSE){
;
}
return kInPortByte(0x60);
}
키보드 컨트롤러를 통해, A20 게이트를 활성화하는 코드
BYTE kGetKeyboardScanCode(void){
// 출력 버퍼(포트0x60)에 데이터가 있을 때까지 대기
while(kIsOutputBufferFull() == FALSE){
;
}
return kInPortByte(0x60);
}
void kEnableA20Gate(void){
BYTE bOutputPortData;
int i;
// 컨트롤 레지스터(포트 0x64)에 키보드 컨트롤러의 출력 포트 값을 읽는 커맨트(0xD0) 전송
kOutPortByte(0x64, 0xD0);
// 출력 포트의 데이터를 기다렸다가 읽음
for(i=0; i<0xFFFF; i++){
// 출력 버퍼(포트 0x60)가 차있으면, 데이터를 읽을 수 있음
if(kIsOutputBufferFull() == TRUE){
break;
}
}
// 출력 포트(포트 0x60)에 수신된 키보드 컨트롤러의 출력 포트 값을 읽음
bOutputPortData = kInPortByte(0x60);
// A20 게이트 비트 설정 - A20게이트 활성화 비트(비트1)를 1로 설정
bOutputPortData != 0x02;
// 입력 버퍼(포트 0x60)에 데이터가 비어있으면, 출력 포트에 값을 쓰는 커맨드와 출력 포트 데이터 전송
for(i=0; i<0xFFFF; i++){
// 입력 버퍼(포트 0x60)가 비었으면, 커맨드 전송 가능
if(kIsInputBufferFull() == FALSE){
break;
}
// 커맨드 레지스터(0x64)에 출력 포트 설정 커맨드(0xD1)를 전달
kOutPortByte(0x64, 0xD1);
// 입력 버퍼(0x60)에 A20 게이트 비트가 1로 설정된 값을 전달
kOutPortByte(0x60, bOutputPortData);
}
}
키보드 LED 상태를 변경하려면, 입력 버퍼(포트 0x60)로 0xED 커맨드를 전송해서, 키보드에 LED상태 데이터가 전송될 것임을 미리 알려야 한다.
LED 상태 데이터는 1byte 중, 하위 3bit만 사용한다.
키보드 LED를 키려면 비트 1, 끄려면 비트 0으로 설정한다.
키보드의 상태 LED를 제어하는 코드
BOOL kChangeKeyboardLED(BOOL bCapsLockOn, BOOL bNumLockOn, BOOL bScrollLockOn){
int i, j;
// 키보드에 LED 변경 커맨드 전송하고, 커맨드가 처리될 때까지 대기
for(i=0; i<0xFFFF; i++){
break;
}
}
// 입력버퍼(포트 0x60)로 LED 상태 변경 커맨드(0xED) 전송
kOutPortByte(0x60, 0xED);
for(i=0; i<0xFFFF; i++){
// 입력 버퍼(포트 0x60)가 비어 있으면, 키보드가 커맨드를 가져간 것임
if(kIsInputBufferFull()==FALSE){
break;
}
}
//키보드가 LED 상태 변경 커맨드를 가져갔으므로 ACK가 올 때까지 대기
for(j=0; j<100; j++){
for(i=0; i<0xFFFF; i++){
//출력 버퍼(포트 0x60)가 차있으면, 데이터를 읽을 수 있음
if(kIsOutputBufferFull()==TRUE){
break;
}
}
// 출력 버퍼(포트 0x60)에서 읽은 데이터가 ACK(0xFA)이면 성공
if(kInPortByte(0x60) == 0xFA){
break
}
}
if(j >= 100){
return FALSE;
}
// LED 변경 값을 키보드로 전송하고, 데이터 처리가 완료될 때까지 대기
kOutPortByte(0x60, (bCapsLockOn << 2) | (bNumLockOn << 1) | bScrollLockOn);
for(i=0; i<0xFFFF; i++){
//입력 버퍼(포트 0x60)가 비어 있으면, 키보드가 LED데이터를 가져간 것임
if(kIsInputBufferFull() == FALSE){
break;
}
// 키보드가 LED 데이터를 가져갔으므로 ACK가 올 때까지 대기
for(j=0; j<100; j++){
for(i=0; i<0xFFFF; i++){
//출력 버퍼(포트 0x60)가 차있으면, 데이터를 읽을 수 있음
if(kIsOutputBufferFull()==TRUE){
break;
}
}
// 출력 버퍼(포트 0x60)에서 읽은 데이터가 ACK(0xFA)이면 성공
if(kInPortByte(0x60) == 0xFA){
return TRUE;
}
}
if(j >= 100){
return TRUE;
}
}
스캔 코드 매핑 테이블을 구성하는 엔트리
typedef struct kKeyMappingEntryStruct{
// Shift 키나 Caps Lock 키와 조합되지 않는 ASCII 코드
BYTE bNormalCode;
// Shift 키나 Caps Lock 키와 조합된 ASCII 코드
BYTE bCombinedCode;
} KEYMAPPINGENTRY;
스캔 코드를 ASCII 코드로 매핑하는 테이블
static KEYMAPPINGENTRY gs_vstKeyMappingTable[ KEY_MAPPINGTABLEMAXCOUNT ] =
{
/* 0 */ { KEY_NONE , KEY_NONE },
/* 1 */ { KEY_ESC , KEY_ESC },
/* 2 */ { '1' , '!' },
/* 3 */ { '2' , '@' },
/* 4 */ { '3' , '#' },
/* 5 */ { '4' , '$' },
/* 6 */ { '5' , '%' },
/* 7 */ { '6' , '^' },
/* 8 */ { '7' , '&' },
/* 9 */ { '8' , '*' },
/* 10 */ { '9' , '(' },
/* 11 */ { '0' , ')' },
/* 12 */ { '-' , '_' },
/* 13 */ { '=' , '+' },
/* 14 */ { KEY_BACKSPACE , KEY_BACKSPACE },
/* 15 */ { KEY_TAB , KEY_TAB },
/* 16 */ { 'q' , 'Q' },
/* 17 */ { 'w' , 'W' },
/* 18 */ { 'e' , 'E' },
/* 19 */ { 'r' , 'R' },
/* 20 */ { 't' , 'T' },
/* 21 */ { 'y' , 'Y' },
/* 22 */ { 'u' , 'U' },
/* 23 */ { 'i' , 'I' },
/* 24 */ { 'o' , 'O' },
/* 25 */ { 'p' , 'P' },
/* 26 */ { '[' , '{' },
/* 27 */ { ']' , '}' },
/* 28 */ { '\\n' , '\\n' },
/* 29 */ { KEY_CTRL , KEY_CTRL },
/* 30 */ { 'a' , 'A' },
/* 31 */ { 's' , 'S' },
/* 32 */ { 'd' , 'D' },
/* 33 */ { 'f' , 'F' },
/* 34 */ { 'g' , 'G' },
/* 35 */ { 'h' , 'H' },
/* 36 */ { 'j' , 'J' },
/* 37 */ { 'k' , 'K' },
/* 38 */ { 'l' , 'L' },
/* 39 */ { ';' , ':' },
/* 40 */ { '\\'' , '\\"' },
/* 41 */ { '`' , '~' },
/* 42 */ { KEY_LSHIFT , KEY_LSHIFT },
/* 43 */ { '\\\\' , '|' },
/* 44 */ { 'z' , 'Z' },
/* 45 */ { 'x' , 'X' },
/* 46 */ { 'c' , 'C' },
/* 47 */ { 'v' , 'V' },
/* 48 */ { 'b' , 'B' },
/* 49 */ { 'n' , 'N' },
/* 50 */ { 'm' , 'M' },
/* 51 */ { ',' , '<' },
/* 52 */ { '.' , '>' },
/* 53 */ { '/' , '?' },
/* 54 */ { KEY_RSHIFT , KEY_RSHIFT },
/* 55 */ { '*' , '*' },
/* 56 */ { KEY_LALT , KEY_LALT },
/* 57 */ { ' ' , ' ' },
/* 58 */ { KEY_CAPSLOCK , KEY_CAPSLOCK },
/* 59 */ { KEY_F1 , KEY_F1 },
/* 60 */ { KEY_F2 , KEY_F2 },
/* 61 */ { KEY_F3 , KEY_F3 },
/* 62 */ { KEY_F4 , KEY_F4 },
/* 63 */ { KEY_F5 , KEY_F5 },
/* 64 */ { KEY_F6 , KEY_F6 },
/* 65 */ { KEY_F7 , KEY_F7 },
/* 66 */ { KEY_F8 , KEY_F8 },
/* 67 */ { KEY_F9 , KEY_F9 },
/* 68 */ { KEY_F10 , KEY_F10 },
/* 69 */ { KEY_NUMLOCK , KEY_NUMLOCK },
/* 70 */ { KEY_SCROLLLOCK , KEY_SCROLLLOCK },
/* 71 */ { KEY_HOME , '7' },
/* 72 */ { KEY_UP , '8' },
/* 73 */ { KEY_PAGEUP , '9' },
/* 74 */ { '-' , '-' },
/* 75 */ { KEY_LEFT , '4' },
/* 76 */ { KEY_CENTER , '5' },
/* 77 */ { KEY_RIGHT , '6' },
/* 78 */ { '+' , '+' },
/* 79 */ { KEY_END , '1' },
/* 80 */ { KEY_DOWN , '2' },
/* 81 */ { KEY_PAGEDOWN , '3' },
/* 82 */ { KEY_INS , '0' },
/* 83 */ { KEY_DEL , '.' },
/* 84 */ { KEY_NONE , KEY_NONE },
/* 85 */ { KEY_NONE , KEY_NONE },
/* 86 */ { KEY_NONE , KEY_NONE },
/* 87 */ { KEY_F11 , KEY_F11 },
/* 88 */ { KEY_F12 , KEY_F12 }
};