[ipxe-devel] [PATCH 2/2] virtio-net: reset virtio NICs when booting under UEFI

Laszlo Ersek lersek at redhat.com
Fri Apr 10 19:53:22 UTC 2015


In efi_init() [src/interface/efi/efi_init.c], iPXE registers the
efi_shutdown_hook() function as a callback for the
EVT_SIGNAL_EXIT_BOOT_SERVICES event. This event is emitted under UEFI when
the OS loader (including the Linux EFI stub) calls ExitBootServices().

Currently, that event results in the following call chain:

efi_shutdown_hook()                         [src/interface/efi/efi_init.c]
  shutdown_boot()                           [src/include/ipxe/init.h]
    shutdown(1)                             [src/core/init.c]
      /* Call registered shutdown functions (in reverse order) */
      forall startup_fn:
        startup_fn->shutdown(1)

This infrastructure is fine. However, the virtio-net driver does not
register such a shutdown function at the moment.

Consequently, virtio-net devices remain configured (active) after
ExitBootServices(). If the runtime OS then overwrites the virtio ring area
for whatever purpose, before it loads its own virtio-net driver (which
usually starts by resetting the device), then QEMU tries to interpret
whatever garbage ends up in the ring as virtio communication, and kills
the guest with a message like:

  qemu-system-x86_64: Guest moved used index from 14 to 62857

Add global startup and shutdown hooks for the virtio-net driver. When the
shutdown hook is called outside of the ExitBootServices() callback
context, it does nothing (because the list of open virtio-net NICs is
expected to be empty, due to earlier, controlled teardowns). Otherwise,
all open virtio-net NICs are reset, which causes QEMU to deconfigure (ie.
forget about) the virtio rings.

Cc: Michael Brown <mcb30 at ipxe.org>
Cc: Stefan Hajnoczi <stefanha at redhat.com>
Cc: Gerd Hoffmann <kraxel at redhat.com>
Cc: BALATON Zoltan <balaton at eik.bme.hu>
Signed-off-by: Laszlo Ersek <lersek at redhat.com>
---
 src/drivers/net/virtio-net.c | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/src/drivers/net/virtio-net.c b/src/drivers/net/virtio-net.c
index 533ccb0..63f2196 100644
--- a/src/drivers/net/virtio-net.c
+++ b/src/drivers/net/virtio-net.c
@@ -32,6 +32,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #include <ipxe/ethernet.h>
 #include <ipxe/virtio-ring.h>
 #include <ipxe/virtio-pci.h>
+#include <ipxe/init.h>
 #include "virtio-net.h"
 
 /*
@@ -84,7 +85,18 @@ enum {
 	RX_BUF_SIZE = 1522,
 };
 
+/*
+ * We must reset all virtio-net NICs when exiting. This is particularly
+ * important when running under UEFI, because then we don't get a controlled
+ * teardown sequence, just a callback on ExitBootServices(), and then we must
+ * not even free memory.
+ */
+static struct list_head open_nics;
+
 struct virtnet_nic {
+	/** Entry in open_nics */
+	struct list_head list;
+
 	/** Base pio register address */
 	unsigned long ioaddr;
 
@@ -207,6 +219,9 @@ static int virtnet_open ( struct net_device *netdev ) {
 	features = vp_get_features ( ioaddr );
 	vp_set_features ( ioaddr, features & ( 1 << VIRTIO_NET_F_MAC ) );
 	vp_set_status ( ioaddr, VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_DRIVER_OK );
+
+	/* Add ourselves to the list of open virtnet NICs */
+	list_add ( &virtnet->list, &open_nics );
 	return 0;
 }
 
@@ -231,6 +246,9 @@ static void virtnet_close ( struct net_device *netdev ) {
 	}
 	INIT_LIST_HEAD ( &virtnet->rx_iobufs );
 	virtnet->rx_num_iobufs = 0;
+
+	/* Remove ourselves from the list of open virtnet NICs */
+	list_del ( &virtnet->list );
 }
 
 /** Transmit packet
@@ -417,3 +435,27 @@ struct pci_driver virtnet_driver __pci_driver = {
 	.probe = virtnet_probe,
 	.remove = virtnet_remove,
 };
+
+static void virtnet_startup ( void ) {
+	INIT_LIST_HEAD ( &open_nics );
+}
+
+static void virtnet_shutdown ( int booting ) {
+	struct virtnet_nic *virtnet;
+
+	if ( ! booting ) {
+		assert ( list_empty ( &open_nics ) );
+		return;
+	}
+
+	list_for_each_entry ( virtnet, &open_nics, list ) {
+		DBGC ( virtnet, "VIRTIO-NET %p ioaddr=%#lx "
+		       "resetting for boot\n", virtnet, virtnet->ioaddr);
+		vp_reset ( virtnet->ioaddr );
+	}
+}
+
+struct startup_fn virtnet_startup_fn __startup_fn ( STARTUP_NORMAL ) = {
+	.startup  = virtnet_startup,
+	.shutdown = virtnet_shutdown,
+};
-- 
1.8.3.1




More information about the ipxe-devel mailing list