Appunti sullo sviluppo di un driver per Linux per il controllo di una automobilina radiocomandata

di Marco Faella e Marco Trucillo

 

3.1    Write

Veniamo finalmente al cuore del driver: la funzione di scrittura. 
Essa consiste essenzialmente in un ciclo while che invia ogni carattere fornito dall'utente alla porta parallela appropriata, dopo aver filtrato i comandi non validi. In più, la funzione deve far si che il comando rimanga attivo per il numero di secondi specificato. Per far questo la funzione deve "dormire" e lo fa modificando direttamente il record di attivazione (process control block) del processo che ha invocato car_write, record puntato dalla variabile globale current. Poi invoca lo scheduler perché dia il controllo ad un altro processo finché il numero di secondi richiesto non è trascorso. 
Ultimo accorgimento, un ciclo while più interno che, ritentando più volte la scrittura di ogni carattere, cerca di aggirare eventuali difetti occasionali della porta. 
La funzione restituisce il numero di caratteri correttamente inviati alla porta. 

static int car_write(struct inode * inode, struct file * file, const char * buf, int count) 
{ 
 unsigned int minor = MINOR(inode->i_rdev); 
 int  retval,tries; 
 char c; 
 const char *temp; 

 temp = buf; 
 while (count > 0) { 
    c = get_user(temp); 
    if ( ( CAR_DATA(c) & ( AVANTI | DIETRO ) ) || ( CAR_DATA(c) & ( DESTRA | SINISTRA ) ) ) return temp-buf;  /* comando contraddittorio */ 
    tries=0; 
    do { 
       tries++; 
       retval = car_char_polled(CAR_DATA(c), minor); 
    } while ( (!retval) && (tries <= MAX_TRIES)); 
    if (!retval) return temp-buf; 
    count--; 
    temp++; 
    /* interviene sul process control block */ 
    current->state = TASK_INTERRUPTIBLE; 
    current->timeout = jiffies + CAR_TIME(c)*100; 
    schedule(); 
 } 
 return temp-buf; 
} 
 

 

3.2    Inizializzazione

L'inizializzazione del driver è compito della procedura  car_init, che per prima cosa invoca register_chrdev(), la funzione che, come abbiamo già visto, tutti i driver a caratteri devono chiamare per esportare le proprie funzionalità verso il sistema operativo e più precisamente presso il Virtual File System. 
Fatto questo, car_init si servirà di car_probe sia per verificare, con una chiamata a check_region(), la presenza della porta parallela necessaria sia per controllarne il corretto funzionamento. Quest'ultimo controllo viene fatto scrivendo un carattere sulla porta e rileggendolo dalla stessa. 
Se la porta supera i due test appena descritti, viene acquisita dal driver con la chiamata a request_region()
La funzione printk(), più volte usata in questa porzione del driver, è la versione kernel della printf ed ha, tranne poche eccezioni, il suo stesso formato. A differenza di quest'ultima, la printk scrive generalmente su file di sistema invece che sullo schermo, tranne che durante la fase di boot del sistema. 

static struct file_operations car_fops = { 
 NULL,       /* car_lseek   */ 
 NULL,       /* car_read    */ 
 car_write, 
 NULL,       /* car_readdir */ 
 NULL,       /* car_select  */ 
 car_ioctl, 
 NULL,       /* car_mmap    */ 
 car_open, 
 car_release 
}; 
 

static int car_probe(int offset) 
{ 
 int base,tries=0; 
 unsigned int testvalue; 

 base = CAR_B(offset); 
 if (base == 0) return -1;         

 if (check_region(base, ((base == 0x3bc)? 3 : 8))  < 0) 
     return -1; 
 /* scrivo sulla porta e controllo se ho scritto bene */ 
 do { 
    outb_p(0x055, base); /* valore di prova 101010 */ 
    testvalue = inb_p(base); 
    tries++; 
 } while( (tries < MAX_TRIES) && (testvalue !=0x55)); 

 if (testvalue == 0x055) { /* inizializzazione riuscita */ 
    CAR_F(offset) |= CAR_EXIST; 
    car_reset(offset); 
    printk(KERN_INFO "car %d all'indirizzo 0x%04x, polling ", offset, base); 
    request_region(base, ((base == 0x3bc)? 3 : 8), "car"); 
    return 1; 
 } else return 0; 
} 
 

int car_init(void) 
{ 
 int offset = 0; 
 int count = 0; 

 if (register_chrdev(CAR_MAJOR,"car",&car_fops)) { 
    printk("car: non posso prendere il # major %d\n", CAR_MAJOR); 
    return -EIO; 
 } 

 for (offset = 0; offset < CAR_NO; offset++) count+= car_probe(offset); 
  
 if (count == 0) 
    printk("car: Driver configurato ma nessuna intefaccia trovata.\n"); 

 return 0; 
} 

 
 

3.3    Conclusioni

 Finisce qui l'esposizione del nostro lavoro. Anche se abbiamo descritto soltanto uno scheletro di driver, ci auguriamo possa comunque risultare utile a quanti, per studio o lavoro, debbano inoltrarsi nella programmazione di sistema in ambiente Linux. 
 
 

3.4     Bibliografia e webliografia

Bibliografia

  • Barkakati, Naba 

  • "Segreti di Linux", Apogeo  
  • Alessandro Rubini, Andy Oram 

  • "Linux Device Drivers", O'Reilly & Associates 
  • AA. VV. 

  • "Linux Kernel Internals", Addison Wesley 

Webliografia