PLUTO Journal nr.6 [mSQL e Web] [Indice] [Schede Cyclades] Articoli

Device Drivers #3

di Georg v. Zezschwitz and Alessandro Rubini

Ristampato con il permesso del Linux Journal. This material is reprinted with permission of Linux Journal.

Il Dèmone è nei Dettagli

Questo articolo è il terzo di cinque parti sulla scrittura di device driver a carattere, e introduce i concetti riguardanti la lettura e la scrittura del dispositivo, come pure l'implementazione di ioctl().

Sulla base dell'ambiente che abbiamo predisposto nei due precedenti articoli, cercheremo questa volta di riempire la struttura con il materiale relativo alle interrupt. Straordinariamente, Linux ci nasconde la maggior parte delle questioni problematiche, così che non dovremo scrivere nemmeno una linea di assembler.


The Devil's in the Details

This article, the third of four sections on writing character device drivers, introduces the different concepts of reading, writing, and ioctl-calls, and how to form them into concrete code.

Based on the clean code environment of the two previous articles, we will now fill the mold with all the nasty interrupt stuff. Astonishingly, Linux hides most of this from us, so we do not need a single line of assembler...

Leggere e scrivere

Finora il nostro driver per la skel-machine è in grado di agganciarsi e sganciarsi dal kernel senza problemi, ma non siamo ancora stati in grado di leggere o scrivere nemmeno un carattere. Inizieremo quindi fornendo il codice per le funzioni skel_read() e skel_write(), che abbiamo presentato nel precedente articolo, sotto il paragrafo Fops e filp. Ad entrambe le funzioni vengono passati quattro argomenti:

Reading and writing

Right now, our magic skel-machine driver can load and even unload (painlessly, unlike in dos), but we have neither read nor written a single character. So we will start filling the skel_read() and skel_write() functions introduced in the previous article (under fops and filp) with content. Both function are supplied with four arguments:

	Static int skel_read (struct inode *inode,
	                      struct file *file,
	                      char *buf, int count)
	Static int skel_write (struct inode *inode,
	                       struct file *file,
	                       char *buf, int count)
La struttura inode ci fornisce le informazioni che abbiamo già usato all'interno di skel_open(). Per esempio, avevamo determinato quale scheda l'utente sta aprendo (ricordiamo che il nostro driver supporta dispositivi multipli) usando inode->i_rdev; avevamo poi trasferito questa informazione, insieme all'indirizzo di I/O della scheda e il numero di IRQ, nel campo private_data della struttura file. Possiamo quindi ignorare queste informazioni nella read(), ma se non avessimo usato private_data l'unico modo per sapere a quale scheda stiamo per parlare sarebbe la lettura di inode. The inode structure supplies us with information we used already during the skel_open() call. For example, we determined from inode->i_rdev which of perhaps multiple boards the user wants to open, and transfered this information, together with information about this board's base address and interrupt to the private_data entry of the file descriptor. Therefore, we might ignore this information now, but if we did not use this hack, inode is our only chance to find out to which board we are talking.
La struttura file, invece, contiene informazioni probabilmente più utili: si possono esplorare tutti i suoi campi in <linux/fs.h>. Se si vuole usare il campo private_data lo si trova qui; anche il campo f_flags spesso utile in quanto rivela, per esempio, se il programma desidera operare in modo bloccante o non-bloccante -- questo argomento verrà spiegato dettagliatamente più avanti. The file structure contains information that is probably more valuable. You can explore all its elements in its definition in <linux/fs.h>. If you use the private_data entry, you find it here, and you should also make use of the f_flags entry, revealing to you, for instance, if the user wants blocking or non-blocking mode. (We explain this topic in more detail later on.)
Gli argomenti buf e count dicono dove mettere i dati che leggiamo (o dove trovare i dati che dobbiamo scrivere), e quanti bytes ci sono. Bisogna però ricordare che ogni processo ha il suo spazio di indirizzamento privato, mentre il codice del kernel risiede in uno spazio di indirizzamento che è comune a tutti i processi. Quando le chiamate di sistema eseguono per conto di uno specifico processo, eseguono nello spazio del kernel ma devono (e possono) accedere allo spazio utente. Storicamente lo spazio utente veniva indirizzato in assembler usando il registro fs; le versioni attuali di linux nascondono le istruzioni specifiche all'interno di funzioni chiamate get_user_byte() per leggere un byte dallo spazio utente, put_user_byte() per scriverne uno, eccetera. Queste funzioni si chiamavano precedentemente con nomi come get_fs_byte, mentre adesso solo memcpy_tofs() e memcpy_fromfs() revelano la vecchia implementazione, anche su piattaforme diverse da x86. Chi volesse esplorare tutte queste funzioni può trovarne le definizioni in . The buf and count arguments tell us where to put the bytes we read (or where to find the bytes we shall write) and how many bytes there are. But you must remember that every process has its own private address space. When in kernel code, you have an address space which is common to all the processes. When system calls execute on behalf of a specific process, they run in kernel address space, but are still able to access the user space. Historically, this was done through assembler using the fs register; current linux kernels hide the specific code within functions called get_user_byte() for reading a byte from user address space, put_user_byte() for writing one, and so on. They were formerly known as get_fs_byte and similar, and only memcpy_tofs() and memcpy_fromfs() reveal these old days even on a DEC Alpha. If you want to explore them all, you'll find the definition in .
Immaginiamo ora un hardware ideale che non aspetta altro che di ricevere i nostri dati, legge e scrive velocemente, e viene visto attraverso una semplice porta dati da 8 bit all'indirizzo base della nostra interfaccia. Nonostante questo non sia realistico, il programmatore impaziente può provare il codice seguente: Let us imagine ideal hardware that is always hungry to receive our data, reads and writes quickly, and is accessed through a simple 8-bit data-port at the base address of our interface. Though it is unrealistic, the impatient of you might try the following code:
	Static int skel_read (struct inode *inode,
	                      struct file *file,
			      char *buf, int count) { 
		int n = count;
		char *port = PORT0 ((struct Skel_Hw*)(file->private_data));

		while (n--) { 
			Wait till device is ready
			aspetta che la periferica sia pronta
			put_user_byte (inb_p (port), buf);
			buf++; 
		} 
		return count; 
	}
