linux2.6.28块设备mmc_sd卡初始化和识别流程及读写请求流程

图片
/
图片
/

//扫描总线上设备mmc设备
void mmc_rescan(struct work_struct *work)
{
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
u32 ocr;
int err;

mmc_bus_get(host);

//如果为第一次扫描总线上设备
if (host->bus_ops == NULL) {
/*
* Only we can add a new handler, so it’s safe to
* release the lock here.
*/
mmc_bus_put(host);

    if (host->ops->get_cd && host->ops->get_cd(host) == 0)
        goto out;

/* 驱动中使用mmc_claim_host(host);来得知,当前mmc控制器是否被占用,当前mmc控制器如果被占用,那么 host->claimed = 1;否则为0,如果为1,那么会在for(;;)循环中调用schedule切换出自己,当占用mmc控制器的操作完成之后,执行 mmc_release_host()的时候,会激活登记到等待队列&host->wq中的其他程序获得mmc主控制器的物理使用权*/
//这个函数和mmc_release_host(host);配对使用,相当于一把锁,就是在同一个时间只有一个sd卡可以保持和主控制器通讯;
mmc_claim_host(host);
最开始,卡是power_off
//开启host电源
mmc_power_up(host);
设置host为idle模式
mmc_go_idle(host);
///是用于验证SD卡接口操作状态的有效性命令(CMD8)。
//如果 SD_SEND_IF_COND指示为符合SD2.0标准的卡,
//则设置操作状态寄存器ocrbit30指示能 够处理块地址SDHC卡
//在SD子系统中,所有的数据传输均是由host发起。
//Host发送完一命令则会等待中断的产生,在这里采用完成量来实现进程的阻塞。
mmc_send_if_cond(host, host->ocr_avail);

    /*
     * First we search for SDIO...
     */

//判断是否是SDIO协议卡
//通过发送命令CMD5
//CMD5协议与SD接口中的ACMD41类似,用于检查是否支持SDIO的电压。
//CMD5的回应中,MP为0,表示是SDIO卡;如果MP为1,表示不但是SDIO卡,并且是SD卡。
err = mmc_send_io_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_sdio(host, ocr))
mmc_power_off(host);
goto out;
}

    /*
     * ...then normal SD...
     */

//判断是否是SD协议卡
//ocr 是指 card 內部的 Operation Condition Register (OCR) 讀出來的值
//發送 CMD41 CMD55 讀取 OCR 的值
//#define SD_APP_OP_COND 41 /* bcr [31:0] OCR R3 */
err = mmc_send_app_op_cond(host, 0, &ocr);//本函数后面有详解
if (!err) {
//装载 绑定 SD卡设备
if (mmc_attach_sd(host, ocr))
mmc_power_off(host);
goto out;
}

    /*
     * ...and finally MMC.
     */

//判断是否是MMC协议卡
err = mmc_send_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_mmc(host, ocr))
mmc_power_off(host);
goto out;
}
释放卡对主机的持有权
mmc_release_host(host);
mmc_power_off(host);
} else {
if (host->bus_ops->detect && !host->bus_dead)
host->bus_ops->detect(host);

    mmc_bus_put(host);
}

out:
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}
/

/**

  • mmc_wait_for_cmd - start a command and wait for completion
  • @host: MMC host to start command
  • @cmd: MMC command to start
  • @retries: maximum number of retries
  • Start a new MMC command for a host, and wait for the command
  • to complete. Return any error that occurred while the command
  • was executing. Do not attempt to parse the response.
    */
    //这个是SDIO卡的
    int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
    {
    struct mmc_request mrq;
    cmd->retries = retries;
    mrq.cmd = cmd;
    cmd->data = NULL;
    mmc_wait_for_req(host, &mrq);
    return cmd->error;
    }

/**

  • mmc_wait_for_app_cmd - start an application command and wait for
    completion
  • @host: MMC host to start command
  • @card: Card to send MMC_APP_CMD to
  • @cmd: MMC command to start
  • @retries: maximum number of retries
  • Sends a MMC_APP_CMD, checks the card response, sends the command
  • in the parameter and waits for it to complete. Return any error
  • that occurred while the command was executing. Do not attempt to
  • parse the response.
    */
    //这个是SD协议卡的
    int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card,
    struct mmc_command *cmd, int retries)
    {
    struct mmc_request mrq;
int i, err;

BUG_ON(!cmd);
BUG_ON(retries < 0);

err = -EIO;

/*
 * We have to resend MMC_APP_CMD for each attempt so
 * we cannot use the retries field in mmc_command.
 */
for (i = 0;i <= retries;i++) {
    memset(&mrq, 0, sizeof(struct mmc_request));

    err = mmc_app_cmd(host, card);
    if (err) {
        /* no point in retrying; no APP commands allowed */
        if (mmc_host_is_spi(host)) {
            if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
                break;
        }
        continue;
    }

    memset(&mrq, 0, sizeof(struct mmc_request));

    memset(cmd->resp, 0, sizeof(cmd->resp));
    cmd->retries = 0;

    mrq.cmd = cmd;
    cmd->data = NULL;

    mmc_wait_for_req(host, &mrq);

    err = cmd->error;
    if (!cmd->error)
        break;

    /* no point in retrying illegal APP commands */
    if (mmc_host_is_spi(host)) {
        if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
            break;
    }
}

return err;

}
/

//判断是否是SD协议卡
int mmc_send_app_op_cond(struct mmc_host *host, u32 ocr, u32 *rocr)
{
struct mmc_command cmd;
int i, err = 0;

BUG_ON(!host);

memset(&cmd, 0, sizeof(struct mmc_command));

//判断是否是SD协议卡
//ocr 是指 card 內部的 Operation Condition Register (OCR) 讀出來的值
//發送 CMD41 CMD55 讀取 OCR 的值
//#define SD_APP_OP_COND 41 /* bcr [31:0] OCR R3 */
//见SD卡手册

图片

图片

图片

图片

cmd.opcode = SD_APP_OP_COND;
if (mmc_host_is_spi(host))
    cmd.arg = ocr & (1 << 30); /* SPI only defines one bit */
else
    cmd.arg = ocr;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R3 | MMC_CMD_BCR;

for (i = 100; i; i--) {

//MMC_CMD_RETRIES 重复的次数?
err = mmc_wait_for_app_cmd(host, NULL, &cmd, MMC_CMD_RETRIES);
if (err)
break;

    /* if we're just probing, do a single pass */
    if (ocr == 0)
        break;

    /* otherwise wait until reset completes */
    if (mmc_host_is_spi(host)) {
        if (!(cmd.resp[0] & R1_SPI_IDLE))
            break;
    } else {
        if (cmd.resp[0] & MMC_CARD_BUSY)
            break;
    }

    err = -ETIMEDOUT;

    mmc_delay(10);
}

if (rocr && !mmc_host_is_spi(host))
    *rocr = cmd.resp[0];

return err;

}

/

/**

  • mmc_wait_for_req - start a request and wait for completion
  • @host: MMC host to start command
  • @mrq: MMC request to start
  • Start a new MMC custom command request for a host, and wait
  • for the command to complete. Does not attempt to parse the
  • response.
    */
    void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
    {
    DECLARE_COMPLETION_ONSTACK(complete);
mrq->done_data = &complete;
mrq->done = mmc_wait_done;

//开始request
//mmc_start_request->host->ops->request(即sdhci_request)
mmc_start_request(host, mrq);
//这里采用完成量来实现进程的阻塞。
wait_for_completion(&complete);
}
/

mmc_send_if_cond实现,从字面意思上看该函数就是发送一个SD标准命令,亦如usb的标准命令一样。

