반응형

→ 키보드 컨트롤러, I/O 포트, 레지스터

 

키보드 컨트롤러PC 내부 버스(BUS)와 포트 I/O 방식으로 연결되어 있으며, 포트 주소는 0x60과 0x64를 사용합니다.

 

실제로 할당된 포트는 2개지만 포트에서 데이터를 읽을 때와 쓸 때 접근하는 레지스터가 다르므로, 사실상 4개의 레지스터와 연결된 것과 같습니다.

 

아래의 사진은 PC 내부 버스와 키보드 컨트롤러, 키보드와 마우스의 연결을 나타낸 것이고

 

아래의 표는 읽기와 쓰기를 수행할 때 각 포트로 접근 가능한 키보드 컨트롤러의 내부 레지스터를 나타낸 것입니다.

 

키보드와 마우스, 키보드 컨트롤러, PC의 관계

 

I/O 포트와 키보드 컨트롤러 레지스터의 관계

 

키보드 레지스터는 총 4개가 있으며 모두 1byte입니다.

 

1. 컨트롤 레지스터

2. 상태 레지스터

3. 입력 버퍼

4. 출력 버퍼

 

이 중 상태 레지스터키보드 컨트롤러의 상태를 표시하는 레지스터키보드 컨트롤러로부터 키 값을 읽거나 쓰려면 반드시 체크해야 하는 비트를 포함하기 때문에 키보드 컨트롤러와 사용자에게 중요한 역할을 하고 있습니다.

 

아래는 상태 레지스터가 나타내는 비트의 의미를 정리한 것입니다.

 

상태 레지스터의 비트 구성과 의미

 

사실 키보드 컨트롤러로부터 키 값을 얻는 것이 목적이라면 상태 레지스터(포트 0x64)를 읽어서 OUTB 비트가 1인지 검사하고 나서 출력 버퍼 레지스터(포트 0x60)를 읽는 것만으로 충분하지만

키보드 컨트롤러를 통해 키보드와 마우스를 제어해야 하므로 키보드 컨트롤러의 커맨드에 대해서 자세히 알아야 합니다.

 

아래는 키보드, 마우스, A20 게이트, 프로세서 리셋과 관련된 커맨드를 정리한 것입니다

 

키보드와 마우스, 기타 시스템 제어에 관련된 키보드 컨트롤러 커맨드

 

인터럽트 활성화에 관련된 0x20, 0x60 커맨드마우스 디바이스와 관련된 0xA7, 0xA8, 0xD4 커맨드는 나중에 알아보고, 키보드에서 데이터를 읽는 방법과 더불어 키보드 및 시스템 제어에 관련된 커맨드를 중심으로 알아봅니다.

 


→ 키보드 컨트롤러 제어

 

일반적으로 부트 로더가 실행되기 전에 키보드는 이미 BIOS에 의해 활성화된 상태입니다.

 

그러므로 키보드를 활성화하는 단계를 굳이 수행하지 않아도 키보드에서 키 값을 읽는데 아무 문제가 없습니다.

 

하지만, 만약을 대비하여 키보드를 직접 활성화합니다.

 

 

키보드 컨트롤러에서 키보드 디바이스를 사용 가능하게 하려면, 커맨드 포트로 키보드 디바이스 활성화 커맨드인 0xAE를 보내면 되지만, 이것은 '키보드 컨트롤러'에서 활성화된 것이지 실제 키보드가 활성화된 것이 아닙니다.

 

키보드 컨트롤러와 키보드는 ps/2 방식의 케이블로 연결되어 있고, PC의 외부에 존재하기 때문에 키보드에도 활성화 커맨드를 보내줘야 합니다.

 

 

키보드에 직접 데이터를 보내는 방법커맨드를 전송하지 않고, 입력 버퍼에 키보드로 보낼 커맨드를 직접 쓰면 됩니다.

 

키보드는 키보드 컨트롤러와 달리 커맨드나 데이터에 대한 응답이 전송되며, 정상적으로 처리할 경우 ACK(0xFA)를 전송합니다.

 

만일 ACK가 수신되지 않으면 수행 도중 에러가 발생한 것이므로, 재시도하거나 작업을 포기해야 합니다.

 

아래는 키보드 커맨드 중에서 키보드 활성화와 LED 제어에 대한 커맨드를 정리한 것입니다.

 

LED와 키보드 활성화에 관련된 키보드 커맨드

 

