Linux驅動開發實戰(一):LED控制驅動詳解

Linux驅動開發野火實戰(一):LED控制驅動詳解


文章目錄

  • Linux驅動開發野火實戰(一):LED控制驅動詳解
  • 引言
  • 一、基礎知識
    • 1.1 什么是字符設備驅動
    • 1.2 重要的數據結構
      • read 函數
      • write 函數
      • open 函數
      • release 函數
  • 二、 驅動程序實現
    • 2.1 完整的驅動代碼示例
    • 2.2 整體流程(圖解)
    • 2.3 用戶空間與內核空間交互(圖解)
    • 2.4 驅動模塊初始化
      • 虛擬地址映射
    • 2.5 拷貝數據
    • 2.6 控制GPIO輸出的LED開關狀態
    • 2.7 LED驅動程序的退出函數
  • 三、實驗過程
    • 項目編譯
    • 連接開發板
      • 掛載NFS文件系統
    • 加載驅動(點燈!)
  • 總結


引言

在Linux設備驅動開發中,字符設備驅動是最基礎也是最常見的驅動類型。本文將從理論到實踐,詳細講解字符設備驅動的開發流程,幫助讀者掌握驅動開發的核心知識


一、基礎知識

1.1 什么是字符設備驅動

字符設備(Character Device)是Linux中最基本的設備類型之一,它的特點是數據以字符流的方式被訪問,像串口、鍵盤、LED等都屬于字符設備。與塊設備不同,字符設備不能隨機訪問,只能順序讀寫。

1.2 重要的數據結構

在開發字符設備驅動之前,我們需要了解幾個關鍵的數據結構:

struct file_operations {struct module *owner;ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);...
};

這個結構體定義了驅動程序需要實現的接口函數。

read 函數

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)

作用: 響應用戶空間的讀取請求
參數:

  • filp:文件結構體指針
  • buf:用戶空間緩沖區指針
  • cnt:要讀取的字節數
  • offt:文件位置指針
    返回值:
  • 成功返回實際讀取的字節數
  • 失敗返回負錯誤碼
  • 使用場景:
  • 讀取設備狀態
  • 獲取設備數據
  • 將數據從內核空間復制到用戶空間

write 函數

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)

作用: 響應用戶空間的寫入請求
參數:

  • filp:文件結構體指針
  • buf:用戶空間數據緩沖區指針
  • cnt:要寫入的字節數
  • offt:文件位置指針
    返回值:
  • 成功返回實際寫入的字節數
  • 失敗返回負錯誤碼
    使用場景:
  • 向設備發送控制命令
  • 更新設備狀態
  • 將數據從用戶空間復制到內核空間

open 函數

static int led_open(struct inode *inode, struct file *filp)

作用: 當用戶空間調用 open() 打開設備文件時被調用
參數:

  • inode:包含文件系統信息的結構體,如設備號等
  • filp:文件結構體,包含文件操作方法、私有數據等
    返回值:
  • 成功返回0
  • 失敗返回負錯誤碼
    使用場景:
  • 初始化設備
  • 檢查設備狀態
  • 分配資源
  • 增加使用計數

release 函數

static int led_release(struct inode *inode, struct file *filp)

作用: 當最后一個打開的文件被關閉時調用
參數:

  • inode:索引節點結構體指針
  • filp:文件結構體指針
    返回值:
  • 成功返回0
  • 失敗返回負錯誤碼
    使用場景:
  • 釋放資源
  • 清理設備狀態
  • 減少使用計數

二、 驅動程序實現

2.1 完整的驅動代碼示例

代碼如下:

#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		   /* 動態申請主設備號 */
#define DEV_NAME "red_led" /*led設備名字 *//* 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;/*從用戶空間拷貝數據到內核空間*/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);}/*寫成功后,返回寫入的字數*/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相關寄存器映射 */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);/* 設置GPIO1_IO04復用為普通GPIO*/iowrite32(5, SW_MUX_GPIO1_IO04);/*設置GPIO屬性*/iowrite32(0x10B0, SW_PAD_GPIO1_IO04);/* 設置GPIO1_IO04為輸出功能 */iowrite32(1 << 4, GPIO1_GDIR);/* LED輸出高電平 */iowrite32(1 << 4, GPIO1_DR);/* 注冊字符設備驅動 */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);/* 注銷字符設備驅動 */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");

2.2 整體流程(圖解)

在這里插入圖片描述

2.3 用戶空間與內核空間交互(圖解)

在這里插入圖片描述

2.4 驅動模塊初始化

虛擬地址映射

  1. ioremap 函數
void __iomem *ioremap(unsigned long phys_addr, unsigned long size);

