mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-07-29 05:13:54 -06:00

In extioi_setirq() we try to operate on a bit array stored as an
array of uint32_t using the set_bit() and clear_bit() functions
by casting the pointer to 'unsigned long *'.
This has two problems:
* the alignment of 'uint32_t' is less than that of 'unsigned long'
so we pass an insufficiently aligned pointer, which is
undefined behaviour
* on big-endian hosts the 64-bit 'unsigned long' will have
its two halves the wrong way around, and we will produce
incorrect results
The undefined behaviour is shown by the clang undefined-behaviour
sanitizer when running the loongarch64-virt functional test:
/mnt/nvmedisk/linaro/qemu-from-laptop/qemu/include/qemu/bitops.h:41:5: runtime error: store to misaligned address 0x555559745d9c for type 'unsigned long', which requires 8 byte alignment
0x555559745d9c: note: pointer points here
ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
^
#0 0x555556fb81c4 in set_bit /mnt/nvmedisk/linaro/qemu-from-laptop/qemu/include/qemu/bitops.h:41:9
#1 0x555556fb81c4 in extioi_setirq /mnt/nvmedisk/linaro/qemu-from-laptop/qemu/build/clang/../../hw/intc/loongarch_extioi.c:65:9
#2 0x555556fb6e90 in pch_pic_irq_handler /mnt/nvmedisk/linaro/qemu-from-laptop/qemu/build/clang/../../hw/intc/loongarch_pch_pic.c:75:5
#3 0x555556710265 in serial_ioport_write /mnt/nvmedisk/linaro/qemu-from-laptop/qemu/build/clang/../../hw/char/serial.c
Fix these problems by using set_bit32() and clear_bit32(),
which work with bit arrays stored as an array of uint32_t.
Cc: qemu-stable@nongnu.org
Fixes: cbff2db1e9
("hw/intc: Add LoongArch extioi interrupt controller(EIOINTC)")
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Reviewed-by: Bibo Mao <maobibo@loongson.cn>
Message-id: 20241108135514.4006953-4-peter.maydell@linaro.org
456 lines
13 KiB
C
456 lines
13 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* Loongson 3A5000 ext interrupt controller emulation
|
|
*
|
|
* Copyright (C) 2021 Loongson Technology Corporation Limited
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/module.h"
|
|
#include "qemu/log.h"
|
|
#include "qapi/error.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/loongarch/virt.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "exec/address-spaces.h"
|
|
#include "hw/intc/loongarch_extioi.h"
|
|
#include "migration/vmstate.h"
|
|
#include "trace.h"
|
|
|
|
|
|
static void extioi_update_irq(LoongArchExtIOI *s, int irq, int level)
|
|
{
|
|
int ipnum, cpu, found, irq_index, irq_mask;
|
|
|
|
ipnum = s->sw_ipmap[irq / 32];
|
|
cpu = s->sw_coremap[irq];
|
|
irq_index = irq / 32;
|
|
irq_mask = 1 << (irq & 0x1f);
|
|
|
|
if (level) {
|
|
/* if not enable return false */
|
|
if (((s->enable[irq_index]) & irq_mask) == 0) {
|
|
return;
|
|
}
|
|
s->cpu[cpu].coreisr[irq_index] |= irq_mask;
|
|
found = find_first_bit(s->cpu[cpu].sw_isr[ipnum], EXTIOI_IRQS);
|
|
set_bit(irq, s->cpu[cpu].sw_isr[ipnum]);
|
|
if (found < EXTIOI_IRQS) {
|
|
/* other irq is handling, need not update parent irq level */
|
|
return;
|
|
}
|
|
} else {
|
|
s->cpu[cpu].coreisr[irq_index] &= ~irq_mask;
|
|
clear_bit(irq, s->cpu[cpu].sw_isr[ipnum]);
|
|
found = find_first_bit(s->cpu[cpu].sw_isr[ipnum], EXTIOI_IRQS);
|
|
if (found < EXTIOI_IRQS) {
|
|
/* other irq is handling, need not update parent irq level */
|
|
return;
|
|
}
|
|
}
|
|
qemu_set_irq(s->cpu[cpu].parent_irq[ipnum], level);
|
|
}
|
|
|
|
static void extioi_setirq(void *opaque, int irq, int level)
|
|
{
|
|
LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
|
|
trace_loongarch_extioi_setirq(irq, level);
|
|
if (level) {
|
|
set_bit32(irq, s->isr);
|
|
} else {
|
|
clear_bit32(irq, s->isr);
|
|
}
|
|
extioi_update_irq(s, irq, level);
|
|
}
|
|
|
|
static MemTxResult extioi_readw(void *opaque, hwaddr addr, uint64_t *data,
|
|
unsigned size, MemTxAttrs attrs)
|
|
{
|
|
LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
|
|
unsigned long offset = addr & 0xffff;
|
|
uint32_t index, cpu;
|
|
|
|
switch (offset) {
|
|
case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
|
|
index = (offset - EXTIOI_NODETYPE_START) >> 2;
|
|
*data = s->nodetype[index];
|
|
break;
|
|
case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
|
|
index = (offset - EXTIOI_IPMAP_START) >> 2;
|
|
*data = s->ipmap[index];
|
|
break;
|
|
case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
|
|
index = (offset - EXTIOI_ENABLE_START) >> 2;
|
|
*data = s->enable[index];
|
|
break;
|
|
case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
|
|
index = (offset - EXTIOI_BOUNCE_START) >> 2;
|
|
*data = s->bounce[index];
|
|
break;
|
|
case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
|
|
index = (offset - EXTIOI_COREISR_START) >> 2;
|
|
/* using attrs to get current cpu index */
|
|
cpu = attrs.requester_id;
|
|
*data = s->cpu[cpu].coreisr[index];
|
|
break;
|
|
case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
|
|
index = (offset - EXTIOI_COREMAP_START) >> 2;
|
|
*data = s->coremap[index];
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
trace_loongarch_extioi_readw(addr, *data);
|
|
return MEMTX_OK;
|
|
}
|
|
|
|
static inline void extioi_enable_irq(LoongArchExtIOI *s, int index,\
|
|
uint32_t mask, int level)
|
|
{
|
|
uint32_t val;
|
|
int irq;
|
|
|
|
val = mask & s->isr[index];
|
|
irq = ctz32(val);
|
|
while (irq != 32) {
|
|
/*
|
|
* enable bit change from 0 to 1,
|
|
* need to update irq by pending bits
|
|
*/
|
|
extioi_update_irq(s, irq + index * 32, level);
|
|
val &= ~(1 << irq);
|
|
irq = ctz32(val);
|
|
}
|
|
}
|
|
|
|
static inline void extioi_update_sw_coremap(LoongArchExtIOI *s, int irq,
|
|
uint64_t val, bool notify)
|
|
{
|
|
int i, cpu;
|
|
|
|
/*
|
|
* loongarch only support little endian,
|
|
* so we paresd the value with little endian.
|
|
*/
|
|
val = cpu_to_le64(val);
|
|
|
|
for (i = 0; i < 4; i++) {
|
|
cpu = val & 0xff;
|
|
val = val >> 8;
|
|
|
|
if (!(s->status & BIT(EXTIOI_ENABLE_CPU_ENCODE))) {
|
|
cpu = ctz32(cpu);
|
|
cpu = (cpu >= 4) ? 0 : cpu;
|
|
}
|
|
|
|
if (s->sw_coremap[irq + i] == cpu) {
|
|
continue;
|
|
}
|
|
|
|
if (notify && test_bit32(irq + i, s->isr)) {
|
|
/*
|
|
* lower irq at old cpu and raise irq at new cpu
|
|
*/
|
|
extioi_update_irq(s, irq + i, 0);
|
|
s->sw_coremap[irq + i] = cpu;
|
|
extioi_update_irq(s, irq + i, 1);
|
|
} else {
|
|
s->sw_coremap[irq + i] = cpu;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void extioi_update_sw_ipmap(LoongArchExtIOI *s, int index,
|
|
uint64_t val)
|
|
{
|
|
int i;
|
|
uint8_t ipnum;
|
|
|
|
/*
|
|
* loongarch only support little endian,
|
|
* so we paresd the value with little endian.
|
|
*/
|
|
val = cpu_to_le64(val);
|
|
for (i = 0; i < 4; i++) {
|
|
ipnum = val & 0xff;
|
|
ipnum = ctz32(ipnum);
|
|
ipnum = (ipnum >= 4) ? 0 : ipnum;
|
|
s->sw_ipmap[index * 4 + i] = ipnum;
|
|
val = val >> 8;
|
|
}
|
|
}
|
|
|
|
static MemTxResult extioi_writew(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size,
|
|
MemTxAttrs attrs)
|
|
{
|
|
LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
|
|
int cpu, index, old_data, irq;
|
|
uint32_t offset;
|
|
|
|
trace_loongarch_extioi_writew(addr, val);
|
|
offset = addr & 0xffff;
|
|
|
|
switch (offset) {
|
|
case EXTIOI_NODETYPE_START ... EXTIOI_NODETYPE_END - 1:
|
|
index = (offset - EXTIOI_NODETYPE_START) >> 2;
|
|
s->nodetype[index] = val;
|
|
break;
|
|
case EXTIOI_IPMAP_START ... EXTIOI_IPMAP_END - 1:
|
|
/*
|
|
* ipmap cannot be set at runtime, can be set only at the beginning
|
|
* of intr driver, need not update upper irq level
|
|
*/
|
|
index = (offset - EXTIOI_IPMAP_START) >> 2;
|
|
s->ipmap[index] = val;
|
|
extioi_update_sw_ipmap(s, index, val);
|
|
break;
|
|
case EXTIOI_ENABLE_START ... EXTIOI_ENABLE_END - 1:
|
|
index = (offset - EXTIOI_ENABLE_START) >> 2;
|
|
old_data = s->enable[index];
|
|
s->enable[index] = val;
|
|
|
|
/* unmask irq */
|
|
val = s->enable[index] & ~old_data;
|
|
extioi_enable_irq(s, index, val, 1);
|
|
|
|
/* mask irq */
|
|
val = ~s->enable[index] & old_data;
|
|
extioi_enable_irq(s, index, val, 0);
|
|
break;
|
|
case EXTIOI_BOUNCE_START ... EXTIOI_BOUNCE_END - 1:
|
|
/* do not emulate hw bounced irq routing */
|
|
index = (offset - EXTIOI_BOUNCE_START) >> 2;
|
|
s->bounce[index] = val;
|
|
break;
|
|
case EXTIOI_COREISR_START ... EXTIOI_COREISR_END - 1:
|
|
index = (offset - EXTIOI_COREISR_START) >> 2;
|
|
/* using attrs to get current cpu index */
|
|
cpu = attrs.requester_id;
|
|
old_data = s->cpu[cpu].coreisr[index];
|
|
s->cpu[cpu].coreisr[index] = old_data & ~val;
|
|
/* write 1 to clear interrupt */
|
|
old_data &= val;
|
|
irq = ctz32(old_data);
|
|
while (irq != 32) {
|
|
extioi_update_irq(s, irq + index * 32, 0);
|
|
old_data &= ~(1 << irq);
|
|
irq = ctz32(old_data);
|
|
}
|
|
break;
|
|
case EXTIOI_COREMAP_START ... EXTIOI_COREMAP_END - 1:
|
|
irq = offset - EXTIOI_COREMAP_START;
|
|
index = irq / 4;
|
|
s->coremap[index] = val;
|
|
|
|
extioi_update_sw_coremap(s, irq, val, true);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return MEMTX_OK;
|
|
}
|
|
|
|
static const MemoryRegionOps extioi_ops = {
|
|
.read_with_attrs = extioi_readw,
|
|
.write_with_attrs = extioi_writew,
|
|
.impl.min_access_size = 4,
|
|
.impl.max_access_size = 4,
|
|
.valid.min_access_size = 4,
|
|
.valid.max_access_size = 8,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static MemTxResult extioi_virt_readw(void *opaque, hwaddr addr, uint64_t *data,
|
|
unsigned size, MemTxAttrs attrs)
|
|
{
|
|
LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
|
|
|
|
switch (addr) {
|
|
case EXTIOI_VIRT_FEATURES:
|
|
*data = s->features;
|
|
break;
|
|
case EXTIOI_VIRT_CONFIG:
|
|
*data = s->status;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
|
|
return MEMTX_OK;
|
|
}
|
|
|
|
static MemTxResult extioi_virt_writew(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size,
|
|
MemTxAttrs attrs)
|
|
{
|
|
LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
|
|
|
|
switch (addr) {
|
|
case EXTIOI_VIRT_FEATURES:
|
|
return MEMTX_ACCESS_ERROR;
|
|
|
|
case EXTIOI_VIRT_CONFIG:
|
|
/*
|
|
* extioi features can only be set at disabled status
|
|
*/
|
|
if ((s->status & BIT(EXTIOI_ENABLE)) && val) {
|
|
return MEMTX_ACCESS_ERROR;
|
|
}
|
|
|
|
s->status = val & s->features;
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
return MEMTX_OK;
|
|
}
|
|
|
|
static const MemoryRegionOps extioi_virt_ops = {
|
|
.read_with_attrs = extioi_virt_readw,
|
|
.write_with_attrs = extioi_virt_writew,
|
|
.impl.min_access_size = 4,
|
|
.impl.max_access_size = 4,
|
|
.valid.min_access_size = 4,
|
|
.valid.max_access_size = 8,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static void loongarch_extioi_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
LoongArchExtIOI *s = LOONGARCH_EXTIOI(dev);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
|
int i, pin;
|
|
|
|
if (s->num_cpu == 0) {
|
|
error_setg(errp, "num-cpu must be at least 1");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < EXTIOI_IRQS; i++) {
|
|
sysbus_init_irq(sbd, &s->irq[i]);
|
|
}
|
|
|
|
qdev_init_gpio_in(dev, extioi_setirq, EXTIOI_IRQS);
|
|
memory_region_init_io(&s->extioi_system_mem, OBJECT(s), &extioi_ops,
|
|
s, "extioi_system_mem", 0x900);
|
|
sysbus_init_mmio(sbd, &s->extioi_system_mem);
|
|
|
|
if (s->features & BIT(EXTIOI_HAS_VIRT_EXTENSION)) {
|
|
memory_region_init_io(&s->virt_extend, OBJECT(s), &extioi_virt_ops,
|
|
s, "extioi_virt", EXTIOI_VIRT_SIZE);
|
|
sysbus_init_mmio(sbd, &s->virt_extend);
|
|
s->features |= EXTIOI_VIRT_HAS_FEATURES;
|
|
} else {
|
|
s->status |= BIT(EXTIOI_ENABLE);
|
|
}
|
|
|
|
s->cpu = g_new0(ExtIOICore, s->num_cpu);
|
|
if (s->cpu == NULL) {
|
|
error_setg(errp, "Memory allocation for ExtIOICore faile");
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < s->num_cpu; i++) {
|
|
for (pin = 0; pin < LS3A_INTC_IP; pin++) {
|
|
qdev_init_gpio_out(dev, &s->cpu[i].parent_irq[pin], 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void loongarch_extioi_finalize(Object *obj)
|
|
{
|
|
LoongArchExtIOI *s = LOONGARCH_EXTIOI(obj);
|
|
|
|
g_free(s->cpu);
|
|
}
|
|
|
|
static void loongarch_extioi_reset(DeviceState *d)
|
|
{
|
|
LoongArchExtIOI *s = LOONGARCH_EXTIOI(d);
|
|
|
|
s->status = 0;
|
|
}
|
|
|
|
static int vmstate_extioi_post_load(void *opaque, int version_id)
|
|
{
|
|
LoongArchExtIOI *s = LOONGARCH_EXTIOI(opaque);
|
|
int i, start_irq;
|
|
|
|
for (i = 0; i < (EXTIOI_IRQS / 4); i++) {
|
|
start_irq = i * 4;
|
|
extioi_update_sw_coremap(s, start_irq, s->coremap[i], false);
|
|
}
|
|
|
|
for (i = 0; i < (EXTIOI_IRQS_IPMAP_SIZE / 4); i++) {
|
|
extioi_update_sw_ipmap(s, i, s->ipmap[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const VMStateDescription vmstate_extioi_core = {
|
|
.name = "extioi-core",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_UINT32_ARRAY(coreisr, ExtIOICore, EXTIOI_IRQS_GROUP_COUNT),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_loongarch_extioi = {
|
|
.name = TYPE_LOONGARCH_EXTIOI,
|
|
.version_id = 3,
|
|
.minimum_version_id = 3,
|
|
.post_load = vmstate_extioi_post_load,
|
|
.fields = (const VMStateField[]) {
|
|
VMSTATE_UINT32_ARRAY(bounce, LoongArchExtIOI, EXTIOI_IRQS_GROUP_COUNT),
|
|
VMSTATE_UINT32_ARRAY(nodetype, LoongArchExtIOI,
|
|
EXTIOI_IRQS_NODETYPE_COUNT / 2),
|
|
VMSTATE_UINT32_ARRAY(enable, LoongArchExtIOI, EXTIOI_IRQS / 32),
|
|
VMSTATE_UINT32_ARRAY(isr, LoongArchExtIOI, EXTIOI_IRQS / 32),
|
|
VMSTATE_UINT32_ARRAY(ipmap, LoongArchExtIOI, EXTIOI_IRQS_IPMAP_SIZE / 4),
|
|
VMSTATE_UINT32_ARRAY(coremap, LoongArchExtIOI, EXTIOI_IRQS / 4),
|
|
|
|
VMSTATE_STRUCT_VARRAY_POINTER_UINT32(cpu, LoongArchExtIOI, num_cpu,
|
|
vmstate_extioi_core, ExtIOICore),
|
|
VMSTATE_UINT32(features, LoongArchExtIOI),
|
|
VMSTATE_UINT32(status, LoongArchExtIOI),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static Property extioi_properties[] = {
|
|
DEFINE_PROP_UINT32("num-cpu", LoongArchExtIOI, num_cpu, 1),
|
|
DEFINE_PROP_BIT("has-virtualization-extension", LoongArchExtIOI, features,
|
|
EXTIOI_HAS_VIRT_EXTENSION, 0),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void loongarch_extioi_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->realize = loongarch_extioi_realize;
|
|
device_class_set_legacy_reset(dc, loongarch_extioi_reset);
|
|
device_class_set_props(dc, extioi_properties);
|
|
dc->vmsd = &vmstate_loongarch_extioi;
|
|
}
|
|
|
|
static const TypeInfo loongarch_extioi_info = {
|
|
.name = TYPE_LOONGARCH_EXTIOI,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(struct LoongArchExtIOI),
|
|
.class_init = loongarch_extioi_class_init,
|
|
.instance_finalize = loongarch_extioi_finalize,
|
|
};
|
|
|
|
static void loongarch_extioi_register_types(void)
|
|
{
|
|
type_register_static(&loongarch_extioi_info);
|
|
}
|
|
|
|
type_init(loongarch_extioi_register_types)
|