diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
index acc45ebb549..43db8f1b5c0 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -663,8 +663,12 @@ riscv_elf_copy_indirect_symbol (struct bfd_link_info *info,
 }
 
 static char
-riscv_elf_tls_type_from_hi_reloc (unsigned int r_type)
+riscv_elf_tls_type_from_hi_reloc (struct bfd_link_info *info,
+				  unsigned int r_type,
+				  struct elf_link_hash_entry *h,
+				  bool can_relax)
 {
+  bool local_exec = SYMBOL_REFERENCES_LOCAL (info, h);
   switch (r_type)
     {
       case R_RISCV_TLS_GD_HI20:
@@ -672,7 +676,10 @@ riscv_elf_tls_type_from_hi_reloc (unsigned int r_type)
       case R_RISCV_TLS_GOT_HI20:
 	return GOT_TLS_IE;
       case R_RISCV_TLSDESC_HI20:
-	return GOT_TLSDESC;
+	if (!can_relax || !bfd_link_executable (info))
+	  return GOT_TLSDESC;
+	else
+	  return local_exec ? GOT_TLS_LE : GOT_TLS_IE;
       case R_RISCV_TPREL_HI20:
 	return GOT_TLS_LE;
       default:
@@ -866,7 +873,10 @@ riscv_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	  case R_RISCV_TLSDESC_HI20:
 	  case R_RISCV_TPREL_HI20:
 	    {
-	      char tls_type = riscv_elf_tls_type_from_hi_reloc (r_type);
+	      bool can_relax = rel != relocs + sec->reloc_count - 1
+			       && ELFNN_R_TYPE ((rel + 1)->r_info) == R_RISCV_RELAX
+			       && rel->r_offset == (rel + 1)->r_offset;
+	      char tls_type = riscv_elf_tls_type_from_hi_reloc (info, r_type, h, can_relax);
 
 	      /* Local exec is only allowed for executables.  */
 	      if (tls_type == GOT_TLS_LE && !bfd_link_executable (info))
@@ -1816,6 +1826,8 @@ perform_relocation (const reloc_howto_type *howto,
     case R_RISCV_TLS_GOT_HI20:
     case R_RISCV_TLS_GD_HI20:
     case R_RISCV_TLSDESC_HI20:
+    case R_RISCV_TLSDESC_LE_HI:
+    case R_RISCV_TLSDESC_IE_HI:
       if (ARCH_SIZE > 32 && !VALID_UTYPE_IMM (RISCV_CONST_HIGH_PART (value)))
 	return bfd_reloc_overflow;
       value = ENCODE_UTYPE_IMM (RISCV_CONST_HIGH_PART (value));
@@ -1828,6 +1840,8 @@ perform_relocation (const reloc_howto_type *howto,
     case R_RISCV_PCREL_LO12_I:
     case R_RISCV_TLSDESC_LOAD_LO12:
     case R_RISCV_TLSDESC_ADD_LO12:
+    case R_RISCV_TLSDESC_LE_LO:
+    case R_RISCV_TLSDESC_IE_LO:
       value = ENCODE_ITYPE_IMM (value);
       break;
 
@@ -2834,6 +2848,35 @@ riscv_elf_relocate_section (bfd *output_bfd,
 	    r = bfd_reloc_overflow;
 	  break;
 
+	case R_RISCV_TLSDESC_IE_HI:
+	  {
+	    bfd_vma insn = MATCH_AUIPC | (X_A0 << OP_SH_RD);
+	    relocation = dtpoff (info, relocation);
+	    bfd_putl32 (insn, contents + rel->r_offset);
+	    break;
+	  }
+	case R_RISCV_TLSDESC_IE_LO:
+	  {
+	    bfd_vma insn = MATCH_LREG | (X_A0 << OP_SH_RD) | (X_A0 << OP_SH_RS1);
+	    relocation = dtpoff (info, relocation);
+	    bfd_putl32 (insn, contents + rel->r_offset);
+	    break;
+	  }
+	case R_RISCV_TLSDESC_LE_HI:
+	  {
+	    bfd_vma insn = MATCH_LUI | (X_A0 << OP_SH_RD);
+	    relocation = tpoff (info, relocation);
+	    bfd_putl32 (insn, contents + rel->r_offset);
+	    break;
+	  }
+	case R_RISCV_TLSDESC_LE_LO:
+	  {
+	    bfd_vma insn = MATCH_ADDI | (X_A0 << OP_SH_RD) | (X_A0 << OP_SH_RS1);
+	    relocation = tpoff (info, relocation);
+	    bfd_putl32 (insn, contents + rel->r_offset);
+	    break;
+	  }
+
 	case R_RISCV_GPREL_I:
 	case R_RISCV_GPREL_S:
 	  {
@@ -4878,6 +4921,95 @@ _bfd_riscv_relax_tls_le (bfd *abfd,
     }
 }
 
+/* Relax TLSDESC (global-dynamic) references to TLS IE or LE references. */
+
+static bool
+_bfd_riscv_relax_tlsdesc (bfd *abfd,
+			  asection *sec,
+			  asection *sym_sec,
+			  struct bfd_link_info *link_info,
+			  struct elf_link_hash_entry *h,
+			  Elf_Internal_Rela *rel,
+			  bfd_vma symval,
+			  bfd_vma max_alignment ATTRIBUTE_UNUSED,
+			  bfd_vma reserve_size ATTRIBUTE_UNUSED,
+			  bool *again,
+			  riscv_pcgp_relocs *pcgp_relocs,
+			  bool undefined_weak)
+{
+  BFD_ASSERT (rel->r_offset + 4 <= sec->size);
+  BFD_ASSERT (bfd_link_executable (link_info));
+  riscv_pcgp_hi_reloc *hi = NULL;
+  bool local_exec;
+  unsigned sym;
+
+  /* Chain the _LO relocs to their corresponding _HI reloc to compute the
+     actual target address.  */
+  switch (ELFNN_R_TYPE (rel->r_info)) {
+    case R_RISCV_TLSDESC_HI20: {
+      /* If the corresponding lo relocation has already been seen then it's not
+	 safe to relax this relocation.  */
+      if (riscv_find_pcgp_lo_reloc (pcgp_relocs, rel->r_offset))
+	return true;
+      riscv_record_pcgp_hi_reloc (pcgp_relocs,
+				  rel->r_offset,
+				  rel->r_addend,
+				  symval,
+				  ELFNN_R_SYM (rel->r_info),
+				  sym_sec,
+				  h,
+				  undefined_weak);
+      sym = ELFNN_R_SYM (rel->r_info);
+      break;
+    }
+
+    case R_RISCV_TLSDESC_LOAD_LO12:
+    case R_RISCV_TLSDESC_ADD_LO12:
+    case R_RISCV_TLSDESC_CALL: {
+      bfd_vma hi_sec_off = symval - sec_addr (sym_sec);
+      hi = riscv_find_pcgp_hi_reloc (pcgp_relocs, hi_sec_off);
+      if (hi == NULL) {
+	riscv_record_pcgp_lo_reloc (pcgp_relocs, hi_sec_off);
+	return true;
+      }
+      sym = hi->hi_sym;
+      symval = hi->hi_addr;
+      sym_sec = hi->sym_sec;
+      h = hi->h;
+      break;
+    }
+    default:
+      abort ();
+  }
+
+  local_exec = SYMBOL_REFERENCES_LOCAL (link_info, h);
+
+  switch (ELFNN_R_TYPE (rel->r_info)) {
+    case R_RISCV_TLSDESC_HI20:
+      *again = true;
+      riscv_relax_delete_bytes (abfd, sec, rel->r_offset, 4, link_info,
+				pcgp_relocs, rel);
+      break;
+    case R_RISCV_TLSDESC_LOAD_LO12:
+      riscv_relax_delete_bytes (abfd, sec, rel->r_offset, 4, link_info,
+				pcgp_relocs, rel);
+      break;
+    case R_RISCV_TLSDESC_ADD_LO12:
+      rel->r_info = ELFNN_R_INFO (sym, local_exec ? R_RISCV_TLSDESC_LE_HI : R_RISCV_TLSDESC_IE_HI);
+      rel->r_addend += hi->hi_addend;
+      break;
+    case R_RISCV_TLSDESC_CALL:
+      rel->r_info = ELFNN_R_INFO (sym, local_exec ? R_RISCV_TLSDESC_LE_LO : R_RISCV_TLSDESC_IE_LO);
+      rel->r_addend += hi->hi_addend;
+      break;
+    default:
+      abort ();
+  }
+
+  return true;
+}
+
+
 /* Implement R_RISCV_ALIGN by deleting excess alignment NOPs.
    Once we've handled an R_RISCV_ALIGN, we can't relax anything else.  */
 
@@ -5182,6 +5314,12 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
 		   || type == R_RISCV_TPREL_LO12_I
 		   || type == R_RISCV_TPREL_LO12_S)
 	    relax_func = _bfd_riscv_relax_tls_le;
+	  else if (bfd_link_executable (info)
+		   && (type == R_RISCV_TLSDESC_HI20
+		       || type == R_RISCV_TLSDESC_LOAD_LO12
+		       || type == R_RISCV_TLSDESC_ADD_LO12
+		       || type == R_RISCV_TLSDESC_CALL))
+	    relax_func = _bfd_riscv_relax_tlsdesc;
 	  else if (!bfd_link_pic (info)
 		   && (type == R_RISCV_PCREL_HI20
 		       || type == R_RISCV_PCREL_LO12_I
diff --git a/bfd/elfxx-riscv.c b/bfd/elfxx-riscv.c
index a4aa71fd809..86f1538f69a 100644
--- a/bfd/elfxx-riscv.c
+++ b/bfd/elfxx-riscv.c
@@ -958,6 +958,61 @@ static reloc_howto_type howto_table_internal[] =
 	 0,				/* src_mask */
 	 ENCODE_STYPE_IMM (-1U),	/* dst_mask */
 	 false),			/* pcrel_offset */
+
+  /* TLSDESC relaxed to Initial Exec.  */
+  HOWTO (R_RISCV_TLSDESC_IE_HI,		/* type */
+	 0,				/* rightshift */
+	 4,				/* size */
+	 32,				/* bitsize */
+	 true,				/* pc_relative */
+	 0,				/* bitpos */
+	 complain_overflow_signed,	/* complain_on_overflow */
+	 bfd_elf_generic_reloc,		/* special_function */
+	 "R_RISCV_TLSDESC_IE_HI",	/* name */
+	 false,				/* partial_inplace */
+	 0,				/* src_mask */
+	 ENCODE_UTYPE_IMM (-1U),	/* dst_mask */
+	 false),			/* pcrel_offset */
+  HOWTO (R_RISCV_TLSDESC_IE_LO,		/* type */
+	 0,				/* rightshift */
+	 4,				/* size */
+	 32,				/* bitsize */
+	 true,				/* pc_relative */
+	 0,				/* bitpos */
+	 complain_overflow_signed,	/* complain_on_overflow */
+	 bfd_elf_generic_reloc,		/* special_function */
+	 "R_RISCV_TLSDESC_IE_LO",	/* name */
+	 false,				/* partial_inplace */
+	 0,				/* src_mask */
+	 ENCODE_ITYPE_IMM (-1U),	/* dst_mask */
+	 false),			/* pcrel_offset */
+  /* TLSDESC relaxed to Local Exec.  */
+  HOWTO (R_RISCV_TLSDESC_LE_HI,		/* type */
+	 0,				/* rightshift */
+	 4,				/* size */
+	 32,				/* bitsize */
+	 false,				/* pc_relative */
+	 0,				/* bitpos */
+	 complain_overflow_signed,	/* complain_on_overflow */
+	 bfd_elf_generic_reloc,		/* special_function */
+	 "R_RISCV_TLSDESC_LE_HI",	/* name */
+	 false,				/* partial_inplace */
+	 0,				/* src_mask */
+	 ENCODE_UTYPE_IMM (-1U),	/* dst_mask */
+	 false),			/* pcrel_offset */
+  HOWTO (R_RISCV_TLSDESC_LE_LO,		/* type */
+	 0,				/* rightshift */
+	 4,				/* size */
+	 32,				/* bitsize */
+	 false,				/* pc_relative */
+	 0,				/* bitpos */
+	 complain_overflow_signed,	/* complain_on_overflow */
+	 bfd_elf_generic_reloc,		/* special_function */
+	 "R_RISCV_TLSDESC_LE_LO",	/* name */
+	 false,				/* partial_inplace */
+	 0,				/* src_mask */
+	 ENCODE_ITYPE_IMM (-1U),	/* dst_mask */
+	 false),			/* pcrel_offset */
 };
 
 /* A mapping from BFD reloc types to RISC-V ELF reloc types.  */
diff --git a/include/elf/riscv.h b/include/elf/riscv.h
index c1e73f7f5c0..a99038d42d5 100644
--- a/include/elf/riscv.h
+++ b/include/elf/riscv.h
@@ -98,12 +98,16 @@ START_RELOC_NUMBERS (elf_riscv_reloc_type)
 END_RELOC_NUMBERS (R_RISCV_max)
 
 /* Internal relocations used exclusively by the relaxation pass.  */
-#define R_RISCV_DELETE  (R_RISCV_max)
-#define R_RISCV_RVC_LUI (R_RISCV_max + 1)
-#define R_RISCV_GPREL_I (R_RISCV_max + 2)
-#define R_RISCV_GPREL_S (R_RISCV_max + 3)
-#define R_RISCV_TPREL_I (R_RISCV_max + 4)
-#define R_RISCV_TPREL_S (R_RISCV_max + 5)
+#define R_RISCV_DELETE		(R_RISCV_max)
+#define R_RISCV_RVC_LUI		(R_RISCV_max + 1)
+#define R_RISCV_GPREL_I		(R_RISCV_max + 2)
+#define R_RISCV_GPREL_S		(R_RISCV_max + 3)
+#define R_RISCV_TPREL_I		(R_RISCV_max + 4)
+#define R_RISCV_TPREL_S		(R_RISCV_max + 5)
+#define R_RISCV_TLSDESC_IE_HI	(R_RISCV_max + 6)
+#define R_RISCV_TLSDESC_IE_LO	(R_RISCV_max + 7)
+#define R_RISCV_TLSDESC_LE_HI	(R_RISCV_max + 8)
+#define R_RISCV_TLSDESC_LE_LO	(R_RISCV_max + 9)
 
 /* Processor specific flags for the ELF header e_flags field.  */
 
diff --git a/include/opcode/riscv.h b/include/opcode/riscv.h
index adea7dbc794..6e359d11388 100644
--- a/include/opcode/riscv.h
+++ b/include/opcode/riscv.h
@@ -355,6 +355,7 @@ static inline unsigned int riscv_insn_length (insn_t insn)
 #define X_T0 5
 #define X_T1 6
 #define X_T2 7
+#define X_A0 10
 #define X_T3 28
 
 #define NGPR 32
diff --git a/ld/testsuite/ld-riscv-elf/tlsbin.d b/ld/testsuite/ld-riscv-elf/tlsbin.d
index 79b7ade405e..cdcd51a9199 100644
--- a/ld/testsuite/ld-riscv-elf/tlsbin.d
+++ b/ld/testsuite/ld-riscv-elf/tlsbin.d
@@ -2,11 +2,6 @@
 #ld: -no-pie tmpdir/tlslib.so
 #readelf: -Wr
 
-Relocation section '.rela.dyn' at offset 0x[0-9a-f]+ contains 2 entries:
- +Offset +Info +Type +Symbol's Value +Symbol's Name \+ Addend
-[0-9a-f]+ +[0-9a-f]+ R_RISCV_TLSDESC +4
-[0-9a-f]+ +[0-9a-f]+ R_RISCV_TLSDESC +0
-
 Relocation section '.rela.plt' at offset 0x[0-9a-f]+ contains 1 entry:
  +Offset +Info +Type +Symbol's Value +Symbol's Name \+ Addend
 [0-9a-f]+ +[0-9a-f]+ R_RISCV_JUMP_SLOT +[0-9a-f]+ __tls_get_addr \+ 0
