PS/2 Keyboard: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
(Added spin on inb(0x64) after reports this was necessary on some old and decrepit hardware. Unconfirmed.)
Line 1: Line 1:
== The Keyboard Controller ==
== The Keyboard Controller ==
The Keyboard Controller (KBC) is located on the mainboard. The name is misleading because the Controller does more then controlling the communication with the hardware on the keyboard.
The Keyboard Controller (KBC) is located on the mainboard. The name is misleading because the Controller does more than controlling the communication with the hardware on the keyboard.


In the early days the controller was a single chip (8042). As of today it is part of the [[Advanced Integrated Peripheral]].
In the early days the controller was a single chip (8042). As of today it is part of the [[Advanced Integrated Peripheral]].

Revision as of 17:49, 10 November 2009

The Keyboard Controller

The Keyboard Controller (KBC) is located on the mainboard. The name is misleading because the Controller does more than controlling the communication with the hardware on the keyboard.

In the early days the controller was a single chip (8042). As of today it is part of the Advanced Integrated Peripheral.

Functions of the KBC

  • Controlling the Keyboard connector (PS/2 OR 5pin DIN connector) and on PS/2 Systems also a PS/2 connector for the Mouse.
  • CPU Reset
  • Enable/Disable the A20-Gate
  • Controlling the PC Speaker together with the Programmable Interval Timer.

KBC Types

There are two kinds of KBCs that somewhat differ from each other. The first KBC is the AT-KBC introduced with the AT-System. The second one is the PS/2-KBC introduced with the PS/2-System.

The ancestor of the AT-System the XT-System used the Programmable Peripheral Interface (PPI) for comparable tasks.

Overview of the AT-KBC
Overview of the PS/2-KBC


USB Legacy Support

By modern standards you will find many PCs bundled with USB input devices. Some PCs may not even have PS/2 connectors at all. To remain compatible with old software, the mainboard emulates USB Keyboards and Mouses as PS/2 devices. This is called USB Legacy Support.

Because the implementation differ by manufacturer and mainboard there are flaws and sometimes even bugs:

  • Some emulation layers also handle the communication with the real PS/2 connectors regardless of any connected USB device. So maybe not all capabilities of the PS/2 connectors and devices can be used.
    For example extended mouse modes needed to use the scroll wheel won't work or the keyboard only works on the first PS/2 connector and the mouse only on the second connector.
  • The SMM BIOS that's providing the PS/2 USB Legacy Support may not support extended memory techniques or the Long mode and therefore cause system crashes.

The follow part needs to to be confirmed!
This emulation layer is disabled as soon as the USB Chipset gets initiated by software. (The mainboard knows the OS has USB drivers and therefore can communicate with the devices directly)

Simple keyboard handler for real mode

This is an example how to use the keyboard in real mode. The Scancode for pressed and released keys is displayed on the screen.

The source works with NASM and FASM. You can copy the output file to the boot sector of a floppy or use it with an Emulator.

 use16
 org 0x7C00
 
 ;Disable interrupts
 cli
 
 ;set DS to 0
 xor  ax, ax
 push ax
 pop  ds
 
 ;The IRQ1 (keyboard) is mapped on interrupt 9 by default
 ;Now we write the address of the handler in the Interrupt descriptor table (IDT)
 mov word[ds:(9*4)  ], keyboard_handler    ;Offset
 mov word[ds:(9*4)+2], 0                   ;Segment
 
 ;Enable interrupts
 sti
 
 jmp $
 
 ;This handler gets called each time a IRQ1 is triggered by the keyboard
 keyboard_handler:
   pusha
  
   ;Apparently spinning until the byte is ready is necessary on some older machines.
 .spin:
   in  al, 0x64
   and al, 0x01
   jz  .spin

   ;read scancode
   in  al, 0x60
  
   ;Here you can do with the scancode whatever you like.
   ;For example converting it to another keyboard layout or test for special keys and trigger a reboot
   
   call write_byte_as_hex
   mov  al, '|'
   call bios.write_char
  
   ;Now we tell the first PIC that the IRQ is handled
   mov al, 0x20
   out 0x20, al
  
   popa
 iret
  
  
 ;Input: al = char
 bios.write_char:
   pusha
   mov  ah, 0x0E
   int  0x10
   popa
 ret
  
 hex_chars: db '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
 
 ;This is a small function to write a value on the screen
 ;Input: al = byte
 write_byte_as_hex:
   pusha
   and  ax, 0xFF
   push ax
   mov  bx, ax
   shr  bx, 4
   mov  al, [hex_chars+bx]
   call bios.write_char
   pop  bx
   and  bx, 0xF
   mov  al, [hex_chars+bx]
   call bios.write_char
   popa
 ret
  
 times 510-($-$$) db 0
 dw 0xAA55

CPU Reset

To trigger a CPU Reset (No matter what state the CPU currently has) you have to write the value 0xFE to the Outputport.

 ;Wait for a empty Input Buffer
 wait1:
 in   al, 0x64
 test al, 00000010b
 jne  wait1
 
 ;Send 0xFE to the keyboard controller.
 mov  al, 0xFE
 out  0x64, al

Protected Mode Keyboard Driver

First of all you should have this steps ready in your OS:

  • Kernel loaded
  • IDT set up
  • PIC remapped
  • IRQ for keyboard unmasked and pointing to your future keyboard ISR

Read all about IRQ's and PIC's at Interrupts

To get keyboard input you read the port 0x60 and save the translated scan code to a buffer.

Receiving Input

