/* * 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 . */ #include #include #include #include #include #include #include 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; void *txbuf; if (dev->txbusy) while (1) { 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")); /* The card may have sent out the packet immediately - set txbusy to 0 in this case. Cases were observed where checking txbuf at the next call of send_card_buffer() is too late: 0 is returned in txbuf and we run in the GRUB_ERR_TIMEOUT case above. Perhaps a timeout in the FW has discarded the recycle buffer. */ st = efi_call_3 (net->get_status, net, 0, &txbuf); dev->txbusy = !(st == GRUB_EFI_SUCCESS && txbuf == dev->txbuf); 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); grub_free (card->txbuf); grub_free (card->rcvbuf); grub_free ((char *)card->name); grub_free (card); } }