åXDISK1-C ARTÅ.DISK1-C LSTÇTËsNOTICE Ñßÿÿÿÿ+-----------------------------------------------------------------+ | ****** NOTICE ****** | | Copyright 1985 by Micro/Systems Journal | | PO Box 1192, Mountainside, NJ 07092 : | All rights reserved, reproduction prohibited without permission : +-----------------------------------------------------------------+ Reprinted from Micro/Systems Journal, May/June 1985 <<>> by Edward Heyman 300 Center Hill Road Centerville, DE 19807 In his article in the 1982 September/October Microsystems Anthony Skjellum made the point that the C programing language is a viable alternative to assembly language for utility programs. My experience is that C not only reduces the time required to develop correct programs versus assembly language, it also makes it simpler to write programs that are "user friendly". This article discusses a practical example of the use of C to write disk utility programs. It also provides some insight into the operation of the Intel 8272 controller chip and the Godbout Disk1 controller board as well as providing several useful utilities and a command language to use to create your own utilities. Although some of the utilities could be written with calls to the CP/M BDOS or BIOS, directly accessing the controller lets you use faster and more powerful commands. For example, entire cylinder reads and writes may be done. Also there are no provisions in the CP/M BDOS or any BIOS that I have seen, to format a disk. <<>> I recently upgraded my S-100 system with a Godbout Disk1 DMA controller and Qume double sided eight inch drives. Although Godbout supplies a Format and a Copy program, I wanted to be able to format individual tracks, to verify a disk and locate any bad sectors and I wanted a faster copy program. I also use the UCSD 'p' system and needed some utilities for special disk formatting and system generation. However my most important objective was to understand how the controller worked. <<>> The first step was to read the Godbout and Intel literature in the Disk1 manual. This literature was very helpful but, as I was to learn, did not tell the whole story. I then wrote routines to implement simple disk operations, placing the routines in a program that let me test their operation. I used a lot of printf() statements so that I could trace the progress of the program and understand what the controller was doing. Once a routine was shown to be correct I placed it in a library and proceeded to the next routine. When the library contained all the primitive disk routines I began a second library that contains more powerful disk commands based on the disk primitives. Once the second library was complete writing the utilities was a simple task. <<>> The Disk1 board uses the Intel 8272 / NEC 765 disk controller chip. The C routines that I developed are applicable, with some modifications, to other boards using this chip. Communication between the CPU and the Disk1 is done through three IO ports. Two of these ports are for the 8272 chip and the third is implemented on the Disk1 board. The ports are as follows: DISK1 CONTROL PORT FUNCTION PORT NAME PORT FUNCTION READ WRITE FD_STATUS CHIP STATUS REGISTER FD_DATA RESULTS DATA COMMAND DATA DISK1_PORT BOARD INTERRUPT STATUS DMA ADDRESS Each command to the disk controller has a command phase, an execution phase and a result phase. During the command phase, a series of bytes is sent to the controller data port. When the controller receives the last byte of the command, it enters the execution phase. When execution is complete the interrupt status is set and the result data bytes must be read by the cpu from the fd_data port. The major commands that the 8272 chip understands are: Number of Command command bytes result bytes Specify 3 0 Sense Interrupt Status 1 2 Sense Drive Status 2 1 *Recalibrate 2 0 Seek 3 0 Read ID 2 7 Format Track 6 7 Read Data 9 7 Read Track 9 7 Write Data 9 7 With single-sided floppies the term "track" is defined. With two sided disks the usage is less clear. I will use the term cylinder to mean the position of the heads with respect to the disk and the term "track" to mean the location that a single head can access when located at a specific cylinder. In general we can find the cylinder and head for a two sided disk knowing the track by: <_cylinder = track / 2; /* integer divide */ head = track % 2; /* remainder of integer divide */_> For single-sided disks the head is always 0 and the cylinder is equal to the track. <<>> The first routines that I wrote were three routines to interrogate the status ports and wait until they are clear. These routines are cmdstat() which signals that the 8272 is ready to accept a command byte, intstat() which signals the completion of the execution phase, and resultstat() which signals that the 8272 is ready to have a result byte read. The code for cmdstat() is given in listing 1. Next I wrote routines that would interpret the results returned by the 8272 after command execution. The 8272 has four status registers named Status Register 0 to Status Register 3. Each of these registers is one byte wide. Almost every bit in each status register byte has a meaning. Not every command returns results in all registers. In addition to the status registers, with some commands, the 8272 returns information about the current track, current sector, current head and the number of bytes per sector as, single byte values. The routines st0(),st1(),st2(), and st3() display the mnemonic for the individual bits along with their values for the four status registers. The code to display the results returned in status register 0 is given in listing 2. Bit fields are not implemented in BDS 'C'. I therefore used the 'shift' operator to move the bits into the rightmost position and then the 'and' operator to mask out the unwanted bits. If bit fields were available a more elegant solution would be to create a structure that would permit direct access of the bits. The five 8272 commands, listed above, that return seven result bytes all have the same result format so I was able to write a single routine, called printresult(), that displays all of the data returned by these 8272 commands. To simplify writing programs to test the procedures, I wrote four routines to allow the drive, cylinder, sector and head to be input from the console. These routines are getdrive(), getcyl(), getsector() and gethead(). The code for getdrive() is shown in listing 3. <<>> Now I was ready to write routines that did something. The first routine was recal(). This routine places the head(s) of the selected drive at cylinder zero. The 8272 command for recalibrate requires two command bytes and does not return any result bytes. To determine if the recalibration was successful it is necessary to use the 8272 Sense Interrupt Status command. This command returns Status Register 0 and the current cylinder number. The last two bits of Status Register 0 are called the interrupt code (IC). The values of IC have the following meanings: 0 = Command completed successfully. 1 = Execution started but not successfully completed. 2 = Invalid command issued, execution never started. 3 = Command aborted because ready signal changed. I used the value of IC and the value of the cylinder location byte to determine if the recalibration was successful. Recal() returns a true (1) if the recalibration was good. If it was not then the routine trys two more times. If the retrys are also unsuccessful then Status Register 0 is decoded for the cause of the failure and an error message is displayed. The program that I wrote to develop the recal() routine is given in listing 4. The program has a short main section and two subroutines, recal() and senseintstat(). Once I had the program running properly I removed the debugging displays and placed the two subroutines in the library. The next command to tackle was dseek() . Dseek() positions the head over the specified cylinder. I called it dseek() to differentiate it from the BDS 'C' built-in seek(). Dseek() is similar to recal() except that the command phase requires a third byte, the cylinder number, to be sent to the 8272. I used the same technique, of writing a program to check the operation of the dseek() routine, this time since I had already developed the senseintstat() routine and had it in the library, I only had to link it to the test program. <<>> Every command to the 8272 starts with a command byte that has a specific structure. Bits 0 to 4 of this byte contain the code that tell the 8272 which command is being called. The command codes are given as #define statements in CDISK.H (listing 5). Bit six, called the mfm bit, tells the 8272 whether a single or double density operation is requested. Bit seven, called the mt bit, tells the 8272 whether the operation is to be single or double sided. Not every command uses the mt and mfm bits. In addition to the the command codes, CDISK.H also contains the global variables that I created to reduce the amount of data that had to be passed between procedures. One particular variable requires some additional discussion. The variable bps describes the format of the disk. The bytes per sector are related to bps by the formula: bytes per sector = 128 * 2^bps. The readid() routine, which returns the density and format of the selected disk, was the next to be implemented. The 8272 read ID command requires a two byte command phase. Bit six of the first byte must be the mfm bit. But we don't know if we are doing a single or double density operation. In fact the reason to use the 8272 read ID command is to determine the density and the format of the disk. If the mfm bit is set incorrectly the 8272 read ID command will return a non zero interrupt code (IC), in status register 0, during the result phase. The way I constructed the readid() routine was to set the global variable mfm[drive] to 1 (double density), call the 8272 read ID command, then check for IC = 0 indicating a correct operation. If the operation is correct the routine sets the global variable n[drive] equal to result byte six and returns true. If IC is non zero then mfm is set to 0 (single density) and the 8272 readID command is called again. If this time IC is zero readid sets n[drive] and returns a true value. If IC is not zero then the other status registers are decoded, an error message is displayed and the routine returns false. In any event the seven byte results array is returned to the caller. The readid() command is the first to be discussed that returns seven result bytes. To read those bytes from the 8272 I created the routine getresult() which is shown in listing 6. I'll say more about getresult() later. Using readid() we have the density and the number of bytes per sector,but we still need to know if the disk is single- or double-sided. The number of sides on a disk can be determined by looking at the two-sided bit in Status Register 3. The 8272 Sense Drive Status command returns Status Register 3. Routine getmt() issues the 8272 Sense Drive Status command, reads the two result bytes, and then sets the global mt[drive] byte in accordance with the two sided bit in Status Register 3. <<>> We now have all we need to write the program Density. This program reports the number of sides, the density and the number of bytes per sector of a disk in the selected drive. The program is given in listing 7. <<>> All of the read and write commands as well as the format command require that the Disk1 be told where in memory to read from or to write to. The setdma() routine does the job. As it is written, all addresses are on 64k page 0. That can easily be changed if you want to address other pages, since the Disk1 DMA register is a 3 byte register. The 8272 read and write commands require a 9 byte command. I have already discussed the content of the first byte. The second byte tells the 8272 which drive and head to use. The next four bytes are the cylinder, the head, the sector and the number of bytes per sector. The remaining three bytes describe the disk format. The first of the three final bytes is called EOT and is the number of the last sector on a side (the first sector is 1). The next byte is GPL, the gap length, which is the spacing between sectors. The final byte is the data length, DTL. DTL has significance only when bps is 0 (ie; 128 bytes per sector). The values of EOT, GPL, and DTL are determined by the values of mfm (density) and n (bytes per sector). Routine setparam() when called after readid() will define the values of the global variables EOT, GPL and DTL. Listing 8 is a segment of the setparam() code. Before I could implement the read or write commands I had to create a routine to fill the command byte array with the nine command bytes. Four very similar commands do the job. The routines are called set??cmd() where ?? is: ^_rs : to read one or more sectors rc : to read a cylinder ws : to write one or more sectors wc : to write a cylinder-^ Listing 9 contains the code for setrscmd(rdcmd,drive,cyl,sector, sectors,hds). Rdcmd is an 9 byte array to hold the read command. Using the rs or ws routine permits any number of sectors on a side to be read or written. The first sector to be read or written is passed to the routine as sector and the number of sectors to process is passed as sectors. Note that when I talk about sectors I mean physical sectors which can be 128, 256, 512 or 1024 bytes long, not CPM logical sectors that are always 128 bytes long. The 8272 chip has the capability of doing multitrack reads or writes. That is, it can start on side 0 and complete the operation on side 1. The read cylinder (rc) and write cylinder (wc) commands require that bit seven of the first command byte be set to mt[drive] to enable (value = 1) or disable (value = 0) a multitrack operation. The routine dread() (listing 10) sends the command bytes to the 8272, waits until the execution phase is complete, and then reads in the result bytes from the 8272. The dwrite() routine operates in the same way as dread(). Now putting it all together, the sequence for a cylinder read is: ^_setdma(buffer); /* mem location to start data */ dseek(drive,cylinder); /* position head */ getmt(drive); /* determine number of sides */ readid(drive,hds,bytes); /* determine n and mfm */ setparam(drive); /* determine EOT,GPL and DTL */ setrccmd(rdcmd,drive,cylinder); /* fill command array */ dread(rdcmd,bytes); /* execute command */_^ I put the last six commands together to make a single readcyl() routine that does the whole job after setdma() is called. A similar routine was developed to write a cylinder and named writecyl(). If only a track is to be read, the command sequence of readcyl() is modified by replacing setrccmd() with setrscmd(rdcmd, drive, cylinder,head,1,eot[drive]). Here I have set the first sector to read as 1 and the number of sectors to read as eot[drive], which will read all the sectors on one side. The resulting routine is readtrack(). Its companion is writetrack(). <<>> The only primitives left to do are those for the formatting operation. The routines required to format a track are similar to those for the read or write operations. The setformat() routine is similar to the setparam() routine. A separate routine is required because the value for GPL is different for formatting and read/writing . It is also necessary to supply the cylinder,head, sector and bytes per sector (bps) for each sector in the track to be formatted. During the formatting operation the 8272 reads each set of four values prior to formatting the sector. The code of setformat() is shown in listing 12. The setformcmd() is similar to the setrtcmd() except that the command is only six bytes long. The last byte in the command array is the value of the data mark (E5). Routine form() is similar to dread() and dwrite(). Formtrack() puts together all the routines required to format a track. Finally the routine formcyl() formats an entire cylinder. Multitrack operations are not allowed with the 8272 format command. A cylinder of a two sided disk therefore requires two calls to the form() routine, once with hds set to 0 and once with hds set to 1. <<>> I have now described all of the primitive routines. We could at this point write the utility programs; however, several operations of the same form appear in the utilities and it is worthwhile creating some higher level routines. The higher level routines were put in library 2. The first of the high level routines is verifycyl(). This routine sets the DMA address then does a cylinder read. If the read is successful, verifycyl() returns true, else it returns false. If verifycyl() returns true, we know that the cylinder is properly formatted and can be read or written to. If we find an error on a cylinder we would like to know which sectors are defective. The routine, versectors(), does that job by reading each sector, one at a time, and reporting any errors that are found. Each side of a two sided disk is handled separately. The high level routine, Verify(), combines verifycyl() and verifysecs() to examine an entire disk and report all defective tracks and sectors. Verify() first checks all 77 cylinders and collects all defective cylinders in an array named badcyls[]. If any errors are found, verify() calls verifysecs() for each of the bad cylinders. Reader() reads multiple cylinders into a buffer. It simply calls readcyl() the required number of times. Compare() compares the contents of two buffers. The buffers are assumed to be cylinders containing sectors. If differences in the buffers are found, the cylinder and sector numbers of the differing sectors are displayed on the console. If no difference in the buffers is found, true is returned. Dmpsec(start,chunk) prints one physical sector to the console in ddt format. The dump starts at location start and the size of the physical sector is chunk. Dmpcyl() prints an entire cylinder to the console, one sector at a time. It first calculates the size of the physical sector, then determines the total number of sectors to print. Dmpcyl() calls dumpsec() to do the printing. <<>> Writing a program to verify a disk is now merely a matter of command line processing and calling verify(). The program formtrk formats a single track. After processing the command line arguments, the program prompts for the cylinder number, head and the number of bytes per sector. Knowing the bytes per sector, the program sets the value of mfm[drive]. The track is then formatted. If an error is encountered in the formatting operation, the contents of the status register are printed, and the program is aborted. If the formatting goes well, the track is verified with verifysecs(). Among other things, formtrk allows you to format the second side of track 0 which the Godbout format program does not do. Dcopy is a disk to disk copy program. It reads and writes four tracks at a time for double density disks (four cylinders for single sided drives, two cylinders for double sided drives) and eight tracks for single density disks. The program gives the user the option of verifying the copy operation. Without the verification the program copies a disk in less than half the time required by the Godbout supplied routine. I have yet to find a compare error when making a copy. Dumpcyl reads a cylinder from a disk and displays each physical sector on the console in ddt format. Readsec reads an individual sector from the selected disk and displays it on the console. <<>> The repair utility is useful when you have formatting errors on one or more sectors of a cylinder. The program first determines if the errors are on the head0 side or the head1 side or both. Next the program reads the bad track(s) into a memory buffer, sector by sector. Bad sectors are filled with data marks (e5 hex). From this point on we are vulnerable to program failure since once we *reformat the track the only place that has data is the memory buffer and termination of the program will result in loss of the entire track. To insure the greatest chances of success I do a loop controlled by verifysec(). If verifysec() returns a false, another attempt (up to 3) to format the track is made. After the track(s) are *reformatted three attempts to write the buffer back to the track with writetrack() are made. If these attempts fail, probably as the result of permanent disk damage, a sector by sector write is done to recover as much data as possible. <<>> Having a disk sector failure is always a traumatic experience. If the sector happens to be on the directory track, the problem is particularly severe, since the disk can't even be accessed. One way to recover some of the files is to use the repair utility. In this case only the files whose directory entries are on the bad sector(s) are lost. With 128 byte sectors, only four files would be lost. With 1024 byte sectors, however, 32 files would be lost. Two sided drives offer a valuable technique to protect against these occasional problems. The technique makes use of the fact that side 1 of cylinder 1 (track 3) is not normally used. We can use this track to store an image of the directory track. The utility savedir does that job. Frequent use of savedir will insure that the directory backup is current. When that terrible moment comes and we have a directory failure the program fixdir comes to the rescue. Fixdir works much like repair except that instead of filling bad sectors with data marks the bad sectors are replaced with their backups from track three. When the repair is completed a list of the files in the affected sector(s) is displayed on the console. <<>> I also run the UCSD 'P' system. My bios requires that I use two systems tracks. For simplicity I format both tracks as single density 128 byte sectors. The program pform does the job. For a single sided disk cylinders one and two are single density for a two sided disk both sides of track 0 are single density. After formatting is complete the disk is verified. The program getcode was created to allow reading sections of the systems tracks and placing the code in normal CP/M files. This program was very useful in copying the 'P' system SBOOT from its location on track 0 so that I could later relocate it to allow for a larger primary boot. It can of course, also be used for moving parts of other operating systems. Putcode is the reverse of getcode. Putcode reads a standard CPM file and then writes it to selected tracks and sector. I used this program to place the 'P' System primary boot, SBOOT, and sbios where I needed them. Pgen is a program for the 'P' system that does the same job that sysgen does for CP/M. The program reads the first two tracks (the systems tracks) of the source disk and writes then to the first two tracks of the destination disk. It checks to see that the disks have the same format since the primary boots are different for single and double sided disks. <<>> I placed all the console hardware specific routines in a file called iolib.c allowing easy modification for different consoles. <<>> The complete listings are available, on disk, from the SIG/M public domain library. Using the libraries you can readily create your own utility programs and/or improve mine. If you do either I'd be interested in hearing from you.  LISTING 1 /* reads the 8272 status port untill the chip is ready for a command */ /* byte, must be done before each command byte is sent to the 8272 */ int cmdstat() { char c; while ( (c = inp(fdcstat)) < 0x80); return(TRUE); } LISTING 2 /* print value of 8272 status register 0's bits */ st0(byte) char byte; { char drive,hd,nr,ec,se,ic; drive = byte & 3; /* drive number */ hd = (byte >> 2) & 1; /* head address ie; 0 or 1 */ nr = (byte >> 3) & 1; /* not ready if 1 */ ec = (byte >> 4) & 1; /* equipment check */ se = (byte >> 5) & 1; /* seek end */ ic = (byte >> 6) & 3; /* interrupt code, does not */ /* work as per 8272 data sheet for read and write commands */ /* since Godbout did not implement the terminal count line */ printf("\n ST0> %s %u %s %u %s %u %s %u %s %u %s %u \n", "drive = ",drive, " head = ",hd," nr = ",nr," ec = ",ec," se = ",se," ic = ",ic); }/* st0 */ LISTING 3 /* prompt for and return drive number */ /* drive may be entered as 'a','b','A','B','0' or '1' */ char getdrive() { char drive; printf("\nEnter drive designation "); do { drive = getchar(); if( (drive == 'A') || (drive == 'B') ) drive -= 'A'; else if( (drive == 'a') || (drive == 'b') ) drive -= 'a'; else if( (drive == '0') || (drive == '1') ) drive -= '0'; else { putchar('\b'); putchar(' '); putchar('\b'); } } while(drive != 0 && drive != 1); return(drive); } /*getdrive*/ LISTING 4 /* RECAL.C */ /* program for testing routines recal() and senseintstat() */ #include bdscio.h #include cdisk.h main() { char drive; int temp; while(TRUE) { drive = getdrive(); printf("\n drive = %u \n",drive); recal(drive); }/*while*/ }/*main*/ senseintstat(bytes) char bytes[]; /* reads and returns the 8272 status register 0 */ /* and the current cylinder number */ { cmdstat(); /* wait till ready for command byte */ outp(fdcdata,c_rsts); /* send command byte */ resultstat(); /* wait till ready for result byte */ bytes[0] = inp(fdcdata); /* get status register 0 */ st0(bytes[0]); /* print Status Register 0 */ resultstat(); /* wait till ready for result byte */ bytes[1] = inp(fdcdata); /* get cylinder number */ printf("\ncylinder = %u\n",bytes[1]); }/* senseint stat */ int recal(drive) char drive; /* move the head to cylinder zero, return TRUE if successful */ /* print error message if unsuccessful */ { int k; /* try counter */ char bytes[8]; /* array to hold results */ for(k=0; k < 3; K++) { cmdstat(); /* wait till ready for command byte */ outp(fdcdata,c_reca); /* send command byte */ cmdstat(); /* wait till ready for command byte */ outp(fdcdata,drive); /* end of 8272 recalibrate command phase*/ intstat(); /* wait till execution phase complete */ senseintstat(bytes); /* check if recal ok */ /* check for satisfactory completion and at cylinder 0 */ if( ((bytes[0] & no_err) == 0x00 ) && (bytes[1] == 0)) return(TRUE); }/* or */ printf("\nRecal error drive %c ",drive+'A'); if ( (bytes[0] & ds_err) != drive ) printf("incorrect drive select\n"); if ( (bytes[0] & nr_err) != 0 ) printf("not ready\n"); if ( (bytes[0] & eq_err) != 0 ) printf("equipment error\n"); return(FALSE); }/* recal */ LISTING 5 /* CDISK.H */ #define TRUE 1 #define FALSE 0 /* the following sets the conditional compile in setdma() so that the */ /* tpa(transient program area is in page 0 for CPM 2.2 and page 1 for */ /* CPM 3. If you are using with CPM 2.2 or a non-banked CPM 3 set to */ /* FALSE */ #define CPM3 TRUE /* DISK1 PORTS */ #define fdport 0xc0 /* base address of disk controller */ #define fdcstat fdport /* 8272 status port */ #define fdcdata fdport + 1 /* 8272 command and results data port */ #define fdma fdport + 2 /* Disk1 DMA port (write) */ #define ints fdport + 2 /* Disk1 interrupt status port (read) */ /* 8272 COMMAND CODES */ #define c_rtk 0x02 /* read a track */ #define c_spec 0x03 /* specify */ #define c_dsts 0x04 /* sense drive status */ #define c_wrat 0x05 /* write data */ #define c_rdat 0x06 /* read data */ #define c_reca 0x07 /* recalibrate */ #define c_rsts 0x08 /* sense interrupt status */ #define c_rdid 0x0A /* read ID */ #define c_form 0x0D /* format */ #define c_seek 0x0F /* seek */ 8272 ERROR MASKS #define ds_err 0x03 /* incorrect disk select */ #define nr_err 0x08 /* not ready error */ #define eq_err 0x10 /* equipment error */ #define no_err 0xc0 /* no error */ /* GLOBAL VARIABLES */ /* globals have the form variable[drive] where drive is 0..3 */ char mt[4]; /* two side operation = 1, one side = 0 */ char mfm[4]; /* double density = 1, single density = 0 */ char sk[4]; /* skip (not used) always 0 */ char bps[4]; /* bytes per sector 0,1,2,3 for Godbout format */ char eot[4]; /* final sector number of track */ char gpl[4]; /* gap length */ char dtl[4]; /* data length */ int x,y; /* cursor coordinates used by iolib.c */ LISTING 6 /* Getresult() routine reads the results from a read,write,format or */ /* readid command returns TRUE for a good read or write */ int getresult(bytes) char bytes[]; { int k; /* byte counter */ for (k = 0 ; k < 7 ; ) { resultstat(); /* wait till ready for result byte */ bytes[k++] = inp(fdcdata); /* read result byte */ } return((bytes[1] == 0x80)); /* return TRUE if end of cylinder */ }/* getresult */ LISTING 7 /* DENSITY.C */ /* program to return number of sides, density and format of a disk */ #include bdscio.h #include cdisk.h main(argc,argv) char **argv; { char cyl,hds,drive; char bytes[8]; int i; printf("\ndensity version 1.3\n"); drive = getdrive(); /* request drive */ hds= 0; /* select head 0 */ cyl = 2; /* select cylinder 2 */ dseek(drive,cyl); /* position head */ getmt(drive); /* find number of sides */ readid(drive,hds,bytes); /* find density and format */ printf("\ndrive %c is ",(drive+'A')); if (mt[drive]) printf("Double Sided, "); else printf("Single sided, "); switch (n[drive]) { case 0 : printf("Single density with 128 Byte sectors\n"); break; case 1 : printf("Double density with 256 Byte sectors\n"); break; case 2 : printf("Double density with 512 Byte sectors\n"); break; case 3 : printf("Double density with 1024 Byte sectors\n"); } /* switch */ } /* density */ LISTING 8 /* segment of setparam() code */ else /* double density */ switch (n[drive]) { case 1 : gpl[drive] = 0x0e; /* gap length */ eot[drive] = 0x1a; /* last sector on track */ dtl[drive] = 0xff; /* no meaning in dd */ break; case 2 : gpl[drive] = 0x1b; /* gap length */ eot[drive] = 0x0f; /* last sector on track */ dtl[drive] = 0xff; /* no meaning in dd */ break; case 3 : gpl[drive] = 0x35; /* gap length */ eot[drive] = 0x08; /* last sector on track */ dtl[drive] = 0xff; /* no meaning in dd */ break; } /* case */ }/* setparam */ LISTING 9 /* Setrscmd() fills the 9 byte read command array */ setrscmd(rdcmd,drive,cyl,sector,sectors,hds) char rdcmd[],drive,cyl,sector,sectors,hds; { rdcmd[0] = (mfm[drive]<<6) | (sk[drive]<< 5) | c_rdat; rdcmd[1] = (hds << 2) | drive; /* head and drive data */ rdcmd[2] = cyl; /* cylinder number */ rdcmd[3] = hds; /* head */ rdcmd[4] = sector; /* first sector to read */ rdcmd[5] = n[drive]; /* disk format */ rdcmd[6] = sector + sectors - 1; /* last sector to read */ rdcmd[7] = gpl[drive]; /* gap length */ rdcmd[8] = dtl[drive]; }/* setrscmd */ LISTING 10 /* perform a read operation either sector or cylinder. Must have set up */ /* the rdcmd[] array before calling this function. Returns TRUE if read */ /* was successful FALSE if an error occured. */ int dread(rdcmd,bytes) char rdcmd[]; /* array contaning the read command */ char bytes[]; /* array to recieve the results */ { int j; /* byte counter */ for (j = 0; j < 9;j++) /* send 9 bytes */ { cmdstat(); /* wait till ready for command byte */ outp(fdcdata,rdcmd[j]); /* send read command byte */ } intstat(); /* wait till end of execution phase */ if(getresult(bytes)) return(TRUE); else { printf("\nread error drive %c head %u ", ( (bytes[0]&3)+'A' ),( (bytes[0]>>2) & 1) ); geterror(bytes); return(FALSE); } }/* dread */ LISTING 11 /* Geterror() prints an error message for an incorrect read or write */ /* command, detailing the error. Must call getresults(bytes) first. */ int geterror(bytes) char bytes[]; /* array containg the results bytes */ { if (( bytes[0] & 8) != 0) printf("not ready \n"); if (( bytes[1] & 0x7f) != 0) { if ((( bytes[1] & 1) != 0) && (( bytes[2] & 1) == 0)) printf("missing ID address mark\n"); if (( bytes[1] & 2 ) != 0 ) printf("write protected\n"); if (( bytes[1] & 4) != 0) printf("no data transfered \n"); if ((( bytes[1] & 0x20) != 0) && ((bytes[2] & 0x20) == 0)) printf("crc error in ID field\n"); } if (( bytes[2] != 0)) { if (( bytes[2] & 1) != 0) printf("missing data address mark\n"); if (( bytes[2] & 0x20) != 0) printf("crc error in data field\n"); if (( bytes[2] & 0x10) != 0) printf("wrong cylinder\n"); } return(TRUE); }/* geterror */ LISTING 12 /* Segment of setform() command that sets up format command sequence */ /* to be read by the controller as it formats each sector. */ for(sector=1,k = 0; sector<=eot[drive]; sector++) {/* do for each sector*/ buffer[k++] = cyl; /* cylinder */ buffer[k++] = hds; /* head (side) */ buffer[k++] = sector; /* sector */ buffer[k++] = n[drive]; /* format */ }/* for */ }/* setformat */ +-----------------------------------------------------------------+ | ****** NOTICE ****** | | Copyright 1985 by Micro/Systems Journal | | PO Box 1192, Mountainside, NJ 07092 : | All rights reserved, reproduction prohibited without permission : +-----------------------------------------------------------------+