那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

i.MX6ULL驅動開發2—新字符設備開發模板

碼農愛學習 ? 來源:碼農愛學習 ? 作者:碼農愛學習 ? 2022-03-17 09:11 ? 次閱讀

上篇文章介紹了字符設備的開發模板,但那是一種舊版本的驅動開發模式,設備驅動需要手動分配設備號再使用 register_chrdev進行注冊,加載成功以后還需要手動使用mknod命令創建設備節點,比較麻煩

目前Linux內核推薦的新字符設備驅動API函數,使得驅動的使用更加自動化,本篇就來一起研究下。

1 舊字符設備驅動的弊端

使用register_chrdev函數注冊字符設備,需要指定一個設備號,這就造成:

需要事先確定好哪些主設備號沒有使用

會將一個主設備號下的所有次設備號都使用掉,比如主設備號為200,那么 0~1048575(2^20-1)這個區間的次設備號就全部都被占用了

回顧上一篇的操作,先是加載驅動:

pYYBAGIyAAmAFyp0AAB4iFrzgsc420.png

加載完,還有手動使用mknod指令來手動創建該設備節點,并且指定驅動程序中寫死的設備號:

pYYBAGIyAA-AX6J5AAAy_IcL8LE995.png

本篇,就要使用一種新的字符驅動編寫方式,實現設備號的自動分配,省去mknod指令操作

2 新字符設備驅動原理

2.1 分配和釋放設備號

使用設備號的時候向Linux內核申請,需要幾個就申請幾個,由Linux內核分配設備可以使用的設備號。

使用如下函數來申請設備號(該函數在上篇提到過):

/*
* dev:保存申請到的設備號
* baseminor:次設備號起始地址,一般baseminor為0 (次設備號以baseminor為起始地址地址開始遞)
* count:要申請的設備號數量
* name:設備名字
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 

如果給定了設備的主設備號和次設備號就使用如下所示函數來注冊設備號即可:

/*
* from:要申請的起始設備號
* count:要申請的設備號數量
* name:設備名字
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name) 

注銷字符設備之后要釋放設備號,不管是通過alloc_chrdev_region函數的動態分配還是register_chrdev_region函數手動指定的設備號,統一使用(和上篇使用的一樣)的釋放函數:

/*
* from:要釋放的設備號
* count:表示從from開始,要釋放的設備號數量
*/
void unregister_chrdev_region(dev_t from, unsigned count) 

新字符設備驅動下,設備號分配示例代碼如下:

int major;      /*主設備號*/ 
int minor;      /*次設備號*/ 
dev_t devid;    /*設備號*/ 

/*定義了主設備號*/
if (major)       
{
   devid = MKDEV(major, 0);    /*大部分驅動次設備號都選擇0*/ 
   register_chrdev_region(devid, 1, "test"); 
} 
/*沒有定義設備號*/ 
else 
{                         
   alloc_chrdev_region(&devid, 0, 1, "test");  /*申請設備號*/ 
   major = MAJOR(devid);       /*獲取分配號的主設備號*/ 
   minor = MINOR(devid);       /*獲取分配號的次設備號*/ 
}

2.2 字符設備注冊

2.2.1 cdev字符設備結構

在Linux中使用cdev結構體表示一個字符設備,其定義在include/linux/cdev.h文件中:

struct cdev { 
    struct kobject               kobj; 
    struct module                *owner; 
    const struct file_operations *ops;  /*文件操作函數集合*/
    struct list_head             list; 
    dev_t                        dev;   /*設備號*/              
    unsigned int                 count; 
};

2.2.2 cdev_init 函數

定義好cdev變量以后就要使用cdev_init函數對其進行初始化:

/*
* cdev:要初始化的cdev結構體變量
* fops:字符設備文件操作函數集合
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops) 

該函數的使用示例如下:

/*要初始化的cdev結構體*/
struct cdev testcdev; 

/* 設備操作函數 */ 
static struct file_operations test_fops = { 
   .owner = THIS_MODULE, 
   /* 其他具體的初始項 */ 
}; 

testcdev.owner = THIS_MODULE;

/* 初始化cdev*/ 
cdev_init(&testcdev, &test_fops); 

2.2.3 cdev_add函數

該函數用于向Linux系統添加字符設備,即cdev結構體變量:

