]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
efi_loader: supply EFI network test
authorHeinrich Schuchardt <xypron.glpk@gmx.de>
Thu, 5 Oct 2017 14:36:07 +0000 (16:36 +0200)
committerAlexander Graf <agraf@suse.de>
Mon, 9 Oct 2017 05:00:36 +0000 (07:00 +0200)
This patch provides an EFI application to check the correct function
of the Simple Network Protocol implementation.

It sends a DHCP request and analyzes the DHCP offer.

Different error conditions including a 10s timeout are checked.

A successful execution will look like this:

=> bootefi nettest
Scanning disk ide.blk#0...
Found 1 disks
WARNING: Invalid device tree, expect boot to fail
Network test
DHCP Discover
DHCP reply received from 192.168.76.2 (52:55:c0:a8:4c:02)
as broadcast message.
OK. The test was completed successfully.

Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
Reviewed-by: Simon Glass <sjg@chromium.org>
Signed-off-by: Alexander Graf <agraf@suse.de>
include/efi_selftest.h
lib/efi_selftest/Makefile
lib/efi_selftest/efi_selftest_snp.c [new file with mode: 0644]
lib/efi_selftest/efi_selftest_util.c [new file with mode: 0644]

index 2f0992f06eee65ed62fea3d1b2c2c33cfaab0d46..7ec42a0406bca52da1add4ddcc224c38ffa2b7c5 100644 (file)
@@ -60,6 +60,17 @@ void efi_st_exit_boot_services(void);
 void efi_st_printf(const char *fmt, ...)
                 __attribute__ ((format (__printf__, 1, 2)));
 
