/**
* Copyright (C) Mellanox Technologies Ltd. 2001-2015. ALL RIGHTS RESERVED.
*
* See file LICENSE for terms.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "pgtable.h"
#include <ucs/arch/bitops.h>
#include <ucs/debug/assert.h>
#include <ucs/debug/log.h>
#include <ucs/debug/memtrack.h>
#include <ucs/sys/math.h>
#include <string.h>
#define ucs_pgt_entry_clear(_pte) \
{ (_pte)->value = 0; }
#define ucs_pgt_entry_value(_pte) \
((void*)((_pte)->value & UCS_PGT_ENTRY_PTR_MASK))
#define ucs_pgt_entry_test(_pte, _flag) \
((_pte)->value & (_flag))
#define ucs_pgt_entry_present(_pte) \
ucs_pgt_entry_test(_pte, UCS_PGT_ENTRY_FLAG_REGION | UCS_PGT_ENTRY_FLAG_DIR)
#define ucs_pgt_is_addr_aligned(_addr) \
(!((_addr) & (UCS_PGT_ADDR_ALIGN - 1)))
#define ucs_pgt_check_ptr(_ptr) \
do { \
ucs_assertv(!((uintptr_t)(_ptr) & (UCS_PGT_ENTRY_MIN_ALIGN - 1)), \
"ptr=%p", (_ptr)); \
} while (0)
#define ucs_pgt_entry_set_region(_pte, _region) \
do { \
ucs_pgt_region_t *tmp = (_region); \
ucs_pgt_check_ptr(tmp); \
(_pte)->value = ((uintptr_t)tmp) | UCS_PGT_ENTRY_FLAG_REGION; \
} while (0)
#define ucs_pgt_entry_set_dir(_pte, _dir) \
do { \
ucs_pgt_dir_t *tmp = (_dir); \
ucs_pgt_check_ptr(tmp); \
(_pte)->value = ((uintptr_t)tmp) | UCS_PGT_ENTRY_FLAG_DIR; \
} while (0)
#define ucs_pgt_entry_get_region(_pte) \
({ \
ucs_assert(ucs_pgt_entry_test(_pte, UCS_PGT_ENTRY_FLAG_REGION)); \
(ucs_pgt_region_t*)ucs_pgt_entry_value(_pte); \
})
#define ucs_pgt_entry_get_dir(_pte) \
({ \
ucs_assert(ucs_pgt_entry_test(_pte, UCS_PGT_ENTRY_FLAG_DIR)); \
(ucs_pgt_dir_t*)ucs_pgt_entry_value(_pte); \
})
static inline ucs_pgt_dir_t* ucs_pgt_dir_alloc(ucs_pgtable_t *pgtable)
{
ucs_pgt_dir_t *pgd;
pgd = pgtable->pgd_alloc_cb(pgtable);
if (pgd == NULL) {
ucs_fatal("Failed to allocate page table directory");
}
ucs_pgt_check_ptr(pgd);
memset(pgd, 0, sizeof(*pgd));
return pgd;
}
static inline void ucs_pgt_dir_release(ucs_pgtable_t *pgtable, ucs_pgt_dir_t* pgd)
{
pgtable->pgd_release_cb(pgtable, pgd);
}
static inline void ucs_pgt_address_advance(ucs_pgt_addr_t *address_p,
unsigned order)
{
ucs_assert(order < 64);
/* coverity[large_shift] */
*address_p += 1ul << order;
}
static void ucs_pgt_entry_dump_recurs(const ucs_pgtable_t *pgtable, unsigned indent,
const ucs_pgt_entry_t *pte, unsigned pte_index,
ucs_pgt_addr_t base, ucs_pgt_addr_t mask,
unsigned shift, ucs_log_level_t log_level)
{
ucs_pgt_region_t *region;
ucs_pgt_dir_t *pgd;
size_t i;
if (ucs_pgt_entry_test(pte, UCS_PGT_ENTRY_FLAG_REGION)) {
region = ucs_pgt_entry_value(pte);
ucs_log(log_level, "%*s[%3u] region " UCS_PGT_REGION_FMT, indent, "",
pte_index, UCS_PGT_REGION_ARG(region));
} else if (ucs_pgt_entry_test(pte, UCS_PGT_ENTRY_FLAG_DIR)) {
pgd = ucs_pgt_entry_get_dir(pte);
ucs_log(log_level, "%*s[%3u] dir %p for [0x%lx..0x%lx], count %u shift %u mask 0x%lx",
indent, " ", pte_index, pgd, base, (base + (1 << shift)) & mask,
pgd->count, shift, mask);
shift -= UCS_PGT_ENTRY_SHIFT;
mask |= UCS_PGT_ENTRY_MASK << shift;
for (i = 0; i < UCS_PGT_ENTRIES_PER_DIR; ++i) {
ucs_pgt_entry_dump_recurs(pgtable, indent + 2, &pgd->entries[i], i,
base | (i << shift), mask, shift, log_level);
++base;
}
} else {
ucs_log(log_level, "%*s[%3u] not present", indent, " ", pte_index);
}
}
static void ucs_pgtable_log(const ucs_pgtable_t *pgtable,
ucs_log_level_t log_level, const char *message)
{
ucs_log(log_level, "pgtable %p %s: base 0x%lx/0x%lx shift %u count %u",
pgtable, message, pgtable->base, pgtable->mask, pgtable->shift,
pgtable->num_regions);
}
void ucs_pgtable_dump(const ucs_pgtable_t *pgtable, ucs_log_level_t log_level)
{
ucs_pgtable_log(pgtable, log_level, "dump");
ucs_pgt_entry_dump_recurs(pgtable, 0, &pgtable->root, 0, pgtable->base,
pgtable->mask, pgtable->shift, log_level);
}
static void ucs_pgtable_trace(ucs_pgtable_t *pgtable, const char *message)
{
ucs_pgtable_log(pgtable, UCS_LOG_LEVEL_TRACE_DATA, message);
}
static void ucs_pgtable_reset(ucs_pgtable_t *pgtable)
{
pgtable->base = 0;
pgtable->mask = ((ucs_pgt_addr_t)-1) << UCS_PGT_ADDR_SHIFT;
pgtable->shift = UCS_PGT_ADDR_SHIFT;
}
/**
* Make the page table map a wider range of addresses - expands by UCS_PGT_ENTRY_SHIFT.
*/
static void ucs_pgtable_expand(ucs_pgtable_t *pgtable)
{
ucs_pgt_dir_t *pgd;
ucs_assertv(pgtable->shift <= (UCS_PGT_ADDR_ORDER - UCS_PGT_ENTRY_SHIFT),
"shift=%u", pgtable->shift);
if (ucs_pgt_entry_present(&pgtable->root)) {
pgd = ucs_pgt_dir_alloc(pgtable);
pgd->entries[(pgtable->base >> pgtable->shift) & UCS_PGT_ENTRY_MASK] =
pgtable->root;
pgd->count = 1;
ucs_pgt_entry_set_dir(&pgtable->root, pgd);
}
pgtable->shift += UCS_PGT_ENTRY_SHIFT;
pgtable->mask <<= UCS_PGT_ENTRY_SHIFT;
pgtable->base &= pgtable->mask;
ucs_pgtable_trace(pgtable, "expand");
}
/**
* Shrink the page table address span if possible
*
* @return Whether it was shrinked.
*/
static int ucs_pgtable_shrink(ucs_pgtable_t *pgtable)
{
ucs_pgt_entry_t *pte;
ucs_pgt_dir_t *pgd;
unsigned pte_idx;
if (!ucs_pgt_entry_present(&pgtable->root)) {
ucs_pgtable_reset(pgtable);
ucs_pgtable_trace(pgtable, "shrink");
return 0;
} else if (!ucs_pgt_entry_test(&pgtable->root, UCS_PGT_ENTRY_FLAG_DIR)) {
return 0;
}
pgd = ucs_pgt_entry_get_dir(&pgtable->root);
ucs_assert(pgd->count > 0); /* should be empty */
/* If there is just one PTE, we can reduce the page table to map
* this PTE only.
*/
if (pgd->count != 1) {
return 0;
}
/* Search for the single PTE in dir */
for (pte_idx = 0, pte = pgd->entries; !ucs_pgt_entry_present(pte); ++pte_idx, ++pte) {
ucs_assert(pte_idx < UCS_PGT_ENTRIES_PER_DIR);
}
/* Remove one level */
pgtable->shift -= UCS_PGT_ENTRY_SHIFT;
pgtable->base |= (ucs_pgt_addr_t)pte_idx << pgtable->shift;
pgtable->mask |= UCS_PGT_ENTRY_MASK << pgtable->shift;
pgtable->root = *pte;
ucs_pgtable_trace(pgtable, "shrink");
ucs_pgt_dir_release(pgtable, pgd);
return 1;
}
static void ucs_pgtable_check_page(ucs_pgt_addr_t address, unsigned order)
{
ucs_assert( (address & ((1ul << order) - 1)) == 0 );
ucs_assertv( ((order - UCS_PGT_ADDR_SHIFT) % UCS_PGT_ENTRY_SHIFT) == 0, "order=%u", order);
}
/**
* @return Order of the next whole page starting in "start" and ending before "end"
* If both are 0, return the full word size.
*/
static unsigned ucs_pgtable_get_next_page_order(ucs_pgt_addr_t start, ucs_pgt_addr_t end)
{
unsigned log2_len;
ucs_assertv(ucs_pgt_is_addr_aligned(start), "start=0x%lx", start);
ucs_assertv(ucs_pgt_is_addr_aligned(end), "end=0x%lx", end);
if ((end == 0) && (start == 0)) {
log2_len = UCS_PGT_ADDR_ORDER; /* entire range */
} else if (end == start) {
log2_len = UCS_PGT_ADDR_SHIFT;
} else {
log2_len = ucs_ilog2(end - start);
if (start) {
log2_len = ucs_min(ucs_ffs64(start), log2_len);
}
}
ucs_assertv((log2_len >= UCS_PGT_ADDR_SHIFT) &&
(log2_len <= UCS_PGT_ADDR_ORDER),
"log2_len=%u start=0x%lx end=0x%lx",
log2_len, start, end);
/* Order should be: [ADDR_SHIFT + k * ENTRY_SHIFT] */
return (((log2_len - UCS_PGT_ADDR_SHIFT) / UCS_PGT_ENTRY_SHIFT) * UCS_PGT_ENTRY_SHIFT)
+ UCS_PGT_ADDR_SHIFT;
}
/**
* Insert a variable-size page to the page table.
*
* @param address address to insert
* @param order page size to insert - should be k*PTE_SHIFT for a certain k
* @param region region to insert
*/
static ucs_status_t
ucs_pgtable_insert_page(ucs_pgtable_t *pgtable, ucs_pgt_addr_t address,
unsigned order, ucs_pgt_region_t *region)
{
ucs_pgt_dir_t dummy_pgd = {};
ucs_pgt_entry_t *pte;
ucs_pgt_dir_t *pgd;
unsigned shift;
ucs_pgtable_check_page(address, order);
ucs_trace_data("insert page 0x%lx order %u, for region " UCS_PGT_REGION_FMT,
address, order, UCS_PGT_REGION_ARG(region));
/* Make root map addresses which include our interval */
while (pgtable->shift < order) {
ucs_pgtable_expand(pgtable);
}
if (ucs_pgt_entry_present(&pgtable->root)) {
while ((address & pgtable->mask) != pgtable->base) {
ucs_pgtable_expand(pgtable);
}
} else {
pgtable->base = address & pgtable->mask;
ucs_pgtable_trace(pgtable, "initialize");
}
/* Insert the page in the PTE */
pgd = &dummy_pgd;
shift = pgtable->shift;
pte = &pgtable->root;
while (1) {
if (order == shift) {
if (ucs_pgt_entry_present(pte)) {
goto err;
}
ucs_pgt_entry_set_region(pte, region);
++pgd->count;
break;
} else {
if (ucs_pgt_entry_test(pte, UCS_PGT_ENTRY_FLAG_REGION)) {
goto err;
}
ucs_assertv(shift >= UCS_PGT_ENTRY_SHIFT + order,
"shift=%u order=%u", shift, order); /* sub PTE should be able to hold it */
if (!ucs_pgt_entry_present(pte)) {
++pgd->count;
ucs_pgt_entry_set_dir(pte, ucs_pgt_dir_alloc(pgtable));
}
pgd = ucs_pgt_entry_get_dir(pte);
shift -= UCS_PGT_ENTRY_SHIFT;
pte = &pgd->entries[(address >> shift) & UCS_PGT_ENTRY_MASK];
}
}
return UCS_OK;
err:
while (ucs_pgtable_shrink(pgtable));
return UCS_ERR_ALREADY_EXISTS;
}
/*
* `region' is only used to compare pointers
*/
static ucs_status_t
ucs_pgtable_remove_page_recurs(ucs_pgtable_t *pgtable, ucs_pgt_addr_t address,
unsigned order, ucs_pgt_dir_t *pgd,
ucs_pgt_entry_t *pte, unsigned shift,
ucs_pgt_region_t *region)
{
ucs_pgt_dir_t *next_dir;
ucs_pgt_entry_t *next_pte;
ucs_status_t status;
unsigned next_shift;
if (ucs_pgt_entry_test(pte, UCS_PGT_ENTRY_FLAG_REGION)) {
ucs_assertv(shift == order, "shift=%u order=%u", shift, order);
if (ucs_pgt_entry_get_region(pte) != region) {
goto no_elem;
}
--pgd->count;
ucs_pgt_entry_clear(pte);
return UCS_OK;
} else if (ucs_pgt_entry_test(pte, UCS_PGT_ENTRY_FLAG_DIR)) {
next_dir = ucs_pgt_entry_get_dir(pte);
next_shift = shift - UCS_PGT_ENTRY_SHIFT;
next_pte = &next_dir->entries[(address >> next_shift) & UCS_PGT_ENTRY_MASK];
status = ucs_pgtable_remove_page_recurs(pgtable, address, order, next_dir,
next_pte, next_shift, region);
if (status != UCS_OK) {
goto no_elem;
}
if (next_dir->count == 0) {
ucs_pgt_entry_clear(pte);
--pgd->count;
ucs_pgt_dir_release(pgtable, next_dir);
}
return UCS_OK;
}
no_elem:
return UCS_ERR_NO_ELEM;
}
static ucs_status_t
ucs_pgtable_remove_page(ucs_pgtable_t *pgtable, ucs_pgt_addr_t address,
unsigned order, ucs_pgt_region_t *region)
{
ucs_pgt_dir_t dummy_pgd = {};
ucs_status_t status;
ucs_pgtable_check_page(address, order);
if ((address & pgtable->mask) != pgtable->base) {
return UCS_ERR_NO_ELEM;
}
status = ucs_pgtable_remove_page_recurs(pgtable, address, order, &dummy_pgd,
&pgtable->root, pgtable->shift,
region);
if (status != UCS_OK) {
return status;
}
while (ucs_pgtable_shrink(pgtable));
return UCS_OK;
}
ucs_status_t ucs_pgtable_insert(ucs_pgtable_t *pgtable, ucs_pgt_region_t *region)
{
ucs_pgt_addr_t address = region->start;
ucs_pgt_addr_t end = region->end;
ucs_status_t status;
unsigned order;
ucs_trace_data("add region " UCS_PGT_REGION_FMT, UCS_PGT_REGION_ARG(region));
if ((address >= end) || !ucs_pgt_is_addr_aligned(address) ||
!ucs_pgt_is_addr_aligned(end))
{
return UCS_ERR_INVALID_PARAM;
}
ucs_assert(address != end);
while (address < end) {
order = ucs_pgtable_get_next_page_order(address, end);
status = ucs_pgtable_insert_page(pgtable, address, order, region);
if (status != UCS_OK) {
goto err;
}
ucs_pgt_address_advance(&address, order);
}
++pgtable->num_regions;
ucs_pgtable_trace(pgtable, "insert");
return UCS_OK;
err:
/* Revert all pages we've inserted by now */
end = address;
address = region->start;
while (address < end) {
order = ucs_pgtable_get_next_page_order(address, end);
ucs_pgtable_remove_page(pgtable, address, order, region);
ucs_pgt_address_advance(&address, order);
}
return status;
}
ucs_status_t ucs_pgtable_remove(ucs_pgtable_t *pgtable, ucs_pgt_region_t *region)
{
ucs_pgt_addr_t address = region->start;
ucs_pgt_addr_t end = region->end;
ucs_status_t status;
unsigned order;
ucs_trace_data("remove region " UCS_PGT_REGION_FMT, UCS_PGT_REGION_ARG(region));
if ((address >= end) || !ucs_pgt_is_addr_aligned(address) ||
!ucs_pgt_is_addr_aligned(end))
{
return UCS_ERR_NO_ELEM;
}
while (address < end) {
order = ucs_pgtable_get_next_page_order(address, end);
status = ucs_pgtable_remove_page(pgtable, address, order, region);
if (status != UCS_OK) {
ucs_assert(address == region->start); /* Cannot be partially removed */
return status;
}
ucs_pgt_address_advance(&address, order);
}
ucs_assert(pgtable->num_regions > 0);
--pgtable->num_regions;
ucs_pgtable_trace(pgtable, "remove");
return UCS_OK;
}
ucs_pgt_region_t *ucs_pgtable_lookup(const ucs_pgtable_t *pgtable,
ucs_pgt_addr_t address)
{
const ucs_pgt_entry_t *pte;
ucs_pgt_region_t *region;
ucs_pgt_dir_t *dir;
unsigned shift;
ucs_trace_func("pgtable=%p address=0x%lx", pgtable, address);
/* Check if the address is mapped by the page table */
if ((address & pgtable->mask) != pgtable->base) {
return NULL;
}
/* Descend into the page table */
pte = &pgtable->root;
shift = pgtable->shift;
for (;;) {
if (ucs_pgt_entry_test(pte, UCS_PGT_ENTRY_FLAG_REGION)) {
region = ucs_pgt_entry_get_region(pte);
ucs_assert((address >= region->start) && (address < region->end));
return region;
} else if (ucs_pgt_entry_test(pte, UCS_PGT_ENTRY_FLAG_DIR)) {
dir = ucs_pgt_entry_get_dir(pte);
shift -= UCS_PGT_ENTRY_SHIFT;
pte = &dir->entries[(address >> shift) & UCS_PGT_ENTRY_MASK];
} else {
return NULL;
}
}
}
static void ucs_pgtable_search_recurs(const ucs_pgtable_t *pgtable,
ucs_pgt_addr_t address, unsigned order,
const ucs_pgt_entry_t *pte, unsigned shift,
ucs_pgt_search_callback_t cb, void *arg,
ucs_pgt_region_t **last_p)
{
ucs_pgt_entry_t *next_pte;
ucs_pgt_region_t *region;
ucs_pgt_dir_t *dir;
unsigned next_shift;
unsigned i;
if (ucs_pgt_entry_test(pte, UCS_PGT_ENTRY_FLAG_REGION)) {
region = ucs_pgt_entry_value(pte);
/* Check that we are not continuing with the previous region */
if (*last_p == region) {
return;
} else if (*last_p != NULL) {
ucs_assert(region->start >= (*last_p)->end);
}
*last_p = region;
/* Assert that the region actually overlaps the address */
ucs_assertv(ucs_max(region->start, address) <=
ucs_min(region->end - 1, address + UCS_MASK_SAFE(order)),
UCS_PGT_REGION_FMT " address=0x%lx order=%d mask 0x%lx",
UCS_PGT_REGION_ARG(region), address, order,
(ucs_pgt_addr_t)UCS_MASK_SAFE(order));
/* Call the callback */
cb(pgtable, region, arg);
} else if (ucs_pgt_entry_test(pte, UCS_PGT_ENTRY_FLAG_DIR)) {
dir = ucs_pgt_entry_get_dir(pte);
ucs_assert(shift >= UCS_PGT_ENTRY_SHIFT);
next_shift = shift - UCS_PGT_ENTRY_SHIFT;
if (order < shift) {
/* One of the sub-ptes maps the region */
ucs_assert(order <= next_shift);
next_pte = &dir->entries[(address >> next_shift) & UCS_PGT_ENTRY_MASK];
ucs_pgtable_search_recurs(pgtable, address, order, next_pte,
next_shift, cb, arg, last_p);
} else {
/* All sub-ptes contained in the region */
for (i = 0; i < UCS_PGT_ENTRIES_PER_DIR; ++i) {
next_pte = &dir->entries[i];
ucs_pgtable_search_recurs(pgtable, address, order, next_pte,
next_shift, cb, arg, last_p);
}
}
}
}
void ucs_pgtable_search_range(const ucs_pgtable_t *pgtable,
ucs_pgt_addr_t from, ucs_pgt_addr_t to,
ucs_pgt_search_callback_t cb, void *arg)
{
ucs_pgt_addr_t address = ucs_align_down_pow2(from, UCS_PGT_ADDR_ALIGN);
ucs_pgt_addr_t end = ucs_align_up_pow2(to, UCS_PGT_ADDR_ALIGN);
ucs_pgt_region_t *last;
unsigned order;
/* if the page table is covering only part of the address space, intersect
* the range with page table address span */
if (pgtable->shift < (sizeof(uint64_t) * 8)) {
address = ucs_max(address, pgtable->base);
end = ucs_min(end, pgtable->base + UCS_BIT(pgtable->shift));
} else {
ucs_assert(pgtable->base == 0);
}
last = NULL;
while (address <= to) {
order = ucs_pgtable_get_next_page_order(address, end);
if ((address & pgtable->mask) == pgtable->base) {
ucs_pgtable_search_recurs(pgtable, address, order, &pgtable->root,
pgtable->shift, cb, arg, &last);
}
if (order == UCS_PGT_ADDR_ORDER) {
break;
}
ucs_pgt_address_advance(&address, order);
}
}
static void ucs_pgtable_purge_callback(const ucs_pgtable_t *pgtable,
ucs_pgt_region_t *region,
void *arg)
{
ucs_pgt_region_t ***region_pp = arg;
**region_pp = region;
++(*region_pp);
}
void ucs_pgtable_purge(ucs_pgtable_t *pgtable, ucs_pgt_search_callback_t cb,
void *arg)
{
unsigned num_regions = pgtable->num_regions;
ucs_pgt_region_t **all_regions, **next_region, *region;
ucs_pgt_addr_t from, to;
ucs_status_t status;
unsigned i;
all_regions = ucs_calloc(num_regions, sizeof(*all_regions),
"pgt_purge_regions");
if (all_regions == NULL) {
ucs_warn("failed to allocate array to collect all regions, will leak");
return;
}
next_region = all_regions;
from = pgtable->base;
to = pgtable->base + ((1ul << pgtable->shift) & pgtable->mask) - 1;
ucs_pgtable_search_range(pgtable, from, to, ucs_pgtable_purge_callback,
&next_region);
ucs_assertv(next_region == all_regions + num_regions,
"next_region=%p all_regions=%p num_regions=%u",
next_region, all_regions, num_regions);
for (i = 0; i < num_regions; ++i) {
region = all_regions[i];
status = ucs_pgtable_remove(pgtable, region);
if (status != UCS_OK) {
ucs_warn("failed to remove pgtable region" UCS_PGT_REGION_FMT,
UCS_PGT_REGION_ARG(region));
}
cb(pgtable, region, arg);
}
ucs_free(all_regions);
/* Page table should be totally empty */
ucs_assert(!ucs_pgt_entry_present(&pgtable->root));
ucs_assertv(pgtable->shift == UCS_PGT_ADDR_SHIFT, "shift=%u", pgtable->shift);
ucs_assertv(pgtable->base == 0, "value=0x%lx", pgtable->base);
ucs_assertv(pgtable->num_regions == 0, "num_regions=%u", pgtable->num_regions);
}
ucs_status_t ucs_pgtable_init(ucs_pgtable_t *pgtable,
ucs_pgt_dir_alloc_callback_t alloc_cb,
ucs_pgt_dir_release_callback_t release_cb)
{
UCS_STATIC_ASSERT(ucs_is_pow2(UCS_PGT_ENTRY_MIN_ALIGN));
/* ADDR_MAX+1 must be power of 2, or wrap around to 0. */
UCS_STATIC_ASSERT(ucs_is_pow2_or_zero(UCS_PGT_ADDR_MAX + 1));
/* We must cover all bits of the address up to ADDR_MAX */
UCS_STATIC_ASSERT(((ucs_ilog2(UCS_PGT_ADDR_MAX) + 1 - UCS_PGT_ADDR_SHIFT) %
UCS_PGT_ENTRY_SHIFT) == 0);
ucs_pgt_entry_clear(&pgtable->root);
ucs_pgtable_reset(pgtable);
pgtable->num_regions = 0;
pgtable->pgd_alloc_cb = alloc_cb;
pgtable->pgd_release_cb = release_cb;
return UCS_OK;
}
void ucs_pgtable_cleanup(ucs_pgtable_t *pgtable)
{
if (pgtable->num_regions != 0) {
ucs_warn("page table not empty during cleanup");
}
}