2005-04-17 02:20:36 +04:00
/**
2005-09-25 08:28:13 +04:00
* \ file drm_lock . c
2005-04-17 02:20:36 +04:00
* IOCTLs for locking
2005-09-25 08:28:13 +04:00
*
2005-04-17 02:20:36 +04:00
* \ author Rickard E . ( Rik ) Faith < faith @ valinux . com >
* \ author Gareth Hughes < gareth @ valinux . com >
*/
/*
* Created : Tue Feb 2 08 : 37 : 54 1999 by faith @ valinux . com
*
* Copyright 1999 Precision Insight , Inc . , Cedar Park , Texas .
* Copyright 2000 VA Linux Systems , Inc . , Sunnyvale , California .
* All Rights Reserved .
*
* Permission is hereby granted , free of charge , to any person obtaining a
* copy of this software and associated documentation files ( the " Software " ) ,
* to deal in the Software without restriction , including without limitation
* the rights to use , copy , modify , merge , publish , distribute , sublicense ,
* and / or sell copies of the Software , and to permit persons to whom the
* Software is furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice ( including the next
* paragraph ) shall be included in all copies or substantial portions of the
* Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL
* VA LINUX SYSTEMS AND / OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM , DAMAGES OR
* OTHER LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE ,
* ARISING FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE .
*/
2011-12-22 23:09:01 +04:00
# include <linux/export.h>
2005-04-17 02:20:36 +04:00
# include "drmP.h"
2005-07-07 15:03:38 +04:00
static int drm_notifier ( void * priv ) ;
2010-08-24 00:53:34 +04:00
static int drm_lock_take ( struct drm_lock_data * lock_data , unsigned int context ) ;
2005-09-25 08:28:13 +04:00
/**
2005-04-17 02:20:36 +04:00
* Lock ioctl .
*
* \ param inode device inode .
2007-08-25 14:23:09 +04:00
* \ param file_priv DRM file private .
2005-04-17 02:20:36 +04:00
* \ param cmd command .
* \ param arg user argument , pointing to a drm_lock structure .
* \ return zero on success or negative number on failure .
*
* Add the current task to the lock wait queue , and attempt to take to lock .
*/
2007-09-03 06:06:45 +04:00
int drm_lock ( struct drm_device * dev , void * data , struct drm_file * file_priv )
2005-04-17 02:20:36 +04:00
{
2005-09-25 08:28:13 +04:00
DECLARE_WAITQUEUE ( entry , current ) ;
2007-09-03 06:06:45 +04:00
struct drm_lock * lock = data ;
2008-11-28 07:22:24 +03:00
struct drm_master * master = file_priv - > master ;
2005-09-25 08:28:13 +04:00
int ret = 0 ;
2005-04-17 02:20:36 +04:00
2007-08-25 14:23:09 +04:00
+ + file_priv - > lock_count ;
2005-04-17 02:20:36 +04:00
2007-09-03 06:06:45 +04:00
if ( lock - > context = = DRM_KERNEL_CONTEXT ) {
2005-09-25 08:28:13 +04:00
DRM_ERROR ( " Process %d using kernel context %d \n " ,
2007-10-19 10:40:40 +04:00
task_pid_nr ( current ) , lock - > context ) ;
2005-09-25 08:28:13 +04:00
return - EINVAL ;
}
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
DRM_DEBUG ( " %d (pid %d) requests lock (0x%08x), flags = 0x%08x \n " ,
2007-10-19 10:40:40 +04:00
lock - > context , task_pid_nr ( current ) ,
2008-11-28 07:22:24 +03:00
master - > lock . hw_lock - > lock , lock - > flags ) ;
2005-04-17 02:20:36 +04:00
2008-11-28 07:22:24 +03:00
add_wait_queue ( & master - > lock . lock_queue , & entry ) ;
spin_lock_bh ( & master - > lock . spinlock ) ;
master - > lock . user_waiters + + ;
spin_unlock_bh ( & master - > lock . spinlock ) ;
2005-04-17 02:20:36 +04:00
for ( ; ; ) {
__set_current_state ( TASK_INTERRUPTIBLE ) ;
2008-11-28 07:22:24 +03:00
if ( ! master - > lock . hw_lock ) {
2005-04-17 02:20:36 +04:00
/* Device has been unregistered */
2009-03-02 13:10:56 +03:00
send_sig ( SIGTERM , current , 0 ) ;
2005-04-17 02:20:36 +04:00
ret = - EINTR ;
break ;
}
2008-11-28 07:22:24 +03:00
if ( drm_lock_take ( & master - > lock , lock - > context ) ) {
master - > lock . file_priv = file_priv ;
master - > lock . lock_time = jiffies ;
2005-09-25 08:28:13 +04:00
atomic_inc ( & dev - > counts [ _DRM_STAT_LOCKS ] ) ;
break ; /* Got lock */
2005-04-17 02:20:36 +04:00
}
2005-09-25 08:28:13 +04:00
2005-04-17 02:20:36 +04:00
/* Contention */
2010-08-27 02:55:28 +04:00
mutex_unlock ( & drm_global_mutex ) ;
2005-04-17 02:20:36 +04:00
schedule ( ) ;
2010-08-27 02:55:28 +04:00
mutex_lock ( & drm_global_mutex ) ;
2005-09-25 08:28:13 +04:00
if ( signal_pending ( current ) ) {
2009-03-02 13:10:54 +03:00
ret = - EINTR ;
2005-04-17 02:20:36 +04:00
break ;
}
}
2008-11-28 07:22:24 +03:00
spin_lock_bh ( & master - > lock . spinlock ) ;
master - > lock . user_waiters - - ;
spin_unlock_bh ( & master - > lock . spinlock ) ;
2005-04-17 02:20:36 +04:00
__set_current_state ( TASK_RUNNING ) ;
2008-11-28 07:22:24 +03:00
remove_wait_queue ( & master - > lock . lock_queue , & entry ) ;
2005-04-17 02:20:36 +04:00
2007-09-03 06:06:45 +04:00
DRM_DEBUG ( " %d %s \n " , lock - > context ,
ret ? " interrupted " : " has lock " ) ;
2007-03-23 05:28:33 +03:00
if ( ret ) return ret ;
2006-01-02 13:32:48 +03:00
2008-08-24 11:02:26 +04:00
/* don't set the block all signals on the master process for now
* really probably not the correct answer but lets us debug xkb
* xserver for now */
2008-11-28 07:22:24 +03:00
if ( ! file_priv - > is_master ) {
2008-08-24 11:02:26 +04:00
sigemptyset ( & dev - > sigmask ) ;
sigaddset ( & dev - > sigmask , SIGSTOP ) ;
sigaddset ( & dev - > sigmask , SIGTSTP ) ;
sigaddset ( & dev - > sigmask , SIGTTIN ) ;
sigaddset ( & dev - > sigmask , SIGTTOU ) ;
dev - > sigdata . context = lock - > context ;
2008-11-28 07:22:24 +03:00
dev - > sigdata . lock = master - > lock . hw_lock ;
2008-08-24 11:02:26 +04:00
block_all_signals ( drm_notifier , & dev - > sigdata , & dev - > sigmask ) ;
}
2005-09-25 08:28:13 +04:00
2007-09-03 06:06:45 +04:00
if ( dev - > driver - > dma_quiescent & & ( lock - > flags & _DRM_LOCK_QUIESCENT ) )
{
2006-01-02 13:32:48 +03:00
if ( dev - > driver - > dma_quiescent ( dev ) ) {
2007-09-03 06:06:45 +04:00
DRM_DEBUG ( " %d waiting for DMA quiescent \n " ,
lock - > context ) ;
2007-08-25 13:22:43 +04:00
return - EBUSY ;
2006-01-02 13:32:48 +03:00
}
}
2005-09-25 08:28:13 +04:00
2006-01-02 13:32:48 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2005-09-25 08:28:13 +04:00
/**
2005-04-17 02:20:36 +04:00
* Unlock ioctl .
*
* \ param inode device inode .
2007-08-25 14:23:09 +04:00
* \ param file_priv DRM file private .
2005-04-17 02:20:36 +04:00
* \ param cmd command .
* \ param arg user argument , pointing to a drm_lock structure .
* \ return zero on success or negative number on failure .
*
* Transfer and free the lock .
*/
2007-09-03 06:06:45 +04:00
int drm_unlock ( struct drm_device * dev , void * data , struct drm_file * file_priv )
2005-04-17 02:20:36 +04:00
{
2007-09-03 06:06:45 +04:00
struct drm_lock * lock = data ;
2010-09-26 02:24:48 +04:00
struct drm_master * master = file_priv - > master ;
2005-04-17 02:20:36 +04:00
2007-09-03 06:06:45 +04:00
if ( lock - > context = = DRM_KERNEL_CONTEXT ) {
2005-09-25 08:28:13 +04:00
DRM_ERROR ( " Process %d using kernel context %d \n " ,
2007-10-19 10:40:40 +04:00
task_pid_nr ( current ) , lock - > context ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
2005-09-25 08:28:13 +04:00
atomic_inc ( & dev - > counts [ _DRM_STAT_UNLOCKS ] ) ;
2005-04-17 02:20:36 +04:00
2010-09-26 02:24:48 +04:00
if ( drm_lock_free ( & master - > lock , lock - > context ) ) {
/* FIXME: Should really bail out here. */
}
2005-04-17 02:20:36 +04:00
unblock_all_signals ( ) ;
return 0 ;
}
/**
* Take the heavyweight lock .
*
* \ param lock lock pointer .
* \ param context locking context .
* \ return one if the lock is held , or zero otherwise .
*
* Attempt to mark the lock as held by the given context , via the \ p cmpxchg instruction .
*/
2010-08-24 00:53:34 +04:00
static
2007-07-11 10:53:40 +04:00
int drm_lock_take ( struct drm_lock_data * lock_data ,
2007-03-23 05:28:33 +03:00
unsigned int context )
2005-04-17 02:20:36 +04:00
{
unsigned int old , new , prev ;
2007-03-23 05:28:33 +03:00
volatile unsigned int * lock = & lock_data - > hw_lock - > lock ;
2005-04-17 02:20:36 +04:00
2008-05-07 06:22:39 +04:00
spin_lock_bh ( & lock_data - > spinlock ) ;
2005-04-17 02:20:36 +04:00
do {
old = * lock ;
2005-09-25 08:28:13 +04:00
if ( old & _DRM_LOCK_HELD )
new = old | _DRM_LOCK_CONT ;
2007-03-23 05:28:33 +03:00
else {
new = context | _DRM_LOCK_HELD |
( ( lock_data - > user_waiters + lock_data - > kernel_waiters > 1 ) ?
_DRM_LOCK_CONT : 0 ) ;
}
2005-04-17 02:20:36 +04:00
prev = cmpxchg ( lock , old , new ) ;
} while ( prev ! = old ) ;
2008-05-07 06:22:39 +04:00
spin_unlock_bh ( & lock_data - > spinlock ) ;
2007-03-23 05:28:33 +03:00
2005-04-17 02:20:36 +04:00
if ( _DRM_LOCKING_CONTEXT ( old ) = = context ) {
if ( old & _DRM_LOCK_HELD ) {
if ( context ! = DRM_KERNEL_CONTEXT ) {
DRM_ERROR ( " %d holds heavyweight lock \n " ,
context ) ;
}
return 0 ;
}
}
2007-03-23 05:28:33 +03:00
if ( ( _DRM_LOCKING_CONTEXT ( new ) ) = = context & & ( new & _DRM_LOCK_HELD ) ) {
2005-09-25 08:28:13 +04:00
/* Have lock */
2005-04-17 02:20:36 +04:00
return 1 ;
}
return 0 ;
}
/**
* This takes a lock forcibly and hands it to context . Should ONLY be used
2005-09-25 08:28:13 +04:00
* inside * _unlock to give lock to kernel before calling * _dma_schedule .
*
2005-04-17 02:20:36 +04:00
* \ param dev DRM device .
* \ param lock lock pointer .
* \ param context locking context .
* \ return always one .
*
* Resets the lock file pointer .
* Marks the lock as held by the given context , via the \ p cmpxchg instruction .
*/
2007-07-11 10:53:40 +04:00
static int drm_lock_transfer ( struct drm_lock_data * lock_data ,
2005-07-07 15:03:38 +04:00
unsigned int context )
2005-04-17 02:20:36 +04:00
{
unsigned int old , new , prev ;
2007-03-23 05:28:33 +03:00
volatile unsigned int * lock = & lock_data - > hw_lock - > lock ;
2005-04-17 02:20:36 +04:00
2007-08-25 14:23:09 +04:00
lock_data - > file_priv = NULL ;
2005-04-17 02:20:36 +04:00
do {
2005-09-25 08:28:13 +04:00
old = * lock ;
new = context | _DRM_LOCK_HELD ;
2005-04-17 02:20:36 +04:00
prev = cmpxchg ( lock , old , new ) ;
} while ( prev ! = old ) ;
return 1 ;
}
/**
* Free lock .
2005-09-25 08:28:13 +04:00
*
2005-04-17 02:20:36 +04:00
* \ param dev DRM device .
* \ param lock lock .
* \ param context context .
2005-09-25 08:28:13 +04:00
*
2005-04-17 02:20:36 +04:00
* Resets the lock file pointer .
* Marks the lock as not held , via the \ p cmpxchg instruction . Wakes any task
* waiting on the lock queue .
*/
2007-07-11 10:53:40 +04:00
int drm_lock_free ( struct drm_lock_data * lock_data , unsigned int context )
2005-04-17 02:20:36 +04:00
{
unsigned int old , new , prev ;
2007-03-23 05:28:33 +03:00
volatile unsigned int * lock = & lock_data - > hw_lock - > lock ;
2008-05-07 06:22:39 +04:00
spin_lock_bh ( & lock_data - > spinlock ) ;
2007-03-23 05:28:33 +03:00
if ( lock_data - > kernel_waiters ! = 0 ) {
drm_lock_transfer ( lock_data , 0 ) ;
lock_data - > idle_has_lock = 1 ;
2008-05-07 06:22:39 +04:00
spin_unlock_bh ( & lock_data - > spinlock ) ;
2007-03-23 05:28:33 +03:00
return 1 ;
}
2008-05-07 06:22:39 +04:00
spin_unlock_bh ( & lock_data - > spinlock ) ;
2005-04-17 02:20:36 +04:00
do {
2005-09-25 08:28:13 +04:00
old = * lock ;
2007-03-23 05:28:33 +03:00
new = _DRM_LOCKING_CONTEXT ( old ) ;
2005-04-17 02:20:36 +04:00
prev = cmpxchg ( lock , old , new ) ;
} while ( prev ! = old ) ;
2007-03-23 05:28:33 +03:00
2005-04-17 02:20:36 +04:00
if ( _DRM_LOCK_IS_HELD ( old ) & & _DRM_LOCKING_CONTEXT ( old ) ! = context ) {
DRM_ERROR ( " %d freed heavyweight lock held by %d \n " ,
2005-09-25 08:28:13 +04:00
context , _DRM_LOCKING_CONTEXT ( old ) ) ;
2005-04-17 02:20:36 +04:00
return 1 ;
}
2007-03-23 05:28:33 +03:00
wake_up_interruptible ( & lock_data - > lock_queue ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/**
* If we get here , it means that the process has called DRM_IOCTL_LOCK
* without calling DRM_IOCTL_UNLOCK .
*
* If the lock is not held , then let the signal proceed as usual . If the lock
* is held , then set the contended flag and keep the signal blocked .
*
* \ param priv pointer to a drm_sigdata structure .
* \ return one if the signal should be delivered normally , or zero if the
* signal should be blocked .
*/
2005-07-07 15:03:38 +04:00
static int drm_notifier ( void * priv )
2005-04-17 02:20:36 +04:00
{
2007-07-11 10:53:40 +04:00
struct drm_sigdata * s = ( struct drm_sigdata * ) priv ;
2005-09-25 08:28:13 +04:00
unsigned int old , new , prev ;
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
/* Allow signal delivery if lock isn't held */
2005-04-17 02:20:36 +04:00
if ( ! s - > lock | | ! _DRM_LOCK_IS_HELD ( s - > lock - > lock )
2005-09-25 08:28:13 +04:00
| | _DRM_LOCKING_CONTEXT ( s - > lock - > lock ) ! = s - > context )
return 1 ;
2005-04-17 02:20:36 +04:00
2005-09-25 08:28:13 +04:00
/* Otherwise, set flag to force call to
drmUnlock */
2005-04-17 02:20:36 +04:00
do {
2005-09-25 08:28:13 +04:00
old = s - > lock - > lock ;
new = old | _DRM_LOCK_CONT ;
2005-04-17 02:20:36 +04:00
prev = cmpxchg ( & s - > lock - > lock , old , new ) ;
} while ( prev ! = old ) ;
return 0 ;
}
2007-03-23 05:28:33 +03:00
/**
* This function returns immediately and takes the hw lock
* with the kernel context if it is free , otherwise it gets the highest priority when and if
* it is eventually released .
*
* This guarantees that the kernel will _eventually_ have the lock _unless_ it is held
* by a blocked process . ( In the latter case an explicit wait for the hardware lock would cause
* a deadlock , which is why the " idlelock " was invented ) .
*
* This should be sufficient to wait for GPU idle without
* having to worry about starvation .
*/
2007-07-11 10:53:40 +04:00
void drm_idlelock_take ( struct drm_lock_data * lock_data )
2007-03-23 05:28:33 +03:00
{
2012-05-17 15:27:21 +04:00
int ret ;
2007-03-23 05:28:33 +03:00
2008-05-07 06:22:39 +04:00
spin_lock_bh ( & lock_data - > spinlock ) ;
2007-03-23 05:28:33 +03:00
lock_data - > kernel_waiters + + ;
if ( ! lock_data - > idle_has_lock ) {
2008-05-07 06:22:39 +04:00
spin_unlock_bh ( & lock_data - > spinlock ) ;
2007-03-23 05:28:33 +03:00
ret = drm_lock_take ( lock_data , DRM_KERNEL_CONTEXT ) ;
2008-05-07 06:22:39 +04:00
spin_lock_bh ( & lock_data - > spinlock ) ;
2007-03-23 05:28:33 +03:00
if ( ret = = 1 )
lock_data - > idle_has_lock = 1 ;
}
2008-05-07 06:22:39 +04:00
spin_unlock_bh ( & lock_data - > spinlock ) ;
2007-03-23 05:28:33 +03:00
}
2011-12-22 23:09:01 +04:00
EXPORT_SYMBOL ( drm_idlelock_take ) ;
2007-03-23 05:28:33 +03:00
2007-07-11 10:53:40 +04:00
void drm_idlelock_release ( struct drm_lock_data * lock_data )
2007-03-23 05:28:33 +03:00
{
unsigned int old , prev ;
volatile unsigned int * lock = & lock_data - > hw_lock - > lock ;
2008-05-07 06:22:39 +04:00
spin_lock_bh ( & lock_data - > spinlock ) ;
2007-03-23 05:28:33 +03:00
if ( - - lock_data - > kernel_waiters = = 0 ) {
if ( lock_data - > idle_has_lock ) {
do {
old = * lock ;
prev = cmpxchg ( lock , old , DRM_KERNEL_CONTEXT ) ;
} while ( prev ! = old ) ;
wake_up_interruptible ( & lock_data - > lock_queue ) ;
lock_data - > idle_has_lock = 0 ;
}
}
2008-05-07 06:22:39 +04:00
spin_unlock_bh ( & lock_data - > spinlock ) ;
2007-03-23 05:28:33 +03:00
}
2011-12-22 23:09:01 +04:00
EXPORT_SYMBOL ( drm_idlelock_release ) ;
2007-03-23 05:28:33 +03:00
2007-09-03 06:06:45 +04:00
int drm_i_have_hw_lock ( struct drm_device * dev , struct drm_file * file_priv )
2007-03-23 05:28:33 +03:00
{
2008-11-28 07:22:24 +03:00
struct drm_master * master = file_priv - > master ;
return ( file_priv - > lock_count & & master - > lock . hw_lock & &
_DRM_LOCK_IS_HELD ( master - > lock . hw_lock - > lock ) & &
master - > lock . file_priv = = file_priv ) ;
2007-03-23 05:28:33 +03:00
}