Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
2838 lines
91 KiB
C
2838 lines
91 KiB
C
/*
|
||
* QEMU emulation of AMD IOMMU (AMD-Vi)
|
||
*
|
||
* Copyright (C) 2011 Eduard - Gabriel Munteanu
|
||
* Copyright (C) 2015, 2016 David Kiarie Kahurani
|
||
*
|
||
* 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/>.
|
||
*
|
||
* Cache implementation inspired by hw/i386/intel_iommu.c
|
||
*/
|
||
|
||
#include "qemu/osdep.h"
|
||
#include "hw/i386/pc.h"
|
||
#include "hw/pci/msi.h"
|
||
#include "hw/pci/pci_bus.h"
|
||
#include "migration/vmstate.h"
|
||
#include "amd_iommu.h"
|
||
#include "qapi/error.h"
|
||
#include "qemu/error-report.h"
|
||
#include "hw/i386/apic_internal.h"
|
||
#include "trace.h"
|
||
#include "hw/i386/apic-msidef.h"
|
||
#include "hw/core/qdev-properties.h"
|
||
#include "kvm/kvm_i386.h"
|
||
#include "qemu/iova-tree.h"
|
||
#include "hw/core/registerfields.h"
|
||
|
||
struct AMDVIAddressSpace {
|
||
PCIBus *bus; /* PCIBus (for bus number) */
|
||
uint8_t devfn; /* device function */
|
||
AMDVIState *iommu_state; /* AMDVI - one per machine */
|
||
MemoryRegion root; /* AMDVI Root memory map region */
|
||
IOMMUMemoryRegion iommu; /* Device's address translation region */
|
||
MemoryRegion iommu_nodma; /* Alias of shared nodma memory region */
|
||
MemoryRegion iommu_ir; /* Device's interrupt remapping region */
|
||
AddressSpace as; /* device's corresponding address space */
|
||
|
||
/* DMA address translation support */
|
||
IOMMUNotifierFlag notifier_flags;
|
||
/* entry in list of Address spaces with registered notifiers */
|
||
QLIST_ENTRY(AMDVIAddressSpace) next;
|
||
/* Record DMA translation ranges */
|
||
IOVATree *iova_tree;
|
||
/* DMA address translation active */
|
||
bool addr_translation;
|
||
};
|
||
|
||
/* AMDVI cache entry */
|
||
typedef struct AMDVIIOTLBEntry {
|
||
uint16_t domid; /* assigned domain id */
|
||
uint16_t devid; /* device owning entry */
|
||
uint64_t perms; /* access permissions */
|
||
uint64_t translated_addr; /* translated address */
|
||
uint64_t page_mask; /* physical page size */
|
||
} AMDVIIOTLBEntry;
|
||
|
||
/*
|
||
* These 'fault' reasons have an overloaded meaning since they are not only
|
||
* intended for describing reasons that generate an IO_PAGE_FAULT as per the AMD
|
||
* IOMMU specification, but are also used to signal internal errors in the
|
||
* emulation code.
|
||
*/
|
||
typedef enum AMDVIFaultReason {
|
||
AMDVI_FR_DTE_RTR_ERR = 1, /* Failure to retrieve DTE */
|
||
AMDVI_FR_DTE_V, /* DTE[V] = 0 */
|
||
AMDVI_FR_DTE_TV, /* DTE[TV] = 0 */
|
||
AMDVI_FR_PT_ROOT_INV, /* Page Table Root ptr invalid */
|
||
AMDVI_FR_PT_ENTRY_INV, /* Failure to read PTE from guest memory */
|
||
} AMDVIFaultReason;
|
||
|
||
typedef struct AMDVIAsKey {
|
||
PCIBus *bus;
|
||
uint8_t devfn;
|
||
} AMDVIAsKey;
|
||
|
||
typedef struct AMDVIIOTLBKey {
|
||
uint64_t gfn;
|
||
uint16_t devid;
|
||
} AMDVIIOTLBKey;
|
||
|
||
typedef struct AMDVIIrteGA {
|
||
uint64_t ga_lo;
|
||
uint64_t ga_hi;
|
||
} AMDVIIrteGA;
|
||
|
||
/* XT IOMMU General Interrupt Control Register layout */
|
||
FIELD(AMDVI_XT_GEN_INTR, DEST_MODE, 2, 1)
|
||
FIELD(AMDVI_XT_GEN_INTR, DEST_LO, 8, 24)
|
||
FIELD(AMDVI_XT_GEN_INTR, VECTOR, 32, 8)
|
||
FIELD(AMDVI_XT_GEN_INTR, DELIVERY_MODE, 40, 1)
|
||
FIELD(AMDVI_XT_GEN_INTR, DEST_HI, 56, 8)
|
||
|
||
/* Interrupt Remapping Table Fields Formats */
|
||
|
||
/* Basic 32-bit IRTE layout (GAEn=0) */
|
||
FIELD(AMDVI_IRTE, VALID, 0, 1)
|
||
FIELD(AMDVI_IRTE, SUP_IOPF, 1, 1)
|
||
FIELD(AMDVI_IRTE, INT_TYPE, 2, 3)
|
||
FIELD(AMDVI_IRTE, RQ_EOI, 5, 1)
|
||
FIELD(AMDVI_IRTE, DM, 6, 1)
|
||
FIELD(AMDVI_IRTE, GUEST_MODE, 7, 1)
|
||
FIELD(AMDVI_IRTE, DESTINATION, 8, 8)
|
||
FIELD(AMDVI_IRTE, VECTOR, 16, 8)
|
||
|
||
/* 128-bit IRTE layout (GAEn=1) */
|
||
FIELD(AMDVI_IRTE_GA_LO, VALID, 0, 1)
|
||
FIELD(AMDVI_IRTE_GA_LO, SUP_IOPF, 1, 1)
|
||
FIELD(AMDVI_IRTE_GA_LO, INT_TYPE, 2, 3)
|
||
FIELD(AMDVI_IRTE_GA_LO, RQ_EOI, 5, 1)
|
||
FIELD(AMDVI_IRTE_GA_LO, DM, 6, 1)
|
||
FIELD(AMDVI_IRTE_GA_LO, GUEST_MODE, 7, 1)
|
||
/*
|
||
* In the 128-bit IRTE format, XT mode uses IRTE_GA_LOW.Destination[23:0]
|
||
* together with IRTE_GA_HI.DestinationHi[7:0] to construct a 32-bit x2APIC
|
||
* destination.
|
||
* Without XTEn (i.e. when x2APIC support is not enabled), only
|
||
* IRTE_GA_LOW.Destination[7:0] is used.
|
||
*/
|
||
FIELD(AMDVI_IRTE_GA_LO, DESTINATION, 8, 24)
|
||
|
||
FIELD(AMDVI_IRTE_GA_HI, VECTOR, 0, 8)
|
||
FIELD(AMDVI_IRTE_GA_HI, DESTINATION_HI, 56, 8)
|
||
|
||
uint64_t amdvi_extended_feature_register(AMDVIState *s)
|
||
{
|
||
uint64_t feature = AMDVI_DEFAULT_EXT_FEATURES;
|
||
if (s->xtsup) {
|
||
feature |= AMDVI_FEATURE_XT;
|
||
}
|
||
if (!s->iommu.dma_translation) {
|
||
feature |= AMDVI_HATS_MODE_RESERVED;
|
||
}
|
||
|
||
return feature;
|
||
}
|
||
|
||
/* configure MMIO registers at startup/reset */
|
||
static void amdvi_set_quad(AMDVIState *s, hwaddr addr, uint64_t val,
|
||
uint64_t romask, uint64_t w1cmask)
|
||
{
|
||
stq_le_p(&s->mmior[addr], val);
|
||
stq_le_p(&s->romask[addr], romask);
|
||
stq_le_p(&s->w1cmask[addr], w1cmask);
|
||
}
|
||
|
||
static uint16_t amdvi_readw(AMDVIState *s, hwaddr addr)
|
||
{
|
||
return lduw_le_p(&s->mmior[addr]);
|
||
}
|
||
|
||
static uint32_t amdvi_readl(AMDVIState *s, hwaddr addr)
|
||
{
|
||
return ldl_le_p(&s->mmior[addr]);
|
||
}
|
||
|
||
static uint64_t amdvi_readq(AMDVIState *s, hwaddr addr)
|
||
{
|
||
return ldq_le_p(&s->mmior[addr]);
|
||
}
|
||
|
||
/* internal write */
|
||
static void amdvi_writeq_raw(AMDVIState *s, hwaddr addr, uint64_t val)
|
||
{
|
||
stq_le_p(&s->mmior[addr], val);
|
||
}
|
||
|
||
/* external write */
|
||
static void amdvi_writew(AMDVIState *s, hwaddr addr, uint16_t val)
|
||
{
|
||
uint16_t romask = lduw_le_p(&s->romask[addr]);
|
||
uint16_t w1cmask = lduw_le_p(&s->w1cmask[addr]);
|
||
uint16_t oldval = lduw_le_p(&s->mmior[addr]);
|
||
|
||
uint16_t oldval_preserved = oldval & (romask | w1cmask);
|
||
uint16_t newval_write = val & ~romask;
|
||
uint16_t newval_w1c_set = val & w1cmask;
|
||
|
||
stw_le_p(&s->mmior[addr],
|
||
(oldval_preserved | newval_write) & ~newval_w1c_set);
|
||
}
|
||
|
||
static void amdvi_writel(AMDVIState *s, hwaddr addr, uint32_t val)
|
||
{
|
||
uint32_t romask = ldl_le_p(&s->romask[addr]);
|
||
uint32_t w1cmask = ldl_le_p(&s->w1cmask[addr]);
|
||
uint32_t oldval = ldl_le_p(&s->mmior[addr]);
|
||
|
||
uint32_t oldval_preserved = oldval & (romask | w1cmask);
|
||
uint32_t newval_write = val & ~romask;
|
||
uint32_t newval_w1c_set = val & w1cmask;
|
||
|
||
stl_le_p(&s->mmior[addr],
|
||
(oldval_preserved | newval_write) & ~newval_w1c_set);
|
||
}
|
||
|
||
static void amdvi_writeq(AMDVIState *s, hwaddr addr, uint64_t val)
|
||
{
|
||
uint64_t romask = ldq_le_p(&s->romask[addr]);
|
||
uint64_t w1cmask = ldq_le_p(&s->w1cmask[addr]);
|
||
uint64_t oldval = ldq_le_p(&s->mmior[addr]);
|
||
|
||
uint64_t oldval_preserved = oldval & (romask | w1cmask);
|
||
uint64_t newval_write = val & ~romask;
|
||
uint64_t newval_w1c_set = val & w1cmask;
|
||
|
||
stq_le_p(&s->mmior[addr],
|
||
(oldval_preserved | newval_write) & ~newval_w1c_set);
|
||
}
|
||
|
||
/* AND a 64-bit register with a 64-bit value */
|
||
static bool amdvi_test_mask(AMDVIState *s, hwaddr addr, uint64_t val)
|
||
{
|
||
return amdvi_readq(s, addr) & val;
|
||
}
|
||
|
||
/* OR a 64-bit register with a 64-bit value storing result in the register */
|
||
static void amdvi_assign_orq(AMDVIState *s, hwaddr addr, uint64_t val)
|
||
{
|
||
amdvi_writeq_raw(s, addr, amdvi_readq(s, addr) | val);
|
||
}
|
||
|
||
/* AND a 64-bit register with a 64-bit value storing result in the register */
|
||
static void amdvi_assign_andq(AMDVIState *s, hwaddr addr, uint64_t val)
|
||
{
|
||
amdvi_writeq_raw(s, addr, amdvi_readq(s, addr) & val);
|
||
}
|
||
|
||
static void amdvi_build_xt_msi_msg(AMDVIState *s, MSIMessage *msg)
|
||
{
|
||
uint64_t xt_reg = amdvi_readq(s, AMDVI_MMIO_XT_GEN_INTR);
|
||
|
||
X86IOMMUIrq irq = {
|
||
.vector = FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, VECTOR),
|
||
.delivery_mode = FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, DELIVERY_MODE),
|
||
.dest_mode = FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, DEST_MODE),
|
||
.dest = (FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, DEST_HI) << 24) |
|
||
FIELD_EX64(xt_reg, AMDVI_XT_GEN_INTR, DEST_LO),
|
||
.trigger_mode = 0,
|
||
.redir_hint = 0,
|
||
};
|
||
|
||
x86_iommu_irq_to_msi_message(&irq, msg);
|
||
}
|
||
|
||
static void amdvi_generate_msi_interrupt(AMDVIState *s)
|
||
{
|
||
MSIMessage msg = {};
|
||
|
||
if (s->intcapxten) {
|
||
trace_amdvi_generate_msi_interrupt("XT GEN");
|
||
amdvi_build_xt_msi_msg(s, &msg);
|
||
} else if (msi_enabled(&s->pci->dev)) {
|
||
trace_amdvi_generate_msi_interrupt("MSI");
|
||
msg = msi_get_message(&s->pci->dev, 0);
|
||
} else {
|
||
trace_amdvi_generate_msi_interrupt("NO MSI");
|
||
return;
|
||
}
|
||
apic_get_class(NULL)->send_msi(&msg);
|
||
}
|
||
|
||
static uint32_t get_next_eventlog_entry(AMDVIState *s)
|
||
{
|
||
uint32_t evtlog_size = s->evtlog_len * AMDVI_EVENT_LEN;
|
||
return (s->evtlog_tail + AMDVI_EVENT_LEN) % evtlog_size;
|
||
}
|
||
|
||
static void amdvi_log_event(AMDVIState *s, uint64_t *evt)
|
||
{
|
||
uint64_t le_evt[2];
|
||
uint32_t evtlog_tail_next;
|
||
|
||
/* event logging not enabled */
|
||
if (!s->evtlog_enabled || amdvi_test_mask(s, AMDVI_MMIO_STATUS,
|
||
AMDVI_MMIO_STATUS_EVT_OVF)) {
|
||
return;
|
||
}
|
||
|
||
evtlog_tail_next = get_next_eventlog_entry(s);
|
||
|
||
/* event log buffer full */
|
||
if (evtlog_tail_next == s->evtlog_head) {
|
||
/* generate overflow interrupt */
|
||
if (s->evtlog_intr) {
|
||
amdvi_assign_orq(s, AMDVI_MMIO_STATUS, AMDVI_MMIO_STATUS_EVT_OVF);
|
||
amdvi_generate_msi_interrupt(s);
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* Convert event buffer to little-endian before writing it to guest memory.
|
||
*/
|
||
le_evt[0] = cpu_to_le64(evt[0]);
|
||
le_evt[1] = cpu_to_le64(evt[1]);
|
||
|
||
if (dma_memory_write(&address_space_memory, s->evtlog + s->evtlog_tail,
|
||
le_evt, AMDVI_EVENT_LEN, MEMTXATTRS_UNSPECIFIED)) {
|
||
trace_amdvi_evntlog_fail(s->evtlog, s->evtlog_tail);
|
||
}
|
||
|
||
s->evtlog_tail = evtlog_tail_next;
|
||
amdvi_writeq_raw(s, AMDVI_MMIO_EVENT_TAIL, s->evtlog_tail);
|
||
|
||
if (s->evtlog_intr) {
|
||
amdvi_assign_orq(s, AMDVI_MMIO_STATUS, AMDVI_MMIO_STATUS_EVENT_INT);
|
||
amdvi_generate_msi_interrupt(s);
|
||
}
|
||
}
|
||
|
||
static void amdvi_setevent_bits(uint64_t *buffer, uint64_t value, int start,
|
||
int length)
|
||
{
|
||
int index = start / 64, bitpos = start % 64;
|
||
uint64_t mask = MAKE_64BIT_MASK(bitpos, length);
|
||
buffer[index] &= ~mask;
|
||
buffer[index] |= (value << bitpos) & mask;
|
||
}
|
||
/*
|
||
* AMDVi event structure
|
||
* 0:15 -> DeviceID
|
||
* 48:63 -> event type + miscellaneous info
|
||
* 64:127 -> related address
|
||
*/
|
||
static void amdvi_encode_event(uint64_t *evt, uint16_t devid, uint64_t addr,
|
||
uint16_t info)
|
||
{
|
||
evt[0] = 0;
|
||
evt[1] = 0;
|
||
|
||
amdvi_setevent_bits(evt, devid, 0, 16);
|
||
amdvi_setevent_bits(evt, info, 48, 16);
|
||
amdvi_setevent_bits(evt, addr, 64, 64);
|
||
}
|
||
/* log an error encountered during a page walk
|
||
*
|
||
* @addr: virtual address in translation request
|
||
*/
|
||
static void amdvi_page_fault(AMDVIState *s, uint16_t devid,
|
||
hwaddr addr, uint16_t info)
|
||
{
|
||
uint64_t evt[2];
|
||
|
||
info |= AMDVI_EVENT_IOPF_I | AMDVI_EVENT_IOPF;
|
||
amdvi_encode_event(evt, devid, addr, info);
|
||
amdvi_log_event(s, evt);
|
||
pci_word_test_and_set_mask(s->pci->dev.config + PCI_STATUS,
|
||
PCI_STATUS_SIG_TARGET_ABORT);
|
||
}
|
||
/*
|
||
* log a master abort accessing device table
|
||
* @devtab : address of device table entry
|
||
* @info : error flags
|
||
*/
|
||
static void amdvi_log_devtab_error(AMDVIState *s, uint16_t devid,
|
||
hwaddr devtab, uint16_t info)
|
||
{
|
||
uint64_t evt[2];
|
||
|
||
info |= AMDVI_EVENT_DEV_TAB_HW_ERROR;
|
||
|
||
amdvi_encode_event(evt, devid, devtab, info);
|
||
amdvi_log_event(s, evt);
|
||
pci_word_test_and_set_mask(s->pci->dev.config + PCI_STATUS,
|
||
PCI_STATUS_SIG_TARGET_ABORT);
|
||
}
|
||
/* log an event trying to access command buffer
|
||
* @addr : address that couldn't be accessed
|
||
*/
|
||
static void amdvi_log_command_error(AMDVIState *s, hwaddr addr)
|
||
{
|
||
uint64_t evt[2];
|
||
uint16_t info = AMDVI_EVENT_COMMAND_HW_ERROR;
|
||
|
||
amdvi_encode_event(evt, 0, addr, info);
|
||
amdvi_log_event(s, evt);
|
||
pci_word_test_and_set_mask(s->pci->dev.config + PCI_STATUS,
|
||
PCI_STATUS_SIG_TARGET_ABORT);
|
||
}
|
||
/* log an illegal command event
|
||
* @addr : address of illegal command
|
||
*/
|
||
static void amdvi_log_illegalcom_error(AMDVIState *s, uint16_t info,
|
||
hwaddr addr)
|
||
{
|
||
uint64_t evt[2];
|
||
|
||
info |= AMDVI_EVENT_ILLEGAL_COMMAND_ERROR;
|
||
amdvi_encode_event(evt, 0, addr, info);
|
||
amdvi_log_event(s, evt);
|
||
}
|
||
/* log an error accessing device table
|
||
*
|
||
* @devid : device owning the table entry
|
||
* @devtab : address of device table entry
|
||
* @info : error flags
|
||
*/
|
||
static void amdvi_log_illegaldevtab_error(AMDVIState *s, uint16_t devid,
|
||
hwaddr addr, uint16_t info)
|
||
{
|
||
uint64_t evt[2];
|
||
|
||
info |= AMDVI_EVENT_ILLEGAL_DEVTAB_ENTRY;
|
||
amdvi_encode_event(evt, devid, addr, info);
|
||
amdvi_log_event(s, evt);
|
||
}
|
||
/* log an error accessing a PTE entry
|
||
* @addr : address that couldn't be accessed
|
||
*/
|
||
static void amdvi_log_pagetab_error(AMDVIState *s, uint16_t devid,
|
||
hwaddr addr, uint16_t info)
|
||
{
|
||
uint64_t evt[2];
|
||
|
||
info |= AMDVI_EVENT_PAGE_TAB_HW_ERROR;
|
||
amdvi_encode_event(evt, devid, addr, info);
|
||
amdvi_log_event(s, evt);
|
||
pci_word_test_and_set_mask(s->pci->dev.config + PCI_STATUS,
|
||
PCI_STATUS_SIG_TARGET_ABORT);
|
||
}
|
||
|
||
static gboolean amdvi_as_equal(gconstpointer v1, gconstpointer v2)
|
||
{
|
||
const AMDVIAsKey *key1 = v1;
|
||
const AMDVIAsKey *key2 = v2;
|
||
|
||
return key1->bus == key2->bus && key1->devfn == key2->devfn;
|
||
}
|
||
|
||
static guint amdvi_as_hash(gconstpointer v)
|
||
{
|
||
const AMDVIAsKey *key = v;
|
||
guint bus = (guint)(uintptr_t)key->bus;
|
||
|
||
return (guint)(bus << 8 | (guint)key->devfn);
|
||
}
|
||
|
||
static AMDVIAddressSpace *amdvi_as_lookup(AMDVIState *s, PCIBus *bus,
|
||
uint8_t devfn)
|
||
{
|
||
const AMDVIAsKey key = { .bus = bus, .devfn = devfn };
|
||
return g_hash_table_lookup(s->address_spaces, &key);
|
||
}
|
||
|
||
static gboolean amdvi_find_as_by_devid(gpointer key, gpointer value,
|
||
gpointer user_data)
|
||
{
|
||
const AMDVIAsKey *as = key;
|
||
const uint16_t *devidp = user_data;
|
||
|
||
return *devidp == PCI_BUILD_BDF(pci_bus_num(as->bus), as->devfn);
|
||
}
|
||
|
||
static AMDVIAddressSpace *amdvi_get_as_by_devid(AMDVIState *s, uint16_t devid)
|
||
{
|
||
return g_hash_table_find(s->address_spaces,
|
||
amdvi_find_as_by_devid, &devid);
|
||
}
|
||
|
||
static gboolean amdvi_iotlb_equal(gconstpointer v1, gconstpointer v2)
|
||
{
|
||
const AMDVIIOTLBKey *key1 = v1;
|
||
const AMDVIIOTLBKey *key2 = v2;
|
||
|
||
return key1->devid == key2->devid && key1->gfn == key2->gfn;
|
||
}
|
||
|
||
static guint amdvi_iotlb_hash(gconstpointer v)
|
||
{
|
||
const AMDVIIOTLBKey *key = v;
|
||
/* Use GPA and DEVID to find the bucket */
|
||
return (guint)(key->gfn << AMDVI_PAGE_SHIFT_4K |
|
||
(key->devid & ~AMDVI_PAGE_MASK_4K));
|
||
}
|
||
|
||
|
||
static AMDVIIOTLBEntry *amdvi_iotlb_lookup(AMDVIState *s, hwaddr addr,
|
||
uint64_t devid)
|
||
{
|
||
AMDVIIOTLBKey key = {
|
||
.gfn = AMDVI_GET_IOTLB_GFN(addr),
|
||
.devid = devid,
|
||
};
|
||
return g_hash_table_lookup(s->iotlb, &key);
|
||
}
|
||
|
||
static void amdvi_iotlb_reset(AMDVIState *s)
|
||
{
|
||
assert(s->iotlb);
|
||
trace_amdvi_iotlb_reset();
|
||
g_hash_table_remove_all(s->iotlb);
|
||
}
|
||
|
||
static gboolean amdvi_iotlb_remove_by_devid(gpointer key, gpointer value,
|
||
gpointer user_data)
|
||
{
|
||
AMDVIIOTLBEntry *entry = (AMDVIIOTLBEntry *)value;
|
||
uint16_t devid = *(uint16_t *)user_data;
|
||
return entry->devid == devid;
|
||
}
|
||
|
||
static void amdvi_iotlb_remove_page(AMDVIState *s, hwaddr addr,
|
||
uint64_t devid)
|
||
{
|
||
AMDVIIOTLBKey key = {
|
||
.gfn = AMDVI_GET_IOTLB_GFN(addr),
|
||
.devid = devid,
|
||
};
|
||
g_hash_table_remove(s->iotlb, &key);
|
||
}
|
||
|
||
static void amdvi_update_iotlb(AMDVIState *s, uint16_t devid,
|
||
uint64_t gpa, IOMMUTLBEntry to_cache,
|
||
uint16_t domid)
|
||
{
|
||
/* don't cache erroneous translations */
|
||
if (to_cache.perm != IOMMU_NONE) {
|
||
AMDVIIOTLBEntry *entry = g_new(AMDVIIOTLBEntry, 1);
|
||
AMDVIIOTLBKey *key = g_new(AMDVIIOTLBKey, 1);
|
||
|
||
key->gfn = AMDVI_GET_IOTLB_GFN(gpa);
|
||
key->devid = devid;
|
||
|
||
trace_amdvi_cache_update(domid, PCI_BUS_NUM(devid), PCI_SLOT(devid),
|
||
PCI_FUNC(devid), gpa, to_cache.translated_addr);
|
||
|
||
if (g_hash_table_size(s->iotlb) >= AMDVI_IOTLB_MAX_SIZE) {
|
||
amdvi_iotlb_reset(s);
|
||
}
|
||
|
||
entry->domid = domid;
|
||
entry->perms = to_cache.perm;
|
||
entry->translated_addr = to_cache.translated_addr;
|
||
entry->page_mask = to_cache.addr_mask;
|
||
entry->devid = devid;
|
||
|
||
g_hash_table_replace(s->iotlb, key, entry);
|
||
}
|
||
}
|
||
|
||
static void amdvi_completion_wait(AMDVIState *s, uint64_t *cmd)
|
||
{
|
||
/* pad the last 3 bits */
|
||
hwaddr addr = extract64(cmd[0], 3, 49) << 3;
|
||
uint64_t data = cmd[1];
|
||
|
||
/* Format the data to be written to guest memory as little-endian */
|
||
uint64_t le_data = cpu_to_le64(data);
|
||
|
||
if (extract64(cmd[0], 52, 8)) {
|
||
amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4),
|
||
s->cmdbuf + s->cmdbuf_head);
|
||
}
|
||
if (extract64(cmd[0], 0, 1)) {
|
||
if (dma_memory_write(&address_space_memory, addr, &le_data,
|
||
AMDVI_COMPLETION_DATA_SIZE,
|
||
MEMTXATTRS_UNSPECIFIED)) {
|
||
trace_amdvi_completion_wait_fail(addr);
|
||
}
|
||
}
|
||
/* set completion interrupt */
|
||
if (extract64(cmd[0], 1, 1)) {
|
||
amdvi_assign_orq(s, AMDVI_MMIO_STATUS, AMDVI_MMIO_STATUS_COMP_INT);
|
||
/* generate interrupt */
|
||
amdvi_generate_msi_interrupt(s);
|
||
}
|
||
trace_amdvi_completion_wait(addr, data);
|
||
}
|
||
|
||
static inline uint64_t amdvi_get_perms(uint64_t entry)
|
||
{
|
||
return (entry & (AMDVI_DEV_PERM_READ | AMDVI_DEV_PERM_WRITE)) >>
|
||
AMDVI_DEV_PERM_SHIFT;
|
||
}
|
||
|
||
/* validate that reserved bits are honoured */
|
||
static bool amdvi_validate_dte(AMDVIState *s, uint16_t devid,
|
||
uint64_t *dte)
|
||
{
|
||
|
||
uint64_t root;
|
||
|
||
if ((dte[0] & AMDVI_DTE_QUAD0_RESERVED) ||
|
||
(dte[1] & AMDVI_DTE_QUAD1_RESERVED) ||
|
||
(dte[2] & AMDVI_DTE_QUAD2_RESERVED) ||
|
||
(dte[3] & AMDVI_DTE_QUAD3_RESERVED)) {
|
||
amdvi_log_illegaldevtab_error(s, devid,
|
||
s->devtab +
|
||
devid * AMDVI_DEVTAB_ENTRY_SIZE, 0);
|
||
return false;
|
||
}
|
||
|
||
/*
|
||
* 1 = Host Address Translation is not supported. Value in MMIO Offset
|
||
* 0030h[HATS] is not meaningful. A non-zero host page table root pointer
|
||
* in the DTE would result in an ILLEGAL_DEV_TABLE_ENTRY event.
|
||
*/
|
||
root = (dte[0] & AMDVI_DEV_PT_ROOT_MASK) >> 12;
|
||
if (root && !s->iommu.dma_translation) {
|
||
amdvi_log_illegaldevtab_error(s, devid,
|
||
s->devtab +
|
||
devid * AMDVI_DEVTAB_ENTRY_SIZE, 0);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/* get a device table entry given the devid */
|
||
static bool amdvi_get_dte(AMDVIState *s, int devid, uint64_t *entry)
|
||
{
|
||
uint32_t offset = devid * AMDVI_DEVTAB_ENTRY_SIZE;
|
||
|
||
if (dma_memory_read(&address_space_memory, s->devtab + offset, entry,
|
||
AMDVI_DEVTAB_ENTRY_SIZE, MEMTXATTRS_UNSPECIFIED)) {
|
||
trace_amdvi_dte_get_fail(s->devtab, offset);
|
||
/* log error accessing dte */
|
||
amdvi_log_devtab_error(s, devid, s->devtab + offset, 0);
|
||
return false;
|
||
}
|
||
|
||
*entry = le64_to_cpu(*entry);
|
||
if (!amdvi_validate_dte(s, devid, entry)) {
|
||
trace_amdvi_invalid_dte(entry[0]);
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/* get pte translation mode */
|
||
static inline uint8_t get_pte_translation_mode(uint64_t pte)
|
||
{
|
||
return (pte >> AMDVI_DEV_MODE_RSHIFT) & AMDVI_DEV_MODE_MASK;
|
||
}
|
||
|
||
static inline uint64_t amdvi_get_pte_entry(AMDVIState *s, uint64_t pte_addr,
|
||
uint16_t devid)
|
||
{
|
||
uint64_t pte;
|
||
|
||
if (dma_memory_read(&address_space_memory, pte_addr,
|
||
&pte, sizeof(pte), MEMTXATTRS_UNSPECIFIED)) {
|
||
trace_amdvi_get_pte_hwerror(pte_addr);
|
||
amdvi_log_pagetab_error(s, devid, pte_addr, 0);
|
||
pte = (uint64_t)-1;
|
||
return pte;
|
||
}
|
||
|
||
pte = le64_to_cpu(pte);
|
||
return pte;
|
||
}
|
||
|
||
static int amdvi_as_to_dte(AMDVIAddressSpace *as, uint64_t *dte)
|
||
{
|
||
uint16_t devid = PCI_BUILD_BDF(pci_bus_num(as->bus), as->devfn);
|
||
AMDVIState *s = as->iommu_state;
|
||
|
||
if (!amdvi_get_dte(s, devid, dte)) {
|
||
/* Unable to retrieve DTE for devid */
|
||
return -AMDVI_FR_DTE_RTR_ERR;
|
||
}
|
||
|
||
if (!(dte[0] & AMDVI_DEV_VALID)) {
|
||
/* DTE[V] not set, address is passed untranslated for devid */
|
||
return -AMDVI_FR_DTE_V;
|
||
}
|
||
|
||
if (!(dte[0] & AMDVI_DEV_TRANSLATION_VALID)) {
|
||
/* DTE[TV] not set, host page table not valid for devid */
|
||
return -AMDVI_FR_DTE_TV;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* For a PTE encoding a large page, return the page size it encodes as described
|
||
* by the AMD IOMMU Specification Table 14: Example Page Size Encodings.
|
||
* No need to adjust the value of the PTE to point to the first PTE in the large
|
||
* page since the encoding guarantees all "base" PTEs in the large page are the
|
||
* same.
|
||
*/
|
||
static uint64_t large_pte_page_size(uint64_t pte)
|
||
{
|
||
assert(PTE_NEXT_LEVEL(pte) == 7);
|
||
|
||
/* Determine size of the large/contiguous page encoded in the PTE */
|
||
return PTE_LARGE_PAGE_SIZE(pte);
|
||
}
|
||
|
||
/*
|
||
* Validate DTE fields and extract permissions and top level data required to
|
||
* initiate the page table walk.
|
||
*
|
||
* On success, returns 0 and stores:
|
||
* - top_level: highest page-table level encoded in DTE[Mode]
|
||
* - dte_perms: effective permissions from the DTE
|
||
*
|
||
* On failure, returns -AMDVI_FR_PT_ROOT_INV. This includes cases where:
|
||
* - DTE permissions disallow read AND write
|
||
* - DTE[Mode] is invalid for translation
|
||
* - IOVA exceeds the address width supported by DTE[Mode]
|
||
* In all such cases a page walk must be aborted.
|
||
*/
|
||
static int amdvi_get_top_pt_level_and_perms(hwaddr address, uint64_t dte,
|
||
uint8_t *top_level,
|
||
IOMMUAccessFlags *dte_perms)
|
||
{
|
||
*dte_perms = amdvi_get_perms(dte);
|
||
if (*dte_perms == IOMMU_NONE) {
|
||
return -AMDVI_FR_PT_ROOT_INV;
|
||
}
|
||
|
||
/* Verifying a valid mode is encoded in DTE */
|
||
*top_level = get_pte_translation_mode(dte);
|
||
|
||
/*
|
||
* Page Table Root pointer is only valid for GPA->SPA translation on
|
||
* supported modes.
|
||
*/
|
||
if (*top_level == 0 || *top_level > 6) {
|
||
return -AMDVI_FR_PT_ROOT_INV;
|
||
}
|
||
|
||
/*
|
||
* If IOVA is larger than the max supported by the highest pgtable level,
|
||
* there is nothing to do.
|
||
*/
|
||
if (address > PT_LEVEL_MAX_ADDR(*top_level)) {
|
||
/* IOVA too large for the current DTE */
|
||
return -AMDVI_FR_PT_ROOT_INV;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Helper function to fetch a PTE using AMD v1 pgtable format.
|
||
* On successful page walk, returns 0 and pte parameter points to a valid PTE.
|
||
* On failure, returns:
|
||
* -AMDVI_FR_PT_ROOT_INV: A page walk is not possible due to conditions like DTE
|
||
* with invalid permissions, Page Table Root can not be read from DTE, or a
|
||
* larger IOVA than supported by page table level encoded in DTE[Mode].
|
||
* -AMDVI_FR_PT_ENTRY_INV: A PTE could not be read from guest memory during a
|
||
* page table walk. This means that the DTE has valid data, but one of the
|
||
* lower level entries in the Page Table could not be read.
|
||
*/
|
||
static int fetch_pte(AMDVIAddressSpace *as, hwaddr address, uint64_t dte,
|
||
uint64_t *pte, hwaddr *page_size)
|
||
{
|
||
uint64_t pte_addr;
|
||
uint8_t pt_level, next_pt_level;
|
||
IOMMUAccessFlags perms;
|
||
int ret;
|
||
|
||
*page_size = 0;
|
||
|
||
/*
|
||
* Verify the DTE is properly configured before page walk, and extract
|
||
* top pagetable level and permissions.
|
||
*/
|
||
ret = amdvi_get_top_pt_level_and_perms(address, dte, &pt_level, &perms);
|
||
if (ret < 0) {
|
||
return ret;
|
||
}
|
||
|
||
/*
|
||
* Retrieve the top pagetable entry by following the DTE Page Table Root
|
||
* Pointer and indexing the top level table using the IOVA from the request.
|
||
*/
|
||
pte_addr = NEXT_PTE_ADDR(dte, pt_level, address);
|
||
*pte = amdvi_get_pte_entry(as->iommu_state, pte_addr, as->devfn);
|
||
|
||
if (*pte == (uint64_t)-1) {
|
||
/*
|
||
* A returned PTE of -1 here indicates a failure to read the top level
|
||
* page table from guest memory. A page walk is not possible and page
|
||
* size must be returned as 0.
|
||
*/
|
||
return -AMDVI_FR_PT_ROOT_INV;
|
||
}
|
||
|
||
/*
|
||
* Calculate page size for the top level page table entry.
|
||
* This ensures correct results for a single level Page Table setup.
|
||
*/
|
||
*page_size = PTE_LEVEL_PAGE_SIZE(pt_level);
|
||
|
||
/*
|
||
* The root page table entry and its level have been determined. Begin the
|
||
* page walk.
|
||
*/
|
||
while (pt_level > 0) {
|
||
|
||
/* Permission bits are ANDed at every level, including the DTE */
|
||
perms &= amdvi_get_perms(*pte);
|
||
if (perms == IOMMU_NONE) {
|
||
return 0;
|
||
}
|
||
|
||
/* Not Present */
|
||
if (!IOMMU_PTE_PRESENT(*pte)) {
|
||
return 0;
|
||
}
|
||
|
||
next_pt_level = PTE_NEXT_LEVEL(*pte);
|
||
|
||
/* Large or Leaf PTE found */
|
||
if (next_pt_level == 0 || next_pt_level == 7) {
|
||
/* Leaf PTE found */
|
||
break;
|
||
}
|
||
|
||
/* Next level must always be less than current level */
|
||
if (pt_level <= next_pt_level) {
|
||
return -AMDVI_FR_PT_ENTRY_INV;
|
||
}
|
||
pt_level = next_pt_level;
|
||
|
||
/*
|
||
* The current entry is a Page Directory Entry. Descend to the lower
|
||
* page table level encoded in current pte, and index the new table
|
||
* using the appropriate IOVA bits to retrieve the new entry.
|
||
*/
|
||
*page_size = PTE_LEVEL_PAGE_SIZE(pt_level);
|
||
|
||
pte_addr = NEXT_PTE_ADDR(*pte, pt_level, address);
|
||
*pte = amdvi_get_pte_entry(as->iommu_state, pte_addr, as->devfn);
|
||
|
||
if (*pte == (uint64_t)-1) {
|
||
/* Failure to read PTE. Page walk skips a page_size chunk */
|
||
return -AMDVI_FR_PT_ENTRY_INV;
|
||
}
|
||
}
|
||
|
||
assert(PTE_NEXT_LEVEL(*pte) == 0 || PTE_NEXT_LEVEL(*pte) == 7);
|
||
|
||
/*
|
||
* Page walk ends when Next Level field on PTE shows that either a leaf PTE
|
||
* or a series of large PTEs have been reached. In the latter case, even if
|
||
* the range starts in the middle of a contiguous page, the returned PTE
|
||
* must be the first PTE of the series.
|
||
*/
|
||
if (PTE_NEXT_LEVEL(*pte) == 7) {
|
||
/* Update page_size with the large PTE page size */
|
||
*page_size = large_pte_page_size(*pte);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
* Invoke notifiers registered for the address space. Update record of mapped
|
||
* ranges in IOVA Tree.
|
||
*/
|
||
static void amdvi_notify_iommu(AMDVIAddressSpace *as, IOMMUTLBEvent *event)
|
||
{
|
||
IOMMUTLBEntry *entry = &event->entry;
|
||
|
||
DMAMap target = {
|
||
.iova = entry->iova,
|
||
.size = entry->addr_mask,
|
||
.translated_addr = entry->translated_addr,
|
||
.perm = entry->perm,
|
||
};
|
||
|
||
/*
|
||
* Search the IOVA Tree for an existing translation for the target, and skip
|
||
* the notification if the mapping is already recorded.
|
||
* When the guest uses large pages, comparing against the record makes it
|
||
* possible to determine the size of the original MAP and adjust the UNMAP
|
||
* request to match it. This avoids failed checks against the mappings kept
|
||
* by the VFIO kernel driver.
|
||
*/
|
||
const DMAMap *mapped = iova_tree_find(as->iova_tree, &target);
|
||
|
||
if (event->type == IOMMU_NOTIFIER_UNMAP) {
|
||
if (!mapped) {
|
||
/* No record exists of this mapping, nothing to do */
|
||
return;
|
||
}
|
||
/*
|
||
* Adjust the size based on the original record. This is essential to
|
||
* determine when large/contiguous pages are used, since the guest has
|
||
* already cleared the PTE (erasing the pagesize encoded on it) before
|
||
* issuing the invalidation command.
|
||
*/
|
||
if (mapped->size != target.size) {
|
||
assert(mapped->size > target.size);
|
||
target.size = mapped->size;
|
||
/* Adjust event to invoke notifier with correct range */
|
||
entry->addr_mask = mapped->size;
|
||
}
|
||
iova_tree_remove(as->iova_tree, target);
|
||
} else { /* IOMMU_NOTIFIER_MAP */
|
||
if (mapped) {
|
||
/*
|
||
* If a mapping is present and matches the request, skip the
|
||
* notification.
|
||
*/
|
||
if (!memcmp(mapped, &target, sizeof(DMAMap))) {
|
||
return;
|
||
} else {
|
||
/*
|
||
* This should never happen unless a buggy guest OS omits or
|
||
* sends incorrect invalidation(s). Report an error in the event
|
||
* it does happen.
|
||
*/
|
||
error_report("Found conflicting translation. This could be due "
|
||
"to an incorrect or missing invalidation command");
|
||
}
|
||
}
|
||
/* Record the new mapping */
|
||
iova_tree_insert(as->iova_tree, &target);
|
||
}
|
||
|
||
/* Invoke the notifiers registered for this address space */
|
||
memory_region_notify_iommu(&as->iommu, 0, *event);
|
||
}
|
||
|
||
/*
|
||
* Walk the guest page table for an IOVA and range and signal the registered
|
||
* notifiers to sync the shadow page tables in the host.
|
||
* Must be called with a valid DTE for DMA remapping i.e. V=1,TV=1
|
||
*/
|
||
static void amdvi_sync_shadow_page_table_range(AMDVIAddressSpace *as,
|
||
uint64_t *dte, hwaddr addr,
|
||
uint64_t size, bool send_unmap)
|
||
{
|
||
IOMMUTLBEvent event;
|
||
|
||
hwaddr page_mask, pagesize;
|
||
hwaddr iova = addr;
|
||
hwaddr end = iova + size - 1;
|
||
|
||
uint64_t pte;
|
||
int ret;
|
||
|
||
while (iova < end) {
|
||
|
||
ret = fetch_pte(as, iova, dte[0], &pte, &pagesize);
|
||
|
||
if (ret == -AMDVI_FR_PT_ROOT_INV) {
|
||
/*
|
||
* Invalid conditions such as the IOVA being larger than supported
|
||
* by current page table mode as configured in the DTE, or a failure
|
||
* to fetch the Page Table from the Page Table Root Pointer in DTE.
|
||
*/
|
||
assert(pagesize == 0);
|
||
return;
|
||
}
|
||
/* PTE has been validated for major errors and pagesize is set */
|
||
assert(pagesize);
|
||
page_mask = ~(pagesize - 1);
|
||
|
||
if (ret == -AMDVI_FR_PT_ENTRY_INV) {
|
||
/*
|
||
* Failure to read PTE from memory, the pagesize matches the current
|
||
* level. Unable to determine the region type, so a safe strategy is
|
||
* to skip the range and continue the page walk.
|
||
*/
|
||
goto next;
|
||
}
|
||
|
||
event.entry.target_as = &address_space_memory;
|
||
event.entry.iova = iova & page_mask;
|
||
/* translated_addr is irrelevant for the unmap case */
|
||
event.entry.translated_addr = (pte & AMDVI_DEV_PT_ROOT_MASK) &
|
||
page_mask;
|
||
event.entry.addr_mask = ~page_mask;
|
||
event.entry.perm = amdvi_get_perms(pte);
|
||
|
||
/*
|
||
* In cases where the leaf PTE is not found, or it has invalid
|
||
* permissions, an UNMAP type notification is sent, but only if the
|
||
* caller requested it.
|
||
*/
|
||
if (!IOMMU_PTE_PRESENT(pte) || (event.entry.perm == IOMMU_NONE)) {
|
||
if (!send_unmap) {
|
||
goto next;
|
||
}
|
||
event.type = IOMMU_NOTIFIER_UNMAP;
|
||
} else {
|
||
event.type = IOMMU_NOTIFIER_MAP;
|
||
}
|
||
|
||
/*
|
||
* The following call might need to adjust event.entry.size in cases
|
||
* where the guest unmapped a series of large pages.
|
||
*/
|
||
amdvi_notify_iommu(as, &event);
|
||
/*
|
||
* In the special scenario where the guest is unmapping a large page,
|
||
* addr_mask has been adjusted before sending the notification. Update
|
||
* pagesize accordingly in order to correctly compute the next IOVA.
|
||
*/
|
||
pagesize = event.entry.addr_mask + 1;
|
||
|
||
next:
|
||
iova &= ~(pagesize - 1);
|
||
|
||
/* Check for 64-bit overflow and terminate walk in such cases */
|
||
if ((iova + pagesize) < iova) {
|
||
break;
|
||
} else {
|
||
iova += pagesize;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Unmap entire range that the notifier registered for i.e. the full AS.
|
||
*
|
||
* This is seemingly technically equivalent to directly calling
|
||
* memory_region_unmap_iommu_notifier_range(), but it allows to check for
|
||
* notifier boundaries and issue notifications with ranges within those bounds.
|
||
*/
|
||
static void amdvi_address_space_unmap(AMDVIAddressSpace *as, IOMMUNotifier *n)
|
||
{
|
||
|
||
hwaddr start = n->start;
|
||
hwaddr end = n->end;
|
||
hwaddr remain;
|
||
DMAMap map;
|
||
|
||
assert(start <= end);
|
||
remain = end - start + 1;
|
||
|
||
/*
|
||
* Divide the notifier range into chunks that are aligned and do not exceed
|
||
* the notifier boundaries.
|
||
*/
|
||
while (remain >= AMDVI_PAGE_SIZE) {
|
||
|
||
IOMMUTLBEvent event;
|
||
|
||
uint64_t mask = dma_aligned_pow2_mask(start, end, 64);
|
||
|
||
event.type = IOMMU_NOTIFIER_UNMAP;
|
||
|
||
IOMMUTLBEntry entry = {
|
||
.target_as = &address_space_memory,
|
||
.iova = start,
|
||
.translated_addr = 0, /* irrelevant for unmap case */
|
||
.addr_mask = mask,
|
||
.perm = IOMMU_NONE,
|
||
};
|
||
event.entry = entry;
|
||
|
||
/* Call notifier registered for updates on this address space */
|
||
memory_region_notify_iommu_one(n, &event);
|
||
|
||
start += mask + 1;
|
||
remain -= mask + 1;
|
||
}
|
||
|
||
assert(!remain);
|
||
|
||
map.iova = n->start;
|
||
map.size = n->end - n->start;
|
||
|
||
iova_tree_remove(as->iova_tree, map);
|
||
}
|
||
|
||
/*
|
||
* For all the address spaces with notifiers registered, unmap the entire range
|
||
* the notifier registered for i.e. clear all the address spaces managed by the
|
||
* IOMMU.
|
||
*/
|
||
static void amdvi_address_space_unmap_all(AMDVIState *s)
|
||
{
|
||
AMDVIAddressSpace *as;
|
||
IOMMUNotifier *n;
|
||
|
||
QLIST_FOREACH(as, &s->amdvi_as_with_notifiers, next) {
|
||
IOMMU_NOTIFIER_FOREACH(n, &as->iommu) {
|
||
amdvi_address_space_unmap(as, n);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* For every translation present in the IOMMU, construct IOMMUTLBEntry data
|
||
* and pass it as parameter to notifier callback.
|
||
*/
|
||
static void amdvi_iommu_replay(IOMMUMemoryRegion *iommu_mr, IOMMUNotifier *n)
|
||
{
|
||
AMDVIAddressSpace *as = container_of(iommu_mr, AMDVIAddressSpace, iommu);
|
||
uint64_t dte[4] = { 0 };
|
||
|
||
if (!(n->notifier_flags & IOMMU_NOTIFIER_MAP)) {
|
||
return;
|
||
}
|
||
|
||
if (amdvi_as_to_dte(as, dte)) {
|
||
return;
|
||
}
|
||
|
||
/* Dropping all mappings for the address space. Also clears the IOVA tree */
|
||
amdvi_address_space_unmap(as, n);
|
||
|
||
amdvi_sync_shadow_page_table_range(as, &dte[0], 0, UINT64_MAX, false);
|
||
}
|
||
|
||
static void amdvi_address_space_sync(AMDVIAddressSpace *as)
|
||
{
|
||
IOMMUNotifier *n;
|
||
uint64_t dte[4] = { 0 };
|
||
|
||
/* If only UNMAP notifiers are registered, drop all existing mappings */
|
||
if (!(as->notifier_flags & IOMMU_NOTIFIER_MAP)) {
|
||
IOMMU_NOTIFIER_FOREACH(n, &as->iommu) {
|
||
/*
|
||
* Directly calling memory_region_unmap_iommu_notifier_range() does
|
||
* not guarantee that the addr_mask eventually passed as parameter
|
||
* to the notifier is valid. Use amdvi_address_space_unmap() which
|
||
* ensures the notifier range is divided into properly aligned
|
||
* regions, and issues notifications for each one.
|
||
*/
|
||
amdvi_address_space_unmap(as, n);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (amdvi_as_to_dte(as, dte)) {
|
||
return;
|
||
}
|
||
|
||
amdvi_sync_shadow_page_table_range(as, &dte[0], 0, UINT64_MAX, true);
|
||
}
|
||
|
||
/*
|
||
* This differs from the replay() method in that it issues both MAP and UNMAP
|
||
* notifications since it is called after global invalidation events in order to
|
||
* re-sync all address spaces.
|
||
*/
|
||
static void amdvi_iommu_address_space_sync_all(AMDVIState *s)
|
||
{
|
||
AMDVIAddressSpace *as;
|
||
|
||
QLIST_FOREACH(as, &s->amdvi_as_with_notifiers, next) {
|
||
amdvi_address_space_sync(as);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Toggle between address translation and passthrough modes by enabling the
|
||
* corresponding memory regions.
|
||
*/
|
||
static void amdvi_switch_address_space(AMDVIAddressSpace *amdvi_as)
|
||
{
|
||
AMDVIState *s = amdvi_as->iommu_state;
|
||
|
||
if (s->dma_remap && amdvi_as->addr_translation) {
|
||
/* Enabling DMA region */
|
||
memory_region_set_enabled(&amdvi_as->iommu_nodma, false);
|
||
memory_region_set_enabled(MEMORY_REGION(&amdvi_as->iommu), true);
|
||
} else {
|
||
/* Disabling DMA region, using passthrough */
|
||
memory_region_set_enabled(MEMORY_REGION(&amdvi_as->iommu), false);
|
||
memory_region_set_enabled(&amdvi_as->iommu_nodma, true);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* For all existing address spaces managed by the IOMMU, enable/disable the
|
||
* corresponding memory regions to reset the address translation mode and
|
||
* use passthrough by default.
|
||
*/
|
||
static void amdvi_reset_address_translation_all(AMDVIState *s)
|
||
{
|
||
AMDVIAddressSpace *iommu_as;
|
||
GHashTableIter as_it;
|
||
|
||
g_hash_table_iter_init(&as_it, s->address_spaces);
|
||
|
||
while (g_hash_table_iter_next(&as_it, NULL, (void **)&iommu_as)) {
|
||
/* Use passthrough as default mode after reset */
|
||
iommu_as->addr_translation = false;
|
||
amdvi_switch_address_space(iommu_as);
|
||
}
|
||
}
|
||
|
||
static void enable_dma_mode(AMDVIAddressSpace *as, bool inval_current)
|
||
{
|
||
/*
|
||
* When enabling DMA mode for the purpose of isolating guest devices on
|
||
* a failure to retrieve or invalid DTE, all existing mappings must be
|
||
* dropped.
|
||
*/
|
||
if (inval_current) {
|
||
IOMMUNotifier *n;
|
||
IOMMU_NOTIFIER_FOREACH(n, &as->iommu) {
|
||
amdvi_address_space_unmap(as, n);
|
||
}
|
||
}
|
||
|
||
if (as->addr_translation) {
|
||
return;
|
||
}
|
||
|
||
/* Installing DTE enabling translation, activate region */
|
||
as->addr_translation = true;
|
||
amdvi_switch_address_space(as);
|
||
/* Sync shadow page tables */
|
||
amdvi_address_space_sync(as);
|
||
}
|
||
|
||
/*
|
||
* If paging was previously in use in the address space
|
||
* - invalidate all existing mappings
|
||
* - switch to no_dma memory region
|
||
*/
|
||
static void enable_nodma_mode(AMDVIAddressSpace *as)
|
||
{
|
||
IOMMUNotifier *n;
|
||
|
||
if (!as->addr_translation) {
|
||
/* passthrough is already active, nothing to do */
|
||
return;
|
||
}
|
||
|
||
as->addr_translation = false;
|
||
IOMMU_NOTIFIER_FOREACH(n, &as->iommu) {
|
||
/* Drop all mappings for the address space */
|
||
amdvi_address_space_unmap(as, n);
|
||
}
|
||
amdvi_switch_address_space(as);
|
||
}
|
||
|
||
/*
|
||
* A guest driver must issue the INVALIDATE_DEVTAB_ENTRY command to the IOMMU
|
||
* after changing a Device Table entry. We can use this fact to detect when a
|
||
* Device Table entry is created for a device attached to a paging domain and
|
||
* enable the corresponding IOMMU memory region to allow for DMA translation if
|
||
* appropriate.
|
||
*/
|
||
static void amdvi_update_addr_translation_mode(AMDVIState *s, uint16_t devid)
|
||
{
|
||
uint8_t dte_mode;
|
||
AMDVIAddressSpace *as;
|
||
uint64_t dte[4] = { 0 };
|
||
int ret;
|
||
|
||
as = amdvi_get_as_by_devid(s, devid);
|
||
if (!as) {
|
||
return;
|
||
}
|
||
|
||
ret = amdvi_as_to_dte(as, dte);
|
||
|
||
if (!ret) {
|
||
dte_mode = (dte[0] >> AMDVI_DEV_MODE_RSHIFT) & AMDVI_DEV_MODE_MASK;
|
||
}
|
||
|
||
switch (ret) {
|
||
case 0:
|
||
/* DTE was successfully retrieved */
|
||
if (!dte_mode) {
|
||
enable_nodma_mode(as); /* DTE[V]=1 && DTE[Mode]=0 => passthrough */
|
||
} else {
|
||
enable_dma_mode(as, false); /* Enable DMA translation */
|
||
}
|
||
break;
|
||
case -AMDVI_FR_DTE_V:
|
||
/* DTE[V]=0, address is passed untranslated */
|
||
enable_nodma_mode(as);
|
||
break;
|
||
case -AMDVI_FR_DTE_RTR_ERR:
|
||
case -AMDVI_FR_DTE_TV:
|
||
/*
|
||
* Enforce isolation by using DMA in rare scenarios where the DTE cannot
|
||
* be retrieved or DTE[TV]=0. Existing mappings are dropped.
|
||
*/
|
||
enable_dma_mode(as, true);
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* log error without aborting since linux seems to be using reserved bits */
|
||
static void amdvi_inval_devtab_entry(AMDVIState *s, uint64_t *cmd)
|
||
{
|
||
uint16_t devid = extract64(cmd[0], 0, 16);
|
||
|
||
trace_amdvi_devtab_inval(PCI_BUS_NUM(devid), PCI_SLOT(devid),
|
||
PCI_FUNC(devid));
|
||
|
||
/* This command should invalidate internal caches of which there isn't */
|
||
if (extract64(cmd[0], 16, 44) || cmd[1]) {
|
||
amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4),
|
||
s->cmdbuf + s->cmdbuf_head);
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* When DMA remapping capability is enabled, check if updated DTE is setup
|
||
* for paging or not, and configure the corresponding memory regions.
|
||
*/
|
||
if (s->dma_remap) {
|
||
amdvi_update_addr_translation_mode(s, devid);
|
||
}
|
||
}
|
||
|
||
static void amdvi_complete_ppr(AMDVIState *s, uint64_t *cmd)
|
||
{
|
||
if (extract64(cmd[0], 16, 16) || extract64(cmd[0], 52, 8) ||
|
||
extract64(cmd[1], 0, 2) || extract64(cmd[1], 3, 29)
|
||
|| extract64(cmd[1], 48, 16)) {
|
||
amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4),
|
||
s->cmdbuf + s->cmdbuf_head);
|
||
}
|
||
trace_amdvi_ppr_exec();
|
||
}
|
||
|
||
static void amdvi_intremap_inval_notify_all(AMDVIState *s, bool global,
|
||
uint32_t index, uint32_t mask)
|
||
{
|
||
x86_iommu_iec_notify_all(X86_IOMMU_DEVICE(s), global, index, mask);
|
||
}
|
||
|
||
static void amdvi_inval_all(AMDVIState *s, uint64_t *cmd)
|
||
{
|
||
if (extract64(cmd[0], 0, 60) || cmd[1]) {
|
||
amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4),
|
||
s->cmdbuf + s->cmdbuf_head);
|
||
}
|
||
|
||
/* Notify global invalidation */
|
||
amdvi_intremap_inval_notify_all(s, true, 0, 0);
|
||
|
||
amdvi_iotlb_reset(s);
|
||
|
||
/*
|
||
* Fully replay the address space i.e. send both UNMAP and MAP events in
|
||
* order to synchronize guest and host IO page tables tables.
|
||
*/
|
||
amdvi_iommu_address_space_sync_all(s);
|
||
|
||
trace_amdvi_all_inval();
|
||
}
|
||
|
||
static gboolean amdvi_iotlb_remove_by_domid(gpointer key, gpointer value,
|
||
gpointer user_data)
|
||
{
|
||
AMDVIIOTLBEntry *entry = (AMDVIIOTLBEntry *)value;
|
||
uint16_t domid = *(uint16_t *)user_data;
|
||
return entry->domid == domid;
|
||
}
|
||
|
||
/*
|
||
* Helper to decode the size of the range to invalidate encoded in the
|
||
* INVALIDATE_IOMMU_PAGES Command format.
|
||
* The size of the region to invalidate depends on the S bit and address.
|
||
* S bit value:
|
||
* 0 : Invalidation size is 4 Kbytes.
|
||
* 1 : Invalidation size is determined by first zero bit in the address
|
||
* starting from Address[12].
|
||
*
|
||
* In the AMD IOMMU Linux driver, an invalidation command with address
|
||
* ((1 << 63) - 1) is sent when intending to clear the entire cache.
|
||
* However, Table 14: Example Page Size Encodings shows that an address of
|
||
* ((1ULL << 51) - 1) encodes the entire cache, so effectively any address with
|
||
* first zero at bit 51 or larger is a request to invalidate the entire address
|
||
* space.
|
||
*/
|
||
static uint64_t amdvi_decode_invalidation_size(hwaddr addr, uint16_t flags)
|
||
{
|
||
uint64_t size = AMDVI_PAGE_SIZE;
|
||
uint8_t fzbit = 0;
|
||
|
||
if (flags & AMDVI_CMD_INVAL_IOMMU_PAGES_S) {
|
||
fzbit = cto64(addr | 0xFFF);
|
||
|
||
if (fzbit >= 51) {
|
||
size = AMDVI_INV_ALL_PAGES;
|
||
} else {
|
||
size = 1ULL << (fzbit + 1);
|
||
}
|
||
}
|
||
return size;
|
||
}
|
||
|
||
/*
|
||
* Synchronize the guest page tables with the shadow page tables kept in the
|
||
* host for the specified range.
|
||
* The invalidation command issued by the guest and intercepted by the VMM
|
||
* does not specify a device, but a domain, since all devices in the same domain
|
||
* share the same page tables. However, vIOMMU emulation creates separate
|
||
* address spaces per device, so it is necessary to traverse the list of all of
|
||
* address spaces (i.e. devices) that have notifiers registered in order to
|
||
* propagate the changes to the host page tables.
|
||
* We cannot return early from this function once a matching domain has been
|
||
* identified and its page tables synced (based on the fact that all devices in
|
||
* the same domain share the page tables). The reason is that different devices
|
||
* (i.e. address spaces) could have different notifiers registered, and by
|
||
* skipping address spaces that appear later on the amdvi_as_with_notifiers list
|
||
* their notifiers (which could differ from the ones registered for the first
|
||
* device/address space) would not be invoked.
|
||
*/
|
||
static void amdvi_sync_domain(AMDVIState *s, uint16_t domid, uint64_t addr,
|
||
uint16_t flags)
|
||
{
|
||
AMDVIAddressSpace *as;
|
||
|
||
uint64_t size = amdvi_decode_invalidation_size(addr, flags);
|
||
|
||
if (size == AMDVI_INV_ALL_PAGES) {
|
||
addr = 0; /* Set start address to 0 and invalidate entire AS */
|
||
} else {
|
||
addr &= ~(size - 1);
|
||
}
|
||
|
||
/*
|
||
* Call notifiers that have registered for each address space matching the
|
||
* domain ID, in order to sync the guest pagetable state with the host.
|
||
*/
|
||
QLIST_FOREACH(as, &s->amdvi_as_with_notifiers, next) {
|
||
|
||
uint64_t dte[4] = { 0 };
|
||
|
||
/*
|
||
* Retrieve the Device Table entry for the devid corresponding to the
|
||
* current address space, and verify the DomainID matches i.e. the page
|
||
* tables to be synced belong to devices in the domain.
|
||
*/
|
||
if (amdvi_as_to_dte(as, dte)) {
|
||
continue;
|
||
}
|
||
|
||
/* Only need to sync the Page Tables for a matching domain */
|
||
if (domid != (dte[1] & AMDVI_DEV_DOMID_ID_MASK)) {
|
||
continue;
|
||
}
|
||
|
||
/*
|
||
* We have determined that there is a valid Device Table Entry for a
|
||
* device matching the DomainID in the INV_IOMMU_PAGES command issued by
|
||
* the guest. Walk the guest page table to sync shadow page table.
|
||
*/
|
||
if (as->notifier_flags & IOMMU_NOTIFIER_MAP) {
|
||
/* Sync guest IOMMU mappings with host */
|
||
amdvi_sync_shadow_page_table_range(as, &dte[0], addr, size, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* we don't have devid - we can't remove pages by address */
|
||
static void amdvi_inval_pages(AMDVIState *s, uint64_t *cmd)
|
||
{
|
||
uint16_t domid = extract64(cmd[0], 32, 16);
|
||
uint64_t addr = extract64(cmd[1], 12, 52) << 12;
|
||
uint16_t flags = extract64(cmd[1], 0, 3);
|
||
|
||
if (extract64(cmd[0], 20, 12) || extract64(cmd[0], 48, 12) ||
|
||
extract64(cmd[1], 3, 9)) {
|
||
amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4),
|
||
s->cmdbuf + s->cmdbuf_head);
|
||
}
|
||
|
||
g_hash_table_foreach_remove(s->iotlb, amdvi_iotlb_remove_by_domid,
|
||
&domid);
|
||
|
||
amdvi_sync_domain(s, domid, addr, flags);
|
||
trace_amdvi_pages_inval(domid);
|
||
}
|
||
|
||
static void amdvi_prefetch_pages(AMDVIState *s, uint64_t *cmd)
|
||
{
|
||
if (extract64(cmd[0], 16, 8) || extract64(cmd[0], 52, 8) ||
|
||
extract64(cmd[1], 1, 1) || extract64(cmd[1], 3, 1) ||
|
||
extract64(cmd[1], 5, 7)) {
|
||
amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4),
|
||
s->cmdbuf + s->cmdbuf_head);
|
||
}
|
||
|
||
trace_amdvi_prefetch_pages();
|
||
}
|
||
|
||
static void amdvi_inval_inttable(AMDVIState *s, uint64_t *cmd)
|
||
{
|
||
if (extract64(cmd[0], 16, 44) || cmd[1]) {
|
||
amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4),
|
||
s->cmdbuf + s->cmdbuf_head);
|
||
return;
|
||
}
|
||
|
||
/* Notify global invalidation */
|
||
amdvi_intremap_inval_notify_all(s, true, 0, 0);
|
||
|
||
trace_amdvi_intr_inval();
|
||
}
|
||
|
||
/* FIXME: Try to work with the specified size instead of all the pages
|
||
* when the S bit is on
|
||
*/
|
||
static void iommu_inval_iotlb(AMDVIState *s, uint64_t *cmd)
|
||
{
|
||
|
||
uint16_t devid = extract64(cmd[0], 0, 16);
|
||
if (extract64(cmd[1], 1, 1) || extract64(cmd[1], 3, 1) ||
|
||
extract64(cmd[1], 6, 6)) {
|
||
amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4),
|
||
s->cmdbuf + s->cmdbuf_head);
|
||
return;
|
||
}
|
||
|
||
if (extract64(cmd[1], 0, 1)) {
|
||
g_hash_table_foreach_remove(s->iotlb, amdvi_iotlb_remove_by_devid,
|
||
&devid);
|
||
} else {
|
||
amdvi_iotlb_remove_page(s, extract64(cmd[1], 12, 52) << 12,
|
||
devid);
|
||
}
|
||
trace_amdvi_iotlb_inval();
|
||
}
|
||
|
||
/* not honouring reserved bits is regarded as an illegal command */
|
||
static void amdvi_cmdbuf_exec(AMDVIState *s)
|
||
{
|
||
uint64_t cmd[2];
|
||
|
||
if (dma_memory_read(&address_space_memory, s->cmdbuf + s->cmdbuf_head,
|
||
cmd, AMDVI_COMMAND_SIZE, MEMTXATTRS_UNSPECIFIED)) {
|
||
trace_amdvi_command_read_fail(s->cmdbuf, s->cmdbuf_head);
|
||
amdvi_log_command_error(s, s->cmdbuf + s->cmdbuf_head);
|
||
return;
|
||
}
|
||
|
||
/*
|
||
* Commands in guest memory are little-endian. Convert once after reading
|
||
* so that command handlers can decode values in host native endianness.
|
||
* Convert back to little-endian only when writing data to guest memory via
|
||
* dma_memory_write().
|
||
*/
|
||
cmd[0] = le64_to_cpu(cmd[0]);
|
||
cmd[1] = le64_to_cpu(cmd[1]);
|
||
|
||
switch (extract64(cmd[0], 60, 4)) {
|
||
case AMDVI_CMD_COMPLETION_WAIT:
|
||
amdvi_completion_wait(s, cmd);
|
||
break;
|
||
case AMDVI_CMD_INVAL_DEVTAB_ENTRY:
|
||
amdvi_inval_devtab_entry(s, cmd);
|
||
break;
|
||
case AMDVI_CMD_INVAL_AMDVI_PAGES:
|
||
amdvi_inval_pages(s, cmd);
|
||
break;
|
||
case AMDVI_CMD_INVAL_IOTLB_PAGES:
|
||
iommu_inval_iotlb(s, cmd);
|
||
break;
|
||
case AMDVI_CMD_INVAL_INTR_TABLE:
|
||
amdvi_inval_inttable(s, cmd);
|
||
break;
|
||
case AMDVI_CMD_PREFETCH_AMDVI_PAGES:
|
||
amdvi_prefetch_pages(s, cmd);
|
||
break;
|
||
case AMDVI_CMD_COMPLETE_PPR_REQUEST:
|
||
amdvi_complete_ppr(s, cmd);
|
||
break;
|
||
case AMDVI_CMD_INVAL_AMDVI_ALL:
|
||
amdvi_inval_all(s, cmd);
|
||
break;
|
||
default:
|
||
trace_amdvi_unhandled_command(extract64(cmd[0], 60, 4));
|
||
/* log illegal command */
|
||
amdvi_log_illegalcom_error(s, extract64(cmd[0], 60, 4),
|
||
s->cmdbuf + s->cmdbuf_head);
|
||
}
|
||
}
|
||
|
||
static void amdvi_cmdbuf_run(AMDVIState *s)
|
||
{
|
||
if (!s->cmdbuf_enabled) {
|
||
trace_amdvi_command_error(amdvi_readq(s, AMDVI_MMIO_CONTROL));
|
||
return;
|
||
}
|
||
|
||
/* check if there is work to do. */
|
||
while (s->cmdbuf_head != s->cmdbuf_tail) {
|
||
trace_amdvi_command_exec(s->cmdbuf_head, s->cmdbuf_tail, s->cmdbuf);
|
||
amdvi_cmdbuf_exec(s);
|
||
s->cmdbuf_head += AMDVI_COMMAND_SIZE;
|
||
|
||
/* wrap head pointer */
|
||
if (s->cmdbuf_head >= s->cmdbuf_len * AMDVI_COMMAND_SIZE) {
|
||
s->cmdbuf_head = 0;
|
||
}
|
||
amdvi_writeq_raw(s, AMDVI_MMIO_COMMAND_HEAD, s->cmdbuf_head);
|
||
}
|
||
}
|
||
|
||
static inline
|
||
const char *amdvi_mmio_get_name(hwaddr addr)
|
||
{
|
||
/* Return MMIO names as string literals */
|
||
switch (addr) {
|
||
#define MMIO_REG_TO_STRING(mmio_reg) case mmio_reg: return #mmio_reg
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_DEVICE_TABLE);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_COMMAND_BASE);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_EVENT_BASE);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_CONTROL);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_EXCL_BASE);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_EXCL_LIMIT);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_EXT_FEATURES);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_COMMAND_HEAD);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_COMMAND_TAIL);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_EVENT_HEAD);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_EVENT_TAIL);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_STATUS);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_PPR_BASE);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_PPR_HEAD);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_PPR_TAIL);
|
||
MMIO_REG_TO_STRING(AMDVI_MMIO_XT_GEN_INTR);
|
||
#undef MMIO_REG_TO_STRING
|
||
default:
|
||
return "UNHANDLED";
|
||
}
|
||
}
|
||
|
||
static uint64_t amdvi_mmio_read(void *opaque, hwaddr addr, unsigned size)
|
||
{
|
||
AMDVIState *s = opaque;
|
||
|
||
uint64_t val = -1;
|
||
if (addr + size > AMDVI_MMIO_SIZE) {
|
||
trace_amdvi_mmio_read_invalid(AMDVI_MMIO_SIZE, addr, size);
|
||
return (uint64_t)-1;
|
||
}
|
||
|
||
if (size == 2) {
|
||
val = amdvi_readw(s, addr);
|
||
} else if (size == 4) {
|
||
val = amdvi_readl(s, addr);
|
||
} else if (size == 8) {
|
||
val = amdvi_readq(s, addr);
|
||
}
|
||
trace_amdvi_mmio_read(amdvi_mmio_get_name(addr), addr, size, addr & ~0x07);
|
||
|
||
return val;
|
||
}
|
||
|
||
static void amdvi_handle_control_write(AMDVIState *s)
|
||
{
|
||
unsigned long control = amdvi_readq(s, AMDVI_MMIO_CONTROL);
|
||
s->enabled = !!(control & AMDVI_MMIO_CONTROL_AMDVIEN);
|
||
|
||
s->evtlog_enabled = s->enabled && !!(control &
|
||
AMDVI_MMIO_CONTROL_EVENTLOGEN);
|
||
|
||
s->evtlog_intr = !!(control & AMDVI_MMIO_CONTROL_EVENTINTEN);
|
||
s->completion_wait_intr = !!(control & AMDVI_MMIO_CONTROL_COMWAITINTEN);
|
||
s->cmdbuf_enabled = s->enabled && !!(control &
|
||
AMDVI_MMIO_CONTROL_CMDBUFLEN);
|
||
s->ga_enabled = !!(control & AMDVI_MMIO_CONTROL_GAEN);
|
||
s->xten = !!(control & AMDVI_MMIO_CONTROL_XTEN) && s->xtsup &&
|
||
s->ga_enabled;
|
||
/*
|
||
* IntCapXTEn controls whether IOMMU-originated interrupts are sent based
|
||
* on the information in XT IOMMU Interrupt Control Registers rather than
|
||
* the IOMMU’s MSI capability registers. Therefore it requires IOMMU
|
||
* x2APIC support capabilities (i.e. XTSup=1), but it is independent of
|
||
* whether a driver chooses to enable x2APIC mode for interrupt remapping
|
||
* (i.e. XTEn=1).
|
||
*/
|
||
s->intcapxten = !!(control & AMDVI_MMIO_CONTROL_INTCAPXTEN) && s->xtsup;
|
||
|
||
/* update the flags depending on the control register */
|
||
if (s->cmdbuf_enabled) {
|
||
amdvi_assign_orq(s, AMDVI_MMIO_STATUS, AMDVI_MMIO_STATUS_CMDBUF_RUN);
|
||
} else {
|
||
amdvi_assign_andq(s, AMDVI_MMIO_STATUS, ~AMDVI_MMIO_STATUS_CMDBUF_RUN);
|
||
}
|
||
if (s->evtlog_enabled) {
|
||
amdvi_assign_orq(s, AMDVI_MMIO_STATUS, AMDVI_MMIO_STATUS_EVT_RUN);
|
||
} else {
|
||
amdvi_assign_andq(s, AMDVI_MMIO_STATUS, ~AMDVI_MMIO_STATUS_EVT_RUN);
|
||
}
|
||
|
||
trace_amdvi_control_status(control);
|
||
amdvi_cmdbuf_run(s);
|
||
}
|
||
|
||
static inline void amdvi_handle_devtab_write(AMDVIState *s)
|
||
|
||
{
|
||
uint64_t val = amdvi_readq(s, AMDVI_MMIO_DEVICE_TABLE);
|
||
s->devtab = (val & AMDVI_MMIO_DEVTAB_BASE_MASK);
|
||
|
||
/* set device table length (i.e. number of entries table can hold) */
|
||
s->devtab_len = (((val & AMDVI_MMIO_DEVTAB_SIZE_MASK) + 1) *
|
||
(AMDVI_MMIO_DEVTAB_SIZE_UNIT /
|
||
AMDVI_MMIO_DEVTAB_ENTRY_SIZE));
|
||
}
|
||
|
||
static inline void amdvi_handle_cmdhead_write(AMDVIState *s)
|
||
{
|
||
s->cmdbuf_head = amdvi_readq(s, AMDVI_MMIO_COMMAND_HEAD)
|
||
& AMDVI_MMIO_CMDBUF_HEAD_MASK
|
||
& (s->cmdbuf_len * AMDVI_COMMAND_SIZE - 1);
|
||
amdvi_cmdbuf_run(s);
|
||
}
|
||
|
||
static inline void amdvi_handle_cmdbase_write(AMDVIState *s)
|
||
{
|
||
s->cmdbuf = amdvi_readq(s, AMDVI_MMIO_COMMAND_BASE)
|
||
& AMDVI_MMIO_CMDBUF_BASE_MASK;
|
||
s->cmdbuf_len = 1UL << (amdvi_readq(s, AMDVI_MMIO_CMDBUF_SIZE_BYTE)
|
||
& AMDVI_MMIO_CMDBUF_SIZE_MASK);
|
||
s->cmdbuf_head = s->cmdbuf_tail = 0;
|
||
}
|
||
|
||
static inline void amdvi_handle_cmdtail_write(AMDVIState *s)
|
||
{
|
||
s->cmdbuf_tail = amdvi_readq(s, AMDVI_MMIO_COMMAND_TAIL)
|
||
& AMDVI_MMIO_CMDBUF_TAIL_MASK
|
||
& (s->cmdbuf_len * AMDVI_COMMAND_SIZE - 1);
|
||
amdvi_cmdbuf_run(s);
|
||
}
|
||
|
||
static inline void amdvi_handle_excllim_write(AMDVIState *s)
|
||
{
|
||
uint64_t val = amdvi_readq(s, AMDVI_MMIO_EXCL_LIMIT);
|
||
s->excl_limit = (val & AMDVI_MMIO_EXCL_LIMIT_MASK) |
|
||
AMDVI_MMIO_EXCL_LIMIT_LOW;
|
||
}
|
||
|
||
static inline void amdvi_handle_evtbase_write(AMDVIState *s)
|
||
{
|
||
uint64_t val = amdvi_readq(s, AMDVI_MMIO_EVENT_BASE);
|
||
|
||
if (amdvi_readq(s, AMDVI_MMIO_STATUS) & AMDVI_MMIO_STATUS_EVENT_INT)
|
||
/* Do not reset if eventlog interrupt bit is set*/
|
||
return;
|
||
|
||
s->evtlog = val & AMDVI_MMIO_EVTLOG_BASE_MASK;
|
||
s->evtlog_len = 1UL << (amdvi_readq(s, AMDVI_MMIO_EVTLOG_SIZE_BYTE)
|
||
& AMDVI_MMIO_EVTLOG_SIZE_MASK);
|
||
|
||
/* clear tail and head pointer to 0 when event base is updated */
|
||
s->evtlog_tail = s->evtlog_head = 0;
|
||
amdvi_writeq_raw(s, AMDVI_MMIO_EVENT_HEAD, s->evtlog_head);
|
||
amdvi_writeq_raw(s, AMDVI_MMIO_EVENT_TAIL, s->evtlog_tail);
|
||
}
|
||
|
||
static inline void amdvi_handle_evttail_write(AMDVIState *s)
|
||
{
|
||
uint64_t val = amdvi_readq(s, AMDVI_MMIO_EVENT_TAIL);
|
||
s->evtlog_tail = val & AMDVI_MMIO_EVTLOG_TAIL_MASK;
|
||
}
|
||
|
||
static inline void amdvi_handle_evthead_write(AMDVIState *s)
|
||
{
|
||
uint64_t val = amdvi_readq(s, AMDVI_MMIO_EVENT_HEAD);
|
||
s->evtlog_head = val & AMDVI_MMIO_EVTLOG_HEAD_MASK;
|
||
}
|
||
|
||
static inline void amdvi_handle_pprbase_write(AMDVIState *s)
|
||
{
|
||
uint64_t val = amdvi_readq(s, AMDVI_MMIO_PPR_BASE);
|
||
s->ppr_log = val & AMDVI_MMIO_PPRLOG_BASE_MASK;
|
||
s->pprlog_len = 1UL << (amdvi_readq(s, AMDVI_MMIO_PPRLOG_SIZE_BYTE)
|
||
& AMDVI_MMIO_PPRLOG_SIZE_MASK);
|
||
}
|
||
|
||
static inline void amdvi_handle_pprhead_write(AMDVIState *s)
|
||
{
|
||
uint64_t val = amdvi_readq(s, AMDVI_MMIO_PPR_HEAD);
|
||
s->pprlog_head = val & AMDVI_MMIO_PPRLOG_HEAD_MASK;
|
||
}
|
||
|
||
static inline void amdvi_handle_pprtail_write(AMDVIState *s)
|
||
{
|
||
uint64_t val = amdvi_readq(s, AMDVI_MMIO_PPR_TAIL);
|
||
s->pprlog_tail = val & AMDVI_MMIO_PPRLOG_TAIL_MASK;
|
||
}
|
||
|
||
/* FIXME: something might go wrong if System Software writes in chunks
|
||
* of one byte but linux writes in chunks of 4 bytes so currently it
|
||
* works correctly with linux but will definitely be busted if software
|
||
* reads/writes 8 bytes
|
||
*/
|
||
static void amdvi_mmio_reg_write(AMDVIState *s, unsigned size, uint64_t val,
|
||
hwaddr addr)
|
||
{
|
||
if (size == 2) {
|
||
amdvi_writew(s, addr, val);
|
||
} else if (size == 4) {
|
||
amdvi_writel(s, addr, val);
|
||
} else if (size == 8) {
|
||
amdvi_writeq(s, addr, val);
|
||
}
|
||
}
|
||
|
||
static void amdvi_mmio_write(void *opaque, hwaddr addr, uint64_t val,
|
||
unsigned size)
|
||
{
|
||
AMDVIState *s = opaque;
|
||
unsigned long offset = addr & 0x07;
|
||
|
||
if (addr + size > AMDVI_MMIO_SIZE) {
|
||
trace_amdvi_mmio_write("error: addr outside region: max ",
|
||
(uint64_t)AMDVI_MMIO_SIZE, size, val, offset);
|
||
return;
|
||
}
|
||
|
||
trace_amdvi_mmio_write(amdvi_mmio_get_name(addr), addr, size, val, offset);
|
||
|
||
switch (addr & ~0x07) {
|
||
case AMDVI_MMIO_CONTROL:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_control_write(s);
|
||
break;
|
||
case AMDVI_MMIO_DEVICE_TABLE:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
/* set device table address
|
||
* This also suffers from inability to tell whether software
|
||
* is done writing
|
||
*/
|
||
if (offset || (size == 8)) {
|
||
amdvi_handle_devtab_write(s);
|
||
}
|
||
break;
|
||
case AMDVI_MMIO_COMMAND_HEAD:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_cmdhead_write(s);
|
||
break;
|
||
case AMDVI_MMIO_COMMAND_BASE:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
/* FIXME - make sure System Software has finished writing in case
|
||
* it writes in chucks less than 8 bytes in a robust way.As for
|
||
* now, this hacks works for the linux driver
|
||
*/
|
||
if (offset || (size == 8)) {
|
||
amdvi_handle_cmdbase_write(s);
|
||
}
|
||
break;
|
||
case AMDVI_MMIO_COMMAND_TAIL:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_cmdtail_write(s);
|
||
break;
|
||
case AMDVI_MMIO_EVENT_BASE:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_evtbase_write(s);
|
||
break;
|
||
case AMDVI_MMIO_EVENT_HEAD:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_evthead_write(s);
|
||
break;
|
||
case AMDVI_MMIO_EVENT_TAIL:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_evttail_write(s);
|
||
break;
|
||
case AMDVI_MMIO_EXCL_LIMIT:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_excllim_write(s);
|
||
break;
|
||
/* PPR log base - unused for now */
|
||
case AMDVI_MMIO_PPR_BASE:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_pprbase_write(s);
|
||
break;
|
||
/* PPR log head - also unused for now */
|
||
case AMDVI_MMIO_PPR_HEAD:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_pprhead_write(s);
|
||
break;
|
||
/* PPR log tail - unused for now */
|
||
case AMDVI_MMIO_PPR_TAIL:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
amdvi_handle_pprtail_write(s);
|
||
break;
|
||
case AMDVI_MMIO_STATUS:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
break;
|
||
case AMDVI_MMIO_XT_GEN_INTR:
|
||
amdvi_mmio_reg_write(s, size, val, addr);
|
||
break;
|
||
}
|
||
}
|
||
|
||
static void amdvi_page_walk(AMDVIAddressSpace *as, uint64_t *dte,
|
||
IOMMUTLBEntry *ret, unsigned perms,
|
||
hwaddr addr)
|
||
{
|
||
hwaddr page_mask, pagesize = 0;
|
||
uint8_t mode;
|
||
uint64_t pte;
|
||
int fetch_ret;
|
||
|
||
/* make sure the DTE has TV = 1 */
|
||
if (!(dte[0] & AMDVI_DEV_TRANSLATION_VALID)) {
|
||
/*
|
||
* A DTE with V=1, TV=0 does not have a valid Page Table Root Pointer.
|
||
* An IOMMU processing a request that requires a table walk terminates
|
||
* the walk when it encounters this condition. Do the same and return
|
||
* instead of assuming that the address is forwarded without translation
|
||
* i.e. the passthrough case, as it is done for the case where DTE[V]=0.
|
||
*/
|
||
return;
|
||
}
|
||
|
||
mode = get_pte_translation_mode(dte[0]);
|
||
if (mode >= 7) {
|
||
trace_amdvi_mode_invalid(mode, addr);
|
||
return;
|
||
}
|
||
if (mode == 0) {
|
||
goto no_remap;
|
||
}
|
||
|
||
/* Attempt to fetch the PTE to determine if a valid mapping exists */
|
||
fetch_ret = fetch_pte(as, addr, dte[0], &pte, &pagesize);
|
||
|
||
/*
|
||
* If walking the page table results in an error of any type, returns an
|
||
* empty PTE i.e. no mapping, or the permissions do not match, return since
|
||
* there is no translation available.
|
||
*/
|
||
if (fetch_ret < 0 || !IOMMU_PTE_PRESENT(pte) ||
|
||
perms != (perms & amdvi_get_perms(pte))) {
|
||
|
||
amdvi_page_fault(as->iommu_state, as->devfn, addr, perms);
|
||
trace_amdvi_page_fault(addr);
|
||
return;
|
||
}
|
||
|
||
/* A valid PTE and page size has been retrieved */
|
||
assert(pagesize);
|
||
page_mask = ~(pagesize - 1);
|
||
|
||
/* get access permissions from pte */
|
||
ret->iova = addr & page_mask;
|
||
ret->translated_addr = (pte & AMDVI_DEV_PT_ROOT_MASK) & page_mask;
|
||
ret->addr_mask = ~page_mask;
|
||
ret->perm = amdvi_get_perms(pte);
|
||
return;
|
||
|
||
no_remap:
|
||
ret->iova = addr & AMDVI_PAGE_MASK_4K;
|
||
ret->translated_addr = addr & AMDVI_PAGE_MASK_4K;
|
||
ret->addr_mask = ~AMDVI_PAGE_MASK_4K;
|
||
ret->perm = amdvi_get_perms(dte[0]);
|
||
}
|
||
|
||
static void amdvi_do_translate(AMDVIAddressSpace *as, hwaddr addr,
|
||
bool is_write, IOMMUTLBEntry *ret)
|
||
{
|
||
AMDVIState *s = as->iommu_state;
|
||
uint16_t devid = PCI_BUILD_BDF(pci_bus_num(as->bus), as->devfn);
|
||
AMDVIIOTLBEntry *iotlb_entry = amdvi_iotlb_lookup(s, addr, devid);
|
||
uint64_t entry[4];
|
||
int dte_ret;
|
||
|
||
if (iotlb_entry) {
|
||
trace_amdvi_iotlb_hit(PCI_BUS_NUM(devid), PCI_SLOT(devid),
|
||
PCI_FUNC(devid), addr, iotlb_entry->translated_addr);
|
||
ret->iova = addr & ~iotlb_entry->page_mask;
|
||
ret->translated_addr = iotlb_entry->translated_addr;
|
||
ret->addr_mask = iotlb_entry->page_mask;
|
||
ret->perm = iotlb_entry->perms;
|
||
return;
|
||
}
|
||
|
||
dte_ret = amdvi_as_to_dte(as, entry);
|
||
|
||
if (dte_ret < 0) {
|
||
if (dte_ret == -AMDVI_FR_DTE_V) {
|
||
/* DTE[V]=0, address is passed untranslated */
|
||
goto out;
|
||
}
|
||
return;
|
||
}
|
||
|
||
amdvi_page_walk(as, entry, ret,
|
||
is_write ? AMDVI_PERM_WRITE : AMDVI_PERM_READ, addr);
|
||
|
||
amdvi_update_iotlb(s, devid, addr, *ret,
|
||
entry[1] & AMDVI_DEV_DOMID_ID_MASK);
|
||
return;
|
||
|
||
out:
|
||
ret->iova = addr & AMDVI_PAGE_MASK_4K;
|
||
ret->translated_addr = addr & AMDVI_PAGE_MASK_4K;
|
||
ret->addr_mask = ~AMDVI_PAGE_MASK_4K;
|
||
ret->perm = IOMMU_RW;
|
||
}
|
||
|
||
static inline bool amdvi_is_interrupt_addr(hwaddr addr)
|
||
{
|
||
return addr >= AMDVI_INT_ADDR_FIRST && addr <= AMDVI_INT_ADDR_LAST;
|
||
}
|
||
|
||
static IOMMUTLBEntry amdvi_translate(IOMMUMemoryRegion *iommu, hwaddr addr,
|
||
IOMMUAccessFlags flag, int iommu_idx)
|
||
{
|
||
AMDVIAddressSpace *as = container_of(iommu, AMDVIAddressSpace, iommu);
|
||
AMDVIState *s = as->iommu_state;
|
||
IOMMUTLBEntry ret = {
|
||
.target_as = &address_space_memory,
|
||
.iova = addr,
|
||
.translated_addr = 0,
|
||
.addr_mask = ~(hwaddr)0,
|
||
.perm = IOMMU_NONE
|
||
};
|
||
|
||
if (!s->enabled) {
|
||
/* AMDVI disabled - corresponds to iommu=off not
|
||
* failure to provide any parameter
|
||
*/
|
||
ret.iova = addr & AMDVI_PAGE_MASK_4K;
|
||
ret.translated_addr = addr & AMDVI_PAGE_MASK_4K;
|
||
ret.addr_mask = ~AMDVI_PAGE_MASK_4K;
|
||
ret.perm = IOMMU_RW;
|
||
return ret;
|
||
} else if (amdvi_is_interrupt_addr(addr)) {
|
||
ret.iova = addr & AMDVI_PAGE_MASK_4K;
|
||
ret.translated_addr = addr & AMDVI_PAGE_MASK_4K;
|
||
ret.addr_mask = ~AMDVI_PAGE_MASK_4K;
|
||
ret.perm = IOMMU_WO;
|
||
return ret;
|
||
}
|
||
|
||
amdvi_do_translate(as, addr, flag & IOMMU_WO, &ret);
|
||
trace_amdvi_translation_result(pci_bus_num(as->bus), PCI_SLOT(as->devfn),
|
||
PCI_FUNC(as->devfn), addr, ret.translated_addr);
|
||
return ret;
|
||
}
|
||
|
||
static int amdvi_get_irte(AMDVIState *s, MSIMessage *origin, uint64_t *dte,
|
||
uint32_t *irte, uint16_t devid)
|
||
{
|
||
uint64_t irte_root, offset;
|
||
|
||
irte_root = dte[2] & AMDVI_IR_PHYS_ADDR_MASK;
|
||
offset = (origin->data & AMDVI_IRTE_OFFSET) << 2;
|
||
|
||
trace_amdvi_ir_irte(irte_root, offset);
|
||
|
||
if (dma_memory_read(&address_space_memory, irte_root + offset,
|
||
irte, sizeof(*irte), MEMTXATTRS_UNSPECIFIED)) {
|
||
trace_amdvi_ir_err("failed to get irte");
|
||
return -AMDVI_IR_GET_IRTE;
|
||
}
|
||
|
||
*irte = le32_to_cpu(*irte);
|
||
trace_amdvi_ir_irte_val(*irte);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int amdvi_int_remap_legacy(AMDVIState *iommu,
|
||
MSIMessage *origin,
|
||
MSIMessage *translated,
|
||
uint64_t *dte,
|
||
X86IOMMUIrq *irq,
|
||
uint16_t sid)
|
||
{
|
||
uint8_t int_type;
|
||
uint32_t irte;
|
||
int ret;
|
||
|
||
/* get interrupt remapping table */
|
||
ret = amdvi_get_irte(iommu, origin, dte, &irte, sid);
|
||
if (ret < 0) {
|
||
return ret;
|
||
}
|
||
|
||
if (!FIELD_EX32(irte, AMDVI_IRTE, VALID)) {
|
||
trace_amdvi_ir_target_abort("RemapEn is disabled");
|
||
return -AMDVI_IR_TARGET_ABORT;
|
||
}
|
||
|
||
if (FIELD_EX32(irte, AMDVI_IRTE, GUEST_MODE)) {
|
||
error_report_once("guest mode is not zero");
|
||
return -AMDVI_IR_ERR;
|
||
}
|
||
|
||
int_type = FIELD_EX32(irte, AMDVI_IRTE, INT_TYPE);
|
||
if (int_type > AMDVI_IOAPIC_INT_TYPE_ARBITRATED) {
|
||
error_report_once("reserved int_type");
|
||
return -AMDVI_IR_ERR;
|
||
}
|
||
|
||
irq->delivery_mode = int_type;
|
||
irq->vector = FIELD_EX32(irte, AMDVI_IRTE, VECTOR);
|
||
irq->dest_mode = FIELD_EX32(irte, AMDVI_IRTE, DM);
|
||
irq->redir_hint = FIELD_EX32(irte, AMDVI_IRTE, RQ_EOI);
|
||
irq->dest = FIELD_EX32(irte, AMDVI_IRTE, DESTINATION);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int amdvi_get_irte_ga(AMDVIState *s, MSIMessage *origin, uint64_t *dte,
|
||
AMDVIIrteGA *irte, uint16_t devid)
|
||
{
|
||
uint64_t irte_root, offset;
|
||
|
||
irte_root = dte[2] & AMDVI_IR_PHYS_ADDR_MASK;
|
||
offset = (origin->data & AMDVI_IRTE_OFFSET) << 4;
|
||
trace_amdvi_ir_irte(irte_root, offset);
|
||
|
||
if (dma_memory_read(&address_space_memory, irte_root + offset,
|
||
irte, sizeof(*irte), MEMTXATTRS_UNSPECIFIED)) {
|
||
trace_amdvi_ir_err("failed to get irte_ga");
|
||
return -AMDVI_IR_GET_IRTE;
|
||
}
|
||
|
||
irte->ga_lo = le64_to_cpu(irte->ga_lo);
|
||
irte->ga_hi = le64_to_cpu(irte->ga_hi);
|
||
trace_amdvi_ir_irte_ga_val(irte->ga_hi, irte->ga_lo);
|
||
return 0;
|
||
}
|
||
|
||
static int amdvi_int_remap_ga(AMDVIState *iommu,
|
||
MSIMessage *origin,
|
||
MSIMessage *translated,
|
||
uint64_t *dte,
|
||
X86IOMMUIrq *irq,
|
||
uint16_t sid)
|
||
{
|
||
AMDVIIrteGA irte;
|
||
uint8_t int_type;
|
||
int ret;
|
||
|
||
/* get interrupt remapping table */
|
||
ret = amdvi_get_irte_ga(iommu, origin, dte, &irte, sid);
|
||
if (ret < 0) {
|
||
return ret;
|
||
}
|
||
|
||
if (!FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, VALID)) {
|
||
trace_amdvi_ir_target_abort("RemapEn is disabled");
|
||
return -AMDVI_IR_TARGET_ABORT;
|
||
}
|
||
|
||
if (FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, GUEST_MODE)) {
|
||
error_report_once("guest mode is not zero");
|
||
return -AMDVI_IR_ERR;
|
||
}
|
||
|
||
int_type = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, INT_TYPE);
|
||
if (int_type > AMDVI_IOAPIC_INT_TYPE_ARBITRATED) {
|
||
error_report_once("reserved int_type is set");
|
||
return -AMDVI_IR_ERR;
|
||
}
|
||
|
||
irq->delivery_mode = int_type;
|
||
irq->vector = FIELD_EX64(irte.ga_hi, AMDVI_IRTE_GA_HI, VECTOR);
|
||
irq->dest_mode = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, DM);
|
||
irq->redir_hint = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, RQ_EOI);
|
||
if (iommu->xten) {
|
||
irq->dest = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, DESTINATION) |
|
||
(FIELD_EX64(irte.ga_hi, AMDVI_IRTE_GA_HI, DESTINATION_HI)
|
||
<< 24);
|
||
} else {
|
||
irq->dest = FIELD_EX64(irte.ga_lo, AMDVI_IRTE_GA_LO, DESTINATION) &
|
||
0xff;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int __amdvi_int_remap_msi(AMDVIState *iommu,
|
||
MSIMessage *origin,
|
||
MSIMessage *translated,
|
||
uint64_t *dte,
|
||
X86IOMMUIrq *irq,
|
||
uint16_t sid)
|
||
{
|
||
int ret;
|
||
uint8_t int_ctl;
|
||
|
||
int_ctl = (dte[2] >> AMDVI_IR_INTCTL_SHIFT) & 3;
|
||
trace_amdvi_ir_intctl(int_ctl);
|
||
|
||
switch (int_ctl) {
|
||
case AMDVI_IR_INTCTL_PASS:
|
||
memcpy(translated, origin, sizeof(*origin));
|
||
return 0;
|
||
case AMDVI_IR_INTCTL_REMAP:
|
||
break;
|
||
case AMDVI_IR_INTCTL_ABORT:
|
||
trace_amdvi_ir_target_abort("int_ctl abort");
|
||
return -AMDVI_IR_TARGET_ABORT;
|
||
default:
|
||
trace_amdvi_ir_err("int_ctl reserved");
|
||
return -AMDVI_IR_ERR;
|
||
}
|
||
|
||
if (iommu->ga_enabled) {
|
||
ret = amdvi_int_remap_ga(iommu, origin, translated, dte, irq, sid);
|
||
} else {
|
||
ret = amdvi_int_remap_legacy(iommu, origin, translated, dte, irq, sid);
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
/* Interrupt remapping for MSI/MSI-X entry */
|
||
static int amdvi_int_remap_msi(AMDVIState *iommu,
|
||
MSIMessage *origin,
|
||
MSIMessage *translated,
|
||
uint16_t sid)
|
||
{
|
||
int ret = 0;
|
||
uint64_t pass = 0;
|
||
uint64_t dte[4] = { 0 };
|
||
X86IOMMUIrq irq = { 0 };
|
||
uint8_t dest_mode, delivery_mode;
|
||
|
||
assert(origin && translated);
|
||
|
||
/*
|
||
* When IOMMU is enabled, interrupt remap request will come either from
|
||
* IO-APIC or PCI device. If interrupt is from PCI device then it will
|
||
* have a valid requester id but if the interrupt is from IO-APIC
|
||
* then requester id will be invalid.
|
||
*/
|
||
if (sid == X86_IOMMU_SID_INVALID) {
|
||
sid = AMDVI_IOAPIC_SB_DEVID;
|
||
}
|
||
|
||
trace_amdvi_ir_remap_msi_req(origin->address, origin->data, sid);
|
||
|
||
/* check if device table entry is set before we go further. */
|
||
if (!iommu || !iommu->devtab_len) {
|
||
memcpy(translated, origin, sizeof(*origin));
|
||
goto out;
|
||
}
|
||
|
||
if (!amdvi_get_dte(iommu, sid, dte)) {
|
||
return -AMDVI_IR_ERR;
|
||
}
|
||
|
||
/* Check if IR is enabled in DTE */
|
||
if (!(dte[2] & AMDVI_IR_REMAP_ENABLE)) {
|
||
memcpy(translated, origin, sizeof(*origin));
|
||
goto out;
|
||
}
|
||
|
||
/* validate that we are configure with intremap=on */
|
||
if (!x86_iommu_ir_supported(X86_IOMMU_DEVICE(iommu))) {
|
||
trace_amdvi_err("Interrupt remapping is enabled in the guest but "
|
||
"not in the host. Use intremap=on to enable interrupt "
|
||
"remapping in amd-iommu.");
|
||
return -AMDVI_IR_ERR;
|
||
}
|
||
|
||
if (origin->address < AMDVI_INT_ADDR_FIRST ||
|
||
origin->address + sizeof(origin->data) > AMDVI_INT_ADDR_LAST + 1) {
|
||
trace_amdvi_err("MSI is not from IOAPIC.");
|
||
return -AMDVI_IR_ERR;
|
||
}
|
||
|
||
/*
|
||
* The MSI data register [10:8] are used to get the upstream interrupt type.
|
||
*
|
||
* See MSI/MSI-X format:
|
||
* https://pdfs.semanticscholar.org/presentation/9420/c279e942eca568157711ef5c92b800c40a79.pdf
|
||
* (page 5)
|
||
*/
|
||
delivery_mode = (origin->data >> MSI_DATA_DELIVERY_MODE_SHIFT) & 7;
|
||
|
||
switch (delivery_mode) {
|
||
case AMDVI_IOAPIC_INT_TYPE_FIXED:
|
||
case AMDVI_IOAPIC_INT_TYPE_ARBITRATED:
|
||
trace_amdvi_ir_delivery_mode("fixed/arbitrated");
|
||
ret = __amdvi_int_remap_msi(iommu, origin, translated, dte, &irq, sid);
|
||
if (ret < 0) {
|
||
goto remap_fail;
|
||
} else {
|
||
/* Translate IRQ to MSI messages */
|
||
x86_iommu_irq_to_msi_message(&irq, translated);
|
||
goto out;
|
||
}
|
||
break;
|
||
case AMDVI_IOAPIC_INT_TYPE_SMI:
|
||
error_report("SMI is not supported!");
|
||
ret = -AMDVI_IR_ERR;
|
||
break;
|
||
case AMDVI_IOAPIC_INT_TYPE_NMI:
|
||
pass = dte[2] & AMDVI_DEV_NMI_PASS_MASK;
|
||
trace_amdvi_ir_delivery_mode("nmi");
|
||
break;
|
||
case AMDVI_IOAPIC_INT_TYPE_INIT:
|
||
pass = dte[2] & AMDVI_DEV_INT_PASS_MASK;
|
||
trace_amdvi_ir_delivery_mode("init");
|
||
break;
|
||
case AMDVI_IOAPIC_INT_TYPE_EINT:
|
||
pass = dte[2] & AMDVI_DEV_EINT_PASS_MASK;
|
||
trace_amdvi_ir_delivery_mode("eint");
|
||
break;
|
||
default:
|
||
trace_amdvi_ir_delivery_mode("unsupported delivery_mode");
|
||
ret = -AMDVI_IR_ERR;
|
||
break;
|
||
}
|
||
|
||
if (ret < 0) {
|
||
goto remap_fail;
|
||
}
|
||
|
||
/*
|
||
* The MSI address register bit[2] is used to get the destination
|
||
* mode. The dest_mode 1 is valid for fixed and arbitrated interrupts
|
||
* only.
|
||
*/
|
||
dest_mode = (origin->address >> MSI_ADDR_DEST_MODE_SHIFT) & 1;
|
||
if (dest_mode) {
|
||
trace_amdvi_ir_err("invalid dest_mode");
|
||
ret = -AMDVI_IR_ERR;
|
||
goto remap_fail;
|
||
}
|
||
|
||
if (pass) {
|
||
memcpy(translated, origin, sizeof(*origin));
|
||
} else {
|
||
trace_amdvi_ir_err("passthrough is not enabled");
|
||
ret = -AMDVI_IR_ERR;
|
||
goto remap_fail;
|
||
}
|
||
|
||
out:
|
||
trace_amdvi_ir_remap_msi(origin->address, origin->data,
|
||
translated->address, translated->data);
|
||
return 0;
|
||
|
||
remap_fail:
|
||
return ret;
|
||
}
|
||
|
||
static int amdvi_int_remap(X86IOMMUState *iommu,
|
||
MSIMessage *origin,
|
||
MSIMessage *translated,
|
||
uint16_t sid)
|
||
{
|
||
return amdvi_int_remap_msi(AMD_IOMMU_DEVICE(iommu), origin,
|
||
translated, sid);
|
||
}
|
||
|
||
static MemTxResult amdvi_mem_ir_write(void *opaque, hwaddr addr,
|
||
uint64_t value, unsigned size,
|
||
MemTxAttrs attrs)
|
||
{
|
||
int ret;
|
||
MSIMessage from = { 0, 0 }, to = { 0, 0 };
|
||
uint16_t sid = AMDVI_IOAPIC_SB_DEVID;
|
||
|
||
from.address = (uint64_t) addr + AMDVI_INT_ADDR_FIRST;
|
||
from.data = (uint32_t) value;
|
||
|
||
trace_amdvi_mem_ir_write_req(addr, value, size);
|
||
|
||
if (!attrs.unspecified) {
|
||
/* We have explicit Source ID */
|
||
sid = attrs.requester_id;
|
||
}
|
||
|
||
ret = amdvi_int_remap_msi(opaque, &from, &to, sid);
|
||
if (ret < 0) {
|
||
/* TODO: log the event using IOMMU log event interface */
|
||
error_report_once("failed to remap interrupt from devid 0x%x", sid);
|
||
return MEMTX_ERROR;
|
||
}
|
||
|
||
apic_get_class(NULL)->send_msi(&to);
|
||
|
||
trace_amdvi_mem_ir_write(to.address, to.data);
|
||
return MEMTX_OK;
|
||
}
|
||
|
||
static MemTxResult amdvi_mem_ir_read(void *opaque, hwaddr addr,
|
||
uint64_t *data, unsigned size,
|
||
MemTxAttrs attrs)
|
||
{
|
||
return MEMTX_OK;
|
||
}
|
||
|
||
static const MemoryRegionOps amdvi_ir_ops = {
|
||
.read_with_attrs = amdvi_mem_ir_read,
|
||
.write_with_attrs = amdvi_mem_ir_write,
|
||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||
.impl = {
|
||
.min_access_size = 4,
|
||
.max_access_size = 4,
|
||
},
|
||
.valid = {
|
||
.min_access_size = 4,
|
||
.max_access_size = 4,
|
||
}
|
||
};
|
||
|
||
static AddressSpace *amdvi_host_dma_iommu(PCIBus *bus, void *opaque, int devfn)
|
||
{
|
||
char name[128];
|
||
AMDVIState *s = opaque;
|
||
AMDVIAddressSpace *amdvi_dev_as;
|
||
AMDVIAsKey *key;
|
||
|
||
amdvi_dev_as = amdvi_as_lookup(s, bus, devfn);
|
||
|
||
/* allocate memory during the first run */
|
||
if (!amdvi_dev_as) {
|
||
snprintf(name, sizeof(name), "amd_iommu_devfn_%d", devfn);
|
||
|
||
amdvi_dev_as = g_new0(AMDVIAddressSpace, 1);
|
||
key = g_new0(AMDVIAsKey, 1);
|
||
|
||
amdvi_dev_as->bus = bus;
|
||
amdvi_dev_as->devfn = (uint8_t)devfn;
|
||
amdvi_dev_as->iommu_state = s;
|
||
amdvi_dev_as->notifier_flags = IOMMU_NOTIFIER_NONE;
|
||
amdvi_dev_as->iova_tree = iova_tree_new();
|
||
amdvi_dev_as->addr_translation = false;
|
||
key->bus = bus;
|
||
key->devfn = devfn;
|
||
|
||
g_hash_table_insert(s->address_spaces, key, amdvi_dev_as);
|
||
|
||
/*
|
||
* Memory region relationships looks like (Address range shows
|
||
* only lower 32 bits to make it short in length...):
|
||
*
|
||
* |--------------------+-------------------+----------|
|
||
* | Name | Address range | Priority |
|
||
* |--------------------+-------------------+----------+
|
||
* | amdvi-root | 00000000-ffffffff | 0 |
|
||
* | amdvi-iommu_nodma | 00000000-ffffffff | 0 |
|
||
* | amdvi-iommu_ir | fee00000-feefffff | 1 |
|
||
* |--------------------+-------------------+----------|
|
||
*/
|
||
memory_region_init_iommu(&amdvi_dev_as->iommu,
|
||
sizeof(amdvi_dev_as->iommu),
|
||
TYPE_AMD_IOMMU_MEMORY_REGION,
|
||
OBJECT(s),
|
||
"amd_iommu", UINT64_MAX);
|
||
memory_region_init(&amdvi_dev_as->root, OBJECT(s),
|
||
"amdvi_root", UINT64_MAX);
|
||
address_space_init(&amdvi_dev_as->as, &amdvi_dev_as->root, name);
|
||
memory_region_add_subregion_overlap(&amdvi_dev_as->root, 0,
|
||
MEMORY_REGION(&amdvi_dev_as->iommu),
|
||
0);
|
||
|
||
/* Build the DMA Disabled alias to shared memory */
|
||
memory_region_init_alias(&amdvi_dev_as->iommu_nodma, OBJECT(s),
|
||
"amdvi-sys", &s->mr_sys, 0,
|
||
memory_region_size(&s->mr_sys));
|
||
memory_region_add_subregion_overlap(&amdvi_dev_as->root, 0,
|
||
&amdvi_dev_as->iommu_nodma,
|
||
0);
|
||
/* Build the Interrupt Remapping alias to shared memory */
|
||
memory_region_init_alias(&amdvi_dev_as->iommu_ir, OBJECT(s),
|
||
"amdvi-ir", &s->mr_ir, 0,
|
||
memory_region_size(&s->mr_ir));
|
||
memory_region_add_subregion_overlap(MEMORY_REGION(&amdvi_dev_as->iommu),
|
||
AMDVI_INT_ADDR_FIRST,
|
||
&amdvi_dev_as->iommu_ir, 1);
|
||
|
||
amdvi_switch_address_space(amdvi_dev_as);
|
||
}
|
||
return &amdvi_dev_as->as;
|
||
}
|
||
|
||
static const PCIIOMMUOps amdvi_iommu_ops = {
|
||
.get_address_space = amdvi_host_dma_iommu,
|
||
};
|
||
|
||
static const MemoryRegionOps mmio_mem_ops = {
|
||
.read = amdvi_mmio_read,
|
||
.write = amdvi_mmio_write,
|
||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||
.impl = {
|
||
.min_access_size = 1,
|
||
.max_access_size = 8,
|
||
.unaligned = false,
|
||
},
|
||
.valid = {
|
||
.min_access_size = 1,
|
||
.max_access_size = 8,
|
||
}
|
||
};
|
||
|
||
static int amdvi_iommu_notify_flag_changed(IOMMUMemoryRegion *iommu,
|
||
IOMMUNotifierFlag old,
|
||
IOMMUNotifierFlag new,
|
||
Error **errp)
|
||
{
|
||
AMDVIAddressSpace *as = container_of(iommu, AMDVIAddressSpace, iommu);
|
||
AMDVIState *s = as->iommu_state;
|
||
|
||
/*
|
||
* Accurate synchronization of the vIOMMU page tables required to support
|
||
* MAP notifiers is provided by the dma-remap feature. In addition, this
|
||
* also requires that the vIOMMU presents the NpCache capability, so a guest
|
||
* driver issues invalidations for both map() and unmap() operations. The
|
||
* capability is already set by default as part of AMDVI_CAPAB_FEATURES and
|
||
* written to the configuration in amdvi_pci_realize().
|
||
*/
|
||
if (!s->dma_remap && (new & IOMMU_NOTIFIER_MAP)) {
|
||
error_setg_errno(errp, ENOTSUP,
|
||
"device %02x.%02x.%x requires dma-remap=1",
|
||
pci_bus_num(as->bus), PCI_SLOT(as->devfn), PCI_FUNC(as->devfn));
|
||
return -ENOTSUP;
|
||
}
|
||
|
||
/*
|
||
* Update notifier flags for address space and the list of address spaces
|
||
* with registered notifiers.
|
||
*/
|
||
as->notifier_flags = new;
|
||
|
||
if (old == IOMMU_NOTIFIER_NONE) {
|
||
QLIST_INSERT_HEAD(&s->amdvi_as_with_notifiers, as, next);
|
||
} else if (new == IOMMU_NOTIFIER_NONE) {
|
||
QLIST_REMOVE(as, next);
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void amdvi_init(AMDVIState *s)
|
||
{
|
||
amdvi_iotlb_reset(s);
|
||
|
||
s->devtab_len = 0;
|
||
s->cmdbuf_len = 0;
|
||
s->cmdbuf_head = 0;
|
||
s->cmdbuf_tail = 0;
|
||
s->evtlog_head = 0;
|
||
s->evtlog_tail = 0;
|
||
s->excl_enabled = false;
|
||
s->excl_allow = false;
|
||
s->mmio_enabled = false;
|
||
s->enabled = false;
|
||
s->cmdbuf_enabled = false;
|
||
s->xten = false;
|
||
s->intcapxten = false;
|
||
|
||
/* reset MMIO */
|
||
memset(s->mmior, 0, AMDVI_MMIO_SIZE);
|
||
amdvi_set_quad(s, AMDVI_MMIO_EXT_FEATURES,
|
||
amdvi_extended_feature_register(s),
|
||
0xffffffffffffffef, 0);
|
||
amdvi_set_quad(s, AMDVI_MMIO_STATUS, 0, 0x98, 0x67);
|
||
}
|
||
|
||
static void amdvi_pci_realize(PCIDevice *pdev, Error **errp)
|
||
{
|
||
AMDVIPCIState *s = AMD_IOMMU_PCI(pdev);
|
||
int ret;
|
||
|
||
ret = pci_add_capability(pdev, AMDVI_CAPAB_ID_SEC, 0,
|
||
AMDVI_CAPAB_SIZE, errp);
|
||
if (ret < 0) {
|
||
return;
|
||
}
|
||
s->capab_offset = ret;
|
||
|
||
ret = pci_add_capability(pdev, PCI_CAP_ID_MSI, 0,
|
||
AMDVI_CAPAB_REG_SIZE, errp);
|
||
if (ret < 0) {
|
||
return;
|
||
}
|
||
ret = pci_add_capability(pdev, PCI_CAP_ID_HT, 0,
|
||
AMDVI_CAPAB_REG_SIZE, errp);
|
||
if (ret < 0) {
|
||
return;
|
||
}
|
||
|
||
if (msi_init(pdev, 0, 1, true, false, errp) < 0) {
|
||
return;
|
||
}
|
||
|
||
/* reset device ident */
|
||
pci_config_set_prog_interface(pdev->config, 0);
|
||
|
||
/* reset AMDVI specific capabilities, all r/o */
|
||
pci_set_long(pdev->config + s->capab_offset, AMDVI_CAPAB_FEATURES);
|
||
pci_set_long(pdev->config + s->capab_offset + AMDVI_CAPAB_BAR_LOW,
|
||
AMDVI_BASE_ADDR & MAKE_64BIT_MASK(14, 18));
|
||
pci_set_long(pdev->config + s->capab_offset + AMDVI_CAPAB_BAR_HIGH,
|
||
AMDVI_BASE_ADDR >> 32);
|
||
pci_set_long(pdev->config + s->capab_offset + AMDVI_CAPAB_RANGE,
|
||
0xff000000);
|
||
pci_set_long(pdev->config + s->capab_offset + AMDVI_CAPAB_MISC, 0);
|
||
pci_set_long(pdev->config + s->capab_offset + AMDVI_CAPAB_MISC,
|
||
AMDVI_MAX_PH_ADDR | AMDVI_MAX_GVA_ADDR | AMDVI_MAX_VA_ADDR);
|
||
}
|
||
|
||
static void amdvi_sysbus_reset(DeviceState *dev)
|
||
{
|
||
AMDVIState *s = AMD_IOMMU_DEVICE(dev);
|
||
|
||
msi_reset(&s->pci->dev);
|
||
amdvi_init(s);
|
||
|
||
/* Discard all mappings on device reset */
|
||
amdvi_address_space_unmap_all(s);
|
||
amdvi_reset_address_translation_all(s);
|
||
}
|
||
|
||
static const VMStateDescription vmstate_xt = {
|
||
.name = "amd-iommu-xt",
|
||
.version_id = 1,
|
||
.minimum_version_id = 1,
|
||
.fields = (VMStateField[]) {
|
||
VMSTATE_BOOL(xten, AMDVIState),
|
||
VMSTATE_BOOL(intcapxten, AMDVIState),
|
||
VMSTATE_END_OF_LIST()
|
||
}
|
||
};
|
||
|
||
static const VMStateDescription vmstate_amdvi_sysbus_migratable = {
|
||
.name = "amd-iommu",
|
||
.version_id = 1,
|
||
.minimum_version_id = 1,
|
||
.priority = MIG_PRI_IOMMU,
|
||
.fields = (VMStateField[]) {
|
||
/* Updated in amdvi_handle_control_write() */
|
||
VMSTATE_BOOL(enabled, AMDVIState),
|
||
VMSTATE_BOOL(ga_enabled, AMDVIState),
|
||
/* bool ats_enabled is obsolete */
|
||
VMSTATE_UNUSED(1), /* was ats_enabled */
|
||
VMSTATE_BOOL(cmdbuf_enabled, AMDVIState),
|
||
VMSTATE_BOOL(completion_wait_intr, AMDVIState),
|
||
VMSTATE_BOOL(evtlog_enabled, AMDVIState),
|
||
VMSTATE_BOOL(evtlog_intr, AMDVIState),
|
||
/* Updated in amdvi_handle_devtab_write() */
|
||
VMSTATE_UINT64(devtab, AMDVIState),
|
||
VMSTATE_UINT64(devtab_len, AMDVIState),
|
||
/* Updated in amdvi_handle_cmdbase_write() */
|
||
VMSTATE_UINT64(cmdbuf, AMDVIState),
|
||
VMSTATE_UINT64(cmdbuf_len, AMDVIState),
|
||
/* Updated in amdvi_handle_cmdhead_write() */
|
||
VMSTATE_UINT32(cmdbuf_head, AMDVIState),
|
||
/* Updated in amdvi_handle_cmdtail_write() */
|
||
VMSTATE_UINT32(cmdbuf_tail, AMDVIState),
|
||
/* Updated in amdvi_handle_evtbase_write() */
|
||
VMSTATE_UINT64(evtlog, AMDVIState),
|
||
VMSTATE_UINT32(evtlog_len, AMDVIState),
|
||
/* Updated in amdvi_handle_evthead_write() */
|
||
VMSTATE_UINT32(evtlog_head, AMDVIState),
|
||
/* Updated in amdvi_handle_evttail_write() */
|
||
VMSTATE_UINT32(evtlog_tail, AMDVIState),
|
||
/* Updated in amdvi_handle_pprbase_write() */
|
||
VMSTATE_UINT64(ppr_log, AMDVIState),
|
||
VMSTATE_UINT32(pprlog_len, AMDVIState),
|
||
/* Updated in amdvi_handle_pprhead_write() */
|
||
VMSTATE_UINT32(pprlog_head, AMDVIState),
|
||
/* Updated in amdvi_handle_tailhead_write() */
|
||
VMSTATE_UINT32(pprlog_tail, AMDVIState),
|
||
/* MMIO registers */
|
||
VMSTATE_UINT8_ARRAY(mmior, AMDVIState, AMDVI_MMIO_SIZE),
|
||
VMSTATE_UINT8_ARRAY(romask, AMDVIState, AMDVI_MMIO_SIZE),
|
||
VMSTATE_UINT8_ARRAY(w1cmask, AMDVIState, AMDVI_MMIO_SIZE),
|
||
VMSTATE_END_OF_LIST()
|
||
},
|
||
.subsections = (const VMStateDescription *const []) {
|
||
&vmstate_xt,
|
||
NULL
|
||
}
|
||
};
|
||
|
||
static void amdvi_sysbus_realize(DeviceState *dev, Error **errp)
|
||
{
|
||
DeviceClass *dc = (DeviceClass *) object_get_class(OBJECT(dev));
|
||
AMDVIState *s = AMD_IOMMU_DEVICE(dev);
|
||
MachineState *ms = MACHINE(qdev_get_machine());
|
||
PCMachineState *pcms = PC_MACHINE(ms);
|
||
X86MachineState *x86ms = X86_MACHINE(ms);
|
||
PCIBus *bus = pcms->pcibus;
|
||
|
||
if (s->pci_id) {
|
||
PCIDevice *pdev = NULL;
|
||
int ret = pci_qdev_find_device(s->pci_id, &pdev);
|
||
|
||
if (ret) {
|
||
error_report("Cannot find PCI device '%s'", s->pci_id);
|
||
return;
|
||
}
|
||
|
||
if (!object_dynamic_cast(OBJECT(pdev), TYPE_AMD_IOMMU_PCI)) {
|
||
error_report("Device '%s' must be an AMDVI-PCI device type", s->pci_id);
|
||
return;
|
||
}
|
||
|
||
s->pci = AMD_IOMMU_PCI(pdev);
|
||
dc->vmsd = &vmstate_amdvi_sysbus_migratable;
|
||
} else {
|
||
s->pci = AMD_IOMMU_PCI(object_new(TYPE_AMD_IOMMU_PCI));
|
||
/* This device should take care of IOMMU PCI properties */
|
||
if (!qdev_realize(DEVICE(s->pci), &bus->qbus, errp)) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
s->iotlb = g_hash_table_new_full(amdvi_iotlb_hash,
|
||
amdvi_iotlb_equal, g_free, g_free);
|
||
|
||
s->address_spaces = g_hash_table_new_full(amdvi_as_hash,
|
||
amdvi_as_equal, g_free, g_free);
|
||
|
||
/* set up MMIO */
|
||
memory_region_init_io(&s->mr_mmio, OBJECT(s), &mmio_mem_ops, s,
|
||
"amdvi-mmio", AMDVI_MMIO_SIZE);
|
||
memory_region_add_subregion(get_system_memory(), AMDVI_BASE_ADDR,
|
||
&s->mr_mmio);
|
||
|
||
/* Create the share memory regions by all devices */
|
||
memory_region_init(&s->mr_sys, OBJECT(s), "amdvi-sys", UINT64_MAX);
|
||
|
||
/* set up the DMA disabled memory region */
|
||
memory_region_init_alias(&s->mr_nodma, OBJECT(s),
|
||
"amdvi-nodma", get_system_memory(), 0,
|
||
memory_region_size(get_system_memory()));
|
||
memory_region_add_subregion_overlap(&s->mr_sys, 0,
|
||
&s->mr_nodma, 0);
|
||
|
||
/* set up the Interrupt Remapping memory region */
|
||
memory_region_init_io(&s->mr_ir, OBJECT(s), &amdvi_ir_ops,
|
||
s, "amdvi-ir", AMDVI_INT_ADDR_SIZE);
|
||
memory_region_add_subregion_overlap(&s->mr_sys, AMDVI_INT_ADDR_FIRST,
|
||
&s->mr_ir, 1);
|
||
|
||
/* Pseudo address space under root PCI bus. */
|
||
x86ms->ioapic_as = amdvi_host_dma_iommu(bus, s, AMDVI_IOAPIC_SB_DEVID);
|
||
|
||
if (kvm_enabled() && x86ms->apic_id_limit > 255 && !s->xtsup) {
|
||
error_report("AMD IOMMU with x2APIC configuration requires xtsup=on");
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
|
||
if (s->xtsup) {
|
||
if (kvm_irqchip_is_split() && !kvm_enable_x2apic()) {
|
||
error_report("AMD IOMMU xtsup=on requires x2APIC support on "
|
||
"the KVM side");
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
}
|
||
|
||
pci_setup_iommu(bus, &amdvi_iommu_ops, s);
|
||
amdvi_init(s);
|
||
}
|
||
|
||
static const Property amdvi_properties[] = {
|
||
DEFINE_PROP_BOOL("xtsup", AMDVIState, xtsup, false),
|
||
DEFINE_PROP_STRING("pci-id", AMDVIState, pci_id),
|
||
DEFINE_PROP_BOOL("dma-remap", AMDVIState, dma_remap, false),
|
||
};
|
||
|
||
static const VMStateDescription vmstate_amdvi_sysbus = {
|
||
.name = "amd-iommu",
|
||
.unmigratable = 1
|
||
};
|
||
|
||
static void amdvi_sysbus_class_init(ObjectClass *klass, const void *data)
|
||
{
|
||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||
X86IOMMUClass *dc_class = X86_IOMMU_DEVICE_CLASS(klass);
|
||
|
||
device_class_set_legacy_reset(dc, amdvi_sysbus_reset);
|
||
dc->vmsd = &vmstate_amdvi_sysbus;
|
||
dc->hotpluggable = false;
|
||
dc_class->realize = amdvi_sysbus_realize;
|
||
dc_class->int_remap = amdvi_int_remap;
|
||
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
||
dc->desc = "AMD IOMMU (AMD-Vi) DMA Remapping device";
|
||
device_class_set_props(dc, amdvi_properties);
|
||
}
|
||
|
||
static const TypeInfo amdvi_sysbus = {
|
||
.name = TYPE_AMD_IOMMU_DEVICE,
|
||
.parent = TYPE_X86_IOMMU_DEVICE,
|
||
.instance_size = sizeof(AMDVIState),
|
||
.class_init = amdvi_sysbus_class_init
|
||
};
|
||
|
||
static void amdvi_pci_class_init(ObjectClass *klass, const void *data)
|
||
{
|
||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||
PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
|
||
|
||
k->vendor_id = PCI_VENDOR_ID_AMD;
|
||
k->device_id = 0x1419;
|
||
k->class_id = 0x0806;
|
||
k->realize = amdvi_pci_realize;
|
||
|
||
set_bit(DEVICE_CATEGORY_MISC, dc->categories);
|
||
dc->desc = "AMD IOMMU (AMD-Vi) DMA Remapping device";
|
||
}
|
||
|
||
static const TypeInfo amdvi_pci = {
|
||
.name = TYPE_AMD_IOMMU_PCI,
|
||
.parent = TYPE_PCI_DEVICE,
|
||
.instance_size = sizeof(AMDVIPCIState),
|
||
.class_init = amdvi_pci_class_init,
|
||
.interfaces = (const InterfaceInfo[]) {
|
||
{ INTERFACE_CONVENTIONAL_PCI_DEVICE },
|
||
{ },
|
||
},
|
||
};
|
||
|
||
static void amdvi_iommu_memory_region_class_init(ObjectClass *klass,
|
||
const void *data)
|
||
{
|
||
IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass);
|
||
|
||
imrc->translate = amdvi_translate;
|
||
imrc->notify_flag_changed = amdvi_iommu_notify_flag_changed;
|
||
imrc->replay = amdvi_iommu_replay;
|
||
}
|
||
|
||
static const TypeInfo amdvi_iommu_memory_region_info = {
|
||
.parent = TYPE_IOMMU_MEMORY_REGION,
|
||
.name = TYPE_AMD_IOMMU_MEMORY_REGION,
|
||
.class_init = amdvi_iommu_memory_region_class_init,
|
||
};
|
||
|
||
static void amdvi_register_types(void)
|
||
{
|
||
type_register_static(&amdvi_pci);
|
||
type_register_static(&amdvi_sysbus);
|
||
type_register_static(&amdvi_iommu_memory_region_info);
|
||
}
|
||
|
||
type_init(amdvi_register_types);
|