linux/arch/mips/kvm/interrupt.c
James Hogan 7801bbe1bd KVM: MIPS/T&E: Implement CP0_EBase register
The CP0_EBase register is a standard feature of MIPS32r2, so we should
always have been implementing it properly. However the register value
was ignored and wasn't exposed to userland.

Fix the emulation of exceptions and interrupts to use the value stored
in guest CP0_EBase, and fix the masks so that the top 3 bits (rather
than the standard 2) are fixed, so that it is always in the guest KSeg0
segment.

Also add CP0_EBASE to the KVM one_reg interface so it can be accessed by
userland, also allowing the CPU number field to be written (which isn't
permitted by the guest).

Signed-off-by: James Hogan <james.hogan@imgtec.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: "Radim Krčmář" <rkrcmar@redhat.com>
Cc: Ralf Baechle <ralf@linux-mips.org>
Cc: linux-mips@linux-mips.org
Cc: kvm@vger.kernel.org
2017-02-03 15:21:30 +00:00

243 lines
5.8 KiB
C

/*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file "COPYING" in the main directory of this archive
* for more details.
*
* KVM/MIPS: Interrupt delivery
*
* Copyright (C) 2012 MIPS Technologies, Inc. All rights reserved.
* Authors: Sanjay Lal <sanjayl@kymasys.com>
*/
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/vmalloc.h>
#include <linux/fs.h>
#include <linux/bootmem.h>
#include <asm/page.h>
#include <asm/cacheflush.h>
#include <linux/kvm_host.h>
#include "interrupt.h"
void kvm_mips_queue_irq(struct kvm_vcpu *vcpu, unsigned int priority)
{
set_bit(priority, &vcpu->arch.pending_exceptions);
}
void kvm_mips_dequeue_irq(struct kvm_vcpu *vcpu, unsigned int priority)
{
clear_bit(priority, &vcpu->arch.pending_exceptions);
}
void kvm_mips_queue_timer_int_cb(struct kvm_vcpu *vcpu)
{
/*
* Cause bits to reflect the pending timer interrupt,
* the EXC code will be set when we are actually
* delivering the interrupt:
*/
kvm_set_c0_guest_cause(vcpu->arch.cop0, (C_IRQ5 | C_TI));
/* Queue up an INT exception for the core */
kvm_mips_queue_irq(vcpu, MIPS_EXC_INT_TIMER);
}
void kvm_mips_dequeue_timer_int_cb(struct kvm_vcpu *vcpu)
{
kvm_clear_c0_guest_cause(vcpu->arch.cop0, (C_IRQ5 | C_TI));
kvm_mips_dequeue_irq(vcpu, MIPS_EXC_INT_TIMER);
}
void kvm_mips_queue_io_int_cb(struct kvm_vcpu *vcpu,
struct kvm_mips_interrupt *irq)
{
int intr = (int)irq->irq;
/*
* Cause bits to reflect the pending IO interrupt,
* the EXC code will be set when we are actually
* delivering the interrupt:
*/
switch (intr) {
case 2:
kvm_set_c0_guest_cause(vcpu->arch.cop0, (C_IRQ0));
/* Queue up an INT exception for the core */
kvm_mips_queue_irq(vcpu, MIPS_EXC_INT_IO);
break;
case 3:
kvm_set_c0_guest_cause(vcpu->arch.cop0, (C_IRQ1));
kvm_mips_queue_irq(vcpu, MIPS_EXC_INT_IPI_1);
break;
case 4:
kvm_set_c0_guest_cause(vcpu->arch.cop0, (C_IRQ2));
kvm_mips_queue_irq(vcpu, MIPS_EXC_INT_IPI_2);
break;
default:
break;
}
}
void kvm_mips_dequeue_io_int_cb(struct kvm_vcpu *vcpu,
struct kvm_mips_interrupt *irq)
{
int intr = (int)irq->irq;
switch (intr) {
case -2:
kvm_clear_c0_guest_cause(vcpu->arch.cop0, (C_IRQ0));
kvm_mips_dequeue_irq(vcpu, MIPS_EXC_INT_IO);
break;
case -3:
kvm_clear_c0_guest_cause(vcpu->arch.cop0, (C_IRQ1));
kvm_mips_dequeue_irq(vcpu, MIPS_EXC_INT_IPI_1);
break;
case -4:
kvm_clear_c0_guest_cause(vcpu->arch.cop0, (C_IRQ2));
kvm_mips_dequeue_irq(vcpu, MIPS_EXC_INT_IPI_2);
break;
default:
break;
}
}
/* Deliver the interrupt of the corresponding priority, if possible. */
int kvm_mips_irq_deliver_cb(struct kvm_vcpu *vcpu, unsigned int priority,
u32 cause)
{
int allowed = 0;
u32 exccode;
struct kvm_vcpu_arch *arch = &vcpu->arch;
struct mips_coproc *cop0 = vcpu->arch.cop0;
switch (priority) {
case MIPS_EXC_INT_TIMER:
if ((kvm_read_c0_guest_status(cop0) & ST0_IE)
&& (!(kvm_read_c0_guest_status(cop0) & (ST0_EXL | ST0_ERL)))
&& (kvm_read_c0_guest_status(cop0) & IE_IRQ5)) {
allowed = 1;
exccode = EXCCODE_INT;
}
break;
case MIPS_EXC_INT_IO:
if ((kvm_read_c0_guest_status(cop0) & ST0_IE)
&& (!(kvm_read_c0_guest_status(cop0) & (ST0_EXL | ST0_ERL)))
&& (kvm_read_c0_guest_status(cop0) & IE_IRQ0)) {
allowed = 1;
exccode = EXCCODE_INT;
}
break;
case MIPS_EXC_INT_IPI_1:
if ((kvm_read_c0_guest_status(cop0) & ST0_IE)
&& (!(kvm_read_c0_guest_status(cop0) & (ST0_EXL | ST0_ERL)))
&& (kvm_read_c0_guest_status(cop0) & IE_IRQ1)) {
allowed = 1;
exccode = EXCCODE_INT;
}
break;
case MIPS_EXC_INT_IPI_2:
if ((kvm_read_c0_guest_status(cop0) & ST0_IE)
&& (!(kvm_read_c0_guest_status(cop0) & (ST0_EXL | ST0_ERL)))
&& (kvm_read_c0_guest_status(cop0) & IE_IRQ2)) {
allowed = 1;
exccode = EXCCODE_INT;
}
break;
default:
break;
}
/* Are we allowed to deliver the interrupt ??? */
if (allowed) {
if ((kvm_read_c0_guest_status(cop0) & ST0_EXL) == 0) {
/* save old pc */
kvm_write_c0_guest_epc(cop0, arch->pc);
kvm_set_c0_guest_status(cop0, ST0_EXL);
if (cause & CAUSEF_BD)
kvm_set_c0_guest_cause(cop0, CAUSEF_BD);
else
kvm_clear_c0_guest_cause(cop0, CAUSEF_BD);
kvm_debug("Delivering INT @ pc %#lx\n", arch->pc);
} else
kvm_err("Trying to deliver interrupt when EXL is already set\n");
kvm_change_c0_guest_cause(cop0, CAUSEF_EXCCODE,
(exccode << CAUSEB_EXCCODE));
/* XXXSL Set PC to the interrupt exception entry point */
arch->pc = kvm_mips_guest_exception_base(vcpu);
if (kvm_read_c0_guest_cause(cop0) & CAUSEF_IV)
arch->pc += 0x200;
else
arch->pc += 0x180;
clear_bit(priority, &vcpu->arch.pending_exceptions);
}
return allowed;
}
int kvm_mips_irq_clear_cb(struct kvm_vcpu *vcpu, unsigned int priority,
u32 cause)
{
return 1;
}
void kvm_mips_deliver_interrupts(struct kvm_vcpu *vcpu, u32 cause)
{
unsigned long *pending = &vcpu->arch.pending_exceptions;
unsigned long *pending_clr = &vcpu->arch.pending_exceptions_clr;
unsigned int priority;
if (!(*pending) && !(*pending_clr))
return;
priority = __ffs(*pending_clr);
while (priority <= MIPS_EXC_MAX) {
if (kvm_mips_callbacks->irq_clear(vcpu, priority, cause)) {
if (!KVM_MIPS_IRQ_CLEAR_ALL_AT_ONCE)
break;
}
priority = find_next_bit(pending_clr,
BITS_PER_BYTE * sizeof(*pending_clr),
priority + 1);
}
priority = __ffs(*pending);
while (priority <= MIPS_EXC_MAX) {
if (kvm_mips_callbacks->irq_deliver(vcpu, priority, cause)) {
if (!KVM_MIPS_IRQ_DELIVER_ALL_AT_ONCE)
break;
}
priority = find_next_bit(pending,
BITS_PER_BYTE * sizeof(*pending),
priority + 1);
}
}
int kvm_mips_pending_timer(struct kvm_vcpu *vcpu)
{
return test_bit(MIPS_EXC_INT_TIMER, &vcpu->arch.pending_exceptions);
}