/*
* cdev:要初始化的cdev結構體變量
* dev:字符設備所使用的設備號
* count:要添加的設備數量
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count) 

2.2.4 cdev_del函數

卸載驅動的時候要使用cdev_del函數從Linux內核中刪除字符設備:

/*
* p:要刪除的字符設備
*/
void cdev_del(struct cdev *p)

2.3 自動創建設備節點

上篇的Linux驅動實驗中,在使用modprobe加載驅動程序以后還需要使用“mknod”命令手動創建設備節點,比較麻煩,這里就來研究一下如何實現自動創建設備節點。

2.3.1 mdev機制

在Linux下通過udev來實現設備文件的自動創建與刪除。使用busybox構建根文件系統的時候,busybox會創建一個udev的簡化版本mdev

所以,在嵌入式開發中使用mdev來實現設備節點文件的自動創建與刪除。Linux系統中的熱插拔事件也由mdev 管理,在/etc/init.d/rcS 文件中如下語句:

echo /sbin/mdev > /proc/sys/kernel/hotplug 

2.3.2 創建和刪除類

自動創建設備節點的工作是在驅動程序的入口函數中完成的,一般在cdev_add函數后面添 加自動創建設備節點相關代碼。

首先要創建一個class類,其實是個結構體,定義在include/linux/device.h里面。class_create是類創建函數(宏定義):

#define class_create(owner, name) \ 
({ \ 
    static struct lock_class_key __key; \ 
    __class_create(owner, name, &__key); \ 
}) 

struct class *__class_create(struct module *owner, 
                             const char *name, 
                             struct lock_class_key *key) 

卸載驅動程序的時候需要使用函數為class_destroy刪除掉類

/*
* cls:要刪除的類
*/
void class_destroy(struct class *cls); 

2.3.3 創建設備

創建好類以后還不能實現自動創建設備節點,還需要在這個類下創建一個設備。使用device_create函數創建設備:

/*
* class:設備要創建哪個類下面
* parent:父設備, 一般為 NULL
* devt:設備號
* drvdata:設備可能會使用的一些數據,一般為 NULL
* fmt:設備名字
*/
struct device *device_create(struct clas *class,  
                             struct device *parent,  
                             dev_t devt,  
                             void *drvdata,  
                             const char *fmt, ...) 

參數最后的...表示這在是一個可變參數的函數。

2.4 設置文件私有數據

每個硬件設備都有一些屬性, 比如主設備號(dev_t),類(class)、設備(device)、開關狀態(state)等等,在編寫驅動的時候你可以將這些屬性全部寫成變量的形式:

 dev_t         devid;     /*設備號*/ 
struct cdev   cdev;      /*cdev*/ 
struct class  *class;    /*類*/ 
struct device *device;   /*設備*/ 
int           major;     /*主設備號*/ 
int           minor;     /*次設備號*/ 

可以將所有屬性信封裝到結構體中, 并在編寫驅動open函數的時候將其作為私有數據添加到設備文件中:

/*設備結構體*/ 
struct test_dev{ 
    dev_t         devid;     /*設備號*/ 
    struct cdev   cdev;      /*cdev*/ 
    struct class  *class;    /*類*/ 
    struct device *device;   /*設備*/ 
    int           major;     /*主設備號*/ 
    int           minor;     /*次設備號*/ 
}; 

struct test_dev testdev; 

/*open函數*/ 
static int test_open(struct inode *inode, struct file *filp) 
{ 
    filp->private_data = &testdev; /*設置私有數據*/ 
    return 0; 
} 

3 驅動程序編寫

在上篇的基礎上進行修改,因為只是更換的驅動程序的編寫方式,與應用程序無關,因此只修改驅動程序即可。

3.1 添加一些定義

因為上篇文章的代碼中使用的是chrdevbase這個名稱,為了減少修改量,這里僅把結構體類型定義為帶有new標志的newchr_dev,變量名仍使用chrdevbase這個名稱。

#define CHRDEVBASE_CNT			1		/* 設備號個數 */
#define CHRDEVBASE_NAME	 "chrdevbase"	/* 名字 */

