Le routine per accedere alle porte I/O sono in /usr/include/asm/io.h
(o linux/include/asm-i386/io.h
nella distribuzione del sorgente
del kernel). Tali routine sono delle macro inline, quindi è sufficiente
usare #include <asm/io.h>
; non serve nessuna libreria
aggiuntiva.
A causa di una limitazione in gcc (presente in tutte le versioni di cui
sono al corrente, compreso egcs), bisogna compilare tutti i sorgenti
che usano tali routine con l'ottimizzazione abilitata (gcc -O1
o
maggiore), oppure usare #define extern static
prima di
#include <asm/io.h>
(ricordarsi poi di mettere #undef
extern
).
Per il debugging si può usare gcc -g -O
(almeno con le ultime
versioni di gcc), sebbene l'ottimizzazione possa causare, a volte,
un comportamento un pò strano del debugger. È possibile evitare
tale inconveniente mettendo le routine che accedono alla porta I/O in
un file sorgente separato e compilare solo quest'ultimo con l'ottimizzazione
abilitata.
Prima di accedere ad una qualsiasi porta, bisogna dare al programma
il permesso per farlo. Ciò si fa chiamando la funzione ioperm()
(dichiarata in unistd.h
e definita nel kernel) da qualche parte
all'inizio del programma (prima di qualunque accesso ad una porta
I/O). La sintassi è ioperm(from, num, turn_on)
, dove from
è il primo numero di porta e num
il numero di porte consecutive a
cui dare l'accesso. Per esempio, ioperm(0x300, 5, 1)
dà l'accesso
alle porte da 0x300 a 0x304 (per un totale di 5 porte). L'ultimo argomento
è un valore booleano che specifica se dare (true (1)) o togliere (false
(0)) al programma l'accesso alle porte. È possibile chiamare più
volte ioperm()
per abilitare più porte non consecutive. Vedere la
pagina di man di ioperm(2)
per i dettagli sulla sintassi.
La chiamata ioperm()
necessita che il programma abbia i privilegi di
root; quindi bisogna eseguirlo da utente root, oppure renderlo
suid root. Dopo la chiamata ioperm
per abilitare le porte che si
vogliono usare, si può rinunciare ai privilegi di root. Alla fine del
programma non è necessario abbandonare esplicitamente i privilegi
di accesso alle porte con ioperm(..., 0)
, ciò verrà
fatto automaticamente quando il processo termina.
Un setuid()
ad un utente non root non disabilita l'accesso alla porta
fornito da ioperm()
, mentre un fork()
lo disabilita (il processo
figlio non ottiene l'accesso, il genitore invece lo mantiene).
ioperm()
può fornire l'accesso solo alle porte da 0x000 a 0x3ff;
per porte più alte bisogna usare iopl()
(che, con una sola
chiamata, fornisce l'accesso a tutte le porte). Per fornire al programma
l'accesso a tutte le porte I/O usare 3 come argomento di livello
(cioè iopl(3)
) (quindi attenzione: accedere alle porte sbagliate
può provocare un sacco di sgradevoli cose al computer). Di nuovo,
per effettuare la chiamata a iopl()
sono necessari i privilegi di root.
Per maggiori dettagli vedere le pagine di man di iopl(2)
.
Per leggere (input) un byte (8 bit) da una porta, chiamare inb(port)
,
che restituisce il byte presente all'ingresso. Per scrivere (output) un
byte, chiamare outb(valore, porta)
(notare l'ordine dei parametri).
Per leggere una word (16 bit) dalle porte x
e x+1
(un byte da
ognuna per formare una word con l'istruzione assembler inw
) chiamare
inw(x)
. Per scrivere una word sulle due porte si usa
outw(valore, x)
. Se si hanno dubbi su quali istruzioni (byte o word)
usare per le porte, probabilmente servono inb()
e outb()
--- la
maggior parte dei dispositivi sono progettati per gestire l'accesso alle
porte a livello di byte. Notare che tutte le istruzioni per accedere alle
porte richiedono almeno un microsecondo (circa) per essere eseguite.
Le macro inb_p()
, outb_p()
, inw_p()
e outw_p()
funzionano esattamente come le precedenti, ma introducono un
ritardo, di circa un microsecondo, dopo l'accesso alla porta. Si può
allungare tale ritardo a circa quattro microsecondi mettendo
#define REALLY_SLOW_IO
prima di #include <asm/io.h>
.
Queste macro, di solito (a meno non si usi #define SLOW_IO_BY_JUMPING
,
che probabilmente è meno preciso), effettuano una scrittura sulla
porta 0x80 per ottenere il ritardo e quindi, prima di usarle, bisogna
dargli l'accesso alla porta 0x80 con ioperm()
(le scritture fatte
sulla porta 0x80 non hanno conseguenze su nessuna parte del sistema).
Più avanti è spiegato come ottenere dei ritardi con sistemi più
versatili.
Nelle raccolte di pagine di man per Linux, nelle versioni
ragionevolmente recenti, ci sono le pagine di man di ioperm(2)
,
iopl(2)
e per le suddette macro.
/dev/port
Un altro modo per accedere alle porte I/O è quello di aprire, in
lettura e/o scrittura, con open()
, il dispositivo a caratteri
/dev/port
(numero primario 1, secondario 4) (le funzioni stdio
f*()
hanno un buffer interno e sono quindi da evitare). Poi
posizionarsi con lseek()
sul byte appropriato nel file
(posizione 0 del file = porta 0x00, posizione 1 del file = porta 0x01
e così via...) e leggere (read()
), o scrivere
(write()
), un byte o una word da, o in, esso.
Ovviamente, perché ciò funzioni, il programma avrà
bisogno dell'accesso in lettura/scrittura a /dev/port
. Questo metodo
è probabilmente più lento del metodo normale precedentemente
descritto, ma non ha bisogno né di ottimizzazioni in compilazione
né della funzione ioperm()
. Non serve nemmeno l'accesso
da root, se si fornisce al gruppo o agli utenti non root l'accesso a
/dev/port
-- ma, in termini di sicurezza del sistema, far questo
è una pessima idea perché è possibile danneggiare il
sistema, e forse anche ottenere l'accesso a root, usando /dev/port
per accedere direttamente agli hard disk, alle schede di rete, ecc.
Non è possibile usare select(2)
oppure poll(2)
per
leggere /dev/port
, perché l'hardware non ha la capacità
di notificare alla CPU il cambiamento del valore in una porta d'ingresso.