diff options
author | Alexander Graf <agraf@suse.de> | 2013-12-16 14:10:20 +0100 |
---|---|---|
committer | Nikunj A Dadhania <nikunj@linux.vnet.ibm.com> | 2013-12-17 11:01:46 +0530 |
commit | dd53579ae82bed0654dd3e4b3052ef2cac58b5f4 (patch) | |
tree | a21b15355998f71e143f42ecd5775e96f7499c6c /lib/libhvcall | |
parent | 69c6fc492ca1f9855643d5be5c96b8e19b8f20f8 (diff) | |
download | SLOF-dd53579ae82bed0654dd3e4b3052ef2cac58b5f4.zip SLOF-dd53579ae82bed0654dd3e4b3052ef2cac58b5f4.tar.gz SLOF-dd53579ae82bed0654dd3e4b3052ef2cac58b5f4.tar.bz2 |
Work around missing sc 1 traps on pHyp
When running a pseries guest in PR KVM on top of pHyp, sc 1 instructions
are handled directly by pHyp, so we don't get to see them.
That means we need to get inventive. Invent a new instruction that behaves
like sc 1, but really is a reserved instruction that traps. This instruction
can be used by KVM to emulate sc 1 behavior.
This patch adds the SLOF support for it. With this, SLOF detects whether
it's running on such a broken setup and if so patches itself to execute
the fake sc 1 instruction instead of the real one.
Furthermore, we also hook into "quiesce" which Linux calls when it boots.
This gives us the chance to also patch Linux when it boots up, so it uses
the fake sc 1 too.
Signed-off-by: Alexander Graf <agraf@suse.de>
Signed-off-by: Nikunj A Dadhania <nikunj@linux.vnet.ibm.com>
Diffstat (limited to 'lib/libhvcall')
-rw-r--r-- | lib/libhvcall/Makefile | 2 | ||||
-rw-r--r-- | lib/libhvcall/brokensc1.c | 162 | ||||
-rw-r--r-- | lib/libhvcall/hvcall.code | 8 | ||||
-rw-r--r-- | lib/libhvcall/hvcall.in | 1 | ||||
-rw-r--r-- | lib/libhvcall/libhvcall.h | 2 |
5 files changed, 174 insertions, 1 deletions
diff --git a/lib/libhvcall/Makefile b/lib/libhvcall/Makefile index 53ed98f..af7fbc3 100644 --- a/lib/libhvcall/Makefile +++ b/lib/libhvcall/Makefile @@ -22,7 +22,7 @@ TARGET = ../libhvcall.a all: $(TARGET) -SRCS = +SRCS = brokensc1.c SRCSS = hvcall.S diff --git a/lib/libhvcall/brokensc1.c b/lib/libhvcall/brokensc1.c new file mode 100644 index 0000000..c37a0f1 --- /dev/null +++ b/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 + +enum broken_sc1 { + SC1_UNKNOWN, + SC1_BROKEN, + SC1_WORKS, +}; + +static unsigned long hcall(unsigned long arg0, unsigned long arg1) +{ + register unsigned long r3 asm("r3") = arg0; + register unsigned long r4 asm("r4") = arg1; + asm volatile("sc 1" + : "=r" (r3) + : "r" (r3), "r" (r4) + : "ctr", "r5", "r6", "r7", "r8", "r9", "r10", "r11", + "r12", "r13", "r31", "lr", "cc"); + return r3; +} + +static enum broken_sc1 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(H_SET_DABR, 0); + if (r == H_SUCCESS || r == H_HARDWARE) { + /* All is fine */ + return SC1_WORKS; + } + + /* We found a broken sc1 host! */ + return SC1_BROKEN; +} + +int patch_broken_sc1(void *start, void *end, uint32_t *test_ins) +{ + static enum broken_sc1 is_broken_sc1 = SC1_UNKNOWN; + uint32_t *p; + /* The sc 1 instruction */ + uint32_t sc1 = 0x44000022; + /* An illegal instruction that KVM interprets as sc 1 */ + uint32_t sc1_replacement = 0x7c000268; + int is_le = (test_ins && *test_ins == 0x48000008); +#ifdef DEBUG_PATCHERY + int cnt = 0; +#endif + + switch (is_broken_sc1) { + case SC1_UNKNOWN: + /* If we never probed sc1 before, let's do so now! */ + is_broken_sc1 = check_broken_sc1(); + return patch_broken_sc1(start, end, test_ins); + case SC1_WORKS: + /* If we know that sc1 works fine, no need to check */ + return 0; + case SC1_BROKEN: + /* Handled below */ + break; + } + + /* 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; +} diff --git a/lib/libhvcall/hvcall.code b/lib/libhvcall/hvcall.code index 6d70b3c..744469f 100644 --- a/lib/libhvcall/hvcall.code +++ b/lib/libhvcall/hvcall.code @@ -115,3 +115,11 @@ PRIM(get_X2d_print_X2d_version) unsigned long addr = TOS.u; POP; get_print_banner(addr); MIRP + +PRIM(check_X2d_and_X2d_patch_X2d_sc1) + unsigned long end = TOS.u; POP; + unsigned long start = TOS.u; POP; + unsigned long patch_ins = TOS.u; POP; + + patch_broken_sc1((void*)start, (void*)end, (void*)patch_ins); +MIRP diff --git a/lib/libhvcall/hvcall.in b/lib/libhvcall/hvcall.in index 1f9ed6b..e99d6d1 100644 --- a/lib/libhvcall/hvcall.in +++ b/lib/libhvcall/hvcall.in @@ -17,6 +17,7 @@ cod(hv-reg-crq) cod(hv-free-crq) cod(hv-send-crq) cod(hv-put-tce) +cod(check-and-patch-sc1) cod(RB@) cod(RB!) diff --git a/lib/libhvcall/libhvcall.h b/lib/libhvcall/libhvcall.h index 03813cd..6356a62 100644 --- a/lib/libhvcall/libhvcall.h +++ b/lib/libhvcall/libhvcall.h @@ -2,6 +2,7 @@ #define __LIBHVCALL_H__ #define H_SUCCESS 0 +#define H_HARDWARE -1 #define H_GET_TCE 0x1C #define H_PUT_TCE 0x20 @@ -94,6 +95,7 @@ extern unsigned long hv_logical_ci_store(unsigned long size, unsigned long addr, extern unsigned long hv_logical_memop(unsigned long dst, unsigned long src, unsigned long esize, unsigned long count, unsigned long op); +extern int patch_broken_sc1(void *start, void *end, uint32_t *test_ins); extern unsigned long hv_cas(unsigned long vec, unsigned long buf, unsigned long size); |