驅動模塊通過申請物理內存–>映射虛擬地址給到應用程序使用的過程解析!.
什麼是IO內存.
外設的 SFR(特殊功能寄存器)
編址與內存的編址是同一個地址空間,叫做IO內存。
Linux 內核運行後,開啓了 MMU(內存管理單元)
,所以不能直接訪問 CPU 的物理地址,也就是說,不能直接使用物理地址訪問系統的 IO 內存。必須將物理地址轉換爲虛擬地址,內核通過虛擬地址來訪問系統的 IO 內存。
有MMU的芯片:x86、ARM9以上的CPU
無MMU的芯片:單片機、ARM Cortex-M系列
IO內存的使用方法.
- 驅動模塊的編寫與硬件是息息相關的,所以第一步應該分析硬件電路;(尋找IO資源)
- 查找CPU芯片數據手冊,獲取相關寄存器地址及配置方法;
- 控制相關狀態,如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>
代碼原型:
參數 | 作用 |
---|---|
start | 物理起始地址 |
n | 申請內存區的大小,以字節爲單位 |
name | 自定義內存區的名字,若申請成功,就可以在/proc/iomem當中找到該名字 |
返回值 | 成功:返回非NULL指針,失敗:返回NULL指針 |
注意:分配只是申請這段內存的物理地址,還不能使用,需要經過映射纔可以使用。
釋放物理內存區 release_mem_region() 頭文件: #include <linux/ioport.h>
代碼原型:
參數 | 作用 |
---|---|
start | 物理起始地址 |
len | 申請內存區的大小,以字節爲單位 |
返回值 | 無 |
IO內存的動態映射 ioremap() 頭文件: #include <linux/io.h>
代碼原型:
參數 | 作用 |
---|---|
offset | 要映射的物理內存區的起始地址 |
size | 物理地址的範圍 |
返回值 | 虛擬地址的指針 |
解除IO內存的動態映射 iounmap() 頭文件: #include <linux/io.h>
代碼原型:
參數 | 作用 |
---|---|
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);