]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - bfd/elfnn-aarch64.c
Fixing cap_meta
[thirdparty/binutils-gdb.git] / bfd / elfnn-aarch64.c
index f1a43e18e0e57a33dbc0638b34d149aed5b64e70..cd54beec7b7d9f6b6977634dcd82501b80ab6cd4 100644 (file)
 #define IS_AARCH64_TLS_RELAX_RELOC(R_TYPE)                     \
   ((R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_ADD                   \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_ADD_LO12           \
+   || (R_TYPE) == BFD_RELOC_MORELLO_TLSDESC_ADR_PAGE20         \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21         \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_ADR_PREL21         \
+   || (R_TYPE) == BFD_RELOC_MORELLO_TLSDESC_CALL               \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_CALL               \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_LD_PREL19          \
+   || (R_TYPE) == BFD_RELOC_MORELLO_TLSDESC_LD128_LO12         \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_LDNN_LO12_NC       \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_LDR                        \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_OFF_G0_NC          \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_ADD                        \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_ADD_LO12           \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21         \
+   || (R_TYPE) == BFD_RELOC_MORELLO_TLSDESC_ADR_PAGE20         \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_ADR_PREL21         \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_CALL               \
+   || (R_TYPE) == BFD_RELOC_MORELLO_TLSDESC_CALL               \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_LD32_LO12_NC       \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_LD64_LO12          \
+   || (R_TYPE) == BFD_RELOC_MORELLO_TLSDESC_LD128_LO12         \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_LDR                        \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_LD_PREL19          \
    || (R_TYPE) == BFD_RELOC_AARCH64_TLSDESC_OFF_G0_NC          \
@@ -447,6 +453,19 @@ elfNN_aarch64_tlsdesc_small_plt_bti_entry[PLT_TLSDESC_ENTRY_SIZE] =
   0x1f, 0x20, 0x03, 0xd5,      /* nop */
 };
 
+static const bfd_byte
+elfNN_aarch64_tlsdesc_small_plt_c64_entry[PLT_TLSDESC_ENTRY_SIZE] =
+{
+  0xe2, 0x8f, 0xbf, 0x62,      /* stp c2, c3, [sp, #-16]! */
+  0x02, 0x00, 0x80, 0x90,      /* adrp c2, 0 */
+  0x03, 0x00, 0x80, 0x90,      /* adrp c3, 0 */
+  0x42, 0x00, 0x40, 0xc2,      /* ldr c2, [c2, #0] */
+  0x63, 0x00, 0x00, 0x02,      /* add c3, c3, 0 */
+  0x40, 0x10, 0xc2, 0xc2,      /* br c2 */
+  0x1f, 0x20, 0x03, 0xd5,      /* nop */
+  0x1f, 0x20, 0x03, 0xd5,      /* nop */
+};
+
 #define elf_info_to_howto              elfNN_aarch64_info_to_howto
 #define elf_info_to_howto_rel          elfNN_aarch64_info_to_howto
 
@@ -2173,6 +2192,51 @@ static reloc_howto_type elfNN_aarch64_howto_table[] =
         0x0,                   /* dst_mask */
         FALSE),                /* pcrel_offset */
 
+  /* Get to the page for the GOT entry for the symbol
+     (G(S) - P) using an ADRP instruction.  */
+  HOWTO64 (MORELLO_R (TLSDESC_ADR_PAGE20),     /* type */
+        12,                    /* rightshift */
+        2,                     /* size (0 = byte, 1 = short, 2 = long) */
+        20,                    /* bitsize */
+        TRUE,                  /* pc_relative */
+        0,                     /* bitpos */
+        complain_overflow_dont,        /* complain_on_overflow */
+        bfd_elf_generic_reloc, /* special_function */
+        MORELLO_R_STR (TLSDESC_ADR_PAGE20),    /* name */
+        FALSE,                 /* partial_inplace */
+        0xfffff,               /* src_mask */
+        0xfffff,               /* dst_mask */
+        TRUE),                 /* pcrel_offset */
+
+  /* LD128: GOT offset G(S) & 0xff0.  */
+  HOWTO64 (MORELLO_R (TLSDESC_LD128_LO12),     /* type */
+        4,                     /* rightshift */
+        2,                     /* size (0 = byte, 1 = short, 2 = long) */
+        12,                    /* bitsize */
+        FALSE,                 /* pc_relative */
+        0,                     /* bitpos */
+        complain_overflow_dont,        /* complain_on_overflow */
+        bfd_elf_generic_reloc, /* special_function */
+        MORELLO_R_STR (TLSDESC_LD128_LO12),    /* name */
+        FALSE,                 /* partial_inplace */
+        0xff0,                 /* src_mask */
+        0xff0,                 /* dst_mask */
+        FALSE),                /* pcrel_offset */
+
+  HOWTO64 (MORELLO_R (TLSDESC_CALL),   /* type */
+        0,                     /* rightshift */
+        2,                     /* size (0 = byte, 1 = short, 2 = long) */
+        0,                     /* bitsize */
+        FALSE,                 /* pc_relative */
+        0,                     /* bitpos */
+        complain_overflow_dont,        /* complain_on_overflow */
+        bfd_elf_generic_reloc, /* special_function */
+        MORELLO_R_STR (TLSDESC_CALL),  /* name */
+        FALSE,                 /* partial_inplace */
+        0x0,                   /* src_mask */
+        0x0,                   /* dst_mask */
+        FALSE),                /* pcrel_offset */
+
   HOWTO (AARCH64_R (COPY),     /* type */
         0,                     /* rightshift */
         2,                     /* size (0 = byte, 1 = short, 2 = long) */
@@ -2381,6 +2445,20 @@ static reloc_howto_type elfNN_aarch64_howto_table[] =
         ALL_ONES,              /* dst_mask */
         FALSE),                /* pcrel_offset */
 
+  HOWTO64 (MORELLO_R (TLSDESC),        /* type */
+        0,                     /* rightshift */
+        2,                     /* size (0 = byte, 1 = short, 2 = long) */
+        64,                    /* bitsize */
+        FALSE,                 /* pc_relative */
+        0,                     /* bitpos */
+        complain_overflow_dont,        /* complain_on_overflow */
+        bfd_elf_generic_reloc, /* special_function */
+        MORELLO_R_STR (TLSDESC),       /* name */
+        FALSE,                 /* partial_inplace */
+        0,                     /* src_mask */
+        ALL_ONES,              /* dst_mask */
+        FALSE),                /* pcrel_offset */
+
   EMPTY_HOWTO (0),
 };
 
@@ -3049,6 +3127,7 @@ struct elf_aarch64_link_hash_table
   /* Used for capability relocations.  */
   asection *srelcaps;
   int c64_rel;
+  bfd_boolean c64_output;
 };
 
 /* Create an entry in an AArch64 ELF linker hash table.  */
@@ -4694,6 +4773,317 @@ _bfd_aarch64_erratum_843419_scan (bfd *input_bfd, asection *section,
   return TRUE;
 }
 
