/*
 * Copyright (C) 2020 Xilinx, Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License"). You may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

#include "qdma.h"
#include "interrupts.hpp"
#include "trace.h"

#ifdef ENABLE_WPP_TRACING
#include "interrupts.tmh"
#endif

using namespace xlnx;

/* ------- Interrupt Queue ------- */
NTSTATUS intr_queue::create(
    WDFDMAENABLER& dma_enabler)
{
    auto buffer_size = size * sizeof(intr_entry);

    /* Align to page size */
    if (buffer_size % PAGE_SIZE) {
        buffer_size = ((buffer_size / PAGE_SIZE) + 1) * PAGE_SIZE;
    }

    TraceVerbose(TRACE_INTR, "Buffer size : %llu, Ring size : %llu",
        buffer_size, size);

    capacity = buffer_size / sizeof(intr_entry);
    npages = buffer_size / PAGE_SIZE;
    color = 1;

    TraceVerbose(TRACE_INTR, "Page size : %llu, Capacity : %llu",
        npages, capacity);

    auto status = WdfCommonBufferCreate(dma_enabler,
                                        buffer_size,
                                        WDF_NO_OBJECT_ATTRIBUTES,
                                        &buffer);
    if (!NT_SUCCESS(status)) {
        TraceError(TRACE_INTR, "Interrupt WdfCommonBufferCreate failed: %!STATUS!", status);
        return status;
    }

    buffer_va = static_cast<void*>(WdfCommonBufferGetAlignedVirtualAddress(buffer));

    clear_contents();

    return status;
}

void intr_queue::clear_contents(void)
{
    auto buffer_size = size * sizeof(intr_entry);
    RtlZeroMemory(buffer_va, buffer_size);
    sw_index = 0;
    intr_cidx_info.rng_idx = 0;
    intr_cidx_info.sw_cidx = 0;
}

void intr_queue::advance_sw_index(void)
{
    ++sw_index;

    if (sw_index == capacity) {
        sw_index = 0;
        /* Flip the color */
        color = color ? 0 : 1;
    }
}

PFORCEINLINE void intr_queue::update_csr_cidx(
    queue_pair *q,
    UINT32 new_cidx)
{
    TraceVerbose(TRACE_INTR, "Intr queue_%u updating c2h pidx to %u", idx, new_cidx);
    intr_cidx_info.rng_idx = (UINT8)idx_abs;
    intr_cidx_info.sw_cidx = (UINT16)new_cidx;

    qdma->hw.qdma_queue_intr_cidx_update(qdma, false /* is_vf */, q->idx, &intr_cidx_info);
}

NTSTATUS intr_queue::intring_dump(qdma_intr_ring_info *intring_info)
{
    size_t len;
    size_t buf_idx;
    size_t ring_entry_sz;
    UINT8 *ring_buff_addr;
    UINT8 *buf = intring_info->pbuffer;
    size_t buf_len = intring_info->buffer_len;

    if ((intring_info->start_idx >= size) ||
        (intring_info->end_idx >= size)) {
        TraceError(TRACE_INTR, "Intr Ring index Range is incorrect : start : %d, end : %d, RING SIZE : %d",
            intring_info->start_idx, intring_info->end_idx, size);
        return STATUS_ACCESS_VIOLATION;
    }

    ring_entry_sz = sizeof(intr_entry);
    intring_info->ring_entry_sz = ring_entry_sz;
    if (intring_info->start_idx <= intring_info->end_idx) {
        len = ((size_t)intring_info->end_idx - (size_t)intring_info->start_idx + 1) * ring_entry_sz;
        len = min(len, buf_len);
        buf_idx = intring_info->start_idx * ring_entry_sz;
        ring_buff_addr = &((UINT8 *)buffer_va)[buf_idx];
        RtlCopyMemory(buf, ring_buff_addr, len);
        intring_info->ret_len = len;
    }
    else {
        len = (size - intring_info->start_idx) * ring_entry_sz;
        len = min(len, buf_len);
        buf_idx = intring_info->start_idx * ring_entry_sz;
        ring_buff_addr = &((UINT8 *)buffer_va)[buf_idx];
        RtlCopyMemory(buf, ring_buff_addr, len);
        intring_info->ret_len = len;

        size_t remain_len = ((size_t)intring_info->end_idx + 1) * ring_entry_sz;
        len = min(remain_len, (buf_len - len));
        ring_buff_addr = &((UINT8 *)buffer_va)[0];
        RtlCopyMemory(&buf[intring_info->ret_len], ring_buff_addr, len);
        intring_info->ret_len += len;
    }

    return STATUS_SUCCESS;
}

