4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

FRDM-K64F上でwolfBootのOTAを動かしてみた

Last updated at Posted at 2020-10-27

はじめに

組込み向け暗号ライブラリを提供しているwolfSSL社のラインナップに、セキュアなOTAが可能なwolfBootというライブラリがあります。IoTの場面で需要が増すであろうSecure OTAの一例として、試しに動かしてみました。
wolfBoot

以下の場所にFRDM-K64Fで動かせるサンプルがあるのでこれを使ってみました。
https://github.com/wolfSSL/wolfBoot-examples/tree/master/freeRTOS-Freescale-K64F-https-TLS1.3

サンプルなのですぐ動くだろうと思っていたら意外と苦労したので、実施内容を記録しておきます(ただし、一番最初に苦労したのはこれ:FRDM-K64Fの修復(bootloader update))。

【今回の環境】

  • FRDM-K64F
  • Ubuntu 20
  • GNU Arm Embedded Toolchain version 9-2020-q2-update
  • MCUXpresso SDK v2.8.2
  • wolfBoot v1.6
  • wolfSSL v4.5.0
  • PicoTCP v1.7.0

wolfBootの仕組み

自分の理解のために大まかな仕組みの絵を書いてみました。
FlashのアドレスはFRDM-K64F用のサンプルで使われているもので、環境によって調整が必要です。

image.png

必要資材の入手

wolfBootサンプル

githubからサンプルコード一式を持ってきます。

cd ~
git clone https://github.com/wolfSSL/wolfBoot-examples.git

サンプルは以下の構成になっていますが、今回はこの中のfreeRTOS-Freescale-K64F-https-TLS1.3を使います。使い方はこの中のREADME.mdにざっくり書いてあります。

wolfBoot-examples
 ├ contiki-nrf52
 ├ freeRTOS-Freescale-K64F-https-TLS1.3
 │  ├ freeRTOS
 │  ├ picotcp  
 │  ├ png
 │  └ src
 ├ riotOS-samr21
 └ wolfBoot
   └ lib
     └ wolfssl

wolfBoot

サンプルの中にwolfBootというディレクトリがありますが、中身が空だったのでgithubからwolfBootを入手してここに設置します。

cd ~
git clone https://github.com/wolfSSL/wolfBoot.git
mv wolfBoot wolfBoot-examples/

wolfSSL

wolfBootの中でwolfSSLを使いますが、中身が空だったのでgithubからwolfSSLを入手して設置します。

cd ~
git clone https://github.com/wolfSSL/wolfssl.git
mv wolfssl wolfBoot-examples/wolfBoot/lib/

PicoTCP

このサンプルはTCP/IP stackとしてPicoTCPを使いますが、こちらもディレクトリの中身が空だったのでgithubから入手して設置します。

cd ~
git clone https://github.com/tass-belgium/picotcp.git
mv picotcp wolfBoot-examples/freeRTOS-Freescale-K64F-https-TLS1.3/

FRDM-K64F用MCUXpresso SDK

ボード用のSDKも必要です。NXP社のSDK Builderからzipでダウンロードして展開しておきます。
SDK Builder

cd ~
unzip SDK_2.8.2_FRDM-K64F.zip

Toolchain

ToolchainはArm社のページからダウンロードして展開して、パスを通しておきます。
GNU Arm Embedded Toolchain Downloads

tar -xf gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2
echo export PATH='~/gcc-arm-none-eabi-9-2020-q2-update/bin:"$PATH"' >> ~/.bashrc
source ~/.bashrc

cu

Ubuntu上でシリアル出力を確認するためにcuコマンドの設定もしておきました。
設定にあたってはこちらを参考に。
LinuxPCでserial通信をしようとした話

sudo apt-get install cu
ls -l /dev/serial/by-id/
cu -s 115200 -l /dev/ttyACM0

ビルド → 一部修正

これでビルドできると思いきや、コンパイルエラーが出たりしたのでいくつか修正しました。

cd ~/wolfBoot-examples/freeRTOS-Freescale-K64F-https-TLS1.3/
make KINETIS=~/SDK_2.8.2_FRDM-K64F

MCUXpresso SDK API呼出しの修正

