#include <xen/errno.h>
#include <xen/acpi.h>
#include <asm/apicdef.h>
+#include <asm/io_apic.h>
#include <asm/amd-iommu.h>
#include <asm/hvm/svm/amd-iommu-proto.h>
u16 header_length, u16 block_length, struct amd_iommu *iommu)
{
u16 dev_length, bdf;
+ int apic;
dev_length = sizeof(*special);
if ( header_length < (block_length + dev_length) )
switch ( special->variety )
{
case ACPI_IVHD_IOAPIC:
- /* set device id of ioapic */
- ioapic_sbdf[special->handle].bdf = bdf;
- ioapic_sbdf[special->handle].seg = seg;
+ /*
+ * Some BIOSes have IOAPIC broken entries so we check for IVRS
+ * consistency here --- whether entry's IOAPIC ID is valid and
+ * whether there are conflicting/duplicated entries.
+ */
+ for ( apic = 0; apic < nr_ioapics; apic++ )
+ {
+ if ( IO_APIC_ID(apic) != special->handle )
+ continue;
+
+ if ( ioapic_sbdf[special->handle].pin_setup )
+ {
+ if ( ioapic_sbdf[special->handle].bdf == bdf &&
+ ioapic_sbdf[special->handle].seg == seg )
+ AMD_IOMMU_DEBUG("IVHD Warning: Duplicate IO-APIC %#x entries\n",
+ special->handle);
+ else
+ {
+ printk(XENLOG_ERR "IVHD Error: Conflicting IO-APIC %#x entries\n",
+ special->handle);
+ if ( amd_iommu_perdev_intremap )
+ return 0;
+ }
+ }
+ else
+ {
+ /* set device id of ioapic */
+ ioapic_sbdf[special->handle].bdf = bdf;
+ ioapic_sbdf[special->handle].seg = seg;
+
+ ioapic_sbdf[special->handle].pin_setup = xzalloc_array(
+ unsigned long, BITS_TO_LONGS(nr_ioapic_entries[apic]));
+ if ( nr_ioapic_entries[apic] &&
+ !ioapic_sbdf[IO_APIC_ID(apic)].pin_setup )
+ {
+ printk(XENLOG_ERR "IVHD Error: Out of memory\n");
+ return 0;
+ }
+ }
+ break;
+ }
+ if ( apic == nr_ioapics )
+ {
+ printk(XENLOG_ERR "IVHD Error: Invalid IO-APIC %#x\n",
+ special->handle);
+ return 0;
+ }
break;
case ACPI_IVHD_HPET:
/* set device id of hpet */
static void update_intremap_entry_from_ioapic(
int bdf,
struct amd_iommu *iommu,
- struct IO_APIC_route_entry *ioapic_rte)
+ const struct IO_APIC_route_entry *rte,
+ const struct IO_APIC_route_entry *old_rte)
{
unsigned long flags;
u32* entry;
u8 delivery_mode, dest, vector, dest_mode;
- struct IO_APIC_route_entry *rte = ioapic_rte;
int req_id;
spinlock_t *lock;
int offset;
spin_lock_irqsave(lock, flags);
offset = get_intremap_offset(vector, delivery_mode);
+ if ( old_rte )
+ {
+ int old_offset = get_intremap_offset(old_rte->vector,
+ old_rte->delivery_mode);
+
+ if ( offset != old_offset )
+ free_intremap_entry(iommu->seg, bdf, old_offset);
+ }
entry = (u32*)get_intremap_entry(iommu->seg, req_id, offset);
update_intremap_entry(entry, vector, delivery_mode, dest_mode, dest);
amd_iommu_flush_intremap(iommu, req_id);
spin_unlock_irqrestore(&iommu->lock, flags);
}
+ set_bit(pin, ioapic_sbdf[IO_APIC_ID(apic)].pin_setup);
}
}
return 0;
struct IO_APIC_route_entry old_rte = { 0 };
struct IO_APIC_route_entry new_rte = { 0 };
unsigned int rte_lo = (reg & 1) ? reg - 1 : reg;
+ unsigned int pin = (reg - 0x10) / 2;
int saved_mask, seg, bdf;
struct amd_iommu *iommu;
*(((u32 *)&new_rte) + 1) = value;
}
+ if ( new_rte.mask &&
+ !test_bit(pin, ioapic_sbdf[IO_APIC_ID(apic)].pin_setup) )
+ {
+ ASSERT(saved_mask);
+ __io_apic_write(apic, reg, value);
+ return;
+ }
+
/* mask the interrupt while we change the intremap table */
if ( !saved_mask )
{
}
/* Update interrupt remapping entry */
- update_intremap_entry_from_ioapic(bdf, iommu, &new_rte);
+ update_intremap_entry_from_ioapic(
+ bdf, iommu, &new_rte,
+ test_and_set_bit(pin,
+ ioapic_sbdf[IO_APIC_ID(apic)].pin_setup) ? &old_rte
+ : NULL);
/* Forward write access to IO-APIC RTE */
__io_apic_write(apic, reg, value);
return;
}
+ if ( msi_desc->remap_index >= 0 )
+ update_intremap_entry_from_msi_msg(iommu, bdf, msi_desc, NULL);
+
+ if ( !msg )
+ return;
+
update_intremap_entry_from_msi_msg(iommu, bdf, msi_desc, msg);
}