设备I/O端口与I/O内存的访问

来源:互联网 发布:韩服lol mac 编辑:程序博客网 时间:2024/04/26 14:24

对于一块实际的设备而言,通常会提供一组寄存器来用于控制设备,读写设备和获取设备状态,也就是我们常说的控制寄存器,数据寄存器和状态寄存器。这些寄存器可能位于I/O空间(这时叫做I/O端口),也可能位于内存空间(对应的内存空间被成为I/O内存)。在Linux中提供了一系列的I/O端口和I/O内存操作的接口如下:

  1)I/O端口操作:在Linux设备驱动中,应使用Linux内核提供的函数来访问定位于I/O空间的端口,包括一下几种:

   *读写字节端口(8位宽)

     unsigned inb(unsigned port);【读】          voi outb(unsigned char byte, unsigned port);【写】

   *读写字端口(16位宽)

     unsigned inw(unsigned port);【读】          voi outw(unsigned short word, unsigned port);【写】

   *读写长字端口(32位宽)

     unsigned inl(unsigned port);【读】          voi outl(unsigned longword, unsigned port);【写】

   *读写一串字节

     unsigned insb(unsigned port, void *addr, unsigned long count);【读】      voi outsb(unsigned port, void *addr, unsigned long count);【写】

   *读写一串字

     unsigned insw(unsigned port, void *addr,unsigned long count);【读】      voi outsb(unsigned port, void *addr, unsigned long count);【写】

   *读写一串长字

     unsigned insl(unsigned port, void * addr, unsigned long count);【读】      voi outsb(unsigned port, void *addr, unsigned long count);【写】

    说明:上述各函数中I/O端口port的类型长度依赖与具体的硬件平台,所以只是写出了unsigned

  2)I/O内存:在内核中访问I/O内存之前,需首先使用ioremap()函数将设备所处的物理地址映射到虚拟地址。

   *ioremap()原型:void *ioremap(unsigned long offset, unsigned long size);

     它返回一个特殊的虚拟地址,该地址可用来存取特定的物理地址范围。用它返回的虚拟地址应该使用iounmap()函数释放。

   *iounmap()原型:void iounmap(void *addr);

   现在,有了物理地址锁映射出来的虚拟地址后,我们就可以通过c指针来直接访问这些地址,但Linux内核也提供了一组函数来完成这中虚拟地址的读写。如下

   *读IO内存

     unsigned int ioread8(void *addr);      unsigned int ioread16(void *addr);     unsigned int ioread32(void *addr);  与之对应的较早版本是:

     unsigned readb(address);                unsigned readw(address);                 unsigned readl(address);  这些在2.6的内核中依然可以使用。

   *写IO内存

     void iowrite8(u8 value,void *addr);  void iowrite16(u16 value, void *addr);   void iowrite32(u32 value, void *addr);  与之对应的较早版本是:

     void writeb(unsigned value, address); void writew(unsigned value,address);  void writel(unsigned value,address);  2.6的内核中依然可以使用。

   *读一串IO内存                                                                          *写一串IO内存

     void ioread8_rep(void *addr, void *buf, unsigned long count);       void iowrite8_rep(void *addr, void *buf, unsigned long count);

     void ioread16_rep(void *addr, void *buf, unsigned long count);     void iowrite8_rep(void *addr, void *buf, unsigned long count);

     void ioread32_rep(void *addr, void *buf, unsigned long count);     void iowrite8_rep(void *addr, void *buf, unsigned long count);

   *复制IO内存

     void memcpy_fromio(void *dest, void *source, unsigned int count);

     void memcpy_toio(void *dest, void *source, unsigned int count);

   *设置IO内存

     void *ioport_map(unsigned long port, unsigned int count);

  3)把IO端口映射到内存空间

     void *ioport_map(unsigned long port, unsigned int count);   通过这个函数,可以把port开始的count个连续的IO端口重映射为一段“内存空间”。然后

                                                                                     就可以在其返回的地址上向访问IO内存一样访问这些端口,当不再需要这种映射时,调用:

     void ioport_unmap(void *addr);                                      来撤销这种映射

  4)IO端口申请

     struct resource *request_region(unsigned long first, unsigned long n, const char *name);

     这个函数向内核申请n个端口,这些端口从first开始,name参数为设备的名称,成功返回非NULL.一旦申请端口使用完成后,应当使用:

     void release_region(unsigned long start, unsigned long n);

  5)IO内存申请

    struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);

    这个函数向内核申请n个内存,这些地址从first开始,name为设备的名称,成功返回非NULL,一旦申请的内存使用完成后,应当使用:

    void release_mem_region() ;          来释放归回给系统。需要说明的是这两个函数也不是必须的,但建议使用。  

6)通过以上的基础,我们就可以归纳出设备驱动访问IO端口和IO内存的步骤。

   一种方法是:直接使用IO端口操作函数:在设备打开或驱动模块被加载时申请IO端口区域,之后使用inb(),outb()等进行端口访问,最后在设备关闭或驱动被卸载时释放IO端口范围。流程如下:

   123

   另外一种途径是:将IO端口映射为内存进行访问,在设备打开或驱动模块被加载时,申请IO端口区域并使用ioport_map()映射到内存,之后使用IO内存的函数进行端口访问,最后,在设备关闭或驱动模块被卸载时释放IO端口并释放映射,流程如下:

   456

  上边是IO端口的访问方法,至于IO内存的访问方法是:首先调用request_mem_region()申请资源,接着将寄存器地址通过ioremap()映射到内核空间的虚拟地址,之后就可以Linux设备访问编程接口访问这些寄存器了,访问完成后,使用ioremap()对申请的虚拟地址进行释放,并释放release_mem_region()申请的IO内存资源。

流程如下:

   789