PS/2 Keyboard: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
m (Heading correction)
(More changes)
Line 20: Line 20:
| 0xED
| 0xED
| LED states (e.g. bit 0 = ScrollLock, bit 1 = NumLock, bit 2 = CapsLock)
| LED states (e.g. bit 0 = ScrollLock, bit 1 = NumLock, bit 2 = CapsLock)
Note: Other bits may be used in international keyboards for other purposes (e.g. a Japanese keyboard might use bit 4 for a "Kana mode" LED).
| Set LEDs
| Set LEDs
| 0xFA (ACK) or 0xFE (Resend)
| 0xFA (ACK) or 0xFE (Resend)
Line 31: Line 32:
| 0 = get scancode, 1 = set scancode set 1, 2 = set scancode set 2, 3 = set scancode set 3
| 0 = get scancode, 1 = set scancode set 1, 2 = set scancode set 2, 3 = set scancode set 3
| Get/set current scan code
| Get/set current scan code
| 0xFA (ACK) or 0xFE (Resend) if scan code is being set; 0xFA (ACK) then the scan code set number, or 0xFE (Resend) if you're getting the scancode
| 0xFA (ACK) or 0xFE (Resend)
|-
|-
| 0xF2
| 0xF2
Line 170: Line 171:
The simplest way to achieve this is for the driver to maintain a queue of commands. When you add a command to the queue, if the queue is empty you start sending the command; otherwise you append the command to the queue. When you receive an "0xFA (ACK)" from the keyboard you discard the command at the head of the queue and start sending the next command in the queue (if any). If you receive an "0xFE (Resend)" from the keyboard you can resend the command at the head of the queue.
The simplest way to achieve this is for the driver to maintain a queue of commands. When you add a command to the queue, if the queue is empty you start sending the command; otherwise you append the command to the queue. When you receive an "0xFA (ACK)" from the keyboard you discard the command at the head of the queue and start sending the next command in the queue (if any). If you receive an "0xFE (Resend)" from the keyboard you can resend the command at the head of the queue.


The remainder of the driver should be a kind of state machine. The state machine moves into a different state when some commands are successfully completed, and when various bytes are received from the keyboard. For example, the driver might be in a default state and receive a break code that puts it into a "waiting for scan code after receiving break code" state. Then it might receive the first byte of a multi-byte scan code and shift to a "waiting for second byte of scan code after receiving break code" state. Finally it might receive the second/last byte of the scan code, combine the previous bytes into some sort of "key press packet" (and send the packet somewhere - e.g. GUI) and then switch back to the default state.
The remainder of the driver should be a kind of state machine. The state machine moves into a different state when some commands are successfully completed, and when various bytes are received from the keyboard. For example, the driver might be in a default state and receive a break code that puts it into a "waiting for scan code after receiving break code" state. Then it might receive the first byte of a multi-byte scan code and shift to a "waiting for second byte of scan code after receiving break code" state. Finally it might receive the second/last byte of the scan code and then switch back to the default state.




== Scan Code Sets, Scan Codes and Key Codes ==
== 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

{| {{wikitable}}
|-
| 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.
<source lang="c">
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);
}
</source>
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.


A scan code set is a set of codes that determine when a key is pressed or repeated, or released. There are 3 different sets of scan codes. The oldest is "scan code set 1", the default is "scan code set 2", and there is a newer (more complex) "scan code set 3". ''Note: Normally on PC compatible systems the keyboard itself uses scan code set 2 and the keyboard controller translates this into scan code set 1 for compatibility. See [["8042"_PS/2_Controller#Translation|"8042"_PS/2_Controller]] for more information about this translation.''


Modern keyboards should support all three scan code sets, however some don't. Scan code set 2 (the default) is the only scan code set that is guaranteed to be supported. In theory (I haven't tried it) it should be possible to attempt to set scan code set 1 or scan code set 3, and then ask the keyboard which scan code it is currently using and see if it actually is using the requested scan code set. In this way it may be possible to determine which scan code sets the keyboard does support.
==== Escaped Scancodes ====


Scan codes themselves are sequences of one or more bytes. In some cases the sequence can be as many as 6 bytes (e.g. the "print screen" key in scan code set 1
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.
generates the sequence 0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5 when pressed). This situation isn't really ideal. In general (for later processing) you want to convert these "one or more byte sequences" into a single integer that uniquely identifies a specific key, that can be used effectively in things like lookup tables (without having sparsely used "many GiB" lookup tables).


There is no standard for "key codes" - it's something you have to make up or invent for your OS. I personally like the idea of having an 8-bit key code where the highest 3 bits determine which row on the keyboard and the lowest 5 bits determine which column (essentially, the keyboard is treated as a grid of up to 8 rows and up to 32 columns of keys). Regardless of what you choose to use for your key codes, it should be something that is used by all keyboard drivers (including USB Keyboards) and could possibly also be used for other input devices (e.g. left mouse button might be treated as "key code 0xF1").
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.
<source lang="c">
unsigned shift_state = 0;
unsigned escaped=0;
void KeyboardIsr()
{
unsigned new_scan_code = inportb(0x60);
if (escaped)
{ new_scan_code += 256;
escaped = 0;
}
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);
}
</source>