/* --------------*/

inline static size_t get_msix_mask_offset(
    UINT32 vector)
{
    return (qdma_trq_cmc_msix_table + (((size_t)vector + 1) * qdma_msix_vectors_mask_step));
};

void qdma_device::mask_msi_entry(
    UINT32 vector)
{
    /* MSIx mask value
     * [bit 0 ]   - 0 : Unmask, 1 : Mask
     * [bit 1-31] - Reserved
     */
    UINT32 mask_val = 1u;
    qdma_conf_reg_write(get_msix_mask_offset(vector), mask_val);
}

void qdma_device::unmask_msi_entry(
    UINT32 vector)
{
    /* MSIx mask value
     * [bit 0 ]   - 0 : Unmask, 1 : Mask
     * [bit 1-31] - Reserved
     */
    UINT32 mask_val = 0u;
    qdma_conf_reg_write(get_msix_mask_offset(vector), mask_val);
}

BOOLEAN EvtErrorInterruptIsr(
    WDFINTERRUPT interrupt,
    ULONG MessageID)
{
    UNREFERENCED_PARAMETER(interrupt);
    UNREFERENCED_PARAMETER(MessageID);

    return WdfInterruptQueueDpcForIsr(interrupt);
}

VOID EvtErrorInterruptDpc(
    WDFINTERRUPT interrupt,
    WDFOBJECT device)
{
    UNREFERENCED_PARAMETER(device);
    UNREFERENCED_PARAMETER(interrupt);

    TraceVerbose(TRACE_INTR, "Error IRQ Fired on Master PF");

    auto irq_ctx = get_irq_context(interrupt);
    if (nullptr == irq_ctx) {
        TraceError(TRACE_INTR, "Err: null irq_ctx in EvtErrorInterruptDpc");
        return;
    }

    auto qdma_dev = irq_ctx->qdma_dev;
    qdma_dev->hw.qdma_hw_error_process(qdma_dev);
    qdma_dev->hw.qdma_hw_error_intr_rearm(qdma_dev);

    return;
}

BOOLEAN EvtUserInterruptIsr(
    WDFINTERRUPT interrupt,
    ULONG MessageID)
{
    UNREFERENCED_PARAMETER(interrupt);
    UNREFERENCED_PARAMETER(MessageID);

    TraceVerbose(TRACE_INTR, "User Interrupt ISR CALLED %lu SUCCESSFull", MessageID);

    return WdfInterruptQueueDpcForIsr(interrupt);
}

VOID EvtUserInterruptDpc(
    WDFINTERRUPT interrupt,
    WDFOBJECT device)
{
    UNREFERENCED_PARAMETER(device);
    UNREFERENCED_PARAMETER(interrupt);

    TraceVerbose(TRACE_INTR, "User Interrupt DPC SUCCESSFull");

    return;
}

BOOLEAN EvtDataInterruptIsr(
    WDFINTERRUPT interrupt,
    ULONG MessageID)
{
    UNREFERENCED_PARAMETER(MessageID);
    auto irq_ctx = get_irq_context(interrupt);
    auto qdma_dev = irq_ctx->qdma_dev;

    if ((irq_ctx->intr_type == interrupt_type::LEGACY) &&
        (qdma_dev->hw.qdma_legacy_intr_conf != nullptr)) {
        TraceVerbose(TRACE_INTR, "LEGACY INTERRUPT RECEIVED");
        auto ret = qdma_dev->hw.qdma_is_legacy_intr_pend(qdma_dev);
        if (ret < 0) {
            /* No interrupt pending, Hence returning from ISR */
            return false;
        }

        qdma_dev->hw.qdma_clear_pend_legacy_intr(qdma_dev);
    }

    irq_ctx->interrupt_handler(irq_ctx);

    if ((irq_ctx->intr_type == interrupt_type::LEGACY) &&
        (qdma_dev->hw.qdma_legacy_intr_conf != nullptr)) {
        qdma_dev->hw.qdma_legacy_intr_conf(qdma_dev, ENABLE);
        TraceVerbose(TRACE_INTR, "LEGACY INTERRUPT RE-ENABLED");
    }

    return true;
}

