From 586b7502ebf3dcfe5ab315052446c6b10bb2637e Mon Sep 17 00:00:00 2001
From: Stefan Hajnoczi <stefanha@gmail.com>
Date: Thu, 14 Jan 2010 08:55:38 +0000
Subject: [PATCH] [fw_cfg] Support fetching files from QEMU fw_cfg

This patch adds support for fetching files from the QEMU fw_cfg file
interface, which exposes a set of files on the host into the guest.
Virtual machine netboot configuration can be maintained by a management
layer on the host.  This feature makes virtual machine deployment via
gPXE more flexible.

URIs are in the form "fw_cfg:<filename>", where <filename> is the name
of the file exposed via fw_cfg.  Note that there are no double slashes
after the "fw_cfg:".

Signed-off-by: Stefan Hajnoczi <stefanha@gmail.com>
---
 src/arch/i386/Makefile               |    1 +
 src/arch/i386/firmware/qemu/fw_cfg.c |  300 ++++++++++++++++++++++++++++++++++
 src/arch/i386/include/bits/errfile.h |    1 +
 src/config/config.c                  |    3 +
 src/config/general.h                 |    1 +
 5 files changed, 306 insertions(+), 0 deletions(-)
 create mode 100644 src/arch/i386/firmware/qemu/fw_cfg.c

diff --git a/src/arch/i386/Makefile b/src/arch/i386/Makefile
index dd8da80..6ddb86a 100644
--- a/src/arch/i386/Makefile
+++ b/src/arch/i386/Makefile
@@ -75,6 +75,7 @@ ISOLINUX_BIN	= /usr/lib/syslinux/isolinux.bin
 #
 SRCDIRS		+= arch/i386/core arch/i386/transitions arch/i386/prefix
 SRCDIRS		+= arch/i386/firmware/pcbios
+SRCDIRS		+= arch/i386/firmware/qemu
 SRCDIRS		+= arch/i386/image
 SRCDIRS		+= arch/i386/drivers
 SRCDIRS		+= arch/i386/drivers/net
