;
;  Program to demonstrate how interrupt driven I/O drivers work on the
;  IBM PC. This program is a simple keyboard handler which deals with
;  the ASCII graphic keys and handles the shift keys properly. The
;  resulting characters are placed on the screen. The program is
;  terminated by pressing the Del key, which causes a reboot.
;
;    Written by Robert B. K. Dewar, 1983
;
;  Here is where control comes when we are first loaded. The first step is
;  to connect the interrupt system to our interrupt handler (the name given
;  to a routine which handles an interrupt). There are two things we need
;  to do, the first is to make sure that the keyboard can signal interrupts,
;  the second is to make sure that they go to our routine. Clearly we should
;  do the second step first, because if we let interrupts occur before we are
;  ready to handle them, we will be in trouble.
;
;  When an interrupt occurs, the effect is to immediately push the current
;  flags and location (code segment, instruction pointer) onto the stack,
;  and then reset the location using a special memory location. For each
;  different interrupt in the system, there is a different location which
;  is assigned. For the keyboard, interrupts use "Interrupt Request One"
;  (IRQ1), which is interrupt number 9 whose special memory location is
;  at 00024h-00027h. This means that we must place the address of our
;  interrupt handler in this location. Before doing so we execute a CLI
;  (clear interrupt) instruction which stops any interrupts in the system
;  from occuring, since we do not want interrupts to occur till we are
;  all set up. We will turn interrupts back on when we are ready for them.

	  .model small

;-------------------------------------------------------------------------------
	  .code
start:	 mov   ax,@data
	 mov   es,ax	     ;set ES to data segment
	 mov   cs:dseg,ax    ;save segment address of data

begin:	 cli		     ;interrupts off
	 mov   ax,0000h      ;set DS to address segment 0000H
	 mov   ds,ax
	 mov   bx,0024h      ;load address
	 mov   word ptr [bx],offset kin   ;set offset of int handler in 00024h
	 mov   [bx+2],cs     ;set segment of interrupt handler in 0026h
	 mov   ax,es	     ;restore DS to point to our data segment
	 mov   ds,ax
;
;  Now we must allow the keyboard to signal interrupts so our interrupt
;  handler will get control when a key is pressed. There are two steps
;  which are necessary. First we must tell the interrupt system that we
;  are prepared to handle keyboard interrupts. This is often called
;  "unmasking" an interrupt. If an interrupt is "masked", then it will
;  never be recognized. The program controls the masking and unmasking
;  of interrupts using the PIC (Program Interrupt Control) register
;  which is accessible via I/O port 21h. Each interrupt has an assigned
;  bit in this register (which is often referred to as the mask register).
;  If the bit is on, the corresponding interrupt is masked. If the bit is
;  off, then the interrupt is unmasked. Our keyboard interrupt (IRQ1) is
;  assigned bit position in the PIC, so here we go.
;
	 in    al,21h	     ;read current PIC contents
	 and   al,11111101b  ;turn off (unmask) IRQ1, leave others alone
	 out   21h,al	     ;rewrite modified PIC contents
;
;  Finally, we must execute an STI instruction (set interrupts). The CLI
;  and STI instructions control the setting of the IF (interrupt flag).
;  If IF is turned off (by CLI), then no interrupts can occur, regardless
;  of the contents of the PIC. If IF is turned on (by STI), then those
;  interrupts which are unmasked (corresponding bits in the PIC off) will
;  be signalled when they occur.
;
	 sti		     ;allow interrupts again
;
;  Now we can start the program, first we will blank the screen
;
blank:	 mov   dx,0	     ;cursor is homed
	 mov   cx,2000	     ;25 lines of 80 chars
	 mov   bl,07h	     ;grey on black
         mov   al,' '        ;blank
	 call  paint	     ;blank screen
;
;  Loop to display keyboard data
;
dloop:	 call  kread	     ;read next key
	 mov   cx,1	     ;paint one char
	 mov   bl,07h	     ;grey on black
	 call  paint
	 inc   dx	     ;bump cursor
	 cmp   dl,80	     ;loop if not end of line
	 jb    dloop
	 mov   dl,0	     ;else reset char 0
	 inc   dh	     ;bump line
	 cmp   dh,25	     ;loop if not end of screen
	 jb    dloop
	 jmp   blank	     ;else reinitialize screen

