키보드 입력 받기

키보드 입력 받기

2025년 5월 8일

키보드 지원 #

사실 이미 인터럽트로 마우스를 움직이기 시작했을 때 추가했던 xhci 드라이버에 키보드도 추가되어 있다. 드라이버는 나중에 구현해보기로 하고 지금은 구현만 확인하겠다.


키보드 입력 구현 #

인터럽트 복습 #

인터럽트 등록은 개발자가 검색된 PCI 기기에 MSI Addr, MSI Data를 등록해서 인터럽트가 발생하면 어떤 메모리에 데이터를 써서 알림을 줘야하는지 알려준다.

주소에 CPU Core의 번호도 적혀있기 때문에 인터럽트가 발생하면 나다 싶은 LAPIC가 MSA Data에서 벡터번호를 꺼내 옆에있는 CPU Core에 전달해서 작업이 시작된다.

CPU는 IDT베이스(=IDTR) + 인터럽트 벡터 * sizeof(descriptor) 로 IDT에 접근해 어떤 게이트 디스크립터인지 알아올 수 있고 여러 작업(ring이나 컨텍스트 전환) 후 ISR(인터럽트 핸들러)을 실행시킨다.

ISR에서는 메시지 큐에 XHCI 인터럽트 메시지를 넣고, 메인 루프에서 이 메시지를 처리하며 xhci 드라이버 코드를 실행한다.

1ProcessEvent > OnEvent 
2> OnTransferEventReceived > OnInterruptCompleted 
3------ 여기서부터 Mouse or Keyboard -------
4> HIDKeyboardDriver::OnDataReceived 
5> NotifyKeyPush > default_observer(modifier, keycode)

구현 코드 #

복습은 끝났고 이제 코드를 보자. 아주 쉽다.

 1namespace {
 2
 3// 일단 이미 USB HID에서 정의된 keycode로 각 문자를 얻어오는 테이블을 만든다.
 4// www.usb.org/sites/default/files/hut1_21_0.pdf
 5const char keycode_map[256] = {
 6  0,    0,    0,    0,    'a',  'b',  'c',  'd', // 0
 7  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l', // 8
 8  'm',  'n',  'o',  'p',  'q',  'r',  's',  't', // 16
 9  'u',  'v',  'w',  'x',  'y',  'z',  '1',  '2', // 24
10  '3',  '4',  '5',  '6',  '7',  '8',  '9',  '0', // 32
11  '\n', '\b', 0x08, '\t', ' ',  '-',  '=',  '[', // 40
12  ']', '\\',  '#',  ';', '\'',  '`',  ',',  '.', // 48
13  '/',  0,    0,    0,    0,    0,    0,    0,   // 56
14  0,    0,    0,    0,    0,    0,    0,    0,   // 64
15  0,    0,    0,    0,    0,    0,    0,    0,   // 72
16  0,    0,    0,    0,    '/',  '*',  '-',  '+', // 80
17  '\n', '1',  '2',  '3',  '4',  '5',  '6',  '7', // 88
18  '8',  '9',  '0',  '.', '\\',  0,    0,    '=', // 96
19};
20
21// 이건 쉬프트 키를 눌렀을때. 대문자가 표시된다.
22const char keycode_map_shifted[256] = {
23  0,    0,    0,    0,    'A',  'B',  'C',  'D', // 0
24  'E',  'F',  'G',  'H',  'I',  'J',  'K',  'L', // 8
25  'M',  'N',  'O',  'P',  'Q',  'R',  'S',  'T', // 16
26  'U',  'V',  'W',  'X',  'Y',  'Z',  '!',  '@', // 24
27  '#',  '$',  '%',  '^',  '&',  '*',  '(',  ')', // 32
28  '\n', '\b', 0x08, '\t', ' ',  '_',  '+',  '{', // 40
29  '}',  '|',  '~',  ':',  '"',  '~',  '<',  '>', // 48
30  '?',  0,    0,    0,    0,    0,    0,    0,   // 56
31  0,    0,    0,    0,    0,    0,    0,    0,   // 64
32  0,    0,    0,    0,    0,    0,    0,    0,   // 72
33  0,    0,    0,    0,    '/',  '*',  '-',  '+', // 80
34  '\n', '1',  '2',  '3',  '4',  '5',  '6',  '7', // 88
35  '8',  '9',  '0',  '.', '\\',  0,    0,    '=', // 96
36};
37
38// 특수 키에 해당하는 비트마스크
39const int kLControlBitMask  = 0b00000001u;
40const int kLShiftBitMask    = 0b00000010u;
41const int kLAltBitMask      = 0b00000100u;
42const int kLGUIBitMask      = 0b00001000u;
43const int kRControlBitMask  = 0b00010000u;
44const int kRShiftBitMask    = 0b00100000u;
45const int kRAltBitMask      = 0b01000000u;
46const int kRGUIBitMask      = 0b10000000u;
47
48}
49
50// 키보드의 default_observer 를 넣어주기만 하는 초기화 함수.
51// 그냥 메세지 큐에 키보드 데이터를 포함해서 넣어줌. 실제 처리는 메인루프에서
52void InitializeKeyboard(std::deque<Message>& msg_queue) {
53  usb::HIDKeyboardDriver::default_observer =
54    [&msg_queue](uint8_t modifier, uint8_t keycode) {
55      const bool shift = (modifier & (kLShiftBitMask | kRShiftBitMask)) != 0;
56      char ascii = keycode_map[keycode];
57      if (shift) {
58        ascii = keycode_map_shifted[keycode];
59      }
60      Message msg{Message::kKeyPush};
61      msg.arg.keyboard.modifier = modifier;
62      msg.arg.keyboard.keycode = keycode;
63      msg.arg.keyboard.ascii = ascii;
64      msg_queue.push_back(msg);
65    };
66}

