Skip to content

samples: mcuboot: Add firmware loader entrance sample #23318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@
/samples/ipc/ipc_service/ @nrfconnect/ncs-si-muffin
/samples/keys/ @nrfconnect/ncs-aegir
/samples/matter/ @nrfconnect/ncs-matter
/samples/mcuboot/ @nrfconnect/ncs-pluto
/samples/mpsl/ @nrfconnect/ncs-dragoon
/samples/net/ @nrfconnect/ncs-cia @nrfconnect/ncs-modem
/samples/nfc/ @nrfconnect/ncs-si-muffin
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
/delete-property/ led0;
/delete-property/ led3;
/delete-property/ sw0;
/delete-property/ mcuboot-button0;
/delete-property/ mcuboot-led0;
};
};

Expand Down
8 changes: 8 additions & 0 deletions cmake/sysbuild/image_signing_firmware_loader.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ function(zephyr_mcuboot_tasks)
set(imgtool_extra)
endif()

if(CONFIG_SOC_SERIES_NRF54LX AND CONFIG_MCUBOOT_BOOTLOADER_SIGNATURE_TYPE_ED25519)
if(NOT CONFIG_MCUBOOT_BOOTLOADER_SIGNATURE_TYPE_PURE)
set(imgtool_extra --sha 512 ${imgtool_extra})
else()
set(imgtool_extra --pure ${imgtool_extra})
endif()
endif()

if(NOT "${keyfile}" STREQUAL "")
set(imgtool_extra -k "${keyfile}" ${imgtool_extra})
endif()
Expand Down
12 changes: 12 additions & 0 deletions samples/mcuboot/firmware_loader_entrance/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#
# Copyright (c) 2025 Nordic Semiconductor
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(firmware_loader_entrance)

target_sources(app PRIVATE src/main.c)
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
mcuboot:
address: 0x0
region: flash_primary
size: 0x8000
app:
address: 0x8200
region: flash_primary
size: 0x5fe00
mcuboot_pad:
address: 0x8000
region: flash_primary
size: 0x200
mcuboot_primary:
address: 0x8000
orig_span: &id001
- app
- mcuboot_pad
region: flash_primary
size: 0x60000
span: *id001
mcuboot_primary_app:
address: 0x8200
orig_span: &id002
- app
region: flash_primary
size: 0x5fe00
span: *id002
mcuboot_secondary:
address: 0x68000
orig_span: &id003
- mcuboot_secondary_pad
- firmware_loader
region: flash_primary
size: 0x60000
span: *id003
mcuboot_secondary_pad:
region: flash_primary
address: 0x68000
size: 0x200
firmware_loader:
region: flash_primary
address: 0x68200
size: 0x5fe00
settings_storage:
address: 0xc8000
region: flash_primary
size: 0x4000
unallocated:
address: 0xcc000
region: flash_primary
size: 0x34000
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
mcuboot:
address: 0x0
region: flash_primary
size: 0xa000
app:
address: 0xa800
region: flash_primary
size: 0x60000
mcuboot_pad:
address: 0xa000
region: flash_primary
size: 0x800
mcuboot_primary:
address: 0xa000
orig_span: &id001
- app
- mcuboot_pad
region: flash_primary
size: 0x60800
span: *id001
mcuboot_primary_app:
address: 0xa800
orig_span: &id002
- app
region: flash_primary
size: 0x60000
span: *id002
mcuboot_secondary:
address: 0x6a800
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might misunderstand the point of the Firmware Loader, but:
One of the main advantages of having two different images is that we can save space.
If this is the case, the example should showcase a smaller secondary slot than primary slot.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually it's a very good point. Actually there is only primary and firmware loader partition used as bootable images.

orig_span: &id003
- mcuboot_secondary_pad
- firmware_loader
region: flash_primary
size: 0x60800
span: *id003
mcuboot_secondary_pad:
region: flash_primary
address: 0x6a800
size: 0x800
firmware_loader:
region: flash_primary
address: 0x6b000
size: 0x60000
settings_storage:
address: 0xcb000
region: flash_primary
size: 0x4000
unallocated:
address: 0xcf000
region: flash_primary
size: 0x96000
35 changes: 35 additions & 0 deletions samples/mcuboot/firmware_loader_entrance/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Enable MCUmgr and dependencies.
CONFIG_NET_BUF=y
CONFIG_ZCBOR=y
CONFIG_MCUMGR=y
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y

# Allow for large Bluetooth data packets.
CONFIG_BT_L2CAP_TX_MTU=498
CONFIG_BT_BUF_ACL_RX_SIZE=502
CONFIG_BT_BUF_ACL_TX_SIZE=502
CONFIG_BT_CTLR_DATA_LENGTH_MAX=251

# Enable the Bluetooth mcumgr transport (unauthenticated).
CONFIG_MCUMGR_TRANSPORT_BT=y
CONFIG_MCUMGR_TRANSPORT_BT_CONN_PARAM_CONTROL=y

# Enable the mcumgr Packet Reassembly feature over Bluetooth and its configuration dependencies.
# MCUmgr buffer size is optimized to fit one SMP packet divided into five Bluetooth Write Commands,
# transmitted with the maximum possible MTU value: 498 bytes.
CONFIG_MCUMGR_TRANSPORT_BT_REASSEMBLY=y
CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE=2475
CONFIG_MCUMGR_GRP_OS_MCUMGR_PARAMS=y
CONFIG_MCUMGR_TRANSPORT_WORKQUEUE_STACK_SIZE=4608

# Enable OS management with bootloader information and boot mode support
CONFIG_MCUMGR_GRP_OS=y
CONFIG_MCUMGR_GRP_OS_RESET_BOOT_MODE=y
CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO=y

