1. Transition contrôlée du mode machine vers le mode utilisateur
Dans l’architecture RISC-V, la gestion des interrputions repose sur une coordination rigoureuse entre niveaux de privilège, état des registres et flux d’exécution. À l’allumage, le processeur démarre en mode machine (M-mode), le niveau de privilège le plus élevé. Pour exécuter du code applicatif en mode utilisateur (U-mode), il faut effectuer une descente de privilège contrôlée, tout en préparant le système à traiter les interruptions futures.
1.1 Désactiver les interruptions globales via mstatus.MIE
Avant toute initialisation, il est impératif de désactiver les interruptions en écrivant 0 dans le bit MIE du registre mstatus. Cela empêche toute interruption asynchrone (timer, E/S, etc.) de perturber la phase critique d’initialisation matérielle. Ce verrouillage temporaire garantit que le noyau configure les structures essentielles sans interférence externe.
Le bit
mstatus.MIEagit comme un interrupteur global pour les interruptions en mode machine. Même désactivé, le processeur continue d’exécuter du code, mais ignore les requêtes d’interruption matérielles.
1.2 Configurer le vecteur d’exception machine (mtvec)
Le registre mtvec définit l’adresse de base du gestionnaire d’exceptions en mode machine. Il doit être initialisé dès le démarrage, car toute excepsion non gérée (instruction illégale, accès mémoire invalide, interruption) redirige le PC vers cette adresse. Une valeur incorecte conduit à un comportement indéfini ou à un blocage système.
# Exemple en assembleur RISC-V
la t0, trap_handler_m
csrw mtvec, t0
1.3 Définir le mode cible après mret avec mstatus.MPP
Le champ MPP (Machine Previous Privilege) dans mstatus indique le niveau de privilège à restaurer lors de l’exécution de l’instruction mret. Pour basculer en mode utilisateur, on fixe MPP = 0b00. Sans cette configuration, mret pourrait laisser le processeur en mode machine ou en mode superviseur, compromettant la sécurité ou la stabilité.
// Configuration en C avec accès aux CSR
#define MPP_USER (0x0 << 11)
// Efface les bits MPP[12:11], puis écrit 0b00
write_csr(mstatus, (read_csr(mstatus) & ~(0x3 << 11)) | MPP_USER);
1.4 Restreindre l’accès mémoire utilisateur via PMP
En l’absence de configuration PMP (Physical Memory Protection), le mode utilisateur ne peut accéder à aucune région mémoire, car la politique par défaut est restrictive. Il faut donc définir des règles PMP autorisant l’accès aux segments code, données et pile de l’application. Chaque règle spécifie une adresse de base, une taille (souvent alignée sur des puissances de deux) et des permissions (R/W/X).
Les entrées PMP sont limitées (typiquement 8 à 16). Il est conseillé de fusionner les régions contiguës ayant les mêmes permissions pour optimiser leur usage. Les zones critiques (vecteurs d’exception, structures noyau) doivent rester inaccessibles en mode utilisateur.
1.5 Préparer la délégation d’exceptions vers le mode superviseur
Même si l’application s’exécute directement en mode utilisateur, certaines exceptions synchrones (comme ECALL ou les défauts de page) doivent être traitées par un gestionnaire en mode superviseur (S-mode). Pour cela, on configure :
stvec: adresse du gestionnaire d’exceptions en S-mode.medeleg: masque de délégation des exceptions depuis M-mode vers S-mode.
Par exemple, activer le bit correspondant à l’exception ECALL provenant du mode utilisateur dans medeleg permet au processeur de transférer directement cette exception au gestionnaire S-mode, évitant un aller-retour coûteux via le mode machine.
// Délégation de ECALL (U-mode) et erreurs de page
uint64_t deleg_val = (1UL << CAUSE_USER_ECALL) |
(1UL << CAUSE_INST_PAGE_FAULT) |
(1UL << CAUSE_LOAD_PAGE_FAULT) |
(1UL << CAUSE_STORE_PAGE_FAULT);
write_csr(medeleg, deleg_val);