diff --git a/src/arch/i386/firmware/qemu/fw_cfg.c b/src/arch/i386/firmware/qemu/fw_cfg.c
new file mode 100644
index 0000000..de26ebe
--- /dev/null
+++ b/src/arch/i386/firmware/qemu/fw_cfg.c
@@ -0,0 +1,300 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <byteswap.h>
+#include <gpxe/io.h>
+#include <gpxe/uri.h>
+#include <gpxe/xfer.h>
+#include <gpxe/open.h>
+#include <gpxe/process.h>
+
+/** @file
+ *
+ * QEMU firmware configuration interface
+ *
+ * The QEMU firmware configuration interface allows boot firmware access to
+ * configuration key/value pairs in the hypervisor.  This can be used to pass
+ * boot commands when starting a virtual machine.
+ *
+ */
+
+#define FW_CFG_CTL		0x510	/** control I/O port */
+#define FW_CFG_DATA		0x511	/** data I/O port */
+#define FW_CFG_SIGNATURE	0x00	/** magic number */
+#define FW_CFG_ID		0x01	/** interface version */
+#define FW_CFG_FILE_DIR		0x19	/** file listing */
+#define FW_CFG_NO_KEY		0xffff	/** invalid key */
+
+/**
+ * File metadata, part of a dir listing
+ */
+struct fw_cfg_file {
+	uint32_t size;		/** length in bytes */
+	uint16_t select;	/** key */
+	uint16_t reserved;
+	char name[56];		/** filename */
+};
+
+/**
+ * A fw_cfg request
+ *
+ * Requests could be performed synchronously in the open() function but the
+ * xfer interface chain is not fully plugged at that point.  Therefore an
+ * explicit request struct needs to be passed to a step() function, which
+ * performs the request later when the callers have plugged their xfer
+ * interfaces.
+ */
+struct fw_cfg_request {
+	/** Reference counter */
+	struct refcnt refcnt;
+	/** Data xfer interface */
+	struct xfer_interface xfer;
+	/** URI being fetched */
+	struct uri *uri;
+	/** Fetch process */
+	struct process process;
+};
+
+/**
+ * Read a bytestring value for a given key
+ *
+ * @v key		FW_CFG_* key
+ * @v buf		Buffer
+ * @v len		Buffer length in bytes
+ *
+ * If key is FW_CFG_NO_KEY then bytes will be read from the current key.
+ */
+static void fw_cfg_get_bytes ( uint16_t key, void *buf, size_t len ) {
+	char *p;
+
+	/* Select key */
+	if ( key != FW_CFG_NO_KEY )
+		outw ( key, FW_CFG_CTL );
+
+	/* Read value */
+	for ( p = buf; len > 0; len--, p++ )
+		*p = inb ( FW_CFG_DATA );
+}
+
+/**
+ * Read a 32-bit integer for a given key
+ *
+ * @v key		FW_CFG_* key
+ * @ret i		Integer value
+ */
+static uint32_t fw_cfg_get_i32 ( uint16_t key ) {
+	uint32_t i;
+	fw_cfg_get_bytes ( key, &i, sizeof ( i ) );
+	return le32_to_cpu ( i );
+}
+
+/**
+ * Probe for the fw_cfg interface
+ *
+ * @ret present		1 if present, 0 otherwise
+ */
+static int fw_cfg_detect ( void ) {
+	/* Check for fw_cfg presence */
+	char signature[4];
+	fw_cfg_get_bytes ( FW_CFG_SIGNATURE, signature, sizeof ( signature ) );
+	if ( memcmp ( signature, "QEMU", 4 ) != 0 ) {
+		DBG ( "FW_CFG signature check failed\n" );
+		return 0;
+	}
+
+	/* Check interface version */
+	if ( fw_cfg_get_i32 ( FW_CFG_ID ) != 1 ) {
+		DBG ( "FW_CFG unsupported version\n" );
+		return 0;
+	}
+
+	DBG ( "FW_CFG support detected\n" );
+	return 1;
+}
+
+/**
+ * Find the key for a given filename
+ *
+ * @v name		Filename
+ * @v file		File pointer
+ * @ret rc		Return status code
+ */
+static int fw_cfg_find_file ( const char *name, struct fw_cfg_file *file ) {
+	uint32_t count = be32_to_cpu ( fw_cfg_get_i32 ( FW_CFG_FILE_DIR ) );
+	DBG2 ( "FW_CFG %d files:\n", count );
+	while ( count-- > 0 ) {
+		fw_cfg_get_bytes ( FW_CFG_NO_KEY, file, sizeof ( *file ) );
+		file->size	= be32_to_cpu ( file->size );
+		file->select	= be16_to_cpu ( file->select );
+		DBG2 ( "FW_CFG \"%s\" %d bytes key=0x%x\n",
+		       file->name, file->size, file->select );
+		if ( strcmp ( name, file->name ) == 0 )
+			return 0;
+	}
+	return -ENOENT;
+}
+
+/**
+ * Read a file into a transfer interface
+ *
+ * @v file		File pointer
+ * @v xfer		Transfer interface
+ * @ret rc		Return status code
+ */
+static int fw_cfg_read_file ( struct fw_cfg_file *file, struct xfer_interface *xfer ) {
+	int rc = 0;
+	size_t len = file->size;
+
+	/* Use seek() to notify recipient of filesize */
+	xfer_seek ( xfer, len, SEEK_SET );
+	xfer_seek ( xfer, 0, SEEK_SET );
+
+	/* Select file key */
+	fw_cfg_get_bytes ( file->select, NULL, 0 );
+
+	while ( rc == 0 && len > 0 ) {
+		size_t read_size = len > 4096 ? 4096 : len;
+		struct io_buffer *iobuf = xfer_alloc_iob ( xfer, read_size );
+		if ( ! iobuf )
+			return -ENOMEM;
+
+		fw_cfg_get_bytes ( FW_CFG_NO_KEY, iob_put ( iobuf, read_size ),
+				   read_size );
+		rc = xfer_deliver_iob ( xfer, iobuf );
+		len -= read_size;
+	}
+	return rc;
+}
+
+/**
+ * Free fw_cfg request
+ *
+ * @v refcnt		Reference counter
+ */
+static void fw_cfg_free ( struct refcnt *refcnt ) {
+	struct fw_cfg_request *fw_cfg =
+		container_of ( refcnt, struct fw_cfg_request, refcnt );
+
+	DBGC2 ( fw_cfg, "FW_CFG %p freed\n", fw_cfg );
+
+	uri_put ( fw_cfg->uri );
+	free ( fw_cfg );
+}
+
+/**
+ * Mark fw_cfg request as complete
+ *
+ * @v fw_cfg		fw_cfg request
+ */
+static void fw_cfg_done ( struct fw_cfg_request *fw_cfg, int rc ) {
+	/* Remove process */
+	process_del ( &fw_cfg->process );
+
+	/* Shut down xfer interface */
+	xfer_nullify ( &fw_cfg->xfer );
+	xfer_close ( &fw_cfg->xfer, rc );
+}
+
+/**
+ * Close a fw_cfg request
+ *
+ * @v xfer		Transfer interface
+ * @v rc		Return status code
+ */
+static void fw_cfg_xfer_close ( struct xfer_interface *xfer, int rc ) {
+	struct fw_cfg_request *fw_cfg =
+		container_of ( xfer, struct fw_cfg_request, xfer );
+
+	DBGC ( fw_cfg, "FW_CFG %p closed\n", fw_cfg );
+
+	fw_cfg_done ( fw_cfg, rc );
+}
+
+/**
+ * fw_cfg process
+ *
+ * @v process		Process
+ */
+static void fw_cfg_step ( struct process *process ) {
+	struct fw_cfg_request *fw_cfg =
+		container_of ( process, struct fw_cfg_request, process );
+	struct fw_cfg_file file;
+	int rc;
+
+	/* Only execute once */
+	process_del ( &fw_cfg->process );
+
+	if ( ( rc = fw_cfg_find_file ( fw_cfg->uri->opaque, &file ) ) == 0 )
+		rc = fw_cfg_read_file ( &file, &fw_cfg->xfer );
+
+	fw_cfg_done ( fw_cfg, rc );
+}
+
+/** fw_cfg data transfer interface operations */
+static struct xfer_interface_operations fw_cfg_xfer_operations = {
+	.close		= fw_cfg_xfer_close,
+	.vredirect	= ignore_xfer_vredirect,
+	.window		= unlimited_xfer_window,
+	.alloc_iob	= default_xfer_alloc_iob,
+	.deliver_iob	= xfer_deliver_as_raw,
+	.deliver_raw	= ignore_xfer_deliver_raw,
+};
+
+/**
+ * Open fw_cfg URI
+ *
+ * @v xfer		Data transfer interface
+ * @v uri		URI
+ * @ret rc		Return status code
+ */
+static int fw_cfg_open ( struct xfer_interface *xfer, struct uri *uri ) {
+	struct fw_cfg_request *fw_cfg;
+
+	/* Sanity checks */
+	if ( ! fw_cfg_detect() )
+		return -ENOSYS;
+	if ( ! uri->opaque )
+		return -EINVAL;
+
+	/* Allocate and populate structure */
+	fw_cfg = zalloc ( sizeof ( *fw_cfg ) );
+	if ( ! fw_cfg )
+		return -ENOMEM;
+	fw_cfg->refcnt.free = fw_cfg_free;
+	fw_cfg->uri = uri_get ( uri );
+	xfer_init ( &fw_cfg->xfer, &fw_cfg_xfer_operations, &fw_cfg->refcnt );
+	process_init ( &fw_cfg->process, fw_cfg_step, &fw_cfg->refcnt );
+
+	DBGC ( fw_cfg, "FW_CFG %p fetching %s\n", fw_cfg, uri->opaque );
+
+	/* Attach to parent interface, mortalise self, and return */
+	xfer_plug_plug ( &fw_cfg->xfer, xfer );
+	ref_put ( &fw_cfg->refcnt );
+	return 0;
+}
+
+/** fw_cfg URI opener */
+struct uri_opener fw_cfg_uri_opener __uri_opener = {
+	.scheme	= "fw_cfg",
+	.open	= fw_cfg_open,
+};
diff --git a/src/arch/i386/include/bits/errfile.h b/src/arch/i386/include/bits/errfile.h
index 32b8a08..849cdc0 100644
--- a/src/arch/i386/include/bits/errfile.h
+++ b/src/arch/i386/include/bits/errfile.h
@@ -15,6 +15,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_biosint		( ERRFILE_ARCH | ERRFILE_CORE | 0x00040000 )
 #define ERRFILE_int13		( ERRFILE_ARCH | ERRFILE_CORE | 0x00050000 )
 #define ERRFILE_pxeparent	( ERRFILE_ARCH | ERRFILE_CORE | 0x00060000 )
