From 0926649bece8e06dfdb19e4f819795da6ee364dd Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@gmail.com>
Date: Tue, 5 Jan 2010 08:05:32 +0000
Subject: [PATCH 1/2] [sanboot] Prevent leaking a stack reference for "keep-san" AoE

When the "keep-san" option is used, the function is exited without
unregistering the stack allocated int13h drive.  To prevent a dangling
pointer to the stack, these structs should be heap allocated.
---
 src/arch/i386/interface/pcbios/aoeboot.c |   54 ++++++++++++++++++-----------
 1 files changed, 33 insertions(+), 21 deletions(-)

diff --git a/src/arch/i386/interface/pcbios/aoeboot.c b/src/arch/i386/interface/pcbios/aoeboot.c
index 8446c15..2670b15 100644
--- a/src/arch/i386/interface/pcbios/aoeboot.c
+++ b/src/arch/i386/interface/pcbios/aoeboot.c
@@ -1,11 +1,11 @@
 #include <stdint.h>
 #include <string.h>
+#include <stdlib.h>
 #include <stdio.h>
-#include <byteswap.h>
+#include <errno.h>
 #include <gpxe/aoe.h>
 #include <gpxe/ata.h>
 #include <gpxe/netdevice.h>
-#include <gpxe/settings.h>
 #include <gpxe/sanboot.h>
 #include <gpxe/abft.h>
 #include <int13.h>
