欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 文化 > ftdi_sio驱动学习笔记 6 - 增加MPSSE GPIO

ftdi_sio驱动学习笔记 6 - 增加MPSSE GPIO

2025/3/19 17:28:33 来源:https://blog.csdn.net/pq113_6/article/details/140457618  浏览:    关键词:ftdi_sio驱动学习笔记 6 - 增加MPSSE GPIO

目录

1. ftdi_private

2. 设置MPSSE模式

3. USB写函数

4. USB读操作

5. mpsse写gpio

6. mpsse读gpio

7. ftdi_gpio_init

8. ftdi_gpio_request

9. ftdi_gpio_free

10. ftdi_gpio_direction_get

11. ftdi_gpio_direction_input

12. ftdi_gpio_direction_output

13. ftdi_gpio_init_valid_mask

14. ftdi_gpio_get

15.  ftdi_gpio_set

15. ftdi_gpio_get_multiple

16. ftdi_gpio_set_multiple

17. 验证


之前GPIO是基于CBUS的模式,对于H系列芯片还支持MPSSE模式。注意CBUS模式和MPSSE模式不能同时使用。基本是根据CBUS模式修改。

1. ftdi_private

一路MPSSE最多支持16个GPIO,所以在ftdi_private里面增加3个u16的变量来表示这些gpio的属性。

#ifdef CONFIG_GPIOLIBstruct gpio_chip gc;struct mutex gpio_lock;	/* protects GPIO state */bool gpio_registered;	/* is the gpiochip in kernel registered */bool gpio_used;		/* true if the user requested a gpio */u8 gpio_altfunc;	/* which pins are in gpio mode */u8 gpio_output;		/* pin directions cache */u8 gpio_value;		/* pin value for outputs */u16 mpsse_gpio_altfunc;u16 mpsse_gpio_output;u16 mpsse_gpio_value;
#endif

mpsse_gpio_altfunc这个变量每个位用于在系统上显示该GPIO是否被占用。 mpsse_gpio_output表示GPIO的方向,而mpsse_gpio_value表示GPIO的电平。

2. 设置MPSSE模式

首先在ftdi_sio.h里面添加MPSSE模式的参数值。

#define FTDI_SIO_BITMODE_MPSSE		0x02

然后是设置MPSSE模式的函数

static int ftdi_set_mpsse_mode(struct usb_serial_port *port)
{return ftdi_set_bitmode(port, FTDI_SIO_BITMODE_MPSSE);
}

在ftdi_private添加一个变量bitmode用来表示芯片当前处于什么模式。

u8 bitmode;

然后在ftdi_set_bitmode里面改变这个变量

priv->bitmode = mode;

