

                        Ŀ
                                           
                            CHAPTER III    
                                           
                        


           Ŀ
                                                        
                  INSTALLING NEW INT 13h FUNCTIONS      
                                                        
                           A FIRST STEP                 
                                                        
           



1. GENERALITIES

   We shall devote this entire chapter to one problem :

      Read the CHRN field of all sectors present on a given track.

Such an aim might look rather limited. In fact, we will have to
introduce various techniques that are needed to solve more
sophisticated problems.
When we call the standard INT 13h to read or write a sector, we do not
need to worry about turning the disk drive motor on, about moving the
read/write head to the desired track : INT 13h takes care of such disk
drive control operations.
Before trying to install functions of our own, we must realize that
nothing comes for free ; the disk controller does exactly what it is
told to do and nothing more.
Of course, we could think of using some routines in ROM. The trouble
is that their location depend on the brand (and model) of the
computer. So, to ensure portability, we must give up the idea of
taking advantage of routines in ROM.
The reader might wonder why we want to expand INT 13h capabilities
rather than install independent services. Well, that is just a choice.
It is neither easier nor more difficult than other approaches.
Anyway, whatever the approach is, the core of the implementation
remains the same.

As we want to provide INT 13h with a new service, we need a new
function number. We choose  FFh  (almost arbitrarily).

Let us recall a well known technique.

There is a standard method to provide an interrupt with additional
services : one install a new interrupt function dispatcher using a TSR
(Terminate and Stay Resident) program. An appropriate .COM file will
do the job.
Of course, the installation part of the program should not be kept
resident ; it may be thrown away as soon as the installation is
completed.
Quite obviously, the interrupt function dispatcher should direct all
pre-existent services calls to the original dispatcher ; therefore we
need to save a pointer to the original dispatcher.

The following program demonstrates a skeleton method of implementing
a new INT 13h.



code		segment
		assume	cs:code, ds:code
		org	100h


start:		jmp 	install

...............................
...............................


old_int13	dd	?


new_int13	proc	far
		cmp	ah,0ffh
		je	new_service
		jmp	cs:old_int13

new_service:

;           Ŀ
;                                 
;               new service       
;               instructions      
;                                 
;           



new_int13	endp


install:
		mov	ax,3513h                   ; get original
		int	21h                        ; int vector

		mov	word ptr old_int13,bx      ; and save
		mov	word ptr old_int13+2,es    ; it

		mov	ax,2513h                   ; set
		mov 	dx,offset new_int13        ; new
		int 	21h                        ; int vector

		mov	dx,offset install          ; evaluate
		add	dx,15                      ; amount of memory
		mov	cl,4                       ; memory to reserve
		shr	dx,cl                      ;  (in paragraphs)

		mov	ax,3100h                   ; terminate and
		int	21h                        ; stay resident


code		ends
		end	start


The installation part of the program above is a quick and dirty one :
it does not verify that the resident part is not already installed.



2. PLANNING OUR WORK

   Before going into details, let us remember that this chapter is
devoted to the implementation of an INT 13h new service (function FFh)
to read the CHRN field of all sectors present on a given track.
The hard work is, of course, to fill in the box marked 'new service
instructions' in the program shown in section 1.

Several steps will be required :

         (A)  Turn the disk drive motor on
         (B)  Recalibrate the read/write head
         (C)  Position the read/write head over the desired track
         (D)  Read CHRN fields

All these steps will be performed at the lowest possible level. The
computer operating system will not be kept informed of what we will
be doing.
There is only a small price to pay the operating system for keeping
it ignorant :

        The price to pay the operating system  Ŀ
                                                              
           Just after exiting from INT 13h / function FFh     
           and wathever the reason for exiting is :           
                                                              
           a disk drive RESET (INT 13h / function 0) MUST     
           be performed.                                      
                                                              
      


REMARK : INT 13h / function FFh requires parameters (drive, track,
         head). Just as for the standard INT 13h, the calling program
can use registers to pass parameters.
INT 13h / function FFh also needs a data area of its own to store its
results.
This data area can also be used to save the parameters passed to
INT 13h / function FFh.
Therefore the first instructions of INT 13h / function FFh could look
like :

                     jmp       over_data

drive                db        ?
track                db        ?
head                 db        ?

results              db        ....  dup (?)

over_data:           push      ds
                     push      cs
                     pop       ds

