MyToyOS: Brincando com o temporizador

Continuando a série de artigos sobre o meu sistema operacional de brinquedo, eis alguma informação sobre o circuito integrado dedicado a temporização que consta, de uma forma ou outra, de toda implementação do IBM-PC. Este chip é conhecido como PIT (Programmable Interval Timer).

O PIT tem 3 temporizadores, divididos em canais. O canal 0 é para uso geral e pode ser atrelado a uma interrupção (IRQ0). O canal 1 é dedicado à temporização do circuito de refresh das RAMs dinâmicas e o canal 2 é ligado ao alto-falante.

Um timer, ao contrário do que o nome pode sugerir, não tem nada a ver com um “relógio”. Ele não mantém a data e hora do sistema. Tudo o que ele faz é, dada uma frequência base de referência, contar pulsos de clock e alterar a saída de acordo com diversos modos de operação para obter formas de onda “temporizadas”… Por exemplo: Suponha que o timer do PC tenha uma frequência base de 1 MHz: Se quiséssemos obter um sinal de 1 kHz poderíamos pedir ao timer para alterar a sua saída dividindo a frequência (nunca multiplicando!) por 1000.

O canal mais fácil de entender (e ouvir seus efeitos) é o 2. Como ele é ligado ao alto-falante, podemos configurar o timer para dividir a frequência base de 1.19318 MHz por um fator N qualquer para obter frequências, em onda quadrada, na faixa audível. Logo depois de termos o canal 2 configurado, o alto-falante pode ser habilitado setando um bit numa porta lógica AND. A frequência base de 1.19318 MHz é meio esquisita, mas é precisa (mantida por cristal piezoelétrico)… Infelizmente esse valor foi escolhido (por algum motivo obscuro) e tornou-se padrão… É assim que funcionam os PCs até hoje!

Vale lembrar que, no caso de sistemas operacionais que trabalham no modo protegido (todos os atuais), as rotinas abaixo só funcionarão no kernel space ou ring 0… Se quiser testar isso, use uma VM com o MS-DOS, por exemplo… Eis duas rotinas simples para colocar uma frequência específica no canal 2 e tocar uma nota:

; void play_sound(unsigned short freq);
;
; Entrada: DI = frequência (1 ~ 20000)...
; OBS: Se for usar isso em MS-DOS e com um compilador C,
; adicione o prólogo e o epílogo e obtenha DI da pilha com:
;
;   mov di,[ebp+2]
;
; Atente para o fato de que DI pode ter que ser preservado e,
; neste caso, use outro registrador (que não seja DX ou AX).
play_sound:
  ; EAX = 1192180/(EDI mod 65536)
  mov eax,1192180
  cdq
  movzx edi,di
  div edi

  ; Guarda o quociente.
  mov edx,eax

  ; Seleciona e prepara o timer 2.
  mov al,0xb6  ; modo 3, lsb/msb
  out 0x43,al

  ; Envia o divisor para o timer 2.
  mov al,dl    ; Envia o LSB
  out 0x42,al
  mov al,dh    ; Envia o MSB
  out 0x42,al

  ; Habilita o alto-falante.
  in al,0x61
  or al,0x03   ; Habilita Gate2 do PIT (bit 1)
               ; Habilita Speaker Out (bit 0)
  out 0x61,al
  ret

; void stop_sound(void);
;
; Pára o som, desabilitando o alto-falante
; e o Gate2 do PIT.
stop_sound:
  in al,0x61
  and al,(not 0x03)
  out 0x61,al
  ret

O registrador de controle do PIT (escrita na porta 0x43) é este:

timer

Onde SC1-SC0 selecionam o canal (de 0b00 até 0b10 – 0b11 é inválido!), RL1-RL0 informam como o divisor será informado (0b11 significa LSB primeiro e depois MSB). Os bits M2-M0 nos dizem o modo de operação do timer e o bit BCD, se setado, diz se o timer será programado usando valores BCD, ao invés de binários. No caso de 0xB6, teremos, em binário: 0b10_11_011_0, ou seja, “canal 2”, “LSB seguido de MSB”, “modo 3” e “binário”.