在这里不得不先说明一点就是在SD子系统中,所有的数据传输均是由host发起。Host发送完一命令则会等待中断的产生,在这里采用完成量来实现进程的阻塞。
int mmc_send_if_cond(struct mmc_host *host, u32 ocr)
{
struct mmc_command cmd;
int err;
static const u8 test_pattern = 0xAA;
u8 result_pattern;

/*
 * To support SD 2.0 cards, we must always invoke SD_SEND_IF_COND
 * before SD_APP_OP_COND. This command will harmlessly fail for
 * SD 1.0 cards.
 */
cmd.opcode = SD_SEND_IF_COND;
cmd.arg = ((ocr & 0xFF8000) != 0) << 8 | test_pattern;
cmd.flags = MMC_RSP_SPI_R7 | MMC_RSP_R7 | MMC_CMD_BCR;

//发送指令,并等待complete 完成量完成
err = mmc_wait_for_cmd(host, &cmd, 0);
if (err)
return err;

if (mmc_host_is_spi(host))
    result_pattern = cmd.resp[1] & 0xFF;
else
    result_pattern = cmd.resp[0] & 0xFF;

if (result_pattern != test_pattern)
    return -EIO;

return 0;

}
/
/

/*

  • Starting point for SD card init.
    */
    int mmc_attach_sd(struct mmc_host *host, u32 ocr)
    {
    int err;

    BUG_ON(!host);
    WARN_ON(!host->claimed);

    mmc_attach_bus(host, &mmc_sd_ops);

    /*

    • We need to get OCR a different way for SPI.
      */
      if (mmc_host_is_spi(host)) {
      mmc_go_idle(host);

      err = mmc_spi_read_ocr(host, 0, &ocr);
      if (err)
      goto err;
      }

    /*

    • Sanity check the voltages that the card claims to
    • support.
      */
      if (ocr & 0x7F) {
      printk(KERN_WARNING "%s: card claims to support voltages "
      “below the defined range. These will be ignored.\n”,
      mmc_hostname(host));
      ocr &= ~0x7F;
      }

    if (ocr & MMC_VDD_165_195) {
    printk(KERN_WARNING "%s: SD card claims to support the "
    "incompletely defined ‘low voltage range’. This "
    “will be ignored.\n”, mmc_hostname(host));
    ocr &= ~MMC_VDD_165_195;
    }

    host->ocr = mmc_select_voltage(host, ocr);

    /*

    • Can we support the voltage(s) of the card(s)?
      */
      if (!host->ocr) {
      err = -EINVAL;
      goto err;
      }

    /*

    • Detect and init the card.
      */
      err = mmc_sd_init_card(host, host->ocr, NULL);
      if (err)
      goto err;

    mmc_release_host(host);

    err = mmc_add_card(host->card);
    if (err)
    goto remove_card;

    return 0;

remove_card:
mmc_remove_card(host->card);
host->card = NULL;
mmc_claim_host(host);
err:
mmc_detach_bus(host);
mmc_release_host(host);

printk(KERN_ERR "%s: error %d whilst initialising SD card\n",
    mmc_hostname(host), err);

return err;

}
/

/*

  • Starting point for MMC card init.
    */
    int mmc_attach_mmc(struct mmc_host *host, u32 ocr)
    {
    int err;

    BUG_ON(!host);
    WARN_ON(!host->claimed);

    mmc_attach_bus(host, &mmc_ops);

    /*

    • We need to get OCR a different way for SPI.
      */
      if (mmc_host_is_spi(host)) {
      err = mmc_spi_read_ocr(host, 1, &ocr);
      if (err)
      goto err;
      }

    /*

    • Sanity check the voltages that the card claims to
    • support.
      */
      if (ocr & 0x7F) {
      printk(KERN_WARNING "%s: card claims to support voltages "
      “below the defined range. These will be ignored.\n”,
      mmc_hostname(host));
      ocr &= ~0x7F;
      }

    host->ocr = mmc_select_voltage(host, ocr);

    /*

    • Can we support the voltage of the card?
      */
      if (!host->ocr) {
      err = -EINVAL;
      goto err;
      }

    /*

    • Detect and init the card.
      */
      err = mmc_init_card(host, host->ocr, NULL);
      if (err)
      goto err;

    mmc_release_host(host);

    err = mmc_add_card(host->card);
    if (err)
    goto remove_card;

    return 0;

remove_card:
mmc_remove_card(host->card);
host->card = NULL;
mmc_claim_host(host);
err:
mmc_detach_bus(host);
mmc_release_host(host);

printk(KERN_ERR "%s: error %d whilst initialising MMC card\n",
    mmc_hostname(host), err);

return err;

}

