• LDD3 第9章 与硬件通信


    一、I/O端口和I/O内存

    每种外设都通过读写寄存器进行控制。大部分外设都有几个寄存器,不管是在内村地址空间还是在I/O地址空间,这些寄存器的访问地址都是连续的。

    在硬件层,内存区域和I/O区域没有区别:都是地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据

    I/O寄存器和常规内存

    I/O寄存器

    • 需要注意CPU编译时不恰当的优化而改变预期I/O动作。
    • 具有边际效应,提高速度有极限
    • 对I/O操作来说优化可能造成致命的错误,收到边际效应影响

    常规内存

    • 内存没有边际效应,写操作读操作的访问速度对CPU性能至关重要,需要多种方法优化。
    • 对于内存操作的优化,过程是透明的,效果良好

    5个宏来解决所有可能的排序问题:

    #include <linux/kernel.h>
    void barrier(void)这个函数通知编译器插入一个内存屏障,但对硬件没有影响
    #include <asm/system.h>
    void rmb(void);
    void read_barrier_depends(void);
    void wmb(void);
    void mb(void);
    这些函数在已编译的指令流中插入硬件内存屏障
    void smp_rmb(void);
    void smp_read_barrier_depends(void);
    void smp_wmb(void);
    void smp_mb(void);
    上述屏障宏版本也插入硬件屏障,但仅仅在内核针对SMP系统编译时有效
    解决宏

     设备驱动程序中使用内存屏障的典型形式如下:

    writel(dev->registers.addr, io_destination_address);
    writel(dev->registers.size, io_size);
    writel(dev->registers.operation, DEV_READ);
    wmb();
    writel(dev->registers.control, DEV_GO);

    允许赋值语句和内存屏障合并使用的宏:

    #define set_mb(var, value)    do { var = value; mb();} while 0
    #define set_wmb(var, value)    do { var = value; wmb();} while 0
    #define set_rmb(var, value)    do { var = value; rmb(); } while 0
    #include <asm/system.h>
    合并使用

    二、使用I/O端口

    I/O端口分配

    一个注册用的接口,允许驱动程序声明自己需要操作的端口。核心函数request_region;

    #include <linux/ioport.h>
    struct resource *request_region(unsigned long first, unsigned long n, const char *name);
    first:要使用起始于firt的n个端口
    name:设备名称
    返回值:成功非NULL,失败NULL
    注册

    所有端口分配可以从/proc/ioports中得到,还可以通过/proc得知那些驱动程序分配了端口

    void release_region(unsigned long start, unsigned long n);
    释放IO

    允许驱动程序检查给定I/O集是否可用:

    int check_region(unsigned long first, usnigned long n);
    它的返回值不能确保分配能否成功,主要还是需要requset_region
    是否可用

    操作IO端口

    多数硬件会把8Wie、16位和32位的端口区分开来

    为了方便移植,I/O端口地址寄存器重映射到内存地址来伪装开端口I/O,头文件<asm/io.h>中有如下内联函数

    unsigned inb(unsigned port);
    void outb(unsigned char byte, unsigned port);
    字节(8位宽度)读写端口
    unsigned inw(unsigned port);
    void outw(unsigned short word, unsigned port);
    用于访问16位端口(字宽度)
    unsigned inl(unsigned port);
    void outl(unsigned longword, unsigned port);
    用于访问32位端口
    端口访问函数

    在用户空间使用端口

    如果要在用户空间使用inb及其相关函数,必须满足下面这些条件:

    • 编译该程序时必须带-O选项强制内联函数展开
    • 必须用ioperm或iopl系统调用来获取对端口进行I/O操作的权限,
    • 必须以root身份运行该程序才能调用ioperm

    串操作

    函数原型如下:

    void insb(unsigned port, void *addr, unsigned long count);
    void outsb(unsigned port, void *addr, unsigned long count);
    从内存地址addr开始连续读/写count数目的字节,只对单一端口port读取或写入
    void insw(unsigned port, void *addr, unsigned long count);
    void outsw(unsigned port, void *addr, unsigned long count);
    对一个16位端口读/写16位数据
    void insl(unsigned port, void *addr, unsigned long count);
    void outsl(unsigned port, void *addr, unsigned long count);
    对一个32位端口读/写32位数据
    串操作函数原型

    三、I/O端口示例

    并口简介

    并口的最小配置由3个8位端口组成。略过吧,没意思

    四、使用I/O内存

     除了x86升普遍使用的I/O端口之外,和设备通信的另一种主要机制是通过使用映射。

    不管访问I/O内存时是否需要调用ioremap,都不鼓励直接使用指向I/O内存的指针。

    I/O内存分配和映射

    使用前,必须首先分配I/O内存区域,用于分配内存区域的接口在<linux/ioport.h>中定义:

    struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
    从start开始分配len字节长的内存区域。
    成功:非NULL指针,失败:NULL值
    
    /* 不再使用已分配的内存区域,释放 */
    void release_mem_region(unsigned long start, usngined long len);
    /* 下面是用来检查给定I/O内存区域是否可用的老函数 */
    int check_mem_region(unsigned long start, usngined long len);

    分配I/O内存并不是访问这些内存之前需要完成的唯一步骤,我们还必须确保I/O内存对内核而言可访问。

    必须首先建立映射,映射建立由ioremap函数完成。

    一旦调用ioremap之后,设备驱动程序即可访问任意的I/O内存地址了

    根据以下定义调用ioremap函数:

    #include <asm/io.h>
    void *ioremap(unsigned long phys_addr, unsigned long size);
    void *ioremap_nocache(unsigned long phys_addr, unsigned long size);
    void iounmap(void *addr);

    访问I/O内存

    可以将ioremap返回值直接当作指针使用。但是不具有可移植性。正确方法是通过一组专用于此目的的函数<asm/io.h>中定义

    /* 要从I/O内存中读取,可使用下面函数之一 */
    unsigned int ioread8(void *addr);
    unsigned int ioread16(void *addr);
    unsigned int ioread32(void *addr);
    addr:应该从ioreamp获得的地址,
    /* 返回值是从给定I/O内存读取到的值 */

    还有一组用于写入I/O内存的类似函数集如下:

    void iowrite8(u8 value, void *addr);
    void iowrite16(u16 value, void *addr);
    void iowrite32(u32 value ,void *addr);

    如果必须在给定I/O内存地址处读/写一系列的值,可使用上述函数的重复版本:

    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);
    /* 从给定的buf向给定的addr读取或写入count个值。注意,count以被写入的数据大小为单位表示 */

    上面的函数需要给定的addr处执行所有I/O操作。如果我们要在一块I/O内存上执行操作:

    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 reeadb(address);
    unsigned readw(address);
    unsgined readl(address);
    /* 用来从I/O内存检索8位、16位和32位的数据 */
    void writeb(unsigned value, address);
    void writew(unsigned value, address);
    void writel(unsgined value, address);
    老接口

    像I/O内存一样使用端口

    为了让处理这类硬件驱动更加易于编写,2.6引入了ioport_map函数:

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

    当不再需要这种映射时,调用撤销函数:

    void ioport_unmap(void *addr);
  • 相关阅读:
    查找数据库表中重复的 Image 类型值
    C#中的引用传递和值传递。
    用JS解决Asp.net Mvc返回JsonResult中DateTime类型数据格式的问题
    根据业务自己设计的.NET工厂模式架构
    封装EF code first用存储过程的分页方法
    2013款MacBook Air装Windows7单系统
    js判断是否在微信浏览器中打开
    EF Code First连接现有数据库
    JS中for循序中延迟加载实现动态效果
    DIV+CSS左右两列自适应高度的方法
  • 原文地址:https://www.cnblogs.com/ch122633/p/9651164.html
Copyright © 2020-2023  润新知