www.delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp/1995/04/03/12:58:59

Date: Mon, 3 Apr 1995 10:01:03 +0500
From: hvb AT netrix DOT com
To: chan1355 AT cs DOT cuhk DOT hk, jk55 AT cornell DOT edu (Jim Karpinski)
Cc: djgpp AT sun DOT soe DOT clarkson DOT edu
Subject: Re: Code for controlling PC Timer
References: <9503312208 DOT AA07914 AT azalea> <9504010251 DOT AA10100 AT cucs18 DOT cs DOT cuhk DOT hk>

Dear all,

Here is the code that I cut and paste from my code.  You may have to
declare the static variables here and there to get it compiled.

/*************************************************************************
        Using the TOD 8253/4 timer.  Works but had problems under
        heavy load of disk access.
*************************************************************************/

#define DOSX_GO32_RAM_OFFSET 0xe0000000

/*---------------------------------------------------------------------------
  Definitions for The PC INTERRUPT CONTROLLER
---------------------------------------------------------------------------*/
#define PC_INT_CONTROLLER  0x20        /* Interrupt controller I/O Address */

#define PC_8253_TIMER_CHANNEL  0x40    /* Timer channel (#0) of the timer chip  */
#define PC_8253_COMMAND_REG   = 0x43   /* Command register of the timer chip    */
#define PC_8253_CH_0_SEQ      = 0x36   /* 00110110 : ch 0, 2 bytes, mode 3, bin */

#define PC_8253_ONE_MSEC_COUNT = 1193   /* The PC 8253 clock input is 1.193180 MHz */
#define PC_8253_CLOCK_RATE     = 1193180
#define TO_KICK_DOS_CLOCK      = 0xffff


/*********************************************************************
*                   PM IRQ 8
* 
*  DESCRIPTION:
*    This function is called when the Time Of Day interrupt occurs
*    while the 386 CPU is in Protected mode.
*    We need to call the DOS real mode interrupt whenever the
* 
*  NOTES:
*    1.  The Rel_1ms clock is not counted every so often to take into
*        account that the real time clock is fast.
*    2.  alias_irq_regs & regs are declared sattic as the 'dpmi' code
*        assumes they belong to the 'DS' selector.
*    3.  Interrupt is explicitly disabled with an "cli" instruction
*        as when int86 is called, the processor may get another clock
*        interrupt when switched back from the real mode.
*********************************************************************/
static void
pm_irq_08 (void)
{
  asm ("cli");
  asm ("push   %eax");
  asm ("push   %ecx");
  asm ("push   %edx");
  asm ("push   %ds");
  asm ("push   $0x48");
  asm ("pop    %ds");

  if ((*One_msec_clock_remainder += *Delta_per_tick) >= PC_8253_CLOCK_RATE)
    *One_msec_clock_remainder -= PC_8253_CLOCK_RATE;
  else
    *Rel_1ms += *Msec_per_tick;

  if ((*Dos_clock_remainder += *Timer_counter) >= TO_KICK_DOS_CLOCK)
  { asm ("pusha");
    asm ("push   %gs");

    *Dos_clock_remainder -= TO_KICK_DOS_CLOCK;

/*-------------------------------------------------------------------
    This is equivalent to _go32_dpmi_simulate_fcall_iret
-------------------------------------------------------------------*/
    Alias_irq_regs.x.cs = Rm_old_irq8.rm_segment;
    Alias_irq_regs.x.ip = Rm_old_irq8.rm_offset;
    Alias_irq_regs.x.ss = 0;
    Alias_irq_regs.x.sp = 0;
    Alias_irq_regs.x.flags = 0;
    Irq_regs.x.ax = 0x302;
    Irq_regs.h.bh = 0;
    Irq_regs.x.cx = 0;
    Irq_regs.x.di = (uint32)(&Alias_irq_regs);
    int86 (0x31, &Irq_regs, &Irq_regs);

    asm ("add    $12, %esp");
    asm ("pop    %gs");
    asm ("popa");
  }
  else
  { asm ("mov    $0x20,%al");
    asm ("out    %al,$0x20");
  }

  asm ("pop    %ds");
  asm ("pop    %edx");
  asm ("pop    %ecx");
  asm ("pop    %eax");
  asm ("leave");
  asm ("iret");
}

