Forum: Mikrocontroller und Digitale Elektronik Bootloader und "doppeltes" FW-Images


von Martin Lohmeyer (Gast)


Lesenswert?

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

von Adam P. (adamap)


Lesenswert?

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.
1
/*******************************************************************************
2
* Prüft ob eine Firmware im Flash vorhanden ist.
3
*
4
* param    void
5
*
6
* return   bool    true     Firmware gefunden
7
*                  false    keine Firmware vorhanden
8
*/
9
static bool fw_flash_bootable(void)
10
{
11
  uint32_t *p_flash = (uint32_t *)(BL_FW_FLASH_START + 4);
12
13
  /* Firmware gefunden */
14
  if((*p_flash != 0x00000000) && (*p_flash != 0xFFFFFFFF))
15
  {
16
    return true;
17
  }
18
  /* keine Firmware */
19
  else
20
  {
21
    return false;
22
  }
23
}

Warum möchtest du überhaupt die FW 2x im Flash halten (alt / & neu)?

Gruß

von Martin Lohmeyer (Gast)


Lesenswert?

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 ...

von Adam P. (adamap)


Lesenswert?

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.

: Bearbeitet durch User
von Martin Lohmeyer (Gast)


Lesenswert?

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!)

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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

von Stefan K. (stefan64)


Lesenswert?

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

von Adam P. (adamap)


Lesenswert?

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.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Martin Lohmeyer (Gast)


Lesenswert?

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

von Stefan K. (stefan64)


Lesenswert?

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

von ABC (Gast)


Lesenswert?

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.

von Adam P. (adamap)


Lesenswert?

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.

von Martin Lohmeyer (Gast)


Lesenswert?

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 ...

von Martin Lohmeyer (Gast)


Lesenswert?

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)?!?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.

von Jim M. (turboj)


Lesenswert?

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.

von Martin Lohmeyer (Gast)


Lesenswert?

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! :-(

von Nop (Gast)


Lesenswert?

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.

von Adam P. (adamap)


Lesenswert?

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.

von Martin Lohmeyer (Gast)


Lesenswert?

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 ...

von Felix F. (wiesel8)


Lesenswert?


von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

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.)

von Jens D. (jens) Benutzerseite


Lesenswert?

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.