Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

ARM Linux I/O 記憶體靜態映射 (map_desc / iotable_init) 實作詳解

編輯:Linux 教學

在 ARM Linux 移植過程中,需建立外設 I/O 物理位址到虛擬位址的靜態映射,供早期初始化 (如 UART、定時器、中斷控制器) 使用,避免依賴 ioremap()。本文以 S5PV210 平台為例,詳細說明 map_desc 結構、iotable_init、plat/mach 兩級 map_io 鏈條、機器啟動宏中的掛載點。

核心結構體


struct map_desc {
    unsigned long virtual;  // 虛擬位址
    unsigned long pfn;      // __phys_to_pfn(物理位址) = 物理頁框號
    unsigned long length;   // 映射大小 (必須為 4KB 頁大小倍數)
    unsigned int type;      // 映射類型 (MT_DEVICE 等)
};

Plat 級 map_io (arch/arm/plat-s5p/cpu.c)


/* 支援的 CPU 類型表 */
static struct cpu_table cpu_ids[] __initdata = {
    {
        .idcode = 0x43110000,
        .idmask = 0xfffff000,
        .map_io = s5pv210_map_io,   // 指向 mach 級 map_io
        .init_clocks = s5pv210_init_clocks,
        .init_uarts = s5pv210_init_uarts,
        .init = s5pv210_init,
        .name = "S5PV210/S5PC110",
    },
};

/* Plat 級靜態映射表 (通用外設) */
static struct map_desc s5p_iodesc[] __initdata = {
    { .virtual = (unsigned long)S5P_VA_CHIPID,  .pfn = __phys_to_pfn(S5P_PA_CHIPID),  .length = SZ_4K,   .type = MT_DEVICE },
    { .virtual = (unsigned long)S3C_VA_SYS,     .pfn = __phys_to_pfn(S5P_PA_SYSCON),  .length = SZ_64K,  .type = MT_DEVICE },
    { .virtual = (unsigned long)S3C_VA_UART,    .pfn = __phys_to_pfn(S3C_PA_UART),    .length = SZ_4K,   .type = MT_DEVICE },
    { .virtual = (unsigned long)VA_VIC0,        .pfn = __phys_to_pfn(S5P_PA_VIC0),    .length = SZ_16K,  .type = MT_DEVICE },
    { .virtual = (unsigned long)VA_VIC1,        .pfn = __phys_to_pfn(S5P_PA_VIC1),    .length = SZ_16K,  .type = MT_DEVICE },
    { .virtual = (unsigned long)S3C_VA_TIMER,   .pfn = __phys_to_pfn(S5P_PA_TIMER),   .length = SZ_16K,  .type = MT_DEVICE },
    { .virtual = (unsigned long)S5P_VA_GPIO,    .pfn = __phys_to_pfn(S5P_PA_GPIO),    .length = SZ_4K,   .type = MT_DEVICE },
    { .virtual = (unsigned long)S3C_VA_WATCHDOG,.pfn = __phys_to_pfn(S5P_PA_WDT),     .length = SZ_4K,   .type = MT_DEVICE },
    { .virtual = (unsigned long)S3C_VA_OTG,     .pfn = __phys_to_pfn(S5P_PA_OTG),     .length = SZ_1M,   .type = MT_DEVICE },
    { .virtual = (unsigned long)S3C_VA_OTGPHY,  .pfn = __phys_to_pfn(S5P_PA_OTGPHY),  .length = SZ_1M,   .type = MT_DEVICE },
};

/* Plat 級初始化入口 */
void __init s5p_init_io(struct map_desc *mach_desc, int size, void __iomem *cpuid_addr)
{
    iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc));  // 建立 plat 級頁表映射

    idcode = __raw_readl(cpuid_addr);
    s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids)); // 匹配 CPU,最終調用 mach 級 map_io
}

Mach 級 map_io (arch/arm/mach-s5pv210/cpu.c)


/* Mach 級專屬映射表 */
static struct map_desc s5pv210_iodesc[] __initdata = {
    { .virtual = (unsigned long)S5P_VA_SYSTIMER, .pfn = __phys_to_pfn(S5PV210_PA_SYSTIMER), .length = SZ_1M,  .type = MT_DEVICE },
    { .virtual = (unsigned long)VA_VIC2,         .pfn = __phys_to_pfn(S5PV210_PA_VIC2),     .length = SZ_16K, .type = MT_DEVICE },
    { .virtual = (unsigned long)VA_VIC3,         .pfn = __phys_to_pfn(S5PV210_PA_VIC3),     .length = SZ_16K, .type = MT_DEVICE },
    { .virtual = (unsigned long)S5P_VA_SROMC,    .pfn = __phys_to_pfn(S5PV210_PA_SROMC),    .length = SZ_4K,  .type = MT_DEVICE },
    { .virtual = (unsigned long)S5P_VA_AUDSS,    .pfn = __phys_to_pfn(S5PV210_PA_AUDSS),    .length = SZ_1M,  .type = MT_DEVICE },
    { .virtual = (unsigned long)S5P_VA_DMC0,     .pfn = __phys_to_pfn(S5PV210_PA_DMC0),     .length = SZ_4K,  .type = MT_DEVICE },
    { .virtual = (unsigned long)S5P_VA_DMC1,     .pfn = __phys_to_pfn(S5PV210_PA_DMC1),     .length = SZ_4K,  .type = MT_DEVICE },
};

/* Mach 級 map_io 實作 */
void __init s5pv210_map_io(void)
{
    iotable_init(s5pv210_iodesc, ARRAY_SIZE(s5pv210_iodesc));
}

機器啟動掛載 (arch/arm/mach-s5pv210/mach-smdkv210.c)


static void __init smdkv210_map_io(void)
{
    s5p_init_io(NULL, 0, S5P_VA_CHIPID);  // 觸發 plat → mach 映射鏈
    ...
}

MACHINE_START(SMDKV210, "smdkv210")
    .phys_io = S3C_PA_UART & 0xfff00000,
    .io_pg_offst = (((u32)S3C_VA_UART) >> 18) & 0xfffc,
    .boot_params = S5P_PA_SDRAM + 0x100,
    .fixup = s5pv210_fixup,
    .init_irq = s5pv210_init_irq,
    .map_io = smdkv210_map_io,   // 機器級 map_io 掛載點
    .init_machine = smdkv210_machine_init,
    .timer = &s5p_systimer,
MACHINE_END

執行流程總結

  1. 核心啟動 → MACHINE_START 中的 .map_io = smdkv210_map_io 被調用。
  2. smdkv210_map_io 呼叫 s5p_init_io (plat 級)。
  3. s5p_init_io 先執行 iotable_init(s5p_iodesc) 建立通用外設映射。
  4. 讀取 CPU ID,透過 s3c_init_cpu 匹配 cpu_table,調用對應的 .map_io 即 s5pv210_map_io (mach 級)。
  5. s5pv210_map_io 執行 iotable_init(s5pv210_iodesc) 建立晶片專屬映射。

使用方式

映射建立後,驅動程式直接使用虛擬位址 + 偏移量存取暫存器,**無需呼叫 ioremap()**。例如 UART 基址為 S3C_VA_UART,存取 THR 暫存器:__raw_writel(val, S3C_VA_UART + 0x00)。

關於 ioremap() 的補充

靜態映射區域若再呼叫 ioremap() 會建立重複映射,雖然通常不會出錯,但浪費頁表項目。**正確做法:靜態映射區域直接用虛擬位址,動態映射區域才用 ioremap()。**

Copyright © Windows教程網 All Rights Reserved