Basically, when the keyboard driver's state machine knows it has received a complete scan code, the next step is to convert the "one or more byte" scan code into a key code.
=== 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)
<source lang="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);
}
</source>
So to enable the caps lock LED and the scroll lock LED you would do
<source lang="c">
kbd_update_leds(CAPS_LED | SCROLL_LED);
</source>


== Key Codes, Key States and Key Mappings ==
=== Non-Printable Characters ===


Once you've got key codes, then next step is to keep track of which keys are currently being pressed. Imagine a computer game that uses the "WASD" keys for player movement - when the 'A' key is being pressed the player rotates clockwise. How does the game know if the 'A' key is currently being pressed? For this you'd want an array of flags, where each flag corresponds to a key code. There is a hidden bonus here - the keyboard driver itself can use the same "array of flags" to determine if a shift key, control key, alt key, etc is down, which can be quite useful when trying to convert the key code into an actual ASCII character or Unicode code point. For example, if the user presses the 'a' key then it might correspond to 'a' or 'A' (depending on capslock state and whether or not a shift key is being held down at the time) or might not correspond to a valid character at all (e.g. "control-a" or "alt-a").
ToDo: think about it. We can however already say that they should follow the same 'datapath' as regular characters: we want cursor displacements, regular key types 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.


Also note that (for example) a "WASD" game doesn't care if the keys are 'W', 'A', 'S' and 'D'. The game wants to know about keys in a specific "T-shaped" pattern on the left of the keyboard. If the keyboard happens to be something different, then the keys in the same location may be completely different (e.g. they would be '<', 'A', 'O' and 'E' keys on a Dvorak keyboard). This helps to explain my preference of having an 8-bit key code where the highest 3 bits determine which row on the keyboard and the lowest 5 bits determine which column (it's easy for a game to ask about the state of the third key on the left of the third row).


Once you're able to keep track of which keys are currently being pressed, the next step is to (attempt to) convert the key into an ASCII character or Unicode code point. At this point you need to know what type of keyboard the user has - is it "US QWERTY", or "French AZERTY", some form of Dvorak, or perhaps it's Arabic. To handle many different keyboard layouts, the keyboard driver needs to use tables to convert key codes into ASCII characters or Unicode code points; so that you only need to load a different "Key Mapping" table to support different keyboard layouts.
=== Non-Latin Characters ===


However, it's not quite that simple. Different keyboard layouts can have different meta keys, different status LEDs, etc. The Key Mapping table has to sort these differences out too. This is why you don't want to detect if the keyboard LEDs have changed earlier, but want to send the "set LEDs" command (if necessary) *after* you've found the entry for the key code in your key map table.
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 [[Topic:10447|loading Arabic fonts]] on the forum, which you might like to look at.


The final step of processing is to combine all relevant information into some sort of "keypress packet" structure, and send it to whomever (e.g. GUI). The entire "keypress packet" might include the following:
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.
* Unicode code point (if applicable)
* Key code
* Pressed/released flag
* Various other key states (shift, alt, control, etc)
* Various "toggle" states (CapsLock, ScrollLock, NumberLock, etc)


==See Also==
==See Also==

Revision as of 09:02, 26 March 2012

Overview

The PS/2 Keyboard is a device that talks to a PS/2 controller using serial communication. Ideally, each different type of PS/2 controller driver should provide some sort of standard/simple "send byte/receive byte" interface, and the PS/2 Keyboard driver would use this interface without caring about lower level details (like what type of PS/2 controller the device is plugged into).

The PS/2 Keyboard accepts commands and sends responses to those commands, and also sends scan codes indicating when a key was pressed or released.

Commands

A PS/2 Keyboard accepts many types of commands. A command is one byte. Some commands have data byte/s which must be sent after the command byte. The keyboard typically responds to a command by sending either an "ACK" (to acknowledge the command) or a "Resend" (to say something was wrong with the previous command) back.

The commands that a PS/2 Keyboard accepts are:

Command Byte Data Byte/s Meaning Response
0xED LED states (e.g. bit 0 = ScrollLock, bit 1 = NumLock, bit 2 = CapsLock)

