MyToyOS: O controlador de teclado (e mouse)

Já falei sobre o PIC, o PIT e o DMAC aqui. Eis o controlador “de teclado”, KBDC (KeyBoarD Controller), cujo nome engana um bocado. O chip 8042 (legado) é, na verdade, um microcontrolador que provê um protocolo específico para troca de dados entre o processador e dispositivos “seriais”. Ele lida, no PC, principalmente com o teclado e com o mouse. Inesperadamente, os dados de ambos os dispositivos trafegam via as mesmas portas de I/O. Ou seja, do ponto de vista do software, teclado e mouse são, ambos, teclados com padrões de stream de dados diferentes. Veremos como isso funciona adiante…

As portas para acesso ao KBDC primário (podem existir 2) são 0x64 e 0x60, onde a porta 0x64 é alocada para obtenção de status (se lida) ou para enviar comandos (se escrita) para o 8042. A porta 0x60 é a porta de dados que deve ser lida ou escrita somente de acordo com o estado de alguns flags contidos na porta de status indicarem essa possibilidade. A porta de status tem o seguinte bitmap:

Bit Mnemônico Descrição
0 OBF Output Buffer Full
1 IBF Input Buffer Full
2 SYS (usada internamente)
3 A2 (usada internamente)
4 INH “Teclado” habilitado
5 TxTO Transmmit TIMEOUT
6 RxTO Receive TIMEOUT
7 PERR Erro de Paridade

Estamos interessados nos bits OBF e IBF apenas. Eles indicam se os “buffers” (registradores) de entrada ou saída contém ou não dados… Lembre-se que o KBDC lida com dispositivos seriais, então, ao escrevermos um comando para o KBDC, este tem que ser repassado para o dispositivo de forma serializada. Isso pode levar um tempo, no qual o “buffer” de “entrada” estará cheio e não pode receber novos comandos. Isso é indicado no bit IBF. A mesma coisa acontece quando o KBDC recebe um byte, quando ele for completamente recebido e colocado no buffer de “saída”, isso é indicado no bit OBF.

Note que “entrada” e “saída”, aqui, são consideradas do ponto de vista do controlador. Ao receber a notificação do teclado que existe uma tecla para ser lida, o KBDC receberá esse scan code e o colocará na “saída” (output) para a futura leitura do processador… Ao receber um comando vindo do processador, este é direcionado para o dispositivo, via KBDC, através de seu registrador (buffer) de “entrada”.

O termo “buffer” aqu nada tem a ver com o “buffer do teclado”, que suporta o enfileiramento de até 16 teclas…

O código abaixo testa OBF antes de tentar ler um scan code vindo do teclado…

wait_kbdc_ob_full:
  in   al,0x64
  test al,1              ; Testa OBF.
  jz   wait_kbdc_ob_full ; OBF=0? fica no loop.

  ; Aqui OBF será 1. Estamos prontos para ler um
  ; dado da porta 0x60.
  ret

Da mesma forma, para escrevermos um comando na porta 0x64 ou um dado na porta 0x60, temos que ter certeza que o “input” buffer esteja vazio verificando se IBF=0!

Teclado ou mouse?

O KBDC não é dedicado ao teclado. Tanto o mouse quanto o teclado são lidos no mesmo par de portas (0x60 e 0x64)…

O KBDC pode controlar até 2 dispositivos seriais e, também, gerar IRQs para anunciar a disponibilidade de dados deles… O método que mostrei anteriormente, fazendo polling do bit OBF, não é o ideal… Nos PCs, o primeiro dispositivo (teclado) está associado à IRQ1, e o segundo (mouse), à IRQ12… No caso do mouse, geralmente a IRQ12 é implementada pelo driver (no caso do MS-DOS, MOUSE.SYS) ou pelo sistema operacional e, simplesmente, lê o stream de dados que é composto das coordenadas X e Y e o estado dos 3 botões (pressionados ou não):

bit: 7 6 5 4 3 2 1 0
byte 1: Y overflow X overflow Y sign X sign 1 middle button right button left button
byte 2: X
byte 3: Y

X e Y são sinalizados. Ou seja, o movimento pode ser feito num sentido ou em outro, nos eixos correspondentes, mas note que o valor informado é o delta em relação à última leitura. Os bits de overflow estão ai para quando você desloca o mouse muito rápido (tipo, ficou com raiva de um bug no seu código e jogou o mouse na parede!).

O fato é que, se o mouse está associado à IRQ12, essa IRQ lerá os 3 bytes, limpando o buffer de saída do KBDC. Assim, ao ler a porta 0x60, fora da IRQ12, estaremos lidando garantidamente com o teclado, não com o mouse. Por default, o mouse está desabilitado e a BIOS tende a não configurá-lo.

Hoje em dia os mouses têm uma “roda” (mouse wheel) que é informada num 4º byte contendo a coordenada Z. Na inicialização do mouse deve-se determinar se ele informa essa 3ª coordenada… Isso é feito por algum comando enviado ao KBDC (veja, mais adiante, a explicação).

