/*
 * Virtio PCI driver - modern (virtio 1.0) device support
 *
 * Copyright IBM Corp. 2007
 * Copyright Red Hat, Inc. 2014
 *
 * Authors:
 *  Anthony Liguori  <aliguori@us.ibm.com>
 *  Rusty Russell <rusty@rustcorp.com.au>
 *  Michael S. Tsirkin <mst@redhat.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met :
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and / or other materials provided with the distribution.
 * 3. Neither the names of the copyright holders nor the names of their contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "osdep.h"
#define VIRTIO_PCI_NO_LEGACY
#include "virtio_pci.h"
#include "virtio.h"
#include "kdebugprint.h"
#include "virtio_ring.h"
#include "virtio_pci_common.h"
#include "windows\virtio_ring_allocation.h"
#include <stddef.h>

#ifdef WPP_EVENT_TRACING
#include "VirtIOPCIModern.tmh"
#endif

static void *vio_modern_map_capability(VirtIODevice *vdev, int cap_offset,
                                       size_t minlen, u32 alignment,
                                       u32 start, u32 size, size_t *len)
{
    u8 bar;
    u32 bar_offset, bar_length;
    void *addr;

    pci_read_config_byte(vdev, cap_offset + offsetof(struct virtio_pci_cap, bar), &bar);
    pci_read_config_dword(vdev, cap_offset + offsetof(struct virtio_pci_cap, offset), &bar_offset);
    pci_read_config_dword(vdev, cap_offset + offsetof(struct virtio_pci_cap, length), &bar_length);

    if (start + minlen > bar_length) {
        DPrintf(0, "bar %i cap is not large enough to map %zu bytes at offset %u\n", bar, minlen, start);
        return NULL;
    }

    bar_length -= start;
    bar_offset += start;

    if (bar_offset & (alignment - 1)) {
        DPrintf(0, "bar %i offset %u not aligned to %u\n", bar, bar_offset, alignment);
        return NULL;
    }

    if (bar_length > size) {
        bar_length = size;
    }

    if (len) {
        *len = bar_length;
    }

    if (bar_offset + minlen > pci_get_resource_len(vdev, bar)) {
        DPrintf(0, "bar %i is not large enough to map %zu bytes at offset %u\n", bar, minlen, bar_offset);
        return NULL;
    }

    addr = pci_map_address_range(vdev, bar, bar_offset, bar_length);
    if (!addr) {
        DPrintf(0, "unable to map %u bytes at bar %i offset %u\n", bar_length, bar, bar_offset);
    }
    return addr;
}

static void *vio_modern_map_simple_capability(VirtIODevice *vdev, int cap_offset, size_t length, u32 alignment)
{
    return vio_modern_map_capability(
        vdev,
        cap_offset,
        length,      // minlen
        alignment,
        0,           // offset
        (u32)length, // size is equal to minlen
        NULL);       // not interested in the full length
}

static void vio_modern_get_config(VirtIODevice *vdev, unsigned offset,
                                  void *buf, unsigned len)
{
    if (!vdev->config) {
        ASSERT(!"Device has no config to read");
        return;
    }
    if (offset + len > vdev->config_len) {
        ASSERT(!"Can't read beyond the config length");
        return;
    }

    switch (len) {
    case 1:
        *(u8 *)buf = ioread8(vdev, vdev->config + offset);
        break;
    case 2:
        *(u16 *)buf = ioread16(vdev, vdev->config + offset);
        break;
    case 4:
        *(u32 *)buf = ioread32(vdev, vdev->config + offset);
        break;
    default:
        ASSERT(!"Only 1, 2, 4 byte config reads are supported");
    }
}

static void vio_modern_set_config(VirtIODevice *vdev, unsigned offset,
                                  const void *buf, unsigned len)
{
    if (!vdev->config) {
        ASSERT(!"Device has no config to write");
        return;
    }
    if (offset + len > vdev->config_len) {
        ASSERT(!"Can't write beyond the config length");
        return;
    }

    switch (len) {
    case 1:
        iowrite8(vdev, *(u8 *)buf, vdev->config + offset);
        break;
    case 2:
        iowrite16(vdev, *(u16 *)buf, vdev->config + offset);
        break;
    case 4:
        iowrite32(vdev, *(u32 *)buf, vdev->config + offset);
        break;
    default:
        ASSERT(!"Only 1, 2, 4 byte config writes are supported");
    }
}

static u32 vio_modern_get_generation(VirtIODevice *vdev)
{
    return ioread8(vdev, &vdev->common->config_generation);
}

static u8 vio_modern_get_status(VirtIODevice *vdev)
{
    return ioread8(vdev, &vdev->common->device_status);
}

static void vio_modern_set_status(VirtIODevice *vdev, u8 status)
{
    /* We should never be setting status to 0. */
    ASSERT(status != 0);
    iowrite8(vdev, status, &vdev->common->device_status);
}

