Работа с клавиатурой AT и PS/2

В заметке об обработке прерываний мы столкнулись с контроллером клавиатуры. Работа с контроллером клавиатуры заслуживает отдельной заметки. Вот она.

Литература

Сразу представлю список источников:

  • Adam Chapweske — The AT-PS/2 Keyboard Interface — 2001.
  • Ying-Wen Bai and Hsiu-Chen Chen — Design of Implementation of a Compatible Keyboard Controller for Keyboards and Mice — 2006.
  • Всеволод Несвижский — Программирование аппаратных средств в Windows — БХВ-Петербург, 2004. Глава 3 — Клавиатура, раздел 3.3. Использование портов.
  • А. И. Поворознюк — Архитектура компьютеров. Архитектура микропроцессорного ядра и системных устройств. Часть 1 — Харьков: Торнадо, 2004. Глава 9 — Клавиатура.
  • Beyond Logic — Interfacing the AT keyboard

Типы клавиатур

В литературе я встречал упоминания нескольких типов клавиатур:

  • IBM PC/XT. Эта клавиатура появилась в 1981 году. Морально устаревшая и несовместимая с последующими типами клавиатур. Контроллер клавиатуры реализован на микросхеме i8255. Для работы с контроллером клавиатуры процессор использовал порты ввода-вывода 0x60 и 0x61. Клавиатура использует набор скан-кодов 1 (о скан-кодах см. ниже).
  • IBM PC/AT и IBM PS/2. Появились в 1984 и 1987 годах. Поддерживаются во всех современных компьютерах. Контроллер клавиатуры реализован на микросхеме i8042. Клавиатура PS/2 мало отличается от клавиатуры AT, они между собой совместимы (и несовместимы с клавиатурой XT). Процессор может посылать контроллеру клавиатуры и самой клавиатуре различные команды (об этом читайте ниже). Клавиатура AT использует набор скан-кодов 2, клавиатура PS/2 поддерживает набор 2 и набор 3. Контроллер клавиатуры позволяет транслировать наборы скан-кодов 2 и 3 в набор 1. Для работы с контроллером клавиатуры процессор использует порты ввода-вывода 0x60 и 0x64.
  • USB. Программирование работы с USB-клавиатурой требует существенно больше усилий, чем с PS/2 [Chapweske]. Впрочем, согласно статье USB Human Interface Devices, некоторые чипсеты поддерживают эмуляцию клавиатуры PS/2:

    Some chipsets support emulating USB keyboards and mice as standard PS/2 devices, but many chipsets don’t.

В этой заметке мы рассмотрим работу с клавиатурой, совместимой с AT-PS/2.

Действующие лица

При работе с клавиатурой мы имеем следующих участников:

  • Процессор. Обрабатывает прерывание IRQ1 от контроллера клавиатуры. Общается с контроллером клавиатуры используя порты ввода-вывода 0x64 и 0x60.
  • Контроллер клавиатуры. Расположен на материнской плате компьютера. Представляет собой микросхему i8042. Общается с процессором по системной шине (порты ввода-вывода 0x64 и 0x60). Генерирует прерывание IRQ1. Общается с клавиатурой по протоколу, разработанному фирмой IBM: принимает от нее скан-коды при нажатии и отпускании клавиш. Может принимать команды от процессора (через порт 0x64) и выполнять их. Может пересылать команды, принимаемые от процессора (через порт 0x60) клавиатуре.
  • Клавиатура. Подключена к компьютеру через разъем PS/2. Внутри содержит микросхему i8048, которая аналогична микросхеме i8042, но выполняет другие функции, а именно — сканирует клавиши клавиатуры на предмет нажатости (клавиши являются ключами, которые при нажатии замыкают линии матрицы; подробнее читайте [Поворознюк, раздел 9.2. Блок клавиатуры]). При нажатии или отпускании клавиши i8048 посылает соответствующий скан-код контролеру клавиатуры (расположенному на материнской плате компьютера). Скан-коды хранятся во внутреннем ПЗУ микросхемы i8048.

В дальнейшем важно не путать понятия контроллер клавиатуры (микросхема, расположенная на материнской плате компьютера) и клавиатура (устройство с клавишами, которое подключается к системному блоку).

Скан-коды

