binutils: backport patches from unstream to fix A53 errata.

This is ported from upstream binutils branch
commit: cde98f8566e14f52b896abc92c357cdd14717505
[AArch64] Cortex-A53 erratum 835769 linker workaround

BUG=chromium:430666
TEST=cbuildbot daisy-release, x86-alex-release, falco-release

Change-Id: I4051be49e1a203d1673f695b430738f8b2bf0e15
Reviewed-on: https://chromium-review.googlesource.com/228604
Reviewed-by: Luis Lozano <[email protected]>
Reviewed-by: Mike Frysinger <[email protected]>
Commit-Queue: Yunlian Jiang <[email protected]>
Tested-by: Yunlian Jiang <[email protected]>
diff --git a/bfd/bfd-in.h b/bfd/bfd-in.h
index c7c5a7d..cbc49ae 100644
--- a/bfd/bfd-in.h
+++ b/bfd/bfd-in.h
@@ -931,10 +931,10 @@
   (bfd *);
 
 extern void bfd_elf64_aarch64_set_options
-  (bfd *, struct bfd_link_info *, int, int, int);
+  (bfd *, struct bfd_link_info *, int, int, int, int);
 
 extern void bfd_elf32_aarch64_set_options
-  (bfd *, struct bfd_link_info *, int, int, int);
+  (bfd *, struct bfd_link_info *, int, int, int, int);
 
 /* ELF AArch64 mapping symbol support.  */
 #define BFD_AARCH64_SPECIAL_SYM_TYPE_MAP	(1 << 0)
diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index 99c5bf8..2a9752e 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -938,10 +938,10 @@
   (bfd *);
 
 extern void bfd_elf64_aarch64_set_options
-  (bfd *, struct bfd_link_info *, int, int, int);
+  (bfd *, struct bfd_link_info *, int, int, int, int);
 
 extern void bfd_elf32_aarch64_set_options
-  (bfd *, struct bfd_link_info *, int, int, int);
+  (bfd *, struct bfd_link_info *, int, int, int, int);
 
 /* ELF AArch64 mapping symbol support.  */
 #define BFD_AARCH64_SPECIAL_SYM_TYPE_MAP	(1 << 0)
diff --git a/bfd/elfnn-aarch64.c b/bfd/elfnn-aarch64.c
index 6096d5f..c527ad2 100644
--- a/bfd/elfnn-aarch64.c
+++ b/bfd/elfnn-aarch64.c
@@ -1611,6 +1611,12 @@
   0x00000000,
 };
 
+static const uint32_t aarch64_erratum_835769_stub[] =
+{
+  0x00000000,    /* Placeholder for multiply accumulate.  */
+  0x14000000,    /* b <label> */
+};
+
 /* Section name for stubs is the associated section name plus this
    string.  */
 #define STUB_SUFFIX ".stub"
@@ -1620,6 +1626,7 @@
   aarch64_stub_none,
   aarch64_stub_adrp_branch,
   aarch64_stub_long_branch,
+  aarch64_stub_erratum_835769_veneer,
 };
 
 struct elf_aarch64_stub_hash_entry
@@ -1654,6 +1661,10 @@
      stub name in the hash table has to be unique; this does not, so
      it can be friendlier.  */
   char *output_name;
+
+  /* The instruction which caused this stub to be generated (only valid for
+     erratum 835769 workaround stubs at present).  */
+  uint32_t veneered_insn;
 };
 
 /* Used to build a map of a section.  This is required for mixed-endian
@@ -1679,6 +1690,17 @@
 #define elf_aarch64_section_data(sec) \
   ((_aarch64_elf_section_data *) elf_section_data (sec))
 
+/* A fix-descriptor for erratum 835769.  */
+struct aarch64_erratum_835769_fix
+{
+  bfd *input_bfd;
+  asection *section;
+  bfd_vma offset;
+  uint32_t veneered_insn;
+  char *stub_name;
+  enum elf_aarch64_stub_type stub_type;
+};
+
 /* The size of the thread control block.  */
 #define TCB_SIZE	16
 
@@ -1799,6 +1821,15 @@
   /* Nonzero to force PIC branch veneers.  */
   int pic_veneer;
 
+  /* Fix erratum 835769.  */
+  int fix_erratum_835769;
+
+  /* A table of fix locations for erratum 835769.  This holds erratum
+     fix locations between elfNN_aarch64_size_stubs() and
+     elfNN_aarch64_write_section().  */
+  struct aarch64_erratum_835769_fix *aarch64_erratum_835769_fixes;
+  unsigned int num_aarch64_erratum_835769_fixes;
+
   /* The number of bytes in the initial entry in the PLT.  */
   bfd_size_type plt_header_size;
 
@@ -2342,6 +2373,9 @@
   bfd *stub_bfd;
   bfd_byte *loc;
   bfd_vma sym_value;
+  bfd_vma veneered_insn_loc;
+  bfd_vma veneer_entry_loc;
+  bfd_signed_vma branch_offset = 0;
   unsigned int template_size;
   const uint32_t *template;
   unsigned int i;
@@ -2382,6 +2416,10 @@
       template = aarch64_long_branch_stub;
       template_size = sizeof (aarch64_long_branch_stub);
       break;
+    case aarch64_stub_erratum_835769_veneer:
+      template = aarch64_erratum_835769_stub;
+      template_size = sizeof (aarch64_erratum_835769_stub);
+      break;
     default:
       BFD_FAIL ();
       return FALSE;
@@ -2424,6 +2462,23 @@
 				stub_entry->stub_offset + 16,
 				sym_value + 12, 0);
       break;
