《linux内核访问外设io资源的方式》由会员分享,可在线阅读,更多相关《linux内核访问外设io资源的方式(10页珍藏版)》请在金锄头文库上搜索。
1、Linux 内核访问外设 I/O 资源的方式Linux 内核访问外设 I/O 资源的方式Author: DongasDate: 08-08-02我们知道默认外设 I/O 资源是不在 Linux 内核空间中的(如 sram 或硬件接口寄存器等),若需要访问该外设 I/O 资源,必须先将其地址映射到内核空间中来,然后才能在内核空间中访问它。Linux 内核访问外设 I/O 内存资源的方式有两种:动态映射(ioremap)和静态映射(map_desc)。一、动态映射(ioremap)方式动态映射方式是大家使用了比较多的,也比较简单。即直接通过内核提供的 ioremap 函数动态创建一段外设 I/O
2、内存资源到内核虚拟地址的映射表,从而可以在内核空间中访问这段 I/O 资源。Ioremap 宏定义在 asm/io.h 内:#define ioremap(cookie,size) _ioremap(cookie,size,0)_ioremap 函数原型为(arm/mm/ioremap.c):void _iomem * _ioremap(unsigned long phys_addr, size_t size, unsigned long flags);phys_addr:要映射的起始的 IO 地址size:要映射的空间的大小flags:要映射的 IO 空间和权限有关的标志该函数返回映射后的内
3、核虚拟地址(3G-4G). 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O 内存资源。举一个简单的例子: (取自 s3c2410 的 iis 音频驱动)比如我们要访问 s3c2410 平台上的 I2S 寄存器, 查看 datasheet 知道 IIS 物理地址为 0x55000000,我们把它定义为宏 S3C2410_PA_IIS,如下:#define S3C2410_PA_IIS (0x55000000)若要在内核空间(iis 驱动)中访问这段 I/O 寄存器(IIS)资源需要先建立到内核地址空间的映射:our_card-regs = ioremap(S3C2410_PA_IIS,
4、 0x100); if (our_card-regs = NULL) err = -ENXIO;goto exit_err;创建好了之后,我们就可以通过 readl(our_card-regs )或 writel(value, our_card-regs)等 IO 接口函数去访问它。二、静态映射(map_desc)方式下面重点介绍静态映射方式即通过 map_desc 结构体静态创建 I/O 资源映射表。内核提供了在系统启动时通过 map_desc 结构体静态创建 I/O 资源到内核地址空间的线性映射表(即 page table)的方式,这种映射表是一种一一映射的关系。程序员可以自己定义该 I/
5、O 内存资源映射后的虚拟地址。创建好了静态映射表,在内核或驱动中访问该 I/O 资源时则无需再进行 ioreamp 动态映射,可以直接通过映射后的 I/O 虚拟地址去访问它。下面详细分析这种机制的原理并举例说明如何通过这种静态映射的方式访问外设 I/O 内存资源。内核提供了一个重要的结构体 struct machine_desc ,这个结构体在内核移植中起到相当重要的作用,内核通过 machine_desc 结构体来控制系统体系架构相关部分的初始化。machine_desc 结构体的成员包含了体系架构相关部分的几个最重要的初始化函数,包括 map_io, init_irq, init_mach
6、ine 以及 phys_io , timer 成员等。machine_desc 结构体定义如下:struct machine_desc /* Note! The first four elements are used* by assembler code in head-armv.S*/unsigned int nr; /* architecture number */unsigned int phys_io; /* start of physical io */unsigned int io_pg_offst; /* byte offset for io * page tabe entry
7、 */const char *name; /* architecture name */ unsigned long boot_params; /* tagged list */unsigned int video_start; /* start of video RAM */unsigned int video_end; /* end of video RAM */unsigned int reserve_lp0 :1; /* never has lp0 */unsigned int reserve_lp1 :1; /* never has lp1 */unsigned int reserv
8、e_lp2 :1; /* never has lp2 */unsigned int soft_reboot :1; /* soft reboot */void (*fixup)(struct machine_desc *,struct tag *, char *,struct meminfo *);void (*map_io)(void);/* IO mapping function */void (*init_irq)(void);struct sys_timer *timer; /* system tick timer */void (*init_machine)(void);这里的 ma
9、p_io 成员即内核提供给用户的创建外设 I/O 资源到内核虚拟地址静态映射表的接口函数。Map_io 成员函数会在系统初始化过程中被调用,流程如下:Start_kernel - setup_arch() paging_init() devicemaps_init()中被调用Machine_desc 结构体通过 MACHINE_START 宏来初始化。注:MACHINE_START 的使用及各个成员函数的调用过程请参考:http:/ Machine_desc 结构体时指定 Map_io 的接口函数,这里以 s3c2410 平台为例。s3c2410 machine_desc 结构体定义如下:/*
10、 arch/arm/mach-s3c2410/Mach-smdk2410.c */MACHINE_START(SMDK2410, SMDK2410) /* TODO: request a new identifier and switch* to SMDK2410 */* Maintainer: Jonas Dietsche */.phys_io = S3C2410_PA_UART,.io_pg_offst = (u32)S3C24XX_VA_UART) 18) & 0xfffc,.boot_params = S3C2410_SDRAM_PA + 0x100,.map_io = smdk241
11、0_map_io,.init_irq = s3c24xx_init_irq, .init_machine = smdk2410_init,.timer = &s3c24xx_timer,MACHINE_END如上,map_io 被初始化为 smdk2410_map_io。smdk2410_map_io 即我们自己定义的创建静态 I/O 映射表的函数。在 Porting 内核到新开发板时,这个函数需要我们自己实现。(注:这个函数通常情况下可以实现得很简单,只要直接调用 iotable_init 创建映射表就行了,我们的板子内核就是。不过 s3c2410 平台这个函数实现得稍微有点复杂,主要是因为
12、它将要创建 IO 映射表的资源分为了三个部分(smdk2410_iodesc, s3c_iodesc 以及 s3c2410_iodesc)在不同阶段分别创建。这里我们取其中一个部分进行分析,不影响对整个概念的理解。)S3c2410 平台的 smdk2410_map_io 函数最终会调用到 s3c2410_map_io 函数。流程如下:s3c2410_map_io - s3c24xx_init_io - s3c2410_map_io下面分析一下 s3c2410_map_io 函数:void _init s3c2410_map_io(struct map_desc *mach_desc, int
13、mach_size)/* register our io-tables */iotable_init(s3c2410_iodesc, ARRAY_SIZE(s3c2410_iodesc);iotable_init 内核提供,定义如下: /* Create the architecture specific mappings*/void _init iotable_init(struct map_desc *io_desc, int nr)int i;for (i = 0; i 4) & 0x0f000000) + 0xf0000000) )s3c2410_iodesc 这个映射表建立成功后,我
14、们在内核中便可以直接通过 S3C24XX_VA_ LCD 访问 LCD 的寄存器资源。如:S3c2410 lcd 驱动的 probe 函数内/* Stop the video and unset ENVID if set */info-regs.lcdcon1 lcdcon1 = readl(S3C2410_LCDCON1); /read映射后的寄存器虚拟地址writel(lcdcon1 /write映射后的虚拟地址S3C2410_LCDCON1 寄存器地址为相对于 S3C24XX_VA_LCD 偏移的一个地址,定义如下:/* include/asm/arch-s3c2410/regs-lcd
15、.h */#define S3C2410_LCDREG(x) (x) + S3C24XX_VA_LCD)/* LCD control registers */#define S3C2410_LCDCON1 S3C2410_LCDREG(0x00)到此,我们知道了通过 map_desc 结构体创建 I/O 内存资源静态映射表的原理了。总结一下发现其实过程很简单,一通过定义 map_desc 结构体创建静态映射表,二在内核中通过创建映射后虚拟地址访问该 IO 资源。三、I/O 静态映射方式应用实例I/O 静态映射方式通常是用在寄存器资源的映射上,这样在编写内核代码或驱动时就不需要再进行ioremap,直接使用映射后的内核虚拟地址访问。同样的 IO 资源只需要在内核初始化过程中映射一次,以后就可以一直使用。寄存器资源映射的例子上面讲原理时已经介绍得很清楚了,这里我举一个 SRAM 的实例介绍如何应用这种