驱动开发,为什么需要映射?

核心概念:为什么需要映射?

PCI 设备通过 BAR(基地址寄存器)声明自己需要的内存或 I/O 空间。这些地址是物理地址 ,CPU 无法直接访问。必须先将这些物理地址映射到内核的虚拟地址空间,驱动程序才能通过指针访问硬件寄存器。

pci_iomappci_ioremap_bar 就是完成这个映射工作的函数。


pci_iomap

函数原型

复制代码
#include <linux/pci.h>

void __iomem *pci_iomap(struct pci_dev *dev, int bar, unsigned long maxlen);

参数详解

  1. struct pci_dev *dev

    • 指向要映射的PCI设备的结构体指针。
  2. int bar

    • 指定要映射的BAR的索引(0-5)。例如,bar=0表示映射BAR0。
  3. unsigned long maxlen

    • 要映射的最大长度(字节数)。

    • 如果设为0,会映射整个BAR区域。

    • 如果指定了长度,可以只映射BAR的一部分。

返回值

  • 成功 :返回指向映射区域的 void __iomem * 类型的指针。

  • 失败 :返回 NULL

特点与注意事项

  1. 不检查BAR类型pci_iomap 会映射无论是内存还是I/O空间的BAR。对于I/O端口的BAR,通常不应该使用内存映射方式访问。

  2. 需要手动判断BAR类型:调用者需要自己检查BAR是内存空间还是I/O空间:

    复制代码
    if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) {
        // 这是I/O端口,应该用pci_iomap_range()或in/out指令
    } else {
        // 这是内存空间,可以用pci_iomap
    }
  3. 灵活性 :可以通过 maxlen 参数控制映射的长度。

使用示例

复制代码
struct pci_dev *pdev;
void __iomem *regs;

/* 映射BAR1的全部区域 */
regs = pci_iomap(pdev, 1, 0);
if (!regs) {
    dev_err(&pdev->dev, "Failed to map BAR1\n");
    return -ENOMEM;
}

/* 使用映射的地址访问硬件 */
writel(0x12345678, regs + REG_CONTROL);  // 写入控制寄存器
u32 status = readl(regs + REG_STATUS);   // 读取状态寄存器

pci_ioremap_bar

函数原型

复制代码
#include <linux/pci.h>

void __iomem *pci_ioremap_bar(struct pci_dev *pdev, int bar);

参数详解

  1. struct pci_dev *pdev

    • 指向要映射的PCI设备的结构体指针。
  2. int bar

    • 指定要映射的BAR的索引(0-5)。

返回值

  • 成功 :返回指向映射区域的 void __iomem * 类型的指针。

  • 失败 :返回 NULL

特点与优势

  1. 自动类型检查pci_ioremap_bar自动检查BAR的类型 。如果BAR是I/O端口空间(IORESOURCE_IO),它不会进行映射并返回NULL。

  2. 映射整个BAR :总是映射整个BAR区域,不能指定部分映射。

  3. 更安全:由于自动类型检查,减少了错误映射I/O端口的风险。

  4. 便捷性:接口更简洁,不需要指定长度参数。

使用示例

复制代码
struct pci_dev *pdev;
void __iomem *regs;

/* 映射BAR0 - 自动检查是否为内存空间 */
regs = pci_ioremap_bar(pdev, 0);
if (!regs) {
    // 失败原因可能是:内存不足、BAR是I/O端口、BAR未启用等
    dev_err(&pdev->dev, "Failed to map BAR0\n");
    return -ENOMEM;
}

/* 访问硬件寄存器 */
iowrite32(0x55AA, regs + OFFSET_CONFIG);
u32 data = ioread32(regs + OFFSET_DATA);

两个函数的对比

特性 pci_iomap pci_ioremap_bar
BAR类型检查 ❌ 不检查,可能错误映射I/O空间 ✅ 自动检查,只映射内存空间
映射范围 可指定长度(0=全部) 总是映射整个BAR
安全性 较低,需要手动检查类型 较高,内置安全检查
使用场景 需要部分映射时;明确知道BAR类型时 大多数情况下的首选
接口简洁性 需要三个参数 只需要两个参数

完整驱动代码示例

复制代码
#include <linux/pci.h>
#include <linux/io.h>

