PCI / PCIe 서브시스템

PCI/PCIe 서브시스템을 하드웨어 링크 계층부터 Linux 커널 드라이버 통합 지점까지 end-to-end로 정리합니다. config space/BAR/MSI-X 같은 기본 메커니즘, 열거와 리소스 할당, IOMMU 연계 DMA 경로, AER 오류 보고와 복구, ASPM/D-state 전원관리, SR-IOV 및 VF 분리 운용, hotplug와 리셋 플로우, sysfs/lspci/setpci/tracepoint 기반 문제 분석까지 서버와 임베디드 환경 모두에서 필요한 실전 포인트를 다룹니다.

전제 조건: 디바이스 드라이버인터럽트 문서를 먼저 읽으세요. 버스/열거/프로브 경로는 초기화 순서와 자원 등록 규칙이 핵심이므로, 장치 발견부터 바인딩까지 흐름을 먼저 고정해야 합니다.
일상 비유: 이 주제는 터미널 입출고 게이트 운영과 비슷합니다. 차량(디바이스)이 들어오면 게이트 규칙(버스 규약)에 맞춰 배정하고 점검하듯이, 드라이버도 바인딩 규약을 정확히 따라야 합니다.

핵심 요약

  • 초기화 순서 — 탐색, 바인딩, 자원 등록 순서를 점검합니다.
  • 제어/데이터 분리 — 빠른 경로와 설정 경로를 분리 설계합니다.
  • IRQ/작업 분할 — 즉시 처리와 지연 처리를 구분합니다.
  • 안전 한계 — 전원/열/타이밍 임계값을 함께 관리합니다.
  • 운영 복구 — 오류 시 재초기화와 롤백 경로를 준비합니다.

단계별 이해

  1. 장치 수명주기 확인
    probe부터 remove까지 흐름을 점검합니다.
  2. 비동기 경로 설계
    IRQ, 워크큐, 타이머 역할을 분리합니다.
  3. 자원 정합성 검증
    DMA/클록/전원 참조를 교차 확인합니다.
  4. 현장 조건 테스트
    연결 끊김/복구/부하 상황을 재현합니다.
관련 표준: PCI Local Bus 3.0 (설정 공간, BAR), PCIe Base Specification 6.0 (직렬 인터커넥트, TLP), CXL 3.1 (캐시 일관성) — PCI/PCIe 서브시스템이 구현하는 버스 아키텍처 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

PCI/PCIe 개요

PCI (Peripheral Component Interconnect)는 1992년 Intel이 제안한 로컬 버스 규격으로, 이후 PCI Express (PCIe)로 진화하며 현대 시스템의 사실상 표준 인터커넥트가 되었습니다. Linux 커널은 PCI 서브시스템을 통해 디바이스 열거(enumeration), 리소스 할당, 드라이버 바인딩, 전원 관리를 통합적으로 처리합니다.

규격연도토폴로지최대 대역폭 (단방향)
PCI 2.x1993공유 병렬 버스 (32/64-bit)133 / 533 MB/s
PCI-X 2.02003공유 병렬 버스 (64-bit)4.3 GB/s (DDR 533)
PCIe 1.02003Point-to-point 직렬250 MB/s × lane
PCIe 2.02007Point-to-point 직렬500 MB/s × lane
PCIe 3.02010Point-to-point 직렬~1 GB/s × lane
PCIe 4.02017Point-to-point 직렬~2 GB/s × lane
PCIe 5.02019Point-to-point 직렬~4 GB/s × lane
PCIe 6.02022Point-to-point 직렬~8 GB/s × lane (PAM4)
PCIe x16 슬롯 대역폭: PCIe 5.0 x16 기준 단방향 약 64 GB/s, 양방향 128 GB/s. GPU, 고성능 NVMe, 네트워크 카드 등이 이 대역폭을 활용합니다.

PCIe 아키텍처

토폴로지

PCIe는 기존 PCI의 공유 병렬 버스를 point-to-point 직렬 링크로 대체합니다. 트리 구조의 핵심 구성 요소:

구성 요소역할Config Header
Root Complex (RC)CPU/메모리 ↔ PCIe 패브릭 연결, 최상위 노드Type 0/1
Switch업스트림 포트 1개 + 다운스트림 포트 N개, 패킷 라우팅Type 1 (Bridge)
EndpointNVMe, NIC, GPU 등 최종 디바이스Type 0
BridgePCI/PCI-X ↔ PCIe 변환Type 1

PCIe 계층 구조

PCIe는 네트워크 프로토콜과 유사한 3계층 모델을 사용합니다:

계층역할주요 단위
Transaction Layer읽기/쓰기 요청, Completion, 메시지 생성TLP (Transaction Layer Packet)
Data Link Layer시퀀스 번호, CRC, ACK/NAK 기반 신뢰성 보장DLLP (Data Link Layer Packet)
Physical Layer전기 신호, 인코딩 (8b/10b, 128b/130b, PAM4), 레인 본딩Ordered Set

TLP (Transaction Layer Packet)

TLP는 PCIe 통신의 핵심 단위입니다. 주요 TLP 타입:

TLP 타입약어용도
Memory ReadMRdMMIO 읽기, DMA 읽기
Memory WriteMWrMMIO 쓰기, DMA 쓰기
Configuration Read/WriteCfgRd/CfgWrConfiguration Space 접근
I/O Read/WriteIORd/IOWr레거시 I/O 포트 접근
CompletionCpl/CplD읽기 요청에 대한 응답 (데이터 포함)
MessageMsg/MsgD인터럽트(MSI), 전원, 에러 시그널링
Posted vs Non-Posted: Memory Write는 Posted(완료 확인 불필요)이고, Memory Read와 Configuration Read/Write는 Non-Posted(Completion 필요)입니다. Posted 트랜잭션은 레이턴시를 줄이지만, 에러 보고가 지연될 수 있습니다.

TLP 헤더 포맷 상세

TLP 헤더는 3 DW (12 바이트) 또는 4 DW (16 바이트)로 구성됩니다. 첫 번째 DW의 Fmt[2:0] 필드가 헤더 길이와 데이터 유무를, Type[4:0] 필드가 트랜잭션 종류를 결정합니다:

TLP (Transaction Layer Packet) 구조 TLP 전체 구조 Header (3/4 DW) Data Payload ECRC (opt) ← Transaction Layer Seq# + LCRC ← DLL Framing ← PHY 3 DW 헤더 (Memory/IO/Config — 32-bit 주소) DW 0: Fmt | Type | TC | TD | EP | Attr | Length DW 1: Requester ID | Tag | LastBE | FirstBE DW 2: Address[31:2] | R DW 0 비트 레이아웃 (MSB → LSB) 비트 [31:29] Fmt — 000: 3DW no data, 001: 4DW no data, 010: 3DW+data, 011: 4DW+data 비트 [28:24] Type — 00000: MRd, 00000+Fmt: MWr, 00100: CfgRd0, 10000: Msg 비트 [22:20] TC — Traffic Class (0~7), VC와 매핑되어 QoS 우선순위 결정 비트 [15] TD — TLP Digest (ECRC) 존재 여부, [14] EP — Error Poisoned 비트 [13:12] Attr — Relaxed Ordering [1], No Snoop [0], IDO [2] 4 DW 헤더 (Memory — 64-bit 주소) DW 0: Fmt|Type|... DW 1: ReqID|Tag|BE DW 2: Addr[63:32] DW 3: Addr[31:2]|R MPS / MRRS — TLP 크기 제어 • MPS (Max Payload Size): TLP 하나에 실을 수 있는 최대 데이터 크기 (128/256/512/1024/2048/4096 B) → Device Control 레지스터 [7:5]에 설정, 링크 양단의 최솟값으로 결정 (커널: pci_set_mps()) • MRRS (Max Read Request Size): Non-Posted Read 요청의 최대 크기 (128~4096 B) → Device Control 레지스터 [14:12]에 설정, MPS와 독립적 (커널: pci_set_mrrs()) • MPS가 너무 작으면 DMA 효율 저하 (헤더 오버헤드 증가), 너무 크면 latency 증가 • 리눅스 커널: CONFIG_PCIE_BUS_DEFAULT / CONFIG_PCIE_BUS_SAFE / CONFIG_PCIE_BUS_PERFORMANCE / pci=pcie_bus_safe
Fmt[2:0]의미헤더데이터예시
0003DW, no data12B없음MRd (32-bit), CfgRd, IORd
0014DW, no data16B없음MRd (64-bit)
0103DW, with data12B있음MWr (32-bit), CfgWr, IOWr
0114DW, with data16B있음MWr (64-bit)
100TLP PrefixPCIe 3.0+ TLP Prefix
TLP Prefix (PCIe 3.0+): TLP 앞에 추가되는 4바이트 접두사로, End-to-End (EE) 또는 Local 타입이 있습니다. PASID (Process Address Space ID), IDE (Integrity and Data Encryption), DOE (Data Object Exchange) 등의 확장 기능에 사용됩니다. Fmt[2:0] = 100b로 식별하며, 최대 4개까지 연결 가능합니다.

Requester ID / Completer ID

TLP 라우팅의 핵심 식별자는 16비트 BDF (Bus:Device:Function)입니다:

필드비트설명
Bus Number[15:8]버스 번호 (0~255)
Device Number[7:3]디바이스 번호 (0~31)
Function Number[2:0]펑션 번호 (0~7)

크레딧 기반 흐름 제어 (Flow Control)

PCIe는 수신 측 버퍼 오버플로우를 방지하기 위해 크레딧 기반 흐름 제어를 사용합니다. 송신 측은 수신 측에서 통지한 크레딧만큼만 TLP를 전송할 수 있으며, 수신 측이 버퍼를 비운 후 UpdateFC DLLP로 크레딧을 반환합니다.

PCIe 크레딧 기반 흐름 제어 송신 측 (Transmitter) Credit Counter 남은 크레딧 추적 TLP 전송 시 크레딧 차감 크레딧 = 0 이면 전송 중단 (Stall) UpdateFC 대기 수신 측 (Receiver) Receive Buffer TLP 임시 저장 버퍼 소비 후 크레딧 반환 준비 초기 크레딧 광고 InitFC1/InitFC2 DLLP TLP 전송 → ← UpdateFC DLLP (크레딧 반환) 6가지 크레딧 타입 ■ Posted Header (PH) ■ Posted Data (PD) ■ Non-Posted Header (NPH) ■ Non-Posted Data (NPD) ■ Completion Header (CplH) ■ Completion Data (CplD)
크레딧 타입헤더 단위데이터 단위설명
Posted (P)1 크레딧 = 1 TLP 헤더 슬롯1 크레딧 = 16 바이트 (4 DW)MWr — 완료 응답 불필요, 크레딧만 있으면 즉시 전송
Non-Posted (NP)1 크레딧 = 1 TLP 헤더 슬롯1 크레딧 = 16 바이트MRd, CfgRd/Wr — Completion 대기, 크레딧 + outstanding request 제한
Completion (Cpl)1 크레딧 = 1 TLP 헤더 슬롯1 크레딧 = 16 바이트CplD — 무한 크레딧(infinite credits) 가능 (송신 측이 이미 NP 크레딧으로 조절됨)
크레딧 부족 (Credit Starvation): 수신 측 버퍼가 느리게 비워지면 송신 측이 크레딧 부족으로 전송을 멈춥니다. 이는 PCIe 대역폭 저하의 주요 원인 중 하나입니다. lspci -vvvDevSta: CorrErr+가 빈번하다면 흐름 제어 문제를 의심할 수 있습니다. 특히 NP 크레딧 부족은 MMIO 읽기(uncacheable) 지연으로 이어지며, 드라이버에서 readl() 호출이 수 마이크로초 이상 블로킹될 수 있습니다.

가상 채널과 트래픽 클래스 (VC/TC)

PCIe는 QoS 지원을 위해 TC (Traffic Class, 0~7)VC (Virtual Channel, 0~7)를 정의합니다. TC-to-VC 매핑은 VC Capability에서 설정하며, 각 VC는 독립적인 크레딧과 버퍼를 가집니다:

TC기본 VC 매핑용도
0VC0일반 트래픽 (기본, 모든 디바이스 필수 지원)
1~7VC1~VC7 (선택)실시간 오디오/비디오, 등시성(Isochronous) 전송 등
실전: 대부분의 디바이스는 TC0/VC0만 사용합니다. VC1+는 실시간 A/V 스트리밍이나 특수 산업용 애플리케이션에서 사용되며, 리눅스 커널에서는 pci_enable_vc()로 추가 VC를 활성화할 수 있지만, 일반적인 서버/데스크탑 환경에서는 거의 사용되지 않습니다.

Data Link Layer — DLLP와 ACK/NAK

Data Link Layer는 TLP의 신뢰성 보장을 담당합니다. 모든 TLP에 시퀀스 번호와 LCRC를 추가하고, ACK/NAK 프로토콜로 전송 오류를 복구합니다:

DLLP 타입용도설명
AckTLP 수신 확인정상 수신된 TLP의 시퀀스 번호를 확인, 송신 측 replay 버퍼 해제
NakTLP 재전송 요청CRC 에러 감지 시 Nak 전송, 송신 측은 replay 버퍼에서 재전송
InitFC1/InitFC2초기 크레딧 교환링크 초기화 시 수신 버퍼 크기를 상대에게 광고
UpdateFC크레딧 갱신수신 버퍼 소비 후 사용 가능한 크레딧을 송신 측에 통지
PM (Power Mgmt)전원 관리PM_Enter_L1, PM_Request_Ack 등 ASPM 상태 전환
Vendor Specific벤더 확장벤더 정의 DLLP
Replay Buffer: 송신 측은 ACK를 받기 전까지 전송된 TLP를 replay 버퍼에 보관합니다. NAK 수신 시 해당 시퀀스 번호부터 재전송합니다. Replay 버퍼 크기는 링크 레이턴시와 대역폭에 비례하여, Gen5 x16에서는 수십 KB에 달할 수 있습니다. Replay가 빈번하면 AER의 Correctable Error 카운터(Bad TLP, Bad DLLP, Replay Number Rollover)가 증가합니다.

LTSSM (Link Training and Status State Machine)

PCIe 물리 계층의 링크 초기화와 속도 협상은 LTSSM이 관리합니다. 링크 업부터 전원 절약 상태까지 모든 전환을 11개 주요 상태로 모델링합니다:

LTSSM — Link Training and Status State Machine Detect Polling Configuration L0 (Active) Recovery L0s L1 L2/L3 Hot Reset Disabled LTSSM 주요 상태 설명 ■ Detect — 전기적 존재 감지 (Receiver Detect). 링크 파트너 유무 확인 ■ Polling — TS1/TS2 Ordered Set 교환. 비트 록, 심볼 록, 레인 폴라리티 확인 ■ Configuration — 링크/레인 번호 협상, 레인 반전(lane reversal) 처리, N_FTS 교환 ■ L0 (Active) — 정상 동작 상태. TLP/DLLP 전송 가능. 최대 성능 ■ Recovery — 비트 록 재동기화, 속도 변경(Gen1↔Gen5), 등화 재수행 ■ L0s — 저전력 대기 (레인별 독립). 복귀 시간 ~1 µs. ASPM의 가장 가벼운 절전 ■ L1 — 저전력 (링크 전체). 복귀 시간 2~64 µs. PLL 정지 가능. L1.1/L1.2 서브스테이트 지원 ■ L2/L3 — 보조 전원(Vaux)만 유지 (L2) 또는 완전 전원 차단 (L3). PME 웨이크업으로 복귀 ■ Hot Reset — 다운스트림 디바이스 리셋. TS1에 Hot Reset 비트 설정, Config Space 초기화 ■ Disabled — 링크 비활성화. 소프트웨어가 Link Disable 비트를 설정하거나 하드웨어 오류 속도 변경 (Speed Change): L0 → Recovery → (속도 협상, EQ 재수행) → L0. Gen3 이상에서는 Recovery.Equalization 서브스테이트에서 송신/수신 등화(TX preset, RX hint) 협상 후 최적 속도를 결정합니다. 커널에서 LTSSM 관찰: lspci -vvv | grep "LnkSta:" → 현재 속도/폭 | dmesg | grep "PCIe link" → 훈련 결과 | AER 로그 → 링크 재훈련 빈도

PCIe 등화 (Equalization) — Gen3+

PCIe Gen3 (8 GT/s) 이상에서는 고속 신호의 ISI(Inter-Symbol Interference)를 보상하기 위해 송수신 등화(Equalization)가 필수입니다:

등화 단계Phase설명
TX De-emphasisPhase 0/1송신 측이 11개 프리셋(P0~P10) 중 하나를 선택하여 고주파 신호를 강조. 커서 전/후 비율 조정
RX AdaptationPhase 2/3수신 측이 CTLE(Continuous Time Linear Equalizer)/DFE(Decision Feedback Equalizer)를 조정하여 eye 다이어그램 최적화
Speed Change등화 완료 후 Recovery → L0로 전환, 협상된 최고 속도로 동작 시작
등화 실패: 등화가 수렴하지 않으면 링크 속도가 Gen3 이하로 강등(downgrade)됩니다. lspci -vvv에서 LnkCap: Speed 16GT/s인데 LnkSta: Speed 8GT/s이면 등화 실패를 의심하세요. 물리적 원인(케이블 품질, PCB 트레이스 길이, 커넥터 접촉)이 대부분이며, 커널 파라미터 pci=noaer로 AER를 비활성화하면 등화 재시도 로그가 사라져 근본 원인을 놓칠 수 있으므로 주의가 필요합니다.

PCIe 세대별 인코딩과 Gen6 FLIT 모드

세대전송률인코딩인코딩 오버헤드유효 대역폭 (×1)유효 대역폭 (×16)
Gen12.5 GT/s8b/10b20%250 MB/s4 GB/s
Gen25 GT/s8b/10b20%500 MB/s8 GB/s
Gen38 GT/s128b/130b~1.5%~985 MB/s~15.75 GB/s
Gen416 GT/s128b/130b~1.5%~1.97 GB/s~31.5 GB/s
Gen532 GT/s128b/130b~1.5%~3.94 GB/s~63 GB/s
Gen664 GT/sPAM4 + 1b/1b (FLIT)~3% (FEC + FLIT)~7.56 GB/s~121 GB/s
Gen7128 GT/sPAM4 + FLIT~3%~15.1 GB/s~242 GB/s
Gen6 FLIT 모드: PCIe 6.0은 기존 바이트 스트림 대신 FLIT (Flow Control Unit) 기반 전송으로 전환합니다.
  • FLIT 크기: 256 바이트 고정. 여러 TLP를 하나의 FLIT에 패킹 가능 (작은 TLP의 효율 향상)
  • PAM4 (Pulse Amplitude Modulation 4-level): NRZ(2레벨) 대신 4레벨 신호로 레인당 2비트/심볼 전송. 동일 주파수에서 2배 대역폭
  • FEC (Forward Error Correction): PAM4의 낮은 SNR을 보완하기 위해 필수. CRC-3 + Reed-Solomon 같은 FEC 코드 사용
  • L0p (Low Power): Gen6 전용 저전력 상태. 레인 수를 동적으로 줄여 (예: x16 → x8 → x4) 유휴 시 전력 절약. 기존 ASPM L0s보다 세밀한 제어
  • CXL 3.0과의 관계: CXL 3.0은 PCIe 6.0의 256B FLIT과 별도의 CXL 68B FLIT 모두 지원하여 코히런시 프로토콜과 I/O를 다중화

Configuration Space

접근 방식

PCI Configuration Space에 접근하는 두 가지 메커니즘:

방식포트 / 메모리공간 크기설명
I/O Port (CAM)0xCF8 (addr) / 0xCFC (data)256 바이트레거시 PCI, Bus/Dev/Func 인코딩
MMIO (ECAM)MCFG ACPI 테이블로 베이스 주소 결정4 KB (PCIe)PCIe 확장 Config Space (4096 바이트)
/* I/O 포트 방식 Configuration Space 접근 (레거시 PCI) */
/* CONFIG_ADDRESS (0xCF8):
 *   [31]    Enable bit
 *   [23:16] Bus number
 *   [15:11] Device number
 *   [10:8]  Function number
 *   [7:2]   Register number (DWORD-aligned)
 *   [1:0]   항상 0
 */
static u32 pci_conf1_read(u8 bus, u8 dev, u8 func, u8 reg)
{
    u32 addr = (1 << 31) |
              ((u32)bus << 16) |
              ((u32)dev << 11) |
              ((u32)func << 8) |
              (reg & 0xFC);
    outl(addr, 0xCF8);
    return inl(0xCFC);
}

/* ECAM (Enhanced Configuration Access Mechanism)
 * MMIO 베이스 + (Bus << 20 | Dev << 15 | Func << 12 | Reg)
 * 각 Function에 4 KB 매핑 → 4096 바이트 Config Space 전체 접근 */

Configuration Header 구조

모든 PCI/PCIe 디바이스는 Configuration Header (처음 64 바이트)를 가집니다:

OffsetSizeField
00h2Vendor ID
02h2Device ID
04h2Command
06h2Status
08h1Revision ID
09h3Class Code (PI + Sub + Base)
0Ch1Cache Line Size
0Dh1Latency Timer
0Eh1Header Type (00h=Endpoint, 01h=Bridge)
0Fh1BIST
10h-27h24BAR 0-5 (Type 0) 또는 BAR 0-1 + Bridge 정보 (Type 1)
28h4CardBus CIS Pointer / Subsystem IDs
2Ch4Subsystem Vendor ID / Subsystem ID
30h4Expansion ROM Base Address
34h1Capabilities Pointer (Capability 연결 리스트 시작)
3Ch4Interrupt Line / Pin / Min_Gnt / Max_Lat

Command 레지스터 (오프셋 04h)

드라이버가 디바이스를 제어하는 핵심 레지스터:

비트이름설명
0I/O SpaceI/O BAR 접근 활성화
1Memory SpaceMemory BAR 접근 활성화
2Bus MasterDMA(버스 마스터링) 활성화
6Parity Error Response패리티 에러 보고
8SERR# Enable시스템 에러 보고 활성화
10INTx Disable레거시 INTx 인터럽트 비활성화 (MSI 사용 시)

Capability 구조

오프셋 34h의 Capabilities Pointer부터 시작하는 연결 리스트(linked list)로, 각 Capability는 ID + Next Pointer + 데이터로 구성됩니다:

Cap ID이름설명
01hPower ManagementD0/D1/D2/D3hot 상태 전환
05hMSIMessage Signaled Interrupt
10hPCIePCIe Capability (Link/Slot 정보)
11hMSI-X확장 MSI (벡터 테이블 기반)

PCIe 4 KB 확장 공간(256~4095)에는 Extended Capabilities가 위치합니다:

Ext Cap ID이름설명
0001hAERAdvanced Error Reporting
0003hSerial Number디바이스 고유 일련번호
000DhACSAccess Control Services (IOMMU 격리)
0010hSR-IOVSingle Root I/O Virtualization
001EhL1 PM SubstatesL1 세부 전원 절약 상태
0023hDOEData Object Exchange (CXL 등)

BAR (Base Address Registers)

BAR은 디바이스가 CPU에 노출하는 메모리/I/O 영역의 시작 주소를 정의합니다. Endpoint(Type 0)는 최대 6개, Bridge(Type 1)는 2개의 BAR을 가집니다.