Note: Other bits may be used in international keyboards for other purposes (e.g. a Japanese keyboard might use bit 4 for a "Kana mode" LED).

Set LEDs 0xFA (ACK) or 0xFE (Resend)
0xEE None Echo (for diagnostic purposes, and useful for device removal detection) 0xEE (Echo) or 0xFE (Resend)
0xF0 0 = get scancode, 1 = set scancode set 1, 2 = set scancode set 2, 3 = set scancode set 3 Get/set current scan code 0xFA (ACK) or 0xFE (Resend) if scan code is being set; 0xFA (ACK) then the scan code set number, or 0xFE (Resend) if you're getting the scancode
0xF2 None Identify keyboard 0xFA (ACK) followed by none or more ID bytes (see [| "Detecting Device Types"])
0xF3 Typematic byte (described below) Set typematic rate and delay 0xFA (ACK) or 0xFE (Resend)
0xF4 None Enable scanning (keyboard will send scan codes) 0xFA (ACK) or 0xFE (Resend)
0xF5 None Disable scanning (keyboard won't send scan codes)

Note: May also restore default parameters

0xFA (ACK) or 0xFE (Resend)
0xF6 None Set default parameters 0xFA (ACK) or 0xFE (Resend)
0xF6 None Set default parameters 0xFA (ACK) or 0xFE (Resend)
0xF7 None Set all keys to typematic/autorepeat only (scancode set 3 only) 0xFA (ACK) or 0xFE (Resend)
0xF8 None Set all keys to make/release (scancode set 3 only) 0xFA (ACK) or 0xFE (Resend)
0xF9 None Set all keys to make only (scancode set 3 only) 0xFA (ACK) or 0xFE (Resend)
0xFA None Set all keys to typematic/autorepeat/make/release (scancode set 3 only) 0xFA (ACK) or 0xFE (Resend)
0xFB Scancode for key Set specific key to typematic/autorepeat only (scancode set 3 only) 0xFA (ACK) or 0xFE (Resend)
0xFC Scancode for key Set specific key to make/release (scancode set 3 only) 0xFA (ACK) or 0xFE (Resend)
0xFD Scancode for key Set specific key to make only (scancode set 3 only) 0xFA (ACK) or 0xFE (Resend)
0xFE None Resend last byte Previously sent byte or 0xFE (Resend)
0xFF None Reset and start self-test 0xAA (self-test passed), 0xFC or 0xFD (self test failed), or 0xFE (Resend)

Typematic Byte

The Typematic Byte used for command 0xF3 (Set typematic rate and delay) has the following format:

Bit Meaning
0 to 4 Repeat rate (00000b = 30 Hz, ..., 11111b = 2 Hz)
5 to 6 Delay before keys repeat (00b = 250 ms, 01b = 500 ms, 01b = 750 ms, 11b = 1000 ms)
7 Must be zero


Special Bytes

The keyboard sends bytes to the system. Some of these bytes have special meaning (e.g. responses from the commands above). The bytes the keyboard may send are:

Response Byte Meaning
0x00 Key detection error or internal buffer overrun
0xAA Self test passed (sent after "0xFF (reset)" command or keyboard power up)
0xEE Response to "0xEE (echo)" command
0xFA Command acknowledged
0xFC and 0xFD Self test failed (sent after "0xFF (reset)" command or keyboard power up)
0xFE Resend (keyboard wants controller to repeat last command it sent)
0xFF Key detection error or internal buffer overrun

All other bytes sent by the keyboard are scan codes, where interpretation depends on the currently selected scan code set.


Driver Model: Command Queue and State Machine

Commands must be sent one at a time (e.g. if your driver is interrupt driven, you can't start sending a command within the IRQ handler because code outside the IRQ handler may be in the middle of sending a command). The command isn't completed until you've received an ACK for it. For example, if you send a command and the keyboard responds with "0xFE (resend)" then you have to send the command again (possibly limited to 3 retries before you give up and assume the keyboard doesn't support the command you're sending or there's been a hardware failure). Finally, sometimes you want to send several commands at once. For example, you might have a "reinitialise()" function that sets the scan code set, sets the typematic byte, sets the LEDs and enables scanning.

The simplest way to achieve this is for the driver to maintain a queue of commands. When you add a command to the queue, if the queue is empty you start sending the command; otherwise you append the command to the queue. When you receive an "0xFA (ACK)" from the keyboard you discard the command at the head of the queue and start sending the next command in the queue (if any). If you receive an "0xFE (Resend)" from the keyboard you can resend the command at the head of the queue.

The remainder of the driver should be a kind of state machine. The state machine moves into a different state when some commands are successfully completed, and when various bytes are received from the keyboard. For example, the driver might be in a default state and receive a break code that puts it into a "waiting for scan code after receiving break code" state. Then it might receive the first byte of a multi-byte scan code and shift to a "waiting for second byte of scan code after receiving break code" state. Finally it might receive the second/last byte of the scan code and then switch back to the default state.


Scan Code Sets, Scan Codes and Key Codes

A scan code set is a set of codes that determine when a key is pressed or repeated, or released. There are 3 different sets of scan codes. The oldest is "scan code set 1", the default is "scan code set 2", and there is a newer (more complex) "scan code set 3". Note: Normally on PC compatible systems the keyboard itself uses scan code set 2 and the keyboard controller translates this into scan code set 1 for compatibility. See "8042"_PS/2_Controller for more information about this translation.

Modern keyboards should support all three scan code sets, however some don't. Scan code set 2 (the default) is the only scan code set that is guaranteed to be supported. In theory (I haven't tried it) it should be possible to attempt to set scan code set 1 or scan code set 3, and then ask the keyboard which scan code it is currently using and see if it actually is using the requested scan code set. In this way it may be possible to determine which scan code sets the keyboard does support.

