summaryrefslogtreecommitdiffstats
path: root/qemu/roms/SLOF/lib/libhvcall/brokensc1.c
blob: e6387e0ab11f8b1d591fd55358191745cf283420 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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;
}