基於S3C2440的嵌入式Linux驅動——MMC/SD子系統解讀(一)
在閱讀本文之前,請先掌握以下基本知識,不然請略過本文。
預備知識:
熟讀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訊號線上沒有資料傳輸。
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命令的傳送函式。