linux設備驅動模型——總線、設備、設備驅動

linux設備驅動模型如下所示(摘自《linux設備驅動開發詳解》):

在Linux內核中,分別使用bus_type、device和device_driver來描述總線、設備和設備驅動之間的關係。首先根據SOC內部的總線關係分別分類出USB總線、PCI總線、I2C總線、SPI總線等常見總線,另外考慮到SOC系統中一些獨立的外設控制器以及掛接在SOC內存空間的外設不依附前面這些總線,linux發明了一種虛擬總線,即platform總線,相應的設備稱爲platform_device,而驅動稱爲platform_driver。linux設備模型中,總線將設備和驅動綁定。在系統每註冊一個設備的時候,會尋找與之匹配的驅動;相反的,在系統每註冊一個驅動的時候,會尋找與之區域的設備,而匹配由總線完成。接下來,以platform總線、platform_device和platform_driver三者的關係爲例進行說明linux設備模型是如何通過總線管理設備與設備驅動的。

先來看下platform_device的註冊,以s3c2440平臺爲例,在mach-smdk2440.c對平臺設備進行了註冊,如下:

首先根據SOC中有哪些設備定義平臺設備數組:
static struct platform_device *smdk2440_devices[] __initdata = {
	&s3c_device_usb,
	&s3c_device_lcd,
	&s3c_device_wdt,
	&s3c_device_i2c0,
	&s3c_device_iis,
};
平臺設備的結構體定義如下:
struct platform_device {
    const char    * name;
    int        id;
    struct device    dev;
    u32        num_resources;
    struct resource    * resource;
};
接着通過platform_add_devices()註冊到platform總線上,分別通過platform_device_register()單個註冊每一個平臺設備,其源碼如下:
int platform_device_register(struct platform_device *pdev)
{
    device_initialize(&pdev->dev);
    return platform_device_add(pdev);
}
詳細看下platform_device_add的源碼:
首先將dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
然後根據設備的num_resources往設備的parent上insert_resource(),最後調用device_add()將設備註冊到device hierarchy。device_add的源碼如下:
int device_add(struct device *dev)
{
    struct device *parent = NULL;
    struct class_interface *class_intf;
    int error = -EINVAL;

    dev = get_device(dev);
    if (!dev)
        goto done;

    /* Temporarily support init_name if it is set.
     * It will override bus_id for now */
    if (dev->init_name)
        dev_set_name(dev, "%s", dev->init_name);

    if (!strlen(dev->bus_id))
        goto done;

    pr_debug("device: '%s': %s\n", dev_name(dev), __func__);

    parent = get_device(dev->parent);
    setup_parent(dev, parent);

    /* use parent numa_node */
    if (parent)
        set_dev_node(dev, dev_to_node(parent));

    /* first, register with generic layer. */
    error = kobject_add(&dev->kobj, dev->kobj.parent, "%s", dev_name(dev));
    if (error)
        goto Error;

    /* notify platform of device entry */
    if (platform_notify)
        platform_notify(dev);

    error = device_create_file(dev, &uevent_attr);
    if (error)
        goto attrError;

    if (MAJOR(dev->devt)) {
        error = device_create_file(dev, &devt_attr);
        if (error)
            goto ueventattrError;

        error = device_create_sys_dev_entry(dev);
        if (error)
            goto devtattrError;
    }

    error = device_add_class_symlinks(dev);
    if (error)
        goto SymlinkError;
    error = device_add_attrs(dev);
    if (error)
        goto AttrsError;
    error = bus_add_device(dev);
    if (error)
        goto BusError;
    error = dpm_sysfs_add(dev);
    if (error)
        goto DPMError;
    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_attach_device(dev);
    if (parent)
        klist_add_tail(&dev->knode_parent, &parent->klist_children);

    if (dev->class) {
        mutex_lock(&dev->class->p->class_mutex);
        /* tie the class to the device */
        klist_add_tail(&dev->knode_class,
                   &dev->class->p->class_devices);

        /* notify any interfaces that the device is here */
        list_for_each_entry(class_intf,
                    &dev->class->p->class_interfaces, node)
            if (class_intf->add_dev)
                class_intf->add_dev(dev, class_intf);
        mutex_unlock(&dev->class->p->class_mutex);
    }
done:
    ......
}
該函數先判斷dev是否爲空,再調用setup_parent()爲dev尋找父節點,再調用kobject_add()往父節點添加kobject節點,
然後分別調用device_create_file爲設備節點創建sysfs的設備節點,再創建一些軟連接符號,接着執行bus_attach_device(dev)爲設備
尋找對應的設備驅動,詳細看下該函數的內容:
void bus_attach_device(struct device *dev)
{
    struct bus_type *bus = dev->bus;
    int ret = 0;
    if (bus) {
        if (bus->p->drivers_autoprobe)
            ret = device_attach(dev);
        WARN_ON(ret < 0);
        if (ret >= 0)
            klist_add_tail(&dev->knode_bus, &bus->p->klist_devices);
    }
}
首先會判斷drivers_autoprobe是否爲真,然後調用device_attach(),成功後再調用 klist_add_tail(&dev->knode_bus, &bus->p->klist_devices);將設備
添加到klist_devices鏈表中,下面詳細看下device_attach函數的內容:
int device_attach(struct device *dev)
{
    int ret = 0;

    down(&dev->sem);
    if (dev->driver) {
        ret = device_bind_driver(dev);
        if (ret == 0)
            ret = 1;
        else {
            dev->driver = NULL;
            ret = 0;
        }
    } else {
        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
    }
    up(&dev->sem);
    return ret;
}
如果dev已經有驅動關聯了,就調用device_bind_driver,否則就調用bus_for_each_drv()從設備
所依附的總線上遍歷已註冊的驅動。該函數中會回調__device_attach()來爲設備尋找匹配的驅動,其源碼如下:
static int __device_attach(struct device_driver *drv, void *data)
{
    struct device *dev = data;
    return driver_probe_device(drv, dev);
}
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    int ret = 0;

    if (!device_is_registered(dev))
        return -ENODEV;
    if (drv->bus->match && !drv->bus->match(dev, drv))
        goto done;

    pr_debug("bus: '%s': %s: matched device %s with driver %s\n",
         drv->bus->name, __func__, dev_name(dev), drv->name);

    ret = really_probe(dev, drv);

