Hallo zusammen,
ich spiele gerade ein wenig zum Thema Bootloader und FW-Update rum.
Fange gerade mit einem primitiven Bootloader an und habe mich an dem
Beispiel von IAR orientiert:
https://www.iar.com/support/tech-notes/general/creating-a-bootloader-for-cortex-m/
Wie sehen kann, steht ja im ersten DWORD die Speicheradresse des Stack
und im zweiten DWORD dann die Sprungadresse zum Programm.
Letzteres wird ja vom Linker gesetzt und hängt von der
Speicherkonfiguration des Linkerfiles ab.
Mein FW-Update-Konzept sieht eine Dreiteilung des internen
Flashspeichers vor: Bootloader | Image 1 | Image 2
Die Speicherbereich Image 1 und Image 2 sollen abwechselnd mit der
aktuellen FW beschrieben werden.
Im EEPROM des Controllers stehen dann Sachen, welches Image gerade
aktuell ist (später auch Prüfsummen und ähnliches).
Das Problem ist jetzt eben, dass ich zu Compile-Zeit nicht weiß in
welchem Speicherbereich (1 oder 2) das FW-Image dann kommt.
Wie mache ich dass dann mit der Sprungadresse?
Und vermutlich nicht nur die! - Sämtliche Funktionsadressen im FW-Image
zeigen ja dann auf den falschen Speicherbereich, oder nicht!?
Funktioniert das überhaupt so, wie ich mir das vorstelle?
Wäre super, wenn ihr mir helfen könnt!
Danke & Grüße
Martin
Martin Lohmeyer schrieb:> Sämtliche Funktionsadressen im FW-Image> zeigen ja dann auf den falschen Speicherbereich, oder nicht!?
So sehe ich das auch. Zur Compile-Zeit existiert die Adresse die im
Linker-Script oder in den Configs hinterlegt ist.
Du könntest deine FW für beide Configs erstellen und falls der
Bootloader auch das flashen übernimmt, kann er entscheiden, welche er
empfangen möchte und flasht diese an die richtige Stelle.
Die Gültigkeit der Firmware müsstest du nicht einmal extra im EEPROM
ablegen.
Ich lösche die 4 Byte des RESET_VECTORS, falls beim Flashen der Firmware
etwas nicht funktioniert hat. Somit erkenne ich ob die FW die dort liegt
gültig ist. Sonst springt der Bootloader erst gar nicht an die Stelle.
Adam P. schrieb:> So sehe ich das auch. Zur Compile-Zeit existiert die Adresse die im> Linker-Script oder in den Configs hinterlegt ist.
Verdammt!
Adam P. schrieb:> Warum möchtest du überhaupt die FW 2x im Flash halten (alt / & neu)?
Weil das die für mich einfachste und schönste Lösung ist. Ansonsten
müsste ich im Bootloader nach einem Update immer Image 2 an die Stelle
von Image 1 kopieren und dann das neue Image booten.
Direkt an die Stelle von Image 1 kann ich nicht schreiben, da die Daten
über UART in einem etwas komplexen Protokoll rein kommen für dessen
Abhandlung Timer, und die RTOS Laufzeitumgebung benötigt werden. Das
kann man nicht in den RAM oder so auslagern, zumal der Platz da eh eng
bemessen ist.
Deshalb war die Idee alles gemütlich an die Speicherstelle 2 zu
schreiben, zu prüfen und neu zu starten ...
Also soll die FW-Flash-Funktionalität in der FW selbst sein:
FW 1 flasht FW 2
oder
FW 2 flasht FW 1? Richtig verstanden? Und der Bootloader soll dann nur
entscheiden welche er beim Restart anspringt?
Aber dann müsste das doch auch gehen.
- Beide FW erstellen
- Aktive FW teilt mit welche Sie ist oder welche FW sie empfangen möchte
- Übertragen & abspeichern
- Falls CRC(Prüfung) + Flashen OK, dann löscht aktive FW ihren reset
vector Eintrag im Flash und erzeugt ein Reset (oder halt eine Info im
EEPROM)
- Bootloader prüft an beiden Basis-Adr. welcher reset vector noch gültig
ist oder halt den Eintrag im EEPROM und springt die FW an.
Denke das müsste machbar sein.
Adam P. schrieb:> Also soll die FW-Flash-Funktionalität in der FW selbst sein:> FW 1 flasht FW 2> oder> FW 2 flasht FW 1? Richtig verstanden? Und der Bootloader soll dann nur> entscheiden welche er beim Restart anspringt?
Richtig.
Adam P. schrieb:> Aber dann müsste das doch auch gehen.> - Beide FW erstellen> - Aktive FW teilt mit welche Sie ist oder welche FW sie empfangen möchte> - Übertragen & abspeichern> - Falls CRC(Prüfung) + Flashen OK, dann löscht aktive FW ihren reset> vector Eintrag im Flash und erzeugt ein Reset (oder halt eine Info im> EEPROM)> - Bootloader prüft an beiden Basis-Adr. welcher reset vector noch gültig> ist und springt die FW an.>> Denke das müsste machbar sein.
Eigentlich ja.
Hat aber drei Nachteile:
1. Das Update-Image ist immer doppelt so groß, da ja beide Versionen
enthalten sein müssen
2. Es müssen dementsprechend auch immer zwei Versionen des Projekts
kompiliert werden.
3. Im Protokoll müsste diese "Abhandlung" hinterlegt werden ...
Ein "Suchen und Ersetzen" während dem Flashen, bei dem die Adressen von
einen in den anderen Adressbereich über einen Offset geändert werden,
geht vermutlich nicht, oder?
(Ist vermutlich eine bescheuerte Idee ... ! - Ich vermute ich muss mit
den drei Nachteilen leben müssen!)
Martin Lohmeyer schrieb:> Funktioniert das überhaupt so, wie ich mir das vorstelle?
Es gibt controller (z.b. STM32L4...) die haben 2 flash Bänke, die sich
auf HW-Ebene umschalten lassen.
Alternativ müsstest Du gucken, ob Du Deinen Compiler dazu bringen
kannst, relocatable code zu erzeugen. Dann müsstest Du nur die interrupt
vector table entsprechend anpassen.
mfg Torsten
Martin Lohmeyer schrieb:> Direkt an die Stelle von Image 1 kann ich nicht schreiben, da die Daten> über UART in einem etwas komplexen Protokoll rein kommen für dessen> Abhandlung Timer, und die RTOS Laufzeitumgebung benötigt werden. Das> kann man nicht in den RAM oder so auslagern, zumal der Platz da eh eng> bemessen ist.
Warum das nicht alles in den Bootloader mit reinpacken? Wenn Dein
Bootloader dieses Protokoll beherrscht, dann kann er komplett unabhängig
von der alten Firmware-Version agieren, was deutlich sicherer ist. Und
das braucht immer noch weniger Platz, als ein komplettes 2. FW-Image zu
speichern.
Welchen mc benutzt Du?
Viele Grüße, Stefan
Martin Lohmeyer schrieb:> Das Update-Image ist immer doppelt so groß, da ja beide Versionen> enthalten sein müssen
Nein, sehe ich nicht so. Du hast in deinem Projekt 2 Configs und
erstellst diese beiden, dann hast du zwei FW z.b:
- Release_FW_1
- Release_FW_2
Und per Protokollabsprache wird dann das richtige übertragen.
Martin Lohmeyer schrieb:> Ein "Suchen und Ersetzen" während dem Flashen, bei dem die Adressen von> einen in den anderen Adressbereich über einen Offset geändert werden,> geht vermutlich nicht, oder?> (Ist vermutlich eine bescheuerte Idee ... ! - Ich vermute ich muss mit> den drei Nachteilen leben müssen!)
Das ist nicht unüblich. Letztendlich ist bei jedem Programm, dass auf
einem modernem OS läuft zur Linkzeit nicht klar, an welche Adresse es
beim Start geladen wird:
https://en.wikipedia.org/wiki/Relocation_(computing)
Diese Tabelle müsstet Du idealerweise dann im RAM halten können, bzw. so
über das Image verteilen, dass der Teil, den Du im RAM halten müsstest
klein genug.
Stefan K. schrieb:> Warum das nicht alles in den Bootloader mit reinpacken? Wenn Dein> Bootloader dieses Protokoll beherrscht, dann kann er komplett unabhängig> von der alten Firmware-Version agieren,
Weil das echt zuviel für ein Bootloader wird ... Grob überschlagen würde
er auf 30k Flash anwachsen.
Stefan K. schrieb:> Welchen mc benutzt Du?
STM32L151RD
Martin Lohmeyer schrieb:> Weil das echt zuviel für ein Bootloader wird ... Grob überschlagen würde> er auf 30k Flash anwachsen.
Der STM32L151RD hat doch 384kb Flash, wo ist das Problem?
Bei einem 32kb Bootloader bleibt Dir 352kb für die Firmware.
Bei einem Minimal-Bootloader, der 2* die Firmware vorhalten muss, bleibt
Dir
(384kb - 8kb) / 2 = 188kb für jedes Firmware-Image.
Viele Grüße, Stefan
Wenn die Update Routine im booter ist, kann sie nicht so einfach
ausgetauscht werden. Das ist ggf ein schwerwiegenden Nachteil. Weiter
wird er dadurch komplexer und hat mehr Fehler.
Ich bin auch generell dafür den booter so schlank wie möglich zu
gestalten. Je weniger Code um so weniger Fehler.
Und Software ist nie fehlerfrei.
Bei Update Routinen im Image immer überprüfen ob das da Update aus
beiden danach noch tut.
Mein Bootloader hat ca. 46K.
Aber wie ABC schon schrieb,
beide Seiten haben ihre Vor-& Nachteile.
Sollte sich die Update Routine wirklich mal ändern, wäre ich dann wohl
auch für die Variante von ABC.
Hast halt bissel mehr Aufwand beim erzeugen der FW (2x Compilieren) aber
dafür wäre es flexibel.
Stefan K. schrieb:> (384kb - 8kb) / 2 = 188kb für jedes Firmware-Image.
Ein wenig muss man auch auf die Sectoren achten...
Im aktuellen Stand ist die FW knapp 153k groß...
ABC schrieb:> Ich bin auch generell dafür den booter so schlank wie möglich zu> gestalten. Je weniger Code um so weniger Fehler.>> Und Software ist nie fehlerfrei.
Noch ein Punkt, was gegen die Sache mit "alles im Bootloader" spricht:
Das Protokoll darf sich nie ändern!
Liegt alles in der FW selbst, kann man das mit einem FW-Update eben
beheben. Im Bootloader wird schwer ...
Hmmm ...
Ganz anderer Vorschlag! - Gar kein Bootloader verwenden, sondern die
zwei Bänke des Controllers verwenden:
http://www.st.com/content/ccc/resource/technical/document/application_note/group0/ab/6a/0f/b7/1a/84/40/c3/DM00230416/files/DM00230416.pdf/jcr:content/translations/en.DM00230416.pdf
Das heißt:
1. Aktuelle FW flasht neues Image in die andere Bank.
2. Aktuelle FW prüft die Prüfsumme
3. Falls alles passt wird das Boot bit (BFB2 gesetzt, oder gelöscht)
Nachtrag: Habe eben erst gesehen, dass da noch zwei Antworten kamen,
während ich geantwortet habe!
Adam P. schrieb:> Nein, sehe ich nicht so. Du hast in deinem Projekt 2 Configs und> erstellst diese beiden, dann hast du zwei FW z.b:> - Release_FW_1> - Release_FW_2>> Und per Protokollabsprache wird dann das richtige übertragen.
Ja, das meine ich ja. Aber Release_FW_1 und Release_FW_2 müssten dann
halte beide beim Host sein, damit das richtige übertragen werden kann.
Das meine ich mit doppelter Größe... - Also beim Host.
Torsten R. schrieb:> Das ist nicht unüblich. Letztendlich ist bei jedem Programm, dass auf> einem modernem OS läuft zur Linkzeit nicht klar, an welche Adresse es> beim Start geladen wird:>> https://en.wikipedia.org/wiki/Relocation_(computing)>> Diese Tabelle müsstet Du idealerweise dann im RAM halten können, bzw. so> über das Image verteilen, dass der Teil, den Du im RAM halten müsstest> klein genug.
Funktioniert dieses Reallocation auch bei einem embedded Controller
(Cortex-M3)?!?
Martin Lohmeyer schrieb:> Funktioniert dieses Reallocation auch bei einem embedded Controller> (Cortex-M3)?!?
Wenn Du eine tool chain hast, die Dir entsprechende binaries für deine
Plattform generiert, wüste ich nicht, warum es nicht funktionieren
sollte. Gff. müsstest Du das Format noch etwas anpassen.
Martin Lohmeyer schrieb:>> Warum das nicht alles in den Bootloader mit reinpacken? Wenn Dein>> Bootloader dieses Protokoll beherrscht, dann kann er komplett unabhängig>> von der alten Firmware-Version agieren,>> Weil das echt zuviel für ein Bootloader wird ... Grob überschlagen würde> er auf 30k Flash anwachsen.
Dann macht man halt nur einen Flash-Kopierer als Bootloader. Die
Firmware selbst packt ihr Update in den freien Flash Bereich (der groß
genug sein muss) und ruft den Bootloader erst am Schluss zum Umkopieren
der Daten auf, d.h. beim Update werden einfach die Programmdaten von
einer höheren auf eine niedrigere Addresse kopiert.
Damit es keine Probleme bei plötzlichen Stromausfällen gibt muss der
Bootloader eine oder zwei Flash Pages für Status Infos benutzen. Dafür
ist er selbst aber schön klein.
Naja ... Das der Linker entsprechend die Adressen anpassen kann ist ja
klar.
Deshalb auch zwei Images.
Meine Idee war immer nur eine FW für Image 1 zu kompilieren.
Wenn das Gerät dann erkennt, dass es das ganze in Flash 2 schreiben
muss, sucht es im übertragenen Image nach den Sprung-Adressen und
ersetzt sie durch die richtigen.
Was aber auch bescheuert ist, da nämlich dann auf jeden Fall die
Prüfsumme falsch ist ...
Alles doof! :-(
Ich kenne das so:
- Firmware lädt Update in einen separaten Flashbereich, z.B. obere
Hälfte und checkt mit CRC, ob das Image OK ist. Wenn nicht, gibt's ne
Fehlermeldung im Protokoll, und es wird abgebrochen.
- Firmware setzt irgendwo im Flash ein "hallo, Update ist da"-Flag.
- Firmware rebootet.
- Bootloader guckt auf das Update-Flag, ob es gesetzt ist.
- Bootloader prüft selber nochmal das Update-Image per CRC.
- Bootloader kopiert das Update in den Firmware-Bereich.
- Bootloader checkt CRC der Firmware - wenn OK, wird das "Update ist
da"-Flag resettet, sonst nochmal kopiert.
- Bootloader startet Firmware.
Mit so einem Design kann auch zu jedem Zeitpunkt der Strom abgedreht
werden. Wenn das beim Kopieren der Firmware im Bootloader passiert,
wiederholt er die Nummer beim nächsten Start eben.
Man kann auch z.B. noch das runtergeladene Update komprimieren und auch
so komprimiert speichern. Dann braucht man nicht das Doppelte an Flash.
Dafür wird der Bootloader aufwendiger, weil er dekomprimieren können
muß.
Außerdem kann der Bootloader auch ohne gesetztes Update-Flag die
eigentliche Firmware vor dem Starten per CRC checken, und wenn das
fehlschlägt, nachsehen, ob das letzte Update vielleicht noch verfügbar
ist zum Drüberkopieren.
Zur Sicherheit sollte außerdem im Bootloader noch ein rudimentäres
Protokoll sein, mit dem man ein Hexfile in den Updatebereich laden kann.
Das ist, wenn alles schiefgeht, als Notnagel. Also wenn die Firmware
kaputt ist und auch kein letztes Update rumliegt.
Das sollte nur auf einem UART mit geringer Baudrate sein. Hauptsächliche
Einschränkung, weswegen das nicht für das reguläre Update gedacht ist:
remote Update geht so nicht.
Martin Lohmeyer schrieb:> sucht es im übertragenen Image nach den Sprung-Adressen und> ersetzt sie durch die richtigen.
...wie hast du dir das angedacht?
Wenn in einem Update Funktionen verändert wurden bzw. neue hinzukamen,
woher soll der Bootloader wissen was er wie zu ersetzen hat.
Und die Funktionen sind doch nicht immer gleich groß oder liegen immer
an der selben stelle, was mit einem Offset zu lösen wäre - aber
bezweifel, dass es dafür eine Lösung gibt.
Lasse mich jedoch gern eines besseren belehren.
Nop schrieb:> Ich kenne das so:>> - Firmware lädt Update in einen separaten Flashbereich, z.B. obere> Hälfte und checkt mit CRC, ob das Image OK ist. Wenn nicht, gibt's ne> Fehlermeldung im Protokoll, und es wird abgebrochen.> - Firmware setzt irgendwo im Flash ein "hallo, Update ist da"-Flag.> - Firmware rebootet.> - Bootloader guckt auf das Update-Flag, ob es gesetzt ist.> - Bootloader prüft selber nochmal das Update-Image per CRC.> - Bootloader kopiert das Update in den Firmware-Bereich.> - Bootloader checkt CRC der Firmware - wenn OK, wird das "Update ist> da"-Flag resettet, sonst nochmal kopiert.> - Bootloader startet Firmware.
Entspricht so in etwa dem bestehenden Speicherkonzept.
Nur das der Bootloader immer Image 1 lädt und bei einem update zuvor die
aktuelle FW von Image 2 Speicherbereich in den Image 1 Speicher bereich
kopiert.
D.h. ein Backup wäre nicht da, außer man hat genug Speicher für beide
8ggf. komprimierte) Images.
Sind Sie komprimiert darf aber beim Entpacken (und Schreiben in das
Flash) nichts schief laufen ...
Adam P. schrieb:> ...wie hast du dir das angedacht?> Wenn in einem Update Funktionen verändert wurden bzw. neue hinzukamen,> woher soll der Bootloader wissen was er wie zu ersetzen hat.>> Und die Funktionen sind doch nicht immer gleich groß oder liegen immer> an der selben stelle, was mit einem Offset zu lösen wäre - aber> bezweifel, dass es dafür eine Lösung gibt.> Lasse mich jedoch gern eines besseren belehren.
Ja ... war ja nur ein Gedanke, wenn quasi ein Jump immer im
Hex-Datenstrom durch einen eindeutigen Identifier und einer
nachfolgenden Adresse mit 0x080????? beginnend steht ...
Martin Lohmeyer schrieb:> D.h. ein Backup wäre nicht da, außer man hat genug Speicher für beide> 8ggf. komprimierte) Images.
Ich würde mir von Kompression nicht al zu viel versprechen. Versuch mal
bin-files zu packen. Zumindest der ARM thumb code enthält so wenig
Redundanz, dass der sich nur schlecht kompremieren läßt.
> Ja ... war ja nur ein Gedanke, wenn quasi ein Jump immer im> Hex-Datenstrom durch einen eindeutigen Identifier und einer> nachfolgenden Adresse mit 0x080????? beginnend steht ...
Nein, Du must Dir , die Informationen, an welcher Stelle Korrekturen
vorzunehmen sind, von der tool chain geben lassen. Es sind ja nicht nur
Sprünge, die korregiert werden müssen (auch Adressen auf Konstanten,
Sprungtabellen, virtual function tables, etc.)
Hallo,
Nop schrieb:
> Ich kenne das so:>> - Firmware lädt Update in einen separaten Flashbereich, z.B. obere> Hälfte und checkt mit CRC, ob das Image OK ist. Wenn nicht, gibt's ne> Fehlermeldung im Protokoll, und es wird abgebrochen.> - Firmware setzt irgendwo im Flash ein "hallo, Update ist da"-Flag.
Unnötig
> - Firmware rebootet.> - Bootloader guckt auf das Update-Flag, ob es gesetzt ist.> - Bootloader prüft selber nochmal das Update-Image per CRC.
nochmal??
> - Bootloader kopiert das Update in den Firmware-Bereich.> - Bootloader checkt CRC der Firmware - wenn OK, wird das "Update ist> da"-Flag resettet, sonst nochmal kopiert.> - Bootloader startet Firmware.
Also ich habe so etwas mit einem Bekannten mal implementiert.
Ganz Grob
-3 Teile 1. Bootloader (8k) 2. Low Flash (Flash-8k)/2 3. High Flash
(Rest)
Der Bootloader berechnet den CRC von Low und High
Dann wird der CRC Wert mit dem CRC abgespeicherten CRC verglichen
Anhand der 4 Werte kann man nun entscheiden, was gemacht werden soll.
Beide CRC gleich und Valid das Programm wird gestartet
High Flash Valid und ungleich Low CRC High Flash wird runter kopiert.
Nun gibt es noch das Problem, dass man nicht weiß. wie groß das Bin File
ist, darum haben wir die ersten 2 Words ans ende von den Bin File
gepackt (Reset Vektor und Vektor Tabelle) um einmal den CRC Wert zu
speichern und danach die Länge vom Binary file.
Der Bootloader schaut dann beim starten ans Ende und läd die 2 words in
die passenden Register
Dazu kommt noch eine AES CBC Verschlüsselung über das gesamte Binary
File (bis auf die ersten 2 Words) welche vom Bootloader beim Flashen
rückgängig gemacht wird.
Laufen tut das auf einem STM32F1 AES in Assambler und in ca. 2ms wird
eine Page Entschlüsselt und geflasht.
Der gesamte Bootloader benötigt etwa 6k bis 7k.
Jens