Linux驅動開發筆記(二) 基于字符設備驅動的I/O操作

文章目錄

  • 前言
  • 一、設備驅動的作用與本質
    • 1. 驅動的作用
    • 2. 有無操作系統的區別
  • 二、內存管理單元MMU
  • 三、相關函數
    • 1. ioremap( )
    • 2. iounmap( )
    • 3. class_create( )
    • 4. class_destroy( )
  • 四、GPIO的基本知識
    • 1. GPIO的寄存器進行讀寫操作流程
    • 2. 引腳復用
    • 2. 定義GPIO寄存器物理地址
  • 五、實驗代碼
    • 1. 宏定義出需要的地址
    • 2. 編寫LED字符設備結構體且初始化
    • 3. container_of( )函數
    • 4. file_operations結構體成員函數的實現
    • 5. 實驗效果


前言

??前段時間我們學習了字符驅動,并實現了字符的回環發送,這部分我們將進行I/O的操作學習,以萬能的點亮LED為例。

一、設備驅動的作用與本質

??直接操作寄存器點亮LED和通過驅動程序點亮LED最本質的區別就是有無使用操作系統。 有操作系統的存在則大大降低了應用軟件與硬件平臺的耦合度,它充當了我們硬件與應用軟件之間的紐帶, 使得應用軟件只需要調用驅動程序接口API就可以讓硬件去完成要求的開發,而應用軟件則不需要關心硬件到底是如何工作的。

1. 驅動的作用

??設備驅動與底層硬件直接打交道,按照硬件設備的具體工作方式讀寫設備寄存器, 完成設備的輪詢、中斷處理、DMA通信,進行物理內存向虛擬內存的映射,最終使通信設備能夠收發數據, 使顯示設備能夠顯示文字和畫面,使存儲設備能夠記錄文件和數據。

2. 有無操作系統的區別

??無操作系統(即裸機)時的設備驅動也就是直接操作寄存器的方式控制硬件,在這樣的系統中,雖然不存在操作系統,但是設備驅動是必須存在的。 一般情況下,對每一種設備驅動都會定義為一個軟件模塊,包含.h文件和.c文件,前者定義該設備驅動的數據結構并聲明外部函數, 后者進行設備驅動的具體實現。其他模塊需要使用這個設備的時候,只需要包含設備驅動的頭文件然后調用其中的外部接口函數即可。 比如我們在51或者STM32中直接看手冊查找對應的寄存器,然后往寄存器相應的位寫入數據0或1便可以實現LED的亮滅。
??有操作系統時的設備驅動反觀有操作系統。首先,驅動硬件工作的的部分仍然是必不可少的,其次,我們還需要將設備驅動融入內核。 為了實現這種融合,必須在所有的設備驅動中設計面向操作系統內核的接口,這樣的接口由操作系統規定,對一類設備而言結構一致,獨立于具體的設備,還是以led為例,我們就要將LED燈引腳對應的數據寄存器(物理地址)映射到程序的虛擬地址空間當中,然后我們就可以像操作寄存器一樣去操作我們的虛擬地址啦!

二、內存管理單元MMU

??MMU是一個實際的硬件,為編程提供了方便統一的內存空間抽象,MMU內部有一個專門存放頁表的頁表地址寄存器,該寄存器存放著頁表的具體位置,這使得只要程序在被分配的虛擬地址范圍內進行讀寫操作,實際上就是對設備(寄存器)的訪問,如下圖所示。他的主要作用是將虛擬地址翻譯成真實的物理地址同時管理和保護內存, 不同的進程有各自的虛擬地址空間,某個進程中的程序不能修改另外一個進程所使用的物理地址,以此使得進程之間互不干擾,相互隔離。 總體而言MMU具有如下功能:

  • 保護內存: MMU給一些指定的內存塊設置了讀、寫以及可執行的權限,這些權限存儲在頁表當中,MMU會檢查CPU當前所處的是特權模式還是用戶模式,如果和操作系統所設置的權限匹配則可以訪問,如果CPU要訪問一段虛擬地址,則將虛擬地址轉換成物理地址,否則將產生異常,防止內存被惡意地修改。
  • 提供方便統一的內存空間抽象,實現虛擬地址到物理地址的轉換: CPU可以運行在虛擬的內存當中,虛擬內存一般要比實際的物理內存大很多,使得CPU可以運行比較大的應用程序。

在這里插入圖片描述
在這里插入圖片描述

三、相關函數

??上面提到了物理地址到虛擬地址的轉換函數。包括ioremap()地址映射和取消地址映射iounmap()函數。

1. ioremap( )

