Chinaunix首页 | 论坛 | 博客
  • 博客访问: 559497
  • 博文数量: 105
  • 博客积分: 3274
  • 博客等级: 中校
  • 技术积分: 1161
  • 用 户 组: 普通用户
  • 注册时间: 2010-02-21 12:14
文章分类

全部博文(105)

文章存档

2011年(1)

2010年(104)

分类: LINUX

2010-07-22 20:16:10

以at91sam系列的spi为例子

Platform设备注册

at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices));

ek_spi_devices结构体如下:

/*

 * SPI devices.

 */

static struct ek_spi_devices[] = {

    {  

        .modalias   = "spidev",

        .chip_select    = 1,

        .max_speed_hz   = 1 * 1000 * 1000,

        .bus_num    = 0,

        .mode               = SPI_MODE_3,

    },

    { 

        .modalias   = "spidev",

        .chip_select    = 2,

        .max_speed_hz   = 1 * 1000 * 1000,

        .bus_num    = 0,

    },

};

这里出现了spi_board_info的结构体,来看看它的定义

/*---------------------------------------------------------------------------*/

 

/*

 * INTERFACE between board init code and SPI infrastructure.

 *

 * No SPI driver ever sees these SPI device table segments, but

 * it's how the SPI core (or adapters that get hotplugged) grows

 * the driver model tree.

 *

 * As a rule, SPI devices can't be probed.  Instead, board init code

 * provides a table listing the devices which are present, with enough * information to bind and set up the device's driver.  There's basic

 * support for nonstatic configurations too; enough to handle adding

 * parport adapters, or microcontrollers acting as USB-to-SPI bridges.

 */

板子初始化代码与spi的接口

虽然不能看到spi设备表,但是它表现了spi核心(或者可以热插拔的适配器)生长的驱动模型树。通常spi设备不能够被探测到,取代方案是板子的初始化代码提供一个存在的设备列表,这个设备列表必须提供足够的信息来绑定和启动设备驱动。这也是支持非静态配置的驱动的基础,也足以应付增加的parport适配器或者表现为USB-to-spi的微控制器。

/**

 * struct spi_board_info - board-specific template for a SPI device

 * @modalias: Initializes spi_device.modalias; identifies the driver. 设备名字,用来与后面的驱动的设备名字匹配,若与后面的platform_driver的设备的名字不一致,将会找不到设备

 * @platform_data: Initializes spi_device.platform_data; the particular data stored there is driver-specific.初始化spi_device.platform_data,其中存储的数据是私有的

 * @controller_data: Initializes spi_device.controller_data; some controllers need hints about hardware setup, e.g. for DMA.初始化spi_device.controller_data,一些控制器关于硬件启动比如DMA所必须的提示信息

 * @irq: Initializes spi_device.irq; depends on how the board is wired.初始化spi_device.irq,由板子的线的接法决定。

 * @max_speed_hz: Initializes spi_device.max_speed_hz; based on limits       from the chip datasheet and board-specific signal quality issues.初始化spi_device.max_speed_hz,由芯片和板子具体的信号质量决定

 * @bus_num: Identifies which spi_master parents the spi_device; unused by spi_new_device(), and otherwise depends on board wiring.标志哪个spi_master来主宰spi_device, spi_new_device()不需要使用它,其他的由板子的接法决定。

 * @chip_select: Initializes spi_device.chip_select; depends on how  the board is wired.初始化spi_device.chip_select由板子的线的接法决定

 * @mode: Initializes spi_device.mode; based on the chip datasheet, board

 *    wiring (some devices support both 3WIRE and standard modes), and

 *    possibly presence of an inverter in the chipselect path.spi的模式

 *

 * When adding new SPI devices to the device tree,

 * as a partial device template.  They hold information which can't always

 * be determined by drivers.  Information that probe() can establish (such

 * as the default transfer wordsize) is not included here.

 *当增加一个新的spi设备到设备树上时,这些结构就会作为结构模板的一部分。它们保存着那些不能由驱动决定的信息。Probe()可以建立的信息(比如默认的字节传输宽度)没有包括在这里。

 * These structures are used in two places.  Their primary role is to

 * be stored in tables of board-specific device descriptors, which are

 * declared early in board initialization and then used (much later) to

 * populate a controller's device tree after the that controller's driver

 * initializes.  A secondary (and atypical) role is as a parameter to

 * spi_new_device() call, which happens after those controller drivers

 * are active in some dynamic board configuration models.

 */这些结构体用于2个地方。主要角色就是用于存储板子上具体的设备的描述,这些描述在板子初始化的就被提前声明,然后在控制器的驱动初始化后被用于构成控制器的设备树的一部分。第二个用途(和非典型的)是作为spi_new_device()的一个参数,主要用于控制器驱动动态加载的模式。

