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 物理地址到內核虛擬地址映射實戰:以 AT91SAM9261 LCD 驅動為例

編輯:Linux 教學

嵌入式 Linux 驅動開發中,將外設寄存器物理地址映射為內核虛擬地址是訪問硬件的前提。本文以 AT91SAM9261EK 開發板 LCD 控制器驅動(atmel_lcdfb.c)為例,完整追蹤 platform_driver 匹配、platform_get_resource 獲取資源、ioremap 建立映射的全流程。

一、 驅動註冊與設備匹配

1. 平台驅動定義

static struct platform_driver atmel_lcdfb_driver = {
    .remove  = __exit_p(atmel_lcdfb_remove),
    .driver  = {
        .name   = "atmel_lcdfb",
        .owner  = THIS_MODULE,
    },
};

2. 平台設備定義(機器文件 at91sam9261_devices.c)

static struct platform_device at91_lcdc_device = {
    .name   = "atmel_lcdfb",        // 必須與驅動 .name 完全一致
    .id     = 0,
    .dev    = {
        .dma_mask            = &lcdc_dmamask,
        .coherent_dma_mask   = 0xffffffff,
        .platform_data       = &lcdc_data,   // 面板參數等
    },
    .resource     = lcdc_resources,
    .num_resources = ARRAY_SIZE(lcdc_resources),
};

3. 資源描述符 lcdc_resources

static struct resource lcdc_resources[] = {
    [0] = {                          // 寄存器物理地址區
        .start = AT91SAM9261_LCDC_BASE,        // 0x00600000
        .end   = AT91SAM9261_LCDC_BASE + SZ_4K - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {                          // 中斷號
        .start = AT91SAM9261_ID_LCDC,
        .end   = AT91SAM9261_ID_LCDC,
        .flags = IORESOURCE_IRQ,
    },
#if defined(CONFIG_FB_INTSRAM)
    [2] = {                          // 內部 SRAM 作為幀緩衝
        .start = AT91SAM9261_SRAM_BASE,
        .end   = AT91SAM9261_SRAM_BASE + AT91SAM9261_SRAM_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
#endif
};

機器初始化調用 at91_add_device_lcdc() → platform_device_register() 將設備掛載到平台總線。

二、 Probe 階段獲取資源並映射

static int atmel_lcdfb_probe(struct platform_device *pdev) {
    struct fb_info *info;
    struct resource *regs;

    // 1. 獲取 MEM 類型資源(索引 0 對應寄存器區)
    regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!regs) return -ENXIO;

    // 2. 分配 framebuffer info 結構
    info = framebuffer_alloc(sizeof(struct atmel_lcdfb_info), &pdev->dev);
    if (!info) return -ENOMEM;

    // 3. 記錄物理地址範圍供 ioremap 使用
    info->fix.mmio_start = regs->start;                    // 0x00600000
    info->fix.mmio_len   = regs->end - regs->start + 1;    // 4KB

    // 4. 核心映射調用
    info->par->mmio = ioremap(info->fix.mmio_start, info->fix.mmio_len);
    if (!info->par->mmio) {
        framebuffer_release(info);
        return -ENOMEM;
    }

    // 後續:初始化硬件、註冊 fb、設置中斷...
    return 0;
}

三、 ioremap 核心原理

  • 功能:在內核虛擬地址空間(vmalloc 區)分配一段連續虛擬頁,建立頁表映射到指定物理地址,屬性為 Strongly-ordered / Device Memory(無緩存、無投機訪問、保序)。
  • 原型:void __iomem *ioremap(resource_size_t phys_addr, unsigned long size);
  • 配對釋放:iounmap(void __iomem *addr);(驅動 remove 或失敗路徑調用)。
  • 讀寫訪問:必須使用 readl/writel、readw/writew、readb/writeb 等 I/O 訪問函數,而非直接指針解引用。

四、 關鍵數據結構關聯圖

platform_device (at91_lcdc_device)
    └── .resource[0] = { start=0x600000, end=0x600FFF, flags=IORESOURCE_MEM }
           ↓ platform_get_resource()
    struct resource *regs
           ↓
    fb_info->fix.mmio_start = regs->start
    fb_info->fix.mmio_len   = resource_size(regs)
           ↓
    void __iomem *mmio = ioremap(mmio_start, mmio_len)
           ↓
    atmel_lcdfb_info->mmio = mmio   // 驅動私有數據保存映射基址
           ↓
    writel(value, mmio + REG_OFFSET)  // 訪問寄存器

五、 常見錯誤與調試

六、 現代內核簡化寫法

Device Tree + devm_ioremap_resource() 自動管理生命周期:

struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
void __iomem *base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base)) return PTR_ERR(base);
// 無需手動 iounmap,設備釋放自動調用
Copyright © Windows教程網 All Rights Reserved
現象原因排查
ioremap 返回 NULL物理地址衝突、超出內核映射區、size=0查看 /proc/iomem 確認區域未被佔用
訪問寄存器死機/數據錯誤未用 ioremap、指針算術錯誤、端對齊檢查 mmio + offset 類型為 void __iomem *
卸載驅動後重載失敗未調用 iounmap 導致虛擬地址洩漏remove 路徑必須 iounmap