diff --git a/Documentation/devicetree/bindings/reserved-memory/arm,mte-tag-storage.yaml b/Documentation/devicetree/bindings/reserved-memory/arm,mte-tag-storage.yaml
new file mode 100644
index 000000000000..a99aaa1e8b6e
--- /dev/null
+++ b/Documentation/devicetree/bindings/reserved-memory/arm,mte-tag-storage.yaml
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/reserved-memory/arm,mte-tag-storage.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Tag storage memory for Memory Tagging Extension
+
+description: |
+  Description of the tag storage memory region that Linux can use to store
+  data when the associated memory is not tagged.
+
+  The reserved memory described by the node must also be described by a
+  standalone 'memory' node.
+
+maintainers:
+  - Alexandru Elisei <alexandru.elisei@arm.com>
+
+allOf:
+  - $ref: reserved-memory.yaml
+
+properties:
+  compatible:
+    const: arm,mte-tag-storage
+
+  reg:
+    description: |
+      Specifies the memory region that MTE uses for tag storage. The size of the
+      region must be equal to the size needed to store all the tags for the
+      associated tagged memory.
+
+  block-size:
+    description: |
+      Specifies the minimum multiple of 4K bytes of tag storage where all the
+      tags stored in the block correspond to a contiguous memory region. This
+      is needed for platforms where the memory controller interleaves tag
+      writes to memory.
+
+      For example, if the memory controller interleaves tag writes for 256KB
+      of contiguous memory across 8K of tag storage (2-way interleave), then
+      the correct value for 'block-size' is 0x2000.
+
+      This value is a hardware property, independent of the selected kernel page
+      size.
+    $ref: /schemas/types.yaml#/definitions/uint32
+
+  tagged-memory:
+    description: |
+      Specifies the memory node, as a phandle, for which all the tags are
+      stored in the tag storage region.
+
+      The memory node must describe one contiguous memory region (i.e, the
+      'ranges' property of the memory node must have exactly one entry).
+    $ref: /schemas/types.yaml#/definitions/phandle
+
+unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - block-size
+  - tagged-memory
+  - reusable
+
+examples:
+  - |
+    reserved-memory {
+      #address-cells = <2>;
+      #size-cells = <2>;
+
+      tags0: tag-storage@8f8000000 {
+        compatible = "arm,mte-tag-storage";
+        reg = <0x08 0xf8000000 0x00 0x4000000>;
+        block-size = <0x1000>;
+        tagged-memory = <&memory0>;
+        reusable;
+      };
+    };
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index aa7c1d435139..92d97930b56e 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -2082,6 +2082,18 @@ config ARM64_MTE
 
 	  Documentation/arch/arm64/memory-tagging-extension.rst.
 
+if ARM64_MTE
+config ARM64_MTE_TAG_STORAGE
+	bool
+	help
+	  Adds support for dynamic management of the memory used by the hardware
+	  for storing MTE tags. This memory, unlike normal memory, cannot be
+	  tagged. When it is used to store tags for another memory location it
+	  cannot be used for any type of allocation.
+
+	  If unsure, say N
+endif # ARM64_MTE
+
 endmenu # "ARMv8.5 architectural features"
 
 menu "ARMv8.7 architectural features"
diff --git a/arch/arm64/include/asm/mte_tag_storage.h b/arch/arm64/include/asm/mte_tag_storage.h
new file mode 100644
index 000000000000..3c2cd29e053e
--- /dev/null
+++ b/arch/arm64/include/asm/mte_tag_storage.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2023 ARM Ltd.
+ */
+#ifndef __ASM_MTE_TAG_STORAGE_H
+#define __ASM_MTE_TAG_STORAGE_H
+
+#ifdef CONFIG_ARM64_MTE_TAG_STORAGE
+void mte_init_tag_storage(void);
+#else
+static inline void mte_init_tag_storage(void)
+{
+}
+#endif /* CONFIG_ARM64_MTE_TAG_STORAGE */
+
+#endif /* __ASM_MTE_TAG_STORAGE_H  */
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index e5d03a7039b4..89c28b538908 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_CRASH_CORE)		+= crash_core.o
 obj-$(CONFIG_ARM_SDE_INTERFACE)		+= sdei.o
 obj-$(CONFIG_ARM64_PTR_AUTH)		+= pointer_auth.o
 obj-$(CONFIG_ARM64_MTE)			+= mte.o
+obj-$(CONFIG_ARM64_MTE_TAG_STORAGE)	+= mte_tag_storage.o
 obj-y					+= vdso-wrap.o
 obj-$(CONFIG_COMPAT_VDSO)		+= vdso32-wrap.o
 obj-$(CONFIG_UNWIND_PATCH_PAC_INTO_SCS)	+= patch-scs.o
