summaryrefslogtreecommitdiff
path: root/mali_kbase/mali_kbase_mmu.c
diff options
context:
space:
mode:
Diffstat (limited to 'mali_kbase/mali_kbase_mmu.c')
-rw-r--r--mali_kbase/mali_kbase_mmu.c288
1 files changed, 253 insertions, 35 deletions
diff --git a/mali_kbase/mali_kbase_mmu.c b/mali_kbase/mali_kbase_mmu.c
index 1694779..65b7da0 100644
--- a/mali_kbase/mali_kbase_mmu.c
+++ b/mali_kbase/mali_kbase_mmu.c
@@ -7,16 +7,21 @@
* Foundation, and any use by you of this program is subject to the terms
* of such GNU licence.
*
- * A copy of the licence is included with the program, and can also be obtained
- * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you can access it online at
+ * http://www.gnu.org/licenses/gpl-2.0.html.
+ *
+ * SPDX-License-Identifier: GPL-2.0
*
*/
-
-
/**
* @file mali_kbase_mmu.c
* Base kernel MMU management.
@@ -100,32 +105,206 @@ static void kbase_mmu_report_fault_and_kill(struct kbase_context *kctx,
/**
- * make_multiple() - Calculate the nearest integral multiple of a given number.
+ * reg_grow_calc_extra_pages() - Calculate the number of backed pages to add to
+ * a region on a GPU page fault
*
- * @minimum: The number to round up.
- * @multiple: The number of which the function shall calculate an integral multiple.
- * @result: The number rounded up to the nearest integral multiple in case of success,
- * or just the number itself in case of failure.
+ * @reg: The region that will be backed with more pages
+ * @fault_rel_pfn: PFN of the fault relative to the start of the region
*
- * Return: 0 in case of success, or -1 in case of failure.
+ * This calculates how much to increase the backing of a region by, based on
+ * where a GPU page fault occurred and the flags in the region.
+ *
+ * This can be more than the minimum number of pages that would reach
+ * @fault_rel_pfn, for example to reduce the overall rate of page fault
+ * interrupts on a region, or to ensure that the end address is aligned.
+ *
+ * Return: the number of backed pages to increase by
*/
-static int make_multiple(size_t minimum, size_t multiple, size_t *result)
+static size_t reg_grow_calc_extra_pages(struct kbase_va_region *reg, size_t fault_rel_pfn)
{
- int err = -1;
+ size_t multiple = reg->extent;
+ size_t reg_current_size = kbase_reg_current_backed_size(reg);
+ size_t minimum_extra = fault_rel_pfn - reg_current_size + 1;
+ size_t remainder;
+
+ if (!multiple) {
+ dev_warn(reg->kctx->kbdev->dev,
+ "VA Region 0x%llx extent was 0, allocator needs to set this properly for KBASE_REG_PF_GROW\n",
+ ((unsigned long long)reg->start_pfn) << PAGE_SHIFT);
+ return minimum_extra;
+ }
+
+ /* Calculate the remainder to subtract from minimum_extra to make it
+ * the desired (rounded down) multiple of the extent.
+ * Depending on reg's flags, the base used for calculating multiples is
+ * different */
+ if (reg->flags & KBASE_REG_TILER_ALIGN_TOP) {
+ /* multiple is based from the top of the initial commit, which
+ * has been allocated in such a way that (start_pfn +
+ * initial_commit) is already aligned to multiple. Hence the
+ * pfn for the end of committed memory will also be aligned to
+ * multiple */
+ size_t initial_commit = reg->initial_commit;
+
+ if (fault_rel_pfn < initial_commit) {
+ /* this case is just to catch in case it's been
+ * recommitted by userspace to be smaller than the
+ * initial commit */
+ minimum_extra = initial_commit - reg_current_size;
+ remainder = 0;
+ } else {
+ /* same as calculating (fault_rel_pfn - initial_commit + 1) */
+ size_t pages_after_initial = minimum_extra + reg_current_size - initial_commit;
+
+ remainder = pages_after_initial % multiple;
+ }
+ } else {
+ /* multiple is based from the current backed size, even if the
+ * current backed size/pfn for end of committed memory are not
+ * themselves aligned to multiple */
+ remainder = minimum_extra % multiple;
+ }
+
+ if (remainder == 0)
+ return minimum_extra;
+
+ return minimum_extra + multiple - remainder;
+}
+
+#ifdef CONFIG_MALI_JOB_DUMP
+static void kbase_gpu_mmu_handle_write_faulting_as(struct kbase_context *kctx,
+ struct kbase_device *kbdev,
+ struct kbase_as *faulting_as,
+ u64 start_pfn, size_t nr, u32 op)
+{
+ mutex_lock(&kbdev->mmu_hw_mutex);
+
+ kbase_mmu_hw_clear_fault(kbdev, faulting_as, kctx,
+ KBASE_MMU_FAULT_TYPE_PAGE);
+ kbase_mmu_hw_do_operation(kbdev, faulting_as, kctx, start_pfn,
+ nr, op, 1);
+
+ mutex_unlock(&kbdev->mmu_hw_mutex);
+
+ kbase_mmu_hw_enable_fault(kbdev, faulting_as, kctx,
+ KBASE_MMU_FAULT_TYPE_PAGE);
+}
+
+static void kbase_gpu_mmu_handle_write_fault(struct kbase_context *kctx,
+ struct kbase_as *faulting_as)
+{
+ struct kbasep_gwt_list_element *pos;
+ struct kbase_va_region *region;
+ struct kbase_device *kbdev;
+ u64 fault_pfn, pfn_offset;
+ u32 op;
+ int ret;
+ int as_no;
- *result = minimum;
+ as_no = faulting_as->number;
+ kbdev = container_of(faulting_as, struct kbase_device, as[as_no]);
+ fault_pfn = faulting_as->fault_addr >> PAGE_SHIFT;
- if (multiple > 0) {
- size_t remainder = minimum % multiple;
+ kbase_gpu_vm_lock(kctx);
- if (remainder != 0)
- *result = minimum + multiple - remainder;
- err = 0;
+ /* Find region and check if it should be writable. */
+ region = kbase_region_tracker_find_region_enclosing_address(kctx,
+ faulting_as->fault_addr);
+ if (!region || region->flags & KBASE_REG_FREE) {
+ kbase_gpu_vm_unlock(kctx);
+ kbase_mmu_report_fault_and_kill(kctx, faulting_as,
+ "Memory is not mapped on the GPU");
+ return;
}
- return err;
+ if (!(region->flags & KBASE_REG_GPU_WR)) {
+ kbase_gpu_vm_unlock(kctx);
+ kbase_mmu_report_fault_and_kill(kctx, faulting_as,
+ "Region does not have write permissions");
+ return;
+ }
+
+ /* Capture handle and offset of the faulting write location
+ * for job dumping if write tracking is enabled.
+ */
+ if (kctx->gwt_enabled) {
+ u64 page_addr = faulting_as->fault_addr & PAGE_MASK;
+ u64 offset = (page_addr >> PAGE_SHIFT) - region->start_pfn;
+ u64 handle = region->start_pfn << PAGE_SHIFT;
+ bool found = false;
+
+ if (KBASE_MEM_TYPE_IMPORTED_UMM == region->cpu_alloc->type)
+ handle |= BIT(0);
+
+ /* Check if this write was already handled. */
+ list_for_each_entry(pos, &kctx->gwt_current_list, link) {
+ if (handle == pos->handle &&
+ offset >= pos->offset &&
+ offset < pos->offset + pos->num_pages) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ pos = kmalloc(sizeof(*pos), GFP_KERNEL);
+ if (pos) {
+ pos->handle = handle;
+ pos->offset = offset;
+ pos->num_pages = 1;
+ list_add(&pos->link, &kctx->gwt_current_list);
+ } else {
+ dev_warn(kbdev->dev, "kmalloc failure");
+ }
+ }
+ }
+
+ pfn_offset = fault_pfn - region->start_pfn;
+ /* Now make this faulting page writable to GPU. */
+ ret = kbase_mmu_update_pages_no_flush(kctx, fault_pfn,
+ &kbase_get_gpu_phy_pages(region)[pfn_offset],
+ 1, region->flags);
+
+ /* flush L2 and unlock the VA (resumes the MMU) */
+ if (kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_6367))
+ op = AS_COMMAND_FLUSH;
+ else
+ op = AS_COMMAND_FLUSH_PT;
+
+ kbase_gpu_mmu_handle_write_faulting_as(kctx, kbdev, faulting_as,
+ fault_pfn, 1, op);
+
+ kbase_gpu_vm_unlock(kctx);
}
+static void kbase_gpu_mmu_handle_permission_fault(struct kbase_context *kctx,
+ struct kbase_as *faulting_as)
+{
+ u32 fault_status;
+
+ fault_status = faulting_as->fault_status;
+
+ switch (fault_status & AS_FAULTSTATUS_ACCESS_TYPE_MASK) {
+ case AS_FAULTSTATUS_ACCESS_TYPE_ATOMIC:
+ case AS_FAULTSTATUS_ACCESS_TYPE_WRITE:
+ kbase_gpu_mmu_handle_write_fault(kctx, faulting_as);
+ break;
+ case AS_FAULTSTATUS_ACCESS_TYPE_EX:
+ kbase_mmu_report_fault_and_kill(kctx, faulting_as,
+ "Execute Permission fault");
+ break;
+ case AS_FAULTSTATUS_ACCESS_TYPE_READ:
+ kbase_mmu_report_fault_and_kill(kctx, faulting_as,
+ "Read Permission fault");
+ break;
+ default:
+ kbase_mmu_report_fault_and_kill(kctx, faulting_as,
+ "Unknown Permission fault");
+ break;
+ }
+}
+#endif
+
void page_fault_worker(struct work_struct *data)
{
u64 fault_pfn;
@@ -175,6 +354,17 @@ void page_fault_worker(struct work_struct *data)
break;
case AS_FAULTSTATUS_EXCEPTION_CODE_PERMISSION_FAULT:
+#ifdef CONFIG_MALI_JOB_DUMP
+ /* If GWT was ever enabled then we need to handle
+ * write fault pages even if the feature was disabled later.
+ */
+ if (kctx->gwt_was_enabled) {
+ kbase_gpu_mmu_handle_permission_fault(kctx,
+ faulting_as);
+ goto fault_done;
+ }
+#endif
+
kbase_mmu_report_fault_and_kill(kctx, faulting_as,
"Permission failure");
goto fault_done;
@@ -283,10 +473,7 @@ void page_fault_worker(struct work_struct *data)
goto fault_done;
}
- err = make_multiple(fault_rel_pfn - kbase_reg_current_backed_size(region) + 1,
- region->extent,
- &new_pages);
- WARN_ON(err);
+ new_pages = reg_grow_calc_extra_pages(region, fault_rel_pfn);
/* cap to max vsize */
new_pages = min(new_pages, region->nr_pages - kbase_reg_current_backed_size(region));
@@ -392,6 +579,25 @@ void page_fault_worker(struct work_struct *data)
/* reenable this in the mask */
kbase_mmu_hw_enable_fault(kbdev, faulting_as, kctx,
KBASE_MMU_FAULT_TYPE_PAGE);
+
+#ifdef CONFIG_MALI_JOB_DUMP
+ if (kctx->gwt_enabled) {
+ /* GWT also tracks growable regions. */
+ struct kbasep_gwt_list_element *pos;
+
+ pos = kmalloc(sizeof(*pos), GFP_KERNEL);
+ if (pos) {
+ pos->handle = region->start_pfn << PAGE_SHIFT;
+ pos->offset = pfn_offset;
+ pos->num_pages = new_pages;
+ list_add(&pos->link,
+ &kctx->gwt_current_list);
+
+ } else {
+ dev_warn(kbdev->dev, "kmalloc failure");
+ }
+ }
+#endif
kbase_gpu_vm_unlock(kctx);
} else {
/* failed to extend, handle as a normal PF */
@@ -853,9 +1059,16 @@ int kbase_mmu_insert_pages_no_flush(struct kbase_context *kctx,
u64 *target = &pgd_page[ofs];
/* Fail if the current page is a valid ATE entry
+ * unless gwt_was_enabled as in that case all
+ * pages will be valid from when
+ * kbase_gpu_gwt_start() cleared the gpu
+ * write flag.
*/
- KBASE_DEBUG_ASSERT(0 == (*target & 1UL));
-
+#ifdef CONFIG_MALI_JOB_DUMP
+ if (!kctx->gwt_was_enabled)
+#endif
+ KBASE_DEBUG_ASSERT
+ (0 == (*target & 1UL));
kctx->kbdev->mmu_mode->entry_set_ate(target,
phys[i], flags, cur_level);
}
@@ -1217,13 +1430,12 @@ KBASE_EXPORT_TEST_API(kbase_mmu_teardown_pages);
* already held by the caller. Refer to kbasep_js_runpool_release_ctx() for more
* information.
*/
-int kbase_mmu_update_pages(struct kbase_context *kctx, u64 vpfn,
- struct tagged_addr *phys, size_t nr,
- unsigned long flags)
+int kbase_mmu_update_pages_no_flush(struct kbase_context *kctx, u64 vpfn,
+ struct tagged_addr *phys, size_t nr,
+ unsigned long flags)
{
phys_addr_t pgd;
u64 *pgd_page;
- size_t requested_nr = nr;
struct kbase_mmu_mode const *mmu_mode;
int err;
@@ -1239,9 +1451,6 @@ int kbase_mmu_update_pages(struct kbase_context *kctx, u64 vpfn,
mmu_mode = kctx->kbdev->mmu_mode;
- dev_warn(kctx->kbdev->dev, "kbase_mmu_update_pages(): updating page share flags on GPU PFN 0x%llx from phys %p, %zu pages",
- vpfn, phys, nr);
-
while (nr) {
unsigned int i;
unsigned int index = vpfn & 0x1FF;
@@ -1293,12 +1502,21 @@ int kbase_mmu_update_pages(struct kbase_context *kctx, u64 vpfn,
}
mutex_unlock(&kctx->mmu_lock);
- kbase_mmu_flush_invalidate(kctx, vpfn, requested_nr, true);
return 0;
fail_unlock:
mutex_unlock(&kctx->mmu_lock);
- kbase_mmu_flush_invalidate(kctx, vpfn, requested_nr, true);
+ return err;
+}
+
+int kbase_mmu_update_pages(struct kbase_context *kctx, u64 vpfn,
+ struct tagged_addr *phys, size_t nr,
+ unsigned long flags)
+{
+ int err;
+
+ err = kbase_mmu_update_pages_no_flush(kctx, vpfn, phys, nr, flags);
+ kbase_mmu_flush_invalidate(kctx, vpfn, nr, true);
return err;
}