Files
wlan-ap/feeds/mcu/mcu/files/mcu.sh
Piotr Dymacz 560e9f326d mcu: fix host support for multi-slot UART based MCUs
In case of MCU with multiple firmware slots support, change of active
slot requires reset. This obviously results in MCU entering the serial
recovery mode in bootloader, with 5 sec timeout, which in case of UART
based MCUs isn't automatically detected and handled in the same way as
USB based devices (hotplug).

Starting host side support script when the MCU is waiting for MCUmgr
commands during recovery is wrong. This fixes the problem by requesting
UART based MCU to boot the firmware after active slot change followed by
reset. While at it, change also how single slot type MCUs are handled
during upgrade (always request reset after the upgrade).

Signed-off-by: Piotr Dymacz <pepe2k@gmail.com>
2023-03-23 08:06:42 +01:00

502 lines
10 KiB
Bash

#!/bin/sh
. /usr/share/libubox/jshn.sh
# Product name and VID:PID used by OpenWrt/OpenWiFi MCUboot fork
MCUBOOT_USB_PRODUCT="MCUboot serial recovery"
MCUBOOT_USB_VID_PID="16c005e1"
# Host support and firmware directories
MCU_HS_DIR="/etc/mcu.d"
MCU_FW_DIR="/lib/firmware/mcu"
MCU_FLOCK_FILE="/tmp/lock/mcu"
MCU_SYSINFO_OUTPUT=
MCU_IMGLIST_OUTPUT=
MCU_SCRIPT_NAME=""
# logger helpers
mcu_log() {
if [ -n "$MCU_SCRIPT_NAME" ]; then
logger -p "$1" -t "${MCU_SCRIPT_NAME}[$$]" "$2"
else
logger -p "$1" "$2"
fi
}
mcu_loge() {
mcu_log "err" "$1"
}
mcu_logd() {
mcu_log "debug" "$1"
}
mcu_logi() {
mcu_log "info" "$1"
}
mcu_logn() {
mcu_log "notice" "$1"
}
mcu_logw() {
mcu_log "warn" "$1"
}
mcu_fetch_fwlist() {
local uart="$1"
local baud="$2"
local flow="$3"
if [ "$flow" = "1" ]; then
flow=" -f"
else
flow=""
fi
[ -n "$baud" ] || baud="115200"
[ -n "$MCU_IMGLIST_OUTPUT" ] && return 0
MCU_IMGLIST_OUTPUT="$(umcumgr -s -d "$uart" -b "$baud$flow" list)"
[ $? -eq 0 ] || {
mcu_loge "request 'list' failed (uart='$uart', baud='$baud', flow='$flow')"
return 1
}
}
mcu_fetch_sysinfo() {
local uart="$1"
local baud="$2"
local flow="$3"
if [ "$flow" = "1" ]; then
flow=" -f"
else
flow=""
fi
[ -n "$baud" ] || baud="115200"
[ -n "$MCU_SYSINFO_OUTPUT" ] && return 0
MCU_SYSINFO_OUTPUT="$(umcumgr -s -d "$uart" -b "$baud$flow" sysinfo)"
[ $? -eq 0 ] || {
mcu_loge "request 'sysinfo' failed (uart='$uart', baud='$baud', flow='$flow')"
return 1
}
}
mcu_get() {
local param="$1"
local slot="$2"
local value
local metadata
local sysinfo_field
case "$param" in
"board"|\
"soft_ver"|\
"serial_num"|\
"active_slot")
sysinfo_field="$param"
;;
"slots_num")
sysinfo_field="single_slot"
;;
"fwname")
[ -n "$slot" ] || return 1
param="image list: slot${slot} fw_name"
metadata="$(echo "$MCU_IMGLIST_OUTPUT" | grep "slot${slot}_metadata=" | cut -d '=' -f 2)"
[ -n "$metadata" ] && {
json_load "$metadata"
json_get_var value fw_name
}
;;
"fwsha")
[ -n "$slot" ] || return 1
param="image list: slot${slot}_hash"
value="$(echo "$MCU_IMGLIST_OUTPUT" | grep "slot${slot}_hash=" | cut -d '=' -f 2)"
;;
*)
return 1
;;
esac
[ -n "$sysinfo_field" ] && {
value="$(echo "$MCU_SYSINFO_OUTPUT" | grep "${sysinfo_field}=" | cut -d '=' -f 2)"
[ "$sysinfo_field" = "single_slot" ] && {
[ -n "$value" ] || value="1"
[ "$value" != "1" ] && value="2"
}
param="sysinfo: $param"
}
[ -n "$value" ] && mcu_logd "$param: '$value'"
echo "$value"
}
mcu_req() {
local cmd="$1"
local uart="$2"
local baud="$3"
local flow="$4"
case "$cmd" in
"boot"|\
"reset")
;;
*)
return 1
;;
esac
if [ "$flow" = "1" ]; then
flow=" -f"
else
flow=""
fi
[ -n "$baud" ] || baud="115200"
umcumgr -s -d "$uart" -b "$baud$flow" "$cmd" || {
mcu_loge "request '$cmd' failed (uart='$uart', baud='$baud', flow='$flow')"
return 1
}
mcu_logi "MCU requested '$cmd'"
}
mcu_sel_slot() {
local slot="$1"
local uart="$2"
local baud="$3"
local flow="$4"
if [ "$flow" = "1" ]; then
flow=" -f"
else
flow=""
fi
[ -n "$baud" ] || baud="115200"
# Request firmware active slot change
umcumgr -s -d "$uart" -b "$baud$flow" select "$slot" || {
mcu_loge "request 'select slot' failed (uart='$uart', baud='$baud', flow='$flow')"
return 1
}
mcu_logi "active firmware slot changed to: '$slot'"
}
mcu_fwfile_sha() {
local path="$1"
local value
[ -f "$path" ] || return 1
value="$(umcumgr -s hash "$path" | grep "hash=" | cut -d '=' -f 2)"
[ -n "$value" ] || return 1
echo "$value"
}
mcu_fw_upload() {
local board="$1"
local slot="$2"
local fw_name="$3"
local uart="$4"
local baud="$5"
local flow="$6"
local fw_path
if [ "$flow" = "1" ]; then
flow=" -f"
else
flow=""
fi
[ -n "$baud" ] || baud="115200"
fw_path="${MCU_FW_DIR}/${board}/${fw_name}/slot${slot}.bin"
umcumgr -q info "$fw_path" > /dev/null 2>&1 || {
mcu_loge "invalid or missing firmware file: '$fw_path'"
return 1
}
mcu_logi "uploading '$fw_name' to slot: '$slot'..."
# Upload fw to selected slot (TODO: slots numbering Zephyr vs. MCUboot)
[ "$slot" = "1" ] && slot="2"
umcumgr -q -n "$slot" -d "$uart" -b "$baud$flow" upload "$fw_path" || {
mcu_loge "request 'upload' failed (uart='$uart', baud='$baud', flow='$flow')"
return 1
}
mcu_logi "firmware uploaded!"
}
mcu_enable_pin_set() {
local gpio="$1"
local gpio_value="$2"
mcu_logd "setting MCU enable_pin '$(basename "$gpio")' to '$gpio_value'"
echo "$gpio_value" > "${gpio}/value" 2>/dev/null
}
mcu_sn_check_and_update() {
local sn="$1"
local uart="$2"
local baud="$3"
local flow="$4"
local sn_dev
# Fetch sysinfo
mcu_fetch_sysinfo "$uart" "$baud" "$flow" || return 1
sn_dev="$(mcu_get "serial_num")"
[ -n "$sn_dev" ] || return 1
[ -n "$sn_dev" ] && {
if [ -z "$sn" ]; then
[ -d /etc/config-shadow ] && {
uci -c /etc/config-shadow -q set mcu.${SECT}.sn="$sn_dev"
uci -c /etc/config-shadow -q commit mcu
}
uci -q set mcu.${SECT}.sn="$sn_dev"
uci -q commit mcu
else
[ "$sn" != "$sn_dev" ] && {
mcu_loge "MCU S/N mismatch ('$sn_dev' != '$sn')!"
return 1
}
fi
}
return 0
}
# Returns:
# 0 if MCU was requested to boot firmware
# 1 on error
# 2 if MCU was requested to reset
mcu_fw_check_and_update() {
local uart="$1"
local baud="$2"
local flow="$3"
local active_slot
local fw_slots
local slot0_fw
local slot0_sha
local slot1_fw
local slot1_sha
local firmware
local fw0_sha
local fw1_sha
local board
local soft_ver
config_get firmware "$SECT" firmware
[ -n "$firmware" ] || mcu_logw "option 'firmware' unset"
# Fetch sysinfo and firmware images list
mcu_fetch_sysinfo "$uart" "$baud" "$flow" || return 1
mcu_fetch_fwlist "$uart" "$baud" "$flow" || return 1
# MCU board name and software version
board="$(mcu_get "board")"
[ $? -eq 0 ] || return 1
soft_ver="$(mcu_get "soft_ver")"
[ $? -eq 0 ] || return 1
# Number of firmware slots and active slot
fw_slots="$(mcu_get "slots_num")"
[ $? -eq 0 ] || return 1
[ -n "$fw_slots" ] || fw_slots="1"
[ "$fw_slots" = "2" ] && {
active_slot="$(mcu_get "active_slot")"
[ $? -eq 0 ] || return 1
}
[ -n "$active_slot" ] || active_slot="0"
# Firmware available?
[ -n "$firmware" ] && {
if [ "$fw_slots" = "2" ]; then
[ -f "${MCU_FW_DIR}/${board}/${firmware}/slot0.bin" -a \
-f "${MCU_FW_DIR}/${board}/${firmware}/slot1.bin" ] || {
mcu_loge "firmware '$firmware' doesn't exist"
return 1
}
fw1_sha="$(mcu_fwfile_sha "${MCU_FW_DIR}/${board}/${firmware}/slot1.bin")"
else
[ -f "${MCU_FW_DIR}/${board}/${firmware}/slot0.bin" ] || {
mcu_loge "firmware '$firmware' doesn't exist"
return 1
}
fi
fw0_sha="$(mcu_fwfile_sha "${MCU_FW_DIR}/${board}/${firmware}/slot0.bin")"
}
slot0_fw="$(mcu_get "fwname" "0")"
[ $? -eq 0 ] || return 1
[ -n "$slot0_fw" ] && {
slot0_sha="$(mcu_get "fwsha" "0")"
[ $? -eq 0 ] || return 1
}
[ "$fw_slots" = "2" ] && {
slot1_fw="$(mcu_get "fwname" "1")"
[ $? -eq 0 ] || return 1
[ -n "$slot1_fw" ] && {
slot1_sha="$(mcu_get "fwsha" "1")"
[ $? -eq 0 ] || return 1
}
}
# No target firmware provided, check what's on device and update config
[ -n "$firmware" ] || {
firmware="$slot0_fw"
[ "$active_slot" = "1" ] && firmware="$slot1_fw"
[ -n "$firmware" ] && {
[ -d /etc/config-shadow ] && {
uci -c /etc/config-shadow -q set mcu.${SECT}.firmware="$firmware"
uci -c /etc/config-shadow -q commit mcu
}
uci -q set mcu.${SECT}.firmware="$firmware"
uci -q commit mcu
mcu_req "boot" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
return 0
}
return 1
}
# Do we have target firmware installed in the first slot?
[ "$firmware" = "$slot0_fw" -a "$slot0_sha" = "$fw0_sha" ] && {
mcu_logi "found matching firmware installed in slot '0'"
if [ "$fw_slots" = "2" -a "$active_slot" != "0" ]; then
mcu_sel_slot "0" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
# Changing active slots requires MCU reset at the moment
mcu_req "reset" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
return 2
else
mcu_req "boot" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
fi
return 0
}
mcu_logi "no matching firmware found in slot '0'"
# Upload firmware and reset on single-slot device
[ "$fw_slots" = "1" ] && {
mcu_fw_upload "$board" "0" "$firmware" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
mcu_req "reset" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
return 2
}
# Do we have target firmware installed in the second slot?
[ "$firmware" = "$slot1_fw" -a "$slot1_sha" = "$fw1_sha" ] && {
mcu_logi "found matching firmware installed in slot '1'"
if [ "$active_slot" != "1" ]; then
mcu_sel_slot "1" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
# Changing active slots requires MCU reset at the moment
mcu_req "reset" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
return 2
else
mcu_req "boot" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
fi
return 0
}
mcu_logi "no matching firmware found in slot '1'"
# Upload and boot firmware on multi-slot device
# Always use inactive slot
if [ "$active_slot" = "0" ]; then
active_slot="1"
else
active_slot="0"
fi
mcu_fw_upload "$board" "$active_slot" "$firmware" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
mcu_sel_slot "$active_slot" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
# Changing active slots requires MCU reset at the moment
mcu_req "reset" "$uart" "$baud" "$flow"
[ $? -ne 0 ] && return 1
return 2
}
mcu_add_uci_config() {
local name="$1"
local interface="$2"
local bootloader="$3"
local firmware="$4"
local enable_pin="$5"
local uart_path="$6"
local uart_baud="$7"
local uart_flow="$8"
uci -q set mcu.${name}="mcu"
uci -q set mcu.${name}.interface="$interface"
uci -q set mcu.${name}.bootloader="$bootloader"
uci -q set mcu.${name}.firmware="$firmware"
[ -n "$enable_pin" ] && uci -q set mcu.${name}.enable_pin="$enable_pin"
[ -n "$uart_path" ] && uci -q set mcu.${name}.uart_path="$uart_path"
[ -n "$uart_baud" ] && uci -q set mcu.${name}.uart_baud="$uart_baud"
[ "$uart_flow" = "1" ] && uci -q set mcu.${name}.uart_flow="1"
uci -q set mcu.${name}.disabled="0"
uci -q commit mcu
}