delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp/2002/01/18/18:31:35

X-Authentication-Warning: delorie.com: mailnull set sender to djgpp-bounces using -f
From: "Petr Maxa" <pmaxa AT mapyoscar DOT sk>
Newsgroups: comp.os.msdos.djgpp
Subject: Re: strange problem with dos DPMI
Date: Sat, 19 Jan 2002 00:19:14 +0100
Lines: 610
Message-ID: <a2aani$vun38$1@ID-127824.news.dfncis.de>
References: <a27jkv$v0b6a$1 AT ID-127824 DOT news DOT dfncis DOT de> <3c479438 DOT sandmann AT clio DOT rice DOT edu>
NNTP-Posting-Host: dial241.za.euroweb.sk (195.72.5.241)
X-Trace: fu-berlin.de 1011396150 33512552 195.72.5.241 (16 [127824])
X-Newsreader: Microsoft Outlook Express 5.50.4522.1200
X-MimeOLE: Produced By Microsoft MimeOLE V5.50.4522.1200
To: djgpp AT delorie DOT com
DJ-Gateway: from newsgroup comp.os.msdos.djgpp
Reply-To: djgpp AT delorie DOT com

If no page fault and after certain time program continues to operate
normally.
I lock the whole memory.

It seems that freezing occures more often when the program tries to save or
read e.g. 60 times few (2 to 60) bytes in a loop during transmition even the
transmittion is slow ( 50 Bd), that is program switch very often from
protected to real mode and back.

I just continue to observe the behaviour of the program.

Just for Your convinience I sent a short version of program, which works
under both environment:

Thanks for Your help.
Petr

#include <dpmi.h>
#include <go32.h>


class DJGPP_INTERRUPT {
   int irq_no,ino,set;
   _go32_dpmi_seginfo   si_pm_oldisr;     /* original prot-mode key IRQ */
   _go32_dpmi_seginfo   si_pm_isr;      /* prot-mode interrupt segment info
*/
   _go32_dpmi_seginfo   si_rm_oldisr;     /* original real mode key IRQ */
   _go32_dpmi_seginfo   si_rm_isr;      /* real mode interrupt segment info
*/
   _go32_dpmi_registers rm_regs;
public:
   DJGPP_INTERRUPT()  {init();};
   ~DJGPP_INTERRUPT() {if(set) uninstall();};
   int  install_irq(int irq_no,unsigned long pm_isr,unsigned long rm_isr=0);
   int  install_vec(int vec_no,unsigned long pm_isr,unsigned long rm_isr=0);
   void uninstall();
   void init();
   int  daj_set() {return set;};
};

#include <stdio.h>
#include <string.h>
#include <dos.h>
#include <bios.h>
#include <unistd.h>
#include <sys/farptr.h>
#include <dpmi.h>
#include <go32.h>
#include <pc.h>
#include <io.h>
#include <crt0.h>
#include <sys/nearptr.h>
#include "kp_seria.h"

void DJGPP_INTERRUPT::init()
{
  set = irq_no = ino = 0;
  memset(&si_pm_oldisr, 0, sizeof(si_pm_oldisr));
  memset(&si_pm_isr   , 0, sizeof(si_pm_isr));
  memset(&si_rm_oldisr, 0, sizeof(si_rm_oldisr));
  memset(&si_rm_isr,    0, sizeof(si_rm_isr));
  memset(&rm_regs,      0, sizeof(rm_regs));
}
int DJGPP_INTERRUPT::install_irq(int irq_no,unsigned long isr,unsigned long
rm_isr)
{
   return install_vec(_go32_info_block.master_interrupt_controller_base +
irq_no,
               isr,rm_isr);
}
int DJGPP_INTERRUPT::install_vec(int vec_no,unsigned long isr,unsigned long
rm_isr)
{
   if(set) uninstall();
   init();
   ino = vec_no;

   /* get and allocate pm handler */
   _go32_dpmi_get_protected_mode_interrupt_vector(ino, &si_pm_oldisr);
   si_pm_isr.pm_selector = _go32_my_cs();
   si_pm_isr.pm_offset   = isr;
   if (_go32_dpmi_allocate_iret_wrapper(&si_pm_isr) != 0)
        goto error;

#ifdef PM_11_01_2002
#else
     rm_isr = 0;
#endif
   /* get and allocate rm handler */
   if (rm_isr)
   {
        _go32_dpmi_get_real_mode_interrupt_vector(ino, &si_rm_oldisr);
        si_rm_isr.pm_selector = _go32_my_cs();
        si_rm_isr.pm_offset   = rm_isr;
        if (_go32_dpmi_allocate_real_mode_callback_iret(&si_rm_isr,
&rm_regs) != 0)
                goto error;
   }
   /* set pm handler */
   if (_go32_dpmi_set_protected_mode_interrupt_vector(ino, &si_pm_isr) != 0)
        goto error;

   /* set rm handler */
   if (rm_isr)
        _go32_dpmi_set_real_mode_interrupt_vector(ino, &si_rm_isr);

   set = 1;
   return 0;

error:
   init();
   return -1;
}

