← Hardware

Hardware 02 · PCIe · Linux v6.18.37

Synopsys DWC PCIe: iATU, Enumeration, DMA와 MSI

DWC link-up 이후 DBI와 iATU가 CPU·PCIe 주소를 연결하고 Linux가 BDF와 BAR를 배치하며 bus mastering, DMA mapping과 MSI/MSI-X를 활성화하는 순서를 추적합니다.

검증 깊이
board → Linux 8 layers
그림
7 boundary diagrams
원본 코드
13 excerpts · 945 lines
기준
Linux v6.18.37

01 · QUESTION

무엇을 증명할 것인가

장치가 `lspci`에는 보이는데 driver의 MMIO, DMA 또는 interrupt만 실패할 때 어느 resource ownership 단계에서 멈춘 것인가?

DWC link-up 이후 DBI와 iATU가 CPU·PCIe 주소를 연결하고 Linux가 BDF와 BAR를 배치하며 bus mastering, DMA mapping과 MSI/MSI-X를 활성화하는 순서를 추적합니다. 이 글은 Linux 로그를 출발점으로 삼지 않습니다. 같은 사건이 보드, controller, firmware, kernel에서 어떤 이름과 번호로 바뀌는지 아래에서 위로 연결합니다.

02 · CONTRACT

입력과 완료 조건

구분확인할 내용
입력L0 link, 정상 configuration access, firmware host windows와 interrupt/IOMMU description
출력driver가 BAR를 매핑하고 DMA queue와 MSI vector를 사용해 transaction을 완료하는 상태
소유권PCI core는 bus/resource, driver는 device register와 queue, DMA/IOMMU core는 IOVA lifetime, IRQ core는 vector lifetime을 소유
완료 조건probe 성공뿐 아니라 MMIO readback, DMA completion, interrupt counter와 teardown까지 검증

03 · BOTTOM-UP

가장 아래 계층부터 확인하는 8단계

그림 1. 아래에서 Linux까지 올라오는 8개 검증 경계위 계층의 로그보다 아래 계층의 물리 증거를 먼저 확정합니다.
01
0. Discover · BDF와 multifunction/bridge 탐색

PCI core는 bus 0의 device/function을 읽고 Header Type과 class code로 endpoint와 bridge를 구분한다. bridge의 Primary/Secondary/Subordinate Bus Number가 뒤쪽 hierarchy를 연다. ARI가 켜진 경우 function numbering과 forwarding 규칙이 달라진다.

증거: `lspci -tv`, config header, bridge bus-number register

START
02
1. Size · BAR sizing과 prefetchable 속성

BAR에 all-ones를 쓰고 readback mask를 해석해 required size와 alignment를 얻는다. 64-bit BAR는 두 DWORD가 한 resource이며 prefetchable은 CPU cacheable이라는 뜻이 아니라 read side effect 없이 bridge가 prefetch할 수 있다는 PCI 속성이다.

증거: raw BAR, resource flags, size와 alignment, bridge window requirement

CHECKPOINT
03
2. Assign · host aperture와 bridge window 배치

Linux는 firmware가 준 주소를 보존하거나 unassigned resource를 host `ranges/_CRS` 안에 배치한다. bridge I/O, non-prefetchable memory, prefetchable memory window가 child BAR 전체를 포함해야 한다. 32-bit endpoint 제약과 64-bit host aperture를 혼동하면 allocation이 실패한다.

증거: `/proc/iomem`, sysfs resource, boot log의 BAR assignment, bridge window

CHECKPOINT
04
3. Enable · Command register와 MMIO mapping

`pci_enable_device()`가 필요한 bridge/device state를 활성화하고 driver가 region을 request한 뒤 `pci_iomap()`으로 BAR를 kernel virtual address에 연결한다. Memory Space Enable과 Bus Master Enable은 다르며 DMA 전에 `pci_set_master()`가 필요하다.

증거: PCI_COMMAND, `/proc/iomem`, requested region owner, mapped BAR virtual/physical address

CHECKPOINT
05
4. DMA · DMA mask, IOVA와 descriptor publication

driver가 DMA mask를 합의한 뒤 coherent ring 또는 streaming buffer를 map한다. IOMMU가 켜지면 device가 보는 DMA address는 IOVA이고 physical address와 같지 않다. descriptor 내용과 ownership bit를 쓴 뒤 `dma_wmb()`를 거쳐 doorbell을 울려야 한다.

증거: DMA mask, IOVA/PA mapping, IOMMU group/domain, descriptor dump, fault log

CHECKPOINT
06
5. MSI · MSI/MSI-X vector와 message routing

MSI는 device가 특정 address/data로 Memory Write TLP를 보내 interrupt를 만든다. MSI-X는 vector별 table entry와 mask를 가진다. GIC ITS, x86 interrupt remapping, RISC-V IMSIC가 message를 target CPU interrupt로 변환할 수 있다.

증거: MSI capability/table, allocated vector, `/proc/interrupts`, irq affinity, remapper fault

CHECKPOINT
07
6. Driver · probe, queue start와 error unwind

probe는 enable, region request, DMA setup, IRQ allocation, hardware reset, queue start, net/block/core registration 순으로 진행한다. 중간에 실패하면 역순으로 해제해야 한다. probe 성공 log만으로 runtime datapath가 검증된 것은 아니다.

증거: dynamic debug, probe 단계별 trace, devres 상태와 remove callback

CHECKPOINT
08
7. Runtime · MMIO doorbell → DMA → MSI completion

CPU가 descriptor를 채우고 doorbell MMIO를 쓰면 endpoint가 PCIe Memory Read로 descriptor/data를 가져간다. 완료 후 endpoint가 host memory status를 쓰고 MSI를 보낸다. interrupt handler는 completion ring을 회수해 upper layer에 완료를 게시한다.

증거: MMIO write trace, PCIe TLP, DMA/IOMMU fault, interrupt timestamp, completion index

LINUX / RESULT
01

0. Discover · BDF와 multifunction/bridge 탐색

PCI core는 bus 0의 device/function을 읽고 Header Type과 class code로 endpoint와 bridge를 구분한다. bridge의 Primary/Secondary/Subordinate Bus Number가 뒤쪽 hierarchy를 연다. ARI가 켜진 경우 function numbering과 forwarding 규칙이 달라진다.

남길 증거: `lspci -tv`, config header, bridge bus-number register

bridge 아래 BDF가 안 보이면 endpoint driver보다 bus-number와 Type-1 routing을 먼저 본다.

02

1. Size · BAR sizing과 prefetchable 속성

BAR에 all-ones를 쓰고 readback mask를 해석해 required size와 alignment를 얻는다. 64-bit BAR는 두 DWORD가 한 resource이며 prefetchable은 CPU cacheable이라는 뜻이 아니라 read side effect 없이 bridge가 prefetch할 수 있다는 PCI 속성이다.

남길 증거: raw BAR, resource flags, size와 alignment, bridge window requirement

device decode를 끈 상태에서 sizing하고 원래 값을 복원해야 한다.

03

2. Assign · host aperture와 bridge window 배치

Linux는 firmware가 준 주소를 보존하거나 unassigned resource를 host `ranges/_CRS` 안에 배치한다. bridge I/O, non-prefetchable memory, prefetchable memory window가 child BAR 전체를 포함해야 한다. 32-bit endpoint 제약과 64-bit host aperture를 혼동하면 allocation이 실패한다.

남길 증거: `/proc/iomem`, sysfs resource, boot log의 BAR assignment, bridge window

resource collision이나 no space가 있으면 driver probe에서 BAR를 만지기 전에 해결한다.

04

3. Enable · Command register와 MMIO mapping

`pci_enable_device()`가 필요한 bridge/device state를 활성화하고 driver가 region을 request한 뒤 `pci_iomap()`으로 BAR를 kernel virtual address에 연결한다. Memory Space Enable과 Bus Master Enable은 다르며 DMA 전에 `pci_set_master()`가 필요하다.

남길 증거: PCI_COMMAND, `/proc/iomem`, requested region owner, mapped BAR virtual/physical address

MMIO read가 all-ones이면 power state, BAR decode, bridge window와 endpoint reset을 재확인한다.

05

4. DMA · DMA mask, IOVA와 descriptor publication

driver가 DMA mask를 합의한 뒤 coherent ring 또는 streaming buffer를 map한다. IOMMU가 켜지면 device가 보는 DMA address는 IOVA이고 physical address와 같지 않다. descriptor 내용과 ownership bit를 쓴 뒤 `dma_wmb()`를 거쳐 doorbell을 울려야 한다.

남길 증거: DMA mask, IOVA/PA mapping, IOMMU group/domain, descriptor dump, fault log

completion 전에 unmap/free하면 device가 재사용 memory를 덮을 수 있다.

06

5. MSI · MSI/MSI-X vector와 message routing

MSI는 device가 특정 address/data로 Memory Write TLP를 보내 interrupt를 만든다. MSI-X는 vector별 table entry와 mask를 가진다. GIC ITS, x86 interrupt remapping, RISC-V IMSIC가 message를 target CPU interrupt로 변환할 수 있다.

남길 증거: MSI capability/table, allocated vector, `/proc/interrupts`, irq affinity, remapper fault

interrupt vector를 해제하기 전에 device MSI generation과 DMA queue를 먼저 멈춘다.

07

6. Driver · probe, queue start와 error unwind

probe는 enable, region request, DMA setup, IRQ allocation, hardware reset, queue start, net/block/core registration 순으로 진행한다. 중간에 실패하면 역순으로 해제해야 한다. probe 성공 log만으로 runtime datapath가 검증된 것은 아니다.

