Linux内核中的一个宏函数例子

作者:计算机教程

377         *__addr = __val;                                               

  3.4.1 资源根节点的定义

从上面的代码看来,除了“##”比较难理解,其它都行。关键也是在“##”。经查资料,了解到这两个#的作用是连接字符串的作用。

int release_resource(struct resource *old){ int retval; write_lock(&resource_lock); retval = __release_resource(old); write_unlock(&resource_lock); return retval;}

在移植linux到龙芯3210的过程中,调试串口的时候,遇到了一个outb函数,却找不到这个函数的原型。当时是用VIM的跳转功能来看的代码。直接用grep工具也找不到这个函数。后问人才发现其实outb实则上是一个宏函数,而这宏函数的写法还真是少见,可能是见识少了。

  函数check_resource()用于实现检查某一段I/O资源是否已被占���。其源代码如下:

369                                                                        

#define request_mem_region(start,n,name)   __request_region(&iomem_resource, (start), (n), (name))#define check_mem_region(start,n) __check_region(&iomem_resource, (start), (n))#define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n))

bwlq = b

  函数do_resource_list()主要通过一个while{}循环以及递归嵌套调用来实现,较为简单,这里就不在详细解释了。

图片 1

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

411 #define __BUILD_IOPORT_PFX(bus, bwlq, type)                            

  Linux将基于I/O映射方式的或内存映射方式的I/O端口通称为“I/O区域”(I/O region)。在讨论对I/O区域的管理之前,我们首先来分析一下Linux是如何实现“I/O资源”这一抽象概念的。

375         BUILD_BUG_ON(sizeof(type) > sizeof(unsigned long));            

  对上述函数的NOTE如下:

373                                                                        

3.6 访问I/O内存资源

379 }

  ②如果this指针为空,那就让new->end等于root->end。这有两层意思:第一种情况就是根结点的child指针为 NULL(即根节点没有任何子资源)。因此此时先暂时将new->end放到最大。第二种情况就是已经遍历完整个child链表,所以此时就让 new表示最后一个子资源后面那一段未使用的资源区间。

那么得到两个函数:

  ④最后,返回所分配的resource结构的指针。

371                                                                        

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

{

  ⑥如果上述两条件不能同时满足,则说明还没有找到,因此要继续扫描链表。在继续扫描之前,我们还是要判断一下this指针是否为空。如果为空, 说明已经扫描完整个child链表,因此就可以推出for循环了。否则就将new->start的值修改为this->end 1,并让 this指向下一个兄弟资源节点,从而继续扫描链表中的下一个子资源节点。

     SLOW_DOWN_IO;

  各成员的含义如下:

}

  ④如果当前被扫描节点不是资源old,那就继续扫描child链表中的下一个元素。因此将指针p指向tmp->sibling成员。

413         __BUILD_IOPORT_SINGLE(bus, bwlq, type, _p, SLOW_DOWN_IO)

  l 如果设置了IORESOURCE_BUSY标志位。则一定要确保当前资源节点就是所指定的I/O区域,然后将当前资源节点从其父资源的child链表中去 除。这可以通过让前一个兄弟资源节点的sibling指针指向当前资源节点的下一个兄弟资源节点来实现(即让*p=res->sibling),最 后调用kfree()函数释放当前资源节点的resource结构。然后函数就可以成功返回了。

 

  除了上述这些“单发”(single-shot)的I/O操作外,某些CPU也支持对某个I/O端口进行连续的读写操作,也即对单个I/O端口 读或写一系列字节、字或32位整数,这就是所谓的“字符串I/O指令”(String Instruction)。这种指令在速度上显然要比用循环来实现同样的功能要快得多。

378         slow;                                                          

  其中,参数start是I/O内存资源的起始物理地址(是CPU的RAM物理地址空间中的物理地址),参数n指定I/O内存资源的大小。

364                                                                        

  ②可以看出,函数实际上是通过调用内部静态函数__request_resource()来完成实际的资源分配工作。如果该函数返回非空指针,则表示有资源冲突;否则,返回NULL就表示分配成功。

414

  1. name指针:指向此资源的名称。
  2. start和end:表示资源的起始物理地址和终止物理地址。它们确定了资源的范围,也即是一个闭区间[start,end]。
  3. flags:描述此资源属性的标志(见下面)。
  4. 指针parent、sibling和child:分别为指向父亲、兄弟和子资源的指针。

