/*
 * QEMU Host 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>
 *   Alvise Rigo <a.rigo@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-dboxshm.h"
#include "sysemu/sysemu.h"
#include "qom/object_interfaces.h"
#include "qemu/sockets.h"
#include "dboxshmem.h"
#include "qapi/qapi-commands-misc.h"

#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>

static dbox_shmem_handle
remote_backend_memory_shm_get(HostMemoryBackend *backend, bool join)
{
    DboxShmMemoryBackend *db = MEMORY_BACKEND_DBOXSHM(backend);
    UuidInfo* info = qmp_query_uuid(NULL);
    dbox_shmem_handle *ret;
    QemuUUID uuid;

    /* secid_char has to be set in advance */
    assert(qemu_uuid_parse(db->secid_char, &uuid) != -1);

    ret = join ? dboxshmem_join(info->UUID, db->secid_char) :
                 dboxshmem_init(info->UUID);

    if (ret->data_ptr == NULL) {
        fprintf(stderr, "%s: Returned NULL for section ID: %s", __func__,
                db->secid_char);
    }

    memory_region_set_ram_block_host_ptr(&backend->mr, ret->data_ptr);
    db->shm_handle = *ret;

    /* The memory region can be now enabled. KVM will configure it as user
     * memory, allowing direct guest access to it. */
    if (backend->mr.enabled) {
        MemoryRegion *container = backend->mr.container;

        /* The memory region has a new host pointer. We need to make sure that
         * the corresponding mapping gets updated. To do so, we remove-add the
         * memory region. Doing so, make sure the memory region does not get
         * destroyed while deleting it via memory_region_del_subregion(). We
         * still need it. */
        memory_region_ref(&backend->mr);
        memory_region_del_subregion(container, &backend->mr);
        memory_region_add_subregion(container, 0, &backend->mr);
        memory_region_unref(&backend->mr);
    } else {
        /* It is the first time that this device is used. Enable the memory
         * region */
        memory_region_set_enabled(&backend->mr, true);
    }

    qapi_free_UuidInfo(info);

    return *ret;
}

static void
remote_backend_memory_shm_release(HostMemoryBackend *backend,
                                  dbox_shmem_handle *shmem)
{
    dboxshmem_leave(shmem);

    /* Clear host pointer */
    memory_region_set_ram_block_host_ptr(&backend->mr, NULL);
}

static void
remote_backend_memory_shm_destructor(MemoryRegion *mr)
{
    void *host;

    /* It might happen that the guest did not free itself the memory. The
     * orchestrator knows what to do in this case. For the time being, just
     * notify about this issue. */
    host = memory_region_set_ram_block_host_ptr(mr, NULL);
    if (host) {
        fprintf(stderr, "dboxshm: Shared memory not released by the guest.\n");
    }
}

static void remote_backend_memory_complete(UserCreatable *uc, Error **errp)
{
    DboxShmMemoryBackend *db = MEMORY_BACKEND_DBOXSHM(uc);
    HostMemoryBackend *mb = MEMORY_BACKEND(db);
    char *name;

    name = object_get_canonical_path_component(OBJECT(db));

    memory_region_init_ram_remote_nomigrate(&mb->mr, OBJECT(db), name, mb->size,
                                  remote_backend_memory_shm_destructor, NULL);
}

static void remote_backend_class_init(ObjectClass *oc, void *data)
{
    HostMemoryBackendClass *bc = MEMORY_BACKEND_CLASS(oc);
    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
    DboxShmMemoryBackendClass *dmbc = DBOXSHM_MEMORY_BACKEND_CLASS(bc);

    dmbc->shm_get = remote_backend_memory_shm_get;
    dmbc->shm_release = remote_backend_memory_shm_release;
    ucc->complete = remote_backend_memory_complete;
}

static void remote_backend_instance_init(Object *o)
{
    DboxShmMemoryBackend *db = MEMORY_BACKEND_DBOXSHM(o);

    db->secid_char = g_malloc0(strlen(UUID_NONE) + 1);
    /* Make sure it is zero'ed */
    memset(&db->shm_handle, 0, sizeof(db->shm_handle));
}

static const TypeInfo dboxshm_backend_info = {
    .name = TYPE_MEMORY_BACKEND_DBOXSHM,
    .parent = TYPE_MEMORY_BACKEND,
    .class_size = sizeof(DboxShmMemoryBackendClass),
    .class_init = remote_backend_class_init,
    .instance_init = remote_backend_instance_init,
    .instance_size = sizeof(DboxShmMemoryBackend),
};

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

type_init(register_types);
