linux/arch/mips/tools/loongson3-llsc-check.c
<<
>>
Prefs
   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}
 310