diff --git a/gdb/aarch64-fbsd-nat.c b/gdb/aarch64-fbsd-nat.c
index ecf7e4fb5a7..05fd641de32 100644
--- a/gdb/aarch64-fbsd-nat.c
+++ b/gdb/aarch64-fbsd-nat.c
@@ -93,7 +93,7 @@ aarch64_fbsd_nat_target::fetch_registers (struct regcache *regcache,
   aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
   if (tdep->has_tls ())
     fetch_regset<uint64_t> (regcache, regnum, NT_ARM_TLS,
-			    &aarch64_fbsd_tls_regset, tdep->tls_regnum);
+			    &aarch64_fbsd_tls_regset, tdep->tls_regnum_base);
 }
 
 /* Store register REGNUM back into the inferior.  If REGNUM is -1, do
@@ -112,7 +112,7 @@ aarch64_fbsd_nat_target::store_registers (struct regcache *regcache,
   aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
   if (tdep->has_tls ())
     store_regset<uint64_t> (regcache, regnum, NT_ARM_TLS,
-			    &aarch64_fbsd_tls_regset, tdep->tls_regnum);
+			    &aarch64_fbsd_tls_regset, tdep->tls_regnum_base);
 }
 
 /* Implement the target read_description method.  */
@@ -121,7 +121,7 @@ const struct target_desc *
 aarch64_fbsd_nat_target::read_description ()
 {
   aarch64_features features;
-  features.tls = have_regset (inferior_ptid, NT_ARM_TLS) != 0;
+  features.tls = have_regset (inferior_ptid, NT_ARM_TLS)? 1 : 0;
   return aarch64_read_description (features);
 }
 
diff --git a/gdb/aarch64-fbsd-tdep.c b/gdb/aarch64-fbsd-tdep.c
index 39d19355105..4df7e311b3c 100644
--- a/gdb/aarch64-fbsd-tdep.c
+++ b/gdb/aarch64-fbsd-tdep.c
@@ -50,7 +50,7 @@ static const struct regcache_map_entry aarch64_fbsd_fpregmap[] =
     { 0 }
   };
 
