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/작업 분할 — 즉시 처리와 지연 처리를 구분합니다.
- 안전 한계 — 전원/열/타이밍 임계값을 함께 관리합니다.
- 운영 복구 — 오류 시 재초기화와 롤백 경로를 준비합니다.
단계별 이해
- 장치 수명주기 확인
probe부터 remove까지 흐름을 점검합니다. - 비동기 경로 설계
IRQ, 워크큐, 타이머 역할을 분리합니다. - 자원 정합성 검증
DMA/클록/전원 참조를 교차 확인합니다. - 현장 조건 테스트
연결 끊김/복구/부하 상황을 재현합니다.
PCI/PCIe 개요
PCI (Peripheral Component Interconnect)는 1992년 Intel이 제안한 로컬 버스 규격으로, 이후 PCI Express (PCIe)로 진화하며 현대 시스템의 사실상 표준 인터커넥트가 되었습니다. Linux 커널은 PCI 서브시스템을 통해 디바이스 열거(enumeration), 리소스 할당, 드라이버 바인딩, 전원 관리를 통합적으로 처리합니다.
| 규격 | 연도 | 토폴로지 | 최대 대역폭 (단방향) |
|---|---|---|---|
| PCI 2.x | 1993 | 공유 병렬 버스 (32/64-bit) | 133 / 533 MB/s |
| PCI-X 2.0 | 2003 | 공유 병렬 버스 (64-bit) | 4.3 GB/s (DDR 533) |
| PCIe 1.0 | 2003 | Point-to-point 직렬 | 250 MB/s × lane |
| PCIe 2.0 | 2007 | Point-to-point 직렬 | 500 MB/s × lane |
| PCIe 3.0 | 2010 | Point-to-point 직렬 | ~1 GB/s × lane |
| PCIe 4.0 | 2017 | Point-to-point 직렬 | ~2 GB/s × lane |
| PCIe 5.0 | 2019 | Point-to-point 직렬 | ~4 GB/s × lane |
| PCIe 6.0 | 2022 | Point-to-point 직렬 | ~8 GB/s × lane (PAM4) |
PCIe 아키텍처
토폴로지
PCIe는 기존 PCI의 공유 병렬 버스를 point-to-point 직렬 링크로 대체합니다. 트리 구조의 핵심 구성 요소:
| 구성 요소 | 역할 | Config Header |
|---|---|---|
| Root Complex (RC) | CPU/메모리 ↔ PCIe 패브릭 연결, 최상위 노드 | Type 0/1 |
| Switch | 업스트림 포트 1개 + 다운스트림 포트 N개, 패킷 라우팅 | Type 1 (Bridge) |
| Endpoint | NVMe, NIC, GPU 등 최종 디바이스 | Type 0 |
| Bridge | PCI/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 Read | MRd | MMIO 읽기, DMA 읽기 |
| Memory Write | MWr | MMIO 쓰기, DMA 쓰기 |
| Configuration Read/Write | CfgRd/CfgWr | Configuration Space 접근 |
| I/O Read/Write | IORd/IOWr | 레거시 I/O 포트 접근 |
| Completion | Cpl/CplD | 읽기 요청에 대한 응답 (데이터 포함) |
| Message | Msg/MsgD | 인터럽트(MSI), 전원, 에러 시그널링 |
TLP 헤더 포맷 상세
TLP 헤더는 3 DW (12 바이트) 또는 4 DW (16 바이트)로 구성됩니다. 첫 번째 DW의 Fmt[2:0] 필드가 헤더 길이와 데이터 유무를, Type[4:0] 필드가 트랜잭션 종류를 결정합니다:
| Fmt[2:0] | 의미 | 헤더 | 데이터 | 예시 |
|---|---|---|---|---|
000 | 3DW, no data | 12B | 없음 | MRd (32-bit), CfgRd, IORd |
001 | 4DW, no data | 16B | 없음 | MRd (64-bit) |
010 | 3DW, with data | 12B | 있음 | MWr (32-bit), CfgWr, IOWr |
011 | 4DW, with data | 16B | 있음 | MWr (64-bit) |
100 | TLP Prefix | — | — | PCIe 3.0+ TLP Prefix |
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) |
- Requester ID: TLP를 발행한 디바이스의 BDF. Non-Posted 트랜잭션에서 Completion을 돌려받을 대상을 식별합니다.
- Completer ID: Completion TLP에 포함되어 어느 디바이스가 응답했는지 표시합니다.
- Tag: 동일 Requester가 여러 Non-Posted 요청을 동시 발행할 때 각 요청을 구분하는 8/10/14비트 식별자입니다.
Extended Tag(PCIe 2.0, 10비트)와14-bit Tag(PCIe 6.0)가 미해결 요청 수를 확장합니다.
크레딧 기반 흐름 제어 (Flow Control)
PCIe는 수신 측 버퍼 오버플로우를 방지하기 위해 크레딧 기반 흐름 제어를 사용합니다. 송신 측은 수신 측에서 통지한 크레딧만큼만 TLP를 전송할 수 있으며, 수신 측이 버퍼를 비운 후 UpdateFC DLLP로 크레딧을 반환합니다.
| 크레딧 타입 | 헤더 단위 | 데이터 단위 | 설명 |
|---|---|---|---|
| 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 크레딧으로 조절됨) |
lspci -vvv의 DevSta: 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 매핑 | 용도 |
|---|---|---|
| 0 | VC0 | 일반 트래픽 (기본, 모든 디바이스 필수 지원) |
| 1~7 | VC1~VC7 (선택) | 실시간 오디오/비디오, 등시성(Isochronous) 전송 등 |
pci_enable_vc()로 추가 VC를 활성화할 수 있지만, 일반적인 서버/데스크탑 환경에서는 거의 사용되지 않습니다.Data Link Layer — DLLP와 ACK/NAK
Data Link Layer는 TLP의 신뢰성 보장을 담당합니다. 모든 TLP에 시퀀스 번호와 LCRC를 추가하고, ACK/NAK 프로토콜로 전송 오류를 복구합니다:
| DLLP 타입 | 용도 | 설명 |
|---|---|---|
| Ack | TLP 수신 확인 | 정상 수신된 TLP의 시퀀스 번호를 확인, 송신 측 replay 버퍼 해제 |
| Nak | TLP 재전송 요청 | CRC 에러 감지 시 Nak 전송, 송신 측은 replay 버퍼에서 재전송 |
| InitFC1/InitFC2 | 초기 크레딧 교환 | 링크 초기화 시 수신 버퍼 크기를 상대에게 광고 |
| UpdateFC | 크레딧 갱신 | 수신 버퍼 소비 후 사용 가능한 크레딧을 송신 측에 통지 |
| PM (Power Mgmt) | 전원 관리 | PM_Enter_L1, PM_Request_Ack 등 ASPM 상태 전환 |
| Vendor Specific | 벤더 확장 | 벤더 정의 DLLP |
AER의 Correctable Error 카운터(Bad TLP, Bad DLLP, Replay Number Rollover)가 증가합니다.LTSSM (Link Training and Status State Machine)
PCIe 물리 계층의 링크 초기화와 속도 협상은 LTSSM이 관리합니다. 링크 업부터 전원 절약 상태까지 모든 전환을 11개 주요 상태로 모델링합니다:
PCIe 등화 (Equalization) — Gen3+
PCIe Gen3 (8 GT/s) 이상에서는 고속 신호의 ISI(Inter-Symbol Interference)를 보상하기 위해 송수신 등화(Equalization)가 필수입니다:
| 등화 단계 | Phase | 설명 |
|---|---|---|
| TX De-emphasis | Phase 0/1 | 송신 측이 11개 프리셋(P0~P10) 중 하나를 선택하여 고주파 신호를 강조. 커서 전/후 비율 조정 |
| RX Adaptation | Phase 2/3 | 수신 측이 CTLE(Continuous Time Linear Equalizer)/DFE(Decision Feedback Equalizer)를 조정하여 eye 다이어그램 최적화 |
| Speed Change | — | 등화 완료 후 Recovery → L0로 전환, 협상된 최고 속도로 동작 시작 |
lspci -vvv에서 LnkCap: Speed 16GT/s인데 LnkSta: Speed 8GT/s이면 등화 실패를 의심하세요. 물리적 원인(케이블 품질, PCB 트레이스 길이, 커넥터 접촉)이 대부분이며, 커널 파라미터 pci=noaer로 AER를 비활성화하면 등화 재시도 로그가 사라져 근본 원인을 놓칠 수 있으므로 주의가 필요합니다.PCIe 세대별 인코딩과 Gen6 FLIT 모드
| 세대 | 전송률 | 인코딩 | 인코딩 오버헤드 | 유효 대역폭 (×1) | 유효 대역폭 (×16) |
|---|---|---|---|---|---|
| Gen1 | 2.5 GT/s | 8b/10b | 20% | 250 MB/s | 4 GB/s |
| Gen2 | 5 GT/s | 8b/10b | 20% | 500 MB/s | 8 GB/s |
| Gen3 | 8 GT/s | 128b/130b | ~1.5% | ~985 MB/s | ~15.75 GB/s |
| Gen4 | 16 GT/s | 128b/130b | ~1.5% | ~1.97 GB/s | ~31.5 GB/s |
| Gen5 | 32 GT/s | 128b/130b | ~1.5% | ~3.94 GB/s | ~63 GB/s |
| Gen6 | 64 GT/s | PAM4 + 1b/1b (FLIT) | ~3% (FEC + FLIT) | ~7.56 GB/s | ~121 GB/s |
| Gen7 | 128 GT/s | PAM4 + FLIT | ~3% | ~15.1 GB/s | ~242 GB/s |
- 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 바이트)를 가집니다:
| Offset | Size | Field |
|---|---|---|
| 00h | 2 | Vendor ID |
| 02h | 2 | Device ID |
| 04h | 2 | Command |
| 06h | 2 | Status |
| 08h | 1 | Revision ID |
| 09h | 3 | Class Code (PI + Sub + Base) |
| 0Ch | 1 | Cache Line Size |
| 0Dh | 1 | Latency Timer |
| 0Eh | 1 | Header Type (00h=Endpoint, 01h=Bridge) |
| 0Fh | 1 | BIST |
| 10h-27h | 24 | BAR 0-5 (Type 0) 또는 BAR 0-1 + Bridge 정보 (Type 1) |
| 28h | 4 | CardBus CIS Pointer / Subsystem IDs |
| 2Ch | 4 | Subsystem Vendor ID / Subsystem ID |
| 30h | 4 | Expansion ROM Base Address |
| 34h | 1 | Capabilities Pointer (Capability 연결 리스트 시작) |
| 3Ch | 4 | Interrupt Line / Pin / Min_Gnt / Max_Lat |
Command 레지스터 (오프셋 04h)
드라이버가 디바이스를 제어하는 핵심 레지스터:
| 비트 | 이름 | 설명 |
|---|---|---|
| 0 | I/O Space | I/O BAR 접근 활성화 |
| 1 | Memory Space | Memory BAR 접근 활성화 |
| 2 | Bus Master | DMA(버스 마스터링) 활성화 |
| 6 | Parity Error Response | 패리티 에러 보고 |
| 8 | SERR# Enable | 시스템 에러 보고 활성화 |
| 10 | INTx Disable | 레거시 INTx 인터럽트 비활성화 (MSI 사용 시) |
Capability 구조
오프셋 34h의 Capabilities Pointer부터 시작하는 연결 리스트(linked list)로, 각 Capability는 ID + Next Pointer + 데이터로 구성됩니다:
| Cap ID | 이름 | 설명 |
|---|---|---|
| 01h | Power Management | D0/D1/D2/D3hot 상태 전환 |
| 05h | MSI | Message Signaled Interrupt |
| 10h | PCIe | PCIe Capability (Link/Slot 정보) |
| 11h | MSI-X | 확장 MSI (벡터 테이블 기반) |
PCIe 4 KB 확장 공간(256~4095)에는 Extended Capabilities가 위치합니다:
| Ext Cap ID | 이름 | 설명 |
|---|---|---|
| 0001h | AER | Advanced Error Reporting |
| 0003h | Serial Number | 디바이스 고유 일련번호 |
| 000Dh | ACS | Access Control Services (IOMMU 격리) |
| 0010h | SR-IOV | Single Root I/O Virtualization |
| 001Eh | L1 PM Substates | L1 세부 전원 절약 상태 |
| 0023h | DOE | Data Object Exchange (CXL 등) |
BAR (Base Address Registers)
BAR은 디바이스가 CPU에 노출하는 메모리/I/O 영역의 시작 주소를 정의합니다. Endpoint(Type 0)는 최대 6개, Bridge(Type 1)는 2개의 BAR을 가집니다.
BAR 크기 결정 알고리즘
BAR 크기는 다음 절차로 결정됩니다. 이는 PCI 스펙에 정의된 표준 메커니즘입니다:
/* 커널 내부: 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 MMIO | 00 | 0/1 | 0 | 4 GB 이하 주소. 단일 BAR 사용 |
| 64-bit MMIO | 10 | 0/1 | 0 | 연속 2개 BAR 사용 (BAR[i] + BAR[i+1]). 4 GB 이상 주소 가능 |
| I/O Port | — | — | 1 | I/O 포트 주소. 최대 64 KB. 레거시, PCIe에서는 비권장 |
- 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 서브시스템은 다음 순서로 디바이스를 발견합니다:
- ACPI/DT에서 Host Bridge 정보 수집 — MCFG 테이블(ECAM 베이스), _CRS(리소스 윈도우)
- Bus 0부터 재귀적 스캔 — 각 Bus/Device/Function에 대해 Vendor ID 읽기 (0xFFFF이면 존재하지 않음)
- Bridge(Type 1) 발견 시 — Secondary/Subordinate Bus Number 할당 후 하위 버스 재귀 탐색
- BAR 크기 결정 및 리소스 할당 — 커널의 리소스 관리자가 주소 범위 배정
- 드라이버 매칭 —
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 버퍼 할당 |
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 매핑
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_DEVICE | CPU → 디바이스 (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 INTx | MSI | MSI-X |
|---|---|---|---|
| 시그널링 | 전용 핀 (공유) | Memory Write TLP | Memory Write TLP |
| 벡터 수 | 4 (INTA~D) | 1~32 | 1~2048 |
| 벡터별 타겟 CPU | 불가 | 제한적 | 개별 지정 가능 |
| Config Space | IRQ Line/Pin | Capability (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)
| 비트 | 필드 | 설명 |
|---|---|---|
| 0 | MSI Enable | 1=MSI 활성화, INTx 자동 비활성화 |
| 3:1 | Multiple Message Capable | 디바이스가 요청하는 벡터 수 (2^N, 최대 32) |
| 6:4 | Multiple Message Enable | 소프트웨어가 할당한 벡터 수 (≤ Capable) |
| 7 | 64-bit Address Capable | 1=64비트 Message Address 지원 |
| 8 | Per-Vector Masking | 1=Mask/Pending 비트 지원 |
| 9 | Extended Message Data Capable | PCIe 4.0+ 확장 데이터 (32비트) |
| 10 | Extended Message Data Enable | 확장 데이터 활성화 |
| 15:11 | Reserved | 예약 (0) |
MSI Capability 레이아웃
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 확장 시 사용)
MSI-X Capability 구조 (Cap ID 11h)
MSI-X는 MSI의 제한(최대 32벡터, 단일 MAR/MDR)을 극복하기 위해 도입되었습니다. 별도의 BAR 영역에 벡터 테이블과 PBA를 배치하여, 최대 2048개 벡터를 각각 독립적으로 구성할 수 있습니다.
MSI-X Message Control Register (Offset 02h)
| 비트 | 필드 | 설명 |
|---|---|---|
| 10:0 | Table Size | 벡터 테이블 크기 - 1 (최대 2047 → 2048 엔트리) |
| 13:11 | Reserved | 예약 |
| 14 | Function Mask | 1=모든 벡터 마스크 (글로벌) |
| 15 | MSI-X Enable | 1=MSI-X 활성화, INTx 자동 비활성화 |
MSI-X Capability + Table Entry 레이아웃
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/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를 통해 매핑 테이블이 관리됩니다.
IRR(Interrupt Request Register): LAPIC 내부 레지스터로, 아직 서비스되지 않은 대기 중인 인터럽트 벡터 비트를 저장합니다. CPU가 인터럽트를 처리하기 시작하면 해당 비트가 ISR(In-Service Register)로 이동합니다.
0xFEE00000~0xFEEFFFFF (1MB) 영역은 LAPIC용으로 예약됩니다. 이 영역이 일반 메모리 또는 MMIO BAR과 겹치면 시스템이 부팅되지 않거나 인터럽트가 손실될 수 있습니다. BIOS/펌웨어가 이 영역을 Reserved로 E820 맵에 보고하는지 확인하세요.커널 MSI 서브시스템
리눅스 커널의 MSI 서브시스템은 irq_domain 계층 구조를 기반으로, 플랫폼별 인터럽트 컨트롤러와 PCI MSI를 추상화합니다.
주요 구조체
| 구조체 | 헤더 | 역할 |
|---|---|---|
struct msi_desc | include/linux/msi.h | MSI/MSI-X 디스크립터 — 벡터 정보, affinity, 마스크 상태 |
struct msi_msg | include/linux/msi.h | Message Address/Data 값 (실제 HW에 프로그래밍되는 값) |
struct msi_domain_info | include/linux/msi.h | irq_domain 레벨 MSI 연산 정의 |
struct msi_domain_ops | include/linux/msi.h | MSI 도메인 콜백: prepare, set_desc 등 |
struct irq_chip | include/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()를 호출하면, 커널은 다음 순서로 벡터를 할당합니다.
커널 소스 경로
| 경로 | 설명 |
|---|---|
drivers/pci/msi/msi.c | PCI MSI 코어 — 할당, 마스킹, 해제 |
drivers/pci/msi/irqdomain.c | PCI 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.c | x86 APIC MSI 도메인 구현 |
drivers/irqchip/irq-gic-v3-its-pci-msi.c | ARM GICv3 ITS PCI-MSI 도메인 |
include/linux/msi.h | MSI 구조체 및 API 헤더 |
벡터 할당과 CPU Affinity
멀티코어 시스템에서 인터럽트를 적절한 CPU에 분배하는 것은 성능에 결정적입니다. 커널은 NUMA 토폴로지를 고려한 자동 affinity 할당을 지원합니다.
pci_alloc_irq_vectors 플래그
| 플래그 | 값 | 설명 |
|---|---|---|
PCI_IRQ_MSIX | 0x04 | MSI-X 사용 시도 |
PCI_IRQ_MSI | 0x02 | MSI 사용 시도 |
PCI_IRQ_LEGACY | 0x01 | 레거시 INTx 폴백 |
PCI_IRQ_AFFINITY | 0x10 | 자동 CPU affinity 할당 (커널이 분배) |
PCI_IRQ_ALL_TYPES | 0x07 | MSI-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
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 Address | APIC ID 직접 인코딩 | IRTE 인덱스 인코딩 |
| 보안 | 디바이스가 임의 CPU에 인터럽트 전송 가능 | IRTE 테이블로 제한됨 |
| Address[4] | Redirection Hint | Interrupt Format (1=remappable) |
| Address[3] | Destination Mode | SHV (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 시 주입
intel_iommu=on + intremap=on으로 IR을 활성화하세요.멀티큐 드라이버 패턴
최신 고성능 디바이스는 하드웨어 멀티큐를 지원하며, 각 큐에 개별 MSI-X 벡터를 할당하여 CPU별 독립적인 인터럽트 처리가 가능합니다.
디바이스별 큐 구조
| 디바이스 | 벡터 배분 | 일반적 벡터 수 | 비고 |
|---|---|---|---|
| NVMe | 1 Admin + N I/O 큐 | CPU 수 + 1 | 각 I/O 큐가 CPU에 바인딩 |
| NIC (e.g. mlx5) | N RX + N TX (또는 Combined) | CPU 수 × 1~2 | ethtool -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/interrupts | CPU별 인터럽트 카운트, 벡터 타입 확인 |
lspci -vvv | MSI/MSI-X Capability 디코딩, BAR 오프셋 |
/sys/bus/pci/devices/*/msi_irqs/ | 할당된 IRQ 번호 목록 |
/proc/irq/*/smp_affinity_list | IRQ → CPU affinity 매핑 |
/proc/irq/*/effective_affinity_list | 실제 적용된 affinity (managed_irq) |
dmesg | MSI 할당/실패 메시지, 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 가속기 등에 널리 적용됩니다.
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 등을 하드웨어 레지스터로 기술합니다.
VF 리소스 할당 구조
SR-IOV 활성화 시 NIC의 물리 자원(TX/RX 큐, MSI-X 벡터, MMIO BAR 공간)이 VF 수에 맞게 분할됩니다. VF별로 독립적인 MMIO BAR 공간이 연속으로 매핑되어 게스트 드라이버가 직접 레지스터를 접근합니다.
커널 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 패스스루 전체 스택
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"
- 라이브 마이그레이션 불가 — 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는 에러를 Correctable과 Uncorrectable (Non-Fatal / Fatal)로 분류하여 보고합니다:
| 분류 | 예시 | 처리 |
|---|---|---|
| Correctable | Bad TLP (CRC 오류 후 재전송 성공), Replay Timer Timeout | 하드웨어가 자동 복구, 카운터 증가 |
| Uncorrectable Non-Fatal | Completion Timeout, Unexpected Completion | 트랜잭션 실패, 디바이스 리셋 가능 |
| Uncorrectable Fatal | Data 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,
};
error_detected() → (FLR 또는 Secondary Bus Reset) → slot_reset() → resume(). 드라이버가 err_handler를 등록하지 않으면 커널은 디바이스를 비활성화합니다.AER Extended Capability 레지스터
AER Extended Capability (ID 0001h)는 PCIe 확장 Config Space (오프셋 0x100+)에 위치하며, 에러 감지/보고/마스킹을 제어합니다:
| 오프셋 | 레지스터 | 설명 |
|---|---|---|
| +00h | AER Enhanced Capability Header | ID=0001h, Version, Next Pointer |
| +04h | Uncorrectable Error Status | 비트별 에러 발생 여부 (RW1C — 1 쓰기로 클리어) |
| +08h | Uncorrectable Error Mask | 마스크된 에러는 보고되지 않음 |
| +0Ch | Uncorrectable Error Severity | 0=Non-Fatal, 1=Fatal 지정 |
| +10h | Correctable Error Status | Correctable 에러 발생 여부 (RW1C) |
| +14h | Correctable Error Mask | 마스크 제어 |
| +18h | Advanced Error Capabilities and Control | ECRC 생성/확인 활성화, First Error Pointer |
| +1Ch~+28h | Header Log | 에러를 유발한 TLP 헤더 4 DW 캡처 |
| +2Ch | Root Error Command | Root Port 전용: CE/NFE/FE 인터럽트 활성화 |
| +30h | Root Error Status | Root Port 전용: 에러 수신 상태 |
| +34h~+38h | Error Source ID | 에러를 보고한 디바이스의 BDF |
| +3Ch~+44h | TLP Prefix Log | TLP Prefix 포함 에러 시 캡처 (PCIe 3.0+) |
| Uncorrectable Error 비트 | 이름 | 기본 Severity | 설명 |
|---|---|---|---|
| 4 | Data Link Protocol Error | Fatal | DLLP/TLP 시퀀스 에러, 링크 레벨 실패 |
| 5 | Surprise Down Error | Fatal | 예기치 않은 링크 다운 (핫 리무브 등) |
| 12 | Poisoned TLP Received | Non-Fatal | EP 비트가 설정된 TLP 수신 (데이터 오염) |
| 13 | Flow Control Protocol Error | Fatal | 크레딧 프로토콜 위반 |
| 14 | Completion Timeout | Non-Fatal | Non-Posted 요청에 대한 Completion 미수신 |
| 15 | Completer Abort | Non-Fatal | Completer가 요청을 거부 (CA status) |
| 16 | Unexpected Completion | Non-Fatal | 요청하지 않은 Completion 수신 |
| 18 | Malformed TLP | Fatal | TLP 형식 오류 (잘못된 길이, 필드 등) |
| 19 | ECRC Error | Non-Fatal | End-to-End CRC 검증 실패 |
| 20 | Unsupported Request Error | Non-Fatal | 디바이스가 처리할 수 없는 TLP 수신 (UR) |
| 21 | ACS Violation | Non-Fatal | ACS 정책에 의해 차단된 트랜잭션 |
# 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 에러 처리 전체 흐름
DPC (Downstream Port Containment)
DPC는 PCIe 3.1에서 도입된 에러 격리 메커니즘으로, Uncorrectable Error 발생 시 해당 다운스트림 포트를 자동으로 비활성화하여 에러가 상위 버스로 전파되는 것을 방지합니다:
| 구분 | AER만 사용 | AER + DPC |
|---|---|---|
| 에러 감지 | Root Port에서 ERR_* 메시지 수신 | 다운스트림 포트에서 즉시 감지 |
| 에러 전파 | 상위 버스에 영향 가능 | 포트 비활성화로 격리 (하위만 영향) |
| 복구 범위 | 전체 버스 리셋 가능 | 해당 포트 하위만 리셋 |
| 트리거 | 소프트웨어 (커널 AER 드라이버) | 하드웨어 자동 (DPC Trigger Status) |
| 커널 드라이버 | aer.c | dpc.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 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 비트 | 이름 | 설명 |
|---|---|---|
| V | ACS Source Validation | Requester ID가 업스트림 포트의 것인지 검증 |
| B | ACS Translation Blocking | ATS(Address Translation Services) 번역된 TLP 차단 |
| R | ACS P2P Request Redirect | 피어 간 직접 요청을 업스트림으로 강제 리다이렉트 |
| C | ACS P2P Completion Redirect | 피어 간 Completion을 업스트림으로 리다이렉트 |
| U | ACS Upstream Forwarding | 업스트림으로 전달되어야 할 TLP의 포워딩 허용 |
| E | ACS P2P Egress Control | 특정 피어로의 직접 전송을 비트맵으로 제어 |
| T | ACS Direct Translated P2P | ATS 번역된 P2P 요청에 대한 세밀한 제어 |
- 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.1 | L1 Substate, PLL 꺼짐 | ~32 μs |
| L1.2 | L1 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 # 절전 우선
pcie_aspm=off를 고려하세요.L1 서브스테이트 (L1.1 / L1.2) 상세
PCIe 3.1에서 도입된 L1 PM Substates (Extended Capability ID 001Eh)는 L1 상태 내에서 더 세밀한 전력 절약을 제공합니다:
# 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는 여러 수준의 리셋을 정의하며, 각 리셋의 범위와 복원 수준이 다릅니다:
| 리셋 유형 | 트리거 방법 | 범위 | Config Space | 커널 API |
|---|---|---|---|---|
| Fundamental (Cold) | 전원 차단 → 재인가 | 전체 시스템 | 완전 초기화 | — |
| Fundamental (Warm) | PERST# 핀 assert | 전체 PCIe 계층 | 완전 초기화 | — |
| Hot Reset | Bridge: Secondary Bus Reset | 하위 버스 전체 | 완전 초기화 | pci_reset_bus() |
| FLR | Device Control: Initiate FLR | 단일 펑션 | 대부분 보존 | pci_reset_function() |
| PM Reset | D3hot → D0 전환 | 단일 펑션 | 대부분 보존 | pci_set_power_state() |
| AF FLR | Advanced 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 Hotplug | PCIe Slot Capability 기반, 커널 pciehp 드라이버 |
| ACPI Hotplug | ACPI _HPP/_HPX 메서드 기반, 서버 플랫폼 |
| Thunderbolt/USB4 | PCIe 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/
P2P DMA (Peer-to-Peer DMA)
PCIe Peer-to-Peer DMA는 두 PCIe 디바이스 간 CPU/시스템 메모리를 거치지 않고 직접 데이터를 전송합니다:
| 사용 사례 | 설명 |
|---|---|
| NVMe → GPU | GPUDirect Storage — 스토리지에서 GPU 메모리로 직접 전송 |
| NVMe → NVMe | NVMe 컨트롤러 간 직접 복사 |
| NVMe → RDMA NIC | NVMe-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(...);
NTB (Non-Transparent Bridge)
NTB(Non-Transparent Bridge)는 두 개의 독립적인 PCIe 계층(hierarchy)을 연결하는 특수 PCIe 브리지 디바이스입니다. 일반적인 PCIe 투명 브리지(Transparent Bridge)와 달리, NTB는 양쪽 호스트 각각에게 독립적인 PCIe 엔드포인트로 보이며, 각 호스트가 자체 PCI 버스 번호와 메모리 주소 공간을 독립적으로 관리할 수 있게 합니다.
NTB의 주요 사용 사례:
- 멀티-호스트 시스템: 두 대 이상의 서버가 PCIe 패브릭을 통해 직접 통신
- HA(고가용성) 클러스터링: 장애 발생 시 빠른 페일오버를 위한 저지연 인터커넥트
- 스토리지 어레이: 듀얼 컨트롤러 스토리지 시스템에서 컨트롤러 간 데이터 미러링
- 텔레콤 블레이드: ATCA/MicroTCA 플랫폼에서 블레이드 간 고속 통신
- 임베디드 시스템: SoC 간 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_intel | Intel QPI/UPI 기반 NTB, B2B(Back-to-Back) 및 RP(Root Port) 모드 지원 |
| AMD | ntb_hw_amd | AMD Data Fabric 기반 NTB, Zen 아키텍처 지원 |
| IDT 89HPESxx | ntb_hw_idt | IDT(현 Renesas) PCIe 스위치 칩, 다중 NT 포트 지원, 유연한 토폴로지 |
| Microsemi/Microchip Switchtec | ntb_hw_switchtec | Switchtec PCIe 스위치 기반 NTB, 핫플러그 지원, 높은 대역폭 |
| AMD EPYC | ntb_hw_epyc | AMD EPYC 서버 프로세서 전용 NTB, NUMA 인식 최적화 |
NTB 통신 흐름
Linux NTB 서브시스템 아키텍처
리눅스 커널의 NTB 서브시스템(drivers/ntb/)은 3계층 구조로 설계되어 있습니다:
- NTB 하드웨어 드라이버 계층: 벤더별 하드웨어 추상화 (
ntb_hw_intel,ntb_hw_amd,ntb_hw_idt,ntb_hw_switchtec) - NTB 코어 (
ntb.ko): 통합 API를 제공하는 버스 프레임워크, 하드웨어 드라이버와 클라이언트 드라이버를 매칭 - NTB 클라이언트 드라이버:
ntb_transport— 링 버퍼 기반 메시지 전달 계층ntb_netdev— NTB 위의 가상 이더넷 인터페이스ntb_perf— DMA 성능 벤치마크 도구ntb_tool— debugfs 기반 디버깅 인터페이스
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를 링 버퍼로 분할하여 사용합니다:
- TX 링 버퍼: 상대 호스트의 Memory Window에 매핑되어 데이터를 쓰는 영역
- RX 링 버퍼: 자신의 Memory Window에 할당되어 상대가 쓴 데이터를 읽는 영역
- Doorbell 알림: 데이터 기록 후 Doorbell을 울려 상대에게 새 데이터 도착을 알림
- 플로우 컨트롤: 링 버퍼의 head/tail 포인터로 오버플로우를 방지
/* 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_netdev는 ntb_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 관련 설정이 필요한 경우가 많습니다:
- Split Root Complex 모드: Intel 플랫폼에서는 IIO 설정에서 PCIe 포트를 NTB 모드로 전환해야 합니다
- BAR 크기 설정: BIOS에서 NTB BAR의 크기를 충분히 크게 설정 (Memory Window 크기에 직접 영향)
- ACPI/DSDT: NTB 디바이스가 ACPI 테이블에 정의되어 플랫폼별 자원(인터럽트, 전원 관리)을 할당받음
- Device Tree (ARM/임베디드): DT 바인딩으로 NTB 컨트롤러의 레지스터 영역, 인터럽트, BAR 매핑 정의
/* 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 대안 기술 비교
| 특성 | NTB | CXL | RDMA (InfiniBand/RoCE) | 공유 메모리 (SMP) |
|---|---|---|---|---|
| 물리 계층 | PCIe | PCIe 물리 계층 | 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 + Doorbell | load/store (캐시 코히런트) | verb API / RDMA ops | 일반 메모리 접근 |
| 주요 용도 | 듀얼 컨트롤러, HA | 메모리 풀링, 가속기 | HPC, 데이터센터 | 단일 시스템 |
| 리눅스 지원 | drivers/ntb/ | drivers/cxl/ | drivers/infiniband/ | 기본 내장 |
- 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.io | I/O | PCIe 호환 — 디바이스 열거, Config Space, MMIO |
| CXL.cache | 디바이스→호스트 캐시 | 디바이스가 호스트 메모리를 캐시 코히런트하게 접근 |
| CXL.mem | 호스트→디바이스 메모리 | 디바이스 메모리를 시스템 주소 공간에 매핑 (Type 2/3) |
| CXL 디바이스 타입 | 프로토콜 | 예시 |
|---|---|---|
| Type 1 | CXL.io + CXL.cache | SmartNIC, 가속기 |
| Type 2 | CXL.io + CXL.cache + CXL.mem | GPU, FPGA (디바이스 메모리 포함) |
| Type 3 | CXL.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 노드로 노출
Google TPU (Tensor Processing Unit)
TPU (Tensor Processing Unit)는 Google이 설계한 ASIC 기반 도메인 특화 가속기로, 행렬 연산(텐서 연산)에 최적화되어 머신러닝 학습(Training)과 추론(Inference) 워크로드를 처리합니다. Cloud TPU는 Google 데이터센터에서 PCIe 또는 자체 호스트 인터페이스로 연결되며, Edge TPU는 PCIe M.2 또는 USB 폼 팩터로 임베디드 환경에서 추론 가속을 제공합니다.
drivers/staging/gasket/ (Gasket 프레임워크)와 drivers/accel/ (ACCEL 서브시스템, 6.2+)을 통해 가속기 드라이버를 지원합니다.TPU 세대별 하드웨어 아키텍처
| 세대 | 연도 | 용도 | 연산 성능 | HBM | 인터커넥트 | 공정 |
|---|---|---|---|---|---|---|
| TPU v1 | 2015 | 추론 전용 | 92 TOPS (INT8) | — (DRAM 8 GB) | PCIe 3.0 x16 | 28nm |
| TPU v2 | 2017 | 학습 + 추론 | 45 TFLOPS (bf16) | HBM2 16 GB | ICI (4링크) | 16nm |
| TPU v3 | 2018 | 학습 + 추론 | 123 TFLOPS (bf16) | HBM2 32 GB | ICI (6링크) | 16nm (수냉) |
| TPU v4 | 2021 | 학습 + 추론 | 275 TFLOPS (bf16) | HBM2e 32 GB | ICI (6링크, 3D Torus) | 7nm |
| TPU v5e | 2023 | 효율 최적화 | 197 TFLOPS (bf16) | HBM2e 16 GB | ICI | N/A |
| TPU v5p | 2023 | 대규모 학습 | 459 TFLOPS (bf16) | HBM2e 95 GB | ICI | N/A |
| TPU v6e (Trillium) | 2024 | 차세대 효율 | 918 TFLOPS (bf16) | HBM 32 GB | ICI | N/A |
| Edge TPU | 2019 | 추론 전용 | 4 TOPS (INT8) | — | PCIe / USB | N/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로 명령어 버퍼와 데이터 전송,
- 완료 인터럽트 수신, 메모리 매핑 관리
TPU 메모리 아키텍처
ICI (Inter-Chip Interconnect)
TPU v2 이후 칩 간 통신을 위한 ICI (Inter-Chip Interconnect)를 제공합니다. ICI는 PCIe를 사용하지 않는 Google 자체 설계 고속 직렬 링크로, 다수의 TPU 칩을 직접 연결하여 Pod 단위의 대규모 분산 학습을 가능하게 합니다.
| 세대 | ICI 링크 수 | 토폴로지 | 링크당 대역폭 | Pod 규모 |
|---|---|---|---|---|
| TPU v2 | 4 | 2D Torus (16×16) | ~496 Gbps | 256 칩 (64 TF/pod) |
| TPU v3 | 6 | 2D Torus (32×16) | ~656 Gbps | 1,024 칩 |
| TPU v4 | 6 | 3D Torus (4×4×4 ~ 큐브) | ~2.4 Tbps (총) | 4,096 칩 |
| TPU v5p | 다수 | 3D Torus | 향상 | 8,960 칩 |
TPU의 PCIe 인터페이스
TPU는 호스트 시스템과 PCIe 버스를 통해 연결됩니다. Edge TPU (Coral)는 표준 PCIe 엔드포인트로, Cloud TPU는 커스텀 호스트 인터페이스 칩을 통해 PCIe로 연결됩니다.
| 디바이스 | PCIe 규격 | Vendor ID | Device ID | BAR 구성 |
|---|---|---|---|---|
| Edge TPU (Apex) | PCIe Gen2 x1 | 1ac1 (Google) | 089a | BAR0: 레지스터, BAR2: 펌웨어/데이터 |
| Cloud TPU v4 | PCIe Gen3 x16 | 1ae0 (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.c | PCI probe/remove, char device 등록, BAR 매핑, ioctl 디스패치 |
gasket_ioctl.c | 표준 ioctl 처리 (페이지 테이블 매핑, 이벤트 fd, 디바이스 리셋) |
gasket_page_table.c | 2단계 디바이스 페이지 테이블 관리 (가상주소 → DMA 주소) |
gasket_interrupt.c | MSI-X 인터럽트 설정 및 eventfd 연동 |
gasket_sysfs.c | sysfs 속성 노출 (펌웨어 버전, 디바이스 상태 등) |
apex_driver.c | Edge 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 프레임워크가 이 페이지 테이블의 생명주기를 관리합니다.
/* 페이지 테이블 매핑 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 인터럽트 처리 (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 | 필요 (디스플레이 출력) | 불필요 (연산 전용) |
| GEM | GPU 버퍼 관리 | 가속기 메모리 관리 |
| 스케줄러 | drm_sched (GPU 작업) | 디바이스별 자체 스케줄러 |
| 주요 드라이버 | i915, amdgpu, nouveau | habanalabs (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;
}
TPU 소프트웨어 스택 (커널-사용자 공간 인터페이스)
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_STAGING | Staging 드라이버 활성화 (Gasket 포함) |
CONFIG_STAGING_GASKET_FRAMEWORK | Gasket 코어 프레임워크 |
CONFIG_STAGING_APEX_DRIVER | Edge TPU (Apex) 드라이버 |
CONFIG_DRM_ACCEL | ACCEL 서브시스템 (Linux 6.2+, DRM 기반) |
CONFIG_DRM | DRM 코어 (ACCEL 의존성) |
CONFIG_PCI_MSI | MSI/MSI-X (TPU 인터럽트) |
CONFIG_IOMMU_SUPPORT | IOMMU (DMA 보안 격리) |
CONFIG_DMA_SHARED_BUFFER | DMA-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 컨텍스트 전환) | 하드웨어 자체 스케줄링 |
| 사용자 API | OpenGL/Vulkan → DRM ioctl | XLA/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.c | PCI probe, BAR 매핑, char device 관리 |
drivers/staging/gasket/gasket_page_table.c | 디바이스 페이지 테이블 (DMA 주소 변환) |
drivers/staging/gasket/gasket_interrupt.c | MSI-X 설정, eventfd 통지 |
drivers/staging/gasket/apex_driver.c | Edge 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.h | ACCEL 서브시스템 API 헤더 |
sysfs 인터페이스
PCI 디바이스 정보는 /sys/bus/pci/ 아래에 노출됩니다:
PCIe Performance 튜닝
PCIe 대역폭을 최대로 활용하려면 TLP 오버헤드를 최소화하고, 흐름 제어 병목을 해소해야 합니다. 주요 튜닝 포인트:
MPS / MRRS 최적화
# 현재 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 |
1a4e0c4). 문제 발생 시 pci=norelaxedordering 커널 파라미터로 RO를 비활성화할 수 있습니다.Completion Timeout 튜닝
Non-Posted 요청(MRd, CfgRd/Wr)에 대한 Completion이 일정 시간 내에 도착하지 않으면 Completion Timeout 에러가 발생합니다:
| 타임아웃 범위 | 값 | 용도 |
|---|---|---|
| 50 µs ~ 50 ms | Range A | 기본. 대부분의 디바이스에 적합 |
| 50 ms ~ 10 s | Range 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 노드 확인
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 트레이스포인트
# 사용 가능한 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/interrupts | pci_alloc_irq_vectors() 확인 |
| DMA 실패 (IOMMU fault) | DMA 매핑 누락, IOMMU 도메인 오류 | dmesg | grep DMAR | dma_map_* 반환값 확인 |
커널 소스 구조
| 경로 | 설명 |
|---|---|
drivers/pci/ | PCI 코어 서브시스템 |
drivers/pci/probe.c | 디바이스 열거, BAR 크기 결정 |
drivers/pci/pci-driver.c | pci_register_driver(), 매칭 로직 |
drivers/pci/msi/ | MSI/MSI-X 서브시스템 |
drivers/pci/pcie/ | PCIe 서비스 (AER, hotplug, PME, DPC) |
drivers/pci/iov.c | SR-IOV 지원 |
drivers/pci/p2pdma.c | Peer-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.h | ACCEL API 헤더 |
include/linux/pci.h | PCI API 헤더 |
include/uapi/linux/pci_regs.h | PCI/PCIe 레지스터 상수 정의 |
주요 Kconfig 옵션
| 옵션 | 설명 |
|---|---|
CONFIG_PCI | PCI 서브시스템 활성화 |
CONFIG_PCI_MSI | MSI/MSI-X 지원 |
CONFIG_PCIEPORTBUS | PCIe Port Bus 드라이버 (AER, Hotplug, PME) |
CONFIG_PCIEAER | PCIe AER (Advanced Error Reporting) |
CONFIG_HOTPLUG_PCI_PCIE | PCIe Native Hotplug |
CONFIG_PCIE_ASPM | ASPM (Active State Power Management) |
CONFIG_PCI_IOV | SR-IOV 지원 |
CONFIG_PCI_P2PDMA | Peer-to-Peer DMA |
CONFIG_VFIO_PCI | VFIO PCI 드라이버 |
CONFIG_CXL_BUS | CXL 버스 지원 |
CONFIG_STAGING_GASKET_FRAMEWORK | Gasket 프레임워크 (Edge TPU) |
CONFIG_STAGING_APEX_DRIVER | Edge TPU (Apex) 드라이버 |
CONFIG_DRM_ACCEL | ACCEL 서브시스템 (6.2+) |
CONFIG_INTEL_IOMMU | Intel VT-d IOMMU |
CONFIG_AMD_IOMMU | AMD-Vi IOMMU |
관련 문서
PCI/PCIe와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.