| /* | |
| * 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #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 ); | |
| } | |
| } |