Parcourir la source

Add simple SD card bootloader.

Petteri Aimonen il y a 3 ans
Parent
commit
9dae7cd184

+ 10 - 3
README.md

@@ -47,9 +47,16 @@ The status led will blink continuously when card is not present, then blink once
 It will depend on the host system whether it gets confused by hotplugging.
 Any IO requests issued when card is removed will be timeouted.
 
-Programming
------------
-The AzulSCSI v1 board can be programmed using USB connection in DFU mode.
+Programming & bootloader
+------------------------
+There is a bootloader that loads new firmware from SD card on boot.
+The firmware file must be e.g. `AzulSCSI.bin` or `AzulSCSIv1_0_2022-xxxxx.bin`.
+Firmware update takes about 1 second, during which the LED will flash rapidly.
+
+When successful, the bootloader removes the update file and continues to main firmware.
+On failure, `azulerr.txt` is written on the SD card.
+
+Alternatively, the board can be programmed using USB connection in DFU mode by setting DIP switch 4.
 The necessary programmer utility for Windows can be downloaded from [GD32 website](http://www.gd32mcu.com/en/download?kw=dfu&lan=en). On Linux and Mac [gd32-dfu-utils](https://github.com/riscv-mcu/gd32-dfu-utils) can be used.
 
 DIP switches

+ 70 - 0
lib/AzulSCSI_platform_GD32F205/AzulSCSI_platform.cpp

@@ -1,9 +1,11 @@
 #include "AzulSCSI_platform.h"
 #include "gd32f20x_sdio.h"
+#include "gd32f20x_fmc.h"
 #include "AzulSCSI_log.h"
 #include "AzulSCSI_config.h"
 #include <SdFat.h>
 #include <scsi.h>
+#include <assert.h>
 
 extern "C" {
 
@@ -323,6 +325,74 @@ void azplatform_reset_watchdog()
     g_watchdog_timeout = WATCHDOG_CRASH_TIMEOUT;
 }
 
+/***********************/
+/* Flash reprogramming */
+/***********************/
+
+bool azplatform_rewrite_flash_page(uint32_t offset, uint8_t buffer[AZPLATFORM_FLASH_PAGE_SIZE])
+{
+    if (offset == 0)
+    {
+        if (buffer[3] != 0x20 || buffer[7] != 0x08)
+        {
+            azlog("Invalid firmware file, starts with: ", bytearray(buffer, 16));
+            return false;
+        }
+    }
+
+    azdbg("Writing flash at offset ", offset, " data ", bytearray(buffer, 4));
+    assert(offset % AZPLATFORM_FLASH_PAGE_SIZE == 0);
+    assert(offset >= AZPLATFORM_BOOTLOADER_SIZE);
+    
+    fmc_unlock();
+    fmc_bank0_unlock();
+
+    fmc_state_enum status;
+    status = fmc_page_erase(FLASH_BASE + offset);
+    if (status != FMC_READY)
+    {
+        azlog("Erase failed: ", (int)status);
+        return false;
+    }
+
+    uint32_t *buf32 = (uint32_t*)buffer;
+    uint32_t num_words = AZPLATFORM_FLASH_PAGE_SIZE / 4;
+    for (int i = 0; i < num_words; i++)
+    {
+        status = fmc_word_program(FLASH_BASE + offset + i * 4, buf32[i]);
+        if (status != FMC_READY)
+        {
+            azlog("Flash write failed: ", (int)status);
+            return false;
+        }   
+    }
+
+    fmc_lock();
+
+    for (int i = 0; i < num_words; i++)
+    {
+        uint32_t expected = buf32[i];
+        uint32_t actual = *(volatile uint32_t*)(FLASH_BASE + offset + i * 4);
+        if (actual != expected)
+        {
+            azlog("Flash verify failed at offset ", offset + i * 4, " got ", actual, " expected ", expected);
+            return false;
+        }
+    }
+    return true;
+}
+
+void azplatform_boot_to_main_firmware()
+{
+    uint32_t *mainprogram_start = (uint32_t*)(0x08000000 + AZPLATFORM_BOOTLOADER_SIZE);
+    SCB->VTOR = (uint32_t)mainprogram_start;
+  
+    __asm__(
+        "msr msp, %0\n\t"
+        "bx %1" : : "r" (mainprogram_start[0]),
+                    "r" (mainprogram_start[1]) : "memory");
+}
+
 /**********************************************/
 /* Mapping from data bytes to GPIO BOP values */
 /**********************************************/

+ 7 - 0
lib/AzulSCSI_platform_GD32F205/AzulSCSI_platform.h

@@ -64,6 +64,13 @@ void azplatform_emergency_log_save();
 typedef void (*sd_callback_t)(uint32_t bytes_complete);
 void azplatform_set_sd_callback(sd_callback_t func, const uint8_t *buffer);
 
+// Reprogram firmware in main program area.
+#define AZPLATFORM_BOOTLOADER_SIZE 32768
+#define AZPLATFORM_FLASH_TOTAL_SIZE (256 * 1024)
+#define AZPLATFORM_FLASH_PAGE_SIZE 2048
+bool azplatform_rewrite_flash_page(uint32_t offset, uint8_t buffer[AZPLATFORM_FLASH_PAGE_SIZE]);
+void azplatform_boot_to_main_firmware();
+
 // Write a single SCSI pin.
 // Example use: SCSI_OUT(ATN, 1) sets SCSI_ATN to low (active) state.
 #define SCSI_OUT(pin, state) \

+ 174 - 0
lib/AzulSCSI_platform_GD32F205/azulscsi_gd32f205.ld

@@ -0,0 +1,174 @@
+/*
+ *
+ * Customized linker script for combining bootloader & main program
+ * PlatformIO default linker script template for STM32 F1/F2/F3/F4/F7/L0/L1/L4
+ *
+ */
+
+/* Entry Point */
+ENTRY(Reset_Handler)
+
+/* Highest address of the user mode stack */ 
+_estack = 0x20020000;    /* end of RAM */
+/* Generate a link error if heap and stack don't fit into RAM */
+_Min_Heap_Size = 0x200;      /* required amount of heap  */
+_Min_Stack_Size = 0x400;     /* required amount of stack */
+
+/* Specify the memory areas */
+MEMORY
+{
+    RAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 128K
+    CCRAM (xrw)    : ORIGIN = 0x10000000, LENGTH = 0K
+    FLASH (rx)     : ORIGIN = 0x08000000, LENGTH = 256K
+}
+
+/* Define output sections */
+SECTIONS
+{
+  /* If bootloader is included, it goes in first 32 kB */
+  .text.bootloader : ALIGN(16) SUBALIGN(16)
+  {
+    KEEP(*(.text.btldr*))
+    . = ALIGN(32768);
+  } >FLASH
+
+  /* The startup code goes first into FLASH */
+  .isr_vector :
+  {
+    . = ALIGN(4);
+    KEEP(*(.isr_vector)) /* Startup code */
+    . = ALIGN(4);
+  } >FLASH
+
+  /* The program code and other data goes into FLASH */
+  .text :
+  {
+    . = ALIGN(4);
+    *(.text)           /* .text sections (code) */
+    *(.text*)          /* .text* sections (code) */
+    *(.glue_7)         /* glue arm to thumb code */
+    *(.glue_7t)        /* glue thumb to arm code */
+    *(.eh_frame)
+
+    KEEP (*(.init))
+    KEEP (*(.fini))
+
+    . = ALIGN(4);
+    _etext = .;        /* define a global symbols at end of code */
+  } >FLASH
+
+  /* Constant data goes into FLASH */
+  .rodata :
+  {
+    . = ALIGN(4);
+    *(.rodata)         /* .rodata sections (constants, strings, etc.) */
+    *(.rodata*)        /* .rodata* sections (constants, strings, etc.) */
+    . = ALIGN(4);
+  } >FLASH
+
+  .ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH
+  .ARM : {
+    __exidx_start = .;
+    *(.ARM.exidx*)
+    __exidx_end = .;
+  } >FLASH
+
+  .preinit_array     :
+  {
+    PROVIDE_HIDDEN (__preinit_array_start = .);
+    KEEP (*(.preinit_array*))
+    PROVIDE_HIDDEN (__preinit_array_end = .);
+  } >FLASH
+  .init_array :
+  {
+    PROVIDE_HIDDEN (__init_array_start = .);
+    KEEP (*(SORT(.init_array.*)))
+    KEEP (*(.init_array*))
+    PROVIDE_HIDDEN (__init_array_end = .);
+  } >FLASH
+  .fini_array :
+  {
+    PROVIDE_HIDDEN (__fini_array_start = .);
+    KEEP (*(SORT(.fini_array.*)))
+    KEEP (*(.fini_array*))
+    PROVIDE_HIDDEN (__fini_array_end = .);
+  } >FLASH
+
+  /* used by the startup to initialize data */
+  _sidata = LOADADDR(.data);
+
+  /* Initialized data sections goes into RAM, load LMA copy after code */
+  .data : 
+  {
+    . = ALIGN(4);
+    _sdata = .;        /* create a global symbol at data start */
+    *(.data)           /* .data sections */
+    *(.data*)          /* .data* sections */
+
+    . = ALIGN(4);
+    _edata = .;        /* define a global symbol at data end */
+  } >RAM AT> FLASH
+  
+  /* Uninitialized data section */
+  . = ALIGN(4);
+  .bss :
+  {
+    /* This is used by the startup in order to initialize the .bss secion */
+    _sbss = .;         /* define a global symbol at bss start */
+    __bss_start__ = _sbss;
+    *(.bss)
+    *(.bss*)
+    *(COMMON)
+
+    . = ALIGN(4);
+    _ebss = .;         /* define a global symbol at bss end */
+    __bss_end__ = _ebss;
+  } >RAM
+
+  /* User_heap_stack section, used to check that there is enough RAM left */
+  ._user_heap_stack :
+  {
+    . = ALIGN(4);
+    PROVIDE ( end = . );
+    PROVIDE ( _end = . );
+    . = . + _Min_Heap_Size;
+    . = . + _Min_Stack_Size;
+    . = ALIGN(4);
+  } >RAM
+
+  /* uninitialized CCRAM objects (like, buffers) */
+  .ccram_bss :
+  {
+    __ccram_start_bss__ = .; /* define a global symbol at ccram start */
+    KEEP(*(.ccram_bss))
+    KEEP(*(.ccram_bss*))
+    . = ALIGN(4);
+    __ccram_end_bss__ = .; /* define a global symbol at end of *used* CCRAM (BSS) */
+  } >CCRAM
+
+  /* initialized CCRAM objects (like, initialized variables) */
+  _si_ccram_data = LOADADDR(.ccram_data);
+  .ccram_data :
+  {
+    . = ALIGN(4);
+    _ccram_start_data = .;    /* create a global symbol at data start */
+    *(.ccram_bss)             /* .data sections */
+    *(.ccram_bss*)            /* .data* sections */
+
+    . = ALIGN(4);
+    _ccram_end_data = .;      /* define a global symbol at data end */
+  } >CCRAM AT> FLASH
+  
+
+  /* Remove information from the standard libraries */
+  /DISCARD/ :
+  {
+    libc.a ( * )
+    libm.a ( * )
+    libgcc.a ( * )
+  }
+
+  .ARM.attributes 0 : { *(.ARM.attributes) }
+}
+
+

+ 3 - 17
platformio.ini

@@ -5,6 +5,7 @@ platform = https://github.com/CommunityGD32Cores/platform-gd32.git
 board = genericGD32F205VC
 board_build.mcu = gd32f205vct6
 board_build.core = gd32
+board_build.ldscript = lib/AzulSCSI_platform_GD32F205/azulscsi_gd32f205.ld
 framework = spl
 lib_compat_mode = off
 lib_deps =
@@ -16,7 +17,7 @@ upload_protocol = stlink
 platform_packages = 
     toolchain-gccarmnoneeabi@1.60301.0
     framework-spl-gd32@https://github.com/CommunityGD32Cores/gd32-pio-spl-package.git
-
+extra_scripts = src/build_bootloader.py
 build_flags = 
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
      -D__SYSTEM_CLOCK_120M_PLL_IRC8M=120000000
@@ -26,22 +27,7 @@ build_flags =
      -DAZULSCSI_V1_0
 
 [env:AzulSCSIv1_1]
-platform = https://github.com/CommunityGD32Cores/platform-gd32.git
-board = genericGD32F205VC
-board_build.mcu = gd32f205vct6
-board_build.core = gd32
-framework = spl
-lib_compat_mode = off
-lib_deps =
-    SdFat_NoArduino
-    minIni
-    AzulSCSI_platform_GD32F205
-    SCSI2SD
-upload_protocol = stlink
-platform_packages = 
-    toolchain-gccarmnoneeabi@1.60301.0
-    framework-spl-gd32@https://github.com/CommunityGD32Cores/gd32-pio-spl-package.git
-
+extends = env:AzulSCSIv1_0
 build_flags = 
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
      -D__SYSTEM_CLOCK_120M_PLL_IRC8M=120000000

+ 4 - 4
src/AzulSCSI.cpp

@@ -299,7 +299,7 @@ static void reinitSCSI()
   
 }
 
