コグノスケ


link 未来から過去へ表示  link 過去から未来へ表示(*)

link もっと前
2020年2月13日 >>> 2020年3月14日
link もっと後

2020年2月16日

Zephyrのブート処理 - C言語と出会うまで

目次: Zephyr

以前(2020年2月1日の日記参照)Zephyrのエントリアドレスを調べましたが、もう少し先までZephyrのブート処理を調べます。復習となりますが、起動の仕方は下記のとおりです。

QEMU SpikeモードでZephyrを起動
$ qemu-system-riscv32 -nographic -machine spike -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/build/zephyr/zephyr.elf -s -S

$ riscv64-zephyr-elf-gdb zephyr/build/zephyr/zephyr.elf

...

(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x00001000 in ?? ()

ブート処理を追うにはデバッガが必須です(ブート部分でシリアル出力はできません)。これも前回の復習となりますが、QEMUはGDBでデバッグするためのオプションを持っています。オプション -sはGDBの接続をlocalhost:1234で受け付けます、という意味で、オプション -SはエミュレータをHalted状態で起動するという意味です。

GDBを接続すると、PCは0x1000を指しています。QEMU Spikeモードはリセット後0x1000から実行を開始するようです。何度かステップ実行siを行うと、0x1010から0x80000000にジャンプするはずです。

QEMUブート直後

   0x1000:      auipc   t0,0x0
   0x1004:      addi    a1,t0,32
   0x1008:      csrr    a0,mhartid
   0x100c:      lw      t0,24(t0)
=> 0x1010:      jr      t0        # 0x80000000にジャンプ

0x1000: 0x00000297      0x02028593      0xf1402573      0x0182a283
0x1010: 0x00028067      0x00000000      0x80000000      0x00000000
                                        ↑この値をロードする

特に難しい話ではなく0x1000 + 24 = 0x1018からロード(0x80000000が書かれている)して、ジャンプするだけです。ジャンプ先にはZephyrのエントリポイント __startがいます。

__start(0x80000000ジャンプ後)

// zephyr/soc/riscv/riscv-privilege/common/vector.S

SECTION_FUNC(vectors, __start)
        .option norvc;

        /*
         * Set mtvec (Machine Trap-Vector Base-Address Register)
         * to __irq_wrapper.
         */
        la t0, __irq_wrapper
        csrw mtvec, t0

        /* Jump to __initialize */
        tail __initialize


// zephyr/arch/riscv/core/isr.S

/*
 * Handler called upon each exception/interrupt/fault
 * In this architecture, system call (ECALL) is used to perform context
 * switching or IRQ offloading (when enabled).
 */
SECTION_FUNC(exception.entry, __irq_wrapper)
        /* Allocate space on thread stack to save registers */
        addi sp, sp, -__z_arch_esf_t_SIZEOF

        /*
         * Save caller-saved registers on current thread stack.
         * NOTE: need to be updated to account for floating-point registers
         * floating-point registers should be accounted for when corresponding
         * config variable is set
         */
        RV_OP_STOREREG ra, __z_arch_esf_t_ra_OFFSET(sp)
        RV_OP_STOREREG gp, __z_arch_esf_t_gp_OFFSET(sp)
        RV_OP_STOREREG tp, __z_arch_esf_t_tp_OFFSET(sp)
        RV_OP_STOREREG t0, __z_arch_esf_t_t0_OFFSET(sp)
        ...

__startは __irq_wrapper関数のアドレス(C言語の関数ではないので、関数と呼ぶのは変ですが)を割り込みベクタに設定したあと、__initializeにジャンプします。

__initialize

// zephyr/arch/riscv/core/reset.S

/*
 * Remainder of asm-land initialization code before we can jump into
 * the C domain
 */
SECTION_FUNC(TEXT, __initialize)
        /*
         * This will boot master core, just halt other cores.
         * Note: need to be updated for complete SMP support
         */
        csrr a0, mhartid
        beqz a0, boot_master_core

loop_slave_core:
        wfi
        j loop_slave_core

boot_master_core:

#ifdef CONFIG_INIT_STACKS
        ...
#endif

        /*
         * Initially, setup stack pointer to
         * _interrupt_stack + CONFIG_ISR_STACK_SIZE
         */
        la sp, _interrupt_stack
        li t0, CONFIG_ISR_STACK_SIZE
        add sp, sp, t0

#ifdef CONFIG_WDOG_INIT
        call _WdogInit
#endif

        /*
         * Jump into C domain. _PrepC zeroes BSS, copies rw data into RAM,
         * and then enters kernel z_cstart
         */
        call _PrepC

最初にmhartidというCPUコアのIDを読み取ります。0番をマスターコアとして、0番以外のコアは割り込み待ち(wfi)の無限ループに入ります。マスターコアはスタックポインタの初期化を行って、_PrepC() 関数を呼び出します。

ここからやっとC言語の世界です。

編集者:すずき(2023/09/24 12:02)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月17日

Zephyrのブート処理 - ドライバの初期化まで

目次: Zephyr

前回はリセット直後からC言語の関数と出会うところまでを紹介しました。今回はドライバの初期化までを紹介します。

最初のC言語の関数 _PrepC

// zephyr/arch/riscv/core/prep_c.c

void _PrepC(void)
{
        z_bss_zero();
#ifdef CONFIG_XIP
        z_data_copy();
#endif
#if defined(CONFIG_RISCV_SOC_INTERRUPT_INIT)
        soc_interrupt_init();
#endif
        z_cstart();
        CODE_UNREACHABLE;
}

ブート後に最初に出会うC言語の関数 _PrepC() ですが、実はまだグローバル変数の初期化が終わっていませんので、この関数で初期化しています。z_bss_zero() は、ゼロ初期化される変数の領域(BSS)を0で初期化します。z_data_copy() は、変数領域をROMからRAMにコピーします。

XIP(Execution In Place)が有効な場合、コード領域はFlash ROMなどに配置され、CPUはROMから直接コードを読み出して実行します。コード領域はそれで良いですが、変数の初期値もROMにおかれていて、そのままでは変数に書き込みできません。そのためROMからRAMにコピーする必要があります。

最後のz_start() まで来ると、やっと普通のC言語の世界です。

Zephyrの初期化処理

// zephyr/kernel/init.c

FUNC_NORETURN void z_cstart(void)
{

        ...

        /* perform basic hardware initialization */
        z_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);    //★level = 0
        z_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);


// zephyr/include/init.h

#define _SYS_INIT_LEVEL_PRE_KERNEL_1    0
#define _SYS_INIT_LEVEL_PRE_KERNEL_2    1
#define _SYS_INIT_LEVEL_POST_KERNEL     2
#define _SYS_INIT_LEVEL_APPLICATION     3

初期化関数z_cstart() は、色々ごちゃごちゃやっているのですが、真ん中あたりでドライバの初期化が行われます。レベルをPRE_KERNEL_1 (level 0), PRE_KERNEL_2 (level 1) の順に指定して、z_sys_device_do_config_level() という関数を呼びます。ちなみにPOST_KERNEL (level 2), APPLICATION (level 3) はもっと後で、カーネルの初期化が終わった後に使われます。

初期化される対象はデバイスドライバ以外(後述の、メモリ割り当てシステムドライバなど)もありますけど、仕組みは同じみたいです。

ドライバの初期化処理

// zephyr/kernel/device.c

