/* * QEMU ram block attributes * * Copyright Intel * * Author: * Chenyi Qiang * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "system/ramblock.h" #include "trace.h" OBJECT_DEFINE_SIMPLE_TYPE_WITH_INTERFACES(RamBlockAttributes, ram_block_attributes, RAM_BLOCK_ATTRIBUTES, OBJECT, { TYPE_RAM_DISCARD_MANAGER }, { }) static size_t ram_block_attributes_get_block_size(const RamBlockAttributes *attr) { /* * Because page conversion could be manipulated in the size of at least 4K * or 4K aligned, Use the host page size as the granularity to track the * memory attribute. */ g_assert(attr && attr->ram_block); g_assert(attr->ram_block->page_size == qemu_real_host_page_size()); return attr->ram_block->page_size; } static bool ram_block_attributes_rdm_is_populated(const RamDiscardManager *rdm, const MemoryRegionSection *section) { const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); const size_t block_size = ram_block_attributes_get_block_size(attr); const uint64_t first_bit = section->offset_within_region / block_size; const uint64_t last_bit = first_bit + int128_get64(section->size) / block_size - 1; unsigned long first_discarded_bit; first_discarded_bit = find_next_zero_bit(attr->bitmap, last_bit + 1, first_bit); return first_discarded_bit > last_bit; } typedef int (*ram_block_attributes_section_cb)(MemoryRegionSection *s, void *arg); static int ram_block_attributes_notify_populate_cb(MemoryRegionSection *section, void *arg) { RamDiscardListener *rdl = arg; return rdl->notify_populate(rdl, section); } static int ram_block_attributes_notify_discard_cb(MemoryRegionSection *section, void *arg) { RamDiscardListener *rdl = arg; rdl->notify_discard(rdl, section); return 0; } static int ram_block_attributes_for_each_populated_section(const RamBlockAttributes *attr, MemoryRegionSection *section, void *arg, ram_block_attributes_section_cb cb) { unsigned long first_bit, last_bit; uint64_t offset, size; const size_t block_size = ram_block_attributes_get_block_size(attr); int ret = 0; first_bit = section->offset_within_region / block_size; first_bit = find_next_bit(attr->bitmap, attr->bitmap_size, first_bit); while (first_bit < attr->bitmap_size) { MemoryRegionSection tmp = *section; offset = first_bit * block_size; last_bit = find_next_zero_bit(attr->bitmap, attr->bitmap_size, first_bit + 1) - 1; size = (last_bit - first_bit + 1) * block_size; if (!memory_region_section_intersect_range(&tmp, offset, size)) { break; } ret = cb(&tmp, arg); if (ret) { error_report("%s: Failed to notify RAM discard listener: %s", __func__, strerror(-ret)); break; } first_bit = find_next_bit(attr->bitmap, attr->bitmap_size, last_bit + 2); } return ret; } static int ram_block_attributes_for_each_discarded_section(const RamBlockAttributes *attr, MemoryRegionSection *section, void *arg, ram_block_attributes_section_cb cb) { unsigned long first_bit, last_bit; uint64_t offset, size; const size_t block_size = ram_block_attributes_get_block_size(attr); int ret = 0; first_bit = section->offset_within_region / block_size; first_bit = find_next_zero_bit(attr->bitmap, attr->bitmap_size, first_bit); while (first_bit < attr->bitmap_size) { MemoryRegionSection tmp = *section; offset = first_bit * block_size; last_bit = find_next_bit(attr->bitmap, attr->bitmap_size, first_bit + 1) - 1; size = (last_bit - first_bit + 1) * block_size; if (!memory_region_section_intersect_range(&tmp, offset, size)) { break; } ret = cb(&tmp, arg); if (ret) { error_report("%s: Failed to notify RAM discard listener: %s", __func__, strerror(-ret)); break; } first_bit = find_next_zero_bit(attr->bitmap, attr->bitmap_size, last_bit + 2); } return ret; } static uint64_t ram_block_attributes_rdm_get_min_granularity(const RamDiscardManager *rdm, const MemoryRegion *mr) { const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); g_assert(mr == attr->ram_block->mr); return ram_block_attributes_get_block_size(attr); } static void ram_block_attributes_rdm_register_listener(RamDiscardManager *rdm, RamDiscardListener *rdl, MemoryRegionSection *section) { RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); int ret; g_assert(section->mr == attr->ram_block->mr); rdl->section = memory_region_section_new_copy(section); QLIST_INSERT_HEAD(&attr->rdl_list, rdl, next); ret = ram_block_attributes_for_each_populated_section(attr, section, rdl, ram_block_attributes_notify_populate_cb); if (ret) { error_report("%s: Failed to register RAM discard listener: %s", __func__, strerror(-ret)); exit(1); } } static void ram_block_attributes_rdm_unregister_listener(RamDiscardManager *rdm, RamDiscardListener *rdl) { RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); int ret; g_assert(rdl->section); g_assert(rdl->section->mr == attr->ram_block->mr); if (rdl->double_discard_supported) { rdl->notify_discard(rdl, rdl->section); } else { ret = ram_block_attributes_for_each_populated_section(attr, rdl->section, rdl, ram_block_attributes_notify_discard_cb); if (ret) { error_report("%s: Failed to unregister RAM discard listener: %s", __func__, strerror(-ret)); exit(1); } } memory_region_section_free_copy(rdl->section); rdl->section = NULL; QLIST_REMOVE(rdl, next); } typedef struct RamBlockAttributesReplayData { ReplayRamDiscardState fn; void *opaque; } RamBlockAttributesReplayData; static int ram_block_attributes_rdm_replay_cb(MemoryRegionSection *section, void *arg) { RamBlockAttributesReplayData *data = arg; return data->fn(section, data->opaque); } static int ram_block_attributes_rdm_replay_populated(const RamDiscardManager *rdm, MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); RamBlockAttributesReplayData data = { .fn = replay_fn, .opaque = opaque }; g_assert(section->mr == attr->ram_block->mr); return ram_block_attributes_for_each_populated_section(attr, section, &data, ram_block_attributes_rdm_replay_cb); } static int ram_block_attributes_rdm_replay_discarded(const RamDiscardManager *rdm, MemoryRegionSection *section, ReplayRamDiscardState replay_fn, void *opaque) { RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rdm); RamBlockAttributesReplayData data = { .fn = replay_fn, .opaque = opaque }; g_assert(section->mr == attr->ram_block->mr); return ram_block_attributes_for_each_discarded_section(attr, section, &data, ram_block_attributes_rdm_replay_cb); } static bool ram_block_attributes_is_valid_range(RamBlockAttributes *attr, uint64_t offset, uint64_t size) { MemoryRegion *mr = attr->ram_block->mr; g_assert(mr); uint64_t region_size = memory_region_size(mr); const size_t block_size = ram_block_attributes_get_block_size(attr); if (!QEMU_IS_ALIGNED(offset, block_size) || !QEMU_IS_ALIGNED(size, block_size)) { return false; } if (offset + size <= offset) { return false; } if (offset + size > region_size) { return false; } return true; } static void ram_block_attributes_notify_discard(RamBlockAttributes *attr, uint64_t offset, uint64_t size) { RamDiscardListener *rdl; QLIST_FOREACH(rdl, &attr->rdl_list, next) { MemoryRegionSection tmp = *rdl->section; if (!memory_region_section_intersect_range(&tmp, offset, size)) { continue; } rdl->notify_discard(rdl, &tmp); } } static int ram_block_attributes_notify_populate(RamBlockAttributes *attr, uint64_t offset, uint64_t size) { RamDiscardListener *rdl; int ret = 0; QLIST_FOREACH(rdl, &attr->rdl_list, next) { MemoryRegionSection tmp = *rdl->section; if (!memory_region_section_intersect_range(&tmp, offset, size)) { continue; } ret = rdl->notify_populate(rdl, &tmp); if (ret) { break; } } return ret; } int ram_block_attributes_state_change(RamBlockAttributes *attr, uint64_t offset, uint64_t size, bool to_discard) { const size_t block_size = ram_block_attributes_get_block_size(attr); const unsigned long first_bit = offset / block_size; const unsigned long nbits = size / block_size; const unsigned long last_bit = first_bit + nbits - 1; const bool is_discarded = find_next_bit(attr->bitmap, attr->bitmap_size, first_bit) > last_bit; const bool is_populated = find_next_zero_bit(attr->bitmap, attr->bitmap_size, first_bit) > last_bit; unsigned long bit; int ret = 0; if (!ram_block_attributes_is_valid_range(attr, offset, size)) { error_report("%s, invalid range: offset 0x%" PRIx64 ", size " "0x%" PRIx64, __func__, offset, size); return -EINVAL; } trace_ram_block_attributes_state_change(offset, size, is_discarded ? "discarded" : is_populated ? "populated" : "mixture", to_discard ? "discarded" : "populated"); if (to_discard) { if (is_discarded) { /* Already private */ } else if (is_populated) { /* Completely shared */ bitmap_clear(attr->bitmap, first_bit, nbits); ram_block_attributes_notify_discard(attr, offset, size); } else { /* Unexpected mixture: process individual blocks */ for (bit = first_bit; bit < first_bit + nbits; bit++) { if (!test_bit(bit, attr->bitmap)) { continue; } clear_bit(bit, attr->bitmap); ram_block_attributes_notify_discard(attr, bit * block_size, block_size); } } } else { if (is_populated) { /* Already shared */ } else if (is_discarded) { /* Completely private */ bitmap_set(attr->bitmap, first_bit, nbits); ret = ram_block_attributes_notify_populate(attr, offset, size); } else { /* Unexpected mixture: process individual blocks */ for (bit = first_bit; bit < first_bit + nbits; bit++) { if (test_bit(bit, attr->bitmap)) { continue; } set_bit(bit, attr->bitmap); ret = ram_block_attributes_notify_populate(attr, bit * block_size, block_size); if (ret) { break; } } } } return ret; } RamBlockAttributes *ram_block_attributes_create(RAMBlock *ram_block) { const int block_size = qemu_real_host_page_size(); RamBlockAttributes *attr; MemoryRegion *mr = ram_block->mr; attr = RAM_BLOCK_ATTRIBUTES(object_new(TYPE_RAM_BLOCK_ATTRIBUTES)); attr->ram_block = ram_block; if (memory_region_set_ram_discard_manager(mr, RAM_DISCARD_MANAGER(attr))) { object_unref(OBJECT(attr)); return NULL; } attr->bitmap_size = ROUND_UP(int128_get64(mr->size), block_size) / block_size; attr->bitmap = bitmap_new(attr->bitmap_size); return attr; } void ram_block_attributes_destroy(RamBlockAttributes *attr) { g_assert(attr); g_free(attr->bitmap); memory_region_set_ram_discard_manager(attr->ram_block->mr, NULL); object_unref(OBJECT(attr)); } static void ram_block_attributes_init(Object *obj) { RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(obj); QLIST_INIT(&attr->rdl_list); } static void ram_block_attributes_finalize(Object *obj) { } static void ram_block_attributes_class_init(ObjectClass *klass, const void *data) { RamDiscardManagerClass *rdmc = RAM_DISCARD_MANAGER_CLASS(klass); rdmc->get_min_granularity = ram_block_attributes_rdm_get_min_granularity; rdmc->register_listener = ram_block_attributes_rdm_register_listener; rdmc->unregister_listener = ram_block_attributes_rdm_unregister_listener; rdmc->is_populated = ram_block_attributes_rdm_is_populated; rdmc->replay_populated = ram_block_attributes_rdm_replay_populated; rdmc->replay_discarded = ram_block_attributes_rdm_replay_discarded; }