Linux设备驱动中的最重要的和最终都要涉及到I/O操作(读写外设控制寄存器),其过程中会涉及到两类地址信息:物理地址和虚拟地址(针对统一编址)。 “内核不能直接操作物理地址”这里指的是不能对物理地址进行I/O操作,但内核还是会对物理地址的IO_MEM(外设寄存器物理地址区间)进行管理(IO内存的相对固定性使得内核可以在软件层对其物理地址进行管理,而不像物理内存的动态分配使用,几乎不会知道你每次所使用的物理内存区的物理地址),以确保I/O操作的安全有序和合法性。 驱动程序中,通常通过设备模型的设备结构体中取得外设控制寄存器的物理地址区间(通过resource结构),然后会通过 request_mem_region确认其可用和合法性,接着就会通过ioremap将物理地址映射到内核虚拟地址空间,这样内核就可以使用该虚拟地址区间进行I/O操作,读写外设寄存器了,当然{zh1}要通过release_mem_region释放对应区域的不可用状态。 其中的request_mem_region涉及到了内核对于I/O内存的管理。 141 #define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0) 其中的 iomem_resource定义如下: 32 struct resource iomem_resource = { 33 .name = "PCI mem", 34 .start = 0, 35 .end = -1, //0x0~0xFFFFFFFF 全部4G空间 36 .flags = IORESOURCE_MEM, 37 }; “Resources are tree-like, allowing nesting etc..”resource为树结构,允许嵌套,其内部有父、子及兄弟指针。 18 struct resource { 19 resource_size_t start; 20 resource_size_t end; 21 const char *name; 22 unsigned long flags; 23 struct resource *parent, *sibling, *child; 24 }; 由其下调用函数的参数名字可以看出iomem_resource为资源树的根,所以其内存范围包括了全部内存空间,其下子资源都在其空间范围内。 595 /** 596 * __request_region - create a new busy resource region 597 * @parent: parent resource descriptor 598 * @start: resource start address 599 * @n: resource region size 600 * @name: reserving caller's ID string 601 * @flags: IO resource flags 602 */ 603 struct resource * __request_region(struct resource *parent, 604 resource_size_t start, resource_size_t n, 605 const char *name, int flags) 606 { 607 struct resource *res = kzalloc(sizeof(*res), GFP_KERNEL); 608 609 if (!res) 610 return NULL; 611 612 res->name = name; 613 res->start = start; 614 res->end = start + n - 1; 615 res->flags = IORESOURCE_BUSY; 616 res->flags |= flags; 617 /* 创建一个新的resource并初始化,资源区域为所申请区域,并标记为BUSY */ 618 write_lock(&resource_lock); 619 620 for (;;) { 621 struct resource *conflict; 622 623 conflict = __request_resource(parent, res); /* 查找是否有与新创建resource有冲突的已创建resource */ 624 if (!conflict) 625 break; /* 如没有冲突则申请成功 */ 626 if (conflict != parent) { 627 parent = conflict; 628 if (!(conflict->flags & IORESOURCE_BUSY)) 629 continue; 630 } 631 /* 如果返回的不是其父资源(所请求范围在父资源范围内),且冲突区间不是已占用(已存在 该地址范围但没有被占用),则在所冲突resource子树下继续申请(在冲突区间范围内申 请), 直至找到或则遍历至叶子节点(无法满足请求) */ 632 /* Uhhuh, that didn't work out.. */ 633 kfree(res); 634 res = NULL; 635 break; 636 } 637 write_unlock(&resource_lock); 638 return res; 639 } 正如,注释的那样,该函数主要创建一个新的标记为BUSY的resource,在不发生冲突的情况下,将其加入资源管理树中,并成功返回。否则,表示目前该物理地址范围不可用(多为已占用BUSY)。 下面是查找冲突resource的函数: 143 /* Return the conflict entry if you can't request it */ 144 static struct resource * __request_resource(struct resource *root, struct resource *new) 145 { 146 resource_size_t start = new->start; 147 resource_size_t end = new->end; 148 struct resource *tmp, **p; 149 150 if (end < start) 151 return root; 152 if (start < root->start) 153 return root; 154 if (end > root->end) 155 return root; /* 从根开始遍历,因为父资源的地址范围包括子资源,如果所请求范围不在父资源的范围内, 直接返回父资源,会在上层函数判断请求失败 。以下为遍历子树*/ 156 p = &root->child; 157 for (;;) { 158 tmp = *p; 159 if (!tmp || tmp->start > end) { 160 new->sibling = tmp; 161 *p = new; /* 置为root的直接child */ 162 new->parent = root; 163 return NULL; 164 } /* 由 tmp->start > end这一条件可以看出在同一层次(兄弟资源),资源的地址范围是从小 到大排列的,如果新地址范围xx在当前已存在resource地址范围之前,即说明是不会冲突的。 然后就将其加入到层次(兄弟节点)的最前(通过将root的直接child设置为new) ,保持其 有序性。 */ 165 p = &tmp->sibling; 166 if (tmp->end < start) 167 continue; 168 return tmp; /* 在前一资源地址范围不和请求范围冲突(范围有交叉)情况下(xx在前一范围之后),继 续遍历root的child链表后继兄弟资源,直到发现冲突resource或者无冲突resource */ 169 } 170 } 可以看到这里的冲突,是指资源(同一层次下)的地址范围有交叉,在多个模块对同一I/O地址操作的情况下,容易发生错乱,导致硬件不能正常工作(通常情况下,外设I/O范围是不可能冲突的),所以要检测驱动所请求的I/O范围是不是有错误或者不合理之处,以保证I/O操作的安全和合理有序。 |