/*
 * Copyright 2013-2014 Intel Corporation - All Rights Reserved
 */

#include "efi.h"
#include "net.h"
#include "fs/pxe/pxe.h"

extern EFI_GUID Tcp4ServiceBindingProtocol;
extern EFI_GUID Tcp4Protocol;


extern struct efi_binding *efi_create_binding(EFI_GUID *, EFI_GUID *);
extern void efi_destroy_binding(struct efi_binding *, EFI_GUID *);
int core_tcp_open(struct pxe_pvt_inode *socket)
{
    struct efi_binding *b;

    b = efi_create_binding(&Tcp4ServiceBindingProtocol, &Tcp4Protocol);
    if (!b)
	return -1;

    socket->net.efi.binding = b;

    return 0;
}

static EFIAPI void null_cb(EFI_EVENT ev, void *context)
{
    EFI_TCP4_COMPLETION_TOKEN *token = context;

    (void)ev;

    uefi_call_wrapper(BS->CloseEvent, 1, token->Event);
}

static int volatile cb_status = -1;
static EFIAPI void tcp_cb(EFI_EVENT ev, void *context)
{
    EFI_TCP4_COMPLETION_TOKEN *token = context;

    (void)ev;

    if (token->Status == EFI_SUCCESS)
	cb_status = 0;
    else
	cb_status = 1;
}

int core_tcp_connect(struct pxe_pvt_inode *socket, uint32_t ip, uint16_t port)
{
    EFI_TCP4_CONNECTION_TOKEN token;
    EFI_TCP4_ACCESS_POINT *ap;
    EFI_TCP4_CONFIG_DATA tdata;
    struct efi_binding *b = socket->net.efi.binding;
    EFI_STATUS status;
    EFI_TCP4 *tcp = (EFI_TCP4 *)b->this;
    int rv = -1;
    int unmapped = 1;
    jiffies_t start, last, cur;

    memset(&tdata, 0, sizeof(tdata));

    ap = &tdata.AccessPoint;
    ap->UseDefaultAddress = TRUE;
    memcpy(&ap->RemoteAddress, &ip, sizeof(ip));
    ap->RemotePort = port;
    ap->ActiveFlag = TRUE; /* Initiate active open */

    tdata.TimeToLive = 64;

    last = start = jiffies();
    while (unmapped){
	status = uefi_call_wrapper(tcp->Configure, 2, tcp, &tdata);
	if (status == EFI_NO_MAPPING) {
	    cur = jiffies();
	    if ( (cur - last) >= EFI_NOMAP_PRINT_DELAY ) {
		last = cur;
		Print(L"core_tcp_connect: stalling on configure with no mapping\n");
	    } else if ( (cur - start) > EFI_NOMAP_PRINT_DELAY * EFI_NOMAP_PRINT_COUNT) {
		Print(L"core_tcp_connect: aborting on no mapping\n");
		unmapped = 0;
	    }
	} else {
	    if (status != EFI_SUCCESS)
		Print(L"core_tcp_connect: tcp->Configure() unsuccessful (%d)", status);
	    unmapped = 0;
	}
    }
    if (status != EFI_SUCCESS)
	return -1;

    status = efi_setup_event(&token.CompletionToken.Event,
			    (EFI_EVENT_NOTIFY)tcp_cb, &token.CompletionToken);
    if (status != EFI_SUCCESS)
	return -1;

    status = uefi_call_wrapper(tcp->Connect, 2, tcp, &token);
    if (status != EFI_SUCCESS) {
	Print(L"Failed to connect: %d\n", status);
	goto out;
    }

    while (cb_status == -1)
	uefi_call_wrapper(tcp->Poll, 1, tcp);

    if (cb_status == 0)
	rv = 0;

    /* Reset */
    cb_status = -1;

out:
    uefi_call_wrapper(BS->CloseEvent, 1, token.CompletionToken.Event);
    return rv;
}

bool core_tcp_is_connected(struct pxe_pvt_inode *socket)
{
    if (socket->net.efi.binding)
	return true;

    return false;
}

