Skip to content

Commit

Permalink
FIXME: Provisional R5900 short loop bug verification tool
Browse files Browse the repository at this point in the history
  • Loading branch information
frno7 committed Nov 30, 2019
1 parent baf32bb commit 59a11ab
Show file tree
Hide file tree
Showing 23 changed files with 1,694 additions and 0 deletions.
3 changes: 3 additions & 0 deletions tools/r5900check/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.o
*.o.d
r5900check
48 changes: 48 additions & 0 deletions tools/r5900check/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# SPDX-License-Identifier: GPL-2.0
#
# Define V=1 for more verbose compile.
# Define S=1 for sanitation checks.

CFLAGS += -g -O2 -Wall -Iinclude -D_GNU_SOURCE

ifeq "$(S)" "1"
CFLAGS += -fsanitize=address -fsanitize=leak -fsanitize=undefined \
-fsanitize-address-use-after-scope -fstack-check
endif

ALL_CFLAGS += $(CFLAGS) $(BASIC_CFLAGS)

R5900CHECK := r5900check

.PHONY: tool
all: $(R5900CHECK)

SRC := $(filter-out elf.c, $(wildcard *.c))
OBJ = $(patsubst %.c, %.o, $(SRC))

$(R5900CHECK): $(OBJ)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $^

$(OBJ): %.o : %.c
$(QUIET_CC)$(CC) $(ALL_CFLAGS) -c -o $@ $<

.PHONY: clean
clean:
$(QUIET_RM)$(RM) -f *.o *.o.d $(R5900CHECK)

V = @
Q = $(V:1=)
QUIET_AR = $(Q:@=@echo ' AR '$@;)
QUIET_AS = $(Q:@=@echo ' AS '$@;)
QUIET_CC = $(Q:@=@echo ' CC '$@;)
QUIET_GEN = $(Q:@=@echo ' GEN '$@;)
QUIET_LINK = $(Q:@=@echo ' LD '$@;)
QUIET_TEST = $(Q:@=@echo ' TEST '$@;)
QUIET_RM = $(Q:@=@echo ' RM '$@;)

BASIC_CFLAGS += -Wp,-MMD,$(@D)/$(@F).d -MT $(@D)/$(@F)

ifneq "$(MAKECMDGOALS)" "clean"
DEP_FILES := $(addsuffix .d, $(OBJ))
$(if $(DEP_FILES),$(eval -include $(DEP_FILES)))
endif
38 changes: 38 additions & 0 deletions tools/r5900check/assert.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-2.0

#ifndef R5900CHECK_ASSERT_H
#define R5900CHECK_ASSERT_H

#include "types.h"

/* Macro definitions from the Linux kernel. */

#define __compiletime_error(message) __attribute__((__error__(message)))

#ifdef __OPTIMIZE__
# define __compiletime_assert(condition, msg, prefix, suffix) \
do { \
extern void prefix ## suffix(void) __compiletime_error(msg); \
if (!(condition)) \
prefix ## suffix(); \
} while (0)
#else
# define __compiletime_assert(condition, msg, prefix, suffix) do { } while (0)
#endif

#define _compiletime_assert(condition, msg, prefix, suffix) \
__compiletime_assert(condition, msg, prefix, suffix)

/**
* compiletime_assert - break build and emit msg if condition is false
* @condition: a compile-time constant condition to check
* @msg: a message to emit if condition is false
*
* In tradition of POSIX assert, this macro will break the build if the
* supplied condition is *false*, emitting the supplied error message if the
* compiler has support to do so.
*/
#define compiletime_assert(condition, msg) \
_compiletime_assert(condition, msg, __compiletime_assert_, __LINE__)

#endif /* R5900CHECK_ASSERT_H */
30 changes: 30 additions & 0 deletions tools/r5900check/build-bug.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: GPL-2.0

#ifndef R5900CHECK_BUILD_BUG_H
#define R5900CHECK_BUILD_BUG_H

#include "assert.h"

/* Macro definitions from the Linux kernel. */

/**
* BUILD_BUG_ON_MSG - break compile if a condition is true & emit supplied
* error message.
* @condition: the condition which the compiler should know is false.
*
* See BUILD_BUG_ON for description.
*/
#define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)

/**
* BUILD_BUG_ON - break compile if a condition is true.
* @condition: the condition which the compiler should know is false.
*
* If you have some code which relies on certain constants being equal, or
* some other compile-time-evaluated condition, you should use BUILD_BUG_ON to
* detect if someone changes it.
*/
#define BUILD_BUG_ON(condition) \
BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)

#endif /* R5900CHECK_BUILD_BUG_H */
216 changes: 216 additions & 0 deletions tools/r5900check/check.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// SPDX-License-Identifier: GPL-2.0

/*
* The short loop bug under certain conditions causes loops to execute only
* once or twice. The Gnu assembler (GAS) has the following note about it:
*
* On the R5900 short loops need to be fixed by inserting a NOP in the
* branch delay slot.
*
* The short loop bug under certain conditions causes loops to execute
* only once or twice. We must ensure that the assembler never
* generates loops that satisfy all of the following conditions:
*
* - a loop consists of less than or equal to six instructions
* (including the branch delay slot);
* - a loop contains only one conditional branch instruction at the
* end of the loop;
* - a loop does not contain any other branch or jump instructions;
* - a branch delay slot of the loop is not NOP (EE 2.9 or later).
*
* We need to do this because of a hardware bug in the R5900 chip.
*/

#include <stdlib.h>