extern struct device __device_init_start[];
extern struct device __device_PRE_KERNEL_1_start[];
extern struct device __device_PRE_KERNEL_2_start[];
extern struct device __device_POST_KERNEL_start[];
extern struct device __device_APPLICATION_start[];
extern struct device __device_init_end[];

...

void z_sys_device_do_config_level(s32_t level)
{
        struct device *info;
        static struct device *config_levels[] = {
                __device_PRE_KERNEL_1_start,
                __device_PRE_KERNEL_2_start,
                __device_POST_KERNEL_start,
                __device_APPLICATION_start,
                /* End marker */
                __device_init_end,
        };

        for (info = config_levels[level]; info < config_levels[level+1];
                                                                info++) {
                int retval;
                const struct device_config *device_conf = info->config;
                ...

実装は上記のようになっています。level 0の場合は __device_PRE_KERNEL_1_start[] という配列を先頭から処理します。他のレベルも同様ですね。

ドライバの初期化情報
======== <-- config_levels[0]
__device_PRE_KERNEL_1_start[0]
__device_PRE_KERNEL_1_start[1]
...
__device_PRE_KERNEL_1_start[n]
======== <-- config_levels[1]
__device_PRE_KERNEL_2_start[0]
__device_PRE_KERNEL_2_start[1]
...
__device_PRE_KERNEL_2_start[n]
======== <-- config_levels[2]
__device_POST_KERNEL_start[0]
__device_POST_KERNEL_start[1]
...
__device_POST_KERNEL_start[n]
======== <-- config_levels[3]
__device_APPLICATION_start[0]
__device_APPLICATION_start[1]
...
__device_APPLICATION_start[n]
======== <-- __device_init_end

このようにメモリ上にstruct deviceがレベル順に並んでいることを期待しているようです。しかし配列の実体はCのソースコード上には存在しないように見えます。これは一体何者なんでしょう?

続きはまた今度。

編集者:すずき(2023/09/24 12:02)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月18日

Zephyrのブート処理 - ドライバの初期化情報の謎

目次: Zephyr

ドライバの初期化に重要な役割を果たしている __device_PRE_KERNEL_1_start[] の謎を追っていきます。謎について復習すると「実体はバイナリにあるもののソースコードには見当たらない」という点でした。

ソースコードでわからなければバイナリを見るのも一つの手です。シンボルの一覧を調べます。

ドライバの初期化情報(シンボル)
$ riscv64-zephyr-elf-nm -n zephyr/build/zephyr/zephyr.elf

...

80002c38 D __device_PRE_KERNEL_1_start
80002c38 D __device_init_start
80002c38 d __device_sys_init_init_static_pools3
80002c44 d __device_uart_spike
80002c50 d __device_sys_init_uart_console_init0
80002c5c D __device_PRE_KERNEL_2_start
80002c5c d __device_sys_init_z_clock_driver_init0
80002c68 D __device_APPLICATION_start
80002c68 D __device_POST_KERNEL_start
80002c68 D __device_init_end

...

メモリイメージを図示すると下記のような感じで、前回のドライバの初期化処理が期待していると予想した並びになっています。違っていたら動きませんから、当たり前ですけども。

ドライバの初期化情報(メモリイメージ)
======== 80002c38 <-- __device_PRE_KERNEL_1_start, __device_init_start
__device_sys_init_init_static_pools3

======== 80002c44
__device_uart_spike

======== 80002c50
__device_sys_init_uart_console_init0

======== 80002c5c <-- __device_PRE_KERNEL_2_start
__device_sys_init_z_clock_driver_init0

======== 80002c68 <-- __device_POST_KERNEL_start, __device_APPLICATION_start, __device_init_end

POST_KERNELとAPPLICATIONは何も要素を持っておらず、同じアドレスになってしまっているものの、確かにレベル順(PRE_KERNEL_1, PRE_KERNEL_2, POST_KERNEL, APPLICATION)に並んでいます。

ドライバの初期化情報が配置されているセクション
$ riscv64-zephyr-elf-readelf -a zephyr/build/zephyr/zephyr.elf

...

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] vector            PROGBITS        80000000 000060 000010 00  AX  0   0  4
  [ 2] exceptions        PROGBITS        80000010 000070 000258 00  AX  0   0  4
  [ 3] text              PROGBITS        80000268 0002c8 002604 00  AX  0   0  4
  [ 4] sw_isr_table      PROGBITS        8000286c 0028cc 000080 00  WA  0   0  4
  [ 5] devconfig         PROGBITS        800028ec 00294c 000030 00   A  0   0  4
  [ 6] rodata            PROGBITS        8000291c 00297c 000305 00   A  0   0  4
  [ 7] datas             PROGBITS        80002c24 002c84 000014 00  WA  0   0  4
  [ 8] initlevel         PROGBITS        80002c38 002c98 000030 00  WA  0   0  4    ★ここに配置される★
  [ 9] _k_mutex_area     PROGBITS        80002c68 002cc8 000014 00  WA  0   0  4
  [10] bss               NOBITS          80002c80 002cdc 000140 00  WA  0   0  8
  [11] noinit            NOBITS          80002dc0 002cdc 000e00 00  WA  0   0 16
  ...

実体はあるのにソースコードに見当たらず、initlevelという変わった名前のセクションに置かれています。どうやら通常のグローバル変数や、static変数ではなさそうです。

一番最初に出てくる __device_sys_init_init_static_pools3がどうやって作られるのか、追いかけたらわかるかもしれません。部分一致でも良いので、それっぽい名前をgrepするとkernel/mempool.cで見つかります。

mempoolシステムドライバの宣言

// zephyr/kernel/mempool.c

SYS_INIT(init_static_pools, PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_OBJECTS);


// zephyr/include/init.h

/* A counter is used to avoid issues when two or more system devices
 * are declared in the same C file with the same init function.
 */
#define Z_SYS_NAME(init_fn) _CONCAT(_CONCAT(sys_init_, init_fn), __COUNTER__)

/**
 * @def SYS_INIT
 *
 * @brief Run an initialization function at boot at specified priority
 *
 * @details This macro lets you run a function at system boot.
 *
 * @param init_fn Pointer to the boot function to run
 *
 * @param level The initialization level, See DEVICE_AND_API_INIT for details.
 *
 * @param prio Priority within the selected initialization level. See
 * DEVICE_AND_API_INIT for details.
 */
#define SYS_INIT(init_fn, level, prio) \
        DEVICE_AND_API_INIT(Z_SYS_NAME(init_fn), "", init_fn, NULL, NULL, level,\
        prio, NULL)


// (参考1)
//  init_fn = init_static_pools
//  level   = PRE_KERNEL_1
//  prio    = CONFIG_KERNEL_INIT_PRIORITY_OBJECTS
//
//  Z_SYS_NAME(init_static_pools) = sys_init_init_static_pools3

マクロの嵐で面食らいますが、基本的に引数をそのまま渡していくだけなので1つずつ見ればさほど難しくありません。

SYS_INIT() はmempoolがシステムドライバであることを宣言するためのAPIです(公式ドキュメント Device Driver Model - Zephyr Project Documentation, System Driver節)。最終的にはDEVICE_AND_API_INIT() が呼ばれます。実はこいつもマクロです、あとで紹介します。

若干難しいのはZ_SYS_NAME() でしょうか?このマクロはsys_init_ と、引数init_fnとカウンタ(__COUNTER__)を連結したトークンを返します。

私の環境でビルドしたときはinit_fnはinit_static_poolsで、__COUNTER__ は3でしたから、sys_init_ とinit_static_poolsと3が連結され、sys_init_init_static_pools3になります。このトークンがDEVICE_AND_API_INIT() の第一引数に渡されます。

シンボル名がsys_init_init_... とinitがダブっていて、変な名前だな?と思った方もいるでしょう、このZ_SYS_NAME() マクロが原因でした。ま、それはさておいて、続きを追いかけます。

ドライバの宣言マクロ

// zephyr/include/device.h

/**
 * @def DEVICE_AND_API_INIT
 *
 * @brief Create device object and set it up for boot time initialization,
 * with the option to set driver_api.
 *
 * @copydetails DEVICE_INIT
 * @param api Provides an initial pointer to the API function struct
 * used by the driver. Can be NULL.
 * @details The driver api is also set here, eliminating the need to do that
 * during initialization.
 */
#ifndef CONFIG_DEVICE_POWER_MANAGEMENT
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info,  \
                            level, prio, api)                             \
        static const struct device_config _CONCAT(__config_, dev_name) __used \
        __attribute__((__section__(".devconfig.init"))) = {               \
                .name = drv_name, .init = (init_fn),                      \
                .config_info = (cfg_info)                                 \
        };                                                                \
        static Z_DECL_ALIGN(struct device) _CONCAT(__device_, dev_name) __used \
        __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \
                .config = &_CONCAT(__config_, dev_name),                  \
                .driver_api = api,                                        \
                .driver_data = data                                       \
        }
