Cortex-M7 sur i.MX8M Plus
Guide complet du coprocesseur temps reel : demarrage, memoire (TCM, OCRAM, DDR), cache, RDC, RPMsg, shared memory, RemoteProc, Device Tree et integration avec le Cortex-A53.
Vue d’ensemble
L’i.MX8M Plus integre un Cortex-M7 @ 800 MHz en plus des 4 coeurs Cortex-A53. Ce coprocesseur est concu pour le temps reel dur : controle moteur, acquisition capteurs, protocoles industriels, traitement audio bas-niveau — tout ce que Linux ne peut pas garantir avec des latences deterministes.
Le M7 fonctionne de maniere independante du A53 : il a ses propres memoires (TCM), son propre vecteur d’interruption (NVIC), et peut tourner meme lorsque Linux est en veille. La communication avec le A53 se fait via RPMsg (mailbox + shared memory).
⚙️ Cortex-M7 sur i.MX8MP
CoreARM Cortex-M7, pipeline 6 etages
Frequence800 MHz
ITCM128 KB (code, 0 wait-state)
DTCM128 KB (data, 0 wait-state)
OCRAMJusqu’a 256 KB (partage avec A53)
Cache L1I-Cache 32 KB + D-Cache 32 KB
FPUDouble precision (DP FPU)
NVIC240 interruptions, 16 niveaux de priorite
DebugSWD / JTAG
CommunicationRPMsg-Lite via MU (Messaging Unit)
Architecture du core
Le Cortex-M7 est un processeur Harvard modifie : des bus separes pour les instructions et les donnees, ce qui permet de lire une instruction et une donnee dans le meme cycle. C’est le plus puissant de la famille Cortex-M.
Ce qui le differencie d’un Cortex-M4
| Caracteristique | Cortex-M4 | Cortex-M7 |
| Pipeline | 3 etages | 6 etages (superscalaire, dual-issue) |
| FPU | Simple precision (SP) | Double precision (DP) |
| Cache | Aucun | I-Cache 32 KB + D-Cache 32 KB |
| TCM | Optionnel | ITCM + DTCM, 0 wait-state |
| Bus | AHB (1 bus) | AXI 64-bit + AHB (Harvard) |
| Branch prediction | Non | Oui |
| MPU regions | 8 | 16 |
| Performance | ~1.25 DMIPS/MHz | ~2.14 DMIPS/MHz |
Dual-issue et pipeline 6 etages
Le M7 peut executer 2 instructions par cycle (dual-issue) quand elles sont independantes. Le pipeline 6 etages (Fetch → Decode → Issue → Execute → Memory → Writeback) permet un throughput eleve, mais attention : un branchement non predit coute 6 cycles de penalite (vs 3 sur M4). Le predicteur de branchement reduit ce cout dans la plupart des cas.
💡
Implications pratiques : a 800 MHz avec dual-issue, le M7 peut atteindre 1712 DMIPS — plus qu’un Cortex-A7 a frequence equivalente. Mais contrairement a un A53, il n’a pas de MMU (pas de memoire virtuelle, pas de Linux possible directement).
Demarrage du M7
Le Cortex-M7 peut etre demarre de 3 facons, selon le moment du cycle de vie du systeme :
1. Demarrage par U-Boot (early boot)
C’est la methode la plus courante pour les systemes de production. Le firmware M7 est charge avant Linux, ce qui garantit que le M7 est operationnel des le demarrage.
# Dans U-Boot, charger le firmware M7 en ITCM et demarrer :
# 1. Charger le binaire depuis la partition FAT
fatload mmc 0:1 0x48000000 firmware_m7.bin
# 2. Copier en TCM (adresse vue du A53 : 0x7E0000)
cp.b 0x48000000 0x7E0000 ${filesize}
# 3. Demarrer le M7
bootaux 0x7E0000
# Le M7 est maintenant en execution.
# Le vecteur de reset est lu a l’adresse 0x7E0000 (= 0x0 vu du M7).
# — Alternative : charger en DDR —
fatload mmc 0:1 0x80000000 firmware_m7_ddr.bin
bootaux 0x80000000
⚠️
Attention : la commande bootaux de U-Boot effectue 3 operations : (1) configure le vecteur de reset du M7, (2) libere le reset du M7, (3) le M7 commence a executer a l’adresse indiquee. Si le firmware est en ITCM, l’adresse U-Boot est 0x7E0000 (mapping A53), ce qui correspond a 0x00000000 cote M7.
2. Demarrage par RemoteProc (Linux runtime)
Linux peut charger et demarrer le firmware M7 via le framework RemoteProc. C’est plus flexible (on peut recharger le firmware sans reboot), mais le M7 demarre apres Linux.
# Depuis Linux :
echo firmware_m7.elf > /sys/class/remoteproc/remoteproc0/firmware
echo start > /sys/class/remoteproc/remoteproc0/state
# Verifier l’etat :
cat /sys/class/remoteproc/remoteproc0/state
# running
# Arreter le M7 :
echo stop > /sys/class/remoteproc/remoteproc0/state
3. Demarrage par le ROM bootloader (production securisee)
Le ROM interne du SoC peut charger le firmware M7 depuis le container d’images signe (HAB). Le M7 demarre avant U-Boot, avant le A53. C’est la methode la plus securisee et la plus rapide, mais elle necessite un build signe.
Mapping des adresses au boot
Le point critique a comprendre : le M7 et le A53 ne voient pas les memes adresses pour les TCM.
| Zone memoire | Adresse vue M7 | Adresse vue A53 / U-Boot | Taille |
| ITCM | 0x00000000 | 0x007E0000 | 128 KB |
| DTCM | 0x20000000 | 0x00800000 | 128 KB |
| OCRAM_S | 0x20200000 | 0x00180000 | 32 KB |
| OCRAM | 0x20240000 | 0x00900000 | 256 KB |
| DDR | 0x40000000 – 0xBFFFFFFF | 0x40000000 – 0xBFFFFFFF | Meme mapping |
| Peripheriques | 0x30000000 – 0x3FFFFFFF | 0x30000000 – 0x3FFFFFFF | Meme mapping |
📌
Regle d’or : quand U-Boot charge un firmware en TCM, il utilise l’adresse 0x7E0000 (vue A53). Le firmware M7 lui-meme est compile pour l’adresse 0x00000000 (vue M7). Le hardware effectue la translation automatiquement. En DDR, les adresses sont les memes pour les deux cores.
Memory map du Cortex-M7
Le Cortex-M7 de l’i.MX8MP a acces a tout l’espace d’adressage 4 GB du SoC, mais les performances varient enormement selon la zone memoire accedee.
graph TD
subgraph M7_MAP["Memory Map Cortex-M7 (vue M7)"]
direction LR
ITCM["ITCM
0x00000000
128 KB
0 wait-state"]
DTCM["DTCM
0x20000000
128 KB
0 wait-state"]
OCRAM["OCRAM
0x20240000
256 KB
1-2 cycles"]
PERIPH["Peripheriques
0x30000000
UART, I2C, SPI..."]
DDR["DDR
0x40000000
jusqu'a 8 GB
10-50+ cycles"]
end
style ITCM fill:#f0fdf4,stroke:#10b981,stroke-width:2px
style DTCM fill:#f0fdf4,stroke:#10b981,stroke-width:2px
style OCRAM fill:#fef3c7,stroke:#d97706,stroke-width:2px
style PERIPH fill:#eff6ff,stroke:#2563eb,stroke-width:2px
style DDR fill:#fef2f2,stroke:#ef4444,stroke-width:2px
Vert = rapide (0 wait-state), Orange = moyen, Rouge = lent (DDR via bus AXI)
Performances par zone memoire
| Zone | Latence | Bande passante | Usage recommande |
| ITCM | 0 wait-state 1.25 ns @ 800 MHz | 64-bit / cycle | Code critique temps reel, vecteurs d’interruption, handlers ISR |
| DTCM | 0 wait-state 1.25 ns @ 800 MHz | 64-bit / cycle | Stack, variables globales, buffers audio, LUT de coefficients |
| OCRAM | 1-2 cycles | 32-bit / cycle | Code secondaire, donnees non critiques, buffers DMA |
| DDR (sans cache) | 50-100+ cycles | Variable (contention bus) | Gros buffers uniquement, code de boot, donnees non temps reel |
| DDR (avec D-Cache) | 1 cycle (hit), 50+ (miss) | 32-bit / cycle (hit) | Donnees volumineuses avec pattern d’acces previsible |
TCM (Tightly Coupled Memory)
Les TCM sont la memoire la plus rapide accessible par le M7 : acces en 1 cycle, sans aucun wait-state, meme a 800 MHz. Elles sont directement connectees au core, sans passer par un bus ni un cache.
ITCM — Instruction TCM (128 KB)
- Adresse M7 : 0x00000000 – 0x0001FFFF
- Adresse A53 : 0x007E0000 – 0x007FFFFF
- Role : code executable — ISR handlers, boucles de traitement temps reel, fonctions critiques
- Taille : 128 KB. Un firmware M7 typique (FreeRTOS + application) fait 64-120 KB
- Particularite : le vecteur d’interruption (table des handlers) est a l’adresse 0x00000000, donc toujours en ITCM
DTCM — Data TCM (128 KB)
- Adresse M7 : 0x20000000 – 0x2001FFFF
- Adresse A53 : 0x00800000 – 0x0081FFFF
- Role : donnees — pile (stack), variables globales, buffers de travail
- Acces aligne : en 64 bits, DTCM est accessible en 1 cycle. Un acces non aligne sur 64 bits peut couter 2 cycles
- DMA : le DTCM est accessible par DMA (SDMA), mais le DMA passe par le bus AXI, donc il est plus lent que l’acces direct du M7
/* Exemple de linker script (firmware M7) — placement en TCM */
MEMORY
{
/* ITCM : code executable, vecteur d’interruption */
m_interrupts (RX) : ORIGIN = 0x00000000, LENGTH = 0x00000400
m_text (RX) : ORIGIN = 0x00000400, LENGTH = 0x0001FC00 /* ~127 KB */
/* DTCM : donnees, stack, heap */
m_data (RW) : ORIGIN = 0x20000000, LENGTH = 0x00020000 /* 128 KB */
/* OCRAM : code/data supplementaire si TCM depasse */
m_data2 (RW) : ORIGIN = 0x20240000, LENGTH = 0x00040000 /* 256 KB */
/* Shared memory : RPMsg vring + data exchange */
m_rpmsg_sh (RW) : ORIGIN = 0x55000000, LENGTH = 0x00100000 /* 1 MB */
}
SECTIONS
{
/* Vecteur d’interruption a l’adresse 0 */
.interrupts : { . = ALIGN(4); KEEP(*(.isr_vector)) } > m_interrupts
.text : { *(.text*) *(.rodata*) } > m_text
.data : { *(.data*) } > m_data
.bss : { *(.bss*) *(COMMON) } > m_data
_stack_top = ORIGIN(m_data) + LENGTH(m_data); /* Stack en haut du DTCM */
}
⚠️
Attention a la taille : 128 KB d’ITCM, c’est peu. Un firmware FreeRTOS + RPMsg-Lite + application fait typiquement 64-120 KB. Si le firmware depasse 128 KB, il faudra placer du code en OCRAM ou en DDR (avec cache active), ce qui degrade les performances temps reel.
OCRAM (On-Chip RAM)
L’OCRAM est une SRAM integree dans le SoC, plus lente que les TCM mais plus grande et partageable entre M7 et A53. Plusieurs banques sont disponibles :
| Banque | Adresse A53 | Adresse M7 | Taille | Usage typique |
| OCRAM_S | 0x00180000 | 0x20200000 | 32 KB | Ressource table RemoteProc |
| OCRAM_1 | 0x00900000 | 0x20240000 | 128 KB | Code/data M7 supplementaire |
| OCRAM_2 | 0x00940000 | 0x20280000 | 128 KB | Buffers DMA, donnees partagees |
L’OCRAM est accessible en 1-2 cycles par le M7, ce qui en fait un bon compromis quand les TCM sont pleins. Mais contrairement aux TCM, l’OCRAM passe par un bus interne et peut subir de la contention si le A53 y accede en meme temps.
📌
Bonne pratique : utiliser le RDC pour assigner des banques OCRAM exclusivement au M7. Cela evite la contention et garantit un acces deterministe. Voir la section RDC ci-dessous.
DDR et ses limites
Le M7 peut acceder a la DDR (meme espace d’adressage 0x40000000+ que le A53), mais c’est fortement deconseille pour du code temps reel. Voici pourquoi :
Pourquoi la DDR est lente pour le M7
| Facteur | Impact | Latence typique |
| Bus AXI | Le M7 accede a la DDR via le bus AXI partage avec le A53, le GPU, le NPU, le VPU… | +10-20 cycles d’arbitrage |
| Controleur DDR | Le DDRC a ses propres latences (CAS, RAS, precharge) | +20-30 cycles |
| Contention | Si le A53 fait un memcpy ou le GPU fait un rendu, le M7 attend | +0 a 100+ cycles (imprevisible) |
| Refresh | La DDR fait des cycles de refresh periodiques qui bloquent les acces | +100-200 ns par refresh |
| Total sans cache | Un acces DDR depuis le M7 peut prendre | 50 a 200+ cycles |
A 800 MHz, un cycle = 1.25 ns. Un acces DDR a 100 cycles = 125 ns — contre 1.25 ns en TCM. C’est 80 a 100x plus lent.
Quand utiliser la DDR malgre tout
- Gros buffers (> 256 KB) : si le firmware a besoin de manipuler des tableaux de donnees volumineux (images, logs, buffers circulaires geants)
- Shared memory : pour echanger des donnees avec le A53, la DDR est la seule option pour les gros volumes
- Code non critique : initialisation, configuration, debug — tout ce qui ne necessite pas de determinisme
⚠️
Regle : tout code execute en DDR doit avoir le cache L1 active, sinon chaque instruction fetch coute 50+ cycles. Voir la section Cache ci-dessous.
Cache L1
Le Cortex-M7 dispose de 2 caches L1 independants :
- I-Cache : 32 KB, 2-way set-associative — cache les instructions lues depuis l’OCRAM ou la DDR
- D-Cache : 32 KB, 4-way set-associative — cache les donnees lues depuis l’OCRAM ou la DDR
💡
Les TCM ne passent pas par le cache. Les acces ITCM et DTCM bypassent completement le cache (ils sont deja a 0 wait-state). Le cache n’est utile que pour les acces OCRAM et DDR.
Activation du cache
Le cache est desactive par defaut au reset du M7. Il faut l’activer explicitement dans le firmware :
/* Activation des caches L1 dans le firmware M7 (CMSIS) */
#include « fsl_cache.h »
void BOARD_InitCache(void)
{
/* Activer I-Cache : accelere le fetch d’instructions depuis OCRAM/DDR */
SCB_EnableICache();
/* Activer D-Cache : accelere les acces data depuis OCRAM/DDR */
SCB_EnableDCache();
}
/* ATTENTION : avec le D-Cache active, les zones de shared memory
* doivent etre configurees en « non-cacheable » dans la MPU,
* sinon le A53 ecrira en DDR mais le M7 lira une valeur perimee
* depuis son cache (probleme de coherence). */
Coherence cache et shared memory
Le Cortex-M7 n’a pas de coherence cache hardware avec le A53. Quand le A53 ecrit en shared memory (DDR), le D-Cache du M7 peut contenir une ancienne valeur. Il faut :
- Option 1 : configurer la region de shared memory en non-cacheable dans la MPU du M7 (recommande)
- Option 2 : invalider manuellement le D-Cache avant chaque lecture de la shared memory (
SCB_InvalidateDCache_by_Addr())
- Option 3 : faire un
SCB_CleanDCache_by_Addr() cote M7 avant d’ecrire, et un invalidate cote A53
/* Configuration MPU : shared memory en non-cacheable */
#include « fsl_common.h »
void BOARD_ConfigMPU(void)
{
ARM_MPU_Disable();
/* Region 0 : ITCM — Normal, Non-cacheable (pas besoin, TCM bypass le cache) */
MPU->RNR = 0;
MPU->RBAR = 0x00000000 | (0 << 0); /* Region 0, adresse base */
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL,
0, 0, 0, 0, /* TEX=0, S=0, C=0, B=0 : Strongly-ordered */
0, ARM_MPU_REGION_SIZE_128KB);
/* Region 1 : DTCM — Normal, Non-cacheable */
MPU->RNR = 1;
MPU->RBAR = 0x20000000;
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL,
0, 0, 0, 0, 0, ARM_MPU_REGION_SIZE_128KB);
/* Region 2 : DDR — Normal, Write-Back, Write-Allocate (cacheable) */
MPU->RNR = 2;
MPU->RBAR = 0x40000000;
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL,
1, 0, 1, 1, /* TEX=1, C=1, B=1 : WB-WA */
0, ARM_MPU_REGION_SIZE_2GB);
/* Region 3 : Shared memory RPMsg (0x55000000) — NON-CACHEABLE */
MPU->RNR = 3;
MPU->RBAR = 0x55000000;
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL,
1, 0, 0, 0, /* TEX=1, C=0, B=0 : Non-cacheable */
0, ARM_MPU_REGION_SIZE_1MB);
/* Region 4 : Peripheriques (0x30000000) — Device, non-cacheable */
MPU->RNR = 4;
MPU->RBAR = 0x30000000;
MPU->RASR = ARM_MPU_RASR(0, ARM_MPU_AP_FULL,
0, 1, 0, 0, /* TEX=0, S=1 : Device, Shareable */
0, ARM_MPU_REGION_SIZE_256MB);
ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);
}
RDC (Resource Domain Controller)
Le RDC est un controleur de securite hardware de l’i.MX8MP qui permet de partitionner les ressources du SoC (peripheriques, memoire, interruptions) entre les differents domaines : A53 (Linux), M7 (RTOS), etc.
Pourquoi le RDC est indispensable
Sans RDC, le A53 et le M7 peuvent acceder aux memes peripheriques et aux memes zones memoire simultanement, ce qui provoque des conflits :
- Linux reconfigure un UART que le M7 utilise → corruption des donnees
- Le A53 ecrit en OCRAM pendant que le M7 lit → donnees incoherentes
- Un driver Linux reprend un GPIO que le M7 pilote → comportement imprevisible
Le RDC resout ca en attribuant chaque ressource a un domaine. Un acces non autorise genere un bus fault (le peripherique repond avec une erreur AXI).
Les 4 domaines
| Domaine | ID | Masters | Usage typique |
| D0 | 0 | Cortex-A53 | Linux — peripheriques standards, DDR, GPU |
| D1 | 1 | Cortex-M7 | RTOS — UART4, I2C, SPI, TCM, OCRAM reserve |
| D2 | 2 | SDMA | DMA — acces memoire pour transferts audio, etc. |
| D3 | 3 | Reserve | Non utilise typiquement |
Types de ressources controlees par le RDC
- Peripheriques (RDC_PDAP) : chaque peripherique a un registre de permission (lecture/ecriture par domaine). Ex : UART4 assigne au domaine 1 = seul le M7 peut y acceder.
- Memoire (RDC_MRC) : jusqu’a 8 regions memoire, chacune avec des permissions par domaine. Ex : OCRAM de 0x900000 a 0x93FFFF assigne au M7 exclusivement.
- Interruptions : le RDC ne gere pas directement les interruptions, mais les peripheriques n’etant plus accessibles au domaine non-autorise, les interruptions associees ne sont plus pertinentes cote A53.
Registres RDC pour un peripherique
/* Structure d’un registre RDC_PDAP (Peripheral Domain Access Permission)
*
* Chaque peripherique a un registre PDAP de 32 bits :
*
* Bits [1:0] = D0 (A53) : 00=No access, 01=W only, 10=R only, 11=R/W
* Bits [3:2] = D1 (M7) : meme encoding
* Bits [5:4] = D2 (SDMA): meme encoding
* Bits [7:6] = D3 : meme encoding
* Bit [28] = SREQ : Semaphore request enable
* Bit [30] = LCK : Lock (non modifiable apres boot)
*
* Exemple : UART4 accessible uniquement par le M7 (D1) :
* RDC_PDAP[UART4] = 0b 00 00 00 11 00 00 00 00 = 0x0000000C
* D3 D2 D1 D0
* — — RW —
*/
Exemples RDC
Voici des exemples concrets de configuration RDC pour un systeme i.MX8MP avec A53 (Linux) + M7 (FreeRTOS).
Exemple 1 : Assigner UART4 au M7
/* Dans le firmware M7 (board.c) — configurer le RDC au demarrage */
#include « fsl_rdc.h »
void BOARD_RdcInit(void)
{
/* UART4 : index RDC = 102 (voir Reference Manual, table 5-3) */
rdc_domain_assignment_t assignment = {
.domainId = 1, /* M7 = domain 1 */
};
rdc_periph_access_config_t periphConfig;
/* Configurer UART4 : acces R/W pour D1 (M7), aucun acces pour D0 (A53) */
periphConfig.periph = kRDC_Periph_UART4;
periphConfig.policy = RDC_ACCESS_POLICY(0, kRDC_NoAccess) /* D0 = A53 : rien */
| RDC_ACCESS_POLICY(1, kRDC_ReadWrite) /* D1 = M7 : R/W */
| RDC_ACCESS_POLICY(2, kRDC_NoAccess) /* D2 = SDMA */
| RDC_ACCESS_POLICY(3, kRDC_NoAccess); /* D3 */
periphConfig.lock = false; /* true pour verrouiller apres boot */
RDC_SetPeriphAccessConfig(RDC, &periphConfig);
}
/* Resultat : si Linux tente d’acceder a UART4 (0x30A60000),
* il recevra un bus error. Le driver Linux ne doit PAS
* etre active pour UART4 dans le DTS. */
Exemple 2 : Reserver une region OCRAM au M7
/* Reserver OCRAM_1 (128 KB @ 0x900000) exclusivement au M7 */
rdc_mem_access_config_t memConfig;
memConfig.mem = kRDC_Mem_MRC0_0; /* Memory Region Controller 0, region 0 */
memConfig.baseAddress = 0x00900000; /* Adresse debut (vue A53) */
memConfig.endAddress = 0x0091FFFF; /* Adresse fin (128 KB) */
memConfig.policy = RDC_ACCESS_POLICY(0, kRDC_NoAccess) /* A53 : rien */
| RDC_ACCESS_POLICY(1, kRDC_ReadWrite) /* M7 : R/W */
| RDC_ACCESS_POLICY(2, kRDC_NoAccess) /* SDMA */
| RDC_ACCESS_POLICY(3, kRDC_NoAccess);
RDC_SetMemAccessConfig(RDC, &memConfig);
/* Cote Linux, cette zone doit etre declaree comme « reserved » dans le DTS :
* reserved-memory {
* m7_ocram: m7-ocram@900000 {
* reg = <0 0x00900000 0 0x20000>;
* no-map;
* };
* };
*/
Exemple 3 : Reserver une zone DDR pour la shared memory
/* Reserver 16 MB de DDR (0x55000000 – 0x55FFFFFF) pour echange M7/A53 */
rdc_mem_access_config_t sharedMemConfig;
sharedMemConfig.mem = kRDC_Mem_MRC1_0;
sharedMemConfig.baseAddress = 0x55000000;
sharedMemConfig.endAddress = 0x55FFFFFF;
sharedMemConfig.policy = RDC_ACCESS_POLICY(0, kRDC_ReadWrite) /* A53 : R/W */
| RDC_ACCESS_POLICY(1, kRDC_ReadWrite) /* M7 : R/W */
| RDC_ACCESS_POLICY(2, kRDC_NoAccess)
| RDC_ACCESS_POLICY(3, kRDC_NoAccess);
RDC_SetMemAccessConfig(RDC, &sharedMemConfig);
/* Ici les DEUX domaines ont acces R/W : c’est la zone de shared memory.
* La coherence doit etre geree par software (MPU non-cacheable + barriers). */
Exemple 4 : Configuration RDC depuis U-Boot
/* U-Boot configure le RDC AVANT de demarrer Linux et le M7.
* Fichier : board/freescale/imx8mp_evk/imx8mp_evk.c
*
* NXP fournit des exemples dans le BSP : */
/* Assigner le UART4 au M7 avant bootaux */
imx_rdc_setup_periph(RDC_PDAP_UART4, RDC_DOMAIN(1));
/* Assigner les GPT timers au M7 */
imx_rdc_setup_periph(RDC_PDAP_GPT1, RDC_DOMAIN(1));
/* Assigner I2C3 au M7 */
imx_rdc_setup_periph(RDC_PDAP_I2C3, RDC_DOMAIN(1));
/* Reserver une zone memoire au M7 */
imx_rdc_setup_region(RDC_REGION(0), 0x55000000, 0x55FFFFFF,
RDC_ACCESS(1, RDC_RW) | RDC_ACCESS(0, RDC_RW));
📌
Coherence RDC / DTS : chaque peripherique assigne au M7 via le RDC doit etre desactive dans le Device Tree Linux (status = "disabled"). Sinon, le driver Linux tentera d’acceder au peripherique et recevra un bus fault. De meme, les zones memoire reservees doivent etre declarees no-map dans reserved-memory.
📌
Communication entre les deux mondes. Le M7 et le A53 sont isoles par le RDC. Pour echanger des commandes et des donnees, ils utilisent RPMsg (messages legers via mailbox) et de la shared memory (zones DDR partagees pour les gros volumes).
RPMsg et RPMsg-Lite
RPMsg (Remote Processor Messaging) est le protocole standard Linux pour la communication inter-processeur. Sur l’i.MX8MP, il repose sur 3 couches :
| Couche | Role | Implementation |
| Mailbox (MU) | Notification hardware : « j’ai mis un message en shared memory » | Messaging Unit i.MX8MP, registre de 32 bits, interruption |
| VirtIO / Vring | File d’attente circulaire en shared memory, descripteurs de buffers | 2 vrings : un TX (A53→M7), un RX (M7→A53) |
| RPMsg | Couche applicative : endpoints, canaux nommes, messages arbitraires | Cote Linux : rpmsg_char ou rpmsg_tty. Cote M7 : RPMsg-Lite |
RPMsg-Lite vs RPMsg classique
RPMsg-Lite est la version NXP optimisee pour les microcontroleurs (M7). Differences :
- Empreinte memoire : ~5 KB de code (vs ~30 KB pour le RPMsg OpenAMP complet)
- Zero-copy : supporte le mode zero-copy ou l’application recoit directement le pointeur vers le buffer vring
- Pas de dependance OS : fonctionne en bare-metal ou avec FreeRTOS
- Meme protocole : 100% compatible avec le driver RPMsg cote Linux
Fonctionnement simplifie
sequenceDiagram
participant A53 as Cortex-A53 (Linux)
participant MU as Mailbox (MU)
participant M7 as Cortex-M7 (FreeRTOS)
A53->>A53: Ecrit message dans vring TX
A53->>MU: Signal "message dispo"
MU->>M7: Interruption MU
M7->>M7: Lit message depuis vring TX
M7->>M7: Traite le message
M7->>M7: Ecrit reponse dans vring RX
M7->>MU: Signal "reponse dispo"
MU->>A53: Interruption MU
A53->>A53: Lit reponse depuis vring RX
Taille des messages RPMsg
Chaque message RPMsg a une taille maximale fixe, definie par la taille du buffer dans le vring :
/* Taille max d’un message RPMsg (defaut NXP) */
#define RL_BUFFER_PAYLOAD_SIZE (496) /* octets de payload */
#define RL_BUFFER_COUNT (256) /* nombre de buffers dans le vring */
/* Structure d’un buffer RPMsg : */
struct rpmsg_hdr {
uint32_t src; /* endpoint source */
uint32_t dst; /* endpoint destination */
uint32_t reserved;
uint16_t len; /* longueur du payload */
uint16_t flags;
uint8_t data[496]; /* payload */
};
/* Total par buffer = 512 octets (header 16 + payload 496) */
/* Memoire totale pour les vrings :
* 2 vrings * 256 buffers * 512 octets = 256 KB */
⚠️
496 octets max par message. RPMsg n’est pas fait pour transporter des gros volumes de donnees (images, tableaux de capteurs). Il est fait pour transporter des commandes, des notifications, et eventuellement des pointeurs vers des zones de shared memory ou se trouvent les vraies donnees.
Vring et shared memory RPMsg
Les vrings (VirtIO rings) sont les structures en shared memory ou transitent les messages RPMsg. Ils doivent etre places a une adresse connue des deux cotes (A53 et M7).
Placement memoire des vrings
/* Adresses conventionnelles NXP pour l’i.MX8MP : */
/* Vring 0 (TX : A53 -> M7) */
#define VDEV0_VRING_BASE 0x55000000
/* Vring 1 (RX : M7 -> A53) */
/* Automatiquement place apres vring 0 :
* VRING1 = VRING0 + taille_vring0 (alignee 4 KB) */
/* Resource table (en OCRAM_S pour etre visible tres tot au boot) */
#define RESOURCE_TABLE_ADDR 0x00180000 /* OCRAM_S, vue A53 */
/* Memoire totale reservee pour RPMsg : */
/* 0x55000000 – 0x5503FFFF = 256 KB (vrings + buffers) */
La resource table
La resource table est une structure incluse dans le firmware M7 (section ELF .resource_table) qui decrit au RemoteProc Linux les ressources necessaires : vrings, traces, etc.
/* Resource table du firmware M7 (rsc_table.c) */
#define NO_RESOURCE_ENTRIES (1) /* 1 seul vdev (rpmsg) */
struct remote_resource_table {
uint32_t version; /* 1 */
uint32_t num; /* nombre d’entrees */
uint32_t reserved[2];
uint32_t offset[NO_RESOURCE_ENTRIES];
/* VirtIO device pour RPMsg */
struct fw_rsc_vdev rpmsg_vdev;
struct fw_rsc_vdev_vring rpmsg_vring0; /* TX */
struct fw_rsc_vdev_vring rpmsg_vring1; /* RX */
};
struct remote_resource_table resource_table = {
.version = 1,
.num = 1,
.offset = { offsetof(struct remote_resource_table, rpmsg_vdev) },
.rpmsg_vdev = {
.type = RSC_VDEV,
.id = VIRTIO_ID_RPMSG, /* 7 = RPMsg */
.dfeatures = 1, /* RPMSG_F_NS (name service) */
.num_of_vrings = 2,
},
/* Vring 0 : A53 -> M7 */
.rpmsg_vring0 = {
.da = 0x55000000, /* adresse device (= adresse physique) */
.align = 0x1000, /* alignement 4 KB */
.num = 256, /* nombre de descripteurs */
},
/* Vring 1 : M7 -> A53 */
.rpmsg_vring1 = {
.da = 0x55008000, /* apres vring 0 */
.align = 0x1000,
.num = 256,
},
};
/* IMPORTANT : cette table doit etre placee dans la section .resource_table
* du binaire ELF, a l’adresse OCRAM_S (0x20200000 vue M7).
* Le linker script doit contenir :
* .resource_table : { KEEP(*(.resource_table)) } > m_ocram_s
*/
Shared memory pour donnees
RPMsg est limite a 496 octets par message. Pour echanger des volumes plus importants (tableaux de capteurs, images, buffers audio), on utilise une zone de shared memory dediee en DDR, separee de la zone RPMsg.
Architecture memoire recommandee
| Zone | Adresse | Taille | Usage |
| RPMsg vrings | 0x55000000 – 0x5503FFFF | 256 KB | Vrings VirtIO, buffers de messages (commandes, notifications) |
| Data exchange | 0x55040000 – 0x55FFFFFF | ~15.7 MB | Gros tableaux, buffers circulaires, donnees capteurs |
graph LR
subgraph DDR["DDR (vue commune A53 / M7)"]
direction LR
VRING["RPMsg Vrings
0x55000000
256 KB
Commandes"]
DATA["Data Exchange
0x55040000
~15.7 MB
Tableaux, buffers"]
end
A53["Cortex-A53
(Linux)"] --> VRING
A53 --> DATA
M7["Cortex-M7
(FreeRTOS)"] --> VRING
M7 --> DATA
style VRING fill:#eff6ff,stroke:#2563eb,stroke-width:2px
style DATA fill:#f0fdf4,stroke:#10b981,stroke-width:2px
style A53 fill:#fef3c7,stroke:#d97706,stroke-width:2px
style M7 fill:#fef3c7,stroke:#d97706,stroke-width:2px
Pourquoi separer RPMsg et les donnees ?
- Taille : les vrings sont limites a ~256 KB. Les donnees peuvent necessite des megaoctets
- Latence : les messages RPMsg sont legers et rapides (~2-5 us). Les transferts de donnees sont plus lourds
- Coherence : les vrings sont geres par le protocole VirtIO (barriers, ordering). La zone data est geree par l’application (plus de flexibilite)
- RDC : les deux zones peuvent avoir des permissions RDC differentes
Declarer la shared memory dans le DTS
/* Device Tree Linux : reserver la memoire pour RPMsg + data exchange */
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* Zone RPMsg : vrings + buffers de messages */
vdev0vring0: vdev0vring0@55000000 {
reg = <0 0x55000000 0 0x8000>; /* 32 KB pour vring 0 */
no-map;
};
vdev0vring1: vdev0vring1@55008000 {
reg = <0 0x55008000 0 0x8000>; /* 32 KB pour vring 1 */
no-map;
};
vdevbuffer: vdevbuffer@55400000 {
compatible = « shared-dma-pool »;
reg = <0 0x55400000 0 0x100000>; /* 1 MB pour buffers RPMsg */
no-map;
};
/* Zone data exchange : gros tableaux, buffers circulaires */
m7_data: m7-data@55500000 {
reg = <0 0x55500000 0 0xB00000>; /* 11 MB pour donnees */
no-map;
};
/* Reserve pour le firmware M7 en DDR (si pas en TCM) */
m7_reserved: m7-reserved@80000000 {
reg = <0 0x80000000 0 0x1000000>; /* 16 MB */
no-map;
};
};
Pattern : pointeurs via RPMsg
Le design pattern le plus efficace pour echanger de gros volumes de donnees entre le A53 et le M7 :
- Le producteur (A53 ou M7) ecrit les donnees dans la zone de data exchange (0x55500000+)
- Il envoie un message RPMsg de quelques octets contenant : l’adresse du buffer, la taille, et un identifiant de commande
- Le consommateur recoit le message RPMsg, lit l’adresse, et accede directement aux donnees en shared memory
sequenceDiagram
participant A53 as A53 (Linux)
participant RPMSG as RPMsg (496 B max)
participant SHM as Shared Memory (11 MB)
participant M7 as M7 (FreeRTOS)
A53->>SHM: 1. Ecrit 32 KB de donnees capteur a 0x55500000
A53->>RPMSG: 2. Envoie {cmd: PROCESS, addr: 0x55500000, len: 32768}
RPMSG->>M7: 3. Message recu (16 octets)
M7->>SHM: 4. Lit 32 KB directement a 0x55500000
M7->>M7: 5. Traite les donnees
M7->>SHM: 6. Ecrit resultat a 0x55508000
M7->>RPMSG: 7. Envoie {cmd: DONE, addr: 0x55508000, len: 4096}
RPMSG->>A53: 8. Reponse recue
A53->>SHM: 9. Lit le resultat
Implementation cote M7
/* Structure de commande echangee via RPMsg (16 octets) */
typedef struct {
uint32_t cmd; /* PROCESS, DONE, ERROR, etc. */
uint32_t addr; /* adresse physique dans la zone data exchange */
uint32_t len; /* taille des donnees en octets */
uint32_t flags; /* options (format, priorite, etc.) */
} shm_msg_t;
#define CMD_PROCESS 0x01
#define CMD_DONE 0x02
#define CMD_ERROR 0xFF
#define DATA_EXCHANGE_BASE 0x55500000
/* Callback RPMsg-Lite quand un message arrive du A53 */
static int32_t rpmsg_callback(void *payload, uint32_t len, uint32_t src, void *priv)
{
shm_msg_t *msg = (shm_msg_t *)payload;
if (msg->cmd == CMD_PROCESS)
{
/* Les donnees sont deja en shared memory a msg->addr.
* Pas de copie ! On travaille directement dessus. */
volatile float *input = (volatile float *)msg->addr;
volatile float *output = (volatile float *)(DATA_EXCHANGE_BASE + 0x8000);
/* Traitement : par exemple, filtrage IIR sur msg->len / 4 echantillons */
uint32_t n_samples = msg->len / sizeof(float);
for (uint32_t i = 0; i < n_samples; i++)
{
output[i] = iir_filter(input[i]);
}
/* Envoyer la reponse via RPMsg (juste le pointeur + taille) */
shm_msg_t reply = {
.cmd = CMD_DONE,
.addr = (uint32_t)output,
.len = n_samples * sizeof(float),
.flags = 0,
};
rpmsg_lite_send(my_rpmsg, my_ept, src, &reply, sizeof(reply), RL_BLOCK);
}
return RL_RELEASE;
}
Implementation cote Linux
/* Application userspace Linux — envoi de donnees via RPMsg + shared memory */
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#define DATA_EXCHANGE_BASE 0x55500000
#define DATA_EXCHANGE_SIZE (11 * 1024 * 1024) /* 11 MB */
int main()
{
/* 1. Mapper la zone de data exchange en userspace */
int fd_mem = open(« /dev/mem », O_RDWR | O_SYNC);
volatile void *shm = mmap(NULL, DATA_EXCHANGE_SIZE,
PROT_READ | PROT_WRITE, MAP_SHARED,
fd_mem, DATA_EXCHANGE_BASE);
/* 2. Ecrire les donnees capteur dans la shared memory */
float *input = (float *)shm;
for (int i = 0; i < 8192; i++)
input[i] = sensor_read(i); /* 32 KB de floats */
/* 3. Envoyer la commande via RPMsg (juste l’adresse + taille) */
int fd_rpmsg = open(« /dev/rpmsg_ctrl0 », O_RDWR);
/* … creer endpoint, envoyer struct shm_msg_t … */
shm_msg_t msg = {
.cmd = CMD_PROCESS,
.addr = DATA_EXCHANGE_BASE,
.len = 8192 * sizeof(float),
.flags = 0,
};
write(fd_rpmsg, &msg, sizeof(msg));
/* 4. Attendre la reponse RPMsg avec le pointeur du resultat */
shm_msg_t reply;
read(fd_rpmsg, &reply, sizeof(reply));
/* 5. Lire le resultat directement depuis la shared memory */
float *result = (float *)((uint8_t *)shm + (reply.addr – DATA_EXCHANGE_BASE));
printf(« Premier echantillon traite : %f\n », result[0]);
munmap((void *)shm, DATA_EXCHANGE_SIZE);
close(fd_mem);
close(fd_rpmsg);
}
💡
Avantage de ce pattern : RPMsg ne transporte que 16 octets (la commande), quelle que soit la taille des donnees. Les donnees restent en place en shared memory — pas de copie, pas de serialisation. C’est le pattern utilise dans tous les designs NXP industriels.
📌
Configuration systeme. On connait le hardware, la memoire, la communication. Il faut maintenant configurer Linux pour cohabiter avec le M7 : RemoteProc pour le lifecycle, Device Tree pour la declaration des ressources, et les peripheriques partages.
RemoteProc
RemoteProc est le framework Linux qui gere le cycle de vie du coprocesseur M7 : chargement du firmware (ELF), demarrage, arret, crash recovery. Il s’interface avec RPMsg pour la communication.
Drivers RemoteProc sur i.MX8MP
| Driver | Fichier source | Role |
| imx_rproc | drivers/remoteproc/imx_rproc.c | Driver principal RemoteProc pour i.MX8MP |
| imx_rpmsg | drivers/rpmsg/imx_rpmsg.c | Driver RPMsg pour la Messaging Unit (MU) |
Configuration kernel
# Kernel .config — activer RemoteProc et RPMsg
CONFIG_REMOTEPROC=y
CONFIG_IMX_REMOTEPROC=y
CONFIG_RPMSG=y
CONFIG_RPMSG_CHAR=y # /dev/rpmsg_ctrl0 pour userspace
CONFIG_RPMSG_TTY=y # /dev/ttyRPMSG0 pour acces serie virtuel
CONFIG_RPMSG_VIRTIO=y
# Optionnel : debug
CONFIG_REMOTEPROC_CDEV=y # /dev/remoteproc0 pour debug
Operations RemoteProc
# — Charger et demarrer le firmware M7 —
# Le firmware doit etre dans /lib/firmware/
cp firmware_m7.elf /lib/firmware/
# Specifier le firmware a charger
echo firmware_m7.elf > /sys/class/remoteproc/remoteproc0/firmware
# Demarrer le M7
echo start > /sys/class/remoteproc/remoteproc0/state
# Le RemoteProc va :
# 1. Parser l’ELF
# 2. Copier les sections en ITCM/DTCM/DDR
# 3. Lire la resource_table
# 4. Configurer les vrings
# 5. Liberer le reset du M7
# 6. Le M7 commence a executer
# Verifier
cat /sys/class/remoteproc/remoteproc0/state
# « running »
# — Arreter le M7 —
echo stop > /sys/class/remoteproc/remoteproc0/state
# — Recharger un nouveau firmware —
echo stop > /sys/class/remoteproc/remoteproc0/state
echo new_firmware.elf > /sys/class/remoteproc/remoteproc0/firmware
echo start > /sys/class/remoteproc/remoteproc0/state
# — Crash recovery automatique —
echo enabled > /sys/class/remoteproc/remoteproc0/recovery
# Si le M7 crash, RemoteProc le redemarrera automatiquement
⚠️
RemoteProc vs bootaux : si le M7 est deja demarre par U-Boot (bootaux), RemoteProc doit etre configure en mode « early boot » dans le DTS (early-booted). Sinon, RemoteProc tentera de reseter le M7, ecrasant le firmware en cours d’execution.
Device Tree (DTS)
Le Device Tree est la piece centrale de la configuration Linux pour le Cortex-M7. Il declare les zones memoire reservees, le node RemoteProc, la Messaging Unit, et desactive les peripheriques assignes au M7.
Vue d’ensemble du DTS
/* DTS complet pour un systeme i.MX8MP avec M7 */
/ {
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* —- Memoire firmware M7 —- */
/* Si le M7 execute depuis la DDR (pas TCM) */
m7_reserved: m7-reserved@80000000 {
reg = <0 0x80000000 0 0x1000000>; /* 16 MB */
no-map;
};
/* —- RPMsg vrings —- */
vdev0vring0: vdev0vring0@55000000 {
reg = <0 0x55000000 0 0x8000>;
no-map;
};
vdev0vring1: vdev0vring1@55008000 {
reg = <0 0x55008000 0 0x8000>;
no-map;
};
/* —- Buffers RPMsg —- */
rsc_table: rsc-table@550ff000 {
reg = <0 0x550ff000 0 0x1000>;
no-map;
};
vdevbuffer: vdevbuffer@55400000 {
compatible = « shared-dma-pool »;
reg = <0 0x55400000 0 0x100000>;
no-map;
};
/* —- Shared memory data exchange —- */
m7_data: m7-data@55500000 {
reg = <0 0x55500000 0 0xB00000>;
no-map;
};
};
/* —- IMX8MP M7 RemoteProc —- */
imx8mp-cm7 {
compatible = « fsl,imx8mp-cm7 »;
/* … voir section detail ci-dessous … */
};
};
Detail des champs DTS
Chaque champ du DTS a un role precis. Voici l’explication detaillee, champ par champ.
reserved-memory : bloquer Linux
reserved-memory {
#address-cells = <2>;
/* ^ Nombre de cellules (uint32_t) pour une adresse.
* 2 = adresse 64-bit (high word + low word).
* Sur i.MX8MP avec >4GB DDR, on utilise 2. */
#size-cells = <2>;
/* ^ Nombre de cellules pour une taille. 2 = taille 64-bit. */
ranges;
/* ^ Mot-cle obligatoire : indique que les adresses enfant
* sont les memes que les adresses parent (pas de translation). */
vdev0vring0: vdev0vring0@55000000 {
reg = <0 0x55000000 0 0x8000>;
/* ^ reg =
* addr = 0x00000000_55000000 = 0x55000000
* size = 0x00000000_00008000 = 32 KB
* C’est l’adresse PHYSIQUE en DDR. */
no-map;
/* ^ CRITIQUE : empeche Linux de mapper cette zone.
* Sans no-map, Linux peut placer des pages kernel ici
* et ecraser les vrings RPMsg.
* no-map signifie : pas dans la page table Linux,
* pas dans le buddy allocator, invisible pour kmalloc. */
};
vdevbuffer: vdevbuffer@55400000 {
compatible = « shared-dma-pool »;
/* ^ Indique que cette zone est un pool DMA partage.
* Le driver RPMsg peut allouer des buffers ici
* via dma_alloc_coherent(). */
reg = <0 0x55400000 0 0x100000>;
no-map;
};
m7_reserved: m7-reserved@80000000 {
reg = <0 0x80000000 0 0x1000000>;
no-map;
/* Reserve 16 MB de DDR pour le firmware M7.
* Utilise si le firmware est trop gros pour les TCM
* et doit etre charge en DDR. */
};
};
Node RemoteProc : declarer le M7
imx8mp-cm7 {
compatible = « fsl,imx8mp-cm7 »;
/* ^ Identifie le driver kernel a utiliser.
* Le driver imx_rproc.c match sur cette string.
* « fsl,imx8mp-cm7 » est specifique a l’i.MX8MP. */
clocks = <&clk IMX8MP_CLK_M7_CORE>;
/* ^ Reference a l’horloge du core M7.
* Le driver active cette horloge avant de demarrer le M7.
* IMX8MP_CLK_M7_CORE = 800 MHz par defaut. */
mbox-names = « tx », « rx », « rxdb »;
/* ^ Noms des canaux de la Messaging Unit (MU) :
* « tx » = canal d’envoi (A53 -> M7)
* « rx » = canal de reception (M7 -> A53)
* « rxdb » = canal doorbell (notification sans donnee) */
mboxes = <&mu 0 1>, <&mu 1 1>, <&mu 3 1>;
/* ^ References aux canaux physiques de la MU :
* <&mu channel direction>
* channel 0, dir 1 = TX register 0
* channel 1, dir 1 = RX register 1
* channel 3, dir 1 = General Purpose (doorbell) */
memory-region = <&vdev0vring0>, <&vdev0vring1>,
<&vdevbuffer>, <&rsc_table>,
<&m7_reserved>;
/* ^ Liste des zones memoire reservees, dans l’ordre :
* [0] = vring 0 (TX)
* [1] = vring 1 (RX)
* [2] = buffer pool RPMsg
* [3] = resource table
* [4] = zone firmware M7 en DDR
* L’ORDRE COMPTE : le driver les lit par index. */
fsl,startup-addr = <0x7E0000>;
/* ^ Adresse de demarrage du M7 (vue A53).
* 0x7E0000 = debut de l’ITCM vu depuis le A53.
* Le RemoteProc ecrira cette adresse dans le registre
* SRC_M4RCR (System Reset Controller) avant de liberer
* le reset du M7. */
fsl,rsc-table = <&rsc_table>;
/* ^ Pointe vers le node de la resource table en memoire.
* Le driver lit la resource table ICI plutot que depuis
* le fichier ELF (utile en mode early-boot). */
status = « okay »;
/* ^ Active le node. « disabled » pour ne pas demarrer le M7. */
};
Messaging Unit (MU)
/* La MU est deja definie dans le DTSI NXP (imx8mp.dtsi).
* Vous n’avez normalement pas a la redefinir, juste a la referencer.
*
* Definition dans imx8mp.dtsi : */
&mu {
status = « okay »;
};
/* La MU de l’i.MX8MP :
* – Adresse : 0x30AA0000 (MU_A cote A53) / 0x30AB0000 (MU_B cote M7)
* – 4 registres TX + 4 registres RX (32 bits chacun)
* – 4 General Purpose registers (doorbell)
* – Interruptions : GIC SPI 97 (A53), NVIC IRQ 27 (M7)
*
* Le MU_A et MU_B sont les deux faces du meme hardware :
* – Ce que A53 ecrit dans TX de MU_A, M7 le lit dans RX de MU_B
* – Et vice versa
*/
Desactiver les peripheriques assignes au M7
/* CRUCIAL : tout peripherique assigne au M7 via le RDC
* doit etre desactive dans le DTS Linux.
* Sinon, le driver Linux tentera de le configurer et recevra
* un bus fault (acces refuse par le RDC). */
/* Exemple : UART4 assigne au M7 */
&uart4 {
status = « disabled »;
/* Le driver serial imx ne sera pas charge pour UART4. */
};
/* Exemple : I2C3 assigne au M7 */
&i2c3 {
status = « disabled »;
};
/* Exemple : GPT1 (timer) assigne au M7 */
&gpt1 {
status = « disabled »;
};
/* Si vous oubliez de desactiver un peripherique,
* vous verrez dans dmesg :
* imx-uart 30a60000.serial: unable to map registers
* (ou un crash noyau si le bus fault n’est pas gere) */
UART4 (console M7)
Par convention NXP, UART4 est le port serie utilise comme console debug du Cortex-M7. Sur la carte EVK i.MX8MP, UART4 est connecte au connecteur debug USB-C (le meme que pour la console Linux, mais sur un port different).
Pourquoi UART4 ?
- U-Boot : U-Boot configure UART4 pour le M7 par defaut dans sa configuration RDC
- BSP NXP : tous les exemples firmware M7 de NXP utilisent UART4 comme console
- EVK : sur la carte EVK, UART4 est route vers le convertisseur USB-to-UART secondaire
- Isolation : UART1 est pour Linux (A53), UART4 est pour le M7 — pas de conflit
Configuration hardware UART4
| Parametre | Valeur |
| Adresse base | 0x30A60000 |
| IRQ | GIC SPI 29 (A53) / NVIC IRQ 29 (M7) |
| Horloge | UART4_CLK_ROOT (parent : SYS_PLL1_80M = 80 MHz) |
| Pins par defaut (EVK) | UART4_TXD = pad UART4_TXD, UART4_RXD = pad UART4_RXD |
| Baudrate par defaut | 115200 8N1 |
| FIFO | 32 octets TX + 32 octets RX |
| Index RDC | kRDC_Periph_UART4 (index 102) |
Configuration dans le firmware M7
/* Initialisation UART4 dans le firmware M7 (FreeRTOS / bare-metal) */
#include « fsl_uart.h »
#include « pin_mux.h »
#include « clock_config.h »
/* Pinmux UART4 (genere par MCUXpresso Config Tools) */
void BOARD_InitPins(void)
{
/* UART4_TXD */
IOMUXC_SetPinMux(IOMUXC_UART4_TXD_UART4_TX, 0U);
IOMUXC_SetPinConfig(IOMUXC_UART4_TXD_UART4_TX,
IOMUXC_SW_PAD_CTL_PAD_DSE(6U) | /* Drive Strength */
IOMUXC_SW_PAD_CTL_PAD_FSEL(2U)); /* Fast Slew Rate */
/* UART4_RXD */
IOMUXC_SetPinMux(IOMUXC_UART4_RXD_UART4_RX, 0U);
IOMUXC_SetPinConfig(IOMUXC_UART4_RXD_UART4_RX,
IOMUXC_SW_PAD_CTL_PAD_DSE(6U) |
IOMUXC_SW_PAD_CTL_PAD_FSEL(2U));
}
/* Init UART4 */
void BOARD_InitUart(void)
{
uart_config_t config;
UART_GetDefaultConfig(&config);
config.baudRate_Bps = 115200;
config.enableTx = true;
config.enableRx = true;
UART_Init(UART4, &config, BOARD_DebugConsoleSrcFreq());
}
/* Utilisation */
PRINTF(« Hello from Cortex-M7!\r\n »);
/* PRINTF redirige vers UART4 via le SDK NXP */
Tester la console M7
# Sur la machine hote, connecter un terminal serie sur le port M7 :
# Identifier le port (souvent le 2eme ttyUSB si la carte a 2 ports debug)
ls /dev/ttyUSB*
# /dev/ttyUSB0 (A53 Linux console)
# /dev/ttyUSB1 (M7 debug console – UART4)
# Se connecter
picocom -b 115200 /dev/ttyUSB1
# ou
minicom -D /dev/ttyUSB1 -b 115200
# Vous devriez voir les messages PRINTF du firmware M7
Vecteurs d’interruption
Le Cortex-M7 utilise le NVIC (Nested Vectored Interrupt Controller) pour gerer les interruptions. Le vecteur d’interruption est une table en memoire (toujours en ITCM a l’adresse 0x00000000) contenant les adresses des handlers.
Table des vecteurs (debut)
/* Table des vecteurs du Cortex-M7 — startup_MIMX8ML8_cm7.S */
__isr_vector:
.long __StackTop /* 0x00 : Initial Stack Pointer */
.long Reset_Handler /* 0x04 : Reset */
.long NMI_Handler /* 0x08 : NMI */
.long HardFault_Handler /* 0x0C : Hard Fault */
.long MemManage_Handler /* 0x10 : Memory Management */
.long BusFault_Handler /* 0x14 : Bus Fault */
.long UsageFault_Handler /* 0x18 : Usage Fault */
.long 0 /* 0x1C : Reserved */
.long 0 /* 0x20 : Reserved */
.long 0 /* 0x24 : Reserved */
.long 0 /* 0x28 : Reserved */
.long SVC_Handler /* 0x2C : SVCall (FreeRTOS) */
.long DebugMon_Handler /* 0x30 : Debug Monitor */
.long 0 /* 0x34 : Reserved */
.long PendSV_Handler /* 0x38 : PendSV (FreeRTOS) */
.long SysTick_Handler /* 0x3C : SysTick */
/* Interruptions specifiques i.MX8MP (IRQ 0 – 159) */
.long GPR_IRQ_IRQHandler /* IRQ 0 */
.long DAP_IRQHandler /* IRQ 1 */
.long SDMA1_IRQHandler /* IRQ 2 */
.long GPU3D_IRQHandler /* IRQ 3 */
/* … */
.long MU_A53_IRQHandler /* IRQ 27 : Messaging Unit */
/* … */
.long UART4_IRQHandler /* IRQ 29 : UART4 */
/* … */
.long GPT1_IRQHandler /* IRQ 55 : General Purpose Timer 1 */
/* … 160 entrees au total */
Configuration NVIC
/* Configurer les priorites d’interruption */
/* Le NVIC du M7 supporte 16 niveaux de priorite (4 bits).
* 0 = priorite la plus haute, 15 = la plus basse.
*
* Regles de priorite recommandees : */
/* Timer systeme FreeRTOS : priorite basse */
NVIC_SetPriority(SysTick_IRQn, 15);
/* Messaging Unit (RPMsg) : priorite moyenne */
NVIC_SetPriority(MU_A53_IRQn, 8);
NVIC_EnableIRQ(MU_A53_IRQn);
/* UART4 (debug console) : priorite basse */
NVIC_SetPriority(UART4_IRQn, 12);
NVIC_EnableIRQ(UART4_IRQn);
/* GPIO (capteur temps reel) : priorite haute */
NVIC_SetPriority(GPIO1_Combined_0_15_IRQn, 2);
NVIC_EnableIRQ(GPIO1_Combined_0_15_IRQn);
/* Timer hardware (controle moteur) : priorite la plus haute */
NVIC_SetPriority(GPT1_IRQn, 0);
NVIC_EnableIRQ(GPT1_IRQn);
/* IMPORTANT : FreeRTOS reserve les priorites les plus basses
* pour PendSV et SysTick. Ne jamais mettre une ISR applicative
* a une priorite >= configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
* si elle appelle des API FreeRTOS (xSemaphoreGiveFromISR, etc.) */
Latences d’interruption
| Scenario | Latence IRQ | Commentaire |
| Handler en ITCM, donnees en DTCM | 12 cycles (15 ns @ 800 MHz) | Meilleur cas : zero wait-state |
| Handler en OCRAM | ~20-30 cycles | 1-2 cycles de latence bus supplementaires |
| Handler en DDR (cache hit) | ~15-20 cycles | Quasi equivalent a ITCM si code en cache |
| Handler en DDR (cache miss) | 100+ cycles | Catastrophique pour le temps reel |
| Tail-chaining (IRQ suivante) | 6 cycles | Pas de push/pop stack entre deux IRQ consecutives |
💡
Tail-chaining : si une interruption arrive pendant qu’une autre se termine, le M7 ne restaure pas le contexte pour immediatement le re-sauvegarder. Il enchaine directement en 6 cycles. C’est un avantage majeur du NVIC pour le temps reel.
Troubleshooting
| Symptome | Cause probable | Solution |
| M7 ne demarre pas (bootaux) |
Firmware mal compile ou mauvaise adresse |
Verifier que le linker script cible 0x00000000 (ITCM). Verifier bootaux 0x7E0000 (adresse A53). |
| HardFault au demarrage |
Vecteur d’interruption mal place |
La section .isr_vector doit etre a l’adresse 0x00000000. Verifier le linker script. |
| Pas de sortie UART4 |
RDC non configure ou pinmux incorrect |
Verifier que UART4 est assigne au domaine M7 dans le RDC. Verifier le pinmux. |
| RPMsg timeout |
Vrings a la mauvaise adresse ou MU non active |
Verifier que les adresses dans rsc_table.c correspondent au DTS. Activer &mu { status = "okay"; }. |
| Donnees corrompues en shared memory |
Probleme de coherence cache |
Configurer la zone shared memory en non-cacheable dans la MPU du M7. Utiliser des memory barriers. |
| Bus fault quand Linux accede a un peripherique |
Peripherique assigne au M7 par le RDC |
Desactiver le peripherique dans le DTS Linux : status = "disabled" |
| RemoteProc: « firmware not found » |
Fichier ELF absent de /lib/firmware |
Copier le firmware dans /lib/firmware/. Le nom doit correspondre exactement. |
| Performance M7 bien en deca des attentes |
Code en DDR sans cache |
Activer I-Cache et D-Cache (SCB_EnableICache(), SCB_EnableDCache()). Mieux : placer le code en ITCM. |
| RemoteProc reset le firmware deja en cours |
M7 demarre par U-Boot mais RemoteProc ne le sait pas |
Ajouter fsl,auto-boot dans le node DTS du M7, ou ne pas activer RemoteProc si le M7 est gere par U-Boot. |
| Jitter eleve sur les ISR |
Contention bus AXI (DDR/OCRAM) |
Placer les handlers ISR critiques en ITCM et les donnees en DTCM. |
Ressources
Documentation NXP
- i.MX8M Plus Reference Manual : chapitres « Cortex-M7 Platform », « RDC », « MU », « Memory Map »
- i.MX8M Plus Applications Processor Datasheet : specifications electriques, pinout
- AN13264 : « How to use Cortex-M7 with i.MX8M Plus » — guide officiel NXP
- AN12570 : « Loading Code on Cortex-M core from Linux » — RemoteProc
SDK et exemples
- MCUXpresso SDK pour i.MX8MP : exemples FreeRTOS, RPMsg-Lite, peripheriques
- MCUXpresso Config Tools : generateur de pinmux, clocks, MPU
- Exemples RPMsg :
SDK/boards/evkmimx8mp/multicore_examples/rpmsg_lite_*
Kernel Linux
- drivers/remoteproc/imx_rproc.c : driver RemoteProc pour i.MX
- Documentation/devicetree/bindings/remoteproc/fsl,imx-rproc.yaml : schema DTS
- drivers/rpmsg/ : drivers RPMsg (virtio, char, tty)
Communaute
- community.nxp.com : forum NXP, categorie « i.MX Processors »
- github.com/nxp-mcuxpresso : SDK et exemples
- github.com/OpenAMP : projet open-source RPMsg / RemoteProc
i.MX8M Plus →
Presentation complete du SoC : CPU, GPU, NPU, DSP, video, connectivite.
Audio →
SAI, DSP HiFi4, codec TAC5212, multivoie TDM, SOF, pipelines temps reel.
Yocto BSP →
Build, paquets NXP, images, layers, customisation, deploiement.
U-Boot →
Bootloader, HAB, boot securise, configuration memoire, bootaux.
Besoin d’expertise Cortex-M7 sur i.MX8MP ?
Integration M7/A53, RPMsg, shared memory, firmware temps reel, RDC, RemoteProc — c’est mon activite principale. Parlons de votre projet.
Discuter de votre projet embarque →