From patchwork Wed Jan 27 09:42:07 2010 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Hajnoczi X-Patchwork-Id: 43777 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [199.232.76.165]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 963F0B7CC1 for ; Wed, 27 Jan 2010 20:43:04 +1100 (EST) Received: from localhost ([127.0.0.1]:57012 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Na4QN-0004jb-PK for incoming@patchwork.ozlabs.org; Wed, 27 Jan 2010 04:42:59 -0500 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1Na4Pi-0004il-HB for qemu-devel@nongnu.org; Wed, 27 Jan 2010 04:42:18 -0500 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1Na4Pd-0004ff-C7 for qemu-devel@nongnu.org; Wed, 27 Jan 2010 04:42:17 -0500 Received: from [199.232.76.173] (port=55521 helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1Na4Pd-0004fS-8j for qemu-devel@nongnu.org; Wed, 27 Jan 2010 04:42:13 -0500 Received: from mail-bw0-f212.google.com ([209.85.218.212]:59162) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1Na4Pc-0000yb-Ej for qemu-devel@nongnu.org; Wed, 27 Jan 2010 04:42:13 -0500 Received: by bwz4 with SMTP id 4so4911100bwz.2 for ; Wed, 27 Jan 2010 01:42:07 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:mime-version:received:date:message-id:subject :from:to:cc:content-type; bh=JpmqVfNt5cbxiTfxJ4yZUMBNWlGdoAG5iPxnM5PzFWI=; b=itonG4sS7PH21E73oh4fvdcwuT/67EjayWLmC53fExU5Ki+mMwRICocZPuB5OgDOu9 1voj9jq+FYA35/bGNRsn9wKcwEinm30nRb470Lb6+pzw5Lk5SxdMH7cxDrg9MdZcAd57 roP1yns0blAUxzhPxTzq+a9kG5m7juJhD3+IY= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=mime-version:date:message-id:subject:from:to:cc:content-type; b=hpuvNhkecPlUZNoDsDMvL5pp58KavLKiDhluQv2zxWOMfEP1FWULCj0cK+OwZdKryD 5Zsm+IApHR4i0WNNY3G6jE6Cg4XTOdq+Y4sEwdtQ8apoL37tvc/vVB1hSs17D+2zrRiE 6eoiULWlMPrcbAeWyGp5Q3tzasczrlGjgilrc= MIME-Version: 1.0 Received: by 10.204.33.216 with SMTP id i24mr4719053bkd.154.1264585327253; Wed, 27 Jan 2010 01:42:07 -0800 (PST) Date: Wed, 27 Jan 2010 09:42:07 +0000 Message-ID: From: Stefan Hajnoczi To: gpxe X-detected-operating-system: by monty-python.gnu.org: GNU/Linux 2.6 (newer, 2) Cc: qemu-devel@nongnu.org Subject: [Qemu-devel] [RFC] gPXE fw_cfg file interface support X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org 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 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