Occorre spendere alcune parole sulla funzione inb_p(): questa è la funzione di lettura dei dati dall'hardware. Si può decidere di usare il suo equivalente veloce, inb(), che omette una minima attesa che può essere necessaria quando si pilota dell'hardware lento, ma noi preferiamo l'approccio più cauto. Notice the inb_p() function call. This is the actual I/O read from the hardware. You might decide to use its fast equivalent, inb(), which omits a minimal delay some slow hardware might need, but I prefer the safe way.
La funzione equivalente skel_write() è quasi uguale, tranne che bisogna rimpiazzare la linea contenente put_user_byte() con la seguente: The equivalent skel_write() function is nearly the same. Just replace the put_user_byte() line by the following:
	outb_p (get_user_byte (buf), port);
C'è da dire, però che questa implementazione ha un sacco di svantaggi. Cosa succede se il kernel si ferma in una di queste funzioni aspettando per un dispositivo che non diventerà mai pronto a ricevere/restuire dati? However, these lines have a lot of disadvantages. While using them, what if Linux sticks in them while waiting for a device that never becomes ready?
Torniamo per un attimo ai discorsi teorici: cosa ci aspettiamo dal nostro driver? Certamente dovrebbe dedicare il tempo di attesa ad altri processi che usino il nostro costoso processore, e dovrebbe avere un buffer di ingresso e uno di uscita in modo da gestire i dati che arrivano mentre i processi non sono in skel_read() o skel_write(). Dovrebbe avere un time-out per riconoscere gli errori, e dovrebbe supportare operazioni bloccanti e non bloccanti. Let us return back to theory for a short time. What do we expect from our driver? It should dedicate the time in the waiting loop to other processes making use of our expensive CPU, and it should have an input and output buffer for bytes arriving while we are not in skel_read() and corresponding skel_write() calls. It should have a time-out in case of errors, and it should support blocking and non-blocking modes.
Modo bloccante e non bloccante Blocking and Non-Blocking Mode
Immaginiamo un processo che voglia leggere 256 bytes. Sfortunatamente, in nostro buffer di ingresso è vuoto quando skel_read() viene chiamata. Cosa dovremmo fare allora: ritornare dicendo che non ci sono ancora dati, o aspettare finchè non arriva almeno qualche byte? Imagine a process which would like to read 256 bytes. Unfortunately, our input buffer is empty when skel_read() is called. So what shall we do: return and say that there is no data yet, or wait till at least some bytes have arrived?
La risposta è entrambe le cose. Il modo blocking significa che l'utente vuole attendere finchè non ci sono dati da leggere. Il modo non-blocking significa che il driver deve ritornare il più presto possibile -- deve solo leggere tutti i bytes che sono disponibili. Regole simili si applicano alla scrittura: una scrittura bloccante significa "non ritornare finchè non puoi accettare alcuni dati", mentre una non bloccante significa "ritorna anche se non puoi accettare niente". The answer is both. Blocking mode means the user wants us to wait till some bytes are read. Non-blocking mode means to return as soon as possible---just read all the bytes that are available. Similar rules apply to writing: Blocking mode means ``Don't return till you can accept some data,'' while non-blocking mode means: ``Return even if nothing is accepted.''
Le chiamate read() e write() di solito ritornano il numero di bytes trasferiti. Se, però, l'operazione è `non-blocking' e non possono essere trasferiti dati, bisogna ritornare -EAGAIN che significa ``Play it again, Sam''. Una volta si ritornava -EWOULDBLOCK, che in Linux è la stessa cosa che -EAGAIN. The read() and write()calls usually return the number of data bytes successfully read or written. If, however, the device is non-blocking and no bytes can be transferred, -EAGAIN should be returned (meaning: ``Play it again, Sam''). Some old code returns -EWOULDBLOCK, which is the same as -EAGAIN under Linux.
Forse adesso state sorridendo felici come quando io ho sentito per la prima volta di questi due modi. Se questi concetti sono nuovi per voi, potrete trovare utili i seguenti suggerimenti. Ogni dispositivo viene aperto per default in modo `blocking', ma l'utente può scegliere di usare il modo `non-blocking' settando O_NONBLOCK nella chiamata open(). Si può anche cambiare il comportamento di un file più avanti usando la funzione fcntl(). La chiamata fcntl() è abbastanza semplice, e la pagina del manuale fornisce informazioni sufficienti per un programmatore. Maybe now you are smiling as happily as I did when I first heard about these two modes. If these concepts are new for you, you might find the following hints helpful. Every device is opened by default in blocking mode, but you may choose non-blocking mode by setting the O_NONBLOCK flag in the open() call. You can even change the behaviour of your files later on with the fcntl() call. The fcntl() call is an easy one, and the man page will be sufficient for any programmer.
La bella addormentata Sleeping Beauty
C'era una volta una bella pricipessa che era stata posta in un lungo e profondo sonno da una strega. Il mondo quasi si era dimenticato di lei e del suo castello circondato ormai dalle rose; finchè un giorno un bel principe venne e la baciò, svegliandola dal suo sonno... e successero tutte le altre belle cose di cui parlano le fiabe. Once upon a time, a beautiful princess was sent by a witch into a long, deep sleep, lasting for a hundred years. The world nearly forgot her and her castle, twined about by roses, until one day, a handsome prince came and kissed her, waking her up...and all the other nice things happened that fairy tales tell about.
Il nostro driver mentre attende i dati deve fare esattamente quello che faceva la bella addormentata: dormire, lasciando girare il mondo per conto suo. Linux offre un meccanismo per questo, chiamato interruptible_sleep_on(). Ogni processo che chiama questa funzione cade addormentato e lascia il suo tempo al resto del mondo. Il processo rimane all'interno di questa funzione finchè un altro processo chiama wake_up_interruptible(). Questo `principe' è di solito un gestore di interrupt che ha ricevuto o spedito dati; oppure Linux stesso, se viene raggiunta una condizione di timeout. Our driver should do exactly what she did while it is waiting for data: sleep, leaving the world spinning around. Linux provides a mechanism for that, called interruptible_sleep_on(). Every process reaching this call will fall asleep and contribute its time slices to the rest of the world. It will stay in this function till another process calls wake_up_interruptible(), and this prince is usually an interrupt handler which has successfully received or sent data, or Linux itself, if a time-out condition has occured.
Installazione di un gestore di interrupt Installing an interrupt handler
Il precedente numero di questa rubrica aveva mostrato un gestore di interruzione minimale, chiamato skel_trial_fn(), ma il suo funzionamento non era stato spiegato. Presentiamo qui un gestore di interrupt completo, che gestirà sia l'input che l'output al dispositivo hardware. Quando il driver attende in modo `blocking' che il dispositivo diventi pronto, si addormenta chiamando interruptible_sleep_on(). Una interruzione valida termina il sonno, facendo ripartire skel_write(). The previous issue of this column showed a minimal interrupt handler, which was called skel_trial_fn(), but its workings were not explained. Here, a ``complete'' interrupt handler is introduced, which will handle both input and output to the actual hardware device. Figure 1 explains a simple version of its concept: When the driver is waiting for the device to get ready (blocking), it goes to sleep by calling interruptible_sleep_on(). A valid interrupt ends this sleep, restarting skel_write().
Non vi e` doppio ciclo che occorre quando si lavora con in buffer interno di output. La ragione è che se possiamo scrivere solo dall'interno di skel_write() non c'è bisogno di un buffer di output. Ma il nostro driver deve anche essere in grado di ricevere dati anche quando non si trova in skel_read(), a dovrebbe spedire i dati di output in background anche quando non si trova in skel_write(). Perciò, cambieremo skel_write() in modo da sostituire la scrittura diretta all'hardware con il salvataggio in un buffer di output, e lascieremo al gestore di interruzione il compito di fare il trasferimento reale verso l'hardware. L'interrupt e skel_write() saranno poi collegati dal `meccanismo della bella addormentata' e dal buffer di output. Figure 1 does not include the double-nested loop structure we need when working with an internal output buffer. The reason is that if we can only perform writing within the skel_write() function there is no need for an internal output buffer. But our driver should catch data even while not in skel_read() and should write out our data in the background even when not in skel_write(). Therefore, we will change the hardware writing in skel_write() to write to an output buffer and let the interrupt handler perform the real writing to the hardware. The interrupt and skel_write() will now be linked by the ``Sleeping Beauty'' mechanism and the output buffer.
Il gestore di interruzione viene installato e disiinstallato durante le chiamate di apertura e chiusura del dispositivo, come suggerito nell'articolo precedente. Il compito viene gestito dalle seguenti funzioni del kernel: The interrupt handler is installed and uninstalled during the open() and close() calls to the device, as suggested in the previous issue. This task is handled by the following kernel calls:
	#include <linux/sched.h>
	int request_irq(unsigned int irq,
	                void (*handler)(int, struct pt_regs *),
	                unsigned long flags,
	                const char *device);
	void free_irq(unsigned int irq);
