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 <[email protected]>
Cc: Stefan Hajnoczi <[email protected]>
Cc: Gerd Hoffmann <[email protected]>
Cc: BALATON Zoltan <[email protected]>
Signed-off-by: Laszlo Ersek <[email protected]>
---
 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

_______________________________________________
ipxe-devel mailing list
[email protected]
https://lists.ipxe.org/mailman/listinfo.cgi/ipxe-devel

Reply via email to