/* reloc_ia64.S - position independent IA-64 ELF shared object relocator
   Copyright (C) 1999 Hewlett-Packard Co.
	Contributed by David Mosberger <davidm@hpl.hp.com>.

    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials
      provided with the distribution.
    * Neither the name of Hewlett-Packard Co. nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
    CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
    BE LIABLE FOR ANYDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
    SUCH DAMAGE.
*/

/*
 * This is written in assembly because the entire code needs to be position
 * independent.  Note that the compiler does not generate code that's position
 * independent by itself because it relies on the global offset table being
 * relocated.
 */
	.text
	.psr abi64
	.psr lsb
	.lsb

/*
 * This constant determines how many R_IA64_FPTR64LSB relocations we
 * can deal with.  If you get EFI_BUFFER_TOO_SMALL errors, you may
 * need to increase this number.
 */
#define MAX_FUNCTION_DESCRIPTORS	750

#define ST_VALUE_OFF	8		/* offset of st_value in elf sym */

#define EFI_SUCCESS		0
#define EFI_LOAD_ERROR		1
#define EFI_BUFFER_TOO_SMALL	5

#define DT_NULL		0		/* Marks end of dynamic section */
#define DT_RELA		7		/* Address of Rela relocs */
#define DT_RELASZ	8		/* Total size of Rela relocs */
#define DT_RELAENT	9		/* Size of one Rela reloc */
#define DT_SYMTAB	6		/* Address of symbol table */
#define DT_SYMENT	11		/* Size of one symbol table entry */

#define R_IA64_NONE		0
#define R_IA64_REL64MSB		0x6e
#define R_IA64_REL64LSB		0x6f
#define R_IA64_DIR64MSB		0x26
#define R_IA64_DIR64LSB		0x27
#define R_IA64_FPTR64MSB	0x46
#define R_IA64_FPTR64LSB	0x47

#define	ldbase	in0	/* load address (address of .text) */
#define	dyn	in1	/* address of _DYNAMIC */

#define d_tag	r16
#define d_val	r17
#define rela	r18
#define relasz	r19
#define relaent	r20
#define addr	r21
#define r_info	r22
#define r_offset r23
#define r_addend r24
#define r_type	r25
#define r_sym	r25	/* alias of r_type ! */
#define fptr	r26
#define fptr_limit r27
#define symtab	f8
#define syment	f9
#define ftmp	f10

#define	target	r16
#define val	r17

#define NLOC	0

#define Pnull		p6
#define Prela		p7
#define Prelasz		p8
#define Prelaent	p9
#define Psymtab		p10
#define Psyment		p11

#define Pnone		p6
#define Prel		p7
#define Pfptr		p8

#define Pmore		p6

#define Poom		p6	/* out-of-memory */

	.global _relocate
	.proc _relocate
_relocate:
	alloc r2=ar.pfs,2,0,0,0
	movl	fptr = @gprel(fptr_mem_base)
	;;
	add	fptr = fptr, gp
	movl	fptr_limit = @gprel(fptr_mem_limit)
	;;
	add	fptr_limit = fptr_limit, gp

search_dynamic:
	ld8	d_tag = [dyn],8
	;;
	ld8	d_val = [dyn],8
	cmp.eq	Pnull,p0 = DT_NULL,d_tag
(Pnull)	br.cond.sptk.few apply_relocs
	cmp.eq	Prela,p0 = DT_RELA,d_tag
	cmp.eq	Prelasz,p0 = DT_RELASZ,d_tag
	cmp.eq	Psymtab,p0 = DT_SYMTAB,d_tag
	cmp.eq	Psyment,p0 = DT_SYMENT,d_tag
	cmp.eq	Prelaent,p0 = DT_RELAENT,d_tag
	;;
(Prela)	add rela = d_val, ldbase
(Prelasz) mov relasz = d_val
(Prelaent) mov relaent = d_val
(Psymtab) add val = d_val, ldbase
	;;
(Psyment) setf.sig syment = d_val
	;;
(Psymtab) setf.sig symtab = val
	br.sptk.few search_dynamic

apply_loop:
	ld8	r_offset = [rela]
	add	addr = 8,rela
	sub	relasz = relasz,relaent
	;;

	ld8	r_info = [addr],8
	;;
	ld8	r_addend = [addr]
	add	target = ldbase, r_offset

	add	rela = rela,relaent
	extr.u	r_type = r_info, 0, 32
	;;
	cmp.eq	Pnone,p0 = R_IA64_NONE,r_type
	cmp.eq	Prel,p0 = R_IA64_REL64LSB,r_type
	cmp.eq	Pfptr,p0 = R_IA64_FPTR64LSB,r_type
(Prel)	br.cond.sptk.few apply_REL64
	;;
	cmp.eq	Prel,p0 = R_IA64_DIR64LSB,r_type // treat DIR64 just like REL64

(Pnone)	br.cond.sptk.few apply_relocs
(Prel)	br.cond.sptk.few apply_REL64
(Pfptr)	br.cond.sptk.few apply_FPTR64

	mov	r8 = EFI_LOAD_ERROR
	br.ret.sptk.few rp

apply_relocs:
	cmp.ltu	Pmore,p0=0,relasz
(Pmore)	br.cond.sptk.few apply_loop

	mov	r8 = EFI_SUCCESS
	br.ret.sptk.few rp

apply_REL64:
	ld8 val = [target]
	;;
	add val = val,ldbase
	;;
	st8 [target] = val
	br.cond.sptk.few apply_relocs

	// FPTR relocs are a bit more interesting: we need to lookup
	// the symbol's value in symtab, allocate 16 bytes of memory,
	// store the value in [target] in the first and the gp in the
	// second dword.
apply_FPTR64:
	st8	[target] = fptr
	extr.u	r_sym = r_info,32,32
	add	target = 8,fptr
	;;

	setf.sig ftmp = r_sym
	mov	r8=EFI_BUFFER_TOO_SMALL
	;;
	cmp.geu	Poom,p0 = fptr,fptr_limit

	xma.lu	ftmp = ftmp,syment,symtab
(Poom)	br.ret.sptk.few rp
	;;
	getf.sig addr = ftmp
	st8	[target] = gp
	;;
	add	addr = ST_VALUE_OFF, addr
	;;
	ld8	val = [addr]
	;;
	add	val = val,ldbase
	;;
	st8	[fptr] = val,16
	br.cond.sptk.few apply_relocs

	.endp _relocate

	.data
	.align 16
fptr_mem_base:
	.space  MAX_FUNCTION_DESCRIPTORS*16
fptr_mem_limit:

#if defined(__ELF__) && defined(__linux__)
	.section .note.GNU-stack,"",%progbits
#endif
