BOROSOFTWAREAll work →
scantool/diag_os_win.c 542 lines
1/*
2 * freediag - Vehicle Diagnostic Utility
3 *
4 * (c) 2014-2015 fenugrec
5 *
6 * OS specific stuff
7
8 * We run the process in high priority.
9 *
10 * WIN32 will use CreateTimerQueueTimer instead of the SIGALRM handler of unix.
11 * Right now there's no self-checking but it should be of OK accuracy for basic stuff ( keepalive messages )
12 * NOTE : that means at least WinXP is required.
13
14 * additional timing info: http://www.windowstimestamp.com/description
15 */
16
17
18#include <stdlib.h>
19#include <string.h>
20#include <assert.h>
21
22#include "diag_tty.h"
23#include "diag_os.h"
24#include "diag.h"
25
26#include "diag_l1.h"
27#include "diag_l2.h"
28#include "diag_l3.h"
29#include "diag_err.h"
30
31
32#include <process.h>
33#include <windows.h>
34#include <conio.h> //for _kbhit, _getch
35/** Check the _kbhit function **/
36#ifdef HAVE_MS_KBHIT
37#define _kbhit kbhit
38#endif
39/** Check the _getch function **/
40#ifdef HAVE_MS_GETCH
41#define _getch getch
42#endif
43
44#include <inttypes.h> //for PRIu64 formatters
45
46static bool diag_os_init_done=0;
47static bool timer_period_changed = 0; //do we need to reset timeEndPeriod on exit
48
49LARGE_INTEGER perfo_freq = {{0,0}}; //for use with QueryPerformanceFrequency and QueryPerformanceCounter
50float pf_conv=0; //this will be (1E6 / perfo_freq) to convert counts to microseconds, i.e. [us]=[counts]*pf_conv
51static int pfconv_valid=0; //flag after querying perfo_freq; nothing will not work without a performance counter
52int shortsleep_reliable=0; //TODO : auto-detect this on startup. See diag_os_millisleep & diag_os_calibrate
53static UINT timer_period = 0; // to store timeBeginPeriod(timer_period) value for future cleanup
54
55
56static void tweak_timing(bool change_interval);
57static void reset_timing(void);
58
59/* periodic callback:
60+* the current implementation uses non-async-signal-safe functions
61+* in the signal handlers. Their behavior is undefined if they happen
62+* to occur during any other non-async-signal-safe function.
63*/
64HANDLE hDiagTimer = INVALID_HANDLE_VALUE;
65
66CRITICAL_SECTION periodic_lock;
67
68VOID CALLBACK timercallback(UNUSED(PVOID lpParam), BOOLEAN timedout) {
69
70 if (!TryEnterCriticalSection(&periodic_lock)) {
71 return;
72 }
73
74 if (!timedout) {
75 //this should never happen.
76 fprintf(stderr, FLFMT "Problem with OS timer callback! Report this !\n", FL);
77 } else {
78 diag_l3_timer(); /* Call L3 Timer */
79 diag_l2_timer(); /* Call L2 timer */
80 }
81 LeaveCriticalSection(&periodic_lock);
82
83 return;
84}
85
86//diag_os_init : Sets up a periodic callback
87//to call diag_l3_timer and diag_l2_timer.
88// Also calls tweak_timing() to increase thread priority.
89//return 0 if ok
90int diag_os_init(void) {
91 unsigned long tmo=ALARM_TIMEOUT;
92
93 if (diag_os_init_done) {
94 return 0;
95 }
96
97 tweak_timing(1);
98
99 //probably the nearest equivalent to a unix interval timer + associated alarm handler
100 //is the timer queue... so that's what we do.
101 //we create the timer in the default timerqueue
102 InitializeCriticalSection(&periodic_lock);
103
104 if (!CreateTimerQueueTimer(&hDiagTimer, NULL,
105 (WAITORTIMERCALLBACK) timercallback, NULL, tmo, tmo,
106 WT_EXECUTEDEFAULT)) {
107 fprintf(stderr, FLFMT "CTQT error.\n", FL);
108 hDiagTimer = INVALID_HANDLE_VALUE;
109 return diag_iseterr(DIAG_ERR_GENERAL);
110 }
111
112 //and get the current performance counter frequency.
113 //From MSDN docs : The frequency of the performance counter is fixed at system boot
114 // and is consistent across all processors. Therefore, the frequency
115 // need only be queried upon application initialization, and the result can be cached.
116 //
117 // Under what circumstances does QueryPerformanceFrequency return FALSE,
118 // or QueryPerformanceCounter return zero?
119 // ->This won't occur on any system that runs Windows XP or later.
120
121 if ( !QueryPerformanceFrequency(&perfo_freq) || (perfo_freq.QuadPart==0)) {
122 fprintf(stderr, FLFMT "Fatal: could not QPF. Please report this !\n", FL);
123 diag_os_close();
124 return diag_iseterr(DIAG_ERR_GENERAL);
125 }
126
127 if (perfo_freq.QuadPart ==0) {
128 fprintf(stderr, FLFMT "Fatal: QPF reports 0Hz. Please report this !\n", FL);
129 diag_os_close();
130 return diag_iseterr(DIAG_ERR_GENERAL);
131 }
132 pf_conv=1.0E6 / perfo_freq.QuadPart;
133 pfconv_valid =1;
134
135 DIAG_DBGM(diag_l0_debug, DIAG_DEBUG_TIMER, DIAG_DBGLEVEL_V,
136 FLFMT "Performance counter frequency : %9" PRIu64 "Hz\n", FL, perfo_freq.QuadPart);
137
138 diag_os_calibrate();
139 diag_os_init_done = 1;
140 return 0;
141
142} //diag_os_init
143
144//diag_os_close: delete alarm handlers / periodic timers
145//return 0 if ok
146int diag_os_close() {
147 DWORD err;
148
149 diag_os_init_done=0; //diag_os_init will have to be done again past this point.
150
151 reset_timing();
152
153 if (hDiagTimer != INVALID_HANDLE_VALUE) {
154 if (DeleteTimerQueueTimer(NULL,hDiagTimer,NULL)) {
155 //success
156 goto goodexit;
157 }
158 //From MSDN : if error_io_pending, not necessary to call again.
159 err=GetLastError();
160 if (err==ERROR_IO_PENDING) {
161 //This is OK and the queue will be deleted automagically.
162 //No need to pester the user about this
163 goto goodexit;
164 }
165 //Otherwise, try again
166 fprintf(stderr, FLFMT "Could not DTQT. Retrying...", FL);
167 Sleep(500); //should be more than enough for IO to complete...
168 if (DeleteTimerQueueTimer(NULL,hDiagTimer,NULL)) {
169 fprintf(stderr, "OK !\n");
170 goto goodexit;
171 }
172 fprintf(stderr, "Failed. Please report this.\n");
173 }
174
175goodexit:
176 hDiagTimer = INVALID_HANDLE_VALUE;
177 DeleteCriticalSection(&periodic_lock);
178 return 0;
179} //diag_os_close
180
181
182//
183void diag_os_millisleep(unsigned int ms) {
184 //This version self-corrects if Sleep() overshoots;
185 //if it undershoots then we run an empty loop for the remaining
186 //time. Eventually "correction" should contain the biggest
187 //overshoot so far; this means every subsequent calls
188 //will almost always run through the NOP loop.
189 LARGE_INTEGER qpc1, qpc2;
190 long real_t;
191 static long correction=0; //auto-adjusment (in us)
192 LONGLONG tdiff; //measured (elapsed) time (in counts)
193
194 QueryPerformanceCounter(&qpc1);
195 assert(pfconv_valid);
196 tdiff=0;
197
198 if (perfo_freq.QuadPart ==0) {
199 Sleep(ms);
200 return;
201 }
202 LONGLONG reqt= (ms * perfo_freq.QuadPart)/1000; //required # of counts
203
204 if ( shortsleep_reliable || (((long) ms-(correction/1000)) > 5)) {
205 //if reliable, or long sleep : try
206 Sleep(ms - (correction/1000));
207 QueryPerformanceCounter(&qpc2);
208 tdiff= qpc2.QuadPart - qpc1.QuadPart;
209 if (tdiff > reqt) {
210 //we busted the required time by:
211 real_t = (long) (pf_conv * (tdiff-reqt)); //in us
212 if (real_t > 1000) {
213 correction += real_t;
214 if (correction > 4000) {
215 correction = 4000;
216 }
217 }
218 return;
219 }
220 }
221
222 //do NOP loop for short sleeps for the remainder. This is ugly
223 //but could help on some systems
224 //if Sleep(ms) was too short this will bring us near
225 //the requested value.
226 while (tdiff < reqt) {
227 QueryPerformanceCounter(&qpc2);
228 tdiff= qpc2.QuadPart - qpc1.QuadPart;
229 }
230 return;
231
232} //diag_os_millisleep
233
234
235int diag_os_ipending(void) {
236 if (_kbhit()) {
237 (void) _getch();
238 return 1;
239 }
240 return 0;
241}
242
243//diag_os_cursor_up : move the console cursor up the specified number of
244//lines and to column 1
245void diag_os_cursor_up(unsigned int lines) {
246 HANDLE console;
247 CONSOLE_SCREEN_BUFFER_INFO buffer_info;
248 int rv;
249
250 console = GetStdHandle(STD_OUTPUT_HANDLE);
251 if ((console == INVALID_HANDLE_VALUE) || (console == NULL)) {
252 return;
253 }
254 rv = GetConsoleScreenBufferInfo(console, &buffer_info);
255 if (rv == 0) {
256 return;
257 }
258 buffer_info.dwCursorPosition.Y -= lines;
259 if (buffer_info.dwCursorPosition.Y < 0) {
260 buffer_info.dwCursorPosition.Y = 0;
261 }
262 buffer_info.dwCursorPosition.X = 0;
263 SetConsoleCursorPosition(console, buffer_info.dwCursorPosition);
264}
265
266//diag_os_clrtoeol : clear the console text from the cursor to the end of
267//the current line
268void diag_os_clrtoeol(void) {
269 HANDLE console;
270 CONSOLE_SCREEN_BUFFER_INFO buffer_info;
271 int rv;
272
273 console = GetStdHandle(STD_OUTPUT_HANDLE);
274 if ((console == INVALID_HANDLE_VALUE) || (console == NULL)) {
275 return;
276 }
277 rv = GetConsoleScreenBufferInfo(console, &buffer_info);
278 if (rv == 0) {
279 return;
280 }
281 while (buffer_info.dwCursorPosition.X < buffer_info.dwSize.X-1) {
282 putchar(' ');
283 buffer_info.dwCursorPosition.X++;
284 }
285 fflush(stdout);
286}
287
288/** Adjust priority and OS time interval
289 *
290 * @param change_interval : if 1, use timeBeginPeriod
291 *
292 * call reset_timing() before exiting
293 */
294static void tweak_timing(bool change_interval) {
295 HANDLE curprocess, curthread;
296
297 //set the current process to high priority.
298 //the resultant "base priority" is a combination of process priority and thread priority.
299 curprocess=GetCurrentProcess();
300 curthread=GetCurrentThread();
301 if (!SetPriorityClass(curprocess, HIGH_PRIORITY_CLASS)) {
302 fprintf(stderr, FLFMT "Warning: could not increase process priority. Timing may be impaired.\n", FL);
303 }
304
305 if (!SetThreadPriority(curthread, THREAD_PRIORITY_HIGHEST)) {
306 fprintf(stderr, FLFMT "Warning : could not increase thread priority. Timing may be impaired.\n", FL);
307 }
308
309 if (!change_interval) {
310 return;
311 }
312
313 // workaround to increase Sleep() routine accuracy by decreasing PerformanceTimer time period to minimum possible
314 // not fatal if any of this fails
315
316 TIMECAPS caps;
317 MMRESULT res = timeGetDevCaps(&caps, sizeof(caps));
318
319 if (res != TIMERR_NOERROR) {
320 printf("Unable to timeGetDevCaps.\n");
321 } else {
322 if (timeBeginPeriod(caps.wPeriodMin) != TIMERR_NOERROR ) {
323 printf ("Error setting OS timer period!\n");
324 } else {
325 timer_period = caps.wPeriodMin;
326 timer_period_changed = 1;
327 }
328 }
329
330 return;
331}
332
333/** reset prio and OS time interval
334 */
335static void reset_timing(void) {
336 HANDLE curprocess, curthread;
337
338 //reset the current process to normal
339 curprocess=GetCurrentProcess();
340 curthread=GetCurrentThread();
341 if (!SetPriorityClass(curprocess, NORMAL_PRIORITY_CLASS)) {
342 fprintf(stderr, FLFMT "Warning: could not reset process priority.\n", FL);
343 }
344
345 if (!SetThreadPriority(curthread, THREAD_PRIORITY_NORMAL)) {
346 fprintf(stderr, FLFMT "Warning : could not reset thread priority.\n", FL);
347 }
348
349 if (!timer_period_changed) {
350 return;
351 }
352
353 // restore multimedia timer
354 if (timeEndPeriod(timer_period) != TIMERR_NOERROR) {
355 printf("Error restoring OS timer period!\n");
356 }
357
358}
359
360
361
362//diag_os_geterr : get OS-specific error string.
363//Either gets the last error if os_errno==0, or print the
364//message associated with the specified os_errno
365// XXX this is not async-safe / re-entrant !
366//
367const char *diag_os_geterr(OS_ERRTYPE os_errno) {
368 //to make this re-entrant, we would need CreateMutex OpenMutex etc.
369 static char errbuf[160]="";
370
371 LPVOID errstr;
372
373 if (os_errno == 0) {
374 os_errno=GetLastError();
375 }
376
377 if (os_errno !=0 ) {
378 DWORD elen = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
379 NULL,
380 os_errno,
381 0,
382 (LPTSTR) &errstr,
383 0,
384 NULL);
385 if (elen) {
386 snprintf(errbuf, sizeof(errbuf), "%s", (const char *) errstr);
387 LocalFree(errstr);
388 } else {
389 //formatmessage failed...
390 snprintf(errbuf, sizeof(errbuf), "UNK:%u", (unsigned int) os_errno);
391 }
392 } else {
393 strcpy(errbuf, "No error");
394 }
395 return (const char *) errbuf;
396
397}
398
399//diag_os_calibrate : run some timing tests to make sure we have
400//adequate performances.
401//On win32, running diag_os_millisleep repeatedly allows it to
402//auto-adjust to a certain degree.
403
404void diag_os_calibrate(void) {
405 static int calibrate_done=0; //do it only once
406 int testval; //timeout to test
407 LARGE_INTEGER qpc1, qpc2;
408 LONGLONG tsum;
409 #define RESOL_ITERS 10
410 unsigned long long resol, maxres, tl1, tl2; //all for _gethrt() test
411 unsigned long t1, t2; //for _getms() test
412
413 assert(pfconv_valid);
414
415 if (calibrate_done) {
416 return;
417 }
418
419 //test _gethrt()
420 resol=0;
421 maxres=0;
422 for (int i=0; i < RESOL_ITERS; i++) {
423 unsigned long long tr;
424 tl1=diag_os_gethrt();
425 while ((tl2=diag_os_gethrt()) == tl1) {}
426 tr = (tl2-tl1);
427 if (tr > maxres) {
428 maxres = tr;
429 }
430 resol += tr;
431 }
432 printf("diag_os_gethrt() resolution <= %luus, avg ~%luus\n",
433 (unsigned long) diag_os_hrtus(maxres), (unsigned long) diag_os_hrtus(resol / RESOL_ITERS));
434
435 //now test diag_os_getms
436 t1=diag_os_getms();
437 while ( ((t2=diag_os_getms())-t1) ==0) {}
438 printf("diag_os_getms() resolution: ~%lums.\n", t2-t1);
439
440 printf("Calibrating timing, this will take a few seconds...\n");
441
442 for (testval=50; testval > 0; testval -= 2) {
443 //Start with the highest timeout to force _millisleep to use Sleep()
444 //and therefore start auto-correcting right away.
445 int i;
446 LONGLONG counts, avgerr, max, min;
447
448 max=0;
449
450 tsum=0;
451 counts=(testval*perfo_freq.QuadPart)/1000; //expected # of counts
452 min=counts;
453#define CAL_ITERS 6
454 for (i=0; i< CAL_ITERS; i++) {
455 LONGLONG timediff;
456 QueryPerformanceCounter(&qpc1);
457 diag_os_millisleep(testval);
458 QueryPerformanceCounter(&qpc2);
459 timediff=(qpc2.QuadPart-qpc1.QuadPart);
460 tsum += timediff;
461 //update extreme records if required:
462 if (timediff < min) {
463 min = timediff;
464 }
465 if (timediff > max) {
466 max = timediff;
467 }
468 }
469 avgerr= (LONGLONG) (((tsum/CAL_ITERS)-counts) * pf_conv); //average error in us
470 //a high spread (max-min) indicates initbus with dumb interfaces will be
471 //fragile. We just print it out; there's not much we can do to fix this.
472 if ((min < counts) || (avgerr > 900)) {
473 printf("diag_os_millisleep(%d) off by %+" PRId64 "%% (%+" PRId64 "us)"
474 "; spread=%" PRId64 "%%\n", testval, (avgerr*100/1000)/testval, avgerr, ((max-min)*100)/counts);
475 }
476
477
478 if (testval>=30) {
479 testval -= 8;
480 }
481 } //for testvals
482
483 printf("Calibration done.\n");
484 calibrate_done=1;
485 return;
486
487} //diag_os_calibrate
488
489//return monotonic clock time, ms precision.
490//resolution and accuracy are not important; GetTickCount() is good enough
491unsigned long diag_os_getms(void) {
492 return (unsigned long) GetTickCount();
493}
494
495//get high resolution timestamp
496unsigned long long diag_os_gethrt(void) {
497 LARGE_INTEGER qpc1;
498 assert(pfconv_valid);
499 QueryPerformanceCounter(&qpc1);
500 return (unsigned long long) qpc1.QuadPart;
501}
502
503//convert a delta of diag_os_gethrt() timestamps to microseconds
504unsigned long long diag_os_hrtus(unsigned long long hrdelta) {
505 assert(pfconv_valid);
506 return (unsigned long long) (hrdelta * (double) pf_conv);
507}
508
509
510void diag_os_initmtx(diag_mtx *mtx) {
511 InitializeCriticalSection((CRITICAL_SECTION *)mtx);
512 return;
513}
514
515void diag_os_initstaticmtx(diag_mtx *mtx) {
516 // No static initialization of mutexes on Windows.
517 diag_os_initmtx(mtx);
518 return;
519}
520
521void diag_os_delmtx(diag_mtx *mtx) {
522 DeleteCriticalSection((CRITICAL_SECTION *)mtx);
523 return;
524}
525
526void diag_os_lock(diag_mtx *mtx) {
527 EnterCriticalSection((CRITICAL_SECTION *)mtx);
528 return;
529}
530
531bool diag_os_trylock(diag_mtx *mtx) {
532 if (!TryEnterCriticalSection((CRITICAL_SECTION *)mtx)) {
533 return 0;
534 }
535 return 1;
536}
537
538void diag_os_unlock(diag_mtx *mtx) {
539 LeaveCriticalSection((CRITICAL_SECTION *)mtx);
540 return;
541}
542