Import QEMU upstream snapshot d2e570c
Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
config DIMM
|
||||
bool
|
||||
select MEM_DEVICE
|
||||
|
||||
config MEM_DEVICE
|
||||
bool
|
||||
|
||||
config NVDIMM
|
||||
bool
|
||||
select MEM_DEVICE
|
||||
|
||||
config SPARSE_MEM
|
||||
bool
|
||||
|
||||
config CXL_MEM_DEVICE
|
||||
bool
|
||||
default y if CXL
|
||||
select MEM_DEVICE
|
||||
|
||||
config SP_MEM
|
||||
bool
|
||||
select MEM_DEVICE
|
||||
+2499
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* CXL Type 3 (memory expander) device QMP stubs
|
||||
*
|
||||
* Copyright(C) 2020 Intel Corporation.
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2. See the
|
||||
* COPYING file in the top-level directory.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-v2-only
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qapi-commands-cxl.h"
|
||||
|
||||
void qmp_cxl_inject_general_media_event(const char *path, CxlEventLog log,
|
||||
uint32_t flags, bool has_maint_op_class,
|
||||
uint8_t maint_op_class,
|
||||
bool has_maint_op_subclass,
|
||||
uint8_t maint_op_subclass,
|
||||
bool has_ld_id, uint16_t ld_id,
|
||||
bool has_head_id, uint8_t head_id,
|
||||
uint64_t dpa,
|
||||
uint8_t descriptor, uint8_t type,
|
||||
uint8_t transaction_type,
|
||||
bool has_channel, uint8_t channel,
|
||||
bool has_rank, uint8_t rank,
|
||||
bool has_device, uint32_t device,
|
||||
const char *component_id,
|
||||
bool has_comp_id_pldm,
|
||||
bool is_comp_id_pldm,
|
||||
bool has_cme_ev_flags,
|
||||
uint8_t cme_ev_flags,
|
||||
bool has_cme_count, uint32_t cme_count,
|
||||
uint8_t sub_type,
|
||||
Error **errp) {}
|
||||
|
||||
void qmp_cxl_inject_dram_event(const char *path, CxlEventLog log,
|
||||
uint32_t flags,
|
||||
bool has_maint_op_class, uint8_t maint_op_class,
|
||||
bool has_maint_op_subclass,
|
||||
uint8_t maint_op_subclass,
|
||||
bool has_ld_id, uint16_t ld_id,
|
||||
bool has_head_id, uint8_t head_id,
|
||||
uint64_t dpa, uint8_t descriptor,
|
||||
uint8_t type, uint8_t transaction_type,
|
||||
bool has_channel, uint8_t channel,
|
||||
bool has_rank, uint8_t rank,
|
||||
bool has_nibble_mask, uint32_t nibble_mask,
|
||||
bool has_bank_group, uint8_t bank_group,
|
||||
bool has_bank, uint8_t bank,
|
||||
bool has_row, uint32_t row,
|
||||
bool has_column, uint16_t column,
|
||||
bool has_correction_mask,
|
||||
uint64List *correction_mask,
|
||||
const char *component_id,
|
||||
bool has_comp_id_pldm,
|
||||
bool is_comp_id_pldm,
|
||||
bool has_sub_channel, uint8_t sub_channel,
|
||||
bool has_cme_ev_flags, uint8_t cme_ev_flags,
|
||||
bool has_cvme_count, uint32_t cvme_count,
|
||||
uint8_t sub_type,
|
||||
Error **errp) {}
|
||||
|
||||
void qmp_cxl_inject_memory_module_event(const char *path, CxlEventLog log,
|
||||
uint32_t flags, bool has_maint_op_class,
|
||||
uint8_t maint_op_class,
|
||||
bool has_maint_op_subclass,
|
||||
uint8_t maint_op_subclass,
|
||||
bool has_ld_id, uint16_t ld_id,
|
||||
bool has_head_id, uint8_t head_id,
|
||||
uint8_t type,
|
||||
uint8_t health_status,
|
||||
uint8_t media_status,
|
||||
uint8_t additional_status,
|
||||
uint8_t life_used,
|
||||
int16_t temperature,
|
||||
uint32_t dirty_shutdown_count,
|
||||
uint32_t corrected_volatile_error_count,
|
||||
uint32_t corrected_persist_error_count,
|
||||
const char *component_id,
|
||||
bool has_comp_id_pldm,
|
||||
bool is_comp_id_pldm,
|
||||
uint8_t sub_type,
|
||||
Error **errp) {}
|
||||
|
||||
void qmp_cxl_inject_poison(const char *path, uint64_t start, uint64_t length,
|
||||
Error **errp)
|
||||
{
|
||||
error_setg(errp, "CXL Type 3 support is not compiled in");
|
||||
}
|
||||
|
||||
void qmp_cxl_inject_uncorrectable_errors(const char *path,
|
||||
CXLUncorErrorRecordList *errors,
|
||||
Error **errp)
|
||||
{
|
||||
error_setg(errp, "CXL Type 3 support is not compiled in");
|
||||
}
|
||||
|
||||
void qmp_cxl_inject_correctable_error(const char *path, CxlCorErrorType type,
|
||||
Error **errp)
|
||||
{
|
||||
error_setg(errp, "CXL Type 3 support is not compiled in");
|
||||
}
|
||||
|
||||
void qmp_cxl_add_dynamic_capacity(const char *path,
|
||||
uint16_t host_id,
|
||||
CxlExtentSelectionPolicy sel_policy,
|
||||
uint8_t region,
|
||||
const char *tag,
|
||||
CxlDynamicCapacityExtentList *extents,
|
||||
Error **errp)
|
||||
{
|
||||
error_setg(errp, "CXL Type 3 support is not compiled in");
|
||||
}
|
||||
|
||||
void qmp_cxl_release_dynamic_capacity(const char *path, uint16_t host_id,
|
||||
CxlExtentRemovalPolicy removal_policy,
|
||||
bool has_forced_removal,
|
||||
bool forced_removal,
|
||||
bool has_sanitize_on_release,
|
||||
bool sanitize_on_release,
|
||||
uint8_t region,
|
||||
const char *tag,
|
||||
CxlDynamicCapacityExtentList *extents,
|
||||
Error **errp)
|
||||
{
|
||||
error_setg(errp, "CXL Type 3 support is not compiled in");
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/mem/memory-device.h"
|
||||
|
||||
MemoryDeviceInfoList *qmp_memory_device_list(void)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint64_t get_plugged_memory_size(void)
|
||||
{
|
||||
return (uint64_t)-1;
|
||||
}
|
||||
|
||||
unsigned int memory_devices_get_reserved_memslots(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool memory_devices_memslot_auto_decision_active(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
/*
|
||||
* Memory Device Interface
|
||||
*
|
||||
* Copyright ProfitBricks GmbH 2012
|
||||
* Copyright (C) 2014 Red Hat Inc
|
||||
* Copyright (c) 2018 Red Hat Inc
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "hw/mem/memory-device.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/core/boards.h"
|
||||
#include "qemu/range.h"
|
||||
#include "hw/virtio/vhost.h"
|
||||
#include "system/kvm.h"
|
||||
#include "system/address-spaces.h"
|
||||
#include "trace.h"
|
||||
|
||||
static bool memory_device_is_empty(const MemoryDeviceState *md)
|
||||
{
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(md);
|
||||
Error *local_err = NULL;
|
||||
MemoryRegion *mr;
|
||||
|
||||
/* dropping const here is fine as we don't touch the memory region */
|
||||
mr = mdc->get_memory_region((MemoryDeviceState *)md, &local_err);
|
||||
if (local_err) {
|
||||
/* Not empty, we'll report errors later when containing the MR again. */
|
||||
error_free(local_err);
|
||||
return false;
|
||||
}
|
||||
return !mr;
|
||||
}
|
||||
|
||||
static gint memory_device_addr_sort(gconstpointer a, gconstpointer b)
|
||||
{
|
||||
const MemoryDeviceState *md_a = MEMORY_DEVICE(a);
|
||||
const MemoryDeviceState *md_b = MEMORY_DEVICE(b);
|
||||
const MemoryDeviceClass *mdc_a = MEMORY_DEVICE_GET_CLASS(a);
|
||||
const MemoryDeviceClass *mdc_b = MEMORY_DEVICE_GET_CLASS(b);
|
||||
const uint64_t addr_a = mdc_a->get_addr(md_a);
|
||||
const uint64_t addr_b = mdc_b->get_addr(md_b);
|
||||
|
||||
if (addr_a > addr_b) {
|
||||
return 1;
|
||||
} else if (addr_a < addr_b) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int memory_device_build_list(Object *obj, void *opaque)
|
||||
{
|
||||
GSList **list = opaque;
|
||||
|
||||
if (object_dynamic_cast(obj, TYPE_MEMORY_DEVICE)) {
|
||||
DeviceState *dev = DEVICE(obj);
|
||||
if (qdev_is_realized(dev)) {
|
||||
*list = g_slist_insert_sorted(*list, dev, memory_device_addr_sort);
|
||||
}
|
||||
}
|
||||
|
||||
object_child_foreach(obj, memory_device_build_list, opaque);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned int memory_device_get_memslots(MemoryDeviceState *md)
|
||||
{
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(md);
|
||||
|
||||
if (mdc->get_memslots) {
|
||||
return mdc->get_memslots(md);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Memslots that are reserved by memory devices (required but still reported
|
||||
* as free from KVM / vhost).
|
||||
*/
|
||||
static unsigned int get_reserved_memslots(MachineState *ms)
|
||||
{
|
||||
if (ms->device_memory->used_memslots >
|
||||
ms->device_memory->required_memslots) {
|
||||
/* This is unexpected, and we warned already in the memory notifier. */
|
||||
return 0;
|
||||
}
|
||||
return ms->device_memory->required_memslots -
|
||||
ms->device_memory->used_memslots;
|
||||
}
|
||||
|
||||
unsigned int memory_devices_get_reserved_memslots(void)
|
||||
{
|
||||
if (!current_machine->device_memory) {
|
||||
return 0;
|
||||
}
|
||||
return get_reserved_memslots(current_machine);
|
||||
}
|
||||
|
||||
bool memory_devices_memslot_auto_decision_active(void)
|
||||
{
|
||||
if (!current_machine->device_memory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return current_machine->device_memory->memslot_auto_decision_active;
|
||||
}
|
||||
|
||||
static unsigned int memory_device_memslot_decision_limit(MachineState *ms,
|
||||
MemoryRegion *mr)
|
||||
{
|
||||
const unsigned int reserved = get_reserved_memslots(ms);
|
||||
const uint64_t size = memory_region_size(mr);
|
||||
unsigned int max = vhost_get_max_memslots();
|
||||
unsigned int free = vhost_get_free_memslots();
|
||||
uint64_t available_space;
|
||||
unsigned int memslots;
|
||||
|
||||
if (kvm_enabled()) {
|
||||
max = MIN(max, kvm_get_max_memslots());
|
||||
free = MIN(free, kvm_get_free_memslots());
|
||||
}
|
||||
|
||||
/*
|
||||
* If we only have less overall memslots than what we consider reasonable,
|
||||
* just keep it to a minimum.
|
||||
*/
|
||||
if (max < MEMORY_DEVICES_SAFE_MAX_MEMSLOTS) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Consider our soft-limit across all memory devices. We don't really
|
||||
* expect to exceed this limit in reasonable configurations.
|
||||
*/
|
||||
if (MEMORY_DEVICES_SOFT_MEMSLOT_LIMIT <=
|
||||
ms->device_memory->required_memslots) {
|
||||
return 1;
|
||||
}
|
||||
memslots = MEMORY_DEVICES_SOFT_MEMSLOT_LIMIT -
|
||||
ms->device_memory->required_memslots;
|
||||
|
||||
/*
|
||||
* Consider the actually still free memslots. This is only relevant if
|
||||
* other memslot consumers would consume *significantly* more memslots than
|
||||
* what we prepared for (> 253). Unlikely, but let's just handle it
|
||||
* cleanly.
|
||||
*/
|
||||
memslots = MIN(memslots, free - reserved);
|
||||
if (memslots < 1 || unlikely(free < reserved)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* We cannot have any other memory devices? So give all to this device. */
|
||||
if (size == ms->maxram_size - ms->ram_size) {
|
||||
return memslots;
|
||||
}
|
||||
|
||||
/*
|
||||
* Simple heuristic: equally distribute the memslots over the space
|
||||
* still available for memory devices.
|
||||
*/
|
||||
available_space = ms->maxram_size - ms->ram_size -
|
||||
ms->device_memory->used_region_size;
|
||||
memslots = (double)memslots * size / available_space;
|
||||
return memslots < 1 ? 1 : memslots;
|
||||
}
|
||||
|
||||
static void memory_device_check_addable(MachineState *ms, MemoryDeviceState *md,
|
||||
MemoryRegion *mr, Error **errp)
|
||||
{
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(md);
|
||||
const uint64_t used_region_size = ms->device_memory->used_region_size;
|
||||
const uint64_t size = memory_region_size(mr);
|
||||
const unsigned int reserved_memslots = get_reserved_memslots(ms);
|
||||
unsigned int required_memslots, memslot_limit;
|
||||
|
||||
/*
|
||||
* Instruct the device to decide how many memslots to use, if applicable,
|
||||
* before we query the number of required memslots the first time.
|
||||
*/
|
||||
if (mdc->decide_memslots) {
|
||||
memslot_limit = memory_device_memslot_decision_limit(ms, mr);
|
||||
mdc->decide_memslots(md, memslot_limit);
|
||||
}
|
||||
required_memslots = memory_device_get_memslots(md);
|
||||
|
||||
/* we will need memory slots for kvm and vhost */
|
||||
if (kvm_enabled() &&
|
||||
kvm_get_free_memslots() < required_memslots + reserved_memslots) {
|
||||
error_setg(errp, "hypervisor has not enough free memory slots left");
|
||||
return;
|
||||
}
|
||||
if (vhost_get_free_memslots() < required_memslots + reserved_memslots) {
|
||||
error_setg(errp, "a used vhost backend has not enough free memory slots left");
|
||||
return;
|
||||
}
|
||||
|
||||
/* will we exceed the total amount of memory specified */
|
||||
if (used_region_size + size < used_region_size ||
|
||||
used_region_size + size > ms->maxram_size - ms->ram_size) {
|
||||
error_setg(errp, "not enough space, currently 0x%" PRIx64
|
||||
" in use of total space for memory devices 0x" RAM_ADDR_FMT,
|
||||
used_region_size, ms->maxram_size - ms->ram_size);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static uint64_t memory_device_get_free_addr(MachineState *ms,
|
||||
const uint64_t *hint,
|
||||
uint64_t align, uint64_t size,
|
||||
Error **errp)
|
||||
{
|
||||
GSList *list = NULL, *item;
|
||||
Range as, new = range_empty;
|
||||
|
||||
range_init_nofail(&as, ms->device_memory->base,
|
||||
memory_region_size(&ms->device_memory->mr));
|
||||
|
||||
/* start of address space indicates the maximum alignment we expect */
|
||||
if (!QEMU_IS_ALIGNED(range_lob(&as), align)) {
|
||||
warn_report("the alignment (0x%" PRIx64 ") exceeds the expected"
|
||||
" maximum alignment, memory will get fragmented and not"
|
||||
" all 'maxmem' might be usable for memory devices.",
|
||||
align);
|
||||
}
|
||||
|
||||
if (hint && !QEMU_IS_ALIGNED(*hint, align)) {
|
||||
error_setg(errp, "address must be aligned to 0x%" PRIx64 " bytes",
|
||||
align);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (hint) {
|
||||
if (range_init(&new, *hint, size) || !range_contains_range(&as, &new)) {
|
||||
error_setg(errp, "can't add memory device [0x%" PRIx64 ":0x%" PRIx64
|
||||
"], usable range for memory devices [0x%" PRIx64 ":0x%"
|
||||
PRIx64 "]", *hint, size, range_lob(&as),
|
||||
range_size(&as));
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (range_init(&new, QEMU_ALIGN_UP(range_lob(&as), align), size)) {
|
||||
error_setg(errp, "can't add memory device, device too big");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* find address range that will fit new memory device */
|
||||
object_child_foreach(OBJECT(ms), memory_device_build_list, &list);
|
||||
for (item = list; item; item = g_slist_next(item)) {
|
||||
const MemoryDeviceState *md = item->data;
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(OBJECT(md));
|
||||
uint64_t next_addr;
|
||||
Range tmp;
|
||||
|
||||
if (memory_device_is_empty(md)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
range_init_nofail(&tmp, mdc->get_addr(md),
|
||||
memory_device_get_region_size(md, &error_abort));
|
||||
|
||||
if (range_overlaps_range(&tmp, &new)) {
|
||||
if (hint) {
|
||||
const DeviceState *d = DEVICE(md);
|
||||
error_setg(errp, "address range conflicts with memory device"
|
||||
" id='%s'", d->id ? d->id : "(unnamed)");
|
||||
goto out;
|
||||
}
|
||||
|
||||
next_addr = QEMU_ALIGN_UP(range_upb(&tmp) + 1, align);
|
||||
if (!next_addr || range_init(&new, next_addr, range_size(&new))) {
|
||||
range_make_empty(&new);
|
||||
break;
|
||||
}
|
||||
} else if (range_lob(&tmp) > range_upb(&new)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!range_contains_range(&as, &new)) {
|
||||
error_setg(errp, "could not find position in guest address space for "
|
||||
"memory device - memory fragmented due to alignments");
|
||||
}
|
||||
out:
|
||||
g_slist_free(list);
|
||||
return range_lob(&new);
|
||||
}
|
||||
|
||||
MemoryDeviceInfoList *qmp_memory_device_list(void)
|
||||
{
|
||||
GSList *devices = NULL, *item;
|
||||
MemoryDeviceInfoList *list = NULL, **tail = &list;
|
||||
|
||||
object_child_foreach(qdev_get_machine(), memory_device_build_list,
|
||||
&devices);
|
||||
|
||||
for (item = devices; item; item = g_slist_next(item)) {
|
||||
const MemoryDeviceState *md = MEMORY_DEVICE(item->data);
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(item->data);
|
||||
MemoryDeviceInfo *info = g_new0(MemoryDeviceInfo, 1);
|
||||
|
||||
/* Let's query infotmation even for empty memory devices. */
|
||||
mdc->fill_device_info(md, info);
|
||||
|
||||
QAPI_LIST_APPEND(tail, info);
|
||||
}
|
||||
|
||||
g_slist_free(devices);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static int memory_device_plugged_size(Object *obj, void *opaque)
|
||||
{
|
||||
uint64_t *size = opaque;
|
||||
|
||||
if (object_dynamic_cast(obj, TYPE_MEMORY_DEVICE)) {
|
||||
const DeviceState *dev = DEVICE(obj);
|
||||
const MemoryDeviceState *md = MEMORY_DEVICE(obj);
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(obj);
|
||||
|
||||
if (qdev_is_realized(dev) && !memory_device_is_empty(md)) {
|
||||
*size += mdc->get_plugged_size(md, &error_abort);
|
||||
}
|
||||
}
|
||||
|
||||
object_child_foreach(obj, memory_device_plugged_size, opaque);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t get_plugged_memory_size(void)
|
||||
{
|
||||
uint64_t size = 0;
|
||||
|
||||
memory_device_plugged_size(qdev_get_machine(), &size);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void memory_device_pre_plug(MemoryDeviceState *md, MachineState *ms,
|
||||
Error **errp)
|
||||
{
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(md);
|
||||
Error *local_err = NULL;
|
||||
uint64_t addr, align = 0;
|
||||
MemoryRegion *mr;
|
||||
|
||||
/* We support empty memory devices even without device memory. */
|
||||
if (memory_device_is_empty(md)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ms->device_memory) {
|
||||
error_setg(errp, "the configuration is not prepared for memory devices"
|
||||
" (e.g., for memory hotplug), consider specifying the"
|
||||
" maxmem option");
|
||||
return;
|
||||
}
|
||||
|
||||
mr = mdc->get_memory_region(md, &local_err);
|
||||
if (local_err) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
memory_device_check_addable(ms, md, mr, &local_err);
|
||||
if (local_err) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* We always want the memory region size to be multiples of the memory
|
||||
* region alignment: for example, DIMMs with 1G+1byte size don't make
|
||||
* any sense. Note that we don't check that the size is multiples
|
||||
* of any additional alignment requirements the memory device might
|
||||
* have when it comes to the address in physical address space.
|
||||
*/
|
||||
if (!QEMU_IS_ALIGNED(memory_region_size(mr),
|
||||
memory_region_get_alignment(mr))) {
|
||||
error_setg(errp, "backend memory size must be multiple of 0x%"
|
||||
PRIx64, memory_region_get_alignment(mr));
|
||||
return;
|
||||
}
|
||||
|
||||
if (mdc->get_min_alignment) {
|
||||
align = mdc->get_min_alignment(md);
|
||||
}
|
||||
align = MAX(align, memory_region_get_alignment(mr));
|
||||
addr = mdc->get_addr(md);
|
||||
addr = memory_device_get_free_addr(ms, !addr ? NULL : &addr, align,
|
||||
memory_region_size(mr), &local_err);
|
||||
if (local_err) {
|
||||
goto out;
|
||||
}
|
||||
mdc->set_addr(md, addr, &local_err);
|
||||
if (!local_err) {
|
||||
trace_memory_device_pre_plug(DEVICE(md)->id ? DEVICE(md)->id : "",
|
||||
addr);
|
||||
}
|
||||
out:
|
||||
error_propagate(errp, local_err);
|
||||
}
|
||||
|
||||
void memory_device_plug(MemoryDeviceState *md, MachineState *ms)
|
||||
{
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(md);
|
||||
unsigned int memslots;
|
||||
uint64_t addr;
|
||||
MemoryRegion *mr;
|
||||
|
||||
if (memory_device_is_empty(md)) {
|
||||
return;
|
||||
}
|
||||
|
||||
memslots = memory_device_get_memslots(md);
|
||||
addr = mdc->get_addr(md);
|
||||
|
||||
/*
|
||||
* We expect that a previous call to memory_device_pre_plug() succeeded, so
|
||||
* it can't fail at this point.
|
||||
*/
|
||||
mr = mdc->get_memory_region(md, &error_abort);
|
||||
g_assert(ms->device_memory);
|
||||
|
||||
ms->device_memory->used_region_size += memory_region_size(mr);
|
||||
ms->device_memory->required_memslots += memslots;
|
||||
if (mdc->decide_memslots && memslots > 1) {
|
||||
ms->device_memory->memslot_auto_decision_active++;
|
||||
}
|
||||
|
||||
memory_region_add_subregion(&ms->device_memory->mr,
|
||||
addr - ms->device_memory->base, mr);
|
||||
trace_memory_device_plug(DEVICE(md)->id ? DEVICE(md)->id : "", addr);
|
||||
}
|
||||
|
||||
void memory_device_unplug(MemoryDeviceState *md, MachineState *ms)
|
||||
{
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(md);
|
||||
const unsigned int memslots = memory_device_get_memslots(md);
|
||||
MemoryRegion *mr;
|
||||
|
||||
if (memory_device_is_empty(md)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* We expect that a previous call to memory_device_pre_plug() succeeded, so
|
||||
* it can't fail at this point.
|
||||
*/
|
||||
mr = mdc->get_memory_region(md, &error_abort);
|
||||
g_assert(ms->device_memory);
|
||||
|
||||
memory_region_del_subregion(&ms->device_memory->mr, mr);
|
||||
|
||||
if (mdc->decide_memslots && memslots > 1) {
|
||||
ms->device_memory->memslot_auto_decision_active--;
|
||||
}
|
||||
ms->device_memory->used_region_size -= memory_region_size(mr);
|
||||
ms->device_memory->required_memslots -= memslots;
|
||||
trace_memory_device_unplug(DEVICE(md)->id ? DEVICE(md)->id : "",
|
||||
mdc->get_addr(md));
|
||||
}
|
||||
|
||||
uint64_t memory_device_get_region_size(const MemoryDeviceState *md,
|
||||
Error **errp)
|
||||
{
|
||||
const MemoryDeviceClass *mdc = MEMORY_DEVICE_GET_CLASS(md);
|
||||
MemoryRegion *mr;
|
||||
|
||||
/* dropping const here is fine as we don't touch the memory region */
|
||||
mr = mdc->get_memory_region((MemoryDeviceState *)md, errp);
|
||||
if (!mr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return memory_region_size(mr);
|
||||
}
|
||||
|
||||
static void memory_devices_region_mod(MemoryListener *listener,
|
||||
MemoryRegionSection *mrs, bool add)
|
||||
{
|
||||
DeviceMemoryState *dms = container_of(listener, DeviceMemoryState,
|
||||
listener);
|
||||
|
||||
if (!memory_region_is_ram(mrs->mr)) {
|
||||
warn_report("Unexpected memory region mapped into device memory region.");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The expectation is that each distinct RAM memory region section in
|
||||
* our region for memory devices consumes exactly one memslot in KVM
|
||||
* and in vhost. For vhost, this is true, except:
|
||||
* * ROM memory regions don't consume a memslot. These get used very
|
||||
* rarely for memory devices (R/O NVDIMMs).
|
||||
* * Memslots without a fd (memory-backend-ram) don't necessarily
|
||||
* consume a memslot. Such setups are quite rare and possibly bogus:
|
||||
* the memory would be inaccessible by such vhost devices.
|
||||
*
|
||||
* So for vhost, in corner cases we might over-estimate the number of
|
||||
* memslots that are currently used or that might still be reserved
|
||||
* (required - used).
|
||||
*/
|
||||
dms->used_memslots += add ? 1 : -1;
|
||||
|
||||
if (dms->used_memslots > dms->required_memslots) {
|
||||
warn_report("Memory devices use more memory slots than indicated as required.");
|
||||
}
|
||||
}
|
||||
|
||||
static void memory_devices_region_add(MemoryListener *listener,
|
||||
MemoryRegionSection *mrs)
|
||||
{
|
||||
return memory_devices_region_mod(listener, mrs, true);
|
||||
}
|
||||
|
||||
static void memory_devices_region_del(MemoryListener *listener,
|
||||
MemoryRegionSection *mrs)
|
||||
{
|
||||
return memory_devices_region_mod(listener, mrs, false);
|
||||
}
|
||||
|
||||
void machine_memory_devices_init(MachineState *ms, hwaddr base, uint64_t size)
|
||||
{
|
||||
g_assert(size);
|
||||
g_assert(!ms->device_memory);
|
||||
ms->device_memory = g_new0(DeviceMemoryState, 1);
|
||||
ms->device_memory->base = base;
|
||||
|
||||
memory_region_init(&ms->device_memory->mr, OBJECT(ms), "device-memory",
|
||||
size);
|
||||
address_space_init(&ms->device_memory->as, &ms->device_memory->mr,
|
||||
"device-memory");
|
||||
memory_region_add_subregion(get_system_memory(), ms->device_memory->base,
|
||||
&ms->device_memory->mr);
|
||||
|
||||
/* Track the number of memslots used by memory devices. */
|
||||
ms->device_memory->listener.region_add = memory_devices_region_add;
|
||||
ms->device_memory->listener.region_del = memory_devices_region_del;
|
||||
memory_listener_register(&ms->device_memory->listener,
|
||||
&ms->device_memory->as);
|
||||
}
|
||||
|
||||
static const TypeInfo memory_device_info = {
|
||||
.name = TYPE_MEMORY_DEVICE,
|
||||
.parent = TYPE_INTERFACE,
|
||||
.class_size = sizeof(MemoryDeviceClass),
|
||||
};
|
||||
|
||||
static void memory_device_register_types(void)
|
||||
{
|
||||
type_register_static(&memory_device_info);
|
||||
}
|
||||
|
||||
type_init(memory_device_register_types)
|
||||
@@ -0,0 +1,13 @@
|
||||
mem_ss = ss.source_set()
|
||||
mem_ss.add(files('memory-device.c'))
|
||||
mem_ss.add(when: 'CONFIG_DIMM', if_true: files('pc-dimm.c'))
|
||||
mem_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx_mc.c'))
|
||||
mem_ss.add(when: 'CONFIG_NVDIMM', if_true: files('nvdimm.c'))
|
||||
mem_ss.add(when: 'CONFIG_CXL_MEM_DEVICE', if_true: files('cxl_type3.c'))
|
||||
mem_ss.add(when: 'CONFIG_SP_MEM', if_true: files('sp-mem.c'))
|
||||
stub_ss.add(files('cxl_type3_stubs.c'))
|
||||
|
||||
stub_ss.add(files('memory-device-stubs.c'))
|
||||
system_ss.add_all(when: 'CONFIG_MEM_DEVICE', if_true: mem_ss)
|
||||
|
||||
system_ss.add(when: 'CONFIG_SPARSE_MEM', if_true: files('sparse-mem.c'))
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Nuvoton NPCM7xx Memory Controller stub
|
||||
*
|
||||
* Copyright 2020 Google LLC
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
|
||||
#include "hw/mem/npcm7xx_mc.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/units.h"
|
||||
|
||||
#define NPCM7XX_MC_REGS_SIZE (4 * KiB)
|
||||
|
||||
static uint64_t npcm7xx_mc_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
/*
|
||||
* If bits 8..11 @ offset 0 are not zero, the boot block thinks the memory
|
||||
* controller has already been initialized and will skip DDR training.
|
||||
*/
|
||||
if (addr == 0) {
|
||||
return 0x100;
|
||||
}
|
||||
|
||||
qemu_log_mask(LOG_UNIMP, "%s: mostly unimplemented\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void npcm7xx_mc_write(void *opaque, hwaddr addr, uint64_t v,
|
||||
unsigned int size)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: mostly unimplemented\n", __func__);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps npcm7xx_mc_ops = {
|
||||
.read = npcm7xx_mc_read,
|
||||
.write = npcm7xx_mc_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
.unaligned = false,
|
||||
},
|
||||
};
|
||||
|
||||
static void npcm7xx_mc_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
NPCM7xxMCState *s = NPCM7XX_MC(dev);
|
||||
|
||||
memory_region_init_io(&s->mmio, OBJECT(s), &npcm7xx_mc_ops, s, "regs",
|
||||
NPCM7XX_MC_REGS_SIZE);
|
||||
sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->mmio);
|
||||
}
|
||||
|
||||
static void npcm7xx_mc_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
dc->desc = "NPCM7xx Memory Controller stub";
|
||||
dc->realize = npcm7xx_mc_realize;
|
||||
}
|
||||
|
||||
static const TypeInfo npcm7xx_mc_types[] = {
|
||||
{
|
||||
.name = TYPE_NPCM7XX_MC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(NPCM7xxMCState),
|
||||
.class_init = npcm7xx_mc_class_init,
|
||||
},
|
||||
};
|
||||
DEFINE_TYPES(npcm7xx_mc_types);
|
||||
+285
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Non-Volatile Dual In-line Memory Module Virtualization Implementation
|
||||
*
|
||||
* Copyright(C) 2015 Intel Corporation.
|
||||
*
|
||||
* Author:
|
||||
* Xiao Guangrong <[email protected]>
|
||||
*
|
||||
* Currently, it only supports PMEM Virtualization.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qemu/pmem.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/visitor.h"
|
||||
#include "hw/mem/nvdimm.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/mem/memory-device.h"
|
||||
#include "system/hostmem.h"
|
||||
|
||||
static void nvdimm_get_label_size(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
NVDIMMDevice *nvdimm = NVDIMM(obj);
|
||||
uint64_t value = nvdimm->label_size;
|
||||
|
||||
visit_type_size(v, name, &value, errp);
|
||||
}
|
||||
|
||||
static void nvdimm_set_label_size(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
NVDIMMDevice *nvdimm = NVDIMM(obj);
|
||||
uint64_t value;
|
||||
|
||||
if (nvdimm->nvdimm_mr) {
|
||||
error_setg(errp, "cannot change property value");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!visit_type_size(v, name, &value, errp)) {
|
||||
return;
|
||||
}
|
||||
if (value < MIN_NAMESPACE_LABEL_SIZE) {
|
||||
error_setg(errp, "Property '%s.%s' (0x%" PRIx64 ") is required"
|
||||
" at least 0x%lx", object_get_typename(obj), name, value,
|
||||
MIN_NAMESPACE_LABEL_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
nvdimm->label_size = value;
|
||||
}
|
||||
|
||||
static void nvdimm_get_uuid(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
NVDIMMDevice *nvdimm = NVDIMM(obj);
|
||||
char *value = NULL;
|
||||
|
||||
value = qemu_uuid_unparse_strdup(&nvdimm->uuid);
|
||||
|
||||
visit_type_str(v, name, &value, errp);
|
||||
g_free(value);
|
||||
}
|
||||
|
||||
|
||||
static void nvdimm_set_uuid(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
NVDIMMDevice *nvdimm = NVDIMM(obj);
|
||||
char *value;
|
||||
|
||||
if (!visit_type_str(v, name, &value, errp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (qemu_uuid_parse(value, &nvdimm->uuid) != 0) {
|
||||
error_setg(errp, "Property '%s.%s' has invalid value",
|
||||
object_get_typename(obj), name);
|
||||
}
|
||||
|
||||
g_free(value);
|
||||
}
|
||||
|
||||
|
||||
static void nvdimm_init(Object *obj)
|
||||
{
|
||||
object_property_add(obj, NVDIMM_LABEL_SIZE_PROP, "size",
|
||||
nvdimm_get_label_size, nvdimm_set_label_size, NULL,
|
||||
NULL);
|
||||
|
||||
object_property_add(obj, NVDIMM_UUID_PROP, "QemuUUID", nvdimm_get_uuid,
|
||||
nvdimm_set_uuid, NULL, NULL);
|
||||
}
|
||||
|
||||
static void nvdimm_finalize(Object *obj)
|
||||
{
|
||||
NVDIMMDevice *nvdimm = NVDIMM(obj);
|
||||
|
||||
g_free(nvdimm->nvdimm_mr);
|
||||
}
|
||||
|
||||
static void nvdimm_prepare_memory_region(NVDIMMDevice *nvdimm, Error **errp)
|
||||
{
|
||||
PCDIMMDevice *dimm = PC_DIMM(nvdimm);
|
||||
uint64_t align, pmem_size, size;
|
||||
MemoryRegion *mr;
|
||||
|
||||
g_assert(!nvdimm->nvdimm_mr);
|
||||
|
||||
if (!dimm->hostmem) {
|
||||
error_setg(errp, "'" PC_DIMM_MEMDEV_PROP "' property must be set");
|
||||
return;
|
||||
}
|
||||
|
||||
mr = host_memory_backend_get_memory(dimm->hostmem);
|
||||
align = memory_region_get_alignment(mr);
|
||||
size = memory_region_size(mr);
|
||||
|
||||
pmem_size = size - nvdimm->label_size;
|
||||
nvdimm->label_data = memory_region_get_ram_ptr(mr) + pmem_size;
|
||||
pmem_size = QEMU_ALIGN_DOWN(pmem_size, align);
|
||||
|
||||
if (size <= nvdimm->label_size || !pmem_size) {
|
||||
HostMemoryBackend *hostmem = dimm->hostmem;
|
||||
|
||||
error_setg(errp, "the size of memdev %s (0x%" PRIx64 ") is too "
|
||||
"small to contain nvdimm label (0x%" PRIx64 ") and "
|
||||
"aligned PMEM (0x%" PRIx64 ")",
|
||||
object_get_canonical_path_component(OBJECT(hostmem)),
|
||||
memory_region_size(mr), nvdimm->label_size, align);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nvdimm->unarmed && memory_region_is_rom(mr)) {
|
||||
HostMemoryBackend *hostmem = dimm->hostmem;
|
||||
|
||||
error_setg(errp, "'unarmed' property must be 'on' since memdev %s "
|
||||
"is read-only",
|
||||
object_get_canonical_path_component(OBJECT(hostmem)));
|
||||
return;
|
||||
}
|
||||
if (memory_region_is_rom(mr)) {
|
||||
nvdimm->readonly = true;
|
||||
}
|
||||
|
||||
nvdimm->nvdimm_mr = g_new(MemoryRegion, 1);
|
||||
memory_region_init_alias(nvdimm->nvdimm_mr, OBJECT(dimm),
|
||||
"nvdimm-memory", mr, 0, pmem_size);
|
||||
memory_region_set_nonvolatile(nvdimm->nvdimm_mr, true);
|
||||
nvdimm->nvdimm_mr->align = align;
|
||||
}
|
||||
|
||||
static MemoryRegion *nvdimm_md_get_memory_region(MemoryDeviceState *md,
|
||||
Error **errp)
|
||||
{
|
||||
NVDIMMDevice *nvdimm = NVDIMM(md);
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (!nvdimm->nvdimm_mr) {
|
||||
nvdimm_prepare_memory_region(nvdimm, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return nvdimm->nvdimm_mr;
|
||||
}
|
||||
|
||||
static void nvdimm_realize(PCDIMMDevice *dimm, Error **errp)
|
||||
{
|
||||
NVDIMMDevice *nvdimm = NVDIMM(dimm);
|
||||
NVDIMMClass *ndc = NVDIMM_GET_CLASS(nvdimm);
|
||||
|
||||
if (!nvdimm->nvdimm_mr) {
|
||||
nvdimm_prepare_memory_region(nvdimm, errp);
|
||||
}
|
||||
|
||||
if (ndc->realize) {
|
||||
ndc->realize(nvdimm, errp);
|
||||
}
|
||||
}
|
||||
|
||||
static void nvdimm_unrealize(PCDIMMDevice *dimm)
|
||||
{
|
||||
NVDIMMDevice *nvdimm = NVDIMM(dimm);
|
||||
NVDIMMClass *ndc = NVDIMM_GET_CLASS(nvdimm);
|
||||
|
||||
if (ndc->unrealize) {
|
||||
ndc->unrealize(nvdimm);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* the caller should check the input parameters before calling
|
||||
* label read/write functions.
|
||||
*/
|
||||
static void nvdimm_validate_rw_label_data(NVDIMMDevice *nvdimm, uint64_t size,
|
||||
uint64_t offset, bool is_write)
|
||||
{
|
||||
assert((nvdimm->label_size >= size + offset) && (offset + size > offset));
|
||||
assert(!is_write || !nvdimm->readonly);
|
||||
}
|
||||
|
||||
static void nvdimm_read_label_data(NVDIMMDevice *nvdimm, void *buf,
|
||||
uint64_t size, uint64_t offset)
|
||||
{
|
||||
nvdimm_validate_rw_label_data(nvdimm, size, offset, false);
|
||||
|
||||
memcpy(buf, nvdimm->label_data + offset, size);
|
||||
}
|
||||
|
||||
static void nvdimm_write_label_data(NVDIMMDevice *nvdimm, const void *buf,
|
||||
uint64_t size, uint64_t offset)
|
||||
{
|
||||
MemoryRegion *mr;
|
||||
PCDIMMDevice *dimm = PC_DIMM(nvdimm);
|
||||
bool is_pmem = object_property_get_bool(OBJECT(dimm->hostmem),
|
||||
"pmem", NULL);
|
||||
uint64_t backend_offset;
|
||||
|
||||
nvdimm_validate_rw_label_data(nvdimm, size, offset, true);
|
||||
|
||||
if (!is_pmem) {
|
||||
memcpy(nvdimm->label_data + offset, buf, size);
|
||||
} else {
|
||||
pmem_memcpy_persist(nvdimm->label_data + offset, buf, size);
|
||||
}
|
||||
|
||||
mr = host_memory_backend_get_memory(dimm->hostmem);
|
||||
backend_offset = memory_region_size(mr) - nvdimm->label_size + offset;
|
||||
memory_region_set_dirty(mr, backend_offset, size);
|
||||
}
|
||||
|
||||
static const Property nvdimm_properties[] = {
|
||||
DEFINE_PROP_BOOL(NVDIMM_UNARMED_PROP, NVDIMMDevice, unarmed, false),
|
||||
};
|
||||
|
||||
static void nvdimm_class_init(ObjectClass *oc, const void *data)
|
||||
{
|
||||
PCDIMMDeviceClass *ddc = PC_DIMM_CLASS(oc);
|
||||
MemoryDeviceClass *mdc = MEMORY_DEVICE_CLASS(oc);
|
||||
NVDIMMClass *nvc = NVDIMM_CLASS(oc);
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
|
||||
ddc->realize = nvdimm_realize;
|
||||
ddc->unrealize = nvdimm_unrealize;
|
||||
mdc->get_memory_region = nvdimm_md_get_memory_region;
|
||||
device_class_set_props(dc, nvdimm_properties);
|
||||
|
||||
nvc->read_label_data = nvdimm_read_label_data;
|
||||
nvc->write_label_data = nvdimm_write_label_data;
|
||||
set_bit(DEVICE_CATEGORY_STORAGE, dc->categories);
|
||||
}
|
||||
|
||||
static const TypeInfo nvdimm_info = {
|
||||
.name = TYPE_NVDIMM,
|
||||
.parent = TYPE_PC_DIMM,
|
||||
.class_size = sizeof(NVDIMMClass),
|
||||
.class_init = nvdimm_class_init,
|
||||
.instance_size = sizeof(NVDIMMDevice),
|
||||
.instance_init = nvdimm_init,
|
||||
.instance_finalize = nvdimm_finalize,
|
||||
};
|
||||
|
||||
static void nvdimm_register_types(void)
|
||||
{
|
||||
type_register_static(&nvdimm_info);
|
||||
}
|
||||
|
||||
type_init(nvdimm_register_types)
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* Dimm device for Memory Hotplug
|
||||
*
|
||||
* Copyright ProfitBricks GmbH 2012
|
||||
* Copyright (C) 2014 Red Hat Inc
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library 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
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/core/boards.h"
|
||||
#include "hw/mem/pc-dimm.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "migration/vmstate.h"
|
||||
#include "hw/mem/nvdimm.h"
|
||||
#include "hw/mem/memory-device.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/visitor.h"
|
||||
#include "qemu/module.h"
|
||||
#include "system/hostmem.h"
|
||||
#include "system/numa.h"
|
||||
#include "trace.h"
|
||||
|
||||
static int pc_dimm_get_free_slot(const int *hint, int max_slots, Error **errp);
|
||||
|
||||
static MemoryRegion *pc_dimm_get_memory_region(PCDIMMDevice *dimm, Error **errp)
|
||||
{
|
||||
if (!dimm->hostmem) {
|
||||
error_setg(errp, "'" PC_DIMM_MEMDEV_PROP "' property must be set");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return host_memory_backend_get_memory(dimm->hostmem);
|
||||
}
|
||||
|
||||
void pc_dimm_pre_plug(PCDIMMDevice *dimm, MachineState *machine, Error **errp)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int slot;
|
||||
|
||||
slot = object_property_get_int(OBJECT(dimm), PC_DIMM_SLOT_PROP,
|
||||
&error_abort);
|
||||
if ((slot < 0 || slot >= machine->ram_slots) &&
|
||||
slot != PC_DIMM_UNASSIGNED_SLOT) {
|
||||
error_setg(errp,
|
||||
"invalid slot number %d, valid range is [0-%" PRIu64 "]",
|
||||
slot, machine->ram_slots - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
slot = pc_dimm_get_free_slot(slot == PC_DIMM_UNASSIGNED_SLOT ? NULL : &slot,
|
||||
machine->ram_slots, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
}
|
||||
object_property_set_int(OBJECT(dimm), PC_DIMM_SLOT_PROP, slot,
|
||||
&error_abort);
|
||||
trace_mhp_pc_dimm_assigned_slot(slot);
|
||||
|
||||
memory_device_pre_plug(MEMORY_DEVICE(dimm), machine, errp);
|
||||
}
|
||||
|
||||
void pc_dimm_plug(PCDIMMDevice *dimm, MachineState *machine)
|
||||
{
|
||||
MemoryRegion *vmstate_mr = pc_dimm_get_memory_region(dimm,
|
||||
&error_abort);
|
||||
|
||||
memory_device_plug(MEMORY_DEVICE(dimm), machine);
|
||||
vmstate_register_ram(vmstate_mr, DEVICE(dimm));
|
||||
/* count only "real" DIMMs, not NVDIMMs */
|
||||
if (!object_dynamic_cast(OBJECT(dimm), TYPE_NVDIMM)) {
|
||||
machine->device_memory->dimm_size += memory_region_size(vmstate_mr);
|
||||
}
|
||||
}
|
||||
|
||||
void pc_dimm_unplug(PCDIMMDevice *dimm, MachineState *machine)
|
||||
{
|
||||
MemoryRegion *vmstate_mr = pc_dimm_get_memory_region(dimm,
|
||||
&error_abort);
|
||||
|
||||
memory_device_unplug(MEMORY_DEVICE(dimm), machine);
|
||||
vmstate_unregister_ram(vmstate_mr, DEVICE(dimm));
|
||||
if (!object_dynamic_cast(OBJECT(dimm), TYPE_NVDIMM)) {
|
||||
machine->device_memory->dimm_size -= memory_region_size(vmstate_mr);
|
||||
}
|
||||
}
|
||||
|
||||
static int pc_dimm_slot2bitmap(Object *obj, void *opaque)
|
||||
{
|
||||
unsigned long *bitmap = opaque;
|
||||
|
||||
if (object_dynamic_cast(obj, TYPE_PC_DIMM)) {
|
||||
DeviceState *dev = DEVICE(obj);
|
||||
if (qdev_is_realized(dev)) { /* count only realized DIMMs */
|
||||
PCDIMMDevice *d = PC_DIMM(obj);
|
||||
set_bit(d->slot, bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
object_child_foreach(obj, pc_dimm_slot2bitmap, opaque);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pc_dimm_get_free_slot(const int *hint, int max_slots, Error **errp)
|
||||
{
|
||||
unsigned long *bitmap;
|
||||
int slot = 0;
|
||||
|
||||
if (max_slots <= 0) {
|
||||
error_setg(errp, "no slots where allocated, please specify "
|
||||
"the 'slots' option");
|
||||
return slot;
|
||||
}
|
||||
|
||||
bitmap = bitmap_new(max_slots);
|
||||
object_child_foreach(qdev_get_machine(), pc_dimm_slot2bitmap, bitmap);
|
||||
|
||||
/* check if requested slot is not occupied */
|
||||
if (hint) {
|
||||
if (*hint >= max_slots) {
|
||||
error_setg(errp, "invalid slot# %d, should be less than %d",
|
||||
*hint, max_slots);
|
||||
} else if (!test_bit(*hint, bitmap)) {
|
||||
slot = *hint;
|
||||
} else {
|
||||
error_setg(errp, "slot %d is busy", *hint);
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* search for free slot */
|
||||
slot = find_first_zero_bit(bitmap, max_slots);
|
||||
if (slot == max_slots) {
|
||||
error_setg(errp, "no free slots available");
|
||||
}
|
||||
out:
|
||||
g_free(bitmap);
|
||||
return slot;
|
||||
}
|
||||
|
||||
static const Property pc_dimm_properties[] = {
|
||||
DEFINE_PROP_UINT64(PC_DIMM_ADDR_PROP, PCDIMMDevice, addr, 0),
|
||||
DEFINE_PROP_UINT32(PC_DIMM_NODE_PROP, PCDIMMDevice, node, 0),
|
||||
DEFINE_PROP_INT32(PC_DIMM_SLOT_PROP, PCDIMMDevice, slot,
|
||||
PC_DIMM_UNASSIGNED_SLOT),
|
||||
DEFINE_PROP_LINK(PC_DIMM_MEMDEV_PROP, PCDIMMDevice, hostmem,
|
||||
TYPE_MEMORY_BACKEND, HostMemoryBackend *),
|
||||
};
|
||||
|
||||
static void pc_dimm_get_size(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
uint64_t value;
|
||||
|
||||
value = memory_device_get_region_size(MEMORY_DEVICE(obj), &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
}
|
||||
|
||||
visit_type_uint64(v, name, &value, errp);
|
||||
}
|
||||
|
||||
static void pc_dimm_init(Object *obj)
|
||||
{
|
||||
object_property_add(obj, PC_DIMM_SIZE_PROP, "uint64", pc_dimm_get_size,
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static void pc_dimm_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
PCDIMMDevice *dimm = PC_DIMM(dev);
|
||||
PCDIMMDeviceClass *ddc = PC_DIMM_GET_CLASS(dimm);
|
||||
MachineState *ms = MACHINE(qdev_get_machine());
|
||||
|
||||
if (ms->numa_state) {
|
||||
int nb_numa_nodes = ms->numa_state->num_nodes;
|
||||
|
||||
if (((nb_numa_nodes > 0) && (dimm->node >= nb_numa_nodes)) ||
|
||||
(!nb_numa_nodes && dimm->node)) {
|
||||
error_setg(errp, "'DIMM property " PC_DIMM_NODE_PROP " has value %"
|
||||
PRIu32 "' which exceeds the number of numa nodes: %d",
|
||||
dimm->node, nb_numa_nodes ? nb_numa_nodes : 1);
|
||||
return;
|
||||
}
|
||||
} else if (dimm->node > 0) {
|
||||
error_setg(errp, "machine doesn't support NUMA");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dimm->hostmem) {
|
||||
error_setg(errp, "'" PC_DIMM_MEMDEV_PROP "' property is not set");
|
||||
return;
|
||||
} else if (host_memory_backend_is_mapped(dimm->hostmem)) {
|
||||
error_setg(errp, "can't use already busy memdev: %s",
|
||||
object_get_canonical_path_component(OBJECT(dimm->hostmem)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ddc->realize) {
|
||||
ddc->realize(dimm, errp);
|
||||
}
|
||||
|
||||
host_memory_backend_set_mapped(dimm->hostmem, true);
|
||||
}
|
||||
|
||||
static void pc_dimm_unrealize(DeviceState *dev)
|
||||
{
|
||||
PCDIMMDevice *dimm = PC_DIMM(dev);
|
||||
PCDIMMDeviceClass *ddc = PC_DIMM_GET_CLASS(dimm);
|
||||
|
||||
if (ddc->unrealize) {
|
||||
ddc->unrealize(dimm);
|
||||
}
|
||||
|
||||
host_memory_backend_set_mapped(dimm->hostmem, false);
|
||||
}
|
||||
|
||||
static uint64_t pc_dimm_md_get_addr(const MemoryDeviceState *md)
|
||||
{
|
||||
return object_property_get_uint(OBJECT(md), PC_DIMM_ADDR_PROP,
|
||||
&error_abort);
|
||||
}
|
||||
|
||||
static void pc_dimm_md_set_addr(MemoryDeviceState *md, uint64_t addr,
|
||||
Error **errp)
|
||||
{
|
||||
object_property_set_uint(OBJECT(md), PC_DIMM_ADDR_PROP, addr, errp);
|
||||
}
|
||||
|
||||
static MemoryRegion *pc_dimm_md_get_memory_region(MemoryDeviceState *md,
|
||||
Error **errp)
|
||||
{
|
||||
return pc_dimm_get_memory_region(PC_DIMM(md), errp);
|
||||
}
|
||||
|
||||
static void pc_dimm_md_fill_device_info(const MemoryDeviceState *md,
|
||||
MemoryDeviceInfo *info)
|
||||
{
|
||||
PCDIMMDeviceInfo *di = g_new0(PCDIMMDeviceInfo, 1);
|
||||
const DeviceClass *dc = DEVICE_GET_CLASS(md);
|
||||
const PCDIMMDevice *dimm = PC_DIMM(md);
|
||||
const DeviceState *dev = DEVICE(md);
|
||||
|
||||
if (dev->id) {
|
||||
di->id = g_strdup(dev->id);
|
||||
}
|
||||
di->hotplugged = dev->hotplugged;
|
||||
di->hotpluggable = dc->hotpluggable;
|
||||
di->addr = dimm->addr;
|
||||
di->slot = dimm->slot;
|
||||
di->node = dimm->node;
|
||||
di->size = object_property_get_uint(OBJECT(dimm), PC_DIMM_SIZE_PROP,
|
||||
NULL);
|
||||
di->memdev = object_get_canonical_path(OBJECT(dimm->hostmem));
|
||||
|
||||
if (object_dynamic_cast(OBJECT(dev), TYPE_NVDIMM)) {
|
||||
info->u.nvdimm.data = di;
|
||||
info->type = MEMORY_DEVICE_INFO_KIND_NVDIMM;
|
||||
} else {
|
||||
info->u.dimm.data = di;
|
||||
info->type = MEMORY_DEVICE_INFO_KIND_DIMM;
|
||||
}
|
||||
}
|
||||
|
||||
static void pc_dimm_class_init(ObjectClass *oc, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
MemoryDeviceClass *mdc = MEMORY_DEVICE_CLASS(oc);
|
||||
|
||||
dc->realize = pc_dimm_realize;
|
||||
dc->unrealize = pc_dimm_unrealize;
|
||||
device_class_set_props(dc, pc_dimm_properties);
|
||||
dc->desc = "DIMM memory module";
|
||||
|
||||
mdc->get_addr = pc_dimm_md_get_addr;
|
||||
mdc->set_addr = pc_dimm_md_set_addr;
|
||||
/* for a dimm plugged_size == region_size */
|
||||
mdc->get_plugged_size = memory_device_get_region_size;
|
||||
mdc->get_memory_region = pc_dimm_md_get_memory_region;
|
||||
mdc->fill_device_info = pc_dimm_md_fill_device_info;
|
||||
}
|
||||
|
||||
static const TypeInfo pc_dimm_info = {
|
||||
.name = TYPE_PC_DIMM,
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(PCDIMMDevice),
|
||||
.instance_init = pc_dimm_init,
|
||||
.class_init = pc_dimm_class_init,
|
||||
.class_size = sizeof(PCDIMMDeviceClass),
|
||||
.interfaces = (const InterfaceInfo[]) {
|
||||
{ TYPE_MEMORY_DEVICE },
|
||||
{ }
|
||||
},
|
||||
};
|
||||
|
||||
static void pc_dimm_register_types(void)
|
||||
{
|
||||
type_register_static(&pc_dimm_info);
|
||||
}
|
||||
|
||||
type_init(pc_dimm_register_types)
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Specific Purpose Memory (SPM) device
|
||||
*
|
||||
* Copyright (c) 2026 Advanced Micro Devices, Inc.
|
||||
*
|
||||
* Authors:
|
||||
* FangSheng Huang <[email protected]>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/module.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/core/qdev.h"
|
||||
#include "hw/mem/sp-mem.h"
|
||||
#include "hw/mem/memory-device.h"
|
||||
#include "system/hostmem.h"
|
||||
|
||||
#define SP_MEM_MEMDEV_PROP "memdev"
|
||||
#define SP_MEM_NODE_PROP "node"
|
||||
#define SP_MEM_ADDR_PROP "addr"
|
||||
|
||||
static const Property sp_mem_properties[] = {
|
||||
DEFINE_PROP_LINK(SP_MEM_MEMDEV_PROP, SpMemDevice, hostmem,
|
||||
TYPE_MEMORY_BACKEND, HostMemoryBackend *),
|
||||
DEFINE_PROP_UINT32(SP_MEM_NODE_PROP, SpMemDevice, node, 0),
|
||||
DEFINE_PROP_UINT64(SP_MEM_ADDR_PROP, SpMemDevice, addr, 0),
|
||||
};
|
||||
|
||||
static uint64_t sp_mem_get_addr(const MemoryDeviceState *md)
|
||||
{
|
||||
return object_property_get_uint(OBJECT(md), SP_MEM_ADDR_PROP,
|
||||
&error_abort);
|
||||
}
|
||||
|
||||
static void sp_mem_set_addr(MemoryDeviceState *md, uint64_t addr,
|
||||
Error **errp)
|
||||
{
|
||||
object_property_set_uint(OBJECT(md), SP_MEM_ADDR_PROP, addr, errp);
|
||||
}
|
||||
|
||||
static MemoryRegion *sp_mem_get_memory_region(MemoryDeviceState *md,
|
||||
Error **errp)
|
||||
{
|
||||
SpMemDevice *spm = SP_MEM(md);
|
||||
|
||||
if (!spm->hostmem) {
|
||||
error_setg(errp, "'%s' property must be set", SP_MEM_MEMDEV_PROP);
|
||||
return NULL;
|
||||
}
|
||||
return host_memory_backend_get_memory(spm->hostmem);
|
||||
}
|
||||
|
||||
static void sp_mem_fill_device_info(const MemoryDeviceState *md,
|
||||
MemoryDeviceInfo *info)
|
||||
{
|
||||
SpMemDeviceInfo *di = g_new0(SpMemDeviceInfo, 1);
|
||||
SpMemDevice *spm = SP_MEM(md);
|
||||
DeviceState *dev = DEVICE(md);
|
||||
|
||||
di->id = dev->id ? g_strdup(dev->id) : NULL;
|
||||
di->addr = spm->addr;
|
||||
di->size = memory_region_size(
|
||||
host_memory_backend_get_memory(spm->hostmem));
|
||||
di->node = spm->node;
|
||||
di->memdev = object_get_canonical_path(OBJECT(spm->hostmem));
|
||||
|
||||
info->u.sp_mem.data = di;
|
||||
info->type = MEMORY_DEVICE_INFO_KIND_SP_MEM;
|
||||
}
|
||||
|
||||
static void sp_mem_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
SpMemDevice *spm = SP_MEM(dev);
|
||||
|
||||
if (!spm->hostmem) {
|
||||
error_setg(errp, "'%s' property is required", SP_MEM_MEMDEV_PROP);
|
||||
return;
|
||||
}
|
||||
if (host_memory_backend_is_mapped(spm->hostmem)) {
|
||||
error_setg(errp, "memory backend '%s' is already in use",
|
||||
object_get_canonical_path_component(OBJECT(spm->hostmem)));
|
||||
return;
|
||||
}
|
||||
host_memory_backend_set_mapped(spm->hostmem, true);
|
||||
}
|
||||
|
||||
static void sp_mem_unrealize(DeviceState *dev)
|
||||
{
|
||||
SpMemDevice *spm = SP_MEM(dev);
|
||||
|
||||
host_memory_backend_set_mapped(spm->hostmem, false);
|
||||
}
|
||||
|
||||
static void sp_mem_class_init(ObjectClass *oc, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
MemoryDeviceClass *mdc = MEMORY_DEVICE_CLASS(oc);
|
||||
|
||||
dc->desc = "SPM (Specific Purpose Memory) device";
|
||||
dc->hotpluggable = false;
|
||||
dc->realize = sp_mem_realize;
|
||||
dc->unrealize = sp_mem_unrealize;
|
||||
device_class_set_props(dc, sp_mem_properties);
|
||||
|
||||
mdc->get_addr = sp_mem_get_addr;
|
||||
mdc->set_addr = sp_mem_set_addr;
|
||||
mdc->get_memory_region = sp_mem_get_memory_region;
|
||||
mdc->get_plugged_size = memory_device_get_region_size;
|
||||
mdc->fill_device_info = sp_mem_fill_device_info;
|
||||
}
|
||||
|
||||
static const TypeInfo sp_mem_types[] = {
|
||||
{
|
||||
.name = TYPE_SP_MEM,
|
||||
.parent = TYPE_DEVICE,
|
||||
.class_init = sp_mem_class_init,
|
||||
.instance_size = sizeof(SpMemDevice),
|
||||
.interfaces = (InterfaceInfo[]) {
|
||||
{ TYPE_MEMORY_DEVICE },
|
||||
{ }
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
DEFINE_TYPES(sp_mem_types)
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* A sparse memory device. Useful for fuzzing
|
||||
*
|
||||
* Copyright Red Hat Inc., 2021
|
||||
*
|
||||
* Authors:
|
||||
* Alexander Bulekov <[email protected]>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/units.h"
|
||||
#include "system/qtest.h"
|
||||
#include "hw/mem/sparse-mem.h"
|
||||
|
||||
#define SPARSE_MEM(obj) OBJECT_CHECK(SparseMemState, (obj), TYPE_SPARSE_MEM)
|
||||
#define SPARSE_BLOCK_SIZE 0x1000
|
||||
|
||||
typedef struct SparseMemState {
|
||||
SysBusDevice parent_obj;
|
||||
MemoryRegion mmio;
|
||||
uint64_t baseaddr;
|
||||
uint64_t length;
|
||||
uint64_t size_used;
|
||||
uint64_t maxsize;
|
||||
GHashTable *mapped;
|
||||
} SparseMemState;
|
||||
|
||||
typedef struct sparse_mem_block {
|
||||
uint8_t data[SPARSE_BLOCK_SIZE];
|
||||
} sparse_mem_block;
|
||||
|
||||
static uint64_t sparse_mem_read(void *opaque, hwaddr addr, unsigned int size)
|
||||
{
|
||||
SparseMemState *s = opaque;
|
||||
uint64_t ret = 0;
|
||||
size_t pfn = addr / SPARSE_BLOCK_SIZE;
|
||||
size_t offset = addr % SPARSE_BLOCK_SIZE;
|
||||
sparse_mem_block *block;
|
||||
|
||||
block = g_hash_table_lookup(s->mapped, (void *)pfn);
|
||||
if (block) {
|
||||
assert(offset + size <= sizeof(block->data));
|
||||
memcpy(&ret, block->data + offset, size);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sparse_mem_write(void *opaque, hwaddr addr, uint64_t v,
|
||||
unsigned int size)
|
||||
{
|
||||
SparseMemState *s = opaque;
|
||||
size_t pfn = addr / SPARSE_BLOCK_SIZE;
|
||||
size_t offset = addr % SPARSE_BLOCK_SIZE;
|
||||
sparse_mem_block *block;
|
||||
|
||||
if (!g_hash_table_lookup(s->mapped, (void *)pfn) &&
|
||||
s->size_used + SPARSE_BLOCK_SIZE < s->maxsize && v) {
|
||||
g_hash_table_insert(s->mapped, (void *)pfn,
|
||||
g_new0(sparse_mem_block, 1));
|
||||
s->size_used += sizeof(block->data);
|
||||
}
|
||||
block = g_hash_table_lookup(s->mapped, (void *)pfn);
|
||||
if (!block) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(offset + size <= sizeof(block->data));
|
||||
|
||||
memcpy(block->data + offset, &v, size);
|
||||
|
||||
}
|
||||
|
||||
static void sparse_mem_enter_reset(Object *obj, ResetType type)
|
||||
{
|
||||
SparseMemState *s = SPARSE_MEM(obj);
|
||||
g_hash_table_remove_all(s->mapped);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps sparse_mem_ops = {
|
||||
.read = sparse_mem_read,
|
||||
.write = sparse_mem_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 1,
|
||||
.max_access_size = 8,
|
||||
.unaligned = false,
|
||||
},
|
||||
};
|
||||
|
||||
static const Property sparse_mem_properties[] = {
|
||||
/* The base address of the memory */
|
||||
DEFINE_PROP_UINT64("baseaddr", SparseMemState, baseaddr, 0x0),
|
||||
/* The length of the sparse memory region */
|
||||
DEFINE_PROP_UINT64("length", SparseMemState, length, UINT64_MAX),
|
||||
/* Max amount of actual memory that can be used to back the sparse memory */
|
||||
DEFINE_PROP_UINT64("maxsize", SparseMemState, maxsize, 10 * MiB),
|
||||
};
|
||||
|
||||
MemoryRegion *sparse_mem_init(uint64_t addr, uint64_t length)
|
||||
{
|
||||
DeviceState *dev;
|
||||
|
||||
dev = qdev_new(TYPE_SPARSE_MEM);
|
||||
qdev_prop_set_uint64(dev, "baseaddr", addr);
|
||||
qdev_prop_set_uint64(dev, "length", length);
|
||||
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
|
||||
sysbus_mmio_map_overlap(SYS_BUS_DEVICE(dev), 0, addr, -10000);
|
||||
return &SPARSE_MEM(dev)->mmio;
|
||||
}
|
||||
|
||||
static void sparse_mem_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
SparseMemState *s = SPARSE_MEM(dev);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
||||
|
||||
if (!qtest_enabled()) {
|
||||
error_setg(errp, "sparse_mem device should only be used "
|
||||
"for testing with QTest");
|
||||
return;
|
||||
}
|
||||
|
||||
assert(s->baseaddr + s->length > s->baseaddr);
|
||||
|
||||
s->mapped = g_hash_table_new_full(NULL, NULL, NULL,
|
||||
(GDestroyNotify)g_free);
|
||||
memory_region_init_io(&s->mmio, OBJECT(s), &sparse_mem_ops, s,
|
||||
"sparse-mem", s->length);
|
||||
sysbus_init_mmio(sbd, &s->mmio);
|
||||
}
|
||||
|
||||
static void sparse_mem_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_props(dc, sparse_mem_properties);
|
||||
|
||||
dc->desc = "Sparse Memory Device";
|
||||
dc->realize = sparse_mem_realize;
|
||||
|
||||
rc->phases.enter = sparse_mem_enter_reset;
|
||||
}
|
||||
|
||||
static const TypeInfo sparse_mem_types[] = {
|
||||
{
|
||||
.name = TYPE_SPARSE_MEM,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(SparseMemState),
|
||||
.class_init = sparse_mem_class_init,
|
||||
},
|
||||
};
|
||||
DEFINE_TYPES(sparse_mem_types);
|
||||
@@ -0,0 +1,8 @@
|
||||
# See docs/devel/tracing.rst for syntax documentation.
|
||||
|
||||
# pc-dimm.c
|
||||
mhp_pc_dimm_assigned_slot(int slot) "%d"
|
||||
# memory-device.c
|
||||
memory_device_pre_plug(const char *id, uint64_t addr) "id=%s addr=0x%"PRIx64
|
||||
memory_device_plug(const char *id, uint64_t addr) "id=%s addr=0x%"PRIx64
|
||||
memory_device_unplug(const char *id, uint64_t addr) "id=%s addr=0x%"PRIx64
|
||||
@@ -0,0 +1 @@
|
||||
#include "trace/trace-hw_mem.h"
|
||||
Reference in New Issue
Block a user