/* * Copyright (C) 2012 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. */ /** * @file mali_sync.c * */ #include #include #include #include "mali_osk.h" #include "mali_kernel_common.h" struct mali_sync_timeline { struct sync_timeline timeline; atomic_t counter; atomic_t signalled; }; struct mali_sync_pt { struct sync_pt pt; u32 order; s32 error; struct timer_list timer; }; static void mali_sync_timed_pt_timeout(unsigned long data); static inline struct mali_sync_timeline *to_mali_sync_timeline(struct sync_timeline *timeline) { return container_of(timeline, struct mali_sync_timeline, timeline); } static inline 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(pt->parent, sizeof(struct mali_sync_pt)); if (!new_pt) { return NULL; } new_mpt = to_mali_sync_pt(new_pt); new_mpt->order = mpt->order; 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(pt->parent); long diff; if (0 != mpt->error) { return mpt->error; } diff = atomic_read(&mtl->signalled) - mpt->order; return diff >= 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); long diff = ma->order - mb->order; if (diff < 0) { return -1; } else if (diff == 0) { return 0; } else { return 1; } } static void timeline_free_pt(struct sync_pt *pt) { struct mali_sync_pt *mpt = to_mali_sync_pt(pt); if (mpt->timer.function == mali_sync_timed_pt_timeout) { del_timer_sync(&mpt->timer); } } static void timeline_print_tl(struct seq_file *s, struct sync_timeline *sync_timeline) { struct mali_sync_timeline *mtl = to_mali_sync_timeline(sync_timeline); seq_printf(s, "%u, %u", atomic_read(&mtl->signalled), atomic_read(&mtl->counter)); } static void timeline_print_pt(struct seq_file *s, struct sync_pt *sync_pt) { struct mali_sync_pt *mpt = to_mali_sync_pt(sync_pt); seq_printf(s, "%u", mpt->order); } static struct sync_timeline_ops mali_timeline_ops = { .driver_name = "Mali", .dup = timeline_dup, .has_signaled = timeline_has_signaled, .compare = timeline_compare, .free_pt = timeline_free_pt, .print_obj = timeline_print_tl, .print_pt = timeline_print_pt }; int mali_sync_timeline_is_ours(struct sync_timeline *timeline) { return (timeline->ops == &mali_timeline_ops); } 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->signalled, 0); return tl; } struct sync_pt *mali_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->error = 0; return pt; } static void mali_sync_timed_pt_timeout(unsigned long data) { struct sync_pt *pt = (struct sync_pt *)data; MALI_DEBUG_ASSERT_POINTER(pt); mali_sync_signal_pt(pt, -ETIME); } struct sync_pt *mali_sync_timed_pt_alloc(struct sync_timeline *parent) { struct sync_pt *pt; struct mali_sync_pt *mpt; const u32 timeout = msecs_to_jiffies(MALI_SYNC_TIMED_FENCE_TIMEOUT); pt = mali_sync_pt_alloc(parent); if (NULL == pt) return NULL; mpt = to_mali_sync_pt(pt); init_timer(&mpt->timer); mpt->timer.function = mali_sync_timed_pt_timeout; mpt->timer.data = (unsigned long)pt; mpt->timer.expires = jiffies + timeout; add_timer(&mpt->timer); return pt; } /* * Returns 0 if sync_pt has been committed and are ready for use, -ETIME if * timeout already happened and the fence has been signalled. * * If an error occurs the sync point can not be used. */ int mali_sync_timed_commit(struct sync_pt *pt) { struct mali_sync_pt *mpt = to_mali_sync_pt(pt); int ret; if (!mali_sync_timeline_is_ours(pt->parent)) { return -EINVAL; } /* Stop timer */ ret = del_timer_sync(&mpt->timer); if (0 == ret) { return -ETIME; } MALI_DEBUG_ASSERT(0 == timeline_has_signaled(pt)); return 0; } void mali_sync_signal_pt(struct sync_pt *pt, int error) { struct mali_sync_pt *mpt = to_mali_sync_pt(pt); struct mali_sync_timeline *mtl = to_mali_sync_timeline(pt->parent); int signalled; long diff; if (0 != error) { MALI_DEBUG_ASSERT(0 > error); mpt->error = error; } do { signalled = atomic_read(&mtl->signalled); diff = signalled - mpt->order; if (diff > 0) { /* The timeline is already at or ahead of this point. This should not happen unless userspace * has been signalling 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. */ MALI_DEBUG_PRINT_ERROR(("Sync points were triggerd in a different order to allocation!\n")); return; } } while (atomic_cmpxchg(&mtl->signalled, signalled, mpt->order) != signalled); sync_timeline_signal(pt->parent); }