/*
 * QEMU KVM Hyper-V test device to support Hyper-V kvm-unit-tests
 *
 * Copyright (C) 2015 Andrey Smetanin <asmetanin@virtuozzo.com>
 *
 * Authors:
 *  Andrey Smetanin <asmetanin@virtuozzo.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 "qemu/osdep.h"
#include <linux/kvm.h>
#include "hw/hw.h"
#include "hw/qdev.h"
#include "hw/isa/isa.h"
#include "sysemu/kvm.h"
#include "target/i386/hyperv.h"
#include "kvm_i386.h"

#define HV_TEST_DEV_MAX_SINT_ROUTES 64

struct HypervTestDev {
    ISADevice parent_obj;
    MemoryRegion sint_control;
    HvSintRoute *sint_route[HV_TEST_DEV_MAX_SINT_ROUTES];
};
typedef struct HypervTestDev HypervTestDev;

#define TYPE_HYPERV_TEST_DEV "hyperv-testdev"
#define HYPERV_TEST_DEV(obj) \
        OBJECT_CHECK(HypervTestDev, (obj), TYPE_HYPERV_TEST_DEV)

enum {
    HV_TEST_DEV_SINT_ROUTE_CREATE = 1,
    HV_TEST_DEV_SINT_ROUTE_DESTROY,
    HV_TEST_DEV_SINT_ROUTE_SET_SINT
};

static int alloc_sint_route_index(HypervTestDev *dev)
{
    int i;

    for (i = 0; i < ARRAY_SIZE(dev->sint_route); i++) {
        if (dev->sint_route[i] == NULL) {
            return i;
        }
    }
    return -1;
}

static void free_sint_route_index(HypervTestDev *dev, int i)
{
    assert(i >= 0 && i < ARRAY_SIZE(dev->sint_route));
    dev->sint_route[i] = NULL;
}

static int find_sint_route_index(HypervTestDev *dev, uint32_t vcpu_id,
                                 uint32_t sint)
{
    HvSintRoute *sint_route;
    int i;

    for (i = 0; i < ARRAY_SIZE(dev->sint_route); i++) {
        sint_route = dev->sint_route[i];
        if (sint_route && sint_route->vcpu_id == vcpu_id &&
            sint_route->sint == sint) {
            return i;
        }
    }
    return -1;
}

static void hv_synic_test_dev_control(HypervTestDev *dev, uint32_t ctl,
                                      uint32_t vcpu_id, uint32_t sint)
{
    int i;
    HvSintRoute *sint_route;

    switch (ctl) {
    case HV_TEST_DEV_SINT_ROUTE_CREATE:
        i = alloc_sint_route_index(dev);
        assert(i >= 0);
        sint_route = kvm_hv_sint_route_create(vcpu_id, sint, NULL);
        assert(sint_route);
        dev->sint_route[i] = sint_route;
        break;
    case HV_TEST_DEV_SINT_ROUTE_DESTROY:
        i = find_sint_route_index(dev, vcpu_id, sint);
        assert(i >= 0);
        sint_route = dev->sint_route[i];
        kvm_hv_sint_route_destroy(sint_route);
        free_sint_route_index(dev, i);
        break;
    case HV_TEST_DEV_SINT_ROUTE_SET_SINT:
        i = find_sint_route_index(dev, vcpu_id, sint);
        assert(i >= 0);
        sint_route = dev->sint_route[i];
        kvm_hv_sint_route_set_sint(sint_route);
        break;
    default:
        break;
    }
}

static void hv_test_dev_control(void *opaque, hwaddr addr, uint64_t data,
                                uint32_t len)
{
    HypervTestDev *dev = HYPERV_TEST_DEV(opaque);
    uint8_t ctl;

    ctl = (data >> 16ULL) & 0xFF;
    switch (ctl) {
    case HV_TEST_DEV_SINT_ROUTE_CREATE:
    case HV_TEST_DEV_SINT_ROUTE_DESTROY:
    case HV_TEST_DEV_SINT_ROUTE_SET_SINT: {
        uint8_t sint = data & 0xFF;
        uint8_t vcpu_id = (data >> 8ULL) & 0xFF;
        hv_synic_test_dev_control(dev, ctl, vcpu_id, sint);
        break;
    }
    default:
        break;
    }
}

static const MemoryRegionOps synic_test_sint_ops = {
    .write = hv_test_dev_control,
    .valid.min_access_size = 4,
    .valid.max_access_size = 4,
    .endianness = DEVICE_LITTLE_ENDIAN,
};

static void hv_test_dev_realizefn(DeviceState *d, Error **errp)
{
    ISADevice *isa = ISA_DEVICE(d);
    HypervTestDev *dev = HYPERV_TEST_DEV(d);
    MemoryRegion *io = isa_address_space_io(isa);

    memset(dev->sint_route, 0, sizeof(dev->sint_route));
    memory_region_init_io(&dev->sint_control, OBJECT(dev),
                          &synic_test_sint_ops, dev,
                          "hyperv-testdev-ctl", 4);
    memory_region_add_subregion(io, 0x3000, &dev->sint_control);
}

static void hv_test_dev_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    set_bit(DEVICE_CATEGORY_MISC, dc->categories);
    dc->realize = hv_test_dev_realizefn;
}

static const TypeInfo hv_test_dev_info = {
    .name           = TYPE_HYPERV_TEST_DEV,
    .parent         = TYPE_ISA_DEVICE,
    .instance_size  = sizeof(HypervTestDev),
    .class_init     = hv_test_dev_class_init,
};

static void hv_test_dev_register_types(void)
{
    type_register_static(&hv_test_dev_info);
}
type_init(hv_test_dev_register_types);