;
;  Here is the background routine for reading keyboard data. The term
;  background refers to the main part of the program, apart from the
;  interrupt handlers, which runs whenever there is no interrupt routine
;  currently active. The interrupt routines are collectively referred
;  to as foreground routines. The keyboard interrupt routine (foreground)
;  puts data into the keyboard ring buffer. This background routine takes
;  data out of the ring buffer.
;
;	 call  kread	     ;call to obtain data character
;	 (al)		     ;ASCII data character obtained
;
;  If no data is available, then kread waits until data becomes available
;
;  Note: in the standard IBM BIOS routines, the function of kread is performed
;  by the "keyboard interrupt" routine accessed by int 16h. This presents some
;  confusion, which arises because of a unique feature on the Intel 8088 chip.
;
;  In addition to the normal methods of signalling an interrupt (keyboard data
;  ready, disk operation finished etc.) it is possible on the 8088 to signal
;  an interrupt using the INT instruction. Really when an interrupt routine
;  is called this way (rather than by a real I/O interrupt), the routine is
;  functioning logically as a normal subroutine. The so called "keyboard
;  interrupt" routine at INT 16h is not really an interrupt routine at all,
;  it is a background subroutine. PC/DOS uses interrupts in this way because
;  it is a convenient, efficient and compact method for providing subroutines
;  for use by the background program.
;
kread	 proc
	 push  bx	     ;save registers
;
;  Loop to wait for data to become available
;
kreadl:  mov   bx,loadp      ;get load pointer
	 cmp   bx,storep     ;any data yet?
	 je    kreadl	     ;loop till data present
;
;  Here we have a data value, get it and increment the load pointer, being
;  sure to wrap it around at the end. Note: a "clever" programmer thinking
;  about efficiency would do the wrap using "and bx,rbufl-1" (think it over!)
;
	 mov   al,rbuf[bx]   ;load data value
	 inc   bx	     ;bump pointer
	 cmp   bx,15	     ;jump if in range
	 jbe   kreadx
	 mov   bx,0	     ;else reset to start of buffer
;
;  Here with character obtained and pointer bumped
;
kreadx:  mov   loadp,bx      ;store updated load pointer
	 pop   bx	     ;restore registers
	 ret		     ;return to caller
kread	 endp

;
;  The following routines are used to access the display. They should be
;  familiar since they are exactly the same routines which we used in the
;  game program. One of the purposes of using procedures is to allow them
;  to be reused in other programs. This way we save the bother of writing
;  them again -- in fact we don't even need to remember how they work!
;
;  Routine to set cursor position
;
;	 (DH,DL)	     Cursor line, column
;	 call  setpos
;
setpos	 proc
	 push  ax	     ;save registers
	 push  bx
	 mov   bh,0	     ;set cursor
	 mov   ah,2
	 int   10h
	 pop   bx	     ;restore registers
	 pop   ax
	 ret		     ;return to caller
setpos	 endp

;
;  Routine to paint characters on screen
;
;	 (DH,DL)	     Line, column of start of area to paint
;	 (CX)		     Number of characters to paint
;	 (BL)		     Required color
;	 (AL)		     Character code
;	 call  paint
;
paint	 proc
	 push  ax	     ;save registers
	 push  bx
	 cmp   cx,0	     ;skip if no chars to paint
	 je    paint1
	 call  setpos	     ;set cursor position
	 mov   bh,0	     ;write the characters
	 mov   ah,9
	 int   10h
paint1:  pop   bx	     ;restore registers
	 pop   ax
	 ret		     ;return to caller
paint	 endp

