我們在 ubuntu 的 home/nfs/07 目錄下新建 led.c 文件,可以在上次實驗的驅動代碼基礎上進行修改,以 下代碼為完整的驅動代碼。 我們已經學會了雜項設備驅動編寫的基本流程,其實需求已經完成了一半了,我們已經注冊了雜項設 備,并生成了設備節點。接下來我們要完成控制 BEEP 的邏輯操作,那么控制 BEEP 就涉及到了對寄存器的 操作,但是對寄存器的操作我們是不能直接訪問的,因為 linux 不能直接訪問我們的物理地址,需要把物理 地址先映射成虛擬地址,我們完成這一步轉換需要用到 ioremap 函數。 完整的驅動文件如下所示: /* * @Descripttion: 基于雜項設備的 LED 驅動 * @version: * @Author: * @Date: 2021-02-23 13:54:49 */ #include //初始化頭文件 #include //最基本的文件,支持動態添加和卸載模塊。 #include #include //文件系統頭文件,定義文件表結構(file,buffer_head,m_inode 等) #include //包含了 copy_to_user、copy_from_user 等內核訪問用戶進程內存地址的函 數定義。 #include //包含了 ioremap、iowrite 等內核訪問 IO 內存等函數的定義。 #include //驅動要寫入內核,與內核相關的頭文件 #define GPIO_DR 0xfdd60000 //LED 物理地址,通過查看原理圖得知 unsigned int *vir_gpio_dr; //存放映射完的虛擬地址的首地址 /** * @name: misc_read * @test: 從設備中讀取數據,當用戶層調用函數 read 時,對應的,內核驅動就會調用這個函數。 * @msg: * @param {structfile} *file file 結構體 * @param {char__user} *ubuf 這是對應用戶層的 read 函數的第二個參數 void *buf * @param {size_t} size 對應應用層的 read 函數的第三個參數 * @param {loff_t} *loff_t 這是用于存放文件的偏移量的,回想一下系統編程時,讀寫文件的操作都會使 偏移量往后移。 * @return {*} 當返回正數時,內核會把值傳給應用程序的返回值。一般的,調用成功會返回成功讀取 的字節數。 如果返回負數,內核就會認為這是錯誤,應用程序返回-1 */ ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t) { printk("misc_read\n "); return 0; } /** * @name: misc_write * @test: 往設備寫入數據,當用戶層調用函數 write 時,對應的,內核驅動就會調用這個函數。 * @msg: * @param {structfile} * filefile 結構體 * @param {constchar__user} *ubuf 這是對應用戶層的 write 函數的第二個參數 const void *buf * @param {size_t} size 對應用戶層的 write 函數的第三個參數 count。 * @param {loff_t} *loff_t 這是用于存放文件的偏移量的,回想一下系統編程時,讀寫文件的操作都會使 偏移量往后移。 * @return {*} 當返回正數時,內核會把值傳給應用程序的返回值。一般的,調用成功會返回成功讀取 的字節數。 如果返回負數,內核就會認為這是錯誤,應用程序返回-1。 */ ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t) { /*應用程序傳入數據到內核空間,然后控制蜂鳴器的邏輯,在此添加*/ // kbuf 保存的是從應用層讀取到的數據 char kbuf[64] = {0}; // copy_from_user 從應用層傳遞數據給內核層 if(copy_from_user(kbuf,ubuf,size)!= 0) { // copy_from_user 傳遞失敗打印 printk("copy_from_user error \n "); return -1; } //打印傳遞進內核的數據 printk("kbuf is %d\n ",kbuf[0]); if(kbuf[0]==1) //傳入數據為 1 ,LED 亮 { *vir_gpio_dr = 0x80008000; } else if(kbuf[0]==0) //傳入數據為 0,LED 滅 *vir_gpio_dr = 0x80000000; return 0; } /** * @name: misc_release * @test: 當設備文件被關閉時內核會調用這個操作,當然這也可以不實現,函數默認為 NULL。關閉設 備永遠成功。 * @msg: * @param {structinode} *inode 設備節點 * @param {structfile} *file filefile 結構體 * @return {0} */ int misc_release(struct inode *inode,struct file *file){ printk("hello misc_relaease bye bye \n "); return 0; } /** * @name: misc_open * @test: 在操作設備前必須先調用 open 函數打開文件,可以干一些需要的初始化操作。 * @msg: * @param {structinode} *inode 設備節點 * @param {structfile} *file filefile 結構體 * @return {0} */ int misc_open(struct inode *inode,struct file *file){ printk("hello misc_open\n "); return 0; } //文件操作集 struct file_operations misc_fops={ .owner = THIS_MODULE, .open = misc_open, .release = misc_release, .read = misc_read, .write = misc_write, }; //miscdevice 結構體 struct miscdevice misc_dev = { .minor = MISC_DYNAMIC_MINOR, .name = "hello_misc", .fops = &misc_fops, }; static int misc_init(void) { int ret; //注冊雜項設備 ret = misc_register(&misc_dev); if(ret<0) { printk("misc registe is error \n"); } printk("misc registe is succeed \n"); //將物理地址轉化為虛擬地址 vir_gpio_dr = ioremap(GPIO_DR,4); if(vir_gpio_dr == NULL) { printk("GPIO_DR ioremap is error \n"); return EBUSY; } printk("GPIO_DR ioremap is ok \n"); return 0; } static void misc_exit(void){ //卸載雜項設備 misc_deregister(&misc_dev); iounmap(vir_gpio_dr); printk(" misc gooodbye! \n"); } module_init(misc_init); module_exit(misc_exit); MODULE_LICENSE("GPL"); 更多內容請關注:北京迅為 |