#include <linux/kernel.h>
 #include <linux/ktime.h>
 #include <linux/list.h>
+#include <linux/math64.h>
 #include <linux/sizes.h>
 #include <linux/workqueue.h>
 #include "ctree.h"
  * @override: override the current timer
  *
  * Discards are issued by a delayed workqueue item.  @override is used to
- * update the current delay as the baseline delay interview is reevaluated
- * on transaction commit.  This is also maxed with any other rate limit.
+ * update the current delay as the baseline delay interval is reevaluated on
+ * transaction commit.  This is also maxed with any other rate limit.
  */
 void btrfs_discard_schedule_work(struct btrfs_discard_ctl *discard_ctl,
                                 bool override)
        block_group = find_next_block_group(discard_ctl, now);
        if (block_group) {
                unsigned long delay = discard_ctl->delay;
+               u32 kbps_limit = READ_ONCE(discard_ctl->kbps_limit);
+
+               /*
+                * A single delayed workqueue item is responsible for
+                * discarding, so we can manage the bytes rate limit by keeping
+                * track of the previous discard.
+                */
+               if (kbps_limit && discard_ctl->prev_discard) {
+                       u64 bps_limit = ((u64)kbps_limit) * SZ_1K;
+                       u64 bps_delay = div64_u64(discard_ctl->prev_discard *
+                                                 MSEC_PER_SEC, bps_limit);
+
+                       delay = max(delay, msecs_to_jiffies(bps_delay));
+               }
 
                /*
                 * This timeout is to hopefully prevent immediate discarding
                                       btrfs_block_group_end(block_group),
                                       0, true);
 
+       discard_ctl->prev_discard = trimmed;
+
        /* Determine next steps for a block_group */
        if (block_group->discard_cursor >= btrfs_block_group_end(block_group)) {
                if (discard_state == BTRFS_DISCARD_BITMAPS) {
        for (i = 0; i < BTRFS_NR_DISCARD_LISTS; i++)
                INIT_LIST_HEAD(&discard_ctl->discard_list[i]);
 
+       discard_ctl->prev_discard = 0;
        atomic_set(&discard_ctl->discardable_extents, 0);
        atomic64_set(&discard_ctl->discardable_bytes, 0);
        discard_ctl->delay = BTRFS_DISCARD_MAX_DELAY_MSEC;
        discard_ctl->iops_limit = BTRFS_DISCARD_MAX_IOPS;
+       discard_ctl->kbps_limit = 0;
 }
 
 void btrfs_discard_cleanup(struct btrfs_fs_info *fs_info)
 
 BTRFS_ATTR_RW(discard, iops_limit, btrfs_discard_iops_limit_show,
              btrfs_discard_iops_limit_store);
 
+static ssize_t btrfs_discard_kbps_limit_show(struct kobject *kobj,
+                                            struct kobj_attribute *a,
+                                            char *buf)
+{
+       struct btrfs_fs_info *fs_info = discard_to_fs_info(kobj);
+
+       return snprintf(buf, PAGE_SIZE, "%u\n",
+                       READ_ONCE(fs_info->discard_ctl.kbps_limit));
+}
+
+static ssize_t btrfs_discard_kbps_limit_store(struct kobject *kobj,
+                                             struct kobj_attribute *a,
+                                             const char *buf, size_t len)
+{
+       struct btrfs_fs_info *fs_info = discard_to_fs_info(kobj);
+       struct btrfs_discard_ctl *discard_ctl = &fs_info->discard_ctl;
+       u32 kbps_limit;
+       int ret;
+
+       ret = kstrtou32(buf, 10, &kbps_limit);
+       if (ret)
+               return -EINVAL;
+
+       WRITE_ONCE(discard_ctl->kbps_limit, kbps_limit);
+
+       return len;
+}
+BTRFS_ATTR_RW(discard, kbps_limit, btrfs_discard_kbps_limit_show,
+             btrfs_discard_kbps_limit_store);
+
 static const struct attribute *discard_debug_attrs[] = {
        BTRFS_ATTR_PTR(discard, discardable_bytes),
        BTRFS_ATTR_PTR(discard, discardable_extents),
        BTRFS_ATTR_PTR(discard, iops_limit),
+       BTRFS_ATTR_PTR(discard, kbps_limit),
        NULL,
 };