/*
 *  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.
 *
 * coreboot starts in ARM mode as well.
 *
 * 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	{r0-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)

	/* Coreboot already places modules at right place.  */
#ifndef GRUB_MACHINE_COREBOOT
	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
#endif

	@ 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)

	.align	3
@ U-boot/coreboot context stack space
VARIABLE(grub_arm_saved_registers)
	.long	0	@ r0
	.long	0	@ r1
	.long	0	@ r2
	.long	0	@ r3
	.long	0	@ r4
	.long	0	@ r5
	.long	0	@ r6
	.long	0	@ r7
	.long	0	@ r8
	.long	0	@ r9
	.long	0	@ r10
	.long	0	@ r11
	.long	0	@ sp
	.long	0	@ lr
entry_state:
