Linux kernel IOMMU代码分析笔记(2)

系统BIOS会检查平台的remapping硬件功能,并且会在主机的系统地址空间内定位memory-mapped remapping硬件寄存器。BIOS通过DMA Remapping Reporting (DMAR) ACPI table向系统软件报告remapping硬件单元信息。

除了RSDPFACS,所有的ACPI table定义都包含一个共同的header定义:

struct acpi_table_header {
    char signature[ACPI_NAME_SIZE]; /* ASCII table signature */
    u32 length;     /* Length of table in bytes, including this header */
    u8 revision;        /* ACPI Specification minor version number */
    u8 checksum;        /* To make sum of entire table == 0 */
    char oem_id[ACPI_OEM_ID_SIZE];  /* ASCII OEM identification */
    char oem_table_id[ACPI_OEM_TABLE_ID_SIZE];  /* ASCII OEM table identification */
    u32 oem_revision;   /* OEM revision number */
    char asl_compiler_id[ACPI_NAME_SIZE];   /* ASCII ASL compiler vendor ID */
    u32 asl_compiler_revision;  /* ASL compiler version */
};

DMA Remapping table定义如下(可以看到包含有acpi_table_header):

struct acpi_table_dmar {
    struct acpi_table_header header;    /* Common ACPI table header */
    u8 width;       /* Host Address Width */
    u8 flags;
    u8 reserved[10];
};

对所有的DMA Remapping结构体,都会包含一个type和一个length

/* DMAR subtable header */

struct acpi_dmar_header {
    u16 type;
    u16 length;
};

DMA Remapping Hardware单元(类型为0)为例:

/* 0: Hardware Unit Definition */

struct acpi_dmar_hardware_unit {
    struct acpi_dmar_header header;
    u8 flags;
    u8 reserved;
    u16 segment;
    u64 address;        /* Register Base Address */
};

其它的还有acpi_dmar_reserved_memoryacpi_dmar_atsr等等定义。

Linux kernel IOMMU代码分析笔记(1)

Linux kernel代码版本是3.10

intel-iommu.h头文件定义了root-entry table address寄存器:

#define DMAR_RTADDR_REG 0x20    /* Root entry table */

DMAR_RTADDR_REG只在iommu_set_root_entry这个函数中使用(这个函数作用是更新root-entry table的地址):

static void iommu_set_root_entry(struct intel_iommu *iommu)
{
    void *addr;
    u32 sts;
    unsigned long flag;

    addr = iommu->root_entry;

    raw_spin_lock_irqsave(&iommu->register_lock, flag);
    dmar_writeq(iommu->reg + DMAR_RTADDR_REG, virt_to_phys(addr));

    writel(iommu->gcmd | DMA_GCMD_SRTP, iommu->reg + DMAR_GCMD_REG);

    /* Make sure hardware complete it */
    IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG,
              readl, (sts & DMA_GSTS_RTPS), sts);

    raw_spin_unlock_irqrestore(&iommu->register_lock, flag);
}

DMAR_RTADDR_REG存储的是root-entry table的物理地址(virt_to_phys()函数把virtual address转成physical address)。整个root-entry table4KiB大小,并且要求起始地址是“页面对齐的(page-alignedpage长度是4KiB)”,所以DMAR_RTADDR_REG12 bits0。更新DMAR_RTADDR_REG代码如下:

dmar_writeq(iommu->reg + DMAR_RTADDR_REG, virt_to_phys(addr));

更新完DMAR_RTADDR_REG寄存器,还要把global command寄存器的SRTPSet Root Table Pointer)位置1

writel(iommu->gcmd | DMA_GCMD_SRTP, iommu->reg + DMAR_GCMD_REG);

最后读取Global Status寄存器的RTPSRoot Table Pointer Status)位,确认更新成功:

IOMMU_WAIT_OP(iommu, DMAR_GSTS_REG,
                  readl, (sts & DMA_GSTS_RTPS), sts);