因为mpsse的读写是需要通过串口的读写流程实现的,所以这里会根据进出mpsse模式打开或关闭串口,另外还需要设置latency以方便读写速度最快(H系列最小可以设置为0,其他则为4,这个值可能需要根据平台调试,如果设置为2,长时间测试可能会读数据错误)。

    if(mode == FTDI_SIO_BITMODE_MPSSE || mode == FTDI_SIO_BITMODE_ASYNC_BITBANG || mode == FTDI_SIO_BITMODE_SYNC_BITBANG) {struct tty_struct *tty = tty_port_tty_get(&port->port); unsigned int latency = priv->latency;usb_serial_generic_open(tty, port);switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:priv->latency = 0;	//For H serial, it can be set to 0.break;default:priv->latency = 4;}write_latency_timer(port);priv->latency = latency;} else {usb_serial_generic_close(port);write_latency_timer(port);}

3. USB写函数

需要添加写数据的api函数。这个函数的功能是把数据通过USB写给FTDI设备,串口默认那个写只是写缓冲,这里是直接通过usb发送。

static int ftdi_usb_write(struct usb_serial_port *port,const unsigned char *data, int count, bool wait)
{
}

参数data是要写出去的数据,count是数据的长度,wait是表示是否等待写数据结束。

直接调用串口的写函数

result = usb_serial_generic_write(tty, port, data, count);
if(result < 0){dev_err(&port->dev, "Write failed\n");return result;
}

这个API函数会先把data写入到端口的FIFO中,然后找到空闲的写urb(FT232H有2个写urb),配置完urb后提交urb即可,并不会等待数据发送结束,而对于mpsse应用,写完后需要等待数据写完,这里是设置了以100ms为单位的超时错误。

if(wait) {result = wait_event_interruptible_timeout(priv->write_wait, !port->tx_bytes, HZ *     (count / priv->max_packet_size + 1) / 10);if (result == -ERESTARTSYS) {// 信号中断睡眠result = -EINTR;} else if (!result) {// 超时dev_err(&port->dev, "Write pending timeout\n");result = -ETIMEDOUT;} else {result = count;}
}

write_wait是一个等待队列头结构体变量,需要在结构体ftdi_private中添加(需要添加头文件<linux/wait.h>)

wait_queue_head_t write_wait;  

需要增加一个写数据结束的回调函数以唤醒等待队列。

static void ftdi_write_bulk_callback(struct urb *urb)
{struct usb_serial_port *port = urb->context;struct ftdi_private *priv = usb_get_serial_port_data(port);usb_serial_generic_write_bulk_callback(urb);if(priv->bitmode == FTDI_SIO_BITMODE_MPSSE || priv->bitmode == FTDI_SIO_BITMODE_SYNC_BITBANG) {if(urb->status == 0) {if(port->tx_bytes == 0) {wake_up_interruptible(&priv->write_wait); // 唤醒等待队列  }}}
}

这里的回调函数需要在ftdi_device里面添加

.write_bulk_callback =	ftdi_write_bulk_callback,

由于这一步会覆盖标准的串口写回调函数,所以在ftdi_write_bulk_callback里面会先调用标准的回调函数usb_serial_generic_write_bulk_callback。

4. USB读操作

读是不用设置的,但是当前读到数据后的回调函数是ftdi_process_read_urb。一旦打开设备,urb读请求就会设置好,然后读入数据后会继续设置urb读请求。而FTDI芯片会一直发送数据,如果没有实际发送的数据,也会发送2个字节的modem状态,而如果有实际数据,前面2个字节也是这个状态。

首先在ftdi_private里面添加新的变量来判断数据结束和读入的数据。

wait_queue_head_t read_wait; 
struct kfifo read_fifo; 
int read_len;

对于mpsse应用,在读之前会写入命令,所以在写之前需要初始化好read_fifo, read_len这2个变量。

在ftdi_process_read_urb结束前添加

    if((priv->bitmode == FTDI_SIO_BITMODE_MPSSE || priv->bitmode == FTDI_SIO_BITMODE_SYNC_BITBANG) && count > 0) {int offset = 2;while(count > 0) {int ret = 0;count += 2;//printk("%s: count=%d,offset=%d,read_len=%d\n", __func__, count, offset, priv->read_len);if(count >= priv->max_packet_size) {if(kfifo_initialized(&priv->read_fifo)) {ret = kfifo_in(&priv->read_fifo, data + offset, priv->max_packet_size - 2);}priv->read_len -= priv->max_packet_size - 2;count -= priv->max_packet_size;offset += priv->max_packet_size;} else {if(kfifo_initialized(&priv->read_fifo)) {ret = kfifo_in(&priv->read_fifo, data + offset, count - 2);}priv->read_len -= count - 2;offset += count;count = 0;}}if(priv->read_len <= 0) {priv->read_len = 0;if(kfifo_initialized(&priv->read_fifo))wake_up_interruptible(&priv->read_wait); // 唤醒等待队列  }}

设备每max_packet_size的前面2个字节是modem status,所以数据是从data[2]开始拷贝的。如果read_len变为0或小于0,就唤醒等待队列。例如FT230X的芯片max_packet_size为64字节,假设接收到124个字节数据,那么实际接收到的数据是128字节。数据结构是:

0x30 0x60 + 62字节数据 + 0x30 0x60 + 62字节数据

所以循环64字节把数据去掉2个字节压入FIFO中。

5. mpsse写gpio

static int ftdi_set_mpsse_pins(struct usb_serial_port *port)

 把16个gpio的状态一次更新,不区分不同gpio。

static int ftdi_set_mpsse_pins(struct usb_serial_port *port)
{struct ftdi_private *priv = usb_get_serial_port_data(port);int result;u8 cmd[6];if(priv->mpsse_enable == false)return 0;cmd[0] = 0x80;cmd[1] = (u8)(priv->mpsse_gpio_value & 0xff);cmd[2] = (u8)(priv->mpsse_gpio_output & 0xff);cmd[3] = 0x82;cmd[4] = (u8)((priv->mpsse_gpio_value >> 8) & 0xff);cmd[5] = (u8)((priv->mpsse_gpio_output >> 8) & 0xff);result = ftdi_usb_write(port, cmd, true);return result;
}

6. mpsse读gpio

static int ftdi_get_mpsse_pins(struct usb_serial_port *port)

这个的返回值就是16个gpio的电平值。

mpsse读GPIO的命令是0x81和0x83,分别读入低字节和高字节GPIO状态。写完这2个命令字后读入2个字节数据。

首先初始化fifo

priv->read_len = 2;
if(kfifo_alloc(&priv->read_fifo, priv->read_len * 2, GFP_KERNEL)) {dev_info(&port->dev, "mpsse get pins read buffer malloc fail\n");return -ENOMEM;
}

然后发送2个字节的读GPIO命令

cmd[0] = 0x81;		//Read AD0-AD7
cmd[1] = 0x83;		//Read AC0-AC7
result = ftdi_usb_write(port, cmd, 2, false);
if(result < 0) {dev_info(&port->dev, "mpsse get pins fail(send command) %d\n", result);goto cleanup;
}

等待读入的数据,即2个字节数据

    result = wait_event_interruptible_timeout(priv->read_wait, !priv->read_len, HZ / 10);if (result == -ERESTARTSYS) {result = -EINTR;// 信号中断睡眠goto cleanup;} else if (result < 0) {  // 超时dev_err(&port->dev, "Read pending timeout\n");priv->read_len = 0; // 防止死循环result = -ETIMEDOUT;goto cleanup;}

最后把数据从fifo取出来,然后返回

result = kfifo_out(&priv->read_fifo, cmd, 2);result = (cmd[1] << 8) | cmd[0];cleanup:kfifo_free(&priv->read_fifo);return result;

7. ftdi_gpio_init

增加ftdi_mpsse_gpio_init_ft232h,由于FT232H是特例,既支持CBUS又支持MPSSE,所以把GPIO分为20个pin,前面4个为CBUS,后面16个是MPSSE。

static void ftdi_mpsse_gpio_init_ft232h(struct usb_serial_port *port)
{struct ftdi_private *priv = usb_get_serial_port_data(port);priv->gc.ngpio += 16;priv->mpsse_gpio_altfunc = 0x0000;
}

然后在ftdi_gpio_init_ft232h后面添加

case FT232H:
case FT232HP:
case FT233HP:result = ftdi_gpio_init_ft232h(port);ftdi_mpsse_gpio_init_ft232h(port);init_waitqueue_head(&priv->read_wait);init_waitqueue_head(&priv->write_wait);break;

同理,添加FT2232H和FT4232H的情况。

static void ftdi_mpsse_gpio_init_ft2232h(struct usb_serial_port *port)
{struct ftdi_private *priv = usb_get_serial_port_data(port);priv->gc.ngpio = 16;priv->mpsse_gpio_altfunc = 0x0000;
}static int ftdi_mpsse_gpio_init_ft4232h(struct usb_serial_port *port)
{struct ftdi_private *priv = usb_get_serial_port_data(port);if(priv->channel == CHANNEL_A || priv->channel == CHANNEL_B)priv->gc.ngpio = 8;else{priv->gc.ngpio = 0;return -EIO;}priv->mpsse_gpio_altfunc = 0x0000;return 0;
}case FT2232H:case FT2232HP:case FT2233HP:ftdi_mpsse_gpio_init_ft2232h(port);init_waitqueue_head(&priv->read_wait);init_waitqueue_head(&priv->write_wait);result = 0;break;case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:result = ftdi_mpsse_gpio_init_ft4232h(port);init_waitqueue_head(&priv->read_wait);init_waitqueue_head(&priv->write_wait);break;

注意FT4232H有点特殊,通道C和通道D不支持MPSSE。 

编译安装驱动后可以看到gpio有20个了。

:~/Project/ftdi_sio$ cd /sys/class
:/sys/class$ cd gpio/gpiochip512
:/sys/class/gpio/gpiochip512$ ls
base  device  label  ngpio  power  subsystem  uevent
:/sys/class/gpio/gpiochip512$ cat base
512
:/sys/class/gpio/gpiochip512$ cat ngpio
20

8. ftdi_gpio_request

这一步是将GPIO暴露出来时执行的,是将芯片的模式切换到CBUS或MPSSE模式,而且只需要在申请第一个GPIO时处理设置模式。

首先把ftdi_private里面的gpio_used改一下,由原来的bool型改为int型,这样记录每个gpio是否使用。

int gpio_used;		/* true if the user requested a gpio */

这个函数里面设置这个变量为true的地方移动到返回前(移动到if外),并且判断这个gpio是不是已经request了,如果是就返回EBUSY错误。

if(priv->gpio_used & (1 << offset))result = -EBUSY;
elsepriv->gpio_used = (1 << offset);

在ftdi_gpio_request里面修改,要分3种情况,

switch(priv->chip_type) {}

如果是FT232H/FT232HP/FT233HP,要区分CBUS和MPSSE模式。

case FT232H:
case FT232HP:
case FT233HP:if(offset < 4) {result = ftdi_set_cbus_pins(port);if (result) {goto fail_setmode;}} else {result = ftdi_set_mpsse_mode(port);if (result) {dev_info(&port->dev, "set mpsse mode fail %d\n", result);goto fail_setmode;}ftdi_set_mpsse_pins(port);}break;

就是将芯片切换到CBUS模式或者MPSSE模式。

如果是其他H系列的芯片则只要设置MPSSE模式。

case FT2232H:
case FT2232HP:
case FT2233HP:
case FT4232H:
case FT4232HP:
case FT4233HP:
case FT4232HA:result = ftdi_set_mpsse_mode(port);if (result) {goto fail_setmode;}	ftdi_set_mpsse_pins(port);break;

其他芯片按照之前的方式

default:result = ftdi_set_cbus_pins(port);if (result) {goto fail_setmode;}break;

这里去掉下面那部分代码是因为使用gpiodev这类库时,程序每次控制gpio都会request或free,不去掉会每次控制其他GPIO。

priv->gpio_output = 0x00;
priv->gpio_value = 0x00;

9. ftdi_gpio_free

这个是新增加的,为了处理unexport所有gpio后,需要做一些处理。在ftdi_gpio_init中添加这个函数的赋值

priv->gc.free = ftdi_gpio_free;

该函数将unexport的gpio对应的位清0

static void ftdi_gpio_free(struct gpio_chip *gc, unsigned int offset)
{struct usb_serial_port *port = gpiochip_get_data(gc);struct ftdi_private *priv = usb_get_serial_port_data(port);priv->gpio_used &= ~(BIT(offset));
}

10. ftdi_gpio_direction_get

这个函数是读取到当前gpio的方向,通过私有数据gpio_output(CBUS模式)或mpsse_gpio_output(MPSSE模式)返回对应的bit就可。

同样区分三种方式,后面所有的gpio控制函数都是这样的结构。

switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4)return !(priv->gpio_output & BIT(gpio));elsereturn !(priv->mpsse_gpio_output & BIT(gpio - 4));case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:return !(priv->mpsse_gpio_output & BIT(gpio));default:return !(priv->gpio_output & BIT(gpio));
}

11. ftdi_gpio_direction_input

这个函数是设置gpio的方向为输入,输入的设定值是0,同样,私有数据gpio_output(CBUS模式)或mpsse_gpio_output(MPSSE模式)设置对应的位即可。

switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4) {priv->gpio_output &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);} else {priv->mpsse_gpio_output &= ~BIT(gpio - 4);result = ftdi_set_mpsse_pins(port);if(result >= 0)result = 0;}break;	case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:priv->mpsse_gpio_output &= ~BIT(gpio);result = ftdi_set_mpsse_pins(port);if(result >= 0)result = 0;break;default:priv->gpio_output &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);break;
}

12. ftdi_gpio_direction_output

这个函数是设置GPIO的方向为输出且输出电平。

switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4) {priv->gpio_output &= ~BIT(gpio);priv->gpio_output |= BIT(gpio);if (value)priv->gpio_value |= BIT(gpio);elsepriv->gpio_value &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);} else {priv->mpsse_gpio_output &= ~BIT(gpio - 4);priv->mpsse_gpio_output |= BIT(gpio - 4);if (value)priv->mpsse_gpio_value |= BIT(gpio - 4);elsepriv->mpsse_gpio_value &= ~BIT(gpio - 4);result = ftdi_set_mpsse_pins(port);if(result >= 0)result = 0;}break;	case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:priv->mpsse_gpio_output &= ~BIT(gpio);priv->mpsse_gpio_output |= BIT(gpio);if (value)priv->mpsse_gpio_value |= BIT(gpio);elsepriv->mpsse_gpio_value &= ~BIT(gpio);result = ftdi_set_mpsse_pins(port);if(result >= 0)result = 0;break;default:priv->gpio_output |= BIT(gpio);if (value)priv->gpio_value |= BIT(gpio);elsepriv->gpio_value &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);break;
}

