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

If you need help, you can contact me:

Timur Tabi
Architect, OS/2 Warp realtime MIDI subsytem
IBM Boca Raton
Multimedia Device Drivers
timur@vnet.ibm.com
(407) 443-5624

If you want to send me source code, you MUST include the combined 
source/assembly listing.  Sending me straight C code is useless.  99% of the
problems occur because the compiler isn't doing what you think it's doing.


Contents
--------

File           Description
-----------------------------------------------------
README.TMR     This file
TIMER0.SYS     HRT driver
TIMER0.SYM     symbols file for above
CLOCK01.SYS    modified CLOCK01.SYS for HRT support
CLOCK01.SYM    symbols file for above
CLOCK02.SYS    modified CLOCK02.SYS for HRT support
CLOCK02.SYM    symbols file for above
TMR0_IDC.H     header file used to communicate with HRT from another PDD
TMR0_IOC.H     header file for IOCtl interface to HRT


Installation
------------

1. Make backups of \OS2\CLOCK*.SYM and \OS2\BOOT\CLOCK*.SYS
2. Copy all CLOCK*.* files (.SYM's in \OS2, .SYS's in \OS2\BOOT)
3. Make sure there isn't a copy of CLOCK0x.SYS in \OS2.  If so, delete it.
3. Copy TIMER0.* into the root directory (or anywhere you like)
4. Add the following line to your CONFIG.SYS:

      BASEDEV=\TIMER0.SYS

The driver is only intended for OS/2 Warp 3.0.  It has not been tested and is
not supported under any previous version of OS/2.  If you do use it under 
any previous version, it is at your own risk.


Microsoft C 6.0 Users
---------------------

The header files were designed for Watcom C/C++ 10.0.  If you are still
using MS C 6.0, let me first tell you that you're living in the dark ages.
Secondly, 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.

However, to date this hasn't been verified.


Usage - Ring 0
--------------

There are two services that the HRT provides:

1. Callback every n milliseconds, where n is an integer >= 1
2. Query the current time

Option 1 requires that the DD register itself with the HRT.  The DD provides
a 16:16 address of a function that the HRT will call every n milliseconds.
Registration (and derigstration) cannot occur during interrupt time.

If a DD re-registers itself (by calling the registration function with the
same 16:16 pointer), the HRT simply changes the count.  This can occur at
interrupt time.

Option 2 is used to obtain a 16:16 pointer to the current count variable.
This variable can then be read by the DD to obtain the current time.  Note
that this variable is only updated if the HRT is active, which is only true
if at least one driver or application has registered itself with the HRT.

To call the HRT, call DevHelp_AttachDD with the 1st parameter as "TIMER0$ "
like this:

   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 drive with TIMER0.SYS, do something like:

   #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, but it can be made from the INIT
COMPLETE routine.  The DevHelp_AttachDD call can be made from the INIT
routine.

HOWEVER, you should realize that when you register your driver, TIMER0
will start taking interrupts every millisecond.  This will affect system
performance.  If you call ptmrfn(TMR0_REG,...) at INIT COMPLETE, and don't
deregister ever, then your driver will always slow down the system, even when
it's not in use.  Instead, you should provide IOCtl's or some other mechanism
to selectively register and deregister your driver with TIMER0.


Usage - Ring 3
--------------

The IOCtl interface provides the ability to obtain a pointer to the clock 
counter, query and set the resolution, and block until a certain time has 
elapsed.  The pointer returned is a 16:16 pointer, which should be converted 
to a 0:32 pointer for use with 32-bit applications.

Currently, the resolution is always 1ms.  Attempts to set it to another value 
will be ignored, and querying the driver will always return 1 millisecond.

HOWEVER, you should set the resolution anyway.  In the future, the driver may
actually use a lower resolution to conserve resources, so don't make any
assumptions.

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.

   #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,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);

The DosOpen("TIMER0$") registers the application as a client.  At this point, the
highres timer driver is taking interrupts, so make sure the driver is open only
when you need timer services.  After the DosClose, the pointer is invalid and the
driver has stopped taking interrupts (unless there are other clients).

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;                 // # 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,0x80,HRT_BLOCKUNTIL,&ulDelay,ulSize2,&ulSize2,NULL,0,NULL);

Using a time-critical thread is important.


COMMON PROBLEMS
---------------

Trap D - This is usually caused by using the wrong version of CLOCK0x.SYS.
In particular, if the old CLOCK0x.SYS is in \OS2, 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 driver.

Clock driver won't load - either some strange performance tool is running, or
you are using the Warp GA version of CLOCK0x.SYS.


CHANGES
-------

6/15/95

Updated TMR0_IDC.H to document error code 5, which is returned if TIMER0.SYS
could not get IRQ 0 from the clock driver.

6/13/95

The driver returns HRTERR_BADPARAM if it can't write to the DosDevIOCtl data
area.  This could happen if pData points to NULL, or cbDataLenMax is less 
than 4 (the size of a ULONG).

For HRT_GET_POINTER, the driver used to return a NULL pointer if it could
not obtain a pointer.  Instead, the driver returns HRTERR_BADPARAM and does
not write anything to the DosDevIOCtl data area.

Fixed bug in TMR0_GETTMRPTR which would write the pointer to the HRT's data
segment instead of the client PDD's data segment.


CAVEATS
-------

Only the 1st parameter to PTMRFN is checked.  No other parameters are 
checked.

It is possible to write to the timer count variable from another PDD and 
screw up the HRT.

While TIMER0 is active (i.e. while it's taking interrupts), DOS sessions
do not have access to timer 0.  This means that the HW_TIMER DOS setting
is ignored.

Performance tools, such as C Set's DDE4XTRA.SYS, are incompatible with this
driver.


BUGS
----

Multiple DD's attached to the HRT has not been tested

The HRT always sets interrupts at 1ms, even if a slower rate would suffice.
For instance, if one driver wants callbacks every 2ms, and another wants
them at 4ms, the driver could program IRQ0 for 2ms ticks.

DD's should not rely on the 1st tick occuring at any particular time.  That
is, if a driver registers for 1ms, the 1st tick might occur less than 1ms after the
registration.

HRT_FREE_POINTER currently doesn't do anything.
