/*
* Copyright (C) 2001 MandrakeSoft S.A.
*
* MandrakeSoft S.A.
* 43, rue d'Aboukir
* 75002 Paris - France
* http://www.linux-mandrake.com/
* http://www.mandrakesoft.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 .
*
* Yunhong Jiang
* Ported to xen by using virtual IRQ line.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* HACK: Route IRQ0 only to VCPU0 to prevent time jumps. */
#define IRQ0_SPECIAL_ROUTING 1
static void vioapic_deliver(struct hvm_vioapic *vioapic, unsigned int irq);
static struct hvm_vioapic *addr_vioapic(const struct domain *d,
unsigned long addr)
{
unsigned int i;
for ( i = 0; i < d->arch.hvm.nr_vioapics; i++ )
{
struct hvm_vioapic *vioapic = domain_vioapic(d, i);
if ( addr >= vioapic->base_address &&
addr < vioapic->base_address + VIOAPIC_MEM_LENGTH )
return vioapic;
}
return NULL;
}
static struct hvm_vioapic *gsi_vioapic(const struct domain *d,
unsigned int gsi, unsigned int *pin)
{
unsigned int i;
/*
* Make sure the compiler does not optimize away the initialization done by
* callers
*/
OPTIMIZER_HIDE_VAR(*pin);
for ( i = 0; i < d->arch.hvm.nr_vioapics; i++ )
{
struct hvm_vioapic *vioapic = domain_vioapic(d, i);
if ( gsi >= vioapic->base_gsi &&
gsi < vioapic->base_gsi + vioapic->nr_pins )
{
*pin = gsi - vioapic->base_gsi;
return vioapic;
}
}
return NULL;
}
static uint32_t vioapic_read_indirect(const struct hvm_vioapic *vioapic)
{
uint32_t result = 0;
switch ( vioapic->ioregsel )
{
case VIOAPIC_REG_VERSION:
result = ((union IO_APIC_reg_01){
.bits = { .version = VIOAPIC_VERSION_ID,
.entries = vioapic->nr_pins - 1 }
}).raw;
break;
case VIOAPIC_REG_APIC_ID:
/*
* Using union IO_APIC_reg_02 for the ID register too, as
* union IO_APIC_reg_00's ID field is 8 bits wide for some reason.
*/
case VIOAPIC_REG_ARB_ID:
result = ((union IO_APIC_reg_02){
.bits = { .arbitration = vioapic->id }
}).raw;
break;
default:
{
uint32_t redir_index = (vioapic->ioregsel - VIOAPIC_REG_RTE0) >> 1;
uint64_t redir_content;
if ( redir_index >= vioapic->nr_pins )
{
gdprintk(XENLOG_WARNING, "apic_mem_readl:undefined ioregsel %x\n",
vioapic->ioregsel);
break;
}
redir_content = vioapic->redirtbl[array_index_nospec(redir_index,
vioapic->nr_pins)].bits;
result = (vioapic->ioregsel & 1) ? (redir_content >> 32)
: redir_content;
break;
}
}
return result;
}
static int cf_check vioapic_read(
struct vcpu *v, unsigned long addr,
unsigned int length, unsigned long *pval)
{
const struct hvm_vioapic *vioapic;
uint32_t result;
HVM_DBG_LOG(DBG_LEVEL_IOAPIC, "addr %lx", addr);
vioapic = addr_vioapic(v->domain, addr);
ASSERT(vioapic);
switch ( addr & 0xff )
{
case VIOAPIC_REG_SELECT:
result = vioapic->ioregsel;
break;
case VIOAPIC_REG_WINDOW:
result = vioapic_read_indirect(vioapic);
break;
default:
result = 0;
break;
}
*pval = result;
return X86EMUL_OKAY;
}
static int vioapic_hwdom_map_gsi(unsigned int gsi, unsigned int trig,
unsigned int pol)
{
struct domain *currd = current->domain;
struct xen_domctl_bind_pt_irq pt_irq_bind = {
.irq_type = PT_IRQ_TYPE_PCI,
.machine_irq = gsi,
};
int ret, pirq = gsi;
ASSERT(is_hardware_domain(currd));
/* Interrupt has been unmasked, bind it now. */
ret = mp_register_gsi(gsi, trig, pol);
if ( ret == -EEXIST )
return 0;
if ( ret )
{
gprintk(XENLOG_WARNING, "vioapic: error registering GSI %u: %d\n",
gsi, ret);
return ret;
}
ret = allocate_and_map_gsi_pirq(currd, pirq, &pirq);
if ( ret )
{
gprintk(XENLOG_WARNING, "vioapic: error mapping GSI %u: %d\n",
gsi, ret);
return ret;
}
pcidevs_lock();
ret = pt_irq_create_bind(currd, &pt_irq_bind);
if ( ret )
{
gprintk(XENLOG_WARNING, "vioapic: error binding GSI %u: %d\n",
gsi, ret);
write_lock(&currd->event_lock);
unmap_domain_pirq(currd, pirq);
write_unlock(&currd->event_lock);
}
pcidevs_unlock();
return ret;
}
static void vioapic_write_redirent(
struct hvm_vioapic *vioapic, unsigned int idx,
int top_word, uint32_t val)
{
struct domain *d = vioapic_domain(vioapic);
struct hvm_irq *hvm_irq = hvm_domain_irq(d);
union vioapic_redir_entry *pent, ent;
bool prev_level;
int unmasked = 0;
unsigned int gsi;
/* Callers of this function should make sure idx is bounded appropriately */
ASSERT(idx < vioapic->nr_pins);
/* Make sure no out-of-bounds value for idx can be used */
idx = array_index_nospec(idx, vioapic->nr_pins);
gsi = vioapic->base_gsi + idx;
spin_lock(&d->arch.hvm.irq_lock);
pent = &vioapic->redirtbl[idx];
ent = *pent;
prev_level = ent.fields.trig_mode == VIOAPIC_LEVEL_TRIG;
if ( top_word )
{
/* Contains only the dest_id. */
ent.bits = (uint32_t)ent.bits | ((uint64_t)val << 32);
}
else
{
unmasked = ent.fields.mask;
/* Remote IRR and Delivery Status are read-only. */
ent.bits = ((ent.bits >> 32) << 32) | val;
ent.fields.delivery_status = 0;
ent.fields.remote_irr = pent->fields.remote_irr;
unmasked = unmasked && !ent.fields.mask;
}
*pent = ent;
if ( gsi == 0 )
{
vlapic_adjust_i8259_target(d);
}
else if ( ent.fields.trig_mode == VIOAPIC_EDGE_TRIG )
pent->fields.remote_irr = 0;
else if ( !ent.fields.mask &&
!ent.fields.remote_irr &&
hvm_irq->gsi_assert_count[gsi] )
{
/* A top word write should never trigger an interrupt injection. */
ASSERT(!top_word);
pent->fields.remote_irr = 1;
vioapic_deliver(vioapic, idx);
}
spin_unlock(&d->arch.hvm.irq_lock);
if ( ent.fields.trig_mode == VIOAPIC_EDGE_TRIG &&
ent.fields.remote_irr && is_iommu_enabled(d) )
{
/*
* Since IRR has been cleared and further interrupts can be
* injected also attempt to deassert any virtual line of passed
* through devices using this pin. Switching a pin from level to
* edge trigger mode can be used as a way to EOI an interrupt at
* the IO-APIC level.
*/
ASSERT(prev_level);
ASSERT(!top_word);
hvm_dpci_eoi(d, gsi);
}
if ( is_hardware_domain(d) && unmasked )
{
/*
* NB: don't call vioapic_hwdom_map_gsi while holding hvm.irq_lock
* since it can cause deadlocks as event_lock is taken by
* allocate_and_map_gsi_pirq, and that will invert the locking order
* used by other parts of the code.
*/
int ret = vioapic_hwdom_map_gsi(gsi, ent.fields.trig_mode,
ent.fields.polarity);
if ( ret )
{
gprintk(XENLOG_ERR,
"unable to bind gsi %u to hardware domain: %d\n", gsi, ret);
unmasked = 0;
}
}
if ( gsi == 0 || unmasked )
pt_may_unmask_irq(d, NULL);
}
static void vioapic_write_indirect(
struct hvm_vioapic *vioapic, uint32_t val)
{
switch ( vioapic->ioregsel )
{
case VIOAPIC_REG_VERSION:
/* Writes are ignored. */
break;
case VIOAPIC_REG_APIC_ID:
/*
* Presumably because we emulate an Intel IOAPIC which only has a
* 4 bit ID field (compared to 8 for AMD), using union IO_APIC_reg_02
* for the ID register (union IO_APIC_reg_00's ID field is 8 bits).
*/
vioapic->id = ((union IO_APIC_reg_02){ .raw = val }).bits.arbitration;
break;
case VIOAPIC_REG_ARB_ID:
break;
default:
{
uint32_t redir_index = (vioapic->ioregsel - VIOAPIC_REG_RTE0) >> 1;
HVM_DBG_LOG(DBG_LEVEL_IOAPIC, "rte[%02x].%s = %08x",
redir_index, vioapic->ioregsel & 1 ? "hi" : "lo", val);
if ( redir_index >= vioapic->nr_pins )
{
gdprintk(XENLOG_WARNING, "vioapic_write_indirect "
"error register %x\n", vioapic->ioregsel);
break;
}
vioapic_write_redirent(
vioapic, redir_index, vioapic->ioregsel&1, val);
break;
}
}
}
static int cf_check vioapic_write(
struct vcpu *v, unsigned long addr,
unsigned int length, unsigned long val)
{
struct hvm_vioapic *vioapic;
vioapic = addr_vioapic(v->domain, addr);
ASSERT(vioapic);
switch ( addr & 0xff )
{
case VIOAPIC_REG_SELECT:
vioapic->ioregsel = val;
break;
case VIOAPIC_REG_WINDOW:
vioapic_write_indirect(vioapic, val);
break;
#if VIOAPIC_VERSION_ID >= 0x20
case VIOAPIC_REG_EOI:
vioapic_update_EOI(v->domain, val);
break;
#endif
default:
break;
}
return X86EMUL_OKAY;
}
static int cf_check vioapic_range(struct vcpu *v, unsigned long addr)
{
return !!addr_vioapic(v->domain, addr);
}
static const struct hvm_mmio_ops vioapic_mmio_ops = {
.check = vioapic_range,
.read = vioapic_read,
.write = vioapic_write
};
static void ioapic_inj_irq(
struct hvm_vioapic *vioapic,
struct vlapic *target,
uint8_t vector,
uint8_t trig_mode,
uint8_t delivery_mode)
{
HVM_DBG_LOG(DBG_LEVEL_IOAPIC, "irq %d trig %d deliv %d",
vector, trig_mode, delivery_mode);
ASSERT((delivery_mode == dest_Fixed) ||
(delivery_mode == dest_LowestPrio));
vlapic_set_irq(target, vector, trig_mode);
}
static void vioapic_deliver(struct hvm_vioapic *vioapic, unsigned int pin)
{
uint16_t dest = vioapic->redirtbl[pin].fields.dest_id;
uint8_t dest_mode = vioapic->redirtbl[pin].fields.dest_mode;
uint8_t delivery_mode = vioapic->redirtbl[pin].fields.delivery_mode;
uint8_t vector = vioapic->redirtbl[pin].fields.vector;
uint8_t trig_mode = vioapic->redirtbl[pin].fields.trig_mode;
struct domain *d = vioapic_domain(vioapic);
struct vlapic *target;
struct vcpu *v;
unsigned int irq = vioapic->base_gsi + pin;
ASSERT(spin_is_locked(&d->arch.hvm.irq_lock));
HVM_DBG_LOG(DBG_LEVEL_IOAPIC,
"dest=%x dest_mode=%x delivery_mode=%x "
"vector=%x trig_mode=%x",
dest, dest_mode, delivery_mode, vector, trig_mode);
switch ( delivery_mode )
{
case dest_LowestPrio:
{
#ifdef IRQ0_SPECIAL_ROUTING
struct vlapic *lapic0 = vcpu_vlapic(d->vcpu[0]);
/* Force to pick vCPU 0 if part of the destination list */
if ( (irq == hvm_isa_irq_to_gsi(0)) && pt_active(&d->arch.vpit.pt0) &&
vlapic_match_dest(lapic0, NULL, 0, dest, dest_mode) &&
/* Mimic the vlapic_enabled check found in vlapic_lowest_prio. */
vlapic_enabled(lapic0) )
target = lapic0;
else
#endif
target = vlapic_lowest_prio(d, NULL, 0, dest, dest_mode);
if ( target != NULL )
{
ioapic_inj_irq(vioapic, target, vector, trig_mode, delivery_mode);
}
else
{
HVM_DBG_LOG(DBG_LEVEL_IOAPIC, "null round robin: "
"vector=%x delivery_mode=%x",
vector, dest_LowestPrio);
}
break;
}
case dest_Fixed:
for_each_vcpu ( d, v )
{
struct vlapic *vlapic = vcpu_vlapic(v);
if ( vlapic_enabled(vlapic) &&
vlapic_match_dest(vlapic, NULL, 0, dest, dest_mode) )
ioapic_inj_irq(vioapic, vlapic, vector, trig_mode,
delivery_mode);
}
break;
case dest_NMI:
{
for_each_vcpu ( d, v )
if ( vlapic_match_dest(vcpu_vlapic(v), NULL,
0, dest, dest_mode) &&
!test_and_set_bool(v->arch.nmi_pending) )
vcpu_kick(v);
break;
}
default:
gdprintk(XENLOG_WARNING, "Unsupported delivery mode %d\n",
delivery_mode);
break;
}
}
void vioapic_irq_positive_edge(struct domain *d, unsigned int irq)
{
unsigned int pin = 0; /* See gsi_vioapic */
struct hvm_vioapic *vioapic = gsi_vioapic(d, irq, &pin);
union vioapic_redir_entry *ent;
if ( !vioapic )
{
ASSERT_UNREACHABLE();
return;
}
HVM_DBG_LOG(DBG_LEVEL_IOAPIC, "irq %x", irq);
ASSERT(pin < vioapic->nr_pins);
ASSERT(spin_is_locked(&d->arch.hvm.irq_lock));
ent = &vioapic->redirtbl[pin];
if ( ent->fields.mask )
return;
if ( ent->fields.trig_mode == VIOAPIC_EDGE_TRIG )
{
vioapic_deliver(vioapic, pin);
}
else if ( !ent->fields.remote_irr )
{
ent->fields.remote_irr = 1;
vioapic_deliver(vioapic, pin);
}
}
void vioapic_update_EOI(struct domain *d, u8 vector)
{
struct hvm_irq *hvm_irq = hvm_domain_irq(d);
union vioapic_redir_entry *ent;
unsigned int i;
ASSERT(has_vioapic(d));
spin_lock(&d->arch.hvm.irq_lock);
for ( i = 0; i < d->arch.hvm.nr_vioapics; i++ )
{
struct hvm_vioapic *vioapic = domain_vioapic(d, i);
unsigned int pin;
for ( pin = 0; pin < vioapic->nr_pins; pin++ )
{
ent = &vioapic->redirtbl[pin];
if ( ent->fields.vector != vector )
continue;
ent->fields.remote_irr = 0;
if ( is_iommu_enabled(d) )
{
spin_unlock(&d->arch.hvm.irq_lock);
hvm_dpci_eoi(d, vioapic->base_gsi + pin);
spin_lock(&d->arch.hvm.irq_lock);
}
if ( (ent->fields.trig_mode == VIOAPIC_LEVEL_TRIG) &&
!ent->fields.mask && !ent->fields.remote_irr &&
hvm_irq->gsi_assert_count[vioapic->base_gsi + pin] )
{
ent->fields.remote_irr = 1;
vioapic_deliver(vioapic, pin);
}
}
}
spin_unlock(&d->arch.hvm.irq_lock);
}
int vioapic_get_mask(const struct domain *d, unsigned int gsi)
{
unsigned int pin = 0; /* See gsi_vioapic */
const struct hvm_vioapic *vioapic = gsi_vioapic(d, gsi, &pin);
if ( !vioapic )
return -EINVAL;
return vioapic->redirtbl[pin].fields.mask;
}
int cf_check vioapic_get_vector(const struct domain *d, unsigned int gsi)
{
unsigned int pin = 0; /* See gsi_vioapic */
const struct hvm_vioapic *vioapic = gsi_vioapic(d, gsi, &pin);
if ( !vioapic )
return -EINVAL;
return vioapic->redirtbl[pin].fields.vector;
}
int vioapic_get_trigger_mode(const struct domain *d, unsigned int gsi)
{
unsigned int pin = 0; /* See gsi_vioapic */
const struct hvm_vioapic *vioapic = gsi_vioapic(d, gsi, &pin);
if ( !vioapic )
return -EINVAL;
return vioapic->redirtbl[pin].fields.trig_mode;
}
static int cf_check ioapic_save(struct vcpu *v, hvm_domain_context_t *h)
{
const struct domain *d = v->domain;
struct hvm_vioapic *s;
if ( !has_vioapic(d) )
return 0;
s = domain_vioapic(d, 0);
if ( s->nr_pins != ARRAY_SIZE(s->domU.redirtbl) ||
d->arch.hvm.nr_vioapics != 1 )
return -EOPNOTSUPP;
return hvm_save_entry(IOAPIC, 0, h, &s->domU);
}
static int cf_check ioapic_load(struct domain *d, hvm_domain_context_t *h)
{
struct hvm_vioapic *s;
if ( !has_vioapic(d) )
return -ENODEV;
s = domain_vioapic(d, 0);
if ( s->nr_pins != ARRAY_SIZE(s->domU.redirtbl) ||
d->arch.hvm.nr_vioapics != 1 )
return -EOPNOTSUPP;
if ( hvm_load_entry(IOAPIC, h, &s->domU) )
return -ENODATA;
return 0;
}
HVM_REGISTER_SAVE_RESTORE(IOAPIC, ioapic_save, ioapic_load, 1, HVMSR_PER_DOM);
void vioapic_reset(struct domain *d)
{
unsigned int i;
if ( !has_vioapic(d) )
{
ASSERT(!d->arch.hvm.nr_vioapics);
return;
}
for ( i = 0; i < d->arch.hvm.nr_vioapics; i++ )
{
struct hvm_vioapic *vioapic = domain_vioapic(d, i);
unsigned int nr_pins = vioapic->nr_pins, base_gsi = vioapic->base_gsi;
unsigned int pin;
memset(vioapic, 0, offsetof(typeof(*vioapic), redirtbl));
for ( pin = 0; pin < nr_pins; pin++ )
vioapic->redirtbl[pin] = (union vioapic_redir_entry){ .fields.mask = 1 };
if ( !is_hardware_domain(d) )
{
ASSERT(!i && !base_gsi);
vioapic->base_address = VIOAPIC_DEFAULT_BASE_ADDRESS;
vioapic->id = 0;
}
else
{
vioapic->base_address = mp_ioapics[i].mpc_apicaddr;
vioapic->id = mp_ioapics[i].mpc_apicid;
}
vioapic->base_gsi = base_gsi;
vioapic->nr_pins = nr_pins;
vioapic->domain = d;
}
}
static void vioapic_free(const struct domain *d, unsigned int nr_vioapics)
{
unsigned int i;
for ( i = 0; i < nr_vioapics; i++)
xfree(domain_vioapic(d, i));
xfree(d->arch.hvm.vioapic);
}
int vioapic_init(struct domain *d)
{
unsigned int i, nr_vioapics, nr_gsis = 0;
if ( !has_vioapic(d) )
{
ASSERT(!d->arch.hvm.nr_vioapics);
return 0;
}
nr_vioapics = is_hardware_domain(d) ? nr_ioapics : 1;
if ( (d->arch.hvm.vioapic == NULL) &&
((d->arch.hvm.vioapic =
xzalloc_array(struct hvm_vioapic *, nr_vioapics)) == NULL) )
return -ENOMEM;
for ( i = 0; i < nr_vioapics; i++ )
{
unsigned int nr_pins, base_gsi;
if ( is_hardware_domain(d) )
{
nr_pins = nr_ioapic_entries[i];
base_gsi = io_apic_gsi_base(i);
}
else
{
nr_pins = ARRAY_SIZE(domain_vioapic(d, 0)->domU.redirtbl);
base_gsi = 0;
}
if ( (domain_vioapic(d, i) =
xmalloc_flex_struct(struct hvm_vioapic, redirtbl,
nr_pins)) == NULL )
{
vioapic_free(d, nr_vioapics);
return -ENOMEM;
}
domain_vioapic(d, i)->nr_pins = nr_pins;
domain_vioapic(d, i)->base_gsi = base_gsi;
nr_gsis = max(nr_gsis, base_gsi + nr_pins);
}
/*
* NB: hvm_domain_irq(d)->nr_gsis is actually the highest GSI + 1, but
* there might be holes in this range (ie: GSIs that don't belong to any
* vIO APIC).
*/
ASSERT(hvm_domain_irq(d)->nr_gsis >= nr_gsis);
d->arch.hvm.nr_vioapics = nr_vioapics;
vioapic_reset(d);
register_mmio_handler(d, &vioapic_mmio_ops);
return 0;
}
void vioapic_deinit(struct domain *d)
{
if ( !has_vioapic(d) )
{
ASSERT(!d->arch.hvm.nr_vioapics);
return;
}
vioapic_free(d, d->arch.hvm.nr_vioapics);
}