]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[xen] Add basic support for PV-HVM domains
authorMichael Brown <mcb30@ipxe.org>
Mon, 28 Jul 2014 22:38:30 +0000 (23:38 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 29 Jul 2014 14:57:44 +0000 (15:57 +0100)
Add basic support for Xen PV-HVM domains (detected via the Xen
platform PCI device with IDs 5853:0001), including support for
accessing configuration via XenStore and enumerating devices via
XenBus.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
18 files changed:
src/Makefile
src/arch/x86/Makefile
src/arch/x86/drivers/xen/hvm.c [new file with mode: 0644]
src/arch/x86/drivers/xen/hvm.h [new file with mode: 0644]
src/arch/x86/include/bits/errfile.h
src/arch/x86/include/bits/xen.h [new file with mode: 0644]
src/include/ipxe/device.h
src/include/ipxe/errfile.h
src/include/ipxe/xen.h [new file with mode: 0644]
src/include/ipxe/xenbus.h [new file with mode: 0644]
src/include/ipxe/xenevent.h [new file with mode: 0644]
src/include/ipxe/xengrant.h [new file with mode: 0644]
src/include/ipxe/xenmem.h [new file with mode: 0644]
src/include/ipxe/xenstore.h [new file with mode: 0644]
src/include/ipxe/xenver.h [new file with mode: 0644]
src/interface/xen/xenbus.c [new file with mode: 0644]
src/interface/xen/xengrant.c [new file with mode: 0644]
src/interface/xen/xenstore.c [new file with mode: 0644]

index c6760ee95e8540afcdc08c6b53f43cd5951bffb4..aeeabcda6de9788c55a507fed187229ed741af4d 100644 (file)
@@ -84,6 +84,7 @@ SRCDIRS               += drivers/bitbash
 SRCDIRS                += drivers/infiniband
 SRCDIRS                += interface/pxe interface/efi interface/smbios
 SRCDIRS                += interface/bofm
+SRCDIRS                += interface/xen
 SRCDIRS                += tests
 SRCDIRS                += crypto crypto/axtls crypto/matrixssl
 SRCDIRS                += hci hci/commands hci/tui
index cdd397d40093230f55b59b427e4545dcd5387103..e555587df98251844c2b64321ef082ca7eb1064f 100644 (file)
@@ -8,6 +8,7 @@ SRCDIRS         += arch/x86/core
 SRCDIRS                += arch/x86/interface/efi
 SRCDIRS                += arch/x86/prefix
 SRCDIRS                += arch/x86/hci/commands
+SRCDIRS                += arch/x86/drivers/xen
 
 # breaks building some of the linux-related objects
 CFLAGS         += -Ulinux
diff --git a/src/arch/x86/drivers/xen/hvm.c b/src/arch/x86/drivers/xen/hvm.c
new file mode 100644 (file)
index 0000000..fbbcdf0
--- /dev/null
@@ -0,0 +1,521 @@
+/*
+ * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ipxe/malloc.h>
+#include <ipxe/pci.h>
+#include <ipxe/cpuid.h>
+#include <ipxe/msr.h>
+#include <ipxe/xen.h>
+#include <ipxe/xenver.h>
+#include <ipxe/xenmem.h>
+#include <ipxe/xenstore.h>
+#include <ipxe/xenbus.h>
+#include <ipxe/xengrant.h>
+#include "hvm.h"
+
+/** @file
+ *
+ * Xen HVM driver
+ *
+ */
+
+/**
+ * Get CPUID base
+ *
+ * @v hvm              HVM device
+ * @ret rc             Return status code
+ */
+static int hvm_cpuid_base ( struct hvm_device *hvm ) {
+       struct {
+               uint32_t ebx;
+               uint32_t ecx;
+               uint32_t edx;
+       } __attribute__ (( packed )) signature;
+       uint32_t base;
+       uint32_t version;
+       uint32_t discard_eax;
+       uint32_t discard_ebx;
+       uint32_t discard_ecx;
+       uint32_t discard_edx;
+
+       /* Scan for magic signature */
+       for ( base = HVM_CPUID_MIN ; base <= HVM_CPUID_MAX ;
+             base += HVM_CPUID_STEP ) {
+               cpuid ( base, &discard_eax, &signature.ebx, &signature.ecx,
+                       &signature.edx );
+               if ( memcmp ( &signature, HVM_CPUID_MAGIC,
+                             sizeof ( signature ) ) == 0 ) {
+                       hvm->cpuid_base = base;
+                       cpuid ( ( base + HVM_CPUID_VERSION ), &version,
+                               &discard_ebx, &discard_ecx, &discard_edx );
+                       DBGC2 ( hvm, "HVM using CPUID base %#08x (v%d.%d)\n",
+                               base, ( version >> 16 ), ( version & 0xffff ) );
+                       return 0;
+               }
+       }
+
+       DBGC ( hvm, "HVM could not find hypervisor\n" );
+       return -ENODEV;
+}
+
+/**
+ * Map hypercall page(s)
+ *
+ * @v hvm              HVM device
+ * @ret rc             Return status code
+ */
+static int hvm_map_hypercall ( struct hvm_device *hvm ) {
+       uint32_t pages;
+       uint32_t msr;
+       uint32_t discard_ecx;
+       uint32_t discard_edx;
+       physaddr_t hypercall_phys;
+       uint32_t version;
+       static xen_extraversion_t extraversion;
+       int xenrc;
+       int rc;
+
+       /* Get number of hypercall pages and MSR to use */
+       cpuid ( ( hvm->cpuid_base + HVM_CPUID_PAGES ), &pages, &msr,
+               &discard_ecx, &discard_edx );
+
+       /* Allocate pages */
+       hvm->hypercall_len = ( pages * PAGE_SIZE );
+       hvm->xen.hypercall = malloc_dma ( hvm->hypercall_len, PAGE_SIZE );
+       if ( ! hvm->xen.hypercall ) {
+               DBGC ( hvm, "HVM could not allocate %d hypercall page(s)\n",
+                      pages );
+               return -ENOMEM;
+       }
+       hypercall_phys = virt_to_phys ( hvm->xen.hypercall );
+       DBGC2 ( hvm, "HVM hypercall page(s) at [%#08lx,%#08lx) via MSR %#08x\n",
+               hypercall_phys, ( hypercall_phys + hvm->hypercall_len ), msr );
+
+       /* Write to MSR */
+       wrmsr ( msr, hypercall_phys );
+
+       /* Check that hypercall mechanism is working */
+       version = xenver_version ( &hvm->xen );
+       if ( ( xenrc = xenver_extraversion ( &hvm->xen, &extraversion ) ) != 0){
+               rc = -EXEN ( xenrc );
+               DBGC ( hvm, "HVM could not get extraversion: %s\n",
+                      strerror ( rc ) );
+               return rc;
+       }
+       DBGC2 ( hvm, "HVM found Xen version %d.%d%s\n",
+               ( version >> 16 ), ( version & 0xffff ) , extraversion );
+
+       return 0;
+}
+
+/**
+ * Unmap hypercall page(s)
+ *
+ * @v hvm              HVM device
+ */
+static void hvm_unmap_hypercall ( struct hvm_device *hvm ) {
+
+       /* Free pages */
+       free_dma ( hvm->xen.hypercall, hvm->hypercall_len );
+}
+
+/**
+ * Allocate and map MMIO space
+ *
+ * @v hvm              HVM device
+ * @v space            Source mapping space
+ * @v pages            Number of pages
+ * @ret mmio           MMIO space address, or NULL on error
+ */
+static void * hvm_ioremap ( struct hvm_device *hvm, unsigned int space,
+                           unsigned int pages ) {
+       struct xen_add_to_physmap add;
+       struct xen_remove_from_physmap remove;
+       physaddr_t mmio_phys;
+       unsigned int i;
+       size_t len;
+       void *mmio;
+       int xenrc;
+       int rc;
+
+       /* Check for available space */
+       len = ( pages * PAGE_SIZE );
+       if ( ( hvm->mmio_offset + len ) > hvm->mmio_len ) {
+               DBGC ( hvm, "HVM could not allocate %zd bytes of MMIO space "
+                      "(%zd of %zd remaining)\n", len,
+                      ( hvm->mmio_len - hvm->mmio_offset ), hvm->mmio_len );
+               goto err_no_space;
+       }
+
+       /* Map this space */
+       mmio = ioremap ( ( hvm->mmio + hvm->mmio_offset ), len );
+       if ( ! mmio ) {
+               DBGC ( hvm, "HVM could not map MMIO space [%08lx,%08lx)\n",
+                      ( hvm->mmio + hvm->mmio_offset ),
+                      ( hvm->mmio + hvm->mmio_offset + len ) );
+               goto err_ioremap;
+       }
+       mmio_phys = virt_to_phys ( mmio );
+
+       /* Add to physical address space */
+       for ( i = 0 ; i < pages ; i++ ) {
+               add.domid = DOMID_SELF;
+               add.idx = i;
+               add.space = space;
+               add.gpfn = ( ( mmio_phys / PAGE_SIZE ) + i );
+               if ( ( xenrc = xenmem_add_to_physmap ( &hvm->xen, &add ) ) !=0){
+                       rc = -EXEN ( xenrc );
+                       DBGC ( hvm, "HVM could not add space %d idx %d at "
+                              "[%08lx,%08lx): %s\n", space, i,
+                              ( mmio_phys + ( i * PAGE_SIZE ) ),
+                              ( mmio_phys + ( ( i + 1 ) * PAGE_SIZE ) ),
+                              strerror ( rc ) );
+                       goto err_add_to_physmap;
+               }
+       }
+
+       /* Update offset */
+       hvm->mmio_offset += len;
+
+       return mmio;
+
+       i = pages;
+ err_add_to_physmap:
+       for ( i-- ; ( signed int ) i >= 0 ; i-- ) {
+               remove.domid = DOMID_SELF;
+               add.gpfn = ( ( mmio_phys / PAGE_SIZE ) + i );
+               xenmem_remove_from_physmap ( &hvm->xen, &remove );
+       }
+       iounmap ( mmio );
+ err_ioremap:
+ err_no_space:
+       return NULL;
+}
+
+/**
+ * Unmap MMIO space
+ *
+ * @v hvm              HVM device
+ * @v mmio             MMIO space address
+ * @v pages            Number of pages
+ */
+static void hvm_iounmap ( struct hvm_device *hvm, void *mmio,
+                         unsigned int pages ) {
+       struct xen_remove_from_physmap remove;
+       physaddr_t mmio_phys = virt_to_phys ( mmio );
+       unsigned int i;
+       int xenrc;
+       int rc;
+
+       /* Unmap this space */
+       iounmap ( mmio );
+
+       /* Remove from physical address space */
+       for ( i = 0 ; i < pages ; i++ ) {
+               remove.domid = DOMID_SELF;
+               remove.gpfn = ( ( mmio_phys / PAGE_SIZE ) + i );
+               if ( ( xenrc = xenmem_remove_from_physmap ( &hvm->xen,
+                                                           &remove ) ) != 0 ) {
+                       rc = -EXEN ( xenrc );
+                       DBGC ( hvm, "HVM could not remove space [%08lx,%08lx): "
+                              "%s\n", ( mmio_phys + ( i * PAGE_SIZE ) ),
+                              ( mmio_phys + ( ( i + 1 ) * PAGE_SIZE ) ),
+                              strerror ( rc ) );
+                       /* Nothing we can do about this */
+               }
+       }
+}
+
+/**
+ * Map shared info page
+ *
+ * @v hvm              HVM device
+ * @ret rc             Return status code
+ */
+static int hvm_map_shared_info ( struct hvm_device *hvm ) {
+       physaddr_t shared_info_phys;
+       int rc;
+
+       /* Map shared info page */
+       hvm->xen.shared = hvm_ioremap ( hvm, XENMAPSPACE_shared_info, 1 );
+       if ( ! hvm->xen.shared ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       shared_info_phys = virt_to_phys ( hvm->xen.shared );
+       DBGC2 ( hvm, "HVM shared info page at [%#08lx,%#08lx)\n",
+               shared_info_phys, ( shared_info_phys + PAGE_SIZE ) );
+
+       /* Sanity check */
+       DBGC2 ( hvm, "HVM wallclock time is %d\n",
+               readl ( &hvm->xen.shared->wc_sec ) );
+
+       return 0;
+
+       hvm_iounmap ( hvm, hvm->xen.shared, 1 );
+ err_alloc:
+       return rc;
+}
+
+/**
+ * Unmap shared info page
+ *
+ * @v hvm              HVM device
+ */
+static void hvm_unmap_shared_info ( struct hvm_device *hvm ) {
+
+       /* Unmap shared info page */
+       hvm_iounmap ( hvm, hvm->xen.shared, 1 );
+}
+
+/**
+ * Map grant table
+ *
+ * @v hvm              HVM device
+ * @ret rc             Return status code
+ */
+static int hvm_map_grant ( struct hvm_device *hvm ) {
+       struct gnttab_query_size size;
+       struct gnttab_set_version version;
+       physaddr_t grant_phys;
+       size_t len;
+       int xenrc;
+       int rc;
+
+       /* Get grant table size */
+       size.dom = DOMID_SELF;
+       if ( ( xenrc = xengrant_query_size ( &hvm->xen, &size ) ) != 0 ) {
+               rc = -EXEN ( xenrc );
+               DBGC ( hvm, "HVM could not get grant table size: %s\n",
+                      strerror ( rc ) );
+               goto err_query_size;
+       }
+       len = ( size.nr_frames * PAGE_SIZE );
+
+       /* Configure to use version 2 tables */
+       version.version = 2;
+       if ( ( xenrc = xengrant_set_version ( &hvm->xen, &version ) ) != 0 ) {
+               rc = -EXEN ( xenrc );
+               DBGC ( hvm, "HVM could not set version 2 grant table: %s\n",
+                      strerror ( rc ) );
+               goto err_set_version;
+       }
+       if ( version.version != 2 ) {
+               DBGC ( hvm, "HVM could not set version 2 grant table\n" );
+               rc = -ENOTTY;
+               goto err_set_version;
+       }
+
+       /* Map grant table */
+       hvm->xen.grant.table = hvm_ioremap ( hvm, XENMAPSPACE_grant_table,
+                                            size.nr_frames );
+       if ( ! hvm->xen.grant.table ) {
+               rc = -ENODEV;
+               goto err_ioremap;
+       }
+       grant_phys = virt_to_phys ( hvm->xen.grant.table );
+       DBGC2 ( hvm, "HVM mapped grant table at [%08lx,%08lx)\n",
+               grant_phys, ( grant_phys + len ) );
+       hvm->xen.grant.count = ( len / sizeof ( hvm->xen.grant.table[0] ) );
+
+       return 0;
+
+       hvm_iounmap ( hvm, hvm->xen.grant.table, size.nr_frames );
+ err_ioremap:
+ err_set_version:
+ err_query_size:
+       return rc;
+}
+
+/**
+ * Unmap grant table
+ *
+ * @v hvm              HVM device
+ */
+static void hvm_unmap_grant ( struct hvm_device *hvm ) {
+       size_t len;
+
+       /* Unmap grant table */
+       len = ( hvm->xen.grant.count * sizeof ( hvm->xen.grant.table[0] ) );
+       hvm_iounmap ( hvm, hvm->xen.grant.table, ( len / PAGE_SIZE ) );
+}
+
+/**
+ * Map XenStore
+ *
+ * @v hvm              HVM device
+ * @ret rc             Return status code
+ */
+static int hvm_map_xenstore ( struct hvm_device *hvm ) {
+       uint64_t xenstore_evtchn;
+       uint64_t xenstore_pfn;
+       physaddr_t xenstore_phys;
+       char *name;
+       int xenrc;
+       int rc;
+
+       /* Get XenStore event channel */
+       if ( ( xenrc = xen_hvm_get_param ( &hvm->xen, HVM_PARAM_STORE_EVTCHN,
+                                          &xenstore_evtchn ) ) != 0 ) {
+               rc = -EXEN ( xenrc );
+               DBGC ( hvm, "HVM could not get XenStore event channel: %s\n",
+                      strerror ( rc ) );
+               return rc;
+       }
+       hvm->xen.store.port = xenstore_evtchn;
+
+       /* Get XenStore PFN */
+       if ( ( xenrc = xen_hvm_get_param ( &hvm->xen, HVM_PARAM_STORE_PFN,
+                                          &xenstore_pfn ) ) != 0 ) {
+               rc = -EXEN ( xenrc );
+               DBGC ( hvm, "HVM could not get XenStore PFN: %s\n",
+                      strerror ( rc ) );
+               return rc;
+       }
+       xenstore_phys = ( xenstore_pfn * PAGE_SIZE );
+
+       /* Map XenStore */
+       hvm->xen.store.intf = ioremap ( xenstore_phys, PAGE_SIZE );
+       if ( ! hvm->xen.store.intf ) {
+               DBGC ( hvm, "HVM could not map XenStore at [%08lx,%08lx)\n",
+                      xenstore_phys, ( xenstore_phys + PAGE_SIZE ) );
+               return -ENODEV;
+       }
+       DBGC2 ( hvm, "HVM mapped XenStore at [%08lx,%08lx) with event port "
+               "%d\n", xenstore_phys, ( xenstore_phys + PAGE_SIZE ),
+               hvm->xen.store.port );
+
+       /* Check that XenStore is working */
+       if ( ( rc = xenstore_read ( &hvm->xen, &name, "name", NULL ) ) != 0 ) {
+               DBGC ( hvm, "HVM could not read domain name: %s\n",
+                      strerror ( rc ) );
+               return rc;
+       }
+       DBGC2 ( hvm, "HVM running in domain \"%s\"\n", name );
+       free ( name );
+
+       return 0;
+}
+
+/**
+ * Unmap XenStore
+ *
+ * @v hvm              HVM device
+ */
+static void hvm_unmap_xenstore ( struct hvm_device *hvm ) {
+
+       /* Unmap XenStore */
+       iounmap ( hvm->xen.store.intf );
+}
+
+/**
+ * Probe PCI device
+ *
+ * @v pci              PCI device
+ * @ret rc             Return status code
+ */
+static int hvm_probe ( struct pci_device *pci ) {
+       struct hvm_device *hvm;
+       int rc;
+
+       /* Allocate and initialise structure */
+       hvm = zalloc ( sizeof ( *hvm ) );
+       if ( ! hvm ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       hvm->mmio = pci_bar_start ( pci, HVM_MMIO_BAR );
+       hvm->mmio_len = pci_bar_size ( pci, HVM_MMIO_BAR );
+       DBGC2 ( hvm, "HVM has MMIO space [%08lx,%08lx)\n",
+               hvm->mmio, ( hvm->mmio + hvm->mmio_len ) );
+
+       /* Fix up PCI device */
+       adjust_pci_device ( pci );
+
+       /* Attach to hypervisor */
+       if ( ( rc = hvm_cpuid_base ( hvm ) ) != 0 )
+               goto err_cpuid_base;
+       if ( ( rc = hvm_map_hypercall ( hvm ) ) != 0 )
+               goto err_map_hypercall;
+       if ( ( rc = hvm_map_shared_info ( hvm ) ) != 0 )
+               goto err_map_shared_info;
+       if ( ( rc = hvm_map_grant ( hvm ) ) != 0 )
+               goto err_map_grant;
+       if ( ( rc = hvm_map_xenstore ( hvm ) ) != 0 )
+               goto err_map_xenstore;
+
+       /* Probe Xen devices */
+       if ( ( rc = xenbus_probe ( &hvm->xen, &pci->dev ) ) != 0 ) {
+               DBGC ( hvm, "HVM could not probe Xen bus: %s\n",
+                      strerror ( rc ) );
+               goto err_xenbus_probe;
+       }
+
+       pci_set_drvdata ( pci, hvm );
+       return 0;
+
+       xenbus_remove ( &hvm->xen, &pci->dev );
+ err_xenbus_probe:
+       hvm_unmap_xenstore ( hvm );
+ err_map_xenstore:
+       hvm_unmap_grant ( hvm );
+ err_map_grant:
+       hvm_unmap_shared_info ( hvm );
+ err_map_shared_info:
+       hvm_unmap_hypercall ( hvm );
+ err_map_hypercall:
+ err_cpuid_base:
+       free ( hvm );
+ err_alloc:
+       return rc;
+}
+
+/**
+ * Remove PCI device
+ *
+ * @v pci              PCI device
+ */
+static void hvm_remove ( struct pci_device *pci ) {
+       struct hvm_device *hvm = pci_get_drvdata ( pci );
+
+       xenbus_remove ( &hvm->xen, &pci->dev );
+       hvm_unmap_xenstore ( hvm );
+       hvm_unmap_grant ( hvm );
+       hvm_unmap_shared_info ( hvm );
+       hvm_unmap_hypercall ( hvm );
+       free ( hvm );
+}
+
+/** PCI device IDs */
+static struct pci_device_id hvm_ids[] = {
+       PCI_ROM ( 0x5853, 0x0001, "hvm", "hvm", 0 ),
+};
+
+/** PCI driver */
+struct pci_driver hvm_driver __pci_driver = {
+       .ids = hvm_ids,
+       .id_count = ( sizeof ( hvm_ids ) / sizeof ( hvm_ids[0] ) ),
+       .probe = hvm_probe,
+       .remove = hvm_remove,
+};
diff --git a/src/arch/x86/drivers/xen/hvm.h b/src/arch/x86/drivers/xen/hvm.h
new file mode 100644 (file)
index 0000000..325d20d
--- /dev/null
@@ -0,0 +1,75 @@
+#ifndef _HVM_H
+#define _HVM_H
+
+/** @file
+ *
+ * Xen HVM driver
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <ipxe/xen.h>
+#include <xen/hvm/hvm_op.h>
+#include <xen/hvm/params.h>
+
+/** Minimum CPUID base */
+#define HVM_CPUID_MIN 0x40000000UL
+
+/** Maximum CPUID base */
+#define HVM_CPUID_MAX 0x4000ff00UL
+
+/** Increment between CPUID bases */
+#define HVM_CPUID_STEP 0x00000100UL
+
+/** Magic signature */
+#define HVM_CPUID_MAGIC "XenVMMXenVMM"
+
+/** Get Xen version */
+#define HVM_CPUID_VERSION 1
+
+/** Get number of hypercall pages */
+#define HVM_CPUID_PAGES 2
+
+/** PCI MMIO BAR */
+#define HVM_MMIO_BAR PCI_BASE_ADDRESS_1
+
+/** A Xen HVM device */
+struct hvm_device {
+       /** Xen hypervisor */
+       struct xen_hypervisor xen;
+       /** CPUID base */
+       uint32_t cpuid_base;
+       /** Length of hypercall table */
+       size_t hypercall_len;
+       /** MMIO base address */
+       unsigned long mmio;
+       /** Current offset within MMIO address space */
+       size_t mmio_offset;
+       /** Length of MMIO address space */
+       size_t mmio_len;
+};
+
+/**
+ * Get HVM parameter value
+ *
+ * @v xen              Xen hypervisor
+ * @v index            Parameter index
+ * @v value            Value to fill in
+ * @ret xenrc          Xen status code
+ */
+static inline int xen_hvm_get_param ( struct xen_hypervisor *xen,
+                                     unsigned int index, uint64_t *value ) {
+       struct xen_hvm_param param;
+       int xenrc;
+
+       param.domid = DOMID_SELF;
+       param.index = index;
+       xenrc = xen_hypercall_2 ( xen, __HYPERVISOR_hvm_op, HVMOP_get_param,
+                                 virt_to_phys ( &param ) );
+       *value = param.value;
+       return xenrc;
+}
+
+#endif /* _HVM_H */
index acf8c3e392135023465a7f2bb1c864579b54e6d4..624575621617aaaeee0f8287fef37be539fbd462 100644 (file)
@@ -45,6 +45,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 
 #define ERRFILE_timer_rdtsc   ( ERRFILE_ARCH | ERRFILE_DRIVER | 0x00000000 )
 #define ERRFILE_timer_bios    ( ERRFILE_ARCH | ERRFILE_DRIVER | 0x00010000 )
+#define ERRFILE_hvm          ( ERRFILE_ARCH | ERRFILE_DRIVER | 0x00020000 )
 
 #define ERRFILE_cpuid_cmd      ( ERRFILE_ARCH | ERRFILE_OTHER | 0x00000000 )
 #define ERRFILE_cpuid_settings ( ERRFILE_ARCH | ERRFILE_OTHER | 0x00010000 )
diff --git a/src/arch/x86/include/bits/xen.h b/src/arch/x86/include/bits/xen.h
new file mode 100644 (file)
index 0000000..dbccf1b
--- /dev/null
@@ -0,0 +1,164 @@
+#ifndef _BITS_XEN_H
+#define _BITS_XEN_H
+
+/** @file
+ *
+ * Xen interface
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/* Hypercall registers */
+#ifdef __x86_64__
+#define XEN_REG1 "rdi"
+#define XEN_REG2 "rsi"
+#define XEN_REG3 "rdx"
+#define XEN_REG4 "r10"
+#define XEN_REG5 "r8"
+#else
+#define XEN_REG1 "ebx"
+#define XEN_REG2 "ecx"
+#define XEN_REG3 "edx"
+#define XEN_REG4 "esi"
+#define XEN_REG5 "edi"
+#endif
+
+/** A hypercall entry point */
+struct xen_hypercall {
+       /** Code generated by hypervisor */
+       uint8_t code[32];
+} __attribute__ (( packed ));
+
+/**
+ * Issue hypercall with one argument
+ *
+ * @v xen              Xen hypervisor
+ * @v hypercall                Hypercall number
+ * @v arg1             First argument
+ * @ret retval         Return value
+ */
+static inline __attribute__ (( always_inline )) unsigned long
+xen_hypercall_1 ( struct xen_hypervisor *xen, unsigned int hypercall,
+                 unsigned long arg1 ) {
+       register unsigned long reg1 asm ( XEN_REG1 ) = arg1;
+       unsigned long retval;
+
+       __asm__ __volatile__ ( "call *%2"
+                              : "=a" ( retval ), "+r" ( reg1 )
+                              : "r" ( &xen->hypercall[hypercall] )
+                              : XEN_REG2, XEN_REG3, XEN_REG4, XEN_REG5,
+                                "memory" );
+       return retval;
+}
+
+/**
+ * Issue hypercall with two arguments
+ *
+ * @v xen              Xen hypervisor
+ * @v hypercall                Hypercall number
+ * @v arg1             First argument
+ * @v arg2             Second argument
+ * @ret retval         Return value
+ */
+static inline __attribute__ (( always_inline )) unsigned long
+xen_hypercall_2 ( struct xen_hypervisor *xen, unsigned int hypercall,
+                 unsigned long arg1, unsigned long arg2 ) {
+       register unsigned long reg1 asm ( XEN_REG1 ) = arg1;
+       register unsigned long reg2 asm ( XEN_REG2 ) = arg2;
+       unsigned long retval;
+
+       __asm__ __volatile__ ( "call *%3"
+                              : "=a" ( retval ), "+r" ( reg1 ), "+r" ( reg2 )
+                              : "r" ( &xen->hypercall[hypercall] )
+                              : XEN_REG3, XEN_REG4, XEN_REG5, "memory" );
+       return retval;
+}
+
+/**
+ * Issue hypercall with three arguments
+ *
+ * @v xen              Xen hypervisor
+ * @v hypercall                Hypercall number
+ * @v arg1             First argument
+ * @v arg2             Second argument
+ * @v arg3             Third argument
+ * @ret retval         Return value
+ */
+static inline __attribute__ (( always_inline )) unsigned long
+xen_hypercall_3 ( struct xen_hypervisor *xen, unsigned int hypercall,
+                 unsigned long arg1, unsigned long arg2, unsigned long arg3 ) {
+       register unsigned long reg1 asm ( XEN_REG1 ) = arg1;
+       register unsigned long reg2 asm ( XEN_REG2 ) = arg2;
+       register unsigned long reg3 asm ( XEN_REG3 ) = arg3;
+       unsigned long retval;
+
+       __asm__ __volatile__ ( "call *%4"
+                              : "=a" ( retval ), "+r" ( reg1 ), "+r" ( reg2 ),
+                                "+r" ( reg3 )
+                              : "r" ( &xen->hypercall[hypercall] )
+                              : XEN_REG4, XEN_REG5, "memory" );
+       return retval;
+}
+
+/**
+ * Issue hypercall with four arguments
+ *
+ * @v xen              Xen hypervisor
+ * @v hypercall                Hypercall number
+ * @v arg1             First argument
+ * @v arg2             Second argument
+ * @v arg3             Third argument
+ * @v arg4             Fourth argument
+ * @ret retval         Return value
+ */
+static inline __attribute__ (( always_inline )) unsigned long
+xen_hypercall_4 ( struct xen_hypervisor *xen, unsigned int hypercall,
+                 unsigned long arg1, unsigned long arg2, unsigned long arg3,
+                 unsigned long arg4 ) {
+       register unsigned long reg1 asm ( XEN_REG1 ) = arg1;
+       register unsigned long reg2 asm ( XEN_REG2 ) = arg2;
+       register unsigned long reg3 asm ( XEN_REG3 ) = arg3;
+       register unsigned long reg4 asm ( XEN_REG4 ) = arg4;
+       unsigned long retval;
+
+       __asm__ __volatile__ ( "call *%5"
+                              : "=a" ( retval ), "+r" ( reg1 ), "+r" ( reg2 ),
+                                "+r" ( reg3 ), "+r" ( reg4 )
+                              : "r" ( &xen->hypercall[hypercall] )
+                              : XEN_REG5, "memory" );
+       return retval;
+}
+
+/**
+ * Issue hypercall with five arguments
+ *
+ * @v xen              Xen hypervisor
+ * @v hypercall                Hypercall number
+ * @v arg1             First argument
+ * @v arg2             Second argument
+ * @v arg3             Third argument
+ * @v arg4             Fourth argument
+ * @v arg5             Fifth argument
+ * @ret retval         Return value
+ */
+static inline __attribute__ (( always_inline )) unsigned long
+xen_hypercall_5 ( struct xen_hypervisor *xen, unsigned int hypercall,
+                 unsigned long arg1, unsigned long arg2, unsigned long arg3,
+                 unsigned long arg4, unsigned long arg5 ) {
+       register unsigned long reg1 asm ( XEN_REG1 ) = arg1;
+       register unsigned long reg2 asm ( XEN_REG2 ) = arg2;
+       register unsigned long reg3 asm ( XEN_REG3 ) = arg3;
+       register unsigned long reg4 asm ( XEN_REG4 ) = arg4;
+       register unsigned long reg5 asm ( XEN_REG5 ) = arg5;
+       unsigned long retval;
+
+       __asm__ __volatile__ ( "call *%6"
+                              : "=a" ( retval ), "+r" ( reg1 ), "+r" ( reg2 ),
+                                "+r" ( reg3 ), "+r" ( reg4 ), "+r" ( reg5 )
+                              : "r" ( &xen->hypercall[hypercall] )
+                              : "memory" );
+       return retval;
+}
+
+#endif /* _BITS_XEN_H */
index 895097349b1e4514e3fb42990c4d786347678e99..7202a69665c79c3cd0e3252bb934675d335b03ee 100644 (file)
@@ -60,6 +60,9 @@ struct device_description {
 /** EFI bus type */
 #define BUS_TYPE_EFI 7
 
+/** Xen bus type */
+#define BUS_TYPE_XEN 8
+
 /** A hardware device */
 struct device {
        /** Name */
index a8b3a8e4ae76f2fbf1afb0f9d6a98a8518bf20a4..3fa499ddbb986921acc43d05aef3ca4b3778f242 100644 (file)
@@ -301,6 +301,9 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_memmap_settings              ( ERRFILE_OTHER | 0x003f0000 )
 #define ERRFILE_param_cmd            ( ERRFILE_OTHER | 0x00400000 )
 #define ERRFILE_deflate                      ( ERRFILE_OTHER | 0x00410000 )
+#define ERRFILE_xenstore             ( ERRFILE_OTHER | 0x00420000 )
+#define ERRFILE_xenbus               ( ERRFILE_OTHER | 0x00430000 )
+#define ERRFILE_xengrant             ( ERRFILE_OTHER | 0x00440000 )
 
 /** @} */
 
diff --git a/src/include/ipxe/xen.h b/src/include/ipxe/xen.h
new file mode 100644 (file)
index 0000000..546b0c3
--- /dev/null
@@ -0,0 +1,73 @@
+#ifndef _IPXE_XEN_H
+#define _IPXE_XEN_H
+
+/** @file
+ *
+ * Xen interface
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+/* Define Xen interface version before including any Xen header files */
+#define __XEN_INTERFACE_VERSION__ 0x00040400
+
+#include <stdint.h>
+#include <ipxe/uaccess.h>
+#include <xen/xen.h>
+#include <xen/event_channel.h>
+
+/* Memory barrier macros used by ring.h */
+#define xen_mb() mb()
+#define xen_rmb() rmb()
+#define xen_wmb() wmb()
+
+struct xen_hypercall;
+
+/** A Xen grant table */
+struct xen_grant {
+       /** Grant table entries */
+       union grant_entry_v2 *table;
+       /** Number of grant table entries (must be a power of two) */
+       unsigned int count;
+       /** Number of grant table entries in use */
+       unsigned int used;
+       /** Most recently used grant reference */
+       unsigned int ref;
+};
+
+/** A XenStore */
+struct xen_store {
+       /** XenStore domain interface */
+       struct xenstore_domain_interface *intf;
+       /** Event channel */
+       evtchn_port_t port;
+};
+
+/** A Xen hypervisor */
+struct xen_hypervisor {
+       /** Hypercall table */
+       struct xen_hypercall *hypercall;
+       /** Shared info page */
+       struct shared_info *shared;
+       /** Grant table */
+       struct xen_grant grant;
+       /** XenStore */
+       struct xen_store store;
+};
+
+#include <bits/xen.h>
+
+/**
+ * Convert a Xen status code to an iPXE status code
+ *
+ * @v xenrc            Xen status code (negated)
+ * @ret rc             iPXE status code (before negation)
+ *
+ * Xen status codes are defined in the file include/xen/errno.h in the
+ * Xen repository.  They happen to match the Linux error codes, some
+ * of which can be found in our include/ipxe/errno/linux.h.
+ */
+#define EXEN( xenrc ) EPLATFORM ( EINFO_EPLATFORM, -(xenrc) )
+
+#endif /* _IPXE_XEN_H */
diff --git a/src/include/ipxe/xenbus.h b/src/include/ipxe/xenbus.h
new file mode 100644 (file)
index 0000000..2777226
--- /dev/null
@@ -0,0 +1,85 @@
+#ifndef _IPXE_XENBUS_H
+#define _IPXE_XENBUS_H
+
+/** @file
+ *
+ * Xen device bus
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <ipxe/device.h>
+#include <ipxe/tables.h>
+#include <ipxe/xen.h>
+#include <xen/io/xenbus.h>
+
+/** A Xen device */
+struct xen_device {
+       /** Generic iPXE device */
+       struct device dev;
+       /** Xen hypervisor */
+       struct xen_hypervisor *xen;
+       /** XenStore key */
+       char *key;
+       /** Backend XenStore key */
+       char *backend;
+       /** Backend domain ID */
+       unsigned long backend_id;
+       /** Driver */
+       struct xen_driver *driver;
+       /** Driver-private data */
+       void *priv;
+};
+
+/** A Xen device driver */
+struct xen_driver {
+       /** Name */
+       const char *name;
+       /** Device type */
+       const char *type;
+       /** Probe device
+        *
+        * @v xendev            Xen device
+        * @ret rc              Return status code
+        */
+       int ( * probe ) ( struct xen_device *xendev );
+       /** Remove device
+        *
+        * @v xendev            Xen device
+        */
+       void ( * remove ) ( struct xen_device *xendev );
+};
+
+/** Xen device driver table */
+#define XEN_DRIVERS __table ( struct xen_driver, "xen_drivers" )
+
+/** Declare a Xen device driver */
+#define __xen_driver __table_entry ( XEN_DRIVERS, 01 )
+
+/**
+ * Set Xen device driver-private data
+ *
+ * @v xendev           Xen device
+ * @v priv             Private data
+ */
+static inline void xen_set_drvdata ( struct xen_device *xendev, void *priv ) {
+       xendev->priv = priv;
+}
+
+/**
+ * Get Xen device driver-private data
+ *
+ * @v xendev           Xen device
+ * @ret priv           Private data
+ */
+static inline void * xen_get_drvdata ( struct xen_device *xendev ) {
+       return xendev->priv;
+}
+
+extern int xenbus_set_state ( struct xen_device *xendev, int state );
+extern int xenbus_backend_wait ( struct xen_device *xendev, int state );
+extern int xenbus_probe ( struct xen_hypervisor *xen, struct device *parent );
+extern void xenbus_remove ( struct xen_hypervisor *xen, struct device *parent );
+
+#endif /* _IPXE_XENBUS_H */
diff --git a/src/include/ipxe/xenevent.h b/src/include/ipxe/xenevent.h
new file mode 100644 (file)
index 0000000..1dd6a0c
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef _IPXE_XENEVENT_H
+#define _IPXE_XENEVENT_H
+
+/** @file
+ *
+ * Xen events
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <ipxe/xen.h>
+#include <xen/event_channel.h>
+
+/**
+ * Close event channel
+ *
+ * @v xen              Xen hypervisor
+ * @v close            Event descriptor
+ * @ret xenrc          Xen status code
+ */
+static inline __attribute__ (( always_inline )) int
+xenevent_close ( struct xen_hypervisor *xen, struct evtchn_close *close ) {
+
+       return xen_hypercall_2 ( xen, __HYPERVISOR_event_channel_op,
+                                EVTCHNOP_close, virt_to_phys ( close ) );
+}
+
+/**
+ * Send event
+ *
+ * @v xen              Xen hypervisor
+ * @v send             Event descriptor
+ * @ret xenrc          Xen status code
+ */
+static inline __attribute__ (( always_inline )) int
+xenevent_send ( struct xen_hypervisor *xen, struct evtchn_send *send ) {
+
+       return xen_hypercall_2 ( xen, __HYPERVISOR_event_channel_op,
+                                EVTCHNOP_send, virt_to_phys ( send ) );
+}
+
+/**
+ * Allocate an unbound event channel
+ *
+ * @v xen              Xen hypervisor
+ * @v alloc_unbound    Event descriptor
+ * @ret xenrc          Xen status code
+ */
+static inline __attribute__ (( always_inline )) int
+xenevent_alloc_unbound ( struct xen_hypervisor *xen,
+                        struct evtchn_alloc_unbound *alloc_unbound ) {
+
+       return xen_hypercall_2 ( xen, __HYPERVISOR_event_channel_op,
+                                EVTCHNOP_alloc_unbound,
+                                virt_to_phys ( alloc_unbound ) );
+}
+
+#endif /* _IPXE_XENEVENT_H */
diff --git a/src/include/ipxe/xengrant.h b/src/include/ipxe/xengrant.h
new file mode 100644 (file)
index 0000000..776eb92
--- /dev/null
@@ -0,0 +1,102 @@
+#ifndef _IPXE_XENGRANT_H
+#define _IPXE_XENGRANT_H
+
+/** @file
+ *
+ * Xen grant tables
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <ipxe/io.h>
+#include <ipxe/xen.h>
+#include <xen/grant_table.h>
+
+/**
+ * Query grant table size
+ *
+ * @v xen              Xen hypervisor
+ * @v size             Table size
+ * @ret xenrc          Xen status code
+ */
+static inline __attribute__ (( always_inline )) int
+xengrant_query_size ( struct xen_hypervisor *xen,
+                     struct gnttab_query_size *size ) {
+
+       return xen_hypercall_3 ( xen, __HYPERVISOR_grant_table_op,
+                                GNTTABOP_query_size,
+                                virt_to_phys ( size ), 1 );
+}
+
+/**
+ * Set grant table version
+ *
+ * @v xen              Xen hypervisor
+ * @v version          Version
+ * @ret xenrc          Xen status code
+ */
+static inline __attribute__ (( always_inline )) int
+xengrant_set_version ( struct xen_hypervisor *xen,
+                      struct gnttab_set_version *version ) {
+
+       return xen_hypercall_3 ( xen, __HYPERVISOR_grant_table_op,
+                                GNTTABOP_set_version,
+                                virt_to_phys ( version ), 1 );
+}
+
+/**
+ * Invalidate access to a page
+ *
+ * @v xen              Xen hypervisor
+ * @v ref              Grant reference
+ */
+static inline __attribute__ (( always_inline )) void
+xengrant_invalidate ( struct xen_hypervisor *xen, grant_ref_t ref ) {
+       union grant_entry_v2 *entry = &xen->grant.table[ref];
+
+       /* Sanity check */
+       assert ( ( readw ( &entry->hdr.flags ) &
+                  ( GTF_reading | GTF_writing ) ) == 0 );
+
+       /* This should apparently be done using a cmpxchg instruction.
+        * We omit this: partly in the interests of simplicity, but
+        * mainly since our control flow generally does not permit
+        * failure paths to themselves fail.
+        */
+       writew ( 0, &entry->hdr.flags );
+}
+
+/**
+ * Permit access to a page
+ *
+ * @v xen              Xen hypervisor
+ * @v ref              Grant reference
+ * @v domid            Domain ID
+ * @v subflags         Additional flags
+ * @v page             Page start
+ */
+static inline __attribute__ (( always_inline )) void
+xengrant_permit_access ( struct xen_hypervisor *xen, grant_ref_t ref,
+                        domid_t domid, unsigned int subflags, void *page ) {
+       union grant_entry_v2 *entry = &xen->grant.table[ref];
+       unsigned long frame = ( virt_to_phys ( page ) / PAGE_SIZE );
+
+       writew ( domid, &entry->full_page.hdr.domid );
+       if ( sizeof ( physaddr_t ) == sizeof ( uint64_t ) ) {
+               writeq ( frame, &entry->full_page.frame );
+       } else {
+               writel ( frame, &entry->full_page.frame );
+       }
+       wmb();
+       writew ( ( GTF_permit_access | subflags ), &entry->full_page.hdr.flags);
+       wmb();
+}
+
+extern int xengrant_alloc ( struct xen_hypervisor *xen, grant_ref_t *refs,
+                           unsigned int count );
+extern void xengrant_free ( struct xen_hypervisor *xen, grant_ref_t *refs,
+                           unsigned int count );
+
+#endif /* _IPXE_XENGRANT_H */
diff --git a/src/include/ipxe/xenmem.h b/src/include/ipxe/xenmem.h
new file mode 100644 (file)
index 0000000..9b9aeda
--- /dev/null
@@ -0,0 +1,46 @@
+#ifndef _IPXE_XENMEM_H
+#define _IPXE_XENMEM_H
+
+/** @file
+ *
+ * Xen memory operations
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <ipxe/xen.h>
+#include <xen/memory.h>
+
+/**
+ * Add page to physical address space
+ *
+ * @v xen              Xen hypervisor
+ * @v add              Page mapping descriptor
+ * @ret xenrc          Xen status code
+ */
+static inline __attribute__ (( always_inline )) int
+xenmem_add_to_physmap ( struct xen_hypervisor *xen,
+                       struct xen_add_to_physmap *add ) {
+
+       return xen_hypercall_2 ( xen, __HYPERVISOR_memory_op,
+                                XENMEM_add_to_physmap, virt_to_phys ( add ) );
+}
+
+/**
+ * Remove page from physical address space
+ *
+ * @v xen              Xen hypervisor
+ * @v remove           Page mapping descriptor
+ * @ret xenrc          Xen status code
+ */
+static inline __attribute__ (( always_inline )) int
+xenmem_remove_from_physmap ( struct xen_hypervisor *xen,
+                            struct xen_remove_from_physmap *remove ) {
+
+       return xen_hypercall_2 ( xen, __HYPERVISOR_memory_op,
+                                XENMEM_remove_from_physmap,
+                                virt_to_phys ( remove ) );
+}
+
+#endif /* _IPXE_XENMEM_H */
diff --git a/src/include/ipxe/xenstore.h b/src/include/ipxe/xenstore.h
new file mode 100644 (file)
index 0000000..f25f157
--- /dev/null
@@ -0,0 +1,29 @@
+#ifndef _IPXE_XENSTORE_H
+#define _IPXE_XENSTORE_H
+
+/** @file
+ *
+ * XenStore interface
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <ipxe/xen.h>
+
+extern __attribute__ (( sentinel )) int
+xenstore_read ( struct xen_hypervisor *xen, char **value, ... );
+extern __attribute__ (( sentinel )) int
+xenstore_read_num ( struct xen_hypervisor *xen, unsigned long *num, ... );
+extern __attribute__ (( sentinel )) int
+xenstore_write ( struct xen_hypervisor *xen, const char *value, ... );
+extern __attribute__ (( sentinel )) int
+xenstore_write_num ( struct xen_hypervisor *xen, unsigned long num, ... );
+extern __attribute__ (( sentinel )) int
+xenstore_rm ( struct xen_hypervisor *xen, ... );
+extern __attribute__ (( sentinel )) int
+xenstore_directory ( struct xen_hypervisor *xen, char **children, size_t *len,
+                    ... );
+extern void xenstore_dump ( struct xen_hypervisor *xen, const char *key );
+
+#endif /* _IPXE_XENSTORE_H */
diff --git a/src/include/ipxe/xenver.h b/src/include/ipxe/xenver.h
new file mode 100644 (file)
index 0000000..5d678c5
--- /dev/null
@@ -0,0 +1,44 @@
+#ifndef _IPXE_XENVER_H
+#define _IPXE_VENVER_H
+
+/** @file
+ *
+ * Xen version
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <ipxe/xen.h>
+#include <xen/version.h>
+
+/**
+ * Get Xen version
+ *
+ * @v xen              Xen hypervisor
+ * @ret version                Version (major.minor: 16 bits each)
+ */
+static inline __attribute__ (( always_inline )) uint32
+xenver_version ( struct xen_hypervisor *xen ) {
+
+       return xen_hypercall_2 ( xen, __HYPERVISOR_xen_version,
+                                XENVER_version, 0 );
+}
+
+/**
+ * Get Xen extra version string
+ *
+ * @v xen              Xen hypervisor
+ * @v extraversion     Extra version string to fill in
+ * @ret xenrc          Xen status code
+ */
+static inline __attribute__ (( always_inline )) int
+xenver_extraversion ( struct xen_hypervisor *xen,
+                     xen_extraversion_t *extraversion ) {
+
+       return xen_hypercall_2 ( xen, __HYPERVISOR_xen_version,
+                                XENVER_extraversion,
+                                virt_to_phys ( extraversion ) );
+}
+
+#endif /* _IPXE_XENVER_H */
diff --git a/src/interface/xen/xenbus.c b/src/interface/xen/xenbus.c
new file mode 100644 (file)
index 0000000..7ac4189
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+ * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdio.h>
+#include <errno.h>
+#include <ipxe/malloc.h>
+#include <ipxe/device.h>
+#include <ipxe/timer.h>
+#include <ipxe/nap.h>
+#include <ipxe/xen.h>
+#include <ipxe/xenstore.h>
+#include <ipxe/xenbus.h>
+
+/** @file
+ *
+ * Xen device bus
+ *
+ */
+
+/* Disambiguate the various error causes */
+#define ETIMEDOUT_UNKNOWN                                              \
+       __einfo_error ( EINFO_ETIMEDOUT_UNKNOWN )
+#define EINFO_ETIMEDOUT_UNKNOWN                                                \
+       __einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateUnknown,          \
+                         "Unknown" )
+#define ETIMEDOUT_INITIALISING                                         \
+       __einfo_error ( EINFO_ETIMEDOUT_INITIALISING )
+#define EINFO_ETIMEDOUT_INITIALISING                                   \
+       __einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateInitialising,     \
+                         "Initialising" )
+#define ETIMEDOUT_INITWAIT                                             \
+       __einfo_error ( EINFO_ETIMEDOUT_INITWAIT )
+#define EINFO_ETIMEDOUT_INITWAIT                                       \
+       __einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateInitWait,         \
+                         "InitWait" )
+#define ETIMEDOUT_INITIALISED                                          \
+       __einfo_error ( EINFO_ETIMEDOUT_INITIALISED )
+#define EINFO_ETIMEDOUT_INITIALISED                                    \
+       __einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateInitialised,      \
+                         "Initialised" )
+#define ETIMEDOUT_CONNECTED                                            \
+       __einfo_error ( EINFO_ETIMEDOUT_CONNECTED )
+#define EINFO_ETIMEDOUT_CONNECTED                                      \
+       __einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateConnected,        \
+                         "Connected" )
+#define ETIMEDOUT_CLOSING                                              \
+       __einfo_error ( EINFO_ETIMEDOUT_CLOSING )
+#define EINFO_ETIMEDOUT_CLOSING                                                \
+       __einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateClosing,          \
+                         "Closing" )
+#define ETIMEDOUT_CLOSED                                               \
+       __einfo_error ( EINFO_ETIMEDOUT_CLOSED )
+#define EINFO_ETIMEDOUT_CLOSED                                         \
+       __einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateClosed,           \
+                         "Closed" )
+#define ETIMEDOUT_RECONFIGURING                                                \
+       __einfo_error ( EINFO_ETIMEDOUT_RECONFIGURING )
+#define EINFO_ETIMEDOUT_RECONFIGURING                                  \
+       __einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateReconfiguring,    \
+                         "Reconfiguring" )
+#define ETIMEDOUT_RECONFIGURED                                         \
+       __einfo_error ( EINFO_ETIMEDOUT_RECONFIGURED )
+#define EINFO_ETIMEDOUT_RECONFIGURED                                   \
+       __einfo_uniqify ( EINFO_ETIMEDOUT, XenbusStateReconfigured,     \
+                         "Reconfigured" )
+#define ETIMEDOUT_STATE( state )                                       \
+       EUNIQ ( EINFO_ETIMEDOUT, (state), ETIMEDOUT_UNKNOWN,            \
+               ETIMEDOUT_INITIALISING, ETIMEDOUT_INITWAIT,             \
+               ETIMEDOUT_INITIALISED, ETIMEDOUT_CONNECTED,             \
+               ETIMEDOUT_CLOSING, ETIMEDOUT_CLOSED,                    \
+               ETIMEDOUT_RECONFIGURING, ETIMEDOUT_RECONFIGURED )
+
+/** Maximum time to wait for backend to reach a given state, in ticks */
+#define XENBUS_BACKEND_TIMEOUT ( 5 * TICKS_PER_SEC )
+
+/**
+ * Set device state
+ *
+ * @v xendev           Xen device
+ * @v state            New state
+ * @ret rc             Return status code
+ */
+int xenbus_set_state ( struct xen_device *xendev, int state ) {
+       int rc;
+
+       /* Attempt to set state */
+       if ( ( rc = xenstore_write_num ( xendev->xen, state, xendev->key,
+                                        "state", NULL ) ) != 0 ) {
+               DBGC ( xendev, "XENBUS %s could not set state=\"%d\": %s\n",
+                      xendev->key, state, strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Get backend state
+ *
+ * @v xendev           Xen device
+ * @ret state          Backend state, or negative error
+ */
+static int xenbus_backend_state ( struct xen_device *xendev ) {
+       unsigned long state;
+       int rc;
+
+       /* Attempt to get backend state */
+       if ( ( rc = xenstore_read_num ( xendev->xen, &state, xendev->backend,
+                                       "state", NULL ) ) != 0 ) {
+               DBGC ( xendev, "XENBUS %s could not read %s/state: %s\n",
+                      xendev->key, xendev->backend, strerror ( rc ) );
+               return rc;
+       }
+
+       return state;
+}
+
+/**
+ * Wait for backend to reach a given state
+ *
+ * @v xendev           Xen device
+ * @v state            Desired backend state
+ * @ret rc             Return status code
+ */
+int xenbus_backend_wait ( struct xen_device *xendev, int state ) {
+       unsigned long started = currticks();
+       unsigned long elapsed;
+       unsigned int attempts = 0;
+       int current_state;
+       int rc;
+
+       /* Wait for backend to reach this state */
+       do {
+
+               /* Get current backend state */
+               current_state = xenbus_backend_state ( xendev );
+               if ( current_state < 0 ) {
+                       rc = current_state;
+                       return rc;
+               }
+               if ( current_state == state )
+                       return 0;
+
+               /* Allow time for backend to react */
+               cpu_nap();
+
+               /* XenStore is a very slow interface; any fixed delay
+                * time would be dwarfed by the XenStore access time.
+                * We therefore use wall clock to time out this
+                * operation.
+                */
+               elapsed = ( currticks() - started );
+               attempts++;
+
+       } while ( elapsed < XENBUS_BACKEND_TIMEOUT );
+
+       /* Construct status code from current backend state */
+       rc = -ETIMEDOUT_STATE ( current_state );
+       DBGC ( xendev, "XENBUS %s timed out after %d attempts waiting for "
+              "%s/state=\"%d\": %s\n", xendev->key, attempts, xendev->backend,
+              state, strerror ( rc ) );
+
+       return rc;
+}
+
+/**
+ * Find driver for Xen device
+ *
+ * @v type             Device type
+ * @ret driver         Driver, or NULL
+ */
+static struct xen_driver * xenbus_find_driver ( const char *type ) {
+       struct xen_driver *xendrv;
+
+       for_each_table_entry ( xendrv, XEN_DRIVERS ) {
+               if ( strcmp ( xendrv->type, type ) == 0 )
+                       return xendrv;
+       }
+       return NULL;
+}
+
+/**
+ * Probe Xen device
+ *
+ * @v xen              Xen hypervisor
+ * @v parent           Parent device
+ * @v type             Device type
+ * @v instance         Device instance
+ * @ret rc             Return status code
+ */
+static int xenbus_probe_device ( struct xen_hypervisor *xen,
+                                struct device *parent, const char *type,
+                                const char *instance ) {
+       struct xen_device *xendev;
+       size_t key_len;
+       int rc;
+
+       /* Allocate and initialise structure */
+       key_len = ( 7 /* "device/" */ + strlen ( type ) + 1 /* "/" */ +
+                   strlen ( instance ) + 1 /* NUL */ );
+       xendev = zalloc ( sizeof ( *xendev ) + key_len );
+       if ( ! xendev ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       snprintf ( xendev->dev.name, sizeof ( xendev->dev.name ), "%s/%s",
+                  type, instance );
+       xendev->dev.desc.bus_type = BUS_TYPE_XEN;
+       INIT_LIST_HEAD ( &xendev->dev.children );
+       list_add_tail ( &xendev->dev.siblings, &parent->children );
+       xendev->dev.parent = parent;
+       xendev->xen = xen;
+       xendev->key = ( ( void * ) ( xendev + 1 ) );
+       snprintf ( xendev->key, key_len, "device/%s/%s", type, instance );
+
+       /* Read backend key */
+       if ( ( rc = xenstore_read ( xen, &xendev->backend, xendev->key,
+                                   "backend", NULL ) ) != 0 ) {
+               DBGC ( xendev, "XENBUS %s could not read backend: %s\n",
+                      xendev->key, strerror ( rc ) );
+               goto err_read_backend;
+       }
+
+       /* Read backend domain ID */
+       if ( ( rc = xenstore_read_num ( xen, &xendev->backend_id, xendev->key,
+                                       "backend-id", NULL ) ) != 0 ) {
+               DBGC ( xendev, "XENBUS %s could not read backend-id: %s\n",
+                      xendev->key, strerror ( rc ) );
+               goto err_read_backend_id;
+       }
+       DBGC ( xendev, "XENBUS %s backend=\"%s\" in domain %ld\n",
+              xendev->key, xendev->backend, xendev->backend_id );
+
+       /* Look for a driver */
+       xendev->driver = xenbus_find_driver ( type );
+       if ( ! xendev->driver ) {
+               DBGC ( xendev, "XENBUS %s has no driver\n", xendev->key );
+               /* Not a fatal error */
+               rc = 0;
+               goto err_no_driver;
+       }
+       xendev->dev.driver_name = xendev->driver->name;
+       DBGC ( xendev, "XENBUS %s has driver \"%s\"\n", xendev->key,
+              xendev->driver->name );
+
+       /* Probe driver */
+       if ( ( rc = xendev->driver->probe ( xendev ) ) != 0 ) {
+               DBGC ( xendev, "XENBUS could not probe %s: %s\n",
+                      xendev->key, strerror ( rc ) );
+               goto err_probe;
+       }
+
+       return 0;
+
+       xendev->driver->remove ( xendev );
+ err_probe:
+ err_no_driver:
+ err_read_backend_id:
+       free ( xendev->backend );
+ err_read_backend:
+       list_del ( &xendev->dev.siblings );
+       free ( xendev );
+ err_alloc:
+       return rc;
+}
+
+/**
+ * Remove Xen device
+ *
+ * @v xendev           Xen device
+ */
+static void xenbus_remove_device ( struct xen_device *xendev ) {
+
+       /* Remove device */
+       xendev->driver->remove ( xendev );
+       free ( xendev->backend );
+       list_del ( &xendev->dev.siblings );
+       free ( xendev );
+}
+
+/**
+ * Probe Xen devices of a given type
+ *
+ * @v xen              Xen hypervisor
+ * @v parent           Parent device
+ * @v type             Device type
+ * @ret rc             Return status code
+ */
+static int xenbus_probe_type ( struct xen_hypervisor *xen,
+                              struct device *parent, const char *type ) {
+       char *children;
+       char *child;
+       size_t len;
+       int rc;
+
+       /* Get children of this key */
+       if ( ( rc = xenstore_directory ( xen, &children, &len, "device",
+                                        type, NULL ) ) != 0 ) {
+               DBGC ( xen, "XENBUS could not list \"%s\" devices: %s\n",
+                      type, strerror ( rc ) );
+               goto err_directory;
+       }
+
+       /* Probe each child */
+       for ( child = children ; child < ( children + len ) ;
+             child += ( strlen ( child ) + 1 /* NUL */ ) ) {
+               if ( ( rc = xenbus_probe_device ( xen, parent, type,
+                                                 child ) ) != 0 )
+                       goto err_probe_device;
+       }
+
+       free ( children );
+       return 0;
+
+ err_probe_device:
+       free ( children );
+ err_directory:
+       return rc;
+}
+
+/**
+ * Probe Xen bus
+ *
+ * @v xen              Xen hypervisor
+ * @v parent           Parent device
+ * @ret rc             Return status code
+ */
+int xenbus_probe ( struct xen_hypervisor *xen, struct device *parent ) {
+       char *types;
+       char *type;
+       size_t len;
+       int rc;
+
+       /* Get children of "device" key */
+       if ( ( rc = xenstore_directory ( xen, &types, &len, "device",
+                                        NULL ) ) != 0 ) {
+               DBGC ( xen, "XENBUS could not list device types: %s\n",
+                      strerror ( rc ) );
+               goto err_directory;
+       }
+
+       /* Probe each child type */
+       for ( type = types ; type < ( types + len ) ;
+             type += ( strlen ( type ) + 1 /* NUL */ ) ) {
+               if ( ( rc = xenbus_probe_type ( xen, parent, type ) ) != 0 )
+                       goto err_probe_type;
+       }
+
+       free ( types );
+       return 0;
+
+       xenbus_remove ( xen, parent );
+ err_probe_type:
+       free ( types );
+ err_directory:
+       return rc;
+}
+
+/**
+ * Remove Xen bus
+ *
+ * @v xen              Xen hypervisor
+ * @v parent           Parent device
+ */
+void xenbus_remove ( struct xen_hypervisor *xen __unused,
+                    struct device *parent ) {
+       struct xen_device *xendev;
+       struct xen_device *tmp;
+
+       /* Remove devices */
+       list_for_each_entry_safe ( xendev, tmp, &parent->children,
+                                  dev.siblings ) {
+               xenbus_remove_device ( xendev );
+       }
+}
diff --git a/src/interface/xen/xengrant.c b/src/interface/xen/xengrant.c
new file mode 100644 (file)
index 0000000..55731bf
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <errno.h>
+#include <assert.h>
+#include <ipxe/io.h>
+#include <ipxe/xen.h>
+#include <ipxe/xengrant.h>
+
+/** @file
+ *
+ * Xen grant tables
+ *
+ */
+
+/**
+ * Allocate grant references
+ *
+ * @v xen              Xen hypervisor
+ * @v refs             Grant references to fill in
+ * @v count            Number of references
+ * @ret rc             Return status code
+ */
+int xengrant_alloc ( struct xen_hypervisor *xen, grant_ref_t *refs,
+                    unsigned int count ) {
+       union grant_entry_v2 *entry;
+       unsigned int mask = ( xen->grant.count - 1 );
+       unsigned int check = 0;
+       unsigned int avail;
+       unsigned int ref;
+
+       /* Fail unless we have enough references available */
+       avail = ( xen->grant.count - xen->grant.used -
+                 GNTTAB_NR_RESERVED_ENTRIES );
+       if ( avail < count ) {
+               DBGC ( xen, "XENGRANT cannot allocate %d references (only %d "
+                      "of %d available)\n", count, avail, xen->grant.count );
+               return -ENOBUFS;
+       }
+       DBGC ( xen, "XENGRANT allocating %d references (from %d of %d "
+              "available)\n", count, avail, xen->grant.count );
+
+       /* Update number of references used */
+       xen->grant.used += count;
+
+       /* Find unused references */
+       for ( ref = xen->grant.ref ; count ; ref = ( ( ref + 1 ) & mask ) ) {
+
+               /* Sanity check */
+               assert ( check++ < xen->grant.count );
+
+               /* Skip reserved references */
+               if ( ref < GNTTAB_NR_RESERVED_ENTRIES )
+                       continue;
+
+               /* Skip in-use references */
+               entry = &xen->grant.table[ref];
+               if ( readw ( &entry->hdr.flags ) & GTF_type_mask )
+                       continue;
+               if ( readw ( &entry->hdr.domid ) == DOMID_SELF )
+                       continue;
+
+               /* Mark reference as in-use.  We leave the flags as
+                * empty (to avoid creating a valid grant table entry)
+                * and set the domid to DOMID_SELF.
+                */
+               writew ( DOMID_SELF, &entry->hdr.domid );
+               DBGC2 ( xen, "XENGRANT allocated ref %d\n", ref );
+
+               /* Record reference */
+               refs[--count] = ref;
+       }
+
+       /* Update cursor */
+       xen->grant.ref = ref;
+
+       return 0;
+}
+
+/**
+ * Free grant references
+ *
+ * @v xen              Xen hypervisor
+ * @v refs             Grant references
+ * @v count            Number of references
+ */
+void xengrant_free ( struct xen_hypervisor *xen, grant_ref_t *refs,
+                    unsigned int count ) {
+       union grant_entry_v2 *entry;
+       unsigned int ref;
+       unsigned int i;
+
+       /* Free references */
+       for ( i = 0 ; i < count ; i++ ) {
+
+               /* Sanity check */
+               ref = refs[i];
+               assert ( ref < xen->grant.count );
+
+               /* Mark reference as unused */
+               entry = &xen->grant.table[ref];
+               writew ( 0, &entry->hdr.flags );
+               writew ( 0, &entry->hdr.domid );
+               DBGC2 ( xen, "XENGRANT freed ref %d\n", ref );
+       }
+}
diff --git a/src/interface/xen/xenstore.c b/src/interface/xen/xenstore.c
new file mode 100644 (file)
index 0000000..b969829
--- /dev/null
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 (at your option) 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ipxe/io.h>
+#include <ipxe/nap.h>
+#include <ipxe/malloc.h>
+#include <ipxe/xen.h>
+#include <ipxe/xenevent.h>
+#include <ipxe/xenstore.h>
+
+/*
+ * xs_wire.h attempts to define a static error table xsd_errors, which
+ * interacts badly with the dynamically generated error numbers used
+ * by iPXE.  Prevent this table from being constructed by including
+ * errno.h only after including xs_wire.h.
+ *
+ */
+#include <xen/io/xs_wire.h>
+#include <errno.h>
+
+/** @file
+ *
+ * XenStore interface
+ *
+ */
+
+/** Request identifier */
+static uint32_t xenstore_req_id;
+
+/**
+ * Send XenStore request raw data
+ *
+ * @v xen              Xen hypervisor
+ * @v data             Data buffer
+ * @v len              Length of data
+ */
+static void xenstore_send ( struct xen_hypervisor *xen, const void *data,
+                           size_t len ) {
+       struct xenstore_domain_interface *intf = xen->store.intf;
+       XENSTORE_RING_IDX prod = readl ( &intf->req_prod );
+       XENSTORE_RING_IDX cons;
+       XENSTORE_RING_IDX idx;
+       const char *bytes = data;
+       size_t offset = 0;
+       size_t fill;
+
+       DBGCP ( intf, "XENSTORE raw request:\n" );
+       DBGCP_HDA ( intf, MASK_XENSTORE_IDX ( prod ), data, len );
+
+       /* Write one byte at a time */
+       while ( offset < len ) {
+
+               /* Wait for space to become available */
+               while ( 1 ) {
+                       cons = readl ( &intf->req_cons );
+                       fill = ( prod - cons );
+                       if ( fill < XENSTORE_RING_SIZE )
+                               break;
+                       DBGC2 ( xen, "." );
+                       cpu_nap();
+                       rmb();
+               }
+
+               /* Write byte */
+               idx = MASK_XENSTORE_IDX ( prod++ );
+               writeb ( bytes[offset++], &intf->req[idx] );
+       }
+
+       /* Update producer counter */
+       wmb();
+       writel ( prod, &intf->req_prod );
+       wmb();
+}
+
+/**
+ * Send XenStore request string (excluding terminating NUL)
+ *
+ * @v xen              Xen hypervisor
+ * @v string           String
+ */
+static void xenstore_send_string ( struct xen_hypervisor *xen,
+                                  const char *string ) {
+
+       xenstore_send ( xen, string, strlen ( string ) );
+}
+
+/**
+ * Receive XenStore response raw data
+ *
+ * @v xen              Xen hypervisor
+ * @v data             Data buffer, or NULL to discard data
+ * @v len              Length of data
+ */
+static void xenstore_recv ( struct xen_hypervisor *xen, void *data,
+                           size_t len ) {
+       struct xenstore_domain_interface *intf = xen->store.intf;
+       XENSTORE_RING_IDX cons = readl ( &intf->rsp_cons );
+       XENSTORE_RING_IDX prod;
+       XENSTORE_RING_IDX idx;
+       char *bytes = data;
+       size_t offset = 0;
+       size_t fill;
+
+       DBGCP ( intf, "XENSTORE raw response:\n" );
+
+       /* Read one byte at a time */
+       while ( offset < len ) {
+
+               /* Wait for data to be ready */
+               while ( 1 ) {
+                       prod = readl ( &intf->rsp_prod );
+                       fill = ( prod - cons );
+                       if ( fill > 0 )
+                               break;
+                       DBGC2 ( xen, "." );
+                       cpu_nap();
+                       rmb();
+               }
+
+               /* Read byte */
+               idx = MASK_XENSTORE_IDX ( cons++ );
+               if ( data )
+                       bytes[offset++] = readb ( &intf->rsp[idx] );
+       }
+       if ( data )
+               DBGCP_HDA ( intf, MASK_XENSTORE_IDX ( cons - len ), data, len );
+
+       /* Update consumer counter */
+       writel ( cons, &intf->rsp_cons );
+       wmb();
+}
+
+/**
+ * Send XenStore request
+ *
+ * @v xen              Xen hypervisor
+ * @v type             Message type
+ * @v req_id           Request ID
+ * @v value            Value, or NULL to omit
+ * @v key              Key path components
+ * @ret rc             Return status code
+ */
+static int xenstore_request ( struct xen_hypervisor *xen,
+                             enum xsd_sockmsg_type type, uint32_t req_id,
+                             const char *value, va_list key ) {
+       struct xsd_sockmsg msg;
+       struct evtchn_send event;
+       const char *string;
+       va_list tmp;
+       int xenrc;
+       int rc;
+
+       /* Construct message header */
+       msg.type = type;
+       msg.req_id = req_id;
+       msg.tx_id = 0;
+       msg.len = 0;
+       DBGC2 ( xen, "XENSTORE request ID %d type %d ", req_id, type );
+
+       /* Calculate total length */
+       va_copy ( tmp, key );
+       while ( ( string = va_arg ( tmp, const char * ) ) != NULL ) {
+               DBGC2 ( xen, "%s%s", ( msg.len ? "/" : "" ), string );
+               msg.len += ( strlen ( string ) + 1 /* '/' or NUL */ );
+       }
+       va_end ( tmp );
+       if ( value ) {
+               DBGC2 ( xen, " = \"%s\"", value );
+               msg.len += strlen ( value );
+       }
+       DBGC2 ( xen, "\n" );
+
+       /* Send message */
+       xenstore_send ( xen, &msg, sizeof ( msg ) );
+       string = va_arg ( key, const char * );
+       assert ( string != NULL );
+       xenstore_send_string ( xen, string );
+       while ( ( string = va_arg ( key, const char * ) ) != NULL ) {
+               xenstore_send_string ( xen, "/" );
+               xenstore_send_string ( xen, string );
+       }
+       xenstore_send ( xen, "", 1 ); /* Separating NUL */
+       if ( value )
+               xenstore_send_string ( xen, value );
+
+       /* Notify the back end */
+       event.port = xen->store.port;
+       if ( ( xenrc = xenevent_send ( xen, &event ) ) != 0 ) {
+               rc = -EXEN ( xenrc );
+               DBGC ( xen, "XENSTORE could not notify back end: %s\n",
+                      strerror ( rc ) );
+               return rc;
+       }
+
+       return 0;
+}
+
+/**
+ * Receive XenStore response
+ *
+ * @v xen              Xen hypervisor
+ * @v req_id           Request ID
+ * @v value            Value to fill in
+ * @v len              Length to fill in
+ * @ret rc             Return status code
+ *
+ * The caller is responsible for eventually calling free() on the
+ * returned value.  Note that the value may comprise multiple
+ * NUL-terminated strings concatenated together.  A terminating NUL
+ * will always be appended to the returned value.
+ */
+static int xenstore_response ( struct xen_hypervisor *xen, uint32_t req_id,
+                              char **value, size_t *len ) {
+       struct xsd_sockmsg msg;
+       char *string;
+       int rc;
+
+       /* Receive message header */
+       xenstore_recv ( xen, &msg, sizeof ( msg ) );
+       *len = msg.len;
+
+       /* Allocate space for response */
+       *value = zalloc ( msg.len + 1 /* terminating NUL */ );
+
+       /* Receive data.  Do this even if allocation failed, or if the
+        * request ID was incorrect, to avoid leaving data in the
+        * ring.
+        */
+       xenstore_recv ( xen, *value, msg.len );
+
+       /* Validate request ID */
+       if ( msg.req_id != req_id ) {
+               DBGC ( xen, "XENSTORE response ID mismatch (got %d, expected "
+                      "%d)\n", msg.req_id, req_id );
+               rc = -EPROTO;
+               goto err_req_id;
+       }
+
+       /* Check for allocation failure */
+       if ( ! *value ) {
+               DBGC ( xen, "XENSTORE could not allocate %d bytes for "
+                      "response\n", msg.len );
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+
+       /* Check for explicit errors */
+       if ( msg.type == XS_ERROR ) {
+               DBGC ( xen, "XENSTORE response error \"%s\"\n", *value );
+               rc = -EIO;
+               goto err_explicit;
+       }
+
+       DBGC2 ( xen, "XENSTORE response ID %d\n", req_id );
+       if ( DBG_EXTRA ) {
+               for ( string = *value ; string < ( *value + msg.len ) ;
+                     string += ( strlen ( string ) + 1 /* NUL */ ) ) {
+                       DBGC2 ( xen, " - \"%s\"\n", string );
+               }
+       }
+       return 0;
+
+ err_explicit:
+ err_alloc:
+ err_req_id:
+       free ( *value );
+       *value = NULL;
+       return rc;
+}
+
+/**
+ * Issue a XenStore message
+ *
+ * @v xen              Xen hypervisor
+ * @v type             Message type
+ * @v response         Response value to fill in, or NULL to discard
+ * @v len              Response length to fill in, or NULL to ignore
+ * @v request          Request value, or NULL to omit
+ * @v key              Key path components
+ * @ret rc             Return status code
+ */
+static int xenstore_message ( struct xen_hypervisor *xen,
+                             enum xsd_sockmsg_type type, char **response,
+                             size_t *len, const char *request, va_list key ) {
+       char *response_value;
+       size_t response_len;
+       int rc;
+
+       /* Send request */
+       if ( ( rc = xenstore_request ( xen, type, ++xenstore_req_id,
+                                      request, key ) ) != 0 )
+               return rc;
+
+       /* Receive response */
+       if ( ( rc = xenstore_response ( xen, xenstore_req_id, &response_value,
+                                       &response_len ) ) != 0 )
+               return rc;
+
+       /* Return response, if applicable */
+       if ( response ) {
+               *response = response_value;
+       } else {
+               free ( response_value );
+       }
+       if ( len )
+               *len = response_len;
+
+       return 0;
+}
+
+/**
+ * Read XenStore value
+ *
+ * @v xen              Xen hypervisor
+ * @v value            Value to fill in
+ * @v key              Key path components
+ * @ret rc             Return status code
+ *
+ * On a successful return, the caller is responsible for calling
+ * free() on the returned value.
+ */
+static int xenstore_vread ( struct xen_hypervisor *xen, char **value,
+                           va_list key ) {
+
+       return xenstore_message ( xen, XS_READ, value, NULL, NULL, key );
+}
+
+/**
+ * Read XenStore value
+ *
+ * @v xen              Xen hypervisor
+ * @v value            Value to fill in
+ * @v ...              Key path components
+ * @ret rc             Return status code
+ *
+ * On a successful return, the caller is responsible for calling
+ * free() on the returned value.
+ */
+__attribute__ (( sentinel )) int
+xenstore_read ( struct xen_hypervisor *xen, char **value, ... ) {
+       va_list key;
+       int rc;
+
+       va_start ( key, value );
+       rc = xenstore_vread ( xen, value, key );
+       va_end ( key );
+       return rc;
+}
+
+/**
+ * Read XenStore numeric value
+ *
+ * @v xen              Xen hypervisor
+ * @v num              Numeric value to fill in
+ * @v ...              Key path components
+ * @ret rc             Return status code
+ */
+__attribute__ (( sentinel )) int
+xenstore_read_num ( struct xen_hypervisor *xen, unsigned long *num, ... ) {
+       va_list key;
+       char *value;
+       char *endp;
+       int rc;
+
+       /* Try to read text value */
+       va_start ( key, num );
+       rc = xenstore_vread ( xen, &value, key );
+       va_end ( key );
+       if ( rc != 0 )
+               goto err_read;
+
+       /* Try to parse as numeric value */
+       *num = strtoul ( value, &endp, 10 );
+       if ( ( *value == '\0' ) || ( *endp != '\0' ) ) {
+               DBGC ( xen, "XENSTORE found invalid numeric value \"%s\"\n",
+                      value );
+               rc = -EINVAL;
+               goto err_strtoul;
+       }
+
+ err_strtoul:
+       free ( value );
+ err_read:
+       return rc;
+}
+
+/**
+ * Write XenStore value
+ *
+ * @v xen              Xen hypervisor
+ * @v value            Value
+ * @v key              Key path components
+ * @ret rc             Return status code
+ */
+static int xenstore_vwrite ( struct xen_hypervisor *xen, const char *value,
+                            va_list key ) {
+
+       return xenstore_message ( xen, XS_WRITE, NULL, NULL, value, key );
+}
+
+/**
+ * Write XenStore value
+ *
+ * @v xen              Xen hypervisor
+ * @v value            Value
+ * @v ...              Key path components
+ * @ret rc             Return status code
+ */
+__attribute__ (( sentinel )) int
+xenstore_write ( struct xen_hypervisor *xen, const char *value, ... ) {
+       va_list key;
+       int rc;
+
+       va_start ( key, value );
+       rc = xenstore_vwrite ( xen, value, key );
+       va_end ( key );
+       return rc;
+}
+
+/**
+ * Write XenStore numeric value
+ *
+ * @v xen              Xen hypervisor
+ * @v num              Numeric value
+ * @v ...              Key path components
+ * @ret rc             Return status code
+ */
+__attribute__ (( sentinel )) int
+xenstore_write_num ( struct xen_hypervisor *xen, unsigned long num, ... ) {
+       char value[ 21 /* "18446744073709551615" + NUL */ ];
+       va_list key;
+       int rc;
+
+       /* Construct value */
+       snprintf ( value, sizeof ( value ), "%ld", num );
+
+       /* Write value */
+       va_start ( key, num );
+       rc = xenstore_vwrite ( xen, value, key );
+       va_end ( key );
+       return rc;
+}
+
+/**
+ * Delete XenStore value
+ *
+ * @v xen              Xen hypervisor
+ * @v ...              Key path components
+ * @ret rc             Return status code
+ */
+__attribute__ (( sentinel )) int
+xenstore_rm ( struct xen_hypervisor *xen, ... ) {
+       va_list key;
+       int rc;
+
+       va_start ( key, xen );
+       rc = xenstore_message ( xen, XS_RM, NULL, NULL, NULL, key );
+       va_end ( key );
+       return rc;
+}
+
+/**
+ * Read XenStore directory
+ *
+ * @v xen              Xen hypervisor
+ * @v children         Child key names to fill in
+ * @v len              Length of child key names to fill in
+ * @v ...              Key path components
+ * @ret rc             Return status code
+ */
+__attribute__ (( sentinel )) int
+xenstore_directory ( struct xen_hypervisor *xen, char **children, size_t *len,
+                    ... ) {
+       va_list key;
+       int rc;
+
+       va_start ( key, len );
+       rc = xenstore_message ( xen, XS_DIRECTORY, children, len, NULL, key );
+       va_end ( key );
+       return rc;
+}
+
+/**
+ * Dump XenStore directory contents (for debugging)
+ *
+ * @v xen              Xen hypervisor
+ * @v key              Key
+ */
+void xenstore_dump ( struct xen_hypervisor *xen, const char *key ) {
+       char *value;
+       char *children;
+       char *child;
+       char *child_key;
+       size_t len;
+       int rc;
+
+       /* Try to dump current key as a value */
+       if ( ( rc = xenstore_read ( xen, &value, key, NULL ) ) == 0 ) {
+               DBGC ( xen, "%s = \"%s\"\n", key, value );
+               free ( value );
+       }
+
+       /* Try to recurse into each child in turn */
+       if ( ( rc = xenstore_directory ( xen, &children, &len, key,
+                                        NULL ) ) == 0 ) {
+               for ( child = children ; child < ( children + len ) ;
+                     child += ( strlen ( child ) + 1 /* NUL */ ) ) {
+
+                       /* Construct child key */
+                       asprintf ( &child_key, "%s/%s", key, child );
+                       if ( ! child_key ) {
+                               DBGC ( xen, "XENSTORE could not allocate child "
+                                      "key \"%s/%s\"\n", key, child );
+                               rc = -ENOMEM;
+                               break;
+                       }
+
+                       /* Recurse into child key, continuing on error */
+                       xenstore_dump ( xen, child_key );
+                       free ( child_key );
+               }
+               free ( children );
+       }
+}