4

I'm writing an DOS Protected Mode application for FreeDOS in C. The Compiler is Open Watcom 1.8.

I need to write a timeout routine, that initiates a timeout variable and then when timeout variable becomes zero the application will know that a specified number of milliseconds(ms) has elapsed.

This, I think can be done via writing an ISR. The ISR will decrement the timeout variable each time ISR is called. The application continously checks whether timeout variable is zero or not.

Now my question is :

Is there any reliable way of implementing such timeout ISR. I read about 0x1C interrupt vector, but that provides a resolution of only 55ms. I need much more resolution. For e.g. of 1ms or even lower if possible.

How can this be done? Any ideas or suggestions?

jacks
  • 294
  • 3
  • 15

2 Answers2

3

Here's a demo showing how to change the default timer rate from 18.2 Hz to other values. Compiles with Borland Turbo C++ (as real-mode EXE app) and with Open Watcom C++ 1.9 (as DPMI/dos4gw app).

// file: tmr.c
#include <stddef.h>
#include <stdio.h>
#include <dos.h>
#include <conio.h>

#ifdef __WATCOMC__
// Define Borland C aliases:
#define getvect _dos_getvect
#define setvect _dos_setvect
#define outportb outp
#define inportb inp
#define disable _disable
#define enable _enable
#endif

typedef unsigned uint;
typedef unsigned char uchar;

void interrupt (*pOldInt1C)(void) = NULL;

volatile uint int1Ccnt = 0;

void interrupt NewInt1C(void)
{
  int1Ccnt++;
  pOldInt1C();
}

void SetPitResolutionInHz(uint ResolutionInHz)
{
  uint count;

  if (ResolutionInHz < 18 || ResolutionInHz >= 65535)
    return;

  count = (ResolutionInHz == 18) ? 0 : (uint)(1193181 / ResolutionInHz);

  disable();

  outportb(0x43, 0x34);
  outportb(0x40, (uchar)(count & 0xFF));
  outportb(0x40, (uchar)(count >> 8));

  enable();
}

int main(void)
{
  pOldInt1C = getvect(0x1C);
  setvect(0x1C, &NewInt1C);

  printf("3 seconds delay, default timer rate...\n");
  while (int1Ccnt < 18*1*3)
  {
    static uint last = 0;
    if (last != int1Ccnt)
    {
      printf("1"); fflush(stdout);
      last = int1Ccnt;
    }
  }
  printf("\n");

  SetPitResolutionInHz(18*2);
  printf("3 seconds delay, double timer rate...\n");
  int1Ccnt = 0;
  while (int1Ccnt < 18*2*3)
  {
    static uint last = 0;
    if (last != int1Ccnt)
    {
      printf("2"); fflush(stdout);
      last = int1Ccnt;
    }
  }
  printf("\n");

  SetPitResolutionInHz(18*3);
  printf("3 seconds delay, triple timer rate...\n");
  int1Ccnt = 0;
  while (int1Ccnt < 18*3*3)
  {
    static uint last = 0;
    if (last != int1Ccnt)
    {
      printf("3"); fflush(stdout);
      last = int1Ccnt;
    }
  }
  printf("\n");

  // Set default rate: 1193181 MHz / 65536 = 18.2 Hz
  SetPitResolutionInHz(18*1);
  printf("3 seconds delay, default timer rate...\n");
  int1Ccnt = 0;
  while (int1Ccnt < 18*1*3)
  {
    static uint last = 0;
    if (last != int1Ccnt)
    {
      printf("1"); fflush(stdout);
      last = int1Ccnt;
    }
  }
  printf("\n");

  setvect(0x1C, pOldInt1C);

  return 0;
}

It will print 1s, 2s and 3s at the default, double and triple the default rates for 3 seconds each.

