Qual è il modo corretto / inteso per gestire gli interrupt e utilizzare l'istruzione WFI risc-v cpu?

Aug 19 2020

Sono molto nuovo nella programmazione bare metal e non ho mai avuto a che fare con interruzioni prima, ma ho imparato su una scheda di sviluppo alimentata da SOC RISC-V FE310-G002.

Ho letto delle istruzioni RISC-V WFI (Wait for interrupt) e dai manuali, non sembra che tu possa fare affidamento su di esso per dormire effettivamente il core. Invece, suggerisce solo che l'esecuzione può essere interrotta al sistema e che l'istruzione dovrebbe essere trattata più come un NOP. Tuttavia, questo mi sembra piuttosto inutile. Considera il seguente frammento di programma ASM:

wfi_loop:
WFI
J wfi_loop

Questo dovrebbe essere fatto poiché non si può fare affidamento su WFI. Tuttavia, su MRET dal gestore di interrupt, verrai comunque coinvolto nel ciclo. Quindi dovresti renderlo condizionale rispetto a una variabile globale il cui valore viene aggiornato nel gestore degli interrupt. Sembra molto complicato.

Inoltre, se l'implementazione rispetta effettivamente l'istruzione WFI e l'interrupt viene attivato appena prima dell'esecuzione dell'istruzione WFI, l'intero core si bloccherà fino a quando non viene attivato un altro interrupt poiché tornerà prima dell'istruzione WFI.

Sembra che l'unico utilizzo corretto dell'istruzione sarebbe all'interno di uno scheduler del kernel quando non c'è lavoro da fare. Ma anche allora, non penso che vorresti mai tornare dal gestore degli interrupt a tale codice, ma piuttosto riavviare l'algoritmo dello scheduler dall'inizio. Ma anche questo sarebbe un problema poiché in qualche modo dovresti ripristinare la pila, ecc ...

Continuo a girare con questo nella mia testa e non riesco a capire un uso sicuro. Forse, se atomicamente, abiliti gli interrupt con CSRRS e quindi chiami immediatamente WFI in questo modo:

CSRRSI zero, mie, 0x80
wfi_loop:
WFI
J wfi_loop
NOP
NOP

Quindi assicurati di incrementare il registro mepc di 8 byte prima di chiamare MRET dal gestore di interrupt. L'interrupt dovrebbe anche essere nuovamente disabilitato nel registro mie all'interno del gestore di interrupt prima di tornare. Questa soluzione sarebbe sicura solo se WFI, J e NOP fossero tutti codificati come istruzioni a 4 byte, indipendentemente dal fatto che vengano utilizzate istruzioni compresse. Dipende anche dal modo in cui il contatore del programma raggiunge l'istruzione WFI prima che sia possibile l'attivazione dell'interrupt, dopo essere stato abilitato dall'istruzione CSRRSI. Ciò consentirebbe quindi di attivare l'interruzione in un posto sicuro nel codice e di tornare in modo tale da uscire dal ciclo che lo stava aspettando.

Immagino che sto solo cercando di capire quale comportamento posso aspettarmi dall'hardware e, quindi, come chiamare e tornare correttamente dagli interrupt e utilizzare l'istruzione WFI?

Risposte

3 ErikEidt Aug 19 2020 at 18:22

Dovrebbe esserci un'attività / thread / processo che è inattivo e dovrebbe assomigliare al tuo primo bit di codice.

Poiché il thread inattivo è impostato per avere la priorità più bassa, se il thread inattivo è in esecuzione, significa che non ci sono altri thread da eseguire o che tutti gli altri thread sono bloccati.

Quando si verifica un interrupt che sblocca un altro thread, la routine del servizio di interrupt dovrebbe riprendere quel thread bloccato invece del thread inattivo interrotto.

Si noti che anche un thread che si blocca su IO viene interrotto, viene interrotto tramite il proprio utilizzo di ecall. Quell'eccezione è una richiesta di I / O e causa il blocco di questo thread: non può essere ripresa finché la richiesta di I / O non è soddisfatta.

