diff --git a/airoha/image/Makefile b/airoha/image/Makefile
index 80059a6ebf12703413f57840eade5b411743fd6b..a601321bbbc777a5d91d06d87658f24ae8d3d8a0 100755
--- a/airoha/image/Makefile
+++ b/airoha/image/Makefile
@@ -24,7 +24,7 @@ define Device/Default-airoha-nand
   KERNEL := kernel-bin | lzma | generate-uboot-images | prepare-atf-blobs | airoha-iowrt-kernel-fit lzma external-static
   DEVICE_UBI_LAYOUT := ubinize.cfg.in
   UBINIZE_OPTS := -m 2048 -p 131072 -s 2048
-  ARTIFACTS := u-boot-nand.bin u-boot-ram.itb bootext.ram
+  ARTIFACTS := u-boot-nand.bin u-boot-recovery.bin u-boot-ram.itb bootext.ram
   IMAGE/production.img := $$(call airoha-nand-production)
 endef
 
@@ -33,7 +33,7 @@ define Device/Default-airoha-emmc
   FIT_PARTITION := 1
   KERNEL := kernel-bin | lzma | generate-uboot-images | prepare-atf-blobs | airoha-iowrt-kernel-fit lzma external-static-with-rootfs
   ARTIFACT/u-boot-emmc.fip := append-uboot-emmc
-  ARTIFACTS := u-boot-emmc.fip u-boot-nand.bin u-boot-ram.itb bootext.ram
+  ARTIFACTS := u-boot-emmc.fip u-boot-nand.bin u-boot-recovery.bin u-boot-ram.itb bootext.ram
   IMAGE/production.img := $$(call airoha-emmc-production)
 endef
 
@@ -52,6 +52,7 @@ define Device/Default-airoha-common
   UBOOT_RAM_LOADADDR := $(UBOOT_LOADADDR)
   UBOOT_RAM_COMPRESSION := lzma
   UBOOT_ENV_BLACKLIST := $(DEFAULT_UBOOT_ENV_BLACKLIST)
+  ARTIFACT/u-boot-recovery.bin := append-uboot-recovery
   ARTIFACT/u-boot-nand.bin := append-uboot-nand
   ARTIFACT/u-boot-ram.itb := append-uboot-ram | lzma | iowrt-uboot-fit lzma
   ARTIFACT/bootext.ram := append-bootext-ram
diff --git a/airoha/image/iopsys-image-common.mk b/airoha/image/iopsys-image-common.mk
index 00a610f77f63f821b0cb93aa11706b5568364f77..000f541fc0d139c6f63d62ee37a8c7431f2984da 100644
--- a/airoha/image/iopsys-image-common.mk
+++ b/airoha/image/iopsys-image-common.mk
@@ -149,9 +149,20 @@ define generate-uboot-nand
 
 	rm -rf $(CERT_PATH)-$(notdir $(1)) $(WORK_PATH)-$(notdir $(1))
 
+# create an7581 specific bootloader image for board recovery
+# --------------------------------------------------------------
+# This bootloader go directly to command line instead of loading
+# BL31/OPTEE images to the memory. This may be needed if BL31/OPTEE
+# are bad, but properly signed.
+#
+# Note: there is no difference in behavior for en7523 case
+	cp $@-u-boot-nand.bin $@-u-boot-recovery.bin
+	echo -n recovery | dd of=$@-u-boot-recovery.bin bs=$$((0x7c000)) seek=1 conv=notrunc
+
 	@echo Saving U-Boot images for $(DEVICE_NAME)
 	cp $@-bootext.ram     $(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-$(DEVICE_NAME)-bootext.ram
 	cp $@-u-boot-nand.bin $(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-$(DEVICE_NAME)-u-boot-nand.bin
+	cp $@-u-boot-recovery.bin $(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-$(DEVICE_NAME)-u-boot-recovery.bin
 endef
 
 # Generate U-Boot eMMC image
@@ -176,6 +187,10 @@ define Build/prepare-atf-blobs
 	fi
 endef
 
+define Build/append-uboot-recovery
+	dd if=$(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-$(DEVICE_NAME)-u-boot-recovery.bin >> $@
+endef
+
 define Build/append-uboot-nand
 	dd if=$(STAGING_DIR_IMAGE)/$(UBOOT_TARGET)-$(DEVICE_NAME)-u-boot-nand.bin >> $@
 endef