//用于將物理內存地址映射到內核的虛擬地址空間
void __iomem *ioremap(phys_addr_t phys_addr, unsigned long size)//定義寄存器物理地址
#define GPIO0_BASE (0xFDD60000)
#define GPIO0_DR (GPIO0_BASE+0x0000)va_dr = ioremap(GPIO0_DR, 4);    // 將物理地址GPIO0_DR,映射給虛擬地址指針,這段地址大小為4個字節
val = ioread32(va_dr);			 //讀取該地址的值,保存到臨時變量,重新賦值
val |= (0x00400000);             // 設置GPIO0_A6引腳低電平
writel(val, va_dr);				 //把值重新寫入到被映射后的虛擬地址當中,實際是往寄存器中寫入了數據
  • 參數:
    • phys_addr:要映射的物理地址的起始地址
    • size:要映射的內存區域的大小(以字節為單位)
  • 返回值:
    • 如果成功,ioremap返回一個指向映射區域的虛擬地址的指針
    • 如果失敗,返回NULL

??在使用ioremap函數將物理地址轉換成虛擬地址之后,理論上我們便可以直接讀寫I/O內存,但是為了符合驅動的跨平臺以及可移植性, 我們應該使用linux中指定的函數(如:iowrite8()、iowrite16()、iowrite32()、ioread8()、ioread16()、ioread32()等)去讀寫I/O內存,如下表所示:

函數名功能
unsigned int ioread8(void __iomem *addr)讀取一個字節(8bit)
unsigned int ioread16(void __iomem *addr)讀取一個字(16bit)
unsigned int ioread32(void __iomem *addr)讀取一個雙字(32bit)
void iowrite8(u8 data, void __iomem *addr)寫入一個字節(8bit)
void iowrite16(u16 data, void __iomem *addr)寫入一個字(16bit)
void iowrite32(u32 data, void __iomem *addr)寫入一個雙字(32bit)

2. iounmap( )

//取消地址映射
void iounmap(void *addr)iounmap(va_dr);     //釋放掉ioremap映射之后的起始地址(虛擬地址)
  • 參數
    • addr: 需要取消ioremap映射之后的起始地址(虛擬地址)。
  • 返回值: 無

3. class_create( )

//提交目錄信息
#define class_create(owner, name) \
({static struct lock_class_key _key; \_class_create(owner, name, &_key); \
})
  • 參數
    • owner:THIS_MODULE (struct module結構體的首地址這個結構體存放了驅動的出口入口)
    • name:目錄名
  • 返回值
    • 成功:返回結構體首地址
    • 失敗:返回錯誤碼指針

注:IS_ERR(cls); 判斷是否為錯誤指針
??PTR_ERR(cls); 將錯誤碼指針轉換為錯誤碼

4. class_destroy( )

//注銷目錄信息
void class_destroy(struct class *cls);
  • 參數
    • cls:結構體首地址
  • 返回值:無

四、GPIO的基本知識

1. GPIO的寄存器進行讀寫操作流程

  • 使能GPIO時鐘(默認開啟,不用設置)
  • 設置引腳復用為GPIO(復位默認為GPIO,不用配置)
  • 設置引腳屬性(上下拉、速率、驅動能力,默認)
  • 控制GPIO引腳為輸出,并輸出高低電平

2. 引腳復用

??對于rockchip系類芯片,我們需要通過參考手冊以及數據手冊來確定引腳的復用功能。首先可以看到泰山派的小燈連接引腳,這里我們選擇GPIO1_B0_d。
在這里插入圖片描述
??通過查詢rk3568官方資料,可以看到該引腳的復用功能如下所示。在這里插入圖片描述
??再查找其復用功能存在于SYS_GRF寄存器,和復用相關的總共8個寄存器,如下圖所示:
在這里插入圖片描述

??查詢 Rockchip_RK3568_TRM_Part1 手冊,GRF_GPIO1B_IOMUX_L寄存器(由于GPIO1_b0是在低八位,下同),如下圖所示:
在這里插入圖片描述
??寄存器總共32位,高16位都是使能位,控制低16位的寫使能,低16位對應4個引腳,每個引腳占用3bits,不同的值引腳復用為不同功能。與此同時由[14:12]進行具體功能的設定。
??我們可以查看到SYS_GRF寄存器的復用功能基地址為0xFDC60000。
在這里插入圖片描述
??此時通過命令行輸入可以查詢到該寄存器的設置情況,可以看到這里默認是GPIO功能。

//目標地址為Address Base(0xfdc60000)+offset(0x0008)
io -r -4 0xfdc60008

2. 定義GPIO寄存器物理地址

