内核request_mem_region 和 ioremap的理解

几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。根据CPU体系结构的不同,CPU对IO端口的编址方式有两种:


(1)I/O映射方式(I/O-mapped)

典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。


(2)内存映射方式(Memory-mapped)

RISC指令系统的CPU(如MIPS ARM PowerPC等)通常只实现一个物理地址空间,像这种情况,外设的I/O端口的物理地址就被映射到内存地址空间中,外设I/O端口成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。


但是,这两者在硬件实现上的差异对于软件来说是完全透明的,驱动程序开发人员可以将内存映射方式的I/O端口和外设内存统一看作是"I/O内存"资源。

一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux在io.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到核心虚地址空间。

     但要使用I/O内存首先要申请,然后才能映射,使用I/O端口首先要申请,或者叫请求,对于I/O端口的请求意思是让内核知道你要访问这个端口,这样内核知道了以后它就不会再让别人也访问这个端口了.毕竟这个世界僧多粥少啊.申请I/O端口的函数是request_region, 申请I/O内存的函数是request_mem_region, 来自include/linux/ioport.h,  如下:

  * Convenience shorthand with allocation */

#define request_region(start,n,name)    __request_region(&ioport_resource, (start), (n), (name))

#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))

#define rename_region(region, newname) do { (region)->name = (newname); } while (0)

extern struct resource * __request_region(struct resource *,

                                         resource_size_t start,

                                         resource_size_t n, const char *name);

这里关键来解析一下request_mem_region函数。

Linux把基于I/O映射方式的I/O端口和基于内存映射方式的I/O端口资源统称为“I/O区域”(I/O Region)。I/O Region仍然是一种I/O资源,因此它仍然可以用resource结构类型来描述。

Linux是以一种倒置的树形结构来管理每一类I/O资源(如:I/O端口、外设内存、DMA和IRQ)的。每一类I/O资源都对应有一颗倒置的资源树,树中的每一个节点都是一个resource结构,而树的根结点root则描述了该类资源的整个资源空间。


1.结构体
   1.1>struct resource iomem_resource = { "PCI mem", 0x00000000, 0xffffffff, IORESOURCE_MEM };
   1.2>struct resource {
                const char *name;
                unsigned long start, end;
                unsigned long flags;
                struct resource *parent, *sibling, *child;
             };
2.调用函数
   request_mem_region(S1D_PHYSICAL_REG_ADDR,S1D_PHYSICAL_REG_SIZE, "EpsonFB_RG")
#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name))
__request_region检查是否可以安全占用起始物理地址S1D_PHYSICAL_REG_ADDR之后的连续S1D_PHYSICAL_REG_SIZE字节大小空间

struct resource * __request_region(struct resource *parent, unsigned long start, unsigned long n, const char *name)
{
    struct resource *res = kmalloc(sizeof(*res), GFP_KERNEL);

    if (res) {
        memset(res, 0, sizeof(*res));
         res->name = name;
         res->start = start;
         res->end = start + n - 1;
         res->flags = IORESOURCE_BUSY;

         write_lock(&resource_lock);

        for (;;) {
            struct resource *conflict;

             conflict = __request_resource(parent, res);    

             //sibling parent下的所有单元,检测申请部分是否存在交叠冲突
            if (!conflict)                                

             //conflict=0;申请成功,正常安置了[start,end]到相应位置
                break;
            if (conflict != parent) {
                 parent = conflict;
                if (!(conflict->flags & IORESOURCE_BUSY))
                    continue;
            }
              kfree(res);                                   

             //检测到了资源交叠冲突,kfree归还kmalloc申请的内存
             res = NULL;
            break;
        }
         write_unlock(&resource_lock);
    }
    return res;
}

static struct resource * __request_resource(struct resource *root, struct resource *new)
{
    unsigned long start = new->start;
    unsigned long end = new->end;
    struct resource *tmp, **p;

    if (end < start)
        return root;
    if (start < root->start)
        return root;
    if (end > root->end)
        return root;
     p = &root->child;                                     

