DT_MACHINE_START～ENDの裏側も確認してみよう

ちょっと早い夏休みだ自由研究をしよう(5日目)

# TL;DR
- DT_MACHINE_START/END定義は、特定セクションに構造体を置くためのマクロ。
- 1 Kernel内に複数のmachineが存在していても良い。ただし、シンボルが上に来ているものが優先となる。
- Device Treeなどを使って、指定されたmachineに該当するものが自動的に選択される。

# はじめに

組込linuxエンジニアであれば、避けては通れない問題。それが `DT_MACHINE_START`～`MACHINE_END`である。たぶんきっと。例えば、rockchipだったら[mach-rockchip/rockchip.c](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/mach-rockchip/rockchip.c?h=v5.13.7#n69)にある通り、こんな感じ。このようにして、arch固有の処理を記述する事ができる。

```c
DT_MACHINE_START(ROCKCHIP_DT, "Rockchip (Device Tree)")
	.l2c_aux_val	= 0,
	.l2c_aux_mask	= ~0,
	.init_time	= rockchip_timer_init,
	.dt_compat	= rockchip_board_dt_compat,
	.init_machine	= rockchip_dt_init,
MACHINE_END
```

なお、ここのdt_compat定義は[同じファイル](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/mach-rockchip/rockchip.c?h=v5.13.7#n58)。

```c
static const char * const rockchip_board_dt_compat[] = {
	"rockchip,rk2928",
	"rockchip,rk3066a",
	"rockchip,rk3066b",
	"rockchip,rk3188",
	"rockchip,rk3228",
	"rockchip,rk3288",
	"rockchip,rv1108",
	NULL,
};
```

これを使って、各製品のdtsなりが定義される。
例えば、Tinker Boardの場合だったら[このあたり](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/boot/dts/rk3288-tinker-s.dts?h=v5.13.7#n10)、

```plaintext
/ {
	model = "Rockchip RK3288 Asus Tinker Board S";
	compatible = "asus,rk3288-tinker-s", "rockchip,rk3288";
};
```

あるいは、同じRK3288を積んだROCK Pi N8だと、[このあたり](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/boot/dts/rk3288-rock-pi-n8.dts?h=v5.13.7#n13)

```plaintext
/ {
	model = "Radxa ROCK Pi N8";
	compatible = "radxa,rockpi-n8", "vamrs,rk3288-vmarc-som",
		     "rockchip,rk3288";
};
```

こんな感じで、一度作った部品がきちんと再利用できるのって、本当に素晴らしいですよね！！

## ところで、DT_MACHINE_START/ENDの説明ってできますか？

このDT_MACHINE_STARTや、MACHINE_ENDの詳細は確認したことありますか？

何か、魔法の言葉のように使われているけど、その詳細まで追ってみたことはあまりない、ような気がする。

本日はここを深堀してみる。

# DT_MACHINE_START / MACHINE_ENDの定義

このマクロは、[arch/arm/include/asm/mach/arch.h](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/include/asm/mach/arch.h?h=v5.13.7) で定義されている。

```c++
#define DT_MACHINE_START(_name, _namestr)		\
static const struct machine_desc __mach_desc_##_name	\
 __used							\
 __section(".arch.info.init") = {			\
	.nr		= ~0,				\
	.name		= _namestr,

#endif
```

`struct_machine_desc`構造体として、`.arch.info.init` sectionに置かれる。

この構造体の定義も同じarch.hにある。

## .arch.info.initのsectionはどこにあるの？

[vmlinux.lds.S](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/kernel/vmlinux.lds.S?h=v5.13.7) の中で、[section arch.info.init](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/kernel/vmlinux.lds.S?h=v5.13.7#n101
)は定義されている。

```c
	.init.arch.info : {
		__arch_info_begin = .;
		*(.arch.info.init)
		__arch_info_end = .;
	}
```

ということで、linkerによって、RO領域内に格納されている、ということになる。また、複数個の構造体が定義されていても、全部この中に保存される。実際のlinux kernelのvmlinuxを作って確認すると、確かに1 sectionとして定義されていることが確認できる。

```plaintext
kmtr@kmtr-virtual-machine:~/work/rasp/linux$ arm-linux-gnueabihf-readelf -S vmlinux
There are 31 section headers, starting at offset 0x11f17cc:

セクションヘッダ:
  [番] 名前              タイプ          アドレス Off    サイズ ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .head.text        PROGBITS        c0008000 008000 0001fc 00  AX  0   0 32
  [ 2] .text             PROGBITS        c0008200 008200 8ad370 00  AX  0   0 32
  [ 3] .rodata           PROGBITS        c08b6000 8b6000 26acea 00  WA  0   0 32
  [ 4] .eh_frame         PROGBITS        c0b20cec b20cec 0000b4 00   A  0   0  4
  [ 5] __ksymtab         PROGBITS        c0b20da0 b20da0 00d140 00   A  0   0  4
  [ 6] __ksymtab_gpl     PROGBITS        c0b2dee0 b2dee0 00d764 00   A  0   0  4
  [ 7] __kcrctab         PROGBITS        c0b3b644 b3b644 0045c0 00   A  0   0  1
  [ 8] __kcrctab_gpl     PROGBITS        c0b3fc04 b3fc04 0047cc 00   A  0   0  1
  [ 9] __ksymtab_strings PROGBITS        c0b443d0 b443d0 029eb6 01 AMS  0   0  1
  [10] __param           PROGBITS        c0b6e288 b6e288 00152c 00   A  0   0  4
  [11] __modver          PROGBITS        c0b6f7b4 b6f7b4 000010 00   A  0   0  4
  [12] .notes            NOTE            c0b6f7c4 b6f7c4 00003c 00   A  0   0  4
  [13] __ex_table        PROGBITS        c0b70000 b70000 000878 00   A  0   0  8
  [14] .vectors          PROGBITS        ffff0000 b80000 000020 00  AX  0   0  4
  [15] .stubs            PROGBITS        ffff1000 b81000 0002ac 00  AX  0   0 32
  [16] .init.text        PROGBITS        c0b712e0 b812e0 032544 00  AX  0   0 32
  [17] .exit.text        PROGBITS        c0ba3824 bb3824 0026e4 00  AX  0   0  4
  [18] .init.proc.info   PROGBITS        c0ba5f08 bb5f08 000034 00   A  0   0  1
  [19] .init.arch.info   PROGBITS        c0ba5f3c bb5f3c 0001a0 00   A  0   0  4　★ココ！！！！！
  [20] .init.tagtable    PROGBITS        c0ba60dc bb60dc 000010 00   A  0   0  4
  [21] .init.pv_table    PROGBITS        c0ba60ec bb60ec 000a24 00   A  0   0  1
  [22] .init.data        PROGBITS        c0ba7000 bb7000 0315c4 00  WA  0   0 4096
  [23] .data             PROGBITS        c0bda000 bea000 145de8 00  WA  0   0 32
  [24] __bug_table       PROGBITS        c0d1fde8 d2fde8 005754 00  WA  0   0  4
  [25] .bss              NOBITS          c0d25540 d3553c 0d11f8 00  WA  0   0 32
  [26] .comment          PROGBITS        00000000 d3553c 000025 01  MS  0   0  1
  [27] .ARM.attributes   ARM_ATTRIBUTES  00000000 d35561 00002a 00      0   0  1
  [28] .symtab           SYMTAB          00000000 d3558c 2b59d0 10     29 146582  4
  [29] .strtab           STRTAB          00000000 feaf5c 206737 00      0   0  1
  [30] .shstrtab         STRTAB          00000000 11f1693 000136 00      0   0  1
```

更に、シンボルを確認すると、4つの__mach_desc_*が確認できる……　あれ？2番目と4番目が同じに見える...

```bash
kmtr@kmtr-virtual-machine:~/work/rasp/linux$ arm-linux-gnueabihf-readelf -W -s vmlinux | grep mach_desc
  2264: c0ba5f3c   104 OBJECT  LOCAL  DEFAULT   19 __mach_desc_GENERIC_DT.1
  3272: c0ba5fa4   104 OBJECT  LOCAL  DEFAULT   19 __mach_desc_BCM2711
  3273: c0ba600c   104 OBJECT  LOCAL  DEFAULT   19 __mach_desc_BCM2835
  3279: c0ba6074   104 OBJECT  LOCAL  DEFAULT   19 __mach_desc_BCM2711
```

### 完全な余談
raspberry piのkernel codeで、本当に2個定義していますね... これだと、「先に定義してある方」しか有効にならないのでは。。。

- https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/mach-bcm/bcm2711.c
- https://github.com/raspberrypi/linux/blob/rpi-5.10.y/arch/arm/mach-bcm/board_bcm2835.c#L123


#struct machine_descの使われ方

とはいえ、これだとまだ「データ構造としては定義されているが、device treeとの関連付けはされていない」状態。もう少し詳しく中身を見る必要がある。そこで、電源投入から順番に起動シーケンスを確認してみよう。

まず、電源が投入されると、linux kernelの先頭に配置されたtext領域の先頭から処理が開始される。その中で、`__mmap_switched()`経由で、`start_kernerl`、そして、`setup_arch()`が呼ばれる。

```plantuml
entry -> text 
text -> arch_arm_kernel_head_common_S : __mmap_switched()
arch_arm_kernel_head_common_S -> init_main_c : start_kernel()
init_main_c ->] : setup_arch()
```

`setup_arch()`では、fdtもしくはtagsベースで、起動するmachineに該当する`struct machine_desc `を探索する。この時、`devicetree.c`は、`arch_get_next_mach()`の関数ポインタをcallbackとして`drivers/of/fdt.c`に渡す。

```plantuml
[-> arch_arm_kernel_setup_c : setup_arch()
arch_arm_kernel_setup_c -> arch_arm_kernel_devtree_c : setup_machine_fdt() 
arch_arm_kernel_devtree_c -> drivers_of_fdt_c : of_flat_dt_match_machine(arch_get_next_mach)
loop 
drivers_of_fdt_c -> arch_arm_kernel_devtree_c : (callback) arch_get_next_mach()
drivers_of_fdt_c <-- arch_arm_kernel_devtree_c
end
arch_arm_kernel_setup_c <-- arch_arm_kernel_devtree_c
alt setup_machine_fdt()では見つからなかった
arch_arm_kernel_setup_c -> arch_arm_kernel_atags_parse_c : setup_machine_tags() 
arch_arm_kernel_setup_c <-- arch_arm_kernel_atags_parse_c
end

```

`drivers/of/fdt.c`内で定義されている、`of_flat_dt_match_machine()`のコードが[以下](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/of/fdt.c?h=v5.13.7#n812)である。

```c
/**
 * of_flat_dt_match_machine - Iterate match tables to find matching machine.
 *
 * @default_match: A machine specific ptr to return in case of no match.
 * @get_next_compat: callback function to return next compatible match table.
 *
 * Iterate through machine match tables to find the best match for the machine
 * compatible string in the FDT.
 */
const void * __init of_flat_dt_match_machine(const void *default_match,
		const void * (*get_next_compat)(const char * const**))
{
	const void *data = NULL;
	const void *best_data = default_match;
	const char *const *compat;
	unsigned long dt_root;
	unsigned int best_score = ~1, score = 0;

	dt_root = of_get_flat_dt_root();
	while ((data = get_next_compat(&compat))) { /*✰ここのget_next_compatは当該関数の引数 */
		score = of_flat_dt_match(dt_root, compat);
		if (score > 0 && score < best_score) {
			best_data = data;
			best_score = score;
		}
	}
	if (!best_data) { ... 
		return NULL;
	}

	pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());

	return best_data;
}
```


`arch/arm/kernel/devtree.c`内で定義されている`arch_get_next_mach()`のコードが[以下](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/arch/arm/kernel/devtree.c?h=v5.13.7#n191)である。前述したvmlinux.lds.Sで記載した通り、arch_info_beginからarch_info_endまでの中に定義されている構造体を走査するためのcallback関数となっている。

```c
static const void * __init arch_get_next_mach(const char *const **match)
{
	static const struct machine_desc *mdesc = __arch_info_begin;
	const struct machine_desc *m = mdesc;

	if (m >= __arch_info_end)
		return NULL;

	mdesc++;
	*match = m->dt_compat;
	return m;
}
```

以上によって、Device Treeに記載されたmachine名と、arch_info_begin～arch_info_endの中に定義されていた構造体が関連付けられる。これによって、arch固有の処理を定義し、実装することができるようになる。

# まとめ
- DT_MACHINE_START/END定義は、特定セクションに構造体を置くためのマクロ。
- 1 Kernel内に複数のmachineが存在していても良い。ただし、シンボルが上に来ているものが優先となる。
- Device Treeなどを使って、指定されたmachineに該当するものが自動的に選択される。


以上になります。