mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-11-04 12:55:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			272 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 *  GRUB  --  GRand Unified Bootloader
 | 
						|
 *  Copyright (C) 2010,2011  Free Software Foundation, Inc.
 | 
						|
 *
 | 
						|
 *  GRUB is free software: you can redistribute it and/or modify
 | 
						|
 *  it under the terms of the GNU General Public License as published by
 | 
						|
 *  the Free Software Foundation, either version 3 of the License, or
 | 
						|
 *  (at your option) any later version.
 | 
						|
 *
 | 
						|
 *  GRUB is distributed in the hope that it will be useful,
 | 
						|
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 *  GNU General Public License for more details.
 | 
						|
 *
 | 
						|
 *  You should have received a copy of the GNU General Public License
 | 
						|
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 */
 | 
						|
 | 
						|
#include <grub/net/netbuff.h>
 | 
						|
#include <grub/dl.h>
 | 
						|
#include <grub/net.h>
 | 
						|
#include <grub/time.h>
 | 
						|
#include <grub/efi/api.h>
 | 
						|
#include <grub/efi/efi.h>
 | 
						|
#include <grub/i18n.h>
 | 
						|
 | 
						|
GRUB_MOD_LICENSE ("GPLv3+");
 | 
						|
 | 
						|
/* GUID.  */
 | 
						|
static grub_efi_guid_t net_io_guid = GRUB_EFI_SIMPLE_NETWORK_GUID;
 | 
						|
static grub_efi_guid_t pxe_io_guid = GRUB_EFI_PXE_GUID;
 | 
						|
 | 
						|
static grub_err_t
 | 
						|
send_card_buffer (struct grub_net_card *dev,
 | 
						|
		  struct grub_net_buff *pack)
 | 
						|
{
 | 
						|
  grub_efi_status_t st;
 | 
						|
  grub_efi_simple_network_t *net = dev->efi_net;
 | 
						|
  grub_uint64_t limit_time = grub_get_time_ms () + 4000;
 | 
						|
 | 
						|
  if (dev->txbusy)
 | 
						|
    while (1)
 | 
						|
      {
 | 
						|
	void *txbuf = NULL;
 | 
						|
	st = efi_call_3 (net->get_status, net, 0, &txbuf);
 | 
						|
	if (st != GRUB_EFI_SUCCESS)
 | 
						|
	  return grub_error (GRUB_ERR_IO,
 | 
						|
			     N_("couldn't send network packet"));
 | 
						|
	if (txbuf == dev->txbuf)
 | 
						|
	  {
 | 
						|
	    dev->txbusy = 0;
 | 
						|
	    break;
 | 
						|
	  }
 | 
						|
	if (txbuf)
 | 
						|
	  {
 | 
						|
	    st = efi_call_7 (net->transmit, net, 0, dev->last_pkt_size,
 | 
						|
			     dev->txbuf, NULL, NULL, NULL);
 | 
						|
	    if (st != GRUB_EFI_SUCCESS)
 | 
						|
	      return grub_error (GRUB_ERR_IO,
 | 
						|
				 N_("couldn't send network packet"));
 | 
						|
	  }
 | 
						|
	if (limit_time < grub_get_time_ms ())
 | 
						|
	  return grub_error (GRUB_ERR_TIMEOUT,
 | 
						|
			     N_("couldn't send network packet"));
 | 
						|
      }
 | 
						|
 | 
						|
  dev->last_pkt_size = (pack->tail - pack->data);
 | 
						|
  if (dev->last_pkt_size > dev->mtu)
 | 
						|
    dev->last_pkt_size = dev->mtu;
 | 
						|
 | 
						|
  grub_memcpy (dev->txbuf, pack->data, dev->last_pkt_size);
 | 
						|
 | 
						|
  st = efi_call_7 (net->transmit, net, 0, dev->last_pkt_size,
 | 
						|
		   dev->txbuf, NULL, NULL, NULL);
 | 
						|
  if (st != GRUB_EFI_SUCCESS)
 | 
						|
    return grub_error (GRUB_ERR_IO, N_("couldn't send network packet"));
 | 
						|
  dev->txbusy = 1;
 | 
						|
  return GRUB_ERR_NONE;
 | 
						|
}
 | 
						|
 | 
						|
static struct grub_net_buff *
 | 
						|
