본문 바로가기

MINT64 OS 개발

11장. 키보드 디바이스 드라이버 추가

Ⅰ. 키보드 컨트롤러 제어

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 );
                }
            }
        }
    }
}

``` 생략 ```