xref: /openbmc/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