Merge tag 'efi-2019-10-rc4-3' of https://gitlab.denx.de/u-boot/custodians/u-boot-efi
[oweals/u-boot.git] / lib / efi_loader / efi_net.c
index 825e064f9aa83f99dd051c72ae836d21aa11ee9d..82d2595847a8cf0f86f18f6ef8f196d01c33a7d6 100644 (file)
@@ -1,8 +1,18 @@
 // SPDX-License-Identifier: GPL-2.0+
 /*
- *  EFI application network access support
+ * Simple network protocol
+ * PXE base code protocol
  *
- *  Copyright (c) 2016 Alexander Graf
+ * Copyright (c) 2016 Alexander Graf
+ *
+ * The simple network protocol has the following statuses and services
+ * to move between them:
+ *
+ * Start():     EfiSimpleNetworkStopped     -> EfiSimpleNetworkStarted
+ * Initialize(): EfiSimpleNetworkStarted     -> EfiSimpleNetworkInitialized
+ * Shutdown():  EfiSimpleNetworkInitialized -> EfiSimpleNetworkStarted
+ * Stop():      EfiSimpleNetworkStarted     -> EfiSimpleNetworkStopped
+ * Reset():     EfiSimpleNetworkInitialized -> EfiSimpleNetworkInitialized
  */
 
 #include <common.h>
@@ -66,10 +76,13 @@ static efi_status_t EFIAPI efi_net_start(struct efi_simple_network *this)
                goto out;
        }
 
-       if (this->mode->state != EFI_NETWORK_STOPPED)
+       if (this->mode->state != EFI_NETWORK_STOPPED) {
                ret = EFI_ALREADY_STARTED;
-       else
+       } else {
+               this->int_status = 0;
+               wait_for_packet->is_signaled = false;
                this->mode->state = EFI_NETWORK_STARTED;
+       }
 out:
        return EFI_EXIT(ret);
 }
@@ -96,10 +109,13 @@ static efi_status_t EFIAPI efi_net_stop(struct efi_simple_network *this)
                goto out;
        }
 
-       if (this->mode->state == EFI_NETWORK_STOPPED)
+       if (this->mode->state == EFI_NETWORK_STOPPED) {
                ret = EFI_NOT_STARTED;
-       else
+       } else {
+               /* Disable hardware and put it into the reset state */
+               eth_halt();
                this->mode->state = EFI_NETWORK_STOPPED;
+       }
 out:
        return EFI_EXIT(ret);
 }
@@ -130,6 +146,15 @@ static efi_status_t EFIAPI efi_net_initialize(struct efi_simple_network *this,
                goto out;
        }
 
+       switch (this->mode->state) {
+       case EFI_NETWORK_INITIALIZED:
+       case EFI_NETWORK_STARTED:
+               break;
+       default:
+               r = EFI_NOT_STARTED;
+               goto out;
+       }
+
        /* Setup packet buffers */
        net_init();
        /* Disable hardware and put it into the reset state */
@@ -144,6 +169,8 @@ static efi_status_t EFIAPI efi_net_initialize(struct efi_simple_network *this,
                r = EFI_DEVICE_ERROR;
                goto out;
        } else {
+               this->int_status = 0;
+               wait_for_packet->is_signaled = false;
                this->mode->state = EFI_NETWORK_INITIALIZED;
        }
 out:
@@ -164,9 +191,31 @@ out:
 static efi_status_t EFIAPI efi_net_reset(struct efi_simple_network *this,
                                         int extended_verification)
 {
+       efi_status_t ret;
+
        EFI_ENTRY("%p, %x", this, extended_verification);
 
-       return EFI_EXIT(EFI_CALL(efi_net_initialize(this, 0, 0)));
+       /* Check parameters */
+       if (!this) {
+               ret = EFI_INVALID_PARAMETER;
+               goto out;
+       }
+
+       switch (this->mode->state) {
+       case EFI_NETWORK_INITIALIZED:
+               break;
+       case EFI_NETWORK_STOPPED:
+               ret = EFI_NOT_STARTED;
+               goto out;
+       default:
+               ret = EFI_DEVICE_ERROR;
+               goto out;
+       }
+
+       this->mode->state = EFI_NETWORK_STARTED;
+       ret = EFI_CALL(efi_net_initialize(this, 0, 0));
+out:
+       return EFI_EXIT(ret);
 }
 
 /*
@@ -191,8 +240,21 @@ static efi_status_t EFIAPI efi_net_shutdown(struct efi_simple_network *this)
                goto out;
        }
 
+       switch (this->mode->state) {
+       case EFI_NETWORK_INITIALIZED:
+               break;
+       case EFI_NETWORK_STOPPED:
+               ret = EFI_NOT_STARTED;
+               goto out;
+       default:
+               ret = EFI_DEVICE_ERROR;
+               goto out;
+       }
+
        eth_halt();
-       this->mode->state = EFI_NETWORK_STOPPED;
+       this->int_status = 0;
+       wait_for_packet->is_signaled = false;
+       this->mode->state = EFI_NETWORK_STARTED;
 
 out:
        return EFI_EXIT(ret);
@@ -270,7 +332,7 @@ static efi_status_t EFIAPI efi_net_statistics(struct efi_simple_network *this,
 /*
  * efi_net_mcastiptomac() - translate multicast IP address to MAC address
  *
- * This function implements the Statistics service of the
+ * This function implements the MCastIPtoMAC service of the
  * EFI_SIMPLE_NETWORK_PROTOCOL. See the Unified Extensible Firmware Interface
  * (UEFI) specification for details.
  *
@@ -285,9 +347,49 @@ static efi_status_t EFIAPI efi_net_mcastiptomac(struct efi_simple_network *this,
                                                struct efi_ip_address *ip,
                                                struct efi_mac_address *mac)
 {
+       efi_status_t ret = EFI_SUCCESS;
+
        EFI_ENTRY("%p, %x, %p, %p", this, ipv6, ip, mac);
 
-       return EFI_EXIT(EFI_INVALID_PARAMETER);
+       if (!this || !ip || !mac) {
+               ret = EFI_INVALID_PARAMETER;
+               goto out;
+       }
+
+       if (ipv6) {
+               ret = EFI_UNSUPPORTED;
+               goto out;
+       }
+
+       /* Multi-cast addresses are in the range 224.0.0.0 - 239.255.255.255 */
+       if ((ip->ip_addr[0] & 0xf0) != 0xe0) {
+               ret = EFI_INVALID_PARAMETER;
+               goto out;
+       };
+
+       switch (this->mode->state) {
+       case EFI_NETWORK_INITIALIZED:
+       case EFI_NETWORK_STARTED:
+               break;
+       default:
+               ret = EFI_NOT_STARTED;
+               goto out;
+       }
+
+       memset(mac, 0, sizeof(struct efi_mac_address));
+
+       /*
+        * Copy lower 23 bits of IPv4 multi-cast address
+        * RFC 1112, RFC 7042 2.1.1.
+        */
+       mac->mac_addr[0] = 0x01;
+       mac->mac_addr[1] = 0x00;
+       mac->mac_addr[2] = 0x5E;
+       mac->mac_addr[3] = ip->ip_addr[1] & 0x7F;
+       mac->mac_addr[4] = ip->ip_addr[2];
+       mac->mac_addr[5] = ip->ip_addr[3];
+out:
+       return EFI_EXIT(ret);
 }
 
 /**
@@ -297,7 +399,7 @@ static efi_status_t EFIAPI efi_net_mcastiptomac(struct efi_simple_network *this,
  * Protocol. See the UEFI spec for details.
  *
  * @this:              the instance of the Simple Network Protocol
- * @readwrite:         true for read, false for write
+ * @read_write:                true for read, false for write
  * @offset:            offset in NVRAM
  * @buffer_size:       size of buffer
  * @buffer:            buffer
@@ -350,10 +452,8 @@ static efi_status_t EFIAPI efi_net_get_status(struct efi_simple_network *this,
        }
 
        if (int_status) {
-               /* We send packets synchronously, so nothing is outstanding */
-               *int_status = EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT;
-               if (new_rx_packet)
-                       *int_status |= EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT;
+               *int_status = this->int_status;
+               this->int_status = 0;
        }
        if (txbuf)
                *txbuf = new_tx_packet;
@@ -404,15 +504,33 @@ static efi_status_t EFIAPI efi_net_transmit
                goto out;
        }
 