void DJGPP_INTERRUPT::uninstall()
{
    if(!set) return;
    int r = 0;
    int final=1;

    disable();
    if (si_pm_oldisr.pm_selector != 0 || si_pm_oldisr.pm_offset != 0)
        r |= _go32_dpmi_set_protected_mode_interrupt_vector(ino,
&si_pm_oldisr);
    if (si_rm_oldisr.rm_segment != 0  || si_rm_oldisr.rm_offset != 0)
        r |= _go32_dpmi_set_real_mode_interrupt_vector(ino, &si_rm_oldisr);

    if (final && r == 0)
    {
        if (si_pm_isr.pm_selector != 0 || si_pm_isr.pm_offset != 0)
                _go32_dpmi_free_iret_wrapper(&si_pm_isr);
        if (si_rm_isr.size)
                _go32_dpmi_free_real_mode_callback(&si_rm_isr);

        init();
    }
   enable();
}
#define IIR_0   0
#define IIR_2   2
#define IIR_4   4
#define IIR_6   6
#define IIR_0xc 0xc

#define STATUS_COM_PRJ_PRET_BUF 1

#define ZRUS_PRJ_ZN_POCAS_VYSIELANIA

#define CHAR_TIM_TIK_DELAY 4 //cim menej, tym rychl. bude MKP reagovat
                             //ale tym viac bude zamestnavany procesor
                             //MKP a program bude menej pruzny
                             //na pomalsich PC tu treba davat vyssie hodnoty

#define ISR_START static void
#define INCL_ISR_START   void
#define ISR_PAR _go32_dpmi_registers *regs
#define TEST_ISR_INSTALLED  djgpp_isr.daj_set()

static const int HWDRV_BUF_LEN = 256;
typedef unsigned char uchar;
typedef unsigned short uint16;
typedef          short  int16;
typedef unsigned long  uint32;
typedef          long   int32;

uchar *anal_char = 0;


template<typename T>
T min(T t1,T t2) { return t1 < t2 ? t1 : t2;}
template<typename T1,typename T2>
T1 min(T1 t1,T2 t2) { return t1 < t2 ? t1 : t2;}

void hwdrv_init_u(struct HWDRV_COM *uhw_com);
void hwdrv_reset_p(struct HWDRV_COM *uhw_com);

struct HWDRV_COM {
        uchar*  uk_buf_vys,*uk_buf_prj;
        uchar   buf_prj[HWDRV_BUF_LEN];
        uint16 BASE_0, BASE_1, BASE_2, BASE_3, BASE_4, BASE_5, BASE_6;
        volatile uint16  uz_prj,tuz_prj, //tuz_prj -> testovacie ukazovatko
kt. zvysuje testovacia rutina
           uv_prj, d_prj, p_ignoruj_prj;
        volatile uint32  u_vys,d_vys;
        int32   p_vys_zn,  p_prj_zn,p_irq;
        int32   rychl,sk_rychl;
        uint16
p_vys,p_prj,p_mod_ch,p_prj_ch,p_pret,p_chpov,p_ch_r,p_char_tim;
        uint16  p_tim,p_tim_p,p_opak,p_O_K,p_i_err,i_err_id;
#define ST_TIM_NONE     0 //timeout nepouzity
#define ST_TIM_RESP     1 //cakanie na odpoved
#define ST_TIM_CHAR_VYS 2 //cakanie na vyslanie 1 znaku
#define ST_TIM_CHAR_PRJ 3 //cakanie na prijem 1 znaku
#define ST_TIM_ONES     4 //oneskorenie vysielania
        int16   timeout,st_tim;
        int16   timeout_resp_set,timeout_char_set,timeout_vys_char_set;
        int16   IRQ;
        uchar   LSR,LSRE,LSRLE,LCR; //Line  Status/Control Register
        uchar   MSR,MCR;            //Modem Status/Control Register
        uchar   FCR,FLen,FTrig;     //Fifo Control Register, Fifo len
        char    IER,IIR;            //Interrupt Enable/Identification
Register
        char    status;
        DJGPP_INTERRUPT djgpp_isr;
        INCL_ISR_START (*new_handle)(ISR_PAR);
};