+static bfd_boolean
+section_start_symbol (bfd *abfd ATTRIBUTE_UNUSED, asection *section,
+                     void *valp)
+{
+  return section->vma == *(bfd_vma *)valp;
+}
+
+/* Capability format functions.  */
+
+static unsigned
+exponent (uint64_t len)
+{
+#define CAP_MAX_EXPONENT 50
+  /* Size is a 65 bit value, so there's an implicit 0 MSB.  */
+  unsigned zeroes = __builtin_clzl (len) + 1;
+
+  /* All bits up to and including CAP_MW - 2 are zero.  */
+  if (CAP_MAX_EXPONENT < zeroes)
+    return (unsigned) -1;
+  else
+    return CAP_MAX_EXPONENT - zeroes;
+#undef CAP_MAX_EXPONENT
+}
+
+#define ONES(x)         ((1ULL << (x)) - 1)
+#define ALIGN_UP(x, a)  (((x) + ONES (a)) & (~ONES (a)))
+
+static bfd_boolean
+c64_valid_cap_range (bfd_vma *basep, bfd_vma *limitp)
+{
+  bfd_vma base = *basep, size = *limitp - *basep;
+
+  unsigned e, old_e;
+
+  if ((e = exponent (size)) == (unsigned) -1)
+    return TRUE;
+
+  size = ALIGN_UP (size, e + 3);
+  old_e = e;
+  e = exponent (size);
+  if (old_e != e)
+    size = ALIGN_UP (size, e + 3);
+
+  base = ALIGN_UP (base, e + 3);
+
+  if (base == *basep && *limitp == base + size)
+    return TRUE;
+
+  *basep = base;
+  *limitp = base + size;
+  return FALSE;
+}
+
+struct sec_change_queue
+{
+  asection *sec;
+  struct sec_change_queue *next;
+};
+
+/* Queue up the change, sorted in order of the output section vma.  */
+
+static void
+queue_section_padding (struct sec_change_queue **queue, asection *sec)
+{
+  struct sec_change_queue *q = *queue, *last_q = NULL, *n;
+
+  while (q != NULL)
+    {
+      if (q->sec->vma > sec->vma)
+       break;
+      last_q = q;
+      q = q->next;
+    }
+
+  n = bfd_zmalloc (sizeof (struct sec_change_queue));
+
+  if (last_q == NULL)
+    *queue = n;
+  else
+    {
+      n->next = q;
+      last_q->next = n;
+    }
+
+  n->sec = sec;
+}
+
+/* Check if the bounds covering all sections between LOW_SEC and HIGH_SEC will
+   get rounded off in the Morello capability format and if it does, queue up a
+   change to fix up the section layout.  */
+static inline void
+record_section_change (asection *sec, struct sec_change_queue **queue)
+{
+  bfd_vma low = sec->vma;
+  bfd_vma high = sec->vma + sec->size;
+
+  if (!c64_valid_cap_range (&low, &high))
+    queue_section_padding (queue, sec);
+}
+
+/* Make sure that all capabilities that refer to sections have bounds that
+   won't overlap with neighbouring sections.  This is needed in two specific
+   cases.  The first case is that of PCC, which needs to span across all
+   executable sections as well as the GOT and PLT sections in the output
+   binary.  The second case is that of linker and ldscript defined symbols that
+   indicate start and/or end of sections.
+
+   In both cases, overlap of capability bounds are avoided by aligning the base
+   of the section and if necessary, adding a pad at the end of the section so
+   that the section following it starts only after the pad.  */
+
+static bfd_vma pcc_low;
+static bfd_vma pcc_high;
+void
+elfNN_c64_resize_sections (bfd *output_bfd, struct bfd_link_info *info,
+                          void (*c64_pad_section) (asection *, bfd_vma),
+                          void (*layout_sections_again) (void))
+{
+  asection *sec, *pcc_low_sec = NULL, *pcc_high_sec = NULL;
+  struct elf_aarch64_link_hash_table *htab = elf_aarch64_hash_table (info);
+  bfd_vma low = (bfd_vma) -1, high = 0;
+  bfd *input_bfd;
+
+  htab->layout_sections_again = layout_sections_again;
+
+  if (!htab->c64_output)
+    return;
+
+  struct sec_change_queue *queue = NULL;
+
+  /* First, walk through all the relocations to find those referring to linker
+     defined and ldscript defined symbols since we set their range to their
+     output sections.  */
+  for (input_bfd = info->input_bfds;
+       htab->c64_rel && input_bfd != NULL; input_bfd = input_bfd->link.next)
+    {
+      Elf_Internal_Shdr *symtab_hdr;
+
+      symtab_hdr = &elf_tdata (input_bfd)->symtab_hdr;
+      if (symtab_hdr->sh_info == 0)
+       continue;
+
+      for (sec = input_bfd->sections; sec != NULL; sec = sec->next)
+       {
+         Elf_Internal_Rela *irelaend, *irela;
+
+         /* If there aren't any relocs, then there's nothing more to do.  */
+         if ((sec->flags & SEC_RELOC) == 0 || sec->reloc_count == 0)
+           continue;
+
+         irela = _bfd_elf_link_read_relocs (input_bfd, sec, NULL, NULL,
+                                            info->keep_memory);
+         if (irela == NULL)
+           continue;
+
+         /* Now examine each relocation.  */
+         irelaend = irela + sec->reloc_count;
+         for (; irela < irelaend; irela++)
+           {
+             unsigned int r_indx;
+             struct elf_link_hash_entry *h;
+             int e_indx;
+             asection *os;
+
+             r_indx = ELFNN_R_SYM (irela->r_info);
+
+             /* Linker defined or linker script defined symbols are always in
+                the symbol hash.  */
+             if (r_indx < symtab_hdr->sh_info)
+               continue;
+
+             e_indx = r_indx - symtab_hdr->sh_info;
+             h = elf_sym_hashes (input_bfd)[e_indx];
+
+             /* XXX Does this ever happen?  */
+             if (h == NULL)
+               continue;
+
+             os = h->root.u.def.section->output_section;
+
+             if (h->root.linker_def)
+               record_section_change (os, &queue);
+             else if (h->root.ldscript_def)
+               {
+                 const char *name = h->root.root.string;
+                 size_t len = strlen (name);
+
+                 if (len > 8 && name[0] == '_' && name[1] == '_'
+                     && (!strncmp (name + 2, "start_", 6)
+                         || !strcmp (name + len - 6, "_start")))
+
+                   {
+                     bfd_vma value = os->vma + os->size;
+
+                     os = bfd_sections_find_if (info->output_bfd,
+                                                section_start_symbol, &value);
+
+                     if (os != NULL)
+                       record_section_change (os, &queue);
+                   }
+                 /* XXX We're overfitting here because the offset of H within
+                    the output section is not yet resolved and ldscript
+                    defined symbols do not have input section information.  */
+                 else
+                   record_section_change (os, &queue);
+               }
+           }
+       }
+    }
+
+  /* Next, walk through output sections to find the PCC span and add a padding
+     at the end to ensure that PCC bounds don't bleed into neighbouring
+     sections.  For now PCC needs to encompass all code sections, .got, .plt
+     and .got.plt.  */
+  for (sec = output_bfd->sections; sec != NULL; sec = sec->next)
+    {
+      /* XXX This is a good place to figure out if there are any readable or
+        writable sections in the PCC range that are not in the list of
+        sections we want the PCC to span and then warn the user of it.  */
+
+#define NOT_OP_SECTION(s) ((s) == NULL || (s)->output_section != sec)
+
+      if ((sec->flags & SEC_CODE) == 0
+         && NOT_OP_SECTION (htab->root.sgotplt)
+         && NOT_OP_SECTION (htab->root.igotplt)
+         && NOT_OP_SECTION (htab->root.sgot)
+         && NOT_OP_SECTION (htab->root.splt)
+         && NOT_OP_SECTION (htab->root.iplt))
+       continue;
+
+      if (sec->vma < low)
+       {
+         low = sec->vma;
+         pcc_low_sec = sec;
+       }
+      if (sec->vma + sec->size > high)
+       {
+         high = sec->vma + sec->size;
+         pcc_high_sec = sec;
+       }
+
+#undef NOT_OP_SECTION
+    }
+
+  /* Sequentially add alignment and padding as required.  We also need to
+     account for the PCC-related alignment and padding here since its
+     requirements could change based on the range of sections it encompasses
+     and whether they need to be padded or aligned.  */
+  while (queue)
+    {
+      unsigned align = 0;
+      bfd_vma padding = 0;
+
+      low = queue->sec->vma;
+      high = queue->sec->vma + queue->sec->size;
+
+      if (!c64_valid_cap_range (&low, &high))
+       {
+         align = __builtin_ctzl (low);
+
+         if (queue->sec->alignment_power < align)
+           queue->sec->alignment_power = align;
+
+         padding = high - queue->sec->vma - queue->sec->size;
+
+         if (queue->sec != pcc_high_sec)
+           {
+             c64_pad_section (queue->sec, padding);
+             padding = 0;
+           }
+       }
+
+      /* If we have crossed all sections within the PCC range, set up alignment
+         and padding for the PCC range.  */
+      if (pcc_high_sec != NULL && pcc_low_sec != NULL
+         && (queue->next == NULL
+             || queue->next->sec->vma > pcc_high_sec->vma))
+       {
+         /* Layout sections since it affects the final range of PCC.  */
+         (*htab->layout_sections_again) ();
+
+         pcc_low = pcc_low_sec->vma;
+         pcc_high = pcc_high_sec->vma + pcc_high_sec->size + padding;
+
+         if (!c64_valid_cap_range (&pcc_low, &pcc_high))
+           {
+             align = __builtin_ctzl (pcc_low);
+             if (pcc_low_sec->alignment_power < align)
+               pcc_low_sec->alignment_power = align;
+
+             padding = pcc_high - pcc_high_sec->vma - pcc_high_sec->size;
+             c64_pad_section (pcc_high_sec, padding);
+           }
+       }
+
+      (*htab->layout_sections_again) ();
+
+      struct sec_change_queue *queue_free = queue;
+
+      queue = queue->next;
+      free (queue_free);
+    }
+
+  if (pcc_low_sec)
+    {
+      if (!pcc_high_sec)
+       abort ();
+      pcc_low = pcc_low_sec->vma;
+      pcc_high = pcc_high_sec->vma + pcc_high_sec->size;
+    }
+}
 
 /* Determine and set the size of the stub section for a final link.
 
@@ -5194,7 +5584,7 @@ elfNN_aarch64_section_map_add (bfd *abfd, asection *sec, char type,
 
 /* Initialise maps of insn/data for input BFDs.  */
 void