+
+    case aarch64_stub_erratum_835769_veneer:
+      veneered_insn_loc = stub_entry->target_section->output_section->vma
+			  + stub_entry->target_section->output_offset
+			  + stub_entry->target_value;
+      veneer_entry_loc = stub_entry->stub_sec->output_section->vma
+			  + stub_entry->stub_sec->output_offset
+			  + stub_entry->stub_offset;
+      branch_offset = veneered_insn_loc - veneer_entry_loc;
+      branch_offset >>= 2;
+      branch_offset &= 0x3ffffff;
+      bfd_putl32 (stub_entry->veneered_insn,
+		  stub_sec->contents + stub_entry->stub_offset);
+      bfd_putl32 (template[1] | branch_offset,
+		  stub_sec->contents + stub_entry->stub_offset + 4);
+      break;
+
     default:
       break;
     }
@@ -2452,6 +2507,9 @@
     case aarch64_stub_long_branch:
       size = sizeof (aarch64_long_branch_stub);
       break;
+    case aarch64_stub_erratum_835769_veneer:
+      size = sizeof (aarch64_erratum_835769_stub);
+      break;
     default:
       BFD_FAIL ();
       return FALSE;
@@ -2640,6 +2698,388 @@
 
 #undef PREV_SEC
 
+#define AARCH64_BITS(x, pos, n) (((x) >> (pos)) & ((1 << (n)) - 1))
+
+#define AARCH64_RT(insn) AARCH64_BITS (insn, 0, 5)
+#define AARCH64_RT2(insn) AARCH64_BITS (insn, 10, 5)
+#define AARCH64_RA(insn) AARCH64_BITS (insn, 10, 5)
+#define AARCH64_RD(insn) AARCH64_BITS (insn, 0, 5)
+#define AARCH64_RN(insn) AARCH64_BITS (insn, 5, 5)
+#define AARCH64_RM(insn) AARCH64_BITS (insn, 16, 5)
+
+#define AARCH64_MAC(insn) (((insn) & 0xff000000) == 0x9b000000)
+#define AARCH64_BIT(insn, n) AARCH64_BITS (insn, n, 1)
+#define AARCH64_OP31(insn) AARCH64_BITS (insn, 21, 3)
+#define AARCH64_ZR 0x1f
+
+/* All ld/st ops.  See C4-182 of the ARM ARM.  The encoding space for
+   LD_PCREL, LDST_RO, LDST_UI and LDST_UIMM cover prefetch ops.  */
+
+#define AARCH64_LD(insn) (AARCH64_BIT (insn, 22) == 1)
+#define AARCH64_LDST(insn) (((insn) & 0x0a000000) == 0x08000000)
+#define AARCH64_LDST_EX(insn) (((insn) & 0x3f000000) == 0x08000000)
+#define AARCH64_LDST_PCREL(insn) (((insn) & 0x3b000000) == 0x18000000)
+#define AARCH64_LDST_NAP(insn) (((insn) & 0x3b800000) == 0x28000000)
+#define AARCH64_LDSTP_PI(insn) (((insn) & 0x3b800000) == 0x28800000)
+#define AARCH64_LDSTP_O(insn) (((insn) & 0x3b800000) == 0x29000000)
+#define AARCH64_LDSTP_PRE(insn) (((insn) & 0x3b800000) == 0x29800000)
+#define AARCH64_LDST_UI(insn) (((insn) & 0x3b200c00) == 0x38000000)
+#define AARCH64_LDST_PIIMM(insn) (((insn) & 0x3b200c00) == 0x38000400)
+#define AARCH64_LDST_U(insn) (((insn) & 0x3b200c00) == 0x38000800)
+#define AARCH64_LDST_PREIMM(insn) (((insn) & 0x3b200c00) == 0x38000c00)
+#define AARCH64_LDST_RO(insn) (((insn) & 0x3b200c00) == 0x38200800)
+#define AARCH64_LDST_UIMM(insn) (((insn) & 0x3b000000) == 0x39000000)
+#define AARCH64_LDST_SIMD_M(insn) (((insn) & 0xbfbf0000) == 0x0c000000)
+#define AARCH64_LDST_SIMD_M_PI(insn) (((insn) & 0xbfa00000) == 0x0c800000)
+#define AARCH64_LDST_SIMD_S(insn) (((insn) & 0xbf9f0000) == 0x0d000000)
+#define AARCH64_LDST_SIMD_S_PI(insn) (((insn) & 0xbf800000) == 0x0d800000)
+
+/* Classify an INSN if it is indeed a load/store.  Return TRUE if INSN
+   is a load/store along with the Rt and Rtn.  Return FALSE if not a
+   load/store.  */
+
+static bfd_boolean
+aarch64_mem_op_p (uint32_t insn, unsigned int *rt, unsigned int *rtn,
+		  bfd_boolean *pair, bfd_boolean *load)
+{
+  uint32_t opcode;
+  unsigned int r;
+  uint32_t opc = 0;
+  uint32_t v = 0;
+  uint32_t opc_v = 0;
+
+  /* Bail out quickly if INSN doesn't fall into the the load-store
+     encoding space.  */
+  if (!AARCH64_LDST (insn))
+    return FALSE;
+
+  *pair = FALSE;
+  *load = FALSE;
+  if (AARCH64_LDST_EX (insn))
+    {
+      *rt = AARCH64_RT (insn);
+      *rtn = *rt;
+      if (AARCH64_BIT (insn, 21) == 1)
+        {
+	  *pair = TRUE;
+	  *rtn = AARCH64_RT2 (insn);
+	}
+      *load = AARCH64_LD (insn);
+      return TRUE;
+    }
+  else if (AARCH64_LDST_NAP (insn)
+	   || AARCH64_LDSTP_PI (insn)
+	   || AARCH64_LDSTP_O (insn)
+	   || AARCH64_LDSTP_PRE (insn))
+    {
+      *pair = TRUE;
+      *rt = AARCH64_RT (insn);
+      *rtn = AARCH64_RT2 (insn);
+      *load = AARCH64_LD (insn);
+      return TRUE;
+    }
+  else if (AARCH64_LDST_PCREL (insn)
+	   || AARCH64_LDST_UI (insn)
+	   || AARCH64_LDST_PIIMM (insn)
+	   || AARCH64_LDST_U (insn)
+	   || AARCH64_LDST_PREIMM (insn)
+	   || AARCH64_LDST_RO (insn)
+	   || AARCH64_LDST_UIMM (insn))
+   {
+      *rt = AARCH64_RT (insn);
+      *rtn = *rt;
+      if (AARCH64_LDST_PCREL (insn))
+	*load = TRUE;
+      opc = AARCH64_BITS (insn, 22, 2);
+      v = AARCH64_BIT (insn, 26);
+      opc_v = opc | (v << 2);
+      *load =  (opc_v == 1 || opc_v == 2 || opc_v == 3
+		|| opc_v == 5 || opc_v == 7);
+      return TRUE;
+   }
+  else if (AARCH64_LDST_SIMD_M (insn)
+	   || AARCH64_LDST_SIMD_M_PI (insn))
+    {
+      *rt = AARCH64_RT (insn);
+      *load = AARCH64_BIT (insn, 22);
+      opcode = (insn >> 12) & 0xf;
+      switch (opcode)
+	{
+	case 0:
+	case 2:
+	  *rtn = *rt + 3;
+	  break;
+
+	case 4:
+	case 6:
+	  *rtn = *rt + 2;
+	  break;
+
+	case 7:
+	  *rtn = *rt;
+	  break;
+
+	case 8:
+	case 10:
+	  *rtn = *rt + 1;
+	  break;
+
+	default:
+	  return FALSE;
+	}
+      return TRUE;
+    }
+  else if (AARCH64_LDST_SIMD_S (insn)
+	   || AARCH64_LDST_SIMD_S_PI (insn))
+    {
+      *rt = AARCH64_RT (insn);
+      r = (insn >> 21) & 1;
+      *load = AARCH64_BIT (insn, 22);
+      opcode = (insn >> 13) & 0x7;
+      switch (opcode)
+	{
+	case 0:
+	case 2:
+	case 4:
+	  *rtn = *rt + r;
+	  break;
+
+	case 1:
+	case 3:
+	case 5:
+	  *rtn = *rt + (r == 0 ? 2 : 3);
+	  break;
+
+	case 6:
+	  *rtn = *rt + r;
+	  break;
+
+	case 7:
+	  *rtn = *rt + (r == 0 ? 2 : 3);
+	  break;
+
+	default:
+	  return FALSE;
+	}
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/* Return TRUE if INSN is multiply-accumulate.  */
+
+static bfd_boolean
+aarch64_mlxl_p (uint32_t insn)
+{
+  uint32_t op31 = AARCH64_OP31 (insn);
+
+  if (AARCH64_MAC (insn)
+      && (op31 == 0 || op31 == 1 || op31 == 5)
+      /* Exclude MUL instructions which are encoded as a multiple accumulate
+	 with RA = XZR.  */
+      && AARCH64_RA (insn) != AARCH64_ZR)
+    return TRUE;
+
+  return FALSE;
+}
+
+/* Some early revisions of the Cortex-A53 have an erratum (835769) whereby
+   it is possible for a 64-bit multiply-accumulate instruction to generate an
+   incorrect result.  The details are quite complex and hard to
+   determine statically, since branches in the code may exist in some
+   circumstances, but all cases end with a memory (load, store, or
+   prefetch) instruction followed immediately by the multiply-accumulate
+   operation.  We employ a linker patching technique, by moving the potentially
+   affected multiply-accumulate instruction into a patch region and replacing
+   the original instruction with a branch to the patch.  This function checks
+   if INSN_1 is the memory operation followed by a multiply-accumulate
+   operation (INSN_2).  Return TRUE if an erratum sequence is found, FALSE
+   if INSN_1 and INSN_2 are safe.  */
+
+static bfd_boolean
+aarch64_erratum_sequence (uint32_t insn_1, uint32_t insn_2)
+{
+  uint32_t rt;
+  uint32_t rtn;
+  uint32_t rn;
+  uint32_t rm;
+  uint32_t ra;
+  bfd_boolean pair;
+  bfd_boolean load;
+
+  if (aarch64_mlxl_p (insn_2)
+      && aarch64_mem_op_p (insn_1, &rt, &rtn, &pair, &load))
+    {
+      /* Any SIMD memory op is independent of the subsequent MLA
+	 by definition of the erratum.  */
+      if (AARCH64_BIT (insn_1, 26))
+	return TRUE;
+
+      /* If not SIMD, check for integer memory ops and MLA relationship.  */
+      rn = AARCH64_RN (insn_2);
+      ra = AARCH64_RA (insn_2);
+      rm = AARCH64_RM (insn_2);
+
+      /* If this is a load and there's a true(RAW) dependency, we are safe
+	 and this is not an erratum sequence.  */
+      if (load &&
+	  (rt == rn || rt == rm || rt == ra
+	   || (pair && (rtn == rn || rtn == rm || rtn == ra))))
+	return FALSE;
+
+      /* We conservatively put out stubs for all other cases (including
+	 writebacks).  */
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static bfd_boolean
+erratum_835769_scan (bfd *input_bfd,
+		     struct bfd_link_info *info,
+		     struct aarch64_erratum_835769_fix **fixes_p,
+		     unsigned int *num_fixes_p,
+		     unsigned int *fix_table_size_p)
+{
+  asection *section;
+  struct elf_aarch64_link_hash_table *htab = elf_aarch64_hash_table (info);
+  struct aarch64_erratum_835769_fix *fixes = *fixes_p;
+  unsigned int num_fixes = *num_fixes_p;
+  unsigned int fix_table_size = *fix_table_size_p;
+
+  if (htab == NULL)
+    return FALSE;
+
+  for (section = input_bfd->sections;
+       section != NULL;
+       section = section->next)
+    {
+      bfd_byte *contents = NULL;
+      struct _aarch64_elf_section_data *sec_data;
+      unsigned int span;
+
+      if (elf_section_type (section) != SHT_PROGBITS
+	  || (elf_section_flags (section) & SHF_EXECINSTR) == 0
+	  || (section->flags & SEC_EXCLUDE) != 0
+	  || (section->sec_info_type == SEC_INFO_TYPE_JUST_SYMS)
+	  || (section->output_section == bfd_abs_section_ptr))
+	continue;
+
+      if (elf_section_data (section)->this_hdr.contents != NULL)
+	contents = elf_section_data (section)->this_hdr.contents;
+      else if (! bfd_malloc_and_get_section (input_bfd, section, &contents))
+	return TRUE;
+
+      sec_data = elf_aarch64_section_data (section);
+      for (span = 0; span < sec_data->mapcount; span++)
+	{
+	  unsigned int span_start = sec_data->map[span].vma;
+	  unsigned int span_end = ((span == sec_data->mapcount - 1)
+				   ? sec_data->map[0].vma + section->size
+				   : sec_data->map[span + 1].vma);
+	  unsigned int i;
+	  char span_type = sec_data->map[span].type;
+
+	  if (span_type == 'd')
+	    continue;
+
+	  for (i = span_start; i + 4 < span_end; i += 4)
+	    {
+	      uint32_t insn_1 = bfd_getl32 (contents + i);
+	      uint32_t insn_2 = bfd_getl32 (contents + i + 4);
+
+	      if (aarch64_erratum_sequence (insn_1, insn_2))
+		{
+		  char *stub_name = NULL;
+		  stub_name = (char *) bfd_malloc
+				(strlen ("__erratum_835769_veneer_") + 16);
+		  if (stub_name != NULL)
+		    sprintf
+		      (stub_name,"__erratum_835769_veneer_%d", num_fixes);
+		  else
+		    return TRUE;
+
+		  if (num_fixes == fix_table_size)
+		    {
+		      fix_table_size *= 2;
+		      fixes =
+			(struct aarch64_erratum_835769_fix *)
+			  bfd_realloc (fixes,
+				       sizeof (struct aarch64_erratum_835769_fix)
+					 * fix_table_size);
+		      if (fixes == NULL)
+			return TRUE;
+		    }
+
+		  fixes[num_fixes].input_bfd = input_bfd;
+		  fixes[num_fixes].section = section;
+		  fixes[num_fixes].offset = i + 4;
+		  fixes[num_fixes].veneered_insn = insn_2;
+		  fixes[num_fixes].stub_name = stub_name;
+		  fixes[num_fixes].stub_type = aarch64_stub_erratum_835769_veneer;
+		  num_fixes++;
+		}
+	    }
+	}
+      if (elf_section_data (section)->this_hdr.contents == NULL)
+	free (contents);
+    }
+
+  *fixes_p = fixes;
+  *num_fixes_p = num_fixes;
+  *fix_table_size_p = fix_table_size;
+  return FALSE;
+}
+
+/* Find or create a stub section.  Returns a pointer to the stub section, and
+   the section to which the stub section will be attached (in *LINK_SEC_P).
+   LINK_SEC_P may be NULL.  */
+
+static asection *
+elf_aarch64_create_or_find_stub_sec (asection **link_sec_p, asection *section,
+				   struct elf_aarch64_link_hash_table *htab)
+{
+  asection *link_sec;
+  asection *stub_sec;
+
+  link_sec = htab->stub_group[section->id].link_sec;
+  BFD_ASSERT (link_sec != NULL);
+  stub_sec = htab->stub_group[section->id].stub_sec;
+
+  if (stub_sec == NULL)
+    {
+      stub_sec = htab->stub_group[link_sec->id].stub_sec;
+      if (stub_sec == NULL)
+	{
+	  size_t namelen;
+	  bfd_size_type len;
+	  char *s_name;
+
+	  namelen = strlen (link_sec->name);
+	  len = namelen + sizeof (STUB_SUFFIX);
+	  s_name = (char *) bfd_alloc (htab->stub_bfd, len);
+	  if (s_name == NULL)
+	    return NULL;
+
+	  memcpy (s_name, link_sec->name, namelen);
+	  memcpy (s_name + namelen, STUB_SUFFIX, sizeof (STUB_SUFFIX));
+	  stub_sec = (*htab->add_stub_section) (s_name, link_sec);
+
+	  if (stub_sec == NULL)
+	    return NULL;
+	  htab->stub_group[link_sec->id].stub_sec = stub_sec;
+	}
+      htab->stub_group[section->id].stub_sec = stub_sec;
+    }
+
+  if (link_sec_p)
+    *link_sec_p = link_sec;
+
+  return stub_sec;
+}
+
 /* Determine and set the size of the stub section for a final link.
 
    The basic idea here is to examine all the relocations looking for
@@ -2659,6 +3099,21 @@
   bfd_boolean stubs_always_before_branch;
   bfd_boolean stub_changed = 0;
   struct elf_aarch64_link_hash_table *htab = elf_aarch64_hash_table (info);
+  struct aarch64_erratum_835769_fix *erratum_835769_fixes = NULL;
+  unsigned int num_erratum_835769_fixes = 0;
+  unsigned int erratum_835769_fix_table_size = 10;
+  unsigned int i;
+
+  if (htab->fix_erratum_835769)
+    {
+      erratum_835769_fixes
+	= (struct aarch64_erratum_835769_fix *)
+	    bfd_zmalloc
+	      (sizeof (struct aarch64_erratum_835769_fix) *
+					erratum_835769_fix_table_size);
+      if (erratum_835769_fixes == NULL)
+	goto error_ret_free_local;
+    }
 
   /* Propagate mach to stub bfd, because it may not have been
      finalized when we created stub_bfd.  */
@@ -2689,7 +3144,9 @@
       bfd *input_bfd;
       unsigned int bfd_indx;
       asection *stub_sec;
+      unsigned prev_num_erratum_835769_fixes = num_erratum_835769_fixes;
 
+      num_erratum_835769_fixes = 0;
       for (input_bfd = info->input_bfds, bfd_indx = 0;
 	   input_bfd != NULL; input_bfd = input_bfd->link_next, bfd_indx++)
 	{
@@ -2942,8 +3399,20 @@
 	      if (elf_section_data (section)->relocs == NULL)
 		free (internal_relocs);
 	    }
+
+	  if (htab->fix_erratum_835769)
+	    {
+	      /* Scan for sequences which might trigger erratum 835769.  */
+	      if (erratum_835769_scan (input_bfd, info, &erratum_835769_fixes,
+				       &num_erratum_835769_fixes,
+				       &erratum_835769_fix_table_size)  != 0)
+		goto error_ret_free_local;
+	    }
 	}
 
+      if (prev_num_erratum_835769_fixes != num_erratum_835769_fixes)
+	stub_changed = TRUE;
+
       if (!stub_changed)
 	break;
 
@@ -2951,15 +3420,76 @@
          stub sections.  */
       for (stub_sec = htab->stub_bfd->sections;
 	   stub_sec != NULL; stub_sec = stub_sec->next)
-	stub_sec->size = 0;
+	{
+	  /* Ignore non-stub sections.  */
+	  if (!strstr (stub_sec->name, STUB_SUFFIX))
+	    continue;
+	  stub_sec->size = 0;
+	}
 
       bfd_hash_traverse (&htab->stub_hash_table, aarch64_size_one_stub, htab);
 
+      /* Add erratum 835769 veneers to stub section sizes too.  */
+      if (htab->fix_erratum_835769)
+	for (i = 0; i < num_erratum_835769_fixes; i++)
+	  {
+	    stub_sec = elf_aarch64_create_or_find_stub_sec (NULL,
+			 erratum_835769_fixes[i].section, htab);
+
+	    if (stub_sec == NULL)
+	      goto error_ret_free_local;
+
+	    stub_sec->size += 8;
+	  }
+
       /* Ask the linker to do its stuff.  */
       (*htab->layout_sections_again) ();
       stub_changed = FALSE;
     }
 
+  /* Add stubs for erratum 835769 fixes now.  */
+  if (htab->fix_erratum_835769)
+    {
+      for (i = 0; i < num_erratum_835769_fixes; i++)
+	{
+	  struct elf_aarch64_stub_hash_entry *stub_entry;
+	  char *stub_name = erratum_835769_fixes[i].stub_name;
+	  asection *section = erratum_835769_fixes[i].section;
+	  unsigned int section_id = erratum_835769_fixes[i].section->id;
+	  asection *link_sec = htab->stub_group[section_id].link_sec;
+	  asection *stub_sec = htab->stub_group[section_id].stub_sec;
+
+	  stub_entry = aarch64_stub_hash_lookup (&htab->stub_hash_table,
+						 stub_name, TRUE, FALSE);
+	  if (stub_entry == NULL)
+	    {
+	      (*_bfd_error_handler) (_("%s: cannot create stub entry %s"),
+				     section->owner,
+				     stub_name);
+	      return FALSE;
+	    }
+
+	  stub_entry->stub_sec = stub_sec;
+	  stub_entry->stub_offset = 0;
+	  stub_entry->id_sec = link_sec;
+	  stub_entry->stub_type = erratum_835769_fixes[i].stub_type;
+	  stub_entry->target_section = section;
+	  stub_entry->target_value = erratum_835769_fixes[i].offset;
+	  stub_entry->veneered_insn = erratum_835769_fixes[i].veneered_insn;
+	  stub_entry->output_name = erratum_835769_fixes[i].stub_name;
+	}
+
+      /* Stash the erratum 835769 fix array for use later in
+	 elfNN_aarch64_write_section().  */
+      htab->aarch64_erratum_835769_fixes = erratum_835769_fixes;
+      htab->num_aarch64_erratum_835769_fixes = num_erratum_835769_fixes;
+    }
+  else
+    {
+      htab->aarch64_erratum_835769_fixes = NULL;
+      htab->num_aarch64_erratum_835769_fixes = 0;
+    }
+
   return TRUE;
 
 error_ret_free_local:
@@ -3052,7 +3582,7 @@
     return;
 
   if ((abfd->flags & DYNAMIC) != 0)
-    return;
+   return;
 
   hdr = &elf_symtab_hdr (abfd);
   localsyms = hdr->sh_info;
@@ -3090,12 +3620,14 @@
 bfd_elfNN_aarch64_set_options (struct bfd *output_bfd,
 			       struct bfd_link_info *link_info,
 			       int no_enum_warn,
-			       int no_wchar_warn, int pic_veneer)
+			       int no_wchar_warn, int pic_veneer,
+			       int fix_erratum_835769)
 {
   struct elf_aarch64_link_hash_table *globals;
 
   globals = elf_aarch64_hash_table (link_info);
   globals->pic_veneer = pic_veneer;
+  globals->fix_erratum_835769 = fix_erratum_835769;
 
   BFD_ASSERT (is_aarch64_elf (output_bfd));
   elf_aarch64_tdata (output_bfd)->no_enum_size_warning = no_enum_warn;
@@ -3406,6 +3938,89 @@
   return value;
 }
 
+/* Data for make_branch_to_erratum_835769_stub().  */
+
+struct erratum_835769_branch_to_stub_data
+{
+  asection *output_section;
+  bfd_byte *contents;
+};
+
+/* Helper to insert branches to erratum 835769 stubs in the right
+   places for a particular section.  */
+
+static bfd_boolean
+make_branch_to_erratum_835769_stub (struct bfd_hash_entry *gen_entry,
+				    void *in_arg)
+{
+  struct elf_aarch64_stub_hash_entry *stub_entry;
+  struct erratum_835769_branch_to_stub_data *data;
+  bfd_byte *contents;
+  unsigned long branch_insn = 0;
+  bfd_vma veneered_insn_loc, veneer_entry_loc;
+  bfd_signed_vma branch_offset;
+  unsigned int target;
+  bfd *abfd;
+
+  stub_entry = (struct elf_aarch64_stub_hash_entry *) gen_entry;
+  data = (struct erratum_835769_branch_to_stub_data *) in_arg;
+
+  if (stub_entry->target_section != data->output_section
+      || stub_entry->stub_type != aarch64_stub_erratum_835769_veneer)
+    return TRUE;
+
+  contents = data->contents;
+  veneered_insn_loc = stub_entry->target_section->output_section->vma
+		      + stub_entry->target_section->output_offset
+		      + stub_entry->target_value;
+  veneer_entry_loc = stub_entry->stub_sec->output_section->vma
+		     + stub_entry->stub_sec->output_offset
+		     + stub_entry->stub_offset;
+  branch_offset = veneer_entry_loc - veneered_insn_loc;
+
+  abfd = stub_entry->target_section->owner;
+  if (!aarch64_valid_branch_p (veneer_entry_loc, veneered_insn_loc))
+	    (*_bfd_error_handler)
+		(_("%B: error: Erratum 835769 stub out "
+		   "of range (input file too large)"), abfd);
+
+  target = stub_entry->target_value;
+  branch_insn = 0x14000000;
+  branch_offset >>= 2;
+  branch_offset &= 0x3ffffff;
+  branch_insn |= branch_offset;
+  bfd_putl32 (branch_insn, &contents[target]);
+
+  return TRUE;
+}
+
+static bfd_boolean
+elfNN_aarch64_write_section (bfd *output_bfd  ATTRIBUTE_UNUSED,
+			     struct bfd_link_info *link_info,
+			     asection *sec,
+			     bfd_byte *contents)
+
+{
+  struct elf_aarch64_link_hash_table *globals =
+					elf_aarch64_hash_table (link_info);
+
+  if (globals == NULL)
+    return FALSE;
+
+  /* Fix code to point to erratum 835769 stubs.  */
+  if (globals->fix_erratum_835769)
+    {
+      struct erratum_835769_branch_to_stub_data data;
+
+      data.output_section = sec;
+      data.contents = contents;
+      bfd_hash_traverse (&globals->stub_hash_table,
+			 make_branch_to_erratum_835769_stub, &data);
+    }
+
+  return FALSE;
+}
+
 /* Perform a relocation as part of a final link.  */
 static bfd_reloc_status_type
 elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
@@ -5769,6 +6384,13 @@
       if (!elfNN_aarch64_output_map_sym (osi, AARCH64_MAP_DATA, addr + 16))
 	return FALSE;
       break;
+    case aarch64_stub_erratum_835769_veneer:
+      if (!elfNN_aarch64_output_stub_sym (osi, stub_name, addr,
+					  sizeof (aarch64_erratum_835769_stub)))
+	return FALSE;
+      if (!elfNN_aarch64_output_map_sym (osi, AARCH64_MAP_INSN, addr))
+	return FALSE;
+      break;
     default:
       BFD_FAIL ();
     }
