/*	$NetBSD: e32boot.cpp,v 1.1 2013/04/28 12:11:27 kiyohara Exp $	*/
/*
 * Copyright (c) 2012, 2013 KIYOHARA Takashi
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR BE LIABLE FOR ANY DIRECT,
 * 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.
 */

#include <e32base.h>
#include <e32def.h>
#include <e32std.h>

#include "cpu.h"
#include "e32boot.h"
#include "ekern.h"
#include "epoc32.h"
#include "netbsd.h"


class E32BootLDD : public DLogicalDevice {
public:
	E32BootLDD(void);
	virtual TInt Install(void);
	virtual void GetCaps(TDes8 &) const;
	virtual DLogicalChannel *CreateL(void);
};

class E32BootChannel : public DLogicalChannel {
public:
	E32BootChannel(DLogicalDevice *);

protected:
	virtual void DoCancel(TInt);
	virtual void DoRequest(TInt, TAny *, TAny *);
	virtual TInt DoControl(TInt, TAny *, TAny *);

private:
	EPOC32 *epoc32;
	TAny *safeAddress;

	TInt BootNetBSD(NetBSD *, struct btinfo_common *);
};


/* E32Dll() function is required by all DLLs. */
GLDEF_C TInt
E32Dll(TDllReason)
{

	return KErrNone;
}

EXPORT_C DLogicalDevice *
CreateLogicalDevice(void)
{

	return new E32BootLDD;
}

E32BootLDD::E32BootLDD(void)
{
	/* Nothing */
}

TInt
E32BootLDD::Install(void)
{

	return SetName(&E32BootName);
}

void
E32BootLDD::GetCaps(TDes8 &aDes) const
{
	TVersion version(0, 0, 0);	/* XXXXX: What is it? Don't check? */

	aDes.FillZ(aDes.MaxLength());
	aDes.Copy((TUint8 *)&version, Min(aDes.MaxLength(), sizeof(version)));
}

DLogicalChannel *
E32BootLDD::CreateL(void)
{

	return new (ELeave) E32BootChannel(this);
}


E32BootChannel::E32BootChannel(DLogicalDevice *aDevice)
    : DLogicalChannel(aDevice)
{

	epoc32 = new EPOC32;
	safeAddress = NULL;
}

void
E32BootChannel::DoCancel(TInt aReqNo)
{
	/* Nothing */
}

void
E32BootChannel::DoRequest(TInt aReqNo, TAny *a1, TAny *a2)
{
	/* Nothing */
}

TInt
E32BootChannel::DoControl(TInt aFunction, TAny *a1, TAny *a2)
{

	switch (aFunction) {
	case KE32BootGetProcessorID:
	{
		TInt id;

		__asm("mrc	p15, 0, %0, c0, c0" : "=r"(id));
		*(TUint *)a1 = id;
		break;
	}

	case KE32BootSetSafeAddress:
	{
		safeAddress = (TAny *)PAGE_ALIGN(a1);
		break;
	}

	case KE32BootBootNetBSD:
	{
		NetBSD *netbsd = (NetBSD *)a1;
		struct btinfo_common *bootinfo = (struct btinfo_common *)a2;

		BootNetBSD(netbsd, bootinfo);

		/* NOTREACHED */

		break;
	}

	default:
		break;
	}
	return KErrNone;
}

