zephyr/drivers/xen/gnttab.c
Mykola Kvach 62fd5ab3e1 drivers: xen: gnttab: do Xen node mapping inside driver
Move memory mapping of Xen node to Grant Table driver system init
function. After moving mapping we don't need anymore records of
xen-xen node into 'mmu_regions' array, so they were deleted from
all SoCs: Rcar Gen3/Gen4 and XenVM.

We need at least 16M of virtual address space to map memory of Xen
node, so the virtual memory sized has been increased to 32 MB, it
should be enough for basic use-cases and mapping of 16M mem region
of Xen node.

Unfortunately, after moving we also need to increase number of XLAT
tables. The previous code was more efficient if we talking about
usage of XLAT tables, because it mapped grant tables using a higher-
order table that allows mapping blocks of 2MB. And after the changes
is maps every 4KB page, so we need more XLAT tables.

Increase number of grant frames, it is needed to sync stage 1 and stage 2
memory mappings, previously we map only one page on stage 2 and further
usage of unmap regions can cause MMU translation errors.

Perform mapping stage 1 before mapping for stage 2 (add to physmap),
because right after stage 1 we can try to access memory and if it is
unmap in stage 2, error will be received during translation.

Note: Xen Grant Table driver doesn't use Zephyr Device Model.

Authored-by: Mykola Kvach <mykola_kvach@epam.com>
Co-authored-by: Oleksii Moisieiev <oleksii_moisieiev@epam.com>
Signed-off-by: Mykola Kvach <mykola_kvach@epam.com>
2023-09-15 11:15:00 +01:00

343 lines
8.2 KiB
C

