PCI / PCIe 서브시스템

PCI/PCIe 하드웨어 아키텍처부터 Linux 커널 드라이버 프레임워크, DMA, 인터럽트, SR-IOV, 전원 관리, 디버깅까지 종합 가이드.

관련 표준: PCI Local Bus 3.0 (설정 공간, BAR), PCIe Base Specification 6.0 (직렬 인터커넥트, TLP), CXL 3.1 (캐시 일관성) — PCI/PCIe 서브시스템이 구현하는 버스 아키텍처 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

PCI/PCIe 개요

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

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

PCIe 아키텍처

토폴로지

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

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

PCIe 계층 구조

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

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

TLP (Transaction Layer Packet)

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

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

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)

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

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

Capability 구조

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

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

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

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

BAR (Base Address Registers)

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

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 크기 결정: 펌웨어/OS가 BAR에 0xFFFFFFFF를 쓰고 다시 읽으면, 디바이스가 디코딩하는 비트만 1로 유지됩니다. 하위 고정 비트를 마스킹하고 비트 반전 + 1 하면 BAR 크기를 얻습니다.
/* 커널에서 BAR 정보 접근 */
struct pci_dev *pdev;

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

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

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

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

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

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

Linux 커널 PCI 서브시스템

핵심 자료구조

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

struct pci_dev 주요 필드

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

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

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

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

PCI 열거 (Enumeration)

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

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

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

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

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

    pci_device_add(dev, bus);
    return dev;
}

PCI 드라이버 작성

드라이버 골격

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

#define MY_VENDOR_ID  0x1234
#define MY_DEVICE_ID  0x5678

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

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

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

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

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

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

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

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

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

    pci_set_drvdata(pdev, priv);

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

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

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

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

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

Managed API (pcim_* / devm_*)

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

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

PCI ID 매칭 매크로

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

DMA 매핑

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

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

Coherent (Consistent) DMA

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

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

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

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

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

Streaming DMA

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

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

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

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

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

DMA 방향

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

Scatter-Gather DMA

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

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

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

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

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

IOMMU / DMA Remapping

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

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

MSI / MSI-X 인터럽트

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

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

MSI Capability 구조 (Cap ID 05h)

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

Message Control Register (Offset 02h)

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

MSI Capability 레이아웃

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 확장 시 사용)
TLP 메커니즘: MSI는 디바이스가 Memory Write TLP를 전송하여 인터럽트를 시그널링합니다. TLP의 주소 필드가 MAR, 데이터 필드가 MDR에 대응합니다. PCIe 스위치/브리지는 이를 일반 메모리 쓰기와 동일하게 라우팅하므로, 별도의 사이드밴드 신호선이 필요 없습니다.

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

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

MSI-X Message Control Register (Offset 02h)

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

MSI-X Capability + Table Entry 레이아웃

