解决方案

zephyr驱动介绍

seo靠我 2023-09-22 15:37:52

目录

一、zephyr驱动结构分析

1. 驱动模型及其实现

1.1 驱动模板代码

1.2 对外API接口subsystem_do_this/subsystem_do_that

1.3 各硬件平台实现该驱动,填充SEO靠我struct api结构

2. 驱动设备注册

3. 设备初始化

4. 系统初始化

5. 获取设备

二、zephyr驱动自定义添加

1. 驱动代码目录添加

​2. 设备树绑定文件目

3. 设备驱动API头文件目录

三、GSEO靠我PIO子系统分析(驱动举例)

1. 子系统对外API

2. 设备注册

一、zephyr驱动结构分析

1. 驱动模型及其实现

      Zephyr驱动五要素如下:

      1)   内核对象结构体,struct device;

2)设SEO靠我备申明与定义的宏,DEVICE_DEFINE/DEVICE_DT_DEFINE、;

3)设备相关的结构体,struct xxx_device_config,struct xxx_device_data;SEO靠我

      4)设备初始化接口,int (*init)(struct device *device);

      5)设备接口的结构体及其成员函数的实现,struct xxx_driver_api ;

Zephyr设备模型为配SEO靠我置作为系统一部分的驱动程序提供了一致的设备模型。设备模型负责初始化配置到系统中的所有驱动程序,每种类型的驱动程序(例如UART、SPI、I2C)都由通用子系统API支持的。其定义如下:

C

/**

* @bSEO靠我rief Runtime device structure (in ROM) per driver instance

 */

struct device {

/** Name of the device inSEO靠我stance */

        const char *name;

        /** Address of device instance config information */

        const void *config;

/**SEO靠我 Address of the API structure exposed by the device instance */

        const void *api;

/** Address of the coSEO靠我mmon device state */

        struct device_state * const state;

/** Address of the device instance private datSEO靠我a */

        void * const data;

        /** optional pointer to handles associated with the device.

         *

* This encodes a sSEO靠我equence of sets of device handles that have

* some relationship to this node. The individual sets areSEO靠我

         * extracted with dedicated API, such as

         * device_required_handles_get().

         */

Z_DEVICE_HANDLES_CONST deviSEO靠我ce_handle_t * const handles;

#ifdef CONFIG_PM_DEVICE

        /** Reference to the device PM resources. */

strucSEO靠我t pm_device * const pm;

#endif

};

name:设备实例名称。

config:用于驱动配置数据,比如I2C驱动,配置包括寄存器地址,时钟源或者设备其他物理数据等。该指针是在使用DSEO靠我EVICE_DEFINE()或者相关宏时传递的。

api:该结构体将通用子系统api映射到驱动程序中用于设备驱动的实现。它通常是只读的,并在构建时填充。

data:可为设备管理指定一个数据结构,比如存放一SEO靠我个锁或者信号,用于API中write/read的阻塞模式实现等。

大多数设备驱动模型将实现一个具备通用设备驱动API的子系统,该子系统可以构造多个设备驱动实例,每个实例中的device->api再由具体SEO靠我的底层驱动实现。

1.1 驱动模板代码

1.2 对外API接口subsystem_do_this/subsystem_do_that

C

typedef int (*subsystem_do_this_t)(coSEO靠我nst struct device *dev, int foo, int bar);

typedef void (*subsystem_do_that_t)(const struct device *dSEO靠我ev, void *baz);

struct subsystem_api {

      subsystem_do_this_t do_this;

      subsystem_do_that_t do_that;

};

statiSEO靠我c inline int subsystem_do_this(const struct device *dev, int foo, int bar)

{

struct subsystem_api *apiSEO靠我;

      api = (struct subsystem_api *)dev->api;

      return api->do_this(dev, foo, bar);

}

static inline void subsySEO靠我stem_do_that(const struct device *dev, void *baz)

{

      struct subsystem_api *api;

api = (struct subsystem_SEO靠我api *)dev->api;

      api->do_that(dev, baz);

}

1.3 各硬件平台实现该驱动,填充struct api结构

C

static int my_driver_do_this(consSEO靠我t struct device *dev, int foo, int bar)

{

      ...

}

static void my_driver_do_that(const struct device *dev, SEO靠我void *baz)