Когда пользователь нажимает клавишу на клавиатуре, клавиатура посылает контроллеру клавиатуры т. н. скан-код — один или несколько байт, которые содержат информацию о том, какая клавиша была задействована и что именно произошло (нажали ее или отпустили). То есть при нажатии клавиши клавиатура посылает один скан-код (make), а при отпускании — другой (break). Существует три набора скан-кодов, которые так и называются: набор 1, набор 2 и набор 3 (scan codes set 1, set 2, set 3). Клавиатура XT поддерживает только набор 1. Для клавиатур, совместимых с AT-PS/2, гарантированным является только набор 2 [Chapweske], однако контроллер клавиатуры умеет преобразовывать набор 2 в набор 1 (эту опцию контролера клавиатуры можно включать и выключать, об этом ниже).

Scan codes set 1

Большинство скан-кодов набора 1 (как нажатия, так и отпускания) состоит из одного байта. Код отпускания формируется так: break = make AND 0x80, где break — код отпускания, make — код нажатия, AND — побитовое И. Встречаются и двухбайтовые коды. Если код двухбайтовый, то первым байтом всегда является байт 0xE0. В таблице ниже приведено несколько примеров. Особняком стоят клавиши Insert и Pause. У Insert скан-код состоит из четырех байт, у Pause — из шести, причем кода отпускания у Pause нет.

Клавиша Код нажатия (make) Код отпускания (break)
A 0x1E 0x9E
Insert 0xE0, 0x52 0xE0, 0xD2
Print Screen 0xE0, 0x2A, 0xE0, 0x37 0xE0, 0xB7, 0xE0, 0xAA
Pause 0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5

Scan codes set 2

В наборе 2 большинство скан-кодов нажатия (make) однобайтовые. Коды же отпускания состоят из двух байтов, первый из которых 0xF0, а второй совпадает со скан-кодом нажатия. Как и в наборе 1, в наборе 2 встречаются двухбайтовые коды нажатия. И так же как в наборе 1, в наборе 2 первым байтом в двухбайтовом коде нажатия является 0xE0. Если код нажатия двухбайтовый, то код отпускания — трехбайтовый, причем байты идут в следующем порядке: 0xE0, 0xF0, второй байт скан-кода нажатия. И снова особняком стоят клавиши Insert и Pause. У Insert код нажатия состоит их четырех байт, код отпускания — из шести. У Pause код нажатия состоит из восьми байт, а кода отпускания нет вовсе. В таблице ниже приведены примеры скан-кодов набора 2.

Клавиша Код нажатия (make) Код отпускания (break)
A 0x1C 0xF0, 0x1C
Insert 0xE0, 0x70 0xE0, 0xF0, 0x70
Print Screen 0xE0, 0x12, 0xE0, 0x7C 0xE0, 0xF0, 0x7C, 0xE0, 0xF0, 0x12
Pause 0xE1, 0x14, 0x77, 0xE1, 0xF0, 0x14, 0xF0, 0x77

Scan codes set 3

Набор 3 выглядит лучше, чем два его предшественника, поскольку он более регулярный: в нем все коды нажатия однобайтовые, а все коды отпускания двухбайтовые (первый байт — 0xF0, второй — код нажатия). Примеры приведены в таблице ниже.

Клавиша Код нажатия (make) Код отпускания (break)
A 0x1C 0xF0, 0x1C
Insert 0x67 0xF0, 0x67
Print Screen 0x57 0xF0, 0x57
Pause 0x62 0xF0, 0x62

Преобразование скан-кодов в ASCII коды

Самый простой вариант преобразования скан-кодов в коды ASCII — это использование массива, элементами которого являются ASCII коды, а индексами — скан-коды. Чтобы описать такой массив для набора скан-кодов 2 на языке ассемблера, мне пришлось скопировать таблицу скан-кодов из Интернета в Microsoft Excel, отсортировать ее по возрастанию скан-кода и, поскольку скан-коды идут не подряд, заполнить пустые места нулевыми символами. Столбец таблицы, в котором содержались ASCII коды, я скопировал в файл с исходным кодом на языке ассемблера и получился вот такой массив:

