在閱讀本文之前,請先掌握以下基本知識,不然請略過本文。

預備知識

熟讀LDD3前十章節的內容。

熟悉核心驅動模型(sysfs)和platform匯流排。

簡要了解過SD卡規範。

本文的內容基於如下硬體和軟體平臺:

目標平臺:TQ2440

CPU:s3c2440

核心版本:3.12.5

基於SD規範4.10,即《SD Specifications Part 1 Physical Layer Simplified Specification Version 4.10》。

一、MMC子系統構架

待寫。。。

二、主要資料結構

待寫。。。

三、MMC子系統初始化

首先看看子系統是如何初始化的,完成哪些工作。

程式碼位於linux/drivers/mmc/core/core.c。

static int __init mmc_init(void)
{
    int ret;

    /* 建立一個工作佇列*/
    workqueue = alloc_ordered_workqueue("kmmcd", 0);
    if (!workqueue)
        return -ENOMEM;

    /* 註冊mmc匯流排,匯流排提供probe方法
       並直接在內部呼叫驅動probe方法*/
    ret = mmc_register_bus();
    if (ret)
        goto destroy_workqueue;
    
    /* 註冊名為mmc_host的類*/
    ret = mmc_register_host_class();
    if (ret)
        goto unregister_bus;

    /* 註冊sdio匯流排,匯流排提供probe方法
       並直接在內部呼叫驅動probe方法*/
    ret = sdio_register_bus();
    if (ret)
        goto unregister_host_class;

    return 0;

unregister_host_class:
    mmc_unregister_host_class();
unregister_bus:
    mmc_unregister_bus();
destroy_workqueue:
    destroy_workqueue(workqueue);

    return ret;
}

程式碼首先註冊了一個工作佇列,這個工作佇列將用於掃描sd卡裝置。我們會在後面進行說明。

工作對類已核心執行緒的形式執行,可以用ps命令看到名為[kmmcd]的核心執行緒。

接著註冊了兩條名為mmc和sdio的匯流排,以及一個名為mmc_host的類。具體程式碼如下:

static struct bus_type mmc_bus_type = {
	.name		= "mmc",
	.dev_attrs	= mmc_dev_attrs,
	.match		= mmc_bus_match,
	.uevent		= mmc_bus_uevent,
	.probe		= mmc_bus_probe,
	.remove		= mmc_bus_remove,
	.shutdown	= mmc_bus_shutdown,
	.pm		= &mmc_bus_pm_ops,
};

int mmc_register_bus(void)
{
	return bus_register(&mmc_bus_type);
}

static struct class mmc_host_class = {
    .name        = "mmc_host",
    .dev_release    = mmc_host_classdev_release,
};

int mmc_register_host_class(void)
{
    return class_register(&mmc_host_class);
}

static struct bus_type sdio_bus_type = {
    .name        = "sdio",
    .dev_attrs    = sdio_dev_attrs,
    .match        = sdio_bus_match,
    .uevent        = sdio_bus_uevent,
    .probe        = sdio_bus_probe,
    .remove        = sdio_bus_remove,
    .pm        = SDIO_PM_OPS_PTR,
};

int sdio_register_bus(void)
{
    return bus_register(&sdio_bus_type);
}

static struct class mmc_host_class = {
    .name        = "mmc_host",
    .dev_release    = mmc_host_classdev_release,
};

int mmc_register_host_class(void)
{
    return class_register(&mmc_host_class);
}

熟悉Linux的裝置驅動模型的同學對這些肯定非常熟悉。匯流排和類的註冊只是呼叫了相應的介面,這些就不再贅述了。

其次,sdio匯流排不是我們關心的。我們只關心mmc匯流排。首先來看看mmc匯流排的match方法:

程式碼位於linux/drivers/mmc/core/bus.c。

/*
 * This currently matches any MMC driver to any MMC card - drivers
 * themselves make the decision whether to drive this card in their
 * probe method.
 */
static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{
	return 1;
}