handler è la funzione che vogliamo installare. Il ruolo dell'argomento flags è di selezionare le caratterische del gestore di interruzione, la più importante è se il gestore di interruzione è veloce (SA_INTERRUPT è settato in flags) oppure lento (SA_INTERRUPT non è settato). Un gestore veloce viene fatto girare con tutte le interruzioni disabilitate, mentre uno lento viene eseguito con tutte le altre interruzione (tranne la sua stessa) abilitate. handler is the actual interrupt handler we wish to install. The role of the flags argument is to set a few features of the handler, the most important being its behaving as a fast (SA_INTERRUPT is set in flags) or as a slow ( SA_INTERRUPT is not set) handler. A fast handler is run with all interrupts disabled, while a slow one is executed with all interrupts except itself enabled.
Infine, l'argomento device viene usato per identificare il gestore quando si legge /proc/interrupts. Finally, the device argument is used to identify our handler when looking at /proc/interrupts.
NdT:
Con la versione 1.3.70 del kernel viene aggiunto un argomento a
request_interrupt() , a free_interrupt() e al gestore di interruzione vero e proprio. Purtroppo non ho tempo di specificare le differenze prima che questa versione del Pluto Journal venga pubblicata
-- mea culpa.
Il gestore di interruzione installato tramite request_irq() riceve come argomenti solo il numero di interruzione e il contenuto, spesso inutile, dei registri del processore. The handler function installed by request_irq() is only passed the interrupt number and the (often useless) contents of the processor registers.
Il nostro gestore di interruzione dovrà quindi come prima cosa determinare a quale scheda l'interruzione si riferisce. Se non possiamo trovare di quale scheda si tratta, chiameremo questa situazione interruzione spuria e la ignoreremo. Nella maggior parte dei casi le interruzioni sono usate per dire che il dispositivo è pronto o per accettare una lettura o una scrittura, quindi dovremo trovare tramite dei test hardware cosa il dispositivo si aspetta che noi facciamo. Therefore, we'll first determine which board the calling interrupt belongs to. If we can't find any boards, a situation called a spurious interrupt has occured, and we should ignore it. Mostly interrupts are used to tell the device is ready either for reading or writing, so we have to find out by some hardware tests what the device wants us to do.
Naturalmente, dovremo lasciare il nostro gestore di interruzione il più velocemente possibile; stranamente, però, printk() (e quindi PDEBUG()), sono permesse anche all'interno di un gestore di interruzione veloce. Questa è una caratteristica molto comoda della implementazine di Linux. Guardando in kernel/printk.c si troverà che l'implementazione è basata, ancora una volta, su code di attesa, in quanto la effettiva consegna dei messaggi ai files di log viene gestita da un processo esterno (solitamente klogd). Of course, we should leave our interrupt handler quickly. Strangely enough, printk() (and thus the PDEBUG line) is allowed even within fast interrupt handlers. This is a very useful feature of the linux implementation. If you look at kernel/printk.c you'll find out that the implementation is based on wait queues, as the actual delivery of messages to log files is handled by an external process (usually klogd).
Linux può gestire un timeout durante interruptible_sleep_on(). Per esempio, se si stanno mandando dei dati ad un dispositivo che dovrebbe rispondere entro un tempo limitato, la gestione di un timeout al fine di segnalare un errore di I/O (ritornando -EIO) al processo che sta usando il dispositivo potrebbe essere una buona scelta. As shown in figure 2, Linux can care for a timeout when in interruptible_sleep_on(). For example, if you have are using a device you send an answer to, and it is expected to reply within a limited time, causing a time-out to signal an I/O error (-EIO) in the return value to the user process might be a good choice.
Certamente il processo utente potrebbe gestire la cosa da solo, usando il mecchanismo di alarm(), ma risulta decisamente più comodo gestire la cosa nel driver stesso. Il tempo limite (il timeout) viene specificato da SKEL_TIMEOUT, che viene contato in `jiffies'. Un `jiffy' è il tempo del battito del cuore di un sistema Linux, e la variabile jiffies viene incrementata ad ogni battito. La frequenza dell'interruzione del clock (il battito in questione) è HZ, definito in (incluso da <linux/sched.h>). Tale frequenza varia tra le differenti architetture supportate e vale, per esempio, 100 Hz su x86 e 1kHz su Alpha). Per definire un timeour prima di interruptible_sleep_on() bastano le seguenti linee: Certainly the user process could care for this, too, using the alarm mechanism. But it is definitly easier to handle this in the driver itself. The timeout criteria is specified by SKEL_TIMEOUT, which is counted in jiffies. Jiffies are the steady heartbeat of a Linux system, a steady timer being incremented every few milliseconds. The frequency is defined by HZ in (included by <linux/sched.h>) and varies on the different architectures (100 Hz Intel, 1 kHz Alpha). You just have to set
	#define SKEL_TIMEOUT (timeout_seconds * HZ)
	/* ... */
	current->timeout = jiffies + SKEL_TIMEOUT
