【轉】Linux設備驅動模型之platform總線詳解

1、什麼是platform(平臺)總線?

    相對於USB、PCI、I2C、SPI等物理總線來說,platform總線是一種虛擬、抽象出來的總線,實際中並不存在這樣的總線。

    那爲什麼需要platform總線呢?其實是Linux設備驅動模型爲了保持設備驅動的統一性而虛擬出來的總線。因爲對於usb設備、i2c設備、pci設備、spi設備等等,他們與cpu的通信都是直接掛在相應的總線下面與我們的cpu進行數據交互的,但是在我們的嵌入式系統當中,並不是所有的設備都能夠歸屬於這些常見的總線,在嵌入式系統裏面,SoC系統中集成的獨立的外設控制器、掛接在SoC內存空間的外設卻不依附與此類總線。所以Linux驅動模型爲了保持完整性,將這些設備掛在一條虛擬的總線上(platform總線),而不至於使得有些設備掛在總線上,另一些設備沒有掛在總線上。

platform總線相關代碼:driver\base\platform.c 文件

相關結構體定義:include\linux\platform_device.h 文件中

2、platform總線管理下的2個重要方面

(1)兩個結構體platform_device和platform_driver

    對於任何一種Linux設備驅動模型下的總線都由兩個部分組成:描述設備相關的結構體和描述驅動相關的結構體,在platform總線下就是platform_device和platform_driver,下面是對兩個結構體的各個元素進行分析:

platform_device結構體:(include\linux\platform_device.h)

struct platform_device {           //  platform總線設備
    const char    * name;          //  平臺設備的名字
    int        id;                 //   ID 是用來區分如果設備名字相同的時候(通過在後面添加一個數字來代表不同的設備,因爲有時候有這種需求)
    struct device    dev;          //   內置的device結構體
    u32        num_resources;      //   資源結構體數量
    struct resource    * resource; //   指向一個資源結構體數組

    const struct platform_device_id    *id_entry; //  用來進行與設備驅動匹配用的id_table表

    /* arch specific additions */
    struct pdev_archdata    archdata;             //  自留地    添加自己的東西
};

platform_device結構體中的struct resource結構體分析:

struct resource {      // 資源結構體
    resource_size_t start;      // 資源的起始值,如果是地址,那麼是物理地址,不是虛擬地址
    resource_size_t end;        // 資源的結束值,如果是地址,那麼是物理地址,不是虛擬地址
    const char *name;           // 資源名
    unsigned long flags;        // 資源的標示,用來識別不同的資源
    struct resource *parent, *sibling, *child;   // 資源指針,可以構成鏈表
};

platform_driver結構體:(include\linux\platform_device.h)

struct platform_driver {
    int (*probe)(struct platform_device *);     //  這個probe函數其實和  device_driver中的是一樣的功能,但是一般是使用device_driver中的那個
    int (*remove)(struct platform_device *);    //  卸載平臺設備驅動的時候會調用這個函數,但是device_driver下面也有,具體調用的是誰這個就得分析了
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;                //   內置的device_driver 結構體 
    const struct platform_device_id *id_table;  //  該設備驅動支持的設備的列表  他是通過這個指針去指向  platform_device_id 類型的數組
};

(2)兩組接口函數(driver\base\platform.c)

int platform_driver_register(struct platform_driver *);       // 用來註冊我們的設備驅動    
void platform_driver_unregister(struct platform_driver *);  // 用來卸載我們的設備驅動
int platform_device_register(struct platform_device *);      // 用來註冊我們的設備      
void platform_device_unregister(struct platform_device *); // 用來卸載我們的設備

3、platform平臺總線的初始化

    (1)platform平臺總線的註冊初始化:  platform_bus_init

