姓名:鄭煜爍? 學號: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