se interruptible_sleep_on dovesse protrarsi oltre il timeout, current->timeout verrebbe azzerato prima che la funzione ritorni, un controllo di current->timeout è quindi sufficiente per riconoscere la condizione di errore. and if interruptible_sleep_on timed out, current->timeout will be cleared after return.
Bisogna fare attenzione al fatto che le interruzioni possono venire generate anche durante l'esecuzione di skel_read() e skel_write(). Le variabili che possono essere cambiate all'interno di un gestore di interruzione devono perciò essere dichiarate volatile, e devono essere protette contro le corse critiche (race conditions). La classica sequenza di codice per proteggere le regioni critiche è la seguente: Be aware that interrupts might happen right within skel_read() and skel_write(). Variables that might be changed within the interrupt should be declared as volatile. They also need to be protected to avoid race conditions. The classic code sequence to protect a critical region is the following:
		unsigned long flags;
		save_flags (flags);
		cli ();
		critical region
		regione_critica
		restore_flags (flags);
Ed ecco, infine, il codice vero e proprio per la lettura e la scrittura. Finally, the code itself:
#define SKEL_IBUFSIZ 512
#define SKEL_OBUFSIZ 512
#define SKEL_TIMEOUT (5*HZ)	/* for 5 seconds timeout */

/* This should be inserted in the Skel_Hw structure */
typedef struct Skel_Hw {
	volatile int ibuf_wpos;	/* write position in input-buffer */
	int ibuf_rpos;		/* read position in input-buffer */
	char *ibuf;		/* the input-buffer itself */
	int obuf_wpos;		/* write position in output-buffer */
	volatile int buf_rpos;	/* read position in output-buffer */
	char *obuf;
	struct wait_queue *skel_wait_iq;
	struct wait_queue *skel_wait_oq;
	...
}