+#define ERRFILE_fw_cfg		( ERRFILE_ARCH | ERRFILE_CORE | 0x00070000 )
 
 #define ERRFILE_bootsector     ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00000000 )
 #define ERRFILE_bzimage	       ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00010000 )
diff --git a/src/config/config.c b/src/config/config.c
index 8252402..8023e41 100644
--- a/src/config/config.c
+++ b/src/config/config.c
@@ -127,6 +127,9 @@ REQUIRE_OBJECT ( tftm );
 #ifdef DOWNLOAD_PROTO_SLAM
 REQUIRE_OBJECT ( slam );
 #endif
+#ifdef DOWNLOAD_PROTO_FW_CFG
+REQUIRE_OBJECT ( fw_cfg );
+#endif
 
 /*
  * Drag in all requested SAN boot protocols
diff --git a/src/config/general.h b/src/config/general.h
index f721f61..3fff9ef 100644
--- a/src/config/general.h
+++ b/src/config/general.h
@@ -61,6 +61,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #undef	DOWNLOAD_PROTO_TFTM	/* Multicast Trivial File Transfer Protocol */
 #undef	DOWNLOAD_PROTO_SLAM	/* Scalable Local Area Multicast */
 #undef	DOWNLOAD_PROTO_FSP	/* FSP? */
+#undef	DOWNLOAD_PROTO_FW_CFG	/* QEMU fw_cfg file interface */
 
 /*
  * SAN boot protocols
-- 
1.6.5
