MMPM/2 Device Driver Reference:Using the High-Resolution Timer

The high-resolution timer (HRT) is used to provide millisecond-accuracy timing services to device drivers and applications.

Introduction
The high-resolution timer package includes the following files:
 * TIMER0.SYS the high-resolution timer driver
 * CLOCK01.SYS a modified CLOCK01.SYS for high-resolution timer support
 * CLOCK02.SYS modified CLOCK02.SYS for high-resolution timer support
 * TMR0_IDC.H the header file used to communicate with the high-resolution timer from another PDD
 * TMR0_IOC.H the header file for IOCtl interface to the high-resolution timer
 * TIMER0.DDP the DDINSTAL installation script

Installation
To install the high-resolution timer device driver, follow these steps:

1. Remove the readonly attribute from \OS2\BOOT\CLOCK*.SYS with the attrib command, e.g.: -r \os2\boot\clock*.sys where * is the number in the name of your existing clock-system file.

2.Make a backup of \OS2\BOOT\CLOCK*.SYS.

3.Use DDINSTAL to install the driver.

4.Reboot.

Note: The high-resolution timer is intended for use with OS/2 Warp 3.0 and later. It has not been tested and is not supported under any previous version of OS/2. If you do use this timer with any previous version of OS/2, you do so at your own risk.

Information for Microsoft C 6.0 Users
The header files were designed for Watcom C/C++ 10.0. If you are using Microsoft C 6.0, the only change that should be needed is to change the __far, __cdecl, and __loadds keywords to _far, _cdecl, and _loadds - i.e., replace the double underscores with single underscores. To date, however, this has not been verified.

Ring-0 Usage
The high-resolution timer provides the following two services:
 * callback every n milliseconds :where n is an integer >= 1. This first option allows your device driver to register itself with the high-resolution timer . The device driver provides a 16:16 address of a function that the high-resolution timer calls every n milliseconds. Registration (and deregistration) cannot occur during interrupt time.
 * If a device driver re-registers itself-by calling the registration function with the same 16:16 pointer-the high-resolution timer simply changes the count. This can occur at interrupt time.


 * query the current time :This second option is used to obtain a 16:16 pointer to the current count variable. This variable can then be read by the device driver to obtain the current time. Note that this variable is only updated if the high-resolution timer is active-which is only true if at least one driver or application has registered itself with the high-resolution timer. Also note that this variable can be synchronized to another timeclock.

To call the high-resolution timer, call DevHelp_AttachDD with the first parameter as "TIMER0$". Remember, DDTable must be allocated in the default data segment. If you make it a local non-static variable (i.e., it is on the stack), the DevHelp_AttachDD call will fail. For example: TIMER0_ATTACH_DD DDTable; PTMRFN ptmrfn;