#define SKEL_IBUF_EMPTY(b)	((b)->ibuf_rpos==(b)->ibuf_wpos)
#define SKEL_OBUF_EMPTY(b)	((b)->obuf_rpos==(b)->obuf_wpos)
#define SKEL_IBUF_FULL(b)	(((b)->ibuf_wpos+1)%SKEL_IBUFSIZ==(b)->ibuf_rpos)
#define SKEL_OBUF_FULL(b)	(((b)->obuf_wpos+1)%SKEL_OBUFSIZ==(b)->obuf_rpos)

Static int skel_open (struct inode *inode, struct file *filp) {
	/* .... */
	/* First we allocate the buffers */
	board->ibuf = (char*) kmalloc (SKEL_IBUFSIZ, GFP_KERNEL);
	if (board->ibuf == NULL)
		return -ENOMEM;
	board->obuf = (char*) kmalloc (SKEL_OBUFSIZ, GFP_KERNEL);
	if (board->obuf == NULL) {
		kfree_s (board->ibuf, SKEL_IBUFSIZ);
		return -ENOMEM;
	}
	/* Now we clear them */
	ibuf_wpos = ibuf_rpos = 0;
	obuf_wpos = obuf_rpos = 0;
	board->irq = board->hwirq;
	if ((err=request_irq(board->irq, skel_interrupt, SA_INTERRUPT, "skel")))
		return err;
}


Static void skel_interrupt(int irq, struct pt_regs *unused) {
	int i;
	Skel_Hw *board;

	for (i=0, board=skel_hw; iirq==irq)	break;	/* spurious */
	if (i==skel_boards)	return;
	if (board_is_ready_for_input())
		skel_hw_write (board);
	if (board_is_ready_for_output())
		skel_hw_read (board);
}


Static inline void skel_hw_write (Skel_Hw *board){
	int rpos;
	char c;

	while (! SKEL_OBUF_EMPTY (board) && board_ready_for_writing()) {
		c = board->obuf [board->obuf_rpos++];
		write_byte_c_to_device();
		board->obuf_rpos %= SKEL_OBUF_SIZ;
	}
	/* Sleeping Beauty */
	wake_up_interruptible (board->skel_wait_oq);
}


Static inline void skel_hw_read (Skel_Hw *board) {
	char c;

	/* If space left in the input buffer & device ready: */
	while (! SKEL_IBUF_FULL (board) && board_ready_for_reading()) {
		read_byte_c_from_device();
		board->ibuf [board->ibuf_wpos++] = c;
		board->ibuf_wpos %= SKEL_IBUFSIZ;
	}
	wake_up_interruptible (board->skel_wait_iq);
}