static void vio_modern_reset(VirtIODevice *vdev)
{
    /* 0 status means a reset. */
    iowrite8(vdev, 0, &vdev->common->device_status);
    /* After writing 0 to device_status, the driver MUST wait for a read of
     * device_status to return 0 before reinitializing the device.
     * This will flush out the status write, and flush in device writes,
     * including MSI-X interrupts, if any.
     */
    while (ioread8(vdev, &vdev->common->device_status)) {
        vdev_sleep(vdev, 1);
    }
}

static u64 vio_modern_get_features(VirtIODevice *vdev)
{
    u64 features;

    iowrite32(vdev, 0, &vdev->common->device_feature_select);
    features = ioread32(vdev, &vdev->common->device_feature);
    iowrite32(vdev, 1, &vdev->common->device_feature_select);
    features |= ((u64)ioread32(vdev, &vdev->common->device_feature) << 32);

    return features;
}

static NTSTATUS vio_modern_set_features(VirtIODevice *vdev, u64 features)
{
    /* Give virtio_ring a chance to accept features. */
    vring_transport_features(vdev, &features);

    if (!virtio_is_feature_enabled(features, VIRTIO_F_VERSION_1)) {
        DPrintf(0, ("virtio: device uses modern interface but does not have VIRTIO_F_VERSION_1\n"));
        return STATUS_INVALID_PARAMETER;
    }

    iowrite32(vdev, 0, &vdev->common->guest_feature_select);
    iowrite32(vdev, (u32)features, &vdev->common->guest_feature);
    iowrite32(vdev, 1, &vdev->common->guest_feature_select);
    iowrite32(vdev, features >> 32, &vdev->common->guest_feature);

    return STATUS_SUCCESS;
}

static u16 vio_modern_set_config_vector(VirtIODevice *vdev, u16 vector)
{
    /* Setup the vector used for configuration events */
    iowrite16(vdev, vector, &vdev->common->msix_config);
    /* Verify we had enough resources to assign the vector */
    /* Will also flush the write out to device */
    return ioread16(vdev, &vdev->common->msix_config);
}

static u16 vio_modern_set_queue_vector(struct virtqueue *vq, u16 vector)
{
    VirtIODevice *vdev = vq->vdev;
    volatile struct virtio_pci_common_cfg *cfg = vdev->common;

    iowrite16(vdev, (u16)vq->index, &cfg->queue_select);
    iowrite16(vdev, vector, &cfg->queue_msix_vector);
    return ioread16(vdev, &cfg->queue_msix_vector);
}

static size_t vring_pci_size(u16 num, bool packed)
{
    /* We only need a cacheline separation. */
    return (size_t)ROUND_TO_PAGES(vring_size(num, SMP_CACHE_BYTES, packed));
}

static NTSTATUS vio_modern_query_vq_alloc(VirtIODevice *vdev,
                                          unsigned index,
                                          unsigned short *pNumEntries,
                                          unsigned long *pRingSize,
                                          unsigned long *pHeapSize)
{
    volatile struct virtio_pci_common_cfg *cfg = vdev->common;
    u16 num;

    if (index >= ioread16(vdev, &cfg->num_queues)) {
        return STATUS_NOT_FOUND;
    }

    /* Select the queue we're interested in */
    iowrite16(vdev, (u16)index, &cfg->queue_select);

    /* Check if queue is either not available or already active. */
    num = ioread16(vdev, &cfg->queue_size);
    /* QEMU has a bug where queues don't revert to inactive on device
     * reset. Skip checking the queue_enable field until it is fixed.
     */
    if (!num /*|| ioread16(vdev, &cfg->queue_enable)*/) {
        return STATUS_NOT_FOUND;
    }

    if (num & (num - 1)) {
        DPrintf(0, "%p: bad queue size %u", vdev, num);
        return STATUS_INVALID_PARAMETER;
    }

    *pNumEntries = num;
    *pRingSize = (unsigned long)vring_pci_size(num, vdev->packed_ring);
    *pHeapSize = vring_control_block_size(num, vdev->packed_ring);

    return STATUS_SUCCESS;
}

