SpenceKonde / megaTinyCore

Arduino core for the tinyAVR 0/1/2-series - Ones's digit 2,4,5,7 (pincount, 8,14,20,24), tens digit 0, 1, or 2 (featureset), preceded by flash in kb. Library maintainers: porting help available!
Other
551 stars 143 forks source link

ATTINY3226 >> can't burn bootloader #835

Closed detamend closed 1 year ago

detamend commented 1 year ago

Chip: ATTINY3226 IDE: Arduino 1.19 Lib Version: 2.6.1 (current)

Since Mouser had some of those '3226 available, I grabbed some of those fine chips.

First played around with them on V2.5.11 - worked fine, burning the bootloader worked like a charm.

Updated to 2.6.1 - trying to burn the bootloader fails with:

Error: cannot parse fuse, 'invalid literal for int() with base 0: '{booltloader.CODESIZE}'' Error while burning bootloader.

error

I didn't set anything fancy - just turned the clock down to 10MHz. Trying different UPDI programmers/speeds didn't change the error.

Flashing the chip with a program works fine - just the bootloader won't work for me.

And yeah: tried a fresh downloaded IDE, grabbed a fresh version of the lib - no luck there.

Update: just tried different chips - I can't flash any bootloader under 2.6.1 (again: 2.5.11 worked fine). I'm expecting there's a problem on my side...

Any help would be greatly appreciated - Spence, love your work!!

SpenceKonde commented 1 year ago

This is a bug in 2.6.1 it has already been corrected for 2.6.2

Modern AVRs have three sections of flash: Bootloader: Code running in the bootloader section can write to flash anywhere on the flash except the bootloader and can write EEPROM and USERROW. Execution always starts at 0x0000 which is in the bootloader section. Upon leaving the bootloader section, if the BOOTRP bit was set (it can only be written from within the bootloader section), all reads from the bootloader section until the next reset will read 0x0000 (no-op), with the main consequence being that user code controlled by another party can't steal some proprietary bootloader (a big concern for many Microchip customers, our bootloader is open source so we DGAF about that). It also prevents leaving an entry point through which the application can rewrite itself (this is provided by both megaTinyCore and DxCore's bootloader - the one in megaTinyCore is relatively sophisticated, because the tinyAVR's check what section code is running from both when the page buffer is written and when the NVMCTRL.CTRLA register is written to fire the command that writes data (so even without BOOTRP, it would be quite challenging for an evil application to try to call the bootloader to modify itself). It's much more important on the Dx-series that only protects the SPM, or ST targeting the high half of the address space) against being run from the wrong section - without BOOTRP, someone could examine the bootloader from the application to locate the SPM or SPM Z+ instruction, then examine the following code to find a path to a "ret" (return) as soon as possible. Then they could write anywhere outside of the bootloader section, despite being in the application section. Since this is an open source platform and we specifically support this, that's all well and good, but the folks who are using this for "life safety critical applications" (the microchip development tools, used in combination with most modern AVRs, and in likely in accordance with some long list of rules that must be observed, are certified for use in life safety critical applications". You know, things like the antilock breaks in your car, or the fuel pump controllers on a jet airliner, the SCRAM system of a nuclear powerplant that it uses to shut down in the event of an emergency. Those obviously demand such protection features. Anyway, back to the sections. Application Code: Code running in the application section can write to flash located only within the Application Data section, and can write EEPROM and USERROW. It can read from anywhere (except, if BOOTRP is set, the bootloader section). It can still jump into the bootloader section in that case, but all it will read is NOOP instructions, until it gets to the end of the bootloader section, continuing to the start of the application code section - that's where the reset vector of the application is located) Application Data: If code is run from the application data section at all, it is minimally privileged - it can still read from the application section and jump back to it, but it can't write to the NVM, not flash, not eeprom, and not userrow.

The sizes of these can be configured by the fuses in units of 256 (tinyavr) or 512 (Dx) bytes. megaTinyCore uses only two configurations:

  1. Optiboot fits in 512b, so we use a 512b application section and the rest is application code. The last two bytes of the bootloader section contain a version number, somewhere in there - I forget where, as I wasn't the one who implemented this) there's an entry point for flash.h.
  2. Non-optiboot boards don't ever self program. They are thus set with a bootloaded size of zero blocks, which, per the datasheet, tells the device to consider the whole thing bootloader code, and hence unwritable except by UPDI.