??需要設置的寄存器的地址為base+offset,由下圖可以知道GPIO1的基地址為:0xFE740000
在這里插入圖片描述??接下來就是確定GPIO的是輸入還是輸出,我們這里需要的是GPIO_SWPORT_DDR_L。
在這里插入圖片描述在這里插入圖片描述
??可以看到GPIO_SWPORT_DDR_L的定義情況,這里我們可以重復上面提到的命令行,查看寄存器的設置情況,我們的b0應當是第1x7+1=8位。
在這里插入圖片描述
??數據寄存器選擇GPIO_SWPORT_DR_L,大致流程和上面一樣就不再贅述了。這里便完成了對GPIO的設置。

五、實驗代碼

在這里插入圖片描述

1. 宏定義出需要的地址

#define GPIO1_BASE (0xFE740000)//一個寄存器32位,其中高16位都是寫使能位,控制低16位的寫使能;低16位對應16個引腳,控制引腳的輸出電平
#define GPIO1_DR_L (GPIO0_BASE + 0x0000)  // GPIO0的低十六位引腳的數據寄存器地址
#define GPIO1_DR_H (GPIO0_BASE + 0x0004)  // GPIO0的高十六位引腳的數據寄存器地址//一個寄存器32位,其中高16位都是寫使能位,控制低16位的寫使能;低16位對應16個引腳,控制引腳的輸入輸出模式
#define GPIO1_DDR_L (GPIO0_BASE + 0x0008)   // GPIO0的低十六位引腳的數據方向寄存器地址
#define GPIO1_DDR_H (GPIO0_BASE + 0x000C)   // GPIO0的低十六位引腳的數據方向寄存器地址

2. 編寫LED字符設備結構體且初始化

