| /* |
| * |
| * (C) COPYRIGHT 2012-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 |
| * |
| */ |
| |
| /* |
| * Code for supporting explicit Linux fences (CONFIG_SYNC_FILE) |
| * Introduced in kernel 4.9. |
| * Android explicit fences (CONFIG_SYNC) can be used for older kernels |
| * (see mali_kbase_sync_android.c) |
| */ |
| |
| #include <linux/sched.h> |
| #include <linux/fdtable.h> |
| #include <linux/file.h> |
| #include <linux/fs.h> |
| #include <linux/module.h> |
| #include <linux/anon_inodes.h> |
| #include <linux/version.h> |
| #include <linux/uaccess.h> |
| #include <linux/sync_file.h> |
| #include <linux/slab.h> |
| #include "mali_kbase_fence_defs.h" |
| #include "mali_kbase_sync.h" |
| #include "mali_kbase_fence.h" |
| #include "mali_kbase.h" |
| |
| static const struct file_operations stream_fops = { |
| .owner = THIS_MODULE |
| }; |
| |
| int kbase_sync_fence_stream_create(const char *name, int *const out_fd) |
| { |
| if (!out_fd) |
| return -EINVAL; |
| |
| *out_fd = anon_inode_getfd(name, &stream_fops, NULL, |
| O_RDONLY | O_CLOEXEC); |
| if (*out_fd < 0) |
| return -EINVAL; |
| |
| return 0; |
| } |
| |
| int kbase_sync_fence_out_create(struct kbase_jd_atom *katom, int stream_fd) |
| { |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) |
| struct fence *fence; |
| #else |
| struct dma_fence *fence; |
| #endif |
| struct sync_file *sync_file; |
| int fd; |
| |
| fence = kbase_fence_out_new(katom); |
| if (!fence) |
| return -ENOMEM; |
| |
| #if (KERNEL_VERSION(4, 9, 67) >= LINUX_VERSION_CODE) |
| /* Take an extra reference to the fence on behalf of the sync_file. |
| * This is only needed on older kernels where sync_file_create() |
| * does not take its own reference. This was changed in v4.9.68, |
| * where sync_file_create() now takes its own reference. |
| */ |
| dma_fence_get(fence); |
| #endif |
| |
| /* create a sync_file fd representing the fence */ |
| sync_file = sync_file_create(fence); |
| if (!sync_file) { |
| #if (KERNEL_VERSION(4, 9, 67) >= LINUX_VERSION_CODE) |
| dma_fence_put(fence); |
| #endif |
| kbase_fence_out_remove(katom); |
| return -ENOMEM; |
| } |
| |
| fd = get_unused_fd_flags(O_CLOEXEC); |
| if (fd < 0) { |
| fput(sync_file->file); |
| kbase_fence_out_remove(katom); |
| return fd; |
| } |
| |
| fd_install(fd, sync_file->file); |
| |
| return fd; |
| } |
| |
| int kbase_sync_fence_in_from_fd(struct kbase_jd_atom *katom, int fd) |
| { |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) |
| struct fence *fence = sync_file_get_fence(fd); |
| #else |
| struct dma_fence *fence = sync_file_get_fence(fd); |
| #endif |
| |
| if (!fence) |
| return -ENOENT; |
| |
| kbase_fence_fence_in_set(katom, fence); |
| |
| return 0; |
| } |
| |
| int kbase_sync_fence_validate(int fd) |
| { |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) |
| struct fence *fence = sync_file_get_fence(fd); |
| #else |
| struct dma_fence *fence = sync_file_get_fence(fd); |
| #endif |
| |
| if (!fence) |
| return -EINVAL; |
| |
| dma_fence_put(fence); |
| |
| return 0; /* valid */ |
| } |
| |
| enum base_jd_event_code |
| kbase_sync_fence_out_trigger(struct kbase_jd_atom *katom, int result) |
| { |
| int res; |
| |
| if (!kbase_fence_out_is_ours(katom)) { |
| /* Not our fence */ |
| return BASE_JD_EVENT_JOB_CANCELLED; |
| } |
| |
| res = kbase_fence_out_signal(katom, result); |
| if (unlikely(res < 0)) { |
| dev_warn(katom->kctx->kbdev->dev, |
| "fence_signal() failed with %d\n", res); |
| } |
| |
| kbase_sync_fence_out_remove(katom); |
| |
| return (result != 0) ? BASE_JD_EVENT_JOB_CANCELLED : BASE_JD_EVENT_DONE; |
| } |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) |
| static void kbase_fence_wait_callback(struct fence *fence, |
| struct fence_cb *cb) |
| #else |
| static void kbase_fence_wait_callback(struct dma_fence *fence, |
| struct dma_fence_cb *cb) |
| #endif |
| { |
| struct kbase_fence_cb *kcb = container_of(cb, |
| struct kbase_fence_cb, |
| fence_cb); |
| struct kbase_jd_atom *katom = kcb->katom; |
| struct kbase_context *kctx = katom->kctx; |
| |
| /* Cancel atom if fence is erroneous */ |
| #if (KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE || \ |
| (KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE && \ |
| KERNEL_VERSION(4, 9, 68) <= LINUX_VERSION_CODE)) |
| if (dma_fence_is_signaled(kcb->fence) && kcb->fence->error) |
| #else |
| if (dma_fence_is_signaled(kcb->fence) && kcb->fence->status < 0) |
| #endif |
| katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; |
| |
| if (kbase_fence_dep_count_dec_and_test(katom)) { |
| /* We take responsibility of handling this */ |
| kbase_fence_dep_count_set(katom, -1); |
| |
| /* To prevent a potential deadlock we schedule the work onto the |
| * job_done_wq workqueue |
| * |
| * The issue is that we may signal the timeline while holding |
| * kctx->jctx.lock and the callbacks are run synchronously from |
| * sync_timeline_signal. So we simply defer the work. |
| */ |
| INIT_WORK(&katom->work, kbase_sync_fence_wait_worker); |
| queue_work(kctx->jctx.job_done_wq, &katom->work); |
| } |
| } |
| |
| int kbase_sync_fence_in_wait(struct kbase_jd_atom *katom) |
| { |
| int err; |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) |
| struct fence *fence; |
| #else |
| struct dma_fence *fence; |
| #endif |
| |
| fence = kbase_fence_in_get(katom); |
| if (!fence) |
| return 0; /* no input fence to wait for, good to go! */ |
| |
| kbase_fence_dep_count_set(katom, 1); |
| |
| err = kbase_fence_add_callback(katom, fence, kbase_fence_wait_callback); |
| |
| kbase_fence_put(fence); |
| |
| if (likely(!err)) { |
| /* Test if the callbacks are already triggered */ |
| if (kbase_fence_dep_count_dec_and_test(katom)) { |
| kbase_fence_free_callbacks(katom); |
| kbase_fence_dep_count_set(katom, -1); |
| return 0; /* Already signaled, good to go right now */ |
| } |
| |
| /* Callback installed, so we just need to wait for it... */ |
| } else { |
| /* Failure */ |
| kbase_fence_free_callbacks(katom); |
| kbase_fence_dep_count_set(katom, -1); |
| |
| katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; |
| |
| /* We should cause the dependent jobs in the bag to be failed, |
| * to do this we schedule the work queue to complete this job */ |
| |
| INIT_WORK(&katom->work, kbase_sync_fence_wait_worker); |
| queue_work(katom->kctx->jctx.job_done_wq, &katom->work); |
| } |
| |
| return 1; /* completion to be done later by callback/worker */ |
| } |
| |
| void kbase_sync_fence_in_cancel_wait(struct kbase_jd_atom *katom) |
| { |
| if (!kbase_fence_free_callbacks(katom)) { |
| /* The wait wasn't cancelled - |
| * leave the cleanup for kbase_fence_wait_callback */ |
| return; |
| } |
| |
| /* Take responsibility of completion */ |
| kbase_fence_dep_count_set(katom, -1); |
| |
| /* Wait was cancelled - zap the atoms */ |
| katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; |
| |
| kbasep_remove_waiting_soft_job(katom); |
| kbase_finish_soft_job(katom); |
| |
| if (jd_done_nolock(katom, NULL)) |
| kbase_js_sched_all(katom->kctx->kbdev); |
| } |
| |
| void kbase_sync_fence_out_remove(struct kbase_jd_atom *katom) |
| { |
| kbase_fence_out_remove(katom); |
| } |
| |
| void kbase_sync_fence_in_remove(struct kbase_jd_atom *katom) |
| { |
| kbase_fence_free_callbacks(katom); |
| kbase_fence_in_remove(katom); |
| } |
| |
| #if (KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE) |
| void kbase_sync_fence_info_get(struct fence *fence, |
| struct kbase_sync_fence_info *info) |
| #else |
| void kbase_sync_fence_info_get(struct dma_fence *fence, |
| struct kbase_sync_fence_info *info) |
| #endif |
| { |
| info->fence = fence; |
| |
| /* translate into CONFIG_SYNC status: |
| * < 0 : error |
| * 0 : active |
| * 1 : signaled |
| */ |
| if (dma_fence_is_signaled(fence)) { |
| #if (KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE || \ |
| (KERNEL_VERSION(4, 10, 0) > LINUX_VERSION_CODE && \ |
| KERNEL_VERSION(4, 9, 68) <= LINUX_VERSION_CODE)) |
| int status = fence->error; |
| #else |
| int status = fence->status; |
| #endif |
| if (status < 0) |
| info->status = status; /* signaled with error */ |
| else |
| info->status = 1; /* signaled with success */ |
| } else { |
| info->status = 0; /* still active (unsignaled) */ |
| } |
| |
| #if (KERNEL_VERSION(4, 8, 0) > LINUX_VERSION_CODE) |
| scnprintf(info->name, sizeof(info->name), "%u#%u", |
| fence->context, fence->seqno); |
| #else |
| scnprintf(info->name, sizeof(info->name), "%llu#%llu", |
| fence->context, fence->seqno); |
| #endif |
| } |
| |
| int kbase_sync_fence_in_info_get(struct kbase_jd_atom *katom, |
| struct kbase_sync_fence_info *info) |
| { |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) |
| struct fence *fence; |
| #else |
| struct dma_fence *fence; |
| #endif |
| |
| fence = kbase_fence_in_get(katom); |
| if (!fence) |
| return -ENOENT; |
| |
| kbase_sync_fence_info_get(fence, info); |
| |
| kbase_fence_put(fence); |
| |
| return 0; |
| } |
| |
| int kbase_sync_fence_out_info_get(struct kbase_jd_atom *katom, |
| struct kbase_sync_fence_info *info) |
| { |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0)) |
| struct fence *fence; |
| #else |
| struct dma_fence *fence; |
| #endif |
| |
| fence = kbase_fence_out_get(katom); |
| if (!fence) |
| return -ENOENT; |
| |
| kbase_sync_fence_info_get(fence, info); |
| |
| kbase_fence_put(fence); |
| |
| return 0; |
| } |
| |
| |
| #ifdef CONFIG_MALI_FENCE_DEBUG |
| void kbase_sync_fence_in_dump(struct kbase_jd_atom *katom) |
| { |
| /* Not implemented */ |
| } |
| #endif |