Переглянути джерело

Merge pull request #477 from ZuluSCSI/feature/common-file-update

Use single firmware bundle for updating all ZuluSCSI platforms
Alex Perez 11 місяців тому
батько
коміт
f9cc823869

+ 28 - 18
.github/workflows/firmware_build.yml

@@ -1,6 +1,6 @@
 name: Build ZuluSCSI firmware
 
-on: 
+on:
   push:
   workflow_dispatch:
   pull_request:
@@ -11,46 +11,56 @@ jobs:
 #    runs-on: self-hosted
     name: Build firmware on GitHub using latest Ubuntu
     runs-on: ubuntu-latest
-    
+
     steps:
       - name: Check out code from GitHub
         uses: actions/checkout@v4
         with:
           path: ZuluSCSI
           fetch-depth: "0"
-      
+
+      - name: Install dependencies
+        run: |
+          sudo apt-get update
+          sudo apt-get install zip
+
       - name: Install platformio
         run: |
           pip install -U platformio
-      
+
       - name: Build firmware
         run: |
           cd ZuluSCSI
           pio run -v -j8
-    
+
       - name: Rename firmware files
         run: |
           cd ZuluSCSI
           utils/rename_binaries.sh
 
+      - name: Create bin package zip file
+        run: |
+          cd ZuluSCSI
+          utils/create_bin_package_zip.sh
+
       - name: Upload binaries into build artifacts
         uses: actions/upload-artifact@v4
         with:
           path: ZuluSCSI/distrib/*
           name: ZuluSCSI binaries
-      
-      # - name: Upload to latest release
-      #   env:
-      #     GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-      #   if: github.ref == 'refs/heads/main'
-      #   run: |
-      #     cd ZuluSCSI
-      #     git tag -d latest
-      #     git tag latest
-      #     git push origin --force latest
-      #     cd distrib
-      #     gh api repos/${GITHUB_REPOSITORY}/releases/tags/latest | jq -r '.assets[] | [.url] | @tsv' | xargs -n 1 gh api -X DELETE || true
-      #     gh release upload --repo ${GITHUB_REPOSITORY} --clobber latest *
+
+      - name: Upload to latest release
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        if: github.ref == 'refs/heads/main'
+        run: |
+          cd ZuluSCSI
+          git tag -d latest
+          git tag latest
+          git push origin --force latest
+          cd distrib
+          gh api repos/${GITHUB_REPOSITORY}/releases/tags/latest | jq -r '.assets[] | [.url] | @tsv' | xargs -n 1 gh api -X DELETE || true
+          gh release upload --repo ${GITHUB_REPOSITORY} --clobber latest *
 
       - name: Upload to newly created release
         env:

+ 241 - 0
lib/ZipParser/zip_parser.cpp

@@ -0,0 +1,241 @@
+/**
+ * ZuluSCSI™ - Copyright (c) 2024 Rabbit Hole Computing™
+ *
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ *
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#include "zip_parser.h"
+
+#define ZIP_PARSER_METHOD_DEFLATE_BYTE 0x08
+#define ZIP_PARSER_METHOD_UNCOMPRESSED_BYTE 0x00
+
+namespace zipparser
+{
+
+    Parser::Parser()
+    {
+        filename_len = 0;
+        Reset();
+    }
+    Parser::Parser(char const *filename, const size_t length)
+    {
+        Reset();
+        SetMatchingFilename(filename, length);
+    }
+
+    void Parser::Reset()
+    {
+        target = parsing_target::signature;
+        position = 0;
+        filename_match = false;
+        crc = 0;
+    }
+
+    void Parser::SetMatchingFilename(char const *filename, const size_t length)
+    {
+        if (filename[0] == '\0')
+            filename_len = 0;
+        else
+        {
+            this->filename = filename;
+            filename_len = length;
+        }
+    }
+
+
+    int32_t Parser::Parse(uint8_t const *buf, const size_t size)
+    {
+        if (filename_len == 0)
+            return PARSE_ERROR;
+
+        static bool matching = true;
+        static bool central_dir = false;
+        static bool local_file_header = false;
+        for (size_t idx = 0; idx < size; idx++)
+        {
+            switch (target)
+            {
+                case parsing_target::signature:
+                    if (++position == 1 && buf[idx] == 'P')
+                        break;
+                    if (position == 2 && buf[idx] == 'K')
+                    {
+                        local_file_header = false;
+                        central_dir = false;
+                        break;
+                    }
+                    if (position == 3 && buf[idx] == 0x03)
+                    {
+                        local_file_header = true;
+                        break;
+                    }
+                    if (position == 3 && buf[idx] == 0x01)
+                    {
+                        central_dir = true;
+                        break;
+                    }
+                    if (central_dir && position == 4 && buf[idx] == 0x2)
+                    {
+                        return PARSE_CENTRAL_DIR;
+                    }
+                    if (local_file_header &&  position == 4 && buf[idx] == 0x04)
+                    {
+                        position = 0;
+                        target = parsing_target::version;
+                        break;
+                    }
+                    return PARSE_ERROR;
+                break;
+                case parsing_target::version:
+                    if (++position == 2)
+                    {
+                        position = 0;
+                        target = parsing_target::flag;
+                    }
+                break;
+                case parsing_target::flag:
+                    if (++position == 2)
+                    {
+                        position = 0;
+                        target = parsing_target::method;
+                    }
+                break;
+                case parsing_target::method:
+                    if (++position == 1)
+                    {
+                        // Currently only uncompresseed files in the zip package are supported
+                        if (!buf[idx] == ZIP_PARSER_METHOD_UNCOMPRESSED_BYTE)
+                        {
+                            return PARSE_UNSUPPORTED_COMPRESSION;
+                        }
+                    }
+                    if (position == 2)
+                    {
+                        if (buf[idx] == 0)
+                        {
+                            position = 0;
+                            target = parsing_target::modify_time;
+                        }
+                        else
+                            return PARSE_UNSUPPORTED_COMPRESSION;
+                    }
+                break;
+                case parsing_target::modify_time:
+                    if (++position == 2)
+                    {
+                        position = 0;
+                        target = parsing_target::modify_date;
+                    }
+                break;
+                case parsing_target::modify_date:
+                    if (++position == 2)
+                    {
+                        position = 0;
+                        target = parsing_target::crc;
+                        crc = 0;
+                    }
+                break;
+                case parsing_target::crc:
+                    crc |= buf[idx] << (8 * position++);
+                    if (position == 4)
+                    {
+                        target = parsing_target::size_compressed;
+                        compressed_data_size = 0;
+                        position = 0;
+                    }
+                break;
+                case parsing_target::size_compressed:
+                    compressed_data_size |= buf[idx] << (8 * position++);
+                    if (position == 4)
+                    {
+                        target = parsing_target::size_uncompressed;
+                        uncompressed_data_size = 0;
+                        position = 0;
+                    }
+                break;
+                case parsing_target::size_uncompressed:
+                    uncompressed_data_size |= buf[idx] << (8 * position++);
+                    if (position == 4)
+                    {
+                        target = parsing_target::filename_len;
+                        current_zip_filename_len = 0;
+                        position = 0;
+                    }
+                break;
+                case parsing_target::filename_len:
+                    current_zip_filename_len |= buf[idx] << (8 * position++);
+                    if (position == 2)
+                    {
+                        target = parsing_target::extra_field_len;
+                        extra_field_len = 0;
+                        position = 0;
+                    }
+                break;
+                case parsing_target::extra_field_len:
+                    extra_field_len |= buf[idx] << (8 * position++);
+                    if (position == 2)
+                    {
+                        target = parsing_target::filename;
+                        position = 0;
+                        filename_match = false;
+                        matching = true;
+                    }
+                break;
+                case parsing_target::filename:
+                    if (position <= current_zip_filename_len - 1)
+                    {    
+                        if (matching && position < filename_len && filename[position] != buf[idx])
+                            matching = false;
+                        if (position == filename_len - 1 && matching)
+                            filename_match = true;
+                        if (position == current_zip_filename_len -1)
+                        {
+                            target = parsing_target::extra_field;
+                            matching = true;
+                            position = 0;
+                            if (extra_field_len == 0)
+                            {
+                                target = parsing_target::end;
+                                return idx + 1;
+                            }
+                        }
+                        else
+                            position++;
+                    }
+                break;
+                case parsing_target::extra_field:
+                    // extra_field_len should be at least 1 by this time
+                    if (++position == extra_field_len)
+                    {
+                        target = parsing_target::end;
+                        return idx + 1;
+                    }
+                break;
+                case parsing_target::end:
+                    return 0;
+                break;
+            }
+        }
+        return size;
+    }
+    bool Parser::FoundMatch()
+    {
+        return filename_match;
+    }
+}
+

+ 63 - 0
lib/ZipParser/zip_parser.h

@@ -0,0 +1,63 @@
+/**
+ * ZuluSCSI™ - Copyright (c) 2024 Rabbit Hole Computing™
+ *
+ * ZuluSCSI™ firmware is licensed under the GPL version 3 or any later version. 
+ *
+ * https://www.gnu.org/licenses/gpl-3.0.html
+ * ----
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version. 
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details. 
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+**/
+
+#pragma once
+
+#include <stdint.h>
+#include <strings.h>
+namespace zipparser
+{
+    enum class parsing_target {signature, version, flag, method, modify_time, modify_date, 
+                                crc, size_compressed, size_uncompressed, filename_len, 
+                                extra_field_len, filename, extra_field, end};
+
+    class Parser
+    {
+        public:
+            Parser();
+            Parser(char const *filename, const size_t length);
+            void SetMatchingFilename(char const *filename, const size_t length);
+            void Reset();
+            static const int32_t PARSE_ERROR = -1;
+            static const int32_t PARSE_CENTRAL_DIR = -2;
+            static const int32_t PARSE_UNSUPPORTED_COMPRESSION = -3;
+            // A state machine that per byte processes the incoming buffer
+            // \param buf a pointer to binary data to be processed
+            // \param size of data to be processed, can by 1 for a single byte at a time
+            // \returns the number of bytes processed or -1 if an error ocurred
+            int32_t Parse(uint8_t const *buf, const size_t size);
+            bool FoundMatch();
+            inline uint32_t GetCompressedSize() {return compressed_data_size;}
+
+        protected:
+            bool filename_match;
+            char const *filename;
+            size_t filename_len;
+            size_t current_zip_filename_len;
+            size_t extra_field_len;
+            uint32_t compressed_data_size;
+            uint32_t uncompressed_data_size;
+            parsing_target target;
+            size_t position;
+            uint32_t crc;
+
+    };
+}

