[comp.lang.pascal] Interrupts on PS/2 Model 50

fryman@webb.psych.ufl.edu (Josh Fryman) (01/05/91)

Greetings. I find that I am in... a rather interesting situation; one which
is causing me premature hair loss, actually. Since I'm having this problem
which is so baffling, it was recommended that I post a message asking any
gurus to see if they might lend me a hand. I seem to be having a bit of a
nasty problem with _some_ interrupts but _not_ others.

I'm writing a psychology experiment program for the professors here at UF, and
I need to do several things. Most of the special features are, naturally,
based on interrupts, and this merely compounds my problem. I'm programming
for a plain vanilla PS/2 50 (w/ mouse). I'm also using TP 5.0.

I'm _trying_ to get the alarm features of the 286 to work, something they're
not very keen on. Also, I am trying to install my own mouse handler (via the
option in interrupt 33h, function 0Ch). The only thing I can notice it doing
is: nothing. It doesn't respond. I check the values I pass into the the alarm
"setter" procedure, and they check as fine. But, the program apparently would
rather ignore anything that does seem to happen. The alarm features are listed
below. I would MOST appreciate someone pointing out what glaring error I have
made. This not the real code (ok, quite far from it), but a real watered down
all-in-one program to just set the alarm and be done with it. I use the same
(similar) code in my program, and it's beyond me as to what the problem is.



program test;

uses dos, crt;

var hour, min, sec, sec100: word;
    regs: registers;
    beeper: pointer;

procedure alarm; interrupt;

begin
  sound(250);
  delay(250);
  nosound;
end;

begin
  clrscr;
  getintvec($4a, beeper);
  setintvec($4a, @alarm);
    fillchar(regs,sizeof(regs),0);
    gettime(hour,min,sec,sec100);
    with regs do
      begin
        ah := $06;
        ch := hour;
        cl := min + 1;
        dl := sec;
        Intr($1a, regs);
      end;
  setintvec($4a, beeper);
  repeat
  until keypressed;
end.

And yes, I am aware that if I run this at the wrong time, it will not function
because I didn't program it (here) to do a minute roll-over. The real program
has these safety features.

As I mentioned before, the other problem I have is the mouse event handler.
The code I wrote works great on my XT (gag) at home (with a LogiTech mouse
that responds fine to Intr 33h functions). It goes something like this:

