diff --git a/gdb/aarch64-linux-nat.c b/gdb/aarch64-linux-nat.c
index d7fcef5a0db..c1d59b5d77c 100644
--- a/gdb/aarch64-linux-nat.c
+++ b/gdb/aarch64-linux-nat.c
@@ -376,6 +376,37 @@ store_za_to_thread (struct regcache *regcache)
 				     tdep->sme_svcr_regnum);
 }
 
+/* Fill GDB's REGCACHE with the ZT register set contents from the
+   thread associated with REGCACHE.  If there is no active ZA register state,
+   make the ZT register contents zero.  */
+
+static void
+fetch_zt_from_thread (struct regcache *regcache)
+{
+  aarch64_gdbarch_tdep *tdep
+    = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
+
+  /* Read ZT state from the thread to the register cache.  */
+  aarch64_zt_regs_copy_to_reg_buf (regcache->ptid ().lwp (),
+				   regcache,
+				   tdep->sme2_zt0_regnum);
+}
+
+/* Store the NT_ARM_ZT register set contents from GDB's REGCACHE to the
+   thread associated with REGCACHE.  */
+
+static void
+store_zt_to_thread (struct regcache *regcache)
+{
+  aarch64_gdbarch_tdep *tdep
+    = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
+
+  /* Write ZT state from the register cache to the thread.  */
+  aarch64_zt_regs_copy_from_reg_buf (regcache->ptid ().lwp (),
+				     regcache,
+				     tdep->sme2_zt0_regnum);
+}
+
 /* Fill GDB's register array with the pointer authentication mask values from
    the current thread.  */
 
@@ -549,6 +580,9 @@ aarch64_fetch_registers (struct regcache *regcache, int regno)
 
       if (tdep->has_sme ())
 	fetch_za_from_thread (regcache);
+
+      if (tdep->has_sme2 ())
+	fetch_zt_from_thread (regcache);
     }
   /* General purpose register?  */
   else if (regno < AARCH64_V0_REGNUM)
@@ -569,6 +603,9 @@ aarch64_fetch_registers (struct regcache *regcache, int regno)
   else if (tdep->has_sme () && regno >= tdep->sme_reg_base
 	   && regno < tdep->sme_reg_base + 3)
     fetch_za_from_thread (regcache);
+  /* SME2 register?  */
+  else if (tdep->has_sme2 () && regno == tdep->sme2_zt0_regnum)
+    fetch_zt_from_thread (regcache);
   /* MTE register?  */
   else if (tdep->has_mte ()
 	   && (regno == tdep->mte_reg_base))
@@ -646,6 +683,9 @@ aarch64_store_registers (struct regcache *regcache, int regno)
 
       if (tdep->has_sme ())
 	store_za_to_thread (regcache);
+
+      if (tdep->has_sme2 ())
+	store_zt_to_thread (regcache);
     }
   /* General purpose register?  */
   else if (regno < AARCH64_V0_REGNUM)
@@ -661,6 +701,8 @@ aarch64_store_registers (struct regcache *regcache, int regno)
   else if (tdep->has_sme () && regno >= tdep->sme_reg_base
 	   && regno < tdep->sme_reg_base + 3)
     store_za_to_thread (regcache);
+  else if (tdep->has_sme2 () && regno == tdep->sme2_zt0_regnum)
+    store_zt_to_thread (regcache);
   /* MTE register?  */
   else if (tdep->has_mte ()
 	   && (regno == tdep->mte_reg_base))
@@ -861,6 +903,10 @@ aarch64_linux_nat_target::read_description ()
   /* SME feature check.  */
   features.svq = aarch64_za_get_svq (tid);
 
+  /* Check for SME2 support.  */
+  if ((hwcap2 & HWCAP2_SME2) || (hwcap2 & HWCAP2_SME2P1))
+    features.sme2 = supports_zt_registers (tid);
+
   return aarch64_read_description (features);
 }
 
@@ -981,6 +1027,9 @@ aarch64_linux_nat_target::thread_architecture (ptid_t ptid)
   features.vq = vq;
   features.svq = svq;
 
