/*
 * Copyright (c) 2014 VMware, 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.
 */

#ifndef __VPORT_H_
#define __VPORT_H_ 1

#include "Gre.h"
#include "Stt.h"
#include "Switch.h"
#include "VxLan.h"
#include "Geneve.h"

#define OVS_MAX_DPPORTS             MAXUINT16
#define OVS_DPPORT_NUMBER_INVALID   OVS_MAX_DPPORTS
/*
 * The local port (0) is a reserved port, that is not allowed to be be
 * created by the netlink command vport add. On linux, this port is created
 * at netlink command datapath new. However, on windows, we do not need to
 * create it, and more, we shouldn't. The userspace attempts to create two
 * internal vports, the LOCAL port (0) and the internal port (with any other
 * port number). The non-LOCAL internal port is used in the userspace when it
 * requests the internal port.
 */
#define OVS_DPPORT_NUMBER_LOCAL    0

/*
 * A Vport, or Virtual Port, is a port on the OVS. It can be one of the
 * following types. Some of the Vports are "real" ports on the hyper-v switch,
 * and some are not:
 * - VIF port (VM's NIC)
 * - External Adapters (physical NIC)
 * - Internal Adapter (Virtual adapter exposed on the host).
 * - Tunnel ports created by OVS userspace.
 */

typedef enum {
    OVS_STATE_UNKNOWN,
    OVS_STATE_PORT_CREATED,
    OVS_STATE_NIC_CREATED,
    OVS_STATE_CONNECTED,
    OVS_STATE_PORT_TEAR_DOWN,
    OVS_STATE_PORT_DELETED,
} OVS_VPORT_STATE;

typedef struct _OVS_VPORT_STATS {
    UINT64 rxPackets;
    UINT64 txPackets;
    UINT64 rxBytes;
    UINT64 txBytes;
} OVS_VPORT_STATS;

typedef struct _OVS_VPORT_ERR_STATS {
    UINT64  rxErrors;
    UINT64  txErrors;
    UINT64  rxDropped;
    UINT64  txDropped;
} OVS_VPORT_ERR_STATS;

/* used for vport netlink commands. */
typedef struct _OVS_VPORT_FULL_STATS {
    OVS_VPORT_STATS;
    OVS_VPORT_ERR_STATS;
}OVS_VPORT_FULL_STATS;
/*
 * Each internal, external adapter or vritual adapter has
 * one vport entry. In addition, we have one vport for each
 * tunnel type, such as vxlan, gre
 */
typedef struct _OVS_VPORT_ENTRY {
    LIST_ENTRY             ovsNameLink;
    LIST_ENTRY             portIdLink;
    LIST_ENTRY             portNoLink;
    LIST_ENTRY             tunnelVportLink;

    OVS_VPORT_STATE        ovsState;
    OVS_VPORT_TYPE         ovsType;
    OVS_VPORT_STATS        stats;
    OVS_VPORT_ERR_STATS    errStats;
    UINT32                 portNo;
    UINT32                 mtu;
    /* ovsName is the ovs (datapath) port name - it is null terminated. */
    CHAR                   ovsName[OVS_MAX_PORT_NAME_LENGTH];

    PVOID                  priv;
    NDIS_SWITCH_PORT_ID    portId;
    NDIS_SWITCH_NIC_INDEX  nicIndex;
    NDIS_SWITCH_NIC_TYPE   nicType;
    UINT16                 numaNodeId;
    NDIS_SWITCH_PORT_STATE portState;
    NDIS_SWITCH_NIC_STATE  nicState;
    NDIS_SWITCH_PORT_TYPE  portType;

    UINT8                  permMacAddress[ETH_ADDR_LEN];
    UINT8                  currMacAddress[ETH_ADDR_LEN];
    UINT8                  vmMacAddress[ETH_ADDR_LEN];

    NDIS_SWITCH_PORT_NAME  hvPortName;
    IF_COUNTED_STRING      portFriendlyName;
    NDIS_SWITCH_NIC_NAME   nicName;
    NDIS_VM_NAME           vmName;
    GUID                   netCfgInstanceId;
    /*
     * OVS userpace has a notion of bridges which basically defines an
     * L2-domain. Each "bridge" has an "internal" port of type
     * OVS_VPORT_TYPE_INTERNAL. Such a port is connected to the OVS datapath in
     * one end, and the other end is a virtual adapter on the hypervisor host.
     * This is akin to the Hyper-V "internal" NIC. It is intuitive to map the
     * Hyper-V "internal" NIC to the OVS bridge's "internal" port, but there's
     * only one Hyper-V NIC but multiple bridges. To support multiple OVS bridge
     * "internal" ports, we use the flag 'isBridgeInternal' in each vport. We
     * support addition of multiple bridge-internal ports. A vport with
     * 'isBridgeInternal' == TRUE is a dummy port and has no backing currently.
     * If a flow actions specifies the output port to be a bridge-internal port,
     * the port is silently ignored.
     */
    BOOLEAN                isBridgeInternal;
    BOOLEAN                isExternal;
    UINT32                 upcallPid; /* netlink upcall port id */
    PNL_ATTR               portOptions;
    BOOLEAN                isAbsentOnHv; /* Is this port present on the
                                             Hyper-V switch? */
} OVS_VPORT_ENTRY, *POVS_VPORT_ENTRY;

