blob: ea420c718888ddaca6b7d5f6c2ee9426ef853f5e [file] [log] [blame]
/*
* 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"
#include "hw/qdev.h"
#include "hw/sysbus.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) {
timer_del(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(ICCDevice *dev)
{
APICCommonState *s = APIC_COMMON(dev);
APICCommonClass *info;
static DeviceState *vapic;
static int apic_no;
static bool mmio_registered;
if (apic_no >= MAX_APICS) {
return -1;
}
s->idx = apic_no++;
info = APIC_COMMON_GET_CLASS(s);
info->init(s);
if (!mmio_registered) {
ICCBus *b = ICC_BUS(qdev_get_parent_bus(DEVICE(dev)));
memory_region_add_subregion(b->apic_address_space, 0, &s->io_memory);
mmio_registered = true;
}
/* 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)
{
ICCDeviceClass *idc = ICC_DEVICE_CLASS(klass);
DeviceClass *dc = DEVICE_CLASS(klass);
dc->vmsd = &vmstate_apic_common;
dc->reset = apic_reset_common;
dc->cannot_instantiate_with_device_add_yet = true; /* FIXME explain why */
dc->props = apic_properties_common;
idc->init = apic_init_common;
}
static const TypeInfo apic_common_type = {
.name = TYPE_APIC_COMMON,
.parent = TYPE_ICC_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)