Compare commits
4 Commits
445e187722
...
stk500v2-l
| Author | SHA1 | Date | |
|---|---|---|---|
| db37f4b410 | |||
| 820a2d1aba | |||
| 9b54969e02 | |||
| be1a88f4ab |
@@ -1,6 +1,5 @@
|
|||||||
---
|
---
|
||||||
BasedOnStyle: LLVM
|
BasedOnStyle: LLVM
|
||||||
Standard: Latest
|
|
||||||
ColumnLimit: 120
|
ColumnLimit: 120
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
TabWidth: 4
|
TabWidth: 4
|
||||||
|
|||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -9,8 +9,3 @@ Debug
|
|||||||
*.eeprom
|
*.eeprom
|
||||||
*.lss
|
*.lss
|
||||||
*.map
|
*.map
|
||||||
|
|
||||||
# CMake / clangd
|
|
||||||
/build/
|
|
||||||
compile_commands.json
|
|
||||||
.cache/
|
|
||||||
|
|||||||
27
.gitmodules
vendored
Normal file
27
.gitmodules
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[submodule "tsb/io"]
|
||||||
|
path = tsb/io
|
||||||
|
url = git@git.blackmark.me:avr/io.git
|
||||||
|
[submodule "tsb/flash"]
|
||||||
|
path = tsb/flash
|
||||||
|
url = git@git.blackmark.me:avr/flash.git
|
||||||
|
[submodule "tsb/uart"]
|
||||||
|
path = tsb/uart
|
||||||
|
url = git@git.blackmark.me:avr/uart.git
|
||||||
|
[submodule "tsb/type"]
|
||||||
|
path = tsb/type
|
||||||
|
url = git@git.blackmark.me:avr/type.git
|
||||||
|
[submodule "stk500v2/type"]
|
||||||
|
path = stk500v2/type
|
||||||
|
url = git@git.blackmark.me:avr/type.git
|
||||||
|
[submodule "stk500v2/io"]
|
||||||
|
path = stk500v2/io
|
||||||
|
url = git@git.blackmark.me:avr/io.git
|
||||||
|
[submodule "stk500v2/uart"]
|
||||||
|
path = stk500v2/uart
|
||||||
|
url = git@git.blackmark.me:avr/uart.git
|
||||||
|
[submodule "stk500v2/flash"]
|
||||||
|
path = stk500v2/flash
|
||||||
|
url = git@git.blackmark.me:avr/flash.git
|
||||||
|
[submodule "blink/io"]
|
||||||
|
path = blink/io
|
||||||
|
url = git@git.blackmark.me:avr/io.git
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.28)
|
|
||||||
|
|
||||||
project(tsb_libavr LANGUAGES CXX)
|
|
||||||
|
|
||||||
# libavr from a local checkout (LIBAVR_ROOT) or the forge; the toolchain file
|
|
||||||
# comes from the same checkout via CMakePresets.json.
|
|
||||||
include(FetchContent)
|
|
||||||
if(NOT LIBAVR_ROOT AND DEFINED ENV{LIBAVR_ROOT})
|
|
||||||
set(LIBAVR_ROOT $ENV{LIBAVR_ROOT})
|
|
||||||
endif()
|
|
||||||
if(LIBAVR_ROOT)
|
|
||||||
FetchContent_Declare(libavr SOURCE_DIR ${LIBAVR_ROOT})
|
|
||||||
else()
|
|
||||||
FetchContent_Declare(libavr GIT_REPOSITORY git@git.blackmark.me:avr/libavr.git GIT_TAG main)
|
|
||||||
endif()
|
|
||||||
FetchContent_MakeAvailable(libavr)
|
|
||||||
|
|
||||||
if(PROJECT_IS_TOP_LEVEL)
|
|
||||||
add_compile_options(-Werror) # warnings are errors for the port's own code
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
# The behavioral test drives the real TinySafeBoot wire protocol over a
|
|
||||||
# simavr pty (as the host tools do) and actually flashes the device. The
|
|
||||||
# runner is a host program built at configure time against libsimavr; if it
|
|
||||||
# or Python is missing, only the size tests run.
|
|
||||||
find_program(_host_cc NAMES cc gcc)
|
|
||||||
find_package(Python3 COMPONENTS Interpreter)
|
|
||||||
if(_host_cc AND Python3_FOUND)
|
|
||||||
set(TSB_DEVICE ${CMAKE_BINARY_DIR}/tsb_device)
|
|
||||||
execute_process(
|
|
||||||
COMMAND ${_host_cc} -O2 -I/usr/include/simavr -I/usr/include/simavr/parts
|
|
||||||
-o ${TSB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/device.c
|
|
||||||
-lsimavr -lsimavrparts -lelf
|
|
||||||
RESULT_VARIABLE _dev_res ERROR_VARIABLE _dev_err)
|
|
||||||
if(NOT _dev_res EQUAL 0)
|
|
||||||
message(STATUS "tsb_device not built (${_dev_err}) — protocol tests skipped")
|
|
||||||
unset(TSB_DEVICE)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# The TinySafeBoot protocol reimplemented on libavr in three variants that trade
|
|
||||||
# clarity for size. Each links into the ATmega328P boot section (BOOTSZ selects
|
|
||||||
# its size; BOOTRST vectors a reset to its base) with -nostartfiles — a polled
|
|
||||||
# loader has no use for the crt or the vector table. The naked entry sits in
|
|
||||||
# .vectors, laid first, and runs. The boot base is FLASHEND+1 minus the section
|
|
||||||
# size; the linker section-start and the source's boot_bytes agree.
|
|
||||||
# All three implement the full oracle feature set (see oracle/README.md):
|
|
||||||
# watchdog bail, one-wire half-duplex, config-page activation timeout, password
|
|
||||||
# gate, emergency erase, config/flash/EEPROM read-write. They differ only in how.
|
|
||||||
# tsb_asm — minimal inline asm, the headline: 502 B in the 512 B section,
|
|
||||||
# matching the hand-written oracle's size and features.
|
|
||||||
# tsb_tricks — compiler trickery, no asm: 808 B in the 1 KB section (BOOTSZ=10).
|
|
||||||
# tsb_pure — pure idiomatic libavr: 950 B in the 1 KB section.
|
|
||||||
#
|
|
||||||
# add_tsb_variant(<name> <boot-section-bytes>)
|
|
||||||
function(add_tsb_variant name bytes)
|
|
||||||
math(EXPR base_dec "32768 - ${bytes}")
|
|
||||||
math(EXPR base_hex "${base_dec}" OUTPUT_FORMAT HEXADECIMAL)
|
|
||||||
add_executable(${name} tsb/${name}.cpp)
|
|
||||||
target_link_libraries(${name} PRIVATE libavr)
|
|
||||||
target_link_options(${name} PRIVATE -nostartfiles -Wl,--section-start=.text=${base_hex})
|
|
||||||
add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${name}>)
|
|
||||||
if(PROJECT_IS_TOP_LEVEL)
|
|
||||||
add_test(NAME ${name}.size
|
|
||||||
COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:${name}>
|
|
||||||
-DLIMIT=${bytes} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
|
|
||||||
if(DEFINED TSB_DEVICE)
|
|
||||||
add_test(NAME ${name}.protocol
|
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/tsbtest.py
|
|
||||||
${TSB_DEVICE} $<TARGET_FILE:${name}> ${base_hex})
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
add_tsb_variant(tsb_asm 512)
|
|
||||||
add_tsb_variant(tsb_pure 1024)
|
|
||||||
add_tsb_variant(tsb_tricks 1024)
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 8,
|
|
||||||
"configurePresets": [
|
|
||||||
{
|
|
||||||
"name": "base",
|
|
||||||
"hidden": true,
|
|
||||||
"generator": "Ninja",
|
|
||||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
|
||||||
"toolchainFile": "$env{LIBAVR_ROOT}/cmake/avr-toolchain.cmake",
|
|
||||||
"cacheVariables": {
|
|
||||||
"CMAKE_BUILD_TYPE": "Release",
|
|
||||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
|
||||||
"CMAKE_COLOR_DIAGNOSTICS": "ON"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "atmega328p-generated",
|
|
||||||
"inherits": "base",
|
|
||||||
"cacheVariables": { "LIBAVR_MCU": "atmega328p", "LIBAVR_REFLECT": "OFF" }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "atmega328p-reflect",
|
|
||||||
"inherits": "base",
|
|
||||||
"cacheVariables": { "LIBAVR_MCU": "atmega328p", "LIBAVR_REFLECT": "ON" }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"buildPresets": [
|
|
||||||
{ "name": "atmega328p-generated", "configurePreset": "atmega328p-generated" },
|
|
||||||
{ "name": "atmega328p-reflect", "configurePreset": "atmega328p-reflect" }
|
|
||||||
],
|
|
||||||
"workflowPresets": [
|
|
||||||
{
|
|
||||||
"name": "atmega328p-generated",
|
|
||||||
"steps": [
|
|
||||||
{ "type": "configure", "name": "atmega328p-generated" },
|
|
||||||
{ "type": "build", "name": "atmega328p-generated" },
|
|
||||||
{ "type": "test", "name": "atmega328p-generated" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"testPresets": [
|
|
||||||
{ "name": "atmega328p-generated", "configurePreset": "atmega328p-generated", "output": { "outputOnFailure": true } }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
239
blink/blink.cppproj
Normal file
239
blink/blink.cppproj
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectVersion>7.0</ProjectVersion>
|
||||||
|
<ToolchainName>com.Atmel.AVRGCC8.CPP</ToolchainName>
|
||||||
|
<ProjectGuid>{d887fc8e-ee68-4248-8382-92dbc9a54145}</ProjectGuid>
|
||||||
|
<avrdevice>ATmega328P</avrdevice>
|
||||||
|
<avrdeviceseries>none</avrdeviceseries>
|
||||||
|
<OutputType>Executable</OutputType>
|
||||||
|
<Language>CPP</Language>
|
||||||
|
<OutputFileName>$(MSBuildProjectName)</OutputFileName>
|
||||||
|
<OutputFileExtension>.elf</OutputFileExtension>
|
||||||
|
<OutputDirectory>$(MSBuildProjectDirectory)\$(Configuration)</OutputDirectory>
|
||||||
|
<AssemblyName>blink</AssemblyName>
|
||||||
|
<Name>blink</Name>
|
||||||
|
<RootNamespace>blink</RootNamespace>
|
||||||
|
<ToolchainFlavour>avr-g++-9.1.0</ToolchainFlavour>
|
||||||
|
<KeepTimersRunning>true</KeepTimersRunning>
|
||||||
|
<OverrideVtor>false</OverrideVtor>
|
||||||
|
<CacheFlash>true</CacheFlash>
|
||||||
|
<ProgFlashFromRam>true</ProgFlashFromRam>
|
||||||
|
<RamSnippetAddress>0x20000000</RamSnippetAddress>
|
||||||
|
<UncachedRange />
|
||||||
|
<preserveEEPROM>true</preserveEEPROM>
|
||||||
|
<OverrideVtorValue>exception_table</OverrideVtorValue>
|
||||||
|
<BootSegment>2</BootSegment>
|
||||||
|
<ResetRule>0</ResetRule>
|
||||||
|
<eraseonlaunchrule>0</eraseonlaunchrule>
|
||||||
|
<EraseKey />
|
||||||
|
<avrtool>com.atmel.avrdbg.tool.atmelice</avrtool>
|
||||||
|
<avrtoolserialnumber>J41800099437</avrtoolserialnumber>
|
||||||
|
<avrdeviceexpectedsignature>0x1E950F</avrdeviceexpectedsignature>
|
||||||
|
<com_atmel_avrdbg_tool_stk500>
|
||||||
|
<ToolOptions>
|
||||||
|
<InterfaceProperties>
|
||||||
|
<IspClock>125000</IspClock>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>ISP</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType>com.atmel.avrdbg.tool.stk500</ToolType>
|
||||||
|
<ToolNumber>
|
||||||
|
</ToolNumber>
|
||||||
|
<ToolName>STK500</ToolName>
|
||||||
|
</com_atmel_avrdbg_tool_stk500>
|
||||||
|
<avrtoolinterface>ISP</avrtoolinterface>
|
||||||
|
<avrtoolinterfaceclock>125000</avrtoolinterfaceclock>
|
||||||
|
<AsfFrameworkConfig>
|
||||||
|
<framework-data xmlns="">
|
||||||
|
<options />
|
||||||
|
<configurations />
|
||||||
|
<files />
|
||||||
|
<documentation help="" />
|
||||||
|
<offline-documentation help="" />
|
||||||
|
<dependencies>
|
||||||
|
<content-extension eid="atmel.asf" uuidref="Atmel.ASF" version="3.47.0" />
|
||||||
|
</dependencies>
|
||||||
|
</framework-data>
|
||||||
|
</AsfFrameworkConfig>
|
||||||
|
<com_atmel_avrdbg_tool_atmelice>
|
||||||
|
<ToolOptions>
|
||||||
|
<InterfaceProperties>
|
||||||
|
<IspClock>125000</IspClock>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>ISP</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType>com.atmel.avrdbg.tool.atmelice</ToolType>
|
||||||
|
<ToolNumber>J41800099437</ToolNumber>
|
||||||
|
<ToolName>Atmel-ICE</ToolName>
|
||||||
|
</com_atmel_avrdbg_tool_atmelice>
|
||||||
|
<custom>
|
||||||
|
<ToolOptions>
|
||||||
|
<InterfaceProperties>
|
||||||
|
<IspClock>125000</IspClock>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>
|
||||||
|
</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType>custom</ToolType>
|
||||||
|
<ToolNumber>
|
||||||
|
</ToolNumber>
|
||||||
|
<ToolName>Custom Programming Tool</ToolName>
|
||||||
|
</custom>
|
||||||
|
<com_atmel_avrdbg_tool_simulator>
|
||||||
|
<ToolOptions xmlns="">
|
||||||
|
<InterfaceProperties>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>
|
||||||
|
</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType xmlns="">com.atmel.avrdbg.tool.simulator</ToolType>
|
||||||
|
<ToolNumber xmlns="">
|
||||||
|
</ToolNumber>
|
||||||
|
<ToolName xmlns="">Simulator</ToolName>
|
||||||
|
</com_atmel_avrdbg_tool_simulator>
|
||||||
|
<AAFDebugger>
|
||||||
|
<AAFDebugFiles>
|
||||||
|
</AAFDebugFiles>
|
||||||
|
</AAFDebugger>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<ToolchainSettings>
|
||||||
|
<AvrGccCpp>
|
||||||
|
<avrgcc.common.Device>-mmcu=atmega328p</avrgcc.common.Device>
|
||||||
|
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
|
||||||
|
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
|
||||||
|
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
|
||||||
|
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
|
||||||
|
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>NDEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<avrgcc.compiler.optimization.level>Optimize for size (-Os)</avrgcc.compiler.optimization.level>
|
||||||
|
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings>
|
||||||
|
<avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic>
|
||||||
|
<avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>NDEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<avrgcccpp.compiler.optimization.level>Optimize for size (-Os)</avrgcccpp.compiler.optimization.level>
|
||||||
|
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic>
|
||||||
|
<avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++17</avrgcccpp.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<ListValues>
|
||||||
|
<Value>libm</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
</AvrGccCpp>
|
||||||
|
</ToolchainSettings>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<ToolchainSettings>
|
||||||
|
<AvrGccCpp>
|
||||||
|
<avrgcc.common.Device>-mmcu=atmega328p</avrgcc.common.Device>
|
||||||
|
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
|
||||||
|
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
|
||||||
|
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
|
||||||
|
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
|
||||||
|
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>DEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<avrgcc.compiler.optimization.level>Optimize (-O1)</avrgcc.compiler.optimization.level>
|
||||||
|
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcc.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcc.compiler.optimization.DebugLevel>
|
||||||
|
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings>
|
||||||
|
<avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic>
|
||||||
|
<avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>DEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<avrgcccpp.compiler.optimization.level>Optimize (-O1)</avrgcccpp.compiler.optimization.level>
|
||||||
|
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcccpp.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcccpp.compiler.optimization.DebugLevel>
|
||||||
|
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic>
|
||||||
|
<avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++17</avrgcccpp.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<ListValues>
|
||||||
|
<Value>libm</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
<avrgcccpp.assembler.debugging.DebugLevel>Default (-Wa,-g)</avrgcccpp.assembler.debugging.DebugLevel>
|
||||||
|
</AvrGccCpp>
|
||||||
|
</ToolchainSettings>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="bootloader.cpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="bootloader.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="clock.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="io\io.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="main.cpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="io" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(AVRSTUDIO_EXE_PATH)\\Vs\\Compiler.targets" />
|
||||||
|
</Project>
|
||||||
46
blink/bootloader.cpp
Normal file
46
blink/bootloader.cpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include "bootloader.hpp"
|
||||||
|
|
||||||
|
#include <avr/io.h>
|
||||||
|
#include <avr/pgmspace.h>
|
||||||
|
#include <avr/wdt.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
typedef void (*jmp_fn)() __attribute__((noreturn));
|
||||||
|
|
||||||
|
jmp_fn boot = reinterpret_cast<jmp_fn>(0x0000);
|
||||||
|
jmp_fn bootloader = reinterpret_cast<jmp_fn>(0x7800 / 2);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool Bootloader::handleReset()
|
||||||
|
{
|
||||||
|
wdt_reset();
|
||||||
|
uint8_t mcuStatus = MCUSR;
|
||||||
|
MCUSR &= ~(1 << WDRF);
|
||||||
|
wdt_disable();
|
||||||
|
return (mcuStatus & (1 << WDRF));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bootloader::reset()
|
||||||
|
{
|
||||||
|
wdt_enable(WDTO_15MS);
|
||||||
|
while (true)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Bootloader::check()
|
||||||
|
{
|
||||||
|
if (pgm_read_byte(reinterpret_cast<uint16_t>(bootloader) * 2) != 0xFF)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Bootloader::call()
|
||||||
|
{
|
||||||
|
if (check())
|
||||||
|
bootloader();
|
||||||
|
else
|
||||||
|
boot();
|
||||||
|
}
|
||||||
24
blink/bootloader.hpp
Normal file
24
blink/bootloader.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Bootloader {
|
||||||
|
public:
|
||||||
|
template <typename Fn>
|
||||||
|
static inline void init(Fn callback)
|
||||||
|
{
|
||||||
|
if (handleReset()) {
|
||||||
|
callback();
|
||||||
|
call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void enter()
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool handleReset();
|
||||||
|
static void reset();
|
||||||
|
static bool check();
|
||||||
|
static void call();
|
||||||
|
};
|
||||||
5
blink/clock.hpp
Normal file
5
blink/clock.hpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
//#define F_CPU 18'432'000
|
||||||
|
#define F_CPU 16'000'000
|
||||||
|
#include <util/delay.h>
|
||||||
1
blink/io
Submodule
1
blink/io
Submodule
Submodule blink/io added at 80de36ee7e
30
blink/main.cpp
Normal file
30
blink/main.cpp
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#include "clock.hpp"
|
||||||
|
|
||||||
|
#include "io/io.hpp"
|
||||||
|
|
||||||
|
#include "bootloader.hpp"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
io::Pin<io::P::B5> ledPin;
|
||||||
|
ledPin.dir(io::Dir::OUT);
|
||||||
|
ledPin = false;
|
||||||
|
|
||||||
|
Bootloader::init([&ledPin]() {
|
||||||
|
for (uint8_t i = 0; i < 10; ++i) {
|
||||||
|
ledPin = true;
|
||||||
|
_delay_ms(50);
|
||||||
|
ledPin = false;
|
||||||
|
_delay_ms(50);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 10; ++i) {
|
||||||
|
ledPin.toggle();
|
||||||
|
_delay_ms(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
Bootloader::enter();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
34
bootloader.atsln
Normal file
34
bootloader.atsln
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Atmel Studio Solution File, Format Version 11.00
|
||||||
|
VisualStudioVersion = 14.0.23107.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "tsb", "tsb\tsb.cppproj", "{DCE6C7E3-EE26-4D79-826B-08594B9AD897}"
|
||||||
|
EndProject
|
||||||
|
Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "stk500v2", "stk500v2\stk500v2.cppproj", "{19798CCE-5D96-40E9-B769-D209715DCE0C}"
|
||||||
|
EndProject
|
||||||
|
Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "blink", "blink\blink.cppproj", "{D887FC8E-EE68-4248-8382-92DBC9A54145}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|AVR = Debug|AVR
|
||||||
|
Release|AVR = Release|AVR
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.ActiveCfg = Debug|AVR
|
||||||
|
{DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Debug|AVR.Build.0 = Debug|AVR
|
||||||
|
{DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.ActiveCfg = Release|AVR
|
||||||
|
{DCE6C7E3-EE26-4D79-826B-08594B9AD897}.Release|AVR.Build.0 = Release|AVR
|
||||||
|
{19798CCE-5D96-40E9-B769-D209715DCE0C}.Debug|AVR.ActiveCfg = Debug|AVR
|
||||||
|
{19798CCE-5D96-40E9-B769-D209715DCE0C}.Debug|AVR.Build.0 = Debug|AVR
|
||||||
|
{19798CCE-5D96-40E9-B769-D209715DCE0C}.Release|AVR.ActiveCfg = Release|AVR
|
||||||
|
{19798CCE-5D96-40E9-B769-D209715DCE0C}.Release|AVR.Build.0 = Release|AVR
|
||||||
|
{D887FC8E-EE68-4248-8382-92DBC9A54145}.Debug|AVR.ActiveCfg = Debug|AVR
|
||||||
|
{D887FC8E-EE68-4248-8382-92DBC9A54145}.Debug|AVR.Build.0 = Debug|AVR
|
||||||
|
{D887FC8E-EE68-4248-8382-92DBC9A54145}.Release|AVR.ActiveCfg = Release|AVR
|
||||||
|
{D887FC8E-EE68-4248-8382-92DBC9A54145}.Release|AVR.Build.0 = Release|AVR
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# Oracle — the hand-written TinySafeBoot assembly
|
|
||||||
|
|
||||||
`tsb-fixedbaud.asm` is the reference implementation this port is measured
|
|
||||||
against: the **native-UART, fixed-baud** TinySafeBoot bootloader, hand-written
|
|
||||||
in AVR assembly. It is the size-and-feature bar for the port's `tsb_asm` tier.
|
|
||||||
|
|
||||||
- **Source**: <https://github.com/seedrobotics/tinysafeboot>
|
|
||||||
(`firmware_ASM/latest_stable_release/20200727-fixedbaud/main.asm`), the Seed
|
|
||||||
Robotics fixed-baud fork of Julien Thomas' TinySafeBoot.
|
|
||||||
- **License**: GPLv3 (see the header in the file). It is vendored here **only as
|
|
||||||
a reference oracle** — it is not compiled, linked, or distributed as part of
|
|
||||||
the MIT-licensed port. Mere aggregation.
|
|
||||||
|
|
||||||
## Why this variant
|
|
||||||
|
|
||||||
The user chose the fixed-baud, hardware-UART variant deliberately: it is the one
|
|
||||||
whose feature set the port must match. It fits the **complete** TSB feature set
|
|
||||||
into the 512-byte ATmega boot section:
|
|
||||||
|
|
||||||
| Feature | Oracle routine |
|
|
||||||
|---|---|
|
|
||||||
| Watchdog-reset bail straight to the app | `RESET` (WDRF check) |
|
|
||||||
| One-wire half-duplex (RX/TX shorted): RXEN/TXEN toggled per direction, TX turnaround guard | `SetRX` / `SetTX` / `TransmitByte` |
|
|
||||||
| Activation timeout read from the config page, with a lockout-proof minimum | `WRX1To` (uses `utimeoutH`) |
|
|
||||||
| 3×`@` activation knock | `ActCharRcvd` |
|
|
||||||
| Password gate; wrong byte hangs (still draining the UART) | `CheckPassword` |
|
|
||||||
| Emergency erase on password `\0` + double-confirm — wipes flash, EEPROM and the config page | `EmergencyErase` |
|
|
||||||
| Device-info block (16 bytes) | `SendDeviceInfo` / `DEVICEINFO` |
|
|
||||||
| App-flash read/write (`f`/`F`), EEPROM read/write (`e`/`E`), config read/write (`c`/`C`) | `CheckCommands` |
|
|
||||||
|
|
||||||
## Assembled size (the bar)
|
|
||||||
|
|
||||||
Assembled for the ATmega328P with `avra`:
|
|
||||||
|
|
||||||
```
|
|
||||||
avra -I /usr/share/avra tsb-fixedbaud.asm # after uncommenting .include "m328Pdef.inc"
|
|
||||||
# Code : 250 words (500 bytes) — the whole loader, all features, in the 512 B section
|
|
||||||
```
|
|
||||||
|
|
||||||
**500 bytes with every feature** — the proof that ≤512 B and full feature parity
|
|
||||||
are simultaneously reachable. The port's `tsb_asm` tier matches this bar; `tsb_pure`
|
|
||||||
and `tsb_tricks` implement the same protocol at larger sizes in the 1 KB section,
|
|
||||||
trading bytes for readability.
|
|
||||||
|
|
||||||
The oracle targets 20 MHz / 33333 baud; the port targets 16 MHz / 115200 baud
|
|
||||||
(what the simavr protocol test drives). Baud and geometry differ, code size and
|
|
||||||
feature set do not.
|
|
||||||
@@ -1,776 +0,0 @@
|
|||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
; TinySafeBoot - The Universal Bootloader for AVR ATmegas
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
;
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; 2020 - Version using native UART, Fixed Baud by Seed Robotics in 2020
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; meant for use on ATMEGA devices only (with native UART - UART0)
|
|
||||||
;
|
|
||||||
; Main differences to Regular TSB Bootloader:
|
|
||||||
; - Uses a native UART (UART0); therefore not compatible with ATTINY
|
|
||||||
; - Baud rate is fixed (set by a macro in the code). No auto bauding.
|
|
||||||
; - Disables TX while not transmitting to allow for one wire flashing
|
|
||||||
; (where RX and TX are shorted, for a multi drop bus)
|
|
||||||
; - Also works with separate RX and TX; however an external pull up
|
|
||||||
; on TX _may_ be required; alternatively you can modify the code
|
|
||||||
; in the ReceiveByte routine so that it won't disable TX.
|
|
||||||
; - FIXES:
|
|
||||||
; - situations where booting onto a bus with active communication could
|
|
||||||
; lock the autobauding feature
|
|
||||||
; - times out and boots to application code if the host stops interacting
|
|
||||||
; with the bootloader
|
|
||||||
;
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; Extended by Seed Robotics from 2017
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; Seed Robotics contributions are available from the Github
|
|
||||||
; repository github.com/seedrobotics
|
|
||||||
; The License and conditions remain as stated below, in the
|
|
||||||
; original notice.
|
|
||||||
;
|
|
||||||
;
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; Written in 2011-2015 by Julien Thomas
|
|
||||||
;
|
|
||||||
; This program is free software; you can redistribute it and/or
|
|
||||||
; modify it under the terms of the GNU General Public License
|
|
||||||
; as published by the Free Software Foundation; either version 3
|
|
||||||
; of the License, or (at your option) any later version.
|
|
||||||
; This program is distributed in the hope that it will be useful,
|
|
||||||
; but WITHOUT ANY WARRANTY; without even the implied warranty
|
|
||||||
; of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
||||||
; See the GNU General Public License for more details.
|
|
||||||
; You should have received a copy of the GNU General Public License
|
|
||||||
; along with this program; if not, see:
|
|
||||||
; http://www.gnu.org/licenses/
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
;
|
|
||||||
;
|
|
||||||
;
|
|
||||||
;***********************************************************************
|
|
||||||
; OVERVIEW
|
|
||||||
;***********************************************************************
|
|
||||||
;
|
|
||||||
; TSB assembly source is organized in 4 segments (approx. line numbers)
|
|
||||||
;
|
|
||||||
; ~ 50 ... Global definitions
|
|
||||||
; ~ ... TSB for ATmegas
|
|
||||||
;
|
|
||||||
;***********************************************************************
|
|
||||||
; ADJUSTMENTS FOR INDIVIDUAL ASSEMBLY
|
|
||||||
;***********************************************************************
|
|
||||||
;
|
|
||||||
; This Sourcecode is directly compatible to: AVRASM2, GAVRASM
|
|
||||||
;
|
|
||||||
.nolist
|
|
||||||
;
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; SPECIFY TARGET AVR
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
;
|
|
||||||
; Comment in and provide def.inc file for target device
|
|
||||||
;
|
|
||||||
; [Examples]
|
|
||||||
;
|
|
||||||
;.include "tn2313def.inc"
|
|
||||||
;.include "tn85def.inc"
|
|
||||||
;.include "m8515def.inc"
|
|
||||||
;.include "m168def.inc"
|
|
||||||
;.include "m161def.inc"
|
|
||||||
;.include "m324Adef.inc"
|
|
||||||
;.include "m328Pdef.inc"
|
|
||||||
;.include "tn441def.inc"
|
|
||||||
;.include "tn167def.inc"
|
|
||||||
;.include "tn861def.inc"
|
|
||||||
;.include "tn841def.inc"
|
|
||||||
;.include "tn84def.inc"
|
|
||||||
;.include "m8def.inc"
|
|
||||||
;.include "m644PAdef.inc"
|
|
||||||
;.include "m644def.inc"
|
|
||||||
;.include "tn167def.inc"
|
|
||||||
;.include "tn25def.inc"
|
|
||||||
;
|
|
||||||
; [...]
|
|
||||||
;
|
|
||||||
;
|
|
||||||
.list
|
|
||||||
;
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; BUILD INFO
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; YY = Year - MM = Month - DD = Day
|
|
||||||
.set YY = 21
|
|
||||||
.set MM = 12
|
|
||||||
.set DD = 21
|
|
||||||
;
|
|
||||||
.set BUILDSTATE = $F3 ; F1 fixed baud, pull up, derived from original (modified for fixed baud)
|
|
||||||
; F2 fixed baud, pull up, guaranteed minimum activation timeout in case of userpage data corruption
|
|
||||||
; F3 adds a CONSTANT with clock speed (Mhz) as word in the last page of memory (clock speed our defined CONSTANT)
|
|
||||||
|
|
||||||
;
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; TSB / TSB-INSTALLER SWITCH
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; 0 = Regular assembly to target address
|
|
||||||
; Other value = NOT SUPPORTED
|
|
||||||
;
|
|
||||||
.set TSBINSTALLER = 0
|
|
||||||
;
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; F_CPU and Baud rate setting
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
.equ F_CPU = 20000000
|
|
||||||
.equ BAUD = 33333 ; baudrate (notice some possible wrong cals: example for 56K, it is actually 55,555, so for BAUD_PRESC give an INT result of 8, we must set BAUD to 55500)
|
|
||||||
|
|
||||||
.equ BAUD_PRESCx10 = (F_CPU * 10/16/BAUD) - 10 ; baud prescale (regular formula = F_CPU * 10/16/BAUD - 1 but we do it x10 to check the rounding)
|
|
||||||
|
|
||||||
; arredondar acima se necesssario
|
|
||||||
.if BAUD_PRESCx10 - ( (BAUD_PRESCx10 / 10) * 10 ) >= 5 ; calculate the remainder: we rely on the fact these are integer divisions. Therefore, dividing by 10, rounds DOWN in integer division
|
|
||||||
.equ BAUD_PRESC = (F_CPU/16/BAUD)
|
|
||||||
.warning "Incrementing default BAUD_PRESC formula by 1 due to rounding."
|
|
||||||
|
|
||||||
.else
|
|
||||||
.equ BAUD_PRESC = (F_CPU/16/BAUD) - 1
|
|
||||||
.warning "Using default BAUD_PRESC formula (no rounding up)"
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.if BAUD_PRESC > 255
|
|
||||||
.error "ERROR: BAUD RATE TOO LOW. WE ONLY WRITE THE UBRRL REGISTER, SO UBRR MUST BE <255 FOR THIS CLOCK FREQ AND BAUD"
|
|
||||||
.endif
|
|
||||||
|
|
||||||
|
|
||||||
;***********************************************************************
|
|
||||||
; AUTO-ADJUST FOR DIFFERENT ASSEMBLY OPTIONS
|
|
||||||
;***********************************************************************
|
|
||||||
;
|
|
||||||
; Always set TINYMEGA=1 bc this code only supports ATMEGA
|
|
||||||
|
|
||||||
.equ TINYMEGA=1
|
|
||||||
|
|
||||||
.if FLASHEND > ($7fff)
|
|
||||||
.error "SORRY! DEVICES OVER 64 KB NOT SUPPORTED YET."
|
|
||||||
.exit
|
|
||||||
.endif
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; Workarounds for devices with renamed or missing definitions
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
;
|
|
||||||
.ifndef SPMCSR ; SPMEN / PGERS / ...
|
|
||||||
.equ SPMCSR = SPMCR
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifndef MCUSR ; PORF / EXTRF / BORF / WDRF
|
|
||||||
.equ MCUSR = MCUCSR
|
|
||||||
.endif
|
|
||||||
|
|
||||||
; Detect Attiny441/841 to amend missing pagesize and apply 4-page mode
|
|
||||||
|
|
||||||
.set FOURPAGES = 0
|
|
||||||
|
|
||||||
.if ((SIGNATURE_000 == $1E) && (SIGNATURE_002 == $15) && (SIGNATURE_001 == $92))
|
|
||||||
.equ PAGESIZE = 32
|
|
||||||
.set FOURPAGES = 1
|
|
||||||
.message "ATTINY441: 4-PAGE-ERASE MODE"
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.if ((SIGNATURE_000 == $1E) && (SIGNATURE_002 == $15) && (SIGNATURE_001 == $93))
|
|
||||||
.equ PAGESIZE = 32
|
|
||||||
.set FOURPAGES = 1
|
|
||||||
.message "ATTINY841: 4-PAGE-ERASE MODE"
|
|
||||||
.endif
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; Universal Constants and Registers
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
.equ REQUEST = '?' ; request / answer / go on
|
|
||||||
.equ CONFIRM = '!' ; confirm / attention
|
|
||||||
|
|
||||||
; Current bootloader date coded into 16-bit number
|
|
||||||
.equ BUILDDATE = YY * 512 + MM * 32 + DD
|
|
||||||
|
|
||||||
; Other
|
|
||||||
.equ INFOLEN = 8 ; *Words* of Device Info
|
|
||||||
.equ BUFFER = SRAM_START
|
|
||||||
|
|
||||||
; Registers (in use by TSB-Firmware and TSB-Installer for ATtinys)
|
|
||||||
.def avecl = r4 ; application vector temp low
|
|
||||||
.def avech = r5 ; application vector temp high
|
|
||||||
.def tmp1 = r16 ; these are
|
|
||||||
.def tmp2 = r17 ; universal
|
|
||||||
.def tmp3 = r18 ; temporary
|
|
||||||
.def tmp4 = r19 ; registers
|
|
||||||
.def bcnt = r20 ; page bytecounter
|
|
||||||
.def cntr1 = r21 ; timeout counter
|
|
||||||
.def rxen = r22 ; check if RX enabled (meaning TX disabled)
|
|
||||||
.def utimeoutH = r23 ; user timeout High byte
|
|
||||||
; special purpose registers start at R26
|
|
||||||
;
|
|
||||||
;
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
; START OF TSB FOR ATMEGAS
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
;
|
|
||||||
; TSB for ATmegas is always coded directly to target address.
|
|
||||||
|
|
||||||
.if TINYMEGA == 1
|
|
||||||
|
|
||||||
.message "ASSEMBLY OF TSB FOR ATMEGA"
|
|
||||||
|
|
||||||
.equ BOOTSTART = (FLASHEND+1)-256 ; = 512 Bytes
|
|
||||||
.equ LASTPAGE = BOOTSTART - PAGESIZE ; = 1 page below TSB!
|
|
||||||
|
|
||||||
.org BOOTSTART
|
|
||||||
|
|
||||||
RESET:
|
|
||||||
cli
|
|
||||||
|
|
||||||
in tmp4, MCUSR ; check reset condition
|
|
||||||
sbrc tmp4, WDRF ; in case of a Watchdog reset
|
|
||||||
rjmp APPJUMP ; immediately leave TSB
|
|
||||||
|
|
||||||
ldi tmp1, low (RAMEND) ; write ramend low
|
|
||||||
out SPL, tmp1 ; into SPL (stackpointer low)
|
|
||||||
.ifdef SPH
|
|
||||||
ldi tmp1, high(RAMEND) ; write ramend high for ATtinys
|
|
||||||
out SPH, tmp1 ; with SRAM > 256 bytes
|
|
||||||
.message "PROVIDING FOR STACK BIGGER THAN 256 BYTES"
|
|
||||||
.endif
|
|
||||||
|
|
||||||
.ifndef DDRD2
|
|
||||||
.equ DDRD2 = DDD2
|
|
||||||
.endif
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; ACTIVATION CHECK
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; Configure UART; no autobauding in this version
|
|
||||||
|
|
||||||
ldi tmp1,BAUD_PRESC ; load baud prescale
|
|
||||||
sts UBRR0L,tmp1 ; set baud prescale
|
|
||||||
; ldi tmp2,HIGH(bpsc) ; save code by not loading UBBRH
|
|
||||||
;sts UBRRH,tmp2 ; to UBRR0
|
|
||||||
;ldi tmp2,( (1<<RXEN0) ) ; enable transmiter and receiver
|
|
||||||
;sts UCSR0B,tmp2
|
|
||||||
|
|
||||||
; Enable Pull up on Port D2 (PD2)
|
|
||||||
cbi DDRD, DDRD2
|
|
||||||
sbi PORTD, PORTD2
|
|
||||||
|
|
||||||
; we will enable RNEN/TXEN in the ReceiveByte and TransmitByte routines
|
|
||||||
|
|
||||||
rcall ZtoLASTPAGE ; set Z to start'o'LASTPAGE
|
|
||||||
adiw zl, 2 ; skip first 2 bytes (APPJUMP)
|
|
||||||
lpm utimeoutH, z+ ; load TIMEOUT byte and store for use in RX byte timeout
|
|
||||||
ori utimeoutH, (F_CPU / 1000000); prevent bootloader lockout due if it gets an invalid (to small) timeout setting
|
|
||||||
; this ensures value is at least the clock rate, which shoudl give about 40ms
|
|
||||||
clr tmp2 ; apparently at times this is not set to 0 on boot? (seen while in debugWire)
|
|
||||||
clr rxen ; same as above
|
|
||||||
|
|
||||||
WRX1To:
|
|
||||||
; we'll check the X register which is where ReceibeByte controls the timeout
|
|
||||||
; the overall timeout of receive byte is the timeout set by the user
|
|
||||||
; therefore, if we get characters while X> 0 we're attempting to activate bootloader;
|
|
||||||
; if not, if X=0 we timedout and go to app start
|
|
||||||
rcall ReceiveByte
|
|
||||||
brcs WRX2To ; if X got to 0 (i.e. carry set), assume we timed out
|
|
||||||
cpi tmp1, '@' ; did we get an activation char = "@"
|
|
||||||
breq ActCharRcvd
|
|
||||||
WRX2To:
|
|
||||||
rjmp APPJUMP ; not an activation char goto APPJUMP in LASTPAGE
|
|
||||||
|
|
||||||
|
|
||||||
ActCharRcvd:
|
|
||||||
inc tmp2
|
|
||||||
cpi tmp2, 3
|
|
||||||
brne WRX1To ; branch if not yet at 3;
|
|
||||||
; otherwise fall through to password check
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; CHECK PASSWORD / EMERGENCY ERASE
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; we use the user timeout (utimeoutH) register for COMM timeout
|
|
||||||
; when we don't get valid data
|
|
||||||
; increase this value to a fixed one now, to cope
|
|
||||||
; with cases where the user timeout is set so low that we don't have time to
|
|
||||||
; do anything
|
|
||||||
ldi utimeoutH, (F_CPU / 78500) ; this should result in 255 for 20Mhz and proportionally
|
|
||||||
; less for lower Clocks, so that we get the same time approx. 2.4sec
|
|
||||||
|
|
||||||
CheckPassword:
|
|
||||||
|
|
||||||
chpw0: ser tmp4 ; tmp4 = 255 enables comparison
|
|
||||||
chpw1: lpm tmp3, z+ ; load pw character from Z
|
|
||||||
and tmp3, tmp4 ; if tmp4 = 0 disables comparison, for wrong password scenarios
|
|
||||||
cpi tmp3, 255 ; byte value 255 indicates
|
|
||||||
breq chpwx ; end of password -> success
|
|
||||||
chpw2: rcall Receivebyte ; else receive next character
|
|
||||||
cpi tmp1, 0 ; rxbyte = 0 will branch
|
|
||||||
breq chpwee ; to confirm emergency erase
|
|
||||||
cp tmp1, tmp3 ; compare password with rxbyte
|
|
||||||
breq chpw0 ; if equal check next character
|
|
||||||
clr tmp4 ; tmp4 = 0 to loop forever
|
|
||||||
rjmp chpw1 ; and smoothen power profile
|
|
||||||
chpwee:
|
|
||||||
; Fix for ISSUE #1: only check for Emergency Erase if we haven't
|
|
||||||
; gotten a wrong password; if we got a wrong password
|
|
||||||
; then we should stay in loop and not escape to Emergency
|
|
||||||
; Erase
|
|
||||||
cpi tmp4, 0 ; if tmp4=0 we are set to loop forever
|
|
||||||
breq chpw1
|
|
||||||
rcall RequestConfirm ; request confirm
|
|
||||||
brts chpa ; not confirmed, leave
|
|
||||||
rcall RequestConfirm ; request 2nd confirm
|
|
||||||
brts chpa ; can't be mistake now
|
|
||||||
rcall EmergencyErase ; go, emergency erase!
|
|
||||||
rjmp Mainloop
|
|
||||||
chpa:
|
|
||||||
rjmp APPJUMP ; start application
|
|
||||||
chpwx:
|
|
||||||
; rjmp SendDeviceInfo ; go on to SendDeviceInfo
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; SEND DEVICEINFO
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
SendDeviceInfo:
|
|
||||||
ldi zl, low (DEVICEINFO*2) ; load address of deviceinfo
|
|
||||||
ldi zh, high(DEVICEINFO*2) ; low and highbyte
|
|
||||||
ldi bcnt, INFOLEN*2
|
|
||||||
rcall SendFromFlash
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; MAIN LOOP TO RECEIVE AND EXECUTE COMMANDS
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
Mainloop:
|
|
||||||
clr zl ; clear Z pointer
|
|
||||||
clr zh ; which is frequently used
|
|
||||||
rcall SendConfirm ; send CONFIRM via RS232
|
|
||||||
rcall Receivebyte ; receive command via RS232
|
|
||||||
rcall CheckCommands ; check command letter
|
|
||||||
rjmp Mainloop ; and loop on
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; CHANGE USER DATA IN LASTPAGE
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
ChangeSettings:
|
|
||||||
rcall GetNewPage ; get new LASTPAGE contents
|
|
||||||
brtc ChangeS0 ; from Host (if confirmed)
|
|
||||||
ret
|
|
||||||
ChangeS0:
|
|
||||||
rcall ZtoLASTPAGE ; re-write LASTPAGE
|
|
||||||
rcall EraseFlashPage
|
|
||||||
rcall WritePage ; erase and write LASTPAGE
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; SEND USER DATA FROM LASTPAGE
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
ControlSettings:
|
|
||||||
rcall ZtoLASTPAGE ; point to LASTPAGE
|
|
||||||
; rcall SendPageFromFlash
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; SEND DATA FROM FLASH MEMORY
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
SendPageFromFlash:
|
|
||||||
ldi bcnt, low (PAGESIZE*2) ; whole Page to send
|
|
||||||
SendFromFlash:
|
|
||||||
rcall SPMwait ; (re)enable RWW read access
|
|
||||||
lpm tmp1, z+ ; read directly from flash
|
|
||||||
rcall Transmitbyte ; and send out to RS232
|
|
||||||
dec bcnt ; bcnt is number of bytes
|
|
||||||
brne SendFromFlash
|
|
||||||
ret
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; READ APPLICATION FLASH
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; read and transmit application flash area (pagewise)
|
|
||||||
|
|
||||||
ReadAppFlash:
|
|
||||||
RAF0:
|
|
||||||
rcall RwaitConfirm
|
|
||||||
brts RAFx
|
|
||||||
rcall SendPageFromFlash
|
|
||||||
RAF1:
|
|
||||||
cpi zl, low (LASTPAGE*2) ; count up to last byte
|
|
||||||
brne RAF0 ; below LASTPAGE
|
|
||||||
cpi zh, high(LASTPAGE*2)
|
|
||||||
brne RAF0
|
|
||||||
RAFx:
|
|
||||||
ret
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; WRITE APPLICATION FLASH
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; Write Appflash pagewise, don't modify anything for ATmegas
|
|
||||||
|
|
||||||
WriteAppFlash:
|
|
||||||
rcall EraseAppFlash ; Erase whole app flash
|
|
||||||
Flash2:
|
|
||||||
rcall GetNewPage ; get next page from host
|
|
||||||
brts FlashX ; stop on user's behalf
|
|
||||||
Flash3:
|
|
||||||
rcall WritePage ; write page data into flash
|
|
||||||
Flash4:
|
|
||||||
cpi zh, high(LASTPAGE*2-1) ; end of available Appflash?
|
|
||||||
brne Flash2 ; if Z reached last location
|
|
||||||
cpi zl, low (LASTPAGE*2-1) ; then we are finished
|
|
||||||
brne Flash2 ; else go on
|
|
||||||
FlashX:
|
|
||||||
ret ; we're already finished!
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; WRITE FLASH PAGE FROM BUFFER, VERIFYING AND VERIFY-ERROR-HANDLING
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
WritePage:
|
|
||||||
rcall YtoBUFFER ; Y=BUFFER, bcnt=PAGESIZE*2
|
|
||||||
WrPa1:
|
|
||||||
ld r0, y+ ; fill R0/R1 with word
|
|
||||||
ld r1, y+ ; from buffer position Y / Y+1
|
|
||||||
ldi tmp1, 0b00000001 ; set only SPMEN in SPMCSR
|
|
||||||
out SPMCSR, tmp1 ; to activate page buffering
|
|
||||||
spm ; store word in page buffer
|
|
||||||
adiw zl, 2 ; and forward to next word
|
|
||||||
subi bcnt, 2
|
|
||||||
brne WrPa1
|
|
||||||
; Z = start of next page now
|
|
||||||
subi zl, low (PAGESIZE*2) ; point back Z to
|
|
||||||
sbci zh, high(PAGESIZE*2) ; start of current page
|
|
||||||
; Z = back on current page's start
|
|
||||||
WrPa2:
|
|
||||||
ldi tmp1, 0b00000101 ; enable PRWRT + SPMEN
|
|
||||||
out SPMCSR, tmp1 ; in SPMCSR
|
|
||||||
spm ; write whole page to flash
|
|
||||||
WrPa3:
|
|
||||||
in tmp1, SPMCSR ; wait for flash write finished
|
|
||||||
sbrc tmp1, 0 ; skip if SPMEN (bit0) cleared
|
|
||||||
rjmp WrPa3 ; ITS BEEN WRITTEN
|
|
||||||
subi zl, low (-PAGESIZE*2) ; same effect as
|
|
||||||
sbci zh, high(-PAGESIZE*2) ; Z = Z + PAGESIZE*2
|
|
||||||
ret
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; CHECK COMMANDS
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
CheckCommands:
|
|
||||||
cpi tmp1, 'c' ; read LASTPAGE
|
|
||||||
breq ControlSettings
|
|
||||||
cpi tmp1, 'C' ; write LASTPAGE
|
|
||||||
breq ChangeSettings
|
|
||||||
cpi tmp1, 'f' ; read Appflash
|
|
||||||
breq ReadAppFlash
|
|
||||||
cpi tmp1, 'F' ; write Appflash
|
|
||||||
breq WriteAppFlash
|
|
||||||
cpi tmp1, 'e' ; read EEPROM
|
|
||||||
breq EepromRead
|
|
||||||
cpi tmp1, 'E' ; write EEPROM
|
|
||||||
breq EEpromWrite
|
|
||||||
rjmp APPJUMP ; else start application
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; EEPROM READ/WRITE ACCESS
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
EepromWrite:
|
|
||||||
EEWr0:
|
|
||||||
rcall GetNewPage ; get EEPROM datablock
|
|
||||||
brts EERWFx ; or abort on host's demand
|
|
||||||
EEWr1:
|
|
||||||
rcall YtoBUFFER ; Y = Buffer and Bcnt = blocksize
|
|
||||||
EEWr2:
|
|
||||||
ld tmp1, y+ ; read EEPROM byte from buffer
|
|
||||||
rcall EEWriteByte
|
|
||||||
dec bcnt ; count down block byte counter
|
|
||||||
brne EEWr2 ; loop on if block not finished
|
|
||||||
rjmp EeWr0
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
EEpromRead:
|
|
||||||
EeRe1:
|
|
||||||
rcall RwaitConfirm ; wait to confirm
|
|
||||||
brts EERWFx ; else we are finished
|
|
||||||
ldi bcnt, low(PAGESIZE*2) ; again PAGESIZE*2 is blocksize
|
|
||||||
EERe2:
|
|
||||||
out EEARL, zl ; current EEPROM address low
|
|
||||||
.ifdef EEARH
|
|
||||||
out EEARH, zh ; current EEPROM address high
|
|
||||||
.endif
|
|
||||||
sbi EECR, 0 ; set EERE - EEPROM read enable
|
|
||||||
in tmp1, EEDR ; read byte from current address
|
|
||||||
rcall Transmitbyte ; send out to RS232
|
|
||||||
adiw zl,1 ; count up EEPROM address
|
|
||||||
dec bcnt ; count down block byte counter
|
|
||||||
brne EERe2 ; loop on if block not finished
|
|
||||||
rjmp EERe1
|
|
||||||
EERWFx:
|
|
||||||
ret
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
EEWriteByte:
|
|
||||||
out EEDR, tmp1 ; write to EEPROM data register
|
|
||||||
out EEARL, zl ; current EEPROM address low
|
|
||||||
.ifdef EEARH
|
|
||||||
out EEARH, zh ; high EEARH for some attinys
|
|
||||||
.endif
|
|
||||||
sbi EECR, 2 ; EEPROM master prog enable
|
|
||||||
sbi EECR, 1 ; EEPE initiate prog cycle
|
|
||||||
EeWB:
|
|
||||||
sbic EECR, 1 ; wait write cycle to complete
|
|
||||||
rjmp EeWB ; before we can go on
|
|
||||||
adiw zl,1 ; count up EEPROM address
|
|
||||||
ret
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; GET NEW PAGE
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
GetNewPage:
|
|
||||||
rcall RequestConfirm ; check for Confirm
|
|
||||||
brts GNPx ; abort if not confirmed
|
|
||||||
GNP0:
|
|
||||||
rcall YtoBUFFER ; Y = BUFFER, bcnt = PAGESIZE*2
|
|
||||||
GNP1:
|
|
||||||
rcall ReceiveByte ; receive serial byte
|
|
||||||
st y+, tmp1 ; and store in buffer
|
|
||||||
dec bcnt ; until full page loaded
|
|
||||||
brne GNP1 ; loop on
|
|
||||||
GNPx:
|
|
||||||
ret ; finished
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; REQUEST TO CONFIRM / AWAIT CONFIRM COMMAND
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
RequestConfirm:
|
|
||||||
ldi tmp1, REQUEST ; send request character
|
|
||||||
rcall Transmitbyte ; prompt to confirm (or not)
|
|
||||||
|
|
||||||
RwaitConfirm:
|
|
||||||
rcall ReceiveByte ; get host's reply
|
|
||||||
clt ; set T=0 for confirmation
|
|
||||||
cpi tmp1, CONFIRM ; if host HAS sent CONFIRM
|
|
||||||
breq RCx ; return with the T=0
|
|
||||||
set ; else set T=1 (NOT CONFIRMED)
|
|
||||||
RCx:
|
|
||||||
ret ; whether confirmed or not
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; FLASH ERASE TOP-TO-BOTTOM ( (BOOTSTART-1) ... $0000)
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
EraseAppFlash:
|
|
||||||
rcall ZtoLASTPAGE ; point Z to LASTPAGE, directly
|
|
||||||
EAF0:
|
|
||||||
subi zl, low (PAGESIZE*2)
|
|
||||||
sbci zh, high(PAGESIZE*2)
|
|
||||||
rcall EraseFlashPage
|
|
||||||
brne EAF0 ; until first page reached
|
|
||||||
EAFx: ret ; and leave with Z = $0000
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; EMERGENCY ERASE OF FLASH / EEPROM / USERDATA
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
EmergencyErase:
|
|
||||||
rcall EraseAppFlash ; erase Application Flash
|
|
||||||
ser tmp1 ; byte value for EEPROM writes
|
|
||||||
EEE0:
|
|
||||||
rcall EEWriteByte ; write EEPROM byte, Z = Z + 1
|
|
||||||
cpi zh, high(EEPROMEND+1)+2 ; EEPROMEND
|
|
||||||
brne EEE0 ; and loop on until finished
|
|
||||||
|
|
||||||
rcall ZtoLASTPAGE ; LASTPAGE is to be erased
|
|
||||||
; rcall EraseFlashPage
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; ERASE ONE FLASH PAGE
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
EraseFlashPage:
|
|
||||||
ldi tmp1, 0b00000011 ; enable PGERS + SPMEN
|
|
||||||
out SPMCSR, tmp1 ; in SPMCSR and erase current
|
|
||||||
spm ; page by SPM (MCU halted)
|
|
||||||
|
|
||||||
; Waiting for SPM to be finished is *obligatory* on ATmegas!
|
|
||||||
SPMwait:
|
|
||||||
in tmp1, SPMCSR
|
|
||||||
sbrc tmp1, 0 ; wait previous SPMEN
|
|
||||||
rjmp SPMwait
|
|
||||||
ldi tmp1, 0b00010001 ; set RWWSRE and SPMEN
|
|
||||||
out SPMCSR, tmp1
|
|
||||||
spm
|
|
||||||
ret
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; OTHER SUBROUTINES
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
YtoBUFFER:
|
|
||||||
ldi yl, low (BUFFER) ; reset pointer
|
|
||||||
ldi yh, high(BUFFER) ; to programming buffer
|
|
||||||
ldi bcnt, low(PAGESIZE*2) ; and often needed
|
|
||||||
ret
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
ZtoLASTPAGE:
|
|
||||||
ldi zl, low (LASTPAGE*2) ; reset Z to LASTPAGE start
|
|
||||||
ldi zh, high(LASTPAGE*2)
|
|
||||||
ret
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; RS232 RECEIVE BYTE
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
; uses: tmp1 (received data byte), cntr1 (for timeout)
|
|
||||||
; also uses utimeoutH which holds the default timeout defined by the user
|
|
||||||
; and X which is actually used to count down
|
|
||||||
SetRX:
|
|
||||||
ldi tmp1,(1<<RXEN0) ; enable receiver (Transmitter disabled)
|
|
||||||
sts UCSR0B,tmp1
|
|
||||||
ser rxen
|
|
||||||
|
|
||||||
ReceiveByte:
|
|
||||||
sbrs rxen, 0
|
|
||||||
rjmp SetRX
|
|
||||||
|
|
||||||
; outer counter
|
|
||||||
mov xh, utimeoutH
|
|
||||||
;ldi xl, 128
|
|
||||||
|
|
||||||
ReceiveByteShortTimeout:
|
|
||||||
ser cntr1 ; inner counter reset
|
|
||||||
|
|
||||||
ReceiveByteShortTimeout1:
|
|
||||||
lds tmp1, UCSR0A ; load UART status register A
|
|
||||||
sbrc tmp1, RXC0 ; if not RXComplete, skip
|
|
||||||
rjmp LoadRXByte
|
|
||||||
dec cntr1 ; if counter not zero
|
|
||||||
brne ReceiveByteShortTimeout1 ; cycle again; else fall through
|
|
||||||
sbiw xl, 1 ; dec outter counter
|
|
||||||
brcc ReceiveByteShortTimeout ; continue of outter counetr still active
|
|
||||||
;ret ;
|
|
||||||
|
|
||||||
LoadRXByte:
|
|
||||||
lds tmp1, UDR0 ; load received character even if RXC is not set
|
|
||||||
ret ; (it loads 0 and UDR FIFO should recover for next char)
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; RS232 SEND CONFIRM CHARACTER
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
SendConfirm:
|
|
||||||
ldi tmp1, CONFIRM
|
|
||||||
rjmp Transmitbyte
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; RS232 TRANSMIT BYTE
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; uses: tmp1 (transmit byte will be shifted out), tmp2 (bitcounter)
|
|
||||||
;
|
|
||||||
|
|
||||||
SetTX:
|
|
||||||
ldi tmp2,(1<<TXEN0) ; enable transmitter (Receiver disabled)
|
|
||||||
sts UCSR0B,tmp2
|
|
||||||
clr rxen
|
|
||||||
|
|
||||||
; wait some guard time to allow receiving devices ot transition
|
|
||||||
; from TX t RX state
|
|
||||||
ser cntr1 ; inner counter reset
|
|
||||||
SetTXShortTimeout:
|
|
||||||
nop
|
|
||||||
dec cntr1 ; if counter not zero
|
|
||||||
brne SetTXShortTimeout ; cycle again; else fall through
|
|
||||||
|
|
||||||
|
|
||||||
TransmitByte:
|
|
||||||
sbrc rxen, 0
|
|
||||||
rjmp SetTX
|
|
||||||
|
|
||||||
; no need to wait for UDRE bc we will wait for TXC on
|
|
||||||
; every char transmitted. TXC occurs later that UDRE
|
|
||||||
; so UDRE should be asserted when TXC asserts
|
|
||||||
sts UDR0, tmp1
|
|
||||||
|
|
||||||
WaitForTXC:
|
|
||||||
lds tmp2, UCSR0A ; wait for TXC (and not UDRE)
|
|
||||||
sbrs tmp2, TXC0 ; bc after this char we may transition
|
|
||||||
rjmp WaitForTXC ; to receiving chars and we want to make sure we get a clean transition
|
|
||||||
; we need to write a 1 to clear the TXC flag; otherwise the flag won't clear
|
|
||||||
sts UCSR0A, tmp2 ; tmp2 should contain an asserted TXC bit
|
|
||||||
ret
|
|
||||||
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; ATMEGA APPJUMP = SIMPLE JUMP TO $0000 (ORIGINAL RESET VECTOR)
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; Boot Reset Vector (BOOTRST) must be activated for TSB on ATmegas.
|
|
||||||
; After timeout or executing commands, TSB for ATmegas will simply
|
|
||||||
; handover to the App by a (relative or absolute) jump to $0000.
|
|
||||||
|
|
||||||
APPJUMP:
|
|
||||||
rcall SPMwait ; make sure everything's done
|
|
||||||
|
|
||||||
.if FLASHEND >= ($1fff)
|
|
||||||
jmp $0000 ; absolute jump
|
|
||||||
.else
|
|
||||||
rjmp $0000 ; relative jump
|
|
||||||
.endif
|
|
||||||
|
|
||||||
|
|
||||||
DEVICEINFO:
|
|
||||||
.message "DEVICE INFO BLOCK FOR ATMEGA"
|
|
||||||
.db "TSB", low (BUILDDATE), high (BUILDDATE), BUILDSTATE
|
|
||||||
.db SIGNATURE_000, SIGNATURE_001, SIGNATURE_002, low (PAGESIZE)
|
|
||||||
.dw BOOTSTART-PAGESIZE
|
|
||||||
.dw EEPROMEND
|
|
||||||
.db $AA, $AA
|
|
||||||
|
|
||||||
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
; DEVICE INFO BLOCK = PERMANENT DATA
|
|
||||||
;-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
; set last word with the clock speed
|
|
||||||
.org FLASHEND
|
|
||||||
.dw (F_CPU/1000000)
|
|
||||||
|
|
||||||
//.message "SAVING CLOCK SPEED IN LAST BYTE AS " (F_CPU/1000000) " Mhz"
|
|
||||||
|
|
||||||
.message "ASSEMBLY OF TSB FOR ATMEGA SUCCESSFULLY FINISHED!"
|
|
||||||
|
|
||||||
.endif ; closing TSB for ATmega sourcecode;
|
|
||||||
|
|
||||||
;***********************************************************************
|
|
||||||
; END OF TSB FOR ATMEGAS
|
|
||||||
;***********************************************************************
|
|
||||||
|
|
||||||
.exit
|
|
||||||
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
; END OF CONDITIONAL ASSEMBLY SOURCE OF TSB FOR ATTINYS AND ATMEGAS
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
;***********************************************************************
|
|
||||||
|
|
||||||
|
|
||||||
5
stk500v2/clock.hpp
Normal file
5
stk500v2/clock.hpp
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
//#define F_CPU 18'432'000
|
||||||
|
#define F_CPU 16'000'000
|
||||||
|
#include <util/delay.h>
|
||||||
113
stk500v2/command.hpp
Normal file
113
stk500v2/command.hpp
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// STK message constants
|
||||||
|
|
||||||
|
static constexpr uint8_t MESSAGE_START = 0x1B; // ASCII ESC
|
||||||
|
static constexpr uint8_t TOKEN = 0x0E;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// STK general command constants
|
||||||
|
|
||||||
|
static constexpr uint8_t CMD_SIGN_ON = 0x01;
|
||||||
|
static constexpr uint8_t CMD_SET_PARAMETER = 0x02;
|
||||||
|
static constexpr uint8_t CMD_GET_PARAMETER = 0x03;
|
||||||
|
static constexpr uint8_t CMD_SET_DEVICE_PARAMETERS = 0x04;
|
||||||
|
static constexpr uint8_t CMD_OSCCAL = 0x05;
|
||||||
|
static constexpr uint8_t CMD_LOAD_ADDRESS = 0x06;
|
||||||
|
static constexpr uint8_t CMD_FIRMWARE_UPGRADE = 0x07;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// STK ISP command constants
|
||||||
|
|
||||||
|
static constexpr uint8_t CMD_ENTER_PROGMODE_ISP = 0x10;
|
||||||
|
static constexpr uint8_t CMD_LEAVE_PROGMODE_ISP = 0x11;
|
||||||
|
static constexpr uint8_t CMD_CHIP_ERASE_ISP = 0x12;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_FLASH_ISP = 0x13;
|
||||||
|
static constexpr uint8_t CMD_READ_FLASH_ISP = 0x14;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_EEPROM_ISP = 0x15;
|
||||||
|
static constexpr uint8_t CMD_READ_EEPROM_ISP = 0x16;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_FUSE_ISP = 0x17;
|
||||||
|
static constexpr uint8_t CMD_READ_FUSE_ISP = 0x18;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_LOCK_ISP = 0x19;
|
||||||
|
static constexpr uint8_t CMD_READ_LOCK_ISP = 0x1A;
|
||||||
|
static constexpr uint8_t CMD_READ_SIGNATURE_ISP = 0x1B;
|
||||||
|
static constexpr uint8_t CMD_READ_OSCCAL_ISP = 0x1C;
|
||||||
|
static constexpr uint8_t CMD_SPI_MULTI = 0x1D;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// STK PP command constants
|
||||||
|
|
||||||
|
static constexpr uint8_t CMD_ENTER_PROGMODE_PP = 0x20;
|
||||||
|
static constexpr uint8_t CMD_LEAVE_PROGMODE_PP = 0x21;
|
||||||
|
static constexpr uint8_t CMD_CHIP_ERASE_PP = 0x22;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_FLASH_PP = 0x23;
|
||||||
|
static constexpr uint8_t CMD_READ_FLASH_PP = 0x24;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_EEPROM_PP = 0x25;
|
||||||
|
static constexpr uint8_t CMD_READ_EEPROM_PP = 0x26;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_FUSE_PP = 0x27;
|
||||||
|
static constexpr uint8_t CMD_READ_FUSE_PP = 0x28;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_LOCK_PP = 0x29;
|
||||||
|
static constexpr uint8_t CMD_READ_LOCK_PP = 0x2A;
|
||||||
|
static constexpr uint8_t CMD_READ_SIGNATURE_PP = 0x2B;
|
||||||
|
static constexpr uint8_t CMD_READ_OSCCAL_PP = 0x2C;
|
||||||
|
|
||||||
|
static constexpr uint8_t CMD_SET_CONTROL_STACK = 0x2D;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// STK HVSP command constants
|
||||||
|
|
||||||
|
static constexpr uint8_t CMD_ENTER_PROGMODE_HVSP = 0x30;
|
||||||
|
static constexpr uint8_t CMD_LEAVE_PROGMODE_HVSP = 0x31;
|
||||||
|
static constexpr uint8_t CMD_CHIP_ERASE_HVSP = 0x32;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_FLASH_HVSP = 0x33;
|
||||||
|
static constexpr uint8_t CMD_READ_FLASH_HVSP = 0x34;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_EEPROM_HVSP = 0x35;
|
||||||
|
static constexpr uint8_t CMD_READ_EEPROM_HVSP = 0x36;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_FUSE_HVSP = 0x37;
|
||||||
|
static constexpr uint8_t CMD_READ_FUSE_HVSP = 0x38;
|
||||||
|
static constexpr uint8_t CMD_PROGRAM_LOCK_HVSP = 0x39;
|
||||||
|
static constexpr uint8_t CMD_READ_LOCK_HVSP = 0x3A;
|
||||||
|
static constexpr uint8_t CMD_READ_SIGNATURE_HVSP = 0x3B;
|
||||||
|
static constexpr uint8_t CMD_READ_OSCCAL_HVSP = 0x3C;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// STK status constants
|
||||||
|
|
||||||
|
// Success
|
||||||
|
static constexpr uint8_t STATUS_CMD_OK = 0x00;
|
||||||
|
|
||||||
|
// Warnings
|
||||||
|
static constexpr uint8_t STATUS_CMD_TOUT = 0x80;
|
||||||
|
static constexpr uint8_t STATUS_RDY_BSY_TOUT = 0x81;
|
||||||
|
static constexpr uint8_t STATUS_SET_PARAM_MISSING = 0x82;
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
static constexpr uint8_t STATUS_CMD_FAILED = 0xC0;
|
||||||
|
static constexpr uint8_t STATUS_CKSUM_ERROR = 0xC1;
|
||||||
|
static constexpr uint8_t STATUS_CMD_UNKNOWN = 0xC9;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// STK parameter constants
|
||||||
|
static constexpr uint8_t PARAM_BUILD_NUMBER_LOW = 0x80;
|
||||||
|
static constexpr uint8_t PARAM_BUILD_NUMBER_HIGH = 0x81;
|
||||||
|
static constexpr uint8_t PARAM_HW_VER = 0x90;
|
||||||
|
static constexpr uint8_t PARAM_SW_MAJOR = 0x91;
|
||||||
|
static constexpr uint8_t PARAM_SW_MINOR = 0x92;
|
||||||
|
static constexpr uint8_t PARAM_VTARGET = 0x94;
|
||||||
|
static constexpr uint8_t PARAM_VADJUST = 0x95;
|
||||||
|
static constexpr uint8_t PARAM_OSC_PSCALE = 0x96;
|
||||||
|
static constexpr uint8_t PARAM_OSC_CMATCH = 0x97;
|
||||||
|
static constexpr uint8_t PARAM_SCK_DURATION = 0x98;
|
||||||
|
static constexpr uint8_t PARAM_TOPCARD_DETECT = 0x9A;
|
||||||
|
static constexpr uint8_t PARAM_STATUS = 0x9C;
|
||||||
|
static constexpr uint8_t PARAM_DATA = 0x9D;
|
||||||
|
static constexpr uint8_t PARAM_RESET_POLARITY = 0x9E;
|
||||||
|
static constexpr uint8_t PARAM_CONTROLLER_INIT = 0x9F;
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
// STK answer constants
|
||||||
|
|
||||||
|
static constexpr uint8_t ANSWER_CKSUM_ERROR = 0xB0;
|
||||||
1
stk500v2/flash
Submodule
1
stk500v2/flash
Submodule
Submodule stk500v2/flash added at 6edb2e5a21
1
stk500v2/io
Submodule
1
stk500v2/io
Submodule
Submodule stk500v2/io added at 80de36ee7e
667
stk500v2/main.cpp
Normal file
667
stk500v2/main.cpp
Normal file
@@ -0,0 +1,667 @@
|
|||||||
|
#include "clock.hpp"
|
||||||
|
|
||||||
|
#include "stk500v2.hpp"
|
||||||
|
|
||||||
|
#include "uart/uart.hpp"
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <avr/boot.h>
|
||||||
|
#include <avr/interrupt.h>
|
||||||
|
#include <avr/io.h>
|
||||||
|
#include <avr/pgmspace.h>
|
||||||
|
|
||||||
|
#include "command.hpp"
|
||||||
|
|
||||||
|
static constexpr auto TIMEOUT = 5000;
|
||||||
|
static constexpr auto BAUD_RATE = 115200;
|
||||||
|
|
||||||
|
using uart_interface = uart::Hardware0<uart::Config<BAUD_RATE>, uart::Driven::BLOCKING>;
|
||||||
|
uart::Uart<uart_interface> serial;
|
||||||
|
|
||||||
|
struct Message {
|
||||||
|
uint8_t start;
|
||||||
|
uint8_t number;
|
||||||
|
uint16_t size;
|
||||||
|
uint8_t token;
|
||||||
|
uint8_t body[275];
|
||||||
|
uint8_t checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline bool receiveByte(uint8_t &data, uint16_t &timeout)
|
||||||
|
{
|
||||||
|
constexpr auto MICROSECOND = 1000.0 * 1000;
|
||||||
|
constexpr auto SYMBOL_SIZE = 9;
|
||||||
|
constexpr auto BYTE_DELAY_US = (SYMBOL_SIZE * MICROSECOND) / BAUD_RATE;
|
||||||
|
constexpr auto NUM_MS_DELAY_STEPS = static_cast<uint16_t>(round(1000 / BYTE_DELAY_US));
|
||||||
|
uint16_t msDelay = NUM_MS_DELAY_STEPS;
|
||||||
|
|
||||||
|
while (timeout) {
|
||||||
|
if (serial.rxByte(data)) {
|
||||||
|
timeout = TIMEOUT;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_delay_us(BYTE_DELAY_US);
|
||||||
|
|
||||||
|
if (--msDelay == 0) {
|
||||||
|
msDelay = NUM_MS_DELAY_STEPS;
|
||||||
|
--timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t calcChecksum(const Message &msg)
|
||||||
|
{
|
||||||
|
uint8_t checksum = msg.start;
|
||||||
|
|
||||||
|
for (uint16_t i = 1; i < 5 + msg.size; ++i) {
|
||||||
|
checksum ^= *(reinterpret_cast<const uint8_t *>(&msg) + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool receiveMessage(Message &msg, uint16_t &timeout)
|
||||||
|
{
|
||||||
|
if (!receiveByte(msg.start, timeout) || msg.start != MESSAGE_START)
|
||||||
|
return false;
|
||||||
|
if (!receiveByte(msg.number, timeout))
|
||||||
|
return false;
|
||||||
|
if (!receiveByte(*(reinterpret_cast<uint8_t *>(&msg.size) + 1), timeout))
|
||||||
|
return false;
|
||||||
|
if (!receiveByte(*reinterpret_cast<uint8_t *>(&msg.size), timeout) || msg.size > sizeof(msg.body))
|
||||||
|
return false;
|
||||||
|
if (!receiveByte(msg.token, timeout) || msg.token != TOKEN)
|
||||||
|
return false;
|
||||||
|
for (uint16_t i = 0; i < msg.size; ++i) {
|
||||||
|
if (!receiveByte(msg.body[i], timeout))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!receiveByte(msg.checksum, timeout) || msg.checksum != calcChecksum(msg))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void transmitMessage(const Message &msg)
|
||||||
|
{
|
||||||
|
serial.txByte(msg.start);
|
||||||
|
serial.txByte(msg.number);
|
||||||
|
serial.txByte(msg.size >> 8);
|
||||||
|
serial.txByte(msg.size & 0xFF);
|
||||||
|
serial.txByte(msg.token);
|
||||||
|
for (uint16_t i = 0; i < msg.size; ++i)
|
||||||
|
serial.txByte(msg.body[i]);
|
||||||
|
serial.txByte(msg.checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isSignOn(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 1 && msg.body[0] == CMD_SIGN_ON)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isGetParameter(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 2 && msg.body[0] == CMD_GET_PARAMETER)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isSetParameter(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 3 && msg.body[0] == CMD_SET_PARAMETER)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isEnterProgmodeIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 12 && msg.body[0] == CMD_ENTER_PROGMODE_ISP)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isReadSignatureIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 6 && msg.body[0] == CMD_READ_SIGNATURE_ISP)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isReadFuseIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 6 && msg.body[0] == CMD_READ_FUSE_ISP)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isReadLockIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 6 && msg.body[0] == CMD_READ_LOCK_ISP)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isLoadAddress(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 5 && msg.body[0] == CMD_LOAD_ADDRESS)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isReadFlashIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 4 && msg.body[0] == CMD_READ_FLASH_ISP)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isReadEepromIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 4 && msg.body[0] == CMD_READ_EEPROM_ISP)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isChipEraseIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 7 && msg.body[0] == CMD_CHIP_ERASE_ISP)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isProgramFlashIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.body[0] == CMD_PROGRAM_FLASH_ISP) {
|
||||||
|
const auto dataSize = static_cast<uint16_t>(msg.body[1]) << 8 | msg.body[2];
|
||||||
|
if (msg.size == (dataSize + 10) && dataSize == SPM_PAGESIZE)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isProgramEepromIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.body[0] == CMD_PROGRAM_EEPROM_ISP) {
|
||||||
|
if (msg.size == (static_cast<uint16_t>(msg.body[1]) << 8 | msg.body[2]) + 10)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool isLeaveProgmodeIsp(const Message &msg)
|
||||||
|
{
|
||||||
|
if (msg.size == 3 && msg.body[0] == CMD_LEAVE_PROGMODE_ISP)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatSignOnAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.size = 3 + 8;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
msg.body[2] = 8;
|
||||||
|
msg.body[3] = 'S';
|
||||||
|
msg.body[4] = 'T';
|
||||||
|
msg.body[5] = 'K';
|
||||||
|
msg.body[6] = '5';
|
||||||
|
msg.body[7] = '0';
|
||||||
|
msg.body[8] = '0';
|
||||||
|
msg.body[9] = '_';
|
||||||
|
msg.body[10] = '2';
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatGetParameterAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.size = 3;
|
||||||
|
|
||||||
|
if (msg.body[1] == PARAM_HW_VER) {
|
||||||
|
msg.body[2] = 1;
|
||||||
|
} else if (msg.body[1] == PARAM_SW_MAJOR) {
|
||||||
|
msg.body[2] = 0x02;
|
||||||
|
} else if (msg.body[1] == PARAM_SW_MINOR) {
|
||||||
|
msg.body[2] = 0x0a;
|
||||||
|
} else if (msg.body[1] == PARAM_SCK_DURATION) {
|
||||||
|
msg.body[2] = 2;
|
||||||
|
} else if (msg.body[1] == PARAM_VADJUST) {
|
||||||
|
msg.body[2] = 25;
|
||||||
|
} else if (msg.body[1] == PARAM_VTARGET) {
|
||||||
|
msg.body[2] = 49;
|
||||||
|
} else if (msg.body[1] == PARAM_OSC_PSCALE) {
|
||||||
|
msg.body[2] = 2;
|
||||||
|
} else if (msg.body[1] == PARAM_OSC_CMATCH) {
|
||||||
|
msg.body[2] = 127;
|
||||||
|
} else if (msg.body[1] == PARAM_TOPCARD_DETECT) {
|
||||||
|
msg.body[2] = 0xFF;
|
||||||
|
} else {
|
||||||
|
msg.size = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.size == 2) {
|
||||||
|
msg.body[1] = STATUS_CMD_FAILED;
|
||||||
|
} else {
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatSetParameterAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.size = 2;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatEnterProgmodeIspAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.size = 2;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatReadSignatureIspAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.size = 4;
|
||||||
|
msg.body[2] = boot_signature_byte_get(msg.body[4] * 2);
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
msg.body[3] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatReadFuseIspAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
constexpr auto READ_LOW_FUSE_BITS = 0x0050;
|
||||||
|
constexpr auto READ_HIGH_FUSE_BITS = 0x0858;
|
||||||
|
constexpr auto READ_EXTENDED_FUSE_BITS = 0x0850;
|
||||||
|
|
||||||
|
msg.size = 4;
|
||||||
|
|
||||||
|
if (*reinterpret_cast<uint16_t *>(msg.body + 2) == READ_EXTENDED_FUSE_BITS) {
|
||||||
|
msg.body[2] = boot_lock_fuse_bits_get(GET_EXTENDED_FUSE_BITS);
|
||||||
|
}
|
||||||
|
if (*reinterpret_cast<uint16_t *>(msg.body + 2) == READ_HIGH_FUSE_BITS) {
|
||||||
|
msg.body[2] = boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS);
|
||||||
|
}
|
||||||
|
if (*reinterpret_cast<uint16_t *>(msg.body + 2) == READ_LOW_FUSE_BITS) {
|
||||||
|
msg.body[2] = boot_lock_fuse_bits_get(GET_LOW_FUSE_BITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
msg.body[3] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatReadLockIspAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.size = 4;
|
||||||
|
msg.body[2] = boot_lock_fuse_bits_get(GET_LOCK_BITS);
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
msg.body[3] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatLoadAddressAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.size = 2;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatReadFlashIspAnswer(Message &msg, uint32_t &addr)
|
||||||
|
{
|
||||||
|
const uint16_t byteAddress = 2 * addr;
|
||||||
|
const uint16_t numBytes = static_cast<uint16_t>(msg.body[1]) << 8 | msg.body[2];
|
||||||
|
msg.size = 3 + numBytes;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
for (uint16_t i = 0; i < numBytes; ++i) {
|
||||||
|
msg.body[i + 2] = pgm_read_byte(static_cast<uint16_t>(byteAddress + i));
|
||||||
|
}
|
||||||
|
const auto numWords = numBytes / 2;
|
||||||
|
addr += numWords;
|
||||||
|
msg.body[numBytes + 2] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool isEepromReady()
|
||||||
|
{
|
||||||
|
return (EECR & (1 << EEPE)) ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitEepromReady()
|
||||||
|
{
|
||||||
|
while (!isEepromReady())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t readEepromByte(const uint8_t *addr)
|
||||||
|
{
|
||||||
|
EEAR = reinterpret_cast<uint16_t>(addr);
|
||||||
|
EECR |= (1 << EERE);
|
||||||
|
return EEDR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeEepromByte(uint8_t *addr, uint8_t value)
|
||||||
|
{
|
||||||
|
EECR = 0;
|
||||||
|
EEAR = reinterpret_cast<uint16_t>(addr);
|
||||||
|
EEDR = value;
|
||||||
|
EECR |= (1 << EEMPE);
|
||||||
|
EECR |= (1 << EEPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void writeFlashPage(uint32_t pageAddress, const uint8_t *data)
|
||||||
|
{
|
||||||
|
boot_page_erase(pageAddress);
|
||||||
|
boot_spm_busy_wait();
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < SPM_PAGESIZE; i += 2) {
|
||||||
|
uint16_t dataWord = *data++;
|
||||||
|
dataWord |= (*data++) << 8;
|
||||||
|
boot_page_fill(pageAddress + i, dataWord);
|
||||||
|
}
|
||||||
|
|
||||||
|
boot_page_write(pageAddress);
|
||||||
|
boot_spm_busy_wait();
|
||||||
|
boot_rww_enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
static inline uint16_t getBootloaderSize()
|
||||||
|
{
|
||||||
|
const auto highFuse = boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS);
|
||||||
|
constexpr auto BOOTSZ0 = 1;
|
||||||
|
constexpr auto BOOTSZ1 = 2;
|
||||||
|
|
||||||
|
if (highFuse & (1 << BOOTSZ1) && highFuse & (1 << BOOTSZ0))
|
||||||
|
return 256 * 2;
|
||||||
|
else if (highFuse & (1 << BOOTSZ1))
|
||||||
|
return 512 * 2;
|
||||||
|
else if (highFuse & (1 << BOOTSZ0))
|
||||||
|
return 1024 * 2;
|
||||||
|
return 2048 * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t getFlashSize()
|
||||||
|
{
|
||||||
|
const auto bootloaderSize = getBootloaderSize();
|
||||||
|
return (FLASHEND - bootloaderSize + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void performChipErase(uint16_t flashStartAddress = 0x0000)
|
||||||
|
{
|
||||||
|
constexpr auto getEepromEraseFuseBit = []() -> bool {
|
||||||
|
constexpr auto EESAVE = 3;
|
||||||
|
return boot_lock_fuse_bits_get(GET_HIGH_FUSE_BITS) & (1 << EESAVE);
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto eraseFlash = [](const uint16_t &flashStartAddress) {
|
||||||
|
const auto flashSize = getFlashSize();
|
||||||
|
const auto byteAddress = 2 * flashStartAddress;
|
||||||
|
for (uint16_t i = byteAddress; i < flashSize; i += SPM_PAGESIZE) {
|
||||||
|
boot_page_erase(i);
|
||||||
|
boot_spm_busy_wait();
|
||||||
|
}
|
||||||
|
boot_rww_enable();
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto eraseEeprom = [getEepromEraseFuseBit]() {
|
||||||
|
const auto eraseEeprom = getEepromEraseFuseBit();
|
||||||
|
if (eraseEeprom) {
|
||||||
|
constexpr auto EEPROM_SIZE = E2END + 1;
|
||||||
|
for (uint16_t i = 0; i < EEPROM_SIZE; ++i) {
|
||||||
|
writeEepromByte(reinterpret_cast<uint8_t *>(i), 0xFF);
|
||||||
|
waitEepromReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
eraseFlash(flashStartAddress);
|
||||||
|
eraseEeprom();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatChipEraseIspAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.size = 2;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatReadEepromIspAnswer(Message &msg, uint32_t &addr)
|
||||||
|
{
|
||||||
|
const uint16_t numBytes = static_cast<uint16_t>(msg.body[1]) << 8 | msg.body[2];
|
||||||
|
msg.size = 3 + numBytes;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
for (uint16_t i = 0; i < numBytes; ++i) {
|
||||||
|
msg.body[i + 2] = readEepromByte(reinterpret_cast<const uint8_t *>(addr + i));
|
||||||
|
}
|
||||||
|
addr += numBytes;
|
||||||
|
msg.body[numBytes + 2] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatProgramFlashIspAnswer(Message &msg, uint32_t &addr)
|
||||||
|
{
|
||||||
|
const auto byteAddress = 2 * addr;
|
||||||
|
if (byteAddress < getFlashSize())
|
||||||
|
writeFlashPage(byteAddress, msg.body + 10);
|
||||||
|
const uint16_t numBytes = static_cast<uint16_t>(msg.body[1]) << 8 | msg.body[2];
|
||||||
|
const auto numWords = numBytes / 2;
|
||||||
|
addr += numWords;
|
||||||
|
|
||||||
|
msg.size = 2;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatProgramEepromIspAnswer(Message &msg, uint32_t &addr)
|
||||||
|
{
|
||||||
|
const uint16_t numBytes = static_cast<uint16_t>(msg.body[1]) << 8 | msg.body[2];
|
||||||
|
for (uint16_t i = 0; i < numBytes; ++i) {
|
||||||
|
writeEepromByte(reinterpret_cast<uint8_t *>(addr + i), msg.body[10 + i]);
|
||||||
|
waitEepromReady();
|
||||||
|
}
|
||||||
|
addr += numBytes;
|
||||||
|
msg.size = 2;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatLeaveProgmodeIspAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.size = 2;
|
||||||
|
msg.body[1] = STATUS_CMD_OK;
|
||||||
|
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void formatErrorAnswer(Message &msg)
|
||||||
|
{
|
||||||
|
msg.start = MESSAGE_START;
|
||||||
|
msg.size = 1;
|
||||||
|
msg.token = TOKEN;
|
||||||
|
msg.body[0] = STATUS_CMD_UNKNOWN;
|
||||||
|
msg.checksum = calcChecksum(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ChipEraseState {
|
||||||
|
NONE = 0,
|
||||||
|
REQUEST = (1 << 1),
|
||||||
|
RESPONSE = (1 << 2),
|
||||||
|
PERFORM = REQUEST | RESPONSE,
|
||||||
|
PROGRAM = (1 << 3),
|
||||||
|
FINISH = REQUEST | RESPONSE | PROGRAM,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr ChipEraseState operator|(const ChipEraseState &self, const ChipEraseState &other)
|
||||||
|
{
|
||||||
|
return static_cast<ChipEraseState>(static_cast<uint8_t>(self) | static_cast<uint8_t>(other));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr ChipEraseState &operator|=(ChipEraseState &self, const ChipEraseState &other)
|
||||||
|
{
|
||||||
|
self = self | other;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void handleMessage(Message &msg, uint32_t &addr, uint16_t &finishEraseAddress,
|
||||||
|
ChipEraseState &chipEraseFlag)
|
||||||
|
{
|
||||||
|
if (isSignOn(msg))
|
||||||
|
formatSignOnAnswer(msg);
|
||||||
|
else if (isGetParameter(msg))
|
||||||
|
formatGetParameterAnswer(msg);
|
||||||
|
else if (isSetParameter(msg))
|
||||||
|
formatSetParameterAnswer(msg);
|
||||||
|
else if (isEnterProgmodeIsp(msg))
|
||||||
|
formatEnterProgmodeIspAnswer(msg);
|
||||||
|
else if (isReadSignatureIsp(msg))
|
||||||
|
formatReadSignatureIspAnswer(msg);
|
||||||
|
else if (isReadFuseIsp(msg))
|
||||||
|
formatReadFuseIspAnswer(msg);
|
||||||
|
else if (isReadLockIsp(msg))
|
||||||
|
formatReadLockIspAnswer(msg);
|
||||||
|
else if (isLoadAddress(msg)) {
|
||||||
|
addr = msg.body[1];
|
||||||
|
addr = (addr << 8) | msg.body[2];
|
||||||
|
addr = (addr << 8) | msg.body[3];
|
||||||
|
addr = (addr << 8) | msg.body[4];
|
||||||
|
formatLoadAddressAnswer(msg);
|
||||||
|
} else if (isReadFlashIsp(msg))
|
||||||
|
formatReadFlashIspAnswer(msg, addr);
|
||||||
|
else if (isReadEepromIsp(msg))
|
||||||
|
formatReadEepromIspAnswer(msg, addr);
|
||||||
|
else if (isChipEraseIsp(msg)) {
|
||||||
|
chipEraseFlag |= ChipEraseState::REQUEST;
|
||||||
|
formatChipEraseIspAnswer(msg);
|
||||||
|
} else if (isProgramFlashIsp(msg)) {
|
||||||
|
chipEraseFlag |= ChipEraseState::PROGRAM;
|
||||||
|
formatProgramFlashIspAnswer(msg, addr);
|
||||||
|
finishEraseAddress = addr;
|
||||||
|
} else if (isProgramEepromIsp(msg))
|
||||||
|
formatProgramEepromIspAnswer(msg, addr);
|
||||||
|
else if (isLeaveProgmodeIsp(msg)) {
|
||||||
|
chipEraseFlag |= ChipEraseState::RESPONSE;
|
||||||
|
formatLeaveProgmodeIspAnswer(msg);
|
||||||
|
} else
|
||||||
|
formatErrorAnswer(msg);
|
||||||
|
|
||||||
|
transmitMessage(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void interfaceTest()
|
||||||
|
{
|
||||||
|
constexpr auto onSignOn = []() {};
|
||||||
|
constexpr auto onSetParam = []() {};
|
||||||
|
constexpr auto onGetParam = [](stk500v2::Param param, uint8_t &result) {
|
||||||
|
using stk500v2::Param;
|
||||||
|
|
||||||
|
switch (param) {
|
||||||
|
case Param::HW_VER:
|
||||||
|
result = 1;
|
||||||
|
break;
|
||||||
|
case Param::SW_MAJOR:
|
||||||
|
result = 2;
|
||||||
|
break;
|
||||||
|
case Param::SW_MINOR:
|
||||||
|
result = 10;
|
||||||
|
break;
|
||||||
|
case Param::SCK_DURATION:
|
||||||
|
result = 2;
|
||||||
|
break;
|
||||||
|
case Param::VADJUST:
|
||||||
|
result = 25;
|
||||||
|
break;
|
||||||
|
case Param::VTARGET:
|
||||||
|
result = 49;
|
||||||
|
break;
|
||||||
|
case Param::OSC_PSCALE:
|
||||||
|
result = 2;
|
||||||
|
break;
|
||||||
|
case Param::OSC_CMATCH:
|
||||||
|
result = 127;
|
||||||
|
break;
|
||||||
|
case Param::TOPCARD_DETECT:
|
||||||
|
result = 0xFF;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr auto callbacks = stk500v2::Callbacks<onSignOn, onSetParam, onGetParam>();
|
||||||
|
|
||||||
|
stk500v2::Stk500v2<uart_interface, TIMEOUT, callbacks> stk500;
|
||||||
|
stk500.init();
|
||||||
|
|
||||||
|
while (stk500.callback())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
/*interfaceTest();
|
||||||
|
return 0;*/
|
||||||
|
|
||||||
|
serial.init();
|
||||||
|
|
||||||
|
Message msg;
|
||||||
|
uint32_t addr = 0x0000;
|
||||||
|
uint16_t finishEraseAddress = 0x0000;
|
||||||
|
ChipEraseState chipEraseFlag = ChipEraseState::NONE;
|
||||||
|
uint16_t timeout = TIMEOUT;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (receiveMessage(msg, timeout)) {
|
||||||
|
handleMessage(msg, addr, finishEraseAddress, chipEraseFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeout == 0) {
|
||||||
|
if (chipEraseFlag == ChipEraseState::PERFORM) {
|
||||||
|
performChipErase();
|
||||||
|
chipEraseFlag = ChipEraseState::NONE;
|
||||||
|
} else if (chipEraseFlag == ChipEraseState::FINISH) {
|
||||||
|
performChipErase(finishEraseAddress);
|
||||||
|
chipEraseFlag = ChipEraseState::NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
asm volatile("jmp 0x0000");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startup() __attribute__((naked, section(".vectors")));
|
||||||
|
void startup()
|
||||||
|
{
|
||||||
|
asm volatile("clr __zero_reg__");
|
||||||
|
SP = RAMEND;
|
||||||
|
SREG = 0;
|
||||||
|
asm volatile("jmp main");
|
||||||
|
}
|
||||||
275
stk500v2/stk500v2.cppproj
Normal file
275
stk500v2/stk500v2.cppproj
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectVersion>7.0</ProjectVersion>
|
||||||
|
<ToolchainName>com.Atmel.AVRGCC8.CPP</ToolchainName>
|
||||||
|
<ProjectGuid>{19798cce-5d96-40e9-b769-d209715dce0c}</ProjectGuid>
|
||||||
|
<avrdevice>ATmega328P</avrdevice>
|
||||||
|
<avrdeviceseries>none</avrdeviceseries>
|
||||||
|
<OutputType>Executable</OutputType>
|
||||||
|
<Language>CPP</Language>
|
||||||
|
<OutputFileName>$(MSBuildProjectName)</OutputFileName>
|
||||||
|
<OutputFileExtension>.elf</OutputFileExtension>
|
||||||
|
<OutputDirectory>$(MSBuildProjectDirectory)\$(Configuration)</OutputDirectory>
|
||||||
|
<AssemblyName>stk500v2</AssemblyName>
|
||||||
|
<Name>stk500v2</Name>
|
||||||
|
<RootNamespace>stk500v2</RootNamespace>
|
||||||
|
<ToolchainFlavour>avr-g++-10.0.1</ToolchainFlavour>
|
||||||
|
<KeepTimersRunning>true</KeepTimersRunning>
|
||||||
|
<OverrideVtor>false</OverrideVtor>
|
||||||
|
<CacheFlash>true</CacheFlash>
|
||||||
|
<ProgFlashFromRam>true</ProgFlashFromRam>
|
||||||
|
<RamSnippetAddress>0x20000000</RamSnippetAddress>
|
||||||
|
<UncachedRange />
|
||||||
|
<preserveEEPROM>true</preserveEEPROM>
|
||||||
|
<OverrideVtorValue>exception_table</OverrideVtorValue>
|
||||||
|
<BootSegment>2</BootSegment>
|
||||||
|
<ResetRule>0</ResetRule>
|
||||||
|
<eraseonlaunchrule>0</eraseonlaunchrule>
|
||||||
|
<EraseKey />
|
||||||
|
<avrtool>com.atmel.avrdbg.tool.atmelice</avrtool>
|
||||||
|
<avrtoolserialnumber>J41800099437</avrtoolserialnumber>
|
||||||
|
<avrdeviceexpectedsignature>0x1E950F</avrdeviceexpectedsignature>
|
||||||
|
<com_atmel_avrdbg_tool_stk500>
|
||||||
|
<ToolOptions>
|
||||||
|
<InterfaceProperties>
|
||||||
|
<IspClock>125000</IspClock>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>ISP</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType>com.atmel.avrdbg.tool.stk500</ToolType>
|
||||||
|
<ToolNumber>
|
||||||
|
</ToolNumber>
|
||||||
|
<ToolName>STK500</ToolName>
|
||||||
|
</com_atmel_avrdbg_tool_stk500>
|
||||||
|
<avrtoolinterface>ISP</avrtoolinterface>
|
||||||
|
<avrtoolinterfaceclock>125000</avrtoolinterfaceclock>
|
||||||
|
<AsfFrameworkConfig>
|
||||||
|
<framework-data xmlns="">
|
||||||
|
<options />
|
||||||
|
<configurations />
|
||||||
|
<files />
|
||||||
|
<documentation help="" />
|
||||||
|
<offline-documentation help="" />
|
||||||
|
<dependencies>
|
||||||
|
<content-extension eid="atmel.asf" uuidref="Atmel.ASF" version="3.47.0" />
|
||||||
|
</dependencies>
|
||||||
|
</framework-data>
|
||||||
|
</AsfFrameworkConfig>
|
||||||
|
<com_atmel_avrdbg_tool_atmelice>
|
||||||
|
<ToolOptions>
|
||||||
|
<InterfaceProperties>
|
||||||
|
<IspClock>125000</IspClock>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>ISP</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType>com.atmel.avrdbg.tool.atmelice</ToolType>
|
||||||
|
<ToolNumber>J41800099437</ToolNumber>
|
||||||
|
<ToolName>Atmel-ICE</ToolName>
|
||||||
|
</com_atmel_avrdbg_tool_atmelice>
|
||||||
|
<custom>
|
||||||
|
<ToolOptions>
|
||||||
|
<InterfaceProperties>
|
||||||
|
<IspClock>125000</IspClock>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>
|
||||||
|
</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType>custom</ToolType>
|
||||||
|
<ToolNumber>
|
||||||
|
</ToolNumber>
|
||||||
|
<ToolName>Custom Programming Tool</ToolName>
|
||||||
|
</custom>
|
||||||
|
<com_atmel_avrdbg_tool_simulator>
|
||||||
|
<ToolOptions xmlns="">
|
||||||
|
<InterfaceProperties>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>
|
||||||
|
</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType xmlns="">com.atmel.avrdbg.tool.simulator</ToolType>
|
||||||
|
<ToolNumber xmlns="">
|
||||||
|
</ToolNumber>
|
||||||
|
<ToolName xmlns="">Simulator</ToolName>
|
||||||
|
</com_atmel_avrdbg_tool_simulator>
|
||||||
|
<AAFDebugger>
|
||||||
|
<AAFDebugFiles>
|
||||||
|
</AAFDebugFiles>
|
||||||
|
</AAFDebugger>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<ToolchainSettings>
|
||||||
|
<AvrGccCpp>
|
||||||
|
<avrgcc.common.Device>-mmcu=atmega328p</avrgcc.common.Device>
|
||||||
|
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
|
||||||
|
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
|
||||||
|
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
|
||||||
|
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
|
||||||
|
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>NDEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<avrgcc.compiler.optimization.level>Optimize for size (-Os)</avrgcc.compiler.optimization.level>
|
||||||
|
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings>
|
||||||
|
<avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic>
|
||||||
|
<avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>NDEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<avrgcccpp.compiler.optimization.level>Optimize for size (-Os)</avrgcccpp.compiler.optimization.level>
|
||||||
|
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic>
|
||||||
|
<avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++20</avrgcccpp.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.linker.general.NoStartupOrDefaultLibs>True</avrgcccpp.linker.general.NoStartupOrDefaultLibs>
|
||||||
|
<avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<ListValues>
|
||||||
|
<Value>libm</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<avrgcccpp.linker.memorysettings.Flash>
|
||||||
|
<ListValues>
|
||||||
|
<Value>.text=0x3C00</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.linker.memorysettings.Flash>
|
||||||
|
<avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
</AvrGccCpp>
|
||||||
|
</ToolchainSettings>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<ToolchainSettings>
|
||||||
|
<AvrGccCpp>
|
||||||
|
<avrgcc.common.Device>-mmcu=atmega328p</avrgcc.common.Device>
|
||||||
|
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
|
||||||
|
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
|
||||||
|
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
|
||||||
|
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
|
||||||
|
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>DEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<avrgcc.compiler.optimization.level>Optimize (-O1)</avrgcc.compiler.optimization.level>
|
||||||
|
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcc.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcc.compiler.optimization.DebugLevel>
|
||||||
|
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings>
|
||||||
|
<avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic>
|
||||||
|
<avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>DEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<avrgcccpp.compiler.optimization.level>Optimize debugging experience (-Og)</avrgcccpp.compiler.optimization.level>
|
||||||
|
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcccpp.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcccpp.compiler.optimization.DebugLevel>
|
||||||
|
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic>
|
||||||
|
<avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++20</avrgcccpp.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.linker.general.NoStartupOrDefaultLibs>True</avrgcccpp.linker.general.NoStartupOrDefaultLibs>
|
||||||
|
<avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<ListValues>
|
||||||
|
<Value>libm</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<avrgcccpp.linker.memorysettings.Flash>
|
||||||
|
<ListValues>
|
||||||
|
<Value>.text=0x3800</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.linker.memorysettings.Flash>
|
||||||
|
<avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
<avrgcccpp.assembler.debugging.DebugLevel>Default (-Wa,-g)</avrgcccpp.assembler.debugging.DebugLevel>
|
||||||
|
</AvrGccCpp>
|
||||||
|
</ToolchainSettings>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="clock.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="command.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="flash\flash.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="io\io.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="main.cpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="type\type.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\config.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\hardware.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\hardware0.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\hardware1.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\software.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\uart.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="flash" />
|
||||||
|
<Folder Include="io" />
|
||||||
|
<Folder Include="uart" />
|
||||||
|
<Folder Include="type" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(AVRSTUDIO_EXE_PATH)\\Vs\\Compiler.targets" />
|
||||||
|
</Project>
|
||||||
344
stk500v2/stk500v2.hpp
Normal file
344
stk500v2/stk500v2.hpp
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace stk500v2 {
|
||||||
|
|
||||||
|
enum class Msg : uint8_t {
|
||||||
|
START = 0x1B, // ASCII ESC
|
||||||
|
TOKEN = 0x0E,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr bool operator==(const Msg &msg, const uint8_t &num)
|
||||||
|
{
|
||||||
|
return static_cast<uint8_t>(msg) == num;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Cmd : uint8_t {
|
||||||
|
SIGN_ON = 0x01,
|
||||||
|
SET_PARAMETER = 0x02,
|
||||||
|
GET_PARAMETER = 0x03,
|
||||||
|
SET_DEVICE_PARAMETERS = 0x04,
|
||||||
|
OSCCAL = 0x05,
|
||||||
|
LOAD_ADDRESS = 0x06,
|
||||||
|
FIRMWARE_UPGRADE = 0x07,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr bool operator==(const Cmd &cmd, const uint8_t &num)
|
||||||
|
{
|
||||||
|
return static_cast<uint8_t>(cmd) == num;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class IspCmd : uint8_t {
|
||||||
|
ENTER_PROGMODE = 0x10,
|
||||||
|
LEAVE_PROGMODE = 0x11,
|
||||||
|
CHIP_ERASE = 0x12,
|
||||||
|
PROGRAM_FLASH = 0x13,
|
||||||
|
READ_FLASH = 0x14,
|
||||||
|
PROGRAM_EEPROM = 0x15,
|
||||||
|
READ_EEPROM = 0x16,
|
||||||
|
PROGRAM_FUSE = 0x17,
|
||||||
|
READ_FUSE = 0x18,
|
||||||
|
PROGRAM_LOCK = 0x19,
|
||||||
|
READ_LOCK = 0x1A,
|
||||||
|
READ_SIGNATURE = 0x1B,
|
||||||
|
READ_OSCCAL = 0x1C,
|
||||||
|
|
||||||
|
SPI_MULTI = 0x1D,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class PpCmd : uint8_t {
|
||||||
|
ENTER_PROGMODE = 0x20,
|
||||||
|
LEAVE_PROGMODE = 0x21,
|
||||||
|
CHIP_ERASE = 0x22,
|
||||||
|
PROGRAM_FLASH = 0x23,
|
||||||
|
READ_FLASH = 0x24,
|
||||||
|
PROGRAM_EEPROM = 0x25,
|
||||||
|
READ_EEPROM = 0x26,
|
||||||
|
PROGRAM_FUSE = 0x27,
|
||||||
|
READ_FUSE = 0x28,
|
||||||
|
PROGRAM_LOCK = 0x29,
|
||||||
|
READ_LOCK = 0x2A,
|
||||||
|
READ_SIGNATURE = 0x2B,
|
||||||
|
READ_OSCCAL = 0x2C,
|
||||||
|
|
||||||
|
SET_CONTROL_STACK = 0x2D,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class HvspCmd : uint8_t {
|
||||||
|
ENTER_PROGMODE = 0x30,
|
||||||
|
LEAVE_PROGMODE = 0x31,
|
||||||
|
CHIP_ERASE = 0x32,
|
||||||
|
PROGRAM_FLASH = 0x33,
|
||||||
|
READ_FLASH = 0x34,
|
||||||
|
PROGRAM_EEPROM = 0x35,
|
||||||
|
READ_EEPROM = 0x36,
|
||||||
|
PROGRAM_FUSE = 0x37,
|
||||||
|
READ_FUSE = 0x38,
|
||||||
|
PROGRAM_LOCK = 0x39,
|
||||||
|
READ_LOCK = 0x3A,
|
||||||
|
READ_SIGNATURE = 0x3B,
|
||||||
|
READ_OSCCAL = 0x3C,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Status : uint8_t {
|
||||||
|
// Success
|
||||||
|
CMD_OK = 0x00,
|
||||||
|
|
||||||
|
// Warnings
|
||||||
|
CMD_TOUT = 0x80,
|
||||||
|
RDY_BSY_TOUT = 0x81,
|
||||||
|
SET_PARAM_MISSING = 0x82,
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
CMD_FAILED = 0xC0,
|
||||||
|
CKSUM_ERROR = 0xC1,
|
||||||
|
CMD_UNKNOWN = 0xC9,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Param : uint8_t {
|
||||||
|
BUILD_NUMBER_LOW = 0x80,
|
||||||
|
BUILD_NUMBER_HIGH = 0x81,
|
||||||
|
HW_VER = 0x90,
|
||||||
|
SW_MAJOR = 0x91,
|
||||||
|
SW_MINOR = 0x92,
|
||||||
|
VTARGET = 0x94,
|
||||||
|
VADJUST = 0x95,
|
||||||
|
OSC_PSCALE = 0x96,
|
||||||
|
OSC_CMATCH = 0x97,
|
||||||
|
SCK_DURATION = 0x98,
|
||||||
|
TOPCARD_DETECT = 0x9A,
|
||||||
|
STATUS = 0x9C,
|
||||||
|
DATA = 0x9D,
|
||||||
|
RESET_POLARITY = 0x9E,
|
||||||
|
CONTROLLER_INIT = 0x9F,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Answer : uint8_t {
|
||||||
|
CKSUM_ERROR = 0xB0,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t Size>
|
||||||
|
struct Message {
|
||||||
|
uint8_t start;
|
||||||
|
uint8_t number;
|
||||||
|
uint16_t size;
|
||||||
|
uint8_t token;
|
||||||
|
uint8_t body[Size];
|
||||||
|
uint8_t checksum;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <auto SignOnFn, auto SetParamFn, auto GetParamFn>
|
||||||
|
struct Callbacks {
|
||||||
|
static constexpr auto onSignOn = SignOnFn;
|
||||||
|
static constexpr auto onSetParam = SetParamFn;
|
||||||
|
static constexpr auto onGetParam = GetParamFn;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Uart, uint16_t Timeout, auto CallbackFns>
|
||||||
|
class Stk500v2 {
|
||||||
|
public:
|
||||||
|
inline void init()
|
||||||
|
{
|
||||||
|
m_serial.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool callback()
|
||||||
|
{
|
||||||
|
if (receiveMessage()) {
|
||||||
|
handleMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_timeout)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr auto MSG_BUFFER_SIZE = 275;
|
||||||
|
using msg_t = Message<MSG_BUFFER_SIZE>;
|
||||||
|
|
||||||
|
msg_t m_msg;
|
||||||
|
Uart m_serial;
|
||||||
|
uint16_t m_timeout = Timeout;
|
||||||
|
|
||||||
|
inline bool receiveByte(uint8_t &data)
|
||||||
|
{
|
||||||
|
constexpr auto MICROSECOND = 1000.0 * 1000;
|
||||||
|
constexpr auto SYMBOL_SIZE = 9;
|
||||||
|
constexpr auto BYTE_DELAY_US = (SYMBOL_SIZE * MICROSECOND) / Uart::BAUD_RATE;
|
||||||
|
constexpr auto NUM_MS_DELAY_STEPS = static_cast<uint16_t>(round(1000 / BYTE_DELAY_US));
|
||||||
|
uint16_t msDelay = NUM_MS_DELAY_STEPS;
|
||||||
|
|
||||||
|
while (m_timeout) {
|
||||||
|
if (m_serial.rxByte(data)) {
|
||||||
|
m_timeout = Timeout;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_delay_us(BYTE_DELAY_US);
|
||||||
|
|
||||||
|
if (--msDelay == 0) {
|
||||||
|
msDelay = NUM_MS_DELAY_STEPS;
|
||||||
|
--m_timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint8_t calcChecksum() const
|
||||||
|
{
|
||||||
|
uint8_t checksum = static_cast<uint8_t>(m_msg.start);
|
||||||
|
|
||||||
|
for (uint16_t i = 1; i < 5 + m_msg.size; ++i) {
|
||||||
|
checksum ^= *(reinterpret_cast<const uint8_t *>(&m_msg) + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool receiveMessage()
|
||||||
|
{
|
||||||
|
constexpr auto CHECK_MESSAGE_FORMAT = true;
|
||||||
|
constexpr auto CHECK_MESSAGE_SIZE = true;
|
||||||
|
constexpr auto CHECK_MESSAGE_CHECKSUM = true;
|
||||||
|
|
||||||
|
if (!receiveByte(m_msg.start)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if constexpr (CHECK_MESSAGE_FORMAT) {
|
||||||
|
if (m_msg.start != Msg::START)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!receiveByte(m_msg.number))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!receiveByte(*(reinterpret_cast<uint8_t *>(&m_msg.size) + 1)))
|
||||||
|
return false;
|
||||||
|
if (!receiveByte(*reinterpret_cast<uint8_t *>(&m_msg.size)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if constexpr (CHECK_MESSAGE_SIZE) {
|
||||||
|
if (m_msg.size > sizeof(m_msg.body))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!receiveByte(m_msg.token))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if constexpr (CHECK_MESSAGE_FORMAT) {
|
||||||
|
if (m_msg.token != Msg::TOKEN)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint16_t i = 0; i < m_msg.size; ++i) {
|
||||||
|
if (!receiveByte(m_msg.body[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!receiveByte(m_msg.checksum))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if constexpr (CHECK_MESSAGE_CHECKSUM) {
|
||||||
|
if (m_msg.checksum != calcChecksum())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void transmitMessage() const
|
||||||
|
{
|
||||||
|
m_serial.txByte(m_msg.start);
|
||||||
|
m_serial.txByte(m_msg.number);
|
||||||
|
m_serial.txByte(m_msg.size >> 8);
|
||||||
|
m_serial.txByte(m_msg.size & 0xFF);
|
||||||
|
m_serial.txByte(m_msg.token);
|
||||||
|
for (uint16_t i = 0; i < m_msg.size; ++i)
|
||||||
|
m_serial.txByte(m_msg.body[i]);
|
||||||
|
m_serial.txByte(m_msg.checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void handleMessage()
|
||||||
|
{
|
||||||
|
constexpr auto CHECK_MESSAGE_LENGTH = true;
|
||||||
|
|
||||||
|
switch (m_msg.body[0]) {
|
||||||
|
case static_cast<uint8_t>(Cmd::SIGN_ON): {
|
||||||
|
if constexpr (CHECK_MESSAGE_LENGTH) {
|
||||||
|
if (m_msg.size != 1)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CallbackFns.onSignOn();
|
||||||
|
sendReply<static_cast<uint8_t>(Cmd::SIGN_ON)>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case static_cast<uint8_t>(Cmd::SET_PARAMETER): {
|
||||||
|
if constexpr (CHECK_MESSAGE_LENGTH) {
|
||||||
|
if (m_msg.size != 3)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CallbackFns.onSetParam();
|
||||||
|
sendReply<static_cast<uint8_t>(Cmd::SET_PARAMETER)>();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case static_cast<uint8_t>(Cmd::GET_PARAMETER): {
|
||||||
|
if constexpr (CHECK_MESSAGE_LENGTH) {
|
||||||
|
if (m_msg.size != 2)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto errorOccurred = CallbackFns.onGetParam(static_cast<Param>(m_msg.body[1]), m_msg.body[2]);
|
||||||
|
sendReply<static_cast<uint8_t>(Cmd::GET_PARAMETER)>(errorOccurred);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case static_cast<uint8_t>(Cmd::SET_DEVICE_PARAMETERS):
|
||||||
|
break;
|
||||||
|
case static_cast<uint8_t>(Cmd::OSCCAL):
|
||||||
|
break;
|
||||||
|
case static_cast<uint8_t>(Cmd::LOAD_ADDRESS):
|
||||||
|
break;
|
||||||
|
case static_cast<uint8_t>(Cmd::FIRMWARE_UPGRADE):
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <uint8_t Command>
|
||||||
|
inline void sendReply(bool errorOccurred = false)
|
||||||
|
{
|
||||||
|
if constexpr (Command == Cmd::SIGN_ON) {
|
||||||
|
m_msg.size = 3 + 8;
|
||||||
|
m_msg.body[1] = static_cast<uint8_t>(Status::CMD_OK);
|
||||||
|
m_msg.body[2] = 8;
|
||||||
|
m_msg.body[3] = 'S';
|
||||||
|
m_msg.body[4] = 'T';
|
||||||
|
m_msg.body[5] = 'K';
|
||||||
|
m_msg.body[6] = '5';
|
||||||
|
m_msg.body[7] = '0';
|
||||||
|
m_msg.body[8] = '0';
|
||||||
|
m_msg.body[9] = '_';
|
||||||
|
m_msg.body[10] = '2';
|
||||||
|
} else if (Command == Cmd::SET_PARAMETER) {
|
||||||
|
m_msg.size = 2;
|
||||||
|
m_msg.body[1] = static_cast<uint8_t>(Status::CMD_OK);
|
||||||
|
} else if (Command == Cmd::GET_PARAMETER) {
|
||||||
|
if (!errorOccurred) {
|
||||||
|
m_msg.size = 3;
|
||||||
|
m_msg.body[1] = static_cast<uint8_t>(Status::CMD_OK);
|
||||||
|
} else {
|
||||||
|
m_msg.size = 2;
|
||||||
|
m_msg.body[1] = static_cast<uint8_t>(Status::CMD_FAILED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_msg.checksum = calcChecksum();
|
||||||
|
|
||||||
|
transmitMessage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace stk500v2
|
||||||
1
stk500v2/type
Submodule
1
stk500v2/type
Submodule
Submodule stk500v2/type added at ce31ef017f
1
stk500v2/uart
Submodule
1
stk500v2/uart
Submodule
Submodule stk500v2/uart added at dd37fa0411
@@ -1,11 +0,0 @@
|
|||||||
execute_process(COMMAND ${SIZE_TOOL} ${ELF} OUTPUT_VARIABLE _out RESULT_VARIABLE _res)
|
|
||||||
if(NOT _res EQUAL 0)
|
|
||||||
message(FATAL_ERROR "avr-size failed")
|
|
||||||
endif()
|
|
||||||
# avr-size line 2 is "<text> <data> <bss> <dec> <hex> <file>".
|
|
||||||
string(REGEX MATCH "\n[ \t]*([0-9]+)" _m "${_out}")
|
|
||||||
set(_text ${CMAKE_MATCH_1})
|
|
||||||
if(_text GREATER LIMIT)
|
|
||||||
message(FATAL_ERROR ".text is ${_text} bytes, over the ${LIMIT}-byte boot section")
|
|
||||||
endif()
|
|
||||||
message(STATUS ".text ${_text} <= ${LIMIT} (boot section budget)")
|
|
||||||
109
test/device.c
109
test/device.c
@@ -1,109 +0,0 @@
|
|||||||
// simavr "device" for the TSB bootloader: load the boot-linked ELF into the
|
|
||||||
// ATmega328P boot section, enter it (BOOTRST is not modelled, so we set PC to
|
|
||||||
// the boot base, exactly as simavr's own board_simduino does), and expose
|
|
||||||
// UART0 as a pty. A host client (Python pyserial, or the real tsbloader) then
|
|
||||||
// speaks the TSB protocol over that pty and actually flashes the device.
|
|
||||||
//
|
|
||||||
// SPM genuinely writes avr->flash on the mega cores, so on exit (or SIGTERM)
|
|
||||||
// we dump the flash image to a file for a ground-truth cross-check against
|
|
||||||
// what the client read back through the bootloader.
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "avr_uart.h"
|
|
||||||
#include "sim_avr.h"
|
|
||||||
#include "sim_elf.h"
|
|
||||||
#include "uart_pty.h"
|
|
||||||
|
|
||||||
static avr_t *avr;
|
|
||||||
static uart_pty_t uart_pty;
|
|
||||||
static const char *dump_path;
|
|
||||||
|
|
||||||
static void finish(int sig)
|
|
||||||
{
|
|
||||||
(void)sig;
|
|
||||||
if (dump_path) {
|
|
||||||
FILE *f = fopen(dump_path, "wb");
|
|
||||||
if (f) {
|
|
||||||
fwrite(avr->flash, 1, avr->flashend + 1, f);
|
|
||||||
fclose(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
uart_pty_stop(&uart_pty);
|
|
||||||
_exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
if (argc < 3) {
|
|
||||||
fprintf(stderr, "usage: %s <tsb.elf> <boot_base_hex> [flash_dump.bin]\n", argv[0]);
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
uint32_t boot_base = (uint32_t)strtoul(argv[2], NULL, 0);
|
|
||||||
dump_path = argc >= 4 ? argv[3] : NULL;
|
|
||||||
|
|
||||||
avr = avr_make_mcu_by_name("atmega328p");
|
|
||||||
if (!avr) {
|
|
||||||
fprintf(stderr, "device: no ATmega328P core\n");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
avr_init(avr);
|
|
||||||
avr->frequency = 16000000;
|
|
||||||
// Real flash powers up erased (0xff); the app region must look erased
|
|
||||||
// before the bootloader programs it.
|
|
||||||
memset(avr->flash, 0xff, avr->flashend + 1);
|
|
||||||
|
|
||||||
// simavr's ELF loader flattens the flash base to 0 (it expects an app at
|
|
||||||
// 0x0), but it hands back the boot code in fw.flash; place it at the boot
|
|
||||||
// section base ourselves and enter there (BOOTRST is not modelled).
|
|
||||||
elf_firmware_t fw = {0};
|
|
||||||
if (elf_read_firmware(argv[1], &fw) != 0) {
|
|
||||||
fprintf(stderr, "device: cannot read %s\n", argv[1]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
memcpy(avr->flash + boot_base, fw.flash, fw.flashsize);
|
|
||||||
avr->pc = boot_base;
|
|
||||||
avr->codeend = avr->flashend;
|
|
||||||
|
|
||||||
// Optional: seed the config page (one page below the boot section) with a
|
|
||||||
// hex byte string, so the password gate and emergency erase can be tested.
|
|
||||||
// Layout: [appjump lo][appjump hi][timeout][password...][0xff].
|
|
||||||
const char *cfg = getenv("TSB_CONFIG");
|
|
||||||
if (cfg) {
|
|
||||||
uint32_t app_end = boot_base - 128; // config page sits directly below the boot code
|
|
||||||
for (int i = 0; cfg[i] && cfg[i + 1]; i += 2) {
|
|
||||||
char b[3] = {cfg[i], cfg[i + 1], 0};
|
|
||||||
avr->flash[app_end + i / 2] = (uint8_t)strtoul(b, NULL, 16);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// POLL_SLEEP makes simavr usleep(1) on every status-register read while the
|
|
||||||
// UART is idle — a host-CPU-saving hack that models no hardware and paces a
|
|
||||||
// tight-polling loader (one that releases TX between bytes, as one-wire does)
|
|
||||||
// in real time, distorting protocol timing. Clear it so the loader runs at
|
|
||||||
// true cycle speed.
|
|
||||||
uint32_t uflags = 0;
|
|
||||||
avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS('0'), &uflags);
|
|
||||||
uflags &= ~AVR_UART_FLAG_POLL_SLEEP;
|
|
||||||
avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS('0'), &uflags);
|
|
||||||
|
|
||||||
uart_pty_init(avr, &uart_pty);
|
|
||||||
uart_pty_connect(&uart_pty, '0');
|
|
||||||
printf("TSB_PTY %s\n", uart_pty.pty.slavename);
|
|
||||||
fflush(stdout);
|
|
||||||
|
|
||||||
signal(SIGTERM, finish);
|
|
||||||
signal(SIGINT, finish);
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
int state = avr_run(avr);
|
|
||||||
if (state == cpu_Done || state == cpu_Crashed)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
finish(0);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
252
test/tsbtest.py
252
test/tsbtest.py
@@ -1,252 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""End-to-end TSB protocol test: spawn the simavr device, speak the TinySafeBoot
|
|
||||||
wire protocol over its pty (as the real host tools do), and actually flash it.
|
|
||||||
|
|
||||||
Usage: tsbtest.py <device_binary> <tsb.elf> <boot_base_hex>
|
|
||||||
Exits 0 if every scenario passes.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
import serial
|
|
||||||
|
|
||||||
CONFIRM = 0x21 # '!'
|
|
||||||
REQUEST = 0x3F # '?'
|
|
||||||
KNOCK = 0x40 # '@'
|
|
||||||
PAGE = 128 # ATmega328P: 64 words
|
|
||||||
|
|
||||||
|
|
||||||
class Device:
|
|
||||||
"""The simavr runner, exposing UART0 as a pty. `config` seeds the config
|
|
||||||
page (via the device's TSB_CONFIG hook) so the password gate and emergency
|
|
||||||
erase are exercisable."""
|
|
||||||
|
|
||||||
def __init__(self, binary, elf, boot_base, dump="/tmp/tsb_dump.bin", config=None):
|
|
||||||
env = dict(os.environ)
|
|
||||||
if config is not None:
|
|
||||||
env["TSB_CONFIG"] = config
|
|
||||||
self.proc = subprocess.Popen(
|
|
||||||
[binary, elf, boot_base, dump],
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=env)
|
|
||||||
self.dump = dump
|
|
||||||
self.pty = None
|
|
||||||
deadline = time.time() + 5
|
|
||||||
while time.time() < deadline:
|
|
||||||
line = self.proc.stdout.readline()
|
|
||||||
if not line:
|
|
||||||
break
|
|
||||||
if line.startswith("TSB_PTY"):
|
|
||||||
self.pty = line.split()[1]
|
|
||||||
break
|
|
||||||
if not self.pty:
|
|
||||||
self.stop()
|
|
||||||
raise RuntimeError("device did not report a pty")
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.proc.terminate()
|
|
||||||
try:
|
|
||||||
self.proc.wait(timeout=3)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
self.proc.kill()
|
|
||||||
|
|
||||||
|
|
||||||
class Host:
|
|
||||||
"""A faithful TSB host, per the wire protocol."""
|
|
||||||
|
|
||||||
def __init__(self, pty):
|
|
||||||
self.s = serial.Serial(pty, 115200, timeout=1.5)
|
|
||||||
self.info = None
|
|
||||||
|
|
||||||
def _read(self, n):
|
|
||||||
data = self.s.read(n)
|
|
||||||
if len(data) != n:
|
|
||||||
raise AssertionError(f"expected {n} bytes, got {len(data)}: {data.hex()}")
|
|
||||||
return data
|
|
||||||
|
|
||||||
def activate(self):
|
|
||||||
self.s.reset_input_buffer()
|
|
||||||
self.s.write(b"@@@")
|
|
||||||
reply = self._read(17)
|
|
||||||
if reply[16] != CONFIRM:
|
|
||||||
raise AssertionError(f"activation reply not '!'-terminated: {reply.hex()}")
|
|
||||||
self.info = reply[:16]
|
|
||||||
return self.info
|
|
||||||
|
|
||||||
# Parsed info-block fields (host math from the spec).
|
|
||||||
@property
|
|
||||||
def pagesize(self):
|
|
||||||
return self.info[9] * 2
|
|
||||||
|
|
||||||
@property
|
|
||||||
def appflash(self):
|
|
||||||
return (self.info[10] | (self.info[11] << 8)) * 2
|
|
||||||
|
|
||||||
@property
|
|
||||||
def eeprom_size(self):
|
|
||||||
return (self.info[12] | (self.info[13] << 8)) + 1
|
|
||||||
|
|
||||||
def _expect(self, byte, what):
|
|
||||||
r = self._read(1)
|
|
||||||
if r[0] != byte:
|
|
||||||
raise AssertionError(f"{what}: expected {byte:#x}, got {r.hex()}")
|
|
||||||
|
|
||||||
# Host-paced page read ('f'/'e'): send '!', take a page, repeat; stop with
|
|
||||||
# anything else, then the Mainloop '!'.
|
|
||||||
def _read_pages(self, cmd, npages):
|
|
||||||
self.s.write(cmd.encode())
|
|
||||||
data = b""
|
|
||||||
for _ in range(npages):
|
|
||||||
self.s.write(bytes([CONFIRM]))
|
|
||||||
data += self._read(PAGE)
|
|
||||||
self.s.write(bytes([REQUEST])) # stop
|
|
||||||
self._expect(CONFIRM, f"{cmd} end")
|
|
||||||
return data
|
|
||||||
|
|
||||||
# Device-paced page write ('F'/'E'): device offers '?', host sends '!'+page,
|
|
||||||
# or anything else to stop.
|
|
||||||
def _write_pages(self, cmd, data):
|
|
||||||
if len(data) % PAGE:
|
|
||||||
data += b"\xff" * (PAGE - len(data) % PAGE)
|
|
||||||
self.s.write(cmd.encode())
|
|
||||||
for off in range(0, len(data), PAGE):
|
|
||||||
self._expect(REQUEST, f"{cmd} '?'")
|
|
||||||
self.s.write(bytes([CONFIRM]) + data[off:off + PAGE])
|
|
||||||
self._expect(REQUEST, f"{cmd} trailing '?'")
|
|
||||||
self.s.write(bytes([REQUEST])) # stop
|
|
||||||
self._expect(CONFIRM, f"{cmd} end")
|
|
||||||
|
|
||||||
def write_flash(self, data):
|
|
||||||
self._write_pages("F", data)
|
|
||||||
|
|
||||||
def read_flash(self, npages):
|
|
||||||
return self._read_pages("f", npages)
|
|
||||||
|
|
||||||
def write_eeprom(self, data):
|
|
||||||
self._write_pages("E", data)
|
|
||||||
|
|
||||||
def read_eeprom(self, npages):
|
|
||||||
return self._read_pages("e", npages)
|
|
||||||
|
|
||||||
def read_config(self):
|
|
||||||
self.s.write(b"c")
|
|
||||||
page = self._read(PAGE)
|
|
||||||
self._expect(CONFIRM, "c end")
|
|
||||||
return page
|
|
||||||
|
|
||||||
def write_config(self, data):
|
|
||||||
assert len(data) == PAGE
|
|
||||||
self.s.write(b"C")
|
|
||||||
self._expect(REQUEST, "C '?'")
|
|
||||||
self.s.write(bytes([CONFIRM]) + data)
|
|
||||||
echo = self._read(PAGE) # device echoes what it programmed
|
|
||||||
self._expect(CONFIRM, "C end")
|
|
||||||
return echo
|
|
||||||
|
|
||||||
# Activation when the config page carries a password: 3×'@' then the
|
|
||||||
# password bytes, then the info block + mainloop '!'.
|
|
||||||
def activate_password(self, password):
|
|
||||||
self.s.reset_input_buffer()
|
|
||||||
self.s.write(bytes([KNOCK, KNOCK, KNOCK]) + password)
|
|
||||||
reply = self._read(17)
|
|
||||||
if reply[16] != CONFIRM:
|
|
||||||
raise AssertionError(f"password activation not '!'-terminated: {reply.hex()}")
|
|
||||||
self.info = reply[:16]
|
|
||||||
return self.info
|
|
||||||
|
|
||||||
# A 0 byte where a password byte is expected requests emergency erase; the
|
|
||||||
# device asks for two confirmations, then wipes and returns to the mainloop.
|
|
||||||
def emergency_erase(self):
|
|
||||||
self.s.reset_input_buffer()
|
|
||||||
self.s.write(bytes([KNOCK, KNOCK, KNOCK, 0x00]))
|
|
||||||
self._expect(REQUEST, "emergency confirm 1")
|
|
||||||
self.s.write(bytes([CONFIRM]))
|
|
||||||
self._expect(REQUEST, "emergency confirm 2")
|
|
||||||
self.s.write(bytes([CONFIRM]))
|
|
||||||
self._expect(CONFIRM, "emergency mainloop ready")
|
|
||||||
|
|
||||||
|
|
||||||
def check(cond, msg):
|
|
||||||
if not cond:
|
|
||||||
raise AssertionError(msg)
|
|
||||||
print(f" ok: {msg}")
|
|
||||||
|
|
||||||
|
|
||||||
# A config page carrying a password "PW": appjump 0, timeout 0x40, password
|
|
||||||
# 0x50 0x57 terminated by 0xff.
|
|
||||||
PW_CONFIG = "0000405057ff"
|
|
||||||
PW_BYTES = bytes([0x50, 0x57])
|
|
||||||
|
|
||||||
|
|
||||||
def scenario_roundtrip(host):
|
|
||||||
"""Activation + info block + flash/EEPROM/config read-write round-trips, on
|
|
||||||
a device with a blank (erased) config page — the usual no-password case."""
|
|
||||||
info = host.activate()
|
|
||||||
check(info[0:3] == b"TSB", f"magic 'TSB' (got {info[0:3]!r})")
|
|
||||||
check(info[6:9] == bytes([0x1E, 0x95, 0x0F]), f"signature 1E 95 0F (got {info[6:9].hex()})")
|
|
||||||
check(info[14] == info[15], f"device-type bytes 14==15 (got {info[14]:#x},{info[15]:#x})")
|
|
||||||
check(host.pagesize == PAGE, f"page size {PAGE} (got {host.pagesize})")
|
|
||||||
check(host.eeprom_size == 1024, f"eeprom size 1024 (got {host.eeprom_size})")
|
|
||||||
print(f" info: {info.hex()} appflash={host.appflash} eeprom={host.eeprom_size}")
|
|
||||||
|
|
||||||
app = bytes(range(256)) # two pages of known data
|
|
||||||
host.write_flash(app)
|
|
||||||
check(host.read_flash(2) == app, "flash round-trip 2 pages")
|
|
||||||
|
|
||||||
edata = bytes((i * 7) & 0xFF for i in range(PAGE))
|
|
||||||
host.write_eeprom(edata)
|
|
||||||
check(host.read_eeprom(1) == edata, "eeprom round-trip 1 page")
|
|
||||||
|
|
||||||
cfg = bytes([0x00, 0x00, 0x40]) + b"\xff" * (PAGE - 3) # timeout 0x40, no password
|
|
||||||
check(host.write_config(cfg) == cfg, "config write echoes the programmed page")
|
|
||||||
check(host.read_config() == cfg, "config read-back matches")
|
|
||||||
|
|
||||||
|
|
||||||
def scenario_password(host):
|
|
||||||
"""A device whose config page carries a password activates only when the
|
|
||||||
host sends it after the knock."""
|
|
||||||
info = host.activate_password(PW_BYTES)
|
|
||||||
check(info[0:3] == b"TSB", f"password activation returns the info block (got {info[0:3]!r})")
|
|
||||||
|
|
||||||
|
|
||||||
def scenario_emergency(host):
|
|
||||||
"""Emergency erase (password 0-byte + two confirms) wipes flash, EEPROM and
|
|
||||||
the config page; the device stays alive in its boot section."""
|
|
||||||
host.emergency_erase()
|
|
||||||
check(host.read_config() == b"\xff" * PAGE, "config page wiped")
|
|
||||||
check(host.read_flash(1) == b"\xff" * PAGE, "application flash wiped")
|
|
||||||
check(host.read_eeprom(1) == b"\xff" * PAGE, "EEPROM wiped")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
binary, elf, boot_base = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
||||||
failures = []
|
|
||||||
|
|
||||||
# Each group runs on its own freshly-reset device (simavr reloads the ELF,
|
|
||||||
# so nothing persists between them); the password groups seed a config page.
|
|
||||||
groups = [
|
|
||||||
("round-trip", None, scenario_roundtrip),
|
|
||||||
("password activation", PW_CONFIG, scenario_password),
|
|
||||||
("emergency erase", PW_CONFIG, scenario_emergency),
|
|
||||||
]
|
|
||||||
for name, config, fn in groups:
|
|
||||||
print(f"--- {name} ---")
|
|
||||||
dev = Device(binary, elf, boot_base, config=config)
|
|
||||||
try:
|
|
||||||
fn(Host(dev.pty))
|
|
||||||
except AssertionError as e:
|
|
||||||
failures.append(f"{name}: {e}")
|
|
||||||
print(f" FAIL: {e}")
|
|
||||||
finally:
|
|
||||||
dev.stop()
|
|
||||||
|
|
||||||
if failures:
|
|
||||||
print(f"FAILED ({len(failures)})")
|
|
||||||
return 1
|
|
||||||
print("ALL PASS")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
||||||
4
tsb/clock.hpp
Normal file
4
tsb/clock.hpp
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define F_CPU 18'432'000
|
||||||
|
#include <util/delay.h>
|
||||||
1
tsb/flash
Submodule
1
tsb/flash
Submodule
Submodule tsb/flash added at 6edb2e5a21
1
tsb/io
Submodule
1
tsb/io
Submodule
Submodule tsb/io added at 80de36ee7e
102
tsb/main.cpp
Normal file
102
tsb/main.cpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#include "clock.hpp"
|
||||||
|
|
||||||
|
#include "uart/uart.hpp"
|
||||||
|
|
||||||
|
constexpr auto READ_FLASH_CMD = 'f';
|
||||||
|
constexpr auto WRITE_FLASH_CMD = 'F';
|
||||||
|
constexpr auto READ_EEPROM_CMD = 'e';
|
||||||
|
constexpr auto WRITE_EEPROM_CMD = 'E';
|
||||||
|
constexpr auto READ_USERDATA_CMD = 'c';
|
||||||
|
constexpr auto WRITE_USERDATA_CMD = 'C';
|
||||||
|
constexpr auto REQUEST_CMD = '?';
|
||||||
|
constexpr auto CONFIRM_CMD = '!';
|
||||||
|
constexpr auto AUTO_BAUDING_CMD = '@';
|
||||||
|
|
||||||
|
using uart_interface = uart::Hardware0<uart::Config<115200>, uart::Driven::BLOCKING>;
|
||||||
|
|
||||||
|
enum class State {
|
||||||
|
WAITING,
|
||||||
|
ACTIVE,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DeviceInfo {
|
||||||
|
char name[3] = {'T', 'S', 'B'};
|
||||||
|
uint8_t date[2] = {0x1a, 0x1f};
|
||||||
|
uint8_t status = 0xf0;
|
||||||
|
uint8_t signature[3] = {0x1e, 0x95, 0x0f};
|
||||||
|
uint8_t pageSize = 0x40;
|
||||||
|
uint16_t flashSize = 0x3ec0;
|
||||||
|
uint16_t eepromSize = 0x03ff;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UserData {
|
||||||
|
uint16_t jumpAddress = 0xAAAA;
|
||||||
|
uint8_t timeout = 0x21;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void sendDeviceInfo()
|
||||||
|
{
|
||||||
|
uart::Uart<uart_interface> serial;
|
||||||
|
|
||||||
|
constexpr DeviceInfo deviceInfo;
|
||||||
|
constexpr UserData userData;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < sizeof(deviceInfo); ++i) {
|
||||||
|
serial.txByte(*(reinterpret_cast<const uint8_t *>(&deviceInfo) + i));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < sizeof(userData); ++i) {
|
||||||
|
serial.txByte(*(reinterpret_cast<const uint8_t *>(&userData) + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t g_lastPage[128] = {};
|
||||||
|
|
||||||
|
static inline void sendUserData()
|
||||||
|
{
|
||||||
|
uart::Uart<uart_interface> serial;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < sizeof(g_lastPage); ++i) {
|
||||||
|
serial.txByte(*(reinterpret_cast<const uint8_t *>(&g_lastPage) + i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void sendConfirm()
|
||||||
|
{
|
||||||
|
uart::Uart<uart_interface> serial;
|
||||||
|
serial.txByte(CONFIRM_CMD);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
uart::Uart<uart_interface> serial;
|
||||||
|
serial.init();
|
||||||
|
|
||||||
|
State state = State::WAITING;
|
||||||
|
|
||||||
|
uint8_t receivedByte = 0;
|
||||||
|
uint8_t autoBaudingCounter = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (serial.rxByte(receivedByte)) {
|
||||||
|
if (state == State::WAITING) {
|
||||||
|
if (receivedByte == AUTO_BAUDING_CMD) {
|
||||||
|
++autoBaudingCounter;
|
||||||
|
}
|
||||||
|
if (autoBaudingCounter == 3) {
|
||||||
|
autoBaudingCounter = 0;
|
||||||
|
state = State::ACTIVE;
|
||||||
|
sendDeviceInfo();
|
||||||
|
}
|
||||||
|
} else if (state == State::ACTIVE) {
|
||||||
|
if (receivedByte == READ_USERDATA_CMD) {
|
||||||
|
sendUserData();
|
||||||
|
sendConfirm();
|
||||||
|
state = State::WAITING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
263
tsb/tsb.cppproj
Normal file
263
tsb/tsb.cppproj
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="14.0">
|
||||||
|
<PropertyGroup>
|
||||||
|
<SchemaVersion>2.0</SchemaVersion>
|
||||||
|
<ProjectVersion>7.0</ProjectVersion>
|
||||||
|
<ToolchainName>com.Atmel.AVRGCC8.CPP</ToolchainName>
|
||||||
|
<ProjectGuid>dce6c7e3-ee26-4d79-826b-08594b9ad897</ProjectGuid>
|
||||||
|
<avrdevice>ATmega328P</avrdevice>
|
||||||
|
<avrdeviceseries>none</avrdeviceseries>
|
||||||
|
<OutputType>Executable</OutputType>
|
||||||
|
<Language>CPP</Language>
|
||||||
|
<OutputFileName>$(MSBuildProjectName)</OutputFileName>
|
||||||
|
<OutputFileExtension>.elf</OutputFileExtension>
|
||||||
|
<OutputDirectory>$(MSBuildProjectDirectory)\$(Configuration)</OutputDirectory>
|
||||||
|
<AssemblyName>tsb</AssemblyName>
|
||||||
|
<Name>tsb</Name>
|
||||||
|
<RootNamespace>tsb</RootNamespace>
|
||||||
|
<ToolchainFlavour>avr-g++-9.1.0</ToolchainFlavour>
|
||||||
|
<KeepTimersRunning>true</KeepTimersRunning>
|
||||||
|
<OverrideVtor>false</OverrideVtor>
|
||||||
|
<CacheFlash>true</CacheFlash>
|
||||||
|
<ProgFlashFromRam>true</ProgFlashFromRam>
|
||||||
|
<RamSnippetAddress>0x20000000</RamSnippetAddress>
|
||||||
|
<UncachedRange />
|
||||||
|
<preserveEEPROM>true</preserveEEPROM>
|
||||||
|
<OverrideVtorValue>exception_table</OverrideVtorValue>
|
||||||
|
<BootSegment>2</BootSegment>
|
||||||
|
<ResetRule>0</ResetRule>
|
||||||
|
<eraseonlaunchrule>0</eraseonlaunchrule>
|
||||||
|
<EraseKey />
|
||||||
|
<avrtool>
|
||||||
|
</avrtool>
|
||||||
|
<avrtoolserialnumber>J41800099437</avrtoolserialnumber>
|
||||||
|
<avrdeviceexpectedsignature>0x1E9705</avrdeviceexpectedsignature>
|
||||||
|
<com_atmel_avrdbg_tool_stk500>
|
||||||
|
<ToolOptions>
|
||||||
|
<InterfaceProperties>
|
||||||
|
<IspClock>125000</IspClock>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>ISP</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType>com.atmel.avrdbg.tool.stk500</ToolType>
|
||||||
|
<ToolNumber>
|
||||||
|
</ToolNumber>
|
||||||
|
<ToolName>STK500</ToolName>
|
||||||
|
</com_atmel_avrdbg_tool_stk500>
|
||||||
|
<avrtoolinterface>ISP</avrtoolinterface>
|
||||||
|
<avrtoolinterfaceclock>125000</avrtoolinterfaceclock>
|
||||||
|
<AsfFrameworkConfig>
|
||||||
|
<framework-data xmlns="">
|
||||||
|
<options />
|
||||||
|
<configurations />
|
||||||
|
<files />
|
||||||
|
<documentation help="" />
|
||||||
|
<offline-documentation help="" />
|
||||||
|
<dependencies>
|
||||||
|
<content-extension eid="atmel.asf" uuidref="Atmel.ASF" version="3.47.0" />
|
||||||
|
</dependencies>
|
||||||
|
</framework-data>
|
||||||
|
</AsfFrameworkConfig>
|
||||||
|
<com_atmel_avrdbg_tool_atmelice>
|
||||||
|
<ToolOptions>
|
||||||
|
<InterfaceProperties>
|
||||||
|
<IspClock>125000</IspClock>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>ISP</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType>com.atmel.avrdbg.tool.atmelice</ToolType>
|
||||||
|
<ToolNumber>J41800099437</ToolNumber>
|
||||||
|
<ToolName>Atmel-ICE</ToolName>
|
||||||
|
</com_atmel_avrdbg_tool_atmelice>
|
||||||
|
<custom>
|
||||||
|
<ToolOptions>
|
||||||
|
<InterfaceProperties>
|
||||||
|
<IspClock>125000</IspClock>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>
|
||||||
|
</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType>custom</ToolType>
|
||||||
|
<ToolNumber>
|
||||||
|
</ToolNumber>
|
||||||
|
<ToolName>Custom Programming Tool</ToolName>
|
||||||
|
</custom>
|
||||||
|
<com_atmel_avrdbg_tool_simulator>
|
||||||
|
<ToolOptions xmlns="">
|
||||||
|
<InterfaceProperties>
|
||||||
|
</InterfaceProperties>
|
||||||
|
<InterfaceName>
|
||||||
|
</InterfaceName>
|
||||||
|
</ToolOptions>
|
||||||
|
<ToolType xmlns="">com.atmel.avrdbg.tool.simulator</ToolType>
|
||||||
|
<ToolNumber xmlns="">
|
||||||
|
</ToolNumber>
|
||||||
|
<ToolName xmlns="">Simulator</ToolName>
|
||||||
|
</com_atmel_avrdbg_tool_simulator>
|
||||||
|
<AAFDebugger>
|
||||||
|
<AAFDebugFiles>
|
||||||
|
</AAFDebugFiles>
|
||||||
|
</AAFDebugger>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<ToolchainSettings>
|
||||||
|
<AvrGccCpp>
|
||||||
|
<avrgcc.common.Device>-mmcu=atmega1284p</avrgcc.common.Device>
|
||||||
|
<avrgcc.common.optimization.RelaxBranches>True</avrgcc.common.optimization.RelaxBranches>
|
||||||
|
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
|
||||||
|
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
|
||||||
|
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
|
||||||
|
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
|
||||||
|
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>NDEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<avrgcc.compiler.optimization.level>Optimize for size (-Os)</avrgcc.compiler.optimization.level>
|
||||||
|
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings>
|
||||||
|
<avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic>
|
||||||
|
<avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>NDEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<avrgcccpp.compiler.optimization.level>Optimize for size (-Os)</avrgcccpp.compiler.optimization.level>
|
||||||
|
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic>
|
||||||
|
<avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++17</avrgcccpp.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<ListValues>
|
||||||
|
<Value>libm</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
</AvrGccCpp>
|
||||||
|
</ToolchainSettings>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<ToolchainSettings>
|
||||||
|
<AvrGccCpp>
|
||||||
|
<avrgcc.common.Device>-mmcu=atmega1284p</avrgcc.common.Device>
|
||||||
|
<avrgcc.common.optimization.RelaxBranches>True</avrgcc.common.optimization.RelaxBranches>
|
||||||
|
<avrgcc.common.outputfiles.hex>True</avrgcc.common.outputfiles.hex>
|
||||||
|
<avrgcc.common.outputfiles.lss>True</avrgcc.common.outputfiles.lss>
|
||||||
|
<avrgcc.common.outputfiles.eep>True</avrgcc.common.outputfiles.eep>
|
||||||
|
<avrgcc.common.outputfiles.srec>True</avrgcc.common.outputfiles.srec>
|
||||||
|
<avrgcc.common.outputfiles.usersignatures>False</avrgcc.common.outputfiles.usersignatures>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcc.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcc.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>DEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcc.compiler.directories.IncludePaths>
|
||||||
|
<avrgcc.compiler.optimization.level>Optimize (-O1)</avrgcc.compiler.optimization.level>
|
||||||
|
<avrgcc.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcc.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcc.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcc.compiler.optimization.DebugLevel>
|
||||||
|
<avrgcc.compiler.warnings.AllWarnings>True</avrgcc.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcc.compiler.warnings.ExtraWarnings>True</avrgcc.compiler.warnings.ExtraWarnings>
|
||||||
|
<avrgcc.compiler.warnings.Pedantic>True</avrgcc.compiler.warnings.Pedantic>
|
||||||
|
<avrgcc.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -std=c11</avrgcc.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultCharTypeUnsigned>
|
||||||
|
<avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>True</avrgcccpp.compiler.general.ChangeDefaultBitFieldUnsigned>
|
||||||
|
<avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<ListValues>
|
||||||
|
<Value>DEBUG</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.symbols.DefSymbols>
|
||||||
|
<avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.compiler.directories.IncludePaths>
|
||||||
|
<avrgcccpp.compiler.optimization.level>Optimize (-O1)</avrgcccpp.compiler.optimization.level>
|
||||||
|
<avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>True</avrgcccpp.compiler.optimization.AllocateBytesNeededForEnum>
|
||||||
|
<avrgcccpp.compiler.optimization.DebugLevel>Maximum (-g3)</avrgcccpp.compiler.optimization.DebugLevel>
|
||||||
|
<avrgcccpp.compiler.warnings.AllWarnings>True</avrgcccpp.compiler.warnings.AllWarnings>
|
||||||
|
<avrgcccpp.compiler.warnings.Pedantic>True</avrgcccpp.compiler.warnings.Pedantic>
|
||||||
|
<avrgcccpp.compiler.miscellaneous.OtherFlags>-fno-threadsafe-statics -Wextra -std=c++17</avrgcccpp.compiler.miscellaneous.OtherFlags>
|
||||||
|
<avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<ListValues>
|
||||||
|
<Value>libm</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.linker.libraries.Libraries>
|
||||||
|
<avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
<ListValues>
|
||||||
|
<Value>%24(PackRepoDir)\Atmel\ATmega_DFP\1.4.346\include</Value>
|
||||||
|
</ListValues>
|
||||||
|
</avrgcccpp.assembler.general.IncludePaths>
|
||||||
|
<avrgcccpp.assembler.debugging.DebugLevel>Default (-Wa,-g)</avrgcccpp.assembler.debugging.DebugLevel>
|
||||||
|
</AvrGccCpp>
|
||||||
|
</ToolchainSettings>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="clock.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="flash\flash.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="io\io.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="main.cpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="type\type.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\config.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\hardware.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\hardware0.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\hardware1.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\software.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="uart\uart.hpp">
|
||||||
|
<SubType>compile</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="flash" />
|
||||||
|
<Folder Include="io" />
|
||||||
|
<Folder Include="uart" />
|
||||||
|
<Folder Include="type" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(AVRSTUDIO_EXE_PATH)\\Vs\\Compiler.targets" />
|
||||||
|
</Project>
|
||||||
360
tsb/tsb_asm.cpp
360
tsb/tsb_asm.cpp
@@ -1,360 +0,0 @@
|
|||||||
// TinySafeBoot on libavr — tier 3: full feature parity in ≤512 B.
|
|
||||||
//
|
|
||||||
// The complete TinySafeBoot feature set — watchdog-reset bail, one-wire
|
|
||||||
// half-duplex UART, a config-page activation timeout, the password gate,
|
|
||||||
// emergency erase, and config/flash/EEPROM read-write — reimplemented for the
|
|
||||||
// 512-byte ATmega328P boot section. Matching the hand-written assembly oracle's
|
|
||||||
// size and features at once is only reachable at assembly density, so the loader
|
|
||||||
// body is one cohesive inline-asm routine. libavr still does the datasheet work:
|
|
||||||
// every geometry, baud and info-block constant below is computed by the library,
|
|
||||||
// never hand-entered, and the loader references them as assembler immediates.
|
|
||||||
//
|
|
||||||
// The wire protocol is strict request/response, which makes the one-wire
|
|
||||||
// turn-around safe: the device owns the line whenever it drives a byte and
|
|
||||||
// releases it (RX-only) whenever it waits for one.
|
|
||||||
|
|
||||||
#include <libavr/libavr.hpp>
|
|
||||||
|
|
||||||
#include <avr/boot.h> // __SPM_ENABLE and the SPM page-op bit names
|
|
||||||
#include <avr/io.h> // SFR addresses / bit numbers for the boot entry
|
|
||||||
|
|
||||||
using namespace avr::literals;
|
|
||||||
namespace spm = avr::spm;
|
|
||||||
|
|
||||||
namespace tsb {
|
|
||||||
|
|
||||||
// Boot geometry — the chip database's to know, not ours.
|
|
||||||
constexpr std::uint16_t page = spm::page_bytes; // 128
|
|
||||||
constexpr std::uint16_t boot_bytes = 512; // BOOTSZ=11
|
|
||||||
constexpr std::uint16_t app_end = spm::flash_bytes - boot_bytes - page; // config page base
|
|
||||||
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
|
||||||
|
|
||||||
// Fixed 115200 8N1; the library solves UBRR + U2X from clock and baud.
|
|
||||||
constexpr auto baud = avr::uart::detail::solve_baud(16_MHz, 115200_Bd);
|
|
||||||
static_assert(baud.u2x && baud.ubrr < 256, "asm bring-up writes UBRR0L only, with U2X0");
|
|
||||||
|
|
||||||
// Activation window: the config page's timeout byte, floored so a corrupt page
|
|
||||||
// can never lock the loader out (at least the clock rate in MHz → ~0.5 s here).
|
|
||||||
constexpr std::uint8_t act_min = 16;
|
|
||||||
// Post-activation communication timeout (~several seconds); the loader bails to
|
|
||||||
// the application if the host falls silent mid-session.
|
|
||||||
constexpr std::uint8_t comm_timeout = 200;
|
|
||||||
|
|
||||||
constexpr std::uint8_t confirm = '!';
|
|
||||||
constexpr std::uint8_t request = '?';
|
|
||||||
constexpr std::uint8_t knock = '@';
|
|
||||||
|
|
||||||
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 19;
|
|
||||||
|
|
||||||
// The 16-byte device-info block, LPM-read on activation. A plain progmem array:
|
|
||||||
// the loader streams it straight out with LPM, so a flash_table wrapper would
|
|
||||||
// add nothing here.
|
|
||||||
// clang-format off
|
|
||||||
[[gnu::progmem]] constexpr std::uint8_t info[16] = {
|
|
||||||
'T', 'S', 'B',
|
|
||||||
build_date & 0xFF, build_date >> 8,
|
|
||||||
0xF3, // status: native-UART fixed-baud lineage
|
|
||||||
0x1E, 0x95, 0x0F, // ATmega328P signature
|
|
||||||
page / 2, // page size in words
|
|
||||||
(app_end / 2) & 0xFF, (app_end / 2) >> 8,
|
|
||||||
eeprom_end & 0xFF, eeprom_end >> 8,
|
|
||||||
0xAA, 0xAA,
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
} // namespace tsb
|
|
||||||
|
|
||||||
// Reset lands here: BOOTRST vectors to the boot base, .vectors is laid first, and
|
|
||||||
// no crt runs. The whole loader is this one naked routine.
|
|
||||||
extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry()
|
|
||||||
{
|
|
||||||
asm volatile(
|
|
||||||
// --- bring-up ------------------------------------------------------
|
|
||||||
" ldi r16, lo8(%[ramend]) \n\t"
|
|
||||||
" out %[spl], r16 \n\t"
|
|
||||||
" ldi r16, hi8(%[ramend]) \n\t"
|
|
||||||
" out %[sph], r16 \n\t"
|
|
||||||
" in r16, %[mcusr] \n\t" // watchdog reset → hand straight back
|
|
||||||
" sbrc r16, 3 \n\t" // MCUSR bit 3 = WDRF
|
|
||||||
" rjmp 9f \n\t" // 9: = appjump
|
|
||||||
" ldi r16, %[ubrr] \n\t" // fixed baud, UBRR0L only
|
|
||||||
" sts %[ubrr0l], r16 \n\t"
|
|
||||||
" ldi r16, 0x02 \n\t" // 1<<U2X0
|
|
||||||
" sts %[ucsr0a], r16 \n\t"
|
|
||||||
" clr r22 \n\t" // direction flag bit0: 0 = receiving, 1 = driving the line
|
|
||||||
// --- activation: 3×'@' inside a config-page-timed window -----------
|
|
||||||
" ldi r30, lo8(%[appto]) \n\t" // Z = config page + 2
|
|
||||||
" ldi r31, hi8(%[appto]) \n\t"
|
|
||||||
" lpm r23, Z+ \n\t" // timeout byte; Z → password
|
|
||||||
" ori r23, %[actmin] \n\t" // lockout-proof floor
|
|
||||||
" clr r17 \n\t" // knock counter
|
|
||||||
"1: rcall tsb_rx \n\t"
|
|
||||||
" brcs 9f \n\t" // window elapsed → application
|
|
||||||
" cpi r16, %[knock] \n\t"
|
|
||||||
" brne 9f \n\t" // any non-'@' → application
|
|
||||||
" inc r17 \n\t"
|
|
||||||
" cpi r17, 3 \n\t"
|
|
||||||
" brne 1b \n\t"
|
|
||||||
// --- password / emergency erase (Z at config-page password) --------
|
|
||||||
" ldi r23, %[commto] \n\t" // widen the timeout for the session
|
|
||||||
"2: ser r19 \n\t" // r19=0xff → comparison enabled
|
|
||||||
"3: lpm r18, Z+ \n\t"
|
|
||||||
" and r18, r19 \n\t" // a prior mismatch (r19=0) blanks the rest
|
|
||||||
" cpi r18, 0xff \n\t"
|
|
||||||
" breq tsb_info \n\t" // 0xff terminator → password satisfied
|
|
||||||
" rcall tsb_rx \n\t"
|
|
||||||
" cpi r16, 0 \n\t"
|
|
||||||
" breq 5f \n\t" // a 0 byte requests emergency erase
|
|
||||||
" cp r16, r18 \n\t"
|
|
||||||
" breq 2b \n\t" // char matched → next, comparison re-armed
|
|
||||||
" clr r19 \n\t" // mismatch → drain forever, never erase
|
|
||||||
" rjmp 3b \n\t"
|
|
||||||
"5: cpi r19, 0 \n\t" // only offer erase if not already wrong
|
|
||||||
" breq 3b \n\t"
|
|
||||||
" rcall tsb_rcnf \n\t" // two confirmations guard the wipe
|
|
||||||
" brts 9f \n\t"
|
|
||||||
" rcall tsb_rcnf \n\t"
|
|
||||||
" brts 9f \n\t"
|
|
||||||
" rcall tsb_emerg \n\t"
|
|
||||||
" rjmp tsb_main \n\t"
|
|
||||||
// --- device info, then the command loop ----------------------------
|
|
||||||
"tsb_info: \n\t"
|
|
||||||
" ldi r30, lo8(%[info]) \n\t"
|
|
||||||
" ldi r31, hi8(%[info]) \n\t"
|
|
||||||
" ldi r20, 16 \n\t"
|
|
||||||
" rcall tsb_sendf \n\t"
|
|
||||||
"tsb_main: \n\t"
|
|
||||||
" clr r30 \n\t" // Z = 0 for the memory commands
|
|
||||||
" clr r31 \n\t"
|
|
||||||
" ldi r16, %[cfm] \n\t" // mainloop ready
|
|
||||||
" rcall tsb_tx \n\t"
|
|
||||||
" rcall tsb_rx \n\t"
|
|
||||||
" rcall tsb_disp \n\t"
|
|
||||||
" rjmp tsb_main \n\t"
|
|
||||||
"tsb_disp: \n\t"
|
|
||||||
" cpi r16, 'f' \n\t"
|
|
||||||
" breq tsb_rflash \n\t"
|
|
||||||
" cpi r16, 'F' \n\t"
|
|
||||||
" breq tsb_wflash \n\t"
|
|
||||||
" cpi r16, 'e' \n\t"
|
|
||||||
" breq tsb_reep \n\t"
|
|
||||||
" cpi r16, 'E' \n\t"
|
|
||||||
" breq tsb_weep \n\t"
|
|
||||||
" cpi r16, 'c' \n\t"
|
|
||||||
" breq tsb_rconf \n\t"
|
|
||||||
" cpi r16, 'C' \n\t"
|
|
||||||
" breq tsb_wconf \n\t"
|
|
||||||
"9: rcall tsb_spmw \n\t" // appjump: finish any SPM, hand over at 0
|
|
||||||
" jmp 0 \n\t"
|
|
||||||
// --- 'f' read application flash (host-paced) -----------------------
|
|
||||||
"tsb_rflash: \n\t"
|
|
||||||
"1: rcall tsb_rwait \n\t"
|
|
||||||
" brts 9f \n\t"
|
|
||||||
" ldi r20, %[page] \n\t"
|
|
||||||
" rcall tsb_sendf \n\t"
|
|
||||||
" cpi r30, lo8(%[appcfg]) \n\t"
|
|
||||||
" ldi r24, hi8(%[appcfg]) \n\t"
|
|
||||||
" cpc r31, r24 \n\t"
|
|
||||||
" brlo 1b \n\t"
|
|
||||||
"9: ret \n\t"
|
|
||||||
// --- 'e' read EEPROM (host-paced) ----------------------------------
|
|
||||||
"tsb_reep: \n\t"
|
|
||||||
"1: rcall tsb_rwait \n\t"
|
|
||||||
" brts 9f \n\t"
|
|
||||||
" ldi r20, %[page] \n\t"
|
|
||||||
"2: out %[earl], r30 \n\t"
|
|
||||||
" out %[earh], r31 \n\t"
|
|
||||||
" sbi %[eecr], 0 \n\t" // EERE
|
|
||||||
" in r16, %[eedr] \n\t"
|
|
||||||
" rcall tsb_tx \n\t"
|
|
||||||
" adiw r30, 1 \n\t"
|
|
||||||
" dec r20 \n\t"
|
|
||||||
" brne 2b \n\t"
|
|
||||||
" rjmp 1b \n\t"
|
|
||||||
"9: ret \n\t"
|
|
||||||
// --- 'F' write application flash -----------------------------------
|
|
||||||
"tsb_wflash: \n\t"
|
|
||||||
" rcall tsb_erapp \n\t" // erase the whole application first (leaves Z=0)
|
|
||||||
"1: rcall tsb_rcnf \n\t"
|
|
||||||
" brts 9f \n\t"
|
|
||||||
" rcall tsb_store \n\t"
|
|
||||||
" cpi r30, lo8(%[appcfg]) \n\t"
|
|
||||||
" ldi r24, hi8(%[appcfg]) \n\t"
|
|
||||||
" cpc r31, r24 \n\t"
|
|
||||||
" brlo 1b \n\t"
|
|
||||||
"9: ret \n\t"
|
|
||||||
// --- 'E' write EEPROM ----------------------------------------------
|
|
||||||
"tsb_weep: \n\t" // Z already 0 from the mainloop
|
|
||||||
"1: rcall tsb_rcnf \n\t"
|
|
||||||
" brts 9f \n\t"
|
|
||||||
" ldi r20, %[page] \n\t"
|
|
||||||
"2: rcall tsb_rx \n\t"
|
|
||||||
" rcall tsb_eewr \n\t"
|
|
||||||
" dec r20 \n\t"
|
|
||||||
" brne 2b \n\t"
|
|
||||||
" rjmp 1b \n\t"
|
|
||||||
"9: ret \n\t"
|
|
||||||
// --- 'c' read config page, 'C' write config page -------------------
|
|
||||||
"tsb_rconf: \n\t"
|
|
||||||
" ldi r30, lo8(%[appcfg]) \n\t"
|
|
||||||
" ldi r31, hi8(%[appcfg]) \n\t"
|
|
||||||
" ldi r20, %[page] \n\t"
|
|
||||||
" rjmp tsb_sendf \n\t"
|
|
||||||
"tsb_wconf: \n\t"
|
|
||||||
" rcall tsb_rcnf \n\t"
|
|
||||||
" brts 9f \n\t"
|
|
||||||
" ldi r30, lo8(%[appcfg]) \n\t"
|
|
||||||
" ldi r31, hi8(%[appcfg]) \n\t"
|
|
||||||
" rcall tsb_erpage \n\t" // erase the config page (Z unchanged)
|
|
||||||
" rcall tsb_store \n\t" // program it from the host
|
|
||||||
" rjmp tsb_rconf \n\t" // rewind Z and echo it back
|
|
||||||
"9: ret \n\t"
|
|
||||||
// --- stream one page host→flash at Z, program it (Z → next page) ----
|
|
||||||
"tsb_store: \n\t"
|
|
||||||
" ldi r20, %[words] \n\t"
|
|
||||||
"1: rcall tsb_rx \n\t"
|
|
||||||
" mov r0, r16 \n\t"
|
|
||||||
" rcall tsb_rx \n\t"
|
|
||||||
" mov r1, r16 \n\t"
|
|
||||||
" ldi r24, %[spm_fill] \n\t"
|
|
||||||
" out %[spmcsr], r24 \n\t"
|
|
||||||
" spm \n\t"
|
|
||||||
" clr r1 \n\t"
|
|
||||||
" adiw r30, 2 \n\t"
|
|
||||||
" dec r20 \n\t"
|
|
||||||
" brne 1b \n\t"
|
|
||||||
" subi r30, lo8(%[page]) \n\t" // back to the page base for PGWRT
|
|
||||||
" sbci r31, hi8(%[page]) \n\t"
|
|
||||||
" ldi r24, %[spm_wrt] \n\t"
|
|
||||||
" out %[spmcsr], r24 \n\t"
|
|
||||||
" spm \n\t"
|
|
||||||
" rcall tsb_spmw \n\t"
|
|
||||||
" subi r30, lo8(-%[page]) \n\t" // Z → next page base
|
|
||||||
" sbci r31, hi8(-%[page]) \n\t"
|
|
||||||
" ret \n\t"
|
|
||||||
// --- erase [0, config page) ----------------------------------------
|
|
||||||
"tsb_erapp: \n\t"
|
|
||||||
" clr r30 \n\t"
|
|
||||||
" clr r31 \n\t"
|
|
||||||
"1: rcall tsb_erpage \n\t"
|
|
||||||
" subi r30, lo8(-%[page]) \n\t"
|
|
||||||
" sbci r31, hi8(-%[page]) \n\t"
|
|
||||||
" cpi r30, lo8(%[appcfg]) \n\t"
|
|
||||||
" ldi r24, hi8(%[appcfg]) \n\t"
|
|
||||||
" cpc r31, r24 \n\t"
|
|
||||||
" brlo 1b \n\t"
|
|
||||||
" clr r30 \n\t" // hand callers Z=0
|
|
||||||
" clr r31 \n\t"
|
|
||||||
" ret \n\t"
|
|
||||||
// --- erase one flash page at Z (busy-wait + RWW re-enable) ----------
|
|
||||||
"tsb_erpage: \n\t"
|
|
||||||
" ldi r24, %[spm_ers] \n\t"
|
|
||||||
" out %[spmcsr], r24 \n\t"
|
|
||||||
" spm \n\t"
|
|
||||||
" rjmp tsb_spmw \n\t" // tail: wait + RWW re-enable, then ret
|
|
||||||
// --- emergency erase: application flash, EEPROM, config page -------
|
|
||||||
"tsb_emerg: \n\t"
|
|
||||||
" rcall tsb_erapp \n\t" // erases the application, leaves Z=0
|
|
||||||
" ser r16 \n\t"
|
|
||||||
"1: rcall tsb_eewr \n\t"
|
|
||||||
" cpi r30, lo8(%[eeend1]) \n\t"
|
|
||||||
" ldi r24, hi8(%[eeend1]) \n\t"
|
|
||||||
" cpc r31, r24 \n\t"
|
|
||||||
" brne 1b \n\t"
|
|
||||||
" ldi r30, lo8(%[appcfg]) \n\t"
|
|
||||||
" ldi r31, hi8(%[appcfg]) \n\t"
|
|
||||||
" rjmp tsb_erpage \n\t" // erase the config page (tail)
|
|
||||||
// --- one EEPROM byte r16 → [Z], Z++ --------------------------------
|
|
||||||
"tsb_eewr: \n\t"
|
|
||||||
"1: sbic %[eecr], 1 \n\t" // EEPE busy
|
|
||||||
" rjmp 1b \n\t"
|
|
||||||
" out %[earl], r30 \n\t"
|
|
||||||
" out %[earh], r31 \n\t"
|
|
||||||
" out %[eedr], r16 \n\t"
|
|
||||||
" sbi %[eecr], 2 \n\t" // EEMPE, then EEPE within 4 cycles
|
|
||||||
" sbi %[eecr], 1 \n\t" // EEPE
|
|
||||||
" adiw r30, 1 \n\t"
|
|
||||||
" ret \n\t"
|
|
||||||
// --- stream r20 flash bytes from Z to the host ---------------------
|
|
||||||
"tsb_sendf: \n\t"
|
|
||||||
"1: lpm r16, Z+ \n\t"
|
|
||||||
" rcall tsb_tx \n\t"
|
|
||||||
" dec r20 \n\t"
|
|
||||||
" brne 1b \n\t"
|
|
||||||
" ret \n\t"
|
|
||||||
// --- SPM busy-wait, then re-enable RWW read access -----------------
|
|
||||||
"tsb_spmw: \n\t"
|
|
||||||
"1: in r24, %[spmcsr] \n\t"
|
|
||||||
" sbrc r24, 0 \n\t"
|
|
||||||
" rjmp 1b \n\t"
|
|
||||||
" ldi r24, %[spm_rww] \n\t"
|
|
||||||
" out %[spmcsr], r24 \n\t"
|
|
||||||
" spm \n\t"
|
|
||||||
" ret \n\t"
|
|
||||||
// --- '?' then await '!' (T=1 ⇒ not confirmed) ----------------------
|
|
||||||
"tsb_rcnf: \n\t"
|
|
||||||
" ldi r16, %[req] \n\t"
|
|
||||||
" rcall tsb_tx \n\t"
|
|
||||||
"tsb_rwait: \n\t"
|
|
||||||
" rcall tsb_rx \n\t"
|
|
||||||
" clt \n\t"
|
|
||||||
" cpi r16, %[cfm] \n\t"
|
|
||||||
" breq 9f \n\t"
|
|
||||||
" set \n\t"
|
|
||||||
"9: ret \n\t"
|
|
||||||
// --- one-wire transmit r16 (drive the line + guard, wait TXC) ------
|
|
||||||
// One-wire: RX and TX share the line, so only one direction is enabled
|
|
||||||
// at a time. Waiting for TXC (whole frame out) before a caller can
|
|
||||||
// release the line is what makes the shared wiring safe.
|
|
||||||
"tsb_tx: \n\t"
|
|
||||||
" sbrc r22, 0 \n\t" // currently receiving? turn the line around
|
|
||||||
" rjmp 2f \n\t"
|
|
||||||
"1: sts %[udr0], r16 \n\t"
|
|
||||||
"3: lds r25, %[ucsr0a] \n\t" // wait for the whole frame out (TXC0)
|
|
||||||
" sbrs r25, 6 \n\t" // UCSR0A bit 6 = TXC0
|
|
||||||
" rjmp 3b \n\t"
|
|
||||||
" sts %[ucsr0a], r25 \n\t" // write 1 to clear TXC
|
|
||||||
" ret \n\t"
|
|
||||||
"2: ldi r25, 0x08 \n\t" // TXEN0 only: drive the line (receiver off)
|
|
||||||
" sts %[ucsr0b], r25 \n\t"
|
|
||||||
" clr r22 \n\t"
|
|
||||||
" ser r21 \n\t" // turn-around guard for a shorted receiver
|
|
||||||
"4: dec r21 \n\t"
|
|
||||||
" brne 4b \n\t"
|
|
||||||
" rjmp 1b \n\t"
|
|
||||||
// --- one-wire receive → r16, C set on timeout ----------------------
|
|
||||||
"tsb_rx: \n\t"
|
|
||||||
" sbrc r22, 0 \n\t" // already receiving? keep the line released
|
|
||||||
" rjmp 1f \n\t"
|
|
||||||
" ldi r25, 0x10 \n\t" // RXEN0 only: release the line and listen
|
|
||||||
" sts %[ucsr0b], r25 \n\t"
|
|
||||||
" ser r22 \n\t"
|
|
||||||
"1: mov r27, r23 \n\t" // outer countdown high = timeout byte
|
|
||||||
" clr r26 \n\t"
|
|
||||||
"2: ser r21 \n\t"
|
|
||||||
"3: lds r16, %[ucsr0a] \n\t"
|
|
||||||
" sbrc r16, 7 \n\t" // UCSR0A bit 7 = RXC0
|
|
||||||
" rjmp 4f \n\t"
|
|
||||||
" dec r21 \n\t"
|
|
||||||
" brne 3b \n\t"
|
|
||||||
" sbiw r26, 1 \n\t"
|
|
||||||
" brcc 2b \n\t"
|
|
||||||
" sec \n\t" // timed out
|
|
||||||
" ret \n\t"
|
|
||||||
"4: lds r16, %[udr0] \n\t"
|
|
||||||
" clc \n\t"
|
|
||||||
" ret \n\t"
|
|
||||||
:
|
|
||||||
: [ramend] "i"(RAMEND), [spl] "I"(_SFR_IO_ADDR(SPL)), [sph] "I"(_SFR_IO_ADDR(SPH)),
|
|
||||||
[mcusr] "I"(_SFR_IO_ADDR(MCUSR)), [ubrr] "n"(tsb::baud.ubrr), [ubrr0l] "n"(_SFR_MEM_ADDR(UBRR0L)),
|
|
||||||
[ucsr0a] "n"(_SFR_MEM_ADDR(UCSR0A)), [ucsr0b] "n"(_SFR_MEM_ADDR(UCSR0B)), [udr0] "n"(_SFR_MEM_ADDR(UDR0)),
|
|
||||||
[spmcsr] "I"(_SFR_IO_ADDR(SPMCSR)), [spm_fill] "n"(_BV(__SPM_ENABLE)),
|
|
||||||
[spm_ers] "n"(_BV(PGERS) | _BV(__SPM_ENABLE)), [spm_wrt] "n"(_BV(PGWRT) | _BV(__SPM_ENABLE)),
|
|
||||||
[spm_rww] "n"(_BV(RWWSRE) | _BV(__SPM_ENABLE)), [eecr] "I"(_SFR_IO_ADDR(EECR)),
|
|
||||||
[eedr] "I"(_SFR_IO_ADDR(EEDR)), [earl] "I"(_SFR_IO_ADDR(EEARL)), [earh] "I"(_SFR_IO_ADDR(EEARH)),
|
|
||||||
[appcfg] "i"(tsb::app_end), [appto] "i"(tsb::app_end + 2), [eeend1] "i"(tsb::eeprom_end + 1),
|
|
||||||
[info] "i"(&tsb::info[0]), [page] "n"(tsb::page), [words] "n"(tsb::page / 2), [actmin] "n"(tsb::act_min),
|
|
||||||
[commto] "n"(tsb::comm_timeout), [cfm] "n"(tsb::confirm), [req] "n"(tsb::request), [knock] "n"(tsb::knock)
|
|
||||||
: "r0", "r1", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r30", "r31",
|
|
||||||
"cc", "memory");
|
|
||||||
}
|
|
||||||
304
tsb/tsb_pure.cpp
304
tsb/tsb_pure.cpp
@@ -1,304 +0,0 @@
|
|||||||
// TinySafeBoot on libavr — tier 1: pure, idiomatic C++.
|
|
||||||
//
|
|
||||||
// A serial flash bootloader for the ATmega328P boot section, reimplementing the
|
|
||||||
// TinySafeBoot native-UART fixed-baud protocol on libavr with the full feature
|
|
||||||
// set of the hand-written oracle: a watchdog-reset bail, one-wire half-duplex,
|
|
||||||
// a config-page activation timeout, the password gate, emergency erase, and
|
|
||||||
// config/flash/EEPROM read-write. This variant is written for clarity —
|
|
||||||
// well-factored functions, no compiler-specific size hacks, no inline assembly.
|
|
||||||
// The one-wire wiring, the flash-resident info block and every SPM/EEPROM lock
|
|
||||||
// are libavr's to handle; the only attribute is the naked reset entry that
|
|
||||||
// stands in for the absent C runtime.
|
|
||||||
|
|
||||||
#include <libavr/libavr.hpp>
|
|
||||||
|
|
||||||
#include <avr/io.h> // SP / RAMEND for the crt-free boot entry
|
|
||||||
|
|
||||||
using namespace avr::literals;
|
|
||||||
namespace spm = avr::spm;
|
|
||||||
namespace ee = avr::eeprom;
|
|
||||||
|
|
||||||
using dev = avr::device<{.clock = 16_MHz}>;
|
|
||||||
// One-wire: RX and TX share the line, exactly as the native-UART TSB expects.
|
|
||||||
using serial_t = dev::uart0<{.baud = 115200_Bd, .max_baud_error = 3_pct, .half_duplex = true}>;
|
|
||||||
inline constexpr serial_t serial{};
|
|
||||||
|
|
||||||
namespace tsb {
|
|
||||||
|
|
||||||
// The loader is purely polled — it never enables interrupts — so every SPM and
|
|
||||||
// EEPROM lock folds to nothing under this posture.
|
|
||||||
constexpr auto off = avr::irq::guard_policy::unused;
|
|
||||||
|
|
||||||
// The handshake bytes, identical across every TSB host.
|
|
||||||
constexpr std::uint8_t confirm = '!';
|
|
||||||
constexpr std::uint8_t request = '?';
|
|
||||||
constexpr std::uint8_t knock = '@';
|
|
||||||
|
|
||||||
// Boot geometry for the 1 KB boot section (BOOTSZ=10). The page size and the
|
|
||||||
// flash/EEPROM extents are the chip database's to know. app_end is the config
|
|
||||||
// page (the LASTPAGE holding the app-jump vector, activation timeout and
|
|
||||||
// password), one page below the boot section.
|
|
||||||
constexpr std::uint16_t page = spm::page_bytes;
|
|
||||||
constexpr std::uint16_t boot_bytes = 1024;
|
|
||||||
constexpr std::uint16_t app_end = spm::flash_bytes - boot_bytes - page;
|
|
||||||
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
|
||||||
|
|
||||||
// Firmware version stamp: YY*512 + MM*32 + DD, the encoding the host decodes.
|
|
||||||
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 19;
|
|
||||||
|
|
||||||
// The 16-byte device-info block the host reads on activation. A flash_table
|
|
||||||
// keeps it in progmem with no .data image (there is no crt to copy one).
|
|
||||||
// clang-format off
|
|
||||||
inline constexpr std::array<std::uint8_t, 16> info_data = {
|
|
||||||
'T', 'S', 'B',
|
|
||||||
build_date & 0xFF, build_date >> 8,
|
|
||||||
0xF3, // status byte (native-UART fixed-baud lineage)
|
|
||||||
0x1E, 0x95, 0x0F, // ATmega328P signature
|
|
||||||
page / 2, // page size in words
|
|
||||||
(app_end / 2) & 0xFF, (app_end / 2) >> 8, // app-flash boundary, words
|
|
||||||
eeprom_end & 0xFF, eeprom_end >> 8,
|
|
||||||
0xAA, 0xAA, // ATmega processor-type marker (bytes 14 == 15)
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
using info = avr::flash_table<info_data>;
|
|
||||||
|
|
||||||
// One page staged in SRAM. Scratch that is always filled before it is read, so
|
|
||||||
// it lives in .noinit — no startup clear (there is no crt) and no .text bytes.
|
|
||||||
[[gnu::section(".noinit")]] std::uint8_t buffer[page];
|
|
||||||
|
|
||||||
// Blocking byte read/write over the one-wire line: read() releases the line to
|
|
||||||
// the receiver, write() takes it and holds it until the frame is out.
|
|
||||||
std::uint8_t rx()
|
|
||||||
{
|
|
||||||
return serial.read_blocking();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tx(std::uint8_t byte)
|
|
||||||
{
|
|
||||||
serial.write(byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|
||||||
{
|
|
||||||
return reinterpret_cast<const std::uint8_t *>(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream `count` bytes to the host, from flash (LPM) or from EEPROM.
|
|
||||||
void send_flash(std::uint16_t addr, std::uint16_t count)
|
|
||||||
{
|
|
||||||
while (count--)
|
|
||||||
tx(avr::flash_load(flash_ptr(addr++)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void send_eeprom(std::uint16_t addr, std::uint16_t count)
|
|
||||||
{
|
|
||||||
while (count--)
|
|
||||||
tx(ee::read(addr++));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take one page from the host into the SRAM buffer.
|
|
||||||
void get_page()
|
|
||||||
{
|
|
||||||
for (std::uint16_t i = 0; i < page; ++i)
|
|
||||||
buffer[i] = rx();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt the host with '?' and report whether it answered '!'.
|
|
||||||
bool request_confirm()
|
|
||||||
{
|
|
||||||
tx(request);
|
|
||||||
return rx() == confirm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Program the SRAM buffer into one already-erased flash page (low byte then
|
|
||||||
// high, as the SPM word buffer wants).
|
|
||||||
void write_flash_page(std::uint16_t addr)
|
|
||||||
{
|
|
||||||
spm::fill<off>(addr, std::span<const std::uint8_t>{buffer, page});
|
|
||||||
spm::write_page<off>(addr);
|
|
||||||
spm::wait();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the SRAM buffer into EEPROM byte by byte.
|
|
||||||
void write_eeprom_page(std::uint16_t addr)
|
|
||||||
{
|
|
||||||
for (std::uint16_t i = 0; i < page; ++i)
|
|
||||||
ee::write<off>(addr + i, buffer[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Erase the whole application, one page at a time (unwritten pages stay erased).
|
|
||||||
void erase_application()
|
|
||||||
{
|
|
||||||
for (std::uint16_t a = 0; a < app_end; a += page) {
|
|
||||||
spm::erase_page<off>(a);
|
|
||||||
spm::wait();
|
|
||||||
}
|
|
||||||
spm::rww_enable<off>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the application: reset vector at 0x0000. Any non-command byte, a wrong
|
|
||||||
// password, or an idle programmer port lands here.
|
|
||||||
[[noreturn]] void appjump()
|
|
||||||
{
|
|
||||||
spm::wait(); // make sure any pending SPM finished before handing over
|
|
||||||
reinterpret_cast<void (*)()>(0)();
|
|
||||||
__builtin_unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'f': stream the application flash back, one page per host '!'. Self-terminates
|
|
||||||
// at the application boundary; the host normally stops earlier with a non-'!'.
|
|
||||||
void read_flash()
|
|
||||||
{
|
|
||||||
for (std::uint16_t a = 0; a < app_end; a += page) {
|
|
||||||
if (rx() != confirm)
|
|
||||||
return;
|
|
||||||
send_flash(a, page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'e': stream EEPROM back, one page per host '!', until the host stops.
|
|
||||||
void read_eeprom()
|
|
||||||
{
|
|
||||||
for (std::uint16_t a = 0;; a += page) {
|
|
||||||
if (rx() != confirm)
|
|
||||||
return;
|
|
||||||
send_eeprom(a, page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'F': erase the whole application first, then take pages the host offers
|
|
||||||
// behind '?'.
|
|
||||||
void write_flash()
|
|
||||||
{
|
|
||||||
erase_application();
|
|
||||||
for (std::uint16_t a = 0; request_confirm(); a += page) {
|
|
||||||
get_page();
|
|
||||||
write_flash_page(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'E': take pages the host offers behind '?' into EEPROM.
|
|
||||||
void write_eeprom()
|
|
||||||
{
|
|
||||||
for (std::uint16_t a = 0; request_confirm(); a += page) {
|
|
||||||
get_page();
|
|
||||||
write_eeprom_page(a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'C': replace the config page, then echo it back for the host to verify.
|
|
||||||
void write_config()
|
|
||||||
{
|
|
||||||
if (!request_confirm())
|
|
||||||
return;
|
|
||||||
get_page();
|
|
||||||
spm::erase_page<off>(app_end);
|
|
||||||
spm::wait();
|
|
||||||
write_flash_page(app_end);
|
|
||||||
spm::rww_enable<off>();
|
|
||||||
send_flash(app_end, page);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emergency erase: wipe the application flash, the EEPROM and the config page.
|
|
||||||
// Reachable only from the password gate (a wrong byte can never reach it), so a
|
|
||||||
// blank config still leaves the loader recoverable.
|
|
||||||
void emergency_erase()
|
|
||||||
{
|
|
||||||
erase_application();
|
|
||||||
for (std::uint16_t a = 0; a <= eeprom_end; ++a)
|
|
||||||
ee::write<off>(a, 0xff);
|
|
||||||
spm::erase_page<off>(app_end);
|
|
||||||
spm::wait();
|
|
||||||
spm::rww_enable<off>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The password gate. The config page holds the password at app_end+3,
|
|
||||||
// terminated by 0xff (a blank page means no password). A byte of 0 requests
|
|
||||||
// emergency erase; a wrong byte hangs the loader, still draining the line, so a
|
|
||||||
// wrong password can never fall through to the erase.
|
|
||||||
enum class gate : std::uint8_t { pass, emergency };
|
|
||||||
|
|
||||||
gate password_gate()
|
|
||||||
{
|
|
||||||
for (const std::uint8_t *pw = flash_ptr(app_end + 3);; ++pw) {
|
|
||||||
std::uint8_t expected = avr::flash_load(pw);
|
|
||||||
if (expected == 0xff)
|
|
||||||
return gate::pass;
|
|
||||||
std::uint8_t got = rx();
|
|
||||||
if (got == 0)
|
|
||||||
return gate::emergency;
|
|
||||||
if (got != expected)
|
|
||||||
for (;;)
|
|
||||||
rx();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] void run()
|
|
||||||
{
|
|
||||||
// A watchdog reset hands straight back to the application, as the reference
|
|
||||||
// loader does, rather than re-entering the bootloader.
|
|
||||||
if (avr::hw::mcusr::wdrf.test())
|
|
||||||
appjump();
|
|
||||||
|
|
||||||
avr::init<serial_t>();
|
|
||||||
|
|
||||||
// Activation: the host knocks three '@' inside a window whose length is the
|
|
||||||
// config page's timeout byte (floored so a corrupt page can never lock the
|
|
||||||
// loader out). An idle port times out and boots the application.
|
|
||||||
std::uint32_t idle = static_cast<std::uint32_t>(avr::flash_load(flash_ptr(app_end + 2)) | 16) << 16;
|
|
||||||
std::uint8_t knocks = 0;
|
|
||||||
while (knocks < 3) {
|
|
||||||
if (auto byte = serial.read())
|
|
||||||
knocks = *byte == knock ? knocks + 1 : 0;
|
|
||||||
else if (--idle == 0)
|
|
||||||
appjump();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (password_gate()) {
|
|
||||||
case gate::pass:
|
|
||||||
send_flash(reinterpret_cast<std::uint16_t>(info::storage.data()), info::size());
|
|
||||||
break;
|
|
||||||
case gate::emergency:
|
|
||||||
if (!request_confirm() || !request_confirm())
|
|
||||||
appjump();
|
|
||||||
emergency_erase();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
tx(confirm); // Mainloop ready
|
|
||||||
switch (rx()) {
|
|
||||||
case 'f':
|
|
||||||
read_flash();
|
|
||||||
break;
|
|
||||||
case 'F':
|
|
||||||
write_flash();
|
|
||||||
break;
|
|
||||||
case 'e':
|
|
||||||
read_eeprom();
|
|
||||||
break;
|
|
||||||
case 'E':
|
|
||||||
write_eeprom();
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
send_flash(app_end, page);
|
|
||||||
break;
|
|
||||||
case 'C':
|
|
||||||
write_config();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
appjump(); // 'q' or any other byte runs the application
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace tsb
|
|
||||||
|
|
||||||
// Reset lands here: BOOTRST vectors to the boot section base and .vectors is
|
|
||||||
// laid first, so this is the first instruction executed. No crt ran, so set the
|
|
||||||
// stack pointer before anything is called.
|
|
||||||
extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry()
|
|
||||||
{
|
|
||||||
SP = RAMEND;
|
|
||||||
tsb::run();
|
|
||||||
}
|
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
// TinySafeBoot on libavr — tier 2: C++ with compiler trickery.
|
|
||||||
//
|
|
||||||
// Same protocol, libavr surface and full feature set as the pure variant
|
|
||||||
// (tsb_pure.cpp) — watchdog bail, one-wire, config-page timeout, password gate,
|
|
||||||
// emergency erase, config/flash/EEPROM read-write — but the readable
|
|
||||||
// one-handler-per-command shape is traded for size. Flash and EEPROM share a
|
|
||||||
// single code path selected by a *runtime* flag decoded from the command byte,
|
|
||||||
// so the compiler cannot constant-propagate it into two clones; attributes
|
|
||||||
// (noinline/noclone) pin that sharing down; the hot page address and byte
|
|
||||||
// counter live in call-saved global registers to erase the prologue push/pop
|
|
||||||
// that C++ function decomposition otherwise pays; and pages stream straight to
|
|
||||||
// SPM/EEPROM with no SRAM staging. No inline assembly.
|
|
||||||
|
|
||||||
#include <libavr/libavr.hpp>
|
|
||||||
|
|
||||||
#include <avr/io.h> // SP / RAMEND for the crt-free boot entry
|
|
||||||
|
|
||||||
using namespace avr::literals;
|
|
||||||
namespace spm = avr::spm;
|
|
||||||
namespace ee = avr::eeprom;
|
|
||||||
|
|
||||||
using dev = avr::device<{.clock = 16_MHz}>;
|
|
||||||
using serial_t = dev::uart0<{.baud = 115200_Bd, .max_baud_error = 3_pct, .half_duplex = true}>;
|
|
||||||
inline constexpr serial_t serial{};
|
|
||||||
|
|
||||||
namespace tsb {
|
|
||||||
|
|
||||||
constexpr auto off = avr::irq::guard_policy::unused;
|
|
||||||
|
|
||||||
constexpr std::uint8_t confirm = '!';
|
|
||||||
constexpr std::uint8_t request = '?';
|
|
||||||
constexpr std::uint8_t knock = '@';
|
|
||||||
|
|
||||||
constexpr std::uint16_t page = spm::page_bytes;
|
|
||||||
constexpr std::uint16_t boot_bytes = 1024;
|
|
||||||
constexpr std::uint16_t app_end = spm::flash_bytes - boot_bytes - page;
|
|
||||||
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
|
||||||
|
|
||||||
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 19;
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
[[gnu::progmem]] constexpr std::uint8_t info[16] = {
|
|
||||||
'T', 'S', 'B',
|
|
||||||
build_date & 0xFF, build_date >> 8,
|
|
||||||
0xF3,
|
|
||||||
0x1E, 0x95, 0x0F,
|
|
||||||
page / 2,
|
|
||||||
(app_end / 2) & 0xFF, (app_end / 2) >> 8,
|
|
||||||
eeprom_end & 0xFF, eeprom_end >> 8,
|
|
||||||
0xAA, 0xAA,
|
|
||||||
};
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
// The hot page walk lives in call-saved global registers, TSB-style: g_addr is
|
|
||||||
// the running flash/EEPROM byte address, g_cnt the byte countdown. Being global
|
|
||||||
// they are never spilled around the rx/tx/spm calls the way a local would be —
|
|
||||||
// r4-r7 are call-saved, so the library's UART and SPM helpers preserve them.
|
|
||||||
register std::uint16_t g_addr asm("r4");
|
|
||||||
register std::uint8_t g_cnt asm("r6");
|
|
||||||
|
|
||||||
std::uint8_t rx()
|
|
||||||
{
|
|
||||||
return serial.read_blocking();
|
|
||||||
}
|
|
||||||
|
|
||||||
void tx(std::uint8_t byte)
|
|
||||||
{
|
|
||||||
serial.write(byte);
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::uint8_t *flash_ptr(std::uint16_t addr)
|
|
||||||
{
|
|
||||||
return reinterpret_cast<const std::uint8_t *>(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream g_cnt bytes to the host from flash (LPM) or EEPROM, memory chosen at
|
|
||||||
// run time so the optimiser cannot split the loop into two clones.
|
|
||||||
[[gnu::noinline, gnu::noclone]] void send(bool flash)
|
|
||||||
{
|
|
||||||
do {
|
|
||||||
tx(flash ? avr::flash_load(flash_ptr(g_addr)) : ee::read(g_addr));
|
|
||||||
++g_addr;
|
|
||||||
} while (--g_cnt);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[gnu::noinline]] bool request_confirm()
|
|
||||||
{
|
|
||||||
tx(request);
|
|
||||||
return rx() == confirm;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream one page straight from the host into the already-erased flash page at
|
|
||||||
// g_addr (SPM word buffer, low byte then high) or into EEPROM — no SRAM staging,
|
|
||||||
// so receive and store are one loop. The memory is a run-time flag.
|
|
||||||
[[gnu::noinline, gnu::noclone]] void store_page(bool flash)
|
|
||||||
{
|
|
||||||
g_cnt = 0;
|
|
||||||
if (flash) {
|
|
||||||
do {
|
|
||||||
std::uint8_t lo = rx();
|
|
||||||
std::uint8_t hi = rx();
|
|
||||||
spm::fill<off>(g_addr + g_cnt, static_cast<std::uint16_t>(lo | (hi << 8)));
|
|
||||||
g_cnt += 2;
|
|
||||||
} while (g_cnt != page);
|
|
||||||
spm::write_page<off>(g_addr);
|
|
||||||
spm::wait();
|
|
||||||
} else {
|
|
||||||
do {
|
|
||||||
ee::write<off>(g_addr + g_cnt, rx());
|
|
||||||
} while (++g_cnt != page);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] void appjump()
|
|
||||||
{
|
|
||||||
spm::wait();
|
|
||||||
reinterpret_cast<void (*)()>(0)();
|
|
||||||
__builtin_unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Erase the whole application, one page at a time.
|
|
||||||
[[gnu::noinline]] void erase_application()
|
|
||||||
{
|
|
||||||
g_addr = 0;
|
|
||||||
do {
|
|
||||||
spm::erase_page<off>(g_addr);
|
|
||||||
spm::wait();
|
|
||||||
g_addr += page;
|
|
||||||
} while (g_addr < app_end);
|
|
||||||
spm::rww_enable<off>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'f'/'e': stream memory back one page per host '!'. send advances g_addr, so
|
|
||||||
// flash self-terminates at the application boundary; EEPROM runs until the host
|
|
||||||
// stops.
|
|
||||||
[[gnu::noinline]] void read_mem(bool flash)
|
|
||||||
{
|
|
||||||
g_addr = 0;
|
|
||||||
for (;;) {
|
|
||||||
if (rx() != confirm)
|
|
||||||
return;
|
|
||||||
g_cnt = page;
|
|
||||||
send(flash);
|
|
||||||
if (flash && g_addr >= app_end)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'F'/'E': flash erases the whole application first, then both take the pages
|
|
||||||
// the host offers behind '?'.
|
|
||||||
[[gnu::noinline]] void write_mem(bool flash)
|
|
||||||
{
|
|
||||||
if (flash)
|
|
||||||
erase_application();
|
|
||||||
g_addr = 0;
|
|
||||||
while (request_confirm()) {
|
|
||||||
store_page(flash);
|
|
||||||
g_addr += page;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'C': replace the config page, then echo it back for the host to verify.
|
|
||||||
void write_config()
|
|
||||||
{
|
|
||||||
if (!request_confirm())
|
|
||||||
return;
|
|
||||||
g_addr = app_end;
|
|
||||||
spm::erase_page<off>(g_addr);
|
|
||||||
spm::wait();
|
|
||||||
store_page(true);
|
|
||||||
spm::rww_enable<off>();
|
|
||||||
g_addr = app_end;
|
|
||||||
g_cnt = page;
|
|
||||||
send(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emergency erase: wipe the application flash, the EEPROM and the config page.
|
|
||||||
[[gnu::noinline]] void emergency_erase()
|
|
||||||
{
|
|
||||||
erase_application();
|
|
||||||
g_addr = 0;
|
|
||||||
do {
|
|
||||||
ee::write<off>(g_addr, 0xff);
|
|
||||||
} while (++g_addr <= eeprom_end);
|
|
||||||
spm::erase_page<off>(app_end);
|
|
||||||
spm::wait();
|
|
||||||
spm::rww_enable<off>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// The password gate. A byte of 0 requests emergency erase; a wrong byte hangs
|
|
||||||
// the loader (still draining the line), so it can never fall through to erase.
|
|
||||||
enum class gate : std::uint8_t { pass, emergency };
|
|
||||||
|
|
||||||
[[gnu::noinline]] gate password_gate()
|
|
||||||
{
|
|
||||||
for (const std::uint8_t *pw = flash_ptr(app_end + 3);; ++pw) {
|
|
||||||
std::uint8_t expected = avr::flash_load(pw);
|
|
||||||
if (expected == 0xff)
|
|
||||||
return gate::pass;
|
|
||||||
std::uint8_t got = rx();
|
|
||||||
if (got == 0)
|
|
||||||
return gate::emergency;
|
|
||||||
if (got != expected)
|
|
||||||
for (;;)
|
|
||||||
rx();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] void run()
|
|
||||||
{
|
|
||||||
if (avr::hw::mcusr::wdrf.test())
|
|
||||||
appjump();
|
|
||||||
|
|
||||||
avr::init<serial_t>();
|
|
||||||
|
|
||||||
std::uint32_t idle = static_cast<std::uint32_t>(avr::flash_load(flash_ptr(app_end + 2)) | 16) << 16;
|
|
||||||
std::uint8_t knocks = 0;
|
|
||||||
while (knocks < 3) {
|
|
||||||
if (auto byte = serial.read())
|
|
||||||
knocks = *byte == knock ? knocks + 1 : 0;
|
|
||||||
else if (--idle == 0)
|
|
||||||
appjump();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (password_gate()) {
|
|
||||||
case gate::pass:
|
|
||||||
g_addr = reinterpret_cast<std::uint16_t>(&info[0]);
|
|
||||||
g_cnt = sizeof(info);
|
|
||||||
send(true);
|
|
||||||
break;
|
|
||||||
case gate::emergency:
|
|
||||||
if (!request_confirm() || !request_confirm())
|
|
||||||
appjump();
|
|
||||||
emergency_erase();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (;;) {
|
|
||||||
tx(confirm); // Mainloop ready
|
|
||||||
// Decode the command arithmetically so flash/write stay run-time values:
|
|
||||||
// bit 5 is the case bit (upper = write), the folded-lower letter picks the
|
|
||||||
// memory. A single unified path serves f/F/e/E.
|
|
||||||
std::uint8_t cmd = rx();
|
|
||||||
std::uint8_t lower = cmd | 0x20;
|
|
||||||
bool write = (cmd & 0x20) == 0;
|
|
||||||
if (lower == 'f' || lower == 'e') {
|
|
||||||
bool flash = lower == 'f';
|
|
||||||
if (write)
|
|
||||||
write_mem(flash);
|
|
||||||
else
|
|
||||||
read_mem(flash);
|
|
||||||
} else if (lower == 'c') {
|
|
||||||
if (write) {
|
|
||||||
write_config();
|
|
||||||
} else {
|
|
||||||
g_addr = app_end;
|
|
||||||
g_cnt = page;
|
|
||||||
send(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
appjump();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace tsb
|
|
||||||
|
|
||||||
extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry()
|
|
||||||
{
|
|
||||||
SP = RAMEND;
|
|
||||||
tsb::run();
|
|
||||||
}
|
|
||||||
1
tsb/type
Submodule
1
tsb/type
Submodule
Submodule tsb/type added at ce31ef017f
1
tsb/uart
Submodule
1
tsb/uart
Submodule
Submodule tsb/uart added at 8f88cdccea
Reference in New Issue
Block a user