int __init platform_bus_init(void)
{
	int error;

	early_platform_cleanup();     //  進行一些早期的平臺清理

	error = device_register(&platform_bus);    //  註冊設備 (在/sys/devices/目錄下建立 platform目錄對應的設備對象  /sys/devices/platform/)
	if (error)
		return error;
	error =  bus_register(&platform_bus_type);    //  總線註冊
	if (error)
		device_unregister(&platform_bus);
	of_platform_register_reconfig_notifier();
	return error;
}

    (2)相關結構體

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;   //  總線的私有數據  p->subsys.kobj 表示該總線在驅動模型中對應的對象
};
struct bus_type_private {
    struct kset subsys;                //  這個是bus主要的kset
    struct kset *drivers_kset;         //  這個kset指針用來指向該總線的 drivers目錄的
    struct kset *devices_kset;         //  這個kse指針用來指向該總線的devices目錄的
    struct klist klist_devices;        //  用來掛接該總線下的設備的一個鏈表頭
    struct klist klist_drivers;        //   用來掛接該總線下的設備驅動的一個鏈表頭
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;  //   是否需要在設備驅動註冊時候子自動匹配設備
    struct bus_type *bus;              //  指向本bus結構體
};

    (3)函數詳解

    bus_register:

int bus_register(struct bus_type *bus)
{
    int retval;
    struct bus_type_private *priv;                               //  定義一個bus_type_private 結構體指針

    priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); //   申請分配內存
    if (!priv)
        return -ENOMEM;

    priv->bus = bus;            //   使用 priv->bus 指向我們傳進來的bus
    bus->p = priv;              //   通過  bus->p  指向priv    這裏其實就是將bus與priv建立關係,這個跟之前的device、class的設計是一樣的

    BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);

    retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);  //   給我們的bus在設備驅動模型中的對象設置名字   bus->p->subsys.kobj
    if (retval)
        goto out;

//   這裏就是對bus的私有數據進行一些填充
    priv->subsys.kobj.kset = bus_kset;      //  設置bus對象的父對象     也就是  /sys/bus 這目錄 作爲他的上層目錄  所有的具體的總線類型對象都是在這個目錄下
    priv->subsys.kobj.ktype = &bus_ktype;   //  設置bus對象的  對象類型爲 bus_ktype
    priv->drivers_autoprobe = 1;            //  配置爲在註冊設備或者是註冊設備驅動時自動進行配置    這個就決定了爲什麼我們在註冊設備或者是設備驅動能夠進行自動匹配

    retval = kset_register(&priv->subsys);  //  註冊kset結構體(內部會調用kobject_add_internal函數,也就是將bus對象添加到 /sys/bus/目錄下, /sys/bus/xxx_busType  對應具體的總線)
    if (retval)
        goto out;

    retval = bus_create_file(bus, &bus_attr_uevent);  //  在該bus下建立屬性文件   (對應的就是 bus下的 uevent屬性)
    if (retval)
        goto bus_uevent_fail;

    priv->devices_kset = kset_create_and_add("devices", NULL, //  在具體總線的目錄下創建 kset 容器對象   /sys/bus/xxx_busType/devices
                         &priv->subsys.kobj);                 //  通過priv->devices_kset指針去指向 這個目錄對應的對象
    if (!priv->devices_kset) {
        retval = -ENOMEM;
        goto bus_devices_fail;
    }

    priv->drivers_kset = kset_create_and_add("drivers", NULL,   //   /sys/bus/xxx_busType/drivers
                         &priv->subsys.kobj);                   //   通過 priv->drivers_kset 指針去指向  這個目錄對應的對象
    if (!priv->drivers_kset) {
        retval = -ENOMEM;
        goto bus_drivers_fail;
    }

    klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);  //  初始化鏈表  klist
    klist_init(&priv->klist_drivers, NULL, NULL);                            //  初始化鏈表  klist

    retval = add_probe_files(bus);    //  添加探針文件   其實內部做的還是添加屬性文件   /sys/bus/xxx_busType/drivers_probe    /sys/bus/xxx_busType/drivers_autoprobe
    if (retval)
        goto bus_probe_files_fail;

    retval = bus_add_attrs(bus);      //  根據 bus->bus_attrs 中的屬性設置來添加屬性文件
    if (retval)
        goto bus_attrs_fail;

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

bus_attrs_fail:
    remove_probe_files(bus);
bus_probe_files_fail:
    kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
    kset_unregister(bus->p->devices_kset);
bus_devices_fail:
    bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
    kset_unregister(&bus->p->subsys);
    kfree(bus->p);
out:
    bus->p = NULL;
    return retval;
}