365 static inline void pfx##out##bwlq##p(type val, unsigned long port)     

  假设某类资源有如下这样一颗资源树:

374         /* Really, we want this to be atomic */                        

3.4 管理I/O端口资源

372         __val = pfx##ioswab##bwlq(__addr, val);                        

  有些体系结构的CPU(如,PowerPC、m68k等)通常只实现一个物理地址空间(RAM)。在这种情况下,外设I/O端口的物理地址就被 映射到CPU的单一物理地址空间中,而成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指 令。这就是所谓的“内存映射方式”(Memory-mapped)。

 90         __asm__ __volatile__(

  其中,宏IO_SPACE_LIMIT表示整个I/O空间的大小,对于X86平台而言,它是0xffff(定义在include/asm-i386/io.h头文件中)。显然,I/O内存空间的大小是4GB。

}

  3.6.2 读写I/O内存资源

 91                 "sbt$0,0x80(%0)"

  一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,这可以通过系统固件(如BIOS)在启动时分配得到,或者通过设备的硬连线 (hardwired)得到。比如,PCI卡的I/O内存资源的物理地址就是在系统启动时由PCI BIOS分配并写到PCI卡的配置空间中的BAR中的。而ISA卡的I/O内存资源的物理地址则是通过设备硬连线映射到640KB-1MB范围之内的。但 是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,因为它们是在系统启动后才已知的(某种意义上讲是动态的),所以驱动程 序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核心虚地址范围,通过访内指令 访问这些I/O内存资源。

 89 #define __SLOW_DOWN_IO

  3.5.2 Pausing I/O

416         __BUILD_IOPORT_PFX(, bwlq, type)                               

  几乎每一种外设都是通过读写设备上的寄存器来进行的。外设寄存器也称为“I/O端口”,通常包括:控制寄存器、状态寄存器和数据寄存器三大类, 而且一个外设的寄存器通常被连续地编址。CPU对外设IO端口物理地址的编址方式有两种:一种是I/O映射方式(I/O-mapped),另一种是内存映 射方式(Memory-mapped)。而具体采用哪一种则取决于CPU的体系结构。

412         __BUILD_IOPORT_SINGLE(bus, bwlq, type, ,)                      

  可以看出,该函数主要通过调用内部静态函数do_resource_list()来实现其功能,其源代码如下:

368         type __val;                                                    

  3.4.3 对I/O内存资源的操作

367         volatile type *__addr;                                         

  对该函数的NOTE如下:

415 #define BUILDIO_IOPORT(bwlq, type)                                     

  ⑴8位宽的字符串I/O操作

376                                                                        

  ⑤然后,判断经过上述这些步骤所形成的资源区域new是否是一段有效的资源(end必须大于或等于start),而且资源区域的长度满足 size参数的要求(end-start+1>=size)。如果这两个条件均满足,则说明我们已经找到了一段满足条件的资源空洞。因此在对new ->end的值进行修正后,然后就可以返回了(返回值0表示成功)。

pxf 为空字符串

NOTE:

419 BUILDIO_IOPORT(b, u8)

  l 判断tmp指针是否为空(tmp指针为空说明已经遍历完整个child链表),或者当前被扫描节点的起始位置start是否比new的结束位置end还要 大。只要这两个条件之一成立的话,就说明没有资源冲突,于是就可以把new链入child链表中:①设置new的sibling指针指向当前正被扫描的节 点tmp(new->sibling=tmp);②当前节点tmp的前一个兄弟节点的sibling指针被修改为指向new这个节点(*p= new);③将new的parent指针设置为指向root。然后函数就可以返回了(返回值NULL表示没有资源冲突)。

417         __BUILD_IOPORT_PFX(__mem_, bwlq, type)