+  /* Check for the SME2 feature.  */
+  features.sme2 = supports_zt_registers (ptid.lwp ());
+
   struct gdbarch_info info;
   info.bfd_arch_info = bfd_lookup_arch (bfd_arch_aarch64, bfd_mach_aarch64);
   info.target_desc = aarch64_read_description (features);
diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index 8e7259a6f42..eaae2d91047 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -4048,6 +4048,10 @@ aarch64_features_from_target_desc (const struct target_desc *tdesc)
 
   features.svq = aarch64_get_tdesc_svq (tdesc);
 
+  /* Check for the SME2 feature.  */
+  features.sme2 = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.sme2")
+		   != nullptr);
+
   return features;
 }
 
@@ -4310,6 +4314,7 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
     }
 
   int first_sme_regnum = -1;
+  int first_sme2_regnum = -1;
   int first_sme_pseudo_regnum = -1;
   const struct tdesc_feature *feature_sme
     = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.sme");
@@ -4336,6 +4341,19 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
 
       /* Add the ZA tile pseudo registers.  */
       num_pseudo_regs += AARCH64_ZA_TILES_NUM;
+
+      /* Now check for the SME2 feature.  SME2 is only available if SME is
+	 available.  */
+      const struct tdesc_feature *feature_sme2
+	= tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.sme2");
+      if (feature_sme2 != nullptr)
+	{
+	  /* Record the first SME2 register.  */
+	  first_sme2_regnum = num_regs;
+
+	  valid_p &= tdesc_numbered_register (feature_sme2, tdesc_data.get (),
+					      num_regs++, "zt0");
+	}
     }
 
   /* Add the TLS register.  */
@@ -4459,6 +4477,9 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   tdep->sme_za_regnum = first_sme_regnum + 2;
   tdep->sme_svq = svq;
 
+  /* Set the SME2 register set details.  */
+  tdep->sme2_zt0_regnum = first_sme2_regnum;
+
   set_gdbarch_push_dummy_call (gdbarch, aarch64_push_dummy_call);
   set_gdbarch_frame_align (gdbarch, aarch64_frame_align);
 
diff --git a/gdb/aarch64-tdep.h b/gdb/aarch64-tdep.h
index 9297487a584..9e4d1a59734 100644
--- a/gdb/aarch64-tdep.h
+++ b/gdb/aarch64-tdep.h
@@ -172,6 +172,16 @@ struct aarch64_gdbarch_tdep : gdbarch_tdep_base
   {
     return sme_svq != 0;
   }
+
+  /* Index of the SME2 ZT0 register.  This is -1 if SME2 is not
+     supported.  */
+  int sme2_zt0_regnum = -1;
+
+  /* Return true if the target supports SME2, and false otherwise.  */
+  bool has_sme2 () const
+  {
+    return sme2_zt0_regnum > 0;
+  }
 };
 
 const target_desc *aarch64_read_description (const aarch64_features &features);
diff --git a/gdb/arch/aarch64-scalable-linux.h b/gdb/arch/aarch64-scalable-linux.h
index cb9d85a9d5d..0f59ddb40b2 100644
--- a/gdb/arch/aarch64-scalable-linux.h
+++ b/gdb/arch/aarch64-scalable-linux.h
@@ -29,6 +29,12 @@
 #define HWCAP2_SME (1 << 23)
 #endif
 
+/* Feature check for Scalable Matrix Extension 2.  */
+#ifndef HWCAP2_SME2
+#define HWCAP2_SME2   (1UL << 37)
+#define HWCAP2_SME2P1 (1UL << 38)
+#endif
+
 /* Streaming mode enabled/disabled bit.  */
 #define SVCR_SM_BIT (1 << 0)
 /* ZA enabled/disabled bit.  */
diff --git a/gdb/arch/aarch64.c b/gdb/arch/aarch64.c
index e1f4948aa25..c93d602c66c 100644
--- a/gdb/arch/aarch64.c
+++ b/gdb/arch/aarch64.c
@@ -25,6 +25,7 @@
 #include "../features/aarch64-pauth.c"
 #include "../features/aarch64-mte.c"
 #include "../features/aarch64-sme.c"
