qemu/hw/intc/loongarch_pch_pic.c
Bibo Mao 2948c1fb6b
hw/intc/loongarch_pch: fix edge triggered irq handling
For edge triggered irq, qemu_irq_pulse is used to inject irq. It will
set irq with high level and low level soon to simluate pulse irq.

For edge triggered irq, irq is injected and set as pending at rising
level, do not clear irq at lowering level. LoongArch pch interrupt will
clear irq for lowering level irq, there will be problem. ACPI ged deivce
is edge-triggered irq, it is used for cpu/memory hotplug.

This patch fixes memory hotplug issue on LoongArch virt machine.

Signed-off-by: Bibo Mao <maobibo@loongson.cn>
Reviewed-by: Song Gao <gaosong@loongson.cn>
Message-Id: <20230707091557.1474790-1-maobibo@loongson.cn>
Signed-off-by: Song Gao <gaosong@loongson.cn>
2023-08-24 11:17:59 +08:00

464 lines
13 KiB
C

/* 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 "hw/sysbus.h"
#include "hw/loongarch/virt.h"
#include "hw/pci-host/ls7a.h"
#include "hw/irq.h"
#include "hw/intc/loongarch_pch_pic.h"
#include "hw/qdev-properties.h"
#include "migration/vmstate.h"
#include "trace.h"
#include "qapi/error.h"
static void pch_pic_update_irq(LoongArchPCHPIC *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)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(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 loongarch_pch_pic_low_readw(void *opaque, hwaddr addr,
unsigned size)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
uint64_t val = 0;
uint32_t offset = addr & 0xfff;
switch (offset) {
case PCH_PIC_INT_ID_LO:
val = PCH_PIC_INT_ID_VAL;
break;
case PCH_PIC_INT_ID_HI:
/*
* With 7A1000 manual
* bit 0-15 pch irqchip version
* bit 16-31 irq number supported with pch irqchip
*/
val = deposit32(PCH_PIC_INT_ID_VER, 16, 16, s->irq_num - 1);
break;
case PCH_PIC_INT_MASK_LO:
val = (uint32_t)s->int_mask;
break;
case PCH_PIC_INT_MASK_HI:
val = s->int_mask >> 32;
break;
case PCH_PIC_INT_EDGE_LO:
val = (uint32_t)s->intedge;
break;
case PCH_PIC_INT_EDGE_HI:
val = s->intedge >> 32;
break;
case PCH_PIC_HTMSI_EN_LO:
val = (uint32_t)s->htmsi_en;
break;
case PCH_PIC_HTMSI_EN_HI:
val = s->htmsi_en >> 32;
break;
case PCH_PIC_AUTO_CTRL0_LO:
case PCH_PIC_AUTO_CTRL0_HI:
case PCH_PIC_AUTO_CTRL1_LO:
case PCH_PIC_AUTO_CTRL1_HI:
break;
default:
break;
}
trace_loongarch_pch_pic_low_readw(size, addr, val);
return val;
}
static uint64_t get_writew_val(uint64_t value, uint32_t target, bool hi)
{
uint64_t mask = 0xffffffff00000000;
uint64_t data = target;
return hi ? (value & ~mask) | (data << 32) : (value & mask) | data;
}
static void loongarch_pch_pic_low_writew(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
uint32_t offset, old_valid, data = (uint32_t)value;
uint64_t old, int_mask;
offset = addr & 0xfff;
trace_loongarch_pch_pic_low_writew(size, addr, data);
switch (offset) {
case PCH_PIC_INT_MASK_LO:
old = s->int_mask;
s->int_mask = get_writew_val(old, data, 0);
old_valid = (uint32_t)old;
if (old_valid & ~data) {
pch_pic_update_irq(s, (old_valid & ~data), 1);
}
if (~old_valid & data) {
pch_pic_update_irq(s, (~old_valid & data), 0);
}
break;
case PCH_PIC_INT_MASK_HI:
old = s->int_mask;
s->int_mask = get_writew_val(old, data, 1);
old_valid = (uint32_t)(old >> 32);
int_mask = old_valid & ~data;
if (int_mask) {
pch_pic_update_irq(s, int_mask << 32, 1);
}
int_mask = ~old_valid & data;
if (int_mask) {
pch_pic_update_irq(s, int_mask << 32, 0);
}
break;
case PCH_PIC_INT_EDGE_LO:
s->intedge = get_writew_val(s->intedge, data, 0);
break;
case PCH_PIC_INT_EDGE_HI:
s->intedge = get_writew_val(s->intedge, data, 1);
break;
case PCH_PIC_INT_CLEAR_LO:
if (s->intedge & data) {
s->intirr &= (~data);
pch_pic_update_irq(s, data, 0);
s->intisr &= (~data);
}
break;
case PCH_PIC_INT_CLEAR_HI:
value <<= 32;
if (s->intedge & value) {
s->intirr &= (~value);
pch_pic_update_irq(s, value, 0);
s->intisr &= (~value);
}
break;
case PCH_PIC_HTMSI_EN_LO:
s->htmsi_en = get_writew_val(s->htmsi_en, data, 0);
break;
case PCH_PIC_HTMSI_EN_HI:
s->htmsi_en = get_writew_val(s->htmsi_en, data, 1);
break;
case PCH_PIC_AUTO_CTRL0_LO:
case PCH_PIC_AUTO_CTRL0_HI:
case PCH_PIC_AUTO_CTRL1_LO:
case PCH_PIC_AUTO_CTRL1_HI:
break;
default:
break;
}
}
static uint64_t loongarch_pch_pic_high_readw(void *opaque, hwaddr addr,
unsigned size)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
uint64_t val = 0;
uint32_t offset = addr & 0xfff;
switch (offset) {
case STATUS_LO_START:
val = (uint32_t)(s->intisr & (~s->int_mask));
break;
case STATUS_HI_START:
val = (s->intisr & (~s->int_mask)) >> 32;
break;
case POL_LO_START:
val = (uint32_t)s->int_polarity;
break;
case POL_HI_START:
val = s->int_polarity >> 32;
break;
default:
break;
}
trace_loongarch_pch_pic_high_readw(size, addr, val);
return val;
}
static void loongarch_pch_pic_high_writew(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
uint32_t offset, data = (uint32_t)value;
offset = addr & 0xfff;
trace_loongarch_pch_pic_high_writew(size, addr, data);
switch (offset) {
case STATUS_LO_START:
s->intisr = get_writew_val(s->intisr, data, 0);
break;
case STATUS_HI_START:
s->intisr = get_writew_val(s->intisr, data, 1);
break;
case POL_LO_START:
s->int_polarity = get_writew_val(s->int_polarity, data, 0);
break;
case POL_HI_START:
s->int_polarity = get_writew_val(s->int_polarity, data, 1);
break;
default:
break;
}
}
static uint64_t loongarch_pch_pic_readb(void *opaque, hwaddr addr,
unsigned size)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
uint64_t val = 0;
uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET;
int64_t offset_tmp;
switch (offset) {
case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END:
offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET;
if (offset_tmp >= 0 && offset_tmp < 64) {
val = s->htmsi_vector[offset_tmp];
}
break;
case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END:
offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET;
if (offset_tmp >= 0 && offset_tmp < 64) {
val = s->route_entry[offset_tmp];
}
break;
default:
break;
}
trace_loongarch_pch_pic_readb(size, addr, val);
return val;
}
static void loongarch_pch_pic_writeb(void *opaque, hwaddr addr,
uint64_t data, unsigned size)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(opaque);
int32_t offset_tmp;
uint32_t offset = (addr & 0xfff) + PCH_PIC_ROUTE_ENTRY_OFFSET;
trace_loongarch_pch_pic_writeb(size, addr, data);
switch (offset) {
case PCH_PIC_HTMSI_VEC_OFFSET ... PCH_PIC_HTMSI_VEC_END:
offset_tmp = offset - PCH_PIC_HTMSI_VEC_OFFSET;
if (offset_tmp >= 0 && offset_tmp < 64) {
s->htmsi_vector[offset_tmp] = (uint8_t)(data & 0xff);
}
break;
case PCH_PIC_ROUTE_ENTRY_OFFSET ... PCH_PIC_ROUTE_ENTRY_END:
offset_tmp = offset - PCH_PIC_ROUTE_ENTRY_OFFSET;
if (offset_tmp >= 0 && offset_tmp < 64) {
s->route_entry[offset_tmp] = (uint8_t)(data & 0xff);
}
break;
default:
break;
}
}
static const MemoryRegionOps loongarch_pch_pic_reg32_low_ops = {
.read = loongarch_pch_pic_low_readw,
.write = loongarch_pch_pic_low_writew,
.valid = {
.min_access_size = 4,
.max_access_size = 8,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static const MemoryRegionOps loongarch_pch_pic_reg32_high_ops = {
.read = loongarch_pch_pic_high_readw,
.write = loongarch_pch_pic_high_writew,
.valid = {
.min_access_size = 4,
.max_access_size = 8,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static const MemoryRegionOps loongarch_pch_pic_reg8_ops = {
.read = loongarch_pch_pic_readb,
.write = loongarch_pch_pic_writeb,
.valid = {
.min_access_size = 1,
.max_access_size = 1,
},
.impl = {
.min_access_size = 1,
.max_access_size = 1,
},
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void loongarch_pch_pic_reset(DeviceState *d)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(d);
int i;
s->int_mask = -1;
s->htmsi_en = 0x0;
s->intedge = 0x0;
s->intclr = 0x0;
s->auto_crtl0 = 0x0;
s->auto_crtl1 = 0x0;
for (i = 0; i < 64; i++) {
s->route_entry[i] = 0x1;
s->htmsi_vector[i] = 0x0;
}
s->intirr = 0x0;
s->intisr = 0x0;
s->last_intirr = 0x0;
s->int_polarity = 0x0;
}
static void loongarch_pch_pic_realize(DeviceState *dev, Error **errp)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(dev);
if (!s->irq_num || s->irq_num > VIRT_PCH_PIC_IRQ_NUM) {
error_setg(errp, "Invalid 'pic_irq_num'");
return;
}
qdev_init_gpio_out(dev, s->parent_irq, s->irq_num);
qdev_init_gpio_in(dev, pch_pic_irq_handler, s->irq_num);
}
static void loongarch_pch_pic_init(Object *obj)
{
LoongArchPCHPIC *s = LOONGARCH_PCH_PIC(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem32_low, obj,
&loongarch_pch_pic_reg32_low_ops,
s, PCH_PIC_NAME(.reg32_part1), 0x100);
memory_region_init_io(&s->iomem8, obj, &loongarch_pch_pic_reg8_ops,
s, PCH_PIC_NAME(.reg8), 0x2a0);
memory_region_init_io(&s->iomem32_high, obj,
&loongarch_pch_pic_reg32_high_ops,
s, PCH_PIC_NAME(.reg32_part2), 0xc60);
sysbus_init_mmio(sbd, &s->iomem32_low);
sysbus_init_mmio(sbd, &s->iomem8);
sysbus_init_mmio(sbd, &s->iomem32_high);
}
static Property loongarch_pch_pic_properties[] = {
DEFINE_PROP_UINT32("pch_pic_irq_num", LoongArchPCHPIC, irq_num, 0),
DEFINE_PROP_END_OF_LIST(),
};
static const VMStateDescription vmstate_loongarch_pch_pic = {
.name = TYPE_LOONGARCH_PCH_PIC,
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT64(int_mask, LoongArchPCHPIC),
VMSTATE_UINT64(htmsi_en, LoongArchPCHPIC),
VMSTATE_UINT64(intedge, LoongArchPCHPIC),
VMSTATE_UINT64(intclr, LoongArchPCHPIC),
VMSTATE_UINT64(auto_crtl0, LoongArchPCHPIC),
VMSTATE_UINT64(auto_crtl1, LoongArchPCHPIC),
VMSTATE_UINT8_ARRAY(route_entry, LoongArchPCHPIC, 64),
VMSTATE_UINT8_ARRAY(htmsi_vector, LoongArchPCHPIC, 64),
VMSTATE_UINT64(last_intirr, LoongArchPCHPIC),
VMSTATE_UINT64(intirr, LoongArchPCHPIC),
VMSTATE_UINT64(intisr, LoongArchPCHPIC),
VMSTATE_UINT64(int_polarity, LoongArchPCHPIC),
VMSTATE_END_OF_LIST()
}
};
static void loongarch_pch_pic_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = loongarch_pch_pic_realize;
dc->reset = loongarch_pch_pic_reset;
dc->vmsd = &vmstate_loongarch_pch_pic;
device_class_set_props(dc, loongarch_pch_pic_properties);
}
static const TypeInfo loongarch_pch_pic_info = {
.name = TYPE_LOONGARCH_PCH_PIC,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(LoongArchPCHPIC),
.instance_init = loongarch_pch_pic_init,
.class_init = loongarch_pch_pic_class_init,
};
static void loongarch_pch_pic_register_types(void)
{
type_register_static(&loongarch_pch_pic_info);
}
type_init(loongarch_pch_pic_register_types)