/*********************************************************************
*                   INSTALL TOD TIMER INTERRUPT 
* 
*  DESCRIPTION:
*    This function will set up the PC time of day (tod) interrupt to
*    generate interrupt approximately every one millisecond.
*    There are the protected mode and real mode interrupts.
*    The protected mode interrupt needs to call the DOS tod interrupt
*    roughly every 18msec to keep the DOS clock happy.  A set of
*    counters are stored in the DOS low memory so the protected mode
*    and real mode code can share.
*    In real mode, we allocate a block of DOS low memory to create
*    the real mode interrupt code.  This code basically will update
*    the Rel_1ms count and call the DOS tod interrupt every 18 msec.
*
*  NOTES:
*
*********************************************************************/
static void
install_tod_timer_interrupt (void)
{
  _go32_dpmi_seginfo          rm_si;
  _go32_dpmi_seginfo          pm_si;
  uint32                      linear_addr;
  uint32                      go32_addr;
  uint16                     *p16;
  uint32                     *p32;

/*---------------------------------------------------------------------------
  This data space is the code for the real mode clock interrupt.
  The C equivalent form is:

  extern void dos_interrupt (void);
  static unsigned long Timer_counter;
  static unsigned long Dos_clock_remainder;
  static unsigned long One_msec_clock_remainder;
  static unsigned long Delta_per_tick;
  static unsigned long Rel_1ms;
  static unsigned long Msec_per_tick;
  
  #define PC_8253_CLOCK_RATE     (1193180L)
  #define TO_KICK_DOS_CLOCK      (0xffffL)
  
  extern void x (void);
  extern void outp (unsigned reg, unsigned value);
  void x (void)
  {
    if ((One_msec_clock_remainder += Delta_per_tick) >= PC_8253_CLOCK_RATE)
      One_msec_clock_remainder -= PC_8253_CLOCK_RATE;
    else
      Rel_1ms += Msec_per_tick;
  
    if ((Dos_clock_remainder += Timer_counter) >= TO_KICK_DOS_CLOCK)
    { Dos_clock_remainder -= TO_KICK_DOS_CLOCK;
      goto dos_interrupt;
    }
    else
      outp (0x20, 0x20);
  }

---------------------------------------------------------------------------*/
  static uint8 real_mode_code[] = {
                                        /* offset  opcode                          Assembly Mnemonic                                         */
    
    0x00,0x00,0x00,0x00,                /* 0000    00000000                        Timer_counter            dd 0                             */
    0x00,0x00,0x00,0x00,                /* 0004    00000000                        Dos_clock_remainder      dd 0                             */
    0x00,0x00,0x00,0x00,                /* 0008    00000000                        One_msec_clock_remainder dd 0                             */
    0x00,0x00,0x00,0x00,                /* 000C    00000000                        Delta_per_tick           dd 0                             */
    0x00,0x00,0x00,0x00,                /* 0010    00000000                        Rel_1ms                  dd 0                             */
    0x00,0x00,0x00,0x00,                /* 0014    00000000                        Msec_per_tick            dd 0                             */
                                
    0xfa,                               /* 0018    FA                              cli                                                       */
    0x50,                               /* 0019    50                              push   ax                                                 */
    0x52,                               /* 001A    52                              push   dx                                                 */
                                
    0x2e,0xa1,0x0c,0x00,                /* 001B    2E: A1 000C R                   mov    ax,WORD PTR cs:Delta_per_tick                      */
    0x2e,0x8b,0x16,0x0e,0x00,           /* 001F    2E: 8B 16 000E R                mov    dx,WORD PTR cs:Delta_per_tick+2                    */
    0x2e,0x01,0x06,0x08,0x00,           /* 0024    2E: 01 06 0008 R                add    WORD PTR cs:One_msec_clock_remainder,ax            */
    0x2e,0x11,0x16,0x0a,0x00,           /* 0029    2E: 11 16 000A R                adc    WORD PTR cs:One_msec_clock_remainder+2,dx          */
    0x2e,0x83,0x3e,0x0a,0x00,0x12,      /* 002E    2E: 83 3E 000A R 12             cmp    WORD PTR cs:One_msec_clock_remainder+2,18          */
    0x72,0x1b,                          /* 0034    72 1B                           jb     $I112                                              */
    0x77,0x09,                          /* 0036    77 09                           ja     $L20000                                            */
    0x2e,0x81,0x3e,0x08,0x00,0xdc,0x34, /* 0038    2E: 81 3E 0008 R 34DC           cmp    WORD PTR cs:One_msec_clock_remainder,13532         */
    0x72,0x10,                          /* 003F    72 10                           jb     $I112                                              */
                                        /* 0041                           $L20000:                                                           */
    0x2e,0x81,0x2e,0x08,0x00,0xdc,0x34, /* 0041    2E: 81 2E 0008 R 34DC           sub    WORD PTR cs:One_msec_clock_remainder,13532         */
    0x2e,0x83,0x1e,0x0a,0x00,0x12,      /* 0048    2E: 83 1E 000A R 12             sbb    WORD PTR cs:One_msec_clock_remainder+2,18          */
    0xeb,0x14,                          /* 004E    EB 14                           jmp    SHORT $I113                                        */
    0x90,                               /* 0050    90                              nop                                                       */
                                        /* 0051                           $I112:                                                             */
    0x2e,0xa1,0x14,0x00,                /* 0051    2E: A1 0014 R                   mov    ax,WORD PTR cs:Msec_per_tick                       */
    0x2e,0x8b,0x16,0x16,0x00,           /* 0055    2E: 8B 16 0016 R                mov    dx,WORD PTR cs:Msec_per_tick+2                     */
    0x2e,0x01,0x06,0x10,0x00,           /* 005A    2E: 01 06 0010 R                add    WORD PTR cs:Rel_1ms,ax                             */
    0x2e,0x11,0x16,0x12,0x00,           /* 005F    2E: 11 16 0012 R                adc    WORD PTR cs:Rel_1ms+2,dx                           */
                                        /* 0064                           $I113:                                                             */
    0x2e,0xa1,0x00,0x00,                /* 0064    2E: A1 0000 R                   mov    ax, WORD PTR cs:Timer_counter                      */
    0x2e,0x8b,0x16,0x02,0x00,           /* 0068    2E: 8B 16 0002 R                mov    dx, WORD PTR cs:Timer_counter+2                    */
    0x2e,0x01,0x06,0x04,0x00,           /* 006D    2E: 01 06 0004 R                add    WORD PTR cs:Dos_clock_remainder, ax                */
    0x2e,0x11,0x16,0x06,0x00,           /* 0072    2E: 11 16 0006 R                adc    WORD PTR cs:Dos_clock_remainder+2, dx              */
    0x2e,0x83,0x3e,0x06,0x00,0x00,      /* 0077    2E: 83 3E 0006 R 00             cmp    WORD PTR cs:Dos_clock_remainder+2, 0               */
    0x75,0x08,                          /* 007D    75 08                           jne    $L20002                                            */
    0x2e,0x83,0x3e,0x04,0x00,0xff,      /* 007F    2E: 83 3E 0004 R FF             cmp    WORD PTR cs:Dos_clock_remainder, TO_KICK_DOS_CLOCK */
    0x72,0x13,                          /* 0085    72 13                           jb     $I114                                              */
                                        /* 0087                           $L20002:                                                           */
    0x2e,0x83,0x2e,0x04,0x00,0xff,      /* 0087    2E: 83 2E 0004 R FF             sub    WORD PTR cs:Dos_clock_remainder, TO_KICK_DOS_CLOCK */
    0x2e,0x83,0x1e,0x06,0x00,0x00,      /* 008D    2E: 83 1E 0006 R 00             sbb    WORD PTR cs:Dos_clock_remainder+2, 0               */
    0x5a,                               /* 0093    5A                              pop    dx                                                 */
    0x58,                               /* 0094    58                              pop    ax                                                 */
    0xea,0x00,0x00,0x00,0x00,           /* 0095    EA 0000 ---- E                  jmp    <to be set at run time>                            */
                                        /* 009A                           $I114:                                                             */
    0xb0,0x20,                          /* 009A    B0 20                           mov    al,20H                                             */
    0xe6,0x20,                          /* 009C    E6 20                           out    20H,al                                             */
    0x5a,                               /* 009E    5A                              pop    dx                                                 */
    0x58,                               /* 009F    58                              pop    ax                                                 */
    0xcf                                /* 00A0    CF                              iret                                                      */
  };
  
/*---------------------------------------------------------------------------
  allocate DOS low memory to store the code above.
---------------------------------------------------------------------------*/
  Dosx_rm_clock_int_seginfo.size = (sizeof (real_mode_code) + 0x10) / 0x10;
  if (_go32_dpmi_allocate_dos_memory (&Dosx_rm_clock_int_seginfo))
    exit (1);

/*---------------------------------------------------------------------------
  make sure where we put the code is paragraph (16-byte) aligned
  fill memory with INT 3 opcode.
---------------------------------------------------------------------------*/
  linear_addr = (uint16)Dosx_rm_clock_int_seginfo.rm_segment * 0x10;
  if (linear_addr & 0x0000000f)
    linear_addr = (linear_addr + 0x10) & 0xfffffff0;
  go32_addr = linear_addr + DOSX_GO32_RAM_OFFSET;
  memset ((uint8 *)go32_addr, 0xcc, Dosx_rm_clock_int_seginfo.size * 0x10);

/*---------------------------------------------------------------------------
  set up pointers to the shared timer variables, and initialize variables
---------------------------------------------------------------------------*/
  p32 = (uint32 *)go32_addr;
  Timer_counter = p32++;
  Dos_clock_remainder = p32++;
  One_msec_clock_remainder = p32++;
  Delta_per_tick = p32++;
  Rel_1ms = p32++;
  Msec_per_tick = p32;

/*---------------------------------------------------------------------------
  write the code to the DOS low memory
---------------------------------------------------------------------------*/
  _go32_dpmi_get_real_mode_interrupt_vector(8, &Rm_old_irq8);
  memcpy ((uint8 *)go32_addr, real_mode_code, sizeof (real_mode_code));

  *Timer_counter = 1193;
  *Delta_per_tick = PC_8253_CLOCK_RATE % Timer_counter_value;
  *Msec_per_tick = 1000 / (PC_8253_CLOCK_RATE / Timer_counter_value);

  p16 = (uint16 *)(go32_addr + 0x96);
  *p16++ = Rm_old_irq8.rm_offset;
  *p16 = Rm_old_irq8.rm_segment;

/*---------------------------------------------------------------------------
  setup real and protected mode interrupt vectors
---------------------------------------------------------------------------*/
  memset (&rm_si, 0, sizeof (rm_si));
  rm_si.rm_offset = 0x18;
  rm_si.rm_segment = (uint16)(linear_addr >> 4);

  memset (&pm_si, 0, sizeof (pm_si));
  pm_si.pm_offset = (int) pm_irq_08;
  pm_si.pm_selector = _go32_my_cs();

  disable();

  _go32_dpmi_set_real_mode_interrupt_vector(8, &rm_si);
  _go32_dpmi_set_protected_mode_interrupt_vector(8, &pm_si);

  outpb (PC_8253_COMMAND_REG, PC_8253_CH_0_SEQ);
  outpb (PC_8253_TIMER_CHANNEL, Timer_counter_value & 0xff);
  outpb (PC_8253_TIMER_CHANNEL, Timer_counter_value >> 8);

  enable();
}