BOOLEAN schedule_dpc(queue_pair* q, UINT8 is_c2h, CCHAR active_processors)
{
    BOOLEAN status = FALSE;
    poll_operation_entry* poll_entry = nullptr;
    PRKDPC dpc = nullptr;
    PVOID arg1 = nullptr;

    poll_entry = (is_c2h) ? q->c2h_q.poll_entry : q->h2c_q.poll_entry;
    if (poll_entry) {
        dpc = &poll_entry->thread->dpc;
        arg1 = poll_entry->thread;

        KeSetTargetProcessorDpc(dpc, (CCHAR)(q->idx % active_processors));
        status = KeInsertQueueDpc(dpc, arg1, NULL);
    }

    return status;
}

void cpm_handle_indirect_interrupt(
    PIRQ_CONTEXT irq_ctx)
{
    queue_pair *q = nullptr;
    UINT8 is_c2h = 0;
    CCHAR active_processors = (CCHAR)KeQueryActiveProcessorCount(NULL);

    auto intr_queue = irq_ctx->intr_q;
    if (nullptr == intr_queue) {
        TraceError(TRACE_INTR, "Invalid vector %lu was called in coal mode", irq_ctx->vector_id);
        return;
    }

    const auto ring_va = static_cast<cpm_intr_entry *>(intr_queue->buffer_va);

    TraceVerbose(TRACE_INTR, "CPM Coal queue SW Index : %u", intr_queue->sw_index);
    TraceVerbose(TRACE_INTR, "CPM Intr PIDX : %u, Intr CIDX : %u", ring_va[intr_queue->sw_index].desc_pidx, ring_va[intr_queue->sw_index].desc_cidx);

    while (ring_va[intr_queue->sw_index].color == intr_queue->color) {
        q = irq_ctx->qdma_dev->qdma_get_queue_pair_by_hwid(ring_va[intr_queue->sw_index].qid);
        if (nullptr == q) {
            TraceError(TRACE_INTR, "Queue not found hw qid : %u Intr qid : %u",
                ring_va[intr_queue->sw_index].qid, intr_queue->idx);
            intr_queue->advance_sw_index();
            continue;
        }

        is_c2h = ring_va[intr_queue->sw_index].intr_type;

        schedule_dpc(q, is_c2h, active_processors);

        intr_queue->advance_sw_index();

        TraceVerbose(TRACE_INTR, "CPM QUEUE ID : %u, is_c2h : %d", ring_va[intr_queue->sw_index].qid, is_c2h);
    }

    if (q) {
        intr_queue->update_csr_cidx(q, intr_queue->sw_index);
    }
}

