summaryrefslogtreecommitdiffstats
path: root/grub-core/net/drivers/efi/efinet.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/net/drivers/efi/efinet.c')
-rw-r--r--grub-core/net/drivers/efi/efinet.c404
1 files changed, 404 insertions, 0 deletions
diff --git a/grub-core/net/drivers/efi/efinet.c b/grub-core/net/drivers/efi/efinet.c
new file mode 100644
index 0000000..5388f95
--- /dev/null
+++ b/grub-core/net/drivers/efi/efinet.c
@@ -0,0 +1,404 @@
+/*
+ * 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;
+ 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"));
+ /*
+ Some buggy firmware could return an arbitrary address instead of the
+ txbuf address we trasmitted, so just check that txbuf is non NULL
+ for success. This is ok because we open the SNP protocol in
+ exclusive mode so we know we're the only ones transmitting on this
+ box and since we only transmit one packet at a time we know our
+ transmit was successfull.
+ */
+ if (txbuf)
+ {
+ dev->txbusy = 0;
+ break;
+ }
+ 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.
+ */
+ txbuf = NULL;
+ st = efi_call_3 (net->get_status, net, 0, &txbuf);
+ dev->txbusy = !(st == GRUB_EFI_SUCCESS && 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 grub_err_t
+open_card (struct grub_net_card *dev)
+{
+ grub_efi_simple_network_t *net;
+
+ /* Try to reopen SNP exlusively to close any active MNP protocol instance
+ that may compete for packet polling
+ */
+ net = grub_efi_open_protocol (dev->efi_handle, &net_io_guid,
+ GRUB_EFI_OPEN_PROTOCOL_BY_EXCLUSIVE);
+ if (net)
+ {
+ if (net->mode->state == GRUB_EFI_NETWORK_STOPPED
+ && efi_call_1 (net->start, net) != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_NET_NO_CARD, "%s: net start failed",
+ dev->name);
+
+ if (net->mode->state == GRUB_EFI_NETWORK_STOPPED)
+ return grub_error (GRUB_ERR_NET_NO_CARD, "%s: card stopped",
+ dev->name);
+
+ if (net->mode->state == GRUB_EFI_NETWORK_STARTED
+ && efi_call_3 (net->initialize, net, 0, 0) != GRUB_EFI_SUCCESS)
+ return grub_error (GRUB_ERR_NET_NO_CARD, "%s: net initialize failed",
+ dev->name);
+
+ /* Enable hardware receive filters if driver declares support for it.
+ We need unicast and broadcast and additionaly all nodes and
+ solicited multicast for IPv6. Solicited multicast is per-IPv6
+ address and we currently do not have API to do it so simply
+ try to enable receive of all multicast packets or evertyhing in
+ the worst case (i386 PXE driver always enables promiscuous too).
+
+ This does trust firmware to do what it claims to do.
+ */
+ if (net->mode->receive_filter_mask)
+ {
+ grub_uint32_t filters = GRUB_EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
+ GRUB_EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST |
+ GRUB_EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST;
+
+ filters &= net->mode->receive_filter_mask;
+ if (!(filters & GRUB_EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST))
+ filters |= (net->mode->receive_filter_mask &
+ GRUB_EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS);
+
+ efi_call_6 (net->receive_filters, net, filters, 0, 0, 0, NULL);
+ }
+
+ efi_call_4 (grub_efi_system_table->boot_services->close_protocol,
+ dev->efi_net, &net_io_guid,
+ grub_efi_image_handle, dev->efi_handle);
+ dev->efi_net = net;
+ }
+
+ /* If it failed we just try to run as best as we can */
+ return GRUB_ERR_NONE;
+}
+
+static void
+close_card (struct grub_net_card *dev)
+{
+ efi_call_1 (dev->efi_net->shutdown, dev->efi_net);
+ efi_call_1 (dev->efi_net->stop, dev->efi_net);
+ efi_call_4 (grub_efi_system_table->boot_services->close_protocol,
+ dev->efi_net, &net_io_guid,
+ grub_efi_image_handle, dev->efi_handle);
+}
+
+static struct grub_net_card_driver efidriver =
+ {
+ .name = "efinet",
+ .open = open_card,
+ .close = close_card,
+ .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;
+ grub_efi_device_path_t *dp, *parent = NULL, *child = NULL;
+
+ /* EDK2 UEFI PXE driver creates IPv4 and IPv6 messaging devices as
+ children of main MAC messaging device. We only need one device with
+ bound SNP per physical card, otherwise they compete with each other
+ when polling for incoming packets.
+ */
+ dp = grub_efi_get_device_path (*handle);
+ if (!dp)
+ continue;
+ for (; ! GRUB_EFI_END_ENTIRE_DEVICE_PATH (dp); dp = GRUB_EFI_NEXT_DEVICE_PATH (dp))
+ {
+ parent = child;
+ child = dp;
+ }
+ if (child
+ && GRUB_EFI_DEVICE_PATH_TYPE (child) == GRUB_EFI_MESSAGING_DEVICE_PATH_TYPE
+ && (GRUB_EFI_DEVICE_PATH_SUBTYPE (child) == GRUB_EFI_IPV4_DEVICE_PATH_SUBTYPE
+ || GRUB_EFI_DEVICE_PATH_SUBTYPE (child) == GRUB_EFI_IPV6_DEVICE_PATH_SUBTYPE)
+ && parent
+ && GRUB_EFI_DEVICE_PATH_TYPE (parent) == GRUB_EFI_MESSAGING_DEVICE_PATH_TYPE
+ && GRUB_EFI_DEVICE_PATH_SUBTYPE (parent) == GRUB_EFI_MAC_ADDRESS_DEVICE_PATH_SUBTYPE)
+ continue;
+
+ 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)
+ {
+ grub_efi_device_path_t *ldp, *dup_dp, *dup_ldp;
+ int match;
+
+ /* EDK2 UEFI PXE driver creates pseudo devices with type IPv4/IPv6
+ as children of Ethernet card and binds PXE and Load File protocols
+ to it. Loaded Image Device Path protocol will point to these pseudo
+ devices. We skip them when enumerating cards, so here we need to
+ find matching MAC device.
+ */
+ ldp = grub_efi_find_last_device_path (dp);
+ if (GRUB_EFI_DEVICE_PATH_TYPE (ldp) != GRUB_EFI_MESSAGING_DEVICE_PATH_TYPE
+ || (GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) != GRUB_EFI_IPV4_DEVICE_PATH_SUBTYPE
+ && GRUB_EFI_DEVICE_PATH_SUBTYPE (ldp) != GRUB_EFI_IPV6_DEVICE_PATH_SUBTYPE))
+ continue;
+ dup_dp = grub_efi_duplicate_device_path (dp);
+ if (!dup_dp)
+ continue;
+ dup_ldp = grub_efi_find_last_device_path (dup_dp);
+ dup_ldp->type = GRUB_EFI_END_DEVICE_PATH_TYPE;
+ dup_ldp->subtype = GRUB_EFI_END_ENTIRE_DEVICE_PATH_SUBTYPE;
+ dup_ldp->length = sizeof (*dup_ldp);
+ match = grub_efi_compare_device_paths (dup_dp, cdp) == 0;
+ grub_free (dup_dp);
+ if (!match)
+ 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);
+}
+