作用: 將物理地址映射到虛擬地址空間
參數:

  • phys_addr:物理地址
  • size:映射的大小(字節數)
    返回值: 映射后的虛擬地址指針
  • void * 類型的指針,指向被映射的虛擬地址
  • __iomem 主要是用于編譯器的檢查地址在內核空間的有效性
    為什么要用: Linux內核出于安全考慮,不允許直接訪問物理地址,必須先映射到虛擬地址

GPIO相關寄存器映射

	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);
  1. 虛擬地址讀寫
void iowrite32(u32 b, void __iomem *addr)   //寫入一個雙字(32bit)unsigned int ioread32(void __iomem *addr)   //讀取一個雙字(32bit)
/* 使能GPIO1時鐘 */iowrite32(0xffffffff, IMX6U_CCM_CCGR1);/* 設置GPIO1_IO04復用為普通GPIO*/iowrite32(5, SW_MUX_GPIO1_IO04);/*設置GPIO屬性*/iowrite32(0x10B0, SW_PAD_GPIO1_IO04);/* 設置GPIO1_IO04為輸出功能 */iowrite32(1 << 4, GPIO1_GDIR);/* LED輸出高電平 */iowrite32(1 << 4, GPIO1_DR);
  1. register_chrdev 函數
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);

作用: 注冊字符設備驅動
參數:

major:主設備號(0表示動態分配)
name:設備名稱
fops:文件操作結構體
次設備號為0,次設備號數量為256
返回值: 成功返回主設備號,失敗返回負值
為什么要用: 向Linux系統注冊一個字符設備,使系統能夠識別和管理該設備

在這里插入圖片描述

2.5 拷貝數據

copy_from_user函數

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

作用: 將數據從用戶空間復制到內核空間
參數:

  • to:內核空間目標地址
  • from:用戶空間源地址
  • n:復制的字節數
    返回值: 成功返回0,失敗返回未復制的字節數
    為什么要用: 內核空間和用戶空間是隔離的,需要專門的函數來安全地傳輸數據,要是有野指針會導致整個系統的崩潰,所以是起到一個安全的作用。

2.6 控制GPIO輸出的LED開關狀態

if (!memcmp(databuf, "on", 2))  // 比較是否接收到"on"命令
{iowrite32(0 << 4, GPIO1_DR); // GPIO1_4輸出低電平,LED亮
}
else if (!memcmp(databuf, "off", 3)) // 比較是否接收到"off"命令
{iowrite32(1 << 4, GPIO1_DR); // GPIO1_4輸出高電平,LED滅
}
  • memcmp()函數:
int memcmp(const void *str1, const void *str2, size_t n)
// 比較兩個內存區域的前n個字節
// 返回0表示相等

2.7 LED驅動程序的退出函數

static void __exit led_exit(void)
{// 1. 取消IO內存映射iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO04);iounmap(SW_PAD_GPIO1_IO04);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);// 2. 注銷字符設備unregister_chrdev(major, DEV_NAME);
}
  • iounmap 函數
void iounmap(void __iomem *addr);

作用: 解除I/O內存映射
參數:

addr: 要解除映射的虛擬地址
為什么要用: 釋放ioremap占用的資源,防止內存泄漏

  • unregister_chrdev 函數
void unregister_chrdev(unsigned int major, const char *name);

作用: 注銷字符設備驅動
參數:

major:設備的主設備號
name:設備名稱
為什么要用: 在驅動卸載時清理系統資源

三、實驗過程

項目編譯

在這里插入圖片描述
然后make
在這里插入圖片描述

連接開發板

打開手機熱點并連上
讓電腦跟手機在同一個局域網內

  • ubuntu端
    在這里插入圖片描述

  • 開發板端
    在這里插入圖片描述

掛載NFS文件系統

sudo mount -t nfs ”NFS服務端IP”:/home/embedfire/workdir /mnt

我們ubuntu的IP為192.168.46.118
所以為

sudo mount -t nfs 192.168.46.118:/home/embedfire/workdir /mnt

在這里插入圖片描述
掛載成功后進入共享文件夾查看

ubuntu把ko文件復制到共享文件夾中
在這里插入圖片描述

我們到共享文件夾ls查看
在這里插入圖片描述

加載驅動(點燈!)

在這里插入圖片描述
244是設備號(動態分配)
0是次設備號
為什么是0
因為在register_chrdev函數定義了
次設備號在(0~256之間隨便選)
ebf-buster-linux/include/linux/fs.h

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{return __register_chrdev(major, 0, 256, name, fops);
}

創建設備文件
在這里插入圖片描述
利用echo應用打開燈的命令
在這里插入圖片描述
請添加圖片描述

