mirror of
				https://git.proxmox.com/git/grub2
				synced 2025-10-31 08:43:24 +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);
 | |
| }
 | |
| 
 | 
