+/* Beginning of stm32l4xx work-around. */
+
+/* Functions encoding instructions necessary for the emission of the
+ fix-stm32l4xx-629360.
+ Encoding is extracted from the
+ ARM (C) Architecture Reference Manual
+ ARMv7-A and ARMv7-R edition
+ ARM DDI 0406C.b (ID072512). */
+
+static inline bfd_vma
+create_instruction_branch_absolute (int branch_offset)
+{
+ /* A8.8.18 B (A8-334)
+ B target_address (Encoding T4). */
+ /* 1111 - 0Sii - iiii - iiii - 10J1 - Jiii - iiii - iiii. */
+ /* jump offset is: S:I1:I2:imm10:imm11:0. */
+ /* with : I1 = NOT (J1 EOR S) I2 = NOT (J2 EOR S). */
+
+ int s = ((branch_offset & 0x1000000) >> 24);
+ int j1 = s ^ !((branch_offset & 0x800000) >> 23);
+ int j2 = s ^ !((branch_offset & 0x400000) >> 22);
+
+ if (branch_offset < -(1 << 24) || branch_offset >= (1 << 24))
+ BFD_ASSERT (0 && "Error: branch out of range. Cannot create branch.");
+
+ bfd_vma patched_inst = 0xf0009000
+ | s << 26 /* S. */
+ | (((unsigned long) (branch_offset) >> 12) & 0x3ff) << 16 /* imm10. */
+ | j1 << 13 /* J1. */
+ | j2 << 11 /* J2. */
+ | (((unsigned long) (branch_offset) >> 1) & 0x7ff); /* imm11. */
+
+ return patched_inst;
+}
+
+static inline bfd_vma
+create_instruction_ldmia (int base_reg, int wback, int reg_mask)
+{
+ /* A8.8.57 LDM/LDMIA/LDMFD (A8-396)
+ LDMIA Rn!, {Ra, Rb, Rc, ...} (Encoding T2). */
+ bfd_vma patched_inst = 0xe8900000
+ | (/*W=*/wback << 21)
+ | (base_reg << 16)
+ | (reg_mask & 0x0000ffff);
+
+ return patched_inst;
+}
+
+static inline bfd_vma
+create_instruction_ldmdb (int base_reg, int wback, int reg_mask)
+{
+ /* A8.8.60 LDMDB/LDMEA (A8-402)
+ LDMDB Rn!, {Ra, Rb, Rc, ...} (Encoding T1). */
+ bfd_vma patched_inst = 0xe9100000
+ | (/*W=*/wback << 21)
+ | (base_reg << 16)
+ | (reg_mask & 0x0000ffff);
+
+ return patched_inst;
+}
+
+static inline bfd_vma
+create_instruction_mov (int target_reg, int source_reg)
+{
+ /* A8.8.103 MOV (register) (A8-486)
+ MOV Rd, Rm (Encoding T1). */
+ bfd_vma patched_inst = 0x4600
+ | (target_reg & 0x7)
+ | ((target_reg & 0x8) >> 3) << 7
+ | (source_reg << 3);
+
+ return patched_inst;
+}
+
+static inline bfd_vma
+create_instruction_sub (int target_reg, int source_reg, int value)
+{
+ /* A8.8.221 SUB (immediate) (A8-708)
+ SUB Rd, Rn, #value (Encoding T3). */
+ bfd_vma patched_inst = 0xf1a00000
+ | (target_reg << 8)
+ | (source_reg << 16)
+ | (/*S=*/0 << 20)
+ | ((value & 0x800) >> 11) << 26
+ | ((value & 0x700) >> 8) << 12
+ | (value & 0x0ff);
+
+ return patched_inst;
+}
+
+static inline bfd_vma
+create_instruction_vldmia (int base_reg, int is_dp, int wback, int num_words,
+ int first_reg)
+{
+ /* A8.8.332 VLDM (A8-922)
+ VLMD{MODE} Rn{!}, {list} (Encoding T1 or T2). */
+ bfd_vma patched_inst = (is_dp ? 0xec900b00 : 0xec900a00)
+ | (/*W=*/wback << 21)
+ | (base_reg << 16)
+ | (num_words & 0x000000ff)
+ | (((unsigned)first_reg >> 1) & 0x0000000f) << 12
+ | (first_reg & 0x00000001) << 22;
+
+ return patched_inst;
+}
+
+static inline bfd_vma
+create_instruction_vldmdb (int base_reg, int is_dp, int num_words,
+ int first_reg)
+{
+ /* A8.8.332 VLDM (A8-922)
+ VLMD{MODE} Rn!, {} (Encoding T1 or T2). */
+ bfd_vma patched_inst = (is_dp ? 0xed300b00 : 0xed300a00)
+ | (base_reg << 16)
+ | (num_words & 0x000000ff)
+ | (((unsigned)first_reg >>1 ) & 0x0000000f) << 12
+ | (first_reg & 0x00000001) << 22;
+
+ return patched_inst;
+}
+
+static inline bfd_vma
+create_instruction_udf_w (int value)
+{
+ /* A8.8.247 UDF (A8-758)
+ Undefined (Encoding T2). */
+ bfd_vma patched_inst = 0xf7f0a000
+ | (value & 0x00000fff)
+ | (value & 0x000f0000) << 16;
+
+ return patched_inst;
+}
+
+static inline bfd_vma
+create_instruction_udf (int value)
+{
+ /* A8.8.247 UDF (A8-758)
+ Undefined (Encoding T1). */
+ bfd_vma patched_inst = 0xde00
+ | (value & 0xff);
+
+ return patched_inst;
+}
+
+/* Functions writing an instruction in memory, returning the next
+ memory position to write to. */
+
+static inline bfd_byte *
+push_thumb2_insn32 (struct elf32_arm_link_hash_table * htab,
+ bfd * output_bfd, bfd_byte *pt, insn32 insn)
+{
+ put_thumb2_insn (htab, output_bfd, insn, pt);
+ return pt + 4;
+}
+
+static inline bfd_byte *
+push_thumb2_insn16 (struct elf32_arm_link_hash_table * htab,
+ bfd * output_bfd, bfd_byte *pt, insn32 insn)
+{
+ put_thumb_insn (htab, output_bfd, insn, pt);
+ return pt + 2;
+}
+
+/* Function filling up a region in memory with T1 and T2 UDFs taking
+ care of alignment. */
+
+static bfd_byte *
+stm32l4xx_fill_stub_udf (struct elf32_arm_link_hash_table * htab,
+ bfd * output_bfd,
+ const bfd_byte * const base_stub_contents,
+ bfd_byte * const from_stub_contents,
+ const bfd_byte * const end_stub_contents)
+{
+ bfd_byte *current_stub_contents = from_stub_contents;
+
+ /* Fill the remaining of the stub with deterministic contents : UDF
+ instructions.
+ Check if realignment is needed on modulo 4 frontier using T1, to
+ further use T2. */
+ if ((current_stub_contents < end_stub_contents)
+ && !((current_stub_contents - base_stub_contents) % 2)
+ && ((current_stub_contents - base_stub_contents) % 4))
+ current_stub_contents =
+ push_thumb2_insn16 (htab, output_bfd, current_stub_contents,
+ create_instruction_udf (0));
+
+ for (; current_stub_contents < end_stub_contents;)
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_udf_w (0));
+
+ return current_stub_contents;
+}
+
+/* Functions writing the stream of instructions equivalent to the
+ derived sequence for ldmia, ldmdb, vldm respectively. */
+
+static void
+stm32l4xx_create_replacing_stub_ldmia (struct elf32_arm_link_hash_table * htab,
+ bfd * output_bfd,
+ const insn32 initial_insn,
+ const bfd_byte *const initial_insn_addr,
+ bfd_byte *const base_stub_contents)
+{
+ int wback = (initial_insn & 0x00200000) >> 21;
+ int ri, rn = (initial_insn & 0x000F0000) >> 16;
+ int insn_all_registers = initial_insn & 0x0000ffff;
+ int insn_low_registers, insn_high_registers;
+ int usable_register_mask;
+ int nb_registers = popcount (insn_all_registers);
+ int restore_pc = (insn_all_registers & (1 << 15)) ? 1 : 0;
+ int restore_rn = (insn_all_registers & (1 << rn)) ? 1 : 0;
+ bfd_byte *current_stub_contents = base_stub_contents;
+
+ BFD_ASSERT (is_thumb2_ldmia (initial_insn));
+
+ /* In BFD_ARM_STM32L4XX_FIX_ALL mode we may have to deal with
+ smaller than 8 registers load sequences that do not cause the
+ hardware issue. */
+ if (nb_registers <= 8)
+ {
+ /* UNTOUCHED : LDMIA Rn{!}, {R-all-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ initial_insn);
+
+ /* B initial_insn_addr+4. */
+ if (!restore_pc)
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_branch_absolute
+ (initial_insn_addr - current_stub_contents));
+
+
+ /* Fill the remaining of the stub with deterministic contents. */
+ current_stub_contents =
+ stm32l4xx_fill_stub_udf (htab, output_bfd,
+ base_stub_contents, current_stub_contents,
+ base_stub_contents +
+ STM32L4XX_ERRATUM_LDM_VENEER_SIZE);
+
+ return;
+ }
+
+ /* - reg_list[13] == 0. */
+ BFD_ASSERT ((insn_all_registers & (1 << 13))==0);
+
+ /* - reg_list[14] & reg_list[15] != 1. */
+ BFD_ASSERT ((insn_all_registers & 0xC000) != 0xC000);
+
+ /* - if (wback==1) reg_list[rn] == 0. */
+ BFD_ASSERT (!wback || !restore_rn);
+
+ /* - nb_registers > 8. */
+ BFD_ASSERT (popcount (insn_all_registers) > 8);
+
+ /* At this point, LDMxx initial insn loads between 9 and 14 registers. */
+
+ /* In the following algorithm, we split this wide LDM using 2 LDM insns:
+ - One with the 7 lowest registers (register mask 0x007F)
+ This LDM will finally contain between 2 and 7 registers
+ - One with the 7 highest registers (register mask 0xDF80)
+ This ldm will finally contain between 2 and 7 registers. */
+ insn_low_registers = insn_all_registers & 0x007F;
+ insn_high_registers = insn_all_registers & 0xDF80;
+
+ /* A spare register may be needed during this veneer to temporarily
+ handle the base register. This register will be restored with the
+ last LDM operation.
+ The usable register may be any general purpose register (that
+ excludes PC, SP, LR : register mask is 0x1FFF). */
+ usable_register_mask = 0x1FFF;
+
+ /* Generate the stub function. */
+ if (wback)
+ {
+ /* LDMIA Rn!, {R-low-register-list} : (Encoding T2). */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (rn, /*wback=*/1, insn_low_registers));
+
+ /* LDMIA Rn!, {R-high-register-list} : (Encoding T2). */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (rn, /*wback=*/1, insn_high_registers));
+ if (!restore_pc)
+ {
+ /* B initial_insn_addr+4. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_branch_absolute
+ (initial_insn_addr - current_stub_contents));
+ }
+ }
+ else /* if (!wback). */
+ {
+ ri = rn;
+
+ /* If Rn is not part of the high-register-list, move it there. */
+ if (!(insn_high_registers & (1 << rn)))
+ {
+ /* Choose a Ri in the high-register-list that will be restored. */
+ ri = ctz (insn_high_registers & usable_register_mask & ~(1 << rn));
+
+ /* MOV Ri, Rn. */
+ current_stub_contents =
+ push_thumb2_insn16 (htab, output_bfd, current_stub_contents,
+ create_instruction_mov (ri, rn));
+ }
+
+ /* LDMIA Ri!, {R-low-register-list} : (Encoding T2). */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (ri, /*wback=*/1, insn_low_registers));
+
+ /* LDMIA Ri, {R-high-register-list} : (Encoding T2). */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (ri, /*wback=*/0, insn_high_registers));
+
+ if (!restore_pc)
+ {
+ /* B initial_insn_addr+4. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_branch_absolute
+ (initial_insn_addr - current_stub_contents));
+ }
+ }
+
+ /* Fill the remaining of the stub with deterministic contents. */
+ current_stub_contents =
+ stm32l4xx_fill_stub_udf (htab, output_bfd,
+ base_stub_contents, current_stub_contents,
+ base_stub_contents +
+ STM32L4XX_ERRATUM_LDM_VENEER_SIZE);
+}
+
+static void
+stm32l4xx_create_replacing_stub_ldmdb (struct elf32_arm_link_hash_table * htab,
+ bfd * output_bfd,
+ const insn32 initial_insn,
+ const bfd_byte *const initial_insn_addr,
+ bfd_byte *const base_stub_contents)
+{
+ int wback = (initial_insn & 0x00200000) >> 21;
+ int ri, rn = (initial_insn & 0x000f0000) >> 16;
+ int insn_all_registers = initial_insn & 0x0000ffff;
+ int insn_low_registers, insn_high_registers;
+ int usable_register_mask;
+ int restore_pc = (insn_all_registers & (1 << 15)) ? 1 : 0;
+ int restore_rn = (insn_all_registers & (1 << rn)) ? 1 : 0;
+ int nb_registers = popcount (insn_all_registers);
+ bfd_byte *current_stub_contents = base_stub_contents;
+
+ BFD_ASSERT (is_thumb2_ldmdb (initial_insn));
+
+ /* In BFD_ARM_STM32L4XX_FIX_ALL mode we may have to deal with
+ smaller than 8 registers load sequences that do not cause the
+ hardware issue. */
+ if (nb_registers <= 8)
+ {
+ /* UNTOUCHED : LDMIA Rn{!}, {R-all-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ initial_insn);
+
+ /* B initial_insn_addr+4. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_branch_absolute
+ (initial_insn_addr - current_stub_contents));
+
+ /* Fill the remaining of the stub with deterministic contents. */
+ current_stub_contents =
+ stm32l4xx_fill_stub_udf (htab, output_bfd,
+ base_stub_contents, current_stub_contents,
+ base_stub_contents +
+ STM32L4XX_ERRATUM_LDM_VENEER_SIZE);
+
+ return;
+ }
+
+ /* - reg_list[13] == 0. */
+ BFD_ASSERT ((insn_all_registers & (1 << 13)) == 0);
+
+ /* - reg_list[14] & reg_list[15] != 1. */
+ BFD_ASSERT ((insn_all_registers & 0xC000) != 0xC000);
+
+ /* - if (wback==1) reg_list[rn] == 0. */
+ BFD_ASSERT (!wback || !restore_rn);
+
+ /* - nb_registers > 8. */
+ BFD_ASSERT (popcount (insn_all_registers) > 8);
+
+ /* At this point, LDMxx initial insn loads between 9 and 14 registers. */
+
+ /* In the following algorithm, we split this wide LDM using 2 LDM insn:
+ - One with the 7 lowest registers (register mask 0x007F)
+ This LDM will finally contain between 2 and 7 registers
+ - One with the 7 highest registers (register mask 0xDF80)
+ This ldm will finally contain between 2 and 7 registers. */
+ insn_low_registers = insn_all_registers & 0x007F;
+ insn_high_registers = insn_all_registers & 0xDF80;
+
+ /* A spare register may be needed during this veneer to temporarily
+ handle the base register. This register will be restored with
+ the last LDM operation.
+ The usable register may be any general purpose register (that excludes
+ PC, SP, LR : register mask is 0x1FFF). */
+ usable_register_mask = 0x1FFF;
+
+ /* Generate the stub function. */
+ if (!wback && !restore_pc && !restore_rn)
+ {
+ /* Choose a Ri in the low-register-list that will be restored. */
+ ri = ctz (insn_low_registers & usable_register_mask & ~(1 << rn));
+
+ /* MOV Ri, Rn. */
+ current_stub_contents =
+ push_thumb2_insn16 (htab, output_bfd, current_stub_contents,
+ create_instruction_mov (ri, rn));
+
+ /* LDMDB Ri!, {R-high-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmdb
+ (ri, /*wback=*/1, insn_high_registers));
+
+ /* LDMDB Ri, {R-low-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmdb
+ (ri, /*wback=*/0, insn_low_registers));
+
+ /* B initial_insn_addr+4. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_branch_absolute
+ (initial_insn_addr - current_stub_contents));
+ }
+ else if (wback && !restore_pc && !restore_rn)
+ {
+ /* LDMDB Rn!, {R-high-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmdb
+ (rn, /*wback=*/1, insn_high_registers));
+
+ /* LDMDB Rn!, {R-low-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmdb
+ (rn, /*wback=*/1, insn_low_registers));
+
+ /* B initial_insn_addr+4. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_branch_absolute
+ (initial_insn_addr - current_stub_contents));
+ }
+ else if (!wback && restore_pc && !restore_rn)
+ {
+ /* Choose a Ri in the high-register-list that will be restored. */
+ ri = ctz (insn_high_registers & usable_register_mask & ~(1 << rn));
+
+ /* SUB Ri, Rn, #(4*nb_registers). */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_sub (ri, rn, (4 * nb_registers)));
+
+ /* LDMIA Ri!, {R-low-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (ri, /*wback=*/1, insn_low_registers));
+
+ /* LDMIA Ri, {R-high-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (ri, /*wback=*/0, insn_high_registers));
+ }
+ else if (wback && restore_pc && !restore_rn)
+ {
+ /* Choose a Ri in the high-register-list that will be restored. */
+ ri = ctz (insn_high_registers & usable_register_mask & ~(1 << rn));
+
+ /* SUB Rn, Rn, #(4*nb_registers) */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_sub (rn, rn, (4 * nb_registers)));
+
+ /* MOV Ri, Rn. */
+ current_stub_contents =
+ push_thumb2_insn16 (htab, output_bfd, current_stub_contents,
+ create_instruction_mov (ri, rn));
+
+ /* LDMIA Ri!, {R-low-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (ri, /*wback=*/1, insn_low_registers));
+
+ /* LDMIA Ri, {R-high-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (ri, /*wback=*/0, insn_high_registers));
+ }
+ else if (!wback && !restore_pc && restore_rn)
+ {
+ ri = rn;
+ if (!(insn_low_registers & (1 << rn)))
+ {
+ /* Choose a Ri in the low-register-list that will be restored. */
+ ri = ctz (insn_low_registers & usable_register_mask & ~(1 << rn));
+
+ /* MOV Ri, Rn. */
+ current_stub_contents =
+ push_thumb2_insn16 (htab, output_bfd, current_stub_contents,
+ create_instruction_mov (ri, rn));
+ }
+
+ /* LDMDB Ri!, {R-high-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmdb
+ (ri, /*wback=*/1, insn_high_registers));
+
+ /* LDMDB Ri, {R-low-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmdb
+ (ri, /*wback=*/0, insn_low_registers));
+
+ /* B initial_insn_addr+4. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_branch_absolute
+ (initial_insn_addr - current_stub_contents));
+ }
+ else if (!wback && restore_pc && restore_rn)
+ {
+ ri = rn;
+ if (!(insn_high_registers & (1 << rn)))
+ {
+ /* Choose a Ri in the high-register-list that will be restored. */
+ ri = ctz (insn_high_registers & usable_register_mask & ~(1 << rn));
+ }
+
+ /* SUB Ri, Rn, #(4*nb_registers). */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_sub (ri, rn, (4 * nb_registers)));
+
+ /* LDMIA Ri!, {R-low-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (ri, /*wback=*/1, insn_low_registers));
+
+ /* LDMIA Ri, {R-high-register-list}. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_ldmia
+ (ri, /*wback=*/0, insn_high_registers));
+ }
+ else if (wback && restore_rn)
+ {
+ /* The assembler should not have accepted to encode this. */
+ BFD_ASSERT (0 && "Cannot patch an instruction that has an "
+ "undefined behavior.\n");
+ }
+
+ /* Fill the remaining of the stub with deterministic contents. */
+ current_stub_contents =
+ stm32l4xx_fill_stub_udf (htab, output_bfd,
+ base_stub_contents, current_stub_contents,
+ base_stub_contents +
+ STM32L4XX_ERRATUM_LDM_VENEER_SIZE);
+
+}
+
+static void
+stm32l4xx_create_replacing_stub_vldm (struct elf32_arm_link_hash_table * htab,
+ bfd * output_bfd,
+ const insn32 initial_insn,
+ const bfd_byte *const initial_insn_addr,
+ bfd_byte *const base_stub_contents)
+{
+ int num_words = ((unsigned int) initial_insn << 24) >> 24;
+ bfd_byte *current_stub_contents = base_stub_contents;
+
+ BFD_ASSERT (is_thumb2_vldm (initial_insn));
+
+ /* In BFD_ARM_STM32L4XX_FIX_ALL mode we may have to deal with
+ smaller than 8 words load sequences that do not cause the
+ hardware issue. */
+ if (num_words <= 8)
+ {
+ /* Untouched instruction. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ initial_insn);
+
+ /* B initial_insn_addr+4. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_branch_absolute
+ (initial_insn_addr - current_stub_contents));
+ }
+ else
+ {
+ bfd_boolean is_dp = /* DP encoding. */
+ (initial_insn & 0xfe100f00) == 0xec100b00;
+ bfd_boolean is_ia_nobang = /* (IA without !). */
+ (((initial_insn << 7) >> 28) & 0xd) == 0x4;
+ bfd_boolean is_ia_bang = /* (IA with !) - includes VPOP. */
+ (((initial_insn << 7) >> 28) & 0xd) == 0x5;
+ bfd_boolean is_db_bang = /* (DB with !). */
+ (((initial_insn << 7) >> 28) & 0xd) == 0x9;
+ int base_reg = ((unsigned int) initial_insn << 12) >> 28;
+ /* d = UInt (Vd:D);. */
+ int first_reg = ((((unsigned int) initial_insn << 16) >> 28) << 1)
+ | (((unsigned int)initial_insn << 9) >> 31);
+
+ /* Compute the number of 8-words chunks needed to split. */
+ int chunks = (num_words % 8) ? (num_words / 8 + 1) : (num_words / 8);
+ int chunk;
+
+ /* The test coverage has been done assuming the following
+ hypothesis that exactly one of the previous is_ predicates is
+ true. */
+ BFD_ASSERT ( (is_ia_nobang ^ is_ia_bang ^ is_db_bang)
+ && !(is_ia_nobang & is_ia_bang & is_db_bang));
+
+ /* We treat the cutting of the words in one pass for all
+ cases, then we emit the adjustments:
+
+ vldm rx, {...}
+ -> vldm rx!, {8_words_or_less} for each needed 8_word
+ -> sub rx, rx, #size (list)
+
+ vldm rx!, {...}
+ -> vldm rx!, {8_words_or_less} for each needed 8_word
+ This also handles vpop instruction (when rx is sp)
+
+ vldmd rx!, {...}
+ -> vldmb rx!, {8_words_or_less} for each needed 8_word. */
+ for (chunk = 0; chunk < chunks; ++chunk)
+ {
+ bfd_vma new_insn = 0;
+
+ if (is_ia_nobang || is_ia_bang)
+ {
+ new_insn = create_instruction_vldmia
+ (base_reg,
+ is_dp,
+ /*wback= . */1,
+ chunks - (chunk + 1) ?
+ 8 : num_words - chunk * 8,
+ first_reg + chunk * 8);
+ }
+ else if (is_db_bang)
+ {
+ new_insn = create_instruction_vldmdb
+ (base_reg,
+ is_dp,
+ chunks - (chunk + 1) ?
+ 8 : num_words - chunk * 8,
+ first_reg + chunk * 8);
+ }
+
+ if (new_insn)
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ new_insn);
+ }
+
+ /* Only this case requires the base register compensation
+ subtract. */
+ if (is_ia_nobang)
+ {
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_sub
+ (base_reg, base_reg, 4*num_words));
+ }
+
+ /* B initial_insn_addr+4. */
+ current_stub_contents =
+ push_thumb2_insn32 (htab, output_bfd, current_stub_contents,
+ create_instruction_branch_absolute
+ (initial_insn_addr - current_stub_contents));
+ }
+
+ /* Fill the remaining of the stub with deterministic contents. */
+ current_stub_contents =
+ stm32l4xx_fill_stub_udf (htab, output_bfd,
+ base_stub_contents, current_stub_contents,
+ base_stub_contents +
+ STM32L4XX_ERRATUM_VLDM_VENEER_SIZE);
+}
+
+static void
+stm32l4xx_create_replacing_stub (struct elf32_arm_link_hash_table * htab,
+ bfd * output_bfd,
+ const insn32 wrong_insn,
+ const bfd_byte *const wrong_insn_addr,
+ bfd_byte *const stub_contents)
+{
+ if (is_thumb2_ldmia (wrong_insn))
+ stm32l4xx_create_replacing_stub_ldmia (htab, output_bfd,
+ wrong_insn, wrong_insn_addr,
+ stub_contents);
+ else if (is_thumb2_ldmdb (wrong_insn))
+ stm32l4xx_create_replacing_stub_ldmdb (htab, output_bfd,
+ wrong_insn, wrong_insn_addr,
+ stub_contents);
+ else if (is_thumb2_vldm (wrong_insn))
+ stm32l4xx_create_replacing_stub_vldm (htab, output_bfd,
+ wrong_insn, wrong_insn_addr,
+ stub_contents);
+}
+
+/* End of stm32l4xx work-around. */
+
+
+static void
+elf32_arm_add_relocation (bfd *output_bfd, struct bfd_link_info *info,
+ asection *output_sec, Elf_Internal_Rela *rel)
+{
+ BFD_ASSERT (output_sec && rel);
+ struct bfd_elf_section_reloc_data *output_reldata;
+ struct elf32_arm_link_hash_table *htab;
+ struct bfd_elf_section_data *oesd = elf_section_data (output_sec);
+ Elf_Internal_Shdr *rel_hdr;
+
+
+ if (oesd->rel.hdr)
+ {
+ rel_hdr = oesd->rel.hdr;
+ output_reldata = &(oesd->rel);
+ }
+ else if (oesd->rela.hdr)
+ {
+ rel_hdr = oesd->rela.hdr;
+ output_reldata = &(oesd->rela);
+ }
+ else
+ {
+ abort ();
+ }
+
+ bfd_byte *erel = rel_hdr->contents;
+ erel += output_reldata->count * rel_hdr->sh_entsize;
+ htab = elf32_arm_hash_table (info);
+ SWAP_RELOC_OUT (htab) (output_bfd, rel, erel);
+ output_reldata->count++;
+}
+