+ 10 - 0
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.cpp

@@ -22,6 +22,7 @@
 #include "ZuluSCSI_platform.h"
 #include "gd32f20x_sdio.h"
 #include "gd32f20x_fmc.h"
+#include "gd32f20x_fwdgt.h"
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_config.h"
 #include "usbd_conf.h"
@@ -396,6 +397,7 @@ static bool get_direct_mode(uint32_t port, uint32_t pin, const char *switch_name
 
 void platform_late_init()
 {
+
     // Initialize usb for CDC serial output
     usb_serial_init();
     g_usb_log_poll_func = &usb_log_poll;
@@ -721,6 +723,14 @@ void platform_reset_watchdog()
     usb_log_poll();
 }
 
+void platform_reset_mcu()
+{
+    // reset in 2 sec ( 1 / (40KHz / 32) * 2500 == 2sec)
+    fwdgt_config(2500, FWDGT_PSC_DIV32);
+    fwdgt_enable();
+
+}
+
 // Poll function that is called every few milliseconds.
 // Can be left empty or used for platform-specific processing.
 void platform_poll()

+ 3 - 0
lib/ZuluSCSI_platform_GD32F205/ZuluSCSI_platform.h

@@ -90,6 +90,9 @@ void platform_disable_led(void);
 // Setup soft watchdog
 void platform_reset_watchdog();
 
+// Reset MCU after a certain amount of time
+void platform_reset_mcu();
+
 // Poll function that is called every few milliseconds.
 // The SD card is free to access during this time, and pauses up to
 // few milliseconds shouldn't disturb SCSI communication.

+ 9 - 0
lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.cpp

@@ -22,6 +22,7 @@
 #include "ZuluSCSI_platform.h"
 #include "gd32f4xx_sdio.h"
 #include "gd32f4xx_fmc.h"
+#include "gd32f4xx_fwdgt.h"
 #include "ZuluSCSI_log.h"
 #include "ZuluSCSI_config.h"
 #include "usb_hs.h"
@@ -511,6 +512,14 @@ void platform_reset_watchdog()
     g_watchdog_timeout = WATCHDOG_CRASH_TIMEOUT;
 }
 
