第四章 linux字元設備驅動一

来源:https://www.cnblogs.com/yiyilearnlinux/archive/2022/11/14/16890942.html
-Advertisement-
Play Games

前言 字元設備是Linux驅動中三大設備之一,字元(char)設備是個能夠像位元組流(類似文件)一樣被訪問的設備,由字元設備驅動程式來實現這種特性。字元設備驅動程式通常至少要實現open、close、read和write的系統調用。字元終端(/dev/console)和串口(/dev/ttyS0以及類 ...


前言

字元設備是Linux驅動中三大設備之一,字元(char)設備是個能夠像位元組流(類似文件)一樣被訪問的設備,由字元設備驅動程式來實現這種特性。字元設備驅動程式通常至少要實現open、close、read和write的系統調用。字元終端(/dev/console)和串口(/dev/ttyS0以及類似備)就是兩個字元設備,它們能很好的說明“流”這種抽象概念。字元設備可以通過文件節點來訪問,比如/dev/tty1和/dev/lp0等。這些設備文件和普通文件之間的唯一差別在於對普通文件的訪問可以前後移動訪問位置,而大多數字元設備是一個只能順序訪問的數據通道。然而,也存在具有數據區特性的字元設備,訪問它們時可前後移動訪問位置。


一、字元設備的編寫步驟

1.實現入口函數xxx_init()和卸載函數xxx_exit()
2.申請設備號register_chrdev(與內核有關)
3.註冊字元設備驅動cdev_alloc cdev_init cdev_add(與內核有關)
4.利用udev/mdev機制創建設備文件(節點),
class_create,device_create(與內核有關)
5.硬體部分初始化 io資源映射ioremap,內核提供gpio庫函數(與硬體相關)
註冊中斷(與硬體相關)
初始化等待隊列(與內核有關)
初始化定時器(與內核有關)
6.構建file_operation結構(與內核相關)
實現操作硬體方法xxx_open,xxx_read,xxx_write...(與硬體有關)

二、字元設備應用

1.視頻教學中的使用

1.實現模塊載入和卸載入口函數

		module_init(chr_dev_init);
	    module_exit(chr_dev_exit);

2.在模塊載入入口函數中
A.申請主設備號(內核中用於區分和管理不通風字元設備)

register_chrdev(unsigned int major, const char * name, const struct file_operations * fops		

B.創建設備節點文件(為用戶提供喲個課操作到文件介面---open());

struct class class_create(owner, name)
struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)

C.硬體的初始化
1.地址的映射

gpx2conf = ioremp(GPX2_CON,GPX2_SIZE);

2.中斷的申請
3.實現硬體的寄存器的初始化
//需要1配置gpio功能為輸出

		*gpx2conf &= ~(0xf<<28);
		*gpx2conf |=(0x1<<28);

D.實現file_operations

const struct file_operations my_fops= {
	.open = chr_drv_open,
	.read = chr_drv_read,
	.write = chr_drv_write,
	.release = chr_drv_close,
};

規範:
1.
//0,實例化全局的設備對象--分配空間
//GFP_KERNEL如果當前空間記憶體不夠用的時候,該函數會一直阻塞(休眠)

//#include <linux/slab.h>
led_dev = kmalloc(sizeof(struct  led_desc), GFP_KERNE);
If(led_dev == NULL)
{
printk(KERN_ERR “malloc error\n”);
return -ENOMEM;
}
led_dev->dev_major = 250;

2.做出錯處理
在某個位置出錯了,要將之前申請到的資源進行釋放

led_dev->dev_major = register_che_dev(0, “led_dev_test”,&my_fops);
if(led_dev->dev_major < 0)
{
printk(KERN_ERR “register_chrdev error\n”);
ret = -ENODEV;
goto ↓err_0;
}

err_0:
kfree(led_dev);
return ret;

2.個人觀點

按照視頻的來最終在使用時,可能會遇到一些bug,比如:
在使用kzalloc申請記憶體的時候總是申請失敗,在參考網上的解決辦法後依舊未能解決,實例化對象需要用到kzalloc這一種方法在我學習的時候,並沒有得到驗證,始終卡在記憶體申請這兒,讓後我使用傳統方法,才達到自己想要的結果。

在這裡插入圖片描述


總結

字元設備驅動編寫方法有多種,其中區別主要在於硬體資源的獲取:傳統字元設備驅動編寫;平臺匯流排;設備樹。前面兩種本質上區別其實並不大,但引入了分層的思想。後續將慢慢和大家分享。

附錄

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>

/* 寄存器物理地址 */
#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 volatile unsigned int  *IMX6U_CCM_CCGR1;
static volatile unsigned int  *SW_MUX_GPIO1_IO03;
static volatile unsigned int  *SW_PAD_GPIO1_IO03;
static volatile unsigned int  *GPIO1_DR;
static volatile unsigned int  *GPIO1_GDIR;

//靜態指定
struct led_desc {
	unsigned int dev_major;//設備號
	struct class *cls;
	struct device *dev;//創建設備文件
	/* 映射後的寄存器虛擬地址指針 */

};

 struct led_desc *led_dev;
static int kernel_val = 555;
//read(fd,buf,size);
ssize_t chr_drv_read(struct file *filp, char __user *buf, size_t count, loff_t *fops)
{
	int ret;
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	
	ret = copy_to_user(buf, &kernel_val, count);
	if(ret > 0)
	{
		printk("failed copy_to_user\r\n");
		return -EFAULT;
	}

	return 0;
}

ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
	int ret;
	int value;
	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	ret = copy_from_user(&value, buf, count);
	if(ret > 0)
	{
		printk("failed copy_from_user\r\n");
		return -EFAULT;
	}
	
	if(value){
		*GPIO1_DR |= (1 << 3);
	}else{
		*GPIO1_DR &= ~(1 << 3);
	}
	
	return 0;
}

int chr_drv_open (struct inode *inode, struct file *filp)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	return 0;
}

int chr_drv_close (struct inode *inode, struct file *filp)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	return 0;
}

const struct file_operations my_fops  = {
	.open = chr_drv_open,
	.read = chr_drv_read,
	.write = chr_drv_write,
	.release = chr_drv_close,
};

/*
②在模塊載入入口函數中
	a  申請主設備號(內核中用於區分和管理不同字元設備)
		register_chrdev()
	b  創建設備節點文件(為用戶提供一個可操作到文件介面--open())
		struct class *class_create()
		struct device *device)create()
	c  硬體初始化
		1.地址映射
		2.中斷申請
		3.實現硬體的寄存器的初始化
	d  實現file_operation
*/
static int __init chr_dev_init(void)
{
	int ret;

	//0 實例化全局的設備對象
	led_dev = kzalloc(sizeof(struct led_desc), GFP_KERNEL);
	if(led_dev == NULL);
	{
		printk("kmalloc failed\r\n");
		return -ENOMEM;
	}

	//一般都是申請設備號資源
	//1.申請設備號
	led_dev->dev_major = register_chrdev(0, "chr_dev_test", &my_fops);
	if(led_dev->dev_major < 0){
		printk(KERN_ERR"register_chrdev failed\n");
		ret = -ENODEV;
		goto err_0;

	}

	//2.創建設備文件
	led_dev->cls = class_create(THIS_MODULE, "chr_cls");
	if(IS_ERR(led_dev->cls))
	{
		printk(KERN_ERR"class_create failed\n");
		ret = PTR_ERR(led_dev->cls);//將指針出錯原因轉換為一個出錯碼
		goto err_1;
	}
	// /dev/led0
	led_dev->dev = device_create(led_dev->cls, NULL, MKDEV(led_dev->dev_major, 0), NULL, "led%d", 0);
	if(IS_ERR(led_dev->dev))
	{
		printk(KERN_ERR"device_create failed\n");
		ret = PTR_ERR(led_dev->dev);//將指針出錯原因轉換為一個出錯碼
		goto err_2;
	}
	//3,硬體初始化
	/* 初始化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);


	/* enable GPIO1
	 * configure GPIO1_io3 as gpio
	 * configure GPIO1_io3 as output 
	 */
	*IMX6U_CCM_CCGR1 |= 0x0C000000;
	
	*SW_MUX_GPIO1_IO03 &= 0xfffffff0;
	*SW_MUX_GPIO1_IO03 |= 0x5;

	*SW_PAD_GPIO1_IO03 &= 0xfffffff0;
	*SW_PAD_GPIO1_IO03 |=  0x10B0;


	*GPIO1_GDIR &= 0xfffffff0;
	*GPIO1_GDIR |= 0x8;


/*
readl   writel
static inline u32 readl(const volatile void __iomem *addr)

static inline void writel(u32 value, void *addr)
 2、使能GPIO1時鐘 
	val = readl(IMX6U_CCM_CCGR1);
	val &= ~(3 << 26);	 清楚以前的設置 
	val |= (3 << 26);	設置新值 
	writel(val, IMX6U_CCM_CCGR1);

	 3、設置GPIO1_IO03的復用功能,將其復用為
	    GPIO1_IO03,最後設置IO屬性。
	 
	writel(5, SW_MUX_GPIO1_IO03);
	
	寄存器SW_PAD_GPIO1_IO03設置IO屬性
	 bit 16:0 HYS關閉
	 bit [15:14]: 00 預設下拉
     bit [13]: 0 kepper功能
     bit [12]: 1 pull/keeper使能
     bit [11]: 0 關閉開路輸出
     bit [7:6]: 10 速度100Mhz
     bit [5:3]: 110 R0/6驅動能力
     bit [0]: 0 低轉換率
	
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	 4、設置GPIO1_IO03為輸出功能 
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);	 清除以前的設置 
	val |= (1 << 3);	 設置為輸出 
	writel(val, GPIO1_GDIR);

	 5、預設關閉LED 
	val = readl(GPIO1_DR);
	val |= (1 << 3);	
	writel(val, GPIO1_DR);
或
writel(readl(led_dev->GPIO1_DR) | (1 << 3),led_dev->GPIO1_DR);


*/
	
	return 0;

err_2:
	class_destroy(led_dev->cls);
err_1:
	unregister_chrdev(led_dev->dev_major, "chr_dev_test");
err_0:
	kfree(&led_dev);
	return	ret;

	return ret;
}