void handle_indirect_interrupt(
    PIRQ_CONTEXT irq_ctx)
{
    queue_pair *q = nullptr;
    UINT8 is_c2h = 0;
    CCHAR active_processors = (CCHAR)KeQueryActiveProcessorCount(NULL);

    auto intr_queue = irq_ctx->intr_q;
    if (nullptr == intr_queue) {
        TraceError(TRACE_INTR, "Invalid vector %lu was called in coal mode", irq_ctx->vector_id);
        return;
    }

    const auto ring_va = static_cast<intr_entry *>(intr_queue->buffer_va);

    TraceVerbose(TRACE_INTR, "Coal queue SW Index : %u", intr_queue->sw_index);
    TraceVerbose(TRACE_INTR, "Intr PIDX : %u, Intr CIDX : %u", ring_va[intr_queue->sw_index].desc_pidx, ring_va[intr_queue->sw_index].desc_cidx);

    while (ring_va[intr_queue->sw_index].color == intr_queue->color) {
        q = irq_ctx->qdma_dev->qdma_get_queue_pair_by_hwid(ring_va[intr_queue->sw_index].qid);
        if (nullptr == q) {
            TraceError(TRACE_INTR, "Queue not found hw qid : %u Intr qid : %u",
                ring_va[intr_queue->sw_index].qid, intr_queue->idx);
            intr_queue->advance_sw_index();
            continue;
        }

        is_c2h = ring_va[intr_queue->sw_index].intr_type;

        schedule_dpc(q, is_c2h, active_processors);

        intr_queue->advance_sw_index();

        TraceVerbose(TRACE_INTR, "QUEUE ID : %u, is_c2h : %d", ring_va[intr_queue->sw_index].qid, is_c2h);
    }

    if (q) {
        intr_queue->update_csr_cidx(q, intr_queue->sw_index);
    }
}

void handle_direct_interrupt(
    PIRQ_CONTEXT irq_ctx)
{
    CCHAR active_processors = (CCHAR)KeQueryActiveProcessorCount(NULL);
    PLIST_ENTRY entry;
    PLIST_ENTRY temp;

    LIST_FOR_EACH_ENTRY_SAFE(&irq_ctx->queue_list_head, temp, entry) {
        queue_pair *queue = CONTAINING_RECORD(entry, queue_pair, list_entry);

        TraceVerbose(TRACE_INTR, "SERVICING QUEUE : %u IN DIRECT INTERRUPT", queue->idx);
        schedule_dpc(queue, 0 /* H2C */, active_processors);
        schedule_dpc(queue, 1 /* C2H */, active_processors);

    }
}

int qdma_device::setup_legacy_vector(queue_pair& q)
{
    int ret = 0;
    int status = 0;
    int legacy_vec = 0;

    WdfInterruptAcquireLock(irq_mgr.irq[legacy_vec]);

    auto irq_ctx = get_irq_context(irq_mgr.irq[legacy_vec]);
    if (false == IS_LIST_EMPTY(&irq_ctx->queue_list_head)) {
        TraceError(TRACE_INTR, "Only One queue is supported in legacy interrupt mode");
        status = -(STATUS_UNSUCCESSFUL);
        goto ErrExit;
    }

    if (hw.qdma_legacy_intr_conf == nullptr) {
        TraceError(TRACE_INTR, "legacy interrupt mode not supported");
        status = -(STATUS_UNSUCCESSFUL);
        goto ErrExit;
    }

    ret = hw.qdma_legacy_intr_conf(this, DISABLE);
    if (ret < 0) {
        TraceError(TRACE_INTR, "qdma_disable_legacy_interrupt failed, ret : %d", ret);
        status = hw.qdma_get_error_code(ret);
        goto ErrExit;
    }

    LIST_ADD_TAIL(&irq_ctx->queue_list_head, &q.list_entry);

    ret = hw.qdma_legacy_intr_conf(this, ENABLE);
    if (ret < 0) {
        TraceError(TRACE_INTR, "qdma_enable_legacy_interrupt failed, ret : %d", ret);
        status = hw.qdma_get_error_code(ret);
        goto ErrExit;
    }

    TraceVerbose(TRACE_INTR, "Vector Allocated [0] for legacy interrupt mode");

    WdfInterruptReleaseLock(irq_mgr.irq[legacy_vec]);

    return legacy_vec;

ErrExit:
    WdfInterruptReleaseLock(irq_mgr.irq[legacy_vec]);
    return status;
}