利用echo應用關燈的命令
在這里插入圖片描述
卸載模塊
在這里插入圖片描述


總結

本文詳細介紹了Linux字符設備驅動的開發流程,包括:

  • 基本概念和原理
  • 完整的代碼實現
  • 詳細的流程圖解
  • 實際操作
    通過本文的學習,大家應該能夠掌握字符設備驅動的開發方法,并能夠開發簡單的字符設備驅動程序。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/70963.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/70963.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/70963.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux上用C++和GCC開發程序實現不同MySQL實例下單個Schema之間的穩定高效的數據遷移

設計一個在Linux上運行的GCC C程序&#xff0c;同時連接兩個不同的MySQL實例&#xff0c;兩個實例中分別有兩個Schema的表結構完全相同&#xff0c;復制一個實例中一個Schema里的所有表的數據到另一個實例中一個Schema里&#xff0c;使用以下快速高效的方法&#xff0c;加入異常…

Redis除了做緩存還能做什么?

Redis 除了作為高性能緩存外&#xff0c;還因其豐富的數據結構和功能&#xff0c;廣泛應用于多種場景。以下是 Redis 的十大核心用途及具體示例&#xff1a; 1. 分布式會話存儲 用途&#xff1a;存儲用戶會話信息&#xff08;如登錄狀態&#xff09;&#xff0c;實現多服務間共…

JBoltAI_SpringBoot如何區分DeepSeek R1深度思考和具體回答的內容(基于Ollama)?

當我們用Ollama運行DeepSeek R1模型&#xff0c;向它提問時&#xff0c;會發現它的回答里是有think標簽的 如果我們直接將Ollama的回復用于生產環境&#xff0c;肯定是不行的&#xff0c;對于不同的場景&#xff0c;前面輸出的一堆內容&#xff0c;可能并不需要在客戶端展示&a…

MySQL 使用 `WHERE` 子句時 `COUNT(*)`、`COUNT(1)` 和 `COUNT(column)` 的區別解析

文章目錄 1. COUNT() 函數的基本作用2. COUNT(*)、COUNT(1) 和 COUNT(column) 的詳細對比2.1 COUNT(*) —— 統計所有符合條件的行2.2 COUNT(1) —— 統計所有符合條件的行2.3 COUNT(column) —— 統計某一列非 NULL 的記錄數 3. 性能對比3.1 EXPLAIN 分析 4. 哪種方式更好&…

將DeepSeek接入vscode的N種方法

接入deepseek方法一:cline 步驟1:安裝 Visual Studio Code 后,左側導航欄上點擊擴展。 步驟2:搜索 cline,找到插件后點擊安裝。 步驟3:在大模型下拉菜單中找到deep seek,然后下面的輸入框輸入你在deepseek申請的api key,就可以用了 讓deepseek給我寫了一首關于天氣的…

AndroidManifest.xml文件的作用

AndroidManifest.xml文件在Android應用程序中扮演著至關重要的角色。它是應用程序的全局配置文件&#xff0c;提供了關于應用程序的所有必要信息&#xff0c;這些信息對于Android系統來說是至關重要的&#xff0c;因為它決定了應用程序的運行方式和權限要求&#xff0c;確保了應…

Mac本地部署Deep Seek R1

Mac本地部署Deep Seek R1 1.安裝本地部署大型語言模型的工具 ollama 官網&#xff1a;https://ollama.com/ 2.下載Deepseek R1模型 網址&#xff1a;https://ollama.com/library/deepseek-r1 根據電腦配置&#xff0c;選擇模型。 我的電腦&#xff1a;Mac M3 24G內存。 這…

React進階之前端業務Hooks庫(五)

前端業務Hooks庫 Hooks原理useStateuseEffect上述問題useState,useEffect 復用的能力練習:怎樣實現一套React過程中的hooks狀態 & 副作用Hooks原理 不能在循環中、條件判斷、子函數中調用,只能在函數最外層去調用useEffect 中,deps 為空,執行一次useState 使用: imp…

從像素到光線:現代Shader開發的范式演進與性能優化實踐

引言 在實時圖形渲染領域&#xff0c;Shader作為GPU程序的核心載體&#xff0c;其開發范式已從早期的固定功能管線演進為高度可編程的計算單元。本文通過解析關鍵技術案例&#xff0c;結合現代圖形API&#xff08;如Vulkan、Metal&#xff09;的特性&#xff0c;深入探討Shade…

(七)消息隊列-Kafka 序列化avro(傳遞)

