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 {
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. */
int gasket_page_table_init(
struct gasket_page_table **ppg_tbl,
......@@ -353,6 +288,85 @@ int gasket_page_table_init(
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. */
void gasket_page_table_cleanup(struct gasket_page_table *pg_tbl)
{
......@@ -404,482 +418,449 @@ int gasket_page_table_partition(
EXPORT_SYMBOL(gasket_page_table_partition);
/*
* See gasket_page_table.h for general description.
*
* gasket_page_table_map calls either gasket_map_simple_pages() or
* gasket_map_extended_pages() to actually perform the mapping.
* Return whether a host buffer was mapped as coherent memory.
*
* 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(
struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
uint num_pages)
static int is_coherent(struct gasket_page_table *pg_tbl, ulong host_addr)
{
int ret;
u64 min, max;
if (!num_pages)
/* whether the host address is within user virt range */
if (!pg_tbl->coherent_pages)
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)) {
ret = gasket_map_simple_pages(
pg_tbl, host_addr, dev_addr, num_pages);
return min <= host_addr && host_addr < max;
}
/*
* 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 {
ret = gasket_map_extended_pages(
pg_tbl, host_addr, dev_addr, num_pages);
ret = get_user_pages_fast(
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,
"%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;
"%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) {
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.
*
* 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.
* Return the index of the page for the address in the simple table.
* Does not perform validity checking.
*/
void gasket_page_table_unmap(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
static int gasket_simple_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr)
{
if (!num_pages)
return;
mutex_lock(&pg_tbl->mutex);
gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages);
mutex_unlock(&pg_tbl->mutex);
return (dev_addr >> GASKET_SIMPLE_PAGE_SHIFT) &
(pg_tbl->config.total_entries - 1);
}
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(
pg_tbl, gasket_components_to_dev_address(pg_tbl, 1, 0, 0),
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);
return (dev_addr >> GASKET_EXTENDED_LVL0_SHIFT) &
((1 << GASKET_EXTENDED_LVL0_WIDTH) - 1);
}
/* 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);
gasket_page_table_unmap_all_nolock(pg_tbl);
mutex_unlock(&pg_tbl->mutex);
return (dev_addr >> GASKET_EXTENDED_LVL1_SHIFT) &
(GASKET_PAGES_PER_SUBTABLE - 1);
}
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);
gasket_page_table_unmap_all_nolock(pg_tbl);
writeq(pg_tbl->config.total_entries, pg_tbl->extended_offset_reg);
mutex_unlock(&pg_tbl->mutex);
}
if (!gasket_is_pte_range_free(
pg_tbl->entries + gasket_simple_page_idx(pg_tbl, dev_addr),
num_pages))
return -EBUSY;
/* 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);
return 0;
}
/* See gasket_page_table.h for description. */
int gasket_page_table_lookup_page(
struct gasket_page_table *pg_tbl, ulong dev_addr, struct page **ppage,
ulong *poffset)
/* Safely return a page to the OS. */
static bool gasket_release_page(struct page *page)
{
uint page_num;
struct gasket_page_table_entry *pte;
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;
if (!page)
return false;
fail:
*ppage = NULL;
*poffset = 0;
mutex_unlock(&pg_tbl->mutex);
return -1;
}
if (!PageReserved(page))
SetPageDirty(page);
put_page(page);
/* 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 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(
struct gasket_page_table *pg_tbl, ulong dev_addr, ulong bytes)
/*
* Unmap and release mapped pages.
* 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)) {
dev_err(pg_tbl->device,
"mapping size 0x%lX must be page aligned\n", bytes);
return true;
/* release the address from the driver, */
if (ptes[i].status == PTE_INUSE) {
if (ptes[i].dma_addr) {
dma_unmap_page(pg_tbl->device, ptes[i].dma_addr,
PAGE_SIZE, DMA_FROM_DEVICE);
}
if (num_pages == 0) {
dev_err(pg_tbl->device,
"requested mapping is less than one page: %lu / %lu\n",
bytes, PAGE_SIZE);
return true;
if (gasket_release_page(ptes[i].page))
--pg_tbl->num_active_pages;
}
ptes[i].status = PTE_FREE;
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);
/* and clear the PTE. */
memset(&ptes[i], 0, sizeof(struct gasket_page_table_entry));
}
}
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)
return 0;
return page_table->config.total_entries;
}
EXPORT_SYMBOL(gasket_page_table_max_size);
uint slot = gasket_simple_page_idx(pg_tbl, dev_addr);
/* See gasket_page_table.h for description. */
uint gasket_page_table_num_entries(struct gasket_page_table *pg_tbl)
{
if (!pg_tbl)
return 0;
return pg_tbl->num_simple_entries + pg_tbl->num_extended_entries;
gasket_perform_unmapping(pg_tbl, pg_tbl->entries + slot,
pg_tbl->base_slot + slot, num_pages, 1);
}
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)
return 0;
return pg_tbl->num_simple_entries;
}
EXPORT_SYMBOL(gasket_page_table_num_simple_entries);
uint slot_idx, remain, len;
struct gasket_page_table_entry *pte;
u64 __iomem *slot_base;
/* See gasket_page_table.h for description. */
uint gasket_page_table_num_active_pages(struct gasket_page_table *pg_tbl)
{
if (!pg_tbl)
return 0;
return pg_tbl->num_active_pages;
}
EXPORT_SYMBOL(gasket_page_table_num_active_pages);
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);
/* See gasket_page_table.h */
int gasket_page_table_system_status(struct gasket_page_table *page_table)
{
if (!page_table)
return GASKET_STATUS_LAMED;
while (remain > 0) {
/* TODO: Add check to ensure pte remains valid? */
len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx);
if (gasket_page_table_num_entries(page_table) == 0) {
dev_dbg(page_table->device, "Page table size is 0\n");
return GASKET_STATUS_LAMED;
if (pte->status == PTE_INUSE) {
slot_base = (u64 __iomem *)(page_address(pte->page) +
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.
* If there is an error, no pages are mapped.
* Convert (simple, page, offset) into a device address.
* 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(
struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
uint num_pages)
static ulong gasket_components_to_dev_address(
struct gasket_page_table *pg_tbl, int is_simple, uint page_index,
uint offset)
{
int ret;
uint slot_idx = gasket_simple_page_idx(pg_tbl, dev_addr);
ulong lvl0_index, lvl1_index;
ret = gasket_alloc_simple_entries(pg_tbl, dev_addr, num_pages);
if (ret) {
dev_err(pg_tbl->device,
"page table slots %u (@ 0x%lx) to %u are not available\n",
slot_idx, dev_addr, slot_idx + num_pages - 1);
return ret;
if (is_simple) {
/* Return simple addresses directly. */
lvl0_index = page_index & (pg_tbl->config.total_entries - 1);
return (lvl0_index << GASKET_SIMPLE_PAGE_SHIFT) | offset;
}
ret = gasket_perform_mapping(
pg_tbl, pg_tbl->entries + slot_idx,
pg_tbl->base_slot + slot_idx, host_addr, num_pages, 1);
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;
/*
* 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;
}
/*
* gasket_map_extended_pages - Get and map buffers to extended addresses.
* If there is an error, no pages are mapped.
* Validity checking for simple addresses.
*
* 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(
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;
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);
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);
ulong page_index =
(dev_addr / PAGE_SIZE) & (pg_tbl->config.total_entries - 1);
while (remain > 0) {
len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx);
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;
}
slot_base =
(u64 __iomem *)(page_address(pte->page) + pte->offset);
ret = gasket_perform_mapping(
pg_tbl, pte->sublevel + slot_idx, slot_base + slot_idx,
host_addr, len, 0);
if (ret) {
gasket_page_table_unmap_nolock(
pg_tbl, dev_addr, num_pages);
return ret;
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;
}
remain -= len;
slot_idx = 0;
pte++;
host_addr += len * PAGE_SIZE;
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 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
* 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.
* 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 extended pages.
*/
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)
static bool gasket_is_extended_dev_addr_bad(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
{
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 {
ret = get_user_pages_fast(
page_addr - offset, 1, 1, &page);
/* Starting byte index of dev_addr into the first mapped page */
ulong page_offset = dev_addr & (PAGE_SIZE - 1);
ulong page_global_idx, page_lvl0_idx;
ulong num_lvl0_pages;
ulong addr;
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;
/* 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;
}
++pg_tbl->num_active_pages;
ptes[i].page = page;
ptes[i].offset = offset;
/* Find the starting sub-page index in the space of all sub-pages. */
page_global_idx = (dev_addr / PAGE_SIZE) &
(pg_tbl->config.total_entries * GASKET_PAGES_PER_SUBTABLE - 1);
/* 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,
"%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);
/* Find the starting level 0 index. */
page_lvl0_idx = gasket_extended_lvl0_page_idx(pg_tbl, dev_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();
}
/* Get the count of affected level 0 pages. */
num_lvl0_pages = (num_pages + GASKET_PAGES_PER_SUBTABLE - 1) /
GASKET_PAGES_PER_SUBTABLE;
/* Make the DMA-space address available to the device. */
dma_addr = (ptes[i].dma_addr + offset) | GASKET_VALID_SLOT_FLAG;
if (gasket_components_to_dev_address(
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) {
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);
if (page_lvl0_idx >= pg_tbl->num_extended_entries) {
dev_err(pg_tbl->device,
"starting level 0 slot at %lu is too large, max is < "
"%u\n", page_lvl0_idx, pg_tbl->num_extended_entries);
return true;
}
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.
*/
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)
{
if (!gasket_is_pte_range_free(
pg_tbl->entries + gasket_simple_page_idx(pg_tbl, dev_addr),
num_pages))
return -EBUSY;
if (!num_pages)
return;
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
* table slots are available. If necessary, memory is allocated for second level
* 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.
* Allocate and map pages to simple addresses.
* If there is an error, no pages are mapped.
*/
static int gasket_alloc_extended_entries(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_entries)
static int gasket_map_simple_pages(
struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
uint num_pages)
{
int ret = 0;
uint remain, subtable_slot_idx, len;
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);
int ret;
uint slot_idx = gasket_simple_page_idx(pg_tbl, dev_addr);
if (pte->status == PTE_FREE) {
ret = gasket_alloc_extended_subtable(pg_tbl, pte, slot);
ret = gasket_alloc_simple_entries(pg_tbl, dev_addr, num_pages);
if (ret) {
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;
}
} else {
if (!gasket_is_pte_range_free(
pte->sublevel + subtable_slot_idx, len))
return -EBUSY;
}
remain -= len;
subtable_slot_idx = 0;
pte++;
slot++;
}
ret = gasket_perform_mapping(
pg_tbl, pg_tbl->entries + slot_idx,
pg_tbl->base_slot + slot_idx, host_addr, num_pages, 1);
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(
}
/*
* Non-locking entry to unmapping routines.
* The page table mutex must be held by the caller.
*/
static void gasket_page_table_unmap_nolock(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
{
if (!num_pages)
return;
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);
}
/*
* Unmap and release pages mapped to simple addresses.
* Allocate slots in an extended page table. Check to see if a range of page
* table slots are available. If necessary, memory is allocated for second level
* 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 void gasket_unmap_simple_pages(
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)
{
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,
pg_tbl->base_slot + slot, num_pages, 1);
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_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.
* The page table mutex must be held by the caller.
* gasket_map_extended_pages - Get and map buffers to extended addresses.
* If there is an error, no pages are mapped.
*/
static void gasket_unmap_extended_pages(
struct gasket_page_table *pg_tbl, 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)
{
int ret;
ulong dev_addr_end;
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) {
/* TODO: Add check to ensure pte remains valid? */
len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx);
if (pte->status == PTE_INUSE) {
slot_base = (u64 __iomem *)(page_address(pte->page) +
pte->offset);
gasket_perform_unmapping(
pg_tbl, pte->sublevel + slot_idx,
slot_base + slot_idx, len, 0);
slot_base =
(u64 __iomem *)(page_address(pte->page) + pte->offset);
ret = gasket_perform_mapping(
pg_tbl, pte->sublevel + slot_idx, slot_base + slot_idx,
host_addr, len, 0);
if (ret) {
gasket_page_table_unmap_nolock(
pg_tbl, dev_addr, num_pages);
return ret;
}
remain -= len;
slot_idx = 0;
pte++;
host_addr += len * PAGE_SIZE;
}
return 0;
}
/*
* Unmap and release mapped pages.
* The page table mutex must be held by the caller.
* See gasket_page_table.h for general description.
*
* 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(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *ptes,
u64 __iomem *slots, uint num_pages, int is_simple_mapping)
int gasket_page_table_map(
struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
uint num_pages)
{
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();
int ret;
/* release the address from the driver, */
if (ptes[i].status == PTE_INUSE) {
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;
if (!num_pages)
return 0;
/* and clear the PTE. */
memset(&ptes[i], 0, sizeof(struct gasket_page_table_entry));
mutex_lock(&pg_tbl->mutex);
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.
* The page table mutex must be held before this call.
* See gasket_page_table.h for general description.
*
* 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(
struct gasket_page_table *pg_tbl, struct gasket_page_table_entry *pte,
u64 __iomem *slot)
void gasket_page_table_unmap(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
{
/* 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));
if (!num_pages)
return;
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 bool gasket_release_page(struct page *page)
static void gasket_page_table_unmap_all_nolock(struct gasket_page_table *pg_tbl)
{
if (!page)
return false;
if (!PageReserved(page))
SetPageDirty(page);
put_page(page);
return true;
gasket_unmap_simple_pages(
pg_tbl, gasket_components_to_dev_address(pg_tbl, 1, 0, 0),
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);
}
/* 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)
/* See gasket_page_table.h for description. */
void gasket_page_table_unmap_all(struct gasket_page_table *pg_tbl)
{
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);
/*
* Validity checking for simple addresses.
*
* 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)
/* See gasket_page_table.h for description. */
void gasket_page_table_reset(struct gasket_page_table *pg_tbl)
{
ulong page_offset = dev_addr & (PAGE_SIZE - 1);
ulong page_index =
(dev_addr / PAGE_SIZE) & (pg_tbl->config.total_entries - 1);
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;
mutex_lock(&pg_tbl->mutex);
gasket_page_table_unmap_all_nolock(pg_tbl);
writeq(pg_tbl->config.total_entries, pg_tbl->extended_offset_reg);
mutex_unlock(&pg_tbl->mutex);
}
/*
* Validity checking for extended addresses.
*
* 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 extended pages.
*/
static bool gasket_is_extended_dev_addr_bad(
struct gasket_page_table *pg_tbl, ulong dev_addr, uint num_pages)
/* See gasket_page_table.h for description. */
int gasket_page_table_lookup_page(
struct gasket_page_table *pg_tbl, ulong dev_addr, struct page **ppage,
ulong *poffset)
{
/* Starting byte index of dev_addr into the first mapped page */
ulong page_offset = dev_addr & (PAGE_SIZE - 1);
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;
}
uint page_num;
struct gasket_page_table_entry *pte;
/* Find the starting sub-page index in the space of all sub-pages. */
page_global_idx = (dev_addr / PAGE_SIZE) &
(pg_tbl->config.total_entries * GASKET_PAGES_PER_SUBTABLE - 1);
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;
/* Find the starting level 0 index. */
page_lvl0_idx = gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
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;
/* Get the count of affected level 0 pages. */
num_lvl0_pages = (num_pages + GASKET_PAGES_PER_SUBTABLE - 1) /
GASKET_PAGES_PER_SUBTABLE;
pte = pg_tbl->entries + pg_tbl->num_simple_entries + page_num;
if (pte->status != PTE_INUSE)
goto fail;
if (gasket_components_to_dev_address(
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;
/* 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;
}
if (page_lvl0_idx >= pg_tbl->num_extended_entries) {
dev_err(pg_tbl->device,
"starting level 0 slot at %lu is too large, max is < "
"%u\n", page_lvl0_idx, pg_tbl->num_extended_entries);
return true;
}
*ppage = pte->page;
*poffset = pte->offset;
mutex_unlock(&pg_tbl->mutex);
return 0;
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,
"ending level 0 slot at %lu is too large, max is <= %u\n",
page_lvl0_idx + num_lvl0_pages,
pg_tbl->num_extended_entries);
"host mapping address 0x%lx must be page aligned\n",
host_addr);
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);
/*
* 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)
/* See gasket_page_table.h for description. */
bool gasket_page_table_is_dev_addr_bad(
struct gasket_page_table *pg_tbl, ulong dev_addr, ulong bytes)
{
int i;
uint num_pages = bytes / PAGE_SIZE;
for (i = 0; i < num_entries; i++) {
if (ptes[i].status != PTE_FREE)
return false;
if (bytes & (PAGE_SIZE - 1)) {
dev_err(pg_tbl->device,
"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;
}
/*
* 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);
/*
* Convert (simple, page, offset) into a device address.
* 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)
/* See gasket_page_table.h for description. */
uint gasket_page_table_max_size(struct gasket_page_table *page_table)
{
ulong lvl0_index, lvl1_index;
if (is_simple) {
/* 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;
if (!page_table)
return 0;
return page_table->config.total_entries;
}
EXPORT_SYMBOL(gasket_page_table_max_size);
/*
* Return the index of the page for the address in the simple table.
* Does not perform validity checking.
*/
static int gasket_simple_page_idx(
struct gasket_page_table *pg_tbl, ulong dev_addr)
/* See gasket_page_table.h for description. */
uint gasket_page_table_num_entries(struct gasket_page_table *pg_tbl)
{
return (dev_addr >> GASKET_SIMPLE_PAGE_SHIFT) &
(pg_tbl->config.total_entries - 1);
if (!pg_tbl)
return 0;
return pg_tbl->num_simple_entries + pg_tbl->num_extended_entries;
}
EXPORT_SYMBOL(gasket_page_table_num_entries);
/*
* 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)
/* See gasket_page_table.h for description. */
uint gasket_page_table_num_simple_entries(struct gasket_page_table *pg_tbl)
{
return (dev_addr >> GASKET_EXTENDED_LVL0_SHIFT) &
((1 << GASKET_EXTENDED_LVL0_WIDTH) - 1);
if (!pg_tbl)
return 0;
return pg_tbl->num_simple_entries;
}
EXPORT_SYMBOL(gasket_page_table_num_simple_entries);
/*
* 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)
/* See gasket_page_table.h for description. */
uint gasket_page_table_num_active_pages(struct gasket_page_table *pg_tbl)
{
return (dev_addr >> GASKET_EXTENDED_LVL1_SHIFT) &
(GASKET_PAGES_PER_SUBTABLE - 1);
if (!pg_tbl)
return 0;
return pg_tbl->num_active_pages;
}
EXPORT_SYMBOL(gasket_page_table_num_active_pages);
/*
* Return whether a host buffer was mapped as coherent memory.
*
* 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)
/* See gasket_page_table.h */
int gasket_page_table_system_status(struct gasket_page_table *page_table)
{
u64 min, max;
/* whether the host address is within user virt range */
if (!pg_tbl->coherent_pages)
return 0;
if (!page_table)
return GASKET_STATUS_LAMED;
min = (u64)pg_tbl->coherent_pages[0].user_virt;
max = min + PAGE_SIZE * pg_tbl->num_coherent_pages;
if (gasket_page_table_num_entries(page_table) == 0) {
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. */
......
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