Static int skel_write (struct inode *inode,
		       struct file *file,
		       char *buf, int count) {
	int n;
	int written=0;
	Skel_Hw board = (Skel_Hw*) (file->private_data);

	for (;;) {
		while (writtenobuf [board->obuf_wpos] = get_user_byte (buf);
			buf++; board->obuf_wpos++;
			written++;
			board->obuf_wpos %= SKEL_OBUFSIZ;
		}
		if (written)	return written;
		if (file->f_flags & O_NONBLOCK)
			return -EAGAIN;
		current->timeout = jiffies + SKEL_TIMEOUT;
		interruptible_sleep_on ( &(board->skel_wait_oq));
		/* Why did we return? */
		if (current->signal & ~current->blocked)
			/* If the signal is not not being blocked */
			return -ERESTARTSYS;
		if (!current->timeout)
			/* no write till timout: i/o-error */
			return -EIO;
	}
}


Static int skel_read (struct inode *inode,
		      struct file *file,
		      char *buf, int count) {
	Skel_Hw board = (Skel_Hw*) (file->private_data);
	int bytes_read = 0;

	if (!count)	return 0;

	if (SKEL_IBUF_EMPTY (board)) {
		if (file->f_flags & O_NONBLOCK)
			/* Non-blocking */
			return	-EAGAIN;

		current->time_out = jiffies+SKEL_TIMEOUT;

		for (;;) {
			skel_tell_hw_we_ask_for_data();
			interruptible_sleep_on ( &(board->skel_wait_iq));
			if (current->signal & ~current->blocked)
				return	-ERESTARTSYS;
			if (! SKEL_IBUF_EMPTY (board))
				break;
			if (!current->timeout)
				/* Got timeout: return -EIO */
				return	-EIO;
		}
	}
	/* if some bytes are here, return them */

	while (! SKEL_IBUF_EMPTY (board)) {
		put_user_byte (board->ibuf [board->ibuf_rpos], buf);
		buf++; board->ibuf_rpos++;
		bytes_read++;
		board->ibuf_rpos %= SKEL_IBUFSIZ;
		if (--count == 0)	break;
	}
	if (count) /* still looking for some bytes */
		skel_tell_hw_we_ask_for_data();
	return bytes_read;
}
Come usare select() Handling select()
L'ultima importante funzione di I/O che mostriamo è select(), una delle parti più interessanti di Unix, nella nostra opinione. The last important I/O function to be shown is select(), one of the most interesting parts of Unix, in my opinion.
La chiamata di sistema select() viene usara per attendere che qualche dispositivo diventi pronto, ed è una delle funzioni più difficili per il novello programmatore C. Mentre il suo uso dall'interno dell'applicazione non viene mostrato qui, la parte dell'implementazione specifica del device driver è mostrata qui sotto, e la sua caratteristica che colpise di più è la sua compattezza. The select() call is used to wait for some device to become ready, and is one of the most scary functions for the novice C programmer. While its use from within the application is not shown here, the driver-specific part of the system call is shown, and its most impressive feature is its compactness.
Ecco il codice completo: Here's the full code:
Static int skel_select(struct inode *inode,
                       struct file *filp,
                       int sel_type,
                       select_table *wait) {
	Skel_Clientdata *data=filp->private_data;
	Skel_Hw *board=data->board;

	if (sel_type==SEL_IN) {
		if (! SKEL_IBUF_EMPTY (board))
			return 1;	/* readable */
		skel_tell_hw_we_ask_for_data();
		select_wait(&(board->skel_wait_iq), wait);
		/* not readable */
		return 0;
	}
	if (sel_type==SEL_OUT) {
		if (! SKEL_OBUF_FULL (board))
			return 1;	/* writable */
		/* hw knows */
		select_wait (&(board->skel_wait_oq), wait);
		return 0;
	}
	return 0;	/* exception condition: cannot happen */
}        
Come si vede, il kernel si prende cura di tutti i problemi della gestione delle code di attesa, e occurre solo controllare che il dispositivo sia pronto. As you see, the kernel takes care of all the hassle of managing wait queues, and you have only to check for readiness.
Quando abbiamo scritto la prima implementazione di select() per il nostro driver, non avevamo ancora capito la implementazione delle pre id attesa, ma questo in effetti non serve ad un programmatore. Occorre solo sapere che il codice mostrato funziona. Le code di attesa sono problematiche, e di solito durante la scrittura di un driver non si ha il tempo di affrontare il problema nei dettagli. When we first wrote a select() call for a driver, we didn't understand the wait_queue implementation, and you don't need to either. You only have to know that the code works. wait_queues are challenging, and usually when you write a driver you have no time to accept the challenge.
In effetti, select() è compresa meglio nelle sue relazioni con read() e write(): se select() dice che il file è leggibile, allora la prossima read() non si bloccherà (indipendentemente da O_NONBLOCK). Questo significa che occorre dire all'hardware di ritornare qualche dato. L'interruzione raccoglierà i dati e sveglierà la coda di attesa. Se l'utente sta selezionando per la scrittura, la situazione è simile: il driver deve dire se la prossima write() si bloccherà o no. Se il buffer è pieno si bloccherà, ma non occorre riferire questo evento all'hardware in quanto write() doverbbe averlo già notificato (durante il riempimento del buffer). Se il buffer non è pieno, write() non si bloccherà, quindi select() ritornerà 1. Actually, select is better understood in its relationships with read and write: if select() says that the file is readable, the next read must not block (independently of O_NONBLOCK), and this means you have to tell the hardware to return data. The interrupt will collect data, and awake the queue. If the user is selecting for writing, the situation is similar: the driver must tell if write() will block or not. If the buffer is full it will block, but you don't need to tell the hardware about it, since write() has already told it (when it filled the buffer). If the buffer is not full, the write won't block, so you return 1.
Questo modo di pensare select() per la scrittura può sembrare strano, in quanto capita di aver bisogno di scritture sincrone, e un programmatore si aspetterebbe che un dispositivo sia scrivibile quando ha già accettato tutto l'output precedente. Sfortunatamente, questo modo di implementare le cose romperrebbe tutto il macchinario delle operazioni blocking/non-blocking, e quindi viene fornita al programmatore esigente una chiamata di sistema extra: il driver dovrebbe in questo caso offrire una operazione fsync() all'interno delle sue fops. L'applicazione invocherà allora fops->fsync attraverso la chiamata di sistema fsync, e se il driver non supporta tale funzione, -EINVAL verrà ritornato al programma. This way to think of selecting for write may appear strange, as there are times when you need synchronous write, and you may expect that a device is writable when it has already accepted pending input. Unfortunately, this way of doing things will break the blocking/nonblocking machinery, and thus an extra call is provided: if you need synchronous write, the driver must offer (within its fops) the fsync()call. The application invokes fops->fsync through the fsync() system call, and if the driver doesn't support it, -EINVAL is returned.
ioctl()
Passaggio di informazioni di controllo
ioctl()
Passing Control Information
Immaginiamo di voler cambiare la baud-rate della scheda serialie multiporta che abbiamo costruito. O che dobbiamo dire al frame grabber di cambiare la risoluzione. O quasiasi altra cosa.... Si potrebbero impacchettare queate istruzxioni all'interno di sequenze di escape, come, per esempio, si fa per posizionare il cursore sui terminali ansi, ma ill metodo comunemente usato è la funzione ioctl(). Imagine that you want to change the baud-rate of your self-built serial multiport card. Or tell your frame grabber to change the resolution. Or whatever else... You could wrap these instructions into some escape sequences, like, for example, the screen positioning in ansi emulation. But, the normal method for this is making an ioctl() call.
Le chiamate ioctl() sono definite in e hanno la forma ioctl() calls as defined in have the form
	ioctl (int file_handle, int command, ...)