struct my_device {
    void __iomem *bar0_mem;
    void __iomem *bar2_mem;
};

static int my_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    struct my_device *dev;
    int ret;
    
    /* 启用PCI设备 */
    ret = pci_enable_device(pdev);
    if (ret) return ret;
    
    /* 申请资源区域 */
    ret = pci_request_regions(pdev, "my_driver");
    if (ret) goto err_disable;
    
    /* 映射内存空间的BAR - 推荐使用pci_ioremap_bar */
    dev->bar0_mem = pci_ioremap_bar(pdev, 0);
    if (!dev->bar0_mem) {
        ret = -ENOMEM;
        goto err_release;
    }
    
    /* 如果需要部分映射,使用pci_iomap */
    dev->bar2_mem = pci_iomap(pdev, 2, 0x1000); // 只映射前4KB
    if (!dev->bar2_mem) {
        ret = -ENOMEM;
        goto err_unmap_bar0;
    }
    
    /* 设备初始化... */
    return 0;
    
err_unmap_bar0:
    pci_iounmap(pdev, dev->bar0_mem);
err_release:
    pci_release_regions(pdev);
err_disable:
    pci_disable_device(pdev);
    return ret;
}

static void my_remove(struct pci_dev *pdev)
{
    struct my_device *dev = pci_get_drvdata(pdev);
    
    /* 取消映射 */
    if (dev->bar2_mem)
        pci_iounmap(pdev, dev->bar2_mem);
    if (dev->bar0_mem)
        pci_iounmap(pdev, dev->bar0_mem);
        
    /* 释放资源 */
    pci_release_regions(pdev);
    pci_disable_device(pdev);
}

重要注意事项

  1. 必须先调用 pci_request_regions:在映射之前必须成功申请资源所有权。

  2. 使用正确的访问函数:映射后必须使用专门的I/O访问函数:

    复制代码
    // 对于内存映射I/O(MMIO)
    ioread8(), ioread16(), ioread32()
    iowrite8(), iowrite16(), iowrite32()
    
    // 或者传统函数(取决于架构)
    readb(), readw(), readl()
    writeb(), writew(), writel()
  3. 不要直接使用指针 :绝对不能直接解引用 void __iomem * 指针:

    复制代码
    // 错误!
    u32 value = *regs;
    
    // 正确!
    u32 value = readl(regs);
  4. 配对使用 pci_iounmap :在驱动卸载时必须调用 pci_iounmap 来取消映射。

总结

  • pci_ioremap_bar 是首选:在大多数情况下,由于它的安全性和简洁性,应该优先使用。

  • pci_iomap 用于特殊情况:当需要部分映射或对BAR类型有明确控制时使用。

  • 遵循正确的调用顺序pci_enable_devicepci_request_regions → 映射函数。

  • 使用正确的I/O访问函数:映射后必须使用专门的读写函数访问硬件。

这两个函数是PCI驱动开发中访问硬件寄存器的基石,正确使用它们对于编写稳定可靠的驱动程序至关重要。

相关推荐
应用市场7 小时前
Linux驱动开发原理详解:从入门到实践
linux·运维·驱动开发
linweidong11 小时前
跨平台驱动开发:打造兼容多款MCU的硬核方案
驱动开发·单片机·嵌入式硬件·bsp·rtos·spi驱动·hal设计
撬动未来的支点1 天前
【Linux】Linux驱动开发与BSP开发:嵌入式系统的两大基石
linux·驱动开发
碰大点2 天前
第8章 zynq uboot更新系统镜像并引导启动和个人心得
驱动开发·fpga开发·uboot·zynq
piaoroumi3 天前
AM62X调试蓝牙
linux·arm开发·驱动开发
DeeplyMind3 天前
AMD KFD的BO设计分析系列6-1: VRAM BO的显存分配分析
linux·驱动开发·amdgpu·rocm·kfd
sukalot5 天前
windows显示驱动开发-Windows 显示驱动程序模型 (WDDM) 64 位问题
驱动开发
DeeplyMind5 天前
第二章:模块的编译与运行-7 Loading and Unloading Modules
linux·驱动开发
sukalot6 天前
windows显示驱动开发-浮点、围栏支持、资源管理
windows·驱动开发