13. ftdi_gpio_init_valid_mask

这个函数用于初始化芯片的合法掩码。

unsigned long map = priv->gpio_altfunc;
switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:map = (priv->gpio_altfunc & 0x0f) | (priv->mpsse_gpio_altfunc << 4);break;	case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:map = priv->mpsse_gpio_altfunc;break;default:break;
}

如果altfunc对应的位为1,在系统里面这个GPIO就是被占用的。 

14. ftdi_gpio_get

这个函数是读取对应gpio的电平值,即sysfs中value文件的值。同样,分3种情况读取gpio即可。

switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4){result = ftdi_read_cbus_pins(port);} else {mutex_lock(&priv->gpio_lock);result = ftdi_get_mpsse_pins(port);mutex_unlock(&priv->gpio_lock);result <<= 4;}break;case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:mutex_lock(&priv->gpio_lock);result = ftdi_get_mpsse_pins(port);mutex_unlock(&priv->gpio_lock);break;default:result = ftdi_read_cbus_pins(port);break;
}

15.  ftdi_gpio_set

对应ftdi_gpio_get,设置某个gpio的电平。

switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:if(gpio < 4) {if (value)priv->gpio_value |= BIT(gpio);elsepriv->gpio_value &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);} else {if (value)priv->mpsse_gpio_value |= BIT(gpio - 4);elsepriv->mpsse_gpio_value &= ~BIT(gpio - 4);result = ftdi_set_mpsse_pins(port);}break;	case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:if (value)priv->mpsse_gpio_value |= BIT(gpio);elsepriv->mpsse_gpio_value &= ~BIT(gpio);result = ftdi_set_mpsse_pins(port);break;default:if (value)priv->gpio_value |= BIT(gpio);elsepriv->gpio_value &= ~BIT(gpio);result = ftdi_set_cbus_pins(port);break;
}