4、platform平臺設備註冊

(1)platform平臺總線註冊函數:  platform_device_register

/**
 * platform_device_register - add a platform-level device
 * @pdev: platform device we're adding
 */
int platform_device_register(struct platform_device *pdev)
{
	device_initialize(&pdev->dev);
	arch_setup_pdev_archdata(pdev);
	return platform_device_add(pdev);
}

(2)函數分析

int platform_device_add(struct platform_device *pdev)
{
    int i, ret = 0;

    if (!pdev)
        return -EINVAL;

    if (!pdev->dev.parent)              
        pdev->dev.parent = &platform_bus;       //  將平臺設備的父設備設置爲 platform_bus (對應的就是  /sys/devices/platform 這個目錄)

    pdev->dev.bus = &platform_bus_type;         //  設置平臺設備掛接在 platform總線下     platform_bus_type

    if (pdev->id != -1)
        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id); //  給平臺設備對應的對象設置名字  name.id  (如果我們的 pdev->id 設置不等於-1時)
    else
        dev_set_name(&pdev->dev, "%s", pdev->name);

//   下面的for 循環是對平臺設備資源的一些處理
    for (i = 0; i < pdev->num_resources; i++) {
        struct resource *p, *r = &pdev->resource[i];

        if (r->name == NULL)
            r->name = dev_name(&pdev->dev);

        p = r->parent;
        if (!p) {
            if (resource_type(r) == IORESOURCE_MEM)
                p = &iomem_resource;
            else if (resource_type(r) == IORESOURCE_IO)
                p = &ioport_resource;
        }

        if (p && insert_resource(p, r)) {
            printk(KERN_ERR
                   "%s: failed to claim resource %d\n",
                   dev_name(&pdev->dev), i);
            ret = -EBUSY;
            goto failed;
        }
    }
//////////////////////////////////////////////////////////////////

    pr_debug("Registering platform device '%s'. Parent at %s\n",
         dev_name(&pdev->dev), dev_name(pdev->dev.parent));

    ret = device_add(&pdev->dev);    //   將平臺設備添加到系統中去  /sys/devices/platform/xxx
    if (ret == 0)
        return ret;

 failed:
    while (--i >= 0) {
        struct resource *r = &pdev->resource[i];
        unsigned long type = resource_type(r);

        if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
            release_resource(r);
    }

    return ret;
}

5、platform平臺設備驅動註冊

(1)platform平臺設備驅動註冊函數: platform_driver_register

/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
				struct module *owner)
{
	drv->driver.owner = owner;
	drv->driver.bus = &platform_bus_type;    //  設置設備驅動 掛接在 platform平臺總線下

        //  下面做的就是對 drv 中的函數指針進行填充 
	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;

	return driver_register(&drv->driver);    //  註冊設備驅動
}

driver_register:

 

int driver_register(struct device_driver *drv)
{
    int ret;
    struct device_driver *other;           //    定義一個設備驅動指針  other

    BUG_ON(!drv->bus->p);

    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 -EBUSY;
    }

    ret = bus_add_driver(drv);                  //   在總線掛接設備驅動  就是將設備驅動對應的kobj對象與組織建立關係
    if (ret)
        return ret;
    ret = driver_add_groups(drv, drv->groups);   //  
    if (ret)
        bus_remove_driver(drv);
    return ret;
}

bus_add_driver:

int bus_add_driver(struct device_driver *drv)
{
    struct bus_type *bus;             //  定義一個bus_type 結構體指針
    struct driver_private *priv;      //   定義一個 driver_private  指針
    int error = 0;

    bus = bus_get(drv->bus);       //   獲取 drv的bus
    if (!bus)
        return -EINVAL;

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

    priv = kzalloc(sizeof(*priv), GFP_KERNEL);  //  給priv 申請分配內存空間
    if (!priv) {
        error = -ENOMEM;
        goto out_put_bus;
    }
    klist_init(&priv->klist_devices, NULL, NULL);  //  初始化 priv->klist_devices 鏈表
    priv->driver = drv;                            //  使用 priv->driver  指向 drv
    drv->p = priv;                                 //   使用drv->p 指向 priv    這兩步見多了  ,跟之前分析的是一樣的意思  就是建立關係
    priv->kobj.kset = bus->p->drivers_kset;        //   設置設備驅動對象的父對象(  也就是指向一個 kset )    父對象就是   /sys/bus/bus_type/drivers/  這個目錄對應的對象
    error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, //  添加kobject 對象到目錄層次中     就能夠在  /sys/bus/bus_type/drivers/ 目錄中看到設備驅動對應的文件了
                     "%s", drv->name);                             //  priv->kobj->ktype =  driver_ktype     對象類型
    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);  //  鏈表掛接:   priv->knode_bus  掛接到  bus->p->klist_drivers 鏈表頭上去
    module_add_driver(drv->owner, drv);

    error = driver_create_file(drv, &driver_attr_uevent);   //  建立屬性文件:   uevent
    if (error) {
        printk(KERN_ERR "%s: uevent attr (%s) failed\n",
            __func__, drv->name);
    }
    error = driver_add_attrs(bus, drv);                    //  根據總線的   bus->drv_attrs  來建立屬性文件
    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);
    }

    if (!drv->suppress_bind_attrs) {
        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 0;

out_unregister:
    kobject_put(&priv->kobj);
    kfree(drv->p);
    drv->p = NULL;
out_put_bus:
    bus_put(bus);
    return error;
}

driver_attach:

int driver_attach(struct device_driver *drv)
{
    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);   //  這個函數的功能就是:   依次去匹配bus總線下的各個設備
}


int bus_for_each_dev(struct bus_type *bus, struct device *start,
             void *data, int (*fn)(struct device *, void *))
{
    struct klist_iter i;                //  定義一個klist_iter 結構體變量  包含:  struct klist   和  struct klist_node
    struct device *dev;             
    int error = 0;

    if (!bus)
        return -EINVAL;

    klist_iter_init_node(&bus->p->klist_devices, &i,      //  這個函數的功能就是將 klist_devices  和  knode_bus填充到 i 變量中
                 (start ? &start->p->knode_bus : NULL));
    while ((dev = next_device(&i)) && !error)       //  依次返回出總線上的各個設備結構體device
        error = fn(dev, data);                      //  對於每一個設備和設備驅動都調用fn這個函數  直道成功  或者全部都匹配不上 
    klist_iter_exit(&i);
    return error;
}



static int __driver_attach(struct device *dev, void *data)
{
    struct device_driver *drv = data;      //  定義一個device_driver 指針

    /*
     * Lock device and try to bind to it. We drop the error
     * here and always return 0, because we need to keep trying
     * to bind to devices and some drivers will return an error
     * simply if it didn't support the device.
     *
     * driver_probe_device() will spit a warning if there
     * is an error.
     */

    if (!driver_match_device(drv, dev))           //  通過這個函數進行匹配  調用總線下的match 函數
        return 0;

    if (dev->parent)    /* Needed for USB */
        device_lock(dev->parent);
    device_lock(dev);
    if (!dev->driver)
        driver_probe_device(drv, dev);   //  調用probe函數
    device_unlock(dev);
    if (dev->parent)
        device_unlock(dev->parent);

    return 0;
}



int driver_probe_device(struct device_driver *drv, struct device *dev)
{
    int ret = 0;

    if (!device_is_registered(dev))     //  判斷這個設備是否已經註冊了
        return -ENODEV;

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

    pm_runtime_get_noresume(dev);
    pm_runtime_barrier(dev);
    ret = really_probe(dev, drv);       //    在這個函數中就會調用設備驅動或者是總線下的 probe  函數
    pm_runtime_put_sync(dev);

    return ret;
}