;
;  Here we have the keyboard interrupt handler itself. Control will be
;  passed here if the following conditions are met:
;
;    1)  The keyboard got some new data
;    2)  Processor interrupts are enabled (IF was set on by STI)
;    3)  The interrupt is unmasked (bit 1 in the PIC is off)
;    4)  No other interrupt is busy at the time
;
;  Note that we do not know what code was interrupted, it may be our background
;  program, or it may be some other piece of operating system code (like the
;  code used to put characters on the screen). The point is that we cannot
;  rely on the segment registers being set right, although at least CS is
;  set correctly, since it was set from location 00026h at interrupt time.
;

dseg	 dw    ?	     ;segment address of data

kin	 proc far
	 push  ds	     ;save segment registers from background
	 push  es
	 push  ax	     ;save other registers from background
	 push  bx
	 push  cx
	 push  dx
	 mov   ds,cs:dseg    ;set DS to point to our data
	 mov   es,cs:dseg    ;set ES to point to our data
;
;  When the interrupt occured, IF was automatically turned off, to stop
;  further interrupts occuring. It may be that there are more important
;  interrupts in the system which we cannot afford to hold up while we
;  process our interrupt, so we intend to turn IF back on so that it is
;  possible for other interrupts to occur. However, the interrupt system
;  has a definite idea of the relative importance of interrupts, called
;  their priority. There are 8 possible interrupts (IRQ0 to IRQ7). IRQ0
;  (the timer interrupt) is considered to be the most important (highest
;  priority), amd IRQ7	is the least important (lowest priority). When
;  we turn IF back on again, the interrupt system will let higher priority
;  interrupts (IRQ0 in this case) interrupt the interrupt routine, but
;  equal or lower priority interrupts will continue to be shut out (in
;  particular, it is good to know that equal priority interrupts are
;  shut out, we would get really confused if we could interrupt ourself!).
;
;  When we are all done processing the interrupt, we will have to indicate
;  the fact to the interrupt system so that the equal and lower priority
;  interrupts can occur again. We will see how this is done later.
;
;  A system which works in this manner is called a priority interrupt
;  system.
;
	 sti		     ;allow higher priority interrupts
;
;  Next step is to obtain the keyboard data (which is in port 60h). After
;  getting the data, the keyboard interface requires us to set the top bit
;  of the keyboard control port (port 61h) for just a moment, to indicate
;  that we got the data OK.
;
	 in    al,60h	     ;get keyboard data
	 mov   bl,al	     ;save in BL
	 in    al,61h	     ;read keyboard control
	 mov   ah,al	     ;save original value
	 or    al,80h	     ;set required bit
	 out   61h,al
	 xchg  al,ah	     ;reset original value
	 out   61h,al
	 mov   al,bl	     ;Keyboard data back to al
;
;  Now we test for an error situation called an overrun. The keyboard interface
;  pays attention to whether we read each key as it is pressed. If it notices
;  that a key is pressed and we have not yet read the previous key, then it
;  knows that a key is going to be lost. It signals this situation by using
;  the code 0ffh (normally an impossible code). This could occur if, for
;  example, the background program left IF turned off for so long that the
;  interrupt routine could not get control. This particular program never
;  makes such a mistake, but an interrupt handler is a general routine
;  which might be used in any program, and it cannot rely on the background
;  program being properly coded!
;
	 cmp   al,0ffh	     ;did we get an overrun?
	 jne   kin0	     ;jump if not
	 jmp   kin8	     ;else go sound alarm
;
;  You might expect at this stage that all we have to do is to store the
;  keyboard data in the ring buffer. Wrong! The data which comes in from
;  the keyboard doesn't look anything like ASCII codes. Instead what we
;  get is an 7 bit value which is the key number, which is unrelated to
;  the ASCII code. When the key is pressed, we get an interrupt with
;  this code. When the key is released, we get another interrupt, using
;  the same code, but with the 80h (upper) bit set on. Normally we will
;  ignore these release codes. The exception is the SHIFT keys.
;
;  We call the key press code a "make" and the key release code a "break"
;
kin0:	 test  al,80h	     ;is the break bit on
	 jz    kin3	     ;jump if not
