Linux 核心訪問外設寄存器(如 SRAM、控制器寄存器)必須先將物理地址映射到核心虛擬地址空間。本文詳細對比動態映射 ioremap 與靜態映射 map_desc 兩大機制的原理、調用流程、適用場景與實現細節。
外設 I/O 資源物理地址(如 0x55000000)不在核心線性映射區(PAGE_OFFSET 以上),核心無法直接通過指針解引用訪問。必須建立「物理地址 → 核心虛擬地址」的頁表映射,才能使用 readl/writel 等訪問函數。
// asm/io.h
#define ioremap(cookie, size) __ioremap(cookie, size, 0)
// arm/mm/ioremap.c
void __iomem *__ioremap(unsigned long phys_addr, size_t size, unsigned long flags);
// 返回:映射後的核心虛擬地址(3GB-4GB 區域)
// 物理地址定義
#define S3C2410_PA_IIS (0x55000000)
// 驅動 probe 中映射
our_card->regs = ioremap(S3C2410_PA_IIS, 0x100);
if (!our_card->regs) { err = -ENXIO; goto exit_err; }
// 後續訪問
val = readl(our_card->regs + offset);
writel(value, our_card->regs + offset);
// 驅動 remove 中解映射
iounmap(our_card->regs);
| 優點 | 缺點 |
|---|---|
| 使用簡單,運行時動態決定 | 運行時分配頁表項,有微小性能開銷 |
| 模組化驅動友好,支援熱插拔 | 映射地址不固定,調試時需查看運行時值 |
| 適用於大多數平台驅動 | 需驅動代碼顯式調用 |
核心在啟動早期(start_kernel → setup_arch → paging_init → devicemaps_init)調用 machine_desc->map_io,由平台代碼定義靜態映射表,建立「物理頁框號 → 虛擬地址」的一對一線性映射。
// include/asm-arm/mach/map.h
struct map_desc {
unsigned long virtual; // 映射後虛擬地址
unsigned long pfn; // 物理頁框號 (phys_addr >> PAGE_SHIFT)
unsigned long length; // 映射長度
unsigned int type; // 內存類型(MT_DEVICE 等)
};
// arch/arm/mach-s3c2410/mach-smdk2410.c
MACHINE_START(SMDK2410, "SMDK2410")
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100,
.map_io = smdk2410_map_io, // 關鍵掛鉤
.init_irq = s3c24xx_init_irq,
.init_machine = smdk2410_init,
.timer = &s3c24xx_timer,
MACHINE_END
// 調用鏈:smdk2410_map_io → s3c24xx_init_io → s3c2410_map_io
void __init s3c2410_map_io(struct map_desc *mach_desc, int mach_size)
{
iotable_init(s3c2410_iodesc, ARRAY_SIZE(s3c2410_iodesc));
// ... 其他 iodesc
}
// arch/arm/mach-s3c2410/s3c2410.c
static struct map_desc s3c2410_iodesc[] __initdata = {
IODESC_ENT(USBHOST),
IODESC_ENT(CLKPWR),
IODESC_ENT(LCD),
IODESC_ENT(TIMER),
// ...
};
// IODESC_ENT 宏展開
#define IODESC_ENT(x) { \
(unsigned long)S3C24XX_VA_##x, \
__phys_to_pfn(S3C24XX_PA_##x), \
S3C24XX_SZ_##x, \
MT_DEVICE \
}
// include/asm-arm/arch-s3c2410/map.h
#define S3C24XX_VA_LCD S3C2410_ADDR(0x00600000) // 虛擬地址
#define S3C2410_PA_LCD (0x4D000000) // 物理地址
#define S3C24XX_SZ_LCD SZ_1M // 大小
#define S3C2410_ADDR(x) ((void __iomem *)0xF0000000 + (x)) // 線性偏移
// 驅動無需 ioremap,直接使用預定義虛擬地址宏
lcdcon1 = readl(S3C2410_LCDCON1); // S3C2410_LCDCON1 = S3C24XX_VA_LCD + 0x00
writel(val, S3C2410_LCDCON1);
| 優點 | 缺點 |
|---|---|
| 零運行時開銷,啟動時一次性建立頁表 | 需平台移植時修改核心啟動代碼 |
| 虛擬地址固定,便於調試與跨驅動共享 | 佔用核心虛擬地址空間(通常 0xF0000000+) |
| 適合 SoC 片上外設固定地址場景 | 不支援熱插拔、動態地址分配設備 |
| 維度 | ioremap 動態映射 | map_desc 靜態映射 |
|---|---|---|
| 調用時機 | 驅動 probe 運行時 | 核心啟動早期 map_io |
| 地址確定性 | 運行時動態分配 | 編譯時/啟動時固定 |
| 適用設備 | PCI、USB、熱插拔、動態地址 | SoC 片上外設、固定物理地址 |
| 驅動代碼複雜度 | 需調用 ioremap/iounmap | 直接使用預定義宏 |
| 頁表開銷 | 運行時分配 PTE | 啟動時預建立 |
| 跨平台可移植性 | 高(標準 API) | 低(平台強相關) |
// 通用 ARM 平台物理轉虛擬宏
#define IO_ADDRESS(x) (((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000)
// 用於平台統一的線性偏移計算
動態 ioremap 是通用驅動開發的標準方式,靈活支援熱插拔與跨平台;靜態 map_desc 是 SoC 平台移植的核心機制,通過啟動階段建立固定線性映射,實現零開銷訪問。理解兩者差異與適用場景,是嵌入式 Linux 驅動開發與平台移植的關鍵技能。