そのままだと以下のコンパイルエラーが発生。

src/pico_enet_kinetis.c: In function 'enet_poll':
src/pico_enet_kinetis.c:167:13: error: too few arguments to function 'ENET_GetRxFrameSize'
  167 |         if (ENET_GetRxFrameSize(&g_handle, &size) == kStatus_ENET_RxFrameEmpty)
      |             ^~~~~~~~~~~~~~~~~~~
src/pico_enet_kinetis.c:169:9: error: too few arguments to function 'ENET_ReadFrame'
  169 |         ENET_ReadFrame(enet->base, &g_handle, g_frame, size);
      |         ^~~~~~~~~~~~~~
src/pico_enet_kinetis.c: In function 'enet_send':
src/pico_enet_kinetis.c:179:9: error: too few arguments to function 'ENET_SendFrame'
  179 |     if (ENET_SendFrame(enet->base, &g_handle, buf, len) != kStatus_ENET_TxFrameBusy)
      |         ^~~~~~~~~~~~~~

ENET_???はMCUXpresso SDKのAPI。API Reference Manualを見ると、multiple-ringサポートのためにringIdを指定する引数が追加された模様。Single ringの場合は0とかNULLとかを追加すれば良いようなので、上記3ヶ所の関数呼出しを修正。

(修正前)src/pico_enet_kinetis.c
ENET_GetRxFrameSize(&g_handle, &size)
ENET_ReadFrame(enet->base, &g_handle, g_frame, size)
ENET_SendFrame(enet->base, &g_handle, buf, len)
(修正後)src/pico_enet_kinetis.c
ENET_GetRxFrameSize(&g_handle, &size, 0)
ENET_ReadFrame(enet->base, &g_handle, g_frame, size, 0, NULL)
ENET_SendFrame(enet->base, &g_handle, buf, len, 0, false, NULL)

wolfBoot本体のコンパイルエラー対応

wolfBoot本体のmakeでエラーが出てしまいました。MCUXpresso SDKのインクルードファイルが読み込めていません。

hal/kinetis.c:25:10: fatal error: fsl_common.h: No such file or directory
   25 | #include "fsl_common.h"
      |          ^~~~~~~~~~~~~~

wolfBoot本体のMakefileから参照される../wolfBoot/arch.mkに、target別のコンパイルオプションが定義されていますが、そこで参照している変数が定義されていないことが原因。これら環境変数はサンプルのsrc/wolfboot.configに定義しておくのが良さそうなので、以下の記述を追記。

(追記内容)src/wolfboot.config
MCUXPRESSO?=$(KINETIS)
MCUXPRESSO_DRIVERS?=$(MCUXPRESSO)/devices/MK64F12
MCUXPRESSO_CMSIS?=$(MCUXPRESSO)/CMSIS
MCUXPRESSO_CPU?=MK64FN1M0VLL12

デバッグメッセージの追加

動かしたときに処理がどう進んでいるかわからなかったので、シリアルポートにデバッグメッセージを出すようにしました。参考にしたのはMCUXpresso SDKに付属のデモアプリhello_world

main()関数の初期フェーズでBOARD_InitDebugConsole()を実行します。BOARD_InitDebugConsole()の実体はMCUXpresso SDKのdevices\MK64F12\project_template\board.cから該当部分だけコピー。
これで、任意の場所にPRINTF("message");と書くことでシリアルポートにメッセージ出力できるようになりました。

(追記後)src/main.c
# include "fsl_debug_console.h"
(...snip...)
void BOARD_InitDebugConsole(void)
{
    uint32_t uartClkSrcFreq = BOARD_DEBUG_UART_CLK_FREQ;
    DbgConsole_Init(BOARD_DEBUG_UART_INSTANCE, BOARD_DEBUG_UART_BAUDRATE, BOARD_DEBUG_UART_TYPE, uartClkSrcFreq);
}

int main(void) {
    BOARD_InitPins();
    BOARD_BootClockRUN();
    BOARD_InitDebugConsole();
    PRINTF("main(): START\r\n");
(...snip...)
}

このDebugConsoleの仕組みを使うためには、インクルードディレクトリやオブジェクトファイルの追加が必要なので、Makefileの該当箇所に以下の通り追記。

