17.2 NVIC konfiguráció: engedélyezés és prioritás

17.2.1 Motiváció: két kapu, egy döntéshozó

Tegyük fel, hogy egy 1 kHz-es periodikus timer megszakítására épülő háttérfeladatot és egy UART RX megszakítást kell üzemeltetnünk ugyanazon a Cortex-M3-on. A periféria már konfigurált, a vektortábla megfelelő bejegyzése a kezelőre mutat (lásd Reset és boot-folyamat: vektortábla), a globális megszakítás-maszk nyitva van — mégsem történik semmi. A hiba oka szinte mindig ugyanaz: a periféria kér, de a kérést az NVIC (Nested Vectored Interrupt Controller) még nem engedélyezte, vagy a prioritás úgy lett beállítva, hogy a kérés örökre várakozik.

Ez a helyzet tipikus mérnöki feladatot ad: két független kaput kell összehangolni. Az egyik kapu a periféria saját megszakítás-engedélyező bitje (ez a periféria-vezérlő regisztereiben van), a másik az NVIC engedélyező bitje. A kettő közül az NVIC az, amelyet assembly szinten, közvetlen regiszterírással ismerni kell: hibakeresésnél nem elég egy CMSIS-függvényt hívni, a regiszter visszaolvasásával kell bizonyítani, hogy az engedélyezés és a prioritás tényleg beállt. Ráadásul a prioritás beállítása bitmező-művelet egy 32 bites regiszterben — pontosan az a minta, amelyet AArch64-en a BFI/UBFX kapcsán már láttunk (Bitfield-utasítások: BFI/UBFX/SBFX és csomagolt adatszerkezetek).

17.2.2 Definíció: az NVIC és a System Control Space

Definíció (NVIC-engedélyezés és prioritás). Egy külső IRQ akkor válik kiszolgálhatóvá, ha (a) az NVIC ISER regiszterének megfelelő bitje 1, (b) a globális maszk (PRIMASK) nyitva van, és (c) az adott kivétel prioritása nem alacsonyabb egy éppen futó, magasabb rendű kivételénél. A prioritásértéket az IPR megfelelő bájtja tárolja; kisebb szám magasabb rendet jelent (lásd A Cortex-M kivételmodell: prioritás és vektortábla-bejegyzések).

Az NVIC az Armv7-M architektúra része, és fix, architekturálisan rögzített címeken érhető el a System Control Space-ben (SCS). Az itt használt összes utasítás Thumb-2/Armv7-M ISA (a könyv II. része); az I. részben tanult A64 utasítások itt nem érvényesek, a két ISA egyetlen példában sem keverhető.

Regiszter Szó-cím Indexelés 1 írásának hatása Visszaolvasáskor
NVIC_ISER0–7 0xE000E100 + 4·i IRQ 32i … 32i+31 engedélyez az engedélyezettség
NVIC_ICER0–7 0xE000E180 + 4·i ugyanaz letilt az engedélyezettség (ugyanaz, mint az ISER)
NVIC_ISPR0–7 0xE000E200 + 4·i ugyanaz pending beállít az aktuális pending állapot
NVIC_ICPR0–7 0xE000E280 + 4·i ugyanaz pending töröl az aktuális pending állapot (ugyanaz, mint az ISPR)
NVIC_IABR0–7 0xE000E300 + 4·i ugyanaz nincs hatása aktív állapot
NVIC_IPR bájtok 0xE000E400 + n IRQ n prioritásbájt prioritásbájt

A táblázat két szabálya a gyakorlatban a legfontosabb. Egyrészt az NVIC regiszterei írás-1-hatásúak: a 0 írása nem töröl semmit, ezért ISER = (1 << b) és nem ISER &= ~(1 << b) alakban használjuk őket. Másrészt a párok ugyanazt az állapotot olvassák: az ISER és az ICER az engedélyezettséget, az ISPR és az ICPR az aktuális pending állapotot. Az ICPR tehát nem „mindig 0-t” ad, hanem 1-et mutat minden olyan biten, amely éppen függőben van. Emiatt a |= és &= operátorok ezeken a regisztereken félrevezetők: egy ICPR |= m a már függő összes megszakítás pending bitjét is visszaírná, és ezzel törölné. Az ICER 1-es írása csak tilt, a pending bitet nem törli; arra az ICPR való.