dove i puntini indicano un argimento di tipo char * (in base alla pagina del manuale). Stranamente, il kernel riceve questi argomenti tipizzati diversamente in fs/ioctl.c, dove appare la forma: where ... is considered to be one argument of the type char * (according to the ioctl man page). Strange as things sometimes are, the kernel receives these arguments in fs/ioctl.c in the form:
	int sys_ioctl (unsigned int fd, unsigned int cmd,
	               unsigned long arg);
Per aumentare la confusione, <linux/ioctl.h> dà le regole dettagliate riguardo a come devono essere costruiti i comandi che appaiono come secondo parametro, ma nonostante ciò sono ancora pochi i driver che seguono queste buone idee -- principalemente perchè i vecchi driver devono mantenere i vecchi comandi, per non creare grosse incompatibilità con le applicazioni. To enlarge this confusion, <linux/ioctl.h> gives detailed rules how the commands in the second parameter should be built, but nobody in all the drivers is actually following these (good) ideas yet.
Invece di pulire i sorgenti del kernel cerchiamo di capire l'idea generale delle chiamate ioctl(); il modo più corretto per definire i comandi di ioctl è descritto bene in Documentation/ioctl-number.txt, ma noi ci limiteremo qui alla forma semplice, anche se è sconsigliata. Anyway, instead of cleaning up the Linux source tree, lets concentrate on the general idea of ioctl() calls. As the user, you pass the file handle and a command in the first two arguments and pass a pointer to a data structure the driver should read, write, or read and write as the third parameter.
Il programma passa al kernel come primi argomenti di una chiamata ioctl() il file descriptor e un numero di comando, un puntatore ad una struttura dati che deve essere letta, scritta, o letta-e-scritta, dal driver viene passato come terzo parametro (opzionale).
Alcuni comandi vengono interpretati dal kernel prima di passare il controllo al dirver, come FIONBIO, che cambia il valore del flag blocking/non-blocking per il file. Il resto dei comandi, la maggior parte, vengono passati alla nostra funzione ioctl() all'interno del driver. Il prototipo di tale funzione è il seguente: A few commands are interpreted by the kernel itself, for example, FIONBIO, which changes the blocking/non-blocking flag of the file. The rest is passed to our own, driver-specific ioctl() call, and will arrive here in the form:
	int skel_ioctl (struct inode *inode,
	                struct file *file,
	                unsigned int cmd,
	                unsigned long arg)