&#xff08;七&#xff09;消息隊列-Kafka 序列化avro&#xff08;傳遞&#xff09; 客從遠方來&#xff0c;遺我雙鯉魚。呼兒烹鯉魚&#xff0c;中有尺素書。 ——佚名《飲馬長城窟行》 本文已同步CSDN、掘金平臺、知乎等多個平臺&#xff0c;圖片依然保持最初發布的水印&…

PXE批量網絡裝機與Kickstart自動化安裝工具

目錄 一、系統裝機的原理 1.1、系統裝機方式 1.2、系統安裝過程 二、PXE批量網絡裝機 2.1、PXE實現原理 2.2、搭建PXE實際案例 2.2.1、安裝必要軟件 2.2.2、搭建DHCP服務器 2.2.3、搭建TFTP服務器 2.2.4、掛載鏡像并拷貝引導文件到tftp服務啟動引導文件夾下 2.2.5、編…

【全棧開發】從0開始搭建一個圖書管理系統【一】框架搭建

【全棧開發】從0開始搭建一個圖書管理系統【一】框架搭建 前言 現在流行降本增笑&#xff0c;也就是不但每個人都要有事干不能閑著&#xff0c;更重要的是每個人都要通過報功的方式做到平日的各項工作異常飽和&#xff0c;實現1.5人的支出干2人的活計。單純的數據庫開發【膚淺…

部署Flink1.20.1

1、設置環境變量 export JAVA_HOME/cluster/jdk export CLASSPATH.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jarp #export HIVE_HOME/cluster/hive export MYSQL_HOME/cluster/mysql export HADOOP_HOME/cluster/hadoop3 export HADOOP_CONF_DIR$HADOOP_HOME/etc/hadoop …

【超詳細】神經網絡的可視化解釋

《------往期經典推薦------》 一、AI應用軟件開發實戰專欄【鏈接】 項目名稱項目名稱1.【人臉識別與管理系統開發】2.【車牌識別與自動收費管理系統開發】3.【手勢識別系統開發】4.【人臉面部活體檢測系統開發】5.【圖片風格快速遷移軟件開發】6.【人臉表表情識別系統】7.【…

深入了解 Python 中的 MRO(方法解析順序)

文章目錄 深入了解 Python 中的 MRO&#xff08;方法解析順序&#xff09;什么是 MRO&#xff1f;如何計算 MRO&#xff1f;C3 算法的合并規則C3 算法的合并步驟示例&#xff1a;合并過程解析 MRO 解析失敗的場景使用 mro() 方法查看 MRO示例 1&#xff1a;基本用法 菱形繼承與…

數字化賦能:制造業如何突破低效生產的瓶頸?

隨著全球經濟的快速發展與市場需求的變化&#xff0c;制造業面臨著前所未有的壓力與挑戰。生產效率、資源管理、品質控制、成本控制等方面的問題日益突出&#xff0c;尤其是低效生產成為了許多制造企業亟待解決的瓶頸。在這種背景下&#xff0c;數字化轉型成為提升制造業效率的…

Element-Plus,使用 El-form中 的 scroll-to-error 沒有效果問題記錄

因業務需要表單組件中嵌套著表格列表&#xff0c;內容比較多&#xff1b; 所以需要表單校驗不通過時&#xff0c;自動定位到不通過的節點&#xff1b; 但發現這個像是沒有起到效果一樣&#xff0c;后面就是排查的思路了&#xff1a; 容器高度問題&#xff1a;如果表單容器的高度…

基于Javase的停車場收費管理系統

基于Javase的停車場收費管理系統 停車場管理系統開發文檔 項目概述 1.1 項目背景 隨著現代化城市的不斷發展&#xff0c;車輛數量不斷增加&#xff0c;停車難問題也日益突出。為了更好地管理停車場資 源&#xff0c;提升停車效率&#xff0c;需要一個基于Java SE的停車場管理…

網絡協議 HTTP、HTTPS、HTTP/1.1、HTTP/2 對比分析

1. 基本定義 HTTP&#xff08;HyperText Transfer Protocol&#xff09; 應用層協議&#xff0c;用于客戶端與服務器之間的數據傳輸&#xff08;默認端口 80&#xff09;。 HTTP/1.0&#xff1a;早期版本&#xff0c;每個請求需單獨建立 TCP 連接&#xff0c;效率低。HTTP/1.1&…

DeepSeek掘金——調用DeepSeek API接口 實現智能數據挖掘與分析

調用DeepSeek API接口:實現智能數據挖掘與分析 在當今數據驅動的時代,企業和開發者越來越依賴高效的數據挖掘與分析工具來獲取有價值的洞察。DeepSeek作為一款先進的智能數據挖掘平臺,提供了強大的API接口,幫助用戶輕松集成其功能到自己的應用中。本文將詳細介紹如何調用D…