LoongArch: KVM: Add EXTIOI read and write functions

Upstream: no

Implementation of EXTIOI interrupt controller address
space read and write function simulation.

Signed-off-by: Tianrui Zhao <zhaotianrui@loongson.cn>
Signed-off-by: Xianglai Li <lixianglai@loongson.cn>
This commit is contained in:
Xianglai Li 2024-06-15 18:41:22 +08:00
parent 59537b43c2
commit 2dc138292b
3 changed files with 594 additions and 2 deletions

View File

@ -19,8 +19,25 @@
#define EXTIOI_BASE 0x1400
#define EXTIOI_SIZE 0x900
#define EXTIOI_NODETYPE_START 0xa0
#define EXTIOI_NODETYPE_END 0xbf
#define EXTIOI_IPMAP_START 0xc0
#define EXTIOI_IPMAP_END 0xc7
#define EXTIOI_ENABLE_START 0x200
#define EXTIOI_ENABLE_END 0x21f
#define EXTIOI_BOUNCE_START 0x280
#define EXTIOI_BOUNCE_END 0x29f
#define EXTIOI_ISR_START 0x300
#define EXTIOI_ISR_END 0x31f
#define EXTIOI_COREISR_START 0x400
#define EXTIOI_COREISR_END 0x71f
#define EXTIOI_COREMAP_START 0x800
#define EXTIOI_COREMAP_END 0x8ff
#define LS3A_INTC_IP 8
#define EXTIOI_SW_COREMAP_FLAG (1 << 0)
struct loongarch_extioi {
spinlock_t lock;
struct kvm *kvm;

View File

@ -42,6 +42,8 @@ struct kvm_vm_stat {
u64 hugepages;
u64 ipi_read_exits;
u64 ipi_write_exits;
u64 extioi_read_exits;
u64 extioi_write_exits;
};
struct kvm_vcpu_stat {

View File

@ -7,18 +7,591 @@
#include <asm/kvm_vcpu.h>
#include <linux/count_zeros.h>
#define loongarch_ext_irq_lock(s, flags) spin_lock_irqsave(&s->lock, flags)
#define loongarch_ext_irq_unlock(s, flags) spin_unlock_irqrestore(&s->lock, flags)
static void extioi_update_irq(struct loongarch_extioi *s, int irq, int level)
{
int ipnum, cpu, found, irq_index, irq_mask;
struct kvm_interrupt vcpu_irq;
struct kvm_vcpu *vcpu;
ipnum = s->ipmap.reg_u8[irq / 32];
ipnum = count_trailing_zeros(ipnum);
ipnum = (ipnum >= 0 && ipnum < 4) ? ipnum : 0;
cpu = s->sw_coremap[irq];
vcpu = kvm_get_vcpu(s->kvm, cpu);
irq_index = irq / 32;
/* length of accessing core isr is 4 bytes */
irq_mask = 1 << (irq & 0x1f);
if (level) {
/* if not enable return false */
if (((s->enable.reg_u32[irq_index]) & irq_mask) == 0)
return;
s->coreisr.reg_u32[cpu][irq_index] |= irq_mask;
found = find_first_bit(s->sw_coreisr[cpu][ipnum], EXTIOI_IRQS);
set_bit(irq, s->sw_coreisr[cpu][ipnum]);
} else {
s->coreisr.reg_u32[cpu][irq_index] &= ~irq_mask;
clear_bit(irq, s->sw_coreisr[cpu][ipnum]);
found = find_first_bit(s->sw_coreisr[cpu][ipnum], EXTIOI_IRQS);
}
if (found < EXTIOI_IRQS)
/* other irq is handling, need not update parent irq level */
return;
vcpu_irq.irq = level ? INT_HWI0 + ipnum : -(INT_HWI0 + ipnum);
kvm_vcpu_ioctl_interrupt(vcpu, &vcpu_irq);
}
void extioi_set_irq(struct loongarch_extioi *s, int irq, int level)
{
unsigned long *isr = (unsigned long *)s->isr.reg_u8;
unsigned long flags;
level ? set_bit(irq, isr) : clear_bit(irq, isr);
if (!level)
return;
loongarch_ext_irq_lock(s, flags);
extioi_update_irq(s, irq, level);
loongarch_ext_irq_unlock(s, flags);
}
static inline void extioi_enable_irq(struct kvm_vcpu *vcpu, struct loongarch_extioi *s,
int index, u8 mask, int level)
{
u8 val;
int irq;
val = mask & s->isr.reg_u8[index];
irq = ffs(val);
while (irq != 0) {
/*
* enable bit change from 0 to 1,
* need to update irq by pending bits
*/
extioi_update_irq(s, irq - 1 + index * 8, level);
val &= ~(1 << (irq - 1));
irq = ffs(val);
}
}
static int loongarch_extioi_writeb(struct kvm_vcpu *vcpu,
struct loongarch_extioi *s,
gpa_t addr, int len, const void *val)
{
int index, irq, ret = 0;
u8 data, old_data, cpu;
u8 coreisr, old_coreisr;
gpa_t offset;
data = *(u8 *)val;
offset = addr - EXTIOI_BASE;
switch (offset) {
case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END:
index = (offset - EXTIOI_NODETYPE_START);
s->nodetype.reg_u8[index] = data;
break;
case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END:
/*
* ipmap cannot be set at runtime, can be set only at the beginning
* of intr driver, need not update upper irq level
*/
index = (offset - EXTIOI_IPMAP_START);
s->ipmap.reg_u8[index] = data;
break;
case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END:
index = (offset - EXTIOI_ENABLE_START);
old_data = s->enable.reg_u8[index];
s->enable.reg_u8[index] = data;
/*
* 1: enable irq.
* update irq when isr is set.
*/
data = s->enable.reg_u8[index] & ~old_data & s->isr.reg_u8[index];
extioi_enable_irq(vcpu, s, index, data, 1);
/*
* 0: disable irq.
* update irq when isr is set.
*/
data = ~s->enable.reg_u8[index] & old_data & s->isr.reg_u8[index];
extioi_enable_irq(vcpu, s, index, data, 0);
break;
case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END:
/* do not emulate hw bounced irq routing */
index = offset - EXTIOI_BOUNCE_START;
s->bounce.reg_u8[index] = data;
break;
case EXTIOI_COREISR_START ... EXTIOI_COREISR_END:
/* length of accessing core isr is 8 bytes */
index = (offset - EXTIOI_COREISR_START);
/* using attrs to get current cpu index */
cpu = vcpu->vcpu_id;
coreisr = data;
old_coreisr = s->coreisr.reg_u8[cpu][index];
/* write 1 to clear interrupt */
s->coreisr.reg_u8[cpu][index] = old_coreisr & ~coreisr;
coreisr &= old_coreisr;
irq = ffs(coreisr);
while (irq != 0) {
extioi_update_irq(s, irq - 1 + index * 8, 0);
coreisr &= ~(1 << (irq - 1));
irq = ffs(coreisr);
}
break;
case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END:
irq = offset - EXTIOI_COREMAP_START;
index = irq;
s->coremap.reg_u8[index] = data;
cpu = data & 0xff;
cpu = ffs(cpu) - 1;
cpu = (cpu >= 4) ? 0 : cpu;
if (s->sw_coremap[irq] == cpu)
break;
if (test_bit(irq, (unsigned long *)s->isr.reg_u8)) {
/*
* lower irq at old cpu and raise irq at new cpu
*/
extioi_update_irq(s, irq, 0);
s->sw_coremap[irq] = cpu;
extioi_update_irq(s, irq, 1);
} else
s->sw_coremap[irq] = cpu;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int loongarch_extioi_writew(struct kvm_vcpu *vcpu,
struct loongarch_extioi *s,
gpa_t addr, int len, const void *val)
{
int i, index, irq, ret = 0;
u8 cpu;
u32 data, old_data;
u32 coreisr, old_coreisr;
gpa_t offset;
data = *(u32 *)val;
offset = addr - EXTIOI_BASE;
switch (offset) {
case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END:
index = (offset - EXTIOI_NODETYPE_START) >> 2;
s->nodetype.reg_u32[index] = data;
break;
case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END:
/*
* ipmap cannot be set at runtime, can be set only at the beginning
* of intr driver, need not update upper irq level
*/
index = (offset - EXTIOI_IPMAP_START) >> 2;
s->ipmap.reg_u32[index] = data;
break;
case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END:
index = (offset - EXTIOI_ENABLE_START) >> 2;
old_data = s->enable.reg_u32[index];
s->enable.reg_u32[index] = data;
/*
* 1: enable irq.
* update irq when isr is set.
*/
data = s->enable.reg_u32[index] & ~old_data & s->isr.reg_u32[index];
index = index << 2;
for (i = 0; i < sizeof(data); i++) {
u8 mask = (data >> (i * 8)) & 0xff;
extioi_enable_irq(vcpu, s, index + i, mask, 1);
}
/*
* 0: disable irq.
* update irq when isr is set.
*/
data = ~s->enable.reg_u32[index] & old_data & s->isr.reg_u32[index];
for (i = 0; i < sizeof(data); i++) {
u8 mask = (data >> (i * 8)) & 0xff;
extioi_enable_irq(vcpu, s, index, mask, 0);
}
break;
case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END:
/* do not emulate hw bounced irq routing */
index = (offset - EXTIOI_BOUNCE_START) >> 2;
s->bounce.reg_u32[index] = data;
break;
case EXTIOI_COREISR_START ... EXTIOI_COREISR_END:
/* length of accessing core isr is 8 bytes */
index = (offset - EXTIOI_COREISR_START) >> 2;
/* using attrs to get current cpu index */
cpu = vcpu->vcpu_id;
coreisr = data;
old_coreisr = s->coreisr.reg_u32[cpu][index];
/* write 1 to clear interrupt */
s->coreisr.reg_u32[cpu][index] = old_coreisr & ~coreisr;
coreisr &= old_coreisr;
irq = ffs(coreisr);
while (irq != 0) {
extioi_update_irq(s, irq - 1 + index * 32, 0);
coreisr &= ~(1 << (irq - 1));
irq = ffs(coreisr);
}
break;
case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END:
irq = offset - EXTIOI_COREMAP_START;
index = irq >> 2;
s->coremap.reg_u32[index] = data;
for (i = 0; i < sizeof(data); i++) {
cpu = data & 0xff;
cpu = ffs(cpu) - 1;
cpu = (cpu >= 4) ? 0 : cpu;
data = data >> 8;
if (s->sw_coremap[irq + i] == cpu)
continue;
if (test_bit(irq, (unsigned long *)s->isr.reg_u8)) {
/*
* lower irq at old cpu and raise irq at new cpu
*/
extioi_update_irq(s, irq + i, 0);
s->sw_coremap[irq + i] = cpu;
extioi_update_irq(s, irq + i, 1);
} else
s->sw_coremap[irq + i] = cpu;
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int loongarch_extioi_writel(struct kvm_vcpu *vcpu,
struct loongarch_extioi *s,
gpa_t addr, int len, const void *val)
{
int i, index, irq, bits, ret = 0;
u8 cpu;
u64 data, old_data;
u64 coreisr, old_coreisr;
gpa_t offset;
data = *(u64 *)val;
offset = addr - EXTIOI_BASE;
switch (offset) {
case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END:
index = (offset - EXTIOI_NODETYPE_START) >> 3;
s->nodetype.reg_u64[index] = data;
break;
case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END:
/*
* ipmap cannot be set at runtime, can be set only at the beginning
* of intr driver, need not update upper irq level
*/
index = (offset - EXTIOI_IPMAP_START) >> 3;
s->ipmap.reg_u64 = data;
break;
case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END:
index = (offset - EXTIOI_ENABLE_START) >> 3;
old_data = s->enable.reg_u64[index];
s->enable.reg_u64[index] = data;
/*
* 1: enable irq.
* update irq when isr is set.
*/
data = s->enable.reg_u64[index] & ~old_data & s->isr.reg_u64[index];
index = index << 3;
for (i = 0; i < sizeof(data); i++) {
u8 mask = (data >> (i * 8)) & 0xff;
extioi_enable_irq(vcpu, s, index + i, mask, 1);
}
/*
* 0: disable irq.
* update irq when isr is set.
*/
data = ~s->enable.reg_u64[index] & old_data & s->isr.reg_u64[index];
for (i = 0; i < sizeof(data); i++) {
u8 mask = (data >> (i * 8)) & 0xff;
extioi_enable_irq(vcpu, s, index, mask, 0);
}
break;
case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END:
/* do not emulate hw bounced irq routing */
index = (offset - EXTIOI_BOUNCE_START) >> 3;
s->bounce.reg_u64[index] = data;
break;
case EXTIOI_COREISR_START ... EXTIOI_COREISR_END:
/* length of accessing core isr is 8 bytes */
index = (offset - EXTIOI_COREISR_START) >> 3;
/* using attrs to get current cpu index */
cpu = vcpu->vcpu_id;
coreisr = data;
old_coreisr = s->coreisr.reg_u64[cpu][index];
/* write 1 to clear interrupt */
s->coreisr.reg_u64[cpu][index] = old_coreisr & ~coreisr;
coreisr &= old_coreisr;
bits = sizeof(u64) * 8;
irq = find_first_bit((void *)&coreisr, bits);
while (irq < bits) {
extioi_update_irq(s, irq + index * bits, 0);
bitmap_clear((void *)&coreisr, irq, 1);
irq = find_first_bit((void *)&coreisr, bits);
}
break;
case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END:
irq = offset - EXTIOI_COREMAP_START;
index = irq >> 3;
s->coremap.reg_u64[index] = data;
for (i = 0; i < sizeof(data); i++) {
cpu = data & 0xff;
cpu = ffs(cpu) - 1;
cpu = (cpu >= 4) ? 0 : cpu;
data = data >> 8;
if (s->sw_coremap[irq + i] == cpu)
continue;
if (test_bit(irq, (unsigned long *)s->isr.reg_u8)) {
/*
* lower irq at old cpu and raise irq at new cpu
*/
extioi_update_irq(s, irq + i, 0);
s->sw_coremap[irq + i] = cpu;
extioi_update_irq(s, irq + i, 1);
} else
s->sw_coremap[irq + i] = cpu;
}
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int kvm_loongarch_extioi_write(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, const void *val)
{
return 0;
int ret;
struct loongarch_extioi *extioi = vcpu->kvm->arch.extioi;
unsigned long flags;
if (!extioi) {
kvm_err("%s: extioi irqchip not valid!\n", __func__);
return -EINVAL;
}
vcpu->kvm->stat.extioi_write_exits++;
loongarch_ext_irq_lock(extioi, flags);
switch (len) {
case 1:
ret = loongarch_extioi_writeb(vcpu, extioi, addr, len, val);
break;
case 4:
ret = loongarch_extioi_writew(vcpu, extioi, addr, len, val);
break;
case 8:
ret = loongarch_extioi_writel(vcpu, extioi, addr, len, val);
break;
default:
WARN_ONCE(1, "%s: Abnormal address access:addr 0x%llx,size %d\n",
__func__, addr, len);
}
loongarch_ext_irq_unlock(extioi, flags);
return ret;
}
static int loongarch_extioi_readb(struct kvm_vcpu *vcpu, struct loongarch_extioi *s,
gpa_t addr, int len, void *val)
{
int index, ret = 0;
gpa_t offset;
u64 data;
offset = addr - EXTIOI_BASE;
switch (offset) {
case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END:
index = offset - EXTIOI_NODETYPE_START;
data = s->nodetype.reg_u8[index];
break;
case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END:
index = offset - EXTIOI_IPMAP_START;
data = s->ipmap.reg_u8[index];
break;
case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END:
index = offset - EXTIOI_ENABLE_START;
data = s->enable.reg_u8[index];
break;
case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END:
index = offset - EXTIOI_BOUNCE_START;
data = s->bounce.reg_u8[index];
break;
case EXTIOI_COREISR_START ... EXTIOI_COREISR_END:
/* length of accessing core isr is 8 bytes */
index = offset - EXTIOI_COREISR_START;
data = s->coreisr.reg_u8[vcpu->vcpu_id][index];
break;
case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END:
index = offset - EXTIOI_COREMAP_START;
data = s->coremap.reg_u8[index];
break;
default:
ret = -EINVAL;
break;
}
*(u8 *)val = data;
return ret;
}
static int loongarch_extioi_readw(struct kvm_vcpu *vcpu, struct loongarch_extioi *s,
gpa_t addr, int len, void *val)
{
int index, ret = 0;
gpa_t offset;
u64 data;
offset = addr - EXTIOI_BASE;
switch (offset) {
case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END:
index = (offset - EXTIOI_NODETYPE_START) >> 2;
data = s->nodetype.reg_u32[index];
break;
case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END:
index = (offset - EXTIOI_IPMAP_START) >> 2;
data = s->ipmap.reg_u32[index];
break;
case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END:
index = (offset - EXTIOI_ENABLE_START) >> 2;
data = s->enable.reg_u32[index];
break;
case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END:
index = (offset - EXTIOI_BOUNCE_START) >> 2;
data = s->bounce.reg_u32[index];
break;
case EXTIOI_COREISR_START ... EXTIOI_COREISR_END:
/* length of accessing core isr is 8 bytes */
index = (offset - EXTIOI_COREISR_START) >> 2;
data = s->coreisr.reg_u32[vcpu->vcpu_id][index];
break;
case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END:
index = (offset - EXTIOI_COREMAP_START) >> 2;
data = s->coremap.reg_u32[index];
break;
default:
ret = -EINVAL;
break;
}
*(u32 *)val = data;
return ret;
}
static int loongarch_extioi_readl(struct kvm_vcpu *vcpu, struct loongarch_extioi *s,
gpa_t addr, int len, void *val)
{
int index, ret = 0;
gpa_t offset;
u64 data;
offset = addr - EXTIOI_BASE;
switch (offset) {
case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END:
index = (offset - EXTIOI_NODETYPE_START) >> 3;
data = s->nodetype.reg_u64[index];
break;
case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END:
index = (offset - EXTIOI_IPMAP_START) >> 3;
data = s->ipmap.reg_u64;
break;
case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END:
index = (offset - EXTIOI_ENABLE_START) >> 3;
data = s->enable.reg_u64[index];
break;
case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END:
index = (offset - EXTIOI_BOUNCE_START) >> 3;
data = s->bounce.reg_u64[index];
break;
case EXTIOI_COREISR_START ... EXTIOI_COREISR_END:
/* length of accessing core isr is 8 bytes */
index = (offset - EXTIOI_COREISR_START) >> 3;
data = s->coreisr.reg_u64[vcpu->vcpu_id][index];
break;
case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END:
index = (offset - EXTIOI_COREMAP_START) >> 3;
data = s->coremap.reg_u64[index];
break;
default:
ret = -EINVAL;
break;
}
*(u64 *)val = data;
return ret;
}
static int kvm_loongarch_extioi_read(struct kvm_vcpu *vcpu,
struct kvm_io_device *dev,
gpa_t addr, int len, void *val)
{
return 0;
int ret;
struct loongarch_extioi *extioi = vcpu->kvm->arch.extioi;
unsigned long flags;
if (!extioi) {
kvm_err("%s: extioi irqchip not valid!\n", __func__);
return -EINVAL;
}
vcpu->kvm->stat.extioi_read_exits++;
loongarch_ext_irq_lock(extioi, flags);
switch (len) {
case 1:
ret = loongarch_extioi_readb(vcpu, extioi, addr, len, val);
break;
case 4:
ret = loongarch_extioi_readw(vcpu, extioi, addr, len, val);
break;
case 8:
ret = loongarch_extioi_readl(vcpu, extioi, addr, len, val);
break;
default:
WARN_ONCE(1, "%s: Abnormal address access:addr 0x%llx,size %d\n",
__func__, addr, len);
}
loongarch_ext_irq_unlock(extioi, flags);
return ret;
}
static const struct kvm_io_device_ops kvm_loongarch_extioi_ops = {