static const int POCET_HW_COM = 2;

struct HWDRV_COM hw_com[POCET_HW_COM];

struct HWDRV_COM *hw_com1   = &hw_com[0];
struct HWDRV_COM *hw_com2   = &hw_com[1];

ISR_START HWDRV_COM1_ISR   (ISR_PAR);
ISR_START HWDRV_COM2_ISR   (ISR_PAR);


void hwdrv_enable_irq(int16 irq)
{
  uchar m21=inportb(0x21);
  outportb(0x21,m21 & ((1 << irq) ^ 0xff));
}
void hwdrv_disable_irq(int16 irq)
{
  uchar m21=inportb(0x21);
  outportb(0x21,m21 | (1 << irq));
}
void hwdrv_install_isr(struct HWDRV_COM *uhw_com)
{
  uhw_com->djgpp_isr.install_irq(uhw_com->IRQ,(unsigned
long)uhw_com->new_handle,(unsigned long)uhw_com->new_handle);
  hwdrv_enable_irq(uhw_com->IRQ);
}
void hwdrv_uninstall_isr(struct HWDRV_COM *uhw_com)
{
  hwdrv_disable_irq(uhw_com->IRQ);
  uhw_com->djgpp_isr.uninstall();
}

void hwdrv_posli_tlg(struct HWDRV_COM *hwcom,uchar *buf,int32 len,int16
p_ignoruj_prj)
{
   if(!hwcom) return;
//      outportb(hwcom->BASE_1,0);
//      if(hwcom->IRQ) hwdrv_disable_irq(hwcom->IRQ);
   hwcom->p_ignoruj_prj = p_ignoruj_prj;
   hwcom->u_vys = 0;
   hwcom->d_vys = len;
   hwcom->uv_prj  = hwcom->uz_prj =
   hwcom->tuz_prj = hwcom->d_prj  = 0;
   hwcom->uk_buf_vys = buf;
   hwcom->timeout_vys_char_set = hwcom->timeout_char_set *
min(len,hwcom->FLen);
   hwcom->p_char_tim = 0;
   hwcom->timeout = hwcom->timeout_char_set * min(len,hwcom->FLen);
   hwcom->st_tim  = ST_TIM_CHAR_VYS;
   hwcom->IER |= 2;
   #ifdef ZRUS_PRJ_ZN_POCAS_VYSIELANIA
     hwcom->IER &= 0xfa; //test - zakazem prijem !!!
   #endif
   outportb(hwcom->BASE_1,hwcom->IER);
}

