Keyboard Driver - Open Community
Transcript
Keyboard Driver - Open Community
Keyboard Driver by Salvatore D'Angelo Table of contents 1 Introduzione............................................................................................................................................................................. 2 2 Keyboard Programming...........................................................................................................................................................2 3 Video Programming.................................................................................................................................................................7 4 K&C Programming step 0: setup keyboard interrupt handler................................................................................................. 7 5 K&C Programming step 1: la routine printk..........................................................................................................................10 6 K&C Programming step 2: stampa degli scan code dei tasti premuti................................................................................... 12 7 K&C Programming step 3: conversione scan code/ASCII....................................................................................................13 8 K&C Programming step 4: supporto tasti SHIFT..................................................................................................................14 9 K&C Programming step 5: supporto tasti ALT, ALTGR, CTRL e EXTENDED KEYs......................................................14 10 K&C Programming step 6: supporto per il tasto CAPS LOCK........................................................................................... 17 11 K&C Programming step 7: aggiungiamo altre features al nostro driver..............................................................................18 12 K&C Programming step 8: completiamo la gestione del keypad........................................................................................ 20 13 K&C Programming step 9: gestione 4 consoles...................................................................................................................20 14 Conclusioni...........................................................................................................................................................................21 15 Download source code......................................................................................................................................................... 21 16 Bibliografia...........................................................................................................................................................................21 Open Community (C) 2004 Copyright © 2004 The Open Community Group. All rights reserved. Keyboard Driver 1. Introduzione Questo articolo vuole essere una guida step by step che guiderà il lettore nella scrittura di un driver per console (video e tastiera) basato sul codice sorgente di Linux. Prerequisito necessario alla lettura di questo articolo è la lettura dell'articolo [3] il cui codice allegato sarà la base di partenza su cui scriveremo il nostro driver. Per la scrittura di questo driver sono state presi come riferimento i kernels 0.96a e 1.0 del codice ufficiale di Linux, oltre al kernel [1]. Il driver di tastiera intercetterà i tasti premuti dall'utente e li stamperà su console video. Si gestiranno 4 console video a cui è possibile accedere attraverso la pressione dei tasti CTRL-ALT-Fn (con 1<=n<=4). Il driver è scritto in modo tale da gestire diverse locale di tastiere. Nel nostro caso, le locale supportate sono solo due: americana e italiana. Prima di iniziare la codifica del driver, l'articolo effettua una ampia panoramica sugli aspetti teorici legati alla programmazione della tastiera e console. 2. Keyboard Programming Sui PC IBM la gestione hardware della tastiera è delegata al chip 8042 (vecchie macchine AT) o ai chips 8741/8742 (nuove macchine). Nell'articolo [3] abbiamo già visto che il controller 8042 viene anche utilizzato per consentire al processore di indirizzare più di 1 Mb di memoria RAM attraverso l'abilitazione della linea A20. Sui PC IBM è possibile accedere alla tastiera in tre modi diversi, attraverso il sistema operativo, attraverso il BIOS (quando il processore lavora in modalità reale) e attraverso la programmazione diretta del controller 8042. Nel nostro caso le prime due modalità non sono applicabili visto che il nostro software non poggia su alcun sistema operativo e lavora in modalità protetta, per cui l'unico modo che abbiamo per gestire la tastiera è attraverso la sua programmazione low level. Per poter intercettare i dati provenienti dalla tastiera è necessario associare questa periferica ad una linea IRQ sul PIC. Nell'articolo sul boot process di Linux abbiamo parlato ampiamente di cosa sia il PIC e i comandi necessari alla sua programmazione. Abbiamo visto anche che il processo di boot disabilita tutte le linee IRQ eccetto IRQ2 sul Master. Sui PC IBM, generalmente, la tastiera la si associa all'IRQ1 del PIC Master che, nel processo di boot, è stato associato al vettore di interrupt 0x21. Ogni volta che si verifica un evento di tastiera, viene settato la linea IRQ1 la quale notificherà alla CPU il verificarsi di questo evento. In base al valore del bit IF (gestito attraverso i comandi assembler STI e CLI) del registro EFLAGS la CPU puo' decidere di ignorare o gestire l'evento. Nel primo caso la CPU continuerà a fare quello che stava facendo, nel secondo caso, invece, attiverà l'handler che si occuperà della gestione dell'evento. Quando un utente preme un tasto la tastiera genera due eventi, uno associato alla pressione del tasto ed uno al rilascio. Ad ogni tasto è generalmente associato uno scan code di 8 bit, dove i 7 bits meno significativi contengono il codice relativo al tasto, mentre quello più significativo conterrà 0 o 1 a seconda se il tasto è stato premuto o rilasciato. E' chiaro, quindi, che scan codes < 128 indicano la pressione di un tasto, mentre quelli >= 128 indicano un rilascio. Vedremo più avanti che, in alcuni casi, questa regola ha delle eccezioni. Supponendo che l'utente prema il tasto ESC, allora verrà generato uno scan code pari a 1. Se l'utente continua a mantenere premuto il tasto verranno generati degli scan code pari a 1 con una frequenza che dipenderà dal keyboard type rate correntemente settato per la tastiera. Quando l'utente rilascerà il tasto verrà generato uno scan code uguale a 129 (1+128). Ci sono dei tasti che generano scan code estesi, come ad esempio accade per il tasto RIGHT CTRL. Per questi tipi di tasti vengono generati due interrupt, il primo restituisce uno scan code pari a 224 (0xE0), mentre il secondo restituirà il vero e proprio scan code del tasto che, nel nostro caso, è 29 (29 è lo scan code del tasto LEFT CTRL). Mantenendo premuto il tasto RIGHT CTRL verranno generati a ripetizione gli scan code 224 29 con una frequenza che dipende dal keyboard typing rate correntemente settato. Quando il tasto verrà rilasciato verranno generati due scan code pari a 224 157 (157=29+128). Esistono poi dei tasti a cui vengono associati due codici estesi. Ad esempio, quando si preme il tasto PGUP vengono generati i seguenti due codice estesi 224 42 - 224 73. Mantenendo premuto il tasto verranno generati i soli scan code 224 73. Al rilascio Open Community (C) 2004 Page 2 Keyboard Driver del tasto verranno generati, invece, i due scan code estesi 224 170 224 201 (si osservi come 170 = 42+128, 201=73+128). Quindi da tutto cio' si evince che i tasti possono essere raggruppati in tre categorie: • tasti con scan code singolo; • tasti con scan code esteso; • tasti con doppio scan code esteso; A queste tre categorie non appartengono i tasti PRTSCN e BREAK su cui va fatto un discorso a parte. PRTSCN Quando questo tasto viene premuto senza modificatore (ALT, CTRL or SHIFT) invia i seguenti scan codes 224 42 224 55. Se il tasto rimane premuto, solo lo scan code 55 viene restituito al driver. Al rilascio del tasto vengono generati due extended scan codes 224 170 224 183 (si noti come 170=128+42 e 183=128+55). Quando il tasto viene premuto in combinazione con SHIFT o CTRL (sinistro o destro) vengono generati gli scan codes 224 55, mentre al rilascio vengono generati 224 183. Infine, in combinazione con uno dei tasti ALT genera il solo scan code 84. BREAK La pressione di questo tasto genera i seguenti scan codes: 225 29 69 225 157 197. Mantenendo il tasto premuto non vengono generati altri scan codes. In combinazione con il tasto CTRL (sinistro o destro) viene generato la seguente sequenza di scan codes 224 70, mentre in fase di rilascio viene generata la sequenza 224 198. La comunicazione del driver con il controller 8042 avviene attraverso le porte di I/O standard 0x60, 0x61 e 0x64 che ora analizzeremo in maggiore dettaglio. Porta I/O 0x60 La porta di I/O 0x60 è la porta attraverso la quale l'interrupt handler legge lo scan code relativo al tasto premuto. Questa è una porta sia di input che di output. Quando viene utilizzata come porta di input puo' restituire sia lo scan code del tasto premuto sia un particolare codice di ritorno, che esprime il risultato di un comando precedentemente inviato al controller. Nella sezione Scan Code Map sono elencati gli scan code relativi a ciascun tasto, mentre i seguenti sono gli altri possibili codici di ritorno prelevabili da questa porta. • 0x00, keyboard error, sono stati premuti troppi tasti contemporaneamente; • 0xAA, Basic Assurance Test (BAT) completato; • 0xAB-0x41, risultato della richiesta di Keybord ID su tastiere di tipo MF II; • 0xEE, risultato di un comando di echo; • 0xFA, ACK(noledge). Ad ogni comando che il driver invia al controller 8042 quest'ultimo risponde con un valore di ACK per indicare che esso è stato eseguito con successo. • 0xFC, BAT fallito; • 0xFE, indica al driver che per vari motivi il controller desidera che gli venga reinviato il precedente comando (forse perchè quest'ultimo risultava invalido); • 0xFF, keyboard error. La porta di I/O 0x60 oltre ad essere utilizzata per leggere informazioni provenienti dal controller 8042, puo' anche essere utilizzata per inviare comandi alla tastiera. Un comando puo' essere costituito da uno o due bytes. Nel secondo caso, prima di inviare il secondo byte, bisogna aspettare che il primo byte venga inviato al controller. Quando cio' avviene il bit 1 sulla porta 0x64 viene attivato. Durante la trasmissione di un comando a due bytes, la tastiera interrompe la ricezione degli scan codes. Quando si invia un comando invalido, il controller reagisce con un codice di resend (0xFE) che invita il driver a rinviare il comando. Vediamo ora i comandi di nostro maggior interesse, che è possibile inviare al controller della tastiera. Comandi della Tastiera Page 3 Open Community (C) 2004 Keyboard Driver Tutti i comandi, eccetto echo e resend chiedono la ricezione di un ACK da parte del controller. Keyboard LEDs (0xED) Questo comando è composto da due bytes, il primo è 0xED e il secondo indica quali dei tre leds bisogna accendere (valore 1) o spegnere (valore 0). 2th byte = 0 0 0 0 0 X X X | | |_ Scroll Lock | |___ Num Lock |_____ Caps Lock Echo (0xEE) Questo comando è costituito da un solo byte ed è utilizzato solo a scopi diagnostici. Se il comando viene eseguito con successo, viene attivato un interrupt con scan code 0xEE. Select scan code set (0xF0) E' possibile selezionare 3 possibili scan code set: 1, 2 o 3; dove 2 è quello di default. Questo comando è costituito da due bytes. Il primo è ovviamente 0xF0, mentre il secondo puo' assumere i seguenti valori a seconda dell'operazione voluta. 2th byte = 0: 1: 2: 3: restituisce lo scan code imposta lo scan code set imposta lo scan code set imposta lo scan code set set corrente (1, 2 o 3) 1 2 -> standard 3 Keyboard identification (0xF2) Questo comando serve a stabilire che tipo di tastiera stiamo utilizzando: XT, AT o MF II. Nel primo caso, come valore d'output non otterremo nulla, cioè un time out error sulla porta 0x64. Nel secondo caso si verifica un interrupt con codice di ACK e nel terzo caso 3 si verificano 3 interrupt con i codici 0xAB e 0x41. Set type rate (0xF3) Il secondo byte di questo comando indica il type rate da impostare. 2th byte = 0 X X X X X X X X | | |_______|___ Timing |___|_____________ Pause Timing indica il type rate, mentre Pause è l'attesa prima di poter ricevere il prossimo scan code. Questi sono i valori ammissibili per i due parametri. Timings: 0: 30 1: 26.7 2: 24 4: 20 8: 15 keys/sec keys/sec keys/sec keys/sec keys/sec 10: 10 13: 9 16: 7.5 20: 5 31: 2 keys/sec keys/sec keys/sec keys/sec keys/sec Pause: 0: 250 1: 500 2: 750 4: 1000 ms ms ms ms Enable Scanning (0F4) Open Community (C) 2004 Page 4 Keyboard Driver Azzera l'output buffer e fa partire la ricezione degli scan code da parte della tastiera. Reset keyboard disable scanning (0xF5) Effettua il reset della tastiera sospendendo la ricezione degli scan code. Reset keyboard enable scanning (0xF6) Come il comando precedente effettua il reset della tastiera ma non inibisce la ricezione degli scan code. Resend last transmission (0xFE) Invia di nuovo il precedente comando al controller. Porta I/O 0x61 Questa porta viene utilizzata per avvisare il controller dell'avvenuta ricezione di uno scan code, disabilitando e riabilitando subito la tastiera. Finchè non si effettua questa doppia operazione è possibile leggere dalla porta 0x60 lo scan code quante volte si vuole. Qui a noi interessa solo il bit 7 il quale disabilita la tastiera se viene impostato a 1 e la riabilita se viene impostato a 0. Quindi dopo aver letto lo scan code, spesso quello che viene fatto è disabilitare/abilitare la tastiera in modo da poter ricevere il successivo scan code. x=in(0x61); out(x|0x80, 0x61); out(x#0x7f, 0x61); // disable keyboard // enable keyboard Porta I/O 0x64 Questa porta è sia di input che di output. Quando viene utilizzata come porta di input, fornisce le seguenti informazioni. X X X X X X X X | | | | | | |__ | | | | | | | | | | | |______ | | | | | | | | | |__________ | | | | | | | |______________ | | | | | | | | | | | |___________________ | | | |___________________________ |_______________________________ 0: 1: 1: 0: 1: 0: 1: 0: 1: 0: 1: 1: keyboard data presente nel buffer output buffer vuoto user data present nel buffer output buffer vuoto selftest successful reset 0x64 è l'ultima porta a cui si è avvuto accesso 0x60 è l'ultima porta a cui si è avvuto accesso keyboard abilitata keyboard locked timeout-error parity-error nell'ultima trasmissione La porta 0x64 quando viene utilizzata come porta di output puo' essere utile per inviare alcuni comandi di controllo al controller 8042. Qui di seguito riportiamo alcuni dei comandi che è possibile inviare alla tastiera attraverso questa porta. Keyboard self-test (0xAA) Questo comando avvia un self-test della tastiera. Se il controller effettua tale test con successo, nel successivo interrupt restituisce il codice 0x55. Disattiva tastiera (0xAD) Impedisce la ricezione di scan code da parte del controller. Alcuni driver Linux (vedi [9]) utilizzano questo comando per Page 5 Open Community (C) 2004 Keyboard Driver disattivare la tastiera, anzichè il bit 7 della porta 0x61. Attiva tastiera (0xAE) Riattiva la ricezione di scan code da parte del controller. Alcuni driver Linux (vedi [9]) utilizzano questo comando per attivare la tastiera, anzichè il bit 7 della porta 0x61. Scan Code Map Abbiamo visto come la pressione di tasti, se sono vere certe condizioni, generano un serie interrupt che attiverà un handler attraverso cui si potrà leggere lo scan code relativo al tasto premuto. In genere il controller 8042 gestisce tre scan code map: 1, 2 (default) e 3. In questa sezione mostriamo la mappa degli scan code di default prendendo come riferimento una tastiera italiana. Tasto Scan code Tasto Scan code Tasto Scan code Tasto Scan code ESC 1 H 35 1 2 J 36 2 3 K 37 3 4 L 38 4 5 o' 39 5 6 à 40 6 7 ù 43 7 8 LSHIFT 42 8 9 < 86 9 10 Z 44 0 11 X 45 ' 12 C 46 i' 13 V 47 BACKSPACE14 B 48 TAB 15 N 49 Q 16 M 50 W 17 , 51 E 18 - 53 R 19 RSHIFT 54 T 20 LCTRL 29 Y 21 ALT 56 U 22 SPACE 57 I 23 CAPS LOCK 58 O 24 F1 59 P 25 F2 60 è 26 F3 61 + 27 F4 62 ENTER 28 F5 63 A 30 F6 64 S 31 F7 65 D 32 F8 66 F 33 F9 67 G 34 F10 87 . 52 F11 88 Tasto keypad Scan code Tasto keypad Scan code Tasto keypad Scan code Tasto keypad Scan code * 55 5 76 - 74 6 77 + 78 1 79 7 71 2 80 8 72 3 81 9 73 0 82 4 75 , 83 Extended key Scan code Open Community (C) 2004 Page 6 Keyboard Driver / (keypad) 224 53 ALTGR 224 56 CTRL RIGHT 224 29 Extended key Double scan code Extended key Double scan code CANC 224 42 224 83 DEL 224 42 224 79 PG DOWN 224 42 224 81 PG UP 224 42 224 73 INIT 224 42 224 71 INS 224 42 224 82 LEFT ARROW 224 42 224 75 RIGHT ARROW 224 42 224 77 UP ARROW 224 42 224 72 DOWN ARROW 224 42 224 80 3. Video Programming Il PC IBM usa un video mappato in memoria per il terminale di sistema (video). In pratica la scrittura di pixel sullo schermo avviene scrivendo opportunamente dei bytes in una precisa regione della memoria RAM. In questa sezione prenderemo in considerazione solo la modalità video a caratteri, trascurando quella grafica. Le regioni di video RAM partono all'indirizzo 0xB0000 per i video monocromatici (oggi davvero rari) e 0xB8000 per quelli a colori. Ogni carattere sullo schermo occupa due bytes: il byte di ordine inferiore contiene il codice ASCII del carattere, mentre quello di ordine superiore è il byte degli attributi che ne specifica il colore, il lampeggiamento ecc. L'intero schermo di 26 righe per 80 colonne occupa 4 Kb in video RAM. 4. K&C Programming step 0: setup keyboard interrupt handler Per prima cosa da fare in questo step è quella di modificare la routine principale del kernel affinchè inizializzi il TTY layer del kernel. Il TTY layer gestisce i terminali del kernel. Un terminale non è altro che un dispositivo composto da una tastiera ed un video. Quindi creiamo un nuovo file chiamato tty_io.c in cui va scritta la routine di inizializzazione del layer TTY. Per ora questa routine chiamerà solo la routine di inizializzazione della console. void tty_io(void) { con_init(); } La routine con_init è implementata in un nuovo file chiamato console.c che conterrà tutto il codice necessario per la gestione delle 4 consoles. Questa routine per ora non farà altro che inizializzare il codice del driver della tastiera. void con_init(void) { keyboard_init(); } La funzione keyboard_init è definita in un nuovo file keyboard.c che conterrà tutto il codice relativo al driver della tastiera. Questa routine non fa altro che definire un handler per gestire l'interrupt di tastiera. L'handler dell'interrupt di tastiera viene associato in questa routine all'IRQ1 a cui corrisponde, come sappiamo, il vettore di interrupt 0x21 (vedi la sezione PIC programming in [3]). Per fare cio' utilizzeremo la routine set_intr_gate, definita nel file system.h, che associa un handler a uno specifico vettore di Page 7 Open Community (C) 2004 Keyboard Driver interrupt nella IDT. set_intr_gate(0x21, <kb_intr); Fatto cio' si abilita la linea IRQ1, disabilitata in fase di boot, ponendo a 0 il bit 1 della porta di I/O 0x21 (quella del Master). outb_p(inb_p(0x21) & 0xfd, 0x21); Completano la fase di initializazzione le istruzioni per disabilitare/abilitare la tastiera, grazie alle quali sarà possibile iniziare a ricevere scan code attraverso l'handler. // disable/enable the keyboard a=inb_p(0x61); outb_p(a|0x80, 0x61); outb(a, 0x61); Le routine outb_p, inb_p e outb sono definite nel file io.h. A questo punto ogni volta che si premerà un tasto verrà attivata la routine kb_intr. Questa routine non puo' essere una funzione C visto che il compilatore mette RET come istruzione finale di una funzione, mentre il nostro interrupt handler deve terminare con IRET. Quindi utilizziamo un nuovo file chiamato asm.S con una routine kb_intr in cui salviamo nello stack i registri e invochiamo la routine C keyboard_interrupt (il vero handler dell'interrupt di tastiera). #define SAVE_ALL \ cld; \ pusha; \ pushw %ds; \ pushw %es; \ pushw %fs; \ pushw %gs; \ movw $0x10,%si; \ movw %si,%ds; \ movw %si,%es; \ pushl $ret_from_intr .global kb_intr ; .align 16,0x90; kb_intr: SAVE_ALL /* salva tutto! */ STI call keyboard_interrupt RET Si noti come gli interrupt vengono disabilitati prima dell'invocazione dell' handler e che l'istruzione RET non fa altro che prelevare l'indirizzo di ret_from_intr dallo stack (visto che la SAVE_ALL la aveva messo sul top dello stack) e fare un jump ad esso. La routine ret_from_intr non fa altro che ripristinare i registri e invocare IRET. ret_from_intr: CLI /* NO INTERRUPT! */ popw %gs popw %fs popw %es popw %ds popa IRET Open Community (C) 2004 Page 8 Keyboard Driver Nel codice allegato le seguenti istruzioni vengono sostituiti dalla macro ENTRY (vedi il codice sorgente allegato). .global kb_intr; .align 16,0x90; kb_intr: Per ora nell'handler della tastiera stamperemo un semplice messaggio di "Hello Keyboard", cosi' come riportato in [3]. unsigned char *vid_mem = (unsigned char *)(0xb8000); void keyboard_interrupt(void) { *vid_mem++ = 'H'; *vid_mem++ = 0x6; *vid_mem++ = 'e'; *vid_mem++ = 0x6; *vid_mem++ = 'l'; *vid_mem++ = 0x6; *vid_mem++ = 'l'; *vid_mem++ = 0x6; *vid_mem++ = 'o'; *vid_mem++ = 0x6; .... } Affinchè il codice scritto finora sia invocabile dal kernel è necessario modificare la main routine affinche inizializzi il codice del terminale. void start_kernel(void) { tty_init(); sti(); // idle loop while(1); } sti è una routine C che praticamente esegue una istruzione assembler STI. In questo caso è necessario invocarla affinchè la CPU accetti interrupt dal PIC. A questo punto possiamo passare alle modifiche del Makefile necessarie per compilare il codice fin qui realizzato. Visto che d'ora in poi gli object files del kernel diventeranno sempre di più, definiamo la macro KERNEL_OBJS in modo da contenerli tutti. KERNEL_OBJ=head.o main.o tty_io.o keyboard.o console.o asm.o quindi modifichiamo la regola che crea l'immagine del kernel nel modo seguente: kernel: $(KERNEL_OBJ) $(LD) -e stext -Ttext 0x1000 -s --oformat binary $(KERNEL_OBJ) -o $@ A questo punto, per evitare che ogni volta che introduciamo un file C bisogna modificare il Makefile, rimuoviamo tutte le regole esplicite sostituendole con la seguente. .c.o: $(CC) -Wall -O -fstrength-reduce -fomit-frame-pointer -c $< -o $@ Come al solito con i seguenti comandi è possibile ottenere il boot disk del nostro nuovo kernel. make clean Page 9 Open Community (C) 2004 Keyboard Driver make make disk 5. K&C Programming step 1: la routine printk Finora abbiamo visto che per scrivere un messaggio a video, bisogna scrivere i bytes da visualizzare direttamente nell video RAM. Per lo sviluppo del nostro driver ed eventuali estensioni future, sicuramente risulta importante poter disporre di una routine di stampa che consenta di stampare messaggi in modo più agevole, magari accettando anche parametri come la printf della standard library. Sappiamo che per il nostro kernel non è possibile utilizzare quest'ultima, per cui si rende necessario implementare una routine simile direttamente nel kernel. Nel file console.c definiamo una struttura console_c che rappresenta una generica console. struct console_s { unsigned long video; unsigned long pos; unsigned long x,y; unsigned char attr; }; L'attributo video punta alla locazione di inizio delle memoria video per la console. La memoria video RAM considereremo solo 4 regioni (pagine) di 4Kb ciascuna, una per ogni console supportata. Gli attributi x, y sono le coordinate correnti del cursore per la console. L'attributo pos definisce la posizione del cursore nella memoria video RAM. In generale, dati i valori di video, x e y è possibile dedurre il valore di pos. L'attributo attr specifica l'attributo correntemente utilizzato dalla routine di stampa. In questo step supportiamo una sola console, definita nel modo seguente: static struct console_s cc = { MEM_VIDEO, MEM_VIDEO, 0, 0, 0x07}; nei steps successivi aggiungeremo il supporto per le altre 3 consoles. Definiamo alcune macro che ci torneranno utili, soprattutto nei steps successivi nella gestione delle consoles. #define #define #define #define #define VIDEO X Y POS ATTR (cc.video) (cc.x) (cc.y) (cc.pos) (cc.attr) Definiamo una nuova routine per il posizionamento del cursore in un dato punto del video. Questa routine prende in input le coordinate x,y e calcola il relativo valore di pos. static inline void gotoxy(unsigned int new_x,unsigned int new_y) { if (new_x>=SCR_W || new_y>=SCR_H) return; X=new_x; Y=new_y POS=VIDEO+((Y*SCR_W+X)<<1); } Le costanti SCR_W e SCR_H definiscono, rispettivamente, la larghezza e altezza del video (80x25). Utilizziamo questa routine in con_init prima dell'inizializzazione della tastiera. Open Community (C) 2004 Page 10 Keyboard Driver void con_init(void) { gotoxy(0, 0); keyboard_init(); } Nei successivi steps vedremo come far stampare i caratteri digitati subito dopo la scrittura dei messaggi di boot. A questo punto siamo pronti ad esaminare la routine printk il cui codice è riportato qui di seguito. asmlinkage int printk(const char *fmt, ...) { va_list args; int len; char *ch; long flags; va_start(args, fmt); len = vsprintf(buf,fmt,args); va_end(args); save_flags(flags); cli(); for (ch=buf; *ch; ch++) print_char (*ch); restore_flags(flags); return len; } Questa routine introduce molte novità che ora analizzeremo una ad una in dettaglio. Innanzittutto notiamo l'uso di una nuova keyword asmlinkage definita nel file kernel.h. Alan Cox tempo fa rispose in questo modo ad un nuovo utente Linux che chiese a cosa servisse questa direttiva: asmlinkage is used to mark routines called from the .S (assembler) files so they dont get mashed by C++ name mangling in pratica egli dice che viene usata davanti alle routine C invocate da file assembler .S in modo tale che il compilatore non faccia ottimizzazione sui nomi presenti nella signature della routine. Le FAQ di kernelnewbies.org aggiungono inoltre: The asmlinkage tag is one other thing that we should observe about this simple function. This is a #define for some gcc magic that tells the compiler that the function should not expect to find any of its arguments in registers (a common optimization), but only on the CPU's stack. cioè, un compilatore GCC potrebbe applicare alcune ottimizzazioni ponendo i parametri in input nei registri anzichè sullo stack. Questa direttiva lo impedisce. Notiamo poi l'uso delle routine: save_flags(flags); cli(); ... restore_flags(flags); anzichè le classiche cli() e sti() per disabilitare/abilitare gli interrupt. Questo avviene per una ragione molto importante. Supponiamo di invocare printk da una routine che ha disabilitato gli interrupt per qualche motivo. Supponiamo, inoltre, di Page 11 Open Community (C) 2004 Keyboard Driver usare cli() e sti() all'interno di printk anzichè le routine mostrate sopra. Il risultato sarebbe che all'uscita della printk gli interrupt vengono abilitati, mentre quello che si vorrebbe è di ritornare alla routine chiamante con lo stato del bit IF del registro EFLAGS uguale a prima dell'invocazione. Il ciclo for seguente, stampa a video la stringa in input a printk. for (ch=buf; *ch; ch++) print_char (*ch); buf è un buffer di 1024 caratteri precedentemente elaborato attraverso le seguenti istruzioni: va_start(args, fmt); len = vsprintf(buf,fmt,args); va_end(args); che praticamente esplodono i parametri addizionali della printk formattandoli in base alle direttive riportate nella stringa. Per maggiori dettagli si controlli il file vsprintf.c che in questa sede evitiamo di descrivere visto che si tratta di una routine C che, seppur complessa, non dovrebbe presentare difficoltà di comprensione per un programmatore C di media esperienza. La routine print_char oltre a stampare i caratteri a video, tiene conto dei return carriage, dello scroll del video e altro ancora. Si veda il file sorgente console.c per maggiori dettagli. A questo punto, testiamo la nostra routine printk sostituendo nell'handler della keyboard le precedenti istruzioni di stampa, con la sola riga: printk("Hello Keyboard"); 6. K&C Programming step 2: stampa degli scan code dei tasti premuti Modifichiamo ora il nostro handler affinchè sia capace di stampare a video gli scan code relativi ai tasti premuti. Nello step precedente abbiamo visto che l'handler viene attivato una sola volta, alla pressione del primo tasto, mentre le volte successive non accade nulla. Questo avviene perchè ad ogni attivazione, la tastiera deve essere disabilitata e abilitata per accettare successivi scan code. Abbiamo visto che fare cio' basta porre a 1 il bit 7 della porta 0x61 e poi riportarla subito a 0: x=inb_p(0x61); outb_p(x|0x80, 0x61); outb_p(x&0x7f, 0x61); questo ovviamente subito dopo la lettura dello scan code dalla porta 0x60. scancode=inb_p(0x60); Nella fase di PIC programming in [3] abbiamo visto che l'end of interrupt (EOI) viene dichiarato inviando il comando OCW2 (bit 5 a 1) sulla porta 0x20 (Master) o 0xA0 (Slave). Quindi utilizziamo la seguente istruzione per informare il PIC circa il completamento dell'interrupt. outb(0x20, 0x20); Come ultima azione del keyboard handler, stampiamo lo scan code relativo al tasto premuto. printk("Scan code = %d \n", scancode); Open Community (C) 2004 Page 12 Keyboard Driver 7. K&C Programming step 3: conversione scan code/ASCII Da un punto di vista utente è ovvio che la stampa degli scan code non ha senso, l'utente vuole visualizzare i codici ASCII relativi ai tasti premuti. Per far cio' bisogna avere un meccanismo che ci consenta di convertire questi scan code in codici ASCII. Definiamo innanzitutto una tabella di funzioni (uno per ogni scan code possibile) come riportato di seguito. static fptr key_table[] = { none,do_self,do_self,do_self, do_self,do_self,do_self,do_self, do_self,do_self,do_self,do_self, do_self,do_self,do_self,do_self, do_self,do_self,do_self,do_self, do_self,do_self,do_self,do_self, ... } Le funzioni riportate in questa tabella, sono le seguenti: do_self: converte uno scan code in un carattere ASCII; none: non fa nulla; enter: gestisce il tasto ENTER; lshift: gestisce la pressione del tasto sinistro SHIFT; unlshift: gestisce il rilascio del tasto sinistro SHIFT; rshift: gestisce la pressione del tasto destro SHIFT; unrshift: gestisce il rilascio del tasto destro SHIFT; ctrl: gestisce la pressione del tasto CTRL; unctrl: gestisce il rilascio del tasto CTRL; slash: gestisce il tasto SLASH sul keypad; star: gestisce il tasto STAR sul keypad; alt: gestisce la pressione dei tasti ALT e ALTGR; unalt: gestisce il rilascio dei tasti ALT e ALTGR; caps: gestisce la pressione del tasto CAPS LOCK; uncaps: gestisce il rilascio del tasto CAPS LOCK; func: gestisce i tasti funzione; scroll: gestisce il tasto di SCROLL; cursor: gestisce i tasti cursore; minus: gestisce il tasto MINUS sul keypad; plus: gestisce il tasto PLUS sul keypad; A questo punto, alla fine dell'handler basta scrivere: key_table[scancode](scancode); per delegare la gestione degli scan code a queste funzioni. In questo step l'unica routine implementata è la do_self: Page 13 static void do_self(int sc) { unsigned char ch; ch=key_map[sc]; if (ch == 0) { return; Open Community (C) 2004 Keyboard Driver } printk("%c", ch); } che in pratica utilizza una key map definita in keymap.h per convertire gli scan codes in caratteri ASCII. In questo header file è possibile definire key map per tastiere di diversa nazionalità, nel nostro caso sono supportate solo la tastiera italiana e americana. La scelta viene effettuata attraverso la macro KEYBOARD nel Makefile. In questo step non sono gestiti caratteri maiuscoli ottenuti mediante pressione di SHIFT o CAPS LOCK. 8. K&C Programming step 4: supporto tasti SHIFT In questo step faremo in modo che, se l'utente tiene premuto uno dei due tasti SHIFT e preme un tasto, venga stampato a video il relativo carattere ASCII in maiuscolo. Le modifiche da apportare al codice non sono tante. Innanzittutto, nel file keymap.h si aggiunga una nuova mappa che chiameremo shift_map, la quale contiene il mapping tra scan code del tasto premuto con il relativo carattere maiuscolo (vedi codice sorgente allegato). Introduciamo la variabile kmode che non è altro che una maschera che puo' contenere i seguenti valori. #define LSHIFT #define RSHIFT 0x01 0x02 Modifichiamo poi le funzioni lshift, unlshift, rshift e unrshift come mostrato di seguito, in modo tale che quando premiamo o rilasciamo uno dei tasti SHIFT, il valore di kmode venga alterato. static void lshift(int sc) { kmode|=LSHIFT; } static void unlshift(int sc) { kmode&=(~LSHIFT); } static void rshift(int sc) { kmode|=RSHIFT; } static void unrshift(int sc) { kmode&=(~RSHIFT); } Infine modifichiamo la funzione do_self in modo tale che tenga conto del valore di kmode. static void do_self(int sc) { unsigned char ch; if (kmode & (LSHIFT|RSHIFT)) { ch=shift_map[sc]; } else { ch=key_map[sc]; } ..... } 9. K&C Programming step 5: supporto tasti ALT, ALTGR, CTRL e EXTENDED KEYs Open Community (C) 2004 Page 14 Keyboard Driver Anche in questo step, come quello precedente, i soli file a dover essere modificati sono keymap.h e keyboard.c. In questo step, l'obiettivo è quello di associare un codice ASCII anche ai tasti con scan code esteso e quelli premuti in combinazione con ALT, ALTGR e CTRL. Innanzittutto introduciamo altri valori che si potranno avere nella maschera kmode. #define #define #define #define #define #define LSHIFT RSHIFT LCTRL RCTRL ALT ALTGR 0x01 0x02 0x04 0x08 0x10 0x20 Per gestire i tasti premuti in combinazione con ALTGR, introduciamo in keymap.h la mappa alt_map e modifichiamo do_self nel modo seguente. static void do_self(int sc) { unsigned char ch; if (kmode & ALTGR) { ch=alt_map[sc]; } else if (kmode & (LSHIFT|RSHIFT)) { ch=shift_map[sc]; } else { ch=key_map[sc]; } ... } Tenendo premuto uno dei tasti CTRL in combinazione con i tasti relativi alle lettere, effettueremo le seguenti associazioni: CTRL-A = 0x01 CTRL-B = 0x02 CTRL-C = 0x03 ........ Invece tenendo premuto un tasto in combinazione con ALT, attiveremo il bit 7 del relativo codice ASCII (che per definizione è sempre 0). Questo è il codice finale della funzione do_self. Page 15 static void do_self(int sc) { unsigned char ch; if (kmode & ALTGR) { ch=alt_map[sc]; } else if (kmode & (LSHIFT|RSHIFT|LCTRL|RCTRL)) { ch=shift_map[sc]; } else { ch=key_map[sc]; } if (ch == 0) { return; } if (kmode & (LCTRL|RCTRL)) { if ((ch>='a' && ch <='z') || (ch>=224 && ch<=254)) { ch -= 32; } } Open Community (C) 2004 Keyboard Driver if (kmode & (LCTRL|RCTRL)) { ch &= 0x1f; } if (kmode & ALT) { ch |= 0x80; } printk("%c", ch); } I tasti RIGHT CTRL e ALTGR producono un extended scan code, per cui è necessario modificare l'handler per poterli intercettare. Introduciamo la variabile ke0 che inizializzeremo a 0. unsigned char ke0 = 0; Modifichiamo l'handler in modo tale da intercettare anche extended scan code. In pratica se l'handler riceverà un codice 0xE0, setterà la variabile a 1, se riceverà 0xE1 imposterà la variabile a 2. Qui di seguito il nuovo codice dell'handler. void keyboard_interrupt(void) { ... if (scancode == 0xE0) { ke0 = 1; } else if (scancode == 0xE1) { ke0 = 2; } else { key_table[scancode](scancode); ke0 = 0; } } Vediamo ora le funzioni in key_table che gestiranno i tasti CTRL e ALT. static void ctrl(int sc) { if (ke0) { kmode|=RCTRL; } else { kmode|=LCTRL; } } static void unctrl(int sc) { if (ke0) { kmode&=(~RCTRL); } else { kmode&=(~LCTRL); } } static void alt(int sc) { if (ke0) { kmode|=ALTGR; } else { kmode|=ALT; } } static void unalt(int sc) { Open Community (C) 2004 Page 16 Keyboard Driver if (ke0) { kmode&=(~ALTGR); } else { kmode&=(~ALT); } } 10. K&C Programming step 6: supporto per il tasto CAPS LOCK In questo step aggiungeremo il supporto per il CAPS LOCK. Quando questo tasto viene premuto, bisogna far accendere sulla tastiera il relativo LED e scrivere in maiuscolo tutte le lettere successivamente digitate. Ad una pressione successiva del tasto, bisognerà spegnere il led e rispristinare la condizione iniziale. Introduciamo le seguenti costanti. #define CAPS 0x40 #define CAPSDOWN 0x80 Definiamo, inoltre, una maschera di bits che conterrà i leds correntemente attivi. #define SCRLED 0x01 #define NUMLED 0x02 #define CAPSLED 0x04 unsigned char kleds = NUMLED; static unsigned char old_leds = NUMLED; Implementiamo poi le routine di gestione del tasto caps lock. static void caps(int sc) { if (!(kmode&CAPSDOWN)) { kleds^=CAPSLED; kmode^=CAPS; kmode|=CAPSDOWN; set_leds(); } } static void uncaps(int sc) { kmode&=(~CAPSDOWN); } La routine set_leds si occupa di accendere o spegnere i leds in base al valore della maschera kleds, utilizzando il comando 0xED sulla porta 0x60. void set_leds(void) { if (kleds != old_leds) { old_leds=kleds; kb_wait(); outb(0xED, 0x60); kb_wait(); outb(kleds, 0x60); } } Infine è necessario una piccola modifica alla routine do_self per fare in modo che le lettere digitate con CAPS LOCK attivo vengano stampate in maiuscolo. Page 17 static void do_self(int sc) { ... Open Community (C) 2004 Keyboard Driver if (kmode & (LCTRL|RCTRL|CAPS)) { if ((ch>='a' && ch <='z') || (ch>=224 && ch<=254)) { ch -= 32; } } ... } 11. K&C Programming step 7: aggiungiamo altre features al nostro driver In questo step aggiungeremo altre importanti features al nostro driver. Faremo in modo che digitando ALT-nnn venga stampato a video il carattere ASCII nnn., supporteremo il reboot attraverso la pressione dei tasti CTRL-ALT-CANC, gestiremo il keypad (o tastierino numerico) compreso il supporto per il tasto num-lock. Quando digitiamo ALT-nnn utilizziamo una variabile temporanea per contenere il valore nnn. static int npadch = 0; Tenendo premuto il tasto ALT e digitando i numeri nnn sul keypad dovremo essere in grado di visualizzare il carattere relativo al codice ASCII nnn. Implementiamo la routine cursor come riportato di seguito. static void cursor(int sc) { unsigned char ch; if (sc < 0x47 || sc > 0x53) { return; } sc-=0x47; // if CTRL-ALT-DEL has been pressed, reboot the machine if (sc == 12 && (kmode&(LCTRL|RCTRL)) && (kmode&(ALT|ALTGR))) { reboot(); return; } if (ke0 == 1) { return; } // the user is pressing ALT-numpad. if ((kmode & ALT) && sc!=12) { npadch=npadch*10 + pad_map[sc]; return; } if (kleds & NUMLED) { ch = num_map[sc]; printk("%c", ch); } } Si osservi innanzittutto come questa routine accetta solo scan code compresi tra 0x47 e 0x53 e normalizza i loro valori facendoli partire da 0. A questo punto, avviene il controllo per vedere se è stato premuto CTRL-ALT-CANC e, in caso affermativo, viene effettuato il Open Community (C) 2004 Page 18 Keyboard Driver reboot con la routine reboot. long no_idt[2] = {0, 0}; /* * This routine reboots the machine by asking the keyboard * controller to pulse the reset-line low. We try that for * a while, and if it doesn't work, we do some other stupid * things. */ static void reboot(void) { int i; sti(); for (;;) { for (i=0; i<100; i++) { kb_wait(); *((unsigned short *)0x472)=0x1234; /* pulse reset low */ outb(0xfe,0x64); } __asm__("\tlidt no_idt"::); } } Se il tasto ALT risulta premuto è non abbiamo premuto il tasto CANC, allora aggiorniamo il valore di npadch. Vediamo con un esempio come avviene questa modifica. Supponiamo che l'utente digiti ALT-126. Alla prima iterazione, quando digitiamo 1, npadch vale: npadch = 0*10 + 1 = 1 alla seconda iterazione, quando digitiamo 2, npadch vale: npadch = 1*10 + 2 = 12 alla terza iterazione, quando digitiamo 6, npadch vale: npadch = 12*10 + 6 = 126 Rilasciando il tasto ALT verrà eseguita la routine unalt che noi modificheremo nel modo seguente. static void unalt(int sc) { if (ke0) { kmode&=(~ALTGR); } else { kmode&=(~ALT); // // // if check if we press ALT-char code. For example if we press ALT-126 the ~ character should be printed. (npadch != 0) { printk("%c", npadch); npadch=0; } } } Si osservi che se al rilascio di ALT npadch contiene un valore non nullo nnn, questo codice ASCII viene stampato a video. Per cui premendo ALT-126 comparirà a video il carattere ~ il cui codice ASCII è proprio 126. Si osservi l'uso di una nuova mappa pad_map dichiarata nel file keymap.h. Page 19 Open Community (C) 2004 Keyboard Driver Se NUM LOCK è attivo e si digita un numero sul keypad viene visualizzato il relativo numero presente nella mappa num_map dichiarata sempre nel file keymap.h. Per fare in modo che quando si preme NUM LOCK si accende/spegne il relativo led, bisogna implementare la routine num. static void num(int sc) { ... kleds^=NUMLED; set_leds(); } 12. K&C Programming step 8: completiamo la gestione del keypad Ci sono alcuni tasti come /, *, - e + che andrebbero ulteriormente gestiti. Questi vengono gestiti attraverso le routine slash, star, minus and plus. Per le ultime tre, cosi' come è scritto il nostro driver basterebbe anche una semplice chiamata a do_self, anche se ho preferito lasciare il codice originale di Linux. Infine, per slash avviene una semplice stampa del carattere /. Il tasto scroll, invece, è gestito attraverso la routine scroll, la quale provvederà solo ad accendere/spegnere il relativo led. static void scroll(int sc) { kleds^=SCRLED; set_leds(); } 13. K&C Programming step 9: gestione 4 consoles Fino ad ora abbiamo gestito una sola console video di 80 colonne x 25 righe. In questo step vedremo come sia possibile gestire 4 console a cui è possibile accedere premendo i tasti CTRL-ALT-Fn (1<=n<=4). Tutte le modifiche per aggiungere questo supporto impatteranno maggiormente il file console.c e solo in parte il file keyboard.c . Introduciamo alcune costanti e dichiariamo un array globale che conterrà i dati delle 4 consoles di cui la prima sarà quella di default. #define N_CONSOLES 4 ... #define SCR_SIZE (SCR_W*SCR_H*2) #define is_valid_console(n) ((n)>=1 && (n)<=N_CONSOLES) ... #define MAKE_CONSOLE(n) { \ MEM_VIDEO + SCR_SIZE*(n-1), \ MEM_VIDEO + SCR_SIZE*(n-1), \ 0,0,0x07} static struct console_s console[N_CONSOLES+1] = { MAKE_CONSOLE(1), // Not used MAKE_CONSOLE(1), // Console 1 MAKE_CONSOLE(2), // Console 2 MAKE_CONSOLE(3), // Console 3 MAKE_CONSOLE(4), // Console 4 }; static struct console_s *cc = &console[1]; Infine aggiungiamo la routine che verrà utilizzata per effettuare lo switch tra consoles. void switch_to_console(int n) { long flags; if (is_valid_console(n) && cc != &console[n]) { Open Community (C) 2004 Page 20 Keyboard Driver save_flags(flags); cli(); cc = &console[n]; set_origin(); set_cursor_pos(); restore_flags(flags); } } Questa routine verrà invocata nel keyboard driver quando premeremo CTRL-ALT-Fn con 1<=n<=4. Per far cio' bisognerà implementare la routine func che gestirà la pressione dei tasti funzione. Senza ulteriori commenti riportiamo qui di seguito il codice che risulta abbastanza semplice da comprendere. static void func(int sc) { if (sc < 0x3b) { return; } sc-=0x3b; if (sc > 9) { sc-=18; if (sc < 10 || sc > 11) { return; } } if ((kmode & (LCTRL|RCTRL)) && (kmode & ALT)) { switch_to_console(sc+1); } else { printk("%s", func_map[sc]); } } Si osservi come venga invocata la routine swicth_to_console se si preme CTRL-ALT-Fn. Se, invece, viene premuto un tasto Fn singolarmente verranno stampate a video sequenze di caratteri prestabilite nella mappa func_map definita sempre nel file keymap.h. 14. Conclusioni Sicuramente con questo articolo il lettore avrà le idee più chiare su come Linux gestire una tastiera su architetture Intel. Il codice di Linux utilizzato come riferimento sicuramente non è nuovissimo, tuttavia io penso che sia una valida guida per il lettore che non ha grande esperienza nell'ambito del kernel programming. 15. Download source code Clicca qui (../downloadarea.html) per scaricare il codice. 16. Bibliografia [1] Codice sorgente del kernel Pablox http://members.xoom.virgilio.it/javaman [2] Codice sorgente del kernel Scaraos http://www.scaramanga.co.uk/scaraOS [3] Linux Native Boot Process (boot.html) Page 21 Open Community (C) 2004 Keyboard Driver [4] Understanding Linux Kernel Bovet & Cesati O'Reilly [5] Linux Internals Second Edition Michael Beck & Harold Boheme Addison Wesley [6] Intel Corporation - "80386 Programmer's Manual" (http://www7.informatik.uni-erlangen.de/~msdoerfe/embedded/386html/toc.htm) [7] Sistema operativo Prometeus Antonio Mazzeo http://www.programmazione.it [8] Wout Mertens' Guide to Keyboard Programming x1.1 http://osdev.neopages.net/docs/pdf/wout_kbd.pdf [9] Linux Source Code 0.96a http://www.kernel.org [10] Linux Source Code 1.0 http://www.kernel.org [11] Progettazione e sviluppo dei sistemi operativi Edizione italiana Andrew S Tanembaum Paolo Ciancarino Gruppo Editoriale Jackson [12] Inline assembly for x86 in Linux http://www-106.ibm.com/developerworks/linux/library/l-ia.html Open Community (C) 2004 Open Community (C) 2004 Page 22