match返回居然直接返回了1。這表示任意的驅動都能和mmc卡裝置成功匹配。

從註釋中我們也能看出,驅動的probe方法將會決定驅動是否能真正的匹配這個mmc卡裝置。

熟悉裝置驅動模型的可能知道,隨著match返回1表示匹配成功後,將會呼叫匯流排提供的probe方法。接著我們來看下mmc匯流排的probe方法。

程式碼位於linux/drivers/mmc/core/bus.c。

static int mmc_bus_probe(struct device *dev)
{
	struct mmc_driver *drv = to_mmc_driver(dev->driver);
	struct mmc_card *card = mmc_dev_to_card(dev);

	return drv->probe(card);
}
從這裡我們可以看到在mmc的probe方法中直接呼叫了驅動probe方法,這也驗證了剛才註釋中所說的話。

從上面分析可以看出,子系統初始化程式碼僅僅註冊了兩條匯流排和一個類,並建立了一個工作佇列。

四、核心層與控制器層間的介面API

MMC核心層要和SD卡裝置進行通訊,為了完成這一個工作需要將CMD或者ACMD命令通過MMC/SD控制器傳送給SD卡。

那麼MMC核心層如何將通訊的資料包交給MMC/SD控制器,並讓後者去傳送呢?

MMC通過函式mmc_wait_for_req完成這個工作,我們來看下這個函式。

4.1 mmc_wait_for_req 函式

下列程式碼位於linux/drivers/mmc/core/core.c。
/**
 *	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)
{
	__mmc_start_req(host, mrq);
	mmc_wait_for_req_done(host, mrq);
}
EXPORT_SYMBOL(mmc_wait_for_req);

    通過註釋可以發現,該函式會阻塞並等待request的完成。

   該函式分兩步走,第一步呼叫__mmc_start_req傳送命令,第二部呼叫 mmc_wait_for_req_done等待命令完成。

   分別來看下這兩個函式 :

static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)
{
	/* 初始化completion,並設定done方法*/
	init_completion(&mrq->completion);
	mrq->done = mmc_wait_done;
	/* 如果mmc已經被拔出,設定錯誤並返回錯誤*/
	if (mmc_card_removed(host->card)) {
		mrq->cmd->error = -ENOMEDIUM;
		complete(&mrq->completion);
		return -ENOMEDIUM;
	}
	/* 傳送命令 */
	mmc_start_request(host, mrq);
	return 0;
} 

該函式首先初始化了completion並設定了mrq->done方法為mmc_wait_done函式,該函式如下。

static void mmc_wait_done(struct mmc_request *mrq)
{
    complete(&mrq->completion);
}
這邊使用completion的目的是為了等待request傳送的完成。

在第二步mmc_wait_for_req_done中會使用wait_for_completion函式等待mmc控制器完成request,控制器驅動在完成request的傳送後,會呼叫mrq->done方法來啟用處於等待中的wait_for_completion函式。

隨後函式會首先檢查sd卡是否已被拔出,如果卡都被拔出了則沒有必要傳送request,可以直接呼叫copletion函式告之相關的等待函式,並設定error值然後返回錯誤。

#define mmc_card_removed(c)	((c) && ((c)->state & MMC_CARD_REMOVED))

如果sd卡存在,則呼叫mmc_start_request函式傳送request,該函式如下:

static void
mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
#ifdef CONFIG_MMC_DEBUG
    unsigned int i, sz;
    struct scatterlist *sg;
#endif

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

    pr_debug("%s: starting CMD%u arg %08x flags %08x\n",
         mmc_hostname(host), mrq->cmd->opcode,
         mrq->cmd->arg, mrq->cmd->flags);

    if (mrq->data) {
        pr_debug("%s:     blksz %d blocks %d flags %08x "
            "tsac %d ms nsac %d\n",
            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);
    }

    WARN_ON(!host->claimed);

    mrq->cmd->error = 0;
    mrq->cmd->mrq = mrq;
    if (mrq->data) {
        BUG_ON(mrq->data->blksz > host->max_blk_size);
        BUG_ON(mrq->data->blocks > host->max_blk_count);
        BUG_ON(mrq->data->blocks * mrq->data->blksz >
            host->max_req_size);

#ifdef CONFIG_MMC_DEBUG
        sz = 0;
        for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i)
            sz += sg->length;
        BUG_ON(sz != mrq->data->blocks * mrq->data->blksz);
