| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| #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 "sync.h" |
| #include <mali_kbase.h> |
| #include <mali_kbase_sync.h> |
| |
| 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; |
| }; |
| |
| 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, |
| }; |
| |
| |
| |
| |
| |
| 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; |
| |
| <------> |
| <------>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; |
| } |
| |
| #if !MALI_USE_CSF |
| |
| |
| |
| |
| |
| |
| |
| 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; |
| <------>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; |
| <------>} |
| |
| <------> |
| |
| <------> |
| <------>fd = get_unused_fd_flags(O_RDWR | O_CLOEXEC); |
| <------>if (fd < 0) { |
| <------><------>sync_pt_free(pt); |
| <------><------>sync_fence_put(fence); |
| <------><------>katom->fence = NULL; |
| <------><------>goto out; |
| <------>} |
| |
| <------> |
| <------>katom->fence = fence; |
| |
| <------> |
| <------>sync_fence_install(fence, fd); |
| 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; |
| } |
| #endif |
| |
| 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; |
| } |
| |
| #if !MALI_USE_CSF |
| |
| static int kbase_sync_timeline_is_ours(struct sync_timeline *timeline) |
| { |
| <------>return timeline->ops == &mali_timeline_ops; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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) { |
| <------><------><------> |
| <------><------><------> * 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_BIFROST_DEBUG |
| <------><------><------>pr_err("Fences were triggered in a different order to allocation!"); |
| #endif |
| <------><------><------>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 (katom->fence->num_fences != 1) { |
| <------><------> |
| <------><------> * come from us |
| <------><------> */ |
| <------><------>return BASE_JD_EVENT_JOB_CANCELLED; |
| <------>} |
| |
| <------>pt = container_of(katom->fence->cbs[0].sync_pt, struct sync_pt, base); |
| <------>timeline = sync_pt_parent(pt); |
| |
| <------>if (!kbase_sync_timeline_is_ours(timeline)) { |
| <------><------> |
| <------><------>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; |
| |
| <------>return atomic_read(&fence->status); |
| } |
| |
| 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; |
| |
| <------> |
| <------> * If negative then cancel this atom and its dependencies. |
| <------> */ |
| <------>if (kbase_fence_get_status(fence) < 0) |
| <------><------>katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; |
| |
| <------> |
| <------> * 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) { |
| <------><------> |
| <------><------>return 0; |
| <------>} |
| |
| <------>if (ret < 0) { |
| <------><------>katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; |
| <------><------> |
| <------><------> * 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) { |
| <------><------> |
| <------><------> * kbase_fence_wait_callback |
| <------><------> */ |
| <------><------>return; |
| <------>} |
| |
| <------> |
| <------>katom->event_code = BASE_JD_EVENT_JOB_CANCELLED; |
| |
| <------>kbasep_remove_waiting_soft_job(katom); |
| <------>kbase_finish_soft_job(katom); |
| |
| <------>if (kbase_jd_done_nolock(katom, true)) |
| <------><------>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) |
| { |
| <------>u32 string_len; |
| |
| <------>if (!katom->fence) |
| <------><------>return -ENOENT; |
| |
| <------>info->fence = katom->fence; |
| <------>info->status = kbase_fence_get_status(katom->fence); |
| |
| <------>string_len = strscpy(info->name, katom->fence->name, sizeof(info->name)); |
| <------>string_len += sizeof(char); |
| <------> |
| <------>KBASE_DEBUG_ASSERT(string_len <= sizeof(info->name)); |
| <------>CSTD_UNUSED(string_len); |
| |
| <------>return 0; |
| } |
| |
| int kbase_sync_fence_out_info_get(struct kbase_jd_atom *katom, |
| <------><------><------><------> struct kbase_sync_fence_info *info) |
| { |
| <------>u32 string_len; |
| |
| <------>if (!katom->fence) |
| <------><------>return -ENOENT; |
| |
| <------>info->fence = katom->fence; |
| <------>info->status = kbase_fence_get_status(katom->fence); |
| |
| <------>string_len = strscpy(info->name, katom->fence->name, sizeof(info->name)); |
| <------>string_len += sizeof(char); |
| <------> |
| <------>KBASE_DEBUG_ASSERT(string_len <= sizeof(info->name)); |
| <------>CSTD_UNUSED(string_len); |
| |
| <------>return 0; |
| } |
| |
| #ifdef CONFIG_MALI_BIFROST_FENCE_DEBUG |
| void kbase_sync_fence_in_dump(struct kbase_jd_atom *katom) |
| { |
| <------> |
| <------> * 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 |
| #endif |
| |