static void __exit chr_dev_exit(void)
{
	//一般都是釋放資源
		/* 取消映射 */
	iounmap(IMX6U_CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);
	
	device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
	class_destroy(led_dev->cls);
	unregister_chrdev(led_dev->dev_major, "chr_dev_test");

	

}
//①實現模塊的載入和卸載入口函數
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");




您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 題目: 跳一跳是一款微信小游戲,游戲規則非常簡單,只需玩家要從一個方塊跳到下一個方塊,如果未能成功跳到下一個方塊則游戲結束。 計分規則如下: 1. 如果成功跳到下一個方塊上,但未跳到方塊中心,加1分 2. 如果成功跳到下一個方塊上,且剛好跳到方塊中心,則第一次加2分,此後連續跳到中心時每次遞增2分。 ...
  • Go 程式運行時,有些場景下會導致進程進入某個“高點”,然後就再也下不來了。 比如,多年前曹大寫過的一篇文章講過,在做活動時線上涌入的大流量把 goroutine 數抬升了不少,流量恢復之後 goroutine 數也沒降下來,導致 GC 的壓力升高,總體的 CPU 消耗也較平時上升了 2 個點左右。 ...
  • 先看示例代碼 點擊查看代碼 #include <iostream> #include<cstring> using namespace std; class Student{ public: Student(int _age , const char * _name) { this->age=_ag ...
  • 買賣股票的最好時機(一) 描述 假設你有一個數組prices,長度為n,其中prices[i]是股票在第i天的價格,請根據這個價格數組,返回買賣股票能獲得的最大收益 1.你可以買入一次股票和賣出一次股票,並非每天都可以買入或賣出一次,總共只能買入和賣出一次,且買入必須在賣出的前面的某一天 2.如果不 ...
  • 摘要 C/S端軟體,左側導航菜單+右側頁面切換的佈局很常見。 這篇文章介紹下使用ContentControl控制項和TabControl控制項如何實現基礎的頁面切換。 一、使用ContentControl實現頁面切換 頁面使用UserControl來實現。 基於MVVM框架的思想,利用數據綁定機制,將控 ...
  • 一、基本概念 線程安全(thread safe):指的是被任意多的線程同時執行,都可以保證正確性。 除基本類型外,很少有類型是線程安全的,線程安全的責任基本落在開發者身上,System.Collections.Concurrent命名空間下的類型的除外。 線程安全最常見的手段一般是使用【排它鎖】,將 ...
  • 一、概念 《Threading in C# 》(Joseph Albahari):https://www.albahari.com/threading/ 《Threading in C# 》中文翻譯(GKarch ):https://blog.gkarch.com/topic/threading.h ...
  • 核心思路是使用Region的求交集和並集的結果與原始Region對比 Winform項目自帶這個類庫,如果使用控制台,需要先在nuget安裝System.Drawing.Common /// <summary> /// 計算兩個形狀的關係 /// </summary> /// <param name ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...