diff options
Diffstat (limited to 'qemu/roms/SLOF/lib/libhvcall/brokensc1.c')
-rw-r--r-- | qemu/roms/SLOF/lib/libhvcall/brokensc1.c | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/qemu/roms/SLOF/lib/libhvcall/brokensc1.c b/qemu/roms/SLOF/lib/libhvcall/brokensc1.c new file mode 100644 index 000000000..e6387e0ab --- /dev/null +++ b/qemu/roms/SLOF/lib/libhvcall/brokensc1.c @@ -0,0 +1,162 @@ +#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; +} + +static 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_SUCCESS || r == H_HARDWARE) { + /* All is fine */ + return 0; + } + + /* We found a broken sc1 host! */ + return 1; +} + +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; +} |