struct _OVS_SWITCH_CONTEXT;

POVS_VPORT_ENTRY OvsFindVportByPortNo(POVS_SWITCH_CONTEXT switchContext,
                                      UINT32 portNo);
/* "name" is null-terminated */
POVS_VPORT_ENTRY OvsFindVportByOvsName(POVS_SWITCH_CONTEXT switchContext,
                                       PSTR name);
POVS_VPORT_ENTRY OvsFindVportByHvNameA(POVS_SWITCH_CONTEXT switchContext,
                                       PSTR name);
POVS_VPORT_ENTRY OvsFindVportByPortIdAndNicIndex(POVS_SWITCH_CONTEXT switchContext,
                                                 NDIS_SWITCH_PORT_ID portId,
                                                 NDIS_SWITCH_NIC_INDEX index);
POVS_VPORT_ENTRY OvsFindTunnelVportByDstPortAndType(POVS_SWITCH_CONTEXT switchContext,
                                                    UINT16 dstPort,
                                                    OVS_VPORT_TYPE ovsPortType);
POVS_VPORT_ENTRY OvsFindTunnelVportByDstPortAndNWProto(POVS_SWITCH_CONTEXT switchContext,
                                                       UINT16 dstPort,
                                                       UINT8 nwProto);
POVS_VPORT_ENTRY OvsFindTunnelVportByPortType(POVS_SWITCH_CONTEXT switchContext,
                                              OVS_VPORT_TYPE ovsPortType);

NDIS_STATUS OvsAddConfiguredSwitchPorts(struct _OVS_SWITCH_CONTEXT *switchContext);
NDIS_STATUS OvsInitConfiguredSwitchNics(struct _OVS_SWITCH_CONTEXT *switchContext);

VOID OvsClearAllSwitchVports(struct _OVS_SWITCH_CONTEXT *switchContext);

NDIS_STATUS HvCreateNic(POVS_SWITCH_CONTEXT switchContext,
                        PNDIS_SWITCH_NIC_PARAMETERS nicParam);
NDIS_STATUS HvCreatePort(POVS_SWITCH_CONTEXT switchContext,
                         PNDIS_SWITCH_PORT_PARAMETERS portParam,
                         NDIS_SWITCH_NIC_INDEX nicIndex);
NDIS_STATUS HvUpdatePort(POVS_SWITCH_CONTEXT switchContext,
                         PNDIS_SWITCH_PORT_PARAMETERS portParam);
VOID HvTeardownPort(POVS_SWITCH_CONTEXT switchContext,
                    PNDIS_SWITCH_PORT_PARAMETERS portParam);
VOID HvDeletePort(POVS_SWITCH_CONTEXT switchContext,
                  PNDIS_SWITCH_PORT_PARAMETERS portParam);
VOID HvConnectNic(POVS_SWITCH_CONTEXT switchContext,
                  PNDIS_SWITCH_NIC_PARAMETERS nicParam);
VOID HvUpdateNic(POVS_SWITCH_CONTEXT switchContext,
                 PNDIS_SWITCH_NIC_PARAMETERS nicParam);
VOID HvDeleteNic(POVS_SWITCH_CONTEXT switchContext,
                 PNDIS_SWITCH_NIC_PARAMETERS nicParam);
VOID HvDisconnectNic(POVS_SWITCH_CONTEXT switchContext,
                     PNDIS_SWITCH_NIC_PARAMETERS nicParam);