-/* Register numbers are relative to tdep->tls_regnum.  */
+/* Register numbers are relative to tdep->tls_regnum_base.  */
 
 static const struct regcache_map_entry aarch64_fbsd_tls_regmap[] =
   {
@@ -151,7 +151,7 @@ aarch64_fbsd_supply_tls_regset (const struct regset *regset,
   struct gdbarch *gdbarch = regcache->arch ();
   aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
 
-  regcache->supply_regset (regset, tdep->tls_regnum, regnum, buf, size);
+  regcache->supply_regset (regset, tdep->tls_regnum_base, regnum, buf, size);
 }
 
 static void
@@ -162,7 +162,7 @@ aarch64_fbsd_collect_tls_regset (const struct regset *regset,
   struct gdbarch *gdbarch = regcache->arch ();
   aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
 
-  regcache->collect_regset (regset, tdep->tls_regnum, regnum, buf, size);
+  regcache->collect_regset (regset, tdep->tls_regnum_base, regnum, buf, size);
 }
 
 const struct regset aarch64_fbsd_tls_regset =
@@ -201,7 +201,7 @@ aarch64_fbsd_core_read_description (struct gdbarch *gdbarch,
   asection *tls = bfd_get_section_by_name (abfd, ".reg-aarch-tls");
 
   aarch64_features features;
-  features.tls = tls != nullptr;
+  features.tls = tls != nullptr? 1 : 0;
 
   return aarch64_read_description (features);
 }
@@ -218,10 +218,10 @@ aarch64_fbsd_get_thread_local_address (struct gdbarch *gdbarch, ptid_t ptid,
   regcache = get_thread_arch_regcache (current_inferior ()->process_target (),
 				       ptid, gdbarch);
 
-  target_fetch_registers (regcache, tdep->tls_regnum);
+  target_fetch_registers (regcache, tdep->tls_regnum_base);
 
   ULONGEST tpidr;
-  if (regcache->cooked_read (tdep->tls_regnum, &tpidr) != REG_VALID)
+  if (regcache->cooked_read (tdep->tls_regnum_base, &tpidr) != REG_VALID)
     error (_("Unable to fetch %%tpidr"));
 
   /* %tpidr points to the TCB whose first member is the dtv
diff --git a/gdb/aarch64-linux-nat.c b/gdb/aarch64-linux-nat.c
index caefcb36485..0f4d0af8af6 100644
--- a/gdb/aarch64-linux-nat.c
+++ b/gdb/aarch64-linux-nat.c
@@ -440,21 +440,22 @@ fetch_tlsregs_from_thread (struct regcache *regcache)
 {
   aarch64_gdbarch_tdep *tdep
     = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
-  int regno = tdep->tls_regnum;
+  int regno = tdep->tls_regnum_base;
 
   gdb_assert (regno != -1);
+  gdb_assert (tdep->tls_register_count > 0);
 
-  uint64_t tpidr = 0;
+  uint64_t tpidrs[tdep->tls_register_count] = { 0 };
   struct iovec iovec;
-
-  iovec.iov_base = &tpidr;
-  iovec.iov_len = sizeof (tpidr);
+  iovec.iov_base = tpidrs;
+  iovec.iov_len = sizeof (tpidrs);
 
   int tid = get_ptrace_pid (regcache->ptid ());
   if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_TLS, &iovec) != 0)
-      perror_with_name (_("unable to fetch TLS register"));
+      perror_with_name (_("unable to fetch TLS registers"));
 
-  regcache->raw_supply (regno, &tpidr);
+  for (int i = 0; i < tdep->tls_register_count; i++)
+    regcache->raw_supply (regno + i, &tpidrs[i]);
 }
 
 /* Store to the current thread the valid TLS register set in GDB's
@@ -465,21 +466,24 @@ store_tlsregs_to_thread (struct regcache *regcache)
 {
   aarch64_gdbarch_tdep *tdep
     = gdbarch_tdep<aarch64_gdbarch_tdep> (regcache->arch ());
-  int regno = tdep->tls_regnum;
+  int regno = tdep->tls_regnum_base;
 
   gdb_assert (regno != -1);
+  gdb_assert (tdep->tls_register_count > 0);
 
-  uint64_t tpidr = 0;
+  uint64_t tpidrs[tdep->tls_register_count] = { 0 };
 
-  if (REG_VALID != regcache->get_register_status (regno))
-    return;
+  for (int i = 0; i < tdep->tls_register_count; i++)
+    {
+      if (REG_VALID != regcache->get_register_status (regno + i))
+	continue;
 
-  regcache->raw_collect (regno, (char *) &tpidr);
+      regcache->raw_collect (regno + i, (char *) &tpidrs[i]);
+    }
 
   struct iovec iovec;
-
-  iovec.iov_base = &tpidr;
-  iovec.iov_len = sizeof (tpidr);
+  iovec.iov_base = &tpidrs;
+  iovec.iov_len = sizeof (tpidrs);
 
   int tid = get_ptrace_pid (regcache->ptid ());
   if (ptrace (PTRACE_SETREGSET, tid, NT_ARM_TLS, &iovec) != 0)
@@ -531,7 +535,9 @@ aarch64_fetch_registers (struct regcache *regcache, int regno)
       && (regno == tdep->mte_reg_base))
     fetch_mteregs_from_thread (regcache);
 
-  if (tdep->has_tls () && regno == tdep->tls_regnum)
+  if (tdep->has_tls ()
+      && regno >= tdep->tls_regnum_base
+      && regno < tdep->tls_regnum_base + tdep->tls_register_count)
     fetch_tlsregs_from_thread (regcache);
 }
 
@@ -607,7 +613,9 @@ aarch64_store_registers (struct regcache *regcache, int regno)
       && (regno == tdep->mte_reg_base))
     store_mteregs_to_thread (regcache);
 
-  if (tdep->has_tls () && regno == tdep->tls_regnum)
+  if (tdep->has_tls ()
+      && regno >= tdep->tls_regnum_base
+      && regno < tdep->tls_regnum_base + tdep->tls_register_count)
     store_tlsregs_to_thread (regcache);
 }
 
@@ -788,7 +796,7 @@ aarch64_linux_nat_target::read_description ()
   features.vq = aarch64_sve_get_vq (tid);
   features.pauth = hwcap & AARCH64_HWCAP_PACA;
   features.mte = hwcap2 & HWCAP2_MTE;
-  features.tls = true;
+  features.tls = aarch64_tls_register_count (tid);
 
   return aarch64_read_description (features);
 }
diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c
index a321aee036a..69eb1b030bf 100644
--- a/gdb/aarch64-linux-tdep.c
+++ b/gdb/aarch64-linux-tdep.c
@@ -754,22 +754,30 @@ aarch64_linux_iterate_over_regset_sections (struct gdbarch *gdbarch,
 	  "MTE registers", cb_data);
     }
 
+  /* Handle the TLS registers.  */
   if (tdep->has_tls ())
     {
+      gdb_assert (tdep->tls_regnum_base != -1);
+      gdb_assert (tdep->tls_register_count > 0);
+
+      int sizeof_tls_regset
+	= AARCH64_TLS_REGISTER_SIZE * tdep->tls_register_count;
+
       const struct regcache_map_entry tls_regmap[] =
 	{
-	  { 1, tdep->tls_regnum, 8 },
+	  { tdep->tls_register_count, tdep->tls_regnum_base,
+	    AARCH64_TLS_REGISTER_SIZE },
 	  { 0 }
 	};
 
       const struct regset aarch64_linux_tls_regset =
 	{
-	  tls_regmap, regcache_supply_regset, regcache_collect_regset
+	  tls_regmap, regcache_supply_regset, regcache_collect_regset,
+	  REGSET_VARIABLE_SIZE
 	};
 
-      cb (".reg-aarch-tls", AARCH64_LINUX_SIZEOF_TLSREGSET,
-	  AARCH64_LINUX_SIZEOF_TLSREGSET, &aarch64_linux_tls_regset,
-	  "TLS register", cb_data);
+      cb (".reg-aarch-tls", sizeof_tls_regset, sizeof_tls_regset,
+	  &aarch64_linux_tls_regset, "TLS register", cb_data);
     }
 }
 
@@ -779,7 +787,6 @@ static const struct target_desc *
 aarch64_linux_core_read_description (struct gdbarch *gdbarch,
 				     struct target_ops *target, bfd *abfd)
 {
-  asection *tls = bfd_get_section_by_name (abfd, ".reg-aarch-tls");
   gdb::optional<gdb::byte_vector> auxv = target_read_auxv_raw (target);
   CORE_ADDR hwcap = linux_get_hwcap (auxv, target, gdbarch);
   CORE_ADDR hwcap2 = linux_get_hwcap2 (auxv, target, gdbarch);
@@ -788,7 +795,16 @@ aarch64_linux_core_read_description (struct gdbarch *gdbarch,
   features.vq = aarch64_linux_core_read_vq (gdbarch, abfd);
   features.pauth = hwcap & AARCH64_HWCAP_PACA;
   features.mte = hwcap2 & HWCAP2_MTE;
-  features.tls = tls != nullptr;
+
+  /* Handle the TLS section.  */
+  asection *tls = bfd_get_section_by_name (abfd, ".reg-aarch-tls");
+  if (tls != nullptr)
+    {
+      size_t size = bfd_section_size (tls);
+      /* Convert the size to the number of actual registers, by
+	 dividing by 8.  */
+      features.tls = size / AARCH64_TLS_REGISTER_SIZE;
+    }
 
   return aarch64_read_description (features);
 }
diff --git a/gdb/aarch64-linux-tdep.h b/gdb/aarch64-linux-tdep.h
index 9a7e4339dba..8ae33efc605 100644
--- a/gdb/aarch64-linux-tdep.h
+++ b/gdb/aarch64-linux-tdep.h
@@ -39,9 +39,6 @@
 /* The MTE regset consists of a 64-bit register.  */
 #define AARCH64_LINUX_SIZEOF_MTE_REGSET (8)
 
-/* The TLS regset consists of a single register.  */
-#define AARCH64_LINUX_SIZEOF_TLSREGSET (X_REGISTER_SIZE)
-
 extern const struct regset aarch64_linux_gregset;
 extern const struct regset aarch64_linux_fpregset;
 
diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c
index cf20bb40b78..024385a9fd8 100644
--- a/gdb/aarch64-tdep.c
+++ b/gdb/aarch64-tdep.c
@@ -3465,8 +3465,18 @@ aarch64_features_from_target_desc (const struct target_desc *tdesc)
       = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.pauth") != nullptr);
   features.mte
       = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.mte") != nullptr);
-  features.tls
-      = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.tls") != nullptr);
+
+  const struct tdesc_feature *tls_feature
+    = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.tls");
+
+  if (tls_feature != nullptr)
+    {
+      /* We have TLS registers.  Find out how many.  */
+      if (tdesc_unnumbered_register (tls_feature, "tpidr2"))
+	features.tls = 2;
+      else
+	features.tls = 1;
+    }
 
   return features;
 }
@@ -3526,7 +3536,7 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   bool valid_p = true;
   int i, num_regs = 0, num_pseudo_regs = 0;
   int first_pauth_regnum = -1, ra_sign_state_offset = -1;
-  int first_mte_regnum = -1, tls_regnum = -1;
+  int first_mte_regnum = -1, first_tls_regnum = -1;
   uint64_t vq = aarch64_get_tdesc_vq (info.target_desc);
 
   if (vq > AARCH64_MAX_SVE_VQ)
@@ -3614,15 +3624,38 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
     }
 
   /* Add the TLS register.  */
