设备驱动中的iomem(kernel-4.7)

来源:互联网 发布:ios6.1.3可用软件 编辑:程序博客网 时间:2024/04/30 14:30

对于外设的访问,最终都是通过读写设备上的寄存器实现的,寄存器不外乎:控制寄存器、状态寄存器和数据寄存器,这些外设寄存器也称为“IO端口”,并且一个外设的寄存器通常是连续编址的。

不同的CPU体系对外设IO端口物理地址的编址方式也不同,分为I/O映射方式(I/O-mapped)和内存映射方式(Memory-mapped)。

Linux设计了一个通用的数据结构resource来描述各种I/O资源(如:I/O端口、外设内存、DMA和IRQ等)。该结构定义在include/linux/ioport.h头文件中。Linux是以一种倒置的树形结构来管理每一类I/O资源。每一类I/O资源都对应有一颗倒置的资源树,树中的每一个节点都是个resource结构。基于上述这个思想,Linux将基于I/O映射方式的I/O端口和基于内存映射方式的I/O端口资源统称为“I/O区域”(I/O Region)

以pci设备为例,硬件的拓扑结构就决定了硬件在内存映射到CPU的物理地址,由于内存访问都是虚拟地址,所有就需要ioremap,此时物理内存是存在的,所以不用再分配内存,只需要做映射即可。

resource结构体:

/* * Resources are tree-like, allowing * nesting etc.. */struct resource {    resource_size_t start;    resource_size_t end;    const char *name;    unsigned long flags;    unsigned long desc;    struct resource *parent, *sibling, *child;};

申请I/O端口的函数是request_region, 申请I/O内存的函数是request_mem_regionrequest_mem_region函数并没有做实际性的映射工作,只是告诉内核要使用一块内存地址,声明占有,也方便内核管理这些资源。重要的还是ioremap函数,ioremap主要是检查传入地址的合法性,建立页表(包括访问权限),完成物理地址到虚拟地址的转换。

I/O Region的分配

在函数__request_resource()的基础上,Linux实现了用于分配I/O区域的函数__request_region(),如下:

/** * __request_region - create a new busy resource region * @parent: parent resource descriptor * @start: resource start address * @n: resource region size * @name: reserving caller's ID string * @flags: IO resource flags */struct resource * __request_region(struct resource *parent,                   resource_size_t start, resource_size_t n,                   const char *name, int flags){    DECLARE_WAITQUEUE(wait, current);    struct resource *res = alloc_resource(GFP_KERNEL);//分配一个resource结构    if (!res)        return NULL;    res->name = name;    res->start = start;    res->end = start + n - 1;    write_lock(&resource_lock);    for (;;) {        struct resource *conflict;        res->flags = resource_type(parent) | resource_ext_type(parent);        res->flags |= IORESOURCE_BUSY | flags;        res->desc = parent->desc;        conflict = __request_resource(parent, res);//调用__request_resource()函数进行资源分配        if (!conflict)            break;        if (conflict != parent) { //分配不成功,则进一步判断所返回的冲突资源节点是否就是父资源节点parent            if (!(conflict->flags & IORESOURCE_BUSY)) {                parent = conflict;                continue;            }        }        if (conflict->flags & flags & IORESOURCE_MUXED) {            add_wait_queue(&muxed_resource_wait, &wait);            write_unlock(&resource_lock);            set_current_state(TASK_UNINTERRUPTIBLE);            schedule();            remove_wait_queue(&muxed_resource_wait, &wait);            write_lock(&resource_lock);            continue;        }        /* Uhhuh, that didn't work out.. */        free_resource(res);        res = NULL;        break;    }    write_unlock(&resource_lock);    return res;}

用一个for循环开始进行资源分配,循环体的步骤如下:

首先,调用__request_resource()函数进行资源分配。如果返回NULL,说明分配成功,因此就执行break语句退出for循环,返回所分配的resource结构的指针,函数成功地结束。
  
如果__request_resource()函数分配不成功,则进一步判断所返回的冲突资源节点是否就是父资源节点parent。如果不是,则将分配行为下降一个层次,即试图在当前冲突的资源节点中进行分配(只有在冲突的资源节点没有设置IORESOURCE_BUSY的情况下才可以),于是让 parent指针等于conflict,并在conflict->flags&IORESOURCE_BUSY为0的情况下执行 continue语句继续for循环。否则如果相冲突的资源节点就是父节点parent,或者相冲突资源节点设置了IORESOURCE_BUSY标志位,则宣告分配失败。于是调用free_resource函数释放所分配的resource结构,并将res指针置为NULL,最后用break语句推出for循环。
   
最后,返回所分配的resource结构的指针。

I/O Region的释放

函数__release_region()实现在一个父资源节点parent中释放给定范围的I/O Region。实际上该函数的实现思想与__release_resource()相类似。其源代码如下:

/** * __release_region - release a previously reserved resource region * @parent: parent resource descriptor * @start: resource start address * @n: resource region size * * The described resource region must match a currently busy region. */void __release_region(struct resource *parent, resource_size_t start,            resource_size_t n){    struct resource **p;    resource_size_t end;    p = &parent->child;    end = start + n - 1;    write_lock(&resource_lock);    for (;;) {        struct resource *res = *p;        if (!res)            break;        if (res->start <= start && res->end >= end) {            if (!(res->flags & IORESOURCE_BUSY)) {                p = &res->child;                continue;            }            if (res->start != start || res->end != end)                break;            *p = res->sibling;            write_unlock(&resource_lock);            if (res->flags & IORESOURCE_MUXED)                wake_up(&muxed_resource_wait);            free_resource(res);            return;        }        p = &res->sibling;    }    write_unlock(&resource_lock);    printk(KERN_WARNING "Trying to free nonexistent resource "        "<%016llx-%016llx>\n", (unsigned long long)start,        (unsigned long long)end);}

类似地,该函数也是通过一个for循环来遍历父资源parent的child链表。为此,它让指针res指向当前正被扫描的子资源节点,指针p指向前一个子资源节点的sibling成员变量,p的初始值为指向parent->child。For循环体的步骤如下:
  ①让res指针指向当前被扫描的子资源节点(res=*p)。
  ②如果res指针为NULL,说明已经扫描完整个child链表,所以退出for循环。
  ③如果res指针不为NULL,则继续看看所指定的I/O区域范围是否完全包含在当前资源节点中,也即看看[start,start+n-1]是否包含在res->[start,end]中。如果不属于,则让p指向当前资源节点的sibling成员,然后继续for循环。如果属于,则执行下列步骤:
  
先看看当前资源节点是否设置了IORESOURCE_BUSY标志位。如果没有设置该标志位,则说明该资源节点下面可能还会有子节点,因此将扫描过程下降一个层次,于是修改p指针,使它指向res->child,然后执行continue语句继续for循环。
如果设置了IORESOURCE_BUSY标志位。则一定要确保当前资源节点就是所指定的I/O区域,然后将当前资源节点从其父资源的child链表中去除。这可以通过让前一个兄弟资源节点的sibling指针指向当前资源节点的下一个兄弟资源节点来实现(即让*p=res->sibling),最后调用free_resource函数释放当前资源节点的resource结构。然后函数就可以成功返回了。

管理I/O端口资源

Linux是基于“I/O Region”这一概念来实现对I/O端口资源(I/O-mapped 或 Memory-mapped)的管理的。

Linux在kernel/resource.c文件中定义了全局变量ioport_resourceiomem_resource,来分别描述基于I/O映射方式的整个I/O端口空间和基于内存映射方式的I/O内存资源空间(包括I/O端口和外设内存)。其定义如下:

struct resource ioport_resource = {    .name   = "PCI IO",    .start  = 0,    .end    = IO_SPACE_LIMIT,    .flags  = IORESOURCE_IO,};struct resource iomem_resource = {    .name   = "PCI mem",    .start  = 0,    .end    = -1,    .flags  = IORESOURCE_MEM,};

显然,I/O内存空间的大小是4GB。

未完待续。。。。

0 0