#include "check.h"
#include "elf32.h"
#include "file.h"
#include "inst.h"
#include "print.h"

bool inst_branch_offset(int *offset, const union mips_instruction inst)
{
if (inst.i_format.opcode == beq_op ||
inst.i_format.opcode == beql_op ||
inst.i_format.opcode == bne_op ||
inst.i_format.opcode == bnel_op) {
if (offset)
*offset = inst.i_format.simmediate;
return true;
}

if (inst.i_format.opcode == bcond_op) {
if (inst.i_format.rt == bgez_op ||
inst.i_format.rt == bgezal_op ||
inst.i_format.rt == bgezall_op ||
inst.i_format.rt == bgezl_op ||
inst.i_format.rt == bltz_op ||
inst.i_format.rt == bltzal_op ||
inst.i_format.rt == bltzall_op ||
inst.i_format.rt == bltzl_op) {
if (offset)
*offset = inst.i_format.simmediate;
return true;
}
}

if (inst.i_format.rt == 0) {
if ((inst.i_format.opcode == bgtz_op) ||
(inst.i_format.opcode == bgtzl_op) ||
(inst.i_format.opcode == blez_op) ||
(inst.i_format.opcode == blezl_op)) {
if (offset)
*offset = inst.i_format.simmediate;
return true;
}
}

return false;
}

bool inst_branch(const union mips_instruction inst)
{
return inst_branch_offset(NULL, inst);
}

bool inst_jump(const union mips_instruction inst)
{
if (inst.j_format.opcode == j_op ||
inst.j_format.opcode == jal_op)
return true;

if (inst.r_format.opcode == spec_op &&
inst.r_format.rt == 0 &&
inst.r_format.re == 0 &&
inst.r_format.func == jalr_op)
return true;

if (inst.r_format.opcode == spec_op &&
inst.r_format.rt == 0 &&
inst.r_format.rd == 0 &&
inst.r_format.re == 0 &&
inst.r_format.func == jr_op)
return true;

return false;
}

static bool inst_nop(const size_t i,
const union mips_instruction *inst, const size_t inst_count)
{
if (inst_count <= i)
return false;

// pr_info("%s %08x\n", __func__, inst[i].word);

return inst[i].word == 0;
}

static bool inst_branch_or_jump(const ssize_t i,
const union mips_instruction *inst, const size_t inst_count)
{
if (i < 0 || inst_count <= i)
return false;

// pr_info("%s %08x\n", __func__, inst[i].word);

return inst_branch(inst[i]) || inst_jump(inst[i]);
}

static bool short_loop_erratum(const size_t i,
const union mips_instruction *inst, const size_t inst_count)
{
int offset;

if (inst_count <= i)
return false;

if (!inst_branch_offset(&offset, inst[i]))
return false;

if (offset <= -6 || 0 <= offset)
return false;

if (inst_nop(i + 1, inst, inst_count))
return false;

for (int k = offset + 1; k < 0; k++)
if (inst_branch_or_jump((ssize_t)i + k, inst, inst_count))
return false;

// pr_info("\t%08x %d\n", inst[i].word, offset);

return true;
}

static void pr_short_loop_erratum(const size_t i,
const union mips_instruction *inst, const size_t inst_count,
Elf_Shdr *shdr, Elf_Ehdr *ehdr, struct file file)
{
int offset;

if (inst_count <= i)
return;

if (!inst_branch_offset(&offset, inst[i]))
return;

printf("erratum shortloop path %s\n", file.path);

for (int k = offset + 1; k <= 1; k++) {
const ssize_t j = (ssize_t)i + k;
const u32 addr = shdr->sh_addr + j * sizeof(*inst);

printf("code %08x ", addr);

if (j < 0 || inst_count <= j)
printf(" - -");
else
printf("%2d %08x", k, inst[j].word);

printf("\n");
}

exit(EXIT_FAILURE);
}

static void check_text_section(Elf_Shdr *shdr,
Elf_Ehdr *ehdr, struct file file)
{
const union mips_instruction *inst =
elf_ent_for_offset(shdr->sh_offset, ehdr);
const size_t inst_count = shdr->sh_size / sizeof(*inst);
size_t branch_count = 0;

pr_info("section name %s\n", elf_section_name(shdr, ehdr));
pr_info("section instruction count %zu\n", inst_count);

for (size_t i = 0; i < inst_count; i++) {
if (short_loop_erratum(i, inst, inst_count))
pr_short_loop_erratum(i, inst, inst_count,
shdr, ehdr, file);

if (inst_branch(inst[i]))
branch_count++;
}

pr_info("section branch count %zu\n", branch_count);
}

void check_file(struct file file)
{
Elf_Ehdr *ehdr = file.data;
Elf_Shdr *shdr;

pr_info("check %s\n", file.path);

if (!elf_identify(file.data, file.size)) {
fprintf(stderr, "%s: not a valid ELF object\n", file.path);
exit(EXIT_FAILURE);
}

elf_for_each_section_type (shdr, ehdr, SHT_PROGBITS)
if (shdr->sh_flags & SHF_EXECINSTR)
check_text_section(shdr, ehdr, file);
}
10 changes: 10 additions & 0 deletions tools/r5900check/check.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0

#ifndef R5900CHECK_CHECK_H
#define R5900CHECK_CHECK_H

#include "file.h"

void check_file(struct file file);

#endif /* R5900CHECK_CHECK_H */
Loading

0 comments on commit 59a11ab

Please sign in to comment.