關於同一個設備類申請多個設備的方法及container_of()解析

當一個設備類擁有多個子設備時,如何在一個驅動模塊中申請多個設備?.


關於一個設備類下有多個設備,在Linux系統中並不少見
toooodev
當我們編寫的驅動模塊需要達到這樣的效果時,有些關鍵的函數和思想不容錯過!

關鍵函數解析.

container_of() 頭文件: #include <linux/kernel.h>

根據結構體中的某個成員的地址,從而得到整個結構體的地址

代碼原型:
container_of()

參數 作用
ptr 已知結構體成員的地址
type 要獲取整個結構體的類型
member 已知結構體成員的名字

圖解
其實就是一個很簡單的數學算數問題,我們已知一個 type結構體類型 及type結構體成員名 member 和成員的地址 ptr;

如果我們要計算在結構體type中member的偏移量大小size,我們直接用 size = (&type) - ptr(即 &member);

同理,爲了求出type的首地址,我們只需要 &type = ptr - size 即可;

那麼關鍵的 size 怎麼求呢?
我們看到了代碼原型中有 offsetof 這個函數

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)


這裏就涉及到 0指針 的使用

#include<stdio.h>
 
struct type
{
	char i ;
	int j;
	char k;
};
 
int main()
{
	struct type temp;
	printf("&temp = %p\n",&temp);   
	printf("&temp.k = %p\n",&temp.k);
	printf("&((struct type *)0)->k = %d\n",((int)&((struct type *)0)->k));
 
}


&((struct type *)0)->k 的意思是:這裏的0被強行轉化爲 struct type * 類型,相當於有一個虛擬的首地址爲0的struct type 結構體,並用一個指針指向成員k,而 &((struct type *)0)->k 就是成員k到該結構體首地址的相對距離,也就是求出了結構體成員距離結構體首地址的距離size!運行結果:
0指針

(type *)( (char *)__mptr - offsetof(type,member) ):然後我們只需要用 ptr 已知成員函數的地址減去求出來的 size ,結構體 type 的首地址不就出來了嗎!

contianer_of源碼中的第一句: const typeof( ((type *)0)->member ) *__mptr = (ptr); ,查閱了一些大佬的博客才突然醒悟,這是內核工程師的一個小手段,就一句話就避免傳遞的 ptrmember 類型不匹配的問題。


源碼編寫思路.

由於不同的設備需要不同字符設備結構體、設備號以及 對應的設備編號;
那麼我們可以定義一個結構體數組,在結構體中包含了字符設備結構體、設備號以及對應的設備編號

struct led_dev
{
	struct cdev cdev;			//字符設備結構體,每個led燈對應字符設備結構體
	dev_t	dev_num;			//設備號
	unsigned int n;				//對應哪一個led燈
	
}led_dev_tbl[4];				//記錄4個LED的相關信息


注意事項.

 

//入口函數
static int __init gec6818_led_init(void)
{
	
	int rt=0;
	int i=0;
	
	struct resource *gpioc_res=NULL;	
	struct resource *gpioe_res=NULL;

	//動態申請設備號
	//申請次設備的數量爲4
	//顯示在/proc/devices文件
	rt=alloc_chrdev_region(&led_num,0,4,"myled");
	
	if(rt < 0)
	{
		printk("alloc_chrdev_region fail\n");
		
		return rt;
		
	}
	
	for(i=0; i<4; i++)
	{
		//獲取設備號
		led_dev_tbl[i].dev_num = led_num+i;
		
		//指定對應的led燈編號
		led_dev_tbl[i].n = i+1;
		
		
		//字符設備初始化
		cdev_init(&led_dev_tbl[i].cdev,&gec6818_led_fops);
		
		//字符設備添加到內核
		rt = cdev_add(&led_dev_tbl[i].cdev,led_dev_tbl[i].dev_num,1);
		
		if(rt < 0)
		{
			printk("cdev_add fail\n");
			goto fail_cdev_add;
			
		}		
	}

	//創建類,類只需一個
	leds_class=class_create(THIS_MODULE, "myled");
	
	if (IS_ERR(leds_class))
	{
		rt = PTR_ERR(leds_class);
		
		printk("class_create  fail\n");
		
		goto fail_class_create;
		
	}
	
	//創建設備有四個
	//myled1,myled2,myled3,myled4
	for(i=0; i<4; i++)
	{
		//在/dev/目錄看到對應的設備文件名
		leds_device=device_create(leds_class, NULL, led_dev_tbl[i].dev_num, NULL, "%s%d","myled",i+1);
		
		if (IS_ERR(leds_device))
		{
			rt = PTR_ERR(leds_device);
			
			printk("device_create  fail\n");
			
			goto fail_device_create;
			
		}			
		
		
	}
	
	...
    ...
    ...

fail_class_create:
	for(i=0; i<4; i++)
		cdev_del(&led_dev_tbl[i].cdev);	
	
fail_cdev_add:
	for(i=0; i<4; i++)
		unregister_chrdev_region(led_dev_tbl[i].dev_num,1);
	
	return rt;
}


