Commit 80947e7c authored by Geert Uytterhoeven's avatar Geert Uytterhoeven Committed by Benjamin Herrenschmidt
Browse files

powerpc: Keep track of emulated instructions



If CONFIG_PPC_EMULATED_STATS is enabled, make available counters for the
various classes of emulated instructions under
/sys/kernel/debug/powerpc/emulated_instructions/ (assumed debugfs is mounted on
/sys/kernel/debug).  Optionally (controlled by
/sys/kernel/debug/powerpc/emulated_instructions/do_warn), rate-limited warnings
can be printed to the console when instructions are emulated.

Signed-off-by: default avatarGeert Uytterhoeven <Geert.Uytterhoeven@sonycom.com>
Signed-off-by: default avatarBenjamin Herrenschmidt <benh@kernel.crashing.org>
parent f312deb4
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -41,6 +41,19 @@ config HCALL_STATS
	  This option will add a small amount of overhead to all hypervisor
	  calls.

config PPC_EMULATED_STATS
	bool "Emulated instructions tracking"
	depends on DEBUG_FS
	help
	  Adds code to keep track of the number of instructions that are
	  emulated by the in-kernel emulator. Counters for the various classes
	  of emulated instructions are available under
	  powerpc/emulated_instructions/ in the root of the debugfs file
	  system. Optionally (controlled by
	  powerpc/emulated_instructions/do_warn in debugfs), rate-limited
	  warnings can be printed to the console when instructions are
	  emulated.

config CODE_PATCHING_SELFTEST
	bool "Run self-tests of the code-patching code."
	depends on DEBUG_KERNEL
+73 −0
Original line number Diff line number Diff line
/*
 *  Copyright 2007 Sony Corporation
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _ASM_POWERPC_EMULATED_OPS_H
#define _ASM_POWERPC_EMULATED_OPS_H

#include <asm/atomic.h>


#ifdef CONFIG_PPC_EMULATED_STATS

struct ppc_emulated_entry {
	const char *name;
	atomic_t val;
};

extern struct ppc_emulated {
#ifdef CONFIG_ALTIVEC
	struct ppc_emulated_entry altivec;
#endif
	struct ppc_emulated_entry dcba;
	struct ppc_emulated_entry dcbz;
	struct ppc_emulated_entry fp_pair;
	struct ppc_emulated_entry isel;
	struct ppc_emulated_entry mcrxr;
	struct ppc_emulated_entry mfpvr;
	struct ppc_emulated_entry multiple;
	struct ppc_emulated_entry popcntb;
	struct ppc_emulated_entry spe;
	struct ppc_emulated_entry string;
	struct ppc_emulated_entry unaligned;
#ifdef CONFIG_MATH_EMULATION
	struct ppc_emulated_entry math;
#elif defined(CONFIG_8XX_MINIMAL_FPEMU)
	struct ppc_emulated_entry 8xx;
#endif
#ifdef CONFIG_VSX
	struct ppc_emulated_entry vsx;
#endif
} ppc_emulated;

extern u32 ppc_warn_emulated;

extern void ppc_warn_emulated_print(const char *type);

#define PPC_WARN_EMULATED(type)						 \
	do {								 \
		atomic_inc(&ppc_emulated.type.val);			 \
		if (ppc_warn_emulated)					 \
			ppc_warn_emulated_print(ppc_emulated.type.name); \
	} while (0)

#else /* !CONFIG_PPC_EMULATED_STATS */

#define PPC_WARN_EMULATED(type)	do { } while (0)

#endif /* !CONFIG_PPC_EMULATED_STATS */

#endif /* _ASM_POWERPC_EMULATED_OPS_H */
+16 −4
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@
#include <asm/system.h>
#include <asm/cache.h>
#include <asm/cputable.h>
#include <asm/emulated_ops.h>