/*

  • Starting point for SDIO card init.
    */
    int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
    {
    int err;
    int i, funcs;
    struct mmc_card *card;

    BUG_ON(!host);
    WARN_ON(!host->claimed);

    mmc_attach_bus(host, &mmc_sdio_ops);

    /*

    • Sanity check the voltages that the card claims to
    • support.
      */
      if (ocr & 0x7F) {
      printk(KERN_WARNING "%s: card claims to support voltages "
      “below the defined range. These will be ignored.\n”,
      mmc_hostname(host));
      ocr &= ~0x7F;
      }

    if (ocr & MMC_VDD_165_195) {
    printk(KERN_WARNING "%s: SDIO card claims to support the "
    "incompletely defined ‘low voltage range’. This "
    “will be ignored.\n”, mmc_hostname(host));
    ocr &= ~MMC_VDD_165_195;
    }

    host->ocr = mmc_select_voltage(host, ocr);

    /*

    • Can we support the voltage(s) of the card(s)?
      */
      if (!host->ocr) {
      err = -EINVAL;
      goto err;
      }

    /*

    • Inform the card of the voltage
      */
      err = mmc_send_io_op_cond(host, host->ocr, &ocr);
      if (err)
      goto err;

    /*

    • For SPI, enable CRC as appropriate.
      */
      if (mmc_host_is_spi(host)) {
      err = mmc_spi_set_crc(host, use_spi_crc);
      if (err)
      goto err;
      }

    /*

    • The number of functions on the card is encoded inside
    • the ocr.
      */
      funcs = (ocr & 0x70000000) >> 28;

    /*

    • Allocate card structure.
      */
      card = mmc_alloc_card(host, NULL);
      if (IS_ERR(card)) {
      err = PTR_ERR(card);
      goto err;
      }

    card->type = MMC_TYPE_SDIO;
    card->sdio_funcs = funcs;

    host->card = card;

    /*

    • For native busses: set card RCA and quit open drain mode.
      */
      if (!mmc_host_is_spi(host)) {
      err = mmc_send_relative_addr(host, &card->rca);
      if (err)
      goto remove;

      mmc_set_bus_mode(host, MMC_BUSMODE_PUSHPULL);
      }

    /*

    • Select card, as all following commands rely on that.
      */
      if (!mmc_host_is_spi(host)) {
      err = mmc_select_card(card);
      if (err)
      goto remove;
      }

    /*

    • Read the common registers.
      */
      err = sdio_read_cccr(card);
      if (err)
      goto remove;

    /*

    • Read the common CIS tuples.
      */
      err = sdio_read_common_cis(card);
      if (err)
      goto remove;

    /*

    • Switch to high-speed (if supported).
      */
      err = sdio_enable_hs(card);
      if (err)
      goto remove;

    /*

    • Change to the card’s maximum speed.
      /
      if (mmc_card_highspeed(card)) {
      /
      • The SDIO specification doesn’t mention how
      • the CIS transfer speed register relates to
      • high-speed, but it seems that 50 MHz is
      • mandatory.
        */
        mmc_set_clock(host, 50000000);
        } else {
        mmc_set_clock(host, card->cis.max_dtr);
        }

    /*

    • Switch to wider bus (if supported).
      */
      err = sdio_enable_wide(card);
      if (err)
      goto remove;

    /*

    • Initialize (but don’t add) all present functions.
      */
      for (i = 0;i < funcs;i++) {
      err = sdio_init_func(host->card, i + 1);
      if (err)
      goto remove;
      }

    mmc_release_host(host);

    /*

    • First add the card to the driver model…
      */
      err = mmc_add_card(host->card);
      if (err)
      goto remove_added;

    /*

    • …then the SDIO functions.
      */
      for (i = 0;i < funcs;i++) {
      err = sdio_add_func(host->card->sdio_func[i]);
      if (err)
      goto remove_added;
      }

    return 0;

