]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
LoongArch: KVM: Add PCHPIC read and write functions
authorXianglai Li <lixianglai@loongson.cn>
Wed, 13 Nov 2024 08:18:27 +0000 (16:18 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Wed, 13 Nov 2024 08:18:27 +0000 (16:18 +0800)
Add implementation of IPI interrupt controller's address space read and
write function simulation.

Implement interrupt injection interface under loongarch.

Signed-off-by: Tianrui Zhao <zhaotianrui@loongson.cn>
Signed-off-by: Xianglai Li <lixianglai@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
arch/loongarch/include/asm/kvm_host.h
arch/loongarch/include/asm/kvm_pch_pic.h
arch/loongarch/kvm/intc/pch_pic.c

index a6b82c8ab7bc92fd08261a45e7471edabf21268c..d1f75b8541074150032439c9f1e449d62205fcc8 100644 (file)
@@ -51,6 +51,8 @@ struct kvm_vm_stat {
        u64 ipi_write_exits;
        u64 eiointc_read_exits;
        u64 eiointc_write_exits;
+       u64 pch_pic_read_exits;
+       u64 pch_pic_write_exits;
 };
 
 struct kvm_vcpu_stat {
index 914be4fd35e97b8df08f543b37ce77f8cbbc748c..e6df6a4c1c7052302e384d4c0926796cb236db49 100644 (file)
@@ -8,6 +8,35 @@
 
 #include <kvm/iodev.h>
 
+#define PCH_PIC_SIZE                   0x3e8
+
+#define PCH_PIC_INT_ID_START           0x0
+#define PCH_PIC_INT_ID_END             0x7
+#define PCH_PIC_MASK_START             0x20
+#define PCH_PIC_MASK_END               0x27
+#define PCH_PIC_HTMSI_EN_START         0x40
+#define PCH_PIC_HTMSI_EN_END           0x47
+#define PCH_PIC_EDGE_START             0x60
+#define PCH_PIC_EDGE_END               0x67
+#define PCH_PIC_CLEAR_START            0x80
+#define PCH_PIC_CLEAR_END              0x87
+#define PCH_PIC_AUTO_CTRL0_START       0xc0
+#define PCH_PIC_AUTO_CTRL0_END         0xc7
+#define PCH_PIC_AUTO_CTRL1_START       0xe0
+#define PCH_PIC_AUTO_CTRL1_END         0xe7
+#define PCH_PIC_ROUTE_ENTRY_START      0x100
+#define PCH_PIC_ROUTE_ENTRY_END                0x13f
+#define PCH_PIC_HTMSI_VEC_START                0x200
+#define PCH_PIC_HTMSI_VEC_END          0x23f
+#define PCH_PIC_INT_IRR_START          0x380
+#define PCH_PIC_INT_IRR_END            0x38f
+#define PCH_PIC_INT_ISR_START          0x3a0
+#define PCH_PIC_INT_ISR_END            0x3af
+#define PCH_PIC_POLARITY_START         0x3e0
+#define PCH_PIC_POLARITY_END           0x3e7
+#define PCH_PIC_INT_ID_VAL             0x7000000UL
+#define PCH_PIC_INT_ID_VER             0x1UL
+
 struct loongarch_pch_pic {
        spinlock_t lock;
        struct kvm *kvm;
@@ -27,5 +56,7 @@ struct loongarch_pch_pic {
 };
 
 int kvm_loongarch_register_pch_pic_device(void);
+void pch_pic_set_irq(struct loongarch_pch_pic *s, int irq, int level);
+void pch_msi_set_irq(struct kvm *kvm, int irq, int level);
 
 #endif /* __ASM_KVM_PCH_PIC_H */
index 564b7dcbbbed6e5fd37fdd236c699ccfa6da6da0..1269ab264ac27ce8f577793aa4ee86b018489c42 100644 (file)
 #include <asm/kvm_vcpu.h>
 #include <linux/count_zeros.h>
 
+/* update the isr according to irq level and route irq to eiointc */
+static void pch_pic_update_irq(struct loongarch_pch_pic *s, int irq, int level)
+{
+       u64 mask = BIT(irq);
+
+       /*
+        * set isr and route irq to eiointc and
+        * the route table is in htmsi_vector[]
+        */
+       if (level) {
+               if (mask & s->irr & ~s->mask) {
+                       s->isr |= mask;
+                       irq = s->htmsi_vector[irq];
+                       eiointc_set_irq(s->kvm->arch.eiointc, irq, level);
+               }
+       } else {
+               if (mask & s->isr & ~s->irr) {
+                       s->isr &= ~mask;
+                       irq = s->htmsi_vector[irq];
+                       eiointc_set_irq(s->kvm->arch.eiointc, irq, level);
+               }
+       }
+}
+
+/* update batch irqs, the irq_mask is a bitmap of irqs */
+static void pch_pic_update_batch_irqs(struct loongarch_pch_pic *s, u64 irq_mask, int level)
+{
+       int irq, bits;
+
+       /* find each irq by irqs bitmap and update each irq */
+       bits = sizeof(irq_mask) * 8;
+       irq = find_first_bit((void *)&irq_mask, bits);
+       while (irq < bits) {
+               pch_pic_update_irq(s, irq, level);
+               bitmap_clear((void *)&irq_mask, irq, 1);
+               irq = find_first_bit((void *)&irq_mask, bits);
+       }
+}
+
+/* called when a irq is triggered in pch pic */
+void pch_pic_set_irq(struct loongarch_pch_pic *s, int irq, int level)
+{
+       u64 mask = BIT(irq);
+
+       spin_lock(&s->lock);
+       if (level)
+               s->irr |= mask; /* set irr */
+       else {
+               /*
+                * In edge triggered mode, 0 does not mean to clear irq
+                * The irr register variable is cleared when cpu writes to the
+                * PCH_PIC_CLEAR_START address area
+                */
+               if (s->edge & mask) {
+                       spin_unlock(&s->lock);
+                       return;
+               }
+               s->irr &= ~mask;
+       }
+       pch_pic_update_irq(s, irq, level);
+       spin_unlock(&s->lock);
+}
+
+/* msi irq handler */
+void pch_msi_set_irq(struct kvm *kvm, int irq, int level)
+{
+       eiointc_set_irq(kvm->arch.eiointc, irq, level);
+}
+
+/*
+ * pch pic register is 64-bit, but it is accessed by 32-bit,
+ * so we use high to get whether low or high 32 bits we want
+ * to read.
+ */
+static u32 pch_pic_read_reg(u64 *s, int high)
+{
+       u64 val = *s;
+
+       /* read the high 32 bits when high is 1 */
+       return high ? (u32)(val >> 32) : (u32)val;
+}
+
+/*
+ * pch pic register is 64-bit, but it is accessed by 32-bit,
+ * so we use high to get whether low or high 32 bits we want
+ * to write.
+ */
+static u32 pch_pic_write_reg(u64 *s, int high, u32 v)
+{
+       u64 val = *s, data = v;
+
+       if (high) {
+               /*
+                * Clear val high 32 bits
+                * Write the high 32 bits when the high is 1
+                */
+               *s = (val << 32 >> 32) | (data << 32);
+               val >>= 32;
+       } else
+               /*
+                * Clear val low 32 bits
+                * Write the low 32 bits when the high is 0
+                */
+               *s = (val >> 32 << 32) | v;
+
+       return (u32)val;
+}
+
+static int loongarch_pch_pic_read(struct loongarch_pch_pic *s, gpa_t addr, int len, void *val)
+{
+       int offset, index, ret = 0;
+       u32 data = 0;
+       u64 int_id = 0;
+
+       offset = addr - s->pch_pic_base;
+
+       spin_lock(&s->lock);
+       switch (offset) {
+       case PCH_PIC_INT_ID_START ... PCH_PIC_INT_ID_END:
+               /* int id version */
+               int_id |= (u64)PCH_PIC_INT_ID_VER << 32;
+               /* irq number */
+               int_id |= (u64)31 << (32 + 16);
+               /* int id value */
+               int_id |= PCH_PIC_INT_ID_VAL;
+               *(u64 *)val = int_id;
+               break;
+       case PCH_PIC_MASK_START ... PCH_PIC_MASK_END:
+               offset -= PCH_PIC_MASK_START;
+               index = offset >> 2;
+               /* read mask reg */
+               data = pch_pic_read_reg(&s->mask, index);
+               *(u32 *)val = data;
+               break;
+       case PCH_PIC_HTMSI_EN_START ... PCH_PIC_HTMSI_EN_END:
+               offset -= PCH_PIC_HTMSI_EN_START;
+               index = offset >> 2;
+               /* read htmsi enable reg */
+               data = pch_pic_read_reg(&s->htmsi_en, index);
+               *(u32 *)val = data;
+               break;
+       case PCH_PIC_EDGE_START ... PCH_PIC_EDGE_END:
+               offset -= PCH_PIC_EDGE_START;
+               index = offset >> 2;
+               /* read edge enable reg */
+               data = pch_pic_read_reg(&s->edge, index);
+               *(u32 *)val = data;
+               break;
+       case PCH_PIC_AUTO_CTRL0_START ... PCH_PIC_AUTO_CTRL0_END:
+       case PCH_PIC_AUTO_CTRL1_START ... PCH_PIC_AUTO_CTRL1_END:
+               /* we only use default mode: fixed interrupt distribution mode */
+               *(u32 *)val = 0;
+               break;
+       case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
+               /* only route to int0: eiointc */
+               *(u8 *)val = 1;
+               break;
+       case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
+               offset -= PCH_PIC_HTMSI_VEC_START;
+               /* read htmsi vector */
+               data = s->htmsi_vector[offset];
+               *(u8 *)val = data;
+               break;
+       case PCH_PIC_POLARITY_START ... PCH_PIC_POLARITY_END:
+               /* we only use defalut value 0: high level triggered */
+               *(u32 *)val = 0;
+               break;
+       default:
+               ret = -EINVAL;
+       }
+       spin_unlock(&s->lock);
+
+       return ret;
+}
+
 static int kvm_pch_pic_read(struct kvm_vcpu *vcpu,
                        struct kvm_io_device *dev,
                        gpa_t addr, int len, void *val)
 {
-       return 0;
+       int ret;
+       struct loongarch_pch_pic *s = vcpu->kvm->arch.pch_pic;
+
+       if (!s) {
+               kvm_err("%s: pch pic irqchip not valid!\n", __func__);
+               return -EINVAL;
+       }
+
+       /* statistics of pch pic reading */
+       vcpu->kvm->stat.pch_pic_read_exits++;
+       ret = loongarch_pch_pic_read(s, addr, len, val);
+
+       return ret;
+}
+
+static int loongarch_pch_pic_write(struct loongarch_pch_pic *s, gpa_t addr,
+                                       int len, const void *val)
+{
+       int ret;
+       u32 old, data, offset, index;
+       u64 irq;
+
+       ret = 0;
+       data = *(u32 *)val;
+       offset = addr - s->pch_pic_base;
+
+       spin_lock(&s->lock);
+       switch (offset) {
+       case PCH_PIC_MASK_START ... PCH_PIC_MASK_END:
+               offset -= PCH_PIC_MASK_START;
+               /* get whether high or low 32 bits we want to write */
+               index = offset >> 2;
+               old = pch_pic_write_reg(&s->mask, index, data);
+               /* enable irq when mask value change to 0 */
+               irq = (old & ~data) << (32 * index);
+               pch_pic_update_batch_irqs(s, irq, 1);
+               /* disable irq when mask value change to 1 */
+               irq = (~old & data) << (32 * index);
+               pch_pic_update_batch_irqs(s, irq, 0);
+               break;
+       case PCH_PIC_HTMSI_EN_START ... PCH_PIC_HTMSI_EN_END:
+               offset -= PCH_PIC_HTMSI_EN_START;
+               index = offset >> 2;
+               pch_pic_write_reg(&s->htmsi_en, index, data);
+               break;
+       case PCH_PIC_EDGE_START ... PCH_PIC_EDGE_END:
+               offset -= PCH_PIC_EDGE_START;
+               index = offset >> 2;
+               /* 1: edge triggered, 0: level triggered */
+               pch_pic_write_reg(&s->edge, index, data);
+               break;
+       case PCH_PIC_CLEAR_START ... PCH_PIC_CLEAR_END:
+               offset -= PCH_PIC_CLEAR_START;
+               index = offset >> 2;
+               /* write 1 to clear edge irq */
+               old = pch_pic_read_reg(&s->irr, index);
+               /*
+                * get the irq bitmap which is edge triggered and
+                * already set and to be cleared
+                */
+               irq = old & pch_pic_read_reg(&s->edge, index) & data;
+               /* write irr to the new state where irqs have been cleared */
+               pch_pic_write_reg(&s->irr, index, old & ~irq);
+               /* update cleared irqs */
+               pch_pic_update_batch_irqs(s, irq, 0);
+               break;
+       case PCH_PIC_AUTO_CTRL0_START ... PCH_PIC_AUTO_CTRL0_END:
+               offset -= PCH_PIC_AUTO_CTRL0_START;
+               index = offset >> 2;
+               /* we only use default mode: fixed interrupt distribution mode */
+               pch_pic_write_reg(&s->auto_ctrl0, index, 0);
+               break;
+       case PCH_PIC_AUTO_CTRL1_START ... PCH_PIC_AUTO_CTRL1_END:
+               offset -= PCH_PIC_AUTO_CTRL1_START;
+               index = offset >> 2;
+               /* we only use default mode: fixed interrupt distribution mode */
+               pch_pic_write_reg(&s->auto_ctrl1, index, 0);
+               break;
+       case PCH_PIC_ROUTE_ENTRY_START ... PCH_PIC_ROUTE_ENTRY_END:
+               offset -= PCH_PIC_ROUTE_ENTRY_START;
+               /* only route to int0: eiointc */
+               s->route_entry[offset] = 1;
+               break;
+       case PCH_PIC_HTMSI_VEC_START ... PCH_PIC_HTMSI_VEC_END:
+               /* route table to eiointc */
+               offset -= PCH_PIC_HTMSI_VEC_START;
+               s->htmsi_vector[offset] = (u8)data;
+               break;
+       case PCH_PIC_POLARITY_START ... PCH_PIC_POLARITY_END:
+               offset -= PCH_PIC_POLARITY_START;
+               index = offset >> 2;
+               /* we only use defalut value 0: high level triggered */
+               pch_pic_write_reg(&s->polarity, index, 0);
+               break;
+       default:
+               ret = -EINVAL;
+               break;
+       }
+       spin_unlock(&s->lock);
+
+       return ret;
 }
 
 static int kvm_pch_pic_write(struct kvm_vcpu *vcpu,
                        struct kvm_io_device *dev,
                        gpa_t addr, int len, const void *val)
 {
-       return 0;
+       int ret;
+       struct loongarch_pch_pic *s = vcpu->kvm->arch.pch_pic;
+
+       if (!s) {
+               kvm_err("%s: pch pic irqchip not valid!\n", __func__);
+               return -EINVAL;
+       }
+
+       /* statistics of pch pic writing */
+       vcpu->kvm->stat.pch_pic_write_exits++;
+       ret = loongarch_pch_pic_write(s, addr, len, val);
+
+       return ret;
 }
 
 static const struct kvm_io_device_ops kvm_pch_pic_ops = {