215 lines
5.0 KiB
C
215 lines
5.0 KiB
C
|
// 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 <zhuchen@loongson.cn>
|
||
|
* Authors: Huacai Chen <chenhc@lemote.com>
|
||
|
*/
|
||
|
|
||
|
#include <linux/kvm_host.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->vcpus[id], &irq);
|
||
|
break;
|
||
|
|
||
|
case CORE0_CLEAR_OFF:
|
||
|
s->status &= ~data;
|
||
|
if (!s->status) {
|
||
|
irq.cpu = id;
|
||
|
irq.irq = -6;
|
||
|
kvm_vcpu_ioctl_interrupt(kvm->vcpus[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;
|
||
|
}
|
||
|
}
|