| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (C) 2024 Loongson Technology Corporation Limited |
| */ |
| |
| #include <linux/kvm_host.h> |
| #include <asm/kvm_ipi.h> |
| #include <asm/kvm_vcpu.h> |
| |
| static void ipi_send(struct kvm *kvm, uint64_t data) |
| { |
| int cpu, action; |
| uint32_t status; |
| struct kvm_vcpu *vcpu; |
| struct kvm_interrupt irq; |
| |
| cpu = ((data & 0xffffffff) >> 16) & 0x3ff; |
| vcpu = kvm_get_vcpu_by_cpuid(kvm, cpu); |
| if (unlikely(vcpu == NULL)) { |
| kvm_err("%s: invalid target cpu: %d\n", __func__, cpu); |
| return; |
| } |
| |
| action = BIT(data & 0x1f); |
| spin_lock(&vcpu->arch.ipi_state.lock); |
| status = vcpu->arch.ipi_state.status; |
| vcpu->arch.ipi_state.status |= action; |
| spin_unlock(&vcpu->arch.ipi_state.lock); |
| if (status == 0) { |
| irq.irq = LARCH_INT_IPI; |
| kvm_vcpu_ioctl_interrupt(vcpu, &irq); |
| } |
| } |
| |
| static void ipi_clear(struct kvm_vcpu *vcpu, uint64_t data) |
| { |
| uint32_t status; |
| struct kvm_interrupt irq; |
| |
| spin_lock(&vcpu->arch.ipi_state.lock); |
| vcpu->arch.ipi_state.status &= ~data; |
| status = vcpu->arch.ipi_state.status; |
| spin_unlock(&vcpu->arch.ipi_state.lock); |
| if (status == 0) { |
| irq.irq = -LARCH_INT_IPI; |
| kvm_vcpu_ioctl_interrupt(vcpu, &irq); |
| } |
| } |
| |
| static uint64_t read_mailbox(struct kvm_vcpu *vcpu, int offset, int len) |
| { |
| uint64_t data = 0; |
| |
| spin_lock(&vcpu->arch.ipi_state.lock); |
| data = *(ulong *)((void *)vcpu->arch.ipi_state.buf + (offset - 0x20)); |
| spin_unlock(&vcpu->arch.ipi_state.lock); |
| |
| switch (len) { |
| case 1: |
| return data & 0xff; |
| case 2: |
| return data & 0xffff; |
| case 4: |
| return data & 0xffffffff; |
| case 8: |
| return data; |
| default: |
| kvm_err("%s: unknown data len: %d\n", __func__, len); |
| return 0; |
| } |
| } |
| |
| static void write_mailbox(struct kvm_vcpu *vcpu, int offset, uint64_t data, int len) |
| { |
| void *pbuf; |
| |
| spin_lock(&vcpu->arch.ipi_state.lock); |
| pbuf = (void *)vcpu->arch.ipi_state.buf + (offset - 0x20); |
| |
| switch (len) { |
| case 1: |
| *(unsigned char *)pbuf = (unsigned char)data; |
| break; |
| case 2: |
| *(unsigned short *)pbuf = (unsigned short)data; |
| break; |
| case 4: |
| *(unsigned int *)pbuf = (unsigned int)data; |
| break; |
| case 8: |
| *(unsigned long *)pbuf = (unsigned long)data; |
| break; |
| default: |
| kvm_err("%s: unknown data len: %d\n", __func__, len); |
| } |
| spin_unlock(&vcpu->arch.ipi_state.lock); |
| } |
| |
| static int send_ipi_data(struct kvm_vcpu *vcpu, gpa_t addr, uint64_t data) |
| { |
| int i, idx, ret; |
| uint32_t val = 0, mask = 0; |
| |
| /* |
| * Bit 27-30 is mask for byte writing. |
| * If the mask is 0, we need not to do anything. |
| */ |
| if ((data >> 27) & 0xf) { |
| /* Read the old val */ |
| idx = srcu_read_lock(&vcpu->kvm->srcu); |
| ret = kvm_io_bus_read(vcpu, KVM_IOCSR_BUS, addr, sizeof(val), &val); |
| srcu_read_unlock(&vcpu->kvm->srcu, idx); |
| if (unlikely(ret)) { |
| kvm_err("%s: : read data from addr %llx failed\n", __func__, addr); |
| return ret; |
| } |
| /* Construct the mask by scanning the bit 27-30 */ |
| for (i = 0; i < 4; i++) { |
| if (data & (BIT(27 + i))) |
| mask |= (0xff << (i * 8)); |
| } |
| /* Save the old part of val */ |
| val &= mask; |
| } |
| val |= ((uint32_t)(data >> 32) & ~mask); |
| idx = srcu_read_lock(&vcpu->kvm->srcu); |
| ret = kvm_io_bus_write(vcpu, KVM_IOCSR_BUS, addr, sizeof(val), &val); |
| srcu_read_unlock(&vcpu->kvm->srcu, idx); |
| if (unlikely(ret)) |
| kvm_err("%s: : write data to addr %llx failed\n", __func__, addr); |
| |
| return ret; |
| } |
| |
| static int mail_send(struct kvm *kvm, uint64_t data) |
| { |
| int cpu, mailbox, offset; |
| struct kvm_vcpu *vcpu; |
| |
| cpu = ((data & 0xffffffff) >> 16) & 0x3ff; |
| vcpu = kvm_get_vcpu_by_cpuid(kvm, cpu); |
| if (unlikely(vcpu == NULL)) { |
| kvm_err("%s: invalid target cpu: %d\n", __func__, cpu); |
| return -EINVAL; |
| } |
| mailbox = ((data & 0xffffffff) >> 2) & 0x7; |
| offset = IOCSR_IPI_BASE + IOCSR_IPI_BUF_20 + mailbox * 4; |
| |
| return send_ipi_data(vcpu, offset, data); |
| } |
| |
| static int any_send(struct kvm *kvm, uint64_t data) |
| { |
| int cpu, offset; |
| struct kvm_vcpu *vcpu; |
| |
| cpu = ((data & 0xffffffff) >> 16) & 0x3ff; |
| vcpu = kvm_get_vcpu_by_cpuid(kvm, cpu); |
| if (unlikely(vcpu == NULL)) { |
| kvm_err("%s: invalid target cpu: %d\n", __func__, cpu); |
| return -EINVAL; |
| } |
| offset = data & 0xffff; |
| |
| return send_ipi_data(vcpu, offset, data); |
| } |
| |
| static int loongarch_ipi_readl(struct kvm_vcpu *vcpu, gpa_t addr, int len, void *val) |
| { |
| int ret = 0; |
| uint32_t offset; |
| uint64_t res = 0; |
| |
| offset = (uint32_t)(addr & 0x1ff); |
| WARN_ON_ONCE(offset & (len - 1)); |
| |
| switch (offset) { |
| case IOCSR_IPI_STATUS: |
| spin_lock(&vcpu->arch.ipi_state.lock); |
| res = vcpu->arch.ipi_state.status; |
| spin_unlock(&vcpu->arch.ipi_state.lock); |
| break; |
| case IOCSR_IPI_EN: |
| spin_lock(&vcpu->arch.ipi_state.lock); |
| res = vcpu->arch.ipi_state.en; |
| spin_unlock(&vcpu->arch.ipi_state.lock); |
| break; |
| case IOCSR_IPI_SET: |
| res = 0; |
| break; |
| case IOCSR_IPI_CLEAR: |
| res = 0; |
| break; |
| case IOCSR_IPI_BUF_20 ... IOCSR_IPI_BUF_38 + 7: |
| if (offset + len > IOCSR_IPI_BUF_38 + 8) { |
| kvm_err("%s: invalid offset or len: offset = %d, len = %d\n", |
| __func__, offset, len); |
| ret = -EINVAL; |
| break; |
| } |
| res = read_mailbox(vcpu, offset, len); |
| break; |
| default: |
| kvm_err("%s: unknown addr: %llx\n", __func__, addr); |
| ret = -EINVAL; |
| break; |
| } |
| *(uint64_t *)val = res; |
| |
| return ret; |
| } |
| |
| static int loongarch_ipi_writel(struct kvm_vcpu *vcpu, gpa_t addr, int len, const void *val) |
| { |
| int ret = 0; |
| uint64_t data; |
| uint32_t offset; |
| |
| data = *(uint64_t *)val; |
| |
| offset = (uint32_t)(addr & 0x1ff); |
| WARN_ON_ONCE(offset & (len - 1)); |
| |
| switch (offset) { |
| case IOCSR_IPI_STATUS: |
| ret = -EINVAL; |
| break; |
| case IOCSR_IPI_EN: |
| spin_lock(&vcpu->arch.ipi_state.lock); |
| vcpu->arch.ipi_state.en = data; |
| spin_unlock(&vcpu->arch.ipi_state.lock); |
| break; |
| case IOCSR_IPI_SET: |
| ret = -EINVAL; |
| break; |
| case IOCSR_IPI_CLEAR: |
| /* Just clear the status of the current vcpu */ |
| ipi_clear(vcpu, data); |
| break; |
| case IOCSR_IPI_BUF_20 ... IOCSR_IPI_BUF_38 + 7: |
| if (offset + len > IOCSR_IPI_BUF_38 + 8) { |
| kvm_err("%s: invalid offset or len: offset = %d, len = %d\n", |
| __func__, offset, len); |
| ret = -EINVAL; |
| break; |
| } |
| write_mailbox(vcpu, offset, data, len); |
| break; |
| case IOCSR_IPI_SEND: |
| ipi_send(vcpu->kvm, data); |
| break; |
| case IOCSR_MAIL_SEND: |
| ret = mail_send(vcpu->kvm, *(uint64_t *)val); |
| break; |
| case IOCSR_ANY_SEND: |
| ret = any_send(vcpu->kvm, *(uint64_t *)val); |
| break; |
| default: |
| kvm_err("%s: unknown addr: %llx\n", __func__, addr); |
| ret = -EINVAL; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| static int kvm_ipi_read(struct kvm_vcpu *vcpu, |
| struct kvm_io_device *dev, |
| gpa_t addr, int len, void *val) |
| { |
| vcpu->stat.ipi_read_exits++; |
| return loongarch_ipi_readl(vcpu, addr, len, val); |
| } |
| |
| static int kvm_ipi_write(struct kvm_vcpu *vcpu, |
| struct kvm_io_device *dev, |
| gpa_t addr, int len, const void *val) |
| { |
| vcpu->stat.ipi_write_exits++; |
| return loongarch_ipi_writel(vcpu, addr, len, val); |
| } |
| |
| static const struct kvm_io_device_ops kvm_ipi_ops = { |
| .read = kvm_ipi_read, |
| .write = kvm_ipi_write, |
| }; |
| |
| static int kvm_ipi_regs_access(struct kvm_device *dev, |
| struct kvm_device_attr *attr, |
| bool is_write) |
| { |
| int len = 4; |
| int cpu, addr; |
| uint64_t val; |
| void *p = NULL; |
| struct kvm_vcpu *vcpu; |
| |
| cpu = (attr->attr >> 16) & 0x3ff; |
| addr = attr->attr & 0xff; |
| |
| vcpu = kvm_get_vcpu(dev->kvm, cpu); |
| if (unlikely(vcpu == NULL)) { |
| kvm_err("%s: invalid target cpu: %d\n", __func__, cpu); |
| return -EINVAL; |
| } |
| |
| switch (addr) { |
| case IOCSR_IPI_STATUS: |
| p = &vcpu->arch.ipi_state.status; |
| break; |
| case IOCSR_IPI_EN: |
| p = &vcpu->arch.ipi_state.en; |
| break; |
| case IOCSR_IPI_SET: |
| p = &vcpu->arch.ipi_state.set; |
| break; |
| case IOCSR_IPI_CLEAR: |
| p = &vcpu->arch.ipi_state.clear; |
| break; |
| case IOCSR_IPI_BUF_20: |
| p = &vcpu->arch.ipi_state.buf[0]; |
| len = 8; |
| break; |
| case IOCSR_IPI_BUF_28: |
| p = &vcpu->arch.ipi_state.buf[1]; |
| len = 8; |
| break; |
| case IOCSR_IPI_BUF_30: |
| p = &vcpu->arch.ipi_state.buf[2]; |
| len = 8; |
| break; |
| case IOCSR_IPI_BUF_38: |
| p = &vcpu->arch.ipi_state.buf[3]; |
| len = 8; |
| break; |
| default: |
| kvm_err("%s: unknown ipi register, addr = %d\n", __func__, addr); |
| return -EINVAL; |
| } |
| |
| if (is_write) { |
| if (len == 4) { |
| if (get_user(val, (uint32_t __user *)attr->addr)) |
| return -EFAULT; |
| *(uint32_t *)p = (uint32_t)val; |
| } else if (len == 8) { |
| if (get_user(val, (uint64_t __user *)attr->addr)) |
| return -EFAULT; |
| *(uint64_t *)p = val; |
| } |
| } else { |
| if (len == 4) { |
| val = *(uint32_t *)p; |
| return put_user(val, (uint32_t __user *)attr->addr); |
| } else if (len == 8) { |
| val = *(uint64_t *)p; |
| return put_user(val, (uint64_t __user *)attr->addr); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int kvm_ipi_get_attr(struct kvm_device *dev, |
| struct kvm_device_attr *attr) |
| { |
| switch (attr->group) { |
| case KVM_DEV_LOONGARCH_IPI_GRP_REGS: |
| return kvm_ipi_regs_access(dev, attr, false); |
| default: |
| kvm_err("%s: unknown group (%d)\n", __func__, attr->group); |
| return -EINVAL; |
| } |
| } |
| |
| static int kvm_ipi_set_attr(struct kvm_device *dev, |
| struct kvm_device_attr *attr) |
| { |
| switch (attr->group) { |
| case KVM_DEV_LOONGARCH_IPI_GRP_REGS: |
| return kvm_ipi_regs_access(dev, attr, true); |
| default: |
| kvm_err("%s: unknown group (%d)\n", __func__, attr->group); |
| return -EINVAL; |
| } |
| } |
| |
| static int kvm_ipi_create(struct kvm_device *dev, u32 type) |
| { |
| int ret; |
| struct kvm *kvm; |
| struct kvm_io_device *device; |
| struct loongarch_ipi *s; |
| |
| if (!dev) { |
| kvm_err("%s: kvm_device ptr is invalid!\n", __func__); |
| return -EINVAL; |
| } |
| |
| kvm = dev->kvm; |
| if (kvm->arch.ipi) { |
| kvm_err("%s: LoongArch IPI has already been created!\n", __func__); |
| return -EINVAL; |
| } |
| |
| s = kzalloc(sizeof(struct loongarch_ipi), GFP_KERNEL); |
| if (!s) |
| return -ENOMEM; |
| |
| spin_lock_init(&s->lock); |
| s->kvm = kvm; |
| |
| /* |
| * Initialize IOCSR device |
| */ |
| device = &s->device; |
| kvm_iodevice_init(device, &kvm_ipi_ops); |
| mutex_lock(&kvm->slots_lock); |
| ret = kvm_io_bus_register_dev(kvm, KVM_IOCSR_BUS, IOCSR_IPI_BASE, IOCSR_IPI_SIZE, device); |
| mutex_unlock(&kvm->slots_lock); |
| if (ret < 0) { |
| kvm_err("%s: Initialize IOCSR dev failed, ret = %d\n", __func__, ret); |
| goto err; |
| } |
| |
| kvm->arch.ipi = s; |
| return 0; |
| |
| err: |
| kfree(s); |
| return -EFAULT; |
| } |
| |
| static void kvm_ipi_destroy(struct kvm_device *dev) |
| { |
| struct kvm *kvm; |
| struct loongarch_ipi *ipi; |
| |
| if (!dev || !dev->kvm || !dev->kvm->arch.ipi) |
| return; |
| |
| kvm = dev->kvm; |
| ipi = kvm->arch.ipi; |
| kvm_io_bus_unregister_dev(kvm, KVM_IOCSR_BUS, &ipi->device); |
| kfree(ipi); |
| } |
| |
| static struct kvm_device_ops kvm_ipi_dev_ops = { |
| .name = "kvm-loongarch-ipi", |
| .create = kvm_ipi_create, |
| .destroy = kvm_ipi_destroy, |
| .set_attr = kvm_ipi_set_attr, |
| .get_attr = kvm_ipi_get_attr, |
| }; |
| |
| int kvm_loongarch_register_ipi_device(void) |
| { |
| return kvm_register_device_ops(&kvm_ipi_dev_ops, KVM_DEV_TYPE_LOONGARCH_IPI); |
| } |