// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Simple sanity tests for instruction emulation infrastructure.
*
* Copyright IBM Corp. 2016
*/
#define pr_fmt(fmt) "emulate_step_test: " fmt
#include <linux/ptrace.h>
#include <asm/sstep.h>
#include <asm/ppc-opcode.h>
#include <asm/code-patching.h>
#include <asm/inst.h>
#define MAX_SUBTESTS 16
#define IGNORE_GPR(n) (0x1UL << (n))
#define IGNORE_XER (0x1UL << 32)
#define IGNORE_CCR (0x1UL << 33)
static void __init init_pt_regs(struct pt_regs *regs)
{
static unsigned long msr;
static bool msr_cached;
memset(regs, 0, sizeof(struct pt_regs));
if (likely(msr_cached)) {
regs->msr = msr;
return;
}
asm volatile("mfmsr %0" : "=r"(regs->msr));
regs->msr |= MSR_FP;
regs->msr |= MSR_VEC;
regs->msr |= MSR_VSX;
msr = regs->msr;
msr_cached = true;
}
static void __init show_result(char *mnemonic, char *result)
{
pr_info("%-14s : %s\n", mnemonic, result);
}
static void __init show_result_with_descr(char *mnemonic, char *descr,
char *result)
{
pr_info("%-14s : %-50s %s\n", mnemonic, descr, result);
}
static void __init test_ld(void)
{
struct pt_regs regs;
unsigned long a = 0x23;
int stepped = -1;
init_pt_regs(®s);
regs.gpr[3] = (unsigned long) &a;
/* ld r5, 0(r3) */
stepped = emulate_step(®s, ppc_inst(PPC_RAW_LD(5, 3, 0)));
if (stepped == 1 && regs.gpr[5] == a)
show_result("ld", "PASS");
else
show_result("ld", "FAIL");
}
static void __init test_lwz(void)
{
struct pt_regs regs;
unsigned int a = 0x4545;
int stepped = -1;
init_pt_regs(®s);
regs.gpr[3] = (unsigned long) &a;
/* lwz r5, 0(r3) */
stepped = emulate_step(®s, ppc_inst(PPC_RAW_LWZ(5, 3, 0)));
if (stepped == 1 && regs.gpr[5] == a)
show_result("lwz", "PASS");
else
show_result("lwz", "FAIL");
}
static void __init test_lwzx(void)
{
struct pt_regs regs;
unsigned int a[3] = {0x0, 0x0, 0x1234};
int stepped = -1;
init_pt_regs(®s);
regs.gpr[3] = (unsigned long) a;
regs.gpr[4] = 8;
regs.gpr[5] = 0x8765;
/* lwzx r5, r3, r4 */
stepped = emulate_step(®s, ppc_inst(PPC_RAW_LWZX(5, 3, 4)));
if (stepped == 1 && regs.gpr[5] == a[2])
show_result("lwzx", "PASS");
else
show_result("lwzx", "FAIL");
}
static void __init test_std(void)
{
struct pt_regs regs;
unsigned long a = 0x1234;
int stepped = -1;
init_pt_regs(®s);
regs.gpr[3] = (unsigned long) &a;
regs.gpr[5] = 0x5678;
/* std r5, 0(r3) */
stepped = emulate_step(®s, ppc_inst(PPC_RAW_STD(5, 3, 0)));
if (stepped == 1 && regs.gpr[5] == a)
show_result("std", "PASS");
else
show_result("std", "FAIL");
}
static void __init test_ldarx_stdcx(void)
{
struct pt_regs regs;
unsigned long a = 0x1234;
int stepped = -1;
unsigned long cr0_eq = 0x1 << 29; /* eq bit of CR0 */
init_pt_regs(®s);
asm volatile("mfcr %0" : "=r"(regs.ccr));
/*** ldarx ***/
regs.gpr[3] = (unsigned long) &a;
regs.gpr[4] = 0;
regs.gpr[5] = 0x5678;
/* ldarx r5, r3, r4, 0 */
stepped = emulate_step(®s, ppc_inst(PPC_RAW_LDARX(5, 3, 4, 0)));
/*
* Don't touch 'a' here. Touching 'a' can do Load/store
* of 'a' which result in failure of subsequent stdcx.
* Instead, use hardcoded value for comparison.
*/
if (stepped <= 0 || regs.gpr[5] != 0x1234) {
show_result("ldarx / stdcx.", "FAIL (ldarx)");
return;
}
/*** stdcx. ***/
regs.gpr[5] = 0x9ABC;
/* stdcx. r5, r3, r4 */
stepped = emulate_step(®s, ppc_inst(PPC_RAW_STDCX(5, 3, 4)));
/*
* Two possible scenarios that indicates successful emulation
* of stdcx. :
* 1. Reservation is active and store is performed. In this
* case cr0.eq bit will be set to 1.
* 2. Reservation is not active and store is not performed.
* In this case cr0.eq bit will be set to 0.
*/
if (stepped == 1 && ((regs.gpr[5] == a && (regs.ccr & cr0_eq))
|| (regs.gpr[5] != a && !(regs.ccr & cr0_eq))))
show_result("ldarx / stdcx.", "PASS");
else
show_result("ldarx / stdcx.", "FAIL (stdcx.)");
}
#ifdef CONFIG_PPC_FPU
static void __init test_lfsx_stfsx(void)
{
struct pt_regs regs;
union {
float a;
int b;
} c;
int cached_b;
int stepped = -1;
init_pt_regs(®s);
/*** lfsx ***/
c.a = 123.45;
cached_b = c.b;
regs.gpr[3] = (unsigned long) &c.a;
regs.gpr[4] = 0;
/* lfsx frt1