; --  push all registers to be preserved

                     mov       drive,dl
                     mov       track,ch
                     mov       head,dh

                     ..................
                     ..................

Of course, the last instructions to be executed should return a
pointer to the results (e.g. through  ES:BX) and an error code (e.g.
through AH).


NOTE : Various procedures will be described in the next sections. Some
       (easy) work is required to put all the pieces together.
We leave that to the reader.




3. TURNING THE DISK DRIVE MOTOR ON

Only one parameter is required : the drive number (0 = drive A:
1 = drive B:).
We need to access the disk drive DIGITAL OUTPUT REGISTER (DOR) through
port 3F2h.

To turn the motor of drive 0 on, the bit setting of the DOR must be
changed into

          Ŀ
                            bit number                   
                                                         
            7    6    5    4    3    2    1    0  
          Ĵ
            0    0    0    1    1    1    0    0  
          


To turn the motor of drive 1 on, the bit setting of the DOR must be
changed into

          Ŀ
                            bit number                   
                                                         
            7    6    5    4    3    2    1    0  
          Ĵ
            0    0    1    0    1    1    0    1  
          


The byte at memory location  0:440h  holds the count down until the
disk drive motor is shut off. As we do not know for how long the disk
drive is required, it is safer to set the counter to the highest
possible value : FFh.
The computer hardware decrements this counter about 18.2 times per
second.
So, unless we need the motor to be left on for more than about
14 seconds (255/18.2), we do not need to worry any more about this
counter.

We are now ready to put it all together :

motor_on         proc     near
                 xor      ax,ax
                 mov      es,ax
                 cli
                 mov      byte ptr es:[440h],0ffh
                 sti
                 mov      cl,drive
                 mov      al,10h
                 shl      al,cl
                 or       al,cl
                 or       al,0ch
                 mov      dx,3f2h
                 out      dx,al

; -- wait for motor to come to speed  (500 milliseconds)

                 mov      al,3
                 xor      cx,cx
m1:              loop     m1
                 dec      al
                 jnz      m1
                 ret
motor_on         endp




4. RECALIBRATING THE READ/WRITE HEAD

   (a) The recalibrate operation retracts the read/write head to
       track 0.
It belongs to a group of disk controller operations which can only be
accessed at low level. Operations in this group will be used quite
often and they all require similar programming methods. Let us call
them LDC operations.
Before looking into the details of the recalibrate operation, we want
to discuss some generalities about LDC operations.

LDC operations make use of two disk controller registers :

     . the  DATA    register   (through port  3F5h)
     . the  STATUS  register   (through port  3F4h)

The disk controller operates in three phases when dealing with LDC
operations :
                  .  the command phase
                  .  the execution phase
                  .  the result phase

There is nothing to be said about the execution phase ; the disk drive
electronics performs the requested operation.

Some LDC operations have no result phase (i.e. the disk controller
does not return any result). The recalibrate operation is an example.

The general requirements of the command phase are :


        command phase requirements  Ŀ
                                                              
           send an appropriate sequence of bytes to the       
           DATA register (after receiving all bytes in the    
           sequence the disk controller will  enter  into     
           the execution phase)                               
                                                              
         --------------------  BEWARE  --------------------   
                                                              
           before sending EACH byte :                         
                                                              
           check if the DATA register is ready to accept      
           it, i.e. make sure                                 
                                                              
               that bit 6 of the STATUS register is 0  (*)    
           AND that bit 7 of the STATUS register is 1  (*)    
                                                              
      Ĵ
           (*) allowing for up to 65536 retries               
      

If the command phase fails, despite  all the allowed retries, an
error code must be IRETurned to the calling program. The calling
program should save the error code, perform a RESET operation (as
explained in section 2) and take proper action (error message, retry,
program termination).
Most command phase failures are due to program bugs!


The command phase sequence required by the recalibrate operation is
the 2-byte sequence :


               Ŀ
                                                   
                     07           drive number     
                                                   
               


That is all we need to know to write a recalibrate procedure :


recalibrate           proc       near
                      mov        ah,7
                      call       out_to_nec     ; send contents of AH
                                                ; to DATA register
                      jc         in_out_error
                      mov        ah,drive
                      call       out_to_nec
                      jc         in_out_error

; -- wait for head to settle (30 milliseconds)

                      mov        cx,4000h
r1:                   loop       r1

                      ret


in_out_error:         mov        ah,..          ; error code