-bfd_elfNN_aarch64_init_maps (bfd *abfd)
+bfd_elfNN_aarch64_init_maps (bfd *abfd, struct bfd_link_info *info)
 {
   Elf_Internal_Sym *isymbuf;
   Elf_Internal_Shdr *hdr;
@@ -5222,6 +5612,8 @@ bfd_elfNN_aarch64_init_maps (bfd *abfd)
   if (isymbuf == NULL)
     return;
 
+  struct elf_aarch64_link_hash_table *htab = elf_aarch64_hash_table ((info));
+
   for (i = 0; i < localsyms; i++)
     {
       Elf_Internal_Sym *isym = &isymbuf[i];
@@ -5236,7 +5628,12 @@ bfd_elfNN_aarch64_init_maps (bfd *abfd)
 
          if (bfd_is_aarch64_special_symbol_name
              (name, BFD_AARCH64_SPECIAL_SYM_TYPE_MAP))
-           elfNN_aarch64_section_map_add (abfd, sec, name[1], isym->st_value);
+           {
+             elfNN_aarch64_section_map_add (abfd, sec, name[1],
+                                            isym->st_value);
+             if (!htab->c64_output && name[1] == 'c')
+               htab->c64_output = TRUE;
+           }
        }
     }
   elf_aarch64_tdata (abfd)->secmaps_initialised = TRUE;
