diff --git a/check/main.c b/check/main.c
index 4beeb0adae76..4a88cc8f8708 100644
--- a/check/main.c
+++ b/check/main.c
@@ -2454,7 +2454,7 @@ static int reset_nlink(struct btrfs_trans_handle *trans,
 	 * add_link() will handle the nlink inc, so new nlink must be correct
 	 */
 	list_for_each_entry(backref, &rec->backrefs, list) {
-		ret = btrfs_add_link(trans, root, rec->ino, backref->dir,
+		ret = btrfs_add_link(trans, root, rec->ino, root, backref->dir,
 				     backref->name, backref->namelen,
 				     &backref->index, 0);
 		if (ret < 0)
diff --git a/check/mode-common.c b/check/mode-common.c
index 1f42743c390a..0dca0a789a4d 100644
--- a/check/mode-common.c
+++ b/check/mode-common.c
@@ -463,7 +463,7 @@ int link_inode_to_lostfound(struct btrfs_trans_handle *trans,
 		error("failed to create '%s' dir: %m", dir_name);
 		goto out;
 	}
-	ret = btrfs_add_link(trans, root, ino, lost_found_ino,
+	ret = btrfs_add_link(trans, root, ino, root, lost_found_ino,
 			     namebuf, name_len, NULL, 0);
 	/*
 	 * Add ".INO" suffix several times to handle case where
@@ -480,8 +480,8 @@ int link_inode_to_lostfound(struct btrfs_trans_handle *trans,
 		snprintf(namebuf + name_len, BTRFS_NAME_LEN - name_len,
 			 ".%llu", ino);
 		name_len += count_digits(ino) + 1;
-		ret = btrfs_add_link(trans, root, ino, lost_found_ino, namebuf,
-				     name_len, NULL, 0);
+		ret = btrfs_add_link(trans, root, ino, root, lost_found_ino,
+				     namebuf, name_len, NULL, 0);
 	}
 	if (ret < 0) {
 		errno = -ret;
diff --git a/check/mode-lowmem.c b/check/mode-lowmem.c
index 275f6d16ebc7..b52316754753 100644
--- a/check/mode-lowmem.c
+++ b/check/mode-lowmem.c
@@ -1040,8 +1040,8 @@ static int repair_ternary_lowmem(struct btrfs_root *root, u64 dir_ino, u64 ino,
 				name_len, 0);
 		if (ret)
 			goto out;
-		ret = btrfs_add_link(trans, root, ino, dir_ino, name, name_len,
-				     &index, 1);
+		ret = btrfs_add_link(trans, root, ino, root, dir_ino, name,
+				     name_len, &index, 1);
 		goto out;
 	}
 out:
diff --git a/convert/main.c b/convert/main.c
index c1047bacbe01..bcd310228fcd 100644
--- a/convert/main.c
+++ b/convert/main.c
@@ -104,6 +104,7 @@
 #include "kernel-shared/transaction.h"
 #include "kernel-shared/free-space-tree.h"
 #include "kernel-shared/file-item.h"
+#include "kernel-shared/ctree.h"
 #include "crypto/hash.h"
 #include "common/defs.h"
 #include "common/extent-cache.h"
@@ -840,8 +841,8 @@ static int create_image(struct btrfs_root *root,
 			ino, root->root_key.objectid);
 		goto out;
 	}
-	ret = btrfs_add_link(trans, root, ino, BTRFS_FIRST_FREE_OBJECTID, name,
-			     strlen(name), NULL, 0);
+	ret = btrfs_add_link(trans, root, ino, root, BTRFS_FIRST_FREE_OBJECTID,
+			     name, strlen(name), NULL, 0);
 	if (ret < 0) {
 		errno = -ret;
 		error("failed to link ino %llu to '/%s' in root %llu: %m",
@@ -1145,6 +1146,79 @@ static int convert_open_fs(const char *devname,
 	return -1;
 }
 
+static struct btrfs_root *link_image_subvol(struct btrfs_fs_info *fs_info,
+					    const char *base)
+{
+	struct btrfs_root *fs_root = fs_info->fs_root;
+	struct btrfs_root *image_root;
+	struct btrfs_trans_handle *trans;
+	struct btrfs_key key;
+	char buf[BTRFS_NAME_LEN + 1]; /* for snprintf null */
+	int len;
+	int ret;
+
+	strcpy(buf, base);
+	len = strlen(base);
+	if (len == 0 || len > BTRFS_NAME_LEN) {
+		ret = -EINVAL;
+		error("invalid image subvolume name: %s", base);
+		image_root = ERR_PTR(ret);
+		goto out;
+	}
+
+	/*
+	 * 1 root ref, 1 root backref,
+	 * 1 dir_index, 1 dir_item.
+	 */
+	trans = btrfs_start_transaction(fs_root, 4);
+	if (IS_ERR(trans)) {
+		ret = PTR_ERR(trans);
+		errno = -ret;
+		error_msg(ERROR_MSG_START_TRANS, "%m");
+		image_root = ERR_PTR(ret);
+		goto out;
+	}
+	key.objectid = CONV_IMAGE_SUBVOL_OBJECTID;
+	key.type = BTRFS_ROOT_ITEM_KEY;
+	key.offset = (u64)-1;
+	image_root = btrfs_read_fs_root(fs_info, &key);
+	if (IS_ERR(image_root)) {
+		ret = PTR_ERR(image_root);
+		errno = -ret;
+		error("failed to read convert image subvolume: %m");
+		image_root = ERR_PTR(ret);
+		goto out;
+	}
+	for (int i = 0; i < 1024; i++) {
+		ret = btrfs_add_link(trans,
+			image_root, btrfs_root_dirid(&image_root->root_item),
+			fs_root, btrfs_root_dirid(&fs_root->root_item),
+			buf, len, NULL, 0);
+		if (ret != -EEXIST)
+			break;
+		len = snprintf(buf, ARRAY_SIZE(buf), "%s%d", base, i);
+		if (len < 1 || len > BTRFS_NAME_LEN) {
+			ret = -EINVAL;
+			break;
+		}
+	}
+	if (ret < 0) {
+		errno = -ret;
+		error("failed to link image subvolume: %m");
+		image_root = ERR_PTR(ret);
+		goto out;
+	}
+	ret = btrfs_commit_transaction(trans, fs_root);
+	if (ret < 0) {
+		errno = -ret;
+		error("failed to commit transaction: %m");
+		image_root = ERR_PTR(ret);
+		goto out;
+	}
+out:
+	return image_root;
+}
+
 static int do_convert(const char *devname, u32 convert_flags, u32 nodesize,
 		const char *fslabel, int progress,
 		struct btrfs_mkfs_features *features, u16 csum_type,
@@ -1311,9 +1385,8 @@ static int do_convert(const char *devname, u32 convert_flags, u32 nodesize,
 		task_deinit(ctx.info);
 	}
 
-	image_root = btrfs_mksubvol(root, subvol_name,
-				    CONV_IMAGE_SUBVOL_OBJECTID, true);
-	if (!image_root) {
+	image_root = link_image_subvol(root->fs_info, subvol_name);
+	if (IS_ERR(image_root)) {
 		error("unable to link subvolume %s", subvol_name);
 		goto fail;
 	}
diff --git a/kernel-shared/ctree.h b/kernel-shared/ctree.h
index 501feaa08a0a..ff43fefca802 100644
--- a/kernel-shared/ctree.h
+++ b/kernel-shared/ctree.h
@@ -1225,8 +1225,10 @@ int btrfs_new_inode(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 		u64 ino, u32 mode);
 int btrfs_change_inode_flags(struct btrfs_trans_handle *trans,
 			     struct btrfs_root *root, u64 ino, u64 flags);
-int btrfs_add_link(struct btrfs_trans_handle *trans, struct btrfs_root *root,
-		   u64 ino, u64 parent_ino, char *name, int namelen,
+int btrfs_add_link(struct btrfs_trans_handle *trans,
+		   struct btrfs_root *child_root, u64 child_ino,
+		   struct btrfs_root *parent_root, u64 parent_ino,
+		   char *name, int namelen,
 		   u64 *index, int ignore_existed);
 int btrfs_unlink(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 		 u64 ino, u64 parent_ino, u64 index, const char *name,
diff --git a/kernel-shared/inode.c b/kernel-shared/inode.c
index 43ab685a45e1..38ca882141e2 100644
--- a/kernel-shared/inode.c
+++ b/kernel-shared/inode.c
@@ -170,33 +170,44 @@ out:
 }
 
 /*
- * Add dir_item/index for 'parent_ino' if add_backref is true, also insert a
- * backref from the ino to parent dir and update the nlink(Kernel version does
- * not do this thing)
+ * Link child inode (@child_ino of @child_root) under the directory of parent
+ * inode (@parent_ino of @parent_root, must be a directory inode), using
+ * @name.
  *
- * Currently only supports adding link from an inode to another inode.
+ * If @child_root and @parent_root are different, @child_ino must be the rootdir
+ * of @child_root.
+ *
+ * If @index is not NULL, and points to a non-zero value, this function would use
+ * *index as the DIR_INDEX, caller must ensure there is no conflicts.
+ * If @ignore_existed is true, any conflicting DIR_ITEM/DIR_INDEX would be ignored.
  */
-int btrfs_add_link(struct btrfs_trans_handle *trans, struct btrfs_root *root,
-		   u64 ino, u64 parent_ino, char *name, int namelen,
+int btrfs_add_link(struct btrfs_trans_handle *trans,
+		   struct btrfs_root *child_root, u64 child_ino,
+		   struct btrfs_root *parent_root, u64 parent_ino,
+		   char *name, int namelen,
 		   u64 *index, int ignore_existed)
 {
 	struct btrfs_path *path;
 	struct btrfs_key key;
 	struct btrfs_inode_item *inode_item;
+	bool link_subvol = false;
 	u32 imode;
 	u32 nlink;
 	u64 inode_size;
 	u64 ret_index = 0;
 	int ret = 0;
 
+	if (btrfs_comp_cpu_keys(&child_root->root_key, &parent_root->root_key))
+		link_subvol = true;
+
 	path = btrfs_alloc_path();
 	if (!path)
 		return -ENOMEM;
 
-	key.objectid = ino;
+	key.objectid = child_ino;
 	key.type = BTRFS_INODE_ITEM_KEY;
 	key.offset = 0;
-	ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
+	ret = btrfs_search_slot(NULL, child_root, &key, path, 0, 0);
 	if (ret > 0) {
 		ret = -ENOENT;
 		/* fallthrough */
@@ -209,31 +220,72 @@ int btrfs_add_link(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 	imode = btrfs_inode_mode(path->nodes[0], inode_item);
 	btrfs_release_path(path);
 
+	/*
+	 * If linking a subvolume, child_ino must be the rootdir of that
+	 * subvolume.
+	 */
+	if (link_subvol && child_ino != btrfs_root_dirid(&child_root->root_item)) {
+		error("child ino is not rootdir, has %llu expect %llu",
+		      child_ino, btrfs_root_dirid(&child_root->root_item));
+		ret = -EUCLEAN;
+		goto out;
+	}
+
 	if (index && *index) {
 		ret_index = *index;
 	} else {
-		ret = btrfs_find_free_dir_index(root, parent_ino, &ret_index);
+		ret = btrfs_find_free_dir_index(parent_root, parent_ino,
+						&ret_index);
 		if (ret < 0)
 			goto out;
 	}
 
-	ret = check_dir_conflict(root, name, namelen, parent_ino, ret_index);
+	ret = check_dir_conflict(parent_root, name, namelen, parent_ino,
+				 ret_index);
 	if (ret < 0 && !(ignore_existed && ret == -EEXIST))
 		goto out;
 
-	/* Add inode ref */
-	ret = btrfs_insert_inode_ref(trans, root, name, namelen,
-				     ino, parent_ino, ret_index);
-	if (ret < 0 && !(ignore_existed && ret == -EEXIST))
-		goto out;
+	if (link_subvol) {
+		struct btrfs_root *tree_root = trans->fs_info->tree_root;
+
+		/* Add root backref. */
+		ret = btrfs_add_root_ref(trans, tree_root,
+				child_root->root_key.objectid,
+				BTRFS_ROOT_BACKREF_KEY,
+				parent_root->root_key.objectid,
+				parent_ino, ret_index, name, namelen);
+		if (ret < 0) {
+			errno = -ret;
+			error("failed to add backref for child root %lld: %m",
+			      child_root->root_key.objectid);
+			goto out;
+		}
+
+		ret = btrfs_add_root_ref(trans, tree_root,
+				parent_root->root_key.objectid,
+				BTRFS_ROOT_REF_KEY, child_root->root_key.objectid,
+				parent_ino, ret_index, name, namelen);
+		if (ret < 0) {
+			errno = -ret;
+			error("failed to add root ref for child root %lld: %m",
+			      child_root->root_key.objectid);
+			goto out;
+		}
+	} else {
+		/* Add inode ref */
+		ret = btrfs_insert_inode_ref(trans, child_root, name, namelen,
+					     child_ino, parent_ino, ret_index);
+		if (ret < 0 && !(ignore_existed && ret == -EEXIST))
+			goto out;
+	}
 
 	/* do not update nlinks if existed */
-	if (!ret) {
-		/* Update nlinks for the inode */
-		key.objectid = ino;
+	if (!ret && !link_subvol) {
+		/* Update nlinks for the child inode. */
+		key.objectid = child_ino;
 		key.type = BTRFS_INODE_ITEM_KEY;
 		key.offset = 0;
-		ret = btrfs_search_slot(trans, root, &key, path, 1, 1);
+		ret = btrfs_search_slot(trans, child_root, &key, path, 1, 1);
 		if (ret) {
 			if (ret > 0)
 				ret = -ENOENT;
@@ -250,11 +302,16 @@ int btrfs_add_link(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 	}
 
 	/* Add dir_item and dir_index */
-	key.objectid = ino;
-	key.type = BTRFS_INODE_ITEM_KEY;
-	key.offset = 0;
-	ret = btrfs_insert_dir_item(trans, root, name, namelen, parent_ino,
-				    &key, fs_umode_to_ftype(imode), ret_index);
+	if (link_subvol) {
+		memcpy(&key, &child_root->root_key, sizeof(struct btrfs_key));
+	} else {
+		key.objectid = child_ino;
+		key.type = BTRFS_INODE_ITEM_KEY;
+		key.offset = 0;
+	}
+	ret = btrfs_insert_dir_item(trans, parent_root, name, namelen,
+				    parent_ino, &key,
+				    fs_umode_to_ftype(imode), ret_index);
 	if (ret < 0)
 		goto out;
 
@@ -262,7 +319,7 @@ int btrfs_add_link(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 	key.objectid = parent_ino;
 	key.type = BTRFS_INODE_ITEM_KEY;
 	key.offset = 0;
-	ret = btrfs_search_slot(trans, root, &key, path, 1, 1);
+	ret = btrfs_search_slot(trans, parent_root, &key, path, 1, 1);
 	if (ret)
 		goto out;
 	inode_item = btrfs_item_ptr(path->nodes[0], path->slots[0],
@@ -596,8 +653,8 @@ int btrfs_mkdir(struct btrfs_trans_handle *trans, struct btrfs_root *root,
 	ret = btrfs_new_inode(trans, root, ret_ino, mode | S_IFDIR);
 	if (ret)
 		goto out;
-	ret = btrfs_add_link(trans, root, ret_ino, parent_ino, name, namelen,
-			     NULL, 0);
+	ret = btrfs_add_link(trans, root, ret_ino, root, parent_ino, name,
+			     namelen, NULL, 0);
 	if (ret)
 		goto out;
 out:
@@ -607,136 +664,6 @@ out:
 	return ret;
 }
 
-struct btrfs_root *btrfs_mksubvol(struct btrfs_root *root,
-				  const char *base, u64 root_objectid,
-				  bool convert)
-{
-	struct btrfs_trans_handle *trans;
-	struct btrfs_fs_info *fs_info = root->fs_info;
-	struct btrfs_root *tree_root = fs_info->tree_root;
-	struct btrfs_root *new_root = NULL;
-	struct btrfs_path path = { 0 };
-	struct btrfs_inode_item *inode_item;
-	struct extent_buffer *leaf;
-	struct btrfs_key key;
-	u64 dirid = btrfs_root_dirid(&root->root_item);
-	u64 index = 2;
-	char buf[BTRFS_NAME_LEN + 1]; /* for snprintf null */
-	int len;
-	int i;
-	int ret;
-
-	len = strlen(base);
-	if (len == 0 || len > BTRFS_NAME_LEN)
-		return NULL;
-
-	key.objectid = dirid;
-	key.type = BTRFS_DIR_INDEX_KEY;
-	key.offset = (u64)-1;
-
-	ret = btrfs_search_slot(NULL, root, &key, &path, 0, 0);
-	if (ret <= 0) {
-		error("search for DIR_INDEX dirid %llu failed: %d",
-				(unsigned long long)dirid, ret);
-		goto fail;
-	}
-
-	if (path.slots[0] > 0) {
-		path.slots[0]--;
-		btrfs_item_key_to_cpu(path.nodes[0], &key, path.slots[0]);
-		if (key.objectid == dirid && key.type == BTRFS_DIR_INDEX_KEY)
-			index = key.offset + 1;
-	}
-	btrfs_release_path(&path);
-
-	trans = btrfs_start_transaction(root, 1);
-	if (IS_ERR(trans)) {
-		ret = PTR_ERR(trans);
-		errno = -ret;
-		error_msg(ERROR_MSG_START_TRANS, "%m");
-		goto fail;
-	}
-
-	key.objectid = dirid;
-	key.offset = 0;
-	key.type =  BTRFS_INODE_ITEM_KEY;
-
-	ret = btrfs_lookup_inode(trans, root, &path, &key, 1);
-	if (ret) {
-		error("search for INODE_ITEM %llu failed: %d",
-				(unsigned long long)dirid, ret);
-		goto fail;
-	}
-	leaf = path.nodes[0];
-	inode_item = btrfs_item_ptr(leaf, path.slots[0],
-				    struct btrfs_inode_item);
-
-	key.objectid = root_objectid;
-	key.offset = (u64)-1;
-	key.type = BTRFS_ROOT_ITEM_KEY;
-
-	memcpy(buf, base, len);
-	if (convert) {
-		for (i = 0; i < 1024; i++) {
-			ret = btrfs_insert_dir_item(trans, root, buf, len,
-					dirid, &key, BTRFS_FT_DIR, index);
-			if (ret != -EEXIST)
-				break;
-			len = snprintf(buf, ARRAY_SIZE(buf), "%s%d", base, i);
-			if (len < 1 || len > BTRFS_NAME_LEN) {
-				ret = -EINVAL;
-				break;
-			}
-		}
-	} else {
-		ret = btrfs_insert_dir_item(trans, root, buf, len, dirid, &key,
-					    BTRFS_FT_DIR, index);
-	}
-	if (ret)
-		goto fail;
-
-	btrfs_set_inode_size(leaf, inode_item, len * 2 +
-			     btrfs_inode_size(leaf, inode_item));
-	btrfs_mark_buffer_dirty(leaf);
-	btrfs_release_path(&path);
-
-	/* add the backref first */
-	ret = btrfs_add_root_ref(trans, tree_root, root_objectid,
-				 BTRFS_ROOT_BACKREF_KEY,
-				 root->root_key.objectid,
-				 dirid, index, buf, len);
-	if (ret) {
-		error("unable to add root backref for %llu: %d",
-				root->root_key.objectid, ret);
-		goto fail;
-	}
-
-	/* now add the forward ref */
-	ret = btrfs_add_root_ref(trans, tree_root, root->root_key.objectid,
-				 BTRFS_ROOT_REF_KEY, root_objectid,
-				 dirid, index, buf, len);
-	if (ret) {
-		error("unable to add root ref for %llu: %d",
-				root->root_key.objectid, ret);
-		goto fail;
-	}
-
-	ret = btrfs_commit_transaction(trans, root);
-	if (ret) {
-		errno = -ret;
-		error_msg(ERROR_MSG_COMMIT_TRANS, "%m");
-		goto fail;
-	}
-
-	new_root = btrfs_read_fs_root(fs_info, &key);
-	if (IS_ERR(new_root)) {
-		error("unable to fs read root: %lu", PTR_ERR(new_root));
-		new_root = NULL;
-	}
-fail:
-	return new_root;
-}
-
 /*
  * Walk the tree of allocated inodes and find a hole.
  */