남길 증거: dynamic debug, probe 단계별 trace, devres 상태와 remove callback

각 resource의 획득과 실패 label을 표로 만들어 leak와 double-free를 확인한다.

08

7. Runtime · MMIO doorbell → DMA → MSI completion

CPU가 descriptor를 채우고 doorbell MMIO를 쓰면 endpoint가 PCIe Memory Read로 descriptor/data를 가져간다. 완료 후 endpoint가 host memory status를 쓰고 MSI를 보낸다. interrupt handler는 completion ring을 회수해 upper layer에 완료를 게시한다.

남길 증거: MMIO write trace, PCIe TLP, DMA/IOMMU fault, interrupt timestamp, completion index

한 transaction ID를 descriptor index, DMA address, interrupt vector와 끝까지 연결한다.

BAR 점유와 MMIO 접근의 경계

PCI 코어가 BAR 주소를 배정했다고 드라이버가 그 범위를 곧바로 점유한 것은 아닙니다. probe에서 필요한 BAR의 유형(IORESOURCE_MEM 또는 IORESOURCE_IO), 길이와 사용할 레지스터 오프셋을 확인한 뒤 pci_request_region()으로 특정 BAR를, 또는 pci_request_regions()으로 장치의 PCI I/O·메모리 영역을 예약하고 실패를 처리합니다. 제공되는 시스템에서는 메모리 영역을 /proc/iomem, 포트 I/O 영역을 /proc/ioports에서 교차 확인할 수 있습니다. 이 목록만으로 장치 레지스터가 정상 응답한다고 판단하지는 않습니다.

pci_iomap()이 돌려주는 __iomem 값은 일반 RAM 포인터가 아닌 I/O 접근용 토큰입니다. 메모리 BAR의 레지스터는 장치가 정한 폭·정렬·바이트 순서·부작용을 확인하고 접근합니다. 예를 들어 little-endian 32비트 레지스터에는 readl()/writel()을 사용할 수 있고, I/O port BAR를 매핑한 토큰에는 포트 I/O도 지원하는 ioread32()/iowrite32() 계열을 선택합니다. 매핑 실패를 확인하고, 직접 획득한 자원은 probe 실패나 remove 때 장치 사용과 동시 접근을 멈춘 뒤 pci_iounmap() 및 대응하는 영역 해제로 정리합니다. BAR 배치, 주소 공간, 종료 순서를 함께 대조합니다.

writel() 호출이 끝났다고 해서 PCIe Memory Write가 장치에 도착한 것은 아닙니다. 쓰기 도착을 보장해야 하는 지점에서는 같은 장치에서 읽어도 안전한 레지스터를 읽어 posted write를 비우는 방법을 검토합니다. 읽으면 상태가 지워지는 레지스터를 임의로 선택하지 않으며, reset 중처럼 MMIO 읽기가 실패할 수 있으면 적절한 configuration-space 읽기를 검토합니다. 이 readback만으로 장치 명령 수행 완료나 DMA 버퍼 소유권 전환까지 확인할 수는 없습니다. descriptor 게시 순서와 완료 확인은 해당 장치·DMA API의 별도 계약입니다.

기술 기준: Linux 6.18 PCI 자원 예약, I/O accessor, posted write와 readback, /proc 자원 목록. 연재 참고: Avinash Pal, PCIe Internals 6편·7편.

04 · DIAGRAMS

주소와 상태가 이동하는 모습

그림 2. PCIe 실행 경로왼쪽부터 신호나 데이터를 처리하는 순서를 나타냅니다. 각 단계에서 확인할 항목을 함께 표시했습니다.
01 Cfg scan
02 BAR sizing
03 Window assignment
04 Device enable
05 BAR iomap
06 Driver probe
그림 3. firmware와 Linux 번호 공간의 연결왼쪽부터 신호나 데이터를 처리하는 순서를 나타냅니다. 각 단계에서 확인할 항목을 함께 표시했습니다.
01 Descriptor write
02 dma_wmb
03 Doorbell
04 PCIe DMA
05 Completion write
06 MSI
07 IRQ handler
그림 4. 입력, 소유권, 완료 조건의 경계완료 조건을 충족하기 전에는 다음 계층이 해당 자원을 재사용하면 안 됩니다.
INPUT

L0 link, 정상 configuration access, firmware host windows와 interrupt/IOMMU description

OWNER

PCI core는 bus/resource, driver는 device register와 queue, DMA/IOMMU core는 IOVA lifetime, IRQ core는 vector lifetime을 소유

OUTPUT

driver가 BAR를 매핑하고 DMA queue와 MSI vector를 사용해 transaction을 완료하는 상태

DONE

probe 성공뿐 아니라 MMIO readback, DMA completion, interrupt counter와 teardown까지 검증

05 · SYNOPSYS DESIGNWARE IP

Synopsys DWC Controller programming: DBI, iATU, AXI와 MSI

link가 L0에 들어온 뒤에는 PHY보다 DWC digital controller의 주소 변환과 Root Port programming이 중심이 됩니다. CPU가 보는 DBI/AXI 주소, PCIe bus address, endpoint BAR address, DMA IOVA를 서로 다른 번호 공간으로 두고 DWC iATU region이 어느 두 공간을 연결하는지 계산합니다.

그림 5. Synopsys DWC PCIe subsystem의 실제 분할SoC glue, digital controller, PIPE, PHY를 서로 다른 register bank로 봅니다.
01

DBI / DBI2

RC Type-1 configuration, Port Logic와 일부 EP shadow/BAR programming에 접근합니다.

관찰
dbi_base와 dbi_base2, RO write enable 구간
대표 실패
DBI와 application register base를 바꿔 읽어 전혀 다른 값을 정상으로 오판
02

Config aperture

PCI core의 BDF access를 DWC config-space window로 받습니다.

관찰
child bus 여부에 따라 CFG0 또는 CFG1
대표 실패
bus 1 endpoint에 CFG1을 쓰거나 bridge 뒤 장치에 CFG0을 써 Completion이 없음
03

Outbound iATU

CPU AXI base/limit를 PCIe target address와 TLP type으로 변환합니다.

관찰
MEM/IO/CFG0/CFG1/MSG region
대표 실패
base·limit inclusive 계산 또는 upper address 오류로 일부 BAR만 접근 실패
04

Inbound iATU

Endpoint가 보낸 Memory Read/Write를 SoC physical aperture로 변환합니다.

관찰
BAR match 또는 address match, target base
대표 실패
DMA address와 inbound target/IOMMU 구성이 맞지 않아 AXI abort
05

AXI master/slave

CPU MMIO/config 요청과 inbound DMA request를 outstanding transaction으로 운반합니다.

관찰
AXI ID/width/burst/ordering과 error response
대표 실패
PCIe Completion Timeout과 SoC interconnect timeout을 같은 오류로 취급
06

Root Port setup

class code, BAR, bus number, command, lane width와 speed capability를 RC configuration에 맞춥니다.

관찰
dw_pcie_setup_rc()와 host_ops
대표 실패
DBI RO-write override를 닫지 않아 runtime write protection이 깨짐
07

MSI controller

DWC 내부 MSI address, enable/mask/status를 irqdomain으로 연결하거나 platform 외부 MSI domain을 사용합니다.

관찰
PCIE_MSI_ADDR/ENABLE/MASK/STATUS
대표 실패
내부 MSI와 GIC ITS 경로를 동시에 구성하거나 둘 다 구성하지 않음
08

Optional eDMA

일부 DWC configuration은 embedded DMA read/write channel과 linked-list engine을 포함합니다.

관찰
channel control, doorbell, LLP, interrupt status
대표 실패
일반 endpoint bus-master DMA와 DWC eDMA를 같은 엔진으로 오해

PHY와 Controller가 만나는 PIPE 경계

한쪽 register만 보고 link 상태를 판정하지 않습니다. 동일 사건에 대해 PHY evidence와 controller evidence를 같은 행에 놓고 처음 불일치하는 구간을 찾습니다.

단계PHY 쪽에서 확인DWC Controller 쪽에서 확인경계 판정
Configuration readPHY: L0와 DLL active를 유지Controller: CFG0/CFG1 iATU와 BDF target 생성0xffff이면 LNKSTA와 iATU region을 같은 순간에 저장
BAR MMIOPHY: TLP 전송 품질과 replay를 보장Controller: outbound MEM base/limit/target 변환CPU physical, PCI bus address, BAR assigned base를 세 열로 계산
Endpoint DMA readPHY: Memory Read TLP와 Completion 전달Controller/SoC: inbound routing 또는 IOMMU requester contextIOMMU fault의 requester ID와 BDF를 연결
Endpoint DMA writePHY: posted Memory Write 전달Controller/SoC: inbound target과 cache coherencyposted이므로 CPU memory 관찰과 device completion을 따로 확인
MSIPHY: MSI Memory Write TLP 전달Controller: internal MSI status 또는 external MSI domain routeDMA completion write가 보인 뒤 MSI가 오는 순서를 timestamp로 검증
TeardownPHY: 새 TLP 차단 및 link/power transitionController: bus master off, IRQ mask, ATU/queue 해제device가 더 이상 DMA하지 않는 증거 뒤 mapping을 반환

DWC 공개 register와 읽는 목적