/* Allocate MSIx Vector position */
UINT32 qdma_device::alloc_msix_vector_position(queue_pair& q)
{
    UINT32 weight;
    UINT32 vector;

    WdfSpinLockAcquire(irq_mgr.lock);

    vector = irq_mgr.data_vector_id_start;
    weight = irq_mgr.irq_weight[vector];

    for (UINT32 i = irq_mgr.data_vector_id_start + 1; i < irq_mgr.data_vector_id_end; ++i) {
        if (irq_mgr.irq_weight[i] < weight) {
            weight = irq_mgr.irq_weight[i];
            vector = i;
        }
    }

    ++irq_mgr.irq_weight[vector];

    WdfSpinLockRelease(irq_mgr.lock);

    if (op_mode == queue_op_mode::INTR_MODE) {
        WdfInterruptAcquireLock(irq_mgr.irq[vector]);

        auto irq_ctx = get_irq_context(irq_mgr.irq[vector]);
        LIST_ADD_TAIL(&irq_ctx->queue_list_head, &queue_pairs[q.idx].list_entry);

        WdfInterruptReleaseLock(irq_mgr.irq[vector]);
    }
    else if (op_mode == queue_op_mode::INTR_COAL_MODE) {
        /* For indirect interrupt, return absolute interrupt queue index */
        auto irq_ctx = get_irq_context(irq_mgr.irq[vector]);
        vector = irq_ctx->intr_q->idx_abs;
    }

    TraceVerbose(TRACE_INTR, "Vector Allocated [%u]. Weight : %u",
        vector, irq_mgr.irq_weight[vector]);

    return vector;
}

/* Free MSIX vector position */
void qdma_device::free_msix_vector_position(
    queue_pair& q,
    UINT32 vector)
{
    auto RELATIVE_INTR_QID = [](auto q) { return q % (UINT32)qdma_max_msix_vectors_per_pf; };
    if (op_mode == queue_op_mode::INTR_COAL_MODE)
        vector = RELATIVE_INTR_QID(vector);
    else if (op_mode == queue_op_mode::INTR_MODE) {
        WdfInterruptAcquireLock(irq_mgr.irq[vector]);
        auto irq_ctx = get_irq_context(irq_mgr.irq[vector]);
        PLIST_ENTRY entry;
        LIST_FOR_EACH_ENTRY(&irq_ctx->queue_list_head, entry) {
            queue_pair *queue = CONTAINING_RECORD(entry, queue_pair, list_entry);
            if (queue->idx == q.idx) {
                LIST_DEL_NODE(entry);
                break;
            }
        }
        WdfInterruptReleaseLock(irq_mgr.irq[vector]);
    }

    WdfSpinLockAcquire(irq_mgr.lock);

    --irq_mgr.irq_weight[vector];

    TraceVerbose(TRACE_INTR, "Vector Released. New weight : %u", irq_mgr.irq_weight[vector]);
    WdfSpinLockRelease(irq_mgr.lock);
}

int qdma_device::assign_interrupt_vector(queue_pair& q)
{
    UINT32 vec;

    if (irq_mgr.intr_type == interrupt_type::MSIX)
        vec = alloc_msix_vector_position(q);
    else {
        vec = setup_legacy_vector(q);
    }
    return vec;
}

void qdma_device::free_interrupt_vector(queue_pair& q, UINT32 vec_id)
{
    if (irq_mgr.intr_type == interrupt_type::MSIX)
        free_msix_vector_position(q, vec_id);
    else
        clear_legacy_vector(q, vec_id);
}

/* Clear legacy vector and disable interrupts */
void qdma_device::clear_legacy_vector(
    queue_pair& q,
    UINT32 vector)
{
    UNREFERENCED_PARAMETER(q);

    WdfInterruptAcquireLock(irq_mgr.irq[vector]);

    auto irq_ctx = get_irq_context(irq_mgr.irq[vector]);
    auto queue_item = irq_ctx->queue_list_head;

    if (hw.qdma_legacy_intr_conf != nullptr) {
        hw.qdma_legacy_intr_conf(this, DISABLE);
    }

    INIT_LIST_HEAD(&irq_ctx->queue_list_head);
    WdfInterruptReleaseLock(irq_mgr.irq[vector]);
}