Beware, this code screws up BIOS/DOS timings (both use timer interrupts for various delays). To workaround that you want to hook vector 8 (IRQ0) instead of vector 0x1C (it's called from vector 8 ISR) and call the original IRQ0 ISR from your new IRQ0 ISR at about the default rate (you need to count interrupts for that). When you don't call it from your ISR, you need to manually signal the end of interrupt handling by doing outportb(0x20, 0x20); before returning from the ISR (the original ISR does that, but if you don't call it, it's your responsibility).

EDIT: Be advised that in virtualized environments timer interrupts may be lost or delivered at irregular intervals, especially if you set a high interrupt rate and your PC is busy with other tasks. Also, even on a physical machine you can have issues with high timer frequencies, System Management Interrupts (SMIs) will introduce jitter/variable latency in interrupt delivery. You can't get rid of them, they're handled transparently by the BIOS.

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
  • Well, I was looking for such issues after reading the answers and it doesn't surprise me that they do exits. :) But is there some more reliable way to implements such things, that keep hardware happy. I mean how do operating systems like Windows and Linux implement such things? Of course, the kernel gurus must be credited for their hard work, but I think that in a single tasking environment like DOS , it would be simpler to implement those same principles. Isn't it? – jacks Jul 16 '12 at 13:00
  • Neither Windows nor Linux (the most common kind of it) is a real-time OS. They are designed to run on stock hardware stably. They have provisions for time synchronization when running in a VM, which is not the same thing as getting perfect timer interrupts. Beyond that they don't really care nor can do much about the imperfections of the executing environment. – Alexey Frunze Jul 16 '12 at 13:12
  • Actually perfect timer interrupts are not required - a few ms can be tolerated - say instead of 2ms, interrupt is invoked after 5ms. What I'm looking for a stable approach w/o screwing things up. – jacks Jul 16 '12 at 13:21
  • You didn't say what you're trying to achieve. What is tolerable depends on what you're doing, right? – Alexey Frunze Jul 16 '12 at 13:22
  • I'm trying to implement timeout for a hard drive(HDD) application. This application will access the HDD via ata interface. The timeout is required to keep an eye on whether disk is following the protocol or not - For e.g. during a read, a HDD can become unresponsive and then the host need to issue a reset after a timeout. The timeouts are also required for other hardware e.g for slow devices attached to parallel port etc. So, the resolution need to be in microseconds. – jacks Jul 16 '12 at 13:28
1

You can hook the timer interrupt (08h) and configure the PIT to get a rate of up to 1.2 Mhz.

Here's some old TASM-style assembly that shows how this can be done:

tmTimerHandler PROC
    push ds
    mov ds,cs:tmDataSeg
    add ds:tmTicker,65536
    pop ds
    jmp cs:tmOldTimer
tmTimerHandler ENDP


tmInit PROC
    mov tmDataSeg,ds
    mov tmTicker,65536
    push es
    ; Save the old timer interrupt vector
    mov ax,3508h
    int 21h
    mov dword ptr tmOldTimer+0,ebx
    mov word ptr tmOldTimer+4,es
    pop es

    ; Install our own timer interrupt vector
    push ds
    mov ax,2508h
    push cs
    pop ds
    mov edx,OFFSET tmTimerHandler
    int 21h
    pop ds

    ; Configure the PIT to generate interrupts
    ; at the maximum rate
    mov al,34h
    out 43h,al
    xor al,al  ; zero divisor
    out 40h,al
    out 40h,al
    ret
tmInit ENDP


tmClose PROC
   push ds
   mov ax,2508h
   lds edx,tmOldTimer
   int 21h
   pop ds
   ret
tmClose ENDP


; Returns the current tick count in eax
tmGetTimer PROC
   pushf
   cli
   xor eax,eax
   out 43h,al
   in al,40h
   mov ah,al
   in al,40h
   xchg ah,al
   neg eax
   add eax,tmTicker
   popf
   ret
tmGetTimer ENDP


.data
  tmOldTimer    df 0
  tmDataSeg dw 0
  tmTicker  dd 0
Michael
  • 57,169
  • 9
  • 80
  • 125
  • Well, thanks for throwing light on issue. :) But it seems that there will be some hardware issues with this approach as Alexey pointed out. I'm searching for more of such issues in this approach. – jacks Jul 16 '12 at 13:04