int core_tcp_write(struct pxe_pvt_inode *socket, const void *data,
		   size_t len, bool copy)
{
    EFI_TCP4_TRANSMIT_DATA txdata;
    EFI_TCP4_FRAGMENT_DATA *frag;
    struct efi_binding *b = socket->net.efi.binding;
    EFI_TCP4_IO_TOKEN iotoken;
    EFI_STATUS status;
    EFI_TCP4 *tcp = (EFI_TCP4 *)b->this;
    int rv = -1;

    (void)copy;

    memset(&iotoken, 0, sizeof(iotoken));
    memset(&txdata, 0, sizeof(txdata));

    txdata.DataLength = len;
    txdata.FragmentCount = 1;

    frag = &txdata.FragmentTable[0];
    frag->FragmentLength = len;
    frag->FragmentBuffer = (void *)data;

    iotoken.Packet.TxData = &txdata;

    status = efi_setup_event(&iotoken.CompletionToken.Event,
			     (EFI_EVENT_NOTIFY)tcp_cb, &iotoken.CompletionToken);
    if (status != EFI_SUCCESS)
	return -1;

    status = uefi_call_wrapper(tcp->Transmit, 2, tcp, &iotoken);
    if (status != EFI_SUCCESS) {
	Print(L"tcp transmit failed, %d\n", status);
	goto out;
    }

    while (cb_status == -1)
	uefi_call_wrapper(tcp->Poll, 1, tcp);

    if (cb_status == 0)
	rv = 0;

    /* Reset */
    cb_status = -1;

out:
    uefi_call_wrapper(BS->CloseEvent, 1, iotoken.CompletionToken.Event);
    return rv;
}

void core_tcp_close_file(struct inode *inode)
{
    struct pxe_pvt_inode *socket = PVT(inode);
    struct efi_binding *b = socket->net.efi.binding;
    EFI_TCP4_CLOSE_TOKEN token;
    EFI_STATUS status;
    EFI_TCP4 *tcp = (EFI_TCP4 *)b->this;

  if (!socket->tftp_goteof) {
	memset(&token, 0, sizeof(token));

	status = efi_setup_event(&token.CompletionToken.Event,
				 (EFI_EVENT_NOTIFY)null_cb,
				 &token.CompletionToken);
	if (status != EFI_SUCCESS)
	    return;

	status = uefi_call_wrapper(tcp->Close, 2, tcp, &token);
	if (status != EFI_SUCCESS)
	    Print(L"tcp close failed: %d\n", status);
    }

    efi_destroy_binding(b, &Tcp4ServiceBindingProtocol);
    socket->net.efi.binding = NULL;
}

static char databuf[8192];

void core_tcp_fill_buffer(struct inode *inode)
{
    struct pxe_pvt_inode *socket = PVT(inode);
    struct efi_binding *b = socket->net.efi.binding;
    EFI_TCP4_IO_TOKEN iotoken;
    EFI_TCP4_RECEIVE_DATA rxdata;
    EFI_TCP4_FRAGMENT_DATA *frag;
    EFI_STATUS status;
    EFI_TCP4 *tcp = (EFI_TCP4 *)b->this;
    void *data;
    size_t len;

    memset(&iotoken, 0, sizeof(iotoken));
    memset(&rxdata, 0, sizeof(rxdata));

    status = efi_setup_event(&iotoken.CompletionToken.Event,
		      (EFI_EVENT_NOTIFY)tcp_cb, &iotoken.CompletionToken);
    if (status != EFI_SUCCESS)
	return;

    iotoken.Packet.RxData = &rxdata;
    rxdata.FragmentCount = 1;
    rxdata.DataLength = sizeof(databuf);
    frag = &rxdata.FragmentTable[0];
    frag->FragmentBuffer = databuf;
    frag->FragmentLength = sizeof(databuf);

    status = uefi_call_wrapper(tcp->Receive, 2, tcp, &iotoken);
    if (status == EFI_CONNECTION_FIN) {
	socket->tftp_goteof = 1;
	if (inode->size == (uint64_t)-1)
	    inode->size = socket->tftp_filepos;
	socket->ops->close(inode);
	goto out;
    }

    while (cb_status == -1)
	uefi_call_wrapper(tcp->Poll, 1, tcp);

    /* Reset */
    cb_status = -1;

    len = frag->FragmentLength;
    memcpy(databuf, frag->FragmentBuffer, len);
    data = databuf;

    socket->tftp_dataptr = data;
    socket->tftp_filepos += len;
    socket->tftp_bytesleft = len;

out:
    uefi_call_wrapper(BS->CloseEvent, 1, iotoken.CompletionToken.Event);
}