15. ftdi_gpio_get_multiple

功能类似ftdi_gpio_set,只是获取多个gpio。

switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:int mpsse = 0;result = 0;if(priv->bitmode != FTDI_SIO_BITMODE_MPSSE) {result = ftdi_read_cbus_pins(port);}else {mutex_lock(&priv->gpio_lock);mpsse = ftdi_get_mpsse_pins(port);mutex_unlock(&priv->gpio_lock);}result |= mpsse << 4;break;case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:mutex_lock(&priv->gpio_lock);result = ftdi_get_mpsse_pins(port);mutex_unlock(&priv->gpio_lock);break;default:result = ftdi_read_cbus_pins(port);break;
}

16. ftdi_gpio_set_multiple

功能类似ftdi_gpio_set,只是设置多个gpio。

switch(priv->chip_type) {case FT232H:case FT232HP:case FT233HP:unsigned long mask_mpsse = ((*mask) >> 4) & 0xffff;unsigned long bits_mpsse = ((*bits) >> 4) & 0xffff;if(priv->bitmode == FTDI_SIO_BITMODE_MPSSE) {priv->mpsse_gpio_value &= ~mask_mpsse;priv->mpsse_gpio_value |= bits_mpsse & mask_mpsse;ftdi_set_mpsse_pins(port);} else {priv->gpio_value &= ~mask_cbus;priv->gpio_value |= bits_cbus & mask_cbus;ftdi_set_cbus_pins(port);}break;	case FT2232H:case FT2232HP:case FT2233HP:case FT4232H:case FT4232HP:case FT4233HP:case FT4232HA:priv->mpsse_gpio_value &= ~(*mask);priv->mpsse_gpio_value |= *bits & *mask;ftdi_set_mpsse_pins(port);break;default:priv->gpio_value &= ~(*mask);priv->gpio_value |= *bits & *mask;ftdi_set_cbus_pins(port);break;
}