+  int tls_register_count = 0;
   if (feature_tls != nullptr)
     {
-      tls_regnum = num_regs;
-      /* Validate the descriptor provides the mandatory TLS register
-	 and allocate its number.  */
-      valid_p = tdesc_numbered_register (feature_tls, tdesc_data.get (),
-					 tls_regnum, "tpidr");
+      first_tls_regnum = num_regs;
 
-      num_regs++;
+      /* Look for the TLS registers.  tpidr is required, but tpidr2 is
+	 optional.  */
+      valid_p
+	= tdesc_numbered_register (feature_tls, tdesc_data.get (),
+				   first_tls_regnum, "tpidr");
+
+      if (valid_p)
+	{
+	  tls_register_count++;
+
+	  bool has_tpidr2
+	    = tdesc_numbered_register (feature_tls, tdesc_data.get (),
+				       first_tls_regnum + tls_register_count,
+				       "tpidr2");
+
+	  /* Figure out how many TLS registers we have.  */
+	  if (has_tpidr2)
+	    tls_register_count++;
+
+	  num_regs += tls_register_count;
+	}
+      else
+	{
+	  warning (_("Provided TLS register feature doesn't contain "
+		     "required tpidr register."));
+	  return nullptr;
+	}
     }
 
   /* Add the pauth registers.  */
