diff --git a/fs/crypto/Kconfig b/fs/crypto/Kconfig
index a5f5c30368a2..3010e91f6659 100644
--- a/fs/crypto/Kconfig
+++ b/fs/crypto/Kconfig
@@ -30,3 +30,9 @@ config FS_ENCRYPTION_INLINE_CRYPT
 	depends on FS_ENCRYPTION && BLK_INLINE_ENCRYPTION
 	help
 	  Enable fscrypt to use inline encryption hardware if available.
+
+config FS_ENCRYPTION_METADATA
+	bool "Enable metadata encryption with fscrypt"
+	depends on FS_ENCRYPTION && BLK_INLINE_ENCRYPTION
+	help
+	  Enable fscrypt to encrypt metadata.
\ No newline at end of file
diff --git a/fs/crypto/Makefile b/fs/crypto/Makefile
index 652c7180ec6d..8403c7956983 100644
--- a/fs/crypto/Makefile
+++ b/fs/crypto/Makefile
@@ -12,3 +12,4 @@ fscrypto-y := crypto.o \
 
 fscrypto-$(CONFIG_BLOCK) += bio.o
 fscrypto-$(CONFIG_FS_ENCRYPTION_INLINE_CRYPT) += inline_crypt.o
+fscrypto-$(CONFIG_FS_ENCRYPTION_METADATA) += metadata_crypt.o
\ No newline at end of file
diff --git a/fs/crypto/fscrypt_private.h b/fs/crypto/fscrypt_private.h
index 8117a61b6f55..dca254590a70 100644
--- a/fs/crypto/fscrypt_private.h
+++ b/fs/crypto/fscrypt_private.h
@@ -327,6 +327,25 @@ int fscrypt_hkdf_expand(const struct fscrypt_hkdf *hkdf, u8 context,
 void fscrypt_destroy_hkdf(struct fscrypt_hkdf *hkdf);
 
 /* inline_crypt.c */
+
+static inline int fscrypt_get_num_devices(struct super_block *sb)
+{
+	if (sb->s_cop->get_num_devices)
+		return sb->s_cop->get_num_devices(sb);
+	return 1;
+}
+
+static inline struct request_queue *fscrypt_get_device(struct super_block *sb,
+						unsigned int device_index)
+{
+	if (sb->s_cop->get_device)
+		return sb->s_cop->get_device(sb, device_index);
+	else if (WARN_ON_ONCE(device_index != 0))
+		return NULL;
+	else
+		return bdev_get_queue(sb->s_bdev);
+}
+
 #ifdef CONFIG_FS_ENCRYPTION_INLINE_CRYPT
 int fscrypt_select_encryption_impl(struct fscrypt_info *ci);
 
diff --git a/fs/crypto/inline_crypt.c b/fs/crypto/inline_crypt.c
index 5bbce79df638..f8f7363ebcd0 100644
--- a/fs/crypto/inline_crypt.c
+++ b/fs/crypto/inline_crypt.c
@@ -26,24 +26,6 @@ struct fscrypt_blk_crypto_key {
 	struct request_queue *devs[];
 };
 
-static int fscrypt_get_num_devices(struct super_block *sb)
-{
-	if (sb->s_cop->get_num_devices)
-		return sb->s_cop->get_num_devices(sb);
-	return 1;
-}
-
-static struct request_queue *fscrypt_get_device(struct super_block *sb,
-						unsigned int device_index)
-{
-	if (sb->s_cop->get_device)
-		return sb->s_cop->get_device(sb, device_index);
-	else if (WARN_ON_ONCE(device_index != 0))
-		return NULL;
-	else
-		return bdev_get_queue(sb->s_bdev);
-}
-
 static unsigned int fscrypt_get_dun_bytes(const struct fscrypt_info *ci)
 {
 	struct super_block *sb = ci->ci_inode->i_sb;
diff --git a/fs/crypto/metadata_crypt.c b/fs/crypto/metadata_crypt.c
new file mode 100644
index 000000000000..5e16df130509
--- /dev/null
+++ b/fs/crypto/metadata_crypt.c
@@ -0,0 +1,220 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Metadata encryption support for fscrypt
+ *
+ * Copyright 2020 Google LLC
+ */
+
+#include <keys/user-type.h>
+#include <linux/blk-crypto.h>
+#include <linux/blkdev.h>
+#include <linux/buffer_head.h>
+#include <linux/sched/mm.h>
+
+#include "fscrypt_private.h"
+
+/* TODO: mostly copied from keysetup_v1.c - maybe refactor this function */
+static int fscrypt_metadata_get_key_from_id(const char *prefix,
+					    char *descriptor_hex,
+					    unsigned int min_keysize,
+					    char *raw_key)
+{
+	char *description;
+	struct key *key;
+	const struct user_key_payload *ukp;
+	const struct fscrypt_key *payload;
+	int err = -ENOKEY;
+
+	if (strlen(descriptor_hex) != FSCRYPT_KEY_DESCRIPTOR_SIZE * 2)
+		return -EINVAL;
+
+	description = kasprintf(GFP_NOFS, "%s%s", prefix, descriptor_hex);
+	if (!description)
+		return -ENOMEM;
+
+	key = request_key(&key_type_logon, description, NULL);
+	kfree(description);
+	if (IS_ERR(key))
+		return PTR_ERR(key);
+
+	down_read(&key->sem);
+	ukp = user_key_payload_locked(key);
+
+	if (!ukp) /* was the key revoked before we acquired its semaphore? */
+		goto out;
+
+	payload = (const struct fscrypt_key *)ukp->data;
+
+	if (ukp->datalen != sizeof(struct fscrypt_key) ||
+	    payload->size < 1 || payload->size > FSCRYPT_MAX_KEY_SIZE) {
+		fscrypt_warn(NULL,
+			     "key with description '%s' has invalid payload",
+			     key->description);
+		goto out;
+	}
+
+	if (payload->size < min_keysize) {
+		fscrypt_warn(NULL,
+			     "key with description '%s' is too short (got %u bytes, need %u+ bytes)",
+			     key->description, payload->size, min_keysize);
+		goto out;
+	}
+
+	memcpy(raw_key, payload->raw, min_keysize);
+	err = 0;
+
+out:
+	up_read(&key->sem);
+	key_put(key);
+
+	return err;
+}
+
+/**
+ * fscrypt_setup_metadata_encryption() - prepare a super_block for metadata
+ *					 encryption
+ * @sb: The super_block to set up metadata encryption for
+ * @key_desc_hex: The key descriptor (in hex) to look for in the logon keyring.
+ * @fscrypt_mode_num: The FSCRYPT_MODE_* to use as the encryption algorithm.
+ *
+ * Return: 0 on success, negative number on error.
+ */
+int fscrypt_setup_metadata_encryption(struct super_block *sb,
+				      char *key_desc_hex,
+				      int fscrypt_mode_num)
+{
+	int err = 0;
+	enum blk_crypto_mode_num crypto_mode;
+	unsigned int lblk_bits = 64;
+	unsigned int dun_bytes;
+	unsigned int dummy;
+	char raw_key[FSCRYPT_MAX_KEY_SIZE];
+
+	if (fscrypt_mode_num > __FSCRYPT_MODE_MAX || fscrypt_mode_num < 0 ||
+	    !fscrypt_modes[fscrypt_mode_num].cipher_str) {
+		fscrypt_warn(NULL, "Invalid fscrypt mode %d specified for metadata encryption.",
+			     fscrypt_mode_num);
+		return -EOPNOTSUPP;
+	}
+
+	if (sb->s_cop->get_ino_and_lblk_bits)
+		sb->s_cop->get_ino_and_lblk_bits(sb, &dummy, &lblk_bits);
+	dun_bytes = DIV_ROUND_UP(lblk_bits, 8);
+
+	if (fscrypt_modes[fscrypt_mode_num].ivsize < dun_bytes) {
+		fscrypt_warn(NULL, "The fscrypt mode only supports %d DUN bytes, but FS requires support for %d DUN bytes.",
+			     fscrypt_modes[fscrypt_mode_num].ivsize, dun_bytes);
+		return -EOPNOTSUPP;
+	}
+
+	crypto_mode = fscrypt_modes[fscrypt_mode_num].blk_crypto_mode;
+
+	err = fscrypt_metadata_get_key_from_id(
+					FSCRYPT_KEY_DESC_PREFIX,
+					key_desc_hex,
+					fscrypt_modes[fscrypt_mode_num].keysize,
+					raw_key);
+	if (err)
+		goto out;
+
+	sb->s_metadata_key = kzalloc(sizeof(*sb->s_metadata_key), GFP_NOFS);
+	if (!sb->s_metadata_key) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	err = blk_crypto_init_key(sb->s_metadata_key, raw_key, crypto_mode,
+				  dun_bytes, sb->s_blocksize);
+	if (err)
+		goto out_free_key;
+
+	err = blk_crypto_start_using_key(sb->s_metadata_key,
+					 bdev_get_queue(sb->s_bdev));
+	if (err)
+		goto out_free_key;
+
+	goto out;
+out_free_key:
+	fscrypt_free_metadata_encryption(sb);
+out:
+	memzero_explicit(raw_key, sizeof(raw_key));
+	return err;
+}
+
+/**
+ * fscrypt_metadata_crypt_prepare_all_devices() - prepare all devices used by
+ *					the filesystem for metadata encryption.
+ * @sb: The super_block whose devices to prepare
+ *
+ * This function should be called when the filesystem has determined all its
+ * devices. This might happen only after some initial setup, which is why
+ * this is a separate function from fscrypt_setup_metadata_encryption().
+ *
+ * Return: 0 on success, negative on error.
+ */
+int fscrypt_metadata_crypt_prepare_all_devices(struct super_block *sb)
+{
+	int num_devices;
+	int i;
+	struct request_queue *q;
+
+	if (!sb->s_metadata_key)
+		return 0;
+
+	num_devices = fscrypt_get_num_devices(sb);
+	for (i = 0; i < num_devices; i++) {
+		q = fscrypt_get_device(sb, i);
+		if (!q || blk_crypto_start_using_key(sb->s_metadata_key, q))
+			return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+/**
+ * fscrypt_free_metadata_encryption() - free metadata encryption fields in
+ *					super_block.
+ * @sb: The super_block to free metatdata encryption fields from
+ */
+void fscrypt_free_metadata_encryption(struct super_block *sb)
+{
+	int num_devices;
+	int i;
+	struct request_queue *q;
+
+	if (!sb->s_metadata_key)
+		return;
+
+	num_devices = fscrypt_get_num_devices(sb);
+
+	for (i = 0; i < num_devices; i++) {
+		q = fscrypt_get_device(sb, i);
+		if (WARN_ON(!q))
+			continue;
+		blk_crypto_evict_key(q, sb->s_metadata_key);
+	}
+
+	memzero_explicit(sb->s_metadata_key, sizeof(*sb->s_metadata_key));
+	kzfree(sb->s_metadata_key);
+	sb->s_metadata_key = NULL;
+}
+
+/**
+ * fscrypt_metadata_crypt_bio() - Add metadata encryption context to bio.
+ *
+ * @bio: The bio to add the encryption context to
+ * @lblk: The logical block number within the filesystem at which this bio
+ *	  starts reading/writing data.
+ * @sb: The superblock of the filesystem
+ * @gfp_mask: gfp_mask for bio_crypt_context allocation
+ */
+void fscrypt_metadata_crypt_bio(struct bio *bio, u64 lblk,
+				struct super_block *sb, gfp_t gfp_mask)
+{
+	u64 dun[BLK_CRYPTO_DUN_ARRAY_SIZE] = { 0 };
+
+	if (!sb->s_metadata_key)
+		return;
+
+	dun[0] = lblk;
+	bio_crypt_set_ctx(bio, sb->s_metadata_key, dun, gfp_mask);
+}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 7519ae003a08..aba3b0e2d56f 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1438,6 +1438,9 @@ struct super_block {
 	const struct fscrypt_operations	*s_cop;
 	struct key		*s_master_keys; /* master crypto keys in use */
 #endif
+#ifdef CONFIG_FS_ENCRYPTION_METADATA
+	struct blk_crypto_key	*s_metadata_key;
+#endif
 #ifdef CONFIG_FS_VERITY
 	const struct fsverity_operations *s_vop;
 #endif
diff --git a/include/linux/fscrypt.h b/include/linux/fscrypt.h
index d835fd19a20a..f7cdc8627984 100644
--- a/include/linux/fscrypt.h
+++ b/include/linux/fscrypt.h
@@ -594,6 +594,53 @@ static inline bool fscrypt_mergeable_bio_bh(struct bio *bio,
 }
 #endif /* !CONFIG_FS_ENCRYPTION_INLINE_CRYPT */
 
+/* metadata_crypt.c */
+#ifdef CONFIG_FS_ENCRYPTION_METADATA
+
+int fscrypt_setup_metadata_encryption(struct super_block *sb,
+				      char *key_desc_hex,
+				      int fscrypt_mode_num);
+
+int fscrypt_metadata_crypt_prepare_all_devices(struct super_block *sb);
+
+void fscrypt_free_metadata_encryption(struct super_block *sb);
+
+void fscrypt_metadata_crypt_bio(struct bio *bio, u64 lblk,
+				struct super_block *sb, gfp_t gfp_mask);
+
+static inline bool fscrypt_metadata_crypted(struct super_block *sb)
+{
+	return sb->s_metadata_key;
+}
+
+#else /* CONFIG_FS_ENCRYPTION_METADATA */
+
+static inline int fscrypt_setup_metadata_encryption(struct super_block *sb,
+						    char *key_desc_hex,
+						    int fscrypt_mode_num)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline int
+fscrypt_metadata_crypt_prepare_all_devices(struct super_block *sb)
+{
+	return 0;
+}
+
+static inline void fscrypt_free_metadata_encryption(struct super_block *sb) { }
+
+static inline void fscrypt_metadata_crypt_bio(struct bio *bio, u64 lblk,
+					      struct super_block *sb,
+					      gfp_t gfp_mask) { }
+
+static inline bool fscrypt_metadata_crypted(struct super_block *sb)
+{
+	return false;
+}
+
+#endif /* CONFIG_FS_ENCRYPTION_METADATA */
+
 /**
  * fscrypt_inode_uses_inline_crypto() - test whether an inode uses inline
  *					encryption