@@ -6464,6 +7086,16 @@
 	}
     }
 
+  /* Init mapping symbols information to use later to distingush between
+     code and data while scanning for erratam 835769.  */
+  if (htab->fix_erratum_835769)
+    for (ibfd = info->input_bfds; ibfd != NULL; ibfd = ibfd->link_next)
+      {
+	if (!is_aarch64_elf (ibfd))
+	  continue;
+	bfd_elfNN_aarch64_init_maps (ibfd);
+      }
+
   /* We now have determined the sizes of the various dynamic sections.
      Allocate memory for them.  */
   relocs = FALSE;
@@ -7313,6 +7945,9 @@
 #define elf_backend_size_info			\
   elfNN_aarch64_size_info
 
+#define elf_backend_write_section		\
+  elfNN_aarch64_write_section
+
 #define elf_backend_can_refcount       1
 #define elf_backend_can_gc_sections    1
 #define elf_backend_plt_readonly       1
diff --git a/ld/emultempl/aarch64elf.em b/ld/emultempl/aarch64elf.em
index b3279bf..87aba34 100644
--- a/ld/emultempl/aarch64elf.em
+++ b/ld/emultempl/aarch64elf.em
@@ -30,6 +30,7 @@
 static int no_enum_size_warning = 0;
 static int no_wchar_size_warning = 0;
 static int pic_veneer = 0;