Scan codes themselves are sequences of one or more bytes. In some cases the sequence can be as many as 6 bytes (e.g. the "print screen" key in scan code set 1 generates the sequence 0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5 when pressed). This situation isn't really ideal. In general (for later processing) you want to convert these "one or more byte sequences" into a single integer that uniquely identifies a specific key, that can be used effectively in things like lookup tables (without having sparsely used "many GiB" lookup tables).

There is no standard for "key codes" - it's something you have to make up or invent for your OS. I personally like the idea of having an 8-bit key code where the highest 3 bits determine which row on the keyboard and the lowest 5 bits determine which column (essentially, the keyboard is treated as a grid of up to 8 rows and up to 32 columns of keys). Regardless of what you choose to use for your key codes, it should be something that is used by all keyboard drivers (including USB Keyboards) and could possibly also be used for other input devices (e.g. left mouse button might be treated as "key code 0xF1").

Basically, when the keyboard driver's state machine knows it has received a complete scan code, the next step is to convert the "one or more byte" scan code into a key code.


Key Codes, Key States and Key Mappings

Once you've got key codes, then next step is to keep track of which keys are currently being pressed. Imagine a computer game that uses the "WASD" keys for player movement - when the 'A' key is being pressed the player rotates clockwise. How does the game know if the 'A' key is currently being pressed? For this you'd want an array of flags, where each flag corresponds to a key code. There is a hidden bonus here - the keyboard driver itself can use the same "array of flags" to determine if a shift key, control key, alt key, etc is down, which can be quite useful when trying to convert the key code into an actual ASCII character or Unicode code point. For example, if the user presses the 'a' key then it might correspond to 'a' or 'A' (depending on capslock state and whether or not a shift key is being held down at the time) or might not correspond to a valid character at all (e.g. "control-a" or "alt-a").

Also note that (for example) a "WASD" game doesn't care if the keys are 'W', 'A', 'S' and 'D'. The game wants to know about keys in a specific "T-shaped" pattern on the left of the keyboard. If the keyboard happens to be something different, then the keys in the same location may be completely different (e.g. they would be '<', 'A', 'O' and 'E' keys on a Dvorak keyboard). This helps to explain my preference of having an 8-bit key code where the highest 3 bits determine which row on the keyboard and the lowest 5 bits determine which column (it's easy for a game to ask about the state of the third key on the left of the third row).

Once you're able to keep track of which keys are currently being pressed, the next step is to (attempt to) convert the key into an ASCII character or Unicode code point. At this point you need to know what type of keyboard the user has - is it "US QWERTY", or "French AZERTY", some form of Dvorak, or perhaps it's Arabic. To handle many different keyboard layouts, the keyboard driver needs to use tables to convert key codes into ASCII characters or Unicode code points; so that you only need to load a different "Key Mapping" table to support different keyboard layouts.

However, it's not quite that simple. Different keyboard layouts can have different meta keys, different status LEDs, etc. The Key Mapping table has to sort these differences out too. This is why you don't want to detect if the keyboard LEDs have changed earlier, but want to send the "set LEDs" command (if necessary) *after* you've found the entry for the key code in your key map table.

The final step of processing is to combine all relevant information into some sort of "keypress packet" structure, and send it to whomever (e.g. GUI). The entire "keypress packet" might include the following:

  • Unicode code point (if applicable)
  • Key code
  • Pressed/released flag
  • Various other key states (shift, alt, control, etc)
  • Various "toggle" states (CapsLock, ScrollLock, NumberLock, etc)

See Also

Threads

External Links