1#!/bin/bash 2set -eo pipefail 3 4# Get the root mtd device number (mtdX) from "/dev/ubiblockX_Y on /" 5findrootmtd() { 6 rootmatch=" on / " 7 m="$(mount | grep "${rootmatch}" | grep "ubiblock")" 8 m="${m##*ubiblock}" 9 m="${m%_*}" 10 if [ -z "${m}" ]; then 11 # default to bmc mtd (0) 12 m=0 13 fi 14 echo "mtd${m}" 15} 16 17findrootubi() { 18 rootmatch=" on / " 19 m="$(mount | grep "${rootmatch}")" 20 m="${m##*ubiblock}" 21 m="${m% on*}" 22 echo "ubi${m}" 23} 24 25# Get the mtd device number (mtdX) 26findmtd() { 27 m="$(grep -xl "$1" /sys/class/mtd/*/name)" 28 m="${m%/name}" 29 m="${m##*/}" 30 echo "${m}" 31} 32 33# Get the mtd device number only (return X of mtdX) 34findmtdnum() { 35 m="$(findmtd "$1")" 36 m="${m##mtd}" 37 echo "${m}" 38} 39 40# Get the ubi device number (ubiX_Y) 41findubi() { 42 u="$(grep -xl "$1" /sys/class/ubi/ubi?/subsystem/ubi*/name)" 43 u="${u%/name}" 44 u="${u##*/}" 45 echo "${u}" 46} 47 48# Get the ubi device number (ubiX_Y) on a specific mtd 49findubi_onmtd() { 50 u="$(grep -xl "$1" /sys/class/ubi/ubi"$2"/subsystem/ubi"$2"*/name)" 51 u="${u%/name}" 52 u="${u##*/}" 53 echo "${u}" 54} 55 56# Get all ubi device names on a specific mtd that match requested string 57findubiname_onmtd() { 58 u="$(grep -h "$1" /sys/class/ubi/ubi"$2"/subsystem/ubi"$2"*/name)" 59 u="${u%/name}" 60 u="${u##*/}" 61 echo "${u}" 62} 63 64# Get the name from the requested ubiX_Y volume 65findname() { 66 n="$(cat /sys/class/ubi/$1/name)" 67 echo "${n}" 68} 69 70# Set the u-boot envs that perform a side switch on failure to boot 71set_wdt2bite() { 72 if ! fw_printenv wdt2bite 2>/dev/null; then 73 fw_setenv wdt2bite "mw.l 0x1e785024 0xa 1; mw.b 0x1e78502c 0xb3 1" 74 fw_setenv bootalt "run wdt2bite" 75 fw_setenv obmc_bootcmd "ubi part obmc-ubi; run do_rwreset; ubi read \ 76\${loadaddr} \${kernelname}; bootm \${loadaddr} || run bootalt" 77 fi 78} 79 80# Make space on flash before creating new volumes. This can be enhanced 81# determine current flash usage. For now only keep a "keepmax" number of them 82ubi_remove_volumes() 83{ 84 rootubi="$(findrootubi)" 85 rootname="$(findname "${rootubi}")" 86 rootversion="${rootname##*-}" 87 rootkernel="kernel-${rootversion}" 88 89 # Just keep max number of volumes before updating, don't delete the version 90 # the BMC is booted from, and when a version is identified to be deleted, 91 # delete both the rofs and kernel volumes for that version. 92 rmnames="$(findubiname_onmtd "${name%-*}-" "${ro}")" 93 rmnames=(${rmnames}) 94 ubicount="${#rmnames[@]}" 95 while [ ${ubicount} -ge ${keepmax} ]; do 96 # Loop through existing volumes and skip currently active ones 97 for (( index=0; index<${#rmnames[@]}; index++ )); do 98 rmname="${rmnames[${index}]}" 99 rmversion="${rmname##*-}" 100 [ "${rmversion}" == "${version}" ] && continue 101 rmubi="$(findubi_onmtd "rofs-${rmversion}" "${ro}")" 102 if [[ ( "${rmubi}" != "${rootubi}" ) && 103 ( "${rmname}" != "${rootkernel}" ) ]]; then 104 ubi_remove "rofs-${rmversion}" "${ro}" 105 ubi_remove "kernel-${rmversion}" "${ro}" 106 # Remove priority value 107 fw_setenv "${rmversion}" 108 break 109 fi 110 done 111 # Decrease count regardless to avoid an infinite loop 112 (( ubicount-- )) 113 done 114} 115 116ubi_rw() { 117 rwmtd="$(findmtd "${reqmtd}")" 118 rw="${rwmtd#mtd}" 119 ubidev="/dev/ubi${rw}" 120 121 # Update rwfs_size, check imgsize was specified, otherwise it'd clear the var 122 if [ ! -z "$imgsize" ]; then 123 rwsize="$(fw_printenv -n rwfs_size 2>/dev/null)" || true 124 if [[ "${imgsize}" != "${rwsize}" ]]; then 125 fw_setenv rwfs_size "${imgsize}" 126 fi 127 fi 128 129 vol="$(findubi "${name}")" 130 if [ -z "${vol}" ]; then 131 ubimkvol "${ubidev}" -N "${name}" -s "${imgsize}" 132 fi 133} 134 135ubi_ro() { 136 keepmax=2 # Default 2 volumes per mtd 137 romtd="$(findmtd "${reqmtd}")" 138 romtd2="$(findmtd "${reqmtd2}")" 139 140 if [ ! "${romtd}" == "${romtd2}" ]; then 141 # Request to use alternate mtd device, choose the non-root one 142 keepmax=1 # 1 volume on each of the requested mtds 143 rootmtd="$(findrootmtd)" 144 if [ "${rootmtd}" == "${romtd}" ]; then 145 romtd="${romtd2}" 146 fi 147 fi 148 ro="${romtd#mtd}" 149 ubidev="/dev/ubi${ro}" 150 151 ubi_remove_volumes 152 153 if [ -z "${imgfile}" ]; then 154 echo "Unable to create read-only volume. Image file not specified." 155 return 1 156 fi 157 158 # Create a ubi volume, dynamically sized to fit BMC image if size unspecified 159 img="/tmp/images/${version}/${imgfile}" 160 imgsize="$(stat -c '%s' ${img})" 161 162 vol="$(findubi "${name}")" 163 if [ ! -z "${vol}" ]; then 164 # Allow a duplicate kernel volume on the alt mtd 165 if [[ "${name}" =~ "kernel" ]]; then 166 vol="$(findubi_onmtd "${name}" "${ro}")" 167 fi 168 fi 169 if [ -z "${vol}" ]; then 170 ubimkvol "${ubidev}" -N "${name}" -s "${imgsize}" --type=static 171 vol="$(findubi "${name}")" 172 fi 173} 174 175# Squashfs images need a ubi block 176ubi_block() { 177 vol="$(findubi "${name}")" 178 ubidevid="${vol#ubi}" 179 block="/dev/ubiblock${ubidevid}" 180 if [ ! -e "$block" ]; then 181 ubiblock --create "/dev/ubi${ubidevid}" 182 fi 183} 184 185ubi_updatevol() { 186 vol="$(findubi "${name}")" 187 ubidevid="${vol#ubi}" 188 img="/tmp/images/${version}/${imgfile}" 189 ubiupdatevol "/dev/ubi${ubidevid}" "${img}" 190} 191 192ubi_remove() { 193 rmname="$1" 194 rmmtd="$2" 195 if [ ! -z "${rmmtd}" ]; then 196 vol="$(findubi_onmtd "${rmname}" "${rmmtd}")" 197 else 198 vol="$(findubi "${rmname}")" 199 fi 200 201 if [ ! -z "$vol" ]; then 202 vol="${vol%_*}" 203 204 if grep -q "$rmname" /proc/mounts; then 205 mountdir=$(grep "$rmname" /proc/mounts | cut -d " " -f 2) 206 umount "$mountdir" 207 rm -r "$mountdir" 208 fi 209 210 ubirmvol "/dev/${vol}" -N "$rmname" 211 fi 212} 213 214ubi_cleanup() { 215 # When ubi_cleanup is run, it expects one or no active version. 216 activeVersion=$(busctl --list --no-pager tree \ 217 xyz.openbmc_project.Software.BMC.Updater | \ 218 grep /xyz/openbmc_project/software/ | tail -c 9) 219 220 if [[ -z "$activeVersion" ]]; then 221 vols=$(ubinfo -a | grep "rofs-" | cut -c 14-) 222 vols=(${vols}) 223 else 224 vols=$(ubinfo -a | grep "rofs-" | \ 225 grep -v "$activeVersion" | cut -c 14-) 226 vols=(${vols}) 227 fi 228 229 for (( index=0; index<${#vols[@]}; index++ )); do 230 ubi_remove ${vols[index]} 231 done 232} 233 234mount_alt_rwfs() { 235 altNum="$(findmtdnum "alt-bmc")" 236 if [ ! -z "${altNum}" ]; then 237 altRwfs=$(ubinfo -a -d ${altNum} | grep -w "rwfs") || true 238 if [ ! -z "${altRwfs}" ]; then 239 altVarMount="/media/alt/var" 240 mkdir -p "${altVarMount}" 241 if mount ubi"${altNum}":rwfs "${altVarMount}" -t ubifs -o defaults; then 242 mkdir -p "${altVarMount}"/persist/etc 243 fi 244 fi 245 fi 246} 247 248remount_ubi() { 249 bmcmtd="$(findmtd "bmc")" 250 altbmcmtd="$(findmtd "alt-bmc")" 251 mtds="${bmcmtd: -1}","${altbmcmtd: -1}" 252 253 IFS=',' read -r -a mtds <<< "$mtds" 254 mtds=($(echo "${mtds[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) 255 for mtd in ${mtds[@]}; do 256 # Get information on all ubi volumes 257 ubinfo=$(ubinfo -d ${mtd}) 258 presentVolumes=${ubinfo##*:} 259 IFS=', ' read -r -a array <<< "$presentVolumes" 260 for element in ${array[@]}; do 261 elementProperties=$(ubinfo -d $mtd -n $element) 262 # Get ubi volume name by getting rid of additional properties 263 name=${elementProperties#*Name:} 264 name="${name%Character*}" 265 name="$(echo -e "${name}" | tr -d '[:space:]')" 266 267 if [[ ${name} == rofs-* ]]; then 268 mountdir="/media/${name}" 269 270 if [ ! -d ${mountdir} ]; then 271 mkdir -p "${mountdir}" 272 # U-Boot will create the ubiblock for the running version, but not 273 # for the version on the other chip 274 if [ ! -e "/dev/ubiblock${mtd}_${element}" ]; then 275 ubiblock --create /dev/ubi${mtd}_${element} 276 fi 277 mount -t squashfs -o ro "/dev/ubiblock${mtd}_${element}" "${mountdir}" 278 fi 279 fi 280 done 281 done 282 283 set_wdt2bite 284} 285 286# Read the current env variable and set it on the alternate boot env 287copy_env_var_to_alt() { 288 varName=$1 289 value="$(fw_printenv -n "${varName}")" 290 fw_setenv -c /etc/alt_fw_env.config "${varName}" "${value}" 291} 292 293# When the alternate bmc chip boots, u-boot thinks its the primary mtdX. 294# Therefore need to swap the chip numbers when copying the ubiblock and root to 295# alternate bmc u-boot environment. 296copy_ubiblock_to_alt() { 297 value="$(fw_printenv -n ubiblock)" 298 bmcNum="$(findmtdnum "bmc")" 299 altNum="$(findmtdnum "alt-bmc")" 300 replaceAlt="${value/${altNum},/${bmcNum},}" 301 302 if [[ "${value}" == "${replaceAlt}" ]]; then 303 replaceBmc="${value/${bmcNum},/${altNum},}" 304 value=${replaceBmc} 305 else 306 value=${replaceAlt} 307 fi 308 309 fw_setenv -c /etc/alt_fw_env.config ubiblock "${value}" 310} 311 312copy_root_to_alt() { 313 value="$(fw_printenv -n root)" 314 bmcNum="$(findmtdnum "bmc")" 315 altNum="$(findmtdnum "alt-bmc")" 316 replaceAlt="${value/${altNum}_/${bmcNum}_}" 317 318 if [[ "${value}" == "${replaceAlt}" ]]; then 319 replaceBmc="${value/${bmcNum}_/${altNum}_}" 320 value=${replaceBmc} 321 else 322 value=${replaceAlt} 323 fi 324 325 fw_setenv -c /etc/alt_fw_env.config root "${value}" 326} 327 328ubi_setenv() { 329 # The U-Boot environment maintains two banks of environment variables. 330 # The banks need to be consistent with each other to ensure that these 331 # variables can reliably be read from file. In order to guarantee that the 332 # banks are both correct, we need to run fw_setenv twice. 333 variable=$1 334 if [[ "$variable" == *"="* ]]; then 335 varName="${variable%=*}" 336 value="${variable##*=}" 337 # Write only if var is not set already to the requested value 338 currentValue="$(fw_printenv -n "${varName}" 2>/dev/null)" || true 339 if [[ "${currenValue}" != "${value}" ]]; then 340 fw_setenv "$varName" "$value" 341 fw_setenv "$varName" "$value" 342 fi 343 else 344 fw_setenv "$variable" 345 fw_setenv "$variable" 346 fi 347} 348 349mtd_write() { 350 flashmtd="$(findmtd "${reqmtd}")" 351 img="/tmp/images/${version}/${imgfile}" 352 flashcp -v ${img} /dev/${flashmtd} 353} 354 355backup_env_vars() { 356 copy_env_var_to_alt kernelname 357 copy_ubiblock_to_alt 358 copy_root_to_alt 359} 360 361update_env_vars() { 362 vol="$(findubi rofs-"${version}")" 363 if [ -z "${vol}" ]; then 364 return 1 365 fi 366 ubidevid="${vol#ubi}" 367 block="/dev/ubiblock${ubidevid}" 368 if [ ! -e "${block}" ]; then 369 return 1 370 fi 371 ubi_setenv "kernelname=kernel-${version}" 372 ubi_setenv "ubiblock=$(echo "${ubidevid}" | sed 's/_/,/')" 373 ubi_setenv "root=${block}" 374} 375 376#TODO: Replace the implementation with systemd-inhibitors lock 377# once systemd/systemd#949 is resolved 378rebootguardenable() { 379 dir="/run/systemd/system/" 380 file="reboot-guard.conf" 381 units=("reboot" "poweroff" "halt") 382 383 for unit in "${units[@]}"; do 384 mkdir -p ${dir}${unit}.target.d 385 echo -e "[Unit]\nRefuseManualStart=yes" >> ${dir}${unit}.target.d/${file} 386 done 387} 388 389#TODO: Replace the implementation with systemd-inhibitors lock 390# once systemd/systemd#949 is resolved 391rebootguarddisable() { 392 dir="/run/systemd/system/" 393 file="reboot-guard.conf" 394 units=("reboot" "poweroff" "halt") 395 396 for unit in "${units[@]}"; do 397 rm -rf ${dir}${unit}.target.d/${file} 398 done 399} 400 401# Create a copy in the alt mtd 402create_vol_in_alt() { 403 alt="alt-bmc" 404 altmtd="$(findmtd "${alt}")" 405 if [ ! -z "${altmtd}" ]; then 406 reqmtd="${alt}" 407 reqmtd2="${alt}" 408 ubi_ro 409 ubi_updatevol 410 fi 411} 412 413# Copy contents of one MTD device to another 414mtd_copy() { 415 in=$1 416 out=$2 417 418 # Must erase MTD first to prevent corruption 419 flash_eraseall "${out}" 420 dd if="${in}" of="${out}" 421} 422 423mirroruboot() { 424 bmc="$(findmtd "u-boot")" 425 bmcdev="/dev/${bmc}" 426 alt="$(findmtd "alt-u-boot")" 427 altdev="/dev/${alt}" 428 429 checksum_bmc="$(md5sum "${bmcdev}")" 430 checksum_bmc="${checksum_bmc% *}" 431 checksum_alt="$(md5sum "${altdev}")" 432 checksum_alt="${checksum_alt% *}" 433 434 if [[ "${checksum_bmc}" != "${checksum_alt}" ]]; then 435 bmcenv="$(findmtd "u-boot-env")" 436 bmcenvdev="/dev/${bmcenv}" 437 altenv="$(findmtd "alt-u-boot-env")" 438 altenvdev="/dev/${altenv}" 439 440 echo "Mirroring U-boot to alt chip" 441 mtd_copy "${bmcdev}" "${altdev}" 442 mtd_copy "${bmcenvdev}" "${altenvdev}" 443 444 copy_ubiblock_to_alt 445 copy_root_to_alt 446 fi 447} 448 449# The eMMC partition labels for the kernel and rootfs are boot-a/b and rofs-a/b. 450# Return the label (a or b) for the running partition. 451mmc_get_primary_label() { 452 rootmatch=" on / " 453 root="$(mount -l | grep "${rootmatch}")" 454 if [[ "${root}" == *"rofs-a"* ]]; then 455 echo "a" 456 else 457 echo "b" 458 fi 459} 460 461# The eMMC partition labels for the kernel and rootfs are boot-a/b and rofs-a/b. 462# Return the label (a or b) for the non-running partition. 463mmc_get_secondary_label() { 464 rootmatch=" on / " 465 root="$(mount -l | grep "${rootmatch}")" 466 if [[ "${root}" == *"rofs-a"* ]]; then 467 echo "b" 468 else 469 echo "a" 470 fi 471} 472 473mmc_update() { 474 # Update the secondary (non-running) boot and rofs partitions. 475 label="$(mmc_get_secondary_label)" 476 477 # Update the boot and rootfs partitions, restore their labels after the update 478 # by getting the partition number mmcblk0pX from their label. 479 zstd -d -c ${imgpath}/${version}/image-kernel | dd of="/dev/disk/by-partlabel/boot-${label}" 480 number="$(readlink -f /dev/disk/by-partlabel/boot-${label})" 481 number="${number##*mmcblk0p}" 482 sgdisk --change-name=${number}:boot-${label} /dev/mmcblk0 1>/dev/null 483 484 zstd -d -c ${imgpath}/${version}/image-rofs | dd of="/dev/disk/by-partlabel/rofs-${label}" 485 number="$(readlink -f /dev/disk/by-partlabel/rofs-${label})" 486 number="${number##*mmcblk0p}" 487 sgdisk --change-name=${number}:rofs-${label} /dev/mmcblk0 1>/dev/null 488 489 # Run this after sgdisk for labels to take effect. 490 partprobe 491 492 # Update hostfw 493 if [ -f ${imgpath}/${version}/image-hostfw ]; then 494 hostfwdir=$(grep "hostfw " /proc/mounts | cut -d " " -f 2) 495 cp ${imgpath}/${version}/image-hostfw ${hostfwdir}/hostfw-${label} 496 mkdir -p ${hostfwdir}/alternate 497 mount ${hostfwdir}/hostfw-${label} ${hostfwdir}/alternate -o ro 498 fi 499 500 # Store the label where the other properties like purpose and priority are 501 # preserved via the storePriority() function in the serialize files, so that 502 # it can be used for the remove function. 503 label_dir="/var/lib/phosphor-bmc-code-mgmt/${version}" 504 label_file="${label_dir}/partlabel" 505 mkdir -p "${label_dir}" 506 echo "${label}" > "${label_file}" 507} 508 509mmc_remove() { 510 # Render the filesystem unbootable by wiping out the first 1MB, this 511 # invalidates the filesystem header. 512 # If the label property does not exist, assume it's the secondary 513 # (non-running) one since the running device should not be erased. 514 label="" 515 label_file="/var/lib/phosphor-bmc-code-mgmt/${version}/partlabel" 516 if [ -f "${label_file}" ]; then 517 label="$(cat "${label_file}")" 518 else 519 label="$(mmc_get_secondary_label)" 520 fi 521 dd if=/dev/zero of=/dev/disk/by-partlabel/boot-${label} count=2048 522 dd if=/dev/zero of=/dev/disk/by-partlabel/rofs-${label} count=2048 523 524 hostfw_alt="hostfw/alternate" 525 if grep -q "${hostfw_alt}" /proc/mounts; then 526 hostfw_alt=$(grep "${hostfw_alt}" /proc/mounts | cut -d " " -f 2) 527 umount "${hostfw_alt}" 528 fi 529 hostfw_base="hostfw " 530 if grep -q "${hostfw_base}" /proc/mounts; then 531 hostfw_base=$(grep "${hostfw_base}" /proc/mounts | cut -d " " -f 2) 532 rm -f ${hostfw_base}/hostfw-${label} 533 fi 534} 535 536# Set the requested version as primary for the BMC to boot from upon reboot. 537mmc_setprimary() { 538 # Point root to the label of the requested BMC rootfs. If the label property 539 # does not exist, determine if the requested version is functional or not. 540 label="" 541 label_file="/var/lib/phosphor-bmc-code-mgmt/${version}/partlabel" 542 if [ -f "${label_file}" ]; then 543 label="$(cat "${label_file}")" 544 else 545 functional="$(busctl call xyz.openbmc_project.ObjectMapper \ 546 /xyz/openbmc_project/software/functional \ 547 org.freedesktop.DBus.Properties Get ss \ 548 xyz.openbmc_project.Association endpoints)" 549 if [[ "${functional}" =~ "${version}" ]]; then 550 label="$(mmc_get_primary_label)" 551 else 552 label="$(mmc_get_secondary_label)" 553 fi 554 fi 555 fw_setenv bootside "${label}" 556} 557 558case "$1" in 559 mtduboot) 560 reqmtd="$2" 561 version="$3" 562 imgfile="image-u-boot" 563 mtd_write 564 ;; 565 ubirw) 566 reqmtd="$2" 567 name="$3" 568 imgsize="$4" 569 ubi_rw 570 ;; 571 ubiro) 572 reqmtd="$(echo "$2" | cut -d "+" -f 1)" 573 reqmtd2="$(echo "$2" | cut -d "+" -f 2)" 574 name="$3" 575 version="$4" 576 imgfile="image-rofs" 577 ubi_ro 578 ubi_updatevol 579 ubi_block 580 ;; 581 ubikernel) 582 reqmtd="$(echo "$2" | cut -d "+" -f 1)" 583 reqmtd2="$(echo "$2" | cut -d "+" -f 2)" 584 name="$3" 585 version="$4" 586 imgfile="image-kernel" 587 ubi_ro 588 ubi_updatevol 589 create_vol_in_alt 590 ;; 591 ubiremove) 592 name="$2" 593 ubi_remove "${name}" 594 ;; 595 ubicleanup) 596 ubi_cleanup 597 ;; 598 ubisetenv) 599 ubi_setenv "$2" 600 ;; 601 ubiremount) 602 remount_ubi 603 mount_alt_rwfs 604 ;; 605 createenvbackup) 606 backup_env_vars 607 ;; 608 updateubootvars) 609 version="$2" 610 update_env_vars 611 ;; 612 rebootguardenable) 613 rebootguardenable 614 ;; 615 rebootguarddisable) 616 rebootguarddisable 617 ;; 618 mirroruboot) 619 mirroruboot 620 ;; 621 mmc) 622 version="$2" 623 imgpath="$3" 624 mmc_update 625 ;; 626 mmc-remove) 627 version="$2" 628 mmc_remove 629 ;; 630 mmc-setprimary) 631 version="$2" 632 mmc_setprimary 633 ;; 634 *) 635 echo "Invalid argument" 636 exit 1 637 ;; 638esac 639