+static int fix_erratum_835769 = 0;
 
 static void
 gld${EMULATION_NAME}_before_parse (void)
@@ -297,7 +298,7 @@
   bfd_elf${ELFSIZE}_aarch64_set_options (link_info.output_bfd, &link_info,
 				 no_enum_size_warning,
 				 no_wchar_size_warning,
-				 pic_veneer);
+				 pic_veneer, fix_erratum_835769);
 
   stub_file = lang_add_input_file ("linker stubs",
 				   lang_input_file_is_fake_enum,
@@ -346,6 +347,7 @@
 #define OPTION_PIC_VENEER		310
 #define OPTION_STUBGROUP_SIZE           311
 #define OPTION_NO_WCHAR_SIZE_WARNING	312
+#define OPTION_FIX_ERRATUM_835769	313
 '
 
 PARSE_AND_LIST_SHORTOPTS=p
@@ -356,6 +358,7 @@
   { "pic-veneer", no_argument, NULL, OPTION_PIC_VENEER},
   { "stub-group-size", required_argument, NULL, OPTION_STUBGROUP_SIZE },
   { "no-wchar-size-warning", no_argument, NULL, OPTION_NO_WCHAR_SIZE_WARNING},
+  { "fix-cortex-a53-835769", no_argument, NULL, OPTION_FIX_ERRATUM_835769},
 '
 
 PARSE_AND_LIST_OPTIONS='
@@ -373,6 +376,7 @@
                            after each stub section.  Values of +/-1 indicate\n\
                            the linker should choose suitable defaults.\n"
 		   ));