static NTSTATUS vio_modern_setup_vq(struct virtqueue **queue,
                                    VirtIODevice *vdev,
                                    VirtIOQueueInfo *info,
                                    unsigned index,
                                    u16 msix_vec)
{
    volatile struct virtio_pci_common_cfg *cfg = vdev->common;
    struct virtqueue *vq;
    void *vq_addr;
    u16 off;
    unsigned long ring_size, heap_size;
    NTSTATUS status;

    /* select the queue and query allocation parameters */
    status = vio_modern_query_vq_alloc(vdev, index, &info->num, &ring_size, &heap_size);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    /* get offset of notification word for this vq */
    off = ioread16(vdev, &cfg->queue_notify_off);

    /* try to allocate contiguous pages, scale down on failure */
    while (!(info->queue = mem_alloc_contiguous_pages(vdev, vring_pci_size(info->num, vdev->packed_ring)))) {
        if (info->num > 0) {
            info->num /= 2;
        } else {
            return STATUS_INSUFFICIENT_RESOURCES;
        }
    }

    vq_addr = mem_alloc_nonpaged_block(vdev, heap_size);
    if (vq_addr == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    /* create the vring */
    if (vdev->packed_ring) {
        vq = vring_new_virtqueue_packed(index, info->num,
            SMP_CACHE_BYTES, vdev,
            info->queue, vp_notify, vq_addr);
    } else {
        vq = vring_new_virtqueue_split(index, info->num,
            SMP_CACHE_BYTES, vdev,
            info->queue, vp_notify, vq_addr);
    }

    if (!vq) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto err_new_queue;
    }

    /* activate the queue */
    iowrite16(vdev, info->num, &cfg->queue_size);
    iowrite64_twopart(vdev, mem_get_physical_address(vdev, info->queue),
        &cfg->queue_desc_lo, &cfg->queue_desc_hi);
    iowrite64_twopart(vdev, mem_get_physical_address(vdev, vq->avail_va),
        &cfg->queue_avail_lo, &cfg->queue_avail_hi);
    iowrite64_twopart(vdev, mem_get_physical_address(vdev, vq->used_va),
        &cfg->queue_used_lo, &cfg->queue_used_hi);

    if (vdev->notify_base) {
        /* offset should not wrap */
        if ((u64)off * vdev->notify_offset_multiplier + 2
            > vdev->notify_len) {
            DPrintf(0,
                "%p: bad notification offset %u (x %u) "
                "for queue %u > %zd",
                vdev,
                off, vdev->notify_offset_multiplier,
                index, vdev->notify_len);
            status = STATUS_INVALID_PARAMETER;
            goto err_map_notify;
        }
        vq->notification_addr = (void *)(vdev->notify_base +
            off * vdev->notify_offset_multiplier);
    } else {
        vq->notification_addr = vio_modern_map_capability(vdev,
            vdev->notify_map_cap, 2, 2,
            off * vdev->notify_offset_multiplier, 2,
            NULL);
    }

    if (!vq->notification_addr) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto err_map_notify;
    }

    if (msix_vec != VIRTIO_MSI_NO_VECTOR) {
        msix_vec = vdev->device->set_queue_vector(vq, msix_vec);
        if (msix_vec == VIRTIO_MSI_NO_VECTOR) {
            status = STATUS_DEVICE_BUSY;
            goto err_assign_vector;
        }
    }

    /* enable the queue */
    iowrite16(vdev, 1, &vdev->common->queue_enable);

    *queue = vq;
    return STATUS_SUCCESS;

err_assign_vector:
err_map_notify:
    virtqueue_shutdown(vq);