+#include "../features/aarch64-sme2.c"
 #include "../features/aarch64-tls.c"
 
 /* See arch/aarch64.h.  */
@@ -62,6 +63,9 @@ aarch64_create_target_description (const aarch64_features &features)
     regnum = create_feature_aarch64_sme (tdesc.get (), regnum,
 					 sve_vl_from_vq (features.svq));
 
+  if (features.sme2)
+    regnum = create_feature_aarch64_sme2 (tdesc.get (), regnum);
+
   return tdesc.release ();
 }
 
diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h
index c1cd233c51e..65c6205115e 100644
--- a/gdb/arch/aarch64.h
+++ b/gdb/arch/aarch64.h
@@ -48,6 +48,9 @@ struct aarch64_features
 
      These use at most 5 bits to represent.  */
   uint8_t svq = 0;
+
+  /* Whether SME2 is supported.  */
+  bool sme2 = false;
 };
 
 inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs)
@@ -56,7 +59,8 @@ inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs)
     && lhs.pauth == rhs.pauth
     && lhs.mte == rhs.mte
     && lhs.tls == rhs.tls
-    && lhs.svq == rhs.svq;
+    && lhs.svq == rhs.svq
+    && lhs.sme2 == rhs.sme2;
 }
 
 namespace std
@@ -79,6 +83,9 @@ namespace std
       gdb_assert (features.svq >= 0);
       gdb_assert (features.svq <= 16);
       h = h << 5 | (features.svq & 0x5);
+
+      /* SME2 feature.  */
+      h = h << 1 | features.sme2;
       return h;
     }
   };
@@ -220,4 +227,7 @@ enum aarch64_regnum
 #define AARCH64_SME_MIN_SVL 128
 #define AARCH64_SME_MAX_SVL 2048
 
+/* Size of the SME2 ZT0 register in bytes.  */
+#define AARCH64_SME2_ZT0_SIZE 64
+
 #endif /* ARCH_AARCH64_H */
diff --git a/gdb/features/aarch64-sme2.c b/gdb/features/aarch64-sme2.c
new file mode 100644
index 00000000000..a14b98fae9d
--- /dev/null
+++ b/gdb/features/aarch64-sme2.c
@@ -0,0 +1,43 @@
+/* Copyright (C) 2023 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include "gdbsupport/tdesc.h"
+
+/* This function is NOT auto generated from xml.  Create the AArch64 SME2
+   feature into RESULT.
+
+   The ZT0 register is only available when the SME ZA register is
+   available.  */
+
+static int
+create_feature_aarch64_sme2 (struct target_desc *result, long regnum)
+{
+  struct tdesc_feature *feature;
+  tdesc_type *element_type;
+
+  feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.sme2");
+
+  /* Byte type.  */
+  element_type = tdesc_named_type (feature, "uint8");
+
+  /* Vector of 64 bytes.  */
+  element_type = tdesc_create_vector (feature, "sme2_bv", element_type, 64);
+
+  /* The following is the ZT0 register, with 512 bits (64 bytes).  */
+  tdesc_create_reg (feature, "zt0", regnum++, 1, nullptr, 512, "sme2_bv");
+  return regnum;
+}
diff --git a/gdb/nat/aarch64-scalable-linux-ptrace.c b/gdb/nat/aarch64-scalable-linux-ptrace.c
index d3a50ed0c81..dc0e45fa91e 100644
--- a/gdb/nat/aarch64-scalable-linux-ptrace.c
+++ b/gdb/nat/aarch64-scalable-linux-ptrace.c
@@ -519,10 +519,75 @@ aarch64_initialize_za_regset (int tid)
   if (ptrace (PTRACE_SETREGSET, tid, NT_ARM_ZA, &iovec) < 0)
     perror_with_name (_("Failed to initialize the NT_ARM_ZA register set."));
 
