/*
 * qemu/kvm integration
 *
 * Copyright (C) 2006-2008 Qumranet Technologies
 *
 * Licensed under the terms of the GNU GPL version 2 or higher.
 */
#include "config.h"
#include "config-host.h"

#include <assert.h>
#include <string.h>
#include "hw/hw.h"
#include "sysemu.h"
#include "qemu-common.h"
#include "console.h"
#include "block.h"
#include "compatfd.h"
#include "gdbstub.h"
#include "monitor.h"
#include "cpus.h"

#include "qemu-kvm.h"

#define EXPECTED_KVM_API_VERSION 12

#if EXPECTED_KVM_API_VERSION != KVM_API_VERSION
#error libkvm: userspace and kernel version mismatch
#endif

#define ALIGN(x, y) (((x)+(y)-1) & ~((y)-1))

#ifdef KVM_CAP_IRQ_ROUTING
static inline void clear_gsi(KVMState *s, unsigned int gsi)
{
    uint32_t *bitmap = s->used_gsi_bitmap;

    if (gsi < s->max_gsi) {
        bitmap[gsi / 32] &= ~(1U << (gsi % 32));
    } else {
        DPRINTF("Invalid GSI %u\n", gsi);
    }
}
#endif

#ifdef KVM_CAP_DEVICE_ASSIGNMENT
int kvm_assign_pci_device(KVMState *s,
                          struct kvm_assigned_pci_dev *assigned_dev)
{
    return kvm_vm_ioctl(s, KVM_ASSIGN_PCI_DEVICE, assigned_dev);
}

static int kvm_old_assign_irq(KVMState *s,
                              struct kvm_assigned_irq *assigned_irq)
{
    return kvm_vm_ioctl(s, KVM_ASSIGN_IRQ, assigned_irq);
}

int kvm_device_intx_set_mask(KVMState *s, uint32_t dev_id, bool masked)
{
    struct kvm_assigned_pci_dev assigned_dev;

    assigned_dev.assigned_dev_id = dev_id;
    assigned_dev.flags = masked ? KVM_DEV_ASSIGN_MASK_INTX : 0;
    return kvm_vm_ioctl(s, KVM_ASSIGN_SET_INTX_MASK, &assigned_dev);
}

#ifdef KVM_CAP_ASSIGN_DEV_IRQ
int kvm_assign_irq(KVMState *s, struct kvm_assigned_irq *assigned_irq)
{
    int ret;

    ret = kvm_ioctl(s, KVM_CHECK_EXTENSION, KVM_CAP_ASSIGN_DEV_IRQ);
    if (ret > 0) {
        return kvm_vm_ioctl(s, KVM_ASSIGN_DEV_IRQ, assigned_irq);
    }

    return kvm_old_assign_irq(s, assigned_irq);
}

int kvm_deassign_irq(KVMState *s, struct kvm_assigned_irq *assigned_irq)
{
    return kvm_vm_ioctl(s, KVM_DEASSIGN_DEV_IRQ, assigned_irq);
}
#else
int kvm_assign_irq(KVMState *s, struct kvm_assigned_irq *assigned_irq)
{
    return kvm_old_assign_irq(s, assigned_irq);
}
#endif
#endif

#ifdef KVM_CAP_DEVICE_DEASSIGNMENT
int kvm_deassign_pci_device(KVMState *s,
                            struct kvm_assigned_pci_dev *assigned_dev)
{
    return kvm_vm_ioctl(s, KVM_DEASSIGN_PCI_DEVICE, assigned_dev);
}
#endif

int kvm_del_routing_entry(struct kvm_irq_routing_entry *entry)
{
#ifdef KVM_CAP_IRQ_ROUTING
    KVMState *s = kvm_state;
    struct kvm_irq_routing_entry *e, *p;
    int i, gsi, found = 0;

    gsi = entry->gsi;

    for (i = 0; i < s->irq_routes->nr; ++i) {
        e = &s->irq_routes->entries[i];
        if (e->type == entry->type && e->gsi == gsi) {
            switch (e->type) {
            case KVM_IRQ_ROUTING_IRQCHIP:{
                    if (e->u.irqchip.irqchip ==
                        entry->u.irqchip.irqchip
                        && e->u.irqchip.pin == entry->u.irqchip.pin) {
                        p = &s->irq_routes->entries[--s->irq_routes->nr];
                        *e = *p;
                        found = 1;
                    }
                    break;
                }
            case KVM_IRQ_ROUTING_MSI:{
                    if (e->u.msi.address_lo ==
                        entry->u.msi.address_lo
                        && e->u.msi.address_hi ==
                        entry->u.msi.address_hi
                        && e->u.msi.data == entry->u.msi.data) {
                        p = &s->irq_routes->entries[--s->irq_routes->nr];
                        *e = *p;
                        found = 1;
                    }
                    break;
                }
            default:
                break;
            }
            if (found) {
                /* If there are no other users of this GSI
                 * mark it available in the bitmap */
                for (i = 0; i < s->irq_routes->nr; i++) {
                    e = &s->irq_routes->entries[i];
                    if (e->gsi == gsi)
                        break;
                }
                if (i == s->irq_routes->nr) {
                    clear_gsi(s, gsi);
                }

                return 0;
            }
        }
    }
    return -ESRCH;
#else
    return -ENOSYS;
#endif
}

