嵌入式之Linux驅動(七)

姓名:鄭煜爍? 學號:19029100010? 學院:電子工程學院

轉自:https://blog.csdn.net/u012142460/article/details/79125461

【嵌牛導讀】Linux中的設備及其所起到的作用

【嵌牛鼻子】設備模型介紹以及platform設備驅動

【嵌牛提問】驅動需要哪些設備,作用是什么

【嵌牛正文】

前面講過了字符驅動,我們把過程再來回顧一下,我們是如何來完成一個驅動的。

1、設備號相關問題,手動或自動創建設備號。

2、設備對象相關問題,完成驅動操作方法集合,并向內核注冊該設備對象。

3、生成設備節點

這其中有一個最大的問題:設備和驅動高度耦合,設備修改后,驅動也需要修改,牽一發而動全身。這為后續的驅動開發造成了很不好的影響。我們應該做到的是,高內聚低耦合。設備修改后只修改相應的設備文件,驅動需要修改就只修改驅動部分。那驅動和設備如何聯系起來的呢?這就又需要要給文件來配合,總線。這就是Linux2.6以后的設備驅動模型:總線、設備、驅動。

? ? ? 那三者是如何分工的呢?

? ? ? 設備:描述一個設備的詳細信息。例如一個串口,那應該體現該串口設備的相關信息,例如串口的名稱,設備的地址,設備的中斷號等等。

? ? ? 驅動:描述如何使用設備的方法,這個好說,其實還是一堆方法嘛。為用戶提供操作設備的方法。

? ? ? 總線:總得有一個東西把設備和驅動聯系起來把,那就是總線??偩€中有一個匹配函數,系統每注冊一個設備,就去找一下系統中與之匹配的驅動。同樣,注冊驅動的時候,也要去系統中尋找一個與之匹配的設備。這樣,設備和驅動就聯系到一起了。

? ? ? 系統中有很多總線,I2C總線,SPI總線,USB總線。但有很多設備沒有總線實體,例如LED燈,這算什么總線?Linux添加了一種虛擬總線,叫做平臺總線---platform。

我們再來了解下一個問題,linux設備模型的對象化。c語言是面向過程的語言,很多高級語言c++,java等都是面向對象的過程。c語言同樣可以實現對象化的過程。Linux的設備模型就向我們很好的展示了這個過程。我們就以platform設備模型為例來看一看。

? ? ? 在c語言如何實現一個類呢?用的就是結構體。與類相比,結構體不能定義方法,但我們可以用函數指針來作為方法。文件操作集合struct file_operations就是一個典型的例子。繼承如何實現呢?很簡單,講父類(父結構體)包含就ok了,父類(父結構體)中的變量或者方法就能用了。

? ? ? linux設備驅動模型中的根類是kobject,這是linux中所有驅動模型的基類,我們剛才講的總線、設備、驅動都是繼承

struct device {? //設備的基類

struct device *parent;

struct device_private *p;

struct kobject kobj;? ? ? ? //++++++++++++++++++++++繼承自kobject

const char *init_name; /* initial name of the device */

const struct device_type *type;

struct mutex mutex; /* mutex to synchronize calls to

* its driver.

*/

struct bus_type *bus; /* type of bus device is on */

struct device_driver *driver; /* which driver has allocated this

? device */

void *platform_data; /* Platform specific data, device

? core doesn't touch it */

struct dev_pm_info power;

struct dev_power_domain *pwr_domain;

...........}

struct device_driver {//設備驅動基類結構體

const char *name;

struct bus_type *bus;

struct module *owner;

const char *mod_name; /* used for built-in modules */

bool suppress_bind_attrs; /* disables bind/unbind via sysfs */

const struct of_device_id *of_match_table;

int (*probe) (struct device *dev);

int (*remove) (struct device *dev);

void (*shutdown) (struct device *dev);

int (*suspend) (struct device *dev, pm_message_t state);

int (*resume) (struct device *dev);

const struct attribute_group **groups;

const struct dev_pm_ops *pm;

struct driver_private *p;//這個里面包含object

};

struct driver_private {

struct kobject kobj;? ? //父類

struct klist klist_devices;

struct klist_node knode_bus;

struct module_kobject *mkobj;

struct device_driver *driver;

};

總線基類結構體

struct bus_type {

const char *name;

struct bus_attribute *bus_attrs;

struct device_attribute *dev_attrs;

struct driver_attribute *drv_attrs;

int (*match)(struct device *dev, struct device_driver *drv);

int (*uevent)(struct device *dev, struct kobj_uevent_env *env);

int (*probe)(struct device *dev);

int (*remove)(struct device *dev);

void (*shutdown)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);

