nRF52840のDFUでSlot1がpermanentになり詰んだ話 - ダウングレードによるデバイスロック問題

この記事はLLM(GitHub Copilot)によって作成されました。

nRF Connect SDK 2.9で開発したnRF52840のファームウェアで、MCUboot/MCUmgrを使ったDFU(Device Firmware Update)中に、デバイスが完全にロックされてそれ以上DFUできなくなるという深刻なバグに遭遇しました。この問題は既にオープンソースコミュニティでも報告されていますが、ダウングレード特有のケースは明示的に議論されていないため、本記事で詳細を共有します。

発生した問題

症状: デバイスの完全なロック

バージョンが小さいファームウェアをDFUすると、デバイスが完全にロックされ、それ以上DFUできなくなるという問題が発生しました。

具体的な流れ:

1. Primary Slot: v1.0.2.2 (実行中)
2. DFU実行: v1.0.1.1 をアップロード
3. 再起動
4. MCUbootがバージョンチェック: 1.0.2.2 > 1.0.1.1 → スワップ拒否
5. Primary Slot: v1.0.2.2 のまま起動
6. Secondary Slot: v1.0.1.1 が pending 状態で残る
7. 次のDFUを試みる
8. MCUmgr: "There is no free slot to place the image" エラー
9. デッドロック状態 → DFU不可能

再現条件

以下の3つの条件が揃うと必ず発生します:

  1. ダウングレード防止が有効

    CONFIG_MCUMGR_GRP_IMG_VERSION_CMP_USE_BUILD_NUMBER=y
    
  2. バージョンが小さいイメージをDFU

    • Primary: 1.0.2.2
    • DFU: 1.0.1.1 (ダウングレード)
  3. PERMANENT modeで書き込み (nRF Connect appのデフォルト)

    • Upgrade Mode: “Confirm only”
    • Secondary slotに image_ok=SET が設定される

エラーメッセージ

nRF Connect app:
- "SMP Command Error: There is no free slot to place the image"

mcumgr CLI:
- "Error: 6" (image erase失敗)
- Upload: 0.00% で停止

根本原因: MCUbootとMCUmgrの判断基準のミスマッチ

この問題の本質は、**MCUbootとMCUmgrの設計上の「すき間」**にあります。

┌─────────────────────────────────────┐
│ MCUmgr (DFU制御)                     │
│ - Secondary slotに空きがあるかチェック │
│ - バージョン比較はしない               │
│ - pending フラグがあれば「使用中」判定  │
└─────────────┬───────────────────────┘
              │ ギャップ!
              ▼
┌─────────────────────────────────────┐
│ MCUboot (起動時のスワップ制御)         │
│ - バージョン比較を実行                 │
│ - ダウングレード検出 → スワップ拒否     │
│ - Secondary slotはそのまま残る        │
└─────────────────────────────────────┘

問題: MCUmgrは「Secondary slotにイメージがある」と判断するが、MCUbootは「スワップしない」と判断。結果、スワップされないイメージがSecondary slotに永久に残ります。

MCUmgrのスロット判定ロジック

bool is_slot_free(int slot) {
    if (slot == 1) {
        // Secondary slotをチェック
        if (magic == BOOT_MAGIC_GOOD && pending) {
            return false;  // 使用中と判定
        }
        return true;  // 空きスロット
    }
}

盲点: MCUbootがそのイメージを実際にスワップするかはチェックしない!

これは既知の問題

Apache mynewt-mcumgr Issue #157 (2021年11月)

URL: https://github.com/apache/mynewt-mcumgr/issues/157

タイトル: “img_mgmt_erase is too strict, won’t allow erasing pending secondary slot”

状況: Open(未解決)

MCUbootがスワップを拒否し、Secondary slotにpendingイメージが残る問題として報告されています。報告者は “We are now stuck with a device that can only be recovered using a debugger” と述べており、今回のケースと同様の深刻な状況です。

Zephyr Issue #58103 (2023年5月)

URL: https://github.com/zephyrproject-rtos/zephyr/issues/58103

タイトル: “mcuboot/mcumgr: board not upgradable after ‘Not enough free space to run swap upgrade’”