    //root下的第一个链表元素*p.[child链表是以I/O资源物理地址从低到高的顺序排列的]
    for (;;) {
         tmp = *p;
        if (!tmp || tmp->start > end) {
            new->sibling = tmp;
            *p = new;
//可以从root->child=null开始我们的分析考虑,此时tmp=null,那么第一个申请将以!tmp条件满足而进入
//这时root->child的值为new指针,new->sibling = tmp = null;当第二次申请发生时:如果tmp->start > end成立,
//那么,root->child的值为new指针,new->sibling = tmp;这样就链接上了,空间分布图如:
//child=[start,end]-->[tmp->start,tmp->end](1);

//如果条件tmp->start > end不成立,那么只能是!tmp条件进入
//那么,root->child的值不变,tmp->sibling = new;new->sibling = tmp = null这样就链接上了,空间分布图如:
//child=[child->start,child->end]-->[start,end](2);
//当第三次申请发生时:如果start在(2)中的[child->end,end]之间,那么tmp->end < start将成立,继而continue,
//此时tmp = (2)中的[start,end],因为tmp->start < end,所以继续执行p = &tmp->slibing = null,
//因为tmp->end > start,所以资源冲突,返回(2)中的[start,end]域
//综上的两个边界值情况和一个中间值情况的分析,可以知道代码实现了一个从地地址到高地址的顺序链表
//模型图:childe=[a,b]-->[c,d]-->[e,f],此时有一个[x,y]需要插入进去,tmp作为sibling指针游动
//tmp指向child=[a,b],
//tmp指向[a,b],当tmp->start>y时,插入后的链接图为:child=[x,y]-->[a,b]-->[c,d]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp
//tmp指向[c,d],当tmp->start>y时,插入后的链接图为:child=[a,b]-->[x,y]-->[c,d]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp
//tmp指向[e,f],当tmp->start>y时,插入后的链接图为:child=[a,b]-->[c,d]-->[x,y]-->[e,f]-->null;当tmp->end>=x时,冲突返回tmp
//tmp指向null                  ,插入后的链接图为:child=[a,b]-->[c,d]-->[e,f]-->[x,y]-->null;
//顺利的达到了检测冲突,顺序链接的目的
            new->parent = root;    
            return NULL;
        }
         p = &tmp->sibling;
        if (tmp->end < start)
            continue;
        return tmp;
    }
}


其实说白了,request_mem_region函数并没有做实际性的映射工作,只是告诉内核要使用一块内存地址,声明占有,也方便内核管理这些资源。


重要的还是ioremap函数,ioremap主要是检查传入地址的合法性,建立页表(包括访问权限),完成物理地址到虚拟地址的转换。

void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);

iounmap函数用于取消ioremap()所做的映射,原型如下:

void iounmap(void * addr);

这两个函数都是实现在mm/ioremap.c文件中。

在将I/O内存资源的物理地址映射成核心虚地址后,理论上讲我们就可以象读写RAM那样直接读写I/O内存资源了。为了保证驱动程序的跨平台的可移植性,我们应该使用Linux中特定的函数来访问I/O内存资源,而不应该通过指向核心虚地址的指针来访问。如在x86平台上,读写I/O的函数如下所示:

#define readb(addr) (*(volatile unsigned char *) __io_virt(addr))
#define readw(addr) (*(volatile unsigned short *) __io_virt(addr))
#define readl(addr) (*(volatile unsigned int *) __io_virt(addr))

#define writeb(b,addr) (*(volatile unsigned char *) __io_virt(addr) = (b))
#define writew(b,addr) (*(volatile unsigned short *) __io_virt(addr) = (b))
#define writel(b,addr) (*(volatile unsigned int *) __io_virt(addr) = (b))

#define memset_io(a,b,c) memset(__io_virt(a),(b),(c))
#define memcpy_fromio(a,b,c) memcpy((a),__io_virt(b),(c))
#define memcpy_toio(a,b,c) memcpy(__io_virt(a),(b),(c))

最后,特别强调驱动程序中mmap函数的实现方法。用mmap映射一个设备,意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问。

---------------------
作者:kerneler_
来源:CSDN
原文:https://blog.csdn.net/skyflying2012/article/details/8672011
版权声明:本文为博主原创文章,转载请附上博文链接!