32-bit Memory BAR 비트 필드 [31:n] Base Address [n-1:4] Reserved [3] P [2:0] Type/Space P(비트 3): Prefetchable, Type(비트 2:1): 00=32-bit, 10=64-bit, Space(비트 0): 0=Memory, 1=I/O 64-bit Memory BAR 배치 BAR[i] (하위 32비트) BAR[i+1] (상위 32비트)

BAR 크기 결정 알고리즘

BAR 크기는 다음 절차로 결정됩니다. 이는 PCI 스펙에 정의된 표준 메커니즘입니다:

BAR 크기 결정 — 비트 패턴 예시 (1 MB Memory BAR) Step 1: 원래 BAR 값 저장 후, 0xFFFFFFFF 쓰기 pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &orig); // orig = 0xC0100004 pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, 0xFFFFFFFF); Step 2: BAR 읽기 — 디바이스가 디코딩하는 비트만 1 유지 pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &val); // val = 0xFFF00004 ^^^^^ = 주소 비트 (1 유지) ^^^^^ = 크기 비트 (0으로 고정) + 타입 비트 Step 3: 타입 비트 마스킹 → 반전 → +1 = 크기 masked = val & PCI_BASE_ADDRESS_MEM_MASK; // 0xFFF00000 (하위 4비트 제거) size = ~masked + 1; // ~0xFFF00000 + 1 = 0x00100000 = 1 MB → BAR 크기 = 1 MB (0x100000) Step 4: 원래 BAR 값 복원 pci_write_config_dword(pdev, PCI_BASE_ADDRESS_0, orig); 핵심 원리 BAR의 하위 비트 중 0으로 남는 비트 수가 정렬(alignment)을 결정하며, 이것이 곧 BAR 크기입니다. 예: 하위 20비트가 0 → 2^20 = 1 MB. PCI 스펙은 BAR 크기가 항상 2의 거듭제곱이고 자연 정렬됨을 보장합니다.
/* 커널 내부: BAR 크기 결정 (drivers/pci/probe.c — 간략화) */
static u64 pci_size(u64 base, u64 maxbase, u64 mask)
{
    u64 size = mask & maxbase;  /* 타입 비트 마스킹 */
    if (!size)
        return 0;
    /* 1의 보수 + 1 = 2의 보수 = 크기
     * 예: size = 0xFFF00000 → ~size + 1 = 0x00100000 = 1 MB */
    size = (size & ~(size - 1)) - 1;  /* 최하위 set 비트 ~ 위 모두 포함 */
    if (base == maxbase && ((base | (size - 1)) & mask) != mask)
        return 0;
    return size + 1;
}

BAR 유형과 속성

BAR 유형비트 [2:1]비트 [3]비트 [0]설명
32-bit MMIO000/104 GB 이하 주소. 단일 BAR 사용
64-bit MMIO100/10연속 2개 BAR 사용 (BAR[i] + BAR[i+1]). 4 GB 이상 주소 가능
I/O Port1I/O 포트 주소. 최대 64 KB. 레거시, PCIe에서는 비권장
Prefetchable vs Non-Prefetchable:
  • Prefetchable (P=1): 읽기 시 부작용(side-effect)이 없는 영역. CPU가 투기적 읽기(speculative read)와 쓰기 결합(write combining)을 수행할 수 있습니다. 프레임버퍼, DMA 버퍼 등에 사용. Bridge 윈도우에서 별도 prefetchable 윈도우로 라우팅됩니다.
  • Non-Prefetchable (P=0): 읽기 시 부작용이 있을 수 있는 영역 (예: 상태 레지스터 읽기가 값을 변경). 반드시 요청한 크기와 주소 그대로 접근해야 합니다. 디바이스 레지스터 영역에 사용.
  • 주의: Non-Prefetchable BAR은 32-bit Bridge 윈도우만 통과할 수 있어, 4 GB 이상 주소에 배치가 제한될 수 있습니다. 이는 대용량 메모리 시스템에서 BAR 할당 실패의 원인이 됩니다.
/* 커널에서 BAR 정보 접근 */
struct pci_dev *pdev;

/* BAR의 물리 시작 주소 */
resource_size_t bar_start = pci_resource_start(pdev, 0);

/* BAR 영역 크기 */
resource_size_t bar_len = pci_resource_len(pdev, 0);

/* BAR 플래그 (IORESOURCE_MEM, IORESOURCE_IO 등) */
unsigned long bar_flags = pci_resource_flags(pdev, 0);

/* MMIO BAR을 가상 주소로 매핑 */
void __iomem *regs = pci_iomap(pdev, 0, bar_len);
if (!regs)
    return -ENOMEM;

/* MMIO 읽기/쓰기 */
u32 val = ioread32(regs + 0x10);
iowrite32(0x1, regs + 0x14);

/* 해제 */
pci_iounmap(pdev, regs);

Linux 커널 PCI 서브시스템

핵심 자료구조

구조체헤더역할
struct pci_dev<linux/pci.h>PCI 디바이스 인스턴스 (vendor/device, BARs, IRQ 등)
struct pci_driver<linux/pci.h>PCI 드라이버 (probe/remove, ID 테이블)
struct pci_bus<linux/pci.h>PCI 버스 (하위 디바이스 리스트, 브릿지 정보)
struct pci_host_bridge<linux/pci.h>Host Bridge (Root Complex 대응)
struct resource<linux/ioport.h>I/O, MMIO, IRQ 리소스 추상화

struct pci_dev 주요 필드

struct pci_dev {
    struct pci_bus  *bus;             /* 디바이스가 연결된 버스 */
    unsigned int    devfn;            /* Device + Function 번호 (8비트) */
    unsigned short  vendor, device;   /* Vendor ID, Device ID */
    unsigned short  subsystem_vendor; /* Subsystem Vendor ID */
    unsigned short  subsystem_device; /* Subsystem Device ID */
    unsigned int    class;            /* Class Code (24비트) */
    u8              revision;         /* Revision ID */

    struct resource resource[PCI_NUM_RESOURCES]; /* BAR 리소스 */
    unsigned int    irq;              /* 할당된 IRQ 번호 */

    pci_power_t     current_state;    /* D0, D1, D2, D3hot, D3cold */
    unsigned int    is_busmaster:1;   /* Bus Master 활성화 여부 */
    unsigned int    msi_enabled:1;    /* MSI 활성화 여부 */
    unsigned int    msix_enabled:1;   /* MSI-X 활성화 여부 */

    struct pci_driver *driver;       /* 바인딩된 드라이버 */
    struct device    dev;             /* 범용 디바이스 모델 */
    /* ... */
};

PCI 열거 (Enumeration)

커널 부팅 시 PCI 서브시스템은 다음 순서로 디바이스를 발견합니다:

  1. ACPI/DT에서 Host Bridge 정보 수집 — MCFG 테이블(ECAM 베이스), _CRS(리소스 윈도우)
  2. Bus 0부터 재귀적 스캔 — 각 Bus/Device/Function에 대해 Vendor ID 읽기 (0xFFFF이면 존재하지 않음)
  3. Bridge(Type 1) 발견 시 — Secondary/Subordinate Bus Number 할당 후 하위 버스 재귀 탐색
  4. BAR 크기 결정 및 리소스 할당 — 커널의 리소스 관리자가 주소 범위 배정
  5. 드라이버 매칭pci_device_id 테이블 기반으로 드라이버 probe() 호출
/* 커널 내부: PCI 디바이스 스캔 핵심 경로 (simplified) */
/* drivers/pci/probe.c */

struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
{
    struct pci_dev *dev;

    dev = pci_get_slot(bus, devfn);
    if (dev)
        return dev;  /* 이미 스캔됨 */

    dev = pci_scan_device(bus, devfn);
    if (!dev)
        return NULL;

    pci_device_add(dev, bus);
    return dev;
}

PCI 드라이버 작성

드라이버 골격

#include <linux/module.h>
#include <linux/pci.h>

#define MY_VENDOR_ID  0x1234
#define MY_DEVICE_ID  0x5678

struct my_priv {
    void __iomem *regs;
    /* 디바이스별 private 데이터 */
};

static int my_probe(struct pci_dev *pdev,
                    const struct pci_device_id *id)
{
    struct my_priv *priv;
    int err;

    /* 1. PCI 디바이스 활성화 */
    err = pcim_enable_device(pdev);
    if (err)
        return err;

    /* 2. BAR 리소스 요청 (managed) */
    err = pcim_iomap_regions(pdev, BIT(0), KBUILD_MODNAME);
    if (err)
        return err;

    /* 3. private 데이터 할당 */
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    /* 4. BAR 0 매핑 주소 획득 */
    priv->regs = pcim_iomap_table(pdev)[0];

    /* 5. DMA 마스크 설정 */
    err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
    if (err)
        return err;

    /* 6. Bus Master 활성화 (DMA 사용 시 필수) */
    pci_set_master(pdev);

    /* 7. MSI-X 인터럽트 설정 */
    err = pci_alloc_irq_vectors(pdev, 1, 16, PCI_IRQ_MSIX | PCI_IRQ_MSI);
    if (err < 0)
        return err;

    pci_set_drvdata(pdev, priv);

    dev_info(&pdev->dev, "probed successfully\\n");
    return 0;
}

static void my_remove(struct pci_dev *pdev)
{
    /* pcim_* / devm_* 사용 시 자동 정리 — 명시적 해제 불필요 */
    pci_free_irq_vectors(pdev);
    dev_info(&pdev->dev, "removed\\n");
}

/* PCI Device ID 테이블 — 드라이버가 지원하는 디바이스 목록 */
static const struct pci_device_id my_pci_ids[] = {
    { PCI_DEVICE(MY_VENDOR_ID, MY_DEVICE_ID) },
    { PCI_DEVICE_CLASS(PCI_CLASS_NETWORK_ETHERNET << 8, 0xFFFF00) },
    { 0, }  /* 종료 마커 */
};
MODULE_DEVICE_TABLE(pci, my_pci_ids);

static struct pci_driver my_driver = {
    .name     = KBUILD_MODNAME,
    .id_table = my_pci_ids,
    .probe    = my_probe,
    .remove   = my_remove,
};
module_pci_driver(my_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example PCI driver");

Managed API (pcim_* / devm_*)

커널은 Managed Device Resource API를 제공하여 드라이버 해제 시 자동 정리합니다:

Managed API수동 API용도
pcim_enable_device()pci_enable_device()디바이스 활성화
pcim_iomap_regions()pci_request_regions() + pci_iomap()BAR 리소스 요청 + MMIO 매핑
devm_kzalloc()kzalloc() + kfree()메모리 할당
devm_request_irq()request_irq() + free_irq()인터럽트 핸들러 등록
dmam_alloc_coherent()dma_alloc_coherent() + dma_free_coherent()DMA 버퍼 할당
Managed API 권장: pcim_* / devm_* API를 사용하면 에러 경로에서의 리소스 누수를 방지하고 remove() 함수를 간소화할 수 있습니다. 새 드라이버에서는 항상 managed API를 우선 사용하세요.

PCI ID 매칭 매크로

매크로매칭 기준
PCI_DEVICE(vendor, device)Vendor + Device ID
PCI_DEVICE_SUB(vendor, device, subvendor, subdevice)+ Subsystem IDs
PCI_DEVICE_CLASS(class, class_mask)Class Code 기반 매칭
PCI_VDEVICE(vendor, device)PCI_DEVICE 축약 (벤더 prefix 자동)
PCI_ANY_ID와일드카드 (모든 값 매칭)

DMA 매핑

심화 학습: DMA 매핑 API 전체, IOMMU 심화(VT-d/AMD-Vi/SMMU), SWIOTLB, CMA, DMA-BUF, P2P DMA, 캐시 일관성, 보안에 대한 종합 가이드는 DMA 심화 페이지를 참조하십시오.

PCI 디바이스가 시스템 메모리에 직접 접근하려면 DMA (Direct Memory Access)를 사용합니다. 커널은 두 가지 DMA 매핑 모델을 제공합니다:

Coherent (Consistent) DMA

CPU와 디바이스가 동시에 접근하는 공유 메모리에 적합합니다. 캐시 코히런시가 하드웨어적으로 보장됩니다.

dma_addr_t dma_handle;
void *cpu_addr;
size_t size = 4096;

/* 할당: CPU 가상 주소 + DMA 버스 주소 반환 */
cpu_addr = dma_alloc_coherent(&pdev->dev, size, &dma_handle, GFP_KERNEL);
if (!cpu_addr)
    return -ENOMEM;

/* 디바이스에 DMA 주소 전달 (레지스터에 기록) */
iowrite32(lower_32_bits(dma_handle), regs + 0x20);
iowrite32(upper_32_bits(dma_handle), regs + 0x24);

/* 해제 */
dma_free_coherent(&pdev->dev, size, cpu_addr, dma_handle);

Streaming DMA

일시적 단방향 전송에 적합합니다. 매핑 전후 명시적 캐시 동기화가 필요합니다.

dma_addr_t dma_handle;
void *buf = kmalloc(4096, GFP_KERNEL);

/* 매핑: CPU 버퍼 → DMA 주소 */
dma_handle = dma_map_single(&pdev->dev, buf, 4096, DMA_TO_DEVICE);
if (dma_mapping_error(&pdev->dev, dma_handle)) {
    kfree(buf);
    return -EIO;
}

/* 디바이스가 DMA 전송 수행... */

/* 언매핑 (반드시 전송 완료 후) */
dma_unmap_single(&pdev->dev, dma_handle, 4096, DMA_TO_DEVICE);
kfree(buf);

DMA 방향

상수의미
DMA_TO_DEVICECPU → 디바이스 (TX)
DMA_FROM_DEVICE디바이스 → CPU (RX)
DMA_BIDIRECTIONAL양방향
DMA_NONE디버깅용

Scatter-Gather DMA

물리적으로 불연속적인 메모리 페이지를 하나의 DMA 전송으로 처리합니다:

struct scatterlist sg[MAX_SG];
int nents, mapped;

sg_init_table(sg, MAX_SG);
/* sg 엔트리에 페이지/오프셋/길이 설정 */
sg_set_page(&sg[0], page0, 4096, 0);
sg_set_page(&sg[1], page1, 4096, 0);

/* 매핑: IOMMU가 있으면 연속 DMA 주소로 합칠 수 있음 */
mapped = dma_map_sg(&pdev->dev, sg, nents, DMA_FROM_DEVICE);

/* 언매핑 */
dma_unmap_sg(&pdev->dev, sg, nents, DMA_FROM_DEVICE);

IOMMU / DMA Remapping

IOMMU(Intel VT-d, AMD-Vi)는 디바이스의 DMA 주소를 물리 주소로 변환하는 하드웨어입니다:

기능설명
주소 변환디바이스별 페이지 테이블로 DMA 접근 범위 제한
격리디바이스가 허가되지 않은 메모리에 접근하는 것을 차단
SG 합치기불연속 물리 페이지를 연속 DMA 주소로 매핑
가상화게스트 VM에 디바이스 직접 할당 (VFIO passthrough)
커널 부팅 파라미터:
  intel_iommu=on        # Intel VT-d 활성화
  iommu=pt              # Passthrough 모드 (성능 우선, 격리 최소)
  iommu.strict=1        # Strict 모드 (보안 우선, 즉시 IOTLB 무효화)

MSI / MSI-X 인터럽트

PCI 레거시 인터럽트(INTA#~INTD#)는 공유 인터럽트 라인 문제가 있습니다. MSI (Message Signaled Interrupt)MSI-X는 메모리 쓰기(TLP) 방식으로 인터럽트를 전달하여 이 문제를 해결합니다.

특성Legacy INTxMSIMSI-X
시그널링전용 핀 (공유)Memory Write TLPMemory Write TLP
벡터 수4 (INTA~D)1~321~2048
벡터별 타겟 CPU불가제한적개별 지정 가능
Config SpaceIRQ Line/PinCapability (ID=05h)Capability (ID=11h)
공유여러 디바이스 공유전용전용

MSI Capability 구조 (Cap ID 05h)

MSI Capability는 PCI Configuration Space의 Capability List에 연결되며, Cap ID 05h로 식별됩니다. Message Control Register의 설정에 따라 32비트/64비트 주소와 Per-Vector Masking 지원 여부가 결정됩니다.

Message Control Register (Offset 02h)

비트필드설명
0MSI Enable1=MSI 활성화, INTx 자동 비활성화
3:1Multiple Message Capable디바이스가 요청하는 벡터 수 (2^N, 최대 32)
6:4Multiple Message Enable소프트웨어가 할당한 벡터 수 (≤ Capable)
764-bit Address Capable1=64비트 Message Address 지원
8Per-Vector Masking1=Mask/Pending 비트 지원
9Extended Message Data CapablePCIe 4.0+ 확장 데이터 (32비트)
10Extended Message Data Enable확장 데이터 활성화
15:11Reserved예약 (0)

MSI Capability 레이아웃

MSI Capability 구조 레지스터 레이아웃 Offset Size Field +00h 8-bit Capability ID (0x05) +01h 8-bit Next Capability Pointer +02h 16-bit Message Control (MSI Enable, MMC, MME, C64, PVM) +04h 32-bit Message Address Lower 32 bits +08h 32-bit Message Address Upper 32 bits ← 64-bit 모드에서만 +0Ch 16-bit Message Data +0Eh 16-bit Extended Message Data ← PCIe 4.0+ +10h 32-bit Mask Bits (Per-Vector Masking) +14h 32-bit Pending Bits (Per-Vector Masking) MSI: 1~32개 벡터 (MMC/MME), MSI-X는 별도 테이블 구조 사용

Message Address Register (MAR) — x86 APIC 인코딩

x86에서 MSI Message Address는 LAPIC의 메모리 매핑 영역(0xFEExxxxx)을 가리킵니다.

비트 31:20  0xFEE (고정 — LAPIC 기본 주소 상위 12비트)
비트 19:12  Destination ID (target APIC ID)
비트    11  Reserved
비트    10  Reserved
비트     4  Redirection Hint (RH)
           0 = Destination ID가 직접 타겟 지정
           1 = 로직 모드에서 lowest-priority 가능
비트     3  Destination Mode (DM)
           0 = Physical Mode (APIC ID 직접)
           1 = Logical Mode (클러스터/flat)
비트   2:0  Reserved

Message Data Register (MDR)

비트  7:0   Vector (IDT 엔트리 번호, 0x10~0xFE)
비트 10:8   Delivery Mode
            000 = Fixed, 001 = Lowest Priority
            010 = SMI,   100 = NMI
            101 = INIT,  111 = ExtINT
비트   11   Reserved
비트   12   Reserved
비트   13   Reserved
비트   14   Level (edge: 무시, level: 0=deassert, 1=assert)
비트   15   Trigger Mode (0=Edge, 1=Level)
비트 31:16  Reserved (PCIe 4.0 확장 시 사용)
TLP 메커니즘: MSI는 디바이스가 Memory Write TLP를 전송하여 인터럽트를 시그널링합니다. TLP의 주소 필드가 MAR, 데이터 필드가 MDR에 대응합니다. PCIe 스위치/브리지는 이를 일반 메모리 쓰기와 동일하게 라우팅하므로, 별도의 사이드밴드 신호선이 필요 없습니다.

MSI-X Capability 구조 (Cap ID 11h)

MSI-X는 MSI의 제한(최대 32벡터, 단일 MAR/MDR)을 극복하기 위해 도입되었습니다. 별도의 BAR 영역에 벡터 테이블과 PBA를 배치하여, 최대 2048개 벡터를 각각 독립적으로 구성할 수 있습니다.

MSI-X Message Control Register (Offset 02h)

비트필드설명
10:0Table Size벡터 테이블 크기 - 1 (최대 2047 → 2048 엔트리)
13:11Reserved예약
14Function Mask1=모든 벡터 마스크 (글로벌)
15MSI-X Enable1=MSI-X 활성화, INTx 자동 비활성화

MSI-X Capability + Table Entry 레이아웃

Configuration Space — MSI-X Capability (ID: 11h) 00h Message Control [31:16] Next Ptr [15:8] Cap ID: 11h [7:0] 04h Table Offset [31:3] (8바이트 정렬) BIR[2:0] 08h PBA Offset [31:3] (8바이트 정렬) BIR[2:0] [10:0] Table Size−1 [14] Function Mask [15] MSI-X Enable BAR 영역 — MSI-X Table Entry (벡터당 16바이트, 최대 2048개) +00h Message Address Lower [31:0] +04h Message Address Upper [31:0] (64비트 확장) +08h Message Data [31:0] (유효: 하위 16비트) +0Ch Reserved [31:1] M [0] 범례 주소/인터럽트 전달 BAR 내 오프셋 BIR (BAR 번호 0~5) Message Data Mask 비트 [0] 실선(─): Table Offset/BIR · 점선(╌╌): PBA Offset/BIR · Mask[0]=1이면 해당 벡터 차단 BIR[2:0]: 테이블이 위치한 BAR 번호 · Offset[31:3]: BAR 내 시작 주소 (8바이트 정렬)
MSI-X Capability 레지스터 맵 (Configuration Space)과 BAR 영역 Table Entry 구조. 비트 폭은 32비트 기준 비례 표현. 실선=Table Offset/BIR, 점선=PBA Offset/BIR.

PBA (Pending Bit Array)

PBA는 벡터가 마스크된 동안 발생한 인터럽트를 기록합니다. 각 비트가 테이블 엔트리에 1:1 대응하며, 벡터 언마스크 시 pending 비트가 설정되어 있으면 인터럽트가 즉시 전달됩니다. PBA는 읽기 전용이며, 하드웨어가 자동으로 관리합니다.

/* 커널 내부: MSI-X 벡터 개별 마스킹 (drivers/pci/msi/msi.c) */
static void msix_mask_irq(struct msi_desc *desc)
{
    void __iomem *addr = desc->pci.mask_base +
                         desc->msi_index * PCI_MSIX_ENTRY_SIZE +
                         PCI_MSIX_ENTRY_VECTOR_CTRL;
    u32 ctrl = readl(addr);
    ctrl |= PCI_MSIX_ENTRY_CTRL_MASKBIT;
    writel(ctrl, addr);
    /* readl() — 쓰기가 디바이스에 도달했음을 보장 (flush) */
    readl(addr);
}

static void msix_unmask_irq(struct msi_desc *desc)
{
    void __iomem *addr = desc->pci.mask_base +
                         desc->msi_index * PCI_MSIX_ENTRY_SIZE +
                         PCI_MSIX_ENTRY_VECTOR_CTRL;
    u32 ctrl = readl(addr);
    ctrl &= ~PCI_MSIX_ENTRY_CTRL_MASKBIT;
    writel(ctrl, addr);
    readl(addr);
}
MSI vs MSI-X 핵심 차이: MSI는 Configuration Space에 주소/데이터를 저장하므로 변경 시 Config 접근이 필요하지만, MSI-X는 BAR 영역(MMIO)에 테이블을 배치하여 벡터별 독립적이고 빠른 접근이 가능합니다. 또한 MSI-X는 벡터별 개별 마스킹을 기본 지원합니다.

인터럽트 전달 메커니즘

MSI/MSI-X 인터럽트는 디바이스가 특정 주소로 Memory Write TLP를 전송하는 방식으로 동작합니다. 플랫폼에 따라 전달 경로가 다릅니다.

x86: LAPIC 기반 전달

디바이스가 0xFEE00000 범위의 주소로 Memory Write TLP를 전송하면, PCIe Root Complex가 이를 메모리 트랜잭션이 아닌 인터럽트 메시지로 인식하여 대상 CPU의 LAPIC에 전달합니다.

ARM: ITS (Interrupt Translation Service) 기반 전달

ARM GICv3/v4에서는 ITS가 MSI 메시지를 수신하여 DeviceID + EventID를 LPI(Locality-specific Peripheral Interrupt) 번호로 변환합니다. ITS Command Queue를 통해 매핑 테이블이 관리됩니다.

MSI/MSI-X 인터럽트 전달 경로 (x86) PCIe Device MSI-X Table TLP PCIe Fabric Switch / Bridge Mem Wr Root Complex 0xFEExxxxx 감지 IOMMU / IR Interrupt Remapping (optional) LAPIC IRR 비트 설정 CPU IDT MSI 전달 경로 IOMMU IR 활성 시 추가 경로

IRR(Interrupt Request Register): LAPIC 내부 레지스터로, 아직 서비스되지 않은 대기 중인 인터럽트 벡터 비트를 저장합니다. CPU가 인터럽트를 처리하기 시작하면 해당 비트가 ISR(In-Service Register)로 이동합니다.

APIC 주소 범위 충돌: 0xFEE00000~0xFEEFFFFF (1MB) 영역은 LAPIC용으로 예약됩니다. 이 영역이 일반 메모리 또는 MMIO BAR과 겹치면 시스템이 부팅되지 않거나 인터럽트가 손실될 수 있습니다. BIOS/펌웨어가 이 영역을 Reserved로 E820 맵에 보고하는지 확인하세요.

커널 MSI 서브시스템

리눅스 커널의 MSI 서브시스템은 irq_domain 계층 구조를 기반으로, 플랫폼별 인터럽트 컨트롤러와 PCI MSI를 추상화합니다.

주요 구조체

구조체헤더역할
struct msi_descinclude/linux/msi.hMSI/MSI-X 디스크립터 — 벡터 정보, affinity, 마스크 상태
struct msi_msginclude/linux/msi.hMessage Address/Data 값 (실제 HW에 프로그래밍되는 값)
struct msi_domain_infoinclude/linux/msi.hirq_domain 레벨 MSI 연산 정의
struct msi_domain_opsinclude/linux/msi.hMSI 도메인 콜백: prepare, set_desc 등
struct irq_chipinclude/linux/irq.h인터럽트 마스크/언마스크/EOI 등 HW 연산

msi_msg / msi_desc 코드

/* include/linux/msi.h */
struct msi_msg {
    union {
        u64 address;
        struct {
            u32 address_lo;   /* MAR 하위 32비트 */
            u32 address_hi;   /* MAR 상위 32비트 (64-bit) */
        };
    };
    u32 data;             /* MDR — 벡터, 전달 모드 등 */
};

struct msi_desc {
    unsigned int        irq;          /* Linux IRQ 번호 */
    unsigned int        nvec_used;    /* 사용 벡터 수 */
    struct device       *dev;          /* 소유 디바이스 */
    struct msi_msg      msg;           /* 현재 프로그래밍된 메시지 */
    struct irq_affinity_desc *affinity; /* CPU affinity */
    struct {
        u32         masked;       /* MSI: Mask Bits 캐시 */
        struct {
            u8      is_64;    /* 64비트 주소 지원 */
            u16     entry_nr; /* MSI-X 테이블 인덱스 */
            void __iomem *mask_base; /* MSI-X 테이블 VA */
        } pci;
    };
    u16                 msi_index;    /* 벡터 인덱스 */
};

pci_alloc_irq_vectors() 내부 흐름

드라이버가 pci_alloc_irq_vectors()를 호출하면, 커널은 다음 순서로 벡터를 할당합니다.

pci_alloc_irq_vectors() 초기화 흐름 pci_alloc_irq_vectors(pdev, min, max, flags) PCI_IRQ_MSIX PCI_IRQ_MSI MSI-X 초기화 __pci_enable_msix_range() msix_capability_init() • BAR 영역 ioremap (MSI-X Table) • msi_desc 할당 (벡터 수만큼) • MSI-X Enable + INTx 비활성화 MSI 초기화 (MSI-X 실패 시) __pci_enable_msi_range() msi_capability_init() • Config Space에 MAR/MDR 프로그래밍 • irq_domain 통해 HW 프로그래밍 • MSI Enable 비트 설정 실패 시 실패 시 Fallback: Legacy INTx (공유 인터럽트) 드라이버 사용: pci_irq_vector(pdev, i) → 벡터별 IRQ 번호 획득 MSI-X 최대 2048 벡터 / MSI 최대 32 벡터 / 모두 Level-triggered 아닌 Edge-triggered

커널 소스 경로

경로설명
drivers/pci/msi/msi.cPCI MSI 코어 — 할당, 마스킹, 해제
drivers/pci/msi/irqdomain.cPCI MSI irq_domain 인터페이스
drivers/pci/msi/api.c드라이버 API — pci_alloc_irq_vectors() 등
kernel/irq/msi.c범용 MSI irq_domain 프레임워크
arch/x86/kernel/apic/msi.cx86 APIC MSI 도메인 구현
drivers/irqchip/irq-gic-v3-its-pci-msi.cARM GICv3 ITS PCI-MSI 도메인
include/linux/msi.hMSI 구조체 및 API 헤더

벡터 할당과 CPU Affinity

멀티코어 시스템에서 인터럽트를 적절한 CPU에 분배하는 것은 성능에 결정적입니다. 커널은 NUMA 토폴로지를 고려한 자동 affinity 할당을 지원합니다.

pci_alloc_irq_vectors 플래그

플래그설명
PCI_IRQ_MSIX0x04MSI-X 사용 시도
PCI_IRQ_MSI0x02MSI 사용 시도
PCI_IRQ_LEGACY0x01레거시 INTx 폴백
PCI_IRQ_AFFINITY0x10자동 CPU affinity 할당 (커널이 분배)
PCI_IRQ_ALL_TYPES0x07MSI-X | MSI | LEGACY 전부 시도

NVMe 스타일 affinity 벡터 할당

/* MSI-X 할당 — 커널이 최적 벡터 수 결정 + 자동 affinity */
struct irq_affinity affd = {
    .pre_vectors  = 1,  /* Admin Queue 전용 벡터 (affinity 제외) */
    .post_vectors = 0,
};

int nvecs = pci_alloc_irq_vectors_affinity(pdev,
    1,           /* 최소 벡터 수 */
    num_queues,  /* 최대 벡터 수 (보통 I/O 큐 수 + 1) */
    PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_AFFINITY,
    &affd);

if (nvecs < 0)
    return nvecs;

/* 각 벡터에 대한 IRQ 번호 획득 및 핸들러 등록 */
for (int i = 0; i < nvecs; i++) {
    int irq = pci_irq_vector(pdev, i);

    err = devm_request_irq(&pdev->dev, irq, my_isr, 0,
                           KBUILD_MODNAME, &queues[i]);
    if (err)
        goto err_free;
}

/* ... 드라이버 운영 ... */

/* 해제 — devm 사용 시 자동 해제, 수동 시: */
pci_free_irq_vectors(pdev);

sysfs affinity 인터페이스

# 특정 IRQ의 현재 affinity 확인
cat /proc/irq/<irq_num>/smp_affinity_list
# 출력 예: 0-3  (CPU 0~3에 분배)

# affinity를 CPU 4로 변경
echo 4 > /proc/irq/<irq_num>/smp_affinity_list

# managed_irq인 경우 (커널이 관리, 사용자 변경 불가)
cat /proc/irq/<irq_num>/effective_affinity_list

# NUMA 노드별 인터럽트 분포 확인
for irq in /proc/irq/*/smp_affinity_list; do
    echo "$(dirname $irq | xargs basename): $(cat $irq)"
done | sort -t: -k2 -n
NUMA 최적화: PCI_IRQ_AFFINITY 플래그 사용 시, 커널은 디바이스가 연결된 NUMA 노드의 CPU를 우선 할당합니다. 이는 NVMe, 고성능 NIC 등에서 캐시 미스와 크로스 노드 트래픽을 줄여 지연시간을 최소화합니다. irq_set_affinity_hint()로 드라이버가 힌트를 제공할 수도 있습니다.

Interrupt Remapping (IOMMU)

Intel VT-d 또는 AMD-Vi의 Interrupt Remapping (IR)은 디바이스가 전송하는 MSI 메시지를 IOMMU가 중간에서 변환하여, 보안 격리와 유연한 인터럽트 라우팅을 제공합니다.

호환 형식 vs 리매핑 형식

특성호환 형식 (Compatibility)리매핑 형식 (Remappable)
Message AddressAPIC ID 직접 인코딩IRTE 인덱스 인코딩
보안디바이스가 임의 CPU에 인터럽트 전송 가능IRTE 테이블로 제한됨
Address[4]Redirection HintInterrupt Format (1=remappable)
Address[3]Destination ModeSHV (Sub-Handle Valid)
Address[19:5]Destination ID 상위IRTE Handle (인덱스)
Data[15:0]Vector + Delivery 직접Sub-Handle (하위 인덱스)
IRTE 참조없음handle + sub-handle → IRTE

Posted Interrupts (VM 패스스루)

VT-d Posted Interrupts는 물리 디바이스의 MSI가 VM의 가상 APIC에 직접 주입되도록 합니다. vCPU가 실행 중이면 VM-Exit 없이 인터럽트가 전달되어, VFIO 패스스루 성능을 크게 향상시킵니다.

Posted Interrupt 흐름:
  Device → MSI TLP → IOMMU → IRTE (Posted Interrupt Descriptor 참조)
    ├─ vCPU 실행 중: PI Notification → 가상 APIC 직접 주입 (no VM-Exit)
    └─ vCPU 대기 중: Outstanding Notification → wakeup → VM-Entry 시 주입
보안 경고: Interrupt Remapping이 비활성화된 상태에서 악의적 디바이스(또는 DMA 공격)가 임의의 APIC ID와 벡터로 MSI를 전송하면, 커널 권한 상승이 가능합니다. 프로덕션 시스템에서는 반드시 intel_iommu=on + intremap=on으로 IR을 활성화하세요.

멀티큐 드라이버 패턴

최신 고성능 디바이스는 하드웨어 멀티큐를 지원하며, 각 큐에 개별 MSI-X 벡터를 할당하여 CPU별 독립적인 인터럽트 처리가 가능합니다.

디바이스별 큐 구조

디바이스벡터 배분일반적 벡터 수비고
NVMe1 Admin + N I/O 큐CPU 수 + 1각 I/O 큐가 CPU에 바인딩
NIC (e.g. mlx5)N RX + N TX (또는 Combined)CPU 수 × 1~2ethtool -L로 조정
RDMA (mlx5_ib)N Completion 큐CPU 수CQ 완료 인터럽트
GPU (AMDGPU)기능별 분리디바이스 정의Compute, GFX, SDMA 등

멀티큐 드라이버 코드 패턴

static int mydev_setup_irqs(struct pci_dev *pdev,
                             struct mydev_priv *priv)
{
    int num_cpus = num_online_cpus();
    int nvecs, i;
    struct irq_affinity affd = {
        .pre_vectors  = 1,  /* 관리용 벡터 */
        .post_vectors = 0,
    };

    /* MSI-X 우선 → MSI 폴백 → Legacy 폴백 */
    nvecs = pci_alloc_irq_vectors_affinity(pdev,
        2,              /* 최소: admin + I/O 1개 */
        num_cpus + 1,   /* 최대: admin + CPU당 1개 */
        PCI_IRQ_ALL_TYPES | PCI_IRQ_AFFINITY,
        &affd);

    if (nvecs < 0)
        return nvecs;

    priv->num_io_queues = nvecs - 1;

    /* 벡터 0: Admin Queue */
    if (devm_request_irq(&pdev->dev,
            pci_irq_vector(pdev, 0),
            mydev_admin_isr, 0,
            "mydev-admin", priv))
        goto err_free;

    /* 벡터 1~N: I/O Queues */
    for (i = 0; i < priv->num_io_queues; i++) {
        int irq = pci_irq_vector(pdev, i + 1);

        if (devm_request_irq(&pdev->dev, irq,
                mydev_io_isr, 0,
                "mydev-io", &priv->io_queues[i]))
            goto err_free;
    }

    return 0;

err_free:
    pci_free_irq_vectors(pdev);
    return -ENOMEM;
}
벡터 수 조정: pci_alloc_irq_vectors()가 요청보다 적은 벡터를 반환할 수 있습니다. 드라이버는 반환된 벡터 수에 맞춰 큐 수를 동적으로 조정해야 합니다. NVMe 드라이버는 min(num_possible_cpus(), max_hw_queues)로 최대 요청 수를 결정합니다.

