Skip to content

Commit

Permalink
Use a separate virtual memory range for importing grant mappings
Browse files Browse the repository at this point in the history
The initial implementation of mirage_xen_gnttab_{map,mapv,unmap} was
wrong in using heap-allocated memory for grant mappings imported from
other Xen domains. This is not possible, since once a grant is unmapped
the associated guest pages are mapped by Xen to a "dead" page (writes
are a no-op, reads return 0xff) and can no longer be re-used as "normal"
memory.

This change introduces a separate virtual memory range used for such
mappings; a private ABI from Solo5 provides a way to determine which
memory range to use and a simple bitmap allocator is used to externally
manage usage of pages in this memory range.
  • Loading branch information
mato committed Oct 7, 2020
1 parent 528d6c2 commit 94372da
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 8 deletions.
17 changes: 17 additions & 0 deletions lib/bindings/bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define __XEN_BINDINGS_H__

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "xen/xen.h"

Expand Down Expand Up @@ -118,4 +119,20 @@ static inline uint16_t atomic_sync_cmpxchgw(volatile uint16_t *ptr,
#error Not implemented
#endif /* __x86_64__ */

/*
* Accessor for retrieving guest-virtual memory range suitable for importing
* grant mappings from Solo5.
*/
void solo5__xen_get_gntmap_area(uint64_t *addr, size_t *size);

/*
* Bitmap allocator for virtual memory used for importing grant mappings.
*/
struct bmap_allocator;
typedef struct bmap_allocator bmap_allocator_t;

bmap_allocator_t *bmap_init(uint64_t start_addr, size_t n_pages);
void *bmap_alloc(bmap_allocator_t *alloc, size_t n);
void bmap_free(bmap_allocator_t *alloc, void *addr, size_t n);

#endif /* __XEN_BINDINGS_H__ */
251 changes: 251 additions & 0 deletions lib/bindings/bmap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
* Copyright (c) 2020 Martin Lucina <[email protected]>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <stddef.h>
#include <stdint.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#ifndef TEST_STANDALONE
#include "bindings.h"
#else
#define PAGE_SIZE 4096
#endif

/*
* This is a simple bitmap allocator for virtual memory addresses. Worst-case
* performance for bmap_alloc() is O(n), where n = total_pages / 8. bmap_free()
* is O(1) but requires the caller to keep the size of the allocated block.
*/
struct bmap_allocator {
long *bmap; /* 1 bit per page; 1=free, 0=used */
size_t bmap_size; /* # of words in bmap[] */
uint64_t start_addr; /* starting virtual memory address */
};
typedef struct bmap_allocator bmap_allocator_t;

#define BPW (sizeof(long) * 8)
_Static_assert(sizeof(long) == 8, "long must be 64 bits");

/*
* Returns 0-based index of first set bit in (bmap[]), starting with the bit
* index (at), or -1 if none found and end of (bmap[]) was reached.
*/
static int ffs_at(long *bmap, size_t bmap_size, int at)
{
int word = at / BPW;
int shift = at % BPW;
int bit = 0;
int i;

for (i = word; i < bmap_size; i++) {
if (i == word)
/* (at) is not on a word boundary; shift so we can use ffsl */
bit = __builtin_ffsl(bmap[i] >> shift);
else
bit = __builtin_ffsl(bmap[i]);
if (bit)
break;
}

if (bit) {
if (i == word)
/* Restore previous shift if any */
bit += shift;
return (i * BPW) + (bit - 1);
}
else
return -1;
}

/*
* Returns 0-based index of first clear bit in (bmap[]), starting with the bit
* index (at), or -1 if none found and end of (bmap[]) was reached.
*/
static int ffc_at(long *bmap, size_t bmap_size, int at)
{
int word = at / BPW;
int shift = at % BPW;
int bit = 0;
int i;

for (i = word; i < bmap_size; i++) {
if (i == word)
/* (at) is not on a word boundary; shift so we can use ffsl */
bit = __builtin_ffsl(~bmap[i] >> shift);
else
bit = __builtin_ffsl(~bmap[i]);
if (bit)
break;
}

if (bit) {
if (i == word)
/* Restore previous shift if any */
bit += shift;
return (i * BPW) + (bit - 1);
}
else
return -1;
}

/*
* Set (n) bits in (bmap[]) at 0-based bit index (at).
*/
static void setn_at(long *bmap, size_t bmap_size, int at, int n)
{
assert((at + n - 1) < (bmap_size * BPW));
while (n > 0) {
n -= 1;
bmap[((at + n) / BPW)] |= (1UL << ((at + n) % BPW));
}
}

/*
* Clear (n) bits in (bmap[]) at 0-based bit index (at).
*/
static void clearn_at(long *bmap, size_t bmap_size, int at, int n)
{
assert((at + n - 1) < (bmap_size * BPW));
while (n > 0) {
n -= 1;
bmap[((at + n) / BPW)] &= ~(1UL << ((at + n) % BPW));
}
}

/*
* Allocate (n) pages from (alloc), returns a memory address or NULL if no
* space found.
*/
void *bmap_alloc(bmap_allocator_t *alloc, size_t n)
{
int a = 0, b = 0;
size_t bmap_bits = alloc->bmap_size * BPW;

/*
* Allocating 0 pages is not allowed.
*/
assert(n >= 1);

while (1) {
/*
* Look for the first free page starting at (b), initially 0.
*/
a = ffs_at(alloc->bmap, alloc->bmap_size, b);
if (a < 0)
return NULL;
/*
* Look for the first used page after the found free page.
*/
b = ffc_at(alloc->bmap, alloc->bmap_size, a);
if (b < 0)
/*
* Nothing found; all remaining pages from a..bmap_bits are free.
*/
b = bmap_bits;
/*
* Is the block big enough? If yes, mark as used (0) and return it.
*/
if (b - a >= n) {
clearn_at(alloc->bmap, alloc->bmap_size, a, n);
return (void *)(alloc->start_addr + (a * PAGE_SIZE));
}
/*
* Stop the search if we hit the end of bmap[] and did not find a large
* enough block.
*/
if (b == bmap_bits)
return NULL;
/*
* If we got here, loop with (b) set to the last seen used page.
*/
}
}

/*
* Free (n) pages at (addr) from (alloc).
*/
void bmap_free(bmap_allocator_t *alloc, void *addr, size_t n)
{
/*
* Verify that:
* addr is page-aligned
* addr is within the range given to alloc
* n is at least 1; the maxiumum size of n is checked in clearn_at().
*/
assert(((uintptr_t)addr & (PAGE_SIZE - 1)) == 0);
assert((uintptr_t)addr >= alloc->start_addr);
assert(n >= 1);

int a = ((uintptr_t)addr - alloc->start_addr) / PAGE_SIZE;
setn_at(alloc->bmap, alloc->bmap_size, a, n);
}

/*
* Initialise the allocator to use (n_pages) at (start_addr).
*/
bmap_allocator_t *bmap_init(uint64_t start_addr, size_t n_pages)
{
bmap_allocator_t *alloc = malloc(sizeof (bmap_allocator_t));
assert(alloc != NULL);
/*
* n_pages must be a multiple of BPW.
*/
assert((n_pages % BPW) == 0);
alloc->bmap_size = n_pages / BPW;
alloc->bmap = malloc(alloc->bmap_size * sizeof(long));
assert(alloc->bmap);
alloc->start_addr = start_addr;
/*
* All pages are initially free; set all bits in bmap[].
*/
memset(alloc->bmap, 0xff, alloc->bmap_size * sizeof(long));

return alloc;
}

#ifdef TEST_STANDALONE
/*
* For standalone testing of the algorithm.
*/

#include <stdio.h>

int main(int argc, char *argv[])
{
if (argc != 2) {
printf("need allocation size\n");
return 1;
}

int n = atoi(argv[1]);
bmap_allocator_t *alloc = bmap_init(0x100000, 4096);

char *a;
int c = 0;
do {
a = bmap_alloc(alloc, n);
if (a)
printf ("%d: %p - %p\n", ++c, a, a + (n * PAGE_SIZE) - 1);
} while (a);

/* Keep valgrind happy when testing standalone. */
free(alloc->bmap);
free(alloc);
}

#endif
32 changes: 25 additions & 7 deletions lib/bindings/gnttab.c
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ static const size_t NR_GRANT_TABLE_ENTRIES =

static grant_entry_v1_t *gnttab_table;

/*
* Grant pages imported from other Xen domains must use a separate virtual
* memory address space. Solo5 maps us an extra 1GB address space at physical
* address 4GB (see bindings/xen/platform.c), of which we use a suitably sized
* area for this purpose.
*/
#define NR_GNTMAP_AREA_PAGES (2 * NR_GRANT_TABLE_ENTRIES)
static bmap_allocator_t *gnttab_alloc;

void gnttab_init(void)
{
int rc = posix_memalign((void **)&gnttab_table, PAGE_SIZE,
Expand All @@ -65,6 +74,15 @@ void gnttab_init(void)
}
DPRINTF(1, "pages = %zd, entries = %zd\n", NR_GRANT_TABLE_PAGES,
NR_GRANT_TABLE_ENTRIES);

uint64_t gntmap_area_addr;
size_t gntmap_area_size;
solo5__xen_get_gntmap_area(&gntmap_area_addr, &gntmap_area_size);
assert(NR_GNTMAP_AREA_PAGES <= (gntmap_area_size / PAGE_SIZE));
gnttab_alloc = bmap_init(gntmap_area_addr, NR_GNTMAP_AREA_PAGES);
assert(gnttab_alloc);
DPRINTF(1, "gntmap area: %zd pages @ 0x%lx\n", NR_GNTMAP_AREA_PAGES,
gntmap_area_addr);
}

/*
Expand Down Expand Up @@ -225,8 +243,8 @@ mirage_xen_gnttab_map(value v_unused_ctx, value v_ref, value v_domid,
void *addr;
int rc;

rc = posix_memalign(&addr, PAGE_SIZE, 1 * PAGE_SIZE);
if (rc != 0)
addr = bmap_alloc(gnttab_alloc, 1);
if (addr == NULL)
caml_raise_out_of_memory();

v_mapping = alloc_mapping(1);
Expand All @@ -238,7 +256,7 @@ mirage_xen_gnttab_map(value v_unused_ctx, value v_ref, value v_domid,
mapping->entries[0].handle = ref;
rc = gnttab_map(mapping);
if (rc != 0) {
free(mapping->start_addr);
bmap_free(gnttab_alloc, mapping->start_addr, mapping->count);
mapping->start_addr = NULL;
mapping->count = 0;
caml_failwith("mirage_xen_gnttab_map: failed");
Expand Down Expand Up @@ -270,8 +288,8 @@ mirage_xen_gnttab_mapv(value v_unused_ctx, value v_array, value v_writable)
void *addr;
int rc;

rc = posix_memalign(&addr, PAGE_SIZE, count * PAGE_SIZE);
if (rc != 0)
addr = bmap_alloc(gnttab_alloc, count);
if (addr == NULL)
caml_raise_out_of_memory();

v_mapping = alloc_mapping(count);
Expand All @@ -287,7 +305,7 @@ mirage_xen_gnttab_mapv(value v_unused_ctx, value v_array, value v_writable)
}
rc = gnttab_map(mapping);
if (rc != 0) {
free(mapping->start_addr);
bmap_free(gnttab_alloc, mapping->start_addr, count);
mapping->start_addr = NULL;
mapping->count = 0;
caml_failwith("mirage_xen_gnttab_mapv: failed");
Expand Down Expand Up @@ -317,7 +335,7 @@ mirage_xen_gnttab_unmap(value v_unused_ctx, value v_mapping)
rc = gnttab_unmap(mapping);
if (rc != 0)
caml_failwith("mirage_xen_gnttab_unmap: failed");
free(mapping->start_addr);
bmap_free(gnttab_alloc, mapping->start_addr, mapping->count);
mapping->start_addr = 0;
mapping->count = 0;
CAMLreturn(Val_unit);
Expand Down
2 changes: 1 addition & 1 deletion lib/dune
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
(archive_name mirage-xen_bindings)
(language c)
(include_dirs ../include)
(names main evtchn gnttab
(names main bmap evtchn gnttab
alloc_pages_stubs atomic_stubs barrier_stubs checksum_stubs clock_stubs
cstruct_stubs mm_stubs)
(flags (:include cflags.sexp) -O2 -std=c99 -Wall -Werror
Expand Down

0 comments on commit 94372da

Please sign in to comment.