efi_loader: supply EFI network test
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>
This commit is contained in:
parent
1b6332597f
commit
5ca23ed5bc
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
|
424
lib/efi_selftest/efi_selftest_snp.c
Normal file
424
lib/efi_selftest/efi_selftest_snp.c
Normal file
@ -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,
|
||||
};
|
25
lib/efi_selftest/efi_selftest_util.c
Normal file
25
lib/efi_selftest/efi_selftest_util.c
Normal file
@ -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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user