@@ -3675,7 +3708,8 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   tdep->pauth_reg_base = first_pauth_regnum;
   tdep->ra_sign_state_regnum = -1;
   tdep->mte_reg_base = first_mte_regnum;
-  tdep->tls_regnum = tls_regnum;
+  tdep->tls_regnum_base = first_tls_regnum;
+  tdep->tls_register_count = tls_register_count;
 
   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 55ccf2e777d..ff94c0a23b0 100644
--- a/gdb/aarch64-tdep.h
+++ b/gdb/aarch64-tdep.h
@@ -111,12 +111,13 @@ struct aarch64_gdbarch_tdep : gdbarch_tdep_base
     return mte_reg_base != -1;
   }
 
-  /* TLS register.  This is -1 if the TLS register is not available.  */
-  int tls_regnum = 0;
+  /* TLS registers.  This is -1 if the TLS registers are not available.  */
+  int tls_regnum_base = 0;
+  int tls_register_count = 0;
 
   bool has_tls() const
   {
-    return tls_regnum != -1;
+    return tls_regnum_base != -1;
   }
 
   /* The W pseudo-registers.  */
diff --git a/gdb/arch/aarch64.c b/gdb/arch/aarch64.c
index 0f73286f145..565c5e7a81c 100644
--- a/gdb/arch/aarch64.c
+++ b/gdb/arch/aarch64.c
@@ -53,8 +53,9 @@ aarch64_create_target_description (const aarch64_features &features)
   if (features.mte)
     regnum = create_feature_aarch64_mte (tdesc.get (), regnum);
 
