4 * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
6 * SPDX-License-Identifier: GPL-2.0+
8 * This unit test covers the Simple Network Protocol as well as
9 * the CopyMem and SetMem boottime services.
11 * A DHCP discover message is sent. The test is successful if a
12 * DHCP reply is received.
14 * TODO: Once ConnectController and DisconnectController are implemented
15 * we should connect our code as controller.
18 #include <efi_selftest.h>
21 * MAC address for broadcasts
23 static const u8 BROADCAST_MAC
[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
37 #define DHCP_FLAGS_UNICAST 0x0000
38 #define DHCP_FLAGS_BROADCAST 0x0080
49 * Message type option.
51 #define DHCP_MESSAGE_TYPE 0x35
52 #define DHCPDISCOVER 1
61 struct ethernet_hdr eth_hdr
;
62 struct ip_udp_hdr ip_udp
;
63 struct dhcp_hdr dhcp_hdr
;
67 static struct efi_boot_services
*boottime
;
68 static struct efi_simple_network
*net
;
69 static struct efi_event
*timer
;
70 static const efi_guid_t efi_net_guid
= EFI_SIMPLE_NETWORK_GUID
;
72 static unsigned int net_ip_id
;
75 * Compute the checksum of the IP header. We cover even values of length only.
76 * We cannot use net/checksum.c due to different CFLAGS values.
79 * @len: length of header in bytes
82 static unsigned int efi_ip_checksum(const void *buf
, size_t len
)
88 for (i
= 0; i
< len
; i
+= 2)
91 sum
= (sum
>> 16) + (sum
& 0xffff);
99 * Transmit a DHCPDISCOVER message.
101 static efi_status_t
send_dhcp_discover(void)
107 * Fill ethernet header
109 boottime
->copy_mem(p
.eth_hdr
.et_dest
, (void *)BROADCAST_MAC
, ARP_HLEN
);
110 boottime
->copy_mem(p
.eth_hdr
.et_src
, &net
->mode
->current_address
,
112 p
.eth_hdr
.et_protlen
= htons(PROT_IP
);
116 p
.ip_udp
.ip_hl_v
= 0x45;
117 p
.ip_udp
.ip_len
= htons(sizeof(struct dhcp
) -
118 sizeof(struct ethernet_hdr
));
119 p
.ip_udp
.ip_id
= htons(++net_ip_id
);
120 p
.ip_udp
.ip_off
= htons(IP_FLAGS_DFRAG
);
121 p
.ip_udp
.ip_ttl
= 0xff; /* time to live */
122 p
.ip_udp
.ip_p
= IPPROTO_UDP
;
123 boottime
->set_mem(&p
.ip_udp
.ip_dst
, 4, 0xff);
124 p
.ip_udp
.ip_sum
= efi_ip_checksum(&p
.ip_udp
, IP_HDR_SIZE
);
129 p
.ip_udp
.udp_src
= htons(68);
130 p
.ip_udp
.udp_dst
= htons(67);
131 p
.ip_udp
.udp_len
= htons(sizeof(struct dhcp
) -
132 sizeof(struct ethernet_hdr
) -
133 sizeof(struct ip_hdr
));
137 p
.dhcp_hdr
.op
= BOOTREQUEST
;
138 p
.dhcp_hdr
.htype
= HWT_ETHER
;
139 p
.dhcp_hdr
.hlen
= HWL_ETHER
;
140 p
.dhcp_hdr
.flags
= htons(DHCP_FLAGS_UNICAST
);
141 boottime
->copy_mem(&p
.dhcp_hdr
.chaddr
,
142 &net
->mode
->current_address
, ARP_HLEN
);
146 p
.opt
[0] = 0x63; /* DHCP magic cookie */
150 p
.opt
[4] = DHCP_MESSAGE_TYPE
;
151 p
.opt
[5] = 0x01; /* length */
152 p
.opt
[6] = DHCPDISCOVER
;
153 p
.opt
[7] = 0x39; /* maximum message size */
154 p
.opt
[8] = 0x02; /* length */
155 p
.opt
[9] = 0x02; /* 576 bytes */
157 p
.opt
[11] = 0xff; /* end of options */
160 * Transmit DHCPDISCOVER message.
162 ret
= net
->transmit(net
, 0, sizeof(struct dhcp
), &p
, NULL
, NULL
, 0);
163 if (ret
!= EFI_SUCCESS
)
164 efi_st_error("Sending a DHCP request failed\n");
166 efi_st_printf("DHCP Discover\n");
173 * Create a 1 s periodic timer.
174 * Start the network driver.
176 * @handle: handle of the loaded image
177 * @systable: system table
178 * @return: EFI_ST_SUCCESS for success
180 static int setup(const efi_handle_t handle
,
181 const struct efi_system_table
*systable
)
185 boottime
= systable
->boottime
;
188 * Create a timer event.
190 ret
= boottime
->create_event(EVT_TIMER
, TPL_CALLBACK
, NULL
, NULL
,
192 if (ret
!= EFI_SUCCESS
) {
193 efi_st_error("Failed to create event\n");
194 return EFI_ST_FAILURE
;
197 * Set timer period to 1s.
199 ret
= boottime
->set_timer(timer
, EFI_TIMER_PERIODIC
, 10000000);
200 if (ret
!= EFI_SUCCESS
) {
201 efi_st_error("Failed to set timer\n");
202 return EFI_ST_FAILURE
;
205 * Find an interface implementing the SNP protocol.
207 ret
= boottime
->locate_protocol(&efi_net_guid
, NULL
, (void **)&net
);
208 if (ret
!= EFI_SUCCESS
) {
210 efi_st_error("Failed to locate simple network protocol\n");
211 return EFI_ST_FAILURE
;
214 * Check hardware address size.
217 efi_st_error("Mode not provided\n");
218 return EFI_ST_FAILURE
;
220 if (net
->mode
->hwaddr_size
!= ARP_HLEN
) {
221 efi_st_error("HwAddressSize = %u, expected %u\n",
222 net
->mode
->hwaddr_size
, ARP_HLEN
);
223 return EFI_ST_FAILURE
;
226 * Check that WaitForPacket event exists.
228 if (!net
->wait_for_packet
) {
229 efi_st_error("WaitForPacket event missing\n");
230 return EFI_ST_FAILURE
;
233 * Initialize network adapter.
235 ret
= net
->initialize(net
, 0, 0);
236 if (ret
!= EFI_SUCCESS
) {
237 efi_st_error("Failed to initialize network adapter\n");
238 return EFI_ST_FAILURE
;
241 * Start network adapter.
243 ret
= net
->start(net
);
244 if (ret
!= EFI_SUCCESS
) {
245 efi_st_error("Failed to start network adapter\n");
246 return EFI_ST_FAILURE
;
248 return EFI_ST_SUCCESS
;
254 * A DHCP discover message is sent. The test is successful if a
255 * DHCP reply is received within 10 seconds.
257 * @return: EFI_ST_SUCCESS for success
259 static int execute(void)
262 struct efi_event
*events
[2];
268 struct efi_mac_address srcaddr
;
269 struct efi_mac_address destaddr
;
273 * The timeout is to occur after 10 s.
275 unsigned int timeout
= 10;
277 /* Setup may have failed */
278 if (!net
|| !timer
) {
279 efi_st_error("Cannot execute test after setup failure\n");
280 return EFI_ST_FAILURE
;
284 * Send DHCP discover message
286 ret
= send_dhcp_discover();
287 if (ret
!= EFI_SUCCESS
)
288 return EFI_ST_FAILURE
;
291 * If we would call WaitForEvent only with the WaitForPacket event,
292 * our code would block until a packet is received which might never
293 * occur. By calling WaitFor event with both a timer event and the
294 * WaitForPacket event we can escape this blocking situation.
296 * If the timer event occurs before we have received a DHCP reply
297 * a further DHCP discover message is sent.
300 events
[1] = net
->wait_for_packet
;
303 * Wait for packet to be received or timer event.
305 boottime
->wait_for_event(2, events
, &index
);
308 * The timer event occurred. Check for timeout.
312 efi_st_error("Timeout occurred\n");
313 return EFI_ST_FAILURE
;
316 * Send further DHCP discover message
318 ret
= send_dhcp_discover();
319 if (ret
!= EFI_SUCCESS
)
320 return EFI_ST_FAILURE
;
326 buffer_size
= sizeof(buffer
);
327 net
->receive(net
, NULL
, &buffer_size
, &buffer
,
328 &srcaddr
, &destaddr
, NULL
);
329 if (ret
!= EFI_SUCCESS
) {
330 efi_st_error("Failed to receive packet");
331 return EFI_ST_FAILURE
;
334 * Check the packet is meant for this system.
335 * Unfortunately QEMU ignores the broadcast flag.
336 * So we have to check for broadcasts too.
338 if (efi_st_memcmp(&destaddr
, &net
->mode
->current_address
,
340 efi_st_memcmp(&destaddr
, BROADCAST_MAC
, ARP_HLEN
))
343 * Check this is a DHCP reply
345 if (buffer
.p
.eth_hdr
.et_protlen
!= ntohs(PROT_IP
) ||
346 buffer
.p
.ip_udp
.ip_hl_v
!= 0x45 ||
347 buffer
.p
.ip_udp
.ip_p
!= IPPROTO_UDP
||
348 buffer
.p
.ip_udp
.udp_src
!= ntohs(67) ||
349 buffer
.p
.ip_udp
.udp_dst
!= ntohs(68) ||
350 buffer
.p
.dhcp_hdr
.op
!= BOOTREPLY
)
353 * We successfully received a DHCP reply.
359 * Write a log message.
361 addr
= (u8
*)&buffer
.p
.ip_udp
.ip_src
;
362 efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
363 addr
[0], addr
[1], addr
[2], addr
[3], &srcaddr
);
364 if (!efi_st_memcmp(&destaddr
, BROADCAST_MAC
, ARP_HLEN
))
365 efi_st_printf("as broadcast message.\n");
367 efi_st_printf("as unicast message.\n");
369 return EFI_ST_SUCCESS
;
373 * Tear down unit test.
375 * Close the timer event created in setup.
376 * Shut down the network adapter.
378 * @return: EFI_ST_SUCCESS for success
380 static int teardown(void)
383 int exit_status
= EFI_ST_SUCCESS
;
389 ret
= boottime
->set_timer(timer
, EFI_TIMER_STOP
, 0);
390 if (ret
!= EFI_SUCCESS
) {
391 efi_st_error("Failed to stop timer");
392 exit_status
= EFI_ST_FAILURE
;
397 ret
= boottime
->close_event(timer
);
398 if (ret
!= EFI_SUCCESS
) {
399 efi_st_error("Failed to close event");
400 exit_status
= EFI_ST_FAILURE
;
405 * Stop network adapter.
407 ret
= net
->stop(net
);
408 if (ret
!= EFI_SUCCESS
) {
409 efi_st_error("Failed to stop network adapter\n");
410 exit_status
= EFI_ST_FAILURE
;
413 * Shut down network adapter.
415 ret
= net
->shutdown(net
);
416 if (ret
!= EFI_SUCCESS
) {
417 efi_st_error("Failed to shut down network adapter\n");
418 exit_status
= EFI_ST_FAILURE
;
425 EFI_UNIT_TEST(snp
) = {
426 .name
= "simple network protocol",
427 .phase
= EFI_EXECUTE_BEFORE_BOOTTIME_EXIT
,
430 .teardown
= teardown
,