#endif

        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_host_clk_hold(host);
    led_trigger_event(host->led, LED_FULL);
    /* 傳送request*/
    host->ops->request(host, mrq);
}
該函式會列印一堆資訊,然後清除cmd->error,並繫結cmd和mrq,接著如果mrq是請求資料

mmc_host_clk_hold函式是通過巨集CONFIG_MMC_CLKGATE來進行使能的,這個巨集預設是不開啟的,具體就不分析了,簡要說下這個巨集的作用。

這個巨集的作用是使能時鐘門控功能,這個功能在不需要MMC控制器工作的時候,停止MMC控制器,以節省功耗。

隨後會呼叫led_trigger_event觸發led事件,這個牽涉到Led子系統,就不進行說明了。

順便提一句,s3c2440的mmc控制器驅動並沒有使用這個led觸發功能,也就是說host->led是為空的。

最後呼叫了mmc控制器驅動提供的request方法傳送request。

這裡需要注意下函式指標的形參:一個為host表示mmc控制器,一個為mrq表示request請求

很顯然,要求host指向的mmc控制器傳送mrq指向的請求,同時,也可以看出所有傳遞到mmc控制器驅動的請求都是使用struct mmc_request結構體進行封裝的。

至此,第一步完成,接著我們來看第二步:

static void mmc_wait_for_req_done(struct mmc_host *host,
				  struct mmc_request *mrq)
{
	struct mmc_command *cmd;

	while (1) {
		wait_for_completion(&mrq->completion);

		cmd = mrq->cmd;

		/*
		 * If host has timed out waiting for the sanitize
		 * to complete, card might be still in programming state
		 * so let's try to bring the card out of programming
		 * state.
		 */
		if (cmd->sanitize_busy && cmd->error == -ETIMEDOUT) {
			if (!mmc_interrupt_hpi(host->card)) {
				pr_warning("%s: %s: Interrupted sanitize\n",
					   mmc_hostname(host), __func__);
				cmd->error = 0;
				break;
			} else {
				pr_err("%s: %s: Failed to interrupt sanitize\n",
				       mmc_hostname(host), __func__);
			}
		}
		if (!cmd->error || !cmd->retries ||
		    mmc_card_removed(host->card))
			break;

		pr_debug("%s: req failed (CMD%u): %d, retrying...\n",
			 mmc_hostname(host), cmd->opcode, cmd->error);
		cmd->retries--;
		cmd->error = 0;
                /* 沒有成功,嘗試再次傳送request*/
                host->ops->request(host, mrq);
	}
}
這個函式首先呼叫了wait_for_completion來等待mmc控制器驅動呼叫mmc_wait_done來喚醒自己。

被喚醒後會執行一系列檢查,如果request成功傳送,則會break,並直接返回。

如果沒有傳送成功,只要retries非0,則會嘗試再次呼叫mmc控制器驅動的request方法再次傳送。

4.2 CMD和ACMD傳送函式

通過4.1小結,我們知道MMC核心層如何將request交給MMC控制器驅動,並由後者傳送該request給sd卡。

通過SD卡規範,我們知道有兩種形式的命令,一種為CMD,而另一種為ACMD。

MMC子系統提供了兩個函式來完成這兩命令的傳送,分別是mmc_wait_for_cmd和mmc_wait_for_app_cmd。

先來看下CMD的傳送函式:

下列程式碼位於linux/drivers/mmc/core/core.c。