+  if (supports_zt_registers (tid))
+    {
+      /* If this target supports SME2, upon initializing ZA, we also need to
+	 initialize the ZT registers with 0 values.  Do so now.  */
+      gdb::byte_vector zt_new_state (AARCH64_SME2_ZT0_SIZE, 0);
+      aarch64_store_zt_regset (tid, zt_new_state);
+    }
+
   /* The NT_ARM_ZA register set should now contain a zero-initialized ZA
      payload.  */
 }
 
+/* See nat/aarch64-scalable-linux-ptrace.h.  */
+
+gdb::byte_vector
+aarch64_fetch_zt_regset (int tid)
+{
+  /* Read NT_ARM_ZT.  This register set is only available if
+     the ZA bit is non-zero.  */
+  gdb::byte_vector zt_state (AARCH64_SME2_ZT0_SIZE);
+
+  struct iovec iovec;
+  iovec.iov_len = AARCH64_SME2_ZT0_SIZE;
+  iovec.iov_base = zt_state.data ();
+
+  if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_ZT, &iovec) < 0)
+    perror_with_name (_("Failed to fetch NT_ARM_ZT register set."));
+
+  return zt_state;
+}
+
+/* See nat/aarch64-scalable-linux-ptrace.h.  */
+
+void
+aarch64_store_zt_regset (int tid, const gdb::byte_vector &zt_state)
+{
+  gdb_assert (zt_state.size () == AARCH64_SME2_ZT0_SIZE
+	      || zt_state.size () == 0);
+
+  /* We need to be mindful of writing data to NT_ARM_ZT.  If the ZA bit
+     is 0 and we write something to ZT, it will flip the ZA bit.
+
+     Right now this is taken care of by callers of this function.  */
+  struct iovec iovec;
+  iovec.iov_len = zt_state.size ();
+  iovec.iov_base = (void *) zt_state.data ();
+
+  /* Write the contents of ZT_STATE to the NT_ARM_ZT register set.  */
+  if (ptrace (PTRACE_SETREGSET, tid, NT_ARM_ZT, &iovec) < 0)
+    perror_with_name (_("Failed to write to the NT_ARM_ZT register set."));
+}
+
+/* See nat/aarch64-scalable-linux-ptrace.h.  */
+
+bool
+supports_zt_registers (int tid)
+{
+  gdb_byte zt_state[AARCH64_SME2_ZT0_SIZE];
+
+  struct iovec iovec;
+  iovec.iov_len = AARCH64_SME2_ZT0_SIZE;
+  iovec.iov_base = (void *) zt_state;
+
+  if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_ZT, &iovec) < 0)
+    return false;
+
+  return true;
+}
+
 /* If we are running in BE mode, byteswap the contents
    of SRC to DST for SIZE bytes.  Other, just copy the contents
    from SRC to DST.  */
@@ -989,3 +1054,72 @@ aarch64_za_regs_copy_from_reg_buf (int tid,
   /* At this point we have written the data contained in the register cache to
      the thread's NT_ARM_ZA register set.  */
 }
