Linux如何访问mmio空间,一文读懂Linux下如何访问I/O端口和I/O内存
虽然访问I/O端口非常简单,但是检测哪些I/O端口已经分配给I/O设备可能就不这么简单了,对基于ISA总线的系统来说更是如此。通常,I/O设备驱动程序为了探测硬件设备,需要盲目地向某一I/O端口写入数据;但是,如果其他硬件设备已经使用这个端口,那么系统就会崩溃。为了防止这种情况的发生,内核必须使用“资源”来记录分配给每个硬件设备的I/O端口。资源表示某个实体的一部分,这部分被互斥地分配给设备驱动程序。在这里,资源表示I/O端口地址的一个范围。每个资源对应的信息存放在resource数据结构中:本文引用地址:http://www.eepw.com.cn/article/201712/373670.htm
1.struct resource {
2. resource_size_t start;// 资源范围的开始
3. resource_size_t end;// 资源范围的结束
4. const char *name; //资源拥有者的名字
5. unsigned long flags;// 各种标志
6. struct resource *parent, *sibling, *child;// 指向资源树中父亲,兄弟和孩子的指针
};
所有的同种资源都插入到一个树型数据结构(父亲、兄弟和孩子)中;例如,表示I/O端口地址范围的所有资源都包括在一个根节点为ioport_resource的树中。节点的孩子被收集在一个链表中,其第一个元素由child指向。sibling字段指向链表中的下一个节点。
为什么使用树?例如,考虑一下IDE硬盘接口所使用的I/O端口地址-比如说从0xf000 到 0xf00f。那么,start字段为0xf000 且end 字段为0xf00f的这样一个资源包含在树中,控制器的常规名字存放在name字段中。但是,IDE设备驱动程序需要记住另外的信息,也就是IDE链主盘使用0xf000 到0xf007的子范围,从盘使用0xf008 到0xf00f的子范围。为了做到这点,设备驱动程序把两个子范围对应的孩子插入到从0xf000 到0xf00f的整个范围对应的资源下。
一般来说,树中的每个节点肯定相当于父节点对应范围的一个子范围。I/O端口资源树(ioport_resource)的根节点跨越了整个I/O地址空间(从端口0到65535)。
任何设备驱动程序都可以使用下面三个函数,传递给它们的参数为资源树的根节点和要插入的新资源数据结构的地址:
request_resource( ) //把一个给定范围分配给一个I/O设备。
allocate_resource( ) //在资源树中寻找一个给定大小和排列方式的可用范围;若存在,将这个范围分配给一个I/O设备(主要由PCI设备驱动程序使用,可以使用任意的端口号和主板上的内存地址对其进行配置)。
release_resource( ) //释放以前分配给I/O设备的给定范围。
内核也为以上函数定义了一些应用于I/O端口的快捷函数:request_region( )分配I/O端口的给定范围,release_region( )释放以前分配给I/O端口的范围。当前分配给I/O设备的所有I/O地址的树都可以从/proc/ioports文件中获得。
2、内存映射方式
将IO端口映射为内存进行访问,在设备打开或驱动模块被加载时,申请IO端口区域并使用ioport_map()映射到内存,之后使用IO内存的函数进行端口访问,最后,在设备关闭或驱动模块被卸载时释放IO端口并释放映射。
映射函数的原型为:
void *ioport_map(unsigned long port, unsigned int count);
通过这个函数,可以把port开始的count个连续的I/O端口重映射为一段“内存空间”。然后就可以在其返回的地址上像访问I/O内存一样访问这些I/O端口。但请注意,在进行映射前,还必须通过request_region( )分配I/O端口。
当不再需要这种映射时,需要调用下面的函数来撤消:
void ioport_unmap(void *addr);
在设备的物理地址被映射到虚拟地址之后,尽管可以直接通过指针访问这些地址,但是宜使用Linux内核的如下一组函数来完成访问I/O内存:
读I/O内存
unsigned int ioread8(void *addr);
unsigned int ioread16(void *addr);
unsigned int ioread32(void *addr);
与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
写I/O内存
void iowrite8(u8 value, void *addr);
void iowrite16(u16 value, void *addr);
void iowrite32(u32 value, void *addr);
与上述函数对应的较早版本的函数为(这些函数在Linux 2.6中仍然被支持):
void writeb(unsigned value, address);
void writew(unsigned value, address);
void writel(unsigned value, address);
流程如下:
六、Linux下访问IO内存
IO内存的访问方法是:首先调用request_mem_region()申请资源,接着将寄存器地址通过ioremap()映射到内核空间的虚拟地址,之后就可以Linux设备访问编程接口访问这些寄存器了,访问完成后,使用ioremap()对申请的虚拟地址进行释放,并释放release_mem_region()申请的IO内存资源。
struct resource *requset_mem_region(unsigned long start, unsigned long len,char *name);
这个函数从内核申请len个内存地址(在3G~4G之间的虚地址),而这里的start为I/O物理地址,name为设备的名称。注意,。如果分配成功,则返回非NULL,否则,返回NULL。
另外,可以通过/proc/iomem查看系统给各种设备的内存范围。
要释放所申请的I/O内存,应当使用release_mem_region()函数:
void release_mem_region(unsigned long start, unsigned long len)
申请一组I/O内存后, 调用ioremap()函数:
void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);
其中三个参数的含义为:
phys_addr:与requset_mem_region函数中参数start相同的I/O物理地址;
size:要映射的空间的大小;
flags:要映射的IO空间的和权限有关的标志;
功能:将一个I/O地址空间映射到内核的虚拟地址空间上(通过release_mem_region()申请到的)
流程如下:
七、ioremap和ioport_map
下面具体看一下ioport_map和ioport_umap的源码:
void __iomem *ioport_map(unsigned long port, unsigned int nr)
{
1.
if (port > PIO_MASK)
2.
return NULL;
3.
return (void __iomem *) (unsigned long) (port + PIO_OFFSET);
4.
}
5.
6.
void ioport_unmap(void __iomem *addr)
7.
{
8.
/* Nothing to do */
ioport_map仅仅是将port加上PIO_OFFSET(64k),而ioport_unmap则什么都不做。这样portio的64k空间就被映射到虚拟地址的64k~128k之间,而ioremap返回的虚拟地址则肯定在3G之上。ioport_map函数的目的是试图提供与ioremap一致的虚拟地址空间。分析ioport_map()的源代码可发现,所谓的映射到内存空间行为实际上是给开发人员制造的一个“假象”,并没有映射到内核虚拟地址,仅仅是为了让工程师可使用统一的I/O内存访问接口ioread8/iowrite8(......)访问I/O端口。
最后来看一下ioread8的源码,其实现也就是对虚拟地址进行了判断,以区分IO端口和IO内存,然后分别使用inb/outb和readb/writeb来读写。
unsigned int fastcall ioread8(void __iomem *addr)
{
IO_COND(addr, return inb(port), return readb(addr));
}
#define VERIFY_PIO(port) BUG_ON((port & ~PIO_MASK) != PIO_OFFSET)
#define IO_COND(addr, is_pio, is_mmio) do { \
unsigned long port = (unsigned long __force)addr; \
if (port
VERIFY_PIO(port); \
port &= PIO_MASK; \
is_pio; \
} else { \
is_mmio; \
} \
} while (0)
展开:
unsigned int fastcall ioread8(void __iomem *addr)
{
unsigned long port = (unsigned long __force)addr;
if( port
BUG_ON( (port & ~PIO_MASK) != PIO_OFFSET );
port &= PIO_MASK;
return inb(port);
}else{
return readb(addr);
}
}
八、总结
外设IO寄存器地址独立编址的CPU,这时应该称外设IO寄存器为IO端口,访问IO寄存器可通过ioport_map将其映射到虚拟地址空间,但实际上这是给开发人员制造的一个“假象”,并没有映射到内核虚拟地址,仅仅是为了可以使用和IO内存一样的接口访问IO寄存器;也可以直接使用in/out指令访问IO寄存器。
例如:Intel x86平台普通使用了名为内存映射(MMIO)的技术,该技术是PCI规范的一部分,IO设备端口被映射到内存空间,映射后,CPU访问IO端口就如同访 问内存一样。
外设IO寄存器地址统一编址的CPU,这时应该称外设IO寄存器为IO内存,访问IO寄存器可通过ioremap将其映射到虚拟地址空间,然后再使用read/write接口访问。
Linux如何访问mmio空间,一文读懂Linux下如何访问I/O端口和I/O内存相关推荐
- 一文读懂 Linux 内存分配全过程
在<你真的理解内存分配>一文中,我们介绍了 malloc 申请内存的原理,但其在内核怎么实现的呢?所以,本文主要分析在 Linux 内核中对堆内存分配的实现过程. 本文使用 Linux 2 ...
- ac3165 linux驱动_一文读懂Linux系统启动流程
Linux启动管理 11.1 CentOS 6.x系统启动过程详解 CentOS 6.x系统启动过程发生了较大的变化,使用Upstart启动服务取代了原先的System V init启动服务.Upst ...
- 一文读懂linux操作系统
☞☞☞点击查看更多优秀嵌入式博客☜☜☜ 三相电机故障排除 初识linux 发展历史 linux操作指令 常用 Linux 命令的基本使用 常用快捷键 linux 与window系统的区别 linux版 ...
- 一文读懂 | Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈
点击蓝字 关注我们 因公众号更改推送规则,请点"在看"并加"星标"第一时间获取精彩技术分享 来源于网络,侵删 栈是什么?栈有什么作用? 首先,栈 (stack) ...
- 一文读懂 Linux 下单机实现百万并发的内核黑科技:连接跟踪(Conntrack)
公众号关注 「奇妙的 Linux 世界」 设为「星标」,每天带你玩转 Linux ! 本文介绍连接跟踪(connection tracking,conntrack,CT)的原理,应用,及其在 Linu ...
- 一文读懂linux 下zram
本文转自https://blog.csdn.net/longwang155069/article/details/51900031 zram 技术的由来: zram(也称为 zRAM,先前称为 com ...
- linux内核区分cpu架构,一文读懂处理器,内核,芯片三个概念的区别
一.处理器简介 处理器一般指中央处理器.中央处理器(CPU,Central Processing Unit)是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control U ...
- 非标准语法;请使用 _一文读懂使用MCU SPI访问具有非标准SPI接口ADC的方法
好文章当然要分享啦~如果您喜欢这篇文章,请联系后台添加白名单,欢迎转载哟~ 问题 能否用MCU访问非标准SPI接口? 答案 可以,但可能需要做一些额外的努力. 当前许多精密模数转换器(ADC)具有串行 ...
- 一文读懂Linux进程、进程组、会话、僵尸
作者简介 herongwei,北交硕士毕业,现就职于搜狗公司,后端开发工程师.从事 C++,Golang ,Linux 后端开发. 追求技术,热爱编程与分享,希望能和大家多多交流学习~ 座右铭: ...
最新文章
- php mpdf html 转pdf,使用 MPDF 将HTML转为PDF,然后将该PDF转为PNG图片的时候,中文报错... ......
- Windows 下使用 BaiduExporter + Aria2 下载百度网盘文件
- LiveVideoStackCon深圳-嵌入式音频开发
- MongoDB 字段拼接 $concat(aggregation)
- InstallShield For .Net制作.Net项目安装包之完整代码
- Selenium断言的使用,等待
- rocketmq支持最大消息_分布式消息引擎Apache RocketMQ最佳实践
- 20211010 PHP笔记
- python实践项目(六)
- schedule event mysql_mysql计划任务:event schedule
- Linux man 命令后面的圆括号的意义
- 大牛熬夜整理,京东网易滴滴,天津农行软开java面试题合集,大牛已于上个月入职华为!
- 计算机快捷键任务管理器,任务管理器快捷键,小编教你电脑如何打开任务管理器...
- html表头和左侧固定js,固定表头jquery datatable的使用与定制
- html图片圆点切换,图片轮播带小圆点选择左右切换
- c语言大一,C语言复习 大一.doc
- HDU - 4565 So Easy! 矩阵快速幂
- HTML5系列代码:框模型的大小
- linux(4)进程控制
- 技术面试官应该怎么问?面试者应该怎么答?
热门文章
- 阮一峰react demo代码研究的学习笔记 - demo4 debug - create element and Render
- when is odata request sent for Live report in SAP CRM
- Cannot maintain customer-spec. event handler registration in SAP system
- SAP CRM WebClient UI F4 value help is centrally implemented in CRM_THTMLB_UTIL/F4Frameset.html
- SAP Spartacus初始化时和user token相关的APP INITIALIZER
- Web socket广播
- 推荐一个包含大量PDF编程电子书的github仓库
- Jerry Wang 2014年在SAP社区CRM板块的积分曾经一直占据第一
- SAP S/4HANA生产订单创建时使用的工厂数据是从什么地方带出来的 1
- 数据库服务器的文件结构6,以下数据库服务器的文件结构