-int main(void)
+extern "C" int azulscsi_main(void)
 {
   azplatform_init();
 
@@ -312,7 +312,7 @@ int main(void)
     {
       blinkStatus(BLINK_ERROR_NO_SD_CARD);
       delay(1000);
-      azplatform_reset_watchdog(15000);
+      azplatform_reset_watchdog();
     } while (!SD.begin(SD_CONFIG));
     azlog("SD card init succeeded after retry");
   }
@@ -331,7 +331,7 @@ int main(void)
 
   while (1)
   {
-    azplatform_reset_watchdog(WATCHDOG_CRASH_TIMEOUT);
+    azplatform_reset_watchdog();
     scsiPoll();
     scsiDiskPoll();
     scsiLogPhaseChange(scsiDev.phase);
@@ -357,7 +357,7 @@ int main(void)
           {
             blinkStatus(BLINK_ERROR_NO_SD_CARD);
             delay(1000);
-            azplatform_reset_watchdog(15000);
+            azplatform_reset_watchdog();
           } while (!SD.begin(SD_CONFIG));
           azlog("SD card reinit succeeded");
           print_sd_info();

+ 119 - 0
src/AzulSCSI_bootloader.cpp

@@ -0,0 +1,119 @@
+// Simple bootloader that loads new firmware from SD card.
+
+#include <AzulSCSI_platform.h>
+#include "AzulSCSI_log.h"
+#include <SdFat.h>
+#include <string.h>
+
+extern SdFs SD;
+extern FsFile g_logfile;
+
+bool find_firmware_image(FsFile &file, char name[MAX_FILE_PATH + 1])
+{
+    FsFile root;
+    root.open("/");
+
+    while (file.openNext(&root, O_READ))
+    {
+        if (file.isDir()) continue;
+
+        int namelen = file.getName(name, MAX_FILE_PATH);
+
+        if (namelen >= 11 &&
+            strncasecmp(name, "azulscsi", 8) == 0 &&
+            strncasecmp(name + namelen - 3, "bin", 3) == 0)
+        {
+            root.close();
+            azlog("Found firmware file: ", name);
+            return true;
+        }
+
+        file.close();
+    }
+
+    root.close();
+    return false;
+}
+
+bool program_firmware(FsFile &file)
+{
+    uint32_t fwsize = file.size() - AZPLATFORM_BOOTLOADER_SIZE;
+    uint32_t num_pages = (fwsize + AZPLATFORM_FLASH_PAGE_SIZE - 1) / AZPLATFORM_FLASH_PAGE_SIZE;
+    static uint8_t buffer[AZPLATFORM_FLASH_PAGE_SIZE];
+
+    if (fwsize > AZPLATFORM_FLASH_TOTAL_SIZE)
+    {
+        azlog("Firmware too large: ", (int)fwsize, " flash size ", (int)AZPLATFORM_FLASH_TOTAL_SIZE);
+        return false;
+    }
+
+    if (!file.seek(AZPLATFORM_BOOTLOADER_SIZE))
+    {
+        azlog("Seek failed");
+        return false;
+    }
+
+    for (int i = 0; i < num_pages; i++)
+    {
+        if (i % 2)
+            LED_ON();
+        else
+            LED_OFF();
+        
+        if (file.read(buffer, AZPLATFORM_FLASH_PAGE_SIZE) <= 0)
+        {
+            azlog("Firmware file read failed on page ", i);
+            return false;
+        }
+
+        if (!azplatform_rewrite_flash_page(AZPLATFORM_BOOTLOADER_SIZE + i * AZPLATFORM_FLASH_PAGE_SIZE, buffer))
+        {
+            azlog("Flash programming failed on page ", i);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+extern "C"
+int bootloader_main(void)
+{
+    azplatform_init();
+    g_azlog_debug = true;
+
+    azlog("Bootloader version: " __DATE__ " " __TIME__ " " PLATFORM_NAME);
+
+    if (SD.begin(SD_CONFIG))
+    {
+        FsFile fwfile;
+        char name[MAX_FILE_PATH + 1];
+        if (find_firmware_image(fwfile, name))
+        {
+            if (program_firmware(fwfile))
+            {
+                azlog("Firmware update successful!");
+                fwfile.close();
+                if (!SD.remove(name))
+                {
+                    azlog("Failed to remove firmware file");
+                }
+            }
+            else
+            {
+                azlog("Firmware update failed!");
+                azplatform_emergency_log_save();
+            }
+            
+        }
+    }
+    else
+    {
+        azlog("Bootloader SD card init failed");
+    }
+
+    azlog("Bootloader continuing to main firmware");
+    azplatform_boot_to_main_firmware();
+
+    return 0;
+}

+ 1 - 1
src/AzulSCSI_config.h

@@ -21,7 +21,7 @@
 #define HDIMG_ID_POS  2                 // Position to embed ID number
 #define HDIMG_LUN_POS 3                 // Position to embed LUN numbers
 #define HDIMG_BLK_POS 5                 // Position to embed block size numbers
-#define MAX_FILE_PATH 32                // Maximum file name length
+#define MAX_FILE_PATH 64                // Maximum file name length
 #define MAX_BLOCKSIZE 1024              // Maximum BLOCK size
 
 // Read buffer size

+ 13 - 2
src/AzulSCSI_log.cpp

@@ -3,15 +3,26 @@
 #include "AzulSCSI_platform.h"
 
 const char *g_azlog_firmwareversion = __DATE__ " " __TIME__;
+bool g_azlog_debug = true;
 
 // This memory buffer can be read by debugger and is also saved to azullog.txt
 #define LOGBUFMASK (LOGBUFSIZE - 1)
+
+// The log buffer is in special uninitialized RAM section so that it is not reset
+// when soft rebooting or jumping from bootloader.
+uint32_t g_log_magic;
 char g_logbuffer[LOGBUFSIZE + 1];
-uint32_t g_logpos = 0;
-bool g_azlog_debug = true;
+uint32_t g_logpos;
 
 void azlog_raw(const char *str)
 {
+    // Keep log from reboot / bootloader if magic matches expected value
+    if (g_log_magic != 0xAA55AA55)
+    {
+        g_log_magic = 0xAA55AA55;
+        g_logpos = 0;
+    }
+
     const char *p = str;
     while (*p)
     {

+ 22 - 0
src/AzulSCSI_main.cpp

@@ -0,0 +1,22 @@
+// Simple wrapper file that diverts boot from main program to bootloader
+// when building the bootloader image by build_bootloader.py.
+
+#ifdef AZULSCSI_BOOTLOADER_MAIN
+
+extern "C" int bootloader_main(void);
+
+int main(void)
+{
+    return bootloader_main();
+}
+
+#else
+
+extern "C" int azulscsi_main(void);
+
+int main(void)
+{
+    return azulscsi_main();
+}
+
+#endif

+ 59 - 0
src/build_bootloader.py

@@ -0,0 +1,59 @@
+# Adds a platformio/Scons target to build the bootloader image.
+# It is basically a copy of the main firmware but using AzulSCSI_bootloader.cpp
+# as the main() function.
+
+import os
+
+Import("env")
+
+# Build a version of AzulSCSI_main.cpp that calls bootloader instead
+env2 = env.Clone()
+env2.Append(CPPFLAGS = "-DAZULSCSI_BOOTLOADER_MAIN")
+bootloader_main = env2.Object(
+    os.path.join("$BUILD_DIR", "bootloader_main.o"),
+    ["AzulSCSI_main.cpp"]
+)
+
+# Include all other dependencies except AzulSCSI_main.cpp
+dep_objs = []
+for nodelist in env["PIOBUILDFILES"]:
+    for node in nodelist:
+        filename = str(node.rfile())
+        if 'AzulSCSI_main.o' not in filename:
+            dep_objs.append(node)
+print("Bootloader dependencies: ", type(dep_objs), str([str(f.rfile()) for f in dep_objs]))
+
+# Build bootloader.elf
+bootloader_elf = env2.Program(
+    os.path.join("$BUILD_DIR", "bootloader.elf"),
+    [bootloader_main] + dep_objs
+)
+
+# Strip bootloader symbols so that it can be combined with main program
+bootloader_bin = env.ElfToBin(
+    os.path.join("$BUILD_DIR", "bootloader.bin"),
+    bootloader_elf
+)
+
+# Convert back to .o to facilitate linking
+temp_src = env.subst(os.path.join("$BUILD_DIR", "bootloader_bin.cpp"))
+open(temp_src, 'w').write(
+'''
+__asm__(
+"   .section .text.btldr\\n"
+"btldr:\\n"
+"   .incbin \\"%s\\"\\n"
+);
+''' % bootloader_bin[0]
+)
+
+bootloader_obj = env.Object(
+    os.path.join("$BUILD_DIR", "bootloader_bin.o"),
+    temp_src
+)
+
+# Add bootloader binary as dependency to the main firmware
+main_fw = os.path.join("$BUILD_DIR", env.subst("$PROGNAME$PROGSUFFIX"))
+env.Depends(bootloader_obj, bootloader_bin)
+env.Depends(main_fw, bootloader_obj)
+env.Append(LINKFLAGS = [bootloader_obj])