키보드 컨트롤러의 키보드 활성화커맨드 포트에 0xAE를 보내면 됩니다.

 

키보드 컨트롤러커맨드를 보냈다면 이제 키보드에 직접 커맨드를 보내는 일만 남았습니다.

 

키보드로 커맨드를 보내려면 입력 버퍼의 상태 처리와 키보드의 응답 처리를 해야 하므로 약간 까다롭습니다.

 

 

키보드키보드 컨트롤러프로세서보다 아주 느리게 동작하므로 프로세서가 커맨드를 전송하고 한참을 기다려야 수행이 완료됩니다.

 

여기서 문제는 커맨드가 완료될 때까지 얼마나 기다려야 하는 가입니다.

 

커맨드를 처리하는 시간키보드와 키보드 컨트롤러의 상태에 따라 가변적인 부분이므로 키보드 컨트롤러의 상태를 확인할 수 있어야 하는데, 이때 사용하는 것이 키보드 컨트롤러의 상태 레지스터(포트 0x64)입니다.

 

 

상태 레지스터입력 버퍼 상태를 표시하는 비트(비트 1)와 출력 버퍼의 상태를 표시하는 비트(비트 0)가 있습니다.

 

입력 버퍼 상태 비트(비트 1)를 통해 입력 버퍼가 비었는지 확인한 후 키보드 커맨드를 송신하고, 출력 버퍼 상태 비트(비트 0)를 통해 출력 버퍼에 데이터가 있는지 확인한 후 실행 결과를 읽음으로써 보다 효율적으로 처리할 수 있습니다.

 

아래는 입력 버퍼 상태 비트와 출력 버퍼 상태 비트를 이용하여 키보드를 활성화하는 코드입니다.

 

입력 포트의 상태와 출력 포트의 상태를 확인하는 코드는 앞으로 자주 사용할 것이므로 함수로 분리하여 작성합니다.

 

키보드 컨트롤러와 키보드를 활성화하는 코드 1

 

키보드 컨트롤러와 키보드를 활성화하는 코드 2

 

위 코드에서 입력 버퍼의 상태출력 버퍼의 상태를 확인할 때 루프를 0xFFFF만큼 수행하고 있지만, 루프 횟수는 꼭 0xFFFF가 아니어도 됩니다.

 

입력 버퍼에 데이터가 비거나 출력 버퍼에 데이터가 찰 만큼의 시간이면 충분합니다.

 

2byte 정수의 최댓값인 0xFFFF를 사용한 이유이 정도면 충분하다고 생각하기 때문입니다.

 

키보드로부터 커맨드 처리 상태(ACK)가 올 때까지 루프를 100회 수행하는 것도 임의로 선택한 것입니다.

 

키보드는 커맨드 또는 데이터를 수신할 때마다 키보드 컨트롤러로 응답 코드를 전송하므로 키보드로 커맨드나 데이터를 전송하고 나서 처리 상태를 확인해야 합니다.

 

키보드는 커맨드를 성공적으로 처리했을 때 ACK(0xFA)를 전송하는데, 이때 ACK가 전달되기 전에 몇몇 키 값이 이미 출력 버퍼에 들어 있을 수 있기 때문에 출력 버퍼를 계속 읽어 뒤쪽에 삽입된 ACK(0xFA)를 찾아야 합니다.

 

루프 100회는 바로 출력 버퍼에서 삽입된 데이터를 계속 읽어 내는 횟수인 것으로, 100회 정도면 출력 버퍼에 들어있는 데이터를 전부 읽고도 남는 횟수이므로 ACK를 찾기에 충분합니다.

 

 

위 코드에서 사용한 kOutPortByte(), kInPortByte() 함수어셈블리어의 OUT 명령어와 IN 명령어를 수행하는 어셈블리어 함수입니다.

 

IN 명령어포트 I/O 주소에서 데이터를 읽어오는 역할이며, 포트 I/O 주소를 지정하는데 DX 레지스터를 사용하며 포트에서 값을 읽어 AX 레지스터에 저장합니다.

 

OUT 명령어는 IN 명령어와 반대로 포트 I/O 주소에 데이터를 출력하는 역할이며, 기능은 반대지만 포트 I/O 주소에는 DX 레지스터를 사용하며, 값을 보내는 레지스터로는 AX 레지스터를 사용합니다.

 

