[v4,1/6,gdb/aarch64] sme2: Enable SME2 for AArch64 gdb on Linux

Message ID 20230913101840.179101-2-luis.machado@arm.com
State New
Headers
Series SME2 support for AArch64 gdb/gdbserver on Linux |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 fail Patch failed to apply
linaro-tcwg-bot/tcwg_gdb_build--master-arm fail Patch failed to apply
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 fail Patch failed to apply
linaro-tcwg-bot/tcwg_gdb_check--master-arm fail Patch failed to apply

Commit Message

Luis Machado Sept. 13, 2023, 10:18 a.m. UTC
  v3:

- Adjust function comment about the use of regcache's thread.

--

SME2 defines a new 512-bit register named ZT0, and it is only available
if SME is also supported.  The ZT0 state is valid only if the SVCR ZA bit
is enabled.  Otherwise its contents are empty (0).

The target description is dynamic and gets generated at runtime based on the
availability of the feature.

Validated under Fast Models.
---
 gdb/aarch64-linux-nat.c                 |  49 +++++++++
 gdb/aarch64-tdep.c                      |  21 ++++
 gdb/aarch64-tdep.h                      |  10 ++
 gdb/arch/aarch64-scalable-linux.h       |   6 ++
 gdb/arch/aarch64.c                      |   4 +
 gdb/arch/aarch64.h                      |  12 ++-
 gdb/features/aarch64-sme2.c             |  43 ++++++++
 gdb/nat/aarch64-scalable-linux-ptrace.c | 134 ++++++++++++++++++++++++
 gdb/nat/aarch64-scalable-linux-ptrace.h |  26 +++++
 9 files changed, 304 insertions(+), 1 deletion(-)
 create mode 100644 gdb/features/aarch64-sme2.c
  

Patch

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 e2fbe102472..f2f97a57eaf 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 */