디버깅과 모니터링

MSI/MSI-X 관련 문제를 진단할 때 사용하는 주요 도구와 인터페이스입니다.

/proc/interrupts 해석

# MSI-X 벡터별 인터럽트 카운트 확인
grep "nvme" /proc/interrupts
#            CPU0       CPU1       CPU2       CPU3
# 36:      15234          0          0          0  IR-PCI-MSIX  0-edge  nvme0q0
# 37:          0      28451          0          0  IR-PCI-MSIX  1-edge  nvme0q1
# 38:          0          0      31205          0  IR-PCI-MSIX  2-edge  nvme0q2
# 39:          0          0          0      29876  IR-PCI-MSIX  3-edge  nvme0q3
# → IR-PCI-MSIX: Interrupt Remapping + PCI MSI-X
# → 각 큐가 서로 다른 CPU에 할당되어 있음 확인

lspci -vvv MSI Capability 디코딩

# MSI/MSI-X Capability 상세 확인
lspci -vvv -s 01:00.0 | grep -A 10 -E "MSI:|MSI-X:"
# Capabilities: [68] MSI: Enable- Count=1/1 Maskable- 64bit+
#         Address: 0000000000000000  Data: 0000
# Capabilities: [a0] MSI-X: Enable+ Count=65 Masked-
#         Vector table: BAR=0 offset=00002000
#         PBA: BAR=0 offset=00003000
# → Enable+: MSI-X 활성화 상태
# → Count=65: 65개 벡터 테이블 엔트리
# → BAR=0 offset=00002000: BAR 0의 0x2000에 벡터 테이블

sysfs + dmesg 확인

# 디바이스의 MSI 활성화 상태 확인
cat /sys/bus/pci/devices/0000:01:00.0/msi_irqs/*
# 또는
ls /sys/bus/pci/devices/0000:01:00.0/msi_irqs/
# 36 37 38 39  → 할당된 IRQ 번호 목록

# MSI 활성화 여부
cat /sys/bus/pci/devices/0000:01:00.0/msi_bus

# 커널 부팅 시 MSI 관련 메시지 확인
dmesg | grep -i "msi\|irq.*vector"
# [    1.234] nvme 0000:01:00.0: enabling device (0000 -> 0002)
# [    1.235] nvme 0000:01:00.0: PCI->APIC IRQ transform: INT A -> IRQ 36
# [    1.236] nvme nvme0: 4/0/0 default/read/poll queues

디버깅 도구 요약

도구/경로용도
/proc/interruptsCPU별 인터럽트 카운트, 벡터 타입 확인
lspci -vvvMSI/MSI-X Capability 디코딩, BAR 오프셋
/sys/bus/pci/devices/*/msi_irqs/할당된 IRQ 번호 목록
/proc/irq/*/smp_affinity_listIRQ → CPU affinity 매핑
/proc/irq/*/effective_affinity_list실제 적용된 affinity (managed_irq)
dmesgMSI 할당/실패 메시지, irq_domain 로그
ftrace (irq_handler_entry)인터럽트 핸들러 호출 추적
perf stat -e irq_vectors:*인터럽트 벡터 이벤트 통계

SR-IOV (Single Root I/O Virtualization)

SR-IOV(Single Root I/O Virtualization)는 PCI-SIG 표준(ECN SR-IOV 1.1)에 정의된 하드웨어 가상화 기술입니다. 하나의 물리 PCIe 디바이스(PF, Physical Function)를 여러 경량 가상 디바이스(VF, Virtual Function)로 분할하고, 각 VF에 독립적인 Config Space·BAR·MSI-X·TX/RX 큐를 부여하여 VM에 직접 패스스루(passthrough)합니다. IOMMU가 VF별 DMA 격리를 보장하므로, 에뮬레이션 오버헤드 없이 네이티브 대비 5% 이내 성능 손실로 I/O 가속이 가능합니다. 고성능 NIC(Intel X710/E810, Mellanox ConnectX), NVMe SSD, HBA, RDMA 가속기 등에 널리 적용됩니다.

SR-IOV 아키텍처 — PF/VF 분할과 VM 직접 할당 Host CPU & PCIe Root Complex IOMMU (Intel VT-d / AMD-Vi) — VF별 독립 DMA Domain 생성 Physical NIC (예: Intel X710 / Mellanox ConnectX-6) PF (Physical Function) BDF: 0000:03:00.0 Full Config Space (256B/4KB) 전체 BAR·큐·펌웨어 제어 호스트 드라이버 (i40e) SR-IOV Capability 보유 VF 0 03:00.2 경량 Config Space VF BAR0 (MMIO) MSI-X 2 벡터 TX/RX 큐 2쌍 VF 1 03:00.3 경량 Config Space VF BAR0 (MMIO) MSI-X 2 벡터 TX/RX 큐 2쌍 VF 2 03:00.4 경량 Config Space VF BAR0 (MMIO) MSI-X 2 벡터 TX/RX 큐 2쌍 VF N-1 최대 63 VF IOMMU — VF별 독립 Domain (DMA 격리) Group 15 (PF) Group 16 (VF 0) Group 17 (VF 1) Group 18 (VF 2) Host (PF 관리) i40e / ixgbe 드라이버 MAC/VLAN/QoS 정책 제어 VM 1 VF 0 직접 접근 vfio-pci 드라이버 VM 2 VF 1 직접 접근 vfio-pci 드라이버 VM 3 VF 2 직접 접근 vfio-pci 드라이버 각 VF는 별도 IOMMU 그룹 → DMA 격리 보장. VFIO를 통해 VM에 안전하게 패스스루 PF 드라이버(호스트)가 mailbox API로 VF MAC 주소·VLAN·대역폭 정책 통제 VM은 VF를 마치 전용 NIC처럼 사용 → 에뮬레이션 없이 거의 네이티브 성능

PF / VF 핵심 개념

개념설명특이사항
PF (Physical Function) 전체 디바이스 기능을 가진 물리 함수. SR-IOV Capability 보유. 호스트 드라이버가 직접 관리 VF 생성·삭제·MAC·VLAN·QoS 정책을 PF 드라이버가 제어 (mailbox)
VF (Virtual Function) 경량화된 가상 함수. 독립적 PCIe Config Space(일부)·BAR·MSI-X·TX/RX 큐 보유 Config Space 일부(전원 관리 등)는 PF가 가상화. VF 자체는 리셋·전원 제어 불가
VF BAR PF의 SR-IOV Capability에 정의된 VF 전용 MMIO BAR 영역. NumVFs × VF_BAR_size로 연속 매핑 VF[n] BAR 기준주소 = VF BAR 기준 + n × VF_BAR_size
First VF Offset / VF Stride VF의 Routing ID(BDF) 간격과 첫 VF BDF 오프셋. SR-IOV Capability 레지스터에 하드코딩 VF[n] BDF = PF BDF + FirstVFOffset + n × VFStride
ARI (Alt. Routing-ID) Function 번호를 8비트로 확장 — 한 디바이스에 최대 256개 Function 허용 PCIe 2.1 이상, ARI 지원 루트 포트 필요. 대규모 VF(예: 64개+)에 필수
IOMMU 그룹 격리 각 VF는 별도 IOMMU 그룹에 배치 → DMA 격리 보장. 단독 VFIO 패스스루 가능 ACS 미지원 스위치 아래에서는 PF와 VF가 동일 그룹이 될 수 있음 — lspci로 확인
Mailbox PF ↔ VF 간 메시지 채널. VF가 MAC/VLAN 변경·링크 상태 등을 PF에 요청 보안 경계: VM이 임의 MAC 스푸핑·VLAN 침입 불가 (PF가 화이트리스트 관리)

SR-IOV PCIe Extended Capability 구조

SR-IOV Extended Capability(Cap ID 0x0010)는 PCIe Extended Configuration Space(오프셋 0x100 이상)에 위치하며 PF에서만 노출됩니다. VF 수·BAR 크기·Stride 등을 하드웨어 레지스터로 기술합니다.

SR-IOV Extended Capability 레지스터 레이아웃 (PF only) 오프셋 레지스터 설명 및 주요 필드 +0x00 32비트 SR-IOV Ext. Cap Header Cap ID = 0x0010 [15:0] Cap ID(0x10) [19:16] Cap Version [31:20] Next Cap Ptr lspci에서 "SR-IOV" 출력 근거 +0x04 SR-IOV Capabilities [0] VF Migration [21] ARI Capable VF Migration 지원 여부, ARI Capable Hierarchy pci_sriov_capable() 내부에서 참조 +0x08 16b + 16b SR-IOV Control / Status Control[0] VF Enable Control[2] VF MSE Control[4] ARI VF Enable=1 → VF Config Space 버스에 노출 VF MSE(Memory Space Enable) — VF BAR MMIO 활성화 커널이 pci_enable_sriov() 시 이 비트를 자동 셋 +0x0C InitialVFs / TotalVFs [15:0] InitialVFs [31:16] TotalVFs TotalVFs: 하드웨어 지원 최대 VF 수 (RO) sriov_totalvfs sysfs 노드 값의 근거 +0x10 NumVFs / FuncDepLink [15:0] NumVFs (RW) [31:16] FuncDepLink NumVFs: 실제 활성화할 VF 수 (0 ~ TotalVFs, RW) sriov_numvfs에 쓰면 커널이 이 레지스터를 설정 +0x14 First VF Offset / VF Stride [15:0] FirstVFOffset [31:16] VFStride VF[n] BDF = PF BDF + FirstVFOffset + n × VFStride PCIe 라우팅 ID(BDF) 계산에 사용 (RO) +0x18 VF Device ID [31:16] VF Device ID (RO) VF Config Space에 노출되는 Device ID (PF의 Vendor ID는 공유) VF 드라이버의 pci_device_id 테이블 매칭에 사용 +0x24 ~ +0x38 6×32비트 VF BAR 0 ~ 5 VF MMIO 주소 공간 크기 인코딩 (PF BAR과 별도, VF 전용) VF BAR 크기는 PCI-compatible alignment 인코딩 pci_enable_sriov() 시 커널이 VF BAR 전체 범위를 NumVFs × VF_BAR_size 크기로 연속 할당 VF BDF 계산 공식 VF[n] BDF = PF BDF + FirstVFOffset + n × VFStride (ARI 모드: 8비트 Function 번호 사용) lspci -vvv 출력 예시 (PF의 SR-IOV Capability) Capabilities: [180] Single Root I/O Virtualization (SR-IOV) IOVCap: Migration- Interrupt Message Number: 000 IOVCtl: Enable+ VF MSE+ ARI+ Initial VFs: 64, Total VFs: 64, Number of VFs: 4, Function Dependency Link: 00 First VF Offset: 128, VF Stride: 1, VF Device ID: 154c [VF BAR0-5 ...]

