DTO 최적화

이 페이지에서는 DTO 구현에 적용 가능한 최적화를 논의하고 루트 노드 오버레이의 제한사항을 설명하고 DTBO 이미지에서 압축된 오버레이를 구성하는 자세한 방법을 다루며 샘플 구현 안내 및 코드도 제공합니다.

커널 명령줄

기기 트리의 원래 커널 명령줄은 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의 내부 테스트에서 2405 .dtb 및 283 .dtbo DT 노드에 libufdt를 사용하면 컴파일 후 파일 크기가 70,618 및 8,566바이트가 됩니다. FreeBSD(124ms 런타임)에서 포팅된 DTO 구현과 비교하면 libufdt DTO 런타임은 10ms입니다.

Pixel 기기의 성능 테스트에서는 libufdtlibfdt를 비교했습니다. 기본 노드 효과의 수는 비슷하지만 다음과 같은 차이가 있습니다.

  • 500 오버레이(추가 또는 재정의) 작업에 6배~8배의 시간차가 있음
  • 1000 오버레이(추가 또는 재정의) 작업에 8배~10배의 시간차가 있음

X로 설정된 횟수 추가 예시:

그림 1. 추가 횟수: X

X로 설정된 횟수 재정의의 예:

그림 2. 재정의 횟수: X

libufdtlibfdt 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 버전을 사용할 때 DTBO 이미지에 압축된 오버레이를 사용할 수 있도록 지원하는 기능이 추가되었습니다. DTBO 헤더 v1을 사용할 때에는 dt_table_entry에 있는 가장 작은 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' will be 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 */
};

현재 zlibgzip 압축이 지원됩니다.

enum dt_compression_info {
    NO_COMPRESSION,
    ZLIB_COMPRESSION,
    GZIP_COMPRESSION
};

Android 9에서는 오버레이 애플리케이션의 정확성을 확인하는 데 도움이 되도록 압축 오버레이 테스트 지원 기능을 VtsFirmwareDtboVerification 테스트에 추가합니다.

샘플 DTO 구현

다음 안내는 libufdt(아래 샘플 코드)를 포함하는 DTO의 샘플 구현을 보여줍니다.

샘플 DTO 안내

  1. 라이브러리를 포함합니다. libufdt를 사용하려면 데이터 구조 및 API에 libfdt를 포함합니다.
    #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. DTO에 ufdt_apply_overlay()를 호출하여 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);
}