xref: /linux/arch/mips/tools/loongson3-llsc-check.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
1 // SPDX-License-Identifier: GPL-2.0-only
2 #include <byteswap.h>
3 #include <elf.h>
4 #include <endian.h>
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <inttypes.h>
8 #include <stdbool.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/mman.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <unistd.h>
16 
17 #ifdef be32toh
18 /* If libc provides le{16,32,64}toh() then we'll use them */
19 #elif BYTE_ORDER == LITTLE_ENDIAN
20 # define le16toh(x)	(x)
21 # define le32toh(x)	(x)
22 # define le64toh(x)	(x)
23 #elif BYTE_ORDER == BIG_ENDIAN
24 # define le16toh(x)	bswap_16(x)
25 # define le32toh(x)	bswap_32(x)
26 # define le64toh(x)	bswap_64(x)
27 #endif
28 
29 /* MIPS opcodes, in bits 31:26 of an instruction */
30 #define OP_SPECIAL	0x00
31 #define OP_REGIMM	0x01
32 #define OP_BEQ		0x04
33 #define OP_BNE		0x05
34 #define OP_BLEZ		0x06
35 #define OP_BGTZ		0x07
36 #define OP_BEQL		0x14
37 #define OP_BNEL		0x15
38 #define OP_BLEZL	0x16
39 #define OP_BGTZL	0x17
40 #define OP_LL		0x30
41 #define OP_LLD		0x34
42 #define OP_SC		0x38
43 #define OP_SCD		0x3c
44 
45 /* Bits 20:16 of OP_REGIMM instructions */
46 #define REGIMM_BLTZ	0x00
47 #define REGIMM_BGEZ	0x01
48 #define REGIMM_BLTZL	0x02
49 #define REGIMM_BGEZL	0x03
50 #define REGIMM_BLTZAL	0x10
51 #define REGIMM_BGEZAL	0x11
52 #define REGIMM_BLTZALL	0x12
53 #define REGIMM_BGEZALL	0x13
54 
55 /* Bits 5:0 of OP_SPECIAL instructions */
56 #define SPECIAL_SYNC	0x0f
57 
usage(FILE * f)58 static void usage(FILE *f)
59 {
60 	fprintf(f, "Usage: loongson3-llsc-check /path/to/vmlinux\n");
61 }
62 
se16(uint16_t x)63 static int se16(uint16_t x)
64 {
65 	return (int16_t)x;
66 }
67 
is_ll(uint32_t insn)68 static bool is_ll(uint32_t insn)
69 {
70 	switch (insn >> 26) {
71 	case OP_LL:
72 	case OP_LLD:
73 		return true;
74 
75 	default:
76 		return false;
77 	}
78 }
79 
is_sc(uint32_t insn)80 static bool is_sc(uint32_t insn)
81 {
82 	switch (insn >> 26) {
83 	case OP_SC:
84 	case OP_SCD:
85 		return true;
86 
87 	default:
88 		return false;
89 	}
90 }
91 
is_sync(uint32_t insn)92 static bool is_sync(uint32_t insn)
93 {
94 	/* Bits 31:11 should all be zeroes */
95 	if (insn >> 11)
96 		return false;
97 
98 	/* Bits 5:0 specify the SYNC special encoding */
99 	if ((insn & 0x3f) != SPECIAL_SYNC)
100 		return false;
101 
102 	return true;
103 }
104 
is_branch(uint32_t insn,int * off)105 static bool is_branch(uint32_t insn, int *off)
106 {
107 	switch (insn >> 26) {
108 	case OP_BEQ:
109 	case OP_BEQL:
110 	case OP_BNE:
111 	case OP_BNEL:
112 	case OP_BGTZ:
113 	case OP_BGTZL:
114 	case OP_BLEZ:
115 	case OP_BLEZL:
116 		*off = se16(insn) + 1;
117 		return true;
118 
119 	case OP_REGIMM:
120 		switch ((insn >> 16) & 0x1f) {
121 		case REGIMM_BGEZ:
122 		case REGIMM_BGEZL:
123 		case REGIMM_BGEZAL:
124 		case REGIMM_BGEZALL:
125 		case REGIMM_BLTZ:
126 		case REGIMM_BLTZL:
127 		case REGIMM_BLTZAL:
128 		case REGIMM_BLTZALL:
129 			*off = se16(insn) + 1;
130 			return true;
131 
132 		default:
133 			return false;
134 		}
135 
136 	default:
137 		return false;
138 	}
139 }
140 
check_ll(uint64_t pc,uint32_t * code,size_t sz)141 static int check_ll(uint64_t pc, uint32_t *code, size_t sz)
142 {
143 	ssize_t i, max, sc_pos;
144 	int off;
145 
146 	/*
147 	 * Every LL must be preceded by a sync instruction in order to ensure
148 	 * that instruction reordering doesn't allow a prior memory access to
149 	 * execute after the LL & cause erroneous results.
150 	 */
151 	if (!is_sync(le32toh(code[-1]))) {
152 		fprintf(stderr, "%" PRIx64 ": LL not preceded by sync\n", pc);
153 		return -EINVAL;
154 	}
155 
156 	/* Find the matching SC instruction */
157 	max = sz / 4;
158 	for (sc_pos = 0; sc_pos < max; sc_pos++) {
159 		if (is_sc(le32toh(code[sc_pos])))
160 			break;
161 	}
162 	if (sc_pos >= max) {
163 		fprintf(stderr, "%" PRIx64 ": LL has no matching SC\n", pc);
164 		return -EINVAL;
165 	}
166 
167 	/*
168 	 * Check branches within the LL/SC loop target sync instructions,
169 	 * ensuring that speculative execution can't generate memory accesses
170 	 * due to instructions outside of the loop.
171 	 */
172 	for (i = 0; i < sc_pos; i++) {
173 		if (!is_branch(le32toh(code[i]), &off))
174 			continue;
175 
176 		/*
177 		 * If the branch target is within the LL/SC loop then we don't
178 		 * need to worry about it.
179 		 */
180 		if ((off >= -i) && (off <= sc_pos))
181 			continue;
182 
183 		/* If the branch targets a sync instruction we're all good... */
184 		if (is_sync(le32toh(code[i + off])))
185 			continue;
186 
187 		/* ...but if not, we have a problem */
188 		fprintf(stderr, "%" PRIx64 ": Branch target not a sync\n",
189 			pc + (i * 4));
190 		return -EINVAL;
191 	}
192 
193 	return 0;
194 }
195 
check_code(uint64_t pc,uint32_t * code,size_t sz)196 static int check_code(uint64_t pc, uint32_t *code, size_t sz)
197 {
198 	int err = 0;
199 
200 	if (sz % 4) {
201 		fprintf(stderr, "%" PRIx64 ": Section size not a multiple of 4\n",
202 			pc);
203 		err = -EINVAL;
204 		sz -= (sz % 4);
205 	}
206 
207 	if (is_ll(le32toh(code[0]))) {
208 		fprintf(stderr, "%" PRIx64 ": First instruction in section is an LL\n",
209 			pc);
210 		err = -EINVAL;
211 	}
212 
213 #define advance() (	\
214 	code++,		\
215 	pc += 4,	\
216 	sz -= 4		\
217 )
218 
219 	/*
220 	 * Skip the first instruction, allowing check_ll to look backwards
221 	 * unconditionally.
222 	 */
223 	advance();
224 
225 	/* Now scan through the code looking for LL instructions */
226 	for (; sz; advance()) {
227 		if (is_ll(le32toh(code[0])))
228 			err |= check_ll(pc, code, sz);
229 	}
230 
231 	return err;
232 }
233 
main(int argc,char * argv[])234 int main(int argc, char *argv[])
235 {
236 	int vmlinux_fd, status, err, i;
237 	const char *vmlinux_path;
238 	struct stat st;
239 	Elf64_Ehdr *eh;
240 	Elf64_Shdr *sh;
241 	void *vmlinux;
242 
243 	status = EXIT_FAILURE;
244 
245 	if (argc < 2) {
246 		usage(stderr);
247 		goto out_ret;
248 	}
249 
250 	vmlinux_path = argv[1];
251 	vmlinux_fd = open(vmlinux_path, O_RDONLY);
252 	if (vmlinux_fd == -1) {
253 		perror("Unable to open vmlinux");
254 		goto out_ret;
255 	}
256 
257 	err = fstat(vmlinux_fd, &st);
258 	if (err) {
259 		perror("Unable to stat vmlinux");
260 		goto out_close;
261 	}
262 
263 	vmlinux = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, vmlinux_fd, 0);
264 	if (vmlinux == MAP_FAILED) {
265 		perror("Unable to mmap vmlinux");
266 		goto out_close;
267 	}
268 
269 	eh = vmlinux;
270 	if (memcmp(eh->e_ident, ELFMAG, SELFMAG)) {
271 		fprintf(stderr, "vmlinux is not an ELF?\n");
272 		goto out_munmap;
273 	}
274 
275 	if (eh->e_ident[EI_CLASS] != ELFCLASS64) {
276 		fprintf(stderr, "vmlinux is not 64b?\n");
277 		goto out_munmap;
278 	}
279 
280 	if (eh->e_ident[EI_DATA] != ELFDATA2LSB) {
281 		fprintf(stderr, "vmlinux is not little endian?\n");
282 		goto out_munmap;
283 	}
284 
285 	for (i = 0; i < le16toh(eh->e_shnum); i++) {
286 		sh = vmlinux + le64toh(eh->e_shoff) + (i * le16toh(eh->e_shentsize));
287 
288 		if (sh->sh_type != SHT_PROGBITS)
289 			continue;
290 		if (!(sh->sh_flags & SHF_EXECINSTR))
291 			continue;
292 
293 		err = check_code(le64toh(sh->sh_addr),
294 				 vmlinux + le64toh(sh->sh_offset),
295 				 le64toh(sh->sh_size));
296 		if (err)
297 			goto out_munmap;
298 	}
299 
300 	status = EXIT_SUCCESS;
301 out_munmap:
302 	munmap(vmlinux, st.st_size);
303 out_close:
304 	close(vmlinux_fd);
305 out_ret:
306 	fprintf(stdout, "loongson3-llsc-check returns %s\n",
307 		status ? "failure" : "success");
308 	return status;
309 }
310