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