diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index f44eb0a04afd..ca722ecbd3de 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -184,6 +184,11 @@ struct fsxattr {
 #define BLKSECDISCARD _IO(0x12,125)
 #define BLKROTATIONAL _IO(0x12,126)
 #define BLKZEROOUT _IO(0x12,127)
+#define BLKTRACESETUP_EXT _IOWR(0x12,128,struct blk_user_trace_setup_ext)
+#define BLKTRACESTART_EXT _IO(0x12,129)
+#define BLKTRACESTOP_EXT _IO(0x12,130)
+#define BLKTRACETEARDOWN_EXT _IO(0x12,131)
+
 /*
  * A jump here: 130-131 are reserved for zoned block devices
  * (see uapi/linux/blkzoned.h)
diff --git a/kernel/trace/blktrace.c b/kernel/trace/blktrace.c
index f707ebde0062..3bd56b741379 100644
--- a/kernel/trace/blktrace.c
+++ b/kernel/trace/blktrace.c
@@ -609,6 +609,17 @@ static void get_probe_ref(void)
 	mutex_unlock(&blk_probe_mutex);
 }
 
+static void blk_trace_free_ext(struct blk_trace_ext *bt)
+{
+	debugfs_remove(bt->msg_file);
+	debugfs_remove(bt->dropped_file);
+	relay_close(bt->rchan);
+	debugfs_remove(bt->dir);
+	free_percpu(bt->sequence);
+	free_percpu(bt->msg_data);
+	kfree(bt);
+}
+
 static void put_probe_ref(void)
 {
 	mutex_lock(&blk_probe_mutex);
@@ -624,6 +635,13 @@ static void blk_trace_cleanup(struct blk_trace *bt)
 	put_probe_ref();
 }
 
+static void blk_trace_cleanup_ext(struct blk_trace_ext *bt)
+{
+	synchronize_rcu();
+	blk_trace_free_ext(bt);
+	put_probe_ref();
+}
+
 static int __blk_trace_remove(struct request_queue *q)
 {
 	struct blk_trace *bt;
@@ -639,12 +657,28 @@ static int __blk_trace_remove(struct request_queue *q)
 	return 0;
 }
 
+static int __blk_trace_remove_ext(struct request_queue *q)
+{
+	struct blk_trace_ext *bt;
+
+	bt = xchg(&q->blk_trace_ext, NULL);
+	if (!bt)
+		return -EINVAL;
+
+	if (bt->trace_state != Blktrace_running)
+		blk_trace_cleanup_ext(bt);
+
+	return 0;
+}
+
 int blk_trace_remove(struct request_queue *q)
 {
 	int ret;
 
 	mutex_lock(&q->debugfs_mutex);
 	ret = __blk_trace_remove(q);
+	if (ret)
+		__blk_trace_remove_ext(q);
 	mutex_unlock(&q->debugfs_mutex);
 
 	return ret;
@@ -662,6 +696,17 @@ static ssize_t blk_dropped_read(struct file *filp, char __user *buffer,
 	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
 }
 
+static ssize_t blk_dropped_read_ext(struct file *filp, char __user *buffer,
+				size_t count, loff_t *ppos)
+{
+	struct blk_trace_ext *bt = filp->private_data;
+	char buf[16];
+
+	snprintf(buf, sizeof(buf), "%u\n", atomic_read(&bt->dropped));
+
+	return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf));
+}
+
 static const struct file_operations blk_dropped_fops = {
 	.owner =	THIS_MODULE,
 	.open =		simple_open,
@@ -669,6 +714,13 @@ static const struct file_operations blk_dropped_fops = {
 	.llseek =	default_llseek,
 };
 
+static const struct file_operations blk_dropped_fops_ext = {
+	.owner =	THIS_MODULE,
+	.open =		simple_open,
+	.read =		blk_dropped_read_ext,
+	.llseek =	default_llseek,
+};
+
 static ssize_t blk_msg_write(struct file *filp, const char __user *buffer,
 				size_t count, loff_t *ppos)
 {
@@ -689,6 +741,26 @@ static ssize_t blk_msg_write(struct file *filp, const char __user *buffer,
 	return count;
 }
 
+static ssize_t blk_msg_write_ext(struct file *filp, const char __user *buffer,
+				size_t count, loff_t *ppos)
+{
+	char *msg;
+	struct blk_trace_ext *bt;
+
+	if (count >= BLK_TN_MAX_MSG)
+		return -EINVAL;
+
+	msg = memdup_user_nul(buffer, count);
+       if (IS_ERR(msg))
+		return PTR_ERR(msg);
+
+	bt = filp->private_data;
+	__trace_note_message_ext(bt, NULL, "%s", msg);
+	kfree(msg);
+
+	return count;
+}
+
 static const struct file_operations blk_msg_fops = {
 	.owner =	THIS_MODULE,
 	.open =		simple_open,
@@ -696,6 +768,13 @@ static const struct file_operations blk_msg_fops = {
 	.llseek =	noop_llseek,
 };
 
+static const struct file_operations blk_msg_fops_ext = {
+	.owner =	THIS_MODULE,
+	.open =		simple_open,
+	.write =	blk_msg_write_ext,
+	.llseek =	noop_llseek,
+};
+
 /*
  * Keep track of how many times we encountered a full subbuffer, to aid
  * the user space app in telling how many lost events there were.
@@ -730,12 +809,31 @@ static struct dentry *blk_create_buf_file_callback(const char *filename,
 					&relay_file_operations);
 }
 
+static int blk_subbuf_start_callback_ext(struct rchan_buf *buf, void *subbuf,
+				     void *prev_subbuf, size_t prev_padding)
+{
+	struct blk_trace_ext *bt;
+
+	if (!relay_buf_full(buf))
+		return 1;
+
+	bt = buf->chan->private_data;
+	atomic_inc(&bt->dropped);
+	return 0;
+}
+
 static const struct rchan_callbacks blk_relay_callbacks = {
 	.subbuf_start		= blk_subbuf_start_callback,
 	.create_buf_file	= blk_create_buf_file_callback,
 	.remove_buf_file	= blk_remove_buf_file_callback,
 };
 
+static struct rchan_callbacks blk_relay_callbacks_ext = {
+	.subbuf_start		= blk_subbuf_start_callback_ext,
+	.create_buf_file	= blk_create_buf_file_callback,
+	.remove_buf_file	= blk_remove_buf_file_callback,
+};
+
 static void blk_trace_setup_lba(struct blk_trace *bt,
 				struct block_device *bdev)
 {
@@ -748,6 +846,18 @@ static void blk_trace_setup_lba(struct blk_trace *bt,
 	}
 }
 
+static void blk_trace_setup_lba_ext(struct blk_trace_ext *bt,
+				struct block_device *bdev)
+{
+	if (bdev) {
+		bt->start_lba = bdev->bd_start_sect;
+		bt->end_lba = bdev->bd_start_sect + bdev_nr_sectors(bdev);
+	} else {
+		bt->start_lba = 0;
+		bt->end_lba = -1ULL;
+	}
+}
+
 /*
  * Setup everything required to start tracing
  */
@@ -858,6 +968,106 @@ static int do_blk_trace_setup(struct request_queue *q, char *name, dev_t dev,
 	return ret;
 }
 
+static int do_blk_trace_setup_ext(struct request_queue *q, char *name, dev_t dev,
+			      struct block_device *bdev,
+			      struct blk_user_trace_setup_ext *buts)
+{
+	struct blk_trace_ext *bt = NULL;
+	struct dentry *dir = NULL;
+	int ret;
+
+	if (q->blk_trace) {
+		pr_err("queue is already associated with legecy trace\n");
+		return -EINVAL;
+	}
+	if (!buts->buf_size || !buts->buf_nr)
+		return -EINVAL;
+
+	if (!blk_debugfs_root)
+		return -ENOENT;
+
+	strncpy(buts->name, name, BLKTRACE_BDEV_SIZE);
+	buts->name[BLKTRACE_BDEV_SIZE - 1] = '\0';
+
+	/*
+	 * some device names have larger paths - convert the slashes
+	 * to underscores for this to work as expected
+	 */
+	strreplace(buts->name, '/', '_');
+
+	bt = kzalloc(sizeof(*bt), GFP_KERNEL);
+	if (!bt)
+		return -ENOMEM;
+
+	ret = -ENOMEM;
+	bt->sequence = alloc_percpu(unsigned long);
+	if (!bt->sequence)
+		goto err;
+
+	bt->msg_data = __alloc_percpu(BLK_TN_MAX_MSG, __alignof__(char));
+	if (!bt->msg_data)
+		goto err;
+
+	ret = -ENOENT;
+
+	dir = debugfs_lookup(buts->name, blk_debugfs_root);
+	if (!dir)
+		bt->dir = dir = debugfs_create_dir(buts->name, blk_debugfs_root);
+	if (!dir)
+		goto err;
+
+	bt->dev = dev;
+	atomic_set(&bt->dropped, 0);
+	INIT_LIST_HEAD(&bt->running_ext_list);
+
+	ret = -EIO;
+	bt->dropped_file = debugfs_create_file("dropped", 0444, dir, bt,
+					       &blk_dropped_fops_ext);
+	if (!bt->dropped_file)
+		goto err;
+
+	bt->msg_file = debugfs_create_file("msg", 0222, dir, bt, &blk_msg_fops_ext);
+	if (!bt->msg_file)
+		goto err;
+
+	bt->rchan = relay_open("trace", dir, buts->buf_size,
+				buts->buf_nr, &blk_relay_callbacks_ext, bt);
+	if (!bt->rchan)
+		goto err;
+
+	bt->act_mask = buts->act_mask;
+	if (!bt->act_mask)
+		bt->act_mask = (u64) -1ULL;
+
+	bt->prio_mask = buts->prio_mask;
+
+	blk_trace_setup_lba_ext(bt, bdev);
+
+	/* overwrite with user settings */
+	if (buts->start_lba)
+		bt->start_lba = buts->start_lba;
+	if (buts->end_lba)
+		bt->end_lba = buts->end_lba;
+
+	bt->pid = buts->pid;
+	bt->trace_state = Blktrace_setup;
+
+	ret = -EBUSY;
+	if (cmpxchg(&q->blk_trace_ext, NULL, bt))
+		goto err;
+
+	get_probe_ref();
+
+	ret = 0;
+err:
+	if (dir && !bt->dir)
+		dput(dir);
+	if (ret)
+		blk_trace_free_ext(bt);
+	return ret;
+}
+
+
 static int __blk_trace_setup(struct request_queue *q, char *name, dev_t dev,
 			     struct block_device *bdev, char __user *arg)
 {
@@ -879,6 +1089,27 @@ static int __blk_trace_setup(struct request_queue *q, char *name, dev_t dev,
 	return 0;
 }
 
+static int __blk_trace_setup_ext(struct request_queue *q, char *name, dev_t dev,
+			     struct block_device *bdev, char __user *arg)
+{
+	struct blk_user_trace_setup_ext buts;
+	int ret;
+
+	ret = copy_from_user(&buts, arg, sizeof(buts));
+	if (ret)
+		return -EFAULT;
+
+	ret = do_blk_trace_setup_ext(q, name, dev, bdev, &buts);
+	if (ret)
+		return ret;
+
+	if (copy_to_user(arg, &buts, sizeof(buts))) {
+		__blk_trace_remove_ext(q);
+		return -EFAULT;
+	}
+	return 0;
+}
+
 int blk_trace_setup(struct request_queue *q, char *name, dev_t dev,
 		    struct block_device *bdev,
 		    char __user *arg)
@@ -969,12 +1200,57 @@ static int __blk_trace_startstop(struct request_queue *q, int start)
 	return ret;
 }
 
+static int __blk_trace_startstop_ext(struct request_queue *q, int start)
+{
+	int ret;
+	struct blk_trace_ext *bt;
+
+	bt = rcu_dereference_protected(q->blk_trace_ext,
+				       lockdep_is_held(&q->debugfs_mutex));
+	if (bt == NULL)
+		return -EINVAL;
+
+	/*
+	 * For starting a trace, we can transition from a setup or stopped
+	 * trace. For stopping a trace, the state must be running
+	 */
+	ret = -EINVAL;
+	if (start) {
+		if (bt->trace_state == Blktrace_setup ||
+		    bt->trace_state == Blktrace_stopped) {
+			blktrace_seq++;
+			smp_mb();
+			bt->trace_state = Blktrace_running;
+			spin_lock_irq(&running_trace_ext_lock);
+			list_add(&bt->running_ext_list,
+					&running_trace_ext_list);
+			spin_unlock_irq(&running_trace_ext_lock);
+
+			trace_note_time_ext(bt);
+			ret = 0;
+		}
+	} else {
+		if (bt->trace_state == Blktrace_running) {
+			bt->trace_state = Blktrace_stopped;
+			spin_lock_irq(&running_trace_ext_lock);
+			list_del_init(&bt->running_ext_list);
+			spin_unlock_irq(&running_trace_ext_lock);
+			relay_flush(bt->rchan);
+			ret = 0;
+		}
+	}
+
+	return ret;
+}
+
 int blk_trace_startstop(struct request_queue *q, int start)
 {
 	int ret;
 
 	mutex_lock(&q->debugfs_mutex);
 	ret = __blk_trace_startstop(q, start);
+	if (ret)
+		ret = __blk_trace_startstop_ext(q, start);
 	mutex_unlock(&q->debugfs_mutex);
 
 	return ret;
@@ -1011,6 +1287,10 @@ int blk_trace_ioctl(struct block_device *bdev, unsigned cmd, char __user *arg)
 		bdevname(bdev, b);
 		ret = __blk_trace_setup(q, b, bdev->bd_dev, bdev, arg);
 		break;
+	case BLKTRACESETUP_EXT:
+		bdevname(bdev, b);
+		ret = __blk_trace_setup_ext(q, b, bdev->bd_dev, bdev, arg);
+		break;
 #if defined(CONFIG_COMPAT) && defined(CONFIG_X86_64)
 	case BLKTRACESETUP32:
 		bdevname(bdev, b);
@@ -1023,9 +1303,18 @@ int blk_trace_ioctl(struct block_device *bdev, unsigned cmd, char __user *arg)
 	case BLKTRACESTOP:
 		ret = __blk_trace_startstop(q, start);
 		break;
+	case BLKTRACESTART_EXT:
+		start = 1;
+		/* fallthrough */
+	case BLKTRACESTOP_EXT:
+		ret = __blk_trace_startstop_ext(q, start);
+		break;
 	case BLKTRACETEARDOWN:
 		ret = __blk_trace_remove(q);
 		break;
+	case BLKTRACETEARDOWN_EXT:
+		ret = __blk_trace_remove_ext(q);
+		break;
 	default:
 		ret = -ENOTTY;
 		break;
@@ -1049,6 +1338,11 @@ void blk_trace_shutdown(struct request_queue *q)
 		__blk_trace_remove(q);
 	}
 
+	if (rcu_dereference_protected(q->blk_trace_ext,
+				      lockdep_is_held(&q->debugfs_mutex))) {
+		__blk_trace_startstop_ext(q, 0);
+		__blk_trace_remove_ext(q);
+	}
 	mutex_unlock(&q->debugfs_mutex);
 }
 
