最近看了下mmc读写,起由是Vendor发来eMMC固件升级要求,说如果使用了CMD18 + CMD12,就要升级,因为不知道OEM使用情况,建议都升级。

我们来确认下内核的情况, 内核版本3.18,高通平台。

#define MMC_READ_DAT_UNTIL_STOP  11   /* adtc [31:0] dadr        R1  */
#define MMC_STOP_TRANSMISSION 12 /* ac R1b */
#define MMC_SEND_STATUS 13 /* ac [31:16] RCA R1 */
#define MMC_BUS_TEST_R 14 /* adtc R1 */
#define MMC_GO_INACTIVE_STATE 15 /* ac [31:16] RCA */
#define MMC_BUS_TEST_W 19 /* adtc R1 */
#define MMC_SPI_READ_OCR 58 /* spi spi_R3 */
#define MMC_SPI_CRC_ON_OFF 59 /* spi [0:0] flag spi_R1 */

/* class 2 */
#define MMC_SET_BLOCKLEN 16 /* ac [31:0] block len R1 */
#define MMC_READ_SINGLE_BLOCK 17 /* adtc [31:0] data addr R1 */
#define MMC_READ_MULTIPLE_BLOCK 18 /* adtc [31:0] data addr R1 */
#define MMC_SEND_TUNING_BLOCK 19 /* adtc R1 */
#define MMC_SEND_TUNING_BLOCK_HS200 21 /* adtc R1 */

CMD18就是MMC_READ_MULTIPLE_BLOCK,CMD12就是MMC_STOP_TRANSMISSION。

查下相关代码在mmc_test_prepare_mrq中使用。

static void mmc_test_prepare_mrq(struct mmc_test_card *test,
struct mmc_request *mrq, struct scatterlist *sg, unsigned sg_len,
unsigned dev_addr, unsigned blocks, unsigned blksz, int write)
{
BUG_ON(!mrq || !mrq->cmd || !mrq->data || !mrq->stop);

if (blocks > 1) {
mrq->cmd->opcode = write ?
MMC_WRITE_MULTIPLE_BLOCK : MMC_READ_MULTIPLE_BLOCK;
} else {
mrq->cmd->opcode = write ?
MMC_WRITE_BLOCK : MMC_READ_SINGLE_BLOCK;
}

mrq->cmd->arg = dev_addr;
if (!mmc_card_blockaddr(test->card))
mrq->cmd->arg <<= 9;

mrq->cmd->flags = MMC_RSP_R1 | MMC_CMD_ADTC;

if (blocks == 1)
mrq->stop = NULL;
else {
mrq->stop->opcode = MMC_STOP_TRANSMISSION; //CMD12
mrq->stop->arg = 0;
mrq->stop->flags = MMC_RSP_R1B | MMC_CMD_AC;
}

mrq->data->blksz = blksz;
mrq->data->blocks = blocks;
mrq->data->flags = write ? MMC_DATA_WRITE : MMC_DATA_READ;
mrq->data->sg = sg;
mrq->data->sg_len = sg_len;

mmc_set_data_timeout(mrq->data, test->card);
}

这个具体是在mmc_test里,先看看log情况。

mmc_test默认是模块CONFIG_MMC_TEST=m,insmod有如下签名问题。

insmod: failed to load mmc_test.ko: Required key not available

先略过签名改成y后,如下路径会多出mmc_test目录。

# ls /sys/bus/mmc/drivers
mmc_test mmcblk

ok,用mmc0会直接Oops,先用mmc1 (SD card)试下,操作步骤是:

echo mmc1:0001 > /sys/bus/mmc/drivers/mmcblk/unbind
echo mmc1:0001 > /sys/bus/mmc/drivers/mmc_test/bind

这时候到如下路径就能看到多出test和testlist两个文件。

xxx:/sys/kernel/debug/mmc1/mmc1:0001 # ls
state status test testlist

其中6就是多块读测试:

echo 6 > test

把CONFIG_MMC_DEBUG放开,太多打印了,能看到这项测试的log

[  137.468637] mmc1: starting CMD18 arg 00000000 flags 00000035
[ 137.468644] mmc1: blksz 512 blocks 16 flags 00000200 tsac 100 ms nsac 0
[ 137.468650] mmc1: CMD12 arg 00000000 flags 0000001d
[ 137.468696] sdhci [sdhci_irq()]: *** mmc1 got interrupt: 0x00000001
[ 137.469279] sdhci [sdhci_irq()]: *** mmc1 got interrupt: 0x00000002
[ 137.469309] sdhci [sdhci_irq()]: *** mmc1 got interrupt: 0x00000003
[ 137.469323] mmc1: accumulated busy time is 33456 usec
[ 137.469331] mmc1: req done (CMD18): 0: 00000900 00000000 00000000 00000000
[ 137.469336] mmc1: 8192 bytes transferred: 0
[ 137.469343] mmc1: (CMD12): 0: 00000b00 00000000 00000000 00000000

ok, 那mmc0到底用了没,放开debug,确是没有发现CMD18 + CMD12的打印,偶尔看了下LK的代码:

/*
* Function: mmc sdhci read
* Arg : mmc device structure, block address, number of blocks & destination
* Return : 0 on Success, non zero on success
* Flow : Fill in the command structure & send the command
*/
uint32_t mmc_sdhci_read(struct mmc_device *dev, void *dest,
uint64_t blk_addr, uint32_t num_blocks)
{
uint32_t mmc_ret = 0;
struct mmc_command cmd;
struct mmc_card *card = &dev->card;

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

/* CMD17/18 Format:
* [31:0] Data Address
*/
if (num_blocks == 1)
cmd.cmd_index = CMD17_READ_SINGLE_BLOCK;
else
cmd.cmd_index = CMD18_READ_MULTIPLE_BLOCK;
...
/* For multi block read failures send stop command */
if (mmc_ret && num_blocks > 1)
{
return mmc_stop_command(dev);
}

/*
* Response contains 32 bit Card status.
* Parse the errors & provide relevant information
*/
return mmc_parse_response(cmd.resp[0]);
}

正常的使用应该在这类读写接口里,kernel的mmc0/mmc1读写是怎么样的,从上面的log能看出在mmc_blk_rw_rq_prep中:

static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq,
struct mmc_card *card,
int disable_multi,
struct mmc_queue *mq)
{
u32 readcmd, writecmd;
struct mmc_blk_request *brq = &mqrq->brq;
struct request *req = mqrq->req;
struct mmc_blk_data *md = mq->data;
bool do_data_tag;

/*
* Reliable writes are used to implement Forced Unit Access and
* REQ_META accesses, and are supported only on MMCs.
*
* XXX: this really needs a good explanation of why REQ_META
* is treated special.
*/
bool do_rel_wr = ((req->cmd_flags & REQ_FUA) ||
(req->cmd_flags & REQ_META)) &&
(rq_data_dir(req) == WRITE) &&
(md->flags & MMC_BLK_REL_WR);

...
brq->cmd.arg = blk_rq_pos(req);
if (!mmc_card_blockaddr(card))
brq->cmd.arg <<= 9;
brq->cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_ADTC;
brq->data.blksz = 512;
brq->stop.opcode = MMC_STOP_TRANSMISSION; //stop
brq->stop.arg = 0;
...
if (brq->data.blocks > 1 || do_rel_wr) {
/* SPI multiblock writes terminate using a special
* token, not a STOP_TRANSMISSION request.
*/
if (!mmc_host_is_spi(card->host) ||
rq_data_dir(req) == READ)
brq->mrq.stop = &brq->stop; //stop
readcmd = MMC_READ_MULTIPLE_BLOCK;
writecmd = MMC_WRITE_MULTIPLE_BLOCK;
} else {
brq->mrq.stop = NULL;
readcmd = MMC_READ_SINGLE_BLOCK;
writecmd = MMC_WRITE_BLOCK;
}
...

流程调用大概看下:

mmc_blk_probe
|
mmc_blk_alloc/mmc_blk_alloc_part
|
mmc_blk_alloc_req (md->queue.issue_fn = mmc_blk_issue_rq)
|
mmc_blk_issue_rq
|
mmc_blk_issue_rw_rq
|
mmc_blk_rw_rq_prep

请求发出主要是通过一个线程 call issue_fn。

static int mmc_queue_thread(void *d)
{
struct mmc_queue *mq = d;
struct request_queue *q = mq->queue;
struct mmc_card *card = mq->card;

current->flags |= PF_MEMALLOC;
if (card->host->wakeup_on_idle)
set_wake_up_idle(true);

down(&mq->thread_sem);
do {
struct request *req = NULL;
struct mmc_queue_req *tmp;
unsigned int cmd_flags = 0;

spin_lock_irq(q->queue_lock);
set_current_state(TASK_INTERRUPTIBLE);
req = blk_fetch_request(q);
mq->mqrq_cur->req = req;
spin_unlock_irq(q->queue_lock);

if (req || mq->mqrq_prev->req) {
set_current_state(TASK_RUNNING);
cmd_flags = req ? req->cmd_flags : 0;
mq->issue_fn(mq, req); // mmc_blk_issue_rq

线程的创建是在mmc_init_queue:

/**
* mmc_init_queue - initialise a queue structure.
* @mq: mmc queue
* @card: mmc card to attach this queue
* @lock: queue lock
* @subname: partition subname
*
* Initialise a MMC card request queue.
*/
int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
spinlock_t *lock, const char *subname, int area_type)
{
struct mmc_host *host = card->host;
u64 limit = BLK_BOUNCE_HIGH;
int ret;
struct mmc_queue_req *mqrq_cur = &mq->mqrq[0];
struct mmc_queue_req *mqrq_prev = &mq->mqrq[1];
...
success:
sema_init(&mq->thread_sem, 1);

/* hook for pm qos legacy init */
if (card->host->ops->init)
card->host->ops->init(card->host);

mq->thread = kthread_run(mmc_queue_thread, mq, "mmcqd/%d%s",
host->index, subname ? subname : "");

ps看下:

# ps | grep mmc
root 288 2 0 0 irq_thread 00000000 S irq/145-mmc0
root 290 2 0 0 irq_thread 00000000 S irq/147-mmc1
root 292 2 0 0 mmc_cmdq_t 00000000 D mmc-cmdqd/0
root 293 2 0 0 mmc_queue_ 00000000 S mmcqd/0rpmb

插上SD card,

root      288   2     0      0     irq_thread 00000000 S irq/145-mmc0
root 290 2 0 0 irq_thread 00000000 S irq/147-mmc1
root 291 2 0 0 mmc_cmdq_t 00000000 D mmc-cmdqd/0
root 293 2 0 0 mmc_queue_ 00000000 S mmcqd/0rpmb
root 303 2 0 0 mmc_start_ 00000000 S mmcqd/1

上面创建的线程应该就是mmcqd/0rpmb和mmcqd/1,mmcqd/0rpmb是mmc0的rpmb区分,mmcqd/1才是for mmc1(SD card)。

那还有一个线程mmc-cmdqd/0是啥,线程创建往上再细看下:

int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
spinlock_t *lock, const char *subname, int area_type)
{
struct mmc_host *host = card->host;
u64 limit = BLK_BOUNCE_HIGH;
int ret;
struct mmc_queue_req *mqrq_cur = &mq->mqrq[0];
struct mmc_queue_req *mqrq_prev = &mq->mqrq[1];

if (mmc_dev(host)->dma_mask && *mmc_dev(host)->dma_mask)
limit = (u64)dma_max_pfn(mmc_dev(host)) << PAGE_SHIFT;

mq->card = card;
if (card->ext_csd.cmdq_support &&
(area_type == MMC_BLK_DATA_AREA_MAIN)) {
mq->queue = blk_init_queue(mmc_cmdq_dispatch_req, lock);
if (!mq->queue)
return -ENOMEM;
mmc_cmdq_setup_queue(mq, card);
ret = mmc_cmdq_init(mq, card);
if (ret) {
pr_err("%s: %d: cmdq: unable to set-up\n",
mmc_hostname(card->host), ret);
blk_cleanup_queue(mq->queue);
} else {
sema_init(&mq->thread_sem, 1);
/* hook for pm qos cmdq init */
if (card->host->cmdq_ops->init)
card->host->cmdq_ops->init(card->host);
mq->queue->queuedata = mq;
mq->thread = kthread_run(mmc_cmdq_thread, mq,
"mmc-cmdqd/%d%s",
host->index,
subname ? subname : "");
if (IS_ERR(mq->thread)) {
pr_err("%s: %d: cmdq: failed to start mmc-cmdqd thread\n",
mmc_hostname(card->host), ret);
ret = PTR_ERR(mq->thread);
}

return ret; //已经返回了
}
}

mq->queue = blk_init_queue(mmc_request_fn, lock);
if (!mq->queue)
return -ENOMEM;

如果card->ext_csd.cmdq_support为真,也就是说支持cmdq,如果创建mmc-cmdqd/0成功就返回了,ok, cmdq是啥。

看到ext_csd,很显然应该是个硬件特性,再看下开机mmc0/mmc1相关log:

[    8.332885] mmc0: SDHCI controller on 7824900.sdhci [7824900.sdhci] using 32-bit ADMA in CMDQ mode
...
[ 8.504130] mmc1: SDHCI controller on 7864900.sdhci [7864900.sdhci] using 32-bit ADMA in legacy mode

看到这里似乎知道了,mmc0用的是CMDQ方式工作,而mmc1仍然使用的是过去的旧方式。

我们再对比看下CMDQ的线程mmc_cmdq_thread,它对应请求是mmc_blk_cmdq_issue_rq/mmc_blk_cmdq_rw_prep,所有相关的cmdq的接口都在cmdq_hci.c里。

来看下git log:

commit 72dfcb7c9f6134282b8077beea420d5f0d736cc9
Author: Venkat Gopalakrishnan <venkatg@codeaurora.org>
Date: Fri May 29 17:25:46 2015 -0700

mmc: cmdq: support for command queue enabled host

This patch adds CMDQ support for command-queue compatible
hosts.

Command queue is added in eMMC-5.1 specification. This
enables the controller to process upto 32 requests at
a time.

Change-Id: I0486495ef57c64bf8427e917daeb184c69b8dc73
Signed-off-by: Asutosh Das <asutoshd@codeaurora.org>
Signed-off-by: Sujit Reddy Thumma <sthumma@codeaurora.org>
Signed-off-by: Konstantin Dorfman <kdorfman@codeaurora.org>
Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>

drivers/mmc/host/Kconfig | 13 ++++
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/cmdq_hci.c | 656 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
drivers/mmc/host/cmdq_hci.h | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/mmc/host.h | 12 ++++
log
commit df118e6a7fd85f1f649e2b35aed1d2b18cc29b94
Author: Asutosh Das <asutoshd@codeaurora.org>
Date: Fri Oct 17 16:36:47 2014 +0530

mmc: sdhci: add command queue support to sdhci

Adds command-queue support to SDHCi compliant drivers.

Change-Id: I1efee7f1c86e102364083e9158e4d45c887dd06e
Signed-off-by: Asutosh Das <asutoshd@codeaurora.org>
Signed-off-by: Konstantin Dorfman <kdorfman@codeaurora.org>
Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>

drivers/mmc/host/sdhci.c | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
drivers/mmc/host/sdhci.h | 3 +++
include/linux/mmc/sdhci.h | 2 ++
3 files changed, 144 insertions(+), 3 deletions(-)

@@ -293,6 +293,8 @@ struct sdhci_host {

u32 auto_cmd_err_sts;
struct ratelimit_state dbg_dump_rs;
+ struct cmdq_host *cq_host;
+
unsigned long private[0] ____cacheline_aligned;
};

static irqreturn_t sdhci_irq(int irq, void *dev_id)
{
irqreturn_t result = IRQ_NONE;
@@ -2948,6 +2963,15 @@ static irqreturn_t sdhci_irq(int irq, void *dev_id)
}

do {
+ if (host->mmc->card && mmc_card_cmdq(host->mmc->card) &&
+ !mmc_host_halt(host->mmc)) {
+ pr_debug("*** %s: cmdq intr: 0x%08x\n",
+ mmc_hostname(host->mmc),
+ intmask);
+ result = sdhci_cmdq_irq(host->mmc, intmask);
+ goto out;
+ }
+
if (intmask & SDHCI_INT_AUTO_CMD_ERR)
host->auto_cmd_err_sts = sdhci_readw(host,
SDHCI_AUTO_CMD_ERR);

@@ -3918,12 +4042,24 @@ int sdhci_add_host(struct sdhci_host *host)
sdhci_writel(host, host->ier, SDHCI_SIGNAL_ENABLE);
}

- pr_info("%s: SDHCI controller on %s [%s] using %s\n",
+ if (mmc->caps2 & MMC_CAP2_CMD_QUEUE) {
+ bool dma64 = (host->flags & SDHCI_USE_ADMA_64BIT) ?
+ true : false;
+ ret = sdhci_cmdq_init(host, mmc, dma64);
+ if (ret)
+ pr_err("%s: CMDQ init: failed (%d)\n",
+ mmc_hostname(host->mmc), ret);
+ else
+ host->cq_host->ops = &sdhci_cmdq_ops;
+ }
+ pr_info("%s: SDHCI controller on %s [%s] using %s in %s mode\n",
mmc_hostname(mmc), host->hw_name, dev_name(mmc_dev(mmc)),
(host->flags & SDHCI_USE_ADMA) ?
((host->flags & SDHCI_USE_ADMA_64BIT) ?
"64-bit ADMA" : "32-bit ADMA") :
- ((host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"));
+ ((host->flags & SDHCI_USE_SDMA) ? "DMA" : "PIO"),
+ ((mmc->caps2 & MMC_CAP2_CMD_QUEUE) && !ret) ?
+ "CMDQ" : "legacy");

那基本上就是这样了,还有一个地方:

static void sdhci_finish_data(struct sdhci_host *host)
{
...
/*
* Need to send CMD12 if -
* a) open-ended multiblock transfer (no CMD23)
* b) error in multiblock transfer
*/
if (data->stop &&
(data->error ||
!host->mrq->sbc)) {

/*
* The controller needs a reset of internal state machines
* upon error conditions.
*/
if (data->error) {
sdhci_do_reset(host, SDHCI_RESET_CMD);
sdhci_do_reset(host, SDHCI_RESET_DATA);
}

sdhci_send_command(host, data->stop);
} else

so it should not be for CQ xfer.

btw: 高通总是不正面答复我,给个差评:]