#else
/*
 * Use the default device_pm_control for devices that do not call the
 * DEVICE_DEFINE macro so that caller of hook functions
 * need not check device_pm_control != NULL.
 */
#define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \
                            level, prio, api)                            \
        DEVICE_DEFINE(dev_name, drv_name, init_fn,                       \
                      device_pm_control_nop, data, cfg_info, level,      \
                      prio, api)
#endif


//DEVICE_AND_API_INITを展開すると最終的に下記のようになる
//
//変数名
//  _CONCAT(__config_, dev_name) = __config_sys_init_init_static_pools3
//構造体メンバー
//  drv_name = ""
//  init_fn = init_static_pools
//  cfg_info = NULL
//なので、

static const struct device_config __config_sys_init_init_static_pools3 __used
__attribute__((__section__(".devconfig.init"))) = {
        .name = "",
        .init = init_static_pools,
        .config_info = NULL
};


//変数名
//  _CONCAT(__device_, dev_name) = __device_sys_init_init_static_pools3
//セクション名
//  level = PRE_KERNEL_1
//  prio = 30
//  ".init_" #level STRINGIFY(prio) = ".init_PRE_KERNEL_130"
//構造体メンバー
//  _CONCAT(__config_, dev_name) = __config_sys_init_init_static_pools3
//  api  = NULL
//  data = NULL
//なので、

static Z_DECL_ALIGN(struct device) __device_sys_init_init_static_pools3 __used
__attribute__((__section__(".init_PRE_KERNEL_130"))) = {
        .config = &__config_sys_init_init_static_pools3,
        .driver_api = NULL,
        .driver_data = NULL
}


//(参考2: DEVICE_AND_API_INITの引数と値)
//  dev_name = Z_SYS_NAME(init_fn) = Z_SYS_NAME(init_static_pools) = sys_init_init_static_pools3
//  drv_name = ""
//  init_fn  = init_fn = init_static_pools
//  data     = NULL
//  cfg_info = NULL
//  level    = level = PRE_KERNEL_1
//  prio     = prio = CONFIG_KERNEL_INIT_PRIORITY_OBJECTS = 30
//  api      = NULL

//(参考1: SYS_INITの引数と値)
//  init_fn = init_static_pools
//  level   = PRE_KERNEL_1
//  prio    = CONFIG_KERNEL_INIT_PRIORITY_OBJECTS = 30

ちょっと長いですがDEVICE_AND_API_INIT() を見ていくと、最後は上記のように __config_sys_init_init_static_pools3と __device_sys_init_init_static_pools3の2つの構造体の宣言に展開されることがわかります。

後者の __device_sys_init_init_static_pools3は、先程nmでzephyr.elfを見たときに出てきた、アイツです。それに加えて __device_sys_init_init_static_pools3は、.init_PRE_KERNEL_130という変な名前のセクションに配置されることもわかると思います。

理解が合っているか不安なときは、答え合わせとしてバイナリをチェックです。ビルド時に生成されるオブジェクトbuild/zephyr/kernel/CMakeFiles/kernel.dir/mempool.c.objのシンボルテーブルをobjdumpで確認するとわかりやすいです。

mempool.c.objのシンボルテーブル
$ riscv64-zephyr-elf-objdump -t zephyr/build/zephyr/kernel/CMakeFiles/kernel.dir/mempool.c.obj

...

00000000 l     O .bss.lock      00000000 lock
00000000 l    d  .devconfig.init        00000000 .devconfig.init
00000000 l     O .devconfig.init        0000000c __config_sys_init_init_static_pools3
00000000 l    d  .init_PRE_KERNEL_130   00000000 .init_PRE_KERNEL_130
00000000 l     O .init_PRE_KERNEL_130   0000000c __device_sys_init_init_static_pools3

...

セクション .init_PRE_KERNEL_130に __device_sys_init_init_static_pools3が配置されていることが確認できました。じゃあ、どうやって __device_PRE_KERNEL_1_startに含まれるようになるんでしょう?

続きはまた今度。

編集者:すずき(2023/09/24 12:03)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月19日

Zephyrのブート処理 - ドライバの初期化情報とリンカーの妙技

目次: Zephyr

引き続き、ドライバの初期化に重要な役割を果たしている __device_PRE_KERNEL_1_start[] の謎を追っていきます。謎は「__device_PRE_KERNEL_1_start[] の実体はバイナリにあるもののソースコードには見当たらない」ことです。前回まででわかったことを復習すると、

  • __device_PRE_KERNEL_1_start[] の要素である初期化情報(__device_sys_init_init_static_pools3など)は、各ドライバの宣言SYS_INIT(), DEVICE_AND_API_INIT() などのAPIで作成される
  • __device_sys_init_init_static_pools3は .init_PRE_KERNEL_130セクションに配置される

配列の要素がどうやってできたかわかったので、残る謎は下記になります。

  • __device_PRE_KERNEL_1_start[] を構成する方法(__device_sys_init_init_static_pools3などを集めてくる方法)

これまた復習になりますが、__device_PRE_KERNEL_1_start[] はinitlevelセクションに配置されていました。

ドライバの初期化情報が配置されているセクション
$ riscv64-zephyr-elf-readelf -a zephyr/build/zephyr/zephyr.elf

...

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] vector            PROGBITS        80000000 000060 000010 00  AX  0   0  4
  [ 2] exceptions        PROGBITS        80000010 000070 000258 00  AX  0   0  4
  [ 3] text              PROGBITS        80000268 0002c8 002604 00  AX  0   0  4
  [ 4] sw_isr_table      PROGBITS        8000286c 0028cc 000080 00  WA  0   0  4
  [ 5] devconfig         PROGBITS        800028ec 00294c 000030 00   A  0   0  4
  [ 6] rodata            PROGBITS        8000291c 00297c 000305 00   A  0   0  4
  [ 7] datas             PROGBITS        80002c24 002c84 000014 00  WA  0   0  4
  [ 8] initlevel         PROGBITS        80002c38 002c98 000030 00  WA  0   0  4    ★ここに配置される★
  [ 9] _k_mutex_area     PROGBITS        80002c68 002cc8 000014 00  WA  0   0  4
  [10] bss               NOBITS          80002c80 002cdc 000140 00  WA  0   0  8
  [11] noinit            NOBITS          80002dc0 002cdc 000e00 00  WA  0   0 16
  ...

