Re: [PATCH v2] mmc: Add CONFIG_MMC_SIMULATE_MAX_SPEED

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 22 February 2016 at 18:18, Mark Salyzyn <salyzyn@xxxxxxxxxxx> 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@xxxxxxxxxxx>
> ---
> 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
>
--
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



[Index of Archives]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux