DTO の最適化

このページでは、デバイスツリー オーバーレイ(DTO)の実装で可能な最適化、ルートノードのオーバーレイにおける制限、DTBO イメージでの圧縮オーバーレイの設定方法の詳細について説明します。さらに、実装手順のサンプルとコードも示します。

カーネル コマンドライン

デバイスツリー(DT)の元のカーネル コマンドラインは、chosen/bootargs ノードにあります。ブートローダーは、この場所をカーネル コマンドラインの他のソースと連結する必要があります。

/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};

DTO は、メイン DT とオーバーレイ DT から値を連結することはできません。メイン DT のカーネル コマンドラインを chosen/bootargs に挿入し、オーバーレイ DT のカーネル コマンドラインを chosen/bootargs_ext に挿入する必要があります。ブートローダーは、その後、これらの場所を連結し、結果をカーネルに渡すことができます。

main.dts overlay.dts
/dts-v1/;

/ {
  chosen: chosen {
    bootargs = "...";
  };
};
/dts-v1/;
/plugin/;

&chosen {
  bootargs_ext = "...";
};

libufdt

最新の libfdt は DTO をサポートしますが、libufdt を使用して DTO を実装します(platform/system/libufdt にある AOSP ソース)。libufdt は、フラット化されたデバイスツリー(FDT)から、実際のツリー構造(フラット化されていないデバイスツリー(ufdt)を構築するため、2 つの .dtb ファイルのマージが O(N2) から O(N) に改善されます(N は、ツリー内のノードの数)。

パフォーマンス テスト

Google の内部テストでは、2,405 個の .dtb DT ノードと 283 個の .dtbo DT ノードで libufdt を使用すると、コンパイル後のファイルサイズは 70,618 バイトと 8,566 バイトになります。FreeBSD から移植された DTO の実装(124 ms ランタイム)と比較すると、libufdt DTO のランタイムは 10 ms です。

libufdtlibfdt を比較した Pixel デバイスのパフォーマンス テスト。ベースノードの数による影響は似ていますが、次の違いがあります。

  • 500 回のオーバーレイ(追加または上書き)操作では、6 倍から 8 倍の時間差があります
  • 1,000 回のオーバーレイ(追加または上書き)操作では、8 倍から 10 倍の時間差があります

追加回数を X に設定した例:

図 1. 追加回数を X に設定。

オーバーライド回数を X に設定した例:

図 2. オーバーライド回数を X に設定。

libufdt は、いくつかの libfdt API とデータ構造を使用して構築します。libufdt を使用する場合は、libfdt を含めてリンクする必要があります。ただし、コードでは、libfdt API を使用して DTB や DTBO を操作できます。

libufdt DTO API

libufdt の DTO へのメイン API は次のとおりです。

struct fdt_header *ufdt_apply_overlay(
        struct fdt_header *main_fdt_header,
        size_t main_fdt_size,
        void *overlay_fdt,
        size_t overlay_size);

パラメータ main_fdt_header はメイン DT で、overlay_fdt.dtbo ファイルのコンテンツを格納しているバッファです。戻り値は、統合した DT を含む新しいバッファです(エラーの場合は null です)。統合した DT は FDT 形式でフォーマットされ、カーネルの起動時にカーネルに渡すことができます。

戻り値からの新しいバッファは、libufdt をブートローダーに移行するときに実装する必要のある dto_malloc() によって作成されます。リファレンス実装については、sysdeps/libufdt_sysdeps_*.c をご覧ください。

ルートノードの制限

オーバーレイ操作はラベルに依存するため、メイン DT のルートノードに新しいノードやプロパティをオーバーレイすることはできません。ラベルはメイン DT が定義する必要があり、オーバーレイ DT はラベルを使用してオーバーレイするノードを割り当てるため、ルートノードにラベルを付与することはできません。そのため、ルートノードにはオーバーレイできません。

SoC ベンダーは、メイン DT のオーバーレイ機能を定義する必要があります。ODM / OEM がノードを追加または上書きするには SoC ベンダーが定義したラベルを使用する必要があります。回避策として、ベースの DT のルートノードの下に odm ノードを定義し、オーバーレイ DT のすべての ODM ノードが新しいノードを追加できるようにできます。または、ベースの DT のすべての SoC 関連ノードを、以下で説明するように、ルートノードの下の soc ノードに配置することもできます。

main.dts overlay.dts
/dts-v1/;

/ {
    compatible = "corp,bar";
    ...

    chosen: chosen {
        bootargs = "...";
    };

    /* nodes for all soc nodes */
    soc {
        ...
        soc_device@0: soc_device@0 {
            compatible = "corp,bar";
            ...
        };
        ...
    };

    odm: odm {
        /* reserved for overlay by odm */
    };
};
/dts-v1/;
/plugin/;

/ {
};

&chosen {
    bootargs_ex = "...";
};

&odm {
    odm_device@0 {
        ...
    };
    ...
};

圧縮オーバーレイの使用

Android 9 では、バージョン 1 の DT テーブル ヘッダーを使用している場合に、DTBO イメージで圧縮オーバーレイを使用できるようになりました。DTBO header v1 を使用する場合、dt_table_entry 内の flags フィールドの最下位 4 ビットは DT エントリの圧縮形式を示します。

struct dt_table_entry_v1 {
  uint32_t dt_size;
  uint32_t dt_offset;  /* offset from head of dt_table_header */
  uint32_t id;         /* optional, must be zero if unused */
  uint32_t rev;        /* optional, must be zero if unused */
  uint32_t flags;      /* For version 1 of dt_table_header, the 4 least significant bits
                        of 'flags' are used to indicate the compression
                        format of the DT entry as per the enum 'dt_compression_info' */
  uint32_t custom[3];  /* optional, must be zero if unused */
};

現在、zlib 圧縮と gzip 圧縮がサポートされています。

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

Android 9 では、VtsFirmwareDtboVerification テストで圧縮オーバーレイをテストできるようになりました。これにより、オーバーレイ アプリの正確性を確認できます。

DTO の実装のサンプル

libufdt を使用した DTO の実装のサンプルを次に示します(以下のサンプルコードを参照)。

DTO のサンプルの手順

  1. ライブラリを追加します。libufdt を使用するには、libfdt を追加してデータ構造と API を指定します。
    #include <libfdt.h>
    #include <ufdt_overlay.h>
    
  2. メイン DT とオーバーレイ DT を読み込みます。.dtb.dtbo をストレージからメモリに読み込みます。正確な手順は、設計によって異なります。この時点で、.dtb.dtbo のバッファとサイズが取得できます。
    main_size = my_load_main_dtb(main_buf, main_buf_size)
    
    overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);
    
  3. DT をオーバーレイします。
    1. ufdt_install_blob() を使用して、メイン DT の FDT ヘッダーを取得します。
      main_fdt_header = ufdt_install_blob(main_buf, main_size);
      main_fdt_size = main_size;
      
    2. ufdt_apply_overlay() を DTO に呼び出して、FDT 形式の統合した DT を取得します。
      merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                      overlay_buf, overlay_size);
      
    3. merged_fdt を使用して、dtc_totalsize() のサイズを取得します。
      merged_fdt_size = dtc_totalsize(merged_fdt);
      
    4. 統合した DT を渡して、カーネルを起動します。
      my_kernel_entry(0, machine_type, merged_fdt);
      

DTO コードのサンプル

#include <libfdt.h>
#include <ufdt_overlay.h>

…

{
  struct fdt_header *main_fdt_header;
  struct fdt_header *merged_fdt;

  /* load main dtb into memory and get the size */
  main_size = my_load_main_dtb(main_buf, main_buf_size);

  /* load overlay dtb into memory and get the size */
  overlay_size = my_load_overlay_dtb(overlay_buf, overlay_buf_size);

  /* overlay */
  main_fdt_header = ufdt_install_blob(main_buf, main_size);
  main_fdt_size = main_size;
  merged_fdt = ufdt_apply_overlay(main_fdt_header, main_fdt_size,
                                  overlay_buf, overlay_size);
  merged_fdt_size = dtc_totalsize(merged_fdt);

  /* pass to kernel */
  my_kernel_entry(0, machine_type, merged_fdt);
}