“小王,告诉你一个好消息,最难理解的部分不知不觉中已经讲完了,今天的课程就简单多了,而且最重要的是咱们的Linux设备驱动核心理论课也差不多了…”
“最难的部分?已经讲完了?我咋没感觉呢..你讲的真是太好了,太通俗易懂了,太..”小王调皮的说。 “切,就你嘴甜,我还不知道你啊,小脑筋..”我白了小王一样。
那么今天呢?今天就讲讲IO内存静态映射。在将Linux移植到目标电路板中,通常会建立外设IO内存物理地址到虚拟地址的静态映射,这个映射通过在电路板对应的
map_desc结构体数组中添加新的成员来完成,map_desc结构体的定义如下: struct map_desc {
unsigned long virtual; //虚拟地址
unsigned long pfn; //__phys_to_pfn(phy_addr) unsigned long length; //大小 unsigned int type; //类型 }
将Linux操作系统移植到特定平台上,MACHINE_START到MACHINE_EDN宏之间的定义针对特定电路板而设计,其中的map_io()成员函数完成IO内存的静态映射,最后通过调用cpu—>map_io()建立map_desc数组中物理内存和虚拟内存的静态映射关系。
在一个已经移植好的OS的内核中,驱动工程师完全可以对非常规内存区域的IO内存(外设控制器寄存器,MCU内部集成的外设控制寄存器等)依照电路板的资源使用情况添加到map_desc数组中。下边给出SMDK2440内存情况定义的map_desc:
SMDK2440内存资源情况定义的map_desc#include
#include
#include
#include
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, } };
#define UCON S3C2410_UCON_DEFAULT | S3C2410_UCON_UCLK
#define ULCON S3C2410_LCON_CS8 | S3C2410_LCON_PNONE | S3C2410_LCON_STOPB #define UFCON S3C2410_UFCON_RXTRIG8 | S3C2410_UFCON_FIFOMODE static struct s3c2410_uartcfg smdk2440_uartcfgs[] = { [0] = {
.hwport = 0, .flags = 0, .ucon = 0x3c5, .ulcon = 0x03, .ufcon = 0x51,
}, [1] = {
.hwport = 1, .flags = 0, .ucon = 0x3c5, .ulcon = 0x03, .ufcon = 0x51, },
/* IR port */ [2] = {
.hwport = 2, .flags = 0, .ucon = 0x3c5, .ulcon = 0x43, .ufcon = 0x51, } };
/* LCD driver info */
static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = { .regs = {
.lcdcon1 = S3C2410_LCDCON1_TFT16BPP | S3C2410_LCDCON1_TFT |
S3C2410_LCDCON1_CLKVAL(0x04), .lcdcon2 = S3C2410_LCDCON2_VBPD(7) | S3C2410_LCDCON2_LINEVAL(319) | S3C2410_LCDCON2_VFPD(6) | S3C2410_LCDCON2_VSPW(3),
.lcdcon3 = S3C2410_LCDCON3_HBPD(19) | S3C2410_LCDCON3_HOZVAL(239) | S3C2410_LCDCON3_HFPD(7), .lcdcon4 = S3C2410_LCDCON4_MVAL(0) | S3C2410_LCDCON4_HSPW(3), .lcdcon5 = S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_INVVLINE | S3C2410_LCDCON5_INVVFRAME | S3C2410_LCDCON5_PWREN | S3C2410_LCDCON5_HWSWP, }, #if 0
/* currently setup by downloader */ .gpccon = 0xaa940659,
.gpccon_mask = 0xffffffff, .gpcup = 0x0000ffff, .gpcup_mask = 0xffffffff, .gpdcon = 0xaa84aaa0, .gpdcon_mask = 0xffffffff, .gpdup = 0x0000faff, .gpdup_mask = 0xffffffff, #endif
.lpcsel = ((0xCE6) & ~7) | 1<<4, .width = 240, .height = 320, .xres = { .min = 240, .max = 240, .defval = 240, }, .yres = { .min = 320, .max = 320, .defval = 320, }, .bpp = { .min = 16, .max = 16, .defval = 16, }, };
static struct platform_device *smdk2440_devices[] __initdata = { &s3c_device_usb, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c, &s3c_device_iis, };
static struct s3c24xx_board smdk2440_board __initdata = { .devices = smdk2440_devices,
.devices_count = ARRAY_SIZE(smdk2440_devices) };
static void __init smdk2440_map_io(void) {
s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
s3c24xx_init_clocks(16934400);
s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs)); s3c24xx_set_board(&smdk2440_board); }
static void __init smdk2440_machine_init(void) {
/* Configure the LEDs (even if we have no LED support)*/ s3c2410_gpio_cfgpin(S3C2410_GPF4, S3C2410_GPF4_OUTP); s3c2410_gpio_cfgpin(S3C2410_GPF5, S3C2410_GPF5_OUTP); s3c2410_gpio_cfgpin(S3C2410_GPF6, S3C2410_GPF6_OUTP); s3c2410_gpio_cfgpin(S3C2410_GPF7, S3C2410_GPF7_OUTP); s3c2410_gpio_setpin(S3C2410_GPF4, 0); s3c2410_gpio_setpin(S3C2410_GPF5, 0); s3c2410_gpio_setpin(S3C2410_GPF6, 0); s3c2410_gpio_setpin(S3C2410_GPF7, 0); s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg); s3c2410_pm_init(); }
MACHINE_START(S3C2440, \ /* Maintainer: Ben Dooks
.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
“小王,今天要讲的另外一个主题是DMA”我补充道:
DMA是一种无须CPU的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。使用DMA可以是系统CPU从实际的IO数据传输过程中摆脱出来,从而大大提
供系统的吞吐率。DMA方式的数据传输由DMA控制器(DMAC)控制,在传输期间,CPU可以并发地执行其他任务,当DMA结束后,DMAC通过中断通知CPU数据传输已经结束,然后由CPU执行相应的中断服务程序进行后续处理。
说到DMA,就会想到Cache,两者本身似乎是好不相关的事物。的确,假设DMA针对内存的目的地址和Cache缓存的对象没有重叠区域,DMA和Cache之间就相安无事,但是,如果有重叠呢,经过DMA操作,Cache缓存对应的内存的数据已经被修改,而CPU本身并不知道,它仍然认为Cache中的数据仍
然还是内存中的数据,以后访问Cache映射的内存时,它仍然使用陈旧的Cache数据,这就会发生Cache与内存之间数据“不一致性”的错误。一旦出现这样的情况,没有处理好,驱动就将无
法正常运行。那么怎样解决呢?最简单的方法是直接禁止DMA目标地址范围内内存的Cache功能,当然这是牺牲性能的,但却高可靠。不是吗,所以这两者之间究竟怎么平衡,还真不好解决。
其实啊,Cache不一致的情况在其他地方也是可能发生的,比如,对于带MMU功能的ARM处理器,在开启MMU之前,需要设置Cache无效,TLB也是如此。
说了,那么多DMA理论的东西,剩下的来点Linux下DMA编程的东西,当然,这里也是点一下,具体怎么操作,我可要卖个关子到下节了。
在内存中用于与外设交互数据的一块区域被称作DMA缓冲区,在设备不支持scatter/gatherCSG,分散/聚集操作的情况下,DMA缓冲区必须是物理上联系的。
对于ISA设备而言,其DMA操作只能在16MB以下的内存进行,因此,在使用kmalloc()和__get_free_pages()及其类似函数申请DMA缓冲区时应使用GFP_DMA标 志,这样能保证获得的内存是具备DMA能力的。
内核中定义了__get_free_pages()针对DMA的“快捷方式”__get_dma_pages(),它在申请标志中添加了GFP_DMA,如下所示:
#define __get_dma__pages(gfp_mask, order) __get_free_pages((gfp_mask) | GFP_DMA, (order)) \我不想使用order为参数的申请DMA内存,感觉就是怪怪的,那咋办?\小王抱怨道. 那?这样吧,你就用另外一个函数dma_mem_alloc()源代码如下: static unsigned long dma_mem_alloc(int size) {
int order = get_order(size); //大小->指数 return __get_dma_pages(GFP_KERNEL, order); }
\小王,感觉怎样,要不咱们下节继续?看你挺多不懂的地方?\我看到小王那充满疑惑的眼神。 \好,行,我正有这种打算呢\小王又露出了让人陶醉的笑容。