/*
 * QEMU Remote Memory Backend
 *
 * Copyright (C) 2013-2014 Red Hat Inc
 * Copyright (C) 2018 Virtual Open Systems
 *
 * Authors:
 *   Igor Mammedov <imammedo@redhat.com>
 *   Benjamin Boutin <b.boutin@virtualopensystems.com>
 *   Julian Vetter <j.vetter@virtualopensystems.com>
 *
 * 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 "sysemu/hostmem-remote.h"
#include "qapi/qapi-types-misc.h"
#include "sysemu/sysemu.h"
#include "sysemu/kvm.h"
#include "dboxmm.h"
#include "hw/hw.h"
#include "migration/register.h"
#include "migration/misc.h"

#define REMOTE_BACKEND_REALLOC_FLAG 0x1

HostMemoryBackendRemote *rb_pointer;
static uint32_t instances_nb = 0;

static uint32_t get_inst_nb(void) {
    return instances_nb;
}

static void remote_backend_instance_unparent(Object *obj)
{
    /* Throw an hardware error when the last chunk of memory is removed. */
    if (instances_nb == 1) {
        hw_error("It's not possible to detach your primary RAM section.\n");
    }
    instances_nb--;

    fprintf(stderr, "instances decremented: %u\n", instances_nb);
}

static void
remote_backend_memory_destructor(MemoryRegion *mr)
{
    memory_region_free_ram_block_host_ptr(mr, dbox_free);
}

static void
remote_backend_memory_complete(UserCreatable *uc, Error **errp)
{
    char *path;
    UuidInfo* info;
    void *host_ptr;

    HostMemoryBackendRemote *rb = MEMORY_BACKEND_REMOTE(uc);
    HostMemoryBackend *mb = MEMORY_BACKEND(rb);

    if (!mb->size) {
        error_setg(errp, "can't create backend with size 0");
        return;
    }

    path = object_get_canonical_path_component(OBJECT(mb));

    /* Allocate an empty section ID (it will be set later
     * in the actual allocation)*/
    rb->secid = g_malloc(strlen(UUID_NONE) + 1);

    memory_region_init_ram_remote_nomigrate(&mb->mr,
                                            OBJECT(mb),
                                            path,
                                            mb->size,
                                            remote_backend_memory_destructor,
                                            errp);

    if(!runstate_check(RUN_STATE_INMIGRATE)) {
        info = qmp_query_uuid(NULL);
        host_ptr = dbox_alloc(info->UUID, rb->secid);
        memory_region_set_ram_block_host_ptr(&mb->mr, host_ptr);
        memory_region_set_enabled(&mb->mr, true);
        qapi_free_UuidInfo(info);
    }

    g_free(path);
}

static void remote_backend_class_init(ObjectClass *oc, void *data)
{
    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
    RemoteMemoryBackendClass *rmbc = REMOTE_MEMORY_BACKEND_CLASS(oc);

    rmbc->get_instances_nb = get_inst_nb;
    ucc->complete = remote_backend_memory_complete;
    oc->unparent = remote_backend_instance_unparent;
    instances_nb = 0;
}

static int remote_backend_save_setup(QEMUFile *f, void *opaque)
{
    HostMemoryBackendRemote *hmbr = opaque;
    HostMemoryBackend *mb = MEMORY_BACKEND(hmbr);

    qemu_put_buffer(f, (uint8_t *)hmbr->secid, 37);

    assert(mb->mr.is_remote);

    if (mb->mr.disaggregated_migration) {
        qemu_put_be64(f, (mb->mr.addr | REMOTE_BACKEND_REALLOC_FLAG));
    } else {
        qemu_put_be64(f, mb->mr.addr);
    }

    return 0;
}

static int remote_backend_load_state(QEMUFile *f, void *opaque, int versionid)
{
    HostMemoryBackendRemote *hmbr = opaque;
    hwaddr addr;

    qemu_get_buffer(f, (uint8_t *)hmbr->secid, 37);
    addr = qemu_get_be64(f);

    if (addr & REMOTE_BACKEND_REALLOC_FLAG) {
        ram_remote_attach_section_id(hmbr->secid,
                                     (addr & ~REMOTE_BACKEND_REALLOC_FLAG),
                                     true);
    } else {
        ram_remote_attach_section_id(hmbr->secid, addr, false);
    }

    return 0;
}

static SaveVMHandlers remote_backend_savevm_handlers = {
    .save_setup     = remote_backend_save_setup,
    .load_state     = remote_backend_load_state,
};

static void remote_backend_instance_init(Object *o)
{
    HostMemoryBackendRemote *hmbr = MEMORY_BACKEND_REMOTE(o);

    if (MAX_NODES == instances_nb) {
        fprintf(stderr,"Too many backend instances\n");
        abort();
    }
    instances_nb++;
    fprintf(stderr, "instances incremented: %u\n", instances_nb);

    register_savevm_live(NULL, "remote_memory_backend", 0, 1,
                         &remote_backend_savevm_handlers, hmbr);
}

static const TypeInfo remote_backend_info = {
    .name = TYPE_MEMORY_BACKEND_REMOTE,
    .parent = TYPE_MEMORY_BACKEND,
    .class_size = sizeof(RemoteMemoryBackendClass),
    .class_init = remote_backend_class_init,
    .instance_init = remote_backend_instance_init,
    .instance_size = sizeof(HostMemoryBackendRemote),
};

static void register_types(void)
{
    type_register_static(&remote_backend_info);
}

type_init(register_types);