+void platform_reset_mcu()
+{
+    // reset in 2 sec ( 1 / (32KHz / 32) * 2000 == 2sec)
+    fwdgt_config(2000, FWDGT_PSC_DIV32);
+    fwdgt_enable();
+
+}
+
 // Poll function that is called every few milliseconds.
 // Can be left empty or used for platform-specific processing.
 void platform_poll()

+ 3 - 0
lib/ZuluSCSI_platform_GD32F450/ZuluSCSI_platform.h

@@ -82,6 +82,9 @@ void platform_disable_led(void);
 // Setup soft watchdog
 void platform_reset_watchdog();
 
+// Reset MCU
+void platform_reset_mcu();
+
 // Poll function that is called every few milliseconds.
 // The SD card is free to access during this time, and pauses up to
 // few milliseconds shouldn't disturb SCSI communication.

+ 5 - 0
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform.cpp

@@ -863,6 +863,11 @@ void platform_poll()
 #endif // ENABLE_AUDIO_OUTPUT
 }
 
+void platform_reset_mcu()
+{
+    watchdog_reboot(0, 0, 2000);
+}
+
 uint8_t platform_get_buttons()
 {
     uint8_t buttons = 0;

+ 4 - 0
lib/ZuluSCSI_platform_RP2MCU/ZuluSCSI_platform.h

@@ -99,6 +99,10 @@ bool platform_is_initiator_mode_enabled();
 // Setup soft watchdog if supported
 void platform_reset_watchdog();
 
+// Reset MCU
+void platform_reset_mcu();
+
+
 // Poll function that is called every few milliseconds.
 // The SD card is free to access during this time, and pauses up to
 // few milliseconds shouldn't disturb SCSI communication.

+ 12 - 1
platformio.ini

@@ -3,12 +3,17 @@
 [platformio]
 default_envs = ZuluSCSIv1_0, ZuluSCSIv1_0_mini, ZuluSCSIv1_1_plus, ZuluSCSI_RP2040, ZuluSCSI_RP2040_Audio, ZuluSCSI_Pico, ZuluSCSI_Pico_DaynaPORT, ZuluSCSI_BS2, ZuluSCSI_Pico_2, ZuluSCSI_Pico_2_DaynaPORT
 
+[env]
+build_flags =
+    -DBUILD_ENV=$PIOENV
+
 ; Example platform to serve as a base for porting efforts
 [env:template]
 platform = ststm32
 framework = arduino
 board = bluepill_f103c8
 build_flags =
+    ${env.build_flags}
     -Os -Isrc
     -DLOGBUFSIZE=512
     -DPREFETCH_BUFFER_SIZE=0
@@ -49,7 +54,9 @@ extra_scripts = src/build_bootloader.py
 debug_build_flags = 
      -Os -Wall -Wno-sign-compare -ggdb -g3
 build_flags = 
+    ${env.build_flags}
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
+     -DBUILD_ENV=$PIOENV
      -D__SYSTEM_CLOCK_120M_PLL_IRC8M=120000000
      -DSPI_DRIVER_SELECT=3
      -DSD_CHIP_SELECT_MODE=2
@@ -64,6 +71,7 @@ build_flags =
 [env:ZuluSCSIv1_0_mini]
 extends = env:ZuluSCSIv1_0
 build_flags = 
+    ${env.build_flags}
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
      -D__SYSTEM_CLOCK_120M_PLL_IRC8M=120000000
      -DSPI_DRIVER_SELECT=3
@@ -79,7 +87,8 @@ build_flags =
 ; ZuluSCSI V1.1+ hardware platforms, this support v1.1, v1.1 ODE, and vl.2
 [env:ZuluSCSIv1_1_plus]
 extends = env:ZuluSCSIv1_0
-build_flags = 
+build_flags =
+    ${env.build_flags}
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
      -D__SYSTEM_CLOCK_120M_PLL_IRC8M=120000000
      -DSPI_DRIVER_SELECT=3
@@ -115,6 +124,7 @@ debug_tool = cmsis-dap
 debug_build_flags =
     -O2 -ggdb -g3
 build_flags =
+    ${env.build_flags}
     -O2 -Isrc -ggdb -g3
     -Wall -Wno-sign-compare -Wno-ignored-qualifiers
     -DSPI_DRIVER_SELECT=3
@@ -275,6 +285,7 @@ extra_scripts = src/build_bootloader.py
 debug_tool = stlink
 debug_build_flags = -Os -ggdb -g3
 build_flags = 
+    ${env.build_flags}
      -Os -Wall -Wno-sign-compare -ggdb -g3 -Isrc
      -D__SYSTEM_CLOCK_200M_PLL_IRC16M=200000000
      -DSPI_DRIVER_SELECT=3

+ 111 - 2
src/ZuluSCSI.cpp

@@ -50,6 +50,7 @@
 #include <string.h>
 #include <strings.h>
 #include <ctype.h>
+#include <zip_parser.h>
 #include "ZuluSCSI_config.h"
 #include "ZuluSCSI_platform.h"
 #include "ZuluSCSI_log.h"
@@ -816,6 +817,112 @@ static void reinitSCSI()
 #endif // ZULUSCSI_NETWORK
 
 }
