精华内容
下载资源
问答
  • 几个常用IO端口读写函数

    千次阅读 2008-07-29 17:17:00
    DOS中,几个常用端口读写函数: int inport(int prot); //从指定的输入端口读入一个字,并返回这个字 int inportb(int port); //从指定的输入端口读入一个字节,并返回这个字节 void outport(int port,int word); //...
     DOS中,几个常用端口读写函数:
    
     
    int inport(int prot);     //从指定的输入端口读入一个字,并返回这个字
    int inportb(int port);  //从指定的输入端口读入一个字节,并返回这个字节
    void outport(int port,int word); //将字word写入指定的输出端口port
    void outportb(int port,char byte); //将字节byte写入指定的输出端口port


    这些功能可以通过宏定义实现:
    对于IO空间映射在存储空间的结构,输入输出处理
    #define inp(port) (*((volatile byte *)(port)))
    #define inpw(port) (*((volatile word *)(port)))
    #define inpdw(port) (*((volatile dword *)(port)))
    #define outp(port,val) (*((volatile byte *)(port))=((byte)(val)))
    #define outpw(port, val) (*((volatile word *)(port))=((word)(val)))
    #define outpdw(port, val) (*((volatile dword *)(port))=((dword)(val)))

    对于IO空间映射在存储空间的结构,输入输出处理
    #define inp(port)  (*((volatile byte *) (port)))

    dos下读端口。在dos下就是读内存,读内存就是取某一个地址的内容,比如读n端口就是读取内存n处的内容,
    (*((volatile  byte  *)  (port)))
    其实就是
    *(unsigned char *)port  
       

    在BC3.1自带的dos.h中,定义如下
    #ifndef _PORT_DEFS
    unsigned        _Cdecl inport ( unsigned __portid );
    unsigned char   _Cdecl inportb( unsigned __portid );
    unsigned        _Cdecl inpw   ( unsigned __portid );
    int             _Cdecl inp    ( unsigned __portid );
    #endif


    在VC的C运行库中,说明如下:
    Outputs a byte (_outp), a word (_outpw), or a double word (_outpd) at a port

    int _outp(
       unsigned short port,
       int databyte
    );
    unsigned short _outpw(
       unsigned short port,
       unsigned short dataword
    );
    unsigned long _outpd(
       unsigned short port,
       unsigned long dataword
    );

    VC中,outputb和inportb对应的函数为_inp和_outp。使用时需要<conio.h>头文件。建议使用_inp和 _outp,而不是使用inp和outp。因为_inp和_outp在Debug和Release都可以使用,而inp和outp只能用于Release 设置。如果在 Debug设置时使用inp和outp,编译时不会错,但会产生链接错误如下:
         error LINK2001:Unresolved external symbol _inp
         error LINK2001:Unresolved external symbol _outp
         fatal error LNK1120:2 Unresolved externals.
    以上方法只适用于Win9x,在Windows NT/2000下任何硬件I/O操作均需要通过设备启动程序。

    可以借助DDK(http://www.microsoft.com/ddk)编写设备驱动程序。当然你也可以购买现成的软件,如http: //china.zealsoft.com/cn/ntport/。Windows的开发同DOS开发有很大的不同。如果你设计的是控制台程序,那么整个 程序运行起来更DOS程序差不多,这时你可以象在 DOS中一样使用标准输出函数。如果你设计的是图形界面的程序,那么所有C和C++的标准输入/输出函数都不再能使用了。可以使用TextOut或 DrawText输出文字。

    展开全文
  • CPU地址空间,IO端口IO内存

    千次阅读 2016-12-11 16:23:59
    在程序指令中的虚拟地址经过段映 射和页面映射后,就生成了物理地址,这物理地址被放到CPU的地址线上。  物理地址空间,部分给物理RAM(内存)用,部分给总线用,这是由硬件设计来决定的,因此在32 bits地址...

    1)物理地址:CPU地址总线传来的地址,由硬件 电路控制其具体含义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。在程序指令中的虚拟地址经过段映 射和页面映射后,就生成了物理地址,这个物理地址被放到CPU的地址线上。
            物理地址空间,一部分给物理RAM(内存)用,一部分给总线用,这是由硬件设计来决定的,因此在32 bits地址线的x86处理器中,物理地址空间是2的32次方,即4GB,但物理RAM一般不能上到4GB,因为还有一部分要给总线用(总线上还挂着别的 许多设备)。在PC机中,一般是把低端物理地址给RAM用,高端物理地址给总线用。
     
    2)总线地址:总线的地址线或在地址周期上产生的信号。外设使用的是总线地址,CPU使用的是物理地址。
            物理地址与总线地址之间的关系由系统的设计决定的。在x86平台上,物理地址就是总线地址,这是因为它们共享相同的地址空间——这句话有点难理解,详见下 面的“独立编址”。在其他平台上,可能需要转换/映射。比如:CPU需要访问物理地址是0xfa000的单元,那么在x86平台上,会产生一个PCI总线 上对0xfa000地址的访问。因为物理地址和总线地址相同,所以凭眼睛看是不能确定这个地址是用在哪儿的,它或者在内存中,或者是某个卡上的存储单元, 甚至可能这个地址上没有对应的存储器。

    3)虚拟地址:现代操作系统普遍采用虚拟内存管理(Virtual Memory Management)机制,这需要MMU(Memory Management Unit)的支持。MMU通常是CPU的一部分,如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被 内存芯片(物理内存)接收,这称为物理地址(Physical A
    ddress),如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。
            Linux中,进程的4GB(虚拟)内存分为用户空间、内核空间。用户空间分布为0~3GB(即PAGE_OFFSET,在0X86中它等于0xC0000000)
    ,剩下的1G为内核空间。程序员只能使用虚拟地址。系统中每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。
            CPU发出取指令请求时的地址是当前上下文的虚拟地址,MMU再从页表中找到这个虚拟地址的物理地址,完成取指。同样读取数据的也是虚拟地址,比如mov ax, var. 编译时var就是一个虚拟地址,也是通过MMU从也表中来找到物理地址,再产生总线时序,完成取数据的。

    4)地址空间映射
     这 里要说的是Intel构架下的CPU地址空间布局,注意这里没有说是内存地址空间布局。 我们说的内存通常是指DRAM,DRAM相对于CPU也可以算是外部设备,CPU地址空间是CPU访问外部设备过程中的一个概念,CPU除了访问DRAM 外还会访问许多其他的设备。可以粗略的认为CPU地址空间包含DRAM地址空间,但两者却是不同的概念。而且DRAM地址空间是由内存控制器直接访问的, 由CPU间接访问的。 过去很长一段时间Intel CPU是32位的,也就是可以访问到4GB的地址空间,但是当时的DRAM通常也就是512MB到2GB之间,现在假设DRAM是1GB,那么就是3GB 的地址空间是空的。 在计算机里面,地址也是资源。这空的地址空间就用来访问外部设备IO所用,这部分被称为MMIO(Memory Mapped I/O)。MMIO的空间是很大的,它包含了PCI的配置空间(256MB或者更大),内置集成显存(256MB,或者更大),还有其他很多东西 。所以这部分的大小是不容忽视的。
       以上主要说明:假设CPU是32位(i386),其可访问的地址范围大小是4G地址空间,在X86平台上,物理地址和总线地址共享这4G地址空间(物理地址就是总线地址), 但物理RAM一般不能上到4GB,因为还有一部分要给总线用(总线上还挂着别的许多设备), 假设RAM是1G,那么3G的地址空间就是空的,而在计算机里面,地址也是资源,这空的地址空间就用来访问外部设备IO资源所用,及产生内存映射时就用到这段地址空间。

    几乎每一种外设都是通过读写设备上的寄存器来进行的,通常包括控制寄存器、状态寄存器和数据寄存器三大类,外设的寄存器通常被连续地编址。根据CPU体系结构的不同,CPU对IO端口的编址方式有两种:

    (1)I/O映射方式(I/O-mapped)

      典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。   

    (2)内存映射方式(Memory-mapped)

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

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

       一般来说,在系统运行时,外设的I/O内存资源的物理地址是已知的(通过request_mem_region()),由硬件的设计决定。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址 预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内(通过页表),然后才能根据映射所得到的核 心虚地址范围,通过访内指令访问这些I/O内存资源。Linux在io.h头文件中声明了函数ioremap(),(在内核驱动程序的初始化阶段,通过ioremap()将物理地址映射到内核虚拟空间(3GB-4GB);在驱动程序的mmap系统调用中,使用remap_page_range()将该块ROM映射到用户虚拟空间。这样内核空间和用户空间都能访问这段被映射后的虚拟地址。)

    4GB以下的地址空间的布局情况

    同样的图中红色字体部分为寄存器,这些寄存器与地址空间布局有着密切的关系。这 些寄存器的详细说明可以参考spec。比如说“Egress Port Registers”这个4KB的窗口,会根据EPBAR的设置被放置到MMIO的任意一个DMI Interface的位置,但让该窗口不能与其他任何窗口重叠。

     

           另外我也将我所了解的情况说明一下。


          1.先看TOLUD-4GB的位置,可以看到有几处都是DMI Interface(Subtractive Decode)。DMI是南桥与北桥的接口,访问DMI,也就是访问南桥。

          另外要解释的是Substactive decode,在计算机中地址译码有三种形式,当主设备通过指定地址访问总线上的从设备,一个是Positive decode,有从设备解码后发现是访问自己的,于是它就会响应,否则就没有从设备响应;一个是Negative decode,从设备收到该地址经解码后发现不属于自己的地址范围,从设备就转发出去;一个是Subtractive decode,在4个时钟周期内没有从设备响应,该地址就会发送到扩展的总线上面解码。

          

                              引自:http://www.microsoft.com/whdc/system/bus/pci/default.mspx

           DMI Interface(Subtractive Decode)的意思就是CPU发送一地址先到北桥上解码,如果该地址没有北桥上的设备占用,那么就用该地址就会被传送到南桥上解码,,也就是访问南桥上 的设备。可以假想为一开始4GB空间都是DMI Interface(Subtractive Decode),然后0-TOLUD被DRAM声明占用,TOLUD-4GB也纷纷被各种设备占用,于是就剩下了支离破碎的几个DMI Interface。(目前看上去这样理解是通顺的,但我希望它也是正确的)。

    IO PortIO Mem的区别

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

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

    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寄存器映射到某个内存区域,因为访问内存就不要特殊的指令。

    展开全文
  • io端口io内存

    千次阅读 2012-01-19 21:10:37
    驱动程序编写过程中,很少会注意到IO Port和IO Mem...主存16M字节的SDRAM,外设是视频采集卡,上面16M字节的SDRAM作为缓冲区。 1.  CPU是i386架构的情况在i386系列的处理中,内存和外部IO是独立编址,也是
    驱动程序编写过程中,很少会注意到IO PortIO Mem的区别。虽然使用一些不符合规范的代码可以达到最终目的,这是极其不推荐使用的。 
    

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


    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.     CPUARMPPC架构的情况

    在这一类的嵌入式处理器中,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 空间是相对于内存空间而言的,它通过特定的指令 in out 来访问。端口号标识了外设的寄存器地址 Intel 语法的 in out 指令格式为:
    IN 累加器 , { 端口号│ DX}
    OUT { 端口号│ DX}, 累加器
    目前,大多数嵌入式微控制器如 ARM PowerPC 等中并不提供 I/O 空间,而仅存在内存空间。 内存空间可以直接通过地址、指针来访问,程序和程序运行中使用的变量和其他数据都存在于内存空间中。
    即便是在 X86 处理器中,虽然提供了 I/O 空间,如果由我们自己设计电路板,外设仍然可以只挂接在内存空间。此时, CPU 可以像访问一个内存单元那样访问外设 I/O 端口,而不需要设立专门的 I/O 指令。因此,内存空间是必须的,而 I/O 空间是可选的。
    2 inb outb
    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
    3 readb writeb:
    在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是工程师宜使用 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 空间。 例如,所有比较新的图形卡都有几 MB RAM ,称为显存,用它来存放要在屏幕上显示的屏幕影像。
    1
    .地址映射
    根据设备和总线类型的不同, PC 体系结构中的 I/O 空间可以在三个不同的物理地址范围之间进行映射:
    1 )对于连接到 ISA 总线上的大多数设备
    I/O
    空间通常被映射到从 0xa0000 0xfffff 的物理地址范围,这就在 640K 1MB 之间留出了一段空间,这就是所谓的“洞”。
    2 )对于使用 VESA 本地总线( VLB )的一些老设备
    这是主要由图形卡使用的一条专用总线: I/O 空间被映射到从 0xe00000 0xffffff 的地址范围中,也就是 14MB 16MB 之间。因为这些设备使页表的初始化更加复杂,因此已经不生产这种设备。
    3 )对于连接到 PCI 总线的设备
    I/O 空间被映射到很大的物理地址区间,位于 RAM 物理地址的顶端。这种设备的处理比较简单。
    2
    .访问 I/O 空间
    内核如何访问一个 I/O 空间单元? 让我们从 PC 体系结构开始入手,这个问题很容易就可以解决,之后我们再进一步讨论其他体系结构。
    展开全文
  • IO端口IO内存

    千次阅读 2016-09-04 10:41:13
    从CPU连出来一把线:数据总线、地址总线、控制总线,这把线上挂着N个接口,相同的,不同的,名字叫做存储器接口、中断控制接口、DMA接口、并行接口、串行接口、AD接口……一个设备要想接入,就用自己的接口和...

    从CPU连出来一把线:数据总线、地址总线、控制总线,这把线上挂着N个接口,有相同的,有不同的,名字叫做存储器接口、中断控制接口、DMA接口、并行接口、串行接口、AD接口……一个设备要想接入,就用自己的接口和总线上的某个匹配接口对接……于是总线上出现了各种设备:内存、硬盘,鼠标、键盘,显示器……
    对于CPU而言,如果它要发数据到某个设备,其实是发到对应的接口,接口电路里有多个寄存器(也称为端口),访问设备实际上是访问相关的端口,所有的信息会由接口转给它的设备。那么CPU会准备数据到数据总线,但是诸多接口,该发给谁呢?这时就须要为各接口分配一个地址,然后把地址放在地址总线上,需要的控制信息放到控制总线上,就可以和设备通信了。
      对一个系统而言,通常会有多个外设,每个外设的接口电路中,又会有多个端口,每个端口都需要一个地址,为他们标识一个具体的地址值,是系统必须解决的事,与此同时,你还有个内存条,可能是512M或1G或更大的金士顿、现代DDR2之类,他们的每一个地址也都需要分配一个标识值,另外,很多外设有自己的内存、缓冲区,就像你的内存条一样,你同样需要为它们分配内存……你的CPU可能需要和它们的每一个字节都打交道,所以:别指望偷懒,它们的每一寸土地都要规划好!这听起来就很烦,做起来可能就直接导致脑细胞全部阵亡。但事情总是得有人去做,ARM可能会这样做:他这次设计的CPU是32位的,最多也就能寻址2^32=4G空间,于是把这4GB空间丢给内存和端口,让他们瓜分。但英特尔或许有更好的分配方式……

    1、地址的概念
    1)物理地址:CPU地址总线传来的地址,由硬件电路控制其具体含义。物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存、BIOS等)。在程序指令中的虚拟地址经过段映射和页面映射后,就生成了物理地址,这个物理地址被放到CPU的地址线上。
    物理地址空间,一部分给物理RAM(内存)用,一部分给总线用,这是由硬件设计来决定的,因此在32 bits地址线的x86处理器中,物理地址空间是2的32次方,即4GB,但物理RAM一般不能上到4GB,因为还有一部分要给总线用(总线上还挂着别的许多设备)。在PC机中,一般是把低端物理地址给RAM用,高端物理地址给总线用。 
    2)总线地址:总线的地址线或在地址周期上产生的信号。外设使用的是总线地址,CPU使用的是物理地址。
    物理地址与总线地址之间的关系由系统的设计决定的。在x86平台上,物理地址就是总线地址,这是因为它们共享相同的地址空间——这句话有点难理解,详见下面的“独立编址”。在其他平台上,可能需要转换/映射。比如:CPU需要访问物理地址是0xfa000的单元,那么在x86平台上,会产生一个PCI总线上对0xfa000地址的访问。因为物理地址和总线地址相同,所以凭眼睛看是不能确定这个地址是用在哪儿的,它或者在内存中,或者是某个卡上的存储单元,甚至可能这个地址上没有对应的存储器。

    3)虚拟地址:现代操作系统普遍采用虚拟内存管理(Virtual Memory Management)机制,这需要MMU(Memory Management Unit)的支持。MMU通常是CPU的一部分,如果处理器没有MMU,或者有MMU但没有启用,CPU执行单元发出的内存地址将直接传到芯片引脚上,被内存芯片(物理内存)接收,这称为物理地址(Physical Address),如果处理器启用了MMU,CPU执行单元发出的内存地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址。

      Linux中,进程的4GB(虚拟)内存分为用户空间、内核空间。用户空间分布为0~3GB(即PAGE_OFFSET,在0X86中它等于0xC0000000),剩下的1G为内核空间。程序员只能使用虚拟地址。系统中每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。
      CPU发出取指令请求时的地址是当前上下文的虚拟地址,MMU再从页表中找到这个虚拟地址的物理地址,完成取指。同样读取数据的也是虚拟地址,比如mov ax, var. 编译时var就是一个虚拟地址,也是通过MMU从也表中来找到物理地址,再产生总线时序,完成取数据的。

    2、编址方式
    1)外设都是通过读写设备上的寄存器来进行的,外设寄存器也称为“I/O端口”,而IO端口有两种编址方式:独立编址和统一编制。
      统一编址:外设接口中的IO寄存器(即IO端口)与主存单元一样看待,每个端口占用一个存储单元的地址,将主存的一部分划出来用作IO地址空间,如,在PDP-11中,把最高的4K主存作为IO设备寄存器地址。端口占用了存储器的地址空间,使存储量容量减小。
      统一编址也称为“I/O内存”方式,外设寄存器位于“内存空间”(很多外设有自己的内存、缓冲区,外设的寄存器和内存统称“I/O空间”)。
      如,Samsung的S3C2440,是32位ARM处理器,它的4GB地址空间被外设、RAM等瓜分:
        0x8000 1000 LED 8*8点阵的地址
        0x4800 0000 ~ 0x6000 0000 SFR(特殊暂存器)地址空间
        0x3800 1002 键盘地址
        0x3000 0000 ~ 0x3400 0000 SDRAM空间 
        0x2000 0020 ~ 0x2000 002e IDE
        0x1900 0300 CS8900

      独立编址(单独编址):IO地址与存储地址分开独立编址,I/O端口地址不占用存储空间的地址范围,这样,在系统中就存在了另一种与存储地址无关的IO地址,CPU也必须具有专用与输入输出操作的IO指令(IN、OUT等)和控制逻辑。独立编址下,地址总线上过来一个地址,设备不知道是给IO端口的、还是给存储器的,于是处理器通过MEMR/MEMW和IOR/IOW两组控制信号来实现对I/O端口和存储器的不同寻址。如,intel 80x86就采用单独编址,CPU内存和I/O是一起编址的,就是说内存一部分的地址和I/O地址是重叠的。
      独立编址也称为“I/O端口”方式,外设寄存器位于“I/O(地址)空间”。
      对于x86架构来说,通过IN/OUT指令访问。PC架构一共有65536个8bit的I/O端口,组成64K个I/O地址空间,编号从0~0xFFFF,有16位,80x86用低16位地址线A0-A15来寻址。连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个32bit的端口。I/O地址空间和CPU的物理地址空间是两个不同的概念,例如I/O地址空间为64K,一个32bit的CPU物理地址空间是4G。如,在Intel 8086+Redhat9.0 下用“more /proc/ioports”可看到:
        0000-001f : dma1
        0020-003f : pic1
        0040-005f : timer
        0060-006f : keyboard
        0070-007f : rtc
        0080-008f : dma page reg
        00a0-00bf : pic2
        00c0-00df : dma2
        00f0-00ff : fpu
        0170-0177 : ide1
        ……
      不过Intel x86平台普通使用了名为内存映射(MMIO)的技术,该技术是PCI规范的一部分,IO设备端口被映射到内存空间,映射后,CPU访问IO端口就如同访问内存一样。看Intel TA 719文档给出的x86/x64系统典型内存地址分配表:
        系统资源 占用
    ------------------------------------------------------------------------
        BIOS 1M
        本地APIC 4K
        芯片组保留 2M
        IO APIC 4K
        PCI设备 256M
        PCI Express设备 256M
        PCI设备(可选) 256M
        显示帧缓存 16M
        TSEG 1M

      对于某一既定的系统,它要么是独立编址、要么是统一编址,具体采用哪一种则取决于CPU的体系结构。 如,PowerPC、m68k等采用统一编址,而X86等则采用独立编址,存在IO空间的概念。目前,大多数嵌入式微控制器如ARM、PowerPC等并不提供I/O空间,仅有内存空间,可直接用地址、指针访问。但对于Linux内核而言,它可能用于不同的CPU,所以它必须都要考虑这两种方式,于是它采用一种新的方法,将基于I/O映射方式的或内存映射方式的I/O端口通称为“I/O区域”(I/O region),不论你采用哪种方式,都要先申请IO区域:request_resource(),结束时释放它:release_resource()。

    二、linux I/O端口与I/O内存

    IO端口:当一个寄存器或者内存位于IO空间时;
    IO内存:当一个内存或者寄存器位于内存空间时;

      在一些CPU制造商在其芯片上实现了一个单地址空间(统一编址)的同时,其它的CPU制造商认为外设不同于内存,应该有一个独立的地址空间给外设(单独编址),其生产处理器(特别是x86家族)的I/O端口有自己的读写信号线和特殊的CPU指令来存取端口。因为外设要与外设总线相匹配,并且大部分流行的I/O总线都是以个人计算机(主要是x86家族)作为模型,所以即便那些没有单独地址空间给I/O端口的处理器,也必须在访问外设时模拟成读写端口。这通常通过外部芯片组(PC中的南北桥)或者在CPU核中附加额外电路来实现(基于嵌入式应用的处理器)。
      由于同样的理由,Linux在所有计算机平台上都实现了I/O端口,甚至在那些单地址空间的CPU平台上(模拟I/O端口)。但并不是所有的设备都会将其寄存器映射到I/O端口。虽然ISA设备普遍使用I/O端口,但大部分PCI设备将寄存器映射到某个内存地址区。这种I/O内存方法通常是首选的,因为它无需使用特殊的处理器指令,CPU存取内存也更有效率,并且编译器在存取内存时在寄存器分配和寻址模式的选择上有更多自由。

     

    1.IO寄存器和常规内存
      I/O寄存器和RAM的主要不同是I/O操作有边际效应(side effect),而内存操作没有:访问内存只是在内存某一位置存储数值。因为内存存取速度严重影响CPU的性能,编译器可能会对源码进行优化,主要是:使用高速缓存和重排读/写指令的顺序。对于传统内存(至少在单处理器系统)这些优化是透明有益的,但是对于I/O 寄存器,这可能是致命错误,因为它们干扰了那些"边际效应"(驱动程序存取I/O 寄存器就是为了获取边际效应)。因此,驱动程序必须确保在存取寄存器时,不能使用高速缓存并且不能重新编排读写指令的顺序。
      side effect 是指:访问I/O寄存器时,不仅仅会像访问普通内存一样影响存储单元的值,更重要的是它可能改变CPU的I/O端口电平、输出时序或CPU对I/O端口电平的反应等等,从而实现CPU的控制功能。CPU在电路中的意义就是实现其side effect 。举个例子,有些设备的中断状态寄存器只要一读取,便自动清零。

      硬件缓冲的问题是最易解决的:只要将底层硬件配置(或者自动地或者通过Linux 初始化代码)为当存取I/O区时,禁止任何硬件缓冲(不管是I/O 内存还是I/O 端口)。

      编译器优化和硬件重编排读写指令顺序的解决方法是:在硬件或处理器必须以一个特定顺序执行的操作之间安放一个内存屏障(memory barrier)。

    2.操作IO端口(申请,访问,释放)
      I/O 端口是驱动用来和很多设备通讯的方法。
    (1)申请I/O 端口
      在驱动还没独占设备之前,不应对端口进行操作。内核提供了一个注册接口,以允许驱动声明其需要的端口:

    /* request_region告诉内核:要使用first开始的n个端口。参数name为设备名。如果分配成功返回值是非NULL;否则无法使用需要的端口(/proc/ioports包含了系统当前所有端口的分配信息,若request_region分配失败时,可以查看该文件,看谁先用了你要的端口) */
    struct resource *request_region(unsigned long first, unsigned long n, const char *name);

     

    (2)访问IO端口:

      在驱动成功请求到I/O 端口后,就可以读写这些端口了。大部分硬件会将8位、16位和32位端口区分开,无法像访问内存那样混淆使用。驱动程序必须调用不同的函数来访问不同大小的端口。
      Linux 内核头文件(体系依赖的头文件<asm/io.h>) 定义了下列内联函数来存取I/O端口:

    复制代码
    /* inb/outb:读/写字节端口(8位宽)。有些体系将port参数定义为unsigned long;而有些平台则将它定义为unsigned short。inb的返回类型也是依赖体系的 */
    unsigned inb(unsigned port);
    void outb(unsigned char byte, unsigned port);
    
    /* inw/outw:读/写字端口(16位宽) */
    unsigned inw(unsigned port);
    void outw(unsigned short word, unsigned port);
    
    /* inl/outl:读/写32位端口。longword也是依赖体系的,有的体系为unsigned long;而有的为unsigned int */
    unsigned inl(unsigned port);
    void outl(unsigned longword, unsigned port);
    复制代码

     

    (3)释放IO端口:

    /* 用完I/O端口后(可能在模块卸载时),应当调用release_region将I/O端口返还给系统。参数start和n应与之前传递给request_region一致 */
    void release_region(unsigned long start, unsigned long n);

     

    3.操作IO内存(申请,映射,访问,释放):
      尽管 I/O 端口在x86世界中非常流行,但是用来和设备通讯的主要机制是通过内存映射的寄存器和设备内存,两者都称为I/O 内存,因为寄存器和内存之间的区别对软件是透明的。
      I/O 内存仅仅是一个类似于RAM 的区域,处理器通过总线访问该区域,以实现对设备的访问。同样,读写这个区域是有边际效应。
      根据计算机体系和总线不同,I/O 内存可分为可以或者不可以通过页表来存取。若通过页表存取,内核必须先重新编排物理地址,使其对驱动程序可见,这就意味着在进行任何I/O操作之前,你必须调用ioremap;如果不需要页表,I/O内存区域就类似于I/O端口,你可以直接使用适当的I/O函数读写它们。
      由于边际效应的缘故,不管是否需要 ioremap,都不鼓励直接使用I/O内存指针,而应使用专门的I/O内存操作函数。这些I/O内存操作函数不仅在所有平台上是安全,而且对直接使用指针操作 I/O 内存的情况进行了优化。

    (1)申请I/O 内存: 
      I/O 内存区在使用前必须先分配。分配内存区的函数接口在<linux/ioport.h>定义中:

    /* request_mem_region分配一个开始于start,len字节的I/O内存区。分配成功,返回一个非NULL指针;否则返回NULL。系统当前所有I/O内存分配信息都在/proc/iomem文件中列出,你分配失败时,可以看看该文件,看谁先占用了该内存区 */
    struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

     

    (2)映射:
      在访问I/O内存之前,分配I/O内存并不是唯一要求的步骤,你还必须保证内核可存取该I/O内存。访问I/O内存并不只是简单解引用指针,在许多体系中,I/O 内存无法以这种方式直接存取。因此,还必须通过ioremap 函数设置一个映射。

    /* ioremap用于将I/O内存区映射到虚拟地址。参数phys_addr为要映射的I/O内存起始地址,参数size为要映射的I/O内存的大小,返回值为被映射到的虚拟地址 */
    void *ioremap(unsigned long phys_addr, unsigned long size);

     

    (3)访问IO内存:
      经过 ioremap之后,设备驱动就可以存取任何I/O内存地址。注意,ioremap返回的地址不可以直接解引用;相反,应当使用内核提供的访问函数。访问I/O内存的正确方式是通过一系列专门用于实现此目的的函数:

    复制代码
    #include <asm/io.h>
    /* I/O内存读函数。参数addr应当是从ioremap获得的地址(可能包含一个整型偏移); 返回值是从给定I/O内存读取到的值 */
    unsigned int ioread8(void *addr);
    unsigned int ioread16(void *addr);
    unsigned int ioread32(void *addr);
    
    /* I/O内存写函数。参数addr同I/O内存读函数,参数value为要写的值 */
    void iowrite8(u8 value, void *addr);
    void iowrite16(u16 value, void *addr);
    void iowrite32(u32 value, void *addr);
    
    /* 以下这些函数读和写一系列值到一个给定的 I/O 内存地址,从给定的buf读或写count个值到给定的addr。参数count表示要读写的数据个数,而不是字节大小 */
    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,,onst void *buf,,nsigned long count);
    
    /* 需要操作一块I/O 地址时,使用下列函数(这些函数的行为类似于它们的C库类似函数): */
    void memset_io(void *addr, u8 value, unsigned int count);
    void memcpy_fromio(void *dest, void *source, unsigned int count);
    void memcpy_toio(void *dest, void *source, unsigned int count);
    
    /* 旧的I/O内存读写函数,不推荐使用 */
    unsigned readb(address);
    unsigned readw(address);
    unsigned readl(address); 
    void writeb(unsigned value, address);
    void writew(unsigned value, address);
    void writel(unsigned value, address);
    复制代码

     

    (4)释放IO内存步骤:

    void iounmap(void * addr); /* iounmap用于释放不再需要的映射 */
    void release_mem_region(unsigned long start, unsigned long len); /* iounmap用于释放不再需要的映射 */

     

    4、像IO内存一样使用端口

      一些硬件有一个有趣的特性: 有些版本使用 I/O 端口;而有些版本则使用 I/O 内存。不管是I/O 端口还是I/O 内存,处理器见到的设备寄存器都是相同的,只是访问方法不同。为了统一编程接口,使驱动程序易于编写,2.6 内核提供了一个ioport_map函数:

    /* ioport_map重新映射count个I/O端口,使它们看起来I/O内存。此后,驱动程序可以在ioport_map返回的地址上使用ioread8和同类函数。这样,就可以在编程时,消除了I/O 端口和I/O 内存的区别 */
    void *ioport_map(unsigned long port, unsigned int count);
    
    void ioport_unmap(void *addr);/* ioport_unmap用于释放不再需要的映射 */

    注意,I/O 端口在重新映射前必须使用request_region分配分配所需的I/O 端口。

     

    5、ARM体系的IO操作接口
      s3c24x0处理器使用的是I/O内存,也就是说:s3c24x0处理器使用统一编址方式,I/O寄存器和内存使用的是单一地址空间,并且读写I/O寄存器和读写内存的指令是相同的。所以推荐使用I/O内存的相关指令和函数。但这并不表示I/O端口的指令在s3c24x0中不可用。如果你注意过s3c24x0关于I/O方面的内核源码,你就会发现:其实I/O端口的指令只是一个外壳,内部还是使用和I/O内存一样的代码。注意以下几点:
      1)所有的读写指令(I/O操作函数)所赋的地址必须都是虚拟地址,你有两种选择:使用内核已经定义好的地址,如在include/asm-arm/arch-s3c2410/regs-xxx.h中定义了s3c2410处理器各外设寄存器地址(其他处理器芯片也可在类似路径找到内核定义好的外设寄存器的虚拟地址;另一种方法就是使用自己用ioremap映射的虚拟地址。绝对不能使用实际的物理地址,否则会因为内核无法处理地址而出现oops。
      2)在使用I/O指令时,可以不使用request_region和request_mem_region,而直接使用outb、ioread等指令。因为request的功能只是告诉内核端口被谁占用了,如再次request,内核会制止(资源busy)。但是不推荐这么做,这样的代码也不规范,可能会引起并发问题(很多时候我们都需要独占设备)。
      3)在使用I/O指令时,所赋的地址数据有时必须通过强制类型转换为 unsigned long ,不然会有警告。
      4)在include\asm-arm\arch-s3c2410\hardware.h中定义了很多io口的操作函数,有需要可以在驱动中直接使用,很方便。

    展开全文
  • IO端口IO内存简介

    2012-08-28 13:16:05
    通常,一个外设会几个寄存器,并且这些寄存器的存取地址是连续的。这些存取地址要么处于内存地址空间,要么属于IO地址空间。  从纯硬件的角度来看,内存区域和IO区域没有概念上的区别。它们都是通过在地址总线...
  • 一个简单的IOCP(IO完成端口)服务器/客户端类 ——A simple IOCP Server/Client Class By spinoza 原文【选自CodeProject】 http://www.codeproject.com/Articles/10330/A-simple-IOCP-Server-Client-Class 源代码:...
  • IO完成端口与线程池

    千次阅读 2012-06-06 13:11:01
    具体情况参考《windows核心编程》第五版的第十章第十章 (Vista以上) MSDN示例http://msdn.microsoft.com/en-us/library/windows/desktop/ms686980(v=vs.85).aspx IO完成端口 1.创建空的IO完成端口,...
  • STM32端口IO方向设置问题

    千次阅读 2020-04-27 10:34:39
    STM32端口IO方向设置问题 STM32端口寄存器配置 STM32 的 IO 口可以由软件配置成如下 8 种模式: 1、输入浮空 ...每 IO 口可以自由编程,但 IO 口寄存器必须要按 32 位字被访问。STM32 的很多 IO ...
  • 【x86架构】常用IO端口

    千次阅读 2017-05-20 20:46:58
    x86架构常用IO端口介绍。
  • 超级详细的K60144引脚IO端口资源列表 设计的时候的必备 贼好用 积分意思一下啦
  • 我用STM32的PB端口作为数据线,0~7为输入,8~15为输出,现在想读出0~7端口的电平值赋给Value,那么这函数该怎么写? 方法:value=GPIOB->IDR&0x00FF; 我用STM32的PC端口作为数据线,0~7为输出,写value=0x...
  • x86 IO端口见解

    千次阅读 2013-05-05 20:44:12
     Linux将基于I/O映射方式的I/O端口和基于内存映射方式的I/O端口资源统称为“I/O区域”(I/O Region)。I/O Region仍然是种I/O资源,因此它仍然可以用resource结构类型来描述。下面我们就来看看Linux是如何管理I/O...
  • 51单片机的IO端口

    千次阅读 2017-03-06 22:41:38
    51的IO特性
  • Linux下的IO端口IO内存

    千次阅读 2012-08-27 10:34:06
    说明:本文系通过阅读网络上诸多相关文章,整理而成。   设备驱动程序要直接访问外设或其接口卡上的物理电路,这部分通常都是... 类CPU(如M68K,Power PC等)把这些寄存器看作内存的部分,寄存器参与内存统一
  • 单片机IO端口工作原理

    千次阅读 2012-10-06 09:41:08
    图中有一个是读锁存器的缓冲器,也就是说,要读取D锁存器输出端Q的数据,那就得使读锁存器的这个缓冲器的三态控制端(上图中标号为‘读锁存器’端)有效。图中另一个是读引脚的缓冲器,要读取P0.X引脚上的数据,
  • 异步IO、APC、IO完成端口、线程池与高性能服务器之 异步IO背景:轮询 PIO DMA 中断 早期IO设备的速度与CPU相比,还不是太悬殊。CPU定时轮询IO设备,看看有无处理要求,则加以处理,完成后返回继续工作。...
  • 在使用socket网络编程,实现一个服务器时,初学者最容易想到方法是当服务器监听的socket接受(accept)到一个客户端时创建一个线程,然后在线程中调用recv函数实时监控客户端是否数据发送过来。这种方法是可以实现...
  • 先总体上简单的说一下总线这概念,其实它是束线的整合体,看成是十条线组合在大条名字叫bus的线上,就叫总线。条总线上可以允许连接多设备,那它怎么识别?总线就是发不同的地址编码(0000111之类),...
  • 单片机工程师在面试的过程中,经常会碰到一些相同的问题,笔者总结了十个提问率较高的问题,供大家参考。...首先我们来看一下两个IO口控制两颗LED的情况,电路图如下:LED1和LED2直接接单片机两个IO...
  • 重叠IO-完成端口

    千次阅读 2010-04-08 20:07:00
    kruglinski_at_gmail_dot_comBlog:kruglinski.blogchina.com早在两年前我就已经能很熟练的运用完成端口这种技术了,只是一直没有机会将它用在什么项目中,这段时间见到这种技术被过分炒作,过分的神秘化,就想写篇解释...
  • 理解完成端口(IO completion port)

    千次阅读 2008-01-27 19:54:00
    给的例子要不就是给一个复杂的封装,要不就是给一个简单的收发数据。注意,完成端口不仅仅用于网络数据的收发,它可以用于windows 平台的各种IO操作。不过我这里只关注在winsock编程中的应用。 要写出一篇真的让人...
  • 一个对Winsock完成端口模型封装的类 在 Windows下进行网络服务端程序开发毫无疑问Winsock 完成端口模型是最高效的Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现完成端口模型懂了之后是比较简单的但是要...
  • 编址的方法分为统一编址与独立编址: 统一编址的原理是将IO端口...CPU访问一个端口的操作与访问内存的操作相同,也使用访问内存的指令。独立编址是为端口地址单独开辟一部分地址空间,其访问指令也需要使用单独的指
  •  Windows socket之IO完成端口(IOCP)模型开发

    万次阅读 热门讨论 2013-01-21 16:31:56
    Windows socket之IO完成端口(IOCP)模型开发 IO完成端口种内核对象。利用完成端口,套接字应用程序能够管理数百上千套接字。应用程序创建完成端口对象后,通过指定一定数量的服务线程,为已经完成的重叠IO...
  • socket IO完成端口模型详解

    千次阅读 2010-05-05 22:35:00
    //工作者线程有一个参数,是指向完成端口的句柄 DWORD WINAPI WorkerThread(LPVOID CompletionPortID) {  HANDLE CompletionPort=(HANDLE)CompletionPortID;  DWORD dwBytesTransferred;  SOCKET ...
  • 原作者姓名 Fang(fangguicheng@21...CPU定时轮询IO设备,看看有无处理要求,则加以处理,完成后返回继续工作。至今,软盘驱动器还保留着这种轮询工作方式。 随着CPU性能的迅速提高,这种效率低下的工作方式浪
  • 关于IO 完成 端口的 一些 困惑

    千次阅读 2013-05-25 21:05:11
    完成端口(IOCP)是所有Windows I/O模型中最复杂,也是性能最好的种.在关于IOCP的编程中,难点之就是超时控制. 以下以HTTP服务器程序为例说说. 其实超时控制也不是很难,问题是Windows的IOCP模型本身并没有提供...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 142,231
精华内容 56,892
关键字:

一个端口可有几个io地址