Kernel - Planning SMP fix with sleep edge case
[tpg/acess2.git] / KernelLand / Kernel / semaphore.c
1 /*
2  * Acess2 Kernel
3  * - By John Hodge (thePowersGang)
4  *
5  * semaphore.c
6  * - Semaphores
7  */
8 #include <acess.h>
9 #include <semaphore.h>
10 #include <threads_int.h>
11
12 #define SEMAPHORE_DEBUG         0       // Debug semaphores
13
14 // === CODE ===
15 //
16 // Initialise a semaphore
17 //
18 void Semaphore_Init(tSemaphore *Sem, int Value, int MaxValue, const char *Module, const char *Name)
19 {
20         memset(Sem, 0, sizeof(tSemaphore));
21         Sem->Value = Value;
22         Sem->ModName = Module;
23         Sem->Name = Name;
24         Sem->MaxValue = MaxValue;
25 }
26 //
27 // Wait for items to be avaliable
28 //
29 int Semaphore_Wait(tSemaphore *Sem, int MaxToTake)
30 {
31         tThread *us;
32          int    taken;
33         if( MaxToTake < 0 ) {
34                 Log_Warning("Threads", "Semaphore_Wait: User bug - MaxToTake(%i) < 0, Sem=%p(%s)",
35                         MaxToTake, Sem, Sem->Name);
36                 MaxToTake = 0;
37         }
38         
39         SHORTLOCK( &Sem->Protector );
40         
41         // Check if there's already items avaliable
42         if( Sem->Value > 0 )
43         {
44                 // Take what we need
45                 if( MaxToTake && Sem->Value > MaxToTake )
46                         taken = MaxToTake;
47                 else
48                         taken = Sem->Value;
49                 Sem->Value -= taken;
50         }
51         else
52         {
53                 #if 0
54                 Threads_int_Sleep(THREAD_STAT_SEMAPHORESLEEP,
55                         Sem, MaxToTake,
56                         &Sem->Waiting, &Sem->LastWaiting, &Sem->Protector);
57                 #endif
58                 SHORTLOCK( &glThreadListLock );
59                 
60                 // - Remove from active list
61                 us = Threads_RemActive();
62                 us->Next = NULL;
63                 // - Mark as sleeping
64                 us->Status = THREAD_STAT_SEMAPHORESLEEP;
65                 us->WaitPointer = Sem;
66                 us->RetStatus = MaxToTake;      // Use RetStatus as a temp variable
67                 
68                 // - Add to waiting
69                 if(Sem->LastWaiting) {
70                         Sem->LastWaiting->Next = us;
71                         Sem->LastWaiting = us;
72                 }
73                 else {
74                         Sem->Waiting = us;
75                         Sem->LastWaiting = us;
76                 }
77                 
78                 #if DEBUG_TRACE_STATE || SEMAPHORE_DEBUG
79                 Log("%p (%i %s) waiting on semaphore %p %s:%s",
80                         us, us->TID, us->ThreadName,
81                         Sem, Sem->ModName, Sem->Name);
82                 #endif
83                 SHORTREL( &Sem->Protector );
84                 SHORTREL( &glThreadListLock );
85                 // NOTE: This can break in SMP
86                 // Sleep until woken (either by getting what we need, or a timer event)
87                 Threads_int_WaitForStatusEnd( THREAD_STAT_SEMAPHORESLEEP );
88                 // We're only woken when there's something avaliable (or a signal arrives)
89                 #if DEBUG_TRACE_STATE || SEMAPHORE_DEBUG
90                 Log("Semaphore %p %s:%s woken from wait", Sem, Sem->ModName, Sem->Name);
91                 #endif
92                 us->WaitPointer = NULL;
93                 
94                 taken = us->RetStatus;
95                 
96                 // Get the lock again
97                 SHORTLOCK( &Sem->Protector );
98         }
99         
100         // While there is space, and there are thread waiting
101         // wake the first thread and give it what it wants (or what's left)
102         while( (Sem->MaxValue == 0 || Sem->Value < Sem->MaxValue) && Sem->Signaling )
103         {
104                  int    given;
105                 tThread *toWake = Sem->Signaling;
106                 
107                 Sem->Signaling = Sem->Signaling->Next;
108                 // Reset ->LastWaiting to NULL if we have just removed the last waiting thread
109                 if( Sem->Signaling == NULL )
110                         Sem->LastSignaling = NULL;
111                 
112                 // Figure out how much to give
113                 if( toWake->RetStatus && Sem->Value + toWake->RetStatus < Sem->MaxValue )
114                         given = toWake->RetStatus;
115                 else
116                         given = Sem->MaxValue - Sem->Value;
117                 Sem->Value -= given;
118                 
119                 
120                 #if DEBUG_TRACE_STATE || SEMAPHORE_DEBUG
121                 Log("%p (%i %s) woken by wait on %p %s:%s",
122                         toWake, toWake->TID, toWake->ThreadName,
123                         Sem, Sem->ModName, Sem->Name);
124                 #endif
125                 
126                 // Save the number we gave to the thread's status
127                 toWake->RetStatus = given;
128                 
129                 // Wake the sleeper
130                 if( toWake->Status != THREAD_STAT_ACTIVE )
131                         Threads_AddActive(toWake);
132         }
133         SHORTREL( &Sem->Protector );
134         
135         #if DEBUG_TRACE_STATE || SEMAPHORE_DEBUG
136         Log("Semaphore %p %s:%s took %i by wait",
137                 Sem, Sem->ModName, Sem->Name, taken);
138         #endif
139
140         return taken;
141 }
142
143 //
144 // Add items to a semaphore
145 //
146 int Semaphore_Signal(tSemaphore *Sem, int AmmountToAdd)
147 {
148          int    given;
149          int    added;
150         
151         if( AmmountToAdd < 0 ) {
152                 Log_Warning("Threads", "Semaphore_Signal: User bug - AmmountToAdd(%i) < 0, Sem=%p(%s)",
153                         AmmountToAdd, Sem, Sem->Name);
154         }
155         SHORTLOCK( &Sem->Protector );
156         
157         // Check if we have to block
158         if( Sem->MaxValue && Sem->Value == Sem->MaxValue )
159         {
160                 tThread *us;
161                 #if 0
162                 Log_Debug("Threads", "Semaphore_Signal: IDLE Sem = %s:%s", Sem->ModName, Sem->Name);
163                 Log_Debug("Threads", "Semaphore_Signal: Sem->Value(%i) == Sem->MaxValue(%i)", Sem->Value, Sem->MaxValue);
164                 #endif
165                 
166                 SHORTLOCK( &glThreadListLock );
167                 // - Remove from active list
168                 us = Threads_RemActive();
169                 us->Next = NULL;
170                 // - Mark as sleeping
171                 us->Status = THREAD_STAT_SEMAPHORESLEEP;
172                 us->WaitPointer = Sem;
173                 us->RetStatus = AmmountToAdd;   // Use RetStatus as a temp variable
174                 
175                 // - Add to waiting
176                 if(Sem->LastSignaling) {
177                         Sem->LastSignaling->Next = us;
178                         Sem->LastSignaling = us;
179                 }
180                 else {
181                         Sem->Signaling = us;
182                         Sem->LastSignaling = us;
183                 }
184                 
185                 #if DEBUG_TRACE_STATE || SEMAPHORE_DEBUG
186                 Log("%p (%i %s) signaling semaphore %p %s:%s",
187                         us, us->TID, us->ThreadName,
188                         Sem, Sem->ModName, Sem->Name);
189                 #endif
190                 
191                 SHORTREL( &glThreadListLock );  
192                 SHORTREL( &Sem->Protector );
193                 Threads_int_WaitForStatusEnd(THREAD_STAT_SEMAPHORESLEEP);
194                 // We're only woken when there's something avaliable
195                 us->WaitPointer = NULL;
196                 
197                 added = us->RetStatus;
198                 
199                 // Get the lock again
200                 SHORTLOCK( &Sem->Protector );
201         }
202         // Non blocking
203         else
204         {
205                 // Figure out how much we need to take off
206                 if( Sem->MaxValue && Sem->Value + AmmountToAdd > Sem->MaxValue)
207                         added = Sem->MaxValue - Sem->Value;
208                 else
209                         added = AmmountToAdd;
210                 Sem->Value += added;
211         }
212         
213         // While there are items avaliable, and there are thread waiting
214         // wake the first thread and give it what it wants (or what's left)
215         while( Sem->Value && Sem->Waiting )
216         {
217                 tThread *toWake = Sem->Waiting;
218                 
219                 // Remove thread from list (double ended, so clear LastWaiting if needed)
220                 Sem->Waiting = Sem->Waiting->Next;
221                 if( Sem->Waiting == NULL )
222                         Sem->LastWaiting = NULL;
223                 
224                 // Figure out how much to give to woken thread
225                 // - Requested count is stored in ->RetStatus
226                 if( toWake->RetStatus && Sem->Value > toWake->RetStatus )
227                         given = toWake->RetStatus;
228                 else
229                         given = Sem->Value;
230                 Sem->Value -= given;
231                 
232                 // Save the number we gave to the thread's status
233                 toWake->RetStatus = given;
234                 
235                 if(toWake->bInstrTrace)
236                         Log("%s(%i) given %i from %p", toWake->ThreadName, toWake->TID, given, Sem);
237                 #if DEBUG_TRACE_STATE || SEMAPHORE_DEBUG
238                 Log("%p (%i %s) woken by signal on %p %s:%s",
239                         toWake, toWake->TID, toWake->ThreadName,
240                         Sem, Sem->ModName, Sem->Name);
241                 #endif
242                 
243                 // Wake the sleeper
244                 if( toWake->Status != THREAD_STAT_ACTIVE )
245                         Threads_AddActive(toWake);
246                 else
247                         Warning("Thread %p (%i %s) is already awake",
248                                 toWake, toWake->TID, toWake->ThreadName);
249         }
250         SHORTREL( &Sem->Protector );
251         
252         return added;
253 }
254
255 void Semaphore_ForceWake(tThread *Thread)
256 {
257         if( !CPU_HAS_LOCK(&Thread->IsLocked) ) {
258                 Log_Error("Semaphore", "Force wake should be called with the thread lock held");
259                 return ;
260         }
261         if( Thread->Status != THREAD_STAT_SEMAPHORESLEEP ) {
262                 Log_Error("Semaphore", "_ForceWake called on non-semaphore thread");
263                 return ;
264         }
265
266         tSemaphore *sem = Thread->WaitPointer;
267         SHORTLOCK( &sem->Protector );
268         tThread *prev = NULL;
269         if( sem->Waiting == Thread )
270                 sem->Waiting = sem->Waiting->Next;
271         else
272         {
273                 for( prev = sem->Waiting; prev && prev->Next != Thread; prev = prev->Next )
274                         ;
275                 if( prev )
276                         prev->Next = Thread->Next;
277         }
278         if( sem->LastWaiting == Thread )
279                 sem->LastWaiting = prev;
280         SHORTREL( &sem->Protector );
281         Thread->RetStatus = 0;
282         Threads_AddActive(Thread);
283 }
284
285 //
286 // Get the current value of a semaphore
287 //
288 int Semaphore_GetValue(tSemaphore *Sem)
289 {
290         return Sem->Value;
291 }
292
293 // === EXPORTS ===
294 EXPORT(Semaphore_Init);
295 EXPORT(Semaphore_Wait);
296 EXPORT(Semaphore_Signal);

UCC git Repository :: git.ucc.asn.au