ちょっと早い夏休みだ自由研究をしよう(5日目)
TL;DR
- DT_MACHINE_START/END定義は、特定セクションに構造体を置くためのマクロ。
- 1 Kernel内に複数のmachineが存在していても良い。ただし、シンボルが上に来ているものが優先となる。
- Device Treeなどを使って、指定されたmachineに該当するものが自動的に選択される。
はじめに
組込linuxエンジニアであれば、避けては通れない問題。それが DT_MACHINE_START
~MACHINE_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個定義していますね... これだと、「先に定義してある方」しか有効にならないのでは。。。
- 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()
が呼ばれる。
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に該当するものが自動的に選択される。
以上になります。