struct aligninfo {
	unsigned char len;
@@ -730,8 +731,10 @@ int fix_alignment(struct pt_regs *regs)
	areg = dsisr & 0x1f;		/* register to update */

#ifdef CONFIG_SPE
	if ((instr >> 26) == 0x4)
	if ((instr >> 26) == 0x4) {
		PPC_WARN_EMULATED(spe);
		return emulate_spe(regs, reg, instr);
	}
#endif

	instr = (dsisr >> 10) & 0x7f;
@@ -783,23 +786,28 @@ int fix_alignment(struct pt_regs *regs)
			flags |= SPLT;
			nb = 8;
		}
		PPC_WARN_EMULATED(vsx);
		return emulate_vsx(addr, reg, areg, regs, flags, nb);
	}
#endif
	/* A size of 0 indicates an instruction we don't support, with
	 * the exception of DCBZ which is handled as a special case here
	 */
	if (instr == DCBZ)
	if (instr == DCBZ) {
		PPC_WARN_EMULATED(dcbz);
		return emulate_dcbz(regs, addr);
	}
	if (unlikely(nb == 0))
		return 0;

	/* Load/Store Multiple instructions are handled in their own
	 * function
	 */
	if (flags & M)
	if (flags & M) {
		PPC_WARN_EMULATED(multiple);
		return emulate_multiple(regs, addr, reg, nb,
					flags, instr, swiz);
	}

	/* Verify the address of the operand */
	if (unlikely(user_mode(regs) &&
@@ -816,8 +824,12 @@ int fix_alignment(struct pt_regs *regs)
	}

	/* Special case for 16-byte FP loads and stores */
	if (nb == 16)
	if (nb == 16) {
		PPC_WARN_EMULATED(fp_pair);
		return emulate_fp_pair(addr, reg, flags);
	}

	PPC_WARN_EMULATED(unaligned);

	/* If we are loading, get the data from user space, else
	 * get it from register values
+94 −2
Original line number Diff line number Diff line
@@ -33,7 +33,9 @@
#include <linux/backlight.h>
#include <linux/bug.h>
#include <linux/kdebug.h>
#include <linux/debugfs.h>

#include <asm/emulated_ops.h>
#include <asm/pgtable.h>
#include <asm/uaccess.h>
#include <asm/system.h>
@@ -757,36 +759,44 @@ static int emulate_instruction(struct pt_regs *regs)

	/* Emulate the mfspr rD, PVR. */
	if ((instword & PPC_INST_MFSPR_PVR_MASK) == PPC_INST_MFSPR_PVR) {
		PPC_WARN_EMULATED(mfpvr);
		rd = (instword >> 21) & 0x1f;
		regs->gpr[rd] = mfspr(SPRN_PVR);
		return 0;
	}

	/* Emulating the dcba insn is just a no-op.  */
	if ((instword & PPC_INST_DCBA_MASK) == PPC_INST_DCBA)
	if ((instword & PPC_INST_DCBA_MASK) == PPC_INST_DCBA) {
		PPC_WARN_EMULATED(dcba);
		return 0;
	}

	/* Emulate the mcrxr insn.  */
	if ((instword & PPC_INST_MCRXR_MASK) == PPC_INST_MCRXR) {
		int shift = (instword >> 21) & 0x1c;
		unsigned long msk = 0xf0000000UL >> shift;

		PPC_WARN_EMULATED(mcrxr);
		regs->ccr = (regs->ccr & ~msk) | ((regs->xer >> shift) & msk);
		regs->xer &= ~0xf0000000UL;
		return 0;
	}

	/* Emulate load/store string insn. */
	if ((instword & PPC_INST_STRING_GEN_MASK) == PPC_INST_STRING)
	if ((instword & PPC_INST_STRING_GEN_MASK) == PPC_INST_STRING) {
		PPC_WARN_EMULATED(string);
		return emulate_string_inst(regs, instword);
	}

	/* Emulate the popcntb (Population Count Bytes) instruction. */
	if ((instword & PPC_INST_POPCNTB_MASK) == PPC_INST_POPCNTB) {
		PPC_WARN_EMULATED(popcntb);
		return emulate_popcntb_inst(regs, instword);
	}

	/* Emulate isel (Integer Select) instruction */
	if ((instword & PPC_INST_ISEL_MASK) == PPC_INST_ISEL) {
		PPC_WARN_EMULATED(isel);
		return emulate_isel(regs, instword);
	}

@@ -984,6 +994,8 @@ void SoftwareEmulation(struct pt_regs *regs)

#ifdef CONFIG_MATH_EMULATION
	errcode = do_mathemu(regs);
	if (errcode >= 0)
		PPC_WARN_EMULATED(math);

	switch (errcode) {
	case 0:
@@ -1005,6 +1017,9 @@ void SoftwareEmulation(struct pt_regs *regs)

#elif defined(CONFIG_8XX_MINIMAL_FPEMU)
	errcode = Soft_emulate_8xx(regs);
	if (errcode >= 0)
		PPC_WARN_EMULATED(8xx);

	switch (errcode) {
	case 0:
		emulate_single_step(regs);
@@ -1088,6 +1103,7 @@ void altivec_assist_exception(struct pt_regs *regs)

	flush_altivec_to_thread(current);

	PPC_WARN_EMULATED(altivec);
	err = emulate_altivec(regs);
	if (err == 0) {
		regs->nip += 4;		/* skip emulated instruction */
@@ -1286,3 +1302,79 @@ void kernel_bad_stack(struct pt_regs *regs)
void __init trap_init(void)
{
}


#ifdef CONFIG_PPC_EMULATED_STATS

#define WARN_EMULATED_SETUP(type)	.type = { .name = #type }

struct ppc_emulated ppc_emulated = {
#ifdef CONFIG_ALTIVEC
	WARN_EMULATED_SETUP(altivec),
#endif
	WARN_EMULATED_SETUP(dcba),
	WARN_EMULATED_SETUP(dcbz),
	WARN_EMULATED_SETUP(fp_pair),
	WARN_EMULATED_SETUP(isel),
	WARN_EMULATED_SETUP(mcrxr),
	WARN_EMULATED_SETUP(mfpvr),
	WARN_EMULATED_SETUP(multiple),
	WARN_EMULATED_SETUP(popcntb),
	WARN_EMULATED_SETUP(spe),
	WARN_EMULATED_SETUP(string),
	WARN_EMULATED_SETUP(unaligned),
#ifdef CONFIG_MATH_EMULATION
	WARN_EMULATED_SETUP(math),
#elif defined(CONFIG_8XX_MINIMAL_FPEMU)
	WARN_EMULATED_SETUP(8xx),
#endif
#ifdef CONFIG_VSX
	WARN_EMULATED_SETUP(vsx),
#endif
};

u32 ppc_warn_emulated;

void ppc_warn_emulated_print(const char *type)
{
	if (printk_ratelimit())
		pr_warning("%s used emulated %s instruction\n", current->comm,
			   type);
}

static int __init ppc_warn_emulated_init(void)
{
	struct dentry *dir, *d;
	unsigned int i;
	struct ppc_emulated_entry *entries = (void *)&ppc_emulated;

	if (!powerpc_debugfs_root)
		return -ENODEV;

	dir = debugfs_create_dir("emulated_instructions",
				 powerpc_debugfs_root);
	if (!dir)
		return -ENOMEM;

	d = debugfs_create_u32("do_warn", S_IRUGO | S_IWUSR, dir,
			       &ppc_warn_emulated);
	if (!d)
		goto fail;

	for (i = 0; i < sizeof(ppc_emulated)/sizeof(*entries); i++) {
		d = debugfs_create_u32(entries[i].name, S_IRUGO | S_IWUSR, dir,
				       (u32 *)&entries[i].val.counter);
		if (!d)
			goto fail;
	}

	return 0;

fail:
	debugfs_remove_recursive(dir);
	return -ENOMEM;
}

device_initcall(ppc_warn_emulated_init);

#endif /* CONFIG_PPC_EMULATED_STATS */