Chinaunix首页 | 论坛 | 博客
  • 博客访问: 646882
  • 博文数量: 121
  • 博客积分: 4034
  • 博客等级: 上校
  • 技术积分: 1439
  • 用 户 组: 普通用户
  • 注册时间: 2010-04-28 12:42
文章分类

全部博文(121)

文章存档

2017年(8)

2016年(10)

2013年(2)

2012年(3)

2011年(18)

2010年(80)

分类: LINUX

2010-12-30 22:35:52

I/O端口

计算机只认识0和1。无论驱动程序写得多么复杂,归根到底无非还是向某个端口赋值,这个值只能是0或者1,或者是写寄存器,而接收这个0或者1的就 是I/O口。如果CPU提供了很多的I/O口,那么我们的工作就会容易一些。与中断和内存不同,使用一个没有申请的I/O端口不会使CPU产生异常,也就 不会导致诸如"Segmentation fault"这类错误的发生。在使用I/O端口前,也应该检查I/O端口是否已有别的程序在使用。若没有,再把此端口标记为正在使用,在使用完以后释放 它。CPU对外设I/O端口物理地址的编址方式有两种:一种是I/O映射,另一种是内存映射。Linux将基于I/O映射方式的或内存映射方式的I/O端 口通称为"I/O区域(region)"。Linux内核使用resource结构类型来描述这些I/O区域。resource结构在ioport.h文 件中定义。

  1. struct resource {  
  2.     const char *name;                   //资源名称  
  3.     unsigned long start, end;               //资源起始和终止地址  
  4.     unsigned long flags;                    //资源属性,如是否可读、可缓存等  
  5.     struct resource *parent, *sibling, *child;      //资源以树的形式进行管理  
  6. }; 

我们不需要特别关注资源的管理过程,让Linux内核完成就可以了。对于驱动开发人员来说,需要关心的是下面的宏,因为它们才是对端口进行操作的函数。

  1. #define request_region(start,n,name)    __request_region(&ioport_resource, (start), (n), (name))  
  2. #define request_mem_region(start,n,name)      
  3. __request_region(&iomem_resource, (start), (n), (name))  
  4. #define rename_region(region, newname) do { (region)->name = (newname); } while (0)  
  5. #define release_region(start,n)     __release_region(&ioport_resource, (start), (n))  
  6. #define check_mem_region(start,n)   __check_region(&iomem_resource, (start), (n))  
  7. #define release_mem_region(start,n) __release_region(&iomem_resource, (start), (n)) 

request_region告诉内核,使用起始地址为start的n个端口,name是设备名。在request_region函数中通过 kzalloc函数分配内存,并将分配的内存设置为0。当然,kzalloc函数实际上还是通过kmalloc完成内存分配的。 request_region函数返回所分配的resource结构的指针。分配的端口可以从/proc/ioports文件中得到。

request_region宏和request_mem_region宏类似,都是使用request_region函数获得资源,区别是申请的 资源不同。ioport_resource描述基于I/O映射方式的整个I/O端口空间,iomem_resource描述基于内存映射方式的I/O内存 资源空间。读者可以参考下面两个结构。

  1. struct resource ioport_resource = {  
  2.     .name   = "PCI IO",  
  3.     .start  = 0x0000,  
  4.     .end    = IO_SPACE_LIMIT,       //对S3C2410处理器,该值为0xffffffff  
  5.     .flags  = IORESOURCE_IO,        //资源类型,0x00000100  
  6. };  
  7. struct resource iomem_resource = {  
  8.     .name   = "PCI mem",  
  9.     .start  = 0UL,  
  10.     .end    = ~0UL,  
  11.     .flags  = IORESOURCE_MEM,   //资源类型,0x00000200  
  12. }; 

如果端口使用完毕,应该将其返还给内核,以便其他设备使用。这个工作由release_region完成。通常在卸载模块时调用。显然 release_region的核心是kfree函数。感兴趣的读者可以在kernel/resource.c文件中找到release_region函 数的实现。

check_region用来检查给定的I/O端口是否可用,原理是先申请一块可用的区域,并将其释放。

