Reverse Engineering Bosch EDC16: Kalibrációs Pointer Rendszer, MPC555 Direkt Címzés és Mapswitch Architektúra
A Bosch EDC16 az egyik legelterjedtebb dízel motorvezérlő platform. BMW, VW, Audi, Ford, gyakorlatilag bármelyik európai gyártó járművében találhatsz belőle valamilyen változatot. A szoftver-architektúra közös, de a visszafejtés korántsem egyszerű. A kalibrációs adatok, a térképek, a karakterisztikák és a konstansok többszintű indirekción át rejtőznek a kódban.
Egy pár BMW platformot vettem górcső alá (DDE5, DDE6.0, DDE6.2, két különböző ECU típuson: EDC16CP35 és EDC16C31), és a végén nagyjából ki lehet jelenteni, hogy a BMW EDC16 kalibrációs architektúrája az MPC562 és MPC563 alapú változatokban gyakorlatilag azonos. Ugyanaz a négyszintű pointer rendszer, ugyanaz a dual variant mechanizmus, ugyanaz a flash mirror. A platformok között csak a konkrét címek térnek el, maga a működés azonos. Egyetlen annotáló script, pár konfigurációs paraméter cseréjével, bármelyik MPC562-alapú BMW EDC16-on elboldogul.
Egy fontos kivétel akad: az EDC16C31 DDE5 (a korábbi E60/E61 530d, 525d motorokban), ami MPC555-re épül, és architektúrájában komoly eltéréseket mutat. Erről a bejegyzés végén lesz külön szó.
Bosch EDC16 MPC562-563 (DDE6.0 / DDE6.2)
Az EDC16 hardvere a Motorola/Freescale MPC562-563 processzorra épül. Ez egy 32 bites PowerPC mag, amit kifejezetten autóipari alkalmazásokra terveztek. A bináris két fő részből áll:
| Blokk | Cím | Méret | Tartalom |
|---|---|---|---|
Külső Flash | 0x000000 - 0x1FFFFF | 2 MB | Alkalmazás kód + kalibrációs terület |
Belső Flash | 0x400000 - 0x480FFF | ~524 KB | Bosch Funktionsbibliothek (OS + standard rutinok) |
A belső flash a Bosch standard könyvtára. Ez nagyjából minden EDC16-ban egyforma, és az összes alapvető rutint tartalmazza: interpolációt, matematikát, szűrőket, RTOS-t. Az applikációs kód a 2 MB-os flash-ben ezeket bl MPC:FUN_xxxxx utasításokkal hívogatja.
Hardver memória architektúra (A2L validációval)
Az MPC562-ben mindössze 32 KB belső CALRAM van (0x3F8000 - 0x3FFFFF), ebből a Bosch szoftver alkalmazásszinten 26 KB-ot használ (0x3F9800 - 0x3FFFFF). Ezt a területet az RTOS kernel és az időkritikus ISR rutinok foglalják. A tényleges alkalmazás a külső SRAM chipeken fut, amiket az MPC562 External Bus Interface (EBI) kezel.
Az alábbi felosztás egy 5660 változót tartalmazó A2L fájl elemzésén alapul. A változónevek és a prefixek elég egyértelműen kirajzolják, mire valók az egyes RAM területek.
| Régió | Címtartomány | Méret | Funkció (A2L elemzés alapján) |
|---|---|---|---|
| INT_CALRAM (on-chip) | 0x3F9800 - 0x3FFFFF | 26 KB | RTOS kernel, ISR stack, időkritikus változók, 0 wait-state hozzáférés |
| CS0 - External Flash | 0x000000 - 0x1FFFFF | 2 MB | Applikációs kód + Kalibrációs terület |
| EXT_SRAM_A | 0x6F8000 - 0x6F87FF | 2 KB | Mérőpont puffer (kis, kiemelt): 99.2% _mp suffix (Messpunkt), ~128 db kritikus measurement point |
| EXT_SRAM_B | 0x7F8000 - 0x7FFFFF | 32 KB | Fő alkalmazói RAM (SDA, r13-relatív): 2469 futási változó, state machines, FrmMng, InjCrv, Lambda... |
| EXT_SRAM_C | 0x800000 - 0x807FFF | 32 KB | Stack + RTOS workspace: 0 nevesített változó az A2L-ben, tiszta futásidejű stack terület |
| EXT_SRAM_D | 0x8FE000 - 0x8FFFFF | 8 KB | Nagy diagnosztikai mérőpont puffer: 3063 db measurement point, 98.2% _mp suffix |
Kulcs felismerés: az _mp suffix a Messpunkt (mérőpont) rövidítése. Ezek nem a tényleges vezérlési változók, hanem shadow másolatok, amiket a BMW gyári diagnosztikai rendszer és például a Test-O olvasgat. A motor igazi vezérlése az EXT_SRAM_B régióban történik (r13-relatív címzéssel), és az eredmények másolatai kerülnek át az EXT_SRAM_A és EXT_SRAM_D pufferekbe, hogy kívülről is megfigyelhetők legyenek.
A CAN kommunikációhoz sok helyen egy dedikált dupla-portos SRAM (DPRAM) chip is található a boardon, ezt a CPU és a CAN-vezérlő közötti adatcserére használják. Ghidrában a memória blokkok beállításánál érdemes ezeket a tényleges méretnél egy kicsit nagyobbra definiálni.
A flash belső logikai felosztása
A 2 MB-os külső flash nem homogén, több, funkcionálisan elkülönített régióból áll. A felosztás szoftver-verziónként változhat, szóval minden új binárisnál érdemes a kód és a kalibráció határait ellenőrizni. A kalibrációs adatok jellemzően két különálló régióban laknak:
| Régió | Tartalom | Hozzáférés |
|---|---|---|
0x040000 - 0x052000 | Skalár kalibrációk, flag-ek, konfiguráció | r2/r14-relatív (SDA direct) |
0x0C0000 - 0x0FFFFF | Map-ek, táblák, karakterisztikák | Pointer table (r15) |
A pontos határok szoftver-verziónként eltérnek, de a két régió közötti funkcionális elkülönítés (skalár vs térkép) minden BMW EDC16-ban ugyanaz. Ha a kalibrációs terület nem összefüggő, az annotáló scriptnek mindkét tartományt ismernie kell.
Az első lépés: Memory Map validáció
Az MPC562-nél a külső flash a CPU 0x000000 címére van mappolva. Ezt egy egyszerű teszttel lehet ellenőrizni: az MPC56x reset vector-a a 0x100 címen található. Ha ott értelmes utasítás van, akkor a WinOLS offsetek és a CPU címek egybeesnek.
0x100: 48 00 01 82 ba 0x180 ; Abszolút branch a startup kódra
A ba 0x180 utasítás a tényleges inicializáló kódra ugrik. Ez megerősíti, hogy WinOLS offset = CPU cím, nincs eltérés.
0x200000 file offsettől tárolja. Ez a processzor címterében 0x400000-nak felel meg. Ghidra betöltéskor az external flash-t 0x000000-ra, az internal flash-t 0x400000-ra kell mappolni.0x000000-tól kezdődik. Az E60 535d HEX fájlban például az első adat 0x008000-nál indul, ami 0x8000 byte-os eltolást jelent a WinOLS nyers binárishoz képest. Ghidrában az Intel HEX loader automatikusan a helyes címekre rakja az adatokat, de WinOLS-ben WinOLS cím = CPU cím - 0x8000 korrekció kell.A startup: Register poisoning és bázisregiszter inicializáció
A reset vector utáni kód először egy register poisoning mintát hajt végre: az összes általános célú regisztert 0x7F7F7F7F értékkel tölti fel:
lis r1, 0x7f7f addi r1, r1, 0x7f7f or r2, r1, r1 ... ; r3-r31 szintén 0x7F7F7F7F or r31, r1, r1
Ez egy debug jellegű előinicializálás: ha bármelyik regiszter használat előtt nem kap tényleges értéket, a 0x7F7F7F7F minta azonnal szemet szúr a memória dumpban. A tényleges bázisregiszter beállítás ezután következik a flash alkalmazáskódban:
lis r11, 0x80 addi r1, r11, 0x0 ; r1 = 0x00800000 - EXT_SRAM_C (stack, downward growing) lis r13, 0x80 subi r13, r13, 0x10 ; r13 = 0x007FFFF0 - EXT_SRAM_B (SDA bázis, minden EDC16-ban azonos) lis r2, 0x5 subi r2, r2, 0x7F10 ; r2 = 0x000480F0 - TOC/SDA2 Base (flash konstansok) lis r14, 0x4 addi r14, r14, 0x6454 ; r14 = 0x00046454 - SDA Base (flash konstansok)
Dual Variant architektúra
Minden BMW EDC16 bináris két szoftver-variánst tartalmaz, és a boot során dől el, melyik fog futni. Az r2 és r14 regiszterek a variáns kiválasztásakor kapják meg a végleges értéküket, és a két lehetőség más-más kalibrációs adathalmazra mutat.
A Variant A és B r2/r14 értékei szoftver-verziónként minimálisan eltérnek, de a mechanizmus azonos. Ennek az architektúrának köszönhetően egyetlen firmware bináris több hardverkonfigurációt is ki tud szolgálni. Elég a variant flag-et módosítani, és az ECU máris teljesen más kalibrációs adatokkal dolgozik.
Pointer relokáció a variánsváltáskor
A dual variant váltás nem merül ki az r2 és r14 regiszterek cseréjével. A boot szekvencia egy relokációs rendszert is lefuttat, ami 7 db RAM-ban tárolt pointert átszámol a régi és az új variáns kalibrációs báziscíme közötti különbség alapján:
r12 = 0x007FE100 ; fix referencia pont (RAM vége közelében) ; Variant A: r11 = 0x3E98C ; fix offset ; Variant B: r11 = lwz(r14 - 0x7740) + 0x3E98C ; flash-ből olvasott delta + fix offset delta = r12 - r11 ; Irány 0 (undo): new_ptr = delta + old_ptr ; Irány 1 (apply): new_ptr = old_ptr - delta
A négyszintű kalibráció-elérési modell
A BMW EDC16 firmware négyféleképpen fér hozzá a kalibrációs adatokhoz. Ezek a Bosch firmware-architektúra sajátosságai, és minden BMW EDC16-ban jelen vannak.
| Szint | Mechanizmus | Példa |
|---|---|---|
| 1 - r15 Pointer tábla | lwz rXX, offset(r15) - pointer tábla entry, kalibrációs blokk base | [r15 + 0x5CC] = 0x0F5078 |
| 2 - Közvetlen SDA | lwz rXX, offset(r2) vagy (r14) - közvetlenül kalibrációs adat | [r2 + 0x7034] = 0x04F124 |
| 3 - Propagáció | addi / mr / or - bázisregiszter továbbvitel függvényen belül | addi r3, r31, 0x4FC = 0x0F5574 |
| 4 - Flash mirror | lis rXX, imm ; load rYY, offset(rXX) - 0x01000000+ tükörcím | lis r11, 0x10c ; lhz r11, 0x3eea(r11) = 0x0C3EEA |
A szintek nem hierarchikusak és nem zárják ki egymást: egy függvényen belül is vegyesen megjelenhet a pointer tábla elérés, a közvetlen SDA hozzáférés és a flash mirror minta. A firmware ezeket párhuzamosan, hibrid módon használja.
Szint 1: Az r15 pointer tábla
Az r15 regiszter instance pointerként viselkedik: a kalibrációs pointer tábla báziscímére mutat. A beállítás mechanizmusa minden BMW EDC16-ban azonos. Egy feltételes logika választ két lehetséges forrás közül (instance A vagy B), aztán az eredmény r15-be kerül:
; Kritikus szekció belépés (interrupt disable): bl FUN_0001f590 ; OSEK TaskEnter ; Instance selector ellenőrzés: lbz r12, CAL_MapSelector ; aktív dataset index cmpwi r12, 0x1 bne else lbz r12, CAL_OperatingMode ; allokációs állapot cmpwi r12, 0x4 ; 4 = RAM másolat kész, váltás engedélyezve bne else ; Instance A (allokált RAM másolat): lwz r31, CAL_ActivePtr_Alt ; RAM pointer tábla b done else: ; Instance B (default, flash pointer tábla): lwz r31, CAL_ActivePtr_Default ; flash tábla cím done: lwz r12, PTR_TABLE_REF(r2) ; flash pointer tábla referencia cím addi r15, r31, 0x0 ; r15 = kiválasztott instance subf r12, r12, r31 ; offset = r15 - referencia cím stw r12, CAL_ActiveOffset ; offset mentése ; Kritikus szekció kilépés: b FUN_0001f5b8 ; OSEK TaskExit (tail call)
A teljes r15 beállítás egy atomi művelet: interrupt disable/enable párossal van védve (OSEK kritikus szekció), hogy a pointer váltás közben ne történhessen félkész állapotú olvasás. A CAL_SetActiveDataset függvény neve, paraméterei és belső szerkezete minden BMW EDC16-ban azonos, csak a konkrét RAM címek térnek el.
A Pointer Tábla
A pointer tábla egy sűrű, 4 bájtos bejegyzésekből álló tömb a flash-ben. Minden entry formátuma:
A pointer tábla báziscíme és az entry-k száma szoftver-verziónként eltér (tipikusan 380-460 entry a 0x050000 - 0x060000 tartományban), de a formátum és a feloldási mechanizmus univerzális. A legmegbízhatóbb út a megtaláláshoz: azonosítsd a CAL_SetActiveDataset függvényt, és oldd fel az r2-relatív referenciát.
Az A2L szimbólum-import után a pointer tábla valódi modulneveket kap. Minden entry egy-egy Bosch szoftver-modul kalibrációs blokkjára mutat, a Bosch prefix konvenció pedig elárulja, mire való: ExeMon a végrehajtás monitor, OvRMon a fordulatszám monitor, FrmMng a CAN frame manager, AirCtl a levegő szabályozás, CoEng a motor koordinátor, InjCrv a befecskendezés, CrCtl a tempomat. A kisebb modulok csak egy-két konstanst tartalmaznak, a nagyobbak (InjCrv, FrmMng, AirCtl) viszont több ezer byte-nyi térképet és karakterisztikát.
A pointer tábla Ghidrában, A2L szimbólum-import után: minden 4 byte-os entry egy Bosch kalibrációs modul nevét és flash címét tartalmazza.
Szint 2: Közvetlen SDA hozzáférés (r2/r14-relatív)
Az r2 és r14 regiszterek az SDA2 és SDA bázisként szolgálnak. Az SDA2 címzési tartomány (r2 plusz/mínusz 0x8000) tipikusan a 0x040000 - 0x052000 kalibrációs régiót fedi le, ahol a skalár konstansok, flag-ek és a pointer tábla bázisa is megtalálható:
lwz r12, 0x7034(r2) ; r2 + 0x7034 = kalibrációs adat lhz r3, -0x1a6e(r2) ; r2 - 0x1A6E = skalár konstans
A nagy kalibrációs térképek (0x0C0000 - 0x0FFFFF) kívül esnek az r2 címzési hatótávolságán. Ott a pointer tábla (Szint 1) az elsődleges út, míg az r2/r14-relatív hozzáférés inkább a skalár konfigurációkat szolgálja ki.
Szint 4: Flash mirror abszolút hozzáférés
A 2 MB-os külső flash nem csak a 0x000000 - 0x1FFFFF tartományban érhető el, hanem tükörképként a 0x01000000 - 0x011FFFFF címen is. Ez az MPC562 EBI chip select konfigurációjából adódik, és minden EDC16-ban jelen van.
lis r11, 0x10c ; r11 = 0x010C0000 lhz r11, 0x3eea(r11) ; [0x010C3EEA] = flash tükörkép, valós cím: 0x0C3EEA sth r11, 0x77e(r13) ; eredmény a RAM-ba
Ghidrában a 0x01000000 bázison egy 2MB-os overlay memory block létrehozásával a piros (unresolved) referenciák feloldódnak.
A kalibrációs rendszer működés közben
Eddig azt néztük meg, hogyan van felhúzva maga az infrastruktúra: milyen úton-módon jut el a kód a kalibrációs adatokig. Most jöjjön két konkrét modul, amin keresztül látszik, hogy a firmware ténylegesen mihez kezd ezekkel az adatokkal, amikor a motort kell vezérelni.
Eset 1: Main Torque Limiter
A fő nyomaték limiter dolga egyszerű: a fordulatszám és a variant coding alapján megmondja, mennyi nyomatékot szabad kiadni. Egyetlen pointer tábla bejegyzésből dolgozik, semmi több.
A Main Torque Limiter wrapper függvény Ghidrában, az annotátor által feloldott pointer referenciákkal.
lwz r31, 0x268(r15) ; PTR[0x268] = kalibrációs blokk (408 byte) lha r3, -0x6844(r13) ; r3 = RPM (X tengely) lbz r4, -0x7793(r13) ; r4 = Variant Coding (Y tengely) addi r5, r31, 0xa ; r5 = map header subi r6, r13, 0x4216 ; r6 = interpolátor cache (RAM) bl FUN_002541cc ; 2D INTERPOLÁCIÓ
A Main Torque Limiter 21x8 térkép a WinOLS-ben. X tengely: variant coding (0-7), Y tengely: RPM (600-5060), Z: nyomaték Nm-ben.
A 2D map struktúrája a flash-ben:
| Offset | Típus | Tartalom |
|---|---|---|
+0x00 | int16 | X_size = 21 (RPM tengelypont darabszám) |
+0x02 | int16 | Y_size = 8 (variant coding tengelypont darabszám) |
+0x04 | int16[21] | RPM töréspontok: 600, 800, 1000, ... 5060 |
+0x2E | int16[8] | Variant coding: 0, 1, 2, ... 7 |
+0x3E | int16[168] | Nyomaték értékek (21 x 8, Nm) |
A 2D interpolátor bilineáris interpolációt csinál 16.16 fixpontos aritmetikával, cached tengely pozícióval. A "cached" itt azt jelenti, hogy az interpolátor megjegyzi, hol járt legutóbb a tengelyen, és a következő híváskor onnan indul. Ha az RPM az előző hívás óta alig változott, ami motornál elég gyakori helyzet, a szegmens megkeresése szinte azonnal megvan.
A 2D interpolátor függvény eleje: X_size és Y_size olvasás, majd az X tengely keresés indítása.
Az interpoláció után van még egy érdekes kis részlet, egy korrekciós ág:
lbz r11, 0x1(r31) ; korrekciós enable flag cmpwi r11, 0x0 beq skip ; ha 0: nincs korrekció ; korrekciós szorzás (SOHA nem fut, mert az enable byte minden kalibrációban 0): lha r11, -0x687e(r13) ; runtime szorzó mullw r3, r12, r11 ; result x factor li r4, 0x2000 ; 8192 = Q13 fixpont (1.0x) bl FUN_0025380c ; r3 = (result x factor) / 8192, szaturált skip: sth r3, -0x720e(r13) ; output = Main TQ Limiter eredmény
Ez jól mutatja a Bosch fejlesztési filozófiáját: a kódban ott van egy runtime korrekciós szorzó, de a konkrét kalibrációban ki van kapcsolva. A Bosch modulárisan fejleszt, a funkciókat pedig kapcsolókkal engedi vagy tiltja. Ami az egyik OEM kalibrációban aktív, a másikban simán ki lehet kapcsolva, a kód viszont mindig ott marad, ha esetleg kell.
Eset 2: Boost Pressure Control - Töltőnyomás szabályozás
A boost modul egy tekintélyes méretű függvény, és egyetlen pointer tábla bejegyzésből szedi ki az összes adatát: PTR[0x558], ami egy 4492 bájtos kalibrációs blokkra mutat. Ezen a blokkon belül több mint 20 térkép és konstans van.
A boost modul map-választó logikája: egy bit-teszt dönti el, hogy a base vagy az alternatív boost target map-et olvassa.
lwz r31, 0x558(r15) ; PTR[0x558] = boost kalibráció (4492 byte blokk) lha r3, -0x6844(r13) ; r3 = RPM lha r4, -0x6df0(r13) ; r4 = befecskendezési mennyiség (mg/cyc) lbz r12, runtime_flag ; runtime flag byte rlwinm. r12, r12, 0x1f, 0x1f, 0x1f ; bit 1 teszt beq use_base ; bit 1 = 0: base map ; bit 1 = 1: alternatív boost map addi r5, r31, 0x568 ; alt boost target b call_interp use_base: addi r5, r31, 0x9fe ; base boost target call_interp: bl FUN_004541cc ; 2D INTERPOLÁCIÓ, r3 = boost target [mbar]
A base boost target 16x16 térkép a WinOLS-ben. X tengely: befecskendezés (mg/cyc, 0.01 faktor), Y tengely: RPM, Z: kívánt töltőnyomás mbar-ban.
A base boost target után a kód gyors egymásutánban két DPF regenerációhoz kötődő boost map-et is beolvas:
A DPF regeneráció phase 1 és phase 2 boost map hívások egymás után.
; DPF Regen Phase 1 boost target: addi r5, r31, 0x572 ; regen phase 1 boost bl FUN_004541cc ; 2D INTERPOLÁCIÓ ; DPF Regen Phase 2 boost target: addi r5, r31, 0x7b6 ; regen phase 2 boost bl FUN_004541cc ; 2D INTERPOLÁCIÓ
Tehát három boost target fekszik párhuzamosan kiolvasva (base/alt, regen phase 1, regen phase 2), és a modul későbbi logikája dönti el, melyiket küldi tovább célértékként a szabályozóra.
A boost blokk barometrikus korrekciói
A boost blokkon belül négy olyan térkép is van, ami a légköri nyomást használja Y tengelyként (RPM x baro):
A boost limit vs légköri nyomás térkép a WinOLS-ben: az egyetlen ténylegesen kalibrált barometrikus térkép ebben a szoftverben.
Négyből viszont csak egy aktív. Háromféle korrekciós mechanizmus van beépítve a kódba (additív, multiplikatív, és egy abszolút limit), de ebben a BMW kalibrációban a BMW kalibrátorok csak a limit táblát kapcsolták be. Szokásos Bosch mintázat, mindent beletesznek a szoftverbe, aztán az OEM kalibrátor eldönti, melyikkel akar dolgozni.
A boost blokk teljes térképe
Egyetlen pointer tábla entry mögött 22+ kalibrációs objektum rejtőzik:
| Offset | Funkció |
|---|---|
+0x3C | Baro korrekció (additív, inaktív) |
+0xE0 | Baro korrekció (multiplikatív, semleges) |
+0x184 | 1D karakterisztika |
+0x19E | 2D map |
+0x202, +0x224, +0x23E | 1D karakterisztikák |
+0x25C, +0x3B0, +0x504 | 2D map-ek (mg/cyc x RPM) |
+0x568 | Alt boost target |
+0x572 | DPF regen phase 1 boost |
+0x7B6 | DPF regen phase 2 boost |
+0x9FE | Base boost target |
+0xC42 | Baro korrekció (additív, inaktív) |
+0xE86 | Boost limit vs légköri nyomás |
+0x10CE | 2D map (RPM x mg/cyc) |
+0x1134, +0x1138 | 32-bit kompárátorok |
+0x1144 | Rate limiter / ramp |
+0x1150, +0x1154 | Threshold konstans + 1D karakterisztika |
Automatizáció: A Ghidra Annotáló Script
Miután a manuális nyomkövetéssel megértettük a rendszert, egy Ghidra Python script segítségével az egészet automatizálhatjuk. A script mind a négy szinten dolgozik egyszerre:
A Ghidra annotáló script eredménye.
| Szint | Mit csinál |
|---|---|
| L1: r15 pointer tábla | Minden lwz rX, offset(r15) mellé beírja a feloldott pointert és méretet |
| L2: r2/r14 SDA direct | Minden r2/r14-relatív load/store mellé kiírja a kalibrációs címet |
| L3: Regiszter propagáció | addi, mr, or másolásokat követ, A2L symbol lookup |
| L4: lis + load mirror | lis rX, imm ; load rY, offset(rX) feloldja a mirror címet |
A script egyetlen konfigurációs blokkból dolgozik, amelyben 5 paramétert kell az adott binárishoz beállítani: pointer tábla báziscím, r2 érték, r14 érték, és a két kalibrációs tartomány határai. Ez az összes. A négy szint felismerési logikája és maga az annotáció generálás teljesen univerzális, nem kell hozzányúlni.
A script háromféle módban tud futni, attól függően, hogy milyen kalibrációs információ van a kezünk ügyében:
- r15 POINTER TABLE: csak pointer tábla
- DIRECT SDA: csak r2/r14-relatív
- HYBRID: minden szint egyszerre
Teljes hybrid konfigurációval futtatva a script ~17,800 annotációt szór szét a kódban, ~1,700 függvényben, egy ~3,870 függvényből álló binárisban. Ez a szám minden tesztelt BMW EDC16 binárison szinte hajszálpontosan ugyanaz - ami nem véletlen egybeesés, hanem az egységes architektúra egyik legjobb bizonyítéka.
A kódbázis regiszter-statisztikájából is szépen kilátszik a multi-regiszter architektúra:
| Regiszter | Funkciók száma | Megjegyzés |
|---|---|---|
r31 | 190 | Leggyakoribb (hagyományos base regiszter) |
r12 | 178 | Scratch regiszter, közvetlen pointer felhasználás |
r30 | 85 | Második leggyakoribb dedikált base |
r29 | 48 | Pl. CoEng (DPF nyomatékkorlátozás) modul |
r28 | 29 | Pl. Rail Pressure Control modul |
r27-r24 | 38 | Ritkábban használt base regiszterek |
DAMOS integráció
Ahol a DAMOS (a Bosch kalibrációs adatbázisa) rendelkezésre áll, ott a pointer címek helyére beírhatók az igazi funkcionális nevek:
lwz r12, 0x80(r15) PTR[0x80], AFSCD_DebPlHiSetyDrftDef_C addi r4, r12, 0x27e calib: AFSCD_swtSensInstVal_C li r3, 0x15 bl FUN_0007df84
Térképstruktúra és testreszabás
Eddig arról volt szó, hogyan találjuk meg a kalibrációs adatokat. De mit kezdjünk velük, ha egyszer megvannak? A Bosch 2D interpolátor a térkép méretét dinamikusan olvassa a headerből, és ez elképesztően hasznos tulajdonság: azt jelenti, hogy a térképeket bővíthetjük anélkül, hogy a kódhoz hozzá kellene nyúlnunk.
A 2D térkép bináris felépítése
Minden 2D kalibrációs térkép pontosan ugyanúgy néz ki a flash-ben. A 2D interpolátor futásidőben olvassa ki a méreteket a header első két szavából, és ebből számolja ki, hol vannak a tengelyek és a Z adatok:
+0x00: int16 ; X_size (pl. 0x0010 = 16 pont) +0x02: int16 ; Y_size (pl. 0x0010 = 16 pont) +0x04: int16[X] ; X tengely töréspontok (pl. RPM: 500, 750, 1000, ...) +0x04+X*2: int16[Y] ; Y tengely töréspontok (pl. Nm: 0, 20, 40, ...) +0x04+X*2+Y*2: int16[X*Y] ; Z értékek (interpolált kimeneti adat)
Egy térkép összmérete tehát: 2 + X*2 + Y*2 + X*Y*2 byte (a 4 byte-os header, plusz a két tengely, plusz a Z mátrix). A lényeg az, hogy a kód sehol nem tartalmaz beégetett méretet - mindent menet közben, a headerből olvas ki. Ez lesz a kulcsa annak, hogy a térképeket bővíteni is tudjuk, de erről mindjárt.
Az interpolátor működése
A 2D interpolátor (FUN_00457794 és társai) lépésről lépésre kb. ezt csinálja:
; 1. Header olvasás: lhz r11, 0x0(r5) ; X_size = header[0] lhz r12, 0x2(r5) ; Y_size = header[1] ; 2. X tengely keresés (cached): ; Lineáris keresés a töréspontok között, az előző hívás ; indexéből indulva (gyors, ha az RPM alig változott) ; 3. Y tengely keresés (cached): ; Ugyanaz, a Y_size alapján számított offset-ről ; 4. Bilineáris interpoláció: ; 16.16 fixpontos szorzás a 4 szomszédos Z értékből ; Eredmény: int16 a hívó regiszterbe (r3)
A cached tengely keresés lényege, hogy az interpolátor eltárolja az előző hívás X és Y szegmens indexét egy RAM-ban lévő cache struktúrában (amit r6-ban kap paraméterként). Ha a bemenet alig változott - márpedig motornál ez a tipikus, az RPM és a terhelés nem szokott ciklusról ciklusra ugrálni - a keresés 1-2 összehasonlítás után már meg is van.
Térképek bővítése
Mivel az interpolátor dinamikus, a térkép méretét simán megváltoztathatjuk a header átírásával. Mondjuk ha az eredeti térkép 16x16-os, és mi 32x16-ra akarjuk nyújtani:
| 16x16 (eredeti) | 32x16 (bővített) | |
|---|---|---|
| Header | 4 byte | 4 byte |
| X tengely | 32 byte | 64 byte |
| Y tengely | 32 byte | 32 byte |
| Z adat | 512 byte | 1024 byte |
| Összesen | 580 byte | 1124 byte |
A Bosch fejlesztési folyamatban a kalibrátorok menet közben hangolgatják a térképméreteket. Ehhez a kalibrációs blokkokban a térképek mögött sokszor padding (tartalék terület) van hagyva, ezekben az ecukban jellemzően 50-200 byte a számunkra releváns térképenként (NM-IQ konverzió vagy injektor kivezérlési idő). Ha a bővítés belefér ebbe a paddingbe, elég a headert és az adatokat átírni, a pointer offset-ek és a szomszédos térképek nyugodtan maradhatnak a helyükön.
Ha viszont a bővítés nem fér bele a paddingbe, akkor a megnövelt térképet szabad flash területre kell átköltöztetni, és a pointer/offset referenciát átírni az új címre. Ez assembly szintű patch-csel elméletileg megoldható: az eredeti addi r5, r31, offset utasítást kicseréljük egy lis r5, HI; ori r5, r5, LO abszolút címzésre, de a buktatók majd alább.
Gyakorlati példa: Nyomaték/Mennyiség átváltás bővítése
Az Nm/IQ (nyomaték/befecskendezési mennyiség) átváltó térkép az egyik legfontosabb kalibrációs objektum a motorvezérlőben. Az eredeti 16x16-os változatban 550 Nm-ig vannak töréspontok, ami egy gyári kalibrációhoz tökéletesen elég - de ha Stage 2+ tuningot csinálunk, ennyi már kevés.
A kibővített 32x16 Nm-IQ átváltó térkép: az X tengely 0-tól 1400 Nm-ig, 32 töréspont. Az eredeti 16 pontos tengely dupla felbontásra bővítve, a 650 Nm feletti tartomány extrapolált értékekkel feltöltve.
A bővítés menete:
| Lépés | Mit csinálunk |
|---|---|
| 1. Padding ellenőrzés | Megnézzük, mennyi szabad hely van a térkép vége és a következő objektum headere között |
| 2. Maximum méret kalkuláció | A szabad hely alapján: (hely - 2 - Y*2) / (1 + Y*2) = max X_size |
| 3. Header átírás | X_size módosítása 16-ról 32-re (egyetlen szó a flash-ben) |
| 4. X tengely bővítés | Új töréspontok hozzáadása (pl. 650, 700, 750, ... 1400 Nm) |
| 5. Z adat kitöltés | A meglévő adatokból lineáris extrapoláció az új tartományra |
A következő induláskor az interpolátor a 32-es X_size-t olvassa ki a headerből, és 32 töréspontot keres az X tengelyen. A kód nem változott, csak az adat.
Kivétel: EDC16C31 DDE5 - Az MPC5xx világ
Az eddig tárgyalt EDC16 platformok (CP35, DDE6) mind MPC562/MPC563 processzorra épülnek, 2 MB külső flash-sel és az r15 pointer táblás kalibrációs címzéssel. A korábbi generációs EDC16C31 DDE5 (BMW E46/E60/E61 20d, 25d, 30d első szériák) viszont teljesen más hardveren fut: a Motorola MPC555 processzoron.
MPC555: Más hardver, más világ
Az MPC555 az MPC500 családba tartozik, és az MPC562-höz képest komolyan eltér a memória-architektúrája:
| Jellemző | EDC16 MPC562/563 (DDE6.0/DDE6.2) | EDC16C31 MPC555 (DDE5) |
|---|---|---|
| Processzor | MPC562/MPC563 | MPC555 |
| Internal Flash | 0x400000 - 0x480FFF (~512 KB) | 0x000000 - 0x06FFFF (448 KB) |
| External Flash | 0x000000 - 0x1FFFFF (2 MB) | 0x800000 - 0x8FFFFF (1 MB) |
| Internal SRAM | 0x3F8000 - 0x3FFFFF (32 KB CALRAM) | SRAM A: 0x3F9800 - 0x3FBFFF (10 KB) SRAM B: 0x3FC000 - 0x3FFFFF (16 KB) |
| External SRAM | 0x6F8000 - 0x8FFFFF (több chip) | 0x400000 - 0x401FFF (~8 KB) |
| r13 | 0x007FFFF0 (EXT_SRAM_B) | 0x004017F0 (EXT_SRAM) |
| Kalibráció címzés | r15 pointer tábla | Közvetlen (direkt) címzés |
Direkt kalibrációs címzés - r15 pointer tábla nélkül
A legfontosabb architekturális különbség: az MPC555 alapú EDC16C31-ben nincs r15 pointer tábla. A kód közvetlenül, abszolút címzéssel hivatkozik a flash kalibrációs területre:
; MPC563 EDC16CP35 DDE6 ; Két lépcsős indirekcó: lwz r31, 0x268(r15) ; 1. pointer tábla - blokk cím addi r5, r31, 0xa ; 2. blokkon belüli offset bl interpolate_2D ; MPC555 EDC16C31 DDE5 ; Közvetlen flash hivatkozás: lis r5, 0x8C ; r5 = 0x008C0000 (ext flash kalibrációs terület) addi r5, r5, 0x3FE4 ; r5 = 0x008C3FE4 (konkrét térkép cím) bl interpolate_2D
Az MPC555 DDE5-ben a kalibrációs címek egyenesen bele vannak égetve az applikációs kódba. Nincs pointer tábla, nincs indirekcó, nincs CAL_SetActiveDataset. Minden térkép, karakterisztika és konstans egy fix lis + addi (vagy lis + ori) abszolút címmel elérhető.
Ez az egyszerűbb felépítés a statikus elemzésnek kifejezetten kedvez (Ghidrában azonnal látszik, hova mutat egy hivatkozás), viszont a kalibrációs terület módosítását nehezíti: ha áthelyezünk egy térképet, az összes kódreferenciát meg kell keresni és átírni. Az MPC562 r15 pointer táblás megoldásánál ehhez képest egyetlen pointer entry módosítása elég.
0x800000 báziscímre mappolva.Ghidra r13 konfigurálás MPC555-höz
Az MPC555-nél az r13 = 0x004017F0, és a signed 16-bit offset tartomány (+-0x8000) mindkét SRAM tartományt lefedi: a belső SRAM-ot negatív offset-tel (pl. r13 - 0x4D08 = 0x3FCAE8, ez SRAM B-be esik), a külső SRAM-ot meg pozitív offset-tel. Ghidrában a regiszter beállításnál erre kell figyelni: az assume r13 = 0x4017F0 a helyes érték, nem a 0x7FFFF0, amit az MPC562-nél használunk.
Mapswitch: Miért nehéz?
A tuning közösségben a "mapswitch" (futásidőben több kalibrációs térkép közötti váltás) az egyik legkeresettebb funkció. Elméletben egyszerűnek hangzik: ha az ECU a pointer táblán vagy közvetlen címzésen át éri el a kalibrációt, irányítsuk át a pointert vagy a címet egy másik térképre, és kész. A gyakorlatban viszont ez jóval bonyolultabb mint gondolnánk.
A Bosch nyomaték-pipeline és a konzisztencia probléma
A modern Bosch EDC16 firmware nem egy egyszerű "térkép be - output ki" rendszer. A nyomatékvezérlés egy többszintű pipeline: több párhuzamos alrendszer számol nyomatéklimiteket (füst limiter, boost limiter, váltóvédelem, motorvédelem), és ezeket egy arbiter MIN-eli össze. Ráadásul a Bosch egy belső konzisztencia-ellenőrzőt is üzemeltet, ami figyeli, hogy a különböző alrendszerek számolt értékei összhangban vannak-e.
Ha egy mapswitch patch csak egyetlen alrendszert piszkál meg (pl. a fő nyomaték limitert), miközben a többi szépen a stock kalibrációból számolgat tovább, a konzisztencia-ellenőrzés eltérést fog észlelni. Az eredmény nem szép: ECU leállás, derate mód, vagy CAN kommunikáció korrupció.
Az r15 base swap csapda
Az egyik kézenfekvőnek tűnő ötlet: az r15 pointer tábla báziscímét futásidőben átírjuk egy klón pointer tábla címére, ahol a kívánt térképek más adatokra mutatnak. Ez elméletben az összes r15-felhasználót egyszerre irányítaná át. A baj ott van, hogy a Bosch variant switch infrastruktúra (a CAL_SetActiveDataset és a hozzá tartozó callback dispatch) egy atomi műveletet hajt végre bootkor:
; 1. Base pointer kiválasztás és tárolás: stw r3, CAL_BasePtr(r13) ; r15 base pointer - RAM ; 2. Callback dispatch (12 entry, 12 byte/entry): bl CAL_NotifySubscribers ; minden feliratkozott modul értesítése ; Eredmény: 12 derived érték frissítve a RAM-ban ; A base pointer ÉS a 12 derived érték EGYÜTT konzisztens
Ha egy mapswitch patch csak a base pointert írja felül, de nem hívja meg a callback dispatch-et, a 12 derived érték stale állapotban marad. A Bosch belső logika erre rájön: az r15 base az "új" kalibrációra mutat, de a derived értékek még a "régiből" valók. A tesztjeim alapján az eredmény: CAN frame korrupció, gázpedál nem reagál, hamis riasztás motor túlmelegedés miatt.
A megoldás: végső output cella felülírás
A megbízható megközelítés nem a pointer-szintű átirányítás, hanem a nyomaték-pipeline végső kimeneti cellájának felülírása. A Bosch torque pipeline végén van egyetlen RAM cella, ami az interpolált nyomaték limiter értéket tartalmazza. Ezt egyetlen downstream modul olvassa, és ami a lényeg: nincs rajta konzisztencia-ellenőrzés.
Ha a mapswitch patch az összes Bosch számolást békén hagyja (stock pointer tábla, stock base, stock variant switch), és csak ezt a végső output cellát írja felül egy kívánt értékkel, semmilyen belső ellenőrzés nem sérül. A többi alrendszer (füst limiter, boost limit, váltóvédelem) változatlanul működik, a motor védelme biztosított marad.
; Hook: közvetlenül a stock nyomaték-kimenet írása UTÁN lbz r12, map_counter(r13) ; aktív térkép index (0/1/2) cmpwi r12, 0x0 beq skip ; map 0 → stock, ne módosíts ; Kalibráció-vezérelt felülírás: lis r11, CAL_TQ_HI ori r11, r11, CAL_TQ_LO lha r3, 0x0(r11) ; r3 = TQ override érték (kalibrációból) sth r3, TQ_output(r13) ; felülír a kívánt értékkel skip: ; Stolen instrukciók (a hook által kiszedett eredeti kód): addi r1, r1, 0x10 ; stack frame felszabadítás blr ; visszatérés
Az override érték a flash kalibrációs területén van tárolva, WinOLS-ben map definícióként kalibrálható, és maga a kód soha nem módosul. Ez a "data driven patch" filozófia: a logika a cave-ben van, az értékek meg a kalibrációs területen. Ha változtatni akarunk, csak az adatot piszkáljuk, a patch-hez nem kell hozzányúlni.
Záró gondolat
Négy BMW platform, két ECU típus (CP35, C31), négy szoftver-verzió, egy végeredmény: a BMW EDC16 kalibrációs architektúra az MPC562/563-alapú változatokban egységes. Ugyanaz a pointer tábla, ugyanaz a dual variant, ugyanaz a flash mirror, ugyanaz a script - csak más paraméterekkel. Az MPC555-ös DDE5 egy korábbi generáció, ami direkt címzéssel dolgozik: egyszerűbb statikus elemzés, de nehezebb testreszabás.
A mapswitch fejezet megmutatta, miért nem triviális a többtérképes konfiguráció: a Bosch konzisztencia-mechanizmusai nem tűrik a félkész módosítást. A megbízható út nem a pointer rendszer megkerülése, hanem a pipeline végső output cellájának kontrollált felülírása - ott, ahol a Bosch belső logika már nem ellenőriz.
A tanulság: ha egy megközelítés több platformon is ugyanazt az eredményt adja, az nem véletlen, hanem rendszerszintű igazolás. Az annotáló script bármelyik MPC562-alapú BMW EDC16-on működik, a konfigurációs paraméterek cseréjével. A gépezet ugyanaz, csak a címek mások. Az MPC555-höz a direkt címzési mód felismerése kell, de az annotálási logika ott is adaptálható.
< VISSZA A LOGOKHOZ