Linux驱动开发19之spi驱动模型的数据结构

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangdapao12138/article/details/82391475

1.SPI传输模式(各定义在include/linux/spi.h

先看一下这些和时序有关的:

00075: #define SPI_CPHA 0x01 /* clock phase */   //时钟相位,数据采样时刻对应着的SCLK是第一个(0)还是第二个边沿(1)
00076: #define SPI_CPOL 0x02 /* clock polarity */ //时钟极性,SCLK空闲时候的电压,是0还是1,

CPHA和CPOL组合可以决定是上升沿还是下降沿采样,还有空闲时刻的SCLK电平状态是怎样的。这两个组合导致有四种模式状态。

http://hi.csdn.net/attachment/201110/31/0_13200785146Qe2.gif

http://hi.csdn.net/attachment/201111/1/0_1320107447kAEg.gif
00077: #define SPI_MODE_0 (0|0) /* (original MicroWire) */
00078: #define SPI_MODE_1 (0|SPI_CPHA)
00079: #define SPI_MODE_2 (SPI_CPOL|0)
00080: #define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
00081: #define SPI_CS_HIGH 0x04   /* chipselect active high? */     //片选高电平
00082: #define SPI_LSB_FIRST 0x08      /* per-word bits-on-wire */ //LSB先行
00083: #define SPI_3WIRE 0x10 /* SI/SO signals shared *///3线模式 SI和SO同一根线 
00084: #define SPI_LOOP 0x20        /* loopback mode *///回送模式
00085: #define SPI_NO_CS 0x40       /* 1 dev/bus, no chipselect */    //单个设备占用一根SPI总线,所以没片选
00086: #define SPI_READY 0x80      //从机拉低电平停止数据传输


2.SPI的设备参数spi_device结构体

我们知道一个spi设备势必对应一个spi_device结构体。 这个结构体包含了我们需要结构体的所有的参数。

00069: struct spi_device {
00070: struct device dev;      //从设备文件,包含了硬件信息
00071: struct spi_master *master;       //spi主机
00072: u32 max_speed_hz;          //最大速率
00073: u8 chip_select;                 //片选
00074: u8 mode;                  //模式
00086: /* slave pulls low to pause */
00087: u8 bits_per_word;           //一个字有多少位
00088: int irq;                            //中断号
00089: void *controller_state;            //控制器状态
00090: void *controller_data;            //控制器数据
00091: char modalias[SPI_NAME_SIZE];//名字
00102: } ? end spi_device ? ;

3.SPI设备添加spi_new_device

00330: struct spi_device *spi_new_device(struct spi_master *master, struct spi_board_info *chip)
00332: {
00333: struct spi_device *proxy;
00334: int statu;
00336:
/* NOTE: caller did any chip->bus_num checks necessary. *
00338: * Also, unless we change the return value convention to use
00339: * error-or-pointer (not NULL-or-pointer), troubleshootability
00340: * suggests syslogged diagnostics are best here (ugh). */
00343: proxy = spi_alloc_device(master);   //3.1 spi设备初始化
00344: if (!proxy) return NULL;
00347: WARN_ON(strlen(chip->modalias) >= sizeof(proxy->modalias));

00349: proxy->chip_select = chip->chip_select;  //片选
00350: proxy->max_speed_hz = chip->max_speed_hz;      //最大速率
00351: proxy->mode = chip->mode;    //模式
00352: proxy->irq = chip->irq;           //中断号
00353: strlcpy(proxy->modalias, chip->modalias, sizeof(proxy->modalias));
00354: proxy->dev.platform_data = (void *) chip->platform_data;
00355: proxy->controller_data = chip->controller_data;
00356: proxy->controller_state = NULL;
00358: status = spi_add_device(proxy);      //3.2 添加spi设备
00359: if (status < 0) {
00360: spi_dev_put(proxy); //增加spi设备的引用计数
00361: return NULL;
00362: }
00364: return proxy;
00365: } ? end spi_new_device ?

3.1分配SPI设备

00223: struct spi_device *spi_alloc_device(struct spi_master *master)
00224: {
00225: struct spi_device *spi;
00226: struct device *dev = master->dev.parent;
00228: if (!spi_master_get(master))    //判断spi主机是否存在
00229: return NULL;
00231: spi = kzalloc(sizeof *spi, GFP_KERNEL); //分配内存
00232: if (!spi) {
00233: dev_err(dev, "cannot alloc spi_device\n");
00234: spi_master_put(master); //增加主机引用计数
00235: return NULL;
00236: }
00238: spi->master = master; //设置spi主机
00239: spi->dev.parent = dev; //spi设备文件的父设备为spi主机设备文件的父设备
00240: spi->dev.bus = &spi_bus_type; //总线类型
00241: spi->dev.release = spidev_release;   //释放方法
00242: device_initialize(&spi->dev);          //设备初始化
00243: return spi;
00244: } ? end spi_alloc_device ?

3.2 添加spi设备

00256: int spi_add_device(struct spi_device *spi)
00257: {
00258: static DEFINE_MUTEX(spi_add_lock);
00259: struct device *dev = spi->master->dev.parent;
00260: struct device *d;
00261: int status;
00263: /* Chipselects are numbered 0..max; validate. */
00264: if (spi->chip_select >= spi->master->num_chipselect) {
00265: dev_err(dev, "cs%d >= max %d\n",spi->chip_select, spi->master->num_chipselect);
00268: return -EINVAL;
00269: }
00271: /* Set the bus ID string */
00272: dev_set_name(&spi->dev, "%s.%u", dev_name(&spi->master->dev), spi->chip_select);
00276: /* We need to make sure there's no other device with this
00277: * chipselect **BEFORE** we call setup(), else we'll trash
00278: * its configuration. Lock against concurrent add() calls.
00279: */
00280: mutex_lock(&spi_add_lock);
00282: d = bus_find_device_by_name(&spi_bus_type, NULL,dev_name(&spi->dev));         //查找总线上的spi设备
00283: if (d != NULL) {              //判断是否已经在使用了
00284: dev_err(dev, "chipselect %d already in use\n",spi->chip_select);
00286: put_device(d);
00287: status = -EBUSY;
00288: goto ¯done;
00289: }
00291: /* Drivers may modify this initial i/o setup, but will
00292: * normally rely on the device being setup. Devices
00293: * using SPI_CS_HIGH can't coexist well otherwise...
00294: */
00295: status = spi_setup(spi);     //3.2.1调用spi主机 setup方法
00296: if (status < 0) {
00297: dev_err(dev, "can't %s %s, status %d\n", "setup", dev_name(&spi->dev), status);
00299: goto ¯done;
00300: }
00302: /*Device may be bound to an active driver when this returns */
00303: status = device_add(&spi->dev);            //3.2.2添加设备
00304: if (status < 0)
00305: dev_err(dev, "can't %s %s, status %d\n","add", dev_name(&spi->dev), status);
00307: else
00308: dev_dbg(dev, "registered child %s\n", dev_name (&spi->dev));
00310: done:
00311: mutex_unlock(&spi_add_lock);
00312: return status;
00313: } ? end spi_add_device ?

3.2.1 spi setup方法

00635: int spi_setup(struct spi_device *spi)
00636: {
00637: unsigned bad_bits;
00638: int status;
00639:
00640:
/* help drivers fail *cleanly* when they need options
00641: * that aren't supported with their current master
00642: */
00643: bad_bits = spi->mode & ~spi->master->mode_bits; //比较spi设备的模式和spi主机支持的模式
00644: if (bad_bits) {           //存在不支持的模式
00645: dev_dbg(&spi->dev, "setup: unsupported mode bits %x\n",bad_bits);
00647: return -EINVAL;
00648: }
00649:
00650:
if (!spi->bits_per_word)    ////若没设置设备的每个字含多少位
00651: spi->bits_per_word = 8;    //则默认设置为8
00652:
00653:
status = spi->master->setup(spi);   //调用spi主机的setup方法
00654:
00655:
dev_dbg(&spi->dev, "setup mode %d, %s%s%s%s""%u bits/w, %u Hz max --> %d\n",
00657: (int) (spi->mode & (SPI_CPOL | SPI_CPHA)),(spi->mode & SPI_CS_HIGH) ? "cs_high, " :"",
00659: (spi->mode & SPI_LSB_FIRST) ? "lsb, " : "",(spi->mode & SPI_3WIRE) ? "3wire, " : "",
00661: (spi->mode & SPI_LOOP) ? "loopback, " : "",spi->bits_per_word, spi->max_speed_hz,status);
00665: return status;
00666: } ? end spi_setup ?

4.spi板级设备

4.1板级设备结构体

00700: struct spi_board_info {
00708: char modalias[SPI_NAME_SIZE]; //名字
00709: const void *platform_data; //平台数据
00710: void *controller_data; //控制器数据
00711: int irq; //中断号
00714: u32 max_speed_hz; //最大速率
00723: u16 bus_num; //spi总线编号
00724: u16 chip_select; //片选
00729: u8 mode; //模式
00736: } ? end spi_board_info ?

我们看到这个结构体和spi_device结构体的参数是差不多的。

4.2 板级设备注册(静态注册,一般在板级初始化函数中调用)

00388: spi_register_board_info(struct spi_board_info const *info, unsigned n)
00389: {
00390: struct boardinfo *bi;
00392: bi = kmalloc(sizeof(*bi) + n * sizeof *info, GFP_KERNEL); //分配内存
00393: if (!bi)
00394: return -ENOMEM;
00395: bi->n_board_info = n;
00396: memcpy(bi->board_info, info, n * sizeof *info); //设置bi的板级信息
00398: mutex_lock(&board_lock);
00399: list_add_tail(&bi->list, &board_list); //添加bi->list到全局board_list链表
00400: mutex_unlock(&board_lock);
00401: return 0;
00402: }

5.spi设备驱动

spi _device必有spi_driver与之对应!

5.1 spi设备驱动结构体

00175: struct spi_driver {
00176: const struct spi_device_id *id_table; //spi设备id
00177: int (*probe)(struct spi_device *spi); //probe方法(探测到设备)
00178: int (*remove)(struct spi_device *spi); //remove方法(设备移除)
00179: void (*shutdown)(struct spi_device *spi); //shutdown方法(关闭设备)
00180: int (*suspend)(struct spi_device *spi, pm_message_t mesg); //suspend方法(挂起设备)
00181: int (*resume)(struct spi_device *spi); //resume方法(唤醒设备)
00182: struct device_driver driver; //设备驱动文件
00183: };

5.2 spi设备驱动注册

00176: int spi_register_driver(struct spi_driver *sdrv)
00177: {
00178: sdrv->driver.bus = &spi_bus_type; //总线类型      
00179: if (sdrv->probe) //若存在probe方法
00180: sdrv->driver.probe = spi_drv_probe; //设置其设备驱动文件的probe方法为spi_drv_probe
00181: if (sdrv->remove) //若存在remove方法
00182: sdrv->driver.remove = spi_drv_remove; //设置其设备驱动文件的remove方法为spi_drv_remove
00183: if (sdrv->shutdown) //若存在shutdown方法
00184: sdrv->driver.shutdown = spi_drv_shutdown; //设置其设备驱动文件的shutdown方法为spi_drv_shutdown
00185: return driver_register(&sdrv->driver); //注册设备驱动
00186: }

这里的probe方法会在设备与驱动匹配的时候给调用,参看really_probe函数的部分代码:

  1. if (dev->bus->probe) {        //若总线有probe方法(spi子系统的没有) 
  2.     ret = dev->bus->probe(dev);   //则调用总线的probe方法 
  3.     if (ret) 
  4.         goto probe_failed; 
  5. }  
  6. else if (drv->probe) {   //若存在设备驱动的probe方法 
  7.     ret = drv->probe(dev);       //则调用设备驱动的probe方法 
  8.     if (ret) 
  9.         goto probe_failed; 

5.3 sp_drv_probe

00150: static int spi_drv_probe(struct device *dev)
00151: {
00152: const struct spi_driver *sdrv = to_spi_driver(dev->driver); //根据设备文件的设备驱动找到spi设备驱动
00154: return sdrv->probe(to_spi_device(dev)); //调用spi设备驱动的probe方法
00155: }

6.spi主机

6.1 spi主机结构体

00235: struct spi_master {
00236: struct device dev;      //spi主机设备文件
00244: s16 bus_num;           //spi总线号
00249: u16 num_chipselect; //片选号
00254: u16 dma_alignment; //dma算法
00257: u16 mode_bits;         //模式位
00260: u16 flags;          //传输类型标志
00271: int (*setup)(struct spi_device *spi); //setup方法
00292: int (*transfer)(struct spi_device *spi, struct spi_message *mesg); //传输方法
00295: /*called on release() to free memory provided by spi_master */
00296: void (*cleanup)(struct spi_device *spi) //cleanup方法
00296: ;
00297: } ? end spi_master ?

6.2 flags标志

00261: #define SPI_MASTER_HALF_DUPLEX BIT(0)        //半双工
00261: /* can't do full duplex */
00262: #define SPI_MASTER_NO_RX BIT(1)               //不读
00262: /* can't do buffer read */
00263: #define SPI_MASTER_NO_TX BIT(2)               //不写

6.3 spi主机初始化

00465: struct spi_master *spi_alloc_master(struct device*dev, unsigned size)
00466: {
00467: struct spi_master *master;
00469: if (!dev)
00470: return NULL;
00472: master = kzalloc(size + sizeof *master, GFP_KERNEL); //分配内存
00473: if (!master)
00474: return NULL;
00476: device_initialize(&master->dev);     //初始化主机设备文件
00477: master->dev.class = &spi_master_class;   //指定设备类spi_master_class
00478: master->dev.parent = get_device(dev);    //设置spi主机设备的父设备
00479: spi_master_set_devdata(master, &master[1]); //设置设备数据
00481: return master;
00482: }

6.4注册spi主机

00505: int spi_register_master(struct spi_master *master
00505: )
00506: {
00507: static atomic_t dyn_bus_id = ATOMIC_INIT((1<<15) - 1);
00508: struct device *dev = master->dev.parent; //获得spi主机设备的父设备
00509: int status = -ENODEV;
00510: int dynamic = 0;
00512: if (!dev)
00513: return -ENODEV;
00518: if (master->num_chipselect == 0) //判断片选个数
00519: return -EINVAL;
00522: if (master->bus_num < 0) {//验证spi总线编号
00526: master->bus_num = atomic_dec_return(&dyn_bus_id);
00527: dynamic = 1;
00528: }
00533: dev_set_name(&master->dev, "spi%u", master->bus_num); //设置spi主机设备名
00534: status = device_add(&master->dev); //添加spi主机设备
00535: if (status < 0)
00536: goto ¯done;
00537: dev_dbg(dev, "registered master %s%s\n", dev_name(&master->dev), dynamic ? " (dynamic)" : "");
00540: /* populate children from any spi device tables */
00541: scan_boardinfo(master);遍历spi主机链表,添加进全局spi_master_list链表,遍历全局board_list查找bi结构体,找到匹配的板级spi设备
00542: status = 0;
00543: done:
00544: return status;
00545: } ? end spi_register_master

6.5 匹配spi主机和板级spi设备

  1. static void spi_match_master_to_boardinfo(struct spi_master *master,
  2. struct spi_board_info *bi) 
  3.     struct spi_device *dev; 
  4.     if (master->bus_num != bi->bus_num)   //判断是否所属的spi总线 
  5.         return
  6.     dev = spi_new_device(master, bi);   //添加新的spi设备 
  7.     if (!dev) 
  8.         dev_err(master->dev.parent, "can't create new device for %s\n",bi->modalias); 

在注册板级设备或主机设备的时候都会添加spi板级设备添加进board_list链表,spi主机设备添加进spi_master_list链表。不管是先注册spi板级设备还是先注册spi主机设备都会调用list_for_each_entry遍历对应的要匹配的设备的链表,查找是否有匹配的例子若找到都会调用spi_match_master_to_boardinfo函数添加spi设备

 

6.6 spi主机设备类

00439: static struct class spi_master_class = {
00440: .name = "spi_master",
00441: .owner = THIS_MODULE,
00442: .dev_release = spi_master_release,
00443: };

7.spi总线

7.1 spi总线结构体

00139: struct bus_type spi_bus_type = {
00140: .name = "spi",
00141: .dev_attrs = spi_dev_attrs,
00142: .match = spi_match_device, //匹配方法
00143: .uevent = spi_uevent,
00144: .suspend = spi_suspend,
00145: .resume = spi_resume,
00146: };

7.2设备匹配方法spi_match_device

前面的匹配方法是spi板级设备与spi主机设备的匹配方法,匹配的结果是添加新spi设备spi_new_device

这里的匹配是spi设备和spi驱动的匹配,匹配的结果是会调用spi驱动的设备驱动文件probe方法,spi_drv_probe

00083: static int spi_match_device(struct device *dev, struct device_driver *drv)
00084: {
00085: const struct spi_device *spi = to_spi_device(dev);
00086: const struct spi_driver *sdrv = to_spi_driver(drv) ;
00087:
00088:
if (sdrv->id_table) //spi设备驱动存在支持id
00089: return !!spi_match_id(sdrv->id_table, spi); //spi设备驱动表的匹配
00090:
00091:
return strcmp(spi->modalias, drv->name) == 0; //比较spi设备的名字和spi设备驱动的名字
00092: }

I2C一样,使用的是两种方式,一种是id_table,另一种为设备和驱动名称。

7.3 spi_match_id

00064: static const struct spi_device_id *spi_match_id(const struct spi_device_id *id, const struct spi_device *sdev)
00066: {
00067: while (id->name[0]) {//id表的成员的名字域不为空
00068: if (!strcmp(sdev->modalias, id->name)) //则判断其名字是否与spi设备的名字一样
00069: return id; //一样则返回该id
00070: id++;//id表指针++,指向下一个id
00071: }
00072: return NULL;
00073: }

8.spi消息与传输

8.1 spi消息结构体

00466: struct spi_message {
00467: struct list_head transfers; //spi传输事务链表头
00469: struct spi_device *spi; //所属spi设备
00471: unsigned is_dma_mapped:1;
00485: void (*complete)(void *context);
00486: void *context;
00487: unsigned actual_length;
00488: int status; //传输状态
00494: struct list_head queue;
00495: void *state;
00496: } ? end spi_message ?

8.2 初始化spi消息

00498: static inline void spi_message_init(struct
00498: spi_message *m)
00499: {
00500: memset(m, 0, sizeof *m);
00501: INIT_LIST_HEAD(&m->transfers); //初始化spi消息的事务链表头
00502: }

8.3 添加传输事物到spi传输链表

00505: spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
00506: {
00507: list_add_tail(&t->transfer_list, &m->transfers);
00508: }

8.4 spi传输结构体

00417: struct spi_transfer {
00423: const void *tx_buf; //发送缓冲区指针
00424: void *rx_buf; //接收缓冲区指针
00425: unsigned len; //消息长度
00427: dma_addr_t tx_dma; //DMA发送地址
00428: dma_addr_t rx_dma; //DMA接收地址
00430: unsigned cs_change:1;
00431: u8 bits_per_word; //一个字多少位
00432: u16 delay_usecs; //毫秒级延时
00433: u32 speed_hz; //速率
00435: struct list_head transfer_list; //传输链表头
00436: } ? end spi_transfer ? ;

9.spi子系统的初始化

00860: static int __init spi_init(void)
00861: {
00862: int status;
00863:
00864:
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); //分配数据收发缓冲区
00865: if (!buf) {
00866: status = -ENOMEM;
00867: goto ¯err0;
00868: }
00870: status = bus_register(&spi_bus_type); //注册spi总线
00871: if (status < 0)
00872: goto ¯err1;
00874: status = class_register(&spi_master_class); //注册spi主机类 "/sys/class/spi_master"
00875: if (status < 0)
00876: goto ¯err2;
00877: return 0;
00879: err2:
00880: bus_unregister(&spi_bus_type);
00881: err1:
00882: kfree(buf);
00883: buf = NULL;
00884: err0:
00885: return status;
00886: } ? end spi_init ?

00895: postcore_initcall(spi_init);

//入口声明 #define postcore_initcall(fn)    __define_initcall("2",fn,2)

10.spi子系统的API

10.1 read

00589: static inline int spi_read(struct spi_device *spi, u8 *buf, size_t len)
00591: {
00592: struct spi_transfer t = {
00593: .rx_buf = buf,
00594: .len = len,
00595: };
00596: struct spi_message m;
00598: spi_message_init(&m); //spi消息初始化(初始化传输事务链表头)
00599: spi_message_add_tail(&t, &m); //添加spi传输到spi消息传输链表
00600: return spi_sync(spi, &m); //spi同步传输
00601: }

10.2 write

00565: static inline int spi_write(struct spi_device *spi, const u8 *buf, size_t len)
00567: {
00568: struct spi_transfer t = {
00569: .tx_buf = buf,
00570: .len = len,
00571: };
00572: struct spi_message m;
00573:
00574:
spi_message_init(&m); //spi消息初始化(初始化传输事务链表头)
00575: spi_message_add_tail(&t, &m); //添加spi传输到spi消息传输链表
00576: return spi_sync(spi, &m); //spi同步传输
00577: }

spi的读写操作都是初始化一个spi_transfer传输结构体,并将其添加进spi消息传输事务链表中

然后通过spi_sync来同步读写操作,接着看下spi_sync的具体代码

spi_sync(spi, &m)

       status = spi_async(spi, message);

00698: int spi_async(struct spi_device *spi, struct spi_message *message)
00699: {
00700: struct spi_master *master = spi->master;
00707: if ((master->flags & SPI_MASTER_HALF_DUPLEX)|| (spi->mode & SPI_3WIRE)) { //主机为半双工或spi设备为3线设备
00709: struct spi_transfer *xfer;
00710: unsigned flags = master->flags;
00712: list_for_each_entry(xfer, &message->transfers,transfer_list) { //遍历spi消息的传输事务链表
00713: if (xfer->rx_buf && xfer->tx_buf) //判断接收或发送缓冲区是否为空
00714: return -EINVAL;
00715: if ((flags & SPI_MASTER_NO_TX) && xfer->tx_buf) //检验无spi数据发送的情况
00716: return -EINVAL;
00717: if ((flags & SPI_MASTER_NO_RX) && xfer->rx_buf) //检验无spi数据接收的情况
00718: return -EINVAL;
00719: }
00720: }
00722: message->spi = spi; //设置spi消息所属的spi设备
00723: message->status = -EINPROGRESS; //设置spi消息的传输状态
00724: return master->transfer(spi, message); //调用spi主机的transfer方法,收发spi信息给spi设备
00725: } ? end spi_async ?

10.3 write_then_read

00803: int spi_write_then_read(struct spi_device *spi,const u8 *txbuf, unsigned n_tx,u8 *rxbuf, unsigned n_rx)
00806: {
00807: static DEFINE_MUTEX(lock);
00809: int status;
00810: struct spi_message message;
00811: struct spi_transfer x[2]; //声明两个spi传输结构体
00812: u8 *local_buf;
00818: if ((n_tx + n_rx) > SPI_BUFSIZ) //验证发送和接收的数据总和是否溢出
00819: return -EINVAL;
00821: spi_message_init(&message); //spi消息初始化(初始化传输事务链表头)
00822: memset(x, 0, sizeof x);
00823: if (n_tx) {
00824: x[0].len = n_tx;
00825: spi_message_add_tail(&x[0], &message); //添加spi传输到spi消息传输链表
00826: }
00827: if (n_rx) {
00828: x[1].len = n_rx;
00829: spi_message_add_tail(&x[1], &message); //添加spi传输到spi消息传输链表
00830: }
00833: if (!mutex_trylock(&lock)) { //尝试上锁 失败
00834: local_buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); //则分配local_buf内存
00835: if (!local_buf)
00836: return -ENOMEM;
00837: } else
00838: local_buf = buf; //指向默认分配好内存的缓冲区(spi_init中初始化的)
00840: memcpy(local_buf, txbuf, n_tx); //发送缓冲区的内容复制到local_buf
00841: x[0].tx_buf = local_buf; //发送的spi传输结构体
00842: x[1].rx_buf = local_buf + n_tx; //接收的spi传输结构体
00845: status = spi_sync(spi, &message); //spi同步传输--发送数据
00846: if (status == 0)
00847: memcpy(rxbuf, x[1].rx_buf, n_rx); //接收返回的数据复制到rxbuf
00849: if (x[0].tx_buf == buf)
00850: mutex_unlock(&lock); //解锁
00851: else
00852: kfree(local_buf); //释放内存
00854: return status;
00855: } ? end spi_write_then_read ?

猜你喜欢

转载自blog.csdn.net/wangdapao12138/article/details/82391475