mISDN/drivers/isdn/hardware/mISDN/hw_lock.h

171 lines
4.6 KiB
C

/* $Id$
*
* hw_lock.h Hardware locking inline routines
*
* Author Karsten Keil (keil@isdn4linux.de)
*
* This file is (c) under GNU PUBLIC LICENSE
*
*/
/* Description of the locking mechanism
*
* The locking must grant serialisized and atomic
* access to the ISDN hardware registers, if the lock
* is aquired no other process or IRQ is alloed to
* access ISDN hardware registers.
*
* In general here are 3 possible entry points:
* 1. the ISDN interrupt routine
* 2. ISDN timer routines in the hardware module
* 3. messages that came from upper layers
*
* Since most work must be do in the interrupt routine
* (to grant minimum IRQ latency) and only few things with
* need direct HW access must be done for messages from upper
* layers, we should allow other IRQs in our IRQ routines and
* only block our own routines in this case. Since the common IRQ
* routines allready mask the same IRQ, we only need to protect us
* from timer and uper layers. The disadvantage is, that we need to
* disable local IRQ for the 2. and 3. points, but since the routines
* which need hardware access are well known and small, the impact
* is very small.
*
* We have a two stage locking to make this working:
* A spinlock which protect the state LOCK Flag (STATE_FLAG_BUSY) and
* also protect us from local IRQs from the entry points 2 and 3.
*
* In the hardware IRQ we aquire the spinlock, set the STATE_FLAG_BUSY
* LOCK Flag and then release the spinlock. It can never happen that
* the STATE_FLAG_BUSY is allready set in this case, see later.
*
* In the other cases (from timer or upper layers) we aquire the spinlock
* test_and_set the STATE_FLAG_BUSY LOCK Flag, if it was allready set
* (a ISDN IRQ is running on the other CPU) we schedule timeout or add a other
* small timeout.
* If it was not set, we have the lock and we don't release the spinlock until we have
* done the harware work.
*
* To avoid any kind of deadlocking, it is important that we release the lock
* before we call functions that deliver to upper layers.
* To leave the impact of disabled local IRQ small, it is important to only protect
* small areas where hardware is accessed.
*
* The following routines handle the lock in the entry point from upper layers and other
* none IRQ cases (module init/exit stuff).
*
* They never called directly, but via the wrappers assigned to theinstance
* inst.lock / inst.unlock pointers.
*
* Here are two defines which can be used for DEBUGING and PROFILING
* SPIN_DEBUG and LOCK_STATISTIC
*
*/
#ifndef __hw_lock__
#define __hw_lock__
typedef struct _hisax_HWlock {
u_long flags;
spinlock_t lock;
#ifdef SPIN_DEBUG
void *spin_adr;
void *busy_adr;
#endif
volatile u_int state;
#ifdef LOCK_STATISTIC
u_int try_ok;
u_int try_wait;
u_int try_inirq;
u_int try_mult;
u_int irq_ok;
u_int irq_fail;
#endif
} hisax_HWlock_t;
#define STATE_FLAG_BUSY 1
#define STATE_FLAG_INIRQ 2
static inline void lock_HW(hisax_HWlock_t *lock)
{
register u_long flags;
#ifdef LOCK_STATISTIC
int wait = 0;
#endif
spin_lock_irqsave(&lock->lock, flags);
#ifdef SPIN_DEBUG
lock->spin_adr = __builtin_return_address(0);
#endif
while (test_and_set_bit(STATE_FLAG_BUSY, &lock->state)) {
/* allready busy so we delay */
spin_unlock_irqrestore(&lock->lock, flags);
#ifdef SPIN_DEBUG
lock->spin_adr = NULL;
#endif
/* delay 1 jiffie is enought */
if (in_interrupt()) {
#ifdef LOCK_STATISTIC
lock->try_inirq++;
#endif
printk(KERN_ERR "lock_HW: try to schedule in IRQ state(%x)\n",
lock->state);
mdelay(1);
} else {
#ifdef LOCK_STATISTIC
if (wait++)
lock->try_mult++;
else
lock->try_wait++;
#endif
schedule_timeout(1);
}
spin_lock_irqsave(&lock->lock, flags);
#ifdef SPIN_DEBUG
lock->spin_adr = __builtin_return_address(0);
#endif
}
/* get the LOCK */
lock->flags = flags;
#ifdef SPIN_DEBUG
lock->busy_adr = __builtin_return_address(0);
#endif
#ifdef LOCK_STATISTIC
if (!wait)
lock->try_ok++;
#endif
}
static inline void unlock_HW(hisax_HWlock_t *lock)
{
if (!test_and_clear_bit(STATE_FLAG_BUSY, &lock->state)) {
printk(KERN_ERR "lock_HW: STATE_FLAG_BUSY not locked state(%x)\n",
lock->state);
}
#ifdef SPIN_DEBUG
lock->busy_adr = NULL;
lock->spin_adr = NULL;
#endif
spin_unlock_irqrestore(&lock->lock, lock->flags);
}
static inline void lock_HW_init(hisax_HWlock_t *lock)
{
spin_lock_init(&lock->lock);
lock->state = 0;
#ifdef SPIN_DEBUG
lock->busy_adr = NULL;
lock->spin_adr = NULL;
#endif
#ifdef LOCK_STATISTIC
lock->try_ok = 0;
lock->try_wait = 0;
lock->try_inirq = 0;
lock->try_mult = 0;
lock->irq_ok = 0;
lock->irq_fail = 0;
#endif
}
#endif