blob: db5a0accbf9058187663195ba051a86f6aa83d9f [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 "migration/qemu-file.h"
#include "hw/arm/pic.h"
#include "hw/android/goldfish/device.h"
#include "hw/irq.h"
#include "hw/hw.h"
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 {
struct goldfish_device dev;
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 QFIELD_STRUCT struct goldfish_int_state
QFIELD_BEGIN(goldfish_int_fields)
QFIELD_INT32(level),
QFIELD_INT32(pending_count),
QFIELD_INT32(irq_enabled),
QFIELD_INT32(fiq_enabled),
QFIELD_END
static void goldfish_int_save(QEMUFile* f, void* opaque)
{
struct goldfish_int_state* s = opaque;
qemu_put_struct(f, goldfish_int_fields, s);
}
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(QEMUFile* f, void* opaque, int version_id)
{
struct goldfish_int_state* s = opaque;
if (version_id != GOLDFISH_INT_SAVE_VERSION)
return -1;
if (qemu_get_struct(f, goldfish_int_fields, s) < 0)
return -1;
goldfish_int_update(s);
return 0;
}
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 uint32_t goldfish_int_read(void *opaque, hwaddr offset)
{
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, uint32_t value)
{
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 CPUReadMemoryFunc *goldfish_int_readfn[] = {
goldfish_int_read,
goldfish_int_read,
goldfish_int_read
};
static CPUWriteMemoryFunc *goldfish_int_writefn[] = {
goldfish_int_write,
goldfish_int_write,
goldfish_int_write
};
qemu_irq* goldfish_interrupt_init(uint32_t base, qemu_irq parent_irq, qemu_irq parent_fiq)
{
int ret;
struct goldfish_int_state *s;
qemu_irq* qi;
s = g_malloc0(sizeof(*s));
qi = qemu_allocate_irqs(goldfish_int_set_irq, s, GFD_MAX_IRQ);
s->dev.name = "goldfish_interrupt_controller";
s->dev.id = -1;
s->dev.base = base;
s->dev.size = 0x1000;
s->parent_irq = parent_irq;
s->parent_fiq = parent_fiq;
ret = goldfish_device_add(&s->dev, goldfish_int_readfn, goldfish_int_writefn, s);
if(ret) {
g_free(s);
return NULL;
}
register_savevm(NULL,
"goldfish_int",
0,
GOLDFISH_INT_SAVE_VERSION,
goldfish_int_save,
goldfish_int_load,
s);
return qi;
}