1、前言
IO端口與IO內存的概念:外設中的寄存器稱爲是IO端口,外設中的內存稱爲是IO內存。二者統稱爲IO空間。Linux內核是通過虛擬地址訪問外設的。所以需要先將虛擬地址映射到相應外設的物理地址上,linux的映射方式有兩種:靜態映射(map_desc)和動態映射(ioremap),其實也是內核訪問外設資源的方式。
2、靜態映射基本過程:
在驅動中配置寄存器,可以調用類似於s3c_gpio_cfgpin、s3c_gpio_setpull、gpio_direction_output等直接配置IO寄存器的函數。這些函數訪問的虛擬地址,這些虛擬地址都是已經在啓動啓動的時候通過靜態映射方式映射到IO寄存器的物理地址上。
靜態映射概念:所謂的靜態映射是指,虛擬地址到物理地址的轉換所需要的頁表在操作系統啓動時已經配置好,不需要用戶進行配置,虛擬地址到物理地址的查錶轉換可直接完成(fromnet)。
以s5pv210爲例講述:
MACHINE_START(SMDKV210,"SMDKV210")
/* Maintainer: Kukjin Kim<[email protected]> */
.boot_params =S5P_PA_SDRAM + 0x100,
.init_irq =s5pv210_init_irq,
.map_io = smdkv210_map_io,
.init_machine = smdkv210_machine_init,
#ifdefCONFIG_S5P_HIGH_RES_TIMERS
.timer = &s5p_systimer,
#else
.timer =&s5p_timer,
#endif
MACHINE_END
其中IO映射函數是smdkv210_map_io實現:
staticvoid __init smdkv210_map_io(void)
{
s5p_init_io(NULL,0, S5P_VA_CHIPID);
s3c24xx_init_clocks(24000000);
s5pv210_gpiolib_init();
s3c24xx_init_uarts(smdkv210_uartcfgs,
ARRAY_SIZE(smdkv210_uartcfgs));
#ifndefCONFIG_S5P_HIGH_RES_TIMERS
s5p_set_timer_source(S5P_PWM2, S5P_PWM4);
#endif
s5p_reserve_bootmem(s5pv210_media_devs,
ARRAY_SIZE(s5pv210_media_devs),
S5P_RANGE_MFC);
}
該函數中調用s5p_init_io進行地址的映射,
/*read cpu identification code */
void__init s5p_init_io(struct map_desc*mach_desc,
int size, void __iomem *cpuid_addr)
{
unsigned long idcode;
/* initialize the io descriptors we need forinitialization */
iotable_init(s5p_iodesc, ARRAY_SIZE(s5p_iodesc));
if (mach_desc)
iotable_init(mach_desc, size);
idcode = __raw_readl(cpuid_addr);
s3c_init_cpu(idcode,cpu_ids, ARRAY_SIZE(cpu_ids));
}
其中最重要是struct map_desc s5p_iodesc[]結構體 和 staticstruct map_desc s5pv210_iodesc[]:
unsigned long virtual; // IO空間映射後的虛擬地址
unsigned long pfn; // IO空間的物理地址所在的頁幀號
unsigned long length; // IO空間的長度
unsigned int type; // IO空間的類型
};
/*minimal IO mapping這部分是最小系統層次的映射*/
staticstruct map_desc s5p_iodesc[] __initdata = {
{
.virtual =(unsigned long)S5P_VA_CHIPID,
.pfn =__phys_to_pfn(S5P_PA_CHIPID),
.length =SZ_4K,
.type =MT_DEVICE,
}, {
.virtual = (unsigned long)S3C_VA_SYS,
.pfn = __phys_to_pfn(S5P_PA_SYSCON),
.length = SZ_64K,
.type = MT_DEVICE,
},{
.virtual =(unsigned long)S3C_VA_TIMER,
.pfn =__phys_to_pfn(S5P_PA_TIMER),
.length =SZ_16K,
.type =MT_DEVICE,
},{
.virtual =(unsigned long)S3C_VA_WATCHDOG,
.pfn =__phys_to_pfn(S3C_PA_WDT),
.length =SZ_4K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)S5P_VA_SROMC,
.pfn =__phys_to_pfn(S5P_PA_SROMC),
.length =SZ_4K,
.type =MT_DEVICE,
},
};
/*Initial IO mappings*/
staticstruct map_desc s5pv210_iodesc[] __initdata = {
{
.virtual =(unsigned long)S5P_VA_SYSTIMER,
.pfn =__phys_to_pfn(S5PV210_PA_SYSTIMER),
.length =SZ_4K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)S5P_VA_GPIO,
.pfn =__phys_to_pfn(S5PV210_PA_GPIO),
.length =SZ_4K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)VA_VIC0,
.pfn =__phys_to_pfn(S5PV210_PA_VIC0),
.length =SZ_16K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)VA_VIC1,
.pfn =__phys_to_pfn(S5PV210_PA_VIC1),
.length =SZ_16K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)VA_VIC2,
.pfn =__phys_to_pfn(S5PV210_PA_VIC2),
.length =SZ_16K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)VA_VIC3,
.pfn =__phys_to_pfn(S5PV210_PA_VIC3),
.length =SZ_16K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)S3C_VA_UART,
.pfn =__phys_to_pfn(S3C_PA_UART),
.length =SZ_512K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)S5P_VA_DMC0,
.pfn =__phys_to_pfn(S5PV210_PA_DMC0),
.length =SZ_4K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)S5P_VA_DMC1,
.pfn =__phys_to_pfn(S5PV210_PA_DMC1),
.length =SZ_4K,
.type =MT_DEVICE,
},{
.virtual = (unsigned long)S5P_VA_BUS_AXI_DSYS,
.pfn = __phys_to_pfn(S5PV210_PA_BUS_AXI_DSYS),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S5P_VA_BUS_AXI_VSYS,
.pfn = __phys_to_pfn(S5PV210_PA_BUS_AXI_VSYS),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual = (unsigned long)S5P_VA_BUS_AXI_XSYS,
.pfn = __phys_to_pfn(S5PV210_PA_BUS_AXI_XSYS),
.length = SZ_4K,
.type = MT_DEVICE,
}, {
.virtual =(unsigned long)S3C_VA_USB_HSPHY,
.pfn =__phys_to_pfn(S5PV210_PA_HSPHY),
.length =SZ_4K,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)S3C_VA_OTG,
.pfn =__phys_to_pfn(S5PV210_PA_OTG),
.length =SZ_1M,
.type =MT_DEVICE,
}, {
.virtual =(unsigned long)S3C_VA_OTGSFR,
.pfn =__phys_to_pfn(S5PV210_PA_OTGSFR),
.length =SZ_1M,
.type =MT_DEVICE,
},
#ifdefined(CONFIG_HRT_RTC)
{
.virtual = (unsigned long)S5P_VA_RTC,
.pfn = __phys_to_pfn(S5PV210_PA_RTC),
.length = SZ_4K,
.type = MT_DEVICE,
},
#endif
{
.virtual = (unsigned long)S5P_VA_AUDSS,
.pfn = __phys_to_pfn(S5PV210_PA_AUDSS),
.length = SZ_1M,
.type = MT_DEVICE,
},
};
說明:
虛擬地址的定義(只截取了部分爲例):
#defineS3C_ADDR_BASE (0xFD000000)
#ifndef__ASSEMBLY__
#defineS3C_ADDR(x) ((void __iomem __force*)S3C_ADDR_BASE + (x))
#else
#defineS3C_ADDR(x) (S3C_ADDR_BASE + (x))
#endif
#defineS3C_VA_IRQ S3C_ADDR(0x00000000) /* irq controller(s) */
#define S3C_VA_SYS S3C_ADDR(0x00100000) /*system control */
#defineS3C_VA_MEM S3C_ADDR(0x00200000) /* memory control */
……
#defineS3C_ADDR_CPU(x) S3C_ADDR(0x00500000 + (x))
#endif/* __ASM_PLAT_MAP_H */
物理地址的定義(只截取了部分爲例):
……
#defineS5P_PA_SDRAM S5PV210_PA_SDRAM
#defineS5P_PA_SROMC S5PV210_PA_SROMC
#define S5P_PA_SYSCON S5PV210_PA_SYSCON
#defineS5P_PA_TIMER S5PV210_PA_TIMER
……
////////////////////////////////////////////////////////////
#define S5P_VA_GPIO S3C_ADDR(0x01500000)
////////////////////////////////////////////////////////////
……
#defineS5PV210_PA_CHIPID 0xE0000000
#define S5PV210_PA_SYSCON 0xE0100000
#define S5PV210_PA_GPIO 0xE0200000
……
#defineS5PV210_PA_TIMER 0xE2500000
#defineS5PV210_PA_SYSTIMER 0xE2600000
#defineS5PV210_PA_WATCHDOG 0xE2700000
#defineS5PV210_PA_RTC 0xE2800000
……
比如物理地址爲E0100000 的SYSCON部分就被映射到了S3C_ADDR(0x00100000)也就是0xFD100000的虛擬地址上。
再比如物理地址爲E0200000 的GPIO部分就被映射到了S3C_ADDR(0x01500000)也就是0XFE500000的虛擬地址上。
第二個結構體數組的映射實現是通過s3c_init_cpu(idcode,cpu_ids, ARRAY_SIZE(cpu_ids))函數調用s5pv210_map_io實現:
static struct cpu_table cpu_ids[]__initdata = {
{
.idcode =0x43110000,
.idmask =0xfffff000,
.map_io = s5pv210_map_io,
.init_clocks = s5pv210_init_clocks,
.init_uarts = s5pv210_init_uarts,
.init =s5pv210_init,
.name =name_s5pv210,
},
void__init s5pv210_map_io(void)
{
iotable_init(s5pv210_iodesc, ARRAY_SIZE(s5pv210_iodesc));
……
}
iotable_init涉及了內存管理,實現代碼:
void__init iotable_init(struct map_desc *io_desc, int nr)
{
int i;
for (i = 0; i < nr; i++)
create_mapping(io_desc + i);
}
到此,基本上就完成了s5p_iodesc和 s5pv210_iodesc的映射。從此刻開始內核可以通過IO空間映射後的虛擬地址訪問外設IO空間。