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

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

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驱动开发中访问硬件寄存器的基石,正确使用它们对于编写稳定可靠的驱动程序至关重要。

相关推荐
cxr8289 天前
SPARC方法论在Claude Code基于规则驱动开发中的应用
人工智能·驱动开发·claude·智能体
sukalot9 天前
window显示驱动开发—显示适配器的子设备
驱动开发
Evan_ZGYF丶10 天前
【RK3576】【Android14】如何在Android14下单独编译kernel-6.1?
linux·驱动开发·android14·rk3576
sukalot11 天前
window显示驱动开发—视频呈现网络简介
驱动开发
sukalot11 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(二)
驱动开发
zwhSunday11 天前
Linux驱动开发(1)概念、环境与代码框架
linux·运维·驱动开发
sukalot12 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(三)
驱动开发
sukalot12 天前
window显示驱动开发—为头装载和专用监视器生成自定义合成器应用(一)
驱动开发
cxr82813 天前
基于Claude Code的 规范驱动开发(SDD)指南
人工智能·hive·驱动开发·敏捷流程·智能体