Compare commits
2 Commits
cbddbc4457
...
dacd21bd78
| Author | SHA1 | Date | |
|---|---|---|---|
| dacd21bd78 | |||
| 90a29d872f |
@@ -142,6 +142,10 @@ endif()
|
|||||||
# compile-time constant; a different PUREBOOT_TIMEOUT builds the re-timed
|
# compile-time constant; a different PUREBOOT_TIMEOUT builds the re-timed
|
||||||
# binary a self-update then installs.
|
# binary a self-update then installs.
|
||||||
set(PUREBOOT_TIMEOUT 8 CACHE STRING "pureboot activation window, seconds")
|
set(PUREBOOT_TIMEOUT 8 CACHE STRING "pureboot activation window, seconds")
|
||||||
|
# Every mega runs the loader from its hardware boot section and boots the
|
||||||
|
# application at word 0; the tinies get the trampoline surgery. All megas
|
||||||
|
# assume a 16 MHz crystal at 115200 Bd; the tinies their internal RC at
|
||||||
|
# 57600 Bd over the software UART.
|
||||||
if(LIBAVR_MCU STREQUAL "attiny13a")
|
if(LIBAVR_MCU STREQUAL "attiny13a")
|
||||||
set(_pb_flash 1024)
|
set(_pb_flash 1024)
|
||||||
set(_pb_wrap "")
|
set(_pb_wrap "")
|
||||||
@@ -158,6 +162,51 @@ elseif(LIBAVR_MCU STREQUAL "attiny85")
|
|||||||
set(_pb_baud 57600)
|
set(_pb_baud 57600)
|
||||||
set(_pb_eeprom 512)
|
set(_pb_eeprom 512)
|
||||||
set(_pb_limit 510)
|
set(_pb_limit 510)
|
||||||
|
elseif(LIBAVR_MCU MATCHES "^atmega8a?$")
|
||||||
|
set(_pb_flash 8192)
|
||||||
|
set(_pb_wrap -Wl,--pmem-wrap-around=8k)
|
||||||
|
set(_pb_page 64)
|
||||||
|
set(_pb_hz 16000000)
|
||||||
|
set(_pb_baud 115200)
|
||||||
|
set(_pb_eeprom 512)
|
||||||
|
set(_pb_limit 512)
|
||||||
|
elseif(LIBAVR_MCU STREQUAL "atmega16")
|
||||||
|
set(_pb_flash 16384)
|
||||||
|
set(_pb_wrap -Wl,--pmem-wrap-around=16k)
|
||||||
|
set(_pb_page 128)
|
||||||
|
set(_pb_hz 16000000)
|
||||||
|
set(_pb_baud 115200)
|
||||||
|
set(_pb_eeprom 512)
|
||||||
|
set(_pb_limit 512)
|
||||||
|
elseif(LIBAVR_MCU MATCHES "^atmega32a?$")
|
||||||
|
set(_pb_flash 32768)
|
||||||
|
set(_pb_wrap -Wl,--pmem-wrap-around=32k)
|
||||||
|
set(_pb_page 128)
|
||||||
|
set(_pb_hz 16000000)
|
||||||
|
set(_pb_baud 115200)
|
||||||
|
set(_pb_eeprom 1024)
|
||||||
|
set(_pb_limit 512)
|
||||||
|
elseif(LIBAVR_MCU STREQUAL "atmega168a")
|
||||||
|
set(_pb_flash 16384)
|
||||||
|
set(_pb_wrap -Wl,--pmem-wrap-around=16k)
|
||||||
|
set(_pb_page 128)
|
||||||
|
set(_pb_hz 16000000)
|
||||||
|
set(_pb_baud 115200)
|
||||||
|
set(_pb_eeprom 512)
|
||||||
|
set(_pb_limit 512)
|
||||||
|
elseif(LIBAVR_MCU STREQUAL "atmega1284p")
|
||||||
|
# 128 KiB: wire flash addresses are word addresses, reads go through
|
||||||
|
# ELPM, and the PC's modulo wrap exceeds what --pmem-wrap-around models.
|
||||||
|
# The slot is 1 KiB — this chip's own smallest boot sector; the far
|
||||||
|
# machinery cannot fit 512 B (see pureboot/README.md).
|
||||||
|
set(_pb_flash 131072)
|
||||||
|
set(_pb_wrap "")
|
||||||
|
set(_pb_page 256)
|
||||||
|
set(_pb_hz 16000000)
|
||||||
|
set(_pb_baud 115200)
|
||||||
|
set(_pb_eeprom 4096)
|
||||||
|
set(_pb_limit 1024)
|
||||||
|
set(_pb_slot 1024)
|
||||||
else()
|
else()
|
||||||
set(_pb_flash 32768)
|
set(_pb_flash 32768)
|
||||||
set(_pb_wrap -Wl,--pmem-wrap-around=32k)
|
set(_pb_wrap -Wl,--pmem-wrap-around=32k)
|
||||||
@@ -167,14 +216,27 @@ else()
|
|||||||
set(_pb_eeprom 1024)
|
set(_pb_eeprom 1024)
|
||||||
set(_pb_limit 512)
|
set(_pb_limit 512)
|
||||||
endif()
|
endif()
|
||||||
math(EXPR _pb_base "${_pb_flash} - 512")
|
if(NOT DEFINED _pb_slot)
|
||||||
|
set(_pb_slot 512)
|
||||||
|
endif()
|
||||||
|
math(EXPR _pb_base "${_pb_flash} - ${_pb_slot}")
|
||||||
math(EXPR _pb_base_hex "${_pb_base}" OUTPUT_FORMAT HEXADECIMAL)
|
math(EXPR _pb_base_hex "${_pb_base}" OUTPUT_FORMAT HEXADECIMAL)
|
||||||
if(LIBAVR_MCU STREQUAL "atmega328p")
|
if(LIBAVR_MCU MATCHES "^atmega")
|
||||||
set(_pb_app 0)
|
set(_pb_app 0)
|
||||||
else()
|
else()
|
||||||
math(EXPR _pb_app "${_pb_base} - 2")
|
math(EXPR _pb_app "${_pb_base} - 2")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# simavr names its cores after the base dies; the A revisions run on them.
|
||||||
|
set(_pb_sim_mcu ${LIBAVR_MCU})
|
||||||
|
if(LIBAVR_MCU STREQUAL "atmega8a")
|
||||||
|
set(_pb_sim_mcu atmega8)
|
||||||
|
elseif(LIBAVR_MCU STREQUAL "atmega32a")
|
||||||
|
set(_pb_sim_mcu atmega32)
|
||||||
|
elseif(LIBAVR_MCU STREQUAL "atmega168a")
|
||||||
|
set(_pb_sim_mcu atmega168)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_executable(pureboot pureboot/pureboot.cpp)
|
add_executable(pureboot pureboot/pureboot.cpp)
|
||||||
target_link_libraries(pureboot PRIVATE libavr)
|
target_link_libraries(pureboot PRIVATE libavr)
|
||||||
target_compile_definitions(pureboot PRIVATE PUREBOOT_TIMEOUT=${PUREBOOT_TIMEOUT})
|
target_compile_definitions(pureboot PRIVATE PUREBOOT_TIMEOUT=${PUREBOOT_TIMEOUT})
|
||||||
@@ -205,7 +267,7 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:pbapp> $<TARGET_FILE:pbapp>.bin)
|
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:pbapp> $<TARGET_FILE:pbapp>.bin)
|
||||||
add_test(NAME pureboot.protocol
|
add_test(NAME pureboot.protocol
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
|
||||||
${PB_DEVICE} $<TARGET_FILE:pureboot> ${LIBAVR_MCU} ${_pb_hz} ${_pb_base_hex}
|
${PB_DEVICE} $<TARGET_FILE:pureboot> ${_pb_sim_mcu} ${_pb_hz} ${_pb_base_hex}
|
||||||
${_pb_page} ${_pb_baud} ${_pb_eeprom} $<TARGET_FILE:pbapp>.bin
|
${_pb_page} ${_pb_baud} ${_pb_eeprom} $<TARGET_FILE:pbapp>.bin
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||||||
${CMAKE_BINARY_DIR}/pbtest-work)
|
${CMAKE_BINARY_DIR}/pbtest-work)
|
||||||
@@ -215,7 +277,7 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
# installed one slot lower, must serve the full command set.
|
# installed one slot lower, must serve the full command set.
|
||||||
add_test(NAME pureboot.reloc
|
add_test(NAME pureboot.reloc
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbreloc.py
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbreloc.py
|
||||||
${PB_DEVICE} $<TARGET_FILE:pureboot> ${LIBAVR_MCU} ${_pb_hz} ${_pb_base_hex}
|
${PB_DEVICE} $<TARGET_FILE:pureboot> ${_pb_sim_mcu} ${_pb_hz} ${_pb_base_hex}
|
||||||
${_pb_page} ${_pb_baud} ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
${_pb_page} ${_pb_baud} ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||||||
${CMAKE_BINARY_DIR}/pbreloc-work)
|
${CMAKE_BINARY_DIR}/pbreloc-work)
|
||||||
set_tests_properties(pureboot.reloc PROPERTIES TIMEOUT 180
|
set_tests_properties(pureboot.reloc PROPERTIES TIMEOUT 180
|
||||||
@@ -233,7 +295,7 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
add_image_outputs(pureboot9)
|
add_image_outputs(pureboot9)
|
||||||
add_test(NAME pureboot.update
|
add_test(NAME pureboot.update
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbupdate.py
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbupdate.py
|
||||||
${PB_DEVICE} $<TARGET_FILE:pureboot> $<TARGET_FILE:pureboot9> ${LIBAVR_MCU}
|
${PB_DEVICE} $<TARGET_FILE:pureboot> $<TARGET_FILE:pureboot9> ${_pb_sim_mcu}
|
||||||
${_pb_hz} ${_pb_base_hex} ${_pb_page} ${_pb_baud} $<TARGET_FILE:pbapp>.bin
|
${_pb_hz} ${_pb_base_hex} ${_pb_page} ${_pb_baud} $<TARGET_FILE:pbapp>.bin
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||||||
${CMAKE_BINARY_DIR}/pbupdate-work)
|
${CMAKE_BINARY_DIR}/pbupdate-work)
|
||||||
|
|||||||
@@ -16,71 +16,502 @@
|
|||||||
{
|
{
|
||||||
"name": "atmega328p-generated",
|
"name": "atmega328p-generated",
|
||||||
"inherits": "base",
|
"inherits": "base",
|
||||||
"cacheVariables": { "LIBAVR_MCU": "atmega328p", "LIBAVR_REFLECT": "OFF" }
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega328p",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "atmega328p-reflect",
|
"name": "atmega328p-reflect",
|
||||||
"inherits": "base",
|
"inherits": "base",
|
||||||
"cacheVariables": { "LIBAVR_MCU": "atmega328p", "LIBAVR_REFLECT": "ON" }
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega328p",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "attiny85-generated",
|
"name": "attiny85-generated",
|
||||||
"inherits": "base",
|
"inherits": "base",
|
||||||
"cacheVariables": { "LIBAVR_MCU": "attiny85", "LIBAVR_REFLECT": "OFF" }
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "attiny85",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "attiny85-reflect",
|
"name": "attiny85-reflect",
|
||||||
"inherits": "base",
|
"inherits": "base",
|
||||||
"cacheVariables": { "LIBAVR_MCU": "attiny85", "LIBAVR_REFLECT": "ON" }
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "attiny85",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "attiny13a-generated",
|
"name": "attiny13a-generated",
|
||||||
"inherits": "base",
|
"inherits": "base",
|
||||||
"cacheVariables": { "LIBAVR_MCU": "attiny13a", "LIBAVR_REFLECT": "OFF" }
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "attiny13a",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "attiny13a-reflect",
|
"name": "attiny13a-reflect",
|
||||||
"inherits": "base",
|
"inherits": "base",
|
||||||
"cacheVariables": { "LIBAVR_MCU": "attiny13a", "LIBAVR_REFLECT": "ON" }
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "attiny13a",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8-generated",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega8",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8-reflect",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega8",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8a-generated",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega8a",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8a-reflect",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega8a",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega16-generated",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega16",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega16-reflect",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega16",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32-generated",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega32",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32-reflect",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega32",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32a-generated",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega32a",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32a-reflect",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega32a",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega168a-generated",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega168a",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega168a-reflect",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega168a",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega1284p-generated",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega1284p",
|
||||||
|
"LIBAVR_REFLECT": "OFF"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega1284p-reflect",
|
||||||
|
"inherits": "base",
|
||||||
|
"cacheVariables": {
|
||||||
|
"LIBAVR_MCU": "atmega1284p",
|
||||||
|
"LIBAVR_REFLECT": "ON"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"buildPresets": [
|
"buildPresets": [
|
||||||
{ "name": "atmega328p-generated", "configurePreset": "atmega328p-generated" },
|
{
|
||||||
{ "name": "atmega328p-reflect", "configurePreset": "atmega328p-reflect" },
|
"name": "atmega328p-generated",
|
||||||
{ "name": "attiny85-generated", "configurePreset": "attiny85-generated" },
|
"configurePreset": "atmega328p-generated"
|
||||||
{ "name": "attiny85-reflect", "configurePreset": "attiny85-reflect" },
|
},
|
||||||
{ "name": "attiny13a-generated", "configurePreset": "attiny13a-generated" },
|
{
|
||||||
{ "name": "attiny13a-reflect", "configurePreset": "attiny13a-reflect" }
|
"name": "atmega328p-reflect",
|
||||||
|
"configurePreset": "atmega328p-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny85-generated",
|
||||||
|
"configurePreset": "attiny85-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny85-reflect",
|
||||||
|
"configurePreset": "attiny85-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny13a-generated",
|
||||||
|
"configurePreset": "attiny13a-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny13a-reflect",
|
||||||
|
"configurePreset": "attiny13a-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8-generated",
|
||||||
|
"configurePreset": "atmega8-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8-reflect",
|
||||||
|
"configurePreset": "atmega8-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8a-generated",
|
||||||
|
"configurePreset": "atmega8a-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8a-reflect",
|
||||||
|
"configurePreset": "atmega8a-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega16-generated",
|
||||||
|
"configurePreset": "atmega16-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega16-reflect",
|
||||||
|
"configurePreset": "atmega16-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32-generated",
|
||||||
|
"configurePreset": "atmega32-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32-reflect",
|
||||||
|
"configurePreset": "atmega32-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32a-generated",
|
||||||
|
"configurePreset": "atmega32a-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32a-reflect",
|
||||||
|
"configurePreset": "atmega32a-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega168a-generated",
|
||||||
|
"configurePreset": "atmega168a-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega168a-reflect",
|
||||||
|
"configurePreset": "atmega168a-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega1284p-generated",
|
||||||
|
"configurePreset": "atmega1284p-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega1284p-reflect",
|
||||||
|
"configurePreset": "atmega1284p-reflect"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"workflowPresets": [
|
"workflowPresets": [
|
||||||
{
|
{
|
||||||
"name": "atmega328p-generated",
|
"name": "atmega328p-generated",
|
||||||
"steps": [
|
"steps": [
|
||||||
{ "type": "configure", "name": "atmega328p-generated" },
|
{
|
||||||
{ "type": "build", "name": "atmega328p-generated" },
|
"type": "configure",
|
||||||
{ "type": "test", "name": "atmega328p-generated" }
|
"name": "atmega328p-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega328p-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "atmega328p-generated"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "attiny85-generated",
|
"name": "attiny85-generated",
|
||||||
"steps": [
|
"steps": [
|
||||||
{ "type": "configure", "name": "attiny85-generated" },
|
{
|
||||||
{ "type": "build", "name": "attiny85-generated" },
|
"type": "configure",
|
||||||
{ "type": "test", "name": "attiny85-generated" }
|
"name": "attiny85-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "attiny85-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "attiny85-generated"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "attiny13a-generated",
|
"name": "attiny13a-generated",
|
||||||
"steps": [
|
"steps": [
|
||||||
{ "type": "configure", "name": "attiny13a-generated" },
|
{
|
||||||
{ "type": "build", "name": "attiny13a-generated" },
|
"type": "configure",
|
||||||
{ "type": "test", "name": "attiny13a-generated" }
|
"name": "attiny13a-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "attiny13a-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "test",
|
||||||
|
"name": "attiny13a-generated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8-generated",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega8-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega8-generated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8-reflect",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega8-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega8-reflect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8a-generated",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega8a-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega8a-generated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega8a-reflect",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega8a-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega8a-reflect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega16-generated",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega16-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega16-generated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega16-reflect",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega16-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega16-reflect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32-generated",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega32-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega32-generated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32-reflect",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega32-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega32-reflect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32a-generated",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega32a-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega32a-generated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega32a-reflect",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega32a-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega32a-reflect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega168a-generated",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega168a-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega168a-generated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega168a-reflect",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega168a-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega168a-reflect"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega1284p-generated",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega1284p-generated"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega1284p-generated"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atmega1284p-reflect",
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"type": "configure",
|
||||||
|
"name": "atmega1284p-reflect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"name": "atmega1284p-reflect"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"testPresets": [
|
"testPresets": [
|
||||||
{ "name": "atmega328p-generated", "configurePreset": "atmega328p-generated", "output": { "outputOnFailure": true } },
|
{
|
||||||
{ "name": "attiny85-generated", "configurePreset": "attiny85-generated", "output": { "outputOnFailure": true } },
|
"name": "atmega328p-generated",
|
||||||
{ "name": "attiny13a-generated", "configurePreset": "attiny13a-generated", "output": { "outputOnFailure": true } }
|
"configurePreset": "atmega328p-generated",
|
||||||
|
"output": {
|
||||||
|
"outputOnFailure": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny85-generated",
|
||||||
|
"configurePreset": "attiny85-generated",
|
||||||
|
"output": {
|
||||||
|
"outputOnFailure": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "attiny13a-generated",
|
||||||
|
"configurePreset": "attiny13a-generated",
|
||||||
|
"output": {
|
||||||
|
"outputOnFailure": true
|
||||||
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,28 +2,34 @@
|
|||||||
|
|
||||||
A serial bootloader on [libavr](https://git.blackmark.me/avr/libavr), pure by
|
A serial bootloader on [libavr](https://git.blackmark.me/avr/libavr), pure by
|
||||||
constraint: one C++ source, no inline assembly, no global register variables
|
constraint: one C++ source, no inline assembly, no global register variables
|
||||||
(attributes allowed), built for every chip libavr targets, **512 bytes on
|
(attributes allowed), built for every chip libavr targets, **fitting each
|
||||||
each** — 488 B on the ATtiny13A, 502 B on the ATtiny85, 504 B on the
|
chip's smallest boot sector**: 512 bytes everywhere — 488 B on the
|
||||||
ATmega328P. The device speaks primitives; every composite — verify, erase,
|
ATtiny13A, 502 B on the ATtiny85, 466–504 B across the megas — except the
|
||||||
|
ATmega1284P, whose smallest boot sector is 1 KiB and whose far-flash
|
||||||
|
machinery (ELPM reads, RAMPZ page commands, word-addressed wire) lands at
|
||||||
|
558 B in a 1 KiB slot: the 512-byte figure is a hardware boundary that chip
|
||||||
|
simply does not have, and no implementation of this feature set fits it
|
||||||
|
there. The device speaks primitives; every composite — verify, erase,
|
||||||
reset-vector surgery, updating the loader itself — lives in the host tool
|
reset-vector surgery, updating the loader itself — lives in the host tool
|
||||||
(`pureboot.py`).
|
(`pureboot.py`).
|
||||||
|
|
||||||
The image is **position-independent**: control flow is PC-relative, the
|
The image is **position-independent**: control flow is PC-relative, the
|
||||||
read/write paths take wire addresses, the write guard protects the 512-byte
|
read/write paths take wire addresses, the write guard protects the slot the
|
||||||
slot the code is *running* in (from the runtime return address), the info
|
code is *running* in (from the runtime return address), the info block is
|
||||||
block is addressed from that same anchor, and the application jump is an
|
addressed from that same anchor, and the application jump is an indirect
|
||||||
indirect call to an absolute entry. The identical binary therefore runs from
|
call to an absolute entry. The identical binary therefore runs from any
|
||||||
any 512-byte slot with every command intact — which makes pureboot **its own
|
slot with every command intact — which makes pureboot **its own staging
|
||||||
staging loader**: the host installs the same binary one slot below the
|
loader**: the host installs the same binary one slot below the resident,
|
||||||
resident, jumps into it, and lets it rewrite the resident. On the tinies the
|
jumps into it, and lets it rewrite the resident. The slot is 512 bytes
|
||||||
budget is 510, not 512: a slot's last word belongs to the host-managed
|
(1 KiB on the word-addressed large chips, matching their boot-sector
|
||||||
trampoline (below).
|
minimum); on the tinies the budget is 510, not 512: a slot's last word
|
||||||
|
belongs to the host-managed trampoline (below).
|
||||||
|
|
||||||
## Link
|
## Link
|
||||||
|
|
||||||
| Chip | Serial | Baud | Clock assumed |
|
| Chip | Serial | Baud | Clock assumed |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| ATmega328P | USART0, RXD/TXD = PD0/PD1 | 115200 8N1 | 16 MHz crystal |
|
| every ATmega (8/8A, 16, 32/32A, 168A, 328P, 1284P) | the hardware USART (USART0), RXD/TXD per pinout | 115200 8N1 | 16 MHz crystal |
|
||||||
| ATtiny85 | software UART, RX = PB0, TX = PB1 | 57600 8N1 | 8 MHz internal RC |
|
| ATtiny85 | software UART, RX = PB0, TX = PB1 | 57600 8N1 | 8 MHz internal RC |
|
||||||
| ATtiny13A | software UART, RX = PB0, TX = PB1 | 57600 8N1 | 9.6 MHz internal RC |
|
| ATtiny13A | software UART, RX = PB0, TX = PB1 | 57600 8N1 | 9.6 MHz internal RC |
|
||||||
|
|
||||||
@@ -55,6 +61,10 @@ write to finish and sends the prompt `+` (0x2b) — the prompt is therefore
|
|||||||
also the completion ack of the previous command. A session is: await `+`,
|
also the completion ack of the previous command. A session is: await `+`,
|
||||||
send a command, read its reply, repeat.
|
send a command, read its reply, repeat.
|
||||||
|
|
||||||
|
On chips whose flash exceeds 64 KiB (the 1284P — info-block flag bit 1) the
|
||||||
|
`R`/`W` flash addresses are **word** addresses; everywhere else they are byte
|
||||||
|
addresses. EEPROM addresses are always bytes, counts always bytes.
|
||||||
|
|
||||||
| Cmd | Arguments | Reply |
|
| Cmd | Arguments | Reply |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `b` | — | the 12-byte info block |
|
| `b` | — | the 12-byte info block |
|
||||||
@@ -88,18 +98,29 @@ The info block (`b`):
|
|||||||
|---|---|
|
|---|---|
|
||||||
| 0–2 | `'P'`, `'B'`, protocol version (1) |
|
| 0–2 | `'P'`, `'B'`, protocol version (1) |
|
||||||
| 3–5 | device signature |
|
| 3–5 | device signature |
|
||||||
| 6 | SPM page size in bytes |
|
| 6 | SPM page size in bytes (0 means 256) |
|
||||||
| 7–8 | loader base — application flash ends here |
|
| 7–8 | loader base — application flash ends here (a word address when bit 1 is set) |
|
||||||
| 9–10 | EEPROM size |
|
| 9–10 | EEPROM size |
|
||||||
| 11 | bit 0 set: host must patch the reset vector (no hardware boot section) |
|
| 11 | bit 0: host must patch the reset vector (no hardware boot section); bit 1: flash wire addresses are word addresses |
|
||||||
|
|
||||||
Composites are the host's job: verify = read back and compare, erase =
|
Composites are the host's job: verify = read back and compare, erase =
|
||||||
write `0xff` (per page for flash, per byte for EEPROM).
|
write `0xff` (per page for flash, per byte for EEPROM).
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
**ATmega328P**: program the loader at 0x7e00 with an external programmer.
|
**Megas**: program the loader at `flash − 512` with an external programmer.
|
||||||
Two fuse profiles, same binary:
|
Every mega's smallest-but-one BOOTSZ puts the boot-section start exactly at
|
||||||
|
the loader base (512 B — the m8/16/168A reach it at their second-smallest
|
||||||
|
step, the m32/328P at their smallest), so the ATmega328P profiles below
|
||||||
|
apply to all of them with their own addresses; the per-chip BOOTSZ ladders
|
||||||
|
live in the host tool (`BOOT_FUSE`). The **ATmega1284P** is the exception:
|
||||||
|
its smallest boot section is 1 KB, so the standalone profile does not exist
|
||||||
|
— BOOTSZ = 512 words always, and with BOOTRST programmed reset lands at
|
||||||
|
0x1f800, one erased slot below the loader (the loader-first walk behavior
|
||||||
|
below, built in). Its staging slot sits inside that same 1 KB section, so
|
||||||
|
self-update needs no fuse change.
|
||||||
|
|
||||||
|
ATmega328P profiles (addresses for its 32 KiB):
|
||||||
|
|
||||||
| BOOTSZ | BOOTRST | Behavior |
|
| BOOTSZ | BOOTRST | Behavior |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|||||||
@@ -53,14 +53,40 @@ consteval avr::hertz_t clock()
|
|||||||
|
|
||||||
using dev = avr::device<{.clock = clock()}>;
|
using dev = avr::device<{.clock = clock()}>;
|
||||||
|
|
||||||
// Geometry: the resident loader owns the top 512 bytes of flash; the word
|
// The watchdog reset flag's home: MCUSR, or the classic megas' MCUCSR.
|
||||||
// below it is the trampoline (the application's relocated reset vector) on
|
consteval std::int16_t wdrf_field()
|
||||||
// chips without a hardware boot section. The RWWSRE bit marks a separate
|
{
|
||||||
// boot section — on classic AVR the two capabilities coincide.
|
auto reg = std::string_view{avr::hw::db.regs[static_cast<std::size_t>(avr::power::detail::reset_reg())].name};
|
||||||
constexpr std::uint16_t boot_bytes = 512;
|
return avr::hw::db.field_index(reg, "WDRF");
|
||||||
constexpr std::uint16_t base = static_cast<std::uint16_t>(spm::flash_bytes - boot_bytes);
|
}
|
||||||
|
|
||||||
|
// Geometry: the resident loader owns the top slot of flash — 512 bytes,
|
||||||
|
// except on the >64 KiB chips whose own smallest boot sector is 1 KiB (the
|
||||||
|
// 1284P): there the slot is 1 KiB, matching the hardware boundary the
|
||||||
|
// 512-byte figure comes from everywhere else. The word below the slot is
|
||||||
|
// the trampoline (the application's relocated reset vector) on chips
|
||||||
|
// without a hardware boot section. The RWWSRE bit marks a separate boot
|
||||||
|
// section — on classic AVR the two capabilities coincide (the m8/m32 packs
|
||||||
|
// spell its register SPMCR).
|
||||||
|
constexpr std::uint16_t slot_bytes = spm::flash_bytes > 65536 ? 1024 : 512;
|
||||||
|
constexpr std::uint32_t base = spm::flash_bytes - slot_bytes;
|
||||||
constexpr std::uint16_t page = spm::page_bytes;
|
constexpr std::uint16_t page = spm::page_bytes;
|
||||||
constexpr bool boot_section = avr::hw::db.field_index("SPMCSR", "RWWSRE") >= 0;
|
constexpr bool boot_section = [] {
|
||||||
|
for (auto reg : {"SPMCSR", "SPMCR"})
|
||||||
|
if (avr::hw::db.field_index(reg, "RWWSRE") >= 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}();
|
||||||
|
|
||||||
|
// Past 64 KiB a byte address no longer fits the wire's 16 bits, so on the
|
||||||
|
// large chips every flash address on the wire — and all slot arithmetic —
|
||||||
|
// is a word address instead ('J' always was one). A slot spans the same
|
||||||
|
// wire-high-byte pair in either unit (512 B = 2 x 256 bytes, 1 KiB =
|
||||||
|
// 2 x 256 words), so the slot index is the high byte with its low bit
|
||||||
|
// dropped everywhere.
|
||||||
|
constexpr bool word_flash = spm::flash_bytes > 65536;
|
||||||
|
constexpr std::uint16_t wire_base = word_flash ? static_cast<std::uint16_t>(base / 2) : static_cast<std::uint16_t>(base);
|
||||||
|
constexpr std::uint16_t wire_page_mask = word_flash ? (page / 2 - 1) : (page - 1);
|
||||||
|
|
||||||
// The activation window, in seconds, is a compile-time constant (the build
|
// The activation window, in seconds, is a compile-time constant (the build
|
||||||
// may override it): the whole EEPROM belongs to the application, and
|
// may override it): the whole EEPROM belongs to the application, and
|
||||||
@@ -71,8 +97,10 @@ constexpr bool boot_section = avr::hw::db.field_index("SPMCSR", "RWWSRE") >= 0;
|
|||||||
constexpr std::uint8_t timeout_seconds = PUREBOOT_TIMEOUT;
|
constexpr std::uint8_t timeout_seconds = PUREBOOT_TIMEOUT;
|
||||||
|
|
||||||
// The 12-byte info block the host reads with the 'b' command; flash-resident
|
// The 12-byte info block the host reads with the 'b' command; flash-resident
|
||||||
// (there is no crt to copy a .data image).
|
// (there is no crt to copy a .data image), word-aligned so its wire (word)
|
||||||
inline constexpr std::array<std::uint8_t, 12> info_data = {
|
// address is exact on the large chips. The page byte is the wire count
|
||||||
|
// convention: 0 means 256.
|
||||||
|
[[gnu::progmem]] alignas(2) inline constexpr std::array<std::uint8_t, 12> info_data = {
|
||||||
'P',
|
'P',
|
||||||
'B',
|
'B',
|
||||||
1, // magic, protocol version
|
1, // magic, protocol version
|
||||||
@@ -80,13 +108,14 @@ inline constexpr std::array<std::uint8_t, 12> info_data = {
|
|||||||
avr::hw::db.signature[1],
|
avr::hw::db.signature[1],
|
||||||
avr::hw::db.signature[2],
|
avr::hw::db.signature[2],
|
||||||
static_cast<std::uint8_t>(page),
|
static_cast<std::uint8_t>(page),
|
||||||
base & 0xff,
|
wire_base & 0xff,
|
||||||
base >> 8, // app flash ends here; resident loader base
|
wire_base >> 8, // app flash ends here; resident loader base (a word address on large chips)
|
||||||
avr::hw::db.mem.eeprom_size & 0xff,
|
avr::hw::db.mem.eeprom_size & 0xff,
|
||||||
avr::hw::db.mem.eeprom_size >> 8,
|
avr::hw::db.mem.eeprom_size >> 8,
|
||||||
boot_section ? 0 : 1, // bit 0: host must patch the reset vector (no hardware boot section)
|
// bit 0: host must patch the reset vector (no hardware boot section);
|
||||||
|
// bit 1: flash wire addresses are word addresses
|
||||||
|
static_cast<std::uint8_t>((boot_section ? 0 : 1) | (word_flash ? 2 : 0)),
|
||||||
};
|
};
|
||||||
using info = avr::flash_table<info_data>;
|
|
||||||
|
|
||||||
// The serial link: the hardware USART where the chip has one, the polled
|
// The serial link: the hardware USART where the chip has one, the polled
|
||||||
// software UART (no vector — the table belongs to the application) on PB0/PB1
|
// software UART (no vector — the table belongs to the application) on PB0/PB1
|
||||||
@@ -98,19 +127,19 @@ using info = avr::flash_table<info_data>;
|
|||||||
template <avr::hertz_t C>
|
template <avr::hertz_t C>
|
||||||
consteval std::int16_t rxc_field()
|
consteval std::int16_t rxc_field()
|
||||||
{
|
{
|
||||||
return avr::hw::db.field_index("UCSR0A", "RXC0");
|
return avr::uart::detail::ufield<'0', "UCSR#A", "RXC#">();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <avr::hertz_t C>
|
template <avr::hertz_t C>
|
||||||
consteval std::int16_t txc_field()
|
consteval std::int16_t txc_field()
|
||||||
{
|
{
|
||||||
return avr::hw::db.field_index("UCSR0A", "TXC0");
|
return avr::uart::detail::ufield<'0', "UCSR#A", "TXC#">();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <avr::hertz_t C>
|
template <avr::hertz_t C>
|
||||||
consteval std::int16_t status_reg()
|
consteval std::int16_t status_reg()
|
||||||
{
|
{
|
||||||
return avr::hw::db.reg_index("UCSR0A");
|
return avr::uart::detail::ureg<'0', "UCSR#A">();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <avr::hertz_t C>
|
template <avr::hertz_t C>
|
||||||
@@ -190,7 +219,8 @@ struct software_link {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using link = std::conditional_t<avr::hw::db.has_reg("UDR0"), hardware_link<dev::clock>, software_link<dev::clock>>;
|
using link = std::conditional_t<avr::hw::db.has_instance("USART0") || avr::hw::db.has_instance("USART"),
|
||||||
|
hardware_link<dev::clock>, software_link<dev::clock>>;
|
||||||
|
|
||||||
// The application's entry, an absolute address the linker pins (--defsym in
|
// The application's entry, an absolute address the linker pins (--defsym in
|
||||||
// CMakeLists.txt): 0x0000 on the mega (word 0 stays the application's own
|
// CMakeLists.txt): 0x0000 on the mega (word 0 stays the application's own
|
||||||
@@ -245,20 +275,31 @@ std::uint16_t rx16()
|
|||||||
return static_cast<std::uint16_t>(low | (link::rx() << 8));
|
return static_cast<std::uint16_t>(low | (link::rx() << 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::uint8_t *flash_ptr(std::uint16_t address)
|
|
||||||
{
|
|
||||||
return reinterpret_cast<const std::uint8_t *>(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The streamers take the count in the wire's 8-bit form: 0 means 256.
|
// The streamers take the count in the wire's 8-bit form: 0 means 256.
|
||||||
// send_flash stays out of line: its two callers ('b' and 'R') otherwise each
|
// send_flash stays out of line: its two callers ('b' and 'R') otherwise each
|
||||||
// inline a private copy of the loop.
|
// inline a private copy of the loop. On the large chips the address is a
|
||||||
|
// word address and the read goes through ELPM (flash_load_far).
|
||||||
[[gnu::noinline]] void send_flash(std::uint16_t address, std::uint8_t count)
|
[[gnu::noinline]] void send_flash(std::uint16_t address, std::uint8_t count)
|
||||||
{
|
{
|
||||||
|
if constexpr (word_flash) {
|
||||||
|
// The 24-bit cursor as the machine holds it: the RAMPZ byte and a
|
||||||
|
// 16-bit Z, carried explicitly (the reassembled 32-bit address
|
||||||
|
// folds away inside the inlined far load). A single read never
|
||||||
|
// crosses a 64 KiB boundary — the protocol forbids it and the host
|
||||||
|
// splits its chunks there — so RAMPZ holds for the whole run.
|
||||||
|
std::uint8_t rampz = static_cast<std::uint8_t>(address >> 15);
|
||||||
|
std::uint16_t z = static_cast<std::uint16_t>(address << 1);
|
||||||
|
do {
|
||||||
|
link::tx(avr::flash_load_far<std::uint8_t>((static_cast<std::uint32_t>(rampz) << 16) | z));
|
||||||
|
if (++z == 0)
|
||||||
|
++rampz; // robustness for a host that reads across 64 KiB
|
||||||
|
} while (--count);
|
||||||
|
} else {
|
||||||
do
|
do
|
||||||
link::tx(avr::flash_load(flash_ptr(address++)));
|
link::tx(avr::flash_load(reinterpret_cast<const std::uint8_t *>(address++)));
|
||||||
while (--count);
|
while (--count);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void send_eeprom(std::uint16_t address, std::uint8_t count)
|
void send_eeprom(std::uint16_t address, std::uint8_t count)
|
||||||
{
|
{
|
||||||
@@ -287,7 +328,7 @@ void store_eeprom(std::uint16_t address, std::uint8_t count)
|
|||||||
// copy flashed one slot lower may rewrite the slot above it — how pureboot
|
// copy flashed one slot lower may rewrite the slot above it — how pureboot
|
||||||
// updates itself. On the mega the RWW section is re-enabled so reads work
|
// updates itself. On the mega the RWW section is re-enabled so reads work
|
||||||
// immediately.
|
// immediately.
|
||||||
void program_flash(std::uint16_t address, std::uint8_t slot_high)
|
void program_flash(std::uint16_t wire_address, std::uint8_t slot_high)
|
||||||
{
|
{
|
||||||
// A buffer word cannot be loaded twice without an erase (§26.2.1), so a
|
// A buffer word cannot be loaded twice without an erase (§26.2.1), so a
|
||||||
// refused page's drained data must not linger for the next write:
|
// refused page's drained data must not linger for the next write:
|
||||||
@@ -297,8 +338,34 @@ void program_flash(std::uint16_t address, std::uint8_t slot_high)
|
|||||||
spm::rww_enable<off>();
|
spm::rww_enable<off>();
|
||||||
else
|
else
|
||||||
spm::clear_buffer<off>();
|
spm::clear_buffer<off>();
|
||||||
// The address is the loop's only state: pages are aligned, so the walk
|
// One induction either way. On the byte-addressed chips the wire address
|
||||||
// ends when the offset bits wrap back to zero.
|
// itself walks the page (aligned, so the offset bits wrap to zero); on
|
||||||
|
// the word-addressed large chips the wire word address becomes a 32-bit
|
||||||
|
// byte cursor once, and their 256-byte page makes its low byte the whole
|
||||||
|
// in-page offset. The slot index is one high byte of the wire address —
|
||||||
|
// two values on byte-addressed chips (the & ~1), bits 16:9 re-packed on
|
||||||
|
// the large ones.
|
||||||
|
spm::flash_address_t address;
|
||||||
|
std::uint8_t page_high;
|
||||||
|
if constexpr (word_flash) {
|
||||||
|
// Pages are aligned, so one page never crosses a 64 KiB boundary:
|
||||||
|
// RAMPZ is a per-page constant and the fill cursor is a 16-bit Z
|
||||||
|
// whose low byte is the whole in-page offset (256-byte pages). The
|
||||||
|
// slot index is simply the wire word address's high byte.
|
||||||
|
const std::uint8_t rampz = static_cast<std::uint8_t>(wire_address >> 15);
|
||||||
|
const std::uint16_t z0 = static_cast<std::uint16_t>(wire_address << 1);
|
||||||
|
std::uint16_t z = z0;
|
||||||
|
do {
|
||||||
|
std::uint8_t low = link::rx();
|
||||||
|
std::uint8_t high = link::rx();
|
||||||
|
spm::fill<off>((static_cast<spm::flash_address_t>(rampz) << 16) | z,
|
||||||
|
static_cast<std::uint16_t>(low | (high << 8)));
|
||||||
|
z += 2;
|
||||||
|
} while (static_cast<std::uint8_t>(z));
|
||||||
|
address = (static_cast<spm::flash_address_t>(rampz) << 16) | z0;
|
||||||
|
page_high = static_cast<std::uint8_t>(wire_address >> 8) & 0xfe;
|
||||||
|
} else {
|
||||||
|
address = static_cast<spm::flash_address_t>(wire_address);
|
||||||
do {
|
do {
|
||||||
std::uint8_t low = link::rx();
|
std::uint8_t low = link::rx();
|
||||||
std::uint8_t high = link::rx();
|
std::uint8_t high = link::rx();
|
||||||
@@ -306,10 +373,11 @@ void program_flash(std::uint16_t address, std::uint8_t slot_high)
|
|||||||
address += 2;
|
address += 2;
|
||||||
} while (static_cast<std::uint8_t>(address) & (page - 1));
|
} while (static_cast<std::uint8_t>(address) & (page - 1));
|
||||||
address -= 2; // back inside the page — erase and write ignore the word bits
|
address -= 2; // back inside the page — erase and write ignore the word bits
|
||||||
const std::uint8_t page_high = static_cast<std::uint8_t>(address >> 8) & 0xfe;
|
page_high = static_cast<std::uint8_t>(address >> 8) & 0xfe;
|
||||||
|
}
|
||||||
if (page_high != slot_high) {
|
if (page_high != slot_high) {
|
||||||
// The tinies halt the CPU through the erase and the write, so only
|
// The tinies halt the CPU through the erase and the write, so only
|
||||||
// the mega — running on while its RWW section programs — waits.
|
// the megas — running on while their RWW section programs — wait.
|
||||||
spm::erase_page<off>(address);
|
spm::erase_page<off>(address);
|
||||||
if constexpr (boot_section)
|
if constexpr (boot_section)
|
||||||
spm::wait();
|
spm::wait();
|
||||||
@@ -336,18 +404,20 @@ void send_fuses()
|
|||||||
{
|
{
|
||||||
// A watchdog reset belongs to the application (whose watchdog stays
|
// A watchdog reset belongs to the application (whose watchdog stays
|
||||||
// forced on until it clears WDRF) — no activation window in its way.
|
// forced on until it clears WDRF) — no activation window in its way.
|
||||||
if (avr::hw::mcusr::wdrf.test())
|
// The flag register is MCUSR, or the classic megas' MCUCSR.
|
||||||
|
if (avr::hw::field_impl<wdrf_field()>::test())
|
||||||
run_app();
|
run_app();
|
||||||
|
|
||||||
link::init();
|
link::init();
|
||||||
|
|
||||||
// The high byte of the 512-byte-aligned base this copy runs at: the word
|
// The high byte of the 512-byte-aligned base this copy runs at: the
|
||||||
// return address's high byte is the byte address >> 9 (the slot index),
|
// return address is a word address, whose high byte is the 256-word slot
|
||||||
// doubled back into address terms. program_flash refuses this one slot
|
// index — on byte-addressed chips doubled back into byte terms.
|
||||||
// and the info block is addressed from it, so both follow wherever the
|
// program_flash refuses this one slot and the info block is addressed
|
||||||
// code was flashed.
|
// from it, so both follow wherever the code was flashed.
|
||||||
const std::uint8_t slot_high =
|
const std::uint16_t ra_words = reinterpret_cast<std::uint16_t>(__builtin_return_address(0));
|
||||||
static_cast<std::uint8_t>((reinterpret_cast<std::uint16_t>(__builtin_return_address(0)) >> 8) << 1);
|
const std::uint8_t slot_high = word_flash ? static_cast<std::uint8_t>(ra_words >> 8) & 0xfe
|
||||||
|
: static_cast<std::uint8_t>((ra_words >> 8) << 1);
|
||||||
|
|
||||||
// The knock: 'p' then 'b', each under a fresh window; any other byte is
|
// The knock: 'p' then 'b', each under a fresh window; any other byte is
|
||||||
// line noise and waits again. Falling out of a window runs the app.
|
// line noise and waits again. Falling out of a window runs the app.
|
||||||
@@ -364,11 +434,15 @@ void send_fuses()
|
|||||||
case 'b': { // info block, read relative to the running slot
|
case 'b': { // info block, read relative to the running slot
|
||||||
// The block sits in the image's first 256 bytes (the build lint
|
// The block sits in the image's first 256 bytes (the build lint
|
||||||
// asserts it), and slots are 512-aligned — so the low byte of its
|
// asserts it), and slots are 512-aligned — so the low byte of its
|
||||||
// link address is its offset in any slot, and the high byte of
|
// link address (in wire units: bytes, or words on the large
|
||||||
// its runtime address is the running slot's. Built as a byte
|
// chips) is its offset in any slot, and the high byte of its
|
||||||
// pair so no absolute 16-bit address is ever materialized.
|
// runtime address is the running slot's. Built as a byte pair so
|
||||||
const std::uint8_t low = static_cast<std::uint8_t>(reinterpret_cast<std::uint16_t>(info::storage.data()));
|
// no absolute address is ever materialized.
|
||||||
send_flash(std::bit_cast<std::uint16_t>(std::array{low, slot_high}), info::size());
|
const auto link_low = reinterpret_cast<std::uint16_t>(info_data.data());
|
||||||
|
const std::uint8_t low =
|
||||||
|
word_flash ? static_cast<std::uint8_t>(link_low >> 1) : static_cast<std::uint8_t>(link_low);
|
||||||
|
send_flash(std::bit_cast<std::uint16_t>(std::array{low, slot_high}),
|
||||||
|
static_cast<std::uint8_t>(info_data.size()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'J': { // jump to a wire word address: hand-over and staging transfer
|
case 'J': { // jump to a wire word address: hand-over and staging transfer
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ else:
|
|||||||
|
|
||||||
PROMPT = b"+"
|
PROMPT = b"+"
|
||||||
PROTOCOL_VERSION = 1
|
PROTOCOL_VERSION = 1
|
||||||
SLOT = 512 # the loader slot size; also the self-update staging distance
|
SLOT = 512 # the loader slot on byte-addressed chips; word-addressed ones (>64 KiB) use 1 KiB — their own smallest boot sector
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
@@ -270,12 +270,17 @@ class Info:
|
|||||||
raise Error(f"protocol version {raw[2]}, tool speaks {PROTOCOL_VERSION}")
|
raise Error(f"protocol version {raw[2]}, tool speaks {PROTOCOL_VERSION}")
|
||||||
self.raw = bytes(raw)
|
self.raw = bytes(raw)
|
||||||
self.signature = raw[3:6]
|
self.signature = raw[3:6]
|
||||||
self.page = raw[6]
|
self.page = raw[6] or 256 # the wire count convention: 0 means 256
|
||||||
self.base = raw[7] | (raw[8] << 8)
|
|
||||||
self.eeprom_size = raw[9] | (raw[10] << 8)
|
|
||||||
self.patch_vector = bool(raw[11] & 1)
|
self.patch_vector = bool(raw[11] & 1)
|
||||||
self.flash_size = self.base + SLOT
|
# Large chips speak word addresses for flash (bit 1); the host keeps
|
||||||
self.stage = self.base - SLOT # where a staging copy of the loader goes
|
# every address in bytes and converts at the wire.
|
||||||
|
self.word_flash = bool(raw[11] & 2)
|
||||||
|
scale = 2 if self.word_flash else 1
|
||||||
|
self.base = (raw[7] | (raw[8] << 8)) * scale
|
||||||
|
self.eeprom_size = raw[9] | (raw[10] << 8)
|
||||||
|
self.slot = 1024 if self.word_flash else SLOT
|
||||||
|
self.flash_size = self.base + self.slot
|
||||||
|
self.stage = self.base - self.slot # where a staging copy of the loader goes
|
||||||
# The hand-over target, as the word address 'J' takes: the trampoline
|
# The hand-over target, as the word address 'J' takes: the trampoline
|
||||||
# below the loader (tinies), or word 0 (mega — the application's own
|
# below the loader (tinies), or word 0 (mega — the application's own
|
||||||
# reset vector; BOOTRST re-vectors a reset into the loader instead).
|
# reset vector; BOOTRST re-vectors a reset into the loader instead).
|
||||||
@@ -331,25 +336,41 @@ class Loader:
|
|||||||
self._expect_prompt(timeout)
|
self._expect_prompt(timeout)
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
def _stream_read(self, command, address, count):
|
def _stream_read(self, command, address, count, address_scale=1):
|
||||||
data = b""
|
data = b""
|
||||||
while count:
|
while count:
|
||||||
chunk = min(count, 256)
|
chunk = min(count, 256)
|
||||||
head = bytes((ord(command), address & 0xFF, address >> 8, chunk & 0xFF))
|
wire = address // address_scale
|
||||||
|
head = bytes((ord(command), wire & 0xFF, wire >> 8, chunk & 0xFF))
|
||||||
data += self._command(head, chunk, 5.0)
|
data += self._command(head, chunk, 5.0)
|
||||||
address += chunk
|
address += chunk
|
||||||
count -= chunk
|
count -= chunk
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def read_flash(self, address, count):
|
def read_flash(self, address, count):
|
||||||
|
if not self.info.word_flash:
|
||||||
return self._stream_read("R", address, count)
|
return self._stream_read("R", address, count)
|
||||||
|
# Word-addressed wire: widen to even bounds and never let one read
|
||||||
|
# cross a 64 KiB boundary (the device holds RAMPZ for a whole run).
|
||||||
|
start = address & ~1
|
||||||
|
span = (address + count + 1 & ~1) - start
|
||||||
|
data = b""
|
||||||
|
at = start
|
||||||
|
remaining = span
|
||||||
|
while remaining:
|
||||||
|
chunk = min(remaining, 0x10000 - (at & 0xFFFF))
|
||||||
|
data += self._stream_read("R", at, chunk, address_scale=2)
|
||||||
|
at += chunk
|
||||||
|
remaining -= chunk
|
||||||
|
return data[address - start : address - start + count]
|
||||||
|
|
||||||
def read_eeprom(self, address, count):
|
def read_eeprom(self, address, count):
|
||||||
return self._stream_read("r", address, count)
|
return self._stream_read("r", address, count)
|
||||||
|
|
||||||
def write_page(self, address, data):
|
def write_page(self, address, data):
|
||||||
assert len(data) == self.info.page and address % self.info.page == 0
|
assert len(data) == self.info.page and address % self.info.page == 0
|
||||||
head = bytes((ord("W"), address & 0xFF, address >> 8))
|
wire = address // (2 if self.info.word_flash else 1)
|
||||||
|
head = bytes((ord("W"), wire & 0xFF, wire >> 8))
|
||||||
self._command(head + data, 0, 2.0)
|
self._command(head + data, 0, 2.0)
|
||||||
|
|
||||||
def write_eeprom(self, address, data):
|
def write_eeprom(self, address, data):
|
||||||
@@ -495,14 +516,32 @@ def covered(pages, info, skip_blank):
|
|||||||
# ----------------------------------------------------------------- fuses ---
|
# ----------------------------------------------------------------- fuses ---
|
||||||
|
|
||||||
|
|
||||||
def mega_boot(high_fuse):
|
# Per-chip boot fuse geometry, keyed by the signature's family/part bytes:
|
||||||
"""Decode the ATmega328P high fuse's boot configuration (DS40002061B
|
# which byte of the 'F' reply (low, lock, extended, high) carries BOOTSZ/
|
||||||
§27.3, Table 27-13/27-16): BOOTSZ1:0 in bits 2:1 select the boot-section
|
# BOOTRST, and the BOOTSZ->words ladder. Sources: Atmel-2486/2466/2503
|
||||||
words, BOOTRST in bit 0 (programmed = 0) re-vectors reset to its start.
|
# (HIGH fuse), Atmel-8271 (m168A: EXTENDED; m328P: HIGH), Atmel-42719.
|
||||||
Returns (bootrst_programmed, boot_section_start_byte)."""
|
BOOT_FUSE = {
|
||||||
bootsz = (high_fuse >> 1) & 0x03
|
bytes((0x93, 0x07)): (3, {0b11: 128, 0b10: 256, 0b01: 512, 0b00: 1024}), # m8/8A
|
||||||
words = {0b11: 256, 0b10: 512, 0b01: 1024, 0b00: 2048}[bootsz]
|
bytes((0x94, 0x03)): (3, {0b11: 128, 0b10: 256, 0b01: 512, 0b00: 1024}), # m16
|
||||||
return (high_fuse & 1) == 0, 0x8000 - words * 2
|
bytes((0x95, 0x02)): (3, {0b11: 256, 0b10: 512, 0b01: 1024, 0b00: 2048}), # m32/32A
|
||||||
|
bytes((0x94, 0x06)): (2, {0b11: 128, 0b10: 256, 0b01: 512, 0b00: 1024}), # m168A
|
||||||
|
bytes((0x95, 0x0F)): (3, {0b11: 256, 0b10: 512, 0b01: 1024, 0b00: 2048}), # m328P
|
||||||
|
bytes((0x97, 0x05)): (3, {0b11: 512, 0b10: 1024, 0b01: 2048, 0b00: 4096}), # 1284P
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def mega_boot(info, fuse_bytes):
|
||||||
|
"""Decode a mega's boot configuration from its fuses (the byte and the
|
||||||
|
BOOTSZ ladder are per chip): BOOTSZ1:0 in bits 2:1 select the
|
||||||
|
boot-section words, BOOTRST in bit 0 (programmed = 0) re-vectors reset
|
||||||
|
to its start. Returns (bootrst_programmed, boot_section_start_byte)."""
|
||||||
|
entry = BOOT_FUSE.get(bytes(info.signature[1:3]))
|
||||||
|
if entry is None:
|
||||||
|
raise Error(f"unknown mega signature {info.signature.hex()} — no boot fuse map")
|
||||||
|
which, ladder = entry
|
||||||
|
fuse = fuse_bytes[which]
|
||||||
|
words = ladder[(fuse >> 1) & 0x03]
|
||||||
|
return (fuse & 1) == 0, info.flash_size - words * 2
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------- loader update ---
|
# ---------------------------------------------------------- loader update ---
|
||||||
@@ -534,12 +573,13 @@ def staging_content(image, info):
|
|||||||
that word, which for a staging copy is the slot's own last word: an rjmp
|
that word, which for a staging copy is the slot's own last word: an rjmp
|
||||||
to the resident base. The staging copy's fall-through and 'J'-free exit
|
to the resident base. The staging copy's fall-through and 'J'-free exit
|
||||||
both land in a loader instead of garbage."""
|
both land in a loader instead of garbage."""
|
||||||
if len(image) > (SLOT - 2 if info.patch_vector else SLOT):
|
slot = info.slot
|
||||||
raise Error(f"loader image is {len(image)} B, the slot holds {SLOT - 2 if info.patch_vector else SLOT}")
|
if len(image) > (slot - 2 if info.patch_vector else slot):
|
||||||
content = bytearray(image) + bytearray([0xFF] * (SLOT - len(image)))
|
raise Error(f"loader image is {len(image)} B, the slot holds {slot - 2 if info.patch_vector else slot}")
|
||||||
|
content = bytearray(image) + bytearray([0xFF] * (slot - len(image)))
|
||||||
if info.patch_vector:
|
if info.patch_vector:
|
||||||
through = rjmp_to((info.base - 2) // 2, info.base // 2, info.flash_size // 2)
|
through = rjmp_to((info.base - 2) // 2, info.base // 2, info.flash_size // 2)
|
||||||
content[SLOT - 2], content[SLOT - 1] = through & 0xFF, through >> 8
|
content[slot - 2], content[slot - 1] = through & 0xFF, through >> 8
|
||||||
return bytes(content)
|
return bytes(content)
|
||||||
|
|
||||||
|
|
||||||
@@ -557,14 +597,13 @@ def update_preflight(image, info, fuse_bytes):
|
|||||||
if not info.patch_vector:
|
if not info.patch_vector:
|
||||||
if fuse_bytes is None:
|
if fuse_bytes is None:
|
||||||
raise Error("a loader update on this chip needs its fuses — unreadable? pass --assume-fuses")
|
raise Error("a loader update on this chip needs its fuses — unreadable? pass --assume-fuses")
|
||||||
high = fuse_bytes[3]
|
bootrst, bls_start = mega_boot(info, fuse_bytes)
|
||||||
bootrst, bls_start = mega_boot(high)
|
|
||||||
if info.stage < bls_start:
|
if info.stage < bls_start:
|
||||||
raise Error(
|
raise Error(
|
||||||
f"cannot self-update: the staging slot {info.stage:#06x} lies below the "
|
f"cannot self-update: the staging slot {info.stage:#06x} lies below the "
|
||||||
f"boot section ({bls_start:#06x}, high fuse {high:#04x}) where SPM is disabled "
|
f"boot section ({bls_start:#06x}) where SPM is disabled "
|
||||||
f"— a boot section of at least 1 KB (BOOTSZ) is required, and only an "
|
f"— a boot section of at least two slots ({2 * info.slot} B, BOOTSZ) is "
|
||||||
f"external programmer can change fuses"
|
f"required, and only an external programmer can change fuses"
|
||||||
)
|
)
|
||||||
if not bootrst:
|
if not bootrst:
|
||||||
warnings.append(
|
warnings.append(
|
||||||
@@ -605,7 +644,7 @@ class UpdateState:
|
|||||||
self.data = {
|
self.data = {
|
||||||
"signature": info.signature.hex(),
|
"signature": info.signature.hex(),
|
||||||
"base": info.base,
|
"base": info.base,
|
||||||
"staging": loader.read_flash(info.stage, SLOT).hex(),
|
"staging": loader.read_flash(info.stage, info.slot).hex(),
|
||||||
"page0": loader.read_flash(0, info.page).hex() if info.patch_vector else "",
|
"page0": loader.read_flash(0, info.page).hex() if info.patch_vector else "",
|
||||||
}
|
}
|
||||||
with open(self.path, "w") as f:
|
with open(self.path, "w") as f:
|
||||||
@@ -661,7 +700,7 @@ def op_update_loader(loader, wait, path, state_path, fuse_bytes):
|
|||||||
for warning in update_preflight(image, info, fuse_bytes):
|
for warning in update_preflight(image, info, fuse_bytes):
|
||||||
print(f"note: {warning}")
|
print(f"note: {warning}")
|
||||||
staged = staging_content(image, info)
|
staged = staging_content(image, info)
|
||||||
resident = bytes(image) + bytes([0xFF] * (SLOT - len(image)))
|
resident = bytes(image) + bytes([0xFF] * (info.slot - len(image)))
|
||||||
page = info.page
|
page = info.page
|
||||||
|
|
||||||
state = UpdateState(state_path)
|
state = UpdateState(state_path)
|
||||||
@@ -671,7 +710,7 @@ def op_update_loader(loader, wait, path, state_path, fuse_bytes):
|
|||||||
# address 0 (the 1 KB tiny13A), its first page carries the reset vector:
|
# address 0 (the 1 KB tiny13A), its first page carries the reset vector:
|
||||||
# written last, so any earlier interruption still resets into the old
|
# written last, so any earlier interruption still resets into the old
|
||||||
# resident, and from then on resets enter the staging copy.
|
# resident, and from then on resets enter the staging copy.
|
||||||
order = list(range(0, SLOT, page))
|
order = list(range(0, info.slot, page))
|
||||||
if info.stage == 0:
|
if info.stage == 0:
|
||||||
order = order[1:] + [0]
|
order = order[1:] + [0]
|
||||||
if write_differing(loader, info.stage, staged, order):
|
if write_differing(loader, info.stage, staged, order):
|
||||||
@@ -694,7 +733,7 @@ def op_update_loader(loader, wait, path, state_path, fuse_bytes):
|
|||||||
loader.enter_copy(info.base, wait)
|
loader.enter_copy(info.base, wait)
|
||||||
if redirect:
|
if redirect:
|
||||||
write_differing(loader, 0, state.page0)
|
write_differing(loader, 0, state.page0)
|
||||||
order = list(range(0, SLOT, page))
|
order = list(range(0, info.slot, page))
|
||||||
if info.stage == 0:
|
if info.stage == 0:
|
||||||
order = [0] + order[1:]
|
order = [0] + order[1:]
|
||||||
write_differing(loader, info.stage, state.staging, order)
|
write_differing(loader, info.stage, state.staging, order)
|
||||||
@@ -710,7 +749,7 @@ def check_walk_region(pages, info, fuse_bytes, force):
|
|||||||
Only checkable when the fuses are known (--fuses or --assume-fuses)."""
|
Only checkable when the fuses are known (--fuses or --assume-fuses)."""
|
||||||
if info.patch_vector or fuse_bytes is None:
|
if info.patch_vector or fuse_bytes is None:
|
||||||
return
|
return
|
||||||
bootrst, bls_start = mega_boot(fuse_bytes[3])
|
bootrst, bls_start = mega_boot(info, fuse_bytes)
|
||||||
if not bootrst or bls_start >= info.base:
|
if not bootrst or bls_start >= info.base:
|
||||||
return
|
return
|
||||||
overlap = [a for a in sorted(pages) if a >= bls_start and pages[a].count(0xFF) != len(pages[a])]
|
overlap = [a for a in sorted(pages) if a >= bls_start and pages[a].count(0xFF) != len(pages[a])]
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
symbols = subprocess.run([nm, "-C", elf], capture_output=True, text=True, check=True).stdout
|
symbols = subprocess.run([nm, "-C", elf], capture_output=True, text=True, check=True).stdout
|
||||||
info = [line for line in symbols.splitlines() if "flash_table" in line and "::storage" in line]
|
info = [line for line in symbols.splitlines() if "info_data" in line]
|
||||||
if len(info) != 1:
|
if len(info) != 1:
|
||||||
print(f"FAIL: expected one info-block storage symbol, found {len(info)}")
|
print(f"FAIL: expected one info-block storage symbol, found {len(info)}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ consteval avr::hertz_t clock()
|
|||||||
|
|
||||||
using dev = avr::device<{.clock = clock()}>;
|
using dev = avr::device<{.clock = clock()}>;
|
||||||
|
|
||||||
template <avr::hertz_t C, bool Hardware = avr::hw::db.has_reg("UDR0")>
|
template <avr::hertz_t C,
|
||||||
|
bool Hardware = avr::hw::db.has_instance("USART0") || avr::hw::db.has_instance("USART")>
|
||||||
struct link {
|
struct link {
|
||||||
using tx_t = avr::uart::usart0<C, {.baud = 115200_Bd, .max_baud_error = 2.5_pct}>;
|
using tx_t = avr::uart::usart0<C, {.baud = 115200_Bd, .max_baud_error = 2.5_pct}>;
|
||||||
static void tx(char c)
|
static void tx(char c)
|
||||||
@@ -35,9 +36,12 @@ struct link {
|
|||||||
}
|
}
|
||||||
[[noreturn]] static void idle()
|
[[noreturn]] static void idle()
|
||||||
{
|
{
|
||||||
|
// 'L' hands back to the loader at the top slot — 512 bytes, or the
|
||||||
|
// 1 KiB the >64 KiB chips use.
|
||||||
|
constexpr std::uint32_t slot = avr::hw::db.mem.flash_size > 65536 ? 1024 : 512;
|
||||||
for (;;)
|
for (;;)
|
||||||
if (tx_t::read_blocking() == 'L')
|
if (tx_t::read_blocking() == 'L')
|
||||||
reinterpret_cast<void (*)()>((avr::hw::db.mem.flash_size - 512) / 2)();
|
reinterpret_cast<void (*)()>(static_cast<std::uint16_t>((avr::hw::db.mem.flash_size - slot) / 2))();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ def fail(message):
|
|||||||
def main():
|
def main():
|
||||||
device_bin, elf, mcu, hz, base_hex, page, baud, tool, workdir = sys.argv[1:]
|
device_bin, elf, mcu, hz, base_hex, page, baud, tool, workdir = sys.argv[1:]
|
||||||
base, page, baud = int(base_hex, 0), int(page), int(baud)
|
base, page, baud = int(base_hex, 0), int(page), int(baud)
|
||||||
stage = base - 512
|
stage = None # derived from the device's own info (slot-sized) below
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(tool)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(tool)))
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
import pbsim
|
import pbsim
|
||||||
@@ -46,6 +46,7 @@ def main():
|
|||||||
resident_info = info.raw
|
resident_info = info.raw
|
||||||
|
|
||||||
# Install the staging copy exactly as the update flow would.
|
# Install the staging copy exactly as the update flow would.
|
||||||
|
stage = info.stage
|
||||||
staged = pb.staging_content(image, info)
|
staged = pb.staging_content(image, info)
|
||||||
pb.write_differing(loader, stage, staged)
|
pb.write_differing(loader, stage, staged)
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ def main():
|
|||||||
|
|
||||||
# Restore the resident image through the staged copy, then 'J' back
|
# Restore the resident image through the staged copy, then 'J' back
|
||||||
# into it and prove it lives.
|
# into it and prove it lives.
|
||||||
resident = image + b"\xff" * (512 - len(image))
|
resident = image + b"\xff" * (info.slot - len(image))
|
||||||
pb.write_differing(loader, base, resident)
|
pb.write_differing(loader, base, resident)
|
||||||
back_info = loader.enter_copy(base, 25)
|
back_info = loader.enter_copy(base, 25)
|
||||||
if back_info.raw != resident_info:
|
if back_info.raw != resident_info:
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class Device:
|
|||||||
def __init__(self, binary, elf, mcu, hz, base_hex, page, baud, dump, reset_hex=None, resume=None):
|
def __init__(self, binary, elf, mcu, hz, base_hex, page, baud, dump, reset_hex=None, resume=None):
|
||||||
cmd = [binary, elf, mcu, hz, base_hex, str(page), str(baud), dump]
|
cmd = [binary, elf, mcu, hz, base_hex, str(page), str(baud), dump]
|
||||||
if reset_hex is not None or resume is not None:
|
if reset_hex is not None or resume is not None:
|
||||||
cmd.append(reset_hex if reset_hex is not None else ("0" if mcu != "atmega328p" else base_hex))
|
cmd.append(reset_hex if reset_hex is not None else ("0" if not mcu.startswith("atmega") else base_hex))
|
||||||
if resume is not None:
|
if resume is not None:
|
||||||
cmd.append(resume)
|
cmd.append(resume)
|
||||||
self.log = open(dump + ".log", "a")
|
self.log = open(dump + ".log", "a")
|
||||||
|
|||||||
@@ -90,11 +90,17 @@ def main():
|
|||||||
read_flash = os.path.join(workdir, "readback_flash.bin")
|
read_flash = os.path.join(workdir, "readback_flash.bin")
|
||||||
read_eeprom = os.path.join(workdir, "readback_eeprom.bin")
|
read_eeprom = os.path.join(workdir, "readback_eeprom.bin")
|
||||||
|
|
||||||
# The geometry the host will discover, for computing the expected image.
|
# The geometry the host will discover, for computing the expected image:
|
||||||
|
# megas carry a boot section (no vector surgery), the large ones speak
|
||||||
|
# word addresses, and the page byte is the wire's 0-means-256.
|
||||||
|
mega = mcu.startswith("atmega")
|
||||||
|
word_flash = base + 512 > 0x10000
|
||||||
|
wire_base = base // 2 if word_flash else base
|
||||||
|
flags = (0 if mega else 1) | (2 if word_flash else 0)
|
||||||
info = pb.Info(
|
info = pb.Info(
|
||||||
bytes([ord("P"), ord("B"), 1, 0, 0, 0, page])
|
bytes([ord("P"), ord("B"), 1, 0, 0, 0, page & 0xFF])
|
||||||
+ bytes([base & 0xFF, base >> 8, eeprom_size & 0xFF, eeprom_size >> 8])
|
+ bytes([wire_base & 0xFF, wire_base >> 8, eeprom_size & 0xFF, eeprom_size >> 8])
|
||||||
+ bytes([0 if mcu == "atmega328p" else 1])
|
+ bytes([flags])
|
||||||
)
|
)
|
||||||
|
|
||||||
device = Device(device_bin, elf, mcu, hz, base_hex, page, baud, dump)
|
device = Device(device_bin, elf, mcu, hz, base_hex, page, baud, dump)
|
||||||
@@ -150,8 +156,9 @@ def main():
|
|||||||
fail("loader region looks erased in the ground-truth dump")
|
fail("loader region looks erased in the ground-truth dump")
|
||||||
|
|
||||||
# The surgery, decoded independently: the patched vector must land on the
|
# The surgery, decoded independently: the patched vector must land on the
|
||||||
# loader, the trampoline on the application's own entry.
|
# loader, the trampoline on the application's own entry (tinies only —
|
||||||
if mcu != "atmega328p":
|
# the megas' word 0 stays the application's).
|
||||||
|
if not mega:
|
||||||
flash_words = (base + 512) // 2
|
flash_words = (base + 512) // 2
|
||||||
app = open(app_bin, "rb").read()
|
app = open(app_bin, "rb").read()
|
||||||
word0 = flash_true[0] | (flash_true[1] << 8)
|
word0 = flash_true[0] | (flash_true[1] << 8)
|
||||||
|
|||||||
@@ -41,10 +41,21 @@ class PowerFail(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
MEGA_FUSES = "ffffffdd" # high 0xdd: BOOTSZ = 1 KB, BOOTRST unprogrammed
|
def assumed_fuses(pb, image):
|
||||||
|
"""Synthetic 'F' bytes for --assume-fuses: the smallest boot section
|
||||||
|
covering both the resident and the staging slot (two slots — what a
|
||||||
|
self-update needs), BOOTRST unprogrammed — the per-chip BOOTSZ ladder
|
||||||
|
and fuse byte come from the tool's own table, keyed by the update
|
||||||
|
image's embedded signature."""
|
||||||
|
info = pb.image_info(image)
|
||||||
|
which, ladder = pb.BOOT_FUSE[bytes(info.signature[1:3])]
|
||||||
|
bits = min((b for b in ladder if ladder[b] * 2 >= 2 * info.slot), key=lambda b: ladder[b])
|
||||||
|
fuses = bytearray((0xFF, 0xFF, 0xFF, 0xFF))
|
||||||
|
fuses[which] = 0xF8 | (bits << 1) | 1
|
||||||
|
return bytes(fuses)
|
||||||
|
|
||||||
|
|
||||||
def make_fault_loader(pb, base, kill_region, kill_hits, device):
|
def make_fault_loader(pb, base, slot, kill_region, kill_hits, device):
|
||||||
"""A Loader whose write_page kills the device (or, with device=None,
|
"""A Loader whose write_page kills the device (or, with device=None,
|
||||||
just the host) at the Nth write into a region; the sequence
|
just the host) at the Nth write into a region; the sequence
|
||||||
stage->resident->stage distinguishes the install from the restore."""
|
stage->resident->stage distinguishes the install from the restore."""
|
||||||
@@ -59,7 +70,7 @@ def make_fault_loader(pb, base, kill_region, kill_hits, device):
|
|||||||
if address >= base:
|
if address >= base:
|
||||||
phase = "resident"
|
phase = "resident"
|
||||||
self.seen_resident = True
|
self.seen_resident = True
|
||||||
elif address >= base - 512:
|
elif address >= base - slot:
|
||||||
phase = "stage_restore" if self.seen_resident else "stage"
|
phase = "stage_restore" if self.seen_resident else "stage"
|
||||||
else:
|
else:
|
||||||
phase = "app"
|
phase = "app"
|
||||||
@@ -77,7 +88,8 @@ def make_fault_loader(pb, base, kill_region, kill_hits, device):
|
|||||||
def main():
|
def main():
|
||||||
(device_bin, elf, update_elf, mcu, hz, base_hex, page, baud, app_bin, tool, workdir) = sys.argv[1:]
|
(device_bin, elf, update_elf, mcu, hz, base_hex, page, baud, app_bin, tool, workdir) = sys.argv[1:]
|
||||||
base, page, baud = int(base_hex, 0), int(page), int(baud)
|
base, page, baud = int(base_hex, 0), int(page), int(baud)
|
||||||
mega = mcu == "atmega328p"
|
mega = mcu.startswith("atmega")
|
||||||
|
slot = 1024 if base + 1024 > 0x10000 and mega else 512 # word-addressed chips use the 1 KiB slot
|
||||||
reset_hex = "0" if mega else None # the mega runs BOOTRST-unprogrammed here
|
reset_hex = "0" if mega else None # the mega runs BOOTRST-unprogrammed here
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(tool)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(tool)))
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
@@ -95,7 +107,7 @@ def main():
|
|||||||
fail("the update image is byte-identical to the resident build")
|
fail("the update image is byte-identical to the resident build")
|
||||||
dump = os.path.join(workdir, "dump.bin")
|
dump = os.path.join(workdir, "dump.bin")
|
||||||
state = os.path.join(workdir, "update.pbstate")
|
state = os.path.join(workdir, "update.pbstate")
|
||||||
fuses = bytes.fromhex(MEGA_FUSES) if mega else None
|
fuses = assumed_fuses(pb, images["v0"]) if mega else None
|
||||||
|
|
||||||
def connect(device):
|
def connect(device):
|
||||||
port = pb.Port(device.pty, baud)
|
port = pb.Port(device.pty, baud)
|
||||||
@@ -109,16 +121,16 @@ def main():
|
|||||||
return port, loader
|
return port, loader
|
||||||
|
|
||||||
def padded(image):
|
def padded(image):
|
||||||
return image + b"\xff" * (512 - len(image))
|
return image + b"\xff" * (slot - len(image))
|
||||||
|
|
||||||
def resident_bytes(loader):
|
def resident_bytes(loader):
|
||||||
return loader.read_flash(base, 256) + loader.read_flash(base + 256, 256)
|
return loader.read_flash(base, slot)
|
||||||
|
|
||||||
def assert_state(loader, image, app_pages):
|
def assert_state(loader, image, app_pages):
|
||||||
if resident_bytes(loader) != padded(image):
|
if resident_bytes(loader) != padded(image):
|
||||||
fail("resident loader does not match the update image")
|
fail("resident loader does not match the update image")
|
||||||
stage = base - 512
|
stage = base - slot
|
||||||
got = loader.read_flash(stage, 256) + loader.read_flash(stage + 256, 256)
|
got = loader.read_flash(stage, slot)
|
||||||
for address, data in app_pages.items():
|
for address, data in app_pages.items():
|
||||||
if stage <= address < base:
|
if stage <= address < base:
|
||||||
if got[address - stage : address - stage + page] != data:
|
if got[address - stage : address - stage + page] != data:
|
||||||
@@ -136,7 +148,7 @@ def main():
|
|||||||
# A clean CLI update, resident -> v9.
|
# A clean CLI update, resident -> v9.
|
||||||
args = ["--update-loader", os.path.join(workdir, "v9.bin"), "--state", state, "--stay"]
|
args = ["--update-loader", os.path.join(workdir, "v9.bin"), "--state", state, "--stay"]
|
||||||
if mega:
|
if mega:
|
||||||
args += ["--assume-fuses", MEGA_FUSES]
|
args += ["--assume-fuses", fuses.hex()]
|
||||||
out = pbsim.run_tool(tool, device.pty, baud, *args)
|
out = pbsim.run_tool(tool, device.pty, baud, *args)
|
||||||
if "loader updated" not in out:
|
if "loader updated" not in out:
|
||||||
fail("update did not report success")
|
fail("update did not report success")
|
||||||
@@ -167,7 +179,7 @@ def main():
|
|||||||
port, loader = connect(device)
|
port, loader = connect(device)
|
||||||
target = "v9" if resident_bytes(loader) == padded(images["v0"]) else "v0"
|
target = "v9" if resident_bytes(loader) == padded(images["v0"]) else "v0"
|
||||||
image_path = os.path.join(workdir, target + ".bin")
|
image_path = os.path.join(workdir, target + ".bin")
|
||||||
injected = make_fault_loader(pb, base, kill_region, kill_hits, device if kill_device else None)(port)
|
injected = make_fault_loader(pb, base, slot, kill_region, kill_hits, device if kill_device else None)(port)
|
||||||
injected.info = loader.info
|
injected.info = loader.info
|
||||||
try:
|
try:
|
||||||
pb.op_update_loader(injected, 25, image_path, state, fuses)
|
pb.op_update_loader(injected, 25, image_path, state, fuses)
|
||||||
@@ -193,10 +205,10 @@ def main():
|
|||||||
# Ground truth: the simulator's own flash against the final state, and
|
# Ground truth: the simulator's own flash against the final state, and
|
||||||
# on the tinies an independent decode of the reset routing.
|
# on the tinies an independent decode of the reset routing.
|
||||||
flash = open(dump, "rb").read()
|
flash = open(dump, "rb").read()
|
||||||
if flash[base : base + 512] != padded(images[final]):
|
if flash[base : base + slot] != padded(images[final]):
|
||||||
fail("ground-truth resident region does not match the final image")
|
fail("ground-truth resident region does not match the final image")
|
||||||
if not mega:
|
if not mega:
|
||||||
flash_words = (base + 512) // 2
|
flash_words = (base + slot) // 2
|
||||||
word0 = flash[0] | (flash[1] << 8)
|
word0 = flash[0] | (flash[1] << 8)
|
||||||
if rjmp_decode(word0, 0, flash_words) != base // 2:
|
if rjmp_decode(word0, 0, flash_words) != base // 2:
|
||||||
fail("ground-truth reset vector does not land on the loader")
|
fail("ground-truth reset vector does not land on the loader")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// the patched vector are not what is under test), and exposes the loader's
|
// the patched vector are not what is under test), and exposes the loader's
|
||||||
// serial link as a pty for the real host tool:
|
// serial link as a pty for the real host tool:
|
||||||
//
|
//
|
||||||
// - ATmega328P: the hardware USART0 through simavr's uart_pty.
|
// - Megas: the hardware USART through simavr's uart_pty.
|
||||||
// - Tinies: an 8N1 bridge between a pty and the GPIO software UART
|
// - Tinies: an 8N1 bridge between a pty and the GPIO software UART
|
||||||
// (drives PB0, the loader's RX; decodes PB1, its TX), timed against the
|
// (drives PB0, the loader's RX; decodes PB1, its TX), timed against the
|
||||||
// simulated cycle counter.
|
// simulated cycle counter.
|
||||||
@@ -279,7 +279,7 @@ int main(int argc, char *argv[])
|
|||||||
unsigned page = (unsigned)atoi(argv[5]);
|
unsigned page = (unsigned)atoi(argv[5]);
|
||||||
unsigned baud = (unsigned)atoi(argv[6]);
|
unsigned baud = (unsigned)atoi(argv[6]);
|
||||||
dump_path = argv[7];
|
dump_path = argv[7];
|
||||||
use_uart_pty = strcmp(mcu_name, "atmega328p") == 0;
|
use_uart_pty = strncmp(mcu_name, "atmega", 6) == 0; // every mega links over its hardware USART
|
||||||
|
|
||||||
avr = avr_make_mcu_by_name(mcu_name);
|
avr = avr_make_mcu_by_name(mcu_name);
|
||||||
if (!avr) {
|
if (!avr) {
|
||||||
|
|||||||
@@ -27,9 +27,12 @@ def expect_error(what, fn, *needles):
|
|||||||
fail(f"{what}: no error raised")
|
fail(f"{what}: no error raised")
|
||||||
|
|
||||||
|
|
||||||
def info_of(pb, base, page, patch, flash):
|
def info_of(pb, base, page, patch, flash, signature=(0x1E, 0x93, 0x0B), word_flash=False):
|
||||||
raw = bytes((0x50, 0x42, 1, 0x1E, 0x93, 0x0B, page, base & 0xFF, base >> 8,
|
scale = 2 if word_flash else 1
|
||||||
0, 2, 1 if patch else 0))
|
wire_base = base // scale
|
||||||
|
flags = (1 if patch else 0) | (2 if word_flash else 0)
|
||||||
|
raw = bytes((0x50, 0x42, 1, *signature, page & 0xFF, wire_base & 0xFF, wire_base >> 8,
|
||||||
|
0, 2, flags))
|
||||||
info = pb.Info(raw)
|
info = pb.Info(raw)
|
||||||
assert info.flash_size == flash
|
assert info.flash_size == flash
|
||||||
return info
|
return info
|
||||||
@@ -50,16 +53,41 @@ def main():
|
|||||||
import pureboot as pb
|
import pureboot as pb
|
||||||
|
|
||||||
tiny = info_of(pb, 0x1E00, 64, True, 0x2000)
|
tiny = info_of(pb, 0x1E00, 64, True, 0x2000)
|
||||||
mega = info_of(pb, 0x7E00, 128, False, 0x8000)
|
mega = info_of(pb, 0x7E00, 128, False, 0x8000, signature=(0x1E, 0x95, 0x0F))
|
||||||
|
|
||||||
# mega_boot: BOOTSZ words and the BOOTRST sense, DS40002061B §27.
|
# mega_boot: BOOTSZ words and the BOOTRST sense per chip — the fuse byte
|
||||||
for bits, start in ((0b11, 0x7E00), (0b10, 0x7C00), (0b01, 0x7800), (0b00, 0x7000)):
|
# index (HIGH everywhere but the m168A's EXTENDED) and the per-family
|
||||||
prog, at = pb.mega_boot((0xF8 | (bits << 1)) & ~1)
|
# ladders (Atmel-2486/2466/2503/8271/42719). Synthetic 'F' replies: only
|
||||||
|
# the boot byte carries meaning.
|
||||||
|
cases = (
|
||||||
|
((0x1E, 0x93, 0x07), 0x2000, 3, {0b11: 0x1F00, 0b10: 0x1E00, 0b01: 0x1C00, 0b00: 0x1800}), # m8
|
||||||
|
((0x1E, 0x94, 0x03), 0x4000, 3, {0b11: 0x3F00, 0b10: 0x3E00, 0b01: 0x3C00, 0b00: 0x3800}), # m16
|
||||||
|
((0x1E, 0x95, 0x02), 0x8000, 3, {0b11: 0x7E00, 0b10: 0x7C00, 0b01: 0x7800, 0b00: 0x7000}), # m32
|
||||||
|
((0x1E, 0x94, 0x06), 0x4000, 2, {0b11: 0x3F00, 0b10: 0x3E00, 0b01: 0x3C00, 0b00: 0x3800}), # m168A
|
||||||
|
((0x1E, 0x95, 0x0F), 0x8000, 3, {0b11: 0x7E00, 0b10: 0x7C00, 0b01: 0x7800, 0b00: 0x7000}), # m328P
|
||||||
|
((0x1E, 0x97, 0x05), 0x20000, 3, {0b11: 0x1FC00, 0b10: 0x1F800, 0b01: 0x1F000, 0b00: 0x1E000}), # 1284P
|
||||||
|
)
|
||||||
|
for signature, flash, which, ladder in cases:
|
||||||
|
# Word-addressed chips carry the 1 KiB slot (their smallest boot sector).
|
||||||
|
slot = 1024 if flash > 0x10000 else 512
|
||||||
|
chip = info_of(pb, flash - slot, 128 if flash < 0x20000 else 0, False, flash,
|
||||||
|
signature=signature, word_flash=flash > 0x10000)
|
||||||
|
for bits, start in ladder.items():
|
||||||
|
fuses = bytearray((0xFF, 0xFF, 0xFF, 0xFF))
|
||||||
|
fuses[which] = (0xF8 | (bits << 1)) & ~1
|
||||||
|
prog, at = pb.mega_boot(chip, bytes(fuses))
|
||||||
if not prog or at != start:
|
if not prog or at != start:
|
||||||
fail(f"mega_boot BOOTSZ={bits:02b} programmed: {prog} {at:#06x}")
|
fail(f"mega_boot {signature[1]:02x}{signature[2]:02x} BOOTSZ={bits:02b} programmed: {prog} {at:#07x}")
|
||||||
prog, at = pb.mega_boot(0xF8 | (bits << 1) | 1)
|
fuses[which] |= 1
|
||||||
|
prog, at = pb.mega_boot(chip, bytes(fuses))
|
||||||
if prog or at != start:
|
if prog or at != start:
|
||||||
fail(f"mega_boot BOOTSZ={bits:02b} unprogrammed: {prog} {at:#06x}")
|
fail(f"mega_boot {signature[1]:02x}{signature[2]:02b} unprogrammed: {prog} {at:#07x}")
|
||||||
|
|
||||||
|
# Word-addressed info decode: the 1284P's base/page ride the wire scaled,
|
||||||
|
# and its slot is 1 KiB.
|
||||||
|
big = info_of(pb, 0x1FC00, 0, False, 0x20000, signature=(0x1E, 0x97, 0x05), word_flash=True)
|
||||||
|
if big.page != 256 or big.base != 0x1FC00 or big.stage != 0x1F800 or big.slot != 1024:
|
||||||
|
fail(f"word-addressed info decode: page {big.page}, base {big.base:#x}, stage {big.stage:#x}")
|
||||||
|
|
||||||
# Surgery: word 0 lands on the loader, the trampoline on the original
|
# Surgery: word 0 lands on the loader, the trampoline on the original
|
||||||
# entry — checked with an independent decoder.
|
# entry — checked with an independent decoder.
|
||||||
|
|||||||
Reference in New Issue
Block a user