;                     ...        .....
;                     ...        .....

;                     IRETURN TO CALLING PROGRAM

recalibrate           endp



; out_to_nec          send contents of AH to DATA register
;                     returns   CF = 0    if success
;                               CF = 1    if failure

out_to_nec            proc       near
                      clc
                      mov        dx,3f4h        ; STATUS reg port
                      xor        cx,cx
out1:                 in         al,dx
                      test       al,40h         ; bit 6 of STATUS reg
                      jz         out4
                      loop       out1
out2:                 stc
                      ret
out4:                 xor        cx,cx
out5:                 in         al,dx
                      test       al,80h         ; bit 7 of STATUS reg
                      jnz        out6
                      loop       out5
                      jmp        out2
out6:                 mov        al,ah
                      inc        dx
                      out        dx,al
                      ret
out_to_nec            endp



(b)  The recalibrate operation has no result phase. Why? Well, I do
     not know. I could not find any explanation in any manual.

How does one know the read/write head is actually retracted to
track 0 after a recalibrate operation. There is only one way : by
performing a LDC operation known as the  'sense interrupt status'
operation.

The command phase requires a 1-byte sequence to perform a sense
interrupt status operation. It is

                        Ŀ
                                      
                              08      
                                      
                        

Before looking into the details of the result phase for a sense
interrupt status operation, we want to discuss some generalities about
result phases.

The disk drive controller provides up to  7  result bytes (the exact
number depends on the LDC operation).
ALL result phase bytes MUST be read (the disk controller will not
accept any new command until they are read).
Let us decribe how the result bytes are to be read :


        to read the result phase bytes :  Ŀ
                                                              
           read an appropriate number of bytes from the       
           DATA register                                      
                                                              
         --------------------  BEWARE  --------------------   
                                                              
           before reading EACH byte :                         
                                                              
           check if the DATA register is ready to confide     
           its secrets, i.e. make sure                        
                                                              
               that bit 6 of the STATUS register is 1  (*)    
           AND that bit 7 of the STATUS register is 1  (*)    
                                                              
      Ĵ
           (*) allowing for up to 65536 retries               
      

If the appropriate number of bytes cannot be read from the DATA
register, an error code should be IRETurned to the calling program ...
(just as for command phase failure).


The number of result phase bytes is  2  for the sense interrupt status
operation.

Let us call the first one       ST0
            the second one      PCN     (Present Cylinder Number).

If a sense interrupt status operation is performed right after a
recalibrate operation then

                   PCN    should  be   00

        bit  7  of ST0    should  be    0
        bit  6  of ST0    should  be    0
        bit  5  of ST0    should  be    1
        bit  4  of ST0    should  be    0
        bit  3  of ST0    should  be    0

If any of these conditions is not satisfied, an error code should be
IRETurned to the calling program to report a recalibrate failure.
The calling program should try recalibrating three times before
assuming an error is real.


We end this section with a procedure that demonstrates how to read the
result phase bytes :


read_results        proc      near
                    mov       cx,..         ; number of bytes to read

                    mov       bx,offset some_where
rr1:                push      cx
                    call      in_from_nec
                    jnc       rr2
                    jmp       in_out_error  ; see part (a)
rr2:                mov       [bx],al
                    inc       bx
                    pop       cx
                    loop      rr1
                    ret
read_results        endp



; in_from_nec       read one byte from DATA register
;                   returns   CF = 0   byte in AL  if success
;                             CF = 1               if failure

in_from_nec         proc      near
                    clc
                    xor       cx,cx
                    mov       dx,3f4h       ; STATUS reg port
in2:                in        al,dx
                    test      al,80h        ; bit 7 of STATUS reg
                    jnz       in4
                    loop      in2
in3:                stc
                    ret
in4:                xor       cx,cx
in5:                in        al,dx
                    test      al,40h        ; bit 6 of STATUS reg
                    jnz       in6
                    loop      in5
                    jmp       in3
in6:                inc       dx
                    in        al,dx
                    ret
in_from_nec         endp



5.  MOVING THE READ/WRITE HEAD

    The disk controller provides us with a LDC operation known as the
seek operation.
The seek operation positions the read/write head over the desired
track.
Its command phase requires a 3-byte sequence


           Ŀ
                                                         
                0Fh       (head * 4) + drive     track   
                                                         
           


