在 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 等)
};
/* 支援的 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 級專屬映射表 */
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));
}
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
MACHINE_START 中的 .map_io = smdkv210_map_io 被調用。smdkv210_map_io 呼叫 s5p_init_io (plat 級)。s5p_init_io 先執行 iotable_init(s5p_iodesc) 建立通用外設映射。s3c_init_cpu 匹配 cpu_table,調用對應的 .map_io 即 s5pv210_map_io (mach 級)。s5pv210_map_io 執行 iotable_init(s5pv210_iodesc) 建立晶片專屬映射。映射建立後,驅動程式直接使用虛擬位址 + 偏移量存取暫存器,**無需呼叫 ioremap()**。例如 UART 基址為 S3C_VA_UART,存取 THR 暫存器:__raw_writel(val, S3C_VA_UART + 0x00)。
靜態映射區域若再呼叫 ioremap() 會建立重複映射,雖然通常不會出錯,但浪費頁表項目。**正確做法:靜態映射區域直接用虛擬位址,動態映射區域才用 ioremap()。**