linux cached释放_正点原子Linux第四十一章嵌入式Linux LED驱动开发实验
1)資料下載:點擊資料即可下載
2)對正點原子Linux感興趣的同學可以加群討論:935446741
3)關注正點原子公眾號,獲取最新資料更新
第四十一章嵌入式Linux LED驅動開發實驗
上一章我們詳細的講解了字符設備驅動開發步驟,并且用一個虛擬的chrdevbase設備為例帶領大家完成了第一個字符設備驅動的開發。本章我們就開始編寫第一個真正的Linux字符設備驅動。在I.MX6U-ALPHA開發板上有一個LED燈,我們在裸機篇中已經編寫過此LED燈的裸機驅動,本章我們就來學習一下如何編寫Linux下的LED燈驅動。
41.1 Linux下LED燈驅動原理
Linux下的任何外設驅動,最終都是要配置相應的硬件寄存器。所以本章的LED燈驅動最終也是對I.MX6ULL的IO口進行配置,與裸機實驗不同的是,在Linux下編寫驅動要符合Linux的驅動框架。I.MX6U-ALPHA開發板上的LED連接到I.MX6ULL的GPIO1_IO03這個引腳上,因此本章實驗的重點就是編寫Linux下I.MX6UL引腳控制驅動。關于I.MX6ULL的GPIO詳細講解請參考第八章。
41.1.1地址映射
在編寫驅動之前,我們需要先簡單了解一下MMU這個神器,MMU全稱叫做Memory Manage Unit,也就是內存管理單元。在老版本的Linux中要求處理器必須有MMU,但是現在Linux內核已經支持無MMU的處理器了。MMU主要完成的功能如下:
①、完成虛擬空間到物理空間的映射。
②、內存保護,設置存儲器的訪問權限,設置虛擬存儲空間的緩沖特性。
我們重點來看一下第①點,也就是虛擬空間到物理空間的映射,也叫做地址映射。首先了解兩個地址概念:虛擬地址(VA,Virtual Address)、物理地址(PA,Physcical Address)。對于32位的處理器來說,虛擬地址范圍是2^32=4GB,我們的開發板上有512MB的DDR3,這512MB的內存就是物理內存,經過MMU可以將其映射到整個4GB的虛擬空間,如圖41.1.1所示:
圖41.1.1內存映射
物理內存只有512MB,虛擬內存有4GB,那么肯定存在多個虛擬地址映射到同一個物理地址上去,虛擬地址范圍比物理地址范圍大的問題處理器自會處理,這里我們不要去深究,因為MMU是很復雜的一個東西,后續有時間的話正點原子Linux團隊會專門做MMU專題教程。
Linux內核啟動的時候會初始化MMU,設置好內存映射,設置好以后CPU訪問的都是虛擬地址。比如I.MX6ULL的GPIO1_IO03引腳的復用寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的地址為0X020E0068。如果沒有開啟MMU的話直接向0X020E0068這個寄存器地址寫入數據就可以配置GPIO1_IO03的復用功能。現在開啟了MMU,并且設置了內存映射,因此就不能直接向0X020E0068這個地址寫入數據了。我們必須得到0X020E0068這個物理地址在Linux系統里面對應的虛擬地址,這里就涉及到了物理內存和虛擬內存之間的轉換,需要用到兩個函數:ioremap和iounmap。
1、ioremap函數
ioremap函數用于獲取指定物理地址空間對應的虛擬地址空間,定義在arch/arm/include/asm/io.h文件中,定義如下:
示例代碼41.1.1 ioremap函數
1 #define ioremap(cookie,size) __arm_ioremap((cookie),(size), MT_DEVICE)
2
3void __iomem * __arm_ioremap(phys_addr_t phys_addr,size_t size,unsignedint mtype)
4{
5return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
6}
ioremap是個宏,有兩個參數:cookie和size,真正起作用的是函數__arm_ioremap,此函數有三個參數和一個返回值,這些參數和返回值的含義如下:
phys_addr:要映射給的物理起始地址。
size:要映射的內存空間大小。
mtype:ioremap的類型,可以選擇MT_DEVICE、MT_DEVICE_NONSHARED、MT_DEVICE_CACHED和MT_DEVICE_WC,ioremap函數選擇MT_DEVICE。
返回值:__iomem類型的指針,指向映射后的虛擬空間首地址。
假如我們要獲取I.MX6ULL的IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器對應的虛擬地址,使用如下代碼即可:
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
static void __iomem* SW_MUX_GPIO1_IO03;
SW_MUX_GPIO1_IO03 = ioremap(GPIO1_GDIR_BASE, 4);
宏SW_MUX_GPIO1_IO03_BASE是寄存器物理地址,SW_MUX_GPIO1_IO03是映射后的虛擬地址。對于I.MX6ULL來說一個寄存器是4字節(32位)的,因此映射的內存長度為4。映射完成以后直接對SW_MUX_GPIO1_IO03進行讀寫操作即可。
2、iounmap函數
卸載驅動的時候需要使用iounmap函數釋放掉ioremap函數所做的映射,iounmap函數原型如下:
示例代碼41.1.2 iounmap函數原型
void iounmap (volatilevoid __iomem *addr)
iounmap只有一個參數addr,此參數就是要取消映射的虛擬地址空間首地址。假如我們現在要取消掉IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器的地址映射,使用如下代碼即可:
iounmap(SW_MUX_GPIO1_IO03);
41.1.2 I/O內存訪問函數
這里說的I/O是輸入/輸出的意思,并不是我們學習單片機的時候講的GPIO引腳。這里涉及到兩個概念:I/O端口和I/O內存。當外部寄存器或內存映射到IO空間時,稱為I/O端口。當外部寄存器或內存映射到內存空間時,稱為I/O內存。但是對于ARM來說沒有I/O空間這個概念,因此ARM體系下只有I/O內存(可以直接理解為內存)。使用ioremap函數將寄存器的物理地址映射到虛擬地址以后,我們就可以直接通過指針訪問這些地址,但是Linux內核不建議這么做,而是推薦使用一組操作函數來對映射后的內存進行讀寫操作。
1、讀操作函數
讀操作函數有如下幾個:
示例代碼41.1.2.1 讀操作函數
1 u8 readb(constvolatilevoid __iomem *addr)
2 u16 readw(constvolatilevoid __iomem *addr)
3 u32 readl(constvolatilevoid __iomem *addr)
readb、readw和readl這三個函數分別對應8bit、16bit和32bit讀操作,參數addr就是要讀取寫內存地址,返回值就是讀取到的數據。
2、寫操作函數
寫操作函數有如下幾個:
示例代碼41.1.2.2 寫操作函數
1void writeb(u8 value,volatilevoid __iomem *addr)
2void writew(u16 value,volatilevoid __iomem *addr)
3void writel(u32 value,volatilevoid __iomem *addr)
writeb、writew和writel這三個函數分別對應8bit、16bit和32bit寫操作,參數value是要寫入的數值,addr是要寫入的地址。
41.2 硬件原理圖分析
本章實驗硬件原理圖參考8.3小節即可。
41.3 實驗程序編寫
本實驗對應的例程路徑為:開發板光盤->2、Linux驅動例程->2_led。
本章實驗編寫Linux下的LED燈驅動,可以通過應用程序對I.MX6U-ALPHA開發板上的LED燈進行開關操作。
41.3.1 LED燈驅動程序編寫
新建名為“2_led”文件夾,然后在2_led文件夾里面創建VSCode工程,工作區命名為“led”。工程創建好以后新建led.c文件,此文件就是led的驅動文件,在led.c里面輸入如下內容:
示例代碼41.3.1.1 led.c驅動文件代碼
1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <asm/mach/map.h>
10 #include <asm/uaccess.h>
11 #include <asm/io.h>
12/***************************************************************
13 Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
14文件名 : led.c
15作者 : 左忠凱
16版本 : V1.0
17描述 : LED驅動文件。
18其他 : 無
19論壇 : http://www.openedv.com
20日志 : 初版V1.0 2019/1/30 左忠凱創建
21 ***************************************************************/
22 #define LED_MAJOR 200 /* 主設備號 */
23 #define LED_NAME "led" /* 設備名字 */
24
25 #define LEDOFF 0 /* 關燈 */
26 #define LEDON 1 /* 開燈 */
27
28/* 寄存器物理地址 */
29 #define CCM_CCGR1_BASE (0X020C406C)
30 #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
31 #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
32 #define GPIO1_DR_BASE (0X0209C000)
33 #define GPIO1_GDIR_BASE (0X0209C004)
34
35/* 映射后的寄存器虛擬地址指針 */
36staticvoid __iomem *IMX6U_CCM_CCGR1;
37staticvoid __iomem *SW_MUX_GPIO1_IO03;
38staticvoid __iomem *SW_PAD_GPIO1_IO03;
39staticvoid __iomem *GPIO1_DR;
40staticvoid __iomem *GPIO1_GDIR;
41
42/*
43 * @description : LED打開/關閉
44 * @param - sta : LEDON(0) 打開LED,LEDOFF(1) 關閉LED
45 * @return : 無
46 */
47void led_switch(u8 sta)
48{
49 u32 val =0;
50if(sta == LEDON){
51 val = readl(GPIO1_DR);
52 val &=~(1<<3);
53 writel(val, GPIO1_DR);
54}elseif(sta == LEDOFF){
55 val = readl(GPIO1_DR);
56 val|=(1<<3);
57 writel(val, GPIO1_DR);
58}
59}
60
61/*
62 * @description : 打開設備
63 * @param – inode : 傳遞給驅動的inode
64 * @param - filp : 設備文件,file結構體有個叫做private_data的成員變量
65 * 一般在open的時候將private_data指向設備結構體。
66 * @return : 0 成功;其他失敗
67 */
68staticint led_open(struct inode *inode,struct file *filp)
69{
70return0;
71}
72
73/*
74 * @description : 從設備讀取數據
75 * @param - filp : 要打開的設備文件(文件描述符)
76 * @param - buf : 返回給用戶空間的數據緩沖區
77 * @param - cnt : 要讀取的數據長度
78 * @param - offt : 相對于文件首地址的偏移
79 * @return : 讀取的字節數,如果為負值,表示讀取失敗
80 */
81static ssize_t led_read(struct file *filp,char __user *buf,
size_t cnt, loff_t *offt)
82{
83return0;
84}
85
86/*
87 * @description : 向設備寫數據
88 * @param - filp : 設備文件,表示打開的文件描述符
89 * @param - buf : 要寫給設備寫入的數據
90 * @param - cnt : 要寫入的數據長度
91 * @param - offt : 相對于文件首地址的偏移
92 * @return : 寫入的字節數,如果為負值,表示寫入失敗
93 */
94static ssize_t led_write(struct file *filp,constchar __user *buf,
size_t cnt, loff_t *offt)
95{
96int retvalue;
97unsignedchar databuf[1];
98unsignedchar ledstat;
99
100 retvalue = copy_from_user(databuf, buf, cnt);
101if(retvalue <0){
102 printk("kernel write failed!rn");
103return-EFAULT;
104}
105
106 ledstat = databuf[0]; /* 獲取狀態值 */
107
108if(ledstat == LEDON){
109 led_switch(LEDON); /* 打開LED燈 */
110}elseif(ledstat == LEDOFF){
111 led_switch(LEDOFF); /* 關閉LED燈 */
112}
113return0;
114}
115
116/*
117 * @description : 關閉/釋放設備
118 * @param – filp : 要關閉的設備文件(文件描述符)
119 * @return : 0 成功;其他失敗
120 */
121staticint led_release(struct inode *inode,struct file *filp)
122{
123return0;
124}
125
126/* 設備操作函數 */
127staticstruct file_operations led_fops ={
128.owner = THIS_MODULE,
129.open = led_open,
130.read = led_read,
131.write = led_write,
132.release = led_release,
133};
134
135/*
136 * @description : 驅動出口函數
137 * @param : 無
138 * @return : 無
139 */
140staticint __init led_init(void)
141{
142int retvalue =0;
143 u32 val =0;
144
145/* 初始化LED */
146/* 1、寄存器地址映射 */
147 IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
148 SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
149 SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
150 GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
151 GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);
152
153/* 2、使能GPIO1時鐘 */
154 val = readl(IMX6U_CCM_CCGR1);
155 val &=~(3<<26);/* 清除以前的設置 */
156 val |=(3<<26);/* 設置新值 */
157 writel(val, IMX6U_CCM_CCGR1);
158
159/* 3、設置GPIO1_IO03的復用功能,將其復用為
160 * GPIO1_IO03,最后設置IO屬性。
161 */
162 writel(5, SW_MUX_GPIO1_IO03);
163
164/* 寄存器SW_PAD_GPIO1_IO03設置IO屬性 */
165 writel(0x10B0, SW_PAD_GPIO1_IO03);
166
167/* 4、設置GPIO1_IO03為輸出功能 */
168 val = readl(GPIO1_GDIR);
169 val &=~(1<<3);/* 清除以前的設置 */
170 val |=(1<<3);/* 設置為輸出 */
171 writel(val, GPIO1_GDIR);
172
173/* 5、默認關閉LED */
174 val = readl(GPIO1_DR);
175 val |=(1<<3);
176 writel(val, GPIO1_DR);
177
178/* 6、注冊字符設備驅動 */
179 retvalue = register_chrdev(LED_MAJOR, LED_NAME,&led_fops);
180if(retvalue <0){
181 printk("register chrdev failed!rn");
182return-EIO;
183}
184return0;
185}
186
187/*
188 * @description : 驅動出口函數
189 * @param : 無
190 * @return : 無
191 */
192staticvoid __exit led_exit(void)
193{
194/* 取消映射 */
195 iounmap(IMX6U_CCM_CCGR1);
196 iounmap(SW_MUX_GPIO1_IO03);
197 iounmap(SW_PAD_GPIO1_IO03);
198 iounmap(GPIO1_DR);
199 iounmap(GPIO1_GDIR);
200
201/* 注銷字符設備驅動 */
202 unregister_chrdev(LED_MAJOR, LED_NAME);
203}
204
205 module_init(led_init);
206 module_exit(led_exit);
207 MODULE_LICENSE("GPL");
208 MODULE_AUTHOR("zuozhongkai");
第22~26行,定義了一些宏,包括主設備號、設備名字、LED開/關宏。
第29~33行,本實驗要用到的寄存器宏定義。
第36~40行,經過內存映射以后的寄存器地址指針。
第47~59行,led_switch函數,用于控制開發板上的LED燈亮滅,當參數sta為LEDON(0)的時候打開LED燈,sta為LEDOFF(1)的時候關閉LED燈。
第68~71行,led_open函數,為空函數,可以自行在此函數中添加相關內容,一般在此函數中將設備結構體作為參數filp的私有數據(filp->private_data)。
第81~84行,led_read函數,為空函數,如果想在應用程序中讀取LED的狀態,那么就可以在此函數中添加相應的代碼,比如讀取GPIO1_DR寄存器的值,然后返回給應用程序。
第94~114行,led_write函數,實現對LED燈的開關操作,當應用程序調用write函數向led設備寫數據的時候此函數就會執行。首先通過函數copy_from_user獲取應用程序發送過來的操作信息(打開還是關閉LED),最后根據應用程序的操作信息來打開或關閉LED燈。
第121~124行,led_release函數,為空函數,可以自行在此函數中添加相關內容,一般關閉設備的時候會釋放掉led_open函數中添加的私有數據。
第127~133行,設備文件操作結構體led_fops的定義和初始化。
第140~185行,驅動入口函數led_init,此函數實現了LED的初始化工作,147~151行通過ioremap函數獲取物理寄存器地址映射后的虛擬地址,得到寄存器對應的虛擬地址以后就可以完成相關初始化工作了。比如是能GPIO1時鐘、設置GPIO1_IO03復用功能、配置GPIO1_IO03的屬性等等。最后,最重要的一步!使用register_chrdev函數注冊led這個字符設備。
第192~202行,驅動出口函數led_exit,首先使用函數iounmap取消內存映射,最后使用函數unregister_chrdev注銷led這個字符設備。
第205~206行,使用module_init和module_exit這兩個函數指定led設備驅動加載和卸載函數。
第207~208行,添加LICENSE和作者信息。
41.3.2 編寫測試APP
編寫測試APP,led驅動加載成功以后手動創建/dev/led節點,應用APP通過操作/dev/led文件來完成對LED設備的控制。向/dev/led文件寫0表示關閉LED燈,寫1表示打開LED燈。新建ledApp.c文件,在里面輸入如下內容:
示例代碼41.3.2.1 ledApp.c文件代碼
1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8/***************************************************************
9 Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
10文件名 : ledApp.c
11作者 : 左忠凱
12版本 : V1.0
13描述 : LED驅測試APP。
14其他 : 無
15使用方法 :./ledtest /dev/led 0 關閉LED
16 ./ledtest /dev/led 1 打開LED
17論壇 : http://www.openedv.com
18日志 : 初版V1.0 2019/1/30 左忠凱創建
19 ***************************************************************/
20
21 #define LEDOFF 0
22 #define LEDON 1
23
24/*
25 * @description : main主程序
26 * @param - argc : argv數組元素個數
27 * @param - argv : 具體參數
28 * @return : 0 成功;其他失敗
29 */
30int main(int argc,char*argv[])
31{
32 int fd, retvalue;
33 char*filename;
34 unsignedchar databuf[1];
35
36 if(argc !=3){
37 printf("Error Usage!rn");
38 return-1;
39 }
40
41 filename = argv[1];
42
43 /* 打開led驅動 */
44 fd = open(filename, O_RDWR);
45 if(fd <0){
46 printf("file %s open failed!rn", argv[1]);
47 return-1;
48 }
49
50 databuf[0]= atoi(argv[2]);/* 要執行的操作:打開或關閉 */
51
52 /* 向/dev/led文件寫入數據 */
53 retvalue = write(fd, databuf,sizeof(databuf));
54 if(retvalue <0){
55 printf("LED Control Failed!rn");
56 close(fd);
57 return-1;
58 }
59
60 retvalue = close(fd);/* 關閉文件 */
61 if(retvalue <0){
62 printf("file %s close failed!rn", argv[1]);
63 return-1;
64 }
65 return0;
66}
ledApp.c的內容還是很簡單的,就是對led的驅動文件進行最基本的打開、關閉、寫操作等。
41.4 運行測試
41.4.1 編譯驅動程序和測試APP
1、編譯驅動程序
編寫Makefile文件,本章實驗的Makefile文件和第四十章實驗基本一樣,只是將obj-m變量的值改為led.o,Makefile內容如下所示:
示例代碼41.4.1.1 Makefile文件
1 KERNELDIR:= /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := led.o
......
11 clean:
12$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
第4行,設置obj-m變量的值為led.o。
輸入如下命令編譯出驅動模塊文件:
make-j32
編譯成功以后就會生成一個名為“led.ko”的驅動模塊文件。
2、編譯測試APP
輸入如下命令編譯測試ledApp.c這個測試程序:
arm-linux-gnueabihf-gcc ledApp.c -o ledApp
編譯成功以后就會生成ledApp這個應用程序。
41.4.2 運行測試
將上一小節編譯出來的led.ko和ledApp這兩個文件拷貝到rootfs/lib/modules/4.1.15目錄中,重啟開發板,進入到目錄lib/modules/4.1.15中,輸入如下命令加載led.ko驅動模塊:
depmod //第一次加載驅動的時候需要運行此命令
modprobe led.ko //加載驅動
驅動加載成功以后創建“/dev/led”設備節點,命令如下:
mknod /dev/led c 200 0
驅動節點創建成功以后就可以使用ledApp軟件來測試驅動是否工作正常,輸入如下命令打開LED燈:
./ledApp /dev/led 1 //打開LED燈
輸入上述命令以后觀察I.MX6U-ALPHA開發板上的紅色LED燈是否點亮,如果點亮的話說明驅動工作正常。在輸入如下命令關閉LED燈:
./ledApp /dev/led 0 //關閉LED燈
輸入上述命令以后觀察I.MX6U-ALPHA開發板上的紅色LED燈是否熄滅,如果熄滅的話說明我們編寫的LED驅動工作完全正常!至此,我們成功編寫了第一個真正的Linux驅動設備程序。
如果要卸載驅動的話輸入如下命令即可:
rmmodled.ko
總結
以上是生活随笔為你收集整理的linux cached释放_正点原子Linux第四十一章嵌入式Linux LED驱动开发实验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c调用python gensim包_Ju
- 下一篇: 运行android程序时显示stop,A