Prepare v2023.10
[platform/kernel/u-boot.git] / lib / efi_selftest / efi_selftest_snp.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * efi_selftest_snp
4  *
5  * Copyright (c) 2017 Heinrich Schuchardt <xypron.glpk@gmx.de>
6  *
7  * This unit test covers the Simple Network Protocol as well as
8  * the CopyMem and SetMem boottime services.
9  *
10  * A DHCP discover message is sent. The test is successful if a
11  * DHCP reply is received.
12  *
13  * TODO: Once ConnectController and DisconnectController are implemented
14  *       we should connect our code as controller.
15  */
16
17 #include <efi_selftest.h>
18 #include <net.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_PROTOCOL_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 set timer\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                 net = NULL;
210                 efi_st_error("Failed to locate simple network protocol\n");
211                 return EFI_ST_FAILURE;
212         }
213         /*
214          * Check hardware address size.
215          */
216         if (!net->mode) {
217                 efi_st_error("Mode not provided\n");
218                 return EFI_ST_FAILURE;
219         }
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;
224         }
225         /*
226          * Check that WaitForPacket event exists.
227          */
228         if (!net->wait_for_packet) {
229                 efi_st_error("WaitForPacket event missing\n");
230                 return EFI_ST_FAILURE;
231         }
232         if (net->mode->state == EFI_NETWORK_INITIALIZED) {
233                 /*
234                  * Shut down network adapter.
235                  */
236                 ret = net->shutdown(net);
237                 if (ret != EFI_SUCCESS) {
238                         efi_st_error("Failed to shut down network adapter\n");
239                         return EFI_ST_FAILURE;
240                 }
241         }
242         if (net->mode->state == EFI_NETWORK_STARTED) {
243                 /*
244                  * Stop network adapter.
245                  */
246                 ret = net->stop(net);
247                 if (ret != EFI_SUCCESS) {
248                         efi_st_error("Failed to stop network adapter\n");
249                         return EFI_ST_FAILURE;
250                 }
251         }
252         /*
253          * Start network adapter.
254          */
255         ret = net->start(net);
256         if (ret != EFI_SUCCESS && ret != EFI_ALREADY_STARTED) {
257                 efi_st_error("Failed to start network adapter\n");
258                 return EFI_ST_FAILURE;
259         }
260         if (net->mode->state != EFI_NETWORK_STARTED) {
261                 efi_st_error("Failed to start network adapter\n");
262                 return EFI_ST_FAILURE;
263         }
264         /*
265          * Initialize network adapter.
266          */
267         ret = net->initialize(net, 0, 0);
268         if (ret != EFI_SUCCESS) {
269                 efi_st_error("Failed to initialize network adapter\n");
270                 return EFI_ST_FAILURE;
271         }
272         if (net->mode->state != EFI_NETWORK_INITIALIZED) {
273                 efi_st_error("Failed to initialize network adapter\n");
274                 return EFI_ST_FAILURE;
275         }
276         return EFI_ST_SUCCESS;
277 }
278
279 /*
280  * Execute unit test.
281  *
282  * A DHCP discover message is sent. The test is successful if a
283  * DHCP reply is received within 10 seconds.
284  *
285  * Return:      EFI_ST_SUCCESS for success
286  */
287 static int execute(void)
288 {
289         efi_status_t ret;
290         struct efi_event *events[2];
291         efi_uintn_t index;
292         union {
293                 struct dhcp p;
294                 u8 b[PKTSIZE];
295         } buffer;
296         struct efi_mac_address srcaddr;
297         struct efi_mac_address destaddr;
298         size_t buffer_size;
299         u8 *addr;
300
301         /*
302          * The timeout is to occur after 10 s.
303          */
304         unsigned int timeout = 10;
305
306         /* Setup may have failed */
307         if (!net || !timer) {
308                 efi_st_error("Cannot execute test after setup failure\n");
309                 return EFI_ST_FAILURE;
310         }
311
312         /* Check media connected */
313         ret = net->get_status(net, NULL, NULL);
314         if (ret != EFI_SUCCESS) {
315                 efi_st_error("Failed to get status");
316                 return EFI_ST_FAILURE;
317         }
318         if (net->mode && net->mode->media_present_supported &&
319             !net->mode->media_present) {
320                 efi_st_error("Network media is not connected");
321                 return EFI_ST_FAILURE;
322         }
323
324         /*
325          * Send DHCP discover message
326          */
327         ret = send_dhcp_discover();
328         if (ret != EFI_SUCCESS)
329                 return EFI_ST_FAILURE;
330
331         /*
332          * If we would call WaitForEvent only with the WaitForPacket event,
333          * our code would block until a packet is received which might never
334          * occur. By calling WaitFor event with both a timer event and the
335          * WaitForPacket event we can escape this blocking situation.
336          *
337          * If the timer event occurs before we have received a DHCP reply
338          * a further DHCP discover message is sent.
339          */
340         events[0] = timer;
341         events[1] = net->wait_for_packet;
342         for (;;) {
343                 /*
344                  * Wait for packet to be received or timer event.
345                  */
346                 boottime->wait_for_event(2, events, &index);
347                 if (index == 0) {
348                         /*
349                          * The timer event occurred. Check for timeout.
350                          */
351                         --timeout;
352                         if (!timeout) {
353                                 efi_st_error("Timeout occurred\n");
354                                 return EFI_ST_FAILURE;
355                         }
356                         /*
357                          * Send further DHCP discover message
358                          */
359                         ret = send_dhcp_discover();
360                         if (ret != EFI_SUCCESS)
361                                 return EFI_ST_FAILURE;
362                         continue;
363                 }
364                 /*
365                  * Receive packets until buffer is empty
366                  */
367                 for (;;) {
368                         buffer_size = sizeof(buffer);
369                         ret = net->receive(net, NULL, &buffer_size, &buffer,
370                                            &srcaddr, &destaddr, NULL);
371                         if (ret == EFI_NOT_READY) {
372                                 /* The received buffer is empty. */
373                                 break;
374                         }
375
376                         if (ret != EFI_SUCCESS) {
377                                 efi_st_error("Failed to receive packet");
378                                 return EFI_ST_FAILURE;
379                         }
380                         /*
381                          * Check the packet is meant for this system.
382                          * Unfortunately QEMU ignores the broadcast flag.
383                          * So we have to check for broadcasts too.
384                          */
385                         if (memcmp(&destaddr, &net->mode->current_address, ARP_HLEN) &&
386                             memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
387                                 continue;
388                         /*
389                          * Check this is a DHCP reply
390                          */
391                         if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
392                             buffer.p.ip_udp.ip_hl_v != 0x45 ||
393                             buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
394                             buffer.p.ip_udp.udp_src != ntohs(67) ||
395                             buffer.p.ip_udp.udp_dst != ntohs(68) ||
396                             buffer.p.dhcp_hdr.op != BOOTREPLY)
397                                 continue;
398                         /*
399                          * We successfully received a DHCP reply.
400                          */
401                         goto received;
402                 }
403         }
404 received:
405         /*
406          * Write a log message.
407          */
408         addr = (u8 *)&buffer.p.ip_udp.ip_src;
409         efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
410                       addr[0], addr[1], addr[2], addr[3], &srcaddr);
411         if (!memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
412                 efi_st_printf("as broadcast message.\n");
413         else
414                 efi_st_printf("as unicast message.\n");
415
416         return EFI_ST_SUCCESS;
417 }
418
419 /*
420  * Tear down unit test.
421  *
422  * Close the timer event created in setup.
423  * Shut down the network adapter.
424  *
425  * Return:      EFI_ST_SUCCESS for success
426  */
427 static int teardown(void)
428 {
429         efi_status_t ret;
430         int exit_status = EFI_ST_SUCCESS;
431
432         if (timer) {
433                 /*
434                  * Stop timer.
435                  */
436                 ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
437                 if (ret != EFI_SUCCESS) {
438                         efi_st_error("Failed to stop timer");
439                         exit_status = EFI_ST_FAILURE;
440                 }
441                 /*
442                  * Close timer event.
443                  */
444                 ret = boottime->close_event(timer);
445                 if (ret != EFI_SUCCESS) {
446                         efi_st_error("Failed to close event");
447                         exit_status = EFI_ST_FAILURE;
448                 }
449         }
450         if (net) {
451                 /*
452                  * Shut down network adapter.
453                  */
454                 ret = net->shutdown(net);
455                 if (ret != EFI_SUCCESS) {
456                         efi_st_error("Failed to shut down network adapter\n");
457                         exit_status = EFI_ST_FAILURE;
458                 }
459                 if (net->mode->state != EFI_NETWORK_STARTED) {
460                         efi_st_error("Failed to shutdown network adapter\n");
461                         return EFI_ST_FAILURE;
462                 }
463                 /*
464                  * Stop network adapter.
465                  */
466                 ret = net->stop(net);
467                 if (ret != EFI_SUCCESS) {
468                         efi_st_error("Failed to stop network adapter\n");
469                         exit_status = EFI_ST_FAILURE;
470                 }
471                 if (net->mode->state != EFI_NETWORK_STOPPED) {
472                         efi_st_error("Failed to stop network adapter\n");
473                         return EFI_ST_FAILURE;
474                 }
475         }
476
477         return exit_status;
478 }
479
480 EFI_UNIT_TEST(snp) = {
481         .name = "simple network protocol",
482         .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
483         .setup = setup,
484         .execute = execute,
485         .teardown = teardown,
486 #ifdef CONFIG_SANDBOX
487         /*
488          * Running this test on the sandbox requires setting environment
489          * variable ethact to a network interface connected to a DHCP server and
490          * ethrotate to 'no'.
491          */
492         .on_request = true,
493 #endif
494 };