err_new_queue:
    mem_free_nonpaged_block(vdev, vq_addr);
    mem_free_contiguous_pages(vdev, info->queue);
    return status;
}

static void vio_modern_del_vq(VirtIOQueueInfo *info)
{
    struct virtqueue *vq = info->vq;
    VirtIODevice *vdev = vq->vdev;

    iowrite16(vdev, (u16)vq->index, &vdev->common->queue_select);

    if (vdev->msix_used) {
        iowrite16(vdev, VIRTIO_MSI_NO_VECTOR, &vdev->common->queue_msix_vector);
        /* Flush the write out to device */
        ioread16(vdev, &vdev->common->queue_msix_vector);
    }

    virtqueue_shutdown(vq);

    mem_free_nonpaged_block(vdev, vq);
    mem_free_contiguous_pages(vdev, info->queue);
}

static const struct virtio_device_ops virtio_pci_device_ops = {
    .get_config = vio_modern_get_config,
    .set_config = vio_modern_set_config,
    .get_config_generation = vio_modern_get_generation,
    .get_status = vio_modern_get_status,
    .set_status = vio_modern_set_status,
    .reset = vio_modern_reset,
    .get_features = vio_modern_get_features,
    .set_features = vio_modern_set_features,
    .set_config_vector = vio_modern_set_config_vector,
    .set_queue_vector = vio_modern_set_queue_vector,
    .query_queue_alloc = vio_modern_query_vq_alloc,
    .setup_queue = vio_modern_setup_vq,
    .delete_queue = vio_modern_del_vq,
};

static u8 find_next_pci_vendor_capability(VirtIODevice *vdev, u8 offset)
{
    u8 id = 0;
    int iterations = 48;

    if (pci_read_config_byte(vdev, offset, &offset) != 0) {
        return 0;
    }

    while (iterations-- && offset >= 0x40) {
        offset &= ~3;
        if (pci_read_config_byte(vdev, offset + offsetof(PCI_CAPABILITIES_HEADER,
                CapabilityID), &id) != 0) {
            break;
        }
        if (id == 0xFF) {
            break;
        }
        if (id == PCI_CAPABILITY_ID_VENDOR_SPECIFIC) {
            return offset;
        }
        if (pci_read_config_byte(vdev, offset + offsetof(PCI_CAPABILITIES_HEADER,
                Next), &offset) != 0) {
            break;
        }
    }
    return 0;
}

static u8 find_first_pci_vendor_capability(VirtIODevice *vdev)
{
    u8 hdr_type, offset;
    u16 status;

    if (pci_read_config_byte(vdev, offsetof(PCI_COMMON_HEADER, HeaderType), &hdr_type) != 0) {
        return 0;
    }
    if (pci_read_config_word(vdev, offsetof(PCI_COMMON_HEADER, Status), &status) != 0) {
        return 0;
    }
    if ((status & PCI_STATUS_CAPABILITIES_LIST) == 0) {
        return 0;
    }

    switch (hdr_type & ~PCI_MULTIFUNCTION) {
    case PCI_BRIDGE_TYPE:
        offset = offsetof(PCI_COMMON_HEADER, u.type1.CapabilitiesPtr);
        break;
    case PCI_CARDBUS_BRIDGE_TYPE:
        offset = offsetof(PCI_COMMON_HEADER, u.type2.CapabilitiesPtr);
        break;
    default:
        offset = offsetof(PCI_COMMON_HEADER, u.type0.CapabilitiesPtr);
        break;
    }

    if (offset != 0) {
        offset = find_next_pci_vendor_capability(vdev, offset);
    }
    return offset;
}

/* Populate Offsets with virtio vendor capability offsets within the PCI config space */
static void find_pci_vendor_capabilities(VirtIODevice *vdev, int *Offsets, size_t nOffsets)
{
    u8 offset = find_first_pci_vendor_capability(vdev);
    while (offset > 0) {
        u8 cfg_type, bar;
        pci_read_config_byte(vdev, offset + offsetof(struct virtio_pci_cap, cfg_type), &cfg_type);
        pci_read_config_byte(vdev, offset + offsetof(struct virtio_pci_cap, bar), &bar);

        if (bar < PCI_TYPE0_ADDRESSES &&
            cfg_type < nOffsets &&
            pci_get_resource_len(vdev, bar) > 0) {
            Offsets[cfg_type] = offset;
        }

        offset = find_next_pci_vendor_capability(vdev, offset + offsetof(PCI_CAPABILITIES_HEADER, Next));
    }
}