;
;  Here for a key break. We are only interested in the shift keys. Notice
;  that it is completely arbitrary which keys we assign as shift keys.
;  The keyboard itself has no idea that the shift keys are special.
;
	 and   al,7fh	     ;remove break bit
	 cmp   al,42	     ;is this key 42 (left shift key)
	 je    kin1	     ;jump if so
	 cmp   al,54	     ;else is it key 54 (right shift key)
	 je    kin2	     ;jump if so
	 jmp   kin12	     ;any other break code is ignored
;
;  Here for left shift key being released
;
kin1:	 mov   lshift,0      ;indicate left shift key not pressed
	 jmp   kin12	     ;all done with this interrupt
;
;  Here for right shift key being released
;
kin2:	 mov   rshift,0      ;indicate right shift key not pressed
	 jmp   kin12	     ;all done with this interrupt
;
;  Here for a key being made, first test for shift key cases
;
kin3:	 cmp   al,42	     ;is this key 42 (left shift key)
	 je    kin4	     ;jump if so
	 cmp   al,54	     ;else is it key 54 (right shift key)
	 je    kin5	     ;jump if so
;
;  Check for Del key. This is used to terminate the program by doing a reboot
;  (much as the real BIOS routine reboots on an Alt-Ctrl-Del combination). The
;  reboot is achieved by passing control to location FFFF:0000 which we do
;  with a far jump.
;
	 cmp   al,83	     ;jump if not Del key
	 jne   kin6
	 jmp   dword ptr reset	 ;else take reset jump
;
;  Here for left shift key being pressed
;
kin4:	 mov   lshift,1      ;indicate left shift key currently pressed
	 jmp   kin12	     ;all done with this interrupt
;
;  Here for right shift key being pressed
;
kin5:	 mov   rshift,1      ;indicate right shift key currently pressed
	 jmp   kin12	     ;all done with this interrupt
;
;  Here for some other key than a shift key being pressed. We must convert
;  it to its proper ASCII code, taking shift keys into account.
;
kin6:	 mov   bh,0	     ;put key number in BX for indexing
	 mov   bl,al
	 shl   bx,1	     ;double key number to index table
	 mov   al,lshift     ;test shift key pressed
	 or    al,rshift
	 jz    kin7	     ;jump if both shift keys off
	 inc   bx	     ;else bump to get shifted translation
;
;  Now we get the character. A value of zero indicates that we do not allow
;  the key (not an ASCII graphic), so we sound the alarm.
;
kin7:	 mov   al,kna[bx]    ;load code
	 or    al,al	     ;test defined
	 jz    kin8	     ;if not go sound alarm
;
;  Put key in ring buffer, testing first to make sure there is room in
;  the buffer. The buffer will get full if the background falls more
;  than 15 characters behind the keyboard.
;
	 mov   bx,storep     ;get number of characters in buffer
	 sub   bx,loadp
	 and   bx,rbufl-1    ;this deals with the ring wrap around
	 cmp   bx,rbufl-1    ;ring buffer full?
	 je    kin8	     ;if so, go sound alarm
	 mov   bx,storep     ;else reload store pointer
	 mov   rbuf[bx],al   ;store the data
	 inc   bx	     ;bump pointer
	 and   bx,rbufl-1    ;with wrap around
	 mov   storep,bx
	 jmp   kin12	     ;all done with this interrupt
;
;  Come here if erroneous key, or keyboard overrun, or keyboard ring buffer
;  is full. What we want to do is to sound the alarm. The alarm is actually
;  a speaker, which we can turn off and on. To make an audible note, we
;  must turn it on and off at an appropriate frequency. The speaker is
;  controlled by bit 1 of the keyboard control port (port 61h).
;
kin8:	 mov   bx,200	     ;number of cycles for reasonable length tone
	 in    al,61h	     ;get setting of control port
;
;  Loop through cycles, first turn speaker on
;
kin9:	 or    al,00000010b  ;turn speaker on
	 out   61h,al
	 mov   cx,70	     ;set delay for half cycle
kin10:	 loop  kin10	     ;delay with speaker on
;
;  Now turn speaker off
;
	 and   al,11111101b  ;turn speaker off
	 out   61h,al
	 mov   cx,70	     ;set delay for half cycle