O modo 3 gera uma onda quadrada, Ou seja, o timer conta de 0 até metade do valor informado e muda de 0 para 1, mantém em 1 até chegar no valor informado e retorna a 0, zerando o contador interno de novo… Esse modo é chamado de Square Wave Generator.

Como curiosidade (mas, não estou certo disso, cabe a você obter o código fonte de uma BIOS e verificar). A nota configurada nos beeps emitidos pela BIOS durante o boot é o Lá Central (A) de 440 Hz, aproximadamente (o divisor é 2711,773, arredondado para 2712). Se você tiver um violão afinado, provavelmente obterá a mesma frequência na 5ª corda (verifique!)…

Não mexa no canal 1!

O canal 1 não deve ser futucado… A temporização de refresh das memórias RAM de sua máquina é uma tarefa delicada e só a BIOS é capaz de calcular um valor ideal, além de ajustar o modo adequado para gerar os pulsos em OUT1 para o circuito de refresh de sua placa mãe… Mexer com esse canal provavelmente não causará danos permanentes às memórias, mas pode fazer com que sua máquina “trave” até o próximo reboot…

UPDATE: O leitor e amigo Axey Gabriel Müller Endres me chamou a atenção para o fato de que, nas arquiteturas modernas de PCs o canal 1 to PIT não é mais usado para refresh de memórias. O motivo é simples: As memórias atuais são muito rápidas e o PIT trabalha com a frequência base de 1.19318 MHz, não podendo gerar sinal rápido o suficiente para esse tipo de memórias (que trabalham na faixa de uns 800 MHz, no caso de algumas DDR3 1600, mais comuns, hoje em dia)…Essa é a frequência de I/O… a frequência base para latência do sinal CAS (e RAS?) gira em torno de 200 MHz (o sinal CAS têm que permanecer em nível alto durante, pelo menos, 10 ns, no mínimo) que, óbviamente, é cerca de 200 vezes maior que a frequência base do PIT.

O trabalho de refreshing agora é de um dos APICs (local ou I/O?), que possui um subsistema timer, interno… Isso quer dizer que mexer no canal 1 do PIT pode não causar efeito algum… Mas, lembro que usei a frase “… pode fazer com que sua máquina ‘trave'”… PODE, no sentido de “talvez”! A dica continua a mesma: Não mexa no canal 1 do PIT!

E o canal 0?

Bem… esse canal é livre para o usuário. Geralmente o sistema operacional o utiliza para gerar interrupções com frequência específica. Por exemplo, para realizar task switching a cada 20 ms… Por exemplo, pode-se usar o modo 0 para gerar uma interrupção depois que o contador atinja o valor desejado… Com a frequência base de 1.19318 MHz temos ciclos de cerca de 0,838 μs (f=\frac{1}{1.19318\cdot10^6}) e, para gerar uma interrupção a cada 20 ms precisaríamos configurar o canal 0 para contar cerca de 23864 ciclos.

A IRQ0 está atrelada ao canal 0 e pode ser mascarada (se não quisermos as interrupções frequentes)… assim, configuramos o canal 0 como LSB/MSB e modo 0 e enviamos o valor 0x5D38, tirando a máscara da IRQ0 no PIC (Programmable Interrupr Conroller)… Pronto… daqui a 20 ms ocorrerá uma interrupção… A saída OUT1 permanece alta até que o PIT (Programmable Interval Timer) seja configurado com o modo 0 de novo, mas isso não é problema, já que a IRQ0 é avaliada (asserted), pelo PIC, sempre na subida do pulso (rising edge).

No caso de usar o canal 0 para controlar o chaveamento de tarefas, o tratador da IRQ0 vai fazer seus testes (scheduling) e, se for o caso, salvar o contexto da tarefa atual e carregar a do próximo contexto… ou seja, o que o task switcher faz…

Existem outros timers!

Além do PIT, existem outros timers nas arquiteturas “modernas”. Por exemplo, existe um High Precision Events Timer (HPET) para lidar com fatias de tempo menores (para multimedia, por exemplo). Mas, deste eu falo depois…

Anúncios