| /* |
| * 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; |
| grub_size_t len; |
| |
| 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 (limit_time < grub_get_time_ms ()) |
| return grub_error (GRUB_ERR_TIMEOUT, N_("couldn't send network packet")); |
| } |
| |
| len = (pack->tail - pack->data); |
| if (len > dev->mtu) |
| len = dev->mtu; |
| |
| grub_memcpy (dev->txbuf, pack->data, len); |
| |
| st = efi_call_7 (net->transmit, net, 0, len, |
| 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); |
| } |
| |