/* SPDX-License-Identifier: MIT */
/*
****************************************************************************
* (C) 2006 - Cambridge University
* (C) 2021-2022 - EPAM Systems
****************************************************************************
*
* File: gnttab.c
* Author: Steven Smith (sos22@cam.ac.uk)
* Changes: Grzegorz Milos (gm281@cam.ac.uk)
*
* Date: July 2006
*
* Environment: Xen Minimal OS
* Description: Simple grant tables implementation. About as stupid as it's
* possible to be and still work.
*
****************************************************************************
*/
#include <zephyr/arch/arm64/hypercall.h>
#include <zephyr/xen/generic.h>
#include <zephyr/xen/gnttab.h>
#include <zephyr/xen/public/grant_table.h>
#include <zephyr/xen/public/memory.h>
#include <zephyr/xen/public/xen.h>
#include <zephyr/sys/barrier.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/device_mmio.h>
LOG_MODULE_REGISTER(xen_gnttab);
/* Timeout for grant table ops retrying */
#define GOP_RETRY_DELAY 200
#define GNTTAB_SIZE DT_REG_SIZE_BY_IDX(DT_INST(0, xen_xen), 0)
BUILD_ASSERT(!(GNTTAB_SIZE % XEN_PAGE_SIZE), "Size of gnttab have to be aligned on XEN_PAGE_SIZE");
/* NR_GRANT_FRAMES must be less than or equal to that configured in Xen */
#define NR_GRANT_FRAMES (GNTTAB_SIZE / XEN_PAGE_SIZE)
#define NR_GRANT_ENTRIES \
(NR_GRANT_FRAMES * XEN_PAGE_SIZE / sizeof(grant_entry_v1_t))
BUILD_ASSERT(GNTTAB_SIZE <= CONFIG_KERNEL_VM_SIZE);
DEVICE_MMIO_TOPLEVEL_STATIC(grant_tables, DT_INST(0, xen_xen));
static struct gnttab {
struct k_sem sem;
grant_entry_v1_t *table;
grant_ref_t gref_list[NR_GRANT_ENTRIES];
} gnttab;
static grant_ref_t get_free_entry(void)
{
grant_ref_t gref;
unsigned int flags;
k_sem_take(&gnttab.sem, K_FOREVER);
flags = irq_lock();
gref = gnttab.gref_list[0];
__ASSERT((gref >= GNTTAB_NR_RESERVED_ENTRIES &&
gref < NR_GRANT_ENTRIES), "Invalid gref = %d", gref);
gnttab.gref_list[0] = gnttab.gref_list[gref];
irq_unlock(flags);
return gref;
}
static void put_free_entry(grant_ref_t gref)
{
unsigned int flags;
flags = irq_lock();
gnttab.gref_list[gref] = gnttab.gref_list[0];
gnttab.gref_list[0] = gref;
irq_unlock(flags);
k_sem_give(&gnttab.sem);
}
static void gnttab_grant_permit_access(grant_ref_t gref, domid_t domid,
unsigned long gfn, bool readonly)
{
uint16_t flags = GTF_permit_access;
if (readonly) {
flags |= GTF_readonly;
}
gnttab.table[gref].frame = gfn;
gnttab.table[gref].domid = domid;
/* Need to be sure that gfn and domid will be set before flags */
barrier_dmem_fence_full();
gnttab.table[gref].flags = flags;
}
grant_ref_t gnttab_grant_access(domid_t domid, unsigned long gfn,
bool readonly)
{
grant_ref_t gref = get_free_entry();
gnttab_grant_permit_access(gref, domid, gfn, readonly);
return gref;
}
/* Reset flags to zero in order to stop using the grant */
static int gnttab_reset_flags(grant_ref_t gref)
{
uint16_t flags, nflags;
uint16_t *pflags;
pflags = &gnttab.table[gref].flags;
nflags = *pflags;
do {
flags = nflags;
if (flags & (GTF_reading | GTF_writing)) {
LOG_WRN("gref = %u still in use! (0x%x)\n",
gref, flags);
return 1;
}
nflags = synch_cmpxchg(pflags, flags, 0);
} while (nflags != flags);
return 0;
}
int gnttab_end_access(grant_ref_t gref)
{
int rc;
__ASSERT((gref >= GNTTAB_NR_RESERVED_ENTRIES &&
gref < NR_GRANT_ENTRIES), "Invalid gref = %d", gref);
rc = gnttab_reset_flags(gref);
if (!rc) {
return rc;
}
put_free_entry(gref);
return 0;
}
int32_t gnttab_alloc_and_grant(void **map, bool readonly)
{
void *page;
unsigned long gfn;
grant_ref_t gref;
__ASSERT_NO_MSG(map != NULL);
page = k_aligned_alloc(XEN_PAGE_SIZE, XEN_PAGE_SIZE);
if (page == NULL) {
return -ENOMEM;
}
gfn = xen_virt_to_gfn(page);
gref = gnttab_grant_access(0, gfn, readonly);
*map = page;
return gref;
}
static void gop_eagain_retry(int cmd, struct gnttab_map_grant_ref *gref)
{
unsigned int step = 10, delay = step;
int16_t *status = &gref->status;
do {
HYPERVISOR_grant_table_op(cmd, gref, 1);
if (*status == GNTST_eagain) {
k_sleep(K_MSEC(delay));
}
delay += step;
} while ((*status == GNTST_eagain) && (delay < GOP_RETRY_DELAY));
if (delay >= GOP_RETRY_DELAY) {
LOG_ERR("Failed to map grant, timeout reached\n");
*status = GNTST_bad_page;
}
}
void *gnttab_get_page(void)
{
int ret;
void *page_addr;
struct xen_remove_from_physmap rfpm;
page_addr = k_aligned_alloc(XEN_PAGE_SIZE, XEN_PAGE_SIZE);
if (!page_addr) {
LOG_WRN("Failed to allocate memory for gnttab page!\n");
return NULL;
}
rfpm.domid = DOMID_SELF;
rfpm.gpfn = xen_virt_to_gfn(page_addr);
/*
* GNTTABOP_map_grant_ref will simply replace the entry in the P2M
* and not release any RAM that may have been associated with
* page_addr, so we release this memory before mapping.
*/
ret = HYPERVISOR_memory_op(XENMEM_remove_from_physmap, &rfpm);
if (ret) {
LOG_WRN("Failed to remove gnttab page from physmap, ret = %d\n", ret);
return NULL;
}
return page_addr;
}
void gnttab_put_page(void *page_addr)
{
int ret, nr_extents = 1;
struct xen_memory_reservation reservation;
xen_pfn_t page = xen_virt_to_gfn(page_addr);
/*
* After unmapping there will be a 4Kb holes in address space
* at 'page_addr' positions. To keep it contiguous and be able
* to return such addresses to memory allocator we need to
* populate memory on unmapped positions here.
*/
memset(&reservation, 0, sizeof(reservation));
reservation.domid = DOMID_SELF;
reservation.extent_order = 0;
reservation.nr_extents = nr_extents;
set_xen_guest_handle(reservation.extent_start, &page);
ret = HYPERVISOR_memory_op(XENMEM_populate_physmap, &reservation);
if (ret != nr_extents) {
LOG_WRN("failed to populate physmap on gfn = 0x%llx, ret = %d\n",
page, ret);
return;
}
k_free(page_addr);
}
int gnttab_map_refs(struct gnttab_map_grant_ref *map_ops, unsigned int count)
{
int i, ret;
ret = HYPERVISOR_grant_table_op(GNTTABOP_map_grant_ref, map_ops, count);
if (ret) {
return ret;
}
for (i = 0; i < count; i++) {
switch (map_ops[i].status) {
case GNTST_no_device_space:
LOG_WRN("map_grant_ref failed, no device space for page #%d\n", i);
break;
case GNTST_eagain:
/* Operation not done; need to try again */
gop_eagain_retry(GNTTABOP_map_grant_ref, &map_ops[i]);
/* Need to re-check status for current page */
i--;
break;
default:
break;
}
}
return 0;
}
int gnttab_unmap_refs(struct gnttab_map_grant_ref *unmap_ops, unsigned int count)
{
return HYPERVISOR_grant_table_op(GNTTABOP_unmap_grant_ref, unmap_ops, count);
}
static const char * const gnttab_error_msgs[] = GNTTABOP_error_msgs;
const char *gnttabop_error(int16_t status)
{
status = -status;
if (status < 0 || (uint16_t) status >= ARRAY_SIZE(gnttab_error_msgs)) {
return "bad status";
} else {
return gnttab_error_msgs[status];
}
}
static int gnttab_init(void)
{
grant_ref_t gref;
struct xen_add_to_physmap xatp;
struct gnttab_setup_table setup;
xen_pfn_t frames[NR_GRANT_FRAMES];
int rc = 0, i;
/* Will be taken/given during gnt_refs allocation/release */
k_sem_init(&gnttab.sem, 0, NR_GRANT_ENTRIES - GNTTAB_NR_RESERVED_ENTRIES);
for (
gref = GNTTAB_NR_RESERVED_ENTRIES;
gref < NR_GRANT_ENTRIES;
gref++
) {
put_free_entry(gref);
}
for (i = 0; i < NR_GRANT_FRAMES; i++) {
xatp.domid = DOMID_SELF;
xatp.size = 0;
xatp.space = XENMAPSPACE_grant_table;
xatp.idx = i;
xatp.gpfn = xen_virt_to_gfn(Z_TOPLEVEL_ROM_NAME(grant_tables).phys_addr) + i;
rc = HYPERVISOR_memory_op(XENMEM_add_to_physmap, &xatp);
__ASSERT(!rc, "add_to_physmap failed; status = %d\n", rc);
}
setup.dom = DOMID_SELF;
setup.nr_frames = NR_GRANT_FRAMES;
set_xen_guest_handle(setup.frame_list, frames);
rc = HYPERVISOR_grant_table_op(GNTTABOP_setup_table, &setup, 1);
__ASSERT((!rc) && (!setup.status), "Table setup failed; status = %s\n",
gnttabop_error(setup.status));
DEVICE_MMIO_TOPLEVEL_MAP(grant_tables, K_MEM_CACHE_WB | K_MEM_PERM_RW);
gnttab.table = (grant_entry_v1_t *)DEVICE_MMIO_TOPLEVEL_GET(grant_tables);
LOG_DBG("%s: grant table mapped\n", __func__);
return 0;
}
SYS_INIT(gnttab_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);