Register/영역역할검증 시 주의점
PCIE_MISC_CONTROL_1_OFF 0x8bcDBI read-only write enableRC setup 중에만 PCIE_DBI_RO_WR_EN을 set하고 반드시 clear
PCIE_ATU_VIEWPORT 0x900v4.80a 이전 region selectordirection과 index를 선택한 뒤 공용 register window programming
PCIE_ATU_UNROLL_BASEv4.80a 이후 direct region baseoutbound/inbound와 region index가 address에 포함됨
REGION_CTRL1TLP type MEM/IO/CFG0/CFG1/MSG주소만 맞아도 type이 틀리면 상대편 routing이 다름
REGION_CTRL2enable, BAR mode, config shift, function matchenable readback과 LINK_WAIT_IATU retry를 확인
LOWER/UPPER_BASE + LIMITCPU 측 source aperturelimit가 inclusive이며 4GiB 경계를 넘는 region은 upper limit 지원 확인
LOWER/UPPER_TARGETPCIe 측 target address 또는 BDFCFG region은 bus/device/function field encoding 확인
MSI_ADDR/ENABLE/MASK/STATUSDWC internal MSI blockirqdomain vector와 status bit group을 맞춤
그림 6. DWC와 PHY 초기화 순서왼쪽부터 신호나 데이터를 처리하는 순서를 나타냅니다. 각 단계에서 확인할 항목을 함께 표시했습니다.
01 dw_pcie_host_init
02 platform host_init
03 dw_pcie_setup_rc
04 DBI RC programming
05 iATU config/MEM windows
06 start_link
07 wait_for_link
08 pci_host_probe
그림 7. 주소 또는 상태가 DWC를 통과하는 경로왼쪽부터 신호나 데이터를 처리하는 순서를 나타냅니다. 각 단계에서 확인할 항목을 함께 표시했습니다.
01 CPU PA
02 AXI request
03 outbound iATU base/limit match
04 target+TLP type
05 PCIe BAR/BDF
06 Completion or posted finish

CFG0와 CFG1의 차이

Root Port 바로 아래 bus의 endpoint에는 Type 0 Configuration Request가 필요하고, bridge 너머에는 Type 1 routing이 필요합니다. DWC host driver는 bus topology에 따라 iATU TLP type과 target BDF를 바꾸므로 bus number가 잘못되면 PHY가 완전히 정상이어도 enumeration이 끊깁니다.

Viewport와 unrolled iATU

DWC v4.80a 이전에는 PCIE_ATU_VIEWPORT로 region과 방향을 고른 뒤 하나의 register 창을 재사용합니다. 이후 IP는 region마다 직접 접근 가능한 unrolled 공간을 둘 수 있습니다. Linux는 version/capability로 방식을 결정하므로 bring-up dump도 같은 방식을 따라야 합니다.

DMA 주소는 iATU 하나로 끝나지 않는다

Endpoint가 보는 PCIe address가 SoC interconnect address로 변환된 뒤 IOMMU가 다시 IOVA를 physical page로 변환할 수 있습니다. DWC inbound iATU, SMMU stream ID, DMA API가 각각 한 번씩 주소 공간을 바꿀 수 있으므로 descriptor에 적힌 값만 보고 PA라고 부르면 안 됩니다.

DWC eDMA와 endpoint DMA 구분

DWC eDMA는 controller IP에 선택적으로 합성되는 별도 엔진입니다. NVMe나 Ethernet endpoint가 자체 DMA engine으로 bus master transaction을 만드는 것과 다릅니다. Linux config와 register presence를 확인한 뒤 eDMA channel register를 해석합니다.

06 · DETAILED MANUAL

DBI, ECAM, iATU, BAR, DMA와 MSI를 주소 단위로 추적하는 상세 매뉴얼

이 장은 link가 L0에 들어온 이후를 다룹니다. 같은 숫자를 CPU physical address, PCI bus address, endpoint BAR offset, DMA address와 IOVA로 번갈아 부르는 실수를 막기 위해 모든 transaction을 '누가 생성했고 어느 translation을 통과했는가'로 기록합니다. 예시 주소는 계산 방법을 보여 주기 위한 가상 값이며 실제 board의 DT ranges, iommu-map과 DWC region capability로 다시 계산해야 합니다.

공개적으로 다시 확인할 수 있는 자료

공개되지 않은 제조사 레지스터나 제품별 오프셋은 추측하지 않았습니다. 아래의 제조사 자료, 아키텍처 문서, Linux v6.18.37 원본으로 확인할 수 있는 내용만 다뤘습니다.

07 · REGISTERS

읽어야 할 상태와 해석

같은 동작에 대한 활성화 상태, 처리 상태, 자원 소유권, 완료 레지스터 값을 함께 기록합니다. 읽으면 지워지는 비트나 1을 써야 지워지는 비트가 있는지는 먼저 데이터시트에서 확인합니다.

레지스터/상태소유 블록검증할 의미
BAR0..5endpoint aperturesize/type/prefetch와 assigned base
PCI_COMMANDdecode controlMEM/IO/BUS_MASTER bit를 단계별로 확인
Bridge windowsType-1 headerchild BAR 전체를 포함하는지 확인
MSI-X Table/PBABAR 내부vector control mask와 pending bit
IOMMU contextplatform specificrequester ID가 올바른 domain에 연결됐는지 확인
AER statusRoot/EndpointUnsupported Request, Completion Timeout, Poisoned TLP

08 · LINUX SOURCE

Linux v6.18.37 원본 코드와 줄별 해설

라인 번호를 고정해 외우는 대신 함수 선언을 기준으로 Linux v6.18.37 tree에서 다시 찾았습니다. 로컬 tree에 있는 파일은 실제 코드를 직접 싣고, 나머지는 원본 파일과 읽을 함수 좌표를 연결했습니다.

Linux v6.18.37 · 실제 원본 코드

DWC outbound iATU region programming과 enable 확인

drivers/pci/controller/dwc/pcie-designware.c:517-613

