CPU对I/O端口的编制方式
设备通常会提供一组寄存器来用于控制设备、读写设备和获取设备状态,即控制寄存器、数据寄存器和状态寄存器。这些寄存器可能位于 I/O 空间,也可能位于内存空间。当位于 I/O 空间时,通常被称为 I/O 端口,位于内存空间时,对应的内存空间被称为 I/O 内存。
- I/O映射方式:X86处理器为外设实现了单独的地址空间,称IO地址,通过IO指令来访问;
- 内存映射方式:ARM、PowerPC等通常只实现一个物理地址空间,外设I/O端口为内存的一部分,CPU可以访问内存单元那样访问IO端口。
物理地址到虚拟地址的映射
内存管理单元(MMU)通常以页为单位进行处理,而不是字节,ioremap函数也同样属于页映射。
在内核中访问 I/O 内存之前,需首先使用 ioremap()函数将设备所处的物理地址映射到虚拟地址。ioremap()的原型如下:
void *ioremap(unsigned long offset, unsigned long size);
参数:
- 物理地址
- 要映射的空间的大小
返回值:页映射,返回虚拟地址
※同一物理地址可以多次ioremap映射,分配的虚拟空间地址各部相同,iounmap互补影响。
ioremap()与 vmalloc()类似,也需要建立新的页表,但是它并不进行 vmalloc()中所执行的内存分配行为。ioremap()返回一个特殊的虚拟地址,该地址可用来存取特定的物理地址范围。通过 ioremap()获得的虚拟地址应该被 iounmap()函数释放,其原型如下:
void iounmap(void * addr);
参数:
- addr是ioremap生成的虚拟地址
简单实现的驱动代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
//用于存放虚拟地址和物理地址
unsigned int virt_addr,phys_addr;
//用户存放三个寄存器的地址
unsigned int *GPL2CON,*GPL2DAT,*GPL2PUD;
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("SZA");
static void gpl2_device_init(void)
{
phys_addr = 0x11000100;
GPL2CON = (unsigned int*)ioremap(phys_addr,0x01);
GPL2DAT = GPL2CON + 1;
GPL2PUD = GPL2CON + 2;
printk("virt_addr is %x \n",virt_addr);
printk("GPL2CON is %x \n",(unsigned int)GPL2CON);
printk("GPL2DAT is %x \n",(unsigned int)GPL2DAT);
printk("GPL2PUD is %x \n",(unsigned int)GPL2PUD);
}
static void gpl2_configure(void)
{
*GPL2CON &= 0xFFFFFFF1;
*GPL2CON |= 0x00000001;
*GPL2PUD |=0x0003;
}
static void gpl2_on(void)
{
*GPL2DAT |= 0x01;
}
static void gpl2_off(void)
{
*GPL2DAT &= 0xfe;
}
static int led_gpl2_init(void)
{
printk("init!\n");
gpl2_device_init();//实现IO内存的映射
gpl2_configure();//配置GPL2为输出模式
gpl2_on();
printk("led gpl2 open\n");
return 0;
}
static void led_gpl2_exit(void)
{
printk("exit!\n");
gpl2_off();
printk("led gpl2 close\n");
}
module_init(led_gpl2_init);
module_exit(led_gpl2_exit);