日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux字符设备驱动详解

發(fā)布時間:2023/12/31 linux 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux字符设备驱动详解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • 系列文章目錄
  • 前言
  • 驅(qū)動目錄
  • 正文
    • Linux內(nèi)核是怎么設(shè)計字符設(shè)備
    • 第一步:填充并保存硬件接口
    • 第二步:創(chuàng)建設(shè)備文件(節(jié)點)
    • 第三步:用戶空間編程
      • 野火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è)備樹實現(xiàn)RGB燈驅(qū)動)
Linux字符設(shè)備驅(qū)動詳解七(“插件“設(shè)備樹實現(xiàn)RGB燈驅(qū)動)

前言

很久沒有認真寫一篇博客了,剛好最近學(xué)習(xí)了Linux字符設(shè)備驅(qū)動,好記性不如爛筆頭,當然是要抓緊記下來,在開始之前安利一位師弟寫的幾篇博客,寫得很不錯。本文主要來自正點原子、野火Linux教程及本人理解,若有侵權(quán)請及時聯(lián)系本人刪除。
從單片機到ARM Linux驅(qū)動——Linux驅(qū)動入門篇
Linux字符設(shè)備驅(qū)動開發(fā)(2)——讓開發(fā)板上的燈閃爍

驅(qū)動目錄

/dev/xxx

正文

Linux內(nèi)核是怎么設(shè)計字符設(shè)備

結(jié)合前兩篇文章,我這里講的就比較簡潔,下圖是字符設(shè)備的整體框圖。將其分為三步。

第一步:填充并保存硬件接口

這一步就是驅(qū)動文件所實現(xiàn)的,以原子LED驅(qū)動為例,第一步主要完成通過操作寄存器填充硬件接口,然后通過cdev_init函數(shù)保存接口file_operation到cdev中,通過cdev_add函數(shù),根據(jù)哈希函數(shù)保存cdev到probes哈希表中,方便內(nèi)核查找file_operation使用。而這兩個函數(shù)下面代碼register_chrdev(LED_MAJOR, LED_NAME, &led_fops) 已經(jīng)幫我們都完成了,詳情參考Linux源碼。

下圖說明了Linux使用一張哈希表(數(shù)組+鏈表)來管理設(shè)備號。

talk is cheap, show you the code(原子驅(qū)動文件)

#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <asm/mach/map.h> #include <asm/uaccess.h> #include <asm/io.h>#define LED_MAJOR 200 /* 主設(shè)備號 */ #define LED_NAME "led" /* 設(shè)備名字 */#define LEDOFF 0 /* 關(guān)燈 */ #define LEDON 1 /* 開燈 *//* 寄存器物理地址 */ #define CCM_CCGR1_BASE (0X020C406C) #define SW_MUX_GPIO1_IO03_BASE (0X020E0068) #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4) #define GPIO1_DR_BASE (0X0209C000) #define GPIO1_GDIR_BASE (0X0209C004)/* 映射后的寄存器虛擬地址指針 */ static void __iomem *IMX6U_CCM_CCGR1; static void __iomem *SW_MUX_GPIO1_IO03; static void __iomem *SW_PAD_GPIO1_IO03; static void __iomem *GPIO1_DR; static void __iomem *GPIO1_GDIR;/** @description : LED打開/關(guān)閉* @param - sta : LEDON(0) 打開LED,LEDOFF(1) 關(guān)閉LED* @return : 無*/ void led_switch(u8 sta) {u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3); writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val|= (1 << 3); writel(val, GPIO1_DR);} }/** @description : 打開設(shè)備* @param - inode : 傳遞給驅(qū)動的inode* @param - filp : 設(shè)備文件,file結(jié)構(gòu)體有個叫做private_data的成員變量* 一般在open的時候?qū)rivate_data指向設(shè)備結(jié)構(gòu)體。* @return : 0 成功;其他 失敗*/ static int led_open(struct inode *inode, struct file *filp) {return 0; }/** @description : 從設(shè)備讀取數(shù)據(jù) * @param - filp : 要打開的設(shè)備文件(文件描述符)* @param - buf : 返回給用戶空間的數(shù)據(jù)緩沖區(qū)* @param - cnt : 要讀取的數(shù)據(jù)長度* @param - offt : 相對于文件首地址的偏移* @return : 讀取的字節(jié)數(shù),如果為負值,表示讀取失敗*/ static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {return 0; }/** @description : 向設(shè)備寫數(shù)據(jù) * @param - filp : 設(shè)備文件,表示打開的文件描述符* @param - buf : 要寫給設(shè)備寫入的數(shù)據(jù)* @param - cnt : 要寫入的數(shù)據(jù)長度* @param - offt : 相對于文件首地址的偏移* @return : 寫入的字節(jié)數(shù),如果為負值,表示寫入失敗*/ static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0]; /* 獲取狀態(tài)值 */if(ledstat == LEDON) { led_switch(LEDON); /* 打開LED燈 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF); /* 關(guān)閉LED燈 */}return 0; }/** @description : 關(guān)閉/釋放設(shè)備* @param - filp : 要關(guān)閉的設(shè)備文件(文件描述符)* @return : 0 成功;其他 失敗*/ static int led_release(struct inode *inode, struct file *filp) {return 0; }/* 設(shè)備操作函數(shù) */ static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release = led_release, };/** @description : 驅(qū)動出口函數(shù)* @param : 無* @return : 無*/ static int __init led_init(void) {int retvalue = 0;u32 val = 0;/* 初始化LED *//* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能GPIO1時鐘 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26); /* 清楚以前的設(shè)置 */val |= (3 << 26); /* 設(shè)置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、設(shè)置GPIO1_IO03的復(fù)用功能,將其復(fù)用為* GPIO1_IO03,最后設(shè)置IO屬性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03設(shè)置IO屬性*bit 16:0 HYS關(guān)閉*bit [15:14]: 00 默認下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 關(guān)閉開路輸出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驅(qū)動能力*bit [0]: 0 低轉(zhuǎn)換率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、設(shè)置GPIO1_IO03為輸出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3); /* 清除以前的設(shè)置 */val |= (1 << 3); /* 設(shè)置為輸出 */writel(val, GPIO1_GDIR);/* 5、默認關(guān)閉LED */val = readl(GPIO1_DR);val |= (1 << 3); writel(val, GPIO1_DR);/* 6、注冊字符設(shè)備驅(qū)動 */retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);if(retvalue < 0){printk("register chrdev failed!\r\n");return -EIO;}return 0; }/** @description : 驅(qū)動出口函數(shù)* @param : 無* @return : 無*/ static void __exit led_exit(void) {/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* 注銷字符設(shè)備驅(qū)動 */unregister_chrdev(LED_MAJOR, LED_NAME); }module_init(led_init); module_exit(led_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("mu-xin");