The keyboard communicates with your computer through a chip called 8042 (on modern system, the functionality of that chip is emulated by the chipset). Any key press or key release leads to the transmission of a scancode to the 8042 which then raises IRQ1 and makes the scancode available in its data port (port 0x60).

From your point of view, things are as easy as

 void KeyboardIsr()
 {
    byte new_scan_code = inportb(0x60);
 
    /* Do something with the scancode.
     * Remember you only get '''one''' byte of the scancode each time the ISR is invoked.
     * (Though most of the times the scancode is only one byte.) 
     */
 
    /* Acknowledge the IRQ, pretty much tells the PIC that we can accept >=priority IRQs now. */
    outportb(0x20,0x20);
 }

See http://www.win.tue.nl/~aeb/linux/kbd/scancodes.html for hints about how you should interpret what you get from the keyboard.

Note that if you repeatedly read the port 0x60 without waiting for another IRQ, you'll read the same byte again. That's the 'normal' behaviour of keyboard controller, but if that doesn't suit your needs, you can still "acknowledge" the scancode by quickly disabling and re-enabling the keyboard at the PPI (Programmable Peripheral Interface):

 int temp = inportb(0x61);
 outportb(0x61,temp | 0x80);  /* Disable */
 outportb(0x61,temp & 0x7F);  /* Re-enable */

Scancodes

The keyboard only reports keystrokes. That means it makes no difference between an uppercase 'A' and a lowercase 'a' when you hit the key. The trick is that you'll get two kind of scancodes: make codes and break codes, depending on whether you hit or release the key. For instance if you type an uppercase 'A', you'll get

0x2A (make shift)
0x1E (make A)
0x9E (break A)
0xAA (break shift)

Similarly, when you type 'hi' with the CTRL key hold down, your keyboard sends the codes 1d, 23, a3, 17, 97, 9d to the CPU. That means if you want to interpret character 'A' differently based on whether the 'SHIFT' key is up or down, you have first to remember the state of the shift key.

 char lowercase[256] = {0x1E:'a'};
 char uppercase[256] = {0x1E:'A'};
 
 unsigned shift_state = 0;
 
 void KeyboardIsr()
 {
    byte new_scan_code = inportb(0x60);
 
    switch(new_scan_code) {
        case 0x2a: 
            shift_state = 1; 
            break;
 
        case 0xaa: 
            shift_state = 0;
            break;
 
        default:
           if (new_scan_code & 0x80) {
              /* Ignore the break code */
           } else {
              new_char =(shift_state ? uppercase:lowercase)[new_scan_code];
              /* Do something with new_char. */
           }
           break;
     }
 
     outportb(0x20,0x20);
 }

The arrays lowercase and uppercase are the conversion arrays, also called keyboard map. A different keyboard language will lead to different keyboard maps, so you're welcome to provide an API call that will allow the change of that map. You might also want an alternate map for characters composed with ~[ALTgr] key hold down or a wchar_t based map that will allow you to map a keystroke directly to Unicode characters.


Escaped Scancodes

Several keys on your keyboard have a two-bytes scan code (they're said to be escaped scancodes). All those codes start with 0xe0 and are then followed by the 'real' scancode, which means when receiving scancode 0xe0, you should just store the info and wait for the next scancode to know what key even actually occurred.

If you look at the list of escaped scancodes on http://www.win.tue.nl/~aeb/linux/kbd/scancodes.html, you'll note that none of them correspond to a 'printable' character.

 unsigned shift_state = 0;
 unsigned escaped=0;
 
 void KeyboardIsr()
 {
    unsigned new_scan_code = inportb(0x60);
    if (escaped) new_scan_code += 256;
    switch(new_scan_code) {
    case 0x2a: shift_state = 1; break;
    case 0xaa: shift_state = 0; break;
    case 0xe0: escaped = 1; break;
    default:
       if (new_scan_code & 0x80) {
          // ignore the break code
       } else {
          new_char=(shift_state?uppercase:lowercase)[new_scan_code];
          // do something with new_char
       } 
   break;
 
   outportb(0x20,0x20);
 }

Keyboard LEDs

The LEDs of the keyboard are quite simple to enable and disable. To update the status, you must wait for the keyboard buffer to be empty and ready to receive commands. This consists of read port 0x64 until bit 1 is 0. Then you send the command byte 0xED. Then you wait for the keyboard buffer to clear again, and then you send the byte consisting of the LED data. Here is some example code (in C)

 #define SCROLL_LED 1
 #define NUM_LED 2
 #define CAPS_LED 4
 
 void kbd_update_leds(uint8_t status){
 	uint8_t tmp;
 	while((inportb(0x64)&2)!=0){} //loop until zero
 	outportb(0x60,0xED);
 	
 	while((inportb(0x64)&2)!=0){} //loop until zero
 	outportb(0x60,status);
 }

So to enable the caps lock LED and the scroll lock LED you would do

 kbd_update_leds(CAPS_LED | SCROLL_LED);

Non-Printable Characters

ToDo: think about it. We can however already say that they should follow the same 'datapath' as regular characters: we want cursor displacements, regular keytypes and commands (such as CTRL+S) to be delivered to the application in the same order the user typed them, and the best way to achieve this is to ensure they're delivered through the same data stream.


Non-Latin Characters

In case you'd like to write an OS with support of Arabic, Kanji or whatever, you'll at least need support for Unicode. We had a thread about loading Arabic fonts on the forum, which you might like to look at.

Anyway, please consider the following: even if your userbase is mainly Arabic/Japanese/whatever, your guru base is likely to be international and to prefer messages about "queue" or "thread" than about whatever the translation might look like, so English might be preferred for "behind the scene" stuff.

See Also

Threads

External Links