diff --git a/rpi-update b/rpi-update index c1e6060..1a4247c 100755 --- a/rpi-update +++ b/rpi-update @@ -15,6 +15,10 @@ REPO_URI=${REPO_URI:-"https://github.com/raspberrypi/rpi-firmware"} REPO_API_URI=${REPO_URI/github.com/api.github.com\/repos} REPO_CONTENT_URI=${REPO_URI/github.com/raw.githubusercontent.com} +BOOTLOADER_REPO_URI=${BOOTLOADER_REPO_URI:-"https://github.com/raspberrypi/rpi-eeprom"} +BOOTLOADER_REPO_API_URI=${BOOTLOADER_REPO_URI/github.com/api.github.com\/repos} +BOOTLOADER_REPO_CONTENT_URI=${BOOTLOADER_REPO_URI/github.com/raw.githubusercontent.com} + UPDATE_SELF=${UPDATE_SELF:-1} UPDATE_REPO_URI="https://github.com/raspberrypi/rpi-update" UPDATE_REPO_CONTENT_URI=${UPDATE_REPO_URI/github.com/raw.githubusercontent.com} @@ -55,6 +59,7 @@ else fi WORK_PATH=${WORK_PATH:-"${ROOT_PATH}/root"} SKIP_KERNEL=${SKIP_KERNEL:-} +SKIP_BOOTLOADER=${SKIP_BOOTLOADER:-1} SKIP_FIRMWARE=${SKIP_FIRMWARE:-0} SKIP_SDK=${SKIP_SDK:-0} SKIP_VCLIBS=${SKIP_VCLIBS:-0} @@ -79,6 +84,8 @@ FW_MODPATH="${ROOT_PATH}/lib/modules" FW_REV_IN=${1:-""} FW_REVFILE="${FW_PATH}/.firmware_revision" SELFUPDATE_SCRIPT="${WORK_PATH}/.updateScript.sh" +BOOTLOADER_REV_IN=${2:-""} +BOOTLOADER_REVFILE="${FW_PATH}/.bootloader_revision" [ "${RPI_UPDATE_UNSUPPORTED}" -ne 0 ] && echo -e "You appear to be trying to update firmware on an incompatible distribution. To force update, run the following:\nsudo -E RPI_UPDATE_UNSUPPORTED=0 rpi-update" && exit 1 @@ -138,7 +145,7 @@ function update_self() { cat > "${SELFUPDATE_SCRIPT}" << EOF if mv "${_tempFileName}" "$0"; then rm -- "\$0" - exec env UPDATE_SELF=0 /bin/bash "$0" "${FW_REV_IN}" + exec env UPDATE_SELF=0 /bin/bash "$0" "${FW_REV_IN}" "${BOOTLOADER_REV_IN}" else echo " !!! Failed!" fi @@ -233,70 +240,91 @@ function update_sdk { fi } -# Check if the bootloader is older than the latest critical release. An old -# bootloader shouldn't block rpi-update so just inform the user that bootloader -# is out of date. -function check_eeprom_version { - local CURRENT_VERSION="" - local FIRST_VERSION=1557513636 - # MIN VERSION for Sep 10 2019 EEPROM - local MIN_VERSION=1568112110 - local HAVE_BOOTLOADER_EEPROM=0 - - # Skip EEPROM check if vcgencmd is missing because it won't be possible to - # check the version. - if ! command -v vcgencmd > /dev/null; then - return - fi - - rev= - if [ -f /proc/cpuinfo ]; then - rev="$(sed -n '/^Revision/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo)" - fi - - if [ -n $rev ] && [ $(((0x$rev >> 23) & 1)) -ne 0 ] && [ $(((0x$rev >> 12) & 15)) -eq 3 ]; then - HAVE_BOOTLOADER_EEPROM=1 - fi - - if [ "${HAVE_BOOTLOADER_EEPROM}" != 1 ]; then - return - fi - - # vcgencmd doesn't return non-zero for unknown commands. - if vcgencmd bootloader_config | grep -qi "Command not registered"; then - # Firmware is too old to return the bootloader config - return - fi - - # If FREEZE_VERSION is specified then assume that the user doesn't want - # an EEPROM update so skip the check. - if vcgencmd bootloader_config | grep -q FREEZE_VERSION=1; then - return - fi - - if vcgencmd bootloader_version | grep -q timestamp; then - CURRENT_VERSION=$(vcgencmd bootloader_version | grep timestamp | awk '{print $2}') - if [ "${CURRENT_VERSION}" = "0" ]; then - # If a timestamp of zero is returned then it's new firmware but an - # old bootloader. Assume bootloader v0 - CURRENT_VERSION="${FIRST_VERSION}" +function download_bootloader_tools { + for tool in rpi-eeprom-update rpi-eeprom-config rpi-eeprom-digest; do + temp="$(mktemp)" + if ! eval curl -fs ${CURL_OPTIONS} "${BOOTLOADER_REPO_CONTENT_URI}/${BOOTLOADER_REV}/${tool}" --output "${temp}"; then + echo " !!! Failed to download ${tool}" + rm -f "${temp}" + exit 1 fi - elif vcgencmd bootloader_version | grep -qi "unknown"; then - return - else - # New bootloader / old firmware ? Try to parse the date - CURRENT_VERSION=$(date -u +%s --date "$(vcgencmd bootloader_version | head -n1)") - fi + mv "${temp}" "/usr/bin/${tool}" + chmod a+rx "/usr/bin/${tool}" + done +} - # Failed to parse the version. Default to the initial production release. - if [ -z "${CURRENT_VERSION}" ]; then - CURRENT_VERSION="${FIRST_VERSION}" - fi +function download_bootloader_images { + bcm_chip=$1 + latest_eeprom=$(eval curl -fs ${CURL_OPTIONS} ${BOOTLOADER_REPO_API_URI}/git/trees/${BOOTLOADER_REV}?recursive=1 | grep "firmware-${bcm_chip}/latest/pieeprom" | sed 's/.*\(pieeprom.*\)".*/\1/' | sort -r | head -n1) + if [[ -n "${latest_eeprom}" ]]; then + fw_path="/lib/firmware/raspberrypi/bootloader-${bcm_chip}/latest" + if [[ ! -d "${fw_path}" ]]; then + echo "Path ${fw_path} not found. Please install the rpi-eeprom package first" + exit 1 + fi + if [[ "${BOOTLOADER_REV_IN}" != "master" ]]; then + echo "Non default bootloader revision specified (${BOOTLOADER_REV_IN}) deleting local bootloader binaries in ${fw_path}" + rm -rf "${fw_path}" + mkdir -p "${fw_path}" + fi + if [[ ! -f "${fw_path}/${latest_eeprom}" ]]; then + eeprom_temp="$(mktemp)" + if ! eval curl -fs ${CURL_OPTIONS} "${BOOTLOADER_REPO_CONTENT_URI}/${BOOTLOADER_REV}/firmware-${bcm_chip}/latest/${latest_eeprom}" --output "${eeprom_temp}"; then + echo " !!! Failed to download firmware-${bcm_chip}/${latest_eeprom}" + rm -rf "${eeprom_temp}" + exit 1 + fi + mv "${eeprom_temp}" "${fw_path}/${latest_eeprom}" + chmod a+r "${fw_path}/${latest_eeprom}" + fi - if [ "${CURRENT_VERSION}" -lt "${MIN_VERSION}" ]; then - echo "A newer bootloader EEPROM version is available." - echo "On Debian, try: sudo apt update; sudo apt install rpi-eeprom" - echo "then reboot to install the new bootloader" + # Always download the latest recovery.bin if bootloader_rev has changed. + recovery_temp="$(mktemp)" + if ! eval curl -fs ${CURL_OPTIONS} "${BOOTLOADER_REPO_CONTENT_URI}/${BOOTLOADER_REV}/firmware-${bcm_chip}/latest/recovery.bin" --output "${recovery_temp}"; then + echo " !!! Failed to download ${bcm_chip}/recovery.bin" + rm -rf "${recovery_temp}" + exit 1 + fi + mv "${recovery_temp}" "${fw_path}/recovery.bin" + chmod a+r "${fw_path}/recovery.bin" + fi +} + +function download_bootloader { + if [[ ${SKIP_BOOTLOADER} -eq 0 ]]; then + echo "Downloading bootloader tools" + download_bootloader_tools + + echo "Downloading bootloader images" + if grep -q "bcm2711" /proc/device-tree/compatible; then + download_bootloader_images 2711 + fi + if grep -q "bcm2712" /proc/device-tree/compatible; then + download_bootloader_images 2712 + fi + fi +} + +function update_bootloader { + if [[ ${SKIP_BOOTLOADER} -eq 0 ]]; then + sed /etc/default/rpi-eeprom-update -i -e "s/^FIRMWARE_RELEASE_STATUS.*/FIRMWARE_RELEASE_STATUS=\"latest\"/" + if [[ "${BOOTLOADER_REV_IN}" != "master" ]]; then + # Use latest according to rpi-eeprom git hash + echo "Using latest bootloader from rpi-eeprom ${BOOTLOADER_REV_IN}" + config_temp="$(mktemp)" + if ! rpi-eeprom-config > "${config_temp}"; then + echo "Failed to read current bootloader config" + exit 1 + fi + if ! rpi-eeprom-config -a "${config_temp}"; then + echo "Failed to update bootloader - rpi-eeprom-rev ${BOOTLOADER_REV_IN}" + exit 1 + fi + rm -f "${config_temp}" + else + # Update if newer + rpi-eeprom-update -a + fi fi } @@ -316,8 +344,8 @@ function show_notice { if ${NOTICE_HASH_EXISTS}; then local NOTICE=$(echo "${FULL_NOTICE}" | tail -n+2) local NEW_HASH=${FW_REV} - local LOCAL_lt_NOTICE=$(compare_hashes "${LOCAL_HASH}" lt "${NOTICE_HASH}") - local NEW_ge_NOTICE=$(compare_hashes "${NEW_HASH}" ge "${NOTICE_HASH}") + local LOCAL_lt_NOTICE=$(compare_hashes "${REPO_API_URI}" "${LOCAL_HASH}" lt "${NOTICE_HASH}") + local NEW_ge_NOTICE=$(compare_hashes "${REPO_API_URI}" "${NEW_HASH}" ge "${NOTICE_HASH}") if ! ${LOCAL_lt_NOTICE} && ! ${NEW_ge_NOTICE}; then return fi @@ -455,6 +483,7 @@ function finalise { fi echo " *** Storing current firmware revision" echo "${FW_REV}" > "${FW_REVFILE}" + echo "${BOOTLOADER_REV}" > "${BOOTLOADER_REVFILE}" } function do_backup { @@ -509,13 +538,14 @@ function do_update { check_partition 256 fi check_initramfs - check_eeprom_version show_notice + download_bootloader download_rev if [[ -f "${FW_REPOLOCAL}/pre-install" ]]; then echo " *** Running pre-install script" source "${FW_REPOLOCAL}/pre-install" fi + update_bootloader update_firmware update_modules update_vc_libs @@ -624,14 +654,14 @@ function noobs_fix { } function get_hash_date { - local COMMITS_URI=${REPO_API_URI}/commits/$1 + local COMMITS_URI=${1}/commits/$2 eval curl ${CURL_OPTIONS_API} -s ${CURL_OPTIONS} "${COMMITS_URI}" | grep "date" | head -1 | awk -F\" '{print $4}' } function compare_hashes { - local DATE1=$(get_hash_date "$1") - local DATE2=$(get_hash_date "$3") - if [ $(date -d "${DATE1}" +%s) -$2 $(date -d "${DATE2}" +%s) ]; then + local DATE1=$(get_hash_date "${1}" "$2") + local DATE2=$(get_hash_date "${1}" "$4") + if [ $(date -d "${DATE1}" +%s) -$3 $(date -d "${DATE2}" +%s) ]; then echo true else echo false @@ -640,10 +670,16 @@ function compare_hashes { function get_long_hash { # ask github for long version hash - local COMMITS_URI=${REPO_API_URI}/commits/$1 + local COMMITS_URI=${1}/commits/$2 eval curl ${CURL_OPTIONS_API} -s ${CURL_OPTIONS} "${COMMITS_URI}" | awk 'BEGIN {hash=""} { if (hash == "" && $1 == "\"sha\":") {hash=substr($2, 2, 40);} } END {print hash}' } +function print_diffs { + local diff_uri="$1" + SEPARATOR="======================================================" + eval curl ${CURL_OPTIONS_API} -s ${CURL_OPTIONS} "${diff_uri}" | awk -v SEPARATOR="${SEPARATOR}" -F\" ' { if ($2 == "commits") {commits=1} if (commits && $2 == "message") {print SEPARATOR "\nCommit: " $4} }' | sed 's/\\n\\n/\nCommit:\ /g' +} + if [[ ${EUID} -ne 0 ]]; then echo " !!! This tool must be run as root" @@ -688,15 +724,28 @@ if [[ "${FW_REV_IN}" == "" ]]; then FW_REV_IN=${BRANCH} fi +if [[ "${BOOTLOADER_REV_IN}" == "" ]]; then + BOOTLOADER_REV_IN=${BRANCH} +fi + ARTIFACT="" BUILD="" FW_REV="" if [[ ${FW_REV_IN} != http* ]] && [[ ! -f ${FW_REV_IN} ]]; then - FW_REV=$(get_long_hash "${FW_REV_IN}") + FW_REV=$(get_long_hash "${REPO_API_URI}" "${FW_REV_IN}") fi echo FW_REV:$FW_REV +BOOTLOADER_REV="" +BOOTLOADER_LOCAL_HASH="" +if [[ ${SKIP_BOOTLOADER} -eq 0 ]]; then + if [[ ${BOOTLOADER_REV_IN} != http* ]] && [[ ! -f ${BOOTLOADER_REV_IN} ]]; then + BOOTLOADER_REV=$(get_long_hash "${BOOTLOADER_REPO_API_URI}" "${BOOTLOADER_REV_IN}") + fi + echo BOOTLOADER_REV:$BOOTLOADER_REV +fi + if [[ "${FW_REV}" == "" ]]; then if [[ ${FW_REV_IN} != http* ]] && [[ ! -f ${FW_REV_IN} ]]; then IFS=':' read ARTIFACT BUILD <<<${FW_REV_IN} @@ -735,23 +784,59 @@ if [[ ! -f "${FW_REVFILE}" ]]; then else if [[ "${ARTIFACT}" != "" ]]; then LOCAL_HASH=$(cat "${FW_REVFILE}") + if [[ "${SKIP_BOOTLOADER}" -eq 0 ]]; then + BOOTLOADER_LOCAL_HASH=$(cat "${BOOTLOADER_REVFILE}") + fi else - LOCAL_HASH=$(get_long_hash "$(cat "${FW_REVFILE}")") + LOCAL_HASH=$(get_long_hash ${REPO_API_URI} "$(cat "${FW_REVFILE}")") + if [[ "${SKIP_BOOTLOADER}" -eq 0 ]]; then + if [[ ! -f "${BOOTLOADER_REVFILE}" ]]; then + BOOTLOADER_LOCAL_HASH=0 + else + BOOTLOADER_LOCAL_HASH=$(get_long_hash "${BOOTLOADER_REPO_API_URI}" "$(cat "${BOOTLOADER_REVFILE}")") + fi + fi fi - if [[ "${LOCAL_HASH}" == "${FW_REV}" ]]; then - echo " *** Your firmware is already up to date (delete ${FW_REVFILE} to force an update anyway)" + + if [[ "${LOCAL_HASH}" == "${FW_REV}" && "${BOOTLOADER_LOCAL_HASH}" == "${BOOTLOADER_REV}" ]] ; then + echo " *** Your firmware is already up to date (delete ${FW_REVFILE} and ${BOOTLOADER_REVFILE} to force an update anyway)" exit 0 + else + if [[ "${LOCAL_HASH}" == "${FW_REV}" ]]; then + # Skip other components if only the bootloader has changed + SKIP_FIRMWARE=1 + SKIP_KERNEL=1 + SKIP_VCLIBS=1 + SKIP_SDK=1 + SKIP_DOWNLOAD=1 + ARTIFACT="" + elif [[ "${BOOTLOADER_LOCAL_HASH}" == "${BOOTLOADER_REV}" ]] ; then + SKIP_BOOTLOADER=1 + fi fi + if [[ ${JUST_CHECK} -ne 0 ]]; then - if $(compare_hashes "${LOCAL_HASH}" lt "${FW_REV}"); then + if $(compare_hashes "${REPO_API_URI}" "${LOCAL_HASH}" lt "${FW_REV}"); then echo " *** Firmware update required. New commits available:" DIFF_URI=${REPO_API_URI}/compare/${LOCAL_HASH}...${FW_REV} - else + print_diffs "${DIFF_URI}" + elif $(compare_hashes "${REPO_API_URI}" "${LOCAL_HASH}" gt "${FW_REV}"); then echo " *** Firmware downgrade requested. Commits to drop:" DIFF_URI=${REPO_API_URI}/compare/${FW_REV}...${LOCAL_HASH} + print_diffs "${DIFF_URI}" + fi + + if [[ ${SKIP_BOOTLOADER} -eq 0 ]]; then + if $(compare_hashes "${BOOTLOADER_REPO_API_URI}" "${BOOTLOADER_LOCAL_HASH}" lt "${BOOTLOADER_REV}"); then + echo " *** Bootloader update required. New commits available:" + DIFF_URI=${BOOTLOADER_REPO_API_URI}/compare/${BOOTLOADER_LOCAL_HASH}...${BOOTLOADER_REV} + print_diffs "${DIFF_URI}" + elif $(compare_hashes "${BOOTLOADER_REPO_API_URI}" "${BOOTLOADER_LOCAL_HASH}" gt "${BOOTLOADER_REV}"); then + echo " *** Bootloader downgrade requested. Commits to drop:" + DIFF_URI=${BOOTLOADER_REPO_API_URI}/compare/${BOOTLOADER_REV}...${BOOTLOADER_LOCAL_HASH} + print_diffs "${DIFF_URI}" + fi fi - SEPARATOR="======================================================" - eval curl ${CURL_OPTIONS_API} -s ${CURL_OPTIONS} "${DIFF_URI}" | awk -v SEPARATOR="${SEPARATOR}" -F\" ' { if ($2 == "commits") {commits=1} if (commits && $2 == "message") {print SEPARATOR "\nCommit: " $4} }' | sed 's/\\n\\n/\nCommit:\ /g' exit 2 fi fi