Pertanto, un thread bloccato su IO viene sospeso esattamente come se fosse stato interrotto - e un interrupt di clock o un interrupt IO è in grado di riprendere un processo diverso da quello immediatamente interrotto, cosa che accadrà nel caso in cui il processo inattivo era in esecuzione e si verifica un evento che un processo era in attesa.


Quello che faccio è usare il scratchcsr per puntare al blocco di contesto per il processo / thread attualmente in esecuzione. All'interruzione, salvo il minor numero di registri necessari per (iniziare a) servire l'interruzione. Se l'interrupt fa sì che un altro processo / thread diventi eseguibile, quando riprendo dall'interrupt, controllo le priorità del processo e posso scegliere un cambio di contesto invece di riprendere ciò che è stato interrotto. Se riprendo ciò che è stato interrotto, è un rapido ripristino. E per cambiare contesto, finisco di salvare il contesto della CPU del thread interrotto, quindi riprendo un altro processo / thread, cambiando il scratchregistro.

(Per gli interrupt annidati, non consento gli interrupt di contesto al ripristino, ma sugli interrupt dopo aver salvato il contesto corrente, imposto il scratchcsr su uno stack di interrupt di blocchi di contesto prima di riattivare gli interrupt a priorità più alta. ottimizzazione possiamo presumere che un thread inattivo scritto su misura non abbia bisogno di nient'altro che il suo PC salvato / ripristinato.)

4 MargaretBloom Aug 19 2020 at 18:22

Quindi dovresti renderlo condizionale rispetto a una variabile globale il cui valore viene aggiornato nel gestore degli interrupt.

Devi farlo indipendentemente dall'implementazione di wficome non sai quale evento ha causato il risveglio del cervo.
Potresti avere n interrupt abilitati durante l'esecuzione wfie qualcuno di essi potrebbe essere stato sollevato.

wfiè un'ottimizzazione , consente di risparmiare energia finché non accade qualcosa. Come hai notato, lo scheduler del sistema operativo potrebbe trovarsi nella condizione che nessun thread sia programmabile (ad esempio, tutti aspettano IO o semplicemente non ce n'è) in quel caso deve fare qualcosa del tipo (con tutta la necessaria visibilità e semantica di atomicità):

while ( ! is_there_a_schedulable_thread());

Sta solo aspettando .
Ma invece di creare un circuito stretto (che potrebbe danneggiare le prestazioni e la potenza) lo scheduler può usare:

while ( ! is_there_a_schedulable_thread())
{
  __wfi();
}

Nel peggiore dei casi è proprio come il ciclo stretto, nella migliore delle ipotesi metterà in pausa il cuore fino a quando non si verifica un interrupt esterno (il che significa che potenzialmente un IO è stato completato e quindi un thread potrebbe essere libero di eseguire).

Anche in caso di assenza di thread, svegliarsi ogni x microsecondi (a causa di un'interruzione del timer) è meglio che sprecare energia in loop.

wfipuò anche essere utile per incorporare la programmazione se ti capita di avere tutto il lavoro sui gestori di interrupt (ad esempio, quando viene premuto un pulsante o simili).
Nel caso, la mainfunzione sarebbe semplicemente in loop per sempre, proprio come lo scheduler ma senza una condizione di uscita. Un'istruzione migliorerà notevolmente la durata della batteria.
wfi

Non puoi usarlo wfiovunque o potresti ritrovarti ad aspettare un'interruzione che non si verifica mai (in effetti, è un'istruzione privilegiata).

Consideralo come un'ottimizzazione per il coordinamento con l'hardware.

In particolare, non è stato progettato come un modo per essere sicuri che sia stata attivata un'interruzione:

void wait_for_int(int int_num)
{
   //Leave only interrupt int_num enabled
   enable_only_int(int_num);
   __wfi();
   restore_interrupts();
}

Potrebbe essere usato in questo modo data un'implementazione specifica di RISC-V ma, come si vede dallo pseudo-codice, non è poi così conveniente.
Disabilitare tutti gli interrupt tranne uno è generalmente qualcosa che un sistema operativo non può permettersi.
Un'applicazione incorporata potrebbe, però.