转 Linux设备驱动剖析之IIC(三)

原文:https://www.cnblogs.com/lknlfy/p/3265122.html

下面以eeprom用户程序调用ioctl函数的写操作为例追踪IIC子系统的调用过程。eeprom的用户测试是大部分开发板都自带的。看写一个字节数据的eeprom_write_byte函数的定义:

复制代码

int eeprom_write_byte(struct eeprom *e, __u16 mem_addr, __u8 data)
{
    if(e->type == EEPROM_TYPE_8BIT_ADDR) {
        __u8 buf[2] = { mem_addr & 0x00ff, data };
        return i2c_write_2b(e, buf);
    } else if(e->type == EEPROM_TYPE_16BIT_ADDR) {
        __u8 buf[3] = 
            { (mem_addr >> 8) & 0x00ff, mem_addr & 0x00ff, data };
        return i2c_write_3b(e, buf);
    } 
    fprintf(stderr, "ERR: unknown eeprom type\n");
    return -1;
}

复制代码

这里使用的是8位地址,因此调用的是i2c_write_2b函数,为什么是2b?这是eeprom规定的,写数据之前要先写地址。注意buf[0]=要写的地址,buf[1]=要写的字节数据。下面是i2c_write_2b函数的定义:

复制代码

static int i2c_write_2b(struct eeprom *e, __u8 buf[2])
{
    int r;
    // we must simulate a plain I2C byte write with SMBus functions
    r = i2c_smbus_write_byte_data(e->fd, buf[0], buf[1]);
    if(r < 0)
        fprintf(stderr, "Error i2c_write_2b: %s\n", strerror(errno));
    usleep(10);
    return r;
}

复制代码

就调用了i2c_smbus_write_byte_data函数,注意参数的含义,下面是它的定义:

复制代码

static inline __s32 i2c_smbus_write_byte_data(int file, __u8 command, 
                                              __u8 value)
{
    union i2c_smbus_data data;
    data.byte = value;
    return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
                            I2C_SMBUS_BYTE_DATA, &data);
}

复制代码

调用了i2c_smbus_access函数,继续追踪,看i2c_smbus_access函数的定义:

复制代码

static inline __s32 i2c_smbus_access(int file, char read_write, __u8 command, 
                                     int size, union i2c_smbus_data *data)
{
    struct i2c_smbus_ioctl_data args;

    args.read_write = read_write;
    args.command = command;
    args.size = size;
    args.data = data;
    return ioctl(file,I2C_SMBUS,&args);
}

复制代码

首先弄清楚参数的含义,file是使用open打开的文件。read_write表示是读操作还是写操作,这里是写,所以它的值为I2C_SMBUS_WRITE。command是要写的地址。size的值为I2C_SMBUS_BYTE_DATA。最后一个参数data.byte=要写的字节数据。

     下面开始进入ioctl系统调用,最后会到达i2c-dev.c中的i2cdev_ioctl函数,看它的定义:

复制代码

