diff --git a/ROMFS/px4fmu_common/init.d/rcS b/ROMFS/px4fmu_common/init.d/rcS index eb06764d4c..5f8cde30bd 100644 --- a/ROMFS/px4fmu_common/init.d/rcS +++ b/ROMFS/px4fmu_common/init.d/rcS @@ -150,6 +150,14 @@ fi if [ $USE_ALT_UPDATE_DIRS = yes ] then + # Check for new CAN-firmware directory + if [ -e /fs/microsd/ufw_staging_tmp ] + then + echo "Updating CAN firmware staging directory" + rm -r /fs/microsd/ufw_staging + mv /fs/microsd/ufw_staging_tmp /fs/microsd/ufw_staging + fi + # Check for an update of the ext_autostart folder, and replace the old one with it if [ -e /fs/microsd/ext_autostart_new ] then diff --git a/Tools/auterion/remote_update_fmu.sh b/Tools/auterion/remote_update_fmu.sh index ba181bc49e..0c9e53449d 100755 --- a/Tools/auterion/remote_update_fmu.sh +++ b/Tools/auterion/remote_update_fmu.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Flash PX4 to a device running AuterionOS in the local network if [ "$1" == "-h" ] || [ "$1" == "--help" ] || [ $# -lt 2 ]; then - echo "Usage: $0 -f [-c ] -d [-u ] [-p ] [--revert]" + echo "Usage: $0 -f [-c ] [-x ] -d [-u ] [-p ] [--revert]" exit 1 fi @@ -9,7 +9,7 @@ ssh_port=22 ssh_user=root ssh_opts="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" -while getopts ":f:c:d:p:u:r" opt; do +while getopts ":f:c:x:d:p:u:r" opt; do case ${opt} in f ) if [ -n "$OPTARG" ]; then @@ -27,6 +27,14 @@ while getopts ":f:c:d:p:u:r" opt; do exit 1 fi ;; + x ) + if [ -f "$OPTARG" ]; then + external_firmware_files+=("$OPTARG") + else + echo "ERROR: -x requires a valid firmware file path." + exit 1 + fi + ;; d ) if [ "$OPTARG" ]; then device="$OPTARG" @@ -74,12 +82,23 @@ else tmp_dir="$(mktemp -d)" config_path="" firmware_path="" + ufw_path="" if [ -d "$config_dir" ]; then cp -r "$config_dir" "$tmp_dir/config" config_path=config fi + if [ ${#external_firmware_files[@]} -gt 0 ]; then + mkdir -p "$tmp_dir/ufw" + for _ext_fw in "${external_firmware_files[@]}"; do + external_firmware_name="$(basename "$_ext_fw")" + cp "$_ext_fw" "$tmp_dir/ufw/$external_firmware_name" + echo "Including external firmware in update-dev.tar: ufw/$external_firmware_name" + done + ufw_path="ufw" + fi + if [ -f "$firmware_file" ]; then extension="${firmware_file##*.}" cp "$firmware_file" "$tmp_dir/firmware.$extension" @@ -92,7 +111,7 @@ else pushd "$tmp_dir" &>/dev/null - if [ -z $firmware_path ] && [ -z $config_path ]; then + if [ -z "$firmware_path" ] && [ -z "$config_path" ] && [ -z "$ufw_path" ]; then exit 1 fi @@ -103,7 +122,21 @@ else tar_name="gtar" fi - $tar_name -C "$tmp_dir" --sort=name --owner=root:0 --group=root:0 --mtime='2019-01-01 00:00:00' -cvf $target_file_name $firmware_path $config_path + tar_entries=() + if [ -n "$firmware_path" ]; then + tar_entries+=("$firmware_path") + fi + if [ -n "$config_path" ]; then + tar_entries+=("$config_path") + fi + if [ -n "$ufw_path" ]; then + tar_entries+=("$ufw_path") + fi + + if ! $tar_name -C "$tmp_dir" --sort=name --owner=root:0 --group=root:0 --mtime='2019-01-01 00:00:00' -cvf $target_file_name "${tar_entries[@]}"; then + echo "ERROR: Failed to create $target_file_name" + exit 1 + fi # send it to the target to start flashing scp $ssh_opts -P $ssh_port "$target_file_name" $ssh_user@"$device":$target_dir diff --git a/Tools/auterion/upload_skynode.sh b/Tools/auterion/upload_skynode.sh index 10d8740b42..3144e3aa30 100755 --- a/Tools/auterion/upload_skynode.sh +++ b/Tools/auterion/upload_skynode.sh @@ -4,6 +4,7 @@ DIR="$(dirname $(readlink -f $0))" DEFAULT_AUTOPILOT_HOST=10.41.1.1 DEFAULT_AUTOPILOT_PORT=33333 DEFAULT_AUTOPILOT_USER=auterion +EXTERNAL_FIRMWARE_FILES=() for i in "$@" do @@ -26,6 +27,9 @@ do --wifi) DEFAULT_AUTOPILOT_HOST=10.41.0.1 ;; + --ext-fw=*) + EXTERNAL_FIRMWARE_FILES+=("${i#*=}") + ;; *) # unknown option ;; @@ -42,6 +46,9 @@ ARGUMENTS+=(-d "$AUTOPILOT_HOST") ARGUMENTS+=(-p "$AUTOPILOT_PORT") ARGUMENTS+=(-u "$AUTOPILOT_USER") ARGUMENTS+=(${PX4_BINARY_FILE:+-f "$PX4_BINARY_FILE"}) +for _ext_fw in "${EXTERNAL_FIRMWARE_FILES[@]}"; do + ARGUMENTS+=(-x "$_ext_fw") +done ARGUMENTS+=($REVERT_AUTOPILOT_ARGUMENT) echo "Flashing $AUTOPILOT_HOST ..." diff --git a/src/drivers/uavcan/uavcan_drivers/posix/include/uavcan_posix/firmware_version_checker.hpp b/src/drivers/uavcan/uavcan_drivers/posix/include/uavcan_posix/firmware_version_checker.hpp index ddea2bbef5..ca72aaffb4 100644 --- a/src/drivers/uavcan/uavcan_drivers/posix/include/uavcan_posix/firmware_version_checker.hpp +++ b/src/drivers/uavcan/uavcan_drivers/posix/include/uavcan_posix/firmware_version_checker.hpp @@ -94,7 +94,7 @@ protected: * @return True - the class will begin sending update requests. * False - the node will be ignored, no request will be sent. */ - virtual bool shouldRequestFirmwareUpdate(uavcan::NodeID, + virtual bool shouldRequestFirmwareUpdate(uavcan::NodeID node_id, const uavcan::protocol::GetNodeInfo::Response &node_info, FirmwareFilePath &out_firmware_file_path) { @@ -174,6 +174,10 @@ protected: return true; } + static constexpr unsigned _MaxChunk = 512 / sizeof(uint64_t); + inline static uint64_t _chunk[_MaxChunk] + px4_cache_aligned_data() = {}; + public: struct AppDescriptor { uavcan::uint8_t signature[sizeof(uavcan::uint64_t)]; @@ -193,9 +197,6 @@ public: static int getFileInfo(const char *path, AppDescriptor &descriptor, int limit = 0) { using namespace std; - - const unsigned MaxChunk = 512 / sizeof(uint64_t); - // Make sure this does not present as a valid descriptor struct { union { @@ -206,14 +207,13 @@ public: } s; int rv = -ENOENT; - uint64_t chunk[MaxChunk]; int fd = open(path, O_RDONLY); if (fd >= 0) { AppDescriptor *pdescriptor = UAVCAN_NULLPTR; while (pdescriptor == UAVCAN_NULLPTR && limit >= 0) { - int len = read(fd, chunk, sizeof(chunk)); + int len = read(fd, _chunk, sizeof(_chunk)); if (len == 0) { break; @@ -224,10 +224,10 @@ public: goto out_close; } - uint64_t *p = &chunk[0]; + uint64_t *p = &_chunk[0]; if (limit > 0) { - limit -= sizeof(chunk); + limit -= sizeof(_chunk); } do { @@ -237,7 +237,7 @@ public: rv = 0; break; } - } while (p++ <= &chunk[MaxChunk - (sizeof(AppDescriptor) / sizeof(chunk[0]))]); + } while (p++ <= &_chunk[_MaxChunk - (sizeof(AppDescriptor) / sizeof(_chunk[0]))]); } out_close: diff --git a/src/drivers/uavcan/uavcan_module.hpp b/src/drivers/uavcan/uavcan_module.hpp index 0eadd821f9..232c78f3bb 100644 --- a/src/drivers/uavcan/uavcan_module.hpp +++ b/src/drivers/uavcan/uavcan_module.hpp @@ -51,6 +51,7 @@ #define UAVCAN_MAX_PATH_LENGTH (128 + 40) #define UAVCAN_SD_ROOT_PATH CONFIG_BOARD_ROOT_PATH "/" #define UAVCAN_FIRMWARE_PATH UAVCAN_SD_ROOT_PATH"ufw" +#define UAVCAN_SD_STAGING_PATH UAVCAN_SD_ROOT_PATH"ufw_staging/" #define UAVCAN_ROMFS_FW_PATH "/etc/uavcan/fw" #define UAVCAN_ROMFS_FW_PREFIX "_" diff --git a/src/drivers/uavcan/uavcan_servers.cpp b/src/drivers/uavcan/uavcan_servers.cpp index f808c01e73..05bcdc041c 100644 --- a/src/drivers/uavcan/uavcan_servers.cpp +++ b/src/drivers/uavcan/uavcan_servers.cpp @@ -56,6 +56,12 @@ #include #include +static constexpr size_t FW_DB_LINE_SIZE = 256; // key(~15) + '=' + filename(NAME_MAX) + '\n' +static const char FW_DB_PATH[] = UAVCAN_FIRMWARE_PATH "/FW.db"; +static const char FW_DB_TMP_PATH[] = UAVCAN_FIRMWARE_PATH "/FW.db.tmp"; +static uint8_t _buffer[512] +px4_cache_aligned_data() = {}; + /** * @file uavcan_servers.cpp * @@ -136,12 +142,20 @@ int UavcanServers::init() return ret; } + /* Check if all entries in the firmware database are still valid */ + validateFwDatabase(); + /* Check for firmware in the root directory, move it to appropriate location on the SD card, as defined by the APDesc. */ migrateFWFromRoot(UAVCAN_FIRMWARE_PATH, UAVCAN_SD_ROOT_PATH); + /* + Check for firmware in the staging directory. Using a staging dir avoids concurrent write conflicts. + */ + migrateFWFromRoot(UAVCAN_FIRMWARE_PATH, UAVCAN_SD_STAGING_PATH); + /* Start the Node */ return 0; } @@ -222,6 +236,7 @@ void UavcanServers::migrateFWFromRoot(const char *sd_path, const char *sd_root_p snprintf(dstpath, sizeof(dstpath), "%s/%d.bin", sd_path, descriptor.board_id); if (copyFw(dstpath, srcpath) >= 0) { + updateFwDatabase(dstpath, bin_names[i]); unlink(srcpath); } } @@ -230,7 +245,6 @@ void UavcanServers::migrateFWFromRoot(const char *sd_path, const char *sd_root_p int UavcanServers::copyFw(const char *dst, const char *src) { int rv = 0; - uint8_t buffer[512] {}; int dfd = open(dst, O_WRONLY | O_CREAT, 0666); @@ -250,7 +264,7 @@ int UavcanServers::copyFw(const char *dst, const char *src) ssize_t size = 0; do { - size = read(sfd, buffer, sizeof(buffer)); + size = read(sfd, _buffer, sizeof(_buffer)); if (size < 0) { PX4_ERR("copyFw: couldn't read"); @@ -263,7 +277,7 @@ int UavcanServers::copyFw(const char *dst, const char *src) ssize_t written = 0; do { - written = write(dfd, &buffer[total_written], remaining); + written = write(dfd, &_buffer[total_written], remaining); if (written < 0) { PX4_ERR("copyFw: couldn't write"); @@ -282,3 +296,112 @@ int UavcanServers::copyFw(const char *dst, const char *src) return rv; } + +void UavcanServers::updateFwDatabase(const char *fw_path, const char *original_filename) +{ + // DB key is the filename portion of fw_path, e.g. "12345.bin" + const char *last_slash = strrchr(fw_path, '/'); + const char *new_filename = last_slash ? (last_slash + 1) : fw_path; + + // The new/updated DB entry, e.g. "122.bin=122-1.17.63eeff1a.uavcan-dev.bin\n" + char new_entry[FW_DB_LINE_SIZE]; + snprintf(new_entry, sizeof(new_entry), "%s=%s\n", new_filename, original_filename); + + // Open the existing DB for reading (may not exist yet) and a temp file for writing. + FILE *db_in = fopen(FW_DB_PATH, "r"); + FILE *db_out = fopen(FW_DB_TMP_PATH, "w"); + + if (!db_out) { + if (db_in) { fclose(db_in); } + + PX4_WARN("updateFwDatabase: couldn't open tmp file"); + return; + } + + // Copy all existing entries to the temp file, replacing the matching key in-place. + bool line_updated = false; + char line[FW_DB_LINE_SIZE]; + + if (db_in) { + while (fgets(line, sizeof(line), db_in)) { + if (strncmp(line, new_filename, strlen(new_filename)) == 0 && line[strlen(new_filename)] == '=') { + fputs(new_entry, db_out); + line_updated = true; + + } else { + fputs(line, db_out); + } + } + + fclose(db_in); + } + + // Key was not present in the existing DB — append it as a new entry. + if (!line_updated) { + fputs(new_entry, db_out); + } + + fflush(db_out); + fsync(fileno(db_out)); + fclose(db_out); + + // Atomically replace the old DB with the updated version. + rename(FW_DB_TMP_PATH, FW_DB_PATH); +} + +void UavcanServers::validateFwDatabase() +{ + FILE *db_in = fopen(FW_DB_PATH, "r"); + + if (!db_in) { + return; // no DB yet, nothing to validate + } + + FILE *db_out = fopen(FW_DB_TMP_PATH, "w"); + + if (!db_out) { + fclose(db_in); + PX4_WARN("validateFwDatabase: couldn't open tmp file"); + return; + } + + bool changed = false; + char line[FW_DB_LINE_SIZE]; + + while (fgets(line, sizeof(line), db_in)) { + // Each valid line is "key=original_filename\n", e.g. "122.bin=122-1.17.abc.bin" + const char *eq = strchr(line, '='); + + if (eq == nullptr) { + changed = true; // malformed line — drop it + continue; + } + + // Build the full path to the firmware file to check if it still exists. + char full_path[UAVCAN_MAX_PATH_LENGTH + 1]; + snprintf(full_path, sizeof(full_path), "%s/%.*s", UAVCAN_FIRMWARE_PATH, + (int)(eq - line), line); + + struct stat sb; + + if (stat(full_path, &sb) == 0 && S_ISREG(sb.st_mode)) { + fputs(line, db_out); + + } else { + PX4_INFO("validateFwDatabase: removing stale entry for '%.*s'", (int)(eq - line), line); + changed = true; + } + } + + fclose(db_in); + fflush(db_out); + fsync(fileno(db_out)); + fclose(db_out); + + if (changed) { + rename(FW_DB_TMP_PATH, FW_DB_PATH); + + } else { + unlink(FW_DB_TMP_PATH); + } +} diff --git a/src/drivers/uavcan/uavcan_servers.hpp b/src/drivers/uavcan/uavcan_servers.hpp index ea2649ad67..4bb937bbd4 100644 --- a/src/drivers/uavcan/uavcan_servers.hpp +++ b/src/drivers/uavcan/uavcan_servers.hpp @@ -34,6 +34,7 @@ #pragma once #include +#include #include #include @@ -74,6 +75,8 @@ private: void unpackFwFromROMFS(const char *sd_path, const char *romfs_path); void migrateFWFromRoot(const char *sd_path, const char *sd_root_path); int copyFw(const char *dst, const char *src); + void updateFwDatabase(const char *fw_path, const char *original_filename); + void validateFwDatabase(); uavcan_posix::dynamic_node_id_server::FileEventTracer _tracer; uavcan_posix::dynamic_node_id_server::FileStorageBackend _storage_backend;