/*newchr設備結構體 */
struct newchr_dev{
	dev_t         devid;	/* 設備號   */
	struct cdev   cdev;		/* cdev     */
	struct class  *class;	/* 類       */
	struct device *device;	/* 設備     */
	int           major;	/* 主設備號 */
	int           minor;	/* 次設備號 */
};

struct newchr_dev chrdevbase; /* 自定義字符設備 */

3.2 修改open函數

在上篇程序的基礎上增加了一條“設置私有數據”

static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	printk("chrdevbase open!\r\n");
    filp->private_data = &chrdevbase; /* 設置私有數據 */
	return 0;
}

3.3 修改init函數

這個修改比較大,因為要在init函數中使用設備號的自動分配

static int __init chrdevbase_init(void)
{
    /* 注冊字符設備驅動 */
	/* 1、創建設備號 */
	if (chrdevbase.major) /* 定義了設備號 */
    {
		chrdevbase.devid = MKDEV(chrdevbase.major, 0);
		register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
	} 
    else /* 沒有定義設備號 */
    {
		alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT, CHRDEVBASE_NAME);	/* 申請設備號 */
		chrdevbase.major = MAJOR(chrdevbase.devid);	/* 獲取分配號的主設備號 */
		chrdevbase.minor = MINOR(chrdevbase.devid);	/* 獲取分配號的次設備號 */
	}
	printk("chrdevbase major=%d,minor=%d\r\n",chrdevbase.major, chrdevbase.minor);	
	
	/* 2、初始化cdev */
	chrdevbase.cdev.owner = THIS_MODULE;
	cdev_init(&chrdevbase.cdev, &chrdevbase_fops);
	
	/* 3、添加一個cdev */
	cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);

	/* 4、創建類 */
	chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
	if (IS_ERR(chrdevbase.class)) 
    {
		return PTR_ERR(chrdevbase.class);
	}

	/* 5、創建設備 */
	chrdevbase.device = device_create(chrdevbase.class, NULL, chrdevbase.devid, NULL, CHRDEVBASE_NAME);
	if (IS_ERR(chrdevbase.device)) 
    {
		return PTR_ERR(chrdevbase.device);
	}
    
	printk("chrdevbase init done!\r\n");
	return 0;
}

3.4 修改exit函數

因為init修改較大,對應的exit也要進行大的修改:

static void __exit chrdevbase_exit(void)
{
    /* 注銷字符設備驅動 */
	cdev_del(&chrdevbase.cdev);/*  刪除cdev */
	unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT); /* 注銷設備號 */

	device_destroy(chrdevbase.class, chrdevbase.devid);
	class_destroy(chrdevbase.class);
    
    printk("chrdevbase exit done!\r\n");
}

至此,修改完畢,其它的與之前的一樣。

3.5 新舊驅動方式對比

通過一張圖來對比新舊兩種驅動編寫方式的區別

舊方式編寫驅動的流程

pYYBAGIyAdOAKvkdAACBY8npGD4752.png

新方式編寫驅動的流程

pYYBAGIyAdiAMiETAAEM8HQykVI076.png

可以看出主要區別在驅動的加載和卸載。

4 編譯驅動

和上次編譯驅動的方式一樣,使用makefile,因為驅動的c文件名由chrdevbase.c改為了newchrdevbase.c,因此makefile文件中也要把名字改掉。

編譯完之后,將編譯出的ko文件先復制到ubuntu虛擬機的tftpboot目錄中,為后面的測序做準備。

pYYBAGIyAeCARtBwAACum9V3bvM294.png

復制后,看一下tftpboot目錄:

poYBAGIyAeWAcC6OAABRgY3MQbY132.png

5 程序測試

5.1 文件發送到板子

和上篇一樣,使用tftp傳輸,將ubuntu虛擬機編譯出的ko文件發送到linux板子中

再來看下tftp傳輸的硬件環境示意圖:

poYBAGIx__mAJAMxAAFqlflsgR8688.png

然后是傳輸指令以及傳輸結果,可以看到newchrdevbase.ko已經從ubuntu虛擬機的tftpboot目錄傳輸到了linux板子的/lib/modules/4.1.15目錄中了。

pYYBAGIyAfuAE3rpAACDeMdklK0816.png

5.2 測試

輸入如下兩條指令加載 newchrdevbase.ko 驅動模塊:

depmod //第一次加載驅動的時候需要運行此命令 modprobe newchrdevbase.ko //加載驅動