struct spi_board_info {

       /* the device name and module name are coupled, like platform_bus;

        * "modalias" is normally the driver name.

        *

        * platform_data goes to spi_device.dev.platform_data,

        * controller_data goes to spi_device.controller_data,

        * irq is copied too

        */

       char        modalias[32];

       const void       *platform_data;

       void        *controller_data;

       int          irq;

 

       /* slower signaling on noisy or low voltage boards */

       u32         max_speed_hz;

 

       /* bus_num is board specific and matches the bus_num of some

        * spi_master that will probably be registered later.

        *

        * chip_select reflects how this chip is wired to that master;

        * it's less than num_chipselect.

        */

       u16         bus_num;

       u16         chip_select;

 

       /* mode becomes spi_device.mode, and is essential for chips

        * where the default of SPI_CS_HIGH = 0 is wrong.

        */

       u8           mode;

 

       /* ... may need additional spi_device chip config data here.

        * avoid stuff protocol drivers can set; but include stuff

        * needed to behave without being bound to a driver:

        *  - quirks like clock rate mattering when not selected

        */

};

现在这个结构体应该比较清晰了,然后进入函数

at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices));

void __init at91_add_device_spi(struct spi_board_info *devices, int nr_devices)

{

       int i;

       unsigned long cs_pin;

       short enable_spi0 = 0;

       short enable_spi1 = 0;

 

       /* Choose SPI chip-selects */

       for (i = 0; i < nr_devices; i++) {

              if (devices[i].controller_data)

                     cs_pin = (unsigned long) devices[i].controller_data;

              else if (devices[i].bus_num == 0)

                     cs_pin = spi0_standard_cs[devices[i].chip_select];

              else

                     cs_pin = spi1_standard_cs[devices[i].chip_select];

 

              if (devices[i].bus_num == 0)

                     enable_spi0 = 1;

              else

                     enable_spi1 = 1;

 

              /* enable chip-select pin */

              at91_set_gpio_output(cs_pin, 1);

 

              /* pass chip-select pin to driver */

              devices[i].controller_data = (void *) cs_pin;

       }

 

       spi_register_board_info(devices, nr_devices);

 

       /* Configure SPI bus(es) */

       if (enable_spi0) {

              at91_set_B_periph(AT91_PIN_PA0, 0);       /* SPI0_MISO */

              at91_set_B_periph(AT91_PIN_PA1, 0);       /* SPI0_MOSI */

              at91_set_B_periph(AT91_PIN_PA2, 0);       /* SPI0_SPCK */

 

              at91_clock_associate("spi0_clk", &at91sam9263_spi0_device.dev, "spi_clk");

              platform_device_register(&at91sam9263_spi0_device);

       }

       if (enable_spi1) {

              at91_set_A_periph(AT91_PIN_PB12, 0);     /* SPI1_MISO */

              at91_set_A_periph(AT91_PIN_PB13, 0);     /* SPI1_MOSI */

              at91_set_A_periph(AT91_PIN_PB14, 0);     /* SPI1_SPCK */

 

              at91_clock_associate("spi1_clk", &at91sam9263_spi1_device.dev, "spi_clk");

              platform_device_register(&at91sam9263_spi1_device);

       }

}

先选择好控制器,然后选择片选脚,再初始化相应的PIN脚,时钟。

我们主要关注的以下2个函数。

spi_register_board_info(devices, nr_devices);

platform_device_register(&at91sam9263_spi0_device);

spi_register_board_info这个函数比较简单,主要是spi没有什么太多的信息,基本上什么都不用做。如下

static inline int spi_register_board_info(struct spi_board_info const *info, unsigned n)

       { return 0; }

然后看下platform_device_register(&at91sam9263_spi0_device);

看看at91sam9263_spi0_device的定义

static struct platform_device at91sam9263_spi0_device = {

       .name             = "atmel_spi",

       .id          = 0,

       .dev        = {

                            .dma_mask            = &spi_dmamask,

                            .coherent_dma_mask     = DMA_BIT_MASK(32),

       },