{

      ...

}

static struct subsystem_api my_driver_api_funcs = {

      .do_this = my_driver_do_this,

.do_tSEO靠我hat = my_driver_do_that

};

2. 驱动设备注册

内核在路径zephyr\include\device.h下提供了宏接口,用于设备驱动的注册,并且按照注册的参数自动进行初始化,下面介绍SEO靠我几个常用的宏。

DEVICE_DEFINE

C

#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_device,                \

dSEO靠我ata_ptr, cfg_ptr, level, prio, api_ptr)                \

Z_DEVICE_STATE_DEFINE(DT_INVALID_NODE, dev_nSEO靠我ame) \

        Z_DEVICE_DEFINE(DT_INVALID_NODE, dev_name, drv_name, init_fn,        \

pm_device,              SEO靠我                          \

                        data_ptr, cfg_ptr, level, prio, api_ptr,        \

&Z_DEVICE_STATE_NAME(devSEO靠我_name))

Z_DEVICE_STATE_DEFINE

C

#define Z_DEVICE_STATE_DEFINE(node_id, dev_name)                       SEO靠我 \

        static struct device_state Z_DEVICE_STATE_NAME(dev_name)        \

__attribute__((__section__(".z_deSEO靠我vstate")));

Z_DEVICE_DEFINE

C

#define Z_DEVICE_DEFINE(node_id, dev_name, drv_name, init_fn, pm_device,\SEO靠我

                        data_ptr, cfg_ptr, level, prio, api_ptr, state_ptr, ...)        \

Z_DEVICE_DEFINE_PRE(node_id, dev_naSEO靠我me, __VA_ARGS__)                \

COND_CODE_1(DT_NODE_EXISTS(node_id), (), (static))                \SEO靠我

                const Z_DECL_ALIGN(struct device)                        \

DEVICE_NAME_GET(dev_name) __used          SEO靠我              \

        __attribute__((__section__(".z_device_" #level STRINGIFY(prio)"_"))) = { \

.name = drvSEO靠我_name,                                        \

.config = (cfg_ptr),                                 SEO靠我       \

                .api = (api_ptr),                                        \

.state = (state_ptr),             SEO靠我                           \

                .data = (data_ptr),                                        \

COND_CODE_1(SEO靠我CONFIG_PM_DEVICE, (.pm = pm_device,), ())        \

Z_DEVICE_DEFINE_INIT(node_id, dev_name)           SEO靠我             \

        };                                                                \

BUILD_ASSERT(sizeofSEO靠我(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \

Z_STRINGIFY(DEVICE_NAME_GET(drv_name)) " too longSEO靠我"); \

        Z_INIT_ENTRY_DEFINE(DEVICE_NAME_GET(dev_name), init_fn,                \

(&DEVICE_NAME_GET(dev_nSEO靠我ame)), level, prio)

#ifdef __cplusplus

}

参数含义:

dev_name - 设备名称,用户可通过该名称使用device_get_binding()获取设备

drv_namSEO靠我e - 驱动名称

inif_fn - 初始化函数指针

pm-action-cb - 电源管理回调函数指针,如果NULL表明不使用PM

data_ptr - 设备私有数据指针

cfg_ptr - 该驱动实例的配SEO靠我置数据指针

level,prio - 初始化等级,后面会具体介绍

api_ptr - 提供该设备驱动的struct api结构指针

可以看到是通过宏定义来声明一个struct dev,并保存至段.z_devSEO靠我ice_。不难猜出,用户调用device_get_binding()时,就会从段.z_device_通过dev_name遍历到对应的struct dev。同时通过Z_INIT_ENTRY_DEFINESEO靠我宏,将初始化函数和初始化等级注册进内核,在内核启动时会相应完成自动初始化。

DEVICE_DT_DEFINE

C

#define DEVICE_DT_DEFINE(node_id, init_fn, pm_SEO靠我device,                        \

                         data_ptr, cfg_ptr, level, prio,                \

api_ptr, ...)       SEO靠我                                 \

        Z_DEVICE_STATE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id)) \