TInt
E32BootChannel::BootNetBSD(NetBSD *netbsd, struct btinfo_common *bootinfo)
{
	TAny *mmu_disabled, *ttb;

	__asm("adr %0, mmu_disabled" : "=r"(mmu_disabled));
	mmu_disabled = epoc32->GetPhysicalAddress(mmu_disabled);
	/*
	 * ARMv3 can't read TTB from CP15 C1.
	 * Also can't read Control Register.
	 */
	ttb = epoc32->GetPhysicalAddress(epoc32->GetTTB());

	__asm __volatile("				\
	mrs	r12, cpsr;				\
	/* Clear PSR_MODE and Interrupts */		\
	bic	r12, r12, #0xdf;			\
	/* Disable Interrupts(IRQ/FIQ) */		\
	orr	r12, r12, #(3 << 6);			\
	/* Set SVC32 MODE */				\
	orr	r12, r12, #0x13;			\
	msr	cpsr_c, r12;				\
							\
	ldr	r10, [%0, #0x0];			\
	ldr	sp, [%0, #0x4];				\
	ldr	lr, [%0, #0x8];				\
	mov	r12, %1;				\
	" :: "r"(netbsd), "r"(bootinfo));

	__asm __volatile("				\
	mov	r7, %2;					\
	mov	r8, %1;					\
	mov	r9, %0;					\
							\
	/* Set all domains to 15 */			\
	mov	r0, #0xffffffff;			\
	mcr	p15, 0, r0, c3, c0;			\
							\
	/* Disable MMU */				\
	mov	r0, #0x38; /* WBUF | 32BP | 32BD */	\
	mcr	p15, 0, r0, c1, c0, 0;			\
							\
	mov	pc, r7;					\
							\
mmu_disabled:						\
	/*						\
	 * r8	safe address(maybe frame-buffer address)\
	 * r9	ttb					\
	 * r10	buffer (netbsd)				\
	 * r11	memory descriptor			\
	 * r12	bootinfo				\
	 * sp	load descriptor				\
	 * lr	entry point				\
	 */						\
	/* save lr to r7 before call functions. */	\
	mov	r7, lr;					\
							\
	mov	r0, r8;					\
	mov	r1, r9;					\
	bl	vtop;					\
	mov	r8, r0;					\
							\
	/*						\
	 * Copy bootinfo to safe address.		\
	 * That addr used to framebuffer by EPOC32.	\
	 */						\
	mov	r0, r12;				\
	mov	r1, r9;					\
	bl	vtop;					\
	mov	r1, r0;					\
	mov	r12, r8;				\
	mov	r0, r8;					\
	mov	r2, #0x400;				\
	bl	copy;					\
							\
	/* save lr(r7) to r8. it is no need. */		\
	mov	r8, r7;					\
							\
	/* Copy loader to safe address + 0x400. */	\
	add	r0, r12, #0x400;			\
	adr	r1, miniloader_start;			\
	adr	r2, miniloader_end;			\
	sub	r2, r2, r1;				\
	bl	copy;					\
							\
	/* Make load-descriptor to safe addr + 0x800. */\
	mov	r0, sp;					\
	mov	r1, r9;					\
	bl	vtop;					\
	mov	sp, r0;					\
	add	r4, r12, #0x800;			\
							\
next_section:						\
	ldmia	sp!, {r5 - r7};				\
							\
next_page:						\
	add	r0, r10, r6;				\
	mov	r1, r9;					\
	bl	vtop;					\
	/* vtop returns set mask to r2 */		\
	orr	r2, r0, r2;				\
	add	r2, r2, #1;				\
	sub	r2, r2,	r0;				\
	cmp	r2, r7;					\
	movgt	r2, r7;					\
	mov	r1, r0;					\
	mov	r0, r5;					\
	stmia	r4!, {r0 - r2};				\
	add	r5, r5, r2;				\
	add	r6, r6, r2;				\
	subs	r7, r7, r2;				\
	bgt	next_page;				\
							\
	ldr	r0, [sp];				\
	cmp	r0, #0xffffffff;			\
	beq	fin;					\
	/* Pad to section align. */			\
	str	r5, [r4], #4;				\
	mov	r6, #0xffffffff;			\
	str	r6, [r4], #4;				\
	sub	r2, r0, r5;				\
	str	r2, [r4], #4;				\
	b	next_section;				\
							\
fin:							\
	stmia	r4, {r5 - r7};				\
	add	sp, r12, #0x800;			\
							\
	/* save lr(r8) to r11. r11 is no need. */	\
	mov	r11, r8;				\
							\
	/* Fixup load-descriptor by BTINFO_MEMORY. */	\
	mov	r10, r12;				\
	mov	r9, sp;					\
	add	r8, sp, #12;				\
next_bootinfo:						\
	ldmia	r10, {r0, r1};				\
	cmp	r1, #0;		/* BTINFO_NONE */	\
	beq	btinfo_none;				\
							\
	cmp	r1, #2;		/* BTINFO_MEMORY */	\
	beq	btinfo_memory;				\
	add	r10, r10, r0;				\
	b	next_bootinfo;				\
							\
btinfo_none:						\
	/* ENOMEM */					\
	add	r2, r12, #0x800;			\
	mov	r1, #640;				\
	mov	r0, #0x00ff0000;			\
	orr	r0, r0, #0x00ff;			\
98:							\
	str	r0, [r2], #4;				\
	subs	r1, r1, #4;				\
	bgt	98b;					\
	mov	r1, #640;				\
	mov	r0, #0xff000000;			\
	orr	r0, r0, #0xff00;			\
99:							\
	str	r0, [r2], #4;				\
	subs	r1, r1, #4;				\
	bgt	99b;					\
100:							\
	b	100b;					\
							\
btinfo_memory:						\
	ldmia	r10!, {r4 - r7};			\
	ldr	r4, [r9, #0];				\
	subs	r4, r4, r6;				\
	addgt	r6, r6, r4;				\
	subgt	r7, r7, r4;				\
next_desc:						\
	ldmia	r9, {r3 - r5};				\
	ldmia	r8!, {r0 - r2};				\
	add	r3, r3, r5;				\
	add	r4, r4, r5;				\
	cmp	r3, r0;					\
	cmpeq	r4, r1;					\
	beq	join_desc;				\
							\
	ldr	r3, [r9, #0];				\
	cmp	r3, r6;					\
	strlt	r6, [r9, #0];	/* Fixup */		\
	cmp	r5, r7;					\
	bgt	split_desc;				\
	add	r6, r6, r5;				\
	sub	r7, r7, r5;				\
	add	r9, r9, #12;				\
	stmia	r9, {r0 - r2};				\
	cmp	r0, #0xffffffff;			\
	beq	fixuped;				\
	b	next_desc;				\
							\
join_desc:	/* Join r8 descriptor to r9. */		\
	add	r5, r5, r2;				\
	str	r5, [r9, #8];				\
	b	next_desc;				\
							\
split_desc:	/* Split r9 descriptor. */		\
	ldr	r3, [r9, #0];				\
	ldr	r4, [r9, #4];				\
	str	r7, [r9, #8];				\
							\
	sub	r6, r5, r7;				\
	add	r5, r4, r7;				\
	add	r4, r3, r7;				\
	sub	r8, r8, #12;	/* Back to prev desc */	\
	add	r9, r9, #12;/* Point to splited desc */ \
							\
	cmp	r8, r9;					\
	bne	2f;					\
	add	r0, r8, #12;				\
	mov	r1, r8;					\
	mov	r2, #0;					\
1:							\
	ldr	r3, [r8, r2];				\
	add	r2, r2, #12;				\
	cmp	r3, #0xffffffff;			\
	bne	1b;					\
	bl	copy;					\
	add	r8, r8, #12; /* Point to moved desc */	\
2:							\
	stmia	r9, {r4 - r6};				\
	b	next_bootinfo;				\
							\
fixuped:						\
	/* Jump to miniloader. Our LR is entry-point! */\
	add	pc, r12, #0x400;			\
							\
vtop:							\
	/*						\
	 * paddr vtop(vaddr, ttb)			\
	 */						\
	bic	r2, r0, #0x000f0000; /* L1_ADDR_BITS */	\
	bic	r2, r2, #0x0000ff00; /* L1_ADDR_BITS */	\
	bic	r2, r2, #0x000000ff; /* L1_ADDR_BITS */	\
	mov	r2, r2, lsr #(20 - 2);			\
	ldr	r1, [r1, r2];				\
	and	r3, r1, #0x3;	/* L1_TYPE_MASK */	\
	cmp	r3, #0;					\
	bne	valid;					\
							\
invalid:						\
	mov	r0, #-1;				\
	mov	pc, lr;					\
							\
valid:							\
	cmp	r3, #0x2;				\
	bgt	3f;					\
	beq	2f;					\
							\
1:	/* Coarse L2 */					\
	mov	r2, #10;				\
	b	l2;					\
							\
2:	/* Section */					\
	mov	r2, #0xff000000;    /* L1_S_ADDR_MASK */\
	add	r2, r2, #0x00f00000;/* L1_S_ADDR_MASK */\
	mvn	r2, r2;					\
	bic	r3, r1, r2;				\
	and	r0, r0, r2;				\
	orr	r0, r3, r0;				\
	mov	pc, lr;					\
							\
3:	/* Fine L2 */					\
	mov	r2, #12;				\
l2:							\
	mov	r3, #1;					\
	mov	r3, r3, lsl r2;				\
	sub	r3, r3, #1;				\
	bic	r1, r1, r3;	/* L2 table */		\
	mov	r3, r0, lsl #12;			\
	mov	r3, r3, lsr #12;			\
	sub	r2, r2, #22;				\
	rsb	r2, r2, #0;				\
	mov	r3, r3, lsr r2;	/* index for L2 */	\
	ldr	r1, [r1, r3, lsl #2];			\
	and	r3, r1, #0x3;	/* L2_TYPE_MASK */	\
	cmp	r3, #0;					\
	beq	invalid;				\
	cmp	r3, #2;					\
	movlt	r2, #16;	/* L2_L_SHIFT */	\
	moveq	r2, #12;	/* L2_S_SHIFT */	\
	movgt	r2, #10;	/* L2_T_SHIFT */	\
	mov	r3, #1;					\
	mov	r2, r3, lsl r2;				\
	sub	r2, r2, #1;				\
	bic	r3, r1, r2;				\
	and	r0, r0, r2;				\
	orr	r0, r3, r0;				\
	mov	pc, lr;					\
							\
miniloader_start:					\
	b	miniloader;				\
							\
copy:							\
	/*						\
	 * void copy(dest, src, len)			\
	 */						\
	cmp	r0, r1;					\
	bgt	rcopy;					\
lcopy:							\
	ldr	r3, [r1], #4;				\
	str	r3, [r0], #4;				\
	subs	r2, r2, #4;				\
	bgt	lcopy;					\
	mov	pc, lr;					\
rcopy:							\
	subs	r2, r2, #4;				\
	ldr	r3, [r1, r2];				\
	str	r3, [r0, r2];				\
	bgt	rcopy;					\
	mov	pc, lr;					\
							\
							\
miniloader:						\
	/*						\
	 * r11	entry-point				\
	 * r12	bootinfo				\
	 * sp	load-descriptor				\
	 */						\
load:							\
	ldmia	sp!, {r0 - r2};				\
	cmp	r0, #0xffffffff;			\
	beq	end;					\
	cmp	r1, #0xffffffff;/* Skip section align */\
	blne	copy;		 /* or copy */		\
	b	load;					\
							\
end:							\
	mov	r0, r12;				\
	mov	pc, r11;				\
	nop;						\
	nop;						\
miniloader_end:						\
							\
	"
	::"r"(ttb),
	  "r"(safeAddress),
	  "r"(mmu_disabled)
	);

	/* NOTREACHED */

	return -1;
}