/*
 * Copyright (c) 2016 Cloudbase Solutions Srl
 *
 * 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 "Recirc.h"
#include "Flow.h"
#include "Jhash.h"

/*
 * --------------------------------------------------------------------------
 * '_OVS_DEFERRED_ACTION_QUEUE' structure is responsible for keeping track of
 * all deferred actions. The maximum number of deferred actions should not
 * exceed 'DEFERRED_ACTION_QUEUE_SIZE'.
 * --------------------------------------------------------------------------
 */
typedef struct _OVS_DEFERRED_ACTION_QUEUE {
    UINT32  head;
    UINT32  tail;
    OVS_DEFERRED_ACTION deferredActions[DEFERRED_ACTION_QUEUE_SIZE];
} OVS_DEFERRED_ACTION_QUEUE, *POVS_DEFERRED_ACTION_QUEUE;

typedef struct _OVS_DEFERRED_ACTION_DATA {
    OVS_DEFERRED_ACTION_QUEUE   queue;
    UINT32                      level;
} OVS_DEFERRED_ACTION_DATA, *POVS_DEFERRED_ACTION_DATA;

static POVS_DEFERRED_ACTION_DATA deferredData = NULL;

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsInit --
 *     The function allocates all necessary deferred actions resources.
 * --------------------------------------------------------------------------
 */
NTSTATUS
OvsDeferredActionsInit()
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG count = KeQueryMaximumProcessorCountEx(ALL_PROCESSOR_GROUPS);

    deferredData = OvsAllocateMemoryPerCpu(sizeof(*deferredData),
                                           count,
                                           OVS_RECIRC_POOL_TAG);
    if (!deferredData) {
        status = NDIS_STATUS_RESOURCES;
    }

    return status;
}

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsCleanup --
 *     The function frees all deferred actions resources.
 * --------------------------------------------------------------------------
 */
VOID
OvsDeferredActionsCleanup()
{
    if (deferredData) {
        OvsFreeMemoryWithTag(deferredData, OVS_RECIRC_POOL_TAG);
        deferredData = NULL;
    }
}

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsQueueGet --
 *     The function returns the deferred action queue corresponding to the
 *     current processor.
 * --------------------------------------------------------------------------
 */
POVS_DEFERRED_ACTION_QUEUE
OvsDeferredActionsQueueGet()
{
    POVS_DEFERRED_ACTION_QUEUE queue = NULL;
    ULONG index = 0;
    KIRQL oldIrql = KeGetCurrentIrql();

    if (oldIrql < DISPATCH_LEVEL) {
        KeRaiseIrqlToDpcLevel();
    }

    index = KeGetCurrentProcessorNumberEx(NULL);
    queue = &deferredData[index].queue;

    if (oldIrql < DISPATCH_LEVEL) {
        KeLowerIrql(oldIrql);
    }

    return queue;
}

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsLevelGet --
 *     The function returns the deferred action execution level corresponding
 *     to the current processor.
 * --------------------------------------------------------------------------
 */
UINT32
OvsDeferredActionsLevelGet()
{
    UINT32 level = 0;
    ULONG index = 0;
    KIRQL oldIrql = KeGetCurrentIrql();

    if (oldIrql < DISPATCH_LEVEL) {
        KeRaiseIrqlToDpcLevel();
    }

    index = KeGetCurrentProcessorNumberEx(NULL);
    level = deferredData[index].level;

    if (oldIrql < DISPATCH_LEVEL) {
        KeLowerIrql(oldIrql);
    }

    return level;
}

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsLevelInc --
 *     The function increments the deferred action execution level
 *     corresponding to the current processor.
 * --------------------------------------------------------------------------
 */
VOID
OvsDeferredActionsLevelInc()
{
    ULONG index = 0;
    KIRQL oldIrql = KeGetCurrentIrql();

    if (oldIrql < DISPATCH_LEVEL) {
        KeRaiseIrqlToDpcLevel();
    }

    index = KeGetCurrentProcessorNumberEx(NULL);
    deferredData[index].level++;

    if (oldIrql < DISPATCH_LEVEL) {
        KeLowerIrql(oldIrql);
    }
}

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsLevelDec --
 *     The function decrements the deferred action execution level
 *     corresponding to the current processor.
 * --------------------------------------------------------------------------
 */
VOID
OvsDeferredActionsLevelDec()
{
    ULONG index = 0;
    KIRQL oldIrql = KeGetCurrentIrql();

    if (oldIrql < DISPATCH_LEVEL) {
        KeRaiseIrqlToDpcLevel();
    }

    index = KeGetCurrentProcessorNumberEx(NULL);
    deferredData[index].level--;

    if (oldIrql < DISPATCH_LEVEL) {
        KeLowerIrql(oldIrql);
    }
}

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsQueueInit --
 *     The function resets the queue to be ready for the next packet.
 * --------------------------------------------------------------------------
 */
