From: Saugata Das <saugata.das@xxxxxxxxxx> This patch groups the read or write transfers to eMMC in different contexts based on the block number. Transfers to consecutive blocks are grouped to a common context. So several small transfers combine to give performance like a large multi block transfer. The patch creates a context of 1MB multiple in non-large unit mode. Reliable mode is enabled in the context based on whether reliable write is enabled. Signed-off-by: Saugata Das <saugata.das@xxxxxxxxxx> --- drivers/mmc/card/block.c | 263 ++++++++++++++++++++++++++++++++++++++++++---- drivers/mmc/core/mmc.c | 6 + include/linux/mmc/card.h | 13 +++ include/linux/mmc/core.h | 9 ++ include/linux/mmc/host.h | 1 + include/linux/mmc/mmc.h | 3 + 6 files changed, 275 insertions(+), 20 deletions(-) diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c index 176b78e..161174a 100644 --- a/drivers/mmc/card/block.c +++ b/drivers/mmc/card/block.c @@ -127,6 +127,8 @@ enum mmc_blk_status { module_param(perdev_minors, int, 0444); MODULE_PARM_DESC(perdev_minors, "Minors numbers to allocate per device"); +static int mmc_blk_issue_rw_rq(struct mmc_queue *, struct request *); + static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk) { struct mmc_blk_data *md; @@ -1071,6 +1073,192 @@ static int mmc_blk_err_check(struct mmc_card *card, return MMC_BLK_SUCCESS; } +/* + * Update the context information in the ext. CSD + */ +static int mmc_context_modify(struct mmc_queue *mq, + struct mmc_card *card, + unsigned int context_id, + unsigned char context_conf) +{ + /* + * Wait for any ongoing transfer + */ + if (card->host->areq) + mmc_blk_issue_rw_rq(mq, NULL); + + /* + * CONTEXT_CONF array starts from context id 1 + */ + return mmc_switch(card, EXT_CSD_CMD_SET_NORMAL, + EXT_CSD_CONTEXT_CONF + context_id - 1, + context_conf, + card->ext_csd.generic_cmd6_time); +} + +/* + * Update timestamp, size and close context if needed + */ +static int mmc_check_close_context(struct mmc_queue *mq, + struct mmc_card *card, + unsigned int context_id, + unsigned int status) +{ + /* + * Check only for valid contexts + */ + if ((context_id > 0) && + (context_id <= card->ext_csd.max_context_id) && + (card->mmc_context[context_id].valid)) { + + /* + * Incase of an error or we are multiple of erase block then + * close the context + */ + if ((status) || + ((card->mmc_context[context_id].size % + card->ext_csd.lu_size) == 0)) { + if (mmc_context_modify(mq, card, context_id, + MMC_CONTEXT_CLOSE)) + return -1; + card->mmc_context[context_id].valid = 0; + } + return 0; + } + return 0; +} + +/* + * Update timestamp, size and close context if needed + */ +static int mmc_force_close_all_write_context(struct mmc_queue *mq, + struct mmc_card *card) +{ + int i, ret = 0; + for (i = 1; i <= card->ext_csd.max_context_id; i++) { + if (card->mmc_context[i].direction != MMC_CONTEXT_READ) { + ret = mmc_check_close_context(mq, card, i, + MMC_CONTEXT_FORCE_CLOSE); + if (ret) + return ret; + } + } + return ret; +} + +/* + * Search for a context which is in the same direction and + * continuous in block numbers. If no matching context is + * found then take up an unused context. If all contexts are + * used then close the context which is least recently used, + * close it and the use it for the new transfer + */ +static int mmc_get_context_id(struct mmc_queue *mq, + struct mmc_card *card, + unsigned int offset, + unsigned int size, + unsigned int direction, + unsigned int rel_wr) +{ + int i, free_index = -1, lru_index = -1, ret_index; + unsigned int old_timestamp = -1; + unsigned int reliability_mode = rel_wr ? (0x01<<5) : 0; + struct mmc_context *context_ptr = &card->mmc_context[1]; + + /* + * For older than 4.5 eMMC, there is no context ID + */ + if (card->ext_csd.max_context_id == 0) + return 0; + + if (card->host->caps2 & MMC_CAP2_NO_CONTEXT) + return 0; + + /* + * If the request is LU size multiple then avoid + * using any context + */ + if ((size % card->ext_csd.lu_size) == 0) + return 0; + + /* + * Context 0 is unused + */ + for (i = 1; i <= card->ext_csd.max_context_id; i++, context_ptr++) { + + unsigned int context_start_blk; + + if (!context_ptr->valid) { + if (free_index == -1) + free_index = i; + continue; + } + + context_start_blk = context_ptr->offset - + context_ptr->size; + + if ((context_start_blk <= offset) && + (context_ptr->offset > offset)) { + int ret = mmc_check_close_context(mq, card, i, + MMC_CONTEXT_FORCE_CLOSE); + if (ret < 0) + return ret; + if (free_index == -1) + free_index = i; + break; + } + + if ((lru_index == -1) || + (context_ptr->timestamp < old_timestamp)) { + old_timestamp = context_ptr->timestamp; + lru_index = i; + } + + if ((context_ptr->direction != direction) || + (context_ptr->offset != offset) || + (context_ptr->reliability_mode != + reliability_mode)) + continue; + + context_ptr->timestamp = jiffies; + context_ptr->offset += size; + context_ptr->size += size; + return i; + } + + if (free_index != -1) { + /* + * Open a free context + */ + if (mmc_context_modify(mq, card, free_index, + (direction & 0x03) | reliability_mode)) + return -1; + ret_index = free_index; + } else if (lru_index != -1) { + /* + * Close and reopen the LRU context + */ + if (mmc_context_modify(mq, card, lru_index, MMC_CONTEXT_CLOSE)) + return -1; + + if (mmc_context_modify(mq, card, lru_index, + (direction & 0x03) | reliability_mode)) + return -1; + ret_index = lru_index; + } else + return -1; + + context_ptr = &card->mmc_context[ret_index]; + context_ptr->offset = offset+size; + context_ptr->size = size; + context_ptr->direction = direction; + context_ptr->timestamp = jiffies; + context_ptr->reliability_mode = reliability_mode; + context_ptr->valid = 1; + + return ret_index; +} + static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, struct mmc_card *card, int disable_multi, @@ -1080,7 +1268,6 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, 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 @@ -1157,16 +1344,6 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, mmc_apply_rel_rw(brq, card, req); /* - * Data tag is used only during writing meta data to speed - * up write and any subsequent read of this meta data - */ - do_data_tag = (card->ext_csd.data_tag_unit_size) && - (req->cmd_flags & REQ_META) && - (rq_data_dir(req) == WRITE) && - ((brq->data.blocks * brq->data.blksz) >= - card->ext_csd.data_tag_unit_size); - - /* * Pre-defined multi-block transfers are preferable to * open ended-ones (and necessary for reliable writes). * However, it is not sufficient to just send CMD23, @@ -1184,15 +1361,54 @@ static void mmc_blk_rw_rq_prep(struct mmc_queue_req *mqrq, * We'll avoid using CMD23-bounded multiblock writes for * these, while retaining features like reliable writes. */ - if ((md->flags & MMC_BLK_CMD23) && mmc_op_multi(brq->cmd.opcode) && - (do_rel_wr || !(card->quirks & MMC_QUIRK_BLK_NO_CMD23) || - do_data_tag)) { - brq->sbc.opcode = MMC_SET_BLOCK_COUNT; - brq->sbc.arg = brq->data.blocks | - (do_rel_wr ? (1 << 31) : 0) | - (do_data_tag ? (1 << 29) : 0); - brq->sbc.flags = MMC_RSP_R1 | MMC_CMD_AC; - brq->mrq.sbc = &brq->sbc; + if ((md->flags & MMC_BLK_CMD23) && mmc_op_multi(brq->cmd.opcode)) { + /* + * Context ID is used for non-large unit and non-reliable + * write transfers provided the start block number + * sequentially follows any of the open contexts + */ + int context_id; + + /* + * Data tag is used only during writing meta data to speed + * up write and any subsequent read of this meta data + */ + bool do_data_tag = (card->ext_csd.data_tag_unit_size) && + (req->cmd_flags & REQ_META) && + (rq_data_dir(req) == WRITE) && + ((brq->data.blocks * brq->data.blksz) >= + card->ext_csd.data_tag_unit_size); + + /* + * During fsync, ensure that data is committed to storage + */ + if (req->cmd_flags & (REQ_FLUSH | REQ_SYNC)) + mmc_force_close_all_write_context(mq, card); + + /* + * Context ID is used for non-large unit and non-reliable + * write transfers provided the start block number + * sequentially follows any of the open contexts + */ + context_id = mmc_get_context_id(mq, + card, + blk_rq_pos(req), + brq->data.blocks, + (rq_data_dir(req) == WRITE) ? MMC_CONTEXT_WRITE : + MMC_CONTEXT_READ, + do_rel_wr); + + if (do_rel_wr || !(card->quirks & MMC_QUIRK_BLK_NO_CMD23) || + do_data_tag || (context_id > 0)) { + brq->sbc.opcode = MMC_SET_BLOCK_COUNT; + brq->sbc.arg = brq->data.blocks | + (do_rel_wr ? (1 << 31) : 0) | + (do_data_tag ? (1 << 29) : 0) | + (((context_id > 0) && (!do_rel_wr)) ? + ((context_id & 0x0f) << 25) : 0); + brq->sbc.flags = MMC_RSP_R1 | MMC_CMD_AC; + brq->mrq.sbc = &brq->sbc; + } } mmc_set_data_timeout(&brq->data, card); @@ -1284,6 +1500,13 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc) type = rq_data_dir(req) == READ ? MMC_BLK_READ : MMC_BLK_WRITE; mmc_queue_bounce_post(mq_rq); + if (mmc_check_close_context(mq, + card, + ((brq->sbc.arg) >> 25) & 0x0f, + status) < 0) { + status = MMC_BLK_CMD_ERR; + } + switch (status) { case MMC_BLK_SUCCESS: case MMC_BLK_PARTIAL: diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index dc03291..469d100 100644 --- a/drivers/mmc/core/mmc.c +++ b/drivers/mmc/core/mmc.c @@ -533,6 +533,12 @@ static int mmc_read_ext_csd(struct mmc_card *card, u8 *ext_csd) } else { card->ext_csd.data_tag_unit_size = 0; } + + /* LU size in 512 byte sectors */ + card->ext_csd.lu_size = + (ext_csd[EXT_CSD_LARGE_UNIT_SIZE_M1] + 1) * 0x800; + card->ext_csd.max_context_id = + ext_csd[EXT_CSD_CONTEXT_CAPABILITIES] & 0x0f; } out: diff --git a/include/linux/mmc/card.h b/include/linux/mmc/card.h index f9a0663..2e5b85a 100644 --- a/include/linux/mmc/card.h +++ b/include/linux/mmc/card.h @@ -73,6 +73,8 @@ struct mmc_ext_csd { unsigned int hpi_cmd; /* cmd used as HPI */ unsigned int data_sector_size; /* 512 bytes or 4KB */ unsigned int data_tag_unit_size; /* DATA TAG UNIT size */ + unsigned int lu_size; + unsigned int max_context_id; unsigned int boot_ro_lock; /* ro lock support */ bool boot_ro_lockable; u8 raw_partition_support; /* 160 */ @@ -184,6 +186,16 @@ struct sdio_func_tuple; #define MAX_MMC_PART_NAME_LEN 20 /* + * Contexts can be upto 15 + */ +#define MMC_NUM_CONTEXT 15 +#define MMC_CONTEXT_CLOSE 0 +#define MMC_CONTEXT_WRITE 1 +#define MMC_CONTEXT_READ 2 + +#define MMC_CONTEXT_FORCE_CLOSE 1 + +/* * MMC Physical partitions */ struct mmc_part { @@ -268,6 +280,7 @@ struct mmc_card { struct dentry *debugfs_root; struct mmc_part part[MMC_NUM_PHY_PARTITION]; /* physical partitions */ unsigned int nr_parts; + struct mmc_context mmc_context[MMC_NUM_CONTEXT]; }; /* diff --git a/include/linux/mmc/core.h b/include/linux/mmc/core.h index 87a976c..f20ebc5 100644 --- a/include/linux/mmc/core.h +++ b/include/linux/mmc/core.h @@ -130,6 +130,15 @@ struct mmc_request { void (*done)(struct mmc_request *);/* completion function */ }; +struct mmc_context { + unsigned int offset; + unsigned int size; + unsigned int direction; + unsigned int timestamp; + unsigned int reliability_mode; + unsigned int valid; +}; + struct mmc_host; struct mmc_card; struct mmc_async_req; diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h index dd13e05..661e262 100644 --- a/include/linux/mmc/host.h +++ b/include/linux/mmc/host.h @@ -255,6 +255,7 @@ struct mmc_host { #define MMC_CAP2_NO_SLEEP_CMD (1 << 4) /* Don't allow sleep command */ #define MMC_CAP2_HS200_1_8V_SDR (1 << 5) /* can support */ #define MMC_CAP2_HS200_1_2V_SDR (1 << 6) /* can support */ +#define MMC_CAP2_NO_CONTEXT (1 << 7) /* context ID not supported */ #define MMC_CAP2_HS200 (MMC_CAP2_HS200_1_8V_SDR | \ MMC_CAP2_HS200_1_2V_SDR) diff --git a/include/linux/mmc/mmc.h b/include/linux/mmc/mmc.h index b822a2c..809c3a5 100644 --- a/include/linux/mmc/mmc.h +++ b/include/linux/mmc/mmc.h @@ -274,6 +274,7 @@ struct _mmc_csd { #define EXT_CSD_FLUSH_CACHE 32 /* W */ #define EXT_CSD_CACHE_CTRL 33 /* R/W */ #define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ +#define EXT_CSD_CONTEXT_CONF 37 /* R/W : array of 15 config */ #define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */ #define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ #define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ @@ -316,6 +317,8 @@ struct _mmc_csd { #define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ #define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ #define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ +#define EXT_CSD_LARGE_UNIT_SIZE_M1 495 /* RO */ +#define EXT_CSD_CONTEXT_CAPABILITIES 496 /* RO */ #define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ #define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */ #define EXT_CSD_HPI_FEATURES 503 /* RO */ -- 1.7.4.3 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html