17.2.3 Engedélyezés: ISER és ICER bitenként

Az engedélyezés bitkiosztása egyszerű, mert az ISER/ICER bitek IRQ-szám szerint indexeltek:

regiszterindex=⌊IRQn32⌋,bit=IRQnmod32,maszk=1≪(IRQnmod32)\text{regiszterindex} = \left\lfloor \frac{IRQn}{32} \right\rfloor, \qquad \text{bit} = IRQn \bmod 32, \qquad \text{maszk} = 1 \ll (IRQn \bmod 32)

A str.w r3, [r2, r1, lsl #2] alakú regiszter-offszetes tárolás pontosan ezt a 4 · i bájt-offszetet állítja elő, külön szorzás nélkül. Fontos következmény: a IRQn = 33 esetén a regiszter az ISER1 (0xE000E104), a bit pedig az 1-es — nem a 33-as, mert a 32 bites szóhatáron túl új regiszter kezdődik.

Ugyanez a séma érvényes az ISPR-re (pending beállítás) és az ICPR-re (pending törlés). Az ISPR különösen hasznos hibakeresésnél: egy periféria nélkül, tisztán szoftverből pendíthetjük a megszakítást, és ellenőrizhetjük, hogy a kezelő elindul-e. Az IABR ezzel szemben csak olvasható: megmutatja, hogy az adott kivétel éppen aktív (fut vagy futott, és még nem fejeződött be) — ez reentrancia-vizsgálatnál és a kezelő „beszorulásának” kimutatásánál hasznos.

17.2.4 Prioritás: IPR és a bitmező-elhelyezés

A prioritásbájtok a 0xE000E400-tól induló, 240 bájtos tömbben vannak, és itt egy másik indexelés él, mint a vektortáblában: a prioritásmező bájtindexe az IRQ-szám, nem a kivételszám. Ennek oka, hogy a konfigurálható rendszerkivételek (MemManage, BusFault, UsageFault, SVCall, Debug Monitor, PendSV és SysTick) prioritása nem itt, hanem az SCB SHPR1–3 regisztereiben (0xE000ED18–0xE000ED20) található. A Reset (−3), az NMI (−2) és a HardFault (−1) prioritása fix: ezeknek nincs írható SHPR-mezőjük, és nem is vezethető le rájuk beírandó prioritásérték (kivételmodell). Az NVIC_SetPriority(SysTick_IRQn, …) CMSIS-hívás ezért nem is az IPR-t írja, hanem az SHPR-t — a látszat ellenére a két útvonal nem ugyanaz.

Egy IRQ prioritásmezőjének elhelyezése levezetve:

  1. szó-index: w=⌊IRQn/4⌋w = \lfloor IRQn / 4 \rfloor, mert egy 32 bites IPR szó négy prioritásmezőt tartalmaz;
  2. szó címe: 0xE000E400 + 4·w;
  3. bájt-sáv a szón belül: l=IRQnmod4l = IRQn \bmod 4;
  4. bit-offszet a szón belül: 8·l8·l, azaz rendre 0, 8, 16 vagy 24.

Például IRQ 33 esetén w=8w = 8, a szó címe 0xE000E420, l=1l = 1, tehát a mező a szó 15:8 bitjein van, a bájt címe pedig 0xE000E421.

A művelet maga tipikus read–modify–write bitmező-illesztés: maszkkal töröljük a régi mezőt, majd a 8 bites új értéket a helyére tolva OR-ral összefésüljük. A a034-ben tanult BFI itt nem használható közvetlenül, mert a BFI az lsb és a szélesség operandusát is immediate-ként várja; futásidőben számolt mezőpozícióhoz a maszk+shift minta marad. Ha viszont az IRQ-szám fordítási időben ismert (például egy fix, beégetett konfigurációnál), a fordító jellemzően magától bfi/ubfx szekvenciát állít elő — ezt a mintát a Tipikus fordítói mintázatok felismerése tárgyalja.

Két gyakorlati buktató tartozik ide. Először: a prioritásmező nem azonos a logikai prioritásszámmal. A kivételmodell szerint négy dolgot kell külön kezelni: a nyers prioritásbájtot, a megvalósított bitek számát (CMSIS-ben __NVIC_PRIO_BITS, a könyv laborjában LAB_NVIC_PRIO_BITS), az ebből adódó logikai prioritást, és az AIRCR.PRIGROUP szerinti csoport- és alprioritást. A CMSIS NVIC_SetPriority() a kapott logikai értéket balra tolja (8−__NVIC_PRIO_BITS)(8 - \text{\_\_NVIC\_PRIO\_BITS}) bittel. Ha mi nyers bájtot írunk, a hívó felelőssége a shiftelés. Másodszor: a nem implementált alsó bitek olvasáskor 0-t adnak, ezért a 0x01 és 0x07 ugyanazt a prioritást jelentheti egy 3 bitet megvalósító Cortex-M3-on. Ezt nem lehet kitalálni, csak megmérni: írjunk 0xFF-et egy szabad IRQ prioritásmezőjébe, olvassuk vissza, és a visszakapott minta megmutatja a megvalósított biteket.

17.2.5 A konfiguráció helyes sorrendje

A megszakítás-vezérlő beállítása nem tetszőleges sorrendben biztonságos. A követendő minta:

  1. a periféria megszakítás-kérése legyen letiltva a periféria-vezérlőben;
  2. nvic_disable_irq(n) — tisztítsuk a korábbi állapotot;
  3. nvic_clear_pending(n) — ha a periféria már pendített, a bit törlése megakadályozza a „száguldó” kezelőt;
  4. nvic_set_priority(n, prio);
  5. nvic_enable_irq(n);
  6. a globális maszk feloldása (cpsie i, PRIMASK) — ez külön mechanizmus, amelyet a ISR írása: C és assembly vegyesen és a Megszakítás-latencia mérése és korlátai tárgyal részletesen;
  7. a periféria saját megszakítás-engedélyezése.

A 3. lépés elhagyása a leggyakoribb „első próbálkozás” hiba: ha az engedélyezés előtt egy pending bit már be van állítva, a kezelő azonnal lefut — sokszor még azelőtt, hogy a szoftveres állapot, amire támaszkodik, inicializálódott volna.

17.2.6 Kidolgozott példa: egy timer megszakítás engedélyezése és prioritása

Munkakönyvtár: ~/arm-labor/cm/a069/ — az alfejezet minden laborfájlja ide kerül, és minden parancs innen futtatandó (a könyvtárrendet a Labor-alapkörnyezet alfejezet írja le).

mkdir -p ~/arm-labor/cm/a069
cd ~/arm-labor/cm/a069

A példa a fejezet laborkörnyezetének CMSDK APB timerét használja mps2-an385 alatt. A periféria báziscíme és az IRQ-száma a QEMU machine dokumentált memóriatérképéből ellenőrizendő, ezért a kód szimbolikus konstansokat használ; az NVIC-címek architekturálisan rögzítettek.

    .syntax unified
    .cpu    cortex-m3
    .thumb

    .equ    NVIC_ISER, 0xE000E100
    .equ    NVIC_ICER, 0xE000E180
    .equ    NVIC_ISPR, 0xE000E200
    .equ    NVIC_ICPR, 0xE000E280
    .equ    NVIC_IABR, 0xE000E300
    .equ    NVIC_IPR,  0xE000E400

    .text
    .thumb_func
    .global nvic_enable_irq
    @ r0 = IRQn  (0..239)
nvic_enable_irq:
    ubfx    r1, r0, #5, #3              @ r1 = IRQn / 32   -> ISER index
    ubfx    r2, r0, #0, #5              @ r2 = IRQn % 32
    movs    r3, #1
    lsls    r3, r3, r2                  @ maszk = 1 << (IRQn % 32)
    movw    r2, #(NVIC_ISER & 0xFFFF)
    movt    r2, #(NVIC_ISER >> 16)
    str.w   r3, [r2, r1, lsl #2]        @ ISER[IRQn/32] = maszk
    dsb
    isb
    bx      lr

    .thumb_func
    .global nvic_disable_irq
    @ r0 = IRQn ; ugyanaz a minta, csak NVIC_ICER bázissal
nvic_disable_irq:
    ubfx    r1, r0, #5, #3
    ubfx    r2, r0, #0, #5
    movs    r3, #1
    lsls    r3, r3, r2
    movw    r2, #(NVIC_ICER & 0xFFFF)
    movt    r2, #(NVIC_ICER >> 16)
    str.w   r3, [r2, r1, lsl #2]
    dsb
    isb
    bx      lr

    .thumb_func
    .global nvic_set_priority
    @ r0 = IRQn, r1 = prioritásbájt (nyers IPR-bájt, nem logikai prioritásszám)
nvic_set_priority:
    uxtb    r1, r1                      @ csak a 8 bites mező érdekes
    ubfx    r2, r0, #0, #2              @ r2 = IRQn % 4   -> bájt-sáv
    lsls    r2, r2, #3                  @ bit-offszet = 8 * (IRQn % 4)
    bic     r0, r0, #3                  @ r0 = IRQn & ~3  -> szó-offszet
    movw    r3, #(NVIC_IPR & 0xFFFF)
    movt    r3, #(NVIC_IPR >> 16)
    add     r3, r3, r0                  @ r3 = IPR szó címe
    ldr     r0, [r3]                    @ régi szó
    movs    r12, #0xFF
    lsls    r12, r12, r2                @ maszk = 0xFF << bit-offszet
    bic     r0, r0, r12                 @ mező törlése
    lsls    r12, r1, r2                 @ új érték a helyére tolva
    orrs    r0, r0, r12                 @ összefésülés
    str     r0, [r3]                    @ visszaírás
    dsb
    isb
    bx      lr

A C oldali megfelelő és a teljes inicializálási sorrend:

#include <stdint.h>

#define NVIC_ISER(i)  (*(volatile uint32_t *)(0xE000E100u + 4u * (i)))
#define NVIC_ICER(i)  (*(volatile uint32_t *)(0xE000E180u + 4u * (i)))
#define NVIC_ICPR(i)  (*(volatile uint32_t *)(0xE000E280u + 4u * (i)))
#define NVIC_IABR(i)  (*(volatile const uint32_t *)(0xE000E300u + 4u * (i)))
#define NVIC_IPR_BYTE(irq) (*(volatile uint8_t *)(0xE000E400u + (uint32_t)(irq)))

#define IRQ_TIMER0  8u   /* MPS2-AN385 Timer0 */

static void nvic_enable_irq(unsigned irq)  { NVIC_ISER(irq / 32u) = 1u << (irq % 32u); }
static void nvic_disable_irq(unsigned irq) { NVIC_ICER(irq / 32u) = 1u << (irq % 32u); }
static void nvic_clear_pending(unsigned irq){ NVIC_ICPR(irq / 32u) = 1u << (irq % 32u); }
static void nvic_set_priority(unsigned irq, uint8_t raw_prio)
{
    NVIC_IPR_BYTE(irq) = raw_prio;      /* byte-index = IRQ-szám, nem kivételszám! */
}

void timer0_nvic_init(void)
{
    nvic_disable_irq(IRQ_TIMER0);
    nvic_clear_pending(IRQ_TIMER0);          /* maradék pending törlése  */
    nvic_set_priority(IRQ_TIMER0, 0xC0u);    /* nyers 0xC0: 3 bitnél logikai 6, 8 bitnél 192 */
    nvic_enable_irq(IRQ_TIMER0);             /* ISER bit beállítása      */
    __asm volatile ("dsb" ::: "memory");
    __asm volatile ("isb" ::: "memory");
    /* a globális maszk (PRIMASK) és a periféria engedélyezése külön lépés */
}

17.2.7 Várható állapot és ellenőrzés

Minden várt cím és bit ugyanabból a szimbolikus IRQ-ból (IRQ_TIMER0 = 8) származik. Tiszta, csak ezt az IRQ-t engedélyező állapotban:

Ellenőrzés Cím / művelet Képlet Várt érték (IRQ_TIMER0 = 8)
engedélyezettség x/1xw 0xE000E100 ISER[8 / 32], bit 8 % 32 0x00000100
prioritás bájt x/1xb 0xE000E408 0xE000E400 + 8 0xC0 (a megvalósított felső bitek szerint maszkolva)
prioritás szó x/1xw 0xE000E408 0xE000E400 + 4·(8 / 4), sáv 8 % 4 = 0 alsó bájt 0xC0; a többi bájt a 9–11. IRQ prioritása
pending x/1xw 0xE000E280 ICPR[0], bit 8 a bit az aktuális pending állapot: 0, ha nincs függő kérés
aktív állapot x/1xw 0xE000E300 IABR[0], bit 8 0, amíg a kezelő nem fut

A pending regiszterek viselkedését egy rövid teszt igazolja, amely a megszakítást közben tiltva tartja, így semmi nem fut le: (1) tiltás és pending törlés; (2) ISPR-rel szoftveres pending; (3) visszaolvasás az ISPR-ből és az ICPR-ből (mindkettő 1-et mutat a biten); (4) törlés ICPR-be írt 1-gyel; (5) újabb visszaolvasás (0). A teszt ugyanebben a futásban a megvalósított prioritásbiteket is megméri: 0xFF-et ír a prioritásbájtba, és visszaolvassa. A kimenet semihostingon megy, a startup és a linker script a kanonikus pár (a065, a066):

/* lab-a069-test.c — NVIC-állapot ellenőrzése egyetlen szimbolikus IRQ-ból.
 * Minden cím és bit a LAB_IRQ-ból származik; a kimenet semihostingon megy. */
#include <stdint.h>

#define IRQ_TIMER0  8u                   /* MPS2-AN385 Timer0 */
#define LAB_IRQ     IRQ_TIMER0

#define NVIC_ISER(i)  (*(volatile uint32_t *)(0xE000E100u + 4u * (i)))
#define NVIC_ICER(i)  (*(volatile uint32_t *)(0xE000E180u + 4u * (i)))
#define NVIC_ISPR(i)  (*(volatile uint32_t *)(0xE000E200u + 4u * (i)))
#define NVIC_ICPR(i)  (*(volatile uint32_t *)(0xE000E280u + 4u * (i)))
#define NVIC_IPR_ADDR(irq)  (0xE000E400u + (uint32_t)(irq))
#define NVIC_IPR_BYTE(irq)  (*(volatile uint8_t *)NVIC_IPR_ADDR(irq))

#define IRQ_WORD(irq)  ((irq) / 32u)
#define IRQ_MASK(irq)  (1u << ((irq) % 32u))

static void sh_call(uint32_t op, const void *arg)
{
    register uint32_t r0 asm("r0") = op;
    register const void *r1 asm("r1") = arg;
    asm volatile ("bkpt 0xab" : "+r"(r0) : "r"(r1) : "memory");
}

static void put_hex(const char *label, uint32_t v)
{
    char buf[48];
    char *p = buf;
    while (*label) *p++ = *label++;
    *p++ = '0'; *p++ = 'x';
    for (int sh = 28; sh >= 0; sh -= 4) *p++ = "0123456789abcdef"[(v >> sh) & 0xFu];
    *p++ = '\n'; *p = '\0';
    sh_call(0x04u, buf);                                  /* SYS_WRITE0 */
}

static int fails;
static void expect(const char *label, uint32_t got, uint32_t want)
{
    put_hex(label, got);
    if (got != want) { fails++; put_hex("  HIBA, várt: ", want); }
}

void lab_nvic_done(void) { }    /* GDB-töréspont: a konfiguráció itt már lefutott */

int main(void)
{
    const uint32_t w = IRQ_WORD(LAB_IRQ), m = IRQ_MASK(LAB_IRQ);

    __asm volatile ("cpsid i" ::: "memory");            /* a teszt alatt nincs kiszolgálás */
    put_hex("IRQ maszk:          ", m);
    put_hex("IPR bájt címe:      ", NVIC_IPR_ADDR(LAB_IRQ));

    /* 1. kiindulás: tiltva, pending törölve */
    NVIC_ICER(w) = m;
    NVIC_ICPR(w) = m;
    expect("ISER (tiltva):      ", NVIC_ISER(w) & m, 0u);

    /* 2. tiltott IRQ-n szoftveres pending: az ISPR és az ICPR is a pending állapotot olvassa */
    NVIC_ISPR(w) = m;
    expect("ISPR a beállítás után: ", NVIC_ISPR(w) & m, m);
    expect("ICPR a beállítás után: ", NVIC_ICPR(w) & m, m);

    /* 3. törlés write-1-to-clear módon, majd újabb visszaolvasás */
    NVIC_ICPR(w) = m;
    expect("ICPR a törlés után:    ", NVIC_ICPR(w) & m, 0u);

    /* 4. megvalósított prioritásbitek: 0xFF írása, visszaolvasás */
    NVIC_IPR_BYTE(LAB_IRQ) = 0xFFu;
    put_hex("IPR 0xFF visszaolvasva: ", NVIC_IPR_BYTE(LAB_IRQ));

    /* 5. végleges konfiguráció: prioritás, majd engedélyezés */
    NVIC_IPR_BYTE(LAB_IRQ) = 0xC0u;
    NVIC_ISER(w) = m;
    __asm volatile ("dsb\n isb" ::: "memory");
    expect("ISER (engedélyezve): ", NVIC_ISER(w), m);
    expect("IPR bájt:            ", NVIC_IPR_BYTE(LAB_IRQ), 0xC0u);

    lab_nvic_done();
    sh_call(0x04u, fails ? "nvic FAIL\n" : "nvic ok\n");
    sh_call(0x18u, (const void *)(fails ? 0x20023u : 0x20026u));   /* SYS_EXIT */
    for (;;) { }
}
cp ~/arm-labor/cm/a065/lab-mps2.ld ~/arm-labor/cm/a066/lab-startup.s .
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -ffreestanding -nostdlib -O1 -g \
    -T lab-mps2.ld -o lab-a069.elf lab-startup.s lab-a069-test.c
qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -nographic -semihosting \
    -kernel lab-a069.elf; echo "kilépési állapot: $?"

Kimenet a helyettesítő eszközlánccal (Clang + ld.lld) és a tools/cm-run.py emulátorral, amely az NVIC regisztereit az Armv7-M leírás szerint modellezi (a teszt a megszakítást végig tiltva tartja, ezért kézbesítés itt nem történik), --prio-bits 3 beállítással:

IRQ maszk:          0x00000100
IPR bájt címe:      0xe000e408
ISER (tiltva):      0x00000000
ISPR a beállítás után: 0x00000100
ICPR a beállítás után: 0x00000100
ICPR a törlés után:    0x00000000
IPR 0xFF visszaolvasva: 0x000000e0
ISER (engedélyezve): 0x00000100
IPR bájt:            0x000000c0
nvic ok
kilépési állapot: 0

Az IPR 0xFF visszaolvasva sor az egyetlen, amely a futtató környezettől függ: 3 megvalósított bitnél 0xe0, 8 bitnél 0xff (a modellt --prio-bits 8-cal futtatva az utóbbi jött ki, minden más sor változatlan maradt). A QEMU stable-8.2 mps2-an385 NVIC-modellje Armv7-M-en 8 bitet valósít meg, ott 0xff várható; a fenti, 0xe0-t mutató kimenet a 3 bites helyettesítő modellből származik. A sor mutatja meg, hány prioritásbitet valósít meg a futtatókörnyezet. Ebből kell a LAB_NVIC_PRIO_BITS-et rögzíteni: a megvalósított bitek száma a visszaolvasott maszk egyeseinek száma, a legalacsonyabb konfigurálható prioritás pedig maga a maszk (0xe0, illetve 0xff). A 0xC0 nyers érték mindkét esetben érvényes, de csak a 3 bites esetben jelenti a második legalacsonyabb szintet. A várt kimenetet tehát mindig a saját futtatás visszaolvasásából, nem egy másik eszköz adataiból kell levezetni.

GDB-vel az állapotot csak a konfiguráció lefutása után érdemes olvasni. A -S kapcsolóval indított QEMU a reset után, az első utasítás előtt áll meg; ekkor az NVIC még az indulás előtti állapotot mutatja, engedélyezett bitet még nem láthatunk. A system emulátor GDB-szerverét a -gdb tcp::PORT kapcsoló nyitja meg; a II. rész egységesen a -S -gdb tcp::3333 alakot használja (a -s a -gdb tcp::1234 rövidítése, az is működik, de akkor a GDB-ben is az 1234-es port kell). A qemu-user -g 1234 kapcsolója a system emulátorba nem vehető át, ezért itt nem használható:

qemu-system-arm -machine mps2-an385 -cpu cortex-m3 -nographic -semihosting \
    -kernel lab-a069.elf -S -gdb tcp::3333
# másik terminálban:
gdb-multiarch lab-a069.elf -ex "target remote :3333" \
    -ex "break lab_nvic_done" -ex "continue" \
    -ex "x/1xw 0xE000E100" -ex "x/1xb 0xE000E408" -ex "x/1xw 0xE000E280"

A lab_nvic_done töréspontnál a konfiguráció már lefutott, ezért ott várható a fenti táblázat állapota. Ha a gdbstub hibát ad az SCS-címek olvasásakor, a firmware saját visszaolvasása (a teszt kimenete) a bizonyíték.

17.2.8 Tipikus hibák és ellenpróbák

17.2.9 Feladatok

  1. (magyarázó) Adott IRQ 33. Határozza meg az ISER regisztert és bitet, a maszkot, valamint az IPR szó címét, bájt-sávját és bit-offszetjét. Indokolja, miért nem a kivételszámot használjuk az IPR indexelésénél.
  2. (implementációs) Írjon nvic_get_priority(irq) függvényt Thumb-2 assemblyben, amely visszaadja egy IRQ prioritásbájtját. Készítsen olyan változatot is, amely szóhosszú olvasást és maszkolást használ a bájtolvasás helyett.
  3. (hibakeresési/mérési) Állítson breakpoint-ot a timer kezelő első utasítására, és GDB-vel olvassa ki az IABR-t a kezelő futása közben, majd a kezelő végén. Magyarázza a különbséget, és mérje meg, mennyi idő telik el az ISPR-alapú pendítés és a kezelő első utasítása között (ciklusszámítással vagy SysTick-alapú időméréssel).

17.2.10 Megoldások

1. exc = IRQ 33 + 16 = 49, de az IPR-t az IRQ-szám indexeli: w = 33 / 4 = 8 → szó cím 0xE000E400 + 32 = 0xE000E420; l = 33 mod 4 = 1 → bit-offszet 8, azaz a szó 15:8 bitjei; bájt cím 0xE000E421. Az ISER-nél i = 33 / 32 = 1 → 0xE000E104, bit 33 mod 32 = 1 → maszk 0x00000002. Azért az IRQ-szám az index, mert a rendszer-kivételek prioritása az SCB SHPR1–3-ban van; az IPR tömb csak a külső IRQ-kat fedi le.

2. A bájtolvasó változat kihasználja, hogy a bájtcím éppen 0xE000E400 + IRQn:

    .thumb_func
    .global nvic_get_priority
nvic_get_priority:
    movw    r1, #(NVIC_IPR & 0xFFFF)
    movt    r1, #(NVIC_IPR >> 16)
    add     r1, r1, r0              @ r1 = 0xE000E400 + IRQn
    ldrb    r0, [r1]                @ a prioritásbájt
    bx      lr

A szóhosszú változat: w = IRQn & ~3 címről ldr, majd lsrs r0, r0, r2 a r2 = 8·(IRQn mod 4) shiftel, végül and r0, r0, #0xFF (a 0xFF modified immediate-ként kódolható). A szóhosszú változat akkor előnyös, ha az implementáció a bájt-elérést megszorítja; a bájtváltozat viszont pontosan azt a CMSIS-mintát tükrözi, amelyet a valós kódbázisok használnak.

3. Az IABR a kezelő futása alatt 1-est mutat a kérdéses biten (a kivétel aktív), a kezelő visszatérése után 0-t. Ez azért hasznos, mert megkülönbözteti a „pending, de még nem futott” és az „aktív, éppen futó” állapotot. A latenciamérésnél az ISPR-be írt pendítés és a kezelő első utasítása közötti ciklusszám a belépési latencia felső korlátját adja; mivel QEMU-emulációban nincs hardverhű ciklusszám, az eredmény csak relatív/illusztratív, és ezt a mérési jegyzőkönyvben jelölni kell.

17.2.11 Rövid összefoglaló

Az NVIC engedélyezését az ISER/ICER végzi IRQ-szám szerinti bitenkénti indexeléssel, a prioritást pedig a 0xE000E400-tól induló bájttömb tárolja, ahol a bájtindex az IRQ-szám (nem a kivételszám), a mezőpozíció pedig 8⋅(IRQnmod4)8 \cdot (IRQn \bmod 4). Az ISPR/ICPR a pending állapotot állítja és mindkettő vissza is olvassa, az IABR az aktív állapotot kérdezi le. A biztonságos sorrend: letiltás, pending törlés, prioritás, engedélyezés, majd a globális maszk és a periféria engedélyezése.

Továbbhasznált definíciók: - írás-1-hatású regiszter: olyan NVIC-regiszter, amelyben az 1-es írás műveletet vált ki, a 0 írása pedig hatástalan. - prioritásbájt: az IPR tömb 0xE000E400 + IRQn címén lévő 8 bites mező, amelynek kisebb értéke magasabb rendet jelent. - aktív állapot: az IABR által mutatott feltétel, amely szerint az adott kivétel éppen kiszolgálás alatt áll.

17.2.12 Források és ellenőrzési hiányok

Az alábbiak dokumentációs kiindulópontok (forrásállapot: nem megnyitott, lásd az Olvasói útmutató jelöléseit):

Ellenőrzési hiányok: (0) a lab-a069-test.c tesztet és a könyv assembly NVIC-rutinjait (nvic_enable_irq, nvic_disable_irq, nvic_set_priority) a helyettesítő eszközlánccal fordítottuk, és a tools/cm-run.py NVIC-regisztermodelljén futtattuk. Ez a modell az Armv7-M regiszterszemantikát követi (ISER/ICER az engedélyezettséget, ISPR/ICPR a pending állapotot olvassa), de nem a QEMU NVIC-je; a teszt a megszakítást tiltva tartja. A QEMU alatti futtatás és a GDB-lépések a laborban ellenőrizendők (a közös táblázatot lásd az a061 forrásjegyzetében). (1) a CMSDK APB timer báziscíme és pontos IRQ-száma az mps2-an385 machine-en; (2) a mps2-an385 (QEMU) és a fizikai MPS2/STM32 által megvalósított prioritásbitek száma — ezt a fenti visszaolvasásos próbával kell meghatározni; (3) a byte/halfword elérés támogatottsága az egyes NVIC-regisztereknél; (4) a dsb/isb kötelező volta az NVIC-engedélyezés után. Mindegyik pont a hallgató laborjában, az adott toolchain-verzióval ellenőrizendő.