remove_added:
/* Remove without lock if the device has been added. /
mmc_sdio_remove(host);
mmc_claim_host(host);
remove:
/
And with lock if it hasn’t been added. */
if (host->card)
mmc_sdio_remove(host);
err:
mmc_detach_bus(host);
mmc_release_host(host);

printk(KERN_ERR "%s: error %d whilst initialising SDIO card\n",
    mmc_hostname(host), err);

return err;

}

观察传递的参数,是mrq。可以知道mmc最终命令的承载都是用struct mmc_request *mrq 这样的结构完成
static void
mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
if (mrq->data) {
mmc_hostname(host), mrq->data->blksz,
mrq->data->blocks, mrq->data->flags,
mrq->data->timeout_ns / 1000000,
mrq->data->timeout_clks);
}

if (mrq->stop) {
    pr_debug("%s:     CMD%u arg %08x flags %08x\n",
         mmc_hostname(host), mrq->stop->opcode,
         mrq->stop->arg, mrq->stop->flags);
}

led_trigger_event(host->led, LED_FULL);

mrq->cmd->error = 0;
mrq->cmd->mrq = mrq;
if (mrq->data) {
        host->max_req_size);

    mrq->cmd->data = mrq->data;
    mrq->data->error = 0;
    mrq->data->mrq = mrq;
    if (mrq->stop) {
        mrq->data->stop = mrq->stop;
        mrq->stop->error = 0;
        mrq->stop->mrq = mrq;
    }
}

mmc_start_request->host->ops->request(即sdhci_request)
///mmc_start_request要交给各个host完成处理了。Mmc驱动是一个通用框架驱动,
//不同的host对应的命令处理必定有所差别。针对sdhci标准的host mmc驱动。
//host->ops->request(host, mrq);的执行将交给,sdhci_request()完成。
host->ops->request(host, mrq);
}
/

static const struct mmc_host_ops sdhci_ops = {
//request函数指针指向的函数用来处理host向从设备发送命令的请求,
.request = sdhci_request,
//set_ios用来设置电源、时钟等等之类(需要重点关注),
.set_ios = sdhci_set_ios,
//get_ro用来判断是否写保护
.get_ro = sdhci_get_ro,
//使能SD/MMC IRQ中断
.enable_sdio_irq = sdhci_enable_sdio_irq,
};
///

//static const struct mmc_host_ops sdhci_ops = {
// .request = sdhci_request,
//request函数指针指向的函数用来处理host向从设备发送命令的请求,
/*****************************************************************************\

  •                                                                       *
    
  • MMC callbacks *
  •                                                                       *
    

*****************************************************************************/

static void sdhci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
//注意函数一进来,host结构体发生变化,已经不再是mmc_host结构,
//而是各具体的厂商的host,如这里的struct sdhci_host *host;
//其实是host = mmc_priv(mmc);这么的得来的。
struct sdhci_host *host;
unsigned long flags;

host = mmc_priv(mmc);

spin_lock_irqsave(&host->lock, flags);

#ifndef CONFIG_LEDS_CLASS
sdhci_activate_led(host);
#endif
//保存起mrq结构
host->mrq = mrq;

if ((mmc->caps & MMC_CAP_ON_BOARD) || (host->flags & SDHCI_DEVICE_ALIVE))

//当命令传输完成系统调用中断处理函数sdhci_irq。在其中进行中断完成后的处理???。
//sdhci_add_host 中
// tasklet_init(&host->finish_tasklet, sdhci_tasklet_finish, (unsigned long)host);
sdhci_send_command(host, mrq->cmd);//发送命令
else {
if (!(readl(host->ioaddr + SDHCI_PRESENT_STATE) & SDHCI_CARD_PRESENT)
|| (host->flags & SDHCI_DEVICE_DEAD)) {
host->mrq->cmd->error = -ENOMEDIUM;
tasklet_schedule(&host->finish_tasklet);
} else
sdhci_send_command(host, mrq->cmd);
}
//为了保证编译器顺序编译,防止编译器优化打乱执行顺序
mmiowb();
spin_unlock_irqrestore(&host->lock, flags);
}
//

