Ⅰ. 키보드 컨트롤러 제어
1. 키보드와 키보드 컨트롤러 활성화
- 키보드 컨트롤러에서 키보드 디바이스를 사용 가능하게 하려면, 커맨드 포트로 키보드 디바이스 활성화 커맨드인 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 ; 함수를 호출한 다음 코드의 위치로 복귀
2. 키보드 컨트롤러에서 키 값 읽기
- 키보드 컨트롤에서 키 값(스캔 코드)를 읽는 코드
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);
}
}
3. 키보드 LED 상태 제어
- 키보드 LED 상태를 변경하려면, 입력 버퍼(포트 0x60)로 0xED 커맨드를 전송해서, 키보드에 LED상태 데이터가 전송될 것임을 미리 알려야 한다.
- 키보드 커맨드를 잘 처리했는지 ACK를 확인하고 LED상태를 나타내는 데이터를 전송한다.
- LED 상태 데이터는 1byte 중, 하위 3bit만 사용한다.
- Caps Lock은 비트 2, Num Lock은 비트 1, Scroll Lock은 비트 0에 할당되어 있다.
- 키보드 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;
}
}
Ⅲ. 스캔 코드와 간단한 셸
- 수신된 데이터를 처리하여, ASCII 코드 형태로 변환한다.
1. 스캔 코드를 ASCII 문자로 변환
- 스캔 코드 매핑 테이블을 구성하는 엔트리
- typedef struct kKeyMappingEntryStruct{ // Shift 키나 Caps Lock 키와 조합되지 않는 ASCII 코드 BYTE bNormalCode; // Shift 키나 Caps Lock 키와 조합된 ASCII 코드 BYTE bCombinedCode; } KEYMAPPINGENTRY;
- 스캔 코드를 ASCII 코드로 매핑하는 테이블
- 기능 키들 중, HOME키나 F1 키는 별도로 할당된 ASCII문자가 존재하지 않는다. 따라서, 이러한 키에 0x80 이상의 값을 할당하고 매크로를 정의하여 애플리케이션에서 사용하게 작성되었다.
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 } };
- 키보드의 키 상태를 관리하는 자료구조
- 조합 키의 입력 상태와 확장 키 입력 상태를 저장하는 필드이다.
- Scroll Lock 키에 대한 상태가 존재하는 이유는 조합 키를 사용하지는 않지만, 키보드 LED 상태를 저장하려면 필요하기 때문이다.
typedef struct kKeyboardManagerStruct{ // 조합 키 정보 BOOL bShiftDown; BOOL bCapsLockOn; BOOL bNumLockOn; BOOL bScrollLockOn; // 확장 키를 처리하기 위한 정보 BOOL bExtendedCodeIn; int iSkipCountForPause; } KEYBORDMANAGER;
- 조합된 키를 선택해야 하는지, 여부를 반환하는 함수의 코드
- 키 그룹을 알파벳 키, 숫자와 기호키, 숫자 패드로 나누어 처리한다.
// 조합된 키 값을 사용해야 하는지 여부를 반환 BOOL kIsUseCombinedCode( BOOL bScanCode ) { BYTE bDownScanCode; BOOL bUseCombinedKey; bDownScanCode = bScanCode & 0x7F; // 알파벳 키라면 Shift 키와 Caps Lock의 영향을 받음 if( kIsAlphabetScanCode( bDownScanCode ) == TRUE ) { // 만약 Shift 키와 Caps Lock 키 중에 하나만 눌러져있으면 조합된 키를 되돌려 줌 if( gs_stKeyboardManager.bShiftDown ^ gs_stKeyboardManager.bCapsLockOn ) { bUseCombinedKey = TRUE; } else { bUseCombinedKey = FALSE; } } // 숫자와 기호 키라면 Shift 키의 영향을 받음 else if( kIsNumberOrSymbolScanCode( bDownScanCode ) == TRUE ) { // Shift 키가 눌러져있으면 조합된 키를 되돌려 줌 if( gs_stKeyboardManager.bShiftDown == TRUE ) { bUseCombinedKey = TRUE; } else { bUseCombinedKey = FALSE; } } // 숫자 패드 키라면 Num Lock 키의 영향을 받음 // 0xE0만 제외하면 확장 키 코드와 숫자 패드의 코드가 겹치므로, // 확장 키 코드가 수신되지 않았을 때만처리 조합된 코드 사용 else if( ( kIsNumberPadScanCode( bDownScanCode ) == TRUE ) && ( gs_stKeyboardManager.bExtendedCodeIn == FALSE ) ) { // Num Lock 키가 눌러져있으면, 조합된 키를 되돌려 줌 if( gs_stKeyboardManager.bNumLockOn == TRUE ) { bUseCombinedKey = TRUE; } else { bUseCombinedKey = FALSE; } } return bUseCombinedKey; } // 스캔 코드가 알파벳 범위인지 여부를 반환 BOOL kIsAlphabetScanCode( BYTE bScanCode ) { // 변환 테이블을 값을 직접 읽어서 알파벳 범위인지 확인 if( ( 'a' <= gs_vstKeyMappingTable[ bScanCode ].bNormalCode ) && ( gs_vstKeyMappingTable[ bScanCode ].bNormalCode <= 'z' ) ) { return TRUE; } return FALSE; } // 숫자 또는 기호 범위인지 여부를 반환 BOOL kIsNumberOrSymbolScanCode( BYTE bScanCode ) { // 숫자 패드나 확장 키 범위를 제외한 범위(스캔 코드 2~53)에서 영문자가 아니면 // 숫자 또는 기호임 if( ( 2 <= bScanCode ) && ( bScanCode <= 53 ) && ( kIsAlphabetScanCode( bScanCode ) == FALSE ) ) { return TRUE; } return FALSE; } // 숫자 패드 범위인지 여부를 반환 BOOL kIsNumberPadScanCode( BYTE bScanCode ) { // 숫자 패드는 스캔 코드의 71~83에 있음 if( ( 71 <= bScanCode ) && ( bScanCode <= 83 ) ) { return TRUE; } return FALSE; }
- 조합 키의 상태를 갱신하는 함수의 코드
void UpdateCombinationKeyStatusAndLED(BYTE bScanCode){
BOOL bDown;
BYTE bDownScanCode;
BOOL bLEDStatusChanged = FALSE;
// 놀림 또는 떨어짐 상태처리, 최상위 비트(비트7)가 1이면 키가 떨어졌음을 의미하고
// 0이면 눌림을 의미함
if(bScanCode & 0x80){
bDown = FALSE;
bDownScanCode = bScanCode & 0x7F;
}else{
bDown = TRUE;
bDownScanCode = bScanCode;
}
// 조합 키 검색
// Shift 키의 스캔 코드(42 or 54)이면, Shift 키 상태 갱신
if((bDownScanCode == 42) || (bDownScanCode == 54)){
gs_stKeyboardManager.bShiftDown = bDown;
}
// Caps Lock 키의 스캔코드(58)이면, Caps Lock의 상태 갱신하고 LED 상태 변경
else if((bDownScanCode == 58) && (bDown == TRUE)){
gs_stKeyboardManager.bCapsLockOn ^= TRUE;
bLEDStatusChanged = TRUE;
}
// Num Lock 키의 스캔 코드(69)이면, Num Lock의 상태를 갱신하고 LED 상태 변경
else if((bDownScanCode == 69) && (bDown == TRUE)){
gs_stKeyboardManager.bNumLockOn ^= TRUE;
bLEDStatusChanged = TRUE;
}
// Scroll Lock 키의 스캔 코드(70)이면, Scroll Lock의 상태를 갱신하고 LED 상태 변경
else if((bDownScanCode == 70) && (bDown == TRUE)){
gs_stKeyboardManager.bScrollLockOn ^= TRUE;
bLEDStatusChanged = TRUE;
}
// LED 상태가 변했으면, 키보드로 커맨드를 전송하여 LED 변경
if(bLEDStatusChanged == TRUE){
kChangeKeyboardLED(gs_stKeyboardManager.bCapsLockOn,
gs_stKeyboardManager.bNumLockOn, gs_stKeyboardManager.bScrollLockOn);
}
}
- 스캔 코드를 ASCII 코드로 변환하는 함수의 코드
- 확장 키는 2개 이상의 스캔코드로 구성되며, 2번째나 3번째까지 키를 확인해야 정확한 키값을 찾을 수 있다.
- Pause 키는 다른 확장키와 달리, 0xE1로 시작하여 0x1D, 0x45가 전송된다.
- Pause만 0xE1을 수신하므로, 0xE1을 수신하였을 경우 나머지 0x1D와 0x45를 무시하게 처리하였다.
- Pause 키를 제외한 다른 확장키는 0xE0와 일반 키의 스캔 코드가 전송된다.
- 0xE0을 수신한 경우, 확장 키임을 저장해둔 후 다음 키가 수신되었을 때 ASCII 코드와 함께 확장 키임을 알려주는 방식으로 처리되었다.
// 매크로 정의 // Pause 키를 처리하기 위해, 무시해야 하는 나머지 스캔 코드의 수 #define KEY_SKIPCOUNTFORPAUSE 2 // 키 상태에 대한 플래그 #define KEY_FLAGS_UP 0X00 #define KEY_FLAGS_DOWN 0X01 #define KEY_FLAGS_EXTENDENDKEY 0X02 // 함수 코드 // 스캔 코드를 ASCII 코드로 변환 BOOL kConvertScanCodeToASCIICode (BYTE bScanCode, BYTE* pbASCIICODE, BOOL* pbFlags){ BOOL bUseCombinedKey; // 이전에 Pause 키가 수신되었다면, Pause의 남은 스캔 코드를 무시 if( gs_stKeyboardManager.iSkipCountForPause > 0 ) { gs_stKeyboardManager.iSkipCountForPause--; return FALSE; } // Pause 키는 특별히 처리 if( bScanCode == 0xE1 ) { *pbASCIICode = KEY_PAUSE; *pbFlags = KEY_FLAGS_DOWN; gs_stKeyboardManager.iSkipCountForPause = KEY_SKIPCOUNTFORPAUSE; return TRUE; } // 확장 키 코드가 들어왔을 때, 실제 키 값은 다음에 들어오므로 플래그 설정만 하고 종료 else if( bScanCode == 0xE0 ) { gs_stKeyboardManager.bExtendedCodeIn = TRUE; return FALSE; } // 조합된 키를 반환해야 하는가? bUseCombinedKey = kIsUseCombinedCode( bScanCode ); // 키 값 설정 if( bUseCombinedKey == TRUE ) { *pbASCIICode = gs_vstKeyMappingTable[ bScanCode & 0x7F ].bCombinedCode; } else { *pbASCIICode = gs_vstKeyMappingTable[ bScanCode & 0x7F ].bNormalCode; } // 확장 키 유무 설정 if( gs_stKeyboardManager.bExtendedCodeIn == TRUE ) { *pbFlags = KEY_FLAGS_EXTENDEDKEY; gs_stKeyboardManager.bExtendedCodeIn = FALSE; } else { *pbFlags = 0; } // 눌러짐 또는 떨어짐 유무 설정 if( ( bScanCode & 0x80 ) == 0 ) { *pbFlags |= KEY_FLAGS_DOWN; } // 조합 키 눌림 또는 떨어짐 상태를 갱신 UpdateCombinationKeyStatusAndLED( bScanCode ); return TRUE; }
2. 간단한 셸 구현
- 입력된 키 값을 화면에 출력하는 아주 간단한 셸(Shell) 구현
- 입력된 스캔 코드를 변환하여, 화면에 순차적으로 출력하는 기능을 구현한다.
char vcTemp[2] {0, }; BYTE bFlags; BYTE bTemp; int i = 0; while(1){ // 출력 버퍼(포트 0x60)가 차 있으면, 스캔 코드를 읽을 수 있음 if(kIsOutputBufferFull() == TRUE){ // 출력 버퍼(포트 0x60)에서 스캔 코드를 읽어서 저장 bTemp = kGetKeyboardScanCode(); // 스캔 코드를 ASCII 코드로 변환하는 함수를 호출하여, ASCII 코드와 // 눌림 또는 떨어짐 정보를 반환 if(kConvertScanCodeToASCIICode(bTemp, &(vcTemp[0]), &bFlags) == TRUE ){ // 키가 눌러졌으면, 키의 ASCII 코드 값을 화면에 출력 if(bFlags & KEY_FLAGS_DOWN){ kPrintString (i++, 13, vcTemp); } } } }
Ⅲ. 키보드 디바이스 드라이버의 통합과 빌드
1. 키보드 디바이스 드라이버 파일 추가
- (2.Kernel64/Source) 위치에 Keyboard.c와 Keyboard.h파일을 작성한다.
- 앞 설명 외에, A20 게이트 활성화 코드를 응용하여, 프로세서를 리셋하는 kReboot()함수가 추가되었다.
- Keyboard.c
#include "Types.h"
#include "AssemblyUtility.h"
#include "Keyboard.h"
////////////////////////////////////////////////////////////////////////////////
// 키보드 컨트롤러 및 키보드 제어에 관련된 함수들
////////////////////////////////////////////////////////////////////////////////
// 출력 버퍼(포트 0x60)에 수신된 데이터가 있는지 여부를 반환
BOOL kIsOutputBufferFull( void )
{
// 상태 레지스터(포트 0x64)에서 읽은 값에 출력 버퍼 상태 비트(비트 0)가
// 1로 설정되어 있으면 출력 버퍼에 키보드가 전송한 데이터가 존재함
if(kInPortByte( 0x64 ) & 0x01)
{
return TRUE;
}
return FALSE;
}
// 입력 버퍼(포트 0x60)에 프로세서가 쓴 데이터가 남아있는지 여부를 반환
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;
}
// 출력 버퍼(포트 0x60)에서 키를 읽음
BYTE kGetKeyboardScanCode( void )
{
// 출력 버퍼(포트 0x60)에 데이터가 있을 때까지 대기
while( kIsOutputBufferFull() == FALSE )
{
;
}
return kInPortByte( 0x60 );
}
// 키보드 LED의 ON/OFF를 변경
BOOL kChangeKeyboardLED( BOOL bCapsLockOn, BOOL bNumLockOn, BOOL bScrollLockOn )
{
int i, j;
// 키보드에 LED 변경 커맨드 전송하고 커맨드가 처리될 때까지 대기
for( i = 0 ; i < 0xFFFF ; i++ )
{
// 출력 버퍼(포트 0x60)가 비었으면 커맨드 전송 가능
if( kIsInputBufferFull() == FALSE )
{
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 )
{
break;
}
}
if( j >= 100 )
{
return FALSE;
}
return TRUE;
}
// A20 게이트를 활성화
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 게이트 비트 설정
bOutputPortData |= 0x01;
// 입력 버퍼(포트 0x60)에 데이터가 비어있으면 출력 포트에 값을 쓰는 커맨드와 출력 포트 데이터 전송
for( i = 0 ; i < 0xFFFF ; i++ )
{
// 입력 버퍼(포트 0x60)가 비었으면 커맨드 전송 가능
if( kIsInputBufferFull() == FALSE )
{
break;
}
}
// 커맨드 레지스터(0x64)에 출력 포트 설정 커맨드(0xD1)을 전달
kOutPortByte( 0x64, 0xD1 );
// 입력 버퍼(0x60)에 A20 게이트 비트가 1로 설정된 값을 전달
kOutPortByte( 0x60, bOutputPortData );
}
// 프로세서를 리셋(Reset)
void kReboot( void )
{
int i;
// 입력 버퍼(포트 0x60)에 데이터가 비어있으면 출력 포트에 값을 쓰는 커맨드와 출력 포트 데이터 전송
for( i = 0 ; i < 0xFFFF ; i++ )
{
// 입력 버퍼(포트 0x60)가 비었으면 커맨드 전송 가능
if( kIsInputBufferFull() == FALSE )
{
break;
}
}
// 커맨드 레지스터(0x64)에 출력 포트 설정 커맨드(0xD1)을 전달
kOutPortByte( 0x64, 0xD1 );
// 입력 버퍼(0x60)에 0을 전달하여 프로세서를 리셋(Reset)함
kOutPortByte( 0x60, 0x00 );
while( 1 )
{
;
}
}
////////////////////////////////////////////////////////////////////////////////
// 스캔 코드를 ASCII 코드로 변환하는 기능에 관련된 함수들
////////////////////////////////////////////////////////////////////////////////
// 키보드 상태를 관리하는 키보드 매니저
static KEYBOARDMANAGER gs_stKeyboardManager = { 0, };
// 스캔 코드를 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 }
};
// 스캔 코드가 알파벳 범위인지 여부를 반환
BOOL kIsAlphabetScanCode( BYTE bScanCode )
{
// 변환 테이블을 값을 직접 읽어서 알파벳 범위인지 확인
if( ( 'a' <= gs_vstKeyMappingTable[ bScanCode ].bNormalCode ) &&
( gs_vstKeyMappingTable[ bScanCode ].bNormalCode <= 'z' ) )
{
return TRUE;
}
return FALSE;
}
// 숫자 또는 기호 범위인지 여부를 반환
BOOL kIsNumberOrSymbolScanCode( BYTE bScanCode )
{
// 숫자 패드나 확장 키 범위를 제외한 범위(스캔 코드 2~53)에서 영문자가 아니면
// 숫자나 기호임
if( ( 2 <= bScanCode ) && ( bScanCode <= 53 ) &&
( kIsAlphabetScanCode( bScanCode ) == FALSE ) )
{
return TRUE;
}
return FALSE;
}
// 숫자 패드 범위인지 여부를 반환
BOOL kIsNumberPadScanCode( BYTE bScanCode )
{
// 숫자 패드는 스캔 코드의 71~83에 있음
if( ( 71 <= bScanCode ) && ( bScanCode <= 83 ) )
{
return TRUE;
}
return FALSE;
}
// 조합된 키 값을 사용해야 하는지 여부를 반환
BOOL kIsUseCombinedCode( BOOL bScanCode )
{
BYTE bDownScanCode;
BOOL bUseCombinedKey;
bDownScanCode = bScanCode & 0x7F;
// 알파벳 키라면 Shift 키와 Caps Lock의 영향을 받음
if( kIsAlphabetScanCode( bDownScanCode ) == TRUE )
{
// 만약 Shift 키와 Caps Lock 키 중에 하나만 눌러져있으면 조합된 키를 되돌려 줌
if( gs_stKeyboardManager.bShiftDown ^ gs_stKeyboardManager.bCapsLockOn )
{
bUseCombinedKey = TRUE;
}
else
{
bUseCombinedKey = FALSE;
}
}
// 숫자와 기호 키라면 Shift 키의 영향을 받음
else if( kIsNumberOrSymbolScanCode( bDownScanCode ) == TRUE )
{
// Shift 키가 눌러져있으면 조합된 키를 되돌려 줌
if( gs_stKeyboardManager.bShiftDown == TRUE )
{
bUseCombinedKey = TRUE;
}
else
{
bUseCombinedKey = FALSE;
}
}
// 숫자 패드 키라면 Num Lock 키의 영향을 받음
// 0xE0만 제외하면 확장 키 코드와 숫자 패드의 코드가 겹치므로,
// 확장 키 코드가 수신되지 않았을 때만처리 조합된 코드 사용
else if( ( kIsNumberPadScanCode( bDownScanCode ) == TRUE ) &&
( gs_stKeyboardManager.bExtendedCodeIn == FALSE ) )
{
// Num Lock 키가 눌러져있으면, 조합된 키를 되돌려 줌
if( gs_stKeyboardManager.bNumLockOn == TRUE )
{
bUseCombinedKey = TRUE;
}
else
{
bUseCombinedKey = FALSE;
}
}
return bUseCombinedKey;
}
// 조합 키의 상태를 갱신하고 LED 상태도 동기화 함
void UpdateCombinationKeyStatusAndLED( BYTE bScanCode )
{
BOOL bDown;
BYTE bDownScanCode;
BOOL bLEDStatusChanged = FALSE;
// 눌림 또는 떨어짐 상태처리, 최상위 비트(비트 7)가 1이면 키가 떨어졌음을 의미하고
// 0이면 떨어졌음을 의미함
if( bScanCode & 0x80 )
{
bDown = FALSE;
bDownScanCode = bScanCode & 0x7F;
}
else
{
bDown = TRUE;
bDownScanCode = bScanCode;
}
// 조합 키 검색
// Shift 키의 스캔 코드(42 or 54)이면 Shift 키의 상태 갱신
if( ( bDownScanCode == 42 ) || ( bDownScanCode == 54 ) )
{
gs_stKeyboardManager.bShiftDown = bDown;
}
// Caps Lock 키의 스캔 코드(58)이면 Caps Lock의 상태 갱신하고 LED 상태 변경
else if( ( bDownScanCode == 58 ) && ( bDown == TRUE ) )
{
gs_stKeyboardManager.bCapsLockOn ^= TRUE;
bLEDStatusChanged = TRUE;
}
// Num Lock 키의 스캔 코드(69)이면 Num Lock의 상태를 갱신하고 LED 상태 변경
else if( ( bDownScanCode == 69 ) && ( bDown == TRUE ) )
{
gs_stKeyboardManager.bNumLockOn ^= TRUE;
bLEDStatusChanged = TRUE;
}
// Scroll Lock 키의 스캔 코드(70)이면 Scroll Lock의 상태를 갱신하고 LED 상태 변경
else if( ( bDownScanCode == 70 ) && ( bDown == TRUE ) )
{
gs_stKeyboardManager.bScrollLockOn ^= TRUE;
bLEDStatusChanged = TRUE;
}
// LED 상태가 변했으면 키보드로 커맨드를 전송하여 LED를 변경
if( bLEDStatusChanged == TRUE )
{
kChangeKeyboardLED( gs_stKeyboardManager.bCapsLockOn,
gs_stKeyboardManager.bNumLockOn, gs_stKeyboardManager.bScrollLockOn );
}
}
// 스캔 코드를 ASCII 코드로 변환
BOOL kConvertScanCodeToASCIICode( BYTE bScanCode, BYTE* pbASCIICode, BOOL* pbFlags )
{
BOOL bUseCombinedKey;
// 이전에 Pause 키가 수신되었다면, Pause의 남은 스캔 코드를 무시
if( gs_stKeyboardManager.iSkipCountForPause > 0 )
{
gs_stKeyboardManager.iSkipCountForPause--;
return FALSE;
}
// Pause 키는 특별히 처리
if( bScanCode == 0xE1 )
{
*pbASCIICode = KEY_PAUSE;
*pbFlags = KEY_FLAGS_DOWN;
gs_stKeyboardManager.iSkipCountForPause = KEY_SKIPCOUNTFORPAUSE;
return TRUE;
}
// 확장 키 코드가 들어왔을 때, 실제 키 값은 다음에 들어오므로 플래그 설정만 하고 종료
else if( bScanCode == 0xE0 )
{
gs_stKeyboardManager.bExtendedCodeIn = TRUE;
return FALSE;
}
// 조합된 키를 반환해야 하는가?
bUseCombinedKey = kIsUseCombinedCode( bScanCode );
// 키 값 설정
if( bUseCombinedKey == TRUE )
{
*pbASCIICode = gs_vstKeyMappingTable[ bScanCode & 0x7F ].bCombinedCode;
}
else
{
*pbASCIICode = gs_vstKeyMappingTable[ bScanCode & 0x7F ].bNormalCode;
}
// 확장 키 유무 설정
if( gs_stKeyboardManager.bExtendedCodeIn == TRUE )
{
*pbFlags = KEY_FLAGS_EXTENDEDKEY;
gs_stKeyboardManager.bExtendedCodeIn = FALSE;
}
else
{
*pbFlags = 0;
}
// 눌러짐 또는 떨어짐 유무 설정
if( ( bScanCode & 0x80 ) == 0 )
{
*pbFlags |= KEY_FLAGS_DOWN;
}
// 조합 키 눌림 또는 떨어짐 상태를 갱신
UpdateCombinationKeyStatusAndLED( bScanCode );
return TRUE;
}
- Keyboard.h
#ifndef __KEYBOARD_H__
#define __KEYBOARD_H__
#include "Types.h"
// 매크로
// Pause 키를 처리하기 위해 무시해야 하는 나머지 스캔 코드의 수
#define KEY_SKIPCOUNTFORPAUSE 2
// 키 상태에 대한 플래그
#define KEY_FLAGS_UP 0x00
#define KEY_FLAGS_DOWN 0x01
#define KEY_FLAGS_EXTENDEDKEY 0x02
// 스캔 코드 매핑 테이블에 대한 매크로
#define KEY_MAPPINGTABLEMAXCOUNT 89
#define KEY_NONE 0x00
#define KEY_ENTER '\n'
#define KEY_TAB '\t'
#define KEY_ESC 0x1B
#define KEY_BACKSPACE 0x08
#define KEY_CTRL 0x81
#define KEY_LSHIFT 0x82
#define KEY_RSHIFT 0x83
#define KEY_PRINTSCREEN 0x84
#define KEY_LALT 0x85
#define KEY_CAPSLOCK 0x86
#define KEY_F1 0x87
#define KEY_F2 0x88
#define KEY_F3 0x89
#define KEY_F4 0x8A
#define KEY_F5 0x8B
#define KEY_F6 0x8C
#define KEY_F7 0x8D
#define KEY_F8 0x8E
#define KEY_F9 0x8F
#define KEY_F10 0x90
#define KEY_NUMLOCK 0x91
#define KEY_SCROLLLOCK 0x92
#define KEY_HOME 0x93
#define KEY_UP 0x94
#define KEY_PAGEUP 0x95
#define KEY_LEFT 0x96
#define KEY_CENTER 0x97
#define KEY_RIGHT 0x98
#define KEY_END 0x99
#define KEY_DOWN 0x9A
#define KEY_PAGEDOWN 0x9B
#define KEY_INS 0x9C
#define KEY_DEL 0x9D
#define KEY_F11 0x9E
#define KEY_F12 0x9F
#define KEY_PAUSE 0xA0
// 구조체
#pragma pack( push, 1 )
// 스캔 코드 테이블을 구성하는 항목
typedef struct kKeyMappingEntryStruct
{
// Shift 키나 Caps Lock 키와 조합되지 않는 ASCII 코드
BYTE bNormalCode;
// Shift 키나 Caps Lock 키와 조합된 ASCII 코드
BYTE bCombinedCode;
} KEYMAPPINGENTRY;
// 키보드의 상태를 관리하는 자료구조
typedef struct kKeyboardManagerStruct
{
// 조합 키 정보
BOOL bShiftDown;
BOOL bCapsLockOn;
BOOL bNumLockOn;
BOOL bScrollLockOn;
// 확장 키를 처리하기 위한 정보
BOOL bExtendedCodeIn;
int iSkipCountForPause;
} KEYBOARDMANAGER;
// 함수
BOOL kIsOutputBufferFull( void );
BOOL kIsInputBufferFull( void );
BOOL kActivateKeyboard( void );
BYTE kGetKeyboardScanCode( void );
BOOL kChangeKeyboardLED( BOOL bCapsLockOn, BOOL bNumLockOn, BOOL bScrollLockOn );
void kEnableA20Gate( void );
void kReboot( void );
BOOL kIsAlphabetScanCode( BYTE bScanCode );
BOOL kIsNumberOrSymbolScanCode( BYTE bScanCode );
BOOL kIsNumberPadScanCode( BYTE bScanCode );
BOOL kIsUseCombinedCode( BOOL bScanCode );
void UpdateCombinationKeyStatusAndLED( BYTE bScanCode );
BOOL kConvertScanCodeToASCIICode( BYTE bScanCode, BYTE* pbASCIICode, BOOL* pbFlags );
#endif /*__KEYBOARD_H__*/
2. 어셈블리어 유틸리티 파일 추가
- 어셈블리어 유틸리티 함수 소스 파일
- (2.Kernel64/Source)위치에 AssemblyUtility.asm파일을 작성한다.
[BITS 64] ; 이하의 코드는 64비트 코드로 설정 SECTION .text ; text 섹션(세그먼트)을 정의 ; C 언어에서 호출할 수 있도록 이름을 노출함(Export) global 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 ; 함수를 호출한 다음 코드의 위치로 복귀
- 어셈블리어 유틸리티 함수 헤더 파일
- (2.Kernel64/Source)위치에 AssemblyUtility.h파일을 작성한다.
#ifndef __ASSEMBLYUTILITY_H__ #define __ASSEMBLYUTILITY_H__ #include "Types.h" // 함수 BYTE kInPortByte( WORD wPort ); void kOutPortByte( WORD wPort, BYTE bData ); #endif /*__ASSEMBLYUTILITY_H__*/
3. C언어 커널 엔트리 포인트 파일 수정
- 키보드 컨트롤러와 키보드를 활성화하고 간단한 셸을 실행한다.
- 엔트리 포인트의 뒷부분에 키보드 드라이버 함수를 호출하는 코드를 추가한다.
- (2.Kernel64/Source/Main.c)를 수정한다.
#include "Types.h"
#include "Keyboard.h"
// 함수 선언
void kPrintString (int iX, int iY, const char* pcString);
// 아래 함수는 C언어 커널의 시작 부분임
void Main(void){
char vcTemp[ 2 ] = { 0, };
BYTE bFlags;
BYTE bTemp;
int i = 0;
kPrintString( 0, 10, "Switch To IA-32e Mode Success~!!" );
kPrintString( 0, 11, "IA-32e C Language Kernel Start..............[Pass]" );
kPrintString( 0, 12, "Keyboard Activate...........................[ ]" );
// 키보드를 활성화
if( kActivateKeyboard() == TRUE )
{
kPrintString( 45, 12, "Pass" );
kChangeKeyboardLED( FALSE, FALSE, FALSE );
}
else
{
kPrintString( 45, 12, "Fail" );
while( 1 ) ;
}
while( 1 )
{
// 출력 버퍼(포트 0x60)가 차 있으면 스캔 코드를 읽을 수 있음
if( kIsOutputBufferFull() == TRUE )
{
// 출력 버퍼(포트 0x60)에서 스캔 코드를 읽어서 저장
bTemp = kGetKeyboardScanCode();
// 스캔 코드를 ASCII 코드로 변환하는 함수를 호출하여 ASCII 코드와
// 눌림 또는 떨어짐 정보를 반환
if( kConvertScanCodeToASCIICode( bTemp, &( vcTemp[ 0 ] ), &bFlags ) == TRUE )
{
// 키가 눌러졌으면 키의 ASCII 코드 값을 화면에 출력
if( bFlags & KEY_FLAGS_DOWN )
{
kPrintString( i++, 13, vcTemp );
}
}
}
}
}
``` 생략 ```
'MINT64 OS 개발' 카테고리의 다른 글
13장. PIC 컨트롤러와 인터럽트 핸들러 이용해, 인터럽트 처리 (0) | 2023.07.21 |
---|---|
12장. GDT, IDT 테이블, TSS 세그먼트를 추가해 인터럽트에 대비하자 (0) | 2023.07.21 |
10장. 64비트 모드로 전환 (0) | 2023.07.20 |
9장. 페이징 기능을 활성화하여, 64비트 전환 준비 (0) | 2023.07.20 |
8장. A20 게이트를 활성화하여 1MB이상 영역에 접근 (0) | 2023.07.20 |