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