mirror of
https://github.com/PX4/PX4-Autopilot.git
synced 2026-05-21 04:33:10 +08:00
feat(uavcan): flash firmware onto CAN nodes from SD card (#27043)
Place firmware .bin files at the SD card root or staging directory (/fs/microsd/ufw_staging/); on boot the UAVCAN server migrates them to /fs/microsd/ufw/ and updates FW.db, then flashes any connected node whose firmware version mismatches. - Add firmware migration from SD root and staging dir into /fs/microsd/ufw/ - Maintain FW.db flat-file database mapping board IDs to original filenames - Use cache-aligned DMA-safe read/write buffers (required on STM32H7) - Add Tools/auterion/remote_update_fmu.sh for SSH-based FMU+canio updates
This commit is contained in:
committed by
GitHub
parent
11fa1dae1e
commit
aa7c66e0a7
@@ -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
|
||||
|
||||
@@ -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 <firmware.px4|.elf> [-c <configuration_dir>] -d <IP/Device> [-u <user>] [-p <ssh_port>] [--revert]"
|
||||
echo "Usage: $0 -f <firmware.px4|.elf> [-c <configuration_dir>] [-x <canio_firmware.bin>] -d <IP/Device> [-u <user>] [-p <ssh_port>] [--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
|
||||
|
||||
@@ -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 ..."
|
||||
|
||||
+9
-9
@@ -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:
|
||||
|
||||
@@ -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 "_"
|
||||
|
||||
|
||||
@@ -56,6 +56,12 @@
|
||||
#include <uavcan_posix/dynamic_node_id_server/file_storage_backend.hpp>
|
||||
#include <uavcan_posix/firmware_version_checker.hpp>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <px4_platform_common/px4_config.h>
|
||||
#include <drivers/drv_hrt.h>
|
||||
|
||||
#include <uavcan/protocol/dynamic_node_id_server/centralized.hpp>
|
||||
#include <uavcan/protocol/file_server.hpp>
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user