포트에서 읽고 쓸 데이터의 크기는 어떤 AX 레지스터를 사용하느냐에 따라서 달라지고, EAX 레지스터를 사용할 경우 32bit(4byte), AX 레지스터는 16bit(2byte), AL 레지스터는 8bit(1byte) 데이터를 처리할 수 있습니다.

 

키보드 컨트롤러포트로 한 바이트만 처리할 수 있으므로, 여기서는 AL 레지스터를 사용합니다.

 

아래는 kInPortByte(), kOutPortByte() 함수의 코드입니다.

 

kInPortByte() 함수와 kOutPortByte() 함수의 코드

 

위의 코드를 보면 보호 모드와 달리 파라미터를 스택에서 넘겨받지 않고 RDI나 RSI 레지스터로 넘겨받습니다.

 

이는 보호 모드와 IA-32e 모드의 함수 호출 규약이 서로 다르기 때문입니다.

 


 

→ IA-32e 모드의 호출 규약

 

C 코드에서 어셈블리어 함수를 호출하려면 호출 규약을 지켜야 합니다.

 

보호 모드 호출 규약과 IA-32e 모드 호출 규약의 차이점을 위주로 살펴봅니다.

 

IA-32e 모드의 C 호출 규약보호 모드의 C 호출 규약크게 3가지 부분에서 차이가 납니다.

 

1. 파라미터를 전달할 때 레지스터를 우선으로 사용한다는 것입니다.

2. 레지스터 또는 스택에 파라미터를 삽입하는 순서입니다.

3. 함수의 반환 값으로 사용하는 레지스터입니다.

 

▶ 파라미터를 전달할 때 레지스터를 우선으로 사용한다는 것입니다.

파라미터로 사용하는 레지스터는 파라미터의 타입에 따라 다릅니다.

 

정수 타입의 파라미터의 경우 RDI, RSI, RDX, RCX, R8, R9 레지스터의 순서로 모두 6개를 사용하며

 

실수 타입의 파라미터의 경우 XMM0 ~ XMM7 레지스터의 순서로 모두 8개를 사용합니다.

 

파라미터의 수가 정해진 레지스터의 수를 넘으면 보호 모드와 마찬가지로 스택 영역을 사용합니다.

 

 

▶ 레지스터 또는 스택에 파라미터를 삽입하는 순서입니다.

보호 모드의 경우 파라미터 리스트의 오른쪽에서 왼쪽으로 이동하면서 파라미터를 스택에 삽입했지만, 64bit 모드에서는 파라미터 리스트의 왼쪽에서 오른쪽으로 이동하면서 레지스터나 스택을 사용하여 삽입합니다.

 

 

▶ 함수의 반환 값으로 사용하는 레지스터입니다.

보호 모드는 EAX 레지스터를 사용하여 반환 값을 처리하지만, 64bit 모드는 정수 타입이면 RAX 또는 RDX 레지스터를 사용하고 실수 타입은 XMM0 또는 XMM0과 XMM1 레지스터를 사용합니다.

 

 

참고)

반환 값으로 RAX, RDX 레지스터와 XMM0, XMM1 레지스터를 사용하게 되어 있지만, 일반적으로 RAX 레지스터와 XMM0 레지스터를 사용합니다.

그래서 FS64 OS의 어셈블리어 함수도 반환 값으로 RAX 레지스터와 XMM0 레지스터만 사용하도록 작성되었습니다.

XMM0~7 레지스터는 실수 연산에 관련된 FPU(Floating Point Unit)에 관련된 레지스터입니다.

FPU는 나중에 다시 살펴볼 것입니다.

 

 

이러한 차이 때문에 어셈블리어 코드가 비교적 스택을 덜 사용하는 구조를 하게 된 것입니다.

 

파라미터 전달과 반환 값 전달 방식이 기존의 보호 모드와 다르지만, 이러한 것 외에 나머지는 보호 모드와 거의 동일합니다.

 

아래는 이해를 돕기 위해 호출 규약을 그림으로 나타낸 것입니다.

 

IA-32e 모드의 호출 규약

 

이제 다시 본론으로 돌아가 키보드 컨트롤러에서 키 값을 읽는 방법키보드 컨트롤러로 A20 게이트를 활성화하는 방법, 프로세서를 리셋하는 방법 그리고 키보드 LED를 제어하는 방법을 알아봅니다.

 


 

→ 키보드 컨트롤러에서 키 값 읽기

 

