/**
* pvr_page_table_l1_get_or_insert() - Retrieves (optionally inserting if
* necessary) a level 1 page table from the specified level 2 page table entry.
* @op_ctx: Target MMU op context.
* @should_insert: [IN] Specifies whether new page tables should be inserted
* when empty page table entries are encountered during traversal.
*
* Return:
* * 0 on success, or
*
* If @should_insert is %false:
* * -%ENXIO if a level 1 page table would have been inserted.
*
* If @should_insert is %true:
* * Any error encountered while inserting the level 1 page table.
*/
static int
pvr_page_table_l1_get_or_insert(struct pvr_mmu_op_context *op_ctx,
bool should_insert)
{
struct pvr_page_table_l2 *l2_table =
&op_ctx->mmu_ctx->page_table_l2;
struct pvr_page_table_l1 *table;
if (pvr_page_table_l2_entry_is_valid(l2_table,
op_ctx->curr_page.l2_idx)) {
op_ctx->curr_page.l1_table =
l2_table->entries[op_ctx->curr_page.l2_idx];
return 0;
}
if (!should_insert)
return -ENXIO;
/* Take a prealloced table. */
table = op_ctx->map.l1_prealloc_tables;
if (!table)
return -ENOMEM;
/* Pop */
op_ctx->map.l1_prealloc_tables = table->next_free;
table->next_free = NULL;
/* Ensure new table is fully written out before adding to L2 page table. */
wmb();
pvr_page_table_l2_insert(op_ctx, table);
return 0;
}
/**
* pvr_page_table_l0_get_or_insert() - Retrieves (optionally inserting if
* necessary) a level 0 page table from the specified level 1 page table entry.
* @op_ctx: Target MMU op context.
* @should_insert: [IN] Specifies whether new page tables should be inserted
* when empty page table entries are encountered during traversal.
*
* Return:
* * 0 on success,
*
* If @should_insert is %false:
* * -%ENXIO if a level 0 page table would have been inserted.
*
* If @should_insert is %true:
* * Any error encountered while inserting the level 0 page table.
*/
static int
pvr_page_table_l0_get_or_insert(struct pvr_mmu_op_context *op_ctx,
bool should_insert)
{
struct pvr_page_table_l0 *table;
if (pvr_page_table_l1_entry_is_valid(op_ctx->curr_page.l1_table,
op_ctx->curr_page.l1_idx)) {
op_ctx->curr_page.l0_table =
op_ctx->curr_page.l1_table->entries[op_ctx->curr_page.l1_idx];
return 0;
}
if (!should_insert)
return -ENXIO;
/* Take a prealloced table. */
table = op_ctx->map.l0_prealloc_tables;
if (!table)
return -ENOMEM;
/* Pop */
op_ctx->map.l0_prealloc_tables = table->next_free;
table->next_free = NULL;
/* Ensure new table is fully written out before adding to L1 page table. */
wmb();
pvr_page_table_l1_insert(op_ctx, table);
return 0;
}
/**
* pvr_mmu_context_create() - Create an MMU context.
* @pvr_dev: PVR device associated with owning VM context.
*
* Returns:
* * Newly created MMU context object on success, or
* * -%ENOMEM if no memory is available,
* * Any error code returned by pvr_page_table_l2_init().
*/
struct pvr_mmu_context *pvr_mmu_context_create(struct pvr_device *pvr_dev)
{
struct pvr_mmu_context *ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
int err;
if (!ctx)
return ERR_PTR(-ENOMEM);
err = pvr_page_table_l2_init(&ctx->page_table_l2, pvr_dev);
if (err)
return ERR_PTR(err);
/**
* pvr_mmu_get_root_table_dma_addr() - Get the DMA address of the root of the
* page table structure behind a VM context.
* @ctx: Target MMU context.
*/
dma_addr_t pvr_mmu_get_root_table_dma_addr(struct pvr_mmu_context *ctx)
{
return ctx->page_table_l2.backing_page.dma_addr;
}
/**
* pvr_page_table_l1_alloc() - Allocate a l1 page_table object.
* @ctx: MMU context of owning VM context.
*
* Returns:
* * Newly created page table object on success, or
* * -%ENOMEM if no memory is available,
* * Any error code returned by pvr_page_table_l1_init().
*/
static struct pvr_page_table_l1 *
pvr_page_table_l1_alloc(struct pvr_mmu_context *ctx)
{
int err;
/**
* pvr_page_table_l0_alloc() - Allocate a l0 page_table object.
* @ctx: MMU context of owning VM context.
*
* Returns:
* * Newly created page table object on success, or
* * -%ENOMEM if no memory is available,
* * Any error code returned by pvr_page_table_l0_init().
*/
static struct pvr_page_table_l0 *
pvr_page_table_l0_alloc(struct pvr_mmu_context *ctx)
{
int err;
/**
* pvr_mmu_op_context_require_sync() - Mark an MMU op context as requiring a
* sync operation for the referenced page tables up to a specified level.
* @op_ctx: Target MMU op context.
* @level: Maximum page table level for which a sync is required.
*/
static void
pvr_mmu_op_context_require_sync(struct pvr_mmu_op_context *op_ctx,
enum pvr_mmu_sync_level level)
{
if (op_ctx->sync_level_required < level)
op_ctx->sync_level_required = level;
}
/**
* pvr_mmu_op_context_sync_manual() - Trigger a sync of some or all of the
* page tables referenced by a MMU op context.
* @op_ctx: Target MMU op context.
* @level: Maximum page table level to sync.
*
* Do not call this function directly. Instead use
* pvr_mmu_op_context_sync_partial() which is checked against the current
* value of &op_ctx->sync_level_required as set by
* pvr_mmu_op_context_require_sync().
*/
static void
pvr_mmu_op_context_sync_manual(struct pvr_mmu_op_context *op_ctx,
enum pvr_mmu_sync_level level)
{
/*
* We sync the page table levels in ascending order (starting from the
* leaf node) to ensure consistency.
*/
WARN_ON(level < PVR_MMU_SYNC_LEVEL_NONE);
if (level <= PVR_MMU_SYNC_LEVEL_NONE)
return;
if (op_ctx->curr_page.l0_table)
pvr_page_table_l0_sync(op_ctx->curr_page.l0_table);
if (level < PVR_MMU_SYNC_LEVEL_1)
return;
if (op_ctx->curr_page.l1_table)
pvr_page_table_l1_sync(op_ctx->curr_page.l1_table);
/**
* pvr_mmu_op_context_sync_partial() - Trigger a sync of some or all of the
* page tables referenced by a MMU op context.
* @op_ctx: Target MMU op context.
* @level: Requested page table level to sync up to (inclusive).
*
* If @level is greater than the maximum level recorded by @op_ctx as requiring
* a sync operation, only the previously recorded maximum will be used.
*
* Additionally, if @level is greater than or equal to the maximum level
* recorded by @op_ctx as requiring a sync operation, that maximum level will be
* reset as a full sync will be performed. This is equivalent to calling
* pvr_mmu_op_context_sync().
*/
static void
pvr_mmu_op_context_sync_partial(struct pvr_mmu_op_context *op_ctx,
enum pvr_mmu_sync_level level)
{
/*
* If the requested sync level is greater than or equal to the
* currently required sync level, we do two things:
* * Don't waste time syncing levels we haven't previously marked as
* requiring a sync, and
* * Reset the required sync level since we are about to sync
* everything that was previously marked as requiring a sync.
*/
if (level >= op_ctx->sync_level_required) {
level = op_ctx->sync_level_required;
op_ctx->sync_level_required = PVR_MMU_SYNC_LEVEL_NONE;
}
pvr_mmu_op_context_sync_manual(op_ctx, level);
}
/**
* pvr_mmu_op_context_sync() - Trigger a sync of every page table referenced by
* a MMU op context.
* @op_ctx: Target MMU op context.
*
* The maximum level marked internally as requiring a sync will be reset so
* that subsequent calls to this function will be no-ops unless @op_ctx is
* otherwise updated.
*/
static void
pvr_mmu_op_context_sync(struct pvr_mmu_op_context *op_ctx)
{
pvr_mmu_op_context_sync_manual(op_ctx, op_ctx->sync_level_required);
/**
* pvr_mmu_op_context_load_tables() - Load pointers to tables in each level of
* the page table tree structure needed to reference the physical page
* referenced by a MMU op context.
* @op_ctx: Target MMU op context.
* @should_create: Specifies whether new page tables should be created when
* empty page table entries are encountered during traversal.
* @load_level_required: Maximum page table level to load.
*
* If @should_create is %true, this function may modify the stored required
* sync level of @op_ctx as new page tables are created and inserted into their
* respective parents.
*
* Since there is only one root page table, it is technically incorrect to call
* this function with a value of @load_level_required greater than or equal to
* the root level number. However, this is not explicitly disallowed here.
*
* Return:
* * 0 on success,
* * Any error returned by pvr_page_table_l1_get_or_create() if
* @load_level_required >= 1 except -%ENXIO, or
* * Any error returned by pvr_page_table_l0_get_or_create() if
* @load_level_required >= 0 except -%ENXIO.
*/
static int
pvr_mmu_op_context_load_tables(struct pvr_mmu_op_context *op_ctx,
bool should_create,
enum pvr_mmu_sync_level load_level_required)
{
const struct pvr_page_table_l1 *l1_head_before =
op_ctx->map.l1_prealloc_tables;
const struct pvr_page_table_l0 *l0_head_before =
op_ctx->map.l0_prealloc_tables;
int err;
/* Clear tables we're about to fetch in case of error states. */
if (load_level_required >= PVR_MMU_SYNC_LEVEL_1)
op_ctx->curr_page.l1_table = NULL;
if (load_level_required >= PVR_MMU_SYNC_LEVEL_0)
op_ctx->curr_page.l0_table = NULL;
/* Get or create L1 page table. */
if (load_level_required >= PVR_MMU_SYNC_LEVEL_1) {
err = pvr_page_table_l1_get_or_insert(op_ctx, should_create);
if (err) {
/*
* If @should_create is %false and no L1 page table was
* found, return early but without an error. Since
* pvr_page_table_l1_get_or_create() can only return
* -%ENXIO if @should_create is %false, there is no
* need to check it here.
*/
if (err == -ENXIO)
err = 0;
return err;
}
}
/* Get or create L0 page table. */
if (load_level_required >= PVR_MMU_SYNC_LEVEL_0) {
err = pvr_page_table_l0_get_or_insert(op_ctx, should_create);
if (err) {
/*
* If @should_create is %false and no L0 page table was
* found, return early but without an error. Since
* pvr_page_table_l0_get_or_insert() can only return
* -%ENXIO if @should_create is %false, there is no
* need to check it here.
*/
if (err == -ENXIO)
err = 0;
/*
* At this point, an L1 page table could have been
* inserted but is now empty due to the failed attempt
* at inserting an L0 page table. In this instance, we
* must remove the empty L1 page table ourselves as
* pvr_page_table_l1_remove() is never called as part
* of the error path in
* pvr_page_table_l0_get_or_insert().
*/
if (l1_head_before != op_ctx->map.l1_prealloc_tables) {
pvr_page_table_l2_remove(op_ctx);
pvr_mmu_op_context_require_sync(op_ctx, PVR_MMU_SYNC_LEVEL_2);
}
return err;
}
}
/*
* A sync is only needed if table objects were inserted. This can be
* inferred by checking if the pointer at the head of the linked list
* has changed.
*/
if (l1_head_before != op_ctx->map.l1_prealloc_tables)
pvr_mmu_op_context_require_sync(op_ctx, PVR_MMU_SYNC_LEVEL_2);
else if (l0_head_before != op_ctx->map.l0_prealloc_tables)
pvr_mmu_op_context_require_sync(op_ctx, PVR_MMU_SYNC_LEVEL_1);
return 0;
}
/**
* pvr_mmu_op_context_set_curr_page() - Reassign the current page of an MMU op
* context, syncing any page tables previously assigned to it which are no
* longer relevant.
* @op_ctx: Target MMU op context.
* @device_addr: New pointer target.
* @should_create: Specify whether new page tables should be created when
* empty page table entries are encountered during traversal.
*
* This function performs a full sync on the pointer, regardless of which
* levels are modified.
*
* Return:
* * 0 on success, or
* * Any error returned by pvr_mmu_op_context_load_tables().
*/
static int
pvr_mmu_op_context_set_curr_page(struct pvr_mmu_op_context *op_ctx,
u64 device_addr, bool should_create)
{
pvr_mmu_op_context_sync(op_ctx);
/**
* pvr_mmu_op_context_next_page() - Advance the current page of an MMU op
* context.
* @op_ctx: Target MMU op context.
* @should_create: Specify whether new page tables should be created when
* empty page table entries are encountered during traversal.
*
* If @should_create is %false, it is the caller's responsibility to verify that
* the state of the table references in @op_ctx is valid on return. If -%ENXIO
* is returned, at least one of the table references is invalid. It should be
* noted that @op_ctx as a whole will be left in a valid state if -%ENXIO is
* returned, unlike other error codes. The caller should check which references
* are invalid by comparing them to %NULL. Only &@ptr->l2_table is guaranteed
* to be valid, since it represents the root of the page table tree structure.
*
* Return:
* * 0 on success,
* * -%EPERM if the operation would wrap at the top of the page table
* hierarchy,
* * -%ENXIO if @should_create is %false and a page table of any level would
* have otherwise been created, or
* * Any error returned while attempting to create missing page tables if
* @should_create is %true.
*/
static int
pvr_mmu_op_context_next_page(struct pvr_mmu_op_context *op_ctx,
bool should_create)
{
s8 load_level_required = PVR_MMU_SYNC_LEVEL_NONE;
if (++op_ctx->curr_page.l0_idx != ROGUE_MMUCTRL_ENTRIES_PT_VALUE_X)
goto load_tables;
if (++op_ctx->curr_page.l2_idx != ROGUE_MMUCTRL_ENTRIES_PC_VALUE)
goto load_tables;
/*
* If the pattern continued, we would set &op_ctx->curr_page.l2_idx to
* zero here. However, that would wrap the top layer of the page table
* hierarchy which is not a valid operation. Instead, we warn and return
* an error.
*/
WARN(true,
"%s(%p) attempted to loop the top of the page table hierarchy",
__func__, op_ctx);
return -EPERM;
/* If indices have wrapped, we need to load new tables. */
load_tables:
/* First, flush tables which will be unloaded. */
pvr_mmu_op_context_sync_partial(op_ctx, load_level_required);
/* Then load tables from the required level down. */
return pvr_mmu_op_context_load_tables(op_ctx, should_create,
load_level_required);
}
/**
* DOC: Single page operations
*/
/**
* pvr_page_create() - Create a device-virtual memory page and insert it into
* a level 0 page table.
* @op_ctx: Target MMU op context pointing at the device-virtual address of the
* target page.
* @dma_addr: DMA address of the physical page backing the created page.
* @flags: Page options saved on the level 0 page table entry for reading by
* the device.
*
* Return:
* * 0 on success, or
* * -%EEXIST if the requested page already exists.
*/
static int
pvr_page_create(struct pvr_mmu_op_context *op_ctx, dma_addr_t dma_addr,
struct pvr_page_flags_raw flags)
{
/* Do not create a new page if one already exists. */
if (pvr_page_table_l0_entry_is_valid(op_ctx->curr_page.l0_table,
op_ctx->curr_page.l0_idx)) {
return -EEXIST;
}
/**
* pvr_page_destroy() - Destroy a device page after removing it from its
* parent level 0 page table.
* @op_ctx: Target MMU op context.
*/
static void
pvr_page_destroy(struct pvr_mmu_op_context *op_ctx)
{
/* Do nothing if the page does not exist. */
if (!pvr_page_table_l0_entry_is_valid(op_ctx->curr_page.l0_table,
op_ctx->curr_page.l0_idx)) {
return;
}
/* Clear the parent L0 page table entry. */
pvr_page_table_l0_remove(op_ctx);
/**
* pvr_mmu_op_context_destroy() - Destroy an MMU op context.
* @op_ctx: Target MMU op context.
*/
void pvr_mmu_op_context_destroy(struct pvr_mmu_op_context *op_ctx)
{
const bool flush_caches =
op_ctx->sync_level_required != PVR_MMU_SYNC_LEVEL_NONE;
pvr_mmu_op_context_sync(op_ctx);
/* Unmaps should be flushed immediately. Map flushes can be deferred. */
if (flush_caches && !op_ctx->map.sgt)
pvr_mmu_flush_exec(op_ctx->mmu_ctx->pvr_dev, true);
while (op_ctx->map.l0_prealloc_tables) {
struct pvr_page_table_l0 *tmp = op_ctx->map.l0_prealloc_tables;
/**
* pvr_mmu_op_context_create() - Create an MMU op context.
* @ctx: MMU context associated with owning VM context.
* @sgt: Scatter gather table containing pages pinned for use by this context.
* @sgt_offset: Start offset of the requested device-virtual memory mapping.
* @size: Size in bytes of the requested device-virtual memory mapping. For an
* unmapping, this should be zero so that no page tables are allocated.
*
* Returns:
* * Newly created MMU op context object on success, or
* * -%ENOMEM if no memory is available,
* * Any error code returned by pvr_page_table_l2_init().
*/
struct pvr_mmu_op_context *
pvr_mmu_op_context_create(struct pvr_mmu_context *ctx, struct sg_table *sgt,
u64 sgt_offset, u64 size)
{
int err;
if (size) {
/*
* The number of page table objects we need to prealloc is
* indicated by the mapping size, start offset and the sizes
* of the areas mapped per PT or PD. The range calculation is
* identical to that for the index into a table for a device
* address, so we reuse those functions here.
*/
const u32 l1_start_idx = pvr_page_table_l2_idx(sgt_offset);
const u32 l1_end_idx = pvr_page_table_l2_idx(sgt_offset + size);
const u32 l1_count = l1_end_idx - l1_start_idx + 1;
const u32 l0_start_idx = pvr_page_table_l1_idx(sgt_offset);
const u32 l0_end_idx = pvr_page_table_l1_idx(sgt_offset + size);
const u32 l0_count = l0_end_idx - l0_start_idx + 1;
/*
* Alloc and push page table entries until we have enough of
* each type, ending with linked lists of l0 and l1 entries in
* reverse order.
*/
for (int i = 0; i < l1_count; i++) {
struct pvr_page_table_l1 *l1_tmp =
pvr_page_table_l1_alloc(ctx);
err = PTR_ERR_OR_ZERO(l1_tmp);
if (err)
goto err_cleanup;
/**
* pvr_mmu_op_context_unmap_curr_page() - Unmap pages from a memory context
* starting from the current page of an MMU op context.
* @op_ctx: Target MMU op context pointing at the first page to unmap.
* @nr_pages: Number of pages to unmap.
*
* Return:
* * 0 on success, or
* * Any error encountered while advancing @op_ctx.curr_page with
* pvr_mmu_op_context_next_page() (except -%ENXIO).
*/
static int
pvr_mmu_op_context_unmap_curr_page(struct pvr_mmu_op_context *op_ctx,
u64 nr_pages)
{
int err;
if (nr_pages == 0)
return 0;
/*
* Destroy first page outside loop, as it doesn't require a page
* advance beforehand. If the L0 page table reference in
* @op_ctx.curr_page is %NULL, there cannot be a mapped page at
* @op_ctx.curr_page (so skip ahead).
*/
if (op_ctx->curr_page.l0_table)
pvr_page_destroy(op_ctx);
for (u64 page = 1; page < nr_pages; ++page) {
err = pvr_mmu_op_context_next_page(op_ctx, false);
/*
* If the page table tree structure at @op_ctx.curr_page is
* incomplete, skip ahead. We don't care about unmapping pages
* that cannot exist.
*
* FIXME: This could be made more efficient by jumping ahead
* using pvr_mmu_op_context_set_curr_page().
*/
if (err == -ENXIO)
continue;
else if (err)
return err;
pvr_page_destroy(op_ctx);
}
return 0;
}
/**
* pvr_mmu_unmap() - Unmap pages from a memory context.
* @op_ctx: Target MMU op context.
* @device_addr: First device-virtual address to unmap.
* @size: Size in bytes to unmap.
*
* The total amount of device-virtual memory unmapped is
* @nr_pages * %PVR_DEVICE_PAGE_SIZE.
*
* Returns:
* * 0 on success, or
* * Any error code returned by pvr_page_table_ptr_init(), or
* * Any error code returned by pvr_page_table_ptr_unmap().
*/
int pvr_mmu_unmap(struct pvr_mmu_op_context *op_ctx, u64 device_addr, u64 size)
{
int err = pvr_mmu_op_context_set_curr_page(op_ctx, device_addr, false);
/**
* pvr_mmu_map_sgl() - Map part of a scatter-gather table entry to
* device-virtual memory.
* @op_ctx: Target MMU op context pointing to the first page that should be
* mapped.
* @sgl: Target scatter-gather table entry.
* @offset: Offset into @sgl to map from. Must result in a starting address
* from @sgl which is CPU page-aligned.
* @size: Size of the memory to be mapped in bytes. Must be a non-zero multiple
* of the device page size.
* @page_flags: Page options to be applied to every device-virtual memory page
* in the created mapping.
*
* Return:
* * 0 on success,
* * -%EINVAL if the range specified by @offset and @size is not completely
* within @sgl, or
* * Any error encountered while creating a page with pvr_page_create(), or
* * Any error encountered while advancing @op_ctx.curr_page with
* pvr_mmu_op_context_next_page().
*/
static int
pvr_mmu_map_sgl(struct pvr_mmu_op_context *op_ctx, struct scatterlist *sgl,
u64 offset, u64 size, struct pvr_page_flags_raw page_flags)
{
const unsigned int pages = size >> PVR_DEVICE_PAGE_SHIFT;
dma_addr_t dma_addr = sg_dma_address(sgl) + offset;
const unsigned int dma_len = sg_dma_len(sgl);
struct pvr_page_table_ptr ptr_copy;
unsigned int page;
int err;
/*
* Before progressing, save a copy of the start pointer so we can use
* it again if we enter an error state and have to destroy pages.
*/
memcpy(&ptr_copy, &op_ctx->curr_page, sizeof(ptr_copy));
/*
* Create first page outside loop, as it doesn't require a page advance
* beforehand.
*/
err = pvr_page_create(op_ctx, dma_addr, page_flags);
if (err)
return err;
for (page = 1; page < pages; ++page) {
err = pvr_mmu_op_context_next_page(op_ctx, true);
if (err)
goto err_destroy_pages;
dma_addr += PVR_DEVICE_PAGE_SIZE;
err = pvr_page_create(op_ctx, dma_addr, page_flags);
if (err)
goto err_destroy_pages;
}
/**
* pvr_mmu_map() - Map an object's virtual memory to physical memory.
* @op_ctx: Target MMU op context.
* @size: Size of memory to be mapped in bytes. Must be a non-zero multiple
* of the device page size.
* @flags: Flags from pvr_gem_object associated with the mapping.
* @device_addr: Virtual device address to map to. Must be device page-aligned.
*
* Returns:
* * 0 on success, or
* * Any error code returned by pvr_page_table_ptr_init(), or
* * Any error code returned by pvr_mmu_map_sgl(), or
* * Any error code returned by pvr_page_table_ptr_next_page().
*/
int pvr_mmu_map(struct pvr_mmu_op_context *op_ctx, u64 size, u64 flags,
u64 device_addr)
{
struct pvr_page_table_ptr ptr_copy;
struct pvr_page_flags_raw flags_raw;
struct scatterlist *sgl;
u64 mapped_size = 0;
unsigned int count;
int err;
if (!size)
return 0;
if ((op_ctx->map.sgt_offset | size) & ~PVR_DEVICE_PAGE_MASK)
return -EINVAL;
err = pvr_mmu_op_context_set_curr_page(op_ctx, device_addr, true);
if (err)
return -EINVAL;
err = pvr_mmu_map_sgl(op_ctx, sgl, sgl_offset, map_sgl_len,
flags_raw);
if (err)
break;
/*
* Flag the L0 page table as requiring a flush when the MMU op
* context is destroyed.
*/
pvr_mmu_op_context_require_sync(op_ctx, PVR_MMU_SYNC_LEVEL_0);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.