2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 2010,2011 Free Software Foundation, Inc.
2N/A * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/net/netbuff.h>
2N/A#include <grub/dl.h>
2N/A#include <grub/net.h>
2N/A#include <grub/time.h>
2N/A#include <grub/efi/api.h>
2N/A#include <grub/efi/efi.h>
2N/A
2N/AGRUB_MOD_LICENSE ("GPLv3+");
2N/A
2N/A#ifdef __sun__
2N/A#define MULTI_CARD_UNDI_BROKEN_WORKAROUND
2N/A#define WORKAROUND_BROKEN_INTEL_DRIVERS
2N/A#include <grub/env.h>
2N/A#define DELAY_ENV_VAR "efinet_delay"
2N/A#define DEFAULT_DELAY 1
2N/Astatic unsigned long efinet_receive_delay = DEFAULT_DELAY;
2N/A#endif
2N/A
2N/A/* GUID. */
2N/Astatic grub_efi_guid_t net_io_guid = GRUB_EFI_SIMPLE_NETWORK_GUID;
2N/Astatic grub_efi_guid_t pxe_io_guid = GRUB_EFI_PXE_GUID;
2N/A
2N/A#define EFINET_FILTERS (EFI_SNP_FILTER_UNICAST|EFI_SNP_FILTER_BROADCAST)
2N/A
2N/Astatic grub_err_t
2N/Asend_card_buffer (const struct grub_net_card *dev,
2N/A struct grub_net_buff *pack)
2N/A{
2N/A grub_efi_status_t st;
2N/A grub_efi_simple_network_t *net = dev->efi_net;
2N/A grub_uint64_t limit_time = grub_get_time_ms () + 4000;
2N/A
2N/A if (! net)
2N/A return grub_error (GRUB_ERR_IO, "EFI network not initialized");
2N/A
2N/A st = efi_call_7 (net->transmit, net, 0, (pack->tail - pack->data),
2N/A pack->data, NULL, NULL, NULL);
2N/A if (st != GRUB_EFI_SUCCESS)
2N/A return grub_error (GRUB_ERR_IO, "couldn't send network packet");
2N/A while (1)
2N/A {
2N/A void *txbuf = NULL;
2N/A st = efi_call_3 (net->get_status, net, 0, &txbuf);
2N/A if (st != GRUB_EFI_SUCCESS)
2N/A return grub_error (GRUB_ERR_IO, "couldn't send network packet");
2N/A if (txbuf)
2N/A return GRUB_ERR_NONE;
2N/A if (limit_time < grub_get_time_ms ())
2N/A return grub_error (GRUB_ERR_TIMEOUT, "couldn't send network packet");
2N/A }
2N/A}
2N/A
2N/Astatic struct grub_net_buff *
2N/Aget_card_packet (const struct grub_net_card *dev)
2N/A{
2N/A grub_efi_simple_network_t *net = dev->efi_net;
2N/A grub_err_t err;
2N/A grub_efi_status_t st;
2N/A grub_efi_uintn_t bufsize = 1536;
2N/A struct grub_net_buff *nb;
2N/A
2N/A if (! net)
2N/A return NULL;
2N/A
2N/A#ifdef WORKAROUND_BROKEN_INTEL_DRIVERS
2N/A if (efinet_receive_delay > 0)
2N/A grub_millisleep((grub_uint32_t)efinet_receive_delay);
2N/A#endif
2N/A
2N/A nb = grub_netbuff_alloc (bufsize);
2N/A if (!nb)
2N/A return NULL;
2N/A
2N/A /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible
2N/A by 4. So that IP header is aligned on 4 bytes. */
2N/A grub_netbuff_reserve (nb, 2);
2N/A if (!nb)
2N/A {
2N/A grub_netbuff_free (nb);
2N/A return NULL;
2N/A }
2N/A
2N/A st = efi_call_7 (net->receive, net, NULL, &bufsize,
2N/A nb->data, NULL, NULL, NULL);
2N/A if (st == GRUB_EFI_BUFFER_TOO_SMALL)
2N/A {
2N/A grub_netbuff_free (nb);
2N/A
2N/A bufsize = ALIGN_UP (bufsize, 32);
2N/A
2N/A nb = grub_netbuff_alloc (bufsize);
2N/A if (!nb)
2N/A return NULL;
2N/A
2N/A /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible
2N/A by 4. So that IP header is aligned on 4 bytes. */
2N/A grub_netbuff_reserve (nb, 2);
2N/A if (!nb)
2N/A {
2N/A grub_netbuff_free (nb);
2N/A return NULL;
2N/A }
2N/A
2N/A st = efi_call_7 (net->receive, net, NULL, &bufsize,
2N/A nb->data, NULL, NULL, NULL);
2N/A }
2N/A if (st != GRUB_EFI_SUCCESS)
2N/A {
2N/A grub_netbuff_free (nb);
2N/A return NULL;
2N/A }
2N/A err = grub_netbuff_put (nb, bufsize);
2N/A if (err)
2N/A {
2N/A grub_netbuff_free (nb);
2N/A return NULL;
2N/A }
2N/A
2N/A return nb;
2N/A}
2N/A
2N/Astatic void
2N/Aefinet_close (const struct grub_net_card *dev)
2N/A{
2N/A grub_efi_simple_network_t *net = dev->efi_net;
2N/A
2N/A if (net)
2N/A {
2N/A /* First we call shutdown(), then stop() */
2N/A grub_efi_status_t st = efi_call_1(net->shutdown, net);
2N/A if (st != GRUB_EFI_SUCCESS)
2N/A grub_dprintf("net", "Error calling shutdown() for efinet: 0x%lx\n",
2N/A (long)st);
2N/A st = efi_call_1(net->stop, net);
2N/A if (st != GRUB_EFI_SUCCESS)
2N/A grub_dprintf("net", "Error stop()ing efinet: 0x%lx\n",
2N/A (long)st);
2N/A
2N/A st = efi_call_4(grub_efi_system_table->boot_services->close_protocol,
2N/A dev->efi_net, &net_io_guid, grub_efi_image_handle, dev->efi_handle);
2N/A if (st != GRUB_EFI_SUCCESS)
2N/A grub_dprintf("net", "Error closing protocol handle efnet: 0x%lx\n",
2N/A (long)st);
2N/A else
2N/A ((struct grub_net_card *)dev)->efi_net = 0;
2N/A }
2N/A}
2N/A
2N/Astatic struct grub_net_card_driver efidriver =
2N/A {
2N/A .name = "efinet",
2N/A .close = efinet_close,
2N/A .send = send_card_buffer,
2N/A .recv = get_card_packet
2N/A };
2N/A
2N/A
2N/Astatic int
2N/Asetup_filters(grub_efi_simple_network_t *net, int exclusive)
2N/A{
2N/A grub_efi_status_t st;
2N/A
2N/A /* If the current set of filters on the NIC doesn't match what we
2N/A want (constained by the set of supported filters, of course),
2N/A set them up now. */
2N/A if ((net->mode->receive_filter_setting & EFINET_FILTERS) !=
2N/A (EFINET_FILTERS & net->mode->receive_filter_mask))
2N/A {
2N/A /* If we're not exclusive, OR-in the required filters, just
2N/A in case we're sharing the interface. Of course, this is
2N/A no guarantee that these will be respected when the other
2N/A consumer changes the flags, but this is the best we can
2N/A do for now. */
2N/A grub_uint32_t newfilters = (exclusive ? 0 :
2N/A net->mode->receive_filter_setting) |
2N/A (EFINET_FILTERS & net->mode->receive_filter_mask);
2N/A
2N/A st = efi_call_6(net->receive_filters, net, newfilters,
2N/A net->mode->receive_filter_setting & ~(newfilters),
2N/A 1, /* Reset multicast filters */
2N/A 0, /* No multicast filters included */
2N/A 0);
2N/A
2N/A /* Check if setting the filters failed */
2N/A if (st != GRUB_EFI_SUCCESS ||
2N/A (net->mode->receive_filter_setting & EFINET_FILTERS) !=
2N/A (EFINET_FILTERS & net->mode->receive_filter_mask))
2N/A return -1;
2N/A }
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Astatic void
2N/Agrub_efinet_findcards (void)
2N/A{
2N/A grub_efi_uintn_t num_handles;
2N/A grub_efi_handle_t *handles;
2N/A grub_efi_handle_t *handle;
2N/A int i = 0;
2N/A
2N/A /* Find handles which support the disk io interface. */
2N/A handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &net_io_guid,
2N/A 0, &num_handles);
2N/A if (! handles)
2N/A return;
2N/A for (handle = handles; num_handles--; handle++)
2N/A {
2N/A grub_efi_simple_network_t *net;
2N/A struct grub_net_card *card;
2N/A#ifdef MULTI_CARD_UNDI_BROKEN_WORKAROUND
2N/A struct grub_efi_pxe *pxe;
2N/A volatile struct grub_efi_pxe_mode *pxe_mode;
2N/A#endif
2N/A
2N/A /* We cannot ask for the exclusive handle here, otherwise we'd
2N/A blow away pxe, who may be holding our cached ack that we'll
2N/A retrieve later */
2N/A net = grub_efi_open_protocol (*handle, &net_io_guid,
2N/A GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
2N/A if (! net)
2N/A /* This should not happen... Why? */
2N/A continue;
2N/A
2N/A#ifdef MULTI_CARD_UNDI_BROKEN_WORKAROUND
2N/A pxe = grub_efi_open_protocol (*handle, &pxe_io_guid,
2N/A GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
2N/A if (!pxe)
2N/A continue;
2N/A pxe_mode = pxe->mode;
2N/A if (!pxe_mode->dhcp_ack_recvd)
2N/A continue;
2N/A#endif
2N/A
2N/A if (net->mode->state == GRUB_EFI_NETWORK_STOPPED
2N/A && efi_call_1 (net->start, net) != GRUB_EFI_SUCCESS)
2N/A continue;
2N/A
2N/A if (net->mode->state == GRUB_EFI_NETWORK_STOPPED)
2N/A continue;
2N/A
2N/A if (net->mode->state == GRUB_EFI_NETWORK_STARTED
2N/A && efi_call_3 (net->initialize, net, 0, 0) != GRUB_EFI_SUCCESS)
2N/A continue;
2N/A
2N/A#if !defined(MULTI_CARD_UNDI_BROKEN_WORKAROUND)
2N/A /* Set up receive filters here */
2N/A setup_filters(net, 0);
2N/A#endif
2N/A
2N/A card = grub_zalloc (sizeof (struct grub_net_card));
2N/A if (!card)
2N/A {
2N/A grub_print_error ();
2N/A grub_free (handles);
2N/A return;
2N/A }
2N/A
2N/A card->name = grub_xasprintf ("efinet%d", i++);
2N/A card->driver = &efidriver;
2N/A card->flags = 0;
2N/A card->default_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
2N/A card->mtu = net->mode->max_packet_size;
2N/A grub_memcpy (card->default_address.mac,
2N/A (void *)net->mode->current_address,
2N/A sizeof (card->default_address.mac));
2N/A card->efi_net = net;
2N/A card->efi_handle = *handle;
2N/A
2N/A grub_net_card_register (card);
2N/A#ifdef MULTI_CARD_UNDI_BROKEN_WORKAROUND
2N/A /* We only support one active NIC at a time */
2N/A break;
2N/A#endif
2N/A }
2N/A grub_free (handles);
2N/A}
2N/A
2N/A/*
2N/A * This function assumes that card->efi_net was already initialized to
2N/A * a protocol handle for SNP.
2N/A */
2N/Astatic int
2N/Ainit_efinet(struct grub_net_card *card)
2N/A{
2N/A grub_efi_simple_network_t *net;
2N/A int exclusive;
2N/A
2N/A /* Open the SNP exclusively, otherwise upper layer drivers such as MNP may
2N/A steal our packets before we've had a chance to see them. */
2N/A net = grub_efi_open_protocol_full (card->efi_handle, &net_io_guid,
2N/A grub_efi_image_handle, card->efi_handle,
2N/A GRUB_EFI_OPEN_PROTOCOL_BY_DRIVER|GRUB_EFI_OPEN_PROTOCOL_BY_EXCLUSIVE);
2N/A
2N/A /* If we cannot get exclusive use of the NIC, print a warning, but
2N/A try to continue */
2N/A if (! net)
2N/A {
2N/A grub_printf("WARNING: Could not open efinet exclusively. Packet loss\n");
2N/A grub_printf(" May occur if there is contention with other UEFI\n");
2N/A grub_printf(" drivers (network I/O may be slower).\n");
2N/A net = card->efi_net;
2N/A exclusive = 0;
2N/A }
2N/A else
2N/A {
2N/A card->efi_net = net;
2N/A exclusive = 1;
2N/A
2N/A /* Once we gain exclusive access, the SNP will probably be shut down.
2N/A reinitialize it here. */
2N/A
2N/A if (net->mode->state == GRUB_EFI_NETWORK_STOPPED
2N/A && efi_call_1 (net->start, net) != GRUB_EFI_SUCCESS)
2N/A return -1;
2N/A
2N/A if (net->mode->state == GRUB_EFI_NETWORK_STOPPED)
2N/A return -1;
2N/A
2N/A if (net->mode->state == GRUB_EFI_NETWORK_STARTED
2N/A && efi_call_3 (net->initialize, net, 0, 0) != GRUB_EFI_SUCCESS)
2N/A return -1;
2N/A }
2N/A
2N/A if (setup_filters(net, exclusive) != 0)
2N/A return -1;
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Astatic void
2N/Agrub_efi_net_config_real (grub_efi_handle_t hnd, char **device,
2N/A char **path)
2N/A{
2N/A struct grub_net_card *card;
2N/A grub_efi_device_path_t *dp;
2N/A
2N/A dp = grub_efi_get_device_path (hnd);
2N/A if (! dp)
2N/A return;
2N/A
2N/A FOR_NET_CARDS (card)
2N/A {
2N/A grub_efi_device_path_t *cdp;
2N/A struct grub_efi_pxe *pxe;
2N/A volatile struct grub_efi_pxe_mode *pxe_mode;
2N/A if (card->driver != &efidriver)
2N/A continue;
2N/A cdp = grub_efi_get_device_path (card->efi_handle);
2N/A if (! cdp)
2N/A continue;
2N/A if (grub_efi_compare_device_paths (dp, cdp) != 0)
2N/A continue;
2N/A pxe = grub_efi_open_protocol (hnd, &pxe_io_guid,
2N/A GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
2N/A if (! pxe)
2N/A return;
2N/A
2N/A pxe_mode = pxe->mode;
2N/A grub_net_configure_by_dhcp_ack (card->name, card, 0,
2N/A (struct grub_net_bootp_packet *)
2N/A &pxe_mode->dhcp_ack,
2N/A sizeof (pxe_mode->dhcp_ack),
2N/A 1, device, path);
2N/A
2N/A /* Fully initialize SNP by gaining exclusive access, if possible,
2N/A and setting up the receive filters properly. */
2N/A if (init_efinet(card) != 0)
2N/A grub_printf("Could not initialize the NIC via UEFI\n");
2N/A
2N/A return;
2N/A }
2N/A}
2N/A
2N/A#ifdef WORKAROUND_BROKEN_INTEL_DRIVERS
2N/Astatic char *
2N/Aefinet_change_delay(struct grub_env_var *var __attribute__ ((unused)),
2N/A const char *val)
2N/A{
2N/A unsigned long receive_delay;
2N/A
2N/A if (! val)
2N/A return NULL;
2N/A
2N/A receive_delay = grub_strtoul(val, 0, 0);
2N/A if (receive_delay > 0 && receive_delay <= 1000)
2N/A efinet_receive_delay = receive_delay;
2N/A
2N/A return grub_strdup(val);
2N/A}
2N/A#endif
2N/A
2N/AGRUB_MOD_INIT(efinet)
2N/A{
2N/A grub_efinet_findcards ();
2N/A grub_efi_net_config = grub_efi_net_config_real;
2N/A#ifdef WORKAROUND_BROKEN_INTEL_DRIVERS
2N/A grub_register_variable_hook (DELAY_ENV_VAR, 0, efinet_change_delay);
2N/A#endif
2N/A}
2N/A
2N/AGRUB_MOD_FINI(efinet)
2N/A{
2N/A struct grub_net_card *card, *next;
2N/A#ifdef WORKAROUND_BROKEN_INTEL_DRIVERS
2N/A grub_register_variable_hook (DELAY_ENV_VAR, 0, 0);
2N/A#endif
2N/A
2N/A FOR_NET_CARDS_SAFE (card, next)
2N/A if (card->driver == &efidriver)
2N/A grub_net_card_unregister (card);
2N/A}