blob: 7c1d09e20b2d5931a12b7c4223d845b5c7d9426c [file] [log] [blame]
/*
* Copyright 2013-2014 Intel Corporation - All Rights Reserved
*/
#include <string.h>
#include <minmax.h>
#include "efi.h"
#include "net.h"
#include "fs/pxe/pxe.h"
extern EFI_GUID Udp4ServiceBindingProtocol, Udp4Protocol;
/*
* This UDP binding is configured to operate in promiscuous mode. It is
* only used for reading packets. It has no associated state unlike
* socket->net.efi.binding, which has a remote IP address and port
* number.
*/
static struct efi_binding *udp_reader;
/**
* Try to configure this UDP socket
*
* @param:udp, the EFI_UDP4 socket to configure
* @param:udata, the EFI_UDP4_CONFIG_DATA to use
* @param:f, the name of the function as a wide string.
*
* @out: status as EFI_STATUS
*/
EFI_STATUS core_udp_configure(EFI_UDP4 *udp, EFI_UDP4_CONFIG_DATA *udata,
short unsigned int *f)
{
EFI_STATUS status;
int unmapped = 1;
jiffies_t start, last, cur;
last = start = jiffies();
while (unmapped){
status = uefi_call_wrapper(udp->Configure, 2, udp, udata);
if (status != EFI_NO_MAPPING)
unmapped = 0;
else {
cur = jiffies();
if ( (cur - last) >= EFI_NOMAP_PRINT_DELAY ) {
last = cur;
Print(L"%s: stalling on configure with no mapping\n", f);
} else if ( (cur - start) > EFI_NOMAP_PRINT_DELAY * EFI_NOMAP_PRINT_COUNT) {
Print(L"%s: aborting on no mapping\n", f);
unmapped = 0;
}
}
}
return status;
}
/**
* Open a socket
*
* @param:socket, the socket to open
*
* @out: error code, 0 on success, -1 on failure
*/
int core_udp_open(struct pxe_pvt_inode *socket)
{
EFI_UDP4_CONFIG_DATA udata;
struct efi_binding *b;
EFI_STATUS status;
EFI_UDP4 *udp;
(void)socket;
udp_reader = efi_create_binding(&Udp4ServiceBindingProtocol, &Udp4Protocol);
if (!udp_reader)
return -1;
b = efi_create_binding(&Udp4ServiceBindingProtocol, &Udp4Protocol);
if (!b)
goto bail;
udp = (EFI_UDP4 *)udp_reader->this;
memset(&udata, 0, sizeof(udata));
status = core_udp_configure(udp, &udata, L"core_udp_open");
if (status != EFI_SUCCESS)
goto bail;
socket->net.efi.binding = b;
/*
* Save the random local port number that the UDPv4 Protocol
* Driver picked for us. The TFTP protocol uses the local port
* number as the TID.
*/
status = uefi_call_wrapper(udp->GetModeData, 5, udp,
&udata, NULL, NULL, NULL);
if (status != EFI_SUCCESS)
Print(L"Failed to get UDP mode data: %d\n", status);
else
socket->net.efi.localport = udata.StationPort;
return 0;
bail:
if (b)
efi_destroy_binding(b, &Udp4ServiceBindingProtocol);
efi_destroy_binding(udp_reader, &Udp4ServiceBindingProtocol);
udp_reader = NULL;
return -1;
}
/**
* Close a socket
*
* @param:socket, the socket to open
*/
void core_udp_close(struct pxe_pvt_inode *socket)
{
efi_destroy_binding(udp_reader, &Udp4ServiceBindingProtocol);
udp_reader = NULL;
if (!socket->net.efi.binding)
return;
efi_destroy_binding(socket->net.efi.binding, &Udp4ServiceBindingProtocol);
socket->net.efi.binding = NULL;
}
/**
* Establish a connection on an open socket
*
* @param:socket, the open socket
* @param:ip, the ip address
* @param:port, the port number, host-byte order
*/
void core_udp_connect(struct pxe_pvt_inode *socket, uint32_t ip,
uint16_t port)
{
EFI_UDP4_CONFIG_DATA udata;
EFI_STATUS status;
EFI_UDP4 *udp;
udp = (EFI_UDP4 *)socket->net.efi.binding->this;
memset(&udata, 0, sizeof(udata));
/* Re-use the existing local port number */
udata.StationPort = socket->net.efi.localport;
udata.UseDefaultAddress = TRUE;
memcpy(&udata.RemoteAddress, &ip, sizeof(ip));
udata.RemotePort = port;
udata.AcceptPromiscuous = TRUE;
udata.TimeToLive = 64;
status = core_udp_configure(udp, &udata, L"core_udp_connect");
if (status != EFI_SUCCESS) {
Print(L"Failed to configure UDP: %d\n", status);
return;
}
}
/**
* Tear down a connection on an open socket
*
* @param:socket, the open socket
*/
void core_udp_disconnect(struct pxe_pvt_inode *socket)
{
EFI_STATUS status;
EFI_UDP4 *udp;
udp = (EFI_UDP4 *)socket->net.efi.binding->this;
/* Reset */
status = uefi_call_wrapper(udp->Configure, 2, udp, NULL);
if (status != EFI_SUCCESS)
Print(L"Failed to reset UDP: %d\n", status);
}
static int volatile cb_status = -1;
static EFIAPI void udp4_cb(EFI_EVENT event, void *context)
{
(void)event;
EFI_UDP4_COMPLETION_TOKEN *token = context;
if (token->Status == EFI_SUCCESS)
cb_status = 0;
else
cb_status = 1;
}
/**
* Read data from the network stack
*
* @param:socket, the open socket
* @param:buf, location of buffer to store data
* @param:buf_len, size of buffer
* @out: src_ip, ip address of the data source
* @out: src_port, port number of the data source, host-byte order
*/
int core_udp_recv(struct pxe_pvt_inode *socket, void *buf, uint16_t *buf_len,
uint32_t *src_ip, uint16_t *src_port)
{
EFI_UDP4_COMPLETION_TOKEN token;
EFI_UDP4_FRAGMENT_DATA *frag;
EFI_UDP4_RECEIVE_DATA *rxdata;
struct efi_binding *b;
EFI_STATUS status;
EFI_UDP4 *udp;
size_t size;
int rv = -1;
jiffies_t start;
(void)socket;
b = udp_reader;
udp = (EFI_UDP4 *)b->this;
memset(&token, 0, sizeof(token));
status = efi_setup_event(&token.Event, (EFI_EVENT_NOTIFY)udp4_cb,
&token);
if (status != EFI_SUCCESS)
return -1;
status = uefi_call_wrapper(udp->Receive, 2, udp, &token);
if (status != EFI_SUCCESS)
goto bail;
start = jiffies();
while (cb_status == -1) {
/* 15ms receive timeout... */
if (jiffies() - start >= 15) {
if (jiffies() - start >= 30)
dprintf("Failed to cancel UDP\n");
uefi_call_wrapper(udp->Cancel, 2, udp, &token);
dprintf("core_udp_recv: timed out\n");
}
uefi_call_wrapper(udp->Poll, 1, udp);
}
if (cb_status == 0)
rv = 0;
/* Reset */
cb_status = -1;
if (rv)
goto bail;
rxdata = token.Packet.RxData;
frag = &rxdata->FragmentTable[0];
size = min(frag->FragmentLength, *buf_len);
memcpy(buf, frag->FragmentBuffer, size);
*buf_len = size;
memcpy(src_port, &rxdata->UdpSession.SourcePort, sizeof(*src_port));
memcpy(src_ip, &rxdata->UdpSession.SourceAddress, sizeof(*src_ip));
uefi_call_wrapper(BS->SignalEvent, 1, rxdata->RecycleSignal);
bail:
uefi_call_wrapper(BS->CloseEvent, 1, token.Event);
return rv;
}
/**
* Send a UDP packet.
*
* @param:socket, the open socket
* @param:data, data buffer to send
* @param:len, size of data bufer
*/
void core_udp_send(struct pxe_pvt_inode *socket, const void *data, size_t len)
{
EFI_UDP4_COMPLETION_TOKEN *token;
EFI_UDP4_TRANSMIT_DATA *txdata;
EFI_UDP4_FRAGMENT_DATA *frag;
struct efi_binding *b = socket->net.efi.binding;
EFI_STATUS status;
EFI_UDP4 *udp = (EFI_UDP4 *)b->this;
token = zalloc(sizeof(*token));
if (!token)
return;
txdata = zalloc(sizeof(*txdata));
if (!txdata) {
free(token);
return;
}
status = efi_setup_event(&token->Event, (EFI_EVENT_NOTIFY)udp4_cb,
token);
if (status != EFI_SUCCESS)
goto bail;
txdata->DataLength = len;
txdata->FragmentCount = 1;
frag = &txdata->FragmentTable[0];
frag->FragmentLength = len;
frag->FragmentBuffer = (void *)data;
token->Packet.TxData = txdata;
status = uefi_call_wrapper(udp->Transmit, 2, udp, token);
if (status != EFI_SUCCESS)
goto close;
while (cb_status == -1)
uefi_call_wrapper(udp->Poll, 1, udp);
/* Reset */
cb_status = -1;
close:
uefi_call_wrapper(BS->CloseEvent, 1, token->Event);
bail:
free(txdata);
free(token);
}
/**
* Send a UDP packet to a destination
*
* @param:socket, the open socket
* @param:data, data buffer to send
* @param:len, size of data bufer
* @param:ip, the ip address
* @param:port, the port number, host-byte order
*/
void core_udp_sendto(struct pxe_pvt_inode *socket, const void *data,
size_t len, uint32_t ip, uint16_t port)
{
EFI_UDP4_COMPLETION_TOKEN *token;
EFI_UDP4_TRANSMIT_DATA *txdata;
EFI_UDP4_FRAGMENT_DATA *frag;
EFI_UDP4_CONFIG_DATA udata;
EFI_STATUS status;
struct efi_binding *b;
EFI_UDP4 *udp;
(void)socket;
b = efi_create_binding(&Udp4ServiceBindingProtocol, &Udp4Protocol);
if (!b)
return;
udp = (EFI_UDP4 *)b->this;
token = zalloc(sizeof(*token));
if (!token)
goto out;
txdata = zalloc(sizeof(*txdata));
if (!txdata)
goto bail;
memset(&udata, 0, sizeof(udata));
/* Re-use the existing local port number */
udata.StationPort = socket->net.efi.localport;
udata.UseDefaultAddress = TRUE;
memcpy(&udata.RemoteAddress, &ip, sizeof(ip));
udata.RemotePort = port;
udata.AcceptPromiscuous = TRUE;
udata.TimeToLive = 64;
status = core_udp_configure(udp, &udata, L"core_udp_sendto");
if (status != EFI_SUCCESS)
goto bail;
status = efi_setup_event(&token->Event, (EFI_EVENT_NOTIFY)udp4_cb,
token);
if (status != EFI_SUCCESS)
goto bail;
txdata->DataLength = len;
txdata->FragmentCount = 1;
frag = &txdata->FragmentTable[0];
frag->FragmentLength = len;
frag->FragmentBuffer = (void *)data;
token->Packet.TxData = txdata;
status = uefi_call_wrapper(udp->Transmit, 2, udp, token);
if (status != EFI_SUCCESS)
goto close;
while (cb_status == -1)
uefi_call_wrapper(udp->Poll, 1, udp);
/* Reset */
cb_status = -1;
close:
uefi_call_wrapper(BS->CloseEvent, 1, token->Event);
bail:
free(txdata);
free(token);
out:
efi_destroy_binding(b, &Udp4ServiceBindingProtocol);
}