int kvm_update_routing_entry(struct kvm_irq_routing_entry *entry,
                             struct kvm_irq_routing_entry *newentry)
{
#ifdef KVM_CAP_IRQ_ROUTING
    KVMState *s = kvm_state;
    struct kvm_irq_routing_entry *e;
    int i;

    if (entry->gsi != newentry->gsi || entry->type != newentry->type) {
        return -EINVAL;
    }

    for (i = 0; i < s->irq_routes->nr; ++i) {
        e = &s->irq_routes->entries[i];
        if (e->type != entry->type || e->gsi != entry->gsi) {
            continue;
        }
        switch (e->type) {
        case KVM_IRQ_ROUTING_IRQCHIP:
            if (e->u.irqchip.irqchip == entry->u.irqchip.irqchip &&
                e->u.irqchip.pin == entry->u.irqchip.pin) {
                memcpy(&e->u.irqchip, &newentry->u.irqchip,
                       sizeof e->u.irqchip);
                return 0;
            }
            break;
        case KVM_IRQ_ROUTING_MSI:
            if (e->u.msi.address_lo == entry->u.msi.address_lo &&
                e->u.msi.address_hi == entry->u.msi.address_hi &&
                e->u.msi.data == entry->u.msi.data) {
                memcpy(&e->u.msi, &newentry->u.msi, sizeof e->u.msi);
                return 0;
            }
            break;
        default:
            break;
        }
    }
    return -ESRCH;
#else
    return -ENOSYS;
#endif
}

int kvm_get_irq_route_gsi(void)
{
#ifdef KVM_CAP_IRQ_ROUTING
    KVMState *s = kvm_state;
    int i, bit;
    uint32_t *buf = s->used_gsi_bitmap;

    /* Return the lowest unused GSI in the bitmap */
    for (i = 0; i < s->max_gsi / 32; i++) {
        bit = ffs(~buf[i]);
        if (!bit) {
            continue;
        }

        return bit - 1 + i * 32;
    }

    return -ENOSPC;
#else
    return -ENOSYS;
#endif
}

#ifdef KVM_CAP_IRQ_ROUTING
static void kvm_msi_routing_entry(struct kvm_irq_routing_entry *e,
                                  KVMMsiMessage *msg)

{
    e->gsi = msg->gsi;
    e->type = KVM_IRQ_ROUTING_MSI;
    e->flags = 0;
    e->u.msi.address_lo = msg->addr_lo;
    e->u.msi.address_hi = msg->addr_hi;
    e->u.msi.data = msg->data;
}
#endif

int kvm_msi_message_add(KVMMsiMessage *msg)
{
#ifdef KVM_CAP_IRQ_ROUTING
    struct kvm_irq_routing_entry e;
    int ret;

    ret = kvm_get_irq_route_gsi();
    if (ret < 0) {
        return ret;
    }
    msg->gsi = ret;

    kvm_msi_routing_entry(&e, msg);
    kvm_add_routing_entry(kvm_state, &e);
    return 0;
#else
    return -ENOSYS;
#endif
}

int kvm_msi_message_del(KVMMsiMessage *msg)
{
#ifdef KVM_CAP_IRQ_ROUTING
    struct kvm_irq_routing_entry e;

    kvm_msi_routing_entry(&e, msg);
    return kvm_del_routing_entry(&e);
#else
    return -ENOSYS;
#endif
}

int kvm_msi_message_update(KVMMsiMessage *old, KVMMsiMessage *new)
{
#ifdef KVM_CAP_IRQ_ROUTING
    struct kvm_irq_routing_entry e1, e2;
    int ret;

    new->gsi = old->gsi;
    if (memcmp(old, new, sizeof(KVMMsiMessage)) == 0) {
        return 0;
    }

    kvm_msi_routing_entry(&e1, old);
    kvm_msi_routing_entry(&e2, new);

    ret = kvm_update_routing_entry(&e1, &e2);
    if (ret < 0) {
        return ret;
    }

    return 1;
#else
    return -ENOSYS;
#endif
}


#ifdef KVM_CAP_DEVICE_MSIX
int kvm_assign_set_msix_nr(KVMState *s, struct kvm_assigned_msix_nr *msix_nr)
{
    return kvm_vm_ioctl(s, KVM_ASSIGN_SET_MSIX_NR, msix_nr);
}

int kvm_assign_set_msix_entry(KVMState *s,
                              struct kvm_assigned_msix_entry *entry)
{
    return kvm_vm_ioctl(s, KVM_ASSIGN_SET_MSIX_ENTRY, entry);
}
#endif

#if !defined(TARGET_I386)
void kvm_arch_init_irq_routing(KVMState *s)
{
}
#endif
