diff --git a/Documentation/btrfs-subvolume.rst b/Documentation/btrfs-subvolume.rst
index eb116c4bdc95..8eaab5872d70 100644
--- a/Documentation/btrfs-subvolume.rst
+++ b/Documentation/btrfs-subvolume.rst
@@ -130,7 +130,7 @@ list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
 
         For every subvolume the following information is shown by default:
 
-        ID *ID* gen *generation* top level *parent_ID* path *path*
+        ID *ID* gen *generation* top level *parent_ID* [size (referenced) *rfer* size (exclusive) *excl*] path *path*
 
         where *ID* is subvolume's (root)id, *generation* is an internal counter which is
         updated every transaction, *parent_ID* is the same as the parent subvolume's id,
@@ -138,6 +138,11 @@ list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
         The subvolume's ID may be used by the subvolume set-default command,
         or at mount time via the *subvolid=* option.
 
+	The sizes of a subvolume will only be outputted if qgroup is enabled,
+	as that's the only way to get accurate size of a subvolume.
+	The referenced size is the total bytes of every extent referred by that subvolume.
+	While the exclusive size is total bytes that is exclusive to that subvolume.
+
         ``Options``
 
         Path filtering:
@@ -163,6 +168,11 @@ list [options] [-G [\+|-]<value>] [-C [+|-]<value>] [--sort=rootid,gen,ogen,path
         -q
                 print the parent UUID of the subvolume
                 (*parent* here means subvolume of which this subvolume is a snapshot).
+
+	-Q
+		always print the qgroup sizes of the subvolume
+		If qgroup is not enabled, a warning would be outputted and all
+		qgroup sizes would be zero.
         -R
                 print the UUID of the sent subvolume, where the subvolume is the result of a receive operation.
 
diff --git a/cmds/subvolume-list.c b/cmds/subvolume-list.c
index e05f7228b889..28826571ab97 100644
--- a/cmds/subvolume-list.c
+++ b/cmds/subvolume-list.c
@@ -66,6 +66,7 @@ static const char * const cmd_subvolume_list_usage[] = {
 	OPTLINE("-g", "print the generation of the subvolume"),
 	OPTLINE("-u", "print the uuid of subvolumes (and snapshots)"),
 	OPTLINE("-q", "print the parent uuid of the snapshots"),
+	OPTLINE("-Q", "always print the qgroup size of each subvolume (instead of auto detect)"),
 	OPTLINE("-R", "print the uuid of the received snapshots"),
 	"",
 	"Type filtering:",
@@ -131,6 +132,10 @@ struct root_attr {
 	/* creation time of this root in sec*/
 	time_t otime;
 
+	/* Qgroup accounting. */
+	u64 rfer;
+	u64 excl;
+
 	u8 uuid[BTRFS_UUID_SIZE];
 	u8 puuid[BTRFS_UUID_SIZE];
 	u8 ruuid[BTRFS_UUID_SIZE];
@@ -195,6 +200,8 @@ enum btrfs_list_column_enum {
 	BTRFS_LIST_PUUID,
 	BTRFS_LIST_RUUID,
 	BTRFS_LIST_UUID,
+	BTRFS_LIST_QGROUP_RFER,
+	BTRFS_LIST_QGROUP_EXCL,
 	BTRFS_LIST_PATH,
 	BTRFS_LIST_ALL,
 };
@@ -286,6 +293,16 @@ static struct {
 		.column_name	= "UUID",
 		.need_print	= 0,
 	},
+	{
+		.name		= "rfer",
+		.column_name	= "RFER",
+		.need_print	= 0,
+	},
+	{
+		.name		= "excl",
+		.column_name	= "EXCL",
+		.need_print	= 0,
+	},
 	{
 		.name		= "path",
 		.column_name	= "Path",
@@ -567,6 +584,10 @@ static void update_root_attr(struct root_attr *found,
 		found->ogen = new->root_offset;
 	if (new->otime)
 		found->otime = new->otime;
+	if (new->rfer)
+		found->rfer = new->rfer;
+	if (new->excl)
+		found->excl = new->excl;
 	if (new->uuid[0])
 		memcpy(&found->uuid, new->uuid, BTRFS_UUID_SIZE);
 	if (new->puuid[0])
@@ -574,6 +595,7 @@ static void update_root_attr(struct root_attr *found,
 	if (new->ruuid[0])
 		memcpy(&found->ruuid, new->ruuid, BTRFS_UUID_SIZE);
 
+
 }
 
 static int update_root(struct rb_root *root_lookup,
@@ -789,6 +811,88 @@ static int lookup_ino_path(int fd, struct root_info *ri)
 	return 0;
 }
 
+static int fill_qgroup_attrs(int fd, struct rb_root *root_lookup)
+{
+	struct btrfs_ioctl_search_args args = {
+		.key = {
+			.tree_id = BTRFS_QUOTA_TREE_OBJECTID,
+			.max_type = BTRFS_QGROUP_INFO_KEY,
+			.min_type = BTRFS_QGROUP_INFO_KEY,
+			.max_objectid = (u64)-1,
+			.max_offset = (u64)-1,
+			.max_transid = (u64)-1,
+			.nr_items = 4096,
+		},
+	};
+	struct btrfs_ioctl_search_key *sk = &args.key;
+	int ret;
+
+	while (1) {
+		unsigned long off = 0;
+
+		ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args);
+		if (ret < 0) {
+			if (errno == ENOENT)
+				ret = -ENOTTY;
+			else
+				ret = -errno;
+			goto out;
+		}
+		/* the ioctl returns the number of item it found in nr_items */
+		if (sk->nr_items == 0)
+			goto out;
+
+		for (int i = 0; i < sk->nr_items; i++) {
+			struct root_attr attr = { 0 };
+			struct btrfs_ioctl_search_header *sh;
+			struct btrfs_qgroup_info_item *info;
+			struct btrfs_key key;
+
+			sh = (struct btrfs_ioctl_search_header *)(args.buf + off);
+			off += sizeof(*sh);
+
+			key.objectid = btrfs_search_header_objectid(sh);
+			key.type = btrfs_search_header_type(sh);
+			key.offset = btrfs_search_header_offset(sh);
+
+			/* Skip unrealted items and higher level qgroups. */
+			if (key.type != BTRFS_QGROUP_INFO_KEY ||
+			    btrfs_qgroup_level(key.offset))
+				goto next;
+
+			info = (struct btrfs_qgroup_info_item *)(args.buf + off);
+
+			attr.root_id = key.offset;
+			attr.rfer = btrfs_stack_qgroup_info_rfer(info);
+			attr.excl = btrfs_stack_qgroup_info_excl(info);
+			ret = add_root(root_lookup, &attr);
+			if (ret < 0)
+				break;
+next:
+			off += btrfs_search_header_len(sh);
+
+			/*
+			 * record the mins in sk so we can make sure the
+			 * next search doesn't repeat this root
+			 */
+			sk->min_type = key.type;
+			sk->min_offset = key.offset;
+			sk->min_objectid = key.objectid;
+		}
+		sk->nr_items = 4096;
+		/*
+		 * this iteration is done, step forward one qgroup for the next
+		 * ioctl
+		 */
+		if (sk->min_offset < (u64)-1)
+			sk->min_offset++;
+		else
+			break;
+	}
+out:
+	return ret;
+}
+
 static int list_subvol_search(int fd, struct rb_root *root_lookup)
 {
 	int ret;
@@ -1157,6 +1261,12 @@ static void print_subvolume_column(struct root_info *subv,
 			uuid_unparse(attrs->ruuid, uuidparse);
 		pr_verbose(LOG_DEFAULT, "%-36s", uuidparse);
 		break;
+	case BTRFS_LIST_QGROUP_RFER:
+		pr_verbose(LOG_DEFAULT, "%llu", attrs->rfer);
+		break;
+	case BTRFS_LIST_QGROUP_EXCL:
+		pr_verbose(LOG_DEFAULT, "%llu", attrs->excl);
+		break;
 	case BTRFS_LIST_PATH:
 		BUG_ON(!attrs->full_path);
 		pr_verbose(LOG_DEFAULT, "%s", attrs->full_path);
@@ -1280,6 +1390,12 @@ static void print_subvol_json_key(struct format_ctx *fctx,
 	case BTRFS_LIST_UUID:
 		fmt_print(fctx, column_name, attrs->uuid);
 		break;
+	case BTRFS_LIST_QGROUP_RFER:
+		fmt_print(fctx, column_name, attrs->rfer);
+		break;
+	case BTRFS_LIST_QGROUP_EXCL:
+		fmt_print(fctx, column_name, attrs->excl);
+		break;
 	case BTRFS_LIST_PUUID:
 		fmt_print(fctx, column_name, attrs->puuid);
 		break;
@@ -1363,6 +1479,7 @@ static int btrfs_list_subvols(int fd, struct rb_root *root_lookup)
 {
 	int ret;
 	struct rb_node *n;
+	bool qgroup_enabled = false;
 
 	ret = list_subvol_search(fd, root_lookup);
 	if (ret) {
@@ -1370,6 +1487,17 @@ static int btrfs_list_subvols(int fd, struct rb_root *root_lookup)
 		return ret;
 	}
 
+	ret = fill_qgroup_attrs(fd, root_lookup);
+	/*
+	 * Qgroup is not enabled but the user is trying to output qgroup info
+	 * explicitly, give a warning at least.
+	 */
+	if (ret < 0 && (btrfs_list_columns[BTRFS_LIST_QGROUP_RFER].need_print ||
+			btrfs_list_columns[BTRFS_LIST_QGROUP_EXCL].need_print)) {
+		errno = -ret;
+		warning("qgroup output is not available: %m");
+	}
+
 	/*
 	 * now we have an rbtree full of root_info objects, but we need to fill
 	 * in their path names within the subvol that is referencing each one.
@@ -1379,11 +1507,17 @@ static int btrfs_list_subvols(int fd, struct rb_root *root_lookup)
 		struct root_info *entry;
 
 		entry = to_root_info(n);
+		if (entry->attrs.rfer && entry->attrs.excl)
+			qgroup_enabled = true;
 		ret = lookup_ino_path(fd, entry);
 		if (ret && ret != -ENOENT)
 			return ret;
 		n = rb_next(n);
 	}
+	if (qgroup_enabled) {
+		btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_RFER);
+		btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_EXCL);
+	}
 
 	return 0;
 }
@@ -1568,7 +1702,7 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
 		};
 
 		c = getopt_long(argc, argv,
-				    "acdgopqsurRG:C:t", long_options, NULL);
+				    "acdgopqQsurRG:C:t", long_options, NULL);
 		if (c < 0)
 			break;
 
@@ -1609,6 +1743,10 @@ static int cmd_subvolume_list(const struct cmd_struct *cmd, int argc, char **arg
 		case 'q':
 			btrfs_list_setup_print_column(BTRFS_LIST_PUUID);
 			break;
+		case 'Q':
+			btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_RFER);
+			btrfs_list_setup_print_column(BTRFS_LIST_QGROUP_EXCL);
+			break;
 		case 'R':
 			btrfs_list_setup_print_column(BTRFS_LIST_RUUID);
 			break;
