blob: f01157029b8a967b0670f017fa0c4f3b8837a332 [file] [log] [blame]
#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;
}