分类: LINUX
2010-12-30 22:35:52
计算机只认识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文 件中定义。
- struct resource {
- const char *name; //资源名称
- unsigned long start, end; //资源起始和终止地址
- unsigned long flags; //资源属性,如是否可读、可缓存等
- struct resource *parent, *sibling, *child; //资源以树的形式进行管理
- };
我们不需要特别关注资源的管理过程,让Linux内核完成就可以了。对于驱动开发人员来说,需要关心的是下面的宏,因为它们才是对端口进行操作的函数。
- #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)
- #define release_region(start,n) __release_region(&ioport_resource, (start), (n))
- #define check_mem_region(start,n) __check_region(&iomem_resource, (start), (n))
- #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内存 资源空间。读者可以参考下面两个结构。
- struct resource ioport_resource = {
- .name = "PCI IO",
- .start = 0x0000,
- .end = IO_SPACE_LIMIT, //对S3C2410处理器,该值为0xffffffff
- .flags = IORESOURCE_IO, //资源类型,0x00000100
- };
- struct resource iomem_resource = {
- .name = "PCI mem",
- .start = 0UL,
- .end = ~0UL,
- .flags = IORESOURCE_MEM, //资源类型,0x00000200
- };
如果端口使用完毕,应该将其返还给内核,以便其他设备使用。这个工作由release_region完成。通常在卸载模块时调用。显然 release_region的核心是kfree函数。感兴趣的读者可以在kernel/resource.c文件中找到release_region函 数的实现。
check_region用来检查给定的I/O端口是否可用,原理是先申请一块可用的区域,并将其释放。
获得了可用的端口,可以对其进行读/写操作。这些访问I/O口的函数在asm/io.h文件中定义,与平台紧密相关。下面几个函数是S3C2410平台使用的。
- #define inb(p) (__builtin_constant_p((p)) ? __inbc(p) : __inb(p))
- #define inw(p) (__builtin_constant_p((p)) ? __inwc(p) : __inw(p))
- #define inl(p) (__builtin_constant_p((p)) ? __inlc(p) : __inl(p))
- #define outb(v,p) (__builtin_constant_p((p)) ? __outbc(v,p) : __outb(v,p))
- #define outw(v,p) (__builtin_constant_p((p)) ? __outwc(v,p) : __outw(v,p))
- #define outl(v,p) (__builtin_constant_p((p)) ? __outlc(v,p) : __outl(v,p))
其中in表示读取某个端口的值,out表示向某个端口赋值。b、w、l分别代表8位、16位和32位的端口。从下面的汇编代码得知,端口控制最终是通过调用strb和ldrb指令实现的。
- #define __outbc(value,port) \
- ({ \
- if (__PORT_PCIO((port))) \
- __asm__ __volatile__( \
- "strb %0, [%1, %2] @ outbc" \
- : : "r" (value), "r" (PCIO_BASE), "Jr" ((port)));\
- else \
- __asm__ __volatile__( \
- "strb %0, [%1, #0] @ outbc" \
- : : "r" (value), "r" ((port))); \
- })
- #define __inbc(port) \
- ({ \
- unsigned char result; \
- if (__PORT_PCIO((port))) \
- __asm__ __volatile__( \
- "ldrb %0, [%1, %2] @ inbc" \
- : "=r" (result) : "r" (PCIO_BASE), "Jr" ((port)));\
- else \
- __asm__ __volatile__( \
- "ldrb %0, [%1, #0] @ inbc" \
- : "=r" (result) : "r" ((port))); \
- result; \
- })
上面谈到的I/O操作都是一次传输一个数据,实际上内核也实现了对I/O端口的串操作,从速度上快了许多。不过在新版本的内核中,已经明确指出这些方法并不推荐使用,估计在新版本的内核中将被淘汰。
- #define insb(p,d,l) __raw_readsb(__ioaddr(p),d,l)
- #define insw(p,d,l) __raw_readsw(__ioaddr(p),d,l)
- #define insl(p,d,l) __raw_readsl(__ioaddr(p),d,l)
- #define outsb(p,d,l) __raw_writesb(__ioaddr(p),d,l)
- #define outsw(p,d,l) __raw_writesw(__ioaddr(p),d,l)
- #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文件中。
- #ifndef __arch_ioremap
- #define ioremap(cookie,size) __ioremap(cookie,size,0)
- #define ioremap_noCache(cookie,size) __ioremap(cookie,size,0)
- #define ioremap_Cached(cookie,size) __ioremap(cookie,size,L_PTE_CACHEABLE)
- #define iounmap(cookie) __iounmap(cookie)
- #else
- #define ioremap(cookie,size) __arch_ioremap((cookie),(size),0)
- #define ioremap_noCache(cookie,size) __arch_ioremap((cookie),(size),0)
- #define ioremap_Cached(cookie,size) __arch_ioremap((cookie),(size),L_PTE_CACHEABLE)
- #define iounmap(cookie) __arch_iounmap(cookie)
- #endif
所调用的__ioremap和__iounmap函数在arch\arm\mm\ioremap.c文件中实现。
- void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags)
- void __iounmap(void __iomem *addr)
phys_addr是要映射的起始的I/O地址,size是要映射的空间的大小,flags是要映射的I/O空间的和权限有关的标志。在将I/O内 存资源的物理地址映射成核心虚地址后,可以像读/写内存一样读/写I/O资源。下面的一组函数是在lib/iomap.c文件中定义的,其中参数addr 是经过ioremap映射的地址。
- unsigned int fastcall ioread8(void __iomem *addr) //从addr处读取1个字节
- unsigned int fastcall ioread16(void __iomem *addr)
- unsigned int fastcall ioread16be(void __iomem *addr)
- unsigned int fastcall ioread32(void __iomem *addr)
- unsigned int fastcall ioread32be(void __iomem *addr)
- void fastcall iowrite8(u8 val, void __iomem *addr)
- void fastcall iowrite16(u16 val, void __iomem *addr)
- void fastcall iowrite16be(u16 val, void __iomem *addr)
- void fastcall iowrite32(u32 val, void __iomem *addr)
- void fastcall iowrite32be(u32 val, void __iomem *addr)
下列一组函数是上述函数的重复版本,当必须在给定的I/O内存地址处读/写一系列值时使用。
- void fastcall ioread8_rep(void __iomem *addr, void *dst, unsigned long count)
- void fastcall ioread16_rep(void __iomem *addr, void *dst, unsigned long count)
- void fastcall ioread32_rep(void __iomem *addr, void *dst, unsigned long count)
- void fastcall iowrite8_rep(void __iomem *addr, const void *src, unsigned long count)
- void fastcall iowrite16_rep(void __iomem *addr, const void *src, unsigned long count)
- void fastcall iowrite32_rep(void __iomem *addr, const void *src, unsigned long count)
如果要对一块I/O内存进行操作,可以使用下面的宏。
- #define memset_io(c,v,l) _memset_io(__mem_pci(c),(v),(l))
- #define memcpy_fromio(a,c,l) _memcpy_fromio((a),__mem_pci(c),(l))
- #define memcpy_toio(c,a,l) _memcpy_toio(__mem_pci(c),(a),(l))
其中的三个函数在arm\kernel\io.c文件中实现,其功能注释得非常清楚。
- /*
- * Copy data from IO memory space to "real" memory space.
- * This needs to be optimized.
- *
- void _memcpy_fromio(void *to, const volatile void __iomem *from, size_t count)
- {
- unsigned char *t = to;
- while (count) {
- count--;
- *t = readb(from);
- t++;
- from++;
- }
- }
- /*
- * Copy data from "real" memory space to IO memory space.
- * This needs to be optimized.
- */
- void _memcpy_toio(volatile void __iomem *to, const void *from, size_t count)
- {
- const unsigned char *f = from;
- while (count) {
- count--;
- writeb(*f, to);
- f++;
- to++;
- }
- }
- /*
- * "memset" on IO memory space.
- * This needs to be optimized.
- */
- void _memset_io(volatile void __iomem *dst, int c, size_t count)
- {
- while (count) {
- count--;
- writeb(c, dst);
- dst++;
- }
- }