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#include <net.h>
  19
  20/*
  21 * MAC address for broadcasts
  22 */
  23static const u8 BROADCAST_MAC[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
  24
  25struct 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
  60struct 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
  67static struct efi_boot_services *boottime;
  68static struct efi_simple_network *net;
  69static struct efi_event *timer;
  70static const efi_guid_t efi_net_guid = EFI_SIMPLE_NETWORK_PROTOCOL_GUID;
  71/* IP packet ID */
  72static 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 */
  82static 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 */
 101static 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 */
 180static 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 */
 287static 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        /*
 313         * Send DHCP discover message
 314         */
 315        ret = send_dhcp_discover();
 316        if (ret != EFI_SUCCESS)
 317                return EFI_ST_FAILURE;
 318
 319        /*
 320         * If we would call WaitForEvent only with the WaitForPacket event,
 321         * our code would block until a packet is received which might never
 322         * occur. By calling WaitFor event with both a timer event and the
 323         * WaitForPacket event we can escape this blocking situation.
 324         *
 325         * If the timer event occurs before we have received a DHCP reply
 326         * a further DHCP discover message is sent.
 327         */
 328        events[0] = timer;
 329        events[1] = net->wait_for_packet;
 330        for (;;) {
 331                u32 int_status;
 332
 333                /*
 334                 * Wait for packet to be received or timer event.
 335                 */
 336                boottime->wait_for_event(2, events, &index);
 337                if (index == 0) {
 338                        /*
 339                         * The timer event occurred. Check for timeout.
 340                         */
 341                        --timeout;
 342                        if (!timeout) {
 343                                efi_st_error("Timeout occurred\n");
 344                                return EFI_ST_FAILURE;
 345                        }
 346                        /*
 347                         * Send further DHCP discover message
 348                         */
 349                        ret = send_dhcp_discover();
 350                        if (ret != EFI_SUCCESS)
 351                                return EFI_ST_FAILURE;
 352                        continue;
 353                }
 354                /*
 355                 * Receive packet
 356                 */
 357                buffer_size = sizeof(buffer);
 358                ret = net->get_status(net, &int_status, NULL);
 359                if (ret != EFI_SUCCESS) {
 360                        efi_st_error("Failed to get status");
 361                        return EFI_ST_FAILURE;
 362                }
 363                if (!(int_status & EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT)) {
 364                        efi_st_error("RX interrupt not set");
 365                        return EFI_ST_FAILURE;
 366                }
 367                ret = net->receive(net, NULL, &buffer_size, &buffer,
 368                                   &srcaddr, &destaddr, NULL);
 369                if (ret != EFI_SUCCESS) {
 370                        efi_st_error("Failed to receive packet");
 371                        return EFI_ST_FAILURE;
 372                }
 373                /*
 374                 * Check the packet is meant for this system.
 375                 * Unfortunately QEMU ignores the broadcast flag.
 376                 * So we have to check for broadcasts too.
 377                 */
 378                if (memcmp(&destaddr, &net->mode->current_address, ARP_HLEN) &&
 379                    memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
 380                        continue;
 381                /*
 382                 * Check this is a DHCP reply
 383                 */
 384                if (buffer.p.eth_hdr.et_protlen != ntohs(PROT_IP) ||
 385                    buffer.p.ip_udp.ip_hl_v != 0x45 ||
 386                    buffer.p.ip_udp.ip_p != IPPROTO_UDP ||
 387                    buffer.p.ip_udp.udp_src != ntohs(67) ||
 388                    buffer.p.ip_udp.udp_dst != ntohs(68) ||
 389                    buffer.p.dhcp_hdr.op != BOOTREPLY)
 390                        continue;
 391                /*
 392                 * We successfully received a DHCP reply.
 393                 */
 394                break;
 395        }
 396
 397        /*
 398         * Write a log message.
 399         */
 400        addr = (u8 *)&buffer.p.ip_udp.ip_src;
 401        efi_st_printf("DHCP reply received from %u.%u.%u.%u (%pm) ",
 402                      addr[0], addr[1], addr[2], addr[3], &srcaddr);
 403        if (!memcmp(&destaddr, BROADCAST_MAC, ARP_HLEN))
 404                efi_st_printf("as broadcast message.\n");
 405        else
 406                efi_st_printf("as unicast message.\n");
 407
 408        return EFI_ST_SUCCESS;
 409}
 410
 411/*
 412 * Tear down unit test.
 413 *
 414 * Close the timer event created in setup.
 415 * Shut down the network adapter.
 416 *
 417 * @return:     EFI_ST_SUCCESS for success
 418 */
 419static int teardown(void)
 420{
 421        efi_status_t ret;
 422        int exit_status = EFI_ST_SUCCESS;
 423
 424        if (timer) {
 425                /*
 426                 * Stop timer.
 427                 */
 428                ret = boottime->set_timer(timer, EFI_TIMER_STOP, 0);
 429                if (ret != EFI_SUCCESS) {
 430                        efi_st_error("Failed to stop timer");
 431                        exit_status = EFI_ST_FAILURE;
 432                }
 433                /*
 434                 * Close timer event.
 435                 */
 436                ret = boottime->close_event(timer);
 437                if (ret != EFI_SUCCESS) {
 438                        efi_st_error("Failed to close event");
 439                        exit_status = EFI_ST_FAILURE;
 440                }
 441        }
 442        if (net) {
 443                /*
 444                 * Shut down network adapter.
 445                 */
 446                ret = net->shutdown(net);
 447                if (ret != EFI_SUCCESS) {
 448                        efi_st_error("Failed to shut down network adapter\n");
 449                        exit_status = EFI_ST_FAILURE;
 450                }
 451                if (net->mode->state != EFI_NETWORK_STARTED) {
 452                        efi_st_error("Failed to shutdown network adapter\n");
 453                        return EFI_ST_FAILURE;
 454                }
 455                /*
 456                 * Stop network adapter.
 457                 */
 458                ret = net->stop(net);
 459                if (ret != EFI_SUCCESS) {
 460                        efi_st_error("Failed to stop network adapter\n");
 461                        exit_status = EFI_ST_FAILURE;
 462                }
 463                if (net->mode->state != EFI_NETWORK_STOPPED) {
 464                        efi_st_error("Failed to stop network adapter\n");
 465                        return EFI_ST_FAILURE;
 466                }
 467        }
 468
 469        return exit_status;
 470}
 471
 472EFI_UNIT_TEST(snp) = {
 473        .name = "simple network protocol",
 474        .phase = EFI_EXECUTE_BEFORE_BOOTTIME_EXIT,
 475        .setup = setup,
 476        .execute = execute,
 477        .teardown = teardown,
 478#ifdef CONFIG_SANDBOX
 479        /*
 480         * Running this test on the sandbox requires setting environment
 481         * variable ethact to a network interface connected to a DHCP server and
 482         * ethrotate to 'no'.
 483         */
 484        .on_request = true,
 485#endif
 486};
 487