static __inline BOOLEAN
OvsIsTunnelVportType(OVS_VPORT_TYPE ovsType)
{
    return ovsType == OVS_VPORT_TYPE_VXLAN ||
           ovsType == OVS_VPORT_TYPE_GENEVE ||
           ovsType == OVS_VPORT_TYPE_STT ||
           ovsType == OVS_VPORT_TYPE_GRE;
}


static __inline PVOID
GetOvsVportPriv(POVS_VPORT_ENTRY ovsVport)
{
    return ovsVport->priv;
}

static __inline BOOLEAN
OvsIsInternalVportType(OVS_VPORT_TYPE ovsType)
{
    return ovsType == OVS_VPORT_TYPE_INTERNAL;
}

static __inline BOOLEAN
OvsIsVirtualExternalVport(POVS_VPORT_ENTRY vport)
{
    return vport->nicType == NdisSwitchNicTypeExternal &&
           vport->nicIndex == 0;
}

static __inline BOOLEAN
OvsIsRealExternalVport(POVS_VPORT_ENTRY vport)
{
    return vport->nicType == NdisSwitchNicTypeExternal &&
           vport->nicIndex != 0;
}

static __inline BOOLEAN
OvsIsBridgeInternalVport(POVS_VPORT_ENTRY vport)
{
    ASSERT(vport->isBridgeInternal != TRUE ||
           vport->ovsType == OVS_VPORT_TYPE_INTERNAL);
    return vport->isBridgeInternal == TRUE;
}

static __inline BOOLEAN
OvsIsInternalNIC(NDIS_SWITCH_NIC_TYPE   nicType)
{
    return nicType == NdisSwitchNicTypeInternal;
}

static __inline BOOLEAN
OvsIsRealExternalNIC(NDIS_SWITCH_NIC_TYPE   nicType,
                     NDIS_SWITCH_NIC_INDEX  nicIndex)
{
    return nicType == NdisSwitchNicTypeExternal &&
           nicIndex != 0;
}

NTSTATUS OvsRemoveAndDeleteVport(PVOID usrParamsCtx,
                                 POVS_SWITCH_CONTEXT switchContext,
                                 POVS_VPORT_ENTRY vport,
                                 BOOLEAN hvDelete, BOOLEAN ovsDelete);
static __inline POVS_VPORT_ENTRY
OvsGetExternalVport(POVS_SWITCH_CONTEXT switchContext)
{
    return switchContext->virtualExternalVport;
}

static __inline UINT32
OvsGetExternalMtu(POVS_SWITCH_CONTEXT switchContext)
{
    ASSERT(OvsGetExternalVport(switchContext));
    return ((POVS_VPORT_ENTRY) OvsGetExternalVport(switchContext))->mtu;
}

static __inline UINT16
GetPortFromPriv(POVS_VPORT_ENTRY vport)
{
    UINT16 dstPort = 0;
    PVOID vportPriv = GetOvsVportPriv(vport);

    /* XXX would better to have a commom tunnel "parent" structure */
    ASSERT(vportPriv);
    switch(vport->ovsType) {
    case OVS_VPORT_TYPE_GRE:
        break;
    case OVS_VPORT_TYPE_STT:
        dstPort = ((POVS_STT_VPORT)vportPriv)->dstPort;
        break;
    case OVS_VPORT_TYPE_VXLAN:
        dstPort = ((POVS_VXLAN_VPORT)vportPriv)->dstPort;
        break;
    case OVS_VPORT_TYPE_GENEVE:
        dstPort = ((POVS_GENEVE_VPORT) vportPriv)->dstPort;
        break;
    default:
        ASSERT(! "Port is not a tunnel port");
    }
    ASSERT(dstPort || vport->ovsType == OVS_VPORT_TYPE_GRE);
    return dstPort;
}

NDIS_STATUS InitOvsVportCommon(POVS_SWITCH_CONTEXT switchContext,
                               POVS_VPORT_ENTRY vport);
NTSTATUS OvsInitTunnelVport(PVOID usrParamsCtx, POVS_VPORT_ENTRY vport,
                            OVS_VPORT_TYPE ovsType, UINT16 dstport);
NTSTATUS OvsInitBridgeInternalVport(POVS_VPORT_ENTRY vport);

POVS_VPORT_ENTRY OvsAllocateVport(VOID);

#endif /* __VPORT_H_ */