DDTable.pfn=NULL; if( DevHelp_AttachDD((NPSZ) "TIMER0$ ", (NPBYTE) &DDTable)) {   // TIMER0.SYS not loaded }

if( !DDTable.pfn ) {   // Something wrong with TIMER0.SYS }

ptmrfn=DDTable.pfn; To register your driver with TIMER0.SYS, do something like this:
 * 1) define N   10                            // Call me every N milliseconds

void __far __loadds InterruptHandler( void ) {  // This function is called every N milliseconds }

if( ptmrfn( TMR0_REG, (ULONG)InterruptHandler, N)) {  // Registration failed } A ptmrfn call must occur at ring 0. This means that it cannot be made from your strategy INIT routine. The DevHelp_AttachDD call can be made from the INIT routine.

The ptrmfn call cannot be made from INIT COMPLETE either. Attempts to do so will result in a return code of 6.

Ring-3 Usage
The IOCtl interface provides the ability to:
 * obtain a pointer to the clock counter:The pointer returned is a 16:16 pointer, which should be converted to a 0:32 pointer for use with 32-bit applications.
 * query and set the resolution :Currently, the resolution is always 1 millisecond. Attempts to set it to another value will be ignored, and querying the driver will always return 1 millisecond. You should set the resolution anyway-in the future, the driver may actually use a lower resolution to conserve resources.


 * block until a certain time has elapsed

Use the following code as an example of reading the current time. The compiler used is C-Set++ 2.1. If you use a different compiler, you may need another technique for handling 16:16 pointers.
 * 1) include "tmr0_ioc.h"

APIRET rc; HFILE hfile; ULONG ulAction; ULONG ulOpenFlag = OPEN_ACTION_OPEN_IF_EXISTS; ULONG ulOpenMode = OPEN_FLAGS_FAIL_ON_ERROR | OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE;

ULONG ulResolution = 1; ULONG ulSize1=sizeof( ulResolution );

ULONG * _Seg16 pulTimer16; ULONG ulSize2=sizeof( pulTimer16 ); ULONG *pulTimer;

rc=DosOpen( "TIMER0$ ", &hfile, &ulAction, 0, 0, ulOpenFlag, ulOpenMode, NULL ); if( rc ) {  printf( "Couldn't open TIMER0$.\n" ); return; }

printf( "TIMER0$ opened. File Handle is %lu\n",hfile );

rc=DosDevIOCtl( hfile, HRT_IOCTL_CATEGORY, HRT_SET_RESOLUTION,               &ulResolution, ulSize1, &ulSize1, NULL, 0, NULL ); if( rc ) {  printf( "Couldn't set resolution.\n" ); DosClose( hfile ); return; }

rc=DosDevIOCtl( hfile, HRT_IOCTL_CATEGORY, HRT_GET_POINTER,               NULL, 0, NULL, &pulTimer16, ulSize2, &ulSize2 ); if( rc ) {  printf( "Couldn't get pointer.\n" ); DosClose( hfile ); return; }

pulTimer=pulTimer16;               // Converts a 16:16 pointer to a 0:32 pointer if( !pulTimer ) {  printf( "NULL pointer.\n" ); DosClose( hfile ); return; }

// At this point, pulTimer is now a pointer to the timer 0 counter variable

rc=DosClose( hfile ); DosOpen( "TIMER0$" ) registers the application as a client. At this point, the high-resolution timer driver is taking interrupts; so make sure the driver is open only when you need timer services. The pointer is valid for the life of the process; and each call to HRT_GET_POINTER allocates another selector-therefore, this call should be made only once.

To block until a certain time has elapsed, use the following code as an example. For brevity, the details (such as checking return codes) have been omitted. ULONG ulDelay=1;                     // Number of milliseconds to wait ULONG ulSize2=sizeof( ulDelay );

DosOpen( "TIMER0$ ", &hfile, &ulAction, 0, 0, ulOpenFlag, ulOpenMode, NULL ); DosSetPriority( 0, PRTYC_TIMECRITICAL, 0, 0 ); DosDevIOCtl( hfile, HRT_IOCTL_CATEGORY, HRT_BLOCKUNTIL,            &ulDelay, ulSize2, &ulSize2, NULL, 0, NULL ); Using a time-critical thread is important.

Common Problems
The following problems might occur due to the nature of the high-resolution driver.
 * trap D:This is usually caused by using the wrong version of CLOCK0x.SYS. If the old CLOCK0x.SYS is in \OS2, for example, and the new one is in \OS2\ BOOT, the kernel will load the old driver. A quick way to check is to use the command "dir \clock0?.sy? /s" on the boot partition. This will search the entire hard drive for the clock drivers.
 * clock driver will not load:Either some performance tool is running, or you are using the Warp GA version of CLOCK0x.SYS.

Caveats
The following should be considered when using the high-resolution timer: If a driver registers for 1 millisecond, for example, the first tick might occur less than 1 millisecond after the registration.
 * Only the first parameter to PTMRFN is checked; no other parameters are checked.
 * It is possible to write to the timer count variable from another PDD and disrupt the high-resolution timer.
 * While TIMER0 is active (i.e., while it is taking interrupts), DOS sessions do not have access to timer 0.
 * Performance tools such as C Set's DDE4XTRA.SYS and VisualAge's CPPOPA3.SYS are incompatible with this driver.
 * Device drivers should not depend on having the first tick occur at any particular time.

Anomalies
The following anomalies currently exist in the use of the high-resolution timer:
 * Having multiple device driver's attached to the high-resolution timer has not been tested.
 * The high-resolution timer sets interrupts at 1 millisecond even if a slower rate would suffice. If one driver wants callbacks every 2 milliseconds and another wants them at 4 milliseconds, the driver could program IRQ 0 for 2-millisecond ticks, but it does not.
 * HRT_FREE_POINTER does not currently do anything.
 * While the high-resolution timer is taking interrupts, DOS sessions (VDMs) will not receive INT 8 or INT 1Ch calls. Only one device driver can receive interrupts on a given IRQ in OS/2; and VTIMER.SYS - the VDD that provides INT 8 and INT 1Ch services to VDMs - expects CLOCK0x.SYS to deliver IRQ 0 interrupts. Because the high-resolution timer gets IRQ 0 interrupts, CLOCK0x.SYS - and therefore VTIMER.SYS - cannot get them.