998 lines
32 KiB
C
998 lines
32 KiB
C
/*********************************************************
|
|
* Copyright (C) 2015-2017 VMware, Inc. All rights reserved.
|
|
*
|
|
* 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 version 2 and no 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 St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*********************************************************/
|
|
|
|
/*
|
|
* monLoader.c --
|
|
*
|
|
* Processes the monitor loader header.
|
|
*
|
|
* See monLoader.h for a full description.
|
|
*/
|
|
|
|
#ifdef VMX86_VMX
|
|
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include "vmx.h"
|
|
#include "vm_assert.h"
|
|
|
|
#elif defined VMKERNEL
|
|
|
|
#include "vmkernel.h"
|
|
#include "libc.h"
|
|
#include "vm_libc.h"
|
|
|
|
#define UINT_MAX (~0U)
|
|
|
|
#elif defined VMMON
|
|
|
|
#include "hostKernel.h"
|
|
|
|
#ifdef __linux__
|
|
# include "driver-config.h"
|
|
# include <linux/string.h>
|
|
# include <linux/kernel.h>
|
|
#elif defined __APPLE__
|
|
# include <machine/limits.h>
|
|
# include <string.h>
|
|
#else
|
|
# include <limits.h>
|
|
# include <string.h>
|
|
#endif
|
|
|
|
#include "vm_assert.h"
|
|
|
|
#else /* !defined VMX86_VMX && !defined VMKERNEL && !defined VMMON */
|
|
#error MonLoader cannot be built as part of this environment
|
|
#endif
|
|
|
|
#include "vm_basic_types.h"
|
|
#include "monLoader.h"
|
|
#include "vcpuid.h"
|
|
#include "x86types.h"
|
|
#include "x86paging_64.h"
|
|
#include "monLoaderLog.h"
|
|
|
|
#define CANONICAL_MASK MASK64(36)
|
|
/* The index of VPN v within an array of LxEs where index 0 maps VPN b. */
|
|
#define L4EArrayIdx(b,v) ((unsigned)((((v) - (b)) & CANONICAL_MASK) >> 36))
|
|
#define L3EArrayIdx(b,v) ((unsigned)((((v) - (b)) & CANONICAL_MASK) >> 27))
|
|
#define L2EArrayIdx(b,v) ((unsigned)((((v) - (b)) & CANONICAL_MASK) >> 18))
|
|
#define L1EArrayIdx(b,v) ((unsigned)((((v) - (b)) & CANONICAL_MASK) >> 9))
|
|
|
|
/*
|
|
* Import address space from VMX rather than building it. Once monitor loading
|
|
* is removed from VMX entirely, code to import will be deleted.
|
|
*/
|
|
#define IMPORT_AS_FROM_VMX (TRUE)
|
|
|
|
/* The maximum (canonical-address) VPN */
|
|
#define VPN_MAX MASK64(52)
|
|
|
|
/* Sufficient MPN counts to accommodate the monitor's top 64MB. */
|
|
#define L4MPNCOUNTMAX 1
|
|
#define L3MPNCOUNTMAX 1
|
|
#define L2MPNCOUNTMAX 1
|
|
#define L1MPNCOUNTMAX 32
|
|
|
|
#define LINE_INVALID ((unsigned)-1)
|
|
|
|
typedef struct MonPTMPNs {
|
|
MPN L4MPNs[L4MPNCOUNTMAX]; /* The page root. Definitely only one page. */
|
|
MPN L3MPNs[L3MPNCOUNTMAX];
|
|
MPN L2MPNs[L2MPNCOUNTMAX];
|
|
MPN L1MPNs[L1MPNCOUNTMAX];
|
|
unsigned L4MPNCount;
|
|
unsigned L3MPNCount;
|
|
unsigned L2MPNCount;
|
|
unsigned L1MPNCount;
|
|
} MonPTMPNs;
|
|
|
|
typedef struct MonLoaderContext {
|
|
MonLoaderEnvContext *envCtx; /* Environment-specific context */
|
|
struct {
|
|
MonPTMPNs ptMPNs; /* Mappings into the AS for the current VCPU. */
|
|
VPN ASFirstVPN; /* first VPN in the address space */
|
|
VPN ASLastVPN; /* last VPN in the address space (inclusive) */
|
|
uint64 ASPTEFlags; /* PTE flags for L4->L1 connection. */
|
|
Vcpuid currentVCPU;
|
|
Bool ASImported; /* Was this AS imported or created anew? */
|
|
Bool hasAddrSpace;
|
|
} vcpu;
|
|
} MonLoaderContext;
|
|
|
|
|
|
static const Bool importFromVMX = IMPORT_AS_FROM_VMX;
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderTranslateMonVPNToL1E --
|
|
*
|
|
* Translates a monitor VPN to a level 1 page table entry, for the current
|
|
* VCPU. Assumes preallocated and connected page tables, so only the L1
|
|
* table is examined.
|
|
*
|
|
* Result:
|
|
* ML_OK and a PTE if successful, some other value on error. Fails if a
|
|
* page table page is not already allocated or the VPN is not in the
|
|
* context's address space's defined range.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderTranslateMonVPNToL1E(MonLoaderContext *ctx, // IN/OUT
|
|
VPN monVPN, // IN
|
|
PT_L1E *pte) // OUT
|
|
{
|
|
Vcpuid vcpu = ctx->vcpu.currentVCPU;
|
|
/* Only modify the L1E as L4->L1 are guaranteed connected already. */
|
|
unsigned L1Off = PT_LPN_2_L1OFF(monVPN);
|
|
unsigned L1Page = L1EArrayIdx(ctx->vcpu.ASFirstVPN, monVPN);
|
|
|
|
if (monVPN < ctx->vcpu.ASFirstVPN || monVPN > ctx->vcpu.ASLastVPN) {
|
|
return ML_ERROR_INVALID_VPN; /* Address is outside address space. */
|
|
}
|
|
if (ctx->vcpu.ptMPNs.L1MPNs[L1Page] == INVALID_MPN) {
|
|
return ML_ERROR_MAP; /* Page table not sufficiently preallocated. */
|
|
}
|
|
if (!MonLoaderCallout_GetPTE(ctx->envCtx, ctx->vcpu.ptMPNs.L1MPNs[L1Page],
|
|
L1Off, vcpu, pte)) {
|
|
return ML_ERROR_CALLOUT_GETPTE;
|
|
}
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderIsMapped --
|
|
*
|
|
* Checks whether a VPN has an existing mapping on the current VCPU.
|
|
* Assumes preallocated and connected page tables.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error. Fails if a page table
|
|
* page is not already allocated or the VPN is not in the context's
|
|
* address space's defined range.
|
|
*
|
|
* Side effects:
|
|
* None.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderIsMapped(MonLoaderContext *ctx, // IN/OUT
|
|
VPN monVPN, // IN
|
|
Bool *mapped) // OUT
|
|
{
|
|
PT_L1E pte;
|
|
MonLoaderError ret = MonLoaderTranslateMonVPNToL1E(ctx, monVPN, &pte);
|
|
|
|
if (ret != ML_OK) {
|
|
return ret;
|
|
}
|
|
*mapped = PTE_PRESENT(pte);
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderAllocMPN --
|
|
*
|
|
* Allocates a new MPN.
|
|
*
|
|
* Result:
|
|
* ML_OK and an MPN, on success. An error and no MPN on failure.
|
|
*
|
|
* Side effects:
|
|
* An MPN may be allocated.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderAllocMPN(MonLoaderContext *ctx, // IN
|
|
MPN *mpn) // OUT
|
|
{
|
|
*mpn = MonLoaderCallout_AllocMPN(ctx->envCtx, ctx->vcpu.currentVCPU);
|
|
if (*mpn == INVALID_MPN) {
|
|
return ML_ERROR_ALLOC;
|
|
}
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderMapMPN --
|
|
*
|
|
* Maps an MPN into the current VCPU's address space at a VPN.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error. Fails if a page table
|
|
* page is not already allocated or the VPN is not in the context's
|
|
* address space's defined range.
|
|
*
|
|
* Side effects:
|
|
* An MPN is mapped on success. Nothing on failure.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderMapMPN(MonLoaderContext *ctx, // IN/OUT
|
|
MPN mpn, // IN
|
|
uint64 flags, // IN
|
|
VPN monVPN) // IN
|
|
{
|
|
MonPTMPNs *ptMPNs = &ctx->vcpu.ptMPNs;
|
|
/* Only modify the L1E, as L4->L1 are guaranteed connected already. */
|
|
unsigned L1Off = PT_LPN_2_L1OFF(monVPN);
|
|
unsigned L1Page = L1EArrayIdx(ctx->vcpu.ASFirstVPN, monVPN);
|
|
|
|
if (vmx86_debug) {
|
|
MonLoaderError ret;
|
|
Bool mapped;
|
|
if (monVPN < ctx->vcpu.ASFirstVPN || monVPN > ctx->vcpu.ASLastVPN) {
|
|
LOG(5, "%s: monVPN=0x%"FMTVPN"x, AS=0x%"FMTVPN"x-0x%"FMTVPN"x\n",
|
|
__FUNCTION__, monVPN, ctx->vcpu.ASFirstVPN,
|
|
ctx->vcpu.ASLastVPN);
|
|
return ML_ERROR_INVALID_VPN; /* Address is outside address space. */
|
|
}
|
|
if (ptMPNs->L1MPNs[L1Page] == INVALID_MPN) {
|
|
return ML_ERROR_MAP; /* Page table not sufficiently preallocated. */
|
|
}
|
|
ret = MonLoaderIsMapped(ctx, monVPN, &mapped);
|
|
if (ret != ML_OK) {
|
|
return ret;
|
|
}
|
|
if (mapped) {
|
|
return ML_ERROR_ALREADY_MAPPED;
|
|
}
|
|
}
|
|
if (!MonLoaderCallout_MapMPNInPTE(ctx->envCtx, ptMPNs->L1MPNs[L1Page], L1Off,
|
|
flags, mpn, ctx->vcpu.currentVCPU)) {
|
|
return ML_ERROR_CALLOUT_MAPINPTE;
|
|
}
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderCreateAddressSpace --
|
|
*
|
|
* Creates or (if importAS is set) verifies an address space. The VPN
|
|
* range specified by firstVPN and size is used to determine page counts
|
|
* at each page table level to map every page. Sufficient pages are then
|
|
* allocated or verified.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error.
|
|
*
|
|
* Side effects:
|
|
* MPNs are allocated if not importing. Sets ctx->vcpu.hasAddrSpace.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderCreateAddressSpace(MonLoaderContext *ctx, // IN/OUT
|
|
VPN firstVPN, // IN
|
|
uint64 flags, // IN
|
|
uint64 monPages, // IN
|
|
Bool importAS) // IN
|
|
{
|
|
Vcpuid vcpu = ctx->vcpu.currentVCPU;
|
|
VPN lastVPN = firstVPN + monPages - 1;
|
|
unsigned L3MPNsNeeded = L3EArrayIdx(0, lastVPN) -
|
|
L3EArrayIdx(0, firstVPN) + 1;
|
|
unsigned L2MPNsNeeded = L2EArrayIdx(0, lastVPN) -
|
|
L2EArrayIdx(0, firstVPN) + 1;
|
|
unsigned L1MPNsNeeded = L1EArrayIdx(0, lastVPN) -
|
|
L1EArrayIdx(0, firstVPN) + 1;
|
|
unsigned i;
|
|
MPN mpn;
|
|
MonPTMPNs *ptMPNs;
|
|
|
|
if (vcpu == VCPUID_INVALID) {
|
|
return ML_ERROR_ARGS;
|
|
}
|
|
|
|
/* Verify MonPTMPNs is large enough for this address space. */
|
|
if (L3MPNsNeeded > L3MPNCOUNTMAX || L2MPNsNeeded > L2MPNCOUNTMAX ||
|
|
L1MPNsNeeded > L1MPNCOUNTMAX) {
|
|
return ML_ERROR_ADDRSPACE_TOO_LARGE;
|
|
}
|
|
ctx->vcpu.ASFirstVPN = firstVPN;
|
|
ctx->vcpu.ASLastVPN = lastVPN;
|
|
ctx->vcpu.ASPTEFlags = flags;
|
|
ctx->vcpu.ASImported = importAS;
|
|
|
|
ptMPNs = &ctx->vcpu.ptMPNs;
|
|
|
|
if (importAS) {
|
|
mpn = MonLoaderCallout_GetPageRoot(ctx->envCtx, vcpu);
|
|
if (mpn == INVALID_MPN) {
|
|
return ML_ERROR_CALLOUT_PAGEROOT_GET;
|
|
}
|
|
LOG(5, "%s: vcpu %u page root=0x%"FMT64"x\n", __FUNCTION__, vcpu, mpn);
|
|
ptMPNs->L4MPNs[0] = mpn;
|
|
MonLoaderCallout_ImportPage(ctx->envCtx, mpn, vcpu);
|
|
} else {
|
|
NOT_IMPLEMENTED();
|
|
}
|
|
ptMPNs->L4MPNCount = 1;
|
|
|
|
for (i = 0; i < L3MPNsNeeded; i++) {
|
|
if (importAS) {
|
|
VPN monVPN = firstVPN + i * PT_PAGES_PER_L4E;
|
|
unsigned L4Off = PT_LPN_2_L4OFF(monVPN);
|
|
PT_L1E pte;
|
|
if (!MonLoaderCallout_GetPTE(ctx->envCtx, ptMPNs->L4MPNs[0], L4Off,
|
|
vcpu, &pte)) {
|
|
return ML_ERROR_CALLOUT_GETPTE;
|
|
}
|
|
LOG(5, "%s: monVPN=0x%"FMTVPN"x: L4E=0x%"FMT64"x\n", __FUNCTION__,
|
|
monVPN, pte);
|
|
mpn = LM_PTE_2_PFN(pte);
|
|
if (mpn == INVALID_MPN || !ML_PERMS_MATCH(pte, flags)) {
|
|
return ML_ERROR_PAGE_TABLE_IMPORT;
|
|
}
|
|
MonLoaderCallout_ImportPage(ctx->envCtx, mpn, vcpu);
|
|
} else {
|
|
NOT_IMPLEMENTED();
|
|
}
|
|
ptMPNs->L3MPNs[i] = mpn;
|
|
}
|
|
ptMPNs->L3MPNCount = L3MPNsNeeded;
|
|
|
|
for (i = 0; i < L2MPNsNeeded; i++) {
|
|
if (importAS) {
|
|
VPN monVPN = firstVPN + i * PT_PAGES_PER_L3E;
|
|
unsigned L3Off = PT_LPN_2_L3OFF(monVPN);
|
|
unsigned L3Page = L3EArrayIdx(ctx->vcpu.ASFirstVPN, monVPN);
|
|
PT_L1E pte;
|
|
if (!MonLoaderCallout_GetPTE(ctx->envCtx, ptMPNs->L3MPNs[L3Page],
|
|
L3Off, vcpu, &pte)) {
|
|
return ML_ERROR_CALLOUT_GETPTE;
|
|
}
|
|
LOG(5, "%s: monVPN=0x%"FMTVPN"x: L3E=0x%"FMT64"x\n", __FUNCTION__,
|
|
monVPN, pte);
|
|
mpn = LM_PTE_2_PFN(pte);
|
|
if (mpn == INVALID_MPN || !ML_PERMS_MATCH(pte, flags)) {
|
|
return ML_ERROR_PAGE_TABLE_IMPORT;
|
|
}
|
|
MonLoaderCallout_ImportPage(ctx->envCtx, mpn, vcpu);
|
|
} else {
|
|
NOT_IMPLEMENTED();
|
|
}
|
|
ptMPNs->L2MPNs[i] = mpn;
|
|
}
|
|
ptMPNs->L2MPNCount = L2MPNsNeeded;
|
|
|
|
for (i = 0; i < L1MPNsNeeded; i++) {
|
|
if (importAS) {
|
|
VPN monVPN = firstVPN + i * PT_PAGES_PER_L2E;
|
|
unsigned L2Off = PT_LPN_2_L2OFF(monVPN);
|
|
unsigned L2Page = L2EArrayIdx(ctx->vcpu.ASFirstVPN, monVPN);
|
|
PT_L1E pte;
|
|
if (!MonLoaderCallout_GetPTE(ctx->envCtx, ptMPNs->L2MPNs[L2Page],
|
|
L2Off, vcpu, &pte)) {
|
|
return ML_ERROR_CALLOUT_GETPTE;
|
|
}
|
|
LOG(5, "%s: monVPN=0x%"FMTVPN"x: L2E=0x%"FMT64"x\n", __FUNCTION__,
|
|
monVPN, pte);
|
|
mpn = LM_PTE_2_PFN(pte);
|
|
if (mpn == INVALID_MPN || !ML_PERMS_MATCH(pte, flags)) {
|
|
return ML_ERROR_PAGE_TABLE_IMPORT;
|
|
}
|
|
MonLoaderCallout_ImportPage(ctx->envCtx, mpn, vcpu);
|
|
} else {
|
|
NOT_IMPLEMENTED();
|
|
}
|
|
ptMPNs->L1MPNs[i] = mpn;
|
|
}
|
|
ptMPNs->L1MPNCount = L1MPNsNeeded;
|
|
|
|
ctx->vcpu.hasAddrSpace = TRUE;
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderMapPageTables --
|
|
*
|
|
* Maps in the page tables for the specified level, or verifies that the
|
|
* existing mappings match internal state and flags if the address space
|
|
* was imported. Assumes pre-allocated memory at all levels and
|
|
* pre-connected L4->L1 page tables.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error.
|
|
*
|
|
* Side effects:
|
|
* Page tables are updated if mappings are created.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderMapPageTables(MonLoaderContext *ctx, // IN/OUT
|
|
unsigned level, // IN
|
|
uint64 flags, // IN
|
|
VPN monVPN, // IN
|
|
uint64 monPages) // IN
|
|
{
|
|
Bool verify = ctx->vcpu.ASImported;
|
|
uint64 i;
|
|
unsigned count;
|
|
MPN *ptMPNs;
|
|
|
|
if (vmx86_debug) {
|
|
if (!ctx->vcpu.hasAddrSpace) {
|
|
return ML_ERROR_NO_ADDRSPACE;
|
|
}
|
|
if (level < 1 || level > 4) {
|
|
return ML_ERROR_ARGS;
|
|
}
|
|
}
|
|
|
|
switch (level) {
|
|
case 4:
|
|
count = ctx->vcpu.ptMPNs.L4MPNCount;
|
|
ptMPNs = ctx->vcpu.ptMPNs.L4MPNs;
|
|
break;
|
|
case 3:
|
|
count = ctx->vcpu.ptMPNs.L3MPNCount;
|
|
ptMPNs = ctx->vcpu.ptMPNs.L3MPNs;
|
|
break;
|
|
case 2:
|
|
count = ctx->vcpu.ptMPNs.L2MPNCount;
|
|
ptMPNs = ctx->vcpu.ptMPNs.L2MPNs;
|
|
break;
|
|
case 1:
|
|
count = ctx->vcpu.ptMPNs.L1MPNCount;
|
|
ptMPNs = ctx->vcpu.ptMPNs.L1MPNs;
|
|
break;
|
|
default:
|
|
return ML_ERROR_ARGS;
|
|
break;
|
|
|
|
if (monPages != count) {
|
|
return ML_ERROR_PAGE_TABLE_MAP_SIZE;
|
|
}
|
|
|
|
for (i = 0; i < monPages; i++) {
|
|
VPN vpn = monVPN + i;
|
|
if (verify) {
|
|
PT_L1E l1e;
|
|
if (MonLoaderTranslateMonVPNToL1E(ctx, vpn, &l1e) != ML_OK ||
|
|
LM_PTE_2_PFN(l1e) != ptMPNs[i] || !ML_PERMS_MATCH(l1e, flags)) {
|
|
return ML_ERROR_PAGE_TABLE_VERIFY;
|
|
}
|
|
} else {
|
|
NOT_IMPLEMENTED();
|
|
}
|
|
}
|
|
}
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderZero --
|
|
*
|
|
* Allocates new pages, zeroes them and maps them on the current VCPU.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error. The number of
|
|
* MPNs allocated in *allocs is incremented.
|
|
*
|
|
* Side effects:
|
|
* Allocations, zeroing, mapping and accounting are performed.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderZero(MonLoaderContext *ctx, // IN/OUT
|
|
uint64 flags, // IN
|
|
VPN monVPN, // IN
|
|
uint64 numPages, // IN
|
|
uint64 monPages, // IN
|
|
unsigned *allocs) // IN/OUT
|
|
{
|
|
uint64 i;
|
|
|
|
if (numPages == 0 || numPages > monPages) {
|
|
return ML_ERROR_SIZE;
|
|
}
|
|
for (i = 0; i < numPages; i++) {
|
|
MPN mpn;
|
|
MonLoaderError ret = MonLoaderAllocMPN(ctx, &mpn);
|
|
if (ret != ML_OK) {
|
|
return ret;
|
|
}
|
|
if (!MonLoaderCallout_FillPage(ctx->envCtx, 0, mpn,
|
|
ctx->vcpu.currentVCPU)) {
|
|
return ML_ERROR_CALLOUT_ZERO;
|
|
}
|
|
ret = MonLoaderMapMPN(ctx, mpn, flags, monVPN + i);
|
|
if (ret != ML_OK) {
|
|
return ret;
|
|
}
|
|
*allocs += 1;
|
|
}
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderCopyFromBlob --
|
|
*
|
|
* Allocates new pages, copies blob contents and maps the pages.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error. The number of
|
|
* MPNs allocated in *allocs is incremented.
|
|
*
|
|
* Side effects:
|
|
* Allocations, copying, mapping and accounting are performed.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderCopyFromBlob(MonLoaderContext *ctx, // IN/OUT
|
|
uint64 flags, // IN
|
|
VPN monVPN, // IN
|
|
uint64 monBytes, // IN
|
|
uint64 blobOffset, // IN
|
|
uint64 blobSize, // IN
|
|
unsigned *allocs) // IN/OUT
|
|
{
|
|
uint64 bytesLeft = blobSize;
|
|
|
|
if (blobSize > monBytes || blobSize == 0) {
|
|
return ML_ERROR_SIZE; /* Entry corrupt: size mismatch. */
|
|
}
|
|
while (bytesLeft) {
|
|
MPN mpn;
|
|
uint64 toCopy = MIN(bytesLeft, PAGE_SIZE);
|
|
|
|
MonLoaderError ret = MonLoaderAllocMPN(ctx, &mpn);
|
|
if (ret != ML_OK) {
|
|
return ret;
|
|
}
|
|
|
|
ret = MonLoaderMapMPN(ctx, mpn, flags, monVPN);
|
|
if (ret != ML_OK) {
|
|
return ret;
|
|
}
|
|
|
|
if (!MonLoaderCallout_CopyFromBlob(ctx->envCtx, blobOffset, toCopy, mpn,
|
|
ctx->vcpu.currentVCPU)) {
|
|
return ML_ERROR_CALLOUT_COPY;
|
|
}
|
|
|
|
monVPN++;
|
|
blobOffset += toCopy;
|
|
bytesLeft -= toCopy;
|
|
*allocs += 1;
|
|
}
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderShareWork --
|
|
*
|
|
* Maps user or host pages in. monPages is a maximum, so partial sharing
|
|
* is considered successful and remaining monitor pages will not be mapped.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error.
|
|
*
|
|
* Side effects:
|
|
* User pages may be mapped in.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderShareWork(MonLoaderContext *ctx, // IN/OUT
|
|
uint64 flags, // IN
|
|
VPN monVPN, // IN
|
|
uint64 monPages, // IN
|
|
uint64 subIndex, // IN
|
|
Bool user) // IN
|
|
{
|
|
Vcpuid vcpu = ctx->vcpu.currentVCPU;
|
|
uint64 i;
|
|
|
|
if (monPages == 0 || monPages > UINT_MAX) {
|
|
return ML_ERROR_SIZE; /* Entry corrupt: size mismatch. */
|
|
}
|
|
for (i = 0; i < monPages; i++) {
|
|
MPN mpn;
|
|
MonLoaderError ret;
|
|
unsigned pgNum = (unsigned)i;
|
|
|
|
if (user) {
|
|
mpn = MonLoaderCallout_GetSharedUserPage(ctx->envCtx, subIndex, pgNum,
|
|
vcpu);
|
|
} else {
|
|
mpn = MonLoaderCallout_GetSharedHostPage(ctx->envCtx, subIndex, pgNum,
|
|
vcpu);
|
|
}
|
|
if (mpn == INVALID_MPN) {
|
|
/* Partial sharing is allowed. Return success if any occurred. */
|
|
return i != 0 ? ML_OK : ML_ERROR_SHARE;
|
|
}
|
|
ret = MonLoaderMapMPN(ctx, mpn, flags, monVPN + i);
|
|
if (ret != ML_OK) {
|
|
return ret;
|
|
}
|
|
}
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
#ifndef VMMON
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderSetEntrypoint --
|
|
*
|
|
* Calls out to set the code and stack entrypoint.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error.
|
|
*
|
|
* Side effects:
|
|
* The monitor entrypoint for the current VCPU may be set.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderSetEntrypoint(MonLoaderContext *ctx, // IN
|
|
uint16 codeSelector, // IN
|
|
VA64 code, // IN
|
|
uint16 stackSelector, // IN
|
|
VA64 stack) // IN
|
|
{
|
|
if (VA_2_VPN(code) < ctx->vcpu.ASFirstVPN ||
|
|
VA_2_VPN(code) > ctx->vcpu.ASLastVPN ||
|
|
VA_2_VPN(stack) < ctx->vcpu.ASFirstVPN ||
|
|
VA_2_VPN(stack) > ctx->vcpu.ASLastVPN) {
|
|
return ML_ERROR_INVALID_VPN; /* Address is outside address space. */
|
|
}
|
|
if (MonLoaderCallout_SetEntrypoint(ctx->envCtx, codeSelector, code,
|
|
stackSelector, stack)) {
|
|
return ML_OK;
|
|
} else {
|
|
return ML_ERROR_CALLOUT_ENTRYPOINT;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderShareFromUser --
|
|
*
|
|
* Maps user pages in. monPages is a maximum, so partial sharing is
|
|
* considered successful and remaining monitor pages will not be mapped.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error.
|
|
*
|
|
* Side effects:
|
|
* User pages may be mapped in.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderShareFromUser(MonLoaderContext *ctx, // IN/OUT
|
|
uint64 flags, // IN
|
|
VPN monVPN, // IN
|
|
uint64 monPages, // IN
|
|
uint64 subIndex) // IN
|
|
{
|
|
return MonLoaderShareWork(ctx, flags, monVPN, monPages, subIndex, TRUE);
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderShareFromHost --
|
|
*
|
|
* Maps host pages in. monPages is a maximum, so partial sharing is
|
|
* considered successful and remaining monitor pages will not be mapped.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error.
|
|
*
|
|
* Side effects:
|
|
* Host pages may be mapped in.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderShareFromHost(MonLoaderContext *ctx, // IN/OUT
|
|
uint64 flags, // IN
|
|
VPN monVPN, // IN
|
|
uint64 monPages, // IN
|
|
uint64 subIndex) // IN
|
|
{
|
|
return MonLoaderShareWork(ctx, flags, monVPN, monPages, subIndex, FALSE);
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoaderShareFromBlob --
|
|
*
|
|
* Maps blob pages in. monPages is a maximum, so partial sharing is
|
|
* considered successful and remaining monitor pages will not be
|
|
* mapped. blobOffset must be page-aligned and blobSize must be a
|
|
* multiple of PAGE_SIZE. Writeable mappings are not allowed.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error.
|
|
*
|
|
* Side effects:
|
|
* Blob pages may be mapped in.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
static MonLoaderError
|
|
MonLoaderShareFromBlob(MonLoaderContext *ctx, // IN/OUT
|
|
uint64 flags, // IN
|
|
VPN monVPN, // IN
|
|
uint64 monBytes, // IN
|
|
uint64 blobOffset, // IN
|
|
uint64 blobSize) // IN
|
|
{
|
|
uint64 endOff = blobOffset + blobSize;
|
|
|
|
if ((blobOffset & PAGE_MASK) != 0 || (blobSize & PAGE_MASK) != 0 ||
|
|
(flags & PTE_RW) != 0) {
|
|
return ML_ERROR_SHARE;
|
|
}
|
|
if (blobSize > monBytes || blobSize == 0) {
|
|
return ML_ERROR_SIZE;
|
|
}
|
|
while (blobOffset < endOff) {
|
|
MonLoaderError ret;
|
|
MPN mpn = MonLoaderCallout_GetBlobMpn(ctx->envCtx, blobOffset);
|
|
if (mpn == INVALID_MPN) {
|
|
return ML_ERROR_INVALID_VPN;
|
|
}
|
|
ret = MonLoaderMapMPN(ctx, mpn, flags, monVPN);
|
|
if (ret != ML_OK) {
|
|
return ret;
|
|
}
|
|
blobOffset += PAGE_SIZE;
|
|
monVPN++;
|
|
}
|
|
ASSERT(blobOffset == endOff);
|
|
return ML_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
*----------------------------------------------------------------------
|
|
*
|
|
* MonLoader_Process --
|
|
*
|
|
* Builds and populates a monitor address space per the given a header and
|
|
* VCPU count. Calls out to support functions.
|
|
*
|
|
* Result:
|
|
* ML_OK if successful, some other value on error. If an error occurs,
|
|
* *line contains the line in the table at which the failure occurred and
|
|
* *vcpu contains the VCPU for which processing was running.
|
|
*
|
|
* Side effects:
|
|
* An address space and page table mappings are created or verified, for
|
|
* all VCPUs. Callouts are called.
|
|
*
|
|
*----------------------------------------------------------------------
|
|
*/
|
|
MonLoaderError
|
|
MonLoader_Process(MonLoaderHeader *header, // IN/OUT
|
|
unsigned numVCPUs, // IN
|
|
void *args, // IN
|
|
unsigned *line, // OUT
|
|
Vcpuid *vcpu) // OUT
|
|
{
|
|
unsigned i;
|
|
MonLoaderError ret;
|
|
MonLoaderContext ctx;
|
|
|
|
if (numVCPUs > MAX_VCPUS) {
|
|
return ML_ERROR_ARGS;
|
|
}
|
|
|
|
if (header->magic != LOADER_HEADER_MAGIC) {
|
|
return ML_ERROR_MAGIC;
|
|
}
|
|
|
|
if (vmx86_debug && !line) {
|
|
return ML_ERROR_ARGS;
|
|
}
|
|
*line = LINE_INVALID;
|
|
|
|
if (header->entries == 0 || header->count == 0) {
|
|
return ML_ERROR_TABLE_MISSING;
|
|
}
|
|
|
|
if (!MonLoaderCallout_Init(args, &ctx.envCtx)) {
|
|
return ML_ERROR_CALLOUT_INIT;
|
|
}
|
|
ret = ML_OK;
|
|
for (*vcpu = 0; *vcpu < numVCPUs && ret == ML_OK; (*vcpu) += 1) {
|
|
/* Reset VCPU-specific part of context. */
|
|
memset(&ctx.vcpu, 0, sizeof ctx.vcpu);
|
|
ctx.vcpu.currentVCPU = *vcpu;
|
|
ctx.vcpu.hasAddrSpace = FALSE;
|
|
for (i = 0; i < header->count && ret == ML_OK; i++) {
|
|
MonLoaderEntry *entry = &header->entries[i];
|
|
MonLoaderContentType content = entry->content;
|
|
MonLoaderSourceType source = entry->source;
|
|
VPN monVPN = entry->monVPN;
|
|
uint64 blobOffset = entry->blobSrc.offset;
|
|
uint64 blobSize = entry->blobSrc.size;
|
|
uint64 flags = entry->flags;
|
|
uint64 monPages = entry->monPages;
|
|
uint64 monBytes = PAGES_2_BYTES(monPages);
|
|
uint64 subIndex = entry->subIndex;
|
|
uint64 bspOnly = entry->bspOnly;
|
|
uint64 isPriv = vmx86_server || entry->procVmmon;
|
|
*line = i;
|
|
|
|
/*
|
|
* The entry is specific to the bootstrap, which only runs in VCPU 0.
|
|
*/
|
|
if (bspOnly && !IS_BOOT_VCPUID(*vcpu)) {
|
|
continue;
|
|
}
|
|
/*
|
|
* Entries marked as privileged (isPriv == TRUE) are processed
|
|
* by the privileged layer (VMKernel/vmmon). Others are
|
|
* processed by the VMX. One exception is the entry of type
|
|
* ML_CONTENT_ADDRSPACE: it is always processed by the VMX and,
|
|
* if marked as privileged, also by the privileged layer.
|
|
*/
|
|
if (isPriv != MonLoaderCallout_IsPrivileged(ctx.envCtx) &&
|
|
(content != ML_CONTENT_ADDRSPACE || isPriv == 0)) {
|
|
continue;
|
|
}
|
|
if (monPages == 0 || monVPN + monPages - 1 > VPN_MAX) {
|
|
ret = ML_ERROR_SIZE;
|
|
continue;
|
|
}
|
|
|
|
switch (content) {
|
|
case ML_CONTENT_ADDRSPACE:
|
|
if (ctx.vcpu.hasAddrSpace) {
|
|
ret = ML_ERROR_DUPLICATE;
|
|
continue;
|
|
}
|
|
/* Create or verify the address space and page table MPNs. */
|
|
ret = MonLoaderCreateAddressSpace(&ctx, monVPN, flags, monPages,
|
|
importFromVMX);
|
|
break;
|
|
case ML_CONTENT_PAGETABLE_L4:
|
|
case ML_CONTENT_PAGETABLE_L3:
|
|
case ML_CONTENT_PAGETABLE_L2:
|
|
case ML_CONTENT_PAGETABLE_L1:
|
|
/* Create or verify page table mappings. */
|
|
ret = MonLoaderMapPageTables(&ctx, CONTENT_TO_PTLEVEL(content),
|
|
flags, monVPN, monPages);
|
|
break;
|
|
case ML_CONTENT_ALLOCZERO: {
|
|
uint64 numPages;
|
|
if (source == ML_SOURCE_NONE) {
|
|
numPages = monPages;
|
|
} else if (source == ML_SOURCE_BLOB) {
|
|
/*
|
|
* Used for loading the uninitialized data section
|
|
* (.bss), which has a blob size but no blob image
|
|
* associated with it.
|
|
*/
|
|
numPages = CEILING(blobSize, PAGE_SIZE);
|
|
} else {
|
|
ret = ML_ERROR_SOURCE_INVALID;
|
|
break;
|
|
}
|
|
ret = MonLoaderZero(&ctx, flags, monVPN, numPages, monPages,
|
|
&entry->allocs);
|
|
break;
|
|
}
|
|
case ML_CONTENT_COPY:
|
|
switch (source) {
|
|
case ML_SOURCE_BLOB:
|
|
ret = MonLoaderCopyFromBlob(&ctx, flags, monVPN, monBytes,
|
|
blobOffset, blobSize,
|
|
&entry->allocs);
|
|
break;
|
|
default:
|
|
ret = ML_ERROR_SOURCE_INVALID;
|
|
break;
|
|
}
|
|
break;
|
|
case ML_CONTENT_SHARE:
|
|
switch (source) {
|
|
case ML_SOURCE_USER:
|
|
ret = MonLoaderShareFromUser(&ctx, flags, monVPN,
|
|
monPages, subIndex);
|
|
break;
|
|
case ML_SOURCE_HOST:
|
|
ret = MonLoaderShareFromHost(&ctx, flags, monVPN,
|
|
monPages, subIndex);
|
|
break;
|
|
case ML_SOURCE_BLOB:
|
|
ret = MonLoaderShareFromBlob(&ctx, flags, monVPN,
|
|
monBytes, blobOffset,
|
|
blobSize);
|
|
break;
|
|
default:
|
|
ret = ML_ERROR_SOURCE_INVALID;
|
|
break;
|
|
}
|
|
break;
|
|
case ML_CONTENT_INVALID:
|
|
default:
|
|
ret = ML_ERROR_CONTENT_INVALID;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#ifndef VMMON
|
|
if (ret == ML_OK) {
|
|
if (ctx.vcpu.ASLastVPN == 0) {
|
|
ret = ML_ERROR_NO_ADDRSPACE;
|
|
} else {
|
|
ret = MonLoaderSetEntrypoint(&ctx, header->codeSelector,
|
|
header->codeEntrypoint,
|
|
header->stackSelector,
|
|
header->stackEntrypoint);
|
|
}
|
|
}
|
|
#endif
|
|
MonLoaderCallout_CleanUp(ctx.envCtx);
|
|
return ret;
|
|
}
|