+/*
+ * Compare memory.
+ * We cannot use lib/string.c due to different CFLAGS values.
+ *
+ * @buf1:      first buffer
+ * @buf2:      second buffer
+ * @length:    number of bytes to compare
+ * @return:    0 if both buffers contain the same bytes
+ */
+int efi_st_memcmp(const void *buf1, const void *buf2, size_t length);
+
 /*
  * Reads an Unicode character from the input device.
  *
index 30f196093307df0f5b59b93a676d6510e681f1d0..e446046e0220b2329da36ec0004b3a2ea1920dcf 100644 (file)
@@ -15,12 +15,18 @@ CFLAGS_efi_selftest_events.o := $(CFLAGS_EFI)
 CFLAGS_REMOVE_efi_selftest_events.o := $(CFLAGS_NON_EFI)
 CFLAGS_efi_selftest_exitbootservices.o := $(CFLAGS_EFI)
 CFLAGS_REMOVE_efi_selftest_exitbootservices.o := $(CFLAGS_NON_EFI)
+CFLAGS_efi_selftest_snp.o := $(CFLAGS_EFI)
+CFLAGS_REMOVE_efi_selftest_snp.o := $(CFLAGS_NON_EFI)
 CFLAGS_efi_selftest_tpl.o := $(CFLAGS_EFI)
 CFLAGS_REMOVE_efi_selftest_tpl.o := $(CFLAGS_NON_EFI)
+CFLAGS_efi_selftest_util.o := $(CFLAGS_EFI)
+CFLAGS_REMOVE_efi_selftest_util.o := $(CFLAGS_NON_EFI)
 
 obj-$(CONFIG_CMD_BOOTEFI_SELFTEST) += \
 efi_selftest.o \
 efi_selftest_console.o \
 efi_selftest_events.o \
 efi_selftest_exitbootservices.o \
-efi_selftest_tpl.o
+efi_selftest_snp.o \
+efi_selftest_tpl.o \
+efi_selftest_util.o
diff --git a/lib/efi_selftest/efi_selftest_snp.c b/lib/efi_selftest/efi_selftest_snp.c
new file mode 100644 (file)
index 0000000..638be01
--- /dev/null
@@ -0,0 +1,424 @@
+/*
+ * efi_selftest_snp
+ *
+ * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ *
+ * This unit test covers the Simple Network Protocol as well as
+ * the CopyMem and SetMem boottime services.
+ *
+ * A DHCP discover message is sent. The test is successful if a
+ * DHCP reply is received.
+ *
+ * TODO: Once ConnectController and DisconnectController are implemented
+ *      we should connect our code as controller.
+ */
+
+#include <efi_selftest.h>
+
+/*
+ * MAC address for broadcasts
+ */
+static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+struct dhcp_hdr {
+       u8 op;
+#define BOOTREQUEST 1
+#define BOOTREPLY 2
+       u8 htype;
+# define HWT_ETHER 1
+       u8 hlen;
+# define HWL_ETHER 6
+       u8 hops;
+       u32 xid;
+       u16 secs;
+       u16 flags;
+#define DHCP_FLAGS_UNICAST     0x0000
+#define DHCP_FLAGS_BROADCAST   0x0080
+       u32 ciaddr;
+       u32 yiaddr;
+       u32 siaddr;
+       u32 giaddr;
+       u8 chaddr[16];
+       u8 sname[64];
+       u8 file[128];
+};
+
+/*
+ * Message type option.
+ */
+#define DHCP_MESSAGE_TYPE      0x35
+#define DHCPDISCOVER           1
+#define DHCPOFFER              2
+#define DHCPREQUEST            3
+#define DHCPDECLINE            4
+#define DHCPACK                        5
+#define DHCPNAK                        6
+#define DHCPRELEASE            7
+
+struct dhcp {
+       struct ethernet_hdr eth_hdr;
+       struct ip_udp_hdr ip_udp;
+       struct dhcp_hdr dhcp_hdr;
+       u8 opt[128];
+} __packed;
+
+static struct efi_boot_services *boottime;
+static struct efi_simple_network *net;
+static struct efi_event *timer;
+static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_GUID;
+/* IP packet ID */
+static unsigned int net_ip_id;
+
+/*
+ * Compute the checksum of the IP header. We cover even values of length only.
+ * We cannot use net/checksum.c due to different CFLAGS values.
+ *
+ * @buf:       IP header
+ * @len:       length of header in bytes
+ * @return:    checksum
+ */
+static unsigned int efi_ip_checksum(const void *buf, size_t len)
+{
+       size_t i;
+       u32 sum = 0;
+       const u16 *pos = buf;
+
+       for (i = 0; i < len; i += 2)
+               sum += *pos++;
+
+       sum = (sum >> 16) + (sum & 0xffff);
+       sum += sum >> 16;
+       sum = ~sum & 0xffff;
+
+       return sum;
+}
+
+/*
+ * Transmit a DHCPDISCOVER message.
+ */
+static efi_status_t send_dhcp_discover(void)
+{
+       efi_status_t ret;
+       struct dhcp p = {};
+
+       /*
+        * Fill ethernet header
+        */
+       boottime->copy_mem(p.eth_hdr.et_dest, (void *)BROADCAST_MAC, ARP_HLEN);
+       boottime->copy_mem(p.eth_hdr.et_src, &net->mode->current_address,
+                          ARP_HLEN);
+       p.eth_hdr.et_protlen = htons(PROT_IP);
+       /*
+        * Fill IP header
+        */
+       p.ip_udp.ip_hl_v        = 0x45;
+       p.ip_udp.ip_len         = htons(sizeof(struct dhcp) -
+                                       sizeof(struct ethernet_hdr));
+       p.ip_udp.ip_id          = htons(++net_ip_id);
+       p.ip_udp.ip_off         = htons(IP_FLAGS_DFRAG);
+       p.ip_udp.ip_ttl         = 0xff; /* time to live */
+       p.ip_udp.ip_p           = IPPROTO_UDP;
+       boottime->set_mem(&p.ip_udp.ip_dst, 4, 0xff);
+       p.ip_udp.ip_sum         = efi_ip_checksum(&p.ip_udp, IP_HDR_SIZE);
+
+       /*
+        * Fill UDP header
+        */
+       p.ip_udp.udp_src        = htons(68);
+       p.ip_udp.udp_dst        = htons(67);
+       p.ip_udp.udp_len        = htons(sizeof(struct dhcp) -
+                                       sizeof(struct ethernet_hdr) -
+                                       sizeof(struct ip_hdr));
+       /*
+        * Fill DHCP header
+        */
+       p.dhcp_hdr.op           = BOOTREQUEST;
+       p.dhcp_hdr.htype        = HWT_ETHER;
+       p.dhcp_hdr.hlen         = HWL_ETHER;
+       p.dhcp_hdr.flags        = htons(DHCP_FLAGS_UNICAST);
+       boottime->copy_mem(&p.dhcp_hdr.chaddr,
+                          &net->mode->current_address, ARP_HLEN);
+       /*
+        * Fill options
+        */
+       p.opt[0]        = 0x63; /* DHCP magic cookie */
+       p.opt[1]        = 0x82;
+       p.opt[2]        = 0x53;
+       p.opt[3]        = 0x63;
+       p.opt[4]        = DHCP_MESSAGE_TYPE;
+       p.opt[5]        = 0x01; /* length */
+       p.opt[6]        = DHCPDISCOVER;
+       p.opt[7]        = 0x39; /* maximum message size */
+       p.opt[8]        = 0x02; /* length */
+       p.opt[9]        = 0x02; /* 576 bytes */
+       p.opt[10]       = 0x40;
+       p.opt[11]       = 0xff; /* end of options */
+
+       /*
+        * Transmit DHCPDISCOVER message.
+        */
+       ret = net->transmit(net, 0, sizeof(struct dhcp), &p, NULL, NULL, 0);
+       if (ret != EFI_SUCCESS)
+               efi_st_error("Sending a DHCP request failed\n");
+       else
+               efi_st_printf("DHCP Discover\n");
+       return ret;
+}
+
+/*
+ * Setup unit test.
+ *
+ * Create a 1 s periodic timer.
+ * Start the network driver.
+ *
+ * @handle:    handle of the loaded image
+ * @systable:  system table
+ * @return:    EFI_ST_SUCCESS for success
+ */
+static int setup(const efi_handle_t handle,
+                const struct efi_system_table *systable)
+{
+       efi_status_t ret;
+
+       boottime = systable->boottime;
+
+       /*
+        * Create a timer event.
+        */
+       ret = boottime->create_event(EVT_TIMER, TPL_CALLBACK, NULL, NULL,
+                                    &timer);
+       if (ret != EFI_SUCCESS) {
+               efi_st_error("Failed to create event\n");
+               return EFI_ST_FAILURE;
+       }
+       /*
+        * Set timer period to 1s.
+        */
+       ret = boottime->set_timer(timer, EFI_TIMER_PERIODIC, 10000000);
+       if (ret != EFI_SUCCESS) {
+               efi_st_error("Failed to locate simple network protocol\n");
+               return EFI_ST_FAILURE;
+       }
+       /*
+        * Find an interface implementing the SNP protocol.
+        */
+       ret = boottime->locate_protocol(&efi_net_guid, NULL, (void **)&net);
+       if (ret != EFI_SUCCESS) {
+               efi_st_error("Failed to locate simple network protocol\n");
+               return EFI_ST_FAILURE;
+       }
+       /*
+        * Check hardware address size.
+        */
+       if (!net->mode) {
+               efi_st_error("Mode not provided\n");
+               return EFI_ST_FAILURE;
+       }
+       if (net->mode->hwaddr_size != ARP_HLEN) {
+               efi_st_error("HwAddressSize = %u, expected %u\n",
+                            net->mode->hwaddr_size, ARP_HLEN);
+               return EFI_ST_FAILURE;
+       }
+       /*
+        * Check that WaitForPacket event exists.
+        */
+       if (!net->wait_for_packet) {
+               efi_st_error("WaitForPacket event missing\n");
+               return EFI_ST_FAILURE;
+       }
+       /*
+        * Initialize network adapter.
+        */
+       ret = net->initialize(net, 0, 0);
+       if (ret != EFI_SUCCESS) {
+               efi_st_error("Failed to initialize network adapter\n");
+               return EFI_ST_FAILURE;
+       }
+       /*
+        * Start network adapter.
+        */
+       ret = net->start(net);
+       if (ret != EFI_SUCCESS) {
+               efi_st_error("Failed to start network adapter\n");
+               return EFI_ST_FAILURE;
+       }
+       return EFI_ST_SUCCESS;
+}
+
+/*
+ * Execute unit test.
+ *
+ * A DHCP discover message is sent. The test is successful if a
+ * DHCP reply is received within 10 seconds.
+ *
+ * @return:    EFI_ST_SUCCESS for success
+ */
+static int execute(void)
+{
+       efi_status_t ret;
+       struct efi_event *events[2];
+       size_t index;
+       union {
+               struct dhcp p;
+               u8 b[PKTSIZE];
+       } buffer;
+       struct efi_mac_address srcaddr;
+       struct efi_mac_address destaddr;
+       size_t buffer_size;
+       u8 *addr;
+       /*
+        * The timeout is to occur after 10 s.
+        */
+       unsigned int timeout = 10;
+
+       /*
+        * Send DHCP discover message
+        */
+       ret = send_dhcp_discover();
+       if (ret != EFI_SUCCESS)
+               return EFI_ST_FAILURE;
+
+       /*
+        * If we would call WaitForEvent only with the WaitForPacket event,
+        * our code would block until a packet is received which might never
+        * occur. By calling WaitFor event with both a timer event and the
+        * WaitForPacket event we can escape this blocking situation.
+        *
+        * If the timer event occurs before we have received a DHCP reply
+        * a further DHCP discover message is sent.
+        */
+       events[0] = timer;
+       events[1] = net->wait_for_packet;
+       for (;;) {
+               /*
+                * Wait for packet to be received or timer event.
+                */
+               boottime->wait_for_event(2, events, &index);
+               if (index == 0) {
+                       /*
+                        * The timer event occurred. Check for timeout.
+                        */
+                       --timeout;
+                       if (!timeout) {
+                               efi_st_error("Timeout occurred\n");
+                               return EFI_ST_FAILURE;
+                       }
+                       /*
+                        * Send further DHCP discover message
+                        */
+                       ret = send_dhcp_discover();
+                       if (ret != EFI_SUCCESS)
+                               return EFI_ST_FAILURE;
+                       continue;
+               }
+               /*
+                * Receive packet
+                */
+               buffer_size = sizeof(buffer);
+               net->receive(net, NULL, &buffer_size, &buffer,
+                            &srcaddr, &destaddr, NULL);
+               if (ret != EFI_SUCCESS) {
+                       efi_st_error("Failed to receive packet");
+                       return EFI_ST_FAILURE;
+               }
+               /*
+                * Check the packet is meant for this system.
+                * Unfortunately QEMU ignores the broadcast flag.
+                * So we have to check for broadcasts too.
+                */
+               if (efi_st_memcmp(&destaddr, &net->mode->current_address,
+                                 ARP_HLEN) &&
+                   efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
+                       continue;
+               /*
+                * Check this is a DHCP reply
+                */
+               if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
+                   buffer.p.ip_udp.ip_hl_v != 0x45 ||
+                   buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
+                   buffer.p.ip_udp.udp_src != ntohs(67) ||
+                   buffer.p.ip_udp.udp_dst != ntohs(68) ||
+                   buffer.p.dhcp_hdr.op != BOOTREPLY)
+                       continue;
+               /*
+                * We successfully received a DHCP reply.
+                */
+               break;
+       }
+
+       /*
+        * Write a log message.
+        */
+       addr = (u8 *)&buffer.p.ip_udp.ip_src;
+       efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
+                     addr[0], addr[1], addr[2], addr[3], &srcaddr);
+       if (!efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
+               efi_st_printf("as broadcast message.\n");
+       else
+               efi_st_printf("as unicast message.\n");
+
+       return EFI_ST_SUCCESS;
+}
+
+/*
+ * Tear down unit test.
+ *
+ * Close the timer event created in setup.
+ * Shut down the network adapter.
+ *
+ * @return:    EFI_ST_SUCCESS for success
+ */
+static int teardown(void)
+{
+       efi_status_t ret;
+       int exit_status = EFI_ST_SUCCESS;
+
+       if (timer) {
+               /*
+                * Stop timer.
+                */
+               ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
+               if (ret != EFI_SUCCESS) {
+                       efi_st_error("Failed to stop timer");
+                       exit_status = EFI_ST_FAILURE;
+               }
+               /*
+                * Close timer event.
+                */
+               ret = boottime->close_event(timer);
+               if (ret != EFI_SUCCESS) {
+                       efi_st_error("Failed to close event");
+                       exit_status = EFI_ST_FAILURE;
+               }
+       }
+       if (net) {
+               /*
+                * Stop network adapter.
+                */
+               ret = net->stop(net);
+               if (ret != EFI_SUCCESS) {
+                       efi_st_error("Failed to stop network adapter\n");
+                       exit_status = EFI_ST_FAILURE;
+               }
+               /*
+                * Shut down network adapter.
+                */
+               ret = net->shutdown(net);
+               if (ret != EFI_SUCCESS) {
+                       efi_st_error("Failed to shut down network adapter\n");
+                       exit_status = EFI_ST_FAILURE;
+               }
+       }
+
+       return exit_status;
+}
+
+EFI_UNIT_TEST(snp) = {
+       .name = "simple network protocol",
+       .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
+       .setup = setup,
+       .execute = execute,
+       .teardown = teardown,
+};
diff --git a/lib/efi_selftest/efi_selftest_util.c b/lib/efi_selftest/efi_selftest_util.c
new file mode 100644 (file)
index 0000000..c9c295e
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * efi_selftest_util
+ *
+ * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ *
+ * Utility functions
+ */
+
+#include <efi_selftest.h>
+
+int efi_st_memcmp(const void *buf1, const void *buf2, size_t length)
+{
+       const u8 *pos1 = buf1;
+       const u8 *pos2 = buf2;
+
+       for (; length; --length) {
+               if (*pos1 != *pos2)
+                       return pos1 - pos2;
+               ++pos1;
+               ++pos2;
+       }
+       return EFI_ST_SUCCESS;
+}