1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24#ifdef SDL_TIMER_OS2
25
26#define INCL_DOSMISC
27#define INCL_DOSERRORS
28#define INCL_DOSSEMAPHORES
29#define INCL_DOSDATETIME
30#define INCL_DOSPROCESS
31#define INCL_DOSPROFILE
32#define INCL_DOSEXCEPTIONS
33#include <os2.h>
34
35#include "SDL_thread.h"
36#include "SDL_timer.h"
37#include "../SDL_timer_c.h"
38
39
40#define TIME_WRAP_VALUE (~(DWORD)0)
41
42/* The first high-resolution ticks value of the application */
43static long long hires_start_ticks;
44/* The number of ticks per second of the high-resolution performance counter */
45static ULONG hires_ticks_per_second;
46
47void SDL_StartTicks(void)
48{
49        DosTmrQueryFreq(&hires_ticks_per_second);
50        DosTmrQueryTime((PQWORD)&hires_start_ticks);
51}
52
53DECLSPEC Uint32 SDLCALL SDL_GetTicks(void)
54{
55        long long hires_now;
56        ULONG ticks = ticks;
57
58        DosTmrQueryTime((PQWORD)&hires_now);
59/*
60        hires_now -= hires_start_ticks;
61        hires_now *= 1000;
62        hires_now /= hires_ticks_per_second;
63*/
64        /* inline asm to avoid runtime inclusion */
65        _asm {
66           push edx
67           push eax
68           mov eax, dword ptr hires_now
69           mov edx, dword ptr hires_now+4
70           sub eax, dword ptr hires_start_ticks
71           sbb edx, dword ptr hires_start_ticks+4
72           mov ebx,1000
73           mov ecx,edx
74           mul ebx
75           push eax
76           push edx
77           mov eax,ecx
78           mul ebx
79           pop eax
80           add edx,eax
81           pop eax
82           mov ebx, dword ptr hires_ticks_per_second
83           div ebx
84           mov dword ptr ticks, eax
85           pop edx
86           pop eax
87        }
88
89        return ticks;
90
91}
92
93/* High resolution sleep, originally made by Ilya Zakharevich */
94DECLSPEC void SDLCALL SDL_Delay(Uint32 ms)
95{
96  /* This is similar to DosSleep(), but has 8ms granularity in time-critical
97     threads even on Warp3. */
98  HEV     hevEvent1     = 0;   /* Event semaphore handle    */
99  HTIMER  htimerEvent1  = 0;   /* Timer handle              */
100  APIRET  rc            = NO_ERROR;  /* Return code               */
101  int ret = 1;
102  ULONG priority = 0, nesting;   /* Shut down the warnings */
103  PPIB pib;
104  PTIB tib;
105  char *e = NULL;
106  APIRET badrc;
107  int switch_priority = 50;
108
109  DosCreateEventSem(NULL,      /* Unnamed */
110                    &hevEvent1,  /* Handle of semaphore returned */
111                    DC_SEM_SHARED, /* Shared needed for DosAsyncTimer */
112                    FALSE);      /* Semaphore is in RESET state  */
113
114  if (ms >= switch_priority)
115    switch_priority = 0;
116  if (switch_priority)
117  {
118    if (DosGetInfoBlocks(&tib, &pib)!=NO_ERROR)
119      switch_priority = 0;
120    else
121    {
122 /* In Warp3, to switch scheduling to 8ms step, one needs to do
123    DosAsyncTimer() in time-critical thread.  On laters versions,
124    more and more cases of wait-for-something are covered.
125
126    It turns out that on Warp3fp42 it is the priority at the time
127    of DosAsyncTimer() which matters.  Let's hope that this works
128    with later versions too...  XXXX
129  */
130      priority = (tib->tib_ptib2->tib2_ulpri);
131      if ((priority & 0xFF00) == 0x0300) /* already time-critical */
132        switch_priority = 0;
133 /* Make us time-critical.  Just modifying TIB is not enough... */
134 /* tib->tib_ptib2->tib2_ulpri = 0x0300;*/
135 /* We do not want to run at high priority if a signal causes us
136    to longjmp() out of this section... */
137      if (DosEnterMustComplete(&nesting))
138        switch_priority = 0;
139      else
140        DosSetPriority(PRTYS_THREAD, PRTYC_TIMECRITICAL, 0, 0);
141    }
142  }
143
144  if ((badrc = DosAsyncTimer(ms,
145        (HSEM) hevEvent1, /* Semaphore to post        */
146        &htimerEvent1))) /* Timer handler (returned) */
147    e = "DosAsyncTimer";
148
149  if (switch_priority && tib->tib_ptib2->tib2_ulpri == 0x0300)
150  {
151 /* Nobody switched priority while we slept...  Ignore errors... */
152 /* tib->tib_ptib2->tib2_ulpri = priority; */ /* Get back... */
153    if (!(rc = DosSetPriority(PRTYS_THREAD, (priority>>8) & 0xFF, 0, 0)))
154      rc = DosSetPriority(PRTYS_THREAD, 0, priority & 0xFF, 0);
155  }
156  if (switch_priority)
157    rc = DosExitMustComplete(&nesting); /* Ignore errors */
158
159  /* The actual blocking call is made with "normal" priority.  This way we
160     should not bother with DosSleep(0) etc. to compensate for us interrupting
161     higher-priority threads.  The goal is to prohibit the system spending too
162     much time halt()ing, not to run us "no matter what". */
163  if (!e)     /* Wait for AsyncTimer event */
164    badrc = DosWaitEventSem(hevEvent1, SEM_INDEFINITE_WAIT);
165
166  if (e) ;    /* Do nothing */
167  else if (badrc == ERROR_INTERRUPT)
168    ret = 0;
169  else if (badrc)
170    e = "DosWaitEventSem";
171  if ((rc = DosCloseEventSem(hevEvent1)) && !e) { /* Get rid of semaphore */
172    e = "DosCloseEventSem";
173    badrc = rc;
174  }
175  if (e)
176  {
177    SDL_SetError("[SDL_Delay] : Had error in %s(), rc is 0x%x\n", e, badrc);
178  }
179}
180
181/* Data to handle a single periodic alarm */
182static int timer_alive = 0;
183static SDL_Thread *timer = NULL;
184
185static int SDLCALL RunTimer(void *unused)
186{
187        DosSetPriority(PRTYS_THREAD, PRTYC_TIMECRITICAL, 0, 0);
188        while ( timer_alive ) {
189                if ( SDL_timer_running ) {
190                        SDL_ThreadedTimerCheck();
191                }
192                SDL_Delay(10);
193        }
194        return(0);
195}
196
197/* This is only called if the event thread is not running */
198int SDL_SYS_TimerInit(void)
199{
200        timer_alive = 1;
201        timer = SDL_CreateThread(RunTimer, NULL);
202        if ( timer == NULL )
203                return(-1);
204        return(SDL_SetTimerThreaded(1));
205}
206
207void SDL_SYS_TimerQuit(void)
208{
209        timer_alive = 0;
210        if ( timer ) {
211                SDL_WaitThread(timer, NULL);
212                timer = NULL;
213        }
214}
215
216int SDL_SYS_StartTimer(void)
217{
218        SDL_SetError("Internal logic error: OS/2 uses threaded timer");
219        return(-1);
220}
221
222void SDL_SYS_StopTimer(void)
223{
224        return;
225}
226
227#endif /* SDL_TIMER_OS2 */
228