00000396 static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
00000397 {
00000398     struct i2c_client *client = file->private_data;
00000399     unsigned long funcs;
00000400 
00000401     dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
00000402         cmd, arg);
00000403 
00000404     switch (cmd) {
00000405     case I2C_SLAVE:
00000406     case I2C_SLAVE_FORCE:
00000407         /* NOTE:  devices set up to work with "new style" drivers
00000408          * can't use I2C_SLAVE, even when the device node is not
00000409          * bound to a driver.  Only I2C_SLAVE_FORCE will work.
00000410          *
00000411          * Setting the PEC flag here won't affect kernel drivers,
00000412          * which will be using the i2c_client node registered with
00000413          * the driver model core.  Likewise, when that client has
00000414          * the PEC flag already set, the i2c-dev driver won't see
00000415          * (or use) this setting.
00000416          */
00000417         if ((arg > 0x3ff) ||
00000418             (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
00000419             return -EINVAL;
00000420         if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
00000421             return -EBUSY;
00000422         /* REVISIT: address could become busy later */
00000423         client->addr = arg;
00000424         return 0;
00000425     case I2C_TENBIT:
00000426         if (arg)
00000427             client->flags |= I2C_M_TEN;
00000428         else
00000429             client->flags &= ~I2C_M_TEN;
00000430         return 0;
00000431     case I2C_PEC:
00000432         if (arg)
00000433             client->flags |= I2C_CLIENT_PEC;
00000434         else
00000435             client->flags &= ~I2C_CLIENT_PEC;
00000436         return 0;
00000437     case I2C_FUNCS:
00000438         funcs = i2c_get_functionality(client->adapter);
00000439         return put_user(funcs, (unsigned long __user *)arg);
00000440 
00000441     case I2C_RDWR:
00000442         return i2cdev_ioctl_rdrw(client, arg);
00000443 
00000444     case I2C_SMBUS:
00000445         return i2cdev_ioctl_smbus(client, arg);
00000446 
00000447     case I2C_RETRIES:
00000448         client->adapter->retries = arg;
00000449         break;
00000450     case I2C_TIMEOUT:
00000451         /* For historical reasons, user-space sets the timeout
00000452          * value in units of 10 ms.
00000453          */
00000454         client->adapter->timeout = msecs_to_jiffies(arg * 10);
00000455         break;
00000456     default:
00000457         /* NOTE:  returning a fault code here could cause trouble
00000458          * in buggy userspace code.  Some old kernel bugs returned
00000459          * zero in this case, and userspace code might accidentally
00000460          * have depended on that bug.
00000461          */
00000462         return -ENOTTY;
00000463     }
00000464     return 0;
00000465 }

复制代码

比较简单,根据不同的cmd执行不同的分支。由于ioctl传下来的cmd是I2C_SMBUS,因此直接看444、445行,调用i2cdev_ioctl_smbus函数:

复制代码

00000311 static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
00000312         unsigned long arg)
00000313 {
00000314     struct i2c_smbus_ioctl_data data_arg;
00000315     union i2c_smbus_data temp;
00000316     int datasize, res;
00000317 
00000318     if (copy_from_user(&data_arg,
00000319                (struct i2c_smbus_ioctl_data __user *) arg,
00000320                sizeof(struct i2c_smbus_ioctl_data)))
00000321         return -EFAULT;
00000322     if ((data_arg.size != I2C_SMBUS_BYTE) &&
00000323         (data_arg.size != I2C_SMBUS_QUICK) &&
00000324         (data_arg.size != I2C_SMBUS_BYTE_DATA) &&
00000325         (data_arg.size != I2C_SMBUS_WORD_DATA) &&
00000326         (data_arg.size != I2C_SMBUS_PROC_CALL) &&
00000327         (data_arg.size != I2C_SMBUS_BLOCK_DATA) &&
00000328         (data_arg.size != I2C_SMBUS_I2C_BLOCK_BROKEN) &&
00000329         (data_arg.size != I2C_SMBUS_I2C_BLOCK_DATA) &&
00000330         (data_arg.size != I2C_SMBUS_BLOCK_PROC_CALL)) {
00000331         dev_dbg(&client->adapter->dev,
00000332             "size out of range (%x) in ioctl I2C_SMBUS.\n",
00000333             data_arg.size);
00000334         return -EINVAL;
00000335     }
00000336     /* Note that I2C_SMBUS_READ and I2C_SMBUS_WRITE are 0 and 1,
00000337        so the check is valid if size==I2C_SMBUS_QUICK too. */
00000338     if ((data_arg.read_write != I2C_SMBUS_READ) &&
00000339         (data_arg.read_write != I2C_SMBUS_WRITE)) {
00000340         dev_dbg(&client->adapter->dev,
00000341             "read_write out of range (%x) in ioctl I2C_SMBUS.\n",
00000342             data_arg.read_write);
00000343         return -EINVAL;
00000344     }
00000345 
00000346     /* Note that command values are always valid! */
00000347 
00000348     if ((data_arg.size == I2C_SMBUS_QUICK) ||
00000349         ((data_arg.size == I2C_SMBUS_BYTE) &&
00000350         (data_arg.read_write == I2C_SMBUS_WRITE)))
00000351         /* These are special: we do not use data */
00000352         return i2c_smbus_xfer(client->adapter, client->addr,
00000353                       client->flags, data_arg.read_write,
00000354                       data_arg.command, data_arg.size, NULL);
00000355 
00000356     if (data_arg.data == NULL) {
00000357         dev_dbg(&client->adapter->dev,
00000358             "data is NULL pointer in ioctl I2C_SMBUS.\n");
00000359         return -EINVAL;
00000360     }
00000361 
00000362     if ((data_arg.size == I2C_SMBUS_BYTE_DATA) ||
00000363         (data_arg.size == I2C_SMBUS_BYTE))
00000364         datasize = sizeof(data_arg.data->byte);
00000365     else if ((data_arg.size == I2C_SMBUS_WORD_DATA) ||
00000366          (data_arg.size == I2C_SMBUS_PROC_CALL))
00000367         datasize = sizeof(data_arg.data->word);
00000368     else /* size == smbus block, i2c block, or block proc. call */
00000369         datasize = sizeof(data_arg.data->block);
00000370 
00000371     if ((data_arg.size == I2C_SMBUS_PROC_CALL) ||
00000372         (data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) ||
00000373         (data_arg.size == I2C_SMBUS_I2C_BLOCK_DATA) ||
00000374         (data_arg.read_write == I2C_SMBUS_WRITE)) {
00000375         if (copy_from_user(&temp, data_arg.data, datasize))
00000376             return -EFAULT;
00000377     }
00000378     if (data_arg.size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
00000379         /* Convert old I2C block commands to the new
00000380            convention. This preserves binary compatibility. */
00000381         data_arg.size = I2C_SMBUS_I2C_BLOCK_DATA;
00000382         if (data_arg.read_write == I2C_SMBUS_READ)
00000383             temp.block[0] = I2C_SMBUS_BLOCK_MAX;
00000384     }
00000385     res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
00000386           data_arg.read_write, data_arg.command, data_arg.size, &temp);
00000387     if (!res && ((data_arg.size == I2C_SMBUS_PROC_CALL) ||
00000388              (data_arg.size == I2C_SMBUS_BLOCK_PROC_CALL) ||
00000389              (data_arg.read_write == I2C_SMBUS_READ))) {
00000390         if (copy_to_user(data_arg.data, &temp, datasize))
00000391             return -EFAULT;
00000392     }
00000393     return res;
00000394 }