-  if (features.tls)
-    regnum = create_feature_aarch64_tls (tdesc.get (), regnum);
+  /* TLS registers.  */
+  if (features.tls > 0)
+    regnum = create_feature_aarch64_tls (tdesc.get (), regnum, features.tls);
 
   return tdesc.release ();
 }
diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h
index 8e3fd36726a..b1a6ce3ef0e 100644
--- a/gdb/arch/aarch64.h
+++ b/gdb/arch/aarch64.h
@@ -33,7 +33,9 @@ struct aarch64_features
 
   bool pauth = false;
   bool mte = false;
-  bool tls = false;
+
+  /* A positive TLS value indicates the number of TLS registers available.  */
+  uint8_t tls = 0;
 };
 
 inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs)
@@ -56,7 +58,9 @@ namespace std
       h = features.vq;
       h = h << 1 | features.pauth;
       h = h << 1 | features.mte;
-      h = h << 1 | features.tls;
+      /* Shift by two bits for now.  We may need to increase this in the future
+	 if more TLS registers get added.  */
+      h = h << 2 | features.tls;
       return h;
     }
   };
@@ -96,7 +100,9 @@ enum aarch64_regnum
   AARCH64_LAST_V_ARG_REGNUM = AARCH64_V0_REGNUM + 7
 };
 
-#define V_REGISTER_SIZE 16
+/* Sizes of various AArch64 registers.  */
+#define AARCH64_TLS_REGISTER_SIZE 8
+#define V_REGISTER_SIZE	  16
 
 /* Pseudo register base numbers.  */
 #define AARCH64_Q0_REGNUM 0
@@ -117,8 +123,6 @@ enum aarch64_regnum
 #define AARCH64_NUM_REGS AARCH64_FPCR_REGNUM + 1
 #define AARCH64_SVE_NUM_REGS AARCH64_SVE_VG_REGNUM + 1
 
-#define	AARCH64_TLS_REGS_SIZE (8)
-
 /* There are a number of ways of expressing the current SVE vector size:
 
    VL : Vector Length.
diff --git a/gdb/features/Makefile b/gdb/features/Makefile
index c3e07809db3..f260849636b 100644
--- a/gdb/features/Makefile
+++ b/gdb/features/Makefile
@@ -202,7 +202,6 @@ FEATURE_XMLFILES = aarch64-core.xml \
 	aarch64-fpu.xml \
 	aarch64-pauth.xml \
 	aarch64-mte.xml \
-	aarch64-tls.xml \
 	arc/v1-core.xml \
 	arc/v1-aux.xml \
 	arc/v2-core.xml \
diff --git a/gdb/features/aarch64-tls.c b/gdb/features/aarch64-tls.c
index 30d730dffba..8a59d26b353 100644
--- a/gdb/features/aarch64-tls.c
+++ b/gdb/features/aarch64-tls.c
@@ -1,14 +1,43 @@
-/* THIS FILE IS GENERATED.  -*- buffer-read-only: t -*- vi:set ro:
-  Original: aarch64-tls.xml */
+/* Copyright (C) 2022 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 description containing the TLS registers.  TPIDR is
+   always available, but TPIDR2 is only available on some systems.
+
+   COUNT is the number of registers in this set.  The minimum is 1.  */
+
 static int