由於不同的次設備使用的都是同一套 file_operations 文件操作集,所以要在對應的文件操作函數中,區分是什麼設備調用的,這個時候就涉及到 struct file *file 這個結構體了,通過它進行函數之間的私有數據傳遞,具體見以下源碼!

static int  gec6818_led_open (struct inode * inode, struct file *file)
{
	//如何判斷當前是打開/dev/myled1、/dev/myled2、/dev/myled3、/dev/myled4
	//獲取用戶打開的設備對應的字符設備結構體
	//container_of:根據結構體中的某個成員的地址,從而得到整個結構體的地址
	//現在必須得到led_dev_tbl[0]或led_dev_tbl[1]的首地址
	struct led_dev *dev = container_of(inode->i_cdev,struct led_dev,cdev);
	
	if(dev->n ==1 )
	{
		//配置GPIOE13爲輸出模式
		iowrite32(ioread32(gpioe_altfn0_va)&(~(3<<26)),gpioe_altfn0_va);
		iowrite32(ioread32(gpioe_outenb_va)|(1<<13),gpioe_outenb_va);			
	}

	if(dev->n ==2 )
	{
		
		//配置GPIOC17爲輸出模式
		iowrite32((ioread32(gpioc_altfn1_va)&(~(3<<2)))|(1<<2),gpioc_altfn1_va);
		iowrite32(ioread32(gpioc_outenb_va)|(1<<17),gpioc_outenb_va);	
	}
	
	if(dev->n ==3 )
	{	
		//GPIOC8
		iowrite32((ioread32(gpioc_altfn0_va)&(~(3<<16)))|(1<<16), gpioc_altfn0_va);
    	iowrite32((ioread32(gpioc_outenb_va)|(1<<8)), gpioc_outenb_va);
	}

	if(dev->n ==4 )
	{
		//GPIOC7
		iowrite32((ioread32(gpioc_altfn0_va)&(~(3<<14)))|(1<<14), gpioc_altfn0_va);
    	iowrite32((ioread32(gpioc_outenb_va)|(1<<7)), gpioc_outenb_va);
	}
	
	file ->private_data = dev;		//通過struct file *file進行函數之間的私有數據傳遞,這裏意思是假如打開了/dev/myledx ,
									//那麼gec6818_led_write函數通過struct file *file 就可以知道操作哪個led了
	
	printk("gec6818_led_open \n");
	
	return 0;
}

static ssize_t gec6818_led_write (struct file * file, const char __user * buf, size_t len, loff_t * off)
{
	int ret;
	
	char led_sta=0;
	
	struct led_dev *dev = file->private_data;   //在前面的open函數中file ->private_data = dev後,此處可以取出file ->private_data的值,便可次設備的具體信息
	
	//判斷當前len是否合法
	if(len > 1)
		return -EINVAL;		//返回參數無效錯誤碼
		
	//從用戶空間拷貝數據
	
	ret = copy_from_user(&led_sta,buf,1);
	
	if(ret !=0 )
		return -EFAULT;
            ...
            ...
            ...
	
	//獲取成功複製的字節數
	len = len - ret;

	
	return len;
}

 


我的GITHUB

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章