NTSTATUS qdma_device::configure_irq(
    PIRQ_CONTEXT irq_context,
    ULONG vec)
{
    irq_context->vector_id = vec;
    irq_context->qdma_dev = this;

    irq_mgr.irq_weight[vec] = 0u;

    if (op_mode == queue_op_mode::INTR_COAL_MODE) { /* Indirect interrupt */
        irq_context->intr_q = &irq_mgr.intr_q[vec];
        irq_mgr.intr_q[vec].vector = vec;
        irq_context->is_coal = true;
        if (hw_version_info.ip_type == QDMA_VERSAL_HARD_IP) {
            irq_context->interrupt_handler = &cpm_handle_indirect_interrupt;
        }
        else {
            irq_context->interrupt_handler = &handle_indirect_interrupt;
        }
    }
    else { /* Direct interrupt */
        INIT_LIST_HEAD(&irq_context->queue_list_head);

        irq_context->is_coal = false;
        irq_context->interrupt_handler = &handle_direct_interrupt;
    }
    irq_context->intr_type = irq_mgr.intr_type;

    return STATUS_SUCCESS;
}

NTSTATUS qdma_device::arrange_msix_vector_types(void)
{
    ULONG vector = 0;

    if (dev_conf.is_master_pf) { /* Master PF */
        TraceInfo(TRACE_INTR, "Setting Error Interrupt by Master PF\n");
        irq_mgr.err_vector_id = vector;
        ++vector;
    }

    irq_mgr.user_vector_id = vector;
    ++vector;

    if (pcie.get_num_msix_vectors() <= vector) {
        return STATUS_UNSUCCESSFUL;
    }

    irq_mgr.data_vector_id_start = vector;

    if (op_mode == queue_op_mode::INTR_COAL_MODE)
        irq_mgr.data_vector_id_end = irq_mgr.data_vector_id_start + IND_INTR_MAX_DATA_VECTORS;
    else
        irq_mgr.data_vector_id_end = pcie.get_num_msix_vectors();

    TraceVerbose(TRACE_INTR, "Function: %0X, Err vec : %lu, User vec : %lu Data vec : %u, Tot : %lu",
        dev_conf.dev_sbdf.sbdf.fun_no, irq_mgr.err_vector_id,
        irq_mgr.user_vector_id, irq_mgr.data_vector_id_start,
        irq_mgr.data_vector_id_end);

    return STATUS_SUCCESS;
}