SCAN_CODE_SET2 db 0,0,0,0,0,0,0,0,0,0,0,0,0,126,0,0,0,0,0,0,'Q','1',0,0,0,'Z','S','A','W','2',0,0,'C','X','D','E','4','3',0,0,' ','V'
               db 'F','T','R','5',0,0,'N','B','H','G','Y','6',0,'J',0,'M',0,'U','7','8',0,0,44,'K','I','O','0','9',0,0,'.','/','L',59,'P','-',0,0,0
               db 39,0,'[','=',0,0,0,0,0,']',0,92,0,0,0,0,0,0,0,0,0,0,0,'1',0,'4','7',0,0,0,'0','.','2','5','6','8',0,0,'0','+','3','-','*','9',0,0,0,0,0,0

Порты ввода-вывода 0x64 и 0x60

Как я уже говорил, для общения с контроллером клавиатуры процессор использует порты ввода-вывода 0x64 и 0x60 (эти порты связаны с несколькими регистрами контроллера клавиатуры; размер каждого из регистров — 1 байт). В таблице ниже показано, что означают операции чтения и записи в эти порты.

Порт Доступ Описание Ассемблерная команда
0x60 Чтение Читать выходной буфер контроллера клавиатуры in al, 0x60
0x60 Запись Писать во входной буфер контроллера клавиатуры out 0x60, al
0x64 Чтение Читать регистр статуса контроллера клавиатуры in al, 0x64
0x64 Запись Послать команду контроллеру клавиатуры out 0x64, al

Как отмечает в своей статье Adam Chapweske, существует путаница в понятиях «выходной буфер» и «входной буфер». Причем путаница присутствует и в его статье (использование им флагов IBF и OBF не соответствует его же понятиям о входном и выходном буферах). Мы примем следующие определения: если процессор считывает данные из порта 0x60 (это могут быть данные, присылаемые клавиатурой контроллеру клавиатуры, либо данные, которые контроллер клавиатуры формирует самостоятельно в ответ на команду от процессора), то он считывает их из выходного буфера контроллера клавиатуры; если процессор записывает данные в порт 0x60 (он может делать это, что послать данные клавиатуре либо чтобы передать аргумент для команды, которую он послал контроллеру клавиатуры — о командах читайте раздел ниже), то он записывает их во входной буфер контроллера клавиатуры.

Нам понадобится как минимум читать выходной буфер контроллера клавиатуры, потому что именно так мы можем получить скан-коды нажимаемых пользователем клавиш. Когда в выходной буфер контроллера клавиатуры поступает очередной байт, присланный клавиатурой, устанавливается в 1 флаг под названием Output Buffer Full (OBF) — самый младший бит регистра статуса контроллера клавиатуры, и генерируется прерывание IRQ1. Когда вы считываете байт из выходного буфера контроллера клавиатуры, флаг OBF сбрасывается в 0. Перед тем, как считывать данные из выходного буфера контроллера клавиатуры, следует убедиться, что флаг OBF установлен в 1 — для этого надо считать значение регистра статуса. Операцию чтения байта из выходного буфера контроллера клавиатуры я поместил в функцию Keyboard_ReadOutputBuffer (см. раздел Исходный код). Однако если вы читаете байты из выходного буфера внутри обработчика прерывания IRQ1, то и так понятно, что флаг OBF установлен и убеждаться в этом лишний раз нет необходимости.

У нас также может возникнуть необходимость записать что-нибудь во входной буфер контроллера клавиатуры. Такая необходимость может возникнуть в двух случаях:

  • Необходимо послать команду контроллеру клавиатуры, причем команда имеет аргументы, которые и записываются во входной буфер клавиатуры.
  • Необходимо послать команду клавиатуре.

В любом случае, при записи данных во входной буфер контроллера клавиатуры надо проверять флаг Input Buffer Full (IBF) — следующий после OBF бит регистра статуса контроллера клавиатуры. Записать очередной байт во входной буфер можно только если флаг IBF сброшен в 0. Операцию записи байта в входной буфер контроллера клавиатуры я поместил в функцию Keyboard_WriteInputBuffer (см. раздел Исходный код).

Команды

Возможность посылать команды контроллеру клавиатуры и самой клавиатуре появилась впервые в клавиатуре AT. В PS/2 были добавлены новые команды. Команды позволяют процессору получить от соответствующего устройства информацию (например, считать регистр статуса контроллера клавиатуры) или изменить поведение устройства (например, отключить генерацию прерывания IRQ1 контроллером клавиатуры). Команды могут иметь аргументы (аналогия с параметрами функции). В результате выполнения команды устройство, которому команда адресована, присылает в ответ некую информацию (аналогия с возвращаемым значением функции). Контроллер клавиатуры и сама клавиатура имеют отличающиеся наборы команд, они рассмотрены ниже.

