mirror of
https://github.com/torvalds/linux.git
synced 2026-05-27 00:22:00 +02:00
With passthrough HW timer, timer interrupt is injected by HW. When
inject emulated CPU interrupt by software such SIP0/SIP1/IPI, HW timer
interrupt may be lost.
Here check whether there is timer tick value inversion before and after
injecting emulated CPU interrupt by software, timer enabling by reading
timer cfg register is skipped. If the timer tick value is detected with
changing, then timer should be enabled. And inject a timer interrupt by
software if there is.
Cc: <stable@vger.kernel.org>
Fixes: f45ad5b8aa ("LoongArch: KVM: Implement vcpu interrupt operations").
Signed-off-by: Bibo Mao <maobibo@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
192 lines
4.4 KiB
C
192 lines
4.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
|
|
*/
|
|
|
|
#include <linux/err.h>
|
|
#include <linux/errno.h>
|
|
#include <asm/kvm_csr.h>
|
|
#include <asm/kvm_vcpu.h>
|
|
#include <asm/kvm_dmsintc.h>
|
|
|
|
static unsigned int priority_to_irq[EXCCODE_INT_NUM] = {
|
|
[INT_TI] = CPU_TIMER,
|
|
[INT_IPI] = CPU_IPI,
|
|
[INT_SWI0] = CPU_SIP0,
|
|
[INT_SWI1] = CPU_SIP1,
|
|
[INT_HWI0] = CPU_IP0,
|
|
[INT_HWI1] = CPU_IP1,
|
|
[INT_HWI2] = CPU_IP2,
|
|
[INT_HWI3] = CPU_IP3,
|
|
[INT_HWI4] = CPU_IP4,
|
|
[INT_HWI5] = CPU_IP5,
|
|
[INT_HWI6] = CPU_IP6,
|
|
[INT_HWI7] = CPU_IP7,
|
|
[INT_AVEC] = CPU_AVEC,
|
|
};
|
|
|
|
static int kvm_irq_deliver(struct kvm_vcpu *vcpu, unsigned int priority)
|
|
{
|
|
unsigned int irq = 0;
|
|
unsigned long old, new;
|
|
|
|
clear_bit(priority, &vcpu->arch.irq_pending);
|
|
if (priority < EXCCODE_INT_NUM)
|
|
irq = priority_to_irq[priority];
|
|
|
|
switch (priority) {
|
|
case INT_AVEC:
|
|
if (!kvm_guest_has_msgint(&vcpu->arch))
|
|
break;
|
|
dmsintc_inject_irq(vcpu);
|
|
fallthrough;
|
|
case INT_TI:
|
|
case INT_IPI:
|
|
case INT_SWI0:
|
|
case INT_SWI1:
|
|
old = kvm_read_hw_gcsr(LOONGARCH_CSR_TVAL);
|
|
set_gcsr_estat(irq);
|
|
new = kvm_read_hw_gcsr(LOONGARCH_CSR_TVAL);
|
|
|
|
/* Inject TI if TVAL inverted */
|
|
if (new > old)
|
|
set_gcsr_estat(CPU_TIMER);
|
|
break;
|
|
|
|
case INT_HWI0 ... INT_HWI7:
|
|
set_csr_gintc(irq);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int kvm_irq_clear(struct kvm_vcpu *vcpu, unsigned int priority)
|
|
{
|
|
unsigned int irq = 0;
|
|
unsigned long old, new;
|
|
|
|
clear_bit(priority, &vcpu->arch.irq_clear);
|
|
if (priority < EXCCODE_INT_NUM)
|
|
irq = priority_to_irq[priority];
|
|
|
|
switch (priority) {
|
|
case INT_AVEC:
|
|
if (!kvm_guest_has_msgint(&vcpu->arch))
|
|
break;
|
|
fallthrough;
|
|
case INT_TI:
|
|
case INT_IPI:
|
|
case INT_SWI0:
|
|
case INT_SWI1:
|
|
old = kvm_read_hw_gcsr(LOONGARCH_CSR_TVAL);
|
|
clear_gcsr_estat(irq);
|
|
new = kvm_read_hw_gcsr(LOONGARCH_CSR_TVAL);
|
|
|
|
/* Inject TI if TVAL inverted */
|
|
if (new > old)
|
|
set_gcsr_estat(CPU_TIMER);
|
|
break;
|
|
|
|
case INT_HWI0 ... INT_HWI7:
|
|
clear_csr_gintc(irq);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void kvm_deliver_intr(struct kvm_vcpu *vcpu)
|
|
{
|
|
unsigned int priority;
|
|
unsigned long *pending = &vcpu->arch.irq_pending;
|
|
unsigned long *pending_clr = &vcpu->arch.irq_clear;
|
|
|
|
for_each_set_bit(priority, pending_clr, EXCCODE_INT_NUM)
|
|
kvm_irq_clear(vcpu, priority);
|
|
|
|
for_each_set_bit(priority, pending, EXCCODE_INT_NUM)
|
|
kvm_irq_deliver(vcpu, priority);
|
|
}
|
|
|
|
int kvm_pending_timer(struct kvm_vcpu *vcpu)
|
|
{
|
|
return test_bit(INT_TI, &vcpu->arch.irq_pending);
|
|
}
|
|
|
|
/*
|
|
* Only support illegal instruction or illegal Address Error exception,
|
|
* Other exceptions are injected by hardware in kvm mode
|
|
*/
|
|
static void _kvm_deliver_exception(struct kvm_vcpu *vcpu,
|
|
unsigned int code, unsigned int subcode)
|
|
{
|
|
unsigned long val, vec_size;
|
|
|
|
/*
|
|
* BADV is added for EXCCODE_ADE exception
|
|
* Use PC register (GVA address) if it is instruction exeception
|
|
* Else use BADV from host side (GPA address) for data exeception
|
|
*/
|
|
if (code == EXCCODE_ADE) {
|
|
if (subcode == EXSUBCODE_ADEF)
|
|
val = vcpu->arch.pc;
|
|
else
|
|
val = vcpu->arch.badv;
|
|
kvm_write_hw_gcsr(LOONGARCH_CSR_BADV, val);
|
|
}
|
|
|
|
/* Set exception instruction */
|
|
kvm_write_hw_gcsr(LOONGARCH_CSR_BADI, vcpu->arch.badi);
|
|
|
|
/*
|
|
* Save CRMD in PRMD
|
|
* Set IRQ disabled and PLV0 with CRMD
|
|
*/
|
|
val = kvm_read_hw_gcsr(LOONGARCH_CSR_CRMD);
|
|
kvm_write_hw_gcsr(LOONGARCH_CSR_PRMD, val);
|
|
val = val & ~(CSR_CRMD_PLV | CSR_CRMD_IE);
|
|
kvm_write_hw_gcsr(LOONGARCH_CSR_CRMD, val);
|
|
|
|
/* Set exception PC address */
|
|
kvm_write_hw_gcsr(LOONGARCH_CSR_ERA, vcpu->arch.pc);
|
|
|
|
/*
|
|
* Set exception code
|
|
* Exception and interrupt can be inject at the same time
|
|
* Hardware will handle exception first and then extern interrupt
|
|
* Exception code is Ecode in ESTAT[16:21]
|
|
* Interrupt code in ESTAT[0:12]
|
|
*/
|
|
val = kvm_read_hw_gcsr(LOONGARCH_CSR_ESTAT);
|
|
val = (val & ~CSR_ESTAT_EXC) | code;
|
|
kvm_write_hw_gcsr(LOONGARCH_CSR_ESTAT, val);
|
|
|
|
/* Calculate expcetion entry address */
|
|
val = kvm_read_hw_gcsr(LOONGARCH_CSR_ECFG);
|
|
vec_size = (val & CSR_ECFG_VS) >> CSR_ECFG_VS_SHIFT;
|
|
if (vec_size)
|
|
vec_size = (1 << vec_size) * 4;
|
|
val = kvm_read_hw_gcsr(LOONGARCH_CSR_EENTRY);
|
|
vcpu->arch.pc = val + code * vec_size;
|
|
}
|
|
|
|
void kvm_deliver_exception(struct kvm_vcpu *vcpu)
|
|
{
|
|
unsigned int code;
|
|
unsigned long *pending = &vcpu->arch.exception_pending;
|
|
|
|
if (*pending) {
|
|
code = __ffs(*pending);
|
|
_kvm_deliver_exception(vcpu, code, vcpu->arch.esubcode);
|
|
*pending = 0;
|
|
vcpu->arch.esubcode = 0;
|
|
}
|
|
}
|