第四篇:中断重映射与 PASID — DMA 之外的能力¶
源码:
drivers/iommu/intel/irq_remapping.cdrivers/iommu/intel/pasid.cdrivers/iommu/intel/pasid.h| 系列目录:Intel IOMMU 内核源码深度解析
第一部分:中断重映射¶
1. 为什么需要中断重映射¶
在没有 IOMMU 中断重映射的传统 x86 系统中,设备 MSI/MSI-X 中断直接写入 CPU 的 Local APIC(或 x2APIC),Destination ID 和 Vector 由 PCI 配置空间写入。这带来两个问题:
- 虚拟化隔离:恶意或故障设备可以伪造中断消息,触发任意 CPU 中断向量
- 地址空间限制:xAPIC 的 8-bit Destination ID 限制了两 socket 以上的扩展
VT-d 中断重映射通过在 IOMMU 中引入 IRTE (Interrupt Remapping Table Entry) 来解决这些问题。
中断重映射流程¶
PCIe MSI/MSI-X TLP (设备 → IOMMU → 系统总线)
│
│ 传统路径 (无 IR):
│ ┌──────────────────────────────────────────┐
│ │ MSI Addr = 0xFEEXXXXX │
│ │ MSI Data = Vector + Delivery Mode etc. │
│ │ 直接写入 LAPIC │
│ └──────────────────────────────────────────┘
│
│ IOMMU 重映射路径 (有 IR):
│ ┌──────────────────────────────────────────┐
│ │ MSI Addr = 0xFEEXXXXX + IR Index │
│ │ MSI Data = 写入 IRTE 指定的格式 │
│ │ ↓ │
│ │ IOMMU 截获 → IRTE 查找 │
│ │ ↓ │
│ │ 转换:destination ID + vector + delivery │
│ │ ↓ │
│ │ 经过重映射的中断消息 → LAPIC/x2APIC │
│ └──────────────────────────────────────────┘
2. IRTE 表结构¶
2.1 struct ir_table — 中断重映射表¶
// drivers/iommu/intel/iommu.h:501-504
#define INTR_REMAP_TABLE_ENTRIES 65536
struct ir_table {
struct irte *base; /* 指向 IRTE 表 (物理连续) */
unsigned long *bitmap; /* IRTE 分配位图 */
};
IRTE 表固定为 65536 个条目,每条目 16 字节(总计 1MB),由 bitmap 管理分配。
2.2 struct irq_2_iommu — IRQ → IOMMU 映射¶
// drivers/iommu/intel/irq_remapping.c:43-50
struct irq_2_iommu {
struct intel_iommu *iommu; /* 所属 IOMMU */
u16 irte_index; /* IRTE 表中的索引 */
u16 sub_handle; /* 子句柄 (用于多条目分配) */
u8 irte_mask; /* 掩码 (0 表示单条目) */
bool posted_msi; /* 是否使用 Posted MSI */
bool posted_vcpu; /* 是否投递到虚拟 CPU */
};
2.3 struct intel_ir_data — 每个 IRQ 的私有数据¶
// drivers/iommu/intel/irq_remapping.c:52-58
struct intel_ir_data {
struct irq_2_iommu irq_2_iommu;
struct irte irte_entry; /* IRTE 格式值 */
union {
struct msi_msg msi_entry; /* MSI 消息 */
};
};
3. IRTE 格式¶
标准的 IRTE 128-bit 格式(VT-d spec 3.0, section 9.10):
Bit 名称 说明
─────────────────────────────────────────────────────────
127 IRTA_E 扩展中断使能 (判断 EIM 模式)
82 Posted Mode 1=Posted Interrupt, 0=Remapped
79:72 Vector 中断向量 (0-255)
63:56 DST (xAPIC) Destination ID, 低 8 位
48:40 DST (x2APIC) 扩展 Destination ID 高 8 位
31:16 DST (x2APIC) 扩展 Destination ID 低 16 位 → 总计 32 位
18 IM IRTE 索引模式
17:16 AVL Available / 软件可用
15 TM Trigger Mode (0=edge, 1=level)
12 RH 重定向提示 (Redirect Hint)
11 DM Delivery Mode (0=Fixed, 1=LowPri, ...)
8 FPD 故障处理禁用
4:0 DST (xAPIC) 高 5 位 (xAPIC 模式组合后总计 8 位)
x2APIC vs xAPIC 模式¶
// drivers/iommu/intel/irq_remapping.c:60-61
#define IR_X2APIC_MODE(mode) (mode ? (1 << 11) : 0)
#define IRTE_DEST(dest) ((eim_mode) ? dest : dest << 8)
xAPIC 模式 (eim_mode=0): - Destination ID 8-bit → 最多 256 CPUs - DST 字段分散在两个位置组合
x2APIC 模式 (eim_mode=1):
- Destination ID 32-bit → 支持大规模多处理器
- 需要 ecap_eim_support (iommu.h:214) 为 1
4. IRTE 分配与管理¶
4.1 分配 IRTE¶
// drivers/iommu/intel/irq_remapping.c:104-145 — alloc_irte()
static int alloc_irte(struct intel_iommu *iommu,
struct irq_2_iommu *irq_iommu, u16 count)
{
struct ir_table *table = iommu->ir_table;
unsigned int mask = 0;
unsigned long flags;
int index;
if (count > 1) {
count = __roundup_pow_of_two(count);
mask = ilog2(count); // 多条目: 2^mask 对齐
}
if (mask > ecap_max_handle_mask(iommu->ecap))
return -1;
raw_spin_lock_irqsave(&irq_2_ir_lock, flags);
index = bitmap_find_free_region(table->bitmap,
INTR_REMAP_TABLE_ENTRIES, mask);
raw_spin_unlock_irqrestore(&irq_2_ir_lock, flags);
irq_iommu->iommu = iommu;
irq_iommu->irte_index = index;
irq_iommu->sub_handle = 0;
irq_iommu->irte_mask = mask;
...
return index;
}
4.2 锁序¶
// drivers/iommu/intel/irq_remapping.c:67-78
/*
* Lock ordering:
* ->dmar_global_lock
* ->irq_2_ir_lock
* ->qi->q_lock
* ->iommu->register_lock
* Note:
* intel_irq_remap_ops.{supported,prepare,enable,disable,reenable} are called
* in single-threaded environment with interrupt disabled, so no need to take
* the dmar_global_lock.
*/
DEFINE_RAW_SPINLOCK(irq_2_ir_lock);
dmar_global_lock (rwsem)
│
├── irq_2_ir_lock (raw_spinlock) ← 保护 IRTE 位图分配
│ └── qi->q_lock (raw_spinlock) ← 保护 QI 提交
│
└── iommu->register_lock (raw_spinlock) ← 保护寄存器操作
5. 中断重映射的启停¶
5.1 准备阶段¶
// drivers/iommu/intel/irq_remapping.c:700-766 — intel_prepare_irq_remapping()
static int __init intel_prepare_irq_remapping(void)
{
// 1. 检查硬件 erratum
if (irq_remap_broken) { ... }
// 2. 确保 DMAR 表存在
if (dmar_table_init() < 0) return -ENODEV;
// 3. 检查 DMAR 中断重映射支持
if (!dmar_ir_support()) return -ENODEV;
// 4. 解析 IOAPIC scope
if (parse_ioapics_under_ir()) { ... }
// 5. 验证所有 IOMMU 都支持 IR
for_each_iommu(iommu, drhd)
if (!ecap_ir_support(iommu->ecap)) goto error;
// 6. 检测 x2APIC 模式
if (x2apic_supported()) {
eim = !dmar_x2apic_optout();
if (!eim) pr_info("x2apic disabled by BIOS\n");
}
for_each_iommu(iommu, drhd) {
if (eim && !ecap_eim_support(iommu->ecap)) {
eim = 0; // 降级到 xAPIC
}
}
eim_mode = eim;
// 7. 为每个 IOMMU 设置中断重映射
for_each_iommu(iommu, drhd) {
if (intel_setup_irq_remapping(iommu))
goto error;
}
return 0;
}
5.2 启用阶段¶
// drivers/iommu/intel/irq_remapping.c:797-826 — intel_enable_irq_remapping()
static int __init intel_enable_irq_remapping(void)
{
for_each_iommu(iommu, drhd) {
if (!ir_pre_enabled(iommu))
iommu_enable_irq_remapping(iommu); // 写 GCMD 寄存器
setup = true;
}
irq_remapping_enabled = 1;
set_irq_posting_cap();
pr_info("Enabled IRQ remapping in %s mode\n",
eim_mode ? "x2apic" : "xapic");
return eim_mode ? IRQ_REMAP_X2APIC_MODE : IRQ_REMAP_XAPIC_MODE;
}
5.3 Posted Interrupt¶
Posted Interrupt 是 x2APIC 模式下的高级特性,用于虚拟化场景:
// drivers/iommu/intel/irq_remapping.c:771-795 — set_irq_posting_cap()
static inline void set_irq_posting_cap(void)
{
if (!disable_irq_post) {
// PI 需要 CPU cmpxchg16b 支持 (跨 128-bit 边界的原子更新)
if (boot_cpu_has(X86_FEATURE_CX16))
intel_irq_remap_ops.capability |= 1 << IRQ_POSTING_CAP;
// 所有 IOMMU 都必须支持 cap_pi_support
for_each_iommu(iommu, drhd)
if (!cap_pi_support(iommu->cap)) {
intel_irq_remap_ops.capability &=
~(1 << IRQ_POSTING_CAP);
break;
}
}
}
Posted Interrupt 允许 IOMMU 直接将中断投递到虚拟 CPU 的 vAPIC,绕过 VMM,显著降低虚拟化中断延迟。
6. 中断重映射硬件交互图¶
设备 MSI/MSI-X 配置
│
│ pci_alloc_irq_vectors() / request_irq()
│
├── 软件设置:
│ ├── alloc_irte(iommu, irq_iommu, count) ← 分配 IRTE
│ ├── 填充 IRTE 字段 (DST, Vector, DM, TM)
│ ├── 计算 MSI Address = 0xFEEXXXXX | IRTE_INDEX
│ ├── 计算 MSI Data
│ └── pci_write_config_dword(dev, MSI_ADDR, addr)
│ pci_write_config_dword(dev, MSI_DATA, data)
│
├── 硬件路径 (设备发起 MSI):
│ │
│ │ PCIe TLP: MWr { Addr=0xFEE00000 | IRTE_INDEX, Data }
│ │ │
│ │ ▼
│ │ IOMMU 拦截:
│ │ ├── 检查 Addr[19:5] 是否在中断地址范围 (0xFEEX_XXXX)
│ │ ├── 读取 IRTE[index] → DST, Vector, DM, TM, Posted
│ │ ├── 权限检查: req_id 匹配
│ │ ├── Posted Mode?
│ │ │ ├── 是 → 写入 vAPIC page (虚拟化直通)
│ │ │ └── 否 → 构造 xAPIC/x2APIC 消息 → 系统总线 → LAPIC
│ │ └── IQA invalidation (如果在 IRTE 修改后)
│ │
│ └── CPU LAPIC → IRR → ISR → EOI
│
└── 软件释放:
├── free_irq()
└── bitmap_release_region(ir_table->bitmap, irte_index, mask)
第二部分:PASID¶
7. PASID 概念¶
PASID(Process Address Space ID)是一个 20-bit 的标识符,允许单个 PCIe 设备同时使用多个 I/O 页表。这样,不同进程可以拥有独立的 IOVA 空间。
// drivers/iommu/intel/pasid.h:13-19
#define PASID_MAX 0x100000 // 1,048,576 (2^20)
#define MAX_NR_PASID_BITS 20
#define PASID_PTE_MASK 0x3F
#define PASID_PTE_PRESENT 1
#define PASID_PTE_FPD 2 // Fault Processing Disable
#define PDE_PFN_MASK PAGE_MASK
#define PASID_PDE_SHIFT 6 // 每个目录项指向 64 条 PASID
#define PASID_TBL_ENTRIES BIT(PASID_PDE_SHIFT)
全局 PASID 限制¶
// drivers/iommu/intel/pasid.c:28
u32 intel_pasid_max_id = PASID_MAX; // = 0x100000
// 每个 IOMMU 初始化时可以限缩:
// iommu.c:1631-1632
intel_pasid_max_id = min_t(u32, temp, intel_pasid_max_id);
8. PASID 译类型¶
// drivers/iommu/intel/pasid.h:43-46
#define PASID_ENTRY_PGTT_FL_ONLY (1) /* First-Level only — SVA */
#define PASID_ENTRY_PGTT_SL_ONLY (2) /* Second-Level only — IOVA */
#define PASID_ENTRY_PGTT_NESTED (3) /* Nested — FL on SL (VM) */
#define PASID_ENTRY_PGTT_PT (4) /* Pass-Through — identity mapping */
| 类型 | 用途 | 页表格式 |
|---|---|---|
| FL_ONLY | Shared Virtual Addressing (SVA) | First-level x86-64 page table |
| SL_ONLY | Per-PASID IOVA mapping | Second-level VT-d page table |
| NESTED | VM: guest VA → guest PA → host PA | First-level nested on second-level |
| PT | Pass-through (identity) | 无转换 |
9. PASID 表结构¶
9.1 表层次结构¶
PASID Table Hierarchy (3-level)
│
│ 设备 → Context Entry (scalable mode)
│ │
│ └── PASID Directory Table (PDE)
│ │ pasid_dir_entry[512] (9-bit dir index)
│ │
│ └── PASID Table Entry Group (每 512 条为一个 table)
│ pasid_entry[PASID % 512]
│ └── pasid_entry.val[8] ← 64 字节 per entry
│
└── PASID 地址:
| Dir Index (9 bits) | Table Index (6 bits) | Reserved |
| PASID[19:11] | PASID[10:6] | PASID[5:0]|
9.2 关键数据结构¶
// drivers/iommu/intel/pasid.h:35-52
struct pasid_dir_entry {
u64 val; // 指向一个 PASID Table Entry Group
};
struct pasid_entry {
u64 val[8]; // 64 字节 PASID entry (8 × u64)
};
struct pasid_table {
void *table; // PASID directory table 虚拟地址
u32 max_pasid; // 最大 PASID 值
};
9.3 访问宏¶
// pasid.h:55-58 — 检查 Directory Entry Present
static inline bool pasid_pde_is_present(struct pasid_dir_entry *pde)
{
return READ_ONCE(pde->val) & PASID_PTE_PRESENT;
}
// pasid.h:61-68 — 从 PDE 获取 PASID Table
static inline struct pasid_entry *
get_pasid_table_from_pde(struct pasid_dir_entry *pde)
{
if (!pasid_pde_is_present(pde))
return NULL;
return phys_to_virt(READ_ONCE(pde->val) & PDE_PFN_MASK);
}
// pasid.h:71-74 — 检查 PASID Entry Present
static inline bool pasid_pte_is_present(struct pasid_entry *pte)
{
return READ_ONCE(pte->val[0]) & PASID_PTE_PRESENT;
}
10. PASID 表分配¶
// drivers/iommu/intel/pasid.c:38-78 — intel_pasid_alloc_table()
int intel_pasid_alloc_table(struct device *dev)
{
struct device_domain_info *info;
struct pasid_table *pasid_table;
struct pasid_dir_entry *dir;
u32 max_pasid = 0;
int order, size;
info = dev_iommu_priv_get(dev);
pasid_table = kzalloc_obj(*pasid_table);
// 确定 PASID 表大小
if (info->pasid_supported)
max_pasid = min_t(u32, pci_max_pasids(to_pci_dev(dev)),
intel_pasid_max_id);
// 每个目录项覆盖 64 个 PASID (PASID_PDE_SHIFT=6)
// 每个目录项 8 字节 → size = (max_pasid >> 6) × 8
size = max_pasid >> (PASID_PDE_SHIFT - 3);
order = size ? get_order(size) : 0;
// 从 IOMMU 所在 NUMA 节点分配
dir = iommu_alloc_pages_node_sz(info->iommu->node, GFP_KERNEL,
1 << (order + PAGE_SHIFT));
pasid_table->table = dir;
pasid_table->max_pasid = 1 << (order + PAGE_SHIFT + 3);
info->pasid_table = pasid_table;
// 非 coherent 架构需要 cache flush
if (!ecap_coherent(info->iommu->ecap))
clflush_cache_range(pasid_table->table, (1 << order) * PAGE_SIZE);
return 0;
}
11. PASID Entry 配置¶
11.1 First-Level Setup¶
// drivers/iommu/intel/pasid.c:381-418 — intel_pasid_setup_first_level()
int intel_pasid_setup_first_level(struct intel_iommu *iommu, struct device *dev,
phys_addr_t fsptptr, u32 pasid, u16 did,
int flags)
{
struct pasid_entry *pte;
// 检查硬件能力
if (!ecap_flts(iommu->ecap))
return -EINVAL;
if ((flags & PASID_FLAG_FL5LP) && !cap_fl5lp_support(iommu->cap))
return -EINVAL;
spin_lock(&iommu->lock);
pte = intel_pasid_get_entry(dev, pasid); // 获取或分配 PASID entry
if (pasid_pte_is_present(pte)) {
spin_unlock(&iommu->lock);
return -EBUSY;
}
pasid_pte_config_first_level(iommu, pte, fsptptr, did, flags);
spin_unlock(&iommu->lock);
pasid_flush_caches(iommu, pte, pasid, did); // IOTLB cache flush
return 0;
}
11.2 Second-Level Setup¶
// drivers/iommu/intel/pasid.c:423-444 — pasid_pte_config_second_level()
static void pasid_pte_config_second_level(struct intel_iommu *iommu,
struct pasid_entry *pte,
struct dmar_domain *domain, u16 did)
{
pasid_clear_entry(pte);
pasid_set_domain_id(pte, did);
pasid_set_slptr(pte, pt_info.ssptptr); // 第二级页表指针
pasid_set_address_width(pte, pt_info.aw);
pasid_set_translation_type(pte, PASID_ENTRY_PGTT_SL_ONLY);
pasid_set_fault_enable(pte);
pasid_set_page_snoop(pte, ...); // Snoop 控制
if (domain->dirty_tracking)
pasid_set_ssade(pte); // Dirty tracking
pasid_set_present(pte);
}
11.3 Pass-Through Setup¶
// drivers/iommu/intel/pasid.c:572-596 — intel_pasid_setup_pass_through()
int intel_pasid_setup_pass_through(struct intel_iommu *iommu,
struct device *dev, u32 pasid)
{
u16 did = FLPT_DEFAULT_DID; // Domain ID = 1
spin_lock(&iommu->lock);
pte = intel_pasid_get_entry(dev, pasid);
if (pasid_pte_is_present(pte))
return -EBUSY;
pasid_pte_config_pass_through(iommu, pte, did);
spin_unlock(&iommu->lock);
pasid_flush_caches(iommu, pte, pasid, did);
return 0;
}
11.4 Nested Setup¶
// drivers/iommu/intel/pasid.c:621-654 — pasid_pte_config_nestd()
static void pasid_pte_config_nestd(struct intel_iommu *iommu,
struct pasid_entry *pte,
struct iommu_hwpt_vtd_s1 *s1_cfg,
struct dmar_domain *s2_domain,
u16 did)
{
pasid_clear_entry(pte);
if (s1_cfg->addr_width == ADDR_WIDTH_5LEVEL)
pasid_set_flpm(pte, 1); // 5-level first-level
pasid_set_flptr(pte, s1_cfg->pgtbl_addr); // S1 页表地址
if (s1_cfg->flags & IOMMU_VTD_S1_SRE) {
pasid_set_sre(pte); // Supervisor Request Enable
if (s1_cfg->flags & IOMMU_VTD_S1_WPE)
pasid_set_wpe(pte); // Write Protect Enable
}
if (s1_cfg->flags & IOMMU_VTD_S1_EAFE)
pasid_set_eafe(pte); // Extended Access Flag Enable
...
}
12. 综合架构图¶
IOMMU Hardware
│
├── GCMD_REG (0x18)
│ ├── TE (bit 31) — DMA Translation Enable
│ ├── IRE (bit 25) — Interrupt Remapping Enable
│ └── QIE (bit 26) — Queued Invalidation Enable
│
├── RTADDR_REG (0x20) → Root Table → Context Table → DMA PT
│
├── IRTA_REG (0xB8) → IRTE Table (65536 entries)
│ ├── IRTE[0]: {DST, Vector, Delivery Mode, Posted?}
│ ├── IRTE[1]: ...
│ └── IRTE[N]: ...
│
├── QI (Queued Invalidation)
│ ├── IQH_REG (0x80) — Head pointer
│ ├── IQT_REG (0x88) — Tail pointer
│ └── IQA_REG (0x90) — Queue address
│
├── PRQ (Page Request Queue, PRI)
│ ├── PQH_REG (0xC0) — Head
│ ├── PQT_REG (0xC8) — Tail
│ └── PQA_REG (0xD0) — Address
│
├── Fault Registers
│ ├── FSTS_REG (0x34) — Fault Status
│ └── FRCD (0x40+) — Fault Recording Registers
│
└── PerfMon (VT-d 4.0+)
└── PERFCAP_REG (0x300) → Counters
13. PASID 与中断重映射的交互¶
在虚拟化场景中,PASID 和中断重映射协同工作:
VM Guest
│
├── vCPU (PASID-aware)
│ │
│ ├── GPU 计算 (PASID=101): 第一级页表 (guest VA → guest PA)
│ │ └── 嵌套在 S2 (guest PA → host PA)
│ │
│ └── NVMe 中断 (IRTE[x]):
│ ├── Posted Interrupt → 直接投递到 vCPU's vAPIC page
│ └── 无需 VM-Exit
│
└── IOMMUFD (用户态控制)
├── IOAS → IOVA 空间
├── HWPT_PAGING / HWPT_NESTED → 硬件页表
└── VIOMMU → 安全设备分配
14. 关键行号速查¶
| 符号 | 文件 | 行号 | 说明 |
|---|---|---|---|
struct irq_2_iommu |
irq_remapping.c | 43 | IRQ→IOMMU 映射 |
struct intel_ir_data |
irq_remapping.c | 52 | per-IRQ 数据 |
DEFINE_RAW_SPINLOCK(irq_2_ir_lock) |
irq_remapping.c | 78 | IRTE 分配锁 |
alloc_irte |
irq_remapping.c | 104 | IRTE 分配 |
intel_prepare_irq_remapping |
irq_remapping.c | 700 | IR 准备 |
intel_enable_irq_remapping |
irq_remapping.c | 797 | IR 启用 |
set_irq_posting_cap |
irq_remapping.c | 771 | Posted Interrupt |
intel_pasid_max_id |
pasid.c | 28 | 全局 PASID 限制 |
intel_pasid_alloc_table |
pasid.c | 38 | PASID 表分配 |
intel_pasid_free_table |
pasid.c | 80 | PASID 表释放 |
intel_pasid_setup_first_level |
pasid.c | 381 | FL 配置 |
intel_pasid_setup_second_level |
pasid.c | 446 | SL 配置 |
intel_pasid_setup_pass_through |
pasid.c | 572 | PT 配置 |
intel_pasid_setup_nested |
pasid.c | 675 | Nested 配置 |
pasid_pte_config_nestd |
pasid.c | 621 | Nested PTE 填充 |
pasid_pte_config_second_level |
pasid.c | 423 | SL PTE 填充 |
struct pasid_entry |
pasid.h | 39 | PASID entry (val[8]) |
struct pasid_table |
pasid.h | 49 | PASID 表 |
PASID_MAX |
pasid.h | 13 | 最大 PASID (0x100000) |
PASID_ENTRY_PGTT_FL_ONLY |
pasid.h | 43 | FL 类型常量 |
PASID_ENTRY_PGTT_SL_ONLY |
pasid.h | 44 | SL 类型常量 |
PASID_ENTRY_PGTT_NESTED |
pasid.h | 45 | Nested 类型常量 |
PASID_ENTRY_PGTT_PT |
pasid.h | 46 | PT 类型常量 |
下一篇文章¶
第五篇:SVA、嵌套翻译与 IOMMUFD — VT-d 的未来