blob: 4c136a842aaa9817f96021e5b43968552e5916cd [file] [log] [blame]
/* SPDX-License-Identifier: LGPL-2.0-only */
/*
* Test interrupts
*
* Copyright 2024 Nicholas Piggin, IBM Corp.
*/
#include <libcflat.h>
#include <util.h>
#include <migrate.h>
#include <alloc.h>
#include <asm/setup.h>
#include <asm/handlers.h>
#include <asm/hcall.h>
#include <asm/processor.h>
#include <asm/time.h>
#include <asm/barrier.h>
#include <asm/mmu.h>
#include "alloc_phys.h"
#include "vmalloc.h"
static volatile bool got_interrupt;
static volatile struct pt_regs recorded_regs;
static void mce_handler(struct pt_regs *regs, void *opaque)
{
bool *is_fetch = opaque;
got_interrupt = true;
memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
if (*is_fetch)
regs->nip = regs->link;
else
regs_advance_insn(regs);
}
static void fault_handler(struct pt_regs *regs, void *opaque)
{
memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
if (regs->trap == 0x400 || regs->trap == 0x480)
regs->nip = regs->link;
else
regs_advance_insn(regs);
}
static void test_mce(void)
{
unsigned long addr = -4ULL;
uint8_t tmp;
bool is_fetch;
bool mmu = mmu_enabled();
report_prefix_push("mce");
handle_exception(0x200, mce_handler, &is_fetch);
handle_exception(0x300, fault_handler, NULL);
handle_exception(0x380, fault_handler, NULL);
handle_exception(0x400, fault_handler, NULL);
handle_exception(0x480, fault_handler, NULL);
if (mmu)
mmu_disable();
if (machine_is_powernv()) {
enable_mcheck();
} else {
report(mfmsr() & MSR_ME, "pseries machine has MSR[ME]=1");
if (!(mfmsr() & MSR_ME)) { /* try to fix it */
enable_mcheck();
}
if (mfmsr() & MSR_ME) {
disable_mcheck();
report(mfmsr() & MSR_ME, "pseries is unable to change MSR[ME]");
if (!(mfmsr() & MSR_ME)) { /* try to fix it */
enable_mcheck();
}
}
}
is_fetch = false;
asm volatile("lbz %0,0(%1)" : "=r"(tmp) : "r"(addr));
/* KVM does not MCE on access outside partition scope */
report_kfail(host_is_kvm, got_interrupt, "MCE on access to invalid real address");
if (got_interrupt) {
report(mfspr(SPR_DAR) == addr, "MCE sets DAR correctly");
if (cpu_has_power_mce)
report(recorded_regs.msr & (1ULL << 21), "d-side MCE sets SRR1[42]");
got_interrupt = false;
}
is_fetch = true;
asm volatile("mtctr %0 ; bctrl" :: "r"(addr) : "ctr", "lr");
/* KVM does not MCE on access outside partition scope */
report_kfail(host_is_kvm, got_interrupt, "MCE on fetch from invalid real address");
if (got_interrupt) {
report(recorded_regs.nip == addr, "MCE sets SRR0 correctly");
if (cpu_has_power_mce)
report(!(recorded_regs.msr & (1ULL << 21)), "i-side MCE clears SRR1[42]");
got_interrupt = false;
}
if (mmu)
mmu_enable(NULL);
handle_exception(0x200, NULL, NULL);
handle_exception(0x300, NULL, NULL);
handle_exception(0x380, NULL, NULL);
handle_exception(0x400, NULL, NULL);
handle_exception(0x480, NULL, NULL);
report_prefix_pop();
}
static void dside_handler(struct pt_regs *regs, void *data)
{
got_interrupt = true;
memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
regs_advance_insn(regs);
}
static void iside_handler(struct pt_regs *regs, void *data)
{
got_interrupt = true;
memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
regs->nip = regs->link;
}
static void test_dseg_nommu(void)
{
uint64_t msr, tmp;
report_prefix_push("dseg");
/* Some HV start in radix mode and need 0x300 */
handle_exception(0x300, &dside_handler, NULL);
handle_exception(0x380, &dside_handler, NULL);
asm volatile(
" mfmsr %0 \n \
ori %1,%0,%2 \n \
mtmsrd %1 \n \
lbz %1,0(0) \n \
mtmsrd %0 "
: "=r"(msr), "=r"(tmp) : "i"(MSR_DR): "memory");
report(got_interrupt, "interrupt on NULL dereference");
got_interrupt = false;
handle_exception(0x300, NULL, NULL);
handle_exception(0x380, NULL, NULL);
report_prefix_pop();
}
static void test_mmu(void)
{
uint64_t tmp, addr;
phys_addr_t base, top;
if (!mmu_enabled()) {
test_dseg_nommu();
return;
}
phys_alloc_get_unused(&base, &top);
report_prefix_push("dsi");
addr = top + PAGE_SIZE;
handle_exception(0x300, &dside_handler, NULL);
asm volatile("lbz %0,0(%1)" : "=r"(tmp) : "r"(addr));
report(got_interrupt, "dsi on out of range dereference");
report(mfspr(SPR_DAR) == addr, "DAR set correctly");
report(mfspr(SPR_DSISR) & (1ULL << 30), "DSISR set correctly");
got_interrupt = false;
handle_exception(0x300, NULL, NULL);
report_prefix_pop();
report_prefix_push("dseg");
addr = -4ULL;
handle_exception(0x380, &dside_handler, NULL);
asm volatile("lbz %0,0(%1)" : "=r"(tmp) : "r"(addr));
report(got_interrupt, "dseg on out of range dereference");
report(mfspr(SPR_DAR) == addr, "DAR set correctly");
got_interrupt = false;
handle_exception(0x380, NULL, NULL);
report_prefix_pop();
report_prefix_push("isi");
addr = top + PAGE_SIZE;
handle_exception(0x400, &iside_handler, NULL);
asm volatile("mtctr %0 ; bctrl" :: "r"(addr) : "ctr", "lr");
report(got_interrupt, "isi on out of range fetch");
report(recorded_regs.nip == addr, "SRR0 set correctly");
report(recorded_regs.msr & (1ULL << 30), "SRR1 set correctly");
got_interrupt = false;
handle_exception(0x400, NULL, NULL);
report_prefix_pop();
report_prefix_push("iseg");
addr = -4ULL;
handle_exception(0x480, &iside_handler, NULL);
asm volatile("mtctr %0 ; bctrl" :: "r"(addr) : "ctr", "lr");
report(got_interrupt, "isi on out of range fetch");
report(recorded_regs.nip == addr, "SRR0 set correctly");
got_interrupt = false;
handle_exception(0x480, NULL, NULL);
report_prefix_pop();
}
static void dec_handler(struct pt_regs *regs, void *data)
{
got_interrupt = true;
memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
regs->msr &= ~MSR_EE;
}
static void test_dec(void)
{
uint64_t msr;
uint64_t tb;
report_prefix_push("decrementer");
handle_exception(0x900, &dec_handler, NULL);
asm volatile(
" mtdec %1 \n \
mfmsr %0 \n \
ori %0,%0,%2 \n \
mtmsrd %0,1 "
: "=r"(msr) : "r"(10000), "i"(MSR_EE): "memory");
tb = get_tb();
while (!got_interrupt) {
if (get_tb() - tb > tb_hz * 5)
break; /* timeout 5s */
}
report(got_interrupt, "interrupt on decrementer underflow");
got_interrupt = false;
handle_exception(0x900, NULL, NULL);
if (!machine_is_powernv())
goto done; /* Skip HV tests */
handle_exception(0x980, &dec_handler, NULL);
mtspr(SPR_LPCR, mfspr(SPR_LPCR) | LPCR_HDICE);
asm volatile(
" mtspr 0x136,%1 \n \
mtdec %3 \n \
mfmsr %0 \n \
ori %0,%0,%2 \n \
mtmsrd %0,1 "
: "=r"(msr) : "r"(10000), "i"(MSR_EE), "r"(0x7fffffff): "memory");
tb = get_tb();
while (!got_interrupt) {
if (get_tb() - tb > tb_hz * 5)
break; /* timeout 5s */
}
mtspr(SPR_LPCR, mfspr(SPR_LPCR) & ~LPCR_HDICE);
report(got_interrupt, "interrupt on hdecrementer underflow");
got_interrupt = false;
handle_exception(0x980, NULL, NULL);
done:
report_prefix_pop();
}
static volatile uint64_t recorded_heir;
static void heai_handler(struct pt_regs *regs, void *data)
{
got_interrupt = true;
memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
regs_advance_insn(regs);
if (cpu_has_heai)
recorded_heir = mfspr(SPR_HEIR);
}
static void program_handler(struct pt_regs *regs, void *data)
{
got_interrupt = true;
memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
regs_advance_insn(regs);
}
/*
* This tests invalid instruction handling. powernv (HV) should take an
* HEAI interrupt with the HEIR SPR set to the instruction image. pseries
* (guest) should take a program interrupt. CPUs which support prefix
* should report prefix instruction in (H)SRR1[34].
*/
static void test_illegal(void)
{
report_prefix_push("illegal instruction");
if (machine_is_powernv()) {
handle_exception(0xe40, &heai_handler, NULL);
} else {
handle_exception(0x700, &program_handler, NULL);
}
asm volatile(".long 0x12345678" ::: "memory");
report(got_interrupt, "interrupt on invalid instruction");
got_interrupt = false;
if (cpu_has_heai)
report(recorded_heir == 0x12345678, "HEIR: 0x%08lx", recorded_heir);
report(!regs_is_prefix(&recorded_regs), "(H)SRR1 prefix bit clear");
if (cpu_has_prefix) {
asm volatile(".balign 8 ; .long 0x04000123; .long 0x00badc0d");
report(got_interrupt, "interrupt on invalid prefix instruction");
got_interrupt = false;
if (cpu_has_heai)
report(recorded_heir == 0x0400012300badc0d, "HEIR: 0x%08lx", recorded_heir);
report(regs_is_prefix(&recorded_regs), "(H)SRR1 prefix bit set");
}
handle_exception(0xe40, NULL, NULL);
handle_exception(0x700, NULL, NULL);
report_prefix_pop();
}
static void dec_ignore_handler(struct pt_regs *regs, void *data)
{
mtspr(SPR_DEC, 0x7fffffff);
}
static void test_privileged(void)
{
unsigned long msr;
if (!mmu_enabled())
return;
report_prefix_push("privileged instruction");
handle_exception(0x700, &program_handler, NULL);
handle_exception(0x900, &dec_ignore_handler, NULL);
enter_usermode();
asm volatile("mfmsr %0" : "=r"(msr) :: "memory");
exit_usermode();
report(got_interrupt, "interrupt on privileged instruction");
got_interrupt = false;
handle_exception(0x900, NULL, NULL);
handle_exception(0x700, NULL, NULL);
report_prefix_pop();
}
static void sc_handler(struct pt_regs *regs, void *data)
{
got_interrupt = true;
memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
}
static void test_sc(void)
{
report_prefix_push("syscall");
handle_exception(0xc00, &sc_handler, NULL);
asm volatile("sc 0" ::: "memory");
report(got_interrupt, "interrupt on sc 0 instruction");
got_interrupt = false;
if (cpu_has_sc_lev)
report(((recorded_regs.msr >> 20) & 0x3) == 0, "SRR1 set LEV=0");
if (machine_is_powernv()) {
asm volatile("sc 1" ::: "memory");
report(got_interrupt, "interrupt on sc 1 instruction");
got_interrupt = false;
if (cpu_has_sc_lev)
report(((recorded_regs.msr >> 20) & 0x3) == 1, "SRR1 set LEV=1");
}
handle_exception(0xc00, NULL, NULL);
report_prefix_pop();
}
static void trace_handler(struct pt_regs *regs, void *data)
{
got_interrupt = true;
memcpy((void *)&recorded_regs, regs, sizeof(struct pt_regs));
regs->msr &= ~(MSR_SE | MSR_BE);
}
static void program_trace_handler(struct pt_regs *regs, void *data)
{
regs->msr &= ~(MSR_SE | MSR_BE);
regs->nip += 4;
}
extern char trace_insn[];
extern char trace_insn2[];
extern char trace_insn3[];
extern char trace_rfid[];
static void test_trace(void)
{
unsigned long msr;
report_prefix_push("trace");
handle_exception(0xd00, &trace_handler, NULL);
msr = mfmsr() | MSR_SE;
asm volatile(
" mtmsr %0 \n"
".global trace_insn \n"
"trace_insn: \n"
" nop \n"
: : "r"(msr) : "memory");
report(got_interrupt, "interrupt on single step");
got_interrupt = false;
report(recorded_regs.nip == (unsigned long)trace_insn + 4,
"single step interrupt at the correct address");
if (cpu_has_siar)
report(mfspr(SPR_SIAR) == (unsigned long)trace_insn,
"single step recorded SIAR at the correct address");
msr = mfmsr() | MSR_SE;
asm volatile(
" mtmsr %0 \n"
".global trace_insn2 \n"
"trace_insn2: \n"
" b 1f \n"
" nop \n"
"1: \n"
: : "r"(msr) : "memory");
report(got_interrupt, "interrupt on single step branch");
got_interrupt = false;
report(recorded_regs.nip == (unsigned long)trace_insn2 + 8,
"single step interrupt at the correct address");
if (cpu_has_siar)
report(mfspr(SPR_SIAR) == (unsigned long)trace_insn2,
"single step recorded SIAR at the correct address");
msr = mfmsr() | MSR_BE;
asm volatile(
" mtmsr %0 \n"
".global trace_insn3 \n"
"trace_insn3: \n"
" nop \n"
" b 1f \n"
" nop \n"
"1: \n"
: : "r"(msr) : "memory");
report(got_interrupt, "interrupt on branch trace");
got_interrupt = false;
report(recorded_regs.nip == (unsigned long)trace_insn3 + 12,
"branch trace interrupt at the correct address");
if (cpu_has_siar)
report(mfspr(SPR_SIAR) == (unsigned long)trace_insn3 + 4,
"branch trace recorded SIAR at the correct address");
handle_exception(0x700, &program_trace_handler, NULL);
msr = mfmsr() | MSR_SE;
asm volatile(
" mtmsr %0 \n"
" trap \n"
: : "r"(msr) : "memory");
report(!got_interrupt, "no interrupt on single step trap");
got_interrupt = false;
handle_exception(0x700, NULL, NULL);
msr = mfmsr() | MSR_SE;
mtspr(SPR_SRR0, (unsigned long)trace_rfid);
mtspr(SPR_SRR1, mfmsr());
asm volatile(
" mtmsr %0 \n"
" rfid \n"
".global trace_rfid \n"
"trace_rfid: \n"
: : "r"(msr) : "memory");
report(!got_interrupt, "no interrupt on single step rfid");
got_interrupt = false;
handle_exception(0xd00, NULL, NULL);
report_prefix_pop();
}
int main(int argc, char **argv)
{
report_prefix_push("interrupts");
if (vm_available())
setup_vm();
if (cpu_has_power_mce)
test_mce();
test_mmu();
test_illegal();
test_privileged();
test_dec();
test_sc();
test_trace();
report_prefix_pop();
return report_summary();
}