kin11:	 loop  kin11	     ;delay with speaker off
;
;  Loop till speaker turned on and off enough times
;
	 dec   bx	     ;decrement cycle counter
	 jnz   kin9	     ;loop back if more to go, else end wi speaker off
;
;  All done with processing interrupt. As we discussed before, we must
;  tell the interrupt system we are done so that it will now permit equal
;  (other keyboard) and lower priority interrupts to occur. This is done
;  by sending an "End of Interrupt" (EOI) signal to the interrupt system,
;  by writing the special value 20h to port 20h. Before issuing this EOI
;  signal we turn interrupts off since we do not want another keyboard
;  interrupt handled until we are completely done with this one. The IRET
;  instruction will restore the background flags, including the interrupt
;  flag and thus turn interrupts back on.
;
kin12:	 cli		     ;interrupts off
	 mov   al,20h	     ;load EOI command value
	 out   20h,al	     ;inform interrupt system we are done
;
;  Now we restore registers and return to background. The actual return is
;  done using an IRET instruction which restores the background code segment,
;  instruction pointer, and flags values, using the saved values which were
;  automatically stacked when the interrupt was signalled.
;
	 pop   dx	     ;restore registers
	 pop   cx
	 pop   bx
	 pop   ax
	 pop   es
	 pop   ds
	 iret		     ;interrupt return
kin	 endp
;-------------------------------------------------------------------------------


;-------------------------------------------------------------------------------
	  .data
;
;  Data areas. The following 16 location buffer is used to hold keyboard
;  values, stored by the interrupt handler. It is called a ring buffer,
;  because the values are stored in sequence until the end is reached
;  and then subsequent values are stored starting at the beginning again,
;  so the 16 locations are joined as if in a continuous ring.
;
;  The values in storep and loadp are indexes into rbuf (in the range
;  0 to 15). The value in storep indicates the next location into which
;  data must be stored, and the value in loadp indicates the location
;  of the next data value to be retrieved. The idea is that storep runs
;  ahead of loadp on the ring. If the value in loadp is equal to storep,
;  it means that loadp has "caught up" and there is no data in the ring.
;  We assume that storep never manages to "lap" loadp, otherwise we would
;  get confused. In other words, who ever is taking data out of the buffer,
;  using loadp, must do it fast enough so that there are never more than
;  15 elements stored.
;
;  Actually, the routine which is storing into rbuf can check to make sure
;  that this "overrun" situation can never occur, and that is what we do
;  in this program.
;
;  The ring buffer is a power of 2 long. As we will see later, this makes it
;  easier to perform the required wrap around of pointer values.
;
rbufl	 equ   16	     ;length (must be a power of 2)
rbuf	 db    rbufl dup (?) ;space for 16 key values
loadp	 dw    0	     ;index for loading data
storep	 dw    0	     ;index for storing data
;
;  Address used to pass control to reset routine when Del key is pressed
;
reset	 dw    0000h	     ;offset value is zero
	 dw    0ffffh	     ;segment is top paragraph in memory
;
;  The only other data locations are the flags used by the interrupt handler
;  to remember whether either of the two shift keys is currently pressed.
;
lshift	 db    0	     ;non-zero if left shift key is pressed
rshift	 db    0	     ;non-zero if right shift key is pressed