复制代码

一大堆的if判断。318行,调用copy_from_user函数将用户空间的数据拷贝到内核空间。这样data_arg的内容就与ioctl第三个参数的内容是一样的了。

322至335行,都是判断,一路走来,我们知道data_arg.size的值为I2C_SMBUS_BYTE_DATA,所以这里的if条件不会成立。

338至344行,如果既不是读操作又不是写操作,那肯定不行,返回出错。

348行,由于不满足第一个条件,所以不会执行if里的语句。

356至360行,我们的data_arg.data是不为NULL的,可以继续往下执行。

362行,data_arg.size == I2C_SMBUS_BYTE_DATA这个条件满足,所以执行364行的语句,因此datasize的值为1。

371行,由于满足data_arg.read_write == I2C_SMBUS_WRITE这个条件,所以执行375行语句,将data_arg.data的第一个字节拷贝到temp变量中。

378行,条件不满足,略过。

先看387行,条件不满足,因此就剩下385行的i2c_smbus_xfer函数,下面看它在drivers/i2c/i2c-core.c中的定义:

复制代码

00002066 s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
00002067            char read_write, u8 command, int protocol,
00002068            union i2c_smbus_data *data)
00002069 {
00002070     unsigned long orig_jiffies;
00002071     int try;
00002072     s32 res;
00002073 
00002074     flags &= I2C_M_TEN | I2C_CLIENT_PEC;
00002075 
00002076     if (adapter->algo->smbus_xfer) {
00002077         i2c_lock_adapter(adapter);
00002078 
00002079         /* Retry automatically on arbitration loss */
00002080         orig_jiffies = jiffies;
00002081         for (res = 0, try = 0; try <= adapter->retries; try++) {
00002082             res = adapter->algo->smbus_xfer(adapter, addr, flags,
00002083                             read_write, command,
00002084                             protocol, data);
00002085             if (res != -EAGAIN)
00002086                 break;
00002087             if (time_after(jiffies,
00002088                        orig_jiffies + adapter->timeout))
00002089                 break;
00002090         }
00002091         i2c_unlock_adapter(adapter);
00002092     } else
00002093         res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
00002094                           command, protocol, data);
00002095 
00002096     return res;
00002097 }