int (*resume)(struct device *dev);

const struct dev_pm_ops *pm;

struct subsys_private *p;//這個里面繼續++++++++++++++=

};

struct subsys_private {

struct kset subsys;? //kset是object基類的集合

struct kset *devices_kset;

struct kset *drivers_kset;

struct klist klist_devices;

struct klist klist_drivers;

struct blocking_notifier_head bus_notifier;

unsigned int drivers_autoprobe:1;

struct bus_type *bus;

struct list_head class_interfaces;

struct kset glue_dirs;

struct mutex class_mutex;

struct class *class;

};

int (*match)(struct device *dev, struct device_driver *drv);總線中的match方法就是用來匹配驅動和設備的。

struct bus_type、struct device 、struct device_driver 分別是總線、設備、驅動的基類結構體。具體的總線、設備、驅動都繼承自這里。

繼續,來看看我們的平臺設備platform相關的類。

1、platform總線

platform總線是bus_type的一個對象實例。

struct bus_type platform_bus_type = {

.name = "platform",

.dev_attrs? = platform_dev_attrs,

.match? = platform_match,

.uevent? = platform_uevent,

.pm = &platform_dev_pm_ops,

};

借此我們來看一下,platform是如何匹配的驅動和設備的呢?

static int platform_match(struct device *dev, struct device_driver *drv)

{

struct platform_device *pdev = to_platform_device(dev);

struct platform_driver *pdrv = to_platform_driver(drv);

/* Attempt an OF style match first */

if (of_driver_match_device(dev, drv))

return 1;

/* Then try to match against the id table */

if (pdrv->id_table)

return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */

return (strcmp(pdev->name, drv->name) == 0);

}

匹配方法有三種:1、基于設備樹風格的匹配。2、基于id_table表的匹配 3、基于設備name和驅動name的匹配。設備樹的方式我們先不講,我們介紹一下后后兩種方式。請繼續向下看。

2、platform設備

struct platform_device {

const char * name;

int id;

struct device dev;

u32 num_resources;

struct resource * resource;

const struct platform_device_id *id_entry;

/* MFD cell pointer */

struct mfd_cell *mfd_cell;

/* arch specific additions */

struct pdev_archdata archdata;

};

name:設備名稱,可以用來與驅動名稱進行匹配,match函數中的name匹配方法就是比對這里的name

id:? ? ? 設備id,現在我們一般直接設置成-1,不用它了。

dev:? 設備父類

num_resources:資源數量

resource:資源結構體。這是設備最重要的東西。

id_entry:也是用來匹配,match函數中id_table匹配的方法。

resource是設備中最重要的東西。驅動要操作設備,首先需要知道什么呢?當然是設備地址了,如果有中斷還需要知道中斷號,這就是resource的作用。

struct resource {

resource_size_t start;

resource_size_t end;

const char *name;

unsigned long flags;

struct resource *parent, *sibling, *child;

};

start:資源起始(起始地址或中斷號起始值)

end:資源結束(結束地址或中斷號結束值)

name:名稱

flags:資源標志定義,表征是什么類型的資源,最常用的有以下幾個:

? ? ? ? ? IORESOURCE_MEM:內存資源,也包括IO內存,

? ? ? ? IORESOURCE_IRQ:中斷資源

? ? ? ? ? IORESOURCE_DMA :DMA通道資源

3、platform驅動

struct platform_driver {

int (*probe)(struct platform_device *);

int (*remove)(struct platform_device *);

void (*shutdown)(struct platform_device *);

int (*suspend)(struct platform_device *, pm_message_t state);

int (*resume)(struct platform_device *);

struct device_driver driver;

const struct platform_device_id *id_table;

};

platform驅動中包含多個方法、一個設備基類、一個id_table表

probe:這是匹配上之后要執行的函數。

remove:所驅動的平臺設備被移除時或平臺驅動注銷時所用

driver:基類,driver中的name成員可用來匹配設備,match匹配函數的第三種方法

id_table:可以驅動的平臺設備ID列表。match匹配函數的第二種方法。主要設置name即可。

struct platform_device_id {

char name[PLATFORM_NAME_SIZE];

kernel_ulong_t driver_data

__attribute__((aligned(sizeof(kernel_ulong_t))));

};

好,相關的類介紹完畢,現在來看看相關的API函數??偩€相關的工作一般情況下內核已經給我們做好了,我們只需要做設備和驅動相關的工作。

1、設備注冊與注銷

int platform_add_devices(struct platform_device **devs, int num)