Команды контроллеру клавиатуры

Команда представляет собой число размером один байт. Чтобы послать команду контроллеру клавиатуры, процессор должен записать ее в порт 0x64. Если у команды есть аргументы, то они должны быть последовательно записаны во входной буфер контроллера (порт 0x60). В результате выполнения некоторых команд контроллер помещает в свой выходной буфер число (значение которого можно считать из порта 0x60) — некий результат, аналог возвращаемого значения в функциях. В таблице ниже приведены некоторые команды для контроллера клавиатуры (более полный список есть в [Chapweske] и [Несвижский]).

Название команды Код команды Аргументы Возвращаемое значение
Read Command Byte 0x20 нет command byte
Write Command Byte 0x60 command byte нет
Keyboard interface test 0xAB нет 0x00 — ok;
0x01 — сlock line stuck low;
0x02 — clock line stuck high;
0x03 — data line stuck low;
0x04 — data line stuck high
Mouse interface test 0xA9 нет 0x00 — ok;
0x01 — сlock line stuck low;
0x02 — clock line stuck high;
0x03 — data line stuck low;
0x04 — data line stuck high
Controller self-test 0xAA нет 0x55 — ok
Enable mouse interface 0xA8 нет нет
Disable mouse interface 0xA7 нет нет

Из всех команд контроллера клавиатуры наиболее важными мне кажутся команды Read Command Byte и Write Command Byte. Command Byte — это 8-разрядный регистр контроллера клавиатуры, который содержит флаги, которые влияют на поведение контроллера (см. таблицу ниже).

7 6 5 4 3 2 1 0
AT XLAT PC EN OVR SYS INT
PS/2 XLAT EN2 EN SYS INT2 INT

Наиболее важными мне кажутся следующие флаги (об остальных флагах читайте в [Chapweske]):

  • XLAT — разрешает или запрещает трансляцию скан-кодов в набор 1 (0 — запрещено, 1 — разрешено)
  • INT2 — разрешает или запрещает прерывание IRQ12 от мыши (0 — запрещено, 1 — разрешено)
  • INT — разрешает или запрещает прерывание IRQ1 от клавиатуры (0 — запрещено, 1 — разрешено)

Запись байта в порт 0x64 я поместил в функцию Keyboard_SendCommand (см. раздел Исходный код). Поскольку некоторые принимают аргументы, а некоторые — нет, некоторые команды возвращают значение, а некоторые — нет, функцию Keyboard_SendCommand следует использовать вместе с функциями Keyboard_WriteInputBuffer (чтобы передать аргументы команды) и Keyboard_ReadOutputBuffer (чтобы считать возвращаемое значение). В качестве примера использования этих функций ниже приведен код, который отключает трансляцию скан-кодов в набор 1: сначала он считывает command byte из контроллера клавиатуры в регистр al, затем сбрасывает бит XLAT в регистре al в 0, а потом записывает содержимое регистра al обратно в command byte.

; Disable scan-code translation to set2
push word KEYBOARD_CONTROLLER_COMMAND_READ_COMMAND_BYTE
call Keyboard_SendCommand ; send "Read Command Byte" command to keyboard controller
call Keyboard_ReadOutputBuffer ; al = command byte
and al, not KEYBOARD_COMMAND_BYTE_XLAT ; reset XLAT bit to 0
push word KEYBOARD_CONTROLLER_COMMAND_WRITE_COMMAND_BYTE
call Keyboard_SendCommand ; send "Write Command Byte" command to keyboard controller
push ax
call Keyboard_WriteInputBuffer ; command byte = al

Команды клавиатуре

Команда клавиатуре представляет собой число размером 1 байт. Чтобы послать команду клавиатуре, ее надо записать во входной буфер контроллера клавиатуры (порт 0x60). Контроллер клавиатуры пересылает клавиатуре любые данные, записанные в его входной буфер (если только они не являются аргументами команды контроллеру клавиатуры). Команда клавиатуре может иметь аргументы. Аргументы должны быть последовательно записаны во входной буфер контроллера клавиатуры непосредственно после самой команды. В ответ клавиатура всегда присылает байт 0xFA (Acknowledge), который показывает, что команда принята или 0xFE (Resend), который показывает, что с командой что-то пошло не так. Этот байт нужно считать из выходного буфера контроллера клавиатуры. Некоторые наиболее важные с моей точки зрения команды клавиатуре перечислены в таблице ниже:

Название команды Код команды Аргументы Возвращаемое значение
RESEND 0xFE нет последний посланный байт или 0xFE (Resend)
DISABLE 0xF5 нет 0xFA (Acknowledge) или 0xFE (Resend)
ENABLE 0xF4 нет 0xFA (Acknowledge) или 0xFE (Resend)
SET_SCAN_CODE_SET 0xF0 0x01, 0x02 или 0x03 0xFA (Acknowledge) или 0xFE (Resend)
Echo 0xEE нет 0xEE (Echo) или 0xFE (Resend)
Set/Reset LEDs 0xED битовая маска: бит 0 — ScrollLock, бит 1 — NumLock, бит 2 — CapsLock 0xFA (Acknowledge) или 0xFE (Resend)

В качестве примера посылки команд клавиатуре приведу код, который сначала посылает клавиатуре команду прекратить сканирование (в результате клавиатура не будет посылать системному блоку скан-коды при нажатии клавиш), а затем — команду возобновить сканирование.

; Disable keyboard
push word KEYBOARD_COMMAND_DISABLE
call Keyboard_WriteInputBuffer
call Keyboard_ReadOutputBuffer

; Enable keyboard
push word KEYBOARD_COMMAND_ENABLE
call Keyboard_WriteInputBuffer
call Keyboard_ReadOutputBuffer

Исходный код — Функции для работы с клавиатурой

Функции и макроопределения для работы с клавиатурой я поместил в файл keyboard.inc.

; keyboard.inc

; An array of ASCII symbols which reflects keyboard layout. Indexes into this array are keyboard scan-codes.
SCAN_CODE_SET1 db 0,'1234567890-+',0,0,'QWERTYUIOP[]',0,0,'ASDFGHJKL;',"'`",0,0,'ZXCVBNM,./',0,'*',0,' ',0, 0,0,0,0,0,0,0,0,0,0, 0,0,'789-456+1230.',0,0
SCAN_CODE_SET2 db 0,0,0,0,0,0,0,0,0,0,0,0,0,126,0,0,0,0,0,0,'Q','1',0,0,0,'Z','S','A','W','2',0,0,'C','X','D','E','4','3',0,0,' ','V'
               db 'F','T','R','5',0,0,'N','B','H','G','Y','6',0,'J',0,'M',0,'U','7','8',0,0,44,'K','I','O','0','9',0,0,'.','/','L',59,'P','-',0,0,0
               db 39,0,'[','=',0,0,0,0,0,']',0,92,0,0,0,0,0,0,0,0,0,0,0,'1',0,'4','7',0,0,0,'0','.','2','5','6','8',0,0,'0','+','3','-','*','9',0,0,0,0,0,0
               
; AT-PS/2 Keyboard Controller Status Register Bits
KEYBOARD_CONTROLLER_STATUS_PERR equ 10000000b
KEYBOARD_CONTROLLER_STATUS_RxTO equ 01000000b
KEYBOARD_CONTROLLER_STATUS_TO   equ 01000000b
KEYBOARD_CONTROLLER_STATUS_TxTO equ 00100000b
KEYBOARD_CONTROLLER_STATUS_MOBF equ 00100000b
KEYBOARD_CONTROLLER_STATUS_INH  equ 00010000b
KEYBOARD_CONTROLLER_STATUS_A2   equ 00001000b
KEYBOARD_CONTROLLER_STATUS_SYS  equ 00000100b
KEYBOARD_CONTROLLER_STATUS_IBF  equ 00000010b ; if IBF=0, Input Buffer is empty and you can write data to it; if IBF=1 Input Biffer is full and you shouldn't write data to it
KEYBOARD_CONTROLLER_STATUS_OBF  equ 00000001b ; if OBF=1, Output Buffer is full and you can read data from it; if OBF=0 Output Buffer is empty and you shouldn't read data from it

