/* 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 "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 (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); } } 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); 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 void loongarch_pic_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); LoongarchPICClass *lpc = LOONGARCH_PIC_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); } 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)