Az előző alfejezetben (GPIO közvetlen vezérlése: bitbanging) nyers memóriacímekkel és hexadecimális offsetekkel vezéreltük a GPIO-t. Ez a gép szintjén pontos, de a forráskód szintjén törékeny: egy nagyobb firmware-ben több tíz perifériaregiszter varázsszámként való karbantartása átláthatatlan és hibára hajlamos. Ha egy új perifériát kell kezelni, vagy a regiszterkészlet bővül, a nyers címek sora olvashatatlanná válik.
A mérnöki megoldás a periféria-struktúra: a
regisztereket egy C struct mezőiként írjuk le, a struktúra
kezdetét egy fix báziscímre illesztjük, és a továbbiakban nevesített
mezőkkel érünk el mindent. Ez az absztrakció nem változtatja meg a
generált gépi kódot – ugyanaz a báziscím+offset minta marad –, de a
forráskód olvashatóvá, típusellenőrzötté és statikusan verifikálhatóvá
válik. Ebben az alfejezetben felépítjük ezt az absztrakciót, és
megmutatjuk az assembly-szintű megfelelőjét.
Periféria-struktúra (peripheral register struct): olyan C struktúratípus, amelynek mezői egy periféria regisztereit reprezentálják, sorrendben és méretben a hardver regisztertérképének megfelelően, és amelyet egy fordítási idejű konstans báziscímre castolunk.
A definíció három pillére:
Mezők mint regiszterek. Minden mező egy
regisztert jelöl. A mező típusa a regiszter szélességét tükrözze: 32
bites regiszterhez uint32_t, 16 biteshez
uint16_t, 8 biteshez uint8_t. A gyakorlatban a
legtöbb perifériaregiszter 32 bites, ezért a uint32_t a
leggyakoribb választás.
Sorrend és offset. A struktúra mezőinek sorrendje adja a regiszterek offset-sorrendjét. Az első mező a báziscím + 0, a második a báziscím + az első mező mérete (igazítási kitöltéssel), és így tovább. Ez pontosan a Struktúrák, igazítás és mezőelérés alfejezetben tanult mezőoffset-számítás: mezőcím = báziscím + mezőoffset.
Fix báziscímre castolás. A struktúra típusát egy
#define makróval kötjük egy abszolút címhez, például
((cmsdk_gpio_t *)0x40010000UL). Az egész számból pointerré
castolás C-ben implementációdefiniált, de a beágyazott programozás
bevett mintája: a memóriába leképzett regisztereket pontosan így érünk
el.
A assembly-szintű megfelelő változatlan. A
GPIO0->OUTENSET = érték; C kifejezésből a fordító
STR utasítást készít, ahol a bázisregiszter a
0x40010000, az offset pedig a mező pozíciója. A
a024-ben tanult összefüggés tehát közvetlenül érvényes: a
struktúra-alapú elérés ugyanazt a báziscím+offset mintázatot adja, mint
a nyers című változat.
A perifériaregiszterek elérése volatile (illékony)
minősítést igényel. A volatile kulcsszó azt mondja a
fordítónak, hogy az adott objektum értéke a program akaratán kívül is
megváltozhat, ezért:
E nélkül egy -O2 szinten optimalizáló fordító a
látszólag hatástalan írásokat eltávolíthatja, vagy a read-modify-write
sorozatot átrendezheti. A volatile azonban nem biztosít
atomitást és nem zár ki versenyhelyzetet: a
GPIO0->DATAOUT |= (1u << pin); továbbra is három
gépi lépés (olvas, módosít, ír), amely megszakítás közben megszakítható.
A GPIO közvetlen vezérlése: bitbanging
RMW-mintája itt is érvényes.
A volatile-t két szinten lehet elhelyezni:
volatile uint32_t; ekkor maga a struktúratípus hordozza a
tulajdonságot, és a pointer nem kell volatile legyen;volatile cmsdk_gpio_t *;
ekkor a mutatott objektum minden elérése volatile.Mindkettő helyes. A mezőnkénti változat előnye, hogy a típus önmagában dokumentálja a szándékot, és később a pointer típusát nem kell módosítani. Hátránya, hogy minden meződeklarációt el kell látni a kulcsszóval. A könyv a mezőnkénti változatot használja.
Előny: olvashatóság és karbantarthatóság. A
GPIO0->OUTENSET név önmagát dokumentálja; a
0x40010010 cím nem. A struktúra egy helyen írja le a
periféria teljes regisztertérképét, és a mezőnevekhez kötött szemantika
csökkenti a téves offset használatát. A fordító típusellenőrzést végez a
mezőkön, a _Static_assert + offsetof párosával
pedig a struktúra elrendezése fordítási időben ellenőrizhető. Ez az
ellenőrzés azonban csak azt igazolja, hogy a struktúra azt a
táblázatot írja le, amelyet mi az assertekbe beírtunk. Ha a
táblázat maga hibás, a struktúra és az assertek együtt, egymással
egyezően hibásak. A hardvertérkép forrása a gyártói leírás; azt a
struktúra nem helyettesíti, legfeljebb pontosan átveszi.
Kockázat: a struktúra elrendezése és a tényleges
hardver-layout eltérhet. Ha a mezőtípusok nem egyeznek a regiszterek
szélességével, a fordító kitöltést (padding) szúrhat
be, és a további mezők offsetje elcsúszik. Például ha az első mező
uint8_t, a második uint32_t, akkor a második a
4. bájton kezdődik a várt 1. helyett. Ezért kötelező a struktúra
méretének és kulcsmező-offsetjeinek statikus ellenőrzése, és a
regiszterekhez mindig a hardver szélességének megfelelő típust kell
választani. A __attribute__((packed)) nem javasolt:
igazítatlan hozzáférést eredményezhet, ami Armv7-M-en HardFault-ot
válthat ki (kivéve, ha a hardver kifejezetten támogatja az igazítatlan
elérést, ez Cortex-M3-on nem általános).
További kockázat, hogy a struktúra-alapú elérés csak akkor azonos a nyers cíművel, ha a báziscím és az offsetek pontosan egyeznek. Ezt a generált assembly összehasonlításával lehet igazolni.
Munkakönyvtár: ~/arm-labor/cm/a074/ —
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/a074
cd ~/arm-labor/cm/a074A GPIO-műveletek implementált CMSDK GPIO perifériát és valódi
bemenetet feltételeznek. A QEMU 8.2.2 mps2-an385
modelljében a GPIO nincs implementálva; ott a fizikai be-/kimeneti próba
SKIP, és a regiszterek írása nem igazolja a lábak
működését.
Tekintsük az MPS2-AN385 CMSDK GPIO regiszterelrendezését. A GPIO0
báziscíme a dokumentált memóriatérképen 0x40010000. A
regisztertérkép az Arm Cortex-M System Design Kit műszaki leírásának
3-8. táblázatából (3.5.2) származik:
| Offset | Név | Szerep |
|---|---|---|
| 0x000 | DATA |
olvasás: a lábak tényleges szintje; írás: kimeneti adat |
| 0x004 | DATAOUT |
kimeneti adatregiszter |
| 0x008–0x00C | — | fenntartott (két szó) |
| 0x010 | OUTENSET |
kimenet engedélyezése (set) |
| 0x014 | OUTENCLR |
kimenet engedélyezése (clear) |
| 0x018 | ALTFUNCSET |
alternatív funkció engedélyezése (set) |
| 0x01C | ALTFUNCCLR |
alternatív funkció engedélyezése (clear) |
| 0x020 | INTENSET |
megszakítás engedélyezése (set) |
| 0x024 | INTENCLR |
megszakítás tiltása (clear) |
| 0x028 | INTTYPESET |
élvezérlés (set) |
| 0x02C | INTTYPECLR |
szintvezérlés (clear) |
| 0x030 | INTPOLSET |
polaritás: felfutó él / magas szint (set) |
| 0x034 | INTPOLCLR |
polaritás: lefutó él / alacsony szint (clear) |
| 0x038 | INTSTATUS / INTCLEAR |
olvasás: függő megszakítások; írás: W1C nyugtázás |
Ez a táblázat a II. rész egyetlen forrása a
GPIO-hoz: a lab-cmsdk-gpio.h fejléc ebből készül, az a073 assembly-konstansai ugyanezek, és a II. záróprojekt is ezt a fejlécet másolja át, nem
saját struktúrát ír. A fenntartott szavakat RESERVED0[2]
tömb tölti ki, így az OUTENSET a 0x010-re
kerül. A 0x038-on egy union fejezi ki, hogy
ugyanazon a címen az olvasás INTSTATUS, az írás
INTCLEAR (a const tag miatt az
INTSTATUS-ba írni fordítási hiba). A struktúra a
0x038-ig tartó részleges térképet írja le, ezért a teljes
mérete 0x03C. A fejléc a CMSDK-modellt írja le; nem
állítja, hogy a QEMU 8.2.2 AN385 GPIO-eszközt valósít meg. A nyugtázás a
megfelelő maszkot írja az INTSTATUS/INTCLEAR
közös címére; a pin tartománya (pin < 16) a függvények
rögzített előfeltétele:
/* lab-cmsdk-gpio.h — CMSDK AHB GPIO regisztertérkép (részleges, 0x000–0x038)
* Forrás: Arm Cortex-M System Design Kit TRM, 3.5.2, 3-8. táblázat.
* Ez a fejléc a II. rész EGYETLEN forrása a GPIO-offsetekhez; az a073
* assembly-konstansai és a záróprojekt (a078) is ebből származnak.
* Az offsetof-ellenőrzés csak azt igazolja, hogy a struktúra a fenti
* táblázatot írja le; a hardver térképét nem bizonyítja. */
#ifndef LAB_CMSDK_GPIO_H
#define LAB_CMSDK_GPIO_H
#include <stddef.h>
#include <stdint.h>
typedef struct {
volatile uint32_t DATA; /* 0x000 RW bemeneti szint olvasás / kimenet írás */
volatile uint32_t DATAOUT; /* 0x004 RW kimeneti adatregiszter */
uint32_t RESERVED0[2]; /* 0x008–0x00C fenntartott */
volatile uint32_t OUTENSET; /* 0x010 RW 1 → a láb kimenet */
volatile uint32_t OUTENCLR; /* 0x014 RW 1 → a láb bemenet */
volatile uint32_t ALTFUNCSET; /* 0x018 RW 1 → alternatív funkció */
volatile uint32_t ALTFUNCCLR; /* 0x01C RW 1 → alternatív funkció ki */
volatile uint32_t INTENSET; /* 0x020 RW 1 → megszakítás engedélyezve */
volatile uint32_t INTENCLR; /* 0x024 RW 1 → megszakítás tiltva (NEM nyugtáz) */
volatile uint32_t INTTYPESET; /* 0x028 RW 1 → élvezérelt */
volatile uint32_t INTTYPECLR; /* 0x02C RW 1 → szintvezérelt */
volatile uint32_t INTPOLSET; /* 0x030 RW 1 → felfutó él / magas szint */
volatile uint32_t INTPOLCLR; /* 0x034 RW 1 → lefutó él / alacsony szint */
union { /* 0x038 ugyanaz a cím: */
volatile const uint32_t INTSTATUS; /* olvasás: függő megszakítások */
volatile uint32_t INTCLEAR; /* írás: 1 → az eseményjelző törlése (W1C) */
};
} cmsdk_gpio_t;
_Static_assert(offsetof(cmsdk_gpio_t, DATA) == 0x000, "DATA");
_Static_assert(offsetof(cmsdk_gpio_t, DATAOUT) == 0x004, "DATAOUT");
_Static_assert(offsetof(cmsdk_gpio_t, OUTENSET) == 0x010, "OUTENSET");
_Static_assert(offsetof(cmsdk_gpio_t, OUTENCLR) == 0x014, "OUTENCLR");
_Static_assert(offsetof(cmsdk_gpio_t, ALTFUNCSET) == 0x018, "ALTFUNCSET");
_Static_assert(offsetof(cmsdk_gpio_t, ALTFUNCCLR) == 0x01C, "ALTFUNCCLR");
_Static_assert(offsetof(cmsdk_gpio_t, INTENSET) == 0x020, "INTENSET");
_Static_assert(offsetof(cmsdk_gpio_t, INTENCLR) == 0x024, "INTENCLR");
_Static_assert(offsetof(cmsdk_gpio_t, INTTYPESET) == 0x028, "INTTYPESET");
_Static_assert(offsetof(cmsdk_gpio_t, INTTYPECLR) == 0x02C, "INTTYPECLR");
_Static_assert(offsetof(cmsdk_gpio_t, INTPOLSET) == 0x030, "INTPOLSET");
_Static_assert(offsetof(cmsdk_gpio_t, INTPOLCLR) == 0x034, "INTPOLCLR");
_Static_assert(offsetof(cmsdk_gpio_t, INTSTATUS) == 0x038, "INTSTATUS");
_Static_assert(offsetof(cmsdk_gpio_t, INTCLEAR) == 0x038, "INTCLEAR");
_Static_assert(sizeof(cmsdk_gpio_t) == 0x03C, "sizeof");
#define CMSDK_GPIO0_BASE 0x40010000UL /* MPS2-AN385 */
#define CMSDK_GPIO1_BASE 0x40011000UL
#define CMSDK_GPIO_PINS 16u /* lábak száma portonként: pin < 16 előfeltétel */
#define CMSDK_GPIO0 ((cmsdk_gpio_t *)CMSDK_GPIO0_BASE)
#define CMSDK_GPIO1 ((cmsdk_gpio_t *)CMSDK_GPIO1_BASE)
/* Platformtámogatás: a QEMU 8.2.2 mps2-an385 gépen a GPIO nincs implementálva.
QEMU-buildnél LAB_HAVE_GPIO = 0, és a GPIO-próbák SKIP-et jelentenek. */
#ifndef LAB_HAVE_GPIO
#define LAB_HAVE_GPIO 0
#endif
#endifA struktúra-alapú és a nyers című elérés összevetése, a megszakításkezelő függvényekkel együtt:
/* lab-gpio.c — struktúra-alapú és nyers című elérés, megszakítás-kezelés
* Előfeltétel minden függvénynél: pin < CMSDK_GPIO_PINS (16). A függvények ezt
* nem ellenőrzik; nagyobb pin érték más láb bitjét vagy (pin >= 32 esetén) C-ben
* meghatározatlan eltolást adna. */
#include "lab-cmsdk-gpio.h"
void gpio_set_output(uint32_t pin) { CMSDK_GPIO0->OUTENSET = 1u << pin; }
#define GPIO0_OUTENSET (*(volatile uint32_t *)(CMSDK_GPIO0_BASE + 0x010))
void gpio_set_output_raw(uint32_t pin) { GPIO0_OUTENSET = 1u << pin; }
void gpio_write(uint32_t pin, int value)
{
if (value) CMSDK_GPIO0->DATAOUT |= 1u << pin;
else CMSDK_GPIO0->DATAOUT &= ~(1u << pin);
}
void gpio_enable_interrupt(uint32_t pin) { CMSDK_GPIO0->INTENSET = 1u << pin; }
void gpio_disable_interrupt(uint32_t pin) { CMSDK_GPIO0->INTENCLR = 1u << pin; } /* tiltás */
/* nyugtázás, W1C */
void gpio_clear_interrupt(uint32_t pin) { CMSDK_GPIO0->INTCLEAR = 1u << pin; }
uint32_t gpio_pending(void) { return CMSDK_GPIO0->INTSTATUS; }A gpio_set_output és a gpio_set_output_raw
azonos assembly-re kell forduljon. Ezt -O2 mellett a
arm-none-eabi-gcc -S kimenetén vagy az objektfájl
disassemblyjén ellenőrizzük:
arm-none-eabi-gcc -std=c11 -mcpu=cortex-m3 -mthumb -O2 -c lab-gpio.c -o lab-gpio.o
arm-none-eabi-objdump -d lab-gpio.oKimenet a helyettesítő eszközlánccal (Clang
-O2 -fomit-frame-pointer, disassembly:
tools/thumb-dump.py; a GNU arm-none-eabi-gcc a
bázist jellemzően literal poolból tölti, a regiszterkiosztás
eltérhet):
00000000 <gpio_set_output>:
0: 2101 movs r1, #1
2: fa01 f000 lsl.w r0, r1, r0
6: 2110 movs r1, #0x10
8: f2c4 0101 movt r1, #0x4001 @ r1 = 0x40010010 = OUTENSET
c: 6008 str r0, [r1]
e: 4770 bx lr
00000010 <gpio_set_output_raw>:
10: 2101 movs r1, #1
12: fa01 f000 lsl.w r0, r1, r0
16: 2110 movs r1, #0x10
18: f2c4 0101 movt r1, #0x4001
1c: 6008 str r0, [r1]
1e: 4770 bx lr
...
0000005c <gpio_clear_interrupt>:
5c: 2101 movs r1, #1
5e: fa01 f000 lsl.w r0, r1, r0
62: 2138 movs r1, #0x38
64: f2c4 0101 movt r1, #0x4001 @ r1 = 0x40010038 = INTCLEAR
68: 6008 str r0, [r1]
6a: 4770 bx lr
A két első függvény gépi kódja azonos, és mindkettő a
0x40010010 címre ír. A fordító itt a teljes címet
movs + movt párral rakja össze, ezért a
0x10 eltolás az alsó félszóban látszik. Egy literal poolból
betöltött bázis esetén ugyanez str r0, [r1, #16] alakban
jelenne meg; a báziscím+offset minta (a024)
mindkét formában ugyanaz. A gpio_clear_interrupt a
0x40010038-ra, a gpio_disable_interrupt a
0x40010024-re ír; a kettő tehát két különböző művelet.
Ellenőrzési feltétel: a struktúra-alapú és a nyers
változat disassemblyje egyezzen, és a célcím a gyártói táblázat szerinti
legyen. A fordítás QEMU nélkül is ellenőrizhető. A regiszterek tényleges
viselkedése viszont csak támogatott hardveren próbálható ki, mert a QEMU
mps2-an385 gépen a GPIO nincs implementálva (lásd a támogatási mátrixot); ott a futó próba
SKIP.
| Tünet | Hipotézis | Adatgyűjtés | Javítás |
|---|---|---|---|
| Az írás „eltűnik”, a periféria nem reagál | Hiányzó volatile; a fordító elhagyta az írást |
objdump -d: nincs str a várt helyen |
Minden regisztermező volatile |
| A második regiszter rossz címen íródik | Padding a mezők között; rossz mezőtípus | offsetof kiíratása vagy
_Static_assert |
uint32_t minden 32 bites regiszterre;
_Static_assert |
| A struktúra mérete nagyobb a vártnál | Záró padding a struktúra igazítása miatt | sizeof ellenőrzése |
Sorrend/mezőtípusok rendezése;
_Static_assert(sizeof(...)) |
| A castolt pointerrel más típusban is írunk ugyanoda | Strict aliasing megsértése | Fordító figyelmeztetései, viselkedésváltozás -O2-n |
Egyetlen hozzáférési típus használata |
| Igazítatlan elérés HardFault-ot okoz | packed struktúra vagy uint8_t mező 32
bites regiszterre |
Fault-regiszterek (CFSR) vizsgálata |
Igazított, szélességhelyes mezők |
Ellenpróba: vegyük ki a volatile-t a
DATAOUT mezőből, és fordítsuk -O2-vel. Ha a
gpio_write függvény két egymást követő írása összevonódik
vagy eltűnik, a volatile hiánya igazolt. Hasonlóképpen
cseréljük az OUTENSET mezőt uint8_t-re, vagy
hagyjuk ki a RESERVED0[2] tömböt: a
_Static_assert offset-hibát jelez. (A helyettesítő
eszközlánccal kipróbálva egy szándékosan elrontott
INTSTATUS == 0x030 assert a fordítást
static assertion failed ... INTSTATUS hibával állította
meg.)
(magyarázó, alapszint — cél:
megértés-ellenőrzés) Magyarázd meg, miért nem elegendő a
struktúra pointerét volatile-nak jelölni, ha a mezők nem
azok. Mi a különbség a volatile cmsdk_gpio_t *p és a
cmsdk_gpio_t *volatile p között? Melyik védi a
perifériaregiszterek elérését?
(implementációs, alapszint — cél: rutinépítés)
Írd meg a cmsdk_gpio_t struktúrát a 0x038-ig
tartó teljes térképpel (a fenntartott szavakkal és az
ALTFUNC párral együtt), _Static_assert
ellenőrzésekkel minden offsetre és a méretre (0x03C).
Készíts gpio_enable_interrupt,
gpio_disable_interrupt és gpio_clear_interrupt
függvényt, és indokold, melyik regiszterbe ír a tiltás és melyikbe a
nyugtázás.
(hibakeresési/mérési, középszint — cél:
rutinépítés) Adott egy struktúra, amelyben a mezők
uint8_t, uint16_t, uint32_t
típusúak vegyesen. Írd ki az egyes mezők offsetof értékét
egy rövid C programmal, és hasonlítsd össze a hardver
regisztertérképével. Azonosítsd, hol keletkezik padding, és javítsd a
struktúrát úgy, hogy a _Static_assert-ek átmenjenek. Ezután
vesd össze a struktúra-alapú és a nyers című elérés generált assemblyjét
arm-none-eabi-objdump -d-vel.
(integráló, elmélyült — cél: fejezetek összekapcsolása:
ISR írása: C és assembly vegyesen) Írj
egy GPIO0_Handler ISR-t, amely a struktúra-alapú
CMSDK_GPIO0->INTCLEAR-be írt maszkkal nyugtázza az
eseményt (write-1-to-clear, ugyanaz a 0x038 cím, mint az
INTSTATUS olvasása), majd egy
volatile uint8_t g_gpio_pending megosztott jelzőt állít —
pontosan a070 TIMER0_IRQHandler
mintáját követve, csak itt a periféria-struktúrán keresztül, nem nyers
címmel. Indokold a070 alapján, miért nem kell az
ISR-ben explicit regisztermentés, és mi történne, ha a
g_gpio_pending-ről lemaradna a volatile.
Ellenőrizd objdump-pal, hogy a struktúra-alapú
INTCLEAR-írás ugyanarra a str-mintára fordul
(a 0x40010038 címre), mint egy nyers címmel írt megfelelője
— ez a jelen fejezet saját ellenőrzési módszere, most az ISR
kontextusában alkalmazva.
1. megoldás — Segítség. Gondold át külön, mit véd a
volatile a mutatott oldalon, és mit a
pointer oldalán — a kettő nem ugyanaz a tulajdonság.
1. megoldás — Teljes megoldás. A
volatile cmsdk_gpio_t *p azt jelenti, hogy a mutatott
objektum volatile: minden p->mezo elérés volatile lesz,
függetlenül attól, hogy a meződeklaráció tartalmaz-e
volatile-t. A cmsdk_gpio_t *volatile p ezzel
szemben magát a pointerváltozót védi: a pointer értéke nem
gyorsítótárazható, de a mutatott struktúra mezői nem lesznek
volatile-ok, ha a mezők nem azok. A perifériaregiszterek védelméhez az
első forma (vagy a mezőnkénti volatile) kell. A két forma
kombinálható is, de a gyakorlatban a mezőnkénti volatile a
legkevésbé félreérthető.
2. megoldás — Segítség. Indulj a gyártói
táblázatból, ne a korábbi struktúrából: hol vannak fenntartott szavak,
és mi van a 0x038-on olvasáskor, illetve íráskor?
2. megoldás — Teljes megoldás. A struktúra a fenti
lab-cmsdk-gpio.h: RESERVED0[2] a
0x008–0x00C helyen,
ALTFUNCSET/ALTFUNCCLR a
0x018/0x01C-n, a 0x038-on
INTSTATUS/INTCLEAR union,
sizeof == 0x03C. A három függvény:
void gpio_enable_interrupt(uint32_t pin) { CMSDK_GPIO0->INTENSET = 1u << pin; } /* 0x020 */
/* 0x024: tiltás */
void gpio_disable_interrupt(uint32_t pin) { CMSDK_GPIO0->INTENCLR = 1u << pin; }
/* 0x038: nyugtázás, W1C */
void gpio_clear_interrupt(uint32_t pin) { CMSDK_GPIO0->INTCLEAR = 1u << pin; }Az INTENCLR-be írás a megszakítást
tiltja: a láb további eseményei nem kérnek
megszakítást, de a már beállt eseményjelző megmarad. Az
eseményjelzőt az INTCLEAR törli
(write-1-to-clear), és utána a megszakítás engedélyezve marad. A korábbi
kiadás gpio_clear_interrupt néven az
INTENCLR-be írt. Ez nyugtázás helyett tiltott, ezért a
függvénynek új neve (gpio_disable_interrupt) lett, a
gpio_clear_interrupt pedig a 0x038-ra ír.
Ellenpróba (támogatott hardveren; QEMU-n SKIP):
engedélyezett, élvezérelt pin mellett (1) egy él beállítja az
INTSTATUS bitjét és lefut a kezelő; (2) a kezelő
gpio_clear_interrupt-tal nyugtáz; (3) egy újabb él
újraengedélyezés nélkül ismét megszakítást kér. Ha a
(3) lépés elmarad, a nyugtázás valójában tiltás volt. Ha a kezelő a
visszatérés után azonnal újra belép, a nyugtázás elmaradt.
3. megoldás. A vegyes típusú struktúra tipikusan így néz ki:
typedef struct {
uint8_t A; /* 0x000 */
uint32_t B; /* 0x004 – padding 0x001..0x003 */
uint16_t C; /* 0x008 */
uint8_t D; /* 0x00A */
uint32_t E; /* 0x00C */
} mixed_t;Az offsetof értékek: A=0, B=4,
C=8, D=10 (0x0A),
E=12 (0x0C), sizeof=16
(0x10). Az A után 3 bájt padding van,
D után 1 bájt, a struktúra végén 1 bájt. Ha a hardver
regisztertérképe azt mondja, hogy B a 0x001 címen van,
akkor ez a struktúra hibás. A javítás: minden 32 bites regiszterhez
uint32_t, és a _Static_assert-ek felveszik a
várt offseteket. A disassembly-ellenőrzés ezek után azt mutatja, hogy a
struktúra-alapú STR [r0, #offset] és a nyers
STR [r0, #offset] azonos kódot ad.
A periféria-struktúra a memóriába leképzett regisztereket C
struct mezőiként írja le, és egy fix báziscímre castolja. A
mezőoffsetek adják a regisztercímeket, a báziscím+offset minta assembly
szinten változatlan. A volatile mezők garantálják, hogy a
fordító ne optimalizálja el a periféria-hozzáféréseket. Az olvashatóság
előnye mellé a _Static_assert-ek és az
offsetof adnak biztonságot a padding és a téves elrendezés
ellen. A struktúra-alapú és a nyers című elérés generált kódja azonos
kell legyen.
Továbbhasznált definíciók: - periféria-struktúra: C struktúratípus, amelynek mezői egy periféria regisztereit képezik le, fix báziscímre castolva. - volatile: minősítő, amely biztosítja, hogy a fordító minden hozzáférést ténylegesen végrehajtson, és ne rendezze át azokat. - mezőoffset: a struktúramező kezdőcíme a struktúra kezdetéhez képest; a regiszter címének kiszámításához használjuk.
Az alábbiak dokumentációs kiindulópontok (forrásállapot: nem megnyitott, lásd az Olvasói útmutató jelöléseit):
STR immediate-offset kódolás.mps2-an385 machine és a CMSDK GPIO
modellezése; a báziscím és az offsetek ellenőrzése
qemu-system-arm -M mps2-an385 -monitor stdio +
info mtree paranccsal.volatile, offsetof,
_Static_assert.volatile szemantika.Ellenőrzési hiányok: A regisztertérképet a lektor a
CMSDK műszaki leírás 3-8. táblázatával vetette össze, és ennek alapján
javítottuk (a korábbi kiadásból hiányzott a két fenntartott szó, és
INENSET/INENCLR név szerepelt az
ALTFUNC mezők helyén). A lab-cmsdk-gpio.h-t és
a lab-gpio.c-t a helyettesítő eszközlánccal (Clang,
-std=c11) lefordítottuk; az assertek átmentek, a közölt
disassembly ebből a fordításból származik. A regiszterek viselkedése
(nyugtázás, újbóli megszakításkérés) csak támogatott hardveren
próbálható ki; ez ebben a munkafolyamatban nem történt meg. A
_Static_assert offset-ellenőrzések használatához a fordító
C11-támogatása szükséges; a projekt build-beállításait ennek megfelelően
kell ellenőrizni. Az assembly-kimenetek -O2 szinten
várhatók, de a konkrét regiszterkiosztás fordítóverziófüggő lehet.