LoongArch: KVM: Implement vcpu interrupt operations

Implement vcpu interrupt operations such as vcpu set irq and vcpu
clear irq, using set_gcsr_estat() to set irq which is parsed by the
irq bitmap.

Reviewed-by: Bibo Mao <maobibo@loongson.cn>
Tested-by: Huacai Chen <chenhuacai@loongson.cn>
Signed-off-by: Tianrui Zhao <zhaotianrui@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
This commit is contained in:
Tianrui Zhao 2023-10-02 10:01:28 +08:00 committed by Huacai Chen
parent 84be4212dc
commit f45ad5b8aa
2 changed files with 221 additions and 0 deletions

View File

@ -0,0 +1,183 @@
// 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>
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,
};
static int kvm_irq_deliver(struct kvm_vcpu *vcpu, unsigned int priority)
{
unsigned int irq = 0;
clear_bit(priority, &vcpu->arch.irq_pending);
if (priority < EXCCODE_INT_NUM)
irq = priority_to_irq[priority];
switch (priority) {
case INT_TI:
case INT_IPI:
case INT_SWI0:
case INT_SWI1:
set_gcsr_estat(irq);
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;
clear_bit(priority, &vcpu->arch.irq_clear);
if (priority < EXCCODE_INT_NUM)
irq = priority_to_irq[priority];
switch (priority) {
case INT_TI:
case INT_IPI:
case INT_SWI0:
case INT_SWI1:
clear_gcsr_estat(irq);
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;
if (!(*pending) && !(*pending_clr))
return;
if (*pending_clr) {
priority = __ffs(*pending_clr);
while (priority <= INT_IPI) {
kvm_irq_clear(vcpu, priority);
priority = find_next_bit(pending_clr,
BITS_PER_BYTE * sizeof(*pending_clr),
priority + 1);
}
}
if (*pending) {
priority = __ffs(*pending);
while (priority <= INT_IPI) {
kvm_irq_deliver(vcpu, priority);
priority = find_next_bit(pending,
BITS_PER_BYTE * sizeof(*pending),
priority + 1);
}
}
}
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;
}
}

View File

@ -458,6 +458,44 @@ void kvm_lose_fpu(struct kvm_vcpu *vcpu)
preempt_enable();
}
int kvm_vcpu_ioctl_interrupt(struct kvm_vcpu *vcpu, struct kvm_interrupt *irq)
{
int intr = (int)irq->irq;
if (intr > 0)
kvm_queue_irq(vcpu, intr);
else if (intr < 0)
kvm_dequeue_irq(vcpu, -intr);
else {
kvm_err("%s: invalid interrupt ioctl %d\n", __func__, irq->irq);
return -EINVAL;
}
kvm_vcpu_kick(vcpu);
return 0;
}
long kvm_arch_vcpu_async_ioctl(struct file *filp,
unsigned int ioctl, unsigned long arg)
{
void __user *argp = (void __user *)arg;
struct kvm_vcpu *vcpu = filp->private_data;
if (ioctl == KVM_INTERRUPT) {
struct kvm_interrupt irq;
if (copy_from_user(&irq, argp, sizeof(irq)))
return -EFAULT;
kvm_debug("[%d] %s: irq: %d\n", vcpu->vcpu_id, __func__, irq.irq);
return kvm_vcpu_ioctl_interrupt(vcpu, &irq);
}
return -ENOIOCTLCMD;
}
int kvm_arch_vcpu_precreate(struct kvm *kvm, unsigned int id)
{
return 0;