一、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);

LDD3 第9章 与硬件通信的更多相关文章

  1. LDD3 第15章 内存映射和DMA

    本章内容分为三个部分: 第一部分讲述了mmap系统调用的实现过程.将设备内存直接映射到用户进程的地址空间,尽管不是所有设备都需要,但是能显著的提高设备性能. 如何跨越边界直接访问用户空间的内存页,一些 ...

  2. 第23章 java线程通信——生产者/消费者模型案例

    第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...

  3. 利用Socket与硬件通信(智能家居)

    前几天做一个智能家居APP,硬件段使用的是ESP8266WIFI模块,其实不管是WIFI模块还是蓝牙,通信都是同样一个道理,获取IP和端口来进行通信. 我是通过XCOM v2.0 发送信息,移动端接收 ...

  4. 第4章 TCP/IP通信案例:访问Internet上的Web服务器

    第4章 TCP/IP通信案例:访问Internet上的Web服务器 4.2 部署代理服务器 书中为了演示访问Internet上的Web服务器的全过程,使用了squid代理服务器程序模拟了一个代理服务器 ...

  5. Android利用Socket与硬件通信之智能家居APP

    前几天做一个智能家居APP,硬件段使用的是ESP8266WIFI模块,其实不管是WIFI模块还是蓝牙,通信都是同样一个道理,获取IP和端口来进行通信. 我是通过XCOM v2.0 发送信息,移动端接收 ...

  6. Java多线程编程核心技术-第3章-线程间通信-读书笔记

    第 3 章 线程间通信 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大 ...

  7. 【STM32H7教程】第57章 STM32H7硬件JPEG编解码基础知识和HAL库API

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第57章       STM32H7硬件JPEG编解码基础知识 ...

  8. LDD3 第13章 USB驱动程序

    通用串行总线(USB)是主机和外围设备之间的一种连接.最新USB规范修订增加了理论上高达480Mbps的高速连接. 从拓扑上看,USB子系统并不是以总线的方式来布置的,它是一颗由几个点对点的连接构建而 ...

  9. ldd3 第12章 PCI驱动程序

    PCI接口 PCI寻址 引导阶段 配置寄存器和初始化 MODULE_DEVICE_TABLE 注册PCI驱动程序 佬式PCI探测 激活PCI设备 访问配置空间 访问I/O和内存空间 PCI中断 硬件抽 ...

随机推荐

  1. ORACLE Physical Standby DG搭建

    主库: 一:强制force logging: alter database force logging; 二:开启主库的归档模式 三:主库添加standby redo log,比redo日志组多一组: ...

  2. Java-package import关键字

    package包关键字,在java中,有包的概念,主要是用来归类 分类作用: 便于项目的开发和维护: 通过分包,可以很清晰的来管理类: 上述 一个类Animal: 在开头有个 package com. ...

  3. PHP-执行外部程序

    备份 / 恢复数据库 exec - 执行一个外部程序(在 php 文件所在目录进行执行) 很久以前写的,很多方法是项目中的直接复制粘体用不了,只能提供下思路. 用到执行外部程序的就这一句: exec( ...

  4. 知道css有个content属性吗?有什么作用?有什么应用?

    css的content属性专门应用在 before/after 伪元素上,用来插入生成内容.最常见的应用是利用伪类清除浮动. //一种常见利用伪类清除浮动的代码 .clearfix:after { c ...

  5. 涛涛的小马甲 Android之Handler机制

    首先需要了解一个基本的概念ANR:Application not response 即应用程序无响应,也就是俗话说的死机. 出现Anr的原因是: 主线程需要做很多重要的事情,响应点击事件,更新UI如果 ...

  6. Python笔记(十二)_文件

    文件的打开模式 'r':以只读的方式打开文件(默认) 'w':以写入的方式打开文件,会覆盖已存在的文件 'x':用写入的方式打开文件,如果文件已存在,会抛出异常 'a':用写入的方式打开文件,如果文件 ...

  7. JavaScript高级程序设计(第3版) 第三章 (基本概念)

    3.1 语法 1.不以数字开头的数字,字母,下划线,美元符号 2.注释:html <!-- --> css/**/ js单行// 多行/**/ 3.ES5 引入了严格模式(strict m ...

  8. 自定义ThreadLocal和事务(基于自定义AOP)

    参考<架构探险--从零开始写javaweb框架>4.6章节 自定义ThreadLocal package smart; import java.util.Collections; impo ...

  9. 《剑指offer》面试题3 二维数组中的查找 Java版

    (二维数组,每行递增,每列递增.输入二维数组和一个整数,判断数组中是否含有此数.) 我的方法:拿到题目,根据题目条件我提取出这样一个特性:一个数的右边和下面的数都比它大.于是就可以写出一种递归的方法: ...

  10. 查找idt table 所對應的page table in Linux

    #include <linux/kernel.h> #include <linux/module.h> #include <linux/types.h> #incl ...