-       if (header_size) {
-               /*
-                * TODO: We would need to create the header
-                * if header_size != 0
-                */
-               ret = EFI_UNSUPPORTED;
+       /* At least the IP header has to fit into the buffer */
+       if (buffer_size < this->mode->media_header_size) {
+               ret = EFI_BUFFER_TOO_SMALL;
                goto out;
        }
 
+       /*
+        * TODO:
+        * Support VLANs. Use net_set_ether() for copying the header. Use a
+        * U_BOOT_ENV_CALLBACK to update the media header size.
+        */
+       if (header_size) {
+               struct ethernet_hdr *header = buffer;
+
+               if (!dest_addr || !protocol ||
+                   header_size != this->mode->media_header_size) {
+                       ret = EFI_INVALID_PARAMETER;
+                       goto out;
+               }
+               if (!src_addr)
+                       src_addr = &this->mode->current_address;
+
+               memcpy(header->et_dest, dest_addr, ARP_HLEN);
+               memcpy(header->et_src, src_addr, ARP_HLEN);
+               header->et_protlen = htons(*protocol);
+       }
+
        switch (this->mode->state) {
        case EFI_NETWORK_STOPPED:
                ret = EFI_NOT_STARTED;
@@ -429,7 +547,7 @@ static efi_status_t EFIAPI efi_net_transmit
        net_send_packet(transmit_buffer, buffer_size);
 
        new_tx_packet = buffer;
-
+       this->int_status |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT;
 out:
        return EFI_EXIT(ret);
 }
@@ -487,12 +605,6 @@ static efi_status_t EFIAPI efi_net_receive
                ret = EFI_NOT_READY;
                goto out;
        }
-       /* Check that we at least received an Ethernet header */
-       if (net_rx_packet_len < sizeof(struct ethernet_hdr)) {
-               new_rx_packet = false;
-               ret = EFI_NOT_READY;
-               goto out;
-       }
        /* Fill export parameters */
        eth_hdr = (struct ethernet_hdr *)net_rx_packet;
        protlen = ntohs(eth_hdr->et_protlen);
@@ -517,7 +629,8 @@ static efi_status_t EFIAPI efi_net_receive
        /* Copy packet */
        memcpy(buffer, net_rx_packet, net_rx_packet_len);
        *buffer_size = net_rx_packet_len;
-       new_rx_packet = false;
+       new_rx_packet = 0;
+       this->int_status &= ~EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT;
 out:
        return EFI_EXIT(ret);
 }
@@ -526,6 +639,9 @@ out:
  * efi_net_set_dhcp_ack() - take note of a selected DHCP IP address
  *
  * This function is called by dhcp_handler().
+ *
+ * @pkt:       packet received by dhcp_handler()
+ * @len:       length of the packet received
  */
 void efi_net_set_dhcp_ack(void *pkt, int len)
 {
@@ -548,7 +664,6 @@ void efi_net_set_dhcp_ack(void *pkt, int len)
 static void efi_net_push(void *pkt, int len)
 {
        new_rx_packet = true;
-       wait_for_packet->is_signaled = true;
 }
 
 /**
@@ -556,8 +671,8 @@ static void efi_net_push(void *pkt, int len)
  *
  * This notification function is called in every timer cycle.
  *
- * @event      the event for which this notification function is registered
- * @context    event context - not used in this function
+ * @event:     the event for which this notification function is registered
+ * @context:   event context - not used in this function
  */
 static void EFIAPI efi_network_timer_notify(struct efi_event *event,
                                            void *context)
@@ -577,6 +692,17 @@ static void EFIAPI efi_network_timer_notify(struct efi_event *event,
                push_packet = efi_net_push;
                eth_rx();
                push_packet = NULL;
+               if (new_rx_packet) {
+                       /* Check that we at least received an Ethernet header */
+                       if (net_rx_packet_len >=
+                           sizeof(struct ethernet_hdr)) {
+                               this->int_status |=
+                                       EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT;
+                               wait_for_packet->is_signaled = true;
+                       } else {
+                               new_rx_packet = 0;
+                       }
+               }
        }
 out:
        EFI_EXIT(EFI_SUCCESS);
@@ -751,9 +877,10 @@ efi_status_t efi_net_register(void)
        netobj->net.transmit = efi_net_transmit;
        netobj->net.receive = efi_net_receive;
        netobj->net.mode = &netobj->net_mode;
-       netobj->net_mode.state = EFI_NETWORK_STARTED;
+       netobj->net_mode.state = EFI_NETWORK_STOPPED;
        memcpy(netobj->net_mode.current_address.mac_addr, eth_get_ethaddr(), 6);
        netobj->net_mode.hwaddr_size = ARP_HLEN;
+       netobj->net_mode.media_header_size = ETHER_HDR_SIZE;
        netobj->net_mode.max_packet_size = PKTSIZE;
        netobj->net_mode.if_type = ARP_ETHER;