// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note /* * * (C) COPYRIGHT 2011-2023 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 license. * * 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. * */ #include #include #include #if IS_ENABLED(CONFIG_SYNC_FILE) #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if !MALI_USE_CSF /** * DOC: This file implements the logic behind software only jobs that are * executed within the driver rather than being handed over to the GPU. */ static void kbasep_add_waiting_soft_job(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; unsigned long lflags; spin_lock_irqsave(&kctx->waiting_soft_jobs_lock, lflags); list_add_tail(&katom->queue, &kctx->waiting_soft_jobs); spin_unlock_irqrestore(&kctx->waiting_soft_jobs_lock, lflags); } void kbasep_remove_waiting_soft_job(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; unsigned long lflags; spin_lock_irqsave(&kctx->waiting_soft_jobs_lock, lflags); list_del(&katom->queue); spin_unlock_irqrestore(&kctx->waiting_soft_jobs_lock, lflags); } static void kbasep_add_waiting_with_timeout(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; /* Record the start time of this atom so we could cancel it at * the right time. */ katom->start_timestamp = ktime_get_raw(); /* Add the atom to the waiting list before the timer is * (re)started to make sure that it gets processed. */ kbasep_add_waiting_soft_job(katom); /* Schedule timeout of this atom after a period if it is not active */ if (!timer_pending(&kctx->soft_job_timeout)) { unsigned int timeout_ms = (unsigned int)atomic_read(&kctx->kbdev->js_data.soft_job_timeout_ms); mod_timer(&kctx->soft_job_timeout, jiffies + msecs_to_jiffies(timeout_ms)); } } static int kbasep_read_soft_event_status(struct kbase_context *kctx, u64 evt, unsigned char *status) { unsigned char *mapped_evt; struct kbase_vmap_struct map; mapped_evt = kbase_vmap_prot(kctx, evt, sizeof(*mapped_evt), KBASE_REG_CPU_RD, &map); if (!mapped_evt) return -EFAULT; *status = *mapped_evt; kbase_vunmap(kctx, &map); return 0; } static int kbasep_write_soft_event_status(struct kbase_context *kctx, u64 evt, unsigned char new_status) { unsigned char *mapped_evt; struct kbase_vmap_struct map; if ((new_status != BASE_JD_SOFT_EVENT_SET) && (new_status != BASE_JD_SOFT_EVENT_RESET)) return -EINVAL; mapped_evt = kbase_vmap_prot(kctx, evt, sizeof(*mapped_evt), KBASE_REG_CPU_WR, &map); if (!mapped_evt) return -EFAULT; *mapped_evt = new_status; kbase_vunmap(kctx, &map); return 0; } static int kbase_dump_cpu_gpu_time(struct kbase_jd_atom *katom) { struct kbase_vmap_struct map; void *user_result; struct timespec64 ts; struct base_dump_cpu_gpu_counters data; u64 system_time = 0ULL; u64 cycle_counter; u64 jc = katom->jc; struct kbase_context *kctx = katom->kctx; int pm_active_err; memset(&data, 0, sizeof(data)); /* Take the PM active reference as late as possible - otherwise, it could * delay suspend until we process the atom (which may be at the end of a * long chain of dependencies */ #ifdef CONFIG_MALI_ARBITER_SUPPORT atomic_inc(&kctx->kbdev->pm.gpu_users_waiting); #endif /* CONFIG_MALI_ARBITER_SUPPORT */ pm_active_err = kbase_pm_context_active_handle_suspend( kctx->kbdev, KBASE_PM_SUSPEND_HANDLER_DONT_REACTIVATE); if (pm_active_err) { struct kbasep_js_device_data *js_devdata = &kctx->kbdev->js_data; /* We're suspended - queue this on the list of suspended jobs * Use dep_item[1], because dep_item[0] was previously in use * for 'waiting_soft_jobs'. */ mutex_lock(&js_devdata->runpool_mutex); list_add_tail(&katom->dep_item[1], &js_devdata->suspended_soft_jobs_list); mutex_unlock(&js_devdata->runpool_mutex); /* Also adding this to the list of waiting soft job */ kbasep_add_waiting_soft_job(katom); return pm_active_err; } #ifdef CONFIG_MALI_ARBITER_SUPPORT else atomic_dec(&kctx->kbdev->pm.gpu_users_waiting); #endif /* CONFIG_MALI_ARBITER_SUPPORT */ kbase_backend_get_gpu_time(kctx->kbdev, &cycle_counter, &system_time, &ts); kbase_pm_context_idle(kctx->kbdev); data.sec = (__u64)ts.tv_sec; data.usec = ts.tv_nsec / 1000; data.system_time = system_time; data.cycle_counter = cycle_counter; /* Assume this atom will be cancelled until we know otherwise */ katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; /* GPU_WR access is checked on the range for returning the result to * userspace for the following reasons: * - security, this is currently how imported user bufs are checked. * - userspace ddk guaranteed to assume region was mapped as GPU_WR */ user_result = kbase_vmap_prot(kctx, jc, sizeof(data), KBASE_REG_GPU_WR, &map); if (!user_result) return 0; memcpy(user_result, &data, sizeof(data)); kbase_vunmap(kctx, &map); /* Atom was fine - mark it as done */ katom->event_code = BASE_JD_EVENT_DONE; return 0; } #if IS_ENABLED(CONFIG_SYNC_FILE) /* Called by the explicit fence mechanism when a fence wait has completed */ void kbase_soft_event_wait_callback(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; rt_mutex_lock(&kctx->jctx.lock); kbasep_remove_waiting_soft_job(katom); kbase_finish_soft_job(katom); if (kbase_jd_done_nolock(katom, true)) kbase_js_sched_all(kctx->kbdev); rt_mutex_unlock(&kctx->jctx.lock); } #endif static void kbasep_soft_event_complete_job(struct kthread_work *work) { struct kbase_jd_atom *katom = container_of(work, struct kbase_jd_atom, work); struct kbase_context *kctx = katom->kctx; int resched; rt_mutex_lock(&kctx->jctx.lock); resched = kbase_jd_done_nolock(katom, true); rt_mutex_unlock(&kctx->jctx.lock); if (resched) kbase_js_sched_all(kctx->kbdev); } void kbasep_complete_triggered_soft_events(struct kbase_context *kctx, u64 evt) { int cancel_timer = 1; struct list_head *entry, *tmp; unsigned long lflags; spin_lock_irqsave(&kctx->waiting_soft_jobs_lock, lflags); list_for_each_safe(entry, tmp, &kctx->waiting_soft_jobs) { struct kbase_jd_atom *katom = list_entry(entry, struct kbase_jd_atom, queue); switch (katom->core_req & BASE_JD_REQ_SOFT_JOB_TYPE) { case BASE_JD_REQ_SOFT_EVENT_WAIT: if (katom->jc == evt) { list_del(&katom->queue); katom->event_code = BASE_JD_EVENT_DONE; kthread_init_work(&katom->work, kbasep_soft_event_complete_job); kthread_queue_work(&kctx->kbdev->job_done_worker, &katom->work); } else { /* There are still other waiting jobs, we cannot * cancel the timer yet. */ cancel_timer = 0; } break; #ifdef CONFIG_MALI_FENCE_DEBUG case BASE_JD_REQ_SOFT_FENCE_WAIT: /* Keep the timer running if fence debug is enabled and * there are waiting fence jobs. */ cancel_timer = 0; break; #endif } } if (cancel_timer) del_timer(&kctx->soft_job_timeout); spin_unlock_irqrestore(&kctx->waiting_soft_jobs_lock, lflags); } #ifdef CONFIG_MALI_FENCE_DEBUG static void kbase_fence_debug_check_atom(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; struct device *dev = kctx->kbdev->dev; int i; for (i = 0; i < 2; i++) { struct kbase_jd_atom *dep; list_for_each_entry(dep, &katom->dep_head[i], dep_item[i]) { if (dep->status == KBASE_JD_ATOM_STATE_UNUSED || dep->status == KBASE_JD_ATOM_STATE_COMPLETED) continue; if ((dep->core_req & BASE_JD_REQ_SOFT_JOB_TYPE) == BASE_JD_REQ_SOFT_FENCE_TRIGGER) { /* Found blocked trigger fence. */ struct kbase_sync_fence_info info; if (!kbase_sync_fence_in_info_get(dep, &info)) { dev_warn(dev, "\tVictim trigger atom %d fence [%pK] %s: %s\n", kbase_jd_atom_id(kctx, dep), info.fence, info.name, kbase_sync_status_string(info.status)); } } kbase_fence_debug_check_atom(dep); } } } static void kbase_fence_debug_wait_timeout(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; struct device *dev = katom->kctx->kbdev->dev; int timeout_ms = atomic_read(&kctx->kbdev->js_data.soft_job_timeout_ms); unsigned long lflags; struct kbase_sync_fence_info info; spin_lock_irqsave(&kctx->waiting_soft_jobs_lock, lflags); if (kbase_sync_fence_in_info_get(katom, &info)) { /* Fence must have signaled just after timeout. */ spin_unlock_irqrestore(&kctx->waiting_soft_jobs_lock, lflags); return; } dev_warn(dev, "ctx %d_%d: Atom %d still waiting for fence [%pK] after %dms\n", kctx->tgid, kctx->id, kbase_jd_atom_id(kctx, katom), info.fence, timeout_ms); dev_warn(dev, "\tGuilty fence [%pK] %s: %s\n", info.fence, info.name, kbase_sync_status_string(info.status)); /* Search for blocked trigger atoms */ kbase_fence_debug_check_atom(katom); spin_unlock_irqrestore(&kctx->waiting_soft_jobs_lock, lflags); kbase_sync_fence_in_dump(katom); } struct kbase_fence_debug_work { struct kbase_jd_atom *katom; struct work_struct work; }; static void kbase_fence_debug_wait_timeout_worker(struct kthread_work *work) { struct kbase_fence_debug_work *w = container_of(work, struct kbase_fence_debug_work, work); struct kbase_jd_atom *katom = w->katom; struct kbase_context *kctx = katom->kctx; rt_mutex_lock(&kctx->jctx.lock); kbase_fence_debug_wait_timeout(katom); rt_mutex_unlock(&kctx->jctx.lock); kfree(w); } static void kbase_fence_debug_timeout(struct kbase_jd_atom *katom) { struct kbase_fence_debug_work *work; struct kbase_context *kctx = katom->kctx; /* Enqueue fence debug worker. Use job_done_worker to get * debug print ordered with job completion. */ work = kzalloc(sizeof(struct kbase_fence_debug_work), GFP_ATOMIC); /* Ignore allocation failure. */ if (work) { work->katom = katom; kthread_init_work(&work->work, kbase_fence_debug_wait_timeout_worker); kthread_queue_work(&kctx->kbdev->job_done_worker, &work->work); } } #endif /* CONFIG_MALI_FENCE_DEBUG */ void kbasep_soft_job_timeout_worker(struct timer_list *timer) { struct kbase_context *kctx = container_of(timer, struct kbase_context, soft_job_timeout); u32 timeout_ms = (u32)atomic_read(&kctx->kbdev->js_data.soft_job_timeout_ms); ktime_t cur_time = ktime_get_raw(); bool restarting = false; unsigned long lflags; struct list_head *entry, *tmp; spin_lock_irqsave(&kctx->waiting_soft_jobs_lock, lflags); list_for_each_safe(entry, tmp, &kctx->waiting_soft_jobs) { struct kbase_jd_atom *katom = list_entry(entry, struct kbase_jd_atom, queue); s64 elapsed_time = ktime_to_ms(ktime_sub(cur_time, katom->start_timestamp)); if (elapsed_time < (s64)timeout_ms) { restarting = true; continue; } switch (katom->core_req & BASE_JD_REQ_SOFT_JOB_TYPE) { case BASE_JD_REQ_SOFT_EVENT_WAIT: /* Take it out of the list to ensure that it * will be cancelled in all cases */ list_del(&katom->queue); katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; kthread_init_work(&katom->work, kbasep_soft_event_complete_job); kthread_queue_work(&kctx->kbdev->job_done_worker, &katom->work); break; #ifdef CONFIG_MALI_FENCE_DEBUG case BASE_JD_REQ_SOFT_FENCE_WAIT: kbase_fence_debug_timeout(katom); break; #endif } } if (restarting) mod_timer(timer, jiffies + msecs_to_jiffies(timeout_ms)); spin_unlock_irqrestore(&kctx->waiting_soft_jobs_lock, lflags); } static int kbasep_soft_event_wait(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; unsigned char status; /* The status of this soft-job is stored in jc */ if (kbasep_read_soft_event_status(kctx, katom->jc, &status)) { katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; return 0; } if (status == BASE_JD_SOFT_EVENT_SET) return 0; /* Event already set, nothing to do */ kbasep_add_waiting_with_timeout(katom); return 1; } static void kbasep_soft_event_update_locked(struct kbase_jd_atom *katom, unsigned char new_status) { /* Complete jobs waiting on the same event */ struct kbase_context *kctx = katom->kctx; if (kbasep_write_soft_event_status(kctx, katom->jc, new_status) != 0) { katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; return; } if (new_status == BASE_JD_SOFT_EVENT_SET) kbasep_complete_triggered_soft_events(kctx, katom->jc); } /** * kbase_soft_event_update() - Update soft event state * @kctx: Pointer to context * @event: Event to update * @new_status: New status value of event * * Update the event, and wake up any atoms waiting for the event. * * Return: 0 on success, a negative error code on failure. */ int kbase_soft_event_update(struct kbase_context *kctx, u64 event, unsigned char new_status) { int err = 0; rt_mutex_lock(&kctx->jctx.lock); if (kbasep_write_soft_event_status(kctx, event, new_status)) { err = -ENOENT; goto out; } if (new_status == BASE_JD_SOFT_EVENT_SET) kbasep_complete_triggered_soft_events(kctx, event); out: rt_mutex_unlock(&kctx->jctx.lock); return err; } static void kbasep_soft_event_cancel_job(struct kbase_jd_atom *katom) { katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; if (kbase_jd_done_nolock(katom, true)) kbase_js_sched_all(katom->kctx->kbdev); } #if IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST static void kbase_debug_copy_finish(struct kbase_jd_atom *katom) { struct kbase_debug_copy_buffer *buffers = katom->softjob_data; unsigned int i; unsigned int nr = katom->nr_extres; if (!buffers) return; kbase_gpu_vm_lock(katom->kctx); for (i = 0; i < nr; i++) { uint p; struct kbase_mem_phy_alloc *gpu_alloc = buffers[i].gpu_alloc; if (!buffers[i].pages) break; for (p = 0; p < buffers[i].nr_pages; p++) { struct page *pg = buffers[i].pages[p]; if (pg) put_page(pg); } if (buffers[i].is_vmalloc) vfree(buffers[i].pages); else kfree(buffers[i].pages); if (gpu_alloc) { switch (gpu_alloc->type) { case KBASE_MEM_TYPE_IMPORTED_USER_BUF: { kbase_free_user_buffer(&buffers[i]); break; } default: /* Nothing to be done. */ break; } kbase_mem_phy_alloc_put(gpu_alloc); } } kbase_gpu_vm_unlock(katom->kctx); kfree(buffers); katom->softjob_data = NULL; } static int kbase_debug_copy_prepare(struct kbase_jd_atom *katom) { struct kbase_debug_copy_buffer *buffers; struct base_jd_debug_copy_buffer *user_buffers = NULL; unsigned int i; unsigned int nr = katom->nr_extres; int ret = 0; void __user *user_structs = (void __user *)(uintptr_t)katom->jc; if (!user_structs) return -EINVAL; buffers = kcalloc(nr, sizeof(*buffers), GFP_KERNEL); if (!buffers) { ret = -ENOMEM; goto out_cleanup; } katom->softjob_data = buffers; user_buffers = kmalloc_array(nr, sizeof(*user_buffers), GFP_KERNEL); if (!user_buffers) { ret = -ENOMEM; goto out_cleanup; } ret = copy_from_user(user_buffers, user_structs, sizeof(*user_buffers) * nr); if (ret) { ret = -EFAULT; goto out_cleanup; } for (i = 0; i < nr; i++) { u64 addr = user_buffers[i].address; u64 page_addr = addr & PAGE_MASK; u64 end_page_addr = addr + user_buffers[i].size - 1; u64 last_page_addr = end_page_addr & PAGE_MASK; size_t nr_pages = (last_page_addr - page_addr) / PAGE_SIZE + 1; int pinned_pages; struct kbase_va_region *reg; struct base_external_resource user_extres; if (!addr) continue; if (last_page_addr < page_addr) { ret = -EINVAL; goto out_cleanup; } buffers[i].nr_pages = nr_pages; buffers[i].offset = addr & ~PAGE_MASK; if (buffers[i].offset >= PAGE_SIZE) { ret = -EINVAL; goto out_cleanup; } buffers[i].size = user_buffers[i].size; if (nr_pages > (KBASE_MEM_PHY_ALLOC_LARGE_THRESHOLD / sizeof(struct page *))) { buffers[i].is_vmalloc = true; buffers[i].pages = vzalloc(nr_pages * sizeof(struct page *)); } else { buffers[i].is_vmalloc = false; buffers[i].pages = kcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL); } if (!buffers[i].pages) { ret = -ENOMEM; goto out_cleanup; } pinned_pages = get_user_pages_fast(page_addr, nr_pages, 1, /* Write */ buffers[i].pages); if (pinned_pages < 0) { /* get_user_pages_fast has failed - page array is not * valid. Don't try to release any pages. */ buffers[i].nr_pages = 0; ret = pinned_pages; goto out_cleanup; } if ((size_t)pinned_pages != nr_pages) { /* Adjust number of pages, so that we only attempt to * release pages in the array that we know are valid. */ buffers[i].nr_pages = (unsigned int)pinned_pages; ret = -EINVAL; goto out_cleanup; } user_extres = user_buffers[i].extres; if (user_extres.ext_resource == 0ULL) { ret = -EINVAL; goto out_cleanup; } kbase_gpu_vm_lock(katom->kctx); reg = kbase_region_tracker_find_region_enclosing_address( katom->kctx, user_extres.ext_resource & ~(__u64)BASE_EXT_RES_ACCESS_EXCLUSIVE); if (kbase_is_region_invalid_or_free(reg) || reg->gpu_alloc == NULL) { ret = -EINVAL; goto out_unlock; } buffers[i].gpu_alloc = kbase_mem_phy_alloc_get(reg->gpu_alloc); buffers[i].nr_extres_pages = reg->nr_pages; if (reg->nr_pages * PAGE_SIZE != buffers[i].size) dev_warn( katom->kctx->kbdev->dev, "Copy buffer is not of same size as the external resource to copy.\n"); switch (reg->gpu_alloc->type) { case KBASE_MEM_TYPE_IMPORTED_USER_BUF: { struct kbase_mem_phy_alloc *alloc = reg->gpu_alloc; const unsigned long nr_pages = alloc->imported.user_buf.nr_pages; const unsigned long start = alloc->imported.user_buf.address; if (alloc->imported.user_buf.mm != current->mm) { ret = -EINVAL; goto out_unlock; } buffers[i].extres_pages = kcalloc(nr_pages, sizeof(struct page *), GFP_KERNEL); if (!buffers[i].extres_pages) { ret = -ENOMEM; goto out_unlock; } kbase_gpu_vm_unlock(katom->kctx); ret = get_user_pages_fast(start, nr_pages, 0, buffers[i].extres_pages); kbase_gpu_vm_lock(katom->kctx); if (ret != (int)nr_pages) { /* Adjust number of pages, so that we only * attempt to release pages in the array that we * know are valid. */ if (ret < 0) buffers[i].nr_extres_pages = 0; else buffers[i].nr_extres_pages = (unsigned int)ret; goto out_unlock; } ret = 0; break; } default: /* Nothing to be done. */ break; } kbase_gpu_vm_unlock(katom->kctx); } kfree(user_buffers); return ret; out_unlock: kbase_gpu_vm_unlock(katom->kctx); out_cleanup: /* Frees allocated memory for kbase_debug_copy_job struct, including * members, and sets jc to 0 */ kbase_debug_copy_finish(katom); kfree(user_buffers); return ret; } #if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE static void *dma_buf_kmap_page(struct kbase_mem_phy_alloc *gpu_alloc, unsigned long page_num, struct page **page) { struct sg_table *sgt = gpu_alloc->imported.umm.sgt; struct sg_page_iter sg_iter; unsigned long page_index = 0; if (WARN_ON(gpu_alloc->type != KBASE_MEM_TYPE_IMPORTED_UMM)) return NULL; if (!sgt) return NULL; if (WARN_ON(page_num >= gpu_alloc->nents)) return NULL; for_each_sg_page(sgt->sgl, &sg_iter, sgt->nents, 0) { if (page_index == page_num) { *page = sg_page_iter_page(&sg_iter); return kbase_kmap(*page); } page_index++; } return NULL; } #endif /** * kbase_mem_copy_from_extres() - Copy from external resources. * * @kctx: kbase context within which the copying is to take place. * @buf_data: Pointer to the information about external resources: * pages pertaining to the external resource, number of * pages to copy. * * Return: 0 on success, error code otherwise. */ static int kbase_mem_copy_from_extres(struct kbase_context *kctx, struct kbase_debug_copy_buffer *buf_data) { unsigned int i; unsigned int target_page_nr = 0; struct page **pages = buf_data->pages; u64 offset = buf_data->offset; size_t extres_size = buf_data->nr_extres_pages * PAGE_SIZE; size_t to_copy = min(extres_size, buf_data->size); struct kbase_mem_phy_alloc *gpu_alloc = buf_data->gpu_alloc; int ret = 0; size_t dma_to_copy; KBASE_DEBUG_ASSERT(pages != NULL); kbase_gpu_vm_lock(kctx); if (!gpu_alloc) { ret = -EINVAL; goto out_unlock; } switch (gpu_alloc->type) { case KBASE_MEM_TYPE_IMPORTED_USER_BUF: { for (i = 0; i < buf_data->nr_extres_pages && target_page_nr < buf_data->nr_pages; i++) { struct page *pg = buf_data->extres_pages[i]; void *extres_page = kbase_kmap(pg); if (extres_page) { ret = kbase_mem_copy_to_pinned_user_pages(pages, extres_page, &to_copy, buf_data->nr_pages, &target_page_nr, offset); kbase_kunmap(pg, extres_page); if (ret) goto out_unlock; } } } break; case KBASE_MEM_TYPE_IMPORTED_UMM: { struct dma_buf *dma_buf = gpu_alloc->imported.umm.dma_buf; KBASE_DEBUG_ASSERT(dma_buf != NULL); if (dma_buf->size > buf_data->nr_extres_pages * PAGE_SIZE) dev_warn(kctx->kbdev->dev, "External resources buffer size mismatch"); dma_to_copy = min(dma_buf->size, (size_t)(buf_data->nr_extres_pages * PAGE_SIZE)); ret = dma_buf_begin_cpu_access(dma_buf, DMA_FROM_DEVICE); if (ret) goto out_unlock; for (i = 0; i < dma_to_copy / PAGE_SIZE && target_page_nr < buf_data->nr_pages; i++) { #if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE struct page *pg; void *extres_page = dma_buf_kmap_page(gpu_alloc, i, &pg); #else void *extres_page = dma_buf_kmap(dma_buf, i); #endif if (extres_page) { ret = kbase_mem_copy_to_pinned_user_pages(pages, extres_page, &to_copy, buf_data->nr_pages, &target_page_nr, offset); #if KERNEL_VERSION(5, 6, 0) <= LINUX_VERSION_CODE kbase_kunmap(pg, extres_page); #else dma_buf_kunmap(dma_buf, i, extres_page); #endif if (ret) break; } } dma_buf_end_cpu_access(dma_buf, DMA_FROM_DEVICE); break; } default: ret = -EINVAL; } out_unlock: kbase_gpu_vm_unlock(kctx); return ret; } static int kbase_debug_copy(struct kbase_jd_atom *katom) { struct kbase_debug_copy_buffer *buffers = katom->softjob_data; unsigned int i; if (WARN_ON(!buffers)) return -EINVAL; for (i = 0; i < katom->nr_extres; i++) { int res = kbase_mem_copy_from_extres(katom->kctx, &buffers[i]); if (res) return res; } return 0; } #endif /* IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST */ #endif /* !MALI_USE_CSF */ #define KBASEP_JIT_ALLOC_GPU_ADDR_ALIGNMENT ((u32)0x7) int kbasep_jit_alloc_validate(struct kbase_context *kctx, struct base_jit_alloc_info *info) { size_t j; CSTD_UNUSED(kctx); /* If the ID is zero, then fail the job */ if (info->id == 0) return -EINVAL; /* Sanity check that the PA fits within the VA */ if (info->va_pages < info->commit_pages) return -EINVAL; /* Ensure the GPU address is correctly aligned */ if ((info->gpu_alloc_addr & KBASEP_JIT_ALLOC_GPU_ADDR_ALIGNMENT) != 0) return -EINVAL; /* Interface version 2 (introduced with kernel driver version 11.5) * onward has padding and a flags member to validate. * * Note: To support earlier versions the extra bytes will have been set * to 0 by the caller. */ /* Check padding is all zeroed */ for (j = 0; j < sizeof(info->padding); j++) { if (info->padding[j] != 0) return -EINVAL; } /* Only valid flags shall be set */ if (info->flags & ~(BASE_JIT_ALLOC_VALID_FLAGS)) return -EINVAL; #if !MALI_JIT_PRESSURE_LIMIT_BASE /* If just-in-time memory allocation pressure limit feature is disabled, * heap_info_gpu_addr must be zeroed-out */ if (info->heap_info_gpu_addr) return -EINVAL; #endif #if !MALI_USE_CSF /* If BASE_JIT_ALLOC_HEAP_INFO_IS_SIZE is set, heap_info_gpu_addr * cannot be 0 */ if ((info->flags & BASE_JIT_ALLOC_HEAP_INFO_IS_SIZE) && !info->heap_info_gpu_addr) return -EINVAL; #endif /* !MALI_USE_CSF */ return 0; } #if !MALI_USE_CSF static int kbase_jit_allocate_prepare(struct kbase_jd_atom *katom) { __user u8 *data = (__user u8 *)(uintptr_t)katom->jc; struct base_jit_alloc_info *info; struct kbase_context *kctx = katom->kctx; struct kbase_device *kbdev = kctx->kbdev; u32 count; int ret; u32 i; if (!kbase_mem_allow_alloc(kctx)) { dev_dbg(kbdev->dev, "Invalid attempt to allocate JIT memory by %s/%d for ctx %d_%d", current->comm, current->pid, kctx->tgid, kctx->id); ret = -EINVAL; goto fail; } /* For backwards compatibility, and to prevent reading more than 1 jit * info struct on jit version 1 */ if (katom->nr_extres == 0) katom->nr_extres = 1; count = katom->nr_extres; /* Sanity checks */ if (!data || count > kctx->jit_max_allocations || count > ARRAY_SIZE(kctx->jit_alloc)) { ret = -EINVAL; goto fail; } /* Copy the information for safe access and future storage */ info = kmalloc_array(count, sizeof(*info), GFP_KERNEL); if (!info) { ret = -ENOMEM; goto fail; } katom->softjob_data = info; for (i = 0; i < count; i++, info++, data += sizeof(*info)) { if (copy_from_user(info, data, sizeof(*info)) != 0) { ret = -EINVAL; goto free_info; } ret = kbasep_jit_alloc_validate(kctx, info); if (ret) goto free_info; KBASE_TLSTREAM_TL_ATTRIB_ATOM_JITALLOCINFO( kbdev, katom, info->va_pages, info->commit_pages, info->extension, info->id, info->bin_id, info->max_allocations, info->flags, info->usage_id); } katom->jit_blocked = false; lockdep_assert_held(&kctx->jctx.lock); list_add_tail(&katom->jit_node, &kctx->jctx.jit_atoms_head); /* * Note: * The provided info->gpu_alloc_addr isn't validated here as * userland can cache allocations which means that even * though the region is valid it doesn't represent the * same thing it used to. * * Complete validation of va_pages, commit_pages and extension * isn't done here as it will be done during the call to * kbase_mem_alloc. */ return 0; free_info: kfree(katom->softjob_data); katom->softjob_data = NULL; fail: return ret; } static u8 *kbase_jit_free_get_ids(struct kbase_jd_atom *katom) { if (WARN_ON((katom->core_req & BASE_JD_REQ_SOFT_JOB_TYPE) != BASE_JD_REQ_SOFT_JIT_FREE)) return NULL; return (u8 *)katom->softjob_data; } static void kbase_jit_add_to_pending_alloc_list(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; struct list_head *target_list_head = NULL; struct kbase_jd_atom *entry; list_for_each_entry(entry, &kctx->jctx.jit_pending_alloc, queue) { if (katom->age < entry->age) { target_list_head = &entry->queue; break; } } if (target_list_head == NULL) target_list_head = &kctx->jctx.jit_pending_alloc; list_add_tail(&katom->queue, target_list_head); } static int kbase_jit_allocate_process(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; struct kbase_device *kbdev = kctx->kbdev; struct base_jit_alloc_info *info; struct kbase_va_region *reg; struct kbase_vmap_struct mapping; u64 *ptr, new_addr; u32 count = katom->nr_extres; u32 i; bool ignore_pressure_limit = false; trace_sysgraph(SGR_SUBMIT, kctx->id, kbase_jd_atom_id(kctx, katom)); if (katom->jit_blocked) { list_del(&katom->queue); katom->jit_blocked = false; } info = katom->softjob_data; if (WARN_ON(!info)) { katom->event_code = BASE_JD_EVENT_JOB_INVALID; return 0; } for (i = 0; i < count; i++, info++) { /* The JIT ID is still in use so fail the allocation */ if (kctx->jit_alloc[info->id]) { katom->event_code = BASE_JD_EVENT_MEM_GROWTH_FAILED; return 0; } } #if MALI_JIT_PRESSURE_LIMIT_BASE /* * If this is the only JIT_ALLOC atom in-flight or if JIT pressure limit * is disabled at the context scope, then bypass JIT pressure limit * logic in kbase_jit_allocate(). */ if (!kbase_ctx_flag(kctx, KCTX_JPL_ENABLED) || (kctx->jit_current_allocations == 0)) { ignore_pressure_limit = true; } #else ignore_pressure_limit = true; #endif /* MALI_JIT_PRESSURE_LIMIT_BASE */ for (i = 0, info = katom->softjob_data; i < count; i++, info++) { if (kctx->jit_alloc[info->id]) { /* The JIT ID is duplicated in this atom. Roll back * previous allocations and fail. */ u32 j; info = katom->softjob_data; for (j = 0; j < i; j++, info++) { kbase_jit_free(kctx, kctx->jit_alloc[info->id]); kctx->jit_alloc[info->id] = KBASE_RESERVED_REG_JIT_ALLOC; } katom->event_code = BASE_JD_EVENT_MEM_GROWTH_FAILED; return 0; } /* Create a JIT allocation */ reg = kbase_jit_allocate(kctx, info, ignore_pressure_limit); if (!reg) { struct kbase_jd_atom *jit_atom; bool can_block = false; lockdep_assert_held(&kctx->jctx.lock); list_for_each_entry(jit_atom, &kctx->jctx.jit_atoms_head, jit_node) { if (jit_atom == katom) break; if ((jit_atom->core_req & BASE_JD_REQ_SOFT_JOB_TYPE) == BASE_JD_REQ_SOFT_JIT_FREE) { u8 *free_ids = kbase_jit_free_get_ids(jit_atom); if (free_ids && *free_ids && kctx->jit_alloc[*free_ids]) { /* A JIT free which is active and * submitted before this atom */ can_block = true; break; } } } if (!can_block) { /* Mark the failed allocation as well as the * other un-attempted allocations in the set, * so we know they are in use even if the * allocation itself failed. */ for (; i < count; i++, info++) { kctx->jit_alloc[info->id] = KBASE_RESERVED_REG_JIT_ALLOC; } katom->event_code = BASE_JD_EVENT_MEM_GROWTH_FAILED; dev_warn_ratelimited(kbdev->dev, "JIT alloc softjob failed: atom id %d\n", kbase_jd_atom_id(kctx, katom)); return 0; } /* There are pending frees for an active allocation * so we should wait to see whether they free the * memory. Add to the list of atoms for which JIT * allocation is pending. */ kbase_jit_add_to_pending_alloc_list(katom); katom->jit_blocked = true; /* Rollback, the whole set will be re-attempted */ while (i-- > 0) { info--; kbase_jit_free(kctx, kctx->jit_alloc[info->id]); kctx->jit_alloc[info->id] = NULL; } return 1; } /* Bind it to the user provided ID. */ kctx->jit_alloc[info->id] = reg; } for (i = 0, info = katom->softjob_data; i < count; i++, info++) { u64 entry_mmu_flags = 0; /* * Write the address of the JIT allocation to the user provided * GPU allocation. */ ptr = kbase_vmap_prot(kctx, info->gpu_alloc_addr, sizeof(*ptr), KBASE_REG_CPU_WR, &mapping); if (!ptr) { /* * Leave the allocations "live" as the JIT free atom * will be submitted anyway. */ katom->event_code = BASE_JD_EVENT_JOB_INVALID; return 0; } reg = kctx->jit_alloc[info->id]; new_addr = reg->start_pfn << PAGE_SHIFT; *ptr = new_addr; #if defined(CONFIG_MALI_VECTOR_DUMP) /* * Retrieve the mmu flags for JIT allocation * only if dumping is enabled */ entry_mmu_flags = kbase_mmu_create_ate(kbdev, (struct tagged_addr){ 0 }, reg->flags, MIDGARD_MMU_BOTTOMLEVEL, kctx->jit_group_id); #endif KBASE_TLSTREAM_TL_ATTRIB_ATOM_JIT(kbdev, katom, info->gpu_alloc_addr, new_addr, info->flags, entry_mmu_flags, info->id, info->commit_pages, info->extension, info->va_pages); kbase_vunmap(kctx, &mapping); kbase_trace_jit_report_gpu_mem(kctx, reg, KBASE_JIT_REPORT_ON_ALLOC_OR_FREE); } katom->event_code = BASE_JD_EVENT_DONE; return 0; } static void kbase_jit_allocate_finish(struct kbase_jd_atom *katom) { struct base_jit_alloc_info *info; lockdep_assert_held(&katom->kctx->jctx.lock); if (WARN_ON(!katom->softjob_data)) return; /* Remove atom from jit_atoms_head list */ list_del(&katom->jit_node); if (katom->jit_blocked) { list_del(&katom->queue); katom->jit_blocked = false; } info = katom->softjob_data; /* Free the info structure */ kfree(info); } static int kbase_jit_free_prepare(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; struct kbase_device *kbdev = kctx->kbdev; __user void *data = (__user void *)(uintptr_t)katom->jc; u8 *ids; u32 count = MAX(katom->nr_extres, 1); u32 i; int ret; /* Sanity checks */ if (count > ARRAY_SIZE(kctx->jit_alloc)) { ret = -EINVAL; goto fail; } /* Copy the information for safe access and future storage */ ids = kmalloc_array(count, sizeof(*ids), GFP_KERNEL); if (!ids) { ret = -ENOMEM; goto fail; } lockdep_assert_held(&kctx->jctx.lock); katom->softjob_data = ids; /* For backwards compatibility */ if (katom->nr_extres) { /* Fail the job if there is no list of ids */ if (!data) { ret = -EINVAL; goto free_info; } if (copy_from_user(ids, data, sizeof(*ids) * count) != 0) { ret = -EINVAL; goto free_info; } } else { katom->nr_extres = 1; *ids = (u8)katom->jc; } for (i = 0; i < count; i++) KBASE_TLSTREAM_TL_ATTRIB_ATOM_JITFREEINFO(kbdev, katom, ids[i]); list_add_tail(&katom->jit_node, &kctx->jctx.jit_atoms_head); return 0; free_info: kfree(katom->softjob_data); katom->softjob_data = NULL; fail: return ret; } static void kbase_jit_free_process(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; u8 *ids = kbase_jit_free_get_ids(katom); u32 count = katom->nr_extres; u32 i; if (ids == NULL) { katom->event_code = BASE_JD_EVENT_JOB_INVALID; return; } for (i = 0; i < count; i++, ids++) { /* * If the ID is zero or it is not in use yet then fail the job. */ if ((*ids == 0) || (kctx->jit_alloc[*ids] == NULL)) { katom->event_code = BASE_JD_EVENT_JOB_INVALID; return; } } } static void kbasep_jit_finish_worker(struct kthread_work *work) { struct kbase_jd_atom *katom = container_of(work, struct kbase_jd_atom, work); struct kbase_context *kctx = katom->kctx; int resched; rt_mutex_lock(&kctx->jctx.lock); kbase_finish_soft_job(katom); resched = kbase_jd_done_nolock(katom, true); rt_mutex_unlock(&kctx->jctx.lock); if (resched) kbase_js_sched_all(kctx->kbdev); } void kbase_jit_retry_pending_alloc(struct kbase_context *kctx) { LIST_HEAD(jit_pending_alloc_list); struct list_head *i, *tmp; list_splice_tail_init(&kctx->jctx.jit_pending_alloc, &jit_pending_alloc_list); list_for_each_safe(i, tmp, &jit_pending_alloc_list) { struct kbase_jd_atom *pending_atom = list_entry(i, struct kbase_jd_atom, queue); KBASE_TLSTREAM_TL_EVENT_ATOM_SOFTJOB_START(kctx->kbdev, pending_atom); kbase_kinstr_jm_atom_sw_start(pending_atom); if (kbase_jit_allocate_process(pending_atom) == 0) { /* Atom has completed */ kthread_init_work(&pending_atom->work, kbasep_jit_finish_worker); kthread_queue_work(&kctx->kbdev->job_done_worker, &pending_atom->work); } KBASE_TLSTREAM_TL_EVENT_ATOM_SOFTJOB_END(kctx->kbdev, pending_atom); kbase_kinstr_jm_atom_sw_stop(pending_atom); } } static void kbase_jit_free_finish(struct kbase_jd_atom *katom) { struct kbase_context *kctx = katom->kctx; u8 *ids; size_t j; lockdep_assert_held(&kctx->jctx.lock); ids = kbase_jit_free_get_ids(katom); if (WARN_ON(ids == NULL)) return; /* Remove this atom from the jit_atoms_head list */ list_del(&katom->jit_node); for (j = 0; j != katom->nr_extres; ++j) { if ((ids[j] != 0) && (kctx->jit_alloc[ids[j]] != NULL)) { /* * If the ID is valid but the allocation request failed * still succeed this soft job but don't try and free * the allocation. */ if (kctx->jit_alloc[ids[j]] != KBASE_RESERVED_REG_JIT_ALLOC) { KBASE_TLSTREAM_TL_JIT_USEDPAGES( kctx->kbdev, kctx->jit_alloc[ids[j]]->gpu_alloc->nents, ids[j]); kbase_jit_free(kctx, kctx->jit_alloc[ids[j]]); } kctx->jit_alloc[ids[j]] = NULL; } } /* Free the list of ids */ kfree(ids); kbase_jit_retry_pending_alloc(kctx); } static int kbase_ext_res_prepare(struct kbase_jd_atom *katom) { __user struct base_external_resource_list *user_ext_res; struct base_external_resource_list *ext_res; u64 count = 0; size_t copy_size; user_ext_res = (__user struct base_external_resource_list *)(uintptr_t)katom->jc; /* Fail the job if there is no info structure */ if (!user_ext_res) return -EINVAL; if (copy_from_user(&count, &user_ext_res->count, sizeof(u64)) != 0) return -EINVAL; /* Is the number of external resources in range? */ if (!count || count > BASE_EXT_RES_COUNT_MAX) return -EINVAL; /* Copy the information for safe access and future storage */ copy_size = sizeof(*ext_res); copy_size += sizeof(struct base_external_resource) * (count - 1); ext_res = memdup_user(user_ext_res, copy_size); if (IS_ERR(ext_res)) return PTR_ERR(ext_res); /* * Overwrite the count with the first value incase it was changed * after the fact. */ ext_res->count = count; katom->softjob_data = ext_res; return 0; } static void kbase_ext_res_process(struct kbase_jd_atom *katom, bool map) { struct base_external_resource_list *ext_res; uint64_t i; bool failed = false; ext_res = katom->softjob_data; if (!ext_res) goto failed_jc; kbase_gpu_vm_lock(katom->kctx); for (i = 0; i < ext_res->count; i++) { u64 gpu_addr; gpu_addr = ext_res->ext_res[i].ext_resource & ~(__u64)BASE_EXT_RES_ACCESS_EXCLUSIVE; if (map) { if (!kbase_sticky_resource_acquire(katom->kctx, gpu_addr)) goto failed_loop; } else { if (!kbase_sticky_resource_release_force(katom->kctx, NULL, gpu_addr)) failed = true; } } /* * In the case of unmap we continue unmapping other resources in the * case of failure but will always report failure if _any_ unmap * request fails. */ if (failed) katom->event_code = BASE_JD_EVENT_JOB_INVALID; else katom->event_code = BASE_JD_EVENT_DONE; kbase_gpu_vm_unlock(katom->kctx); return; failed_loop: while (i > 0) { u64 const gpu_addr = ext_res->ext_res[i - 1].ext_resource & ~(__u64)BASE_EXT_RES_ACCESS_EXCLUSIVE; kbase_sticky_resource_release_force(katom->kctx, NULL, gpu_addr); --i; } katom->event_code = BASE_JD_EVENT_JOB_INVALID; kbase_gpu_vm_unlock(katom->kctx); failed_jc: return; } static void kbase_ext_res_finish(struct kbase_jd_atom *katom) { struct base_external_resource_list *ext_res; ext_res = katom->softjob_data; /* Free the info structure */ kfree(ext_res); } int kbase_process_soft_job(struct kbase_jd_atom *katom) { int ret = 0; struct kbase_context *kctx = katom->kctx; struct kbase_device *kbdev = kctx->kbdev; KBASE_TLSTREAM_TL_EVENT_ATOM_SOFTJOB_START(kbdev, katom); kbase_kinstr_jm_atom_sw_start(katom); trace_sysgraph(SGR_SUBMIT, kctx->id, kbase_jd_atom_id(kctx, katom)); switch (katom->core_req & BASE_JD_REQ_SOFT_JOB_TYPE) { case BASE_JD_REQ_SOFT_DUMP_CPU_GPU_TIME: ret = kbase_dump_cpu_gpu_time(katom); break; #if IS_ENABLED(CONFIG_SYNC_FILE) case BASE_JD_REQ_SOFT_FENCE_TRIGGER: katom->event_code = kbase_sync_fence_out_trigger( katom, katom->event_code == BASE_JD_EVENT_DONE ? 0 : -EFAULT); break; case BASE_JD_REQ_SOFT_FENCE_WAIT: { ret = kbase_sync_fence_in_wait(katom); if (ret == 1) { #ifdef CONFIG_MALI_FENCE_DEBUG kbasep_add_waiting_with_timeout(katom); #else kbasep_add_waiting_soft_job(katom); #endif } break; } #endif case BASE_JD_REQ_SOFT_EVENT_WAIT: ret = kbasep_soft_event_wait(katom); break; case BASE_JD_REQ_SOFT_EVENT_SET: kbasep_soft_event_update_locked(katom, BASE_JD_SOFT_EVENT_SET); break; case BASE_JD_REQ_SOFT_EVENT_RESET: kbasep_soft_event_update_locked(katom, BASE_JD_SOFT_EVENT_RESET); break; #if IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST case BASE_JD_REQ_SOFT_DEBUG_COPY: { int res = kbase_debug_copy(katom); if (res) katom->event_code = BASE_JD_EVENT_JOB_INVALID; break; } #endif /* IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST */ case BASE_JD_REQ_SOFT_JIT_ALLOC: ret = kbase_jit_allocate_process(katom); break; case BASE_JD_REQ_SOFT_JIT_FREE: kbase_jit_free_process(katom); break; case BASE_JD_REQ_SOFT_EXT_RES_MAP: kbase_ext_res_process(katom, true); break; case BASE_JD_REQ_SOFT_EXT_RES_UNMAP: kbase_ext_res_process(katom, false); break; } /* Atom is complete */ KBASE_TLSTREAM_TL_EVENT_ATOM_SOFTJOB_END(kbdev, katom); kbase_kinstr_jm_atom_sw_stop(katom); return ret; } void kbase_cancel_soft_job(struct kbase_jd_atom *katom) { switch (katom->core_req & BASE_JD_REQ_SOFT_JOB_TYPE) { #if IS_ENABLED(CONFIG_SYNC_FILE) case BASE_JD_REQ_SOFT_FENCE_WAIT: kbase_sync_fence_in_cancel_wait(katom); break; #endif case BASE_JD_REQ_SOFT_EVENT_WAIT: kbasep_soft_event_cancel_job(katom); break; default: /* This soft-job doesn't support cancellation! */ KBASE_DEBUG_ASSERT(0); } } int kbase_prepare_soft_job(struct kbase_jd_atom *katom) { switch (katom->core_req & BASE_JD_REQ_SOFT_JOB_TYPE) { case BASE_JD_REQ_SOFT_DUMP_CPU_GPU_TIME: { if (!IS_ALIGNED(katom->jc, cache_line_size())) return -EINVAL; } break; #if IS_ENABLED(CONFIG_SYNC_FILE) case BASE_JD_REQ_SOFT_FENCE_TRIGGER: { struct base_fence fence; int fd; if (copy_from_user(&fence, (__user void *)(uintptr_t)katom->jc, sizeof(fence)) != 0) return -EINVAL; fd = kbase_sync_fence_out_create(katom, fence.basep.stream_fd); if (fd < 0) return -EINVAL; fence.basep.fd = fd; if (copy_to_user((__user void *)(uintptr_t)katom->jc, &fence, sizeof(fence)) != 0) { kbase_sync_fence_out_remove(katom); /* fd should have been closed here, but there's * no good way of doing that. Since * copy_to_user() very rarely fails, and the fd * will get closed on process termination this * won't be a problem. */ fence.basep.fd = -EINVAL; return -EINVAL; } } break; case BASE_JD_REQ_SOFT_FENCE_WAIT: { struct base_fence fence; int ret; if (copy_from_user(&fence, (__user void *)(uintptr_t)katom->jc, sizeof(fence)) != 0) return -EINVAL; /* Get a reference to the fence object */ ret = kbase_sync_fence_in_from_fd(katom, fence.basep.fd); if (ret < 0) return ret; } break; #endif /* CONFIG_SYNC_FILE */ case BASE_JD_REQ_SOFT_JIT_ALLOC: return kbase_jit_allocate_prepare(katom); case BASE_JD_REQ_SOFT_JIT_FREE: return kbase_jit_free_prepare(katom); case BASE_JD_REQ_SOFT_EVENT_WAIT: case BASE_JD_REQ_SOFT_EVENT_SET: case BASE_JD_REQ_SOFT_EVENT_RESET: if (katom->jc == 0) return -EINVAL; break; #if IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST case BASE_JD_REQ_SOFT_DEBUG_COPY: return kbase_debug_copy_prepare(katom); #endif /* IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST */ case BASE_JD_REQ_SOFT_EXT_RES_MAP: return kbase_ext_res_prepare(katom); case BASE_JD_REQ_SOFT_EXT_RES_UNMAP: return kbase_ext_res_prepare(katom); default: /* Unsupported soft-job */ return -EINVAL; } return 0; } void kbase_finish_soft_job(struct kbase_jd_atom *katom) { trace_sysgraph(SGR_COMPLETE, katom->kctx->id, kbase_jd_atom_id(katom->kctx, katom)); switch (katom->core_req & BASE_JD_REQ_SOFT_JOB_TYPE) { case BASE_JD_REQ_SOFT_DUMP_CPU_GPU_TIME: /* Nothing to do */ break; #if IS_ENABLED(CONFIG_SYNC_FILE) case BASE_JD_REQ_SOFT_FENCE_TRIGGER: /* If fence has not yet been signaled, do it now */ kbase_sync_fence_out_trigger(katom, katom->event_code == BASE_JD_EVENT_DONE ? 0 : -EFAULT); break; case BASE_JD_REQ_SOFT_FENCE_WAIT: /* Release katom's reference to fence object */ kbase_sync_fence_in_remove(katom); break; #endif /* CONFIG_SYNC_FILE */ #if IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST case BASE_JD_REQ_SOFT_DEBUG_COPY: kbase_debug_copy_finish(katom); break; #endif /* IS_ENABLED(CONFIG_MALI_VECTOR_DUMP) || MALI_UNIT_TEST */ case BASE_JD_REQ_SOFT_JIT_ALLOC: kbase_jit_allocate_finish(katom); break; case BASE_JD_REQ_SOFT_EXT_RES_MAP: kbase_ext_res_finish(katom); break; case BASE_JD_REQ_SOFT_EXT_RES_UNMAP: kbase_ext_res_finish(katom); break; case BASE_JD_REQ_SOFT_JIT_FREE: kbase_jit_free_finish(katom); break; } } void kbase_resume_suspended_soft_jobs(struct kbase_device *kbdev) { LIST_HEAD(local_suspended_soft_jobs); struct kbase_jd_atom *tmp_iter; struct kbase_jd_atom *katom_iter; struct kbasep_js_device_data *js_devdata; bool resched = false; KBASE_DEBUG_ASSERT(kbdev); js_devdata = &kbdev->js_data; /* Move out the entire list */ mutex_lock(&js_devdata->runpool_mutex); list_splice_init(&js_devdata->suspended_soft_jobs_list, &local_suspended_soft_jobs); mutex_unlock(&js_devdata->runpool_mutex); /* * Each atom must be detached from the list and ran separately - * it could be re-added to the old list, but this is unlikely */ list_for_each_entry_safe(katom_iter, tmp_iter, &local_suspended_soft_jobs, dep_item[1]) { struct kbase_context *kctx = katom_iter->kctx; rt_mutex_lock(&kctx->jctx.lock); /* Remove from the global list */ list_del(&katom_iter->dep_item[1]); /* Remove from the context's list of waiting soft jobs */ kbasep_remove_waiting_soft_job(katom_iter); if (kbase_process_soft_job(katom_iter) == 0) { kbase_finish_soft_job(katom_iter); resched |= kbase_jd_done_nolock(katom_iter, true); #ifdef CONFIG_MALI_ARBITER_SUPPORT atomic_dec(&kbdev->pm.gpu_users_waiting); #endif /* CONFIG_MALI_ARBITER_SUPPORT */ } rt_mutex_unlock(&kctx->jctx.lock); } if (resched) kbase_js_sched_all(kbdev); } #endif /* !MALI_USE_CSF */