Linux 裝置匯流排驅動模型
儘管LDD3中說對多數程式設計師掌握裝置驅動模型不是必要的,但對於嵌入式Linux的底層程式設計師而言,對裝置驅動模型的學習非常重要。
Linux裝置模型的目的:為核心建立一個統一的裝置模型,從而又一個對系統結構的一般性抽象描述。換句話說,Linux裝置模型提取了裝置操作的共同屬性,進行抽象,並將這部分共同的屬性在核心中實現,而為需要新新增裝置或驅動提供一般性的統一介面,這使得驅動程式的開發變得更簡單了,而程式設計師只需要去學習介面就行了。
在核心裡,有各種各樣的匯流排,如 usb_bus_type、spi_bus_type、pci_bus_type、platform_bus_type、i2c_bus_type 等,核心通過匯流排將裝置與驅動分離。此文,基於 Linux2.6.32.2 簡單分析裝置驅動模型,以後看具體的匯流排裝置模型時會更加清晰。
裝置模型是層次的結構,層次的每一個節點都是通過kobject實現的。在檔案上則體現在sysfs檔案系統。
關於kobkect,前面的文章已經分析過了,如果不清楚請移步 http://blog.csdn.net/lizuobin2/article/details/51523693
kobject 結構可能的層次結構如圖:
關於 uevet mdev 前面也說過了,請參考 http://blog.csdn.net/lizuobin2/article/details/51534385
對於整個 裝置匯流排驅動模型 的樣子,大概如下圖吧,也並不複雜。簡單來說,bus 負責維護 註冊進來的devcie 與 driver ,每註冊進來一個device 或者 driver 都會呼叫 Bus->match 函式 將device 與 driver 進行配對,並將它們加入連結串列,如果配對成功,呼叫Bus->probe或者driver->probe函式
一、匯流排
核心通過 bus_register 進行 Bus 註冊,那麼它註冊到了哪裡,“根”在哪? 答案是 bus_kest
int __init buses_init(void) { // /sys/bus 目錄 這裡建立的 bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL); if (!bus_kset) return -ENOMEM; return 0; }
bus 的型別為 bus_type
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 bus_type_private *p;
};
struct bus_type_private {
struct kset subsys;
struct kset *drivers_kset;
struct kset *devices_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
};
int bus_register(struct bus_type *bus)
{
int retval;
struct bus_type_private *priv;
priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
/* 1. bus 與 prv 相互建立聯絡 */
// 私有資料 .bus -> bus 本身
priv->bus = bus;
// bus->p 指向 priv
bus->p = priv;
// 核心通知鏈
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
/* 設定 bus->prv->subsys->kobj */
// 設定 priv->subsys.kobj.name = bus->name 對應於/sys/ 目錄下的目錄名
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
// 所有的 priv->subsys.kobj.kset 指向 bus_kse 對應於圖中④與六的關係
priv->subsys.kobj.kset = bus_kset;
// 所有的priv->subsys.kobj.ktype 等於 bus_ktype
priv->subsys.kobj.ktype = &bus_ktype;
priv->drivers_autoprobe = 1;
/* 註冊 kset (bus->prv->subsys priv->devices_kset priv->drivers_kset) */
// 註冊 priv->subsys ,由於 priv->subsys.kobj.kset = bus_kset,所以會在 /sys/bus/目錄下建立 目錄 如/sys/bus/plateform
retval = kset_register(&priv->subsys);
// sysfs_create_file(&bus->p->subsys.kobj, &bus_attr_uevent->attr);
retval = bus_create_file(bus, &bus_attr_uevent);
// 由於 priv->subsys.kobj.kset = bus_kset ,因此會建立 /sys/bus/XXX/devices 目錄 如 /sys/bus/plateform/devices
priv->devices_kset = kset_create_and_add("devices", NULL,
&priv->subsys.kobj);
// 同理 建立 /sys/bus/XXX/devices 目錄 如 /sys/bus/plateform/drivers
priv->drivers_kset = kset_create_and_add("drivers", NULL,
&priv->subsys.kobj);
// 初始化 klist_devices 並設定get put 函式 初始化 klist_drivers 不知為何沒有get put ?
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
retval = add_probe_files(bus); // static inline int add_probe_files(struct bus_type *bus) { return 0; }
// 新增 bus->attrs 屬性檔案
retval = bus_add_attrs(bus);
return 0;
}
目前,能通過 bus_register 函式處理的工作有:
1、將 Bus 與 priv 相互建立聯絡
2、BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
3、設定 bus->priv->subsys(kset).kobj 的名字為 bus->name
4、設定 bus->priv->subsys(kset).kobj.kset 指向 bus_kset
5、設定 bus->priv->subsys(kset).kobj.ktype 為 bus_ktype ,提供 show store 函式
6、設定 bus->priv->drivers_autoprobe = 1;
7、註冊 bus->priv->subsys(kset) 對應於圖中④與⑥的關係
由於4,且沒有指定bus->priv->subsys(kset).kobj.Parent,會將 bus_kest.kobj 設定為 bus->priv->subsys(kset).kobj.Parent 因此,會將bus->priv->subsys(kset).kobj.entry 加入 bus_kest 連結串列,且會在/sys/bus目錄下建立相應的匯流排目錄/sys/bus/$(bus->name),例如 /sys/bus/platform
8、建立 bus_attr_uevent->attr 屬性檔案
9、建立並註冊 devices_kset ,devices_kset.kobj.parent = bus->priv->subsys.kobj ,名字為 device ,因此會建立 /sys/bus/$(bus->name)/devices
10、建立並註冊 drivers_kset ,drivers_kset.kobj.parent = bus->priv->subsys.kobj ,名字為 drivers ,因此會建立 /sys/bus/$(bus->name)/drivers
11、初始化 bus->priv->klist_devices 連結串列
12、初始化 bus->priv->klist_drivers 連結串列
13、建立 bus->bus_attrs 屬性檔案
下面來看個例子 ,修改自LDD3 。基於Linux 2.6.32.2 核心
/*
* Definitions for the virtual LDD bus.
*
* lddbus.h
*/
extern struct device ldd_bus;
extern struct bus_type ldd_bus_type;
/*
* The LDD driver type.
*/
struct ldd_driver {
char *version;
struct module *module;
struct device_driver driver;
struct driver_attribute version_attr;
};
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);
/*
* A device type for things "plugged" into the LDD bus.
*/
struct ldd_device {
char *name;
struct ldd_driver *driver;
struct device dev;
};
#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);
extern int register_ldd_device(struct ldd_device *);
extern void unregister_ldd_device(struct ldd_device *);
extern int register_ldd_driver(struct ldd_driver *);
extern void unregister_ldd_driver(struct ldd_driver *);
#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include "lddbus.h"
MODULE_AUTHOR("Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "$Revision: 1.9 $";
//--------------------------------- bus ----------------------------------------
static int ldd_match(struct device *dev, struct device_driver *drv)
{
struct ldd_device *pdev = to_ldd_device(dev);
return !strncmp(pdev->name, drv->name, strlen(drv->name));
}
struct bus_type ldd_bus_type = {
.name = "ldd",
.match = ldd_match,
};
//--------------------------------- device --------------------------------------
static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
return snprintf(buf, strlen(Version), "%s\n", Version);
}
static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);
// parent device
static void ldd_bus_release(struct device *dev)
{
printk(KERN_DEBUG "lddbus release\n");
}
static void ldd_dev_release(struct device *dev){ }
struct device ldd_bus = {
.init_name = "ldd0",<span style="white-space:pre"> </span>// ldd0 就是匯流排的名字,這裡改成 ldd_bus 更恰當
.release = ldd_bus_release
};
int register_ldd_device(struct ldd_device *ldddev)
{
ldddev->dev.bus = &ldd_bus_type;
ldddev->dev.parent = &ldd_bus;
ldddev->dev.release = ldd_dev_release;
return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);
void unregister_ldd_device(struct ldd_device *ldddev)
{
device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device);
//--------------------------------- driver --------------------------------------
static ssize_t show_version(struct device_driver *driver, char *buf)
{
struct ldd_driver *ldriver = to_ldd_driver(driver);
sprintf(buf, "%s\n", ldriver->version);
return strlen(buf);
}
int register_ldd_driver(struct ldd_driver *driver)
{
int ret;
driver->driver.bus = &ldd_bus_type;
ret = driver_register(&driver->driver);
if (ret)
return ret;
driver->version_attr.attr.name = "version";
driver->version_attr.attr.owner = driver->module;
driver->version_attr.attr.mode = S_IRUGO;
driver->version_attr.show = show_version;
driver->version_attr.store = NULL;
return driver_create_file(&driver->driver, &driver->version_attr);
}
void unregister_ldd_driver(struct ldd_driver *driver)
{
driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver);
//--------------------------------- bus ----------------------------------------
static int __init ldd_bus_init(void)
{
int ret;
device_register(&ldd_bus);
ret = bus_register(&ldd_bus_type);
if (ret)
return ret;
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
printk(KERN_NOTICE "Unable to create version attribute\n");
return ret;
}
static void ldd_bus_exit(void)
{
bus_unregister(&ldd_bus_type);
}
module_init(ldd_bus_init);
module_exit(ldd_bus_exit);
insmod bus.ko 之後發現,/sys/bus 目錄下多了一個 ldd目錄,這個目錄就是我們向核心註冊的 匯流排 ldd ,該目錄下有一個devices 和 drivers目錄,因為現在並沒有向該匯流排註冊任何的驅動和裝置,因此這兩個資料夾是空的。
cat version 會呼叫show函式,顯示我們在 Bus 中設定的屬性。
二、driver
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 */
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;
};
int driver_register(struct device_driver *drv)
{
ret = bus_add_driver(drv);
ret = driver_add_groups(drv, drv->groups);
}
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
int error = 0;
bus = bus_get(drv->bus);
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
// 在/sys/bus/xxx/drivers 目錄下建立目錄
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
// 匹配 dev
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
}
// 將driver 加入 Bus->p->kist_drivers連結串列
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
// 如果設定了drv->mod_name 根據名字尋找模組
module_add_driver(drv->owner, drv);
// 在/sys/bus/xxx/drivers/建立屬性檔案
error = driver_create_file(drv, &driver_attr_uevent);
error = driver_add_attrs(bus, drv);
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
}
kobject_uevent(&priv->kobj, KOBJ_ADD);
return 0;
}
詳細說一下driver匹配device的過程
在向Bus註冊一個driver時,會呼叫到 driver_attch(drv) 來尋找與之配對的 deivice
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
根據名字我們應該能猜測出來,呼叫Bus的每一個 dev 與 driver 進行 __driver_attach
在 __driver_attach 中,首先會呼叫到 driver_match_device 函式(return drv->bus->match ? drv->bus->match(dev, drv) : 1;)進行匹配,
如果匹配成功,則呼叫 driver_probe_device(drv, dev),然後呼叫 really_probe(dev, drv)
really_probe 中幹了四件大事
1、dev->driver = drv;
在dev 中記錄driver ,配對成功了嘛,在男方族譜上記錄一下女方的名字。。然而device_driver結構中並沒有device成員,因此並沒有在女方族譜上記錄男方的名字。
2、driver_sysfs_add(dev)
在sysfs中該 dev.kobj 目錄下建立與之匹配的driver的符號連線,名字為“driver”
在sysfs中該 driver.kobj 目錄下建立與之匹配的device的符號連線,名字為 kobject_name(&dev->kobj)
3、呼叫 probe
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
} else if (drv->probe) {
ret = drv->probe(dev);
}
4、driver_bound
klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
如果 device 未繫結到一個 driver 連結串列,則將這個 device 放入 driver 連結串列中,看來一個device只能有一個driver,但是driver可以支援多個device
總結一下 driver_register 的工作
1、初始化 drv->priv->klist_devices 連結串列,該連結串列儲存該驅動所支援的devices2、drv 與 priv 相互建立聯絡
3、設定 drv->priv->kobj.kset = bus->p->drivers_kset;
4、建立並註冊 drv->priv->kobj ,設定 drv->priv->kobj.ktype = driver_ktype ,drv->priv->kobj.name = drv->name , drv->priv->kobj.parent = bus->p->drivers_kset.kobj 因此,會建立 /sys/bus/$(bus->name)/drivers/$(drv->name) 目錄
5、呼叫 drv->bus->match(dev, drv) ,匹配dev ,匹配成功呼叫probe函式
6、將driver 加入 Bus->p->kist_drivers連結串列
7、建立屬性檔案
8、kobject_uevent(&priv->kobj, KOBJ_ADD);
下面來看個例子:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <linux/io.h>
#include "lddbus.h"
struct ldd_driver ldd_drv = {
.version = "version 1.0",
.driver = {
.name = "myldd",
},
};
static int ldd_drv_init(void){
register_ldd_driver(&ldd_drv);
return 0;
}
static void ldd_drv_exit(void){
unregister_ldd_driver(&ldd_drv);
}
module_init(ldd_drv_init);
module_exit(ldd_drv_exit);
MODULE_LICENSE("GPL");
insmod drv.ko 之後,我們會發現 /sys/bus/ldd/drivers 目錄下多了一個 myldd 目錄,這就是我們向核心註冊的ldd總線上的myldd驅動程式。同樣 cat version 會顯示設定好的屬性。
三、device
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
const char *init_name; /* initial name of the device */
struct device_type *type;
struct semaphore sem; /* semaphore to synchronize calls to
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;
/* arch specific additions */
struct dev_archdata archdata;
dev_t devt; /* dev_t, creates the sysfs "dev" */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
};
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
void device_initialize(struct device *dev)
{
// 設定 dev->kobj.kset 為 devices_kset
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
init_MUTEX(&dev->sem);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_init_wakeup(dev, 0);
device_pm_init(dev);
set_dev_node(dev, -1);
}
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct class_interface *class_intf;
dev = get_device(dev);
if (!dev->p) {
error = device_private_init(dev);
}
// 如果設定了 init_name 將 init_name 設定為dev->kobj->name
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
// 將 parent_device.kobj 設定為dev->kobj->parent
parent = get_device(dev->parent);
setup_parent(dev, parent); // 這裡需要根據 例項分析
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
// 在 xxxx 目錄下建立目錄 xxxx是其父裝置 例如platform_bus
// 如果沒有device->parent 則在/sys/ 目錄下建立
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
// 建立屬性檔案
error = device_create_file(dev, &uevent_attr);
// 如果有主次裝置號 建立dev 屬性檔案
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
error = device_create_sys_dev_entry(dev);
devtmpfs_create_node(dev);
}
// 從 dev->class->p->class_subsys.kobj 目錄下建立到 /sys/devices/xxxx/subsystem 的軟連線
error = device_add_class_symlinks(dev);
// 設定屬性檔案
error = device_add_attrs(dev);
error = bus_add_device(dev);
error = dpm_sysfs_add(dev);
device_pm_add(dev);
/* Notify clients of device addition. This call must come
* after dpm_sysf_add() and before kobject_uevent().
*/
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev); // device_attach(dev); 匹配drv
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
}
}
int bus_add_device(struct device *dev)
{
if (bus) {
// 建立屬性檔案
error = device_add_attrs(bus, dev);
// 建立 /sys/bus/$(bus->name)/devices/$(dev->name) 到 /sys/devices/$(dev->name) 的軟連線
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
// 建立 /sys/devices/$(dev->name)/subsystem 到 /sys/bus/$(bus->name)/devices/$(dev->name) 的軟連線
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
// 建立 /sys/devices/$(dev->name)/bus 到 /sys/bus/$(bus->name)/devices/$(dev->name) 的軟連線
error = make_deprecated_bus_links(dev); // return sysfs_create_link(&dev->kobj, &dev->bus->p->subsys.kobj, "bus");
// 將 dev 加入 bus->p->klist_devices 連結串列
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
return 0;
}
總結一下 device_register 函式的工作1、設定 dev->kobj.kset 為 devices_kset
2、設定 dev->kobj.ktype 為 device_ktype
3、如果設定了 init_name 將 init_name 設定為dev->kobj->name
4、將 dev->kobj->parent 設定為 parent_device.kobj
5、在 xxxx 目錄下建立目錄 xxxx是其父裝置 例如platform_bus 如果沒有device->parent 則在/sys/ 目錄下建立
6、platform_notify
7、建立屬性檔案
8、如果設定了 裝置號,則建立屬性檔案 dev
9、建立各種軟連線,其中/sys/bus/xxx/devices/目錄下 的目錄 為 /sys/devices 目錄的軟體接
10、blocking_notifier_call_chain
11、kobject_uevent
12、bus_probe_device 匹配drv ,這個匹配過程和前面註冊driver時是一樣的,最終都會呼叫到 really_probe ,匹配成功則呼叫probe函式,不再贅述。
下面來看個例子:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include "lddbus.h"
static dev_t devid;
static struct ldd_device ldd_dev = {
.name = "myldd",
.dev = {
.init_name = "myldd",
},
};
static int ldd_dev_init(void){
alloc_chrdev_region(&devid, 0, 1, "mylddtest");
//ldd_dev.dev.devt = devid;
register_ldd_device(&ldd_dev);
return 0;
}
static void ldd_dev_exit(void){
unregister_ldd_device(&ldd_dev);
}
module_init(ldd_dev_init);
module_exit(ldd_dev_exit);
MODULE_LICENSE("GPL");
device 相對driver 要複雜一些,insmod dev.ko 之後,我們可以在/sys/devices 目錄下看到新增了一個目錄 ldd0(ldd_bus) ,在 ldd0 (ldd_bus)目錄下看到我們向ldd匯流排註冊的myldd裝置(ldd0是 myldd 的父裝置),在/sys/bus/ldd/devices/ 目錄下同樣可以看到 myldd , 因為這裡的Myldd 是指向 /sys/devices/ldd0/myldd 的軟連線。
/sys/devices/ldd0/myldd/driver 目錄 與該裝置匹配的驅動程式,我們在Bus->match中設定的匹配條件--名字相同。
我們並未看到屬性檔案 dev ,是因為我們沒有指定Myldd裝置的裝置號,將 dev.c 程式碼中的 ldd_dev.dev.devt = devid 註釋去掉,解除安裝原來驅動,重新載入。
我們會發現,現在有了屬性檔案 dev ,cat dev 顯示的是該裝置的裝置號,在此條件之下,mdev 會幫我們建立裝置節點,因此在 /dev 目錄下已經有了裝置節點。
至此,簡單的裝置匯流排驅動模型就結束了,雖然例子很簡單,但我相信對以後的理解各類負責的匯流排裝置驅動模型也是會有幫助的。