       .resource = spi0_resources,

       .num_resources      = ARRAY_SIZE(spi0_resources),

};

结构体platform_device也介绍下

struct platform_device {

       const char       * name;   // 设备名

       int          id;           // 设备编号

       struct device   dev;      // device 结构

       u32         num_resources;  //设备所使用的各类资源数量

       struct resource * resource;  // 资源 主要是io内存和irq

};

* platform_device.name ... which is also used to for driver matching.

可用于和驱动匹配

* platform_device.id ... the device instance number, or else "-1" to indicate there's only one.

设备的编号,如果是唯一设备,则用-1表示。

 

这里主要关心devspi0_resources,

       .dev        = {

                            .dma_mask            = &spi_dmamask,

                            .coherent_dma_mask     = DMA_BIT_MASK(32),

       },

   这里用到了DMA相关,看下device里面关于DMA的部分

u64         *dma_mask;   /* dma mask (if dma'able device) */

       u64         coherent_dma_mask;/* Like dma_mask, but for

                                        alloc_coherent mappings as

                                        not all hardware supports

                                        64 bit addresses for consistent

                                        allocations such descriptors. */

 

       struct device_dma_parameters *dma_parms;

spi_dmamask定义如下  

static u64 spi_dmamask = DMA_BIT_MASK(32);说明该设备是支持32DMA操作的

然后是spi0_resources,

static struct resource spi0_resources[] = {

       [0] = {

              .start       = AT91SAM9263_BASE_SPI0,

              .end = AT91SAM9263_BASE_SPI0 + SZ_16K - 1,

              .flags      = IORESOURCE_MEM,

       },

       [1] = {

              .start       = AT91SAM9263_ID_SPI0,

              .end = AT91SAM9263_ID_SPI0,

              .flags      = IORESOURCE_IRQ,

       },

};

很明显它用到了IO内存和IO中断。

 

进入platform_device_register(&at91sam9263_spi0_device);  // 平台设备注册

/**

 * 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);

       return platform_device_add(pdev);

}

从函数中可以看出它就是把platform_device的设备初始化,然后再提供给platform_device_add

先看device_initialize(&pdev->dev);

/**

 * device_initialize - init device structure.

 * @dev: device.

 *

 * This prepares the device for use by other layers,

 * including adding it to the device hierarchy.

 * It is the first half of device_register(), if called by

 * that, though it can also be called separately, so one

 * may use @dev's fields (e.g. the refcount).

 */

这为设备让其他层使用准备,包括把它增加到设备的层次结构中去。

void device_initialize(struct device *dev)

{

       dev->kobj.kset = devices_kset;

       kobject_init(&dev->kobj, &device_ktype);

       klist_init(&dev->klist_children, klist_children_get,

                 klist_children_put);

       INIT_LIST_HEAD(&dev->dma_pools);

       INIT_LIST_HEAD(&dev->node);

       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);

}

这段代码就不说了,知道怎么回事就可以了。然后看看

/**

 * platform_device_add - add a platform device to device hierarchy

 * @pdev: platform device we're adding

 *

 * This is part 2 of platform_device_register(), though may be called

 * separately _iff_ pdev was allocated by platform_device_alloc().

 */

int platform_device_add(struct platform_device *pdev)

{

       int i, ret = 0;

 

       if (!pdev)

              return -EINVAL;

 

       if (!pdev->dev.parent)  // 如果为空则直接挂到platform

              pdev->dev.parent = &platform_bus;

 

       pdev->dev.bus = &platform_bus_type; 

 

       if (pdev->id != -1)  // 如果设备不是唯一的,则需要加上序号

              snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,

                      pdev->id);

       else           // 设备唯一 则直接把名字复制上去

              strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);

 

   // 以下的循环主要是为了个平台设备申请资源

       for (i = 0; i < pdev->num_resources; i++) {

              struct resource *p, *r = &pdev->resource[i];

 

              if (r->name == NULL)

                     r->name = pdev->dev.bus_id;

 

              p = r->parent;

              if (!p) {

                     if (r->flags & IORESOURCE_MEM)

                            p = &iomem_resource;

                     else if (r->flags & IORESOURCE_IO)

                            p = &ioport_resource;

              }

 

              if (p && insert_resource(p, r)) {

                     printk(KERN_ERR

                            "%s: failed to claim resource %d\n",

                            pdev->dev.bus_id, i);

                     ret = -EBUSY;

                     goto failed;

              }

       }

 

       pr_debug("Registering platform device '%s'. Parent at %s\n",

               pdev->dev.bus_id, pdev->dev.parent->bus_id);

 

       ret = device_add(&pdev->dev);

       if (ret == 0)

              return ret;

 

 failed:

       while (--i >= 0)

              if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))

                     release_resource(&pdev->resource[i]);

       return ret;

}

