Zephyrのリンカスクリプトを修正する

www.mouser.jp

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_ADDRROM_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 PartitionsLinking 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
    }
...

これで、リンカスクリプトを手動で修正しなくて済むようになりました。