复制代码

2076行,对于s3c6410的IIC控制器驱动来说,没有定义smbus_xfer函数,因此执行2093行的i2c_smbus_xfer_emulated函数,它的定义如下:

复制代码

00001889 static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
00001890                    unsigned short flags,
00001891                    char read_write, u8 command, int size,
00001892                    union i2c_smbus_data *data)
00001893 {
00001894     /* So we need to generate a series of msgs. In the case of writing, we
00001895       need to use only one message; when reading, we need two. We initialize
00001896       most things with sane defaults, to keep the code below somewhat
00001897       simpler. */
00001898     unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
00001899     unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
00001900     int num = read_write == I2C_SMBUS_READ ? 2 : 1;
00001901     struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
00001902                               { addr, flags | I2C_M_RD, 0, msgbuf1 }
00001903                             };
00001904     int i;
00001905     u8 partial_pec = 0;
00001906     int status;
00001907 
00001908     msgbuf0[0] = command;
00001909     switch (size) {
00001910     case I2C_SMBUS_QUICK:
00001911         msg[0].len = 0;
00001912         /* Special case: The read/write field is used as data */
00001913         msg[0].flags = flags | (read_write == I2C_SMBUS_READ ?
00001914                     I2C_M_RD : 0);
00001915         num = 1;
00001916         break;
00001917     case I2C_SMBUS_BYTE:
00001918         if (read_write == I2C_SMBUS_READ) {
00001919             /* Special case: only a read! */
00001920             msg[0].flags = I2C_M_RD | flags;
00001921             num = 1;
00001922         }
00001923         break;
00001924     case I2C_SMBUS_BYTE_DATA:
00001925         if (read_write == I2C_SMBUS_READ)
00001926             msg[1].len = 1;
00001927         else {
00001928             msg[0].len = 2;
00001929             msgbuf0[1] = data->byte;
00001930         }
00001931         break;
00001932     case I2C_SMBUS_WORD_DATA:
00001933         if (read_write == I2C_SMBUS_READ)
00001934             msg[1].len = 2;
00001935         else {
00001936             msg[0].len = 3;
00001937             msgbuf0[1] = data->word & 0xff;
00001938             msgbuf0[2] = data->word >> 8;
00001939         }
00001940         break;
00001941     case I2C_SMBUS_PROC_CALL:
00001942         num = 2; /* Special case */
00001943         read_write = I2C_SMBUS_READ;
00001944         msg[0].len = 3;
00001945         msg[1].len = 2;
00001946         msgbuf0[1] = data->word & 0xff;
00001947         msgbuf0[2] = data->word >> 8;
00001948         break;
00001949     case I2C_SMBUS_BLOCK_DATA:
00001950         if (read_write == I2C_SMBUS_READ) {
00001951             msg[1].flags |= I2C_M_RECV_LEN;
00001952             msg[1].len = 1; /* block length will be added by
00001953                        the underlying bus driver */
00001954         } else {
00001955             msg[0].len = data->block[0] + 2;
00001956             if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) {
00001957                 dev_err(&adapter->dev,
00001958                     "Invalid block write size %d\n",
00001959                     data->block[0]);
00001960                 return -EINVAL;
00001961             }
00001962             for (i = 1; i < msg[0].len; i++)
00001963                 msgbuf0[i] = data->block[i-1];
00001964         }
00001965         break;
00001966     case I2C_SMBUS_BLOCK_PROC_CALL:
00001967         num = 2; /* Another special case */
00001968         read_write = I2C_SMBUS_READ;
00001969         if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
00001970             dev_err(&adapter->dev,
00001971                 "Invalid block write size %d\n",
00001972                 data->block[0]);
00001973             return -EINVAL;
00001974         }
00001975         msg[0].len = data->block[0] + 2;
00001976         for (i = 1; i < msg[0].len; i++)
00001977             msgbuf0[i] = data->block[i-1];
00001978         msg[1].flags |= I2C_M_RECV_LEN;
00001979         msg[1].len = 1; /* block length will be added by
00001980                    the underlying bus driver */
00001981         break;
00001982     case I2C_SMBUS_I2C_BLOCK_DATA:
00001983         if (read_write == I2C_SMBUS_READ) {
00001984             msg[1].len = data->block[0];
00001985         } else {
00001986             msg[0].len = data->block[0] + 1;
00001987             if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) {
00001988                 dev_err(&adapter->dev,
00001989                     "Invalid block write size %d\n",
00001990                     data->block[0]);
00001991                 return -EINVAL;
00001992             }
00001993             for (i = 1; i <= data->block[0]; i++)
00001994                 msgbuf0[i] = data->block[i];
00001995         }
00001996         break;
00001997     default:
00001998         dev_err(&adapter->dev, "Unsupported transaction %d\n", size);
00001999         return -EOPNOTSUPP;
00002000     }
00002001 
00002002     i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK
00002003                       && size != I2C_SMBUS_I2C_BLOCK_DATA);
00002004     if (i) {
00002005         /* Compute PEC if first message is a write */
00002006         if (!(msg[0].flags & I2C_M_RD)) {
00002007             if (num == 1) /* Write only */
00002008                 i2c_smbus_add_pec(&msg[0]);
00002009             else /* Write followed by read */
00002010                 partial_pec = i2c_smbus_msg_pec(0, &msg[0]);
00002011         }
00002012         /* Ask for PEC if last message is a read */
00002013         if (msg[num-1].flags & I2C_M_RD)
00002014             msg[num-1].len++;
00002015     }
00002016 
00002017     status = i2c_transfer(adapter, msg, num);
00002018     if (status < 0)
00002019         return status;
00002020 
00002021     /* Check PEC if last message is a read */
00002022     if (i && (msg[num-1].flags & I2C_M_RD)) {
00002023         status = i2c_smbus_check_pec(partial_pec, &msg[num-1]);
00002024         if (status < 0)
00002025             return status;
00002026     }
00002027 
00002028     if (read_write == I2C_SMBUS_READ)
00002029         switch (size) {
00002030         case I2C_SMBUS_BYTE:
00002031             data->byte = msgbuf0[0];
00002032             break;
00002033         case I2C_SMBUS_BYTE_DATA:
00002034             data->byte = msgbuf1[0];
00002035             break;
00002036         case I2C_SMBUS_WORD_DATA:
00002037         case I2C_SMBUS_PROC_CALL:
00002038             data->word = msgbuf1[0] | (msgbuf1[1] << 8);
00002039             break;
00002040         case I2C_SMBUS_I2C_BLOCK_DATA:
00002041             for (i = 0; i < data->block[0]; i++)
00002042                 data->block[i+1] = msgbuf1[i];
00002043             break;
00002044         case I2C_SMBUS_BLOCK_DATA:
00002045         case I2C_SMBUS_BLOCK_PROC_CALL:
00002046             for (i = 0; i < msgbuf1[0] + 1; i++)
00002047                 data->block[i] = msgbuf1[i];
00002048             break;
00002049         }
00002050     return 0;
00002051 }

