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

最新文章

  1. 将语音搜索集成到Google Now中
  2. CodeForces 279D The Minimum Number of Variables 题解
  3. 2015年最有价值的30个响应式WORDPRESS主题
  4. js中初学函数的使用
  5. HDU 3397 Sequence operation (区间合并,操作比较多)
  6. LoadRunner常见问题整理(转)
  7. 设置VS2015上关闭和打开tab快捷键
  8. JavaScript中String对象处理HTML标记中文本的方法
  9. MVC4加载zTree树小控件
  10. dojo表格分页插件报错
  11. java 有序数组合并
  12. ubuntu使用遇到的问题
  13. C#反射の一个泛型反射实现的网络请求框架
  14. rar自动压缩备份
  15. java多线程快速入门(八)
  16. 爬虫解析库——BeautifulSoup
  17. 数学:二次剩余与n次剩余
  18. assetbundle 对自定义shader的打包
  19. 影响SEO 搜索引擎优化的网页制作细节
  20. zabbix日常监控(监控缓存)

热门文章

  1. 虚拟机中安装Linux_Centos7操作系统(最小化安装)
  2. Windows 下开启FTP服务并创建FTP用户
  3. scrapy过滤重复数据和增量爬取
  4. ajax跨域处理 No &#39;Access-Control-Allow-Origin&#39; header is present on the requested resource 问题
  5. jmeter之登录接口的一次简单压测与分析
  6. 获取EasyUI日期输入框的值
  7. Hibernate入门2
  8. SQL常用语句之数据库的创建、删除以及属性的修改-篇幅1
  9. Python入门习题5.蒙特卡罗方法计算圆周率
  10. redis 哨兵配置文件解读sentinel.conf