diff --git a/arch/arm64/kernel/mte_tag_storage.c b/arch/arm64/kernel/mte_tag_storage.c
new file mode 100644
index 000000000000..2f32265d8ad8
--- /dev/null
+++ b/arch/arm64/kernel/mte_tag_storage.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Support for dynamic tag storage.
+ *
+ * Copyright (C) 2023 ARM Ltd.
+ */
+
+#include <linux/memblock.h>
+#include <linux/mm.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_fdt.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/range.h>
+#include <linux/string.h>
+#include <linux/xarray.h>
+
+#include <asm/mte_tag_storage.h>
+
+struct tag_region {
+	struct range mem_range;	/* Memory associated with the tag storage, in PFNs. */
+	struct range tag_range;	/* Tag storage memory, in PFNs. */
+	u32 block_size_pages;	/* Tag block size, in pages. */
+	phandle mem_phandle;	/* phandle for the associated memory node. */
+};
+
+#define MAX_TAG_REGIONS	32
+
+static struct tag_region tag_regions[MAX_TAG_REGIONS];
+static int num_tag_regions;
+
+static u32 __init get_block_size_pages(u32 block_size_bytes)
+{
+	u32 a = PAGE_SIZE;
+	u32 b = block_size_bytes;
+	u32 r;
+
+	/* Find greatest common divisor using the Euclidian algorithm. */
+	do {
+		r = a % b;
+		a = b;
+		b = r;
+	} while (b != 0);
+
+	return PHYS_PFN(PAGE_SIZE * block_size_bytes / a);
+}
+
+int __init tag_storage_probe(struct reserved_mem *rmem)
+{
+	struct tag_region *region;
+	u32 block_size_bytes;
+	int ret;
+
+	if (num_tag_regions == MAX_TAG_REGIONS) {
+		pr_err("Exceeded maximum number of tag storage regions");
+		goto out_err;
+	}
+
+	region = &tag_regions[num_tag_regions];
+	region->tag_range.start = PHYS_PFN(rmem->base);
+	region->tag_range.end = PHYS_PFN(rmem->base + rmem->size - 1);
+
+	ret = of_flat_read_u32(rmem->fdt_node, "block-size", &block_size_bytes);
+	if (ret || block_size_bytes == 0) {
+		pr_err("Invalid or missing 'block-size' property");
+		goto out_err;
+	}
+
+	region->block_size_pages = get_block_size_pages(block_size_bytes);
+	if (range_len(&region->tag_range) % region->block_size_pages != 0) {
+		pr_err("Tag storage region size 0x%llx pages is not a multiple of block size 0x%x pages",
+		       range_len(&region->tag_range), region->block_size_pages);
+		goto out_err;
+	}
+
+	ret = of_flat_read_u32(rmem->fdt_node, "tagged-memory", &region->mem_phandle);
+	if (ret) {
+		pr_err("Invalid or missing 'tagged-memory' property");
+		goto out_err;
+	}
+
+	num_tag_regions++;
+	return 0;
+
+out_err:
+	num_tag_regions = 0;
+	return -EINVAL;
+}
+RESERVEDMEM_OF_DECLARE(tag_storage, "arm,mte-tag-storage", tag_storage_probe);
+
+static int __init mte_find_tagged_memory_regions(void)
+{
+	struct device_node *mem_dev;
+	struct tag_region *region;
+	struct range *mem_range;
+	const __be32 *reg;
+	u64 addr, size;
+	int i;
+
+	for (i = 0; i < num_tag_regions; i++) {
+		region = &tag_regions[i];
+		mem_range = &region->mem_range;
+
+		mem_dev = of_find_node_by_phandle(region->mem_phandle);
+		if (!mem_dev) {
+			pr_err("Cannot find tagged memory node");
+			goto out;
+		}
+
+		reg = of_get_property(mem_dev, "reg", NULL);
+		if (!reg) {
+			pr_err("Invalid tagged memory node");
+			goto out_put_mem;
+		}
+
+		addr = of_translate_address(mem_dev, reg);
+		if (addr == OF_BAD_ADDR) {
+			pr_err("Invalid memory address");
+			goto out_put_mem;
+		}
+
+		size = of_read_number(reg + of_n_addr_cells(mem_dev), of_n_size_cells(mem_dev));
+		if (!size) {
+			pr_err("Invalid memory size");
+			goto out_put_mem;
+		}
+
+		mem_range->start = PHYS_PFN(addr);
+		mem_range->end = PHYS_PFN(addr + size - 1);
+
+		of_node_put(mem_dev);
+	}
+
+	return 0;
+
+out_put_mem:
+	of_node_put(mem_dev);
+out:
+	return -EINVAL;
+}
+
+void __init mte_init_tag_storage(void)
+{
+	int ret;
+
+	if (num_tag_regions == 0)
+		return;
+
+	ret = mte_find_tagged_memory_regions();
+	if (ret)
+		goto out_disabled;
+
+	return;
+
+out_disabled:
+	num_tag_regions = 0;
+	pr_info("MTE tag storage region management disabled");
+}
diff --git a/arch/arm64/mm/init.c b/arch/arm64/mm/init.c
index 74c1db8ce271..2ccc0c294a13 100644
--- a/arch/arm64/mm/init.c
+++ b/arch/arm64/mm/init.c
@@ -39,6 +39,7 @@
 #include <asm/kernel-pgtable.h>
 #include <asm/kvm_host.h>
 #include <asm/memory.h>
+#include <asm/mte_tag_storage.h>
 #include <asm/numa.h>
 #include <asm/sections.h>
 #include <asm/setup.h>
@@ -386,6 +387,8 @@ void __init mem_init(void)
 	/* this will put all unused low memory onto the freelists */
 	memblock_free_all();
 
+	mte_init_tag_storage();
+
 	/*
 	 * Check boundaries twice: Some fundamental inconsistencies can be
 	 * detected at build time already.
