418 lines
9.5 KiB
C
418 lines
9.5 KiB
C
/*
|
|
* 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);
|
|
}
|