Update.
This effort is ongoing on a private branch so no need to worry about
changes yet . . .
I finished the recipe below and was able to create and run a split
image.
I followed the steps below with some minor mods…
1) I did not build a temporary library. Instead I just removed the
symbols from the .a’s in the app (actually renamed them so we could
trace
back if this ever fails to work). Then I link the app against its
libraries and also with a the .elf from the loader using
--just-symbols=loader.elf. This allows the symbols to properly
resolve.
2) Instead of copying loader.elf and removing the symbols that were
unused
in the shared link, I renamed them with a _loader suffix. This
probably
needs a bit more work, as I should probably copy the loader.
3) I didn’t merge any symbols from apps/app and apps/loader. See
ramifications on stack usage below.
For a simple test I build two versions of blinky to run as a split
application: blinky runs as the loader app which blinks at 1Hz and
blinky2
runs as the split app that blinks at 2 Hz. (NOTE: I have not added the
ability for the loader to validate and run the app yet, so I do this
manually through the debugger).
I get the following for image sizes.
Pauls-MacBook-Pro-4:src paulfdietrich$ newt size app
Size of Application Image: app
...
objsize
text data bss dec hex filename
896 4 4884 5784
1698 /Users/paulfdietrich/dev/new_boot/bin/targets/app/app/apps/blinky2/bli
nky2.elf
Size of Loader Image: loader
...
objsize
text data bss dec hex filename
7684 1172 2376 11232
2be0 /Users/paulfdietrich/dev/new_boot/bin/targets/app/loader/apps/blinky/b
linky.elf
You can see that most of the code (since most except startup etc are
duplicates) is in the loader app (7.6 k). A small amount (896 bytes)
lives in the app. If I boot the system, it runs blinky just fine. If
I
boot, set a breakpoint to main in blinky and set the PC to the reset
vector of the second image and continue, it runs the blinky2 just
fine.
The debugger shows that code running os stuff from the first image
partition. Success!!
When I did the same for two bluetooth apps (I used two identical
copies of
bletiny), I could see that the loader was about 100k and the app was
about 1k. Great news!! As this means we will have tons of app space
for
bluetooth apps even with the whole stack present.
There was also an unpleasant surprise — RAM usage was bad for the
app!!!
I think this is because we put the loader stacks in BSS that we define
in
app/main.c. There is no way when linking the app against the loader
to
know that the BSS data from the loader is stack and re-use it— it
could be
global data that is uninitialized and used by the loader code when
running
as part of the app. There’s a simple fix, malloc stacks for the
loader in
main.c instead of statically allocating them.
There’s still a lot to do to make this production ready, but this
was the
highest risk part that I wasn’t sure would work.
The one tricky piece remaining is how to better compare a symbol to
determine its identical. Right now I use the name, and size, but
compiler
defines that are different between the two builds could cause this to
fail
(imagine a #define FOO 1 or #define FOO 2 inside some ifdef that is
based
upon a feature). So I am not sure what to do with this. I’ve
almost
convinced myself this won’t happen, but want to try to add something
to
the code to validate — even if it just produces an error at the end.
That’s all for now. I still expect to be at this for a while to get
newt
put back together, sort out how to debug this kind of thing, and most
importantly, write an real “loader app” that can download split
images via
bluetooth and net_mgr.
Paul
On 6/27/16, 2:37 PM, "[email protected]" <[email protected]> wrote:
Im on to how to implement this in newt. Here are my thoughts for
review.
I am not that familiar with Go or with Newt code, so I’m thinking
the most
important thing is to get this to work and then figure out how to
re-architect this in newt to be “nice”. Here are roughly the
steps I will
take.
Design
0) Modify the target.go to contain a
loader=<@apache-mynewt-core/apps/loader> or something that specifies
an
app to run as the loader. Unless there is objections I will name this
‘loader’ or ‘split-loader'
1) Modify struct Builder Build.go. Factor out the following into a
app
class since the split app target has two apps: Packages, Features,
APIs, and BuildPackage into an app class. Put this into apps.go in
some
apps package. Then re-use this package to build dependencies and
features
for the loader and app separately.
2) Modify Build.go to allow compile and link in two separate steps so
we
can compile the app without having to link it right away.
3) Modify Build.go Builder to contain two apps (loader and app)
instead of
1. If the loader app was nil, that is OK.
4) Modify build to build the loader first if it exists (build and
link).
5) Modify build to compile the app after the loader excluding
linking.
6) Create a list of common packages. This would be done by comparing
the
two package lists in the two apps. If the two apps have the same
package
name, compare the .a files to make sure they are appropriately
identical
(since features and cflags can wreak havoc). Build a temporary
library
split.a that combines all the packages.
7) Extract the symbols from loader.elf.
8) For each symbol in loader.elf. If the symbols in in split.A,
remove it
from split.a, else remove it from loader.elf. I’ll probably slurp
in both
symbol files into maps so I don’t repeatedly call nm.
9) Link app.elf.
Please comment. This feels like a lot of magic to me.
Paul
On 6/24/16, 3:47 PM, "aditi hilbert" <[email protected]> wrote:
That’s generally the practice for units in field where security is
a
concern (e.g. industrial IoT devices).
aditi
On Jun 24, 2016, at 3:18 PM, will sanfilippo <[email protected]>
wrote:
My expectation would be that the bootloaders would be locked down
and
thus not upgradeable in the field…
Will
On Jun 24, 2016, at 2:04 PM, [email protected] wrote:
Thanks for the comments. I think there may be some nomenclature
issues
here related to the bootloader.
In my design the bootloader is the fixed bit at 0x0 containing the
reset
vectors and the code swap and run PIR images.
In the Nordic docs (since we are sometimes focused on the NRF
chips)
they
call this the MBR. The bootloader in NRF-speak is another app that
can
be
loaded that can replace the soft device (but I don¹t think it
replaces
the
MBR)
Paul
On 6/24/16, 1:43 PM, "marko kiiskila" <[email protected]> wrote:
Hi all,
On Jun 24, 2016, at 1:16 PM, David G. Simmons <[email protected]>
wrote:
On Jun 24, 2016, at 12:55 PM, [email protected]
<mailto:[email protected]>
wrote:
I think your main concern was about boatload upgradability. My
assumption
was that in the field you would NOT upgrade it. From what I
have
heard,
most folks lock the flash sectors of the boot loader before
shipping.
This is a larger question for the group. How do others feel
about
boot
loader upgrade. Regarding your upgrade procedure below, if a
boot
loader
upgrade fails in step B, the thing would become a brick.
You¹re right that this is probably a larger discussion
regarding the
filed-upgradability of a bootloader.
That being said, I¹m not sure how a failure in step B yields a
brick.
This would be power outage/system reset when BIR is erased, and
new
bootloader has not been copied in yet. MCU always starts
executing
whatever
is in BIR at reset.
Because of this time window, I would not update boot loader.
Here¹s a scenario for an upgradeable bootloader:
1) Bootloader is signed with a SHA or PKI from a host computer
‹
only
bootloaders signed with this key will be accepted except over
direct
serial/jtag connection. If you have physical access to the
device,
you
win. This also allows a new host to Œtake over¹
responsibility for
delivering bootloaders to devices by providing a new bootloader
with a
new SHA/PKI key.
2) new bootloader image is sent to the device and stored in
SAR ‹
could
be stored in SIR
a) new bootloader key is checked against current bootloader
key.
If
keys don¹t match, new bootloader image is erased otherwise
continue
b) Keys match, so new bootlaoder is written to PIR and device
reboots
to new bootloader
c) if boot to new bootlaoder in PIR succeeds, PIR region is
written
to BIR and reboots ‹ we have successfully updated the
bootloader
d) if boot to new bootloader in PIR fails, reboots to old
bootloader,
and PIR is erased.
This has the advantage ‹ by first loading the bootloader into
SAR
for
key
validation ‹ of ensuring that a rogue bootloader does not
wipe out
an
existing AIIC/ADIC pair. It required 3 reboots to field-upgrade
a
bootloader, but it maintains system integrity. Using this
method,
bootloaders could be updated over bluetooth as well, as long as
the
keys
match, making filed upgrades of bootloaders possible.
In step B, the new bootloader is in PIR while the old bootloader
remains in BIR. System reboots to PIR. If that fails, meaning
the
new
bootloader is bad, it simply reboots our of BIR (the old
bootloader,
and
erases the bad bootloader in PIR. If it succeeds, meaning the
new
bootloader is good, it copies itself to BIR and reboots from
BIR,
then
erases PIR and is ready for an application to be sent.
Bootloader code is not position independent. Maybe it¹s possible
to
do with the later versions of gcc, but it was not the last time I
tried.
I was not able to figure out how to keep .data/.bss at fixed
offset,
while
allowing .text to float. The bootloader at mynewt does place
quite a
few things at .data/.bss, so some rewrite would be needed
(assuming
gcc does not have this feature for our target CPUs).