/* Configuration Space (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 vs MSI-X 핵심 차이: MSI는 Configuration Space에 주소/데이터를 저장하므로 변경 시 Config 접근이 필요하지만, MSI-X는 BAR 영역(MMIO)에 테이블을 배치하여 벡터별 독립적이고 빠른 접근이 가능합니다. 또한 MSI-X는 벡터별 개별 마스킹을 기본 지원합니다.

인터럽트 전달 메커니즘

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

x86: LAPIC 기반 전달

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

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

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

MSI/MSI-X 인터럽트 전달 경로 (x86) PCIe Device MSI-X Table TLP PCIe Fabric Switch / Bridge Mem Wr Root Complex 0xFEExxxxx 감지 IOMMU / IR Interrupt Remapping (optional) LAPIC IRR 비트 설정 CPU IDT MSI 전달 경로 IOMMU IR 활성 시 추가 경로
APIC 주소 범위 충돌: 0xFEE00000~0xFEEFFFFF (1MB) 영역은 LAPIC용으로 예약됩니다. 이 영역이 일반 메모리 또는 MMIO BAR과 겹치면 시스템이 부팅되지 않거나 인터럽트가 손실될 수 있습니다. BIOS/펌웨어가 이 영역을 Reserved로 E820 맵에 보고하는지 확인하세요.

커널 MSI 서브시스템

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

주요 구조체

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

msi_msg / msi_desc 코드

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

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

pci_alloc_irq_vectors() 내부 흐름

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

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

벡터 할당과 CPU Affinity

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

pci_alloc_irq_vectors 플래그

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

NVMe 스타일 affinity 벡터 할당

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

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

if (nvecs < 0)
    return nvecs;

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

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

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

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

sysfs affinity 인터페이스

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

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

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

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

Interrupt Remapping (IOMMU)

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

호환 형식 vs 리매핑 형식

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

Posted Interrupts (VM 패스스루)

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

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

멀티큐 드라이버 패턴

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

디바이스별 큐 구조

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

멀티큐 드라이버 코드 패턴

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

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

    if (nvecs < 0)
        return nvecs;

    priv->num_io_queues = nvecs - 1;

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

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

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

    return 0;

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

디버깅과 모니터링

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

/proc/interrupts 해석

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

lspci -vvv MSI Capability 디코딩

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

sysfs + dmesg 확인

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

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

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

디버깅 도구 요약

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

SR-IOV (Single Root I/O Virtualization)

SR-IOV는 하나의 물리 디바이스(PF)를 여러 가상 디바이스(VF)로 분할하여, 각 VF를 VM에 직접 할당(passthrough)할 수 있게 합니다.

개념설명
PF (Physical Function)전체 디바이스 기능을 가진 물리 함수, 호스트 드라이버가 관리
VF (Virtual Function)경량화된 가상 함수, 독립적인 Config Space/BAR/MSI-X 보유
VF BARPF의 SR-IOV Capability에 정의된 VF용 BAR 리소스
ARIAlternative 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는 에러를 CorrectableUncorrectable (Non-Fatal / Fatal)로 분류하여 보고합니다:

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

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

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

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

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

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

전원 관리

PCI 전원 상태 (D-States)

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

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

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

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

static DEFINE_SIMPLE_DEV_PM_OPS(my_pm_ops, my_suspend, my_resume);

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

ASPM (Active State Power Management)

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

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

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

PCI 핫플러그 (Hotplug)

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

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

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

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

VFIO (Virtual Function I/O)

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

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

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

# IOMMU 그룹 확인
ls -l /sys/bus/pci/devices/0000:03:00.0/iommu_group/devices/

P2P DMA (Peer-to-Peer DMA)

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

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

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

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

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

CXL (Compute Express Link)

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

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

# cxl-cli 도구 (ndctl 패키지)
cxl list                       # CXL 토폴로지 표시
cxl list -M                    # 메모리 디바이스 목록
cxl create-region -t ram ...   # CXL 메모리 리전 생성 → NUMA 노드로 노출

Google TPU (Tensor Processing Unit)

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

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

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

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

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

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

/*
 * TPU 시스톨릭 어레이 동작 원리
 *
 * 128×128 Processing Element (PE) 격자 구조
 * 각 PE는 MAC (Multiply-Accumulate) 연산 1회/사이클 수행
 *
 *   Weight 데이터 → 상단에서 아래로 흐름 (preloaded)
 *   Input 데이터 → 좌측에서 우측으로 흐름
 *   결과(부분합) → 상단에서 아래로 누적
 *
 *        w[0]    w[1]    w[2]    ...    w[127]
 *          ↓       ↓       ↓              ↓
 * x[0] → [PE] → [PE] → [PE] → ... → [PE] → (partial sum)
 * x[1] → [PE] → [PE] → [PE] → ... → [PE]
 * x[2] → [PE] → [PE] → [PE] → ... → [PE]
 *  ...     ↓       ↓       ↓              ↓
 * x[127]→ [PE] → [PE] → [PE] → ... → [PE]
 *          ↓       ↓       ↓              ↓
 *        y[0]    y[1]    y[2]          y[127]
 *
 * 1 사이클에 128×128 = 16,384 MAC 연산
 * bf16 기준: 16,384 × 2 (MAC = mul + add) = 32,768 FLOPS/사이클
 *
 * TPU v4: 클럭 ~1.05 GHz × 32,768 FLOPS × 4 MXU × 2 (bf16 packing)
 *       ≈ 275 TFLOPS (bf16)
 */

/* 시스톨릭 어레이의 커널 관점 의미:
 * - 호스트 CPU는 MXU를 직접 프로그래밍하지 않음
 * - XLA/PJRT 컴파일러가 HLO → TPU 명령어로 변환
 * - 커널 드라이버 역할: DMA로 명령어 버퍼와 데이터 전송,
 *   완료 인터럽트 수신, 메모리 매핑 관리
 */