+
+// Update firmware by unzipping the firmware package
+static void firmware_update()
+{
+  const char firmware_prefix[] = FIRMWARE_PREFIX;
+  FsFile root = SD.open("/");
+  FsFile file;
+  char name[MAX_FILE_PATH + 1];
+  while (1)
+  {
+    if (!file.openNext(&root, O_RDONLY))
+    {
+      file.close();
+      root.close();
+      return;
+    }
+    if (file.isDir())
+      continue;
+
+    file.getName(name, sizeof(name));
+    if (strlen(name) + 1 < sizeof(firmware_prefix))
+      continue;
+    if ( strncasecmp(firmware_prefix, name, sizeof(firmware_prefix) -1) == 0)
+    {
+      break;
+    }
+  }
+
+  logmsg("Found firmware package ", name);
+
+  zipparser::Parser parser = zipparser::Parser(FIRMWARE_NAME_PREFIX, sizeof(FIRMWARE_NAME_PREFIX) - 1);
+  uint8_t buf[512];
+  int32_t parsed_length;
+  int bytes_read = 0;
+  while ((bytes_read = file.read(buf, sizeof(buf))) > 0)
+  {
+    parsed_length = parser.Parse(buf, bytes_read);
+    if (parsed_length == sizeof(buf))
+       continue;
+    if (parsed_length >= 0)
+    {
+      if (!parser.FoundMatch())
+      {
+        parser.Reset();
+        file.seekSet(file.position() - (sizeof(buf) - parsed_length) + parser.GetCompressedSize());
+      }
+      else
+      {
+        // seek to start of compressed data in matching file
+        file.seekSet(file.position() - (sizeof(buf) - parsed_length));
+        break;
+      }
+    }
+    if (parsed_length < 0)
+    {
+      file.close();
+      root.close();
+      return;
+    }
+  }
+
+
+  if (parser.FoundMatch())
+  {
+
+    logmsg("Unzipping matching firmware with prefix: ", FIRMWARE_NAME_PREFIX);
+    FsFile target_firmware;
+    target_firmware.open(&root, "ZuluSCSI.bin", O_BINARY | O_WRONLY | O_CREAT | O_TRUNC);
+    uint32_t position = 0;
+    while ((bytes_read = file.read(buf, sizeof(buf))) > 0)
+    {
+      if (bytes_read > parser.GetCompressedSize() - position)
+        bytes_read =  parser.GetCompressedSize() - position;
+      target_firmware.write(buf, bytes_read);
+      position += bytes_read;
+      if (position >= parser.GetCompressedSize())
+      {
+        break;
+      }
+    }
+    // zip file has a central directory at the end of the file,
+    // so the compressed data should never hit the end of the file
+    // so bytes read should always be greater than 0 for a valid datastream
+    if (bytes_read > 0)
+    {
+      target_firmware.close();
+      file.close();
+      root.remove(name);
+      root.close();
+      logmsg("Update extracted from package, rebooting MCU");
+      platform_reset_mcu();
+    }
+    else
+    {
+      target_firmware.close();
+      logmsg("Error reading firmware package file");
+      root.remove("ZuluSCSI.bin");
+    }
+  }
+  else
+    logmsg("Updater did not find matching file in package: ", name);
+  file.close();
+  root.close();
+}
+
+
 // Place all the setup code that requires the SD card to be initialized here
 // Which is pretty much everything after platform_init and and platform_late_init
 static void zuluscsi_setup_sd_card()