void hwdrv_init_com(struct HWDRV_COM *hwcom,int16 n_p_tim_p)
{
//rychlost je viazana na rychlost hodin v uart, st. je to 1843200 Hz,
//z tohto kmitoctu sa vypocita deliaci pomer pre zodpovedajucu rychlost,
//kedze pre vyslanie 1 bitu je potrebnych 16 zakmitov => 16 * 115200 =
1843200
//(ak je vyssia rychlost hodin, je mozne dosiahnut vyssej prenosovej
rychlosti)
   unsigned int i;
   uint16 uart_delicka;
   uchar b_l,b_h;
   uchar FTrig[4]={1,4,8,14};

   if(hwcom->st_tim == ST_TIM_RESP)
    {
        hwcom->p_tim_p++;

        if(n_p_tim_p && ((hwcom->p_tim_p % n_p_tim_p)!=0))
        goto END_INIT_COM; //bude sa inicializovat az pri n-tom timeoute
prijmu
     }

    hwcom->sk_rychl = hwcom->rychl;

   if(hwcom->sk_rychl<2)     hwcom->sk_rychl = 50;
   else
   if(hwcom->sk_rychl>115200L) hwcom->sk_rychl = 115200L;
   uart_delicka = (int16) (115200L / hwcom->sk_rychl);
   b_l = (uchar) 0xFF &   uart_delicka;
   b_h = (uchar) 0xFF & ( uart_delicka >> 8 );
   hwcom->sk_rychl = 115200L / uart_delicka;

   hwcom->MCR = 0;
   outportb(hwcom->BASE_4,hwcom->MCR = 0);
   outportb(hwcom->BASE_1,hwcom->IER = 0);  // zakaz vsetkych preruseni
   for(i=0xFFFFU;i;i--)
    {
     hwcom->LSR = inportb(hwcom->BASE_5);
     if(hwcom->LSR & 0x1f) inportb(hwcom->BASE_0); else break;
    }
   inportb(hwcom->BASE_6);
   outportb(hwcom->BASE_4,3);
   outportb(hwcom->BASE_3,0x80);   // inicializace prenosove rychlosti
   outportb(hwcom->BASE_0,b_l);
   outportb(hwcom->BASE_1,b_h);
   outportb(hwcom->BASE_3,hwcom->LCR);
   outportb(hwcom->BASE_4,hwcom->MCR = 10); //treba nastavovat druhy
bit(RTS),
                                         //aby na kabloch, kde su prepojene
                                         //RTS -> CTS fungovala komunikacia
!!
   outportb(hwcom->BASE_2,hwcom->FCR); //0xc7=fifo
   hwcom->FLen = ((inportb(hwcom->BASE_2) & 0xC0)==0xC0) &&
   hwcom->FCR ? 16 : 1;
   hwcom->FTrig = FTrig[hwcom->FCR>>6];

END_INIT_COM:
   hwcom->timeout_char_set = (int16)((18.2 * 11) / hwcom->rychl) +
CHAR_TIM_TIK_DELAY;
   hwcom->timeout  = 0;
   hwcom->st_tim = ST_TIM_NONE;
   hwcom->status = 0;
}

void hwdrv_base_init_com(struct HWDRV_COM *uhw_com,int16 BASE,
     int32 rychlost,uchar LCR,uchar FCR,int16 timeout_resp_set,
     int16 IRQ)
{
        hwdrv_init_u(uhw_com);
        hwdrv_reset_p(uhw_com);

        uhw_com->IRQ               = IRQ;
        uhw_com->BASE_0            = BASE;
        uhw_com->BASE_1            = BASE+1;
        uhw_com->BASE_2            = BASE+2;
        uhw_com->BASE_3            = BASE+3;
        uhw_com->BASE_4            = BASE+4;
        uhw_com->BASE_5            = BASE+5;
        uhw_com->BASE_6            = BASE+6;
        uhw_com->rychl             = rychlost;
        uhw_com->LCR               = LCR;
        uhw_com->FCR               = FCR;
        uhw_com->timeout_resp_set  = timeout_resp_set;
        uhw_com->uk_buf_prj        = uhw_com->buf_prj;

        hwdrv_init_com(uhw_com,0);
}

struct HWDRV_COM *hwdrv_instaluj_COM_handle(int16 BASE,int16 IRQ,uchar
LCR,int32 rychlost)
{
struct HWDRV_COM *uhw_com;

  if(hw_com1->IRQ==0)
   {
     uhw_com = hw_com1;
     uhw_com->new_handle = HWDRV_COM1_ISR;
   }
  else
  if(hw_com2->IRQ==0)
   {
     uhw_com = hw_com2;
     uhw_com->new_handle = HWDRV_COM2_ISR;
    }
   else
     return 0;
   hwdrv_base_init_com(uhw_com,BASE,rychlost,LCR,0,2000,IRQ);
   hwdrv_install_isr(uhw_com);
   return uhw_com;
}

