/*
* 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.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <strings.h>
#include <errno.h>
#include <assert.h>
#include <ipxe/io.h>
#include <ipxe/xen.h>
#include <ipxe/xengrant.h>
/** @file
*
* Xen grant tables
*
*/
/** Grant table version to try setting
*
* Using version 1 grant tables limits guests to using 16TB of
* grantable RAM, and prevents the use of subpage grants. Some
* versions of the Xen hypervisor refuse to allow the grant table
* version to be set after the first grant references have been
* created, so the loaded operating system may be stuck with whatever
* choice we make here. We therefore currently use version 2 grant
* tables, since they give the most flexibility to the loaded OS.
*
* Current versions (7.2.0) of the Windows PV drivers have no support
* for version 2 grant tables, and will merrily create version 1
* entries in what the hypervisor believes to be a version 2 table.
* This causes some confusion.
*
* Avoid this problem by attempting to use version 1 tables, since
* otherwise we may render Windows unable to boot.
*
* Play nicely with other potential bootloaders by accepting either
* version 1 or version 2 grant tables (if we are unable to set our
* requested version).
*/
#define XENGRANT_TRY_VERSION 1
/**
* Initialise grant table
*
* @v xen Xen hypervisor
* @ret rc Return status code
*/
int xengrant_init ( struct xen_hypervisor *xen ) {
struct gnttab_query_size size;
struct gnttab_set_version set_version;
struct gnttab_get_version get_version;
struct grant_entry_v1 *v1;
union grant_entry_v2 *v2;
unsigned int version;
int xenrc;
int rc;
/* Get grant table size */
size.dom = DOMID_SELF;
if ( ( xenrc = xengrant_query_size ( xen, &size ) ) != 0 ) {
rc = -EXEN ( xenrc );
DBGC ( xen, "XENGRANT could not get table size: %s\n",
strerror ( rc ) );
return rc;
}
xen->grant.len = ( size.nr_frames * PAGE_SIZE );
/* Set grant table version, if applicable */
set_version.version = XENGRANT_TRY_VERSION;
if ( ( xenrc = xengrant_set_version ( xen, &set_version ) ) != 0 ) {
rc = -EXEN ( xenrc );
DBGC ( xen, "XENGRANT could not set version %d: %s\n",
XENGRANT_TRY_VERSION, strerror ( rc ) );
/* Continue; use whatever version is current */
}
/* Get grant table version */
get_version.dom = DOMID_SELF;
get_version.pad = 0;
if ( ( xenrc = xengrant_get_version ( xen, &get_version ) ) == 0 ) {
version = get_version.version;
switch ( version ) {
case 0:
/* Version not yet specified: will be version 1 */
version = 1;
break;
case 1 :
/* Version 1 table: nothing special to do */
break;
case 2:
/* Version 2 table: configure shift appropriately */
xen->grant.shift = ( fls ( sizeof ( *v2 ) /
sizeof ( *v1 ) ) - 1 );
break;
default:
/* Unsupported version */
DBGC ( xen, "XENGRANT detected unsupported version "
"%d\n", version );
return -ENOTSUP;
}
} else {
rc = -EXEN ( xenrc );
DBGC ( xen, "XENGRANT could not get version (assuming v1): "
"%s\n", strerror ( rc ) );
version = 1;
}
DBGC ( xen, "XENGRANT using v%d table with %d entries\n",
version, xengrant_entries ( xen ) );
return 0;
}
/**
* 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 ) {
struct grant_entry_header *hdr;
unsigned int entries = xengrant_entries ( xen );
unsigned int mask = ( entries - 1 );
unsigned int check = 0;
unsigned int avail;
unsigned int ref;
/* Fail unless we have enough references available */
avail = ( entries - 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, entries );
return -ENOBUFS;
}
DBGC ( xen, "XENGRANT allocating %d references (from %d of %d "
"available)\n", count, avail, entries );
/* 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++ < entries );
/* Skip reserved references */
if ( ref < GNTTAB_NR_RESERVED_ENTRIES )
continue;
/* Skip in-use references */
hdr = xengrant_header ( xen, ref );
if ( readw ( &hdr->flags ) & GTF_type_mask )
continue;
if ( readw ( &hdr->domid ) == DOMID_SELF )
continue;
/* Zero reference */
xengrant_zero ( xen, hdr );
/* 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, &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 ) {
struct grant_entry_header *hdr;
unsigned int ref;
unsigned int i;
/* Free references */
for ( i = 0 ; i < count ; i++ ) {
/* Sanity check */
ref = refs[i];
assert ( ref < xengrant_entries ( xen ) );
/* Zero reference */
hdr = xengrant_header ( xen, ref );
xengrant_zero ( xen, hdr );
DBGC2 ( xen, "XENGRANT freed ref %d\n", ref );
}
}