diff options
Diffstat (limited to 'drivers/staging/gasket/gasket_page_table.c')
-rw-r--r-- | drivers/staging/gasket/gasket_page_table.c | 162 |
1 files changed, 72 insertions, 90 deletions
diff --git a/drivers/staging/gasket/gasket_page_table.c b/drivers/staging/gasket/gasket_page_table.c index d4c5f8aa7dd3..b7d460cf15fb 100644 --- a/drivers/staging/gasket/gasket_page_table.c +++ b/drivers/staging/gasket/gasket_page_table.c @@ -10,10 +10,18 @@ * * This file assumes 4kB pages throughout; can be factored out when necessary. * - * Address format is as follows: + * There is a configurable number of page table entries, as well as a + * configurable bit index for the extended address flag. Both of these are + * specified in gasket_page_table_init through the page_table_config parameter. + * + * The following example assumes: + * page_table_config->total_entries = 8192 + * page_table_config->extended_bit = 63 + * + * Address format: * Simple addresses - those whose containing pages are directly placed in the * device's address translation registers - are laid out as: - * [ 63 - 40: Unused | 39 - 28: 0 | 27 - 12: page index | 11 - 0: page offset ] + * [ 63 - 25: 0 | 24 - 12: page index | 11 - 0: page offset ] * page index: The index of the containing page in the device's address * translation registers. * page offset: The index of the address into the containing page. @@ -21,7 +29,7 @@ * Extended address - those whose containing pages are contained in a second- * level page table whose address is present in the device's address translation * registers - are laid out as: - * [ 63 - 40: Unused | 39: flag | 38 - 37: 0 | 36 - 21: dev/level 0 index | + * [ 63: flag | 62 - 34: 0 | 33 - 21: dev/level 0 index | * 20 - 12: host/level 1 index | 11 - 0: page offset ] * flag: Marker indicating that this is an extended address. Always 1. * dev index: The index of the first-level page in the device's extended @@ -103,12 +111,6 @@ struct gasket_page_table_entry { /* The status of this entry/slot: free or in use. */ enum pte_status status; - /* Address of the page in DMA space. */ - dma_addr_t dma_addr; - - /* Linux page descriptor for the page described by this structure. */ - struct page *page; - /* * Index for alignment into host vaddrs. * When a user specifies a host address for a mapping, that address may @@ -119,6 +121,12 @@ struct gasket_page_table_entry { */ int offset; + /* Address of the page in DMA space. */ + dma_addr_t dma_addr; + + /* Linux page descriptor for the page described by this structure. */ + struct page *page; + /* * If this is an extended and first-level entry, sublevel points * to the second-level entries underneath this entry. @@ -317,12 +325,10 @@ static void gasket_free_extended_subtable(struct gasket_page_table *pg_tbl, /* 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); + DMA_TO_DEVICE); vfree(pte->sublevel); @@ -435,6 +441,19 @@ static int is_coherent(struct gasket_page_table *pg_tbl, ulong host_addr) return min <= host_addr && host_addr < max; } +/* Safely return a page to the OS. */ +static bool gasket_release_page(struct page *page) +{ + if (!page) + return false; + + if (!PageReserved(page)) + SetPageDirty(page); + put_page(page); + + return true; +} + /* * Get and map last level page table buffers. * @@ -458,7 +477,6 @@ static int gasket_perform_mapping(struct gasket_page_table *pg_tbl, 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 - @@ -487,24 +505,16 @@ static int gasket_perform_mapping(struct gasket_page_table *pg_tbl, 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); - - 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; + + if (dma_mapping_error(pg_tbl->device, + ptes[i].dma_addr)) { + if (gasket_release_page(ptes[i].page)) + --pg_tbl->num_active_pages; + + memset(&ptes[i], 0, + sizeof(struct gasket_page_table_entry)); + return -EINVAL; } - /* Wait until the page is mapped. */ - mb(); } /* Make the DMA-space address available to the device. */ @@ -545,7 +555,7 @@ static ulong gasket_extended_lvl0_page_idx(struct gasket_page_table *pg_tbl, ulong dev_addr) { return (dev_addr >> GASKET_EXTENDED_LVL0_SHIFT) & - ((1 << GASKET_EXTENDED_LVL0_WIDTH) - 1); + (pg_tbl->config.total_entries - 1); } /* @@ -574,19 +584,6 @@ static int gasket_alloc_simple_entries(struct gasket_page_table *pg_tbl, return 0; } -/* Safely return a page to the OS. */ -static bool gasket_release_page(struct page *page) -{ - if (!page) - return false; - - if (!PageReserved(page)) - SetPageDirty(page); - put_page(page); - - return true; -} - /* * Unmap and release mapped pages. * The page table mutex must be held by the caller. @@ -603,23 +600,23 @@ static void gasket_perform_unmapping(struct gasket_page_table *pg_tbl, */ for (i = 0; i < num_pages; i++) { /* release the address from the device, */ - if (is_simple_mapping || ptes[i].status == PTE_INUSE) + if (is_simple_mapping || ptes[i].status == PTE_INUSE) { writeq(0, &slots[i]); - else + } else { ((u64 __force *)slots)[i] = 0; - /* Force sync around the address release. */ - mb(); + /* sync above PTE update before updating mappings */ + wmb(); + } /* release the address from the driver, */ if (ptes[i].status == PTE_INUSE) { - if (ptes[i].dma_addr) { + if (ptes[i].page && ptes[i].dma_addr) { dma_unmap_page(pg_tbl->device, ptes[i].dma_addr, - PAGE_SIZE, DMA_FROM_DEVICE); + PAGE_SIZE, DMA_BIDIRECTIONAL); } if (gasket_release_page(ptes[i].page)) --pg_tbl->num_active_pages; } - ptes[i].status = PTE_FREE; /* and clear the PTE. */ memset(&ptes[i], 0, sizeof(struct gasket_page_table_entry)); @@ -684,38 +681,21 @@ static inline bool gasket_addr_is_simple(struct gasket_page_table *pg_tbl, * Convert (simple, page, offset) into a device address. * Examples: * Simple page 0, offset 32: - * Input (0, 0, 32), Output 0x20 + * Input (1, 0, 32), Output 0x20 * Simple page 1000, offset 511: - * Input (0, 1000, 512), Output 0x3E81FF + * Input (1, 1000, 511), Output 0x3E81FF * Extended page 0, offset 32: * Input (0, 0, 32), Output 0x8000000020 * Extended page 1000, offset 511: - * Input (1, 1000, 512), Output 0x8003E81FF + * Input (0, 1000, 511), 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 (is_simple) { - /* Return simple addresses directly. */ - lvl0_index = page_index & (pg_tbl->config.total_entries - 1); - return (lvl0_index << GASKET_SIMPLE_PAGE_SHIFT) | offset; - } + ulong dev_addr = (page_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; + return is_simple ? dev_addr : (pg_tbl->extended_flag | dev_addr); } /* @@ -896,9 +876,13 @@ static int gasket_alloc_extended_subtable(struct gasket_page_table *pg_tbl, /* Map the page into DMA space. */ pte->dma_addr = dma_map_page(pg_tbl->device, pte->page, 0, PAGE_SIZE, - DMA_BIDIRECTIONAL); - /* Wait until the page is mapped. */ - mb(); + DMA_TO_DEVICE); + if (dma_mapping_error(pg_tbl->device, pte->dma_addr)) { + free_page(page_addr); + vfree(pte->sublevel); + memset(pte, 0, sizeof(struct gasket_page_table_entry)); + return -ENOMEM; + } /* make the addresses available to the device */ dma_addr = (pte->dma_addr + pte->offset) | GASKET_VALID_SLOT_FLAG; @@ -1047,11 +1031,6 @@ int gasket_page_table_map(struct gasket_page_table *pg_tbl, ulong host_addr, } 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); @@ -1151,7 +1130,7 @@ fail: *ppage = NULL; *poffset = 0; mutex_unlock(&pg_tbl->mutex); - return -1; + return -EINVAL; } /* See gasket_page_table.h for description. */ @@ -1291,7 +1270,7 @@ int gasket_alloc_coherent_memory(struct gasket_dev *gasket_dev, u64 size, return -EINVAL; mem = dma_alloc_coherent(gasket_get_device(gasket_dev), - num_pages * PAGE_SIZE, &handle, 0); + num_pages * PAGE_SIZE, &handle, GFP_KERNEL); if (!mem) goto nomem; @@ -1303,7 +1282,6 @@ int gasket_alloc_coherent_memory(struct gasket_dev *gasket_dev, u64 size, GFP_KERNEL); if (!gasket_dev->page_table[index]->coherent_pages) goto nomem; - *dma_address = 0; gasket_dev->coherent_buffer.length_bytes = PAGE_SIZE * (num_pages); @@ -1318,20 +1296,19 @@ int gasket_alloc_coherent_memory(struct gasket_dev *gasket_dev, u64 size, (u64)mem + j * PAGE_SIZE; } - if (*dma_address == 0) - goto nomem; return 0; nomem: if (mem) { dma_free_coherent(gasket_get_device(gasket_dev), num_pages * PAGE_SIZE, mem, handle); + gasket_dev->coherent_buffer.length_bytes = 0; + gasket_dev->coherent_buffer.virt_base = NULL; + gasket_dev->coherent_buffer.phys_base = 0; } - if (gasket_dev->page_table[index]->coherent_pages) { - kfree(gasket_dev->page_table[index]->coherent_pages); - gasket_dev->page_table[index]->coherent_pages = NULL; - } + kfree(gasket_dev->page_table[index]->coherent_pages); + gasket_dev->page_table[index]->coherent_pages = NULL; gasket_dev->page_table[index]->num_coherent_pages = 0; return -ENOMEM; } @@ -1359,6 +1336,11 @@ int gasket_free_coherent_memory(struct gasket_dev *gasket_dev, u64 size, gasket_dev->coherent_buffer.virt_base = NULL; gasket_dev->coherent_buffer.phys_base = 0; } + + kfree(gasket_dev->page_table[index]->coherent_pages); + gasket_dev->page_table[index]->coherent_pages = NULL; + gasket_dev->page_table[index]->num_coherent_pages = 0; + return 0; } |