diff --git a/firmware/application/apps/ui_flash_utility.cpp b/firmware/application/apps/ui_flash_utility.cpp index a3658c3e..b2b30d76 100644 --- a/firmware/application/apps/ui_flash_utility.cpp +++ b/firmware/application/apps/ui_flash_utility.cpp @@ -30,6 +30,30 @@ static const char16_t* firmware_folder = u"/FIRMWARE"; Thread* FlashUtilityView::thread{nullptr}; static constexpr size_t max_filename_length = 26; +bool valid_firmware_file(std::filesystem::path::string_type path) { + File firmware_file; + uint32_t read_buffer[128]; + uint32_t checksum{1}; + + // test read of the whole file just to validate checksum (baseband flash code will re-read when flashing) + auto result = firmware_file.open(path.c_str()); + if (!result.is_valid()) { + checksum = 0; + for (uint32_t i = FLASH_STARTING_ADDRESS; i < FLASH_ROM_SIZE / sizeof(read_buffer); i++) { + auto readResult = firmware_file.read(&read_buffer, sizeof(read_buffer)); + + // if file is smaller than 1MB, assume it's a downgrade to an old FW version and ignore the checksum + if ((!readResult) || (readResult.value() != sizeof(read_buffer))) { + checksum = FLASH_EXPECTED_CHECKSUM; + break; + } + + checksum += simple_checksum((uint32_t)read_buffer, sizeof(read_buffer)); + } + } + return (checksum == FLASH_EXPECTED_CHECKSUM); +} + FlashUtilityView::FlashUtilityView(NavigationView& nav) : nav_(nav) { add_children({&labels, @@ -111,8 +135,11 @@ void FlashUtilityView::flash_firmware(std::filesystem::path::string_type path) { if (endsWith(path, u".tar")) { // extract, then update path = extract_tar(u'/' + path).native(); - if (path.empty()) return; } + + if (path.empty() || !valid_firmware_file(path.c_str())) + return; // bad firmware image - just returning back to the file list + ui::Painter painter; painter.fill_rectangle( {0, 0, portapack::display.width(), portapack::display.height()}, diff --git a/firmware/application/apps/ui_flash_utility.hpp b/firmware/application/apps/ui_flash_utility.hpp index 01ea2875..00950b4e 100644 --- a/firmware/application/apps/ui_flash_utility.hpp +++ b/firmware/application/apps/ui_flash_utility.hpp @@ -32,8 +32,14 @@ #include "untar.hpp" #include +#define FLASH_ROM_SIZE 1048576 +#define FLASH_STARTING_ADDRESS 0x00000000 +#define FLASH_EXPECTED_CHECKSUM 0x00000000 + namespace ui { +bool valid_firmware_file(std::filesystem::path::string_type path); + class FlashUtilityView : public View { public: FlashUtilityView(NavigationView& nav); diff --git a/firmware/application/ui_external_items_menu_loader.cpp b/firmware/application/ui_external_items_menu_loader.cpp index f5b453d7..98fbe60e 100644 --- a/firmware/application/ui_external_items_menu_loader.cpp +++ b/firmware/application/ui_external_items_menu_loader.cpp @@ -107,6 +107,7 @@ namespace ui { /* static */ bool ExternalItemsMenuLoader::run_external_app(ui::NavigationView& nav, std::filesystem::path filePath) { File app; + uint32_t checksum{0}; auto openError = app.open(filePath); if (openError) @@ -115,7 +116,6 @@ namespace ui { application_information_t application_information = {}; auto readResult = app.read(&application_information, sizeof(application_information_t)); - if (!readResult) return false; @@ -135,6 +135,8 @@ namespace ui { if (!readResult) return false; + checksum += simple_checksum((uint32_t)&application_information.memory_location[file_read_index], readResult.value()); + if (readResult.value() < std::filesystem::max_file_block_size) break; } @@ -156,6 +158,8 @@ namespace ui { if (!readResult) return false; + checksum += simple_checksum((uint32_t)target_memory, readResult.value()); + if (readResult.value() != bytes_to_read) break; } @@ -168,11 +172,16 @@ namespace ui { if (!readResult) return false; + checksum += simple_checksum((uint32_t)&application_information.memory_location[file_read_index], readResult.value()); + if (readResult.value() < std::filesystem::max_file_block_size) break; } } + if (checksum != EXT_APP_EXPECTED_CHECKSUM) + return false; + application_information.externalAppEntry(nav); return true; } diff --git a/firmware/application/ui_external_items_menu_loader.hpp b/firmware/application/ui_external_items_menu_loader.hpp index b4345b9e..0c3b04c8 100644 --- a/firmware/application/ui_external_items_menu_loader.hpp +++ b/firmware/application/ui_external_items_menu_loader.hpp @@ -29,6 +29,8 @@ #include "file.hpp" +#define EXT_APP_EXPECTED_CHECKSUM 0x00000000 + namespace ui { template diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 1e442990..d3ebe0e9 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -503,8 +503,8 @@ void SystemStatusView::rtc_battery_workaround() { month = (timestamp.FAT_date >> 5) & 0xF; day = timestamp.FAT_date & 0x1F; - // bump to next month at 28 days for simplicity - if (++day > 28) { + // bump to next month + if (++day > rtc_time::days_per_month(year, month)) { day = 1; if (++month > 12) { month = 1; @@ -547,16 +547,16 @@ InformationView::InformationView( <ime}); #if GCC_VERSION_MISMATCH - static constexpr Style style_gcc_warning{ - .font = font::fixed_8x16, - .background = {33, 33, 33}, - .foreground = Color::yellow(), - }; - version.set_style(&style_gcc_warning); + version.set_style(&Styles::yellow); #else version.set_style(&style_infobar); #endif + if (firmware_checksum_error()) { + version.set("FLASH ERROR"); + version.set_style(&Styles::red); + } + ltime.set_style(&style_infobar); refresh(); set_dirty(); @@ -568,6 +568,17 @@ void InformationView::refresh() { ltime.set_date_enabled(pmem::clock_with_date()); } +bool InformationView::firmware_checksum_error() { + static bool fw_checksum_checked{false}; + static bool fw_checksum_error{false}; + + // only checking firmware checksum once per boot + if (!fw_checksum_checked) { + fw_checksum_error = (simple_checksum(FLASH_STARTING_ADDRESS, FLASH_ROM_SIZE) != FLASH_EXPECTED_CHECKSUM); + } + return fw_checksum_error; +} + /* Navigation ************************************************************/ bool NavigationView::is_top() const { diff --git a/firmware/application/ui_navigation.hpp b/firmware/application/ui_navigation.hpp index 61333e15..4b20cc82 100644 --- a/firmware/application/ui_navigation.hpp +++ b/firmware/application/ui_navigation.hpp @@ -286,6 +286,7 @@ class InformationView : public View { public: InformationView(NavigationView& nav); void refresh(); + bool firmware_checksum_error(); private: // static constexpr auto version_string = "v1.4.4"; // This is commented out as we are now setting the version via ENV (VERSION_STRING=v1.0.0) diff --git a/firmware/application/usb_serial_shell.cpp b/firmware/application/usb_serial_shell.cpp index 26fdf118..96a220db 100644 --- a/firmware/application/usb_serial_shell.cpp +++ b/firmware/application/usb_serial_shell.cpp @@ -41,6 +41,7 @@ #include "chprintf.h" #include "chqueues.h" #include "ui_external_items_menu_loader.hpp" +#include "ui_flash_utility.hpp" #include "untar.hpp" #include "ui_widget.hpp" @@ -170,7 +171,13 @@ static void cmd_flash(BaseSequentialStream* chp, int argc, char* argv[]) { } else if (strEndsWith(path.native(), u".bin")) { // nothing to do for this case yet. } else { - chprintf(chp, "error only .bin or .ppfw.tar files canbe flashed.\r\n"); + chprintf(chp, "error only .bin or .ppfw.tar files can be flashed.\r\n"); + nav->pop(); + return; + } + + if (!ui::valid_firmware_file(path.native().c_str())) { + chprintf(chp, "error corrupt firmware file.\r\n"); nav->pop(); return; } diff --git a/firmware/common/portapack_persistent_memory.cpp b/firmware/common/portapack_persistent_memory.cpp index 7723f77c..3d711462 100644 --- a/firmware/common/portapack_persistent_memory.cpp +++ b/firmware/common/portapack_persistent_memory.cpp @@ -32,6 +32,7 @@ #include "string_format.hpp" #include "ui_styles.hpp" #include "ui_painter.hpp" +#include "ui_flash_utility.hpp" #include "utility.hpp" #include "rtc_time.hpp" @@ -1051,6 +1052,9 @@ bool debug_dump() { pmem_dump_file.write_line("Ext APPS version req'd: 0x" + to_string_hex(VERSION_MD5)); pmem_dump_file.write_line("GCC version: " + to_string_dec_int(__GNUC__) + "." + to_string_dec_int(__GNUC_MINOR__) + "." + to_string_dec_int(__GNUC_PATCHLEVEL__)); + // firmware checksum + pmem_dump_file.write_line("Firmware calculated checksum: 0x" + to_string_hex(simple_checksum(FLASH_STARTING_ADDRESS, FLASH_ROM_SIZE), 8)); + // write persistent memory pmem_dump_file.write_line("\n[Persistent Memory]"); diff --git a/firmware/common/utility.cpp b/firmware/common/utility.cpp index d4cbdc26..36446e0a 100644 --- a/firmware/common/utility.cpp +++ b/firmware/common/utility.cpp @@ -235,3 +235,10 @@ std::string join(char c, std::initializer_list strings) { return result; } + +uint32_t simple_checksum(uint32_t buffer_address, uint32_t length) { + uint32_t checksum = 0; + for (uint32_t i = 0; i < length; i += 4) + checksum += *(uint32_t*)(buffer_address + i); + return checksum; +} \ No newline at end of file diff --git a/firmware/common/utility.hpp b/firmware/common/utility.hpp index b1688caf..924297f6 100644 --- a/firmware/common/utility.hpp +++ b/firmware/common/utility.hpp @@ -217,4 +217,6 @@ struct range_t { std::string join(char c, std::initializer_list strings); +uint32_t simple_checksum(uint32_t buffer_address, uint32_t length); + #endif /*__UTILITY_H__*/ diff --git a/firmware/tools/export_external_apps.py b/firmware/tools/export_external_apps.py index 228d82e6..8249ab9e 100755 --- a/firmware/tools/export_external_apps.py +++ b/firmware/tools/export_external_apps.py @@ -104,6 +104,14 @@ for external_image_prefix in sys.argv[4:]: external_application_image = patch_image(external_application_image, search_address, replace_address) external_application_image[memory_location_header_position:memory_location_header_position+4] = replace_address.to_bytes(4, byteorder='little') + checksum = 0 + for i in range(0, len(external_application_image), 4): + checksum += external_application_image[i] + (external_application_image[i + 1] << 8) + (external_application_image[i + 2] << 16) + (external_application_image[i + 3] << 24) + + final_checksum = 0 + checksum = (final_checksum - checksum) & 0xFFFFFFFF + external_application_image += checksum.to_bytes(4, 'little') + write_image(external_application_image, "{}/{}.ppma".format(binary_dir, external_image_prefix)) continue @@ -127,5 +135,13 @@ for external_image_prefix in sys.argv[4:]: print("application {} can not exceed 32kb: {} bytes used".format(external_image_prefix, len(external_application_image))) sys.exit(-1) + checksum = 0 + for i in range(0, len(external_application_image), 4): + checksum += external_application_image[i] + (external_application_image[i + 1] << 8) + (external_application_image[i + 2] << 16) + (external_application_image[i + 3] << 24) + + final_checksum = 0 + checksum = (final_checksum - checksum) & 0xFFFFFFFF + external_application_image += checksum.to_bytes(4, 'little') + # write .ppma (portapack mayhem application) write_image(external_application_image, "{}/{}.ppma".format(binary_dir, external_image_prefix)) diff --git a/firmware/tools/make_spi_image.py b/firmware/tools/make_spi_image.py index a7151793..812028b3 100755 --- a/firmware/tools/make_spi_image.py +++ b/firmware/tools/make_spi_image.py @@ -74,7 +74,24 @@ for image in images: padded_data = image['data'] + (spi_image_default_byte * pad_size) spi_image += padded_data -if len(spi_image) > spi_size: - raise RuntimeError('SPI flash image size of %d exceeds device size of %d bytes' % (len(spi_image), spi_size)) +if len(spi_image) > spi_size - 4: + raise RuntimeError('SPI flash image size of %d exceeds device size of %d bytes' % (len(spi_image) + 4, spi_size)) + +pad_size = spi_size - 4 - len(spi_image) +for i in range(pad_size): + spi_image += spi_image_default_byte + +# quick "add up the words" checksum: +checksum = 0 +for i in range(0, len(spi_image), 4): + checksum += (spi_image[i] + (spi_image[i + 1] << 8) + (spi_image[i + 2] << 16) + (spi_image[i + 3] << 24)) + +final_checksum = 0 +checksum = (final_checksum - checksum) & 0xFFFFFFFF + +spi_image += checksum.to_bytes(4, 'little') write_image(spi_image, output_path) + +percent_remaining = round(1000 * pad_size / spi_size) / 10; +print ("Space remaining in flash ROM:", pad_size, "bytes (", percent_remaining, "%)") \ No newline at end of file diff --git a/firmware/tools/simple_checksum.py b/firmware/tools/simple_checksum.py new file mode 100644 index 00000000..f3e9f9d1 --- /dev/null +++ b/firmware/tools/simple_checksum.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2024 Mark Thompson +# +# This file is part of PortaPack. +# +# 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 2, 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; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +import sys +import struct + +usage_message = """ +PortaPack ROM image checksum checker + +Usage: +""" + +def read_image(path): + f = open(path, 'rb') + data = f.read() + f.close() + return data + +def write_image(data, path): + f = open(path, 'wb') + f.write(data) + f.close() + +if len(sys.argv) != 2: + print(usage_message) + sys.exit(-1) + +image = read_image(sys.argv[1]) +image = bytearray(image) + +# simple "add up the words" checksum: +checksum = 0 +for i in range(0, len(image), 4): + checksum += (image[i] + (image[i + 1] << 8) + (image[i + 2] << 16) + (image[i + 3] << 24)) + +checksum &= 0xFFFFFFFF +print ("Simple checksum =", checksum)