VF 리소스 할당 구조

SR-IOV 활성화 시 NIC의 물리 자원(TX/RX 큐, MSI-X 벡터, MMIO BAR 공간)이 VF 수에 맞게 분할됩니다. VF별로 독립적인 MMIO BAR 공간이 연속으로 매핑되어 게스트 드라이버가 직접 레지스터를 접근합니다.

SR-IOV VF 리소스 할당 (Intel X710 NIC 기준, NumVFs=4) PCIe MMIO 주소 공간 MSI-X 벡터 할당 TX/RX 큐 분할 PF BAR0 (128KB MMIO) 글로벌 제어·펌웨어·전체 큐 제어 VF BAR0 연속 영역 (128KB) = 4 VF × 32KB VF 0 MMIO +0x000000 (32KB) VF 1 MMIO +0x008000 (32KB) VF 2 MMIO +0x010000 (32KB) VF 3 MMIO +0x018000 (32KB) VF[n] 기준 = VF BAR 기준 + n×32KB PF MSI-X 128 벡터 관리·링크·에러 인터럽트 전용 VF 0 MSI-X 2 벡터 벡터 0: TX 완료 벡터 1: RX 수신 VF 1 MSI-X 2 벡터 벡터 0: TX 완료 벡터 1: RX 수신 VF 2 MSI-X 2 벡터 벡터 0: TX 완료 벡터 1: RX 수신 VF 3 MSI-X 2 벡터 벡터 0: TX 완료 벡터 1: RX 수신 PF 큐 32쌍 (TX+RX) 호스트 네이티브 I/O 전용 VF 0 큐 2쌍 (TX[0]+RX[0]) 큐 번호 = VF_idx × 큐수 VF 1 큐 2쌍 (TX[2]+RX[2]) VF 2 큐 2쌍 (TX[4]+RX[4]) VF 3 큐 2쌍 (TX[6]+RX[6]) VF Config Space = Vendor/Device ID + BAR 주소 + MSI-X Capability 만 노출 (전원 관리 등 제거) VF 드라이버: BAR0 ioremap → TX/RX 디스크립터 링 물리 주소 MMIO 레지스터에 직접 기록 → DMA 시작 자원 요약 (Intel X710 기준) • 총 TX/RX 큐: 512쌍 → PF 최대 256쌍 + VF당 최대 16쌍 • MSI-X: PF 128개 + VF당 1~4개 (디바이스별 상이) • 최대 VF 수: 64개 (ARI 활성화 시) VF 드라이버 동작 핵심 ① VF BAR0 ioremap → TX/RX 링 물리 주소 레지스터에 기록 ② MSI-X 벡터로 TX 완료 / RX 수신 인터럽트 처리 ③ mailbox로 PF에 MAC/VLAN 설정 요청

커널 API

#include <linux/pci.h>

/* ── VF 활성화 / 비활성화 ── */
/* PF 드라이버에서 num_vfs개 VF 활성화. 성공 시 num_vfs 반환 */
int  pci_enable_sriov(struct pci_dev *dev, int nr_virtfn);

/* 모든 VF 비활성화. VM에 할당된 VF 있으면 호출 금지 */
void pci_disable_sriov(struct pci_dev *dev);

/* ── VF 수 조회 ── */
int  pci_num_vf(struct pci_dev *dev);           /* 현재 활성 VF 수 */
int  pci_vfs_assigned(struct pci_dev *dev);      /* VFIO에 할당된 VF 수 */
int  pci_sriov_get_totalvfs(struct pci_dev *dev);  /* 하드웨어 최대 VF 수 */

/* ── PF ↔ VF 관계 탐색 ── */
struct pci_dev *pci_physfn(struct pci_dev *dev);   /* VF → PF */
bool  pci_is_virtfn(struct pci_dev *dev);           /* VF 여부 확인 */

/* ── ARI 지원 확인 ── */
bool  pci_ari_enabled(struct pci_bus *bus);    /* 버스 ARI 활성 여부 */

/* ── VF 드라이버에서 PF 데이터 접근 패턴 ── */
struct pci_dev  *physfn = pci_physfn(vf_pdev);
struct my_pf_priv *pf  = pci_get_drvdata(physfn);
/* pf를 통해 mailbox, 전역 자원에 접근 */

PF 드라이버 구현

#include <linux/pci.h>
#include <linux/netdevice.h>

struct my_pf_priv {
    struct pci_dev     *pdev;
    int                 num_vfs;
    struct my_vf_info  *vf_info;   /* VF당 MAC·VLAN 설정 테이블 */
    void __iomem       *hw;        /* PF BAR0 */
};

static int my_sriov_configure(struct pci_dev *pdev, int num_vfs)
{
    struct my_pf_priv *pf = pci_get_drvdata(pdev);
    int ret;

    if (num_vfs == 0) {
        /* VFIO에 할당된 VF가 있으면 비활성화 거부 */
        if (pci_vfs_assigned(pdev))
            return -EPERM;

        my_hw_disable_vfs(pf);
        kfree(pf->vf_info);
        pf->vf_info = NULL;
        pci_disable_sriov(pdev);
        pf->num_vfs = 0;
        return 0;
    }

    if (num_vfs > pci_sriov_get_totalvfs(pdev))
        return -EINVAL;

    /* VF당 자원 구조체 할당 (MAC 주소, VLAN, 신뢰 모드 등) */
    pf->vf_info = kcalloc(num_vfs, sizeof(*pf->vf_info), GFP_KERNEL);
    if (!pf->vf_info)
        return -ENOMEM;

    /* 하드웨어 VF 큐·인터럽트 할당 */
    ret = my_hw_enable_vfs(pf, num_vfs);
    if (ret) {
        kfree(pf->vf_info);
        return ret;
    }

    /* PCIe SR-IOV Capability NumVFs 레지스터 설정 & VF Config Space 노출 */
    ret = pci_enable_sriov(pdev, num_vfs);
    if (ret) {
        my_hw_disable_vfs(pf);
        kfree(pf->vf_info);
        return ret;
    }

    pf->num_vfs = num_vfs;
    dev_info(&pdev->dev, "%d VFs 활성화\n", num_vfs);
    return num_vfs;   /* 성공 시 활성화된 VF 수 반환 */
}

/* PF 드라이버의 NDO 콜백 — VF MAC/VLAN/QoS 정책 제어 */
static int my_ndo_set_vf_mac(struct net_device *dev, int vf_id,
                              u8 *mac)
{
    struct my_pf_priv *pf = netdev_priv(dev);
    if (vf_id >= pf->num_vfs)
        return -EINVAL;
    ether_addr_copy(pf->vf_info[vf_id].mac, mac);
    my_hw_set_vf_mac(pf, vf_id, mac);  /* MMIO 레지스터에 MAC 기록 */
    return 0;
}

static const struct net_device_ops my_netdev_ops = {
    .ndo_set_vf_mac        = my_ndo_set_vf_mac,
    .ndo_set_vf_vlan       = my_ndo_set_vf_vlan,
    .ndo_set_vf_rate       = my_ndo_set_vf_rate,       /* QoS 대역폭 */
    .ndo_get_vf_config     = my_ndo_get_vf_config,
    .ndo_set_vf_trust      = my_ndo_set_vf_trust,      /* 신뢰 모드 */
    .ndo_set_vf_link_state = my_ndo_set_vf_link_state,
};

static struct pci_driver my_pf_driver = {
    .name            = "my_nic_pf",
    .id_table        = my_pf_ids,
    .probe           = my_pf_probe,
    .remove          = my_pf_remove,
    .sriov_configure = my_sriov_configure,  /* sysfs sriov_numvfs 쓰기 시 호출 */
};

VF 드라이버 구현

/* VF 드라이버 — VM 내부 게스트 OS 또는 호스트에서 VF 직접 제어 */
struct my_vf_priv {
    struct pci_dev    *pdev;
    void __iomem      *hw;       /* VF BAR0 */
    struct my_tx_ring  tx_ring;
    struct my_rx_ring  rx_ring;
};

static int my_vf_probe(struct pci_dev *pdev,
                       const struct pci_device_id *id)
{
    struct my_vf_priv *vf;
    int ret;

    ret = pcim_enable_device(pdev);
    ret = pcim_iomap_regions(pdev, BIT(0), "my_nic_vf");

    vf = devm_kzalloc(&pdev->dev, sizeof(*vf), GFP_KERNEL);
    vf->pdev = pdev;
    vf->hw   = pcim_iomap_table(pdev)[0];  /* VF BAR0 ioremap */

    pci_set_master(pdev);   /* DMA Bus Master 활성화 */

    /* VF는 IOMMU가 격리 보장 — DMA 마스크 직접 설정 */
    ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));

    /* TX/RX 디스크립터 링 DMA 할당 후 하드웨어 레지스터에 물리 주소 기록 */
    my_vf_setup_rings(vf);

    /* MSI-X 벡터 할당 (VF Config Space에 명시된 최대 수 이내) */
    ret = pci_alloc_irq_vectors(pdev, 1, MY_VF_MSIX_MAX, PCI_IRQ_MSIX);

    request_irq(pci_irq_vector(pdev, 0), my_vf_tx_irq,
                0, "my_vf_tx", vf);
    request_irq(pci_irq_vector(pdev, 1), my_vf_rx_irq,
                0, "my_vf_rx", vf);

    /* PF mailbox로 MAC 주소 요청 (VF는 MAC 직접 변경 불가) */
    my_vf_mailbox_send(vf, MSG_REQUEST_MAC, NULL);

    pci_set_drvdata(pdev, vf);
    return 0;
}

static const struct pci_device_id my_vf_ids[] = {
    { PCI_VDEVICE(INTEL, 0x154c), 0 },   /* Intel X710 VF */
    { PCI_VDEVICE(INTEL, 0x1571), 0 },   /* Intel XL710 VF */
    {}
};
MODULE_DEVICE_TABLE(pci, my_vf_ids);

static struct pci_driver my_vf_driver = {
    .name     = "my_nic_vf",
    .id_table = my_vf_ids,
    .probe    = my_vf_probe,
    .remove   = my_vf_remove,
    /* VF 드라이버는 sriov_configure 없음 */
};

SR-IOV + IOMMU + VFIO → VM 패스스루 전체 스택

SR-IOV + IOMMU + VFIO → VM 패스스루 전체 스택 VM 1 (게스트) Guest NIC 드라이버 (iavf.ko) Guest 물리 주소(GPA)로 TX/RX 링 디스크립터 업데이트 VM 2 (게스트) Guest NIC 드라이버 (iavf.ko) Guest 물리 주소(GPA)로 TX/RX 링 디스크립터 업데이트 QEMU / KVM 하이퍼바이저 + VFIO VFIO VF 0 (/dev/vfio/16) VFIO_IOMMU_MAP_DMA: GPA → HPA 매핑 등록 VFIO VF 1 (/dev/vfio/17) VFIO_IOMMU_MAP_DMA: GPA → HPA 매핑 등록 IOMMU (Intel VT-d / AMD-Vi) Domain 16 (VF 0 전용): GPA → HPA 변환 Domain 17 (VF 1 전용): GPA → HPA 변환 Interrupt Remapping: VF MSI-X → irqfd → KVM → 게스트 인터럽트 물리 NIC (PCIe 디바이스) PF (03:00.0) i40e.ko (호스트) MAC/VLAN 정책 제어 VF 0 (03:00.2) iavf.ko (게스트) IOMMU Group 16 VF 1 (03:00.3) iavf.ko (게스트) IOMMU Group 17 VF 2 (미할당) 03:00.4 호스트 직접 사용 mailbox 데이터 흐름 핵심 포인트 ① TX: VM 드라이버가 GPA 기반 TX 디스크립터 링 직접 업데이트 — VM-exit 발생 없음 (zero-copy) ② DMA: NIC VF가 GPA로 DMA 시 IOMMU가 GPA→HPA 변환 — VF는 VM 전용 메모리 외 접근 불가 ③ RX 인터럽트: VF MSI-X → IOMMU Interrupt Remapping → QEMU irqfd → KVM → 게스트 vCPU 인터럽트 ④ 정책: PF 드라이버가 mailbox로 VF MAC/VLAN/대역폭 제어 — VM이 임의 MAC 스푸핑·VLAN 침입 불가 ⑤ 한계: VF는 하드웨어 상태 보유 → 라이브 마이그레이션 불가 (vDPA/virtio 사용 시 마이그레이션 가능)

sysfs 인터페이스 상세

# PF 기준 경로: /sys/bus/pci/devices/0000:03:00.0/

## VF 수 조회 및 제어
cat /sys/bus/pci/devices/0000:03:00.0/sriov_totalvfs    # 최대 VF 수 (RO)
cat /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs      # 현재 활성 VF 수
echo 4 > /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs # VF 4개 활성화
echo 0 > /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs # VF 전부 비활성화

## VF 활성화 후 생성된 VF 디바이스 목록
ls -l /sys/bus/pci/devices/0000:03:00.0/virtfn*
# virtfn0 → ../0000:03:00.2
# virtfn1 → ../0000:03:00.3

## VF → PF 역방향 참조
readlink /sys/bus/pci/devices/0000:03:00.2/physfn
# ../0000:03:00.0  (PF BDF)

## VF를 vfio-pci에 바인딩 (VM 패스스루 준비)
VFBDF="0000:03:00.2"
echo $VFBDF > /sys/bus/pci/devices/$VFBDF/driver/unbind 2>/dev/null || true
echo "vfio-pci" > /sys/bus/pci/devices/$VFBDF/driver_override
echo $VFBDF > /sys/bus/pci/drivers/vfio-pci/bind

## IOMMU 그룹 번호 확인
readlink /sys/bus/pci/devices/$VFBDF/iommu_group | grep -o '[0-9]*$'

## ip link로 PF에서 VF 설정 (네트워크 드라이버)
ip link set enp3s0f0 vf 0 mac 52:54:00:aa:bb:cc  # VF 0 MAC 설정
ip link set enp3s0f0 vf 0 vlan 100               # VF 0 VLAN 태그
ip link set enp3s0f0 vf 0 rate 1000              # 최대 1Gbps 대역폭
ip link set enp3s0f0 vf 0 trust on               # 신뢰 모드 (MAC 스푸핑 허용)
ip link set enp3s0f0 vf 0 spoofchk off           # 스푸핑 검사 비활성
ip link show enp3s0f0                             # VF 설정 전체 확인

SR-IOV + DPDK 고성능 패킷 처리

SR-IOV VF를 DPDK의 PMD(Poll Mode Driver)와 결합하면 커널 네트워크 스택을 완전히 우회하여 라인 레이트(100Gbps+) 패킷 처리가 가능합니다. DPDK iavf PMD는 VF BAR에 직접 접근하여 TX/RX 디스크립터 링을 폴링 방식으로 처리합니다.

# DPDK에서 SR-IOV VF 사용 예시

# 1. 대용량 페이지 설정
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
mount -t hugetlbfs nodev /mnt/huge

# 2. VF를 vfio-pci에 바인딩 (위 절차와 동일)

# 3. DPDK testpmd로 VF 성능 테스트
dpdk-testpmd -l 0-3 -n 4 \
  -- --forward-mode=io --nb-cores=2 \
  --rxq=2 --txq=2 --stats-period=1

# DPDK 지원 VF PMD 드라이버
# Intel iavf PMD:    drivers/net/iavf/  (X710/E810 VF)
# Mellanox mlx5 PMD: drivers/net/mlx5/  (ConnectX VF)
# Virtio-user PMD:   DPDK vhost 통합 시 SR-IOV 대안

디버깅

# SR-IOV Capability 및 VF 상태 확인
lspci -vvv -s 0000:03:00.0 | grep -A 12 "SR-IOV"
# IOVCtl: Enable+ → VF 활성화됨
# Number of VFs: 4, Total VFs: 64

# 생성된 VF 디바이스 목록
lspci | grep "Virtual Function"
# 03:00.2 Ethernet [0200]: Intel [Virtual Function]

# IOMMU 그룹 격리 확인 (VF별 별도 그룹이어야 안전)
for vf in /sys/bus/pci/devices/0000:03:00.*/iommu_group; do
    echo "$(basename $(dirname $vf)): group $(readlink $vf | grep -o '[0-9]*$')"
done

# dmesg: SR-IOV 활성화 로그
dmesg | grep -i "sriov\|virtfn\|VF enable"
# pci 0000:03:00.0: 4 VFs enabled
# pci 0000:03:00.2: [8086:154c] type 00 class 0x020000

# VF 드라이버 바인딩 상태 확인
lspci -nnk -s 0000:03:00.2
# Kernel driver in use: vfio-pci  ← VM 패스스루 준비 완료
# Kernel driver in use: iavf      ← 호스트에서 직접 사용 중

# VF가 대규모일 때: ARI 활성화 여부 확인
lspci -vvv -s 0000:03:00.0 | grep "ARI Forwarding"
# ARI Forwarding: +  ← 256개 Function 지원 (64+ VF 가능)

# VF 통계 (PF 드라이버에서 조회)
ethtool -S enp3s0f0 | grep -i "vf"
SR-IOV 주요 제약사항:
  • 라이브 마이그레이션 불가 — VF는 하드웨어 상태를 가져 VFIO passthrough VM의 라이브 마이그레이션이 불가능합니다. vDPA(virtio Data Path Acceleration) 또는 virtio를 사용하면 마이그레이션 가능합니다.
  • VF 비활성화 전 VM 종료 필요pci_vfs_assigned()가 0이 아닐 때 pci_disable_sriov()는 -EPERM을 반환합니다.
  • ACS 미지원 스위치 경고 — ACS 없는 PCIe 스위치 아래 VF는 P2P DMA로 인해 IOMMU 격리가 불완전할 수 있습니다.
  • VF Config Space 제한 — VF는 FLR(Function Level Reset) 이외의 전원 관리·ASPM 기능이 없으며, Config Space의 일부 필드만 접근 가능합니다.
  • SR-IOV vs mdev 선택 — 하드웨어가 SR-IOV를 지원하면 성능이 뛰어나지만, 라이브 마이그레이션이 필요하거나 VF 수보다 더 많은 VM을 서비스할 때는 mdev 또는 virtio가 적합합니다.

AER (Advanced Error Reporting)

PCIe AER는 에러를 CorrectableUncorrectable (Non-Fatal / Fatal)로 분류하여 보고합니다:

분류예시처리
CorrectableBad TLP (CRC 오류 후 재전송 성공), Replay Timer Timeout하드웨어가 자동 복구, 카운터 증가
Uncorrectable Non-FatalCompletion Timeout, Unexpected Completion트랜잭션 실패, 디바이스 리셋 가능
Uncorrectable FatalData Link Protocol Error, Malformed TLP, Poisoned TLP링크 다운, 디바이스 리셋 필수
/* PCI 드라이버에서 AER 에러 복구 콜백 등록 */
static pci_ers_result_t
my_error_detected(struct pci_dev *pdev, pci_channel_state_t state)
{
    if (state == pci_channel_io_perm_failure)
        return PCI_ERS_RESULT_DISCONNECT;

    /* I/O 중단, 리소스 정리 */
    my_stop_io(pdev);
    return PCI_ERS_RESULT_NEED_RESET;
}

static pci_ers_result_t
my_slot_reset(struct pci_dev *pdev)
{
    /* 디바이스 재초기화 */
    if (my_reinit_hw(pdev))
        return PCI_ERS_RESULT_DISCONNECT;
    return PCI_ERS_RESULT_RECOVERED;
}

static void my_resume(struct pci_dev *pdev)
{
    /* I/O 재개 */
    my_restart_io(pdev);
}

static const struct pci_error_handlers my_err_handler = {
    .error_detected = my_error_detected,
    .slot_reset     = my_slot_reset,
    .resume         = my_resume,
};

static struct pci_driver my_driver = {
    /* ... */
    .err_handler = &my_err_handler,
};
AER 복구 흐름: error_detected() → (FLR 또는 Secondary Bus Reset) → slot_reset()resume(). 드라이버가 err_handler를 등록하지 않으면 커널은 디바이스를 비활성화합니다.

AER Extended Capability 레지스터

AER Extended Capability (ID 0001h)는 PCIe 확장 Config Space (오프셋 0x100+)에 위치하며, 에러 감지/보고/마스킹을 제어합니다:

오프셋레지스터설명
+00hAER Enhanced Capability HeaderID=0001h, Version, Next Pointer
+04hUncorrectable Error Status비트별 에러 발생 여부 (RW1C — 1 쓰기로 클리어)
+08hUncorrectable Error Mask마스크된 에러는 보고되지 않음
+0ChUncorrectable Error Severity0=Non-Fatal, 1=Fatal 지정
+10hCorrectable Error StatusCorrectable 에러 발생 여부 (RW1C)
+14hCorrectable Error Mask마스크 제어
+18hAdvanced Error Capabilities and ControlECRC 생성/확인 활성화, First Error Pointer
+1Ch~+28hHeader Log에러를 유발한 TLP 헤더 4 DW 캡처
+2ChRoot Error CommandRoot Port 전용: CE/NFE/FE 인터럽트 활성화
+30hRoot Error StatusRoot Port 전용: 에러 수신 상태
+34h~+38hError Source ID에러를 보고한 디바이스의 BDF
+3Ch~+44hTLP Prefix LogTLP Prefix 포함 에러 시 캡처 (PCIe 3.0+)
Uncorrectable Error 비트이름기본 Severity설명
4Data Link Protocol ErrorFatalDLLP/TLP 시퀀스 에러, 링크 레벨 실패
5Surprise Down ErrorFatal예기치 않은 링크 다운 (핫 리무브 등)
12Poisoned TLP ReceivedNon-FatalEP 비트가 설정된 TLP 수신 (데이터 오염)
13Flow Control Protocol ErrorFatal크레딧 프로토콜 위반
14Completion TimeoutNon-FatalNon-Posted 요청에 대한 Completion 미수신
15Completer AbortNon-FatalCompleter가 요청을 거부 (CA status)
16Unexpected CompletionNon-Fatal요청하지 않은 Completion 수신
18Malformed TLPFatalTLP 형식 오류 (잘못된 길이, 필드 등)
19ECRC ErrorNon-FatalEnd-to-End CRC 검증 실패
20Unsupported Request ErrorNon-Fatal디바이스가 처리할 수 없는 TLP 수신 (UR)
21ACS ViolationNon-FatalACS 정책에 의해 차단된 트랜잭션
# AER 에러 카운터 확인 (sysfs)
cat /sys/bus/pci/devices/0000:03:00.0/aer_dev_correctable
# RxErr 0 BadTLP 12 BadDLLP 0 Rollover 0 Timeout 0 NonFatalErr 0 ...

cat /sys/bus/pci/devices/0000:03:00.0/aer_dev_nonfatal
# Undefined 0 DLP 0 SDES 0 TLP 0 FCP 0 CmpltTO 3 CmpltAbrt 0 ...

# AER 에러 주입 (디버깅/테스트용)
echo 1 > /sys/kernel/debug/pci-error-inject/0000:03:00.0/inject_uncorrectable
# (CONFIG_PCIEAER_INJECT=y 필요)

