Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  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
 58static void usage(FILE *f)
 59{
 60	fprintf(f, "Usage: loongson3-llsc-check /path/to/vmlinux\n");
 61}
 62
 63static int se16(uint16_t x)
 64{
 65	return (int16_t)x;
 66}
 67
 68static 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
 80static 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
 92static 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
105static 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
141static 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
196static 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 instructionm 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
234int 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;
301out_munmap:
302	munmap(vmlinux, st.st_size);
303out_close:
304	close(vmlinux_fd);
305out_ret:
306	fprintf(stdout, "loongson3-llsc-check returns %s\n",
307		status ? "failure" : "success");
308	return status;
309}