Commit 89457a71 authored by Xianglai Li's avatar Xianglai Li
Browse files

LoongArch: KVM: Add EXTIOI read and write functions

LoongArch inclusion
category: feature
bugzilla: https://gitee.com/openeuler/kernel/issues/IB8FOC



------------------------------------------

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

Signed-off-by: default avatarTianrui Zhao <zhaotianrui@loongson.cn>
Signed-off-by: default avatarXianglai Li <lixianglai@loongson.cn>
parent 9ac5bce4
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -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;
+2 −0
Original line number Diff line number Diff line
@@ -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 {
+575 −2
Original line number Diff line number Diff line
@@ -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 = {