mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-11-03 21:48:15 +00:00 
			
		
		
		
	Replace @return and @param. Signed-off-by: Heinrich Schuchardt <heinrich.schuchardt@canonical.com>
		
			
				
	
	
		
			495 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			495 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0+
 | 
						|
/*
 | 
						|
 * efi_selftest_snp
 | 
						|
 *
 | 
						|
 * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
 | 
						|
 *
 | 
						|
 * 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>
 | 
						|
#include <net.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_PROTOCOL_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 set timer\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) {
 | 
						|
		net = NULL;
 | 
						|
		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;
 | 
						|
	}
 | 
						|
	if (net->mode->state == EFI_NETWORK_INITIALIZED) {
 | 
						|
		/*
 | 
						|
		 * Shut down network adapter.
 | 
						|
		 */
 | 
						|
		ret = net->shutdown(net);
 | 
						|
		if (ret != EFI_SUCCESS) {
 | 
						|
			efi_st_error("Failed to shut down network adapter\n");
 | 
						|
			return EFI_ST_FAILURE;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (net->mode->state == EFI_NETWORK_STARTED) {
 | 
						|
		/*
 | 
						|
		 * Stop network adapter.
 | 
						|
		 */
 | 
						|
		ret = net->stop(net);
 | 
						|
		if (ret != EFI_SUCCESS) {
 | 
						|
			efi_st_error("Failed to stop network adapter\n");
 | 
						|
			return EFI_ST_FAILURE;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	/*
 | 
						|
	 * Start network adapter.
 | 
						|
	 */
 | 
						|
	ret = net->start(net);
 | 
						|
	if (ret != EFI_SUCCESS && ret != EFI_ALREADY_STARTED) {
 | 
						|
		efi_st_error("Failed to start network adapter\n");
 | 
						|
		return EFI_ST_FAILURE;
 | 
						|
	}
 | 
						|
	if (net->mode->state != EFI_NETWORK_STARTED) {
 | 
						|
		efi_st_error("Failed to start network adapter\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;
 | 
						|
	}
 | 
						|
	if (net->mode->state != EFI_NETWORK_INITIALIZED) {
 | 
						|
		efi_st_error("Failed to initialize 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];
 | 
						|
	efi_uintn_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;
 | 
						|
 | 
						|
	/* Setup may have failed */
 | 
						|
	if (!net || !timer) {
 | 
						|
		efi_st_error("Cannot execute test after setup failure\n");
 | 
						|
		return EFI_ST_FAILURE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Check media connected */
 | 
						|
	ret = net->get_status(net, NULL, NULL);
 | 
						|
	if (ret != EFI_SUCCESS) {
 | 
						|
		efi_st_error("Failed to get status");
 | 
						|
		return EFI_ST_FAILURE;
 | 
						|
	}
 | 
						|
	if (net->mode && net->mode->media_present_supported &&
 | 
						|
	    !net->mode->media_present) {
 | 
						|
		efi_st_error("Network media is not connected");
 | 
						|
		return EFI_ST_FAILURE;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * 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 packets until buffer is empty
 | 
						|
		 */
 | 
						|
		for (;;) {
 | 
						|
			buffer_size = sizeof(buffer);
 | 
						|
			ret = net->receive(net, NULL, &buffer_size, &buffer,
 | 
						|
					   &srcaddr, &destaddr, NULL);
 | 
						|
			if (ret == EFI_NOT_READY) {
 | 
						|
				/* The received buffer is empty. */
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			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 (memcmp(&destaddr, &net->mode->current_address, ARP_HLEN) &&
 | 
						|
			    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.
 | 
						|
			 */
 | 
						|
			goto received;
 | 
						|
		}
 | 
						|
	}
 | 
						|
received:
 | 
						|
	/*
 | 
						|
	 * 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 (!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) {
 | 
						|
		/*
 | 
						|
		 * 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;
 | 
						|
		}
 | 
						|
		if (net->mode->state != EFI_NETWORK_STARTED) {
 | 
						|
			efi_st_error("Failed to shutdown network adapter\n");
 | 
						|
			return EFI_ST_FAILURE;
 | 
						|
		}
 | 
						|
		/*
 | 
						|
		 * 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;
 | 
						|
		}
 | 
						|
		if (net->mode->state != EFI_NETWORK_STOPPED) {
 | 
						|
			efi_st_error("Failed to stop network adapter\n");
 | 
						|
			return 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,
 | 
						|
#ifdef CONFIG_SANDBOX
 | 
						|
	/*
 | 
						|
	 * Running this test on the sandbox requires setting environment
 | 
						|
	 * variable ethact to a network interface connected to a DHCP server and
 | 
						|
	 * ethrotate to 'no'.
 | 
						|
	 */
 | 
						|
	.on_request = true,
 | 
						|
#endif
 | 
						|
};
 |