關於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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章