デバイスツリーの基本構造とC言語による解析
組み込みLinuxシステムにおいて、デバイスツリー(Device Tree)はハードウェアリソースと周辺機器の接続関係を記述するために不可欠です。C言語を用いてデバイスツリーノードを解析することで、ハードウェア構成情報を動的に取得し、ドライバの移植性を向上させることができます。
デバイスツリーの構造
デバイスツリーソース(.dts)はバイナリ形式(.dtb)にコンパイルされ、ブートローダーによってメモリにロードされます。カーネルはこの構造を解析して platform_device を構築します。各ノードには、互換性を示す compatible、アドレスを示す reg、割り込みを示す interrupts などの主要なプロパティが含まれます。
C言語によるノード情報の取得
Linuxカーネルは、デバイスツリーの内容にアクセスするためのAPIを提供しています。代表的な関数には of_find_node_by_name()、of_property_read_u32()、of_iomap() などがあります。
// デバイスツリーから周辺機器パラメータを解析する実装例
#include <linux/of.h>
#include <linux/platform_device.h>
static int dev_resource_probe(struct platform_device *pdev)
{
struct device_node *node = pdev->dev.of_node;
u32 addr_offset, poll_interval;
// レジスタのベースアドレス・オフセットを読み取り
if (of_property_read_u32(node, "reg", &addr_offset)) {
return -EINVAL;
}
// 更新間隔(単位:ms)を読み取り、存在しない場合はデフォルト値を設定
if (of_property_read_u32(node, "update-ms", &poll_interval)) {
poll_interval = 500; // デフォルト値
}
dev_info(&pdev->dev, "Base: 0x%x, Interval: %u ms\n", addr_offset, poll_interval);
return 0;
}
// マッチングテーブルの定義
static const struct of_device_id custom_driver_match[] = {
{ .compatible = "my-corp,custom-periph" },
{ }
};
MODULE_DEVICE_TABLE(of, custom_driver_match);
このプロセスでは、まず device_node ポインタを取得し、of_property_read_u32 を使用して数値プロパティを抽出します。そして、compatible 文字列を照合することで、ドライバとデバイスノードが紐付けられます。
カーネルにおけるデバイスツリーのロードと初期化
カーネルは起動の初期段階で、U-Bootなどのブートローダーから渡されたDTB(Device Tree Blob)を検証します。early_init_dt_verify() によって整合性が確認された後、解析フェーズに移ります。
情報の展開(Unflattening)
カーネルは unflatten_device_tree() を呼び出し、フラットなバイナリ構造を struct device_node の階層的なオブジェクトへと変換します。これにより、カーネル内の各サブシステムがノード間の親子関係を容易に辿れるようになります。
void __init unflatten_device_tree(void)
{
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false);
}
initial_boot_params はDTBの開始アドレスを指し、of_root に解析後のルートノードが保持されます。
ドライバにおけるデバイスツリー解析のパターン
プラットフォームドライバは、デバイスツリーを利用してハードウェア記述とドライバコードを分離(デカップリング)します。
of_match_table による自動マッチング
ドライバが登録される際、カーネルは of_match_table を参照して適切なデバイスを探索します。
static const struct of_device_id hardware_ids[] = {
{ .compatible = "acme,io-controller-v1", .data = (void *)RESOURCE_TYPE_A },
{ .compatible = "acme,io-controller-v2", .data = (void *)RESOURCE_TYPE_B },
{ /* センチネル */ }
};
デバイスツリー内のノードが compatible = "acme,io-controller-v1"; を持っている場合、このドライバが選択され、probe 関数が実行されます。
リソースの動的取得:I/Oメモリと割り込み
ハードコードされたアドレスを避け、platform_get_resource を用いてリソースを取得するのが現代的な手法です。
struct resource *mem_res;
void __iomem *reg_base;
int irq_line;
// メモリリソースの取得とマッピング
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
reg_base = devm_ioremap_resource(&pdev->dev, mem_res);
// 割り込み番号の取得
irq_line = platform_get_irq(pdev, 0);
高度な解析テクニックとトラブルシューティング
phandle によるノード間参照
デバイスツリーでは、あるノードが別のノードを参照する場合に phandle を使用します。例えば、GPIOコントローラやクロック源を指定する際に頻用されます。
// DTS内での参照例
display_subsystem {
pwms = <&pwm1 0 5000000>;
};
ドライバ側では of_parse_phandle() を用いてターゲットとなるノードのポインタを取得できます。
dmesg による不整合の特定
ドライバの紐付けが失敗した場合、dmesg ログを確認することが解決への近道です。よくあるエラーパターンは以下の通りです。
- status = "disabled": デバイスツリー上でノードが無効化されている。
- Compatible string mismatch: DTSの文字列とドライバのマッチテーブルが一致していない。
- Invalid phandle: 参照しているノードが存在しない、または名前が間違っている。
ログに of_parse_phandle: bad phandle などのメッセージが出力されている場合は、DTS内の参照関係を再確認する必要があります。これにより、ハードウェアの定義ミスを早期に発見できます。