这里先看看以下几个结构体

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 (*suspend_late)(struct device *dev, pm_message_t state);

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

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

       struct pm_ext_ops *pm;

       struct bus_type_private *p;

};

 

struct bus_type platform_bus_type = {

       .name             = "platform",

       .dev_attrs       = platform_dev_attrs,

       .match            = platform_match,

       .uevent           = platform_uevent,

       .pm         = PLATFORM_PM_OPS_PTR,

};

只有非常少的bus_tyoe成员需要初始化;它们中的大多数都由设备模型核心所控制,但是我们必须为总线指定名字以及其他必要的方法。

上面这个platform总线名字是platform

属性platform_dev_attrs,

static struct device_attribute platform_dev_attrs[] = {

       __ATTR_RO(modalias),

       __ATTR_NULL,

};

也仅一个名字。

platform_match就是看总线设备与总线驱动能否匹配上,实际上就是比较了下名字。

platform_uevent主要用于热插拔。

 

看看函数的最后,ret = device_add(&pdev->dev);

进去瞅瞅 ,函数相对长点,但是也挺容易。

/**

 * device_add - add device to device hierarchy.

 * @dev: device.

 *

 * This is part 2 of device_register(), though may be called

 * separately _iff_ device_initialize() has been called separately.

 *

 * This adds it to the kobject hierarchy via kobject_add(), adds it

 * to the global and sibling lists for the device, then

 * adds it to the other relevant subsystems of the driver model.

 */

int device_add(struct device *dev)

{

       struct device *parent = NULL;

       struct class_interface *class_intf;

       int error = -EINVAL;

 

       dev = get_device(dev);  // 设备使用计数加1

       if (!dev)

              goto done;

 

       /* Temporarily support init_name if it is set.

        * It will override bus_id for now */

       if (dev->init_name)  // 如果有初始化名字,则覆盖掉bus_id

              dev_set_name(dev, "%s", dev->init_name);

 

       if (!strlen(dev->bus_id))

              goto done;

 

       pr_debug("device: '%s': %s\n", dev->bus_id, __func__);

 

       parent = get_device(dev->parent);  // 设备parent引用计数加1

       setup_parent(dev, parent);  // 建立起设备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->bus_id);

       if (error)

              goto Error;

 

       /* notify platform of device entry */

       if (platform_notify)

              platform_notify(dev);

 

       /* notify clients of device entry (new way) */

       if (dev->bus)

              blocking_notifier_call_chain(&dev->bus->p->bus_notifier,

                                        BUS_NOTIFY_ADD_DEVICE, dev);

 

       error = device_create_file(dev, &uevent_attr);  // 增加uevent属性

       if (error)

              goto attrError;

 

       if (MAJOR(dev->devt)) {       // 如果定义了设备号,则增加dev设备号属性

              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);

       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 */

              list_add_tail(&dev->node, &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:

       put_device(dev);

       return error;

 DPMError:

       bus_remove_device(dev);

 BusError:

       if (dev->bus)

              blocking_notifier_call_chain(&dev->bus->p->bus_notifier,

                                        BUS_NOTIFY_DEL_DEVICE, dev);

       device_remove_attrs(dev);

 AttrsError:

       device_remove_class_symlinks(dev);

 SymlinkError:

       if (MAJOR(dev->devt))

              device_remove_sys_dev_entry(dev);

 devtattrError:

       if (MAJOR(dev->devt))

              device_remove_file(dev, &devt_attr);

 ueventattrError:

       device_remove_file(dev, &uevent_attr);

 attrError:

       kobject_uevent(&dev->kobj, KOBJ_REMOVE);

       kobject_del(&dev->kobj);

 Error:

       cleanup_device_parent(dev);

       if (parent)

              put_device(parent);

       goto done;

}

从以上可以看出设备注册的流程

platform_device_register->device_initialize->device_add->setup_parent->kobject_add->device_create_file->device_add_attrs->bus_add_device->kobject_uevent->bus_attach_device

 

怎样把设备添加到总线上呢,详细分析下bus_attach_device();

/**

 * bus_attach_device - add device to bus

 * @dev: device tried to attach to a driver

 *

 * - Add device to bus's list of devices. 增加设备到总线下面的设备上去

 * - Try to attach to driver. 尝试连接到驱动程序

 */

void bus_attach_device(struct device *dev)

{

       struct bus_type *bus = dev->bus;

       int ret = 0;

 

       if (bus) {  // 如果有对应的总线

              if (bus->p->drivers_autoprobe)  // 如果 drivers_autoprobe1

                     ret = device_attach(dev);   // 则把相应的设备尝试连到驱动上

              WARN_ON(ret < 0);

              if (ret >= 0)

                     klist_add_tail(&dev->knode_bus, &bus->p->klist_devices);

       }

}

进入ret = device_attach(dev)分析

/**

 * device_attach - try to attach device to a driver. 尝试把设备连上驱动

 * @dev: device.

 *

 * Walk the list of drivers that the bus has and call

 * driver_probe_device() for each pair. If a compatible

 * pair is found, break out and return.

 *遍历总线上的驱动并且调用driver_probe_device(),如果有匹配的找到,则跳出并返回。

 * Returns 1 if the device was bound to a driver;

 * 0 if no matching device was found;

 * -ENODEV if the device is not registered.

 *

 * When called for a USB interface, @dev->parent->sem must be held.

 */

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;

}