通過Makefile編譯,make后生成名為“l(fā)ed.ko”的驅(qū)動模塊文件,使用insmod命令加載模塊,到此完成第一步

第二步:創(chuàng)建設(shè)備文件(節(jié)點)

第二步使用mknod命令創(chuàng)建設(shè)備文件(節(jié)點)。接下來我們就可以在用戶態(tài)操作這個文件(節(jié)點),比如向此設(shè)備文件(節(jié)點)寫入數(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é)點)。

野火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);}/*寫成功后,返回寫入的字數(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時鐘 */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 ] 字符串,其中選項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ù)組元素個數(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這個應(yīng)用程序

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

命令測試

./ledApp /dev/led 1 開燈 ./ledApp /dev/led 0 滅燈

當我們在用戶空間調(diào)用open后發(fā)生了什么,在用戶空間調(diào)用open函數(shù)后,系統(tǒng)由用戶態(tài)進入內(nèi)核態(tài)

  • get_unused_fd_flags
    • 為本次操作分配一個未使用過的文件描述符
  • do_file_open
    • 生成一個空白struct file結(jié)構(gòu)體
    • 從文件系統(tǒng)中查找到文件對應(yīng)的inode
  • do_dentry_open
static int do_dentry_open(struct file *f,struct inode *inode,int (*open)(struct inode *, struct file *)) {.../*把inode的i_fop賦值給struct file的f_op*/f->f_op = fops_get(inode->i_fop);...if (!open)open = f->f_op->open;if (open) {error = open(inode, f);if (error)goto cleanup_all;}... }

在do_dentry_open函數(shù)中調(diào)用下面的open函數(shù)

  • def_chr_fops->chrdev_open
static int chrdev_open(struct inode *inode, struct file *filp) {const struct file_operations *fops;struct cdev *p;struct cdev *new = NULL;...struct kobject *kobj;int idx;/*從內(nèi)核哈希表cdev_map中,根據(jù)設(shè)備號查找自己注冊的sturct cdev,獲取cdev中的file_operation接口*/kobj = kobj_lookup(cdev_map, inode>i_rdev,&idx);new = container_of(kobj, struct cdev, kobj);...inode->i_cdev = p = new;...fops = fops_get(p->ops);.../*把cdev中的file_operation接口賦值給struct file的f_op*/replace_fops(filp, fops);/*調(diào)用自己實現(xiàn)的file_operation接口中的open函數(shù)*/if (filp->f_op->open) {ret = filp->f_op->open(inode, filp);if (ret)goto out_cdev_put;}... }

總結(jié)

完成上面三步后就完成了LED字符設(shè)備的驅(qū)動編寫,其他的字符設(shè)備可參考此驅(qū)動。

用戶空間調(diào)用open函數(shù)打開某個設(shè)備文件后,在進程中會為設(shè)備文件分配一個未使用過的文件描述符,并且生成一個空白struct file結(jié)構(gòu)體,然后從文件系統(tǒng)中查找到文件對應(yīng)的inode,這里的inode也就是第二步自己在文件系統(tǒng)中創(chuàng)建的設(shè)備節(jié)點,然后把該inode的i_fop賦值給進程中的struct file的f_op,也就是說此時進程已經(jīng)找到了設(shè)備節(jié)點了,但是還沒有調(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,此時進程已經(jīng)可以最終調(diào)用自己實現(xiàn)的file_operation接口中的open函數(shù),至此進程獲得了該設(shè)備文件的自己實現(xiàn)的讀寫等操作。

注意glibc庫的fopen函數(shù)、系統(tǒng)調(diào)用open函數(shù)和自己實現(xiàn)的硬件接口open函數(shù)不能混淆。在字符設(shè)備驅(qū)動中系統(tǒng)調(diào)用open函數(shù)最終調(diào)用的是自己實現(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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。