Jump to content

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

From EDM2
Created page with "== Using the High-Resolution Timer == The high-resolution timer (HRT) is used to provide millisecond-accuracy timing services to device drivers and applications. === Introduc..."
 
No edit summary
Line 1: Line 1:
== Using the High-Resolution Timer ==
=Using the High-Resolution Timer=
The high-resolution timer (HRT) is used to provide millisecond-accuracy timing services to device drivers and applications.
The high-resolution timer (HRT) is used to provide millisecond-accuracy timing services to device drivers and applications.


=== Introduction ===
==Introduction==
The high-resolution timer package includes the following files:
The high-resolution timer package includes the following files:
: TIMER0.SYS the high-resolution timer driver
: [[TIMER0.SYS]] the high-resolution timer driver
: CLOCK01.SYS a modified CLOCK01.SYS for high-resolution timer support
: [[CLOCK01.SYS]] a modified CLOCK01.SYS for high-resolution timer support
: CLOCK02.SYS modified CLOCK02.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_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
: TMR0_IOC.H the header file for IOCtl interface to the high-resolution timer
: TIMER0.DDP the DDINSTAL installation script
: TIMER0.DDP the DDINSTAL installation script


=== Installation ===
===Installation===
To install the high-resolution timer device driver, follow these steps:
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.:
1.Remove the readonly attribute from \OS2\BOOT\CLOCK*.SYS with the attrib command, e.g.:  


<pre class="western">-r \os2\boot\clock*.sys</pre>
-r \os2\boot\clock*.sys
where * is the number in the name of your existing clock-system file. <br />2.Make a backup of \OS2\BOOT\CLOCK*.SYS. <br />3.Use DDINSTAL to install the driver. <br />4.Reboot.
 
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.
'''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 ===
=== 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.
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 ===
=== Ring-0 Usage ===
The high-resolution timer provides the following two services:
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.
;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.
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 &quot;TIMER0$ &quot;. 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:
;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.


<pre class="western">TIMER0_ATTACH_DD DDTable;
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:
<pre>
TIMER0_ATTACH_DD DDTable;
PTMRFN ptmrfn;
PTMRFN ptmrfn;


DDTable.pfn=NULL;
DDTable.pfn=NULL;
if( DevHelp_AttachDD( ( NPSZ ) &quot;TIMER0$ &quot;, ( NPBYTE )  &amp;DDTable ) )
if( DevHelp_AttachDD( ( NPSZ ) "TIMER0$ ", ( NPBYTE )  &DDTable ) )
{
{
     // TIMER0.SYS not loaded
     // TIMER0.SYS not loaded
Line 49: Line 55:
}
}


ptmrfn=DDTable.pfn;</pre>
ptmrfn=DDTable.pfn;
 
</pre>
To register your driver with TIMER0.SYS, do something like this:
To register your driver with TIMER0.SYS, do something like this:
 
<pre>
<pre class="western">#define N    10                              // Call me every N milliseconds
#define N    10                              // Call me every N milliseconds


void __far __loadds InterruptHandler( void )
void __far __loadds InterruptHandler( void )
Line 63: Line 69:
{
{
   // Registration failed
   // Registration failed
}</pre>
}
 
</pre>
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.
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.
The ptrmfn() call cannot be made from INIT COMPLETE either. Attempts to do so will result in a return code of 6.
Line 72: Line 78:
The IOCtl interface provides the ability to:
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.
;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
;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.


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.
;block until a certain time has elapsed


<pre class="western">#include &quot;tmr0_ioc.h&quot;
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.
<pre>
#include "tmr0_ioc.h"


APIRET rc;
APIRET rc;
Line 95: Line 101:
ULONG  * _Seg16 pulTimer16;
ULONG  * _Seg16 pulTimer16;
ULONG  ulSize2=sizeof( pulTimer16 );
ULONG  ulSize2=sizeof( pulTimer16 );
ULONG  *pulTimer;</pre>
ULONG  *pulTimer;




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


printf( &quot;TIMER0$ opened.  File Handle is %lu\n&quot;,hfile );</pre>
printf( "TIMER0$ opened.  File Handle is %lu\n",hfile );




<pre class="western">rc=DosDevIOCtl( hfile, HRT_IOCTL_CATEGORY, HRT_SET_RESOLUTION,
rc=DosDevIOCtl( hfile, HRT_IOCTL_CATEGORY, HRT_SET_RESOLUTION,
                 &amp;ulResolution, ulSize1, &amp;ulSize1, NULL, 0, NULL );
                 &ulResolution, ulSize1, &ulSize1, NULL, 0, NULL );
if( rc )
if( rc )
{
{
   printf( &quot;Couldn't set resolution.\n&quot; );
   printf( "Couldn't set resolution.\n" );
   DosClose( hfile );
   DosClose( hfile );
   return;
   return;
}</pre>
}




<pre class="western">rc=DosDevIOCtl( hfile, HRT_IOCTL_CATEGORY, HRT_GET_POINTER,
rc=DosDevIOCtl( hfile, HRT_IOCTL_CATEGORY, HRT_GET_POINTER,
                 NULL, 0, NULL, &amp;pulTimer16, ulSize2, &amp;ulSize2 );
                 NULL, 0, NULL, &pulTimer16, ulSize2, &ulSize2 );
if( rc )
if( rc )
{
{
   printf( &quot;Couldn't get pointer.\n&quot; );
   printf( "Couldn't get pointer.\n" );
   DosClose( hfile );
   DosClose( hfile );
   return;
   return;
Line 130: Line 136:
if( !pulTimer )
if( !pulTimer )
{
{
   printf( &quot;NULL pointer.\n&quot; );
   printf( "NULL pointer.\n" );
   DosClose( hfile );
   DosClose( hfile );
   return;
   return;
Line 137: Line 143:
// At this point, pulTimer is now a pointer to the timer 0 counter variable
// At this point, pulTimer is now a pointer to the timer 0 counter variable


rc=DosClose( hfile );</pre>
rc=DosClose( hfile );
 
</pre>
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.
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.
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.
 
<pre>
<pre class="western">ULONG  ulDelay=1;                    // Number of milliseconds to wait
ULONG  ulDelay=1;                    // Number of milliseconds to wait
ULONG  ulSize2=sizeof( ulDelay );
ULONG  ulSize2=sizeof( ulDelay );


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


Line 157: Line 162:
The following problems might occur due to the nature of the high-resolution driver.
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 &quot;dir \clock0?.sy? /s&quot; on the boot partition. This will search the entire hard drive for the clock drivers.
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.
clock driver will not load Either some performance tool is running, or you are using the Warp GA version of CLOCK0x.SYS.

Revision as of 22:46, 12 April 2016

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:

#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.

#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:

  • 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 Visual Age's CPPOPA3.SYS are incompatible with this driver.
  • Device drivers should not depend on having the first tick occur at any particular time.

If a driver registers for 1 millisecond, for example, the first tick might occur less than 1 millisecond after the registration.

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.

Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation