mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-05 00:33:55 -06:00
hw: move interrupt controllers to hw/intc/, configure with default-configs/
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
parent
d2c0bd8458
commit
7702e47c21
35 changed files with 34 additions and 28 deletions
|
@ -3,3 +3,21 @@ common-obj-$(CONFIG_I8259) += i8259_common.o i8259.o
|
|||
common-obj-$(CONFIG_PL190) += pl190.o
|
||||
common-obj-$(CONFIG_PUV3) += puv3_intc.o
|
||||
common-obj-$(CONFIG_XILINX) += xilinx_intc.o
|
||||
common-obj-$(CONFIG_ETRAXFS) += etraxfs_pic.o
|
||||
common-obj-$(CONFIG_IMX) += imx_avic.o
|
||||
common-obj-$(CONFIG_LM32) += lm32_pic.o
|
||||
common-obj-$(CONFIG_REALVIEW) += realview_gic.o
|
||||
common-obj-$(CONFIG_SLAVIO) += sbi.o slavio_intctl.o sun4c_intctl.o
|
||||
common-obj-$(CONFIG_IOAPIC) += ioapic_common.o
|
||||
common-obj-$(CONFIG_ARM_GIC) += arm_gic_common.o
|
||||
|
||||
obj-$(CONFIG_APIC) += apic.o apic_common.o
|
||||
obj-$(CONFIG_ARM_GIC) += arm_gic.o
|
||||
obj-$(CONFIG_ARM_GIC_KVM) += arm_gic_kvm.o
|
||||
obj-$(CONFIG_STELLARIS) += armv7m_nvic.o
|
||||
obj-$(CONFIG_EXYNOS4) += exynos4210_gic.o exynos4210_combiner.o
|
||||
obj-$(CONFIG_GRLIB) += grlib_irqmp.o
|
||||
obj-$(CONFIG_IOAPIC) += ioapic.o
|
||||
obj-$(CONFIG_OMAP) += omap_intc.o
|
||||
obj-$(CONFIG_OPENPIC) += openpic.o
|
||||
obj-$(CONFIG_SH4) += sh_intc.o
|
||||
|
|
911
hw/intc/apic.c
Normal file
911
hw/intc/apic.c
Normal file
|
@ -0,0 +1,911 @@
|
|||
/*
|
||||
* APIC support
|
||||
*
|
||||
* Copyright (c) 2004-2005 Fabrice Bellard
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
#include "qemu/thread.h"
|
||||
#include "hw/i386/apic_internal.h"
|
||||
#include "hw/i386/apic.h"
|
||||
#include "hw/i386/ioapic.h"
|
||||
#include "hw/pci/msi.h"
|
||||
#include "qemu/host-utils.h"
|
||||
#include "trace.h"
|
||||
#include "hw/i386/pc.h"
|
||||
#include "hw/i386/apic-msidef.h"
|
||||
|
||||
#define MAX_APIC_WORDS 8
|
||||
|
||||
#define SYNC_FROM_VAPIC 0x1
|
||||
#define SYNC_TO_VAPIC 0x2
|
||||
#define SYNC_ISR_IRR_TO_VAPIC 0x4
|
||||
|
||||
static APICCommonState *local_apics[MAX_APICS + 1];
|
||||
|
||||
static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode);
|
||||
static void apic_update_irq(APICCommonState *s);
|
||||
static void apic_get_delivery_bitmask(uint32_t *deliver_bitmask,
|
||||
uint8_t dest, uint8_t dest_mode);
|
||||
|
||||
/* Find first bit starting from msb */
|
||||
static int fls_bit(uint32_t value)
|
||||
{
|
||||
return 31 - clz32(value);
|
||||
}
|
||||
|
||||
/* Find first bit starting from lsb */
|
||||
static int ffs_bit(uint32_t value)
|
||||
{
|
||||
return ctz32(value);
|
||||
}
|
||||
|
||||
static inline void set_bit(uint32_t *tab, int index)
|
||||
{
|
||||
int i, mask;
|
||||
i = index >> 5;
|
||||
mask = 1 << (index & 0x1f);
|
||||
tab[i] |= mask;
|
||||
}
|
||||
|
||||
static inline void reset_bit(uint32_t *tab, int index)
|
||||
{
|
||||
int i, mask;
|
||||
i = index >> 5;
|
||||
mask = 1 << (index & 0x1f);
|
||||
tab[i] &= ~mask;
|
||||
}
|
||||
|
||||
static inline int get_bit(uint32_t *tab, int index)
|
||||
{
|
||||
int i, mask;
|
||||
i = index >> 5;
|
||||
mask = 1 << (index & 0x1f);
|
||||
return !!(tab[i] & mask);
|
||||
}
|
||||
|
||||
/* return -1 if no bit is set */
|
||||
static int get_highest_priority_int(uint32_t *tab)
|
||||
{
|
||||
int i;
|
||||
for (i = 7; i >= 0; i--) {
|
||||
if (tab[i] != 0) {
|
||||
return i * 32 + fls_bit(tab[i]);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void apic_sync_vapic(APICCommonState *s, int sync_type)
|
||||
{
|
||||
VAPICState vapic_state;
|
||||
size_t length;
|
||||
off_t start;
|
||||
int vector;
|
||||
|
||||
if (!s->vapic_paddr) {
|
||||
return;
|
||||
}
|
||||
if (sync_type & SYNC_FROM_VAPIC) {
|
||||
cpu_physical_memory_rw(s->vapic_paddr, (void *)&vapic_state,
|
||||
sizeof(vapic_state), 0);
|
||||
s->tpr = vapic_state.tpr;
|
||||
}
|
||||
if (sync_type & (SYNC_TO_VAPIC | SYNC_ISR_IRR_TO_VAPIC)) {
|
||||
start = offsetof(VAPICState, isr);
|
||||
length = offsetof(VAPICState, enabled) - offsetof(VAPICState, isr);
|
||||
|
||||
if (sync_type & SYNC_TO_VAPIC) {
|
||||
assert(qemu_cpu_is_self(CPU(s->cpu)));
|
||||
|
||||
vapic_state.tpr = s->tpr;
|
||||
vapic_state.enabled = 1;
|
||||
start = 0;
|
||||
length = sizeof(VAPICState);
|
||||
}
|
||||
|
||||
vector = get_highest_priority_int(s->isr);
|
||||
if (vector < 0) {
|
||||
vector = 0;
|
||||
}
|
||||
vapic_state.isr = vector & 0xf0;
|
||||
|
||||
vapic_state.zero = 0;
|
||||
|
||||
vector = get_highest_priority_int(s->irr);
|
||||
if (vector < 0) {
|
||||
vector = 0;
|
||||
}
|
||||
vapic_state.irr = vector & 0xff;
|
||||
|
||||
cpu_physical_memory_write_rom(s->vapic_paddr + start,
|
||||
((void *)&vapic_state) + start, length);
|
||||
}
|
||||
}
|
||||
|
||||
static void apic_vapic_base_update(APICCommonState *s)
|
||||
{
|
||||
apic_sync_vapic(s, SYNC_TO_VAPIC);
|
||||
}
|
||||
|
||||
static void apic_local_deliver(APICCommonState *s, int vector)
|
||||
{
|
||||
uint32_t lvt = s->lvt[vector];
|
||||
int trigger_mode;
|
||||
|
||||
trace_apic_local_deliver(vector, (lvt >> 8) & 7);
|
||||
|
||||
if (lvt & APIC_LVT_MASKED)
|
||||
return;
|
||||
|
||||
switch ((lvt >> 8) & 7) {
|
||||
case APIC_DM_SMI:
|
||||
cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_SMI);
|
||||
break;
|
||||
|
||||
case APIC_DM_NMI:
|
||||
cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_NMI);
|
||||
break;
|
||||
|
||||
case APIC_DM_EXTINT:
|
||||
cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_HARD);
|
||||
break;
|
||||
|
||||
case APIC_DM_FIXED:
|
||||
trigger_mode = APIC_TRIGGER_EDGE;
|
||||
if ((vector == APIC_LVT_LINT0 || vector == APIC_LVT_LINT1) &&
|
||||
(lvt & APIC_LVT_LEVEL_TRIGGER))
|
||||
trigger_mode = APIC_TRIGGER_LEVEL;
|
||||
apic_set_irq(s, lvt & 0xff, trigger_mode);
|
||||
}
|
||||
}
|
||||
|
||||
void apic_deliver_pic_intr(DeviceState *d, int level)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
|
||||
if (level) {
|
||||
apic_local_deliver(s, APIC_LVT_LINT0);
|
||||
} else {
|
||||
uint32_t lvt = s->lvt[APIC_LVT_LINT0];
|
||||
|
||||
switch ((lvt >> 8) & 7) {
|
||||
case APIC_DM_FIXED:
|
||||
if (!(lvt & APIC_LVT_LEVEL_TRIGGER))
|
||||
break;
|
||||
reset_bit(s->irr, lvt & 0xff);
|
||||
/* fall through */
|
||||
case APIC_DM_EXTINT:
|
||||
cpu_reset_interrupt(CPU(s->cpu), CPU_INTERRUPT_HARD);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void apic_external_nmi(APICCommonState *s)
|
||||
{
|
||||
apic_local_deliver(s, APIC_LVT_LINT1);
|
||||
}
|
||||
|
||||
#define foreach_apic(apic, deliver_bitmask, code) \
|
||||
{\
|
||||
int __i, __j, __mask;\
|
||||
for(__i = 0; __i < MAX_APIC_WORDS; __i++) {\
|
||||
__mask = deliver_bitmask[__i];\
|
||||
if (__mask) {\
|
||||
for(__j = 0; __j < 32; __j++) {\
|
||||
if (__mask & (1 << __j)) {\
|
||||
apic = local_apics[__i * 32 + __j];\
|
||||
if (apic) {\
|
||||
code;\
|
||||
}\
|
||||
}\
|
||||
}\
|
||||
}\
|
||||
}\
|
||||
}
|
||||
|
||||
static void apic_bus_deliver(const uint32_t *deliver_bitmask,
|
||||
uint8_t delivery_mode, uint8_t vector_num,
|
||||
uint8_t trigger_mode)
|
||||
{
|
||||
APICCommonState *apic_iter;
|
||||
|
||||
switch (delivery_mode) {
|
||||
case APIC_DM_LOWPRI:
|
||||
/* XXX: search for focus processor, arbitration */
|
||||
{
|
||||
int i, d;
|
||||
d = -1;
|
||||
for(i = 0; i < MAX_APIC_WORDS; i++) {
|
||||
if (deliver_bitmask[i]) {
|
||||
d = i * 32 + ffs_bit(deliver_bitmask[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (d >= 0) {
|
||||
apic_iter = local_apics[d];
|
||||
if (apic_iter) {
|
||||
apic_set_irq(apic_iter, vector_num, trigger_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
case APIC_DM_FIXED:
|
||||
break;
|
||||
|
||||
case APIC_DM_SMI:
|
||||
foreach_apic(apic_iter, deliver_bitmask,
|
||||
cpu_interrupt(CPU(apic_iter->cpu), CPU_INTERRUPT_SMI)
|
||||
);
|
||||
return;
|
||||
|
||||
case APIC_DM_NMI:
|
||||
foreach_apic(apic_iter, deliver_bitmask,
|
||||
cpu_interrupt(CPU(apic_iter->cpu), CPU_INTERRUPT_NMI)
|
||||
);
|
||||
return;
|
||||
|
||||
case APIC_DM_INIT:
|
||||
/* normal INIT IPI sent to processors */
|
||||
foreach_apic(apic_iter, deliver_bitmask,
|
||||
cpu_interrupt(CPU(apic_iter->cpu),
|
||||
CPU_INTERRUPT_INIT)
|
||||
);
|
||||
return;
|
||||
|
||||
case APIC_DM_EXTINT:
|
||||
/* handled in I/O APIC code */
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
foreach_apic(apic_iter, deliver_bitmask,
|
||||
apic_set_irq(apic_iter, vector_num, trigger_mode) );
|
||||
}
|
||||
|
||||
void apic_deliver_irq(uint8_t dest, uint8_t dest_mode, uint8_t delivery_mode,
|
||||
uint8_t vector_num, uint8_t trigger_mode)
|
||||
{
|
||||
uint32_t deliver_bitmask[MAX_APIC_WORDS];
|
||||
|
||||
trace_apic_deliver_irq(dest, dest_mode, delivery_mode, vector_num,
|
||||
trigger_mode);
|
||||
|
||||
apic_get_delivery_bitmask(deliver_bitmask, dest, dest_mode);
|
||||
apic_bus_deliver(deliver_bitmask, delivery_mode, vector_num, trigger_mode);
|
||||
}
|
||||
|
||||
static void apic_set_base(APICCommonState *s, uint64_t val)
|
||||
{
|
||||
s->apicbase = (val & 0xfffff000) |
|
||||
(s->apicbase & (MSR_IA32_APICBASE_BSP | MSR_IA32_APICBASE_ENABLE));
|
||||
/* if disabled, cannot be enabled again */
|
||||
if (!(val & MSR_IA32_APICBASE_ENABLE)) {
|
||||
s->apicbase &= ~MSR_IA32_APICBASE_ENABLE;
|
||||
cpu_clear_apic_feature(&s->cpu->env);
|
||||
s->spurious_vec &= ~APIC_SV_ENABLE;
|
||||
}
|
||||
}
|
||||
|
||||
static void apic_set_tpr(APICCommonState *s, uint8_t val)
|
||||
{
|
||||
/* Updates from cr8 are ignored while the VAPIC is active */
|
||||
if (!s->vapic_paddr) {
|
||||
s->tpr = val << 4;
|
||||
apic_update_irq(s);
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t apic_get_tpr(APICCommonState *s)
|
||||
{
|
||||
apic_sync_vapic(s, SYNC_FROM_VAPIC);
|
||||
return s->tpr >> 4;
|
||||
}
|
||||
|
||||
static int apic_get_ppr(APICCommonState *s)
|
||||
{
|
||||
int tpr, isrv, ppr;
|
||||
|
||||
tpr = (s->tpr >> 4);
|
||||
isrv = get_highest_priority_int(s->isr);
|
||||
if (isrv < 0)
|
||||
isrv = 0;
|
||||
isrv >>= 4;
|
||||
if (tpr >= isrv)
|
||||
ppr = s->tpr;
|
||||
else
|
||||
ppr = isrv << 4;
|
||||
return ppr;
|
||||
}
|
||||
|
||||
static int apic_get_arb_pri(APICCommonState *s)
|
||||
{
|
||||
/* XXX: arbitration */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* <0 - low prio interrupt,
|
||||
* 0 - no interrupt,
|
||||
* >0 - interrupt number
|
||||
*/
|
||||
static int apic_irq_pending(APICCommonState *s)
|
||||
{
|
||||
int irrv, ppr;
|
||||
irrv = get_highest_priority_int(s->irr);
|
||||
if (irrv < 0) {
|
||||
return 0;
|
||||
}
|
||||
ppr = apic_get_ppr(s);
|
||||
if (ppr && (irrv & 0xf0) <= (ppr & 0xf0)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return irrv;
|
||||
}
|
||||
|
||||
/* signal the CPU if an irq is pending */
|
||||
static void apic_update_irq(APICCommonState *s)
|
||||
{
|
||||
CPUState *cpu;
|
||||
|
||||
if (!(s->spurious_vec & APIC_SV_ENABLE)) {
|
||||
return;
|
||||
}
|
||||
cpu = CPU(s->cpu);
|
||||
if (!qemu_cpu_is_self(cpu)) {
|
||||
cpu_interrupt(cpu, CPU_INTERRUPT_POLL);
|
||||
} else if (apic_irq_pending(s) > 0) {
|
||||
cpu_interrupt(cpu, CPU_INTERRUPT_HARD);
|
||||
}
|
||||
}
|
||||
|
||||
void apic_poll_irq(DeviceState *d)
|
||||
{
|
||||
APICCommonState *s = APIC_COMMON(d);
|
||||
|
||||
apic_sync_vapic(s, SYNC_FROM_VAPIC);
|
||||
apic_update_irq(s);
|
||||
}
|
||||
|
||||
static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode)
|
||||
{
|
||||
apic_report_irq_delivered(!get_bit(s->irr, vector_num));
|
||||
|
||||
set_bit(s->irr, vector_num);
|
||||
if (trigger_mode)
|
||||
set_bit(s->tmr, vector_num);
|
||||
else
|
||||
reset_bit(s->tmr, vector_num);
|
||||
if (s->vapic_paddr) {
|
||||
apic_sync_vapic(s, SYNC_ISR_IRR_TO_VAPIC);
|
||||
/*
|
||||
* The vcpu thread needs to see the new IRR before we pull its current
|
||||
* TPR value. That way, if we miss a lowering of the TRP, the guest
|
||||
* has the chance to notice the new IRR and poll for IRQs on its own.
|
||||
*/
|
||||
smp_wmb();
|
||||
apic_sync_vapic(s, SYNC_FROM_VAPIC);
|
||||
}
|
||||
apic_update_irq(s);
|
||||
}
|
||||
|
||||
static void apic_eoi(APICCommonState *s)
|
||||
{
|
||||
int isrv;
|
||||
isrv = get_highest_priority_int(s->isr);
|
||||
if (isrv < 0)
|
||||
return;
|
||||
reset_bit(s->isr, isrv);
|
||||
if (!(s->spurious_vec & APIC_SV_DIRECTED_IO) && get_bit(s->tmr, isrv)) {
|
||||
ioapic_eoi_broadcast(isrv);
|
||||
}
|
||||
apic_sync_vapic(s, SYNC_FROM_VAPIC | SYNC_TO_VAPIC);
|
||||
apic_update_irq(s);
|
||||
}
|
||||
|
||||
static int apic_find_dest(uint8_t dest)
|
||||
{
|
||||
APICCommonState *apic = local_apics[dest];
|
||||
int i;
|
||||
|
||||
if (apic && apic->id == dest)
|
||||
return dest; /* shortcut in case apic->id == apic->idx */
|
||||
|
||||
for (i = 0; i < MAX_APICS; i++) {
|
||||
apic = local_apics[i];
|
||||
if (apic && apic->id == dest)
|
||||
return i;
|
||||
if (!apic)
|
||||
break;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void apic_get_delivery_bitmask(uint32_t *deliver_bitmask,
|
||||
uint8_t dest, uint8_t dest_mode)
|
||||
{
|
||||
APICCommonState *apic_iter;
|
||||
int i;
|
||||
|
||||
if (dest_mode == 0) {
|
||||
if (dest == 0xff) {
|
||||
memset(deliver_bitmask, 0xff, MAX_APIC_WORDS * sizeof(uint32_t));
|
||||
} else {
|
||||
int idx = apic_find_dest(dest);
|
||||
memset(deliver_bitmask, 0x00, MAX_APIC_WORDS * sizeof(uint32_t));
|
||||
if (idx >= 0)
|
||||
set_bit(deliver_bitmask, idx);
|
||||
}
|
||||
} else {
|
||||
/* XXX: cluster mode */
|
||||
memset(deliver_bitmask, 0x00, MAX_APIC_WORDS * sizeof(uint32_t));
|
||||
for(i = 0; i < MAX_APICS; i++) {
|
||||
apic_iter = local_apics[i];
|
||||
if (apic_iter) {
|
||||
if (apic_iter->dest_mode == 0xf) {
|
||||
if (dest & apic_iter->log_dest)
|
||||
set_bit(deliver_bitmask, i);
|
||||
} else if (apic_iter->dest_mode == 0x0) {
|
||||
if ((dest & 0xf0) == (apic_iter->log_dest & 0xf0) &&
|
||||
(dest & apic_iter->log_dest & 0x0f)) {
|
||||
set_bit(deliver_bitmask, i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void apic_startup(APICCommonState *s, int vector_num)
|
||||
{
|
||||
s->sipi_vector = vector_num;
|
||||
cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_SIPI);
|
||||
}
|
||||
|
||||
void apic_sipi(DeviceState *d)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
|
||||
cpu_reset_interrupt(CPU(s->cpu), CPU_INTERRUPT_SIPI);
|
||||
|
||||
if (!s->wait_for_sipi)
|
||||
return;
|
||||
cpu_x86_load_seg_cache_sipi(s->cpu, s->sipi_vector);
|
||||
s->wait_for_sipi = 0;
|
||||
}
|
||||
|
||||
static void apic_deliver(DeviceState *d, uint8_t dest, uint8_t dest_mode,
|
||||
uint8_t delivery_mode, uint8_t vector_num,
|
||||
uint8_t trigger_mode)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
uint32_t deliver_bitmask[MAX_APIC_WORDS];
|
||||
int dest_shorthand = (s->icr[0] >> 18) & 3;
|
||||
APICCommonState *apic_iter;
|
||||
|
||||
switch (dest_shorthand) {
|
||||
case 0:
|
||||
apic_get_delivery_bitmask(deliver_bitmask, dest, dest_mode);
|
||||
break;
|
||||
case 1:
|
||||
memset(deliver_bitmask, 0x00, sizeof(deliver_bitmask));
|
||||
set_bit(deliver_bitmask, s->idx);
|
||||
break;
|
||||
case 2:
|
||||
memset(deliver_bitmask, 0xff, sizeof(deliver_bitmask));
|
||||
break;
|
||||
case 3:
|
||||
memset(deliver_bitmask, 0xff, sizeof(deliver_bitmask));
|
||||
reset_bit(deliver_bitmask, s->idx);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (delivery_mode) {
|
||||
case APIC_DM_INIT:
|
||||
{
|
||||
int trig_mode = (s->icr[0] >> 15) & 1;
|
||||
int level = (s->icr[0] >> 14) & 1;
|
||||
if (level == 0 && trig_mode == 1) {
|
||||
foreach_apic(apic_iter, deliver_bitmask,
|
||||
apic_iter->arb_id = apic_iter->id );
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case APIC_DM_SIPI:
|
||||
foreach_apic(apic_iter, deliver_bitmask,
|
||||
apic_startup(apic_iter, vector_num) );
|
||||
return;
|
||||
}
|
||||
|
||||
apic_bus_deliver(deliver_bitmask, delivery_mode, vector_num, trigger_mode);
|
||||
}
|
||||
|
||||
static bool apic_check_pic(APICCommonState *s)
|
||||
{
|
||||
if (!apic_accept_pic_intr(&s->busdev.qdev) || !pic_get_output(isa_pic)) {
|
||||
return false;
|
||||
}
|
||||
apic_deliver_pic_intr(&s->busdev.qdev, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
int apic_get_interrupt(DeviceState *d)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
int intno;
|
||||
|
||||
/* if the APIC is installed or enabled, we let the 8259 handle the
|
||||
IRQs */
|
||||
if (!s)
|
||||
return -1;
|
||||
if (!(s->spurious_vec & APIC_SV_ENABLE))
|
||||
return -1;
|
||||
|
||||
apic_sync_vapic(s, SYNC_FROM_VAPIC);
|
||||
intno = apic_irq_pending(s);
|
||||
|
||||
if (intno == 0) {
|
||||
apic_sync_vapic(s, SYNC_TO_VAPIC);
|
||||
return -1;
|
||||
} else if (intno < 0) {
|
||||
apic_sync_vapic(s, SYNC_TO_VAPIC);
|
||||
return s->spurious_vec & 0xff;
|
||||
}
|
||||
reset_bit(s->irr, intno);
|
||||
set_bit(s->isr, intno);
|
||||
apic_sync_vapic(s, SYNC_TO_VAPIC);
|
||||
|
||||
/* re-inject if there is still a pending PIC interrupt */
|
||||
apic_check_pic(s);
|
||||
|
||||
apic_update_irq(s);
|
||||
|
||||
return intno;
|
||||
}
|
||||
|
||||
int apic_accept_pic_intr(DeviceState *d)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
uint32_t lvt0;
|
||||
|
||||
if (!s)
|
||||
return -1;
|
||||
|
||||
lvt0 = s->lvt[APIC_LVT_LINT0];
|
||||
|
||||
if ((s->apicbase & MSR_IA32_APICBASE_ENABLE) == 0 ||
|
||||
(lvt0 & APIC_LVT_MASKED) == 0)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t apic_get_current_count(APICCommonState *s)
|
||||
{
|
||||
int64_t d;
|
||||
uint32_t val;
|
||||
d = (qemu_get_clock_ns(vm_clock) - s->initial_count_load_time) >>
|
||||
s->count_shift;
|
||||
if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_TIMER_PERIODIC) {
|
||||
/* periodic */
|
||||
val = s->initial_count - (d % ((uint64_t)s->initial_count + 1));
|
||||
} else {
|
||||
if (d >= s->initial_count)
|
||||
val = 0;
|
||||
else
|
||||
val = s->initial_count - d;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static void apic_timer_update(APICCommonState *s, int64_t current_time)
|
||||
{
|
||||
if (apic_next_timer(s, current_time)) {
|
||||
qemu_mod_timer(s->timer, s->next_time);
|
||||
} else {
|
||||
qemu_del_timer(s->timer);
|
||||
}
|
||||
}
|
||||
|
||||
static void apic_timer(void *opaque)
|
||||
{
|
||||
APICCommonState *s = opaque;
|
||||
|
||||
apic_local_deliver(s, APIC_LVT_TIMER);
|
||||
apic_timer_update(s, s->next_time);
|
||||
}
|
||||
|
||||
static uint32_t apic_mem_readb(void *opaque, hwaddr addr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t apic_mem_readw(void *opaque, hwaddr addr)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void apic_mem_writeb(void *opaque, hwaddr addr, uint32_t val)
|
||||
{
|
||||
}
|
||||
|
||||
static void apic_mem_writew(void *opaque, hwaddr addr, uint32_t val)
|
||||
{
|
||||
}
|
||||
|
||||
static uint32_t apic_mem_readl(void *opaque, hwaddr addr)
|
||||
{
|
||||
DeviceState *d;
|
||||
APICCommonState *s;
|
||||
uint32_t val;
|
||||
int index;
|
||||
|
||||
d = cpu_get_current_apic();
|
||||
if (!d) {
|
||||
return 0;
|
||||
}
|
||||
s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
|
||||
index = (addr >> 4) & 0xff;
|
||||
switch(index) {
|
||||
case 0x02: /* id */
|
||||
val = s->id << 24;
|
||||
break;
|
||||
case 0x03: /* version */
|
||||
val = 0x11 | ((APIC_LVT_NB - 1) << 16); /* version 0x11 */
|
||||
break;
|
||||
case 0x08:
|
||||
apic_sync_vapic(s, SYNC_FROM_VAPIC);
|
||||
if (apic_report_tpr_access) {
|
||||
cpu_report_tpr_access(&s->cpu->env, TPR_ACCESS_READ);
|
||||
}
|
||||
val = s->tpr;
|
||||
break;
|
||||
case 0x09:
|
||||
val = apic_get_arb_pri(s);
|
||||
break;
|
||||
case 0x0a:
|
||||
/* ppr */
|
||||
val = apic_get_ppr(s);
|
||||
break;
|
||||
case 0x0b:
|
||||
val = 0;
|
||||
break;
|
||||
case 0x0d:
|
||||
val = s->log_dest << 24;
|
||||
break;
|
||||
case 0x0e:
|
||||
val = s->dest_mode << 28;
|
||||
break;
|
||||
case 0x0f:
|
||||
val = s->spurious_vec;
|
||||
break;
|
||||
case 0x10 ... 0x17:
|
||||
val = s->isr[index & 7];
|
||||
break;
|
||||
case 0x18 ... 0x1f:
|
||||
val = s->tmr[index & 7];
|
||||
break;
|
||||
case 0x20 ... 0x27:
|
||||
val = s->irr[index & 7];
|
||||
break;
|
||||
case 0x28:
|
||||
val = s->esr;
|
||||
break;
|
||||
case 0x30:
|
||||
case 0x31:
|
||||
val = s->icr[index & 1];
|
||||
break;
|
||||
case 0x32 ... 0x37:
|
||||
val = s->lvt[index - 0x32];
|
||||
break;
|
||||
case 0x38:
|
||||
val = s->initial_count;
|
||||
break;
|
||||
case 0x39:
|
||||
val = apic_get_current_count(s);
|
||||
break;
|
||||
case 0x3e:
|
||||
val = s->divide_conf;
|
||||
break;
|
||||
default:
|
||||
s->esr |= ESR_ILLEGAL_ADDRESS;
|
||||
val = 0;
|
||||
break;
|
||||
}
|
||||
trace_apic_mem_readl(addr, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
static void apic_send_msi(hwaddr addr, uint32_t data)
|
||||
{
|
||||
uint8_t dest = (addr & MSI_ADDR_DEST_ID_MASK) >> MSI_ADDR_DEST_ID_SHIFT;
|
||||
uint8_t vector = (data & MSI_DATA_VECTOR_MASK) >> MSI_DATA_VECTOR_SHIFT;
|
||||
uint8_t dest_mode = (addr >> MSI_ADDR_DEST_MODE_SHIFT) & 0x1;
|
||||
uint8_t trigger_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1;
|
||||
uint8_t delivery = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) & 0x7;
|
||||
/* XXX: Ignore redirection hint. */
|
||||
apic_deliver_irq(dest, dest_mode, delivery, vector, trigger_mode);
|
||||
}
|
||||
|
||||
static void apic_mem_writel(void *opaque, hwaddr addr, uint32_t val)
|
||||
{
|
||||
DeviceState *d;
|
||||
APICCommonState *s;
|
||||
int index = (addr >> 4) & 0xff;
|
||||
if (addr > 0xfff || !index) {
|
||||
/* MSI and MMIO APIC are at the same memory location,
|
||||
* but actually not on the global bus: MSI is on PCI bus
|
||||
* APIC is connected directly to the CPU.
|
||||
* Mapping them on the global bus happens to work because
|
||||
* MSI registers are reserved in APIC MMIO and vice versa. */
|
||||
apic_send_msi(addr, val);
|
||||
return;
|
||||
}
|
||||
|
||||
d = cpu_get_current_apic();
|
||||
if (!d) {
|
||||
return;
|
||||
}
|
||||
s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
|
||||
trace_apic_mem_writel(addr, val);
|
||||
|
||||
switch(index) {
|
||||
case 0x02:
|
||||
s->id = (val >> 24);
|
||||
break;
|
||||
case 0x03:
|
||||
break;
|
||||
case 0x08:
|
||||
if (apic_report_tpr_access) {
|
||||
cpu_report_tpr_access(&s->cpu->env, TPR_ACCESS_WRITE);
|
||||
}
|
||||
s->tpr = val;
|
||||
apic_sync_vapic(s, SYNC_TO_VAPIC);
|
||||
apic_update_irq(s);
|
||||
break;
|
||||
case 0x09:
|
||||
case 0x0a:
|
||||
break;
|
||||
case 0x0b: /* EOI */
|
||||
apic_eoi(s);
|
||||
break;
|
||||
case 0x0d:
|
||||
s->log_dest = val >> 24;
|
||||
break;
|
||||
case 0x0e:
|
||||
s->dest_mode = val >> 28;
|
||||
break;
|
||||
case 0x0f:
|
||||
s->spurious_vec = val & 0x1ff;
|
||||
apic_update_irq(s);
|
||||
break;
|
||||
case 0x10 ... 0x17:
|
||||
case 0x18 ... 0x1f:
|
||||
case 0x20 ... 0x27:
|
||||
case 0x28:
|
||||
break;
|
||||
case 0x30:
|
||||
s->icr[0] = val;
|
||||
apic_deliver(d, (s->icr[1] >> 24) & 0xff, (s->icr[0] >> 11) & 1,
|
||||
(s->icr[0] >> 8) & 7, (s->icr[0] & 0xff),
|
||||
(s->icr[0] >> 15) & 1);
|
||||
break;
|
||||
case 0x31:
|
||||
s->icr[1] = val;
|
||||
break;
|
||||
case 0x32 ... 0x37:
|
||||
{
|
||||
int n = index - 0x32;
|
||||
s->lvt[n] = val;
|
||||
if (n == APIC_LVT_TIMER) {
|
||||
apic_timer_update(s, qemu_get_clock_ns(vm_clock));
|
||||
} else if (n == APIC_LVT_LINT0 && apic_check_pic(s)) {
|
||||
apic_update_irq(s);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0x38:
|
||||
s->initial_count = val;
|
||||
s->initial_count_load_time = qemu_get_clock_ns(vm_clock);
|
||||
apic_timer_update(s, s->initial_count_load_time);
|
||||
break;
|
||||
case 0x39:
|
||||
break;
|
||||
case 0x3e:
|
||||
{
|
||||
int v;
|
||||
s->divide_conf = val & 0xb;
|
||||
v = (s->divide_conf & 3) | ((s->divide_conf >> 1) & 4);
|
||||
s->count_shift = (v + 1) & 7;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
s->esr |= ESR_ILLEGAL_ADDRESS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void apic_pre_save(APICCommonState *s)
|
||||
{
|
||||
apic_sync_vapic(s, SYNC_FROM_VAPIC);
|
||||
}
|
||||
|
||||
static void apic_post_load(APICCommonState *s)
|
||||
{
|
||||
if (s->timer_expiry != -1) {
|
||||
qemu_mod_timer(s->timer, s->timer_expiry);
|
||||
} else {
|
||||
qemu_del_timer(s->timer);
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps apic_io_ops = {
|
||||
.old_mmio = {
|
||||
.read = { apic_mem_readb, apic_mem_readw, apic_mem_readl, },
|
||||
.write = { apic_mem_writeb, apic_mem_writew, apic_mem_writel, },
|
||||
},
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static void apic_init(APICCommonState *s)
|
||||
{
|
||||
memory_region_init_io(&s->io_memory, &apic_io_ops, s, "apic-msi",
|
||||
MSI_SPACE_SIZE);
|
||||
|
||||
s->timer = qemu_new_timer_ns(vm_clock, apic_timer, s);
|
||||
local_apics[s->idx] = s;
|
||||
|
||||
msi_supported = true;
|
||||
}
|
||||
|
||||
static void apic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
APICCommonClass *k = APIC_COMMON_CLASS(klass);
|
||||
|
||||
k->init = apic_init;
|
||||
k->set_base = apic_set_base;
|
||||
k->set_tpr = apic_set_tpr;
|
||||
k->get_tpr = apic_get_tpr;
|
||||
k->vapic_base_update = apic_vapic_base_update;
|
||||
k->external_nmi = apic_external_nmi;
|
||||
k->pre_save = apic_pre_save;
|
||||
k->post_load = apic_post_load;
|
||||
}
|
||||
|
||||
static const TypeInfo apic_info = {
|
||||
.name = "apic",
|
||||
.instance_size = sizeof(APICCommonState),
|
||||
.parent = TYPE_APIC_COMMON,
|
||||
.class_init = apic_class_init,
|
||||
};
|
||||
|
||||
static void apic_register_types(void)
|
||||
{
|
||||
type_register_static(&apic_info);
|
||||
}
|
||||
|
||||
type_init(apic_register_types)
|
402
hw/intc/apic_common.c
Normal file
402
hw/intc/apic_common.c
Normal file
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
* APIC support - common bits of emulated and KVM kernel model
|
||||
*
|
||||
* Copyright (c) 2004-2005 Fabrice Bellard
|
||||
* Copyright (c) 2011 Jan Kiszka, Siemens AG
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
#include "hw/i386/apic.h"
|
||||
#include "hw/i386/apic_internal.h"
|
||||
#include "trace.h"
|
||||
#include "sysemu/kvm.h"
|
||||
|
||||
static int apic_irq_delivered;
|
||||
bool apic_report_tpr_access;
|
||||
|
||||
void cpu_set_apic_base(DeviceState *d, uint64_t val)
|
||||
{
|
||||
trace_cpu_set_apic_base(val);
|
||||
|
||||
if (d) {
|
||||
APICCommonState *s = APIC_COMMON(d);
|
||||
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
|
||||
info->set_base(s, val);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t cpu_get_apic_base(DeviceState *d)
|
||||
{
|
||||
if (d) {
|
||||
APICCommonState *s = APIC_COMMON(d);
|
||||
trace_cpu_get_apic_base((uint64_t)s->apicbase);
|
||||
return s->apicbase;
|
||||
} else {
|
||||
trace_cpu_get_apic_base(MSR_IA32_APICBASE_BSP);
|
||||
return MSR_IA32_APICBASE_BSP;
|
||||
}
|
||||
}
|
||||
|
||||
void cpu_set_apic_tpr(DeviceState *d, uint8_t val)
|
||||
{
|
||||
APICCommonState *s;
|
||||
APICCommonClass *info;
|
||||
|
||||
if (!d) {
|
||||
return;
|
||||
}
|
||||
|
||||
s = APIC_COMMON(d);
|
||||
info = APIC_COMMON_GET_CLASS(s);
|
||||
|
||||
info->set_tpr(s, val);
|
||||
}
|
||||
|
||||
uint8_t cpu_get_apic_tpr(DeviceState *d)
|
||||
{
|
||||
APICCommonState *s;
|
||||
APICCommonClass *info;
|
||||
|
||||
if (!d) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
s = APIC_COMMON(d);
|
||||
info = APIC_COMMON_GET_CLASS(s);
|
||||
|
||||
return info->get_tpr(s);
|
||||
}
|
||||
|
||||
void apic_enable_tpr_access_reporting(DeviceState *d, bool enable)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
|
||||
|
||||
apic_report_tpr_access = enable;
|
||||
if (info->enable_tpr_reporting) {
|
||||
info->enable_tpr_reporting(s, enable);
|
||||
}
|
||||
}
|
||||
|
||||
void apic_enable_vapic(DeviceState *d, hwaddr paddr)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
|
||||
|
||||
s->vapic_paddr = paddr;
|
||||
info->vapic_base_update(s);
|
||||
}
|
||||
|
||||
void apic_handle_tpr_access_report(DeviceState *d, target_ulong ip,
|
||||
TPRAccess access)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
|
||||
vapic_report_tpr_access(s->vapic, CPU(s->cpu), ip, access);
|
||||
}
|
||||
|
||||
void apic_report_irq_delivered(int delivered)
|
||||
{
|
||||
apic_irq_delivered += delivered;
|
||||
|
||||
trace_apic_report_irq_delivered(apic_irq_delivered);
|
||||
}
|
||||
|
||||
void apic_reset_irq_delivered(void)
|
||||
{
|
||||
trace_apic_reset_irq_delivered(apic_irq_delivered);
|
||||
|
||||
apic_irq_delivered = 0;
|
||||
}
|
||||
|
||||
int apic_get_irq_delivered(void)
|
||||
{
|
||||
trace_apic_get_irq_delivered(apic_irq_delivered);
|
||||
|
||||
return apic_irq_delivered;
|
||||
}
|
||||
|
||||
void apic_deliver_nmi(DeviceState *d)
|
||||
{
|
||||
APICCommonState *s = APIC_COMMON(d);
|
||||
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
|
||||
|
||||
info->external_nmi(s);
|
||||
}
|
||||
|
||||
bool apic_next_timer(APICCommonState *s, int64_t current_time)
|
||||
{
|
||||
int64_t d;
|
||||
|
||||
/* We need to store the timer state separately to support APIC
|
||||
* implementations that maintain a non-QEMU timer, e.g. inside the
|
||||
* host kernel. This open-coded state allows us to migrate between
|
||||
* both models. */
|
||||
s->timer_expiry = -1;
|
||||
|
||||
if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_MASKED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
d = (current_time - s->initial_count_load_time) >> s->count_shift;
|
||||
|
||||
if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_TIMER_PERIODIC) {
|
||||
if (!s->initial_count) {
|
||||
return false;
|
||||
}
|
||||
d = ((d / ((uint64_t)s->initial_count + 1)) + 1) *
|
||||
((uint64_t)s->initial_count + 1);
|
||||
} else {
|
||||
if (d >= s->initial_count) {
|
||||
return false;
|
||||
}
|
||||
d = (uint64_t)s->initial_count + 1;
|
||||
}
|
||||
s->next_time = s->initial_count_load_time + (d << s->count_shift);
|
||||
s->timer_expiry = s->next_time;
|
||||
return true;
|
||||
}
|
||||
|
||||
void apic_init_reset(DeviceState *d)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
int i;
|
||||
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s->tpr = 0;
|
||||
s->spurious_vec = 0xff;
|
||||
s->log_dest = 0;
|
||||
s->dest_mode = 0xf;
|
||||
memset(s->isr, 0, sizeof(s->isr));
|
||||
memset(s->tmr, 0, sizeof(s->tmr));
|
||||
memset(s->irr, 0, sizeof(s->irr));
|
||||
for (i = 0; i < APIC_LVT_NB; i++) {
|
||||
s->lvt[i] = APIC_LVT_MASKED;
|
||||
}
|
||||
s->esr = 0;
|
||||
memset(s->icr, 0, sizeof(s->icr));
|
||||
s->divide_conf = 0;
|
||||
s->count_shift = 0;
|
||||
s->initial_count = 0;
|
||||
s->initial_count_load_time = 0;
|
||||
s->next_time = 0;
|
||||
s->wait_for_sipi = 1;
|
||||
|
||||
if (s->timer) {
|
||||
qemu_del_timer(s->timer);
|
||||
}
|
||||
s->timer_expiry = -1;
|
||||
}
|
||||
|
||||
void apic_designate_bsp(DeviceState *d)
|
||||
{
|
||||
if (d == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
APICCommonState *s = APIC_COMMON(d);
|
||||
s->apicbase |= MSR_IA32_APICBASE_BSP;
|
||||
}
|
||||
|
||||
static void apic_reset_common(DeviceState *d)
|
||||
{
|
||||
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
||||
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
|
||||
bool bsp;
|
||||
|
||||
bsp = cpu_is_bsp(s->cpu);
|
||||
s->apicbase = APIC_DEFAULT_ADDRESS |
|
||||
(bsp ? MSR_IA32_APICBASE_BSP : 0) | MSR_IA32_APICBASE_ENABLE;
|
||||
|
||||
s->vapic_paddr = 0;
|
||||
info->vapic_base_update(s);
|
||||
|
||||
apic_init_reset(d);
|
||||
|
||||
if (bsp) {
|
||||
/*
|
||||
* LINT0 delivery mode on CPU #0 is set to ExtInt at initialization
|
||||
* time typically by BIOS, so PIC interrupt can be delivered to the
|
||||
* processor when local APIC is enabled.
|
||||
*/
|
||||
s->lvt[APIC_LVT_LINT0] = 0x700;
|
||||
}
|
||||
}
|
||||
|
||||
/* This function is only used for old state version 1 and 2 */
|
||||
static int apic_load_old(QEMUFile *f, void *opaque, int version_id)
|
||||
{
|
||||
APICCommonState *s = opaque;
|
||||
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
|
||||
int i;
|
||||
|
||||
if (version_id > 2) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* XXX: what if the base changes? (registered memory regions) */
|
||||
qemu_get_be32s(f, &s->apicbase);
|
||||
qemu_get_8s(f, &s->id);
|
||||
qemu_get_8s(f, &s->arb_id);
|
||||
qemu_get_8s(f, &s->tpr);
|
||||
qemu_get_be32s(f, &s->spurious_vec);
|
||||
qemu_get_8s(f, &s->log_dest);
|
||||
qemu_get_8s(f, &s->dest_mode);
|
||||
for (i = 0; i < 8; i++) {
|
||||
qemu_get_be32s(f, &s->isr[i]);
|
||||
qemu_get_be32s(f, &s->tmr[i]);
|
||||
qemu_get_be32s(f, &s->irr[i]);
|
||||
}
|
||||
for (i = 0; i < APIC_LVT_NB; i++) {
|
||||
qemu_get_be32s(f, &s->lvt[i]);
|
||||
}
|
||||
qemu_get_be32s(f, &s->esr);
|
||||
qemu_get_be32s(f, &s->icr[0]);
|
||||
qemu_get_be32s(f, &s->icr[1]);
|
||||
qemu_get_be32s(f, &s->divide_conf);
|
||||
s->count_shift = qemu_get_be32(f);
|
||||
qemu_get_be32s(f, &s->initial_count);
|
||||
s->initial_count_load_time = qemu_get_be64(f);
|
||||
s->next_time = qemu_get_be64(f);
|
||||
|
||||
if (version_id >= 2) {
|
||||
s->timer_expiry = qemu_get_be64(f);
|
||||
}
|
||||
|
||||
if (info->post_load) {
|
||||
info->post_load(s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apic_init_common(SysBusDevice *dev)
|
||||
{
|
||||
APICCommonState *s = APIC_COMMON(dev);
|
||||
APICCommonClass *info;
|
||||
static DeviceState *vapic;
|
||||
static int apic_no;
|
||||
|
||||
if (apic_no >= MAX_APICS) {
|
||||
return -1;
|
||||
}
|
||||
s->idx = apic_no++;
|
||||
|
||||
info = APIC_COMMON_GET_CLASS(s);
|
||||
info->init(s);
|
||||
|
||||
sysbus_init_mmio(dev, &s->io_memory);
|
||||
|
||||
/* Note: We need at least 1M to map the VAPIC option ROM */
|
||||
if (!vapic && s->vapic_control & VAPIC_ENABLE_MASK &&
|
||||
ram_size >= 1024 * 1024) {
|
||||
vapic = sysbus_create_simple("kvmvapic", -1, NULL);
|
||||
}
|
||||
s->vapic = vapic;
|
||||
if (apic_report_tpr_access && info->enable_tpr_reporting) {
|
||||
info->enable_tpr_reporting(s, true);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void apic_dispatch_pre_save(void *opaque)
|
||||
{
|
||||
APICCommonState *s = APIC_COMMON(opaque);
|
||||
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
|
||||
|
||||
if (info->pre_save) {
|
||||
info->pre_save(s);
|
||||
}
|
||||
}
|
||||
|
||||
static int apic_dispatch_post_load(void *opaque, int version_id)
|
||||
{
|
||||
APICCommonState *s = APIC_COMMON(opaque);
|
||||
APICCommonClass *info = APIC_COMMON_GET_CLASS(s);
|
||||
|
||||
if (info->post_load) {
|
||||
info->post_load(s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_apic_common = {
|
||||
.name = "apic",
|
||||
.version_id = 3,
|
||||
.minimum_version_id = 3,
|
||||
.minimum_version_id_old = 1,
|
||||
.load_state_old = apic_load_old,
|
||||
.pre_save = apic_dispatch_pre_save,
|
||||
.post_load = apic_dispatch_post_load,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(apicbase, APICCommonState),
|
||||
VMSTATE_UINT8(id, APICCommonState),
|
||||
VMSTATE_UINT8(arb_id, APICCommonState),
|
||||
VMSTATE_UINT8(tpr, APICCommonState),
|
||||
VMSTATE_UINT32(spurious_vec, APICCommonState),
|
||||
VMSTATE_UINT8(log_dest, APICCommonState),
|
||||
VMSTATE_UINT8(dest_mode, APICCommonState),
|
||||
VMSTATE_UINT32_ARRAY(isr, APICCommonState, 8),
|
||||
VMSTATE_UINT32_ARRAY(tmr, APICCommonState, 8),
|
||||
VMSTATE_UINT32_ARRAY(irr, APICCommonState, 8),
|
||||
VMSTATE_UINT32_ARRAY(lvt, APICCommonState, APIC_LVT_NB),
|
||||
VMSTATE_UINT32(esr, APICCommonState),
|
||||
VMSTATE_UINT32_ARRAY(icr, APICCommonState, 2),
|
||||
VMSTATE_UINT32(divide_conf, APICCommonState),
|
||||
VMSTATE_INT32(count_shift, APICCommonState),
|
||||
VMSTATE_UINT32(initial_count, APICCommonState),
|
||||
VMSTATE_INT64(initial_count_load_time, APICCommonState),
|
||||
VMSTATE_INT64(next_time, APICCommonState),
|
||||
VMSTATE_INT64(timer_expiry,
|
||||
APICCommonState), /* open-coded timer state */
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static Property apic_properties_common[] = {
|
||||
DEFINE_PROP_UINT8("id", APICCommonState, id, -1),
|
||||
DEFINE_PROP_BIT("vapic", APICCommonState, vapic_control, VAPIC_ENABLE_BIT,
|
||||
true),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void apic_common_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
SysBusDeviceClass *sc = SYS_BUS_DEVICE_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->vmsd = &vmstate_apic_common;
|
||||
dc->reset = apic_reset_common;
|
||||
dc->no_user = 1;
|
||||
dc->props = apic_properties_common;
|
||||
sc->init = apic_init_common;
|
||||
}
|
||||
|
||||
static const TypeInfo apic_common_type = {
|
||||
.name = TYPE_APIC_COMMON,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(APICCommonState),
|
||||
.class_size = sizeof(APICCommonClass),
|
||||
.class_init = apic_common_class_init,
|
||||
.abstract = true,
|
||||
};
|
||||
|
||||
static void register_types(void)
|
||||
{
|
||||
type_register_static(&apic_common_type);
|
||||
}
|
||||
|
||||
type_init(register_types)
|
723
hw/intc/arm_gic.c
Normal file
723
hw/intc/arm_gic.c
Normal file
|
@ -0,0 +1,723 @@
|
|||
/*
|
||||
* ARM Generic/Distributed Interrupt Controller
|
||||
*
|
||||
* Copyright (c) 2006-2007 CodeSourcery.
|
||||
* Written by Paul Brook
|
||||
*
|
||||
* This code is licensed under the GPL.
|
||||
*/
|
||||
|
||||
/* This file contains implementation code for the RealView EB interrupt
|
||||
* controller, MPCore distributed interrupt controller and ARMv7-M
|
||||
* Nested Vectored Interrupt Controller.
|
||||
* It is compiled in two ways:
|
||||
* (1) as a standalone file to produce a sysbus device which is a GIC
|
||||
* that can be used on the realview board and as one of the builtin
|
||||
* private peripherals for the ARM MP CPUs (11MPCore, A9, etc)
|
||||
* (2) by being directly #included into armv7m_nvic.c to produce the
|
||||
* armv7m_nvic device.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/arm_gic_internal.h"
|
||||
|
||||
//#define DEBUG_GIC
|
||||
|
||||
#ifdef DEBUG_GIC
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { fprintf(stderr, "arm_gic: " fmt , ## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...) do {} while(0)
|
||||
#endif
|
||||
|
||||
static const uint8_t gic_id[] = {
|
||||
0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1
|
||||
};
|
||||
|
||||
#define NUM_CPU(s) ((s)->num_cpu)
|
||||
|
||||
static inline int gic_get_current_cpu(GICState *s)
|
||||
{
|
||||
if (s->num_cpu > 1) {
|
||||
CPUState *cpu = ENV_GET_CPU(cpu_single_env);
|
||||
return cpu->cpu_index;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO: Many places that call this routine could be optimized. */
|
||||
/* Update interrupt status after enabled or pending bits have been changed. */
|
||||
void gic_update(GICState *s)
|
||||
{
|
||||
int best_irq;
|
||||
int best_prio;
|
||||
int irq;
|
||||
int level;
|
||||
int cpu;
|
||||
int cm;
|
||||
|
||||
for (cpu = 0; cpu < NUM_CPU(s); cpu++) {
|
||||
cm = 1 << cpu;
|
||||
s->current_pending[cpu] = 1023;
|
||||
if (!s->enabled || !s->cpu_enabled[cpu]) {
|
||||
qemu_irq_lower(s->parent_irq[cpu]);
|
||||
return;
|
||||
}
|
||||
best_prio = 0x100;
|
||||
best_irq = 1023;
|
||||
for (irq = 0; irq < s->num_irq; irq++) {
|
||||
if (GIC_TEST_ENABLED(irq, cm) && GIC_TEST_PENDING(irq, cm)) {
|
||||
if (GIC_GET_PRIORITY(irq, cpu) < best_prio) {
|
||||
best_prio = GIC_GET_PRIORITY(irq, cpu);
|
||||
best_irq = irq;
|
||||
}
|
||||
}
|
||||
}
|
||||
level = 0;
|
||||
if (best_prio < s->priority_mask[cpu]) {
|
||||
s->current_pending[cpu] = best_irq;
|
||||
if (best_prio < s->running_priority[cpu]) {
|
||||
DPRINTF("Raised pending IRQ %d (cpu %d)\n", best_irq, cpu);
|
||||
level = 1;
|
||||
}
|
||||
}
|
||||
qemu_set_irq(s->parent_irq[cpu], level);
|
||||
}
|
||||
}
|
||||
|
||||
void gic_set_pending_private(GICState *s, int cpu, int irq)
|
||||
{
|
||||
int cm = 1 << cpu;
|
||||
|
||||
if (GIC_TEST_PENDING(irq, cm))
|
||||
return;
|
||||
|
||||
DPRINTF("Set %d pending cpu %d\n", irq, cpu);
|
||||
GIC_SET_PENDING(irq, cm);
|
||||
gic_update(s);
|
||||
}
|
||||
|
||||
/* Process a change in an external IRQ input. */
|
||||
static void gic_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
/* Meaning of the 'irq' parameter:
|
||||
* [0..N-1] : external interrupts
|
||||
* [N..N+31] : PPI (internal) interrupts for CPU 0
|
||||
* [N+32..N+63] : PPI (internal interrupts for CPU 1
|
||||
* ...
|
||||
*/
|
||||
GICState *s = (GICState *)opaque;
|
||||
int cm, target;
|
||||
if (irq < (s->num_irq - GIC_INTERNAL)) {
|
||||
/* The first external input line is internal interrupt 32. */
|
||||
cm = ALL_CPU_MASK;
|
||||
irq += GIC_INTERNAL;
|
||||
target = GIC_TARGET(irq);
|
||||
} else {
|
||||
int cpu;
|
||||
irq -= (s->num_irq - GIC_INTERNAL);
|
||||
cpu = irq / GIC_INTERNAL;
|
||||
irq %= GIC_INTERNAL;
|
||||
cm = 1 << cpu;
|
||||
target = cm;
|
||||
}
|
||||
|
||||
if (level == GIC_TEST_LEVEL(irq, cm)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (level) {
|
||||
GIC_SET_LEVEL(irq, cm);
|
||||
if (GIC_TEST_TRIGGER(irq) || GIC_TEST_ENABLED(irq, cm)) {
|
||||
DPRINTF("Set %d pending mask %x\n", irq, target);
|
||||
GIC_SET_PENDING(irq, target);
|
||||
}
|
||||
} else {
|
||||
GIC_CLEAR_LEVEL(irq, cm);
|
||||
}
|
||||
gic_update(s);
|
||||
}
|
||||
|
||||
static void gic_set_running_irq(GICState *s, int cpu, int irq)
|
||||
{
|
||||
s->running_irq[cpu] = irq;
|
||||
if (irq == 1023) {
|
||||
s->running_priority[cpu] = 0x100;
|
||||
} else {
|
||||
s->running_priority[cpu] = GIC_GET_PRIORITY(irq, cpu);
|
||||
}
|
||||
gic_update(s);
|
||||
}
|
||||
|
||||
uint32_t gic_acknowledge_irq(GICState *s, int cpu)
|
||||
{
|
||||
int new_irq;
|
||||
int cm = 1 << cpu;
|
||||
new_irq = s->current_pending[cpu];
|
||||
if (new_irq == 1023
|
||||
|| GIC_GET_PRIORITY(new_irq, cpu) >= s->running_priority[cpu]) {
|
||||
DPRINTF("ACK no pending IRQ\n");
|
||||
return 1023;
|
||||
}
|
||||
s->last_active[new_irq][cpu] = s->running_irq[cpu];
|
||||
/* Clear pending flags for both level and edge triggered interrupts.
|
||||
Level triggered IRQs will be reasserted once they become inactive. */
|
||||
GIC_CLEAR_PENDING(new_irq, GIC_TEST_MODEL(new_irq) ? ALL_CPU_MASK : cm);
|
||||
gic_set_running_irq(s, cpu, new_irq);
|
||||
DPRINTF("ACK %d\n", new_irq);
|
||||
return new_irq;
|
||||
}
|
||||
|
||||
void gic_complete_irq(GICState *s, int cpu, int irq)
|
||||
{
|
||||
int update = 0;
|
||||
int cm = 1 << cpu;
|
||||
DPRINTF("EOI %d\n", irq);
|
||||
if (irq >= s->num_irq) {
|
||||
/* This handles two cases:
|
||||
* 1. If software writes the ID of a spurious interrupt [ie 1023]
|
||||
* to the GICC_EOIR, the GIC ignores that write.
|
||||
* 2. If software writes the number of a non-existent interrupt
|
||||
* this must be a subcase of "value written does not match the last
|
||||
* valid interrupt value read from the Interrupt Acknowledge
|
||||
* register" and so this is UNPREDICTABLE. We choose to ignore it.
|
||||
*/
|
||||
return;
|
||||
}
|
||||
if (s->running_irq[cpu] == 1023)
|
||||
return; /* No active IRQ. */
|
||||
/* Mark level triggered interrupts as pending if they are still
|
||||
raised. */
|
||||
if (!GIC_TEST_TRIGGER(irq) && GIC_TEST_ENABLED(irq, cm)
|
||||
&& GIC_TEST_LEVEL(irq, cm) && (GIC_TARGET(irq) & cm) != 0) {
|
||||
DPRINTF("Set %d pending mask %x\n", irq, cm);
|
||||
GIC_SET_PENDING(irq, cm);
|
||||
update = 1;
|
||||
}
|
||||
if (irq != s->running_irq[cpu]) {
|
||||
/* Complete an IRQ that is not currently running. */
|
||||
int tmp = s->running_irq[cpu];
|
||||
while (s->last_active[tmp][cpu] != 1023) {
|
||||
if (s->last_active[tmp][cpu] == irq) {
|
||||
s->last_active[tmp][cpu] = s->last_active[irq][cpu];
|
||||
break;
|
||||
}
|
||||
tmp = s->last_active[tmp][cpu];
|
||||
}
|
||||
if (update) {
|
||||
gic_update(s);
|
||||
}
|
||||
} else {
|
||||
/* Complete the current running IRQ. */
|
||||
gic_set_running_irq(s, cpu, s->last_active[s->running_irq[cpu]][cpu]);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t gic_dist_readb(void *opaque, hwaddr offset)
|
||||
{
|
||||
GICState *s = (GICState *)opaque;
|
||||
uint32_t res;
|
||||
int irq;
|
||||
int i;
|
||||
int cpu;
|
||||
int cm;
|
||||
int mask;
|
||||
|
||||
cpu = gic_get_current_cpu(s);
|
||||
cm = 1 << cpu;
|
||||
if (offset < 0x100) {
|
||||
if (offset == 0)
|
||||
return s->enabled;
|
||||
if (offset == 4)
|
||||
return ((s->num_irq / 32) - 1) | ((NUM_CPU(s) - 1) << 5);
|
||||
if (offset < 0x08)
|
||||
return 0;
|
||||
if (offset >= 0x80) {
|
||||
/* Interrupt Security , RAZ/WI */
|
||||
return 0;
|
||||
}
|
||||
goto bad_reg;
|
||||
} else if (offset < 0x200) {
|
||||
/* Interrupt Set/Clear Enable. */
|
||||
if (offset < 0x180)
|
||||
irq = (offset - 0x100) * 8;
|
||||
else
|
||||
irq = (offset - 0x180) * 8;
|
||||
irq += GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
res = 0;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (GIC_TEST_ENABLED(irq + i, cm)) {
|
||||
res |= (1 << i);
|
||||
}
|
||||
}
|
||||
} else if (offset < 0x300) {
|
||||
/* Interrupt Set/Clear Pending. */
|
||||
if (offset < 0x280)
|
||||
irq = (offset - 0x200) * 8;
|
||||
else
|
||||
irq = (offset - 0x280) * 8;
|
||||
irq += GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
res = 0;
|
||||
mask = (irq < GIC_INTERNAL) ? cm : ALL_CPU_MASK;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (GIC_TEST_PENDING(irq + i, mask)) {
|
||||
res |= (1 << i);
|
||||
}
|
||||
}
|
||||
} else if (offset < 0x400) {
|
||||
/* Interrupt Active. */
|
||||
irq = (offset - 0x300) * 8 + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
res = 0;
|
||||
mask = (irq < GIC_INTERNAL) ? cm : ALL_CPU_MASK;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (GIC_TEST_ACTIVE(irq + i, mask)) {
|
||||
res |= (1 << i);
|
||||
}
|
||||
}
|
||||
} else if (offset < 0x800) {
|
||||
/* Interrupt Priority. */
|
||||
irq = (offset - 0x400) + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
res = GIC_GET_PRIORITY(irq, cpu);
|
||||
} else if (offset < 0xc00) {
|
||||
/* Interrupt CPU Target. */
|
||||
if (s->num_cpu == 1 && s->revision != REV_11MPCORE) {
|
||||
/* For uniprocessor GICs these RAZ/WI */
|
||||
res = 0;
|
||||
} else {
|
||||
irq = (offset - 0x800) + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq) {
|
||||
goto bad_reg;
|
||||
}
|
||||
if (irq >= 29 && irq <= 31) {
|
||||
res = cm;
|
||||
} else {
|
||||
res = GIC_TARGET(irq);
|
||||
}
|
||||
}
|
||||
} else if (offset < 0xf00) {
|
||||
/* Interrupt Configuration. */
|
||||
irq = (offset - 0xc00) * 2 + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
res = 0;
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (GIC_TEST_MODEL(irq + i))
|
||||
res |= (1 << (i * 2));
|
||||
if (GIC_TEST_TRIGGER(irq + i))
|
||||
res |= (2 << (i * 2));
|
||||
}
|
||||
} else if (offset < 0xfe0) {
|
||||
goto bad_reg;
|
||||
} else /* offset >= 0xfe0 */ {
|
||||
if (offset & 3) {
|
||||
res = 0;
|
||||
} else {
|
||||
res = gic_id[(offset - 0xfe0) >> 2];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
bad_reg:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"gic_dist_readb: Bad offset %x\n", (int)offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t gic_dist_readw(void *opaque, hwaddr offset)
|
||||
{
|
||||
uint32_t val;
|
||||
val = gic_dist_readb(opaque, offset);
|
||||
val |= gic_dist_readb(opaque, offset + 1) << 8;
|
||||
return val;
|
||||
}
|
||||
|
||||
static uint32_t gic_dist_readl(void *opaque, hwaddr offset)
|
||||
{
|
||||
uint32_t val;
|
||||
val = gic_dist_readw(opaque, offset);
|
||||
val |= gic_dist_readw(opaque, offset + 2) << 16;
|
||||
return val;
|
||||
}
|
||||
|
||||
static void gic_dist_writeb(void *opaque, hwaddr offset,
|
||||
uint32_t value)
|
||||
{
|
||||
GICState *s = (GICState *)opaque;
|
||||
int irq;
|
||||
int i;
|
||||
int cpu;
|
||||
|
||||
cpu = gic_get_current_cpu(s);
|
||||
if (offset < 0x100) {
|
||||
if (offset == 0) {
|
||||
s->enabled = (value & 1);
|
||||
DPRINTF("Distribution %sabled\n", s->enabled ? "En" : "Dis");
|
||||
} else if (offset < 4) {
|
||||
/* ignored. */
|
||||
} else if (offset >= 0x80) {
|
||||
/* Interrupt Security Registers, RAZ/WI */
|
||||
} else {
|
||||
goto bad_reg;
|
||||
}
|
||||
} else if (offset < 0x180) {
|
||||
/* Interrupt Set Enable. */
|
||||
irq = (offset - 0x100) * 8 + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
if (irq < 16)
|
||||
value = 0xff;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (value & (1 << i)) {
|
||||
int mask =
|
||||
(irq < GIC_INTERNAL) ? (1 << cpu) : GIC_TARGET(irq + i);
|
||||
int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK;
|
||||
|
||||
if (!GIC_TEST_ENABLED(irq + i, cm)) {
|
||||
DPRINTF("Enabled IRQ %d\n", irq + i);
|
||||
}
|
||||
GIC_SET_ENABLED(irq + i, cm);
|
||||
/* If a raised level triggered IRQ enabled then mark
|
||||
is as pending. */
|
||||
if (GIC_TEST_LEVEL(irq + i, mask)
|
||||
&& !GIC_TEST_TRIGGER(irq + i)) {
|
||||
DPRINTF("Set %d pending mask %x\n", irq + i, mask);
|
||||
GIC_SET_PENDING(irq + i, mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (offset < 0x200) {
|
||||
/* Interrupt Clear Enable. */
|
||||
irq = (offset - 0x180) * 8 + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
if (irq < 16)
|
||||
value = 0;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (value & (1 << i)) {
|
||||
int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK;
|
||||
|
||||
if (GIC_TEST_ENABLED(irq + i, cm)) {
|
||||
DPRINTF("Disabled IRQ %d\n", irq + i);
|
||||
}
|
||||
GIC_CLEAR_ENABLED(irq + i, cm);
|
||||
}
|
||||
}
|
||||
} else if (offset < 0x280) {
|
||||
/* Interrupt Set Pending. */
|
||||
irq = (offset - 0x200) * 8 + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
if (irq < 16)
|
||||
irq = 0;
|
||||
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (value & (1 << i)) {
|
||||
GIC_SET_PENDING(irq + i, GIC_TARGET(irq + i));
|
||||
}
|
||||
}
|
||||
} else if (offset < 0x300) {
|
||||
/* Interrupt Clear Pending. */
|
||||
irq = (offset - 0x280) * 8 + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
for (i = 0; i < 8; i++) {
|
||||
/* ??? This currently clears the pending bit for all CPUs, even
|
||||
for per-CPU interrupts. It's unclear whether this is the
|
||||
corect behavior. */
|
||||
if (value & (1 << i)) {
|
||||
GIC_CLEAR_PENDING(irq + i, ALL_CPU_MASK);
|
||||
}
|
||||
}
|
||||
} else if (offset < 0x400) {
|
||||
/* Interrupt Active. */
|
||||
goto bad_reg;
|
||||
} else if (offset < 0x800) {
|
||||
/* Interrupt Priority. */
|
||||
irq = (offset - 0x400) + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
if (irq < GIC_INTERNAL) {
|
||||
s->priority1[irq][cpu] = value;
|
||||
} else {
|
||||
s->priority2[irq - GIC_INTERNAL] = value;
|
||||
}
|
||||
} else if (offset < 0xc00) {
|
||||
/* Interrupt CPU Target. RAZ/WI on uniprocessor GICs, with the
|
||||
* annoying exception of the 11MPCore's GIC.
|
||||
*/
|
||||
if (s->num_cpu != 1 || s->revision == REV_11MPCORE) {
|
||||
irq = (offset - 0x800) + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq) {
|
||||
goto bad_reg;
|
||||
}
|
||||
if (irq < 29) {
|
||||
value = 0;
|
||||
} else if (irq < GIC_INTERNAL) {
|
||||
value = ALL_CPU_MASK;
|
||||
}
|
||||
s->irq_target[irq] = value & ALL_CPU_MASK;
|
||||
}
|
||||
} else if (offset < 0xf00) {
|
||||
/* Interrupt Configuration. */
|
||||
irq = (offset - 0xc00) * 4 + GIC_BASE_IRQ;
|
||||
if (irq >= s->num_irq)
|
||||
goto bad_reg;
|
||||
if (irq < GIC_INTERNAL)
|
||||
value |= 0xaa;
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (value & (1 << (i * 2))) {
|
||||
GIC_SET_MODEL(irq + i);
|
||||
} else {
|
||||
GIC_CLEAR_MODEL(irq + i);
|
||||
}
|
||||
if (value & (2 << (i * 2))) {
|
||||
GIC_SET_TRIGGER(irq + i);
|
||||
} else {
|
||||
GIC_CLEAR_TRIGGER(irq + i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* 0xf00 is only handled for 32-bit writes. */
|
||||
goto bad_reg;
|
||||
}
|
||||
gic_update(s);
|
||||
return;
|
||||
bad_reg:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"gic_dist_writeb: Bad offset %x\n", (int)offset);
|
||||
}
|
||||
|
||||
static void gic_dist_writew(void *opaque, hwaddr offset,
|
||||
uint32_t value)
|
||||
{
|
||||
gic_dist_writeb(opaque, offset, value & 0xff);
|
||||
gic_dist_writeb(opaque, offset + 1, value >> 8);
|
||||
}
|
||||
|
||||
static void gic_dist_writel(void *opaque, hwaddr offset,
|
||||
uint32_t value)
|
||||
{
|
||||
GICState *s = (GICState *)opaque;
|
||||
if (offset == 0xf00) {
|
||||
int cpu;
|
||||
int irq;
|
||||
int mask;
|
||||
|
||||
cpu = gic_get_current_cpu(s);
|
||||
irq = value & 0x3ff;
|
||||
switch ((value >> 24) & 3) {
|
||||
case 0:
|
||||
mask = (value >> 16) & ALL_CPU_MASK;
|
||||
break;
|
||||
case 1:
|
||||
mask = ALL_CPU_MASK ^ (1 << cpu);
|
||||
break;
|
||||
case 2:
|
||||
mask = 1 << cpu;
|
||||
break;
|
||||
default:
|
||||
DPRINTF("Bad Soft Int target filter\n");
|
||||
mask = ALL_CPU_MASK;
|
||||
break;
|
||||
}
|
||||
GIC_SET_PENDING(irq, mask);
|
||||
gic_update(s);
|
||||
return;
|
||||
}
|
||||
gic_dist_writew(opaque, offset, value & 0xffff);
|
||||
gic_dist_writew(opaque, offset + 2, value >> 16);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps gic_dist_ops = {
|
||||
.old_mmio = {
|
||||
.read = { gic_dist_readb, gic_dist_readw, gic_dist_readl, },
|
||||
.write = { gic_dist_writeb, gic_dist_writew, gic_dist_writel, },
|
||||
},
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static uint32_t gic_cpu_read(GICState *s, int cpu, int offset)
|
||||
{
|
||||
switch (offset) {
|
||||
case 0x00: /* Control */
|
||||
return s->cpu_enabled[cpu];
|
||||
case 0x04: /* Priority mask */
|
||||
return s->priority_mask[cpu];
|
||||
case 0x08: /* Binary Point */
|
||||
/* ??? Not implemented. */
|
||||
return 0;
|
||||
case 0x0c: /* Acknowledge */
|
||||
return gic_acknowledge_irq(s, cpu);
|
||||
case 0x14: /* Running Priority */
|
||||
return s->running_priority[cpu];
|
||||
case 0x18: /* Highest Pending Interrupt */
|
||||
return s->current_pending[cpu];
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"gic_cpu_read: Bad offset %x\n", (int)offset);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void gic_cpu_write(GICState *s, int cpu, int offset, uint32_t value)
|
||||
{
|
||||
switch (offset) {
|
||||
case 0x00: /* Control */
|
||||
s->cpu_enabled[cpu] = (value & 1);
|
||||
DPRINTF("CPU %d %sabled\n", cpu, s->cpu_enabled[cpu] ? "En" : "Dis");
|
||||
break;
|
||||
case 0x04: /* Priority mask */
|
||||
s->priority_mask[cpu] = (value & 0xff);
|
||||
break;
|
||||
case 0x08: /* Binary Point */
|
||||
/* ??? Not implemented. */
|
||||
break;
|
||||
case 0x10: /* End Of Interrupt */
|
||||
return gic_complete_irq(s, cpu, value & 0x3ff);
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"gic_cpu_write: Bad offset %x\n", (int)offset);
|
||||
return;
|
||||
}
|
||||
gic_update(s);
|
||||
}
|
||||
|
||||
/* Wrappers to read/write the GIC CPU interface for the current CPU */
|
||||
static uint64_t gic_thiscpu_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
GICState *s = (GICState *)opaque;
|
||||
return gic_cpu_read(s, gic_get_current_cpu(s), addr);
|
||||
}
|
||||
|
||||
static void gic_thiscpu_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
GICState *s = (GICState *)opaque;
|
||||
gic_cpu_write(s, gic_get_current_cpu(s), addr, value);
|
||||
}
|
||||
|
||||
/* Wrappers to read/write the GIC CPU interface for a specific CPU.
|
||||
* These just decode the opaque pointer into GICState* + cpu id.
|
||||
*/
|
||||
static uint64_t gic_do_cpu_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
GICState **backref = (GICState **)opaque;
|
||||
GICState *s = *backref;
|
||||
int id = (backref - s->backref);
|
||||
return gic_cpu_read(s, id, addr);
|
||||
}
|
||||
|
||||
static void gic_do_cpu_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
GICState **backref = (GICState **)opaque;
|
||||
GICState *s = *backref;
|
||||
int id = (backref - s->backref);
|
||||
gic_cpu_write(s, id, addr, value);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps gic_thiscpu_ops = {
|
||||
.read = gic_thiscpu_read,
|
||||
.write = gic_thiscpu_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static const MemoryRegionOps gic_cpu_ops = {
|
||||
.read = gic_do_cpu_read,
|
||||
.write = gic_do_cpu_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
void gic_init_irqs_and_distributor(GICState *s, int num_irq)
|
||||
{
|
||||
int i;
|
||||
|
||||
i = s->num_irq - GIC_INTERNAL;
|
||||
/* For the GIC, also expose incoming GPIO lines for PPIs for each CPU.
|
||||
* GPIO array layout is thus:
|
||||
* [0..N-1] SPIs
|
||||
* [N..N+31] PPIs for CPU 0
|
||||
* [N+32..N+63] PPIs for CPU 1
|
||||
* ...
|
||||
*/
|
||||
if (s->revision != REV_NVIC) {
|
||||
i += (GIC_INTERNAL * s->num_cpu);
|
||||
}
|
||||
qdev_init_gpio_in(&s->busdev.qdev, gic_set_irq, i);
|
||||
for (i = 0; i < NUM_CPU(s); i++) {
|
||||
sysbus_init_irq(&s->busdev, &s->parent_irq[i]);
|
||||
}
|
||||
memory_region_init_io(&s->iomem, &gic_dist_ops, s, "gic_dist", 0x1000);
|
||||
}
|
||||
|
||||
static void arm_gic_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
/* Device instance realize function for the GIC sysbus device */
|
||||
int i;
|
||||
GICState *s = ARM_GIC(dev);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
||||
ARMGICClass *agc = ARM_GIC_GET_CLASS(s);
|
||||
|
||||
agc->parent_realize(dev, errp);
|
||||
if (error_is_set(errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
gic_init_irqs_and_distributor(s, s->num_irq);
|
||||
|
||||
/* Memory regions for the CPU interfaces (NVIC doesn't have these):
|
||||
* a region for "CPU interface for this core", then a region for
|
||||
* "CPU interface for core 0", "for core 1", ...
|
||||
* NB that the memory region size of 0x100 applies for the 11MPCore
|
||||
* and also cores following the GIC v1 spec (ie A9).
|
||||
* GIC v2 defines a larger memory region (0x1000) so this will need
|
||||
* to be extended when we implement A15.
|
||||
*/
|
||||
memory_region_init_io(&s->cpuiomem[0], &gic_thiscpu_ops, s,
|
||||
"gic_cpu", 0x100);
|
||||
for (i = 0; i < NUM_CPU(s); i++) {
|
||||
s->backref[i] = s;
|
||||
memory_region_init_io(&s->cpuiomem[i+1], &gic_cpu_ops, &s->backref[i],
|
||||
"gic_cpu", 0x100);
|
||||
}
|
||||
/* Distributor */
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
/* cpu interfaces (one for "current cpu" plus one per cpu) */
|
||||
for (i = 0; i <= NUM_CPU(s); i++) {
|
||||
sysbus_init_mmio(sbd, &s->cpuiomem[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void arm_gic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
ARMGICClass *agc = ARM_GIC_CLASS(klass);
|
||||
|
||||
dc->no_user = 1;
|
||||
agc->parent_realize = dc->realize;
|
||||
dc->realize = arm_gic_realize;
|
||||
}
|
||||
|
||||
static const TypeInfo arm_gic_info = {
|
||||
.name = TYPE_ARM_GIC,
|
||||
.parent = TYPE_ARM_GIC_COMMON,
|
||||
.instance_size = sizeof(GICState),
|
||||
.class_init = arm_gic_class_init,
|
||||
.class_size = sizeof(ARMGICClass),
|
||||
};
|
||||
|
||||
static void arm_gic_register_types(void)
|
||||
{
|
||||
type_register_static(&arm_gic_info);
|
||||
}
|
||||
|
||||
type_init(arm_gic_register_types)
|
176
hw/intc/arm_gic_common.c
Normal file
176
hw/intc/arm_gic_common.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* ARM GIC support - common bits of emulated and KVM kernel model
|
||||
*
|
||||
* Copyright (c) 2012 Linaro Limited
|
||||
* Written by Peter Maydell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hw/arm_gic_internal.h"
|
||||
|
||||
static void gic_pre_save(void *opaque)
|
||||
{
|
||||
GICState *s = (GICState *)opaque;
|
||||
ARMGICCommonClass *c = ARM_GIC_COMMON_GET_CLASS(s);
|
||||
|
||||
if (c->pre_save) {
|
||||
c->pre_save(s);
|
||||
}
|
||||
}
|
||||
|
||||
static int gic_post_load(void *opaque, int version_id)
|
||||
{
|
||||
GICState *s = (GICState *)opaque;
|
||||
ARMGICCommonClass *c = ARM_GIC_COMMON_GET_CLASS(s);
|
||||
|
||||
if (c->post_load) {
|
||||
c->post_load(s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_gic_irq_state = {
|
||||
.name = "arm_gic_irq_state",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT8(enabled, gic_irq_state),
|
||||
VMSTATE_UINT8(pending, gic_irq_state),
|
||||
VMSTATE_UINT8(active, gic_irq_state),
|
||||
VMSTATE_UINT8(level, gic_irq_state),
|
||||
VMSTATE_BOOL(model, gic_irq_state),
|
||||
VMSTATE_BOOL(trigger, gic_irq_state),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_gic = {
|
||||
.name = "arm_gic",
|
||||
.version_id = 4,
|
||||
.minimum_version_id = 4,
|
||||
.pre_save = gic_pre_save,
|
||||
.post_load = gic_post_load,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_BOOL(enabled, GICState),
|
||||
VMSTATE_BOOL_ARRAY(cpu_enabled, GICState, NCPU),
|
||||
VMSTATE_STRUCT_ARRAY(irq_state, GICState, GIC_MAXIRQ, 1,
|
||||
vmstate_gic_irq_state, gic_irq_state),
|
||||
VMSTATE_UINT8_ARRAY(irq_target, GICState, GIC_MAXIRQ),
|
||||
VMSTATE_UINT8_2DARRAY(priority1, GICState, GIC_INTERNAL, NCPU),
|
||||
VMSTATE_UINT8_ARRAY(priority2, GICState, GIC_MAXIRQ - GIC_INTERNAL),
|
||||
VMSTATE_UINT16_2DARRAY(last_active, GICState, GIC_MAXIRQ, NCPU),
|
||||
VMSTATE_UINT16_ARRAY(priority_mask, GICState, NCPU),
|
||||
VMSTATE_UINT16_ARRAY(running_irq, GICState, NCPU),
|
||||
VMSTATE_UINT16_ARRAY(running_priority, GICState, NCPU),
|
||||
VMSTATE_UINT16_ARRAY(current_pending, GICState, NCPU),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void arm_gic_common_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
GICState *s = ARM_GIC_COMMON(dev);
|
||||
int num_irq = s->num_irq;
|
||||
|
||||
if (s->num_cpu > NCPU) {
|
||||
error_setg(errp, "requested %u CPUs exceeds GIC maximum %d",
|
||||
s->num_cpu, NCPU);
|
||||
return;
|
||||
}
|
||||
s->num_irq += GIC_BASE_IRQ;
|
||||
if (s->num_irq > GIC_MAXIRQ) {
|
||||
error_setg(errp,
|
||||
"requested %u interrupt lines exceeds GIC maximum %d",
|
||||
num_irq, GIC_MAXIRQ);
|
||||
return;
|
||||
}
|
||||
/* ITLinesNumber is represented as (N / 32) - 1 (see
|
||||
* gic_dist_readb) so this is an implementation imposed
|
||||
* restriction, not an architectural one:
|
||||
*/
|
||||
if (s->num_irq < 32 || (s->num_irq % 32)) {
|
||||
error_setg(errp,
|
||||
"%d interrupt lines unsupported: not divisible by 32",
|
||||
num_irq);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void arm_gic_common_reset(DeviceState *dev)
|
||||
{
|
||||
GICState *s = FROM_SYSBUS(GICState, SYS_BUS_DEVICE(dev));
|
||||
int i;
|
||||
memset(s->irq_state, 0, GIC_MAXIRQ * sizeof(gic_irq_state));
|
||||
for (i = 0 ; i < s->num_cpu; i++) {
|
||||
if (s->revision == REV_11MPCORE) {
|
||||
s->priority_mask[i] = 0xf0;
|
||||
} else {
|
||||
s->priority_mask[i] = 0;
|
||||
}
|
||||
s->current_pending[i] = 1023;
|
||||
s->running_irq[i] = 1023;
|
||||
s->running_priority[i] = 0x100;
|
||||
s->cpu_enabled[i] = false;
|
||||
}
|
||||
for (i = 0; i < 16; i++) {
|
||||
GIC_SET_ENABLED(i, ALL_CPU_MASK);
|
||||
GIC_SET_TRIGGER(i);
|
||||
}
|
||||
if (s->num_cpu == 1) {
|
||||
/* For uniprocessor GICs all interrupts always target the sole CPU */
|
||||
for (i = 0; i < GIC_MAXIRQ; i++) {
|
||||
s->irq_target[i] = 1;
|
||||
}
|
||||
}
|
||||
s->enabled = false;
|
||||
}
|
||||
|
||||
static Property arm_gic_common_properties[] = {
|
||||
DEFINE_PROP_UINT32("num-cpu", GICState, num_cpu, 1),
|
||||
DEFINE_PROP_UINT32("num-irq", GICState, num_irq, 32),
|
||||
/* Revision can be 1 or 2 for GIC architecture specification
|
||||
* versions 1 or 2, or 0 to indicate the legacy 11MPCore GIC.
|
||||
* (Internally, 0xffffffff also indicates "not a GIC but an NVIC".)
|
||||
*/
|
||||
DEFINE_PROP_UINT32("revision", GICState, revision, 1),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void arm_gic_common_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->reset = arm_gic_common_reset;
|
||||
dc->realize = arm_gic_common_realize;
|
||||
dc->props = arm_gic_common_properties;
|
||||
dc->vmsd = &vmstate_gic;
|
||||
dc->no_user = 1;
|
||||
}
|
||||
|
||||
static const TypeInfo arm_gic_common_type = {
|
||||
.name = TYPE_ARM_GIC_COMMON,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(GICState),
|
||||
.class_size = sizeof(ARMGICCommonClass),
|
||||
.class_init = arm_gic_common_class_init,
|
||||
.abstract = true,
|
||||
};
|
||||
|
||||
static void register_types(void)
|
||||
{
|
||||
type_register_static(&arm_gic_common_type);
|
||||
}
|
||||
|
||||
type_init(register_types)
|
167
hw/intc/arm_gic_kvm.c
Normal file
167
hw/intc/arm_gic_kvm.c
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* ARM Generic Interrupt Controller using KVM in-kernel support
|
||||
*
|
||||
* Copyright (c) 2012 Linaro Limited
|
||||
* Written by Peter Maydell
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "sysemu/kvm.h"
|
||||
#include "kvm_arm.h"
|
||||
#include "hw/arm_gic_internal.h"
|
||||
|
||||
#define TYPE_KVM_ARM_GIC "kvm-arm-gic"
|
||||
#define KVM_ARM_GIC(obj) \
|
||||
OBJECT_CHECK(GICState, (obj), TYPE_KVM_ARM_GIC)
|
||||
#define KVM_ARM_GIC_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(KVMARMGICClass, (klass), TYPE_KVM_ARM_GIC)
|
||||
#define KVM_ARM_GIC_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(KVMARMGICClass, (obj), TYPE_KVM_ARM_GIC)
|
||||
|
||||
typedef struct KVMARMGICClass {
|
||||
ARMGICCommonClass parent_class;
|
||||
DeviceRealize parent_realize;
|
||||
void (*parent_reset)(DeviceState *dev);
|
||||
} KVMARMGICClass;
|
||||
|
||||
static void kvm_arm_gic_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
/* Meaning of the 'irq' parameter:
|
||||
* [0..N-1] : external interrupts
|
||||
* [N..N+31] : PPI (internal) interrupts for CPU 0
|
||||
* [N+32..N+63] : PPI (internal interrupts for CPU 1
|
||||
* ...
|
||||
* Convert this to the kernel's desired encoding, which
|
||||
* has separate fields in the irq number for type,
|
||||
* CPU number and interrupt number.
|
||||
*/
|
||||
GICState *s = (GICState *)opaque;
|
||||
int kvm_irq, irqtype, cpu;
|
||||
|
||||
if (irq < (s->num_irq - GIC_INTERNAL)) {
|
||||
/* External interrupt. The kernel numbers these like the GIC
|
||||
* hardware, with external interrupt IDs starting after the
|
||||
* internal ones.
|
||||
*/
|
||||
irqtype = KVM_ARM_IRQ_TYPE_SPI;
|
||||
cpu = 0;
|
||||
irq += GIC_INTERNAL;
|
||||
} else {
|
||||
/* Internal interrupt: decode into (cpu, interrupt id) */
|
||||
irqtype = KVM_ARM_IRQ_TYPE_PPI;
|
||||
irq -= (s->num_irq - GIC_INTERNAL);
|
||||
cpu = irq / GIC_INTERNAL;
|
||||
irq %= GIC_INTERNAL;
|
||||
}
|
||||
kvm_irq = (irqtype << KVM_ARM_IRQ_TYPE_SHIFT)
|
||||
| (cpu << KVM_ARM_IRQ_VCPU_SHIFT) | irq;
|
||||
|
||||
kvm_set_irq(kvm_state, kvm_irq, !!level);
|
||||
}
|
||||
|
||||
static void kvm_arm_gic_put(GICState *s)
|
||||
{
|
||||
/* TODO: there isn't currently a kernel interface to set the GIC state */
|
||||
}
|
||||
|
||||
static void kvm_arm_gic_get(GICState *s)
|
||||
{
|
||||
/* TODO: there isn't currently a kernel interface to get the GIC state */
|
||||
}
|
||||
|
||||
static void kvm_arm_gic_reset(DeviceState *dev)
|
||||
{
|
||||
GICState *s = ARM_GIC_COMMON(dev);
|
||||
KVMARMGICClass *kgc = KVM_ARM_GIC_GET_CLASS(s);
|
||||
|
||||
kgc->parent_reset(dev);
|
||||
kvm_arm_gic_put(s);
|
||||
}
|
||||
|
||||
static void kvm_arm_gic_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
int i;
|
||||
GICState *s = KVM_ARM_GIC(dev);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
||||
KVMARMGICClass *kgc = KVM_ARM_GIC_GET_CLASS(s);
|
||||
|
||||
kgc->parent_realize(dev, errp);
|
||||
if (error_is_set(errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
i = s->num_irq - GIC_INTERNAL;
|
||||
/* For the GIC, also expose incoming GPIO lines for PPIs for each CPU.
|
||||
* GPIO array layout is thus:
|
||||
* [0..N-1] SPIs
|
||||
* [N..N+31] PPIs for CPU 0
|
||||
* [N+32..N+63] PPIs for CPU 1
|
||||
* ...
|
||||
*/
|
||||
i += (GIC_INTERNAL * s->num_cpu);
|
||||
qdev_init_gpio_in(dev, kvm_arm_gic_set_irq, i);
|
||||
/* We never use our outbound IRQ lines but provide them so that
|
||||
* we maintain the same interface as the non-KVM GIC.
|
||||
*/
|
||||
for (i = 0; i < s->num_cpu; i++) {
|
||||
sysbus_init_irq(sbd, &s->parent_irq[i]);
|
||||
}
|
||||
/* Distributor */
|
||||
memory_region_init_reservation(&s->iomem, "kvm-gic_dist", 0x1000);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
kvm_arm_register_device(&s->iomem,
|
||||
(KVM_ARM_DEVICE_VGIC_V2 << KVM_ARM_DEVICE_ID_SHIFT)
|
||||
| KVM_VGIC_V2_ADDR_TYPE_DIST);
|
||||
/* CPU interface for current core. Unlike arm_gic, we don't
|
||||
* provide the "interface for core #N" memory regions, because
|
||||
* cores with a VGIC don't have those.
|
||||
*/
|
||||
memory_region_init_reservation(&s->cpuiomem[0], "kvm-gic_cpu", 0x1000);
|
||||
sysbus_init_mmio(sbd, &s->cpuiomem[0]);
|
||||
kvm_arm_register_device(&s->cpuiomem[0],
|
||||
(KVM_ARM_DEVICE_VGIC_V2 << KVM_ARM_DEVICE_ID_SHIFT)
|
||||
| KVM_VGIC_V2_ADDR_TYPE_CPU);
|
||||
}
|
||||
|
||||
static void kvm_arm_gic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
ARMGICCommonClass *agcc = ARM_GIC_COMMON_CLASS(klass);
|
||||
KVMARMGICClass *kgc = KVM_ARM_GIC_CLASS(klass);
|
||||
|
||||
agcc->pre_save = kvm_arm_gic_get;
|
||||
agcc->post_load = kvm_arm_gic_put;
|
||||
kgc->parent_realize = dc->realize;
|
||||
kgc->parent_reset = dc->reset;
|
||||
dc->realize = kvm_arm_gic_realize;
|
||||
dc->reset = kvm_arm_gic_reset;
|
||||
dc->no_user = 1;
|
||||
}
|
||||
|
||||
static const TypeInfo kvm_arm_gic_info = {
|
||||
.name = TYPE_KVM_ARM_GIC,
|
||||
.parent = TYPE_ARM_GIC_COMMON,
|
||||
.instance_size = sizeof(GICState),
|
||||
.class_init = kvm_arm_gic_class_init,
|
||||
.class_size = sizeof(KVMARMGICClass),
|
||||
};
|
||||
|
||||
static void kvm_arm_gic_register_types(void)
|
||||
{
|
||||
type_register_static(&kvm_arm_gic_info);
|
||||
}
|
||||
|
||||
type_init(kvm_arm_gic_register_types)
|
553
hw/intc/armv7m_nvic.c
Normal file
553
hw/intc/armv7m_nvic.c
Normal file
|
@ -0,0 +1,553 @@
|
|||
/*
|
||||
* ARM Nested Vectored Interrupt Controller
|
||||
*
|
||||
* Copyright (c) 2006-2007 CodeSourcery.
|
||||
* Written by Paul Brook
|
||||
*
|
||||
* This code is licensed under the GPL.
|
||||
*
|
||||
* The ARMv7M System controller is fairly tightly tied in with the
|
||||
* NVIC. Much of that is also implemented here.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "hw/arm.h"
|
||||
#include "exec/address-spaces.h"
|
||||
#include "hw/arm_gic_internal.h"
|
||||
|
||||
typedef struct {
|
||||
GICState gic;
|
||||
struct {
|
||||
uint32_t control;
|
||||
uint32_t reload;
|
||||
int64_t tick;
|
||||
QEMUTimer *timer;
|
||||
} systick;
|
||||
MemoryRegion sysregmem;
|
||||
MemoryRegion gic_iomem_alias;
|
||||
MemoryRegion container;
|
||||
uint32_t num_irq;
|
||||
} nvic_state;
|
||||
|
||||
#define TYPE_NVIC "armv7m_nvic"
|
||||
/**
|
||||
* NVICClass:
|
||||
* @parent_reset: the parent class' reset handler.
|
||||
*
|
||||
* A model of the v7M NVIC and System Controller
|
||||
*/
|
||||
typedef struct NVICClass {
|
||||
/*< private >*/
|
||||
ARMGICClass parent_class;
|
||||
/*< public >*/
|
||||
DeviceRealize parent_realize;
|
||||
void (*parent_reset)(DeviceState *dev);
|
||||
} NVICClass;
|
||||
|
||||
#define NVIC_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(NVICClass, (klass), TYPE_NVIC)
|
||||
#define NVIC_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(NVICClass, (obj), TYPE_NVIC)
|
||||
#define NVIC(obj) \
|
||||
OBJECT_CHECK(nvic_state, (obj), TYPE_NVIC)
|
||||
|
||||
static const uint8_t nvic_id[] = {
|
||||
0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1
|
||||
};
|
||||
|
||||
/* qemu timers run at 1GHz. We want something closer to 1MHz. */
|
||||
#define SYSTICK_SCALE 1000ULL
|
||||
|
||||
#define SYSTICK_ENABLE (1 << 0)
|
||||
#define SYSTICK_TICKINT (1 << 1)
|
||||
#define SYSTICK_CLKSOURCE (1 << 2)
|
||||
#define SYSTICK_COUNTFLAG (1 << 16)
|
||||
|
||||
int system_clock_scale;
|
||||
|
||||
/* Conversion factor from qemu timer to SysTick frequencies. */
|
||||
static inline int64_t systick_scale(nvic_state *s)
|
||||
{
|
||||
if (s->systick.control & SYSTICK_CLKSOURCE)
|
||||
return system_clock_scale;
|
||||
else
|
||||
return 1000;
|
||||
}
|
||||
|
||||
static void systick_reload(nvic_state *s, int reset)
|
||||
{
|
||||
if (reset)
|
||||
s->systick.tick = qemu_get_clock_ns(vm_clock);
|
||||
s->systick.tick += (s->systick.reload + 1) * systick_scale(s);
|
||||
qemu_mod_timer(s->systick.timer, s->systick.tick);
|
||||
}
|
||||
|
||||
static void systick_timer_tick(void * opaque)
|
||||
{
|
||||
nvic_state *s = (nvic_state *)opaque;
|
||||
s->systick.control |= SYSTICK_COUNTFLAG;
|
||||
if (s->systick.control & SYSTICK_TICKINT) {
|
||||
/* Trigger the interrupt. */
|
||||
armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK);
|
||||
}
|
||||
if (s->systick.reload == 0) {
|
||||
s->systick.control &= ~SYSTICK_ENABLE;
|
||||
} else {
|
||||
systick_reload(s, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void systick_reset(nvic_state *s)
|
||||
{
|
||||
s->systick.control = 0;
|
||||
s->systick.reload = 0;
|
||||
s->systick.tick = 0;
|
||||
qemu_del_timer(s->systick.timer);
|
||||
}
|
||||
|
||||
/* The external routines use the hardware vector numbering, ie. the first
|
||||
IRQ is #16. The internal GIC routines use #32 as the first IRQ. */
|
||||
void armv7m_nvic_set_pending(void *opaque, int irq)
|
||||
{
|
||||
nvic_state *s = (nvic_state *)opaque;
|
||||
if (irq >= 16)
|
||||
irq += 16;
|
||||
gic_set_pending_private(&s->gic, 0, irq);
|
||||
}
|
||||
|
||||
/* Make pending IRQ active. */
|
||||
int armv7m_nvic_acknowledge_irq(void *opaque)
|
||||
{
|
||||
nvic_state *s = (nvic_state *)opaque;
|
||||
uint32_t irq;
|
||||
|
||||
irq = gic_acknowledge_irq(&s->gic, 0);
|
||||
if (irq == 1023)
|
||||
hw_error("Interrupt but no vector\n");
|
||||
if (irq >= 32)
|
||||
irq -= 16;
|
||||
return irq;
|
||||
}
|
||||
|
||||
void armv7m_nvic_complete_irq(void *opaque, int irq)
|
||||
{
|
||||
nvic_state *s = (nvic_state *)opaque;
|
||||
if (irq >= 16)
|
||||
irq += 16;
|
||||
gic_complete_irq(&s->gic, 0, irq);
|
||||
}
|
||||
|
||||
static uint32_t nvic_readl(nvic_state *s, uint32_t offset)
|
||||
{
|
||||
uint32_t val;
|
||||
int irq;
|
||||
|
||||
switch (offset) {
|
||||
case 4: /* Interrupt Control Type. */
|
||||
return (s->num_irq / 32) - 1;
|
||||
case 0x10: /* SysTick Control and Status. */
|
||||
val = s->systick.control;
|
||||
s->systick.control &= ~SYSTICK_COUNTFLAG;
|
||||
return val;
|
||||
case 0x14: /* SysTick Reload Value. */
|
||||
return s->systick.reload;
|
||||
case 0x18: /* SysTick Current Value. */
|
||||
{
|
||||
int64_t t;
|
||||
if ((s->systick.control & SYSTICK_ENABLE) == 0)
|
||||
return 0;
|
||||
t = qemu_get_clock_ns(vm_clock);
|
||||
if (t >= s->systick.tick)
|
||||
return 0;
|
||||
val = ((s->systick.tick - (t + 1)) / systick_scale(s)) + 1;
|
||||
/* The interrupt in triggered when the timer reaches zero.
|
||||
However the counter is not reloaded until the next clock
|
||||
tick. This is a hack to return zero during the first tick. */
|
||||
if (val > s->systick.reload)
|
||||
val = 0;
|
||||
return val;
|
||||
}
|
||||
case 0x1c: /* SysTick Calibration Value. */
|
||||
return 10000;
|
||||
case 0xd00: /* CPUID Base. */
|
||||
return cpu_single_env->cp15.c0_cpuid;
|
||||
case 0xd04: /* Interrypt Control State. */
|
||||
/* VECTACTIVE */
|
||||
val = s->gic.running_irq[0];
|
||||
if (val == 1023) {
|
||||
val = 0;
|
||||
} else if (val >= 32) {
|
||||
val -= 16;
|
||||
}
|
||||
/* RETTOBASE */
|
||||
if (s->gic.running_irq[0] == 1023
|
||||
|| s->gic.last_active[s->gic.running_irq[0]][0] == 1023) {
|
||||
val |= (1 << 11);
|
||||
}
|
||||
/* VECTPENDING */
|
||||
if (s->gic.current_pending[0] != 1023)
|
||||
val |= (s->gic.current_pending[0] << 12);
|
||||
/* ISRPENDING */
|
||||
for (irq = 32; irq < s->num_irq; irq++) {
|
||||
if (s->gic.irq_state[irq].pending) {
|
||||
val |= (1 << 22);
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* PENDSTSET */
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_SYSTICK].pending)
|
||||
val |= (1 << 26);
|
||||
/* PENDSVSET */
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_PENDSV].pending)
|
||||
val |= (1 << 28);
|
||||
/* NMIPENDSET */
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_NMI].pending)
|
||||
val |= (1 << 31);
|
||||
return val;
|
||||
case 0xd08: /* Vector Table Offset. */
|
||||
return cpu_single_env->v7m.vecbase;
|
||||
case 0xd0c: /* Application Interrupt/Reset Control. */
|
||||
return 0xfa05000;
|
||||
case 0xd10: /* System Control. */
|
||||
/* TODO: Implement SLEEPONEXIT. */
|
||||
return 0;
|
||||
case 0xd14: /* Configuration Control. */
|
||||
/* TODO: Implement Configuration Control bits. */
|
||||
return 0;
|
||||
case 0xd24: /* System Handler Status. */
|
||||
val = 0;
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_MEM].active) val |= (1 << 0);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_BUS].active) val |= (1 << 1);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_USAGE].active) val |= (1 << 3);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_SVC].active) val |= (1 << 7);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_DEBUG].active) val |= (1 << 8);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_PENDSV].active) val |= (1 << 10);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_SYSTICK].active) val |= (1 << 11);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_USAGE].pending) val |= (1 << 12);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_MEM].pending) val |= (1 << 13);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_BUS].pending) val |= (1 << 14);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_SVC].pending) val |= (1 << 15);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_MEM].enabled) val |= (1 << 16);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_BUS].enabled) val |= (1 << 17);
|
||||
if (s->gic.irq_state[ARMV7M_EXCP_USAGE].enabled) val |= (1 << 18);
|
||||
return val;
|
||||
case 0xd28: /* Configurable Fault Status. */
|
||||
/* TODO: Implement Fault Status. */
|
||||
qemu_log_mask(LOG_UNIMP, "Configurable Fault Status unimplemented\n");
|
||||
return 0;
|
||||
case 0xd2c: /* Hard Fault Status. */
|
||||
case 0xd30: /* Debug Fault Status. */
|
||||
case 0xd34: /* Mem Manage Address. */
|
||||
case 0xd38: /* Bus Fault Address. */
|
||||
case 0xd3c: /* Aux Fault Status. */
|
||||
/* TODO: Implement fault status registers. */
|
||||
qemu_log_mask(LOG_UNIMP, "Fault status registers unimplemented\n");
|
||||
return 0;
|
||||
case 0xd40: /* PFR0. */
|
||||
return 0x00000030;
|
||||
case 0xd44: /* PRF1. */
|
||||
return 0x00000200;
|
||||
case 0xd48: /* DFR0. */
|
||||
return 0x00100000;
|
||||
case 0xd4c: /* AFR0. */
|
||||
return 0x00000000;
|
||||
case 0xd50: /* MMFR0. */
|
||||
return 0x00000030;
|
||||
case 0xd54: /* MMFR1. */
|
||||
return 0x00000000;
|
||||
case 0xd58: /* MMFR2. */
|
||||
return 0x00000000;
|
||||
case 0xd5c: /* MMFR3. */
|
||||
return 0x00000000;
|
||||
case 0xd60: /* ISAR0. */
|
||||
return 0x01141110;
|
||||
case 0xd64: /* ISAR1. */
|
||||
return 0x02111000;
|
||||
case 0xd68: /* ISAR2. */
|
||||
return 0x21112231;
|
||||
case 0xd6c: /* ISAR3. */
|
||||
return 0x01111110;
|
||||
case 0xd70: /* ISAR4. */
|
||||
return 0x01310102;
|
||||
/* TODO: Implement debug registers. */
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR, "NVIC: Bad read offset 0x%x\n", offset);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void nvic_writel(nvic_state *s, uint32_t offset, uint32_t value)
|
||||
{
|
||||
uint32_t oldval;
|
||||
switch (offset) {
|
||||
case 0x10: /* SysTick Control and Status. */
|
||||
oldval = s->systick.control;
|
||||
s->systick.control &= 0xfffffff8;
|
||||
s->systick.control |= value & 7;
|
||||
if ((oldval ^ value) & SYSTICK_ENABLE) {
|
||||
int64_t now = qemu_get_clock_ns(vm_clock);
|
||||
if (value & SYSTICK_ENABLE) {
|
||||
if (s->systick.tick) {
|
||||
s->systick.tick += now;
|
||||
qemu_mod_timer(s->systick.timer, s->systick.tick);
|
||||
} else {
|
||||
systick_reload(s, 1);
|
||||
}
|
||||
} else {
|
||||
qemu_del_timer(s->systick.timer);
|
||||
s->systick.tick -= now;
|
||||
if (s->systick.tick < 0)
|
||||
s->systick.tick = 0;
|
||||
}
|
||||
} else if ((oldval ^ value) & SYSTICK_CLKSOURCE) {
|
||||
/* This is a hack. Force the timer to be reloaded
|
||||
when the reference clock is changed. */
|
||||
systick_reload(s, 1);
|
||||
}
|
||||
break;
|
||||
case 0x14: /* SysTick Reload Value. */
|
||||
s->systick.reload = value;
|
||||
break;
|
||||
case 0x18: /* SysTick Current Value. Writes reload the timer. */
|
||||
systick_reload(s, 1);
|
||||
s->systick.control &= ~SYSTICK_COUNTFLAG;
|
||||
break;
|
||||
case 0xd04: /* Interrupt Control State. */
|
||||
if (value & (1 << 31)) {
|
||||
armv7m_nvic_set_pending(s, ARMV7M_EXCP_NMI);
|
||||
}
|
||||
if (value & (1 << 28)) {
|
||||
armv7m_nvic_set_pending(s, ARMV7M_EXCP_PENDSV);
|
||||
} else if (value & (1 << 27)) {
|
||||
s->gic.irq_state[ARMV7M_EXCP_PENDSV].pending = 0;
|
||||
gic_update(&s->gic);
|
||||
}
|
||||
if (value & (1 << 26)) {
|
||||
armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK);
|
||||
} else if (value & (1 << 25)) {
|
||||
s->gic.irq_state[ARMV7M_EXCP_SYSTICK].pending = 0;
|
||||
gic_update(&s->gic);
|
||||
}
|
||||
break;
|
||||
case 0xd08: /* Vector Table Offset. */
|
||||
cpu_single_env->v7m.vecbase = value & 0xffffff80;
|
||||
break;
|
||||
case 0xd0c: /* Application Interrupt/Reset Control. */
|
||||
if ((value >> 16) == 0x05fa) {
|
||||
if (value & 2) {
|
||||
qemu_log_mask(LOG_UNIMP, "VECTCLRACTIVE unimplemented\n");
|
||||
}
|
||||
if (value & 5) {
|
||||
qemu_log_mask(LOG_UNIMP, "AIRCR system reset unimplemented\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0xd10: /* System Control. */
|
||||
case 0xd14: /* Configuration Control. */
|
||||
/* TODO: Implement control registers. */
|
||||
qemu_log_mask(LOG_UNIMP, "NVIC: SCR and CCR unimplemented\n");
|
||||
break;
|
||||
case 0xd24: /* System Handler Control. */
|
||||
/* TODO: Real hardware allows you to set/clear the active bits
|
||||
under some circumstances. We don't implement this. */
|
||||
s->gic.irq_state[ARMV7M_EXCP_MEM].enabled = (value & (1 << 16)) != 0;
|
||||
s->gic.irq_state[ARMV7M_EXCP_BUS].enabled = (value & (1 << 17)) != 0;
|
||||
s->gic.irq_state[ARMV7M_EXCP_USAGE].enabled = (value & (1 << 18)) != 0;
|
||||
break;
|
||||
case 0xd28: /* Configurable Fault Status. */
|
||||
case 0xd2c: /* Hard Fault Status. */
|
||||
case 0xd30: /* Debug Fault Status. */
|
||||
case 0xd34: /* Mem Manage Address. */
|
||||
case 0xd38: /* Bus Fault Address. */
|
||||
case 0xd3c: /* Aux Fault Status. */
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"NVIC: fault status registers unimplemented\n");
|
||||
break;
|
||||
case 0xf00: /* Software Triggered Interrupt Register */
|
||||
if ((value & 0x1ff) < s->num_irq) {
|
||||
gic_set_pending_private(&s->gic, 0, value & 0x1ff);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"NVIC: Bad write offset 0x%x\n", offset);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t nvic_sysreg_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
nvic_state *s = (nvic_state *)opaque;
|
||||
uint32_t offset = addr;
|
||||
int i;
|
||||
uint32_t val;
|
||||
|
||||
switch (offset) {
|
||||
case 0xd18 ... 0xd23: /* System Handler Priority. */
|
||||
val = 0;
|
||||
for (i = 0; i < size; i++) {
|
||||
val |= s->gic.priority1[(offset - 0xd14) + i][0] << (i * 8);
|
||||
}
|
||||
return val;
|
||||
case 0xfe0 ... 0xfff: /* ID. */
|
||||
if (offset & 3) {
|
||||
return 0;
|
||||
}
|
||||
return nvic_id[(offset - 0xfe0) >> 2];
|
||||
}
|
||||
if (size == 4) {
|
||||
return nvic_readl(s, offset);
|
||||
}
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"NVIC: Bad read of size %d at offset 0x%x\n", size, offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void nvic_sysreg_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
nvic_state *s = (nvic_state *)opaque;
|
||||
uint32_t offset = addr;
|
||||
int i;
|
||||
|
||||
switch (offset) {
|
||||
case 0xd18 ... 0xd23: /* System Handler Priority. */
|
||||
for (i = 0; i < size; i++) {
|
||||
s->gic.priority1[(offset - 0xd14) + i][0] =
|
||||
(value >> (i * 8)) & 0xff;
|
||||
}
|
||||
gic_update(&s->gic);
|
||||
return;
|
||||
}
|
||||
if (size == 4) {
|
||||
nvic_writel(s, offset, value);
|
||||
return;
|
||||
}
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"NVIC: Bad write of size %d at offset 0x%x\n", size, offset);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps nvic_sysreg_ops = {
|
||||
.read = nvic_sysreg_read,
|
||||
.write = nvic_sysreg_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_nvic = {
|
||||
.name = "armv7m_nvic",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(systick.control, nvic_state),
|
||||
VMSTATE_UINT32(systick.reload, nvic_state),
|
||||
VMSTATE_INT64(systick.tick, nvic_state),
|
||||
VMSTATE_TIMER(systick.timer, nvic_state),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void armv7m_nvic_reset(DeviceState *dev)
|
||||
{
|
||||
nvic_state *s = NVIC(dev);
|
||||
NVICClass *nc = NVIC_GET_CLASS(s);
|
||||
nc->parent_reset(dev);
|
||||
/* Common GIC reset resets to disabled; the NVIC doesn't have
|
||||
* per-CPU interfaces so mark our non-existent CPU interface
|
||||
* as enabled by default, and with a priority mask which allows
|
||||
* all interrupts through.
|
||||
*/
|
||||
s->gic.cpu_enabled[0] = true;
|
||||
s->gic.priority_mask[0] = 0x100;
|
||||
/* The NVIC as a whole is always enabled. */
|
||||
s->gic.enabled = true;
|
||||
systick_reset(s);
|
||||
}
|
||||
|
||||
static void armv7m_nvic_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
nvic_state *s = NVIC(dev);
|
||||
NVICClass *nc = NVIC_GET_CLASS(s);
|
||||
|
||||
/* The NVIC always has only one CPU */
|
||||
s->gic.num_cpu = 1;
|
||||
/* Tell the common code we're an NVIC */
|
||||
s->gic.revision = 0xffffffff;
|
||||
s->num_irq = s->gic.num_irq;
|
||||
nc->parent_realize(dev, errp);
|
||||
if (error_is_set(errp)) {
|
||||
return;
|
||||
}
|
||||
gic_init_irqs_and_distributor(&s->gic, s->num_irq);
|
||||
/* The NVIC and system controller register area looks like this:
|
||||
* 0..0xff : system control registers, including systick
|
||||
* 0x100..0xcff : GIC-like registers
|
||||
* 0xd00..0xfff : system control registers
|
||||
* We use overlaying to put the GIC like registers
|
||||
* over the top of the system control register region.
|
||||
*/
|
||||
memory_region_init(&s->container, "nvic", 0x1000);
|
||||
/* The system register region goes at the bottom of the priority
|
||||
* stack as it covers the whole page.
|
||||
*/
|
||||
memory_region_init_io(&s->sysregmem, &nvic_sysreg_ops, s,
|
||||
"nvic_sysregs", 0x1000);
|
||||
memory_region_add_subregion(&s->container, 0, &s->sysregmem);
|
||||
/* Alias the GIC region so we can get only the section of it
|
||||
* we need, and layer it on top of the system register region.
|
||||
*/
|
||||
memory_region_init_alias(&s->gic_iomem_alias, "nvic-gic", &s->gic.iomem,
|
||||
0x100, 0xc00);
|
||||
memory_region_add_subregion_overlap(&s->container, 0x100,
|
||||
&s->gic_iomem_alias, 1);
|
||||
/* Map the whole thing into system memory at the location required
|
||||
* by the v7M architecture.
|
||||
*/
|
||||
memory_region_add_subregion(get_system_memory(), 0xe000e000, &s->container);
|
||||
s->systick.timer = qemu_new_timer_ns(vm_clock, systick_timer_tick, s);
|
||||
}
|
||||
|
||||
static void armv7m_nvic_instance_init(Object *obj)
|
||||
{
|
||||
/* We have a different default value for the num-irq property
|
||||
* than our superclass. This function runs after qdev init
|
||||
* has set the defaults from the Property array and before
|
||||
* any user-specified property setting, so just modify the
|
||||
* value in the GICState struct.
|
||||
*/
|
||||
GICState *s = ARM_GIC_COMMON(obj);
|
||||
/* The ARM v7m may have anything from 0 to 496 external interrupt
|
||||
* IRQ lines. We default to 64. Other boards may differ and should
|
||||
* set the num-irq property appropriately.
|
||||
*/
|
||||
s->num_irq = 64;
|
||||
}
|
||||
|
||||
static void armv7m_nvic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
NVICClass *nc = NVIC_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
nc->parent_reset = dc->reset;
|
||||
nc->parent_realize = dc->realize;
|
||||
dc->vmsd = &vmstate_nvic;
|
||||
dc->reset = armv7m_nvic_reset;
|
||||
dc->realize = armv7m_nvic_realize;
|
||||
}
|
||||
|
||||
static const TypeInfo armv7m_nvic_info = {
|
||||
.name = TYPE_NVIC,
|
||||
.parent = TYPE_ARM_GIC_COMMON,
|
||||
.instance_init = armv7m_nvic_instance_init,
|
||||
.instance_size = sizeof(nvic_state),
|
||||
.class_init = armv7m_nvic_class_init,
|
||||
.class_size = sizeof(NVICClass),
|
||||
};
|
||||
|
||||
static void armv7m_nvic_register_types(void)
|
||||
{
|
||||
type_register_static(&armv7m_nvic_info);
|
||||
}
|
||||
|
||||
type_init(armv7m_nvic_register_types)
|
180
hw/intc/etraxfs_pic.c
Normal file
180
hw/intc/etraxfs_pic.c
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* QEMU ETRAX Interrupt Controller.
|
||||
*
|
||||
* Copyright (c) 2008 Edgar E. Iglesias, Axis Communications AB.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/hw.h"
|
||||
//#include "pc.h"
|
||||
//#include "etraxfs.h"
|
||||
|
||||
#define D(x)
|
||||
|
||||
#define R_RW_MASK 0
|
||||
#define R_R_VECT 1
|
||||
#define R_R_MASKED_VECT 2
|
||||
#define R_R_NMI 3
|
||||
#define R_R_GURU 4
|
||||
#define R_MAX 5
|
||||
|
||||
struct etrax_pic
|
||||
{
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion mmio;
|
||||
void *interrupt_vector;
|
||||
qemu_irq parent_irq;
|
||||
qemu_irq parent_nmi;
|
||||
uint32_t regs[R_MAX];
|
||||
};
|
||||
|
||||
static void pic_update(struct etrax_pic *fs)
|
||||
{
|
||||
uint32_t vector = 0;
|
||||
int i;
|
||||
|
||||
fs->regs[R_R_MASKED_VECT] = fs->regs[R_R_VECT] & fs->regs[R_RW_MASK];
|
||||
|
||||
/* The ETRAX interrupt controller signals interrupts to the core
|
||||
through an interrupt request wire and an irq vector bus. If
|
||||
multiple interrupts are simultaneously active it chooses vector
|
||||
0x30 and lets the sw choose the priorities. */
|
||||
if (fs->regs[R_R_MASKED_VECT]) {
|
||||
uint32_t mv = fs->regs[R_R_MASKED_VECT];
|
||||
for (i = 0; i < 31; i++) {
|
||||
if (mv & 1) {
|
||||
vector = 0x31 + i;
|
||||
/* Check for multiple interrupts. */
|
||||
if (mv > 1)
|
||||
vector = 0x30;
|
||||
break;
|
||||
}
|
||||
mv >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (fs->interrupt_vector) {
|
||||
/* hack alert: ptr property */
|
||||
*(uint32_t*)(fs->interrupt_vector) = vector;
|
||||
}
|
||||
qemu_set_irq(fs->parent_irq, !!vector);
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
pic_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
struct etrax_pic *fs = opaque;
|
||||
uint32_t rval;
|
||||
|
||||
rval = fs->regs[addr >> 2];
|
||||
D(printf("%s %x=%x\n", __func__, addr, rval));
|
||||
return rval;
|
||||
}
|
||||
|
||||
static void pic_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned int size)
|
||||
{
|
||||
struct etrax_pic *fs = opaque;
|
||||
D(printf("%s addr=%x val=%x\n", __func__, addr, value));
|
||||
|
||||
if (addr == R_RW_MASK) {
|
||||
fs->regs[R_RW_MASK] = value;
|
||||
pic_update(fs);
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps pic_ops = {
|
||||
.read = pic_read,
|
||||
.write = pic_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4
|
||||
}
|
||||
};
|
||||
|
||||
static void nmi_handler(void *opaque, int irq, int level)
|
||||
{
|
||||
struct etrax_pic *fs = (void *)opaque;
|
||||
uint32_t mask;
|
||||
|
||||
mask = 1 << irq;
|
||||
if (level)
|
||||
fs->regs[R_R_NMI] |= mask;
|
||||
else
|
||||
fs->regs[R_R_NMI] &= ~mask;
|
||||
|
||||
qemu_set_irq(fs->parent_nmi, !!fs->regs[R_R_NMI]);
|
||||
}
|
||||
|
||||
static void irq_handler(void *opaque, int irq, int level)
|
||||
{
|
||||
struct etrax_pic *fs = (void *)opaque;
|
||||
|
||||
if (irq >= 30)
|
||||
return nmi_handler(opaque, irq, level);
|
||||
|
||||
irq -= 1;
|
||||
fs->regs[R_R_VECT] &= ~(1 << irq);
|
||||
fs->regs[R_R_VECT] |= (!!level << irq);
|
||||
pic_update(fs);
|
||||
}
|
||||
|
||||
static int etraxfs_pic_init(SysBusDevice *dev)
|
||||
{
|
||||
struct etrax_pic *s = FROM_SYSBUS(typeof (*s), dev);
|
||||
|
||||
qdev_init_gpio_in(&dev->qdev, irq_handler, 32);
|
||||
sysbus_init_irq(dev, &s->parent_irq);
|
||||
sysbus_init_irq(dev, &s->parent_nmi);
|
||||
|
||||
memory_region_init_io(&s->mmio, &pic_ops, s, "etraxfs-pic", R_MAX * 4);
|
||||
sysbus_init_mmio(dev, &s->mmio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Property etraxfs_pic_properties[] = {
|
||||
DEFINE_PROP_PTR("interrupt_vector", struct etrax_pic, interrupt_vector),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void etraxfs_pic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = etraxfs_pic_init;
|
||||
dc->props = etraxfs_pic_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo etraxfs_pic_info = {
|
||||
.name = "etraxfs,pic",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(struct etrax_pic),
|
||||
.class_init = etraxfs_pic_class_init,
|
||||
};
|
||||
|
||||
static void etraxfs_pic_register_types(void)
|
||||
{
|
||||
type_register_static(&etraxfs_pic_info);
|
||||
}
|
||||
|
||||
type_init(etraxfs_pic_register_types)
|
455
hw/intc/exynos4210_combiner.c
Normal file
455
hw/intc/exynos4210_combiner.c
Normal file
|
@ -0,0 +1,455 @@
|
|||
/*
|
||||
* Samsung exynos4210 Interrupt Combiner
|
||||
*
|
||||
* Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Evgeny Voevodin <e.voevodin@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Exynos4210 Combiner represents an OR gate for SOC's IRQ lines. It combines
|
||||
* IRQ sources into groups and provides signal output to GIC from each group. It
|
||||
* is driven by common mask and enable/disable logic. Take a note that not all
|
||||
* IRQs are passed to GIC through Combiner.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
#include "hw/arm/exynos4210.h"
|
||||
|
||||
//#define DEBUG_COMBINER
|
||||
|
||||
#ifdef DEBUG_COMBINER
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \
|
||||
## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#define IIC_NGRP 64 /* Internal Interrupt Combiner
|
||||
Groups number */
|
||||
#define IIC_NIRQ (IIC_NGRP * 8)/* Internal Interrupt Combiner
|
||||
Interrupts number */
|
||||
#define IIC_REGION_SIZE 0x108 /* Size of memory mapped region */
|
||||
#define IIC_REGSET_SIZE 0x41
|
||||
|
||||
/*
|
||||
* State for each output signal of internal combiner
|
||||
*/
|
||||
typedef struct CombinerGroupState {
|
||||
uint8_t src_mask; /* 1 - source enabled, 0 - disabled */
|
||||
uint8_t src_pending; /* Pending source interrupts before masking */
|
||||
} CombinerGroupState;
|
||||
|
||||
typedef struct Exynos4210CombinerState {
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion iomem;
|
||||
|
||||
struct CombinerGroupState group[IIC_NGRP];
|
||||
uint32_t reg_set[IIC_REGSET_SIZE];
|
||||
uint32_t icipsr[2];
|
||||
uint32_t external; /* 1 means that this combiner is external */
|
||||
|
||||
qemu_irq output_irq[IIC_NGRP];
|
||||
} Exynos4210CombinerState;
|
||||
|
||||
static const VMStateDescription vmstate_exynos4210_combiner_group_state = {
|
||||
.name = "exynos4210.combiner.groupstate",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT8(src_mask, CombinerGroupState),
|
||||
VMSTATE_UINT8(src_pending, CombinerGroupState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_exynos4210_combiner = {
|
||||
.name = "exynos4210.combiner",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0,
|
||||
vmstate_exynos4210_combiner_group_state, CombinerGroupState),
|
||||
VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState,
|
||||
IIC_REGSET_SIZE),
|
||||
VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2),
|
||||
VMSTATE_UINT32(external, Exynos4210CombinerState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Get Combiner input GPIO into irqs structure
|
||||
*/
|
||||
void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev,
|
||||
int ext)
|
||||
{
|
||||
int n;
|
||||
int bit;
|
||||
int max;
|
||||
qemu_irq *irq;
|
||||
|
||||
max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ :
|
||||
EXYNOS4210_MAX_INT_COMBINER_IN_IRQ;
|
||||
irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq;
|
||||
|
||||
/*
|
||||
* Some IRQs of Int/External Combiner are going to two Combiners groups,
|
||||
* so let split them.
|
||||
*/
|
||||
for (n = 0; n < max; n++) {
|
||||
|
||||
bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
|
||||
|
||||
switch (n) {
|
||||
/* MDNIE_LCD1 INTG1 */
|
||||
case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ...
|
||||
EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3):
|
||||
irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
|
||||
irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]);
|
||||
continue;
|
||||
|
||||
/* TMU INTG3 */
|
||||
case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4):
|
||||
irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
|
||||
irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]);
|
||||
continue;
|
||||
|
||||
/* LCD1 INTG12 */
|
||||
case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ...
|
||||
EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3):
|
||||
irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
|
||||
irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]);
|
||||
continue;
|
||||
|
||||
/* Multi-Core Timer INTG12 */
|
||||
case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ...
|
||||
EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8):
|
||||
irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
|
||||
irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
|
||||
continue;
|
||||
|
||||
/* Multi-Core Timer INTG35 */
|
||||
case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ...
|
||||
EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8):
|
||||
irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
|
||||
irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
|
||||
continue;
|
||||
|
||||
/* Multi-Core Timer INTG51 */
|
||||
case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ...
|
||||
EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8):
|
||||
irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
|
||||
irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
|
||||
continue;
|
||||
|
||||
/* Multi-Core Timer INTG53 */
|
||||
case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ...
|
||||
EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8):
|
||||
irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
|
||||
irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
|
||||
continue;
|
||||
}
|
||||
|
||||
irq[n] = qdev_get_gpio_in(dev, n);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
exynos4210_combiner_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
struct Exynos4210CombinerState *s =
|
||||
(struct Exynos4210CombinerState *)opaque;
|
||||
uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and
|
||||
get a start of corresponding group quad */
|
||||
uint32_t grp_quad_base_n; /* Base of group quad */
|
||||
uint32_t reg_n; /* Register number inside the quad */
|
||||
uint32_t val;
|
||||
|
||||
req_quad_base_n = offset >> 4;
|
||||
grp_quad_base_n = req_quad_base_n << 2;
|
||||
reg_n = (offset - (req_quad_base_n << 4)) >> 2;
|
||||
|
||||
if (req_quad_base_n >= IIC_NGRP) {
|
||||
/* Read of ICIPSR register */
|
||||
return s->icipsr[reg_n];
|
||||
}
|
||||
|
||||
val = 0;
|
||||
|
||||
switch (reg_n) {
|
||||
/* IISTR */
|
||||
case 2:
|
||||
val |= s->group[grp_quad_base_n].src_pending;
|
||||
val |= s->group[grp_quad_base_n + 1].src_pending << 8;
|
||||
val |= s->group[grp_quad_base_n + 2].src_pending << 16;
|
||||
val |= s->group[grp_quad_base_n + 3].src_pending << 24;
|
||||
break;
|
||||
/* IIMSR */
|
||||
case 3:
|
||||
val |= s->group[grp_quad_base_n].src_mask &
|
||||
s->group[grp_quad_base_n].src_pending;
|
||||
val |= (s->group[grp_quad_base_n + 1].src_mask &
|
||||
s->group[grp_quad_base_n + 1].src_pending) << 8;
|
||||
val |= (s->group[grp_quad_base_n + 2].src_mask &
|
||||
s->group[grp_quad_base_n + 2].src_pending) << 16;
|
||||
val |= (s->group[grp_quad_base_n + 3].src_mask &
|
||||
s->group[grp_quad_base_n + 3].src_pending) << 24;
|
||||
break;
|
||||
default:
|
||||
if (offset >> 2 >= IIC_REGSET_SIZE) {
|
||||
hw_error("exynos4210.combiner: overflow of reg_set by 0x"
|
||||
TARGET_FMT_plx "offset\n", offset);
|
||||
}
|
||||
val = s->reg_set[offset >> 2];
|
||||
return 0;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static void exynos4210_combiner_update(void *opaque, uint8_t group_n)
|
||||
{
|
||||
struct Exynos4210CombinerState *s =
|
||||
(struct Exynos4210CombinerState *)opaque;
|
||||
|
||||
/* Send interrupt if needed */
|
||||
if (s->group[group_n].src_mask & s->group[group_n].src_pending) {
|
||||
#ifdef DEBUG_COMBINER
|
||||
if (group_n != 26) {
|
||||
/* skip uart */
|
||||
DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Set Combiner interrupt pending status after masking */
|
||||
if (group_n >= 32) {
|
||||
s->icipsr[1] |= 1 << (group_n - 32);
|
||||
} else {
|
||||
s->icipsr[0] |= 1 << group_n;
|
||||
}
|
||||
|
||||
qemu_irq_raise(s->output_irq[group_n]);
|
||||
} else {
|
||||
#ifdef DEBUG_COMBINER
|
||||
if (group_n != 26) {
|
||||
/* skip uart */
|
||||
DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Set Combiner interrupt pending status after masking */
|
||||
if (group_n >= 32) {
|
||||
s->icipsr[1] &= ~(1 << (group_n - 32));
|
||||
} else {
|
||||
s->icipsr[0] &= ~(1 << group_n);
|
||||
}
|
||||
|
||||
qemu_irq_lower(s->output_irq[group_n]);
|
||||
}
|
||||
}
|
||||
|
||||
static void exynos4210_combiner_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
struct Exynos4210CombinerState *s =
|
||||
(struct Exynos4210CombinerState *)opaque;
|
||||
uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and
|
||||
get a start of corresponding group quad */
|
||||
uint32_t grp_quad_base_n; /* Base of group quad */
|
||||
uint32_t reg_n; /* Register number inside the quad */
|
||||
|
||||
req_quad_base_n = offset >> 4;
|
||||
grp_quad_base_n = req_quad_base_n << 2;
|
||||
reg_n = (offset - (req_quad_base_n << 4)) >> 2;
|
||||
|
||||
if (req_quad_base_n >= IIC_NGRP) {
|
||||
hw_error("exynos4210.combiner: unallowed write access at offset 0x"
|
||||
TARGET_FMT_plx "\n", offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (reg_n > 1) {
|
||||
hw_error("exynos4210.combiner: unallowed write access at offset 0x"
|
||||
TARGET_FMT_plx "\n", offset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (offset >> 2 >= IIC_REGSET_SIZE) {
|
||||
hw_error("exynos4210.combiner: overflow of reg_set by 0x"
|
||||
TARGET_FMT_plx "offset\n", offset);
|
||||
}
|
||||
s->reg_set[offset >> 2] = val;
|
||||
|
||||
switch (reg_n) {
|
||||
/* IIESR */
|
||||
case 0:
|
||||
/* FIXME: what if irq is pending, allowed by mask, and we allow it
|
||||
* again. Interrupt will rise again! */
|
||||
|
||||
DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n",
|
||||
s->external ? "EXT" : "INT",
|
||||
grp_quad_base_n,
|
||||
grp_quad_base_n + 1,
|
||||
grp_quad_base_n + 2,
|
||||
grp_quad_base_n + 3);
|
||||
|
||||
/* Enable interrupt sources */
|
||||
s->group[grp_quad_base_n].src_mask |= val & 0xFF;
|
||||
s->group[grp_quad_base_n + 1].src_mask |= (val & 0xFF00) >> 8;
|
||||
s->group[grp_quad_base_n + 2].src_mask |= (val & 0xFF0000) >> 16;
|
||||
s->group[grp_quad_base_n + 3].src_mask |= (val & 0xFF000000) >> 24;
|
||||
|
||||
exynos4210_combiner_update(s, grp_quad_base_n);
|
||||
exynos4210_combiner_update(s, grp_quad_base_n + 1);
|
||||
exynos4210_combiner_update(s, grp_quad_base_n + 2);
|
||||
exynos4210_combiner_update(s, grp_quad_base_n + 3);
|
||||
break;
|
||||
/* IIECR */
|
||||
case 1:
|
||||
DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n",
|
||||
s->external ? "EXT" : "INT",
|
||||
grp_quad_base_n,
|
||||
grp_quad_base_n + 1,
|
||||
grp_quad_base_n + 2,
|
||||
grp_quad_base_n + 3);
|
||||
|
||||
/* Disable interrupt sources */
|
||||
s->group[grp_quad_base_n].src_mask &= ~(val & 0xFF);
|
||||
s->group[grp_quad_base_n + 1].src_mask &= ~((val & 0xFF00) >> 8);
|
||||
s->group[grp_quad_base_n + 2].src_mask &= ~((val & 0xFF0000) >> 16);
|
||||
s->group[grp_quad_base_n + 3].src_mask &= ~((val & 0xFF000000) >> 24);
|
||||
|
||||
exynos4210_combiner_update(s, grp_quad_base_n);
|
||||
exynos4210_combiner_update(s, grp_quad_base_n + 1);
|
||||
exynos4210_combiner_update(s, grp_quad_base_n + 2);
|
||||
exynos4210_combiner_update(s, grp_quad_base_n + 3);
|
||||
break;
|
||||
default:
|
||||
hw_error("exynos4210.combiner: unallowed write access at offset 0x"
|
||||
TARGET_FMT_plx "\n", offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get combiner group and bit from irq number */
|
||||
static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit)
|
||||
{
|
||||
*bit = irq - ((irq >> 3) << 3);
|
||||
return irq >> 3;
|
||||
}
|
||||
|
||||
/* Process a change in an external IRQ input. */
|
||||
static void exynos4210_combiner_handler(void *opaque, int irq, int level)
|
||||
{
|
||||
struct Exynos4210CombinerState *s =
|
||||
(struct Exynos4210CombinerState *)opaque;
|
||||
uint8_t bit_n, group_n;
|
||||
|
||||
group_n = get_combiner_group_and_bit(irq, &bit_n);
|
||||
|
||||
if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) {
|
||||
DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT"
|
||||
, group_n);
|
||||
return;
|
||||
}
|
||||
|
||||
if (level) {
|
||||
s->group[group_n].src_pending |= 1 << bit_n;
|
||||
} else {
|
||||
s->group[group_n].src_pending &= ~(1 << bit_n);
|
||||
}
|
||||
|
||||
exynos4210_combiner_update(s, group_n);
|
||||
}
|
||||
|
||||
static void exynos4210_combiner_reset(DeviceState *d)
|
||||
{
|
||||
struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d;
|
||||
|
||||
memset(&s->group, 0, sizeof(s->group));
|
||||
memset(&s->reg_set, 0, sizeof(s->reg_set));
|
||||
|
||||
s->reg_set[0xC0 >> 2] = 0x01010101;
|
||||
s->reg_set[0xC4 >> 2] = 0x01010101;
|
||||
s->reg_set[0xD0 >> 2] = 0x01010101;
|
||||
s->reg_set[0xD4 >> 2] = 0x01010101;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps exynos4210_combiner_ops = {
|
||||
.read = exynos4210_combiner_read,
|
||||
.write = exynos4210_combiner_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
/*
|
||||
* Internal Combiner initialization.
|
||||
*/
|
||||
static int exynos4210_combiner_init(SysBusDevice *dev)
|
||||
{
|
||||
unsigned int i;
|
||||
struct Exynos4210CombinerState *s =
|
||||
FROM_SYSBUS(struct Exynos4210CombinerState, dev);
|
||||
|
||||
/* Allocate general purpose input signals and connect a handler to each of
|
||||
* them */
|
||||
qdev_init_gpio_in(&s->busdev.qdev, exynos4210_combiner_handler, IIC_NIRQ);
|
||||
|
||||
/* Connect SysBusDev irqs to device specific irqs */
|
||||
for (i = 0; i < IIC_NIRQ; i++) {
|
||||
sysbus_init_irq(dev, &s->output_irq[i]);
|
||||
}
|
||||
|
||||
memory_region_init_io(&s->iomem, &exynos4210_combiner_ops, s,
|
||||
"exynos4210-combiner", IIC_REGION_SIZE);
|
||||
sysbus_init_mmio(dev, &s->iomem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Property exynos4210_combiner_properties[] = {
|
||||
DEFINE_PROP_UINT32("external", Exynos4210CombinerState, external, 0),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void exynos4210_combiner_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = exynos4210_combiner_init;
|
||||
dc->reset = exynos4210_combiner_reset;
|
||||
dc->props = exynos4210_combiner_properties;
|
||||
dc->vmsd = &vmstate_exynos4210_combiner;
|
||||
}
|
||||
|
||||
static const TypeInfo exynos4210_combiner_info = {
|
||||
.name = "exynos4210.combiner",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(Exynos4210CombinerState),
|
||||
.class_init = exynos4210_combiner_class_init,
|
||||
};
|
||||
|
||||
static void exynos4210_combiner_register_types(void)
|
||||
{
|
||||
type_register_static(&exynos4210_combiner_info);
|
||||
}
|
||||
|
||||
type_init(exynos4210_combiner_register_types)
|
462
hw/intc/exynos4210_gic.c
Normal file
462
hw/intc/exynos4210_gic.c
Normal file
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
* Samsung exynos4210 GIC implementation. Based on hw/arm_gic.c
|
||||
*
|
||||
* Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Evgeny Voevodin <e.voevodin@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "qemu-common.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/arm/exynos4210.h"
|
||||
|
||||
enum ExtGicId {
|
||||
EXT_GIC_ID_MDMA_LCD0 = 66,
|
||||
EXT_GIC_ID_PDMA0,
|
||||
EXT_GIC_ID_PDMA1,
|
||||
EXT_GIC_ID_TIMER0,
|
||||
EXT_GIC_ID_TIMER1,
|
||||
EXT_GIC_ID_TIMER2,
|
||||
EXT_GIC_ID_TIMER3,
|
||||
EXT_GIC_ID_TIMER4,
|
||||
EXT_GIC_ID_MCT_L0,
|
||||
EXT_GIC_ID_WDT,
|
||||
EXT_GIC_ID_RTC_ALARM,
|
||||
EXT_GIC_ID_RTC_TIC,
|
||||
EXT_GIC_ID_GPIO_XB,
|
||||
EXT_GIC_ID_GPIO_XA,
|
||||
EXT_GIC_ID_MCT_L1,
|
||||
EXT_GIC_ID_IEM_APC,
|
||||
EXT_GIC_ID_IEM_IEC,
|
||||
EXT_GIC_ID_NFC,
|
||||
EXT_GIC_ID_UART0,
|
||||
EXT_GIC_ID_UART1,
|
||||
EXT_GIC_ID_UART2,
|
||||
EXT_GIC_ID_UART3,
|
||||
EXT_GIC_ID_UART4,
|
||||
EXT_GIC_ID_MCT_G0,
|
||||
EXT_GIC_ID_I2C0,
|
||||
EXT_GIC_ID_I2C1,
|
||||
EXT_GIC_ID_I2C2,
|
||||
EXT_GIC_ID_I2C3,
|
||||
EXT_GIC_ID_I2C4,
|
||||
EXT_GIC_ID_I2C5,
|
||||
EXT_GIC_ID_I2C6,
|
||||
EXT_GIC_ID_I2C7,
|
||||
EXT_GIC_ID_SPI0,
|
||||
EXT_GIC_ID_SPI1,
|
||||
EXT_GIC_ID_SPI2,
|
||||
EXT_GIC_ID_MCT_G1,
|
||||
EXT_GIC_ID_USB_HOST,
|
||||
EXT_GIC_ID_USB_DEVICE,
|
||||
EXT_GIC_ID_MODEMIF,
|
||||
EXT_GIC_ID_HSMMC0,
|
||||
EXT_GIC_ID_HSMMC1,
|
||||
EXT_GIC_ID_HSMMC2,
|
||||
EXT_GIC_ID_HSMMC3,
|
||||
EXT_GIC_ID_SDMMC,
|
||||
EXT_GIC_ID_MIPI_CSI_4LANE,
|
||||
EXT_GIC_ID_MIPI_DSI_4LANE,
|
||||
EXT_GIC_ID_MIPI_CSI_2LANE,
|
||||
EXT_GIC_ID_MIPI_DSI_2LANE,
|
||||
EXT_GIC_ID_ONENAND_AUDI,
|
||||
EXT_GIC_ID_ROTATOR,
|
||||
EXT_GIC_ID_FIMC0,
|
||||
EXT_GIC_ID_FIMC1,
|
||||
EXT_GIC_ID_FIMC2,
|
||||
EXT_GIC_ID_FIMC3,
|
||||
EXT_GIC_ID_JPEG,
|
||||
EXT_GIC_ID_2D,
|
||||
EXT_GIC_ID_PCIe,
|
||||
EXT_GIC_ID_MIXER,
|
||||
EXT_GIC_ID_HDMI,
|
||||
EXT_GIC_ID_HDMI_I2C,
|
||||
EXT_GIC_ID_MFC,
|
||||
EXT_GIC_ID_TVENC,
|
||||
};
|
||||
|
||||
enum ExtInt {
|
||||
EXT_GIC_ID_EXTINT0 = 48,
|
||||
EXT_GIC_ID_EXTINT1,
|
||||
EXT_GIC_ID_EXTINT2,
|
||||
EXT_GIC_ID_EXTINT3,
|
||||
EXT_GIC_ID_EXTINT4,
|
||||
EXT_GIC_ID_EXTINT5,
|
||||
EXT_GIC_ID_EXTINT6,
|
||||
EXT_GIC_ID_EXTINT7,
|
||||
EXT_GIC_ID_EXTINT8,
|
||||
EXT_GIC_ID_EXTINT9,
|
||||
EXT_GIC_ID_EXTINT10,
|
||||
EXT_GIC_ID_EXTINT11,
|
||||
EXT_GIC_ID_EXTINT12,
|
||||
EXT_GIC_ID_EXTINT13,
|
||||
EXT_GIC_ID_EXTINT14,
|
||||
EXT_GIC_ID_EXTINT15
|
||||
};
|
||||
|
||||
/*
|
||||
* External GIC sources which are not from External Interrupt Combiner or
|
||||
* External Interrupts are starting from EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ,
|
||||
* which is INTG16 in Internal Interrupt Combiner.
|
||||
*/
|
||||
|
||||
static uint32_t
|
||||
combiner_grp_to_gic_id[64-EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = {
|
||||
/* int combiner groups 16-19 */
|
||||
{ }, { }, { }, { },
|
||||
/* int combiner group 20 */
|
||||
{ 0, EXT_GIC_ID_MDMA_LCD0 },
|
||||
/* int combiner group 21 */
|
||||
{ EXT_GIC_ID_PDMA0, EXT_GIC_ID_PDMA1 },
|
||||
/* int combiner group 22 */
|
||||
{ EXT_GIC_ID_TIMER0, EXT_GIC_ID_TIMER1, EXT_GIC_ID_TIMER2,
|
||||
EXT_GIC_ID_TIMER3, EXT_GIC_ID_TIMER4 },
|
||||
/* int combiner group 23 */
|
||||
{ EXT_GIC_ID_RTC_ALARM, EXT_GIC_ID_RTC_TIC },
|
||||
/* int combiner group 24 */
|
||||
{ EXT_GIC_ID_GPIO_XB, EXT_GIC_ID_GPIO_XA },
|
||||
/* int combiner group 25 */
|
||||
{ EXT_GIC_ID_IEM_APC, EXT_GIC_ID_IEM_IEC },
|
||||
/* int combiner group 26 */
|
||||
{ EXT_GIC_ID_UART0, EXT_GIC_ID_UART1, EXT_GIC_ID_UART2, EXT_GIC_ID_UART3,
|
||||
EXT_GIC_ID_UART4 },
|
||||
/* int combiner group 27 */
|
||||
{ EXT_GIC_ID_I2C0, EXT_GIC_ID_I2C1, EXT_GIC_ID_I2C2, EXT_GIC_ID_I2C3,
|
||||
EXT_GIC_ID_I2C4, EXT_GIC_ID_I2C5, EXT_GIC_ID_I2C6,
|
||||
EXT_GIC_ID_I2C7 },
|
||||
/* int combiner group 28 */
|
||||
{ EXT_GIC_ID_SPI0, EXT_GIC_ID_SPI1, EXT_GIC_ID_SPI2 , EXT_GIC_ID_USB_HOST},
|
||||
/* int combiner group 29 */
|
||||
{ EXT_GIC_ID_HSMMC0, EXT_GIC_ID_HSMMC1, EXT_GIC_ID_HSMMC2,
|
||||
EXT_GIC_ID_HSMMC3, EXT_GIC_ID_SDMMC },
|
||||
/* int combiner group 30 */
|
||||
{ EXT_GIC_ID_MIPI_CSI_4LANE, EXT_GIC_ID_MIPI_CSI_2LANE },
|
||||
/* int combiner group 31 */
|
||||
{ EXT_GIC_ID_MIPI_DSI_4LANE, EXT_GIC_ID_MIPI_DSI_2LANE },
|
||||
/* int combiner group 32 */
|
||||
{ EXT_GIC_ID_FIMC0, EXT_GIC_ID_FIMC1 },
|
||||
/* int combiner group 33 */
|
||||
{ EXT_GIC_ID_FIMC2, EXT_GIC_ID_FIMC3 },
|
||||
/* int combiner group 34 */
|
||||
{ EXT_GIC_ID_ONENAND_AUDI, EXT_GIC_ID_NFC },
|
||||
/* int combiner group 35 */
|
||||
{ 0, 0, 0, EXT_GIC_ID_MCT_L1, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 },
|
||||
/* int combiner group 36 */
|
||||
{ EXT_GIC_ID_MIXER },
|
||||
/* int combiner group 37 */
|
||||
{ EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6,
|
||||
EXT_GIC_ID_EXTINT7 },
|
||||
/* groups 38-50 */
|
||||
{ }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { },
|
||||
/* int combiner group 51 */
|
||||
{ EXT_GIC_ID_MCT_L0, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 },
|
||||
/* group 52 */
|
||||
{ },
|
||||
/* int combiner group 53 */
|
||||
{ EXT_GIC_ID_WDT, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 },
|
||||
/* groups 54-63 */
|
||||
{ }, { }, { }, { }, { }, { }, { }, { }, { }, { }
|
||||
};
|
||||
|
||||
#define EXYNOS4210_GIC_NIRQ 160
|
||||
|
||||
#define EXYNOS4210_EXT_GIC_CPU_REGION_SIZE 0x10000
|
||||
#define EXYNOS4210_EXT_GIC_DIST_REGION_SIZE 0x10000
|
||||
|
||||
#define EXYNOS4210_EXT_GIC_PER_CPU_OFFSET 0x8000
|
||||
#define EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(n) \
|
||||
((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET)
|
||||
#define EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(n) \
|
||||
((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET)
|
||||
|
||||
#define EXYNOS4210_GIC_CPU_REGION_SIZE 0x100
|
||||
#define EXYNOS4210_GIC_DIST_REGION_SIZE 0x1000
|
||||
|
||||
static void exynos4210_irq_handler(void *opaque, int irq, int level)
|
||||
{
|
||||
Exynos4210Irq *s = (Exynos4210Irq *)opaque;
|
||||
|
||||
/* Bypass */
|
||||
qemu_set_irq(s->board_irqs[irq], level);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize exynos4210 IRQ subsystem stub.
|
||||
*/
|
||||
qemu_irq *exynos4210_init_irq(Exynos4210Irq *s)
|
||||
{
|
||||
return qemu_allocate_irqs(exynos4210_irq_handler, s,
|
||||
EXYNOS4210_MAX_INT_COMBINER_IN_IRQ);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize board IRQs.
|
||||
* These IRQs contain splitted Int/External Combiner and External Gic IRQs.
|
||||
*/
|
||||
void exynos4210_init_board_irqs(Exynos4210Irq *s)
|
||||
{
|
||||
uint32_t grp, bit, irq_id, n;
|
||||
|
||||
for (n = 0; n < EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ; n++) {
|
||||
s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
|
||||
s->ext_combiner_irq[n]);
|
||||
|
||||
irq_id = 0;
|
||||
if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 4) ||
|
||||
n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4)) {
|
||||
/* MCT_G0 is passed to External GIC */
|
||||
irq_id = EXT_GIC_ID_MCT_G0;
|
||||
}
|
||||
if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 5) ||
|
||||
n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 5)) {
|
||||
/* MCT_G1 is passed to External and GIC */
|
||||
irq_id = EXT_GIC_ID_MCT_G1;
|
||||
}
|
||||
if (irq_id) {
|
||||
s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
|
||||
s->ext_gic_irq[irq_id-32]);
|
||||
}
|
||||
|
||||
}
|
||||
for (; n < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) {
|
||||
/* these IDs are passed to Internal Combiner and External GIC */
|
||||
grp = EXYNOS4210_COMBINER_GET_GRP_NUM(n);
|
||||
bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
|
||||
irq_id = combiner_grp_to_gic_id[grp -
|
||||
EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][bit];
|
||||
|
||||
if (irq_id) {
|
||||
s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
|
||||
s->ext_gic_irq[irq_id-32]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get IRQ number from exynos4210 IRQ subsystem stub.
|
||||
* To identify IRQ source use internal combiner group and bit number
|
||||
* grp - group number
|
||||
* bit - bit number inside group
|
||||
*/
|
||||
uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit)
|
||||
{
|
||||
return EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit);
|
||||
}
|
||||
|
||||
/********* GIC part *********/
|
||||
|
||||
typedef struct {
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion cpu_container;
|
||||
MemoryRegion dist_container;
|
||||
MemoryRegion cpu_alias[EXYNOS4210_NCPUS];
|
||||
MemoryRegion dist_alias[EXYNOS4210_NCPUS];
|
||||
uint32_t num_cpu;
|
||||
DeviceState *gic;
|
||||
} Exynos4210GicState;
|
||||
|
||||
static void exynos4210_gic_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
Exynos4210GicState *s = (Exynos4210GicState *)opaque;
|
||||
qemu_set_irq(qdev_get_gpio_in(s->gic, irq), level);
|
||||
}
|
||||
|
||||
static int exynos4210_gic_init(SysBusDevice *dev)
|
||||
{
|
||||
Exynos4210GicState *s = FROM_SYSBUS(Exynos4210GicState, dev);
|
||||
uint32_t i;
|
||||
const char cpu_prefix[] = "exynos4210-gic-alias_cpu";
|
||||
const char dist_prefix[] = "exynos4210-gic-alias_dist";
|
||||
char cpu_alias_name[sizeof(cpu_prefix) + 3];
|
||||
char dist_alias_name[sizeof(cpu_prefix) + 3];
|
||||
SysBusDevice *busdev;
|
||||
|
||||
s->gic = qdev_create(NULL, "arm_gic");
|
||||
qdev_prop_set_uint32(s->gic, "num-cpu", s->num_cpu);
|
||||
qdev_prop_set_uint32(s->gic, "num-irq", EXYNOS4210_GIC_NIRQ);
|
||||
qdev_init_nofail(s->gic);
|
||||
busdev = SYS_BUS_DEVICE(s->gic);
|
||||
|
||||
/* Pass through outbound IRQ lines from the GIC */
|
||||
sysbus_pass_irq(dev, busdev);
|
||||
|
||||
/* Pass through inbound GPIO lines to the GIC */
|
||||
qdev_init_gpio_in(&s->busdev.qdev, exynos4210_gic_set_irq,
|
||||
EXYNOS4210_GIC_NIRQ - 32);
|
||||
|
||||
memory_region_init(&s->cpu_container, "exynos4210-cpu-container",
|
||||
EXYNOS4210_EXT_GIC_CPU_REGION_SIZE);
|
||||
memory_region_init(&s->dist_container, "exynos4210-dist-container",
|
||||
EXYNOS4210_EXT_GIC_DIST_REGION_SIZE);
|
||||
|
||||
for (i = 0; i < s->num_cpu; i++) {
|
||||
/* Map CPU interface per SMP Core */
|
||||
sprintf(cpu_alias_name, "%s%x", cpu_prefix, i);
|
||||
memory_region_init_alias(&s->cpu_alias[i],
|
||||
cpu_alias_name,
|
||||
sysbus_mmio_get_region(busdev, 1),
|
||||
0,
|
||||
EXYNOS4210_GIC_CPU_REGION_SIZE);
|
||||
memory_region_add_subregion(&s->cpu_container,
|
||||
EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(i), &s->cpu_alias[i]);
|
||||
|
||||
/* Map Distributor per SMP Core */
|
||||
sprintf(dist_alias_name, "%s%x", dist_prefix, i);
|
||||
memory_region_init_alias(&s->dist_alias[i],
|
||||
dist_alias_name,
|
||||
sysbus_mmio_get_region(busdev, 0),
|
||||
0,
|
||||
EXYNOS4210_GIC_DIST_REGION_SIZE);
|
||||
memory_region_add_subregion(&s->dist_container,
|
||||
EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(i), &s->dist_alias[i]);
|
||||
}
|
||||
|
||||
sysbus_init_mmio(dev, &s->cpu_container);
|
||||
sysbus_init_mmio(dev, &s->dist_container);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Property exynos4210_gic_properties[] = {
|
||||
DEFINE_PROP_UINT32("num-cpu", Exynos4210GicState, num_cpu, 1),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void exynos4210_gic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = exynos4210_gic_init;
|
||||
dc->props = exynos4210_gic_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo exynos4210_gic_info = {
|
||||
.name = "exynos4210.gic",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(Exynos4210GicState),
|
||||
.class_init = exynos4210_gic_class_init,
|
||||
};
|
||||
|
||||
static void exynos4210_gic_register_types(void)
|
||||
{
|
||||
type_register_static(&exynos4210_gic_info);
|
||||
}
|
||||
|
||||
type_init(exynos4210_gic_register_types)
|
||||
|
||||
/* IRQ OR Gate struct.
|
||||
*
|
||||
* This device models an OR gate. There are n_in input qdev gpio lines and one
|
||||
* output sysbus IRQ line. The output IRQ level is formed as OR between all
|
||||
* gpio inputs.
|
||||
*/
|
||||
typedef struct {
|
||||
SysBusDevice busdev;
|
||||
|
||||
uint32_t n_in; /* inputs amount */
|
||||
uint32_t *level; /* input levels */
|
||||
qemu_irq out; /* output IRQ */
|
||||
} Exynos4210IRQGateState;
|
||||
|
||||
static Property exynos4210_irq_gate_properties[] = {
|
||||
DEFINE_PROP_UINT32("n_in", Exynos4210IRQGateState, n_in, 1),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_exynos4210_irq_gate = {
|
||||
.name = "exynos4210.irq_gate",
|
||||
.version_id = 2,
|
||||
.minimum_version_id = 2,
|
||||
.minimum_version_id_old = 2,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_VBUFFER_UINT32(level, Exynos4210IRQGateState, 1, NULL, 0, n_in),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
/* Process a change in IRQ input. */
|
||||
static void exynos4210_irq_gate_handler(void *opaque, int irq, int level)
|
||||
{
|
||||
Exynos4210IRQGateState *s = (Exynos4210IRQGateState *)opaque;
|
||||
uint32_t i;
|
||||
|
||||
assert(irq < s->n_in);
|
||||
|
||||
s->level[irq] = level;
|
||||
|
||||
for (i = 0; i < s->n_in; i++) {
|
||||
if (s->level[i] >= 1) {
|
||||
qemu_irq_raise(s->out);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qemu_irq_lower(s->out);
|
||||
}
|
||||
|
||||
static void exynos4210_irq_gate_reset(DeviceState *d)
|
||||
{
|
||||
Exynos4210IRQGateState *s =
|
||||
DO_UPCAST(Exynos4210IRQGateState, busdev.qdev, d);
|
||||
|
||||
memset(s->level, 0, s->n_in * sizeof(*s->level));
|
||||
}
|
||||
|
||||
/*
|
||||
* IRQ Gate initialization.
|
||||
*/
|
||||
static int exynos4210_irq_gate_init(SysBusDevice *dev)
|
||||
{
|
||||
Exynos4210IRQGateState *s = FROM_SYSBUS(Exynos4210IRQGateState, dev);
|
||||
|
||||
/* Allocate general purpose input signals and connect a handler to each of
|
||||
* them */
|
||||
qdev_init_gpio_in(&s->busdev.qdev, exynos4210_irq_gate_handler, s->n_in);
|
||||
|
||||
s->level = g_malloc0(s->n_in * sizeof(*s->level));
|
||||
|
||||
sysbus_init_irq(dev, &s->out);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void exynos4210_irq_gate_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = exynos4210_irq_gate_init;
|
||||
dc->reset = exynos4210_irq_gate_reset;
|
||||
dc->vmsd = &vmstate_exynos4210_irq_gate;
|
||||
dc->props = exynos4210_irq_gate_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo exynos4210_irq_gate_info = {
|
||||
.name = "exynos4210.irq_gate",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(Exynos4210IRQGateState),
|
||||
.class_init = exynos4210_irq_gate_class_init,
|
||||
};
|
||||
|
||||
static void exynos4210_irq_gate_register_types(void)
|
||||
{
|
||||
type_register_static(&exynos4210_irq_gate_info);
|
||||
}
|
||||
|
||||
type_init(exynos4210_irq_gate_register_types)
|
385
hw/intc/grlib_irqmp.c
Normal file
385
hw/intc/grlib_irqmp.c
Normal file
|
@ -0,0 +1,385 @@
|
|||
/*
|
||||
* QEMU GRLIB IRQMP Emulator
|
||||
*
|
||||
* (Multiprocessor and extended interrupt not supported)
|
||||
*
|
||||
* Copyright (c) 2010-2011 AdaCore
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "cpu.h"
|
||||
|
||||
#include "hw/sparc/grlib.h"
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
#define IRQMP_MAX_CPU 16
|
||||
#define IRQMP_REG_SIZE 256 /* Size of memory mapped registers */
|
||||
|
||||
/* Memory mapped register offsets */
|
||||
#define LEVEL_OFFSET 0x00
|
||||
#define PENDING_OFFSET 0x04
|
||||
#define FORCE0_OFFSET 0x08
|
||||
#define CLEAR_OFFSET 0x0C
|
||||
#define MP_STATUS_OFFSET 0x10
|
||||
#define BROADCAST_OFFSET 0x14
|
||||
#define MASK_OFFSET 0x40
|
||||
#define FORCE_OFFSET 0x80
|
||||
#define EXTENDED_OFFSET 0xC0
|
||||
|
||||
typedef struct IRQMPState IRQMPState;
|
||||
|
||||
typedef struct IRQMP {
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion iomem;
|
||||
|
||||
void *set_pil_in;
|
||||
void *set_pil_in_opaque;
|
||||
|
||||
IRQMPState *state;
|
||||
} IRQMP;
|
||||
|
||||
struct IRQMPState {
|
||||
uint32_t level;
|
||||
uint32_t pending;
|
||||
uint32_t clear;
|
||||
uint32_t broadcast;
|
||||
|
||||
uint32_t mask[IRQMP_MAX_CPU];
|
||||
uint32_t force[IRQMP_MAX_CPU];
|
||||
uint32_t extended[IRQMP_MAX_CPU];
|
||||
|
||||
IRQMP *parent;
|
||||
};
|
||||
|
||||
static void grlib_irqmp_check_irqs(IRQMPState *state)
|
||||
{
|
||||
uint32_t pend = 0;
|
||||
uint32_t level0 = 0;
|
||||
uint32_t level1 = 0;
|
||||
set_pil_in_fn set_pil_in;
|
||||
|
||||
assert(state != NULL);
|
||||
assert(state->parent != NULL);
|
||||
|
||||
/* IRQ for CPU 0 (no SMP support) */
|
||||
pend = (state->pending | state->force[0])
|
||||
& state->mask[0];
|
||||
|
||||
level0 = pend & ~state->level;
|
||||
level1 = pend & state->level;
|
||||
|
||||
trace_grlib_irqmp_check_irqs(state->pending, state->force[0],
|
||||
state->mask[0], level1, level0);
|
||||
|
||||
set_pil_in = (set_pil_in_fn)state->parent->set_pil_in;
|
||||
|
||||
/* Trigger level1 interrupt first and level0 if there is no level1 */
|
||||
if (level1 != 0) {
|
||||
set_pil_in(state->parent->set_pil_in_opaque, level1);
|
||||
} else {
|
||||
set_pil_in(state->parent->set_pil_in_opaque, level0);
|
||||
}
|
||||
}
|
||||
|
||||
void grlib_irqmp_ack(DeviceState *dev, int intno)
|
||||
{
|
||||
SysBusDevice *sdev;
|
||||
IRQMP *irqmp;
|
||||
IRQMPState *state;
|
||||
uint32_t mask;
|
||||
|
||||
assert(dev != NULL);
|
||||
|
||||
sdev = SYS_BUS_DEVICE(dev);
|
||||
assert(sdev != NULL);
|
||||
|
||||
irqmp = FROM_SYSBUS(typeof(*irqmp), sdev);
|
||||
assert(irqmp != NULL);
|
||||
|
||||
state = irqmp->state;
|
||||
assert(state != NULL);
|
||||
|
||||
intno &= 15;
|
||||
mask = 1 << intno;
|
||||
|
||||
trace_grlib_irqmp_ack(intno);
|
||||
|
||||
/* Clear registers */
|
||||
state->pending &= ~mask;
|
||||
state->force[0] &= ~mask; /* Only CPU 0 (No SMP support) */
|
||||
|
||||
grlib_irqmp_check_irqs(state);
|
||||
}
|
||||
|
||||
void grlib_irqmp_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
IRQMP *irqmp;
|
||||
IRQMPState *s;
|
||||
int i = 0;
|
||||
|
||||
assert(opaque != NULL);
|
||||
|
||||
irqmp = FROM_SYSBUS(typeof(*irqmp), SYS_BUS_DEVICE(opaque));
|
||||
assert(irqmp != NULL);
|
||||
|
||||
s = irqmp->state;
|
||||
assert(s != NULL);
|
||||
assert(s->parent != NULL);
|
||||
|
||||
|
||||
if (level) {
|
||||
trace_grlib_irqmp_set_irq(irq);
|
||||
|
||||
if (s->broadcast & 1 << irq) {
|
||||
/* Broadcasted IRQ */
|
||||
for (i = 0; i < IRQMP_MAX_CPU; i++) {
|
||||
s->force[i] |= 1 << irq;
|
||||
}
|
||||
} else {
|
||||
s->pending |= 1 << irq;
|
||||
}
|
||||
grlib_irqmp_check_irqs(s);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t grlib_irqmp_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
IRQMP *irqmp = opaque;
|
||||
IRQMPState *state;
|
||||
|
||||
assert(irqmp != NULL);
|
||||
state = irqmp->state;
|
||||
assert(state != NULL);
|
||||
|
||||
addr &= 0xff;
|
||||
|
||||
/* global registers */
|
||||
switch (addr) {
|
||||
case LEVEL_OFFSET:
|
||||
return state->level;
|
||||
|
||||
case PENDING_OFFSET:
|
||||
return state->pending;
|
||||
|
||||
case FORCE0_OFFSET:
|
||||
/* This register is an "alias" for the force register of CPU 0 */
|
||||
return state->force[0];
|
||||
|
||||
case CLEAR_OFFSET:
|
||||
case MP_STATUS_OFFSET:
|
||||
/* Always read as 0 */
|
||||
return 0;
|
||||
|
||||
case BROADCAST_OFFSET:
|
||||
return state->broadcast;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* mask registers */
|
||||
if (addr >= MASK_OFFSET && addr < FORCE_OFFSET) {
|
||||
int cpu = (addr - MASK_OFFSET) / 4;
|
||||
assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
|
||||
|
||||
return state->mask[cpu];
|
||||
}
|
||||
|
||||
/* force registers */
|
||||
if (addr >= FORCE_OFFSET && addr < EXTENDED_OFFSET) {
|
||||
int cpu = (addr - FORCE_OFFSET) / 4;
|
||||
assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
|
||||
|
||||
return state->force[cpu];
|
||||
}
|
||||
|
||||
/* extended (not supported) */
|
||||
if (addr >= EXTENDED_OFFSET && addr < IRQMP_REG_SIZE) {
|
||||
int cpu = (addr - EXTENDED_OFFSET) / 4;
|
||||
assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
|
||||
|
||||
return state->extended[cpu];
|
||||
}
|
||||
|
||||
trace_grlib_irqmp_readl_unknown(addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void grlib_irqmp_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
IRQMP *irqmp = opaque;
|
||||
IRQMPState *state;
|
||||
|
||||
assert(irqmp != NULL);
|
||||
state = irqmp->state;
|
||||
assert(state != NULL);
|
||||
|
||||
addr &= 0xff;
|
||||
|
||||
/* global registers */
|
||||
switch (addr) {
|
||||
case LEVEL_OFFSET:
|
||||
value &= 0xFFFF << 1; /* clean up the value */
|
||||
state->level = value;
|
||||
return;
|
||||
|
||||
case PENDING_OFFSET:
|
||||
/* Read Only */
|
||||
return;
|
||||
|
||||
case FORCE0_OFFSET:
|
||||
/* This register is an "alias" for the force register of CPU 0 */
|
||||
|
||||
value &= 0xFFFE; /* clean up the value */
|
||||
state->force[0] = value;
|
||||
grlib_irqmp_check_irqs(irqmp->state);
|
||||
return;
|
||||
|
||||
case CLEAR_OFFSET:
|
||||
value &= ~1; /* clean up the value */
|
||||
state->pending &= ~value;
|
||||
return;
|
||||
|
||||
case MP_STATUS_OFFSET:
|
||||
/* Read Only (no SMP support) */
|
||||
return;
|
||||
|
||||
case BROADCAST_OFFSET:
|
||||
value &= 0xFFFE; /* clean up the value */
|
||||
state->broadcast = value;
|
||||
return;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* mask registers */
|
||||
if (addr >= MASK_OFFSET && addr < FORCE_OFFSET) {
|
||||
int cpu = (addr - MASK_OFFSET) / 4;
|
||||
assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
|
||||
|
||||
value &= ~1; /* clean up the value */
|
||||
state->mask[cpu] = value;
|
||||
grlib_irqmp_check_irqs(irqmp->state);
|
||||
return;
|
||||
}
|
||||
|
||||
/* force registers */
|
||||
if (addr >= FORCE_OFFSET && addr < EXTENDED_OFFSET) {
|
||||
int cpu = (addr - FORCE_OFFSET) / 4;
|
||||
assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
|
||||
|
||||
uint32_t force = value & 0xFFFE;
|
||||
uint32_t clear = (value >> 16) & 0xFFFE;
|
||||
uint32_t old = state->force[cpu];
|
||||
|
||||
state->force[cpu] = (old | force) & ~clear;
|
||||
grlib_irqmp_check_irqs(irqmp->state);
|
||||
return;
|
||||
}
|
||||
|
||||
/* extended (not supported) */
|
||||
if (addr >= EXTENDED_OFFSET && addr < IRQMP_REG_SIZE) {
|
||||
int cpu = (addr - EXTENDED_OFFSET) / 4;
|
||||
assert(cpu >= 0 && cpu < IRQMP_MAX_CPU);
|
||||
|
||||
value &= 0xF; /* clean up the value */
|
||||
state->extended[cpu] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
trace_grlib_irqmp_writel_unknown(addr, value);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps grlib_irqmp_ops = {
|
||||
.read = grlib_irqmp_read,
|
||||
.write = grlib_irqmp_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static void grlib_irqmp_reset(DeviceState *d)
|
||||
{
|
||||
IRQMP *irqmp = container_of(d, IRQMP, busdev.qdev);
|
||||
assert(irqmp != NULL);
|
||||
assert(irqmp->state != NULL);
|
||||
|
||||
memset(irqmp->state, 0, sizeof *irqmp->state);
|
||||
irqmp->state->parent = irqmp;
|
||||
}
|
||||
|
||||
static int grlib_irqmp_init(SysBusDevice *dev)
|
||||
{
|
||||
IRQMP *irqmp = FROM_SYSBUS(typeof(*irqmp), dev);
|
||||
|
||||
assert(irqmp != NULL);
|
||||
|
||||
/* Check parameters */
|
||||
if (irqmp->set_pil_in == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memory_region_init_io(&irqmp->iomem, &grlib_irqmp_ops, irqmp,
|
||||
"irqmp", IRQMP_REG_SIZE);
|
||||
|
||||
irqmp->state = g_malloc0(sizeof *irqmp->state);
|
||||
|
||||
sysbus_init_mmio(dev, &irqmp->iomem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Property grlib_irqmp_properties[] = {
|
||||
DEFINE_PROP_PTR("set_pil_in", IRQMP, set_pil_in),
|
||||
DEFINE_PROP_PTR("set_pil_in_opaque", IRQMP, set_pil_in_opaque),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void grlib_irqmp_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = grlib_irqmp_init;
|
||||
dc->reset = grlib_irqmp_reset;
|
||||
dc->props = grlib_irqmp_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo grlib_irqmp_info = {
|
||||
.name = "grlib,irqmp",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(IRQMP),
|
||||
.class_init = grlib_irqmp_class_init,
|
||||
};
|
||||
|
||||
static void grlib_irqmp_register_types(void)
|
||||
{
|
||||
type_register_static(&grlib_irqmp_info);
|
||||
}
|
||||
|
||||
type_init(grlib_irqmp_register_types)
|
408
hw/intc/imx_avic.c
Normal file
408
hw/intc/imx_avic.c
Normal file
|
@ -0,0 +1,408 @@
|
|||
/*
|
||||
* i.MX31 Vectored Interrupt Controller
|
||||
*
|
||||
* Note this is NOT the PL192 provided by ARM, but
|
||||
* a custom implementation by Freescale.
|
||||
*
|
||||
* Copyright (c) 2008 OKL
|
||||
* Copyright (c) 2011 NICTA Pty Ltd
|
||||
* Originally written by Hans Jiang
|
||||
*
|
||||
* This code is licensed under the GPL version 2 or later. See
|
||||
* the COPYING file in the top-level directory.
|
||||
*
|
||||
* TODO: implement vectors.
|
||||
*/
|
||||
|
||||
#include "hw/hw.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "qemu/host-utils.h"
|
||||
|
||||
#define DEBUG_INT 1
|
||||
#undef DEBUG_INT /* comment out for debugging */
|
||||
|
||||
#ifdef DEBUG_INT
|
||||
#define DPRINTF(fmt, args...) \
|
||||
do { printf("imx_avic: " fmt , ##args); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, args...) do {} while (0)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Define to 1 for messages about attempts to
|
||||
* access unimplemented registers or similar.
|
||||
*/
|
||||
#define DEBUG_IMPLEMENTATION 1
|
||||
#if DEBUG_IMPLEMENTATION
|
||||
# define IPRINTF(fmt, args...) \
|
||||
do { fprintf(stderr, "imx_avic: " fmt, ##args); } while (0)
|
||||
#else
|
||||
# define IPRINTF(fmt, args...) do {} while (0)
|
||||
#endif
|
||||
|
||||
#define IMX_AVIC_NUM_IRQS 64
|
||||
|
||||
/* Interrupt Control Bits */
|
||||
#define ABFLAG (1<<25)
|
||||
#define ABFEN (1<<24)
|
||||
#define NIDIS (1<<22) /* Normal Interrupt disable */
|
||||
#define FIDIS (1<<21) /* Fast interrupt disable */
|
||||
#define NIAD (1<<20) /* Normal Interrupt Arbiter Rise ARM level */
|
||||
#define FIAD (1<<19) /* Fast Interrupt Arbiter Rise ARM level */
|
||||
#define NM (1<<18) /* Normal interrupt mode */
|
||||
|
||||
|
||||
#define PRIO_PER_WORD (sizeof(uint32_t) * 8 / 4)
|
||||
#define PRIO_WORDS (IMX_AVIC_NUM_IRQS/PRIO_PER_WORD)
|
||||
|
||||
typedef struct {
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion iomem;
|
||||
uint64_t pending;
|
||||
uint64_t enabled;
|
||||
uint64_t is_fiq;
|
||||
uint32_t intcntl;
|
||||
uint32_t intmask;
|
||||
qemu_irq irq;
|
||||
qemu_irq fiq;
|
||||
uint32_t prio[PRIO_WORDS]; /* Priorities are 4-bits each */
|
||||
} IMXAVICState;
|
||||
|
||||
static const VMStateDescription vmstate_imx_avic = {
|
||||
.name = "imx-avic",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT64(pending, IMXAVICState),
|
||||
VMSTATE_UINT64(enabled, IMXAVICState),
|
||||
VMSTATE_UINT64(is_fiq, IMXAVICState),
|
||||
VMSTATE_UINT32(intcntl, IMXAVICState),
|
||||
VMSTATE_UINT32(intmask, IMXAVICState),
|
||||
VMSTATE_UINT32_ARRAY(prio, IMXAVICState, PRIO_WORDS),
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
|
||||
static inline int imx_avic_prio(IMXAVICState *s, int irq)
|
||||
{
|
||||
uint32_t word = irq / PRIO_PER_WORD;
|
||||
uint32_t part = 4 * (irq % PRIO_PER_WORD);
|
||||
return 0xf & (s->prio[word] >> part);
|
||||
}
|
||||
|
||||
static inline void imx_avic_set_prio(IMXAVICState *s, int irq, int prio)
|
||||
{
|
||||
uint32_t word = irq / PRIO_PER_WORD;
|
||||
uint32_t part = 4 * (irq % PRIO_PER_WORD);
|
||||
uint32_t mask = ~(0xf << part);
|
||||
s->prio[word] &= mask;
|
||||
s->prio[word] |= prio << part;
|
||||
}
|
||||
|
||||
/* Update interrupts. */
|
||||
static void imx_avic_update(IMXAVICState *s)
|
||||
{
|
||||
int i;
|
||||
uint64_t new = s->pending & s->enabled;
|
||||
uint64_t flags;
|
||||
|
||||
flags = new & s->is_fiq;
|
||||
qemu_set_irq(s->fiq, !!flags);
|
||||
|
||||
flags = new & ~s->is_fiq;
|
||||
if (!flags || (s->intmask == 0x1f)) {
|
||||
qemu_set_irq(s->irq, !!flags);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Take interrupt if there's a pending interrupt with
|
||||
* priority higher than the value of intmask
|
||||
*/
|
||||
for (i = 0; i < IMX_AVIC_NUM_IRQS; i++) {
|
||||
if (flags & (1UL << i)) {
|
||||
if (imx_avic_prio(s, i) > s->intmask) {
|
||||
qemu_set_irq(s->irq, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
qemu_set_irq(s->irq, 0);
|
||||
}
|
||||
|
||||
static void imx_avic_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
IMXAVICState *s = (IMXAVICState *)opaque;
|
||||
|
||||
if (level) {
|
||||
DPRINTF("Raising IRQ %d, prio %d\n",
|
||||
irq, imx_avic_prio(s, irq));
|
||||
s->pending |= (1ULL << irq);
|
||||
} else {
|
||||
DPRINTF("Clearing IRQ %d, prio %d\n",
|
||||
irq, imx_avic_prio(s, irq));
|
||||
s->pending &= ~(1ULL << irq);
|
||||
}
|
||||
|
||||
imx_avic_update(s);
|
||||
}
|
||||
|
||||
|
||||
static uint64_t imx_avic_read(void *opaque,
|
||||
hwaddr offset, unsigned size)
|
||||
{
|
||||
IMXAVICState *s = (IMXAVICState *)opaque;
|
||||
|
||||
|
||||
DPRINTF("read(offset = 0x%x)\n", offset >> 2);
|
||||
switch (offset >> 2) {
|
||||
case 0: /* INTCNTL */
|
||||
return s->intcntl;
|
||||
|
||||
case 1: /* Normal Interrupt Mask Register, NIMASK */
|
||||
return s->intmask;
|
||||
|
||||
case 2: /* Interrupt Enable Number Register, INTENNUM */
|
||||
case 3: /* Interrupt Disable Number Register, INTDISNUM */
|
||||
return 0;
|
||||
|
||||
case 4: /* Interrupt Enabled Number Register High */
|
||||
return s->enabled >> 32;
|
||||
|
||||
case 5: /* Interrupt Enabled Number Register Low */
|
||||
return s->enabled & 0xffffffffULL;
|
||||
|
||||
case 6: /* Interrupt Type Register High */
|
||||
return s->is_fiq >> 32;
|
||||
|
||||
case 7: /* Interrupt Type Register Low */
|
||||
return s->is_fiq & 0xffffffffULL;
|
||||
|
||||
case 8: /* Normal Interrupt Priority Register 7 */
|
||||
case 9: /* Normal Interrupt Priority Register 6 */
|
||||
case 10:/* Normal Interrupt Priority Register 5 */
|
||||
case 11:/* Normal Interrupt Priority Register 4 */
|
||||
case 12:/* Normal Interrupt Priority Register 3 */
|
||||
case 13:/* Normal Interrupt Priority Register 2 */
|
||||
case 14:/* Normal Interrupt Priority Register 1 */
|
||||
case 15:/* Normal Interrupt Priority Register 0 */
|
||||
return s->prio[15-(offset>>2)];
|
||||
|
||||
case 16: /* Normal interrupt vector and status register */
|
||||
{
|
||||
/*
|
||||
* This returns the highest priority
|
||||
* outstanding interrupt. Where there is more than
|
||||
* one pending IRQ with the same priority,
|
||||
* take the highest numbered one.
|
||||
*/
|
||||
uint64_t flags = s->pending & s->enabled & ~s->is_fiq;
|
||||
int i;
|
||||
int prio = -1;
|
||||
int irq = -1;
|
||||
for (i = 63; i >= 0; --i) {
|
||||
if (flags & (1ULL<<i)) {
|
||||
int irq_prio = imx_avic_prio(s, i);
|
||||
if (irq_prio > prio) {
|
||||
irq = i;
|
||||
prio = irq_prio;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (irq >= 0) {
|
||||
imx_avic_set_irq(s, irq, 0);
|
||||
return irq << 16 | prio;
|
||||
}
|
||||
return 0xffffffffULL;
|
||||
}
|
||||
case 17:/* Fast Interrupt vector and status register */
|
||||
{
|
||||
uint64_t flags = s->pending & s->enabled & s->is_fiq;
|
||||
int i = ctz64(flags);
|
||||
if (i < 64) {
|
||||
imx_avic_set_irq(opaque, i, 0);
|
||||
return i;
|
||||
}
|
||||
return 0xffffffffULL;
|
||||
}
|
||||
case 18:/* Interrupt source register high */
|
||||
return s->pending >> 32;
|
||||
|
||||
case 19:/* Interrupt source register low */
|
||||
return s->pending & 0xffffffffULL;
|
||||
|
||||
case 20:/* Interrupt Force Register high */
|
||||
case 21:/* Interrupt Force Register low */
|
||||
return 0;
|
||||
|
||||
case 22:/* Normal Interrupt Pending Register High */
|
||||
return (s->pending & s->enabled & ~s->is_fiq) >> 32;
|
||||
|
||||
case 23:/* Normal Interrupt Pending Register Low */
|
||||
return (s->pending & s->enabled & ~s->is_fiq) & 0xffffffffULL;
|
||||
|
||||
case 24: /* Fast Interrupt Pending Register High */
|
||||
return (s->pending & s->enabled & s->is_fiq) >> 32;
|
||||
|
||||
case 25: /* Fast Interrupt Pending Register Low */
|
||||
return (s->pending & s->enabled & s->is_fiq) & 0xffffffffULL;
|
||||
|
||||
case 0x40: /* AVIC vector 0, use for WFI WAR */
|
||||
return 0x4;
|
||||
|
||||
default:
|
||||
IPRINTF("imx_avic_read: Bad offset 0x%x\n", (int)offset);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void imx_avic_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
IMXAVICState *s = (IMXAVICState *)opaque;
|
||||
|
||||
/* Vector Registers not yet supported */
|
||||
if (offset >= 0x100 && offset <= 0x2fc) {
|
||||
IPRINTF("imx_avic_write to vector register %d ignored\n",
|
||||
(unsigned int)((offset - 0x100) >> 2));
|
||||
return;
|
||||
}
|
||||
|
||||
DPRINTF("imx_avic_write(0x%x) = %x\n",
|
||||
(unsigned int)offset>>2, (unsigned int)val);
|
||||
switch (offset >> 2) {
|
||||
case 0: /* Interrupt Control Register, INTCNTL */
|
||||
s->intcntl = val & (ABFEN | NIDIS | FIDIS | NIAD | FIAD | NM);
|
||||
if (s->intcntl & ABFEN) {
|
||||
s->intcntl &= ~(val & ABFLAG);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: /* Normal Interrupt Mask Register, NIMASK */
|
||||
s->intmask = val & 0x1f;
|
||||
break;
|
||||
|
||||
case 2: /* Interrupt Enable Number Register, INTENNUM */
|
||||
DPRINTF("enable(%d)\n", (int)val);
|
||||
val &= 0x3f;
|
||||
s->enabled |= (1ULL << val);
|
||||
break;
|
||||
|
||||
case 3: /* Interrupt Disable Number Register, INTDISNUM */
|
||||
DPRINTF("disable(%d)\n", (int)val);
|
||||
val &= 0x3f;
|
||||
s->enabled &= ~(1ULL << val);
|
||||
break;
|
||||
|
||||
case 4: /* Interrupt Enable Number Register High */
|
||||
s->enabled = (s->enabled & 0xffffffffULL) | (val << 32);
|
||||
break;
|
||||
|
||||
case 5: /* Interrupt Enable Number Register Low */
|
||||
s->enabled = (s->enabled & 0xffffffff00000000ULL) | val;
|
||||
break;
|
||||
|
||||
case 6: /* Interrupt Type Register High */
|
||||
s->is_fiq = (s->is_fiq & 0xffffffffULL) | (val << 32);
|
||||
break;
|
||||
|
||||
case 7: /* Interrupt Type Register Low */
|
||||
s->is_fiq = (s->is_fiq & 0xffffffff00000000ULL) | val;
|
||||
break;
|
||||
|
||||
case 8: /* Normal Interrupt Priority Register 7 */
|
||||
case 9: /* Normal Interrupt Priority Register 6 */
|
||||
case 10:/* Normal Interrupt Priority Register 5 */
|
||||
case 11:/* Normal Interrupt Priority Register 4 */
|
||||
case 12:/* Normal Interrupt Priority Register 3 */
|
||||
case 13:/* Normal Interrupt Priority Register 2 */
|
||||
case 14:/* Normal Interrupt Priority Register 1 */
|
||||
case 15:/* Normal Interrupt Priority Register 0 */
|
||||
s->prio[15-(offset>>2)] = val;
|
||||
break;
|
||||
|
||||
/* Read-only registers, writes ignored */
|
||||
case 16:/* Normal Interrupt Vector and Status register */
|
||||
case 17:/* Fast Interrupt vector and status register */
|
||||
case 18:/* Interrupt source register high */
|
||||
case 19:/* Interrupt source register low */
|
||||
return;
|
||||
|
||||
case 20:/* Interrupt Force Register high */
|
||||
s->pending = (s->pending & 0xffffffffULL) | (val << 32);
|
||||
break;
|
||||
|
||||
case 21:/* Interrupt Force Register low */
|
||||
s->pending = (s->pending & 0xffffffff00000000ULL) | val;
|
||||
break;
|
||||
|
||||
case 22:/* Normal Interrupt Pending Register High */
|
||||
case 23:/* Normal Interrupt Pending Register Low */
|
||||
case 24: /* Fast Interrupt Pending Register High */
|
||||
case 25: /* Fast Interrupt Pending Register Low */
|
||||
return;
|
||||
|
||||
default:
|
||||
IPRINTF("imx_avic_write: Bad offset %x\n", (int)offset);
|
||||
}
|
||||
imx_avic_update(s);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps imx_avic_ops = {
|
||||
.read = imx_avic_read,
|
||||
.write = imx_avic_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static void imx_avic_reset(DeviceState *dev)
|
||||
{
|
||||
IMXAVICState *s = container_of(dev, IMXAVICState, busdev.qdev);
|
||||
s->pending = 0;
|
||||
s->enabled = 0;
|
||||
s->is_fiq = 0;
|
||||
s->intmask = 0x1f;
|
||||
s->intcntl = 0;
|
||||
memset(s->prio, 0, sizeof s->prio);
|
||||
}
|
||||
|
||||
static int imx_avic_init(SysBusDevice *dev)
|
||||
{
|
||||
IMXAVICState *s = FROM_SYSBUS(IMXAVICState, dev);;
|
||||
|
||||
memory_region_init_io(&s->iomem, &imx_avic_ops, s, "imx_avic", 0x1000);
|
||||
sysbus_init_mmio(dev, &s->iomem);
|
||||
|
||||
qdev_init_gpio_in(&dev->qdev, imx_avic_set_irq, IMX_AVIC_NUM_IRQS);
|
||||
sysbus_init_irq(dev, &s->irq);
|
||||
sysbus_init_irq(dev, &s->fiq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void imx_avic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
k->init = imx_avic_init;
|
||||
dc->vmsd = &vmstate_imx_avic;
|
||||
dc->reset = imx_avic_reset;
|
||||
dc->desc = "i.MX Advanced Vector Interrupt Controller";
|
||||
}
|
||||
|
||||
static const TypeInfo imx_avic_info = {
|
||||
.name = "imx_avic",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(IMXAVICState),
|
||||
.class_init = imx_avic_class_init,
|
||||
};
|
||||
|
||||
static void imx_avic_register_types(void)
|
||||
{
|
||||
type_register_static(&imx_avic_info);
|
||||
}
|
||||
|
||||
type_init(imx_avic_register_types)
|
258
hw/intc/ioapic.c
Normal file
258
hw/intc/ioapic.c
Normal file
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* ioapic.c IOAPIC emulation logic
|
||||
*
|
||||
* Copyright (c) 2004-2005 Fabrice Bellard
|
||||
*
|
||||
* Split the ioapic logic from apic.c
|
||||
* Xiantao Zhang <xiantao.zhang@intel.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hw/hw.h"
|
||||
#include "hw/i386/pc.h"
|
||||
#include "hw/i386/ioapic.h"
|
||||
#include "hw/i386/ioapic_internal.h"
|
||||
|
||||
//#define DEBUG_IOAPIC
|
||||
|
||||
#ifdef DEBUG_IOAPIC
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { printf("ioapic: " fmt , ## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...)
|
||||
#endif
|
||||
|
||||
static IOAPICCommonState *ioapics[MAX_IOAPICS];
|
||||
|
||||
static void ioapic_service(IOAPICCommonState *s)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t trig_mode;
|
||||
uint8_t vector;
|
||||
uint8_t delivery_mode;
|
||||
uint32_t mask;
|
||||
uint64_t entry;
|
||||
uint8_t dest;
|
||||
uint8_t dest_mode;
|
||||
|
||||
for (i = 0; i < IOAPIC_NUM_PINS; i++) {
|
||||
mask = 1 << i;
|
||||
if (s->irr & mask) {
|
||||
entry = s->ioredtbl[i];
|
||||
if (!(entry & IOAPIC_LVT_MASKED)) {
|
||||
trig_mode = ((entry >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1);
|
||||
dest = entry >> IOAPIC_LVT_DEST_SHIFT;
|
||||
dest_mode = (entry >> IOAPIC_LVT_DEST_MODE_SHIFT) & 1;
|
||||
delivery_mode =
|
||||
(entry >> IOAPIC_LVT_DELIV_MODE_SHIFT) & IOAPIC_DM_MASK;
|
||||
if (trig_mode == IOAPIC_TRIGGER_EDGE) {
|
||||
s->irr &= ~mask;
|
||||
} else {
|
||||
s->ioredtbl[i] |= IOAPIC_LVT_REMOTE_IRR;
|
||||
}
|
||||
if (delivery_mode == IOAPIC_DM_EXTINT) {
|
||||
vector = pic_read_irq(isa_pic);
|
||||
} else {
|
||||
vector = entry & IOAPIC_VECTOR_MASK;
|
||||
}
|
||||
apic_deliver_irq(dest, dest_mode, delivery_mode,
|
||||
vector, trig_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ioapic_set_irq(void *opaque, int vector, int level)
|
||||
{
|
||||
IOAPICCommonState *s = opaque;
|
||||
|
||||
/* ISA IRQs map to GSI 1-1 except for IRQ0 which maps
|
||||
* to GSI 2. GSI maps to ioapic 1-1. This is not
|
||||
* the cleanest way of doing it but it should work. */
|
||||
|
||||
DPRINTF("%s: %s vec %x\n", __func__, level ? "raise" : "lower", vector);
|
||||
if (vector == 0) {
|
||||
vector = 2;
|
||||
}
|
||||
if (vector >= 0 && vector < IOAPIC_NUM_PINS) {
|
||||
uint32_t mask = 1 << vector;
|
||||
uint64_t entry = s->ioredtbl[vector];
|
||||
|
||||
if (entry & (1 << IOAPIC_LVT_POLARITY_SHIFT)) {
|
||||
level = !level;
|
||||
}
|
||||
if (((entry >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1) ==
|
||||
IOAPIC_TRIGGER_LEVEL) {
|
||||
/* level triggered */
|
||||
if (level) {
|
||||
s->irr |= mask;
|
||||
ioapic_service(s);
|
||||
} else {
|
||||
s->irr &= ~mask;
|
||||
}
|
||||
} else {
|
||||
/* According to the 82093AA manual, we must ignore edge requests
|
||||
* if the input pin is masked. */
|
||||
if (level && !(entry & IOAPIC_LVT_MASKED)) {
|
||||
s->irr |= mask;
|
||||
ioapic_service(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ioapic_eoi_broadcast(int vector)
|
||||
{
|
||||
IOAPICCommonState *s;
|
||||
uint64_t entry;
|
||||
int i, n;
|
||||
|
||||
for (i = 0; i < MAX_IOAPICS; i++) {
|
||||
s = ioapics[i];
|
||||
if (!s) {
|
||||
continue;
|
||||
}
|
||||
for (n = 0; n < IOAPIC_NUM_PINS; n++) {
|
||||
entry = s->ioredtbl[n];
|
||||
if ((entry & IOAPIC_LVT_REMOTE_IRR)
|
||||
&& (entry & IOAPIC_VECTOR_MASK) == vector) {
|
||||
s->ioredtbl[n] = entry & ~IOAPIC_LVT_REMOTE_IRR;
|
||||
if (!(entry & IOAPIC_LVT_MASKED) && (s->irr & (1 << n))) {
|
||||
ioapic_service(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t
|
||||
ioapic_mem_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
IOAPICCommonState *s = opaque;
|
||||
int index;
|
||||
uint32_t val = 0;
|
||||
|
||||
switch (addr & 0xff) {
|
||||
case IOAPIC_IOREGSEL:
|
||||
val = s->ioregsel;
|
||||
break;
|
||||
case IOAPIC_IOWIN:
|
||||
if (size != 4) {
|
||||
break;
|
||||
}
|
||||
switch (s->ioregsel) {
|
||||
case IOAPIC_REG_ID:
|
||||
val = s->id << IOAPIC_ID_SHIFT;
|
||||
break;
|
||||
case IOAPIC_REG_VER:
|
||||
val = IOAPIC_VERSION |
|
||||
((IOAPIC_NUM_PINS - 1) << IOAPIC_VER_ENTRIES_SHIFT);
|
||||
break;
|
||||
case IOAPIC_REG_ARB:
|
||||
val = 0;
|
||||
break;
|
||||
default:
|
||||
index = (s->ioregsel - IOAPIC_REG_REDTBL_BASE) >> 1;
|
||||
if (index >= 0 && index < IOAPIC_NUM_PINS) {
|
||||
if (s->ioregsel & 1) {
|
||||
val = s->ioredtbl[index] >> 32;
|
||||
} else {
|
||||
val = s->ioredtbl[index] & 0xffffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
DPRINTF("read: %08x = %08x\n", s->ioregsel, val);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static void
|
||||
ioapic_mem_write(void *opaque, hwaddr addr, uint64_t val,
|
||||
unsigned int size)
|
||||
{
|
||||
IOAPICCommonState *s = opaque;
|
||||
int index;
|
||||
|
||||
switch (addr & 0xff) {
|
||||
case IOAPIC_IOREGSEL:
|
||||
s->ioregsel = val;
|
||||
break;
|
||||
case IOAPIC_IOWIN:
|
||||
if (size != 4) {
|
||||
break;
|
||||
}
|
||||
DPRINTF("write: %08x = %08" PRIx64 "\n", s->ioregsel, val);
|
||||
switch (s->ioregsel) {
|
||||
case IOAPIC_REG_ID:
|
||||
s->id = (val >> IOAPIC_ID_SHIFT) & IOAPIC_ID_MASK;
|
||||
break;
|
||||
case IOAPIC_REG_VER:
|
||||
case IOAPIC_REG_ARB:
|
||||
break;
|
||||
default:
|
||||
index = (s->ioregsel - IOAPIC_REG_REDTBL_BASE) >> 1;
|
||||
if (index >= 0 && index < IOAPIC_NUM_PINS) {
|
||||
if (s->ioregsel & 1) {
|
||||
s->ioredtbl[index] &= 0xffffffff;
|
||||
s->ioredtbl[index] |= (uint64_t)val << 32;
|
||||
} else {
|
||||
s->ioredtbl[index] &= ~0xffffffffULL;
|
||||
s->ioredtbl[index] |= val;
|
||||
}
|
||||
ioapic_service(s);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps ioapic_io_ops = {
|
||||
.read = ioapic_mem_read,
|
||||
.write = ioapic_mem_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
static void ioapic_init(IOAPICCommonState *s, int instance_no)
|
||||
{
|
||||
memory_region_init_io(&s->io_memory, &ioapic_io_ops, s, "ioapic", 0x1000);
|
||||
|
||||
qdev_init_gpio_in(&s->busdev.qdev, ioapic_set_irq, IOAPIC_NUM_PINS);
|
||||
|
||||
ioapics[instance_no] = s;
|
||||
}
|
||||
|
||||
static void ioapic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
IOAPICCommonClass *k = IOAPIC_COMMON_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
k->init = ioapic_init;
|
||||
dc->reset = ioapic_reset_common;
|
||||
}
|
||||
|
||||
static const TypeInfo ioapic_info = {
|
||||
.name = "ioapic",
|
||||
.parent = TYPE_IOAPIC_COMMON,
|
||||
.instance_size = sizeof(IOAPICCommonState),
|
||||
.class_init = ioapic_class_init,
|
||||
};
|
||||
|
||||
static void ioapic_register_types(void)
|
||||
{
|
||||
type_register_static(&ioapic_info);
|
||||
}
|
||||
|
||||
type_init(ioapic_register_types)
|
120
hw/intc/ioapic_common.c
Normal file
120
hw/intc/ioapic_common.c
Normal file
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* IOAPIC emulation logic - common bits of emulated and KVM kernel model
|
||||
*
|
||||
* Copyright (c) 2004-2005 Fabrice Bellard
|
||||
* Copyright (c) 2009 Xiantao Zhang, Intel
|
||||
* Copyright (c) 2011 Jan Kiszka, Siemens AG
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "hw/i386/ioapic.h"
|
||||
#include "hw/i386/ioapic_internal.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
void ioapic_reset_common(DeviceState *dev)
|
||||
{
|
||||
IOAPICCommonState *s = IOAPIC_COMMON(dev);
|
||||
int i;
|
||||
|
||||
s->id = 0;
|
||||
s->ioregsel = 0;
|
||||
s->irr = 0;
|
||||
for (i = 0; i < IOAPIC_NUM_PINS; i++) {
|
||||
s->ioredtbl[i] = 1 << IOAPIC_LVT_MASKED_SHIFT;
|
||||
}
|
||||
}
|
||||
|
||||
static void ioapic_dispatch_pre_save(void *opaque)
|
||||
{
|
||||
IOAPICCommonState *s = IOAPIC_COMMON(opaque);
|
||||
IOAPICCommonClass *info = IOAPIC_COMMON_GET_CLASS(s);
|
||||
|
||||
if (info->pre_save) {
|
||||
info->pre_save(s);
|
||||
}
|
||||
}
|
||||
|
||||
static int ioapic_dispatch_post_load(void *opaque, int version_id)
|
||||
{
|
||||
IOAPICCommonState *s = IOAPIC_COMMON(opaque);
|
||||
IOAPICCommonClass *info = IOAPIC_COMMON_GET_CLASS(s);
|
||||
|
||||
if (info->post_load) {
|
||||
info->post_load(s);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ioapic_init_common(SysBusDevice *dev)
|
||||
{
|
||||
IOAPICCommonState *s = FROM_SYSBUS(IOAPICCommonState, dev);
|
||||
IOAPICCommonClass *info;
|
||||
static int ioapic_no;
|
||||
|
||||
if (ioapic_no >= MAX_IOAPICS) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
info = IOAPIC_COMMON_GET_CLASS(s);
|
||||
info->init(s, ioapic_no);
|
||||
|
||||
sysbus_init_mmio(&s->busdev, &s->io_memory);
|
||||
ioapic_no++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_ioapic_common = {
|
||||
.name = "ioapic",
|
||||
.version_id = 3,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.pre_save = ioapic_dispatch_pre_save,
|
||||
.post_load = ioapic_dispatch_post_load,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT8(id, IOAPICCommonState),
|
||||
VMSTATE_UINT8(ioregsel, IOAPICCommonState),
|
||||
VMSTATE_UNUSED_V(2, 8), /* to account for qemu-kvm's v2 format */
|
||||
VMSTATE_UINT32_V(irr, IOAPICCommonState, 2),
|
||||
VMSTATE_UINT64_ARRAY(ioredtbl, IOAPICCommonState, IOAPIC_NUM_PINS),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void ioapic_common_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
SysBusDeviceClass *sc = SYS_BUS_DEVICE_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
sc->init = ioapic_init_common;
|
||||
dc->vmsd = &vmstate_ioapic_common;
|
||||
dc->no_user = 1;
|
||||
}
|
||||
|
||||
static const TypeInfo ioapic_common_type = {
|
||||
.name = TYPE_IOAPIC_COMMON,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(IOAPICCommonState),
|
||||
.class_size = sizeof(IOAPICCommonClass),
|
||||
.class_init = ioapic_common_class_init,
|
||||
.abstract = true,
|
||||
};
|
||||
|
||||
static void register_types(void)
|
||||
{
|
||||
type_register_static(&ioapic_common_type);
|
||||
}
|
||||
|
||||
type_init(register_types)
|
199
hw/intc/lm32_pic.c
Normal file
199
hw/intc/lm32_pic.c
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* LatticeMico32 CPU interrupt controller logic.
|
||||
*
|
||||
* Copyright (c) 2010 Michael Walle <michael@walle.cc>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "hw/hw.h"
|
||||
#include "hw/i386/pc.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "trace.h"
|
||||
#include "hw/lm32/lm32_pic.h"
|
||||
|
||||
struct LM32PicState {
|
||||
SysBusDevice busdev;
|
||||
qemu_irq parent_irq;
|
||||
uint32_t im; /* interrupt mask */
|
||||
uint32_t ip; /* interrupt pending */
|
||||
uint32_t irq_state;
|
||||
|
||||
/* statistics */
|
||||
uint32_t stats_irq_count[32];
|
||||
};
|
||||
typedef struct LM32PicState LM32PicState;
|
||||
|
||||
static LM32PicState *pic;
|
||||
void lm32_do_pic_info(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
if (pic == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
monitor_printf(mon, "lm32-pic: im=%08x ip=%08x irq_state=%08x\n",
|
||||
pic->im, pic->ip, pic->irq_state);
|
||||
}
|
||||
|
||||
void lm32_irq_info(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
int i;
|
||||
uint32_t count;
|
||||
|
||||
if (pic == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
monitor_printf(mon, "IRQ statistics:\n");
|
||||
for (i = 0; i < 32; i++) {
|
||||
count = pic->stats_irq_count[i];
|
||||
if (count > 0) {
|
||||
monitor_printf(mon, "%2d: %u\n", i, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void update_irq(LM32PicState *s)
|
||||
{
|
||||
s->ip |= s->irq_state;
|
||||
|
||||
if (s->ip & s->im) {
|
||||
trace_lm32_pic_raise_irq();
|
||||
qemu_irq_raise(s->parent_irq);
|
||||
} else {
|
||||
trace_lm32_pic_lower_irq();
|
||||
qemu_irq_lower(s->parent_irq);
|
||||
}
|
||||
}
|
||||
|
||||
static void irq_handler(void *opaque, int irq, int level)
|
||||
{
|
||||
LM32PicState *s = opaque;
|
||||
|
||||
assert(irq < 32);
|
||||
trace_lm32_pic_interrupt(irq, level);
|
||||
|
||||
if (level) {
|
||||
s->irq_state |= (1 << irq);
|
||||
s->stats_irq_count[irq]++;
|
||||
} else {
|
||||
s->irq_state &= ~(1 << irq);
|
||||
}
|
||||
|
||||
update_irq(s);
|
||||
}
|
||||
|
||||
void lm32_pic_set_im(DeviceState *d, uint32_t im)
|
||||
{
|
||||
LM32PicState *s = container_of(d, LM32PicState, busdev.qdev);
|
||||
|
||||
trace_lm32_pic_set_im(im);
|
||||
s->im = im;
|
||||
|
||||
update_irq(s);
|
||||
}
|
||||
|
||||
void lm32_pic_set_ip(DeviceState *d, uint32_t ip)
|
||||
{
|
||||
LM32PicState *s = container_of(d, LM32PicState, busdev.qdev);
|
||||
|
||||
trace_lm32_pic_set_ip(ip);
|
||||
|
||||
/* ack interrupt */
|
||||
s->ip &= ~ip;
|
||||
|
||||
update_irq(s);
|
||||
}
|
||||
|
||||
uint32_t lm32_pic_get_im(DeviceState *d)
|
||||
{
|
||||
LM32PicState *s = container_of(d, LM32PicState, busdev.qdev);
|
||||
|
||||
trace_lm32_pic_get_im(s->im);
|
||||
return s->im;
|
||||
}
|
||||
|
||||
uint32_t lm32_pic_get_ip(DeviceState *d)
|
||||
{
|
||||
LM32PicState *s = container_of(d, LM32PicState, busdev.qdev);
|
||||
|
||||
trace_lm32_pic_get_ip(s->ip);
|
||||
return s->ip;
|
||||
}
|
||||
|
||||
static void pic_reset(DeviceState *d)
|
||||
{
|
||||
LM32PicState *s = container_of(d, LM32PicState, busdev.qdev);
|
||||
int i;
|
||||
|
||||
s->im = 0;
|
||||
s->ip = 0;
|
||||
s->irq_state = 0;
|
||||
for (i = 0; i < 32; i++) {
|
||||
s->stats_irq_count[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int lm32_pic_init(SysBusDevice *dev)
|
||||
{
|
||||
LM32PicState *s = FROM_SYSBUS(typeof(*s), dev);
|
||||
|
||||
qdev_init_gpio_in(&dev->qdev, irq_handler, 32);
|
||||
sysbus_init_irq(dev, &s->parent_irq);
|
||||
|
||||
pic = s;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_lm32_pic = {
|
||||
.name = "lm32-pic",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField[]) {
|
||||
VMSTATE_UINT32(im, LM32PicState),
|
||||
VMSTATE_UINT32(ip, LM32PicState),
|
||||
VMSTATE_UINT32(irq_state, LM32PicState),
|
||||
VMSTATE_UINT32_ARRAY(stats_irq_count, LM32PicState, 32),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void lm32_pic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = lm32_pic_init;
|
||||
dc->reset = pic_reset;
|
||||
dc->vmsd = &vmstate_lm32_pic;
|
||||
}
|
||||
|
||||
static const TypeInfo lm32_pic_info = {
|
||||
.name = "lm32-pic",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(LM32PicState),
|
||||
.class_init = lm32_pic_class_init,
|
||||
};
|
||||
|
||||
static void lm32_pic_register_types(void)
|
||||
{
|
||||
type_register_static(&lm32_pic_info);
|
||||
}
|
||||
|
||||
type_init(lm32_pic_register_types)
|
649
hw/intc/omap_intc.c
Normal file
649
hw/intc/omap_intc.c
Normal file
|
@ -0,0 +1,649 @@
|
|||
/*
|
||||
* TI OMAP interrupt controller emulation.
|
||||
*
|
||||
* Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org>
|
||||
* Copyright (C) 2007-2008 Nokia Corporation
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 or
|
||||
* (at your option) version 3 of the License.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "hw/hw.h"
|
||||
#include "hw/arm/omap.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
/* Interrupt Handlers */
|
||||
struct omap_intr_handler_bank_s {
|
||||
uint32_t irqs;
|
||||
uint32_t inputs;
|
||||
uint32_t mask;
|
||||
uint32_t fiq;
|
||||
uint32_t sens_edge;
|
||||
uint32_t swi;
|
||||
unsigned char priority[32];
|
||||
};
|
||||
|
||||
struct omap_intr_handler_s {
|
||||
SysBusDevice busdev;
|
||||
qemu_irq *pins;
|
||||
qemu_irq parent_intr[2];
|
||||
MemoryRegion mmio;
|
||||
void *iclk;
|
||||
void *fclk;
|
||||
unsigned char nbanks;
|
||||
int level_only;
|
||||
uint32_t size;
|
||||
|
||||
uint8_t revision;
|
||||
|
||||
/* state */
|
||||
uint32_t new_agr[2];
|
||||
int sir_intr[2];
|
||||
int autoidle;
|
||||
uint32_t mask;
|
||||
struct omap_intr_handler_bank_s bank[3];
|
||||
};
|
||||
|
||||
static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq)
|
||||
{
|
||||
int i, j, sir_intr, p_intr, p, f;
|
||||
uint32_t level;
|
||||
sir_intr = 0;
|
||||
p_intr = 255;
|
||||
|
||||
/* Find the interrupt line with the highest dynamic priority.
|
||||
* Note: 0 denotes the hightest priority.
|
||||
* If all interrupts have the same priority, the default order is IRQ_N,
|
||||
* IRQ_N-1,...,IRQ_0. */
|
||||
for (j = 0; j < s->nbanks; ++j) {
|
||||
level = s->bank[j].irqs & ~s->bank[j].mask &
|
||||
(is_fiq ? s->bank[j].fiq : ~s->bank[j].fiq);
|
||||
for (f = ffs(level), i = f - 1, level >>= f - 1; f; i += f,
|
||||
level >>= f) {
|
||||
p = s->bank[j].priority[i];
|
||||
if (p <= p_intr) {
|
||||
p_intr = p;
|
||||
sir_intr = 32 * j + i;
|
||||
}
|
||||
f = ffs(level >> 1);
|
||||
}
|
||||
}
|
||||
s->sir_intr[is_fiq] = sir_intr;
|
||||
}
|
||||
|
||||
static inline void omap_inth_update(struct omap_intr_handler_s *s, int is_fiq)
|
||||
{
|
||||
int i;
|
||||
uint32_t has_intr = 0;
|
||||
|
||||
for (i = 0; i < s->nbanks; ++i)
|
||||
has_intr |= s->bank[i].irqs & ~s->bank[i].mask &
|
||||
(is_fiq ? s->bank[i].fiq : ~s->bank[i].fiq);
|
||||
|
||||
if (s->new_agr[is_fiq] & has_intr & s->mask) {
|
||||
s->new_agr[is_fiq] = 0;
|
||||
omap_inth_sir_update(s, is_fiq);
|
||||
qemu_set_irq(s->parent_intr[is_fiq], 1);
|
||||
}
|
||||
}
|
||||
|
||||
#define INT_FALLING_EDGE 0
|
||||
#define INT_LOW_LEVEL 1
|
||||
|
||||
static void omap_set_intr(void *opaque, int irq, int req)
|
||||
{
|
||||
struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque;
|
||||
uint32_t rise;
|
||||
|
||||
struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5];
|
||||
int n = irq & 31;
|
||||
|
||||
if (req) {
|
||||
rise = ~bank->irqs & (1 << n);
|
||||
if (~bank->sens_edge & (1 << n))
|
||||
rise &= ~bank->inputs;
|
||||
|
||||
bank->inputs |= (1 << n);
|
||||
if (rise) {
|
||||
bank->irqs |= rise;
|
||||
omap_inth_update(ih, 0);
|
||||
omap_inth_update(ih, 1);
|
||||
}
|
||||
} else {
|
||||
rise = bank->sens_edge & bank->irqs & (1 << n);
|
||||
bank->irqs &= ~rise;
|
||||
bank->inputs &= ~(1 << n);
|
||||
}
|
||||
}
|
||||
|
||||
/* Simplified version with no edge detection */
|
||||
static void omap_set_intr_noedge(void *opaque, int irq, int req)
|
||||
{
|
||||
struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque;
|
||||
uint32_t rise;
|
||||
|
||||
struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5];
|
||||
int n = irq & 31;
|
||||
|
||||
if (req) {
|
||||
rise = ~bank->inputs & (1 << n);
|
||||
if (rise) {
|
||||
bank->irqs |= bank->inputs |= rise;
|
||||
omap_inth_update(ih, 0);
|
||||
omap_inth_update(ih, 1);
|
||||
}
|
||||
} else
|
||||
bank->irqs = (bank->inputs &= ~(1 << n)) | bank->swi;
|
||||
}
|
||||
|
||||
static uint64_t omap_inth_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
|
||||
int i, offset = addr;
|
||||
int bank_no = offset >> 8;
|
||||
int line_no;
|
||||
struct omap_intr_handler_bank_s *bank = &s->bank[bank_no];
|
||||
offset &= 0xff;
|
||||
|
||||
switch (offset) {
|
||||
case 0x00: /* ITR */
|
||||
return bank->irqs;
|
||||
|
||||
case 0x04: /* MIR */
|
||||
return bank->mask;
|
||||
|
||||
case 0x10: /* SIR_IRQ_CODE */
|
||||
case 0x14: /* SIR_FIQ_CODE */
|
||||
if (bank_no != 0)
|
||||
break;
|
||||
line_no = s->sir_intr[(offset - 0x10) >> 2];
|
||||
bank = &s->bank[line_no >> 5];
|
||||
i = line_no & 31;
|
||||
if (((bank->sens_edge >> i) & 1) == INT_FALLING_EDGE)
|
||||
bank->irqs &= ~(1 << i);
|
||||
return line_no;
|
||||
|
||||
case 0x18: /* CONTROL_REG */
|
||||
if (bank_no != 0)
|
||||
break;
|
||||
return 0;
|
||||
|
||||
case 0x1c: /* ILR0 */
|
||||
case 0x20: /* ILR1 */
|
||||
case 0x24: /* ILR2 */
|
||||
case 0x28: /* ILR3 */
|
||||
case 0x2c: /* ILR4 */
|
||||
case 0x30: /* ILR5 */
|
||||
case 0x34: /* ILR6 */
|
||||
case 0x38: /* ILR7 */
|
||||
case 0x3c: /* ILR8 */
|
||||
case 0x40: /* ILR9 */
|
||||
case 0x44: /* ILR10 */
|
||||
case 0x48: /* ILR11 */
|
||||
case 0x4c: /* ILR12 */
|
||||
case 0x50: /* ILR13 */
|
||||
case 0x54: /* ILR14 */
|
||||
case 0x58: /* ILR15 */
|
||||
case 0x5c: /* ILR16 */
|
||||
case 0x60: /* ILR17 */
|
||||
case 0x64: /* ILR18 */
|
||||
case 0x68: /* ILR19 */
|
||||
case 0x6c: /* ILR20 */
|
||||
case 0x70: /* ILR21 */
|
||||
case 0x74: /* ILR22 */
|
||||
case 0x78: /* ILR23 */
|
||||
case 0x7c: /* ILR24 */
|
||||
case 0x80: /* ILR25 */
|
||||
case 0x84: /* ILR26 */
|
||||
case 0x88: /* ILR27 */
|
||||
case 0x8c: /* ILR28 */
|
||||
case 0x90: /* ILR29 */
|
||||
case 0x94: /* ILR30 */
|
||||
case 0x98: /* ILR31 */
|
||||
i = (offset - 0x1c) >> 2;
|
||||
return (bank->priority[i] << 2) |
|
||||
(((bank->sens_edge >> i) & 1) << 1) |
|
||||
((bank->fiq >> i) & 1);
|
||||
|
||||
case 0x9c: /* ISR */
|
||||
return 0x00000000;
|
||||
|
||||
}
|
||||
OMAP_BAD_REG(addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void omap_inth_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
|
||||
int i, offset = addr;
|
||||
int bank_no = offset >> 8;
|
||||
struct omap_intr_handler_bank_s *bank = &s->bank[bank_no];
|
||||
offset &= 0xff;
|
||||
|
||||
switch (offset) {
|
||||
case 0x00: /* ITR */
|
||||
/* Important: ignore the clearing if the IRQ is level-triggered and
|
||||
the input bit is 1 */
|
||||
bank->irqs &= value | (bank->inputs & bank->sens_edge);
|
||||
return;
|
||||
|
||||
case 0x04: /* MIR */
|
||||
bank->mask = value;
|
||||
omap_inth_update(s, 0);
|
||||
omap_inth_update(s, 1);
|
||||
return;
|
||||
|
||||
case 0x10: /* SIR_IRQ_CODE */
|
||||
case 0x14: /* SIR_FIQ_CODE */
|
||||
OMAP_RO_REG(addr);
|
||||
break;
|
||||
|
||||
case 0x18: /* CONTROL_REG */
|
||||
if (bank_no != 0)
|
||||
break;
|
||||
if (value & 2) {
|
||||
qemu_set_irq(s->parent_intr[1], 0);
|
||||
s->new_agr[1] = ~0;
|
||||
omap_inth_update(s, 1);
|
||||
}
|
||||
if (value & 1) {
|
||||
qemu_set_irq(s->parent_intr[0], 0);
|
||||
s->new_agr[0] = ~0;
|
||||
omap_inth_update(s, 0);
|
||||
}
|
||||
return;
|
||||
|
||||
case 0x1c: /* ILR0 */
|
||||
case 0x20: /* ILR1 */
|
||||
case 0x24: /* ILR2 */
|
||||
case 0x28: /* ILR3 */
|
||||
case 0x2c: /* ILR4 */
|
||||
case 0x30: /* ILR5 */
|
||||
case 0x34: /* ILR6 */
|
||||
case 0x38: /* ILR7 */
|
||||
case 0x3c: /* ILR8 */
|
||||
case 0x40: /* ILR9 */
|
||||
case 0x44: /* ILR10 */
|
||||
case 0x48: /* ILR11 */
|
||||
case 0x4c: /* ILR12 */
|
||||
case 0x50: /* ILR13 */
|
||||
case 0x54: /* ILR14 */
|
||||
case 0x58: /* ILR15 */
|
||||
case 0x5c: /* ILR16 */
|
||||
case 0x60: /* ILR17 */
|
||||
case 0x64: /* ILR18 */
|
||||
case 0x68: /* ILR19 */
|
||||
case 0x6c: /* ILR20 */
|
||||
case 0x70: /* ILR21 */
|
||||
case 0x74: /* ILR22 */
|
||||
case 0x78: /* ILR23 */
|
||||
case 0x7c: /* ILR24 */
|
||||
case 0x80: /* ILR25 */
|
||||
case 0x84: /* ILR26 */
|
||||
case 0x88: /* ILR27 */
|
||||
case 0x8c: /* ILR28 */
|
||||
case 0x90: /* ILR29 */
|
||||
case 0x94: /* ILR30 */
|
||||
case 0x98: /* ILR31 */
|
||||
i = (offset - 0x1c) >> 2;
|
||||
bank->priority[i] = (value >> 2) & 0x1f;
|
||||
bank->sens_edge &= ~(1 << i);
|
||||
bank->sens_edge |= ((value >> 1) & 1) << i;
|
||||
bank->fiq &= ~(1 << i);
|
||||
bank->fiq |= (value & 1) << i;
|
||||
return;
|
||||
|
||||
case 0x9c: /* ISR */
|
||||
for (i = 0; i < 32; i ++)
|
||||
if (value & (1 << i)) {
|
||||
omap_set_intr(s, 32 * bank_no + i, 1);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
OMAP_BAD_REG(addr);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps omap_inth_mem_ops = {
|
||||
.read = omap_inth_read,
|
||||
.write = omap_inth_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static void omap_inth_reset(DeviceState *dev)
|
||||
{
|
||||
struct omap_intr_handler_s *s = FROM_SYSBUS(struct omap_intr_handler_s,
|
||||
SYS_BUS_DEVICE(dev));
|
||||
int i;
|
||||
|
||||
for (i = 0; i < s->nbanks; ++i){
|
||||
s->bank[i].irqs = 0x00000000;
|
||||
s->bank[i].mask = 0xffffffff;
|
||||
s->bank[i].sens_edge = 0x00000000;
|
||||
s->bank[i].fiq = 0x00000000;
|
||||
s->bank[i].inputs = 0x00000000;
|
||||
s->bank[i].swi = 0x00000000;
|
||||
memset(s->bank[i].priority, 0, sizeof(s->bank[i].priority));
|
||||
|
||||
if (s->level_only)
|
||||
s->bank[i].sens_edge = 0xffffffff;
|
||||
}
|
||||
|
||||
s->new_agr[0] = ~0;
|
||||
s->new_agr[1] = ~0;
|
||||
s->sir_intr[0] = 0;
|
||||
s->sir_intr[1] = 0;
|
||||
s->autoidle = 0;
|
||||
s->mask = ~0;
|
||||
|
||||
qemu_set_irq(s->parent_intr[0], 0);
|
||||
qemu_set_irq(s->parent_intr[1], 0);
|
||||
}
|
||||
|
||||
static int omap_intc_init(SysBusDevice *dev)
|
||||
{
|
||||
struct omap_intr_handler_s *s;
|
||||
s = FROM_SYSBUS(struct omap_intr_handler_s, dev);
|
||||
if (!s->iclk) {
|
||||
hw_error("omap-intc: clk not connected\n");
|
||||
}
|
||||
s->nbanks = 1;
|
||||
sysbus_init_irq(dev, &s->parent_intr[0]);
|
||||
sysbus_init_irq(dev, &s->parent_intr[1]);
|
||||
qdev_init_gpio_in(&dev->qdev, omap_set_intr, s->nbanks * 32);
|
||||
memory_region_init_io(&s->mmio, &omap_inth_mem_ops, s,
|
||||
"omap-intc", s->size);
|
||||
sysbus_init_mmio(dev, &s->mmio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Property omap_intc_properties[] = {
|
||||
DEFINE_PROP_UINT32("size", struct omap_intr_handler_s, size, 0x100),
|
||||
DEFINE_PROP_PTR("clk", struct omap_intr_handler_s, iclk),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void omap_intc_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = omap_intc_init;
|
||||
dc->reset = omap_inth_reset;
|
||||
dc->props = omap_intc_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo omap_intc_info = {
|
||||
.name = "omap-intc",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(struct omap_intr_handler_s),
|
||||
.class_init = omap_intc_class_init,
|
||||
};
|
||||
|
||||
static uint64_t omap2_inth_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
|
||||
int offset = addr;
|
||||
int bank_no, line_no;
|
||||
struct omap_intr_handler_bank_s *bank = NULL;
|
||||
|
||||
if ((offset & 0xf80) == 0x80) {
|
||||
bank_no = (offset & 0x60) >> 5;
|
||||
if (bank_no < s->nbanks) {
|
||||
offset &= ~0x60;
|
||||
bank = &s->bank[bank_no];
|
||||
} else {
|
||||
OMAP_BAD_REG(addr);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
case 0x00: /* INTC_REVISION */
|
||||
return s->revision;
|
||||
|
||||
case 0x10: /* INTC_SYSCONFIG */
|
||||
return (s->autoidle >> 2) & 1;
|
||||
|
||||
case 0x14: /* INTC_SYSSTATUS */
|
||||
return 1; /* RESETDONE */
|
||||
|
||||
case 0x40: /* INTC_SIR_IRQ */
|
||||
return s->sir_intr[0];
|
||||
|
||||
case 0x44: /* INTC_SIR_FIQ */
|
||||
return s->sir_intr[1];
|
||||
|
||||
case 0x48: /* INTC_CONTROL */
|
||||
return (!s->mask) << 2; /* GLOBALMASK */
|
||||
|
||||
case 0x4c: /* INTC_PROTECTION */
|
||||
return 0;
|
||||
|
||||
case 0x50: /* INTC_IDLE */
|
||||
return s->autoidle & 3;
|
||||
|
||||
/* Per-bank registers */
|
||||
case 0x80: /* INTC_ITR */
|
||||
return bank->inputs;
|
||||
|
||||
case 0x84: /* INTC_MIR */
|
||||
return bank->mask;
|
||||
|
||||
case 0x88: /* INTC_MIR_CLEAR */
|
||||
case 0x8c: /* INTC_MIR_SET */
|
||||
return 0;
|
||||
|
||||
case 0x90: /* INTC_ISR_SET */
|
||||
return bank->swi;
|
||||
|
||||
case 0x94: /* INTC_ISR_CLEAR */
|
||||
return 0;
|
||||
|
||||
case 0x98: /* INTC_PENDING_IRQ */
|
||||
return bank->irqs & ~bank->mask & ~bank->fiq;
|
||||
|
||||
case 0x9c: /* INTC_PENDING_FIQ */
|
||||
return bank->irqs & ~bank->mask & bank->fiq;
|
||||
|
||||
/* Per-line registers */
|
||||
case 0x100 ... 0x300: /* INTC_ILR */
|
||||
bank_no = (offset - 0x100) >> 7;
|
||||
if (bank_no > s->nbanks)
|
||||
break;
|
||||
bank = &s->bank[bank_no];
|
||||
line_no = (offset & 0x7f) >> 2;
|
||||
return (bank->priority[line_no] << 2) |
|
||||
((bank->fiq >> line_no) & 1);
|
||||
}
|
||||
OMAP_BAD_REG(addr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void omap2_inth_write(void *opaque, hwaddr addr,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque;
|
||||
int offset = addr;
|
||||
int bank_no, line_no;
|
||||
struct omap_intr_handler_bank_s *bank = NULL;
|
||||
|
||||
if ((offset & 0xf80) == 0x80) {
|
||||
bank_no = (offset & 0x60) >> 5;
|
||||
if (bank_no < s->nbanks) {
|
||||
offset &= ~0x60;
|
||||
bank = &s->bank[bank_no];
|
||||
} else {
|
||||
OMAP_BAD_REG(addr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
case 0x10: /* INTC_SYSCONFIG */
|
||||
s->autoidle &= 4;
|
||||
s->autoidle |= (value & 1) << 2;
|
||||
if (value & 2) /* SOFTRESET */
|
||||
omap_inth_reset(&s->busdev.qdev);
|
||||
return;
|
||||
|
||||
case 0x48: /* INTC_CONTROL */
|
||||
s->mask = (value & 4) ? 0 : ~0; /* GLOBALMASK */
|
||||
if (value & 2) { /* NEWFIQAGR */
|
||||
qemu_set_irq(s->parent_intr[1], 0);
|
||||
s->new_agr[1] = ~0;
|
||||
omap_inth_update(s, 1);
|
||||
}
|
||||
if (value & 1) { /* NEWIRQAGR */
|
||||
qemu_set_irq(s->parent_intr[0], 0);
|
||||
s->new_agr[0] = ~0;
|
||||
omap_inth_update(s, 0);
|
||||
}
|
||||
return;
|
||||
|
||||
case 0x4c: /* INTC_PROTECTION */
|
||||
/* TODO: Make a bitmap (or sizeof(char)map) of access privileges
|
||||
* for every register, see Chapter 3 and 4 for privileged mode. */
|
||||
if (value & 1)
|
||||
fprintf(stderr, "%s: protection mode enable attempt\n",
|
||||
__FUNCTION__);
|
||||
return;
|
||||
|
||||
case 0x50: /* INTC_IDLE */
|
||||
s->autoidle &= ~3;
|
||||
s->autoidle |= value & 3;
|
||||
return;
|
||||
|
||||
/* Per-bank registers */
|
||||
case 0x84: /* INTC_MIR */
|
||||
bank->mask = value;
|
||||
omap_inth_update(s, 0);
|
||||
omap_inth_update(s, 1);
|
||||
return;
|
||||
|
||||
case 0x88: /* INTC_MIR_CLEAR */
|
||||
bank->mask &= ~value;
|
||||
omap_inth_update(s, 0);
|
||||
omap_inth_update(s, 1);
|
||||
return;
|
||||
|
||||
case 0x8c: /* INTC_MIR_SET */
|
||||
bank->mask |= value;
|
||||
return;
|
||||
|
||||
case 0x90: /* INTC_ISR_SET */
|
||||
bank->irqs |= bank->swi |= value;
|
||||
omap_inth_update(s, 0);
|
||||
omap_inth_update(s, 1);
|
||||
return;
|
||||
|
||||
case 0x94: /* INTC_ISR_CLEAR */
|
||||
bank->swi &= ~value;
|
||||
bank->irqs = bank->swi & bank->inputs;
|
||||
return;
|
||||
|
||||
/* Per-line registers */
|
||||
case 0x100 ... 0x300: /* INTC_ILR */
|
||||
bank_no = (offset - 0x100) >> 7;
|
||||
if (bank_no > s->nbanks)
|
||||
break;
|
||||
bank = &s->bank[bank_no];
|
||||
line_no = (offset & 0x7f) >> 2;
|
||||
bank->priority[line_no] = (value >> 2) & 0x3f;
|
||||
bank->fiq &= ~(1 << line_no);
|
||||
bank->fiq |= (value & 1) << line_no;
|
||||
return;
|
||||
|
||||
case 0x00: /* INTC_REVISION */
|
||||
case 0x14: /* INTC_SYSSTATUS */
|
||||
case 0x40: /* INTC_SIR_IRQ */
|
||||
case 0x44: /* INTC_SIR_FIQ */
|
||||
case 0x80: /* INTC_ITR */
|
||||
case 0x98: /* INTC_PENDING_IRQ */
|
||||
case 0x9c: /* INTC_PENDING_FIQ */
|
||||
OMAP_RO_REG(addr);
|
||||
return;
|
||||
}
|
||||
OMAP_BAD_REG(addr);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps omap2_inth_mem_ops = {
|
||||
.read = omap2_inth_read,
|
||||
.write = omap2_inth_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static int omap2_intc_init(SysBusDevice *dev)
|
||||
{
|
||||
struct omap_intr_handler_s *s;
|
||||
s = FROM_SYSBUS(struct omap_intr_handler_s, dev);
|
||||
if (!s->iclk) {
|
||||
hw_error("omap2-intc: iclk not connected\n");
|
||||
}
|
||||
if (!s->fclk) {
|
||||
hw_error("omap2-intc: fclk not connected\n");
|
||||
}
|
||||
s->level_only = 1;
|
||||
s->nbanks = 3;
|
||||
sysbus_init_irq(dev, &s->parent_intr[0]);
|
||||
sysbus_init_irq(dev, &s->parent_intr[1]);
|
||||
qdev_init_gpio_in(&dev->qdev, omap_set_intr_noedge, s->nbanks * 32);
|
||||
memory_region_init_io(&s->mmio, &omap2_inth_mem_ops, s,
|
||||
"omap2-intc", 0x1000);
|
||||
sysbus_init_mmio(dev, &s->mmio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Property omap2_intc_properties[] = {
|
||||
DEFINE_PROP_UINT8("revision", struct omap_intr_handler_s,
|
||||
revision, 0x21),
|
||||
DEFINE_PROP_PTR("iclk", struct omap_intr_handler_s, iclk),
|
||||
DEFINE_PROP_PTR("fclk", struct omap_intr_handler_s, fclk),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
static void omap2_intc_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = omap2_intc_init;
|
||||
dc->reset = omap_inth_reset;
|
||||
dc->props = omap2_intc_properties;
|
||||
}
|
||||
|
||||
static const TypeInfo omap2_intc_info = {
|
||||
.name = "omap2-intc",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(struct omap_intr_handler_s),
|
||||
.class_init = omap2_intc_class_init,
|
||||
};
|
||||
|
||||
static void omap_intc_register_types(void)
|
||||
{
|
||||
type_register_static(&omap_intc_info);
|
||||
type_register_static(&omap2_intc_info);
|
||||
}
|
||||
|
||||
type_init(omap_intc_register_types)
|
1661
hw/intc/openpic.c
Normal file
1661
hw/intc/openpic.c
Normal file
File diff suppressed because it is too large
Load diff
74
hw/intc/realview_gic.c
Normal file
74
hw/intc/realview_gic.c
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* ARM RealView Emulation Baseboard Interrupt Controller
|
||||
*
|
||||
* Copyright (c) 2006-2007 CodeSourcery.
|
||||
* Written by Paul Brook
|
||||
*
|
||||
* This code is licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
typedef struct {
|
||||
SysBusDevice busdev;
|
||||
DeviceState *gic;
|
||||
MemoryRegion container;
|
||||
} RealViewGICState;
|
||||
|
||||
static void realview_gic_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
RealViewGICState *s = (RealViewGICState *)opaque;
|
||||
qemu_set_irq(qdev_get_gpio_in(s->gic, irq), level);
|
||||
}
|
||||
|
||||
static int realview_gic_init(SysBusDevice *dev)
|
||||
{
|
||||
RealViewGICState *s = FROM_SYSBUS(RealViewGICState, dev);
|
||||
SysBusDevice *busdev;
|
||||
/* The GICs on the RealView boards have a fixed nonconfigurable
|
||||
* number of interrupt lines, so we don't need to expose this as
|
||||
* a qdev property.
|
||||
*/
|
||||
int numirq = 96;
|
||||
|
||||
s->gic = qdev_create(NULL, "arm_gic");
|
||||
qdev_prop_set_uint32(s->gic, "num-cpu", 1);
|
||||
qdev_prop_set_uint32(s->gic, "num-irq", numirq);
|
||||
qdev_init_nofail(s->gic);
|
||||
busdev = SYS_BUS_DEVICE(s->gic);
|
||||
|
||||
/* Pass through outbound IRQ lines from the GIC */
|
||||
sysbus_pass_irq(dev, busdev);
|
||||
|
||||
/* Pass through inbound GPIO lines to the GIC */
|
||||
qdev_init_gpio_in(&s->busdev.qdev, realview_gic_set_irq, numirq - 32);
|
||||
|
||||
memory_region_init(&s->container, "realview-gic-container", 0x2000);
|
||||
memory_region_add_subregion(&s->container, 0,
|
||||
sysbus_mmio_get_region(busdev, 1));
|
||||
memory_region_add_subregion(&s->container, 0x1000,
|
||||
sysbus_mmio_get_region(busdev, 0));
|
||||
sysbus_init_mmio(dev, &s->container);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void realview_gic_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
sdc->init = realview_gic_init;
|
||||
}
|
||||
|
||||
static const TypeInfo realview_gic_info = {
|
||||
.name = "realview_gic",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(RealViewGICState),
|
||||
.class_init = realview_gic_class_init,
|
||||
};
|
||||
|
||||
static void realview_gic_register_types(void)
|
||||
{
|
||||
type_register_static(&realview_gic_info);
|
||||
}
|
||||
|
||||
type_init(realview_gic_register_types)
|
156
hw/intc/sbi.c
Normal file
156
hw/intc/sbi.c
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* QEMU Sparc SBI interrupt controller emulation
|
||||
*
|
||||
* Based on slavio_intctl, copyright (c) 2003-2005 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
//#define DEBUG_IRQ
|
||||
|
||||
#ifdef DEBUG_IRQ
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { printf("IRQ: " fmt , ## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...)
|
||||
#endif
|
||||
|
||||
#define MAX_CPUS 16
|
||||
|
||||
#define SBI_NREGS 16
|
||||
|
||||
typedef struct SBIState {
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion iomem;
|
||||
uint32_t regs[SBI_NREGS];
|
||||
uint32_t intreg_pending[MAX_CPUS];
|
||||
qemu_irq cpu_irqs[MAX_CPUS];
|
||||
uint32_t pil_out[MAX_CPUS];
|
||||
} SBIState;
|
||||
|
||||
#define SBI_SIZE (SBI_NREGS * 4)
|
||||
|
||||
static void sbi_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
}
|
||||
|
||||
static uint64_t sbi_mem_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
SBIState *s = opaque;
|
||||
uint32_t saddr, ret;
|
||||
|
||||
saddr = addr >> 2;
|
||||
switch (saddr) {
|
||||
default:
|
||||
ret = s->regs[saddr];
|
||||
break;
|
||||
}
|
||||
DPRINTF("read system reg 0x" TARGET_FMT_plx " = %x\n", addr, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sbi_mem_write(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned dize)
|
||||
{
|
||||
SBIState *s = opaque;
|
||||
uint32_t saddr;
|
||||
|
||||
saddr = addr >> 2;
|
||||
DPRINTF("write system reg 0x" TARGET_FMT_plx " = %x\n", addr, (int)val);
|
||||
switch (saddr) {
|
||||
default:
|
||||
s->regs[saddr] = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps sbi_mem_ops = {
|
||||
.read = sbi_mem_read,
|
||||
.write = sbi_mem_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_sbi = {
|
||||
.name ="sbi",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_UINT32_ARRAY(intreg_pending, SBIState, MAX_CPUS),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void sbi_reset(DeviceState *d)
|
||||
{
|
||||
SBIState *s = container_of(d, SBIState, busdev.qdev);
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < MAX_CPUS; i++) {
|
||||
s->intreg_pending[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int sbi_init1(SysBusDevice *dev)
|
||||
{
|
||||
SBIState *s = FROM_SYSBUS(SBIState, dev);
|
||||
unsigned int i;
|
||||
|
||||
qdev_init_gpio_in(&dev->qdev, sbi_set_irq, 32 + MAX_CPUS);
|
||||
for (i = 0; i < MAX_CPUS; i++) {
|
||||
sysbus_init_irq(dev, &s->cpu_irqs[i]);
|
||||
}
|
||||
|
||||
memory_region_init_io(&s->iomem, &sbi_mem_ops, s, "sbi", SBI_SIZE);
|
||||
sysbus_init_mmio(dev, &s->iomem);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sbi_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = sbi_init1;
|
||||
dc->reset = sbi_reset;
|
||||
dc->vmsd = &vmstate_sbi;
|
||||
}
|
||||
|
||||
static const TypeInfo sbi_info = {
|
||||
.name = "sbi",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(SBIState),
|
||||
.class_init = sbi_class_init,
|
||||
};
|
||||
|
||||
static void sbi_register_types(void)
|
||||
{
|
||||
type_register_static(&sbi_info);
|
||||
}
|
||||
|
||||
type_init(sbi_register_types)
|
513
hw/intc/sh_intc.c
Normal file
513
hw/intc/sh_intc.c
Normal file
|
@ -0,0 +1,513 @@
|
|||
/*
|
||||
* SuperH interrupt controller module
|
||||
*
|
||||
* Copyright (c) 2007 Magnus Damm
|
||||
* Based on sh_timer.c and arm_timer.c by Paul Brook
|
||||
* Copyright (c) 2005-2006 CodeSourcery.
|
||||
*
|
||||
* This code is licensed under the GPL.
|
||||
*/
|
||||
|
||||
#include "hw/sh4/sh_intc.h"
|
||||
#include "hw/hw.h"
|
||||
#include "hw/sh4/sh.h"
|
||||
|
||||
//#define DEBUG_INTC
|
||||
//#define DEBUG_INTC_SOURCES
|
||||
|
||||
#define INTC_A7(x) ((x) & 0x1fffffff)
|
||||
|
||||
void sh_intc_toggle_source(struct intc_source *source,
|
||||
int enable_adj, int assert_adj)
|
||||
{
|
||||
int enable_changed = 0;
|
||||
int pending_changed = 0;
|
||||
int old_pending;
|
||||
|
||||
if ((source->enable_count == source->enable_max) && (enable_adj == -1))
|
||||
enable_changed = -1;
|
||||
|
||||
source->enable_count += enable_adj;
|
||||
|
||||
if (source->enable_count == source->enable_max)
|
||||
enable_changed = 1;
|
||||
|
||||
source->asserted += assert_adj;
|
||||
|
||||
old_pending = source->pending;
|
||||
source->pending = source->asserted &&
|
||||
(source->enable_count == source->enable_max);
|
||||
|
||||
if (old_pending != source->pending)
|
||||
pending_changed = 1;
|
||||
|
||||
if (pending_changed) {
|
||||
CPUState *cpu = CPU(sh_env_get_cpu(first_cpu));
|
||||
if (source->pending) {
|
||||
source->parent->pending++;
|
||||
if (source->parent->pending == 1) {
|
||||
cpu_interrupt(cpu, CPU_INTERRUPT_HARD);
|
||||
}
|
||||
} else {
|
||||
source->parent->pending--;
|
||||
if (source->parent->pending == 0) {
|
||||
cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (enable_changed || assert_adj || pending_changed) {
|
||||
#ifdef DEBUG_INTC_SOURCES
|
||||
printf("sh_intc: (%d/%d/%d/%d) interrupt source 0x%x %s%s%s\n",
|
||||
source->parent->pending,
|
||||
source->asserted,
|
||||
source->enable_count,
|
||||
source->enable_max,
|
||||
source->vect,
|
||||
source->asserted ? "asserted " :
|
||||
assert_adj ? "deasserted" : "",
|
||||
enable_changed == 1 ? "enabled " :
|
||||
enable_changed == -1 ? "disabled " : "",
|
||||
source->pending ? "pending" : "");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void sh_intc_set_irq (void *opaque, int n, int level)
|
||||
{
|
||||
struct intc_desc *desc = opaque;
|
||||
struct intc_source *source = &(desc->sources[n]);
|
||||
|
||||
if (level && !source->asserted)
|
||||
sh_intc_toggle_source(source, 0, 1);
|
||||
else if (!level && source->asserted)
|
||||
sh_intc_toggle_source(source, 0, -1);
|
||||
}
|
||||
|
||||
int sh_intc_get_pending_vector(struct intc_desc *desc, int imask)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
/* slow: use a linked lists of pending sources instead */
|
||||
/* wrong: take interrupt priority into account (one list per priority) */
|
||||
|
||||
if (imask == 0x0f) {
|
||||
return -1; /* FIXME, update code to include priority per source */
|
||||
}
|
||||
|
||||
for (i = 0; i < desc->nr_sources; i++) {
|
||||
struct intc_source *source = desc->sources + i;
|
||||
|
||||
if (source->pending) {
|
||||
#ifdef DEBUG_INTC_SOURCES
|
||||
printf("sh_intc: (%d) returning interrupt source 0x%x\n",
|
||||
desc->pending, source->vect);
|
||||
#endif
|
||||
return source->vect;
|
||||
}
|
||||
}
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
#define INTC_MODE_NONE 0
|
||||
#define INTC_MODE_DUAL_SET 1
|
||||
#define INTC_MODE_DUAL_CLR 2
|
||||
#define INTC_MODE_ENABLE_REG 3
|
||||
#define INTC_MODE_MASK_REG 4
|
||||
#define INTC_MODE_IS_PRIO 8
|
||||
|
||||
static unsigned int sh_intc_mode(unsigned long address,
|
||||
unsigned long set_reg, unsigned long clr_reg)
|
||||
{
|
||||
if ((address != INTC_A7(set_reg)) &&
|
||||
(address != INTC_A7(clr_reg)))
|
||||
return INTC_MODE_NONE;
|
||||
|
||||
if (set_reg && clr_reg) {
|
||||
if (address == INTC_A7(set_reg))
|
||||
return INTC_MODE_DUAL_SET;
|
||||
else
|
||||
return INTC_MODE_DUAL_CLR;
|
||||
}
|
||||
|
||||
if (set_reg)
|
||||
return INTC_MODE_ENABLE_REG;
|
||||
else
|
||||
return INTC_MODE_MASK_REG;
|
||||
}
|
||||
|
||||
static void sh_intc_locate(struct intc_desc *desc,
|
||||
unsigned long address,
|
||||
unsigned long **datap,
|
||||
intc_enum **enums,
|
||||
unsigned int *first,
|
||||
unsigned int *width,
|
||||
unsigned int *modep)
|
||||
{
|
||||
unsigned int i, mode;
|
||||
|
||||
/* this is slow but works for now */
|
||||
|
||||
if (desc->mask_regs) {
|
||||
for (i = 0; i < desc->nr_mask_regs; i++) {
|
||||
struct intc_mask_reg *mr = desc->mask_regs + i;
|
||||
|
||||
mode = sh_intc_mode(address, mr->set_reg, mr->clr_reg);
|
||||
if (mode == INTC_MODE_NONE)
|
||||
continue;
|
||||
|
||||
*modep = mode;
|
||||
*datap = &mr->value;
|
||||
*enums = mr->enum_ids;
|
||||
*first = mr->reg_width - 1;
|
||||
*width = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (desc->prio_regs) {
|
||||
for (i = 0; i < desc->nr_prio_regs; i++) {
|
||||
struct intc_prio_reg *pr = desc->prio_regs + i;
|
||||
|
||||
mode = sh_intc_mode(address, pr->set_reg, pr->clr_reg);
|
||||
if (mode == INTC_MODE_NONE)
|
||||
continue;
|
||||
|
||||
*modep = mode | INTC_MODE_IS_PRIO;
|
||||
*datap = &pr->value;
|
||||
*enums = pr->enum_ids;
|
||||
*first = (pr->reg_width / pr->field_width) - 1;
|
||||
*width = pr->field_width;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
abort();
|
||||
}
|
||||
|
||||
static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id,
|
||||
int enable, int is_group)
|
||||
{
|
||||
struct intc_source *source = desc->sources + id;
|
||||
|
||||
if (!id)
|
||||
return;
|
||||
|
||||
if (!source->next_enum_id && (!source->enable_max || !source->vect)) {
|
||||
#ifdef DEBUG_INTC_SOURCES
|
||||
printf("sh_intc: reserved interrupt source %d modified\n", id);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (source->vect)
|
||||
sh_intc_toggle_source(source, enable ? 1 : -1, 0);
|
||||
|
||||
#ifdef DEBUG_INTC
|
||||
else {
|
||||
printf("setting interrupt group %d to %d\n", id, !!enable);
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((is_group || !source->vect) && source->next_enum_id) {
|
||||
sh_intc_toggle_mask(desc, source->next_enum_id, enable, 1);
|
||||
}
|
||||
|
||||
#ifdef DEBUG_INTC
|
||||
if (!source->vect) {
|
||||
printf("setting interrupt group %d to %d - done\n", id, !!enable);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint64_t sh_intc_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
struct intc_desc *desc = opaque;
|
||||
intc_enum *enum_ids = NULL;
|
||||
unsigned int first = 0;
|
||||
unsigned int width = 0;
|
||||
unsigned int mode = 0;
|
||||
unsigned long *valuep;
|
||||
|
||||
#ifdef DEBUG_INTC
|
||||
printf("sh_intc_read 0x%lx\n", (unsigned long) offset);
|
||||
#endif
|
||||
|
||||
sh_intc_locate(desc, (unsigned long)offset, &valuep,
|
||||
&enum_ids, &first, &width, &mode);
|
||||
return *valuep;
|
||||
}
|
||||
|
||||
static void sh_intc_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
struct intc_desc *desc = opaque;
|
||||
intc_enum *enum_ids = NULL;
|
||||
unsigned int first = 0;
|
||||
unsigned int width = 0;
|
||||
unsigned int mode = 0;
|
||||
unsigned int k;
|
||||
unsigned long *valuep;
|
||||
unsigned long mask;
|
||||
|
||||
#ifdef DEBUG_INTC
|
||||
printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value);
|
||||
#endif
|
||||
|
||||
sh_intc_locate(desc, (unsigned long)offset, &valuep,
|
||||
&enum_ids, &first, &width, &mode);
|
||||
|
||||
switch (mode) {
|
||||
case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break;
|
||||
case INTC_MODE_DUAL_SET: value |= *valuep; break;
|
||||
case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break;
|
||||
default: abort();
|
||||
}
|
||||
|
||||
for (k = 0; k <= first; k++) {
|
||||
mask = ((1 << width) - 1) << ((first - k) * width);
|
||||
|
||||
if ((*valuep & mask) == (value & mask))
|
||||
continue;
|
||||
#if 0
|
||||
printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n",
|
||||
k, first, enum_ids[k], (unsigned int)mask);
|
||||
#endif
|
||||
sh_intc_toggle_mask(desc, enum_ids[k], value & mask, 0);
|
||||
}
|
||||
|
||||
*valuep = value;
|
||||
|
||||
#ifdef DEBUG_INTC
|
||||
printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset, value);
|
||||
#endif
|
||||
}
|
||||
|
||||
static const MemoryRegionOps sh_intc_ops = {
|
||||
.read = sh_intc_read,
|
||||
.write = sh_intc_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
};
|
||||
|
||||
struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id)
|
||||
{
|
||||
if (id)
|
||||
return desc->sources + id;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static unsigned int sh_intc_register(MemoryRegion *sysmem,
|
||||
struct intc_desc *desc,
|
||||
const unsigned long address,
|
||||
const char *type,
|
||||
const char *action,
|
||||
const unsigned int index)
|
||||
{
|
||||
char name[60];
|
||||
MemoryRegion *iomem, *iomem_p4, *iomem_a7;
|
||||
|
||||
if (!address) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
iomem = &desc->iomem;
|
||||
iomem_p4 = desc->iomem_aliases + index;
|
||||
iomem_a7 = iomem_p4 + 1;
|
||||
|
||||
#define SH_INTC_IOMEM_FORMAT "interrupt-controller-%s-%s-%s"
|
||||
snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "p4");
|
||||
memory_region_init_alias(iomem_p4, name, iomem, INTC_A7(address), 4);
|
||||
memory_region_add_subregion(sysmem, P4ADDR(address), iomem_p4);
|
||||
|
||||
snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "a7");
|
||||
memory_region_init_alias(iomem_a7, name, iomem, INTC_A7(address), 4);
|
||||
memory_region_add_subregion(sysmem, A7ADDR(address), iomem_a7);
|
||||
#undef SH_INTC_IOMEM_FORMAT
|
||||
|
||||
/* used to increment aliases index */
|
||||
return 2;
|
||||
}
|
||||
|
||||
static void sh_intc_register_source(struct intc_desc *desc,
|
||||
intc_enum source,
|
||||
struct intc_group *groups,
|
||||
int nr_groups)
|
||||
{
|
||||
unsigned int i, k;
|
||||
struct intc_source *s;
|
||||
|
||||
if (desc->mask_regs) {
|
||||
for (i = 0; i < desc->nr_mask_regs; i++) {
|
||||
struct intc_mask_reg *mr = desc->mask_regs + i;
|
||||
|
||||
for (k = 0; k < ARRAY_SIZE(mr->enum_ids); k++) {
|
||||
if (mr->enum_ids[k] != source)
|
||||
continue;
|
||||
|
||||
s = sh_intc_source(desc, mr->enum_ids[k]);
|
||||
if (s)
|
||||
s->enable_max++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (desc->prio_regs) {
|
||||
for (i = 0; i < desc->nr_prio_regs; i++) {
|
||||
struct intc_prio_reg *pr = desc->prio_regs + i;
|
||||
|
||||
for (k = 0; k < ARRAY_SIZE(pr->enum_ids); k++) {
|
||||
if (pr->enum_ids[k] != source)
|
||||
continue;
|
||||
|
||||
s = sh_intc_source(desc, pr->enum_ids[k]);
|
||||
if (s)
|
||||
s->enable_max++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (groups) {
|
||||
for (i = 0; i < nr_groups; i++) {
|
||||
struct intc_group *gr = groups + i;
|
||||
|
||||
for (k = 0; k < ARRAY_SIZE(gr->enum_ids); k++) {
|
||||
if (gr->enum_ids[k] != source)
|
||||
continue;
|
||||
|
||||
s = sh_intc_source(desc, gr->enum_ids[k]);
|
||||
if (s)
|
||||
s->enable_max++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void sh_intc_register_sources(struct intc_desc *desc,
|
||||
struct intc_vect *vectors,
|
||||
int nr_vectors,
|
||||
struct intc_group *groups,
|
||||
int nr_groups)
|
||||
{
|
||||
unsigned int i, k;
|
||||
struct intc_source *s;
|
||||
|
||||
for (i = 0; i < nr_vectors; i++) {
|
||||
struct intc_vect *vect = vectors + i;
|
||||
|
||||
sh_intc_register_source(desc, vect->enum_id, groups, nr_groups);
|
||||
s = sh_intc_source(desc, vect->enum_id);
|
||||
if (s) {
|
||||
s->vect = vect->vect;
|
||||
|
||||
#ifdef DEBUG_INTC_SOURCES
|
||||
printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n",
|
||||
vect->enum_id, s->vect, s->enable_count, s->enable_max);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (groups) {
|
||||
for (i = 0; i < nr_groups; i++) {
|
||||
struct intc_group *gr = groups + i;
|
||||
|
||||
s = sh_intc_source(desc, gr->enum_id);
|
||||
s->next_enum_id = gr->enum_ids[0];
|
||||
|
||||
for (k = 1; k < ARRAY_SIZE(gr->enum_ids); k++) {
|
||||
if (!gr->enum_ids[k])
|
||||
continue;
|
||||
|
||||
s = sh_intc_source(desc, gr->enum_ids[k - 1]);
|
||||
s->next_enum_id = gr->enum_ids[k];
|
||||
}
|
||||
|
||||
#ifdef DEBUG_INTC_SOURCES
|
||||
printf("sh_intc: registered group %d (%d/%d)\n",
|
||||
gr->enum_id, s->enable_count, s->enable_max);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int sh_intc_init(MemoryRegion *sysmem,
|
||||
struct intc_desc *desc,
|
||||
int nr_sources,
|
||||
struct intc_mask_reg *mask_regs,
|
||||
int nr_mask_regs,
|
||||
struct intc_prio_reg *prio_regs,
|
||||
int nr_prio_regs)
|
||||
{
|
||||
unsigned int i, j;
|
||||
|
||||
desc->pending = 0;
|
||||
desc->nr_sources = nr_sources;
|
||||
desc->mask_regs = mask_regs;
|
||||
desc->nr_mask_regs = nr_mask_regs;
|
||||
desc->prio_regs = prio_regs;
|
||||
desc->nr_prio_regs = nr_prio_regs;
|
||||
/* Allocate 4 MemoryRegions per register (2 actions * 2 aliases).
|
||||
**/
|
||||
desc->iomem_aliases = g_new0(MemoryRegion,
|
||||
(nr_mask_regs + nr_prio_regs) * 4);
|
||||
|
||||
j = 0;
|
||||
i = sizeof(struct intc_source) * nr_sources;
|
||||
desc->sources = g_malloc0(i);
|
||||
|
||||
for (i = 0; i < desc->nr_sources; i++) {
|
||||
struct intc_source *source = desc->sources + i;
|
||||
|
||||
source->parent = desc;
|
||||
}
|
||||
|
||||
desc->irqs = qemu_allocate_irqs(sh_intc_set_irq, desc, nr_sources);
|
||||
|
||||
memory_region_init_io(&desc->iomem, &sh_intc_ops, desc,
|
||||
"interrupt-controller", 0x100000000ULL);
|
||||
|
||||
#define INT_REG_PARAMS(reg_struct, type, action, j) \
|
||||
reg_struct->action##_reg, #type, #action, j
|
||||
if (desc->mask_regs) {
|
||||
for (i = 0; i < desc->nr_mask_regs; i++) {
|
||||
struct intc_mask_reg *mr = desc->mask_regs + i;
|
||||
|
||||
j += sh_intc_register(sysmem, desc,
|
||||
INT_REG_PARAMS(mr, mask, set, j));
|
||||
j += sh_intc_register(sysmem, desc,
|
||||
INT_REG_PARAMS(mr, mask, clr, j));
|
||||
}
|
||||
}
|
||||
|
||||
if (desc->prio_regs) {
|
||||
for (i = 0; i < desc->nr_prio_regs; i++) {
|
||||
struct intc_prio_reg *pr = desc->prio_regs + i;
|
||||
|
||||
j += sh_intc_register(sysmem, desc,
|
||||
INT_REG_PARAMS(pr, prio, set, j));
|
||||
j += sh_intc_register(sysmem, desc,
|
||||
INT_REG_PARAMS(pr, prio, clr, j));
|
||||
}
|
||||
}
|
||||
#undef INT_REG_PARAMS
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Assert level <n> IRL interrupt.
|
||||
0:deassert. 1:lowest priority,... 15:highest priority. */
|
||||
void sh_intc_set_irl(void *opaque, int n, int level)
|
||||
{
|
||||
struct intc_source *s = opaque;
|
||||
int i, irl = level ^ 15;
|
||||
for (i = 0; (s = sh_intc_source(s->parent, s->next_enum_id)); i++) {
|
||||
if (i == irl)
|
||||
sh_intc_toggle_source(s, s->enable_count?0:1, s->asserted?0:1);
|
||||
else
|
||||
if (s->asserted)
|
||||
sh_intc_toggle_source(s, 0, -1);
|
||||
}
|
||||
}
|
471
hw/intc/slavio_intctl.c
Normal file
471
hw/intc/slavio_intctl.c
Normal file
|
@ -0,0 +1,471 @@
|
|||
/*
|
||||
* QEMU Sparc SLAVIO interrupt controller emulation
|
||||
*
|
||||
* Copyright (c) 2003-2005 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "hw/sparc/sun4m.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "trace.h"
|
||||
|
||||
//#define DEBUG_IRQ_COUNT
|
||||
|
||||
/*
|
||||
* Registers of interrupt controller in sun4m.
|
||||
*
|
||||
* This is the interrupt controller part of chip STP2001 (Slave I/O), also
|
||||
* produced as NCR89C105. See
|
||||
* http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt
|
||||
*
|
||||
* There is a system master controller and one for each cpu.
|
||||
*
|
||||
*/
|
||||
|
||||
#define MAX_CPUS 16
|
||||
#define MAX_PILS 16
|
||||
|
||||
struct SLAVIO_INTCTLState;
|
||||
|
||||
typedef struct SLAVIO_CPUINTCTLState {
|
||||
MemoryRegion iomem;
|
||||
struct SLAVIO_INTCTLState *master;
|
||||
uint32_t intreg_pending;
|
||||
uint32_t cpu;
|
||||
uint32_t irl_out;
|
||||
} SLAVIO_CPUINTCTLState;
|
||||
|
||||
typedef struct SLAVIO_INTCTLState {
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion iomem;
|
||||
#ifdef DEBUG_IRQ_COUNT
|
||||
uint64_t irq_count[32];
|
||||
#endif
|
||||
qemu_irq cpu_irqs[MAX_CPUS][MAX_PILS];
|
||||
SLAVIO_CPUINTCTLState slaves[MAX_CPUS];
|
||||
uint32_t intregm_pending;
|
||||
uint32_t intregm_disabled;
|
||||
uint32_t target_cpu;
|
||||
} SLAVIO_INTCTLState;
|
||||
|
||||
#define INTCTL_MAXADDR 0xf
|
||||
#define INTCTL_SIZE (INTCTL_MAXADDR + 1)
|
||||
#define INTCTLM_SIZE 0x14
|
||||
#define MASTER_IRQ_MASK ~0x0fa2007f
|
||||
#define MASTER_DISABLE 0x80000000
|
||||
#define CPU_SOFTIRQ_MASK 0xfffe0000
|
||||
#define CPU_IRQ_INT15_IN (1 << 15)
|
||||
#define CPU_IRQ_TIMER_IN (1 << 14)
|
||||
|
||||
static void slavio_check_interrupts(SLAVIO_INTCTLState *s, int set_irqs);
|
||||
|
||||
// per-cpu interrupt controller
|
||||
static uint64_t slavio_intctl_mem_readl(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
SLAVIO_CPUINTCTLState *s = opaque;
|
||||
uint32_t saddr, ret;
|
||||
|
||||
saddr = addr >> 2;
|
||||
switch (saddr) {
|
||||
case 0:
|
||||
ret = s->intreg_pending;
|
||||
break;
|
||||
default:
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
trace_slavio_intctl_mem_readl(s->cpu, addr, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void slavio_intctl_mem_writel(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
SLAVIO_CPUINTCTLState *s = opaque;
|
||||
uint32_t saddr;
|
||||
|
||||
saddr = addr >> 2;
|
||||
trace_slavio_intctl_mem_writel(s->cpu, addr, val);
|
||||
switch (saddr) {
|
||||
case 1: // clear pending softints
|
||||
val &= CPU_SOFTIRQ_MASK | CPU_IRQ_INT15_IN;
|
||||
s->intreg_pending &= ~val;
|
||||
slavio_check_interrupts(s->master, 1);
|
||||
trace_slavio_intctl_mem_writel_clear(s->cpu, val, s->intreg_pending);
|
||||
break;
|
||||
case 2: // set softint
|
||||
val &= CPU_SOFTIRQ_MASK;
|
||||
s->intreg_pending |= val;
|
||||
slavio_check_interrupts(s->master, 1);
|
||||
trace_slavio_intctl_mem_writel_set(s->cpu, val, s->intreg_pending);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps slavio_intctl_mem_ops = {
|
||||
.read = slavio_intctl_mem_readl,
|
||||
.write = slavio_intctl_mem_writel,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
// master system interrupt controller
|
||||
static uint64_t slavio_intctlm_mem_readl(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
SLAVIO_INTCTLState *s = opaque;
|
||||
uint32_t saddr, ret;
|
||||
|
||||
saddr = addr >> 2;
|
||||
switch (saddr) {
|
||||
case 0:
|
||||
ret = s->intregm_pending & ~MASTER_DISABLE;
|
||||
break;
|
||||
case 1:
|
||||
ret = s->intregm_disabled & MASTER_IRQ_MASK;
|
||||
break;
|
||||
case 4:
|
||||
ret = s->target_cpu;
|
||||
break;
|
||||
default:
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
trace_slavio_intctlm_mem_readl(addr, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void slavio_intctlm_mem_writel(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
SLAVIO_INTCTLState *s = opaque;
|
||||
uint32_t saddr;
|
||||
|
||||
saddr = addr >> 2;
|
||||
trace_slavio_intctlm_mem_writel(addr, val);
|
||||
switch (saddr) {
|
||||
case 2: // clear (enable)
|
||||
// Force clear unused bits
|
||||
val &= MASTER_IRQ_MASK;
|
||||
s->intregm_disabled &= ~val;
|
||||
trace_slavio_intctlm_mem_writel_enable(val, s->intregm_disabled);
|
||||
slavio_check_interrupts(s, 1);
|
||||
break;
|
||||
case 3: // set (disable; doesn't affect pending)
|
||||
// Force clear unused bits
|
||||
val &= MASTER_IRQ_MASK;
|
||||
s->intregm_disabled |= val;
|
||||
slavio_check_interrupts(s, 1);
|
||||
trace_slavio_intctlm_mem_writel_disable(val, s->intregm_disabled);
|
||||
break;
|
||||
case 4:
|
||||
s->target_cpu = val & (MAX_CPUS - 1);
|
||||
slavio_check_interrupts(s, 1);
|
||||
trace_slavio_intctlm_mem_writel_target(s->target_cpu);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps slavio_intctlm_mem_ops = {
|
||||
.read = slavio_intctlm_mem_readl,
|
||||
.write = slavio_intctlm_mem_writel,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
void slavio_pic_info(Monitor *mon, DeviceState *dev)
|
||||
{
|
||||
SysBusDevice *sd;
|
||||
SLAVIO_INTCTLState *s;
|
||||
int i;
|
||||
|
||||
sd = SYS_BUS_DEVICE(dev);
|
||||
s = FROM_SYSBUS(SLAVIO_INTCTLState, sd);
|
||||
for (i = 0; i < MAX_CPUS; i++) {
|
||||
monitor_printf(mon, "per-cpu %d: pending 0x%08x\n", i,
|
||||
s->slaves[i].intreg_pending);
|
||||
}
|
||||
monitor_printf(mon, "master: pending 0x%08x, disabled 0x%08x\n",
|
||||
s->intregm_pending, s->intregm_disabled);
|
||||
}
|
||||
|
||||
void slavio_irq_info(Monitor *mon, DeviceState *dev)
|
||||
{
|
||||
#ifndef DEBUG_IRQ_COUNT
|
||||
monitor_printf(mon, "irq statistic code not compiled.\n");
|
||||
#else
|
||||
SysBusDevice *sd;
|
||||
SLAVIO_INTCTLState *s;
|
||||
int i;
|
||||
int64_t count;
|
||||
|
||||
sd = SYS_BUS_DEVICE(dev);
|
||||
s = FROM_SYSBUS(SLAVIO_INTCTLState, sd);
|
||||
monitor_printf(mon, "IRQ statistics:\n");
|
||||
for (i = 0; i < 32; i++) {
|
||||
count = s->irq_count[i];
|
||||
if (count > 0)
|
||||
monitor_printf(mon, "%2d: %" PRId64 "\n", i, count);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static const uint32_t intbit_to_level[] = {
|
||||
2, 3, 5, 7, 9, 11, 13, 2, 3, 5, 7, 9, 11, 13, 12, 12,
|
||||
6, 13, 4, 10, 8, 9, 11, 0, 0, 0, 0, 15, 15, 15, 15, 0,
|
||||
};
|
||||
|
||||
static void slavio_check_interrupts(SLAVIO_INTCTLState *s, int set_irqs)
|
||||
{
|
||||
uint32_t pending = s->intregm_pending, pil_pending;
|
||||
unsigned int i, j;
|
||||
|
||||
pending &= ~s->intregm_disabled;
|
||||
|
||||
trace_slavio_check_interrupts(pending, s->intregm_disabled);
|
||||
for (i = 0; i < MAX_CPUS; i++) {
|
||||
pil_pending = 0;
|
||||
|
||||
/* If we are the current interrupt target, get hard interrupts */
|
||||
if (pending && !(s->intregm_disabled & MASTER_DISABLE) &&
|
||||
(i == s->target_cpu)) {
|
||||
for (j = 0; j < 32; j++) {
|
||||
if ((pending & (1 << j)) && intbit_to_level[j]) {
|
||||
pil_pending |= 1 << intbit_to_level[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate current pending hard interrupts for display */
|
||||
s->slaves[i].intreg_pending &= CPU_SOFTIRQ_MASK | CPU_IRQ_INT15_IN |
|
||||
CPU_IRQ_TIMER_IN;
|
||||
if (i == s->target_cpu) {
|
||||
for (j = 0; j < 32; j++) {
|
||||
if ((s->intregm_pending & (1 << j)) && intbit_to_level[j]) {
|
||||
s->slaves[i].intreg_pending |= 1 << intbit_to_level[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Level 15 and CPU timer interrupts are only masked when
|
||||
the MASTER_DISABLE bit is set */
|
||||
if (!(s->intregm_disabled & MASTER_DISABLE)) {
|
||||
pil_pending |= s->slaves[i].intreg_pending &
|
||||
(CPU_IRQ_INT15_IN | CPU_IRQ_TIMER_IN);
|
||||
}
|
||||
|
||||
/* Add soft interrupts */
|
||||
pil_pending |= (s->slaves[i].intreg_pending & CPU_SOFTIRQ_MASK) >> 16;
|
||||
|
||||
if (set_irqs) {
|
||||
/* Since there is not really an interrupt 0 (and pil_pending
|
||||
* and irl_out bit zero are thus always zero) there is no need
|
||||
* to do anything with cpu_irqs[i][0] and it is OK not to do
|
||||
* the j=0 iteration of this loop.
|
||||
*/
|
||||
for (j = MAX_PILS-1; j > 0; j--) {
|
||||
if (pil_pending & (1 << j)) {
|
||||
if (!(s->slaves[i].irl_out & (1 << j))) {
|
||||
qemu_irq_raise(s->cpu_irqs[i][j]);
|
||||
}
|
||||
} else {
|
||||
if (s->slaves[i].irl_out & (1 << j)) {
|
||||
qemu_irq_lower(s->cpu_irqs[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
s->slaves[i].irl_out = pil_pending;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* "irq" here is the bit number in the system interrupt register to
|
||||
* separate serial and keyboard interrupts sharing a level.
|
||||
*/
|
||||
static void slavio_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
SLAVIO_INTCTLState *s = opaque;
|
||||
uint32_t mask = 1 << irq;
|
||||
uint32_t pil = intbit_to_level[irq];
|
||||
unsigned int i;
|
||||
|
||||
trace_slavio_set_irq(s->target_cpu, irq, pil, level);
|
||||
if (pil > 0) {
|
||||
if (level) {
|
||||
#ifdef DEBUG_IRQ_COUNT
|
||||
s->irq_count[pil]++;
|
||||
#endif
|
||||
s->intregm_pending |= mask;
|
||||
if (pil == 15) {
|
||||
for (i = 0; i < MAX_CPUS; i++) {
|
||||
s->slaves[i].intreg_pending |= 1 << pil;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s->intregm_pending &= ~mask;
|
||||
if (pil == 15) {
|
||||
for (i = 0; i < MAX_CPUS; i++) {
|
||||
s->slaves[i].intreg_pending &= ~(1 << pil);
|
||||
}
|
||||
}
|
||||
}
|
||||
slavio_check_interrupts(s, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void slavio_set_timer_irq_cpu(void *opaque, int cpu, int level)
|
||||
{
|
||||
SLAVIO_INTCTLState *s = opaque;
|
||||
|
||||
trace_slavio_set_timer_irq_cpu(cpu, level);
|
||||
|
||||
if (level) {
|
||||
s->slaves[cpu].intreg_pending |= CPU_IRQ_TIMER_IN;
|
||||
} else {
|
||||
s->slaves[cpu].intreg_pending &= ~CPU_IRQ_TIMER_IN;
|
||||
}
|
||||
|
||||
slavio_check_interrupts(s, 1);
|
||||
}
|
||||
|
||||
static void slavio_set_irq_all(void *opaque, int irq, int level)
|
||||
{
|
||||
if (irq < 32) {
|
||||
slavio_set_irq(opaque, irq, level);
|
||||
} else {
|
||||
slavio_set_timer_irq_cpu(opaque, irq - 32, level);
|
||||
}
|
||||
}
|
||||
|
||||
static int vmstate_intctl_post_load(void *opaque, int version_id)
|
||||
{
|
||||
SLAVIO_INTCTLState *s = opaque;
|
||||
|
||||
slavio_check_interrupts(s, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_intctl_cpu = {
|
||||
.name ="slavio_intctl_cpu",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_UINT32(intreg_pending, SLAVIO_CPUINTCTLState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription vmstate_intctl = {
|
||||
.name ="slavio_intctl",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.post_load = vmstate_intctl_post_load,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_STRUCT_ARRAY(slaves, SLAVIO_INTCTLState, MAX_CPUS, 1,
|
||||
vmstate_intctl_cpu, SLAVIO_CPUINTCTLState),
|
||||
VMSTATE_UINT32(intregm_pending, SLAVIO_INTCTLState),
|
||||
VMSTATE_UINT32(intregm_disabled, SLAVIO_INTCTLState),
|
||||
VMSTATE_UINT32(target_cpu, SLAVIO_INTCTLState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void slavio_intctl_reset(DeviceState *d)
|
||||
{
|
||||
SLAVIO_INTCTLState *s = container_of(d, SLAVIO_INTCTLState, busdev.qdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_CPUS; i++) {
|
||||
s->slaves[i].intreg_pending = 0;
|
||||
s->slaves[i].irl_out = 0;
|
||||
}
|
||||
s->intregm_disabled = ~MASTER_IRQ_MASK;
|
||||
s->intregm_pending = 0;
|
||||
s->target_cpu = 0;
|
||||
slavio_check_interrupts(s, 0);
|
||||
}
|
||||
|
||||
static int slavio_intctl_init1(SysBusDevice *dev)
|
||||
{
|
||||
SLAVIO_INTCTLState *s = FROM_SYSBUS(SLAVIO_INTCTLState, dev);
|
||||
unsigned int i, j;
|
||||
char slave_name[45];
|
||||
|
||||
qdev_init_gpio_in(&dev->qdev, slavio_set_irq_all, 32 + MAX_CPUS);
|
||||
memory_region_init_io(&s->iomem, &slavio_intctlm_mem_ops, s,
|
||||
"master-interrupt-controller", INTCTLM_SIZE);
|
||||
sysbus_init_mmio(dev, &s->iomem);
|
||||
|
||||
for (i = 0; i < MAX_CPUS; i++) {
|
||||
snprintf(slave_name, sizeof(slave_name),
|
||||
"slave-interrupt-controller-%i", i);
|
||||
for (j = 0; j < MAX_PILS; j++) {
|
||||
sysbus_init_irq(dev, &s->cpu_irqs[i][j]);
|
||||
}
|
||||
memory_region_init_io(&s->slaves[i].iomem, &slavio_intctl_mem_ops,
|
||||
&s->slaves[i], slave_name, INTCTL_SIZE);
|
||||
sysbus_init_mmio(dev, &s->slaves[i].iomem);
|
||||
s->slaves[i].cpu = i;
|
||||
s->slaves[i].master = s;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void slavio_intctl_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = slavio_intctl_init1;
|
||||
dc->reset = slavio_intctl_reset;
|
||||
dc->vmsd = &vmstate_intctl;
|
||||
}
|
||||
|
||||
static const TypeInfo slavio_intctl_info = {
|
||||
.name = "slavio_intctl",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(SLAVIO_INTCTLState),
|
||||
.class_init = slavio_intctl_class_init,
|
||||
};
|
||||
|
||||
static void slavio_intctl_register_types(void)
|
||||
{
|
||||
type_register_static(&slavio_intctl_info);
|
||||
}
|
||||
|
||||
type_init(slavio_intctl_register_types)
|
208
hw/intc/sun4c_intctl.c
Normal file
208
hw/intc/sun4c_intctl.c
Normal file
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* QEMU Sparc Sun4c interrupt controller emulation
|
||||
*
|
||||
* Based on slavio_intctl, copyright (c) 2003-2005 Fabrice Bellard
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "hw/hw.h"
|
||||
#include "hw/sparc/sun4m.h"
|
||||
#include "monitor/monitor.h"
|
||||
#include "hw/sysbus.h"
|
||||
|
||||
//#define DEBUG_IRQ_COUNT
|
||||
//#define DEBUG_IRQ
|
||||
|
||||
#ifdef DEBUG_IRQ
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { printf("IRQ: " fmt , ## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Registers of interrupt controller in sun4c.
|
||||
*
|
||||
*/
|
||||
|
||||
#define MAX_PILS 16
|
||||
|
||||
typedef struct Sun4c_INTCTLState {
|
||||
SysBusDevice busdev;
|
||||
MemoryRegion iomem;
|
||||
#ifdef DEBUG_IRQ_COUNT
|
||||
uint64_t irq_count;
|
||||
#endif
|
||||
qemu_irq cpu_irqs[MAX_PILS];
|
||||
const uint32_t *intbit_to_level;
|
||||
uint32_t pil_out;
|
||||
uint8_t reg;
|
||||
uint8_t pending;
|
||||
} Sun4c_INTCTLState;
|
||||
|
||||
#define INTCTL_SIZE 1
|
||||
|
||||
static void sun4c_check_interrupts(void *opaque);
|
||||
|
||||
static uint64_t sun4c_intctl_mem_read(void *opaque, hwaddr addr,
|
||||
unsigned size)
|
||||
{
|
||||
Sun4c_INTCTLState *s = opaque;
|
||||
uint32_t ret;
|
||||
|
||||
ret = s->reg;
|
||||
DPRINTF("read reg 0x" TARGET_FMT_plx " = %x\n", addr, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sun4c_intctl_mem_write(void *opaque, hwaddr addr,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
Sun4c_INTCTLState *s = opaque;
|
||||
|
||||
DPRINTF("write reg 0x" TARGET_FMT_plx " = %x\n", addr, (unsigned)val);
|
||||
val &= 0xbf;
|
||||
s->reg = val;
|
||||
sun4c_check_interrupts(s);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps sun4c_intctl_mem_ops = {
|
||||
.read = sun4c_intctl_mem_read,
|
||||
.write = sun4c_intctl_mem_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 1,
|
||||
},
|
||||
};
|
||||
|
||||
static const uint32_t intbit_to_level[] = { 0, 1, 4, 6, 8, 10, 0, 14, };
|
||||
|
||||
static void sun4c_check_interrupts(void *opaque)
|
||||
{
|
||||
Sun4c_INTCTLState *s = opaque;
|
||||
uint32_t pil_pending;
|
||||
unsigned int i;
|
||||
|
||||
pil_pending = 0;
|
||||
if (s->pending && !(s->reg & 0x80000000)) {
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (s->pending & (1 << i))
|
||||
pil_pending |= 1 << intbit_to_level[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX_PILS; i++) {
|
||||
if (pil_pending & (1 << i)) {
|
||||
if (!(s->pil_out & (1 << i)))
|
||||
qemu_irq_raise(s->cpu_irqs[i]);
|
||||
} else {
|
||||
if (s->pil_out & (1 << i))
|
||||
qemu_irq_lower(s->cpu_irqs[i]);
|
||||
}
|
||||
}
|
||||
s->pil_out = pil_pending;
|
||||
}
|
||||
|
||||
/*
|
||||
* "irq" here is the bit number in the system interrupt register
|
||||
*/
|
||||
static void sun4c_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
Sun4c_INTCTLState *s = opaque;
|
||||
uint32_t mask = 1 << irq;
|
||||
uint32_t pil = intbit_to_level[irq];
|
||||
|
||||
DPRINTF("Set irq %d -> pil %d level %d\n", irq, pil,
|
||||
level);
|
||||
if (pil > 0) {
|
||||
if (level) {
|
||||
#ifdef DEBUG_IRQ_COUNT
|
||||
s->irq_count++;
|
||||
#endif
|
||||
s->pending |= mask;
|
||||
} else {
|
||||
s->pending &= ~mask;
|
||||
}
|
||||
sun4c_check_interrupts(s);
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_sun4c_intctl = {
|
||||
.name ="sun4c_intctl",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.minimum_version_id_old = 1,
|
||||
.fields = (VMStateField []) {
|
||||
VMSTATE_UINT8(reg, Sun4c_INTCTLState),
|
||||
VMSTATE_UINT8(pending, Sun4c_INTCTLState),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static void sun4c_intctl_reset(DeviceState *d)
|
||||
{
|
||||
Sun4c_INTCTLState *s = container_of(d, Sun4c_INTCTLState, busdev.qdev);
|
||||
|
||||
s->reg = 1;
|
||||
s->pending = 0;
|
||||
}
|
||||
|
||||
static int sun4c_intctl_init1(SysBusDevice *dev)
|
||||
{
|
||||
Sun4c_INTCTLState *s = FROM_SYSBUS(Sun4c_INTCTLState, dev);
|
||||
unsigned int i;
|
||||
|
||||
memory_region_init_io(&s->iomem, &sun4c_intctl_mem_ops, s,
|
||||
"intctl", INTCTL_SIZE);
|
||||
sysbus_init_mmio(dev, &s->iomem);
|
||||
qdev_init_gpio_in(&dev->qdev, sun4c_set_irq, 8);
|
||||
|
||||
for (i = 0; i < MAX_PILS; i++) {
|
||||
sysbus_init_irq(dev, &s->cpu_irqs[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sun4c_intctl_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
|
||||
|
||||
k->init = sun4c_intctl_init1;
|
||||
dc->reset = sun4c_intctl_reset;
|
||||
dc->vmsd = &vmstate_sun4c_intctl;
|
||||
}
|
||||
|
||||
static const TypeInfo sun4c_intctl_info = {
|
||||
.name = "sun4c_intctl",
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(Sun4c_INTCTLState),
|
||||
.class_init = sun4c_intctl_class_init,
|
||||
};
|
||||
|
||||
static void sun4c_intctl_register_types(void)
|
||||
{
|
||||
type_register_static(&sun4c_intctl_info);
|
||||
}
|
||||
|
||||
type_init(sun4c_intctl_register_types)
|
Loading…
Add table
Add a link
Reference in a new issue