/* * 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_SOURCE }, { }) static size_t ram_block_attributes_get_block_size(void) { /* * 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. */ return qemu_real_host_page_size(); } /* RamDiscardSource interface implementation */ static uint64_t ram_block_attributes_rds_get_min_granularity(const RamDiscardSource *rds, const MemoryRegion *mr) { const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); g_assert(mr == attr->ram_block->mr); return ram_block_attributes_get_block_size(); } static bool ram_block_attributes_rds_is_populated(const RamDiscardSource *rds, const MemoryRegionSection *section) { const RamBlockAttributes *attr = RAM_BLOCK_ATTRIBUTES(rds); const size_t block_size = ram_block_attributes_get_block_size(); 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; } 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(); 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) { RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr); ram_discard_manager_notify_discard(rdm, RAM_DISCARD_SOURCE(attr), offset, size); } static int ram_block_attributes_notify_populate(RamBlockAttributes *attr, uint64_t offset, uint64_t size) { RamDiscardManager *rdm = memory_region_get_ram_discard_manager(attr->ram_block->mr); return ram_discard_manager_notify_populate(rdm, RAM_DISCARD_SOURCE(attr), offset, size); } 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(); 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 = ram_block_attributes_get_block_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_add_ram_discard_source(mr, RAM_DISCARD_SOURCE(attr))) { object_unref(OBJECT(attr)); return NULL; } attr->bitmap_size = DIV_ROUND_UP(int128_get64(mr->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_del_ram_discard_source(attr->ram_block->mr, RAM_DISCARD_SOURCE(attr)); object_unref(OBJECT(attr)); } static void ram_block_attributes_init(Object *obj) { } static void ram_block_attributes_finalize(Object *obj) { } static void ram_block_attributes_class_init(ObjectClass *klass, const void *data) { RamDiscardSourceClass *rdsc = RAM_DISCARD_SOURCE_CLASS(klass); rdsc->get_min_granularity = ram_block_attributes_rds_get_min_granularity; rdsc->is_populated = ram_block_attributes_rds_is_populated; }