获得了可用的端口,可以对其进行读/写操作。这些访问I/O口的函数在asm/io.h文件中定义,与平台紧密相关。下面几个函数是S3C2410平台使用的。

  1. #define inb(p)      (__builtin_constant_p((p)) ? __inbc(p)     : __inb(p))  
  2. #define inw(p)      (__builtin_constant_p((p)) ? __inwc(p)     : __inw(p))  
  3. #define inl(p)      (__builtin_constant_p((p)) ? __inlc(p)     : __inl(p))  
  4. #define outb(v,p)       (__builtin_constant_p((p)) ? __outbc(v,p) : __outb(v,p))  
  5. #define outw(v,p)   (__builtin_constant_p((p)) ? __outwc(v,p) : __outw(v,p))  
  6. #define outl(v,p)       (__builtin_constant_p((p)) ? __outlc(v,p) : __outl(v,p)) 

其中in表示读取某个端口的值,out表示向某个端口赋值。b、w、l分别代表8位、16位和32位的端口。从下面的汇编代码得知,端口控制最终是通过调用strb和ldrb指令实现的。

  1. #define __outbc(value,port)                     \  
  2. ({                                      \  
  3.     if (__PORT_PCIO((port)))                    \  
  4.         __asm__ __volatile__(                   \  
  5.         "strb   %0, [%1, %2]    @ outbc"            \  
  6.         : : "r" (value), "r" (PCIO_BASE), "Jr" ((port)));\  
  7.     else                                    \  
  8.         __asm__ __volatile__(                   \  
  9.         "strb   %0, [%1, #0]    @ outbc"            \  
  10.         : : "r" (value), "r" ((port)));             \  
  11. })  
  12. #define __inbc(port)                            \  
  13. ({                                      \  
  14.     unsigned char result;                       \  
  15.     if (__PORT_PCIO((port)))                    \  
  16.         __asm__ __volatile__(                   \  
  17.         "ldrb   %0, [%1, %2]    @ inbc"         \  
  18.         : "=r" (result) : "r" (PCIO_BASE), "Jr" ((port)));\  
  19.     else                                    \  
  20.         __asm__ __volatile__(                   \  
  21.         "ldrb   %0, [%1, #0]    @ inbc"         \  
  22.         : "=r" (result) : "r" ((port)));                \  
  23.     result;                             \  
  24. }) 

上面谈到的I/O操作都是一次传输一个数据,实际上内核也实现了对I/O端口的串操作,从速度上快了许多。不过在新版本的内核中,已经明确指出这些方法并不推荐使用,估计在新版本的内核中将被淘汰。

  1. #define insb(p,d,l) __raw_readsb(__ioaddr(p),d,l)  
  2. #define insw(p,d,l) __raw_readsw(__ioaddr(p),d,l)  
  3. #define insl(p,d,l) __raw_readsl(__ioaddr(p),d,l)  
  4.  
  5. #define outsb(p,d,l)    __raw_writesb(__ioaddr(p),d,l)  
  6. #define outsw(p,d,l)    __raw_writesw(__ioaddr(p),d,l)  
  7. #define outsl(p,d,l)    __raw_writesl(__ioaddr(p),d,l) 

虽然我们已经掌握了读/写I/O端口的方法,但是要知道,I/O端口空间非常有限,无法满足现在总线的设备。实际上现在的总线设备都以内存映射方式 来映射它的I/O端口和外设内存。但是驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内,然后才能根据映射所得到 的核心虚地址范围,通过访内指令访问这些I/O内存资源。Linux内核提供了ioremap( )函数将I/O内存资源的物理地址映射到核心虚地址空间中,对于ARM平台,新版本的内核使用下面几个宏定义完成内存映射,这些宏在asm-arm /io.h文件中。

  1. #ifndef __arch_ioremap  
  2. #define ioremap(cookie,size)            __ioremap(cookie,size,0)  
  3. #define ioremap_noCache(cookie,size)    __ioremap(cookie,size,0)  
  4. #define ioremap_Cached(cookie,size) __ioremap(cookie,size,L_PTE_CACHEABLE)  
  5. #define iounmap(cookie)         __iounmap(cookie)  
  6. #else  
  7. #define ioremap(cookie,size)            __arch_ioremap((cookie),(size),0)  
  8. #define ioremap_noCache(cookie,size)    __arch_ioremap((cookie),(size),0)  
  9. #define ioremap_Cached(cookie,size) __arch_ioremap((cookie),(size),L_PTE_CACHEABLE)  
  10. #define iounmap(cookie)         __arch_iounmap(cookie)  
  11. #endif 