驅動加載成功后,可以看到自動申請到的主設備號和次設備號,如下圖,主設備號為249。

再輸入ls /dev/chrdevbase -l指令驗證/dev/chrdevbase 這個設備節點文件是否存在,如下圖,可以看到設備存在,注意和上篇舊驅動方式操作上的不同之處,舊的驅動方式需要額外使用mknod指令來手動創建該設備節點

poYBAGIyAgGAbiVoAABRaJSZ9J0244.png

驅動已經加載成功,再來測試APP程序,理論上和上篇的效果一樣,實測也是:

pYYBAGIyAgaAb-B4AAB-1c076Y0255.png

OK,測試完畢,測試完使用rmmod指令卸載驅動。

6 總結

此篇文章針對上篇文章使用舊字符驅動編寫方式存在的不足,介紹了一種新的字符驅動編寫方式,對比兩種方式編寫的主要區別,在上篇驅動代碼的基礎上進行修改,并測試通過,和上篇實現一樣的效果,但驅動的加載更加方便,不再需要人為指定設備號。

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 設備
    +關注

    關注

    2

    文章

    4543

    瀏覽量

    70853
  • 模板
    +關注

    關注

    0

    文章

    108

    瀏覽量

    20608
  • 函數
    +關注

    關注

    3

    文章

    4346

    瀏覽量

    62971
