#include <stdint.h>
#include <stddef.h>
#include <cpu.h>
#include "libhvcall.h"
#include "byteorder.h"

// #define DEBUG_PATCHERY

#define H_SET_DABR	0x28
#define INS_SC1		0x44000022
#define INS_SC1_REPLACE	0x7c000268

extern volatile uint32_t sc1ins;

static unsigned long hcall(uint32_t inst, unsigned long arg0, unsigned long arg1)
{
	register unsigned long r3 asm("r3") = arg0;
	register unsigned long r4 asm("r4") = arg1;
	register unsigned long r5 asm("r5") = inst;
	asm volatile("bl 1f		\n"
		     "1:		\n"
		     "li 11, 2f - 1b	\n"
		     "mflr 12		\n"
		     "add 11, 11, 12	\n"
		     "stw 5, 0(11)	\n"
		     "dcbst 0, 11	\n"
		     "sync		\n"
		     "icbi 0, 11	\n"
		     "isync		\n"
		     "2:		\n"
		     ".long 0		\n"
                     : "=r" (r3)
                     : "r" (r3), "r" (r4), "r" (r5)
                     : "ctr", "r0", "r6", "r7", "r8", "r9", "r10", "r11",
                       "r12", "r13", "r31", "lr", "cc");
	return r3;
}

int check_broken_sc1(void)
{
	long r;

	/*
	 * Check if we can do a simple hcall. If it works, we are running in
	 * a sane environment and everything's fine. If it doesn't, we need
	 * to patch the hypercall instruction to something that traps into
	 * supervisor mode.
	 */
	r = hcall(INS_SC1, H_SET_DABR, 0);
	if (r == H_PRIVILEGE) {
		/* We found a broken sc1 host! */
		return 1;
	}

	/* All is fine */
	return 0;
}

int patch_broken_sc1(void *start, void *end, uint32_t *test_ins)
{
	uint32_t *p;
	/* The sc 1 instruction */
	uint32_t sc1 = INS_SC1;
	/* An illegal instruction that KVM interprets as sc 1 */
	uint32_t sc1_replacement = INS_SC1_REPLACE;
	int is_le = (test_ins && *test_ins == 0x48000008);
#ifdef DEBUG_PATCHERY
	int cnt = 0;
#endif

	/* The host is sane, get out of here */
	if (!check_broken_sc1())
		return 0;

	/* We only get here with a broken sc1 implementation */

	/* Trim the range we scan to not cover the data section */
	if (test_ins) {
		/* This is the cpu table matcher for 970FX */
		uint32_t end_bytes[] = { 0xffff0000, 0x3c0000 };
		/*
		 * The .__start symbol contains a trap instruction followed
		 * by lots of zeros.
		 */
		uint32_t start_bytes[] = { 0x7fe00008, 0, 0, 0, 0 };

		if (is_le) {
			end_bytes[0] = bswap_32(end_bytes[0]);
			end_bytes[1] = bswap_32(end_bytes[1]);
			start_bytes[1] = bswap_32(start_bytes[1]);
		}

		/* Find the start of the text section */
		for (p = test_ins; (long)p > (long)start; p--) {
			if (p[0] == start_bytes[0] &&
			    p[1] == start_bytes[1] &&
			    p[2] == start_bytes[2] &&
			    p[3] == start_bytes[3] &&
			    p[4] == start_bytes[4]) {
				/*
				 * We found a match of the instruction sequence
				 *     trap
				 *     .long 0
				 *     .long 0
				 *     .long 0
				 *     .long 0
				 * which marks the beginning of the .text
				 * section on all Linux kernels I've checked.
				 */
#ifdef DEBUG_PATCHERY
				printf("Shortened start from %p to %p\n", end, p);
#endif
				start = p;
				break;
			}
		}

		/* Find the end of the text section */
		for (p = start; (long)p < (long)end; p++) {
			if (p[0] == end_bytes[0] && p[1] == end_bytes[1]) {
				/*
				 * We found a match of the PPC970FX entry in the
				 * guest kernel's CPU table. That table is
				 * usually found early in the .data section and
				 * thus marks the end of the .text section for
				 * us which we need to patch.
				 */
#ifdef DEBUG_PATCHERY
				printf("Shortened end from %p to %p\n", end, p);
#endif
				end = p;
				break;
			}
		}
	}

	if (is_le) {
		/*
		 * The kernel was built for LE mode, so our sc1 and replacement
		 * opcodes are in the wrong byte order. Reverse them.
		 */
		sc1 = bswap_32(sc1);
		sc1_replacement = bswap_32(sc1_replacement);
	}

	/* Patch all sc 1 instructions to reserved instruction 31/308 */
	for (p = start; (long)p < (long)end; p++) {
		if (*p == sc1) {
			*p = sc1_replacement;
			flush_cache(p, sizeof(*p));
#ifdef DEBUG_PATCHERY
			cnt++;
#endif
		}
	}

#ifdef DEBUG_PATCHERY
	printf("Patched %d instructions (%p - %p)\n", cnt, start, end);
#endif

	return 1;
}