# AER 트레이스포인트 활성화
echo 1 > /sys/kernel/debug/tracing/events/ras/aer_event/enable
cat /sys/kernel/debug/tracing/trace_pipe
# aer_event: 0000:03:00.0 PCIe Bus Error: severity=Corrected, type=Data Link Layer ...

AER 에러 처리 전체 흐름

AER 에러 처리 흐름 에러 발생 PCIe 디바이스 에러 로깅 Status 레지스터 기록 ERR_COR / ERR_NONFATAL / ERR_FATAL 메시지 TLP Root Port Root Error Status 기록 MSI/MSI-X IRQ → aer_irq() 커널 핸들러 aer_service_driver drivers/pci/pcie/aer.c Correctable 카운터 증가, 로그, 계속 운영 Uncorrectable Non-Fatal error_detected() → slot_reset() → resume() Uncorrectable Fatal 링크 리셋, 전체 버스 복구 PCI_ERS_RESULT_RECOVERED 정상 복귀, I/O 재개 PCI_ERS_RESULT_DISCONNECT 복구 실패, 디바이스 제거 커널 경로: aer_irq() → aer_process_err_devices() → pcie_do_recovery() → broadcast_error_message() 모든 영향받는 디바이스의 err_handler 콜백을 순서대로 호출하며, 결과에 따라 복구 또는 제거

DPC (Downstream Port Containment)

DPC는 PCIe 3.1에서 도입된 에러 격리 메커니즘으로, Uncorrectable Error 발생 시 해당 다운스트림 포트를 자동으로 비활성화하여 에러가 상위 버스로 전파되는 것을 방지합니다:

구분AER만 사용AER + DPC
에러 감지Root Port에서 ERR_* 메시지 수신다운스트림 포트에서 즉시 감지
에러 전파상위 버스에 영향 가능포트 비활성화로 격리 (하위만 영향)
복구 범위전체 버스 리셋 가능해당 포트 하위만 리셋
트리거소프트웨어 (커널 AER 드라이버)하드웨어 자동 (DPC Trigger Status)
커널 드라이버aer.cdpc.c (PCIe Port Service)
/* DPC Extended Capability (ID 001Dh) 핵심 레지스터 */

/* DPC Control Register */
/*
 *   [1:0] DPC Trigger Enable:
 *         00 = 비활성화
 *         01 = ERR_FATAL만
 *         10 = ERR_NONFATAL 또는 ERR_FATAL
 *   [2]   Completion Control: 1 = DPC 트리거 시 UR Completion 반환
 *   [3]   Interrupt Enable: DPC 이벤트 인터럽트 활성화
 *   [4]   ERR_COR Enable: Correctable 에러도 DPC 트리거 (RP만)
 */

/* DPC Status Register */
/*
 *   [0]   Trigger Status: 1 = DPC가 트리거됨 (포트 비활성화 상태)
 *   [1]   Trigger Reason: 0 = ERR_FATAL, 1 = ERR_NONFATAL
 *   [2]   Interrupt Status: 인터럽트 발생 여부
 *   [4:3] Trigger Reason Extension: RP PIO 에러 세부 이유
 */

/* Linux DPC 드라이버 (drivers/pci/pcie/dpc.c)
 *
 * DPC 트리거 발생 시:
 * 1. dpc_irq() ISR 호출 → 워크큐로 지연 처리
 * 2. dpc_process_error() — Header Log 읽기, 에러 원인 로깅
 * 3. pcie_do_recovery() 호출 — AER와 동일한 복구 경로 사용
 * 4. 복구 성공 시 DPC Trigger Status 클리어 → 포트 재활성화
 */
DPC 실전 확인:
# DPC Capability 확인
lspci -vvv -s 00:1c.0 | grep -A 5 "Downstream Port Containment"
#   DpcCap: INT Msg #0, RPExt+ PoisonedTLP+ SwTrigger+ RP PIO Log 4 ...
#   DpcCtl: Trigger:2 Cmpl+ INT+ ERR_COR- ...
#   DpcSta: Trigger- Reason:00 ...

# DPC 이벤트 로그
dmesg | grep -i dpc
# pcieport 0000:00:1c.0: DPC: containment event, status:0x1f01 source:0x0300

ACS (Access Control Services)

ACS는 PCIe 디바이스 간의 직접 통신(peer-to-peer)을 제어하여 IOMMU 격리를 강화합니다. 가상화 환경에서 VF 간의 트래픽이 IOMMU를 우회하는 것을 방지합니다:

ACS 비트이름설명
VACS Source ValidationRequester ID가 업스트림 포트의 것인지 검증
BACS Translation BlockingATS(Address Translation Services) 번역된 TLP 차단
RACS P2P Request Redirect피어 간 직접 요청을 업스트림으로 강제 리다이렉트
CACS P2P Completion Redirect피어 간 Completion을 업스트림으로 리다이렉트
UACS Upstream Forwarding업스트림으로 전달되어야 할 TLP의 포워딩 허용
EACS P2P Egress Control특정 피어로의 직접 전송을 비트맵으로 제어
TACS Direct Translated P2PATS 번역된 P2P 요청에 대한 세밀한 제어
IOMMU 격리와 ACS:
  • IOMMU는 디바이스 메모리 접근을 격리하지만, PCIe 스위치 내부에서 피어 디바이스 간 직접 통신(P2P)이 가능하면 IOMMU를 우회할 수 있습니다.
  • ACS는 이러한 P2P 트래픽을 Root Complex 방향(업스트림)으로 강제 리다이렉트하여 IOMMU를 반드시 거치게 합니다.
  • ACS가 없는 PCIe 스위치에 연결된 디바이스들은 같은 IOMMU 그룹에 배치되어 개별 격리가 불가능합니다.
  • 커널은 pci_acs_enabled()로 ACS 상태를 확인하며, VFIO는 IOMMU 그룹 단위로 디바이스를 할당합니다.
  • ACS Override: ACS를 지원하지 않는 하드웨어에서 pcie_acs_override=downstream,multifunction 커널 파라미터로 강제 분리할 수 있지만, 보안 위험이 있습니다.
# ACS Capability 확인
lspci -vvv -s 00:1c.0 | grep -A 3 "Access Control Services"
# ACSCap: SrcValid+ TransBlk+ ReqRedir+ CmpltRedir+ UpFwd- EgressCtrl- DirectTrans-
# ACSCtl: SrcValid+ TransBlk+ ReqRedir+ CmpltRedir+ UpFwd- EgressCtrl- DirectTrans-

# IOMMU 그룹 확인 (ACS의 결과)
find /sys/kernel/iommu_groups/ -type l | sort -V
# /sys/kernel/iommu_groups/1/devices/0000:03:00.0
# /sys/kernel/iommu_groups/2/devices/0000:04:00.0  ← ACS 덕분에 별도 그룹

전원 관리

PCI 전원 상태 (D-States)

상태설명복원 시간
D0완전 동작 상태즉시
D1절전 (일부 컨텍스트 유지, 선택적)빠름
D2더 깊은 절전 (선택적)중간
D3hot소프트웨어 절전, Vaux 유지, Config Space 접근 가능10ms+
D3cold전원 완전 차단, 재열거 필요100ms+
/* PCI 드라이버 전원 관리 콜백 */
static int my_suspend(struct device *dev)
{
    struct pci_dev *pdev = to_pci_dev(dev);

    /* 디바이스 I/O 중단, 상태 저장 */
    my_stop_hw(pdev);
    pci_save_state(pdev);
    pci_disable_device(pdev);
    pci_set_power_state(pdev, PCI_D3hot);
    return 0;
}

static int my_resume(struct device *dev)
{
    struct pci_dev *pdev = to_pci_dev(dev);

    pci_set_power_state(pdev, PCI_D0);
    pci_restore_state(pdev);
    pci_enable_device(pdev);
    pci_set_master(pdev);
    my_start_hw(pdev);
    return 0;
}

static DEFINE_SIMPLE_DEV_PM_OPS(my_pm_ops, my_suspend, my_resume);

static struct pci_driver my_driver = {
    /* ... */
    .driver.pm = pm_sleep_ptr(&my_pm_ops),
};

ASPM (Active State Power Management)

PCIe 링크가 유휴 상태일 때 자동으로 저전력 링크 상태로 전환합니다:

상태설명진입/탈출 지연
L0완전 동작 (정상 전송)
L0s빠른 저전력, 단방향~1 μs
L1깊은 저전력, 양방향 링크 비활성2~10 μs
L1.1L1 Substate, PLL 꺼짐~32 μs
L1.2L1 Substate, 공통 모드 전압 제거~32~100 μs
L2/L3보조 전원 / 전원 차단ms 단위
# ASPM 정책 확인 및 설정
cat /sys/module/pcie_aspm/parameters/policy
# [default] performance powersave powersupersave

# 커널 부팅 파라미터
pcie_aspm=off           # ASPM 완전 비활성화 (저지연 요구 시)
pcie_aspm.policy=powersave  # 절전 우선
ASPM과 레이턴시: ASPM L1은 전력을 크게 절약하지만, 링크 복원에 수 마이크로초가 소요됩니다. NVMe SSD나 저지연 네트워크 카드에서는 pcie_aspm=off를 고려하세요.

L1 서브스테이트 (L1.1 / L1.2) 상세

PCIe 3.1에서 도입된 L1 PM Substates (Extended Capability ID 001Eh)는 L1 상태 내에서 더 세밀한 전력 절약을 제공합니다:

PCIe 링크 전원 상태와 L1 서브스테이트 L0 (Active) 전력: 100% | 지연: 0 L0s 전력: 60% | ~1 µs L1 (ASPM) 전력: 40% | 2~10 µs L1 PM Substates (Extended Capability 001Eh) L1.1 (ASPM) PLL OFF 전력: 20% | ~32 µs L1.1 (PCI-PM) D3hot에 의한 진입 전력: 20% L1.2 (ASPM) PLL + 레퍼런스 클럭 OFF 전력: 5% | ~100 µs L1.2 (PCI-PM) D3hot + 클럭 OFF 전력: 5% • L1.1: PLL 정지, 전기 신호 유지. CLKREQ# 핀으로 클럭 게이팅. Common mode voltage 유지 • L1.2: PLL + 레퍼런스 클럭 모두 정지. Common mode voltage 제거. 가장 깊은 절전 (SoC 전력 도메인 차단 가능) • ASPM 변형: 하드웨어가 유휴 감지 후 자동 진입 | PCI-PM 변형: 소프트웨어(D3hot)가 트리거 • T_POWER_ON: L1.2에서 L0 복귀에 필요한 시간 (디바이스가 광고). 이 값이 워크로드 지연 예산을 초과하면 L1.2 비활성화 권장 LTR (Latency Tolerance Reporting): 디바이스가 허용 가능한 최대 서비스 지연을 보고 → 플랫폼이 이를 기반으로 ASPM/C-state 깊이를 결정합니다.
# L1 Substates Capability 확인
lspci -vvv -s 03:00.0 | grep -A 10 "L1 PM Substates"
# L1SubCap: PCI-PM_L1.2+ PCI-PM_L1.1+ ASPM_L1.2+ ASPM_L1.1+ L1_PM_Substates+
#           PortCommonModeRestoreTime=60us PortTPowerOnTime=10us
# L1SubCtl1: PCI-PM_L1.2+ PCI-PM_L1.1+ ASPM_L1.2+ ASPM_L1.1+
#            T_CommonMode=0us LTR1.2_Threshold=163840ns

# LTR (Latency Tolerance Reporting) 확인
lspci -vvv -s 03:00.0 | grep LTR
# LTR+  DevCtl2: ... LTRen+
# LTR Max snoop latency: 3145728ns
# LTR Max no snoop latency: 3145728ns

# CLKREQ# 핀 상태 확인 (플랫폼 의존)
# L1.2 진입에는 CLKREQ# 핀이 물리적으로 연결되어야 합니다.
# 서버에서는 CLKREQ#가 연결되지 않아 L1.2 불가능한 경우가 많습니다.

PCIe 리셋 메커니즘

PCIe는 여러 수준의 리셋을 정의하며, 각 리셋의 범위와 복원 수준이 다릅니다:

PCIe 리셋 유형과 영향 범위 Fundamental Reset (Cold/Warm) PERST# 핀 assert 또는 전원 사이클. 전체 계층 초기화. Config Space 초기화, BAR 클리어, 링크 재훈련 Hot Reset (Secondary Bus Reset) Bridge의 Secondary Bus Reset 비트 설정. 하위 버스 전체 리셋. Config Space 초기화 (Bridge 자신은 유지) FLR (Function Level Reset) 개별 펑션만 리셋. 내부 상태 초기화, 미처리 트랜잭션 폐기. BAR/Command 레지스터는 보존 PM Reset (D3hot → D0 전환) 가장 가벼운 리셋. D3hot에서 D0로 전환 시 내부 로직 초기화. Config Space는 대부분 보존 넓음 좁음
리셋 유형트리거 방법범위Config Space커널 API
Fundamental (Cold)전원 차단 → 재인가전체 시스템완전 초기화
Fundamental (Warm)PERST# 핀 assert전체 PCIe 계층완전 초기화
Hot ResetBridge: Secondary Bus Reset하위 버스 전체완전 초기화pci_reset_bus()
FLRDevice Control: Initiate FLR단일 펑션대부분 보존pci_reset_function()
PM ResetD3hot → D0 전환단일 펑션대부분 보존pci_set_power_state()
AF FLRAdvanced Features FLR단일 펑션대부분 보존pci_reset_function()
/* 커널에서 PCIe 리셋 수행 */

/* 1. FLR (Function Level Reset) — 가장 세밀한 리셋 */
int ret = pci_reset_function(pdev);
/* FLR → AF FLR → PM Reset → Bus Reset 순서로 시도
 * pci_probe_reset_function()으로 지원 여부 확인 가능 */

/* 2. 잠금 없는 FLR (드라이버 내부에서 직접 호출 시) */
pci_reset_function_locked(pdev);

/* 3. Secondary Bus Reset — 하위 버스 전체 리셋 */
pci_reset_bus(pdev);
/* Bridge의 Secondary Bus Reset 비트를 설정/해제
 * 하위 버스의 모든 디바이스가 리셋됨 — 다른 디바이스에 영향 주의! */

/* 4. sysfs를 통한 리셋 */
/* echo 1 > /sys/bus/pci/devices/0000:03:00.0/reset */

/* 5. FLR 지원 여부 확인 */
if (pdev->has_flr) {
    pr_info("Device supports FLR\n");
    pcie_flr(pdev);  /* FLR 직접 수행 (100ms 대기 포함) */
}

/* 리셋 후 주의사항:
 * - FLR 후에도 BAR 매핑은 유지되지만, 디바이스 내부 상태는 초기화됨
 * - MSI/MSI-X는 재설정 필요: pci_alloc_irq_vectors() 재호출
 * - Bus Master, Memory Space 비트 재활성화 필요
 * - DMA 매핑은 유효하지만, 디바이스가 DMA를 중단한 상태이므로 재시작 필요
 */
리셋 사용 시 주의:
  • Bus Reset은 같은 버스의 모든 디바이스에 영향을 줍니다. SR-IOV VF를 포함한 모든 펑션이 리셋됩니다.
  • FLR 후 100ms 대기가 필수입니다. 디바이스가 내부 초기화를 완료할 시간이 필요합니다.
  • VFIO 패스스루 환경에서 VM 재부팅 시 FLR을 사용하여 이전 VM의 상태를 완전히 클리어해야 합니다. FLR을 지원하지 않는 디바이스는 패스스루에 적합하지 않습니다.
  • D3cold → D0은 Fundamental Reset과 동등하며, 디바이스 재열거가 필요할 수 있습니다.

PCI 핫플러그 (Hotplug)

PCIe 디바이스의 런타임 삽입/제거를 지원하는 메커니즘:

메커니즘설명
Native PCIe HotplugPCIe Slot Capability 기반, 커널 pciehp 드라이버
ACPI HotplugACPI _HPP/_HPX 메서드 기반, 서버 플랫폼
Thunderbolt/USB4PCIe tunneling, 동적 디바이스 연결/해제
Surprise Removal사전 알림 없는 제거, 드라이버가 적절히 처리해야 함
# 수동 핫플러그 (sysfs)
# 디바이스 제거
echo 1 > /sys/bus/pci/devices/0000:03:00.0/remove

# 버스 재스캔 (새 디바이스 탐지)
echo 1 > /sys/bus/pci/rescan

# 특정 브릿지 하위만 재스캔
echo 1 > /sys/bus/pci/devices/0000:00:1c.0/rescan

VFIO (Virtual Function I/O)

VFIO는 PCI 디바이스를 유저스페이스(또는 VM)에 안전하게 직접 노출하는 프레임워크입니다. IOMMU 기반 격리를 통해 호스트 메모리 보호를 보장합니다.

개념설명
VFIO Container/dev/vfio/vfio — IOMMU 컨텍스트 관리
VFIO Group/dev/vfio/<N> — IOMMU 그룹 단위 디바이스 집합
VFIO Device그룹 내 개별 디바이스 — Config Space, BAR, 인터럽트 접근
IOMMU Group동일 IOMMU 도메인을 공유하는 디바이스 집합 (ACS 기반 분리)
# VFIO 디바이스 패스스루 설정 (QEMU/KVM)
# 1. 디바이스를 vfio-pci에 바인딩
modprobe vfio-pci
echo "8086 1572" > /sys/bus/pci/drivers/vfio-pci/new_id

# 2. QEMU에서 디바이스 할당
qemu-system-x86_64 \
  -device vfio-pci,host=0000:03:00.0 \
  ...

# IOMMU 그룹 확인
ls -l /sys/bus/pci/devices/0000:03:00.0/iommu_group/devices/
심화 학습: VFIO의 IOMMU 그룹 격리, vfio-pci 드라이버 바인딩, mdev(Mediated Device) 프레임워크, GPU passthrough 실전 설정은 VFIO & mdev (디바이스 패스스루) 페이지에서 자세히 다룹니다.

P2P DMA (Peer-to-Peer DMA)

PCIe Peer-to-Peer DMA는 두 PCIe 디바이스 간 CPU/시스템 메모리를 거치지 않고 직접 데이터를 전송합니다:

사용 사례설명
NVMe → GPUGPUDirect Storage — 스토리지에서 GPU 메모리로 직접 전송
NVMe → NVMeNVMe 컨트롤러 간 직접 복사
NVMe → RDMA NICNVMe-oF 타겟에서 네트워크로 직접 전송
/* P2P DMA 사용 (커널 5.x+) */
#include <linux/pci-p2pdma.h>

/* P2P 가능 여부 확인 */
if (pci_p2pdma_distance(provider, client, false) < 0)
    return -EOPNOTSUPP;  /* 다른 RC 하위이거나 Switch 미지원 */

/* P2P 메모리 할당 (provider 디바이스의 BAR에서) */
void *p2p_mem = pci_alloc_p2pmem(provider, size);

/* DMA 매핑 (client 디바이스 관점) */
dma_addr_t dma = pci_p2pdma_map_sg(...);
P2P 제약: 두 디바이스가 동일 Root Complex 또는 PCIe Switch 하위에 있어야 합니다. CPU의 Root Complex가 P2P TLP 라우팅을 지원하지 않으면 동작하지 않습니다.

NTB (Non-Transparent Bridge)

NTB(Non-Transparent Bridge)는 두 개의 독립적인 PCIe 계층(hierarchy)을 연결하는 특수 PCIe 브리지 디바이스입니다. 일반적인 PCIe 투명 브리지(Transparent Bridge)와 달리, NTB는 양쪽 호스트 각각에게 독립적인 PCIe 엔드포인트로 보이며, 각 호스트가 자체 PCI 버스 번호와 메모리 주소 공간을 독립적으로 관리할 수 있게 합니다.

NTB의 주요 사용 사례:

Host A CPU A MEM Root Complex A NTB Endpoint (Host A 관점) Host A가 보는 NTB 리소스 BAR0: Doorbell Regs BAR2: Scratchpad Regs BAR4: Memory Window 주소 변환 (Inbound) MW 쓰기 → NTB 변환 → Host B 메모리에 도달 통신 메커니즘 1. Doorbell → 상대 호스트 인터럽트 2. Scratchpad → 초기 핸드셰이크 3. MW → 대용량 데이터 전송 NTB Device Primary Side Secondary Side Address Translation Unit Doorbell Regs Doorbell Regs Scratchpad Scratchpad Memory Window A Memory Window B Link Status / Control PCIe Host B CPU B MEM Root Complex B NTB Endpoint (Host B 관점) Host B가 보는 NTB 리소스 BAR0: Doorbell Regs BAR2: Scratchpad Regs BAR4: Memory Window 주소 변환 (Inbound) MW 쓰기 → NTB 변환 → Host A 메모리에 도달 통신 메커니즘 1. Doorbell → 상대 호스트 인터럽트 2. Scratchpad → 초기 핸드셰이크 3. MW → 대용량 데이터 전송 PCIe

NTB 하드웨어 구성요소

NTB 디바이스는 두 호스트 간의 통신을 위해 다음과 같은 하드웨어 리소스를 제공합니다:

구성요소크기용도설명
Doorbell비트 단위 (보통 16~64비트)호스트 간 인터럽트한 호스트가 특정 비트를 설정하면 상대 호스트에 MSI/MSI-X 인터럽트가 발생합니다. 이벤트 알림, 데이터 수신 통지 등에 사용됩니다.
Scratchpad수십 바이트 (레지스터 수 × 4/8B)소량 공유 레지스터양쪽 호스트가 읽기/쓰기 가능한 작은 레지스터 세트입니다. 초기 핸드셰이크, 메모리 윈도우 주소 교환, 상태 플래그 전달에 사용됩니다.
Memory Window (MW)수 MB ~ 수 GB (BAR 크기에 의존)대용량 주소 변환 영역한 호스트의 MMIO 영역에 쓰면 NTB의 주소 변환 유닛(ATU)이 이를 상대 호스트의 물리 메모리 주소로 변환합니다. 대용량 데이터 전송의 핵심입니다.
Link Status레지스터링크 상태 모니터링NTB 양쪽의 PCIe 링크가 정상적으로 연결되었는지 확인합니다. 링크 업/다운 이벤트를 인터럽트로 통지받을 수 있습니다.

NTB 하드웨어 구현체

리눅스 커널에서 지원하는 주요 NTB 하드웨어:

하드웨어커널 드라이버특징
Intel Xeon (Sandy Bridge ~ Skylake)ntb_hw_intelIntel QPI/UPI 기반 NTB, B2B(Back-to-Back) 및 RP(Root Port) 모드 지원
AMDntb_hw_amdAMD Data Fabric 기반 NTB, Zen 아키텍처 지원
IDT 89HPESxxntb_hw_idtIDT(현 Renesas) PCIe 스위치 칩, 다중 NT 포트 지원, 유연한 토폴로지
Microsemi/Microchip Switchtecntb_hw_switchtecSwitchtec PCIe 스위치 기반 NTB, 핫플러그 지원, 높은 대역폭
AMD EPYCntb_hw_epycAMD EPYC 서버 프로세서 전용 NTB, NUMA 인식 최적화

NTB 통신 흐름

