+/* This is derived from bfd_elf_generic_reloc. NewABI allows us to have
+ several relocations against the same address. The addend is derived
+ from the addends of preceding relocations. If we don't need to
+ do something special, we simply keep track of the addend. */
+
+#define GET_RELOC_ADDEND(obfd, sym, entry, sec) \
+{ \
+ /* If we're relocating, and this is an external symbol, we don't \
+ want to change anything. */ \
+ if ((obfd) != NULL \
+ && ((sym)->flags & BSF_SECTION_SYM) == 0 \
+ && (! (entry)->howto->partial_inplace \
+ || (entry)->addend == 0)) \
+ { \
+ (entry)->address += (sec)->output_offset; \
+ return bfd_reloc_ok; \
+ } \
+ \
+ /* The addend of combined relocs is remembered and left for \
+ subsequent relocs. */ \
+ if (prev_reloc_address != (entry)->address \
+ || prev_reloc_section != (sec)) \
+ { \
+ prev_reloc_section = (sec); \
+ prev_reloc_address = (entry)->address; \
+ prev_reloc_addend = (entry)->addend; \
+ } \
+ else \
+ (entry)->addend = prev_reloc_addend; \
+}
+
+#define SET_RELOC_ADDEND(entry) \
+{ \
+ prev_reloc_addend = (entry)->addend; \
+}
+
+static bfd_reloc_status_type
+mips_elf_generic_reloc (bfd *abfd ATTRIBUTE_UNUSED, arelent *reloc_entry,
+ asymbol *symbol, void *data ATTRIBUTE_UNUSED,
+ asection *input_section, bfd *output_bfd,
+ char **error_message ATTRIBUTE_UNUSED)
+{
+ GET_RELOC_ADDEND (output_bfd, symbol, reloc_entry, input_section)
+
+ return bfd_reloc_continue;
+}
+\f
+/* Do a R_MIPS_HI16 relocation. This has to be done in combination
+ with a R_MIPS_LO16 reloc, because there is a carry from the LO16 to
+ the HI16. Here we just save the information we need; we do the
+ actual relocation when we see the LO16.
+
+ MIPS ELF requires that the LO16 immediately follow the HI16. As a
+ GNU extension, for non-pc-relative relocations, we permit an
+ arbitrary number of HI16 relocs to be associated with a single LO16
+ reloc. This extension permits gcc to output the HI and LO relocs
+ itself.
+
+ This cannot be done for PC-relative relocations because both the HI16
+ and LO16 parts of the relocations must be done relative to the LO16
+ part, and there can be carry to or borrow from the HI16 part. */
+
+struct mips_hi16
+{
+ struct mips_hi16 *next;
+ bfd_byte *addr;
+ bfd_vma addend;
+};
+
+/* FIXME: This should not be a static variable. */
+
+static struct mips_hi16 *mips_hi16_list;
+
+static bfd_reloc_status_type
+mips_elf_hi16_reloc (bfd *abfd ATTRIBUTE_UNUSED, arelent *reloc_entry,
+ asymbol *symbol, void *data, asection *input_section,
+ bfd *output_bfd, char **error_message ATTRIBUTE_UNUSED)
+{
+ bfd_reloc_status_type ret;
+ bfd_vma relocation;
+ struct mips_hi16 *n;
+
+ GET_RELOC_ADDEND (output_bfd, symbol, reloc_entry, input_section)
+
+ ret = bfd_reloc_ok;
+
+ if (bfd_is_und_section (symbol->section) && output_bfd == NULL)
+ ret = bfd_reloc_undefined;
+
+ if (bfd_is_com_section (symbol->section))
+ relocation = 0;
+ else
+ relocation = symbol->value;
+
+ relocation += symbol->section->output_section->vma;
+ relocation += symbol->section->output_offset;
+ relocation += reloc_entry->addend;
+
+ if (reloc_entry->address > input_section->_cooked_size)
+ return bfd_reloc_outofrange;
+
+ /* Save the information, and let LO16 do the actual relocation. */
+ n = bfd_malloc (sizeof *n);
+ if (n == NULL)
+ return bfd_reloc_outofrange;
+ n->addr = (bfd_byte *) data + reloc_entry->address;
+ n->addend = relocation;
+ n->next = mips_hi16_list;
+ mips_hi16_list = n;
+
+ if (output_bfd != NULL)
+ reloc_entry->address += input_section->output_offset;
+
+ return ret;
+}
+
+/* Do a R_MIPS_LO16 relocation. This is a straightforward 16 bit
+ inplace relocation; this function exists in order to do the
+ R_MIPS_HI16 relocation described above. */
+
+static bfd_reloc_status_type
+mips_elf_lo16_reloc (bfd *abfd, arelent *reloc_entry, asymbol *symbol,
+ void *data, asection *input_section, bfd *output_bfd,
+ char **error_message)
+{
+ if (mips_hi16_list != NULL)
+ {
+ struct mips_hi16 *l;
+
+ l = mips_hi16_list;
+ while (l != NULL)
+ {
+ unsigned long insn;
+ unsigned long val;
+ unsigned long vallo;
+ struct mips_hi16 *next;
+
+ /* Do the HI16 relocation. Note that we actually don't need
+ to know anything about the LO16 itself, except where to
+ find the low 16 bits of the addend needed by the LO16. */
+ insn = bfd_get_32 (abfd, l->addr);
+ vallo = bfd_get_32 (abfd, (bfd_byte *) data + reloc_entry->address);
+
+ /* The low order 16 bits are always treated as a signed
+ value. */
+ vallo = ((vallo & 0xffff) ^ 0x8000) - 0x8000;
+ val = ((insn & 0xffff) << 16) + vallo;
+ val += l->addend;
+
+ /* If PC-relative, we need to subtract out the address of the LO
+ half of the HI/LO. (The actual relocation is relative
+ to that instruction.) */
+ if (reloc_entry->howto->pc_relative)
+ val -= reloc_entry->address;
+
+ /* At this point, "val" has the value of the combined HI/LO
+ pair. If the low order 16 bits (which will be used for
+ the LO16 insn) are negative, then we will need an
+ adjustment for the high order 16 bits. */
+ val += 0x8000;
+ val = (val >> 16) & 0xffff;
+
+ insn &= ~ (bfd_vma) 0xffff;
+ insn |= val;
+ bfd_put_32 (abfd, insn, l->addr);
+
+ next = l->next;
+ free (l);
+ l = next;
+ }
+
+ mips_hi16_list = NULL;
+ }
+
+ /* Now do the LO16 reloc in the usual way. */
+ return mips_elf_generic_reloc (abfd, reloc_entry, symbol, data,
+ input_section, output_bfd, error_message);
+}
+
+/* Do a R_MIPS_GOT16 reloc. This is a reloc against the global offset
+ table used for PIC code. If the symbol is an external symbol, the
+ instruction is modified to contain the offset of the appropriate
+ entry in the global offset table. If the symbol is a section
+ symbol, the next reloc is a R_MIPS_LO16 reloc. The two 16 bit
+ addends are combined to form the real addend against the section
+ symbol; the GOT16 is modified to contain the offset of an entry in
+ the global offset table, and the LO16 is modified to offset it
+ appropriately. Thus an offset larger than 16 bits requires a
+ modified value in the global offset table.
+
+ This implementation suffices for the assembler, but the linker does
+ not yet know how to create global offset tables. */
+
+static bfd_reloc_status_type
+mips_elf_got16_reloc (bfd *abfd, arelent *reloc_entry, asymbol *symbol,
+ void *data, asection *input_section, bfd *output_bfd,
+ char **error_message)
+{
+ /* If we're relocating, and this is a local symbol, we can handle it
+ just like an R_MIPS_HI16. */
+ if (output_bfd != NULL
+ && ((symbol->flags & BSF_SECTION_SYM) != 0
+ || (symbol->flags & BSF_LOCAL) == 0))
+ return mips_elf_hi16_reloc (abfd, reloc_entry, symbol, data,
+ input_section, output_bfd, error_message);
+
+ /* Otherwise we try to handle it as R_MIPS_GOT_DISP. */
+ return mips_elf_generic_reloc (abfd, reloc_entry, symbol, data,
+ input_section, output_bfd, error_message);
+}
+