當一個設備類擁有多個子設備時,如何在一個驅動模塊中申請多個設備?.
關於一個設備類下有多個設備,在Linux系統中並不少見
當我們編寫的驅動模塊需要達到這樣的效果時,有些關鍵的函數和思想不容錯過!
關鍵函數解析.
container_of() 頭文件: #include <linux/kernel.h>
根據結構體中的某個成員的地址,從而得到整個結構體的地址
代碼原型:
參數 | 作用 |
---|---|
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!運行結果:
(type *)( (char *)__mptr - offsetof(type,member) )
:然後我們只需要用 ptr
已知成員函數的地址減去求出來的 size
,結構體 type
的首地址不就出來了嗎!
contianer_of源碼中的第一句: const typeof( ((type *)0)->member ) *__mptr = (ptr);
,查閱了一些大佬的博客才突然醒悟,這是內核工程師的一個小手段,就一句話就避免傳遞的 ptr
與 member
類型不匹配的問題。
源碼編寫思路.
由於不同的設備需要不同字符設備結構體、設備號以及 對應的設備編號
;
那麼我們可以定義一個結構體數組,在結構體中包含了字符設備結構體、設備號以及對應的設備編號
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;
}