ITKeyword,专注技术干货聚合推荐

注册 | 登录

Linux 设备总线驱动模型

lizuobin2 分享于 2016-06-04

推荐:linux设备模型之总线 设备 和驱动

《Linux内核修炼之道》读书笔记 1、 设备模型的上层建筑由总线(bus) 、设备(device)、 驱动(device_driver)这3个数据结构构成,设备模型表示了它们之间的连接关

2020腾讯云“6.18”活动开始了!!!(巨大优惠重现!4核8G,5M带宽 1999元/3年),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1059

2020阿里云最低价产品入口,含代金券(新老用户有优惠),
地址https://www.aliyun.com/minisite/goods

尽管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函数, 调用 kobject_uevent 函数设置环境变量,mdev进行创建设备节点等操作。后面,我们从 Bus driver 到 device三个部分进行详细的分析。 一、总线

内核通过 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 devicestatic 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 **gr

推荐:Linux&vxWorks总线设备驱动模型

一、字符设备驱动框架 1.linux 字符设备驱动框架 参考http://blog.csdn.net/qingfengtsing/article/details/18502883 工作主要几步 创建设备(mknode或动态分配

oups;

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 链表,该链表保存该驱动所支持的devices

2、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 目录下已经有了设备节点。

至此,简单的设备总线驱动模型就结束了,虽然例子很简单,但我相信对以后的理解各类负责的总线设备驱动模型也是会有帮助的。

推荐:LINUX设备驱动之设备模型三--device&driver&bus(一)

在清楚了kobject之后,就可以继续分析device、driver、bus了,这三者是设备驱动程序的基本数据结构。 我们可以这样理解,内核用device来表示各种设备,然后用dri

    尽管LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式Linux的底层程序员而言,对设备驱动模型的学习非常重要。     Linux设备模型的目的:为内核建立一个统一的设备模型,从

相关阅读排行


相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。