#include "qemu/accel.h"
#include "hw/boards.h"
#include "migration/vmstate.h"
+#include "exec/address-spaces.h"
//#define DEBUG_UNASSIGNED
static bool memory_region_big_endian(MemoryRegion *mr)
{
-#ifdef TARGET_WORDS_BIGENDIAN
+#if TARGET_BIG_ENDIAN
return mr->ops->endianness != DEVICE_LITTLE_ENDIAN;
#else
return mr->ops->endianness == DEVICE_BIG_ENDIAN;
unsigned access_size;
unsigned i;
MemTxResult r = MEMTX_OK;
+ bool reentrancy_guard_applied = false;
if (!access_size_min) {
access_size_min = 1;
access_size_max = 4;
}
+ /* Do not allow more than one simultaneous access to a device's IO Regions */
+ if (mr->dev && !mr->disable_reentrancy_guard &&
+ !mr->ram_device && !mr->ram && !mr->rom_device && !mr->readonly) {
+ if (mr->dev->mem_reentrancy_guard.engaged_in_io) {
+ warn_report_once("Blocked re-entrant IO on MemoryRegion: "
+ "%s at addr: 0x%" HWADDR_PRIX,
+ memory_region_name(mr), addr);
+ return MEMTX_ACCESS_ERROR;
+ }
+ mr->dev->mem_reentrancy_guard.engaged_in_io = true;
+ reentrancy_guard_applied = true;
+ }
+
/* FIXME: support unaligned access? */
access_size = MAX(MIN(size, access_size_max), access_size_min);
access_mask = MAKE_64BIT_MASK(0, access_size * 8);
access_mask, attrs);
}
}
+ if (mr->dev && reentrancy_guard_applied) {
+ mr->dev->mem_reentrancy_guard.engaged_in_io = false;
+ }
return r;
}
}
mr->name = g_strdup(name);
mr->owner = owner;
+ mr->dev = (DeviceState *) object_dynamic_cast(mr->owner, TYPE_DEVICE);
mr->ram_block = NULL;
if (name) {
unsigned size)
{
#ifdef DEBUG_UNASSIGNED
- printf("Unassigned mem read " TARGET_FMT_plx "\n", addr);
+ printf("Unassigned mem read " HWADDR_FMT_plx "\n", addr);
#endif
return 0;
}
uint64_t val, unsigned size)
{
#ifdef DEBUG_UNASSIGNED
- printf("Unassigned mem write " TARGET_FMT_plx " = 0x%"PRIx64"\n", addr, val);
+ printf("Unassigned mem write " HWADDR_FMT_plx " = 0x%"PRIx64"\n", addr, val);
#endif
}
uint64_t align,
uint32_t ram_flags,
const char *path,
- bool readonly,
+ ram_addr_t offset,
Error **errp)
{
Error *err = NULL;
memory_region_init(mr, owner, name, size);
mr->ram = true;
- mr->readonly = readonly;
+ mr->readonly = !!(ram_flags & RAM_READONLY);
mr->terminates = true;
mr->destructor = memory_region_destructor_ram;
mr->align = align;
mr->ram_block = qemu_ram_alloc_from_file(size, mr, ram_flags, path,
- readonly, &err);
+ offset, &err);
if (err) {
mr->size = int128_zero();
object_unparent(OBJECT(mr));
Error *err = NULL;
memory_region_init(mr, owner, name, size);
mr->ram = true;
+ mr->readonly = !!(ram_flags & RAM_READONLY);
mr->terminates = true;
mr->destructor = memory_region_destructor_ram;
mr->ram_block = qemu_ram_alloc_from_fd(size, mr, ram_flags, fd, offset,
- false, &err);
+ &err);
if (err) {
mr->size = int128_zero();
object_unparent(OBJECT(mr));
}
}
+void memory_region_unmap_iommu_notifier_range(IOMMUNotifier *notifier)
+{
+ IOMMUTLBEvent event;
+
+ event.type = IOMMU_NOTIFIER_UNMAP;
+ event.entry.target_as = &address_space_memory;
+ event.entry.iova = notifier->start;
+ event.entry.perm = IOMMU_NONE;
+ event.entry.addr_mask = notifier->end - notifier->start;
+
+ memory_region_notify_iommu_one(notifier, &event);
+}
+
void memory_region_notify_iommu(IOMMUMemoryRegion *iommu_mr,
int iommu_idx,
IOMMUTLBEvent event)
rdmc->unregister_listener(rdm, rdl);
}
+/* Called with rcu_read_lock held. */
+bool memory_get_xlat_addr(IOMMUTLBEntry *iotlb, void **vaddr,
+ ram_addr_t *ram_addr, bool *read_only,
+ bool *mr_has_discard_manager)
+{
+ MemoryRegion *mr;
+ hwaddr xlat;
+ hwaddr len = iotlb->addr_mask + 1;
+ bool writable = iotlb->perm & IOMMU_WO;
+
+ if (mr_has_discard_manager) {
+ *mr_has_discard_manager = false;
+ }
+ /*
+ * The IOMMU TLB entry we have just covers translation through
+ * this IOMMU to its immediate target. We need to translate
+ * it the rest of the way through to memory.
+ */
+ mr = address_space_translate(&address_space_memory, iotlb->translated_addr,
+ &xlat, &len, writable, MEMTXATTRS_UNSPECIFIED);
+ if (!memory_region_is_ram(mr)) {
+ error_report("iommu map to non memory area %" HWADDR_PRIx "", xlat);
+ return false;
+ } else if (memory_region_has_ram_discard_manager(mr)) {
+ RamDiscardManager *rdm = memory_region_get_ram_discard_manager(mr);
+ MemoryRegionSection tmp = {
+ .mr = mr,
+ .offset_within_region = xlat,
+ .size = int128_make64(len),
+ };
+ if (mr_has_discard_manager) {
+ *mr_has_discard_manager = true;
+ }
+ /*
+ * Malicious VMs can map memory into the IOMMU, which is expected
+ * to remain discarded. vfio will pin all pages, populating memory.
+ * Disallow that. vmstate priorities make sure any RamDiscardManager
+ * were already restored before IOMMUs are restored.
+ */
+ if (!ram_discard_manager_is_populated(rdm, &tmp)) {
+ error_report("iommu map to discarded memory (e.g., unplugged via"
+ " virtio-mem): %" HWADDR_PRIx "",
+ iotlb->translated_addr);
+ return false;
+ }
+ }
+
+ /*
+ * Translation truncates length to the IOMMU page size,
+ * check that it did not truncate too much.
+ */
+ if (len & iotlb->addr_mask) {
+ error_report("iommu has granularity incompatible with target AS");
+ return false;
+ }
+
+ if (vaddr) {
+ *vaddr = memory_region_get_ram_ptr(mr) + xlat;
+ }
+
+ if (ram_addr) {
+ *ram_addr = memory_region_get_ram_addr(mr) + xlat;
+ }
+
+ if (read_only) {
+ *read_only = !writable || mr->readonly;
+ }
+
+ return true;
+}
+
void memory_region_set_log(MemoryRegion *mr, bool log, unsigned client)
{
uint8_t mask = 1 << client;
* If memory region `mr' is NULL, do global sync. Otherwise, sync
* dirty bitmap for the specified memory region.
*/
-static void memory_region_sync_dirty_bitmap(MemoryRegion *mr)
+static void memory_region_sync_dirty_bitmap(MemoryRegion *mr, bool last_stage)
{
MemoryListener *listener;
AddressSpace *as;
* is to do a global sync, because we are not capable to
* sync in a finer granularity.
*/
- listener->log_sync_global(listener);
+ listener->log_sync_global(listener, last_stage);
trace_memory_region_sync_dirty(mr ? mr->name : "(all)", listener->name, 1);
}
}
{
DirtyBitmapSnapshot *snapshot;
assert(mr->ram_block);
- memory_region_sync_dirty_bitmap(mr);
+ memory_region_sync_dirty_bitmap(mr, false);
snapshot = cpu_physical_memory_snapshot_and_clear_dirty(mr, addr, size, client);
memory_global_after_dirty_log_sync();
return snapshot;
int memory_region_get_fd(MemoryRegion *mr)
{
- int fd;
-
RCU_READ_LOCK_GUARD();
while (mr->alias) {
mr = mr->alias;
}
- fd = mr->ram_block->fd;
-
- return fd;
+ return mr->ram_block->fd;
}
void *memory_region_get_ram_ptr(MemoryRegion *mr)
{
- void *ptr;
uint64_t offset = 0;
RCU_READ_LOCK_GUARD();
mr = mr->alias;
}
assert(mr->ram_block);
- ptr = qemu_map_ram_ptr(mr->ram_block, offset);
-
- return ptr;
+ return qemu_map_ram_ptr(mr->ram_block, offset);
}
MemoryRegion *memory_region_from_host(void *ptr, ram_addr_t *offset)
memory_region_transaction_begin();
memory_region_ref(mr);
memory_region_del_subregion(container, mr);
- mr->container = container;
- memory_region_update_container_subregions(mr);
+ memory_region_add_subregion_common(container, mr->addr, mr);
memory_region_unref(mr);
memory_region_transaction_commit();
}
return mr && mr != container;
}
-void memory_global_dirty_log_sync(void)
+void memory_global_dirty_log_sync(bool last_stage)
{
- memory_region_sync_dirty_bitmap(NULL);
+ memory_region_sync_dirty_bitmap(NULL, last_stage);
}
void memory_global_after_dirty_log_sync(void)
MEMORY_LISTENER_CALL_GLOBAL(log_global_after_sync, Forward);
}
+/*
+ * Dirty track stop flags that are postponed due to VM being stopped. Should
+ * only be used within vmstate_change hook.
+ */
+static unsigned int postponed_stop_flags;
static VMChangeStateEntry *vmstate_change;
+static void memory_global_dirty_log_stop_postponed_run(void);
void memory_global_dirty_log_start(unsigned int flags)
{
- unsigned int old_flags = global_dirty_tracking;
+ unsigned int old_flags;
+
+ assert(flags && !(flags & (~GLOBAL_DIRTY_MASK)));
if (vmstate_change) {
- qemu_del_vm_change_state_handler(vmstate_change);
- vmstate_change = NULL;
+ /* If there is postponed stop(), operate on it first */
+ postponed_stop_flags &= ~flags;
+ memory_global_dirty_log_stop_postponed_run();
}
- assert(flags && !(flags & (~GLOBAL_DIRTY_MASK)));
- assert(!(global_dirty_tracking & flags));
+ flags &= ~global_dirty_tracking;
+ if (!flags) {
+ return;
+ }
+
+ old_flags = global_dirty_tracking;
global_dirty_tracking |= flags;
trace_global_dirty_changed(global_dirty_tracking);
}
}
+/*
+ * Execute the postponed dirty log stop operations if there is, then reset
+ * everything (including the flags and the vmstate change hook).
+ */
+static void memory_global_dirty_log_stop_postponed_run(void)
+{
+ /* This must be called with the vmstate handler registered */
+ assert(vmstate_change);
+
+ /* Note: postponed_stop_flags can be cleared in log start routine */
+ if (postponed_stop_flags) {
+ memory_global_dirty_log_do_stop(postponed_stop_flags);
+ postponed_stop_flags = 0;
+ }
+
+ qemu_del_vm_change_state_handler(vmstate_change);
+ vmstate_change = NULL;
+}
+
static void memory_vm_change_state_handler(void *opaque, bool running,
RunState state)
{
- unsigned int flags = (unsigned int)(uintptr_t)opaque;
if (running) {
- memory_global_dirty_log_do_stop(flags);
-
- if (vmstate_change) {
- qemu_del_vm_change_state_handler(vmstate_change);
- vmstate_change = NULL;
- }
+ memory_global_dirty_log_stop_postponed_run();
}
}
void memory_global_dirty_log_stop(unsigned int flags)
{
if (!runstate_is_running()) {
+ /* Postpone the dirty log stop, e.g., to when VM starts again */
if (vmstate_change) {
- return;
+ /* Batch with previous postponed flags */
+ postponed_stop_flags |= flags;
+ } else {
+ postponed_stop_flags = flags;
+ vmstate_change = qemu_add_vm_change_state_handler(
+ memory_vm_change_state_handler, NULL);
}
- vmstate_change = qemu_add_vm_change_state_handler(
- memory_vm_change_state_handler,
- (void *)(uintptr_t)flags);
return;
}
for (i = 0; i < level; i++) {
qemu_printf(MTREE_INDENT);
}
- qemu_printf(TARGET_FMT_plx "-" TARGET_FMT_plx
- " (prio %d, %s%s): alias %s @%s " TARGET_FMT_plx
- "-" TARGET_FMT_plx "%s",
+ qemu_printf(HWADDR_FMT_plx "-" HWADDR_FMT_plx
+ " (prio %d, %s%s): alias %s @%s " HWADDR_FMT_plx
+ "-" HWADDR_FMT_plx "%s",
cur_start, cur_end,
mr->priority,
mr->nonvolatile ? "nv-" : "",
for (i = 0; i < level; i++) {
qemu_printf(MTREE_INDENT);
}
- qemu_printf(TARGET_FMT_plx "-" TARGET_FMT_plx
+ qemu_printf(HWADDR_FMT_plx "-" HWADDR_FMT_plx
" (prio %d, %s%s): %s%s",
cur_start, cur_end,
mr->priority,
while (n--) {
mr = range->mr;
if (range->offset_in_region) {
- qemu_printf(MTREE_INDENT TARGET_FMT_plx "-" TARGET_FMT_plx
- " (prio %d, %s%s): %s @" TARGET_FMT_plx,
+ qemu_printf(MTREE_INDENT HWADDR_FMT_plx "-" HWADDR_FMT_plx
+ " (prio %d, %s%s): %s @" HWADDR_FMT_plx,
int128_get64(range->addr.start),
int128_get64(range->addr.start)
+ MR_SIZE(range->addr.size),
memory_region_name(mr),
range->offset_in_region);
} else {
- qemu_printf(MTREE_INDENT TARGET_FMT_plx "-" TARGET_FMT_plx
+ qemu_printf(MTREE_INDENT HWADDR_FMT_plx "-" HWADDR_FMT_plx
" (prio %d, %s%s): %s",
int128_get64(range->addr.start),
int128_get64(range->addr.start)