uefi: net: Add a driver which wraps the UEFI simple network interface.
Change-Id: I1f28e3a8f4d58d9772d1bd0109a74201201a3683
diff --git a/src/drivers/Kconfig b/src/drivers/Kconfig
index 5016147..c66ac7d 100644
--- a/src/drivers/Kconfig
+++ b/src/drivers/Kconfig
@@ -26,6 +26,7 @@
source src/drivers/gpio/Kconfig
source src/drivers/keyboard/Kconfig
source src/drivers/layout/Kconfig
+source src/drivers/net/Kconfig
source src/drivers/power/Kconfig
source src/drivers/sound/Kconfig
source src/drivers/storage/Kconfig
diff --git a/src/drivers/net/Kconfig b/src/drivers/net/Kconfig
new file mode 100644
index 0000000..857838e
--- /dev/null
+++ b/src/drivers/net/Kconfig
@@ -0,0 +1,19 @@
+##
+## Copyright 2016 Google Inc. All rights reserved.
+##
+## 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; version 2 of the License.
+##
+## 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 St, Fifth Floor, Boston, MA 02110-1301 USA
+
+config DRIVER_NET_UEFI
+ bool "UEFI network device driver"
+ default n
diff --git a/src/drivers/net/Makefile.inc b/src/drivers/net/Makefile.inc
index 2c65fac..1cfaa0c 100644
--- a/src/drivers/net/Makefile.inc
+++ b/src/drivers/net/Makefile.inc
@@ -19,4 +19,5 @@
net-y += mii.c
net-y += net.c
net-$(CONFIG_USB) += smsc95xx.c
+net-$(CONFIG_DRIVER_NET_UEFI) += uefi.c
net-$(CONFIG_USB) += usb_eth.c
diff --git a/src/drivers/net/uefi.c b/src/drivers/net/uefi.c
new file mode 100644
index 0000000..3dce04c
--- /dev/null
+++ b/src/drivers/net/uefi.c
@@ -0,0 +1,279 @@
+/*
+ * 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);