Patchwork [RFC] gPXE fw_cfg file interface support

login
register
mail settings
Submitter Stefan Hajnoczi
Date Jan. 27, 2010, 9:42 a.m.
Message ID <fbd9d3991001270142xbb792b5u4b70356434e024c5@mail.gmail.com>
Download mbox | patch
Permalink /patch/43777/
State New
Headers show

Comments

Stefan Hajnoczi - Jan. 27, 2010, 9:42 a.m.
This patch makes it possible for gPXE under QEMU/KVM to fetch files from the
host using the fw_cfg file interface.  This means gPXE in the guest can fetch an
exposed file from the host without using networking.

I believe this feature will be useful to management software so that
network boot
configuration can be managed alongside with the virtual machine configuration,
not on a remote TFTP server.

Any interest in this feature?  I imagine libvirt could use this to set
up iSCSI or
other network boot settings.

Images are fetched from the hypervisor using the fw_cfg interface provided by
QEMU/KVM.  The URI syntax is as follows:

gPXE> imgfetch fw_cfg:genroms/static.gpxe

where 'fw_cfg' is the new URI scheme this patch adds and 'genroms/static.gpxe'
is the filename exported from QEMU/KVM via the fw_cfg interface.

Note that the URI uses a single colon ':', not '://', since there is no
hostname.

Current QEMU/KVM builds can be (ab)used to expose arbitrary files
like this:

qemu -option-rom path/to/file [...]

The file will appear as 'genroms/file'.  Use the 'fw_cfg:genroms/file' URI to
reference it in gPXE.

The fw_cfg file interface is a recent addition to QEMU/KVM.  You may need to
build QEMU/KVM from source in order to get this feature.

Note that this patch only adds the fw_cfg file interface mechanism, it does not
automatically probe for a special file when gPXE starts.  You will
need to manually
"chain fw_cfg:genroms/file".

Any comments?

Stefan
Gerd Hoffmann - Jan. 27, 2010, 10:15 a.m.
> gPXE>  imgfetch fw_cfg:genroms/static.gpxe

Neat idea.  On the qemu side we better shouldn't abuse the -option-rom 
switch though as seabios will try to load these files as option roms.

> Note that this patch only adds the fw_cfg file interface mechanism, it does not
> automatically probe for a special file when gPXE starts.  You will
> need to manually
> "chain fw_cfg:genroms/file".

That should be changed too IMHO.  qemu needs some kind of new switch, so 
you can place the firmware file into some other virtual directory where 
gpxe could look by default, i.e. something like "fw_cfg:gpxe/default". 
Then you can easily have qemu guests netboot from somewhere without any 
local pxe/tftp setup, like this:

   qemu -fw-file gpxe/default=bko.gpxe

with bko.gpxe being:

   #!gpxe
   dhcp net0
   set 209:string pxelinux.cfg/default
   set 210:string http://boot.kernel.org/bko/
   chain http://boot.kernel.org/bko/pxelinux.0

cheers,
   Gerd
Stefan Hajnoczi - Jan. 29, 2010, 10:06 a.m.
On Wed, Jan 27, 2010 at 10:15 AM, Gerd Hoffmann <kraxel@redhat.com> wrote:
>> Note that this patch only adds the fw_cfg file interface mechanism, it
>> does not
>> automatically probe for a special file when gPXE starts.  You will
>> need to manually
>> "chain fw_cfg:genroms/file".
>
> That should be changed too IMHO.  qemu needs some kind of new switch, so you
> can place the firmware file into some other virtual directory where gpxe
> could look by default, i.e. something like "fw_cfg:gpxe/default". Then you
> can easily have qemu guests netboot from somewhere without any local
> pxe/tftp setup, like this:
>
>  qemu -fw-file gpxe/default=bko.gpxe
>
> with bko.gpxe being:
>
>  #!gpxe
>  dhcp net0
>  set 209:string pxelinux.cfg/default
>  set 210:string http://boot.kernel.org/bko/
>  chain http://boot.kernel.org/bko/pxelinux.0

That sounds good.  I will send out a new patch which fetches
fw_cfg:gpxe/default on startup.

Stefan
Shao Miller - Feb. 8, 2010, 2:23 a.m.
Gerd Hoffmann wrote:
>
> ...Then you can easily have qemu guests netboot from somewhere without any
> local pxe/tftp setup, like this:
>
>    qemu -fw-file gpxe/default=bko.gpxe
>

That looks a bit like:

# qemu -kernel gpxe.lkrn -initrd bko.gpxe -hda /dev/null

which might be useful iff the *.lkrn build target feature for using an 
initrd as a gPXE startup script is incorporated into gPXE mainline.  Of 
course, it's different because an .lkrn is not a ROM.

- Shao Miller

Patch

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