extern inline void outb(unsigned char value, unsigned short port) { __asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"    : : "a" (value), "Nd" (port));}extern inline void outb_p(unsigned char value, unsigned short port) { __asm__ __volatile__ ("outb %" "b " "0,%" "w" "1"    __FULL_SLOW_DOWN_IO    : : "a" (value), "Nd" (port));}

从419行看来,传入的参数只有两个b,u8,那么只有两个参数,其它的字符串为空。

  l 否则如果相冲突的资源节点就是父节点parent,或者相冲突资源节点设置了IORESOURCE_BUSY标志位,则宣告分配失败。于是调用kfree ()函数释放所分配的resource结构,并将res指针置为NULL,最后用break语句推出for循环。

static inline void outb(u8 val, unsigned long port)

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

     ... ....

/* * This generates reports for /proc/ioports and /proc/iomem */static char * do_resource_list(struct resource *entry, const char *fmt,   int offset, char *buf, char *end){ if (offset < 0)  offset = 0; while (entry) {  const char *name = entry->name;  unsigned long from, to;  if ((int) (end-buf) < 80)   return buf;  from = entry->start;  to = entry->end;  if (!name)   >";  buf = sprintf(buf, fmt offset, from, to, name);  if (entry->child)     buf = do_resource_list(entry->child, fmt, offset-2, buf, end);  entry = entry->sibling; } return buf;}

     ... ....

  3.2.6 获取资源的名称列表

418

  static rwlock_t resource_lock = RW_LOCK_UNLOCKED;

370         __addr = (void *)__swizzle_addr_##bwlq(mips_io_port_base port);

  ④接下来进行对齐操作。

363 #define __BUILD_IOPORT_SINGLE(pfx, bwlq, type, p, slow)                

  ②调用__request_resource()函数在根节点root申请tmp所表示的资源。如果tmp所描述的资源还被人使用,则该函数返 回NULL,否则返回非空指针。因此接下来在conflict为NULL的情况下,调用__release_resource()将刚刚申请的资源释放 掉。

则看365行,明显得到:

  Linux在include/asm/io.h头文件(对于i386平台就是include/asm-i386/io.h)中定义了一系列读写不同宽度I/O端口的宏函数。如下所示:

在./include/asm/io.h(其实asm是一个软链接,实际上是./include/asm-mips/io.h)中:

  void insl(unsigned port,void * addr,unsigned long count);  void outsl(unsigned port ,void * addr,unsigned long count);

420 BUILDIO_IOPORT(w, u16)

3.2 Linux对I/O资源的管理

static inline void outb_p(u8 val, unsigned long port)

  上述定义中的宏__io_virt()仅仅检查虚地址addr是否是核心空间中的虚地址。该宏在内核2.4.0中的实现是临时性的。具体的实现函数在arch/i386/lib/Iodebug.c文件。

 92                 : : "r" (mips_io_port_base));

  Linux设计了一个通用的数据结构resource来描述各种I/O资源(如:I/O端口、外设内存、DMA和IRQ等)。该结构定义在include/linux/ioport.h头文件中:

相关阅读:移植linux到龙芯3210笔记

  基于上述这个思想,Linux在kernel/Resource.c文件中实现了对资源的申请、释放及查找等操作。

type = u8

  ⑶32位宽的字符串I/O操作

可以看到outb与outb_p的区别在于执行完之后,outb_p调用了一个IO延时。但从这个SLOW_DOWN_IO中看来,相当于汇编中的:
sb $0, 0x80(mips_io_port_base)
意思是清零mips_io_port_base + 0x80这个地址保存的值????
怪怪的。
参考了LDD3中的一段话:
暂停式IO
在处理器试图从总线上快速传输数据时,某些平台(特别是i386)会遇到问题。当处理器时钟相比外设时钟快时(比如ISA)就会出现问题,并且在设备卡特别慢时表现出来。斛决的办法是在每条IO指令之后,如果还有其他类似指令,则插入一个小的延迟。在x86平台上,这种暂停可通过对端口0x80的一条out b指令实现(通常这样做,但很少使用),或者通过使用忙等待实现。相关细节可参与自己平台上asm子目录下的io.h文件。

  ③根据参数min和max修正new->[start,end]的值,以使资源new被包含在[min,max]区域内。

366 {                                                                      

  ①让res指针指向当前被扫描的子资源节点(res=*p)。

{

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

421 BUILDIO_IOPORT(l, u32)

  函数用于取消ioremap()所做的映射,参数addr是指向核心虚地址的指针。这两个函数都是实现在mm/ioremap.c文件中。具体实现可参考《情景分析》一书。

  基于I/O Region的操作函数__XXX_region(),Linux在头文件include/linux/ioport.h中定义了三个对I/O内存资源进 行操作的宏:①request_mem_region()宏,请求分配指定的I/O内存资源。②check_ mem_region()宏,检查指定的I/O内存资源是否已被占用。③release_ mem_region()宏,释放指定的I/O内存资源。这三个宏的定义如下:

  3.2.5 分配接口allocate_resource()

图片 2

  函数get_resource_list()用于获取根节点root的子资源名字列表。该函数主要用来支持/proc/文件系统(比如实现proc/ioports文件和/proc/iomem文件)。其源代码如下:

  struct resource { const char *name; unsigned long start, end; unsigned long flags; struct resource *parent, *sibling, *child;  };

static int __release_resource(struct resource *old){ struct resource *tmp, **p; p = &old->parent->child; for (;;) {  tmp = *p;  if (!tmp)   break;  if (tmp == old) {   *p = tmp->sibling;   old->parent = NULL;   return 0;  }  p = &tmp->sibling; } return -EINVAL;}

  ③如果res指针不为NULL,则继续看看所指定的I/O区域范围是否完全包含在当前资源节点中,也即看看[start,start n-1] 是否包含在res->[start,end]中。如果不属于,则让p指向当前资源节点的sibling成员,然后继续for循环。如果属于,则执行 下列步骤:

  ②如果res指针为NULL,说明已经扫描完整个child链表,所以退出for循环。

  从前几节的阐述我们知道,I/O内存资源是在CPU的单一内存物理地址空间内进行编址的,也即它和系统RAM同处在一个物理地址空间内。因此通过CPU的访内指令就可以访问I/O内存资源。

  对函数的NOTE:

  unsigned char inb(unsigned port);  void outb(unsigned char value,unsigned port);

  void insb(unsigned port,void * addr,unsigned long count);  void outsb(unsigned port ,void * addr,unsigned long count); 

/* * Allocate empty slot in the resource tree given range and alignment. */int allocate_resource(struct resource *root, struct resource *new,        unsigned long size,        unsigned long min, unsigned long max,        unsigned long align,        void (*alignf)(void *, struct resource *, unsigned long),        void *alignf_data){    int err;    write_lock(&resource_lock);    err = find_resource(root, new, size, min, max, align, alignf, alignf_data);    if (err >= 0 && __request_resource(root, new)) err = -EBUSY;    write_unlock(&resource_lock);    return err;}

  l 首先,调用__request_resource()函数进行资源分配。如果返回NULL,说明分配成功,因此就执行break语句推出for循环,返回所分配的resource结构的指针,函数成功地结束。

  函数release_resource()用于实现I/O资源的释放。该函数只有一个参数——即指针old,它指向所要释放的资源。起源代码如下:

  类似地,该函数也是通过一个for循环来遍历父资源parent的child链表。为此,它让指针res指向当前正被扫描的子资源节点,指针p 指向前一个子资源节点的sibling成员变量,p的初始值为指向parent->child。For循环体的步骤如下:

  ⑴读写8位宽的I/O端口

  3.6.1 映射I/O内存资源

  基于I/O Region的操作函数__XXX_region(),Linux在头文件include/linux/ioport.h中定义了三个对I/O端口空间进 行操作的宏:①request_region()宏,请求在I/O端口空间中分配指定范围的I/O端口资源。②check_region()宏,检查 I/O端口空间中的指定I/O端口资源是否已被占用。③release_region()宏,释放I/O端口空间中的指定I/O端口资源。这三个宏的定义 如下:

  对该函数的NOTE如下:

  在find_resource()函数的基础上,函数allocate_resource()实现:在一颗资源树中分配一条指定大小的、且包含在指定区域[min,max]中的、未使用资源区域。其源代码如下:

  同样,该函数也要遍历root的child链表,以寻找未被使用的资源空洞。为此,它让this指针表示当前正被扫描的子资源节点,其初始值等 于root->child,即指向child链表中的第一个节点,并让new->start的初始值等于root->start,然后 用一个for循环开始扫描child链表,对于每一个被扫描的节点,循环体执行如下操作:

  3.5.1 对I/O端口的字符串操作

  3.3.1 I/O Region的分配

#define get_ioport_list(buf) get_resource_list(&ioport_resource, buf, PAGE_SIZE)#define get_mem_list(buf) get_resource_list(&iomem_resource, buf, PAGE_SIZE)

本文由nba买球发布,转载请注明来源

关键词: