blob: 3dce04c1992de6ef29fac5cd358206d5997cc3c0 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include "base/container_of.h"
#include "base/die.h"
#include "base/hexdump.h"
#include "base/init_funcs.h"
#include "base/list.h"
#include "base/xalloc.h"
#include "drivers/net/net.h"
#include "uefi/edk/Protocol/SimpleNetwork.h"
#include "uefi/uefi.h"
static EFI_GUID uefi_simple_network_protocol_guid =
EFI_SIMPLE_NETWORK_PROTOCOL_GUID;
typedef struct {
NetDevice dev;
ListNode list_node;
int initialized;
EFI_SIMPLE_NETWORK_PROTOCOL *snp;
uip_eth_addr mac;
} UefiNetDevice;
ListNode uefinet_devices;
static int uefinet_initialize(UefiNetDevice *dev)
{
if (dev->initialized)
return 0;
EFI_SIMPLE_NETWORK_PROTOCOL *snp = dev->snp;
EFI_SIMPLE_NETWORK_MODE *mode = snp->Mode;
EFI_STATUS status;
if (mode->State == EfiSimpleNetworkStopped) {
status = snp->Start(snp);
if (status != EFI_SUCCESS) {
printf("Failed to start network device.\n");
return 1;
}
}
if (mode->State == EfiSimpleNetworkStarted) {
status = snp->Initialize(snp, 0, 0);
if (status != EFI_SUCCESS) {
printf("Failed to initialize network device.\n");
return 1;
}
}
if (mode->State != EfiSimpleNetworkInitialized) {
printf("Device was not initialized as expected.\n");
return 1;
}
if (mode->HwAddressSize != sizeof(dev->mac)) {
printf("Unexpected MAC size %d.\n",
mode->HwAddressSize);
return 1;
}
memcpy(&dev->mac, &mode->CurrentAddress, sizeof(dev->mac));
dev->initialized = 1;
return 0;
}
static int uefinet_ready(NetDevice *net_dev, int *ready)
{
UefiNetDevice *dev = container_of(net_dev, UefiNetDevice, dev);
EFI_SIMPLE_NETWORK_PROTOCOL *snp = dev->snp;
if (uefinet_initialize(dev))
return 1;
UINT32 intStatus;
VOID *txbufPtr;
EFI_STATUS status = snp->GetStatus(snp, &intStatus, &txbufPtr);
if (status != EFI_SUCCESS) {
printf("Failed to get network device status.\n");
return 1;
}
if (!snp->Mode->MediaPresentSupported) {
printf("Device doesn't support the MediaPresent field.\n");
return 1;
}
*ready = snp->Mode->MediaPresent;
return 0;
}
static int uefinet_recv(NetDevice *net_dev, void *buf, uint16_t *len,
int maxlen)
{
UefiNetDevice *dev = container_of(net_dev, UefiNetDevice, dev);
EFI_SIMPLE_NETWORK_PROTOCOL *snp = dev->snp;
if (uefinet_initialize(dev))
return 1;
// Try to receive until there's some data to return.
UINTN buf_size = maxlen;
EFI_STATUS status = snp->Receive(snp, NULL, &buf_size, buf,
NULL, NULL, NULL);
if (status == EFI_NOT_READY) {
*len = 0;
return 0;
} else {
*len = buf_size;
}
if (status == EFI_BUFFER_TOO_SMALL) {
printf("Packet receive buffer too small.\n");
return 1;
} else if (status != EFI_SUCCESS) {
printf("Failed to receive packets from the device.\n");
return 1;
}
return 0;
}
static int uefinet_send(NetDevice *net_dev, void *buf, uint16_t len)
{
UefiNetDevice *dev = container_of(net_dev, UefiNetDevice, dev);
EFI_SIMPLE_NETWORK_PROTOCOL *snp = dev->snp;
if (uefinet_initialize(dev))
return 1;
EFI_STATUS status;
// Try to send until the transmit request is accepted.
do {
status = snp->Transmit(snp, 0, len, buf, NULL, NULL, NULL);
} while (status == EFI_NOT_READY);
UINT32 intStatus;
VOID *txbufPtr;
do {
status = snp->GetStatus(snp, &intStatus, &txbufPtr);
if (status != EFI_SUCCESS) {
printf("Failed to get network device status.\n");
return 1;
}
} while (txbufPtr != buf);
if (status != EFI_SUCCESS) {
printf("Failed to transmit packets with the device.\n");
return 1;
}
return 0;
}
static const uip_eth_addr *uefinet_get_mac(NetDevice *net_dev)
{
UefiNetDevice *dev = container_of(net_dev, UefiNetDevice, dev);
if (uefinet_initialize(dev))
return NULL;
return &dev->mac;
}
static void uefi_net_create(EFI_SIMPLE_NETWORK_PROTOCOL *snp)
{
UefiNetDevice *net = xzalloc(sizeof(*net));
net->dev.ready = &uefinet_ready;
net->dev.recv = &uefinet_recv;
net->dev.send = &uefinet_send;
net->dev.get_mac = &uefinet_get_mac;
net->snp = snp;
list_insert_after(&net->list_node, &uefinet_devices);
net_add_device(&net->dev);
}
static void uefi_net_destroy(UefiNetDevice *net)
{
net_remove_device(&net->dev);
list_remove(&net->list_node);
free(net);
}
static void uefi_net_poll(NetPoller *poller)
{
// Destroy all the existing devices and recreate them. This is the
// easiest way to ensure stale devices go away and new ones are found.
UefiNetDevice *prev_dev = NULL;
UefiNetDevice *net_dev;
list_for_each(net_dev, uefinet_devices, list_node) {
if (prev_dev)
uefi_net_destroy(prev_dev);
prev_dev = net_dev;
}
if (prev_dev)
uefi_net_destroy(prev_dev);
EFI_SYSTEM_TABLE *sys = uefi_system_table_ptr();
assert(sys);
EFI_BOOT_SERVICES *bs = sys->BootServices;
UINTN buf_size = 0;
EFI_HANDLE dummy_handle;
EFI_STATUS status = bs->LocateHandle(
ByProtocol, &uefi_simple_network_protocol_guid, NULL,
&buf_size, &dummy_handle);
// If no network devices were found, there's nothing more to do.
if (status == EFI_NOT_FOUND)
return;
die_if(status != EFI_BUFFER_TOO_SMALL,
"Error retrieving network protocol handles.\n");
EFI_HANDLE *handles = xmalloc(buf_size);
status = bs->LocateHandle(
ByProtocol, &uefi_simple_network_protocol_guid, NULL,
&buf_size, handles);
die_if(status != EFI_SUCCESS,
"Failed to retrieve network protocol handles.\n");
int handle_count = buf_size / sizeof(dummy_handle);
for (int i = 0; i < handle_count; i++) {
EFI_SIMPLE_NETWORK_PROTOCOL *snp;
status = bs->HandleProtocol(handles[i],
&uefi_simple_network_protocol_guid, (void **)&snp);
die_if(status != EFI_SUCCESS,
"Failed to get network protocol.\n");
uefi_net_create(snp);
}
}
static NetPoller uefinet_poller = {
.poll = &uefi_net_poll,
};
static int uefinet_install_poller(void)
{
list_insert_after(&uefinet_poller.list_node, &net_pollers);
return 0;
}
INIT_FUNC(uefinet_install_poller);