* [PATCH v2] mmc: Add CONFIG_MMC_SIMULATE_MAX_SPEED
@ 2016-02-22 17:18 Mark Salyzyn
2016-03-16 13:03 ` Ulf Hansson
0 siblings, 1 reply; 3+ messages in thread
From: Mark Salyzyn @ 2016-02-22 17:18 UTC (permalink / raw)
To: linux-kernel
Cc: Mark Salyzyn, Jonathan Corbet, Ulf Hansson, Adrian Hunter,
Yangbo Lu, Tomas Winkler, Andrew Morton, James Bottomley,
Kuninori Morimoto, Grant Grundler, Jon Hunter, Luca Porzio,
Yunpeng Gao, Chuanxiao Dong, linux-doc, linux-mmc
When CONFIG_MMC_SIMULATE_MAX_SPEED is enabled, Expose max_read_speed,
max_write_speed and cache_size sysfs controls to simulate a slow
eMMC device. The boot default values, should one wish to set this
behavior right from kernel start:
CONFIG_MMC_SIMULATE_MAX_READ_SPEED
CONFIG_MMC_SIMULATE_MAX_WRITE_SPEED
CONFIG_MMC_SIMULATE_CACHE_SIZE
respectively; and if not defined are 0 (off), 0 (off) and 4 MB
also respectively.
Signed-off-by: Mark Salyzyn <salyzyn@android.com>
---
changes in v2: change from CONFIG_MMC_BLOCK_MAX_SPEED to
CONFIG_MMC_SIMULATE_MAX_SPEED. Add documentation.
Documentation/block/00-INDEX | 6 +
Documentation/block/mmc-max-speed.txt | 39 +++++
drivers/mmc/card/Kconfig | 57 +++++++
drivers/mmc/card/block.c | 294 ++++++++++++++++++++++++++++++++++
drivers/mmc/card/queue.h | 8 +
5 files changed, 404 insertions(+)
create mode 100644 Documentation/block/mmc-max-speed.txt
diff --git a/Documentation/block/00-INDEX b/Documentation/block/00-INDEX
index e840b47..bc51487 100644
--- a/Documentation/block/00-INDEX
+++ b/Documentation/block/00-INDEX
@@ -26,3 +26,9 @@ switching-sched.txt
- Switching I/O schedulers at runtime
writeback_cache_control.txt
- Control of volatile write back caches
+mmc-max-speed.txt
+ - eMMC layer speed simulation, related to /sys/block/mmcblk*/
+ attributes:
+ max_read_speed
+ max_write_speed
+ cache_size
diff --git a/Documentation/block/mmc-max-speed.txt b/Documentation/block/mmc-max-speed.txt
new file mode 100644
index 0000000..3ad1260
--- /dev/null
+++ b/Documentation/block/mmc-max-speed.txt
@@ -0,0 +1,39 @@
+eMMC Block layer simulation speed controls in /sys/block/mmcblk*/
+===============================================
+
+Turned on with CONFIG_MMC_SIMULATE_MAX_SPEED which enables MMC device speed
+limiting. Used to test and simulate the behavior of the system when
+confronted with a slow MMC.
+
+Enables max_read_speed, max_write_speed and cache_size attributes to control
+the write or read maximum KB/second speed behaviors. The defaults are set
+by CONFIG_MMC_SIMULATE_MAX_READ_SPEED, CONFIG_MMC_SIMULATE_MAX_WRITE_SPEED and
+CONFIG_MMC_SIMULATE_CACHE_SIZE config parameters respectively.
+
+NB: There is room for improving the algorithm for aspects tied directly to
+eMMC specific behavior. For instance, wear leveling and stalls from an
+exhausted erase pool. We would expect that if there was a need to provide
+similar speed simulation controls to other types of block devices, aspects of
+their behavior are modelled separately (e.g. head seek times, heat assist,
+shingling and rotational latency).
+
+/sys/block/mmcblk0/max_read_speed:
+
+Number of KB/second reads allowed to the block device. Used to test and
+simulate the behavior of the system when confronted with a slow reading MMC.
+Set to 0 or "off" to place no speed limit.
+
+/sys/block/mmcblk0/max_write_speed:
+
+Number of KB/second writes allowed to the block device. Used to test and
+simulate the behavior of the system when confronted with a slow writing MMC.
+Set to 0 or "off" to place no speed limit.
+
+/sys/block/mmcblk0/cache_size:
+
+Number of MB of high speed memory or high speed SLC cache expected on the
+eMMC device being simulated. Used to help simulate the write-back behavior
+more accurately. The assumption is the cache has no delay, but draws down
+in the background to the MLC/TLC primary store at the max_write_speed rate.
+Any write speed delays will show up when the cache is full, or when an I/O
+request to flush is issued.
diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
index 5562308..1abef59 100644
--- a/drivers/mmc/card/Kconfig
+++ b/drivers/mmc/card/Kconfig
@@ -68,3 +68,60 @@ config MMC_TEST
This driver is only of interest to those developing or
testing a host driver. Most people should say N here.
+
+config MMC_SIMULATE_MAX_SPEED
+ bool "Turn on maximum speed control per block device"
+ depends on MMC_BLOCK
+ help
+ Say Y here to enable MMC device speed limiting. Used to test and
+ simulate the behavior of the system when confronted with a slow MMC.
+
+ Enables max_read_speed, max_write_speed and cache_size attributes to
+ control the write or read maximum KB/second speed behaviors.
+
+ If unsure, say N here.
+
+config MMC_SIMULATE_MAX_READ_SPEED
+ int "KB/second read speed limit per block device"
+ depends on MMC_BLOCK
+ depends on MMC_SIMULATE_MAX_SPEED
+ default 0
+ help
+ Number of KB/second reads allowed to the block device. Used to
+ test and simulate the behavior of the system when confronted with
+ a slow MMC. Set this value if it is required that the simulation
+ starts at boot time.
+
+ Value can be overridden at runtime with the max_read_speed attribute.
+
+ If unsure, say 0 here (no speed limit)
+
+config MMC_SIMULATE_MAX_WRITE_SPEED
+ int "KB/second write speed limit per block device"
+ depends on MMC_BLOCK
+ depends on MMC_SIMULATE_MAX_SPEED
+ default 0
+ help
+ Number of KB/second writes allowed to the block device. Used to
+ test and simulate the behavior of the system when confronted with
+ a slow MMC. Set this value if it is required that the simulation
+ starts at boot time.
+
+ Value can be overridden at runtime with the max_write_speed attribute.
+
+ If unsure, say 0 here (no speed limit)
+
+config MMC_SIMULATE_MAX_SPEED_CACHE_SIZE
+ int "MB of memory or SLC cache"
+ depends on MMC_BLOCK
+ depends on MMC_SIMULATE_MAX_SPEED
+ default 4
+ help
+ Number of MB of high speed memory or SLC cache expected on the
+ eMMC device. Used to help simulate the write-back behavior more
+ accurately. Set this value if it is required that the simulation
+ starts at boot time.
+
+ Value can be overridden at runtime with the cache_size attribute.
+
+ If unsure, say 4 here
diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
index fe207e5..7642673 100644
--- a/drivers/mmc/card/block.c
+++ b/drivers/mmc/card/block.c
@@ -286,6 +286,241 @@ out:
return ret;
}
+#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
+
+/*
+ * helper macros and expectations:
+ * size - unsigned long number of bytes
+ * jiffies - unsigned long HZ timestamp difference
+ * speed - unsigned KB/s transfer rate
+ */
+#define size_and_speed_to_jiffies(size, speed) \
+ ((size) * HZ / (speed) / 1024UL)
+#define jiffies_and_speed_to_size(jiffies, speed) \
+ (((speed) * (jiffies) * 1024UL) / HZ)
+#define jiffies_and_size_to_speed(jiffies, size) \
+ ((size) * HZ / (jiffies) / 1024UL)
+
+/* Limits to report warning */
+/* jiffies_and_size_to_speed(10*HZ, queue_max_hw_sectors(q) * 512UL) ~ 25 */
+#define MIN_SPEED(q) 250 /* 10 times faster than a floppy disk */
+#define MAX_SPEED(q) jiffies_and_size_to_speed(1, queue_max_sectors(q) * 512UL)
+
+#define speed_valid(speed) ((speed) > 0)
+
+static const char off[] = "off\n";
+
+static int max_speed_show(int speed, char *buf)
+{
+ if (speed)
+ return scnprintf(buf, PAGE_SIZE, "%uKB/s\n", speed);
+ else
+ return scnprintf(buf, PAGE_SIZE, off);
+}
+
+static int max_speed_store(const char *buf, struct request_queue *q)
+{
+ unsigned int limit, set = 0;
+
+ if (!strncasecmp(off, buf, sizeof(off) - 2))
+ return set;
+ if (kstrtouint(buf, 0, &set) || (set > INT_MAX))
+ return -EINVAL;
+ if (set == 0)
+ return set;
+ limit = MAX_SPEED(q);
+ if (set > limit)
+ pr_warn("max speed %u ineffective above %u\n", set, limit);
+ limit = MIN_SPEED(q);
+ if (set < limit)
+ pr_warn("max speed %u painful below %u\n", set, limit);
+ return set;
+}
+
+static ssize_t max_write_speed_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
+ int ret = max_speed_show(atomic_read(&md->queue.max_write_speed), buf);
+
+ mmc_blk_put(md);
+ return ret;
+}
+
+static ssize_t max_write_speed_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
+ int set = max_speed_store(buf, md->queue.queue);
+
+ if (set < 0) {
+ mmc_blk_put(md);
+ return set;
+ }
+
+ atomic_set(&md->queue.max_write_speed, set);
+ mmc_blk_put(md);
+ return count;
+}
+
+static const DEVICE_ATTR(max_write_speed, S_IRUGO | S_IWUSR,
+ max_write_speed_show, max_write_speed_store);
+
+static ssize_t max_read_speed_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
+ int ret = max_speed_show(atomic_read(&md->queue.max_read_speed), buf);
+
+ mmc_blk_put(md);
+ return ret;
+}
+
+static ssize_t max_read_speed_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
+ int set = max_speed_store(buf, md->queue.queue);
+
+ if (set < 0) {
+ mmc_blk_put(md);
+ return set;
+ }
+
+ atomic_set(&md->queue.max_read_speed, set);
+ mmc_blk_put(md);
+ return count;
+}
+
+static const DEVICE_ATTR(max_read_speed, S_IRUGO | S_IWUSR,
+ max_read_speed_show, max_read_speed_store);
+
+static ssize_t cache_size_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
+ struct mmc_queue *mq = &md->queue;
+ int cache_size = atomic_read(&mq->cache_size);
+ int ret;
+
+ if (!cache_size)
+ ret = scnprintf(buf, PAGE_SIZE, off);
+ else {
+ int speed = atomic_read(&mq->max_write_speed);
+
+ if (!speed_valid(speed))
+ ret = scnprintf(buf, PAGE_SIZE, "%uMB\n", cache_size);
+ else { /* We accept race between cache_jiffies and cache_used */
+ unsigned long size = jiffies_and_speed_to_size(
+ jiffies - mq->cache_jiffies, speed);
+ long used = atomic_long_read(&mq->cache_used);
+
+ if (size >= used)
+ size = 0;
+ else
+ size = (used - size) * 100 / cache_size
+ / 1024UL / 1024UL;
+
+ ret = scnprintf(buf, PAGE_SIZE, "%uMB %lu%% used\n",
+ cache_size, size);
+ }
+ }
+
+ mmc_blk_put(md);
+ return ret;
+}
+
+static ssize_t cache_size_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct mmc_blk_data *md;
+ unsigned int set = 0;
+
+ if (strncasecmp(off, buf, sizeof(off) - 2)
+ && (kstrtouint(buf, 0, &set) || (set > INT_MAX)))
+ return -EINVAL;
+
+ md = mmc_blk_get(dev_to_disk(dev));
+ atomic_set(&md->queue.cache_size, set);
+ mmc_blk_put(md);
+ return count;
+}
+
+static const DEVICE_ATTR(cache_size, S_IRUGO | S_IWUSR,
+ cache_size_show, cache_size_store);
+
+/* correct for write-back */
+static long mmc_blk_cache_used(struct mmc_queue *mq, unsigned long waitfor)
+{
+ long used = 0;
+ int speed = atomic_read(&mq->max_write_speed);
+
+ if (speed_valid(speed)) {
+ unsigned long size = jiffies_and_speed_to_size(
+ waitfor - mq->cache_jiffies, speed);
+ used = atomic_long_read(&mq->cache_used);
+
+ if (size >= used)
+ used = 0;
+ else
+ used -= size;
+ }
+
+ atomic_long_set(&mq->cache_used, used);
+ mq->cache_jiffies = waitfor;
+
+ return used;
+}
+
+static void mmc_blk_simulate_delay(
+ struct mmc_queue *mq,
+ struct request *req,
+ unsigned long waitfor)
+{
+ int max_speed;
+
+ if (!req)
+ return;
+
+ max_speed = (rq_data_dir(req) == READ)
+ ? atomic_read(&mq->max_read_speed)
+ : atomic_read(&mq->max_write_speed);
+ if (speed_valid(max_speed)) {
+ unsigned long bytes = blk_rq_bytes(req);
+
+ if (rq_data_dir(req) != READ) {
+ int cache_size = atomic_read(&mq->cache_size);
+
+ if (cache_size) {
+ unsigned long size = cache_size * 1024L * 1024L;
+ long used = mmc_blk_cache_used(mq, waitfor);
+
+ used += bytes;
+ atomic_long_set(&mq->cache_used, used);
+ bytes = 0;
+ if (used > size)
+ bytes = used - size;
+ }
+ }
+ waitfor += size_and_speed_to_jiffies(bytes, max_speed);
+ if (time_is_after_jiffies(waitfor)) {
+ long msecs = jiffies_to_msecs(waitfor - jiffies);
+
+ if (likely(msecs > 0))
+ msleep(msecs);
+ }
+ }
+}
+
+#else
+
+#define mmc_blk_simulate_delay(mq, req, waitfor)
+
+#endif
+
static int mmc_blk_open(struct block_device *bdev, fmode_t mode)
{
struct mmc_blk_data *md = mmc_blk_get(bdev->bd_disk);
@@ -1250,6 +1485,23 @@ static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req)
if (ret)
ret = -EIO;
+#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
+ else if (atomic_read(&mq->cache_size)) {
+ long used = mmc_blk_cache_used(mq, jiffies);
+
+ if (used) {
+ int speed = atomic_read(&mq->max_write_speed);
+
+ if (speed_valid(speed)) {
+ unsigned long msecs = jiffies_to_msecs(
+ size_and_speed_to_jiffies(
+ used, speed));
+ if (msecs)
+ msleep(msecs);
+ }
+ }
+ }
+#endif
blk_end_request_all(req, ret);
return ret ? 0 : 1;
@@ -1929,6 +2181,9 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
struct mmc_async_req *areq;
const u8 packed_nr = 2;
u8 reqs = 0;
+#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
+ unsigned long waitfor = jiffies;
+#endif
if (!rqc && !mq->mqrq_prev->req)
return 0;
@@ -1979,6 +2234,8 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
*/
mmc_blk_reset_success(md, type);
+ mmc_blk_simulate_delay(mq, rqc, waitfor);
+
if (mmc_packed_cmd(mq_rq->cmd_type)) {
ret = mmc_blk_end_packed_req(mq_rq);
break;
@@ -2397,6 +2654,14 @@ static void mmc_blk_remove_req(struct mmc_blk_data *md)
card->ext_csd.boot_ro_lockable)
device_remove_file(disk_to_dev(md->disk),
&md->power_ro_lock);
+#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
+ device_remove_file(disk_to_dev(md->disk),
+ &dev_attr_max_write_speed);
+ device_remove_file(disk_to_dev(md->disk),
+ &dev_attr_max_read_speed);
+ device_remove_file(disk_to_dev(md->disk),
+ &dev_attr_cache_size);
+#endif
del_gendisk(md->disk);
}
@@ -2432,6 +2697,27 @@ static int mmc_add_disk(struct mmc_blk_data *md)
ret = device_create_file(disk_to_dev(md->disk), &md->force_ro);
if (ret)
goto force_ro_fail;
+#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
+ atomic_set(&md->queue.max_write_speed,
+ CONFIG_MMC_SIMULATE_MAX_WRITE_SPEED);
+ ret = device_create_file(disk_to_dev(md->disk),
+ &dev_attr_max_write_speed);
+ if (ret)
+ goto max_write_speed_fail;
+ atomic_set(&md->queue.max_read_speed,
+ CONFIG_MMC_SIMULATE_MAX_READ_SPEED);
+ ret = device_create_file(disk_to_dev(md->disk),
+ &dev_attr_max_read_speed);
+ if (ret)
+ goto max_read_speed_fail;
+ atomic_set(&md->queue.cache_size,
+ CONFIG_MMC_SIMULATE_MAX_SPEED_CACHE_SIZE);
+ atomic_long_set(&md->queue.cache_used, 0);
+ md->queue.cache_jiffies = jiffies;
+ ret = device_create_file(disk_to_dev(md->disk), &dev_attr_cache_size);
+ if (ret)
+ goto cache_size_fail;
+#endif
if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) &&
card->ext_csd.boot_ro_lockable) {
@@ -2456,6 +2742,14 @@ static int mmc_add_disk(struct mmc_blk_data *md)
return ret;
power_ro_lock_fail:
+#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
+ device_remove_file(disk_to_dev(md->disk), &dev_attr_cache_size);
+cache_size_fail:
+ device_remove_file(disk_to_dev(md->disk), &dev_attr_max_read_speed);
+max_read_speed_fail:
+ device_remove_file(disk_to_dev(md->disk), &dev_attr_max_write_speed);
+max_write_speed_fail:
+#endif
device_remove_file(disk_to_dev(md->disk), &md->force_ro);
force_ro_fail:
del_gendisk(md->disk);
diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h
index 36cddab..d890d88 100644
--- a/drivers/mmc/card/queue.h
+++ b/drivers/mmc/card/queue.h
@@ -58,6 +58,14 @@ struct mmc_queue {
struct mmc_queue_req mqrq[2];
struct mmc_queue_req *mqrq_cur;
struct mmc_queue_req *mqrq_prev;
+#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
+ atomic_t max_write_speed;
+ atomic_t max_read_speed;
+ atomic_t cache_size;
+ /* i/o tracking */
+ atomic_long_t cache_used;
+ unsigned long cache_jiffies;
+#endif
};
extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *,
--
2.7.0.rc3.207.g0ac5344
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH v2] mmc: Add CONFIG_MMC_SIMULATE_MAX_SPEED
2016-02-22 17:18 [PATCH v2] mmc: Add CONFIG_MMC_SIMULATE_MAX_SPEED Mark Salyzyn
@ 2016-03-16 13:03 ` Ulf Hansson
2016-03-16 16:45 ` Mark Salyzyn
0 siblings, 1 reply; 3+ messages in thread
From: Ulf Hansson @ 2016-03-16 13:03 UTC (permalink / raw)
To: Mark Salyzyn
Cc: linux-kernel@vger.kernel.org, Jonathan Corbet, Adrian Hunter,
Yangbo Lu, Tomas Winkler, Andrew Morton, James Bottomley,
Kuninori Morimoto, Grant Grundler, Jon Hunter, Luca Porzio,
Yunpeng Gao, Chuanxiao Dong, linux-doc@vger.kernel.org, linux-mmc
On 22 February 2016 at 18:18, Mark Salyzyn <salyzyn@android.com> wrote:
> When CONFIG_MMC_SIMULATE_MAX_SPEED is enabled, Expose max_read_speed,
> max_write_speed and cache_size sysfs controls to simulate a slow
> eMMC device. The boot default values, should one wish to set this
> behavior right from kernel start:
>
> CONFIG_MMC_SIMULATE_MAX_READ_SPEED
> CONFIG_MMC_SIMULATE_MAX_WRITE_SPEED
> CONFIG_MMC_SIMULATE_CACHE_SIZE
>
> respectively; and if not defined are 0 (off), 0 (off) and 4 MB
> also respectively.
So this changelog doesn't really tell me *why* this feature is nice to
have. Could you elaborate on this and thus also extend the information
in the changelog please.
Moreover, I have briefly reviewed the code, but I don't want to go
into the details yet... Instead, what I am trying to understand if
this is something that useful+specific for the MMC subsystem, or if
it's something that belongs in the upper generic BLOCK layer. Perhaps
you can comment on this as well?
Kind regards
Uffe
>
> Signed-off-by: Mark Salyzyn <salyzyn@android.com>
> ---
> changes in v2: change from CONFIG_MMC_BLOCK_MAX_SPEED to
> CONFIG_MMC_SIMULATE_MAX_SPEED. Add documentation.
>
> Documentation/block/00-INDEX | 6 +
> Documentation/block/mmc-max-speed.txt | 39 +++++
> drivers/mmc/card/Kconfig | 57 +++++++
> drivers/mmc/card/block.c | 294 ++++++++++++++++++++++++++++++++++
> drivers/mmc/card/queue.h | 8 +
> 5 files changed, 404 insertions(+)
> create mode 100644 Documentation/block/mmc-max-speed.txt
>
> diff --git a/Documentation/block/00-INDEX b/Documentation/block/00-INDEX
> index e840b47..bc51487 100644
> --- a/Documentation/block/00-INDEX
> +++ b/Documentation/block/00-INDEX
> @@ -26,3 +26,9 @@ switching-sched.txt
> - Switching I/O schedulers at runtime
> writeback_cache_control.txt
> - Control of volatile write back caches
> +mmc-max-speed.txt
> + - eMMC layer speed simulation, related to /sys/block/mmcblk*/
> + attributes:
> + max_read_speed
> + max_write_speed
> + cache_size
> diff --git a/Documentation/block/mmc-max-speed.txt b/Documentation/block/mmc-max-speed.txt
> new file mode 100644
> index 0000000..3ad1260
> --- /dev/null
> +++ b/Documentation/block/mmc-max-speed.txt
> @@ -0,0 +1,39 @@
> +eMMC Block layer simulation speed controls in /sys/block/mmcblk*/
> +===============================================
> +
> +Turned on with CONFIG_MMC_SIMULATE_MAX_SPEED which enables MMC device speed
> +limiting. Used to test and simulate the behavior of the system when
> +confronted with a slow MMC.
> +
> +Enables max_read_speed, max_write_speed and cache_size attributes to control
> +the write or read maximum KB/second speed behaviors. The defaults are set
> +by CONFIG_MMC_SIMULATE_MAX_READ_SPEED, CONFIG_MMC_SIMULATE_MAX_WRITE_SPEED and
> +CONFIG_MMC_SIMULATE_CACHE_SIZE config parameters respectively.
> +
> +NB: There is room for improving the algorithm for aspects tied directly to
> +eMMC specific behavior. For instance, wear leveling and stalls from an
> +exhausted erase pool. We would expect that if there was a need to provide
> +similar speed simulation controls to other types of block devices, aspects of
> +their behavior are modelled separately (e.g. head seek times, heat assist,
> +shingling and rotational latency).
> +
> +/sys/block/mmcblk0/max_read_speed:
> +
> +Number of KB/second reads allowed to the block device. Used to test and
> +simulate the behavior of the system when confronted with a slow reading MMC.
> +Set to 0 or "off" to place no speed limit.
> +
> +/sys/block/mmcblk0/max_write_speed:
> +
> +Number of KB/second writes allowed to the block device. Used to test and
> +simulate the behavior of the system when confronted with a slow writing MMC.
> +Set to 0 or "off" to place no speed limit.
> +
> +/sys/block/mmcblk0/cache_size:
> +
> +Number of MB of high speed memory or high speed SLC cache expected on the
> +eMMC device being simulated. Used to help simulate the write-back behavior
> +more accurately. The assumption is the cache has no delay, but draws down
> +in the background to the MLC/TLC primary store at the max_write_speed rate.
> +Any write speed delays will show up when the cache is full, or when an I/O
> +request to flush is issued.
> diff --git a/drivers/mmc/card/Kconfig b/drivers/mmc/card/Kconfig
> index 5562308..1abef59 100644
> --- a/drivers/mmc/card/Kconfig
> +++ b/drivers/mmc/card/Kconfig
> @@ -68,3 +68,60 @@ config MMC_TEST
>
> This driver is only of interest to those developing or
> testing a host driver. Most people should say N here.
> +
> +config MMC_SIMULATE_MAX_SPEED
> + bool "Turn on maximum speed control per block device"
> + depends on MMC_BLOCK
> + help
> + Say Y here to enable MMC device speed limiting. Used to test and
> + simulate the behavior of the system when confronted with a slow MMC.
> +
> + Enables max_read_speed, max_write_speed and cache_size attributes to
> + control the write or read maximum KB/second speed behaviors.
> +
> + If unsure, say N here.
> +
> +config MMC_SIMULATE_MAX_READ_SPEED
> + int "KB/second read speed limit per block device"
> + depends on MMC_BLOCK
> + depends on MMC_SIMULATE_MAX_SPEED
> + default 0
> + help
> + Number of KB/second reads allowed to the block device. Used to
> + test and simulate the behavior of the system when confronted with
> + a slow MMC. Set this value if it is required that the simulation
> + starts at boot time.
> +
> + Value can be overridden at runtime with the max_read_speed attribute.
> +
> + If unsure, say 0 here (no speed limit)
> +
> +config MMC_SIMULATE_MAX_WRITE_SPEED
> + int "KB/second write speed limit per block device"
> + depends on MMC_BLOCK
> + depends on MMC_SIMULATE_MAX_SPEED
> + default 0
> + help
> + Number of KB/second writes allowed to the block device. Used to
> + test and simulate the behavior of the system when confronted with
> + a slow MMC. Set this value if it is required that the simulation
> + starts at boot time.
> +
> + Value can be overridden at runtime with the max_write_speed attribute.
> +
> + If unsure, say 0 here (no speed limit)
> +
> +config MMC_SIMULATE_MAX_SPEED_CACHE_SIZE
> + int "MB of memory or SLC cache"
> + depends on MMC_BLOCK
> + depends on MMC_SIMULATE_MAX_SPEED
> + default 4
> + help
> + Number of MB of high speed memory or SLC cache expected on the
> + eMMC device. Used to help simulate the write-back behavior more
> + accurately. Set this value if it is required that the simulation
> + starts at boot time.
> +
> + Value can be overridden at runtime with the cache_size attribute.
> +
> + If unsure, say 4 here
> diff --git a/drivers/mmc/card/block.c b/drivers/mmc/card/block.c
> index fe207e5..7642673 100644
> --- a/drivers/mmc/card/block.c
> +++ b/drivers/mmc/card/block.c
> @@ -286,6 +286,241 @@ out:
> return ret;
> }
>
> +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
> +
> +/*
> + * helper macros and expectations:
> + * size - unsigned long number of bytes
> + * jiffies - unsigned long HZ timestamp difference
> + * speed - unsigned KB/s transfer rate
> + */
> +#define size_and_speed_to_jiffies(size, speed) \
> + ((size) * HZ / (speed) / 1024UL)
> +#define jiffies_and_speed_to_size(jiffies, speed) \
> + (((speed) * (jiffies) * 1024UL) / HZ)
> +#define jiffies_and_size_to_speed(jiffies, size) \
> + ((size) * HZ / (jiffies) / 1024UL)
> +
> +/* Limits to report warning */
> +/* jiffies_and_size_to_speed(10*HZ, queue_max_hw_sectors(q) * 512UL) ~ 25 */
> +#define MIN_SPEED(q) 250 /* 10 times faster than a floppy disk */
> +#define MAX_SPEED(q) jiffies_and_size_to_speed(1, queue_max_sectors(q) * 512UL)
> +
> +#define speed_valid(speed) ((speed) > 0)
> +
> +static const char off[] = "off\n";
> +
> +static int max_speed_show(int speed, char *buf)
> +{
> + if (speed)
> + return scnprintf(buf, PAGE_SIZE, "%uKB/s\n", speed);
> + else
> + return scnprintf(buf, PAGE_SIZE, off);
> +}
> +
> +static int max_speed_store(const char *buf, struct request_queue *q)
> +{
> + unsigned int limit, set = 0;
> +
> + if (!strncasecmp(off, buf, sizeof(off) - 2))
> + return set;
> + if (kstrtouint(buf, 0, &set) || (set > INT_MAX))
> + return -EINVAL;
> + if (set == 0)
> + return set;
> + limit = MAX_SPEED(q);
> + if (set > limit)
> + pr_warn("max speed %u ineffective above %u\n", set, limit);
> + limit = MIN_SPEED(q);
> + if (set < limit)
> + pr_warn("max speed %u painful below %u\n", set, limit);
> + return set;
> +}
> +
> +static ssize_t max_write_speed_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
> + int ret = max_speed_show(atomic_read(&md->queue.max_write_speed), buf);
> +
> + mmc_blk_put(md);
> + return ret;
> +}
> +
> +static ssize_t max_write_speed_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
> + int set = max_speed_store(buf, md->queue.queue);
> +
> + if (set < 0) {
> + mmc_blk_put(md);
> + return set;
> + }
> +
> + atomic_set(&md->queue.max_write_speed, set);
> + mmc_blk_put(md);
> + return count;
> +}
> +
> +static const DEVICE_ATTR(max_write_speed, S_IRUGO | S_IWUSR,
> + max_write_speed_show, max_write_speed_store);
> +
> +static ssize_t max_read_speed_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
> + int ret = max_speed_show(atomic_read(&md->queue.max_read_speed), buf);
> +
> + mmc_blk_put(md);
> + return ret;
> +}
> +
> +static ssize_t max_read_speed_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
> + int set = max_speed_store(buf, md->queue.queue);
> +
> + if (set < 0) {
> + mmc_blk_put(md);
> + return set;
> + }
> +
> + atomic_set(&md->queue.max_read_speed, set);
> + mmc_blk_put(md);
> + return count;
> +}
> +
> +static const DEVICE_ATTR(max_read_speed, S_IRUGO | S_IWUSR,
> + max_read_speed_show, max_read_speed_store);
> +
> +static ssize_t cache_size_show(struct device *dev,
> + struct device_attribute *attr, char *buf)
> +{
> + struct mmc_blk_data *md = mmc_blk_get(dev_to_disk(dev));
> + struct mmc_queue *mq = &md->queue;
> + int cache_size = atomic_read(&mq->cache_size);
> + int ret;
> +
> + if (!cache_size)
> + ret = scnprintf(buf, PAGE_SIZE, off);
> + else {
> + int speed = atomic_read(&mq->max_write_speed);
> +
> + if (!speed_valid(speed))
> + ret = scnprintf(buf, PAGE_SIZE, "%uMB\n", cache_size);
> + else { /* We accept race between cache_jiffies and cache_used */
> + unsigned long size = jiffies_and_speed_to_size(
> + jiffies - mq->cache_jiffies, speed);
> + long used = atomic_long_read(&mq->cache_used);
> +
> + if (size >= used)
> + size = 0;
> + else
> + size = (used - size) * 100 / cache_size
> + / 1024UL / 1024UL;
> +
> + ret = scnprintf(buf, PAGE_SIZE, "%uMB %lu%% used\n",
> + cache_size, size);
> + }
> + }
> +
> + mmc_blk_put(md);
> + return ret;
> +}
> +
> +static ssize_t cache_size_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t count)
> +{
> + struct mmc_blk_data *md;
> + unsigned int set = 0;
> +
> + if (strncasecmp(off, buf, sizeof(off) - 2)
> + && (kstrtouint(buf, 0, &set) || (set > INT_MAX)))
> + return -EINVAL;
> +
> + md = mmc_blk_get(dev_to_disk(dev));
> + atomic_set(&md->queue.cache_size, set);
> + mmc_blk_put(md);
> + return count;
> +}
> +
> +static const DEVICE_ATTR(cache_size, S_IRUGO | S_IWUSR,
> + cache_size_show, cache_size_store);
> +
> +/* correct for write-back */
> +static long mmc_blk_cache_used(struct mmc_queue *mq, unsigned long waitfor)
> +{
> + long used = 0;
> + int speed = atomic_read(&mq->max_write_speed);
> +
> + if (speed_valid(speed)) {
> + unsigned long size = jiffies_and_speed_to_size(
> + waitfor - mq->cache_jiffies, speed);
> + used = atomic_long_read(&mq->cache_used);
> +
> + if (size >= used)
> + used = 0;
> + else
> + used -= size;
> + }
> +
> + atomic_long_set(&mq->cache_used, used);
> + mq->cache_jiffies = waitfor;
> +
> + return used;
> +}
> +
> +static void mmc_blk_simulate_delay(
> + struct mmc_queue *mq,
> + struct request *req,
> + unsigned long waitfor)
> +{
> + int max_speed;
> +
> + if (!req)
> + return;
> +
> + max_speed = (rq_data_dir(req) == READ)
> + ? atomic_read(&mq->max_read_speed)
> + : atomic_read(&mq->max_write_speed);
> + if (speed_valid(max_speed)) {
> + unsigned long bytes = blk_rq_bytes(req);
> +
> + if (rq_data_dir(req) != READ) {
> + int cache_size = atomic_read(&mq->cache_size);
> +
> + if (cache_size) {
> + unsigned long size = cache_size * 1024L * 1024L;
> + long used = mmc_blk_cache_used(mq, waitfor);
> +
> + used += bytes;
> + atomic_long_set(&mq->cache_used, used);
> + bytes = 0;
> + if (used > size)
> + bytes = used - size;
> + }
> + }
> + waitfor += size_and_speed_to_jiffies(bytes, max_speed);
> + if (time_is_after_jiffies(waitfor)) {
> + long msecs = jiffies_to_msecs(waitfor - jiffies);
> +
> + if (likely(msecs > 0))
> + msleep(msecs);
> + }
> + }
> +}
> +
> +#else
> +
> +#define mmc_blk_simulate_delay(mq, req, waitfor)
> +
> +#endif
> +
> static int mmc_blk_open(struct block_device *bdev, fmode_t mode)
> {
> struct mmc_blk_data *md = mmc_blk_get(bdev->bd_disk);
> @@ -1250,6 +1485,23 @@ static int mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req)
> if (ret)
> ret = -EIO;
>
> +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
> + else if (atomic_read(&mq->cache_size)) {
> + long used = mmc_blk_cache_used(mq, jiffies);
> +
> + if (used) {
> + int speed = atomic_read(&mq->max_write_speed);
> +
> + if (speed_valid(speed)) {
> + unsigned long msecs = jiffies_to_msecs(
> + size_and_speed_to_jiffies(
> + used, speed));
> + if (msecs)
> + msleep(msecs);
> + }
> + }
> + }
> +#endif
> blk_end_request_all(req, ret);
>
> return ret ? 0 : 1;
> @@ -1929,6 +2181,9 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
> struct mmc_async_req *areq;
> const u8 packed_nr = 2;
> u8 reqs = 0;
> +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
> + unsigned long waitfor = jiffies;
> +#endif
>
> if (!rqc && !mq->mqrq_prev->req)
> return 0;
> @@ -1979,6 +2234,8 @@ static int mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *rqc)
> */
> mmc_blk_reset_success(md, type);
>
> + mmc_blk_simulate_delay(mq, rqc, waitfor);
> +
> if (mmc_packed_cmd(mq_rq->cmd_type)) {
> ret = mmc_blk_end_packed_req(mq_rq);
> break;
> @@ -2397,6 +2654,14 @@ static void mmc_blk_remove_req(struct mmc_blk_data *md)
> card->ext_csd.boot_ro_lockable)
> device_remove_file(disk_to_dev(md->disk),
> &md->power_ro_lock);
> +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
> + device_remove_file(disk_to_dev(md->disk),
> + &dev_attr_max_write_speed);
> + device_remove_file(disk_to_dev(md->disk),
> + &dev_attr_max_read_speed);
> + device_remove_file(disk_to_dev(md->disk),
> + &dev_attr_cache_size);
> +#endif
>
> del_gendisk(md->disk);
> }
> @@ -2432,6 +2697,27 @@ static int mmc_add_disk(struct mmc_blk_data *md)
> ret = device_create_file(disk_to_dev(md->disk), &md->force_ro);
> if (ret)
> goto force_ro_fail;
> +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
> + atomic_set(&md->queue.max_write_speed,
> + CONFIG_MMC_SIMULATE_MAX_WRITE_SPEED);
> + ret = device_create_file(disk_to_dev(md->disk),
> + &dev_attr_max_write_speed);
> + if (ret)
> + goto max_write_speed_fail;
> + atomic_set(&md->queue.max_read_speed,
> + CONFIG_MMC_SIMULATE_MAX_READ_SPEED);
> + ret = device_create_file(disk_to_dev(md->disk),
> + &dev_attr_max_read_speed);
> + if (ret)
> + goto max_read_speed_fail;
> + atomic_set(&md->queue.cache_size,
> + CONFIG_MMC_SIMULATE_MAX_SPEED_CACHE_SIZE);
> + atomic_long_set(&md->queue.cache_used, 0);
> + md->queue.cache_jiffies = jiffies;
> + ret = device_create_file(disk_to_dev(md->disk), &dev_attr_cache_size);
> + if (ret)
> + goto cache_size_fail;
> +#endif
>
> if ((md->area_type & MMC_BLK_DATA_AREA_BOOT) &&
> card->ext_csd.boot_ro_lockable) {
> @@ -2456,6 +2742,14 @@ static int mmc_add_disk(struct mmc_blk_data *md)
> return ret;
>
> power_ro_lock_fail:
> +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
> + device_remove_file(disk_to_dev(md->disk), &dev_attr_cache_size);
> +cache_size_fail:
> + device_remove_file(disk_to_dev(md->disk), &dev_attr_max_read_speed);
> +max_read_speed_fail:
> + device_remove_file(disk_to_dev(md->disk), &dev_attr_max_write_speed);
> +max_write_speed_fail:
> +#endif
> device_remove_file(disk_to_dev(md->disk), &md->force_ro);
> force_ro_fail:
> del_gendisk(md->disk);
> diff --git a/drivers/mmc/card/queue.h b/drivers/mmc/card/queue.h
> index 36cddab..d890d88 100644
> --- a/drivers/mmc/card/queue.h
> +++ b/drivers/mmc/card/queue.h
> @@ -58,6 +58,14 @@ struct mmc_queue {
> struct mmc_queue_req mqrq[2];
> struct mmc_queue_req *mqrq_cur;
> struct mmc_queue_req *mqrq_prev;
> +#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
> + atomic_t max_write_speed;
> + atomic_t max_read_speed;
> + atomic_t cache_size;
> + /* i/o tracking */
> + atomic_long_t cache_used;
> + unsigned long cache_jiffies;
> +#endif
> };
>
> extern int mmc_init_queue(struct mmc_queue *, struct mmc_card *, spinlock_t *,
> --
> 2.7.0.rc3.207.g0ac5344
>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] mmc: Add CONFIG_MMC_SIMULATE_MAX_SPEED
2016-03-16 13:03 ` Ulf Hansson
@ 2016-03-16 16:45 ` Mark Salyzyn
0 siblings, 0 replies; 3+ messages in thread
From: Mark Salyzyn @ 2016-03-16 16:45 UTC (permalink / raw)
To: Ulf Hansson
Cc: linux-kernel@vger.kernel.org, Jonathan Corbet, Adrian Hunter,
Yangbo Lu, Tomas Winkler, Andrew Morton, James Bottomley,
Kuninori Morimoto, Grant Grundler, Jon Hunter, Luca Porzio,
Yunpeng Gao, Chuanxiao Dong, linux-doc@vger.kernel.org, linux-mmc
On 03/16/2016 06:03 AM, Ulf Hansson wrote:
> On 22 February 2016 at 18:18, Mark Salyzyn <salyzyn@android.com> wrote:
>> When CONFIG_MMC_SIMULATE_MAX_SPEED is enabled, Expose max_read_speed,
>> max_write_speed and cache_size sysfs controls to simulate a slow
>> eMMC device. The boot default values, should one wish to set this
>> behavior right from kernel start:
>>
>> CONFIG_MMC_SIMULATE_MAX_READ_SPEED
>> CONFIG_MMC_SIMULATE_MAX_WRITE_SPEED
>> CONFIG_MMC_SIMULATE_CACHE_SIZE
>>
>> respectively; and if not defined are 0 (off), 0 (off) and 4 MB
>> also respectively.
> So this changelog doesn't really tell me *why* this feature is nice to
> have. Could you elaborate on this and thus also extend the information
> in the changelog please.
Will do. Why is certainly missing ;-}
Basically we have three choices to determine how a system may behave
when one has an aged out eMMC:
1) wait until we can acquire a device with an old eMMC.
2) increase the temperature on the device and run io activity under a
controlled level until the number of available erase blocks disappear,
or the physical device itself slows.
3) we can adjust the driver to behave in the similar manner, but backed
with a healthy (or rather healthier) eMMC.
#3 is plain just faster and cheaper.
I have one other duty for this driver is to switch out the default
config parameters with module (kernel command line) parameters. Alas I
have been swamped for the past little while.
> Moreover, I have briefly reviewed the code, but I don't want to go
> into the details yet... Instead, what I am trying to understand if
> this is something that useful+specific for the MMC subsystem, or if
> it's something that belongs in the upper generic BLOCK layer. Perhaps
> you can comment on this as well?
A feature much like this can be useful in an upper generic block layer,
in fact I have done so in past lives for spinning media, or RAID
systems, for private/proprietary/development needs. However, each type
of system has a different set of characteristics and tunables to
simulate more accurately their behavior. It is, however, far more
complex to simulate with a device that allows more than one outstanding
command, so it is dead simple to add this into the eMMC driver.
This change starts out with some of the basics, but device cache
behavior is certainly different between this, RAID or spinning media
(eMMC is simpler to emulate). And if/when we feel the need to expand the
simulation to incorporate a limited pool of erase blocks due to aging or
lack of recent fstrim, we certainly will enter device-specific
territory. It will be easier to build in additional precision to the
simulation if we keep this inside the eMMC driver.
Spinning media, for instance, would have its own simulation of drive
head, track and sector position in order to simulate the latencies,
however I have found adding an average latency works well enough in most
scenarios. For RAID, _all_ component drives would need to have their own
mechanical tracking if we wanted to add precision. If I put something
like this in the block layer, I will be signing up for a quagmire if I
was to aid the additional development. Do not get me started on solid
state drives ...
Sadly, I am only passionate about eMMC _today_ since this could work on
any of the 1.6billion devices on the planet right now, and it is a tiny
and KISS cut-in ;-} (merged clean from linux3.4 to current)
>
> Kind regards
> Uffe
>
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2016-03-16 16:45 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-02-22 17:18 [PATCH v2] mmc: Add CONFIG_MMC_SIMULATE_MAX_SPEED Mark Salyzyn
2016-03-16 13:03 ` Ulf Hansson
2016-03-16 16:45 ` Mark Salyzyn
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).