diff --git a/README.md b/README.md index 725ccc8..254839f 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This document describes the device specific aspects of the balenaOS secure boot ### Supported devices * RaspberryPi CM4 ioboard (`raspberrypicm4-ioboard` device type) +* RaspberryPi CM5 ioboard (`raspberrypi5` device type) ### Provisioning diff --git a/conf/samples/raspberrypi5-sb/bblayers.conf.sample b/conf/samples/raspberrypi5-sb/bblayers.conf.sample new file mode 100644 index 0000000..ae79446 --- /dev/null +++ b/conf/samples/raspberrypi5-sb/bblayers.conf.sample @@ -0,0 +1,23 @@ +# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf +# changes incompatibly +POKY_BBLAYERS_CONF_VERSION = "2" + +BBPATH = "${TOPDIR}" +BBFILES ?= "" + +BBLAYERS ?= " \ + ${TOPDIR}/../layers/meta-balena/meta-balena-rust \ + ${TOPDIR}/../layers/meta-balena/meta-balena-common \ + ${TOPDIR}/../layers/meta-balena/meta-balena-kirkstone \ + ${TOPDIR}/../layers/meta-balena-raspberrypi \ + ${TOPDIR}/../layers/meta-balena-rpi-sb \ + ${TOPDIR}/../layers/poky/meta \ + ${TOPDIR}/../layers/poky/meta-poky \ + ${TOPDIR}/../layers/meta-openembedded/meta-oe \ + ${TOPDIR}/../layers/meta-openembedded/meta-filesystems \ + ${TOPDIR}/../layers/meta-openembedded/meta-networking \ + ${TOPDIR}/../layers/meta-openembedded/meta-python \ + ${TOPDIR}/../layers/meta-openembedded/meta-perl \ + ${TOPDIR}/../layers/meta-raspberrypi \ + ${TOPDIR}/../layers/meta-cyclonedx \ + " diff --git a/conf/samples/raspberrypi5-sb/conf-notes.txt b/conf/samples/raspberrypi5-sb/conf-notes.txt new file mode 100644 index 0000000..8c7b355 --- /dev/null +++ b/conf/samples/raspberrypi5-sb/conf-notes.txt @@ -0,0 +1,34 @@ + + _ _ ___ ____ + | |__ __ _| | ___ _ __ __ _ / _ \/ ___| + | '_ \ / _` | |/ _ \ '_ \ / _` | | | \___ \ + | |_) | (_| | | __/ | | | (_| | |_| |___) | + |_.__/ \__,_|_|\___|_| |_|\__,_|\___/|____/ + + -------------------------------------------- + +Resin specific images available: + balena-image + balena-image-flasher + +NPE X500 M3 : $ MACHINE=npe-x500-m3 bitbake balena-image +Raspberry Pi Zero 2 W (64bit) : $ MACHINE=raspberrypi0-2w-64 bitbake balena-image +Raspberry Pi 2 : $ MACHINE=raspberrypi2 bitbake balena-image +Raspberry Pi 3 (using 64bit OS) : $ MACHINE=raspberrypi3-64 bitbake balena-image +Raspberry Pi 3 : $ MACHINE=raspberrypi3 bitbake balena-image +UniPi Neuron (Raspberry Pi 3) (NEW) : $ MACHINE=raspberrypi3-unipi-neuron bitbake balena-image +Raspberry Pi 400 (NEW) : $ MACHINE=raspberrypi400-64 bitbake balena-image +Raspberry Pi 4 : $ MACHINE=raspberrypi4-64 bitbake balena-image +SuperHub (NEW) : $ MACHINE=raspberrypi4-superhub bitbake balena-image +UniPi Neuron (Raspberry Pi 4) (NEW) : $ MACHINE=raspberrypi4-unipi-neuron bitbake balena-image +Raspberry Pi 5 : $ MACHINE=raspberrypi5 bitbake balena-image +Secure boot enabled Raspberry Pi CM5 IO board : $ MACHINE=raspberrypi5 bitbake balena-image-flasher +Raspberry Pi CM4 IO Board (NEW) : $ MACHINE=raspberrypicm4-ioboard bitbake balena-image +Secure boot enabled Raspberry Pi CM4 IO Board (NEW) : $ MACHINE=raspberrypicm4-ioboard bitbake balena-image-flasher +Raspberry Pi (v1 / Zero / Zero W) : $ MACHINE=raspberrypi bitbake balena-image +Revolution Pi Connect 4 (NEW) : $ MACHINE=revpi-connect-4 bitbake balena-image +Revolution Pi Connect (NEW) : $ MACHINE=revpi-connect bitbake balena-image +Revolution Pi Connect S (NEW) : $ MACHINE=revpi-connect-s bitbake balena-image +Revolution Pi Core 3 : $ MACHINE=revpi-core-3 bitbake balena-image +Rocktech-RPI-300 (NEW) : $ MACHINE=rt-rpi-300 bitbake balena-image + diff --git a/conf/samples/raspberrypi5-sb/local.conf.sample b/conf/samples/raspberrypi5-sb/local.conf.sample new file mode 100644 index 0000000..d4e0b14 --- /dev/null +++ b/conf/samples/raspberrypi5-sb/local.conf.sample @@ -0,0 +1,108 @@ +# Supported machines +#MACHINE ?= "raspberrypi" +#MACHINE ?= "raspberrypi2" +#MACHINE ?= "raspberrypi3" +#MACHINE ?= "raspberrypi3-64" +#MACHINE ?= "revpi-connect" +#MACHINE ?= "revpi-connect-s" +#MACHINE ?= "revpi-connect-4" +#MACHINE ?= "revpi-core-3" +#MACHINE ?= "raspberrypi4-64" +#MACHINE ?= "raspberrypi400-64" +#MACHINE ?= "raspberrypicm4-ioboard" +#MACHINE ?= "npe-x500-m3" +#MACHINE ?= "rt-rpi-300" +#MACHINE ?= "raspberrypi3-unipi-neuron" +#MACHINE ?= "raspberrypi4-superhub" +#MACHINE ?= "raspberrypi4-unipi-neuron" +#MACHINE ?= "raspberrypi5" + +# More info meta-resin/README.md +#TARGET_REPOSITORY ?= "" +#TARGET_TAG ?= "" + +# RaspberryPi specific variables +GPU_MEM = "16" + +# for the moment, we disable vc4 graphics for all but the 64 bits machines +DISABLE_VC4GRAPHICS = "1" +DISABLE_VC4GRAPHICS:remove:raspberrypi3-64 = "1" +DISABLE_VC4GRAPHICS:remove:raspberrypi4-64 = "1" +DISABLE_VC4GRAPHICS:remove:raspberrypi5 = "1" +DISABLE_VC4GRAPHICS:remove:raspberrypi0-2w-64 = "1" +DISABLE_VC4GRAPHICS:remove:revpi-connect-s = "1" + +# RPI BSP uses uncompressed kernel images by default +KERNEL_IMAGETYPE="zImage" +KERNEL_BOOTCMD="bootz" + +# When u-boot is enabled we need to use the "Image" format and the "booti" +# command to load the kernel for 64 bits machines +KERNEL_IMAGETYPE:raspberrypi3-64="Image.gz" +KERNEL_BOOTCMD:raspberrypi3-64 = "booti" +KERNEL_IMAGETYPE:raspberrypi4-64="Image.gz" +KERNEL_BOOTCMD:raspberrypi4-64 = "booti" +KERNEL_IMAGETYPE:raspberrypi5="Image.gz" +KERNEL_BOOTCMD:raspberrypi5 = "booti" +KERNEL_IMAGETYPE:raspberrypi0-2w-64 = "Image.gz" +KERNEL_BOOTCMD:raspberrypi0-2w-64 = "booti" + +# RPI Use u-boot. This needs to be 1 as we use u-boot +RPI_USE_U_BOOT = "1" + +# Set this to 1 to disable quiet boot and allow bootloader shell access +#OS_DEVELOPMENT = "1" + +# Set this to make build system generate resinhup bundles +#RESINHUP ?= "yes" + +# Set this to change the supervisor tag used +#SUPERVISOR_TAG ?= "master" + +# Compress final raw image +#BALENA_RAW_IMG_COMPRESSION ?= "xz" + +# Parallelism Options +BB_NUMBER_THREADS ?= "${@oe.utils.cpu_count()}" +PARALLEL_MAKE ?= "-j ${@oe.utils.cpu_count()}" + +# Resin specific distros +DISTRO ?= "resin-systemd" + +# Custom downloads directory +#DL_DIR ?= "${TOPDIR}/downloads" + +# Custom sstate directory +#SSTATE_DIR ?= "${TOPDIR}/sstate-cache" + +# Inheriting this class has shown to speed up builds due to significantly lower +# amounts of data stored in the data cache as well as on disk. +# http://www.yoctoproject.org/docs/latest/mega-manual/mega-manual.html#ref-classes-rm-work +#INHERIT += "rm_work" + +# Remove the old image before the new one is generated to save disk space when RM_OLD_IMAGE is set to 1, this is an easy way to keep the DEPLOY_DIR_IMAGE clean. +RM_OLD_IMAGE = "1" + +# Additional image features +USER_CLASSES ?= "buildstats" + +# By default disable interactive patch resolution (tasks will just fail instead): +PATCHRESOLVE = "noop" + +# Disk Space Monitoring during the build +BB_DISKMON_DIRS = "\ + STOPTASKS,${TMPDIR},1G,100K \ + STOPTASKS,${DL_DIR},1G,100K \ + STOPTASKS,${SSTATE_DIR},1G,100K \ + HALT,${TMPDIR},100M,1K \ + HALT,${DL_DIR},100M,1K \ + HALT,${SSTATE_DIR},100M,1K" + +CONF_VERSION = "2" + +HOSTTOOLS += "docker iptables" + +LICENSE_FLAGS_ACCEPTED = "synaptics-killswitch" + +# CycloneDX SBOM and VEX generation +INHERIT += "cyclonedx-export" diff --git a/recipes-bsp/bootfiles/rpi-cmdline.bbappend b/recipes-bsp/bootfiles/rpi-cmdline.bbappend index 511d9d1..392df8a 100644 --- a/recipes-bsp/bootfiles/rpi-cmdline.bbappend +++ b/recipes-bsp/bootfiles/rpi-cmdline.bbappend @@ -1,7 +1,11 @@ CMDLINE:prepend:raspberrypicm4-ioboard = " ${@bb.utils.contains('DISTRO_FEATURES','osdev-image',"earlycon=uart8250,mmio32,0xfe215040 console=tty1","",d)}" +# TODO: test easrly console +CMDLINE:prepend:raspberrypi5 = " ${@bb.utils.contains('DISTRO_FEATURES','osdev-image',"earlycon=uart8250,mmio32,0x7e215040 console=tty1","",d)}" CMDLINE += "${OS_KERNEL_CMDLINE} ${@oe.utils.conditional('SIGN_API','','',"${OS_KERNEL_SECUREBOOT_CMDLINE}",d)}" # Necessary for balena bootloader to work # These will not be passed to the actual kernel CMDLINE:append := " balena_stage2" -CMDLINE:append:raspberrypi4-64 := " nr_cpus=1" + +# TODO: test whether the RPI5 does not need this +CMDLINE:append := " nr_cpus=1" diff --git a/recipes-bsp/rpi-eeprom/rpi-eeprom_git.bbappend b/recipes-bsp/rpi-eeprom/rpi-eeprom_git.bbappend index 0d7e896..9350298 100644 --- a/recipes-bsp/rpi-eeprom/rpi-eeprom_git.bbappend +++ b/recipes-bsp/rpi-eeprom/rpi-eeprom_git.bbappend @@ -50,7 +50,6 @@ do_compile[vardeps] = "SIGN_API" do_deploy:append() { if [ "x${SIGN_API}" != "x" ]; then install -d ${DEPLOY_DIR_IMAGE}/rpi-eeprom/secure-boot-lock - cp -avL ${S}/${FIRMWARE}/stable/recovery.bin ${DEPLOY_DIR_IMAGE}/rpi-eeprom/secure-boot-lock/bootcode4.bin echo "uart_2ndstage=1" > ${DEPLOY_DIR_IMAGE}/rpi-eeprom/secure-boot-lock/config.txt echo "eeprom_write_protect=1" >> ${DEPLOY_DIR_IMAGE}/rpi-eeprom/secure-boot-lock/config.txt echo "program_pubkey=1" >> ${DEPLOY_DIR_IMAGE}/rpi-eeprom/secure-boot-lock/config.txt @@ -60,3 +59,14 @@ do_deploy:append() { cp -av ${WORKDIR}/pieeprom-latest-stable*sig ${DEPLOY_DIR_IMAGE}/rpi-eeprom/secure-boot-lock/pieeprom.sig fi } +do_deploy:append:rspberrypi4-64() { + if [ "x${SIGN_API}" != "x" ]; then + cp -avL ${S}/${FIRMWARE}/stable/recovery.bin ${DEPLOY_DIR_IMAGE}/rpi-eeprom/secure-boot-lock/bootcode4.bin + fi +} + +do_deploy:append:rspberrypi5() { + if [ "x${SIGN_API}" != "x" ]; then + cp -avL ${S}/${FIRMWARE}/stable/recovery.bin ${DEPLOY_DIR_IMAGE}/rpi-eeprom/secure-boot-lock/recovery.bin + fi +} diff --git a/recipes-core/images/balena-image-initramfs.bbappend b/recipes-core/images/balena-image-initramfs.bbappend index 0f1f4df..e72e930 100644 --- a/recipes-core/images/balena-image-initramfs.bbappend +++ b/recipes-core/images/balena-image-initramfs.bbappend @@ -1,2 +1,4 @@ IMAGE_ROOTFS_MAXSIZE = "65536" +# Required machine overrides IMAGE_ROOTFS_MAXSIZE:raspberrypi4-64 = "65536" +IMAGE_ROOTFS_MAXSIZE:raspberrypi5 = "65536" diff --git a/recipes-core/images/balena-image-sb.inc b/recipes-core/images/balena-image-sb.inc index 3798a14..5df012e 100644 --- a/recipes-core/images/balena-image-sb.inc +++ b/recipes-core/images/balena-image-sb.inc @@ -1,5 +1,8 @@ # Increase boot partition size BALENA_BOOT_SIZE = "174080" +# Required machine overrides +BALENA_BOOT_SIZE:raspberrypi5 = "174080" + BALENA_ESSENTIAL_BOOT_FILES:append:raspberrypi4-64 = " \ cmdline.txt \ @@ -13,6 +16,14 @@ BALENA_ESSENTIAL_BOOT_FILES:append:raspberrypi4-64 = " \ ${SDIMG_KERNELIMAGE} \ ${KERNEL_IMAGETYPE} \ " + +BALENA_ESSENTIAL_BOOT_FILES:append:raspberrypi5 = " \ + cmdline.txt \ + config.txt \ + ${SDIMG_KERNELIMAGE} \ + ${KERNEL_IMAGETYPE} \ + " + do_rootfs[vardeps] += "${@oe.utils.conditional('SIGN_API','','','BALENA_ESSENTIAL_BOOT_FILES',d)}" BALENA_BOOT_PARTITION_FILES:remove = " \ diff --git a/recipes-support/os-helpers/os-helpers.bbappend b/recipes-support/os-helpers/os-helpers.bbappend index 9f2731e..4c40d0b 100644 --- a/recipes-support/os-helpers/os-helpers.bbappend +++ b/recipes-support/os-helpers/os-helpers.bbappend @@ -6,15 +6,32 @@ SRC_URI += " \ " RDEPENDS:${PN}-sb = "os-helpers-otp" -RDEPENDS:${PN}-otp:raspberrypi4-64 = "userlandtools" +RDEPENDS:${PN}-otp = "userlandtools" PACKAGES += " \ ${PN}-otp \ " +DEVICE_TREES:raspberrypicm4-ioboard = "\ + bcm2711-rpi-400.dtb \ + bcm2711-rpi-4-b.dtb \ + bcm2711-rpi-cm4.dtb \ +" + +DEVICE_TREES:raspberrypi5 = "\ + bcm2712d0-rpi-5-b.dtb \ + bcm2712-rpi-500.dtb \ + bcm2712-rpi-5-b.dtb \ + bcm2712-rpi-cm5-cm4io.dtb \ + bcm2712-rpi-cm5-cm5io.dtb \ + bcm2712-rpi-cm5l-cm4io.dtb \ + bcm2712-rpi-cm5l-cm5io.dtb \ +" + do_install:append() { install -m 0775 ${WORKDIR}/os-helpers-otp ${D}${libexecdir} install -m 0775 ${WORKDIR}/os-helpers-sb ${D}${libexecdir} + sed -i -e "s,@@DEVICE_TREES@@,${DEVICE_TREES},g" ${D}${libexecdir}/os-helpers-sb sed -i -e "s,@@KERNEL_IMAGETYPE@@,${KERNEL_IMAGETYPE},g" ${D}${libexecdir}/os-helpers-sb sed -i -e "s,@@BALENA_IMAGE_FLAG_FILE@@,${BALENA_IMAGE_FLAG_FILE},g" ${D}${libexecdir}/os-helpers-sb } diff --git a/recipes-support/os-helpers/os-helpers/os-helpers-otp b/recipes-support/os-helpers/os-helpers/raspberrypi4-64/os-helpers-otp similarity index 100% rename from recipes-support/os-helpers/os-helpers/os-helpers-otp rename to recipes-support/os-helpers/os-helpers/raspberrypi4-64/os-helpers-otp diff --git a/recipes-support/os-helpers/os-helpers/os-helpers-sb b/recipes-support/os-helpers/os-helpers/raspberrypi4-64/os-helpers-sb similarity index 80% rename from recipes-support/os-helpers/os-helpers/os-helpers-sb rename to recipes-support/os-helpers/os-helpers/raspberrypi4-64/os-helpers-sb index 763fd2d..873324c 100644 --- a/recipes-support/os-helpers/os-helpers/os-helpers-sb +++ b/recipes-support/os-helpers/os-helpers/raspberrypi4-64/os-helpers-sb @@ -25,9 +25,7 @@ BALENA_NONENCRYPTED_BOOT_PARTITION_FILES="\ boot.scr \ boot.sig \ config.txt \ - bcm2711-rpi-400.dtb \ - bcm2711-rpi-4-b.dtb \ - bcm2711-rpi-cm4.dtb \ + @@DEVICE_TREES@@ \ @@BALENA_IMAGE_FLAG_FILE@@ \ extra_uEnv.txt \ @@KERNEL_IMAGETYPE@@ \ @@ -67,3 +65,15 @@ do_skip() { # If running a different script, return 0 to skip it test "$(basename "$0")" != "1-bootfiles" } + +get_bootloader_version() { + printf "%d" "0x$(od "/proc/device-tree/chosen/bootloader/build-timestamp" -v -An -t x1 | tr -d ' ' )" +} + +print_bootloader_config() { + blconfig_ofnode_path="/sys/firmware/devicetree/base"$(strings "/sys/firmware/devicetree/base/aliases/blconfig")"" + blconfig_nvmem_path=$(dirname $(find -L /sys/bus/nvmem -maxdepth 3 -samefile "${blconfig_ofnode_path}" 2>/dev/null)) + if [ -n "${blconfig_nvmem_path}" ]; then + cat "${blconfig_nvmem_path}"/nvmem + fi +} diff --git a/recipes-support/os-helpers/os-helpers/raspberrypi5/os-helpers-otp b/recipes-support/os-helpers/os-helpers/raspberrypi5/os-helpers-otp new file mode 100644 index 0000000..0eba625 --- /dev/null +++ b/recipes-support/os-helpers/os-helpers/raspberrypi5/os-helpers-otp @@ -0,0 +1,56 @@ +otp_read_key() { + local _key + _key="$(vcmailbox 0x00030081 40 40 0 8 0 0 0 0 0 0 0 0)" + echo "${_key}" | sed 's/0x//g' | awk '{for(i=8;i<16;i++) printf $i; print ""}' +} + +# https://www.raspberrypi.com/documentation/computers/configuration.html#common-bootloader-properties-chosenbootloader +# +# +---------+-------------------------------------------------------------+ +# | Bit | Description | +# +---------+-------------------------------------------------------------+ +# | 0 | SIGNED BOOT was defined in the EEPROM config file. | +# +---------+-------------------------------------------------------------+ +# | 1 | Reserved | +# +---------+-------------------------------------------------------------+ +# | 2 | The ROM development key has been revoked. See revoke_devkey. | +# +---------+-------------------------------------------------------------+ +# | 3 | The customer public key digest has been written to OTP. See | +# | | program_pubkey. | +# +---------+-------------------------------------------------------------+ +# | 4..31 | Reserved | +# +---------+-------------------------------------------------------------+ +# + +_otp_signed_binary() { + if [ -f "/proc/device-tree/chosen/bootloader/signed" ]; then + _signed_hex=$(xxd -p -l 1 /proc/device-tree/chosen/bootloader/signed) + printf "%08d\n" "$(echo "obase=2; ibase=16; ${_signed_hex}" | bc)" + return 0 + fi + return 1 +} + +# Return sucess if the bootloader is configured with SIGNED_BOOT +otp_is_signed_boot() { + if [ "$(_otp_signed_binary | cut -c8-8)" -eq 1 ]; then + return 0 + fi + return 1 +} + +# Return success if the development key has been revoked +otp_is_devkey_revoked() { + if [ "$(_otp_signed_binary | cut -c6-6)" -eq 1 ]; then + return 0 + fi + return 1 +} + +# Return success if the public key digest is written in the OTP +otp_has_pubkey_digest() { + if [ "$(_otp_signed_binary | cut -c5-5)" -eq 1 ]; then + return 0 + fi + return 1 +} diff --git a/recipes-support/os-helpers/os-helpers/raspberrypi5/os-helpers-sb b/recipes-support/os-helpers/os-helpers/raspberrypi5/os-helpers-sb new file mode 100644 index 0000000..d4c2fd6 --- /dev/null +++ b/recipes-support/os-helpers/os-helpers/raspberrypi5/os-helpers-sb @@ -0,0 +1,76 @@ +#!/bin/sh +# Copyright 2023 Balena Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# vi: ft=sh + +# shellcheck disable=SC1091 +[ -f "/usr/libexec/os-helpers-logging" ] && . /usr/libexec/os-helpers-logging +# shellcheck disable=SC1091 +[ -f "/usr/libexec/os-helpers-otp" ] && . /usr/libexec/os-helpers-otp + +BALENA_NONENCRYPTED_BOOT_PARTITION_FILES="\ + boot.img \ + boot.scr \ + boot.sig \ + config.txt \ + @@DEVICE_TREES@@ \ + @@BALENA_IMAGE_FLAG_FILE@@ \ + extra_uEnv.txt \ + @@KERNEL_IMAGETYPE@@ \ + bootenv \ +" + +is_secured() { + # Check the private key has been programmed + if [ -n "$(otp_read_key | sed s/0//g)" ]; then + # Check that the RSA digest has been programmed + # the development key revoked + # and the bootloader is configured for signed boot + if otp_has_pubkey_digest && otp_is_devkey_revoked && otp_is_signed_boot; then + return 0 + fi + fi + return 1 +} + +do_skip() { + _file="$1" + for b in $BALENA_NONENCRYPTED_BOOT_PARTITION_FILES; do + # See if file is in the non-encrypted boot partition file list + if [ "$b" = "$(basename "$_file")" ]; then + # If so, skip (return 0) if running the 1-bootfiles script that copies files + # to the non-encrypted /mnt/boot partition + # If running a different script, return 1 not to skip it + test "$(basename $0)" = "1-bootfiles" + return + fi + done + # The file is in the encrypted boot partition file list, so do not skip (return 1) if running + # the 1-bootfiles script that copies files to the encrypted /mnt/boot partition + # If running a different script, return 0 to skip it + test "$(basename "$0")" != "1-bootfiles" +} + +get_bootloader_version() { + printf "%d" "0x$(od "/proc/device-tree/chosen/bootloader/build-timestamp" -v -An -t x1 | tr -d ' ' )" +} + +print_bootloader_config() { + blconfig_ofnode_path="/sys/firmware/devicetree/base"$(strings "/sys/firmware/devicetree/base/aliases/blconfig")"" + blconfig_nvmem_path=$(dirname $(find -L /sys/bus/nvmem -maxdepth 3 -samefile "${blconfig_ofnode_path}" 2>/dev/null)) + if [ -n "${blconfig_nvmem_path}" ]; then + cat "${blconfig_nvmem_path}"/nvmem + fi +}