@@ -847,8 +954,10 @@ static void zuluscsi_setup_sd_card()
     } while (!g_sdcard_present);
     logmsg("SD card init succeeded after retry");
   }
-  
-  static const char sg_default[] = "Default"; 
+
+  firmware_update();
+
+  static const char sg_default[] = "Default";
   if (g_sdcard_present)
   {
     char speed_grade_str[10];

+ 5 - 0
src/ZuluSCSI_config.h

@@ -31,6 +31,10 @@
 #define FW_VER_NUM      "24.12.02"
 #define FW_VER_SUFFIX   "devel"
 
+#define DEF_STRINGFY(DEF) STRINGFY(DEF)
+#define STRINGFY(STR) #STR
+#define FIRMWARE_NAME_PREFIX DEF_STRINGFY(BUILD_ENV)
+
 #define ZULU_FW_VERSION FW_VER_NUM "-" FW_VER_SUFFIX
 #define INQUIRY_NAME  PLATFORM_NAME " v" ZULU_FW_VERSION
 #define TOOLBOX_API 0
@@ -39,6 +43,7 @@
 #define CONFIGFILE  "zuluscsi.ini"
 #define LOGFILE     "zululog.txt"
 #define CRASHFILE   "zuluerr.txt"
+#define FIRMWARE_PREFIX "ZuluSCSI-FW"
 
 // Prefix for command file to create new image (case-insensitive)
 #define CREATEFILE "create"

+ 30 - 0
utils/create_bin_package_zip.sh

@@ -0,0 +1,30 @@
+#!/bin/bash
+
+# ZuluSCSI™ - Copyright (c) 2024 Rabbit Hole Computing™
+#
+# ZuluSCSI™ file is licensed under the GPL version 3 or any later version. 
+#
+# https://www.gnu.org/licenses/gpl-3.0.html
+# ----
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version. 
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details. 
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+
+# This script renames the built binaries according to version
+# number and platform.
+
+cd distrib
+DATE=$(date +%Y-%m-%d)
+VERSION=$(git describe --always)
+FWPACKAGE=ZuluSCSI-FW_"$DATE"_"$VERSION".zip
+zip -0 "$FWPACKAGE" *.bin