幾乎每一種外設(shè)都是通過讀寫設(shè)備上的寄存器來進(jìn)行的,通常包括控制寄存器、狀態(tài)寄存器和數(shù)據(jù)寄存器三大類,外設(shè)的寄存器通常被連續(xù)地編址。根據(jù)CPU體系結(jié)構(gòu)的不同,CPU對(duì)IO端口的編址方式有兩種: (1)I/O映射方式(I/O-mapped) 典型地,如X86處理器為外設(shè)專門實(shí)現(xiàn)了一個(gè)單獨(dú)的地址空間,稱為"I/O地址空間"或者"I/O端口空間",CPU通過專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元。 (2)內(nèi)存映射方式(Memory-mapped) RISC指令系統(tǒng)的CPU(如ARM、PowerPC等)通常只實(shí)現(xiàn)一個(gè)物理地址空間,外設(shè)I/O端口成為內(nèi)存的一部分。此時(shí),CPU可以象訪問一個(gè)內(nèi)存單元那樣訪問外設(shè)I/O端口,而不需要設(shè)立專門的外設(shè)I/O指令。 但是,這兩者在硬件實(shí)現(xiàn)上的差異對(duì)于軟件來說是完全透明的,驅(qū)動(dòng)程序開發(fā)人員可以將內(nèi)存映射方式的I/O端口和外設(shè)內(nèi)存統(tǒng)一看作是"I/O內(nèi)存"資源。 一般來說,在系統(tǒng)運(yùn)行時(shí),外設(shè)的I/O內(nèi)存資源的物理地址是已知的,由硬件的設(shè)計(jì)決定。但是CPU通常并沒有為這些已知的外設(shè)I/O內(nèi)存資源的物理地址預(yù)定義虛擬地址范圍,驅(qū)動(dòng)程序并不能直接通過物理地址訪問I/O內(nèi)存資源,而必須將它們映射到核心虛地址空間內(nèi)(通過頁表),然后才能根據(jù)映射所得到的核心虛地址范圍,通過訪內(nèi)指令訪問這些I/O內(nèi)存資源(也即是我們可以像讀寫RAM那樣直接讀寫I/O內(nèi)存(外設(shè)寄存器)資源了)。 為了配置寄存器,我們需要知道寄存器在操作系統(tǒng)中的虛擬地址,因?yàn)轵?qū)動(dòng)中要使用的是虛擬地址而非物理地址。在s3c2410中獲得GPIO各個(gè)寄存器的方法有兩種。 第一種是靜態(tài)映射法:使用系統(tǒng)初始化已經(jīng)設(shè)置好的虛擬地址,這種方法在操作系統(tǒng)啟動(dòng)過程中,頁表已經(jīng)生成,可以直接使用,這時(shí)候可以使用內(nèi)核導(dǎo)出函數(shù)在arch/arm/plat-s3c24xx/Gpio.c s3c2410_gpio_cfgpin s3c2410_gpio_getcfg s3c2410_gpio_setpin s3c2410_gpio_getpin等函數(shù)配置寄存器GPIO引腳功能 以setpin函數(shù)為例: void s3c2410_gpio_setpin(unsigned int pin, unsigned int to) { void __iomem *base = S3C24XX_GPIO_BASE(pin); /*算出端口所在組虛擬基址*/ unsigned long offs = S3C2410_GPIO_OFFSET(pin); /*算出端口所在組的偏移量(0-31)*/ unsigned long flags; unsigned long dat; local_irq_save(flags); dat = __raw_readl(base + 0x04); /*基址 + 0x04為DAT寄存器 */ dat &= ~(1 << offs); dat |= to << offs; __raw_writel(dat, base + 0x04); local_irq_restore(flags); } 分析代碼: void __iomem *base = S3C24XX_GPIO_BASE(pin); //定義一個(gè)指向所要配置的控制寄存器的基地址的指針,并賦初值(是個(gè)虛擬地址) /* #define S3C24XX_GPIO_BASE(x) S3C2410_GPIO_BASE(x) #define S3C2410_GPIO_BASE(pin) ((((pin)&~31)>>1)+ S3C24XX_VA_GPIO) #define S3C24XX_VA_GPIO ((S3C24XX_PA_GPIO-S3C24XX_PA_UART)+ S3C24XX_VA_UART) #define S3C24XX_VA_UART S3C_VA_UART #define S3C_VA_UART S3C_ADDR(0x01000000) #define S3C_ADDR(x)((void __iomem __force *)S3C_ADDR_BASE + (x)) #define S3C_ADDR_BASE (0xF4000000) */ __iomem是linux2.6.9內(nèi)核中加入的特性。是用來個(gè)表示指針是指向一個(gè)I/O的內(nèi)存空間。主要是為了驅(qū)動(dòng)程序的通用性考慮。由于不同的CPU體系結(jié)構(gòu)對(duì)I/O空間的表示可能不同。當(dāng)使用__iomem時(shí),編譯器會(huì)忽略對(duì)變量的檢查(因?yàn)橛玫氖莢oid __iomem)。若要對(duì)它進(jìn)行檢查,當(dāng)__iomem的指針和正常的指針混用時(shí),就會(huì)發(fā)出一些警告。 dat = __raw_readl(base + 0x04);//取原寄存器的值初始化dat dat &= ~(1 << offs); //掩碼運(yùn)算,清零對(duì)應(yīng)位 dat |= to << offs; //功能碼生成,配置對(duì)應(yīng)位的值 __raw_writel(dat, base + 0x04); //寫入控制寄存器 __raw_readl和__raw_writel: Linux對(duì)I/O的操作都定義在asm/io.h中,相應(yīng)的在arm平臺(tái)下,就在asm-arm/io.h中。 #define __raw_writel(v,a)(__chk_io_ptr(a), *(volatile unsigned int __force *)(a) = (v)) #define __raw_readl(a)(__chk_io_ptr(a), *(volatile unsigned int __force *)(a)) 在include\linux\compiler.h中: #ifdef __CHECKER__ …… extern void __chk_io_ptr(void __iomem *); #else …… # define __chk_io_ptr(x) (void)0 …… #endif __raw_readl(a)展開是:((void)0, *(volatile unsigned int _force *)(a))。在定義了__CHECKER__的時(shí)候先調(diào)用__chk_io_ptr檢查該地址,否則__chk_io_ptr什么也不做,* (volatile unsigned int _force *)(a)就是返回地址為a處的值。(void)xx的做法有時(shí)候是有用的,例如編譯器打開了檢查未使用的參數(shù)的時(shí)候需要將沒有用到的參數(shù)這么弄一下才能 編譯通過。CPU對(duì)I/O的物理地址的編程方式有兩種:一種是I/O映射,一種是內(nèi)存映射。 __raw_readl和__raw_writel等是原始的操作I/O的方法,由此派生出來的操作方法有:inb、outb、 _memcpy_fromio、readb、writeb、ioread8、iowrite8等。 靜態(tài)映射方式下虛擬地址的建立是由操作系統(tǒng)完成的,不需要用戶進(jìn)行干涉。在arch/arm/mach-s3c2440/mach-mini2440.c中,其中包含了io端口虛擬地址到物理地址映射的初始化函數(shù): MACHINE_START(S3C2440, "s3cARM s3c2440 development board") 。。。。。 .map_io = s3c2440_map_io, 。。。。。 MACHINE_END static void __init s3c2440_map_io(void) { /*該函數(shù)完成靜態(tài)的物理地址到虛擬地址的轉(zhuǎn)換*/ s3c24xx_init_io(s3c2440_iodesc, ARRAY_SIZE(s3c2440_iodesc)); s3c24xx_init_clocks(12000000); s3c24xx_init_uarts(s3c2440_uartcfgs, ARRAY_SIZE(s3c2440_uartcfgs)); } 第二種是動(dòng)態(tài)映射法,通過ioremap函數(shù)獲得相應(yīng)的虛擬地址。Linux在io.h頭文件中聲明了函數(shù)ioremap(),用來將I/O內(nèi)存資源的物理地址映射到核心虛地址空間(3GB-4GB)中,原型如下: void * ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags); iounmap函數(shù)用于取消ioremap()所做的映射,原型如下: void iounmap(void * addr); 這兩個(gè)函數(shù)都是實(shí)現(xiàn)在arch/XXX/mm/ioremap.c文件中。 我們要特別強(qiáng)調(diào)驅(qū)動(dòng)程序中mmap函數(shù)的實(shí)現(xiàn)方法。用mmap映射一個(gè)設(shè)備,意味著使用戶空間的一段地址關(guān)聯(lián)到設(shè)備內(nèi)存上,這使得只要程序在分配的地址范圍內(nèi)進(jìn)行讀取或者寫入,實(shí)際上就是對(duì)設(shè)備的訪問。 在Linux源代碼中進(jìn)行包含"ioremap"文本的搜索,發(fā)現(xiàn)真正出現(xiàn)的ioremap的地方相當(dāng)少。所以筆者追根索源地尋找I/O操作的物理地址轉(zhuǎn)換到虛擬地址的真實(shí)所在,發(fā)現(xiàn)Linux有替代ioremap的語句,但是這個(gè)轉(zhuǎn)換過程卻是不可或缺的。有沒有出現(xiàn)ioremap是次要的,關(guān)鍵問題是有無虛擬地址和物理地址的轉(zhuǎn)換! 下面的程序在啟動(dòng)的時(shí)候保留一段內(nèi)存,然后使用ioremap將它映射到內(nèi)核虛擬空間,同時(shí)又用remap_page_range映射到用戶虛擬空間,這樣一來,內(nèi)核和用戶都能訪問。如果在內(nèi)核虛擬地址將這段內(nèi)存初始化串"abcd",那么在用戶虛擬地址能夠讀出來: /************mmap_ioremap.c**************/ #include #include #include #include #include #include #include MODULE_PARM(mem_start, "i"); MODULE_PARM(mem_size, "i"); static int mem_start = 101, mem_size = 10; static char *reserve_virt_addr; static int major; int mmapdrv_open(struct inode *inode, struct file *file); int mmapdrv_release(struct inode *inode, struct file *file); int mmapdrv_mmap(struct file *file, struct vm_area_struct *vma); static struct file_operations mmapdrv_fops = { owner: THIS_MODULE, mmap: mmapdrv_mmap, open: mmapdrv_open, release:mmapdrv_release, }; int init_module(void) { if ((major = register_chrdev(0, "mmapdrv", &mmapdrv_fops)) < 0) { printk("mmapdrv: unable to register character device\n"); return ( - EIO); } printk("mmap device major = %d\n", major); printk("high memory physical address 0x%ldM\n", virt_to_phys(high_memory) / 1024 / 1024); reserve_virt_addr = ioremap(mem_start *1024 * 1024, mem_size *1024 * 1024); printk("reserve_virt_addr = 0x%lx\n", (unsigned long)reserve_virt_addr); if (reserve_virt_addr) { int i; for (i = 0; i < mem_size *1024 * 1024; i += 4) { reserve_virt_addr = 'a'; reserve_virt_addr[i + 1] = 'b'; reserve_virt_addr[i + 2] = 'c'; reserve_virt_addr[i + 3] = 'd'; } } else { unregister_chrdev(major, "mmapdrv"); return - ENODEV; } return 0; } /* remove the module */ void cleanup_module(void) { if (reserve_virt_addr)iounmap(reserve_virt_addr); unregister_chrdev(major, "mmapdrv"); return ; } int mmapdrv_open(struct inode *inode, struct file *file) { MOD_INC_USE_COUNT; return (0); } int mmapdrv_release(struct inode *inode, struct file *file) { MOD_DEC_USE_COUNT; return (0); } int mmapdrv_mmap(struct file *file, struct vm_area_struct *vma) { unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; unsigned long size = vma->vm_end - vma->vm_start; if (size > mem_size *1024 * 1024) { printk("size too big\n"); return ( - ENXIO); } offset = offset + mem_start * 1024 * 1024; /* we do not want to have this area swapped out, lock it */ vma->vm_flags |= VM_LOCKED; if (remap_page_range(vma, vma->vm_start, offset, size, PAGE_SHARED)) { printk("remap page range failed\n"); return - ENXIO; } return (0); } remap_page_range函數(shù)的功能是構(gòu)造用于映射一段物理地址的新頁表,實(shí)現(xiàn)了內(nèi)核空間與用戶空間的映射,其原型如下: int remap_page_range(vma_area_struct *vma, unsigned long from, unsigned long to, unsigned long size, pgprot_tprot); 想?yún)⒓?a href="http://m.qingdxww.cn/keyword/嵌入式" target="_blank" class="relatedlink">嵌入式學(xué)習(xí)的朋友可聯(lián)系郭老師QQ754634522 使用mmap最典型的例子是顯示卡的驅(qū)動(dòng),將顯存空間直接從內(nèi)核映射到用戶空間將可提供顯存的讀寫效率。(在內(nèi)核驅(qū)動(dòng)程序的初始化階段,通過ioremap()將物理地址映射到內(nèi)核虛擬空間;在驅(qū)動(dòng)程序的mmap系統(tǒng)調(diào)用中,使用remap_page_range()將該塊ROM映射到用戶虛擬空間。這樣內(nèi)核空間和用戶空間都能訪問這段被映射后的虛擬地址。) |