NTB 통신 흐름 — 3단계 1단계: Scratchpad 핸드셰이크 (초기화) Host A MW 기본주소 기록 spad_write(0, mw_addr) NTB Scratchpad 주소 교환 레지스터 peer_spad_read(0) Host B 상대 MW 주소 획득 2단계: Doorbell 인터럽트 (이벤트 알림) Host A 데이터 준비 완료 peer_db_set(BIT(0)) NTB Doorbell MSI-X 인터럽트 생성 IRQ → db_event() Host B 인터럽트 수신 3단계: Memory Window 데이터 전송 (대용량) Host A memcpy_toio(mw, data, len) MMIO Write (PCIe TLP) NTB ATU 주소 변환: HostA local → HostB phys 변환된 주소로 전달 Host B 메모리 데이터 도착 (DMA 가능) Scratchpad으로 주소 교환 → Memory Window 설정 → 데이터 전송 → Doorbell로 완료 알림

Linux NTB 서브시스템 아키텍처

리눅스 커널의 NTB 서브시스템(drivers/ntb/)은 3계층 구조로 설계되어 있습니다:

사용자 공간 (User Space) ip link set ntb0 up ntb_tool via debugfs ntb_perf sysfs ─── 커널 공간 ─── ntb_netdev 가상 이더넷 (ntb0) ntb_transport 링 버퍼 메시지 전달 ntb_perf DMA 벤치마크 ntb_tool debugfs 디버깅 NTB Core (ntb.ko) 통합 API: ntb_register_client, ntb_db_*, ntb_spad_*, ntb_mw_* ntb_hw_intel Intel Xeon NTB ntb_hw_amd AMD NTB ntb_hw_idt IDT PCIe Switch ntb_hw_switchtec Microsemi Switchtec PCIe NTB Hardware (물리 디바이스)

NTB API 코드 예제

NTB 클라이언트 드라이버를 작성하기 위한 핵심 API:

#include <linux/ntb.h>
#include <linux/module.h>

struct my_ntb_ctx {
    struct ntb_dev *ntb;
    void __iomem *mw_base;     /* Memory Window 가상 주소 */
    resource_size_t mw_size;   /* Memory Window 크기 */
    dma_addr_t mw_phys;        /* DMA 물리 주소 */
    void *rx_buf;              /* 수신 버퍼 */
};

/* Doorbell 이벤트 콜백 — 상대 호스트가 Doorbell을 울릴 때 호출 */
static void my_db_event(void *ctx, int vec)
{
    struct my_ntb_ctx *c = ctx;
    u64 db_bits;

    /* 어떤 Doorbell 비트가 설정되었는지 읽기 */
    db_bits = ntb_db_read(c->ntb);
    pr_info("NTB doorbell event: 0x%llx\n", db_bits);

    /* Doorbell 비트 클리어 */
    ntb_db_clear(c->ntb, db_bits);

    /* 수신 데이터 처리 */
    process_received_data(c);
}

/* 링크 상태 변경 콜백 */
static void my_link_event(void *ctx)
{
    struct my_ntb_ctx *c = ctx;

    if (ntb_link_is_up(c->ntb, NULL, NULL) == 1)
        pr_info("NTB link is UP\n");
    else
        pr_info("NTB link is DOWN\n");
}

static const struct ntb_ctx_ops my_ntb_ops = {
    .link_event = my_link_event,
    .db_event   = my_db_event,
};

/* NTB 디바이스 probe — NTB 코어가 매칭된 디바이스 발견 시 호출 */
static int my_ntb_probe(struct ntb_client *self,
                         struct ntb_dev *ntb)
{
    struct my_ntb_ctx *ctx;
    int rc, mw_count, spad_count;
    resource_size_t mw_size;

    ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
    if (!ctx)
        return -ENOMEM;
    ctx->ntb = ntb;

    /* 리소스 확인 */
    mw_count = ntb_mw_count(ntb, 0);         /* Memory Window 개수 */
    spad_count = ntb_spad_count(ntb);         /* Scratchpad 레지스터 수 */
    pr_info("NTB: %d MWs, %d SPADs\n", mw_count, spad_count);

    /* Memory Window 크기 조회 및 매핑 설정 */
    rc = ntb_mw_get_align(ntb, 0, 0, &mw_size, NULL, NULL, NULL);
    if (rc)
        goto err_free;

    /* 수신 버퍼 할당 (DMA 가능) */
    ctx->rx_buf = dma_alloc_coherent(&ntb->pdev->dev, mw_size,
                                      &ctx->mw_phys, GFP_KERNEL);
    if (!ctx->rx_buf) {
        rc = -ENOMEM;
        goto err_free;
    }

    /* Memory Window 변환 설정: 상대 호스트의 쓰기가 rx_buf에 도착 */
    rc = ntb_mw_set_trans(ntb, 0, 0, ctx->mw_phys, mw_size);
    if (rc)
        goto err_dma;

    /* Scratchpad에 준비 완료 플래그 기록 (상대 호스트가 읽음) */
    ntb_peer_spad_write(ntb, 0, 0, NTB_READY_MAGIC);

    /* Doorbell 마스크 해제 — 인터럽트 수신 활성화 */
    ntb_db_set_mask(ntb, 0);       /* 먼저 모든 비트 마스크 */
    ntb_db_clear_mask(ntb, 0x1);  /* 비트 0만 언마스크 */

    /* 컨텍스트 등록 및 링크 활성화 */
    ntb_set_ctx(ntb, ctx, &my_ntb_ops);
    ntb_link_enable(ntb, NTB_SPEED_AUTO, NTB_WIDTH_AUTO);

    return 0;

err_dma:
    dma_free_coherent(&ntb->pdev->dev, mw_size, ctx->rx_buf, ctx->mw_phys);
err_free:
    kfree(ctx);
    return rc;
}

static void my_ntb_remove(struct ntb_client *self,
                           struct ntb_dev *ntb)
{
    struct my_ntb_ctx *ctx = ntb_get_ctx(ntb);

    ntb_link_disable(ntb);
    ntb_clear_ctx(ntb);
    ntb_mw_clear_trans(ntb, 0, 0);
    dma_free_coherent(&ntb->pdev->dev, ctx->mw_size,
                      ctx->rx_buf, ctx->mw_phys);
    kfree(ctx);
}

static struct ntb_client my_ntb_client = {
    .ops = {
        .probe  = my_ntb_probe,
        .remove = my_ntb_remove,
    },
};
module_ntb_client(my_ntb_client);

ntb_transport: 링 버퍼 기반 메시지 전달

ntb_transport는 NTB Memory Window 위에 신뢰성 있는 메시지 전달 계층을 구축합니다. 내부적으로 각 Memory Window를 링 버퍼로 분할하여 사용합니다:

/* ntb_transport 링 버퍼 디스크립터 (간략화) */
struct ntb_transport_qp {             /* Queue Pair */
    struct ntb_transport_ctx *transport;
    struct ntb_dev *ndev;

    void __iomem *tx_mw;               /* 상대 호스트 MW에 매핑 (쓰기용) */
    dma_addr_t tx_mw_phys;
    unsigned int tx_index;
    unsigned int tx_max_entry;
    unsigned int tx_max_frame;

    void *rx_buff;                      /* 자신의 수신 버퍼 */
    unsigned int rx_index;
    unsigned int rx_max_entry;
    unsigned int rx_max_frame;

    void (*rx_handler)(struct ntb_transport_qp *qp,
                       void *data, int len);
    u64 db_bit;                        /* 이 QP용 Doorbell 비트 */
};

/* 메시지 전송 흐름 */
static int ntb_transport_tx(struct ntb_transport_qp *qp,
                            void *data, unsigned int len)
{
    struct ntb_payload_header *hdr;
    void __iomem *dest;

    /* TX 링에 빈 슬롯이 있는지 확인 */
    if (ntb_transport_tx_free_entry(qp) == 0)
        return -EAGAIN;

    /* 상대 호스트의 Memory Window에 직접 쓰기 */
    dest = qp->tx_mw + qp->tx_index * qp->tx_max_frame;
    memcpy_toio(dest + sizeof(*hdr), data, len);

    /* 헤더 기록 (길이, 플래그) */
    hdr = (struct ntb_payload_header __iomem *)dest;
    iowrite32(len, &hdr->len);
    iowrite32(NTB_PAYLOAD_VALID, &hdr->flags);

    /* 인덱스 갱신 */
    qp->tx_index = (qp->tx_index + 1) % qp->tx_max_entry;

    /* Doorbell로 상대 호스트에 알림 */
    ntb_peer_db_set(qp->ndev, qp->db_bit);
    return 0;
}

ntb_netdev: 가상 이더넷 인터페이스

ntb_netdevntb_transport 위에 구현된 가상 네트워크 드라이버로, NTB 링크를 표준 이더넷 인터페이스(ntb0)로 노출합니다. 이를 통해 TCP/IP 스택, SSH, NFS 등 일반적인 네트워크 애플리케이션을 NTB 위에서 직접 사용할 수 있습니다.

# NTB 하드웨어 드라이버 로드 (Intel 예시)
modprobe ntb_hw_intel
# NTB 전송 계층 로드
modprobe ntb_transport
# NTB 네트워크 인터페이스 생성
modprobe ntb_netdev

# Host A에서 IP 설정
ip addr add 10.0.0.1/24 dev ntb0
ip link set ntb0 up

# Host B에서 IP 설정
ip addr add 10.0.0.2/24 dev ntb0
ip link set ntb0 up

# 연결 테스트
ping 10.0.0.2
# 대역폭 측정
iperf3 -s               # Host B (서버)
iperf3 -c 10.0.0.2      # Host A (클라이언트)

ntb_perf: DMA 성능 벤치마크

ntb_perf는 NTB Memory Window를 통한 DMA 전송 성능을 측정하는 벤치마크 모듈입니다. sysfs 인터페이스를 통해 제어합니다:

# ntb_perf 모듈 로드
modprobe ntb_perf

# 전송 크기 설정 (바이트)
echo 1048576 > /sys/kernel/ntb_perf/0000:03:00.0/run

# 결과 확인
cat /sys/kernel/ntb_perf/0000:03:00.0/run
# 출력 예: "1048576 bytes in 524 usecs, 16.0 Gbps"

# DMA 엔진을 사용한 전송 (CPU 오프로드)
echo 1 > /sys/kernel/ntb_perf/0000:03:00.0/use_dma
echo 4194304 > /sys/kernel/ntb_perf/0000:03:00.0/run

# 여러 스레드로 병렬 벤치마크
echo 4 > /sys/kernel/ntb_perf/0000:03:00.0/threads
echo 1048576 > /sys/kernel/ntb_perf/0000:03:00.0/run

ntb_tool: debugfs 디버깅 인터페이스

ntb_tool은 NTB 하드웨어의 저수준 레지스터에 직접 접근할 수 있는 debugfs 기반 디버깅 도구입니다:

# ntb_tool 모듈 로드
modprobe ntb_tool

# debugfs 마운트 확인
mount -t debugfs none /sys/kernel/debug 2>/dev/null

# NTB 디바이스의 debugfs 경로
ls /sys/kernel/debug/ntb_tool/0000:03:00.0/

# Scratchpad 레지스터 읽기/쓰기
cat /sys/kernel/debug/ntb_tool/0000:03:00.0/spad
echo "0:0xDEADBEEF" > /sys/kernel/debug/ntb_tool/0000:03:00.0/peer_spad

# Doorbell 상태 확인
cat /sys/kernel/debug/ntb_tool/0000:03:00.0/db
# Doorbell 설정 (상대 호스트에 인터럽트 전송)
echo 0x1 > /sys/kernel/debug/ntb_tool/0000:03:00.0/peer_db

# Memory Window 정보
cat /sys/kernel/debug/ntb_tool/0000:03:00.0/mw_trans0

# 링크 상태
cat /sys/kernel/debug/ntb_tool/0000:03:00.0/link

NTB 디바이스 검색 및 BIOS 설정

NTB 디바이스는 표준 PCIe 열거(enumeration) 과정에서 검색됩니다. BIOS/UEFI에서 NTB 관련 설정이 필요한 경우가 많습니다:

/* Intel Xeon NTB BIOS 설정 예시 (lspci로 확인) */

/* NTB 디바이스가 올바르게 열거되었는지 확인 */
/*
 * $ lspci -vv -d 8086:6f0d
 * 03:00.0 Bridge: Intel Corporation Xeon NTB
 *   Control: I/O- Mem+ BusMaster+ ...
 *   Region 0: Memory at ... [size=64K]   ← Doorbell/Scratchpad
 *   Region 2: Memory at ... [size=1M]    ← Memory Window 0
 *   Region 4: Memory at ... [size=256M]  ← Memory Window 1
 *   Capabilities: [40] Express Endpoint, MSI-X
 */

/* Device Tree 바인딩 예시 (ARM 임베디드 플랫폼) */
/*
 * ntb@10000000 {
 *     compatible = "vendor,pcie-ntb";
 *     reg = <0x10000000 0x10000   // 제어 레지스터
 *            0x10100000 0x100000  // Memory Window 0
 *            0x10200000 0x1000000>; // Memory Window 1
 *     interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
 *     num-mws = <2>;
 *     num-spads = <16>;
 * };
 */

NTB vs 대안 기술 비교

특성NTBCXLRDMA (InfiniBand/RoCE)공유 메모리 (SMP)
물리 계층PCIePCIe 물리 계층InfiniBand / Ethernet메모리 버스
지연 시간~1-5 us~200-400 ns~1-3 us~100 ns
대역폭PCIe 세대에 의존 (최대 ~64 GB/s)PCIe 세대에 의존최대 ~400 Gbps메모리 채널 대역폭
캐시 코히런시없음 (소프트웨어 관리)하드웨어 지원없음하드웨어 지원
호스트 수2~수 개 (스위치 기반 시 확장)다수 (CXL 스위치)수백~수천2~8 (NUMA)
CPU 부하중간 (memcpy 필요)낮음 (load/store)낮음 (RDMA 오프로드)낮음 (load/store)
프로그래밍 모델Memory Window + Doorbellload/store (캐시 코히런트)verb API / RDMA ops일반 메모리 접근
주요 용도듀얼 컨트롤러, HA메모리 풀링, 가속기HPC, 데이터센터단일 시스템
리눅스 지원drivers/ntb/drivers/cxl/drivers/infiniband/기본 내장
NTB 사용 시 주의사항:
  • BAR 크기 제한: Memory Window 크기는 NTB 디바이스의 BAR 크기에 의존합니다. BIOS에서 BAR 크기를 충분히 크게 설정하지 않으면 데이터 전송 효율이 저하됩니다. 일부 플랫폼은 최대 BAR 크기가 256MB로 제한됩니다.
  • IOMMU 상호작용: IOMMU(VT-d/AMD-Vi)가 활성화된 환경에서는 NTB Memory Window에 대한 DMA 매핑이 올바르게 설정되어야 합니다. intel_iommu=on 상태에서 NTB가 동작하지 않으면 IOMMU 도메인 설정을 확인하세요.
  • DMA 코히런시: 두 호스트 간에는 하드웨어 캐시 코히런시가 없습니다. 데이터를 기록한 후에는 반드시 wmb()(쓰기 배리어) 또는 dma_wmb()를 사용하고, 읽기 전에는 rmb() 또는 dma_rmb()를 사용해야 합니다.
  • 바이트 순서: 두 호스트의 엔디안이 다를 수 있으므로, 공유 데이터 구조는 고정 엔디안(le32_to_cpu() 등)을 사용해야 합니다.
  • 오류 복구: 상대 호스트가 리부팅되면 NTB 링크가 끊어집니다. 링크 이벤트 콜백을 반드시 구현하여 재연결 로직을 처리하세요.
실전 활용 및 성능 기대치:
  • 스토리지 듀얼 컨트롤러: NTB를 통해 두 스토리지 컨트롤러가 메타데이터와 캐시를 동기화하면 Active-Active 구성이 가능합니다. NVRAM 미러링에 NTB DMA를 활용하면 마이크로초 단위 동기화가 가능합니다.
  • 성능 기대치: PCIe Gen3 x16 기준 약 12-14 GB/s, PCIe Gen4 x16 기준 약 25-28 GB/s의 단방향 처리량을 기대할 수 있습니다. ntb_perf로 실측하여 확인하세요.
  • ntb_netdev 성능: TCP/IP 오버헤드가 추가되므로 원시 NTB 대역폭의 약 60-80%를 활용합니다. 최대 성능이 필요하면 ntb_transport를 직접 사용하거나 커스텀 클라이언트 드라이버를 작성하세요.
  • IDT 스위치 활용: IDT 89HPESxx 시리즈 스위치는 다중 NT 포트를 지원하여 3개 이상의 호스트를 NTB로 연결할 수 있습니다. 이는 멀티-노드 클러스터링에 유용합니다.

CXL (Compute Express Link)

CXL은 PCIe 물리 계층 위에 구축된 인터커넥트로, CPU-디바이스 간 캐시 코히런트 메모리 공유를 지원합니다:

프로토콜용도설명
CXL.ioI/OPCIe 호환 — 디바이스 열거, Config Space, MMIO
CXL.cache디바이스→호스트 캐시디바이스가 호스트 메모리를 캐시 코히런트하게 접근
CXL.mem호스트→디바이스 메모리디바이스 메모리를 시스템 주소 공간에 매핑 (Type 2/3)
CXL 디바이스 타입프로토콜예시
Type 1CXL.io + CXL.cacheSmartNIC, 가속기
Type 2CXL.io + CXL.cache + CXL.memGPU, FPGA (디바이스 메모리 포함)
Type 3CXL.io + CXL.mem메모리 확장기 (Memory Expander)
# Linux CXL 서브시스템 (커널 5.18+)
# CXL 디바이스 확인
ls /sys/bus/cxl/devices/

# cxl-cli 도구 (ndctl 패키지)
cxl list                       # CXL 토폴로지 표시
cxl list -M                    # 메모리 디바이스 목록
cxl create-region -t ram ...   # CXL 메모리 리전 생성 → NUMA 노드로 노출
심화 학습: CXL.mem/cache/io 프로토콜, Type1/2/3 장치 차이, drivers/cxl/ 구조, memory_tier 프레임워크, LLM KV 캐시 활용 패턴, cxl-cli 실전 사용은 CXL 메모리 페이지에서 자세히 다룹니다.

Google TPU (Tensor Processing Unit)

TPU (Tensor Processing Unit)는 Google이 설계한 ASIC 기반 도메인 특화 가속기로, 행렬 연산(텐서 연산)에 최적화되어 머신러닝 학습(Training)과 추론(Inference) 워크로드를 처리합니다. Cloud TPU는 Google 데이터센터에서 PCIe 또는 자체 호스트 인터페이스로 연결되며, Edge TPU는 PCIe M.2 또는 USB 폼 팩터로 임베디드 환경에서 추론 가속을 제공합니다.

커널 관점: TPU는 PCIe 디바이스로서 BAR 매핑, DMA 전송, MSI-X 인터럽트, IOMMU 연동 등 표준 PCIe 메커니즘을 활용합니다. Linux 커널에서는 drivers/staging/gasket/ (Gasket 프레임워크)와 drivers/accel/ (ACCEL 서브시스템, 6.2+)을 통해 가속기 드라이버를 지원합니다.

TPU 세대별 하드웨어 아키텍처

세대연도용도연산 성능HBM인터커넥트공정
TPU v12015추론 전용92 TOPS (INT8)— (DRAM 8 GB)PCIe 3.0 x1628nm
TPU v22017학습 + 추론45 TFLOPS (bf16)HBM2 16 GBICI (4링크)16nm
TPU v32018학습 + 추론123 TFLOPS (bf16)HBM2 32 GBICI (6링크)16nm (수냉)
TPU v42021학습 + 추론275 TFLOPS (bf16)HBM2e 32 GBICI (6링크, 3D Torus)7nm
TPU v5e2023효율 최적화197 TFLOPS (bf16)HBM2e 16 GBICIN/A
TPU v5p2023대규모 학습459 TFLOPS (bf16)HBM2e 95 GBICIN/A
TPU v6e (Trillium)2024차세대 효율918 TFLOPS (bf16)HBM 32 GBICIN/A
Edge TPU2019추론 전용4 TOPS (INT8)PCIe / USBN/A

MXU (Matrix Multiply Unit) — 시스톨릭 어레이

TPU의 핵심 연산 유닛은 MXU (Matrix Multiply Unit)으로, 시스톨릭 어레이(systolic array) 구조를 사용합니다. TPU v2/v3는 칩당 2개의 TensorCore를 가지며, 각 TensorCore에 128×128 시스톨릭 어레이 MXU가 탑재됩니다.

설명 요약:
  • TPU 시스톨릭 어레이 동작 원리
  • 128×128 Processing Element (PE) 격자 구조
  • 각 PE는 MAC (Multiply-Accumulate) 연산 1회/사이클 수행
  • Weight 데이터 → 상단에서 아래로 흐름 (preloaded)
  • Input 데이터 → 좌측에서 우측으로 흐름
  • 결과(부분합) → 상단에서 아래로 누적
  • w[0] w[1] w[2] ... w[127]
  • ↓ ↓ ↓ ↓
  • x[0] → [PE] → [PE] → [PE] → ... → [PE] → (partial sum)
  • x[1] → [PE] → [PE] → [PE] → ... → [PE]
  • x[2] → [PE] → [PE] → [PE] → ... → [PE]
  • ... ↓ ↓ ↓ ↓
  • x[127]→ [PE] → [PE] → [PE] → ... → [PE]
  • ↓ ↓ ↓ ↓
  • y[0] y[1] y[2] y[127]
  • 1 사이클에 128×128 = 16,384 MAC 연산
  • bf16 기준: 16,384 × 2 (MAC = mul + add) = 32,768 FLOPS/사이클
  • TPU v4: 클럭 ~1.05 GHz × 32,768 FLOPS × 4 MXU × 2 (bf16 packing)
  • ≈ 275 TFLOPS (bf16)
  • 시스톨릭 어레이의 커널 관점 의미:
  • 호스트 CPU는 MXU를 직접 프로그래밍하지 않음
  • XLA/PJRT 컴파일러가 HLO → TPU 명령어로 변환
  • 커널 드라이버 역할: DMA로 명령어 버퍼와 데이터 전송,
  • 완료 인터럽트 수신, 메모리 매핑 관리
bf16 (bfloat16): Google이 TPU용으로 설계한 16비트 부동소수점 형식. IEEE fp32와 동일한 8비트 지수부(exponent)를 가져 표현 범위(dynamic range)가 fp32와 같으면서, 7비트 가수부(mantissa)로 메모리 대역폭을 절반으로 줄입니다. 딥러닝 학습에서 fp32와 거의 동등한 수렴 특성을 보입니다.

TPU 메모리 아키텍처

TPU v4 메모리 계층 구조 TensorCore (×2 per chip) MXU 0 128×128 bf16 MXU 1 128×128 bf16 Vector Unit (SIMD ALU) VMEM (16 MB) 벡터 레지스터 파일 SMEM 스칼라 레지스터 파일 CMEM — 공통 메모리 Cross-Core Shared Memory HBM2e — 메인 메모리 32 GB / 1.6 TB/s 대역폭 ICI — Inter-Chip Interconnect 6 링크 / 다른 TPU 칩 직접 연결 커널 드라이버 관점 • HBM은 BAR를 통해 MMIO로 매핑되거나 DMA를 통해 접근 • VMEM/SMEM은 TPU 내부 SRAM — 드라이버가 직접 접근하지 않음 • 호스트 ↔ HBM 데이터 전송은 DMA 엔진이 담당

