//------------------------------------------------------------------------------
tOplkError veth_init(const UINT8 aSrcMac_p[6])
{

#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0))
    // allocate net device structure with priv pointing to stats structure
    pVEthNetDevice_g = alloc_netdev(sizeof(struct net_device_stats), PLK_VETH_NAME,
                                    ether_setup);
#else
    // allocate net device structure with priv pointing to stats structure
    pVEthNetDevice_g = alloc_netdev(sizeof(struct net_device_stats), PLK_VETH_NAME,
                                    NET_NAME_UNKNOWN, ether_setup);
#endif


    if (pVEthNetDevice_g == NULL)
        return kErrorNoResource;

    pVEthNetDevice_g->netdev_ops        = &oplk_netdev_ops;
    pVEthNetDevice_g->watchdog_timeo    = VETH_TX_TIMEOUT;
    pVEthNetDevice_g->destructor        = free_netdev;

    // copy own MAC address to net device structure
    OPLK_MEMCPY(pVEthNetDevice_g->dev_addr, aSrcMac_p, 6);

    //register VEth to the network subsystem
    if (register_netdev(pVEthNetDevice_g))
        DEBUG_LVL_VETH_TRACE("veth_init: Could not register VEth...\n");
    else
        DEBUG_LVL_VETH_TRACE("veth_init: Register VEth successful...\n");

    return kErrorOk;
}
//------------------------------------------------------------------------------
static int veth_xmit(struct sk_buff* pSkb_p, struct net_device* pNetDevice_p)
{
    tOplkError      ret = kErrorOk;
    tFrameInfo      frameInfo;

    //transmit function
    struct net_device_stats* pStats = netdev_priv(pNetDevice_p);

    //save timestemp
    pNetDevice_p->trans_start = jiffies;

    frameInfo.pFrame = (tPlkFrame*)pSkb_p->data;
    frameInfo.frameSize = pSkb_p->len;

    //call send fkt on DLL
    ret = dllkcal_sendAsyncFrame(&frameInfo, kDllAsyncReqPrioGeneric);
    if (ret != kErrorOk)
    {
        DEBUG_LVL_VETH_TRACE("veth_xmit: dllkcal_sendAsyncFrame returned 0x%02X\n", ret);
        netif_stop_queue(pNetDevice_p);
        goto Exit;
    }
    else
    {
        DEBUG_LVL_VETH_TRACE("veth_xmit: frame passed to DLL\n");
        dev_kfree_skb(pSkb_p);

        //set stats for the device
        pStats->tx_packets++;
        pStats->tx_bytes += frameInfo.frameSize;
    }

Exit:
    return 0;
}
//------------------------------------------------------------------------------
static void getMacAdrs(UINT8* pMac_p)
{
    struct ifreq    ifr;
    int             sock;

    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        DEBUG_LVL_VETH_TRACE("%s: Cannot open udp socket for MAC reading: %s\n",
                             __func__,
                             strerror(errno));
        return;
    }

    OPLK_MEMSET(&ifr, 0, sizeof(struct ifreq));
    strncpy(ifr.ifr_name, "plk", IFNAMSIZ);

    if (ioctl(sock, SIOCGIFHWADDR, &ifr) < 0)
    {
        DEBUG_LVL_VETH_TRACE("Cannot get MAC address: '%s' (%d)\n", strerror(errno), errno);
    }

    DEBUG_LVL_VETH_TRACE("Get Mac addr %02x:%02x:%02x:%02x:%02x:%02x\n",
                         ifr.ifr_hwaddr.sa_data[0],
                         ifr.ifr_hwaddr.sa_data[1],
                         ifr.ifr_hwaddr.sa_data[2],
                         ifr.ifr_hwaddr.sa_data[3],
                         ifr.ifr_hwaddr.sa_data[4],
                         ifr.ifr_hwaddr.sa_data[5]);

    close(sock);

    OPLK_MEMCPY(pMac_p, &ifr.ifr_hwaddr.sa_data[0], ETH_ALEN);
}
//------------------------------------------------------------------------------
static int veth_close(struct net_device* pNetDevice_p)
{
    DEBUG_LVL_VETH_TRACE("veth_close\n");

    dllk_deregAsyncHandler(veth_receiveFrame);
    netif_stop_queue(pNetDevice_p);     //stop the interface queue for the network subsystem
    return 0;
}
//------------------------------------------------------------------------------
static void vethTxTimeout(struct net_device* pNetDevice_p)
{
    DEBUG_LVL_VETH_TRACE("%s()\n", __func__);

    // $$$ d.k.: move to extra function, which is called by DLL when new space is available in TxFifo
    if (netif_queue_stopped(pNetDevice_p))
        netif_wake_queue(pNetDevice_p);
}
//------------------------------------------------------------------------------
static int vethStop(struct net_device* pNetDevice_p)
{
    DEBUG_LVL_VETH_TRACE("%s()\n", __func__);

    dllk_deregAsyncHandler(receiveFrameCb);
    netif_stop_queue(pNetDevice_p);     //stop the interface queue for the network subsystem

    return 0;
}
//------------------------------------------------------------------------------
static tOplkError receiveFrameCb(tFrameInfo* pFrameInfo_p,
                                 tEdrvReleaseRxBuffer* pReleaseRxBuffer_p)
{
    tOplkError               ret = kErrorOk;
    struct net_device*       pNetDevice = pVEthNetDevice_g;
    struct net_device_stats* pStats = netdev_priv(pNetDevice);
    struct sk_buff*          pSkb;

    DEBUG_LVL_VETH_TRACE("%s(): FrameSize=%u\n", __func__, pFrameInfo_p->frameSize);

    if ((pSkb = dev_alloc_skb(pFrameInfo_p->frameSize + 2)) == NULL)
    {
        pStats->rx_dropped++;
        goto Exit;
    }
    pSkb->dev = pNetDevice;

    skb_reserve(pSkb, 2);

    OPLK_MEMCPY((void*)skb_put(pSkb, pFrameInfo_p->frameSize), pFrameInfo_p->frame.pBuffer, pFrameInfo_p->frameSize);

    pSkb->protocol = eth_type_trans(pSkb, pNetDevice);
    pSkb->ip_summed = CHECKSUM_UNNECESSARY;

    netif_rx(pSkb);         // call netif_rx with skb

    DEBUG_LVL_VETH_TRACE("%s(): SrcMAC: %02X:%02X:%02x:%02X:%02X:%02x\n",
                         __func__,
                         pFrameInfo_p->frame.pBuffer->aSrcMac[0],
                         pFrameInfo_p->frame.pBuffer->aSrcMac[1],
                         pFrameInfo_p->frame.pBuffer->aSrcMac[2],
                         pFrameInfo_p->frame.pBuffer->aSrcMac[3],
                         pFrameInfo_p->frame.pBuffer->aSrcMac[4],
                         pFrameInfo_p->frame.pBuffer->aSrcMac[5]);

    // update receive statistics
    pStats->rx_packets++;
    pStats->rx_bytes += pFrameInfo_p->frameSize;

Exit:
    *pReleaseRxBuffer_p = kEdrvReleaseRxBufferImmediately;

    return ret;
}
//------------------------------------------------------------------------------
tOplkError veth_init(const UINT8 aSrcMac_p[6])
{
    tOplkError      ret;
    struct ifreq    ifr;
    int             err;

    if ((vethInstance_l.fd = open(TUN_DEV_NAME, O_RDWR)) < 0)
    {
        DEBUG_LVL_VETH_TRACE("Error opening %s\n", TUN_DEV_NAME);
        return kErrorNoFreeInstance;
    }

    OPLK_MEMSET(&ifr, 0, sizeof(ifr));
    ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
    strncpy(ifr.ifr_name, PLK_VETH_NAME, IFNAMSIZ);

    if ((err = ioctl(vethInstance_l.fd, TUNSETIFF, (void*)&ifr)) < 0)
    {
        DEBUG_LVL_VETH_TRACE("Error setting TUN IFF options\n");
        close(vethInstance_l.fd);
        return err;
    }

    // save MAC address of TAP device and Ethernet device to be able to
    // exchange them
    OPLK_MEMCPY(vethInstance_l.macAdrs, aSrcMac_p, 6);
    getMacAdrs(vethInstance_l.tapMacAdrs);

    // start tap receive thread
    vethInstance_l.fStop = FALSE;
    if (pthread_create(&vethInstance_l.threadHandle, NULL, vethRecvThread, (void*)&vethInstance_l) != 0)
        return kErrorNoFreeInstance;

#if (defined(__GLIBC__) && (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 12))
    pthread_setname_np(vethInstance_l.threadHandle, "oplk-veth");
#endif

    // register callback function in DLL
    ret = dllk_regAsyncHandler(receiveFrameCb);

    return ret;
}
//------------------------------------------------------------------------------
static int vethStartXmit(struct sk_buff* pSkb_p, struct net_device* pNetDevice_p)
{
    tOplkError      ret;
    tFrameInfo      frameInfo;

    //transmit function
    struct net_device_stats* pStats = netdev_priv(pNetDevice_p);

    //save time stamp
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0))
    pNetDevice_p->trans_start = jiffies;