/**
 *	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.
 */
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
{
	struct mmc_request mrq = {NULL};

	WARN_ON(!host->claimed);

	/* 清空應答 */
	memset(cmd->resp, 0, sizeof(cmd->resp));
	cmd->retries = retries;

	/* 儲存命令*/
	mrq.cmd = cmd;
	cmd->data = NULL;

	/* 傳送命令並等待response */
	mmc_wait_for_req(host, &mrq);

	return cmd->error;
}
有了4.1小結的分析,這個函式還是比較簡單的。

該函式首先清空命令的應答資料(resp),並儲存命令(cmd)到mrq中,隨後呼叫4.1小節中的mmc_wait_for_req函式傳送CMD。

從這個函式的形參我們可以看出:所有需要傳送的CMD都由mmc_command進行封裝,在函式內部被mmc_request
結構體進行再次封裝,並將mmc_request交給MMC控制器驅動完成CMD的傳送。

接著看下ACMD命令的傳送函式mmc_wait_for_app_cmd:

下列程式碼位於Linux/drivers/mmc/core/sd_ops.h。

/**
 *	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.
 */
int mmc_wait_for_app_cmd(struct mmc_host *host, struct mmc_card *card,
	struct mmc_command *cmd, int retries)
{
	struct mmc_request mrq = {NULL};

	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++) {
		/* 傳送CMD55*/
		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;

		/* 傳送ACMDx*/
		mmc_wait_for_req(host, &mrq);

		err = cmd->error;
		/* 傳送成功,直接break並返回*/
		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;
}

EXPORT_SYMBOL(mmc_wait_for_app_cmd);
該函式的形參cmd儲存了代傳送的ACMD命令。

根據SD卡規範的要求:在傳送ACMD命令只前,需要傳送CMD55,以表示後面一個命令為AMD命令。

所以,該函式首先呼叫mmc_app_cmd函式來發送CMD55命令,我們來看下這個函式:

int mmc_app_cmd(struct mmc_host *host, struct mmc_card *card)
{
	int err;
	struct mmc_command cmd = {0};

	BUG_ON(!host);
	BUG_ON(card && (card->host != host));

	cmd.opcode = MMC_APP_CMD;	/* CMD55 */

	if (card) {
		cmd.arg = card->rca << 16;	/* 卡地址*/
		cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
	} else {
		cmd.arg = 0;	/* 卡地址*/
		cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_BCR;
	}

	/* 傳送cmd並等待(阻塞方式)*/
	err = mmc_wait_for_cmd(host, &cmd, 0);
	if (err)
		return err;

	/* Check that card supported application commands */
	/* 檢查card status第5位,判斷SD卡是否支援ACMD*/
	if (!mmc_host_is_spi(host) && !(cmd.resp[0] & R1_APP_CMD))
		return -EOPNOTSUPP;

	return 0;
}
EXPORT_SYMBOL_GPL(mmc_app_cmd);
先來看下SD規範中關於CMD55的說明:


從上述命令說明中,我們可以看出:

1)該命令為ac型別命令,也就是點對點命令,並且在DAT訊號線上沒有資料傳輸。

2)其次,該命令的引數(31位至16位)為RCA,也就是卡的地址。

3)最後,命令的應答資料格式為R1。

回到函式中。

cmd.arg為傳送命令的引數,函式首先設定了命令的引數為sd卡地址(RCA),這符合上面的描述。

隨後呼叫了之前分析的mmc_wait_for_cmd函式傳送CMD55命令。

上面提到CMD55命令的響應為R1,其格式如下:


其中32bit的card status作為響應資料被儲存在resp陣列中。

card status的具體位定義請檢視SD規範的4.10.1小結。

最後檢查CMD55的響應來判斷SD卡是否支援ACMD命令。

CMD55傳送成功後,返回到mmc_wait_for_app_cmd函式中。

接著,cmd被儲存到mrq.cmd 中,並呼叫mmc_wait_for_req中傳送ACMD命令。


五、小結

    本問主要對MMC子系統架構進行了簡單的介紹,並給出了一些關鍵資料結構。同時,對MMC子系統的初始化過程進行了簡單分析,最後,重點介紹了CMD和ACMD命令的傳送函式。