对技术执着
分类: LINUX
2015-03-14 14:19:28
原文地址:字符驱动之八IO访问(硬件操作) 作者:cyycyh
字符驱动之八IO访问(硬件操作)
2012-04-09
在进行IO访问之前,得先区分IO ports 和 IO mem 还有 IO 空间和内存空间。以下有些概念和总结来自别人的博客,觉得写得不错,就在这里跟大家共享一下,如果你觉得对你一种不尊敬,请您告诉我,我立即删除有关内容。
1、CPU是i386架构的情况
在i386系列的处理中,内存和外部IO是独立编址,也是独立寻址的。MEM的内存空间是32位可以寻址到4G,IO空间是16位可以寻址到64K。
在Linux内核中,访问外设上的IO Port必须通过IO Port的寻址方式。而访问IO Mem就比较罗嗦,外部MEM不能和主存一样访问,虽然大小上不相上下,可是外部MEM是没有在系统中注册的。访问外部IO MEM必须通过remap映射到内核的MEM空间后才能访问。
为了达到接口的同一性,内核提供了IO Port到IO Mem的映射函数。映射后IO Port就可以看作是IO Mem,按照IO Mem的访问方式即可。
2、CPU是ARM 或PPC架构的情况
在这一类的嵌入式处理器中,IO Port的寻址方式是采用内存映射,也就是IO bus就是Mem bus。系统的寻址能力如果是32位,IO Port+Mem(包括IO Mem)可以达到4G。
访问这类IO Port时,我们也可以用IO Port专用寻址方式。至于在对IO Port寻址时,内核是具体如何完成的,这个在内核移植时就已经完成。在这种架构的处理器中,仍然保持对IO Port的支持,完全是i386架构遗留下来的问题,在此不多讨论。而访问IO Mem的方式和i386一致。
注意:linux内核给我提供了完全对IO Port和IO Mem的支持,然而具体去看看driver目录下的驱动程序,很少按照这个规范去组织IO Port和IO Mem资源。对这二者访问最关键问题就是地址的定位,在C语言中,使用volatile 就可以实现。很多的代码访问IO Port中的寄存器时,就使用volatile关键字,虽然功能可以实现,我们还是不推荐使用。就像最简单的延时莫过于while,可是在多任务的系统中是坚决避免的!
3、
(1)内存空间:内存地址寻址范围,32位操作系统内存空间为2的32次幂,即4G。
(2)IO空间:X86特有的一个空间,与内存空间彼此独立的地址空间,32位X86有64K的IO空间。
(3)IO端口:当寄存器或内存位于IO空间时,称为IO端口。一般寄存器也俗称I/O端口,或者说I/O ports,这个I/O端口可以被映射在Memory Space,也可以被映射在I/O Space。
(4)IO内存:当寄存器或内存位于内存空间时,称为IO内存
4 、操作方法:
CPU对外设端口物理地址的编址方式有两种:一种是IO映射方式,另一种是内存映射方式,而具体采用哪一种则取决于CPU的体系结构。(Linux将基于IO映射方式的和内存映射方式的IO端口统称为IO区域(IO region)。
(1)所有的读写指令所赋的地址必须都是虚拟地址,你有两种选择:使用内核已 经定义好的地址,如 S3C2440_GPJCON等等,这些都是内核定义好的虚拟地址,有兴趣的可以看源码。还有一种方法就是使用自己用ioremap映射的虚拟地址。绝对不能使用实际的物理地址,否则会因为内核无法处理地址而出现oops。
(2)在使用I/O指令时,可以不使用request_region和request_mem_region,而直 接使用outb、ioread等指令。因为request的功能只是告诉内核端口被谁占用了,如再次request,内核会制止。
(3)在使用I/O指令时,所赋的地址数据有时必须通过强制类型转换为 unsigned long ,不然会有警告(具体原因请看-内核的数据类型) 。虽然你的程序可能 也可以使用,但是最好还是不要有警告为妙。
(4)在include\asm-arm\arch-s3c2410\hardware.h中定义了很多io口的操作函数 ,有需要可以在驱动中直接使用。【这些来自gzprogramming.blog.chinaunix.net 的博客】
【使用方法之一操作内存】
[NOTE]数据大部分都是无符号长整型
1、申请 【1、2主要将硬件配置和驱动分开,最大限度的提高驱动的使用效率】
2、映射
3、访问
4、释放==>申请之后得释放、映射之后也要解除,有先后顺序
【使用方法之二 IO PORT】
1、申请
2、访问
3、释放
【在访问IO之前一般都会进行屏障处理,以防被优化】
头文件:
#include
#include
[数据结构]
struct resource
{
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
【IO内存操作方法】
/* 【第一步申请IO内存】
* @brief 申请IO内存
* @param[in] start ??物理地址还是虚拟地址呢<测试过貌似无区别è在于IO映射与IO申请分开>
* @param[in] len 从起始地址开始的长度
* @param[in] name 一般为设备名称
* @return 成功返回一个非NULL指针,否则返回NULL
*/
[NOTE]I/O 内存分配情况都 /proc/iomem
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
/*【第二步:phy to vir 的映射】
* @brief 在访问I/O内存之前,必须进行物理地址到虚拟地址的映射
* @param[in] phys_addr 物理地址的起始地址
* @param[in] size 从起始地址开始的长度
* @return 返回内存的虚拟地址,为IO访问做好准备
*/
[NOTE]要强制类型转换(unsigned long)
void *ioremap(unsigned long phys_addr, unsigned long size)
[明确与硬件有关]
void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
/*【第三步:访问IO内存】
* @brief 访问I/O内存
* @param[in] addr 物理地址到虚拟地址的映射的虚拟地址
* @return 返回内存的实际值
*/【一般在访问IO内存之前都会进行屏蔽处理】
从 I/O 内存读,使用下列之一:
unsigned ioread8(void *vir_addr)
unsigned ioread16(void *vir_addr)
unsigned ioread32(void *vir_addr)
写I/O内存, 使用下列之一:
void iowrite8(u8 value, void *vir_addr)
void iowrite16(u16 value, void *vir_addr)
void iowrite32(u32 value, void *vir_addr)
//访问一系列
void ioread8_rep(void *addr, void *buf, unsigned long count);
void ioread16_rep(void *addr, void *buf, unsigned long count);
void ioread32_rep(void *addr, void *buf, unsigned long count);
void iowrite8_rep(void *addr, const void *buf, unsigned long count);
void iowrite16_rep(void *addr, const void *buf, unsigned long count);
void iowrite32_rep(void *addr, const void *buf, unsigned long count);
/*【第四步:释放IO内存】
* @brief 释放IO内存
* @param[in] phys_addr 物理地址的起始地址
* @param[in] len 从起始地址开始的长度
* @return no
*/
(1)申请IO资源要释放
void release_mem_region(unsigned long start, unsigned long len)
(2)映射之后的资源也要释放
void iounmap((void *)vir_addr);
【IO端口操作方法】
/*【第一步申请IO端口】
* @brief 申请IO端口
* @param[in] first 采用内核定义好的虚拟地址
* @param[in] len 从起始地址开始的长度
* @param[in] name 一般为设备名称
* @return 成功返回一个非NULL指针,否则返回NULL
*/
[NOTE]I/O 内存分配情况都 /proc/ioport
[NOTE]first必须采用虚拟地址,而非物理地址,其实S3C2440_GPJCON是内核定义好的虚拟地址。
struct resource *request_region(unsigned long first, unsigned
long n, const char *name)
/*【第二步:访问IO内存】
* @brief 访问I/O内存
* @param[in] port 内核帮忙写好的虚拟地址如S3C2440_GPJCON
* @return
*/
[NOTE]所赋的地址数据有时必须通过强制类型转换为 unsigned long
从 I/O 端口读,使用下列之一:
unsigned inb(unsigned port);
unsigned inw(unsigned port);
unsigned inl(unsigned port);
写 I/O 端口,使用下列之一:
void outb(unsigned char byte, unsigned port);
void outw(unsigned short word, unsigned port);
void outl(unsigned longword, unsigned port);
/* @brief 【第三步:释放IO端口】 */
void release_region(unsigned long start, unsigned long n);
5、小结
其实,IO端口访问也是一种IO内存访问,原因在于IO端口访问是内核在BSP(版及相关的时候)帮我们做了物理地址到虚拟地址的映射,从而我就可以直接使用,看起来貌似是实际地址,实际上不是,是虚拟地址,大家可以自己去宏S3C2440_GPJCON最终展开就是内存的虚拟地址。