void hwdrv_odinstaluj_COM_handle(struct HWDRV_COM *uhw_com)
{
   if((uhw_com==NULL) ||
      (uhw_com->IRQ<=0) ||
       (!uhw_com->TEST_ISR_INSTALLED))
       return;

   outportb(uhw_com->BASE_1,uhw_com->IER = 0);
   hwdrv_uninstall_isr(uhw_com);
}
void fifo_com_isr(struct HWDRV_COM *uhw_com)
{
static int p;
static uchar fl;
static unsigned in;
  uhw_com->p_irq++;
  p=1000;
  while(1)
  {
    p--;if(!p) {uhw_com->p_i_err++;uhw_com->i_err_id = 4;break;}
    fl = 0xf & inportb(uhw_com->BASE_2);
//      uhw_com->IIR = fl;
    if(fl & 1) break;
    uhw_com->IIR = fl;
    switch (fl)
    {
     case IIR_0:uhw_com->p_mod_ch++;inportb(uhw_com->BASE_6);break;
     case IIR_2:if((uhw_com->IER & 2) == 0)
                 {outportb(uhw_com->BASE_1,uhw_com->IER);break;}
                  uhw_com->LSR = inportb(uhw_com->BASE_5);
                  if(uhw_com->LSR & 0x20)  //vyst. reg. prazdny
                   { static int i;
                     uhw_com->timeout = 0;
                     for(i=0;i<uhw_com->FLen;i++)
                     {
                      if(uhw_com->u_vys < uhw_com->d_vys)
                       {

outportb(uhw_com->BASE_0,uhw_com->uk_buf_vys[uhw_com->u_vys]);
                        uhw_com->u_vys++;
                       }
                      else
                      {
                        uhw_com->IER &= 0xfd;  //zakaz vysielania
                        #ifdef ZRUS_PRJ_ZN_POCAS_VYSIELANIA
                         while(inportb(uhw_com->BASE_5) & 1)
                           inportb(uhw_com->BASE_0);
                          uhw_com->IER |= 5;     //povolenie prerusenia od
prijmu
                        #endif
                        outportb(uhw_com->BASE_1,uhw_com->IER);
                        uhw_com->p_vys++;
                        uhw_com->st_tim  = ST_TIM_RESP;
                        uhw_com->timeout = uhw_com->timeout_resp_set;
                        break;
                      }
                     }
                     uhw_com->timeout  += uhw_com->timeout_char_set * i;
                     uhw_com->p_vys_zn += i;
                    }
                  break;
                  case IIR_0xc:
                  case IIR_4:
                   while(1)
                    {
                      p--;if(!p) {uhw_com->p_i_err++;uhw_com->i_err_id =
5;break;}
                      uhw_com->LSR = inportb(uhw_com->BASE_5);
                      if(!(uhw_com->LSR & 1)) break;//pripr. prj. dat
                      if(!(uhw_com->IER & 1) || !uhw_com->uk_buf_prj)
                      {
                       inportb(uhw_com->BASE_0);
                       outportb(uhw_com->BASE_1,uhw_com->IER &= ~1);
                       break;
                      }
                      in = uhw_com->uz_prj;
                      uhw_com->uk_buf_prj[in] = inportb(uhw_com->BASE_0);
                      if(uhw_com->p_ignoruj_prj)
                        {uhw_com->p_ignoruj_prj--;break;}
                      in++;
                      uhw_com->p_prj_zn++;
                      uhw_com->d_prj++;
                      if(in>=HWDRV_BUF_LEN)     in = 0;
                      uhw_com->uz_prj = in;
                      if(in == uhw_com->uv_prj)
                      uhw_com->status |= STATUS_COM_PRJ_PRET_BUF;
                      uhw_com->st_tim  = ST_TIM_CHAR_PRJ;
                      uhw_com->timeout =
uhw_com->timeout_char_set*uhw_com->FTrig;
                    }
                  break;
                  case IIR_6:
                   while(1)
                    {
                     p--;//if(!p) {uhw_com->p_i_err++;uhw_com->i_err_id =
6;break;}
                     in = inportb(uhw_com->BASE_5);
                     uhw_com->LSR = in;
                     if(in & 1) inportb(uhw_com->BASE_0);
                     if(in & 0x1E) //chyba prijmu
                     {
                       uhw_com->LSRE = uhw_com->LSRLE = in;
                       uhw_com->p_prj_ch++;
                     }
                     else
                       break;
                          //in = inportb(uhw_com->BASE_5);
                          //if(in & 1) inportb(uhw_com->BASE_0);
                          //if(!(in & 0x1E)) break; //ziadna chyba
                          //uhw_com->LSR = uhw_com->LSRE = uhw_com->LSRLE =
in;
                          //uhw_com->p_prj_ch++;
                    }
                  break;
                 }
          }
}

