/* * * (C) COPYRIGHT 2012-2017 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. * * 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. * */ /* * Code for supporting explicit Android fences (CONFIG_SYNC) * Known to be good for kernels 4.5 and earlier. * Replaced with CONFIG_SYNC_FILE for 4.9 and later kernels * (see mali_kbase_sync_file.c) */ #include #include #include #include #include #include #include #include "sync.h" #include #include struct mali_sync_timeline { struct sync_timeline timeline; atomic_t counter; atomic_t signaled; }; struct mali_sync_pt { struct sync_pt pt; int order; int result; }; #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) /* For backwards compatibility with kernels before 3.17. After 3.17 * sync_pt_parent is included in the kernel. */ static inline struct sync_timeline *sync_pt_parent(struct sync_pt *pt) { return pt->parent; } #endif static struct mali_sync_timeline *to_mali_sync_timeline( struct sync_timeline *timeline) { return container_of(timeline, struct mali_sync_timeline, timeline); } static struct mali_sync_pt *to_mali_sync_pt(struct sync_pt *pt) { return container_of(pt, struct mali_sync_pt, pt); } static struct sync_pt *timeline_dup(struct sync_pt *pt) { struct mali_sync_pt *mpt = to_mali_sync_pt(pt); struct mali_sync_pt *new_mpt; struct sync_pt *new_pt = sync_pt_create(sync_pt_parent(pt), sizeof(struct mali_sync_pt)); if (!new_pt) return NULL; new_mpt = to_mali_sync_pt(new_pt); new_mpt->order = mpt->order; new_mpt->result = mpt->result; return new_pt; } static int timeline_has_signaled(struct sync_pt *pt) { struct mali_sync_pt *mpt = to_mali_sync_pt(pt); struct mali_sync_timeline *mtl = to_mali_sync_timeline( sync_pt_parent(pt)); int result = mpt->result; int diff = atomic_read(&mtl->signaled) - mpt->order; if (diff >= 0) return (result < 0) ? result : 1; return 0; } static int timeline_compare(struct sync_pt *a, struct sync_pt *b) { struct mali_sync_pt *ma = container_of(a, struct mali_sync_pt, pt); struct mali_sync_pt *mb = container_of(b, struct mali_sync_pt, pt); int diff = ma->order - mb->order; if (diff == 0) return 0; return (diff < 0) ? -1 : 1; } static void timeline_value_str(struct sync_timeline *timeline, char *str, int size) { struct mali_sync_timeline *mtl = to_mali_sync_timeline(timeline); snprintf(str, size, "%d", atomic_read(&mtl->signaled)); } static void pt_value_str(struct sync_pt *pt, char *str, int size) { struct mali_sync_pt *mpt = to_mali_sync_pt(pt); snprintf(str, size, "%d(%d)", mpt->order, mpt->result); } static struct sync_timeline_ops mali_timeline_ops = { .driver_name = "Mali", .dup = timeline_dup, .has_signaled = timeline_has_signaled, .compare = timeline_compare, .timeline_value_str = timeline_value_str, .pt_value_str = pt_value_str, }; /* Allocates a timeline for Mali * * One timeline should be allocated per API context. */ static struct sync_timeline *mali_sync_timeline_alloc(const char *name) { struct sync_timeline *tl; struct mali_sync_timeline *mtl; tl = sync_timeline_create(&mali_timeline_ops, sizeof(struct mali_sync_timeline), name); if (!tl) return NULL; /* Set the counter in our private struct */ mtl = to_mali_sync_timeline(tl); atomic_set(&mtl->counter, 0); atomic_set(&mtl->signaled, 0); return tl; } static int kbase_stream_close(struct inode *inode, struct file *file) { struct sync_timeline *tl; tl = (struct sync_timeline *)file->private_data; sync_timeline_destroy(tl); return 0; } static const struct file_operations stream_fops = { .owner = THIS_MODULE, .release = kbase_stream_close, }; int kbase_sync_fence_stream_create(const char *name, int *const out_fd) { struct sync_timeline *tl; if (!out_fd) return -EINVAL; tl = mali_sync_timeline_alloc(name); if (!tl) return -EINVAL; *out_fd = anon_inode_getfd(name, &stream_fops, tl, O_RDONLY|O_CLOEXEC); if (*out_fd < 0) { sync_timeline_destroy(tl); return -EINVAL; } return 0; } /* Allocates a sync point within the timeline. * * The timeline must be the one allocated by kbase_sync_timeline_alloc * * Sync points must be triggered in *exactly* the same order as they are * allocated. */ static struct sync_pt *kbase_sync_pt_alloc(struct sync_timeline *parent) { struct sync_pt *pt = sync_pt_create(parent, sizeof(struct mali_sync_pt)); struct mali_sync_timeline *mtl = to_mali_sync_timeline(parent); struct mali_sync_pt *mpt; if (!pt) return NULL; mpt = to_mali_sync_pt(pt); mpt->order = atomic_inc_return(&mtl->counter); mpt->result = 0; return pt; } int kbase_sync_fence_out_create(struct kbase_jd_atom *katom, int tl_fd) { struct sync_timeline *tl; struct sync_pt *pt; struct sync_fence *fence; #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) struct files_struct *files; struct fdtable *fdt; #endif int fd; struct file *tl_file; tl_file = fget(tl_fd); if (tl_file == NULL) return -EBADF; if (tl_file->f_op != &stream_fops) { fd = -EBADF; goto out; } tl = tl_file->private_data; pt = kbase_sync_pt_alloc(tl); if (!pt) { fd = -EFAULT; goto out; } fence = sync_fence_create("mali_fence", pt); if (!fence) { sync_pt_free(pt); fd = -EFAULT; goto out; } /* from here the fence owns the sync_pt */ /* create a fd representing the fence */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) fd = get_unused_fd_flags(O_RDWR | O_CLOEXEC); if (fd < 0) { sync_fence_put(fence); goto out; } #else fd = get_unused_fd(); if (fd < 0) { sync_fence_put(fence); goto out; } files = current->files; spin_lock(&files->file_lock); fdt = files_fdtable(files); #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 4, 0) __set_close_on_exec(fd, fdt); #else FD_SET(fd, fdt->close_on_exec); #endif spin_unlock(&files->file_lock); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(3, 7, 0) */ /* bind fence to the new fd */ sync_fence_install(fence, fd); katom->fence = sync_fence_fdget(fd); if (katom->fence == NULL) { /* The only way the fence can be NULL is if userspace closed it * for us, so we don't need to clear it up */ fd = -EINVAL; goto out; } out: fput(tl_file); return fd; } int kbase_sync_fence_in_from_fd(struct kbase_jd_atom *katom, int fd) { katom->fence = sync_fence_fdget(fd); return katom->fence ? 0 : -ENOENT; } int kbase_sync_fence_validate(int fd) { struct sync_fence *fence; fence = sync_fence_fdget(fd); if (!fence) return -EINVAL; sync_fence_put(fence); return 0; } /* Returns true if the specified timeline is allocated by Mali */ static int kbase_sync_timeline_is_ours(struct sync_timeline *timeline) { return timeline->ops == &mali_timeline_ops; } /* Signals a particular sync point * * Sync points must be triggered in *exactly* the same order as they are * allocated. * * If they are signaled in the wrong order then a message will be printed in * debug builds and otherwise attempts to signal order sync_pts will be ignored. * * result can be negative to indicate error, any other value is interpreted as * success. */ static void kbase_sync_signal_pt(struct sync_pt *pt, int result) { struct mali_sync_pt *mpt = to_mali_sync_pt(pt); struct mali_sync_timeline *mtl = to_mali_sync_timeline( sync_pt_parent(pt)); int signaled; int diff; mpt->result = result; do { signaled = atomic_read(&mtl->signaled); diff = signaled - mpt->order; if (diff > 0) { /* The timeline is already at or ahead of this point. * This should not happen unless userspace has been * signaling fences out of order, so warn but don't * violate the sync_pt API. * The warning is only in debug builds to prevent * a malicious user being able to spam dmesg. */ #ifdef CONFIG_MALI_DEBUG pr_err("Fences were triggered in a different order to allocation!"); #endif /* CONFIG_MALI_DEBUG */ return; } } while (atomic_cmpxchg(&mtl->signaled, signaled, mpt->order) != signaled); } enum base_jd_event_code kbase_sync_fence_out_trigger(struct kbase_jd_atom *katom, int result) { struct sync_pt *pt; struct sync_timeline *timeline; if (!katom->fence) return BASE_JD_EVENT_JOB_CANCELLED; #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) if (!list_is_singular(&katom->fence->pt_list_head)) { #else if (katom->fence->num_fences != 1) { #endif /* Not exactly one item in the list - so it didn't (directly) * come from us */ return BASE_JD_EVENT_JOB_CANCELLED; } #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) pt = list_first_entry(&katom->fence->pt_list_head, struct sync_pt, pt_list); #else pt = container_of(katom->fence->cbs[0].sync_pt, struct sync_pt, base); #endif timeline = sync_pt_parent(pt); if (!kbase_sync_timeline_is_ours(timeline)) { /* Fence has a sync_pt which isn't ours! */ return BASE_JD_EVENT_JOB_CANCELLED; } kbase_sync_signal_pt(pt, result); sync_timeline_signal(timeline); kbase_sync_fence_out_remove(katom); return (result < 0) ? BASE_JD_EVENT_JOB_CANCELLED : BASE_JD_EVENT_DONE; } static inline int kbase_fence_get_status(struct sync_fence *fence) { if (!fence) return -ENOENT; #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) return fence->status; #else return atomic_read(&fence->status); #endif } static void kbase_fence_wait_callback(struct sync_fence *fence, struct sync_fence_waiter *waiter) { struct kbase_jd_atom *katom = container_of(waiter, struct kbase_jd_atom, sync_waiter); struct kbase_context *kctx = katom->kctx; /* Propagate the fence status to the atom. * If negative then cancel this atom and its dependencies. */ if (kbase_fence_get_status(fence) < 0) katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; /* 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 ret; sync_fence_waiter_init(&katom->sync_waiter, kbase_fence_wait_callback); ret = sync_fence_wait_async(katom->fence, &katom->sync_waiter); if (ret == 1) { /* Already signaled */ return 0; } if (ret < 0) { 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; } void kbase_sync_fence_in_cancel_wait(struct kbase_jd_atom *katom) { if (sync_fence_cancel_async(katom->fence, &katom->sync_waiter) != 0) { /* The wait wasn't cancelled - leave the cleanup for * kbase_fence_wait_callback */ return; } /* 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) { if (katom->fence) { sync_fence_put(katom->fence); katom->fence = NULL; } } void kbase_sync_fence_in_remove(struct kbase_jd_atom *katom) { if (katom->fence) { sync_fence_put(katom->fence); katom->fence = NULL; } } int kbase_sync_fence_in_info_get(struct kbase_jd_atom *katom, struct kbase_sync_fence_info *info) { if (!katom->fence) return -ENOENT; info->fence = katom->fence; info->status = kbase_fence_get_status(katom->fence); strlcpy(info->name, katom->fence->name, sizeof(info->name)); return 0; } int kbase_sync_fence_out_info_get(struct kbase_jd_atom *katom, struct kbase_sync_fence_info *info) { if (!katom->fence) return -ENOENT; info->fence = katom->fence; info->status = kbase_fence_get_status(katom->fence); strlcpy(info->name, katom->fence->name, sizeof(info->name)); return 0; } #ifdef CONFIG_MALI_FENCE_DEBUG void kbase_sync_fence_in_dump(struct kbase_jd_atom *katom) { /* Dump out the full state of all the Android sync fences. * The function sync_dump() isn't exported to modules, so force * sync_fence_wait() to time out to trigger sync_dump(). */ if (katom->fence) sync_fence_wait(katom->fence, 1); } #endif