+
+/* See nat/aarch64-scalable-linux-ptrace.h.  */
+
+void
+aarch64_zt_regs_copy_to_reg_buf (int tid, struct reg_buffer_common *reg_buf,
+				 int zt_regnum)
+{
+  /* If we have ZA state, read the ZT state.  Otherwise, make the contents of
+     ZT in the register cache all zeroes.  This is how we present the ZT
+     state when it is not initialized (ZA not active).  */
+  if (aarch64_has_za_state (tid))
+    {
+      /* Fetch the current ZT state from the thread.  */
+      gdb::byte_vector zt_state = aarch64_fetch_zt_regset (tid);
+
+      /* Sanity check.  */
+      gdb_assert (!zt_state.empty ());
+
+      /* Copy the ZT data to the register buffer.  */
+      reg_buf->raw_supply (zt_regnum, zt_state.data ());
+    }
+  else
+    {
+      /* Zero out ZT.  */
+      gdb::byte_vector zt_zeroed (AARCH64_SME2_ZT0_SIZE, 0);
+      reg_buf->raw_supply (zt_regnum, zt_zeroed.data ());
+    }
+
+  /* The register buffer should now contain the updated copy of the NT_ARM_ZT
+     state.  */
+}
+
+/* See nat/aarch64-scalable-linux-ptrace.h.  */
+
+void
+aarch64_zt_regs_copy_from_reg_buf (int tid,
+				   struct reg_buffer_common *reg_buf,
+				   int zt_regnum)
+{
+  /* Do we have a valid ZA state?  */
+  bool valid_za = aarch64_has_za_state (tid);
+
+  /* Is the register buffer contents for ZT all zeroes?  */
+  gdb::byte_vector zt_bytes (AARCH64_SME2_ZT0_SIZE, 0);
+  bool zt_is_all_zeroes
+    = reg_buf->raw_compare (zt_regnum, zt_bytes.data (), 0);
+
+  /* If ZA state is valid or if we have non-zero data for ZT in the register
+     buffer, we will invoke ptrace to write the ZT state.  Otherwise we don't
+     have to do anything here.  */
+  if (valid_za || !zt_is_all_zeroes)
+    {
+      if (!valid_za)
+	{
+	  /* ZA state is not valid.  That means we need to initialize the ZA
+	     state prior to writing the ZT state.  */
+	  aarch64_initialize_za_regset (tid);
+	}
+
+      /* Extract the ZT data from the register buffer.  */
+      reg_buf->raw_collect (zt_regnum, zt_bytes.data ());
+
+      /* Write the ZT data to thread TID.  */
+      aarch64_store_zt_regset (tid, zt_bytes);
+    }
+
+  /* At this point we have (potentially) written the data contained in the
+     register cache to the thread's NT_ARM_ZT register set.  */
+}
diff --git a/gdb/nat/aarch64-scalable-linux-ptrace.h b/gdb/nat/aarch64-scalable-linux-ptrace.h
index d609933801d..69c9982b009 100644
--- a/gdb/nat/aarch64-scalable-linux-ptrace.h
+++ b/gdb/nat/aarch64-scalable-linux-ptrace.h
@@ -120,6 +120,16 @@ extern void aarch64_store_za_regset (int tid, const gdb::byte_vector &za_state);
    size.  The bytes of the ZA register are initialized to zero.  */
 extern void aarch64_initialize_za_regset (int tid);
 
+/* Given TID, return the NT_ARM_ZT register set data as a vector of bytes.  */
+extern gdb::byte_vector aarch64_fetch_zt_regset (int tid);
+
+/* Write ZT_STATE for TID.  */
+extern void aarch64_store_zt_regset (int tid, const gdb::byte_vector &zt_state);
+
+/* Return TRUE if thread TID supports the NT_ARM_ZT register set.
+   Return FALSE otherwise.  */
+extern bool supports_zt_registers (int tid);
+
 /* Given a register buffer REG_BUF, update it with SVE/SSVE register data
    from SVE_STATE.  */
 extern void
@@ -151,4 +161,20 @@ aarch64_za_regs_copy_from_reg_buf (int tid,
 				   struct reg_buffer_common *reg_buf,
 				   int za_regnum, int svg_regnum,
 				   int svcr_regnum);
+
+/* Given a thread id TID and a register buffer REG_BUF, update the register
+   buffer with the ZT register set state from thread TID.
+
+   ZT_REGNUM is the register number for ZT0.  */
+extern void
+aarch64_zt_regs_copy_to_reg_buf (int tid, struct reg_buffer_common *reg_buf,
+				 int zt_regnum);
+
+/* Given a thread id TID and a register buffer REG_BUF containing the ZT
+   register set state, write the ZT data to thread TID.
+
+   ZT_REGNUM is the register number for ZT0.  */
+extern void
+aarch64_zt_regs_copy_from_reg_buf (int tid, struct reg_buffer_common *reg_buf,
+				   int zt_regnum);
 #endif /* NAT_AARCH64_SCALABLE_LINUX_PTRACE_H */