Os dados vindos do teclado

Outra vez, diferente da intuição, os dados vindos do teclado não correspondem ao código ASCII das teclas pressionadas, mas a um código da própria tecla… Afinal de contas, o “significado” da tecla nada mais é do que o silk screen que foi pintado em cima dela…

O teclado envia ao KBDC o que chamamos de scan codes. Num teclado ENG-US os scan codes são como mostrados no gráfico abaixo:

E, para as demais teclas:

Isso quer dizer que, se recebermos 0x70 na porta 0x60, então o usuário pressionou a tecla ‘0’ do teclado numérico… Se recebermos 0x45, ele pressionou a tecla ‘0’ do teclado “alfanumérico”… O bit 7 desse código nos diz se a tecla foi pressionada ou solta… Se recebermos 0xF0, o ‘0’ do teclado numérico foi liberado.

Repare que não existe um código 0xE0 ou 0xE1 isolados. Assim, se recebermos 0xE0 ou 0xE1, existirão mais 1 ou 2 bytes no scan code (os 4 bits inferiores de 0xE?) nos dizem quantos, basta adicionar 1)… As setas, por exemplo, têm scan codes de 2 bytes, começados por 0xE0… Note que o byte seguinte é o mesmo que gosta no teclado numérico (dê uma olhada no silk screen do seu teclado)… O mesmo aconte com PgUp, PgDn, Ins, Del, Home e End.

Outro detalhe importante… para demonstrar que scan codes nada têm a ver com os códigos ASCII, basta notar que o teclado não diferencia entre um ‘?’ e um ‘/’ ou um ‘A’ e um ‘a’. A tecla ‘A’ tem scan code 0x1C para ambos os casos. Quem faz a diferenciação é a BIOS ou o sistema operacional… A mesma coisa acontece com as teclas “especiais” como Alt, Shift e Ctrl (e a tecla “Super” — aquela com o logo do Windows — cujo scan code provavelmente é 0xE0,0x5B para o LWin e 0xE0,0x5C para o RWin). Note, também, que teclas adicionais terão scan codes próprios e, provavelmente, precedidos de 0xE0 ou 0xE1.

Existem 3 conjuntos de scan codes diferentes!

Dependendo de como o teclado for configurado, os scan codes obtidos podem não ser os que mostrei acima… Esse ai é o conjunto 1. Teclados suportam os conjuntos 2 e 3. Para selecionar o conjunto basta enviar o comando 0xF0 e escrever 0x01, 0x02 ou 0x03 na porta de dados. Se escrever 0x00, podemos ler o conjunto atualmente selecionado (provavelmente 1).

Além do teclado e mouse

Como ficou claro, podemos enviar comandos para o KBDC para que ele faça coisas além de ler scan codes ou posições do mouse. Basta escrever em 0x64 o comando desejado (desde que o IBF esteja vazio) e obedecer a semântica do comando para ler/escrever na porta 0x60, de acordo com o necessário. Por exemplo, podemos acender ou apagar os LEDs de caps lock, scroll lock e num lock via comando 0xED, bastando enviar um valor de 3 bits (scroll lock no bit 0, num lock no bit 1 e caps lock no bit 2; os demais bits zerados) para a porta 0x60. Devemos depois esperar por um byte na mesma porta 0x60 que deve ser 0xFA (ACK) ou 0xFE (Resend).

Existem outros comandos que não exigem esse tipo de handshake… Por exemplo, o KBDC pode ser usado para habilitar o Gate A20, assim:

kbdc_enable_a20:
  call  wait_kbdc_ib_empty
  mov   al,0xad       ; Desabilita teclado.
  out   0x64,al

  call  wait_kbdc_ib_empty
  mov   al,0xd0       ; Lê "output port A".
  out   0x64,al

  call  wait_kbdc_ob_full
  in    al,0x60       ; Pega o dado da "output port A".
  mov   cl,al         ; Guarda.

  call  wait_kbdc_ib_empty
  mov   al,0xd1       ; Comando: escreve próximo na
  out   0x64,al       ;    byte na "output port A".

  call  wait_kbdc_ib_empty
  mov   al,cl         ; Recupera "output port A" lida
  or    al,2          ; antes e seta o bit "Gate A20".
  out   0x60,al

  call  wait_kbdc_ib_empty
  mov   al,0xae       ; Habilita teclado.
  out   0x64,al

  ; Espera até que o comando seja terminado...
  call  wait_kbdc_ib_empty
  ret

Existem mais comandos disponíveis… Obviamente alguns são para habilitar IRQs e configurar teclado e mouse… Uma referência dos comandos pode ser encontrada aqui. Neste mesmo artigo do OSDev você poderá observar como fazer a “detecção” do dispositivo ligado ao KBDC, como o mouse com mouse wheel ou até mouses com 5 botões!

Anúncios