L'interrupt controller su AVR32 è composto principalmente da un blocco funzionale chiamato masking che processa la ragione e se ritiene valida la richiesta, insieme al livello d’interrupt associato, chiede alla cpu la sua relativa gestione. Tutto questo viene svolto attraverso un meccanismo combinato che vede coinvolti moduli software (attraverso la programmazione di alcuni registri) e logica hardware. Affrontare il tema degli interrupt su AVR32 vuol dire mettere a fuoco diverse tematiche: occorre chiarire i concetti di gruppo, di livello e di interrupt non mascherabile (NMI). Come vengono gestiti gli interrupts sull’AVR32? Quali sono i registri utilizzati per gestire correttamente la procedura d’interrupt? A queste domande cercherà di rispondere questo articolo.
ORGANIZZAZIONE DEGLI INTERRUPTS
Un interrupt appartiene ad un gruppo e ogni gruppo ha un suo livello di priorità, solo un genere di interrupt non appartiene ad un gruppo: si tratta dell’interrupt NMI. Ogni gruppo non può avere più di 32 membri, tipicamente ogni gruppo è formato da un insieme motto più ristretto. Sono disponibili fino a quattro livello di priorità, il livello 0 è meno prioritario rispetto al livello 1 (livello di priorità maggiore). In figura 1 e 2 possiamo vedere schematicamente questa caratteristica.
Nella figura 2 vediamo che il numero dei gruppi è identificato come n, mentre il numero dei membri per gruppo viene referenziato come m.
L’interrupt di tipo NMI (nonmaskable interrupt) è riservato per situazioni altamente critiche che possono capitare in un sistema. In AVR, poi, esiste il concetto di register shadow. Siccome esistono delle differenze tra i diversi modelli è consigliabile consultare la documentazione tecnica del proprio AVR per capirne il ruolo e i modi di funzionamento, tipicamente un registro marcato come shadow consente il ripristino del suo valore in modo più veloce. Se nel nostro sistema abbiamo una serie di interrupts consecutivi, allora la selezione dell’interrupt a più alta priorità viene fatta dalla logica interna harwdware. Esistono tre regole applicabili:
- Se esiste un solo interrupt in attesa allora non c’è nessun problema, questo verrà sicuramente gestito.
- Se esistono due o più interrupts in attesa, allora il gruppo a livello più alto verrà gestito.
- Se due o più gruppi di interrupts sono in attesa e hanno la stessa priorità, allora il gruppo con un numero di membri più basso verrà gestito.
INTERRUPT CONTROLLER
Questo blocco funzionale si occupa della gestione degli eventi asincroni del sistema (interrupts). La prima cosa da fare è di inizializzare l’interrupt controller. Esistono una serie di registri che si occupano della gestione degli interrupts, ogni gruppo utilizzano, poi, propri registri. I registri sono così schematizzati:
Interrupt Request Registers
Per ogni gruppo esiste un Interrupt Request Register (IRR). Il registro IRR è composto da 32 bits ogni bit identifica un possibile interrupt request (di conseguenza è possibile gestire non più di 32 interrupts per gruppo). Questo registro è identificato come INTC_IRRm, dove con m si identifica il gruppo. Siccome ci sono 64 IRRs, uno per gruppo, e ogni IRRs ha 32 bits, uno per ogni interrupt request, allora ci sono 2048 possibili linee d’ingresso. Questi registri sono letti dal software all’interno della gestione dell’interrupt per determinare quale evento risulta pendente.
Interrupt Priority Registers
Questo registro contiene la priorità degli interrupts e riflette il valore del gruppo. Il registro risulta identificato come IPR. Al reset il registro è inizializzato a zero. IPR contiene due campi: INTLEVEL e AUTOVECTOR. Con INTLEVEL si identifica il livello del gruppo, mentre con AUTOVECTOR il suo offset. L’offset, poi, è relativo all’Exception Vector Base Address (EVBA) e contiene il gestore dell’interrupt del gruppo. Esiste un IPR per ogni gruppo e vengono identificati come INTC_IPRm con m viene identificato il gruppo. Questo registro, infine, è di tipo read/write.
Interrupt Cause Registers
Esistono quattro registri di questo tipo. Questi registri vengono identificati come INTC_ICRn, con n si identifica l’interrupt level (da 0 a tre). I registri di questo tipo identificano il gruppo con la più alta priorità che ha pendente un interrupt. Se non ci sono interrupt pendenti di livello n, il livello di priorità è mascherato.
Interrupt masking
Premesso che l’interrupt di tipo NMI non è possibile disabilitarlo, esiste il registro identificato come Status Register che permette di disabilitare, in maniera globale, gli interrupts nel sistema o permette di disabilitare gli interruptus associati al livello. Questo registro è composto da due sezioni: una si occupa della gestione delle flags aritmetici/logici (lower halfword) e l’altra è predisposta per fornirci informazioni sullo stato e i modi operativi del processore (upper halfword). La parte, denominata upper halfword, può essere acceduta solo in modo privilegiata. Se vogliamo disabilitare in maniera globale gli interrupts allora dobbiamo intervenire sul bit GM, mentre se vogliamo, invece, disabilitare un singolo livello allora dobbiamo agire sul bit InM (interrupt Level Mask, dove n può essere da 0 a 3). Gli interrupts di tipo NMI sono, per loro natura, non mascherabili.
Quando viene rilevato un interrupt, la CPU, una volta riscontrato il livello e l’autovector, esegue il codice di gestione posto nella relativa locazione rispetto all’EVBA. I registri che la CPU mette poi a disposizione rappresentano un ottimo aiuto per cercare di ricavare la sorgente dell’interrupt. Gli interrupt vengono abilitati solo quando viene correttamente inizializzato l’interrupt controller: al reset gli interrupts sono comunque disabilitati. Quando un interrupt viene rilevato dal sistema deve essere chiamato il suo interrupt handler, questo gestore restituirà il controllo al flusso normale del software solo quando non ci sono più interrupt in attesa. Il ritorno al flusso normale di programma non viene fatta come quello che succede quando si ritorna da una funzione C: questo perché quando viene sollevato un evento asincrono il processore cambia il suo modo di funzionamento.
UN PICCOLO ESEMPIO
I listati 1 e 2 sono dei piccoli esempi per la gestione degli interrupts su questo componente.
#include <avr32/io.h> #include <sys/interrupts.h> #define SUCCESS 0; #define FAILURE -1; #define INPUT_MASK 0x000000ff #define OUTPUT_MASK 0x0000ff00 __int_handler *pioc_int_handler() { volatile struct avr32_pio_t *pioc = &AVR32_PIOC; int status; status = pioc->isr; status = pioc->pdsr; pioc->odsr =~(status&INPUT_MASK)<<8; /* set led */ return (void *) 0; } int main( void ) { volatile struct avr32_pio_t *pioc = &AVR32_PIOC; /* Setup the PIO controller. Set PIOC[8..15] as output, PIOC[0..7] as input. */ pioc->odr= INPUT_MASK; pioc->per = OUTPUT_MASK; pioc->oer = OUTPUT_MASK; pioc->codr = INPUT_MASK & OUTPUT_MASK; pioc->puer = INPUT_MASK & OUTPUT_MASK; pioc->idr &= ~INPUT_MASK; pioc->ier = INPUT_MASK; pioc->ower = OUTPUT_MASK; /* Setup the interrupt handler, handled by newlib */ set_interrupts_base( (void *) AVR32_INTC_ADDRESS ); /* register_intterupt( (__int_handler) {func}, {GROUP}, {LINE}, {LEVEL} ); */ register_interrupt( (__int_handler) (pioc_int_handler), AVR32_PIOC_IRQ/32, AVR32_PIOC_IRQ % 32, INT0); init_interrupts(); while(1); return 0; }
Listato 1 |
#include <avr32/ioap7000.h> #include <intrinsics.h> #define SUCCESS 0; #define ERROR -1; #define INPUT_MASK 0x000000ff #define OUTPUT_MASK 0x0000ff00 /*! \brief The IAR PIO interrupt handler * * \param ; * \return ; */ #pragma handler=AVR32_PIOC_IRQ_GROUP,2 __interrupt __root void pioc_interrupt_handler() { volatile struct avr32_pio_t *pioc = &AVR32_PIOC; int status; status = pioc->isr; status = pioc->pdsr; pioc->odsr =~(status&INPUT_MASK)<<8; /* set led */ } /*! \brief Sets up the pio and enables interrupts. Push switches to generate interrupts. * * \param ; * \return ; */ int main( void ) { volatile struct avr32_pio_t *pioc = &AVR32_PIOC; /* Setup the PIO controller. Set PIOC[8..15] as output, PIOC[0..7] as input. */ pioc->oer = OUTPUT_MASK; pioc->odr= INPUT_MASK; pioc->per = OUTPUT_MASK&INPUT_MASK; pioc->codr = INPUT_MASK & OUTPUT_MASK; pioc->puer = INPUT_MASK & OUTPUT_MASK; pioc->idr &= ~INPUT_MASK; pioc->ier = INPUT_MASK; pioc->ower = OUTPUT_MASK; __enable_interrupt(); while(1); }
Listato 2 |
Vengono mostrati due esempi. Uno per GCC e l’altro per IAR, questo perché la gestione di un interrupt può divergere a seconda dell’implementazione e, di conseguenza, del compilatore in uso. Un gestore della funzionalità d’interrupt è semplicemente una funzione senza argomenti con un modo particolare per restituire il controllo (una specie di return from interrupt), il corpo della funzione deve essere essenziale e badare al sodo. Uno dei compiti potrebbe essere quello di verificare alcuni registri interni, per esempio del modulo interrupt controller, per verificare un evento e svolgere una determinata azione. Si può anche inserire nella funzione una funzionalità di verifica, come l’accensione di un led per dimostrare la corretta gestione dell’evento. Ogni funzione d’interrupt una volta scritta deve essere inserita nel sistema. Chiaramente devono essere fissate , per esempio, il gruppo e la priorità. Esistono, però, dei piccoli accorgimenti, consigli ai quali è opportuno prestare attenzione. In particolare, la gestione di un interrupt deve essere il più breve possibile e particolare cura deve essere posta in merito alle priorità.
Gli interrupt hardware richiedono attenzione dal sistema operativo e riguardano periferiche esterne, sono asincroni e possono verificarsi durante le istruzioni. Gli interrupt software, invece, sono condizioni speciali nel set di istruzioni rivolte ad errori di varia natura o per richiedere servizi aggiuntivi.