一,IO端口与IO内存

         独立编址:处理器将IO地址在独立的IO地址空间编排(具有独立的操作指令,指令短访问速度快)——设备寄存器与设备内存被映射到IO地址空间称为IO映射;

         统一编址:处理器将IO地址和主内存在一个地址空间编排(具有统一的操作指令,操作内存的指令也可以操作设备寄存器与设备内存,操作种类多,指令长访问速度慢)————设备寄存器与设备内存被映射到内存空间称为内存映射;

       

        IO端口:设备寄存器与设备内存映射到IO地址空间称为IO端口;

        IO内存:设备寄存器与设备内存映射到物理内存地址空间称为IO内存;


        端口:处理器访问设备寄存器或设备内存的设备地址,端口也成为寄存器;由于端口<------>地址<-------->寄存器三者一一对应,所以寄存器地址即就是端口地址也成为端口;

   

二,IO端口与内存

       区别:IO端口访问具有边际效应,内存没有边际效应;

       IO端口 访问需要解决的问题:边际效应,优化(缓存,指令序列)

       优化问题的解决方案        :缓存优化——禁用缓存;

       指令序列优化的解决方案:设置内存屏障-达到避免指令序列被优化而重新排序的目的;

        linux内核实现的内存屏障函数:

<linux/kernel.h>
          barrier();

          通知编译器添加内存屏障,对硬件没有任何影响,屏障前后的指令读写都是从内存中直接读取或写入,处理器对屏障前后的指令不进行优化重新排序;

<asm/system.h>
         mb();        //读写内存屏障;
         wmb();     //写内存屏障;
         rmb();      //读内存屏障;
         read_barrier_depends();

         这几个内存屏障是添加硬件内存屏障,都是barrier()的超集;

          多处理版本:

smp_mb();
         smp_wmb();
         smp_rmb();
         smp_read_barrier_depends();


 二,IO端口访问;

          Linux内核对IO端口访问提供了两种方式,这里是第一种:

1,struct resources *request_region(unsigned long first_port , unsigned n, char *name);

                在对设备端口进行操作之前一定要取得设备的独占访问;

                这个函数是申请设备从first_port 端口开始的n个端口(地址),name为设备名称;

                first_port 为设备端口地址改地址是可以直接访问设备的物理地址;

                函数返回值为NULL则申请失败,非NULL则申请成功;

         2,IO端口操作函数:

                linux内核多端口的操作分为三类,按照端口的位宽来分;

                读取:

       

unsigned char  inb(unsigned long port); //一个字节--8位端口;
                unsigned short inw(unsigned long port);//一个字长(双字节)--16位;
                unsigned inl(unsigned long port); //双子长(四个字节)--32位;

 

               写入:

void outb(char val,unsigned long port); 
               void outw(unsigned short val,unsigned long port);
               void outl(unsigned val,unsigned long port);

 

               串IO操作——对某个端口(读取或 )写入一个数据序列(数据序列的单位是一次能够读写的数据位宽)-与单次操作的区别:单次读取或写入可以调整字节序,而数据序列操作不能调整字节序:

     

//in
                 void insb(unsigned char port,void *addr,unsigned long count);
                 void insw(unsinged short port,void *addr,unsigned long count);
                 void insl(unsigned port,void *addr,unsigned long count);
                //out
                 void outsb(unsigned long port,void *addr,unsigned long count);//将地址addr处开始的count个字节写入端口;
                 void outsw(unsigned long port,void *addr,unsigned long count);
                 void outsl(unsigned long port,void *addr,unsigned long count);

          3,对设备端口独占访问释放:

     

release_region(unsigned long first_port,unsigned long n)

;


三,IO内存访问;

        1,struct resources *request_mem_region(unsigned long addr,unsigned long size,char *name);

              申请从IO地址addr处开始的size大小的IO内存块;

              返回值为NULL申请失败,非NULL则申请成功;


void *ioremap(unsigned long addr,unsigned long size);

              由于内核使用的是处理器的虚拟地址,所以将IO内存的物理地址映射到Linux内核虚拟地址空间中,便于Linux内核访问IO内存空间;

              无论是否存在MMU单元,为了代码的可移植性,都不要对函数返回的虚拟地址直接操作,而是要用Linux内核提供的安全的,经过优化的,平台统一的操作函数;

         2,IO内存操作函数:

               同IO端口一样分为三类:

               读取:

unsigned char read8(void *addr);
               unsigned short read16(void *addr);
               unsigned read32(void *addr);

               写入:

void write8(unsigned char val,void *addr);
               void write16(unsigned short val,void *addr);
               void write32(unsigned val,void *addr);


               数据序列操作--上述函数的重复版本(repeat):

       

//read;
               void ioread8_rep(void *dest,void *source,unsigned long count);
               void ioread16_rep(void *dest,void *source,unsigned long count);
              void ioread32_rep(void *dest,void *source,unsigned long count);
               //write;
               void iowrite8_rep(void *dest,const void *source,unsigned long count);
               void iowrite16_rep(void *dest,const void *source,unsigned long count);
               void iowrite32_req(void *dest,const void *source,unsigned long count);

               将从原地址source开始的count个数据单位写入同一个目标地址dest处,数据单位是一次性写入IO内存的数据位宽;


       3,释放IO内存到内核虚拟地址空间的映射:

 

void iounmap(void *addr,unsigned long count);

       4,释放IO内存独占访问:

void release_mem_region(unsigned long addr, unsigned long size);


四,IO端口的第二种方位方式——采用和IO内存一样的操作函数——从操作层面淡化IO端口与IO内存之间的区别;