uboot/lib/efi_selftest/efi_selftest_snp.c
<<
>>
Prefs
   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
  19/*
  20 * MAC address for broadcasts
  21 */
  22static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
  23
  24struct dhcp_hdr {
  25        u8 op;
  26#define BOOTREQUEST 1
  27#define BOOTREPLY 2
  28        u8 htype;
  29# define HWT_ETHER 1
  30        u8 hlen;
  31# define HWL_ETHER 6
  32        u8 hops;
  33        u32 xid;
  34        u16 secs;
  35        u16 flags;
  36#define DHCP_FLAGS_UNICAST      0x0000
  37#define DHCP_FLAGS_BROADCAST    0x0080
  38        u32 ciaddr;
  39        u32 yiaddr;
  40        u32 siaddr;
  41        u32 giaddr;
  42        u8 chaddr[16];
  43        u8 sname[64];
  44        u8 file[128];
  45};
  46
  47/*
  48 * Message type option.
  49 */
  50#define DHCP_MESSAGE_TYPE       0x35
  51#define DHCPDISCOVER            1
  52#define DHCPOFFER               2
  53#define DHCPREQUEST             3
  54#define DHCPDECLINE             4
  55#define DHCPACK                 5
  56#define DHCPNAK                 6
  57#define DHCPRELEASE             7
  58
  59struct dhcp {
  60        struct ethernet_hdr eth_hdr;
  61        struct ip_udp_hdr ip_udp;
  62        struct dhcp_hdr dhcp_hdr;
  63        u8 opt[128];
  64} __packed;
  65
  66static struct efi_boot_services *boottime;
  67static struct efi_simple_network *net;
  68static struct efi_event *timer;
  69static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_GUID;
  70/* IP packet ID */
  71static unsigned int net_ip_id;
  72
  73/*
  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.
  76 *
  77 * @buf:        IP header
  78 * @len:        length of header in bytes
  79 * @return:     checksum
  80 */
  81static unsigned int efi_ip_checksum(const void *buf, size_t len)
  82{
  83        size_t i;
  84        u32 sum = 0;
  85        const u16 *pos = buf;
  86
  87        for (i = 0; i < len; i += 2)
  88                sum += *pos++;
  89
  90        sum = (sum >> 16) + (sum & 0xffff);
  91        sum += sum >> 16;
  92        sum = ~sum & 0xffff;
  93
  94        return sum;
  95}
  96
  97/*
  98 * Transmit a DHCPDISCOVER message.
  99 */
 100static efi_status_t send_dhcp_discover(void)
 101{
 102        efi_status_t ret;
 103        struct dhcp p = {};
 104
 105        /*
 106         * Fill ethernet header
 107         */
 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,
 110                           ARP_HLEN);
 111        p.eth_hdr.et_protlen = htons(PROT_IP);
 112        /*
 113         * Fill IP header
 114         */
 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);
 124
 125        /*
 126         * Fill UDP header
 127         */
 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));
 133        /*
 134         * Fill DHCP header
 135         */
 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);
 142        /*
 143         * Fill options
 144         */
 145        p.opt[0]        = 0x63; /* DHCP magic cookie */
 146        p.opt[1]        = 0x82;
 147        p.opt[2]        = 0x53;
 148        p.opt[3]        = 0x63;
 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 */
 155        p.opt[10]       = 0x40;
 156        p.opt[11]       = 0xff; /* end of options */
 157
 158        /*
 159         * Transmit DHCPDISCOVER message.
 160         */
 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");
 164        else
 165                efi_st_printf("DHCP Discover\n");
 166        return ret;
 167}
 168
 169/*
 170 * Setup unit test.
 171 *
 172 * Create a 1 s periodic timer.
 173 * Start the network driver.
 174 *
 175 * @handle:     handle of the loaded image
 176 * @systable:   system table
 177 * @return:     EFI_ST_SUCCESS for success
 178 */
 179static int setup(const efi_handle_t handle,
 180                 const struct efi_system_table *systable)
 181{
 182        efi_status_t ret;
 183
 184        boottime = systable->boottime;
 185
 186        /*
 187         * Create a timer event.
 188         */
 189        ret = boottime->create_event(EVT_TIMER, TPL_CALLBACK, NULL, NULL,
 190                                     &timer);
 191        if (ret != EFI_SUCCESS) {
 192                efi_st_error("Failed to create event\n");
 193                return EFI_ST_FAILURE;
 194        }
 195        /*
 196         * Set timer period to 1s.
 197         */
 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;
 202        }
 203        /*
 204         * Find an interface implementing the SNP protocol.
 205         */
 206        ret = boottime->locate_protocol(&efi_net_guid, NULL, (void **)&net);
 207        if (ret != EFI_SUCCESS) {
 208                net = NULL;
 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 */
 258static int execute(void)
 259{
 260        efi_status_t ret;
 261        struct efi_event *events[2];
 262        efi_uintn_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        /* Setup may have failed */
 277        if (!net || !timer) {
 278                efi_st_error("Cannot execute test after setup failure\n");
 279                return EFI_ST_FAILURE;
 280        }
 281
 282        /*
 283         * Send DHCP discover message
 284         */
 285        ret = send_dhcp_discover();
 286        if (ret != EFI_SUCCESS)
 287                return EFI_ST_FAILURE;
 288
 289        /*
 290         * If we would call WaitForEvent only with the WaitForPacket event,
 291         * our code would block until a packet is received which might never
 292         * occur. By calling WaitFor event with both a timer event and the
 293         * WaitForPacket event we can escape this blocking situation.
 294         *
 295         * If the timer event occurs before we have received a DHCP reply
 296         * a further DHCP discover message is sent.
 297         */
 298        events[0] = timer;
 299        events[1] = net->wait_for_packet;
 300        for (;;) {
 301                /*
 302                 * Wait for packet to be received or timer event.
 303                 */
 304                boottime->wait_for_event(2, events, &index);
 305                if (index == 0) {
 306                        /*
 307                         * The timer event occurred. Check for timeout.
 308                         */
 309                        --timeout;
 310                        if (!timeout) {
 311                                efi_st_error("Timeout occurred\n");
 312                                return EFI_ST_FAILURE;
 313                        }
 314                        /*
 315                         * Send further DHCP discover message
 316                         */
 317                        ret = send_dhcp_discover();
 318                        if (ret != EFI_SUCCESS)
 319                                return EFI_ST_FAILURE;
 320                        continue;
 321                }
 322                /*
 323                 * Receive packet
 324                 */
 325                buffer_size = sizeof(buffer);
 326                net->receive(net, NULL, &buffer_size, &buffer,
 327                             &srcaddr, &destaddr, NULL);
 328                if (ret != EFI_SUCCESS) {
 329                        efi_st_error("Failed to receive packet");
 330                        return EFI_ST_FAILURE;
 331                }
 332                /*
 333                 * Check the packet is meant for this system.
 334                 * Unfortunately QEMU ignores the broadcast flag.
 335                 * So we have to check for broadcasts too.
 336                 */
 337                if (efi_st_memcmp(&destaddr, &net->mode->current_address,
 338                                  ARP_HLEN) &&
 339                    efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
 340                        continue;
 341                /*
 342                 * Check this is a DHCP reply
 343                 */
 344                if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
 345                    buffer.p.ip_udp.ip_hl_v != 0x45 ||
 346                    buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
 347                    buffer.p.ip_udp.udp_src != ntohs(67) ||
 348                    buffer.p.ip_udp.udp_dst != ntohs(68) ||
 349                    buffer.p.dhcp_hdr.op != BOOTREPLY)
 350                        continue;
 351                /*
 352                 * We successfully received a DHCP reply.
 353                 */
 354                break;
 355        }
 356
 357        /*
 358         * Write a log message.
 359         */
 360        addr = (u8 *)&buffer.p.ip_udp.ip_src;
 361        efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
 362                      addr[0], addr[1], addr[2], addr[3], &srcaddr);
 363        if (!efi_st_memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
 364                efi_st_printf("as broadcast message.\n");
 365        else
 366                efi_st_printf("as unicast message.\n");
 367
 368        return EFI_ST_SUCCESS;
 369}
 370
 371/*
 372 * Tear down unit test.
 373 *
 374 * Close the timer event created in setup.
 375 * Shut down the network adapter.
 376 *
 377 * @return:     EFI_ST_SUCCESS for success
 378 */
 379static int teardown(void)
 380{
 381        efi_status_t ret;
 382        int exit_status = EFI_ST_SUCCESS;
 383
 384        if (timer) {
 385                /*
 386                 * Stop timer.
 387                 */
 388                ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
 389                if (ret != EFI_SUCCESS) {
 390                        efi_st_error("Failed to stop timer");
 391                        exit_status = EFI_ST_FAILURE;
 392                }
 393                /*
 394                 * Close timer event.
 395                 */
 396                ret = boottime->close_event(timer);
 397                if (ret != EFI_SUCCESS) {
 398                        efi_st_error("Failed to close event");
 399                        exit_status = EFI_ST_FAILURE;
 400                }
 401        }
 402        if (net) {
 403                /*
 404                 * Stop network adapter.
 405                 */
 406                ret = net->stop(net);
 407                if (ret != EFI_SUCCESS) {
 408                        efi_st_error("Failed to stop network adapter\n");
 409                        exit_status = EFI_ST_FAILURE;
 410                }
 411                /*
 412                 * Shut down network adapter.
 413                 */
 414                ret = net->shutdown(net);
 415                if (ret != EFI_SUCCESS) {
 416                        efi_st_error("Failed to shut down network adapter\n");
 417                        exit_status = EFI_ST_FAILURE;
 418                }
 419        }
 420
 421        return exit_status;
 422}
 423
 424EFI_UNIT_TEST(snp) = {
 425        .name = "simple network protocol",
 426        .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
 427        .setup = setup,
 428        .execute = execute,
 429        .teardown = teardown,
 430};
 431