; AT-PS/2 Keyboard Controller's Commands
KEYBOARD_CONTROLLER_COMMAND_READ_COMMAND_BYTE       equ 0x20 ; parameters: no; returns: command byte
KEYBOARD_CONTROLLER_COMMAND_WRITE_COMMAND_BYTE      equ 0x60 ; parameters: command byte
KEYBOARD_CONTROLLER_COMMAND_GET_VERSION_NUMBER      equ 0xA1 ; parameters: no; returns: version number
KEYBOARD_CONTROLLER_COMMAND_GET_PASSWORD_EXISTS     equ 0xA4 ; parameters: no; returns: 0xFA if password exists and 0xF1 otherwise
KEYBOARD_CONTROLLER_COMMAND_SET_PASSWORD            equ 0xA5 ; parameters: null-terminated string of scan codes
KEYBOARD_CONTROLLER_COMMAND_CHECK_PASSWORD          equ 0xA6
PS2_CONTROLLER_COMMAND_DISABLE_MOUSE_INTERFACE      equ 0xA7 ; parameters: no
PS2_CONTROLLER_COMMAND_ENABLE_MOUSE_INTERFACE       equ 0xA8 ; parameters: no
PS2_CONTROLLER_COMMAND_MOUSE_INTERFACE_TEST         equ 0xA9 ; parameters: no; returns: 0x00 - ok, 0x01 - Clock line stuck low, 0x02 - clock line stuck high, 0x03 - data line stuck low, 0x04 - data line stuck high
KEYBOARD_CONTROLLER_COMMAND_CONTROLLER_SELF_TEST    equ 0xAA ; parameters: no; returns: 0x55 if ok
KEYBOARD_CONTROLLER_COMMAND_KEYBOARD_INTERFACE_TEST equ 0xAB ; parameters: no; returns: 0x00 - ok, 0x01 - Clock line stuck low, 0x02 - clock line stuck high, 0x03 - data line stuck low, 0x04 - data line stuck high
KEYBOARD_CONTROLLER_DISABLE_KEYBOARD_INTERFACE      equ 0xAD ; parameters: no
KEYBOARD_CONTROLLER_ENABLE_KEYBOARD_INTERFACE       equ 0xAE ; parameters: no
KEYBOARD_CONTROLLER_COMMAND_GET_VERSION             equ 0xAF
KEYBOARD_CONTROLLER_COMMAND_WRITE_MOUSE_DEVICE      equ 0xD4 ; parameters: value to be sent to PS/2 mouse
KEYBOARD_CONTROLLER_COMMAND_READ_TEST_PORT          equ 0xE0 ; parameters: no; returns: values on test port

; AT-PS/2 Keyboard Controller's Command Byte flags (see KEYBOARD_CONTROLLER_COMMAND_READ_COMMAND_BYTE and KEYBOARD_CONTROLLER_COMMAND_WRITE_COMMAND_BYTE commands)
KEYBOARD_COMMAND_BYTE_XLAT equ 01000000b ; Translate Scan Codes to set 1 (0 - disabled, 1 - enabled)
KEYBOARD_COMMAND_BYTE_EN2  equ 00100000b ; Disable Mouse (0 - enabled, 1 - disabled)
KEYBOARD_COMMAND_BYTE_EN   equ 00010000b ; Disable keyboard (0 - enabled, 1 - disabled)
KEYBOARD_COMMAND_BYTE_OVR  equ 00001000b ; Inhibit Override (0 - enabled, 1 - disabled)
KEYBOARD_COMMAND_BYTE_SYS  equ 00000100b ; System Flag (0 - power-on value, 1 - BAT code received)
KEYBOARD_COMMAND_BYTE_INT2 equ 00000010b ; Mouse Output Buffer Full Interrupt (0 - disabled, 1 - enabled)
KEYBOARD_COMMAND_BYTE_INT  equ 00000001b ; Output Buffer Full Interrupt (0 - disabled, 1 - enabled)

