// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Loongson-3 Virtual IPI interrupt support.
*
* Copyright (C) 2019 Loongson Technologies, Inc. All rights reserved.
*
* Authors: Chen Zhu <[email protected]>
* Authors: Huacai Chen <[email protected]>
*/
#include <linux/kvm_host.h>
#include "interrupt.h"
#define IPI_BASE 0x3ff01000ULL
#define CORE0_STATUS_OFF 0x000
#define CORE0_EN_OFF 0x004
#define CORE0_SET_OFF 0x008
#define CORE0_CLEAR_OFF 0x00c
#define CORE0_BUF_20 0x020
#define CORE0_BUF_28 0x028
#define CORE0_BUF_30 0x030
#define CORE0_BUF_38 0x038
#define CORE1_STATUS_OFF 0x100
#define CORE1_EN_OFF 0x104
#define CORE1_SET_OFF 0x108
#define CORE1_CLEAR_OFF 0x10c
#define CORE1_BUF_20 0x120
#define CORE1_BUF_28 0x128
#define CORE1_BUF_30 0x130
#define CORE1_BUF_38 0x138
#define CORE2_STATUS_OFF 0x200
#define CORE2_EN_OFF 0x204
#define CORE2_SET_OFF 0x208
#define CORE2_CLEAR_OFF 0x20c
#define CORE2_BUF_20 0x220
#define CORE2_BUF_28 0x228
#define CORE2_BUF_30 0x230
#define CORE2_BUF_38 0x238
#define CORE3_STATUS_OFF 0x300
#define CORE3_EN_OFF 0x304
#define CORE3_SET_OFF 0x308
#define CORE3_CLEAR_OFF 0x30c
#define CORE3_BUF_20 0x320
#define CORE3_BUF_28 0x328
#define CORE3_BUF_30 0x330
#define CORE3_BUF_38 0x338
static int loongson_vipi_read(struct loongson_kvm_ipi *ipi,
gpa_t addr, int len, void *val)
{
uint32_t core = (addr >> 8) & 3;
uint32_t node = (addr >> 44) & 3;
uint32_t id = core + node * 4;
uint64_t offset = addr & 0xff;
void *pbuf;
struct ipi_state *s = &(ipi->ipistate[id]);
BUG_ON(offset & (len - 1));
switch (offset) {
case CORE0_STATUS_OFF:
*(uint64_t *)val = s->status;
break;
case CORE0_EN_OFF:
*(uint64_t *)val = s->en;
break;
case CORE0_SET_OFF:
*(uint64_t *)val = 0;
break;
case CORE0_CLEAR_OFF:
*(uint64_t *)val = 0;
break;
case CORE0_BUF_20 ... CORE0_BUF_38:
pbuf = (void *)s->buf + (offset - 0x20);
if (len == 8)
*(uint64_t *)val = *(uint64_t *)pbuf;
else /* Assume len == 4 */
*(uint32_t *)val = *(uint32_t *)pbuf;
break;
default:
pr_notice("%s with unknown addr %llx\n", __func__, addr);
break;
}
return 0;
}
static int loongson_vipi_write(struct loongson_kvm_ipi *ipi,
gpa_t addr, int len, const void *val)
{
uint32_t core = (addr >> 8) & 3;
uint32_t node = (addr >> 44) & 3;
uint32_t id = core + node * 4;
uint64_t data, offset = addr & 0xff;
void *pbuf;
struct kvm *kvm = ipi->kvm;
struct kvm_mips_interrupt irq;
struct ipi_state *s = &(ipi->ipistate[id]);
data = *(uint64_t *)val;
BUG_ON(offset & (len - 1));
switch (offset) {
case CORE0_STATUS_OFF:
break;
case CORE0_EN_OFF:
s->en = data;
break;
case CORE0_SET_OFF:
s->status |= data;
irq.cpu = id;
irq.irq = 6;
kvm_vcpu_ioctl_interrupt(kvm_get_vcpu(kvm, id), &irq);
break;
case CORE0_CLEAR_OFF:
s->status &= ~data;
if (!s->status) {
irq.cpu = id;
irq.irq = -6;
kvm_vcpu_ioctl_interrupt(kvm_get_vcpu(kvm, id), &irq);
}
break;
case CORE0_BUF_20 ... CORE0_BUF_38:
pbuf = (void *)s->buf + (offset - 0x20);
if (len == 8)
*(uint64_t *)pbuf = (uint64_t)data;
else /* Assume len == 4 */
*(uint32_t *)pbuf = (uint32_t)data;
break;
default:
pr_notice("%s with unknown addr %llx\n", __func__, addr);
break;
}
return 0;
}
static int kvm_ipi_read(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
gpa_t addr, int len, void *val)
{
unsigned long flags;
struct loongson_kvm_ipi *ipi;
struct ipi_io_device *ipi_device;
ipi_device = container_of(dev, struct ipi_io_device, device);
ipi = ipi_device->ipi;
spin_lock_irqsave(&ipi->lock, flags);
loongson_vipi_read(ipi, addr, len, val);
spin_unlock_irqrestore(&ipi->lock, flags);
return 0;
}
static int kvm_ipi_write(struct kvm_vcpu *vcpu, struct kvm_io_device *dev,
gpa_t addr, int len, const void *val)
{
unsigned long flags;
struct loongson_kvm_ipi *ipi;
struct ipi_io_device *ipi_device;
ipi_device = container_of(dev, struct ipi_io_device, device);
ipi = ipi_device->ipi;
spin_lock_irqsave(&ipi->lock, flags);
loongson_vipi_write(ipi, addr, len, val);
spin_unlock_irqrestore(&ipi->lock, flags);
return 0;
}
static const struct kvm_io_device_ops kvm_ipi_ops = {
.read = kvm_ipi_read,
.write = kvm_ipi_write,
};
void kvm_init_loongson_ipi(struct kvm *kvm)
{
int i;
unsigned long addr;
struct loongson_kvm_ipi *s;
struct kvm_io_device *device;
s = &kvm->arch.ipi;
s->kvm = kvm;
spin_lock_init(&s->lock);
/*
* Initialize IPI device
*/
for (i = 0; i < 4; i++) {
device = &s->dev_ipi[i].device;
kvm_iodevice_init(device, &kvm_ipi_ops);
addr = (((unsigned long)i) << 44) + IPI_BASE;
mutex_lock(&kvm->slots_lock);
kvm_io_bus_register_dev(kvm, KVM_MMIO_BUS, addr, 0x400, device);
mutex_unlock(&kvm->slots_lock);
s->dev_ipi[i].ipi = s;
s->dev_ipi[i].node_id = i;
}
}