-create_feature_aarch64_tls (struct target_desc *result, long regnum)
+create_feature_aarch64_tls (struct target_desc *result, long regnum, int count)
 {
+  /* TPIDR is always present.  */
+  gdb_assert (count >= 1);
+
   struct tdesc_feature *feature;
 
   feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.tls");
   tdesc_create_reg (feature, "tpidr", regnum++, 1, NULL, 64, "data_ptr");
+
+  /* Add TPIDR2.  */
+  if (count > 1)
+    tdesc_create_reg (feature, "tpidr2", regnum++, 1, NULL, 64, "data_ptr");
+
   return regnum;
 }
diff --git a/gdb/features/aarch64-tls.xml b/gdb/features/aarch64-tls.xml
deleted file mode 100644
index f6437785f71..00000000000
--- a/gdb/features/aarch64-tls.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<?xml version="1.0"?>
-<!-- Copyright (C) 2022 Free Software Foundation, Inc.
-
-     Copying and distribution of this file, with or without modification,
-     are permitted in any medium without royalty provided the copyright
-     notice and this notice are preserved.  -->
-
-<!DOCTYPE feature SYSTEM "gdb-target.dtd">
-<feature name="org.gnu.gdb.aarch64.tls">
-  <reg name="tpidr" bitsize="64" type="data_ptr"/>
-</feature>
diff --git a/gdb/nat/aarch64-linux.c b/gdb/nat/aarch64-linux.c
index 421d1ecb53c..07aaeaa01da 100644
--- a/gdb/nat/aarch64-linux.c
+++ b/gdb/nat/aarch64-linux.c
@@ -250,3 +250,24 @@ aarch64_ps_get_thread_area (struct ps_prochandle *ph,
 
   return PS_OK;
 }
+
+/* See nat/aarch64-linux.h.  */
+
+int
+aarch64_tls_register_count (int tid)
+{
+  uint64_t tls_regs[2];
+  struct iovec iovec;
+  iovec.iov_base = tls_regs;
+  iovec.iov_len = sizeof (tls_regs);
+
+  /* Attempt to read both TPIDR and TPIDR2.  If the request fails, it means
+     the Linux Kernel does not support TPIDR2.
+
+     Otherwise the Linux Kernel supports both TPIDR and TPIDR2.  */
+  if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_TLS, &iovec) != 0)
+    return 1;
+
+  /* TPIDR2 is available as well.  */
+  return 2;
+}
diff --git a/gdb/nat/aarch64-linux.h b/gdb/nat/aarch64-linux.h
index 1777ce32522..517cbb33963 100644
--- a/gdb/nat/aarch64-linux.h
+++ b/gdb/nat/aarch64-linux.h
@@ -129,4 +129,8 @@ ps_err_e aarch64_ps_get_thread_area (struct ps_prochandle *ph,
 				       lwpid_t lwpid, int idx, void **base,
 				       int is_64bit_p);
 
+/* Return the number of TLS registers in the NT_ARM_TLS set.  This is only
+   used for aarch64 state.  */
+int aarch64_tls_register_count (int tid);
+
 #endif /* NAT_AARCH64_LINUX_H */
diff --git a/gdbserver/linux-aarch64-low.cc b/gdbserver/linux-aarch64-low.cc
index db508696261..b657a265ee7 100644
--- a/gdbserver/linux-aarch64-low.cc
+++ b/gdbserver/linux-aarch64-low.cc
@@ -292,9 +292,16 @@ aarch64_store_mteregset (struct regcache *regcache, const void *buf)
 static void
 aarch64_fill_tlsregset (struct regcache *regcache, void *buf)
 {
+  gdb_byte *tls_buf = (gdb_byte *) buf;
   int tls_regnum  = find_regno (regcache->tdesc, "tpidr");
 
-  collect_register (regcache, tls_regnum, buf);
+  collect_register (regcache, tls_regnum, tls_buf);
+
+  /* Read TPIDR2, if it exists.  */
+  gdb::optional<int> regnum = find_regno_no_throw (regcache->tdesc, "tpidr2");
+
+  if (regnum.has_value ())
+    collect_register (regcache, *regnum, tls_buf + sizeof (uint64_t));
 }
 
 /* Store TLS register to regcache.  */