; AT-PS/2 Keyboard Commands (do not confuse "Keyboard" and "Keyboard Controller")
KEYBOARD_COMMAND_RESET                             equ 0xFF ; Causes keyboard to enter "Reset" mode
KEYBOARD_COMMAND_RESEND                            equ 0xFE ; Used to indicate an error in reception.
KEYBOARD_COMMAND_SET_KEY_TYPE_MAKE                 equ 0xFD ; Allows the host to specify a key that is to send only make codes.
KEYBOARD_COMMAND_SET_KEY_TYPE_MAKE_BREAK           equ 0xFC ; Similar to "Set Key Type Make", but both make codes and break codes are enabled (typematic is disabled).
KEYBOARD_COMMAND_SET_KEY_TYPE_TYPEMATIC            equ 0xFB ; Similar to previous two commands, except make and typematic is enabled; break codes are disabled.
KEYBOARD_COMMAND_SET_ALL_KEYS_TYPEMATIC_MAKE_BREAK equ 0xFA ; Default setting. Make codes, break codes, and typematic repeat enabled for all keys.
KEYBOARD_COMMAND_SET_ALL_KEYS_MAKE                 equ 0xF9 ; Causes only make codes to be sent; break codes and typematic repeat are disabled for all keys.
KEYBOARD_COMMAND_SET_ALL_KEYS_MAKE_BREAK           equ 0xF8 ; Similar to the previous two commands, except only typematic repeat is disabled.
KEYBOARD_COMMAND_SET_ALL_KEYS_TYPEMATIC            equ 0xF7 ; Similar to the previous three commands, except only break codes are disabled. Make codes and typematic repeat are enabled.
KEYBOARD_COMMAND_SET_DEFAULT                       equ 0xF6 ; Load default typematic rate/delay (10.9cps / 500ms), key types (all keys typematic/make/break), and scan code set (2).
KEYBOARD_COMMAND_DISABLE                           equ 0xF5 ; Keyboard stops scanning, loads default values (see "Set Default" command), and waits further instructions.
KEYBOARD_COMMAND_ENABLE                            equ 0xF4 ; Reenables keyboard after disabled using previous command.
KEYBOARD_COMMAND_SET_TYPEMATIC_RATE_DELAY          equ 0xF3 ; Host follows this command with one argument byte that defines the typematic rate and delay.
KEYBOARD_COMMAND_SET_SCAN_CODE_SET                 equ 0xF0 ; Host follows this command with one argument byte, that specifies which scan code set the keyboard should use.
KEYBOARD_COMMAND_ECHO                              equ 0xEE ; The keyboard responds with "Echo" (0xEE).
KEYBOARD_COMMAND_SET_RESET_LEDS                    equ 0xED ; The host follows this command with one argument byte, that specifies the state of the keyboard's Num Lock, Caps Lock, and Scroll Lock LEDs.

; Flags that can be combined to form an argument of command KEYBOARD_COMMAND_SET_RESET_LEDS
KEYBOARD_LED_CAPS_LOCK   equ 00000100b
KEYBOARD_LED_NUM_LOCK    equ 00000010b
KEYBOARD_LED_SCROLL_LOCK equ 00000001b

; Parts of scan codes
KEYBOARD_SPECIAL_KEY equ 0xE0
KEYBOARD_BREAK_KEY   equ 0xF0

; <---- Keyboard_SendCommand ------
; Sends a command (writes it to port 0x64) to the keyboard controller, located on the motherboard.
; Parameters: word [ebp + 8] - command (can be one of the constants defined above: KEYBOARD_CONTROLLER_COMMAND_* or PS2_CONTROLLER_COMMAND_*)
Keyboard_SendCommand:
    push ebp
    mov ebp, esp
    push ax
   
@@:
    in al, 0x64 ; read Status Byte
    ; test Input Buffer Full (IBF) flag
    test al, KEYBOARD_CONTROLLER_STATUS_IBF
    jnz @b ; wait until IBF=0
    mov al, [ebp + 8] ; al = command
    out 0x64, al ; send command to keyboard controller
   
    pop ax
    mov esp, ebp
    pop ebp
    ret 2
; --------------------------------->

; <---- Keyboard_ReadOutputBuffer ---
; Reads single byte from the keyboard output buffer.
; Returns: al - byte from the keyboard output buffer
Keyboard_ReadOutputBuffer:
.WaitOutputBuffer:
    in al, 0x64 ; read Status Byte
    ; test Output Buffer Full (OBF) flag
    test al, KEYBOARD_CONTROLLER_STATUS_OBF
    jz .WaitOutputBuffer ; wait until OBF=1
    in al, 0x60 ; read output buffer
    ret
; --------------------------------->

; <---- Keyboard_WriteInputBuffer -
; Writes single byte to the keyboard input buffer.
; Parameters: word [ebp + 8] - lower byte of this is to be sent to keyboard
Keyboard_WriteInputBuffer:
    push ebp
    mov ebp, esp
    push ax

