| /* | |
| * | |
| * (C) COPYRIGHT 2010-2019 ARM Limited. All rights reserved. | |
| * | |
| * This program is free software and is provided to you under the terms of the | |
| * GNU General Public License version 2 as published by the Free Software | |
| * Foundation, and any use by you of this program is subject to the terms | |
| * of such GNU licence. | |
| * | |
| * 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 | |
| * | |
| */ | |
| #include "mali_kbase_gwt.h" | |
| #include <linux/list_sort.h> | |
| static inline void kbase_gpu_gwt_setup_page_permission( | |
| struct kbase_context *kctx, | |
| unsigned long flag, | |
| struct rb_node *node) | |
| { | |
| struct rb_node *rbnode = node; | |
| while (rbnode) { | |
| struct kbase_va_region *reg; | |
| int err = 0; | |
| reg = rb_entry(rbnode, struct kbase_va_region, rblink); | |
| if (reg->nr_pages && !kbase_is_region_invalid_or_free(reg) && | |
| (reg->flags & KBASE_REG_GPU_WR)) { | |
| err = kbase_mmu_update_pages(kctx, reg->start_pfn, | |
| kbase_get_gpu_phy_pages(reg), | |
| reg->gpu_alloc->nents, | |
| reg->flags & flag, | |
| reg->gpu_alloc->group_id); | |
| if (err) | |
| dev_warn(kctx->kbdev->dev, "kbase_mmu_update_pages failure\n"); | |
| } | |
| rbnode = rb_next(rbnode); | |
| } | |
| } | |
| static void kbase_gpu_gwt_setup_pages(struct kbase_context *kctx, | |
| unsigned long flag) | |
| { | |
| kbase_gpu_gwt_setup_page_permission(kctx, flag, | |
| rb_first(&(kctx->reg_rbtree_same))); | |
| kbase_gpu_gwt_setup_page_permission(kctx, flag, | |
| rb_first(&(kctx->reg_rbtree_custom))); | |
| } | |
| int kbase_gpu_gwt_start(struct kbase_context *kctx) | |
| { | |
| kbase_gpu_vm_lock(kctx); | |
| if (kctx->gwt_enabled) { | |
| kbase_gpu_vm_unlock(kctx); | |
| return -EBUSY; | |
| } | |
| INIT_LIST_HEAD(&kctx->gwt_current_list); | |
| INIT_LIST_HEAD(&kctx->gwt_snapshot_list); | |
| /* If GWT is enabled using new vector dumping format | |
| * from user space, back up status of the job serialization flag and | |
| * use full serialisation of jobs for dumping. | |
| * Status will be restored on end of dumping in gwt_stop. | |
| */ | |
| kctx->kbdev->backup_serialize_jobs = kctx->kbdev->serialize_jobs; | |
| kctx->kbdev->serialize_jobs = KBASE_SERIALIZE_INTRA_SLOT | | |
| KBASE_SERIALIZE_INTER_SLOT; | |
| /* Mark gwt enabled before making pages read only in case a | |
| write page fault is triggered while we're still in this loop. | |
| (kbase_gpu_vm_lock() doesn't prevent this!) | |
| */ | |
| kctx->gwt_enabled = true; | |
| kctx->gwt_was_enabled = true; | |
| kbase_gpu_gwt_setup_pages(kctx, ~KBASE_REG_GPU_WR); | |
| kbase_gpu_vm_unlock(kctx); | |
| return 0; | |
| } | |
| int kbase_gpu_gwt_stop(struct kbase_context *kctx) | |
| { | |
| struct kbasep_gwt_list_element *pos, *n; | |
| kbase_gpu_vm_lock(kctx); | |
| if (!kctx->gwt_enabled) { | |
| kbase_gpu_vm_unlock(kctx); | |
| return -EINVAL; | |
| } | |
| list_for_each_entry_safe(pos, n, &kctx->gwt_current_list, link) { | |
| list_del(&pos->link); | |
| kfree(pos); | |
| } | |
| list_for_each_entry_safe(pos, n, &kctx->gwt_snapshot_list, link) { | |
| list_del(&pos->link); | |
| kfree(pos); | |
| } | |
| kctx->kbdev->serialize_jobs = kctx->kbdev->backup_serialize_jobs; | |
| kbase_gpu_gwt_setup_pages(kctx, ~0UL); | |
| kctx->gwt_enabled = false; | |
| kbase_gpu_vm_unlock(kctx); | |
| return 0; | |
| } | |
| static int list_cmp_function(void *priv, struct list_head *a, | |
| struct list_head *b) | |
| { | |
| struct kbasep_gwt_list_element *elementA = container_of(a, | |
| struct kbasep_gwt_list_element, link); | |
| struct kbasep_gwt_list_element *elementB = container_of(b, | |
| struct kbasep_gwt_list_element, link); | |
| CSTD_UNUSED(priv); | |
| if (elementA->page_addr > elementB->page_addr) | |
| return 1; | |
| return -1; | |
| } | |
| static void kbase_gpu_gwt_collate(struct kbase_context *kctx, | |
| struct list_head *snapshot_list) | |
| { | |
| struct kbasep_gwt_list_element *pos, *n; | |
| struct kbasep_gwt_list_element *collated = NULL; | |
| /* Sort the list */ | |
| list_sort(NULL, snapshot_list, list_cmp_function); | |
| /* Combine contiguous areas. */ | |
| list_for_each_entry_safe(pos, n, snapshot_list, link) { | |
| if (collated == NULL || collated->region != | |
| pos->region || | |
| (collated->page_addr + | |
| (collated->num_pages * PAGE_SIZE)) != | |
| pos->page_addr) { | |
| /* This is the first time through, a new region or | |
| * is not contiguous - start collating to this element | |
| */ | |
| collated = pos; | |
| } else { | |
| /* contiguous so merge */ | |
| collated->num_pages += pos->num_pages; | |
| /* remove element from list */ | |
| list_del(&pos->link); | |
| kfree(pos); | |
| } | |
| } | |
| } | |
| int kbase_gpu_gwt_dump(struct kbase_context *kctx, | |
| union kbase_ioctl_cinstr_gwt_dump *gwt_dump) | |
| { | |
| const u32 ubuf_size = gwt_dump->in.len; | |
| u32 ubuf_count = 0; | |
| __user void *user_addr = (__user void *) | |
| (uintptr_t)gwt_dump->in.addr_buffer; | |
| __user void *user_sizes = (__user void *) | |
| (uintptr_t)gwt_dump->in.size_buffer; | |
| kbase_gpu_vm_lock(kctx); | |
| if (!kctx->gwt_enabled) { | |
| kbase_gpu_vm_unlock(kctx); | |
| /* gwt_dump shouldn't be called when gwt is disabled */ | |
| return -EPERM; | |
| } | |
| if (!gwt_dump->in.len || !gwt_dump->in.addr_buffer | |
| || !gwt_dump->in.size_buffer) { | |
| kbase_gpu_vm_unlock(kctx); | |
| /* We don't have any valid user space buffer to copy the | |
| * write modified addresses. | |
| */ | |
| return -EINVAL; | |
| } | |
| if (list_empty(&kctx->gwt_snapshot_list) && | |
| !list_empty(&kctx->gwt_current_list)) { | |
| list_replace_init(&kctx->gwt_current_list, | |
| &kctx->gwt_snapshot_list); | |
| /* We have collected all write faults so far | |
| * and they will be passed on to user space. | |
| * Reset the page flags state to allow collection of | |
| * further write faults. | |
| */ | |
| kbase_gpu_gwt_setup_pages(kctx, ~KBASE_REG_GPU_WR); | |
| /* Sort and combine consecutive pages in the dump list*/ | |
| kbase_gpu_gwt_collate(kctx, &kctx->gwt_snapshot_list); | |
| } | |
| while ((!list_empty(&kctx->gwt_snapshot_list))) { | |
| u64 addr_buffer[32]; | |
| u64 num_page_buffer[32]; | |
| u32 count = 0; | |
| int err; | |
| struct kbasep_gwt_list_element *dump_info, *n; | |
| list_for_each_entry_safe(dump_info, n, | |
| &kctx->gwt_snapshot_list, link) { | |
| addr_buffer[count] = dump_info->page_addr; | |
| num_page_buffer[count] = dump_info->num_pages; | |
| count++; | |
| list_del(&dump_info->link); | |
| kfree(dump_info); | |
| if (ARRAY_SIZE(addr_buffer) == count || | |
| ubuf_size == (ubuf_count + count)) | |
| break; | |
| } | |
| if (count) { | |
| err = copy_to_user((user_addr + | |
| (ubuf_count * sizeof(u64))), | |
| (void *)addr_buffer, | |
| count * sizeof(u64)); | |
| if (err) { | |
| dev_err(kctx->kbdev->dev, "Copy to user failure\n"); | |
| kbase_gpu_vm_unlock(kctx); | |
| return err; | |
| } | |
| err = copy_to_user((user_sizes + | |
| (ubuf_count * sizeof(u64))), | |
| (void *)num_page_buffer, | |
| count * sizeof(u64)); | |
| if (err) { | |
| dev_err(kctx->kbdev->dev, "Copy to user failure\n"); | |
| kbase_gpu_vm_unlock(kctx); | |
| return err; | |
| } | |
| ubuf_count += count; | |
| } | |
| if (ubuf_count == ubuf_size) | |
| break; | |
| } | |
| if (!list_empty(&kctx->gwt_snapshot_list)) | |
| gwt_dump->out.more_data_available = 1; | |
| else | |
| gwt_dump->out.more_data_available = 0; | |
| gwt_dump->out.no_of_addr_collected = ubuf_count; | |
| kbase_gpu_vm_unlock(kctx); | |
| return 0; | |
| } |