1
0

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.

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

Posted at

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

TL;DR

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

はじめに

組込linuxエンジニアであれば、避けては通れない問題。それが DT_MACHINE_STARTMACHINE_ENDである。たぶんきっと。例えば、rockchipだったらmach-rockchip/rockchip.cにある通り、こんな感じ。このようにして、arch固有の処理を記述する事ができる。

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定義は同じファイル

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の場合だったらこのあたり

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

あるいは、同じRK3288を積んだROCK Pi N8だと、このあたり

/ {
	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 で定義されている。

# 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 の中で、section arch.info.initは定義されている。

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

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

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番目が同じに見える...

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個定義していますね... これだと、「先に定義してある方」しか有効にならないのでは。。。

struct machine_descの使われ方

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

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

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

drivers/of/fdt.c内で定義されている、of_flat_dt_match_machine()のコードが以下である。

/**
 * 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()のコードが以下である。前述したvmlinux.lds.Sで記載した通り、arch_info_beginからarch_info_endまでの中に定義されている構造体を走査するためのcallback関数となっている。

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に該当するものが自動的に選択される。

以上になります。

1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?