#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: xengnt.c,v 1.41 2023/02/25 00:35:52 riastradh Exp $");
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/queue.h>
#include <sys/extent.h>
#include <sys/kernel.h>
#include <sys/mutex.h>
#include <uvm/uvm.h>
#include <xen/hypervisor.h>
#include <xen/xen.h>
#include <xen/granttables.h>
#include "opt_xen.h"
#ifdef XENDEBUG
#define DPRINTF(x) printf x
#else
#define DPRINTF(x)
#endif
#define NR_RESERVED_ENTRIES 8
int gnt_v = 0;
#define GNT_ISV1 (gnt_v == 1)
#define GNT_ISV2 (gnt_v == 2)
int gnt_nr_grant_frames;
int gnt_max_grant_frames;
grant_ref_t *gnt_entries;
int last_gnt_entry;
#define XENGNT_NO_ENTRY 0xffffffff
#define NR_GRANT_ENTRIES_PER_PAGE_V1 (PAGE_SIZE / sizeof(grant_entry_v1_t))
#define NR_GRANT_ENTRIES_PER_PAGE_V2 (PAGE_SIZE / sizeof(grant_entry_v2_t))
#define NR_GRANT_ENTRIES_PER_PAGE \
((gnt_v == 1) ? NR_GRANT_ENTRIES_PER_PAGE_V1 : NR_GRANT_ENTRIES_PER_PAGE_V2)
#define NR_GRANT_STATUS_PER_PAGE (PAGE_SIZE / sizeof(grant_status_t))
union {
grant_entry_v1_t *gntt_v1;
grant_entry_v2_t *gntt_v2;
void *gntt;
} grant_table;
int gnt_status_frames;
grant_status_t *grant_status;
kmutex_t grant_lock;
static grant_ref_t xengnt_get_entry(void);
static void xengnt_free_entry(grant_ref_t);
static int xengnt_more_entries(void);
static int xengnt_map_status(void);
static bool xengnt_finish_init(void);
void
xengnt_init(void)
{
struct gnttab_query_size query;
int rc;
int nr_grant_entries;
int i;
struct gnttab_set_version gntversion;
gnt_v = gntversion.version = 2;
rc = HYPERVISOR_grant_table_op(GNTTABOP_set_version, &gntversion, 1);
if (rc < 0 || gntversion.version != 2) {
aprint_debug("GNTTABOP_set_version 2 failed (%d), "
"fall back to version 1\n", rc);
gnt_v = 1;
}
query.dom = DOMID_SELF;
rc = HYPERVISOR_grant_table_op(GNTTABOP_query_size, &query, 1);
if ((rc < 0) || (query.status != GNTST_okay))
gnt_max_grant_frames = 4;
else
gnt_max_grant_frames = query.max_nr_frames;
gnt_nr_grant_frames = gnt_max_grant_frames;
nr_grant_entries =
gnt_max_grant_frames * NR_GRANT_ENTRIES_PER_PAGE;
grant_table.gntt = (void *)uvm_km_alloc(kernel_map,
gnt_max_grant_frames * PAGE_SIZE, 0, UVM_KMF_VAONLY);
if (grant_table.gntt == NULL)
panic("xengnt_init() table no VM space");
gnt_entries = kmem_alloc((nr_grant_entries + 1) * sizeof(grant_ref_t),
KM_SLEEP);
for (i = 0; i <= nr_grant_entries; i++)
gnt_entries[i] = XENGNT_NO_ENTRY;
if (GNT_ISV2) {
gnt_status_frames =
round_page(nr_grant_entries * sizeof(grant_status_t)) / PAGE_SIZE;
grant_status = (void *)uvm_km_alloc(kernel_map,
gnt_status_frames * PAGE_SIZE, 0, UVM_KMF_VAONLY);
if (grant_status == NULL)
panic("xengnt_init() status no VM space");
}
mutex_init(&grant_lock, MUTEX_DEFAULT, IPL_VM);
xengnt_finish_init();
}
bool
xengnt_resume(void)
{
int rc;
struct gnttab_set_version gntversion;
KASSERT(gnt_v == 1 || gnt_v == 2);
gntversion.version = gnt_v;
rc = HYPERVISOR_grant_table_op(GNTTABOP_set_version, &gntversion, 1);
if (GNT_ISV2) {
if (rc < 0 || gntversion.version != 2) {
panic("GNTTABOP_set_version 2 failed %d", rc);
}
} else {
if (rc == 0 && gntversion.version != 1) {
panic("GNTTABOP_set_version 1 failed");
}
}
return xengnt_finish_init();
}
static bool
xengnt_finish_init(void)
{
int previous_nr_grant_frames = gnt_nr_grant_frames;
last_gnt_entry = 0;
gnt_nr_grant_frames = 0;
mutex_enter(&grant_lock);
while (gnt_nr_grant_frames < previous_nr_grant_frames) {
if (xengnt_more_entries() != 0)
panic("xengnt_resume: can't restore grant frames");
}
if (GNT_ISV2)
xengnt_map_status();
mutex_exit(&grant_lock);
return true;
}
bool
xengnt_suspend(void) {
int i;
mutex_enter(&grant_lock);
KASSERT(gnt_entries[last_gnt_entry] == XENGNT_NO_ENTRY);
for (i = 0; i < last_gnt_entry; i++) {
gnt_entries[i] = XENGNT_NO_ENTRY;
}
pmap_kremove((vaddr_t)grant_table.gntt, gnt_nr_grant_frames * PAGE_SIZE);
if (GNT_ISV2) {
pmap_kremove((vaddr_t)grant_status, gnt_status_frames * PAGE_SIZE);
}
pmap_update(pmap_kernel());
mutex_exit(&grant_lock);
return true;
}
static int
xengnt_map_status(void)
{
uint64_t *pages;
size_t sz;
KASSERT(mutex_owned(&grant_lock));
KASSERT(GNT_ISV2);
sz = gnt_status_frames * sizeof(*pages);
pages = kmem_alloc(sz, KM_NOSLEEP);
if (pages == NULL)
return ENOMEM;
#ifdef XENPV
gnttab_get_status_frames_t getstatus;
int err;
getstatus.dom = DOMID_SELF;
getstatus.nr_frames = gnt_status_frames;
set_xen_guest_handle(getstatus.frame_list, pages);
if ((err = HYPERVISOR_grant_table_op(GNTTABOP_get_status_frames,
&getstatus, 1)) != 0)
panic("%s: get_status_frames failed: %d", __func__, err);
if (getstatus.status != GNTST_okay) {
aprint_error("%s: get_status_frames returned %d\n",
__func__, getstatus.status);
kmem_free(pages, sz);
return ENOMEM;
}
#else
for (int i = 0; i < gnt_status_frames; i++) {
struct vm_page *pg;
struct xen_add_to_physmap xmap;
pg = uvm_pagealloc(NULL, 0, NULL, UVM_PGA_USERESERVE|UVM_PGA_ZERO);
pages[i] = atop(uvm_vm_page_to_phys(pg));
xmap.domid = DOMID_SELF;
xmap.space = XENMAPSPACE_grant_table;
xmap.idx = i | XENMAPIDX_grant_table_status;
xmap.gpfn = pages[i];
if (HYPERVISOR_memory_op(XENMEM_add_to_physmap, &xmap) < 0)
panic("%s: Unable to add grant tables\n", __func__);
}
#endif
for (int i = 0; i < gnt_status_frames; i++) {
pmap_kenter_ma(((vaddr_t)grant_status) + i * PAGE_SIZE,
((paddr_t)pages[i]) << PAGE_SHIFT,
VM_PROT_WRITE, 0);
}
pmap_update(pmap_kernel());
kmem_free(pages, sz);
return 0;
}
static int
xengnt_more_entries(void)
{
gnttab_setup_table_t setup;
u_long *pages;
int nframes_new = gnt_nr_grant_frames + 1;
int i, start_gnt;
size_t sz;
KASSERT(mutex_owned(&grant_lock));
if (gnt_nr_grant_frames == gnt_max_grant_frames)
return ENOMEM;
sz = nframes_new * sizeof(*pages);
pages = kmem_alloc(sz, KM_NOSLEEP);
if (pages == NULL)
return ENOMEM;
if (xen_feature(XENFEAT_auto_translated_physmap)) {
struct vm_page *pg;
struct xen_add_to_physmap xmap;
pg = uvm_pagealloc(NULL, 0, NULL, UVM_PGA_USERESERVE|UVM_PGA_ZERO);
pages[gnt_nr_grant_frames] = atop(uvm_vm_page_to_phys(pg));
xmap.domid = DOMID_SELF;
xmap.space = XENMAPSPACE_grant_table;
xmap.idx = gnt_nr_grant_frames;
xmap.gpfn = pages[gnt_nr_grant_frames];
if (HYPERVISOR_memory_op(XENMEM_add_to_physmap, &xmap) < 0)
panic("%s: Unable to add grant frames\n", __func__);
} else {
setup.dom = DOMID_SELF;
setup.nr_frames = nframes_new;
set_xen_guest_handle(setup.frame_list, pages);
if (HYPERVISOR_grant_table_op(GNTTABOP_setup_table, &setup, 1) != 0)
panic("%s: setup table failed", __func__);
if (setup.status != GNTST_okay) {
aprint_error("%s: setup table returned %d\n",
__func__, setup.status);
kmem_free(pages, sz);
return ENOMEM;
}
}
DPRINTF(("xengnt_more_entries: map 0x%lx -> %p\n",
pages[gnt_nr_grant_frames],
(char *)grant_table + gnt_nr_grant_frames * PAGE_SIZE));
pmap_kenter_ma(((vaddr_t)grant_table.gntt) + gnt_nr_grant_frames * PAGE_SIZE,
((paddr_t)pages[gnt_nr_grant_frames]) << PAGE_SHIFT,
VM_PROT_WRITE, 0);
pmap_update(pmap_kernel());
start_gnt = (gnt_nr_grant_frames * NR_GRANT_ENTRIES_PER_PAGE) <
(NR_RESERVED_ENTRIES + 1) ?
(NR_RESERVED_ENTRIES + 1) :
(gnt_nr_grant_frames * NR_GRANT_ENTRIES_PER_PAGE);
for (i = start_gnt;
i < nframes_new * NR_GRANT_ENTRIES_PER_PAGE;
i++) {
KASSERT(gnt_entries[last_gnt_entry] == XENGNT_NO_ENTRY);
gnt_entries[last_gnt_entry] = i;
last_gnt_entry++;
}
gnt_nr_grant_frames = nframes_new;
kmem_free(pages, sz);
return 0;
}
static grant_ref_t
xengnt_get_entry(void)
{
grant_ref_t entry;
static struct timeval xengnt_nonmemtime;
static const struct timeval xengnt_nonmemintvl = {5,0};
KASSERT(mutex_owned(&grant_lock));
if (__predict_false(last_gnt_entry == 0)) {
if (ratecheck(&xengnt_nonmemtime, &xengnt_nonmemintvl))
printf("xengnt_get_entry: out of grant "
"table entries\n");
return XENGNT_NO_ENTRY;
}
KASSERT(gnt_entries[last_gnt_entry] == XENGNT_NO_ENTRY);
last_gnt_entry--;
entry = gnt_entries[last_gnt_entry];
gnt_entries[last_gnt_entry] = XENGNT_NO_ENTRY;
KASSERT(entry != XENGNT_NO_ENTRY && entry > NR_RESERVED_ENTRIES);
KASSERT(last_gnt_entry >= 0);
KASSERT(last_gnt_entry <= gnt_max_grant_frames * NR_GRANT_ENTRIES_PER_PAGE);
return entry;
}
static void
xengnt_free_entry(grant_ref_t entry)
{
mutex_enter(&grant_lock);
KASSERT(entry > NR_RESERVED_ENTRIES);
KASSERT(gnt_entries[last_gnt_entry] == XENGNT_NO_ENTRY);
KASSERT(last_gnt_entry >= 0);
KASSERT(last_gnt_entry <= gnt_max_grant_frames * NR_GRANT_ENTRIES_PER_PAGE);
gnt_entries[last_gnt_entry] = entry;
last_gnt_entry++;
mutex_exit(&grant_lock);
}
int
xengnt_grant_access(domid_t dom, paddr_t ma, int ro, grant_ref_t *entryp)
{
mutex_enter(&grant_lock);
*entryp = xengnt_get_entry();
if (__predict_false(*entryp == XENGNT_NO_ENTRY)) {
mutex_exit(&grant_lock);
return ENOMEM;
}
if (GNT_ISV2) {
grant_table.gntt_v2[*entryp].full_page.frame = ma >> PAGE_SHIFT;
grant_table.gntt_v2[*entryp].hdr.domid = dom;
xen_wmb();
grant_table.gntt_v2[*entryp].hdr.flags =
GTF_permit_access | (ro ? GTF_readonly : 0);
} else {
grant_table.gntt_v1[*entryp].frame = ma >> PAGE_SHIFT;
grant_table.gntt_v1[*entryp].domid = dom;
xen_wmb();
grant_table.gntt_v1[*entryp].flags =
GTF_permit_access | (ro ? GTF_readonly : 0);
}
mutex_exit(&grant_lock);
return 0;
}
static inline uint16_t
xen_atomic_cmpxchg16(volatile uint16_t *ptr, uint16_t val, uint16_t newval)
{
unsigned long result;
__asm volatile(__LOCK_PREFIX
"cmpxchgw %w1,%2"
:"=a" (result)
:"q"(newval), "m" (*ptr), "0" (val)
:"memory");
return result;
}
void
xengnt_revoke_access(grant_ref_t entry)
{
if (GNT_ISV2) {
grant_table.gntt_v2[entry].hdr.flags = 0;
xen_mb();
if (__predict_false(
(grant_status[entry] & (GTF_reading|GTF_writing)) != 0)) {
printf("xengnt_revoke_access(%u): still in use\n",
entry);
} else {
__insn_barrier();
}
} else {
uint16_t flags, nflags;
nflags = grant_table.gntt_v1[entry].flags;
do {
if ((flags = nflags) & (GTF_reading|GTF_writing))
panic("xengnt_revoke_access: still in use");
nflags = xen_atomic_cmpxchg16(
&grant_table.gntt_v1[entry].flags, flags, 0);
} while (nflags != flags);
}
xengnt_free_entry(entry);
}
int
xengnt_status(grant_ref_t entry)
{
if (GNT_ISV2)
return grant_status[entry] & (GTF_reading|GTF_writing);
else
return (grant_table.gntt_v1[entry].flags & (GTF_reading|GTF_writing));
}