1 // SPDX-License-Identifier: GPL-2.0+
5 * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
7 * This unit test covers the Simple Network Protocol as well as
8 * the CopyMem and SetMem boottime services.
10 * A DHCP discover message is sent. The test is successful if a
11 * DHCP reply is received.
13 * TODO: Once ConnectController and DisconnectController are implemented
14 * we should connect our code as controller.
17 #include <efi_selftest.h>
20 * MAC address for broadcasts
22 static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
36 #define DHCP_FLAGS_UNICAST 0x0000
37 #define DHCP_FLAGS_BROADCAST 0x0080
48 * Message type option.
50 #define DHCP_MESSAGE_TYPE 0x35
51 #define DHCPDISCOVER 1
60 struct ethernet_hdr eth_hdr;
61 struct ip_udp_hdr ip_udp;
62 struct dhcp_hdr dhcp_hdr;
66 static struct efi_boot_services *boottime;
67 static struct efi_simple_network *net;
68 static struct efi_event *timer;
69 static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID;
71 static unsigned int net_ip_id;
74 * Compute the checksum of the IP header. We cover even values of length only.
75 * We cannot use net/checksum.c due to different CFLAGS values.
78 * @len: length of header in bytes
81 static unsigned int efi_ip_checksum(const void *buf, size_t len)
87 for (i = 0; i < len; i += 2)
90 sum = (sum >> 16) + (sum & 0xffff);
98 * Transmit a DHCPDISCOVER message.
100 static efi_status_t send_dhcp_discover(void)
106 * Fill Ethernet header
108 boottime->copy_mem(p.eth_hdr.et_dest, (void *)BROADCAST_MAC, ARP_HLEN);
109 boottime->copy_mem(p.eth_hdr.et_src, &net->mode->current_address,
111 p.eth_hdr.et_protlen = htons(PROT_IP);
115 p.ip_udp.ip_hl_v = 0x45;
116 p.ip_udp.ip_len = htons(sizeof(struct dhcp) -
117 sizeof(struct ethernet_hdr));
118 p.ip_udp.ip_id = htons(++net_ip_id);
119 p.ip_udp.ip_off = htons(IP_FLAGS_DFRAG);
120 p.ip_udp.ip_ttl = 0xff; /* time to live */
121 p.ip_udp.ip_p = IPPROTO_UDP;
122 boottime->set_mem(&p.ip_udp.ip_dst, 4, 0xff);
123 p.ip_udp.ip_sum = efi_ip_checksum(&p.ip_udp, IP_HDR_SIZE);
128 p.ip_udp.udp_src = htons(68);
129 p.ip_udp.udp_dst = htons(67);
130 p.ip_udp.udp_len = htons(sizeof(struct dhcp) -
131 sizeof(struct ethernet_hdr) -
132 sizeof(struct ip_hdr));
136 p.dhcp_hdr.op = BOOTREQUEST;
137 p.dhcp_hdr.htype = HWT_ETHER;
138 p.dhcp_hdr.hlen = HWL_ETHER;
139 p.dhcp_hdr.flags = htons(DHCP_FLAGS_UNICAST);
140 boottime->copy_mem(&p.dhcp_hdr.chaddr,
141 &net->mode->current_address, ARP_HLEN);
145 p.opt[0] = 0x63; /* DHCP magic cookie */
149 p.opt[4] = DHCP_MESSAGE_TYPE;
150 p.opt[5] = 0x01; /* length */
151 p.opt[6] = DHCPDISCOVER;
152 p.opt[7] = 0x39; /* maximum message size */
153 p.opt[8] = 0x02; /* length */
154 p.opt[9] = 0x02; /* 576 bytes */
156 p.opt[11] = 0xff; /* end of options */
159 * Transmit DHCPDISCOVER message.
161 ret = net->transmit(net, 0, sizeof(struct dhcp), &p, NULL, NULL, 0);
162 if (ret != EFI_SUCCESS)
163 efi_st_error("Sending a DHCP request failed\n");
165 efi_st_printf("DHCP Discover\n");
172 * Create a 1 s periodic timer.
173 * Start the network driver.
175 * @handle: handle of the loaded image
176 * @systable: system table
177 * @return: EFI_ST_SUCCESS for success
179 static int setup(const efi_handle_t handle,
180 const struct efi_system_table *systable)
184 boottime = systable->boottime;
187 * Create a timer event.
189 ret = boottime->create_event(EVT_TIMER, TPL_CALLBACK, NULL, NULL,
191 if (ret != EFI_SUCCESS) {
192 efi_st_error("Failed to create event\n");
193 return EFI_ST_FAILURE;
196 * Set timer period to 1s.
198 ret = boottime->set_timer(timer, EFI_TIMER_PERIODIC, 10000000);
199 if (ret != EFI_SUCCESS) {
200 efi_st_error("Failed to set timer\n");
201 return EFI_ST_FAILURE;
204 * Find an interface implementing the SNP protocol.
206 ret = boottime->locate_protocol(&efi_net_guid, NULL, (void **)&net);
207 if (ret != EFI_SUCCESS) {
209 efi_st_error("Failed to locate simple network protocol\n");
210 return EFI_ST_FAILURE;
213 * Check hardware address size.
216 efi_st_error("Mode not provided\n");
217 return EFI_ST_FAILURE;
219 if (net->mode->hwaddr_size != ARP_HLEN) {
220 efi_st_error("HwAddressSize = %u, expected %u\n",
221 net->mode->hwaddr_size, ARP_HLEN);
222 return EFI_ST_FAILURE;
225 * Check that WaitForPacket event exists.
227 if (!net->wait_for_packet) {
228 efi_st_error("WaitForPacket event missing\n");
229 return EFI_ST_FAILURE;
231 if (net->mode->state == EFI_NETWORK_INITIALIZED) {
233 * Shut down network adapter.
235 ret = net->shutdown(net);
236 if (ret != EFI_SUCCESS) {
237 efi_st_error("Failed to shut down network adapter\n");
238 return EFI_ST_FAILURE;
241 if (net->mode->state == EFI_NETWORK_STARTED) {
243 * Stop network adapter.
245 ret = net->stop(net);
246 if (ret != EFI_SUCCESS) {
247 efi_st_error("Failed to stop network adapter\n");
248 return EFI_ST_FAILURE;
252 * Start network adapter.
254 ret = net->start(net);
255 if (ret != EFI_SUCCESS && ret != EFI_ALREADY_STARTED) {
256 efi_st_error("Failed to start network adapter\n");
257 return EFI_ST_FAILURE;
259 if (net->mode->state != EFI_NETWORK_STARTED) {
260 efi_st_error("Failed to start network adapter\n");
261 return EFI_ST_FAILURE;
264 * Initialize network adapter.
266 ret = net->initialize(net, 0, 0);
267 if (ret != EFI_SUCCESS) {
268 efi_st_error("Failed to initialize network adapter\n");
269 return EFI_ST_FAILURE;
271 if (net->mode->state != EFI_NETWORK_INITIALIZED) {
272 efi_st_error("Failed to initialize network adapter\n");
273 return EFI_ST_FAILURE;
275 return EFI_ST_SUCCESS;
281 * A DHCP discover message is sent. The test is successful if a
282 * DHCP reply is received within 10 seconds.
284 * @return: EFI_ST_SUCCESS for success
286 static int execute(void)
289 struct efi_event *events[2];
295 struct efi_mac_address srcaddr;
296 struct efi_mac_address destaddr;
301 * The timeout is to occur after 10 s.
303 unsigned int timeout = 10;
305 /* Setup may have failed */
306 if (!net || !timer) {
307 efi_st_error("Cannot execute test after setup failure\n");
308 return EFI_ST_FAILURE;
312 * Send DHCP discover message
314 ret = send_dhcp_discover();
315 if (ret != EFI_SUCCESS)
316 return EFI_ST_FAILURE;
319 * If we would call WaitForEvent only with the WaitForPacket event,
320 * our code would block until a packet is received which might never
321 * occur. By calling WaitFor event with both a timer event and the
322 * WaitForPacket event we can escape this blocking situation.
324 * If the timer event occurs before we have received a DHCP reply
325 * a further DHCP discover message is sent.
328 events[1] = net->wait_for_packet;
333 * Wait for packet to be received or timer event.
335 boottime->wait_for_event(2, events, &index);
338 * The timer event occurred. Check for timeout.
342 efi_st_error("Timeout occurred\n");
343 return EFI_ST_FAILURE;
346 * Send further DHCP discover message
348 ret = send_dhcp_discover();
349 if (ret != EFI_SUCCESS)
350 return EFI_ST_FAILURE;
356 buffer_size = sizeof(buffer);
357 ret = net->get_status(net, &int_status, NULL);
358 if (ret != EFI_SUCCESS) {
359 efi_st_error("Failed to get status");
360 return EFI_ST_FAILURE;
362 if (!(int_status & EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT)) {
363 efi_st_error("RX interrupt not set");
364 return EFI_ST_FAILURE;
366 ret = net->receive(net, NULL, &buffer_size, &buffer,
367 &srcaddr, &destaddr, NULL);
368 if (ret != EFI_SUCCESS) {
369 efi_st_error("Failed to receive packet");
370 return EFI_ST_FAILURE;
373 * Check the packet is meant for this system.
374 * Unfortunately QEMU ignores the broadcast flag.
375 * So we have to check for broadcasts too.
377 if (memcmp(&destaddr, &net->mode->current_address, ARP_HLEN) &&
378 memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
381 * Check this is a DHCP reply
383 if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
384 buffer.p.ip_udp.ip_hl_v != 0x45 ||
385 buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
386 buffer.p.ip_udp.udp_src != ntohs(67) ||
387 buffer.p.ip_udp.udp_dst != ntohs(68) ||
388 buffer.p.dhcp_hdr.op != BOOTREPLY)
391 * We successfully received a DHCP reply.
397 * Write a log message.
399 addr = (u8 *)&buffer.p.ip_udp.ip_src;
400 efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
401 addr[0], addr[1], addr[2], addr[3], &srcaddr);
402 if (!memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
403 efi_st_printf("as broadcast message.\n");
405 efi_st_printf("as unicast message.\n");
407 return EFI_ST_SUCCESS;
411 * Tear down unit test.
413 * Close the timer event created in setup.
414 * Shut down the network adapter.
416 * @return: EFI_ST_SUCCESS for success
418 static int teardown(void)
421 int exit_status = EFI_ST_SUCCESS;
427 ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
428 if (ret != EFI_SUCCESS) {
429 efi_st_error("Failed to stop timer");
430 exit_status = EFI_ST_FAILURE;
435 ret = boottime->close_event(timer);
436 if (ret != EFI_SUCCESS) {
437 efi_st_error("Failed to close event");
438 exit_status = EFI_ST_FAILURE;
443 * Shut down network adapter.
445 ret = net->shutdown(net);
446 if (ret != EFI_SUCCESS) {
447 efi_st_error("Failed to shut down network adapter\n");
448 exit_status = EFI_ST_FAILURE;
450 if (net->mode->state != EFI_NETWORK_STARTED) {
451 efi_st_error("Failed to shutdown network adapter\n");
452 return EFI_ST_FAILURE;
455 * Stop network adapter.
457 ret = net->stop(net);
458 if (ret != EFI_SUCCESS) {
459 efi_st_error("Failed to stop network adapter\n");
460 exit_status = EFI_ST_FAILURE;
462 if (net->mode->state != EFI_NETWORK_STOPPED) {
463 efi_st_error("Failed to stop network adapter\n");
464 return EFI_ST_FAILURE;
471 EFI_UNIT_TEST(snp) = {
472 .name = "simple network protocol",
473 .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
476 .teardown = teardown,
477 #ifdef CONFIG_SANDBOX
479 * Running this test on the sandbox requires setting environment
480 * variable ethact to a network interface connected to a DHCP server and