1229132c3SSong Gao // SPDX-License-Identifier: GPL-2.0 2229132c3SSong Gao /* 3229132c3SSong Gao * Copyright (C) 2025 Loongson Technology Corporation Limited 4229132c3SSong Gao */ 5229132c3SSong Gao 6229132c3SSong Gao #include <linux/kvm_host.h> 7*03de5eecSSong Gao #include <asm/kvm_csr.h> 8229132c3SSong Gao #include <asm/kvm_dmsintc.h> 9229132c3SSong Gao #include <asm/kvm_vcpu.h> 10229132c3SSong Gao 11*03de5eecSSong Gao void dmsintc_inject_irq(struct kvm_vcpu *vcpu) 12*03de5eecSSong Gao { 13*03de5eecSSong Gao unsigned int i; 14*03de5eecSSong Gao unsigned long vector[4], old; 15*03de5eecSSong Gao struct dmsintc_state *ds = &vcpu->arch.dmsintc_state; 16*03de5eecSSong Gao 17*03de5eecSSong Gao if (!ds) 18*03de5eecSSong Gao return; 19*03de5eecSSong Gao 20*03de5eecSSong Gao for (i = 0; i < 4; i++) { 21*03de5eecSSong Gao old = atomic64_read(&(ds->vector_map[i])); 22*03de5eecSSong Gao if (old) 23*03de5eecSSong Gao vector[i] = atomic64_xchg(&(ds->vector_map[i]), 0); 24*03de5eecSSong Gao } 25*03de5eecSSong Gao 26*03de5eecSSong Gao if (vector[0]) { 27*03de5eecSSong Gao old = kvm_read_hw_gcsr(LOONGARCH_CSR_ISR0); 28*03de5eecSSong Gao kvm_write_hw_gcsr(LOONGARCH_CSR_ISR0, vector[0] | old); 29*03de5eecSSong Gao } 30*03de5eecSSong Gao 31*03de5eecSSong Gao if (vector[1]) { 32*03de5eecSSong Gao old = kvm_read_hw_gcsr(LOONGARCH_CSR_ISR1); 33*03de5eecSSong Gao kvm_write_hw_gcsr(LOONGARCH_CSR_ISR1, vector[1] | old); 34*03de5eecSSong Gao } 35*03de5eecSSong Gao 36*03de5eecSSong Gao if (vector[2]) { 37*03de5eecSSong Gao old = kvm_read_hw_gcsr(LOONGARCH_CSR_ISR2); 38*03de5eecSSong Gao kvm_write_hw_gcsr(LOONGARCH_CSR_ISR2, vector[2] | old); 39*03de5eecSSong Gao } 40*03de5eecSSong Gao 41*03de5eecSSong Gao if (vector[3]) { 42*03de5eecSSong Gao old = kvm_read_hw_gcsr(LOONGARCH_CSR_ISR3); 43*03de5eecSSong Gao kvm_write_hw_gcsr(LOONGARCH_CSR_ISR3, vector[3] | old); 44*03de5eecSSong Gao } 45*03de5eecSSong Gao } 46*03de5eecSSong Gao 47*03de5eecSSong Gao int dmsintc_deliver_msi_to_vcpu(struct kvm *kvm, 48*03de5eecSSong Gao struct kvm_vcpu *vcpu, u32 vector, int level) 49*03de5eecSSong Gao { 50*03de5eecSSong Gao struct kvm_interrupt vcpu_irq; 51*03de5eecSSong Gao struct dmsintc_state *ds = &vcpu->arch.dmsintc_state; 52*03de5eecSSong Gao 53*03de5eecSSong Gao if (!level) 54*03de5eecSSong Gao return 0; 55*03de5eecSSong Gao if (!vcpu || vector >= 256) 56*03de5eecSSong Gao return -EINVAL; 57*03de5eecSSong Gao if (!ds) 58*03de5eecSSong Gao return -ENODEV; 59*03de5eecSSong Gao 60*03de5eecSSong Gao vcpu_irq.irq = INT_AVEC; 61*03de5eecSSong Gao set_bit(vector, (unsigned long *)&ds->vector_map); 62*03de5eecSSong Gao kvm_vcpu_ioctl_interrupt(vcpu, &vcpu_irq); 63*03de5eecSSong Gao kvm_vcpu_kick(vcpu); 64*03de5eecSSong Gao 65*03de5eecSSong Gao return 0; 66*03de5eecSSong Gao } 67*03de5eecSSong Gao 68*03de5eecSSong Gao int dmsintc_set_irq(struct kvm *kvm, u64 addr, int data, int level) 69*03de5eecSSong Gao { 70*03de5eecSSong Gao unsigned int irq, cpu; 71*03de5eecSSong Gao struct kvm_vcpu *vcpu; 72*03de5eecSSong Gao 73*03de5eecSSong Gao irq = (addr >> AVEC_IRQ_SHIFT) & AVEC_IRQ_MASK; 74*03de5eecSSong Gao cpu = (addr >> AVEC_CPU_SHIFT) & kvm->arch.dmsintc->cpu_mask; 75*03de5eecSSong Gao if (cpu >= KVM_MAX_VCPUS) 76*03de5eecSSong Gao return -EINVAL; 77*03de5eecSSong Gao vcpu = kvm_get_vcpu_by_cpuid(kvm, cpu); 78*03de5eecSSong Gao if (!vcpu) 79*03de5eecSSong Gao return -EINVAL; 80*03de5eecSSong Gao 81*03de5eecSSong Gao return dmsintc_deliver_msi_to_vcpu(kvm, vcpu, irq, level); 82*03de5eecSSong Gao } 83*03de5eecSSong Gao 84229132c3SSong Gao static int kvm_dmsintc_ctrl_access(struct kvm_device *dev, 85229132c3SSong Gao struct kvm_device_attr *attr, bool is_write) 86229132c3SSong Gao { 87229132c3SSong Gao int addr = attr->attr; 88229132c3SSong Gao unsigned long cpu_bit, val; 89229132c3SSong Gao void __user *data = (void __user *)attr->addr; 90229132c3SSong Gao struct loongarch_dmsintc *s = dev->kvm->arch.dmsintc; 91229132c3SSong Gao 92229132c3SSong Gao switch (addr) { 93229132c3SSong Gao case KVM_DEV_LOONGARCH_DMSINTC_MSG_ADDR_BASE: 94229132c3SSong Gao if (is_write) { 95229132c3SSong Gao if (copy_from_user(&val, data, sizeof(s->msg_addr_base))) 96229132c3SSong Gao return -EFAULT; 97229132c3SSong Gao if (s->msg_addr_base) 98229132c3SSong Gao return -EFAULT; /* Duplicate setting are not allowed. */ 99229132c3SSong Gao if ((val & (BIT(AVEC_CPU_SHIFT) - 1)) != 0) 100229132c3SSong Gao return -EINVAL; 101229132c3SSong Gao s->msg_addr_base = val; 102229132c3SSong Gao cpu_bit = find_first_bit((unsigned long *)&(s->msg_addr_base), 64) - AVEC_CPU_SHIFT; 103229132c3SSong Gao cpu_bit = min(cpu_bit, AVEC_CPU_BIT); 104229132c3SSong Gao s->cpu_mask = GENMASK(cpu_bit - 1, 0) & AVEC_CPU_MASK; 105229132c3SSong Gao } 106229132c3SSong Gao break; 107229132c3SSong Gao case KVM_DEV_LOONGARCH_DMSINTC_MSG_ADDR_SIZE: 108229132c3SSong Gao if (is_write) { 109229132c3SSong Gao if (copy_from_user(&val, data, sizeof(s->msg_addr_size))) 110229132c3SSong Gao return -EFAULT; 111229132c3SSong Gao if (s->msg_addr_size) 112229132c3SSong Gao return -EFAULT; /*Duplicate setting are not allowed. */ 113229132c3SSong Gao s->msg_addr_size = val; 114229132c3SSong Gao } 115229132c3SSong Gao break; 116229132c3SSong Gao default: 117229132c3SSong Gao kvm_err("%s: unknown dmsintc register, addr = %d\n", __func__, addr); 118229132c3SSong Gao return -ENXIO; 119229132c3SSong Gao } 120229132c3SSong Gao 121229132c3SSong Gao return 0; 122229132c3SSong Gao } 123229132c3SSong Gao 124229132c3SSong Gao static int kvm_dmsintc_set_attr(struct kvm_device *dev, 125229132c3SSong Gao struct kvm_device_attr *attr) 126229132c3SSong Gao { 127229132c3SSong Gao switch (attr->group) { 128229132c3SSong Gao case KVM_DEV_LOONGARCH_DMSINTC_GRP_CTRL: 129229132c3SSong Gao return kvm_dmsintc_ctrl_access(dev, attr, true); 130229132c3SSong Gao default: 131229132c3SSong Gao kvm_err("%s: unknown group (%d)\n", __func__, attr->group); 132229132c3SSong Gao return -EINVAL; 133229132c3SSong Gao } 134229132c3SSong Gao } 135229132c3SSong Gao 136229132c3SSong Gao static int kvm_dmsintc_create(struct kvm_device *dev, u32 type) 137229132c3SSong Gao { 138229132c3SSong Gao struct kvm *kvm; 139229132c3SSong Gao struct loongarch_dmsintc *s; 140229132c3SSong Gao 141229132c3SSong Gao if (!dev) { 142229132c3SSong Gao kvm_err("%s: kvm_device ptr is invalid!\n", __func__); 143229132c3SSong Gao return -EINVAL; 144229132c3SSong Gao } 145229132c3SSong Gao 146229132c3SSong Gao kvm = dev->kvm; 147229132c3SSong Gao if (kvm->arch.dmsintc) { 148229132c3SSong Gao kvm_err("%s: LoongArch DMSINTC has already been created!\n", __func__); 149229132c3SSong Gao return -EINVAL; 150229132c3SSong Gao } 151229132c3SSong Gao 152229132c3SSong Gao s = kzalloc(sizeof(struct loongarch_dmsintc), GFP_KERNEL); 153229132c3SSong Gao if (!s) 154229132c3SSong Gao return -ENOMEM; 155229132c3SSong Gao 156229132c3SSong Gao s->kvm = kvm; 157229132c3SSong Gao kvm->arch.dmsintc = s; 158229132c3SSong Gao 159229132c3SSong Gao return 0; 160229132c3SSong Gao } 161229132c3SSong Gao 162229132c3SSong Gao static void kvm_dmsintc_destroy(struct kvm_device *dev) 163229132c3SSong Gao { 164229132c3SSong Gao 165229132c3SSong Gao if (!dev || !dev->kvm || !dev->kvm->arch.dmsintc) 166229132c3SSong Gao return; 167229132c3SSong Gao 168229132c3SSong Gao kfree(dev->kvm->arch.dmsintc); 169229132c3SSong Gao kfree(dev); 170229132c3SSong Gao } 171229132c3SSong Gao 172229132c3SSong Gao static struct kvm_device_ops kvm_dmsintc_dev_ops = { 173229132c3SSong Gao .name = "kvm-loongarch-dmsintc", 174229132c3SSong Gao .create = kvm_dmsintc_create, 175229132c3SSong Gao .destroy = kvm_dmsintc_destroy, 176229132c3SSong Gao .set_attr = kvm_dmsintc_set_attr, 177229132c3SSong Gao }; 178229132c3SSong Gao 179229132c3SSong Gao int kvm_loongarch_register_dmsintc_device(void) 180229132c3SSong Gao { 181229132c3SSong Gao return kvm_register_device_ops(&kvm_dmsintc_dev_ops, KVM_DEV_TYPE_LOONGARCH_DMSINTC); 182229132c3SSong Gao } 183