# make sure and set the flags before unlocking access to the list/enabling interrupts.
[AROS.git] / rom / timer / lowlevel.c
blob72b4ec0a080ac70f9345307c4ee9d62a6813e006
1 /*
2 Copyright © 1995-2017, The AROS Development Team. All rights reserved.
3 $Id$
5 Desc: Common IORequest processing routines
6 Lang: english
7 */
9 #include <aros/debug.h>
10 #include <aros/symbolsets.h>
11 #include <devices/newstyle.h>
12 #include <exec/errors.h>
13 #include <exec/initializers.h>
14 #include <hardware/intbits.h>
15 #include <proto/exec.h>
16 #include <proto/execlock.h>
17 #include <proto/timer.h>
19 #include "timer_intern.h"
20 #include "timer_macros.h"
22 /****************************************************************************************/
24 #define NEWSTYLE_DEVICE 1
26 #define ioStd(x) ((struct IOStdReq *)x)
28 /****************************************************************************************/
30 #if NEWSTYLE_DEVICE
32 static const UWORD SupportedCommands[] =
34 TR_GETSYSTIME,
35 TR_SETSYSTIME,
36 TR_ADDREQUEST,
37 NSCMD_DEVICEQUERY,
41 #endif
43 static void addToWaitList(struct MinList *list, struct timerequest *iotr, struct ExecBase *SysBase)
45 /* We are disabled, so we should take as little time as possible. */
46 struct timerequest *tr;
47 BOOL added = FALSE;
49 ForeachNode(list, tr)
51 /* If the time in the new request is less than the next request */
52 if (CMPTIME(&tr->tr_time, &iotr->tr_time) < 0)
54 /* Add the node before the next request */
55 Insert((struct List *)list, &iotr->tr_node.io_Message.mn_Node, tr->tr_node.io_Message.mn_Node.ln_Pred);
57 added = TRUE;
58 break;
63 * This will catch the case of either an empty list, or request is
64 * for after all other requests
66 if(!added)
67 ADDTAIL(list, iotr);
69 #if PRINT_LIST
70 bug("Current list contents:\n");
72 ForeachNode(list, tr)
74 bug("%u.%u\n", tr->tr_time.tv_secs, tr->tr_time.tv_micro);
76 #endif
79 BOOL common_BeginIO(struct timerequest *timereq, struct TimerBase *TimerBase)
81 ULONG unitNum = (IPTR)timereq->tr_node.io_Unit;
82 BOOL replyit = FALSE;
83 BOOL addedhead = FALSE;
85 timereq->tr_node.io_Message.mn_Node.ln_Type = NT_MESSAGE;
86 timereq->tr_node.io_Error = 0;
88 switch(timereq->tr_node.io_Command)
90 #if NEWSTYLE_DEVICE
91 case NSCMD_DEVICEQUERY:
94 * CHECKME: In timer.device this is maybe a bit problematic, as the
95 * timerequest structure does not have io_Data and io_Length members
97 if (timereq->tr_node.io_Message.mn_Length < sizeof(struct IOStdReq))
99 timereq->tr_node.io_Error = IOERR_BADLENGTH;
101 else if(ioStd(timereq)->io_Length < ((LONG)OFFSET(NSDeviceQueryResult, SupportedCommands)) + sizeof(UWORD *))
103 timereq->tr_node.io_Error = IOERR_BADLENGTH;
105 else
107 struct NSDeviceQueryResult *d = (struct NSDeviceQueryResult *)ioStd(timereq)->io_Data;
109 d->DevQueryFormat = 0;
110 d->SizeAvailable = sizeof(struct NSDeviceQueryResult);
111 d->DeviceType = NSDEVTYPE_TIMER;
112 d->DeviceSubType = 0;
113 d->SupportedCommands = (UWORD *)SupportedCommands;
115 ioStd(timereq)->io_Actual = sizeof(struct NSDeviceQueryResult);
117 break;
118 #endif
120 case TR_GETSYSTIME:
121 GetSysTime(&timereq->tr_time);
123 if (!(timereq->tr_node.io_Flags & IOF_QUICK))
124 ReplyMsg(&timereq->tr_node.io_Message);
126 replyit = FALSE; /* Because replyit will clear the timeval */
127 break;
129 case TR_SETSYSTIME:
130 Disable();
132 /* Set current time value */
133 TimerBase->tb_CurrentTime.tv_secs = timereq->tr_time.tv_secs;
134 TimerBase->tb_CurrentTime.tv_micro = timereq->tr_time.tv_micro;
135 /* Update hardware */
136 EClockSet(TimerBase);
138 Enable();
139 replyit = TRUE;
140 break;
142 case TR_ADDREQUEST:
143 switch(unitNum)
145 case UNIT_WAITUNTIL:
146 Disable();
148 /* Query the hardware first */
149 EClockUpdate(TimerBase);
151 if (CMPTIME(&TimerBase->tb_CurrentTime, &timereq->tr_time) <= 0)
153 timereq->tr_time.tv_secs = timereq->tr_time.tv_micro = 0;
154 timereq->tr_node.io_Error = 0;
155 replyit = TRUE;
157 else
159 #if defined(__AROSEXEC_SMP__)
160 struct ExecLockBase *ExecLockBase = TimerBase->tb_ExecLockBase;
161 if (ExecLockBase) ObtainLock(TimerBase->tb_ListLock, SPINLOCK_MODE_WRITE, 0);
162 #endif
163 /* Ok, we add this to the list */
164 addToWaitList(&TimerBase->tb_Lists[TL_WAITVBL], timereq, SysBase);
165 timereq->tr_node.io_Flags &= ~IOF_QUICK;
168 * If our request was added to the head of the list, we may need to
169 * readjust our hardware interrupt (reset elapsed time).
170 * This routine returns TRUE in order to indicate this.
172 if (TimerBase->tb_Lists[TL_WAITVBL].mlh_Head == (struct MinNode *)timereq)
173 addedhead = TRUE;
175 #if defined(__AROSEXEC_SMP__)
176 if (ExecLockBase) ReleaseLock(TimerBase->tb_ListLock, 0);
177 #endif
178 replyit = FALSE;
182 Enable();
183 break;
185 case UNIT_VBLANK:
186 case UNIT_MICROHZ:
188 #if defined(__AROSEXEC_SMP__)
189 struct ExecLockBase *ExecLockBase = TimerBase->tb_ExecLockBase;
190 #endif
192 Disable();
194 /* Query the hardware first */
195 EClockUpdate(TimerBase);
199 * Adjust the time request to be relative to the
200 * the elapsed time counter that we keep.
202 ADDTIME(&timereq->tr_time, &TimerBase->tb_Elapsed);
204 #if defined(__AROSEXEC_SMP__)
205 if (ExecLockBase) ObtainLock(TimerBase->tb_ListLock, SPINLOCK_MODE_WRITE, 0);
206 #endif
207 /* Slot it into the list. Use unit number as index. */
208 addToWaitList(&TimerBase->tb_Lists[unitNum], timereq, SysBase);
209 timereq->tr_node.io_Flags &= ~IOF_QUICK;
211 /* Indicate if HW need to be reprogrammed */
212 if (TimerBase->tb_Lists[unitNum].mlh_Head == (struct MinNode *)timereq)
213 addedhead = TRUE;
215 #if defined(__AROSEXEC_SMP__)
216 if (ExecLockBase) ReleaseLock(TimerBase->tb_ListLock, 0);
217 #endif
218 Enable();
220 replyit = FALSE;
221 break;
223 case UNIT_ECLOCK:
224 case UNIT_WAITECLOCK:
225 /* TODO: implement these (backport from m68k-Amiga) */
226 default:
227 replyit = FALSE;
228 timereq->tr_node.io_Error = IOERR_NOCMD;
229 break;
231 } /* switch(unitNum) */
232 break;
234 case CMD_CLEAR:
235 case CMD_FLUSH:
236 case CMD_INVALID:
237 case CMD_READ:
238 case CMD_RESET:
239 case CMD_START:
240 case CMD_STOP:
241 case CMD_UPDATE:
242 case CMD_WRITE:
243 default:
244 replyit = TRUE;
245 timereq->tr_node.io_Error = IOERR_NOCMD;
246 break;
248 } /* switch(command) */
250 if (replyit)
252 timereq->tr_time.tv_secs = 0;
253 timereq->tr_time.tv_micro = 0;
255 if (!(timereq->tr_node.io_Flags & IOF_QUICK))
256 ReplyMsg(&timereq->tr_node.io_Message);
259 return addedhead;
262 void TimerProcessMicroHZ(struct TimerBase *TimerBase, struct ExecBase *SysBase, BOOL locked)
264 #if defined(__AROSEXEC_SMP__)
265 struct ExecLockBase *ExecLockBase = TimerBase->tb_ExecLockBase;
266 #endif
267 struct MinList *unit = &TimerBase->tb_Lists[TL_MICROHZ];
268 struct timerequest *tr, *next;
271 * Go through the list and return requests that have completed.
272 * A completed request is one whose time is less than that of the elapsed time.
274 #if defined(__AROSEXEC_SMP__)
275 if (ExecLockBase && !locked) ObtainLock(TimerBase->tb_ListLock, SPINLOCK_MODE_WRITE, 0);
276 #endif
277 ForeachNodeSafe(unit, tr, next)
279 if (CMPTIME(&TimerBase->tb_Elapsed, &tr->tr_time) <= 0)
281 /* This request has finished */
282 REMOVE(tr);
284 #ifdef USE_VBLANK_EMU
285 if (tr == &TimerBase->tb_vblank_timerequest)
287 struct IntVector *iv = &SysBase->IntVects[INTB_VERTB];
289 /* VBlank Emu */
290 if (iv->iv_Code)
292 AROS_INTC2(iv->iv_Code, iv->iv_Data, INTF_VERTB);
296 * Process VBlank unit.
297 * The philosophy behind is that only software which needs to measure
298 * exact intervals uses MICROHZ unit. Others use VBLANK one. As a result,
299 * VBLANK queue is generally more populated than MICROHZ one.
300 * VBLANK queue is checked more rarely than MICROHZ, this helps to decrease
301 * CPU usage.
303 TimerProcessVBlank(TimerBase, SysBase, TRUE);
306 * Automatically requeue/reactivate request.
307 * Feature: get value every time from SysBase. This means
308 * that the user can change our VBlank rate in runtime by modifying
309 * this field.
311 tr->tr_time.tv_secs = 0;
312 tr->tr_time.tv_micro = 1000000 / SysBase->VBlankFrequency;
313 ADDTIME(&tr->tr_time, &TimerBase->tb_Elapsed);
314 addToWaitList(unit, tr, SysBase);
316 continue;
318 #endif
319 D(bug("[Timer] Replying msg 0x%p\n", tr));
321 tr->tr_time.tv_secs = 0;
322 tr->tr_time.tv_micro = 0;
323 tr->tr_node.io_Error = 0;
325 ReplyMsg(&tr->tr_node.io_Message);
327 else
330 The first request hasn't finished, as all requests are in
331 order, we don't bother searching through the remaining
333 break;
336 #if defined(__AROSEXEC_SMP__)
337 if (ExecLockBase && !locked) ReleaseLock(TimerBase->tb_ListLock, 0);
338 #endif
341 void TimerProcessVBlank(struct TimerBase *TimerBase, struct ExecBase *SysBase, BOOL locked)
343 #if defined(__AROSEXEC_SMP__)
344 struct ExecLockBase *ExecLockBase = TimerBase->tb_ExecLockBase;
345 #endif
347 * VBlank handler is the same as above, with two differences:
348 * 1. We don't check for VBlank emulation request.
349 * 2. VBlank unit consists of two list, not one. The second list
350 * is UNIT_WAITUNTIL queue.
351 * We could use subroutines and save some space, but we prefer speed here.
353 struct timerequest *tr, *next;
356 * Go through the "wait for x seconds" list and return requests
357 * that have completed. A completed request is one whose time
358 * is less than that of the elapsed time.
360 #if defined(__AROSEXEC_SMP__)
361 if (ExecLockBase && !locked) ObtainLock(TimerBase->tb_ListLock, SPINLOCK_MODE_WRITE, 0);
362 #endif
363 ForeachNodeSafe(&TimerBase->tb_Lists[TL_VBLANK], tr, next)
365 if (CMPTIME(&TimerBase->tb_Elapsed, &tr->tr_time) <= 0)
367 /* This request has finished */
368 REMOVE(tr);
370 tr->tr_time.tv_secs = tr->tr_time.tv_micro = 0;
371 tr->tr_node.io_Error = 0;
373 ReplyMsg(&tr->tr_node.io_Message);
375 else
376 break;
379 * The other this is the "wait until a specified time". Here a request
380 * is complete if the time we are waiting for is before the current time.
382 ForeachNodeSafe(&TimerBase->tb_Lists[TL_WAITVBL], tr, next)
384 if (CMPTIME(&TimerBase->tb_CurrentTime, &tr->tr_time) <= 0)
386 /* This request has finished */
387 REMOVE(tr);
389 tr->tr_time.tv_secs = tr->tr_time.tv_micro = 0;
390 tr->tr_node.io_Error = 0;
392 ReplyMsg(&tr->tr_node.io_Message);
394 else
395 break;
397 #if defined(__AROSEXEC_SMP__)
398 if (ExecLockBase && !locked) ReleaseLock(TimerBase->tb_ListLock, 0);
399 #endif
402 /****************************************************************************************/
404 static int Timer_Open(struct TimerBase *LIBBASE, struct timerequest *tr, ULONG unitNum, ULONG flags)
407 * Normally, we should check the length of the message and other
408 * such things, however the RKM documents an example where the
409 * length of the timerrequest isn't set, so we must not check
410 * this.
411 * This fixes bug SF# 741580
414 if (unitNum > UNIT_WAITECLOCK)
415 tr->tr_node.io_Error = IOERR_OPENFAIL;
416 else
418 tr->tr_node.io_Error = 0;
419 tr->tr_node.io_Unit = (NULL + unitNum);
420 tr->tr_node.io_Device = &LIBBASE->tb_Device;
423 return TRUE;
426 ADD2OPENDEV(Timer_Open, 0);