| Zeile 221: | Zeile 221: | ||
</pre> | </pre> | ||
| − | == | + | == Tastaturtreiber == |
| + | Im Folgenden soll genauer auf die Ansteuerung der Tastatur als ganzes | ||
| + | eingegangen werden, also das auslesen und verarbeiten der vom Benutzer | ||
| + | gedrückten Tasten. Dabei wird nur auf die manuelle Ansteuerung ohne | ||
| + | BIOS-Funktionen behandelt, wie sie benötigt wird, wenn die Tastatur im | ||
| + | Protected Mode angesteuert werden soll. Es wird davon ausgegangen, dass die | ||
| + | entsprechenden Grundlagen für den Treiber gegeben sind (IRQ-Behandlung, | ||
| + | Port-Funktionen). | ||
| + | |||
| + | === Initialisierung === | ||
| + | Bevor man die Tastatur benutzt, ist es ratsam sie erstmal in einen bekannten, | ||
| + | sauberen Zustand zu bringen. Im | ||
| + | [http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/modules/kbc/keyboard.c Tastaturtreiber von tyndur] | ||
| + | hat sich nach einigem herumexperimentieren mit verschiedenen Emulatoren die | ||
| + | folgende einfache Initialisierung bewährt: | ||
| + | * Solange das Bit 0 im Statusregister (Puffer voll) gesetzt ist Bytes aus dem Datenregister lesen. Das hilft wenn schon vor der initialisierung des Treibers irgendwelche Tasten betätigt wurde, da der KBC dann keine weiteren Zeichen sendet, bis der Puffer geleert (ausgelesen wurde), das tritt zum Beispiel im Zusammenhang mit GRUB oft auf, wenn der Benutzer die Taste zu lange gedrückt hält. | ||
| + | * Tastatur mit Befehl 0xF4 aktivieren Andere Kombinationen wurden auch getestet, haben aber Probleme bereitet, unter anderem war Zeitweise ein Reset der Tastatur drin, was dazu geführt hat, dass diese unter gewissen Emulatoren überhaupt nicht mehr funktionierte. | ||
| + | |||
| + | Dieser Code könnte beispielsweise folgendermassen aussehen: | ||
| + | <c> | ||
| + | static void send_command(uint8_t command); | ||
| + | void init_keyboard(void) | ||
| + | { | ||
| + | // IRQ-Handler fuer Tastatur-IRQ(1) registrieren | ||
| + | register_intr_handler(IRQ_BASE + 1, &irq_handler); | ||
| − | + | // Tastaturpuffer leeren | |
| + | while ((inb(0x64) & 0x1)) { | ||
| + | inb(0x60); | ||
| + | } | ||
| − | + | // Tastatur aktivieren | |
| − | + | send_command(0xF4); | |
| − | + | } | |
| − | < | + | /** Befehl an die Tastatur senden */ |
| − | + | static void send_command(uint8_t command) | |
| − | + | { | |
| + | // Warten bis die Tastatur bereit ist, und der Befehlspuffer leer ist | ||
| + | while ((inb(0x64) & 0x6) != 0x4) {} | ||
| + | outb(0x60, command); | ||
| + | } | ||
| + | </c> | ||
| − | + | In einem vernünftigen Betriebssystem möchte man die Tastatur wahrscheinlich | |
| + | mit IRQs nutzen, damit man jeweils benachrichtigt wird, wenn eine Taste | ||
| + | gedrückt wird, und nicht darauf warten muss. Dabei ist es wichtig, dass die | ||
| + | Routine zur Behandlung der Tastatur-IRQs vor der oben angegebenen | ||
| + | Initialisierung erfolgt, da sich sonst möglicherweise üble Race-Conditions | ||
| + | ergeben, also konkret wenn der Puffer bereits geleert wurde, und noch vor dem | ||
| + | Registrieren der IRQ-Routine eine Taste gedrückt wird, dann ist der Puffer | ||
| + | der Tastatur voll, und sie schickt somit auch keine IRQs mehr, bis er geleert | ||
| + | wird, was aber dann nicht mehr passiert, und die Tastatur hängt somit. | ||
| − | + | === IRQ-Handler === | |
| − | + | Nun sind wir also bereit Tastendrücke von der Tastatur einzulesen. Im | |
| − | + | folgenden wird genauer darauf eingegangen, wie wir die Tastendrücke die wir | |
| + | vom Kontroller über einen IRQ signalisiert erhalten, verarbeiten können. | ||
| + | Der erste und wichtigste Schritt in unserer IRQ-Behandlungsroutine ist es, | ||
| + | das vom Tastaturkontroller bereitgestellte Byte aus dem Puffer zu lesen. In | ||
| + | einem weiteren Schritt müssen wir jetzt entscheiden womit wir es bei diesem | ||
| + | Byte nun genau zu tun haben. Eine erste Unterscheidung, die wir machen müssen, | ||
| + | ist die Unterteilung in gedrückte und gelöste Tasten, was uns durch das oberste | ||
| + | Bit im eingelesenen Byte signalisiert wird. | ||
| − | + | Der Rest des Bytes ist ein Scancode, | |
| − | + | der uns angibt um welche Taste es sich handelt. Dieser Scancode hat überhaupt | |
| − | + | nichts mit irgendwelchen Zeichen zu tun, er gibt uns nur an welche Taste | |
| + | gedrückt oder gelöst wurde, und muss von uns manuell mit einer Scancode-Tabelle | ||
| + | in die korrespondierenden Zeichen umgewandelt werden. Dabei muss man sich | ||
| + | bewusst sein, dass für die Tastatur eine Steuertaste, wie zum Beispiel die | ||
| + | Umschalttaste oder Caps lock, genau das selbe ist wie beispielsweise ein "a". | ||
| + | Wird beispielsweise Umschalt+a gedrückt, um ein grosses "a" zu erhalten, schickt | ||
| + | uns der Tastaturkontroller 2 Bytes, eines Umschalttaste gedrückt und ein anderes | ||
| + | "a" gedrückt. Es ist also Aufgabe des Treibers diese Tasten entsprechend zu | ||
| + | interpretieren. | ||
| − | + | Um das ganze noch komplizierter zu machen, gibt es noch so genannte Extented | |
| + | Scancodes, davon gibt es zwei verschiedene Sorten. Diese werden mit E0- und E1 | ||
| + | bezeichnet, und bestehen aus 2 (E0) und 3 (E1) Bytes. Das erste Byte eines | ||
| + | E0-Scancodes hat überraschenderweise den Wert 0xE0, während das zweite Byte um | ||
| + | welchen e0-Scancode es sich genau handelt. E1-Scancodes bestehen aus 3 Bytes, | ||
| + | das erste hat den Wert 0xE1 während die folgenden zwei Bytes zu einem | ||
| + | Scancode-Wert kombiniert werden. Dabei muss beachtet werden, dass bei den | ||
| + | Extended-Scancodes das Bit 8 des 2. und 3. Bytes darüber Aufschluss gibt, ob es | ||
| + | sich um eine gedrückte oder eine gelöste Taste handelt. Die einzelnen Bytes der | ||
| + | E0-Scancodes werden mit unterschiedlichen IRQs gesendet, man muss sich den | ||
| + | aktuellen Zustand im Code also irgendwie merken. Bei Extended Scancodes | ||
| + | gibt es noch eine weitere Stolperfalle und zwar sind das die so genannten "fake | ||
| + | shifts" [http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html#ss1.6 vgl.] | ||
| − | + | So nun wissen wir also mit welchem Scancode wir es zu tun haben, als nächstes | |
| + | müssen wir den seltsamen Scancode jetzt in etwas sinnvolles umwandeln. Hier gibt | ||
| + | es jetzt zwei Möglichkeiten, entweder wir wandeln sie direkt in ASCII-Zeichen | ||
| + | (oder welche Kodierung auch immer) um, oder wir wandeln sie in ein internes | ||
| + | Format um (meist Keycodes genannt), welches wir dann weitergeben. | ||
| + | Auch hier wieder ein kleiner Codeabschnitt, der zeigt wie man den IRQ-Handler | ||
| + | umsetzen könnte (aus tyndur übernommen, BSD-Lizenz): | ||
| + | <c> | ||
| + | /** | ||
| + | * Scancode in einen Keycode uebersetzen | ||
| + | * @return Keycode oder 0 falls der Scancode nicht bekannt ist | ||
| + | */ | ||
| + | uint8_t translate_scancode(int set, uint16_t scancode); | ||
| − | + | void irq_handler(uint8_t irq) { | |
| − | + | uint8_t scancode; | |
| + | uint8_t keycode = 0; | ||
| + | bool break_code = FALSE; | ||
| − | + | // Status-Variablen fuer das Behandeln von e0- und e1-Scancodes | |
| − | + | static bool e0_code = FALSE; | |
| + | // Wird auf 1 gesetzt, sobald e1 gelesen wurde, und auf 2, sobald das erste | ||
| + | // Datenbyte gelesen wurde | ||
| + | static int e1_code = 0; | ||
| + | static uint16_t e1_prev = 0; | ||
| − | + | scancode = inb(0x60); | |
| − | |||
| − | |||
| − | |||
| − | + | // Um einen Breakcode handelt es sich, wenn das oberste Bit gesetzt ist und | |
| − | + | // es kein e0 oder e1 fuer einen Extended-scancode ist | |
| − | + | if ((scancode & 0x80) && | |
| + | (e1_code || (scancode != 0xE1)) && | ||
| + | (e0_code || (scancode != 0xE0))) | ||
| + | { | ||
| + | break_code = TRUE; | ||
| + | scancode &= ~0x80; | ||
| + | } | ||
| − | + | if (e0_code) { | |
| − | + | // Fake shift abfangen und ignorieren | |
| + | if ((scancode == 0x2A) || (scancode == 0x36)) { | ||
| + | e0_code = FALSE; | ||
| + | return; | ||
| + | } | ||
| + | keycode = translate_scancode(1, scancode); | ||
| + | e0_code = FALSE; | ||
| + | } else if (e1_code == 2) { | ||
| + | // Fertiger e1-Scancode | ||
| + | // Zweiten Scancode in hoeherwertiges Byte packen | ||
| + | e1_prev |= ((uint16_t) scancode << 8); | ||
| + | keycode = translate_scancode(2, e1_prev); | ||
| + | e1_code = 0; | ||
| + | } else if (e1_code == 1) { | ||
| + | // Erstes Byte fuer e1-Scancode | ||
| + | e1_prev = scancode; | ||
| + | e1_code++; | ||
| + | } else if (scancode == 0xE0) { | ||
| + | // Anfang eines e0-Codes | ||
| + | e0_code = TRUE; | ||
| + | } else if (scancode == 0xE1) { | ||
| + | // Anfang eines e1-Codes | ||
| + | e1_code = 1; | ||
| + | } else { | ||
| + | // Normaler Scancode | ||
| + | keycode = translate_scancode(0, scancode); | ||
| + | } | ||
| − | + | if (keycode != 0) { | |
| − | + | // Keycodes an Anwendungen verteilen, die sich dafuer interessieren. | |
| − | + | send_key_event(keycode, break_code); | |
| − | + | } | |
| − | + | } | |
| − | + | </c> | |
| − | |||
| − | + | Dieses Codestück unternimmt genau die oben beschriebenen Schritte. Die Funktion | |
| + | "translate_scancode" übersetzt, wie oben im Kommentar angegeben, die Scancodes | ||
| + | in interne Keycodes der Code dazu findet sich ebenfalls im | ||
| + | [http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/modules/kbc/sc2kc.c tyndur-Repository]. | ||
| + | Es handelt sich dabei aber nur um eine mögliche Umsetzung und keineswegs um die | ||
| + | einzig mögliche, hier muss man sich selbst überlegen wie das für den konkreten | ||
| + | Anwendungsfall am komfortabelsten ist. | ||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | + | == Beispiele zur Benutzung der KBC Funktionen == | |
| − | |||
| − | |||
=== CPU-reset === | === CPU-reset === | ||
| Zeile 317: | Zeile 419: | ||
;Den neuen Wert für den Inputport über Port 0x60 senden | ;Den neuen Wert für den Inputport über Port 0x60 senden | ||
| − | mov al, | + | mov al, 0xFEi |
out 0x60, al | out 0x60, al | ||
</asm> | </asm> | ||