From b3e6a556a716e7cec86071342197e798b38c3cbf Mon Sep 17 00:00:00 2001
From: Andrei Warkentin <andreiw@motorola.com>
Date: Fri, 18 Feb 2011 17:46:00 -0600
Subject: [PATCH] MMC: Split non-page-size aligned accesses.
If the card page size is known, splits the access into an unaligned
and an aligned portion, which helps with the performance.
Change-Id: I4ad7588d613d775212fac87436e418577909a22b
Signed-off-by: Andrei Warkentin <andreiw@motorola.com>
---
drivers/mmc/card/block.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++
include/linux/mmc/card.h | 1 +
2 files changed, 112 insertions(+), 0 deletions(-)
@@ -22,6 +22,7 @@
#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/ctype.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/errno.h>
@@ -67,6 +68,74 @@ struct mmc_blk_data {
static DEFINE_MUTEX(open_lock);
+static ssize_t
+show_block_attr(struct device *dev, struct device_attribute *attr,
+ char *buf);
+
+static ssize_t
+set_block_attr(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count);
+
+static DEVICE_ATTR(page_size, S_IRUGO | S_IWUSR, show_block_attr, set_block_attr);
+
+static ssize_t
+show_block_attr(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ unsigned int val;
+ ssize_t ret = 0;
+ struct mmc_card *card = container_of(dev, struct mmc_card, dev);
+ mmc_claim_host(card->host);
+ if (attr == &dev_attr_page_size)
+ val = card->page_size;
+ else
+ ret = -EINVAL;
+
+ mmc_release_host(card->host);
+ if (!ret)
+ ret = sprintf(buf, "%u\n", val);
+ return ret;
+}
+
+static ssize_t
+set_block_attr(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ ssize_t ret;
+ char *after;
+ unsigned int val, *dest = NULL;
+ struct mmc_card *card = container_of(dev, struct mmc_card, dev);
+ val = simple_strtoul(buf, &after, 10);
+ ret = after - buf;
+
+ while (isspace(*after++))
+ ret++;
+
+ if (ret != count)
+ return -EINVAL;
+
+ if (attr == &dev_attr_page_size)
+ dest = &card->page_size;
+ else
+ return -EINVAL;
+
+ if (dest) {
+ mmc_claim_host(card->host);
+ *dest = val;
+ mmc_release_host(card->host);
+ }
+ return ret;
+}
+
+static struct attribute *capability_attrs[] = {
+ &dev_attr_page_size.attr,
+ NULL,
+};
+
+static struct attribute_group attr_group = {
+ .attrs = capability_attrs,
+};
+
static struct mmc_blk_data *mmc_blk_get(struct gendisk *disk)
{
struct mmc_blk_data *md;
@@ -312,6 +381,38 @@ out:
return err ? 0 : 1;
}
+
+/*
+ * If the request is not aligned, split it into an unaligned
+ * and an aligned portion. Here we can adjust
+ * the size of the MMC request and let the block layer request handle
+ * deal with generating another MMC request.
+ */
+static bool mmc_adjust_write(struct mmc_card *card,
+ struct mmc_request *mrq)
+{
+ unsigned int left_in_page;
+ unsigned int page_size_blocks;
+
+ if (!card->page_size)
+ return false;
+
+ page_size_blocks = card->page_size / mrq->data->blksz;
+ left_in_page = page_size_blocks -
+ (mrq->cmd->arg % page_size_blocks);
+
+ /* Aligned access. */
+ if (left_in_page == page_size_blocks)
+ return false;
+
+ /* Not straddling page boundary. */
+ if (mrq->data->blocks <= left_in_page)
+ return false;
+
+ mrq->data->blocks = left_in_page;
+ return true;
+}
+
static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *req)
{
struct mmc_blk_data *md = mq->data;
@@ -339,6 +440,10 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *req)
brq.stop.flags = MMC_RSP_SPI_R1B | MMC_RSP_R1B | MMC_CMD_AC;
brq.data.blocks = blk_rq_sectors(req);
+ /* Check for unaligned accesses straddling pages. */
+ if (rq_data_dir(req) == WRITE)
+ mmc_adjust_write(card, &brq.mrq);
+
/*
* The block layer doesn't support all sector count
* restrictions, so we need to be prepared for too big
@@ -707,6 +812,10 @@ static int mmc_blk_probe(struct mmc_card *card)
if (err)
goto out;
+ err = sysfs_create_group(&card->dev.kobj, &attr_group);
+ if (err)
+ goto out;
+
string_get_size((u64)get_capacity(md->disk) << 9, STRING_UNITS_2,
cap_str, sizeof(cap_str));
printk(KERN_INFO "%s: %s %s %s %s\n",
@@ -735,6 +844,8 @@ static void mmc_blk_remove(struct mmc_card *card)
/* Stop new requests from getting into the queue */
del_gendisk(md->disk);
+ sysfs_remove_group(&card->dev.kobj, &attr_group);
+
/* Then flush out any already in there */
mmc_cleanup_queue(&md->queue);
@@ -123,7 +123,7 @@ struct mmc_card {
unsigned int erase_size; /* erase size in sectors */
unsigned int erase_shift; /* if erase unit is power 2 */
unsigned int pref_erase; /* in sectors */
+ unsigned int page_size; /* page size in bytes */
u8 erased_byte; /* value of erased bytes */
u32 raw_cid[4]; /* raw card CID */
--
1.7.0.4