get_card_packet (struct grub_net_card *dev)
 | 
						|
{
 | 
						|
  grub_efi_simple_network_t *net = dev->efi_net;
 | 
						|
  grub_err_t err;
 | 
						|
  grub_efi_status_t st;
 | 
						|
  grub_efi_uintn_t bufsize = dev->rcvbufsize;
 | 
						|
  struct grub_net_buff *nb;
 | 
						|
  int i;
 | 
						|
 | 
						|
  for (i = 0; i < 2; i++)
 | 
						|
    {
 | 
						|
      if (!dev->rcvbuf)
 | 
						|
	dev->rcvbuf = grub_malloc (dev->rcvbufsize);
 | 
						|
      if (!dev->rcvbuf)
 | 
						|
	return NULL;
 | 
						|
 | 
						|
      st = efi_call_7 (net->receive, net, NULL, &bufsize,
 | 
						|
		       dev->rcvbuf, NULL, NULL, NULL);
 | 
						|
      if (st != GRUB_EFI_BUFFER_TOO_SMALL)
 | 
						|
	break;
 | 
						|
      dev->rcvbufsize = 2 * ALIGN_UP (dev->rcvbufsize > bufsize
 | 
						|
				      ? dev->rcvbufsize : bufsize, 64);
 | 
						|
      grub_free (dev->rcvbuf);
 | 
						|
      dev->rcvbuf = 0;
 | 
						|
    }
 | 
						|
 | 
						|
  if (st != GRUB_EFI_SUCCESS)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  nb = grub_netbuff_alloc (bufsize + 2);
 | 
						|
  if (!nb)
 | 
						|
    return NULL;
 | 
						|
 | 
						|
  /* Reserve 2 bytes so that 2 + 14/18 bytes of ethernet header is divisible
 | 
						|
     by 4. So that IP header is aligned on 4 bytes. */
 | 
						|
  if (grub_netbuff_reserve (nb, 2))
 | 
						|
    {
 | 
						|
      grub_netbuff_free (nb);
 | 
						|
      return NULL;
 | 
						|
    }
 | 
						|
  grub_memcpy (nb->data, dev->rcvbuf, bufsize);
 | 
						|
  err = grub_netbuff_put (nb, bufsize);
 | 
						|
  if (err)
 | 
						|
    {
 | 
						|
      grub_netbuff_free (nb);
 | 
						|
      return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
  return nb;
 | 
						|
}
 | 
						|
 | 
						|
static struct grub_net_card_driver efidriver =
 | 
						|
  {
 | 
						|
    .name = "efinet",
 | 
						|
    .send = send_card_buffer,
 | 
						|
    .recv = get_card_packet
 | 
						|
  };
 | 
						|
 | 
						|
grub_efi_handle_t
 | 
						|
grub_efinet_get_device_handle (struct grub_net_card *card)
 | 
						|
{
 | 
						|
  if (!card || card->driver != &efidriver)
 | 
						|
    return 0;
 | 
						|
  return card->efi_handle;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
grub_efinet_findcards (void)
 | 
						|
{
 | 
						|
  grub_efi_uintn_t num_handles;
 | 
						|
  grub_efi_handle_t *handles;
 | 
						|
  grub_efi_handle_t *handle;
 | 
						|
  int i = 0;
 | 
						|
 | 
						|
  /* Find handles which support the disk io interface.  */
 | 
						|
  handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, &net_io_guid,
 | 
						|
				    0, &num_handles);
 | 
						|
  if (! handles)
 | 
						|
    return;
 | 
						|
  for (handle = handles; num_handles--; handle++)
 | 
						|
    {
 | 
						|
      grub_efi_simple_network_t *net;
 | 
						|
      struct grub_net_card *card;
 | 
						|
 | 
						|
      net = grub_efi_open_protocol (*handle, &net_io_guid,
 | 
						|
				    GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
 | 
						|
      if (! net)
 | 
						|
	/* This should not happen... Why?  */
 | 
						|
	continue;
 | 
						|
 | 
						|
      if (net->mode->state == GRUB_EFI_NETWORK_STOPPED
 | 
						|
	  && efi_call_1 (net->start, net) != GRUB_EFI_SUCCESS)
 | 
						|
	continue;
 | 
						|
 | 
						|
      if (net->mode->state == GRUB_EFI_NETWORK_STOPPED)
 | 
						|
	continue;
 | 
						|
 | 
						|
      if (net->mode->state == GRUB_EFI_NETWORK_STARTED
 | 
						|
	  && efi_call_3 (net->initialize, net, 0, 0) != GRUB_EFI_SUCCESS)
 | 
						|
	continue;
 | 
						|
 | 
						|
      card = grub_zalloc (sizeof (struct grub_net_card));
 | 
						|
      if (!card)
 | 
						|
	{
 | 
						|
	  grub_print_error ();
 | 
						|
	  grub_free (handles);
 | 
						|
	  return;
 | 
						|
	}
 | 
						|
 | 
						|
      card->mtu = net->mode->max_packet_size;
 | 
						|
      card->txbufsize = ALIGN_UP (card->mtu, 64) + 256;
 | 
						|
      card->txbuf = grub_zalloc (card->txbufsize);
 | 
						|
      if (!card->txbuf)
 | 
						|
	{
 | 
						|
	  grub_print_error ();
 | 
						|
	  grub_free (handles);
 | 
						|
	  grub_free (card);
 | 
						|
	  return;
 | 
						|
	}
 | 
						|
      card->txbusy = 0;
 | 
						|
 | 
						|
      card->rcvbufsize = ALIGN_UP (card->mtu, 64) + 256;
 | 
						|
 | 
						|
      card->name = grub_xasprintf ("efinet%d", i++);
 | 
						|
      card->driver = &efidriver;
 | 
						|
      card->flags = 0;
 | 
						|
      card->default_address.type = GRUB_NET_LINK_LEVEL_PROTOCOL_ETHERNET;
 | 
						|
      grub_memcpy (card->default_address.mac,
 | 
						|
		   net->mode->current_address,
 | 
						|
		   sizeof (card->default_address.mac));
 | 
						|
      card->efi_net = net;
 | 
						|
      card->efi_handle = *handle;
 | 
						|
 | 
						|
      grub_net_card_register (card);
 | 
						|
    }
 | 
						|
  grub_free (handles);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
grub_efi_net_config_real (grub_efi_handle_t hnd, char **device,
 | 
						|
			  char **path)
 | 
						|
{
 | 
						|
  struct grub_net_card *card;
 | 
						|
  grub_efi_device_path_t *dp;
 | 
						|
 | 
						|
  dp = grub_efi_get_device_path (hnd);
 | 
						|
  if (! dp)
 | 
						|
    return;
 | 
						|
 | 
						|
  FOR_NET_CARDS (card)
 | 
						|
  {
 | 
						|
    grub_efi_device_path_t *cdp;
 | 
						|
    struct grub_efi_pxe *pxe;
 | 
						|
    struct grub_efi_pxe_mode *pxe_mode;
 | 
						|
    if (card->driver != &efidriver)
 | 
						|
      continue;
 | 
						|
    cdp = grub_efi_get_device_path (card->efi_handle);
 | 
						|
    if (! cdp)
 | 
						|
      continue;
 | 
						|
    if (grub_efi_compare_device_paths (dp, cdp) != 0)
 | 
						|
      continue;
 | 
						|
    pxe = grub_efi_open_protocol (hnd, &pxe_io_guid,
 | 
						|
				  GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL);
 | 
						|
    if (! pxe)
 | 
						|
      continue;
 | 
						|
    pxe_mode = pxe->mode;
 | 
						|
    grub_net_configure_by_dhcp_ack (card->name, card, 0,
 | 
						|
				    (struct grub_net_bootp_packet *)
 | 
						|
				    &pxe_mode->dhcp_ack,
 | 
						|
				    sizeof (pxe_mode->dhcp_ack),
 | 
						|
				    1, device, path);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
GRUB_MOD_INIT(efinet)
 | 
						|
{
 | 
						|
  grub_efinet_findcards ();
 | 
						|
  grub_efi_net_config = grub_efi_net_config_real;
 | 
						|
}
 | 
						|
 | 
						|
GRUB_MOD_FINI(efinet)
 | 
						|
{
 | 
						|
  struct grub_net_card *card, *next;
 | 
						|
 | 
						|
  FOR_NET_CARDS_SAFE (card, next) 
 | 
						|
    if (card->driver == &efidriver)
 | 
						|
      grub_net_card_unregister (card);
 | 
						|
}
 | 
						|
 |