复制代码

函数很长,但是逻辑却很简单。1898行,定义msgbuf0数组用作写操作,里面放的是要写的数据,后面会看到。

1899行,定义msgbuf1数组用作读操作,这里讨论的是写操作,因此略过与读操作相关的内容。

1900行,因为read_write=I2C_SMBUS_WRITE,所以num的值为1。

1901行,定义2个message数组,同样,一个用作写,一个用作读。

1908行,msgbuf0[0] = command,即要写数据的地址。

1909行,switch(size),由于size的值为I2C_SMBUS_BYTE_DATA,所以1924行的条件成立。

1925行,条件不成立,因此直接到1928行,msg[0].len = 2,写一字节地址和写一个字节数据加起来刚好是2字节。1929行,msgbuf0[1] = data->byte,即要写入的数据。

2002至2015行,与错误检测相关的,略过它不会有什么影响。

先看2022至2050行,都是与读操作相关的,因此不说了。

再看2017行,调用i2c_transfer函数来进行传输,i2c_transfer函数的定义:

复制代码

00001281 int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
00001282 {
00001283     unsigned long orig_jiffies;
00001284     int ret, try;
00001285 
00001286     /* REVISIT the fault reporting model here is weak:
00001287      *
00001288      *  - When we get an error after receiving N bytes from a slave,
00001289      *    there is no way to report "N".
00001290      *
00001291      *  - When we get a NAK after transmitting N bytes to a slave,
00001292      *    there is no way to report "N" ... or to let the master
00001293      *    continue executing the rest of this combined message, if
00001294      *    that's the appropriate response.
00001295      *
00001296      *  - When for example "num" is two and we successfully complete
00001297      *    the first message but get an error part way through the
00001298      *    second, it's unclear whether that should be reported as
00001299      *    one (discarding status on the second message) or errno
00001300      *    (discarding status on the first one).
00001301      */
00001302 
00001303     if (adap->algo->master_xfer) {
00001304 #ifdef DEBUG
00001305         for (ret = 0; ret < num; ret++) {
00001306             dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
00001307                 "len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
00001308                 ? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
00001309                 (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
00001310         }
00001311 #endif
00001312 
00001313         if (in_atomic() || irqs_disabled()) {
00001314             ret = i2c_trylock_adapter(adap);
00001315             if (!ret)
00001316                 /* I2C activity is ongoing. */
00001317                 return -EAGAIN;
00001318         } else {
00001319             i2c_lock_adapter(adap);
00001320         }
00001321 
00001322         /* Retry automatically on arbitration loss */
00001323         orig_jiffies = jiffies;
00001324         for (ret = 0, try = 0; try <= adap->retries; try++) {
00001325             ret = adap->algo->master_xfer(adap, msgs, num);
00001326             if (ret != -EAGAIN)
00001327                 break;
00001328             if (time_after(jiffies, orig_jiffies + adap->timeout))
00001329                 break;
00001330         }
00001331         i2c_unlock_adapter(adap);
00001332 
00001333         return ret;
00001334     } else {
00001335         dev_dbg(&adap->dev, "I2C level transfers not supported\n");
00001336         return -EOPNOTSUPP;
00001337     }
00001338 }

复制代码

一看,呆了眼,函数里面竟然有这么多注释。

1303行,master_xfer这个指针是有赋值的,因此执行if里面的语句。

1304至1311行,调试相关的,打印一些调试信息。

1313至1320行,锁住当前的适配器。

1324行,adap->retries的值在IIC控制器初始化的时候就设置为2,因此重试2次。

1325行,调用的是drivers/i2c/busses/i2c-s3c2410.c里的s3c24xx_i2c_xfer函数,它的定义:https://www.cnblogs.com/lknlfy/p/3265122.html

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值