驱动中的IO访问
1,内存空间与IO空间
1)I/O 映射方式(I/O-mapped) 典型地,如X86处理器为外设专门实现了一个单独的地址空间,称为"I/O地址空间"或者"I/O端口空间",CPU通过专门的I/O指令(如X86的IN和OUT指令)来访问这一空间中的地址单元。
2)内存映射方式(Memory-mapped) RISC指令系统的CPU(如ARM、PowerPC等)通常只实现一个物理地址空间(不提供IO空间),外设I/O端口成为内存的一部分。此时,CPU可以象访问一个内存单元那样访问外设I/O端口,而不需要设立专门的外设I/O指令。
X86处理器虽然提供了IO空间,但是我们仍然可以挂接在内存空间中,CPU就可以像访问内存单元那样方法外设IO端口,不需要专门的IO指令,因此内存空间是必须的,而IO空间是可有可无的。
2,内存管理单元(MMU)
MMU辅助操作系统进行内存管理,提供虚拟地址和物理地址映射、内存访问权限保护和Cache缓存控制等硬件支持。
TLB(Translation Lookaside Buffer)转换旁路缓存,缓存少量的虚拟地址与物理地址的转换关系,是转换表的Cache,被称为“块表”;
TTW(Translation Table walk)转换表漫游,当TLB中没有想要的地址转换关系时,则需要对内存中的转换表(为多级页表)的访问获得虚拟地址和物理地址的对应关系,TTW成功后,结构应写入TLB。
MMU具有的虚拟地址和物理地址映射、内存访问权限保护等功能,使得Linux系统能单独为系统的每个用户进程分配独立的内存空间并保证用户空间不能访问内核空间的地址,为操作系统的虚拟内存管理模块提供了硬件基础。
3,linux内存管理
对于包含MMU的处理器,linux系统提供复杂的存储管理系统,使得进程所能访问的内存到达4GB。
在linux中,进程的4GB内存空间被分为两部分,用户空间(0~3G)和内核空间(3~4G),用户进程只有通过系统调用才可以访问内核空间,不能直接访问。
每个进程的用户空间是完全独立的,用户进程各自有不同的页表。而内核空间不会跟着进程改变,是固定的。内核空间的虚拟地址到物理地址映射是被所有进程共享的,内核的虚拟地址独立于其他程序。
linux中1GB的内核地址空间又被分为物理内存映射区,虚拟内存分配区,高端页面映射区,专用页面映射区,系统保留区。
4,内存的存取
1)用户空间内存动态申请
用户空间动态申请内存函数为malloc(),对应的释放是free(),linux系统中C库的malloc函数一般通过brk和mmap两个系统调用从内核申请内存。
2)内核空间内存的动态申请
kmalloc()和_ _get_free_pages() 申请的内存位于上面说的物理内存映射区,而且物理上是连续的,他们与真实的物理地址只有一个固定的偏移。
vmalloc()在虚拟空间给一块连续内存区,在物理上不一定连续,且申请的虚拟地内存和物理内存没有简单的转换关系。
void *kmalloc(size_t size, int flags);
kmalloc对应kfree()释放内存,kmalloc()底层是用_ _get_free_pages() 实现的,_ _get_free_pages() 是linux内核最底层用于获取空闲内存的方法。
5,设备IO端口和IO内存的访问流程
各种硬件设备通常会提供一组寄存器来控制、读写设备和获取设备状态,即控制寄存器,数据寄存器和状态寄存器。这些寄存器如果位于IO空间则被称为IO端口,当位于内存空间时,对应的内存被称为IO内存。
1)IO端口的两种访问流程
2)IO内存的访问流程
两种访问方式的例子(来自https://blog.csdn.net/dndxhej/article/details/8374205):
利用IO Port方式
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/slab.h> /* kmalloc() */ #include <linux/fs.h> /* everything... */ #include <linux/errno.h> /* error codes */ #include <linux/types.h> /* size_t */ #include <linux/proc_fs.h> #include <linux/fcntl.h> /* O_ACCMODE */ #include <linux/seq_file.h> #include <linux/cdev.h> #include <linux/ioport.h> #include <mach/regs-gpio.h> #include <asm/system.h> /* cli(), *_flags */ #include <asm/uaccess.h> /* copy_*_user */ #include <asm/io.h> #define LED_NUM 4 struct led_dev { struct cdev dev; unsigned port; unsigned long offset; }; struct led_dev led[4]; dev_t dev = 0; static struct resource *led_resource; int led_open(struct inode *inode, struct file *filp) { struct led_dev *led; /* device information */ led = container_of(inode->i_cdev, struct led_dev, dev); filp->private_data = led; /* for other methods */ return 0; /* success */ } int led_release(struct inode *inode, struct file *filp) { return 0; } ssize_t led_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { return 0; } ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { char data; struct led_dev *led; u32 value; printk(KERN_INFO "debug by baikal: led dev write\n"); led = (struct led_dev *)filp->private_data; copy_from_user(&data,buf,count); if(data == '0') { printk(KERN_INFO "debug by baikal: led off\n"); value = inl((unsigned)(S3C2410_GPBDAT)); outl(value | 1<<led->offset,(unsigned)(S3C2410_GPBDAT)); //value = ioread32(led->base); //iowrite32( value | 1<<led->offset, led->base); } else { printk(KERN_INFO "debug by baikal: led on\n"); value = inl((unsigned)(S3C2410_GPBDAT)); outl(value & ~(1<<led->offset),(unsigned)(S3C2410_GPBDAT)); //value = ioread32(led->base); //iowrite32( value & ~(1<<led->offset), led->base); } } struct file_operations led_fops = { .owner = THIS_MODULE, .read = led_read, .write = led_write, //.ioctl = led_ioctl, .open = led_open, .release = led_release, }; static int led_init(void) { int result, i; result = alloc_chrdev_region(&dev, 0, LED_NUM,"LED"); if (result < 0) { printk(KERN_WARNING "LED: can't get major %d\n", MAJOR(dev)); return result; } led_resource = request_region(0x56000014,0x4,"led"); if(led_resource == NULL) { printk(KERN_ERR " Unable to register LED I/O addresses\n"); return -1; } for(i = 0; i < LED_NUM; i++) { cdev_init( &led[i].dev, &led_fops); //led[i].port = ioport_map(0x56000014,0x4); //led[i].base = ioremap(0x56000014,0x4); led[i].offset = i + 5; //leds GPB5\6\7\8 led[i].dev.owner = THIS_MODULE; led[i].dev.ops = &led_fops; result = cdev_add(&led[i].dev,MKDEV(MAJOR(dev),i),1); if(result < 0) { printk(KERN_ERR "LED: can't add led%d\n",i); return result; } } return 0; } static void led_exit(void) { int i; release_region(0x56000014,0x4); for( i = 0; i < LED_NUM; i++) { //iounmap(led[i].base); cdev_del(&led[i].dev); } unregister_chrdev_region(dev, LED_NUM); } module_init(led_init); module_exit(led_exit); MODULE_AUTHOR("Baikal"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Simple LED Driver");
利用IO Mem方式
#include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/slab.h> /* kmalloc() */ #include <linux/fs.h> /* everything... */ #include <linux/errno.h> /* error codes */ #include <linux/types.h> /* size_t */ #include <linux/proc_fs.h> #include <linux/fcntl.h> /* O_ACCMODE */ #include <linux/seq_file.h> #include <linux/cdev.h> #include <linux/ioport.h> #include <asm/system.h> /* cli(), *_flags */ #include <asm/uaccess.h> /* copy_*_user */ #include <asm/io.h> #define LED_NUM 4 struct led_dev { struct cdev dev; void __iomem *base; unsigned long offset; }; struct led_dev led[4]; dev_t dev = 0; int led_open(struct inode *inode, struct file *filp) { struct led_dev *led; /* device information */ led = container_of(inode->i_cdev, struct led_dev, dev); filp->private_data = led; /* for other methods */ return 0; /* success */ } int led_release(struct inode *inode, struct file *filp) { return 0; } ssize_t led_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { return 0; } ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { char data; struct led_dev *led; u32 value; printk(KERN_INFO "debug by baikal: led dev write\n"); led = (struct led_dev *)filp->private_data; copy_from_user(&data,buf,count); if(data == '0') { printk(KERN_INFO "debug by baikal: led off\n"); value = ioread32(led->base); iowrite32( value | 1<<led->offset, led->base); } else { printk(KERN_INFO "debug by baikal: led on\n"); value = ioread32(led->base); iowrite32( value & ~(1<<led->offset), led->base); } } struct file_operations led_fops = { .owner = THIS_MODULE, .read = led_read, .write = led_write, //.ioctl = led_ioctl, .open = led_open, .release = led_release, }; static int led_init(void) { int result, i; result = alloc_chrdev_region(&dev, 0, LED_NUM,"LED"); if (result < 0) { printk(KERN_WARNING "LED: can't get major %d\n", MAJOR(dev)); return result; } for(i = 0; i < LED_NUM; i++) { cdev_init( &led[i].dev, &led_fops); request_mem_region(0x56000014,0x4,"led"); led[i].base = ioremap(0x56000014,0x4); led[i].offset = i + 5; //leds GPB5\6\7\8 led[i].dev.owner = THIS_MODULE; led[i].dev.ops = &led_fops; result = cdev_add(&led[i].dev,MKDEV(MAJOR(dev),i),1); if(result < 0) { printk(KERN_ERR "LED: can't add led%d\n",i); return result; } } return 0; } static void led_exit(void) { int i; release_mem_region(0x56000014,0x4); for( i = 0; i < LED_NUM; i++) { iounmap(led[i].base); cdev_del(&led[i].dev); } unregister_chrdev_region(dev, LED_NUM); } module_init(led_init); module_exit(led_exit); MODULE_AUTHOR("Baikal"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Simple LED Driver");
更多I/O操作及代码分析参考
http://www.cnblogs.com/reality-soul/p/6126376.html
https://blog.csdn.net/ce123_zhouwei/article/details/7204458