#else
    netif_trans_update(pNetDevice_p);
#endif

    frameInfo.frame.pBuffer = (tPlkFrame*)pSkb_p->data;
    frameInfo.frameSize = pSkb_p->len;

    //call send fkt on DLL
    ret = dllkcal_sendAsyncFrame(&frameInfo, kDllAsyncReqPrioGeneric);
    if (ret != kErrorOk)
    {
        DEBUG_LVL_VETH_TRACE("%s(): dllkcal_sendAsyncFrame returned 0x%04X\n", __func__, ret);
        netif_stop_queue(pNetDevice_p);
        goto Exit;
    }
    else
    {
        DEBUG_LVL_VETH_TRACE("%s(): frame passed to DLL\n", __func__);
        dev_kfree_skb(pSkb_p);

        //set stats for the device
        pStats->tx_packets++;
        pStats->tx_bytes += frameInfo.frameSize;
    }

Exit:
    return 0;
}
//------------------------------------------------------------------------------
static int veth_open(struct net_device* pNetDevice_p)
{
    tOplkError  ret = kErrorOk;

    //open the device
    //start the interface queue for the network subsystem
    netif_start_queue(pNetDevice_p);

    // register callback function in DLL
    ret = dllk_regAsyncHandler(veth_receiveFrame);

    DEBUG_LVL_VETH_TRACE("veth_open: dllk_regAsyncHandler returned 0x%02X\n", ret);
    return 0;
}
//------------------------------------------------------------------------------
static tOplkError receiveFrameCb(tFrameInfo* pFrameInfo_p,
                                 tEdrvReleaseRxBuffer* pReleaseRxBuffer_p)
{
    UINT    nwrite;

    // replace the MAC address of the POWERLINK Ethernet interface with virtual
    // Ethernet MAC address before forwarding it into the virtual Ethernet interface
    if (OPLK_MEMCMP(pFrameInfo_p->frame.pBuffer->aDstMac, vethInstance_l.macAdrs, ETH_ALEN) == 0)
    {
        OPLK_MEMCPY(pFrameInfo_p->frame.pBuffer->aDstMac, vethInstance_l.tapMacAdrs, ETH_ALEN);
    }

    nwrite = write(vethInstance_l.fd, pFrameInfo_p->frame.pBuffer, pFrameInfo_p->frameSize);
    if (nwrite != pFrameInfo_p->frameSize)
    {
        DEBUG_LVL_VETH_TRACE("Error writing data to virtual Ethernet interface!\n");
    }

    *pReleaseRxBuffer_p = kEdrvReleaseRxBufferImmediately;

    return kErrorOk;
}
//------------------------------------------------------------------------------
static struct net_device_stats* veth_getStats(struct net_device* pNetDevice_p)
{
    DEBUG_LVL_VETH_TRACE("veth_getStats\n");
    return netdev_priv(pNetDevice_p);
}
//------------------------------------------------------------------------------
static struct net_device_stats* vethGetStats(struct net_device* pNetDevice_p)
{
    DEBUG_LVL_VETH_TRACE("%s()\n", __func__);