NTSTATUS qdma_device::setup_msix_interrupt(
    WDFCMRESLIST resources,
    const WDFCMRESLIST resources_translated)
{
    NTSTATUS status;

    /* Setup interrupts */
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resource;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resource_translated;

    ULONG numResources = WdfCmResourceListGetCount(resources_translated);
    TraceInfo(TRACE_INTR, "Total number of resource : %lu", numResources);

    status = arrange_msix_vector_types();
    if (!NT_SUCCESS(status)) {
        TraceError(TRACE_INTR, "Failed to arrange MSIx vectors");
        return status;
    }

    for (UINT i = 0, vec = 0; i < numResources && vec < pcie.get_num_msix_vectors(); i++) {

        resource = WdfCmResourceListGetDescriptor(resources, i);
        resource_translated = WdfCmResourceListGetDescriptor(resources_translated, i);
        if (resource_translated->Type != CmResourceTypeInterrupt) {
            continue;
        }

        WDF_INTERRUPT_CONFIG config;

        if (irq_mgr.user_vector_id == vec) {
            WDF_INTERRUPT_CONFIG_INIT(&config, EvtUserInterruptIsr, EvtUserInterruptDpc);
        }
        else if ((irq_mgr.err_vector_id == vec) && (dev_conf.is_master_pf)) {
            WDF_INTERRUPT_CONFIG_INIT(&config, EvtErrorInterruptIsr, EvtErrorInterruptDpc);
        }
        else { /* Data interrupts */
            WDF_INTERRUPT_CONFIG_INIT(&config, EvtDataInterruptIsr, nullptr);
        }

        config.InterruptRaw = resource;
        config.InterruptTranslated = resource_translated;
        config.EvtInterruptEnable = nullptr;
        config.EvtInterruptDisable = nullptr;
        config.AutomaticSerialization = TRUE;

        WDF_OBJECT_ATTRIBUTES attribs;
        WDF_OBJECT_ATTRIBUTES_INIT(&attribs);
        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attribs, IRQ_CONTEXT);

        status = WdfInterruptCreate(wdf_dev, &config, &attribs, &irq_mgr.irq[vec]);
        if (!NT_SUCCESS(status)) {
            TraceError(TRACE_INTR, "WdfInterruptCreate failed: %!STATUS!", status);
            return status;
        }

        /* FIXME: 2018.2 Bitstream Issue?? Unmasking of the MSIX vectors not happening. So doing it manually */
        if (hw_version_info.ip_type == QDMA_VERSAL_HARD_IP) {
            unmask_msi_entry(vec);
        }

        PIRQ_CONTEXT irq_context =  get_irq_context(irq_mgr.irq[vec]);
        status = configure_irq(irq_context, vec);
        if (!NT_SUCCESS(status)) {
            TraceError(TRACE_INTR, "WdfInterruptCreate failed: %!STATUS!", status);
            return status;
        }

        if ((irq_mgr.err_vector_id == vec) && (dev_conf.is_master_pf)) {
            int ret = hw.qdma_hw_error_intr_setup((void *)this, (uint16_t)dev_conf.dev_sbdf.sbdf.fun_no, (uint8_t)irq_mgr.err_vector_id);
            if (ret < 0) {
                TraceError(TRACE_INTR, "qdma_error_interrupt_setup() failed with error %d", ret);
                return hw.qdma_get_error_code(ret);
            }

            ret = hw.qdma_hw_error_enable((void *)this, QDMA_ERRS_ALL);
            if (ret < 0) {
                TraceError(TRACE_INTR, "qdma_error_enable() failed with error %d", ret);
                return hw.qdma_get_error_code(ret);
            }

            ret = hw.qdma_hw_error_intr_rearm((void *)this);
            if (ret < 0) {
                TraceError(TRACE_INTR, "qdma_error_interrupt_rearm() failed with error %d", ret);
                return hw.qdma_get_error_code(ret);
            }
        }

        ++vec;

        TraceInfo(TRACE_INTR, "INTERRUPT REGISTERED FOR VECTOR ID: : %d WEIGHT : %d",
            irq_context->vector_id, irq_context->weight);
    }

    return STATUS_SUCCESS;
}

NTSTATUS qdma_device::setup_legacy_interrupt(
    WDFCMRESLIST resources,
    const WDFCMRESLIST resources_translated)
{
    NTSTATUS status;

    /* Setup interrupts */
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resource;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resource_translated;

    ULONG numResources = WdfCmResourceListGetCount(resources_translated);
    TraceInfo(TRACE_INTR, "Total number of resource : %lu", numResources);


    for (UINT i = 0, vec = 0; i < numResources; i++) {

        resource = WdfCmResourceListGetDescriptor(resources, i);
        resource_translated = WdfCmResourceListGetDescriptor(resources_translated, i);
        if (resource_translated->Type != CmResourceTypeInterrupt) {
            continue;
        }

        WDF_INTERRUPT_CONFIG config;

        /* Initializing the interrupt config with Data ISR and DPC handlers */
        WDF_INTERRUPT_CONFIG_INIT(&config, EvtDataInterruptIsr, nullptr);

        config.InterruptRaw = resource;
        config.InterruptTranslated = resource_translated;
        config.EvtInterruptEnable = nullptr;
        config.EvtInterruptDisable = nullptr;
        config.AutomaticSerialization = TRUE;

        WDF_OBJECT_ATTRIBUTES attribs;
        WDF_OBJECT_ATTRIBUTES_INIT(&attribs);
        WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&attribs, IRQ_CONTEXT);

        status = WdfInterruptCreate(wdf_dev, &config, &attribs, &irq_mgr.irq[vec]);
        if (!NT_SUCCESS(status)) {
            TraceError(TRACE_INTR, "WdfInterruptCreate failed: %!STATUS!", status);
            return status;
        }

        PIRQ_CONTEXT irq_context = get_irq_context(irq_mgr.irq[vec]);
        status = configure_irq(irq_context, vec);
        if (!NT_SUCCESS(status)) {
            TraceError(TRACE_INTR, "WdfInterruptCreate failed: %!STATUS!", status);
            return status;
        }

        ++vec;

        TraceInfo(TRACE_INTR, "LEGACY INTERRUPT REGISTERED FOR VECTOR ID: : %d WEIGHT : %d",
            irq_context->vector_id, irq_context->weight);

        /* Only One Vector for Legacy interrupt */
        break;
    }

    return STATUS_SUCCESS;
}