static int really_probe(struct device *dev, struct device_driver *drv)
{
    int ret = 0;

    atomic_inc(&probe_count);
    pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
         drv->bus->name, __func__, drv->name, dev_name(dev));
    WARN_ON(!list_empty(&dev->devres_head));

    dev->driver = drv;                  //   使用 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) {             //   如果總線下的probe函數存在 則調用優先調用這個函數
        ret = dev->bus->probe(dev);
        if (ret)
            goto probe_failed;
    } else if (drv->probe) {            //  否則調用設備驅動中的probe函數
        ret = drv->probe(dev);  //   所以由此可知:  總線中的probe函數具有更高的優先級
        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:
    devres_release_all(dev);
    driver_sysfs_remove(dev);
    dev->driver = NULL;

    if (ret != -ENODEV && ret != -ENXIO) {
        /* driver matched but the probe failed */
        printk(KERN_WARNING
               "%s: probe of %s failed with error %d\n",
               drv->name, dev_name(dev), ret);
    }
    /*
     * Ignore errors returned by ->probe so that the next driver can try
     * its luck.
     */
    ret = 0;
done:
    atomic_dec(&probe_count);
    wake_up(&probe_waitqueue);
    return ret;
}

上面說到了當註冊platform平臺設備驅動時會進行自動匹配的原理,那麼當我們註冊platform平臺設備時進行自動匹配的代碼在哪裏呢?

其實這個之前在分析device_create函數時就已經分析過了,只不過沒有去詳細的分析:

/**********************************************/

platform_device_add

    device_add

        bus_probe_device     //  關鍵就在這個函數

/*********************************************/

函數分析:

void bus_probe_device(struct device *dev)
{
    struct bus_type *bus = dev->bus;             //  獲取設備中的總線類型   bus_type
    int ret;

    if (bus && bus->p->drivers_autoprobe) {       //  如果總線存在  並且  設置了自動進行設備與設備驅動匹配標誌位
        ret = device_attach(dev);                           //  則調用這個函數進行匹配
        WARN_ON(ret < 0);
    }
}




int device_attach(struct device *dev)
{
    int ret = 0;

    device_lock(dev);
    if (dev->driver) {    //    如果我們的設備早就綁定了設備驅動     那麼執行下面的
        ret = device_bind_driver(dev);
        if (ret == 0)
            ret = 1;
        else {
            dev->driver = NULL;
            ret = 0;
        }
    } else {     //   我們就分析這條
        pm_runtime_get_noresume(dev);
        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);   //  遍歷總線的鏈表匹配對應的設備驅動
        pm_runtime_put_sync(dev);
    }
    device_unlock(dev);
    return ret;
}
//  到這裏之後就和上面的其實是一樣的了

總結:  所以由此可知,當我們不管是先註冊設備還是先註冊設備驅動都會進行一次設備與設備驅動的匹配過程,匹配成功之後就會調用probe函數,匹配的原理就是去遍歷總線下的相應的鏈表來找到掛接在他下面的設備或者設備驅動,所以由此可以看出來,這個東西的設計其實是很美的。

6、platform總線下的匹配函數

(1)platform_match函數

static int platform_match(struct device *dev, struct device_driver *drv) // 總線下的設備與設備驅動的匹配函數
{
    struct platform_device *pdev = to_platform_device(dev);      //  通過device  變量獲取到 platform_device 
    struct platform_driver *pdrv = to_platform_driver(drv);      //   通過 driver 獲取  platform_driver

    /* match against the id table first */
    if (pdrv->id_table)    //   如果pdrv中的id_table 表存在
        return platform_match_id(pdrv->id_table, pdev) != NULL;  //  匹配id_table
 
    /* fall-back to driver name match */   //  第二個就是指直接匹配 pdev->name     drv->name  名字是否形同
    return (strcmp(pdev->name, drv->name) == 0);
}




static const struct platform_device_id *platform_match_id(
            const struct platform_device_id *id,
            struct platform_device *pdev)
{
    while (id->name[0]) {  //  循環去比較id_table數組中的各個id名字是否與pdev->name 相同
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;       // 將id_table數組中的名字匹配上的 這個數組項 指針賦值給 pdev->id_entry
            return id;         //  返回這個指針
        }
        id++;
    }
    return NULL;
}

總結: 由上面可知platform總線下設備與設備驅動的匹配原理就是通過名字進行匹配的,先去匹配platform_driver中的id_table表中的各個名字與platform_device->name名字是否相同,如果相同表示匹配成功直接返回,否則直接匹配platform_driver->name與platform_driver->name是否相同,相同則匹配成功,否則失敗。

 

轉自:https://www.cnblogs.com/deng-tao/p/6026373.html

 

 

 

 

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