517	 *                "TLP Digest Rules", when an endpoint receives TLP
518	 *                Digest when its ECRC check functionality is disabled
519	 *                in AER registers, received TLP Digest is just ignored.
520	 * Since there is no issue or error reported either side, best way to
521	 * handle the scenario is to program TD bit by default.
522	 */
523 
524	return val | PCIE_ATU_TD;
525}
526 
527int dw_pcie_prog_outbound_atu(struct dw_pcie *pci,
528			      const struct dw_pcie_ob_atu_cfg *atu)
529{
530	u64 parent_bus_addr = atu->parent_bus_addr;
531	u32 retries, val;
532	u64 limit_addr;
533 
534	limit_addr = parent_bus_addr + atu->size - 1;
535 
536	if ((limit_addr & ~pci->region_limit) != (parent_bus_addr & ~pci->region_limit) ||
537	    !IS_ALIGNED(parent_bus_addr, pci->region_align) ||
538	    !IS_ALIGNED(atu->pci_addr, pci->region_align) || !atu->size) {
539		return -EINVAL;
540	}
541 
542	dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_LOWER_BASE,
543			      lower_32_bits(parent_bus_addr));
544	dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_UPPER_BASE,
545			      upper_32_bits(parent_bus_addr));
546 
547	dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_LIMIT,
548			      lower_32_bits(limit_addr));
549	if (dw_pcie_ver_is_ge(pci, 460A))
550		dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_UPPER_LIMIT,
551				      upper_32_bits(limit_addr));
552 
553	dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_LOWER_TARGET,
554			      lower_32_bits(atu->pci_addr));
555	dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_UPPER_TARGET,
556			      upper_32_bits(atu->pci_addr));
557 
558	val = atu->type | atu->routing | PCIE_ATU_FUNC_NUM(atu->func_no);
559	if (upper_32_bits(limit_addr) > upper_32_bits(parent_bus_addr) &&
560	    dw_pcie_ver_is_ge(pci, 460A))
561		val |= PCIE_ATU_INCREASE_REGION_SIZE;
562	if (dw_pcie_ver_is(pci, 490A) || dw_pcie_ver_is(pci, 500A))
563		val = dw_pcie_enable_ecrc(val);
564	dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_REGION_CTRL1, val);
565 
566	val = PCIE_ATU_ENABLE | atu->ctrl2;
567	if (atu->type == PCIE_ATU_TYPE_MSG) {
568		/* The data-less messages only for now */
569		val |= PCIE_ATU_INHIBIT_PAYLOAD | atu->code;
570	}
571	dw_pcie_writel_atu_ob(pci, atu->index, PCIE_ATU_REGION_CTRL2, val);
572 
573	/*
574	 * Make sure ATU enable takes effect before any subsequent config
575	 * and I/O accesses.
576	 */
577	for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
578		val = dw_pcie_readl_atu_ob(pci, atu->index, PCIE_ATU_REGION_CTRL2);
579		if (val & PCIE_ATU_ENABLE)
580			return 0;
581 
582		mdelay(LINK_WAIT_IATU);
583	}
584 
585	dev_err(pci->dev, "Outbound iATU is not being enabled\n");
586 
587	return -ETIMEDOUT;
588}
589 
590static inline u32 dw_pcie_readl_atu_ib(struct dw_pcie *pci, u32 index, u32 reg)
591{
592	return dw_pcie_readl_atu(pci, PCIE_ATU_REGION_DIR_IB, index, reg);
593}
594 
595static inline void dw_pcie_writel_atu_ib(struct dw_pcie *pci, u32 index, u32 reg,
596					 u32 val)
597{
598	dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_IB, index, reg, val);
599}
600 
601int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int type,
602			     u64 parent_bus_addr, u64 pci_addr, u64 size)
603{
604	u64 limit_addr = pci_addr + size - 1;
605	u32 retries, val;
606 
607	if ((limit_addr & ~pci->region_limit) != (pci_addr & ~pci->region_limit) ||
608	    !IS_ALIGNED(parent_bus_addr, pci->region_align) ||
609	    !IS_ALIGNED(pci_addr, pci->region_align) || !size) {
610		return -EINVAL;
611	}
612 
613	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_BASE,

코드 해설

parent_bus_addr는 controller의 부모 버스 쪽 시작 주소이고 pci_addr는 PCIe 쪽 목표 주소입니다. size - 1을 더한 inclusive limit을 계산한 뒤 window 경계, 정렬, 0 크기를 검사하여 잘못된 범위는 -EINVAL로 거부합니다.

BASE/LIMIT/TARGET을 기록하고 CTRL2의 ENABLE을 쓴 다음 read-back으로 enable 반영을 확인합니다. 쓰기 함수를 호출했다고 해서 hardware가 설정을 받아들인 것은 아닙니다. 정해진 횟수 안에 확인되지 않으면 -ETIMEDOUT입니다.

Linux v6.18.37 · 실제 원본 코드

DWC inbound iATU의 PCI address, parent address와 inclusive limit programming

drivers/pci/controller/dwc/pcie-designware.c:593-677

593}
594 
595static inline void dw_pcie_writel_atu_ib(struct dw_pcie *pci, u32 index, u32 reg,
596					 u32 val)
597{
598	dw_pcie_writel_atu(pci, PCIE_ATU_REGION_DIR_IB, index, reg, val);
599}
600 
601int dw_pcie_prog_inbound_atu(struct dw_pcie *pci, int index, int type,
602			     u64 parent_bus_addr, u64 pci_addr, u64 size)
603{
604	u64 limit_addr = pci_addr + size - 1;
605	u32 retries, val;
606 
607	if ((limit_addr & ~pci->region_limit) != (pci_addr & ~pci->region_limit) ||
608	    !IS_ALIGNED(parent_bus_addr, pci->region_align) ||
609	    !IS_ALIGNED(pci_addr, pci->region_align) || !size) {
610		return -EINVAL;
611	}
612 
613	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_BASE,
614			      lower_32_bits(pci_addr));
615	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_BASE,
616			      upper_32_bits(pci_addr));
617 
618	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LIMIT,
619			      lower_32_bits(limit_addr));
620	if (dw_pcie_ver_is_ge(pci, 460A))
621		dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_LIMIT,
622				      upper_32_bits(limit_addr));
623 
624	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_TARGET,
625			      lower_32_bits(parent_bus_addr));
626	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_TARGET,
627			      upper_32_bits(parent_bus_addr));
628 
629	val = type;
630	if (upper_32_bits(limit_addr) > upper_32_bits(pci_addr) &&
631	    dw_pcie_ver_is_ge(pci, 460A))
632		val |= PCIE_ATU_INCREASE_REGION_SIZE;
633	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL1, val);
634	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2, PCIE_ATU_ENABLE);
635 
636	/*
637	 * Make sure ATU enable takes effect before any subsequent config
638	 * and I/O accesses.
639	 */
640	for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {
641		val = dw_pcie_readl_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2);
642		if (val & PCIE_ATU_ENABLE)
643			return 0;
644 
645		mdelay(LINK_WAIT_IATU);
646	}
647 
648	dev_err(pci->dev, "Inbound iATU is not being enabled\n");
649 
650	return -ETIMEDOUT;
651}
652 
653int dw_pcie_prog_ep_inbound_atu(struct dw_pcie *pci, u8 func_no, int index,
654				int type, u64 parent_bus_addr, u8 bar, size_t size)
655{
656	u32 retries, val;
657 
658	if (!IS_ALIGNED(parent_bus_addr, pci->region_align) ||
659	    !IS_ALIGNED(parent_bus_addr, size))
660		return -EINVAL;
661 
662	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_LOWER_TARGET,
663			      lower_32_bits(parent_bus_addr));
664	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_UPPER_TARGET,
665			      upper_32_bits(parent_bus_addr));
666 
667	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL1, type |
668			      PCIE_ATU_FUNC_NUM(func_no));
669	dw_pcie_writel_atu_ib(pci, index, PCIE_ATU_REGION_CTRL2,
670			      PCIE_ATU_ENABLE | PCIE_ATU_FUNC_NUM_MATCH_EN |
671			      PCIE_ATU_BAR_MODE_ENABLE | (bar << 8));
672 
673	/*
674	 * Make sure ATU enable takes effect before any subsequent config
675	 * and I/O accesses.
676	 */
677	for (retries = 0; retries < LINK_WAIT_MAX_IATU_RETRIES; retries++) {

코드 해설

inbound에서는 BASE와 LIMIT이 pci_addr 쪽 범위이고 TARGET이 parent_bus_addr입니다. outbound와 같은 변수 이름이 나온다고 주소 방향까지 같다고 읽으면 안 됩니다.

경계·정렬·size 검사 후 translation을 enable하고 read-back합니다. 이 설정만으로 IOMMU 매핑 또는 endpoint 드라이버의 DMA buffer 수명 관리까지 끝나는 것은 아닙니다.

Linux v6.18.37 · 실제 원본 코드

ECAM mode에서 immediate bus CFG0와 나머지 bus CFG1 region을 나누는 코드

drivers/pci/controller/dwc/pcie-designware-host.c:412-482

412		res->name = "msg";
413		res->flags = win->res->flags | IORESOURCE_BUSY;
414 
415		if (!devm_request_resource(pci->dev, win->res, res))
416			pp->msg_res = res;
417	}
418}
419 
420static int dw_pcie_config_ecam_iatu(struct dw_pcie_rp *pp)
421{
422	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
423	struct dw_pcie_ob_atu_cfg atu = {0};
424	resource_size_t bus_range_max;
425	struct resource_entry *bus;
426	int ret;
427 
428	bus = resource_list_first_type(&pp->bridge->windows, IORESOURCE_BUS);
429 
430	/*
431	 * Root bus under the host bridge doesn't require any iATU configuration
432	 * as DBI region will be used to access root bus config space.
433	 * Immediate bus under Root Bus, needs type 0 iATU configuration and
434	 * remaining buses need type 1 iATU configuration.
435	 */
436	atu.index = 0;
437	atu.type = PCIE_ATU_TYPE_CFG0;
438	atu.parent_bus_addr = pp->cfg0_base + SZ_1M;
439	/* 1MiB is to cover 1 (bus) * 32 (devices) * 8 (functions) */
440	atu.size = SZ_1M;
441	atu.ctrl2 = PCIE_ATU_CFG_SHIFT_MODE_ENABLE;
442	ret = dw_pcie_prog_outbound_atu(pci, &atu);
443	if (ret)
444		return ret;
445 
446	bus_range_max = resource_size(bus->res);
447 
448	if (bus_range_max < 2)
449		return 0;
450 
451	/* Configure remaining buses in type 1 iATU configuration */
452	atu.index = 1;
453	atu.type = PCIE_ATU_TYPE_CFG1;
454	atu.parent_bus_addr = pp->cfg0_base + SZ_2M;
455	atu.size = (SZ_1M * bus_range_max) - SZ_2M;
456	atu.ctrl2 = PCIE_ATU_CFG_SHIFT_MODE_ENABLE;
457 
458	return dw_pcie_prog_outbound_atu(pci, &atu);
459}
460 
461static int dw_pcie_create_ecam_window(struct dw_pcie_rp *pp, struct resource *res)
462{
463	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
464	struct device *dev = pci->dev;
465	struct resource_entry *bus;
466 
467	bus = resource_list_first_type(&pp->bridge->windows, IORESOURCE_BUS);
468	if (!bus)
469		return -ENODEV;
470 
471	pp->cfg = pci_ecam_create(dev, res, bus->res, &pci_generic_ecam_ops);
472	if (IS_ERR(pp->cfg))
473		return PTR_ERR(pp->cfg);
474 
475	return 0;
476}
477 
478static bool dw_pcie_ecam_enabled(struct dw_pcie_rp *pp, struct resource *config_res)
479{
480	struct resource *bus_range;
481	u64 nr_buses;
482 

코드 해설

Root bus 자신의 configuration은 DBI로 접근하므로 iATU 대상에서 제외합니다. 바로 아래 버스에는 CFG0 window를, 나머지 버스에는 CFG1 window를 설정합니다.

SZ_1M은 한 bus의 32 devices × 8 functions × 4 KiB config 크기입니다. CFG0 설정 실패는 즉시 반환하고, 나머지 버스 범위에 대해서는 CFG1 설정 결과를 반환합니다. 일반 BAR 메모리 window와 혼동하지 않습니다.

Linux v6.18.37 · 실제 원본 코드

DWC Root Port DBI와 outbound window 설정

drivers/pci/controller/dwc/pcie-designware-host.c:1037-1119

1037 
1038	if (speed >= PCIE_SPEED_32_0GT)
1039		dw_pcie_program_presets(pp, PCIE_SPEED_32_0GT);
1040 
1041	if (speed >= PCIE_SPEED_64_0GT)
1042		dw_pcie_program_presets(pp, PCIE_SPEED_64_0GT);
1043}
1044 
1045int dw_pcie_setup_rc(struct dw_pcie_rp *pp)
1046{
1047	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
1048	u32 val;
1049	int ret;
1050 
1051	/*
1052	 * Enable DBI read-only registers for writing/updating configuration.
1053	 * Write permission gets disabled towards the end of this function.
1054	 */
1055	dw_pcie_dbi_ro_wr_en(pci);
1056 
1057	dw_pcie_setup(pci);
1058 
1059	dw_pcie_msi_init(pp);
1060 
1061	/* Setup RC BARs */
1062	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0x00000004);
1063	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_1, 0x00000000);
1064 
1065	/* Setup interrupt pins */
1066	val = dw_pcie_readl_dbi(pci, PCI_INTERRUPT_LINE);
1067	val &= 0xffff00ff;
1068	val |= 0x00000100;
1069	dw_pcie_writel_dbi(pci, PCI_INTERRUPT_LINE, val);
1070 
1071	/* Setup bus numbers */
1072	val = dw_pcie_readl_dbi(pci, PCI_PRIMARY_BUS);
1073	val &= 0xff000000;
1074	val |= 0x00ff0100;
1075	dw_pcie_writel_dbi(pci, PCI_PRIMARY_BUS, val);
1076 
1077	/* Setup command register */
1078	val = dw_pcie_readl_dbi(pci, PCI_COMMAND);
1079	val &= 0xffff0000;
1080	val |= PCI_COMMAND_IO | PCI_COMMAND_MEMORY |
1081		PCI_COMMAND_MASTER | PCI_COMMAND_SERR;
1082	dw_pcie_writel_dbi(pci, PCI_COMMAND, val);
1083 
1084	dw_pcie_hide_unsupported_l1ss(pci);
1085 
1086	dw_pcie_config_presets(pp);
1087	/*
1088	 * If the platform provides its own child bus config accesses, it means
1089	 * the platform uses its own address translation component rather than
1090	 * ATU, so we should not program the ATU here.
1091	 */
1092	if (pp->bridge->child_ops == &dw_child_pcie_ops) {
1093		ret = dw_pcie_iatu_setup(pp);
1094		if (ret)
1095			return ret;
1096	}
1097 
1098	dw_pcie_writel_dbi(pci, PCI_BASE_ADDRESS_0, 0);
1099 
1100	/* Program correct class for RC */
1101	dw_pcie_writew_dbi(pci, PCI_CLASS_DEVICE, PCI_CLASS_BRIDGE_PCI);
1102 
1103	val = dw_pcie_readl_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL);
1104	val |= PORT_LOGIC_SPEED_CHANGE;
1105	dw_pcie_writel_dbi(pci, PCIE_LINK_WIDTH_SPEED_CONTROL, val);
1106 
1107	dw_pcie_dbi_ro_wr_dis(pci);
1108 
1109	return 0;
1110}
1111EXPORT_SYMBOL_GPL(dw_pcie_setup_rc);
1112 
1113static int dw_pcie_pme_turn_off(struct dw_pcie *pci)
1114{
1115	struct dw_pcie_ob_atu_cfg atu = { 0 };
1116	void __iomem *mem;
1117	int ret;
1118 
1119	if (pci->num_ob_windows <= pci->pp.msg_atu_index)

코드 해설

RC의 DBI read-only 쓰기를 잠시 허용하여 BAR, interrupt pin, bus number, command와 class 정보를 구성합니다. 정상 종료에서는 쓰기 허용을 다시 끕니다.

child_ops가 dw_child_pcie_ops인 경우에만 자체 iATU setup을 수행합니다. 플랫폼이 별도 config 접근을 제공할 수 있기 때문입니다. iATU 실패는 중간 return ret로 빠지므로 정상 종료의 마지막 정리가 모든 분기에서 실행된다고 설명해서는 안 됩니다.

Linux v6.18.37 · 실제 원본 코드

DWC 내부 MSI status block을 읽고 irqdomain으로 전달하는 handler

drivers/pci/controller/dwc/pcie-designware-host.c:41-107

41	.supported_flags	= DW_PCIE_MSI_FLAGS_SUPPORTED,
42	.bus_select_token	= DOMAIN_BUS_PCI_MSI,
43	.chip_flags		= MSI_CHIP_FLAG_SET_ACK,
44	.prefix			= "DW-",
45	.init_dev_msi_info	= msi_lib_init_dev_msi_info,
46};
47 
48/* MSI int handler */
49irqreturn_t dw_handle_msi_irq(struct dw_pcie_rp *pp)
50{
51	int i, pos;
52	unsigned long val;
53	u32 status, num_ctrls;
54	irqreturn_t ret = IRQ_NONE;
55	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
56 
57	num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
58 
59	for (i = 0; i < num_ctrls; i++) {
60		status = dw_pcie_readl_dbi(pci, PCIE_MSI_INTR0_STATUS +
61					   (i * MSI_REG_CTRL_BLOCK_SIZE));
62		if (!status)
63			continue;
64 
65		ret = IRQ_HANDLED;
66		val = status;
67		pos = 0;
68		while ((pos = find_next_bit(&val, MAX_MSI_IRQS_PER_CTRL,
69					    pos)) != MAX_MSI_IRQS_PER_CTRL) {
70			generic_handle_domain_irq(pp->irq_domain,
71						  (i * MAX_MSI_IRQS_PER_CTRL) +
72						  pos);
73			pos++;
74		}
75	}
76 
77	return ret;
78}
79 
80/* Chained MSI interrupt service routine */
81static void dw_chained_msi_isr(struct irq_desc *desc)
82{
83	struct irq_chip *chip = irq_desc_get_chip(desc);
84	struct dw_pcie_rp *pp;
85 
86	chained_irq_enter(chip, desc);
87 
88	pp = irq_desc_get_handler_data(desc);
89	dw_handle_msi_irq(pp);
90 
91	chained_irq_exit(chip, desc);
92}
93 
94static void dw_pci_setup_msi_msg(struct irq_data *d, struct msi_msg *msg)
95{
96	struct dw_pcie_rp *pp = irq_data_get_irq_chip_data(d);
97	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
98	u64 msi_target;
99 
100	msi_target = (u64)pp->msi_data;
101 
102	msg->address_lo = lower_32_bits(msi_target);
103	msg->address_hi = upper_32_bits(msi_target);
104 
105	msg->data = d->hwirq;
106 
107	dev_dbg(pci->dev, "msi#%d address_hi %#x address_lo %#x\n",

코드 해설

각 MSI status block을 읽고 set bit마다 block 번호와 bit 위치를 합쳐 pp->irq_domain의 hwirq로 전달합니다. 이 번호는 Linux IRQ 번호나 PCI BDF가 아닙니다.

status가 모두 0이면 IRQ_NONE, 하나라도 발견하면 IRQ_HANDLED입니다. 이 함수의 반복문만으로 endpoint 데이터 처리가 끝났다고 판단하지 않습니다. irqdomain 이후의 action과 장치별 완료 처리는 별도입니다.

Linux v6.18.37 · 실제 원본 코드

DWC MSI target address, enable과 mask register 초기화

drivers/pci/controller/dwc/pcie-designware-host.c:243-293

243			irq_set_chained_handler_and_data(pp->msi_irq[ctrl],
244							 NULL, NULL);
245	}
246 
247	irq_domain_remove(pp->irq_domain);
248}
249EXPORT_SYMBOL_GPL(dw_pcie_free_msi);
250 
251void dw_pcie_msi_init(struct dw_pcie_rp *pp)
252{
253	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
254	u64 msi_target = (u64)pp->msi_data;
255	u32 ctrl, num_ctrls;
256 
257	if (!pci_msi_enabled() || !pp->has_msi_ctrl)
258		return;
259 
260	num_ctrls = pp->num_vectors / MAX_MSI_IRQS_PER_CTRL;
261 
262	/* Initialize IRQ Status array */
263	for (ctrl = 0; ctrl < num_ctrls; ctrl++) {
264		dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_MASK +
265				    (ctrl * MSI_REG_CTRL_BLOCK_SIZE),
266				    pp->irq_mask[ctrl]);
267		dw_pcie_writel_dbi(pci, PCIE_MSI_INTR0_ENABLE +
268				    (ctrl * MSI_REG_CTRL_BLOCK_SIZE),
269				    ~0);
270	}
271 
272	/* Program the msi_data */
273	dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_LO, lower_32_bits(msi_target));
274	dw_pcie_writel_dbi(pci, PCIE_MSI_ADDR_HI, upper_32_bits(msi_target));
275}
276EXPORT_SYMBOL_GPL(dw_pcie_msi_init);
277 
278static int dw_pcie_parse_split_msi_irq(struct dw_pcie_rp *pp)
279{
280	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
281	struct device *dev = pci->dev;
282	struct platform_device *pdev = to_platform_device(dev);
283	u32 ctrl, max_vectors;
284	int irq;
285 
286	/* Parse any "msiX" IRQs described in the devicetree */
287	for (ctrl = 0; ctrl < MAX_MSI_CTRLS; ctrl++) {
288		char msi_name[] = "msiX";
289 
290		msi_name[3] = '0' + ctrl;
291		irq = platform_get_irq_byname_optional(pdev, msi_name);
292		if (irq == -ENXIO)
293			break;

코드 해설

L254의 u64 msi_target = (u64)pp->msi_data는 지역 변수 선언과 형 변환을 포함한 초기화입니다. 함수 선언 또는 함수 경계가 아닙니다. MSI가 비활성화됐거나 자체 MSI controller가 없으면 바로 반환합니다.

각 block의 MASK에는 저장된 irq_mask를 쓰고 ENABLE에는 모든 bit가 1인 값을 씁니다. enable과 mask는 별도이므로 이 값만 보고 모든 vector가 전달 가능하다고 단정할 수 없습니다. 마지막으로 target 주소의 하위·상위 32비트를 MSI_ADDR_LO/HI에 나눠 씁니다.

Linux v6.18.37 · 실제 원본 코드

DWC eDMA mode, channel, IRQ와 linked-list resource detection

drivers/pci/controller/dwc/pcie-designware.c:1088-1150

1088						    &paddr, GFP_KERNEL);
1089		if (!ll->vaddr.mem)
1090			return -ENOMEM;
1091 
1092		ll->paddr = paddr;
1093	}
1094 
1095	return 0;
1096}
1097 
1098int dw_pcie_edma_detect(struct dw_pcie *pci)
1099{
1100	int ret;
1101 
1102	/* Don't fail if no eDMA was found (for the backward compatibility) */
1103	ret = dw_pcie_edma_find_chip(pci);
1104	if (ret)
1105		return 0;
1106 
1107	/* Don't fail on the IRQs verification (for the backward compatibility) */
1108	ret = dw_pcie_edma_irq_verify(pci);
1109	if (ret) {
1110		dev_err(pci->dev, "Invalid eDMA IRQs found\n");
1111		return 0;
1112	}
1113 
1114	ret = dw_pcie_edma_ll_alloc(pci);
1115	if (ret) {
1116		dev_err(pci->dev, "Couldn't allocate LLP memory\n");
1117		return ret;
1118	}
1119 
1120	/* Don't fail if the DW eDMA driver can't find the device */
1121	ret = dw_edma_probe(&pci->edma);
1122	if (ret && ret != -ENODEV) {
1123		dev_err(pci->dev, "Couldn't register eDMA device\n");
1124		return ret;
1125	}
1126 
1127	dev_info(pci->dev, "eDMA: unroll %s, %hu wr, %hu rd\n",
1128		 pci->edma.mf == EDMA_MF_EDMA_UNROLL ? "T" : "F",
1129		 pci->edma.ll_wr_cnt, pci->edma.ll_rd_cnt);
1130 
1131	return 0;
1132}
1133 
1134void dw_pcie_edma_remove(struct dw_pcie *pci)
1135{
1136	dw_edma_remove(&pci->edma);
1137}
1138 
1139void dw_pcie_hide_unsupported_l1ss(struct dw_pcie *pci)
1140{
1141	u16 l1ss;
1142	u32 l1ss_cap;
1143 
1144	if (pci->l1ss_support)
1145		return;
1146 
1147	l1ss = dw_pcie_find_ext_capability(pci, PCI_EXT_CAP_ID_L1SS);
1148	if (!l1ss)
1149		return;
1150 

코드 해설

호환성을 위해 eDMA를 찾지 못하거나 IRQ 검증에 실패해도 이 함수는 0을 반환할 수 있습니다. 따라서 반환 0만으로 eDMA가 사용 가능하다고 결론 내리면 안 됩니다.

LLP 메모리 할당 실패는 오류를 반환합니다. dw_edma_probe의 -ENODEV는 허용하지만 그 밖의 오류는 전달합니다. 이 경로의 서로 다른 반환 정책을 구분해서 로그와 실제 등록 장치를 확인합니다.

Linux v6.18.37 · 실제 원본 코드

Vendor ID를 읽어 pci_dev를 만들고 중복 scan을 방지하는 경계

drivers/pci/probe.c:2612-2668

2612	return NULL;
2613}
2614#endif
2615 
2616/*
2617 * Read the config data for a PCI device, sanity-check it,
2618 * and fill in the dev structure.
2619 */
2620static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
2621{
2622	struct pci_dev *dev;
2623	u32 l;
2624 
2625	/*
2626	 * Create pwrctrl device (if required) for the PCI device to handle the
2627	 * power state. If the pwrctrl device is created, then skip scanning
2628	 * further as the pwrctrl core will rescan the bus after powering on
2629	 * the device.
2630	 */
2631	if (pci_pwrctrl_create_device(bus, devfn))
2632		return NULL;
2633 
2634	if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))
2635		return NULL;
2636 
2637	dev = pci_alloc_dev(bus);
2638	if (!dev)
2639		return NULL;
2640 
2641	dev->devfn = devfn;
2642	dev->vendor = l & 0xffff;
2643	dev->device = (l >> 16) & 0xffff;
2644 
2645	if (pci_setup_device(dev)) {
2646		pci_bus_put(dev->bus);
2647		kfree(dev);
2648		return NULL;
2649	}
2650 
2651	return dev;
2652}
2653 
2654void pcie_report_downtraining(struct pci_dev *dev)
2655{
2656	if (!pci_is_pcie(dev))
2657		return;
2658 
2659	/* Look from the device up to avoid downstream ports with no devices */
2660	if ((pci_pcie_type(dev) != PCI_EXP_TYPE_ENDPOINT) &&
2661	    (pci_pcie_type(dev) != PCI_EXP_TYPE_LEG_END) &&
2662	    (pci_pcie_type(dev) != PCI_EXP_TYPE_UPSTREAM))
2663		return;
2664 
2665	/* Multi-function PCIe devices share the same link/status */
2666	if (PCI_FUNC(dev->devfn) != 0 || dev->is_virtfn)
2667		return;
2668 

코드 해설

pwrctrl 장치를 만든 경우 이 scan은 NULL로 종료하고 전원 공급 이후 core의 rescan에 맡깁니다. 이 return을 vendor ID 응답 실패와 같은 원인으로 합치면 전원 지연을 놓칩니다.

vendor/device ID를 읽은 뒤 pci_dev를 할당하고 pci_setup_device로 내용을 구성합니다. setup 실패는 bus 참조를 돌려주고 dev를 해제합니다. 반환 객체가 생성됐다고 해서 장치 드라이버 probe까지 완료된 것은 아닙니다.

Linux v6.18.37 · 실제 원본 코드

Config header, class, BAR와 subsystem 정보를 pci_dev에 채우는 과정

drivers/pci/probe.c:1987-2091

1987/**
1988 * pci_setup_device - Fill in class and map information of a device
1989 * @dev: the device structure to fill
1990 *
1991 * Initialize the device structure with information about the device's
1992 * vendor,class,memory and IO-space addresses, IRQ lines etc.
1993 * Called at initialisation of the PCI subsystem and by CardBus services.
1994 * Returns 0 on success and negative if unknown type of device (not normal,
1995 * bridge or CardBus).
1996 */
1997int pci_setup_device(struct pci_dev *dev)
1998{
1999	u32 class;
2000	u16 cmd;
2001	u8 hdr_type;
2002	int err, pos = 0;
2003	struct pci_bus_region region;
2004	struct resource *res;
2005 
2006	hdr_type = pci_hdr_type(dev);
2007 
2008	dev->sysdata = dev->bus->sysdata;
2009	dev->dev.parent = dev->bus->bridge;
2010	dev->dev.bus = &pci_bus_type;
2011	dev->hdr_type = FIELD_GET(PCI_HEADER_TYPE_MASK, hdr_type);
2012	dev->multifunction = FIELD_GET(PCI_HEADER_TYPE_MFD, hdr_type);
2013	dev->error_state = pci_channel_io_normal;
2014	set_pcie_port_type(dev);
2015 
2016	err = pci_set_of_node(dev);
2017	if (err)
2018		return err;
2019	pci_set_acpi_fwnode(dev);
2020 
2021	pci_dev_assign_slot(dev);
2022 
2023	/*
2024	 * Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer)
2025	 * set this higher, assuming the system even supports it.
2026	 */
2027	dev->dma_mask = 0xffffffff;
2028 
2029	dev_set_name(&dev->dev, "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
2030		     dev->bus->number, PCI_SLOT(dev->devfn),
2031		     PCI_FUNC(dev->devfn));
2032 
2033	class = pci_class(dev);
2034 
2035	dev->revision = class & 0xff;
2036	dev->class = class >> 8;		    /* upper 3 bytes */
2037 
2038	if (pci_early_dump)
2039		early_dump_pci_device(dev);
2040 
2041	/* Need to have dev->class ready */
2042	dev->cfg_size = pci_cfg_space_size(dev);
2043 
2044	/* Need to have dev->cfg_size ready */
2045	set_pcie_thunderbolt(dev);
2046 
2047	set_pcie_untrusted(dev);
2048 
2049	if (pci_is_pcie(dev))
2050		dev->supported_speeds = pcie_get_supported_speeds(dev);
2051 
2052	/* "Unknown power state" */
2053	dev->current_state = PCI_UNKNOWN;
2054 
2055	/* Early fixups, before probing the BARs */
2056	pci_fixup_device(pci_fixup_early, dev);
2057 
2058	pci_set_removable(dev);
2059 
2060	pci_info(dev, "[%04x:%04x] type %02x class %#08x %s\n",
2061		 dev->vendor, dev->device, dev->hdr_type, dev->class,
2062		 pci_type_str(dev));
2063 
2064	/* Device class may be changed after fixup */
2065	class = dev->class >> 8;
2066 
2067	if (dev->non_compliant_bars && !dev->mmio_always_on) {
2068		pci_read_config_word(dev, PCI_COMMAND, &cmd);
2069		if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
2070			pci_info(dev, "device has non-compliant BARs; disabling IO/MEM decoding\n");
2071			cmd &= ~PCI_COMMAND_IO;
2072			cmd &= ~PCI_COMMAND_MEMORY;
2073			pci_write_config_word(dev, PCI_COMMAND, cmd);
2074		}
2075	}
2076 
2077	dev->broken_intx_masking = pci_intx_mask_broken(dev);
2078 
2079	switch (dev->hdr_type) {		    /* header type */
2080	case PCI_HEADER_TYPE_NORMAL:		    /* standard header */
2081		if (class == PCI_CLASS_BRIDGE_PCI)
2082			goto bad;
2083		pci_read_irq(dev);
2084		pci_read_bases(dev, PCI_STD_NUM_BARS, PCI_ROM_ADDRESS);
2085 
2086		pci_subsystem_ids(dev, &dev->subsystem_vendor, &dev->subsystem_device);
2087 
2088		/*
2089		 * Do the ugly legacy mode stuff here rather than broken chip
2090		 * quirk code. Legacy mode ATA controllers have fixed
2091		 * addresses. These are not always echoed in BAR0-3, and

코드 해설

header type과 multifunction bit를 분리하고 부모 bus, firmware node, class/revision, config 크기 등의 정보를 채웁니다. 초기 dma_mask 32비트는 최종 드라이버의 DMA 설정을 보장하지 않습니다.

early fixup을 거친 뒤 header type에 따라 BAR·IRQ 등 읽는 항목이 달라집니다. 이 발췌는 함수 앞부분으로, 이후 header별 분기 전체를 보여 주지는 않습니다.

Linux v6.18.37 · 실제 원본 코드

BAR all-ones sizing, type, 64-bit pair와 resource flag 해석

drivers/pci/probe.c:335-439

335	res->flags = 0;
336out:
337	if (res->flags)
338		pci_info(dev, "%s %pR\n", res_name, res);
339 
340	return (res->flags & IORESOURCE_MEM_64) ? 1 : 0;
341}
342 
343static __always_inline void pci_read_bases(struct pci_dev *dev,
344					   unsigned int howmany, int rom)
345{
346	u32 rombar, stdbars[PCI_STD_NUM_BARS];
347	unsigned int pos, reg;
348	u16 orig_cmd;
349 
350	BUILD_BUG_ON(statically_true(howmany > PCI_STD_NUM_BARS));
351 
352	if (dev->non_compliant_bars)
353		return;
354 
355	/* Per PCIe r4.0, sec 9.3.4.1.11, the VF BARs are all RO Zero */
356	if (dev->is_virtfn)
357		return;
358 
359	/* No printks while decoding is disabled! */
360	if (!dev->mmio_always_on) {
361		pci_read_config_word(dev, PCI_COMMAND, &orig_cmd);
362		if (orig_cmd & PCI_COMMAND_DECODE_ENABLE) {
363			pci_write_config_word(dev, PCI_COMMAND,
364				orig_cmd & ~PCI_COMMAND_DECODE_ENABLE);
365		}
366	}
367 
368	__pci_size_stdbars(dev, howmany, PCI_BASE_ADDRESS_0, stdbars);
369	if (rom)
370		__pci_size_rom(dev, rom, &rombar);
371 
372	if (!dev->mmio_always_on &&
373	    (orig_cmd & PCI_COMMAND_DECODE_ENABLE))
374		pci_write_config_word(dev, PCI_COMMAND, orig_cmd);
375 
376	for (pos = 0; pos < howmany; pos++) {
377		struct resource *res = &dev->resource[pos];
378		reg = PCI_BASE_ADDRESS_0 + (pos << 2);
379		pos += __pci_read_base(dev, pci_bar_unknown,
380				       res, reg, &stdbars[pos]);
381	}
382 
383	if (rom) {
384		struct resource *res = &dev->resource[PCI_ROM_RESOURCE];
385		dev->rom_base_reg = rom;
386		res->flags = IORESOURCE_MEM | IORESOURCE_PREFETCH |
387				IORESOURCE_READONLY | IORESOURCE_SIZEALIGN;
388		__pci_read_base(dev, pci_bar_mem32, res, rom, &rombar);
389	}
390}
391 
392static void pci_read_bridge_io(struct pci_dev *dev, struct resource *res,
393			       bool log)
394{
395	u8 io_base_lo, io_limit_lo;
396	unsigned long io_mask, io_granularity, base, limit;
397	struct pci_bus_region region;
398 
399	io_mask = PCI_IO_RANGE_MASK;
400	io_granularity = 0x1000;
401	if (dev->io_window_1k) {
402		/* Support 1K I/O space granularity */
403		io_mask = PCI_IO_1K_RANGE_MASK;
404		io_granularity = 0x400;
405	}
406 
407	pci_read_config_byte(dev, PCI_IO_BASE, &io_base_lo);
408	pci_read_config_byte(dev, PCI_IO_LIMIT, &io_limit_lo);
409	base = (io_base_lo & io_mask) << 8;
410	limit = (io_limit_lo & io_mask) << 8;
411 
412	if ((io_base_lo & PCI_IO_RANGE_TYPE_MASK) == PCI_IO_RANGE_TYPE_32) {
413		u16 io_base_hi, io_limit_hi;
414 
415		pci_read_config_word(dev, PCI_IO_BASE_UPPER16, &io_base_hi);
416		pci_read_config_word(dev, PCI_IO_LIMIT_UPPER16, &io_limit_hi);
417		base |= ((unsigned long) io_base_hi << 16);
418		limit |= ((unsigned long) io_limit_hi << 16);
419	}
420 
421	res->flags = (io_base_lo & PCI_IO_RANGE_TYPE_MASK) | IORESOURCE_IO;
422 
423	if (base <= limit) {
424		region.start = base;
425		region.end = limit + io_granularity - 1;
426		pcibios_bus_to_resource(dev->bus, res, &region);
427		if (log)
428			pci_info(dev, "  bridge window %pR\n", res);
429	} else {
430		resource_set_range(res, 0, 0);
431		res->flags |= IORESOURCE_UNSET | IORESOURCE_DISABLED;
432	}
433}
434 
435static void pci_read_bridge_mmio(struct pci_dev *dev, struct resource *res,
436				 bool log)
437{
438	u16 mem_base_lo, mem_limit_lo;
439	unsigned long base, limit;

코드 해설

non-compliant BAR 또는 VF이면 일반 BAR sizing을 건너뜁니다. 그 밖에는 필요할 때 decode를 잠시 끄고 size를 읽은 후 원래 command를 복원합니다.

__pci_read_base의 반환값에 따라 pos를 추가로 진행시켜 64비트 BAR의 두 register 소비를 반영합니다. resource 크기를 읽었다고 해서 사용 가능한 주소까지 할당된 것은 아닙니다.

Linux v6.18.37 · 실제 원본 코드

Root bus resource assignment

drivers/pci/setup-bus.c:2237-2307

2237	free_list(fail_head);
2238}
2239 
2240/*
2241 * First try will not touch PCI bridge res.
2242 * Second and later try will clear small leaf bridge res.
2243 * Will stop till to the max depth if can not find good one.
2244 */
2245void pci_assign_unassigned_root_bus_resources(struct pci_bus *bus)
2246{
2247	LIST_HEAD(realloc_head);
2248	/* List of resources that want additional resources */
2249	struct list_head *add_list = NULL;
2250	int tried_times = 0;
2251	enum release_type rel_type = leaf_only;
2252	LIST_HEAD(fail_head);
2253	int pci_try_num = 1;
2254	enum enable_type enable_local;
2255 
2256	/* Don't realloc if asked to do so */
2257	enable_local = pci_realloc_detect(bus, pci_realloc_enable);
2258	if (pci_realloc_enabled(enable_local)) {
2259		int max_depth = pci_bus_get_depth(bus);
2260 
2261		pci_try_num = max_depth + 1;
2262		dev_info(&bus->dev, "max bus depth: %d pci_try_num: %d\n",
2263			 max_depth, pci_try_num);
2264	}
2265 
2266	while (1) {
2267		/*
2268		 * Last try will use add_list, otherwise will try good to
2269		 * have as must have, so can realloc parent bridge resource
2270		 */
2271		if (tried_times + 1 == pci_try_num)
2272			add_list = &realloc_head;
2273		/*
2274		 * Depth first, calculate sizes and alignments of all
2275		 * subordinate buses.
2276		 */
2277		__pci_bus_size_bridges(bus, add_list);
2278 
2279		pci_root_bus_distribute_available_resources(bus, add_list);
2280 
2281		/* Depth last, allocate resources and update the hardware. */
2282		__pci_bus_assign_resources(bus, add_list, &fail_head);
2283		if (WARN_ON_ONCE(add_list && !list_empty(add_list)))
2284			free_list(add_list);
2285		tried_times++;
2286 
2287		/* Any device complain? */
2288		if (list_empty(&fail_head))
2289			break;
2290 
2291		if (tried_times >= pci_try_num) {
2292			if (enable_local == undefined) {
2293				dev_info(&bus->dev,
2294					 "Some PCI device resources are unassigned, try booting with pci=realloc\n");
2295			} else if (enable_local == auto_enabled) {
2296				dev_info(&bus->dev,
2297					 "Automatically enabled pci realloc, if you have problem, try booting with pci=realloc=off\n");
2298			}
2299			free_list(&fail_head);
2300			break;
2301		}
2302 
2303		/* Third times and later will not check if it is leaf */
2304		if (tried_times + 1 > 2)
2305			rel_type = whole_subtree;
2306 
2307		pci_prepare_next_assign_round(&fail_head, tried_times, rel_type);

코드 해설

먼저 subordinate bus가 필요한 크기와 정렬을 계산하고, root에서 가용 자원을 배분한 뒤 실제 resource 할당을 시도합니다. 실패 목록과 realloc 정책에 따라 다음 시도에 해제할 범위를 조정합니다.

시도 횟수에는 제한이 있습니다. 함수가 끝났다고 해서 모든 BAR가 배치된 것으로 여기지 말고 unassigned 로그와 resource 결과를 확인합니다.

Linux v6.18.37 · 실제 원본 코드

PCI device enable

drivers/pci/pci.c:2143-2183

2143 * pci_enable_device - Initialize device before it's used by a driver.
2144 * @dev: PCI device to be initialized
2145 *
2146 * Initialize device before it's used by a driver. Ask low-level code
2147 * to enable I/O and memory. Wake up the device if it was suspended.
2148 * Beware, this function can fail.
2149 *
2150 * Note we don't actually enable the device many times if we call
2151 * this function repeatedly (we just increment the count).
2152 */
2153int pci_enable_device(struct pci_dev *dev)
2154{
2155	return pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
2156}
2157EXPORT_SYMBOL(pci_enable_device);
2158 
2159/*
2160 * pcibios_device_add - provide arch specific hooks when adding device dev
2161 * @dev: the PCI device being added
2162 *
2163 * Permits the platform to provide architecture specific functionality when
2164 * devices are added. This is the default implementation. Architecture
2165 * implementations can override this.
2166 */
2167int __weak pcibios_device_add(struct pci_dev *dev)
2168{
2169	return 0;
2170}
2171 
2172/**
2173 * pcibios_release_device - provide arch specific hooks when releasing
2174 *			    device dev
2175 * @dev: the PCI device being released
2176 *
2177 * Permits the platform to provide architecture specific functionality when
2178 * devices are released. This is the default implementation. Architecture
2179 * implementations can override this.
2180 */
2181void __weak pcibios_release_device(struct pci_dev *dev) {}
2182 
2183/**

코드 해설

IORESOURCE_MEM | IORESOURCE_IO를 pci_enable_device_flags에 전달하는 wrapper입니다. 이 짧은 함수에 BAR 매핑이나 DMA descriptor 작성이 들어 있는 것은 아닙니다.

드라이버는 반환값을 확인하고 필요한 region 확보·매핑·DMA mask·bus mastering 등 별도 준비를 수행해야 합니다. pci_enable_device 하나를 DMA 시작의 충분조건으로 읽지 않습니다.

Linux v6.18.37 · 실제 원본 코드

BAR mapping

drivers/pci/iomap.c:23-71

23 * the details if this is a MMIO or PIO address space and will just do what
24 * you expect from them in the correct way.
25 *
26 * @maxlen specifies the maximum length to map. If you want to get access to
27 * the complete BAR from offset to the end, pass %0 here.
28 * */
29void __iomem *pci_iomap_range(struct pci_dev *dev,
30			      int bar,
31			      unsigned long offset,
32			      unsigned long maxlen)
33{
34	resource_size_t start, len;
35	unsigned long flags;
36 
37	if (!pci_bar_index_is_valid(bar))
38		return NULL;
39 
40	start = pci_resource_start(dev, bar);
41	len = pci_resource_len(dev, bar);
42	flags = pci_resource_flags(dev, bar);
43 
44	if (len <= offset || !start)
45		return NULL;
46 
47	len -= offset;
48	start += offset;
49	if (maxlen && len > maxlen)
50		len = maxlen;
51	if (flags & IORESOURCE_IO)
52		return __pci_ioport_map(dev, start, len);
53	if (flags & IORESOURCE_MEM)
54		return ioremap(start, len);
55	/* What? */
56	return NULL;
57}
58EXPORT_SYMBOL(pci_iomap_range);
59 
60/**
61 * pci_iomap_wc_range - create a virtual WC mapping cookie for a PCI BAR
62 * @dev: PCI device that owns the BAR
63 * @bar: BAR number
64 * @offset: map memory at the given offset in BAR
65 * @maxlen: max length of the memory to map
66 *
67 * Using this function you will get a __iomem address to your device BAR.
68 * You can access it using ioread*() and iowrite*(). These functions hide
69 * the details if this is a MMIO or PIO address space and will just do what
70 * you expect from them in the correct way. When possible write combining
71 * is used.

코드 해설

BAR 번호와 offset 범위를 검사하고 남은 길이를 maxlen으로 제한합니다. I/O resource는 ioport mapping, memory resource는 ioremap 경로를 사용하며 잘못된 범위·종류는 NULL입니다.

반환값은 CPU에서 장치 BAR에 접근할 __iomem mapping입니다. endpoint가 접근할 DMA 주소가 아닙니다. 매핑을 만들었다고 장치가 DMA를 수행하거나 전송 완료를 알린 것은 아닙니다.

09 · VERIFICATION

실제 장비에서 확인하는 순서

  1. Hierarchy

    실행 `lspci -tv`와 각 bridge의 bus number/window를 저장한다.

    통과 기준 parent window가 child resource를 완전히 포함해야 한다.

  2. BAR

    실행 `cat /sys/bus/pci/devices/<BDF>/resource`를 driver map과 대조한다.

    통과 기준 BAR physical range와 ioremap 대상이 같은 resource여야 한다.

  3. DMA

    실행 IOMMU fault logging과 driver descriptor dump를 동시에 켠다.

    통과 기준 fault requester ID, IOVA, direction이 해당 queue entry와 일치해야 한다.

  4. MSI

    실행 `grep <driver> /proc/interrupts`와 affinity를 확인한다.

    통과 기준 queue activity와 vector count가 함께 증가해야 한다.

  5. Teardown

    실행 unbind/bind와 suspend/resume를 반복한다.

    통과 기준 resource, IOVA, vector가 누적되지 않고 동일 순서로 재획득돼야 한다.

10 · FAILURE MATRIX

처음 끊긴 경계로 원인을 좁히기

관측 결과우선 의심할 경계다음 증거
BAR no spacebridge/host window가 작거나 alignment 불가resource tree와 firmware ranges
MMIO all-onesdecode/power/reset/window 문제PCI_COMMAND, D-state, BAR와 bridge window
IOMMU fault잘못된 IOVA, size, direction 또는 lifetimerequester ID와 descriptor DMA address
MSI count 0message routing/table mask/vector 문제MSI-X entry, remapper, irqdomain
interrupt는 오나 완료 없음DMA visibility 또는 device status 해석 문제descriptor ownership, dma barrier, completion index

핵심은 마지막 오류 메시지가 아니라 처음 기대값과 달라진 checkpoint입니다. 그보다 아래의 통과 증거와 그 지점의 실패 증거를 한 묶음으로 남겨야 수정의 효과도 검증할 수 있습니다.

Host bridge 발견과 PCI 장치 열거는 다릅니다

PCI configuration space를 읽어 장치를 찾으려면 먼저 그 공간에 접근할 host bridge를 알아야 합니다. ACPI 시스템은 펌웨어가 host bridge, configuration 접근 경로와 전달 가능한 주소 창을 설명합니다. PCI 장치 자체는 그 아래에서 configuration access로 찾습니다. 따라서 “PCI는 자동 탐색되므로 보드 설명이 전혀 필요 없다”는 결론은 맞지 않습니다. Device Tree 시스템의 host controller도 해당 바인딩과 플랫폼 자원을 별도로 확인합니다.

Root Complex는 프로세서·메모리 쪽과 PCIe 계층을 잇는 기능 집합이고 Root Port는 그 계층으로 나가는 포트입니다. 한 Root Port를 전체 Root Complex와 동일시하지 않습니다. lspci -t의 bridge 계층, 각 port의 BDF와 downstream 장치, 펌웨어의 host window를 함께 대응시킵니다. host bridge 자원 창과 endpoint BAR 크기·주소도 같은 항목이 아닙니다.

열거되어 BDF가 생긴 상태, 드라이버와 매칭된 상태, probe()가 성공한 상태, 실제 I/O가 완료된 상태를 따로 기록합니다. BAR 배정만으로 DMA queue·interrupt·장치 자체 초기화가 끝나지 않습니다. 이 구분은 이 글의 enable·DMA·MSI 및 종료 경로를 읽는 출발점입니다.

기준: Linux: PCI host bridge의 ACPI 설명과 configuration 탐색. 실제 장비에서 열거·DMA를 실행한 결과는 아닙니다.

English summary. Firmware discovery of a host bridge precedes configuration-space enumeration below it. A Root Port is not the entire Root Complex. Enumeration, binding, successful probe and completed I/O are distinct checkpoints.

Endpoint를 사용하는 드라이버와 만드는 드라이버

호스트 Linux의 pci_driver는 PCI 버스에서 발견된 function의 ID와 매칭되어 그 장치를 사용합니다. 반면 endpoint 모드로 동작하는 SoC의 컨트롤러는 자기 레지스터·클록·리셋 등 플랫폼 자원을 먼저 준비해야 합니다. 이 컨트롤러를 제어하는 코드가 PCI 장치 ID로 발견되는 일반 endpoint 사용 드라이버와 같다고 보면 안 됩니다. 구체적인 bus와 probe 경로는 해당 컨트롤러 구현을 확인합니다.

Linux PCI Endpoint Framework는 EPC(Endpoint Controller), EPF(Endpoint Function), 둘을 연결하는 configfs 계층을 구분합니다. EPC는 BAR 설정·주소 매핑·interrupt 발생 등 컨트롤러 기능을 제공하고, EPF는 호스트에 제공할 function을 구현합니다. configfs 바인딩은 그 둘을 연결하는 작업입니다. 호스트 드라이버의 probe(), endpoint 쪽 EPC 등록, EPF 바인딩을 하나의 probe 단계로 합치지 않습니다.

Linux PCI Endpoint Framework는 이 역할과 API를 구분합니다. RC/EP 전환 지원 여부는 SoC·IP·보드 구성에 따라 다르며, 모든 controller가 두 모드를 제공한다는 뜻은 아닙니다.

English summary. The host PCI function driver consumes an enumerated device. On an endpoint system, the EPC controls hardware while the EPF implements a function; configfs binds them. Controller discovery must be checked in its own platform implementation.