done:
    return ret;
}
可以看到,最後匹配dev與drv的函數是在drv所依附的bus->match()中匹配的,由於這裏註冊的是platform總線,看下
platform總線的定義,如下:
struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_attrs    = platform_dev_attrs,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .pm        = PLATFORM_PM_OPS_PTR,
};
其match方法如下:
static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev;

    pdev = container_of(dev, struct platform_device, dev);
    return (strcmp(pdev->name, drv->name) == 0);
}
match()方法只是判斷dev->name與drv->name是否相同。現在回過來看driver_probe_device()匹配成功的內容,
若dev與drv匹配成功,則會調用really_probe(dev, drv),其源碼如下:
static int really_probe(struct device *dev, struct device_driver *drv)
{
    int ret = 0;

    atomic_inc(&probe_count);

    dev->driver = drv;
    if (driver_sysfs_add(dev)) {
        printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
            __func__, dev_name(dev));
        goto probe_failed;
    }

    if (dev->bus->probe) {
        ret = dev->bus->probe(dev);
        if (ret)
            goto probe_failed;
    } else if (drv->probe) {
        ret = drv->probe(dev);
        if (ret)
            goto probe_failed;
    }

    driver_bound(dev);
    ret = 1;
    pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
         drv->bus->name, __func__, dev_name(dev), drv->name);
    goto done;

probe_failed:
    ......
    return ret;
}
首先將dev->driver指向drv,然後判斷dev->bus->probe與drv->probe方法是否存在,在platform總線上沒有probe方法,
因此會執行drv->probe()方法,即platform_driver的probe方法。

分析好了platform_device的註冊過程,接下來看下platform_driver的註冊過程,以s3c2440平臺的s3c_device_wdt平臺設備的平臺驅動爲例,平臺驅動通過platform_driver_register註冊平臺驅動,先看下s3c_device_wdt的平臺驅動定義,如下:

static struct platform_driver s3c2410wdt_driver = {
	.probe		= s3c2410wdt_probe,
	.remove		= s3c2410wdt_remove,
	.shutdown	= s3c2410wdt_shutdown,
	.suspend	= s3c2410wdt_suspend,
	.resume		= s3c2410wdt_resume,
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "s3c2410-wdt",
	},
};
可以看到.driver.name字段與s3c_device_wdt的平臺設備的.name字段相同,當platform總線的match方法匹配出平臺設備與平臺驅動後就調用平臺驅動
的probe方法了,本例就是s3c2410wdt_driver的.probe方法。下面來詳細看下平臺驅動的註冊,即platform_driver_register函數的內容:
int platform_driver_register(struct platform_driver *drv)
{
    drv->driver.bus = &platform_bus_type;
    if (drv->probe)
        drv->driver.probe = platform_drv_probe;
    if (drv->remove)
        drv->driver.remove = platform_drv_remove;
    if (drv->shutdown)
        drv->driver.shutdown = platform_drv_shutdown;
    if (drv->suspend)
        drv->driver.suspend = platform_drv_suspend;
    if (drv->resume)
        drv->driver.resume = platform_drv_resume;
    return driver_register(&drv->driver);
}
首先將drv->driver.bus設爲platform_bus_type,然後判斷drv的probe、remove、shutdown、suspend和resume字段是否爲空,若爲空,則使用平臺總線對應的
默認平臺驅動接口,然後調用driver_register註冊平臺驅動,其源碼如下:
int driver_register(struct device_driver *drv)
{
    int ret;
    struct device_driver *other;

    if ((drv->bus->probe && drv->probe) ||
        (drv->bus->remove && drv->remove) ||
        (drv->bus->shutdown && drv->shutdown))
        printk(KERN_WARNING "Driver '%s' needs updating - please use "
            "bus_type methods\n", drv->name);

    other = driver_find(drv->name, drv->bus);
    if (other) {
        put_driver(other);
        printk(KERN_ERR "Error: Driver '%s' is already registered, "
            "aborting...\n", drv->name);
        return -EEXIST;
    }

    ret = bus_add_driver(drv);
    if (ret)
        return ret;
    ret = driver_add_groups(drv, drv->groups);
    if (ret)
        bus_remove_driver(drv);
    return ret;
}
首先根據drv->bus和drv->name從drv依附的bus中尋找是否已經註冊了drv->name的平臺驅動,若沒註冊,則調用bus_add_driver()註冊驅動,其源碼如下:
int bus_add_driver(struct device_driver *drv)
{
    struct bus_type *bus;
    struct driver_private *priv;
    int error = 0;

    bus = bus_get(drv->bus);
    if (!bus)
        return -EINVAL;

    pr_debug("bus: '%s': add driver %s\n", bus->name, drv->name);

    priv = kzalloc(sizeof(*priv), GFP_KERNEL);
    if (!priv) {
        error = -ENOMEM;
        goto out_put_bus;
    }
    klist_init(&priv->klist_devices, NULL, NULL);
    priv->driver = drv;
    drv->p = priv;
    priv->kobj.kset = bus->p->drivers_kset;
    error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
                     "%s", drv->name);
    if (error)
        goto out_unregister;

    if (drv->bus->p->drivers_autoprobe) {
        error = driver_attach(drv);
        if (error)
            goto out_unregister;
    }
    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
    module_add_driver(drv->owner, drv);

    error = driver_create_file(drv, &driver_attr_uevent);
    if (error) {
        printk(KERN_ERR "%s: uevent attr (%s) failed\n",
            __func__, drv->name);
    }
    error = driver_add_attrs(bus, drv);
    if (error) {
        /* How the hell do we get out of this pickle? Give up */
        printk(KERN_ERR "%s: driver_add_attrs(%s) failed\n",
            __func__, drv->name);
    }
    error = add_bind_files(drv);
    if (error) {
        /* Ditto */
        printk(KERN_ERR "%s: add_bind_files(%s) failed\n",
            __func__, drv->name);
    }

    kobject_uevent(&priv->kobj, KOBJ_ADD);
    return error;