static void sdhci_send_command(struct sdhci_host *host, struct mmc_command cmd)
{
int flags;
u32 mask;
unsigned long timeout;
/
Wait max 10 ms */
timeout = 10;
//SDHCI_PRESENT_STATE 寄存器的第0位
图片
mask = SDHCI_CMD_INHIBIT;
//SDHCI_PRESENT_STATE 寄存器的第1位
图片
if ((cmd->data != NULL) || (cmd->flags & MMC_RSP_BUSY))
mask |= SDHCI_DATA_INHIBIT;

/* We shouldn't wait for data inihibit for stop commands, even
   though they might use busy signaling */
if (host->mrq->data && (cmd == host->mrq->data->stop))
    mask &= ~SDHCI_DATA_INHIBIT;

//
//#define SDHCI_PRESENT_STATE 0x24
//读一个长整形数据(8字节)当前状态寄存器
while (readl(host->ioaddr + SDHCI_PRESENT_STATE) & mask) {
if (timeout == 0) {
printk(KERN_ERR "%s: Controller never released "
“inhibit bit(s).\n”, mmc_hostname(host->mmc));
sdhci_dumpregs(host);
cmd->error = -EIO;
//
//int sdhci_add_host(struct sdhci_host *host)
// tasklet_init(&host->finish_tasklet, sdhci_tasklet_finish, (unsigned long)host);
finish_tasklet用于命令传输完成后的处理
tasklet_schedule(&host->finish_tasklet);
return;
}
timeout–;
mdelay(1);
}
//改变定时器时间,设置定时时间
mod_timer(&host->timer, jiffies + 10 * HZ);
//命令保存到host起来
host->cmd = cmd;
//准备数据
//里面的关键就是准备DMA,dma_map_sg(),
//从data->sg里面获取到信息,填充到DMA控制器里面。
sdhci_prepare_data(host, cmd->data);

//#define SDHCI_ARGUMENT 0x08
//SD命令参数寄存器
图片
writel(cmd->arg, host->ioaddr + SDHCI_ARGUMENT);

sdhci_set_transfer_mode(host, cmd->data);

if ((cmd->flags & MMC_RSP_136) && (cmd->flags & MMC_RSP_BUSY)) {
    printk(KERN_ERR "%s: Unsupported response type!\n",
        mmc_hostname(host->mmc));
    cmd->error = -EINVAL;

//启动finish_tasklet完成后面的事情。finish_tasklet的定义是sdhci_tasklet_finish
tasklet_schedule(&host->finish_tasklet);
return;
}

if (!(cmd->flags & MMC_RSP_PRESENT))
    flags = SDHCI_CMD_RESP_NONE;
else if (cmd->flags & MMC_RSP_136)
    flags = SDHCI_CMD_RESP_LONG;
else if (cmd->flags & MMC_RSP_BUSY)
    flags = SDHCI_CMD_RESP_SHORT_BUSY;
else
    flags = SDHCI_CMD_RESP_SHORT;

if (cmd->flags & MMC_RSP_CRC)
    flags |= SDHCI_CMD_CRC;
if (cmd->flags & MMC_RSP_OPCODE)
    flags |= SDHCI_CMD_INDEX;
if (cmd->data)
    flags |= SDHCI_CMD_DATA;

//数据发送,这才是真正的控制host发送命令的操作,到这类,mmc控制器才开始跟sd卡做交互
//#define SDHCI_COMMAND 0x0E
图片
writew(SDHCI_MAKE_CMD(cmd->opcode, flags),
host->ioaddr + SDHCI_COMMAND);
}
///

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xx-xx-xxx-xxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值