Changeset View
Standalone View
usr.sbin/bhyve/vmgenc.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright 2020 Conrad Meyer <cem@FreeBSD.org>. All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | |||||
#include <sys/uuid.h> | |||||
#include <assert.h> | |||||
#include <ctype.h> | |||||
#include <err.h> | |||||
#include <errno.h> | |||||
#include <pthread.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <strings.h> | |||||
#include <stdbool.h> | |||||
#include <unistd.h> | |||||
#include <machine/vmm.h> | |||||
#include <vmmapi.h> | |||||
#include "acpi.h" | |||||
#include "mem.h" | |||||
#include "vmgenc.h" | |||||
static struct uuid vmgen_id; | |||||
static uint64_t vmgen_gpa; | |||||
static int | |||||
vmgenc_mem_handler(struct vmctx *ctx __unused, int vcpu __unused, int dir, | |||||
uint64_t addr, int size, uint64_t *val, void *arg1 __unused, | |||||
long arg2 __unused) | |||||
{ | |||||
uint64_t offset; | |||||
/* Ignore writes. */ | |||||
if (dir == MEM_F_WRITE) | |||||
return (0); | |||||
assert(addr >= vmgen_gpa && size <= sizeof(vmgen_id) && | |||||
addr + size <= vmgen_gpa + sizeof(vmgen_id) && | |||||
((size == 8 && (addr & 0x7) == 0) || | |||||
(size == 4 && (addr & 0x3) == 0))); | |||||
offset = addr - vmgen_gpa; | |||||
if (size == 8) | |||||
*val = *(uint64_t *)((char *)&vmgen_id + offset); | |||||
else | |||||
*val = *(uint32_t *)((char *)&vmgen_id + offset); | |||||
return (0); | |||||
} | |||||
void | |||||
vmgenc_init(struct vmctx *ctx) | |||||
{ | |||||
struct mem_range mr; | |||||
uint32_t lowmem_gpa; | |||||
int error; | |||||
rpokala: Since this is going to be an address, shouldn't it be `uint64_t`? | |||||
Done Inline ActionsNo -- we want to allocate in the PCI hole, which is by definition below 4GB. cem: No -- we want to allocate in the PCI hole, which is by definition below 4GB. | |||||
Not Done Inline ActionsFair enough. A comment to that effect would be nice. :-) rpokala: Fair enough. A comment to that effect would be nice. :-) | |||||
/* | |||||
* Slice off some PCI hole memory, which is not allocatable to the | |||||
* guest operating system's general use. | |||||
*/ | |||||
lowmem_gpa = vm_get_lowmem_limit(ctx); | |||||
/* | |||||
* GUID must be 8-byte aligned, per spec. | |||||
*/ | |||||
lowmem_gpa = roundup2(lowmem_gpa, 8); | |||||
vmgen_gpa = lowmem_gpa; | |||||
/* | |||||
* Ostensibly it needs to be cachable, but we don't attempt to enforce | |||||
* that at all. | |||||
*/ | |||||
vm_set_lowmem_limit(ctx, lowmem_gpa + sizeof(vmgen_id)); | |||||
/* | |||||
Done Inline ActionsThis ends up running before PCI assigns resource addresses, so we get the very first lowmem_limit address. I don't know any reason that would cause problems for PCI (it does not appear to), but just an FYI for reviewers. I think it would be good to document Bhyve's physical memory layout and how registered mem/fallback ranges work in a single place; I had some trouble piecing it together across multiple files and libraries. cem: This ends up running before `PCI` assigns resource addresses, so we get the very first… | |||||
Done Inline ActionsFor PCI it does take away a 1GB-aligned region, and won't allow 512MB BARs to be used. This may not be that much of an issue, but I think it could be avoided by putting the UUID into high ROM space. There is 16MB available for this and EFI only uses 2MB max. grehan: For PCI it does take away a 1GB-aligned region, and won't allow 512MB BARs to be used. This may… | |||||
Done Inline ActionsThat's a good consideration, thanks. Do we have any existing scheme for allocating ROM space, or is it just ad-hoc? Also, is the ROM region required to be mapped UC? The spec suggests the UUID should not share a page with anything requiring UC mapping, although it's unclear to me why that would possibly matter. One other possibility would just be to allocate the UUID after the PCI buses in the PCI hole. Usually there is plenty of space there, unless a guest needs very large 32-bit BARs. But I like the suggestion to use ROM space for this instead. The important part is that the UUID doesn't end up on a page included as ordinary "Memory" in SMAP/e820. cem: That's a good consideration, thanks.
Do we have any existing scheme for allocating ROM space… | |||||
Done Inline ActionsROM space allocation is ad-hoc. I suspect it is required to be mapped UC since at least on real h/w it's outside the coherency domain. But, as you mentioned this does seem a strange requirement and may not be enforced. grehan: ROM space allocation is ad-hoc.
I suspect it is required to be mapped UC since at least on… | |||||
* It is basically harmless to always generate a random ID when | |||||
* starting a VM. | |||||
*/ | |||||
error = getentropy(&vmgen_id, sizeof(vmgen_id)); | |||||
if (error == -1) | |||||
err(4, "vmgenc: getentropy"); | |||||
mr = (struct mem_range) { | |||||
.name = "vmgenc", | |||||
.base = vmgen_gpa, | |||||
.size = sizeof(vmgen_id), | |||||
.flags = MEM_F_READ | MEM_F_IMMUTABLE, | |||||
.handler = vmgenc_mem_handler, | |||||
}; | |||||
error = register_mem(&mr); | |||||
if (error != 0) | |||||
errc(4, error, "vmgenc: register_mem"); | |||||
/* XXX When we have suspend/resume/rollback. */ | |||||
#if 0 | |||||
acpi_raise_gpe(ctx, GPE_VMGENC); | |||||
#endif | |||||
} | |||||
void | |||||
vmgenc_write_dsdt(void) | |||||
{ | |||||
dsdt_line(""); | |||||
dsdt_indent(1); | |||||
dsdt_line("Scope (_SB)"); | |||||
dsdt_line("{"); | |||||
dsdt_line(" Device (GENC)"); | |||||
dsdt_line(" {"); | |||||
dsdt_indent(2); | |||||
dsdt_line("Name (_CID, \"VM_Gen_Counter\")"); | |||||
Done Inline ActionsThe VM Gen. ID spec does say the device should have a unique _HID of some kind but leaves the content and form unspecified. Here we have followed the HyperV convention of a readable English named with explicit version number suffix. I picked zero because this is only a partial implementation, but the number is arbitrary. Any sane VM Gen driver will match on _CID or _DDN. I've defined _HID as a Method returning a string, rather than a bare named string, as a hack to work around a stupid naming guideline in the iasl compiler for _HID (but not _CID) names. The guideline clearly does not matter to guests — you could say HyperV is the reference implementation of this spec, and HyperV uses a non-canonical _HID. Every operating system seems to run just fine on Azure. (Canonically, _HID is one of three forms: AAA#### (A is any uppercase ASCII letter, # is any hexadecimal number`, NNNN#### (N is uppercase ASCII or decimal digits, # as before), or ACPI#### (here, "ACPI" is the literal characters, # as before). Obviously this would be very limiting in communicating anything meaningful about the "hardware" implementation in the _HID.) cem: The VM Gen. ID spec does say the device should have a unique `_HID` of some kind but leaves the… | |||||
dsdt_line("Method (_HID, 0, NotSerialized)"); | |||||
dsdt_line("{"); | |||||
dsdt_line(" Return (\"Bhyve_V_Gen_Counter_V1\")"); | |||||
dsdt_line("}"); | |||||
dsdt_line("Name (_UID, 0)"); | |||||
dsdt_line("Name (_DDN, \"VM_Gen_Counter\")"); | |||||
dsdt_line("Name (ADDR, Package (0x02)"); | |||||
dsdt_line("{"); | |||||
dsdt_line(" 0x%08x,", (uint32_t)vmgen_gpa); | |||||
dsdt_line(" 0x%08x", (uint32_t)(vmgen_gpa >> 32)); | |||||
Done Inline ActionsThe spec doesn't say ADDR must be a method, just that it must evaluate to a Package of two integers; that means it could just be a named Package object. Name (ADDR, Package (0x02) { 0x%08x, 0x%08x }) That would be tolerable for our vmgenc(4) and any spec-compliant guest implementation, but I haven't audited operating systems explicitly (Linux would be easy to check; Windows might be impossible). Leaving it as a Method is also, IMO, harmless. cem: The spec doesn't say `ADDR` must be a method, just that it must evaluate to a `Package` of two… | |||||
dsdt_line("})"); | |||||
dsdt_unindent(2); | |||||
dsdt_line(" }"); /* Device (GENC) */ | |||||
dsdt_line("}"); /* Scope (_SB) */ | |||||
dsdt_line(""); | |||||
dsdt_line("Scope (_GPE)"); | |||||
dsdt_line("{"); | |||||
dsdt_line(" Method (_E%02x, 0, NotSerialized)", GPE_VMGENC); | |||||
dsdt_line(" {"); | |||||
dsdt_line(" Notify (\\_SB.GENC, 0x80)"); | |||||
dsdt_line(" }"); | |||||
dsdt_line("}"); | |||||
dsdt_unindent(1); | |||||
} |
Since this is going to be an address, shouldn't it be uint64_t?