//led字符設備結構體
struct led_chrdev {struct cdev dev;unsigned int __iomem *va_dr;    // 數據寄存器虛擬地址保存變量unsigned int __iomem *va_ddr;   // 數據方向寄存器虛擬地址保存變量unsigned int led_pin; 			// 引腳
};static struct led_chrdev led_cdev[DEV_CNT] = {{.led_pin = 8				//CPIO1_B0的偏移為8+0=8},
};

3. container_of( )函數

??在Linux驅動編程當中我們會經常和container_of()這個函數打交道,其宏定義實現如下所示:

#define container_of(ptr, type, member) ({                      \const typeof( ((type *)0)->member ) *__mptr = (ptr);    \(type *)( (char *)__mptr - offsetof(type,member) );})
  • 參數:
    • ptr: 結構體變量中某個成員的地址
    • type: 結構體類型
    • member: 該結構體變量的具體名字
  • 返回值: 結構體type的首地址

??原理其實很簡單,就是通過已知類型type的成員member的地址ptr,計算出結構體type的首地址。 type的首地址 = ptr - size ,需要注意的是它們的大小都是以字節為單位計算的,container_of( )函數的主要作用如下:

  • 判斷ptr 與 member 是否為同一類型
  • 計算size大小,結構體的起始地址 = (type *)((char *)ptr - size) (注:強轉為該結構體指針)

注:文件私有數據
??一般很多的linux驅動都會將文件的私有數據private_data指向設備結構體,其保存了用戶自定義設備結構體的地址。 自定義結構體的地址被保存在private_data后,可以通過讀、寫等操作通過該私有數據去訪問設備結構體中的成員, 這樣做體現了linux中面向對象的程序設計思想。

4. file_operations結構體成員函數的實現

static int led_chrdev_open(struct inode *inode, struct file *filp)
{unsigned int val = 0;struct led_chrdev *led_cdev = (struct led_chrdev *)container_of(inode->i_cdev, struct led_chrdev, dev);filp->private_data = container_of(inode->i_cdev, struct led_chrdev, dev);printk("open\n");//讀取數據方向寄存器val = ioread32(led_cdev->va_ddr);//設置數據方向寄存器為pin位可寫val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));	//設置數據方向寄存器為pin位輸出val |= ((unsigned int)0X1 << (led_cdev->led_pin));//寫入數據方向寄存器iowrite32(val,led_cdev->va_ddr);//讀取數據寄存器val = ioread32(led_cdev->va_dr);//設置數據寄存器為pin位可寫val |= ((unsigned int)0x1 << (led_cdev->led_pin+16));//設置數據寄存器為pin位高電平val |= ((unsigned int)0x1 << (led_cdev->led_pin));//寫入數據寄存器iowrite32(val, led_cdev->va_dr);return 0;
}

??這部分代碼位open_operations結構體的設置,其中container_of()函數和寄存器設置部分需要聯系前節4.2的介紹反復理解(筆者這里看了很久才頓悟)。

5. 實驗效果

最近臨時變化地點,后續補上。

免責聲明:本程序參考了野火和北京訊為科技的部分視頻資料,不作商用僅供學習,若有侵權和錯誤請聯系筆者刪除

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/15702.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/15702.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/15702.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【2024最新華為OD-C卷試題匯總】傳遞悄悄話的最長時間(100分) - 三語言AC題解(Python/Java/Cpp)

&#x1f36d; 大家好這里是清隆學長 &#xff0c;一枚熱愛算法的程序員 ? 本系列打算持續跟新華為OD-C卷的三語言AC題解 &#x1f4bb; ACM銀牌&#x1f948;| 多次AK大廠筆試 &#xff5c; 編程一對一輔導 &#x1f44f; 感謝大家的訂閱? 和 喜歡&#x1f497; 文章目錄 前…

東哥一句兄弟,你還當真了?

關注盧松松&#xff0c;會經常給你分享一些我的經驗和觀點。 你還真把自己當劉強東兄弟了?誰跟你是兄弟了?你在國外的房子又不給我住&#xff0c;你出去旅游也不帶上我!都成人年了&#xff0c;東哥一句客套話&#xff0c;別當真! 今天&#xff0c;東哥在高管會上直言&…

mysql內存結構

一&#xff1a;邏輯存儲結構&#xff1a;表空間->段->區->頁->行、 表空間&#xff1a;一個mysql實例對應多個表空間&#xff0c;用于存儲記錄&#xff0c;索引等數據。 段&#xff1a;分為數據段&#xff0c;索引段&#xff0c;回滾段。innoDB是索引組織表&…

215. 數組中的第K個最大元素(快速排序、堆排序)

根據這道題總結一下快速排序和堆排序&#xff0c;再根據這兩種方法寫這道題。 給定整數數組 nums 和整數 k&#xff0c;請返回數組中第 k 個最大的元素。 請注意&#xff0c;你需要找的是數組排序后的第 k 個最大的元素&#xff0c;而不是第 k 個不同的元素。 你必須設計并實…

qmt量化交易策略小白學習筆記第6期【qmt如何獲取股票歷史漲跌停價格】

qmt如何獲取股票歷史漲跌停價格 qmt更加詳細的教程方法&#xff0c;會持續慢慢梳理。 也可找尋博主的歷史文章&#xff0c;搜索關鍵詞查看解決方案 &#xff01; 感謝關注&#xff0c;需免費開通量化回測與咨詢實盤權限&#xff0c;可以和博主聯系&#xff01; 獲取股票歷史…

[數據結構] -- 單鏈表

&#x1f308; 個人主頁&#xff1a;白子寰 &#x1f525; 分類專欄&#xff1a;C打怪之路&#xff0c;python從入門到精通&#xff0c;數據結構&#xff0c;C語言&#xff0c;C語言題集&#x1f448; 希望得到您的訂閱和支持~ &#x1f4a1; 堅持創作博文(平均質量分82)&#…

c++編程14——STL(3)list

歡迎來到博主的專欄&#xff1a;c編程 博主ID&#xff1a;代碼小豪 文章目錄 list成員類型構造、析構、與賦值iterator元素訪問修改元素list的操作 list list的數據結構是一個鏈表&#xff0c;準確的說應該是一個雙向鏈表。這是一個雙向鏈表的節點結構&#xff1a; list的使用…

Vue學習筆記3——事件處理

事件處理 1、事件處理器&#xff08;1&#xff09;內聯事件處理器&#xff08;2&#xff09;方法事件處理器 2、事件參數3、事件修飾符 1、事件處理器 我們可以使用v-on 指令(簡寫為)來監聽DOM事件&#xff0c;并在事件觸發時執行對應的JavaScript。 用法: v-on:click"me…

JVM學習-執行引擎

執行引擎 執行引擎是Java虛擬機核心組成部分之一虛擬機是一個相對于物理機的概念&#xff0c;這兩種機器都有代碼執行能力&#xff0c;其區別是物理機的執行引擎是直接建立在處理器、緩存、指令集和操作系統層面上的&#xff0c;而虛擬機的執行引擎是由軟件自行實現的&#xf…

【算法】遞歸、搜索與回溯——簡介

簡介&#xff1a;遞歸、搜索與回溯&#xff0c;本節博客主要是簡單記錄一下關于“遞歸、搜索與回溯”的相關簡單概念&#xff0c;為后續算法做鋪墊。 目錄 1.遞歸1.1遞歸概念2.2遞歸意義2.3學習遞歸2.4寫遞歸代碼步驟 2.搜索3.回溯與剪枝 遞歸、搜索、回溯的關系&#xff1a; …

ICML2024 定義新隱私保護升級:DP-BITFIT新型微調技術讓AI模型學習更安全

DeepVisionary 每日深度學習前沿科技推送&頂會論文分享&#xff0c;與你一起了解前沿深度學習信息&#xff01; 引言&#xff1a;差分隱私在大模型微調中的重要性和挑戰 在當今的深度學習領域&#xff0c;大型預訓練模型的微調已成為提高各種任務性能的關鍵技術。然而&am…

推特熱帖:大語言模型自薦能夠替代的20種人類工作!快來看你是否需要轉行!

最近推特上有一個例子引起了廣泛的討論&#xff0c;事情的起因是這樣的&#xff1a;網友讓 GPT-4o 預測一下自己未來將會替代人類哪些工作&#xff1f; 這聽起來很有趣&#xff01;GPT-4o會給出什么樣的預測呢&#xff1f; 3.5研究測試&#xff1a;hujiaoai.cn 4研究測試&…

02-Linux【基礎篇】

一、Linux的目錄結構 1.基本介紹 Linux的文件系統采用層級式的樹狀目錄結構&#xff0c;在此結構中的最上層是根目錄"/"&#xff0c;然后在此目錄下再創建其他的目錄 深刻理解Linux樹狀文件目錄是非常重要的 記住一句經典的話&#xff1a;在Linux世界里&#xff…

如何在 DigitalOcean Droplet 云主機上創建 Ubuntu 服務器

在本文中&#xff0c;你將通過 DigitalOcean 的管理面板創建一個 Ubuntu 服務器&#xff0c;并將其配置為使用你的 SSH 密鑰。設置好服務器后&#xff0c;你可以在其上部署應用程序和網站。 本教程是DigitalOcean云課程簡介的一部分&#xff0c;它指導用戶完成將應用程序安全地…

win10右鍵沒有默認打開方式的選項的處理方法

問題描述 搞了幾個PDF書籍學習一下&#xff0c;不過我不想用默認的WPS打開&#xff0c;因為WPS太惡心人了&#xff0c;占用資源又高。我下載了個Sumatra PDF&#xff0c;這時候我像更改pdf文件默認的打開程序&#xff0c;發現右擊沒有這個選項。 問題解決 右擊文件–屬性–…

汽車以太網發展現狀及挑戰

一、汽車以太網技術聯盟 目前推動汽車以太網技術應用與發展的組織包括&#xff1a;OPEN Alliance&#xff08;One-Pair Ether-Net Alliance SIG&#xff09;聯盟&#xff0c;主要致力于汽車以太網推廣與使用&#xff0c;該聯盟通過推進 BroadR- Reach 單對非屏蔽雙絞線以太網傳…

設計新境界:大數據賦能UI的創新美學

設計新境界&#xff1a;大數據賦能UI的創新美學 引言 隨著大數據技術的蓬勃發展&#xff0c;它已成為推動UI設計創新的重要力量。大數據不僅為界面設計提供了豐富的數據資源&#xff0c;還賦予了設計師以全新的視角和工具來探索美學的新境界。本文將探討大數據如何賦能UI設計…

面試八股之JVM篇3.5——垃圾回收——G1垃圾回收器

&#x1f308;hello&#xff0c;你好鴨&#xff0c;我是Ethan&#xff0c;一名不斷學習的碼農&#xff0c;很高興你能來閱讀。 ??目前博客主要更新Java系列、項目案例、計算機必學四件套等。 &#x1f3c3;人生之義&#xff0c;在于追求&#xff0c;不在成敗&#xff0c;勤通…

1688. 比賽中的配對次數

題目&#xff1a; 給你一個整數 n &#xff0c;表示比賽中的隊伍數。比賽遵循一種獨特的賽制&#xff1a; 如果當前隊伍數是 偶數 &#xff0c;那么每支隊伍都會與另一支隊伍配對。總共進行 n / 2 場比賽&#xff0c;且產生 n / 2 支隊伍進入下一輪。 如果當前隊伍數為 奇數 …

python梯度下降法求解三元線性回歸系數,并繪制結果

import numpy as np import matplotlib.pyplot as plt # 生成隨機數據 np.random.seed(0) X1 2 * np.random.rand(100, 1) X2 3 * np.random.rand(100, 1) X3 4 * np.random.rand(100, 1) y 4 3 * X1 5 * X2 2 * X3 np.random.randn(100, 1) # 合并特征 X_b np.hsta…