嵌入式 Linux 驅動開發中,將外設寄存器物理地址映射為內核虛擬地址是訪問硬件的前提。本文以 AT91SAM9261EK 開發板 LCD 控制器驅動(atmel_lcdfb.c)為例,完整追蹤 platform_driver 匹配、platform_get_resource 獲取資源、ioremap 建立映射的全流程。
static struct platform_driver atmel_lcdfb_driver = {
.remove = __exit_p(atmel_lcdfb_remove),
.driver = {
.name = "atmel_lcdfb",
.owner = THIS_MODULE,
},
};
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),
};
lcdc_resourcesstatic 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() 將設備掛載到平台總線。
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;
}
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) // 訪問寄存器
| 現象 | 原因 | 排查 |
|---|---|---|
| ioremap 返回 NULL | 物理地址衝突、超出內核映射區、size=0 | 查看 /proc/iomem 確認區域未被佔用 |
| 訪問寄存器死機/數據錯誤 | 未用 ioremap、指針算術錯誤、端對齊 | 檢查 mmio + offset 類型為 void __iomem * |
| 卸載驅動後重載失敗 | 未調用 iounmap 導致虛擬地址洩漏 | remove 路徑必須 iounmap |