blob: ccbb094d7ae066c913d624a047c62c7b9e151c34 [file] [log] [blame]
/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
#include "exec/ram_addr.h"
#include "migration/qemu-file.h"
#include "sysemu/char.h"
#include "exec/address-spaces.h"
#include "migration/qemu-file.h"
#include "hw/intc/goldfish_pic.h"
#include "hw/irq.h"
#include "hw/hw.h"
#include "hw/sysbus.h"
static qemu_irq* goldfish_pic = NULL;
static qemu_irq pic_parent_irq = NULL;
static qemu_irq pic_parent_fiq = NULL;
enum {
INTERRUPT_STATUS = 0x00, // number of pending interrupts
INTERRUPT_NUMBER = 0x04,
INTERRUPT_DISABLE_ALL = 0x08,
INTERRUPT_DISABLE = 0x0c,
INTERRUPT_ENABLE = 0x10
};
struct goldfish_int_state {
SysBusDevice parent;
MemoryRegion iomem;
qemu_irq irq;
uint32_t level;
uint32_t pending_count;
uint32_t irq_enabled;
uint32_t fiq_enabled;
qemu_irq parent_irq;
qemu_irq parent_fiq;
};
#define GOLDFISH_INT_SAVE_VERSION 1
#define TYPE_GOLDFISH_PIC "goldfish_pic"
#define GOLDFISH_PIC(obj) OBJECT_CHECK(struct goldfish_int_state, (obj), TYPE_GOLDFISH_PIC)
static void goldfish_int_update(struct goldfish_int_state *s)
{
uint32_t flags;
flags = (s->level & s->irq_enabled);
qemu_set_irq(s->parent_irq, flags != 0);
flags = (s->level & s->fiq_enabled);
qemu_set_irq(s->parent_fiq, flags != 0);
}
static int goldfish_int_load(void* opaque, int version_id)
{
struct goldfish_int_state* s = opaque;
if (version_id != GOLDFISH_INT_SAVE_VERSION)
return -1;
goldfish_int_update(s);
return 0;
}
static const VMStateDescription goldfish_pic_vmsd = {
.name = "goldfish_pic",
.version_id = GOLDFISH_INT_SAVE_VERSION,
.minimum_version_id = GOLDFISH_INT_SAVE_VERSION,
.minimum_version_id_old = GOLDFISH_INT_SAVE_VERSION,
.post_load = &goldfish_int_load,
.fields = (VMStateField[]) {
VMSTATE_UINT32(level, struct goldfish_int_state),
VMSTATE_UINT32(pending_count, struct goldfish_int_state),
VMSTATE_UINT32(irq_enabled, struct goldfish_int_state),
VMSTATE_UINT32(fiq_enabled, struct goldfish_int_state),
VMSTATE_END_OF_LIST()
}
};
static void goldfish_int_set_irq(void *opaque, int irq, int level)
{
struct goldfish_int_state *s = (struct goldfish_int_state *)opaque;
uint32_t mask = (1U << irq);
if(level) {
if(!(s->level & mask)) {
if(s->irq_enabled & mask)
s->pending_count++;
s->level |= mask;
}
}
else {
if(s->level & mask) {
if(s->irq_enabled & mask)
s->pending_count--;
s->level &= ~mask;
}
}
goldfish_int_update(s);
}
static uint64_t goldfish_int_read(void *opaque, hwaddr offset, unsigned size)
{
struct goldfish_int_state *s = (struct goldfish_int_state *)opaque;
switch (offset) {
case INTERRUPT_STATUS: /* IRQ_STATUS */ {
return s->pending_count;
}
case INTERRUPT_NUMBER: {
int i;
uint32_t pending = s->level & s->irq_enabled;
for(i = 0; i < 32; i++) {
if(pending & (1U << i)) {
return i;
}
}
return 0;
}
default:
cpu_abort(current_cpu,
"goldfish_int_read: Bad offset %" HWADDR_PRIx "\n",
offset);
return 0;
}
}
static void goldfish_int_write(void *opaque, hwaddr offset, uint64_t value, unsigned size)
{
struct goldfish_int_state *s = (struct goldfish_int_state *)opaque;
uint32_t mask = (1U << value);
switch (offset) {
case INTERRUPT_DISABLE_ALL:
s->pending_count = 0;
s->level = 0;
break;
case INTERRUPT_DISABLE:
if(s->irq_enabled & mask) {
if(s->level & mask)
s->pending_count--;
s->irq_enabled &= ~mask;
}
break;
case INTERRUPT_ENABLE:
if(!(s->irq_enabled & mask)) {
s->irq_enabled |= mask;
if(s->level & mask)
s->pending_count++;
}
break;
default:
cpu_abort(current_cpu,
"goldfish_int_write: Bad offset %" HWADDR_PRIx "\n",
offset);
return;
}
goldfish_int_update(s);
}
static const MemoryRegionOps mips_qemu_int_ops = {
.read = goldfish_int_read,
.write = goldfish_int_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
qemu_irq* goldfish_interrupt_init(uint32_t base, qemu_irq parent_irq, qemu_irq parent_fiq)
{
pic_parent_irq = parent_irq;
pic_parent_fiq = parent_fiq;
sysbus_create_simple("goldfish_pic", base, NULL);
return goldfish_pic;
}
static void goldfish_int_realize(DeviceState *dev, Error **errp)
{
SysBusDevice *sbdev = SYS_BUS_DEVICE(dev);
struct goldfish_int_state *s = GOLDFISH_PIC(dev);
goldfish_pic = qemu_allocate_irqs(goldfish_int_set_irq, s, GFD_MAX_IRQ);
s->parent_irq = pic_parent_irq;
s->parent_fiq = pic_parent_fiq;
memory_region_init_io(&s->iomem, OBJECT(s), &mips_qemu_int_ops, s,
"goldfish_pic", 0x1000);
sysbus_init_mmio(sbdev, &s->iomem);
}
static void goldfish_int_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = goldfish_int_realize;
dc->vmsd = &goldfish_pic_vmsd;
dc->desc = "goldfish PIC";
}
static const TypeInfo goldfish_int_info = {
.name = TYPE_GOLDFISH_PIC,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(struct goldfish_int_state),
.class_init = goldfish_int_class_init,
};
static void goldfish_int_register(void)
{
type_register_static(&goldfish_int_info);
}
type_init(goldfish_int_register);