Commit 00b60c8d authored by Todd Poynor's avatar Todd Poynor Committed by Greg Kroah-Hartman

staging: gasket: pg tbl: remove static function forward declarations

Remove forward declarations of static functions, move code to avoid
forward references, for kernel style.
Signed-off-by: default avatarTodd Poynor <toddpoynor@google.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent d821f8eb
...@@ -214,71 +214,6 @@ struct gasket_page_table { ...@@ -214,71 +214,6 @@ struct gasket_page_table {
struct gasket_coherent_page_entry *coherent_pages; struct gasket_coherent_page_entry *coherent_pages;
}; };
/* Mapping declarations */
static int gasket_map_simple_pages(
struct gasket_page_table *pg_tbl, ulong host_addr,
ulong dev_addr, uint num_pages);
static int gasket_map_extended_pages(
struct gasket_page_table *pg_tbl, ulong host_addr,
ulong dev_addr, uint num_pages);
static int gasket_perform_mapping(
struct gasket_page_table *pg_tbl,
struct gasket_page_table_entry *pte_base, u64 __iomem *att_base,
ulong host_addr, uint num_pages, int is_simple_mapping);
static int gasket_alloc_simple_entries(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages);
static int gasket_alloc_extended_entries(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_entries);
static int gasket_alloc_extended_subtable(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *pte,
u64 __iomem *att_reg);
/* Unmapping declarations */
static void gasket_page_table_unmap_nolock(
struct gasket_page_table *pg_tbl, ulong start_addr, uint num_pages);
static void gasket_page_table_unmap_all_nolock(
struct gasket_page_table *pg_tbl);
static void gasket_unmap_simple_pages(
struct gasket_page_table *pg_tbl, ulong start_addr, uint num_pages);
static void gasket_unmap_extended_pages(
struct gasket_page_table *pg_tbl, ulong start_addr, uint num_pages);
static void gasket_perform_unmapping(
struct gasket_page_table *pg_tbl,
struct gasket_page_table_entry *pte_base, u64 __iomem *att_base,
uint num_pages, int is_simple_mapping);
static void gasket_free_extended_subtable(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *pte,
u64 __iomem *att_reg);
static bool gasket_release_page(struct page *page);
/* Other/utility declarations */
static inline bool gasket_addr_is_simple(
struct gasket_page_table *pg_tbl, ulong addr);
static bool gasket_is_simple_dev_addr_bad(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages);
static bool gasket_is_extended_dev_addr_bad(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages);
static bool gasket_is_pte_range_free(
struct gasket_page_table_entry *pte, uint num_entries);
static void gasket_page_table_garbage_collect_nolock(
struct gasket_page_table *pg_tbl);
/* Address format declarations */
static ulong gasket_components_to_dev_address(
struct gasket_page_table *pg_tbl, int is_simple, uint page_index,
uint offset);
static int gasket_simple_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr);
static ulong gasket_extended_lvl0_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr);
static ulong gasket_extended_lvl1_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr);
static int is_coherent(struct gasket_page_table *pg_tbl, ulong host_addr);
/* Public/exported functions */
/* See gasket_page_table.h for description. */ /* See gasket_page_table.h for description. */
int gasket_page_table_init( int gasket_page_table_init(
struct gasket_page_table **ppg_tbl, struct gasket_page_table **ppg_tbl,
...@@ -353,6 +288,85 @@ int gasket_page_table_init( ...@@ -353,6 +288,85 @@ int gasket_page_table_init(
return 0; return 0;
} }
/*
* Check if a range of PTEs is free.
* The page table mutex must be held by the caller.
*/
static bool gasket_is_pte_range_free(
struct gasket_page_table_entry *ptes, uint num_entries)
{
int i;
for (i = 0; i < num_entries; i++) {
if (ptes[i].status != PTE_FREE)
return false;
}
return true;
}
/*
* Free a second level page [sub]table.
* The page table mutex must be held before this call.
*/
static void gasket_free_extended_subtable(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *pte,
u64 __iomem *slot)
{
/* Release the page table from the driver */
pte->status = PTE_FREE;
/* Release the page table from the device */
writeq(0, slot);
/* Force sync around the address release. */
mb();
if (pte->dma_addr)
dma_unmap_page(pg_tbl->device, pte->dma_addr, PAGE_SIZE,
DMA_BIDIRECTIONAL);
vfree(pte->sublevel);
if (pte->page)
free_page((ulong)page_address(pte->page));
memset(pte, 0, sizeof(struct gasket_page_table_entry));
}
/*
* Actually perform collection.
* The page table mutex must be held by the caller.
*/
static void gasket_page_table_garbage_collect_nolock(
struct gasket_page_table *pg_tbl)
{
struct gasket_page_table_entry *pte;
u64 __iomem *slot;
/* XXX FIX ME XXX -- more efficient to keep a usage count */
/* rather than scanning the second level page tables */
for (pte = pg_tbl->entries + pg_tbl->num_simple_entries,
slot = pg_tbl->base_slot + pg_tbl->num_simple_entries;
pte < pg_tbl->entries + pg_tbl->config.total_entries;
pte++, slot++) {
if (pte->status == PTE_INUSE) {
if (gasket_is_pte_range_free(
pte->sublevel, GASKET_PAGES_PER_SUBTABLE))
gasket_free_extended_subtable(
pg_tbl, pte, slot);
}
}
}
/* See gasket_page_table.h for description. */
void gasket_page_table_garbage_collect(struct gasket_page_table *pg_tbl)
{
mutex_lock(&pg_tbl->mutex);
gasket_page_table_garbage_collect_nolock(pg_tbl);
mutex_unlock(&pg_tbl->mutex);
}
/* See gasket_page_table.h for description. */ /* See gasket_page_table.h for description. */
void gasket_page_table_cleanup(struct gasket_page_table *pg_tbl) void gasket_page_table_cleanup(struct gasket_page_table *pg_tbl)
{ {
...@@ -404,482 +418,449 @@ int gasket_page_table_partition( ...@@ -404,482 +418,449 @@ int gasket_page_table_partition(
EXPORT_SYMBOL(gasket_page_table_partition); EXPORT_SYMBOL(gasket_page_table_partition);
/* /*
* See gasket_page_table.h for general description. * Return whether a host buffer was mapped as coherent memory.
*
* gasket_page_table_map calls either gasket_map_simple_pages() or
* gasket_map_extended_pages() to actually perform the mapping.
* *
* The page table mutex is held for the entire operation. * A Gasket page_table currently support one contiguous dma range, mapped to one
* contiguous virtual memory range. Check if the host_addr is within that range.
*/ */
int gasket_page_table_map( static int is_coherent(struct gasket_page_table *pg_tbl, ulong host_addr)
struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
uint num_pages)
{ {
int ret; u64 min, max;
if (!num_pages) /* whether the host address is within user virt range */
if (!pg_tbl->coherent_pages)
return 0; return 0;
mutex_lock(&pg_tbl->mutex); min = (u64)pg_tbl->coherent_pages[0].user_virt;
max = min + PAGE_SIZE * pg_tbl->num_coherent_pages;
if (gasket_addr_is_simple(pg_tbl, dev_addr)) { return min <= host_addr && host_addr < max;
ret = gasket_map_simple_pages( }
pg_tbl, host_addr, dev_addr, num_pages);
/*
* Get and map last level page table buffers.
*
* slots is the location(s) to write device-mapped page address. If this is a
* simple mapping, these will be address translation registers. If this is
* an extended mapping, these will be within a second-level page table
* allocated by the host and so must have their __iomem attribute casted away.
*/
static int gasket_perform_mapping(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *ptes,
u64 __iomem *slots, ulong host_addr, uint num_pages,
int is_simple_mapping)
{
int ret;
ulong offset;
struct page *page;
dma_addr_t dma_addr;
ulong page_addr;
int i;
for (i = 0; i < num_pages; i++) {
page_addr = host_addr + i * PAGE_SIZE;
offset = page_addr & (PAGE_SIZE - 1);
dev_dbg(pg_tbl->device, "%s i %d\n", __func__, i);
if (is_coherent(pg_tbl, host_addr)) {
u64 off =
(u64)host_addr -
(u64)pg_tbl->coherent_pages[0].user_virt;
ptes[i].page = NULL;
ptes[i].offset = offset;
ptes[i].dma_addr = pg_tbl->coherent_pages[0].paddr +
off + i * PAGE_SIZE;
} else { } else {
ret = gasket_map_extended_pages( ret = get_user_pages_fast(
pg_tbl, host_addr, dev_addr, num_pages); page_addr - offset, 1, 1, &page);
if (ret <= 0) {
dev_err(pg_tbl->device,
"get user pages failed for addr=0x%lx, "
"offset=0x%lx [ret=%d]\n",
page_addr, offset, ret);
return ret ? ret : -ENOMEM;
} }
++pg_tbl->num_active_pages;
mutex_unlock(&pg_tbl->mutex); ptes[i].page = page;
ptes[i].offset = offset;
/* Map the page into DMA space. */
ptes[i].dma_addr =
dma_map_page(pg_tbl->device, page, 0, PAGE_SIZE,
DMA_BIDIRECTIONAL);
dev_dbg(pg_tbl->device, dev_dbg(pg_tbl->device,
"%s done: ha %llx daddr %llx num %d, ret %d\n", "%s i %d pte %p pfn %p -> mapped %llx\n",
__func__, (unsigned long long)host_addr, __func__, i, &ptes[i],
(unsigned long long)dev_addr, num_pages, ret); (void *)page_to_pfn(page),
return ret; (unsigned long long)ptes[i].dma_addr);
if (ptes[i].dma_addr == -1) {
dev_dbg(pg_tbl->device,
"%s i %d -> fail to map page %llx "
"[pfn %p ohys %p]\n",
__func__, i,
(unsigned long long)ptes[i].dma_addr,
(void *)page_to_pfn(page),
(void *)page_to_phys(page));
return -1;
}
/* Wait until the page is mapped. */
mb();
}
/* Make the DMA-space address available to the device. */
dma_addr = (ptes[i].dma_addr + offset) | GASKET_VALID_SLOT_FLAG;
if (is_simple_mapping) {
writeq(dma_addr, &slots[i]);
} else {
((u64 __force *)slots)[i] = dma_addr;
/* Extended page table vectors are in DRAM,
* and so need to be synced each time they are updated.
*/
dma_map_single(pg_tbl->device,
(void *)&((u64 __force *)slots)[i],
sizeof(u64), DMA_TO_DEVICE);
}
ptes[i].status = PTE_INUSE;
}
return 0;
} }
EXPORT_SYMBOL(gasket_page_table_map);
/* /*
* See gasket_page_table.h for general description. * Return the index of the page for the address in the simple table.
* * Does not perform validity checking.
* gasket_page_table_unmap takes the page table lock and calls either
* gasket_unmap_simple_pages() or gasket_unmap_extended_pages() to
* actually unmap the pages from device space.
*
* The page table mutex is held for the entire operation.
*/ */
void gasket_page_table_unmap( static int gasket_simple_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages) struct gasket_page_table *pg_tbl, ulong dev_addr)
{ {
if (!num_pages) return (dev_addr >> GASKET_SIMPLE_PAGE_SHIFT) &
return; (pg_tbl->config.total_entries - 1);
mutex_lock(&pg_tbl->mutex);
gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages);
mutex_unlock(&pg_tbl->mutex);
} }
EXPORT_SYMBOL(gasket_page_table_unmap);
static void gasket_page_table_unmap_all_nolock(struct gasket_page_table *pg_tbl) /*
* Return the level 0 page index for the given address.
* Does not perform validity checking.
*/
static ulong gasket_extended_lvl0_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr)
{ {
gasket_unmap_simple_pages( return (dev_addr >> GASKET_EXTENDED_LVL0_SHIFT) &
pg_tbl, gasket_components_to_dev_address(pg_tbl, 1, 0, 0), ((1 << GASKET_EXTENDED_LVL0_WIDTH) - 1);
pg_tbl->num_simple_entries);
gasket_unmap_extended_pages(
pg_tbl, gasket_components_to_dev_address(pg_tbl, 0, 0, 0),
pg_tbl->num_extended_entries * GASKET_PAGES_PER_SUBTABLE);
} }
/* See gasket_page_table.h for description. */ /*
void gasket_page_table_unmap_all(struct gasket_page_table *pg_tbl) * Return the level 1 page index for the given address.
* Does not perform validity checking.
*/
static ulong gasket_extended_lvl1_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr)
{ {
mutex_lock(&pg_tbl->mutex); return (dev_addr >> GASKET_EXTENDED_LVL1_SHIFT) &
gasket_page_table_unmap_all_nolock(pg_tbl); (GASKET_PAGES_PER_SUBTABLE - 1);
mutex_unlock(&pg_tbl->mutex);
} }
EXPORT_SYMBOL(gasket_page_table_unmap_all);
/* See gasket_page_table.h for description. */ /*
void gasket_page_table_reset(struct gasket_page_table *pg_tbl) * Allocate page table entries in a simple table.
* The page table mutex must be held by the caller.
*/
static int gasket_alloc_simple_entries(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
{ {
mutex_lock(&pg_tbl->mutex); if (!gasket_is_pte_range_free(
gasket_page_table_unmap_all_nolock(pg_tbl); pg_tbl->entries + gasket_simple_page_idx(pg_tbl, dev_addr),
writeq(pg_tbl->config.total_entries, pg_tbl->extended_offset_reg); num_pages))
mutex_unlock(&pg_tbl->mutex); return -EBUSY;
}
/* See gasket_page_table.h for description. */ return 0;
void gasket_page_table_garbage_collect(struct gasket_page_table *pg_tbl)
{
mutex_lock(&pg_tbl->mutex);
gasket_page_table_garbage_collect_nolock(pg_tbl);
mutex_unlock(&pg_tbl->mutex);
} }
/* See gasket_page_table.h for description. */ /* Safely return a page to the OS. */
int gasket_page_table_lookup_page( static bool gasket_release_page(struct page *page)
struct gasket_page_table *pg_tbl, ulong dev_addr, struct page **ppage,
ulong *poffset)
{ {
uint page_num; if (!page)
struct gasket_page_table_entry *pte; return false;
mutex_lock(&pg_tbl->mutex);
if (gasket_addr_is_simple(pg_tbl, dev_addr)) {
page_num = gasket_simple_page_idx(pg_tbl, dev_addr);
if (page_num >= pg_tbl->num_simple_entries)
goto fail;
pte = pg_tbl->entries + page_num;
if (pte->status != PTE_INUSE)
goto fail;
} else {
/* Find the level 0 entry, */
page_num = gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
if (page_num >= pg_tbl->num_extended_entries)
goto fail;
pte = pg_tbl->entries + pg_tbl->num_simple_entries + page_num;
if (pte->status != PTE_INUSE)
goto fail;
/* and its contained level 1 entry. */
page_num = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
pte = pte->sublevel + page_num;
if (pte->status != PTE_INUSE)
goto fail;
}
*ppage = pte->page;
*poffset = pte->offset;
mutex_unlock(&pg_tbl->mutex);
return 0;
fail: if (!PageReserved(page))
*ppage = NULL; SetPageDirty(page);
*poffset = 0; put_page(page);
mutex_unlock(&pg_tbl->mutex);
return -1;
}
/* See gasket_page_table.h for description. */
bool gasket_page_table_are_addrs_bad(
struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
ulong bytes)
{
if (host_addr & (PAGE_SIZE - 1)) {
dev_err(pg_tbl->device,
"host mapping address 0x%lx must be page aligned\n",
host_addr);
return true; return true;
}
return gasket_page_table_is_dev_addr_bad(pg_tbl, dev_addr, bytes);
} }
EXPORT_SYMBOL(gasket_page_table_are_addrs_bad);
/* See gasket_page_table.h for description. */ /*
bool gasket_page_table_is_dev_addr_bad( * Unmap and release mapped pages.
struct gasket_page_table *pg_tbl, ulong dev_addr, ulong bytes) * The page table mutex must be held by the caller.
*/
static void gasket_perform_unmapping(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *ptes,
u64 __iomem *slots, uint num_pages, int is_simple_mapping)
{ {
uint num_pages = bytes / PAGE_SIZE; int i;
/*
* For each page table entry and corresponding entry in the device's
* address translation table:
*/
for (i = 0; i < num_pages; i++) {
/* release the address from the device, */
if (is_simple_mapping || ptes[i].status == PTE_INUSE)
writeq(0, &slots[i]);
else
((u64 __force *)slots)[i] = 0;
/* Force sync around the address release. */
mb();
if (bytes & (PAGE_SIZE - 1)) { /* release the address from the driver, */
dev_err(pg_tbl->device, if (ptes[i].status == PTE_INUSE) {
"mapping size 0x%lX must be page aligned\n", bytes); if (ptes[i].dma_addr) {
return true; dma_unmap_page(pg_tbl->device, ptes[i].dma_addr,
PAGE_SIZE, DMA_FROM_DEVICE);
} }
if (gasket_release_page(ptes[i].page))
if (num_pages == 0) { --pg_tbl->num_active_pages;
dev_err(pg_tbl->device,
"requested mapping is less than one page: %lu / %lu\n",
bytes, PAGE_SIZE);
return true;
} }
ptes[i].status = PTE_FREE;
if (gasket_addr_is_simple(pg_tbl, dev_addr)) /* and clear the PTE. */
return gasket_is_simple_dev_addr_bad( memset(&ptes[i], 0, sizeof(struct gasket_page_table_entry));
pg_tbl, dev_addr, num_pages); }
return gasket_is_extended_dev_addr_bad(pg_tbl, dev_addr, num_pages);
} }
EXPORT_SYMBOL(gasket_page_table_is_dev_addr_bad);
/* See gasket_page_table.h for description. */ /*
uint gasket_page_table_max_size(struct gasket_page_table *page_table) * Unmap and release pages mapped to simple addresses.
* The page table mutex must be held by the caller.
*/
static void gasket_unmap_simple_pages(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
{ {
if (!page_table) uint slot = gasket_simple_page_idx(pg_tbl, dev_addr);
return 0;
return page_table->config.total_entries;
}
EXPORT_SYMBOL(gasket_page_table_max_size);
/* See gasket_page_table.h for description. */ gasket_perform_unmapping(pg_tbl, pg_tbl->entries + slot,
uint gasket_page_table_num_entries(struct gasket_page_table *pg_tbl) pg_tbl->base_slot + slot, num_pages, 1);
{
if (!pg_tbl)
return 0;
return pg_tbl->num_simple_entries + pg_tbl->num_extended_entries;
} }
EXPORT_SYMBOL(gasket_page_table_num_entries);
/* See gasket_page_table.h for description. */ /*
uint gasket_page_table_num_simple_entries(struct gasket_page_table *pg_tbl) * Unmap and release buffers to extended addresses.
* The page table mutex must be held by the caller.
*/
static void gasket_unmap_extended_pages(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
{ {
if (!pg_tbl) uint slot_idx, remain, len;
return 0; struct gasket_page_table_entry *pte;
return pg_tbl->num_simple_entries; u64 __iomem *slot_base;
}
EXPORT_SYMBOL(gasket_page_table_num_simple_entries);
/* See gasket_page_table.h for description. */ remain = num_pages;
uint gasket_page_table_num_active_pages(struct gasket_page_table *pg_tbl) slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
{ pte = pg_tbl->entries + pg_tbl->num_simple_entries +
if (!pg_tbl) gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
return 0;
return pg_tbl->num_active_pages;
}
EXPORT_SYMBOL(gasket_page_table_num_active_pages);
/* See gasket_page_table.h */ while (remain > 0) {
int gasket_page_table_system_status(struct gasket_page_table *page_table) /* TODO: Add check to ensure pte remains valid? */
{ len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx);
if (!page_table)
return GASKET_STATUS_LAMED;
if (gasket_page_table_num_entries(page_table) == 0) { if (pte->status == PTE_INUSE) {
dev_dbg(page_table->device, "Page table size is 0\n"); slot_base = (u64 __iomem *)(page_address(pte->page) +
return GASKET_STATUS_LAMED; pte->offset);
gasket_perform_unmapping(
pg_tbl, pte->sublevel + slot_idx,
slot_base + slot_idx, len, 0);
} }
return GASKET_STATUS_ALIVE; remain -= len;
slot_idx = 0;
pte++;
}
}
/* Evaluates to nonzero if the specified virtual address is simple. */
static inline bool gasket_addr_is_simple(
struct gasket_page_table *pg_tbl, ulong addr)
{
return !((addr) & (pg_tbl)->extended_flag);
} }
/* /*
* Allocate and map pages to simple addresses. * Convert (simple, page, offset) into a device address.
* If there is an error, no pages are mapped. * Examples:
* Simple page 0, offset 32:
* Input (0, 0, 32), Output 0x20
* Simple page 1000, offset 511:
* Input (0, 1000, 512), Output 0x3E81FF
* Extended page 0, offset 32:
* Input (0, 0, 32), Output 0x8000000020
* Extended page 1000, offset 511:
* Input (1, 1000, 512), Output 0x8003E81FF
*/ */
static int gasket_map_simple_pages( static ulong gasket_components_to_dev_address(
struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr, struct gasket_page_table *pg_tbl, int is_simple, uint page_index,
uint num_pages) uint offset)
{ {
int ret; ulong lvl0_index, lvl1_index;
uint slot_idx = gasket_simple_page_idx(pg_tbl, dev_addr);
ret = gasket_alloc_simple_entries(pg_tbl, dev_addr, num_pages); if (is_simple) {
if (ret) { /* Return simple addresses directly. */
dev_err(pg_tbl->device, lvl0_index = page_index & (pg_tbl->config.total_entries - 1);
"page table slots %u (@ 0x%lx) to %u are not available\n", return (lvl0_index << GASKET_SIMPLE_PAGE_SHIFT) | offset;
slot_idx, dev_addr, slot_idx + num_pages - 1);
return ret;
} }
ret = gasket_perform_mapping( /*
pg_tbl, pg_tbl->entries + slot_idx, * This could be compressed into fewer statements, but
pg_tbl->base_slot + slot_idx, host_addr, num_pages, 1); * A) the compiler should optimize it
* B) this is not slow
if (ret) { * C) this is an uncommon operation
gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages); * D) this is actually readable this way.
dev_err(pg_tbl->device, "gasket_perform_mapping %d\n", ret); */
} lvl0_index = page_index / GASKET_PAGES_PER_SUBTABLE;
return ret; lvl1_index = page_index & (GASKET_PAGES_PER_SUBTABLE - 1);
return (pg_tbl)->extended_flag |
(lvl0_index << GASKET_EXTENDED_LVL0_SHIFT) |
(lvl1_index << GASKET_EXTENDED_LVL1_SHIFT) | offset;
} }
/* /*
* gasket_map_extended_pages - Get and map buffers to extended addresses. * Validity checking for simple addresses.
* If there is an error, no pages are mapped. *
* Verify that address translation commutes (from address to/from page + offset)
* and that the requested page range starts and ends within the set of
* currently-partitioned simple pages.
*/ */
static int gasket_map_extended_pages( static bool gasket_is_simple_dev_addr_bad(
struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr, struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
uint num_pages) {
{ ulong page_offset = dev_addr & (PAGE_SIZE - 1);
int ret; ulong page_index =
ulong dev_addr_end; (dev_addr / PAGE_SIZE) & (pg_tbl->config.total_entries - 1);
uint slot_idx, remain, len;
struct gasket_page_table_entry *pte;
u64 __iomem *slot_base;
ret = gasket_alloc_extended_entries(pg_tbl, dev_addr, num_pages);
if (ret) {
dev_addr_end = dev_addr + (num_pages / PAGE_SIZE) - 1;
dev_err(pg_tbl->device,
"page table slots (%lu,%lu) (@ 0x%lx) to (%lu,%lu) are "
"not available\n",
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr),
dev_addr,
gasket_extended_lvl1_page_idx(pg_tbl, dev_addr),
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr_end),
gasket_extended_lvl1_page_idx(pg_tbl, dev_addr_end));
return ret;
}
remain = num_pages;
slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
pte = pg_tbl->entries + pg_tbl->num_simple_entries +
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
while (remain > 0) { if (gasket_components_to_dev_address(
len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx); pg_tbl, 1, page_index, page_offset) != dev_addr) {
dev_err(pg_tbl->device, "address is invalid, 0x%lX\n",
dev_addr);
return true;
}
slot_base = if (page_index >= pg_tbl->num_simple_entries) {
(u64 __iomem *)(page_address(pte->page) + pte->offset); dev_err(pg_tbl->device,
ret = gasket_perform_mapping( "starting slot at %lu is too large, max is < %u\n",
pg_tbl, pte->sublevel + slot_idx, slot_base + slot_idx, page_index, pg_tbl->num_simple_entries);
host_addr, len, 0); return true;
if (ret) {
gasket_page_table_unmap_nolock(
pg_tbl, dev_addr, num_pages);
return ret;
} }
remain -= len; if (page_index + num_pages > pg_tbl->num_simple_entries) {
slot_idx = 0; dev_err(pg_tbl->device,
pte++; "ending slot at %lu is too large, max is <= %u\n",
host_addr += len * PAGE_SIZE; page_index + num_pages, pg_tbl->num_simple_entries);
return true;
} }
return 0; return false;
} }
/* /*
* Get and map last level page table buffers. * Validity checking for extended addresses.
* *
* slots is the location(s) to write device-mapped page address. If this is a * Verify that address translation commutes (from address to/from page +
* simple mapping, these will be address translation registers. If this is * offset) and that the requested page range starts and ends within the set of
* an extended mapping, these will be within a second-level page table * currently-partitioned extended pages.
* allocated by the host and so must have their __iomem attribute casted away.
*/ */
static int gasket_perform_mapping( static bool gasket_is_extended_dev_addr_bad(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *ptes, struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
u64 __iomem *slots, ulong host_addr, uint num_pages,
int is_simple_mapping)
{ {
int ret; /* Starting byte index of dev_addr into the first mapped page */
ulong offset; ulong page_offset = dev_addr & (PAGE_SIZE - 1);
struct page *page; ulong page_global_idx, page_lvl0_idx;
dma_addr_t dma_addr; ulong num_lvl0_pages;
ulong page_addr; ulong addr;
int i;
for (i = 0; i < num_pages; i++) {
page_addr = host_addr + i * PAGE_SIZE;
offset = page_addr & (PAGE_SIZE - 1);
dev_dbg(pg_tbl->device, "%s i %d\n", __func__, i);
if (is_coherent(pg_tbl, host_addr)) {
u64 off =
(u64)host_addr -
(u64)pg_tbl->coherent_pages[0].user_virt;
ptes[i].page = NULL;
ptes[i].offset = offset;
ptes[i].dma_addr = pg_tbl->coherent_pages[0].paddr +
off + i * PAGE_SIZE;
} else {
ret = get_user_pages_fast(
page_addr - offset, 1, 1, &page);
if (ret <= 0) { /* check if the device address is out of bound */
dev_err(pg_tbl->device, addr = dev_addr & ~((pg_tbl)->extended_flag);
"get user pages failed for addr=0x%lx, " if (addr >> (GASKET_EXTENDED_LVL0_WIDTH + GASKET_EXTENDED_LVL0_SHIFT)) {
"offset=0x%lx [ret=%d]\n", dev_err(pg_tbl->device, "device address out of bounds: 0x%lx\n",
page_addr, offset, ret); dev_addr);
return ret ? ret : -ENOMEM; return true;
} }
++pg_tbl->num_active_pages;
ptes[i].page = page; /* Find the starting sub-page index in the space of all sub-pages. */
ptes[i].offset = offset; page_global_idx = (dev_addr / PAGE_SIZE) &
(pg_tbl->config.total_entries * GASKET_PAGES_PER_SUBTABLE - 1);
/* Map the page into DMA space. */ /* Find the starting level 0 index. */
ptes[i].dma_addr = page_lvl0_idx = gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
dma_map_page(pg_tbl->device, page, 0, PAGE_SIZE,
DMA_BIDIRECTIONAL);
dev_dbg(pg_tbl->device,
"%s i %d pte %p pfn %p -> mapped %llx\n",
__func__, i, &ptes[i],
(void *)page_to_pfn(page),
(unsigned long long)ptes[i].dma_addr);
if (ptes[i].dma_addr == -1) { /* Get the count of affected level 0 pages. */
dev_dbg(pg_tbl->device, num_lvl0_pages = (num_pages + GASKET_PAGES_PER_SUBTABLE - 1) /
"%s i %d -> fail to map page %llx " GASKET_PAGES_PER_SUBTABLE;
"[pfn %p ohys %p]\n",
__func__, i,
(unsigned long long)ptes[i].dma_addr,
(void *)page_to_pfn(page),
(void *)page_to_phys(page));
return -1;
}
/* Wait until the page is mapped. */
mb();
}
/* Make the DMA-space address available to the device. */ if (gasket_components_to_dev_address(
dma_addr = (ptes[i].dma_addr + offset) | GASKET_VALID_SLOT_FLAG; pg_tbl, 0, page_global_idx, page_offset) != dev_addr) {
dev_err(pg_tbl->device, "address is invalid: 0x%lx\n",
dev_addr);
return true;
}
if (is_simple_mapping) { if (page_lvl0_idx >= pg_tbl->num_extended_entries) {
writeq(dma_addr, &slots[i]); dev_err(pg_tbl->device,
} else { "starting level 0 slot at %lu is too large, max is < "
((u64 __force *)slots)[i] = dma_addr; "%u\n", page_lvl0_idx, pg_tbl->num_extended_entries);
/* Extended page table vectors are in DRAM, return true;
* and so need to be synced each time they are updated.
*/
dma_map_single(pg_tbl->device,
(void *)&((u64 __force *)slots)[i],
sizeof(u64), DMA_TO_DEVICE);
} }
ptes[i].status = PTE_INUSE;
if (page_lvl0_idx + num_lvl0_pages > pg_tbl->num_extended_entries) {
dev_err(pg_tbl->device,
"ending level 0 slot at %lu is too large, max is <= %u\n",
page_lvl0_idx + num_lvl0_pages,
pg_tbl->num_extended_entries);
return true;
} }
return 0;
return false;
} }
/* /*
* Allocate page table entries in a simple table. * Non-locking entry to unmapping routines.
* The page table mutex must be held by the caller. * The page table mutex must be held by the caller.
*/ */
static int gasket_alloc_simple_entries( static void gasket_page_table_unmap_nolock(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages) struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
{ {
if (!gasket_is_pte_range_free( if (!num_pages)
pg_tbl->entries + gasket_simple_page_idx(pg_tbl, dev_addr), return;
num_pages))
return -EBUSY;
return 0; if (gasket_addr_is_simple(pg_tbl, dev_addr))
gasket_unmap_simple_pages(pg_tbl, dev_addr, num_pages);
else
gasket_unmap_extended_pages(pg_tbl, dev_addr, num_pages);
} }
/* /*
* Allocate slots in an extended page table. Check to see if a range of page * Allocate and map pages to simple addresses.
* table slots are available. If necessary, memory is allocated for second level * If there is an error, no pages are mapped.
* page tables.
*
* Note that memory for second level page tables is allocated as needed, but
* that memory is only freed on the final close of the device file, when the
* page tables are repartitioned, or the the device is removed. If there is an
* error or if the full range of slots is not available, any memory
* allocated for second level page tables remains allocated until final close,
* repartition, or device removal.
*
* The page table mutex must be held by the caller.
*/ */
static int gasket_alloc_extended_entries( static int gasket_map_simple_pages(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_entries) struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
uint num_pages)
{ {
int ret = 0; int ret;
uint remain, subtable_slot_idx, len; uint slot_idx = gasket_simple_page_idx(pg_tbl, dev_addr);
struct gasket_page_table_entry *pte;
u64 __iomem *slot;
remain = num_entries;
subtable_slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
pte = pg_tbl->entries + pg_tbl->num_simple_entries +
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
slot = pg_tbl->base_slot + pg_tbl->num_simple_entries +
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
while (remain > 0) {
len = min(remain,
GASKET_PAGES_PER_SUBTABLE - subtable_slot_idx);
if (pte->status == PTE_FREE) { ret = gasket_alloc_simple_entries(pg_tbl, dev_addr, num_pages);
ret = gasket_alloc_extended_subtable(pg_tbl, pte, slot);
if (ret) { if (ret) {
dev_err(pg_tbl->device, dev_err(pg_tbl->device,
"no memory for extended addr subtable\n"); "page table slots %u (@ 0x%lx) to %u are not available\n",
slot_idx, dev_addr, slot_idx + num_pages - 1);
return ret; return ret;
} }
} else {
if (!gasket_is_pte_range_free(
pte->sublevel + subtable_slot_idx, len))
return -EBUSY;
}
remain -= len; ret = gasket_perform_mapping(
subtable_slot_idx = 0; pg_tbl, pg_tbl->entries + slot_idx,
pte++; pg_tbl->base_slot + slot_idx, host_addr, num_pages, 1);
slot++;
}
return 0; if (ret) {
gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages);
dev_err(pg_tbl->device, "gasket_perform_mapping %d\n", ret);
}
return ret;
} }
/* /*
...@@ -930,384 +911,338 @@ static int gasket_alloc_extended_subtable( ...@@ -930,384 +911,338 @@ static int gasket_alloc_extended_subtable(
} }
/* /*
* Non-locking entry to unmapping routines. * Allocate slots in an extended page table. Check to see if a range of page
* The page table mutex must be held by the caller. * table slots are available. If necessary, memory is allocated for second level
*/ * page tables.
static void gasket_page_table_unmap_nolock( *
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages) * Note that memory for second level page tables is allocated as needed, but
{ * that memory is only freed on the final close of the device file, when the
if (!num_pages) * page tables are repartitioned, or the the device is removed. If there is an
return; * error or if the full range of slots is not available, any memory
* allocated for second level page tables remains allocated until final close,
if (gasket_addr_is_simple(pg_tbl, dev_addr)) * repartition, or device removal.
gasket_unmap_simple_pages(pg_tbl, dev_addr, num_pages); *
else
gasket_unmap_extended_pages(pg_tbl, dev_addr, num_pages);
}
/*
* Unmap and release pages mapped to simple addresses.
* The page table mutex must be held by the caller. * The page table mutex must be held by the caller.
*/ */
static void gasket_unmap_simple_pages( static int gasket_alloc_extended_entries(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages) struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_entries)
{ {
uint slot = gasket_simple_page_idx(pg_tbl, dev_addr); int ret = 0;
uint remain, subtable_slot_idx, len;
struct gasket_page_table_entry *pte;
u64 __iomem *slot;
gasket_perform_unmapping(pg_tbl, pg_tbl->entries + slot, remain = num_entries;
pg_tbl->base_slot + slot, num_pages, 1); subtable_slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
pte = pg_tbl->entries + pg_tbl->num_simple_entries +
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
slot = pg_tbl->base_slot + pg_tbl->num_simple_entries +
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
while (remain > 0) {
len = min(remain,
GASKET_PAGES_PER_SUBTABLE - subtable_slot_idx);
if (pte->status == PTE_FREE) {
ret = gasket_alloc_extended_subtable(pg_tbl, pte, slot);
if (ret) {
dev_err(pg_tbl->device,
"no memory for extended addr subtable\n");
return ret;
}
} else {
if (!gasket_is_pte_range_free(
pte->sublevel + subtable_slot_idx, len))
return -EBUSY;
}
remain -= len;
subtable_slot_idx = 0;
pte++;
slot++;
}
return 0;
} }
/* /*
* Unmap and release buffers to extended addresses. * gasket_map_extended_pages - Get and map buffers to extended addresses.
* The page table mutex must be held by the caller. * If there is an error, no pages are mapped.
*/ */
static void gasket_unmap_extended_pages( static int gasket_map_extended_pages(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages) struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
uint num_pages)
{ {
int ret;
ulong dev_addr_end;
uint slot_idx, remain, len; uint slot_idx, remain, len;
struct gasket_page_table_entry *pte; struct gasket_page_table_entry *pte;
u64 __iomem *slot_base; u64 __iomem *slot_base;
ret = gasket_alloc_extended_entries(pg_tbl, dev_addr, num_pages);
if (ret) {
dev_addr_end = dev_addr + (num_pages / PAGE_SIZE) - 1;
dev_err(pg_tbl->device,
"page table slots (%lu,%lu) (@ 0x%lx) to (%lu,%lu) are "
"not available\n",
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr),
dev_addr,
gasket_extended_lvl1_page_idx(pg_tbl, dev_addr),
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr_end),
gasket_extended_lvl1_page_idx(pg_tbl, dev_addr_end));
return ret;
}
remain = num_pages; remain = num_pages;
slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr); slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
pte = pg_tbl->entries + pg_tbl->num_simple_entries + pte = pg_tbl->entries + pg_tbl->num_simple_entries +
gasket_extended_lvl0_page_idx(pg_tbl, dev_addr); gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
while (remain > 0) { while (remain > 0) {
/* TODO: Add check to ensure pte remains valid? */
len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx); len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx);
if (pte->status == PTE_INUSE) { slot_base =
slot_base = (u64 __iomem *)(page_address(pte->page) + (u64 __iomem *)(page_address(pte->page) + pte->offset);
pte->offset); ret = gasket_perform_mapping(
gasket_perform_unmapping( pg_tbl, pte->sublevel + slot_idx, slot_base + slot_idx,
pg_tbl, pte->sublevel + slot_idx, host_addr, len, 0);
slot_base + slot_idx, len, 0); if (ret) {
gasket_page_table_unmap_nolock(
pg_tbl, dev_addr, num_pages);
return ret;
} }
remain -= len; remain -= len;
slot_idx = 0; slot_idx = 0;
pte++; pte++;
host_addr += len * PAGE_SIZE;
} }
return 0;
} }
/* /*
* Unmap and release mapped pages. * See gasket_page_table.h for general description.
* The page table mutex must be held by the caller. *
* gasket_page_table_map calls either gasket_map_simple_pages() or
* gasket_map_extended_pages() to actually perform the mapping.
*
* The page table mutex is held for the entire operation.
*/ */
static void gasket_perform_unmapping( int gasket_page_table_map(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *ptes, struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
u64 __iomem *slots, uint num_pages, int is_simple_mapping) uint num_pages)
{ {
int i; int ret;
/*
* For each page table entry and corresponding entry in the device's
* address translation table:
*/
for (i = 0; i < num_pages; i++) {
/* release the address from the device, */
if (is_simple_mapping || ptes[i].status == PTE_INUSE)
writeq(0, &slots[i]);
else
((u64 __force *)slots)[i] = 0;
/* Force sync around the address release. */
mb();
/* release the address from the driver, */ if (!num_pages)
if (ptes[i].status == PTE_INUSE) { return 0;
if (ptes[i].dma_addr) {
dma_unmap_page(pg_tbl->device, ptes[i].dma_addr,
PAGE_SIZE, DMA_FROM_DEVICE);
}
if (gasket_release_page(ptes[i].page))
--pg_tbl->num_active_pages;
}
ptes[i].status = PTE_FREE;
/* and clear the PTE. */ mutex_lock(&pg_tbl->mutex);
memset(&ptes[i], 0, sizeof(struct gasket_page_table_entry));
if (gasket_addr_is_simple(pg_tbl, dev_addr)) {
ret = gasket_map_simple_pages(
pg_tbl, host_addr, dev_addr, num_pages);
} else {
ret = gasket_map_extended_pages(
pg_tbl, host_addr, dev_addr, num_pages);
} }
mutex_unlock(&pg_tbl->mutex);
dev_dbg(pg_tbl->device,
"%s done: ha %llx daddr %llx num %d, ret %d\n",
__func__, (unsigned long long)host_addr,
(unsigned long long)dev_addr, num_pages, ret);
return ret;
} }
EXPORT_SYMBOL(gasket_page_table_map);
/* /*
* Free a second level page [sub]table. * See gasket_page_table.h for general description.
* The page table mutex must be held before this call. *
* gasket_page_table_unmap takes the page table lock and calls either
* gasket_unmap_simple_pages() or gasket_unmap_extended_pages() to
* actually unmap the pages from device space.
*
* The page table mutex is held for the entire operation.
*/ */
static void gasket_free_extended_subtable( void gasket_page_table_unmap(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *pte, struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
u64 __iomem *slot)
{ {
/* Release the page table from the driver */ if (!num_pages)
pte->status = PTE_FREE; return;
/* Release the page table from the device */
writeq(0, slot);
/* Force sync around the address release. */
mb();
if (pte->dma_addr)
dma_unmap_page(pg_tbl->device, pte->dma_addr, PAGE_SIZE,
DMA_BIDIRECTIONAL);
vfree(pte->sublevel);
if (pte->page)
free_page((ulong)page_address(pte->page));
memset(pte, 0, sizeof(struct gasket_page_table_entry)); mutex_lock(&pg_tbl->mutex);
gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages);
mutex_unlock(&pg_tbl->mutex);
} }
EXPORT_SYMBOL(gasket_page_table_unmap);
/* Safely return a page to the OS. */ static void gasket_page_table_unmap_all_nolock(struct gasket_page_table *pg_tbl)
static bool gasket_release_page(struct page *page)
{ {
if (!page) gasket_unmap_simple_pages(
return false; pg_tbl, gasket_components_to_dev_address(pg_tbl, 1, 0, 0),
pg_tbl->num_simple_entries);
if (!PageReserved(page)) gasket_unmap_extended_pages(
SetPageDirty(page); pg_tbl, gasket_components_to_dev_address(pg_tbl, 0, 0, 0),
put_page(page); pg_tbl->num_extended_entries * GASKET_PAGES_PER_SUBTABLE);
return true;
} }
/* Evaluates to nonzero if the specified virtual address is simple. */ /* See gasket_page_table.h for description. */
static inline bool gasket_addr_is_simple( void gasket_page_table_unmap_all(struct gasket_page_table *pg_tbl)
struct gasket_page_table *pg_tbl, ulong addr)
{ {
return !((addr) & (pg_tbl)->extended_flag); mutex_lock(&pg_tbl->mutex);
gasket_page_table_unmap_all_nolock(pg_tbl);
mutex_unlock(&pg_tbl->mutex);
} }
EXPORT_SYMBOL(gasket_page_table_unmap_all);
/* /* See gasket_page_table.h for description. */
* Validity checking for simple addresses. void gasket_page_table_reset(struct gasket_page_table *pg_tbl)
*
* Verify that address translation commutes (from address to/from page + offset)
* and that the requested page range starts and ends within the set of
* currently-partitioned simple pages.
*/
static bool gasket_is_simple_dev_addr_bad(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
{ {
ulong page_offset = dev_addr & (PAGE_SIZE - 1); mutex_lock(&pg_tbl->mutex);
ulong page_index = gasket_page_table_unmap_all_nolock(pg_tbl);
(dev_addr / PAGE_SIZE) & (pg_tbl->config.total_entries - 1); writeq(pg_tbl->config.total_entries, pg_tbl->extended_offset_reg);
mutex_unlock(&pg_tbl->mutex);
if (gasket_components_to_dev_address(
pg_tbl, 1, page_index, page_offset) != dev_addr) {
dev_err(pg_tbl->device, "address is invalid, 0x%lX\n",
dev_addr);
return true;
}
if (page_index >= pg_tbl->num_simple_entries) {
dev_err(pg_tbl->device,
"starting slot at %lu is too large, max is < %u\n",
page_index, pg_tbl->num_simple_entries);
return true;
}
if (page_index + num_pages > pg_tbl->num_simple_entries) {
dev_err(pg_tbl->device,
"ending slot at %lu is too large, max is <= %u\n",
page_index + num_pages, pg_tbl->num_simple_entries);
return true;
}
return false;
} }
/* /* See gasket_page_table.h for description. */
* Validity checking for extended addresses. int gasket_page_table_lookup_page(
* struct gasket_page_table *pg_tbl, ulong dev_addr, struct page **ppage,
* Verify that address translation commutes (from address to/from page + ulong *poffset)
* offset) and that the requested page range starts and ends within the set of
* currently-partitioned extended pages.
*/
static bool gasket_is_extended_dev_addr_bad(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
{ {
/* Starting byte index of dev_addr into the first mapped page */ uint page_num;
ulong page_offset = dev_addr & (PAGE_SIZE - 1); struct gasket_page_table_entry *pte;
ulong page_global_idx, page_lvl0_idx;
ulong num_lvl0_pages;
ulong addr;
/* check if the device address is out of bound */
addr = dev_addr & ~((pg_tbl)->extended_flag);
if (addr >> (GASKET_EXTENDED_LVL0_WIDTH + GASKET_EXTENDED_LVL0_SHIFT)) {
dev_err(pg_tbl->device, "device address out of bounds: 0x%lx\n",
dev_addr);
return true;
}
/* Find the starting sub-page index in the space of all sub-pages. */ mutex_lock(&pg_tbl->mutex);
page_global_idx = (dev_addr / PAGE_SIZE) & if (gasket_addr_is_simple(pg_tbl, dev_addr)) {
(pg_tbl->config.total_entries * GASKET_PAGES_PER_SUBTABLE - 1); page_num = gasket_simple_page_idx(pg_tbl, dev_addr);
if (page_num >= pg_tbl->num_simple_entries)
goto fail;
/* Find the starting level 0 index. */ pte = pg_tbl->entries + page_num;
page_lvl0_idx = gasket_extended_lvl0_page_idx(pg_tbl, dev_addr); if (pte->status != PTE_INUSE)
goto fail;
} else {
/* Find the level 0 entry, */
page_num = gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
if (page_num >= pg_tbl->num_extended_entries)
goto fail;
/* Get the count of affected level 0 pages. */ pte = pg_tbl->entries + pg_tbl->num_simple_entries + page_num;
num_lvl0_pages = (num_pages + GASKET_PAGES_PER_SUBTABLE - 1) / if (pte->status != PTE_INUSE)
GASKET_PAGES_PER_SUBTABLE; goto fail;
if (gasket_components_to_dev_address( /* and its contained level 1 entry. */
pg_tbl, 0, page_global_idx, page_offset) != dev_addr) { page_num = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
dev_err(pg_tbl->device, "address is invalid: 0x%lx\n", pte = pte->sublevel + page_num;
dev_addr); if (pte->status != PTE_INUSE)
return true; goto fail;
} }
if (page_lvl0_idx >= pg_tbl->num_extended_entries) { *ppage = pte->page;
dev_err(pg_tbl->device, *poffset = pte->offset;
"starting level 0 slot at %lu is too large, max is < " mutex_unlock(&pg_tbl->mutex);
"%u\n", page_lvl0_idx, pg_tbl->num_extended_entries); return 0;
return true;
}
if (page_lvl0_idx + num_lvl0_pages > pg_tbl->num_extended_entries) { fail:
*ppage = NULL;
*poffset = 0;
mutex_unlock(&pg_tbl->mutex);
return -1;
}
/* See gasket_page_table.h for description. */
bool gasket_page_table_are_addrs_bad(
struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
ulong bytes)
{
if (host_addr & (PAGE_SIZE - 1)) {
dev_err(pg_tbl->device, dev_err(pg_tbl->device,
"ending level 0 slot at %lu is too large, max is <= %u\n", "host mapping address 0x%lx must be page aligned\n",
page_lvl0_idx + num_lvl0_pages, host_addr);
pg_tbl->num_extended_entries);
return true; return true;
} }
return false; return gasket_page_table_is_dev_addr_bad(pg_tbl, dev_addr, bytes);
} }
EXPORT_SYMBOL(gasket_page_table_are_addrs_bad);
/* /* See gasket_page_table.h for description. */
* Check if a range of PTEs is free. bool gasket_page_table_is_dev_addr_bad(
* The page table mutex must be held by the caller. struct gasket_page_table *pg_tbl, ulong dev_addr, ulong bytes)
*/
static bool gasket_is_pte_range_free(
struct gasket_page_table_entry *ptes, uint num_entries)
{ {
int i; uint num_pages = bytes / PAGE_SIZE;
for (i = 0; i < num_entries; i++) { if (bytes & (PAGE_SIZE - 1)) {
if (ptes[i].status != PTE_FREE) dev_err(pg_tbl->device,
return false; "mapping size 0x%lX must be page aligned\n", bytes);
return true;
} }
if (num_pages == 0) {
dev_err(pg_tbl->device,
"requested mapping is less than one page: %lu / %lu\n",
bytes, PAGE_SIZE);
return true; return true;
}
/*
* Actually perform collection.
* The page table mutex must be held by the caller.
*/
static void gasket_page_table_garbage_collect_nolock(
struct gasket_page_table *pg_tbl)
{
struct gasket_page_table_entry *pte;
u64 __iomem *slot;
/* XXX FIX ME XXX -- more efficient to keep a usage count */
/* rather than scanning the second level page tables */
for (pte = pg_tbl->entries + pg_tbl->num_simple_entries,
slot = pg_tbl->base_slot + pg_tbl->num_simple_entries;
pte < pg_tbl->entries + pg_tbl->config.total_entries;
pte++, slot++) {
if (pte->status == PTE_INUSE) {
if (gasket_is_pte_range_free(
pte->sublevel, GASKET_PAGES_PER_SUBTABLE))
gasket_free_extended_subtable(
pg_tbl, pte, slot);
}
} }
if (gasket_addr_is_simple(pg_tbl, dev_addr))
return gasket_is_simple_dev_addr_bad(
pg_tbl, dev_addr, num_pages);
return gasket_is_extended_dev_addr_bad(pg_tbl, dev_addr, num_pages);
} }
EXPORT_SYMBOL(gasket_page_table_is_dev_addr_bad);
/* /* See gasket_page_table.h for description. */
* Convert (simple, page, offset) into a device address. uint gasket_page_table_max_size(struct gasket_page_table *page_table)
* Examples:
* Simple page 0, offset 32:
* Input (0, 0, 32), Output 0x20
* Simple page 1000, offset 511:
* Input (0, 1000, 512), Output 0x3E81FF
* Extended page 0, offset 32:
* Input (0, 0, 32), Output 0x8000000020
* Extended page 1000, offset 511:
* Input (1, 1000, 512), Output 0x8003E81FF
*/
static ulong gasket_components_to_dev_address(
struct gasket_page_table *pg_tbl, int is_simple, uint page_index,
uint offset)
{ {
ulong lvl0_index, lvl1_index; if (!page_table)
return 0;
if (is_simple) { return page_table->config.total_entries;
/* Return simple addresses directly. */
lvl0_index = page_index & (pg_tbl->config.total_entries - 1);
return (lvl0_index << GASKET_SIMPLE_PAGE_SHIFT) | offset;
}
/*
* This could be compressed into fewer statements, but
* A) the compiler should optimize it
* B) this is not slow
* C) this is an uncommon operation
* D) this is actually readable this way.
*/
lvl0_index = page_index / GASKET_PAGES_PER_SUBTABLE;
lvl1_index = page_index & (GASKET_PAGES_PER_SUBTABLE - 1);
return (pg_tbl)->extended_flag |
(lvl0_index << GASKET_EXTENDED_LVL0_SHIFT) |
(lvl1_index << GASKET_EXTENDED_LVL1_SHIFT) | offset;
} }
EXPORT_SYMBOL(gasket_page_table_max_size);
/* /* See gasket_page_table.h for description. */
* Return the index of the page for the address in the simple table. uint gasket_page_table_num_entries(struct gasket_page_table *pg_tbl)
* Does not perform validity checking.
*/
static int gasket_simple_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr)
{ {
return (dev_addr >> GASKET_SIMPLE_PAGE_SHIFT) & if (!pg_tbl)
(pg_tbl->config.total_entries - 1); return 0;
return pg_tbl->num_simple_entries + pg_tbl->num_extended_entries;
} }
EXPORT_SYMBOL(gasket_page_table_num_entries);
/* /* See gasket_page_table.h for description. */
* Return the level 0 page index for the given address. uint gasket_page_table_num_simple_entries(struct gasket_page_table *pg_tbl)
* Does not perform validity checking.
*/
static ulong gasket_extended_lvl0_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr)
{ {
return (dev_addr >> GASKET_EXTENDED_LVL0_SHIFT) & if (!pg_tbl)
((1 << GASKET_EXTENDED_LVL0_WIDTH) - 1); return 0;
return pg_tbl->num_simple_entries;
} }
EXPORT_SYMBOL(gasket_page_table_num_simple_entries);
/* /* See gasket_page_table.h for description. */
* Return the level 1 page index for the given address. uint gasket_page_table_num_active_pages(struct gasket_page_table *pg_tbl)
* Does not perform validity checking.
*/
static ulong gasket_extended_lvl1_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr)
{ {
return (dev_addr >> GASKET_EXTENDED_LVL1_SHIFT) & if (!pg_tbl)
(GASKET_PAGES_PER_SUBTABLE - 1); return 0;
return pg_tbl->num_active_pages;
} }
EXPORT_SYMBOL(gasket_page_table_num_active_pages);
/* /* See gasket_page_table.h */
* Return whether a host buffer was mapped as coherent memory. int gasket_page_table_system_status(struct gasket_page_table *page_table)
*
* A Gasket page_table currently support one contiguous dma range, mapped to one
* contiguous virtual memory range. Check if the host_addr is within that range.
*/
static int is_coherent(struct gasket_page_table *pg_tbl, ulong host_addr)
{ {
u64 min, max; if (!page_table)
return GASKET_STATUS_LAMED;
/* whether the host address is within user virt range */
if (!pg_tbl->coherent_pages)
return 0;
min = (u64)pg_tbl->coherent_pages[0].user_virt; if (gasket_page_table_num_entries(page_table) == 0) {
max = min + PAGE_SIZE * pg_tbl->num_coherent_pages; dev_dbg(page_table->device, "Page table size is 0\n");
return GASKET_STATUS_LAMED;
}
return min <= host_addr && host_addr < max; return GASKET_STATUS_ALIVE;
} }
/* Record the host_addr to coherent dma memory mapping. */ /* Record the host_addr to coherent dma memory mapping. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment