본문으로 건너뛰기
Bootloader Internals · 34/37

U-Boot PCIe Enumeration — 부트로더가 디바이스를 찾는 흐름 분석

· Hawk · 8분 읽기

#한 줄 요약

“PCIe enumeration은 부트로더가 깨어 있는 동안 한 번만 하는 가장 비싼 트리 탐색입니다.” — Root Complex가 Bus 0부터 시작해 config space를 깊이우선으로 훑고, 디바이스마다 BAR sizingresource 할당을 끝낸 뒤에야 커널이 완성된 PCIe 토폴로지를 인계받습니다. CXL 디바이스는 이 흐름의 마지막 단계에서 DVSEC 캡슐로 자신을 추가 신원합니다.

Ch 19에서 부트로더가 커널에 인계하는 흐름을 봤습니다. 인계 전에 부트로더가 반드시 끝내야 하는 일 중 하나가 PCIe enumeration입니다. SoC가 root complex를 가졌고 PCIe 디바이스가 슬롯에 꽂혀 있으면, U-Boot은 각 디바이스의 BAR을 sizing하고 메모리 공간을 할당DTB의 ranges 속성에 기록한 뒤 커널에 넘깁니다.

데이터센터·AI 가속기 서버에서는 CXL 디바이스가 이 경로를 타고 인식됩니다. 표준 PCIe enumeration 그 자체는 변하지 않았지만, CXL DVSEC을 인식하는 추가 단계최신 부트로더에 들어왔습니다. 이 장은 U-Boot의 PCIe enumeration을 단계별로 분해하고, CXL이 어디서 끼어드는지를 정리합니다.

#Root Complex 초기화

PCIe enumeration은 Root Complex가 깨어 있는 상태에서 시작됩니다. SoC마다 PCIe controller IP가 다르지만 공통 시퀀스가 있습니다.

U-Boot PCIe RC init 시퀀스:

단계작업
1. PCIe controller power-onPMU에서 power domain 활성화
2. Reference clock 공급100 MHz
3. PHY 초기화PIPE interface, equalization, lane bring-up
4. Link training (LTSSM)Detect → Polling → Configuration. L0 도달 시 link active. 실패 시 PERST# assert 후 재시도
5. Speed negotiationGen1 → Gen2 → Gen3 → Gen4 → Gen5
6. Width negotiationx1·x4·x8·x16
7. Root Complex Config Space 초기화Bus 0, Device 0, Function 0이 RC

*LTSSM(Link Training and Status State Machine)*이 L0 상태에 도달해야 config space access가 가능합니다. 이 단계에서 cable 문제·전원 부족·PHY 설정 오류가 자주 잡힙니다.

#Config Space Scan — 깊이우선 탐색

LTSSM이 L0에 도달하면 Bus 0부터 config space를 훑기 시작합니다.

// U-Boot drivers/pci/pci.c (단순화)
int pci_hose_scan_bus(struct pci_controller *hose, int bus)
{
for (dev = 0; dev < PCI_MAX_DEVICES; dev++) {
for (func = 0; func < PCI_MAX_FUNCTIONS; func++) {
bdf = PCI_BDF(bus, dev, func);
// Vendor ID 읽기 — 0xFFFF면 디바이스 없음
vid = pci_read_config_word(bdf, PCI_VENDOR_ID);
if (vid == 0xFFFF)
continue;
// Header type — 0x01이면 bridge (재귀)
htype = pci_read_config_byte(bdf, PCI_HEADER_TYPE);
if ((htype & 0x7F) == PCI_HEADER_TYPE_BRIDGE) {
// Secondary bus 번호 할당하고 재귀 scan
sec_bus = ++max_bus;
pci_write_config_byte(bdf, PCI_SECONDARY_BUS, sec_bus);
pci_hose_scan_bus(hose, sec_bus);
} else {
// Endpoint — BAR sizing
pci_size_bars(bdf);
}
}
}
}

깊이 우선으로 bridge를 만나면 secondary bus를 할당하고 재귀합니다. endpoint를 만나면 BAR sizing을 끝냅니다. 이 과정이 수십~수백 ms 걸립니다 — 디바이스 수와 link 안정성에 따라.

#BAR Sizing

각 endpoint의 BAR디바이스가 요청하는 메모리 크기를 알려 줍니다.

// BAR sizing 절차
uint32_t orig = pci_read_config_dword(bdf, PCI_BAR0);
pci_write_config_dword(bdf, PCI_BAR0, 0xFFFFFFFF);
uint32_t mask = pci_read_config_dword(bdf, PCI_BAR0);
pci_write_config_dword(bdf, PCI_BAR0, orig);
// 크기 계산
uint32_t size = (~(mask & 0xFFFFFFF0)) + 1;
// 예: mask=0xFFF00000 → size=0x100000 (1 MB)

BAR에 모두 1을 쓰고 다시 읽으면 하위 R/W bit는 0으로 굳고 상위는 1로 남는 형태가 됩니다. 이 반전된 마스크BAR이 차지하는 영역 크기입니다.

64-bit BAR이면 BAR0과 BAR1을 합쳐 sizing합니다. CXL 디바이스의 HDM Decoder는 64-bit BAR로 노출되어 512 GB~2 TB DRAM을 매핑합니다.

#Resource 할당

모든 endpoint의 BAR 크기가 수집되면, RC가 가진 memory window분할해 할당합니다.

[U-Boot pci_setup_resources 흐름]
RC memory window: 0x40000000 ~ 0x4FFFFFFF (256 MB)
Endpoint 1: 64 MB 요구 → 0x40000000 ~ 0x43FFFFFF
Endpoint 2: 1 MB 요구 → 0x44000000 ~ 0x440FFFFF
Endpoint 3: 4 GB 요구 → ✗ window 부족! → enumeration 실패

4 GB·512 GB 같은 큰 BARRC의 prefetchable window그만큼 커야 합니다. CXL 메모리 디바이스는 보통 64-bit prefetchable BAR로 매핑되며, DTB의 ranges 속성충분히 큰 window를 정의해 줘야 합니다.

arch/arm64/boot/dts/example-board.dts
pcie@30000000 {
compatible = "vendor,pcie-rc";
reg = <0x0 0x30000000 0x0 0x10000>;
ranges = <
0x82000000 0x0 0x40000000 0x0 0x40000000 0x0 0x10000000 // 32-bit MMIO 256 MB
0xc3000000 0x0 0x80000000 0x80 0x0 0x80 0x0 // 64-bit prefetch 512 GB
>;
};

마지막 항목이 *0x80_00000000 ~ 0x100_00000000 (512 GB)*의 prefetchable window를 정의합니다. CXL.mem 디바이스가 256 GB를 요청해도 이 안에 들어갑니다.

#CXL DVSEC 인식

Modern U-Boot (2024+)은 PCIe enumeration 끝에 DVSEC scan을 추가합니다. CXL 디바이스는 *Vendor=0x1e98(CXL Consortium)*의 DVSEC을 가집니다.

// U-Boot drivers/pci/pci_cxl.c (개념적)
int pci_scan_cxl_dvsec(pci_dev_t bdf)
{
int cap_offset = pci_find_ext_capability(bdf, PCI_EXT_CAP_ID_DVSEC);
while (cap_offset) {
uint16_t vid = pci_read_config_word(bdf, cap_offset + 4);
uint16_t dvsec_id = pci_read_config_word(bdf, cap_offset + 8);
if (vid == PCI_VENDOR_ID_CXL_CONSORTIUM) {
switch (dvsec_id) {
case CXL_DVSEC_DEVICE:
printf("CXL device found at %02x:%02x.%x\n",
PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf));
add_cedt_entry(bdf);
break;
case CXL_DVSEC_PORT:
register_cxl_port(bdf);
break;
}
}
cap_offset = pci_find_next_ext_capability(bdf, cap_offset, PCI_EXT_CAP_ID_DVSEC);
}
}

CXL DVSEC을 발견하면 부트로더가 ACPI CEDT(CXL Early Discovery Table)에 항목을 추가커널에 인계합니다. 커널은 CEDT를 보고 cxl_acpi 드라이버를 활성화합니다.

Modern Embedded Recipes Ch 151에서 cxl_acpi → cxl_pci → cxl_mem 의존성 체인을 볼 수 있습니다.

#DTB로 결과 인계

U-Boot은 enumeration 결과를 DTB의 ranges와 device node로 인계합니다.

// 부트로더가 update한 후의 DTB (예시)
pcie@30000000 {
ranges = <...>; // RC window 그대로
bus-range = <0x0 0x20>; // U-Boot이 찾은 max bus
cxl-mem@5e000000 { // U-Boot이 추가한 노드
compatible = "cxl,memory-device";
reg = <0x80 0x00000000 0x40 0x00000000>; // 256 GB
cxl,dvsec-rev = <1>;
};
};

수동 DTB에는 PCIe 디바이스 노드를 미리 정의 안 함이 권장됩니다 — enumeration 결과로 부트로더가 동적 추가하는 게 권장 흐름입니다.

#임베디드에서 PCIe·CXL을 만나는 자리

임베디드 SoC는 보통 root complex 없이 endpoint로 동작하는 경우가 많습니다. 그러나 AI Edge·Industrial Gateway·NVR 같은 상위 SoC들이 PCIe 5.0 RC를 갖추면서 CXL device attach가 가능해졌습니다.

임베디드 시나리오RC 또는 EP부트로더 enumeration
MCU + Sensor없음N/A
AI Edge box (Jetson Orin 등)RCNVMe·CXL.mem 인식
NVR·NASRCNVMe storage 인식
5G RU·DURCaccelerator (FPGA/NPU) 인식
Automotive ADASRC + EPinter-ECU PCIe·CXL fabric

부트로더의 PCIe enumeration은 commercial 서버만의 일이 아닙니다.

#자주 하는 실수

[U-Boot 로그]
pcie@30000000: link not up after 100ms, giving up
pcie@30000000: no devices found

이 메시지는 LTSSM이 L0에 도달 못 한 것입니다. enumeration이 실패한 게 아니라 link 자체가 안 올라온 상태입니다. PHY 설정·전원·신호 무결성을 먼저 점검해야지 enumeration 코드를 뒤져선 안 됩니다.

#Prefetchable Window가 작아 큰 BAR 매핑 실패

pcie@30000000: out of mem space for device 02:00.0 (req 0x40000000 bytes)

1 GB 이상 BAR을 요청하는 디바이스(GPU·CXL.mem)가 작은 prefetchable window에 안 들어가는 경우입니다. DTB의 ranges충분히 큰 window로 정의해야 합니다. 최소 64 GB 권장 — CXL 디바이스를 고려하면 512 GB 이상도 흔합니다.

#BAR Sizing 후 원래 값 복원 안 함

// 잘못된 sizing
pci_write_config_dword(bdf, PCI_BAR0, 0xFFFFFFFF);
mask = pci_read_config_dword(bdf, PCI_BAR0);
// orig 복원 안 함! → 디바이스가 비정상 BAR 값으로 동작

BAR에 0xFFFFFFFF를 쓰면 그 사이 디바이스가 잘못된 주소를 본다고 생각할 수 있습니다. 반드시 원래 값을 복원한 뒤 resource 할당 단계에서 새 주소를 다시 씁니다.

#CXL DVSEC을 표준 PCIe Capability로 처리

// 잘못 — DVSEC은 Extended Capability지 표준 Cap 아님
int cap = pci_find_capability(bdf, PCI_CAP_ID_DVSEC); // 항상 0 리턴
// 올바름 — Extended Capability (offset 0x100+)에서 찾아야 함
int cap = pci_find_ext_capability(bdf, PCI_EXT_CAP_ID_DVSEC);

DVSEC은 PCIe 4 KB extended config space에 있습니다. 256 byte 표준 config space에서는 찾을 수 없습니다.

#Hot-plug Device를 모름 enumeration

[부팅 후 hot-plug]
pcie@30000000: hot-plug event ignored

U-Boot의 enumeration부팅 시 한 번만입니다. 부팅 후 슬롯에 끼운 디바이스커널의 PCIe hot-plug 메커니즘이 담당합니다. 부트로더에서 처리 시도 자체가 책임 위반입니다.

#정리

  • U-Boot PCIe enumeration은 Root Complex가 LTSSM L0에 도달한 뒤 Bus 0부터 깊이우선으로 진행됩니다.
  • 각 endpoint의 BAR sizing0xFFFFFFFF write/read 반전 마스크요청 크기를 알아냅니다.
  • Resource 할당RC가 가진 memory window를 분할해 endpoint마다 실 주소를 부여합니다.
  • 큰 BAR(GPU·CXL.mem)을 위해 DTB의 prefetchable window를 충분히 크게 정의해야 합니다.
  • CXL 디바이스는 DVSEC을 가집니다. Modern U-Boot은 enumeration 끝에 DVSEC scanACPI CEDT에 항목을 추가합니다.
  • enumeration 결과는 DTB의 ranges와 동적 노드커널에 인계되며, 커널은 cxl_acpi 드라이버로 CXL을 인식합니다.
  • 임베디드 SoC도 Jetson Orin·NVR·5G DU 같은 상위 시스템PCIe 5.0 RC + CXL device attach현실입니다.

#다음 편

Ch 35: EFI·UEFI에서 CXL 초기화에서는 EDK II 기반 BIOS·UEFICEDT(CXL Early Discovery Table)를 생성하고 HDM Decoder를 사전 설정커널이 깨어났을 때 즉시 메모리를 인식하게 하는 흐름을 분해합니다.

#관련 항목

Bootloader Internals · 34 of 37

  1. 1ROM부터 init까지 — 임베디드 부팅 단계의 빈자리 분석
  2. 2Das U-Boot vs TF-A vs EDK II — 임베디드 부트로더 생태계 비교
  3. 3U-Boot 빌드 시스템 분석 — Kconfig·Makefile·defconfig 동작 추적
  4. 4ARM 임베디드 부트 4단계 분해 — BL1·SPL·TPL·U-Boot Proper의 역할
  5. 5U-Boot Falcon Mode — SPL이 U-Boot Proper 없이 커널 직접 부팅
  6. 6Device Tree DTB 부트로더 처리 — 로딩 시점과 fixup 메커니즘 추적
  7. 7U-Boot Driver Model 내부 — uclass·driver·device 추상화 구조
  8. 8U-Boot 보드 초기화 시퀀스 — board_init_f와 board_init_r 분리 이유
  9. 9DDR Controller 프로그래밍과 PHY Training — SPL의 가장 어려운 작업
  10. 10임베디드 스토리지 부팅 분석 — MMC·SCSI·NAND·SPI Flash 비교
  11. 11임베디드 네트워크 부팅 — TFTP·PXE·BOOTP 시퀀스 분석
  12. 12U-Boot USB 부팅 — fastboot·UMS·USB host 메커니즘
  13. 13U-Boot 환경 변수와 bootcmd — 부팅 시나리오 정의하기
  14. 14Modern U-Boot bootflow / bootmeth — 새 추상화 레이어 분석
  15. 15FIT image 구조 분석 — multi-image·hash·configuration 추적
  16. 16U-Boot Verified Boot — RSA 서명과 public key 임베딩 흐름
  17. 17임베디드 A/B 부팅 이중화 — OTA 안전성을 위한 부트 슬롯 설계
  18. 18U-Boot의 EFI 호환 분석 — bootefi 명령과 EFI loader 동작 원리
  19. 19Linux Boot ABI — ARM/ARM64 커널 진입 규약 추적
  20. 20임베디드 펌웨어 업데이트 — RAUC vs SWUpdate 비교
  21. 21새 보드 U-Boot 포팅 실전 — defconfig 작성부터 첫 부팅까지
  22. 22부트로더 디버깅 기법 — DEBUG·JTAG·serial·post-mortem 분석
  23. 23SoC BootROM·eFuse·OTP — 부팅의 0단계 분석
  24. 24SPL·TPL 내부 해부 — 가장 작은 부트 단계의 동작 추적
  25. 25ARM Trusted Firmware-A 통합 — BL1·BL2·BL31·BL32·BL33 흐름
  26. 26DDR Training과 PHY Calibration — 보드별 파라미터 튜닝
  27. 27임베디드 Chain of Trust — 다단계 서명 검증의 전체 흐름
  28. 28임베디드 Flash Layout 설계 — partition·NAND·eMMC·UBI 비교
  29. 29U-Boot Distro Boot — extlinux·boot.scr 표준화 분석
  30. 30부트로더 CI 구축 — build matrix와 자동 부팅 테스트
  31. 31TF-A BL31 EL3 Runtime 분석 — PSCI·SDEI·RAS dispatcher 추적
  32. 32PSCI와 SMCCC ABI — ARM 표준 SMC 호출 규약 분석
  33. 33ARM64 Secondary Core Bring-up — PSCI CPU_ON 호출부터 EL1 진입까지
  34. 34U-Boot PCIe Enumeration — 부트로더가 디바이스를 찾는 흐름 분석
  35. 35EFI·UEFI에서 CXL 초기화 — CEDT 생성과 HDM Decoder 사전 설정
  36. 36부트 시 메모리 토폴로지 결정 — DDR + CXL.mem 통합 인식
  37. 37UEFI Secure Boot 인증서 만료 — 2011→2023 CA 롤오버와 PQC 대비