分析下bus_for_each_drv

/**

 * bus_for_each_drv - driver iterator

 * @bus: bus we're dealing with.

 * @start: driver to start iterating on.

 * @data: data to pass to the callback.

 * @fn: function to call for each driver.

 *

 * This is nearly identical to the device iterator above.

 * We iterate over each driver that belongs to @bus, and call

 * @fn for each. If @fn returns anything but 0, we break out

 * and return it. If @start is not NULL, we use it as the head

 * of the list.

 *该函数迭代了在总线上的每个驱动,并将相关的驱动结构传递给fn,同时传递给data,如果startNULL,则将从总线上的第一个驱动迭代,否则将从start后的第一个驱动迭代。如果fn返回一个非零值,则停止迭代,而这个值也会返回

 * NOTE: we don't return the driver that returns a non-zero

 * value, nor do we leave the reference count incremented for that

 * driver. If the caller needs to know that info, it must set it

 * in the callback. It must also be sure to increment the refcount

 * so it doesn't disappear before returning to the caller.

 */

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,

                   void *data, int (*fn)(struct device_driver *, void *))

{

       struct klist_iter i;

       struct device_driver *drv;

       int error = 0;

 

       if (!bus)

              return -EINVAL;

 

       klist_iter_init_node(&bus->p->klist_drivers, &i,

                          start ? &start->p->knode_bus : NULL);

       while ((drv = next_driver(&i)) && !error)

              error = fn(drv, data);

       klist_iter_exit(&i);

       return error;

}

看看调用的迭代函数__device_attach(),里面调用了函数driver_probe_device

/**

 * driver_probe_device - attempt to bind device & driver together

 * @drv: driver to bind a device to

 * @dev: device to try to bind to the driver

 *

 * First, we call the bus's match function, if one present, which should

 * compare the device IDs the driver supports with the device IDs of the

 * device. Note we don't do this ourselves because we don't know the

 * format of the ID structures, nor what is to be considered a match and

 * what is not.

 *首先,会调用总线匹配函数,就是简单的比较下driverdevice iddevicedevice id

 * This function returns 1 if a match is found, -ENODEV if the device is

 * not registered, and 0 otherwise.

 *

 * This function must be called with @dev->sem held.  When called for a

 * USB interface, @dev->parent->sem must be held as well.

 */

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;                             //match方法进行匹配

 

       pr_debug("bus: '%s': %s: matched device %s with driver %s\n",

               drv->bus->name, __func__, dev->bus_id, drv->name);

 

       ret = really_probe(dev, drv); // 增加一些属性,并真正把设备和驱动绑定上

 

done:

       return ret;

}

从以上可以看出匹配总线上驱动的软件流程如下

:bus_attach_device->device_attach->__device_attach->driver_probe_device()->really_probe->driver_bound

阅读(2182) | 评论(0) | 转发(0) |
0

上一篇:Linux gadget驱动应用

下一篇:paltform 分析(2)

给主人留下些什么吧!~~