DxCore uses four configurations:

  1. Again, optiboot fits in 512b, so that'sthe size of the bootloader section. The last two bytes are again a version number, but they are immediately preceeded by 4 very special bytes that I get the linker to place there as a numeric constant.... which just so happens to, if treated as instruction words, work out to SPM Z+, RET - so DxCore's Flash library executes a call instruction targeting the 253'rd instruction word in order to write the contents of r0 and r1 to the location pointed to by the Z pointer and increment Z (notably, incrementing Z this way handles RAMPZ correctly, otherwise it is handled incorrectly).
  2. In non optiboot configurations with write to app set to disabled (the default), again we set the size of the bootloader section to 0, disabling self programming.
  3. In non-optiboot configuration allowing limited areas of the flash to be programmed, the bootloader section is defined to the minimum size (this is a nuisance - it also means that very early in the startup process we have to set IVSEL to tell it that the vector table is inthe bootloader section), but otherwise that distinction doesn't matter to us (you can't have any self programming, even of the data section, if you don't have a bootloader section), and then sets the application code section to end at the appropriate place as dictated by the tools submenu option, and Flash.h can write to anywhere after that.
  4. Finally, in the case that optiboot is not used, and we want to say "fuck rules, this is my chip, I could use it for toilet paper if I wanted (ouch), let me scribble over the application if I want to", we use the same mock bootloader section as in 3, but here, we needed to make sure the 2 word snippet used in 1) was included in the first 512 bytes. It took some work to figure out a way to get the linker to put it into the first 512b without having to modify the crt.S file (the main obstacle was that PROGMEM variables are a high priority to be placed at low flash addresses - they come before almost everything - because you can only read PROGMEM variables with pgm_read_byte() and it's friends if they're in the first 64k of flash) - but I was able to find a section, .trampolines, into which I could insert 3 instructions of magic to make this possible. rjmp .+4 (jumps over the next two instructions, so if execution naturally gets there, it hops over the additional instructions unimpeded; apparently in some cases that happens during normal execution, and others it doesn't - I initially didn't have it, then discovered that some sketches wouldn't run if flash write was enabled, which that resolved) then the SPM Z+ and RET.

That's all well and good. Except that we follow the naming conventions that the datasheets use for the fuses that control the size of those sections aren't the same everywhere The tinies use BOOTEND, and APPEND (as in Application End) to potentially split the flash into the three sections. For obvious reasons, APPEND is a very bad name for that, since not only is "Append" a word, it's a word very commonly used in programming and rarely outside of it. Microchip quickly realized this (but not after it had released parts and thankfully they didn't try to change what it was called with find/replace in their docs and headers) The Dx-series uses BOOTSIZE (the size of the bootloader) and CODESIZE (the size of the bootloader and the application section, ie, exactly the same thing as APPEND).

Due to a bungled attempt to harmonize file formating between DxCore and megaTinyCore, the body of DxCore's platform.txt wound up in megaTinyCore's platform.txt during development. When found that and fixed it, I missed the bootloader recipe for DxCore.

So burn bootloader fails because wants to substitute in bootloader.CODESIZE and bootloader.BOOTSIZE. Those don't exist, so {bootloader.CODESIZE} is instead treated as literal text, and understandably the upload tool balks when told to set a fuse that can contain a numeric value from 0 to 255 to a string.

detamend commented 1 year ago

Hi Spence, thanks a bunch for the great explanation (my head is still spinning from all those under-the-hood information).

I indeed traced the error down to platform.txt (just by dumb searching for CODESIZE) and saw the differences between 2.5.11 and 2.6.1.

Any workaround I can do on my end?

SpenceKonde commented 1 year ago

change BOOTSIZE and CODESIZE to BOOTEND and APPEND.

SpenceKonde commented 1 year ago

Ohwait, no, other way around.

The damned fools at microchip have APPEND as fuse 7 and BOOTEND as fuse 8, but BOOTSIZE is 7 and CODESIZE is 8 on Dx: they swapped them. This is like a lot of the changes that have happened between tinyAVR and non-tinyAVR - changes to a slightly more sensible behavior but in the process muddies the water.

detamend commented 1 year ago

Thanks! I played a bit around with replacing stuff in platform.txt but had no luck with my experiments. Nevermind - I'll wait for 2.6.2.