Commit 5ca23ed5bc63832baa24a6107537fdd229c458ae
Committed by
Alexander Graf
1 parent
1b6332597f
Exists in
smarc_8mq_lf_v2020.04
and in
17 other branches
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>
Showing 4 changed files with 467 additions and 1 deletions Side-by-side Diff
include/efi_selftest.h
... | ... | @@ -61,6 +61,17 @@ |
61 | 61 | __attribute__ ((format (__printf__, 1, 2))); |
62 | 62 | |
63 | 63 | /* |
64 | + * Compare memory. | |
65 | + * We cannot use lib/string.c due to different CFLAGS values. | |
66 | + * | |
67 | + * @buf1: first buffer | |
68 | + * @buf2: second buffer | |
69 | + * @length: number of bytes to compare | |
70 | + * @return: 0 if both buffers contain the same bytes | |
71 | + */ | |
72 | +int efi_st_memcmp(const void *buf1, const void *buf2, size_t length); | |
73 | + | |
74 | +/* | |
64 | 75 | * Reads an Unicode character from the input device. |
65 | 76 | * |
66 | 77 | * @return: Unicode character |
lib/efi_selftest/Makefile
... | ... | @@ -15,13 +15,19 @@ |
15 | 15 | CFLAGS_REMOVE_efi_selftest_events.o := $(CFLAGS_NON_EFI) |
16 | 16 | CFLAGS_efi_selftest_exitbootservices.o := $(CFLAGS_EFI) |
17 | 17 | CFLAGS_REMOVE_efi_selftest_exitbootservices.o := $(CFLAGS_NON_EFI) |
18 | +CFLAGS_efi_selftest_snp.o := $(CFLAGS_EFI) | |
19 | +CFLAGS_REMOVE_efi_selftest_snp.o := $(CFLAGS_NON_EFI) | |
18 | 20 | CFLAGS_efi_selftest_tpl.o := $(CFLAGS_EFI) |
19 | 21 | CFLAGS_REMOVE_efi_selftest_tpl.o := $(CFLAGS_NON_EFI) |
22 | +CFLAGS_efi_selftest_util.o := $(CFLAGS_EFI) | |
23 | +CFLAGS_REMOVE_efi_selftest_util.o := $(CFLAGS_NON_EFI) | |
20 | 24 | |
21 | 25 | obj-$(CONFIG_CMD_BOOTEFI_SELFTEST) += \ |
22 | 26 | efi_selftest.o \ |
23 | 27 | efi_selftest_console.o \ |
24 | 28 | efi_selftest_events.o \ |
25 | 29 | efi_selftest_exitbootservices.o \ |
26 | -efi_selftest_tpl.o | |
30 | +efi_selftest_snp.o \ | |
31 | +efi_selftest_tpl.o \ | |
32 | +efi_selftest_util.o |
lib/efi_selftest/efi_selftest_snp.c
1 | +/* | |
2 | + * efi_selftest_snp | |
3 | + * | |
4 | + * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de> | |
5 | + * | |
6 | + * SPDX-License-Identifier: GPL-2.0+ | |
7 | + * | |
8 | + * This unit test covers the Simple Network Protocol as well as | |
9 | + * the CopyMem and SetMem boottime services. | |
10 | + * | |
11 | + * A DHCP discover message is sent. The test is successful if a | |
12 | + * DHCP reply is received. | |
13 | + * | |
14 | + * TODO: Once ConnectController and DisconnectController are implemented | |
15 | + * we should connect our code as controller. | |
16 | + */ | |
17 | + | |
18 | +#include <efi_selftest.h> | |
19 | + | |
20 | +/* | |
21 | + * MAC address for broadcasts | |
22 | + */ | |
23 | +static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; | |
24 | + | |
25 | +struct dhcp_hdr { | |
26 | + u8 op; | |
27 | +#define BOOTREQUEST 1 | |
28 | +#define BOOTREPLY 2 | |
29 | + u8 htype; | |
30 | +# define HWT_ETHER 1 | |
31 | + u8 hlen; | |
32 | +# define HWL_ETHER 6 | |
33 | + u8 hops; | |
34 | + u32 xid; | |
35 | + u16 secs; | |
36 | + u16 flags; | |
37 | +#define DHCP_FLAGS_UNICAST 0x0000 | |
38 | +#define DHCP_FLAGS_BROADCAST 0x0080 | |
39 | + u32 ciaddr; | |
40 | + u32 yiaddr; | |
41 | + u32 siaddr; | |
42 | + u32 giaddr; | |
43 | + u8 chaddr[16]; | |
44 | + u8 sname[64]; | |
45 | + u8 file[128]; | |
46 | +}; | |
47 | + | |
48 | +/* | |
49 | + * Message type option. | |
50 | + */ | |
51 | +#define DHCP_MESSAGE_TYPE 0x35 | |
52 | +#define DHCPDISCOVER 1 | |
53 | +#define DHCPOFFER 2 | |
54 | +#define DHCPREQUEST 3 | |
55 | +#define DHCPDECLINE 4 | |
56 | +#define DHCPACK 5 | |
57 | +#define DHCPNAK 6 | |
58 | +#define DHCPRELEASE 7 | |
59 | + | |
60 | +struct dhcp { | |
61 | + struct ethernet_hdr eth_hdr; | |
62 | + struct ip_udp_hdr ip_udp; | |
63 | + struct dhcp_hdr dhcp_hdr; | |
64 | + u8 opt[128]; | |
65 | +} __packed; | |
66 | + | |
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; | |
71 | +/* IP packet ID */ | |
72 | +static unsigned int net_ip_id; | |
73 | + | |
74 | +/* | |
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. | |
77 | + * | |
78 | + * @buf: IP header | |
79 | + * @len: length of header in bytes | |
80 | + * @return: checksum | |
81 | + */ | |
82 | +static unsigned int efi_ip_checksum(const void *buf, size_t len) | |
83 | +{ | |
84 | + size_t i; | |
85 | + u32 sum = 0; | |
86 | + const u16 *pos = buf; | |
87 | + | |
88 | + for (i = 0; i < len; i += 2) | |
89 | + sum += *pos++; | |
90 | + | |
91 | + sum = (sum >> 16) + (sum & 0xffff); | |
92 | + sum += sum >> 16; | |
93 | + sum = ~sum & 0xffff; | |
94 | + | |
95 | + return sum; | |
96 | +} | |
97 | + | |
98 | +/* | |
99 | + * Transmit a DHCPDISCOVER message. | |
100 | + */ | |
101 | +static efi_status_t send_dhcp_discover(void) | |
102 | +{ | |
103 | + efi_status_t ret; | |
104 | + struct dhcp p = {}; | |
105 | + | |
106 | + /* | |
107 | + * Fill ethernet header | |
108 | + */ | |
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, | |
111 | + ARP_HLEN); | |
112 | + p.eth_hdr.et_protlen = htons(PROT_IP); | |
113 | + /* | |
114 | + * Fill IP header | |
115 | + */ | |
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); | |
125 | + | |
126 | + /* | |
127 | + * Fill UDP header | |
128 | + */ | |
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)); | |
134 | + /* | |
135 | + * Fill DHCP header | |
136 | + */ | |
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); | |
143 | + /* | |
144 | + * Fill options | |
145 | + */ | |
146 | + p.opt[0] = 0x63; /* DHCP magic cookie */ | |
147 | + p.opt[1] = 0x82; | |
148 | + p.opt[2] = 0x53; | |
149 | + p.opt[3] = 0x63; | |
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 */ | |
156 | + p.opt[10] = 0x40; | |
157 | + p.opt[11] = 0xff; /* end of options */ | |
158 | + | |
159 | + /* | |
160 | + * Transmit DHCPDISCOVER message. | |
161 | + */ | |
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"); | |
165 | + else | |
166 | + efi_st_printf("DHCP Discover\n"); | |
167 | + return ret; | |
168 | +} | |
169 | + | |
170 | +/* | |
171 | + * Setup unit test. | |
172 | + * | |
173 | + * Create a 1 s periodic timer. | |
174 | + * Start the network driver. | |
175 | + * | |
176 | + * @handle: handle of the loaded image | |
177 | + * @systable: system table | |
178 | + * @return: EFI_ST_SUCCESS for success | |
179 | + */ | |
180 | +static int setup(const efi_handle_t handle, | |
181 | + const struct efi_system_table *systable) | |
182 | +{ | |
183 | + efi_status_t ret; | |
184 | + | |
185 | + boottime = systable->boottime; | |
186 | + | |
187 | + /* | |
188 | + * Create a timer event. | |
189 | + */ | |
190 | + ret = boottime->create_event(EVT_TIMER, TPL_CALLBACK, NULL, NULL, | |
191 | + &timer); | |
192 | + if (ret != EFI_SUCCESS) { | |
193 | + efi_st_error("Failed to create event\n"); | |
194 | + return EFI_ST_FAILURE; | |
195 | + } | |
196 | + /* | |
197 | + * Set timer period to 1s. | |
198 | + */ | |
199 | + ret = boottime->set_timer(timer, EFI_TIMER_PERIODIC, 10000000); | |
200 | + if (ret != EFI_SUCCESS) { | |
201 | + efi_st_error("Failed to locate simple network protocol\n"); | |
202 | + return EFI_ST_FAILURE; | |
203 | + } | |
204 | + /* | |
205 | + * Find an interface implementing the SNP protocol. | |
206 | + */ | |
207 | + ret = boottime->locate_protocol(&efi_net_guid, NULL, (void **)&net); | |
208 | + if (ret != EFI_SUCCESS) { | |
209 | + efi_st_error("Failed to locate simple network protocol\n"); | |
210 | + return EFI_ST_FAILURE; | |
211 | + } | |
212 | + /* | |
213 | + * Check hardware address size. | |
214 | + */ | |
215 | + if (!net->mode) { | |
216 | + efi_st_error("Mode not provided\n"); | |
217 | + return EFI_ST_FAILURE; | |
218 | + } | |
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; | |
223 | + } | |
224 | + /* | |
225 | + * Check that WaitForPacket event exists. | |
226 | + */ | |
227 | + if (!net->wait_for_packet) { | |
228 | + efi_st_error("WaitForPacket event missing\n"); | |
229 | + return EFI_ST_FAILURE; | |
230 | + } | |
231 | + /* | |
232 | + * Initialize network adapter. | |
233 | + */ | |
234 | + ret = net->initialize(net, 0, 0); | |
235 | + if (ret != EFI_SUCCESS) { | |
236 | + efi_st_error("Failed to initialize network adapter\n"); | |
237 | + return EFI_ST_FAILURE; | |
238 | + } | |
239 | + /* | |
240 | + * Start network adapter. | |
241 | + */ | |
242 | + ret = net->start(net); | |
243 | + if (ret != EFI_SUCCESS) { | |
244 | + efi_st_error("Failed to start network adapter\n"); | |
245 | + return EFI_ST_FAILURE; | |
246 | + } | |
247 | + return EFI_ST_SUCCESS; | |
248 | +} | |
249 | + | |
250 | +/* | |
251 | + * Execute unit test. | |
252 | + * | |
253 | + * A DHCP discover message is sent. The test is successful if a | |
254 | + * DHCP reply is received within 10 seconds. | |
255 | + * | |
256 | + * @return: EFI_ST_SUCCESS for success | |
257 | + */ | |
258 | +static int execute(void) | |
259 | +{ | |
260 | + efi_status_t ret; | |
261 | + struct efi_event *events[2]; | |
262 | + size_t index; | |
263 | + union { | |
264 | + struct dhcp p; | |
265 | + u8 b[PKTSIZE]; | |
266 | + } buffer; | |
267 | + struct efi_mac_address srcaddr; | |
268 | + struct efi_mac_address destaddr; | |
269 | + size_t buffer_size; | |
270 | + u8 *addr; | |
271 | + /* | |
272 | + * The timeout is to occur after 10 s. | |
273 | + */ | |
274 | + unsigned int timeout = 10; | |
275 | + | |
276 | + /* | |
277 | + * Send DHCP discover message | |
278 | + */ | |
279 | + ret = send_dhcp_discover(); | |
280 | + if (ret != EFI_SUCCESS) | |
281 | + return EFI_ST_FAILURE; | |
282 | + | |
283 | + /* | |
284 | + * If we would call WaitForEvent only with the WaitForPacket event, | |
285 | + * our code would block until a packet is received which might never | |
286 | + * occur. By calling WaitFor event with both a timer event and the | |
287 | + * WaitForPacket event we can escape this blocking situation. | |
288 | + * | |
289 | + * If the timer event occurs before we have received a DHCP reply | |
290 | + * a further DHCP discover message is sent. | |
291 | + */ | |
292 | + events[0] = timer; | |
293 | + events[1] = net->wait_for_packet; | |
294 | + for (;;) { | |
295 | + /* | |
296 | + * Wait for packet to be received or timer event. | |
297 | + */ | |
298 | + boottime->wait_for_event(2, events, &index); | |
299 | + if (index == 0) { | |
300 | + /* | |
301 | + * The timer event occurred. Check for timeout. | |
302 | + */ | |
303 | + --timeout; | |
304 | + if (!timeout) { | |
305 | + efi_st_error("Timeout occurred\n"); | |
306 | + return EFI_ST_FAILURE; | |
307 | + } | |
308 | + /* | |
309 | + * Send further DHCP discover message | |
310 | + */ | |
311 | + ret = send_dhcp_discover(); | |
312 | + if (ret != EFI_SUCCESS) | |
313 | + return EFI_ST_FAILURE; | |
314 | + continue; | |
315 | + } | |
316 | + /* | |
317 | + * Receive packet | |
318 | + */ | |
319 | + buffer_size = sizeof(buffer); | |
320 | + net->receive(net, NULL, &buffer_size, &buffer, | |
321 | + &srcaddr, &destaddr, NULL); | |
322 | + if (ret != EFI_SUCCESS) { | |
323 | + efi_st_error("Failed to receive packet"); | |
324 | + return EFI_ST_FAILURE; | |
325 | + } | |
326 | + /* | |
327 | + * Check the packet is meant for this system. | |
328 | + * Unfortunately QEMU ignores the broadcast flag. | |
329 | + * So we have to check for broadcasts too. | |
330 | + */ | |
331 | + if (efi_st_memcmp(&destaddr, &net->mode->current_address, | |
332 | + ARP_HLEN) && | |
333 | + efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN)) | |
334 | + continue; | |
335 | + /* | |
336 | + * Check this is a DHCP reply | |
337 | + */ | |
338 | + if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) || | |
339 | + buffer.p.ip_udp.ip_hl_v != 0x45 || | |
340 | + buffer.p.ip_udp.ip_p != IPPROTO_UDP || | |
341 | + buffer.p.ip_udp.udp_src != ntohs(67) || | |
342 | + buffer.p.ip_udp.udp_dst != ntohs(68) || | |
343 | + buffer.p.dhcp_hdr.op != BOOTREPLY) | |
344 | + continue; | |
345 | + /* | |
346 | + * We successfully received a DHCP reply. | |
347 | + */ | |
348 | + break; | |
349 | + } | |
350 | + | |
351 | + /* | |
352 | + * Write a log message. | |
353 | + */ | |
354 | + addr = (u8 *)&buffer.p.ip_udp.ip_src; | |
355 | + efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ", | |
356 | + addr[0], addr[1], addr[2], addr[3], &srcaddr); | |
357 | + if (!efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN)) | |
358 | + efi_st_printf("as broadcast message.\n"); | |
359 | + else | |
360 | + efi_st_printf("as unicast message.\n"); | |
361 | + | |
362 | + return EFI_ST_SUCCESS; | |
363 | +} | |
364 | + | |
365 | +/* | |
366 | + * Tear down unit test. | |
367 | + * | |
368 | + * Close the timer event created in setup. | |
369 | + * Shut down the network adapter. | |
370 | + * | |
371 | + * @return: EFI_ST_SUCCESS for success | |
372 | + */ | |
373 | +static int teardown(void) | |
374 | +{ | |
375 | + efi_status_t ret; | |
376 | + int exit_status = EFI_ST_SUCCESS; | |
377 | + | |
378 | + if (timer) { | |
379 | + /* | |
380 | + * Stop timer. | |
381 | + */ | |
382 | + ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0); | |
383 | + if (ret != EFI_SUCCESS) { | |
384 | + efi_st_error("Failed to stop timer"); | |
385 | + exit_status = EFI_ST_FAILURE; | |
386 | + } | |
387 | + /* | |
388 | + * Close timer event. | |
389 | + */ | |
390 | + ret = boottime->close_event(timer); | |
391 | + if (ret != EFI_SUCCESS) { | |
392 | + efi_st_error("Failed to close event"); | |
393 | + exit_status = EFI_ST_FAILURE; | |
394 | + } | |
395 | + } | |
396 | + if (net) { | |
397 | + /* | |
398 | + * Stop network adapter. | |
399 | + */ | |
400 | + ret = net->stop(net); | |
401 | + if (ret != EFI_SUCCESS) { | |
402 | + efi_st_error("Failed to stop network adapter\n"); | |
403 | + exit_status = EFI_ST_FAILURE; | |
404 | + } | |
405 | + /* | |
406 | + * Shut down network adapter. | |
407 | + */ | |
408 | + ret = net->shutdown(net); | |
409 | + if (ret != EFI_SUCCESS) { | |
410 | + efi_st_error("Failed to shut down network adapter\n"); | |
411 | + exit_status = EFI_ST_FAILURE; | |
412 | + } | |
413 | + } | |
414 | + | |
415 | + return exit_status; | |
416 | +} | |
417 | + | |
418 | +EFI_UNIT_TEST(snp) = { | |
419 | + .name = "simple network protocol", | |
420 | + .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT, | |
421 | + .setup = setup, | |
422 | + .execute = execute, | |
423 | + .teardown = teardown, | |
424 | +}; |
lib/efi_selftest/efi_selftest_util.c
1 | +/* | |
2 | + * efi_selftest_util | |
3 | + * | |
4 | + * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de> | |
5 | + * | |
6 | + * SPDX-License-Identifier: GPL-2.0+ | |
7 | + * | |
8 | + * Utility functions | |
9 | + */ | |
10 | + | |
11 | +#include <efi_selftest.h> | |
12 | + | |
13 | +int efi_st_memcmp(const void *buf1, const void *buf2, size_t length) | |
14 | +{ | |
15 | + const u8 *pos1 = buf1; | |
16 | + const u8 *pos2 = buf2; | |
17 | + | |
18 | + for (; length; --length) { | |
19 | + if (*pos1 != *pos2) | |
20 | + return pos1 - pos2; | |
21 | + ++pos1; | |
22 | + ++pos2; | |
23 | + } | |
24 | + return EFI_ST_SUCCESS; | |
25 | +} |