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

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

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

相关推荐
dlz08364 小时前
POE驱动开发流程
驱动开发
嵌入式-老费6 小时前
Linux camera驱动开发(DVP接口的camera sensor)
驱动开发
VernonJsn21 小时前
visual studio 2022的windows驱动开发
ide·驱动开发·visual studio
嵌入式郑工1 天前
RK3566 LubanCat 开发板 USB Gadget 配置完整复盘
linux·驱动开发·ubuntu
雾削木2 天前
树莓派 ESPHome 固件编译与烧录全攻略(解决超时与串口识别问题)
驱动开发
春日见3 天前
win11 分屏设置
java·开发语言·驱动开发·docker·单例模式·计算机外设
DarkAthena4 天前
【GaussDB】手动编译不同python版本的psycopg2驱动以适配airflow
驱动开发·python·gaussdb
松涛和鸣4 天前
DAY66 SPI Driver for ADXL345 Accelerometer
linux·网络·arm开发·数据库·驱动开发
嵌入式郑工4 天前
# RK3576 平台 RTC 时钟调试全过程
linux·驱动开发·ubuntu
GS8FG4 天前
针对Linux,RK3568平台下,I2C驱动的一点小小的领悟
linux·驱动开发