(追記前)Makefile.
CFLAGS+=-I$(KINETIS_DRIVERS)/drivers -I$(KINETIS_DRIVERS) -DCPU_MK64FN1M0VLL12 -I$(KINETIS_CMSIS)/Include -I$(PHY) -DDEBUG_CONSOLE_ASSERT_DISABLE=1
(...snip...)
OBJS:= \
  $(KINETIS_DRIVERS)/drivers/fsl_clock.o \
(...snip...)
(追記後)Makefile.
CFLAGS+=-I$(KINETIS_DRIVERS)/drivers -I$(KINETIS_DRIVERS) -DCPU_MK64FN1M0VLL12 -I$(KINETIS_CMSIS)/Include -I$(PHY) -DDEBUG_CONSOLE_ASSERT_DISABLE=1
CFLAGS+=-DSDK_DEBUGCONSOLE=1 -DPRINTF_FLOAT_ENABLE=0 -DSERIAL_PORT_TYPE_UART=1 -I$(KINETIS)/components/uart -I$(KINETIS)/components/serial_manager -I$(KINETIS_DRIVERS)/utilities/str -I$(KINETIS_DRIVERS)/utilities/debug_console
(...snip...)
OBJS:= \
  $(KINETIS)/components/uart/uart_adapter.o \
  $(KINETIS)/components/serial_manager/serial_manager.o \
  $(KINETIS)/components/serial_manager/serial_port_uart.o \
  $(KINETIS_DRIVERS)/utilities/str/fsl_str.o \
  $(KINETIS_DRIVERS)/utilities/debug_console/fsl_debug_console.o \
  $(KINETIS_DRIVERS)/drivers/fsl_uart.o \
  $(KINETIS_DRIVERS)/drivers/fsl_clock.o \  
(...snip...)

make cleanの修正

理由は分かってないのですが、一部を修正してmakeするとリンク時にld: error: image.elf uses VFP register arguments, ???.o does notというエラーが出てしまうため、ビルド時には一旦すべてのオブジェクトファイルを削除するのが良さそう。
make KINETIS=~/SDK_2.8.2_FRDM-K64F cleanとしただけだと、MCUXpresso SDK内にできたオブジェクトファイルが削除されなかったため、以下の通り修正。