;
;  Table used to convert keyboard scan codes to ASCII codes. The entry
;  for each key is two bytes, the first byte is the lower case code, and
;  the second byte is the upper case code. A value of zero is used to
;  indicate that the key is not an ASCII graphic character key.
;
kna	 db    0,0	     ;Key number 00 (not used, key numbers start at 1)
	 db    0,0	     ;Key number 01 - Esc
         db    '1!'          ;Key number 02 - 1
         db    '2@'          ;Key number 03 - 2
         db    '3#'          ;Key number 04 - 3
         db    '4$'          ;Key number 05 - 4
         db    '5%'          ;Key number 06 - 5
         db    '6^'          ;Key number 07 - 6
         db    '7&'          ;Key number 08 - 7
         db    '8*'          ;Key number 09 - 8
         db    '9('          ;Key number 10 - 9
         db    '0)'          ;Key number 11 - 0
         db    '-_'          ;Key number 12 - -
         db    '=+'          ;Key number 13 - =
	 db    0,0	     ;Key number 14 - Backspace
	 db    0,0	     ;Key number 15 - Tab
         db    'qQ'          ;Key number 16 - Q
         db    'wW'          ;Key number 17 - W
         db    'eE'          ;Key number 18 - E
         db    'rR'          ;Key number 19 - R
         db    'tT'          ;Key number 20 - T
         db    'yY'          ;Key number 21 - Y
         db    'uU'          ;Key number 22 - U
         db    'iI'          ;Key number 23 - I
         db    'oO'          ;Key number 24 - O
         db    'pP'          ;Key number 25 - P
         db    '[{'          ;Key number 26 - [
         db    ']}'          ;Key number 27 - ]
	 db    0,0	     ;Key number 28 - Enter
	 db    0,0	     ;Key number 29 - Ctrl
         db    'aA'          ;Key number 30 - A
         db    'sS'          ;Key number 31 - S
         db    'dD'          ;Key number 32 - D
         db    'fF'          ;Key number 33 - F
         db    'gG'          ;Key number 34 - G
         db    'hH'          ;Key number 35 - H
         db    'jJ'          ;Key number 36 - J
         db    'kK'          ;Key number 37 - K
         db    'lL'          ;Key number 38 - L
         db    ';:'          ;Key number 39 - ;
         db    27h,'"'       ;Key number 40 - '
         db    '`~'          ;Key number 41 - `
	 db    0,0	     ;Key number 42 - Left shift
         db    '\|'          ;Key number 43 - \
         db    'zZ'          ;Key number 44 - Z
         db    'xX'          ;Key number 45 - X
         db    'cC'          ;Key number 46 - C
         db    'vV'          ;Key number 47 - V
         db    'bB'          ;Key number 48 - B
         db    'nN'          ;Key number 49 - N
         db    'mM'          ;Key number 50 - M
         db    ',<'          ;Key number 51 - ,
         db    '.>'          ;Key number 52 - .
         db    '/?'          ;Key number 53 - /
	 db    0,0	     ;Key number 54 - Right shift
	 db    0,0	     ;Key number 55 - PrtSc
	 db    0,0	     ;Key number 56 - Alt
         db    '  '          ;Key number 57 - Space bar
	 db    0,0	     ;Key number 58 - Caps Lock
	 db    0,0	     ;Key number 59 - F1
	 db    0,0	     ;Key number 60 - F2
	 db    0,0	     ;Key number 61 - F3
	 db    0,0	     ;Key number 62 - F4
	 db    0,0	     ;Key number 63 - F5
	 db    0,0	     ;Key number 64 - F6
	 db    0,0	     ;Key number 65 - F7
	 db    0,0	     ;Key number 66 - F8
	 db    0,0	     ;Key number 67 - F9
	 db    0,0	     ;Key number 68 - F10
	 db    0,0	     ;Key number 69 - Num Lock
	 db    0,0	     ;Key number 70 - Scroll Lock
	 db    0,0	     ;Key number 71 - Numeric pad 7
	 db    0,0	     ;Key number 72 - Numeric pad 8
	 db    0,0	     ;Key number 73 - Numeric pad 9
	 db    0,0	     ;Key number 74 - Numeric pad -
	 db    0,0	     ;Key number 75 - Numeric pad 4
	 db    0,0	     ;Key number 76 - Numeric pad 5
	 db    0,0	     ;Key number 77 - Numeric pad 6
	 db    0,0	     ;Key number 78 - Numeric pad +
	 db    0,0	     ;Key number 79 - Numeric pad 1
	 db    0,0	     ;Key number 80 - Numeric pad 2
	 db    0,0	     ;Key number 81 - Numeric pad 3
	 db    0,0	     ;Key number 82 - Ins
	 db    0,0	     ;Key number 83 - Del
;-------------------------------------------------------------------------------


;-------------------------------------------------------------------------------
	  .stack 100h
;-------------------------------------------------------------------------------

;
;  End of program
;
	 end start