Force far calls on (since it's an event handler); declare the handler as an
interrupt so TP will save the necessary DS registers so I can access my
globals; read ALL CPU registers and STORE them in a local array; perform
functions; move CPU registers BACK onto the stack; exit.

The problem here is a little more straightforward. It wedges the system. As in
power off and power back on. But that's NOT all. If I do this, in conjunction
with my alarm interrupts, my keyboard interrupt (which works fine), and my
time interrupts, it's nice enough to reset the computer for me. As in RESET.
As in go back to square zero, do the tedious memory check, etc, etc. Neat!
I would list the code, but it's rather long. As I recall, some people have
been asking around for a mouse unit in TP. I guess I'm willing to resign
on this and use the same thing if someone can point out where I can grab one.

This big questions are: these things work fine on my machine (well, I can't
try the alarm features since it's an XT), so are the interrupts different on
a PS/2? If not, what IS the problem?

I use similar convention for polling the interrupts (specifically, the
time interrupts; replacing the system timer and monitoring the real time
clock) and these features work great. Why do these work, but not others?

My reference (one of the better ones I have) for the interrupts came from
"PC System Programming" by Abacus books.

thanks for any information that can be offered and for reading this far,

                                    Josh

richardw@uunet.uu.net (01/05/91)

Well, a few comments.  One:  you shouldn't try to do that much in an
interrupt.  The very nature of the interrupt is to process something as fast
as possible, then return control to foreground process.  You are defining an
interrupt that will execute with interrupts masked off for over 0.25 seconds.
Your alarm function should just post a note somewhere that indicates that
time is up, and your keyboard polling routine should check this value and
perform the appropriate tasks.

I'm also including a mouse unit which I have sent out on occasion.  You can
use the function that handles mouse events as a model for your code.  It was
developed on a ps/2 model 50, so you should have no problems.

None of this is endorsed by Microsoft Corp.

-Richard Ward
uunet!microsoft!richardw
microsoft!richardw@uunet.uu.net
The above has no relation to Microsoft corporate policy.

--------------8<-------------------8<------------------8<-------------------

Unit Mouser;

{  This is a base-level unit, i.e. it needs no units other than default
   TP units.  This code is not owned by any company that I work for, or
   have worked for in the past, and therefore no liability for this code
   can be assumed by any company I have been associated with.  This code
   is not copyright, and can be freely distributed, or modified without
   risk of penalty.  No guarantees, express or implied, about this code
   are provided, and the author takes no responsibilities for any use of
   this code.  Light fuse and get away.

   Richard Ward
   rbw@cs.williams.edu                  - old address, may or may not work
   microsoft!richardw@uunet.uu.net      - current address
}

interface
 uses crt,dos;

 const
       Mouse_EventMove       =  1;   { Mouse moved }
       Mouse_EventLeftDown   =  2;   { left button down }
       Mouse_EventLeftUp     =  4;   { left button up }
       Mouse_EventRightDown  =  8;   { right button down }
       Mouse_EventRightUp    = 16;   { right button up }
       Mouse_EventMiddleDown = 32;   { middle button down }
       Mouse_EventMiddleUp   = 64;   { middle button up }

       MouseWaitNoBlock = 0;    { Wait: return immediately }
       MouseWaitBlock   = 1;    { Wait: wait until mouse event }

       MouseDefaultMask = #$77#$ff;    { Default cursor MASK }
       MouseDefaultCursor = #$77#$0;   { Default cursor CURSOR }

 Type
    Mouse_CursorType = string[2];
 Var
    MouseExist : boolean;

Function  Mouse_Reset : boolean;   { Resets the mouse and driver }
Function  Mouse_Test : boolean;    { soft reset of driver }

{ Controlling the mouse }
Procedure Mouse_SetCursor(mask,cursor : Mouse_CursorType);
Procedure Mouse_Cursor(b : boolean);
Procedure Mouse_GetXYB(var x, y, b : word);
Procedure Mouse_SetXY(x, y : word);

{ Event management }
procedure Mouse_EnableEvents(driver:pointer; eventreport:word);
procedure Mouse_DisableEvents;
Function  Mouse_TestEvent(block:byte):boolean;
Procedure Mouse_GetEvent(var etype,bstatus,curx,cury,shifts:word);
Procedure Mouse_StartEvents(emask:word);
Function  Mouse_CountEvents:word;
Procedure Mouse_ClearEventQ;

 implementation

  Const
      Mouse_Intr = $33;

  Type
       IntrnEventRec = record
          eventtype  : word;
          buttons    : word;
          shiftstate : word;
          x,y        : word;
         end;

  Var Mouse_EventQueue : array[0..63] of IntrnEventRec;
      Mouse_EventHead,
      Mouse_EventTail  : word;           { Event Queue pointers }
      CallCount   : longint;             { Count of mouse events }
      MouseExists : boolean;             { Internal flag }
      MouseButton : byte;                { number of buttons on mouse }
      Mouse_Exitsave : pointer;          { pointer to exit procedure }

{  Mouse_Reset performs a hard reset on the mouse driver.  This can take a
   few seconds.  This generally doesn't need to be called, but old mouse
   drivers may not support function 21h
}
 Function Mouse_Reset:boolean;
  var rec:Registers;
  begin
   rec.ax := 0;
   intr(Mouse_Intr, rec);
   MouseExists := rec.ax <> 0;    { Determine if mouse is present }
   MouseButton := lo(rec.bx);     { number of buttons returned in BL }
   Mouse_Reset := MouseExists;    { Return value }
   MouseExist  := MouseExists;    { Set external mouse-present flag }
  end;

{  Mouse_Test performs a software reset on the mouse driver.  This is much
   faster than Mouse_Reset.
}
 Function Mouse_Test : boolean;
  var rec:Registers;
  begin
   rec.ax := $21;                 { software reset }
   intr(Mouse_Intr, rec);
   MouseExists := rec.ax = $ffff; { Determine if mouse is present }
   MouseButton := lo(rec.bx);     { number of buttons in BL }
   Mouse_Test  := MouseExists;    { Return value }
   MouseExist  := MouseExists;    { Set external mouse-present flag }
  end;

{  Mouse_SetCursor determines the format of the mouse cursor on the screen.
   The driver will logically AND the current mouse position character with
   the value defined in mask, then XOR the value in cursor.  Mouse_CursorType
   is a string of length 2, where character 1 is the attribute of the screen
   character, and character 2 is the character itself. Generally speaking,
   you should use the defaults provided, though you can certainly experiment.
}
 procedure Mouse_SetCursor(mask, cursor: Mouse_CursorType);
  var rec:registers;
  begin
   if not MouseExists then exit;
   rec.ax := 10;
   rec.bx := 0;
   rec.cx := ord(mask[1]) shl 8 + ord(mask[2]);
   rec.dx := ord(cursor[1]) shl 8 + ord(mask[2]);
   intr(Mouse_Intr,rec);
  end;

{  Mouse_Cursor controls whether the mouse cursor is displayed or not.  The
   calls are cumulative, meaning if you call Mouse_Cursor(false) four times,
   you must call Mouse_Cursor(true) four times before the cursor will show.
   The mouse always starts with the cursor off.
}
 procedure Mouse_Cursor(b:boolean);
  var rec:registers;
  begin
   if not MouseExists then exit;
   if b then
     rec.ax := 1
    else
     rec.ax := 2;
   intr(Mouse_Intr,rec);
  end;

{  Mouse_GetXYB gets the current x,y position and the state of the buttons.
}
 procedure Mouse_GetXYB(var x, y, b : word);
  var rec:registers;
  begin
   if not MouseExists then exit;
   rec.ax := 3;
   intr(Mouse_Intr,rec);
   x := rec.cx div 8 + 1;     { Mouse coordinates are in pixels }
   y := rec.dx div 8 + 1;     { this translates to TP cursor coordinates }
   b := rec.bx;
  end;


{  Mouse_SetXY sets the current x,y position, based on TP cursor coordinates.
}
 Procedure Mouse_SetXY(x, y : word);
  var rec:registers;
  begin
   rec.ax := 4;
   rec.cx := (x - 1) shl 3;   { convert into pixels, short hand for * 8 }
   rec.dx := (y - 1) shl 3;
   intr(Mouse_Intr, rec);
  end;

{  Mouse_EnableEvents allows you to set up your own event handler for the
   mouse driver.  Before using this call, be sure you know what is required.
}
 procedure Mouse_EnableEvents(driver:pointer; eventreport:word);
  var rec:registers;
  begin
   if not MouseExists then exit;
   rec.ax := 12;
   rec.cx := eventreport;
   rec.dx := ofs(driver^);
   rec.es := seg(driver^);
   intr(Mouse_Intr,rec);
  end;

{  Mouse_DisableEvents stops the mouse driver from calling the driver you set
   up with Mouse_EnableEvents.
}
 procedure Mouse_DisableEvents;
  var rec:registers;
  begin
   if not MouseExists then exit;
   rec.ax := 12;
   rec.cx := 0;
   rec.dx := 0;
   rec.es := 0;
   intr(Mouse_Intr,rec);
  end;

 {$F+}

{  MouseDD is the event handler that I wrote for the mouse.  It stores the
   mouse event in the Mouse_EventQueue array, and returns to the driver.
   It is set up as an interrupt handler, because the mouse driver passes
   the state of the mouse in the registers.
}
 procedure MouseDD(flags,cs,ip,ax,bx,cx,dx,si,di,ds,es,bp:word);
  interrupt;
  begin
   Mouse_EventTail := (Mouse_EventTail + 1) mod 64;
   with Mouse_EventQueue[Mouse_EventTail] do
    begin
      eventtype := ax;
      buttons := bx;
      shiftstate := memW[$40:$17]; { Gets the current state of the shift keys }
      x := cx;
      y := dx;
    end;
   inc(CallCount);
   inline( $8B/$E5/   { Even though we were called as an interrupt, we can't }
           $5D/       { exit as one, because TP inserts an IRET when we want }
           $07/       { to do a RETF.  So this mass of inline pops all the }
           $1F/       { registers off the stack, then does a FAR return }
           $5F/
           $5E/
           $5A/
           $59/
           $5B/
           $58/$CB);
  end;

{  Mouse_TestEvent allows you to query if there are any pending mouse events.
   If you specify the NoBlock option (see constants), TestEvent will return
   immediately.  If you specify Block, then it will wait until something does
   happen.
}
 Function Mouse_TestEvent(block : byte) : boolean;
  begin
   if not MouseExists then exit;
   if block = MouseWaitNoBlock then
    begin
     Mouse_TestEvent:=Mouse_EventHead <> Mouse_EventTail;
     exit;
    end;
   while Mouse_EventHead = Mouse_EventTail do;   { spin until an event }
   Mouse_TestEvent := true;
  end;

{  Mouse_GetEvent will copy the pending event into the variables specified.
   If there is no pending event, etype is set to 0.
}
 Procedure Mouse_GetEvent(var etype,bstatus,curx,cury,shifts:word);
  begin
   if not MouseExists then exit;
   if Mouse_EventHead = Mouse_EventTail then
    begin
     etype := 0;
     exit;
    end;
   Mouse_Eventhead:=(Mouse_Eventhead + 1) mod 64;
   with Mouse_EventQueue[Mouse_EventHead] do
    begin
     etype    := eventtype;
     bstatus  := buttons;
     shifts   := shiftstate;
     curx     := x div 8 + 1;
     cury     := y div 8 + 1;
    end;
  end;

{  Mouse_CountEvents returns the number of pending events
}
 Function Mouse_CountEvents : word;
  begin
   if not MouseExists then
     Mouse_CountEvents := 0
    else
     Mouse_CountEvents := (Mouse_EventTail - Mouse_EventHead + 64 ) mod 64;
  end;

{  Mouse_ClearEventQ discards all pending events.
}
 Procedure Mouse_ClearEventQ;
  begin
   Mouse_EventTail := Mouse_EventHead;
  end;

{  Mouse_StartEvents starts the event queue stuff.  emask is the sum of
   all events that you want to watch for, so if you want to collect all
   mouse clicks, emask := Mouse_EventRightDown + Mouse_EventLeftDown;
}
 Procedure Mouse_StartEvents(emask : word);
  begin
   if not MouseExists then exit;

   Mouse_EnableEvents(@MouseDD,emask and $1F);
  end;

{  This is the exit procedure for the unit, which turns off any mouse
   event drivers.
}
 Procedure Mouse_ExitProc;
  begin
   ExitProc := Mouse_exitsave;
   Mouse_DisableEvents;
  end;

{  Unit startup code.  ExitProc is installed, mouse is soft reset.
}
 begin
  Mouse_EventHead := 0;
  Mouse_EventTail := 0;
  CallCount := 0;
  Mouse_exitsave := ExitProc;
  ExitProc := @Mouse_ExitProc;
  MouseExists := Mouse_Test;
 end.

fryman@webb.psych.ufl.edu (Josh Fryman) (01/08/91)

Richard wrote:

>Well, a few comments.  One:  you shouldn't try to do that much in an
>interrupt.  The very nature of the interrupt is to process something as fast
>as possible, then return control to foreground process.  You are defining an
>interrupt that will execute with interrupts masked off for over 0.25 seconds.
>Your alarm function should just post a note somewhere that indicates that
>time is up, and your keyboard polling routine should check this value and
>perform the appropriate tasks.

A good observation. I appreciate that. (Hadn't really thought about it that
way...) I guess it was my fault for not being a little more clear in my first
message. The problem isn't whether it's too long or not, the problem is:

It just doesn't work.

I can not (for the life of me) get the alarm interrupt (4Ah) to respond. It
just doesn't seem to work with the PS/2, but a PS/2 specific book clearly
states that there are NO differences between the AT and PS/2 interrupts here.

Perhaps I am setting it wrong, but I don't see how that's possible. I use
function 6 of interrupt 1Ah and set it accordingly. I've even reset the time
to 0:00:00.00, and programmed the alarm time for 0:00:15.00 by using hex or
decimal. Still, it just will not seem to "go off". Ideas? Opinions?

P.S.   Thanks for the mouse unit. Now I just need to incorporate it into
       the program.

                                            Josh
                                            (Confused...)