免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1138 | 回复: 0
打印 上一主题 下一主题

io [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2011-12-21 08:41 |只看该作者 |倒序浏览
(function(){ var e = document.getElementById('blog-163-com-main'); if(!!e) e.parentNode.insertBefore(document.getElementById('blog-163-com-topbar'),e); e = document.getElementById('blog-notice-email'); if(!!e) window.setTimeout(function(){e.style.display='';},50); })()
   显示下一条  |  关闭
Galaxy的博客

乱写,什么时候才能强力

 
 
 
 
日志
 
 
<SCRIPT> function g_onSuggestedReadingImgLoad(_img){ var image = new Image(); image.src = _img.src; var _width = 90; var _height = 90; if(image.width > image.height){ _img.style.marginLeft = -Math.floor((image.width*(_height/image.height)-_width)/2)+'px'; _img.style.height = _height+'px'; }else { _img.style.width = _width+'px'; _img.style.marginTop = -Math.floor((image.height*(_width/image.width)-_height)/2)+'px'; } _img.style.display = 'block'; }
Galaxy
小脆·
  加博友   关注他
最新日志 该作者的其他文章
    博主推荐
      相关日志
        随机阅读
          首页推荐 对“推广广告”提建议

          引用 IO端口和IO内存

          默认分类 2010-10-12 21:16:21 阅读74 评论0   字号: 订阅

          本文引用自huzoy《IO端口和IO内存》

           

          引用

          huzoyIO端口和IO内存
          驱动程序编写过程中,很少会注意到IO PortIO Mem的区别。虽然使用一些不符合规范的代码可以达到最终目的,这是极其不推荐使用的。

          结合下图,我们彻底讲述IO端口和IO内存以及内存之间的关系。主存16M字节的SDRAM,外设是个视频采集卡,上面有16M字节的SDRAM作为缓冲区。

          IO端口和IO内存 - huzoy - mylearning

          1.      CPUi386架构的情况在i386系列的处理中,内存和外部IO是独立编址,也是独立寻址的。MEM的内存空间是32位可以寻址到4GIO空间是16位可以寻址到64K

          2.      Linux内核中,访问外设上的IO Port必须通过IO Port的寻址方式。而访问IO Mem就比较罗嗦,外部MEM不能和主存一样访问,虽然大小上不相上下,可是外部MEM是没有在系统中注册的。访问外部IO MEM必须通过remap映射到内核的MEM空间后才能访问。为了达到接口的同一性,内核提供了IO PortIO Mem的映射函数。映射后IO Port就可以看作是IO Mem,按照IO Mem的访问方式即可。

          3.      CPUARM PPC架构的情况

          在这一类的嵌入式处理器中,IO Port的寻址方式是采用内存映射,也就是IO bus就是Mem bus。系统的寻址能力如果是32位,IO PortMem(包括IO Mem)可以达到4G

          访问这类IO Port时,我们也可以用IO Port专用寻址方式。至于在对IO Port寻址时,内核是具体如何完成的,这个在内核移植时就已经完成。在这种架构的处理器中,仍然保持对IO Port的支持,完全是i386架构遗留下来的问题,在此不多讨论。而访问IO Mem的方式和i386一致。

          注意:linux内核给我提供了完全对IO PortIO Mem的支持,然而具体去看看driver目录下的驱动程序,很少按照这个规范去组织IO PortIO Mem资源。对这二者访问最关键问题就是地址的定位C语言中,使用volatile 就可以实现。很多的代码访问IO Port中的寄存器时,就使用volatile关键字,虽然功能可以实现,我们还是不推荐使用。就像最简单的延时莫过于while,可是在多任务的系统中是坚决避免的!

           

           RISC指令系统的CPU(如ARMPowerPC等)通常只实现一个物理地址空间,外设I/O端口成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。

          但是,这两者在硬件实现上的差异对于软件来说是完全透明的,驱动程序开发人员可以将内存映射方式的I/O端口和外设内存统一看作是"I/O内存"资源。

          一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址 预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核 心虚地址范围,通过访内指令访问这些I/O内存资源。Linuxio.h头文件中声明了函数ioremap(),用来将I/O内存资源的物理地址映射到 核心虚地址空间(3GB4GB)中,原型如下:

          void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);

          iounmap
          函数用于取消ioremap()所做的映射,原型如下:

          void iounmap(void * addr);

          这两个函数都是实现在mm/ioremap.c文件中。

          在将I/O内存资源的物理地址映射成核心虚地址后,理论上讲我们就可以象读写RAM那样直接读写I/O内存资源了。为了保证驱动程序的跨平台的可移植 性,我们应该使用Linux中特定的函数来访问I/O内存资源,而不应该通过指向核心虚地址的指针来访问。如在x86平台上,读写I/O的函数如下所示:

          #define readb(addr)   (*(volatile unsigned char *) __io_virt(addr))
          #define readw(addr)   (*(volatile unsigned short *) __io_virt(addr))
          #define readl(addr)    (*(volatile unsigned int *) __io_virt(addr))

          #define writeb(b,addr)   (*(volatile unsigned char *) __io_virt(addr) = (b))
          #define writew(b,addr)    (*(volatile unsigned short *) __io_virt(addr) = (b))
          #define writel(b,addr)    (*(volatile unsigned int *) __io_virt(addr) = (b))

          #define memset_io(a,b,c)   memset(__io_virt(a),(b),(c))
          #define memcpy_fromio(a,b,c)   memcpy((a),__io_virt(b),(c))
          #define memcpy_toio(a,b,c)    memcpy(__io_virt(a),(b),(c))

          最后,我们要特别强调驱动程序中mmap函数的实现方法。用mmap映射一个设备,意味着使用户空间的一段地址关联到设备内存上,这使得只要程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问。

          笔者在Linux源代码中进行包含"ioremap"文本的搜索,发现真正出现的ioremap的地方相当少。所以笔者追根索源地寻找I/O操作的物理地址转换到虚拟地址的真实所在,发现Linux有替代ioremap的语句,但是这个转换过程却是不可或缺的。

           

           

           

          CPU外设端口物理地址的编址方式有两种:

          一种是IO映射方式,另一种是内存映射方式。

           

            Linux将基于IO映射方式的和内存映射方式的IO端口统称为IO区域(IO region)。

           

            IO region仍然是一种IO资源,因此它仍然可以用resource结构类型来描述。

           

            Linux管理IO region

           

            1) request_region()

           

            把一个给定区间的IO端口分配给一个IO设备。

           

            2) check_region()

           

            检查一个给定区间的IO端口是否空闲,或者其中一些是否已经分配给某个IO设备。

           

            3) release_region()

           

            释放以前分配给一个IO设备的给定区间的IO端口。

           

            Linux中可以通过以下辅助函数来访问IO端口:

           

            inb(),inw(),inl(),outb(),outw(),outl()

           

            “b”“w”“l”分别代表8位,16位,32位。

           

          IO内存资源的访问

           

            1) request_mem_region()

           

            请求分配指定的IO内存资源。

           

            2) check_mem_region()

           

            检查指定的IO内存资源是否已被占用。

           

            3) release_mem_region()

           

            释放指定的IO内存资源。

           

            其中传给函数的start address参数是内存区的物理地址(以上函数参数表已省略)。

           

            驱动开发人员可以将内存映射方式的IO端口和外设内存统一看作是IO内存资源。

           

            ioremap()用来将IO资源的物理地址映射到内核虚地址空间(3GB - 4GB)中,参数addr是指向内核虚地址的指针。

           

            Linux中可以通过以下辅助函数来访问IO内存资源:

           

            readb(),readw(),readl(),writeb(),writew(),writel()

           

            Linuxkernel/resource.c文件中定义了全局变量ioport_resourceiomem_resource,来分别描述基于IO映射方式的整个IO端口空间和基于内存映射方式的IO内存资源空间(包括IO端口和外设内存)。

           

           

          内存映射(IO地址和内存地址)

          ARM体系结构下面内存和i/o映射区别
          1)关于IO与内存空间:
              X86处理器中存在着I/O空间的概念,I/O空间是相对于内存空间而言的,它通过特定的指令inout来访问。端口号标识了外设的寄存器地址Intel语法的inout指令格式为:
              IN 累加器, {端口号│DX}
              OUT {端口号│DX},累加器
              目前,大多数嵌入式微控制器如ARMPowerPC等中并不提供I/O空间,而仅存在内存空间。内存空间可以直接通过地址、指针来访问,程序和程序运行中使用的变量和其他数据都存在于内存空间中。
              即便是在X86处理器中,虽然提供了I/O空间,如果由我们自己设计电路板,外设仍然可以只挂接在内存空间。此时,CPU可以像访问一个内存单元那样访问外设I/O端口,而不需要设立专门的I/O指令。因此,内存空间是必须的,而I/O空间是可选的。
          2inboutb
          Linux设备驱动中,宜使用Linux内核提供的函数来访问定位于I/O空间的端口,这些函数包括:
          ·  读写字节端口(8位宽)
          unsigned inb(unsigned port);
          void outb(unsigned char byte, unsigned port);
          ·  读写字端口(16位宽)
          unsigned inw(unsigned port);
          void outw(unsigned short word, unsigned port);
          ·  读写长字端口(32位宽)
          unsigned inl(unsigned port);
          void outl(unsigned longword, unsigned port);
          ·  读写一串字节
          void insb(unsigned port, void *addr, unsigned long count);
          void outsb(unsigned port, void *addr, unsigned long count);
          ·  insb()从端口port开始读count个字节端口,并将读取结果写入addr指向的内存;outsb()addr指向的内存的count个字节连续地写入port开始的端口。
          ·  读写一串字
          void insw(unsigned port, void *addr, unsigned long count);
          void outsw(unsigned port, void *addr, unsigned long count);
          ·  读写一串长字
          void insl(unsigned port, void *addr, unsigned long count);
          void outsl(unsigned port, void *addr, unsigned long count);
          上述各函数中I/O端口号port的类型高度依赖于具体的硬件平台,因此,只是写出了unsigned
          3readbwriteb:
          在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是工程师宜使用Linux内核的如下一组函数来完成设备内存映射的虚拟地址的读写,这些函数包括:
          ·  I/O内存
          unsigned int ioread8(void *addr);
          unsigned int ioread16(void *addr);
          unsigned int ioread32(void *addr);
          与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
          unsigned readb(address);
          unsigned readw(address);
          unsigned readl(address);
          ·  I/O内存
          void iowrite8(u8 value, void *addr);
          void iowrite16(u16 value, void *addr);
          void iowrite32(u32 value, void *addr);
          与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
          void writeb(unsigned value, address);
          void writew(unsigned value, address);
          void writel(unsigned value, address);
          4)把I/O端口映射到“内存空间”:
          void *ioport_map(unsigned long port, unsigned int count);
          通过这个函数,可以把port开始的count个连续的I/O端口重映射为一段“内存空间”。然后就可以在其返回的地址上像访问I/O内存一样访问这些I/O端口。当不再需要这种映射时,需要调用下面的函数来撤消:
          void ioport_unmap(void *addr);
          实际上,分析ioport_map()的源代码可发现,所谓的映射到内存空间行为实际上是给开发人员制造的一个“假象”,并没有映射到内核虚拟地址,仅仅是为了让工程师可使用统一的I/O内存访问接口访问I/O端口。

          11.2.7 I/O
          空间的映射
          很多硬件设备都有自己的内存,通常称之为I/O空间。例如,所有比较新的图形卡都有几MBRAM,称为显存,用它来存放要在屏幕上显示的屏幕影像。
          1
          .地址映射
              根据设备和总线类型的不同,PC体系结构中的I/O空间可以在三个不同的物理地址范围之间进行映射:
          1)对于连接到ISA总线上的大多数设备
          I/O
          空间通常被映射到从0xa00000xfffff的物理地址范围,这就在640K1MB之间留出了一段空间,这就是所谓的“洞”。
          2)对于使用VESA本地总线(VLB)的一些老设备
              这是主要由图形卡使用的一条专用总线:I/O空间被映射到从0xe000000xffffff的地址范围中,也就是14MB16MB之间。因为这些设备使页表的初始化更加复杂,因此已经不生产这种设备。
          3)对于连接到PCI总线的设备
              I/O空间被映射到很大的物理地址区间,位于RAM物理地址的顶端。这种设备的处理比较简单。
          2
          .访问I/O空间
              内核如何访问一个I/O空间单元?让我们从PC体系结构开始入手,这个问题很容易就可以解决,之后我们再进一步讨论其他体系结构。
              不要忘了内核程序作用于虚拟地址,因此I/O空间单元必须表示成大于PAGE_OFFSET的地址。在后面的讨论中,我们假设PAGE_OFFSET等于0xc0000000,也就是说,内核虚拟地址是在第4G
              内核驱动程序必须把I/O空间单元的物理地址转换成内核空间的虚拟地址。PC体系结构中,这可以简单地把32位的物理地址和0xc0000000常量进行或运算得到。例如,假设内核需要把物理地址为0x000b0fe4I/O单元的值存放在t1中,把物理地址为0xfc000000I/O单元的值存放在t2中,就可以使用下面的表达式来完成这项功能:

              t1 = *((unsigned char *)(0xc00b0fe4));
              t2 = *((unsigned char *)(0xfc000000));

              在第六章我们已经介绍过,在初始化阶段,内核已经把可用的RAM物理地址映射到虚拟地址空间第4G的最初部分。因此,分页机制把出现在第一个语句中的虚拟地址0xc00b0fe4映射回到原来的I/O物理地址0x000b0fe4,这正好落在从640K1MB的这段“ISA洞”中。这正是我们所期望的。
              但是,对于第二个语句来说,这里有一个问题,因为其I/O物理地址超过了系统RAM的最大物理地址。因此,虚拟地址0xfc000000就不需要与物理地址0xfc000000相对应。在这种情况下,为了在内核页表中包括对这个I/O物理地址进行映射的虚拟地址,必须对页表进行修改:这可以通过调用ioremap( )函数来实现。ioremap( )vmalloc( )函数类似,都调用get_vm_area( ) 建立一个新的vm_struct描述符,其描述的虚拟地址区间为所请求I/O空间区的大小。然后,ioremap( )函数适当地更新所有进程的对应页表项。
          因此,第二个语句的正确形式应该为:

              io_mem = ioremap(0xfb000000, 0x200000);
              t2 = *((unsigned char *)(io_mem + 0x100000));

              第一条语句建立一个2MB的虚拟地址区间,从0xfb000000开始;第二条语句读取地址0xfc000000的内存单元。驱动程序以后要取消这种映射,就必须使用iounmap( )函数。

              现在让我们考虑一下除PC之外的体系结构。在这种情况下,把I/O物理地址加上0xc0000000常量所得到的相应虚拟地址并不总是正确的。为了提高内核的可移植性,Linux特意包含了下面这些宏来访问I/O空间:
              readb, readw, readl
             分别从一个I/O空间单元读取12或者4个字节
             writeb, writew, writel
             分别向一个I/O空间单元写入12或者4个字节
             memcpy_fromio, memcpy_toio
             把一个数据块从一个I/O空间单元拷贝到动态内存中,另一个函数正好相反,把一个数据块从动态内存中拷贝到一个I/O空间单元
             memset_io
             用一个固定的值填充一个I/O空间区域
          对于0xfc000000 I/O单元的访问推荐使用这样的方法:
              io_mem = ioremap(0xfb000000, 0x200000);
              t2 = readb(io_mem + 0x100000);
              使用这些宏,就可以隐藏不同平台访问I/O空间所用方法的差异。

           

           从本质上来说是一样的,IO端口在Linux驱动中是指IO端口的寄存器,通过操作寄存器来控制IO端口。而IO内存是指一些设备把IO寄存器映射到某个内存区域,因为访问内存就不要特殊的指令。

           

           

            评论这张
          转发至微博
          0  分享到:         
          阅读(74)| 评论(0)| 引用 (1) |举报
          历史上的今天
          相关文章
          最近读者
          登录后,您可以在此留下足迹。
          评论
          点击登录|昵称:
            取消
           
           
           
           
           
           
           
           
           
           
           
           
           
           
          页脚

          网易公司版权所有 ©1997-2011

          ×
          登录网易通行证
           

          欢迎通过百度搜索来到Galaxy的博客!

          注册登录后,你也可以拥有自己的个人博客,还可以和博友更好的交流。

          网易博客欢迎你的加入

          请输入登录信息

          用户名:
              密  码:

           
           
           
           
           
           
           
           
          window.N = {tm:{'zbtn':'nbtn', 'bdc0':'bdc0','bdc2':'bdc1', 'bgc0':'bgc0','bgc1':'bgc1','bgc2':'bgc2','bgh0':'bgc9', 'fc00':'fc03','fc01':'fc04','fc02':'fc05','fc03':'fc06','fc04':'fc07','fc05':'fc09'}}; Date.servTime = '05/09/2011 09:41:29'; location.api = 'http://api.blog.163.com/'; location.msg = 'http://api.blog.163.com/msg/dwr'; location.dwr = 'http://api.blog.163.com/simengru/dwr'; location.vcd = 'http://api.blog.163.com/cap/captcha.jpgx?parentId=54386860&r='; location.mrt = 'http://b.bst.126.net/newpage/style/mbox/'; location.fce = 'http://os.blog.163.com/common/ava.s?host='; location.fce2= 'http://os.blog.163.com/common/ava.s?a=1&t=3864&host='; location.passportfce = 'http://os.blog.163.com/common/ava.s?passport='; location.fpr = 'http://b.bst.126.net/common/portrait/face/preview/'; location.f60 = 'http://b.bst.126.net/common/face60.png'; location.f140= 'http://b.bst.126.net/common/face140.png'; location.adf140= 'http://b.bst.126.net/common/admireface140.png'; location.ept = 'http://b.bst.126.net/common/empty.png'; location.guide_profile_add= 'http://b.bst.126.net/common/guide_profile_add.gif'; location.phtoto_dream = 'http://photo.dream.163.com/blog/writeBlogCallback.do'; window.CF = { ca:false ,cb:'' ,cc:false ,cd:false ,ce:'-3' ,ck:0 ,ci:['api.blog.163.com' ,'http://photo.163.com/photo/html/crossdomain.html?t=20100205' ,'ud.blog.163.com' ] ,cj:[-3] ,cl:'' ,cm:["","blog/","album/","music/","collection/","friends/","profile/","pprank/"] ,cf:0 ,co:{pv:false ,ti:4293 ,tn:'' ,tc:0 ,tl:3 ,ut:0 ,un:'' ,um:'' ,ui:0 ,ud:false} ,cp:{nr:0 ,cr:0 ,vr:-100 ,fr:0} ,cs:0 ,ct:{'nav':['首页','日志','相册','音乐','收藏','博友','关于我'],'enabled':[0,1,6],'defaultnav':parseInt('1111111',2)} ,cu:false ,cv:false ,cw:false }; window.UD = {}; UD.host = { userId:54386860 ,userName:'simengru' ,nickName:'Galaxy' ,baseUrl:'http://simengru.blog.163.com/' ,gender:'他' ,email:'simengru@163.com' ,photo163Name:'simengru' ,photo163HostName:'simengru' ,TOKEN_HTMLMODULE:'' ,isMultiUserBlog:false };
          _ntes_nacc='blog';neteaseTracker(); new Image().src = 'http://blog.163.com/newpage/images/analyse.png?s=p&t='+new Date().getTime();
          您需要登录后才可以回帖 登录 | 注册

          本版积分规则 发表回复

            

          北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
          未成年举报专区
          中国互联网协会会员  联系我们:huangweiwei@itpub.net
          感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

          清除 Cookies - ChinaUnix - Archiver - WAP - TOP