NTSTATUS qdma_device::intr_setup(
    WDFCMRESLIST resources,
    const WDFCMRESLIST resources_translated)
{
    UINT32 i;
    NTSTATUS status = STATUS_SUCCESS;
    PCM_PARTIAL_RESOURCE_DESCRIPTOR resource_desc;
    ULONG numResources = WdfCmResourceListGetCount(resources_translated);

    /* Initialize IRQ spin lock */
    WDF_OBJECT_ATTRIBUTES attr;
    WDF_OBJECT_ATTRIBUTES_INIT(&attr);
    attr.ParentObject = wdf_dev;

    status = WdfSpinLockCreate(&attr, &irq_mgr.lock);
    if (!NT_SUCCESS(status)) {
        TraceError(TRACE_INTR, "WdfSpinLockCreate failed: %!STATUS!", status);
        return status;
    }

    irq_mgr.intr_type = interrupt_type::NONE;
    for (i = 0; i < numResources; i++) {
        resource_desc = WdfCmResourceListGetDescriptor(resources_translated, i);
        if (resource_desc->Type != CmResourceTypeInterrupt) {
            TraceVerbose(TRACE_INTR, "Non Interrupt Resource : %d", resource_desc->Type);
            continue;
        }

        if (resource_desc->Flags & CM_RESOURCE_INTERRUPT_MESSAGE) {
            irq_mgr.intr_type = interrupt_type::MSIX;

            TraceVerbose(TRACE_INTR,
                "Message Interrupt level 0x%0x, Vector 0x%0x, MessageCount %u\n"
                , resource_desc->u.MessageInterrupt.Translated.Level
                , resource_desc->u.MessageInterrupt.Translated.Vector
                , resource_desc->u.MessageInterrupt.Raw.MessageCount
            );
        }
        else {
            irq_mgr.intr_type = interrupt_type::LEGACY;

            TraceVerbose(TRACE_INTR,
                "Legacy Interrupt level: 0x%0x, Vector: 0x%0x\n"
                , resource_desc->u.Interrupt.Level
                , resource_desc->u.Interrupt.Vector
            );
        }
        break;
    }

    if (irq_mgr.intr_type == interrupt_type::LEGACY) {
        status = setup_legacy_interrupt(resources, resources_translated);
        if (!NT_SUCCESS(status)) {
            TraceError(TRACE_INTR, " setup_legacy_interrupt() failed: %!STATUS!", status);
        }
    }
    else if (irq_mgr.intr_type == interrupt_type::MSIX) {
        status = setup_msix_interrupt(resources, resources_translated);
        if (!NT_SUCCESS(status)) {
            TraceError(TRACE_INTR, " setup_msix_interrupt() failed: %!STATUS!", status);
        }
    }
    else {
        TraceError(TRACE_INTR, "Invalid Interrupt Type : %d (valid are legacy and msix)", (int)irq_mgr.intr_type);
        return STATUS_UNSUCCESSFUL;
    }

    return status;
}

void qdma_device::intr_teardown(void)
{
    if ((irq_mgr.intr_type == interrupt_type::LEGACY) &&
        (hw.qdma_legacy_intr_conf != nullptr))
        hw.qdma_legacy_intr_conf((void *)this, DISABLE);
    else {
        /* FIXME: 2018.2 Bitstream issue?? Mask the msix vectors */
        if (hw_version_info.ip_type == QDMA_VERSAL_HARD_IP) {
            for (auto i = 0u; i < pcie.get_num_msix_vectors(); ++i) {
                mask_msi_entry(i);
            }
        }
    }
}
