diff --git a/.gitignore b/.gitignore index d3be4dff..0813a9f8 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,7 @@ build*/ # Vagrant /.vagrant/ + +# Python +__pycache__ +.direnv diff --git a/common/can_bootloader.c b/common/can_bootloader.c index 0172f01a..9561a802 100644 --- a/common/can_bootloader.c +++ b/common/can_bootloader.c @@ -24,6 +24,10 @@ uint8_t m0_firmware_temp_buffer[M0_FIRMWARE_UPDATE_WRITE_CHUNK_SIZE]; #endif +#define ORESAT_F0_FLASH_START_ADDRESS 0x08000000 +#define ORESAT_F0_FIRMWARE_CRC_ADDRESS 0x0800A7F4 +#define ORESAT_F0_FIRMWARE_CODE_ADDRESS 0x0800A804 + /** * Used to fully initialize a can_bootloader_config_t structure. * diff --git a/common/include/oresat_f0.h b/common/include/oresat_f0.h index 58dd51f3..74cc046b 100644 --- a/common/include/oresat_f0.h +++ b/common/include/oresat_f0.h @@ -1,28 +1,35 @@ #ifndef COMMON_INCLUDE_ORESAT_F0_H_ #define COMMON_INCLUDE_ORESAT_F0_H_ -/* TODO: It'd be nice to derive these values from the linker script as the authoratative source */ -#define ORESAT_F0_FLASH_START_ADDRESS 0x08000000 - -#define ORESAT_F0_FIRMWARE_CRC_ADDRESS 0x0800A000 -#define ORESAT_F0_FIRMWARE_LENGTH_ADDRESS (ORESAT_F0_FIRMWARE_CRC_ADDRESS + 0x4) -#define ORESAT_F0_FIRMWARE_VERSION_ADDRESS (ORESAT_F0_FIRMWARE_CRC_ADDRESS + 0x8) -#define ORESAT_F0_FIRMWARE_CODE_ADDRESS (ORESAT_F0_FIRMWARE_CRC_ADDRESS + 0x400) -#define ORESAT_F0_FIRMWARE_CODE_END_ADDRESS 0x0803C000 - -#define ORESAT_F0_FIRMWARE_MAXIMUM_LENGTH (ORESAT_F0_FIRMWARE_CODE_END_ADDRESS - ORESAT_F0_FIRMWARE_CRC_ADDRESS) - - #ifdef __cplusplus extern "C" { #endif -extern int __flash0_base__; -extern size_t __flash0_size__; -extern int __flash0_end__; -extern int __flash1_base__; -extern size_t __flash1_size__; -extern int __flash1_end__; +// See STM32F091xC-bootloader.ld/STM32F4091xC-app.ld for definitions and +// rules_memory.ld for declarations. +extern uint8_t __flash0_base__[]; +extern uint8_t __flash0_end__[]; +extern uint8_t __flash1_base__[]; +extern uint8_t __flash1_end__[]; +extern uint8_t __flash2_base__[]; +extern uint8_t __flash2_end__[]; +extern uint8_t __ram0_base__[]; +extern uint8_t __ram0_end__[]; +extern uint8_t __ram1_base__[]; +extern uint8_t __ram1_end__[]; + +// The __*_size__ symbols are weird. The contents aren't relevent, it's the +// address, converted to a number, which is the represented region size. +extern const void __flash0_size__; +extern const void __flash1_size__; +extern const void __flash2_size__; +extern const void __ram0_size__; +extern const void __ram1_size__; +#define FLASH0_SIZE ((const uint32_t)(&__flash0_size__)) +#define FLASH1_SIZE ((const uint32_t)(&__flash1_size__)) +#define FLASH2_SIZE ((const uint32_t)(&__flash2_size__)) +#define RAM0_SIZE ((const uint32_t)(&__ram0_size__)) +#define RAM1_SIZE ((const uint32_t)(&__ram1_size__)) #ifdef __cplusplus } diff --git a/common/oresat_f0.c b/common/oresat_f0.c index 748ffc33..b4d69ab1 100644 --- a/common/oresat_f0.c +++ b/common/oresat_f0.c @@ -4,14 +4,19 @@ /** - * This function should be called on applications using the STM32F091xC-app.ld linker script and using the bootloader to run them. + * This function is called by applications using the STM32F091xC-app.ld + * linker script and using the bootloader to run them. It overwrites a weak + * symbol from ChibiOS's CRT0 and so will be called late in the init sequence. */ void __late_init(void) { - /* Relocate by software the vector table to the internal SRAM at 0x20000000 ***/ - /* Copy the vector table from the Flash (mapped at the base of the application - load address 0x08003000) to the base address of the SRAM at 0x20000000. */ + // Relocate by software the vector table to the internal SRAM at 0x20000000. + // The vector table comes from flash, mapped at the base of the application + // region, load address __flash2_base__ (0x0800A800). + // + // The SRAM then gets remapped to 0x00000000 to function as the real vector + // table - memcpy((void *)0x20000000, (void *)ORESAT_F0_FIRMWARE_CODE_ADDRESS, 0xC0); + memcpy(__ram0_base__, __flash2_base__, 0xC0); __DSB(); /* Remap SRAM at 0x00000000 */ SYSCFG->CFGR1 |= SYSCFG_CFGR1_MEM_MODE; diff --git a/ld/STM32F091xC-app.ld b/ld/STM32F091xC-app.ld index b7918b28..b5751e3f 100644 --- a/ld/STM32F091xC-app.ld +++ b/ld/STM32F091xC-app.ld @@ -1,34 +1,16 @@ -/* - ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -/* - * STM32F091xC memory setup. - */ +/* STM32F091xC memory setup. It has 256k flash (128 2k pages) and 32k SRAM */ MEMORY { flash0 (rx) : org = 0x08000000, len = 40k /* Bootloader */ - flash1 (rx) : org = 0x0800A400, len = 199k /* Application */ - flash2 (rx) : org = 0x00000000, len = 0 + flash1 (rx) : org = 0x0800A000, len = 2k /* Metadata */ + flash2 (rx) : org = 0x0800A800, len = 214k /* Application */ flash3 (rx) : org = 0x00000000, len = 0 flash4 (rx) : org = 0x00000000, len = 0 flash5 (rx) : org = 0x00000000, len = 0 flash6 (rx) : org = 0x00000000, len = 0 flash7 (rx) : org = 0x00000000, len = 0 - ram0 (wx) : org = 0x20000000, len = 32k - ram1 (wx) : org = 0x20000400, len = 31k + ram0 (wx) : org = 0x20000000, len = 32708 /* 32k - 4 (bootloader magic) */ + ram1 (wx) : org = 0x20000400, len = 31k /* 32k - 1k (fw vector table) */ ram2 (wx) : org = 0x00000000, len = 0 ram3 (wx) : org = 0x00000000, len = 0 ram4 (wx) : org = 0x00000000, len = 0 @@ -41,27 +23,27 @@ MEMORY and a load region (_LMA suffix).*/ /* Flash region to be used for exception vectors.*/ -REGION_ALIAS("VECTORS_FLASH", flash1); -REGION_ALIAS("VECTORS_FLASH_LMA", flash1); +REGION_ALIAS("VECTORS_FLASH", flash2); +REGION_ALIAS("VECTORS_FLASH_LMA", flash2); /* Flash region to be used for constructors and destructors.*/ -REGION_ALIAS("XTORS_FLASH", flash1); -REGION_ALIAS("XTORS_FLASH_LMA", flash1); +REGION_ALIAS("XTORS_FLASH", flash2); +REGION_ALIAS("XTORS_FLASH_LMA", flash2); /* Flash region to be used for code text.*/ -REGION_ALIAS("TEXT_FLASH", flash1); -REGION_ALIAS("TEXT_FLASH_LMA", flash1); +REGION_ALIAS("TEXT_FLASH", flash2); +REGION_ALIAS("TEXT_FLASH_LMA", flash2); /* Flash region to be used for read only data.*/ -REGION_ALIAS("RODATA_FLASH", flash1); -REGION_ALIAS("RODATA_FLASH_LMA", flash1); +REGION_ALIAS("RODATA_FLASH", flash2); +REGION_ALIAS("RODATA_FLASH_LMA", flash2); /* Flash region to be used for various.*/ -REGION_ALIAS("VARIOUS_FLASH", flash1); -REGION_ALIAS("VARIOUS_FLASH_LMA", flash1); +REGION_ALIAS("VARIOUS_FLASH", flash2); +REGION_ALIAS("VARIOUS_FLASH_LMA", flash2); /* Flash region to be used for RAM(n) initialization data.*/ -REGION_ALIAS("RAM_INIT_FLASH_LMA", flash1); +REGION_ALIAS("RAM_INIT_FLASH_LMA", flash2); /* RAM region to be used for Main stack. This stack accommodates the processing of all exceptions and interrupts.*/ @@ -73,7 +55,7 @@ REGION_ALIAS("PROCESS_STACK_RAM", ram1); /* RAM region to be used for data segment.*/ REGION_ALIAS("DATA_RAM", ram1); -REGION_ALIAS("DATA_RAM_LMA", flash1); +REGION_ALIAS("DATA_RAM_LMA", flash2); /* RAM region to be used for BSS segment.*/ REGION_ALIAS("BSS_RAM", ram1); diff --git a/ld/STM32F091xC-bootloader.ld b/ld/STM32F091xC-bootloader.ld index 748729ca..7100a3e2 100644 --- a/ld/STM32F091xC-bootloader.ld +++ b/ld/STM32F091xC-bootloader.ld @@ -1,34 +1,16 @@ -/* - ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -/* - * STM32F091xC memory setup. - */ +/* STM32F091xC memory setup. It has 256k flash (128 2k pages) and 32k SRAM */ MEMORY { flash0 (rx) : org = 0x08000000, len = 40k /* Bootloader */ - flash1 (rx) : org = 0x0800A400, len = 199k /* Application */ - flash2 (rx) : org = 0x00000000, len = 0 + flash1 (rx) : org = 0x0800A000, len = 2k /* Metadata */ + flash2 (rx) : org = 0x0800A800, len = 214k /* Application */ flash3 (rx) : org = 0x00000000, len = 0 flash4 (rx) : org = 0x00000000, len = 0 flash5 (rx) : org = 0x00000000, len = 0 flash6 (rx) : org = 0x00000000, len = 0 flash7 (rx) : org = 0x00000000, len = 0 - ram0 (wx) : org = 0x20000000, len = 32k - ram1 (wx) : org = 0x20000400, len = 31k + ram0 (wx) : org = 0x20000000, len = 32708 /* 32k - 4 for bootloader magic */ + ram1 (wx) : org = 0x20000400, len = 31k /* 32k - 1k for vector table */ ram2 (wx) : org = 0x00000000, len = 0 ram3 (wx) : org = 0x00000000, len = 0 ram4 (wx) : org = 0x00000000, len = 0 diff --git a/src/f0/app_bootloader/Makefile b/src/f0/app_bootloader/Makefile index cb735565..a21dbc74 100644 --- a/src/f0/app_bootloader/Makefile +++ b/src/f0/app_bootloader/Makefile @@ -5,7 +5,7 @@ # Compiler options here. ifeq ($(USE_OPT),) - USE_OPT = -Og -ggdb -fomit-frame-pointer -falign-functions=16 + USE_OPT = -Og -ggdb -falign-functions=16 endif # C specific options here (added to USE_OPT). @@ -106,8 +106,6 @@ DEPDIR := $(APP_ROOT)/.dep BOARDDIR = $(PROJ_ROOT)/boards/$(BOARD) # Project specific files. -#include $(PROJ_SRC)/oresat.mk -include $(PROJ_SRC)/bootloader.mk include $(PROJ_SRC)/util.mk # Licensing files. @@ -125,9 +123,6 @@ include $(CHIBIOS)/os/common/ports/ARMCMx/compilers/GCC/mk/port_v6m.mk # Auto-build files in ./source recursively. include $(CHIBIOS)/tools/mk/autobuild.mk # Other files (optional). -#include $(CHIBIOS)/test/lib/test.mk -#include $(CHIBIOS)/test/rt/rt_test.mk -#include $(CHIBIOS)/test/oslib/oslib_test.mk include $(CHIBIOS)/os/hal/lib/streams/streams.mk # Define linker script file here diff --git a/src/f0/app_bootloader/bootloader.py b/src/f0/app_bootloader/bootloader.py new file mode 100755 index 00000000..25f837f1 --- /dev/null +++ b/src/f0/app_bootloader/bootloader.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +'''This does testing of the CAN bootloader from python using a CANable attached to the host PC''' + +import argparse +import math +import struct +import time +from abc import ABC, abstractmethod +from copy import deepcopy +from dataclasses import dataclass +from itertools import islice + +import can + +# Implements some of ST-AN3154 + + +def batched(iterable, n): + '''batched('ABCDEFG', 3) → ABC DEF G''' + # FIXME: Replace with itertools.batched when 3.12 is standard. Copied from + # https://docs.python.org/3/library/itertools.html#itertools.batched + if n < 1: + raise ValueError('n must be at least one') + it = iter(iterable) + while batch := tuple(islice(it, n)): + yield batch + + +class ExpectMessage(can.Message): + '''Extends can.Message with a mask to compare only the unmasked bytes in .equals() + + Each bit in mask corresponds to a byte in data to compare or ignore + ''' + + def __init__(self, arbitration_id=0, dlc=None, data=None, is_rx=True, mask=0xFF): + assert 0x00 <= mask <= 0xFF + self.mask = [bool(int(b)) for b in reversed(f"{mask:08b}")] + super().__init__( + arbitration_id=arbitration_id, + dlc=dlc, + data=data, + is_rx=is_rx, + is_extended_id=False, + check=True, + ) + + def equals(self, other, *_): + if other is None: + return False + copy = deepcopy(other) + data = [] + for mask, theirs, ours in zip(self.mask, copy.data, self.data): + if mask: + data.append(theirs) + else: + data.append(ours) + copy.data = bytearray(data) + return super().equals(copy, timestamp_delta=None, check_channel=False) + + +class UnexpectedNack(ValueError): + pass + + +class Command(ABC): + ACK = 0x79 + NACK = 0x1F + offset = 0 + + def __init__(self, node_id): + self.canid = node_id + self.offset + + @abstractmethod + def run(self, reader): + pass + + def expect_ack(self, reader): + ack = ExpectMessage(arbitration_id=self.canid, data=[self.ACK]) + nak = ExpectMessage(arbitration_id=self.canid, data=[self.NACK]) + got = reader.get_message(timeout=10) + if not ack.equals(got): + if nak.equals(got): + raise UnexpectedNack("was expecting ACK") + raise ValueError(f"unexpected message in bagging area: {got}") + + def expect_nack(self, reader): + nak = ExpectMessage(arbitration_id=self.canid, data=[self.NACK]) + got = reader.get_message(timeout=10) + if not nak.equals(got): + raise ValueError(f"unexpected message in bagging area: {got}") + + +class Announce(Command): + '''The boootloader announces its presence on the CAN bus and then will wait 100ms for a response + + If the bootloader receives the response it will then enter and stay in command mode, where the + remaining commands become available. It will remain in that mode until a 'Go' is issued. + ''' + + offset = 0 + + def __init__(self, node_id): + super().__init__(node_id) + self.expect = ExpectMessage( + arbitration_id=self.canid, + data=[0x01, 0x02, 0x03, 0x04, 0, 0, 0, 0], + mask=0x0F, + ) + + def run(self, reader): + got = reader.get_message(timeout=100) + if not self.expect.equals(got): + raise ValueError(f"unexpected message in bagging area: {got}") + + yield can.Message( + arbitration_id=got.arbitration_id, + data=got.data[4:8] + b'\x01\x02\x03\x04', + is_extended_id=False, + ) + + +class Go(Command): + '''Commands bootloader to restart. + + The bootloader will revalidate the firmware and perform the standard bus announce message + ''' + + offset = 0x782 + + def __init__(self, node_id): + super().__init__(node_id) + self.command = can.Message(arbitration_id=self.canid, is_extended_id=False) + + def run(self, reader): + yield self.command + self.expect_ack(reader) + + +@dataclass +class GetResponse: + version: int + get: int + read: int + go: int + write: int + erase: int + + +class Get(Command): + '''Gets the version and list of command can IDs + + Can be used to verify which features the bootloader has avaliable. + ''' + + offset = 0x780 + + def __init__(self, node_id): + super().__init__(node_id) + self.msg = can.Message(arbitration_id=self.canid, is_extended_id=False) + + def run(self, reader): + yield self.msg + self.expect_ack(reader) + + count = reader.get_message(0.25) + frames = count.data[0] + 1 + + data = [] + for _ in range(frames): + r = reader.get_message(0.25) + data.append(int.from_bytes(r.data, byteorder='big', signed=False)) + + self.expect_ack(reader) + self.result = GetResponse(*data) + + +class ReadMemory(Command): + '''Reads up to a 256 byte region of memory. + + Valid regions are between addresses 0x0800000 and 0x0803FFFF (flash) and between addresses + 0x02000000 and 0x02007FFF for SRAM. System and Option regions are not currently implemented. + ''' + + offset = 0x781 + + def __init__(self, node_id, address, length): + super().__init__(node_id) + self.length = length + self.address = address + self.expected_frames = int(math.ceil((length) / 8.0)) + self.msg = can.Message( + arbitration_id=self.canid, + data=struct.pack('>IB', address, length - 1), + is_extended_id=False, + ) + + def run(self, reader): + yield self.msg + self.expect_ack(reader) + + data = bytearray() + for _ in range(self.expected_frames): + r = reader.get_message(0.25) + data.extend(r.data) + + self.expect_ack(reader) + self.result = data + + +class ErasePages(Command): + '''Erase a list of up to 7 2kb pages. + + Valid pages are 20 (metadata) and 21-127 (application firmware) + ''' + + offset = 0x02 + + def __init__(self, node_id, pages): + super().__init__(node_id) + self.count = len(pages) + assert 0 <= self.count <= 7 + if self.count == 0: + self.count = 256 # no pages given == erase all pages + data = [self.count - 1] + data.extend(pages) + self.msg = can.Message(arbitration_id=self.canid, data=data, is_extended_id=False) + + @classmethod + def all(cls, node_id): # FIXME: Not implemented in bootloader + return cls(node_id, []) + + def run(self, reader): + yield self.msg + # command ack + self.expect_ack(reader) + # effect ack + for _ in range(self.count): + self.expect_ack(reader) + + +class WriteMemory(Command): + '''Writes up to 256 bytes to flash. + + The page where writes take place must have been erased first. Valid addresses are between + 0x0800A000 and 0x0800A7FF (metadata) and between 0x0800A800 and 0x0803FFFF (application + firmware). It's recommended for alignment reasons to only start writes at even number addresses. + + To write a standard OreSat application image (e.g. foo.crc32.bin), start writing at 0x0800A7F4. + The image is prefixed with 12 bytes of metadata and then the actual application will start at + 0x0800A800. + ''' + + offset = 0x01 + + def __init__(self, node_id, base_address, byte_list): + super().__init__(node_id) + self.byte_list = byte_list + self.msg = can.Message( + arbitration_id=self.canid, + data=struct.pack(">IB", base_address, len(byte_list) - 1), + is_extended_id=False, + ) + + def run(self, reader): + yield self.msg + self.expect_ack(reader) + + for chunk in batched(self.byte_list, 8): + yield can.Message(arbitration_id=self.canid, data=chunk, is_extended_id=False) + + self.expect_ack(reader) + + +def firmware_update(node_id, address, firmware_image): + commands = [] + page_list = range(20, 127) + for chunk in batched(page_list, 7): + commands.append(ErasePages(node_id, chunk)) + + with open(firmware_image, 'rb') as f: + while chunk := f.read(256): + commands.append(WriteMemory(node_id, address, chunk)) + address += len(chunk) + return commands + + +def write_firmware(bus, node_id, filename): + reader = can.BufferedReader() + can.Notifier(bus, [can.Printer(), reader]) + + commands = [Announce(node_id), Get(node_id)] + commands.extend(firmware_update(node_id, 0x0800A7F4, filename)) + commands.append(Go(node_id)) + for command in commands: + print("~~~ Starting", command.__class__.__name__) + for message in command.run(reader): + print("TX msg:", message) + bus.send(message) + # FIXME: send() timeout option doesn't seem to work for socketcan. Get + # CanOperationError: Failed to transmit: No buffer space available [Error Code 105] + # if long writes are transmitted too quickly + time.sleep(0.01) + if isinstance(command, (Get, ReadMemory)): + print("Result:", command.result) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-b', '--bustype', default='socketcan', help="default: %(default)s") + parser.add_argument('-c', '--channel', default='can0', help="default: %(default)s") + parser.add_argument('-r', '--bitrate', default=1000000, help="default: %(default)s", type=int) + parser.add_argument('-n', '--node-id', default=0x7C, help="default: %(default)s", type=int) + parser.add_argument('-p', '--program', help="The image to write (usually foo.crc32.bin") + + args = parser.parse_args() + + filters = [ + {'can_id': Announce.offset + args.node_id, 'can_mask': 0x7FF, 'extende': False}, + {'can_id': Go.offset + args.node_id, 'can_mask': 0x7FF, 'extended': False}, + {'can_id': Get.offset + args.node_id, 'can_mask': 0x7FF, "extended": False}, + {'can_id': ReadMemory.offset + args.node_id, 'can_mask': 0x7FF, 'extended': False}, + {'can_id': WriteMemory.offset + args.node_id, 'can_mask': 0x7FF, 'extended': False}, + {'can_id': ErasePages.offset + args.node_id, 'can_mask': 0x7FF, 'extended': False}, + ] + + with can.Bus( + bustype=args.bustype, + channel=args.channel, + bitrate=args.bitrate, + can_filters=filters, + ) as b: + write_firmware(b, args.node_id, args.program) diff --git a/src/f0/app_bootloader/cfg/halconf.h b/src/f0/app_bootloader/cfg/halconf.h index 05e8e420..9ee6f822 100644 --- a/src/f0/app_bootloader/cfg/halconf.h +++ b/src/f0/app_bootloader/cfg/halconf.h @@ -526,6 +526,18 @@ #define WSPI_USE_MUTUAL_EXCLUSION TRUE #endif +/*===========================================================================*/ +/* Oresat custom CRC driver related settings. */ +/*===========================================================================*/ + +/** + * @brief Enables the CRC driver to use the hardware CRC peripheral. + * @note The driver falls back to a software implementation if FALSE + */ +#if !defined(USE_CRC_HW) || defined(__DOXYGEN__) +#define USE_CRC_HW TRUE +#endif + #endif /* HALCONF_H */ /** @} */ diff --git a/src/f0/app_bootloader/main.c b/src/f0/app_bootloader/main.c index 49da82bb..e76ad1af 100644 --- a/src/f0/app_bootloader/main.c +++ b/src/f0/app_bootloader/main.c @@ -1,20 +1,3 @@ -/* - ChibiOS - Copyright (C) 2006..2018 Giovanni Di Sirio - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ - -/* ChibiOS header files */ #include "ch.h" #include "hal.h" #include "chprintf.h" @@ -22,580 +5,648 @@ #include "can_hw.h" #include "crc.h" #include +// FIXME locking for flash commands #include "flash_f0.h" -#include "can_bootloader.h" #include "util.h" -#define BOOTLOADER_VERSION 0xAB +#define ARRAY_LEN(x) (sizeof(x)/sizeof(x[0])) +#define MIN(a,b) (((a)<(b))?(a):(b)) + +#define SERIAL ((BaseSequentialStream*)&SD2) +#ifdef DEBUG_PRINT +#define dbgprintf(...) chprintf(SERIAL, __VA_ARGS__) +#else +#define dbgprintf(...) +#endif + +#define BOOTLOADER_VERSION 0xAC #define CAN_DRIVER &CAND1 #define CAN_RECEIVE_TIMEOUT TIME_MS2I(150) #define CAN_RECEIVE_TIMEOUT_LONG TIME_MS2I(500) #define CAN_TRANSMIT_TIMEOUT TIME_MS2I(150) -#define TEMP_WRITE_BUFFER_SIZE 4096 -#define BOOTLOADER_VALIDATED_FIRMWARE_MAGIC_NUMBER 0x12345678 -//Memory address at the 31k mark, not allocated or cleared by CRT of hte OS due to linker script specifying RAM as 31k instead of the full 32k -volatile uint32_t *bootloader_magic_number_pointer = (uint32_t *) 0x20007C00; - -uint32_t *cpu_unique_id_low = (uint32_t *)UID_BASE; -uint8_t bootloader_temp_write_buffer[TEMP_WRITE_BUFFER_SIZE]; //Spec says maximum of 2048 bytes needed for buffer - -#define DEBUG_SD (BaseSequentialStream *) &SD2 - -void soft_reset_cortex_m0(void) { - canStop(CAN_DRIVER); - sdStop(&SD2); - - chSysDisable(); - - NVIC_SystemReset(); +#define CAN_ANNOUNCE_MAGIC_NUMBER 0x04030201 +#define UID_LOW (*((const uint32_t *) UID_BASE)) +#define BOOTLOADER_ACK 0x79 +#define BOOTLOADER_NACK 0x1F + +struct FirmwareMetadata { + uint32_t crc; + uint32_t length; + uint32_t version; +} __attribute__((packed)); + +struct FirmwareVectors { + uint32_t main_stack_addr; + __attribute__((noreturn)) void (*reset_handler)(void); +} __attribute__((packed)); + +// The firmware metadata block starts takes up flash page 20, between the +// bootloader and firmware blocks. The relevant metadata lives at the end of the block +#define METADATA (((struct FirmwareMetadata*)(__flash1_end__)) - 1) +#define FIRMWARE_MAX_LENGTH (FLASH1_SIZE + FLASH2_SIZE) +#define FIRMWARE_CRC_START ((void*)&(METADATA->length)) +#define VECTORS ((struct FirmwareVectors*)(__flash2_base__)) + +// The CAN bootloader protocol described in ST Application Note AN3154 +// designates CAN IDs for each of the bootloader commands, but there are two +// flaws with the layout for our purposes: +// - There's no way to distinguish messages from multiple bootloaders on the +// same CAN network - they all use the same IDs. (See also AN3154 p7 note) +// - The bootloader is not CANopen aware, so messages intrude into the CANopen +// address space. Particularly NMT messages which are top priority. +// The previous iteration attempted to solve the first flaw by extending the +// protocol by broadcasting the UID of the MCU as an initial step and then +// looking for the UID as part of the enter wait-for-command stage. The +// downside is that we have no way to associate the UID to CANopen Node ID +// a priori, and we also expect the hardware to change, meaning the UIDs would +// have to be re-associated every time. It also further impinged on the CANopen +// address space. +// +// This new scheme fits the bootloader commands entirely into the CANopen +// reserved address spaces, that are assigned no meaning in CANopen. It also +// remaps the commands based on Node ID, so that each card has a unique set of +// command CAN IDs. This scheme relies on Oresat's convention that Node IDs are +// spaced at least 3, but generally 4 apart. The only downside is that the CAN +// IDs need to be generated at runtime, they are not static. +// +// See CiA301 table 40 (also note typo in decimal representation for 780h) for +// reserved address ranges. See oresat-configs cards.csv for assigned node IDs +#define COMMAND_ANNOUNCE_OFFSET (0x000) +#define COMMAND_WRITE_MEMORY_OFFSET (0x001) +#define COMMAND_ERASE_OFFSET (0x002) +#define COMMAND_GET_OFFSET (0x780) +#define COMMAND_READ_MEMORY_OFFSET (0x781) +#define COMMAND_GO_OFFSET (0x782) + +#define COMMAND_ANNOUNCE(node_id) ((node_id) + COMMAND_ANNOUNCE_OFFSET) +#define COMMAND_WRITE_MEMORY(node_id) ((node_id) + COMMAND_WRITE_MEMORY_OFFSET) +#define COMMAND_ERASE(node_id) ((node_id) + COMMAND_ERASE_OFFSET) +#define COMMAND_GET(node_id) ((node_id) + COMMAND_GET_OFFSET) +#define COMMAND_READ_MEMORY(node_id) ((node_id) + COMMAND_READ_MEMORY_OFFSET) +#define COMMAND_GO(node_id) ((node_id) + COMMAND_GO_OFFSET) + +#define OPTION_DATA0 ((FLASH->OBR & FLASH_OBR_DATA0_Msk) >> FLASH_OBR_DATA0_Pos) + +static uint8_t node_id(void) { + const uint8_t id = OPTION_DATA0; + if(id == 0 || id == 0xFF || id % 4) { + return 0x7C; // prevents commands from intruding into CANopen ID space + } + return id; } -/** - * @return True if the key meta-data fields of the firmware image are reasonable and valid. False other wise - */ -bool validate_firmware_meta_data(const uint32_t length, const uint32_t expected_crc32) { - if (length == 0|| expected_crc32 == 0 || length == UINT32_MAX || expected_crc32 == UINT32_MAX) { - return false; +static const char* command_offset_to_str(const uint32_t v) { + switch (v) { + case COMMAND_GET_OFFSET: + return "COMMAND_GET"; + case COMMAND_READ_MEMORY_OFFSET: + return "COMMAND_READ_MEMORY"; + case COMMAND_GO_OFFSET: + return "COMMAND_GO"; + case COMMAND_WRITE_MEMORY_OFFSET: + return "COMMAND_WRITE_MEMORY"; + case COMMAND_ERASE_OFFSET: + return "COMMAND_ERASE"; } + return "Invalid command"; +} - if( length >= ORESAT_F0_FIRMWARE_MAXIMUM_LENGTH) { - return false; +// Duplicated because different TX and RX types +static void dbgprint_frame_tx(const CANTxFrame * const msg) { + dbgprintf("TX Frame - SID = 0x%03X DLC = %u RTR = %u IDE = %u [ ", + msg->SID, msg->DLC, msg->RTR, msg->IDE); + for(int i = 0; i < msg->DLC; i++) { + dbgprintf("%02X ", msg->data8[i]); } - - return true; + dbgprintf("] "); } -/** - * @param *base_address Memory address at which the firmware metadata is located - * @param length Number of bytes to CRC - * @param expected_crc32 Expected CRC value of memory - * - * @return True if the CRC of flash matche, false otherwise - */ -bool validate_firmware_image(void *base_address, const uint32_t length, const uint32_t expected_crc32) { - if( ! validate_firmware_meta_data(length, expected_crc32) ) { - return false; +static void dbgprint_frame_rx(const CANRxFrame * const msg) { + dbgprintf("RX Frame - SID = 0x%03X DLC = %u RTR = %u IDE = %u [ ", + msg->SID, msg->DLC, msg->RTR, msg->IDE); + for(int i = 0; i < msg->DLC; i++) { + dbgprintf("%02X ", msg->data8[i]); } + dbgprintf("] "); +} - const uint32_t crc_of_flash_data = crc32(base_address, length, 0); +static msg_t can_transmit(const CANTxFrame * const msg) { + dbgprint_frame_tx(msg); + const msg_t r = canTransmitTimeout(CAN_DRIVER, CAN_ANY_MAILBOX, msg, CAN_TRANSMIT_TIMEOUT); + if(r == MSG_OK) { + // FIXME: if we send messages too fast the can driver seems to silently drop them instead + // of returning an error??? Having these print statements here seems slow things down + // enough that it works. This is particularly noticeable in command_read_memory because + // it does a lot of sending + chprintf(SERIAL,"- SUCCESS\r\n"); + //dbgprintf("- SUCCESS\r\n"); + } else { + chprintf(SERIAL, " - FAIL\r\n"); + //dbgprintf(" - FAIL\r\n"); + } + return r; +} - if( crc_of_flash_data != expected_crc32 ) { - return false; +__attribute__((warn_unused_result)) static msg_t can_receive(CANRxFrame * const msg, const sysinterval_t timeout) { + const msg_t r = canReceiveTimeout(CAN_DRIVER, CAN_ANY_MAILBOX, msg, timeout); + switch(r) { + case MSG_OK: + dbgprint_frame_rx(msg); + dbgprintf("\r\n"); + break; + case MSG_TIMEOUT: + dbgprintf("."); + break; + default: + dbgprintf("Error receiving CAN frame: %s\r\n", msg_t_to_str(r)); } + return r; +} - return true; +static msg_t send_ack(const CANRxFrame * const cmd) { + dbgprintf("Transmitting CAN bootloader ACK\r\n"); + return can_transmit(&(CANTxFrame){ + .SID = cmd->SID, + .DLC = 1, + .data8 = { BOOTLOADER_ACK }, + }); } -void branch_to_firmware_image(void) { - chSysDisable(); +static msg_t send_nack(const CANRxFrame * const cmd) { + dbgprintf("Transmitting CAN bootloader NACK\r\n"); + return can_transmit(&(CANTxFrame){ + .SID = cmd->SID, + .DLC = 1, + .data8 = { BOOTLOADER_NACK }, + }); +} - __set_MSP(*(uint32_t*) ORESAT_F0_FIRMWARE_CODE_ADDRESS); - (*(void (**)()) (ORESAT_F0_FIRMWARE_CODE_ADDRESS + 4))(); +static msg_t announce_presence_on_bus(const uint16_t node_id) { + dbgprintf("\r\nBootloader announcing presence on bus...\r\n"); + return can_transmit(&(CANTxFrame){ + .SID = COMMAND_ANNOUNCE(node_id), + .DLC = 8, + .data32 = { 0x04030201, UID_LOW }, + }); } -/** - * @return true if the firmware image is valid, false otherwise - */ -bool check_firmware_crc_and_branch(const bool print_serial_output) { - const uint32_t firmware_expected_crc = *((uint32_t *) ORESAT_F0_FIRMWARE_CRC_ADDRESS); - const uint32_t firmware_expected_length = *((uint32_t *) ORESAT_F0_FIRMWARE_LENGTH_ADDRESS); - const uint32_t firmware_version = *((uint32_t *) ORESAT_F0_FIRMWARE_VERSION_ADDRESS); - const bool firmware_metadata_valid = validate_firmware_meta_data(firmware_expected_length, firmware_expected_crc); - - const bool is_firmware_valid = validate_firmware_image((void*) ORESAT_F0_FIRMWARE_LENGTH_ADDRESS, firmware_expected_length, firmware_expected_crc); - - if( print_serial_output ) { - chprintf(DEBUG_SD, "firmware_expected_crc = 0x%X\r\n", firmware_expected_crc); - chprintf(DEBUG_SD, "firmware_expected_length = %u\r\n", firmware_expected_length); - if( firmware_metadata_valid ) { - chprintf(DEBUG_SD, "firmware_actual CRC = 0x%X\r\n", crc32((void*) ORESAT_F0_FIRMWARE_LENGTH_ADDRESS, firmware_expected_length, 0)); - } - chprintf(DEBUG_SD, "firmware_version = %u\r\n", firmware_version); - chprintf(DEBUG_SD, "firmware_metadata_valid = %u\r\n", firmware_metadata_valid); - chprintf(DEBUG_SD, "is_firmware_valid = %u\r\n", is_firmware_valid); - } +__attribute__((noreturn)) static void soft_reset_cortex_m0(void) { + // FIXME: flush/wait for completion of CAN/Serial instead of sleep + chThdSleepMilliseconds(100); + sdStop(&SD2); + chSysDisable(); - return(is_firmware_valid); + NVIC_SystemReset(); } +static void command_get(const CANRxFrame * const cmd, const uint16_t id) { + send_ack(cmd); + + // We implement only a subset of AN3154, omitting all ReadOutProtect + // handling, write [un]protect, speed, and ID. + const uint16_t cmd_list[] = { + BOOTLOADER_VERSION, + COMMAND_GET(id), + COMMAND_READ_MEMORY(id), + COMMAND_GO(id), + COMMAND_WRITE_MEMORY(id), + COMMAND_ERASE(id), + }; -msg_t can_bootloader_transmit(CANTxFrame *msg) { - const msg_t r = canTransmit(CAN_DRIVER, CAN_ANY_MAILBOX, msg, CAN_TRANSMIT_TIMEOUT); - if( r == MSG_OK ) { - can_api_print_tx_frame(DEBUG_SD, msg, "", " - SUCCESS"); - } else { - can_api_print_tx_frame(DEBUG_SD, msg, "", " - FAIL"); + can_transmit(&(CANTxFrame) { + .SID = cmd->SID, + .DLC = 1, + .data8 = { ARRAY_LEN(cmd_list) - 1 }, + }); + + for(size_t i = 0; i < ARRAY_LEN(cmd_list); i++) { + can_transmit(&(CANTxFrame){ + .SID = cmd->SID, + .DLC = 2, + // We're little endian but data gets sent big endian + .data16 = { __builtin_bswap16(cmd_list[i]) }, + }); } - chThdSleepMilliseconds(20); - return(r); + send_ack(cmd); } -msg_t can_bootloader_receive(CANRxFrame *msg) { - msg_t r = canReceive(CAN_DRIVER, CAN_ANY_MAILBOX, msg, CAN_RECEIVE_TIMEOUT); - if( r == MSG_OK ) { - can_api_print_rx_frame(DEBUG_SD, msg, "", ""); - } - return(r); +static bool valid_read_region(const uint8_t * const start, const uint16_t length) { + const uint8_t * const end = start + length - 1; + if(start >= __flash0_base__ && end < __flash2_end__) + return true; + if(start >= __ram0_base__ && end < __ram1_end__) + return true; + // TODO: AN3154 specifies that the system and option memory regions are + // valid for reading, but they've been omitted here because of time + // constraints. + return false; } -msg_t can_bootloader_receive2(CANRxFrame *msg, sysinterval_t timeout) { - const msg_t r = canReceive(CAN_DRIVER, CAN_ANY_MAILBOX, msg, timeout); - if( r == MSG_OK ) { -#if 1 - can_api_print_rx_frame(DEBUG_SD, msg, "", ""); -#else - chprintf(DEBUG_SD, "CAN RX: SID = 0x%X DLC = %u [", msg->SID, msg->DLC); - for(int i = 0; i < msg->DLC && i < 8; i++ ) { - chprintf(DEBUG_SD, " 0x%X", msg->data8[i]); - } - chprintf(DEBUG_SD, " ]\r\n"); -#endif +static void command_read_memory(const CANRxFrame * const cmd) { + if(cmd->DLC != 5) { + send_nack(cmd); + return; } - return(r); -} + // Address is sent in big endian, we're little endian + uint8_t *read_address = (uint8_t *)__builtin_bswap32(cmd->data32[0]); + uint16_t bytes_to_read = cmd->data8[4] + 1; + dbgprintf("Address to read from: 0x%X\r\n", read_address); + dbgprintf("Number of bytes to read: %u\r\n", bytes_to_read); -void can_bootloader_init_frame(CANTxFrame *msg, const uint32_t sid, const uint32_t dlc) { - memset(msg, 0, sizeof(*msg)); - msg->SID = sid; - msg->IDE = CAN_IDE_STD; - msg->DLC = dlc; -} - -/** - * Sends a ACK or NACK to the host to indicate success or failure of a given CAN command - * - * @sid The command ID for which this ack/nack is with respect. - * @ack_flag ture if an ACK is to be sent, false if a NACK is to be sent - */ -bool can_bootloader_send_ack_nack(const uint32_t sid, const bool ack_flag) { - msg_t tx_r; - CANTxFrame tx_msg; - can_bootloader_init_frame(&tx_msg, sid, 1); - if( ack_flag ) { - tx_msg.data8[0] = STM32_BOOTLOADER_CAN_ACK; - } else { - tx_msg.data8[0] = STM32_BOOTLOADER_CAN_NACK; + if(bytes_to_read > 256) { + send_nack(cmd); + return; } - - if( (tx_r = can_bootloader_transmit(&tx_msg)) != MSG_OK ) { - return false; + if(!valid_read_region(read_address, bytes_to_read)) { + send_nack(cmd); + return; } - return true; + // TODO: AN3154 says nack if ReadOutProtection is enabled and I think this + // is something in the option bytes? But we don't use it and I'm too lazy to + // check right now. + send_ack(cmd); + + while(bytes_to_read > 0) { + CANTxFrame reply = { + .SID = cmd->SID, + .DLC = MIN(bytes_to_read, 8), + }; + memcpy(reply.data8, read_address, reply.DLC); + bytes_to_read -= reply.DLC; + read_address += reply.DLC; + + dbgprintf("Transmitting %u bytes in response to read request.\r\n", reply.DLC); + if(can_transmit(&reply)) { + break; + } + } + + send_ack(cmd); } -bool can_bootloader_send_ack(const uint32_t sid) { - chprintf(DEBUG_SD, "Transmitting CAN bootloader ACK\r\n"); - return(can_bootloader_send_ack_nack(sid, true)); +static void command_go(const CANRxFrame * const cmd) { + // TODO: AN3154 says nack if ReadOutProtection is enabled and I think this + // is something in the option bytes? But we don't use it and I'm too lazy to + // check right now. + // + // We also omit the the address argument, only allowing jumps to the linker + // specified start of firmware. This has the nice property of allowing GO + // to be unable to fail. + send_ack(cmd); } -bool can_bootloader_send_nack(const uint32_t sid) { - chprintf(DEBUG_SD, "Transmitting CAN bootloader NACK\r\n"); - return(can_bootloader_send_ack_nack(sid, false)); + +static bool valid_write_range(const uint8_t * const start, const uint32_t length) { + // TODO: While the general spec allows for writes to SRAM, option, and the + // whole flash range, we limit this to application flash for safety. SRAM + // and option might be worthwhile in the future? + const uint8_t * const end = start + length - 1; + return start >= __flash0_end__ && end < __flash2_end__; } -/** - * @return true if the page_number can be erased, false otherwise - */ -bool is_flash_page_eraseable(const uint8_t page_number) { - uint32_t *bootloader_end_flash = (uint32_t *) &__flash0_end__; - uint32_t last_bootloader_page_number = ((((uint32_t)bootloader_end_flash) - 0x08000000) / STM32F093_FLASH_PAGE_SIZE) - 1; - //chprintf(DEBUG_SD, "last_bootloader_page_number = %u\r\n", last_bootloader_page_number); +static void command_write_memory(const CANRxFrame * const cmd) { + if(cmd->DLC != 5) { + send_nack(cmd); + return; + } + // Address is sent in big endian, we're little endian + const void * const write_address = (void *)__builtin_bswap32(cmd->data32[0]); + const uint16_t bytes_to_write = cmd->data8[4] + 1; + + if(!valid_write_range(write_address, bytes_to_write)) { + dbgprintf("Address 0x%X outside of valid write range...\r\n", write_address); + send_nack(cmd); + return; + } - if( page_number > last_bootloader_page_number && page_number <= FLASH_PAGE_COUNT) { - return true; + send_ack(cmd); + + // Based on the protocol spec, the host will send a maximum of 256 bytes + uint8_t buf[256]; + uint32_t received_bytes = 0; + const uint16_t id = node_id(); + while(received_bytes < bytes_to_write) { + CANRxFrame frame; + if(can_receive(&frame, CAN_RECEIVE_TIMEOUT)) { + //Failed to receive an expected frame of data to be written + send_nack(cmd); + return; + } + // AN3154 recommends 0x04 but says any SID is valid. We choose the + // command SID to reduce unique SIDs. + if(frame.SID != COMMAND_WRITE_MEMORY(id)) { + // Wrong SID, protocol misalignment? + send_nack(&frame); + return; + } + if(frame.DLC + received_bytes > bytes_to_write) { + dbgprintf("Incorrect number of bytes: have %u out of %u, given %u\r\n", + received_bytes, bytes_to_write, frame.DLC); + send_nack(&frame); + return; + } + memcpy(buf + received_bytes, frame.data8, frame.DLC); + received_bytes += frame.DLC; } - return false; -} + dbgprintf("bytes_to_write = %u\r\n", bytes_to_write); + dbgprintf("received_bytes = %u\r\n", received_bytes); -/** - * @return true if the given flash address can be written to, false otherwise. - */ -bool is_flash_write_address_valid(const uint8_t *address) { - uint8_t *application_start_flash = (uint8_t *) &__flash1_base__; - uint8_t *application_end_flash = (uint8_t *) &__flash1_end__; + dbgprintf("Writing data to flash...\r\n"); - if( address >= application_start_flash && address < application_end_flash ) { - return true; + // FIXME: uintptr_t is almost certainly not the right type + int fw_r = flashWriteF091((uintptr_t) write_address, buf, bytes_to_write); + + if(fw_r == FLASH_RETURN_SUCCESS) { + dbgprintf("Successfully wrote data to flash...\r\n"); + send_ack(cmd); + } else { + dbgprintf("Failed to write data to flash...\r\n"); + send_nack(cmd); } +} - return false; +static bool is_page_valid(const uint8_t page) { + const uint32_t metadata_page = ((uint32_t)METADATA - 0x08000000) + / STM32F093_FLASH_PAGE_SIZE; + + return page >= metadata_page && page <= FLASH_PAGE_COUNT; } -bool is_flash_write_address_range_valid(const uint8_t *address, const uint32_t length) { - if( ! is_flash_write_address_valid(address) ) { - return false; +static void command_erase(const CANRxFrame * const cmd) { + if(cmd->DLC < 1) { + send_nack(cmd); + return; } - if( ! is_flash_write_address_valid(address + length)) { - return false; - } - return true; -} -/** - * Handles an incoming CAN frame with it's coresponding commands and actions. - */ -void can_bootloader_handle_frame(CANRxFrame *rx_msg) { - msg_t tx_r; - const uint32_t command_sid = rx_msg->SID; - chprintf(DEBUG_SD, "Handling frame SID 0x%X %s\r\n", command_sid, oresat_bootloader_can_command_t_to_str(command_sid)); - - CANTxFrame reply_msg; - memset(&reply_msg, 0, sizeof(reply_msg)); - - - switch ((oresat_bootloader_can_command_t) command_sid) { - case ORESAT_BOOTLOADER_CAN_COMMAND_GET: - { - can_bootloader_send_ack(command_sid); - - const uint8_t cmd_list[] = {BOOTLOADER_VERSION, - ORESAT_BOOTLOADER_CAN_COMMAND_GET, - ORESAT_BOOTLOADER_CAN_COMMAND_READ_MEMORY, - ORESAT_BOOTLOADER_CAN_COMMAND_GO, - ORESAT_BOOTLOADER_CAN_COMMAND_WRITE_MEMORY, - ORESAT_BOOTLOADER_CAN_COMMAND_ERASE }; - - can_bootloader_init_frame(&reply_msg, command_sid, 1); - reply_msg.data8[0] = sizeof(cmd_list) - 1; //number of tx messages sent below - if( (tx_r = can_bootloader_transmit(&reply_msg)) != MSG_OK ) { - chprintf(DEBUG_SD, "Failed to transmit response - 1.\r\n"); - } + const uint16_t page_count = cmd->data8[0] + 1; + dbgprintf("erase page_count = %u\r\n", page_count); - for(uint32_t i = 0; i < sizeof(cmd_list); i++ ) { - can_bootloader_init_frame(&reply_msg, command_sid, 1); - reply_msg.data8[0] = cmd_list[i]; - if( (tx_r = can_bootloader_transmit(&reply_msg)) != MSG_OK ) { - chprintf(DEBUG_SD, "Failed to transmit response - 2.\r\n"); - } - } + if(page_count <= 7 && cmd->DLC == page_count + 1) { + send_ack(cmd); + } else { + // Mass erase not implemented. Additionally the documentation is + // contradictory on how many pages can be erased in a single run so + // here we only accept a single can frame (the strictest interpretation) + // The other alternative is to accept up to 255/8 = 31 additional can + // frames which contain more page numbers. + send_nack(cmd); + return; + } - can_bootloader_send_ack(command_sid); + for(size_t i = 1; i <= page_count && i < 8; i++) { + const uint8_t page = cmd->data8[i]; + if(!is_page_valid(page) ) { + dbgprintf("Page %u cannot be erased.\r\n", page); + send_nack(cmd); + return; } - break; - case ORESAT_BOOTLOADER_CAN_COMMAND_READ_MEMORY: - { - const uint8_t *address_to_read_from = (uint8_t *) ((rx_msg->data8[0] << 24) | (rx_msg->data8[1] << 16) | (rx_msg->data8[2] << 8) | rx_msg->data8[3]); - const uint8_t number_of_bytes_to_read = rx_msg->data8[4] + 1; - - chprintf(DEBUG_SD, "Address to read from: 0x%X\r\n", address_to_read_from); - chprintf(DEBUG_SD, "Number of bytes to read: %u\r\n", number_of_bytes_to_read); - - can_bootloader_send_ack(command_sid); - - for(int i = 0; i < number_of_bytes_to_read;) { - can_bootloader_init_frame(&reply_msg, command_sid, 0); - for(int j = 0; j < 8 && i < number_of_bytes_to_read; j++, i++) { - reply_msg.data8[j] = address_to_read_from[i]; - reply_msg.DLC++; - } - - if( (tx_r = can_bootloader_transmit(&reply_msg)) != MSG_OK ) { - chprintf(DEBUG_SD, "Failed to transmit response data.\r\n"); - } else { - chprintf(DEBUG_SD, "Transmitted %u bytes in response to read request.\r\n", reply_msg.DLC); - } - } - can_bootloader_send_ack(command_sid); + dbgprintf("Erasing page number = %u\r\n", page); + if(flashPageEraseF091(page) == FLASH_RETURN_SUCCESS ) { + dbgprintf("Successfully erased page\r\n"); + send_ack(cmd); + } else { + dbgprintf("Failed to erase page\r\n"); + send_nack(cmd); + return; } - break; - case ORESAT_BOOTLOADER_CAN_COMMAND_GO: - { - can_bootloader_send_ack(command_sid); + } + dbgprintf("Done with erase command\r\n"); +} - chprintf(DEBUG_SD, "Reseting cortex M0...\r\n"); - chThdSleepMilliseconds(100); +static void handle_can_comms(bool * firmware_valid, const uint16_t id) { + systime_t last_announce = chVTGetSystemTime(); + announce_presence_on_bus(id); - soft_reset_cortex_m0(); - } - break; - case ORESAT_BOOTLOADER_CAN_COMMAND_WRITE_MEMORY: - { - const uint8_t *write_address = (uint8_t *) ((rx_msg->data8[0] << 24) | (rx_msg->data8[1] << 16) | (rx_msg->data8[2] << 8) | rx_msg->data8[3]); - const uint8_t number_of_bytes_to_write = rx_msg->data8[4]; - msg_t r = 0; - - if( is_flash_write_address_range_valid(write_address, number_of_bytes_to_write) ) { - can_bootloader_send_ack(command_sid); - - uint32_t temp_buffer_index = 0; - while( temp_buffer_index < number_of_bytes_to_write ) { - CANRxFrame write_rx_frame; - if( (r = can_bootloader_receive(&write_rx_frame)) != MSG_OK ) { - //Failed to receive an expected frame of data to be written - break; - } - if( write_rx_frame.SID != 0x04 ) {//Recommended ID based on AN3154 - //Wrong SID, protocol misalignment? - break; - } - - for(int frame_idx = 0; frame_idx < write_rx_frame.DLC && frame_idx < 8; frame_idx++ ) { - if( temp_buffer_index < TEMP_WRITE_BUFFER_SIZE ) { - //Based on the protocol spec, the host will send a maximum of 2048 bytes for writing (1 page worth) - bootloader_temp_write_buffer[temp_buffer_index] = write_rx_frame.data8[frame_idx]; - temp_buffer_index++; - } else { - break; - } - } - - can_bootloader_send_ack(command_sid); - } - - chprintf(DEBUG_SD, "number_of_bytes_to_write = %u\r\n", number_of_bytes_to_write); - chprintf(DEBUG_SD, "temp_buffer_index = %u\r\n", temp_buffer_index); - - - if( number_of_bytes_to_write > 0 && temp_buffer_index == number_of_bytes_to_write ) { - chprintf(DEBUG_SD, "Writing data to flash...\r\n"); - - int fw_r = flashWriteF091((uintptr_t) write_address, bootloader_temp_write_buffer, number_of_bytes_to_write); - - if( fw_r == FLASH_RETURN_SUCCESS ) { - chprintf(DEBUG_SD, "Successfully wrote data to flash...\r\n"); - can_bootloader_send_ack(command_sid); - } else { - chprintf(DEBUG_SD, "Failed to write data to flash...\r\n"); - can_bootloader_send_nack(command_sid); - } - } else { - chprintf(DEBUG_SD, "Incorrect number of bytes to write...\r\n"); - can_bootloader_send_nack(command_sid); - } - } else { - chprintf(DEBUG_SD, "Address 0x%X outside of valid write range...\r\n", write_address); - can_bootloader_send_nack(command_sid); + const uint32_t timeout_ms = 100; + const systime_t timeout = TIME_MS2I(timeout_ms) + chVTGetSystemTime(); + + while(true) { + const systime_t now = chVTGetSystemTime(); + if(*firmware_valid) { + if(now >= timeout) { + dbgprintf("No CAN frame after %u ms, resetting and running main firmware...\r\n", + timeout_ms); + return; } - } - break; - case ORESAT_BOOTLOADER_CAN_COMMAND_ERASE: - { - //const uint32_t number_of_pages_to_erase = rx_msg->DLC; - const uint32_t number_of_pages_to_erase = rx_msg->data8[0] + 1; - chprintf(DEBUG_SD, "number_of_pages_to_erase = %u\r\n", number_of_pages_to_erase); - - can_bootloader_send_ack(command_sid); - - for(uint32_t i = 1; i <= number_of_pages_to_erase && i < 8; i++ ) { - int error_count = 0; - - const uint32_t page_number_to_erase = rx_msg->data8[i]; - if( is_flash_page_eraseable(page_number_to_erase) ) { - chprintf(DEBUG_SD, "Erasing page number = %u\r\n", page_number_to_erase); - chThdSleepMilliseconds(10); - - int re = flashPageEraseF091(page_number_to_erase); - - if( re == FLASH_RETURN_SUCCESS ) { - chprintf(DEBUG_SD, "Successfully erased page\r\n"); - } else { - chprintf(DEBUG_SD, "Failed to erase page\r\n"); - error_count++; - } - } else { - chprintf(DEBUG_SD, "Page %u cannot be erased.\r\n", page_number_to_erase); - chThdSleepMilliseconds(10); - error_count++; - } - - chprintf(DEBUG_SD, "Done with erase command\r\n"); - - if( error_count > 0 ) { - can_bootloader_send_nack(command_sid); - } else { - can_bootloader_send_ack(command_sid); - } + } else { + // Re-announce presence on the bus every 5 seconds if the firmware + // image is specifically invalid + if((now - last_announce) > TIME_MS2I(5000)) { + last_announce = now; + announce_presence_on_bus(id); } } + + CANRxFrame msg = {}; + if(can_receive(&msg, CAN_RECEIVE_TIMEOUT_LONG)) { + continue; + } + + dbgprintf("0: %08X 1: %08X\r\n", msg.data32[0], msg.data32[1]); + dbgprintf("0: %08X 1: %08X\r\n", UID_LOW, CAN_ANNOUNCE_MAGIC_NUMBER); + if(msg.SID == COMMAND_ANNOUNCE(id) + && msg.DLC == 8 + && msg.data32[0] == UID_LOW + && msg.data32[1] == CAN_ANNOUNCE_MAGIC_NUMBER + ) { + // Host will TX a reply frame with the magic number and + // CPU unique ID values swapped to indicaet this node + // should stay in bootloader mode + dbgprintf("RXed frame to stay in bootloader mode...\r\n"); break; + } } -} -void can_bootloader_announce_presence_on_bus(void) { - CANTxFrame txmsg; - memset(&txmsg, 0, sizeof(txmsg)); - txmsg.SID = 0; - txmsg.DLC = 8; - txmsg.data32[0] = 0x04030201; - txmsg.data32[1] = *cpu_unique_id_low; + while(true) { + CANRxFrame msg = {}; + if(can_receive(&msg, CAN_RECEIVE_TIMEOUT_LONG)) { + continue; + } - chprintf(DEBUG_SD, "\r\nBootloader announcing presence on bus...\r\n"); - can_bootloader_transmit(&txmsg); + const uint16_t command_offset = msg.SID - id; + chprintf(SERIAL, "Handling frame SID 0x%X %s\r\n", + msg.SID, command_offset_to_str(command_offset)); + switch(command_offset) { + case COMMAND_GET_OFFSET: + command_get(&msg, id); + break; + case COMMAND_READ_MEMORY_OFFSET: + command_read_memory(&msg); + break; + case COMMAND_GO_OFFSET: + command_go(&msg); + // because GO can't fail, we can just return + return; + case COMMAND_WRITE_MEMORY_OFFSET: + *firmware_valid = false; + command_write_memory(&msg); + break; + case COMMAND_ERASE_OFFSET: + *firmware_valid = false; + command_erase(&msg); + break; + } + } } +#define filter4(index, c1, c2, c3, c4) ((CANFilter) { \ + .filter = index, /* Bank index */ \ + .mode = 1, /* List mode */ \ + .scale = 0, /* 16 bits mode */ \ + .assignment = 0, /* Must be set to zero - see docs */ \ + .register1 = (c1) << 21 | (c2) << 5, \ + .register2 = (c3) << 21 | (c4) << 5, \ +}) + +#define filter2(index, c1, c2) ((CANFilter) { \ + .filter = index, /* Bank index */ \ + .mode = 1, /* List mode */ \ + .scale = 1, /* 32 bits mode */ \ + .assignment = 0, /* Must be set to zero - see docs */ \ + .register1 = (c1) << 21, \ + .register2 = (c2) << 21, \ +}) + +#define filter1(index, c1) ((CANFilter) { \ + .filter = index, /* Bank index */ \ + .mode = 0, /* Mask mode */ \ + .scale = 1, /* 32 bits mode */ \ + .assignment = 0, /* Must be set to zero - see docs */ \ + .register1 = (c1) << 21, \ + .register2 = 0x7FF << 21, \ +}) + +static void can_bootloader_run(bool * firmware_valid) { + const CANConfig cancfg = { + // Tool for calculating STM32 bit timing: http://www.bittiming.can-wiki.info/ + .btr = CAN_BTR(1000), + .mcr = CAN_MCR_ABOM //Automatic Bus-Off Management + | CAN_MCR_AWUM //Automatic Wakeup Mode + | CAN_MCR_TXFP //Transmit FIFO Priority + | 0, //Automatic Retransmission (no -> CAN_MCR_NART) + }; + dbgprintf("Initializing CAN peripheral with MCR = 0x%X\r\n", cancfg.mcr); + dbgprintf("Initializing CAN peripheral with BTR = 0x%X\r\n", cancfg.btr); -/*0b100 - Data with EID or (0b110 - RemoteFrame with EID)*/ -//#define set_can_eid_data(x) ((x << 3)|0b100) - -/*0b110 - Mask enable for EID/SID and DATA/RTR*/ -//#define set_can_eid_mask(x) ((x << 3)|0b110) - - -#define set_can_sid_data(x) ((x << 21) | 0b000) -#define set_can_sid_mask(x) ((x << 21) | 0b000) - - -void can_bootloader_run(const bool is_firmware_a_valid) { - CANConfig cancfg; - memset(&cancfg, 0, sizeof(cancfg)); - // Tool for calculating STM32 bit timing: http://www.bittiming.can-wiki.info/ - cancfg.btr = CAN_BTR(1000); - cancfg.mcr = ( - /* MCR (Master Control Register) */ - CAN_MCR_ABOM | //Automatic Bus-Off Management - CAN_MCR_AWUM | //Automatic Wakeup Mode - CAN_MCR_TXFP | //Transmit FIFO Priority - 0 /*CAN_MCR_NART*/ ); // No Automatic Retransmission - - - chprintf(DEBUG_SD, "Initializing CAN peripheral with MCR = 0x%X\r\n", cancfg.mcr); - chprintf(DEBUG_SD, "Initializing CAN peripheral with BTR = 0x%X\r\n", cancfg.btr); - - - /* - *The same manner we can set up to 14 filters (STM32F103) - *For example to use 2 EID Data filter to 2 CAN RX FIFO: - * http://forum.chibios.org/viewtopic.php?t=4079 - * see hal_can_lld.h for definition of CANFilter struct. - */ - -#define NUM_CAN_FILTERS 8 + const uint16_t id = node_id(); + chprintf(SERIAL, "Node ID: %u\r\n", id); - CANFilter can_filter_12[NUM_CAN_FILTERS] = {\ - /*{, , , , , } */ \ - {0, 0, 1, 0, set_can_sid_data(ORESAT_BOOTLOADER_CAN_COMMAND_GET), set_can_sid_mask(0x7FF)},\ - {1, 0, 1, 0, set_can_sid_data(ORESAT_BOOTLOADER_CAN_COMMAND_READ_MEMORY), set_can_sid_mask(0x7FF)},\ - {2, 0, 1, 0, set_can_sid_data(ORESAT_BOOTLOADER_CAN_COMMAND_GO), set_can_sid_mask(0x7FF)},\ - {3, 0, 1, 0, set_can_sid_data(ORESAT_BOOTLOADER_CAN_COMMAND_WRITE_MEMORY), set_can_sid_mask(0x7FF)},\ - {4, 0, 1, 0, set_can_sid_data(ORESAT_BOOTLOADER_CAN_COMMAND_ERASE), set_can_sid_mask(0x7FF)},\ - {5, 0, 1, 0, set_can_sid_data(CAN_BOOTLOADER_WRITE_MEMORY_RESPONSE_SID), set_can_sid_mask(0x7FF)},\ - {6, 0, 1, 0, set_can_sid_data(STM32_BOOTLOADER_CAN_ACK), set_can_sid_mask(0x7FF)},\ - {7, 0, 1, 0, set_can_sid_data(STM32_BOOTLOADER_CAN_NACK), set_can_sid_mask(0x7FF)},\ + const CANFilter filters[] = { + filter4(0, + COMMAND_ANNOUNCE(id), + COMMAND_GET(id), + COMMAND_READ_MEMORY(id), + COMMAND_GO(id) + ), + filter2(1, COMMAND_WRITE_MEMORY(id), COMMAND_ERASE(id)), }; + canSTM32SetFilters(CAN_DRIVER, 0, ARRAY_LEN(filters), filters); -#if 1 - //Set 4 filters from total 14(0xE) - canSTM32SetFilters(CAN_DRIVER, 0, NUM_CAN_FILTERS, &can_filter_12[0]); -#else - /* Put CAN module in normal mode */ - canSTM32SetFilters(CAN_DRIVER, 0, 0, NULL); -#endif canStart(CAN_DRIVER, &cancfg); - chprintf(DEBUG_SD, "Done starting CAN peripheral\r\n"); - - systime_t last_announcement_time_ms = TIME_I2MS(chVTGetSystemTime()); - can_bootloader_announce_presence_on_bus(); - - CANRxFrame rxmsg; - const systime_t start_time = TIME_I2MS(chVTGetSystemTime()); - bool got_stay_bootloader_frame = false; + dbgprintf("Done starting CAN peripheral\r\n"); + handle_can_comms(firmware_valid, id); - const uint32_t bootloader_timeout_threshold_ms = 3000; - while ( true ) { - if( is_firmware_a_valid ) { - if( (! got_stay_bootloader_frame) && ((TIME_I2MS(chVTGetSystemTime())) - start_time) >= bootloader_timeout_threshold_ms ) { - chprintf(DEBUG_SD, "No CAN frame after %u ms, reseting and running main firmware...\r\n", bootloader_timeout_threshold_ms); - break; - } - } else { - if( ! got_stay_bootloader_frame ) { - //Re-announce presence on the bus every 5 seconds if the firmware image is specifically invalid - const systime_t now_time_ms = TIME_I2MS(chVTGetSystemTime()); - - if( now_time_ms < last_announcement_time_ms || (now_time_ms - last_announcement_time_ms) > 5000 ) { - last_announcement_time_ms = now_time_ms; - can_bootloader_announce_presence_on_bus(); - } - } - } + canStop(CAN_DRIVER); +} - const msg_t r = can_bootloader_receive2(&rxmsg, CAN_RECEIVE_TIMEOUT_LONG); - if( r == MSG_OK ) { - if( ! got_stay_bootloader_frame ) { - if( rxmsg.SID == BOOTLOADER_EXPECTED_FIRST_FRAME_ID && rxmsg.DLC == 8 ) { - if( rxmsg.data32[0] == *cpu_unique_id_low && rxmsg.data32[1] == CAN_ANNOUNCE_MAGIC_NUMBER ) { - //Host will TX a reply frame with the magic number and CPU unique ID values swapped to indicaet this node should stay in bootloader mode - chprintf(DEBUG_SD, "RXed frame to stay in bootloader mode...\r\n"); - got_stay_bootloader_frame = true; - } - } - } else { - can_bootloader_handle_frame(&rxmsg); - } - } else { - if( r == MSG_TIMEOUT ) { - chprintf(DEBUG_SD, "."); - } else { - chprintf(DEBUG_SD, "Error receiving CAN frame: %s\r\n", msg_t_to_str(r)); - } - } +static bool is_metadata_valid(void) { + if(METADATA->length == 0 || METADATA->length >= FIRMWARE_MAX_LENGTH) { + return false; } - - canStop(CAN_DRIVER); + return METADATA->crc != 0 && METADATA->crc != UINT32_MAX; } -int main(void) -{ - if( (*bootloader_magic_number_pointer) == BOOTLOADER_VALIDATED_FIRMWARE_MAGIC_NUMBER ) { - //Previous power cycle validated the firmware, just run the firmware without bootstraping the OS. - //We specifically want to branch to the firmware image with the CPU as close to out of reset mode as possible. - *bootloader_magic_number_pointer = 0; + +// The word at the very end of SRAM is not allocated or cleared by CRT of the +// bootloader OS due to linker script specifying RAM as 31k - 4 instead of +// the full 32k +#define VALIDATED_FIRMWARE (*((volatile uint32_t *) __ram0_end__)) +#define BOOTLOADER_VALIDATED_FIRMWARE_MAGIC_NUMBER 0x12345678 + +int main(void) { + if(VALIDATED_FIRMWARE == BOOTLOADER_VALIDATED_FIRMWARE_MAGIC_NUMBER) { + // Previous power cycle validated the firmware, just run the firmware + // without bootstraping the OS. We specifically want to branch to the + // firmware image with the CPU as close to out of reset mode as possible. + VALIDATED_FIRMWARE = 0; __DSB(); - if( check_firmware_crc_and_branch(false) ) { - branch_to_firmware_image(); - } + // The application firmware will eventually remap its vector table to + // SRAM in __late_init(). + __set_MSP(VECTORS->main_stack_addr); + VECTORS->reset_handler(); } - //Note: none of this actually gets run because the __reset_handler_hook branchs to either the A or B firmware images + // Note: none of this gets run when VALIDATED_FIRMWARE is set to the magic + // number because branch_to_firmware_image() branches to the firmware image + // and does not return. halInit(); chSysInit(); + palSetLine(LINE_LED); sdStart(&SD2, NULL); - chprintf(DEBUG_SD, "\r\n=======================================\r\n"); - chprintf(DEBUG_SD, "Bootloader started...\r\n"); - - chprintf(DEBUG_SD, "Bootloader __flash0_base__ = 0x%08X\r\n", (uint8_t *) &__flash0_base__); - chprintf(DEBUG_SD, "Bootloader __flash0_end__ = 0x%08X\r\n", (uint8_t *) &__flash0_end__); + chprintf(SERIAL, "\r\n=======================================\r\n"); + chprintf(SERIAL, "Bootloader started...\r\n"); + +#ifdef DEBUG_PRINT + dbgprintf("Bootloader __flash0_base__ = 0x%08X\r\n", __flash0_base__); + dbgprintf("Bootloader __flash0_end__ = 0x%08X\r\n", __flash0_end__); + dbgprintf("Bootloader __flash0_size__ = 0x%08X\r\n", FLASH0_SIZE); + dbgprintf("Bootloader metadata = 0x%08X\r\n", METADATA); + dbgprintf("Firmware __flash2_base__ = 0x%08X\r\n", __flash2_base__); + dbgprintf("Firmware __flash2_end__ = 0x%08X\r\n", __flash2_end__); + dbgprintf("Firmware __flash2_size__ = 0x%08X\r\n", FLASH2_SIZE); + dbgprintf("SRAM __ram0_base__ = 0x%08X\r\n", __ram0_base__); + dbgprintf("SRAM __ram0_end = 0x%08X\r\n", __ram0_end__); + dbgprintf("SRAM __ram1_base__ = 0x%08X\r\n", __ram1_base__); + dbgprintf("SRAM __ram1_end = 0x%08X\r\n", __ram1_end__); + dbgprintf("FLASH->OBR = 0x%08X\r\n", FLASH->OBR); + dbgprintf("Option bytes0 = 0x%08X\r\n", *((uint32_t *)0x1ffff800)); + dbgprintf("Option bytes1 = 0x%08X\r\n", *((uint32_t *)0x1ffff804)); extern stkalign_t __main_thread_stack_base__; extern stkalign_t __main_thread_stack_end__; - chprintf(DEBUG_SD, "Stack size: %u\r\n", ((uint8_t *) &__main_thread_stack_end__) - ((uint8_t *) &__main_thread_stack_base__)); + dbgprintf("Stack size: %u\r\n", ((uint8_t *) &__main_thread_stack_end__) + - ((uint8_t *) &__main_thread_stack_base__)); - chThdSleepMilliseconds(50); + dbgprintf("Checking firmware validity...\r\n"); +#endif -#if 0 - for(int i = 0; i < 140; i++ ) { - chprintf(DEBUG_SD, "is_page_eraseable(%u) = %u\r\n", i, is_flash_page_eraseable(i)); + const bool metadata_valid = is_metadata_valid(); + uint32_t crc = -1; + if(metadata_valid) { + // FIXME: verify that this is using the HW crc implementation + crc = crc32(FIRMWARE_CRC_START, METADATA->length, 0); } -#endif + bool firmware_valid = metadata_valid && crc == METADATA->crc; - chprintf(DEBUG_SD, "Checking firmware validity...\r\n"); chThdSleepMilliseconds(50); + dbgprintf("firmware expected crc = 0x%X\r\n", METADATA->crc); + dbgprintf("firmware expected length = %u\r\n", METADATA->length); + dbgprintf("firmware version = %u\r\n", METADATA->version); + chprintf(SERIAL, "firmware metadata_valid = %u\r\n", metadata_valid); + chprintf(SERIAL, "firmware actual CRC = 0x%X\r\n", crc); + chprintf(SERIAL, "Current firmware_valid = %u\r\n", firmware_valid); - bool is_firmware_a_valid = check_firmware_crc_and_branch(true); - //is_firmware_a_valid = false; - chprintf(DEBUG_SD, "Current is_firmware_a_valid = %u\r\n", is_firmware_a_valid);chThdSleepMilliseconds(20); - can_bootloader_run(is_firmware_a_valid); + // bootloader commands could invalidate the firmware + can_bootloader_run(&firmware_valid); - if( is_firmware_a_valid ) { - *bootloader_magic_number_pointer = BOOTLOADER_VALIDATED_FIRMWARE_MAGIC_NUMBER; + if(firmware_valid) { + VALIDATED_FIRMWARE = BOOTLOADER_VALIDATED_FIRMWARE_MAGIC_NUMBER; } else { - *bootloader_magic_number_pointer = 0; + VALIDATED_FIRMWARE = 0; } - chprintf(DEBUG_SD, "set *bootloader_magic_number_pointer = 0x%X\r\n", *bootloader_magic_number_pointer); - chprintf(DEBUG_SD, "reseting MCU\r\n"); - chThdSleepMilliseconds(50); + dbgprintf("set VALIDATED_FIRMWARE = 0x%X\r\n", VALIDATED_FIRMWARE); + dbgprintf("reseting MCU\r\n"); soft_reset_cortex_m0(); return 0; } - diff --git a/src/f0/app_bootloader/requirements.txt b/src/f0/app_bootloader/requirements.txt new file mode 100644 index 00000000..318274bf --- /dev/null +++ b/src/f0/app_bootloader/requirements.txt @@ -0,0 +1 @@ +python-can[serial] diff --git a/src/f0/app_bootloader/test_bootloader.py b/src/f0/app_bootloader/test_bootloader.py new file mode 100755 index 00000000..841c4cb2 --- /dev/null +++ b/src/f0/app_bootloader/test_bootloader.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +'''Tests primarly the firmware bootloader implementation, testing of bootloader.py is accessory''' +import argparse +import sys +import time +import unittest + +import can + +from bootloader import (Announce, ErasePages, Get, GetResponse, Go, ReadMemory, + UnexpectedNack, WriteMemory) + +if __name__ == '__main__': + # NOTE: this co-opts -b, -c, and -h from unittest. Don't need them now but deconflict in + # the future? + parser = argparse.ArgumentParser() + parser.add_argument('-b', '--bustype', default='socketcan', help="default: %(default)s") + parser.add_argument('-c', '--channel', default='can0', help="default: %(default)s") + parser.add_argument('-r', '--bitrate', default=1000000, help="default: %(default)s", type=int) + parser.add_argument('-n', '--node-id', default=0x7C, help="default: %(default)s", type=int) + + args, remaining = parser.parse_known_args() + + bus = can.Bus(bustype=args.bustype, channel=args.channel, bitrate=args.bitrate) + reader = can.BufferedReader() + notifier = can.Notifier(bus, [reader]) + + +class TestCommand(unittest.TestCase): + def setUp(self): + self.do_cmds([Announce(args.node_id)]) + + def tearDown(self): + self.do_cmds([Go(args.node_id)]) + + def do_cmds(self, commands): + for command in commands: + for message in command.run(reader): + bus.send(message) + # FIXME: send() timeout option doesn't seem to work for socketcan. Get + # CanOperationError: Failed to transmit: No buffer space available [Error Code 105] + # if long writes are transmitted too quickly + time.sleep(0.01) + + +class TestGet(TestCommand): + def test_get(self): + get = Get(args.node_id) + self.do_cmds([get]) + self.assertEqual( + get.result, + GetResponse( + version=0xAC, + get=Get.offset + args.node_id, + read=ReadMemory.offset + args.node_id, + go=Go.offset + args.node_id, + write=WriteMemory.offset + args.node_id, + erase=ErasePages.offset + args.node_id, + ), + ) + + +class TestReadMemory(TestCommand): + def test_good_address_short(self): + length = 1 + read = ReadMemory(args.node_id, 0x08000000, length) + self.do_cmds([read]) + self.assertEqual(len(read.result), length) + + def test_bad_address_short(self): + length = 1 + read = ReadMemory(args.node_id, 0x88000000, length) + with self.assertRaises(UnexpectedNack): + self.do_cmds([read]) + + def test_good_address_long(self): + length = 256 + read = ReadMemory(args.node_id, 0x08000000, length) + self.do_cmds([read]) + self.assertEqual(len(read.result), length) + + def test_bad_address_long(self): + length = 256 + read = ReadMemory(args.node_id, 0x88000000, length) + with self.assertRaises(UnexpectedNack): + self.do_cmds([read]) + + def test_last_address(self): + # Address is at the top of flash memory space + length = 1 + read = ReadMemory(args.node_id, 0x0803FFFF, length) + self.do_cmds([read]) + self.assertEqual(len(read.result), length) + + def test_last_address_too_long(self): + # Address is at the top of flash memory space + length = 2 + read = ReadMemory(args.node_id, 0x0803FFFF, length) + with self.assertRaises(UnexpectedNack): + self.do_cmds([read]) + + +class TestErasePages(TestCommand): + def test_good_page_short(self): + erase = ErasePages(args.node_id, [20]) + self.do_cmds([erase]) + + def test_bad_page_short(self): + erase = ErasePages(args.node_id, [19]) + with self.assertRaises(UnexpectedNack): + self.do_cmds([erase]) + + def test_good_page_long(self): + erase = ErasePages(args.node_id, range(20, 27)) + self.do_cmds([erase]) + + def test_bad_page_long(self): + erase = ErasePages(args.node_id, range(19, 26)) + with self.assertRaises(UnexpectedNack): + self.do_cmds([erase]) + + def test_last_good_page(self): + erase = ErasePages(args.node_id, [127]) + self.do_cmds([erase]) + + def test_last_bad_page_short(self): + erase = ErasePages(args.node_id, [128]) + with self.assertRaises(UnexpectedNack): + self.do_cmds([erase]) + + def test_last_bad_page_long(self): + erase = ErasePages(args.node_id, range(125, 132)) + with self.assertRaises(UnexpectedNack): + self.do_cmds([erase]) + + def test_mix_n_match(self): + erase = ErasePages(args.node_id, [20, 127, 33, 94, 33, 33]) + self.do_cmds([erase]) + + def test_too_long_range(self): + erase = ErasePages(args.node_id, [20]) + erase.msg.data[0] = 8 + with self.assertRaises(UnexpectedNack): + self.do_cmds([erase]) + + +class TestWriteMemory(TestCommand): + def test_good_address_short(self): + address = 0x0800A000 + data = bytearray([2]) + erase = ErasePages(args.node_id, [20]) + write = WriteMemory(args.node_id, address, data) + read = ReadMemory(args.node_id, address, len(data)) + self.do_cmds([erase, write, read]) + self.assertEqual(data, read.result) + + def test_bad_address_short(self): + address = 0x08000000 + data = bytearray([2]) + erase = ErasePages(args.node_id, [20]) + write = WriteMemory(args.node_id, address, data) + self.do_cmds([erase]) + with self.assertRaises(UnexpectedNack): + self.do_cmds([write]) + + def test_good_address_long(self): + address = 0x0800A000 + data = bytearray([2] * 256) + erase = ErasePages(args.node_id, [20]) + write = WriteMemory(args.node_id, address, data) + read = ReadMemory(args.node_id, address, len(data)) + self.do_cmds([erase, write, read]) + self.assertEqual(data, read.result) + + def test_bad_address_long(self): + address = 0x08000000 + data = bytearray([2] * 256) + erase = ErasePages(args.node_id, [20]) + write = WriteMemory(args.node_id, address, data) + self.do_cmds([erase]) + with self.assertRaises(UnexpectedNack): + self.do_cmds([write]) + + +# TODO: class TestMetadata which tests valid and invalid firmware metadata + + +def tearDownModule(): + bus.shutdown() + + +if __name__ == '__main__': + unittest.main(argv=[sys.argv[0]] + remaining) diff --git a/src/f0/app_bootloader/test_stm32_bootloader.py b/src/f0/app_bootloader/test_stm32_bootloader.py deleted file mode 100644 index 41d0952d..00000000 --- a/src/f0/app_bootloader/test_stm32_bootloader.py +++ /dev/null @@ -1,271 +0,0 @@ -import can -import time,math -#This does testing of the CAN bootloader from python using a CANable attached to the host PC - -BOOTLOADER_EXPECTED_FIRST_FRAME_ID = 0x79 - -CAN_COMMAND_GET = 0x00 -CAN_COMMAND_READ_MEMORY = 0x11 -CAN_COMMAND_GO = 0x21 -CAN_COMMAND_WRITE_MEMORY = 0x31 -CAN_COMMAND_ERASE = 0x43 - - -def rx_ack_nack(bus, sid, timeout=0.25): - msg = bus.recv(timeout) - print("RX msg: " + str(msg)) - - if( msg is not None and msg.arbitration_id == sid and msg.dlc == 1 ): - if( msg.data[0] == 0x79 ): - print("Got ACK from device") - return(True) - elif( msg.data[0] == 0x1F ): - print("Got NACK from device") - return(False) - - raise Exception("Got unexpected CAN frame type: " + str(msg)) - - -def purge_rx_buff(bus): - countdown = 3 - while( countdown > 0 ): - if( bus.recv(0.05) ): - countdown = 3 - else: - countdown -= 1 - - return - - -def read_memory(bus, read_address, num_bytes_to_read): - print("Reading " + str(num_bytes_to_read) + " bytes of data at address 0x" + hex(read_address)) - msg = can.Message(arbitration_id=CAN_COMMAND_READ_MEMORY, - data=[(read_address >> 24) & 0xFF, (read_address >> 16) & 0xFF, (read_address >> 8) & 0xFF, (read_address >> 0) & 0xFF, num_bytes_to_read - 1], - is_extended_id=False) - bus.send(msg) - rx_ack_nack(bus, CAN_COMMAND_READ_MEMORY) - - expected_frame_count = int(math.ceil((num_bytes_to_read) / 8.0)) - print("expected_frame_count = " + str(expected_frame_count)) - - #total_bytes_read = 0 - for idx in range(0, expected_frame_count): - # read data - msg = bus.recv(0.25) - print("Data readback is " + str(msg)) - #total_bytes_read += msg.dlc - - rx_ack_nack(bus, CAN_COMMAND_READ_MEMORY) - - purge_rx_buff(bus) - - return - - -def erase_all_pages(bus): - print("Eraseing all pages") - msg = can.Message(arbitration_id=CAN_COMMAND_ERASE, data=[0xFF], is_extended_id=False) - print("Sending " + str(msg)) - bus.send(msg) - rx_ack_nack(bus, CAN_COMMAND_ERASE) - - return - - -def erase_page(bus, list_of_page_numbers): - print("Erasing page numbers " + str(list_of_page_numbers)) - purge_rx_buff(bus) - - num_pages_to_erase = len(list_of_page_numbers) - data = [num_pages_to_erase - 1] - data.extend(list_of_page_numbers) - msg = can.Message(arbitration_id=CAN_COMMAND_ERASE, data=data, is_extended_id=False) - print("Sending " + str(msg)) - - bus.send(msg) - rx_ack_nack(bus, CAN_COMMAND_ERASE) - - for i in range(0, len(list_of_page_numbers)): - rx_ack_nack(bus, CAN_COMMAND_ERASE, timeout=5.0) - - purge_rx_buff(bus) - - return - - -def write_memory(bus, base_address, byte_list): - print("Writing " + str(len(byte_list)) + " bytes at address 0x" + hex(base_address)) - - msg = can.Message(arbitration_id=CAN_COMMAND_WRITE_MEMORY, - data=[(base_address >> 24) & 0xFF, (base_address >> 16) & 0xFF, (base_address >> 8) & 0xFF, (base_address >> 0) & 0xFF, len(byte_list)], - is_extended_id=False) - bus.send(msg) - rx_ack_nack(bus, msg.arbitration_id) - - idx = 0 - while(True): - temp_data = [] - while( idx < len(byte_list) and len(temp_data) < 8 ): - temp_data.append(byte_list[idx]) - idx += 1 - - if( len(temp_data) > 0 ): - msg = can.Message(arbitration_id=0x04, data=temp_data, is_extended_id=False) - print("Sending: " + str(msg)) - bus.send(msg) - rx_ack_nack(bus, CAN_COMMAND_WRITE_MEMORY) - else: - break - - rx_ack_nack(bus, CAN_COMMAND_WRITE_MEMORY) - - return - - -def firmware_update(bus, address, firmware_image): - page_list = range(20, 126) - - while( len(page_list) > 0 ): - page_list_2 = [] - while( len(page_list_2) < 7 and len(page_list) > 0 ): - page_list_2.append(page_list.pop(0)) - if( len(page_list) > 0 ): - erase_page(bus, page_list_2) - - # for i in range(20, 126): - # erase_page(bus, [i]) - - buff = [] - with open(firmware_image, 'rb') as f: - byte = f.read(1) - while byte != b"": - buff.append(byte) - byte = f.read(1) - - - while(len(buff) > 0): - data = [] - for i in range(0, 64): - if( len(buff) == 0 ): - break - - data.append(int.from_bytes(buff.pop(0), 'big')) - - write_memory(bus, address, data) - address += len(data) - - can_go(bus) - - return - - -def can_go(bus): - msg = can.Message(arbitration_id=CAN_COMMAND_GO, data=[], is_extended_id=False) - bus.send(msg) - - rx_ack_nack(bus, CAN_COMMAND_GO) - - return - - -def can_get(bus): - msg = can.Message(arbitration_id=CAN_COMMAND_GET, data=[], is_extended_id=False) - - bus.send(msg) - rx_ack_nack(bus, msg.arbitration_id) - - print("Reading number of bytes") - msg_number_of_bytes = bus.recv(0.25) - print(str(msg_number_of_bytes)) - - number_of_frames = msg_number_of_bytes.data[0] + 1 - print("number_of_frames = " + str(number_of_frames)) - - for i in range(0, number_of_frames): - msg_command_type = bus.recv(0.25) - print(str(msg_command_type)) - - rx_ack_nack(bus, msg.arbitration_id) - - print("Done with can_get()") - - purge_rx_buff(bus) - - return - - -def test_can_bootloader_client(bus, is_407_cpu=True): - #purge_rx_buff(bus) - can_get(bus) - - # read_memory(bus, 0x08000000, 31) - # read_memory(bus, 0x08000000, 32) - # read_memory(bus, 0x08000000, 33) - - # read_memory(bus, 0x0800A000, 32) - # write_memory(bus, 0x0800A000, [0x0A, 0x0B, 0x0C, 0x0D, 0x01, 0x02, 0x03, 0x04, 0x05]) - # read_memory(bus, 0x0800A000, 32) - - # erase_page_list = [20, 21, 22, 23] - # if( is_407_cpu ): - # erase_page_list = [0, 1, 2, 3] - # erase_page_list = [3] - - # erase_page(bus, erase_page_list) - # read_memory(bus, 0x0800A000, 32) - - # write_memory(bus, 0x0800A000, [0x0A, 0x0B, 0x0C, 0x0D, 0x01, 0x02, 0x03, 0x04, 0x05]) - # read_memory(bus, 0x0800A000, 32) - - if( not is_407_cpu ): - firmware_update(bus, 0x0800A000, '../app_protocard2/build/app_protocard2.crc32.bin') - - return - - - - -if( False ): - rx_msg = None - bitrate = 1000000 - - bus = can.interface.Bus(bustype='slcan', channel='/dev/ttyACM1', bitrate=bitrate) - while( rx_msg is None ): - rx_msg = bus.recv(1) - if( rx_msg is None ): - print("No RX message yet") - - if( rx_msg.arbitration_id == CAN_COMMAND_GET and rx_msg.dlc == 8 ): - if(rx_msg.data[0] == 0x01 and rx_msg.data[1] == 0x02 and rx_msg.data[2] == 0x03 and rx_msg.data[3] == 0x04 ): - msg = can.Message(arbitration_id=BOOTLOADER_EXPECTED_FIRST_FRAME_ID, - data=[rx_msg.data[4], rx_msg.data[5], rx_msg.data[6], rx_msg.data[7], 0x01, 0x02, 0x03, 0x04], - is_extended_id=False) - - print("Sending " + str(msg)) - bus.send(msg) - #rx_ack_nack(bus, msg.arbitration_id) - - test_can_bootloader_client(bus, False) -else: - bitrate = 125000 - bus = can.interface.Bus(bustype='slcan', channel='/dev/ttyACM1', bitrate=bitrate) - - for i in range(0, 99999): - msg = can.Message(arbitration_id=BOOTLOADER_EXPECTED_FIRST_FRAME_ID, data=[0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA], is_extended_id=False) - - bus.send(msg) - print(str(time.time()) + " Sent Initialization frame: " + str(msg)) - - rx_msg = bus.recv(0.05) - if( rx_msg is not None ): - print("RX: " + str(rx_msg)) - test_can_bootloader_client(bus, True) - break - - time.sleep(0.2) - - - - - - diff --git a/toolchain/f0_pack_image.py b/toolchain/f0_pack_image.py index 604558ee..6420a78b 100644 --- a/toolchain/f0_pack_image.py +++ b/toolchain/f0_pack_image.py @@ -1,33 +1,32 @@ -import sys,os -import struct +'''Prepends metadata (crc, length, version) to F0 firmware images''' import binascii +import os +import struct +import sys input_fw_bin_file = sys.argv[1] output_fw_bin_file = sys.argv[2] -version = sys.argv[3] # 20200101 - -total_length = 1020 + os.path.getsize(input_fw_bin_file) - - -f = open(output_fw_bin_file, 'wb') - -f.write(struct.pack("I", total_length)) -f.write(struct.pack("I", int(version))) -for i in range(0, int((1024 - 4 - 4 - 4) / 4)): - f.write(struct.pack("I", 0xFFFFFFFF)) - -f.write(open(input_fw_bin_file, 'rb').read()) -f.close() - - -concated_data = open(output_fw_bin_file, 'rb').read() -crc32 = binascii.crc32(concated_data) - -f = open(output_fw_bin_file, 'wb') -f.write(struct.pack("I", crc32)) -f.write(concated_data) -f.close() - -print("Final Length: " + str(total_length)) -print("Final CRC32: 0x" + str(hex(crc32))) - +version = sys.argv[3] # e.g. 20200101 + +# See app_bootloader struct FirmwareMetadata for definition. Metadata consists +# of 3 uint32_t at the end of the metadata block (which is just before the +# firmware block, see ld/STM32F091xC-bootloader.ld) +LENGTH_LEN = 4 +VERSION_LEN = 4 + +# The length that gets CRC'd. Doesn't include the CRC space itself, but +# does include everything else +total_length = LENGTH_LEN + VERSION_LEN + os.path.getsize(input_fw_bin_file) + +with open(input_fw_bin_file, 'rb') as f: + metadata = struct.pack("II", total_length, int(version)) + firmware = f.read() + crc32 = binascii.crc32(metadata + firmware) + +with open(output_fw_bin_file, 'wb') as f: + f.write(struct.pack("I", crc32)) + f.write(metadata) + f.write(firmware) + +print(f"Final Length: {total_length}") +print(f"Final CRC32: 0x{hex(crc32)}")