Using GnuK with DFU bootloader
peter at digitalbrains.com
Sat Dec 22 15:05:12 CET 2018
On 21/12/2018 04:02, NIIBE Yutaka wrote:
> This sounds not great. Well, it's acceptable, because nobody should
> keep using Gnuk for DFU.
I think it's fine to just use the GnuK that was uploaded through DFU.
The bootloader is erased and readout protection enabled, it should be as
safe as a normal bare-programmed GnuK.
> In this morning, I tried to modify the code, but I realized that the
> sizeof(vector_table) is not determined at the compile time of main.c, in
> the current code of chopstx/entry.c. So, I didn't change the code.
I don't think it's necessary (see further mail), but as an aside: it
would still be possible by using an undefined symbol, and having the
linker fill in that symbol with the correct data. If you don't have a
custom linker script yet, it's cumbersome, because then you do need it.
But you already have a custom linker script, so it's easy.
> Please make sure the second reset works well. I mean, after writing
> Gnuk for DFU (by DFU), reset (which kills DFU by new SYS), reset again,
> and upgrade by reGNUal.
I tried that, and it seems to work fine. I did now also notice the
unintended behaviour with regard to MSP, but it doesn't change run-time
I somehow hadn't realized it was loading the initial MSP from 0x8001000.
This appears to be harmless because it is overwritten with the correct
value very soon after, before it is ever used. But I fixed it and in the
following discussion, I will describe the fixed behaviour, because the
subject matter is already complicated enough without discussing this as
It might be there's some confusion with all the vector tables while
trying to understand the code flow.
First, for easy reference, let's show the vector tables for a
On with the DFU stuff. The smallest DFU bootloader possible with this
code would be 4 KiB long, hence it would locate the user code at
0x8001000. If it would locate before that, the DFU-mode GnuK would be
overwriting itself with the new SYS. We distinguish two cases: DFU-mode
GnuK is at 0x8001000 or it is at a higher address.
First, let's take the, as far as I'm aware, hypothetical situation we
have a bootloader only 4 KiB large, and it loads user code at 0x8001000.
We have the following tables:
The latter two are from the DFU-mode GnuK, only the range
0x8000000-0x8000fff comes from stdaln-sys.elf.
When the Maple Mini boots, it ends up in stdaln-sys.elf::reset. This
sets SCR->VCR as it would be for a bare-programmed Maple Mini. It then
loads MSP with vector at 0x8001000, but this is equal to the initial
MSP at reset, so it is a no-operation. It seems unavoidable because we
need the first 4 KiB to be exactly equal to a bare-programmed chip so we
can reGNUal a normal GnuK into it later. It then jumps to
sys-stm32f103.c::reset from the vector table at 0x8001000, which again
modifies SCR->VCR and MSP to be correct for a GnuK at that origin
(0x8001000). In fact, it just overwrites the actions of
stdaln-sys.elf::reset, and correctly sets the initial MSP from
vector_table. Finally, it jumps to chopstx/entry.c::entry, which it
got from vector_table in the table at 0x8002000. After the jump to
sys-stm32f103.c::reset, no code from stdaln-sys.elf will ever run again,
until we do a reGNUal. And only sys-stm32f103.c::vector and
chopstx/entry.c::vector_table are ever used during further running.
We need to have the whole of stdaln-sys.elf unmodified at the first 4
KiB, because that's what we rely on if ever we update the GnuK with
reGNUal. But it only runs a few lines of code at first bootup, whose
actions even get nullified by the code that runs after.
Now the other situation, where the bootloader is larger than 4 KiB.
Let's say it loads code at 0x8002000. This is correct for the Maple
Mini. Then we have the following tables:
This is where that line of code we're discussing kicks in. The processor
boots, it goes to stdaln-sys.elf::reset, and all that code does is set
up SCR->VCR, load MSP from 0x8001000 and jump to the address at
0x8001004. Oops. That page is blank. So, I put the address of
sys-stm32f103.c::reset at 0x8001004 so it goes right. And with the new
fix, it gets the proper initial MSP. Once we end up in
sys-stm32f103.c::reset, the only vector tables that will ever be used
are sys-stm32f103.c::vector at 0x8002000 (!) and
choptx/entry.c::vector_table at 0x8003000. Only a few cycles after cpu
reset will anything in 0x8001000 be used. Only stdaln-sys.elf::reset
runs, which reads the first two vectors. During further running, ORIGIN
is set to 0x8002000, which is higher than that.
The SYS at the start of the memory might need vector_table at 0x8001000,
but it never runs again until reGNUal, and then only
flash_erase_all_and_exec() runs. I did assume flash_erase_all_and_exec()
would never need the vector_table, seeing how it is erasing all memory
but the first 4 KiB, which includes vector_table and currently all
entries in it.
But the SYS at 0x8002000 and the GnuK at 0x8003000 never refer to
anything at 0x8001000, so there is no need to put a vector table there.
If you expect SYS ever to be using vector_table even before it jumps to
chopstx/entry.c::entry, then yes, we have a problem. Faults and
interrupts that occur in sys-stm32f103.c::reset will for a short time
have a bogus vector table (currently interrupts are disabled, so there's
no problem). However, any such code would have to be considered well
anyway because it mixes code linked at different ORIGINs, and that might
be a recipe for disaster. Since it is currently not used and would
require special attention due to mixed ORIGINs if it were ever used, I
didn't think copying the vector table was called for.
But as long as sys-stm32f103.c::reset is as lean as it is now, there's
no problem. As soon as chopstx/entry.c::entry is entered all vectors are
fully correct, and only code from the high ORIGIN ever runs.
I thought it rather unlikely sys-stm32f103.c::reset would ever get so
heavyweight as to need interrupts, seeing how it is now.
I fixed the issue with the wrong MSP for a DFU larger than 4 KiB in
commit ca3312eb253e6a523e01b64903ebb08d336218ec. Since the MSP was
overwritten with the correct value before anything used the stack, the
original code ran fine. Interrupts were disabled until well after MSP
I also added a reset after flash_protect(). The flash programming manual
>The read protection is activated by setting the RDP option byte and
>then, by applying a system reset to reload the new RDP option byte.
so it's probably better to add that reset.
Incidentally, I also fixed two clean targets in the next commit,
b57c33c204f7fc5c04aab7b2ffd0f7e0bfdc78ea. It's a tiny change. Both can
be found in .
If you would still like to have all of vector_table at 0x8001000 (but
with the reset vector patched to sys-stm32f103.c::reset), I could write
that as well.
Also, I was happy to discover that actually, the boards I bought to
perhaps be handed out to some people at the 35C3 /do/ have the small
STM32duino bootloader that is required for GnuK in DFU-mode! Up until
now, I had been testing with an old board revision I bought in May 2017.
It turns out that in the boards I ordered a few weeks ago they updated
the bootloader (the schematic is unchanged, but they did change the
board, it now uses a micro-USB connector rather than mini-USB).
 Perhaps you could make it so a DFU-programmed GnuK can only ever be
updated with a --with-dfu configured GnuK, then the restriction could be
weakened if this ever becomes necessary.
I use the GNU Privacy Guard (GnuPG) in combination with Enigmail.
You can send me encrypted mail if you want some privacy.
My key is available at <http://digitalbrains.com/2012/openpgp-key-peter>
-------------- next part --------------
A non-text attachment was scrubbed...
Size: 488 bytes
Desc: OpenPGP digital signature
More information about the Gnuk-users