fixed Forbid fallback
[aros:aros.git] / AROS / rom / exec / lockspin.c
1 /*
2     Copyright © 2013, The AROS Development Team. All rights reserved.
3     $Id$
4
5     Desc: Lock a spinlock
6     Lang: english
7 */
8
9 #define DEBUG 1
10
11 #include <aros/debug.h>
12 #include <proto/exec.h>
13
14 #include "exec_intern.h"
15
16 #define MIN_PRI -120
17
18 /*****************************************************************************
19
20     NAME */
21
22         AROS_LH1I(void, LockSpin,
23
24 /*  SYNOPSIS */
25         AROS_LHA(APTR, aspin, A0),
26
27 /*  LOCATION */
28         struct ExecBase *, SysBase, 187, Exec)
29
30 /*  FUNCTION
31         Lock a spinlock exclusively. If the lock is already held
32         by another task, busy wait until the lock is free again.
33         If more than one task waits for an unlock, it is not
34         predictable, which task will get the lock next.
35
36     INPUTS
37         spin - Pointer to spin (TODO: allocated with AllocSpin)
38
39     RESULT
40
41     NOTES
42         A spinlock does nothing more than to busy wait until
43         the spin variable is 0. If it is 0, it sets it to 1 and
44         returns. Spinlocks are protected against concurrency, so
45         they work both in SMP and non SMP systems. On single CPU
46         systems, Forbid() or ObtainSemaphore usually give better
47         performance.
48
49         Like Forbid/Permit every single LockSpin must be followed by
50         one UnlockSpin. Only the task, which locked the spin
51         is allowed to free it again.
52
53     EXAMPLE
54
55     BUGS
56         If a task below priority MIN_PRI (-120) holds a lock, a deadlock
57         may happen.
58
59     SEE ALSO
60         AllocSpin(), UnlockSpin(), ResetSpin()
61
62     INTERNALS
63         Problem on AROS is, that a busy waiting task with a higher
64         priority never gives any CPU time to lower priority tasks.
65         So a lower priority task, which locked the spin before, has
66         no chance to unlock it again. So a deadlock happens.
67
68         So we *need* to give away some CPU time to other tasks, even
69         if this reduces the reaction time of the spinlock or as an
70         alternative, we need to lower the owning task's priority. 
71
72         On Unix-like systems, a busy waiting tasks is scheduled with
73         a lower priority automatically by the scheduler. As the AROS
74         scheduler does not do this, we lower our priority ourselves.
75         As soon as we got the lock, we resume with the original 
76         priority.
77
78 *****************************************************************************/
79 {
80     AROS_LIBFUNC_INIT
81
82     struct SpinLock *spin=(struct SpinLock *) aspin;
83     struct Task *thistask=FindTask(NULL);
84     /* get current task priority (without changing it) */
85     BYTE org_priority;
86     BYTE akt_priority;
87     char *taskname; /* TODO: remove it again */
88     ULONG count=0;
89
90     /* allowed to be called early */
91     /* a spinlock once locked with Forbid needs following Forbid locks, too */
92     if((thistask == NULL) || (spin->owner == (struct Task *) -1) )
93 #TODO
94     {
95         /* if we are already locked, we just increase the count, even if thistask is NULL
96          * if we are not locked ATM, we try Forbid(). 
97          */
98         if(spin->nest == 0)
99         {
100           D(bug("[LOCKSPIN] thistask is NULL, fallback to Forbid()\n"));
101           /* don't call Forbid(), as this calls LockSpin itself */
102           KrnScheduling(KSCHED_FORBID);
103           spin->owner=(struct Task *) -1;
104         }
105         spin->nest++;
106         return;
107     }
108
109     org_priority=thistask->tc_Node.ln_Pri;
110     akt_priority=org_priority;
111     taskname=thistask->tc_Node.ln_Name;
112
113     /* no need to lock this access, either it is us, then there can't be a race or
114      * it is NULL/another task, then the owner is different
115      */
116     if(spin->owner==thistask) 
117     {
118         D(bug("[LOCKSPIN] %lx reentry #%d of task %p (%s)\n", spin, spin->nest, thistask, taskname));
119         spin->nest++;
120         return;
121     }
122     else
123     {
124         D(bug("[LOCKSPIN] %lx task %p (%s) locking me ..\n", spin, thistask, taskname));
125         //D(bug("[LOCKSPIN] %lx task %p (%s) locking me from 0x%p / 0x%p / 0x%p / 0x%p / 0x%p / 0x%p / 0x%p\n", spin, thistask, taskname, __builtin_return_address (1), __builtin_return_address (2), __builtin_return_address (3), __builtin_return_address (4), __builtin_return_address (5), __builtin_return_address (6), __builtin_return_address (7)));
126     }
127
128     while( __sync_lock_test_and_set(&spin->lock, 1) ) 
129     {
130         count++;
131         /* If we are busy far too long, lower our priority, if still possible */
132         /* TODO: Is 0xFFFF a good value? more tests need to be done here */
133         if(count>0xFFFF) 
134         {
135             count=0;
136
137             if(akt_priority>MIN_PRI+5) 
138             {
139                 /* we can go lower */
140                 akt_priority=akt_priority-5;
141                 if(akt_priority <= MIN_PRI) 
142                 {
143                     /* reached lower end */
144                     akt_priority=MIN_PRI;
145                 }
146                 /* lower priority
147                  * we could access owner->tc_Node.ln_Pri, but there is a slight chance, that
148                  * the owner tasks ends, before we get the priority. And we can't use Forbid() here
149                  */
150
151                 struct Task *sectask=spin->owner;
152                 /* DEBUG only, owner might be dead already! */
153                 if(sectask && (sectask != (struct Task *) -1))
154                 {
155                   /* might still return dead values, but won't crash at least */
156                   D(bug("[LOCKSPIN] task %p (%s) still holds the spinlock %lx with priority %d\n",
157                         sectask, sectask->tc_Node.ln_Name, spin, sectask->tc_Node.ln_Pri));
158                 }
159                 else {
160                     bug("[LOCKSPIN] ERROR: spin->owner is NULL, but spinlock %lx is still locked\n", spin);
161                     bug("[LOCKSPIN] ERROR: BRUTE FORCE UNLOCK OF spinlock %lx\n", spin);
162                     Alert( AN_SpinCorrupt );
163                     /* maybe we are lucky.. */
164                     spin->nest=0;
165                     __sync_synchronize();
166                     spin->lock=0;
167                 }
168
169
170                 D(bug("[LOCKSPIN] lower this task %lx (%s) to priority %d\n", 
171                        thistask, taskname, akt_priority));
172                 SetTaskPri(thistask, akt_priority);
173             }
174         }
175 #if 0
176         else 
177         {
178             /* relax CPU ?? */
179         }
180 #endif
181     }
182     D(bug("[LOCKSPIN] %lx task %p (%s) locked me!\n", spin, thistask, taskname));
183
184     spin->nest=1;
185     spin->owner=thistask;
186
187     /* reset original priority */
188     if(akt_priority!=org_priority) 
189     {
190         SetTaskPri(thistask, org_priority);
191     }
192
193     //D(bug("[LOCKSPIN] %lx locked by task %p (%s)\n", spin, thistask, taskname));
194
195     AROS_LIBFUNC_EXIT
196 } /* LockSpin */
197