<think>好的,我现在需要帮助用户理解Linux内核中的pci_ioremap_bar函数的功能用法。首先,我应该回顾一下已有的知识,看看已有的引用材料是否相关。用户提供的引用中提到了ioremap_nocache、devm_ioremapdevm_ioremap_resource,这些函数都与内存映射有关,特别是I/O内存的映射。 首先,用户的问题是关于pci_ioremap_bar的,这个函数可能PCI设备的内存映射相关。根据已有的知识,ioremap_nocache用于将物理地址映射到内核虚拟地址空间,并且禁用缓存,这可能用于直接访问硬件寄存器。而devm_ioremap_resource则结合了资源申请映射,确保资源在使用前已被正确请求。这提示我,pci_ioremap_bar可能也是类似的函数,但特定用于PCI设备的BAR(Base Address Register)。 接下来,我需要确认pci_ioremap_bar的具体功能。根据Linux内核文档,PCI设备的BAR存储了设备所需的内存或I/O空间的基地址。驱动程序需要将这些物理地址映射到内核的虚拟地址空间,以便访问设备的寄存器或内存。pci_ioremap_bar的作用应该就是完成这个映射,可能类似于ioremap_nocache,但专为PCI BAR设计。 然后,考虑用法。用户可能需要知道如何调用这个函数,参数是什么。通常,PCI设备结构体(pci_dev)BAR的序号是参数,返回映射后的虚拟地址。例如,使用pci_ioremap_bar(pdev, bar_num)来获取对应BAR的虚拟地址。 还需要注意资源管理的问题,比如是否使用devm_版本来自动释放资源。用户提到的引用4中,devm_ioremap_resource是先申请资源再映射,而pci_ioremap_bar可能有对应的devm版本,比如devm_pci_ioremap_bar,这样在设备卸载时会自动释放映射,避免资源泄漏。 此外,示例代码会帮助用户理解如何在实际驱动中使用这个函数。比如在probe函数中,获取PCI设备后,调用pci_ioremap_bar来映射特定的BAR,然后通过指针访问该内存区域。 最后,要确保回答覆盖功能、用法、参数、注意事项示例,并且引用相关材料。例如,引用3中的lspci输出显示了PCI设备的内存地址I/O端口,这可能对应于某个BAR,而驱动程序需要使用pci_ioremap_bar来映射这些区域。</think>### pci_ioremap_bar 函数的功能与用法 #### 一、功能解析 `pci_ioremap_bar` 是 Linux 内核中用于映射 **PCI 设备 BAR(Base Address Register)** 到内核虚拟地址空间的函数。BAR 存储了 PCI 设备需要的内存或 I/O 空间基地址,驱动通过该函数将物理地址转换为内核可访问的虚拟地址,从而操作硬件寄存器或设备内存。其特点包括: 1. **自动检测资源类型**:根据 BAR 属性(如内存类型、可预取性)选择是否禁用缓存[^1][^4]。 2. **简化映射流程**:封装了 PCI 配置空间读取地址映射的逻辑。 #### 二、函数原型与参数 ```c void __iomem *pci_ioremap_bar(struct pci_dev *pdev, int bar); ``` - **`pdev`**:指向 PCI 设备的结构体指针(`struct pci_dev`)。 - **`bar`**:BAR 的编号(0-5,对应 PCI 标准定义的 6 个 BAR)。 #### 三、使用步骤 1. **启用 PCI 设备资源**:调用 `pci_enable_device` 确保设备已激活。 2. **请求 BAR 所有权**:使用 `pci_request_region` 声明资源占用(防止冲突)。 3. **执行地址映射**:调用 `pci_ioremap_bar` 获取虚拟地址指针。 4. **访问硬件资源**:通过 `ioread32`/`iowrite32` 等函数操作内存。 5. **资源释放**:卸载驱动时反向释放资源(`pci_iounmap_bar` `pci_release_region`)。 #### 四、代码示例 ```c #include <linux/pci.h> static int probe(struct pci_dev *pdev, const struct pci_device_id *id) { void __iomem *base; int ret; ret = pci_enable_device(pdev); if (ret) return ret; ret = pci_request_region(pdev, 0, "my_driver"); if (ret) goto fail_enable; base = pci_ioremap_bar(pdev, 0); if (!base) { ret = -ENOMEM; goto fail_request; } // 示例:读取寄存器值 u32 reg_value = ioread32(base + REG_OFFSET); return 0; fail_request: pci_release_region(pdev, 0); fail_enable: pci_disable_device(pdev); return ret; } ``` #### 五、注意事项 1. **资源管理**:推荐使用 `devm_pci_ioremap_bar` 自动释放资源。 2. **缓存策略**:若 BAR 标记为可预取(prefetchable),映射时可能启用缓存优化[^1][^3]。 3. **错误处理**:需检查返回值,避免空指针访问。 #### 六、关联场景 例如在 Intel 82540EM 网卡驱动中,通过 `lspci -v` 可看到其内存地址为 `f0200000`(32-bit, non-prefetchable),此时需使用 `pci_ioremap_bar` 禁用缓存以确保寄存器访问的原子性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值