- #include <linux/ioport.h>
- struct resource *request_region(unsigned long first, unsigned long n, const char *name);
这个函数告诉内核,我们要使用起始于first的n个端口,参数name应该是设备的名称。如果分配成功,则返回非NULL。如果request_region返回NULL,那么我们就不能使用这些期望的端口。
访问I/O端口:
访问I/O端口时,多数硬件都会把8位,16位和32位的端口区分开。因此,C语言程序中必须调用不同的函数来访问大小不同的端口。
- unsigned inb(unsigned port);
- void outb(unsigned char byte, unsigned port);
- unsigned inw(unsigned port);
- void outw(unsigned short word, unsigned port);
- unsigned inl(unsigned port);
- void outl(unsigned longword, unsigned port);
读写32位端口。
释放I/O端口:
如果不在使用某组I/O端口(可能在卸载模块时),则应该使用下面的函数将这些端口返回给系统:
void release_region(unsigned long start, unsigned long n);
使用I/O内存的步骤:
1. 申请
2. 映射
3. 访问
4. 释放
根据计算机平台和所使用总线的不同,I/O内存可能是,也可能不是通过页表访问的。如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动程序可见(这通常意味着在进行任何I/O之前必须先调用ioremap)。如果访问无需页表,那么I/O内存区域就非常类似于I/O端口,可以使用适当形式的函数读写它们。
I/O内存申请:
struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);
该函数从start开始分配len字节长的内存区域。如果成功,返回非NULL指针;否则返回NULL值。
I/O内存映射:
- #include<asm/io.h>
- void *ioremap(unsigned long phys_addr, unsigned long size);
void *iounmap( void *addr);
解除映射。
I/O内存访问:
从内存中读取:
- unsigned int ioread8(void *addr);
- unsigned int ioread16(void *addr);
- unsigned int ioread32(void *addr);
其中addr应该是从ioremap获得的地址。
还有一组写入I/O内存类似函数:
- void iowrite8(u8 value, void *addr);
- void iowrite16(u16 value, void *addr);
- void iowrite32(u32 value, void *addr);
void release_mem_region(unsigned long start, unsigned long len);
像I/O内存一样使用I/O端口:
void *ioport_map(unsigned long port, unsigned int count);
该函数重新映射count个I/O端口,使其看起来像I/O内存。此后,驱动程序可在该函数返回的地址上使用ioread8及其同类的函数。
当不再需要这种映射时,需要调用下面的函数来撤销:
void ioport_unmap(void *addr);
上边的是基础知识。
在我们的开发板上内存映射分3个层次(下边的所有内核代码使用的是linux3.6.30.4):
1.开发板的层次
如:声卡,网卡和开发板相关的部分
2.最小系统的层次
系统必须的几个,如GPIO,IRQ,MEMCTRL,UART。
3.其他系统的层次
不影响开机的部分,如USB,LCD,ADC。
开发板mapio的初始化:
smdk2440_map_io函数中会调用:
- static struct map_desc smdk2440_iodesc[] __initdata = {
- /* ISA IO Space map (memory space selected by A24) */
- {
- .virtual = (u32)S3C24XX_VA_ISA_WORD,
- .pfn = __phys_to_pfn(S3C2410_CS2),
- .length = 0x10000,
- .type = MT_DEVICE,
- }, {
- .virtual = (u32)S3C24XX_VA_ISA_WORD + 0x10000,
- .pfn = __phys_to_pfn(S3C2410_CS2 + (1<<24)),
- .length = SZ_4M,
- .type = MT_DEVICE,
- }, {
- .virtual = (u32)S3C24XX_VA_ISA_BYTE,
- .pfn = __phys_to_pfn(S3C2410_CS2),
- .length = 0x10000,
- .type = MT_DEVICE,
- }, {
- .virtual = (u32)S3C24XX_VA_ISA_BYTE + 0x10000,
- .pfn = __phys_to_pfn(S3C2410_CS2 + (1<<24)),
- .length = SZ_4M,
- .type = MT_DEVICE,
- },{
- .virtual = (u32)S3C2410_ADDR(0x07600000),
- .pfn = __phys_to_pfn(0x40000000),
- .length =SZ_4K,
- .type = MT_DEVICE,
- }
- };
最小系统IO初始化:
s3c24xx_init_io函数会调用:
- iotable_init(s3c_iodesc,ARRAY_SIZE(s3c_iodesc));
- /* minimal IO mapping */
- static struct map_desc s3c_iodesc[] __initdata = {
- IODESC_ENT(GPIO),
- IODESC_ENT(IRQ),
- IODESC_ENT(MEMCTRL),
- IODESC_ENT(UART)
- };
- Iotable_init(s3c2440_iodesc,ARRAY_SIZE(s3c2440_iodesc));
- /* Initial IO mappings */
- static struct map_desc s3c2410_iodesc[] __initdata = {
- IODESC_ENT(CLKPWR),
- IODESC_ENT(TIMER),
- IODESC_ENT(WATCHDOG),
- };
所以,如果新添加一个驱动,首先要看是否完成了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结构体定义如下:
- struct machine_desc {
- /*
- * Note! The first four elements are used
- * by assembler code in head.S, head-common.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 */
- 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 reserve_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);
- };
这里的map_io成员即内核提供给用户的创建外设I/O资源到内核虚拟地址静态映射表的接口函数。map_io成员函数会在系统初始化过程中被调用,流程如下:
start_kernel->setup_arch()->paging_init()->devicemaps_init()中被调用。
machine_desc结构体通过MACHINE_START宏来初始化。
- MACHINE_START定义在arch/arm/include/asm/mach/arch.h中
- /*
- * Set of macros to define architecture features. This is built into
- * a table by the linker.
- */
- #define MACHINE_START(_type,_name) /
- static const struct machine_desc __mach_desc_##_type /
- __used /
- __attribute__((__section__(".arch.info.init"))) = { /
- .nr = MACH_TYPE_##_type, /
- .name = _name,
- #define MACHINE_END /
- };
- MACHINE_START(S3C2440, "SMDK2440")
- /* Maintainer: Ben Dooks <ben@fluff.org> */
- .phys_io = S3C2410_PA_UART,
- .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
- .boot_params = S3C2410_SDRAM_PA + 0x100,
- .init_irq = s3c24xx_init_irq,
- .map_io = smdk2440_map_io,
- .init_machine = smdk2440_machine_init,
- .timer = &s3c24xx_timer,
- MACHINE_END
- static const struct machine_desc __mach_desc_SMDK2410
- __attribute_used__
- __attribute__((__section__(".arch.info.init"))) = {
- .nr = MACH_TYPE_SMDK2410, /* architecture number */
- .name = "SMDK2410", /* architecture name */
- /* Maintainer: Jonas Dietsche */
- .phys_io = S3C2410_PA_UART, /* start of physical io */
- .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
- .boot_params = S3C2410_SDRAM_PA + 0x100, /* tagged list */
- .map_io = smdk2410_map_io, /* IO mapping function */
- .init_irq = s3c24xx_init_irq,
- .init_machine = smdk_machine_init,
- .timer = &s3c24xx_timer,
- }
- static void __init smdk2440_map_io(void)
- {
- s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
- s3c24xx_init_clocks(12000000);
- s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
- }
- void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
- {
- unsigned long idcode = 0x0;
- /* initialise the io descriptors we need for initialisation */
- iotable_init(mach_desc, size);
- iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));
- if (cpu_architecture() >= CPU_ARCH_ARMv5) {
- idcode = s3c24xx_read_idcode_v5();
- } else {
- idcode = s3c24xx_read_idcode_v4();
- }
- arm_pm_restart = s3c24xx_pm_restart;
- s3c_init_cpu(idcode, cpu_ids, ARRAY_SIZE(cpu_ids));
- }
- /*
- * Create the architecture specific mappings
- */
- void __init iotable_init(struct map_desc *io_desc, int nr)
- {
- int i;
- for (i = 0; i < nr; i++)
- create_mapping(io_desc + i);
- }
所以,smdk2410_map_io最终调用iotable_init建立映射表。
iotable_init函数的参数有两个:一个是map_desc结构体,另一个是该结构体的数量nr。这里最关键的就是struct map_desc,map_desc结构的定义如下:
map_desc定义在arch/arm/include/asm/mach/map.h中,
- struct map_desc {
- unsigned long virtual; /*映射后的虚拟地址*/
- unsigned long pfn; /*I/O资源物理地址所在的页帧号*/
- unsigned long length; /*I/O资源长度*/
- unsigned int type; /*I/O资源类型*/
- };
create_mapping函数就是通过map_desc提供的信息创建线性映射表的。
这样的话我们就可以知道了创建I/O映射表的大致流程为:只要定义相应的I/O资源的map_desc结构体,并将该结构体传给iotable_init函数执行,就可以创建相应的I/O资源到内核虚拟地址空间的映射表了。我们来看看s3c2410是怎么定义map_desc结构体的。
- static struct map_desc s3c2410_iodesc[] __initdata = {
- IODESC_ENT(CLKPWR),
- IODESC_ENT(TIMER),
- IODESC_ENT(WATCHDOG),
- };
- static struct map_desc s3c2410_iodesc[] __initdata = {
- {
- .virtual = (unsigned long)S3C24XX_VA_ TIMER),
- .pfn = __phys_to_pfn(S3C24XX_PA_ TIMER),
- .length = S3C24XX_SZ_ TIMER,
- .type = MT_DEVICE
- },
- ……
- };
- /* Timers */
- #define S3C24XX_VA_TIMER S3C_VA_TIMER
- #define S3C2410_PA_TIMER (0x51000000)
- #define S3C24XX_SZ_TIMER SZ_1M
- #define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */
- #define S3C_ADDR_BASE (0xF4000000)
- #ifndef __ASSEMBLY__
- #define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))
- #else
- #define S3C_ADDR(x) (S3C_ADDR_BASE + (x))
- #endif
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/ioport.h>
- #include <asm/mach/map.h>
- #include <mach/hardware.h>
- void sram_test(void){
- void *test;
- char sram[] = "my_iomap_test success";
- test = (void*)S3C2410_ADDR(0x07600000);
- memcpy(test,sram,sizeof(sram));
- printk(test);
- printk("/n");
- }
- static int __init my_iomap_init(void){
- struct resource *ret;
- printk("my_iomap_test init/n");
- ret = request_mem_region(0x0000000,0x1000,"sram");
- if(ret == NULL){
- printk("io memory request fail!/n");
- return -1;
- }
- sram_test();
- return 0;
- }
- static void __exit my_iomap_exit(void){
- printk("my_iomap_test exit/n");
- release_mem_region(0x00000000,0x1000);
- }
- module_init(my_iomap_init);
- module_exit(my_iomap_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("liwanpeng");
- static struct map_desc smdk2440_iodesc[] __initdata = {
- /* ISA IO Space map (memory space selected by A24) */
- {
- .virtual = (u32)S3C24XX_VA_ISA_WORD,
- .pfn = __phys_to_pfn(S3C2410_CS2),
- .length = 0x10000,
- .type = MT_DEVICE,
- }, {
- .virtual = (u32)S3C24XX_VA_ISA_WORD + 0x10000,
- .pfn = __phys_to_pfn(S3C2410_CS2 + (1<<24)),
- .length = SZ_4M,
- .type = MT_DEVICE,
- }, {
- .virtual = (u32)S3C24XX_VA_ISA_BYTE,
- .pfn = __phys_to_pfn(S3C2410_CS2),
- .length = 0x10000,
- .type = MT_DEVICE,
- }, {
- .virtual = (u32)S3C24XX_VA_ISA_BYTE + 0x10000,
- .pfn = __phys_to_pfn(S3C2410_CS2 + (1<<24)),
- .length = SZ_4M,
- .type = MT_DEVICE,
- },{
- .virtual = (u32)S3C2410_ADDR(0x07600000),
- .pfn = __phys_to_pfn(0x00000000),
- .length =SZ_4K,
- .type = MT_DEVICE,
- }
- };
- [root@LWP usb]# insmod sram.ko
- my_iomap_test init
- my_iomap_test success
原地址: http://blog.chinaunix.net/uid-20672257-id-3147526.html
注:ioread8()在查了其源码后,发现在内核其实也是inb()....