# Disable Bluetooth ping support
CONFIG_BT_CTLR_LE_PING=n

# Enable logging
CONFIG_LOG=y
16 changes: 16 additions & 0 deletions samples/mcuboot/firmware_loader_entrance/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
sample:
name: Firmware loader entrance
description: Demo application for entering the firmware loader application
common:
sysbuild: true
build_only: true
tests:
sample.mcuboot.firmware_loader_entrance:
harness: bluetooth
tags: bluetooth
platform_allow:
- nrf52840dk/nrf52840
- nrf54l15dk/nrf54l15/cpuapp
integration_platforms:
- nrf52840dk/nrf52840
- nrf54l15dk/nrf54l15/cpuapp
183 changes: 183 additions & 0 deletions samples/mcuboot/firmware_loader_entrance/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/mgmt/mcumgr/transport/smp_bt.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(firmware_entrance_loader, LOG_LEVEL_DBG);

static struct k_work adv_work;

static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, SMP_BT_SVC_UUID_VAL),
};

static const struct bt_data sd[] = {
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, (sizeof(CONFIG_BT_DEVICE_NAME) - 1)),
};

static void adv_work_handler(struct k_work *work)
{
int rc = bt_le_adv_start(BT_LE_ADV_CONN_FAST_2, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));

if (rc) {
LOG_ERR("Advertising failed to start: %d", rc);
return;
}

LOG_INF("Advertising successfully started, device name: %s", CONFIG_BT_DEVICE_NAME);
}

static void advertising_start(void)
{
k_work_submit(&adv_work);
}

static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
LOG_ERR("Connection failed: 0x%02x (%s)", err, bt_hci_err_to_str(err));
return;
}

LOG_INF("Connected");
}

static void disconnected(struct bt_conn *conn, uint8_t reason)
{
LOG_INF("Disconnected, reason: 0x%02x (%s)", reason, bt_hci_err_to_str(reason));
}

static void recycled_cb(void)
{
LOG_INF("Connection object available from previous conn. Disconnect is complete!");
advertising_start();
}

#ifdef CONFIG_BT_SMP
static void security_changed(struct bt_conn *conn, bt_security_t level,
enum bt_security_err err)
{
char addr[BT_ADDR_LE_STR_LEN];

bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

if (!err) {
LOG_INF("Security changed: %s level %u", addr, level);
} else {
LOG_INF("Security failed: %s level %u error: %d (%s)", addr, level, err,
bt_security_err_to_str(err));
}
}
#endif

BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
.recycled = recycled_cb,
#ifdef CONFIG_BT_SMP
.security_changed = security_changed,
#endif
};

#if defined(CONFIG_BT_SMP)
static void auth_passkey_display(struct bt_conn *conn, unsigned int passkey)
{
char addr[BT_ADDR_LE_STR_LEN];

bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

LOG_INF("Passkey for %s: %06u", addr, passkey);
}

static void auth_cancel(struct bt_conn *conn)
{
char addr[BT_ADDR_LE_STR_LEN];

bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

LOG_INF("Pairing cancelled: %s", addr);
}

static void pairing_complete(struct bt_conn *conn, bool bonded)
{
char addr[BT_ADDR_LE_STR_LEN];

bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

LOG_INF("Pairing completed: %s, bonded: %d", addr, bonded);
}

static void pairing_failed(struct bt_conn *conn, enum bt_security_err reason)
{
char addr[BT_ADDR_LE_STR_LEN];

bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));

LOG_INF("Pairing failed conn: %s, reason: %d (%s)", addr, reason,
bt_security_err_to_str(reason));
}

static struct bt_conn_auth_cb conn_auth_callbacks = {
.passkey_display = auth_passkey_display,
.cancel = auth_cancel,
};

static struct bt_conn_auth_info_cb conn_auth_info_callbacks = {
.pairing_complete = pairing_complete,
.pairing_failed = pairing_failed
};
#else
static struct bt_conn_auth_cb conn_auth_callbacks;
static struct bt_conn_auth_info_cb conn_auth_info_callbacks;
#endif

int main(void)
{
int rc;

if (IS_ENABLED(CONFIG_BT_SMP)) {
rc = bt_conn_auth_cb_register(&conn_auth_callbacks);

if (rc) {
LOG_ERR("Failed to register authorization callbacks.");
return 0;
}

rc = bt_conn_auth_info_cb_register(&conn_auth_info_callbacks);

if (rc) {
LOG_ERR("Failed to register authorization info callbacks.");
return 0;
}
}

rc = bt_enable(NULL);

if (rc) {
LOG_ERR("Bluetooth init failed: %d", rc);
return 0;
}

LOG_INF("Bluetooth initialized");

if (IS_ENABLED(CONFIG_SETTINGS)) {
settings_load();
}

k_work_init(&adv_work, adv_work_handler);
advertising_start();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest that main prints out something along the lines of "Reset the device while holding Button 0/1 to enter DFU mode", so users understand how to enter firmware loader mode.

return 0;
}
10 changes: 10 additions & 0 deletions samples/mcuboot/firmware_loader_entrance/sysbuild.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

SB_CONFIG_BOOTLOADER_MCUBOOT=y
SB_CONFIG_MCUBOOT_MODE_FIRMWARE_UPDATER=y
SB_CONFIG_MCUBOOT_MODE_FIRMWARE_UPDATER_BOOT_MODE_ENTRANCE=y
SB_CONFIG_FIRMWARE_LOADER_IMAGE_SMP_SVR_MINI_BOOT=y
Loading