17. 验证

假设控制FT232H的ACBUS7,编号为base + 11,即前面4个为CBUS,所以MPSSE的AC7(第8个gpio)对应11。因为base = 512,所以该GPIO的编号为523。注意,在Ubuntu中要想操作gpio,需要root下执行命令。

:/sys/class/gpio# echo 523 > /sys/class/gpio/export
:/sys/class/gpio# ls
export  gpio523  gpiochip512  unexport
:/sys/class/gpio# echo "out" >/sys/class/gpio/gpio523/direction
:/sys/class/gpio# echo 1 > gpio523/value
:/sys/class/gpio# echo 0 > gpio523/value
:/sys/class/gpio# cat gpio523/value
0
:/sys/class/gpio# gpioinfo 0
gpiochip0 - 20 lines:line   0:      unnamed       kernel   input  active-high [used]line   1:      unnamed       kernel   input  active-high [used]line   2:      unnamed       kernel   input  active-high [used]line   3:      unnamed       kernel   input  active-high [used]line   4:      unnamed       unused   input  active-high line   5:      unnamed       unused   input  active-high line   6:      unnamed       unused   input  active-high line   7:      unnamed       unused   input  active-high line   8:      unnamed       unused   input  active-high line   9:      unnamed       unused   input  active-high line  10:      unnamed       unused   input  active-high line  11:      unnamed       unused   input  active-high line  12:      unnamed       unused   input  active-high line  13:      unnamed       unused   input  active-high line  14:      unnamed       unused   input  active-high line  15:      unnamed       unused   input  active-high line  16:      unnamed       unused   input  active-high line  17:      unnamed       unused   input  active-high line  18:      unnamed       unused   input  active-high line  19:      unnamed       unused   input  active-high

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词