메인루프를 보면 그냥 printk로 입력받은 문자를 출력하는거 말고는 없다.

 1  // event loop
 2  while (true) {
 3    // ...
 4    switch (msg.type) {
 5    case Message::kKeyPush:
 6      if (msg.arg.keyboard.ascii != 0) {
 7        printk("%c", msg.arg.keyboard.ascii);
 8      }
 9      break;
10    default:
11      Log(kError, "Unknown message type: %d\n", msg.type);
12    }
13  }

4d58a434-8941-4f7e-8f87-51798c2ffe08


드라이버 코드 #

이 드라이버는 리포트 디스크립터를 사용하지 않고 간단하게 부트 인터페이스를 사용해서 마우스나 키보드의 데이터를 읽어오는데, 리포트 디스크립터를 사용하도록 만들면 좋을 것 같다.

modifier keycode 1 keycode 2 keycode 3 키보드 조작
0b00000000 0x00 0x00 0x00 초기상태
0b00000000 0x04 0x00 0x00 A 키를 누름
0b00000010 0x04 0x00 0x00 Shift키를 누름
0b00000010 0x04 0x1b 0x00 X 키를 누름
0b00000010 0x04 0x05 0x1b B 키를 누름
0b00000010 0x05 0x1b 0x00 A 키를 뗌
 1  Error HIDKeyboardDriver::OnDataReceived() {
 2    for (int i = 2; i < 8; ++i) {
 3      const uint8_t key = Buffer()[i];
 4      if (key == 0) {
 5        continue;
 6      }
 7      const auto& prev_buf = PreviousBuffer();
 8      // 이번에 새롭게 눌러진 키를 찾는 작업이다.
 9      // Buffer() 구조
10      // - 0: modifier
11      // - 1: reserved
12      // - 2-7: keycode (먼저눌렀다고 앞에 있는건 아니라서 전체검색 필요) 
13      if (std::find(prev_buf.begin() + 2, prev_buf.end(), key) != prev_buf.end()) {
14        continue;
15      }
16      // Buffer()의 0번째는 특수키(modifier)이다. 
17      NotifyKeyPush(Buffer()[0], key);
18    }
19    return MAKE_ERROR(Error::kSuccess);
20  }

comments powered by Disqus