ANDROID: mmc: Add CONFIG_MMC_SIMULATE_MAX_SPEED

When CONFIG_MMC_SIMULATE_MAX_SPEED is enabled, Expose max_read_speed,
max_write_speed and cache_size default module parameters and sysfs
controls to simulate a slow eMMC device. Default values are 0 (off),
0 (off) and 4 MB respectively.

Signed-off-by: Mark Salyzyn <salyzyn@google.com>
Bug: 26976972
Change-Id: I342bfbd8b85f9b790e3f0e1e4e51a900ae07e05d
This commit is contained in:
Mark Salyzyn
2016-01-28 11:12:25 -08:00
committed by Amit Pundir
parent 92f883b0bb
commit 5e09e5ce27
5 changed files with 363 additions and 0 deletions

View File

@@ -32,3 +32,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

View File

@@ -0,0 +1,38 @@
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 and module
default parameters to control the write or read maximum KB/second speed
behaviors.
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.

View File

@@ -95,3 +95,14 @@ config MMC_PARANOID_SD_INIT
work-around for buggy controllers and hardware. Enable
if you are experiencing issues with SD detection.
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.

View File

@@ -257,6 +257,250 @@ out:
return ret;
}
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
static int max_read_speed, max_write_speed, cache_size = 4;
module_param(max_read_speed, int, S_IRUSR | S_IRGRP);
MODULE_PARM_DESC(max_read_speed, "maximum KB/s read speed 0=off");
module_param(max_write_speed, int, S_IRUSR | S_IRGRP);
MODULE_PARM_DESC(max_write_speed, "maximum KB/s write speed 0=off");
module_param(cache_size, int, S_IRUSR | S_IRGRP);
MODULE_PARM_DESC(cache_size, "MB high speed memory or SLC cache");
/*
* 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);
@@ -1336,6 +1580,23 @@ static void mmc_blk_issue_flush(struct mmc_queue *mq, struct request *req)
int ret = 0;
ret = mmc_flush_cache(card);
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
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 ? BLK_STS_IOERR : BLK_STS_OK);
}
@@ -1736,6 +1997,9 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req)
struct mmc_async_req *new_areq;
struct mmc_async_req *old_areq;
bool req_pending = true;
#ifdef CONFIG_MMC_SIMULATE_MAX_SPEED
unsigned long waitfor = jiffies;
#endif
if (new_req) {
mqrq_cur = req_to_mmc_queue_req(new_req);
@@ -1792,6 +2056,8 @@ static void mmc_blk_issue_rw_rq(struct mmc_queue *mq, struct request *new_req)
*/
mmc_blk_reset_success(md, type);
mmc_blk_simulate_delay(mq, rqc, waitfor);
req_pending = blk_end_request(old_req, BLK_STS_OK,
brq->data.bytes_xfered);
/*
@@ -2178,6 +2444,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);
}
@@ -2212,6 +2486,24 @@ 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, 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, 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, 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) {
@@ -2236,6 +2528,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);

View File

@@ -70,6 +70,14 @@ struct mmc_queue {
* associated mmc_queue_req data.
*/
int qcnt;
#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 *,