Z_DSEO靠我EVICE_DEFINE(node_id, Z_DEVICE_DT_DEV_NAME(node_id),                \

DEVICE_DT_NAME(node_id), init_fSEO靠我n,                \

                        pm_device,                                        \

data_ptr, cfg_ptr, level, prioSEO靠我,                        \

                        api_ptr,                                        \

&Z_DEVICE_STATE_NAME(Z_DESEO靠我VICE_DT_DEV_NAME(node_id)),        \

                        __VA_ARGS__)

Z_DEVICE_STATE_DEFINE

C

#define Z_DEVICE_STATE_DEFINE(SEO靠我node_id, dev_name)                        \

static struct device_state Z_DEVICE_STATE_NAME(dev_name) SEO靠我       \

        __attribute__((__section__(".z_devstate")));

Z_DEVICE_DEFINE、

C

#define Z_DEVICE_DEFINE(node_idSEO靠我, dev_name, drv_name, init_fn, pm_device,\

data_ptr, cfg_ptr, level, prio, api_ptr, state_ptr, ...)  SEO靠我      \

        Z_DEVICE_DEFINE_PRE(node_id, dev_name, __VA_ARGS__)                \

COND_CODE_1(DT_NODE_EXISTSEO靠我S(node_id), (), (static))                \

const Z_DECL_ALIGN(struct device)                        \SEO靠我

                DEVICE_NAME_GET(dev_name) __used                        \

