日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux 字符设备驱动开发基础(五)—— ioremap() 函数解析

發布時間:2023/12/9 linux 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux 字符设备驱动开发基础(五)—— ioremap() 函数解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、?ioremap() 函數基礎概念

?? ? ? 幾乎每一種外設都是通過讀寫設備上的寄存器來進行的,通常包括控制寄存器、狀態寄存器和數據寄存器三大類,外設的寄存器通常被連續地編址。根據CPU體系結構的不同,CPU對IO端口的編址方式有兩種:

a -- I/O 映射方式(I/O-mapped)

? ? ? ?典型地,如X86處理器為外設專門實現了一個單獨的地址空間,稱為"I/O地址空間"或者"I/O端口空間",CPU通過專門的I/O指令(如X86的IN和OUT指令)來訪問這一空間中的地址單元。

b -- 內存映射方式(Memory-mapped)

  RISC指令系統的CPU(如ARM、PowerPC等)通常只實現一個物理地址空間,外設I/O端口成為內存的一部分。此時,CPU可以象訪問一個內存單元那樣訪問外設I/O端口,而不需要設立專門的外設I/O指令。

? ? ?但是,這兩者在硬件實現上的差異對于軟件來說是完全透明的,驅動程序開發人員可以將內存映射方式的I/O端口和外設內存統一看作是"I/O內存"資源。

? ? 一般來說,在系統運行時,外設的I/O內存資源的物理地址是已知的,由硬件的設計決定。但是CPU通常并沒有為這些已知的外設I/O內存資源的物理地址預定義虛擬地址范圍,驅動程序并不能直接通過物理地址訪問I/O內存資源,而必須將它們映射到核心虛地址空間內(通過頁表),然后才能根據映射所得到的核心虛地址范圍,通過訪內指令訪問這些I/O內存資源。


? ? ? Linux在io.h頭文件中聲明了函數ioremap(),用來將I/O內存資源的物理地址映射到核心虛地址空間(3GB-4GB)中(這里是內核空間),原型如下:

1、ioremap函數

? ? ?ioremap宏定義在asm/io.h內:

#define ioremap(cookie,size) ? ? ? ? ? __ioremap(cookie,size,0)

__ioremap函數原型為(arm/mm/ioremap.c):

void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);

參數:

phys_addr:要映射的起始的IO地址

size:要映射的空間的大小

flags:要映射的IO空間和權限有關的標志

該函數返回映射后的內核虛擬地址(3G-4G). 接著便可以通過讀寫該返回的內核虛擬地址去訪問之這段I/O內存資源。


2、iounmap函數

? ? iounmap函數用于取消ioremap()所做的映射,原型如下:

? ? ?void iounmap(void * addr);


二、?ioremap() 相關函數解析

? ? ? ? 在將I/O內存資源的物理地址映射成核心虛地址后,理論上講我們就可以象讀寫RAM那樣直接讀寫I/O內存資源了。為了保證驅動程序的跨平臺的可移植性,我們應該使用Linux中特定的函數來訪問I/O內存資源,而不應該通過指向核心虛地址的指針來訪問

讀寫I/O的函數如下所示:

a -- writel()

? ? ???writel()往內存映射的 I/O 空間上寫數據,wirtel() ?I/O 上寫入 32 位數據 (4字節)。

?原型:void writel (unsigned char data , unsigned short addr )

b --?readl()

? ? ? readl() 從內存映射的 I/O 空間上讀數據,readl 從 I/O 讀取 32 位數據 ( 4 字節 )。
?
原型:unsigned char readl (unsigned int addr )

變量 ? ?addr ?是 I/O 地址。

返回值 : 從 I/O 空間讀取的數值。

具體定義如下:

[cpp]?view plaincopy
  • #define?readb?__raw_readb??
  • #define?readw(addr)?__le16_to_cpu(__raw_readw(addr))??
  • #define?readl(addr)?__le32_to_cpu(__raw_readl(addr))??
  • #ifndef?__raw_readb??
  • static?inline?u8?__raw_readb(const?volatile?void?__iomem?*addr)??
  • {??
  • ????return?*(const?volatile?u8?__force?*)?addr;??
  • }??
  • #endif??
  • ???
  • #ifndef?__raw_readw??
  • static?inline?u16?__raw_readw(const?volatile?void?__iomem?*addr)??
  • {??
  • ????return?*(const?volatile?u16?__force?*)?addr;??
  • }??
  • #endif??
  • ???
  • #ifndef?__raw_readl??
  • static?inline?u32?__raw_readl(const?volatile?void?__iomem?*addr)??
  • {??
  • ????return?*(const?volatile?u32?__force?*)?addr;??
  • }??
  • #endif??
  • ???
  • #define?writeb?__raw_writeb??
  • #define?writew(b,addr)?__raw_writew(__cpu_to_le16(b),addr)??
  • #define?writel(b,addr)?__raw_writel(__cpu_to_le32(b),addr)??

  • 三、使用實例

    ? ? ? ? 還是拿我們寫PWM驅動的實例來解析

    1、這里我們先定義了一些寄存器,這里使用的地址均是物理地址:

    [cpp]?view plaincopy
  • #define?GPD0CON???????0x114000a0????
  • #define?TIMER_BASE????0x139D0000???????????????
  • #define?TCFG0?????????0x0000???????????????????
  • #define?TCFG1?????????0x0004????????????????????????????????
  • #define?TCON??????????0x0008?????????????????
  • #define?TCNTB0????????0x000C??????????????
  • #define?TCMPB0????????0x0010???

  • 2、為了使用內存映射,我們需先定義指針用來保存內存映射后的地址:

    [cpp]?view plaincopy
  • static?unsigned?int?*gpd0con;????
  • static?void?*timer_base;????
  • 注意:這里timer_base 指針指向的類型設為 void, 主要因為上面使用了地址偏移,使用void 更有利于我們使用;


    3、使用ioremap() 函數進行內存映射,并將映射的地址賦給我們剛才定義的指針

    [cpp]?view plaincopy
  • gpd0con?=?ioremap(GPD0CON,4);????
  • timer_base?=?ioremap(TIMER_BASE,0x14);???

  • 4、得到地址后,可以調用 writel() 、readl() 函數進行相應的操作

    [cpp]?view plaincopy
  • writel?((readl(gpd0con)&~(0xf<<0))?|?(0x2<<0),gpd0con);????
  • writel?((readl(timer_base?+TCFG0??)&~(0xff<<0))?|?(0xff?<<0),timer_base?+TCFG0);?????
  • writel?((readl(timer_base?+TCFG1?)&~(0xf<<0))?|?(0x2?<<0),timer_base?+TCFG1?);?????
  • ????
  • writel?(500,?timer_base?+TCNTB0??);????
  • writel?(250,?timer_base?+TCMPB0?);????
  • writel?((readl(timer_base?+TCON?)&~(0xf<<0))?|?(0x2?<<0),timer_base?+TCON?);?????
  • 可以看到,這里先從相應的地址中讀取數據,修改完畢后,再利用writel函數進行數據寫入。

    總結

    以上是生活随笔為你收集整理的Linux 字符设备驱动开发基础(五)—— ioremap() 函数解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。