字符設備驅動開發的基本步驟可以看上一篇,本節就以 chrdevbase 這個虛擬設備為例,完整的編寫一個字符設備驅動模塊。chrdevbase 不是實際存在的一個設備,方便講解字符設備的開發而引入的一個虛擬設備。chrdevbase 設備有兩個緩沖區,一個為讀緩沖區,一個為寫緩沖區,這兩個緩沖區的大小都為 100 字節。在應用程序中可以向 chrdevbase 設備的寫緩沖區中寫入數據,從讀緩沖區中讀取數據。chrdevbase 這個虛擬設備的功能很簡單,但是它包含了字符設備的最基本功能。
|需求目標
應用程序調用 open 函數打開 chrdevbase 這個設備,打開以后可以使用 write 函數向chrdevbase 的寫緩沖區 writebuf 中寫入數據(不超過 100 個字節),也可以使用 read 函數讀取讀緩沖區 readbuf 中的數據操作,操作完成以后應用程序使用 close 函數關閉 chrdevbase 設備。
|實現過程
1.新建文件一個nxp文件夾,然后把原廠內核文件復制過來
2、創建Linux_Drivers用于存放驅動文件,創建01_chrdevbase用于存放chrdevbase實驗文件,chrdevbase.c是底層驅動代碼,chrdevbaseApp.c是應用代碼,Makefile編譯底層驅動;
3、編寫代碼
chrdevbase.c文件
#include#include #include #include #include #include #define CHRDEVBASE_MAJOR 200 /* 主設備號 */ #define CHRDEVBASE_NAME "chrdevbase" /* 設備名 */ static char readbuf[100]; /* 讀緩沖區 */ static char writebuf[100]; /* 寫緩沖區 */ static char kerneldata[] = {"kernel data!"}; /* * @description : 打開設備 * @param - inode : 傳遞給驅動的inode * @param - filp : 設備文件,file結構體有個叫做private_data的成員變量 * 一般在open的時候將private_data指向設備結構體。 * @return : 0 成功;其他 失敗 */ static int chrdevbase_open(struct inode *inode, struct file *filp) { //printk("chrdevbase open! "); return 0; } /* * @description : 從設備讀取數據 * @param - filp : 要打開的設備文件(文件描述符) * @param - buf : 返回給用戶空間的數據緩沖區 * @param - cnt : 要讀取的數據長度 * @param - offt : 相對于文件首地址的偏移 * @return : 讀取的字節數,如果為負值,表示讀取失敗 */ static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int retvalue = 0; /* 向用戶空間發送數據 */ memcpy(readbuf, kerneldata, sizeof(kerneldata)); retvalue = copy_to_user(buf, readbuf, cnt); if(retvalue == 0){ printk("kernel senddata ok! "); }else{ printk("kernel senddata failed! "); } //printk("chrdevbase read! "); return 0; } /* * @description : 向設備寫數據 * @param - filp : 設備文件,表示打開的文件描述符 * @param - buf : 要寫給設備寫入的數據 * @param - cnt : 要寫入的數據長度 * @param - offt : 相對于文件首地址的偏移 * @return : 寫入的字節數,如果為負值,表示寫入失敗 */ static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) { int retvalue = 0; /* 接收用戶空間傳遞給內核的數據并且打印出來 */ retvalue = copy_from_user(writebuf, buf, cnt); if(retvalue == 0){ printk("kernel recevdata:%s ", writebuf); }else{ printk("kernel recevdata failed! "); } //printk("chrdevbase write! "); return 0; } /* * @description : 關閉/釋放設備 * @param - filp : 要關閉的設備文件(文件描述符) * @return : 0 成功;其他 失敗 */ static int chrdevbase_release(struct inode *inode, struct file *filp) { //printk("chrdevbase release! "); return 0; } /* * 設備操作函數結構體 */ static struct file_operations chrdevbase_fops = { .owner = THIS_MODULE, .open = chrdevbase_open, .read = chrdevbase_read, .write = chrdevbase_write, .release = chrdevbase_release, }; /* * @description : 驅動入口函數 * @param : 無 * @return : 0 成功;其他 失敗 */ static int __init chrdevbase_init(void) { int retvalue = 0; /* 注冊字符設備驅動 */ retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops); if(retvalue < 0){ printk("chrdevbase driver register failed "); } printk("chrdevbase init! "); return 0; } /* * @description : 驅動出口函數 * @param : 無 * @return : 無 */ static void __exit chrdevbase_exit(void) { /* 注銷字符設備驅動 */ unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME); printk("chrdevbase exit! "); } /* * 將上面兩個函數指定為驅動的入口和出口函數 */ module_init(chrdevbase_init); module_exit(chrdevbase_exit); /* * LICENSE和作者信息 */ MODULE_LICENSE("GPL"); MODULE_AUTHOR("zuozhongkai");
chrdevbaseApp.c文件
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" static char usrdata[] = {"usr data!"}; /* * @description : main主程序 * @param - argc : argv數組元素個數 * @param - argv : 具體參數 * @return : 0 成功;其他 失敗 */ int main(int argc, char *argv[]) { int fd, retvalue; char *filename; char readbuf[100], writebuf[100]; if(argc != 3){ printf("Error Usage! "); return -1; } filename = argv[1]; /* 打開驅動文件 */ fd = open(filename, O_RDWR); if(fd < 0){ printf("Can't open file %s ", filename); return -1; } if(atoi(argv[2]) == 1){ /* 從驅動文件讀取數據 */ retvalue = read(fd, readbuf, 50); if(retvalue < 0){ printf("read file %s failed! ", filename); }else{ /* 讀取成功,打印出讀取成功的數據 */ printf("read data:%s ",readbuf); } } if(atoi(argv[2]) == 2){ /* 向設備驅動寫數據 */ memcpy(writebuf, usrdata, sizeof(usrdata)); retvalue = write(fd, writebuf, 50); if(retvalue < 0){ printf("write file %s failed! ", filename); } } /* 關閉設備 */ retvalue = close(fd); if(retvalue < 0){ printf("Can't close file %s ", filename); return -1; } return 0; }
Makefile文件
KERNELDIR := /home/noah/linux/nxp/linux-imx-rel_imx_4.1.15_2.1.0_ga CURRENT_PATH := $(shell pwd) obj-m := chrdevbase.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
相關解析:
第 1 行,KERNELDIR 表示開發板所使用的 Linux 內核源碼目錄,使用絕對路徑,大家根據自己的實際情況填寫即可。 第2行,CURRENT_PATH表示當前路徑,直接通過運行“pwd”命令來獲取當前所處路徑。 第 3 行,obj-m 表示將 chrdevbase.c 這個文件編譯為 chrdevbase.ko 模塊。 第 8 行,具體的編譯命令,后面的 modules 表示編譯模塊,-C 表示將當前的工作目錄切換到指定目錄中,也就是 KERNERLDIR 目錄。M 表示模塊源碼目錄,“make modules”命令中加入 M=dir 以后程序會自動到指定的 dir 目錄中讀取模塊的源碼并將其編譯為.ko 文件。
4、編譯驅動
直接使用make命令會報錯的,因為kernel中沒有指定編譯器和架構,使用了默認的x86平臺編譯報錯。
5、修改內核的Makefile文件
直接定義ARCH和CROSS_COMPILE 這兩個的變量值為 arm 和 arm-linux-gnueabihf-
6、再次編譯驅動
編譯通過,會生成不少編譯文件;
7、編譯應用程序
應用程序只有一個文件,在ubuntu對應文件夾,直接輸入指令進行編譯:
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
8、SD卡啟動系統
插入SD卡,把撥碼開關調到SD卡啟動,然后ping一下網關,確認網絡通暢才能繼續的進行,如果ping不通就請看看前幾節;
9、啟動內核
在uboot界面輸入下面指令啟動系統,
tftp80800000zImage tftp 83000000 imx6ull-14x14-evk.dtb bootz 80800000 - 83000000
10、創建目錄并復制驅動文件
檢查開發板根文件系統中有沒有“/lib/modules/4.1.15”這個目錄,如果沒有的話自行創建。注意,“/lib/modules/4.1.15”這個目錄用來存放驅動模塊,使用modprobe 命令加載驅動模塊的時候,驅動模塊要存放在此目錄下?!?lib/modules”是通用的,不管你用的什么板子、什么內核,這部分是一樣的。
將 chrdevbase.ko 和 chrdevbaseAPP 復制到 rootfs/lib/modules/4.1.15 目錄中:
11、加載設備驅動
自制的根文件系統,有些命令是不支持的;
//加載驅動 insmodchrdevbase.ko // 查看驅動 lsmod // 指令查看devices信息 cat/proc/devices
效果如下圖:
12、創建設備節點文件
驅動加載成功需要在/dev 目錄下創建一個與之對應的設備節點文件,應用程序就是通過操作這個設備節點文件來完成對具體設備的操作。輸入如下命令創建/dev/chrdevbase 這個設備節點文件:
mknod /dev/chrdevbase c 200 0其中“mknod”是創建節點命令,“/dev/chrdevbase”是要創建的節點文件,“c”表示這是個字符設備,“200”是設備的主設備號,“0”是設備的次設備號。創建完成以后就會存在/dev/chrdevbase 這個文件,可以使用“ls /dev/chrdevbase -l”命令查看,結果如圖:
13、驗證讀寫
使用 chrdevbaseApp 軟件操作 chrdevbase 這個設備,看看讀寫是否正常,首先進行讀操作,輸入如下命令:
//讀 ./chrdevbaseApp /dev/chrdevbase 1 // 寫 ./chrdevbaseApp /dev/chrdevbase 2
相關解析:
三個參數“./chrdevbaseApp”、“/dev/chrdevbase”和“1”,這三個參數分別對應 argv[0]、argv[1]和 argv[2]。
第一個參數表示運行 chrdevbaseAPP 這個軟件,
第二個參數表示測試APP要打開/dev/chrdevbase這個設備。
第三個參數就是要執行的操作,1表示從chrdevbase中讀取數據,2 表示向 chrdevbase 寫數據。
效果如下:
14、卸載驅動模塊
如果不再使用某個設備的話可以將其驅動卸載掉,命令如下:
// 卸載chrdevbase.ko rmmod chrdevbase.ko // 查看驅動 lsmod
至此,chrdevbase 這個設備的整個驅動就驗證完成了,驅動工作正常。
審核編輯:湯梓紅
-
Linux
+關注
關注
87文章
11227瀏覽量
208925 -
函數
+關注
關注
3文章
4305瀏覽量
62430 -
驅動開發
+關注
關注
0文章
130瀏覽量
12062
原文標題:i.MX6ULL|字符設備驅動開發實踐
文章出處:【微信號:玩轉單片機,微信公眾號:玩轉單片機】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論