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
@@ -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
new file mode 100644
@@ -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,
+};
@@ -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 )
@@ -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
@@ -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