/* * Copyright (C) 2010-2014, 2016-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. */ /** * @file mali_osk_locks.c * Implemenation of the OS abstraction layer for the kernel device driver */ #include "mali_osk_locks.h" #include "mali_kernel_common.h" #include "mali_osk.h" #ifdef DEBUG #ifdef LOCK_ORDER_CHECKING static DEFINE_SPINLOCK(lock_tracking_lock); static mali_bool add_lock_to_log_and_check(struct _mali_osk_lock_debug_s *lock, uint32_t tid); static void remove_lock_from_log(struct _mali_osk_lock_debug_s *lock, uint32_t tid); static const char *const lock_order_to_string(_mali_osk_lock_order_t order); #endif /* LOCK_ORDER_CHECKING */ void _mali_osk_locks_debug_init(struct _mali_osk_lock_debug_s *checker, _mali_osk_lock_flags_t flags, _mali_osk_lock_order_t order) { checker->orig_flags = flags; checker->owner = 0; #ifdef LOCK_ORDER_CHECKING checker->order = order; checker->next = NULL; #endif } void _mali_osk_locks_debug_add(struct _mali_osk_lock_debug_s *checker) { checker->owner = _mali_osk_get_tid(); #ifdef LOCK_ORDER_CHECKING if (!(checker->orig_flags & _MALI_OSK_LOCKFLAG_UNORDERED)) { if (!add_lock_to_log_and_check(checker, _mali_osk_get_tid())) { printk(KERN_ERR "%d: ERROR lock %p taken while holding a lock of a higher order.\n", _mali_osk_get_tid(), checker); dump_stack(); } } #endif } void _mali_osk_locks_debug_remove(struct _mali_osk_lock_debug_s *checker) { #ifdef LOCK_ORDER_CHECKING if (!(checker->orig_flags & _MALI_OSK_LOCKFLAG_UNORDERED)) { remove_lock_from_log(checker, _mali_osk_get_tid()); } #endif checker->owner = 0; } #ifdef LOCK_ORDER_CHECKING /* Lock order checking * ------------------- * * To assure that lock ordering scheme defined by _mali_osk_lock_order_t is strictly adhered to, the * following function will, together with a linked list and some extra members in _mali_osk_lock_debug_s, * make sure that a lock that is taken has a higher order than the current highest-order lock a * thread holds. * * This is done in the following manner: * - A linked list keeps track of locks held by a thread. * - A `next' pointer is added to each lock. This is used to chain the locks together. * - When taking a lock, the `add_lock_to_log_and_check' makes sure that taking * the given lock is legal. It will follow the linked list to find the last * lock taken by this thread. If the last lock's order was lower than the * lock that is to be taken, it appends the new lock to the list and returns * true, if not, it return false. This return value is assert()'ed on in * _mali_osk_lock_wait(). */ static struct _mali_osk_lock_debug_s *lock_lookup_list; static void dump_lock_tracking_list(void) { struct _mali_osk_lock_debug_s *l; u32 n = 1; /* print list for debugging purposes */ l = lock_lookup_list; while (NULL != l) { printk(" [lock: %p, tid_owner: %d, order: %d] ->", l, l->owner, l->order); l = l->next; MALI_DEBUG_ASSERT(n++ < 100); } printk(" NULL\n"); } static int tracking_list_length(void) { struct _mali_osk_lock_debug_s *l; u32 n = 0; l = lock_lookup_list; while (NULL != l) { l = l->next; n++; MALI_DEBUG_ASSERT(n < 100); } return n; } static mali_bool add_lock_to_log_and_check(struct _mali_osk_lock_debug_s *lock, uint32_t tid) { mali_bool ret = MALI_FALSE; _mali_osk_lock_order_t highest_order_for_tid = _MALI_OSK_LOCK_ORDER_FIRST; struct _mali_osk_lock_debug_s *highest_order_lock = (struct _mali_osk_lock_debug_s *)0xbeefbabe; struct _mali_osk_lock_debug_s *l; unsigned long local_lock_flag; u32 len; spin_lock_irqsave(&lock_tracking_lock, local_lock_flag); len = tracking_list_length(); l = lock_lookup_list; if (NULL == l) { /* This is the first lock taken by this thread -- record and return true */ lock_lookup_list = lock; spin_unlock_irqrestore(&lock_tracking_lock, local_lock_flag); return MALI_TRUE; } else { /* Traverse the locks taken and find the lock of the highest order. * Since several threads may hold locks, each lock's owner must be * checked so that locks not owned by this thread can be ignored. */ for (;;) { MALI_DEBUG_ASSERT_POINTER(l); if (tid == l->owner && l->order >= highest_order_for_tid) { highest_order_for_tid = l->order; highest_order_lock = l; } if (NULL != l->next) { l = l->next; } else { break; } } l->next = lock; l->next = NULL; } /* We have now found the highest order lock currently held by this thread and can see if it is * legal to take the requested lock. */ ret = highest_order_for_tid < lock->order; if (!ret) { printk(KERN_ERR "Took lock of order %d (%s) while holding lock of order %d (%s)\n", lock->order, lock_order_to_string(lock->order), highest_order_for_tid, lock_order_to_string(highest_order_for_tid)); dump_lock_tracking_list(); } if (len + 1 != tracking_list_length()) { printk(KERN_ERR "************ lock: %p\n", lock); printk(KERN_ERR "************ before: %d *** after: %d ****\n", len, tracking_list_length()); dump_lock_tracking_list(); MALI_DEBUG_ASSERT_POINTER(NULL); } spin_unlock_irqrestore(&lock_tracking_lock, local_lock_flag); return ret; } static void remove_lock_from_log(struct _mali_osk_lock_debug_s *lock, uint32_t tid) { struct _mali_osk_lock_debug_s *curr; struct _mali_osk_lock_debug_s *prev = NULL; unsigned long local_lock_flag; u32 len; u32 n = 0; spin_lock_irqsave(&lock_tracking_lock, local_lock_flag); len = tracking_list_length(); curr = lock_lookup_list; if (NULL == curr) { printk(KERN_ERR "Error: Lock tracking list was empty on call to remove_lock_from_log\n"); dump_lock_tracking_list(); } MALI_DEBUG_ASSERT_POINTER(curr); while (lock != curr) { prev = curr; MALI_DEBUG_ASSERT_POINTER(curr); curr = curr->next; MALI_DEBUG_ASSERT(n++ < 100); } if (NULL == prev) { lock_lookup_list = curr->next; } else { MALI_DEBUG_ASSERT_POINTER(curr); MALI_DEBUG_ASSERT_POINTER(prev); prev->next = curr->next; } lock->next = NULL; if (len - 1 != tracking_list_length()) { printk(KERN_ERR "************ lock: %p\n", lock); printk(KERN_ERR "************ before: %d *** after: %d ****\n", len, tracking_list_length()); dump_lock_tracking_list(); MALI_DEBUG_ASSERT_POINTER(NULL); } spin_unlock_irqrestore(&lock_tracking_lock, local_lock_flag); } static const char *const lock_order_to_string(_mali_osk_lock_order_t order) { switch (order) { case _MALI_OSK_LOCK_ORDER_SESSIONS: return "_MALI_OSK_LOCK_ORDER_SESSIONS"; break; case _MALI_OSK_LOCK_ORDER_MEM_SESSION: return "_MALI_OSK_LOCK_ORDER_MEM_SESSION"; break; case _MALI_OSK_LOCK_ORDER_MEM_INFO: return "_MALI_OSK_LOCK_ORDER_MEM_INFO"; break; case _MALI_OSK_LOCK_ORDER_MEM_PT_CACHE: return "_MALI_OSK_LOCK_ORDER_MEM_PT_CACHE"; break; case _MALI_OSK_LOCK_ORDER_DESCRIPTOR_MAP: return "_MALI_OSK_LOCK_ORDER_DESCRIPTOR_MAP"; break; case _MALI_OSK_LOCK_ORDER_PM_EXECUTION: return "_MALI_OSK_LOCK_ORDER_PM_EXECUTION"; break; case _MALI_OSK_LOCK_ORDER_EXECUTOR: return "_MALI_OSK_LOCK_ORDER_EXECUTOR"; break; case _MALI_OSK_LOCK_ORDER_TIMELINE_SYSTEM: return "_MALI_OSK_LOCK_ORDER_TIMELINE_SYSTEM"; break; case _MALI_OSK_LOCK_ORDER_SCHEDULER: return "_MALI_OSK_LOCK_ORDER_SCHEDULER"; break; case _MALI_OSK_LOCK_ORDER_SCHEDULER_DEFERRED: return "_MALI_OSK_LOCK_ORDER_SCHEDULER_DEFERRED"; break; case _MALI_OSK_LOCK_ORDER_DMA_COMMAND: return "_MALI_OSK_LOCK_ORDER_DMA_COMMAND"; break; case _MALI_OSK_LOCK_ORDER_PROFILING: return "_MALI_OSK_LOCK_ORDER_PROFILING"; break; case _MALI_OSK_LOCK_ORDER_L2: return "_MALI_OSK_LOCK_ORDER_L2"; break; case _MALI_OSK_LOCK_ORDER_L2_COMMAND: return "_MALI_OSK_LOCK_ORDER_L2_COMMAND"; break; case _MALI_OSK_LOCK_ORDER_UTILIZATION: return "_MALI_OSK_LOCK_ORDER_UTILIZATION"; break; case _MALI_OSK_LOCK_ORDER_SESSION_PENDING_JOBS: return "_MALI_OSK_LOCK_ORDER_SESSION_PENDING_JOBS"; break; case _MALI_OSK_LOCK_ORDER_PM_STATE: return "_MALI_OSK_LOCK_ORDER_PM_STATE"; break; default: return ""; } } #endif /* LOCK_ORDER_CHECKING */ #endif /* DEBUG */