关于IO内存

驱动模块通过申请物理内存–>映射虚拟地址给到应用程序使用的过程解析!.


什么是IO内存.

外设的 SFR(特殊功能寄存器) 编址与内存的编址是同一个地址空间,叫做IO内存。
Linux 内核运行后,开启了 MMU(内存管理单元),所以不能直接访问 CPU 的物理地址,也就是说,不能直接使用物理地址访问系统的 IO 内存。必须将物理地址转换为虚拟地址,内核通过虚拟地址来访问系统的 IO 内存。

有MMU的芯片:x86、ARM9以上的CPU
无MMU的芯片:单片机、ARM Cortex-M系列

IO内存的使用方法.

  1. 驱动模块的编写与硬件是息息相关的,所以第一步应该分析硬件电路;(寻找IO资源)
  2. 查找CPU芯片数据手册,获取相关寄存器地址及配置方法;
  3. 控制相关状态,如LED驱动模块的GPIO引脚输出状态等等;

申请物理内存区–>映射得到虚拟地址–>对虚拟地址进行控制;

步骤:
安装驱动:申请物理内存区做一个资源->通过IO内存的动态映射得到虚拟地址
卸载驱动:释放物理内存区->解除IO内存的动态映射

相关源码截取及解析.

内存的申请和映射同样是初始化函数中完成;

//声明资源指针
static struct resource *led_res;

//声明io内存映射指针
static void __iomem		*gpioe_base_va;		
static void __iomem		*gpioe_out_va;		
static void __iomem		*gpioe_outenb_va;	
static void __iomem		*gpioe_altfn0_va;
static void __iomem		*gpioe_altfn1_va;

static void __iomem		*gpioc_base_va;		
static void __iomem		*gpioc_out_va;		
static void __iomem		*gpioc_outenb_va;	
static void __iomem		*gpioc_altfn0_va;
static void __iomem		*gpioc_altfn1_va;

    ...
    ...
    ...

//入口函数
static int __init gec6818_led_init(void)
{
    int ret = 0;
	ret=alloc_chrdev_region(&led_dev_num,0,1,"myled");    //动态申请设备号

        ...
        ...
        ...

    //申请IO内存资源,申请成功还需要映射,否则还是物理地址
    //#define request_mem_region(start,n,name)
    led_res = request_mem_region(0xC001E000, 0x28, "GPIOE");    //0x00~0x24+4 = 0x28
    if(NULL == led_res)
    {
        ret = -ENOMEM;
        printk("request_mem_region error\n");
        goto err_request_mem_region;
    }

    //IO内存的动态映射,将物理地址映射到虚拟地址
    //ioremap(cookie,size)
    gpioe_base_va = ioremap(0xC001E000, 0x28);  //映射基址
    if(NULL == gpioe_base_va)   //映射失败
    {
        ret = -ENOMEM;
        printk("ioremap error\n");
        goto err_ioremap;
    }

    led_res = request_mem_region(0xC001C000, 0x28, "GPIOC");
    if(NULL == led_res)
    {
        ret = -ENOMEM;
        printk("request_mem_region error\n");
        goto err_request_mem_region_c;
    }
    gpioc_base_va = ioremap(0xC001C000, 0x28);  //映射基址
    if(NULL == gpioc_base_va)   //映射失败
    {
        ret = -ENOMEM;
        printk("ioremap error\n");
        goto err_ioremap_c;
    }

    //地址偏移
    gpioe_out_va    = gpioe_base_va;        //GPIOEOUT    0xC001E000
    gpioe_outenb_va = gpioe_base_va + 0x04; //GPIOEOUTENB 0xC001E004
    gpioe_altfn0_va = gpioe_base_va + 0x20; //GPIOEALTFN0 0xC001E020
    gpioe_altfn1_va = gpioe_base_va + 0x24; //GPIOEALTFN1 0xC001E024

    gpioc_out_va    = gpioc_base_va;        //GPIOCOUT    0xC001C000
    gpioc_outenb_va = gpioc_base_va + 0x04; //GPIOCOUTENB 0xC001C004
    gpioc_altfn0_va = gpioc_base_va + 0x20; //GPIOCALTFN0 0xC001C020
    gpioc_altfn1_va = gpioc_base_va + 0x24; //GPIOCALTFN1 0xC001C024

    printk("<3>""device num: %d\n", MAJOR(led_dev_num));
    printk("<3>""device last num: %d\n", MINOR(led_dev_num));

	return 0;

err_ioremap_c:
    release_mem_region(0xC001C000, 0x28);       //释放物理内存区

err_request_mem_region_c:
    iounmap(gpioe_base_va);

err_ioremap:
    release_mem_region(0xC001E000, 0x28);       //释放物理内存区

            ...
            ...
            ...

    return ret;
}

函数解析.

申请物理内存区 request_mem_region() 头文件: #include <linux/ioport.h>

代码原型:
request_mem_region()

参数作用
start物理起始地址
n申请内存区的大小,以字节为单位
name自定义内存区的名字,若申请成功,就可以在/proc/iomem当中找到该名字
返回值成功:返回非NULL指针,失败:返回NULL指针

注意:分配只是申请这段内存的物理地址,还不能使用,需要经过映射才可以使用。

释放物理内存区 release_mem_region() 头文件: #include <linux/ioport.h>

代码原型:
release_mem_region()

参数作用
start物理起始地址
len申请内存区的大小,以字节为单位
返回值

IO内存的动态映射 ioremap() 头文件: #include <linux/io.h>

代码原型:
ioremap()

参数作用
offset要映射的物理内存区的起始地址
size物理地址的范围
返回值虚拟地址的指针

解除IO内存的动态映射 iounmap() 头文件: #include <linux/io.h>

代码原型:
iounmap()

参数作用
cookie虚拟地址的指针
返回值

虚拟地址访问的函数族.

从虚拟地址读取数据 头文件: #inlude <linux/io.h>

具体应用如下:

static int led_open(struct inode *inode, struct file *file)
{   
    iowrite32((ioread32(gpioe_altfn0_va)&(~(3<<26))), gpioe_altfn0_va);
    iowrite32((ioread32(gpioe_outenb_va)|(1<<13)), gpioe_outenb_va);

    iowrite32((ioread32(gpioc_altfn1_va)&(~(3<<2)))|(1<<2), gpioc_altfn1_va);
    iowrite32((ioread32(gpioc_outenb_va)|(1<<17)), gpioc_outenb_va);

    iowrite32((ioread32(gpioc_altfn0_va)&(~(0xf<<14)))|(5<<14), gpioc_altfn0_va);
    iowrite32((ioread32(gpioc_outenb_va)|(3<<7)), gpioc_outenb_va);
	printk("led_open\n");

    return 0;
}

演变流程:裸机代码–>驱动代码

//第一形态
*(volatile unsigned int *)gpioc_altfn1_va&=~(3<<2);
*(volatile unsigned int *)gpioc_altfn1_va|= (1<<2);
//第二形态
int addr;
addr  = ioread32(gpioc_altfn1_va)&(~(3<<2));
addr |= 1<<2;
iowrite32(addr, gpioc_altfn1_va);
//第三形态
iowrite32((ioread32(gpioc_altfn1_va)&(~(3<<2)))|(1<<2),gpioc_altfn1_va);

我的GITHUB

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大大棋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值