out_unregister:
    ......
}
該函數首先根據drv->bus獲得drv依附的bus,再創建一個priv結構體來存放drv在內核中需要表示的相關數據結構,接着判斷
drv->bus->p->drivers_autoprobe是否爲真,若爲真,則調用driver_attach將驅動與某個平臺設備進行關聯。然後通過klist_add_tail將
priv所指向的drv添加到klist_drivers鏈表中,最後會調用driver_create_file等爲drv創建sysfs對應的設備驅動節點。
下面來詳細看下driver_attach中是如何將驅動與某個平臺設備進行關聯的,其源碼如下:
int driver_attach(struct device_driver *drv)
{
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
在bus_for_each_dev中會回調__driver_attach()函數,其源碼如下:
static int __driver_attach(struct device *dev, void *data)
{
    struct device_driver *drv = data;

    if (drv->bus->match && !drv->bus->match(dev, drv))
        return 0;

    if (dev->parent)    /* Needed for USB */
        down(&dev->parent->sem);
    down(&dev->sem);
    if (!dev->driver)
        driver_probe_device(drv, dev);
    up(&dev->sem);
    if (dev->parent)
        up(&dev->parent->sem);

    return 0;
}
該函數首先判斷drv所依附的bus->match()函數是否存在,若存在,則調用其match()函數來匹配設備與設備驅動。由於platform總線的
match()方法只是判斷platform_device與platform_driver的名字是否相同,若相同,則判斷dev->driver是否已關聯。若沒有關聯,
則調用driver_probe_device(drv, dev),該函數在上面platform_device註冊過程中已分析過,其最終調用really_probe(dev, drv),
繼而執行drv的probe方法。

分析完platform_device與platform_driver之間是如何通過platform bus進行關聯的過程後,下面再來分析platform bus的註冊流程,即platform_bus_init()的源碼(在platform.c文件中):

int __init platform_bus_init(void)
{
	int error;

	error = device_register(&platform_bus);
	if (error)
		return error;
	error =  bus_register(&platform_bus_type);
	if (error)
		device_unregister(&platform_bus);
	return error;
}
struct device platform_bus = {
    .init_name    = "platform",
};
struct bus_type platform_bus_type = {
    .name        = "platform",
    .dev_attrs    = platform_dev_attrs,
    .match        = platform_match,
    .uevent        = platform_uevent,
    .pm        = PLATFORM_PM_OPS_PTR,
};
在platform_bus的初始化函數中,首先會通過device_register()註冊platform_bus,再通過bus_register()註冊platform_bus_type。分別來看下兩個註冊
函數的內容。device_register()的源碼如下:
int device_register(struct device *dev)
{
    device_initialize(dev);
    return device_add(dev);
}
該函數的內容與平臺設備的註冊過程沒什麼區別,只不過此時註冊的是platform_bus,沒有指明parent節點,因此將platform_bus作爲一個節點。接着再來看下
bus_register()的內容,如下:
int bus_register(struct bus_type *bus)
{
    int retval;
    struct bus_type_private *priv;

    priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    priv->bus = bus;
    bus->p = priv;

    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
    if (retval)
        goto out;

    priv->subsys.kobj.kset = bus_kset;
    priv->subsys.kobj.ktype = &bus_ktype;
    priv->drivers_autoprobe = 1;

    retval = kset_register(&priv->subsys);
    if (retval)
        goto out;

    retval = bus_create_file(bus, &bus_attr_uevent);
    if (retval)
        goto bus_uevent_fail;

    priv->devices_kset = kset_create_and_add("devices", NULL,
                         &priv->subsys.kobj);
    if (!priv->devices_kset) {
        retval = -ENOMEM;
        goto bus_devices_fail;
    }

    priv->drivers_kset = kset_create_and_add("drivers", NULL,
                         &priv->subsys.kobj);
    if (!priv->drivers_kset) {
        retval = -ENOMEM;
        goto bus_drivers_fail;
    }

    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
    klist_init(&priv->klist_drivers, NULL, NULL);

    retval = add_probe_files(bus);
    if (retval)
        goto bus_probe_files_fail;

    retval = bus_add_attrs(bus);
    if (retval)
        goto bus_attrs_fail;

    pr_debug("bus: '%s': registered\n", bus->name);
    return 0;

bus_attrs_fail:
    ......
}
該函數中會申請priv結構體來存儲bus在內核中需要表示的數據結構,可以看到:
    priv->subsys.kobj.kset = bus_kset;
    priv->subsys.kobj.ktype = &bus_ktype;
    priv->drivers_autoprobe = 1;
這裏將drivers_autoprobe設爲1,且kset爲bus_kset,ktype爲bus_ktype。然後調用kset_register()註冊platform_bus的kset,接着調用
bus_create_file()爲bus創建sysfs的總線節點,然後調用priv->devices_kset = kset_create_and_add("devices", NULL,&priv->subsys.kobj);
和priv->drivers_kset = kset_create_and_add("drivers", NULL,&priv->subsys.kobj)爲總線分別創建device和driver的kset容器。然後分別
初始化priv->klist_devices和priv->klist_drivers鏈表,後面設備和設備驅動的註冊會分別添加到klist_devices和klist_drivers兩個鏈表中。在驅動註冊過程中
的bus_for_each_dev()函數中會遍歷klist_devices來尋找該驅動對應的device;在設備註冊過程中的bus_for_each_drv()函數會遍歷klist_drivers來尋找該設備
對應的driver。 
從上述的platform_bus的註冊過程可以看到,其實platform_bus的註冊分爲platform_bus設備註冊和platform_bus_type註冊。平臺總線本身也是一種設備。相當於一個頂層設備,然後其他子設備掛接到該設備下。由於linux設備驅動模型與sysfs的關係,後序需要詳細分析kobject和kset在整個linux設備驅動模型中的管理流程。


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