.WaitInputBuffer:    
    in al, 0x64 ; read Status Byte
    ; test Input Buffer Full (IBF) flag
    test al, KEYBOARD_CONTROLLER_STATUS_IBF
    jnz .WaitInputBuffer ; wait until IBF=0
    mov al, [ebp + 8] ; al = parameter
    out 0x60, al ; send byte to keyboard
   
    pop ax
    mov esp, ebp
    pop ebp
    ret 2
; --------------------------------->

Обработка прерывания IRQ1 от контроллера клавиатуры

Прерывание IRQ1 генерируется для каждого байта скан-кода в отдельности, т. е. если например пользователь нажал клавишу Insert, скан-код которой в наборе 2 состоит из двух байт (0xE0, 0x70), то последовательно сгенерируется два прерывания IRQ1. код отпускания клавиши Insert состоит из трех байт, поэтому при ее отпускании сгенерируется три прерывания IRQ1. Ниже показан обработчик прерывания IRQ1, в котором предполагается, что используется scan code set 2. При нажатии на клавишу обработчик прерывания должен получить скан-код, перевести его в ASCII-код соответствующего символа и вывести символ на экран.

; cursor position (0...1999). initial value of 160 places the cursor to the 4th row on the screen.
cursor dd 240

KEYBOARD_KEY_IS_SPECIAL_FLAG equ 00000001b
KEYBOARD_KEY_IS_BREAK_FLAG   equ 00000010b
scan_code_flags db 0
; ---- Keyboard Controller --------
irq1_handler:
    push ax
    push edi
   
    in al, 0x60

; <----------------------------------------------
    ;if(al == KEYBOARD_SPECIAL_KEY)
    ;{
    ;    scan_code_flags |= KEYBOARD_KEY_IS_SPECIAL_FLAG;
    ;    return;
    ;}
    ;else if(al == KEYBOARD_BREAK_KEY)
    ;{
    ;    scan_code_flags |= KEYBOARD_KEY_IS_BREAK_FLAG;
    ;    return;
    ;}
    ;else if((scan_code_flags & KEYBOARD_KEY_IS_SPECIAL_FLAG) != 0 || (scan_code_flags & KEYBOARD_KEY_IS_BREAK_FLAG) != 0)
    ;{
    ;    scan_code_flags = 0;
    ;    return;
    ;}
   
    cmp al, KEYBOARD_SPECIAL_KEY
    jnz @f
    or byte [scan_code_flags], KEYBOARD_KEY_IS_SPECIAL_FLAG
    jmp .exit
@@:
    cmp al, KEYBOARD_BREAK_KEY
    jnz @f
    or byte [scan_code_flags], KEYBOARD_KEY_IS_BREAK_FLAG
    jmp .exit
@@:
    test [scan_code_flags], KEYBOARD_KEY_IS_SPECIAL_FLAG
    jz @f
    mov [scan_code_flags], 0
    jmp .exit
@@:
    test [scan_code_flags], KEYBOARD_KEY_IS_BREAK_FLAG
    jz @f
    mov [scan_code_flags], 0
    jmp .exit
; ---------------------------------------------->
   
@@:
    ; convert scan-code to ASCII-code
    dec al
    movzx edi, al
    mov al, [edi+SCAN_CODE_SET2]
    cmp al, 0
    jz .exit
   
    ; print symbol on the screen
    mov ah, 0x07 ; symbol attribute (light gray on black)
    mov edi, [cursor]
    mov [es:edi*2], ax
   
    ; advance cursor position (if(++cursor >= 2000) cursor = 0;)
    inc dword [cursor]
    cmp dword [cursor], 2000
    jb .exit
    mov dword [cursor], 0

.exit:
    pop  edi
    pop  ax
    jmp  int_EOI

2 комментария для “Работа с клавиатурой AT и PS/2”

  1. Представляешь что тут будет со стеком?

    .WaitInputBuffer:
    push ebp
    mov ebp, esp
    push ax

    in al, 0x64 ; read Status Byte
    ; test Input Buffer Full (IBF) flag
    test al, KEYBOARD_CONTROLLER_STATUS_IBF
    jnz .WaitInputBuffer ; wait until IBF=0

Добавить комментарий

Ваш адрес email не будет опубликован.