Zephyrのリンカスクリプトを修正する
nRF52840 Dongle (pca10059)というNordicのボードに、Zephyrを乗せて遊んでいます。
Zephyrのリンカスクリプトがおかしいらしく、うまく動作しなかったのですが、修正方法まで含めて動かす方法が判明したので、まとめます。
Zephyr blinky sample
Zephyrは、samples下に多くのサンプルがあります。
zephyr/samples/basic/blinky
は、pca10059でも動作する様子だったので、このサンプルから動かすことにしました。
このサンプルを動かすと、ボード上の緑のLEDがチカチカします。
今回は説明しませんが、Zephyrをビルドできる環境、ARMクロスコンパイラ、nRFのFlash書き込みツール(nrfutil)が必要です。 この条件がそろえば、下のコマンドでビルドすることが可能です。
# at blinky directory mkdir build && cd build cmake -GNinja -DBOARD=nrf52840_pca10059 .. ninja nrfutil pkg generate --hw-version 52 --sd-req=0x00 --application zephyr/zephyr.hex --application-version 1 pkg.zip nrfutil dfu usb-serial -pkg pkg.zip -p /dev/ttyACM0
ただ、この手順でできたバイナリでは、動作しません。
リンカスクリプト
上記コマンドのビルドで使用されているリンカスクリプトを見てみます。
|> build/zephyr/linker.cmd
$ head -n 10 zephyr/linker.cmd OUTPUT_FORMAT("elf32-littlearm") _region_min_align = 32; MEMORY { FLASH (rx) : ORIGIN = (0x0 + 0), LENGTH = (1024*1K - 0) SRAM (wx) : ORIGIN = 0x20000000, LENGTH = (256 * 1K) IDT_LIST (wx) : ORIGIN = (0x20000000 + (256 * 1K)), LENGTH = 2K } ENTRY("__start") SECTIONS
FLASHのORIGINが0x0番地から始まっていますが、pca10059プログラミングチュートリアルなどを参照すると、0x0 - 0xFFFの先頭4KBはMBR (Master Boot Record)のエリアとなっており、ここには書き込みしてはいけない、とあります。 試しに、手動で、リンカスクリプトを次のように編集して、ビルドすると、うまく動きます。
$ head -n 10 zephyr/linker.cmd OUTPUT_FORMAT("elf32-littlearm") _region_min_align = 32; MEMORY { FLASH (rx) : ORIGIN = (0x0 + 0x1000), LENGTH = (1024*1K - 0x1000) SRAM (wx) : ORIGIN = 0x20000000, LENGTH = (256 * 1K) IDT_LIST (wx) : ORIGIN = (0x20000000 + (256 * 1K)), LENGTH = 2K } ENTRY("__start") SECTIONS
先頭を後ろに4KBずらした分、FLASHの容量を4KB減らしています。
これで手動ではうまくいきますが、毎回リンカスクリプトを編集するのは面倒です。 そこで、Zephyrのビルドプロセスを解析して、ビルドすると自動で上記リンカスクリプトが生成されるようにします。
nRF52リンカスクリプト
nRF52から始まるSoCのリンカスクリプトは、下記にあります。
|> zephyr/soc/arm/nordic_nrf/nrf52/linker.ld
/* linker.ld - Linker command/script file */ /* * Copyright (c) 2014 Wind River Systems, Inc. * * SPDX-License-Identifier: Apache-2.0 */ #include <arch/arm/cortex_m/scripts/linker.ld>
中身を見ると、cortex_mのリンカスクリプトをincludeしているだけです。そこで、cortex_mのリンカスクリプトを見てみます。
|> zephyr/include/arch/arm/cortex_m/scripts/linker.ld
MEMORY { FLASH (rx) : ORIGIN = ROM_ADDR, LENGTH = ROM_SIZE ... SRAM (wx) : ORIGIN = RAM_ADDR, LENGTH = RAM_SIZE /* Used by and documented in include/linker/intlist.ld */ IDT_LIST (wx) : ORIGIN = (RAM_ADDR + RAM_SIZE), LENGTH = 2K }
ROM_ADDR
やROM_SIZE
はマクロになっており、これらのマクロは、同ファイルで下記のように定義されています。
#define ROM_ADDR (CONFIG_FLASH_BASE_ADDRESS + CONFIG_FLASH_LOAD_OFFSET) #ifdef CONFIG_TI_CCFG_PRESENT ... #else #if CONFIG_FLASH_LOAD_SIZE > 0 #define ROM_SIZE CONFIG_FLASH_LOAD_SIZE #else #define ROM_SIZE (CONFIG_FLASH_SIZE*1K - CONFIG_FLASH_LOAD_OFFSET) #endif #endif
ここで、CONFIG_FLASH_LOAD_OFFSET
マクロを4Kに設定できれば、最終的なリンカスクリプトが望むものになることが分かりました。
Zephyrのソースを検索すると、Kconfig.zephyr
で次のようになっていました。
if !HAS_DTS config FLASH_LOAD_OFFSET hex "Kernel load offset" default 0 depends on HAS_FLASH_LOAD_OFFSET help This option specifies the byte offset from the beginning of flash that the kernel should be loaded into. Changing this value from zero will affect the Zephyr image's link, and will decrease the total amount of flash available for use by application code. If unsure, leave at the default value 0.
device treeがない場合は、このKconfigでCONFIG_FLASH_LOAD_OFFSET
を設定することが可能です。
今回は、device treeを使用しているため、device treeで設定する方法を調べます。
Zephyr Doc Device Tree Flash PartitionsのLinking Zephyr Within a Partitionを見ると、
If the chosen node has no zephyr,code-partition property, the application image link uses the entire flash device. If a zephyr,code-partition property is defined, the application link will be restricted to that partition.
とあり、device tree内でFLASHのパーティションを作成し、そのパーティションをchosen
内でcode-partition
プロパティに設定してあげると良さそうなことが分かりました。
pca10059のdevice treeを見てみます。
|> zephyr/boards/arm/nrf52840_pca10059/nrf52840_pca10059.dts
&flash0 { /* * For more information, see: * http://docs.zephyrproject.org/latest/devices/dts/flash_partitions.html */ partitions { compatible = "fixed-partitions"; #address-cells = <1>; #size-cells = <1>; boot_partition: partition@e0000 { label = "mcuboot"; reg = <0x0000e0000 0x0001a000>; }; slot0_partition: partition@1000 { label = "image-0"; reg = <0x00001000 0x000060000>; }; slot1_partition: partition@61000 { label = "image-1"; reg = <0x0061000 0x000060000>; }; scratch_partition: partition@c1000 { label = "image-scratch"; reg = <0x000c1000 0x0001f000>; }; storage_partition: partition@fa000 { label = "storage"; reg = <0x000fa000 0x00004000>; }; }; };
すでにパーティションが作られており、slot0_partition
が0x1000から始まっています。
chosenでzephyr,code-partition
をこのパーティションにしてあげます。
chosen { zephyr,console = &uart0; zephyr,uart-mcumgr = &uart0; zephyr,sram = &sram0; zephyr,flash = &flash0; zephyr,code-partition = &slot0_partition; // 追加 };
もう一度ビルドすると、無事、リンカスクリプトが望み通り作られています。
OUTPUT_FORMAT("elf32-littlearm") _region_min_align = 32; MEMORY { FLASH (rx) : ORIGIN = (0x0 + 4096), LENGTH = 393216 SRAM (wx) : ORIGIN = 0x20000000, LENGTH = (256 * 1K) IDT_LIST (wx) : ORIGIN = (0x20000000 + (256 * 1K)), LENGTH = 2K } ...
これで、リンカスクリプトを手動で修正しなくて済むようになりました。