    return netdev_priv(pNetDevice_p);
}
//------------------------------------------------------------------------------
static void* vethRecvThread(void* pArg_p)
{
    UINT8               buffer[ETH_DATA_LEN];
    UINT                nread;
    tFrameInfo          frameInfo;
    tOplkError          ret = kErrorOk;
    tVethInstance*      pInstance = (tVethInstance*)pArg_p;
    fd_set              readFds;
    int                 result;
    struct timeval      timeout;

    while (!pInstance->fStop)
    {
        timeout.tv_sec = 0;
        timeout.tv_usec = 400000;

        FD_ZERO(&readFds);
        FD_SET(pInstance->fd, &readFds);

        result = select(pInstance->fd + 1, &readFds, NULL, NULL, &timeout);
        switch (result)
        {
            case 0:     // timeout
                //DEBUG_LVL_VETH_TRACE("select timeout\n");
                break;

            case -1:    // error
                DEBUG_LVL_VETH_TRACE("select error: %s\n", strerror(errno));
                break;

            default:    // data from tun/tap ready for read
                nread = read(pInstance->fd, buffer, ETH_DATA_LEN);
                if (nread > 0)
                {
                    DEBUG_LVL_VETH_TRACE("VETH: Read %d bytes from the tap interface\n", nread);
                    DEBUG_LVL_VETH_TRACE("SRC MAC: %02X:%02X:%02x:%02X:%02X:%02x\n",
                                         buffer[6],
                                         buffer[7],
                                         buffer[8],
                                         buffer[9],
                                         buffer[10],
                                         buffer[11]);
                    DEBUG_LVL_VETH_TRACE("DST MAC: %02X:%02X:%02x:%02X:%02X:%02x\n",
                                         buffer[0],
                                         buffer[1],
                                         buffer[2],
                                         buffer[3],
                                         buffer[4],
                                         buffer[5]);
                    // replace src MAC address with MAC address of virtual Ethernet interface
                    OPLK_MEMCPY(&buffer[6], pInstance->macAdrs, ETH_ALEN);

                    frameInfo.frame.pBuffer = (tPlkFrame*)buffer;
                    frameInfo.frameSize = nread;
                    ret = dllkcal_sendAsyncFrame(&frameInfo, kDllAsyncReqPrioGeneric);
                    if (ret != kErrorOk)
                    {
                        DEBUG_LVL_VETH_TRACE("%s(): dllkcal_sendAsyncFrame returned 0x%04X\n", __func__, ret);
                    }
                }
                break;
        }
    }

    pthread_exit(NULL);

    return NULL;
}