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

Linux 核心外設 I/O 內存映射機制:動態 ioremap 與靜態 map_desc 完整對比

編輯:Linux 教學

Linux 核心訪問外設寄存器(如 SRAM、控制器寄存器)必須先將物理地址映射到核心虛擬地址空間。本文詳細對比動態映射 ioremap 與靜態映射 map_desc 兩大機制的原理、調用流程、適用場景與實現細節。

核心問題:為什麼需要映射

外設 I/O 資源物理地址(如 0x55000000)不在核心線性映射區(PAGE_OFFSET 以上),核心無法直接通過指針解引用訪問。必須建立「物理地址 → 核心虛擬地址」的頁表映射,才能使用 readl/writel 等訪問函數。

方式一:動態映射 ioremap

API 定義

// 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 區域)

使用範例:S3C2410 IIS 音頻驅動


// 物理地址定義
#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);

優缺點

優點缺點
使用簡單,運行時動態決定運行時分配頁表項,有微小性能開銷
模組化驅動友好,支援熱插拔映射地址不固定,調試時需查看運行時值
適用於大多數平台驅動需驅動代碼顯式調用

方式二:靜態映射 map_desc

核心機制:啟動階段建立線性映射表

核心在啟動早期(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 等)
};

平台實現範例:S3C2410

1. 定義機器描述符


// 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

2. 實現 map_io 函數


// 調用鏈: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
}

3. 定義靜態映射表


// 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 \
}

4. 地址定義


// 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)低(平台強相關)

進階:IO_ADDRESS 通用宏


// 通用 ARM 平台物理轉虛擬宏
#define IO_ADDRESS(x) (((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000)
// 用於平台統一的線性偏移計算

總結

動態 ioremap 是通用驅動開發的標準方式,靈活支援熱插拔與跨平台;靜態 map_desc 是 SoC 平台移植的核心機制,通過啟動階段建立固定線性映射,實現零開銷訪問。理解兩者差異與適用場景,是嵌入式 Linux 驅動開發與平台移植的關鍵技能。

Copyright © Windows教程網 All Rights Reserved