PCI / PCIe 서브시스템
PCI/PCIe 하드웨어 아키텍처부터 Linux 커널 드라이버 프레임워크, DMA, 인터럽트, SR-IOV, 전원 관리, 디버깅까지 종합 가이드.
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), 전원, 에러 시그널링 |
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 레이아웃 (32-bit Memory BAR):
[31:n] Base Address (n = log2(크기))
[n-1:4] Reserved (0)
[3] Prefetchable (1=가능, 0=불가)
[2:1] Type: 00=32-bit, 10=64-bit
[0] Space: 0=Memory, 1=I/O
64-bit Memory BAR:
BAR[i] 하위 32비트
BAR[i+1] 상위 32비트 (두 개의 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 레이아웃
Offset Size Field
────── ───── ─────────────────────────────────
+00h 8-bit Capability ID (05h)
+01h 8-bit Next Capability Pointer
+02h 16-bit Message Control
+04h 32-bit Message Address (Lower 32)
+08h 32-bit Message Address (Upper 32) ← 64-bit only
+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
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 레이아웃
/* Configuration Space (Capability) */
Offset Size Field
────── ───── ─────────────────────────────────
+00h 8-bit Capability ID (11h)
+01h 8-bit Next Capability Pointer
+02h 16-bit Message Control
+04h 32-bit Table Offset / BIR
[2:0] BAR Indicator Register (어느 BAR에 테이블이 있는지)
[31:3] Offset (8바이트 정렬)
+08h 32-bit PBA Offset / BIR
[2:0] BAR Indicator Register
[31:3] Offset (8바이트 정렬)
/* BAR 영역 — MSI-X Table Entry (각 16바이트) */
Offset Size Field
────── ───── ─────────────────────────────────
+00h 32-bit Message Address (Lower)
+04h 32-bit Message Address (Upper)
+08h 32-bit Message Data
+0Ch 32-bit Vector Control
[0] Mask Bit (1=마스크됨)
[31:1] Reserved
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를 통해 매핑 테이블이 관리됩니다.
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()를 호출하면, 커널은 다음 순서로 벡터를 할당합니다.
pci_alloc_irq_vectors(pdev, min, max, flags)
├─ flags에 PCI_IRQ_MSIX가 있으면 → __pci_enable_msix_range()
│ ├─ msix_capability_init()
│ │ ├─ BAR 영역 ioremap (MSI-X Table)
│ │ ├─ msi_desc 할당 (벡터 수만큼)
│ │ └─ pci_msi_setup_msi_irqs() → irq_domain 통해 HW 프로그래밍
│ └─ 성공 시 MSI-X Enable 비트 설정 + INTx 비활성화
├─ 실패하고 PCI_IRQ_MSI가 있으면 → __pci_enable_msi_range()
│ ├─ msi_capability_init()
│ └─ Config Space에 MAR/MDR 프로그래밍
└─ 실패하고 PCI_IRQ_LEGACY가 있으면 → Legacy INTx 유지 (1 반환)
커널 소스 경로
| 경로 | 설명 |
|---|---|
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는 하나의 물리 디바이스(PF)를 여러 가상 디바이스(VF)로 분할하여, 각 VF를 VM에 직접 할당(passthrough)할 수 있게 합니다.
| 개념 | 설명 |
|---|---|
| PF (Physical Function) | 전체 디바이스 기능을 가진 물리 함수, 호스트 드라이버가 관리 |
| VF (Virtual Function) | 경량화된 가상 함수, 독립적인 Config Space/BAR/MSI-X 보유 |
| VF BAR | PF의 SR-IOV Capability에 정의된 VF용 BAR 리소스 |
| ARI | Alternative Routing-ID Interpretation — 256개 이상 Function 지원 |
/* PF 드라이버에서 VF 활성화 */
static int my_sriov_configure(struct pci_dev *pdev, int num_vfs)
{
if (num_vfs == 0) {
pci_disable_sriov(pdev);
return 0;
}
return pci_enable_sriov(pdev, num_vfs);
}
static struct pci_driver my_driver = {
.name = "my_nic",
.id_table = my_ids,
.probe = my_probe,
.remove = my_remove,
.sriov_configure = my_sriov_configure, /* sysfs 인터페이스 */
};
# sysfs를 통한 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를 VM에 할당 (VFIO)
echo 0000:03:00.2 > /sys/bus/pci/devices/0000:03:00.2/driver/unbind
echo vfio-pci > /sys/bus/pci/devices/0000:03:00.2/driver_override
echo 0000:03:00.2 > /sys/bus/pci/drivers/vfio-pci/bind
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를 등록하지 않으면 커널은 디바이스를 비활성화합니다.전원 관리
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를 고려하세요.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(...);
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 메모리 아키텍처
/*
* TPU v4 메모리 계층 구조
*
* ┌─────────────────────────────────────────────┐
* │ TensorCore (×2 per chip) │
* │ ┌───────┐ ┌───────┐ ┌───────────────┐ │
* │ │ MXU 0 │ │ MXU 1 │ │ Vector Unit │ │
* │ │128×128│ │128×128│ │ (SIMD ALU) │ │
* │ └───┬───┘ └───┬───┘ └──────┬────────┘ │
* │ └──────┬────┘ │ │
* │ ┌──┴──┐ ┌────┴───┐ │
* │ │VMEM │ │ SMEM │ │
* │ │16 MB│ │(Scalar)│ │
* │ └──┬──┘ └────┬───┘ │
* │ └────────┬─────────┘ │
* └──────────────────────┼───────────────────────┘
* ┌────┴────┐
* │ CMEM │ ← 공통 메모리 (Cross-Core)
* │ (Shared)│
* └────┬────┘
* ┌────┴────┐
* │ HBM2e │ ← 메인 메모리 (32 GB)
* │ ~1.6 TB/s│ ← 대역폭
* └────┬────┘
* ┌────┴────┐
* │ ICI │ ← Inter-Chip Interconnect
* │(6 links)│ (다른 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 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 v4 Pod — 3D Torus 토폴로지
*
* 4,096 칩을 4×4×4 "큐브" 블록 단위로 구성하고,
* 큐브들을 다시 3D 토러스로 연결.
*
* z
* ↑ ┌────┬────┬────┬────┐
* │ │chip│chip│chip│chip│ ←─ ICI 링크 (x축)
* │ ├────┼────┼────┼────┤
* │ │chip│chip│chip│chip│
* │ ├────┼────┼────┼────┤
* │ │chip│chip│chip│chip│ ←─ ICI 링크 (y축)
* │ ├────┼────┼────┼────┤
* │ │chip│chip│chip│chip│
* │ └────┴────┴────┴────┘
* └──────────→ x
* ↙ y
*
* 3D 토러스 장점:
* - 각 칩이 6개 이웃과 직접 연결 (±x, ±y, ±z)
* - AllReduce 등 집합 통신에서 bisection bandwidth 극대화
* - 단일 링크 장애 시 대체 경로 존재 (내결함성)
*
* 커널 관점: ICI는 TPU 칩 내부 하드웨어가 관리하며,
* 호스트 커널 드라이버는 ICI를 직접 제어하지 않습니다.
* 호스트 드라이버는 각 TPU 칩에 대한 PCIe 연결만 관리합니다.
*/
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 프레임워크가 이 페이지 테이블의 생명주기를 관리합니다.
/*
* Gasket 2단계 페이지 테이블 구조
*
* 사용자 공간 VA ──→ Gasket ioctl ──→ 디바이스 페이지 테이블
*
* Level 0 (Directory):
* ┌──────────┬──────────┬──────────┬───┐
* │ Entry 0 │ Entry 1 │ Entry 2 │...│ 각 엔트리: L1 테이블 DMA 주소
* └────┬─────┴────┬─────┴──────────┴───┘
* │ │
* ▼ ▼
* Level 1 (Page Table):
* ┌──────────┐ ┌──────────┐
* │ PTE 0 │ │ PTE 0 │ 각 PTE: 호스트 물리 페이지 DMA 주소
* │ PTE 1 │ │ PTE 1 │
* │ ... │ │ ... │
* └──────────┘ └──────────┘
*
* TPU DMA 엔진이 이 페이지 테이블을 참조하여
* 호스트 메모리에 직접 접근 (scatter-gather DMA)
*/
/* 페이지 테이블 매핑 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 │ │ TPU Chip │
* │ │ │ │
* │ ┌────────────┐ │ PCIe │ ┌────────────┐ │
* │ │ User Buffer│──┼────→────┼──│ HBM │ │
* │ │ (pinned) │ │ DMA │ │ (모델 가중치)│ │
* │ └────────────┘ │ │ └────────────┘ │
* │ │ │ │
* │ ┌────────────┐ │ PCIe │ ┌────────────┐ │
* │ │ Input Data │──┼────→────┼──│ HBM │ │
* │ │ (pinned) │ │ DMA │ │ (입력 텐서) │ │
* │ └────────────┘ │ │ └────────────┘ │
* │ │ │ │
* │ │ │ ┌────────────┐ │
* │ │ │ │ TensorCore │ │
* │ │ │ │ (MXU 연산) │ │
* │ │ │ └────────────┘ │
* │ │ │ │
* │ ┌────────────┐ │ PCIe │ ┌────────────┐ │
* │ │ Output Buf │←─┼────←────┼──│ HBM │ │
* │ │ (결과) │ │ DMA │ │ (출력 텐서) │ │
* │ └────────────┘ │ │ └────────────┘ │
* │ │ │ │
* │ ← 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 | 필요 (디스플레이 출력) | 불필요 (연산 전용) |
| 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/ 아래에 노출됩니다:
/sys/bus/pci/devices/0000:03:00.0/
├── vendor # 0x8086
├── device # 0x1572
├── class # 0x020000 (Network Controller)
├── subsystem_vendor # Subsystem Vendor ID
├── subsystem_device # Subsystem Device ID
├── revision # Revision ID
├── config # Configuration Space 바이너리 (256/4096 바이트)
├── resource # BAR 리소스 목록 (start end flags)
├── resource0 # BAR 0 mmap 가능 (root)
├── irq # 할당된 IRQ 번호
├── msi_irqs/ # MSI/MSI-X 벡터 목록
├── numa_node # NUMA 노드 ID
├── local_cpus # 인접 CPU 마스크
├── driver/ # 바인딩된 드라이버 심볼릭 링크
├── driver_override # 강제 드라이버 바인딩
├── enable # 디바이스 활성화/비활성화
├── remove # 디바이스 제거
├── rescan # 하위 버스 재스캔
├── reset # Function Level Reset (FLR)
├── current_link_speed # 현재 링크 속도 (예: 8.0 GT/s PCIe)
├── current_link_width # 현재 링크 폭 (예: x16)
├── max_link_speed # 최대 지원 링크 속도
├── max_link_width # 최대 지원 링크 폭
├── sriov_numvfs # SR-IOV VF 수 (PF만)
├── sriov_totalvfs # 지원 가능한 최대 VF 수
└── iommu_group/ # IOMMU 그룹 정보
디버깅 도구
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 활성화
커널 디버깅
# 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 출력 예시
-[0000:00]-+-00.0 Intel Corporation Host Bridge
+-01.0-[01]----00.0 NVIDIA Corporation GPU
+-1c.0-[02-05]----00.0 Intel PCIe Switch (Upstream)
| +-01.0-[03]----00.0 Intel NIC (VF capable)
| \-02.0-[04]----00.0 Samsung NVMe SSD
+-1d.0-[06]----00.0 Intel USB Controller
\-1f.0 Intel LPC/eSPI Bridge
커널 소스 구조
| 경로 | 설명 |
|---|---|
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 |