ICI (Inter-Chip Interconnect)

TPU v2 이후 칩 간 통신을 위한 ICI (Inter-Chip Interconnect)를 제공합니다. ICI는 PCIe를 사용하지 않는 Google 자체 설계 고속 직렬 링크로, 다수의 TPU 칩을 직접 연결하여 Pod 단위의 대규모 분산 학습을 가능하게 합니다.

세대ICI 링크 수토폴로지링크당 대역폭Pod 규모
TPU v242D Torus (16×16)~496 Gbps256 칩 (64 TF/pod)
TPU v362D Torus (32×16)~656 Gbps1,024 칩
TPU v463D Torus (4×4×4 ~ 큐브)~2.4 Tbps (총)4,096 칩
TPU v5p다수3D Torus향상8,960 칩
TPU v4 Pod — 3D Torus ICI 토폴로지 (4×4 상면도) z ↑ x y Chip 0,0 Chip 1,0 Chip 2,0 Chip 3,0 Chip 0,1 Chip 1,1 Chip 2,1 Chip 3,1 Chip 0,2 Chip 1,2 Chip 2,2 Chip 3,2 Chip 0,3 Chip 1,3 Chip 2,3 Chip 3,3 3D Torus 특성 (4,096칩 = 4×4×4 큐브 블록 × 토러스) ICI 직접 링크 토러스 랩어라운드 각 칩 6개 이웃 (±x, ±y, ±z) · AllReduce bisection BW 극대화 · 단일 링크 장애 시 대체 경로 커널 관점: ICI는 칩 내부 HW 관리, 호스트 드라이버는 PCIe 연결만 관리

TPU의 PCIe 인터페이스

TPU는 호스트 시스템과 PCIe 버스를 통해 연결됩니다. Edge TPU (Coral)는 표준 PCIe 엔드포인트로, Cloud TPU는 커스텀 호스트 인터페이스 칩을 통해 PCIe로 연결됩니다.

디바이스PCIe 규격Vendor IDDevice IDBAR 구성
Edge TPU (Apex)PCIe Gen2 x11ac1 (Google)089aBAR0: 레지스터, BAR2: 펌웨어/데이터
Cloud TPU v4PCIe Gen3 x161ae0 (Google)(비공개)다수 BAR: CSR, DMA, HBM 윈도우
# Edge TPU (Coral PCIe Accelerator) 확인
lspci -nn | grep -i google
# 03:00.0 System peripheral [0880]: Global Unichip Corp. [1ac1:089a]

# 상세 정보
lspci -vvv -s 03:00.0
#   Region 0: Memory at f7000000 (64-bit, prefetchable) [size=16M]
#   Region 2: Memory at f8000000 (64-bit, prefetchable) [size=2M]
#   Capabilities: [80] MSI-X: Enable+ Count=128 Masked-

# Edge TPU BAR 매핑 구조
# BAR0 (16 MB): Apex CSR (Control/Status Registers)
#   - 인터럽트 상태/마스크 레지스터
#   - DMA 디스크립터 링 베이스/크기
#   - 펌웨어 상태 레지스터
# BAR2 (2 MB): 명령어 버퍼 / 데이터 교환 영역

Gasket 프레임워크 (커널 TPU 드라이버)

Google은 TPU 계열 가속기를 위한 커널 드라이버 프레임워크로 Gasket (Google ASIC Software, Kernel Extensions, and Tools)을 개발했습니다. 이 프레임워크는 drivers/staging/gasket/에 위치하며, PCIe 가속기 디바이스의 공통 기능을 추상화합니다.

소스 파일역할
gasket_core.cPCI probe/remove, char device 등록, BAR 매핑, ioctl 디스패치
gasket_ioctl.c표준 ioctl 처리 (페이지 테이블 매핑, 이벤트 fd, 디바이스 리셋)
gasket_page_table.c2단계 디바이스 페이지 테이블 관리 (가상주소 → DMA 주소)
gasket_interrupt.cMSI-X 인터럽트 설정 및 eventfd 연동
gasket_sysfs.csysfs 속성 노출 (펌웨어 버전, 디바이스 상태 등)
apex_driver.cEdge TPU (Apex) 전용 드라이버 — Gasket 위에 구현
/* Gasket 프레임워크 핵심 구조체 (drivers/staging/gasket/) */

/* gasket_driver_desc — 디바이스별 드라이버 기술자 */
struct gasket_driver_desc {
    const char *name;               /* 드라이버 이름 */
    const char *driver_version;      /* 드라이버 버전 문자열 */

    /* PCI BAR 기술 */
    struct gasket_bar_desc bar_descriptions[GASKET_NUM_BARS];
    int num_page_tables;             /* 디바이스 페이지 테이블 수 */
    struct gasket_page_table_config page_table_configs[...];

    /* 인터럽트 기술 */
    int num_interrupts;
    struct gasket_interrupt_desc interrupts[...];

    /* 콜백 */
    int (*device_open_cb)(struct gasket_dev *dev);
    int (*device_close_cb)(struct gasket_dev *dev);
    int (*device_reset_cb)(struct gasket_dev *dev);
    enum gasket_status (*device_status_cb)(struct gasket_dev *dev);
    long (*ioctl_handler_cb)(...);  /* 디바이스별 커스텀 ioctl */
};

/* gasket_dev — 디바이스 인스턴스 (probe 시 생성) */
struct gasket_dev {
    struct pci_dev *pci_dev;
    struct device *dev;
    struct cdev cdev;               /* char device */
    int dev_idx;                     /* /dev/apex_N 인덱스 */

    /* BAR 매핑 */
    struct gasket_bar_data bar_data[GASKET_NUM_BARS];

    /* 페이지 테이블 (디바이스 IOVA → 호스트 물리 주소) */
    struct gasket_page_table *page_table[...];

    /* 인터럽트 */
    struct gasket_interrupt_data *interrupt_data;

    const struct gasket_driver_desc *driver_desc;
    struct mutex mutex;
    int ownership_owned;             /* 독점 소유권 */
};

Edge TPU (Apex) 드라이버 상세

Edge TPU 드라이버(apex_driver.c)는 Gasket 프레임워크 위에 구현된 구체적인 디바이스 드라이버로, Google Coral PCIe/M.2 가속기를 지원합니다.

/* Edge TPU (Apex) 드라이버 — Gasket 기반 구현 */
/* drivers/staging/gasket/apex_driver.c */

#define APEX_DRIVER_NAME     "apex"
#define APEX_DRIVER_VERSION  "1.0"

/* PCI ID 테이블 */
static const struct pci_device_id apex_pci_ids[] = {
    { PCI_DEVICE(0x1ac1, 0x089a) },  /* Edge TPU */
    { 0, }
};

/* Apex BAR 기술 */
enum {
    APEX_BAR_INDEX  = 0,  /* BAR0: CSR 레지스터 (16 MB) */
    APEX_BAR2_INDEX = 2,  /* BAR2: 펌웨어/데이터 (2 MB) */
};

/* 주요 CSR 오프셋 */
#define APEX_BAR2_REG_SCU_BASE    0x1A300  /* SCU (System Control) */
#define APEX_BAR2_REG_KERNEL_HIB  0x1A400  /* Host Interface Block */

/* Apex 드라이버 기술자 — Gasket 프레임워크에 등록 */
static const struct gasket_driver_desc apex_desc = {
    .name            = APEX_DRIVER_NAME,
    .driver_version  = APEX_DRIVER_VERSION,
    .major           = 120,
    .minor           = 0,
    .module          = THIS_MODULE,

    .bar_descriptions = {
        /* BAR0: 레지스터 공간 */
        { .size = 0x1000000, .permissions = 0660 },
        /* BAR2: 데이터 공간 */
        { .size = 0x200000,  .permissions = 0660 },
    },

    .num_page_tables = 1,
    .page_table_configs = { {
        .id = 0,
        .mode = GASKET_PAGE_TABLE_MODE_NORMAL,
        .total_entries = 8192,     /* 4K × 8192 = 32 MB 매핑 가능 */
        .base_reg = 0x1A488,       /* 페이지 테이블 베이스 레지스터 */
    } },

    .num_interrupts  = 1,
    .interrupts      = { {
        .index  = 0,
        .reg    = 0x1A318,          /* 인터럽트 상태 CSR */
        .packing = GASKET_IRQ_UNPACKED,
    } },

    .device_reset_cb  = apex_reset,
    .device_status_cb = apex_get_status,
};

/* Apex probe — Gasket 프레임워크가 호출 */
static int apex_pci_probe(struct pci_dev *pci_dev,
                          const struct pci_device_id *id)
{
    int ret;
    /* Gasket 프레임워크에 디바이스 등록 */
    ret = gasket_pci_add_device(pci_dev, &apex_desc);
    if (ret)
        return ret;
    /* /dev/apex_N 디바이스 노드 생성 */
    /* BAR 매핑, MSI-X 설정, 펌웨어 로드 등 Gasket이 처리 */
    return 0;
}

Gasket 디바이스 페이지 테이블

TPU는 자체 디바이스 페이지 테이블을 가지며, 호스트 사용자 공간 가상 주소를 디바이스가 접근 가능한 DMA 주소로 변환합니다. Gasket 프레임워크가 이 페이지 테이블의 생명주기를 관리합니다.

Gasket 2단계 페이지 테이블 구조 사용자 공간 VA Gasket ioctl 디바이스 페이지 테이블 Level 0 (Directory) Entry 0 L1 DMA 주소 Entry 1 L1 DMA 주소 Entry 2 L1 DMA 주소 ... Level 1 (Page Table) PTE 0 PTE 1 PTE 2 ... PTE 0 PTE 1 PTE 2 ... PTE 0 PTE 1 PTE 2 ... 각 PTE: 호스트 물리 페이지 DMA 주소 TPU DMA 엔진이 이 페이지 테이블을 참조하여 호스트 메모리에 직접 접근 (scatter-gather DMA) 사용자 VA → get_user_pages_fast() → dma_map_page() → 디바이스 PT 엔트리 기록
/* 페이지 테이블 매핑 ioctl */
struct gasket_page_table_ioctl {
    u64 page_table_index;   /* 페이지 테이블 ID */
    u64 size;               /* 매핑 크기 (바이트) */
    u64 host_address;       /* 사용자 공간 가상 주소 */
    u64 device_address;     /* 디바이스 측 주소 (IOVA) */
};

/* Gasket 페이지 테이블 매핑 흐름:
 *
 * 1. 사용자 → ioctl(GASKET_IOCTL_MAP_BUFFER, &pt_ioctl)
 * 2. gasket_page_table_map():
 *    a. get_user_pages_fast() — 사용자 페이지 핀
 *    b. dma_map_page() — 각 페이지의 DMA 주소 획득
 *    c. 디바이스 페이지 테이블 엔트리에 DMA 주소 기록
 *    d. BAR의 페이지 테이블 베이스 레지스터 갱신
 * 3. TPU DMA 엔진이 디바이스 페이지 테이블을 참조하여
 *    호스트 메모리에 scatter-gather DMA 수행
 */

TPU DMA 데이터 전송 흐름

호스트 ↔ TPU 데이터 전송 흐름 Host CPU PCIe 채널 TPU Chip User Buffer (pinned memory) 모델 가중치 DMA → HBM (모델 가중치 로드) Input Data (pinned memory) 입력 텐서 DMA → HBM (입력 텐서) TensorCore (MXU 텐서 연산) Output Buffer (결과 수신) ← 출력 텐서 DMA HBM (출력 텐서) ← MSI-X IRQ (DMA 완료 인터럽트) 단계 1. 사용자 공간: 입력 데이터 버퍼 준비, ioctl로 매핑 요청 2. 커널 드라이버: get_user_pages_fast()로 페이지 핀, DMA 매핑 설정 3. 커널 드라이버: 명령어 큐(Doorbell)에 DMA 전송 명령 기록 4. TPU DMA 엔진: 호스트 메모리 → HBM으로 데이터 전송 5. TPU TensorCore: MXU에서 텐서 연산 실행 6. TPU DMA 엔진: HBM → 호스트 메모리로 결과 전송 7. TPU: MSI-X 인터럽트로 호스트에 완료 알림 8. 커널 드라이버: eventfd를 통해 사용자 공간에 통지

TPU 인터럽트 처리 (MSI-X / eventfd)

/* Gasket 인터럽트 처리 구조 */

/* 1. MSI-X 벡터 할당 (probe 시) */
static int gasket_interrupt_init(struct gasket_dev *dev)
{
    int ret;
    /* MSI-X 벡터 할당 */
    ret = pci_alloc_irq_vectors(dev->pci_dev,
                               dev->num_interrupts,
                               dev->num_interrupts,
                               PCI_IRQ_MSIX);
    if (ret < 0)
        return ret;

    /* 각 벡터에 ISR 등록 */
    for (int i = 0; i < dev->num_interrupts; i++) {
        int irq = pci_irq_vector(dev->pci_dev, i);
        request_irq(irq, gasket_interrupt_handler,
                    0, dev->driver_desc->name, dev);
    }
    return 0;
}

/* 2. ISR — eventfd를 통해 사용자 공간에 통지 */
static irqreturn_t gasket_interrupt_handler(int irq, void *ctx)
{
    struct gasket_dev *dev = ctx;
    struct eventfd_ctx *eventfd;

    /* 인터럽트 상태 레지스터 읽기 및 클리어 */
    u64 status = readq(dev->bar_data[0].virt_base + IRQ_STATUS_REG);
    writeq(status, dev->bar_data[0].virt_base + IRQ_CLEAR_REG);

    /* 등록된 eventfd에 시그널 전달 */
    eventfd = dev->interrupt_data->eventfds[0];
    if (eventfd)
        eventfd_signal(eventfd, 1);

    return IRQ_HANDLED;
}

/* 3. 사용자 공간에서 eventfd 등록 (ioctl) */
/*
 * 사용자 프로그램:
 *   int efd = eventfd(0, EFD_NONBLOCK);
 *   struct gasket_interrupt_eventfd ie = {
 *       .interrupt = 0,        // 인터럽트 인덱스
 *       .event_fd  = efd,      // eventfd 파일 디스크립터
 *   };
 *   ioctl(tpu_fd, GASKET_IOCTL_SET_EVENTFD, &ie);
 *
 *   // 비동기 완료 대기
 *   struct pollfd pfd = { .fd = efd, .events = POLLIN };
 *   poll(&pfd, 1, timeout_ms);
 */

TPU 펌웨어 로딩

/*
 * Edge TPU 펌웨어 로딩 과정
 *
 * 1. 드라이버 probe 시 request_firmware() 호출
 *    - 펌웨어 파일: /lib/firmware/google/apex_firmware.bin
 *    - 또는 유저가 sysfs를 통해 커스텀 펌웨어 로드
 *
 * 2. 펌웨어를 BAR2 영역에 DMA 전송
 *    - BAR2에 매핑된 디바이스 메모리에 직접 기록
 *    - 또는 DMA coherent 버퍼를 통해 전송
 *
 * 3. CSR에 펌웨어 실행 명령 기록
 *    - SCU 레지스터에 리셋 해제 + 실행 비트 설정
 *
 * 4. 펌웨어 준비 상태 폴링
 *    - HIB (Host Interface Block) 상태 레지스터 확인
 *    - 타임아웃 내에 READY 상태 전환 확인
 */

static int apex_reset(struct gasket_dev *gasket_dev)
{
    struct apex_dev *apex = gasket_dev_get_drvdata(gasket_dev);

    /* 1. 칩 소프트 리셋 */
    writeq(SCU_RESET_VALUE,
           gasket_dev->bar_data[2].virt_base + APEX_BAR2_REG_SCU_BASE);

    /* 2. 리셋 완료 대기 */
    usleep_range(100, 200);

    /* 3. 펌웨어 로드 (이미 호스트 메모리에 로드된 경우) */
    memcpy_toio(gasket_dev->bar_data[2].virt_base + FW_LOAD_OFFSET,
                apex->fw_data, apex->fw_size);

    /* 4. 펌웨어 실행 시작 */
    writeq(HIB_START_VALUE,
           gasket_dev->bar_data[2].virt_base + APEX_BAR2_REG_KERNEL_HIB);

    /* 5. READY 상태 폴링 */
    return readq_poll_timeout(
        gasket_dev->bar_data[2].virt_base + HIB_STATUS_REG,
        status, status == FW_STATUS_READY,
        100,          /* 100 µs 간격 */
        5000000);    /* 5초 타임아웃 */
}

ACCEL 서브시스템 (Linux 6.2+)

Linux 6.2부터 도입된 ACCEL (Accelerator) 서브시스템은 ML/AI 가속기를 위한 표준 커널 프레임워크입니다. DRM 코어 인프라를 재활용하면서 /dev/accel/accelN 별도 디바이스 노드를 제공하여 GPU와 가속기를 명확히 분리합니다.

구분DRM (/dev/dri/)ACCEL (/dev/accel/)
용도GPU 렌더링/디스플레이ML/AI 연산 가속
KMS필요 (디스플레이 출력)불필요 (연산 전용)
GEMGPU 버퍼 관리가속기 메모리 관리
스케줄러drm_sched (GPU 작업)디바이스별 자체 스케줄러
주요 드라이버i915, amdgpu, nouveauhabanalabs (Gaudi), qaic, ivpu
디바이스 노드/dev/dri/cardN, /dev/dri/renderDN/dev/accel/accelN
/* ACCEL 드라이버 등록 예시 (DRM 기반) */
#include <drm/drm_accel.h>
#include <drm/drm_drv.h>

static const struct drm_driver my_accel_driver = {
    .driver_features = DRIVER_COMPUTE_ACCEL,  /* 핵심: ACCEL 플래그 */
    .name            = "my_tpu",
    .desc            = "My TPU Accelerator",
    .date            = "20240101",
    .major           = 1,
    .minor           = 0,

    /* GEM 오브젝트 콜백 */
    .gem_create_object = my_gem_create_object,

    /* ioctl 테이블 (디바이스별 커스텀 명령) */
    .ioctls           = my_accel_ioctls,
    .num_ioctls       = ARRAY_SIZE(my_accel_ioctls),

    /* 파일 오퍼레이션 */
    .fops = &my_accel_fops,
};

/*
 * DRIVER_COMPUTE_ACCEL 플래그의 효과:
 * - /dev/accel/accelN 디바이스 노드 자동 생성 (/dev/dri/ 대신)
 * - KMS 관련 ioctl 비활성화 (모드 설정 불필요)
 * - DRM 코어의 메모리 관리/ioctl 인프라는 그대로 활용
 * - sysfs: /sys/class/accel/accel0/
 */

/* PCI probe에서 ACCEL 디바이스 초기화 */
static int my_tpu_probe(struct pci_dev *pdev,
                        const struct pci_device_id *id)
{
    struct drm_device *drm;
    int ret;

    /* DRM/ACCEL 디바이스 할당 */
    drm = drm_dev_alloc(&my_accel_driver, &pdev->dev);
    if (IS_ERR(drm))
        return PTR_ERR(drm);

    /* PCI 리소스 설정 */
    ret = pcim_enable_device(pdev);
    pci_set_master(pdev);
    dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));

    /* 디바이스 등록 → /dev/accel/accel0 생성 */
    ret = drm_dev_register(drm, 0);
    return ret;
}
ACCEL vs Gasket: Gasket은 Google의 자체 프레임워크(staging)로 Edge TPU에 사용되고, ACCEL은 Linux 메인라인 표준 프레임워크(6.2+)로 DRM 인프라를 재활용합니다. 새로운 가속기 드라이버는 ACCEL 서브시스템을 사용하는 것이 권장됩니다.

TPU 소프트웨어 스택 (커널-사용자 공간 인터페이스)

User Space TensorFlow JAX PyTorch/XLA PJRT libtpu.so XLA Compiler (HLO → TPU 명령어) ioctl() / mmap() / eventfd Kernel Space Gasket / ACCEL Framework PCI Subsystem DMA Engine MSI-X IRQ IOMMU BAR MMIO TPU Hardware (MXU + HBM + DMA + ICI)

TPU 주요 ioctl 인터페이스

ioctl기능설명
GASKET_IOCTL_RESET디바이스 리셋TPU 칩 소프트 리셋 + 펌웨어 재로드
GASKET_IOCTL_MAP_BUFFER버퍼 매핑사용자 버퍼를 디바이스 페이지 테이블에 등록
GASKET_IOCTL_UNMAP_BUFFER버퍼 해제디바이스 페이지 테이블 엔트리 제거 + 페이지 언핀
GASKET_IOCTL_SET_EVENTFD이벤트 등록MSI-X 인터럽트에 eventfd 연결
GASKET_IOCTL_CLEAR_EVENTFD이벤트 해제eventfd 연결 해제
GASKET_IOCTL_NUMBER_PAGE_TABLES페이지 테이블 수지원하는 디바이스 페이지 테이블 수 조회
GASKET_IOCTL_PAGE_TABLE_SIZE테이블 크기특정 페이지 테이블의 최대 엔트리 수 조회
GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE단순 테이블 크기단순(1단계) 페이지 테이블 크기 조회

TPU sysfs 인터페이스

# Edge TPU sysfs 경로
ls /sys/class/apex/apex_0/

# 디바이스 속성
cat /sys/class/apex/apex_0/device/vendor     # 0x1ac1 (Google)
cat /sys/class/apex/apex_0/device/device     # 0x089a (Edge TPU)
cat /sys/class/apex/apex_0/temp              # 칩 온도 (밀리도)
cat /sys/class/apex/apex_0/status            # 디바이스 상태

# ACCEL 서브시스템 (Linux 6.2+)
ls /sys/class/accel/accel0/
cat /sys/class/accel/accel0/device/vendor
cat /sys/class/accel/accel0/device/device

# PCIe 링크 상태 확인
lspci -vvv -s 03:00.0 | grep -E "LnkSta|LnkCap"
# LnkCap: Port #0, Speed 5GT/s, Width x1
# LnkSta: Speed 5GT/s, Width x1

# DMA 상태 확인
cat /sys/kernel/debug/dma-buf/bufinfo        # DMA-BUF 사용 현황
cat /proc/iomem | grep -i apex               # BAR 매핑 주소

Edge TPU 드라이버 설치 및 확인

# Edge TPU (Coral) PCIe 드라이버 — Gasket 모듈 로드

# 1. 커널 모듈 확인/로드
modprobe gasket       # Gasket 코어 프레임워크
modprobe apex         # Edge TPU (Apex) 드라이버
lsmod | grep apex
# apex                    28672  0
# gasket                  77824  1 apex

# 2. 디바이스 노드 확인
ls -la /dev/apex_*
# crw-rw---- 1 root apex 120, 0 ... /dev/apex_0

# 3. udev 규칙 (권한 설정)
# /etc/udev/rules.d/65-apex.rules
# SUBSYSTEM=="apex", MODE="0660", GROUP="apex"

# 4. dmesg 로그 확인
dmesg | grep -i apex
# [  2.345] apex 0000:03:00.0: Apex device found
# [  2.346] apex 0000:03:00.0: enabling device (0000 -> 0002)
# [  2.347] apex 0000:03:00.0: successfully loaded firmware

