努力成为linux kernel hacker的人李万鹏原创作品,为梦而战。转载请标明出处
http://blog.csdn.net/woshixingaaa/archive/2011/05/15/6421954.aspx
首先介绍一下I/O端口和I/O内存。
1.I/O端口:当一个寄存器或内存位于I/O空间时,称其为I/O端口。
2.I/O内存:当一个寄存器或内存位于内存空间时,称其为I/O内存。
再来看一下I/O寄存器和常规内存的区别:I/O寄存器具有边际效应(side effect),而内存操作则没有,内存写操作的唯一结果就是在指定位置存贮一个数值;内存读操作则仅仅是返回指定位置最后一次写入的数值。何为边际效应呢?就是读取某个地址时可能导致该地址内容发生变化。比如很多设备的中断状态寄存器只要一读取,便自动清零。
现在来看一看如何在Linux驱动程序中使用I/O端口和I/O内存。
使用I/O端口的步骤:
1.申请
2.访问
3.释放
申请I/O端口:
在尚未对这些端口进行申请之前我们是不应该对这些端口进行操作的。内核为我们提供了一个注册用的接口:
这个函数告诉内核,我们要使用起始于first的n个端口,参数name应该是设备的名称。如果分配成功,则返回非NULL。如果request_region返回NULL,那么我们就不能使用这些期望的端口。
访问I/O端口:
访问I/O端口时,多数硬件都会把8位,16位和32位的端口区分开。因此,C语言程序中必须调用不同的函数来访问大小不同的端口。
字节(8位宽度)读写端口。
读写16位端口。
读写32位端口。
释放I/O端口:
如果不在使用某组I/O端口(可能在卸载模块时),则应该使用下面的函数将这些端口返回给系统:
使用I/O内存的步骤:
1.申请
2.映射
3.访问
4.释放
根据计算机平台和所使用总线的不同,I/O内存可能是,也可能不是通过页表访问的。如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动程序可见(这通常意味着在进行任何I/O之前必须先调用ioremap)。如果访问无需页表,那么I/O内存区域就非常类似于I/O端口,可以使用适当形式的函数读写它们。
I/O内存申请:
该函数从start开始分配len字节长的内存区域。如果成功,返回非NULL指针;否则返回NULL值。
I/O内存映射:
把物理地址转化成虚拟地址,返回值是虚拟地址。
解除映射。
I/O内存访问:
从内存中读取:
其中addr应该是从ioremap获得的地址。
还有一组写入I/O内存类似函数:
I/O内存释放:
像I/O内存一样使用I/O端口:
该函数重新映射count个I/O端口,使其看起来像I/O内存。此后,驱动程序可在该函数返回的地址上使用ioread8及其同类的函数。
当不再需要这种映射时,需要调用下面的函数来撤销:
上边的是基础知识。
在我们的开发板上内存映射分3个层次(下边的所有内核代码使用的是linux3.6.30.4):
1.开发板的层次
如:声卡,网卡和开发板相关的部分
2.最小系统的层次
系统必须的几个,如GPIO,IRQ,MEMCTRL,UART。
3.其他系统的层次
不影响开机的部分,如USB,LCD,ADC。
开发板mapio的初始化:
smdk2440_map_io函数中会调用:
最小系统IO初始化:
s3c24xx_init_io函数会调用:
这个部分是系统启动必须的映射。后续会调用(cpu->map_io)(mach_desc,size);来完成其他映射。这个函数会调用:
所以,如果新添加一个驱动,首先要看是否完成了IO映射,如果没有的话,就在开发板部分加入。Linux内核访问外设I/O资源的方式有两种:动态映射和静态映射(map_desc)。
动态映射方式上边已经讲述,这里着重讲述静态映射——通过map_desc结构静态创建I/O资源映射表。内核提供了在系统启动时通过map_desc结构体静态创建I/O资源到内核地址空间的线性映射表(即page table)的方式。这种映射表是一种一一映射的关系。程序员可以自己定义该I/O内存资源映射后的虚拟地址。创建好静态映射表,在内核或驱动中访问该I/O资源时则无需再进行ioremap动态映射,可以直接通过映射后的I/O虚拟地址去访问。下面详细分析这种机制的原理并举例说明如何通过这种静态映射的方式访问外设I/O内存资源。
内核提供了一个重要的结构体struct machine_desc,这个结构体在内核移植中起到相当重要的作用,内核通过machine_desc结构体来控制系统体系架构相关部分的初始化。machine_desc结构体包含了体系结构相关部分的几个重要成员的初始化函数,包括map_io,init_irq,init_machine以及phys_io,timer成员等。
machine_desc结构体定义如下:
这里的map_io成员即内核提供给用户的创建外设I/O资源到内核虚拟地址静态映射表的接口函数。map_io成员函数会在系统初始化过程中被调用,流程如下:
start_kernel->setup_arch()->paging_init()->devicemaps_init()中被调用。
machine_desc结构体通过MACHINE_START宏来初始化。
用户可以在定义machine_desc结构时指定map_io的接口函数。s3c2410 machine_desc结构体如下定义:
展开后的结果是:
下边是smdk2440_map_io函数:
它调用了s3c24xx_init_io函数:
所以,smdk2410_map_io最终调用iotable_init建立映射表。
iotable_init函数的参数有两个:一个是map_desc结构体,另一个是该结构体的数量nr。这里最关键的就是struct map_desc,map_desc结构的定义如下:
map_desc定义在arch/arm/include/asm/mach/map.h中,
create_mapping函数就是通过map_desc提供的信息创建线性映射表的。
这样的话我们就可以知道了创建I/O映射表的大致流程为:只要定义相应的I/O资源的map_desc结构体,并将该结构体传给iotable_init函数执行,就可以创建相应的I/O资源到内核虚拟地址空间的映射表了。我们来看看s3c2410是怎么定义map_desc结构体的。
IODESC_ENT定义在arch/arm/plat-s3c/include/plat/cpu.h中,展开后等价于:
这里S3C24XX_PA_TIMER和S3C24XX_VA_TIMER为定义在arch/arm/plat-s3c24xx/include/plat/map.h内TIMER寄存器的物理地址和虚拟地址。在这里map_desc结构体的virtual成员被初始化为S3C24XX_VA_TIMER,pfn成员值通过__phys_to_pfn内核函数计算,只要传递给它该I/O的物理地址就可行了。Length为映射资源的大小。MT_DEVICE为I/O类型,通常定义为MT_DEVICE。这里最重要的即virtual成员的值S3C24XX_VA_ TIMER,这个值即该I/O资源映射后的内核虚拟地址,创建映射表成功后,便可以在内核或驱动中直接通过该虚拟地址访问这个I/O资源。
在ARM9中,如果从nand启动,sram被映射到bank0,在启动后这个sram可以用作其他用途。下边是测试程序sram.c:
在内核中修改:
测试结果:
分享到:
相关推荐
《深入理解Linux内核访问外设I/O资源的方式》 在深入探讨Linux内核如何访问外部设备的输入/输出(I/O)资源之前,我们有必要先了解I/O端口与I/O内存的基本概念及其区别。这不仅对于理解操作系统内核与硬件交互的...
### Linux内核访问外设IO资源的方式 #### 一、概述 在Linux内核中,为了能够有效地控制和管理各种外部设备(例如SRAM、硬件接口寄存器等),需要将这些外设的I/O资源映射到内核空间中。通常来说,Linux内核支持两...
本章节主要讲解了 Linux 内核在 I/O 空间管理方面的知识点,包括 I/O 控制器、I/O 空间的管理、I/O 端口和 I/O 内存的访问方式、I/O 资源管理等。 I/O 控制器是计算机中的一个实体,主要职责是控制一个或多个 I/O ...
- Linux内核通过复杂的算法来有效地管理内存资源,以满足不同进程的需求。 - **2.2 地址映射的全过程** - 地址映射是指将进程的虚拟地址空间映射到物理内存的过程。 - 映射过程中涉及到页表、段表等数据结构的...
通过USB驱动程序的开发示例,文章展示了如何向Linux内核提供设备文件I/O接口,以及如何通过标准的核心服务如内存分配、中断转发和等待队列来实现USB设备的驱动开发。类似地,通过GPIO驱动程序的开发示例,文章讲述了...
由于提供的文件信息中含有大量的重复内容和非正文信息,我将直接从“深入理解LINUX内核”这一标题和“深入理解LINUX内核,对内核中使用的最重要的数据结构、算法和程序设计诀窍进行一次遍历。希望理解内核原理的朋友...
总的来说,Linux内核的I/O管理程序是一个复杂而精细的设计,它在硬件与软件之间搭建了一座桥梁,使得操作系统能够有效地利用和管理各种I/O资源。无论是系统开发者还是设备驱动程序员,都需要对这一机制有深入的理解...
5. **I/O端口和总线**:解释I/O端口的使用和总线通信,如I2C、SPI和UART等常见外设总线。 6. **设备树**:详细介绍设备树在嵌入式Linux中的应用,它是如何描述硬件配置,帮助内核初始化硬件资源的。 7. **S3C2440...
平台抽象层则关注具体的硬件平台,如启动流程、芯片选择、定时器、I/O接口和中断控制等。 系统调用是连接内核和用户程序的关键,它是操作系统向用户程序提供的服务接口。在Linux中,系统调用通过软中断机制实现,如...
通过研究这个版本的内核,开发者不仅可以了解Linux内核的基本架构,还可以学习到如何阅读和分析复杂的开源项目,这对于成为一名出色的Linux开发者至关重要。同时,这也为硬件驱动的开发、系统调优和安全研究提供了...
Linux内核是操作系统的核心部分,它负责管理系统的硬件资源,如CPU、内存,以及与外设的交互。《Linux_Kernel核心中文手册(内核图解).pdf》和《linux内核结构详解》是深入理解Linux内核的重要参考资料。这份资料涵盖...
### Linux内核源代码情景分析(下):关键知识点概览 #### 第7章 基于socket的进程间通信 ##### 7.1 系统调用`socket()` - **简介**:`socket()`系统调用是用于创建一个socket端点的基础,它是进程间通信的一种...
这部分代码负责初始化I/O映射表,确保内核能够通过内存访问方式操作外设。 **3. 平台相关的修改文件** 为了适应Hi3511/Hi3512平台的特点,可能需要修改的标准内核文件包括但不限于: - `arch/arm/mach-hi35xx/`: ...
Linux内核是操作系统的核心组件,负责管理和协调计算机硬件资源,为应用程序提供必要的服务。内核的主要职责包括: - **进程管理**:创建、销毁进程,以及进程间的调度。 - **内存管理**:分配和回收内存空间,实现...