__attribute__((__section__(".z_device_" #leSEO靠我vel STRINGIFY(prio)"_"))) = { \

                .name = drv_name,                                        \

.config = (SEO靠我cfg_ptr),                                        \

.api = (api_ptr),                                 SEO靠我       \

                .state = (state_ptr),                                        \

.data = (data_ptr),           SEO靠我                             \

                COND_CODE_1(CONFIG_PM_DEVICE, (.pm = pm_device,), ())        \

Z_DEVICESEO靠我_DEFINE_INIT(node_id, dev_name)                        \

};                                          SEO靠我                      \

        BUILD_ASSERT(sizeof(Z_STRINGIFY(drv_name)) <= Z_DEVICE_MAX_NAME_LEN, \

Z_STRINSEO靠我GIFY(DEVICE_NAME_GET(drv_name)) " too long"); \

Z_INIT_ENTRY_DEFINE(DEVICE_NAME_GET(dev_name), init_fSEO靠我n,                \

                (&DEVICE_NAME_GET(dev_name)), level, prio)

#ifdef __cplusplus

}

DEVICE_DT_DEFINE和DEVSEO靠我ICE_DEFINE一样的作用,传入的参数是节点标识符,dev_name和drv_name是通过节点标识符node_id转换而来。dev_name就是Z_DEVICE_DT_DEV_NAME(nodeSEO靠我_id),drv_name是DEVICE_DT_NAME(node_id)。

该设备声明是外部可见的(即COND_CODE_1(DT_NODE_EXISTS部分),除了调用device_get_bindSEO靠我ing(),也可以直接通过DEVICE_DT_GET()获取。

DEVICE_DECLARE

C

 * @param name Device name*/

#define DEVICE_DECLARE(nameSEO靠我) static const struct device DEVICE_NAME_GET(name)

name -设备名称,声明一个静态设备对象,这个宏可以在顶层使用来声明一个设备,这样DEVICE_GSEO靠我ET()可以在DEVICE_DEFINE()的完整声明之前使用。

3. 设备初始化

前面分析过DEVICE_DEFINE()不仅完成设备的注册,同时也完成设备初始化函数以及初始化等级的注册,最终是通过Z_ISEO靠我NIT_ENTRY_DEFINE()来实现的,下面是具体代码实现,声明结构体struct init_entry,其成员init为注册的初始化函数,dev为该设备结构体指针,将做为初始化函数入参传递。将SEO靠我init_entry放到段.z_init中,同时附带上优先级参数。

C

#define Z_INIT_ENTRY_DEFINE(_entry_name, _init_fn, _device, _levelSEO靠我, _prio)        \

        static const Z_DECL_ALIGN(struct init_entry)                        \

_CONCAT(__initSEO靠我_, _entry_name) __used                        \

__attribute__((__section__(".z_init_" #_level STRINGISEO靠我FY(_prio)"_"))) = { \

                .init = (_init_fn),                                        \

.dev = (_device),  SEO靠我                                      \

        }

C

struct init_entry {

/** Initialization function for the initSEO靠我 entry which will take

         * the dev attribute as parameter. See below.

         */

int (*init)(const struct device SEO靠我*dev);

        /** Pointer to a device driver instance structure. Can be NULL

* if the init entry is not used SEO靠我for a device driver but a services.

         */

        const struct device *dev;

};

优先级参数列表:

level:

PRE_KERNEL_1

用于没有依赖关系的设SEO靠我备,这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。然而,中断子系统将被配置,因此可以设置中断。这个级别的Init函数在中断堆栈上运行。

PRE_KERNEL_2

用于依赖于作为PRE_KESEO靠我RNEL_1级别一部分的初始化设备的设备。这些设备在配置期间不能使用任何内核服务,因为内核服务还不可用。这个级别的Init函数在中断堆栈上运行

POST_KERNEL

用于配置过程中需要内核服务的设备。这SEO靠我个级别的Init函数在内核主任务的上下文中运行

APPLICATION

用于需要自动配置的应用程序组件(即非内核组件)。这些设备可以在配置期间使用内核提供的所有服务。这个级别的Init函数在内核主任务上运SEO靠我行。

prio:

用于level等级相同时,该值越小越早初始化,范围为0~99。

4. 系统初始化

某些场景,只需要在启动时执行某个函数初始化,而不需要像设备注册一样传递各种参数,此时就需要使用下面两个API。SEO靠我其中_init_fn为初始化函数(无入参)

C

#define SYS_INIT(_init_fn, _level, _prio)                                     SEO靠我   \

        Z_INIT_ENTRY_DEFINE(Z_SYS_NAME(_init_fn), _init_fn, NULL, _level, _prio)

C

#define SYS_DEVICE_DEFISEO靠我NE(drv_name, init_fn, level, prio)                \

__DEPRECATED_MACRO SYS_INIT(init_fn, level, prio)SEO靠我

5. 获取设备

device_get_binding

通过设备名称或者通过DT_LABEL(节点标识符)获取struct device *,从段地址_device_start~_device_end从查询。SEO靠我

C

const struct device *z_impl_device_get_binding(const char *name)

{

        const struct device *dev;

/* A nullSEO靠我 string identifies no device.  So does an empty

         * string.

         */

        if ((name == NULL) || (name[0] == \0)) {

reSEO靠我turn NULL;

        }

        /* Split the search into two loops: in the common scenario, where

* device names are storeSEO靠我d in ROM (and are referenced by the user

* with CONFIG_* macros), only cheap pointer comparisons willSEO靠我 be

         * performed. Reserve string comparisons for a fallback.

         */

for (dev = __device_start; dev != __deviSEO靠我ce_end; dev++) {

                if (z_device_is_ready(dev) && (dev->name == name)) {

                        return dev;

                }

        }

for (dev = __deviceSEO靠我_start; dev != __device_end; dev++) {

if (z_device_is_ready(dev) && (strcmp(name, dev->name) == 0)) {SEO靠我

                        return dev;

                }

        }

        return NULL;

}

DEVICE_DT_GET()

通过节点标识符获取设备指针,该设备是由DEVICE_DT_DEFINE(node_id, ...)注册的,代码如下:

CSEO靠我

#define DEVICE_DT_GET(node_id) (&DEVICE_DT_NAME_GET(node_id))

#define DEVICE_DT_NAME_GET(node_id) DEVSEO靠我ICE_NAME_GET(Z_DEVICE_DT_DEV_NAME(node_id))

二、zephyr驱动自定义添加

Zephyr驱动的添加可以分为3个级别:

有驱动API抽象,有设备树绑定:只用添加驱动SEO靠我代码

有驱动API抽象,无设备树绑定:添加设备树绑定文件和驱动代码

无驱动API抽象,无设备树绑定:添加抽象API头文件,添加设备树绑定文件,添加驱动代码

1. 驱动代码目录添加

在app/目录下添加driveSEO靠我rs目录,在drivers/zephyr下的将要添加的驱动类型分类:

如上图所示,有各种类型的驱动文件夹,例如gpio、sensor、spi等驱动类型文件,在sensor类型驱动里又有具体的如ak897SEO靠我5、bma280等sensor驱动。

逐层级CMakeLists.txt添加add_subdirectory()来将新增内容完成添加构建子目录:

逐层级添加Kconfig配置文件

2. 设备树绑定文件目

为了硬SEO靠我件上的灵活性,Zephyr引入了设备树,通过设备树绑定的方式将设备树转换为C宏来使用。Zephyr的设备树绑定文件可能不包含需要用的硬件设备,这就需要自己添加。

3. 设备驱动API头文件目录

对于个人项目SEO靠我开发来说,设备驱动API一般是项目内使用,API抽象的普遍覆盖性并不一定要非常全,此外使用的人员也不需要大范围讨论,根据需求进行自定义就可以, 所形成的头文件放到对应的驱动目录即可,例如zephyr/SEO靠我drivers/sensor/ak8975/ak8975.h。

三、GPIO子系统分析(驱动举例)

以内核GPIO子系统来分析完整的设备驱动模型,包括应用层如何使用,底层驱动如何移植适配。

1. 子系统对外ASEO靠我PI

在include\drivers\gpio.h头文件中,定义了应用层可以调用的API,以及这些API的实现。下面以gpio_pin_configure分析,应用层可以调用gpio_pin_confSEO靠我igure(...),其实现由z_impl_gpio_pin-configure(...)来完成。

C

__syscall int gpio_pin_configure(const struct deviSEO靠我ce *port,

                                 gpio_pin_t pin,

                                 gpio_flags_t flags);

static inline int z_impl_gpio_pin_configure(const structSEO靠我 device *port,

                                            gpio_pin_t pin,

                                            gpio_flags_t flags)

{

        const struct gpio_driver_api *api =

(const struct gpSEO靠我io_driver_api *)port->api;

        const struct gpio_driver_config *const cfg =

(const struct gpio_driver_confSEO靠我ig *)port->config;

        struct gpio_driver_data *data =

                (struct gpio_driver_data *)port->data;

struct gpio_dSEO靠我river_api:内核统一规范了GPIO子系统对外的API接口,不同硬件平台只需适配struct gpio_driver_api中的功能即可。

C

__subsystem struct gpio_driSEO靠我ver_api {

        int (*pin_configure)(const struct device *port, gpio_pin_t pin,

                             gpio_flags_t flags);

int (*poSEO靠我rt_get_raw)(const struct device *port,

                            gpio_port_value_t *value);

int (*port_set_masked_raw)(const strSEO靠我uct device *port,

                                   gpio_port_pins_t mask,

                                   gpio_port_value_t value);

int (*port_set_bits_raw)(const strucSEO靠我t device *port,

                                 gpio_port_pins_t pins);

        int (*port_clear_bits_raw)(const struct device *port,

gpio_portSEO靠我_pins_t pins);

        int (*port_toggle_bits)(const struct device *port,

                                gpio_port_pins_t pins);

int (*pin_intSEO靠我errupt_configure)(const struct device *port,

                                       gpio_pin_t pin,

                                       enum gpio_int_mode, enum gpio_int_trig);

iSEO靠我nt (*manage_callback)(const struct device *port,

                               struct gpio_callback *cb,

                               bool set);

uint32_t (*get_peSEO靠我nding_int)(const struct device *dev);

};

2. 设备注册

分析drivers\gpio\gpio_stm32.c代码和dts文件,看下stm32驱动是如何适配到GPIOSEO靠我子系统中

举例设备树中子节点信息如下:

C

gpioa: gpio@48000000 {

        compatible = "st,stm32-gpio";

        gpio-controller;

#gpio-cells = SEO靠我< 0x2 >;

        reg = < 0x48000000 0x400 >;

        clocks = < &rcc 0x1 0x1 >;

        label = "GPIOA";

        phandle = < 0x22 >;

    };

如果SEO靠我gpioa节点存在,则执行GPIO_DEVICE_INIT_STM32(a, A)

C

#if DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay)

GPIO_DEVISEO靠我CE_INIT_STM32(a, A);

#endif /* DT_NODE_HAS_STATUS(DT_NODELABEL(gpioa), okay) */

通过设备树节点标识符DT_NODELABELSEO靠我(gpioa),获取相关属性:reg,clocks等

C

#define GPIO_DEVICE_INIT_STM32(__suffix, __SUFFIX)                       SEO靠我 \

        GPIO_DEVICE_INIT(DT_NODELABEL(gpio##__suffix),        \

__suffix,                                  SEO靠我      \

                         DT_REG_ADDR(DT_NODELABEL(gpio##__suffix)),        \

STM32_PORT##__SUFFIX,                     SEO靠我           \

                         DT_CLOCKS_CELL(DT_NODELABEL(gpio##__suffix), bits),

DT_CLOCKS_CELL(DT_NODELABEL(gpio##__sSEO靠我uffix), bus))

GPIO_DEVICE_INIT()做3件事:生成gpio_stm32_config,gpio_stm32_data,再完成设备注册

C

#define GPIO_DEVICE_SEO靠我INIT(__node, __suffix, __base_addr, __port, __cenr, __bus) \

       //4.1 将设备树获取的数据,生成自定义的gpio_stm32_config

sSEO靠我tatic const struct gpio_stm32_config gpio_stm32_cfg_## __suffix = {   \

.common = {                  SEO靠我                                     \

                         .port_pin_mask = GPIO_PORT_PIN_MASK_FROM_NGPIOS(16U), \

},     SEO靠我                                                          \

.base = (uint32_t *)__base_addr,         SEO靠我                              \

.port = __port,                                                      SEO靠我 \

                .pclken = { .bus = __bus, .enr = __cenr }                       \

        };        

/*4.2 声明一个私有数据gpio_stm32_data*/SEO靠我                                                                \

static struct gpio_stm32_data gpio_SEO靠我stm32_data_## __suffix;               \

PM_DEVICE_DT_DEFINE(__node, gpio_stm32_pm_action);           SEO靠我            \

        /*

        4.3 完成设备注册,包括初始化函数gpio_stm32_init,电源管理gpio_stm32_pm_device_ctrl,

私有数据&gpio_stm32_data_SEO靠我## __suffix,

        私有配置&gpio_stm32_cfg_## __suffix,

        初始化等级PRE_KERNEL_1,CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,

以及SEO靠我子系统需要适配的struct gpio_driver_api:&gpio_stm32_driver

        */

DEVICE_DT_DEFINE(__node,                         SEO靠我                      \

                            gpio_stm32_init,                                       \

PM_DEVICE_DT_GET(__noSEO靠我de),                               \

                            &gpio_stm32_data_## __suffix,                       \

&gpio_stm32SEO靠我_cfg_## __suffix,                       \

                            PRE_KERNEL_1,                                       \

CONFIGSEO靠我_GPIO_INIT_PRIORITY,                               \

                            &gpio_stm32_driver)

DEVICE_DT_DEFINE()将初始化函数gpio_SEO靠我stm32_init()注册进系统,在内核启动阶段会按优先级自动执行。看代码主要完成一些时钟和电源管理的配置,gpio_stm32_driver完成底层驱动需适配的功能函数。

3. 应用层如何使用

前面已介SEO靠我绍了stm32-gpio通过设备树子节点gpioa: gpio@48000000将GPIOA注册进子系统,下面介绍用户如何使用。

3.1 如何获取设备

device_get_binding(DT_LABESEO靠我L(节点标识符))

C

const struct device *gpioa_dev;

static int pins_stm32_init(void)

{

gpioa = device_get_bindingSEO靠我(DT_LABEL(DT_NODELABEL(gpioa_dev)));

    if (!gpioa) {

        return -ENODEV;

    }

}

DEVICE_DT_GET(节点标识符)

C

struct deviceSEO靠我 *gpioa_dev;

static int pins_stm32_init(void)

{

    gpioa = DEVICE_DT_GET(DT_NODELABEL(gpioa_dev));

if (!gpiSEO靠我oa) {

        return -ENODEV;

    }

}

3.2 如何使用设备

直接通过获取的struct device调用子系统API控制驱动设备

C

gpio_pin_set(gpioa, PIN_INX, 1);

gSEO靠我pio_pin_configure(gpioa, PIN_INX, GPIO_OUTPUT_ACTIVE | FLAGS);
“SEO靠我”的新闻页面文章、图片、音频、视频等稿件均为自媒体人、第三方机构发布或转载。如稿件涉及版权等问题,请与 我们联系删除或处理,客服邮箱:html5sh@163.com,稿件内容仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同 其观点或证实其内容的真实性。

网站备案号:浙ICP备17034767号-2