思い出していただきたいのは、配列の要素は .init_PRE_KERNEL_130セクションに置かれていて、initlevelセクションではなかったことです。つまり誰かがわざわざinitlevelセクションに置き直しています。

そんな芸当ができるのはリンカーしかいませんので、initlevelをキーワードにリンカースクリプトを探します。

initlevelセクションを作るリンカースクリプト
// zephyr/include/linker/common-ram.ld

...

        SECTION_DATA_PROLOGUE(initlevel,,)
        {
                DEVICE_INIT_SECTIONS()
        } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION)


// zephyr/include/linker/linker-defs.h

/*
 * generate a symbol to mark the start of the device initialization objects for
 * the specified level, then link all of those objects (sorted by priority);
 * ensure the objects aren't discarded if there is no direct reference to them
 */

#define DEVICE_INIT_LEVEL(level)                                \
                __device_##level##_start = .;                   \
                KEEP(*(SORT(.init_##level[0-9])));              \
                KEEP(*(SORT(.init_##level[1-9][0-9]))); \

/*
 * link in device initialization objects for all devices that are automatically
 * initialized by the kernel; the objects are sorted in the order they will be
 * initialized (i.e. ordered by level, sorted by priority within a level)
 */

#define DEVICE_INIT_SECTIONS()                  \
                __device_init_start = .;        \
                DEVICE_INIT_LEVEL(PRE_KERNEL_1) \
                DEVICE_INIT_LEVEL(PRE_KERNEL_2) \
                DEVICE_INIT_LEVEL(POST_KERNEL)  \
                DEVICE_INIT_LEVEL(APPLICATION)  \
                __device_init_end = .;          \
                DEVICE_BUSY_BITFIELD()          \

DEVICE_AND_API_INITの宣言を思い出すと、ドライバなどで宣言される初期化セクションの名前は .init_(level)(prio) のような名前でした。DEVICE_INIT_LEVEL() はその法則性に従ってセクションを集めます。最終的に集められたセクションは、全てinitlevelセクションに配置されます。

例えばDEVICE_INIT_SECTIONS() の2行目にあるDEVICE_INIT_LEVEL(PRE_KERNEL_1) ですと、.init_PRE_KERNEL_1(数字) という名前のセクションを集めます。

というわけで、やっと謎が解けました。マクロやリンカースクリプトの見事な連携プレイですね。

prioの制限とエラーチェック

実装を見て気づいたと思いますが、DEVICE_AND_API_INIT() のprioに渡せる数字は2桁が上限です。DEVICE_INIT_LEVEL() は [0-9] もしくは [1-9][0-9] のパターンしかマッチしません。

試しに優先度を3桁にするとどうなるでしょう?mempool.cではCONFIG_KERNEL_INIT_PRIORITY_OBJECTSをprioに渡していたので、menuconfigでコンフィグを変えてみます。

3桁のprioにしてはいけない
$ ninja menuconfig

General Kernel Options  --->
  Initialization Priorities  --->
    (30) Kernel objects initialization priority

このパラメータを300に変更すると…?

riscv64-zephyr-elf/bin/ld: Undefined initialization levels used.
collect2: error: ld returned 1 exit status

リンク時にエラーで怒られました。これはどうやっているのでしょう?実はそんなに難しくありません。initlevelセクションを作るときとほぼ同じ仕組みです。

initlevelで拾えなかった .init_* 系セクションの検知

// zephyr/include/linker/common-ram.ld

        /* verify we don't have rogue .init_<something> initlevel sections */
        SECTION_DATA_PROLOGUE(initlevel_error,,)
        {
                DEVICE_INIT_UNDEFINED_SECTION()
        }
        ASSERT(SIZEOF(initlevel_error) == 0, "Undefined initialization levels used.")


// include/linker/linker-defs.h

/* define a section for undefined device initialization levels */
#define DEVICE_INIT_UNDEFINED_SECTION()         \
                KEEP(*(SORT(.init_[_A-Z0-9]*))) \

このinitlevel_errorセクションでは、initlevelセクションが拾い損ねた .init_* 系のセクションを全て集めます。もし1つでもセクションが拾えた場合、initlevel_errorセクションのサイズが0ではなくなるため、ASSERTに引っかかる仕組みになっています。リンカースクリプトでASSERTやSORTができるとは知らなかったですね……。

エラーチェックもきっちり作られていてZephyrはさすが良くできているなあと思います。

編集者:すずき(2023/09/24 12:03)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月20日

Zephyrの16550シリアルドライバ

目次: Zephyr

先日はZepphyrをQEMUのSpikeモードに移植しましたが、SpikeモードだとCPU数は1以外選べないので、マルチプロセッサにできません。これは知りませんでした。最初に調べておけば良かったですね……。

マルチプロセッサでZephyrを動かしてみたかったので、QEMU RISC-V 32のvirtモード(qemu-system-riscv32のmachineをvirtにすること)に、前回同様にシリアルとメモリだけ動くようなSoCとボードの定義ファイルを作りました。

QEMU virtモードでは、シリアルのハードウェアとして16550をエミュレーションします。Zephyr側には既に16550用のドライバがあるので、わざわざ実装する必要もありません。とても楽です、素晴らしいです、とか何とか思いつつ動かしてみると、シリアルドライバがクラッシュします。んあー。

シリアルドライバが動かない理由

ZephyrをGDBで追うと16550のLCRレジスタを読もうとして、ロードアクセスフォルトが起きていました。アドレスのオフセットを見ると、0xcになっています。正しいオフセットは3のはずです。

シリアルドライバの実装を見ると、オフセットの計算が0x3 x 4 = 0xcとなっていて、おそらくレジスタサイズが4バイト単位だと思ってアクセスしているのでしょう。

世の中にはレジスタが4バイトの実装もあるかもしれませんが、残念なことにQEMUは由緒正しい(?)実装なので16550のレジスタは「1バイト」単位です。4バイト単位でアクセスすると、全く関係ない領域が破壊されます。困りました。

問題を解決するには、SoCの定義ファイルのどこか(soc.h辺りかな?)で、
#define DT_NS16550_REG_SHIFT 0
を定義し、レジスタのサイズが2^0つまり1バイト単位であることを16550シリアルドライバに教える必要があるみたいです。とても大事なことだと思いますが、全くドキュメントに書いていないように見えます。

そこそこコード見ないとわからないので、つらいです……。

シリアルドライバが動いた

無事シリアルが動作したので、QEMUを4 CPUのマルチプロセッサ設定で起動し、GDBで覗いてみます。

QEMU virtモードでマルチプロセッサと、その確認
$ qemu-system-riscv32 -nographic -machine virt -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/build/zephyr/zephyr.elf -cpu rv32 -smp cpus=4 -s -S

qemu-system-riscv32: warning: No -bios option specified. Not loading a firmware.
qemu-system-riscv32: warning: This default will change in a future QEMU release. Please use the -bios option to avoid breakages when this happens.
qemu-system-riscv32: warning: See QEMU's deprecation documentation for details.
*** Booting Zephyr OS build v2.2.0-rc1-123-gcaca3f60b012  ***
threadA: Hello World from QEMU RV32 virt board!
threadB: Hello World from QEMU RV32 virt board!
threadA: Hello World from QEMU RV32 virt board!
threadB: Hello World from QEMU RV32 virt board!
...


$ riscv64-zephyr-elf-gdb zephyr/build/zephyr/zephyr.elf 

...

Type "apropos word" to search for commands related to "word"...
Reading symbols from build/zephyr/zephyr.elf...

(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x00001000 in ?? ()

(gdb) info threads
  Id   Target Id                    Frame
* 1    Thread 1.1 (CPU#0 [running]) 0x00001000 in ?? ()
  2    Thread 1.2 (CPU#1 [running]) 0x00001000 in ?? ()
  3    Thread 1.3 (CPU#2 [running]) 0x00001000 in ?? ()
  4    Thread 1.4 (CPU#3 [running]) 0x00001000 in ?? ()

確かに4 CPUが存在していることがわかります。ZephyrのログはGDB側でcontinueすると出てきます。アプリケーションは、hello_worldではなくsamples/synchronizationを使っています。

メモ: 技術系の話はFacebookから転記しておくことにした。大幅に加筆した。

編集者:すずき(2023/09/24 12:06)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月21日

ZephyrとRISC-Vとマルチプロセッサ

目次: Zephyr

昨日(2020年2月20日の日記参照)の続きです。

シリアルのみですが、ZephyrをQEMU RISC-V 32のvirtモードに移植しました。やっとZephyrが4 CPUで動くぞ、などと思っていましたが、全然ダメでした。2つ目以降のCPUは全く動きませんでした。

CPU 0は動作するが、他のCPUはloop_slave_coreから動かない
$ riscv64-zephyr-elf-gdb zephyr/build/zephyr/zephyr.elf

(gdb) info threads
  Id   Target Id                    Frame
* 1    Thread 1.1 (CPU#0 [halted ]) 0x80000f70 in arch_cpu_idle ()
    at zephyr/soc/riscv/riscv-privilege/common/idle.c:21
  2    Thread 1.2 (CPU#1 [halted ]) loop_slave_core ()
    at zephyr/arch/riscv/core/reset.S:45
  3    Thread 1.3 (CPU#2 [halted ]) loop_slave_core ()
    at zephyr/arch/riscv/core/reset.S:45
  4    Thread 1.4 (CPU#3 [halted ]) loop_slave_core ()
    at zephyr/arch/riscv/core/reset.S:45

理由は簡単で、ZephyrのRISC-V版はSMPに対応していないからです。何を言ってるんだ?って?ええ、実は私も今この瞬間まで知りませんでした。ビックリしました。

リセット付近のソースコードをみると、

Zephyrのリセットベクタ

// zephyr/arch/riscv/core/reset.S

/*
 * Remainder of asm-land initialization code before we can jump into
 * the C domain
 */
SECTION_FUNC(TEXT, __initialize)
        /*
         * This will boot master core, just halt other cores.
         * Note: need to be updated for complete SMP support
         */
        csrr a0, mhartid
        beqz a0, boot_master_core

loop_slave_core:
        wfi
        j loop_slave_core

以上のように、はっきり書いてあります。SMPは一番大事な機能だと思っていただけに、未対応とは思わなんだ。

あー、今日の16550との戦いは一体何だったんだろう。しょんぼりですわ……。

メモ: 技術系の話はFacebookから転記しておくことにした。大幅に加筆した。

編集者:すずき(2023/09/24 12:06)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月22日

Zephyr - まとめリンク

目次: Zephyr

導入、ブート周り

ボード、ドライバなど

SMP対応編

浮動小数点数命令など

その他

編集者:すずき(2024/04/17 02:22)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年3月2日

GCCを調べる - その5 - RTL (Register Transfer Language) の定義

目次: GCC

全てのRTLの定義はgcc/rtl.defに定義されています。例えばinsnの定義は下記のとおりです。

insnの定義

/* An instruction that cannot jump.  */
DEF_RTL_EXPR(INSN, "insn", "uuBeiie", RTX_INSN)

何となくわかるけど、何だこれ?と思いますよね、私は思いました。それもそのはずで、実はこのファイルは単体では意味をなさず、Cファイルから何度も再利用されるからです。Cファイルからrtl.defをインクルードして使いますが、その際にDEF_RTL_EXPR() の意味が変わります。

比較的わかりやすい例を挙げるとしたら、gcc/rtl.cのrtx_formatの定義です。DEF_RTL_EXPR() を定義してから #include "rtl.def" を行う様子がわかるかと思います。

rtl.defの利用方法の一例(gcc/rtl.cより)

/* Indexed by rtx code, gives a sequence of operand-types for
   rtx's of that code.  The sequence is a C string in which
   each character describes one operand.  */

const char * const rtx_format[NUM_RTX_CODE] = {
  /* "*" undefined.
         can cause a warning message
     "0" field is unused (or used in a phase-dependent manner)
         prints nothing
     "i" an integer
         prints the integer
     "n" like "i", but prints entries from `note_insn_name'
     "w" an integer of width HOST_BITS_PER_WIDE_INT
         prints the integer
     "s" a pointer to a string
         prints the string
     "S" like "s", but optional:
	 the containing rtx may end before this operand
     "T" like "s", but treated specially by the RTL reader;
         only found in machine description patterns.
     "e" a pointer to an rtl expression
         prints the expression
     "E" a pointer to a vector that points to a number of rtl expressions
         prints a list of the rtl expressions
     "V" like "E", but optional:
	 the containing rtx may end before this operand
     "u" a pointer to another insn
         prints the uid of the insn.
     "b" is a pointer to a bitmap header.
     "B" is a basic block pointer.
     "t" is a tree pointer.
     "r" a register.
     "p" is a poly_uint16 offset.  */

#define DEF_RTL_EXPR(ENUM, NAME, FORMAT, CLASS)   FORMAT ,
#include "rtl.def"		/* rtl expressions are defined here */
#undef DEF_RTL_EXPR
};

このrtx_formatの場合は、第3引数(FORMAT)とカンマを残して、他は無視するようにDEF_RTL_EXPR() を定義しています。

コメントなどを無視して考えるとrtl.defをインクルードしたあとは、コードは下記のようになります。

include後のrtl.defのイメージ

const char * const rtx_format[NUM_RTX_CODE] = {
DEF_RTL_EXPR(UNKNOWN, "UnKnown", "*", RTX_EXTRA)

DEF_RTL_EXPR(VALUE, "value", "0", RTX_OBJ)

DEF_RTL_EXPR(DEBUG_EXPR, "debug_expr", "0", RTX_OBJ)

...
};

マクロDEF_RTL_EXPR() の展開後はこうなります。

マクロDEF_RTL_EXPR() 展開後のrtl.defのイメージ

const char * const rtx_format[NUM_RTX_CODE] = {
"*",

"0",

"0",

...
};

最終的にrtx_formatは文字列の配列になります。

私もrtl.defの用途全てを理解しているわけではなく、全てを説明することもできないので、またわかったら書くことにします。

編集者:すずき(2023/09/24 11:47)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年3月6日

GCCを調べる - その6 - ベクトルレジスタ用のregister constraintを足す

目次: GCC

GCCのインラインアセンブラでは __asm__("ins %0" : "=r"(aaa) : : ); のように、"=r" という不思議な文字列が出てきます。

これはconstraintsと呼ばれ(GCCのマニュアルへのリンク)、引数がレジスタ(r)なのかメモリアドレス(m)なのか、書き換えられるのか(=)などを説明しています。

どんな文字でも書けるわけではなく、変な文字(例えば 'v')を指定すると「impossible constraint in 'asm'」と怒られます。これは一体どこでチェックしているのでしょう?また、どうやって足せばよいでしょうか?

エラーを出している箇所

このエラーを出すのは、パスでいうと234r.vregsです。

ちなみにbuild_gccというディレクトリ名はGCCのビルドディレクトリのことです。GCCは自動生成コードをかなりの量出力するので、そちらも合わせて見る必要があります。

エラーメッセージを出している箇所

// gcc/function.c

static void
instantiate_virtual_regs_in_insn (rtx_insn *insn)
{
...
  if (asm_noperands (PATTERN (insn)) >= 0)
    {
      if (!check_asm_operands (PATTERN (insn)))  //★★このエラーチェックに引っかかっている
	{
	  error_for_asm (insn, "impossible constraint in %<asm%>");  //★★このエラーメッセージが出ている


// gcc/recog.c

int
check_asm_operands (rtx x)
{
...
  for (i = 0; i < noperands; i++)
    {
      const char *c = constraints[i];
      if (c[0] == '%')
	c++;
      if (! asm_operand_ok (operands[i], c, constraints))  //★★このエラーチェックに引っかかっている
	return 0;
    }

このチェックは何をしているのかというと、lookup_constraint() で文字(例えば 'v')をテーブルから探し、constraintの種類に変換します。未知の文字の場合はCONSTRAINT_UNKNOWNになります。

次にget_constraint_type() でどのカテゴリに属するか見ます。この関数はおかしくて、なぜかCONSTRAINT__UNKNOWNをCT_REGISTERと判定します。レジスタじゃないですよね?意味不明です。

asm文のconstraintを見ている箇所

// gcc/recog.c

int
asm_operand_ok (rtx op, const char *constraint, const char **constraints)
{
  int result = 0;
...
  while (*constraint)
    {
      enum constraint_num cn;
      char c = *constraint;
      int len;
      switch (c)
	{
...
	default:
	  cn = lookup_constraint (constraint);  //★★文字からconstraint_numに変換
						//    未知の文字の場合CONSTRAINT__UNKNOWNになる
	  switch (get_constraint_type (cn))
	    {
	    case CT_REGISTER:  //★★なぜかここに行く
	      if (!result
		  && reg_class_for_constraint (cn) != NO_REGS  //★★レジスタのconstraintがNO_REGSになるとNG
		  && GET_MODE (op) != BLKmode
		  && register_operand (op, VOIDmode))
		result = 1;
	      break;


// build_gcc/gcc/tm-preds.h

static inline enum constraint_num
lookup_constraint (const char *p)
{
  unsigned int index = lookup_constraint_array[(unsigned char) *p];  //★★この配列がカギ
  return (index == UCHAR_MAX
          ? lookup_constraint_1 (p)
          : (enum constraint_num) index);
}

enum constraint_num
{
  CONSTRAINT__UNKNOWN = 0,
  CONSTRAINT_r,
  CONSTRAINT_f,
...

static inline enum constraint_type
get_constraint_type (enum constraint_num c)
{
  if (c >= CONSTRAINT_p)
    {
      if (c >= CONSTRAINT_G)
        return CT_FIXED_FORM;
      return CT_ADDRESS;
    }
  if (c >= CONSTRAINT_m)
    return CT_MEMORY;
  if (c >= CONSTRAINT_I)
    return CT_CONST_INT;
  return CT_REGISTER;  //★★cがCONSTRAINT__UNKNOWNつまり0の場合、どれにも当てはまらずCT_REGISTERと判断される
}


// build_gcc/insn-preds.c

const unsigned char lookup_constraint_array[] = {
  CONSTRAINT__UNKNOWN,
  CONSTRAINT__UNKNOWN,
...
  MIN ((int) CONSTRAINT_r, (int) UCHAR_MAX),
  MIN ((int) CONSTRAINT_s, (int) UCHAR_MAX),
  CONSTRAINT__UNKNOWN,
  CONSTRAINT__UNKNOWN,
  CONSTRAINT__UNKNOWN,  //★★'v' は未定義、未知の文字の場合は全てCONSTRAINT__UNKNOWNになる
  CONSTRAINT__UNKNOWN,
...


// build_gcc/gcc/tm-preds.h

static inline enum reg_class
reg_class_for_constraint (enum constraint_num c)
{
  if (insn_extra_register_constraint (c))
    return reg_class_for_constraint_1 (c);  //★★ここで見つからないとNO_REGSが返されてNG
  return NO_REGS;
}


// build_gcc/gcc/insn-preds.c

enum reg_class
reg_class_for_constraint_1 (enum constraint_num c)
{
  switch (c)
    {
    case CONSTRAINT_r: return GENERAL_REGS;
    case CONSTRAINT_f: return TARGET_HARD_FLOAT ? FP_REGS : NO_REGS;
    case CONSTRAINT_j: return SIBCALL_REGS;
    case CONSTRAINT_l: return JALR_REGS;
    default: break;  //★★CONSTRAINT__UNKNOWNはどのcaseにも当てはまらないのでNO_REGSが返されてNG
    }
  return NO_REGS;
}

明らかにレジスタとは思えないCONSTRAINT__UNKNOWNが返ってきますが、なぜかCT_REGISTERだと思って処理し始め、最終的にエラーとして弾きます。結果オーライですがこれで良いんでしょうか。GCCのコードは訳がわかりません……。

register_constraintの足し方

ではvを正当なconstraintの一員にするにはどうしたら良いでしょうか?

第一歩としてはGCCのconfigディレクトリの下にある *.mdファイル(MarkdownではなくMachine Descriptorです)を編集します。

define_register_constraintを足す

;; config/riscv/riscv.md

(include "predicates.md")
(include "constraints.md")


;; config/riscv/constraints.md

(define_register_constraint "v" "TARGET_VECTOR ? VP_REGS : NO_REGS"
  "A vector register (if available).")

これを足すと(他にも色々やらないといけないんですけど)、先程のreg_class_for_constraint_1() に変化が生じます。

asm文のconstraintチェックが変わる

// build_gcc/gcc/insn-preds.c

enum reg_class
reg_class_for_constraint_1 (enum constraint_num c)
{
  switch (c)
    {
    case CONSTRAINT_r: return GENERAL_REGS;
    case CONSTRAINT_f: return TARGET_HARD_FLOAT ? FP_REGS : NO_REGS;
    case CONSTRAINT_j: return SIBCALL_REGS;
    case CONSTRAINT_v: return TARGET_VECTOR ? VP_REGS : NO_REGS;  //★★これが足されて通過するようになる
    case CONSTRAINT_l: return JALR_REGS;
    default: break;
    }
  return NO_REGS;
}

GCCはこの手のピタゴラスイッチの塊で、何を変えると望みの機能が実装できるか、全くわかりません。こんなもの良くメンテナンスできるなあ、と思います。

編集者:すずき(2023/09/24 11:47)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年3月9日

TinkerBoardが起動しなくなった

目次: ROCK64/ROCKPro64

ASUS TinkerBoardで動かしているlinux-nextを最新版に更新したところ、下記のようなエラーを出力して起動しなくなりました。

linux-next起動時のエラー
[    0.000000] __clk_core_init: Failed to get phase for clk 'sdmmc_drv'
[    0.000000] rockchip_clk_register_branches: failed to register clock sdmmc_drv: -22
[    0.000000] rockchip_clk_register_branches: failed to register clock sdmmc_sample: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock sdio0_drv: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock sdio0_sample: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock sdio1_drv: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock sdio1_sample: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock emmc_drv: -17
[    0.000000] rockchip_clk_register_branches: failed to register clock emmc_sample: -17

エラーメッセージで検索しても、特にめぼしい報告やパッチに引っかかりません。うーん。困りましたね。

原因解析

仕方ないので、更新履歴をgit bisectすると2/13から2/14の間にクロックドライバが変更されたことが原因だとわかりました。

git bisectで見つけた原因となる変更Commit:2760878662a2

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index dc8bdfbd6a0c..ed1797857bae 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -3457,7 +3457,12 @@ static int __clk_core_init(struct clk_core *core)
 	 * Since a phase is by definition relative to its parent, just
 	 * query the current clock phase, or just assume it's in phase.
 	 */
-	clk_core_get_phase(core);
+	ret = clk_core_get_phase(core);
+	if (ret < 0) {
+		pr_warn("%s: Failed to get phase for clk '%s'\n", __func__,
+			core->name);
+		goto out;
+	}
 
 	/*
 	 * Set clk's duty cycle.

しかしこの変更は真っ当に見えます。今までエラーを無視していたところをエラーチェックするようにしているだけだからです。

どの辺りでエラーが出るか調べるためprintkを適当に入れたりしながら追ってみると、下記の場所で躓いていました。

クロック周りのコード

// drivers/clk/clk.c

static int __clk_core_init(struct clk_core *core)
{
...
	/*
	 * Set clk's phase by clk_core_get_phase() caching the phase.
	 * Since a phase is by definition relative to its parent, just
	 * query the current clock phase, or just assume it's in phase.
	 */
	phase = clk_core_get_phase(core);  //★★変更が影響している場所
	if (phase < 0) {
		ret = phase;
		pr_warn("%s: Failed to get phase for clk '%s'\n", __func__,
			core->name);
		//goto out;
	}


// drivers/clk/clk.c

static int clk_core_get_phase(struct clk_core *core)
{
	int ret;

	lockdep_assert_held(&prepare_lock);
	if (!core->ops->get_phase)
		return 0;

	/* Always try to update cached phase if possible */
	ret = core->ops->get_phase(core->hw);  //★★ここでエラーが発生していた
	if (ret >= 0)
		core->phase = ret;

	return ret;
}

独自のget_phaseを持っているドライバがエラーを返すと、clk_core_get_phase() もエラーを返し、先程のエラーメッセージが表示されるみたいです。しかし、この処理自体におかしな箇所はないように思います。真っ当です。

おそらく実装がおかしいのはRockchipのMMCドライバのクロック周りの方でしょう。エラーメッセージに出ているsdmmc_drvを頼りにコードを追います。

Rockchip RK3228クロック周りのコード

// drivers/clk/rockchip/clk-rk3228.c

PNAME(mux_mmc_src_p)		= { "cpll", "gpll", "xin24m", "usb480m" };
...
	COMPOSITE(SCLK_SDMMC, "sclk_sdmmc", mux_mmc_src_p, 0,
			RK2928_CLKSEL_CON(11), 8, 2, MFLAGS, 0, 8, DFLAGS,
			RK2928_CLKGATE_CON(2), 11, GFLAGS),
	...
	//★★sdmmc_drvはこの部分由来です
	MMC(SCLK_SDMMC_DRV,    "sdmmc_drv",    "sclk_sdmmc", RK3228_SDMMC_CON0, 1),


// drivers/clk/rockchip/clk.h

#define MMC(_id, cname, pname, offset, shift)			\
	{							\
		.id		= _id,				\
		.branch_type	= branch_mmc,			\  //★★この値がカギかな?
		.name		= cname,			\
		.parent_names	= (const char *[]){ pname },	\
		.num_parents	= 1,				\
		.muxdiv_offset	= offset,			\
		.div_shift	= shift,			\
	}


// drivers/clk/rockchip/clk-rk3228.c

static void __init rk3228_clk_init(struct device_node *np)
{
...
	rockchip_clk_register_branches(ctx, rk3228_clk_branches,  //★★ここでbranch_typeを見ている箇所がある
				  ARRAY_SIZE(rk3228_clk_branches));
...
}
//★★注: rk3228_clk_initはRK3228のクロックコア初期化関数
//    デバイスツリー内のcompatible = "rockchip,rk3228-cru" を持つノードに応じて呼ばれる
CLK_OF_DECLARE(rk3228_cru, "rockchip,rk3228-cru", rk3228_clk_init);


// drivers/clk/rockchip/clk.c

void __init rockchip_clk_register_branches(
				      struct rockchip_clk_provider *ctx,
				      struct rockchip_clk_branch *list,
				      unsigned int nr_clk)
{
...
	for (idx = 0; idx < nr_clk; idx++, list++) {
		flags = list->flags;

		/* catch simple muxes */
		switch (list->branch_type) {
		...
		case branch_mmc:  //★★branch_type == branch_mmcだったら
			clk = rockchip_clk_register_mmc(  //★★クロックの登録をしている
				list->name,
				list->parent_names, list->num_parents,
				ctx->reg_base + list->muxdiv_offset,
				list->div_shift
			);
			break;
		...


// drivers/clk/rockchip/clk-mmc-phase.c

struct clk *rockchip_clk_register_mmc(const char *name,
				const char *const *parent_names, u8 num_parents,
				void __iomem *reg, int shift)
{
...
	init.name = name;
	init.flags = 0;
	init.num_parents = num_parents;
	init.parent_names = parent_names;
	init.ops = &rockchip_mmc_clk_ops;  //★★クロックの操作関数を登録している
...


// drivers/clk/rockchip/clk-mmc-phase.c

static const struct clk_ops rockchip_mmc_clk_ops = {
	.recalc_rate	= rockchip_mmc_recalc,
	.get_phase	= rockchip_mmc_get_phase,  //★★get_phaseはこの関数
	.set_phase	= rockchip_mmc_set_phase,
};


// drivers/clk/rockchip/clk-mmc-phase.c

static int rockchip_mmc_get_phase(struct clk_hw *hw)
{
	struct rockchip_mmc_clock *mmc_clock = to_mmc_clock(hw);
	unsigned long rate = clk_hw_get_rate(hw);
	u32 raw_value;
	u16 degrees;
	u32 delay_num = 0;

	/* See the comment for rockchip_mmc_set_phase below */
	if (!rate)
		return -EINVAL;  //★★エラーを返している
...

原因らしき箇所が見つかりました。クロック周波数(rate)が設定されていなくても、エラーを返す必要はなく、phaseの初期値0を返せば良いはずですから、return -EINVALをreturn 0にすれば良さそうです。

変更すると無事linux-nextが起動できるようになりました。良かった良かった。

またこのパターン!

こんなバグを2週間以上放置するなんてlinuxらしくないなと思って、パッチを送ろうとしましたが……、なんだかとっても嫌な予感がしたので、rochchip_mmc_get_phaseでLKMLを検索してみました。

検索してびっくり、なんと、全く同じ指摘をしているパッチが先週LKMLに送られています(LKMLへのリンク)。しかも既にclk-nextに取り込まれているじゃないですか。ですがlinux-nextへの反映はまだのようです。

明日まで待っていればclk-nextがlinux-nextに取り込まれるので、何もしなくても解決していたんです。パッチ投稿が3/4なので、今回は本当に気づくタイミングが悪かったです。

LKMLに投稿されていたパッチ

Author: Jerome Brunet < >
Date:   Tue Mar 3 20:29:56 2020 +0100

    clk: rockchip: fix mmc get phase
    
    If the mmc clock has no rate, it can be assumed to be constant.
    In such case, there is no measurable phase shift. Just return 0
    in this case instead of returning an error.
    
    Fixes: 2760878662a2 ("clk: Bail out when calculating phase fails during clk
    registration")
    Tested-by: Markus Reichl <m.reichl@fivetechno.de>
    Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>

diff --git a/drivers/clk/rockchip/clk-mmc-phase.c b/drivers/clk/rockchip/clk-mmc-phase.c
index 4abe7ff31f53..975454a3dd72 100644
--- a/drivers/clk/rockchip/clk-mmc-phase.c
+++ b/drivers/clk/rockchip/clk-mmc-phase.c
@@ -51,9 +51,9 @@ static int rockchip_mmc_get_phase(struct clk_hw *hw)
 	u16 degrees;
 	u32 delay_num = 0;
 
-	/* See the comment for rockchip_mmc_set_phase below */
+	/* Constant signal, no measurable phase shift */
 	if (!rate)
-		return -EINVAL;
+		return 0;
 
 	raw_value = readl(mmc_clock->reg) >> (mmc_clock->shift);

前にも、原因を突き止めたら、解決済みだったというパターン(2018年12月4日の日記2019年9月18日の日記)がありました。

またこの「鶏と卵のパターン」にやられました。ついてない。

コードに特攻する前に、ちゃんと探せば?って思われるかもしれませんが、困ったことにエラーメッセージで検索してもパッチに辿り着けないんです。調べに調べて原因と対策がわかった後に、はぁ?もうパッチあるじゃん!?と気づくから、徒労感が激しい……。

編集者:すずき(2023/09/24 13:22)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年3月10日

誕生日

37歳になりました。おめでとう俺、ありがとう俺。30代も残りわずかです。

何歳になろうが、本人は特に何も変わってませんが、周りから見ると確実にオジサンです。若者へ説教と昔の自慢話だけは絶対しないように気をつけます。

今に始まったことじゃないんですが、いつも何歳だか良くわからなくなります。もちろん (現在の西暦) - (生まれた西暦) で、1月1日〜3月9日は -1すれば良いのはわかってますが、ぱっと答えられません。なぜなのか……。

編集者:すずき(2020/03/15 02:33)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年3月14日

GCCを調べる - その7 - machine mode定義ファイルの場所

目次: GCC

GCCはアーキテクチャごとに設定ファイルがあってgcc/config/(アーキテクチャ名)/ という名前のディレクトリ下に配置されています。

その中にmachine modeを設定するファイルがあります。例えばRISC-Vならgcc/config/riscv/riscv-modes.defですし、AArch64ならgcc/config/aarch64/aarch64-modes.defです。machine modeとは何かはちょっと横に置いて、このファイルの探し方が割と不思議な作りだったので紹介します。

GCCのビルドプロセスは非常にややこしいです。xxxx-modes.defはGCCのコードで直接使われず、前処理で使われます。GCCのビルドディレクトリを (build_dir) と書くとすると、

  • genmodes.cから (build_dir)/build/genmodesというプログラムを作成
  • genmodesの出力を使い (build_dir)/insn-modes.cを生成
  • insn-modes.cをGCCのビルドに使う

こんな仕組みになっています。起点となるgenmodes.cから見ます。

xxxx-modes.defのファイル名を決める部分

// gcc/genmodes.c

static void
create_modes (void)
{
#include "machmode.def"  //★★これに全てのモードが定義されている


// gcc/machmode.def

/* Allow the target to specify additional modes of various kinds.  */
#if HAVE_EXTRA_MODES
# include EXTRA_MODES_FILE  //★★追加のファイルがあればinclude
#endif


// gcc/genmodes.c

#ifdef EXTRA_MODES_FILE
# define HAVE_EXTRA_MODES 1
#else
# define HAVE_EXTRA_MODES 0
# define EXTRA_MODES_FILE ""
#endif

EXTRA_MODES_FILEというマクロにファイル名が定義されていれば、そのファイルもincludeする仕組みになっています。C言語を見慣れている人もかなり面食らう書き方ですが、GCCは「*.defファイルをincludeする」という技を乱発します。メチャクチャすぎる。

EXTRA_MODES_FILEをマクロを定義するのはconfig.gccとconfigureです。

xxxx-modes.defのファイル名を決める部分

# gcc/config.gcc

extra_modes=
if test -f ${srcdir}/config/${cpu_type}/${cpu_type}-modes.def
then
	extra_modes=${cpu_type}/${cpu_type}-modes.def    # ★★aarch64-modes.defのような名前のファイルが存在すればそのファイルを使う
fi


# gcc/configure

# Collect target-machine-specific information.
. ${srcdir}/config.gcc || exit 1    # ★★config.gccの設定を取り込む

...

# Look for a file containing extra machine modes.
if test -n "$extra_modes" && test -f $srcdir/config/$extra_modes; then
  extra_modes_file='$(srcdir)'/config/${extra_modes}    # ★★extra_modesはconfig.gccで定義したものが導入される


cat >>confdefs.h <<_ACEOF
#define EXTRA_MODES_FILE "config/$extra_modes"    # ★★config/aarch64/aarch64-modes.defのような値になる
_ACEOF

fi

やっとたどり着きました。GCCってちょっとしたことでも、ものすごく複雑にできていて、読むのが辛いというか、読んでも意味不明なことが多いです。うーん、辛い。

編集者:すずき(2023/09/24 11:47)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2020年2月13日 >>> 2020年3月14日
link もっと後

管理用メニュー

link 記事を新規作成

<2020>
<<<02>>>
------1
2345678
9101112131415
16171819202122
23242526272829

最近のコメント5件

  • link 24年6月17日
    すずきさん (06/23 00:12)
    「ありがとうございます。バルコニーではない...」
  • link 24年6月17日
    hdkさん (06/22 22:08)
    「GPSの最初の同期を取る時は見晴らしのい...」
  • link 24年5月16日
    すずきさん (05/21 11:41)
    「あー、確かにdpkg-reconfigu...」
  • link 24年5月16日
    hdkさん (05/21 08:55)
    「システム全体のlocale設定はDebi...」
  • link 24年5月17日
    すずきさん (05/20 13:16)
    「そうですねえ、普通はStandardなの...」

最近の記事3件

  • link 24年6月27日
    すずき (06/30 15:39)
    「[何もない組み込み環境でDOOMを動かす - その4 - 自作OSの組み込み環境へ移植] 目次: RISC-V目次: 独自OS...」
  • link 22年12月13日
    すずき (06/30 15:38)
    「[独自OS - まとめリンク] 目次: 独自OS一覧が欲しくなったので作りました。自作OSの紹介その1 - 概要自作OSの紹介...」
  • link 21年6月18日
    すずき (06/29 22:28)
    「[RISC-V - まとめリンク] 目次: RISC-VSiFive社ボードの話、CoreMarkの話のまとめ。RISC-V ...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 06/30 15:39