+  fprintf (file, _("  --fix-cortex-a53-835769      Fix erratum 835769\n"));
 '
 
 PARSE_AND_LIST_ARGS_CASES='
@@ -392,6 +396,10 @@
       pic_veneer = 1;
       break;
 
+    case OPTION_FIX_ERRATUM_835769:
+      fix_erratum_835769 = 1;
+      break;
+
     case OPTION_STUBGROUP_SIZE:
       {
 	const char *end;
diff --git a/ld/ld.texinfo b/ld/ld.texinfo
index c925ecb..988e2d7 100644
--- a/ld/ld.texinfo
+++ b/ld/ld.texinfo
@@ -6248,6 +6248,13 @@
 
 The erratum only affects Thumb-2 code.  Please contact ARM for further details.
 
+@cindex Cortex-A53 erratum 835769 workaround
+@kindex --fix-cortex-a53-835769
+@kindex --no-fix-cortex-a53-835769
+The @samp{--fix-cortex-a53-835769} switch enables a link-time workaround for erratum 835769 present on certain early revisions of Cortex-A53 processors.  The workaround is disabled by default.  It can be enabled by specifying @samp{--fix-cortex-a53-835769}, or disabled unconditionally by specifying @samp{--no-fix-cortex-a53-835769}.
+
+Please contact ARM for further details.
+
 @kindex --merge-exidx-entries
 @kindex --no-merge-exidx-entries
 The @samp{--no-merge-exidx-entries} switch disables the merging of adjacent exidx entries in debuginfo.
diff --git a/ld/testsuite/ld-aarch64/aarch64-elf.exp b/ld/testsuite/ld-aarch64/aarch64-elf.exp
index b0820dc..d2d69e2 100644
--- a/ld/testsuite/ld-aarch64/aarch64-elf.exp
+++ b/ld/testsuite/ld-aarch64/aarch64-elf.exp
@@ -35,6 +35,14 @@
 set aarch64elftests {
     {"EH Frame merge" "-Ttext 0x8000" "" "" {eh-frame-bar.s eh-frame-foo.s}
 	{{objdump --dwarf=frames eh-frame.d}} "eh-frame"}
+    {"Erratum 835769 dump test"
+     "--fix-cortex-a53-835769" "" "" {erratum835769.s}
+     {{objdump -dr erratum835769.d}}
+     "erratum835769"}
+    {"Erratum 835769 dump test -shared"
+     "--fix-cortex-a53-835769 -shared" "" "" {erratum835769.s}
+     {{objdump -dr erratum835769.d}}
+     "erratum835769"}
 }
 
 run_ld_link_tests $aarch64elftests
diff --git a/ld/testsuite/ld-aarch64/erratum835769.d b/ld/testsuite/ld-aarch64/erratum835769.d
new file mode 100644
index 0000000..a040236
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/erratum835769.d
@@ -0,0 +1,48 @@
+#...
+Disassembly of section .text:
+#...
+[0-9a-f]+ <a1ldr>:
+[ \t0-9a-f]+:[ \t]+b8408c87[ \t]+ldr[ \t]+w7, \[x4,#8\]\!
+[ \t0-9a-f]+:[ \t]+1b017c06[ \t]+mul[ \t]+w6, w0, w1
+[ \t0-9a-f]+:[ \t]+f9400084[ \t]+ldr[ \t]+x4, \[x4\]
+[ \t0-9a-f]+:[ \t0-9a-z]+[ \t]+b[ \t]+[0-9a-f]+ <__erratum_835769_veneer_0>
+[ \t0-9a-f]+:[ \t]+aa0503e0[ \t]+mov[ \t]+x0, x5
+[ \t0-9a-f]+:[ \t]+d65f03c0[ \t]+ret
+
+[0-9a-f]+ <a5ldr>:
+[ \t0-9a-f]+:[ \t]+b8408c87[ \t]+ldr[ \t]+w7, \[x4,#8\]!
+[ \t0-9a-f]+:[ \t]+1b017c06[ \t]+mul[ \t]+w6, w0, w1
+[ \t0-9a-f]+:[ \t]+f9400084[ \t]+ldr[ \t]+x4, \[x4\]
+[ \t0-9a-f]+:[ \t0-9a-z]+[ \t]+b[ \t]+[0-9a-f]+ <__erratum_835769_veneer_1>
+[ \t0-9a-f]+:[ \t]+aa0503e0[ \t]+mov[ \t]+x0, x5
+[ \t0-9a-f]+:[ \t]+d65f03c0[ \t]+ret
+
+[0-9a-f]+ <a6ldr>:
+[ \t0-9a-f]+:[ \t]+b8408c87[ \t]+ldr[ \t]+w7, \[x4,#8\]!
+[ \t0-9a-f]+:[ \t]+1b017c06[ \t]+mul[ \t]+w6, w0, w1
+[ \t0-9a-f]+:[ \t]+f9400084[ \t]+ldr[ \t]+x4, \[x4\]
+[ \t0-9a-f]+:[ \t]+9b031885[ \t]+madd[ \t]+x5, x4, x3, x6
+[ \t0-9a-f]+:[ \t]+aa0503e0[ \t]+mov[ \t]+x0, x5
+[ \t0-9a-f]+:[ \t]+d65f03c0[ \t]+ret
+
+[0-9a-f]+ <a7str>:
+[ \t0-9a-f]+:[ \t]+b8408c87[ \t]+ldr[ \t]+w7, \[x4,#8\]!
+[ \t0-9a-f]+:[ \t]+1b017c06[ \t]+mul[ \t]+w6, w0, w1
+[ \t0-9a-f]+:[ \t]+f9000084[ \t]+str[ \t]+x4, \[x4\]
+[ \t0-9a-f]+:[ \t0-9a-z]+[ \t]+b[ \t]+[0-9a-f]+ <__erratum_835769_veneer_2>
+[ \t0-9a-f]+:[ \t]+aa0503e0[ \t]+mov[ \t]+x0, x5
+[ \t0-9a-f]+:[ \t]+d65f03c0[ \t]+ret
+[ \t0-9a-f]+:[ \t]+00000000[ \t]+.inst[ \t]+0x00000000 ; undefined
+
+[0-9a-f]+ <__erratum_835769_veneer_2>:
+[ \t0-9a-f]+:[ \t]+9b031885[ \t]+madd[ \t]+x5, x4, x3, x6
+[ \t0-9a-f]+:[ \t0-9a-z]+[ \t]+b[ \t]+[0-9a-f]+ <a7str\+0x[0-9a-f]+>
+
+[0-9a-f]+ <__erratum_835769_veneer_1>:
+[ \t0-9a-f]+:[ \t]+9ba31845[ \t]+umaddl[ \t]+x5, w2, w3, x6
+[ \t0-9a-f]+:[ \t0-9a-z]+[ \t]+b[ \t]+[0-9a-f]+ <a5ldr\+0x[0-9a-f]+>
+
+[0-9a-f]+ <__erratum_835769_veneer_0>:
+[ \t0-9a-f]+:[ \t]+9b031845[ \t]+madd[ \t]+x5, x2, x3, x6
+[ \t0-9a-f]+:[ \t0-9a-z]+[ \t]+b[ \t]+[0-9a-f]+ <a1ldr\+0x[0-9a-f]+>
+#pass
diff --git a/ld/testsuite/ld-aarch64/erratum835769.s b/ld/testsuite/ld-aarch64/erratum835769.s
new file mode 100644
index 0000000..d57b5ab
--- /dev/null
+++ b/ld/testsuite/ld-aarch64/erratum835769.s
@@ -0,0 +1,75 @@
+        .text
+        .align  2
+        .global main
+        .type   main, %function
+main:
+        stp     x29, x30, [sp, -32]!
+        add     x29, sp, 0
+        mov     x0, -26
+        str     x0, [x29,16]
+        mov     x0, 26
+        str     x0, [x29,24]
+        add     x4, x29, 16
+        mov     x0, -1
+        mov     x1, 2
+        mov     x2, -3
+        mov     x3, 4
+        bl      a1ldr
+        add     x4, x29, 16
+        mov     x0, -1
+        mov     x1, 2
+        mov     x2, -3
+        mov     x3, 4
+        bl      a5ldr
+        mov     w0, 0
+        ldp     x29, x30, [sp], 32
+        ret
+        .size   main, .-main
+
+	.align 2
+	.global a1ldr
+	.type a1ldr, %function
+a1ldr:
+	ldr w7, [x4,8]!
+	mul w6, w0, w1
+	ldr x4, [x4]
+	madd x5, x2, x3, x6
+	mov x0, x5
+	ret
+	.size a1ldr, .-a1ldr
+
+	.align 2
+	.global a5ldr
+	.type a5ldr, %function
+a5ldr:
+	ldr w7, [x4,8]!
+	mul w6, w0, w1
+	ldr x4, [x4]
+	umaddl x5, w2, w3, x6
+	mov x0, x5
+	ret
+	.size a5ldr, .-a5ldr
+
+	.align 2
+	.global a6ldr
+	.type a6ldr, %function
+a6ldr:
+	ldr w7, [x4,8]!
+	mul w6, w0, w1
+	ldr x4, [x4]
+	madd x5, x4, x3, x6
+	mov x0, x5
+	ret
+	.size a6ldr, .-a6ldr
+
+	.align 2
+	.global a6ldr
+	.type a6ldr, %function
+a7str:
+	ldr w7, [x4,8]!
+	mul w6, w0, w1
+	str x4, [x4]
+	madd x5, x4, x3, x6
+	mov x0, x5
+	ret
+	.size a7str, .-a7str