/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2013  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/offsets.h>
#include <grub/symbol.h>
#include <grub/machine/kernel.h>

/*
 * GRUB is called from U-Boot as a Linux Kernel type image, which
 * means among other things that it always enters in ARM state.
 *
 *
 * Overview of GRUB image layout:
 *
 * _start:
 *              Entry point (1 ARM branch instruction, to "codestart")
 * grub_total_module_size:
 *              Data field: Size of included module blob
 *              (when generated by grub-mkimage)
 * codestart:
 *              Remainder of statically-linked executable code and data.
 * __bss_start:
 *              Start of included module blob.
 *              Also where global/static variables are located.
 * _end:
 *              End of bss region (but not necessarily module blob).
 * <stack>:     
 * <modules>:
 *              Loadable modules, post relocation.
 * <heap>:
 */
	
	.text
	.arm
FUNCTION(_start)
	b	codestart
	
	@ Size of final image integrated module blob - set by grub-mkimage
	.org _start + GRUB_KERNEL_MACHINE_TOTAL_MODULE_SIZE
VARIABLE(grub_total_module_size)
	.long 	0

VARIABLE(grub_modbase)
	.long 0
bss_start_ptr:
	.long   EXT_C(__bss_start)
end_ptr:
	.long   EXT_C(_end)

	@ Memory map at start:
	@ * text+data
	@ * list relocations
	@ * modules
	@ Before we enter C, we need to apply the relocations
	@ and get following map:
	@ * text+data
	@ * BSS (cleared)
	@ * stack
	@ * modules
	@
	@ To make things easier we ensure
	@ that BSS+stack is larger than list of relocations
	@ by increasing stack if necessarry.
	@ This allows us to always unconditionally copy backwards
	@ Currently list of relocations is ~5K and stack is set
	@ to be at least 256K

FUNCTION(codestart)
	@ Store context: Machine ID, atags/dtb, ...
	@ U-Boot API signature is stored on the U-Boot heap
	@ Stack pointer used as start address for signature probing
	mov	r12, sp
	adr	sp, entry_state
	push	{r1-r12,lr}	@ store U-Boot context (sp in r12)

	adr     r1, _start
	ldr	r0, bss_start_ptr		@ src
	add     r0, r0, r1

	add	r0, r0, #(GRUB_KERNEL_MACHINE_MOD_ALIGN - 1)
	mvn	r2, #(GRUB_KERNEL_MACHINE_MOD_ALIGN - 1)
	and	r0, r0, r2
1:	
        ldr     r3, [r0], #4  @load next offset
	@ both -2 and -1 are treated the same as we have only one type of relocs
	@ -2 means "end of this type of relocs" and -1 means "end of all relocs"
        add     r2, r3, #2
        cmp     r2, #1
        bls     reloc_done
	@ Adjust next offset
        ldr     r2, [r3, r1]
        add     r2, r2, r1
        str     r2, [r3, r1]
	b 1b

reloc_done:

	@ Modules have been stored as a blob
	@ they need to be manually relocated to _end
	add	r0, r0, #(GRUB_KERNEL_MACHINE_MOD_ALIGN - 1)
	mvn	r1, #(GRUB_KERNEL_MACHINE_MOD_ALIGN - 1)
	and	r0, r0, r1	@ src = aligned end of relocations

	ldr	r1, end_ptr		@ dst = End of BSS
	ldr	r2, grub_total_module_size	@ blob size

	add     r1, r1, #GRUB_KERNEL_MACHINE_STACK_SIZE
	and	r1, r1, #~0x7	@ Ensure 8-byte alignment
	
	sub	sp, r1, #8
	add     r1, r1, #1024

	str     r1, EXT_C(grub_modbase)

	add	r1, r1, r2
	add	r0, r0, r2
	sub     r1, r1, #4
	sub     r0, r0, #4

1:	ldr	r3, [r0], #-4 			@ r3 = *src--
	str	r3, [r1], #-4			@ *dst-- = r3 
	subs	r2, #4				@ remaining -= 4
	bne	1b				@ while remaining != 0

	@ Since we _are_ the C run-time, we need to manually zero the BSS
	@ region before continuing
	ldr	r0, bss_start_ptr	@ zero from here
	@ If unaligned, bytewise zero until base address aligned.
	mov	r2, #0
1:	tst	r0, #3
	beq	2f
	strb	r2, [r0], #1
	b	1b
2:	ldr	r1, end_ptr		@ to here
1:	str	r2, [r0], #4
	cmp	r0, r1
	bne	1b
	
	b	EXT_C(grub_main)

	/*
	 * uboot_syscall():
	 *   This function is effectively a veneer, so it cannot
	 *   modify the stack or corrupt any registers other than
	 *   r12 (ip). Furthermore it needs to restore r8 for
	 *   U-Boot (Global Data Pointer) and preserve it for Grub.
	 */
FUNCTION(grub_uboot_syscall)
	str     r8, transition_space
	str     lr, transition_space + 4
	str     r9, transition_space + 8

	ldr	r8, gd_backup
	ldr	r9, gd_backup + 4

	bl	do_syscall

	ldr     r8, transition_space
	ldr     lr, transition_space + 4
	ldr     r9, transition_space + 8

	bx	lr
do_syscall:

	ldr	ip, grub_uboot_syscall_ptr
	bx	ip
	
FUNCTION(grub_uboot_return)
	adr	sp, entry_state_end
	pop	{r4-r12, lr}
	mov	sp, r12
	bx	lr

	
	.align	3
@ U-boot context stack space
entry_state_end:
VARIABLE(grub_uboot_machine_type)
	.long	0	@ r1
VARIABLE(grub_uboot_boot_data)
	.long	0	@ r2
	.long	0	@ r3
	.long	0	@ r4
	.long	0	@ r5
	.long	0	@ r6
	.long	0	@ r7
gd_backup:	
	.long	0	@ r8 - U-Boot global data pointer up to 2013-09-21
	.long	0	@ r9 - U-Boot global data pointer 2013-09-21 onwards
	.long	0	@ r10
	.long	0	@ r11
VARIABLE(grub_uboot_search_hint)@ U-Boot stack pointer - 
	.long	0	@ also API signature address hint.
	.long	0	@ lr
entry_state:		@ backup for U-Boot context

@ GRUB context stack space
transition_space:	
	.long	0	@ r8
	.long	0	@ lr
	.long	0	@ r9

VARIABLE(grub_uboot_syscall_ptr)
	.long	0	@

	END
