The KVM in-kernel APIC model will reuse parts of the user space model while providing the same frontend view to guest and most management interfaces. Factor out an APIC base class to encapsulate those parts that will be shared by user space and KVM model. This class offers callback hooks for init, base/tpr setting, and the external NMI delivery that will be set via APICCommonInfo structure and implemented specifically in the subclasses. Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
253 lines
7 KiB
C
253 lines
7 KiB
C
/*
|
|
* 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 "apic.h"
|
|
#include "apic_internal.h"
|
|
#include "trace.h"
|
|
|
|
static int apic_irq_delivered;
|
|
|
|
void cpu_set_apic_base(DeviceState *d, uint64_t val)
|
|
{
|
|
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
|
APICCommonInfo *info;
|
|
|
|
trace_cpu_set_apic_base(val);
|
|
|
|
if (s) {
|
|
info = DO_UPCAST(APICCommonInfo, busdev.qdev, s->busdev.qdev.info);
|
|
info->set_base(s, val);
|
|
}
|
|
}
|
|
|
|
uint64_t cpu_get_apic_base(DeviceState *d)
|
|
{
|
|
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
|
|
|
trace_cpu_get_apic_base(s ? (uint64_t)s->apicbase : 0);
|
|
|
|
return s ? s->apicbase : 0;
|
|
}
|
|
|
|
void cpu_set_apic_tpr(DeviceState *d, uint8_t val)
|
|
{
|
|
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
|
APICCommonInfo *info;
|
|
|
|
if (s) {
|
|
info = DO_UPCAST(APICCommonInfo, busdev.qdev, s->busdev.qdev.info);
|
|
info->set_tpr(s, val);
|
|
}
|
|
}
|
|
|
|
uint8_t cpu_get_apic_tpr(DeviceState *d)
|
|
{
|
|
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
|
|
|
return s ? s->tpr >> 4 : 0;
|
|
}
|
|
|
|
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 = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
|
APICCommonInfo *info;
|
|
|
|
info = DO_UPCAST(APICCommonInfo, busdev.qdev, s->busdev.qdev.info);
|
|
info->external_nmi(s);
|
|
}
|
|
|
|
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;
|
|
|
|
qemu_del_timer(s->timer);
|
|
}
|
|
|
|
static void apic_reset_common(DeviceState *d)
|
|
{
|
|
APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d);
|
|
bool bsp;
|
|
|
|
bsp = cpu_is_bsp(s->cpu_env);
|
|
s->apicbase = 0xfee00000 |
|
|
(bsp ? MSR_IA32_APICBASE_BSP : 0) | MSR_IA32_APICBASE_ENABLE;
|
|
|
|
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;
|
|
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) {
|
|
qemu_get_timer(f, s->timer);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int apic_init_common(SysBusDevice *dev)
|
|
{
|
|
APICCommonState *s = FROM_SYSBUS(APICCommonState, dev);
|
|
APICCommonInfo *info;
|
|
static int apic_no;
|
|
|
|
if (apic_no >= MAX_APICS) {
|
|
return -1;
|
|
}
|
|
s->idx = apic_no++;
|
|
|
|
info = DO_UPCAST(APICCommonInfo, busdev.qdev, s->busdev.qdev.info);
|
|
info->init(s);
|
|
|
|
sysbus_init_mmio(&s->busdev, &s->io_memory);
|
|
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,
|
|
.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_TIMER(timer, APICCommonState),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static Property apic_properties_common[] = {
|
|
DEFINE_PROP_UINT8("id", APICCommonState, id, -1),
|
|
DEFINE_PROP_PTR("cpu_env", APICCommonState, cpu_env),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
|
|
void apic_qdev_register(APICCommonInfo *info)
|
|
{
|
|
info->busdev.init = apic_init_common;
|
|
info->busdev.qdev.size = sizeof(APICCommonState),
|
|
info->busdev.qdev.vmsd = &vmstate_apic_common;
|
|
info->busdev.qdev.reset = apic_reset_common;
|
|
info->busdev.qdev.no_user = 1;
|
|
info->busdev.qdev.props = apic_properties_common;
|
|
sysbus_register_withprop(&info->busdev);
|
|
}
|