@@ -302,9 +309,16 @@ aarch64_fill_tlsregset (struct regcache *regcache, void *buf)
 static void
 aarch64_store_tlsregset (struct regcache *regcache, const void *buf)
 {
+  gdb_byte *tls_buf = (gdb_byte *) buf;
   int tls_regnum  = find_regno (regcache->tdesc, "tpidr");
 
-  supply_register (regcache, tls_regnum, buf);
+  supply_register (regcache, tls_regnum, tls_buf);
+
+  /* Write TPIDR2, if it exists.  */
+  gdb::optional<int> regnum = find_regno_no_throw (regcache->tdesc, "tpidr2");
+
+  if (regnum.has_value ())
+    supply_register (regcache, *regnum, tls_buf + sizeof (uint64_t));
 }
 
 bool
@@ -795,8 +809,8 @@ aarch64_adjust_register_sets (const struct aarch64_features &features)
 	    regset->size = AARCH64_LINUX_SIZEOF_MTE;
 	  break;
 	case NT_ARM_TLS:
-	  if (features.tls)
-	    regset->size = AARCH64_TLS_REGS_SIZE;
+	  if (features.tls > 0)
+	    regset->size = AARCH64_TLS_REGISTER_SIZE * features.tls;
 	  break;
 	default:
 	  gdb_assert_not_reached ("Unknown register set found.");
@@ -829,7 +843,7 @@ aarch64_target::low_arch_setup ()
       features.pauth = linux_get_hwcap (8) & AARCH64_HWCAP_PACA;
       /* A-profile MTE is 64-bit only.  */
       features.mte = linux_get_hwcap2 (8) & HWCAP2_MTE;
-      features.tls = true;
+      features.tls = aarch64_tls_register_count (tid);
 
       current_process ()->tdesc = aarch64_linux_read_description (features);
 
diff --git a/gdbserver/regcache.cc b/gdbserver/regcache.cc
index 5cbcea978a0..1c84ef674bf 100644
--- a/gdbserver/regcache.cc
+++ b/gdbserver/regcache.cc
@@ -244,16 +244,28 @@ registers_from_string (struct regcache *regcache, char *buf)
   hex2bin (buf, registers, len / 2);
 }
 
-int
-find_regno (const struct target_desc *tdesc, const char *name)
+/* See regcache.h */
+
+gdb::optional<int>
+find_regno_no_throw (const struct target_desc *tdesc, const char *name)
 {
   for (int i = 0; i < tdesc->reg_defs.size (); ++i)
     {
       if (strcmp (name, find_register_by_number (tdesc, i).name) == 0)
 	return i;
     }
-  internal_error ("Unknown register %s requested",
-		  name);
+  return {};
+}
+
+int
+find_regno (const struct target_desc *tdesc, const char *name)
+{
+  gdb::optional<int> regnum = find_regno_no_throw (tdesc, name);
+
+  if (regnum.has_value ())
+    return *regnum;
+
+  internal_error ("Unknown register %s requested", name);
 }
 
 static void
diff --git a/gdbserver/regcache.h b/gdbserver/regcache.h
index 731c18d16e2..b67eefbe04b 100644
--- a/gdbserver/regcache.h
+++ b/gdbserver/regcache.h
@@ -110,6 +110,11 @@ int register_cache_size (const struct target_desc *tdesc);
 
 int register_size (const struct target_desc *tdesc, int n);
 
+/* No throw version of find_regno.  If NAME is not a known register, return
+   an empty value.  */
+gdb::optional<int> find_regno_no_throw (const struct target_desc *tdesc,
+					const char *name);
+
 int find_regno (const struct target_desc *tdesc, const char *name);
 
 void supply_register (struct regcache *regcache, int n, const void *buf);