Prima di mostrare un breve esempio di implementazione per skel_ioctl(), diamo la regola per costruire i numeri dei comandi (ma ricordiamo che questo è il metodo sconsigliato: un programmatore attento dovrebbe seguire le regole in ioctl-number.txt). Before we show a small example of a skel_ioctl() implementation, the commands you define should obey the following rule:
  1. Si scelga un numero magico libero usando la lista in Documentation/ioctl-number.txt, questo numero cosituirà gli otto bit alti del numero a 16 bit che constituisce il comando.
  2. Si numerino sequenzialmente i comandi negli otto bit più bassi.
  1. Pick up a free MAGIC number from /usr/src/linux/MAGIC and make this number the upper eight bits of your 16-bit command word.
  2. Enumerate your commands in the lower eight bits.
Perchè questo? Immaginiamo che Pierino faccia partire il suo favorito programma di comunicazione per connettersi alla sua casella postale favorita. Pierino ha casualmente cambiato la linea seriale usata da minicom da /dev/ttyS0 a /dev/skel0 (è stata una mossa abbastanza stupida, in effetti). La prima operazione effettuata da minicom è inizializzare la `linea seriale', usando una ioctl() per passare il comando TCGETA. Sfortunatamente, il nostro driver che sta dietro /dev/skel0 usa lo stesso numero per controllare il voltaggio di un esperimento che sta girando da giorni nel laboratorio... Why this? Imagine ``Silly Billy'' starts his favorite terminal program minicom to connect to his favorite mailbox. ``Silly Billy'' accidentally changed the serial line minicom uses from /dev/ttyS0 to /dev/skel0 (he is quite silly). The next thing minicom does is initialise the ``serial line'' with an ioctl() using TCGETA as command. Unfortunately, your device driver being hidden behind /dev/skel0 uses that number to control the voltage for a long-term experiment in the lab...
Se gli otto bit alti dei comandi di ioctl() sono diversi da un driver all'altro, ogni ioctl() che si riferisca ad un device inappropriato fara ritornare il valore -EINVAL, proteggendoci da risultati inaspettati come quello descritto sopra. If the upper eight bits in the commands for ioctl() differ from driver to driver, every ioctl() to an inappropriate device will result in an -EINVAL return, protecting us from extremely unexpected results.
Per finire questa sezione implementeremo ora una funzione ioctl() per leggere o modificare il valore attuale del timeout del nostro driver. Se si vuole usare questa funzione occorre però introdurre una nuova variabile nel driver dopo la definizione di SKEL_TIMEOUT: Now, to finish this section, we will implement an ioctl() call reading or changing the timeout delay in our driver. If you want to use it, you have to introduce a new variable
	unsigned long skel_timeout = SKEL_TIMEOUT;
Ogni occorrenza di SKEL_TIMEOUT andrà poi sostiuita con skel_timeout. right after the definition of SKEL_TIMEOUT and replace every later occurence of SKEL_TIMEOUT with skel_timeout.
Abbiamo poi scelto il numero magico '4' (il codice ascii per il numero 4), e abbiamo definito due comandi: We choose the MAGIC '4' (the ASCII character 4) and define two commands:
	# define SKEL_GET_TIMEOUT 0x3401
	# define SKEL_SET_TIMEOUT 0x3402
Nel nostro programma, queste linee saranno usate per raddoppiare il valore corrente del timeout: In our user process, these lines will double the time-out value:
	/* ... */
	unsigned long timeout;

	if (ioctl (skel_hd, SKEL_GET_TIMEOUT, &timeout) < 0) {
		/* an error occured (Silly Billy?) */
		/* ... */
	}
	timeout *= 2;
	if (ioctl (skel_hd, SKEL_SET_TIMEOUT, &timeout) < 0) {
		/* another error */
		/* ... */
	}
Nel driver, invece, queste linee saranno quelle che compiono il lavoro: And in our driver, these lines will do the work:
	int skel_ioctl (struct inode *inode,
	                struct file *file,
	                unsigned int cmd,
			unsigned long arg) {
		switch (cmd) {
		case SKEL_GET_TIMEOUT:
			put_user_long(skel_timeout, (long*) arg);
			return 0;
		case SKEL_SET_TIMEOUT:
			skel_timeout = get_user_long((long*) arg);
			return 0;
		default:
			return	-EINVAL;	/* for Silly Billy */
		}
	}
Il prossimo numero Next Month
Nelle prossime sezioni investigheremo i segreti della gestione di meoria, e entreremo in contatto con il DMA e mmap(), fornendo al nostro driver il meglio della gestione di memoria di Linux. In the next sections, we will investigate the secrets of Linux memory management and get in touch with DMA and mmap(), providing our driver with the ultimate throughput of Linux memory management.

Georg e Alessandro sono linuxers di 27 anni con un gusto per il lato pratico dell'informatica e una tendenza a dilazionare il sonno.

Georg and Alessandro are both 27-year-old Linuxers with a taste for the practical side of Computer Science and a tendancy to avoid sleep.

PLUTO Journal nr.6 [mSQL e Web] [Indice] [Schede Cyclades] Articoli