前面分析了SDIO探测原理,现在我们来看下SDIO读写传输。SDIO提供了两个新的I/O读写命令: CMD52(IO_RW_DIRECT)和CMD53(IO_RW_EXTENDED)。CMD52用来读写一个字节,CMD53用来读写更多的数据。

CMD53数据传输分块传输(block mode)和字节传输(byte mode)两种模式,下表是与SD memory card传输格式对比:

SDIO cardSD memory card
CMD53 rd (byte)CMD17 (READ_SINGLE_BLOCK)
CMD53 wr (byte)CMD24 (WRITE_BLOCK)
CMD53 rd (blk)CMD18 (READ_MULTIPLE_BLOCK)
CMD53 wr (blk)CMD25 (WRITE_MULTIPLE_BLOCK)

SDIO的一个feature就是可以interrupt host,前提是使能card的相应功能中断(CCCR的IENx/IENM)。host检测到这个pending中断后(CCCR的INTx)后会做一些特定处理。

看下CCCR(common control registers)相关寄存器定义 :

sdio-cccr.png

Linux Kernel传输分析

参考5.x:

sdio_claim_irq()会使能中断:

/**
 *      sdio_claim_irq - claim the IRQ for a SDIO function
 *      @func: SDIO function
 *      @handler: IRQ handler callback
 *
 *      Claim and activate the IRQ for the given SDIO function. The provided
 *      handler will be called when that IRQ is asserted.  The host is always
 *      claimed already when the handler is called so the handler should not
 *      call sdio_claim_host() or sdio_release_host().
 */
int sdio_claim_irq(struct sdio_func *func, sdio_irq_handler_t *handler)
{
        int ret;
        unsigned char reg;

        if (!func)
                return -EINVAL;

        pr_debug("SDIO: Enabling IRQ for %s...\n", sdio_func_id(func));

        if (func->irq_handler) {
                pr_debug("SDIO: IRQ for %s already in use.\n", sdio_func_id(func));
                return -EBUSY;
        }

        ret = mmc_io_rw_direct(func->card, 0, 0, SDIO_CCCR_IENx, 0, &reg);
        if (ret)
                return ret;

        reg |= 1 << func->num;

        reg |= 1; /* Master interrupt enable */

        ret = mmc_io_rw_direct(func->card, 1, 0, SDIO_CCCR_IENx, reg, NULL); //tj: enable int
        if (ret)
                return ret;

        func->irq_handler = handler; //tj: 注册isr
        ret = sdio_card_irq_get(func->card);
        if (ret)
                func->irq_handler = NULL;
        sdio_single_irq_set(func->card);

        return ret;
}
EXPORT_SYMBOL_GPL(sdio_claim_irq);
#define SDIO_CCCR_IENx                0x04    /* Function/Master Interrupt Enable */

call mmc_io_rw_direct()使能此func的中断并挂接中断处理func->irq_handler = handler。然后使能host侧中断:

static int sdio_card_irq_get(struct mmc_card *card)
{
        struct mmc_host *host = card->host;

        WARN_ON(!host->claimed);

        if (!host->sdio_irqs++) {
                if (!(host->caps2 & MMC_CAP2_SDIO_IRQ_NOTHREAD)) {
                        atomic_set(&host->sdio_irq_thread_abort, 0);
                        host->sdio_irq_thread =
                                kthread_run(sdio_irq_thread, host,
                                            "ksdioirqd/%s", mmc_hostname(host));
                        if (IS_ERR(host->sdio_irq_thread)) {
                                int err = PTR_ERR(host->sdio_irq_thread);
                                host->sdio_irqs--;
                                return err;
                        }
                } else if (host->caps & MMC_CAP_SDIO_IRQ) {
                        host->ops->enable_sdio_irq(host, 1);
                }
        }

        return 0;
}

我们看下具备MMC_CAP_SDIO_IRQ能力会走一个host的callback操作(->enable_sdio_irq)。对标准的SD host controller,这个callback定义在sdhci.c:

static const struct mmc_host_ops sdhci_ops = {
        .request        = sdhci_request,
        .post_req       = sdhci_post_req,
        .pre_req        = sdhci_pre_req,
        .set_ios        = sdhci_set_ios,
        .get_cd         = sdhci_get_cd,
        .get_ro         = sdhci_get_ro,
        .hw_reset       = sdhci_hw_reset,
        .enable_sdio_irq = sdhci_enable_sdio_irq, //tj: here

sdhci_enable_sdio_irq()会call sdhci_enable_sdio_irq_nolock():

static void sdhci_enable_sdio_irq_nolock(struct sdhci_host *host, int enable)
{
        if (!(host->flags & SDHCI_DEVICE_DEAD)) {
                if (enable)
                        host->ier |= SDHCI_INT_CARD_INT;
                else
                        host->ier &= ~SDHCI_INT_CARD_INT;

                sdhci_writel(host, host->ier, SDHCI_INT_ENABLE);
                sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
        }
}
#define SDHCI_INT_ENABLE        0x34
#define SDHCI_SIGNAL_ENABLE     0x38
...
#define  SDHCI_INT_CARD_INT     0x00000100

host spec有如下定义:

sdio-host-34h-reg.png

sdio-host-38h-reg.png

clear? ok.

看下isr call stack:

sdhci_irq() -> sdio_signal_irq() with SDHCI_INT_CARD_INT -> queue_delayed_work()
sdio_irq_work() -> sdio_run_irqs() -> process_sdio_pending_irqs()
static int process_sdio_pending_irqs(struct mmc_host *host)
{
        ...
        ret = sdio_get_pending_irqs(host, &pending);
        if (ret)
                return ret;

        count = 0;
        for (i = 1; i <= 7; i++) {
                if (pending & (1 << i)) {
                        func = card->sdio_func[i - 1];
                        if (!func) {
                                pr_warn("%s: pending IRQ for non-existent function\n",
                                        mmc_card_id(card));
                                ret = -EINVAL;
                        } else if (func->irq_handler) {
                                func->irq_handler(func);
                                count++;
                        } else {
                                pr_warn("%s: pending IRQ with no handler\n",
                                        sdio_func_id(func));
                                ret = -EINVAL;
                        }
                }
        }
}

先是获取pending:


static int sdio_get_pending_irqs(struct mmc_host *host, u8 *pending)
{
        struct mmc_card *card = host->card;
        int ret;

        WARN_ON(!host->claimed);

        ret = mmc_io_rw_direct(card, 0, 0, SDIO_CCCR_INTx, 0, pending);
        if (ret) {
                pr_debug("%s: error %d reading SDIO_CCCR_INTx\n",
                       mmc_card_id(card), ret);
                return ret;
        }

然后如果有pending(pending & (1 << i)),走callback(irq_handler),也就是之前在sdio_claim_irq()注册的handler。

sdio_io.c主要是单字节读写传输接口:

 *      sdio_readb - read a single byte from a SDIO function
 *      sdio_writeb - write a single byte to a SDIO function

就是封装了mmc_io_rw_direct()

多数据传输:

 *      sdio_readsb - read from a FIFO on a SDIO function
 *      sdio_writesb - write to a FIFO of a SDIO function

一样的数据切割处理最后call mmc_io_rw_extended( ..., blocks, blksz)

Done.