收藏 人收藏

    評論

    相關推薦

    i.MX6ULL嵌入式Linux開發1-uboot移植初探

    本系列教程以i.MX6ULL處理器的ARM開發板為實驗基礎,學習記錄嵌入式Linux開發的各種知識與經驗,主要內容包括嵌入式Linux移植,嵌入式Linux驅動
    的頭像 發表于 03-07 08:57 ?3932次閱讀
    <b class='flag-5'>i.MX6ULL</b>嵌入式Linux<b class='flag-5'>開發</b>1-uboot移植初探

    i.MX6ULL驅動開發1—字符設備開發模板

    本篇介紹了嵌入式Linux驅動開發中的基礎驅動——字符驅動開發的基本模式,使用了一個虛擬的
    的頭像 發表于 03-17 09:13 ?3390次閱讀
    <b class='flag-5'>i.MX6ULL</b><b class='flag-5'>驅動</b><b class='flag-5'>開發</b>1—<b class='flag-5'>字符</b><b class='flag-5'>設備</b><b class='flag-5'>開發</b><b class='flag-5'>模板</b>

    使用i.MX6ULL開發板進行Linux根文件系統的完善

    上一篇推文講了怎么移植根文件系統,并在i.MX6ULL開發板中運行起來,但是會出現一些提示,現在來進行根文件的完善。
    發表于 10-17 11:13 ?831次閱讀

    移植NXP官方linux 5.4內核到i.MX6ULL開發

    本文描述移植NXP官方 linux 5.4 內核到i.MX6ULL開發板。
    發表于 12-19 11:10 ?2111次閱讀

    I.MX6ULL終結者開發板裸機仿真jlink調試

    I.MX6ULL‘終結者’開發板預留了JTAG仿真接口,并給出了開發文檔,可以實現在JLINK仿真器條件下的單步跟蹤、斷點調試等功能,使得開發研究i
    發表于 07-07 10:56

    i.MX6ULL開發板硬件資源

    迅為i.MX6ULL 終結者開發板硬件資源非常豐富,幾乎將 i.MX6ULL 芯片的所有資源都擴展引出到底板上了,底板提供了豐富的外設接口,開發板的尺寸是 190mm*125mm,充分
    發表于 12-29 06:18

    初識 i.MX6ULL 寄存器

    裸機開發_L1_匯編LED實驗0. 本節目標1. 硬件層電路2. 初識 i.MX6ULL 寄存器2.1 i.MX6ULL 時鐘控制寄存器2.2 i.
    發表于 12-20 07:13

    I.MX6ULL無法枚舉USB2514是為什么?

    你好目前,I.MX6ULL開發存在一些問題。其中之一是OTG USB2無法正常掛載USB2514,無法正確枚舉下游設備,只顯示設備id。us
    發表于 04-03 06:55

    飛凌i.MX6ULL開發板的評測,再次進階擁有更高的性價比

    處理器MCIMX6Y2開發設計,采用先進的ARMCortex-A7內核,運行速度高達800MHz。i.MX6ULL應用處理器包括一個集成的電源管理模塊,降低了外接電源的復雜性,并簡化了上電時序。
    發表于 10-27 11:55 ?1507次閱讀
    飛凌<b class='flag-5'>i.MX6ULL</b><b class='flag-5'>開發</b>板的評測,再次進階擁有更高的性價比

    i.MX6ULL驅動開發4——點亮LED(寄存器版)

    本篇主要介紹了如何通過操作寄存器來點亮i.MX6ULL開發板上的led,通過編寫LED對應的驅動程序和應用程序,實現程序設計的分層。
    的頭像 發表于 05-21 21:26 ?3034次閱讀
    【<b class='flag-5'>i.MX6ULL</b>】<b class='flag-5'>驅動</b><b class='flag-5'>開發</b>4——點亮LED(寄存器版)

    i.MX6ULL|字符設備驅動開發實踐

    字符設備驅動開發的基本步驟可以看上一篇,本節就以 chrdevbase 這個虛擬設備為例,完整的編寫一個
    的頭像 發表于 10-31 11:27 ?720次閱讀

    Linux新字符設備驅動開發方式

    Linux字符設備驅動開發模板中介紹了舊版本的驅動開發
    的頭像 發表于 04-14 12:02 ?907次閱讀
    Linux<b class='flag-5'>新字符</b><b class='flag-5'>設備</b><b class='flag-5'>驅動</b><b class='flag-5'>開發</b>方式

    【北京迅為】i.MX6ULL開發板移植 Debian 文件系統

    【北京迅為】i.MX6ULL開發板移植 Debian 文件系統
    的頭像 發表于 02-10 15:34 ?1233次閱讀
    【北京迅為】<b class='flag-5'>i.MX6ULL</b><b class='flag-5'>開發</b>板移植 Debian 文件系統

    基于i.MX6ULL的掉電檢測設計與軟件測試

    基于i.MX6ULL的掉電檢測設計與軟件測試基于i.MX6ULL平臺設計實現掉電檢測功能,首先選擇一路IO,利用IO電平變化觸發中斷,在編寫驅動時捕獲該路GPIO的中斷,然后在中斷響應函數中發
    的頭像 發表于 11-09 10:40 ?920次閱讀
    基于<b class='flag-5'>i.MX6ULL</b>的掉電檢測設計與軟件測試

    【迅為電子】i.MX6UL和i.MX6ULL芯片區別與開發板對比

    【迅為電子】i.MX6UL和i.MX6ULL芯片區別與開發板對比
    的頭像 發表于 11-28 14:31 ?570次閱讀
    【迅為電子】<b class='flag-5'>i.MX6</b>UL和<b class='flag-5'>i.MX6ULL</b>芯片區別與<b class='flag-5'>開發</b>板對比
    百家乐官网统计概率| 百家乐投注怎么样| 兴山县| 百家乐官网有人赢过吗| 百家乐官网庄闲出现几率| 百家乐官网龙虎台布价格| CEO百家乐官网的玩法技巧和规则| 百家乐风云人物| 在线百家乐博彩网| 金银岛百家乐的玩法技巧和规则 | 百家乐官网特殊计| 波音百家乐网上娱乐| 威尼斯人娱乐 老品牌| 澳门赌场着装| 百家乐官网投注信用最好的 | 百家乐水晶筹码| 百家乐高人破解| 全讯网新2网址| 奉化市| 百家乐官网庄闲和收益| 百家乐桌定制| 盈彩娱乐| 宝龙百家乐官网娱乐城| 百家乐官网心得分享| 威尼斯人娱乐网址| 皇冠网 全讯通| 赌场百家乐官网是如何玩| 传奇百家乐官网的玩法技巧和规则 | 奔驰百家乐官网可信吗| 362百家乐官网的玩法技巧和规则| 百家乐网上投注系统| 大发888官方授权网| 百家乐官网扑克发牌器| 百家乐连开6把小| 大发888ios版| 百家乐官网套装| 希尔顿百家乐试玩| 轮盘赌| 24山风水水口| 235棋牌游戏| 百家乐官网赌场博彩赌场网|