序文:
この記事は、デバイスツリーに関するWei Dongshan先生の説明、元のアドレスから転送されます。レッスン3:Linuxカーネルによるデバイスツリーの処理。
1.ソースから分析します-カーネルhead.Sによるdtbの簡単な処理
最初のセクションを開始します。ソースから分析する必要があります。ubootはいくつかのパラメーターとデバイスツリーファイルをカーネルに渡します。次に、カーネルはこれらのデバイスツリーファイルをどのように処理しますか?カーネル内の実行可能ファイルhead.Sから分析を開始する必要があります。
1.1。r0、r1、r23つのレジスタの設定
BootLoaderがカーネルを起動すると、r0、r1、およびr2の3つのレジスタが設定されます。
- r0は通常0に設定されます。
- r1は通常、マシンIDに設定されます(このパラメーターは、デバイスツリーを使用する場合は使用されません)。
- r2は通常、ATAGSまたはDTBの開始アドレスに設定されます。
ここでのマシンIDは、カーネルにそれがどのCPUであるかを通知し、対応する初期化関数を呼び出すためのものです。以前にデバイスツリーを使用していなかった場合、BootLoaderはマシンIDをカーネルに渡す必要がありましたが、デバイスツリーを使用する場合は、このパラメーターを設定する必要はありません。
r2は、ATAGSの以前の開始アドレス、またはデバイスツリーが使用された後のDTBファイルの開始アドレスのいずれかです。
1.2、head.Sの内容
カーネルhead.Sによって行われる作業は次のとおりです。
- __lookup_processor_type:アセンブリ命令を使用してCPU IDを読み取り、IDに従って対応するproc_info_list構造体(初期化関数とこのタイプのCPUの情報を含む)を見つけます。
- __vet_atags:利用可能なATAGまたはDTBがあるかどうかを判断します。
- __create_pag_tables:ページテーブルを作成します。つまり、仮想アドレスと物理アドレスの間にマッピング関係を作成します。
- __enable_mmu:MMUを有効にします。仮想アドレスは将来使用されます。
- __mmap_switched:__ mmap_swtichedは上記の関数で呼び出されます。
- BootLoaderから渡されたr2パラメーターを変数__atags_pointerに保存します。
- C関数start_kernelを呼び出します。
最終効果:
head.Sとhead-common.Sの最終的な効果:
-
BootLoaderからのr1の値をC変数に割り当てます。__machine_arch_type;
-
BootLoaderによって渡されるr2値、
Ubootは、最初にデバイスツリーファイルをメモリに読み込み、カーネルの起動時にデバイスツリーのアドレスをr2レジスタに書き込みます。
2.デバイスツリーでのプラットフォーム情報の処理(machine_descを選択)
このセクションでは、カーネルがデバイスツリーでプラットフォームデバイス情報を処理する方法について説明します。
2.1。カーネルはどのようにして対応するmachine_descを選択しますか?
前に説明したように、uImageにコンパイルされたカーネルイメージファイルは複数のボードをサポートできます。smdk2410、smdk2440、jz2440がサポートされていることを前提としています(amdk2410、smdk2440はメーカーのパブリックボードであり、国内メーカーは独自のリファレンスボードを設計しています。ボード、 jz2440など)。
これらのボードの構成はわずかに異なり、個別の初期化が必要です。カーネルでは、これらのボードに対して、.initと.nrを含むmachine_desc構造が構築されます。
JZ2440の場合、smdk2440から派生し、カーネルには個別のファイルがなく、smdk2440関連のファイルとコードを使用します。
前のビデオでは、ubootがATAGを使用してパラメーターをカーネルに渡すと、マシンIDが渡され、カーネルはこのマシンIDを使用して最適なmachine_descを見つけると述べました。つまり、マシンIDはmachine_descの.nrと比較され、等しいということは、対応するmachine_descが見つかることを意味します。
ubootがパラメータの受け渡しにATAGを使用せず、DTBファイルを使用する場合、カーネルは対応するmachine_descをどのように選択しますか?
デバイスツリーファイルのルートノードには、次の2行があります。
model = "SMDK2440"
compatible = "samsung,smdk2440","samsung,smdk2410","samsung,smdk24xx";
ここでの互換性のある属性は、必要なmachine_descを宣言し、属性値は、machine_descに順番に一致する一連の文字列にすることができます。
カーネルはsasung、smdk2440をサポートするのに最適です。そうでない場合は、sasung、smdk2410をサポートしてみてください。そうでない場合は、最後にsasung、smdk24xxを試してください。
結論として:
-
デバイスツリーのルートノードの互換性のある属性には、互換性のあるボードの名前を「最も互換性のある」から2番目まで示す一連の文字列が一覧表示されます。
-
カーネルには複数のmachine_descがあり、その中にdt_compatメンバーがあります。これは、machine_descがサポートするボードを示す文字列の配列を指します。
-
互換性のある属性の値を使用して、各「machine_desc.dt_compat」と比較すると、スコアは「一致する互換性のある属性値の位置」であり、スコアが低いほど一致し、対応するmachine_descが選択されます。
2.2、start_kernelの呼び出しプロセス
前のセクションのビデオでは、head.SはDTBの場所を変数__atags_pointerに保存し、最後にstart_kernelを呼び出します。
start_kernelの呼び出しプロセスは次のとおりです。
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c
early_init_dt_verify(phys_to_virt(dt_phys) // 判断是否有效的dtb, drivers/of/ftd.c
initial_boot_params = params;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); // 找到最匹配的machine_desc, drivers/of/ftd.c
while ((data = get_next_compat(&compat))) {
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
machine_desc = mdesc;
3つ目は、デバイス数での実行時の構成情報の処理です。
デバイスツリーは情報送信の役割を果たすだけであり、この情報構成の処理は比較的単純です。つまり、デバイスツリーのDTBファイルからこの情報を抽出し、カーネル内の変数に割り当てます。
関数呼び出しプロセスは次のとおりです。
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c
early_init_dt_scan_nodes(); // drivers/of/ftd.c
/* Retrieve various information from the /chosen node */
of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
/* Initialize {size,address}-cells info */
of_scan_flat_dt(early_init_dt_scan_root, NULL);
/* Setup memory, calling early_init_dt_add_memory_arch */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
情報処理には主に3つのタイプがあります。/chosenノードのbootags属性、ルートノードの#address-cells属性と#size-cells属性、および/ memoryのreg属性です。
-
/ selectedノードのbootargs属性は、カーネル起動のコマンドラインパラメーターです。ルートファイルシステムの場所、最初に実行するアプリケーション、カーネルの印刷情報を印刷するデバイスを指定できます。
-
/ memoryのreg属性は、さまざまなボードメモリのサイズと開始アドレスを指定します。
-
ルートノードの#address-cells属性と#size-cells属性は、属性パラメーターのビット数を指定します。たとえば、前のメモリのreg属性のアドレスが32ビットか64ビットか、サイズは、1つの32ビットまたは2つの32ビットSaidで表されます。
総括する:
- / selectedノードのbootargs属性の値は、グローバル変数に格納されます。boot_command_line;
- ルートノードの次の2つの属性の値を決定します:#address-cells、#size-cells、それらをグローバル変数に格納します:dt_root_addr_cells、dt_root_size_cells;
- / memoryのreg属性を分析し、「base、size」を抽出して、最後にmemblock_add(base、size)を呼び出します。
4. dtbをdevice_nodeに変換します(平坦化しない)
説明する前に、まず質問について考えてみましょう。ubootは、デバイスツリーのDTBファイルをメモリ内の特定の場所に配置して使用できるようにします。カーネルの実行中にDTBが占有するメモリを上書きしないのはなぜですか。
以前にデバイスツリー形式について説明したとき、デバイスツリーファイルで/ memreserve /を使用してメモリの一部を指定できることを知っていました。このメモリは予約済みメモリであり、カーネルはそれを占有しません。このメモリを指定しなくても、カーネルが起動すると、デバイスの数が占める領域が保持されます。
以下は、関数呼び出しプロセスです。
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
arm_memblock_init(mdesc); // arch/arm/kernel/setup.c
early_init_fdt_reserve_self();
/* Reserve the dtb region */
// 把DTB所占区域保留下来, 即调用: memblock_reserve
early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
fdt_totalsize(initial_boot_params),
0);
early_init_fdt_scan_reserved_mem(); // 根据dtb中的memreserve信息, 调用memblock_reserve
unflatten_device_tree(); // arch/arm/kernel/setup.c
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c
/* First pass, scan for size */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
/* Second pass, do actual unflattening */
unflatten_dt_nodes(blob, mem, dad, mynodes);
populate_node
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
np->full_name = fn = ((char *)np) + sizeof(*np);
populate_properties
pp = unflatten_dt_alloc(mem, sizeof(struct property),
__alignof__(struct property));
pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)val;
ご覧のとおり、最初にカーネルにdtbのmemreserve情報を伝え、このメモリ領域を占有せずに予約します。
次に、デバイスツリーのフラット構造を抽出し、2つの構造(device_node構造とプロパティ構造)を含むツリーを構築します。これらの2つの構造を理解することで、このビデオの主な内容を理解できるでしょう。
dtsファイルでは、各中括弧{}はノードを表します。たとえば、ルートノードにはdevice_node構造に対応する中括弧があります。メモリにもdevice_node構造に対応する中括弧があります。
ノードにはさまざまな属性があり、子ノードも含まれている可能性があるため、親子関係もあります。
ルートノードの下にあるメモリ、選択、LED、およびその他のノードは、並列関係と兄弟関係にあります。
親子関係と兄弟関係の場合、device_node構造は次のように定義されます。
struct device_node {
const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL"
const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL"
phandle phandle;
const char *full_name; // 节点的名字, node-name[@unit-address]
struct fwnode_handle fwnode;
struct property *properties; // 节点的属性
struct property *deadprops; /* removed properties */
struct device_node *parent; // 节点的父亲
struct device_node *child; // 节点的孩子(子节点)
struct device_node *sibling; // 节点的兄弟(同级节点)
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
device_node構造体はノードを表し、property構造体はノードの特定のプロパティを表します。
プロパティ構造の定義は次のとおりです。
struct property {
char *name; // 属性名字, 指向dtb文件中的字符串
int length; // 属性值的长度
void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储
struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
};
2つの構造とdtsの内容の相対的な関係は次のとおりです。
特定のコード分析については、ビデオコンテンツを参照してください。
5、device_nodeはplatform_deviceに変換されます
カーネルがdevice_nodeをplatform_deviceに変換する方法。
2つの質問:
5.1。どのdevice_nodeをplatform_deviceに変換できるか
/ {
model = "SMDK24440";
compatible = "samsung,smdk2440";
#address-cells = <1>;
#size-cells = <1>;
//内存设备不会
memory@30000000 {
device_type = "memory";
reg = <0x30000000 0x4000000>;
};
/*
cpus {
cpu {
compatible = "arm,arm926ej-s";
};
};
*/ //只是设置一些启动信息
chosen {
bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
};
/*只有这个led设备才对转换成platfrom_device */
led {
compatible = "jz2440_led";
reg = <S3C2410_GPF(5) 1>;
};
/************************************/
};
カーネル関数of_platform_default_populate_initは、device_nodeツリーをトラバースし、paltform_deviceを生成します。
すべてのdevice_nodesがplatform_deviceに変換されるわけではなく、次のdevice_nodesのみが変換されます。
- ノードには互換性のある属性が含まれている必要があります。
- ルートノードの子ノード(ノードには互換性のある属性が含まれている必要があります)。
- 特別な互換性のある属性を持つノードの子ノード(子ノードには互換性のある属性が必要です):これらの特別な互換性のある属性は、「simple-bus」、「simple-mfd」、「isa」、「arm、amba-bus」です。
ルートノードは例外です。platform_deviceが生成されると、互換性のある属性があっても処理されません。
例えば:
CPUは、LEDの多くの周辺機器、spiコントローラー、i2cコントローラーにアクセスできます。
これらのハードウェアをデバイスツリーでどのように説明しますか?
たとえば、次のノード/ mytestは「simple-bus」と互換性があるため、platform_deviceに変換され、その子ノード/ mytest / mytest @ 0もplatform_deviceに変換されます。
/ i2cノードは通常i2cコントローラーを表します。これはplatform_deviceに変換され、カーネルには対応するplatform_driverがあります。/i2c/at24c02ノードはplatform_deviceに変換されず、その処理方法は完全に親ノードのplatform_driverは、通常、i2c_clientとして作成されました。
同様に、SPIコントローラーを表すために一般的に使用される/ spiノードがあります。これはplatform_deviceに変換され、カーネルに対応するplatform_driverがあります。/spi/flash@0ノードはplatform_deviceに変換されません。 、どのように処理されるか親ノードのplatform_driverによって完全に決定され、通常はspi_deviceとして作成されます。
/ {
mytest {
compatile = "mytest", "simple-bus";
mytest@0 {
compatile = "mytest_0";
};
};
i2c {
compatile = "samsung,i2c";
at24c02 {
compatile = "at24c02";
};
};
spi {
compatile = "samsung,spi";
flash@0 {
compatible = "winbond,w25q32dw";
spi-max-frequency = <25000000>;
reg = <0>;
};
};
};
5.2変換方法
関数呼び出しプロセス:
a。エントリ関数of_platform_default_populate_init(drivers / of / platform.c)が呼び出されるプロセス:
セクション属性があり、コンパイルされたカーネルセクション属性の変数はグループ化されます。
vim arch / arm / kernel / vmlinux.lds
start_kernel // init/main.c
rest_init();
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
kernel_init
kernel_init_freeable();
do_basic_setup();
do_initcalls();
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level); // 比如 do_initcall_level(3)
for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
do_one_initcall(initcall_from_entry(fn)); // 就是调用"arch_initcall_sync(fn)"中定义的fn函数
b。platform_deviceを生成するためのof_platform_default_populate_init(drivers / of / platform.c)のプロセス:
デバイスツリーをトラバースします。
of_platform_default_populate_init
of_platform_default_populate(NULL, NULL, NULL);
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true); // 调用过程看下面
dev = of_device_alloc(np, bus_id, parent); // 根据device_node节点的属性设置platform_device的resource
if (rc) {
of_node_put(child);
break;
}
}
c。of_platform_bus_create(bus、matches、...)の呼び出しプロセス(バスノードを処理してplatform_deviceを生成し、その子ノードを処理するかどうかを決定します):
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); // 生成bus节点的platform_device结构体
if (!dev || !of_match_node(matches, bus)) // 如果bus节点的compatile属性不吻合matches成表, 就不处理它的子节点
return 0;
for_each_child_of_node(bus, child) { // 取出每一个子节点
pr_debug(" create child: %pOF\n", child);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); // 处理它的子节点, of_platform_bus_create是一个递归调用
if (rc) {
of_node_put(child);
break;
}
}
d。I2Cバスノードの処理プロセス:
/ i2cノードは通常、カーネル内の対応するplatform_driverを使用してplatform_deviceに変換されるi2cコントローラーを表します。i2c_add_numbered_adapterはplatform_drvierのプローブ関数で呼び出されます。
i2c_add_numbered_adapter // drivers/i2c/i2c-core-base.c
__i2c_add_numbered_adapter
i2c_register_adapter
of_i2c_register_devices(adap); // drivers/i2c/i2c-core-of.c
for_each_available_child_of_node(bus, node) {
client = of_i2c_register_device(adap, node);
client = i2c_new_device(adap, &info); // 设备树中的i2c子节点被转换为i2c_client
总結:dtb-> device_node-> platform_device
6、platform_deviceとplatform_driverの一致
drivers / base / platform.c:
a。platform_driverを登録するプロセス:
platform_driver_register
__platform_driver_register
drv->driver.probe = platform_drv_probe;
driver_register
bus_add_driver
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 把 platform_driver 放入 platform_bus_type 的driver链表中
driver_attach
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); // 对于plarform_bus_type下的每一个设备, 调用__driver_attach
__driver_attach
ret = driver_match_device(drv, dev); // 判断dev和drv是否匹配成功
return drv->bus->match ? drv->bus->match(dev, drv) : 1; // 调用 platform_bus_type.match
driver_probe_device(drv, dev);
really_probe
drv->probe // platform_drv_probe
platform_drv_probe
struct platform_driver *drv = to_platform_driver(_dev->driver);
drv->probe
b。platform_deviceを登録するプロセス:
platform_device_register
platform_device_add
device_add
bus_add_device
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices); // 把 platform_device 放入 platform_bus_type的device链表中
bus_probe_device(dev);
device_initial_probe
__device_attach
ret = bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver); // // 对于plarform_bus_type下的每一个driver, 调用 __device_attach_driver
__device_attach_driver
ret = driver_match_device(drv, dev);
return drv->bus->match ? drv->bus->match(dev, drv) : 1; // 调用platform_bus_type.match
driver_probe_device
マッチング関数はplatform_bus_type.match、つまりplatform_matchです。マッチングプロセスは、優先度の順に次のようにリストされています。
- 比较platform_dev.driver_override和platform_driver.drv(.driver)-> name;
- platform_dev.dev.of_nodeの互換性のあるプロパティをplatform_driver.drv(.driver)-> of_match_tableと比較します。
- platform_dev.nameとplatform_driver.id_tableを比較します。
- platform_dev.nameとplatform_driver.drv(.driver)-> nameを比較します。
成功、つまり試合は成功です!
セブン、カーネル内のデバイスツリーの操作機能
include / linux /ディレクトリには、ので始まる多くのヘッダーファイルがあります。
7.1。DTBの処理
of_fdt.h //dtb 文件的相关操作函数,我们一般用不到,因为 dtb 文件在内核中已经被转换为 device_node 树(它更易于使用)
7.2、device_nodeの処理
of.h // 提供设备树的一般处理函数, 比如 of_property_read_u32(读取某个属性的u32值), *of_get_child_count(获取某个device_node的子节点数)
of_address.h // 地址相关的函数, 比如 of_get_address(获得reg属性中的addr, size值)
of_match_device(从matches数组中取出与当前设备最匹配的一项)
of_dma.h // 设备树中DMA相关属性的函数
of_gpio.h // GPIO相关的函数
of_graph.h // GPU相关驱动中用到的函数, 从设备树中获得GPU信息
of_iommu.h // 很少用到
of_irq.h // 中断相关的函数
of_mdio.h // MDIO (Ethernet PHY) API
of_net.h // OF helpers for network devices.
of_pci.h // PCI相关函数
of_pdt.h // 很少用到
of_reserved_mem.h // reserved_mem的相关函数
割り込み関連を例にとると、デバイスは割り込みを発行できます。これには、割り込み番号と割り込みトリガーモードが含まれている必要があります。
公式デバイスツリー仕様ツリーのデバイスの例:
soc {
#address-cells = <1>;
#size-cells = <1>;
serial {
compatible = "ns16550";
reg = <0x4600 0x100>;
clock-frequency = <0>;
interrupts = <0xA 0x8>;
interrupt-parent = <&ipic>;
};
};
内部のプロパティにはカットオフ値があります。
沿って:
int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq);
特定の値のペアを解析するか、元のデータを解析できます。
int of_irq_parse_raw(const __be32 *addr, struct of_phandle_args *out_irq);
addrは、特定の値のペアを指し、内部の割り込み番号のトリガーメソッドを解析し、それをof_phandle_args構造体に保存します。
7.3、platform_deviceの処理
of_platform.h //把 device_node 转换为 platform_device 时用到的函数
/* Platform drivers register/unregister */
extern struct platform_device *of_device_alloc(struct device_node *np, const char *bus_id, struct device *parent);
ファイルに関連する関数は、device_node-> platform_deviceで広く使用されています。
// 比如of_device_alloc(根据device_node分配设置platform_device),
// of_find_device_by_node (根据device_node查找到platform_device),
// of_platform_bus_probe (处理device_node及它的子节点)
of_device.h // 设备相关的函数, 比如 of_match_device
可以通过of_match_device找出哪一项最匹配
ファイルの数は3つのカテゴリに分類されます。
- DTBを処理します。
- device_nodeを処理します。
- platform_deviceデバイス関連情報の処理。
8.ルートファイルシステムでデバイスツリーを表示します(デバッグに役立ちます)
8.1、/ sys / firmware / fdt
元のdtbファイルを表示します。
hexdump -C /sys/firmware/fdt
8.2、/ sys / firmware / devicetree
ディレクトリ構造で表示されるdtbファイル、ルートノードはベースディレクトリに対応し、各ノードはディレクトリに対応し、各属性はファイルに対応します。たとえば、#address-cellsの16進数を確認します。
hexdump -C "#address-cells"
互換性のあるビュー:
cat compatible
デバイスツリーのデバイスノードに間違った割り込み属性を設定すると、LEDに対応するプラットフォームデバイスノードを作成できません。
8.3、/ sys / device / platform
システム内のすべてのplatform_devicesはデバイスツリーから取得され、一部は.cファイルに登録されます。
デバイスツリーのplatform_deviceの場合、/ sys / devices / platform / <デバイス名> / of_nodeと入力して、デバイスツリーのプロパティを表示できます。
8.4、/ proc / device-tree
/ sys / firmware / devicetree / baseを指すリンクファイル。