bf16 (bfloat16): Google이 TPU용으로 설계한 16비트 부동소수점 형식. IEEE fp32와 동일한 8비트 지수부(exponent)를 가져 표현 범위(dynamic range)가 fp32와 같으면서, 7비트 가수부(mantissa)로 메모리 대역폭을 절반으로 줄입니다. 딥러닝 학습에서 fp32와 거의 동등한 수렴 특성을 보입니다.

TPU 메모리 아키텍처

/*
 * TPU v4 메모리 계층 구조
 *
 *  ┌─────────────────────────────────────────────┐
 *  │              TensorCore (×2 per chip)        │
 *  │  ┌───────┐  ┌───────┐  ┌───────────────┐   │
 *  │  │ MXU 0 │  │ 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 v242D Torus (16×16)~496 Gbps256 칩 (64 TF/pod)
TPU v362D Torus (32×16)~656 Gbps1,024 칩
TPU v463D Torus (4×4×4 ~ 큐브)~2.4 Tbps (총)4,096 칩
TPU v5p다수3D Torus향상8,960 칩
/*
 * TPU v4 Pod — 3D Torus 토폴로지
 *
 * 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 IDDevice IDBAR 구성
Edge TPU (Apex)PCIe Gen2 x11ac1 (Google)089aBAR0: 레지스터, BAR2: 펌웨어/데이터
Cloud TPU v4PCIe Gen3 x161ae0 (Google)(비공개)다수 BAR: CSR, DMA, HBM 윈도우
# Edge TPU (Coral PCIe Accelerator) 확인
lspci -nn | grep -i google
# 03:00.0 System peripheral [0880]: Global Unichip Corp. [1ac1:089a]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Edge TPU (Apex) 드라이버 상세

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

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

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

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

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

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

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

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

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

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

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

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

Gasket 디바이스 페이지 테이블

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

/*
 * Gasket 2단계 페이지 테이블 구조
 *
 *   사용자 공간 VA ──→ Gasket ioctl ──→ 디바이스 페이지 테이블
 *
 *   Level 0 (Directory):
 *   ┌──────────┬──────────┬──────────┬───┐
 *   │ Entry 0  │ 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필요 (디스플레이 출력)불필요 (연산 전용)
GEMGPU 버퍼 관리가속기 메모리 관리
스케줄러drm_sched (GPU 작업)디바이스별 자체 스케줄러
주요 드라이버i915, amdgpu, nouveauhabanalabs (Gaudi), qaic, ivpu
디바이스 노드/dev/dri/cardN, /dev/dri/renderDN/dev/accel/accelN
/* ACCEL 드라이버 등록 예시 (DRM 기반) */
#include <drm/drm_accel.h>
#include <drm/drm_drv.h>

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

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

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

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

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

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

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

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

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

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

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

TPU 주요 ioctl 인터페이스

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

TPU sysfs 인터페이스

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

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

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

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

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

Edge TPU 드라이버 설치 및 확인

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

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

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

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

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

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

TPU 관련 Kconfig 옵션

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

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

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

TPU 관련 커널 소스 구조

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

sysfs 인터페이스

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

/sys/bus/pci/devices/0000:03:00.0/
├── vendor              # 0x8086
├── device              # 0x1572
├── class               # 0x020000 (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.cpci_register_driver(), 매칭 로직
drivers/pci/msi/MSI/MSI-X 서브시스템
drivers/pci/pcie/PCIe 서비스 (AER, hotplug, PME, DPC)
drivers/pci/iov.cSR-IOV 지원
drivers/pci/p2pdma.cPeer-to-Peer DMA
drivers/pci/controller/플랫폼별 Host Bridge 드라이버
drivers/vfio/pci/VFIO PCI 드라이버
drivers/cxl/CXL 서브시스템
drivers/staging/gasket/Gasket 프레임워크 (Edge TPU)
drivers/accel/ACCEL 서브시스템 (ML/AI 가속기)
include/drm/drm_accel.hACCEL API 헤더
include/linux/pci.hPCI API 헤더
include/uapi/linux/pci_regs.hPCI/PCIe 레지스터 상수 정의

주요 Kconfig 옵션

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