/*
 * ReactOS Realtek 8139 Driver
 *
 * Copyright (C) 2013 Cameron Gutman
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#include "nic.h"

#define NDEBUG
#include <debug.h>

static ULONG SupportedOidList[] =
{
    OID_GEN_SUPPORTED_LIST,
    OID_GEN_HARDWARE_STATUS,
    OID_GEN_MEDIA_SUPPORTED,
    OID_GEN_MEDIA_IN_USE,
    OID_GEN_MAXIMUM_LOOKAHEAD,
    OID_GEN_MAXIMUM_FRAME_SIZE,
    OID_GEN_LINK_SPEED,
    OID_GEN_TRANSMIT_BUFFER_SPACE,
    OID_GEN_RECEIVE_BUFFER_SPACE,
    OID_GEN_RECEIVE_BLOCK_SIZE,
    OID_GEN_TRANSMIT_BLOCK_SIZE,
    OID_GEN_VENDOR_ID,
    OID_GEN_VENDOR_DESCRIPTION,
    OID_GEN_VENDOR_DRIVER_VERSION,
    OID_GEN_CURRENT_PACKET_FILTER,
    OID_GEN_CURRENT_LOOKAHEAD,
    OID_GEN_DRIVER_VERSION,
    OID_GEN_MAXIMUM_TOTAL_SIZE,
    OID_GEN_PROTOCOL_OPTIONS,
    OID_GEN_MAC_OPTIONS,
    OID_GEN_MEDIA_CONNECT_STATUS,
    OID_GEN_MAXIMUM_SEND_PACKETS,
    OID_GEN_XMIT_OK,
    OID_GEN_RCV_OK,
    OID_GEN_XMIT_ERROR,
    OID_GEN_RCV_ERROR,
    OID_GEN_RCV_NO_BUFFER,
    OID_GEN_RCV_CRC_ERROR,
    OID_802_3_PERMANENT_ADDRESS,
    OID_802_3_CURRENT_ADDRESS,
    OID_802_3_MULTICAST_LIST,
    OID_802_3_MAXIMUM_LIST_SIZE,
    OID_802_3_MAC_OPTIONS,
    OID_802_3_RCV_ERROR_ALIGNMENT,
    OID_802_3_XMIT_ONE_COLLISION,
    OID_802_3_XMIT_MORE_COLLISIONS
};

NDIS_STATUS
NTAPI
MiniportQueryInformation (
    IN NDIS_HANDLE MiniportAdapterContext,
    IN NDIS_OID Oid,
    IN PVOID InformationBuffer,
    IN ULONG InformationBufferLength,
    OUT PULONG BytesWritten,
    OUT PULONG BytesNeeded
    )
{
    PRTL_ADAPTER adapter = (PRTL_ADAPTER)MiniportAdapterContext;
    ULONG genericUlong;
    ULONG copyLength;
    PVOID copySource;
    NDIS_STATUS status;
    
    status = NDIS_STATUS_SUCCESS;
    copySource = &genericUlong;
    copyLength = sizeof(ULONG);

    NdisAcquireSpinLock(&adapter->Lock);
    
    switch (Oid)
    {
        case OID_GEN_SUPPORTED_LIST:
            copySource = (PVOID)&SupportedOidList;
            copyLength = sizeof(SupportedOidList);
            break;
            
        case OID_GEN_CURRENT_PACKET_FILTER:
            genericUlong = adapter->PacketFilter;
            break;
            
        case OID_GEN_HARDWARE_STATUS:
            genericUlong = (ULONG)NdisHardwareStatusReady; //FIXME
            break;
            
        case OID_GEN_MEDIA_SUPPORTED:
        case OID_GEN_MEDIA_IN_USE:
        {
            static const NDIS_MEDIUM medium = NdisMedium802_3;
            copySource = (PVOID)&medium;
            copyLength = sizeof(medium);
            break;
        }

        case OID_GEN_RECEIVE_BLOCK_SIZE:
        case OID_GEN_TRANSMIT_BLOCK_SIZE:
        case OID_GEN_CURRENT_LOOKAHEAD:
        case OID_GEN_MAXIMUM_LOOKAHEAD:
        case OID_GEN_MAXIMUM_FRAME_SIZE:
            genericUlong = MAXIMUM_FRAME_SIZE - sizeof(ETH_HEADER);
            break;

        case OID_GEN_LINK_SPEED:
            genericUlong = adapter->LinkSpeedMbps * 1000;
            break;

        case OID_GEN_TRANSMIT_BUFFER_SPACE:
            genericUlong = MAXIMUM_FRAME_SIZE;
            break;

        case OID_GEN_RECEIVE_BUFFER_SPACE:
            genericUlong = RECEIVE_BUFFER_SIZE;
            break;
            
        case OID_GEN_VENDOR_ID:
            //
            // The 3 bytes of the MAC address is the vendor ID
            //
            genericUlong = 0;
            genericUlong |= (adapter->PermanentMacAddress[0] << 16);
            genericUlong |= (adapter->PermanentMacAddress[1] << 8);
            genericUlong |= (adapter->PermanentMacAddress[2] & 0xFF);
            break;
            
        case OID_GEN_VENDOR_DESCRIPTION:
        {
            static UCHAR vendorDesc[] = "ReactOS Team";
            copySource = vendorDesc;
            copyLength = sizeof(vendorDesc);
            break;
        }
            
        case OID_GEN_VENDOR_DRIVER_VERSION:
            genericUlong = DRIVER_VERSION;
            break;
            
        case OID_GEN_DRIVER_VERSION:
        {
            static const USHORT driverVersion =
                 (NDIS_MINIPORT_MAJOR_VERSION << 8) + NDIS_MINIPORT_MINOR_VERSION;
            copySource = (PVOID)&driverVersion;
            copyLength = sizeof(driverVersion);
            break;
        }
            
        case OID_GEN_MAXIMUM_TOTAL_SIZE:
            genericUlong = MAXIMUM_FRAME_SIZE;
            break;
            
        case OID_GEN_PROTOCOL_OPTIONS:
            NDIS_DbgPrint(MIN_TRACE, ("OID_GEN_PROTOCOL_OPTIONS is unimplemented\n"));
            status = NDIS_STATUS_NOT_SUPPORTED;
            break;
            
        case OID_GEN_MAC_OPTIONS:
            genericUlong = NDIS_MAC_OPTION_RECEIVE_SERIALIZED |
                           NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA |
                           NDIS_MAC_OPTION_TRANSFERS_NOT_PEND |
                           NDIS_MAC_OPTION_NO_LOOPBACK;
            break;
            
        case OID_GEN_MEDIA_CONNECT_STATUS:
            genericUlong = adapter->MediaState;
            break;
            
        case OID_GEN_MAXIMUM_SEND_PACKETS:
            genericUlong = 1;
            break;
            
        case OID_802_3_CURRENT_ADDRESS:
            copySource = adapter->CurrentMacAddress;
            copyLength = IEEE_802_ADDR_LENGTH;
            break;
            
        case OID_802_3_PERMANENT_ADDRESS:
            copySource = adapter->PermanentMacAddress;
            copyLength = IEEE_802_ADDR_LENGTH;
            break;
            
        case OID_802_3_MAXIMUM_LIST_SIZE:
            genericUlong = MAXIMUM_MULTICAST_ADDRESSES;
            break;
            
        case OID_GEN_XMIT_OK:
            genericUlong = adapter->TransmitOk;
            break;

        case OID_GEN_RCV_OK:
            genericUlong = adapter->ReceiveOk;
            break;

        case OID_GEN_XMIT_ERROR:
            genericUlong = adapter->TransmitError;
            break;

        case OID_GEN_RCV_ERROR:
            genericUlong = adapter->ReceiveError;
            break;

        case OID_GEN_RCV_NO_BUFFER:
            genericUlong = adapter->ReceiveNoBufferSpace;
            break;

        case OID_GEN_RCV_CRC_ERROR:
            genericUlong = adapter->ReceiveCrcError;
            break;

        case OID_802_3_RCV_ERROR_ALIGNMENT:
            genericUlong = adapter->ReceiveAlignmentError;
            break;

        case OID_802_3_XMIT_ONE_COLLISION:
            genericUlong = adapter->TransmitOneCollision;
            break;

        case OID_802_3_XMIT_MORE_COLLISIONS:
            genericUlong = adapter->TransmitMoreCollisions;
            break;
            
        default:
            NDIS_DbgPrint(MIN_TRACE, ("Unknown OID\n"));
            status = NDIS_STATUS_NOT_SUPPORTED;
            break;
    }
    
    if (status == NDIS_STATUS_SUCCESS)
    {
        if (copyLength > InformationBufferLength)
        {
            *BytesNeeded = copyLength;
            *BytesWritten = 0;
            status = NDIS_STATUS_INVALID_LENGTH;
        }
        else
        {
            NdisMoveMemory(InformationBuffer, copySource, copyLength);
            *BytesWritten = copyLength;
            *BytesNeeded = copyLength;
        }
    }
    else
    {
        *BytesWritten = 0;
        *BytesNeeded = 0;
    }
    
    NdisReleaseSpinLock(&adapter->Lock);

    NDIS_DbgPrint(MAX_TRACE, ("Query OID 0x%x: Completed with status 0x%x (%d, %d)\n",
                              Oid, status, *BytesWritten, *BytesNeeded));

    return status;
}

NDIS_STATUS
NTAPI
MiniportSetInformation (
    IN NDIS_HANDLE MiniportAdapterContext,
    IN NDIS_OID Oid,
    IN PVOID InformationBuffer,
    IN ULONG InformationBufferLength,
    OUT PULONG BytesRead,
    OUT PULONG BytesNeeded
    )
{
    PRTL_ADAPTER adapter = (PRTL_ADAPTER)MiniportAdapterContext;
    ULONG genericUlong;
    NDIS_STATUS status;
    
    status = NDIS_STATUS_SUCCESS;
    
    NdisAcquireSpinLock(&adapter->Lock);

    switch (Oid)
    {
        case OID_GEN_CURRENT_PACKET_FILTER:
            if (InformationBufferLength < sizeof(ULONG))
            {
                *BytesRead = 0;
                *BytesNeeded = sizeof(ULONG);
                status = NDIS_STATUS_INVALID_LENGTH;
                break;
            }
            
            NdisMoveMemory(&genericUlong, InformationBuffer, sizeof(ULONG));
            
            if (genericUlong & 
                (NDIS_PACKET_TYPE_ALL_FUNCTIONAL |
                 NDIS_PACKET_TYPE_FUNCTIONAL |
                 NDIS_PACKET_TYPE_GROUP |
                 NDIS_PACKET_TYPE_MAC_FRAME |
                 NDIS_PACKET_TYPE_SMT |
                 NDIS_PACKET_TYPE_SOURCE_ROUTING))
            {
                *BytesRead = sizeof(ULONG);
                *BytesNeeded = sizeof(ULONG);
                status = NDIS_STATUS_NOT_SUPPORTED;
                break;
            }
            
            adapter->PacketFilter = genericUlong;
            
            status = NICApplyPacketFilter(adapter);
            if (status != NDIS_STATUS_SUCCESS)
            {
                NDIS_DbgPrint(MIN_TRACE, ("Failed to apply new packet filter\n"));
                break;
            }
            
            break;
            
        case OID_GEN_CURRENT_LOOKAHEAD:
            if (InformationBufferLength < sizeof(ULONG))
            {
                *BytesRead = 0;
                *BytesNeeded = sizeof(ULONG);
                status = NDIS_STATUS_INVALID_LENGTH;
                break;
            }
            
            NdisMoveMemory(&genericUlong, InformationBuffer, sizeof(ULONG));

            if (genericUlong > MAXIMUM_FRAME_SIZE - sizeof(ETH_HEADER))
            {
                status = NDIS_STATUS_INVALID_DATA;
            }
            else
            {
                // Ignore this...
            }
            
            break;
            
        case OID_802_3_MULTICAST_LIST:
            if (InformationBufferLength % IEEE_802_ADDR_LENGTH)
            {
                *BytesRead = 0;
                *BytesNeeded = InformationBufferLength + (InformationBufferLength % IEEE_802_ADDR_LENGTH);
                status = NDIS_STATUS_INVALID_LENGTH;
                break;
            }
            
            if (InformationBufferLength / 6 > MAXIMUM_MULTICAST_ADDRESSES)
            {
                *BytesNeeded = MAXIMUM_MULTICAST_ADDRESSES * IEEE_802_ADDR_LENGTH;
                *BytesRead = 0;
                status = NDIS_STATUS_INVALID_LENGTH;
                break;
            }
            
            NdisMoveMemory(adapter->MulticastList, InformationBuffer, InformationBufferLength);
            
            // FIXME: Write to device
            
            break;
            
        default:
            NDIS_DbgPrint(MIN_TRACE, ("Unknown OID\n"));
            status = NDIS_STATUS_NOT_SUPPORTED;
            *BytesRead = 0;
            *BytesNeeded = 0;
            break;
    }
    
    if (status == NDIS_STATUS_SUCCESS)
    {
        *BytesRead = InformationBufferLength;
        *BytesNeeded = 0;
    }
    
    NdisReleaseSpinLock(&adapter->Lock);

    return status;
}