/*************************************************************************
        Using the RTC timer.  Worked on one PC platform but I
        found it did not work on another platform.  Still
        under investigation.
*************************************************************************/
#define DOSX_GO32_RAM_OFFSET   0xe0000000

/*---------------------------------------------------------------------------
                    MC146818A Real Time Clock (RTC)

  This RTC device can generate IRQ8 interrupt on a periodic basis to
  the PC. See:

  - Dallas Semiconductor handbook for device DS1287 (MC146818A equiv.)
  - ISA System Architecture, by Tom Shanley & Don Anderson - Mind Share Inc
    1993. (Chapter 21).

---------------------------------------------------------------------------*/

#define RTC_ADDR_PORT           = 0x70;  /* RTC chip address port */
RTC_DATA_PORT           = 0x71;  /* RTC chip data port */

RTC_STATUS_REG_A        = 0xa;   /* RTC Status Register A RAM offset */
RTC_STATUS_REG_B        = 0xb;   /* RTC Status Register B RAM offset */
RTC_STATUS_REG_C        = 0xc;   /* RTC Status Register C RAM offset */

                                 /* status A register definition */
enum uint8 {
  RTC_REG_A_RATE_MASK   = 0x0f,  /* mask to the DS0-DS3 bits */
  RTC_REG_A_RATE_1K     = 0x06,  /* DS0-DS3 value for 1.024 kHz clock rate */
  RTC_REG_A_UIP_BIT     = 0x80,  /* =1 Update in Progress, Read only */
  RTC_REG_A_DV_MASK     = 0x70,  /* oscillator on and off bit */
  RTC_REG_A_DV_32K      = 0x20   /* DV bits to be set to 32.768khz timebase */
};
                                 /* status B register definition */
RTC_REG_B_PIE_BIT       = 0x40;  /* 1=> Periodic Interrupt enable */
RTC_REG_B_AIE_BIT       = 0x20;  /* 1=> Alarm Interrupt enable */
RTC_REG_B_UEIE_BIT      = 0x10;  /* 1=> Update-Ended Interrupt Enable */

RTC_INT_TYPE            = 0x70;  /* RTC interrupt type */

RTC_REMAINDER_PER_TICK  = 24;    /* count remainder per tick */


- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019