所调用的__ioremap和__iounmap函数在arch\arm\mm\ioremap.c文件中实现。

  1. void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags)  
  2. void __iounmap(void __iomem *addr) 

phys_addr是要映射的起始的I/O地址,size是要映射的空间的大小,flags是要映射的I/O空间的和权限有关的标志。在将I/O内 存资源的物理地址映射成核心虚地址后,可以像读/写内存一样读/写I/O资源。下面的一组函数是在lib/iomap.c文件中定义的,其中参数addr 是经过ioremap映射的地址。

  1. unsigned int fastcall ioread8(void __iomem *addr)       //从addr处读取1个字节  
  2. unsigned int fastcall ioread16(void __iomem *addr)  
  3. unsigned int fastcall ioread16be(void __iomem *addr)  
  4. unsigned int fastcall ioread32(void __iomem *addr)  
  5. unsigned int fastcall ioread32be(void __iomem *addr)  
  6.  
  7. void fastcall iowrite8(u8 val, void __iomem *addr)  
  8. void fastcall iowrite16(u16 val, void __iomem *addr)  
  9. void fastcall iowrite16be(u16 val, void __iomem *addr)  
  10. void fastcall iowrite32(u32 val, void __iomem *addr)  
  11. void fastcall iowrite32be(u32 val, void __iomem *addr) 

下列一组函数是上述函数的重复版本,当必须在给定的I/O内存地址处读/写一系列值时使用。

  1. void fastcall ioread8_rep(void __iomem *addr, void *dst, unsigned long count)  
  2. void fastcall ioread16_rep(void __iomem *addr, void *dst, unsigned long count)  
  3. void fastcall ioread32_rep(void __iomem *addr, void *dst, unsigned long count)  
  4.  
  5. void fastcall iowrite8_rep(void __iomem *addr, const void *src, unsigned long count)  
  6. void fastcall iowrite16_rep(void __iomem *addr, const void *src, unsigned long count)  
  7. void fastcall iowrite32_rep(void __iomem *addr, const void *src, unsigned long count) 

如果要对一块I/O内存进行操作,可以使用下面的宏。

  1. #define memset_io(c,v,l)        _memset_io(__mem_pci(c),(v),(l))  
  2. #define memcpy_fromio(a,c,l)    _memcpy_fromio((a),__mem_pci(c),(l))  
  3. #define memcpy_toio(c,a,l)      _memcpy_toio(__mem_pci(c),(a),(l)) 

其中的三个函数在arm\kernel\io.c文件中实现,其功能注释得非常清楚。

  1. /*  
  2.  * Copy data from IO memory space to "real" memory space.  
  3.  * This needs to be optimized.  
  4.  *  
  5. void _memcpy_fromio(void *to, const volatile void __iomem *from, size_t count)  
  6. {  
  7.     unsigned char *t = to;  
  8.     while (count) {  
  9.         count--;  
  10.         *t = readb(from);  
  11.         t++;  
  12.         from++;  
  13.     }  
  14. }  
  15. /*  
  16.  * Copy data from "real" memory space to IO memory space.  
  17.  * This needs to be optimized.  
  18.  */  
  19. void _memcpy_toio(volatile void __iomem *to, const void *from, size_t count)  
  20. {  
  21.     const unsigned char *f = from;  
  22.     while (count) {  
  23.         count--;  
  24.         writeb(*f, to);  
  25.         f++;  
  26.         to++;  
  27.     }  
  28. }  
  29. /*  
  30.  * "memset" on IO memory space.  
  31.  * This needs to be optimized.  
  32.  */  
  33. void _memset_io(volatile void __iomem *dst, int c, size_t count)  
  34. {  
  35.     while (count) {  
  36.         count--;  
  37.         writeb(c, dst);  
  38.         dst++;  
  39.     }  
  40. }
阅读(3392) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~