#include <asm/iocap.h>
#include <asm/vm_event.h>
+struct hvmemul_cache
+{
+ /* The cache is disabled as long as num_ents > max_ents. */
+ unsigned int num_ents;
+ unsigned int max_ents;
+ struct {
+ paddr_t gpa:PADDR_BITS;
+ unsigned int :BITS_PER_LONG - PADDR_BITS - 8;
+ unsigned int size:8;
+ unsigned long data;
+ } ents[];
+};
+
static void hvmtrace_io_assist(const ioreq_t *p)
{
unsigned int size, event;
vio->mmio_access = (struct npfec){};
vio->mmio_retry = false;
vio->g2m_ioport = NULL;
+
+ hvmemul_cache_disable(v);
}
static int hvmemul_do_io(
rc = HVMTRANS_okay;
}
else
+ {
+ unsigned int token = hvmemul_cache_disable(curr);
+
/*
* We do a modicum of checking here, just for paranoia's sake and to
* definitely avoid copying an unitialised buffer into guest address
* space.
*/
rc = hvm_copy_from_guest_phys(buf, sgpa, bytes);
+ hvmemul_cache_restore(curr, token);
+ }
if ( rc == HVMTRANS_okay )
rc = hvm_copy_to_guest_phys(dgpa, buf, bytes, curr);
struct hvm_vcpu_io *vio = &curr->arch.hvm.hvm_io;
int rc;
+ /*
+ * Enable caching if it's currently disabled, but leave the cache
+ * untouched if it's already enabled, for re-execution to consume
+ * entries populated by an earlier pass.
+ */
+ if ( vio->cache->num_ents > vio->cache->max_ents )
+ {
+ ASSERT(vio->io_req.state == STATE_IOREQ_NONE);
+ vio->cache->num_ents = 0;
+ }
+ else
+ ASSERT(vio->io_req.state == STATE_IORESP_READY);
+
hvm_emulate_init_per_insn(hvmemul_ctxt, vio->mmio_insn,
vio->mmio_insn_bytes);
{
vio->mmio_cache_count = 0;
vio->mmio_insn_bytes = 0;
+ hvmemul_cache_disable(curr);
}
else
{
hvmemul_ctxt->insn_buf);
}
+int hvmemul_cache_init(struct vcpu *v)
+{
+ /*
+ * No insn can access more than 16 independent linear addresses (AVX512F
+ * scatters/gathers being the worst). Each such linear range can span a
+ * page boundary, i.e. may require two page walks. Account for each insn
+ * byte individually, for simplicity.
+ */
+ const unsigned int nents = (CONFIG_PAGING_LEVELS + 1) *
+ (MAX_INST_LEN + 16 * 2);
+ struct hvmemul_cache *cache = xmalloc_flex_struct(struct hvmemul_cache,
+ ents, nents);
+
+ if ( !cache )
+ return -ENOMEM;
+
+ /* Cache is disabled initially. */
+ cache->num_ents = nents + 1;
+ cache->max_ents = nents;
+
+ v->arch.hvm.hvm_io.cache = cache;
+
+ return 0;
+}
+
+unsigned int hvmemul_cache_disable(struct vcpu *v)
+{
+ struct hvmemul_cache *cache = v->arch.hvm.hvm_io.cache;
+ unsigned int token = cache->num_ents;
+
+ cache->num_ents = cache->max_ents + 1;
+
+ return token;
+}
+
+void hvmemul_cache_restore(struct vcpu *v, unsigned int token)
+{
+ struct hvmemul_cache *cache = v->arch.hvm.hvm_io.cache;
+
+ ASSERT(cache->num_ents > cache->max_ents);
+ cache->num_ents = token;
+}
+
+bool hvmemul_read_cache(const struct vcpu *v, paddr_t gpa,
+ void *buffer, unsigned int size)
+{
+ const struct hvmemul_cache *cache = v->arch.hvm.hvm_io.cache;
+ unsigned int i;
+
+ /* Cache unavailable? */
+ if ( cache->num_ents > cache->max_ents )
+ return false;
+
+ while ( size > sizeof(cache->ents->data) )
+ {
+ i = gpa & (sizeof(cache->ents->data) - 1)
+ ? -gpa & (sizeof(cache->ents->data) - 1)
+ : sizeof(cache->ents->data);
+ if ( !hvmemul_read_cache(v, gpa, buffer, i) )
+ return false;
+ gpa += i;
+ buffer += i;
+ size -= i;
+ }
+
+ for ( i = 0; i < cache->num_ents; ++i )
+ if ( cache->ents[i].gpa == gpa && cache->ents[i].size == size )
+ {
+ memcpy(buffer, &cache->ents[i].data, size);
+ return true;
+ }
+
+ return false;
+}
+
+void hvmemul_write_cache(const struct vcpu *v, paddr_t gpa,
+ const void *buffer, unsigned int size)
+{
+ struct hvmemul_cache *cache = v->arch.hvm.hvm_io.cache;
+ unsigned int i;
+
+ /* Cache unavailable? */
+ if ( cache->num_ents > cache->max_ents )
+ return;
+
+ while ( size > sizeof(cache->ents->data) )
+ {
+ i = gpa & (sizeof(cache->ents->data) - 1)
+ ? -gpa & (sizeof(cache->ents->data) - 1)
+ : sizeof(cache->ents->data);
+ hvmemul_write_cache(v, gpa, buffer, i);
+ gpa += i;
+ buffer += i;
+ size -= i;
+ }
+
+ for ( i = 0; i < cache->num_ents; ++i )
+ if ( cache->ents[i].gpa == gpa && cache->ents[i].size == size )
+ {
+ memcpy(&cache->ents[i].data, buffer, size);
+ return;
+ }
+
+ if ( unlikely(i >= cache->max_ents) )
+ {
+ domain_crash(v->domain);
+ return;
+ }
+
+ cache->ents[i].gpa = gpa;
+ cache->ents[i].size = size;
+
+ memcpy(&cache->ents[i].data, buffer, size);
+
+ cache->num_ents = i + 1;
+}
+
/*
* Local variables:
* mode: C
/* This function and all its descendants need to be to be idempotent. */
void hvm_domain_relinquish_resources(struct domain *d)
{
+ struct vcpu *v;
+
if ( hvm_funcs.domain_relinquish_resources )
alternative_vcall(hvm_funcs.domain_relinquish_resources, d);
rtc_deinit(d);
pmtimer_deinit(d);
hpet_deinit(d);
+
+ for_each_vcpu ( d, v )
+ hvmemul_cache_destroy(v);
}
void hvm_domain_destroy(struct domain *d)
v->arch.hvm.inject_event.vector = HVM_EVENT_VECTOR_UNSET;
+ rc = hvmemul_cache_init(v);
+ if ( rc )
+ goto fail4;
+
rc = setup_compat_arg_xlat(v); /* teardown: free_compat_arg_xlat() */
if ( rc != 0 )
goto fail4;
fail5:
free_compat_arg_xlat(v);
fail4:
+ hvmemul_cache_destroy(v);
hvm_funcs.vcpu_destroy(v);
fail3:
vlapic_destroy(v);
unsigned int eflags, new_cpl;
pagefault_info_t pfinfo;
int exn_raised, rc;
+ unsigned int token = hvmemul_cache_disable(v);
struct tss32 tss;
hvm_get_segment_register(v, x86_seg_gdtr, &gdt);
out:
hvm_unmap_entry(optss_desc);
hvm_unmap_entry(nptss_desc);
+
+ hvmemul_cache_restore(v, token);
}
enum hvm_translation_result hvm_translate_get_page(
void *buf, paddr_t addr, unsigned int size, struct vcpu *v, unsigned int flags,
uint32_t pfec, pagefault_info_t *pfinfo)
{
- char *p;
ASSERT(is_hvm_vcpu(v));
/*
return HVMTRANS_need_retry;
}
- p = __map_domain_page(page) + pgoff;
-
- if ( flags & HVMCOPY_to_guest )
+ if ( (flags & HVMCOPY_to_guest) ||
+ !hvmemul_read_cache(v, gfn_to_gaddr(gfn) | pgoff, buf, count) )
{
- if ( p2m_is_discard_write(p2mt) )
+ void *p = __map_domain_page(page) + pgoff;
+
+ if ( !(flags & HVMCOPY_to_guest) )
+ {
+ memcpy(buf, p, count);
+ hvmemul_write_cache(v, gfn_to_gaddr(gfn) | pgoff, buf, count);
+ }
+ else if ( p2m_is_discard_write(p2mt) )
{
static unsigned long lastpage;
memset(p, 0, count);
paging_mark_pfn_dirty(v->domain, _pfn(gfn_x(gfn)));
}
- }
- else
- {
- memcpy(buf, p, count);
- }
- unmap_domain_page(p);
+ unmap_domain_page(p);
+ }
addr += count;
if ( buf )
#include <xen/hypercall.h>
#include <xen/nospec.h>
+#include <asm/hvm/emulate.h>
#include <asm/hvm/support.h>
#include <asm/hvm/viridian.h>
struct domain *currd = curr->domain;
int mode = hvm_guest_x86_mode(curr);
unsigned long eax = regs->eax;
+ unsigned int token;
switch ( mode )
{
}
if ( (eax & 0x80000000) && is_viridian_domain(currd) )
- return viridian_hypercall(regs);
+ {
+ int ret;
+
+ /* See comment below. */
+ token = hvmemul_cache_disable(curr);
+
+ ret = viridian_hypercall(regs);
+
+ hvmemul_cache_restore(curr, token);
+
+ return ret;
+ }
BUILD_BUG_ON(ARRAY_SIZE(hvm_hypercall_table) >
ARRAY_SIZE(hypercall_args_table));
return HVM_HCALL_completed;
}
+ /*
+ * Caching is intended for instruction emulation only. Disable it
+ * for any accesses by hypercall argument copy-in / copy-out.
+ */
+ token = hvmemul_cache_disable(curr);
+
curr->hcall_preempted = false;
if ( mode == 8 )
#endif
}
+ hvmemul_cache_restore(curr, token);
+
HVM_DBG_LOG(DBG_LEVEL_HCALL, "hcall%lu -> %lx", eax, regs->rax);
if ( curr->hcall_preempted )
#include <xen/types.h>
#include <xen/sched.h>
#include <asm/regs.h>
+#include <asm/hvm/emulate.h>
#include <asm/hvm/hvm.h>
#include <asm/hvm/support.h>
#include <asm/hvm/domain.h>
{
if ( p->data_is_ptr )
{
+ struct vcpu *curr = current;
+ unsigned int token = hvmemul_cache_disable(curr);
+
data = 0;
switch ( hvm_copy_from_guest_phys(&data, p->data + step * i,
p->size) )
ASSERT_UNREACHABLE();
/* fall through */
default:
- domain_crash(current->domain);
+ domain_crash(curr->domain);
return X86EMUL_UNHANDLEABLE;
}
+
+ hvmemul_cache_restore(curr, token);
}
else
data = p->data;
struct vcpu *curr = current;
struct vmcb_struct *vmcb = curr->arch.hvm.svm.vmcb;
+ ASSERT(hvmemul_cache_disabled(curr));
+
svm_asid_handle_vmrun();
if ( unlikely(tb_init_done) )
#include <xen/irq.h>
#include <xen/vpci.h>
#include <public/hvm/ioreq.h>
+#include <asm/hvm/emulate.h>
#include <asm/hvm/io.h>
#include <asm/hvm/vpic.h>
#include <asm/hvm/vlapic.h>
if ( !ctrl_address && snoop_addr &&
v->arch.hvm.hvm_io.msix_snoop_gpa )
{
+ unsigned int token = hvmemul_cache_disable(v);
const struct msi_desc *desc;
uint32_t data;
sizeof(data)) == HVMTRANS_okay &&
!(data & PCI_MSIX_VECTOR_BITMASK) )
ctrl_address = snoop_addr;
+
+ hvmemul_cache_restore(v, token);
}
if ( !ctrl_address )
struct hvm_vcpu_asid *p_asid;
bool_t need_flush;
+ ASSERT(hvmemul_cache_disabled(curr));
+
/* Shadow EPTP can't be updated here because irqs are disabled */
if ( nestedhvm_vcpu_in_guestmode(curr) && vcpu_nestedhvm(curr).stale_np2m )
return false;
#include <xen/err.h>
#include <xen/mm.h>
+#include <xen/sched.h>
#include <asm/hvm/hvm.h>
#include <asm/x86_emulate.h>
uint8_t dir,
void *buffer);
+#ifdef CONFIG_HVM
+/*
+ * The cache controlled by the functions below is not like an ordinary CPU
+ * cache, i.e. aiming to help performance, but a "secret store" which is
+ * needed for correctness. The issue it helps addressing is the need for
+ * re-execution of an insn (after data was provided by a device model) to
+ * observe the exact same memory state, i.e. to specifically not observe any
+ * updates which may have occurred in the meantime by other agents.
+ * Therefore this cache gets
+ * - enabled when emulation of an insn starts,
+ * - disabled across processing secondary things like a hypercall resulting
+ * from insn emulation,
+ * - disabled again when an emulated insn is known to not require any
+ * further re-execution.
+ */
+int __must_check hvmemul_cache_init(struct vcpu *v);
+static inline void hvmemul_cache_destroy(struct vcpu *v)
+{
+ XFREE(v->arch.hvm.hvm_io.cache);
+}
+bool hvmemul_read_cache(const struct vcpu *, paddr_t gpa,
+ void *buffer, unsigned int size);
+void hvmemul_write_cache(const struct vcpu *, paddr_t gpa,
+ const void *buffer, unsigned int size);
+unsigned int hvmemul_cache_disable(struct vcpu *);
+void hvmemul_cache_restore(struct vcpu *, unsigned int token);
+/* For use in ASSERT()s only: */
+static inline bool hvmemul_cache_disabled(struct vcpu *v)
+{
+ return hvmemul_cache_disable(v) == hvmemul_cache_disable(v);
+}
+#endif
+
void hvm_dump_emulation_state(const char *loglvl, const char *prefix,
struct hvm_emulate_ctxt *hvmemul_ctxt, int rc);
/* For retries we shouldn't re-fetch the instruction. */
unsigned int mmio_insn_bytes;
unsigned char mmio_insn[16];
+ struct hvmemul_cache *cache;
+
/*
* For string instruction emulation we need to be able to signal a
* necessary retry through other than function return codes.