Linux字符设备驱动详解
文章目錄
- 系列文章目錄
- 前言
- 驅(qū)動目錄
- 正文
- Linux內(nèi)核是怎么設(shè)計(jì)字符設(shè)備
- 第一步:填充并保存硬件接口
- 第二步:創(chuàng)建設(shè)備文件(節(jié)點(diǎn))
- 第三步:用戶空間編程
- 野火echo命令測試
- 原子APP文件測試
- 總結(jié)
系列文章目錄
Linux字符設(shè)備驅(qū)動詳解
Linux字符設(shè)備驅(qū)動詳解二(使用設(shè)備驅(qū)動模型)
Linux字符設(shè)備驅(qū)動詳解三(使用class)
Linux字符設(shè)備驅(qū)動詳解四(使用自屬的xbus驅(qū)動總線)
Linux字符設(shè)備驅(qū)動詳解五(使用platform虛擬平臺總線)
Linux字符設(shè)備驅(qū)動詳解六(設(shè)備樹實(shí)現(xiàn)RGB燈驅(qū)動)
Linux字符設(shè)備驅(qū)動詳解七(“插件“設(shè)備樹實(shí)現(xiàn)RGB燈驅(qū)動)
前言
很久沒有認(rèn)真寫一篇博客了,剛好最近學(xué)習(xí)了Linux字符設(shè)備驅(qū)動,好記性不如爛筆頭,當(dāng)然是要抓緊記下來,在開始之前安利一位師弟寫的幾篇博客,寫得很不錯(cuò)。本文主要來自正點(diǎn)原子、野火Linux教程及本人理解,若有侵權(quán)請及時(shí)聯(lián)系本人刪除。
從單片機(jī)到ARM Linux驅(qū)動——Linux驅(qū)動入門篇
Linux字符設(shè)備驅(qū)動開發(fā)(2)——讓開發(fā)板上的燈閃爍
驅(qū)動目錄
/dev/xxx
正文
Linux內(nèi)核是怎么設(shè)計(jì)字符設(shè)備
結(jié)合前兩篇文章,我這里講的就比較簡潔,下圖是字符設(shè)備的整體框圖。將其分為三步。
第一步:填充并保存硬件接口
這一步就是驅(qū)動文件所實(shí)現(xiàn)的,以原子LED驅(qū)動為例,第一步主要完成通過操作寄存器填充硬件接口,然后通過cdev_init函數(shù)保存接口file_operation到cdev中,通過cdev_add函數(shù),根據(jù)哈希函數(shù)保存cdev到probes哈希表中,方便內(nèi)核查找file_operation使用。而這兩個(gè)函數(shù)下面代碼register_chrdev(LED_MAJOR, LED_NAME, &led_fops) 已經(jīng)幫我們都完成了,詳情參考Linux源碼。
下圖說明了Linux使用一張哈希表(數(shù)組+鏈表)來管理設(shè)備號。
talk is cheap, show you the code(原子驅(qū)動文件)
通過Makefile編譯,make后生成名為“l(fā)ed.ko”的驅(qū)動模塊文件,使用insmod命令加載模塊,到此完成第一步
第二步:創(chuàng)建設(shè)備文件(節(jié)點(diǎn))
第二步使用mknod命令創(chuàng)建設(shè)備文件(節(jié)點(diǎn))。接下來我們就可以在用戶態(tài)操作這個(gè)文件(節(jié)點(diǎn)),比如向此設(shè)備文件(節(jié)點(diǎn))寫入數(shù)據(jù)。
sudo mknod /dev/xxx c 244 0
注意inode上的file_operation并不是自己構(gòu)造的file_operation,而是字符設(shè)備通用的def_chr_fops,那么自己構(gòu)建的file_operation等在應(yīng)用程序調(diào)用open函數(shù)之后,才會綁定在文件上。
第三步:用戶空間編程
完成前兩步我們就可以通過寫APP文件,或者使用echo命令操作設(shè)備文件(節(jié)點(diǎn))。
野火echo命令測試
野火驅(qū)動文件
#include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h>#include <linux/fs.h> #include <linux/uaccess.h> #include <asm/io.h>#define DEV_MAJOR 0 /* 動態(tài)申請主設(shè)備號 */ #define DEV_NAME "red_led" /*led設(shè)備名字 *//* GPIO虛擬地址指針 */ static void __iomem *IMX6U_CCM_CCGR1; static void __iomem *SW_MUX_GPIO1_IO04; static void __iomem *SW_PAD_GPIO1_IO04; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR;static int led_open(struct inode *inode, struct file *filp) {return 0; }static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {return -EFAULT; }static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {unsigned char databuf[10];if(cnt >10)cnt =10;/*從用戶空間拷貝數(shù)據(jù)到內(nèi)核空間*/if(copy_from_user(databuf, buf, cnt)){return -EIO;}if(!memcmp(databuf,"on",2)) { iowrite32(0 << 4, GPIO1_DR); } else if(!memcmp(databuf,"off",3)) {iowrite32(1 << 4, GPIO1_DR);}/*寫成功后,返回寫入的字?jǐn)?shù)*/return cnt; }static int led_release(struct inode *inode, struct file *filp) {return 0; }/* 自定義led的file_operations 接口*/ static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release, };int major = 0; static int __init led_init(void) {/* GPIO相關(guān)寄存器映射 */IMX6U_CCM_CCGR1 = ioremap(0x20c406c, 4);SW_MUX_GPIO1_IO04 = ioremap(0x20e006c, 4);SW_PAD_GPIO1_IO04 = ioremap(0x20e02f8, 4);GPIO1_GDIR = ioremap(0x0209c004, 4);GPIO1_DR = ioremap(0x0209c000, 4);/* 使能GPIO1時(shí)鐘 */iowrite32(0xffffffff, IMX6U_CCM_CCGR1);/* 設(shè)置GPIO1_IO04復(fù)用為普通GPIO*/iowrite32(5, SW_MUX_GPIO1_IO04);/*設(shè)置GPIO屬性*/iowrite32(0x10B0, SW_PAD_GPIO1_IO04);/* 設(shè)置GPIO1_IO04為輸出功能 */iowrite32(1 << 4, GPIO1_GDIR);/* LED輸出高電平 */iowrite32(1<< 4, GPIO1_DR);/* 注冊字符設(shè)備驅(qū)動 */major = register_chrdev(DEV_MAJOR, DEV_NAME, &led_fops);printk(KERN_ALERT "led major:%d\n",major);return 0; }static void __exit led_exit(void) {/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO04);iounmap(SW_PAD_GPIO1_IO04);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* 注銷字符設(shè)備驅(qū)動 */unregister_chrdev(major, DEV_NAME); }module_init(led_init); module_exit(led_exit);MODULE_LICENSE("GPL2"); MODULE_AUTHOR("embedfire "); MODULE_DESCRIPTION("led_module"); MODULE_ALIAS("led_module");echo命令示例(注意echo命令的功能是在顯示器上顯示一段文字,該命令的一般格式為: echo [ -n ] 字符串,其中選項(xiàng)n表示輸出文字后不換行;字符串能加單引號,也能不加單引號。該篇Linux字符設(shè)備驅(qū)動詳解三(使用class))未加單引號
sudo sh -c "echo on >/dev/xxx" 開燈 sudo sh -c "echo on >/dev/xxx" 滅燈原子APP文件測試
原子APP代碼如下
#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h"#define LEDOFF 0 #define LEDON 1/** @description : main主程序* @param - argc : argv數(shù)組元素個(gè)數(shù)* @param - argv : 具體參數(shù)* @return : 0 成功;其他 失敗*/ int main(int argc, char *argv[]) {int fd, retvalue,i;char *filename;unsigned char databuf[1];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打開led驅(qū)動 */fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]); /* 要執(zhí)行的操作:打開或關(guān)閉,使用字符轉(zhuǎn)整形函數(shù)atoi()函數(shù)*//* 向/dev/led文件寫入數(shù)據(jù) */for(i=0;i<10;i++){retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}sleep(1);retvalue = write(fd, 0, sizeof(0));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}sleep(1);}retvalue = close(fd); /* 關(guān)閉文件 */if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0; }通過命令編譯生成ledApp這個(gè)應(yīng)用程序
arm-linux-gnueabihf-gcc ledApp.c -o ledApp命令測試
./ledApp /dev/led 1 開燈 ./ledApp /dev/led 0 滅燈當(dāng)我們在用戶空間調(diào)用open后發(fā)生了什么,在用戶空間調(diào)用open函數(shù)后,系統(tǒng)由用戶態(tài)進(jìn)入內(nèi)核態(tài)
- get_unused_fd_flags
- 為本次操作分配一個(gè)未使用過的文件描述符
- do_file_open
- 生成一個(gè)空白struct file結(jié)構(gòu)體
- 從文件系統(tǒng)中查找到文件對應(yīng)的inode
- do_dentry_open
在do_dentry_open函數(shù)中調(diào)用下面的open函數(shù)
- def_chr_fops->chrdev_open
總結(jié)
完成上面三步后就完成了LED字符設(shè)備的驅(qū)動編寫,其他的字符設(shè)備可參考此驅(qū)動。
用戶空間調(diào)用open函數(shù)打開某個(gè)設(shè)備文件后,在進(jìn)程中會為設(shè)備文件分配一個(gè)未使用過的文件描述符,并且生成一個(gè)空白struct file結(jié)構(gòu)體,然后從文件系統(tǒng)中查找到文件對應(yīng)的inode,這里的inode也就是第二步自己在文件系統(tǒng)中創(chuàng)建的設(shè)備節(jié)點(diǎn),然后把該inode的i_fop賦值給進(jìn)程中的struct file的f_op,也就是說此時(shí)進(jìn)程已經(jīng)找到了設(shè)備節(jié)點(diǎn)了,但是還沒有調(diào)用我們自己寫的驅(qū)動,即真正的file_operation接口。
接下來為了調(diào)用真正的file_operation接口,我們從內(nèi)核哈希表cdev_map中,根據(jù)設(shè)備號查找自己注冊的sturct cdev,獲取cdev中的file_operation接口,把cdev中的file_operation接口賦值給struct file的f_op,此時(shí)進(jìn)程已經(jīng)可以最終調(diào)用自己實(shí)現(xiàn)的file_operation接口中的open函數(shù),至此進(jìn)程獲得了該設(shè)備文件的自己實(shí)現(xiàn)的讀寫等操作。
注意glibc庫的fopen函數(shù)、系統(tǒng)調(diào)用open函數(shù)和自己實(shí)現(xiàn)的硬件接口open函數(shù)不能混淆。在字符設(shè)備驅(qū)動中系統(tǒng)調(diào)用open函數(shù)最終調(diào)用的是自己實(shí)現(xiàn)的硬件接口open函數(shù)。
最后,本篇基于Linux早期內(nèi)核(2.4之前),沒有統(tǒng)一的設(shè)備驅(qū)動模型,后面介紹Linux內(nèi)核2.6版本以后的設(shè)備驅(qū)動模型,即使用掛載在/sys目錄下的sysfs。
詳情請閱讀:Linux字符設(shè)備驅(qū)動詳解二(使用設(shè)備驅(qū)動模型)
總結(jié)
以上是生活随笔為你收集整理的Linux字符设备驱动详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 音频处理软件推荐,Linux
- 下一篇: 【人工智能 一种现代方法】搜索-复习