int platform_device_register(struct platform_device *pdev)

第一個函數可以注冊多個平臺設備,第二個函數只注冊一個設備,而實際上platform_add_devices內部時多次調用platform_device_register函數的。

devs是platform_device結構體二級指針

num是要注冊的個數

pdev是platform_device結構體指針

void platform_device_unregister(struct platform_device *pdev)

設備注銷函數。

2、驅動的注冊與注銷

int platform_driver_register(struct platform_driver *drv)

驅動的注冊

void platform_driver_unregister(struct platform_driver *drv)

驅動的注銷

3、設備資源獲取

我們剛才說到,設備中類中有一個成員變量是resource,是代表設備的硬件信息。驅動要操作設備就必須獲取硬件信息,linux為我們提供了這樣的API函數。

struct resource *platform_get_resource(struct platform_device *dev,

? ? ? unsigned int type, unsigned int num)

功能:獲取設備資源

參數:dev:設備對象

? ? ? ? type:要獲取的資源類型,和resource中的flags對應。

? ? ? ? ? ? ? ? ? IORESOURCE_MEM:內存資源,也包括IO內存,

? ? ? ? ? ? ? ? ? ? IORESOURCE_IRQ:中斷資源

? ? ? ? ? ? ? ? ? ? IORESOURCE_DMA :DMA通道資源

? ? ? num:第幾個資源

返回值:成功返回獲取的資源結構體地址。

有人會不會有這樣的疑問,驅動不是分為字符設備、塊設備、網絡設備嗎?platform設備又是什么?這里不要把兩種搞混淆了,驅動仍是只有字符設備、塊設備和網絡設備三類。平臺設備只是為了符合linux的驅動模型而產生的,除了平臺設備模型,還有I2C設備模型、SPI設備模型。都是為了符合驅動模型而創建的。但i2c設備和spi設備仍然是字符設備。

看個例程把,先來簡單熟悉以下過程

設備,資源中我們假定一個寄存器地址

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/platform_device.h>

#include <linux/ioport.h>

MODULE_LICENSE("GPL");

void demo_release(struct device *dev)

{

printk("%s,%d\n", __func__, __LINE__);

}

struct resource res = {

.start = 0x10004000,

.end = 0x10004007,

.flags = IORESOURCE_MEM,

};

struct platform_device virtual_dev = {

.name = "vir_dev",? //名稱和驅動中匹配。

.id = -1,

.dev = {

.release = demo_release,

},

.num_resources = 1,

.resource = &res,

};

static int __init demo_device_init(void)

{

platform_device_register(&virtual_dev);

printk(KERN_INFO"%s,%d\n",__func__,__LINE__);

return 0;

}

static void __exit demo_device_exit(void)

{

platform_device_unregister(&virtual_dev);

printk(KERN_INFO"%s,%d\n",__func__,__LINE__);

}

module_init(demo_device_init);

module_exit(demo_device_exit);

驅動中,獲得資源,并打印開始和結束地址

#include <linux/init.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/platform_device.h>

#include <linux/errno.h>

#include <linux/ioport.h>

MODULE_LICENSE("GPL");

struct resource *res0;

static int virtual_probe(struct platform_device *pdev)

{

printk(KERN_INFO"%s,%d\n",__func__,__LINE__);

res0 = platform_get_resource(pdev, IORESOURCE_MEM, 0);

printk(KERN_INFO"startaddr:%x,endaddr:%x\n",(unsigned int)res0->start,(unsigned int)res0->end);

return 0;

}

static int virtual_remove(struct platform_device *pdev)

{

printk(KERN_INFO"%s,%d\n",__func__,__LINE__);

return 0;

}

struct platform_driver virtual_driver = {

.probe = virtual_probe,

.remove = virtual_remove,

.driver = {

.name = "vir_dev",? ? //名稱和設備中保持一致

.owner = THIS_MODULE,

}

};

module_platform_driver(virtual_driver);

最后一行是模塊入口函數和出口函數的簡寫方式,這一行就可以代替之前的一堆,這是因為,之前初始化的工作我們將都會放到probe函數中,所以xxx_init()函數除了作為入口函數以及注冊驅動外也就沒什么作用了,所以才有此簡寫方式。

加載兩個模塊執行結果

注冊設備號,注冊字符設備等工作我們放到probe函數中去操作。其余部分和我們之前介紹的字符設備驅動都一樣了?;镜目蚣芫褪沁@樣,下一節我們來看看如何在實際的開發板中利用platform設備點亮LED燈

————————————————

版權聲明:本文為CSDN博主「念念有余」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/u012142460/article/details/79125461

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容