| /* SPDX-License-Identifier: GPL-2.0-or-later */ | 
 | /* | 
 |  * QEMU Loongson 7A1000 I/O interrupt controller. | 
 |  * | 
 |  * Copyright (C) 2021 Loongson Technology Corporation Limited | 
 |  */ | 
 |  | 
 | #include "qemu/osdep.h" | 
 | #include "qemu/bitops.h" | 
 | #include "qemu/log.h" | 
 | #include "hw/irq.h" | 
 | #include "hw/intc/loongarch_pch_pic.h" | 
 | #include "system/kvm.h" | 
 | #include "trace.h" | 
 | #include "qapi/error.h" | 
 |  | 
 | static void pch_pic_update_irq(LoongArchPICCommonState *s, uint64_t mask, | 
 |                                int level) | 
 | { | 
 |     uint64_t val; | 
 |     int irq; | 
 |  | 
 |     if (level) { | 
 |         val = mask & s->intirr & ~s->int_mask; | 
 |         if (val) { | 
 |             irq = ctz64(val); | 
 |             s->intisr |= MAKE_64BIT_MASK(irq, 1); | 
 |             qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 1); | 
 |         } | 
 |     } else { | 
 |         /* | 
 |          * intirr means requested pending irq | 
 |          * do not clear pending irq for edge-triggered on lowering edge | 
 |          */ | 
 |         val = mask & s->intisr & ~s->intirr; | 
 |         if (val) { | 
 |             irq = ctz64(val); | 
 |             s->intisr &= ~MAKE_64BIT_MASK(irq, 1); | 
 |             qemu_set_irq(s->parent_irq[s->htmsi_vector[irq]], 0); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | static void pch_pic_irq_handler(void *opaque, int irq, int level) | 
 | { | 
 |     LoongArchPICCommonState *s = LOONGARCH_PIC_COMMON(opaque); | 
 |     uint64_t mask = 1ULL << irq; | 
 |  | 
 |     assert(irq < s->irq_num); | 
 |     trace_loongarch_pch_pic_irq_handler(irq, level); | 
 |  | 
 |     if (kvm_irqchip_in_kernel()) { | 
 |         kvm_set_irq(kvm_state, irq, !!level); | 
 |         return; | 
 |     } | 
 |  | 
 |     if (s->intedge & mask) { | 
 |         /* Edge triggered */ | 
 |         if (level) { | 
 |             if ((s->last_intirr & mask) == 0) { | 
 |                 /* marked pending on a rising edge */ | 
 |                 s->intirr |= mask; | 
 |             } | 
 |             s->last_intirr |= mask; | 
 |         } else { | 
 |             s->last_intirr &= ~mask; | 
 |         } | 
 |     } else { | 
 |         /* Level triggered */ | 
 |         if (level) { | 
 |             s->intirr |= mask; | 
 |             s->last_intirr |= mask; | 
 |         } else { | 
 |             s->intirr &= ~mask; | 
 |             s->last_intirr &= ~mask; | 
 |         } | 
 |     } | 
 |     pch_pic_update_irq(s, mask, level); | 
 | } | 
 |  | 
 | static uint64_t pch_pic_read(void *opaque, hwaddr addr, uint64_t field_mask) | 
 | { | 
 |     LoongArchPICCommonState *s = LOONGARCH_PIC_COMMON(opaque); | 
 |     uint64_t val = 0; | 
 |     uint32_t offset; | 
 |  | 
 |     offset = addr & 7; | 
 |     addr -= offset; | 
 |     switch (addr) { | 
 |     case PCH_PIC_INT_ID: | 
 |         val = cpu_to_le64(s->id.data); | 
 |         break; | 
 |     case PCH_PIC_INT_MASK: | 
 |         val = s->int_mask; | 
 |         break; | 
 |     case PCH_PIC_INT_EDGE: | 
 |         val = s->intedge; | 
 |         break; | 
 |     case PCH_PIC_HTMSI_EN: | 
 |         val = s->htmsi_en; | 
 |         break; | 
 |     case PCH_PIC_AUTO_CTRL0: | 
 |     case PCH_PIC_AUTO_CTRL1: | 
 |         /* PCH PIC connect to EXTIOI always, discard auto_ctrl access */ | 
 |         break; | 
 |     case PCH_PIC_INT_STATUS: | 
 |         val = s->intisr & (~s->int_mask); | 
 |         break; | 
 |     case PCH_PIC_INT_POL: | 
 |         val = s->int_polarity; | 
 |         break; | 
 |     case PCH_PIC_HTMSI_VEC ... PCH_PIC_HTMSI_VEC_END: | 
 |         val = *(uint64_t *)(s->htmsi_vector + addr - PCH_PIC_HTMSI_VEC); | 
 |         break; | 
 |     case PCH_PIC_ROUTE_ENTRY ... PCH_PIC_ROUTE_ENTRY_END: | 
 |         val = *(uint64_t *)(s->route_entry + addr - PCH_PIC_ROUTE_ENTRY); | 
 |         break; | 
 |     default: | 
 |         qemu_log_mask(LOG_GUEST_ERROR, | 
 |                       "pch_pic_read: Bad address 0x%"PRIx64"\n", addr); | 
 |         break; | 
 |     } | 
 |  | 
 |     return (val >> (offset * 8)) & field_mask; | 
 | } | 
 |  | 
 | static void pch_pic_write(void *opaque, hwaddr addr, uint64_t value, | 
 |                           uint64_t field_mask) | 
 | { | 
 |     LoongArchPICCommonState *s = LOONGARCH_PIC_COMMON(opaque); | 
 |     uint32_t offset; | 
 |     uint64_t old, mask, data, *ptemp; | 
 |  | 
 |     offset = addr & 7; | 
 |     addr -= offset; | 
 |     mask = field_mask << (offset * 8); | 
 |     data = (value & field_mask) << (offset * 8); | 
 |     switch (addr) { | 
 |     case PCH_PIC_INT_MASK: | 
 |         old = s->int_mask; | 
 |         s->int_mask = (old & ~mask) | data; | 
 |         if (old & ~data) { | 
 |             pch_pic_update_irq(s, old & ~data, 1); | 
 |         } | 
 |  | 
 |         if (~old & data) { | 
 |             pch_pic_update_irq(s, ~old & data, 0); | 
 |         } | 
 |         break; | 
 |     case PCH_PIC_INT_EDGE: | 
 |         s->intedge = (s->intedge & ~mask) | data; | 
 |         break; | 
 |     case PCH_PIC_INT_CLEAR: | 
 |         if (s->intedge & data) { | 
 |             s->intirr &= ~data; | 
 |             pch_pic_update_irq(s, data, 0); | 
 |             s->intisr &= ~data; | 
 |         } | 
 |         break; | 
 |     case PCH_PIC_HTMSI_EN: | 
 |         s->htmsi_en = (s->htmsi_en & ~mask) | data; | 
 |         break; | 
 |     case PCH_PIC_AUTO_CTRL0: | 
 |     case PCH_PIC_AUTO_CTRL1: | 
 |         /* Discard auto_ctrl access */ | 
 |         break; | 
 |     case PCH_PIC_INT_POL: | 
 |         s->int_polarity = (s->int_polarity & ~mask) | data; | 
 |         break; | 
 |     case PCH_PIC_HTMSI_VEC ... PCH_PIC_HTMSI_VEC_END: | 
 |         ptemp = (uint64_t *)(s->htmsi_vector + addr - PCH_PIC_HTMSI_VEC); | 
 |         *ptemp = (*ptemp & ~mask) | data; | 
 |         break; | 
 |     case PCH_PIC_ROUTE_ENTRY ... PCH_PIC_ROUTE_ENTRY_END: | 
 |         ptemp = (uint64_t *)(s->route_entry + addr - PCH_PIC_ROUTE_ENTRY); | 
 |         *ptemp = (*ptemp & ~mask) | data; | 
 |         break; | 
 |     default: | 
 |         qemu_log_mask(LOG_GUEST_ERROR, | 
 |                       "pch_pic_write: Bad address 0x%"PRIx64"\n", addr); | 
 |         break; | 
 |     } | 
 | } | 
 |  | 
 | static uint64_t loongarch_pch_pic_read(void *opaque, hwaddr addr, | 
 |                                        unsigned size) | 
 | { | 
 |     uint64_t val = 0; | 
 |  | 
 |     switch (size) { | 
 |     case 1: | 
 |         val = pch_pic_read(opaque, addr, UCHAR_MAX); | 
 |         break; | 
 |     case 2: | 
 |         val = pch_pic_read(opaque, addr, USHRT_MAX); | 
 |         break; | 
 |     case 4: | 
 |         val = pch_pic_read(opaque, addr, UINT_MAX); | 
 |         break; | 
 |     case 8: | 
 |         val = pch_pic_read(opaque, addr, UINT64_MAX); | 
 |         break; | 
 |     default: | 
 |         qemu_log_mask(LOG_GUEST_ERROR, | 
 |                       "loongarch_pch_pic_read: Bad size %d\n", size); | 
 |         break; | 
 |     } | 
 |  | 
 |     trace_loongarch_pch_pic_read(size, addr, val); | 
 |     return val; | 
 | } | 
 |  | 
 | static void loongarch_pch_pic_write(void *opaque, hwaddr addr, | 
 |                                     uint64_t value, unsigned size) | 
 | { | 
 |     trace_loongarch_pch_pic_write(size, addr, value); | 
 |  | 
 |     switch (size) { | 
 |     case 1: | 
 |         pch_pic_write(opaque, addr, value, UCHAR_MAX); | 
 |         break; | 
 |     case 2: | 
 |         pch_pic_write(opaque, addr, value, USHRT_MAX); | 
 |         break; | 
 |         break; | 
 |     case 4: | 
 |         pch_pic_write(opaque, addr, value, UINT_MAX); | 
 |         break; | 
 |     case 8: | 
 |         pch_pic_write(opaque, addr, value, UINT64_MAX); | 
 |         break; | 
 |     default: | 
 |         qemu_log_mask(LOG_GUEST_ERROR, | 
 |                       "loongarch_pch_pic_write: Bad size %d\n", size); | 
 |         break; | 
 |     } | 
 | } | 
 |  | 
 | static const MemoryRegionOps loongarch_pch_pic_ops = { | 
 |     .read = loongarch_pch_pic_read, | 
 |     .write = loongarch_pch_pic_write, | 
 |     .valid = { | 
 |         .min_access_size = 1, | 
 |         .max_access_size = 8, | 
 |         /* | 
 |          * PCH PIC device would not work correctly if the guest was doing | 
 |          * unaligned access. This might not be a limitation on the real | 
 |          * device but in practice there is no reason for a guest to access | 
 |          * this device unaligned. | 
 |          */ | 
 |         .unaligned = false, | 
 |     }, | 
 |     .impl = { | 
 |         .min_access_size = 1, | 
 |         .max_access_size = 8, | 
 |     }, | 
 |     .endianness = DEVICE_LITTLE_ENDIAN, | 
 | }; | 
 |  | 
 | static void loongarch_pic_reset_hold(Object *obj, ResetType type) | 
 | { | 
 |     LoongarchPICClass *lpc = LOONGARCH_PIC_GET_CLASS(obj); | 
 |  | 
 |     if (lpc->parent_phases.hold) { | 
 |         lpc->parent_phases.hold(obj, type); | 
 |     } | 
 |  | 
 |     if (kvm_irqchip_in_kernel()) { | 
 |         kvm_pic_put(obj, 0); | 
 |     } | 
 | } | 
 |  | 
 | static void loongarch_pic_realize(DeviceState *dev, Error **errp) | 
 | { | 
 |     LoongArchPICCommonState *s = LOONGARCH_PIC_COMMON(dev); | 
 |     LoongarchPICClass *lpc = LOONGARCH_PIC_GET_CLASS(dev); | 
 |     SysBusDevice *sbd = SYS_BUS_DEVICE(dev); | 
 |     Error *local_err = NULL; | 
 |  | 
 |     lpc->parent_realize(dev, &local_err); | 
 |     if (local_err) { | 
 |         error_propagate(errp, local_err); | 
 |         return; | 
 |     } | 
 |  | 
 |     qdev_init_gpio_out(dev, s->parent_irq, s->irq_num); | 
 |     qdev_init_gpio_in(dev, pch_pic_irq_handler, s->irq_num); | 
 |  | 
 |     if (kvm_irqchip_in_kernel()) { | 
 |         kvm_pic_realize(dev, errp); | 
 |     } else { | 
 |         memory_region_init_io(&s->iomem, OBJECT(dev), | 
 |                               &loongarch_pch_pic_ops, | 
 |                               s, TYPE_LOONGARCH_PIC, VIRT_PCH_REG_SIZE); | 
 |         sysbus_init_mmio(sbd, &s->iomem); | 
 |     } | 
 | } | 
 |  | 
 | static int loongarch_pic_pre_save(LoongArchPICCommonState *opaque) | 
 | { | 
 |     if (kvm_irqchip_in_kernel()) { | 
 |         return kvm_pic_get(opaque); | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static int loongarch_pic_post_load(LoongArchPICCommonState *opaque, | 
 |                                    int version_id) | 
 | { | 
 |     if (kvm_irqchip_in_kernel()) { | 
 |         return kvm_pic_put(opaque, version_id); | 
 |     } | 
 |  | 
 |     return 0; | 
 | } | 
 |  | 
 | static void loongarch_pic_class_init(ObjectClass *klass, const void *data) | 
 | { | 
 |     DeviceClass *dc = DEVICE_CLASS(klass); | 
 |     LoongarchPICClass *lpc = LOONGARCH_PIC_CLASS(klass); | 
 |     LoongArchPICCommonClass *lpcc = LOONGARCH_PIC_COMMON_CLASS(klass); | 
 |     ResettableClass *rc = RESETTABLE_CLASS(klass); | 
 |  | 
 |     resettable_class_set_parent_phases(rc, NULL, loongarch_pic_reset_hold, | 
 |                                        NULL, &lpc->parent_phases); | 
 |     device_class_set_parent_realize(dc, loongarch_pic_realize, | 
 |                                     &lpc->parent_realize); | 
 |     lpcc->pre_save = loongarch_pic_pre_save; | 
 |     lpcc->post_load = loongarch_pic_post_load; | 
 | } | 
 |  | 
 | static const TypeInfo loongarch_pic_types[] = { | 
 |    { | 
 |         .name               = TYPE_LOONGARCH_PIC, | 
 |         .parent             = TYPE_LOONGARCH_PIC_COMMON, | 
 |         .instance_size      = sizeof(LoongarchPICState), | 
 |         .class_size         = sizeof(LoongarchPICClass), | 
 |         .class_init         = loongarch_pic_class_init, | 
 |     } | 
 | }; | 
 |  | 
 | DEFINE_TYPES(loongarch_pic_types) |