From 586b7502ebf3dcfe5ab315052446c6b10bb2637e Mon Sep 17 00:00:00 2001 From: Stefan Hajnoczi 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:", where 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 --- 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/** @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