(追記前)Makefile.
clean:
        rm -f *.bin *.elf $(OBJS) wolfboot.map *.bin  *.hex src/*.o freeRTOS/*.o $(FREERTOS_PORT)/*.o *.map tags
(追記後)Makefile.
clean:
        rm -f *.bin *.elf $(OBJS) wolfboot.map *.bin  *.hex src/*.o freeRTOS/*.o $(FREERTOS_PORT)/*.o *.map tags
        rm -f $(PHY)/*.o $(KINETIS_DRIVERS)/drivers/*.o $(KINETIS)/components/uart/*.o $(KINETIS)/components/serial_manager/*.o $(KINETIS_DRIVERS)/utilities/str/*.o $(KINETIS_DRIVERS)/utilities/debug_console/*.o

OTA実験

ビルド

ようやくビルドが通るようになりました。
実験前にもう一つ、IPアドレスの修正が必要です。

(修正前)src/main.c
    pico_string_to_ipv4("192.168.178.211", &addr.addr);
    pico_string_to_ipv4("255.255.255.0", &mask.addr);
    pico_string_to_ipv4("192.168.178.1", &gw.addr);

デフォルトでは192.168.178.211を使うようになっているので、ここを利用するNWに合わせて修正してから、ビルド。

cd ~/wolfBoot-examples/freeRTOS-Freescale-K64F-https-TLS1.3/
make KINETIS=~/SDK_2.8.2_FRDM-K64F

ビルドに成功すると以下のファイルができます。

wolfboot-align.bin    ... wolfBoot Bootloader
image.bin             ... firmwareイメージ(署名前)
image_v1_signed.bin   ... sign.pyで署名入りヘッダを付加したもの(v1)
image_v2_signed.bin   ... sign.pyで署名入りヘッダを付加したもの(v2)
factory.bin           ... Flashに書き込むためにwolfboot-align.binとimage_v1_signed.binをcatでつなげたもの

以降の実験用に、v3,v4のイメージも作っておきました。

firmware v1を直接書込み、起動

cp factory.bin /media/???/DAPLINK/

リセットスイッチを押すと、シリアルにメッセージが表示されました。うまく動いているようです。

main(): START
main(): PicoTask is Created
main(): MainTask is Created

Firefoxでhttpsアクセスすると画面が表示されました(理由は不明ですがChromeやEdgeではアクセスできず)。

image.png

firmware v2をOTA書込み

次はWeb画面からimage_v2_signed.binをアップロードします。
以下はシリアルに出力された処理の進行状況。アップロードが終わるまで3分ほどかかります。

https_request(): START
https_request(): "POST " is detected
parse_update(): START
parse_update(): filename="image_v2_signed.bin"
parse_update(): Start to write to flash. Size:    3641C
parse_update(): Writing...     800
parse_update(): Writing...    1000
parse_update(): Writing...    1800
(...snip...)
parse_update(): Writing...   35800
parse_update(): Writing...   36000
parse_update(): Writing...   3641C
parse_update(): Finish to write to flash. Rebooting...
main(): START
main(): PicoTask is Created
main(): MainTask is Created
https_request(): START
https_request(): "GET /" is detected
set_version(): FW version: 2
https_request(): END

アップロード後、リブートの所要時間は15秒ほど。この間にBoot PartitionとUpdate Partitionの内容を交換しているものと思われます。
リブート後はバージョンが2に上がってます。

image.png

firmware v3は一部改ざんしてからOTA書込みしてみる

image_v3_signed.binについては、バイナリエディタで中身を一部を改ざんしてからアップロードしてみました。

https_request(): START
https_request(): "POST " is detected
parse_update(): START
parse_update(): filename="image_v3_signed.bin"
parse_update(): Start to write to flash. Size:    3641C
parse_update(): Writing...     800
parse_update(): Writing...    1000
parse_update(): Writing...    1800
(...snip...)
parse_update(): Writing...   35800
parse_update(): Writing...   36000
parse_update(): Writing...   3641C
parse_update(): Finish to write to flash. Rebooting...
main(): START
main(): PicoTask is Created
main(): MainTask is Created
https_request(): START
https_request(): "GET /" is detected
set_version(): FW version: 2
https_request(): END

今度はリブートが一瞬で終わり、バージョンは2のままです。
Update Partitionの署名検証に失敗したことで、Boot Partitionを交換せずに起動されたようです。

改めてfirmware v1をOTA書込みしてみる

これも試しにやってみました。

https_request(): START
https_request(): "POST " is detected
parse_update(): START
parse_update(): filename="image_v1_signed.bin"
parse_update(): Start to write to flash. Size:    3641C
parse_update(): Writing...     800
parse_update(): Writing...    1000
parse_update(): Writing...    1800
(...snip...)
parse_update(): Writing...   35800
parse_update(): Writing...   36000
parse_update(): Writing...   3641C
parse_update(): Finish to write to flash. Rebooting...
main(): START
main(): PicoTask is Created
main(): MainTask is Created
https_request(): START
https_request(): "GET /" is detected
set_version(): FW version: 2
https_request(): END

今度もリブートが一瞬で終わり、バージョンは2のまま。
バージョン番号が戻っていないかどうかも見ているようです。

最後にfirmware v4をOTA書込み

https_request(): START
https_request(): "POST " is detected
parse_update(): START
parse_update(): filename="image_v4_signed.bin"
parse_update(): Start to write to flash. Size:    3641C
parse_update(): Writing...     800
parse_update(): Writing...    1000
parse_update(): Writing...    1800
(...snip...)
parse_update(): Writing...   35800
parse_update(): Writing...   36000
parse_update(): Writing...   3641C
parse_update(): Finish to write to flash. Rebooting...
main(): START
main(): PicoTask is Created
main(): MainTask is Created
https_request(): START
https_request(): "GET /" is detected
set_version(): FW version: 4
https_request(): END

バージョン2からバージョン4へ、無事にアップデートが行われました。

image.png

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?