static
VOID
OvsDeferredActionsQueueInit(POVS_DEFERRED_ACTION_QUEUE queue)
{
    queue->head = 0;
    queue->tail = 0;
}

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsQueueIsEmpty --
 *     The function verifies if the queue is empty.
 * --------------------------------------------------------------------------
 */
static
BOOLEAN
OvsDeferredActionsQueueIsEmpty(const POVS_DEFERRED_ACTION_QUEUE queue)
{
    return (queue->head == queue->tail);
}

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsQueuePop --
 *     The function pops the next queue element.
 * --------------------------------------------------------------------------
 */
static
POVS_DEFERRED_ACTION
OvsDeferredActionsQueuePop(POVS_DEFERRED_ACTION_QUEUE queue)
{
    POVS_DEFERRED_ACTION deferredAction = NULL;
    KIRQL oldIrql = KeGetCurrentIrql();

    if (oldIrql < DISPATCH_LEVEL) {
        KeRaiseIrqlToDpcLevel();
    }

    if (OvsDeferredActionsQueueIsEmpty(queue)) {
        /* Reset the queue for the next packet. */
        OvsDeferredActionsQueueInit(queue);
    } else {
        deferredAction = &queue->deferredActions[queue->tail++];
    }

    if (oldIrql < DISPATCH_LEVEL) {
        KeLowerIrql(oldIrql);
    }

    return deferredAction;
}

/*
 * --------------------------------------------------------------------------
 * OvsDeferredActionsQueuePush --
 *     The function pushes the current element in the deferred actions queue.
 * --------------------------------------------------------------------------
 */
static
POVS_DEFERRED_ACTION
OvsDeferredActionsQueuePush(POVS_DEFERRED_ACTION_QUEUE queue)
{
    POVS_DEFERRED_ACTION deferredAction = NULL;
    KIRQL oldIrql = KeGetCurrentIrql();

    if (oldIrql < DISPATCH_LEVEL) {
        KeRaiseIrqlToDpcLevel();
    }

    if (queue->head < DEFERRED_ACTION_QUEUE_SIZE) {
        deferredAction = &queue->deferredActions[queue->head++];
    }

    if (oldIrql < DISPATCH_LEVEL) {
        KeLowerIrql(oldIrql);
    }

    return deferredAction;
}

/*
 * --------------------------------------------------------------------------
 * OvsAddDeferredActions --
 *     This function adds the deferred action to the current CPU queue and
 *     returns the new queue entry if the queue is not already full.
 * --------------------------------------------------------------------------
 */
POVS_DEFERRED_ACTION
OvsAddDeferredActions(PNET_BUFFER_LIST nbl,
                      OvsFlowKey *key,
                      POVS_PACKET_HDR_INFO layers,
                      const PNL_ATTR actions)
{
    POVS_DEFERRED_ACTION_QUEUE queue = OvsDeferredActionsQueueGet();
    POVS_DEFERRED_ACTION deferredAction = NULL;
    OVS_PACKET_HDR_INFO layersInit = { 0 };

    deferredAction = OvsDeferredActionsQueuePush(queue);
    if (deferredAction) {
        deferredAction->nbl = nbl;
        deferredAction->actions = actions;
        deferredAction->key = *key;
        if (layers) {
            deferredAction->layers = *layers;
        } else {
            deferredAction->layers = layersInit;
        }
    }

    return deferredAction;
}

/*
 * --------------------------------------------------------------------------
 * OvsProcessDeferredActions --
 *     This function processes all deferred actions contained in the queue
 *     corresponding to the current CPU.
 * --------------------------------------------------------------------------
 */
NDIS_STATUS
OvsProcessDeferredActions(POVS_SWITCH_CONTEXT switchContext,
                          OvsCompletionList *completionList,
                          UINT32 portNo,
                          ULONG sendFlags)
{
    NDIS_STATUS status = NDIS_STATUS_SUCCESS;
    POVS_DEFERRED_ACTION_QUEUE queue = OvsDeferredActionsQueueGet();
    POVS_DEFERRED_ACTION deferredAction = NULL;
    POVS_PACKET_HDR_INFO layersDeferred = NULL;

    /* Process all deferred actions. */
    while ((deferredAction = OvsDeferredActionsQueuePop(queue)) != NULL) {
        layersDeferred = &(deferredAction->layers);

        if (deferredAction->actions) {
            status = OvsDoExecuteActions(switchContext,
                                         completionList,
                                         deferredAction->nbl,
                                         portNo,
                                         sendFlags,
                                         &deferredAction->key, NULL,
                                         layersDeferred, deferredAction->actions,
                                         NlAttrGetSize(deferredAction->actions));
        } else {
            status = OvsDoRecirc(switchContext,
                                 completionList,
                                 deferredAction->nbl,
                                 &deferredAction->key,
                                 portNo,
                                 layersDeferred);
        }
    }

    return status;
}