키보드는 키가 눌리거나 떨어질 때마다 키 별로 할당된 특수한 값을 키보드 컨트롤러로 전달하며, 이 값을 스캔 코드(Scan Code)라고 합니다.

 

먼저 키보드 컨트롤러에서 키 값을 읽어오는 방법에 대해 알아봅니다.

 

별다른 커맨드를 키보드 컨트롤러로 보내지 않으면, 키보드 컨트롤러의 출력 버퍼에는 키보드 또는 마우스에서 수신된 데이터가 저장됩니다.

 

그렇기에 상태 레지스터를 읽어서 출력 버퍼에 데이터가 있는지 확인한 후, 데이터가 있다면 출력 버퍼를 읽어서 저장하면 됩니다.

 

아래는 이러한 내용을 바탕으로 작성된 키 값을 읽어 들이는 코드입니다.

 

앞의 키보드 활성화 코드와 거의 같습니다.

 

키보드 컨트롤에서 키 값(스캔 코드)을 읽는 코드

 


→ A20 게이트 활성화와 프로세스 리셋

 

위의 키보드와 마우스, 키보드 컨트롤러, PC의 관계 그림에서 볼 수 있듯, 키보드 컨트롤러의 출력 포트는 키보드와 마우스 외에 A20 게이트와 프로세서 리셋에 관련된 라인과도 연결되어 있습니다.

 

이것은 출력 포트의 해당 비트를 1로 설정해서 A20 게이트를 활성화하거나 프로세서를 리셋할 수 있다는 것을 의미합니다.

 

A20 게이트 비트와 프로세서 리셋 비트는 출력 포트의 비트 1과 비트 0에 있습니다.

 

그리고 키보드 컨트롤러의 출력 포트는 0xD0, 0xD1 커맨드로 접근할 수 있습니다.

(키보드와 마우스, 기타 시스템 제어에 관련된 키보드 컨트롤러 커맨드 표를 참고)

 

A20 게이트를 활성화하는 방법프로세서를 리셋하는 방법출력 포트의 데이터를 0으로 설정하는 것만 다를 뿐, 나머지 코드는 같으므로 A20 게이트를 활성화하는 코드만 설명하고 넘어갑니다.

 

 

아래는 A20 게이트를 활성화하는 코드를 나타낸 것입니다.

 

출력 포트에서 데이터를 읽어오는 0xD0 커맨드로 현재 출력 포트의 값을 읽고 나서 비트 1을 1로 설정하여 A20 게이트 비트만 변경했습니다.

 

그리고 그 값을 다시 0xD1 커맨드 키보드 컨트롤러의 출력 포트에 보내어 A20 게이트를 활성화했습니다.

 

키보드 컨트롤러를 통해 A20 게이트를 활성화하는 코드

 


 

→ 키보드 LED 상태 제어

 

키보드의 LED 상태를 변경하는 방법키보드를 활성화하는 방법과 아주 비슷합니다.

 

차이점이라면 커맨드 포트를 사용하지 않고 입력 버퍼만을 사용한다는 점입니다.

 

키보드의 LED 상태를 변경하려면 입력 버퍼(포트 0x60)로 0xED 커맨드를 전송해서 키보드에 LED 상태 데이터가 전송될 것임을 미리 알려야 합니다.

 

그리고 키보드가 커맨드를 잘 처리했는지 ACK를 확인하고 나서 LED 상태를 나타내는 데이터를 전송합니다.

 

키보드로 데이터를 전송하고 나서 잘 처리되었는지 확인해야 하므로 이를 확인한 후 성공 여부를 판단합니다.

 

LED 상태 데이터는 1byte 중 하위 3bit만을 사용하며, Caps Lock은 비트 2, Num Lock은 비트 1, Scroll Lock은 비트 0에 할당되어 있습니다.

 

키보드 LED를 켜려면 해당 비트를 1로 설정하면 되고, 반대로 끄려면 0으로 설정하면 됩니다.

 

다음은 키보드의 상태 LED를 제어하는 코드를 나타낸 것입니다.

 

LED의 On/Off 상태를 파라미터로 넘겨받아 처리합니다.

 

키보드의 상태 LED를 제어하는 코드 1

 

키보드의 상태 LED를 제어하는 코드 2

 

 

지금까지 키보드 컨트롤러와 키보드를 제어하는 방법에 대해서 알아봤습니다.

반응형

+ Recent posts