ISR_START HWDRV_COM1_ISR(ISR_PAR)
{
static int p = 0;
  *anal_char = 'a';
  p++;
  anal_char[2]  = (p / 100000) % 10 + '0';
  anal_char[4]  = (p /  10000) % 10 + '0';
  anal_char[6]  = (p /   1000) % 10 + '0';
  anal_char[8]  = (p /    100) % 10 + '0';
  anal_char[10] = (p /     10) % 10 + '0';
  anal_char[12] =            p % 10 + '0';
  fifo_com_isr(hw_com1);
  outportb(0x20,0x20);
  *anal_char = 'A';
}

ISR_START HWDRV_COM2_ISR(ISR_PAR)
{
  anal_char[4] = 'b';
  if((anal_char[6]<'0') || (anal_char[6]>'8'))
    anal_char[6] = '0';
  else
    anal_char[6]++;
  fifo_com_isr(hw_com2);
  outportb(0x20,0x20);
  anal_char[4] = 'B';
}
void hwdrv_init_u(struct HWDRV_COM *uhw_com)
{
  if(!uhw_com) return;
  uhw_com->u_vys = uhw_com->uz_prj = uhw_com->uv_prj = 0;
  uhw_com->d_vys = 0;
  uhw_com->d_prj = 0;
}
void hwdrv_reset_p(struct HWDRV_COM *uhw_com)
{
  if(!uhw_com) return;
  uhw_com->p_vys_zn = uhw_com->p_prj_zn = uhw_com->p_irq    = 0;
  uhw_com->p_vys    = uhw_com->p_prj    = uhw_com->p_mod_ch =
  uhw_com->p_prj_ch = uhw_com->p_chpov  = uhw_com->p_pret   =
  uhw_com->p_tim    = uhw_com->p_tim_p  = uhw_com->p_opak   =
  uhw_com->p_O_K    = uhw_com->p_i_err  = uhw_com->i_err_id = 0;
  uhw_com->LSRLE    = 0;
  uhw_com->p_ch_r   = 0;
}

int _crt0_startup_flags = _CRT0_FLAG_LOCK_MEMORY;

int main()
{
  if ( !(_crt0_startup_flags & _CRT0_FLAG_NEARPTR) )
    __djgpp_nearptr_enable ();
  if ( _crt0_startup_flags & _CRT0_FLAG_NEARPTR)
   {
      anal_char = (uchar *)(0xb8000 + __djgpp_conventional_base);
      *anal_char = 's';
   }
  struct HWDRV_COM *uhw_com = hwdrv_instaluj_COM_handle(0x3F8,4,0x1B,50);

  if(!uhw_com) return -1;

  #define VYS_LEN 100
  uchar p[VYS_LEN];
  for(int i=0;i<VYS_LEN;i++) p[i] = i;

  //hwdrv_posli_tlg(uhw_com,p,VYS_LEN,0);

  int fi = _creat("test.txt",_A_ARCH);
  int pp = 0;

  hwdrv_posli_tlg(uhw_com,p,8,0);

  for(int i = 0; i< 100;)
   {
     pp++;
     _write(fi,p,16);
     if(uhw_com->u_vys == 8)
      {
        hwdrv_posli_tlg(uhw_com,p,8,0);
        delay(10);
        i++;
      }
     _write(fi,p,16);
   }

  printf("pp = %d\n",pp);

   _close(fi);

  hwdrv_odinstaluj_COM_handle(uhw_com);

  return 0;
}



"Charles Sandmann" <sandmann AT clio DOT rice DOT edu> píše v diskusním příspěvku
news:3c479438 DOT sandmann AT clio DOT rice DOT edu...
> > I have program, which runs correctly under win95 dpmi server,
> > but under clear dos v 7.0 with csdpmi5 server has strange behaviour:
>
> If you get a page fault you may have one of two problems:
> 1) null pointers (not caught with W95)
> 2) unlocked memory
>
> Posting the entire error message would be helpful.  You need to als make
> sure you are not using any "extended" interrupts which expect
> pointers to be automatically handled.
>
> > I am looking for some tips as I have tried several test without success.
> > I even created a small version of this program, but it works without any
> > problems also in DOS.
>
> Keep adding features till you find what's broken.


- Raw text -


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