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