임베디드 하드웨어 개발 팀에서 새로운 가상 칩셋이나 커스텀 보드를 설계하고 나면, 소프트웨어 팀에게 "여기에 안드로이드 OS 최신 버전을 올려달라"는 임무가 떨어집니다. 하지만 PC에 윈도우나 리눅스를 설치하듯 뚝딱 설치할 수 있는 게 아닙니다. 대상 보드의 CPU 아키텍처, 램 용량, 레지스터 주소 맵을 안드로이드 빌드 시스템과 리눅스 커널에 일일이 알려주어야 하죠.
이 과정을 안드로이드 디바이스 포팅(Device Porting) 또는 BSP(Board Support Package) 개발이라고 부릅니다. 이번 포스팅에서는 포팅의 성패를 가르는 가장 첫 단추이자 핵심인 BoardConfig.mk 환경 설정과 하드웨어 명세서 역할을 하는 Device Tree(디바이스 트리) 구성, 그리고 시스템/벤더 파티션을 매핑하는 구체적인 절차를 소스 코드와 함께 살펴보겠습니다.

📌 핵심 요약 3줄
- Android 디바이스 포팅은 AOSP 빌드 시스템에 하드웨어 스펙을 알려주는 BoardConfig.mk 작성부터 시작됩니다.
- 리눅스 커널이 하드웨어 주변장치를 인식할 수 있도록 구조화된 명세서인 디바이스 트리(.dts/.dtsi)를 보드 레이아웃에 맞춰 구성해야 합니다.
- 최신 안드로이드 가이드라인에 맞춰 구글 순정 영역(System)과 제조사 독점 영역(Vendor) 파티션의 크기 및 파일 시스템 형식을 정교하게 격리해 주어야 부팅에 성공합니다.
1. 안드로이드 디바이스 포팅 파이프라인
새로운 보드에 OS를 올릴 때 개발자가 타겟팅해야 하는 단계별 작업 영역과 주요 파일들의 관계를 표로 정리해 보았습니다. 아키텍처의 큰 그림을 먼저 머릿속에 넣어두면 디버깅할 때 길을 잃지 않습니다.
| 포팅 단계 | 주요 작업 내용 | 담당 핵심 파일 / 경로 | 비고 |
| 1. 빌드 환경 정의 | 타겟 보드의 CPU 아키텍처, 파티션 크기, 커널 변수 선언 | device/{vendor}/{device}/BoardConfig.mk | AOSP 빌드 시스템(Make/Android.bp) 참조용 |
| 2. 하드웨어 명세 | RAM 매핑, CPU 코어 개수, GPIO 및 인터럽트 주소 기술 | arch/arm64/boot/dts/ 내 .dts, .dtsi | 리눅스 커널 컴파일러(DTC) 참조용 |
| 3. 파티션 이미지 빌드 | 하드웨어 전용 펌웨어, 드라이버 라이브러리 분리 배포 환경 설정 | system.img, vendor.img | 트레블 프로젝트(Project Treble) 규격 준수 |
2. 하드웨어 사양을 명시하는 BoardConfig 설정
AOSP 소스 트리에서 우리 보드의 전용 디렉토리 경로(device/qcom/example_device/)를 개설했다면, 빌드 시스템의 나침반이 될 BoardConfig.mk를 작성해야 합니다.
2.1 SoC 및 CPU 아키텍처 선언
# 타겟 칩셋 플랫폼 명시 (예: 퀄컴 MSM8953)
TARGET_BOARD_PLATFORM := msm8953
# 아키텍처 및 명령 세트 기술
TARGET_ARCH := arm64
TARGET_ARCH_VARIANT := armv8-a
TARGET_CPU_ABI := arm64-v8a
TARGET_CPU_VARIANT := cortex-a53
2.2 부팅 및 커널 커맨드라인 매핑
리눅스 커널이 압축을 풀고 최초 부팅될 때 참조할 메모리 베이스 주소와 페이지 크기, 그리고 시리얼 디버깅을 위한 콘솔 로그 인자를 지정합니다.
# 부트로더 빌드를 생략하고 커널/램디스크 결합 이미지 생성 설정
TARGET_NO_BOOTLOADER := true
# 커널 컴파일 아티팩트 이름 지정
BOARD_KERNEL_IMAGE_NAME := Image.gz-dtb
# 커널 인자 전달 (ttyMSM0 포트를 통해 115200 보레이트로 디버그 로그 출력)
BOARD_KERNEL_CMDLINE := console=ttyMSM0,115200n8 androidboot.hardware=qcom
BOARD_KERNEL_BASE := 0x80000000
BOARD_KERNEL_PAGESIZE := 2048
# 파티션 최대 허용 바이트 크기 정의 (32MB)
BOARD_BOOTIMAGE_PARTITION_SIZE := 33554432
BOARD_RECOVERYIMAGE_PARTITION_SIZE := 33554432
3. Device Tree(디바이스 트리) 및 커널 설정
과거 리눅스 커널은 보드가 바뀔 때마다 C 소스코드 내부에 하드웨어 주소값을 하드코딩해서 매번 커널을 새로 빌드해야 했습니다. 이 비효율을 극복하기 위해 하드웨어 레이아웃 정보만 텍스트 파일로 따로 분리한 것이 Device Tree(.dts)입니다.
3.1 디바이스 트리 작성 예시 (example_device.dts)
1GB의 시스템 메모리(RAM) 범위와 Cortex-A53 CPU 코어 하나를 가진 임베디드 보드의 가장 기초적인 DTS 선언 구조입니다.
/dts-v1/;
/ {
model = "Example Qualcomm Board";
compatible = "qcom,example_device";
// 1GB 용량의 RAM 물리 시작 주소(0x80000000) 매핑
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x40000000>; /* 물리 주소 및 크기(1GB) */
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a53";
reg = <0>;
enable-method = "psci";
};
};
};
4. 구글 순정(System)과 칩셋 제조사(Vendor) 파티션 분리 구체화
구글은 안드로이드 8.0 이후부터 가이드라인을 강화하여 순정 OS 레이어와 제조사의 하드웨어 종속 레이어를 칼같이 분리하도록 강제하고 있습니다. 이를 위해 BoardConfig.mk에 각 이미지 크기를 정확히 명시해야 빌드 시스템이 무사히 이미지를 만들어냅니다.
# 1. System 파티션 설정 (구글 순정 프레임워크 및 앱 영역: 2GB 할당)
BOARD_SYSTEMIMAGE_PARTITION_SIZE := 2147483648
BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE := ext4
# 2. Vendor 파티션 설정 (하드웨어 HAL 데몬 및 독점 드라이버 영역: 1GB 할당)
BOARD_USES_VENDORIMAGE := true
BOARD_VENDORIMAGE_PARTITION_SIZE := 1073741824
BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE := ext4
# 3. 벤더 런타임 제약 활성화
PRODUCT_USE_VENDOR := true
컴파일 완료 후 out/target/product/{device}/ 디렉토리 하위에 생성된 system.img와 vendor.img 바이너리 용량이 설정한 범위를 넘지 않는지 상시 체크해야 합니다.
🛠️ 개발을 위한 팁 (Tips)
- 레퍼런스 디바이스 소스 적극 카피: 맨 땅에서 BoardConfig.mk나 Device Tree를 한 줄씩 치는 엔지니어는 없습니다. 내가 타겟팅하는 칩셋 제조사(Qualcomm, MediaTek 등)의 공식 개발자 보드(DB410c 등)나 레퍼런스 기기의 device/ 폴더 소스를 통째로 복사한 뒤, 내 보드에 없는 센서나 주변장치 노드를 덜어내는 방식으로 커스텀을 시작하는 것이 런타임 에러를 피하는 가장 빠른 지름길입니다.
- dtc 도구로 바이너리 역컴파일: 커널 소스 내부에서 여러 .dtsi 파일들이 인클루드(include)되다 보면 최종적으로 어떤 디바이스 트리 구조가 생성되어 커널에 주입되는지 헷갈릴 때가 많습니다. 이때 빌드 아웃풋 폴더에 생성된 바이너리 형태의 디바이스 트리 블롭 파일(.dtb)을 dtc -I dtb -O dts -o human_readable.dts 빌드된파일.dtb 명령어로 풀어보세요. 최종 취합된 주소 맵을 한눈에 확인할 수 있습니다.
⚠️ 흔히 하는 실수 (Common Mistakes)
- BOARD_KERNEL_BASE 주소 미스매치: 보드가 켜질 때 부트로더(LK, U-Boot 등)가 커널 이미지를 램에 복사해 올리는 주소와 BoardConfig.mk에 기재된 BOARD_KERNEL_BASE 물리 주소가 단 1비트라도 어긋나면 단말기는 시리얼 콘솔에 아무런 글자도 찍지 못하고 무한 침묵(Black Screen)에 빠지게 됩니다. 타겟 보드의 데이터시트에 명시된 실질적인 시스템 RAM 시작 주소(System RAM Base)를 반드시 눈으로 대조해 보아야 합니다.
- PARTITION_SIZE 용량 초과 계산 오류: 소스 트리 안에서 서드파티 라이브러리나 폰트, 기본 에셋 리소스를 왕창 추가하다 보면 빌드 끝자락에 System image larger than allocation 에러 메세지를 만나며 빌드가 실패합니다. 무작정 BoardConfig.mk 안의 파티션 사이즈 수치를 키우면 안 되며, 실제 보드의 eMMC/UFS 물리 파티션 테이블(GPT)에 쪼개져 있는 실제 물리 공간 블록 크기와 정확히 연동되도록 바이트 연산을 계산해서 기재해야 플래싱 도중 하드웨어가 뻗는 대참사를 막을 수 있습니다.
5. 마무리 및 다음 단계
안드로이드 디바이스 포팅은 하드웨어 보드에 인공호흡기를 달아 생명을 불어넣는 BSP 엔지니어링의 정수입니다. BoardConfig와 Device Tree 설정이 올바르게 맞물려 리눅스 커널이 무사히 첫 번째 부팅 이정표(init 프로세스 호출)에 도달하기만 해도 포팅의 큰 산을 넘은 것과 다름없습니다.
첫 부팅에 성공하고 나면, 다음 스테이지로 디스플레이, 카메라, 오디오 같은 장치들을 프레임워크와 연결해 주는 HAL(하드웨어 추상화 계층) 포팅 작업을 이어가야 합니다. 보드 포팅을 진행하다가 부트로더 단에서 멈추거나 커널 패닉(Kernel Panic) 로그가 발생해 골머리를 앓고 계신다면, 언제든 로그 서한과 함께 댓글 남겨주세요. 같이 원인을 짚어봅시다!
'Android System & AOSP Engineering > AOSP Framework & Custom Services' 카테고리의 다른 글
| Android 커널 디버깅 가이드: dmesg, procfs부터 ftrace, kgdb 활용법 (0) | 2025.04.22 |
|---|---|
| Android 디버깅 가이드: Logcat, dumpsys, strace, systrace 실무 활용법 (0) | 2025.04.21 |
| AOSP 소스 코드 수정 가이드: SystemUI 커스텀 및 Framework 시스템 서비스 추가 (0) | 2025.04.19 |
| AOSP 빌드 및 플래싱 총정리: 환경 설정부터 우분투 가이드까지 (0) | 2025.04.17 |
| Android System API 사용법과 Hidden API 접근 우회 및 커스텀 가이드 (0) | 2025.04.16 |