# 5. 추론 테스트 (TensorFlow Lite + Edge TPU Runtime)
# Python에서:
# import tflite_runtime.interpreter as tflite
# interpreter = tflite.Interpreter(
#     model_path='model_edgetpu.tflite',
#     experimental_delegates=[
#         tflite.load_delegate('libedgetpu.so.1')])

TPU 관련 Kconfig 옵션

옵션설명
CONFIG_STAGINGStaging 드라이버 활성화 (Gasket 포함)
CONFIG_STAGING_GASKET_FRAMEWORKGasket 코어 프레임워크
CONFIG_STAGING_APEX_DRIVEREdge TPU (Apex) 드라이버
CONFIG_DRM_ACCELACCEL 서브시스템 (Linux 6.2+, DRM 기반)
CONFIG_DRMDRM 코어 (ACCEL 의존성)
CONFIG_PCI_MSIMSI/MSI-X (TPU 인터럽트)
CONFIG_IOMMU_SUPPORTIOMMU (DMA 보안 격리)
CONFIG_DMA_SHARED_BUFFERDMA-BUF (버퍼 공유)
CONFIG_FW_LOADER펌웨어 로딩 인프라

TPU vs GPU: 커널 드라이버 관점 비교

특성GPU (DRM/KMS)TPU (Gasket/ACCEL)
디스플레이 출력KMS로 제어 (CRTC, Encoder, Connector)없음 (연산 전용)
메모리 관리GEM/TTM (GPU VRAM)디바이스 페이지 테이블 (HBM)
작업 제출커맨드 링 버퍼 (GPU 명령어)DMA 디스크립터 + Doorbell CSR
스케줄링drm_sched (GPU 컨텍스트 전환)하드웨어 자체 스케줄링
사용자 APIOpenGL/Vulkan → DRM ioctlXLA/PJRT → Gasket ioctl
칩 간 통신NVLink/xGMI (제한적)ICI (대규모 3D Torus)
데이터 타입fp32/fp16/int (다목적)bf16/int8 (ML 특화)
프로그래밍범용 셰이더시스톨릭 어레이 고정 파이프라인

TPU 관련 커널 소스 구조

경로설명
drivers/staging/gasket/Gasket 프레임워크 (Edge TPU 지원)
drivers/staging/gasket/gasket_core.cPCI probe, BAR 매핑, char device 관리
drivers/staging/gasket/gasket_page_table.c디바이스 페이지 테이블 (DMA 주소 변환)
drivers/staging/gasket/gasket_interrupt.cMSI-X 설정, eventfd 통지
drivers/staging/gasket/apex_driver.cEdge TPU (Apex) 드라이버
drivers/accel/ACCEL 서브시스템 (Linux 6.2+)
drivers/accel/habanalabs/Intel Gaudi 가속기 드라이버
drivers/accel/qaic/Qualcomm Cloud AI 100 드라이버
drivers/accel/ivpu/Intel VPU (NPU) 드라이버
include/drm/drm_accel.hACCEL 서브시스템 API 헤더

sysfs 인터페이스

PCI 디바이스 정보는 /sys/bus/pci/ 아래에 노출됩니다:

/sys/bus/pci/devices/0000:03:00.0/ 식별 정보 vendor (0x8086) device (0x1572) class (0x020000) subsystem_vendor/device revision numa_node, local_cpus 리소스/링크 config (256/4096B) resource, resource0 irq, msi_irqs/ current_link_speed/width max_link_speed/width iommu_group/ 제어 인터페이스 driver/, driver_override enable, remove, rescan, reset sriov_numvfs sriov_totalvfs 실무 해석 - 식별 정보(vendor/device/class)로 드라이버 매칭과 기능군을 확인합니다. - resource/config는 BAR 및 Config Space 디버깅의 기준입니다. - msi_irqs와 link 속도 속성으로 인터럽트/링크 병목을 빠르게 점검합니다. - enable/remove/rescan/reset은 장치 라이프사이클 제어에 사용됩니다. - SR-IOV 속성은 PF에서 VF 분할 상태와 한계를 보여줍니다.

PCIe Performance 튜닝

PCIe 대역폭을 최대로 활용하려면 TLP 오버헤드를 최소화하고, 흐름 제어 병목을 해소해야 합니다. 주요 튜닝 포인트:

MPS / MRRS 최적화

TLP 오버헤드에 따른 유효 대역폭 MPS=128B (최소): Hdr 16B Payload 128B CRC 효율: 128/(16+128+4+8) ≈ 82% MPS=256B: Hdr Payload 256B CRC 효율: 256/(16+256+4+8) ≈ 90% MPS=4096B (최대): H Payload 4096B 효율: 4096/(16+4096+4+8) ≈ 99.3% Gen5 x16 기준 유효 대역폭 비교: MPS=128B: ~51.7 GB/s | MPS=256B: ~56.7 GB/s | MPS=512B: ~59.4 GB/s | MPS=4096B: ~62.6 GB/s → MPS가 128B → 256B만 되어도 약 10%의 대역폭 향상. 대용량 DMA에서는 MPS 최대화가 중요
# 현재 MPS/MRRS 확인
lspci -vvv -s 03:00.0 | grep -E "MaxPayload|MaxReadReq"
# DevCap: MaxPayload 512 bytes, PhantFunc 0, Latency L0s <512ns, L1 <1us
# DevCtl: ... MaxPayload 256 bytes, MaxReadReq 512 bytes
# 주의: MaxPayload(DevCtl) ≤ MaxPayload(DevCap)

# MPS 정책 설정 (커널 부팅 파라미터)
pci=pcie_bus_safe        # 링크 경로상 최소 MPS로 설정 (가장 안전)
pci=pcie_bus_perf        # 각 버스 도메인 내 최대 MPS 설정 (성능 우선)
pci=pcie_bus_peer2peer   # 전체 계층에서 최소 MPS (P2P DMA 호환성)

# setpci로 MPS 직접 변경 (주의: 양단 일치 필요)
# DevCtl [7:5] = MPS: 000=128, 001=256, 010=512, 011=1024, 100=2048, 101=4096
setpci -s 03:00.0 CAP_EXP+8.W=0x2820  # MPS=256, MRRS=4096 (예시)

# MRRS 변경 (sysfs)
echo 4096 > /sys/bus/pci/devices/0000:03:00.0/max_read_request_size

Relaxed Ordering / No Snoop

TLP 속성비트효과사용 시나리오
Relaxed Ordering (RO)Attr[1]같은 TC의 Posted 쓰기가 이전 Non-Posted 읽기를 추월 가능. 파이프라인 효율 향상NIC DMA, 스토리지 — 순서 의존성 없는 대량 데이터 전송
ID-based Ordering (IDO)Attr[2]서로 다른 Requester ID 간의 TLP 순서 완화. 멀티 VF 환경에서 유용SR-IOV 가상화, 다수 VF의 독립적 DMA 스트림
No Snoop (NS)Attr[0]호스트 CPU 캐시 스누핑 생략. DMA 대상이 non-cacheable 영역일 때 지연 감소GPU 메모리, 프레임버퍼 — CPU 캐시와 무관한 DMA
Relaxed Ordering 주의: Mellanox ConnectX NIC 등 일부 디바이스는 RO 비트가 설정된 DMA를 사용합니다. AMD 플랫폼에서 RO + IOMMU 조합 시 데이터 코럽션이 발생한 사례가 보고되었습니다 (commit 1a4e0c4). 문제 발생 시 pci=norelaxedordering 커널 파라미터로 RO를 비활성화할 수 있습니다.

Completion Timeout 튜닝

Non-Posted 요청(MRd, CfgRd/Wr)에 대한 Completion이 일정 시간 내에 도착하지 않으면 Completion Timeout 에러가 발생합니다:

타임아웃 범위용도
50 µs ~ 50 msRange A기본. 대부분의 디바이스에 적합
50 ms ~ 10 sRange B/C/D느린 디바이스 (NVMe D3→D0 복구, FPGA 재구성)
비활성화Disable영원히 대기 (데드락 위험, 디버깅용)
# Completion Timeout 범위 확인
lspci -vvv -s 03:00.0 | grep "CmpltTO"
# DevCap2: Completion Timeout: Range ABC, TimeoutDis+
# DevCtl2: Completion Timeout: 50us to 50ms, TimeoutDis-

# Completion Timeout 비활성화 (디버깅 시)
setpci -s 03:00.0 CAP_EXP+28.W=0x0010  # DevCtl2: Timeout Disable bit

대역폭 실측 및 병목 분석

# 1. 실제 링크 속도/폭 확인
lspci -vvv -s 03:00.0 | grep -E "LnkCap|LnkSta"
# LnkCap: Port #0, Speed 16GT/s (Gen4), Width x16
# LnkSta: Speed 16GT/s (ok), Width x16 (ok)
# 만약 LnkSta가 LnkCap보다 낮으면 → 등화 실패, 물리적 문제 의심

# 2. PCIe 대역폭 계측 (fio NVMe 예시)
fio --name=seqread --ioengine=io_uring --direct=1 --bs=128k \
    --numjobs=4 --iodepth=64 --filename=/dev/nvme0n1 --rw=read \
    --runtime=10 --time_based
# Gen4 x4 NVMe: 이론 ~7.9 GB/s, 실측 ~6.5-7.0 GB/s (→ 82-89% 효율)

# 3. GPU DMA 대역폭 (CUDA bandwidthTest 예시)
# Gen4 x16: 이론 ~31.5 GB/s, H2D 실측 ~25-27 GB/s (→ 79-86% 효율)

# 4. PCIe 성능 카운터 (Intel perf uncore)
perf stat -e uncore_iio_0/event=0x83,umask=0x04/ -a sleep 5
# 플랫폼별 PCIe 카운터로 실시간 대역폭 모니터링

# 5. 병목 진단 체크리스트
# □ LnkSta Speed/Width가 LnkCap과 일치하는가?
# □ MPS가 DevCap 최대값으로 설정되었는가?
# □ MRRS가 충분히 큰가? (DMA 버스트 크기 ≤ MRRS)
# □ AER Correctable Error 카운터가 빠르게 증가하지 않는가?
# □ ASPM이 활성화되어 레이턴시를 유발하지 않는가?
# □ NUMA locality — DMA 버퍼가 PCIe RC와 같은 NUMA 노드에 있는가?
# □ IOMMU 오버헤드 — 패스스루 시 intel_iommu=pt로 바이패스
NUMA와 PCIe: NUMA 시스템에서 PCIe 디바이스는 특정 NUMA 노드의 Root Complex에 연결됩니다. DMA 버퍼와 인터럽트 핸들러가 같은 NUMA 노드에서 실행되어야 최적 성능을 얻을 수 있습니다:
# 디바이스의 NUMA 노드 확인
cat /sys/bus/pci/devices/0000:03:00.0/numa_node
# 0

# 로컬 CPU 목록 확인
cat /sys/bus/pci/devices/0000:03:00.0/local_cpulist
# 0-15,32-47

# 인터럽트를 로컬 CPU에 바인딩
echo 0-15 > /proc/irq/<IRQ_NUM>/smp_affinity_list

디버깅 도구

lspci

# 기본 디바이스 목록
lspci

# 상세 정보 (-v: verbose, -vv: very verbose)
lspci -vvv -s 03:00.0

# 커널 드라이버 및 모듈 표시
lspci -k

# 트리 구조 (Bus 토폴로지)
lspci -tv

# 숫자 코드 + 이름
lspci -nn

# Configuration Space 16진수 덤프
lspci -xxx -s 03:00.0        # 256 바이트
lspci -xxxx -s 03:00.0       # 4096 바이트 (PCIe 확장)

# Capability 상세 (PCIe Link, MSI-X, AER 등)
lspci -vvv -s 03:00.0 | grep -A 10 "LnkCap\|LnkSta\|MSI-X\|AER"

setpci

# Configuration Space 직접 읽기
setpci -s 03:00.0 VENDOR_ID       # Vendor ID 읽기
setpci -s 03:00.0 04.W            # Command 레지스터 (2바이트)

# Configuration Space 직접 쓰기 (주의!)
setpci -s 03:00.0 04.W=0x0007     # I/O + Memory + Bus Master 활성화

lspci -vvv 출력 완전 해석

실제 lspci -vvv 출력의 핵심 항목을 라인별로 해석합니다. NVMe SSD를 예시로 사용:

## 기본 식별 정보
03:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd
        NVMe SSD Controller PM9A1 (rev 01)
# 03:00.0 → BDF (Bus:03, Device:00, Function:0)
# Non-Volatile memory controller → Class Code 0x010802
# rev 01 → Revision ID

## Subsystem ID — OEM 변형 식별
        Subsystem: Lenovo Device 5023
# 같은 컨트롤러라도 서브시스템 ID로 노트북/서버 변형 구분

## Control (Command 레지스터의 현재 활성 비트)
        Control: I/O- Mem+ BusMaster+ SpecCycle- MemWINV- ...
# Mem+ → Memory Space 활성 (MMIO BAR 접근 가능)
# BusMaster+ → DMA 활성화 상태
# I/O- → I/O Port 비활성 (NVMe는 MMIO만 사용)

## Status (Status 레지스터)
        Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast
                >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
# Cap+ → Capabilities List 있음 (오프셋 34h)
# INTx- → 레거시 인터럽트 비활성 (MSI-X 사용 중)

## BAR 정보
        Region 0: Memory at 6a500000 (64-bit, non-prefetchable) [size=16K]
# BAR0: MMIO, 64-bit, 16 KB (NVMe 레지스터 공간)
# non-prefetchable → 디바이스 레지스터 (side-effect 있음)

## PCIe Capability — 링크 정보 (가장 중요)
        Capabilities: [70] Express (v2) Endpoint, MSI 00
          DevCap: MaxPayload 512 bytes, PhantFunc 0
                  ExtTag+ AttnBtn- AttnInd- PwrInd- RBE+ FLR+ SlotPowerLimit 75W
# MaxPayload 512 → 이 디바이스가 지원하는 최대 MPS (DevCap)
# FLR+ → Function Level Reset 지원

          DevCtl: CorrErr+ NonFatalErr+ FatalErr+ UnsuppReq+
                  RlxdOrd+ ExtTag+ PhantFunc- AuxPwr- NoSnoop+ MaxPayload 256 bytes
                  MaxReadReq 512 bytes
# MaxPayload 256 → 현재 설정된 MPS (DevCap보다 작을 수 있음 — 경로상 최소값)
# MaxReadReq 512 → MRRS 설정값
# RlxdOrd+ → Relaxed Ordering 활성
# NoSnoop+ → No Snoop 활성

          LnkCap: Port #0, Speed 16GT/s (Gen4), Width x4
                  ASPM L1, Exit Latency L1 <64us
                  ClockPM+ Surprise- LLActRep- BwNot- ASPMOptComp+
# Speed 16GT/s, Width x4 → 디바이스의 최대 링크 능력
# ASPM L1 → L1만 지원 (L0s 미지원)

          LnkSta: Speed 16GT/s (ok), Width x4 (ok)
                  TrErr- Train- SlotClk+ DLActive- BWMgmt- ABWMgmt-
# ★ Speed/Width가 LnkCap과 일치 → 링크 정상
# 불일치 시 → 등화 실패, PCB 문제, 슬롯 문제 의심

## MSI-X Capability
        Capabilities: [b0] MSI-X: Enable+ Count=128 Masked-
                  Vector table: BAR=0 offset=00002000
                  PBA: BAR=0 offset=00003000
# MSI-X Enable+ → 현재 MSI-X 활성 상태
# Count=128 → 최대 128개 벡터 (NVMe: QP당 1벡터)
# Vector table @ BAR0+0x2000, PBA @ BAR0+0x3000

## L1 PM Substates (확장 Capability)
        Capabilities: [900 v1] L1 PM Substates
          L1SubCap: PCI-PM_L1.2+ PCI-PM_L1.1+ ASPM_L1.2+ ASPM_L1.1+
                    PortCommonModeRestoreTime=60us PortTPowerOnTime=10us
          L1SubCtl1: PCI-PM_L1.2+ PCI-PM_L1.1+ ASPM_L1.2+ ASPM_L1.1+
                     T_CommonMode=0us LTR1.2_Threshold=163840ns
# L1.2 ASPM 활성 → 가장 깊은 절전 (레퍼런스 클럭까지 차단)
# PortTPowerOnTime=10us → L1.2→L0 복귀에 10µs 소요

## Kernel driver/module
        Kernel driver in use: nvme
        Kernel modules: nvme
# 현재 바인딩된 커널 드라이버

커널 디버깅

# PCI 관련 커널 메시지
dmesg | grep -i pci

# PCI 리소스 할당 정보
cat /proc/iomem | grep -i pci
cat /proc/ioports | grep -i pci

# PCIe 링크 속도/폭 모니터링
cat /sys/bus/pci/devices/0000:03:00.0/current_link_speed
cat /sys/bus/pci/devices/0000:03:00.0/current_link_width

# AER 에러 카운터
cat /sys/bus/pci/devices/0000:03:00.0/aer_dev_correctable
cat /sys/bus/pci/devices/0000:03:00.0/aer_dev_nonfatal
cat /sys/bus/pci/devices/0000:03:00.0/aer_dev_fatal

# FLR (Function Level Reset)
echo 1 > /sys/bus/pci/devices/0000:03:00.0/reset

# 커널 Dynamic Debug
echo 'module pci +p' > /sys/kernel/debug/dynamic_debug/control
echo 'module pcieport +p' > /sys/kernel/debug/dynamic_debug/control

lspci -tv 출력 예시

PCIe 버스 토폴로지 (lspci -t 출력 구조) 00:00.0 Host Bridge 01.0 → [bus 01] → 00.0 NVIDIA GPU 1c.0 → [bus 02-05] PCIe Switch 01.0→[bus 03]→00.0 Intel NIC (VF) 02.0→[bus 04]→00.0 Samsung NVMe 1d.0→[bus 06]→00.0 USB Controller 1f.0 LPC/eSPI Bridge BDF 주소 형식 Bus:Device.Function (B:D:F) 00:1c.0 → Bus=0x00, Dev=0x1C, Func=0 bridge: 하위 버스 범위 [02-05] 지정 lspci -vvv -s 01:00.0 — 상세 정보 lspci -k — 바인딩된 드라이버 확인 lspci -n — vendor:device ID 표시

PCIe 트레이스포인트

# 사용 가능한 PCI 트레이스포인트 목록
ls /sys/kernel/debug/tracing/events/pci/
# pci_bus_read_config, pci_bus_write_config

# AER 트레이스포인트
ls /sys/kernel/debug/tracing/events/ras/
# aer_event

# Config Space 접근 추적 (디버깅/성능 분석)
echo 1 > /sys/kernel/debug/tracing/events/pci/pci_bus_read_config/enable
echo 1 > /sys/kernel/debug/tracing/events/pci/pci_bus_write_config/enable
cat /sys/kernel/debug/tracing/trace_pipe
# nvme-0      [003] .... 12345.678: pci_bus_read_config: 03:00.0 read 2 bytes @ 4 = 0x0006

# AER 이벤트 모니터링
echo 1 > /sys/kernel/debug/tracing/events/ras/aer_event/enable
cat /sys/kernel/debug/tracing/trace_pipe
# aer_event: 0000:03:00.0 PCIe Bus Error: severity=Corrected, type=Data Link Layer ...

# trace-cmd를 이용한 기록
trace-cmd record -e pci -e ras sleep 10
trace-cmd report | less

# PCIe Port Service 드라이버 상태
lspci -vvv -s 00:1c.0 | grep "Kernel driver"
# Kernel driver in use: pcieport
# pcieport 드라이버가 AER, DPC, Hotplug, PME, Bandwidth Notification 서비스 담당
ls /sys/bus/pci/drivers/pcieport/
# 0000:00:01.0 0000:00:1c.0 ... (Root Port / Switch Port 목록)

자주 발생하는 PCIe 문제와 진단

증상가능한 원인진단 명령해결 방법
LnkSta Speed가 LnkCap보다 낮음등화 실패, PCB 문제, 라이저 카드 불량lspci -vvv | grep LnkSta슬롯 교체, 케이블 점검, BIOS 업데이트
LnkSta Width가 x1로 강등레인 접촉 불량, EMI, 슬롯 오정렬lspci -vvv | grep Width카드 재장착, 다른 슬롯 시도
Correctable Error 누적신호 무결성 저하, 케이블 문제cat aer_dev_correctable물리적 점검, MPS 축소 시도
Completion Timeout디바이스 응답 불가, IOMMU 매핑 오류dmesg | grep CmpltTO디바이스 FLR, IOMMU 도메인 확인
BAR 할당 실패주소 공간 부족 (32-bit 제한)dmesg | grep "can't claim BAR"BIOS "Above 4G Decoding" 활성화
디바이스 미인식 (Vendor=ffff)전원 미공급, PERST# 미해제, 물리 결함lspci -nn전원/슬롯/BIOS 설정 확인
MSI-X 인터럽트 미수신INTx Disable 미설정, 벡터 미할당cat /proc/interruptspci_alloc_irq_vectors() 확인
DMA 실패 (IOMMU fault)DMA 매핑 누락, IOMMU 도메인 오류dmesg | grep DMARdma_map_* 반환값 확인

커널 소스 구조

경로설명
drivers/pci/PCI 코어 서브시스템
drivers/pci/probe.c디바이스 열거, BAR 크기 결정
drivers/pci/pci-driver.cpci_register_driver(), 매칭 로직
drivers/pci/msi/MSI/MSI-X 서브시스템
drivers/pci/pcie/PCIe 서비스 (AER, hotplug, PME, DPC)
drivers/pci/iov.cSR-IOV 지원
drivers/pci/p2pdma.cPeer-to-Peer DMA
drivers/pci/controller/플랫폼별 Host Bridge 드라이버
drivers/vfio/pci/VFIO PCI 드라이버
drivers/cxl/CXL 서브시스템
drivers/staging/gasket/Gasket 프레임워크 (Edge TPU)
drivers/accel/ACCEL 서브시스템 (ML/AI 가속기)
include/drm/drm_accel.hACCEL API 헤더
include/linux/pci.hPCI API 헤더
include/uapi/linux/pci_regs.hPCI/PCIe 레지스터 상수 정의

주요 Kconfig 옵션

옵션설명
CONFIG_PCIPCI 서브시스템 활성화
CONFIG_PCI_MSIMSI/MSI-X 지원
CONFIG_PCIEPORTBUSPCIe Port Bus 드라이버 (AER, Hotplug, PME)
CONFIG_PCIEAERPCIe AER (Advanced Error Reporting)
CONFIG_HOTPLUG_PCI_PCIEPCIe Native Hotplug
CONFIG_PCIE_ASPMASPM (Active State Power Management)
CONFIG_PCI_IOVSR-IOV 지원
CONFIG_PCI_P2PDMAPeer-to-Peer DMA
CONFIG_VFIO_PCIVFIO PCI 드라이버
CONFIG_CXL_BUSCXL 버스 지원
CONFIG_STAGING_GASKET_FRAMEWORKGasket 프레임워크 (Edge TPU)
CONFIG_STAGING_APEX_DRIVEREdge TPU (Apex) 드라이버
CONFIG_DRM_ACCELACCEL 서브시스템 (6.2+)
CONFIG_INTEL_IOMMUIntel VT-d IOMMU
CONFIG_AMD_IOMMUAMD-Vi IOMMU

PCI/PCIe와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.