/* Modern device initialization */
NTSTATUS vio_modern_initialize(VirtIODevice *vdev)
{
    int capabilities[VIRTIO_PCI_CAP_PCI_CFG];

    u32 notify_length;
    u32 notify_offset;

    RtlZeroMemory(capabilities, sizeof(capabilities));
    find_pci_vendor_capabilities(vdev, capabilities, VIRTIO_PCI_CAP_PCI_CFG);

    /* Check for a common config, if not found use legacy mode */
    if (!capabilities[VIRTIO_PCI_CAP_COMMON_CFG]) {
        DPrintf(0, "%s(%p): device not found\n", __FUNCTION__, vdev);
        return STATUS_DEVICE_NOT_CONNECTED;
    }

    /* Check isr and notify caps, if not found fail */
    if (!capabilities[VIRTIO_PCI_CAP_ISR_CFG] || !capabilities[VIRTIO_PCI_CAP_NOTIFY_CFG]) {
        DPrintf(0, "%s(%p): missing capabilities %i/%i/%i\n",
            __FUNCTION__, vdev,
            capabilities[VIRTIO_PCI_CAP_COMMON_CFG],
            capabilities[VIRTIO_PCI_CAP_ISR_CFG],
            capabilities[VIRTIO_PCI_CAP_NOTIFY_CFG]);
        return STATUS_INVALID_PARAMETER;
    }

    /* Map bars according to the capabilities */
    vdev->common = vio_modern_map_simple_capability(vdev,
        capabilities[VIRTIO_PCI_CAP_COMMON_CFG],
        sizeof(struct virtio_pci_common_cfg), 4);
    if (!vdev->common) {
        return STATUS_INVALID_PARAMETER;
    }

    vdev->isr = vio_modern_map_simple_capability(vdev,
        capabilities[VIRTIO_PCI_CAP_ISR_CFG],
        sizeof(u8), 1);
    if (!vdev->isr) {
        return STATUS_INVALID_PARAMETER;
    }

    /* Read notify_off_multiplier from config space. */
    pci_read_config_dword(vdev,
        capabilities[VIRTIO_PCI_CAP_NOTIFY_CFG] + offsetof(struct virtio_pci_notify_cap,
        notify_off_multiplier),
        &vdev->notify_offset_multiplier);

    /* Read notify length and offset from config space. */
    pci_read_config_dword(vdev,
        capabilities[VIRTIO_PCI_CAP_NOTIFY_CFG] + offsetof(struct virtio_pci_notify_cap,
        cap.length),
        &notify_length);
    pci_read_config_dword(vdev,
        capabilities[VIRTIO_PCI_CAP_NOTIFY_CFG] + offsetof(struct virtio_pci_notify_cap,
        cap.offset),
        &notify_offset);

    /* Map the notify capability if it's small enough.
     * Otherwise, map each VQ individually later.
     */
    if (notify_length + (notify_offset % PAGE_SIZE) <= PAGE_SIZE) {
        vdev->notify_base = vio_modern_map_capability(vdev,
            capabilities[VIRTIO_PCI_CAP_NOTIFY_CFG], 2, 2,
            0, notify_length,
            &vdev->notify_len);
        if (!vdev->notify_base) {
            return STATUS_INVALID_PARAMETER;
        }
    } else {
        vdev->notify_map_cap = capabilities[VIRTIO_PCI_CAP_NOTIFY_CFG];
    }

    /* Map the device config capability, the PAGE_SIZE size is a guess */
    if (capabilities[VIRTIO_PCI_CAP_DEVICE_CFG]) {
        vdev->config = vio_modern_map_capability(vdev,
            capabilities[VIRTIO_PCI_CAP_DEVICE_CFG], 0, 4,
            0, PAGE_SIZE,
            &vdev->config_len);
        if (!vdev->config) {
            return STATUS_INVALID_PARAMETER;
        }
    }

    vdev->device = &virtio_pci_device_ops;

    return STATUS_SUCCESS;
}
