Linux内核开发之内存与I/O访问(五)
2012-12-11 17:46
363 查看
“小王,告诉你一个好消息,最难理解的部分不知不觉中已经讲完了,今天的课程就简单多了,而且最重要的是咱们的Linux设备驱动核心理论课也差不多了…”
“最难的部分?已经讲完了?我咋没感觉呢..你讲的真是太好了,太通俗易懂了,太..”小王调皮的说。
“切,就你嘴甜,我还不知道你啊,小脑筋..”我白了小王一样。
那么今天呢?今天就讲讲IO内存静态映射。在将Linux移植到目标电路板中,通常会建立外设IO内存物理地址到虚拟地址的静态映射,这个映射通过在电路板对应的
map_desc结构体数组中添加新的成员来完成,map_desc结构体的定义如下:
将Linux操作系统移植到特定平台上,MACHINE_START到MACHINE_EDN宏之间的定义针对特定电路板而设计,其中的map_io()成员函数完成IO内存的静态映射,最后通过调用cpu—>map_io()建立map_desc数组中物理内存和虚拟内存的静态映射关系。
在一个已经移植好的OS的内核中,驱动工程师完全可以对非常规内存区域的IO内存(外设控制器寄存器,MCU内部集成的外设控制寄存器等)依照电路板的资源使用情况添加到map_desc数组中。下边给出SMDK2440内存情况定义的map_desc:
“小王,今天要讲的另外一个主题是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,如下所示:
“最难的部分?已经讲完了?我咋没感觉呢..你讲的真是太好了,太通俗易懂了,太..”小王调皮的说。
“切,就你嘴甜,我还不知道你啊,小脑筋..”我白了小王一样。
那么今天呢?今天就讲讲IO内存静态映射。在将Linux移植到目标电路板中,通常会建立外设IO内存物理地址到虚拟地址的静态映射,这个映射通过在电路板对应的
map_desc结构体数组中添加新的成员来完成,map_desc结构体的定义如下:
structmap_desc { unsignedlongvirtual;//虚拟地址 unsignedlongpfn;//__phys_to_pfn(phy_addr) unsignedlonglength;//大小 unsignedinttype;//类型 }
将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<linux/kernel.h> #include<linux/types.h> #include<linux/interrupt.h> #include<linux/list.h> #include<linux/timer.h> #include<linux/init.h> #include<linux/platform_device.h> #include<asm/mach/arch.h> #include<asm/mach/map.h> #include<asm/mach/irq.h> #include<asm/hardware.h> #include<asm/hardware/iomd.h> #include<asm/io.h> #include<asm/irq.h> #include<asm/mach-types.h> //#include<asm/debug-ll.h> #include<asm/arch/regs-serial.h> #include<asm/arch/regs-gpio.h> #include<asm/arch/regs-lcd.h> #include<asm/arch/idle.h> #include<asm/arch/fb.h> #include"s3c2410.h" #include"s3c2440.h" #include"clock.h" #include"devs.h" #include"cpu.h" #include"pm.h" staticstructmap_descsmdk2440_iodesc[]__initdata={ /*ISAIOSpacemap(memoryspaceselectedbyA24)*/ { .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, } }; #defineUCONS3C2410_UCON_DEFAULT|S3C2410_UCON_UCLK #defineULCONS3C2410_LCON_CS8|S3C2410_LCON_PNONE|S3C2410_LCON_STOPB #defineUFCONS3C2410_UFCON_RXTRIG8|S3C2410_UFCON_FIFOMODE staticstructs3c2410_uartcfgsmdk2440_uartcfgs[]={ [0]={ .hwport =0, .flags =0, .ucon =0x3c5, .ulcon =0x03, .ufcon =0x51, }, [1]={ .hwport =1, .flags =0, .ucon =0x3c5, .ulcon =0x03, .ufcon =0x51, }, /*IRport*/ [2]={ .hwport =2, .flags =0, .ucon =0x3c5, .ulcon =0x43, .ufcon =0x51, } }; /*LCDdriverinfo*/ staticstructs3c2410fb_mach_infosmdk2440_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, }, #if0 /*currentlysetupbydownloader*/ .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, }, }; staticstructplatform_device*smdk2440_devices[]__initdata={ &s3c_device_usb, &s3c_device_lcd, &s3c_device_wdt, &s3c_device_i2c, &s3c_device_iis, }; staticstructs3c24xx_boardsmdk2440_board__initdata={ .devices=smdk2440_devices, .devices_count=ARRAY_SIZE(smdk2440_devices) }; staticvoid__initsmdk2440_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); } staticvoid__initsmdk2440_machine_init(void) { /*ConfiguretheLEDs(evenifwehavenoLEDsupport)*/ 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,"SMDK2440") /*Maintainer:BenDooks<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
“小王,今天要讲的另外一个主题是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()源代码如下:
staticunsignedlongdma_mem_alloc(intsize) { intorder=get_order(size);//大小->指数 return__get_dma_pages(GFP_KERNEL,order); }
"小王,感觉怎样,要不咱们下节继续?看你挺多不懂的地方?"我看到小王那充满疑惑的眼神。
"好,行,我正有这种打算呢"小王又露出了让人陶醉的笑容。
相关文章推荐
- Linux内核开发之内存与I/O访问(一)
- Linux内核开发之内存与I/O访问(一)
- Linux内核开发之内存与I/O访问(二)
- Linux内核开发之内存与I/O访问(二)
- Linux内核开发之内存与I/O访问(三)
- Linux内核开发之内存与I/O访问(二)
- Linux内核开发之内存与I/O访问(一)
- Linux内核开发之内存与I/O访问(四)
- Linux内核开发之内存与I/O访问(二)
- Linux内核开发之内存与I/O访问(六)
- Linux内核开发之内存与I/O访问(三)
- Linux内核开发之内存与I/O访问(五)
- Linux 内核开发 - 内存管理
- linux驱动开发--内核空间中内存的申请与释放
- 从 Linux 内核访问用户空间内存
- 【Linux开发】GCC 4.8及以上支持内存非法访问检查
- linux驱动开发--I/O内存的访问流程
- 【Linux开发】GCC 4.8及以上支持内存非法访问检查
- 从 Linux 内核访问用户空间内存
- 从 Linux 内核访问用户空间内存