The seek operation has no result phase.
The only way to check if the read/write head is actually over the
desired track is to perform a sense interrupt status operation
(section 4(b)) ; it should return :


                   PCN    =   desired track number

        bit  7  of ST0    =      0
        bit  6  of ST0    =      0
        bit  5  of ST0    =      1
        bit  4  of ST0    =      0
        bit  3  of ST0    =      0


We shall not discuss the seek operation any further. It is quite
similar to the recalibrate operation.



6.  READING CHRN FIELDS

    The LDC operation known as 'read ID' reads a CHRN field.
To be more precise :
It keeps reading bytes until a CHRN field is found.
This DOES NOT mean that read ID tries to locate the first CHRN field
on a track. Otherwise we would never be able to read all CHRN fields!

As one guesses, to read all CHRN fields one performs read ID
operations in a loop.
Fifty readings are commonly sufficient to catch all CHRN fiels.
There is no entry in the Guiness Book of Records about CHRN fields.
So I shall just mention that the record I know about is  34  CHRN
fields.

The command phase requires a 2-byte sequence


                Ŀ
                                                   
                     4Ah       (head * 4) + drive  
                                                   
                


The result phase provides  7  bytes :

        the first  result byte is called  ST0
        the second result byte is called  ST1
        the third  result byte is called  ST2

        the four last result bytes hold the 4-byte CHRN field read
                                                   by read ID .

The reader is referred to Hogan's Sourcebook (8.19) for a complete
description of ST0, ST1 and ST2.


As far as LDC operations are concerned that is all we need to write a
procedure that reads all CHRN fields on a track (using programming
methods described in section 4). Just a moment, please!
So far some questions are left unanswered :

(a)  Suppose that there are exactly  11  CHRN fields on the track
     being examined. If we perform  50  read ID operations, some CHRN
fields will be read  4  times and the other ones will be read  5
times. How does one find the exact number of CHRN fields?

(b)  It is natural to demand that readings should start at the
     beginning of a track rather than 'somewhere'.


The problem raised in (b) cannot be solved by standard methods
(we need tricky techniques to be covered in Chapter IV).


Let us answer question (a).
Suppose that we have got a 'high precision clock' so that immediately
after each CHRN field is found we read (and save) the 'exact' time.
After reading fifty CHRN fields we can look closely at the results
(including the time readings). If we know how long it takes a diskette
to perform a complete revolution, it becomes quite easy to determine
the exact number of CHRN fields on the track and what they are.
As we know that there are about 6250 bytes on a track, it is also
quite easy to determine approximately how many bytes there are between
two consecutive CHRN fields.

The system board contains an Intel 8253/8254 timer chip. It provides
three 16-bit counters that may be used for timing or counting in the
system. Each of the counters decrements once every 839 nanoseconds
(the frequency is 1.19318 MHz). That is more than we need!
Details about the 8253 may be found in D.J. BRADLEY's book 'Assembly
Language for the IBM Personal Computer' (Prentice-Hall).

It takes about 32 microseconds to read/write a byte from/to the
diskette. Therefore we do not really need the 8353 timer high
precision.

Other 'clocks' are available.
Let us give an example.

The system board has an Intel 8237 DMA chip. One of its functions is
to refresh memory (it constantly restores the charge in the RAM memory
cells). Refresh cycles occur every 15 microseconds (approximately).
The DMA has a word count register (accessible through port 1) reserved
for the memory refresh function. It is decremented every 15
microseconds (approximately) ; although the exact delay between two
consecutive decrements may vary from one computer to another, it is a
constant for each computer.
So the DMA chip provides us with a nice 'clock'. Reading the word
count register through port 1 is a bit tricky.
Here is a reading procedure :


read_time         proc      near
                  xor       cx,cx
rdt1:             cli

                  out       0ch,al         ; tricky part
                                           ; make DMA happy
                  in        al,1
                  mov       ah,al
                  jmp       short  rdt2
rdt2:             in        al,1
                  sti
                  or        ah,ah
                  jnz       rdt3
                  loop      rdt1
rdt3:             xchg      ah,al
                  ret                      ; time in AX
read_time         endp



We can determine how long it takes the diskette to perform a complete
revolution (whatever 'high precision clock' we use) by experimenting
on a standard diskette.

We are done!


NOTE : Interesting information about the RAM refresh cycle may be
       found in B. Roemmele's excellent article :
                  Instant Speedup for your PC
               PC Magazine  July 1988  pp 331-346.