@@ -13,50 +13,62 @@
 FILE_LICENCE ( GPL2_OR_LATER );
 
 static int aoeboot ( const char *root_path ) {
-	struct ata_device ata;
-	struct int13_drive drive;
+	struct ata_device *ata;
+	struct int13_drive *drive;
 	int rc;
 
-	memset ( &ata, 0, sizeof ( ata ) );
-	memset ( &drive, 0, sizeof ( drive ) );
+	ata = zalloc ( sizeof ( *ata ) );
+	if ( ! ata ) {
+		rc = -ENOMEM;
+		goto err_alloc_ata;
+	}
+	drive = zalloc ( sizeof ( *drive ) );
+	if ( ! drive ) {
+		rc = -ENOMEM;
+		goto err_alloc_drive;
+	}
 
 	/* FIXME: ugly, ugly hack */
 	struct net_device *netdev = last_opened_netdev();
 
-	if ( ( rc = aoe_attach ( &ata, netdev, root_path ) ) != 0 ) {
+	if ( ( rc = aoe_attach ( ata, netdev, root_path ) ) != 0 ) {
 		printf ( "Could not attach AoE device: %s\n",
 			 strerror ( rc ) );
-		goto error_attach;
+		goto err_attach;
 	}
-	if ( ( rc = init_atadev ( &ata ) ) != 0 ) {
+	if ( ( rc = init_atadev ( ata ) ) != 0 ) {
 		printf ( "Could not initialise AoE device: %s\n",
 			 strerror ( rc ) );
-		goto error_init;
+		goto err_init;
 	}
 
 	/* FIXME: ugly, ugly hack */
 	struct aoe_session *aoe =
-		container_of ( ata.backend, struct aoe_session, refcnt );
+		container_of ( ata->backend, struct aoe_session, refcnt );
 	abft_fill_data ( aoe );
 
-	drive.blockdev = &ata.blockdev;
+	drive->blockdev = &ata->blockdev;
 
-	register_int13_drive ( &drive );
-	printf ( "Registered as BIOS drive %#02x\n", drive.drive );
-	printf ( "Booting from BIOS drive %#02x\n", drive.drive );
-	rc = int13_boot ( drive.drive );
+	register_int13_drive ( drive );
+	printf ( "Registered as BIOS drive %#02x\n", drive->drive );
+	printf ( "Booting from BIOS drive %#02x\n", drive->drive );
+	rc = int13_boot ( drive->drive );
 	printf ( "Boot failed\n" );
 
 	/* Leave drive registered, if instructed to do so */
 	if ( keep_san() )
 		return rc;
 
-	printf ( "Unregistering BIOS drive %#02x\n", drive.drive );
-	unregister_int13_drive ( &drive );
+	printf ( "Unregistering BIOS drive %#02x\n", drive->drive );
+	unregister_int13_drive ( drive );
 
- error_init:
-	aoe_detach ( &ata );
- error_attach:
+ err_init:
+	aoe_detach ( ata );
+ err_attach:
+	free ( drive );
+ err_alloc_drive:
+	free ( ata );
+ err_alloc_ata:
 	return rc;
 }
 
-- 
1.6.5


From 3dbc385d29cbdff520ed0694934c01a6808ff1dc Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@gmail.com>
Date: Tue, 5 Jan 2010 17:39:18 +0000
Subject: [PATCH 2/2] [virtio] Add virtio block device sanboot support

This patch adds virtio block device support alongside the existing
iSCSI, ATA-over-Ethernet, and ramdisk block devices.  A gPXE option ROM
can boot a QEMU/KVM virtual machine directly from a virtio block device:

    # Ensure SANBOOT_PROTO_VIRTIO_BLK is defined in config/defaults.h
    make bin/1af41001.rom

The sanboot gPXE command is used with the virtio_blk: root path scheme.
The virtio block device instance is identified by its PCI bus, device,
and function (there could be multiple virtio block devices).

    sanboot virtio_blk:PCI00:04.0

Successfully boots Debian testing i386 and FreeDOS 0.84-pre2 under QEMU.
---
 src/arch/i386/interface/pcbios/virtio_blkboot.c |   70 ++++++
 src/config/config.c                             |    3 +
 src/config/general.h                            |    1 +
 src/drivers/block/virtio-blk.c                  |  271 +++++++++++++++++++++++
 src/drivers/block/virtio-blk.h                  |   30 +++
 src/include/gpxe/errfile.h                      |    2 +
 src/include/gpxe/virtblk.h                      |   37 +++
 7 files changed, 414 insertions(+), 0 deletions(-)
 create mode 100644 src/arch/i386/interface/pcbios/virtio_blkboot.c
 create mode 100644 src/drivers/block/virtio-blk.c
 create mode 100644 src/drivers/block/virtio-blk.h
 create mode 100644 src/include/gpxe/virtblk.h

diff --git a/src/arch/i386/interface/pcbios/virtio_blkboot.c b/src/arch/i386/interface/pcbios/virtio_blkboot.c
new file mode 100644
index 0000000..3c2dd47
--- /dev/null
+++ b/src/arch/i386/interface/pcbios/virtio_blkboot.c
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 Stefan Hajnoczi <stefanha@gmail.com>.
+ *
+ * This program 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 2 of the
+ * License, or any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <gpxe/sanboot.h>
+#include <gpxe/virtblk.h>
+#include <int13.h>
+
+/**
+ * @file
+ *
+ * Virtio block device boot
+ *
+ */
+
+static int virtio_blkboot ( const char *root_path ) {
+	struct int13_drive *drive;
+	struct virtblk *virtblk;
+	int rc;
+
+	virtblk = find_virtblk ( root_path + sizeof ( "virtio_blk:" ) - 1 );
+	if ( ! virtblk )
+		return -ENOENT;
+
+	drive = zalloc ( sizeof ( *drive ) );
+	if ( ! drive )
+		return -ENOMEM;
+
+	drive->blockdev = &virtblk->blockdev;
+
+	register_int13_drive ( drive );
+	printf ( "Registered as BIOS drive %#02x\n", drive->drive );
+	printf ( "Booting from BIOS drive %#02x\n", drive->drive );
+	rc = int13_boot ( drive->drive );
+	printf ( "Boot failed\n" );
+
+	/* Leave drive registered, if instructed to do so */
+	if ( keep_san() )
+		return rc;
+
+	printf ( "Unregistering BIOS drive %#02x\n", drive->drive );
+	unregister_int13_drive ( drive );
+	free ( drive );
+	return rc;
+}
+
+struct sanboot_protocol virtblk_sanboot_protocol __sanboot_protocol = {
+	.prefix = "virtio_blk:",
+	.boot = virtio_blkboot,
+};
diff --git a/src/config/config.c b/src/config/config.c
index 3c43dfb..bb0ff67 100644
--- a/src/config/config.c
+++ b/src/config/config.c
@@ -130,6 +130,9 @@ REQUIRE_OBJECT ( aoeboot );
 #ifdef SANBOOT_PROTO_IB_SRP
 REQUIRE_OBJECT ( ib_srpboot );
 #endif
+#ifdef SANBOOT_PROTO_VIRTIO_BLK
+REQUIRE_OBJECT ( virtio_blkboot );
+#endif
 
 /*
  * Drag in all requested resolvers
diff --git a/src/config/general.h b/src/config/general.h
index ee07dfc..a1dec39 100644
--- a/src/config/general.h
+++ b/src/config/general.h
@@ -63,6 +63,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 //#undef	SANBOOT_PROTO_ISCSI	/* iSCSI protocol */
 //#undef	SANBOOT_PROTO_AOE	/* AoE protocol */
 //#undef	SANBOOT_PROTO_IB_SRP	/* Infiniband SCSI RDMA protocol */
+//#undef	SANBOOT_PROTO_VIRTIO_BLK /* Virtio block device */
 
 /*
  * Name resolution modules
diff --git a/src/drivers/block/virtio-blk.c b/src/drivers/block/virtio-blk.c
new file mode 100644
index 0000000..fab5930
--- /dev/null
+++ b/src/drivers/block/virtio-blk.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2010 Stefan Hajnoczi <stefanha@gmail.com>.
+ *
+ * This program 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 2 of the
+ * License, or any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <errno.h>
+#include <stdlib.h>
+#include <gpxe/process.h>
+#include <gpxe/virtblk.h>
+#include "virtio-blk.h"
+
+/**
+ * @file
+ *
+ * Virtio block devices
+ *
+ */
+
+/** List of virtio block devices */
+static LIST_HEAD ( virtblk_devices );
+
+/**
+ * Register virtio block device
+ *
+ * @v virtblk		Virtio block device
+ *
+ * Adds the virtio block device to the list of virtio block devices.
+ */
+static void register_virtblk ( struct virtblk *virtblk ) {
+	list_add_tail ( &virtblk->virtblk_devices, &virtblk_devices );
+}
+
+/**
+ * Unregister virtio block device
+ *
+ * @v virtblk		Virtio block device
+ *
+ * Removes the virtio block device from the list of virtio block devices.
+ */
+static void unregister_virtblk ( struct virtblk *virtblk ) {
+	list_del ( &virtblk->virtblk_devices );
+}
+
+/**
+ * Find virtio block device by name
+ *
+ * @v device_name	Device name
+ * @ret virtblk		Virtio block device or NULL
+ *
+ * Searches the list of virtio block devices by name.
+ */
+struct virtblk *find_virtblk ( const char *device_name ) {
+	struct virtblk *virtblk;
+	DBG ( "find_virtblk \"%s\"\n", device_name );
+	list_for_each_entry ( virtblk, &virtblk_devices, virtblk_devices ) {
+		if ( ! strcmp ( device_name, virtblk->pdev->dev.name ) )
+			return virtblk;
+	}
+	return NULL;
+}
+
+/**
+ * Issue an I/O request and wait for completion
+ *
+ * @v virtblk		Virtio block device
+ * @v type		VIRTIO_BLK_T_IN or VIRTIO_BLK_T_OUT
+ * @v block		Block number
+ * @v count		Block count
+ * @v buffer		Data buffer
+ * @ret rc		Return status code
+ */
+static int virtblk_command ( struct virtblk *virtblk, u32 type,
+			     uint64_t block, unsigned long count,
+			     userptr_t buffer ) {
+	/*
+	 * Virtio block requests have the following format:
+	 *
+	 *   +--------------------------------+
+	 *   | struct virtio_blk_outhdr  [IN] |
+	 *   +--------------------------------+
+	 *   | In/out buffer         [IN/OUT] |
+	 *   +--------------------------------+
+	 *   | Status byte              [OUT] |
+	 *   +--------------------------------+
+	 *
+	 * The device fills in the status byte to indicate the outcome the
+	 * operation.
+	 */
+	struct vring_virtqueue *vq = &virtblk->virtqueue;
+	struct virtio_blk_outhdr hdr = {
+		.type = type,
+		.ioprio = 0,
+		.sector = block,
+	};
+	uint8_t status = VIRTIO_BLK_S_UNSUPP;
+	struct vring_list list[] = {
+		{
+			.addr	= ( char * ) &hdr,
+			.length	= sizeof ( hdr ),
+		},
+		{
+			.addr	= ( char * ) user_to_virt ( buffer, 0 ),
+			.length	= virtblk->blockdev.blksize * count,
+		},
+		{
+			.addr	= ( char * ) &status,
+			.length	= sizeof ( status ),
+		},
+	};
+	unsigned int in, out;
+
+	DBG ( "VIRTBLK req 0x%02x LBA 0x%llx count 0x%lx\n",
+	      type, block, count );
+
+	/* Number of elements readable and writable */
+	if ( type == VIRTIO_BLK_T_IN ) {
+		out = 1;
+		in = 2;
+	} else {
+		out = 2;
+		in = 1;
+	}
+
+	/* Add to virtqueue and kick host */
+	vring_add_buf ( vq, list, out, in, 0, 0 );
+	vring_kick ( virtblk->pdev->ioaddr, vq, 1 );
+
+	/* Wait for reply */
+	while ( ! vring_more_used ( vq ) ) {
+		mb();
+		step();
+	}
+
+	/* Reclaim virtqueue element */
+	vring_get_buf ( vq, NULL );
+	return status == VIRTIO_BLK_S_OK ? 0 : -EIO;
+}
+
+/**
+ * Read block
+ *
+ * @v blockdev		Block device
+ * @v block		Block number
+ * @v count		Block count
+ * @v buffer		Data buffer
+ * @ret rc		Return status code
+ */
+static int virtblk_read ( struct block_device *blockdev, uint64_t block,
+			  unsigned long count, userptr_t buffer ) {
+	struct virtblk *virtblk =
+		container_of ( blockdev, struct virtblk, blockdev );
+	return virtblk_command ( virtblk, VIRTIO_BLK_T_IN, block,
+				 count, buffer );
+}
+
+/**
+ * Write block
+ *
+ * @v blockdev		Block device
+ * @v block		Block number
+ * @v count		Block count
+ * @v buffer		Data buffer
+ * @ret rc		Return status code
+ */
+static int virtblk_write ( struct block_device *blockdev __unused, uint64_t block __unused,
+			   unsigned long count __unused, userptr_t buffer __unused ) {
+	struct virtblk *virtblk =
+		container_of ( blockdev, struct virtblk, blockdev );
+	return virtblk_command ( virtblk, VIRTIO_BLK_T_OUT, block,
+				 count, buffer );
+}
+
+static struct block_device_operations virtblk_operations = {
+	.read	= virtblk_read,
+	.write	= virtblk_write,
+};
+
+/**
+ * Probe PCI device
+ *
+ * @v pci	PCI device
+ * @v id	PCI ID
+ * @ret rc	Return status code
+ */
+static int virtblk_probe ( struct pci_device *pci,
+			   const struct pci_device_id *id __unused ) {
+	unsigned long ioaddr = pci->ioaddr;
+
+	/* Initialize driver state */
+	struct virtblk *virtblk = zalloc ( sizeof *virtblk );
+	if ( ! virtblk )
+		return -ENOMEM;
+	INIT_LIST_HEAD ( &virtblk->virtblk_devices );
+	virtblk->blockdev.op = &virtblk_operations;
+	virtblk->pdev = pci;
+	pci->priv = virtblk;
+
+	/* Prepare the device */
+	adjust_pci_device ( pci );
+	vp_reset ( ioaddr );
+
+	/* Indicate that the driver is starting */
+	vp_set_status ( ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
+				VIRTIO_CONFIG_S_DRIVER );
+
+	/* Grab the I/O requests virtqueue */
+	if ( vp_find_vq ( ioaddr, 0, &virtblk->virtqueue ) < 0 ) {
+		free ( virtblk );
+		return -ENOENT;
+	}
+
+	/* Feature negotiation */
+	u32 features = vp_get_features ( ioaddr );
+	vp_set_features ( ioaddr, 0 ); /* no features required */
+
+	/* Find out the drive capacity */
+	virtblk->blockdev.blksize = 512; /* the default */
+	vp_get ( ioaddr, 0, &virtblk->blockdev.blocks,
+		 sizeof virtblk->blockdev.blocks );
+
+	/* Indicate that the driver is ready */
+	vp_set_status ( ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
+				VIRTIO_CONFIG_S_DRIVER |
+				VIRTIO_CONFIG_S_DRIVER_OK );
+
+	DBGC ( virtblk, "VIRTBLK %p virtio_blk:%s ioaddr=0x%lx irq=%d features=0x%x capacity=%lld\n",
+	       virtblk, pci->dev.name, ioaddr, pci->irq,
+	       features, virtblk->blockdev.blocks );
+
+	register_virtblk ( virtblk );
+	return 0;
+}
+
+/**
+ * Remove PCI device
+ *
+ * @v pci	PCI device
+ */
+static void virtblk_remove ( struct pci_device *pci ) {
+	struct virtblk *virtblk = pci->priv;
+
+	unregister_virtblk ( virtblk );
+	vp_reset ( pci->ioaddr );
+	free ( virtblk );
+}
+
+static struct pci_device_id virtblk_ids[] = {
+	PCI_ROM(0x1af4, 0x1001, "virtio-blk", "Virtio Block Device", 0),
+};
+
+struct pci_driver virtblk_driver __pci_driver = {
+  .ids = virtblk_ids,
+  .id_count = ( sizeof ( virtblk_ids ) / sizeof ( virtblk_ids[0] ) ),
+  .probe = virtblk_probe,
+  .remove = virtblk_remove,
+};
diff --git a/src/drivers/block/virtio-blk.h b/src/drivers/block/virtio-blk.h
new file mode 100644
index 0000000..3a34868
--- /dev/null
+++ b/src/drivers/block/virtio-blk.h
@@ -0,0 +1,30 @@
+#ifndef _VIRTIO_BLK_H
+#define _VIRTIO_BLK_H
+/* This header is BSD licensed so anyone can use the definitions to implement
+ * compatible drivers/servers. */
+
+FILE_LICENCE ( BSD2 );
+
+/*
+ * Command types
+ */
+
+/* These two define direction. */
+#define VIRTIO_BLK_T_IN		0
+#define VIRTIO_BLK_T_OUT	1
+
+/* This is the first element of the read scatter-gather list. */
+struct virtio_blk_outhdr {
+	/* VIRTIO_BLK_T* */
+	u32 type;
+	/* io priority. */
+	u32 ioprio;
+	/* Sector (ie. 512 byte offset) */
+	u64 sector;
+};
+
+/* And this is the final byte of the write scatter-gather list. */
+#define VIRTIO_BLK_S_OK		0
+#define VIRTIO_BLK_S_IOERR	1
+#define VIRTIO_BLK_S_UNSUPP	2
+#endif /* _VIRTIO_BLK_H */
diff --git a/src/include/gpxe/errfile.h b/src/include/gpxe/errfile.h
index 5231c14..092d7eb 100644
--- a/src/include/gpxe/errfile.h
+++ b/src/include/gpxe/errfile.h
@@ -122,6 +122,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_linda		     ( ERRFILE_DRIVER | 0x00730000 )
 #define ERRFILE_ata		     ( ERRFILE_DRIVER | 0x00740000 )
 #define ERRFILE_srp		     ( ERRFILE_DRIVER | 0x00750000 )
+#define ERRFILE_virtio_blk	     ( ERRFILE_DRIVER | 0x00760000 )
 
 #define ERRFILE_aoe			( ERRFILE_NET | 0x00000000 )
 #define ERRFILE_arp			( ERRFILE_NET | 0x00010000 )
@@ -191,6 +192,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_x509		      ( ERRFILE_OTHER | 0x00160000 )
 #define ERRFILE_login_ui	      ( ERRFILE_OTHER | 0x00170000 )
 #define ERRFILE_ib_srpboot	      ( ERRFILE_OTHER | 0x00180000 )
+#define ERRFILE_virtio_blkboot	      ( ERRFILE_OTHER | 0x00190000 )
 
 /** @} */
 
diff --git a/src/include/gpxe/virtblk.h b/src/include/gpxe/virtblk.h
new file mode 100644
index 0000000..781d433
--- /dev/null
+++ b/src/include/gpxe/virtblk.h
@@ -0,0 +1,37 @@
+#ifndef _GPXE_VIRTBLK_H
+#define _GPXE_VIRTBLK_H
+
+/** @file
+ *
+ * Virtio block device
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <gpxe/list.h>
+#include <gpxe/pci.h>
+#include <gpxe/virtio-ring.h>
+#include <gpxe/virtio-pci.h>
+#include <gpxe/blockdev.h>
+
+/**
+ * Virtio block device
+ */
+struct virtblk {
+	/** List of virtio-blk devices */
+	struct list_head virtblk_devices;
+
+	/** Underlying PCI device */
+	struct pci_device *pdev;
+
+	/** Outgoing requests virtqueue */
+	struct vring_virtqueue virtqueue;
+
+	/** Block device */
+	struct block_device blockdev;
+};
+
+extern struct virtblk *find_virtblk ( const char *device_name );
+
+#endif /* _GPXE_VIRTBLK_H */
-- 
1.6.5