状況: Closed(PR #64586でマージ - 部分的解決)

Fabio Baltieri (Zephyr開発者) の評価:

“Seems pretty serious to me, may lead to devices lost and non upgradable in the field.”

優先度はpriority: mediumバグとして扱われていますが、実際の影響は非常に深刻です。

症状:

$ mcumgr image list
 image=0 slot=1
    flags: pending    # スワップされないイメージ

$ mcumgr image erase
Error: 6              # 消去拒否

$ mcumgr image upload zephyr.signed.bin
 0 B / 213.04 KiB [---] 0.00%  # 停止

今回のケースとの違い

既存報告 今回のケース 共通点
原因: イメージサイズ大 原因: バージョンダウングレード MCUbootがスワップ拒否
swap領域不足 ダウングレード防止 Secondary pendingイメージが残る
Error: 6 “no free slot” 新規DFU不可能

重要な発見: ダウングレード特有のケースは明示的に報告されていない可能性が高いです。理由として、開発環境ではCONFIG_MCUMGR_GRP_IMG_VERSION_CMP_USE_BUILD_NUMBERを無効化していることが多く、通常の運用ではバージョンを常に上げるため発生しにくいことが挙げられます。

解決方法

即座の回復(現状復帰)

方法1: JLinkでSecondary slot消去(最も確実)

# Secondary Slot全体を消去
nrfjprog --erasepage 0x85000-0xfe000

# または Image Trailerのみ消去
nrfjprog --erasepage 0xfde00-0xfe000

# リセット
nrfjprog --reset

効果: Secondary slotが物理的に消去され、MCUmgrが「空き」と認識します。

方法2: 完全消去

nrfjprog --eraseall
nrfjprog --program merged.hex --verify
nrfjprog --reset

方法3: Shellコマンド(利用可能な場合)

mcuboot erase 2

根本的な対策(再発防止)

対策1: 開発時のバージョン管理ルール(推奨)

開発中は常に BUILD 番号を上げる:
v1.0.1.1 → v1.0.2.2 → v1.0.2.3 → v1.0.2.4 ...

絶対にダウングレードしない!

対策2: ダウングレード防止を無効化(開発時のみ)

prj.conf:

# 開発時はビルド番号を無視
# CONFIG_MCUMGR_GRP_IMG_VERSION_CMP_USE_BUILD_NUMBER=y
CONFIG_MCUMGR_GRP_IMG_VERSION_CMP_USE_BUILD_NUMBER=n

注意: 本番環境では必ず有効化すること(セキュリティ上重要)

対策3: TEST modeをデフォルトにする

nRF Connect appの設定:

Upgrade Mode: Test only (Confirm onlyではなく)

効果:

  • image_ok=UNSETで書き込み
  • スワップされなければ自動的にREVERT
  • Secondary slotが自動的にクリアされる

影響範囲

深刻度

Fabio Baltieri (Zephyr開発者) の評価:

“Seems pretty serious to me, may lead to devices lost and non upgradable in the field.”

影響を受けるシステム

  • MCUboot + MCUmgr を使用するすべてのシステム
  • ダウングレード防止が有効な環境
  • nRF52/nRF53/nRF91シリーズなどNordic製品
  • Zephyr RTOS採用の組み込みシステム

特に危険なケース

  1. フィールド配備済みデバイス

    • JLinkアクセス不可
    • リモートDFUのみ
    • 完全にロックされる可能性
  2. 量産後のデバイス

    • デバッグポート無効化済み
    • 物理的な回復手段なし
  3. クリティカルシステム

    • ダウンタイム許容不可
    • 緊急修正が必要なケース

Zephyr/MCUbootプロジェクトの対応状況

PR #64586 (2023年)

  • ビルド時チェック強化: イメージサイズ超過時にビルドエラー
  • 部分的なerase許可: 特定条件下でpendingイメージの消去を許可

現在の状況(2025年)

  • ✅ イメージサイズ問題は改善
  • ダウングレードによるロックは未解決
  • ⚠️ 回避策: JLinkでの強制消去のみ

推奨される運用

開発環境:

CONFIG_MCUMGR_GRP_IMG_VERSION_CMP_USE_BUILD_NUMBER=n
# + バージョンを常に上げる運用

本番環境:

CONFIG_MCUMGR_GRP_IMG_VERSION_CMP_USE_BUILD_NUMBER=y
# + 厳格なバージョン管理
# + TEST mode でのDFU
# + 確認後に手動confirm

まとめ

nRF52840でのDFUトラブルは、MCUbootとMCUmgrの設計上の「すき間」により、スワップされないイメージがSecondary slotに残り続け、新規DFUが完全にブロックされるという問題でした。

この問題は2021年から報告されている既知のバグですが、イメージサイズケースは部分的に解決されたものの、ダウングレードケースは未解決のままです。

開発環境ではCONFIG_MCUMGR_GRP_IMG_VERSION_CMP_USE_BUILD_NUMBER=nを使用し、本番環境では厳格なバージョン管理とTEST modeでのDFUを組み合わせることを推奨します。

特にフィールド配備済みデバイスでこの問題が発生すると、物理的なデバッグアクセスなしでは回復不可能になるため、運用設計時には十分な注意が必要です。

参考リンク