@@ -5393,12 +5790,19 @@ aarch64_calculate_got_entry_vma (struct elf_link_hash_entry *h,
 
 static bfd_reloc_code_real_type
 aarch64_tls_transition_without_check (bfd_reloc_code_real_type r_type,
-                                     struct elf_link_hash_entry *h)
+                                     struct bfd_link_info *info,
+                                     struct elf_link_hash_entry *h,
+                                     bfd_boolean morello_reloc)
 {
   bfd_boolean is_local = h == NULL;
 
   switch (r_type)
     {
+    case BFD_RELOC_MORELLO_TLSDESC_ADR_PAGE20:
+      return (is_local || !bfd_link_pic (info)
+             ? BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1
+             : r_type);
+
     case BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21:
     case BFD_RELOC_AARCH64_TLSGD_ADR_PAGE21:
       return (is_local
@@ -5430,6 +5834,10 @@ aarch64_tls_transition_without_check (bfd_reloc_code_real_type r_type,
              ? BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G2
              : BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G1);
 
+    case BFD_RELOC_MORELLO_TLSDESC_LD128_LO12:
+      return ((is_local || !bfd_link_pie (info)
+              ? BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0_NC : r_type));
+
     case BFD_RELOC_AARCH64_TLSDESC_LDNN_LO12_NC:
     case BFD_RELOC_AARCH64_TLSGD_ADD_LO12_NC:
       return (is_local
@@ -5450,10 +5858,19 @@ aarch64_tls_transition_without_check (bfd_reloc_code_real_type r_type,
              ? BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_HI12
              : BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_PREL19);
 
-    case BFD_RELOC_AARCH64_TLSDESC_ADD:
+    case BFD_RELOC_MORELLO_TLSDESC_CALL:
+      return ((is_local || !bfd_link_pie (info))
+             ? BFD_RELOC_AARCH64_NONE : r_type);
+
     case BFD_RELOC_AARCH64_TLSDESC_ADD_LO12:
+      if (morello_reloc && !is_local && bfd_link_pie (info))
+       return r_type;
+      /* Fall through.  */
+    case BFD_RELOC_AARCH64_TLSDESC_ADD:
     case BFD_RELOC_AARCH64_TLSDESC_CALL:
-      /* Instructions with these relocations will become NOPs.  */
+      /* Instructions with these relocations will be fully resolved during the
+         transition into either a NOP in the A64 case or movk and add in
+        C64.  */
       return BFD_RELOC_AARCH64_NONE;
 
     case BFD_RELOC_AARCH64_TLSLD_ADD_LO12_NC:
@@ -5510,6 +5927,11 @@ aarch64_reloc_got_type (bfd_reloc_code_real_type r_type)
     case BFD_RELOC_AARCH64_TLSLD_ADR_PREL21:
       return GOT_TLS_GD;
 
+    case BFD_RELOC_MORELLO_TLSDESC_ADR_PAGE20:
+    case BFD_RELOC_MORELLO_TLSDESC_CALL:
+    case BFD_RELOC_MORELLO_TLSDESC_LD128_LO12:
+      return GOT_TLSDESC_GD | GOT_CAP;
+
     case BFD_RELOC_AARCH64_TLSDESC_ADD:
     case BFD_RELOC_AARCH64_TLSDESC_ADD_LO12:
     case BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21:
@@ -5540,18 +5962,22 @@ aarch64_reloc_got_type (bfd_reloc_code_real_type r_type)
 static bfd_boolean
 aarch64_can_relax_tls (bfd *input_bfd,
                       struct bfd_link_info *info,
-                      bfd_reloc_code_real_type r_type,
+                      const Elf_Internal_Rela *rel,
                       struct elf_link_hash_entry *h,
                       unsigned long r_symndx)
 {
   unsigned int symbol_got_type;
   unsigned int reloc_got_type;
 
-  if (! IS_AARCH64_TLS_RELAX_RELOC (r_type))
+  bfd_reloc_code_real_type bfd_r_type
+    = elfNN_aarch64_bfd_reloc_from_type (input_bfd,
+                                        ELFNN_R_TYPE (rel->r_info));
+
+  if (! IS_AARCH64_TLS_RELAX_RELOC (bfd_r_type))
     return FALSE;
 
   symbol_got_type = elfNN_aarch64_symbol_got_type (h, input_bfd, r_symndx);
-  reloc_got_type = aarch64_reloc_got_type (r_type);
+  reloc_got_type = aarch64_reloc_got_type (bfd_r_type);
 
   if (symbol_got_type == GOT_TLS_IE && GOT_TLS_GD_ANY_P (reloc_got_type))
     return TRUE;
@@ -5571,17 +5997,29 @@ aarch64_can_relax_tls (bfd *input_bfd,
 static bfd_reloc_code_real_type
 aarch64_tls_transition (bfd *input_bfd,
                        struct bfd_link_info *info,
-                       unsigned int r_type,
+                       const Elf_Internal_Rela *rel,
                        struct elf_link_hash_entry *h,
                        unsigned long r_symndx)
 {
   bfd_reloc_code_real_type bfd_r_type
-    = elfNN_aarch64_bfd_reloc_from_type (input_bfd, r_type);
+    = elfNN_aarch64_bfd_reloc_from_type (input_bfd,
+                                        ELFNN_R_TYPE (rel->r_info));
 
-  if (! aarch64_can_relax_tls (input_bfd, info, bfd_r_type, h, r_symndx))
+  if (! aarch64_can_relax_tls (input_bfd, info, rel, h, r_symndx))
     return bfd_r_type;
 
-  return aarch64_tls_transition_without_check (bfd_r_type, h);
+  bfd_boolean morello_reloc = (bfd_r_type == BFD_RELOC_AARCH64_TLSDESC_ADD_LO12
+                              && (ELFNN_R_TYPE (rel[1].r_info)
+                                  == MORELLO_R (TLSDESC_CALL)));
+
+  /* GD -> IE is not supported for Morello TLSDESC yet.  We do however allow
+     lowering of GD -> LE for static non-pie executables.  XXX It ought to be
+     safe to do this for A64 as well but it is not implemented yet.  */
+  if (h != NULL && morello_reloc && bfd_link_pie (info))
+    return bfd_r_type;
+
+  return aarch64_tls_transition_without_check (bfd_r_type, info, h,
+                                              morello_reloc);
 }
 
 /* Return the base VMA address which should be subtracted from real addresses
@@ -5923,53 +6361,6 @@ aarch64_relocation_aginst_gp_p (bfd_reloc_code_real_type reloc)
          || reloc == BFD_RELOC_AARCH64_MOVW_GOTOFF_G1);
 }
 
-/* Capability format functions.  */
-
-static unsigned
-exponent (uint64_t len)
-{
-#define CAP_MAX_EXPONENT 50
-  /* Size is a 65 bit value, so there's an implicit 0 MSB.  */
-  unsigned zeroes = __builtin_clzl (len) + 1;
-
-  /* All bits up to and including CAP_MW - 2 are zero.  */
-  if (CAP_MAX_EXPONENT < zeroes)
-    return (unsigned) -1;
-  else
-    return CAP_MAX_EXPONENT - zeroes;
-#undef CAP_MAX_EXPONENT
-}
-
-#define ONES(x)         ((1ULL << ((x) + 1)) - 1)
-#define ALIGN_UP(x, a)  (((x) + ONES (a)) & (~ONES (a)))
-
-static bfd_boolean
-c64_valid_cap_range (bfd_vma *basep, bfd_vma *limitp)
-{
-  bfd_vma base = *basep, size = *limitp - *basep;
-
-  unsigned e, old_e;
-
-  if ((e = exponent (size)) == (unsigned) -1)
-    return TRUE;
-
-  size = ALIGN_UP (size, e + 3);
-  old_e = e;
-  e = exponent (size);
-  if (old_e != e)
-    size = ALIGN_UP (size, e + 3);
-
-  base = ALIGN_UP (base, e + 3);
-
-  if (base == *basep && *limitp == base + size)
-    return TRUE;
-
-  *basep = base;
-  *limitp = base + size;
-  return FALSE;
-}
-
-
 /* Build capability meta data, i.e. size and permissions for a capability.  */
 
 static bfd_vma
@@ -5979,91 +6370,117 @@ cap_meta (size_t size, const asection *sec)
   if (size >= (1ULL << 56))
     return (bfd_vma) -1;
 
-  size <<= 8;
-  if (sec->flags & SEC_READONLY
-      || sec->flags & SEC_ROM)
-    return size | 1;
+  /* N.b. We are only ever using this function for Morello.
+     Morello is little-endian.
+     We are returning a 64bit sized integer.
+     The format this metadata is supposed to fit is
+      | 56 bit length | 8 bit permissions |
+     This means that (in little endian layout) we need to put the 56 bit size
+     in the *lower* bits of the uint64_t.  */
+  uint64_t flags = 0;
   if (sec->flags & SEC_CODE)
-    return size | 4;
-  if (sec->flags & SEC_ALLOC)
-    return size | 2;
+    flags = 4;
+  else if (sec->flags & SEC_READONLY
+      || sec->flags & SEC_ROM)
+    flags = 1;
+  else if (sec->flags & SEC_ALLOC)
+    flags = 2;
 
   /* We should always be able to derive a valid set of permissions
      from the section flags.  */
-  abort ();
+  if (flags == 0)
+    abort ();
+  return size | (flags << 56);
 }
 
-static bfd_boolean
-section_start_symbol (bfd *abfd ATTRIBUTE_UNUSED, asection *section,
-                     void *valp)
-{
-  return section->vma == *(bfd_vma *)valp;
+enum c64_section_perm_type {
+    C64_SYM_UNKNOWN = 0,
+    C64_SYM_STANDARD,
+    C64_SYM_LINKER_DEF,
+    C64_SYM_LDSCRIPT_DEF,
+    C64_SYM_LDSCRIPT_START,
+};
+
+static enum c64_section_perm_type
+c64_symbol_section_adjustment (struct elf_link_hash_entry *h, bfd_vma value,
+                              asection *sym_sec, asection **ret_sec,
+                              struct bfd_link_info *info)
+{
+  if (!sym_sec)
+    return C64_SYM_UNKNOWN;
+
+  *ret_sec = sym_sec;
+  if (!h)
+    return C64_SYM_STANDARD;
+
+  /* Linker defined symbols are always at the start of the section they
+     track.  */
+  if (h->root.linker_def)
+    return C64_SYM_LINKER_DEF;
+  else if (h->root.ldscript_def)
+    {
+      const char *name = h->root.root.string;
+      size_t len = strlen (name);
+
+      bfd_vma size = sym_sec->size - (value - sym_sec->vma);
+      /* The special case: the symbol is at the end of the section.
+        This could either mean that it is an end symbol or it is the
+        start of the output section following the symbol.  We try to
+        guess if it is a start of the next section by reading its
+        name.  This is a compatibility hack, ideally linker scripts
+        should be written such that start symbols are defined within
+        the output section it intends to track.  */
+      if (size == 0
+         && (len > 8 && name[0] == '_' && name[1] == '_'
+             && (!strncmp (name + 2, "start_", 6)
+                 || !strcmp (name + len - 6, "_start"))))
+       {
+         asection *s = bfd_sections_find_if (info->output_bfd,
+                                             section_start_symbol,
+                                             &value);
+         if (s != NULL)
+           {
+             *ret_sec = s;
+             return C64_SYM_LDSCRIPT_START;
+           }
+       }
+      return C64_SYM_LDSCRIPT_DEF;
+    }
+  return C64_SYM_STANDARD;
 }
 
 static bfd_reloc_status_type
 c64_fixup_frag (bfd *input_bfd, struct bfd_link_info *info,
-               bfd_reloc_code_real_type bfd_r_type, Elf_Internal_Sym *sym,
-               struct elf_link_hash_entry *h, asection *sym_sec,
-               bfd_byte *frag_loc, bfd_vma value, bfd_signed_vma addend)
+               Elf_Internal_Sym *sym, struct elf_link_hash_entry *h,
+               asection *sym_sec, bfd_byte *frag_loc, bfd_vma value,
+               bfd_signed_vma addend)
 {
-  bfd_vma size = 0;
+  BFD_ASSERT (h || sym);
+  bfd_vma size = sym ? sym->st_size : h->size;
   asection *perm_sec = sym_sec;
   bfd_boolean bounds_ok = FALSE;
 
-  if (sym != NULL)
+  if (size == 0 && sym_sec)
     {
-      size = sym->st_size;
-      if (size == 0)
-       goto need_size;
-    }
-  else if (h != NULL)
-    {
-      size = h->size;
+      bounds_ok = TRUE;
+      enum c64_section_perm_type type
+       = c64_symbol_section_adjustment (h, value, sym_sec, &perm_sec, info);
 
-      if (size == 0 && sym_sec != NULL)
+      switch (type)
        {
-         /* Linker defined symbols are always at the start of the section they
-            track.  */
-         if (h->root.linker_def)
-           {
-             size = sym_sec->output_section->size;
-             bounds_ok = TRUE;
-           }
-         else if (h->root.ldscript_def)
-           {
-             const char *name = h->root.root.string;
-             size_t len = strlen (name);
-
-             /* In the general case, the symbol should be able to access the
-                entire output section following it.  */
-             size = sym_sec->size - (value - sym_sec->vma);
-
-             /* The special case: the symbol is at the end of the section.
-                This could either mean that it is an end symbol or it is the
-                start of the output section following the symbol.  We try to
-                guess if it is a start of the next section by reading its
-                name.  This is a compatibility hack, ideally linker scripts
-                should be written such that start symbols are defined within
-                the output section it intends to track.  */
-             if (size == 0
-                 && (len > 8 && name[0] == '_' && name[1] == '_'
-                     && (!strncmp (name + 2, "start_", 6)
-                         || !strcmp (name + len - 6, "_start"))))
-               {
-                 asection *s = bfd_sections_find_if (info->output_bfd,
-                                                     section_start_symbol,
-                                                     &value);
-                 if (s != NULL)
-                   {
-                     perm_sec = s;
-                     size = s->size;
-                   }
-               }
-             bounds_ok = TRUE;
-           }
-         else if (size == 0 && bfd_link_pic (info)
-                  && SYMBOL_REFERENCES_LOCAL (info, h))
-           goto need_size;
+       case C64_SYM_STANDARD:
+         break;
+       case C64_SYM_LINKER_DEF:
+         size = perm_sec->output_section->size;
+         break;
+       case C64_SYM_LDSCRIPT_DEF:
+         size = perm_sec->size - (value - perm_sec->vma);
+         break;
+       case C64_SYM_LDSCRIPT_START:
+         size = perm_sec->size;
+         break;
+       default:
+         abort ();
        }
     }
 
@@ -6071,7 +6488,7 @@ c64_fixup_frag (bfd *input_bfd, struct bfd_link_info *info,
   if (addend < 0 || (bfd_vma) addend > size)
     return bfd_reloc_outofrange;
 
-  bfd_vma base = value + addend, limit = value + addend + size;
+  bfd_vma base = value, limit = value + size;
 
   if (!bounds_ok && !c64_valid_cap_range (&base, &limit))
     {
@@ -6082,6 +6499,25 @@ c64_fixup_frag (bfd *input_bfd, struct bfd_link_info *info,
       return bfd_reloc_notsupported;
     }
 
+  if (perm_sec && perm_sec->flags & SEC_CODE)
+    {
+      /* Any symbol pointing into an executable section gets bounds according
+        to PCC.  In this case the relocation is set up so that the value is
+        the base of the PCC, the addend is the offset from the PCC base to the
+        VA that we want, and the size is the length of the PCC range.
+        In this function we only use `value` to check the bounds make sense,
+        which is somewhat superfluous when we're using pcc_high and pcc_low
+        since we already enforced that in elfNN_c64_resize_sections.  No harm
+        in instead checking that the bounds on the object that were requested
+        made sense even if they were overridden because this symbol points
+        into an executable section.
+
+        `size` on the other hand is part of the fragment that we output to and
+        we need to change it in order to have functions that can access global
+        data or jump to other functions.  */
+      size = pcc_high - pcc_low;
+    }
+
   if (perm_sec != NULL)
     {
       bfd_vma frag = cap_meta (size, perm_sec);
@@ -6093,17 +6529,30 @@ c64_fixup_frag (bfd *input_bfd, struct bfd_link_info *info,
     }
 
   return bfd_reloc_continue;
+}
+
+/* Given either a local symbol SYM or global symbol H, do we need to adjust
+   capability relocations against the symbol due to the fact that it points to
+   a code section?  */
+static bfd_boolean
+c64_symbol_adjust (struct elf_link_hash_entry *h,
+                  bfd_vma value, asection *sym_sec, struct bfd_link_info *info,
+                  bfd_vma *adjust_addr)
+{
+  asection *tmp_sec;
+  enum c64_section_perm_type type
+    = c64_symbol_section_adjustment (h, value, sym_sec, &tmp_sec, info);
 
-need_size:
+  if (type == C64_SYM_UNKNOWN)
+    return FALSE;
+
+  if (tmp_sec->flags & SEC_CODE)
     {
-      int howto_index = bfd_r_type - BFD_RELOC_AARCH64_RELOC_START;
-      _bfd_error_handler
-       /* xgettext:c-format */
-       (_("%pB: relocation %s against local symbol without size info"),
-        input_bfd, elfNN_aarch64_howto_table[howto_index].name);
-      bfd_set_error (bfd_error_bad_value);
-      return bfd_reloc_notsupported;
+      *adjust_addr = pcc_low;
+      return TRUE;
     }
+
+  return FALSE;
 }
 
 /* Perform a relocation as part of a final link.  The input relocation type
@@ -6407,6 +6856,7 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
     case BFD_RELOC_AARCH64_TLSDESC_ADD:
     case BFD_RELOC_AARCH64_TLSDESC_CALL:
     case BFD_RELOC_AARCH64_TLSDESC_LDR:
+    case BFD_RELOC_MORELLO_TLSDESC_CALL:
       *unresolved_reloc_p = FALSE;
       return bfd_reloc_ok;
 
@@ -6703,6 +7153,21 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
       off = symbol_got_offset (input_bfd, h, r_symndx);
       base_got = globals->root.sgot;
 
+      bfd_boolean c64_reloc =
+       (bfd_r_type == BFD_RELOC_MORELLO_LD128_GOT_LO12_NC
+        || bfd_r_type == BFD_RELOC_MORELLO_ADR_GOT_PAGE);
+
+      if (signed_addend != 0)
+       {
+         int howto_index = bfd_r_type - BFD_RELOC_AARCH64_RELOC_START;
+         _bfd_error_handler
+         /* xgettext:c-format */
+         (_("%pB: symbol plus addend can not be placed into the GOT "
+            "for relocation %s"),
+            input_bfd, elfNN_aarch64_howto_table[howto_index].name);
+         abort ();
+       }
+
       if (base_got == NULL)
        BFD_ASSERT (h != NULL);
 
@@ -6710,9 +7175,7 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
       if (h != NULL)
        {
          bfd_vma addend = 0;
-         bfd_boolean c64_reloc =
-           (bfd_r_type == BFD_RELOC_MORELLO_LD128_GOT_LO12_NC
-            || bfd_r_type == BFD_RELOC_MORELLO_ADR_GOT_PAGE);
+         bfd_vma frag_value;
 
          /* If a symbol is not dynamic and is not undefined weak, bind it
             locally and generate a RELATIVE relocation under PIC mode.
@@ -6733,7 +7196,14 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
              && !symbol_got_offset_mark_p (input_bfd, h, r_symndx))
            relative_reloc = TRUE;
 
-         value = aarch64_calculate_got_entry_vma (h, globals, info, value,
+         if (c64_reloc
+             && c64_symbol_adjust (h, value, sym_sec, info, &frag_value))
+           signed_addend = (value | h->target_internal) - frag_value;
+         else
+           frag_value = value | h->target_internal;
+
+         value = aarch64_calculate_got_entry_vma (h, globals, info,
+                                                  frag_value,
                                                   output_bfd,
                                                   unresolved_reloc_p);
          /* Record the GOT entry address which will be used when generating
@@ -6747,7 +7217,6 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
          value = _bfd_aarch64_elf_resolve_relocation (input_bfd, bfd_r_type,
                                                       place, value,
                                                       addend, weak_undef_p);
-       value |= h->target_internal;
        }
       else
       {
@@ -6771,14 +7240,24 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
 
        if (!symbol_got_offset_mark_p (input_bfd, h, r_symndx))
          {
-           bfd_put_64 (output_bfd, value, base_got->contents + off);
+           bfd_vma frag_value;
+
+           if (c64_reloc
+               && c64_symbol_adjust (h, value, sym_sec, info, &frag_value))
+             signed_addend = (value | sym->st_target_internal) - frag_value;
+           else
+             frag_value = value | sym->st_target_internal;
+
+           bfd_put_64 (output_bfd, frag_value, base_got->contents + off);
 
            /* For local symbol, we have done absolute relocation in static
               linking stage.  While for shared library, we need to update the
               content of GOT entry according to the shared object's runtime
               base address.  So, we need to generate a R_AARCH64_RELATIVE reloc
               for dynamic linker.  */
-           if (bfd_link_pic (info))
+           if (bfd_link_pic (info)
+               || (!bfd_link_pic (info) && bfd_link_executable (info)
+                   && c64_reloc))
              relative_reloc = TRUE;
 
            symbol_got_offset_mark (input_bfd, h, r_symndx);
@@ -6794,8 +7273,6 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
        value = _bfd_aarch64_elf_resolve_relocation (input_bfd, bfd_r_type,
                                                     place, value,
                                                     addend, weak_undef_p);
-
-       value |= sym->st_target_internal;
       }
 
       if (relative_reloc)
@@ -6809,12 +7286,11 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
 
          /* For a C64 relative relocation, also add size and permissions into
             the frag.  */
-         if (bfd_r_type == BFD_RELOC_MORELLO_LD128_GOT_LO12_NC
-             || bfd_r_type == BFD_RELOC_MORELLO_ADR_GOT_PAGE)
+         if (c64_reloc)
            {
              bfd_reloc_status_type ret;
 
-             ret = c64_fixup_frag (input_bfd, info, bfd_r_type, sym, h,
+             ret = c64_fixup_frag (input_bfd, info, sym, h,
                                    sym_sec, base_got->contents + off + 8,
                                    orig_value, 0);
 
@@ -6826,7 +7302,7 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
              if (bfd_link_executable (info) && !bfd_link_pic (info))
                s = globals->srelcaps;
 
-             outrel.r_addend = 0;
+             outrel.r_addend = signed_addend;
            }
          else
            outrel.r_addend = orig_value;
@@ -6954,10 +7430,12 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
 
     case BFD_RELOC_AARCH64_TLSDESC_ADD_LO12:
     case BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21:
+    case BFD_RELOC_MORELLO_TLSDESC_ADR_PAGE20:
     case BFD_RELOC_AARCH64_TLSDESC_ADR_PREL21:
     case BFD_RELOC_AARCH64_TLSDESC_LD32_LO12_NC:
     case BFD_RELOC_AARCH64_TLSDESC_LD64_LO12:
     case BFD_RELOC_AARCH64_TLSDESC_LD_PREL19:
+    case BFD_RELOC_MORELLO_TLSDESC_LD128_LO12:
       if (globals->root.sgot == NULL)
        return bfd_reloc_notsupported;
       value = (symbol_tlsdesc_got_offset (input_bfd, h, r_symndx)
@@ -7017,13 +7495,14 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
 
          bfd_reloc_status_type ret;
 
-         ret = c64_fixup_frag (input_bfd, info, bfd_r_type, sym, h, sym_sec,
+         ret = c64_fixup_frag (input_bfd, info, sym, h, sym_sec,
                                hit_data + 8, value, signed_addend);
 
          if (ret != bfd_reloc_continue)
            return ret;
 
          outrel.r_addend = signed_addend;
+         value |= (h != NULL ? h->target_internal : sym->st_target_internal);
 
          /* Emit a dynamic relocation if we are building PIC.  */
          if (h != NULL
@@ -7034,7 +7513,16 @@ elfNN_aarch64_final_link_relocate (reloc_howto_type *howto,
          else
            outrel.r_info = ELFNN_R_INFO (0, MORELLO_R (RELATIVE));
 
-         value |= (h != NULL ? h->target_internal : sym->st_target_internal);
+         /* Symbols without size information get bounds to the
+            whole section: adjust the base of the capability to the
+            start of the section and set the addend to obtain the
+            correct address for the symbol.  */
+         bfd_vma new_value;
+         if (c64_symbol_adjust (h, value, sym_sec, info, &new_value))
+           {
+             outrel.r_addend += (value - new_value);
+             value = new_value;
+           }
 
          asection *s = globals->srelcaps;
 
@@ -7145,6 +7633,11 @@ clear_erratum_843419_entry (struct elf_aarch64_link_hash_table *globals,
     }
 }
 
+#define BUILD_MOVZ(_reg, _imm) (movz_R0 \
+                               | ((((_imm) >> 16) & 0xffff) << 5) \
+                               | (_reg))
+#define BUILD_MOVK(_reg, _imm) (movk_R0 | (((_imm) & 0xffff) << 5) | (_reg))
+
 /* Handle TLS relaxations.  Relaxing is possible for symbols that use
    R_AARCH64_TLSDESC_ADR_{PAGE, LD64_LO12_NC, ADD_LO12_NC} during a static
    link.
@@ -7154,19 +7647,56 @@ clear_erratum_843419_entry (struct elf_aarch64_link_hash_table *globals,
    case of error.  */
 
 static bfd_reloc_status_type
-elfNN_aarch64_tls_relax (struct elf_aarch64_link_hash_table *globals,
-                        bfd *input_bfd, asection *input_section,
+elfNN_aarch64_tls_relax (bfd *input_bfd, struct bfd_link_info *info,
+                        asection *input_section,
                         bfd_byte *contents, Elf_Internal_Rela *rel,
-                        struct elf_link_hash_entry *h)
+                        struct elf_link_hash_entry *h, unsigned long r_symndx)
 {
   bfd_boolean is_local = h == NULL;
+
   unsigned int r_type = ELFNN_R_TYPE (rel->r_info);
   unsigned long insn;
+  bfd_vma sym_size = 0;
+  struct elf_aarch64_link_hash_table *globals = elf_aarch64_hash_table (info);
 
   BFD_ASSERT (globals && input_bfd && contents && rel);
 
+  if (is_local)
+    {
+      if (h != NULL)
+       sym_size = h->size;
+      else
+       {
+         Elf_Internal_Sym *sym;
+
+         sym = bfd_sym_from_r_symndx (&globals->root.sym_cache, input_bfd,
+                                      r_symndx);
+         BFD_ASSERT (sym != NULL);
+         sym_size = sym->st_size;
+       }
+    }
+
   switch (elfNN_aarch64_bfd_reloc_from_type (input_bfd, r_type))
     {
+    case BFD_RELOC_MORELLO_TLSDESC_ADR_PAGE20:
+      if (is_local || !bfd_link_pic (info))
+       {
+         /* GD->LE relaxation:
+            nop                     =>   movz x1, objsize_hi16
+            adrp x0, :tlsdesc:var   =>   movz x0, :tprel_g1:var  */
+         bfd_putl32 (BUILD_MOVZ(1, sym_size), contents + rel->r_offset - 4);
+         bfd_putl32 (movz_R0, contents + rel->r_offset);
+
+         /* We have relaxed the adrp into a mov, we may have to clear any
+            pending erratum fixes.  */
+         clear_erratum_843419_entry (globals, rel->r_offset, input_section);
+         return bfd_reloc_continue;
+       }
+      else
+       {
+         /* GD->IE relaxation: Not implemented.  */
+         return bfd_reloc_continue;
+       }
     case BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21:
     case BFD_RELOC_AARCH64_TLSGD_ADR_PAGE21:
       if (is_local)
@@ -7335,6 +7865,19 @@ elfNN_aarch64_tls_relax (struct elf_aarch64_link_hash_table *globals,
     case BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_PREL19:
       return bfd_reloc_continue;
 
+    case BFD_RELOC_MORELLO_TLSDESC_LD128_LO12:
+      if (is_local || !bfd_link_pic (info))
+       {
+         /* GD->LE relaxation:
+            ldr xd, [x0, #:tlsdesc_lo12:var] => movk x0, :tprel_g0_nc:var  */
+         bfd_putl32 (movk_R0, contents + rel->r_offset);
+         return bfd_reloc_continue;
+       }
+      else
+       {
+         /* GD->IE relaxation: not implemented.  */
+         return bfd_reloc_continue;
+       }
     case BFD_RELOC_AARCH64_TLSDESC_LDNN_LO12_NC:
       if (is_local)
        {
@@ -7399,13 +7942,35 @@ elfNN_aarch64_tls_relax (struct elf_aarch64_link_hash_table *globals,
          return bfd_reloc_continue;
        }
 
-    case BFD_RELOC_AARCH64_TLSDESC_ADD:
+    case BFD_RELOC_MORELLO_TLSDESC_CALL:
+      /* GD->LE relaxation:
+        blr cd                           =>   add c0, c2, x0  */
+      if (is_local || !bfd_link_pic (info))
+       {
+         bfd_putl32 (0xc2a06040, contents + rel->r_offset);
+         return bfd_reloc_ok;
+       }
+      else
+       goto set_nop;
+
     case BFD_RELOC_AARCH64_TLSDESC_ADD_LO12:
+      /* GD->LE relaxation:
+        ldr cd, [c0, #:tlsdesc_lo12:var] => movk x1, objsize_lo16  */
+      if ((is_local || !bfd_link_pic (info))
+         && ELFNN_R_TYPE (rel[1].r_info) == MORELLO_R (TLSDESC_CALL))
+       {
+         bfd_putl32 (BUILD_MOVK(1, sym_size), contents + rel->r_offset);
+         return bfd_reloc_continue;
+       }
+
+      /* Fall through.  */
+    case BFD_RELOC_AARCH64_TLSDESC_ADD:
     case BFD_RELOC_AARCH64_TLSDESC_CALL:
       /* GD->IE/LE relaxation:
         add x0, x0, #:tlsdesc_lo12:var   =>   nop
         blr xd                           =>   nop
        */
+set_nop:
       bfd_putl32 (INSN_NOP, contents + rel->r_offset);
       return bfd_reloc_ok;
 
@@ -7685,12 +8250,20 @@ elfNN_aarch64_relocate_section (bfd *output_bfd,
             input_section, (uint64_t) rel->r_offset, howto->name, name);
        }
 
+      if (r_symndx
+         && h
+         && IS_AARCH64_TLS_RELOC (bfd_r_type)
+         && h->root.type == bfd_link_hash_undefweak)
+       /* We have already warned about these in aarch64_check_relocs,
+          so just skip over them.  */
+       continue;
+
       /* We relax only if we can see that there can be a valid transition
         from a reloc type to another.
         We call elfNN_aarch64_final_link_relocate unless we're completely
         done, i.e., the relaxation produced the final output we want.  */
 
-      relaxed_bfd_r_type = aarch64_tls_transition (input_bfd, info, r_type,
+      relaxed_bfd_r_type = aarch64_tls_transition (input_bfd, info, rel,
                                                   h, r_symndx);
       if (relaxed_bfd_r_type != bfd_r_type)
        {
@@ -7698,8 +8271,8 @@ elfNN_aarch64_relocate_section (bfd *output_bfd,
          howto = elfNN_aarch64_howto_from_bfd_reloc (bfd_r_type);
          BFD_ASSERT (howto != NULL);
          r_type = howto->type;
-         r = elfNN_aarch64_tls_relax (globals, input_bfd, input_section,
-                                      contents, rel, h);
+         r = elfNN_aarch64_tls_relax (input_bfd, info, input_section,
+                                      contents, rel, h, r_symndx);
          unresolved_reloc = 0;
        }
       else
@@ -7723,6 +8296,8 @@ elfNN_aarch64_relocate_section (bfd *output_bfd,
                                               h, &unresolved_reloc,
                                               save_addend, &addend, sym);
 
+      bfd_boolean c64_rtype = FALSE;
+
       switch (elfNN_aarch64_bfd_reloc_from_type (input_bfd, r_type))
        {
        case BFD_RELOC_AARCH64_TLSGD_ADD_LO12_NC:
@@ -7876,6 +8451,11 @@ elfNN_aarch64_relocate_section (bfd *output_bfd,
            }
          break;
 
+       case BFD_RELOC_MORELLO_TLSDESC_ADR_PAGE20:
+       case BFD_RELOC_MORELLO_TLSDESC_LD128_LO12:
+         c64_rtype = TRUE;
+         /* Fall through.  */
+
        case BFD_RELOC_AARCH64_TLSDESC_ADD_LO12:
        case BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21:
        case BFD_RELOC_AARCH64_TLSDESC_ADR_PREL21:
@@ -7900,7 +8480,10 @@ elfNN_aarch64_relocate_section (bfd *output_bfd,
                {
                  bfd_byte *loc;
                  Elf_Internal_Rela rela;
-                 rela.r_info = ELFNN_R_INFO (indx, AARCH64_R (TLSDESC));
+
+                 rela.r_info = ELFNN_R_INFO (indx,
+                                             (c64_rtype ? MORELLO_R (TLSDESC)
+                                              : AARCH64_R (TLSDESC)));
 
                  rela.r_addend = 0;
                  rela.r_offset = (globals->root.sgotplt->output_section->vma
@@ -8477,19 +9060,19 @@ elfNN_aarch64_check_relocs (bfd *abfd, struct bfd_link_info *info,
   symtab_hdr = &elf_symtab_hdr (abfd);
   sym_hashes = elf_sym_hashes (abfd);
 
-  bfd_elfNN_aarch64_init_maps (abfd);
+  bfd_elfNN_aarch64_init_maps (abfd, info);
 
   rel_end = relocs + sec->reloc_count;
   for (rel = relocs; rel < rel_end; rel++)
     {
       struct elf_link_hash_entry *h;
-      unsigned int r_symndx;
-      unsigned int r_type;
+      unsigned int r_symndx, r_type;
       bfd_reloc_code_real_type bfd_r_type;
       Elf_Internal_Sym *isym;
 
       r_symndx = ELFNN_R_SYM (rel->r_info);
       r_type = ELFNN_R_TYPE (rel->r_info);
+      bfd_r_type = elfNN_aarch64_bfd_reloc_from_type (abfd, r_type);
 
       if (r_symndx >= NUM_SHDR_ENTRIES (symtab_hdr))
        {
@@ -8532,8 +9115,28 @@ elfNN_aarch64_check_relocs (bfd *abfd, struct bfd_link_info *info,
            h = (struct elf_link_hash_entry *) h->root.u.i.link;
        }
 
+      /* Ignore TLS relocations against weak undef symbols and warn about them.
+        The behaviour of weak TLS variables is not well defined. Since making
+        these well behaved is not a priority for Morello, we simply ignore
+        TLS relocations against such symbols here to avoid the linker crashing
+        on these and to enable making progress in other areas.  */
+      if (r_symndx
+         && h
+         && IS_AARCH64_TLS_RELOC (bfd_r_type)
+         && h->root.type == bfd_link_hash_undefweak)
+       {
+         int howto_index = bfd_r_type - BFD_RELOC_AARCH64_RELOC_START;
+         _bfd_error_handler (_("%pB(%pA+%#" PRIx64 "): ignoring TLS relocation "
+                               "%s against undef weak symbol %s"),
+                             abfd, sec,
+                             (uint64_t) rel->r_offset,
+                             elfNN_aarch64_howto_table[howto_index].name,
+                             h->root.root.string);
+         continue;
+       }
+
       /* Could be done earlier, if h were already available.  */
-      bfd_r_type = aarch64_tls_transition (abfd, info, r_type, h, r_symndx);
+      bfd_r_type = aarch64_tls_transition (abfd, info, rel, h, r_symndx);
 
       if (h != NULL)
        {
@@ -8781,6 +9384,8 @@ elfNN_aarch64_check_relocs (bfd *abfd, struct bfd_link_info *info,
             there are no dangling GOT_PAGE relocs.  */
        case BFD_RELOC_MORELLO_ADR_GOT_PAGE:
        case BFD_RELOC_MORELLO_LD128_GOT_LO12_NC:
+       case BFD_RELOC_MORELLO_TLSDESC_ADR_PAGE20:
+       case BFD_RELOC_MORELLO_TLSDESC_LD128_LO12:
          htab->c64_rel = 1;
          /* Fall through.  */
 
@@ -8860,10 +9465,14 @@ elfNN_aarch64_check_relocs (bfd *abfd, struct bfd_link_info *info,
            if ((got_type & GOT_TLS_IE) && GOT_TLS_GD_ANY_P (got_type))
              got_type &= ~ (GOT_TLSDESC_GD | GOT_TLS_GD);
 
-           /* GOT_CAP has higher precedence due to higher alignment and size
-              requirements, so do not overwrite it.  XXX This should be
-              revisited when we add TLS relocations.  */
-           if (old_got_type != got_type && old_got_type != GOT_CAP)
+           /* Prefer the capability reference.  */
+           if ((old_got_type & GOT_CAP) && (got_type & GOT_NORMAL))
+             {
+               got_type &= ~GOT_NORMAL;
+               got_type |= GOT_CAP;
+             }
+
+           if (old_got_type != got_type)
              {
                if (h != NULL)
                  elf_aarch64_hash_entry (h)->got_type = got_type;
@@ -9531,7 +10140,10 @@ elfNN_aarch64_allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
               || h->root.type != bfd_link_hash_undefweak)
              && (!bfd_link_executable (info)
                  || indx != 0
-                 || WILL_CALL_FINISH_DYNAMIC_SYMBOL (dyn, 0, h)))
+                 || WILL_CALL_FINISH_DYNAMIC_SYMBOL (dyn, 0, h)
+                 /* On Morello support only TLSDESC_GD to TLSLE relaxation;
+                    for everything else we must emit a dynamic relocation.  */
+                 || got_type & GOT_CAP))
            {
              if (got_type & GOT_TLSDESC_GD)
                {
@@ -9869,6 +10481,23 @@ elfNN_aarch64_size_dynamic_sections (bfd *output_bfd,
                 elfNN_aarch64_allocate_local_ifunc_dynrelocs,
                 info);
 
+  if (bfd_link_executable (info)
+      && !bfd_link_pic (info)
+      && htab->srelcaps
+      && htab->srelcaps->size > 0)
+    {
+      struct elf_link_hash_entry *h;
+
+      h = _bfd_elf_define_linkage_sym (output_bfd, info,
+                                      htab->srelcaps,
+                                      "__rela_dyn_start");
+      h = _bfd_elf_define_linkage_sym (output_bfd, info,
+                                      htab->srelcaps,
+                                      "__rela_dyn_end");
+
+      h->root.u.def.value = htab->srelcaps->vma + htab->srelcaps->size;
+    }
+
   /* For every jump slot reserved in the sgotplt, reloc_count is
      incremented.  However, when we reserve space for TLS descriptors,
      it's not incremented, so in order to compute the space reserved
@@ -9904,7 +10533,7 @@ elfNN_aarch64_size_dynamic_sections (bfd *output_bfd,
       {
        if (!is_aarch64_elf (ibfd))
          continue;
-       bfd_elfNN_aarch64_init_maps (ibfd);
+       bfd_elfNN_aarch64_init_maps (ibfd, info);
       }
 
   /* We now have determined the sizes of the various dynamic sections.
@@ -10182,25 +10811,6 @@ elfNN_aarch64_always_size_sections (bfd *output_bfd,
   if (bfd_link_relocatable (info))
     return TRUE;
 
-  struct elf_aarch64_link_hash_table *htab = elf_aarch64_hash_table (info);
-
-  if (bfd_link_executable (info)
-      && !bfd_link_pic (info)
-      && htab->srelcaps
-      && htab->srelcaps->size > 0)
-    {
-      struct elf_link_hash_entry *h;
-
-      h = _bfd_elf_define_linkage_sym (output_bfd, info,
-                                      htab->srelcaps,
-                                      "__cap_dynrelocs_start");
-      h = _bfd_elf_define_linkage_sym (output_bfd, info,
-                                      htab->srelcaps,
-                                      "__cap_dynrelocs_end");
-
-      h->root.u.def.value = htab->srelcaps->vma + htab->srelcaps->size;
-    }
-
   tls_sec = elf_hash_table (info)->tls_sec;
 
   if (tls_sec)
@@ -10579,8 +11189,17 @@ elfNN_aarch64_finish_dynamic_sections (bfd *output_bfd,
          const bfd_byte *entry = elfNN_aarch64_tlsdesc_small_plt_entry;
          htab->tlsdesc_plt_entry_size = PLT_TLSDESC_ENTRY_SIZE;
 
+         unsigned adrp_rtype = BFD_RELOC_AARCH64_ADR_HI21_PCREL;
+         unsigned ldr_rtype = BFD_RELOC_AARCH64_LDSTNN_LO12;
+
          aarch64_plt_type type = elf_aarch64_tdata (output_bfd)->plt_type;
-         if (type == PLT_BTI || type == PLT_BTI_PAC)
+         if (htab->c64_rel)
+           {
+             entry = elfNN_aarch64_tlsdesc_small_plt_c64_entry;
+             adrp_rtype = BFD_RELOC_MORELLO_ADR_HI20_PCREL;
+             ldr_rtype = BFD_RELOC_AARCH64_LDST128_LO12;
+           }
+         else if (type == PLT_BTI || type == PLT_BTI_PAC)
            {
              entry = elfNN_aarch64_tlsdesc_small_plt_bti_entry;
            }
@@ -10620,21 +11239,21 @@ elfNN_aarch64_finish_dynamic_sections (bfd *output_bfd,
 
            /* adrp x2, DT_TLSDESC_GOT */
            elf_aarch64_update_plt_entry (output_bfd,
-                                         BFD_RELOC_AARCH64_ADR_HI21_PCREL,
+                                         adrp_rtype,
                                          plt_entry + 4,
                                          (PG (dt_tlsdesc_got)
                                           - PG (adrp1_addr)));
 
            /* adrp x3, 0 */
            elf_aarch64_update_plt_entry (output_bfd,
-                                         BFD_RELOC_AARCH64_ADR_HI21_PCREL,
+                                         adrp_rtype,
                                          plt_entry + 8,
                                          (PG (pltgot_addr)
                                           - PG (adrp2_addr)));
 
            /* ldr x2, [x2, #0] */
            elf_aarch64_update_plt_entry (output_bfd,
-                                         BFD_RELOC_AARCH64_LDSTNN_LO12,
+                                         ldr_rtype,
                                          plt_entry + 12,
                                          PG_OFFSET (dt_tlsdesc_got));