overby@plains.NoDak.edu (Glen Overby) (09/06/90)
I have largely rewritten the XT Wini disk driver. It has already gone thru two beta releases with several of the Referees. I am releasing it to the net in a finished form, but with a fair ammount of debugging code still in the source, which can be compiled in by enabling some #defines. There are still many things I would like to do with the driver, but I feel it is fairly stable, and I don't seem to be getting around to doing what I want. If you get this driver working on your system, please drop me a note and tell me what make and model of controler, drive and CPU you have. I intend to keep a mini-compatability report for this driver, so I know where I stand compatability wise. If it Doesn't work on your system, try enabling some of the debugging information as described in the "Readme" file, and give me as much information about what it DOES and DOESN'T do on your system. Glen Overby echo x - Readme sed '/^X/s///' > Readme << '/' X 7/8/90 X 7/21/90 X 9/5/90 XRE: New XT Winchester Driver for MINIX X XThis is the README file for my rewrite of the Minix XT wini driver. It Xcurrently has a large number of debugging options (enabled with X#defines) which should probably be removed for a "production" driver. XI have attempted (and, I think, succeeded) in cleaning up the old Xdriver, making it more robust, and removed many outright bugs in the WD XAutoconfigure area. X XThe XT wini driver is one of those places that have had a large number Xof changes made by various people trying to fix many different Xcompatability problems. The result is a program with a lot of hacks in Xit, and more floating around the net which will fix other problems. XUnder DOS, these controler differences are hidden by the ROMs, but the XROMs do not support multitasking well, and will not run in protected Xmode, so a custom driver for MINIX is essential. X XI am attempting to write a driver which can easily adapt to a wider Xrange of the compatability issues than did the old XT driver. The new Xdriver is based on the old one, so on the surface they may look the Xsame, but if you look closer you will find that a majority (>75%) of the Xcode has been rewritten. X XPart of the reason I undertook this task is I have seen a lot of 'quick Xhack' solutions to compatability problems, such as changing the X"NR_SECTORS" #define for RLL controlers. As it frequently turns out, Xthis information is available in the ROM tables! The "AT" controler XROM has a field for the number of sectors per track. X XThere were many things I did not like about the design of the old Xdriver, among them is that the parameters for a drive was copied across Xall partitions. I have divided the Drive and Partition information Xinto two separate structures. Another very major design change I made Xis to have one set of routines that execute controler commands, the Xxt_* routines, whereas the old driver spread command execution out over Xa wide set of routines. I feel that my approach results in a driver Xwhich is potentially much more portable to other controlers (such as Xthe AT, Bios, and SCSI). This "feature" has not yet been exercised. X XThe biggest mess of all is in the driver initialization. Here I see Xseveral areas of problems, most of them caused by Western Digital controlers X(and I own one of these). Some WD ROMs (Rev H) use three switches for Xindicating which table to use, rather than two as IBM did. Furthermore, Xthere is the Auto Configure series of ROMs ("AutoConfigure", "SuperBios" and X"GEN2"). When the old driver was compiled with the "AUTO_BIOS" option, XTWO drive had to be specified in xt_wini.c, or the configuration would Xoverwrite areas of memory outside the driver! This generally fell somewhere Xwithin a tty buffer and was harmless, but nevertheless it was bad. X XControler Compatability X----------------------- X XI'd like to gather compatability information. I know of the following Xcontrolers: X X IBM X Western Digital X ROM table (pre-autoconfigure) X Autoconfigure Glen Overby X Super Bios X GEN2 X RLL X Adaptec (RLL) X OMTI Lars Fredrickson X Xebec (they still around?) X AT&T 6300 X X XOther WD-specific issues I have addressed, and which may be problems on Xsom RLL controlers, are: X XSome controlers execute self-tests after being reset. This can take Xnearly a second, and the driver will time-out on fast (80386) CPUs. WD Xcontrolers with the WD1015-14 control processor (an Intel 8049 with Xcustom ROM) exhibit this feature. One solution to this is to have huge Xtiming loops (which will wait more than 32,000 iterations, thus Xrequiring the use of several "ints" for counting), but I chose to use a Xsmaller timing loop (which should be enough when sending commands and Xdata to the controler) and then waiting on a clock timer for a couple Xmore seconds. X XWeirdnesses to Look For When Hacking on the Driver Yourself X----------------------------------------------------------- X XI encountered a lot of subtile "features" in working with this driver which XI feel I should pass along to others who want to try their hand at wini Xdriver hacking. A few of these are documented, while others are not. X X * When sending commands or data to the controler, always check the "REQ" Xbit first. Fast CPUs can send data faster than the controler can accept it, Xso if you don't watch "REQ", the controler will miss a lot. X X * Do NOT enable the DMA controler's DRQ line until the controler's DMA/IRQ Xhas been enabled. The WD controlers leave the lines "floating" (tri-stated) Xand will float "high". The DMA controler uses a "HIGH" state for DMA Request, Xso the controler will run right off and service your request before it ever Xexists. X X * Some controlers execute diagnosticts after RESET, which can take a LONG Xtime (up to a second). I use a timer interrupt to wait that long. X X * The WD Autoconfigure ROMs store their drive parameters in low memory (this Xhas changed on the GEN2). Minix will overwrite this area, but Bruce Evans Xadded support to copy this area before overwiriting it. That works OK if Xyou only have one drive, but if you have two drives, it's useless since the Xparameter area pointed to by INT 41 is valid for ONLY the last drive accessed. XI have found two adjacent drive parameter blocks, but I have verified that Xthe Autoconf ROM switches between those areas. What a mess. Hopefully this Xhas been fixed in the GEN2, which stores the drive parameters in the controler XROM area (apparently they have some RAM shadowing an area of the ROM). X XDebugging the new XT wini. X------------------------- X X[this was written to solve some problems that a few beta testers encountered] XSince Minix will boot at 20MHZ with all the debugging messages enabled, XI believe there is a problem of xt_wini not waiting for the controler Xlong enough in the right places. I have expanded the debugging #defines Xgreatly to facilitate diagnosing exactly where the race is coming in Xfrom. X XIn your first report, you talked of turning "DEBUG_L" on; there were Xreally two _L sections: _L and _L1. Did you ever turn _L1 on? I'm Xassuming you did, and that _L1 actually had more of an effect than did Xplain _L. X XWithout autoconfigure on, the driver should ALWAYS print the ROM Xparameter table. I would like to know if the "Sectors" field is set Xproperly for your drive (if it's 17 (0x11), then it is zero in your ROM Xtable and you'll still have to define NR_SECTORS as 25). If you need more Xtime to read the info, uncomment line 825 and the driver will delay for Xone minute (or whatever you change the 3600 to). X XMost of the dirty work in the driver is done in xt_do and xt_rw. Most Xof this will relate to those areas. X XThe first place I'm thinking of is when data is sent to the controller Xunder program control (and not DMA). This is used for sending Xcommands, configuration paraameters, and reading detailed error Xreturns. Define DEBUG_RW3 to test this. If that makes the system Xwork, note the timeout values. RW2 does about the same thing, but Xdoesn't print out the timeout value. You can actually use either (the XRW3 line was commented out in the first driver), but I would like to Xknow the timeout values. X XNext turn DEBUG_STAT on; this is in the routine which waits for status Xbits to enter a certain state. This routine will hit the clock when Xthe controler takes a long time (you'll see messages about this). If Xthat times out, it's bad news. I don't think this is the problem, so Xif you want to take a short-cut, combine this with the next two tries Xand back out if it starts working. X XLeaving RW{2 or 3} and STAT on, turn on DEBUG_RW1 (some trivial Xdiagnosticts) and DEBUG_RESET on. I don't really think it's a reset Xproblem, so you might want to combine this one with what's next, and Xwatch for the diagnosticts. X XGoing back to the "xt_do" routine, turn DEBUG_DMA on and it will add a Xlonger delay between when the wini controler is given the command and Xthe DMA controler is started. This is specific to commands that wait Xfor an interrupt; turn DEBUG_DO2 on to add the same for the programmed Xdata transfer areas. X XYou now have everything on that was on before with the DEBUG_L and DEBUG_L1. XIf the driver still doesn't work... umm... I dunno. If it does start working Xin a later step, start turning the DEBUG_*'s from previous steps off until Xit quits working. X XFuture Directions X----------------- X XThis is by no means the only things that this driver needs. I'd like Xto see the driver ported to other controlers (AT, ROM, SCSI, SMD), and XI think this should be relatively easy, since I have a small number of Xroutines which talk to the controler itself. There are some other Xfunctionality items that the driver could use: X X * ability to move the head to the landing cylinder when asked, X probably via an ioctl call (this is my sole use of DOS these days). X X * re-read the partition table when asked (like after FDISK modifies X the table) X X * get/set the drive parameters. Get is especially important to X FDISK, so that it can find the number of sectors per track, X heads, and cylinders. X X * Error logging. Keep a log of recent errors, and what the controler X returned. The error returns to Minix are much to generic to track X down a disk drive or controler problem. X X * support for multiple controlers. This includes both multiple (ok, X two -- that's all that's normally supported under DOS) XT X controlers, as well as multiple types of controlers in the system X (i.e. XT and SCSI). I might do the latter if I get a SCSI disk. X XGlen Overby <overby@plains.nodak.edu> X {ogicse, umn-cs, uunet}!plains!overby (UUCP) overby@plains (Bitnet) X overby@cobber.cord.edu (alternate internet) / echo x - xt2_wini.c sed '/^X/s///' > xt2_wini.c << '/' X/* This file contains a driver for IBM PC XT class winchester controllers. X * Also supports Western Digital (WX-2 and related) controllers. X * X * Original code written by Adri Koppes. X * Patches from Gary Oliver for use with the Western Digital WX-2. X * Patches from Harry McGavran for robust operation on turbo clones. X * Patches from Mike Mitchell for WX-2 auto configure operation. X * Major Rewrite by Glen Overby 4/90 X * X * Version: 1.5.10-2.3.A 8/4/90 X * Derived from xt_wini.c as of 1.5.10, X * 3rd author fix level X * No user fixes X * X * References: X * "Storage Management Products Handbook", 1986 Western Digital Corporation X * Specifically, the data sheets on the WD1002S-WX2 in section 6. X * X * The driver supports the following operations (using message format m2): X * X * m_type DEVICE PROC_NR COUNT POSITION ADRRESS X * ---------------------------------------------------------------- X * | DISK_READ | device | proc nr | bytes | offset | buf ptr | X * |------------+---------+---------+---------+---------+---------| X * | DISK_WRITE | device | proc nr | bytes | offset | buf ptr | X * ---------------------------------------------------------------- X * |SCATTERED_IO| device | proc nr | requests| | iov ptr | X * ---------------------------------------------------------------- X * X * The file contains one entry point: X * X * winchester_task: main entry when system is brought up X * X */ X X#include "kernel.h" X#include <minix/callnr.h> X#include <minix/com.h> X#include <minix/partition.h> X X/*#define DEBUG_CMD /* w_do_rdwt */ X/*#define DEBUG_DMA /* dma_set & other DMA mode in xt_do */ X/*#define DEBUG_RESET /* controler reset related */ X/*#define DEBUG_STAT /* stat_wait */ X/*#define DEBUG_DO1 /* xt_do, basics */ X/*#define DEBUG_DO2 /* xt_do, detail */ X/*#define DEBUG_RW1 /* xt_rw, basics */ X/*#define DEBUG_RW2 /* xt_rw, all bytes */ X/*#define DEBUG_RW3 /* xt_rw, controler delay */ X/*#define DEBUG_INIT /* Init time stuff */ X#define DEBUG_CONF /* system HW configuration */ X X#define MAX_DRIVES 2 /* Maximum of two drives supported */ X X/* I/O Ports used by winchester disk controler. */ X#define WIN_DATA 0x320 /* winchester disk controller data register */ X#define WIN_STATUS 0x321 /* winchester disk controller status register*/ X#define WIN_SELECT 0x322 /* winchester disk controller select port */ X#define WIN_DMA 0x323 /* winchester disk controller dma register */ X X/* I/O Ports used by DMA controler. */ X#define DMA_ADDR 0x006 /* port for low 16 bits of DMA address */ X#define DMA_TOP 0x082 /* port for top 4 bits of 20-bit DMA addr */ X#define DMA_COUNT 0x007 /* port for DMA count (count = bytes - 1) */ X#define DMA_M1 0x00B /* DMA mode register */ X#define DMA_M2 0x00C /* DMA byte pointer flip-flop */ X#define DMA_INIT 0x00A /* DMA request mask register (en/disable DRQ) */ X X/* Winchester disk controler status register bits */ X#define WST_REQ 0x001 /* Request bit */ X#define WST_INPUT 0x002 /* Data Input or Output mode X (Set if controller is writing to CPU) */ X#define WST_CMD 0x004 /* Command/Status mode */ X#define WST_BUSY 0x008 /* Set if controler is Busy */ X#define WST_DRQ 0x010 /* DMA request pending */ X#define WST_IRQ 0x020 /* Interrupt request pending */ X X/* Winchester disk controller commands. */ X#define WIN_RECALIBRATE 0x01 /* drive recalibrate */ X#define WIN_SENSE 0x03 /* return controler status */ X#define WIN_READ 0x08 /* drive read */ X#define WIN_WRITE 0x0A /* drive write */ X#define WIN_SPECIFY 0x0C /* set controler params */ X#define WIN_ECC_READ 0x0D /* read ecc length after a correctable ECC error */ X#define WIN_ERROR 0x02 /* command completion byte error field */ X X/* Error codes from XT Controller */ X#define ERR_OK 0x00 /* no error */ X#define ERR_SC 0x02 /* no SC* (select?) from drive */ X#define ERR_WFAULT 0x03 /* Write Fault */ X#define ERR_NOT_RDY 0x04 /* Drive Not Ready */ X#define ERR_TRK0 0x06 /* Track 0 Not Found */ X#define ERR_SEEKING 0x08 /* Drive Still Seeking */ X#define ERR_DATA 0x11 /* Uncorrectable Data Error */ X#define ERR_ADDR_MARK 0x12 /* Data Address Mark Not Found */ X#define ERR_SEEK 0x15 /* Seek Error */ X#define ERR_CORR 0x18 /* Correctable ECC Error */ X#define ERR_BADTRK 0x19 /* Track is flagged as BAD */ X#define ERR_INVALID 0x20 /* Invalid Command */ X#define ERR_SECT_ADDR 0x21 /* Illegal Sector Address */ X#define ERR_SECT_BUF 0x30 /* Sector Buffer Error (diags E0, E4) */ X#define ERR_ROM 0x31 /* Controler ROM Checksum (diags E4) */ X#define ERR_ECCPOLY 0x32 /* ECC generator failed (diags E4) */ X X/* xt_command / Win_DMA port parameters */ X/* NOTE: bits 0 and 1 correspond to the controler INT & DMA Mask port */ X#define DMA_INT 3 /* Command with dma and interrupt */ X#define INT 2 /* Command with interrupt, no dma */ X#define DMA_ 1 /* DMA part of DMA_INT */ X#define NO_DMA_INT 0 /* Command without dma and interrupt */ X#define PGM_READ 0x10 /* No DMA or Interrupt, read data manually */ X#define PGM_WRITE 0x20 /* No DMA or Interrupt, write data manually */ X X/* DMA channel commands. */ X#define DMA_READ 0x47 /* DMA read opcode */ X#define DMA_WRITE 0x4B /* DMA write opcode */ X X#define DMA_RESET_VAL 0x07 /* Disable DRQ */ X#define DMA_SET_VAL 0x03 /* Enable DRQ */ X X/* Parameters for the disk drive. */ X#define SECTOR_SIZE 512 /* physical sector size in bytes */ X#ifndef NR_SECTORS X/* For RLL drives NR_SECTORS has to be defined in the makefile or in config.h. X * There is some hope of getting it from the parameter table for these drives, X * and then this driver should use wn_maxsec like at_wini.c. X * Unfortunately it is not standard in XT parameter tables. X */ X#define NR_SECTORS 0x11 /* number of sectors per track */ X#endif X X/* Error codes */ X#define ERR -1 /* general error */ X X/* Miscellaneous. */ X#define MAX_ERRORS 4 /* how often to try rd/wt before quitting */ X#define MAX_RESULTS 4 /* max number of bytes controller returns */ X#define DEV_PER_DRIVE (1 + NR_PARTITIONS) /* whole drive & each partn */ X#define NR_DEVICES (MAX_DRIVES * DEV_PER_DRIVE) X#define MAX_WIN_RETRY 32000 /* max # times to try to output to WIN */ X#define MAX_WIN_RESET 240 /* max # clock ticks to wait for status */ X X#if AUTO_BIOS X#define AUTO_PARAM 0x1AD /* drive parameter table starts here in sect 0 */ X#define AUTO_ENABLE 0x10 /* auto bios enabled bit from status reg */ X/* some start up parameters in order to extract the drive parameter table */ X/* from the winchester. these should not need changed. */ X#define AUTO_CYLS 306 /* default number of cylinders */ X#define AUTO_HEADS 4 /* default number of heads */ X#define AUTO_RWC 307 /* default reduced write cylinder */ X#define AUTO_WPC 307 /* default write precomp cylinder */ X#define AUTO_ECC 11 /* default ecc burst */ X#define AUTO_CTRL 5 /* default winchester stepping speed byte */ X#endif X X/* Variables. */ XPUBLIC int using_bios = FALSE; /* this disk driver does not use the BIOS */ X XPRIVATE struct wini { /* main drive struct, one entry per partition */ X long wn_low; /* lowest cylinder of partition */ X long wn_size; /* size of partition in blocks */ X int wn_drive; /* drive number addressed */ X X /* Saved state from most recent command for this partition */ X int wn_procnr; /* which proc wanted this operation? */ X int wn_op; /* Operation (read/write) */ X int wn_count; /* byte count */ X vir_bytes wn_address; /* user virtual address */ X} wini[NR_DEVICES]; X Xstruct param { /* ROM Winchester disk parameter table */ X int cylinders; /* number of cylinders on the disk */ X char heads; /* number of heads in the drive */ X int reduced_wr; /* cylinder to start reduced write current */ X int wr_precomp; /* cylinder to start write precompensation */ X char max_ecc; /* maximum ECC burst length */ X char control; /* control byte */ X /* bits definition */ X /* 0-2 step option (XT) */ X /* 3 set if > 8 heads */ X /* 4 0 */ X /* 5 set if defect map on maxcyl+1*/ X /* 6 disable ECC retries */ X /* 7 disable access retries */ X char timeout; /* standard timeout (XT) */ X char ftimeout; /* formatting timeout (XT) */ X char chktimeout; /* drive check timeout (XT) */ X int landing; /* landing zone (AT) */ X char sectors; /* sectors per track (AT) */ X char zero; /* 0 */ X} drive[MAX_DRIVES]; X XPRIVATE int w_need_reset; /* TRUE when controller must be reset */ XPRIVATE int nr_drives; /* Number of drives */ X XPRIVATE message w_mess; /* message buffer for in and out */ X X/* Format of a controler command */ XPRIVATE struct xt_cmd { X char op, /* OP code (read, write, etc) */ X headdr, /* Head and Drive */ X sector, /* sector of the cylinder */ X cyl, /* cylinder of the drive */ X count, /* number of sectors to transfer */ X ctl; /* control byte (head step rate, auto error retry */ X}; X X/* Format of controler drive parameters */ XPRIVATE struct xt_param { X char cyl_h, cyl_l; /* Max Cylinders */ X char heads; /* Heads */ X char rwr_h, rwr_l; /* Reduced Write */ X char wpre_h, wpre_l; /* Write Precompensation */ X char ecc; /* Max ECC Data burst length */ X}; X XPRIVATE unsigned char buf[BLOCK_SIZE] = { 0 }; X /* Buffer used by the startup routine */ X /* Initialized to try to avoid DMA wrap. */ X XFORWARD int w_do_rdwt(); XFORWARD void w_dma_setup(); XFORWARD int w_reset(); XFORWARD void w_wait_int(); XFORWARD int win_specify(); XFORWARD int read_ecc(); XFORWARD int xt_command(); XFORWARD int xt_do(); XFORWARD int xt_rw(); XFORWARD int stat_wait(); XFORWARD int w_clock_mess(); XFORWARD void st_check(); XFORWARD void init_params(); XFORWARD void rom_conf(); XFORWARD void wd_autoconf(); XFORWARD void copy_param(); XFORWARD void copy_prt(); XFORWARD void sort(); XFORWARD void cp(); X X/*=========================================================================* X * winchester_task * X *=========================================================================*/ XPUBLIC void winchester_task() X{ X/* Main program of the winchester disk driver task. */ X X int r, caller, proc_nr; X X /* First initialize the controller */ X init_params(); X X /* Here is the main loop of the disk task. It waits for a message, carries X * it out, and sends a reply. X */ X X while (TRUE) { X /* First wait for a request to read or write a disk block. */ X receive(ANY, &w_mess); /* get a request to do some work */ X if (w_mess.m_source < 0) { X printf("winchester task got message from %d\n", w_mess.m_source); X continue; X } X caller = w_mess.m_source; X proc_nr = w_mess.PROC_NR; X X /* Now carry out the work. */ X switch(w_mess.m_type) { X case DISK_READ: X case DISK_WRITE: r = w_do_rdwt(&w_mess); break; X case SCATTERED_IO: r = do_vrdwt(&w_mess, w_do_rdwt); break; X default: r = EINVAL; break; X } X X /* Finally, prepare and send the reply message. */ X w_mess.m_type = TASK_REPLY; X w_mess.REP_PROC_NR = proc_nr; X X w_mess.REP_STATUS = r; /* # of bytes transferred or error code */ X send(caller, &w_mess); /* send reply to caller */ X } X} X X X/*==========================================================================* X * w_do_rdwt * X *==========================================================================*/ XPRIVATE int w_do_rdwt(m_ptr) Xmessage *m_ptr; /* pointer to read or write w_message */ X{ X/* Carry out a read or write request from the disk. */ X register struct wini *wn; /* partition */ X register struct param *dr; /* drive */ X int r, device, errors; X int sec, cyl, head; X long sector; X X struct xt_cmd command; X X /* Decode the w_message parameters. */ X device = m_ptr->DEVICE; X if (device < 0 || device >= NR_DEVICES) X return(EIO); X if (m_ptr->COUNT != BLOCK_SIZE) X return(EINVAL); X X wn = &wini[device]; /* 'wn' points to entry for this partition */ X dr = &drive[wn->wn_drive]; /* 'dr' points to entry for this drive */ X X if (m_ptr->POSITION % BLOCK_SIZE != 0) X return(EINVAL); X sector = m_ptr->POSITION/SECTOR_SIZE; X if ((sector+BLOCK_SIZE/SECTOR_SIZE) > wn->wn_size) X return(0); X sector += wn->wn_low; /* offset from the beginning of the partition */ X wn->wn_op = m_ptr->m_type; /* DISK_READ or DISK_WRITE */ X wn->wn_address = (vir_bytes) m_ptr->ADDRESS; /* for w_dma_setup */ X wn->wn_procnr = m_ptr->PROC_NR; X wn->wn_count = m_ptr->COUNT; X cyl = sector / (dr->heads * dr->sectors); X sec = (sector % dr->sectors); X head = ((sector % (dr->heads * dr->sectors) ) / dr->sectors); X command.op = (m_ptr->m_type == DISK_READ ? WIN_READ : WIN_WRITE); X command.cyl = cyl & 0xff; X command.sector = ((cyl & 0x300) >>2) | sec; X command.headdr = head | (wn->wn_drive << 5); X command.count = BLOCK_SIZE / SECTOR_SIZE; X command.ctl = dr->control; X X#ifdef DEBUG_CMD X printf("Sector %l Cyl=%d Sec=%d Head=%d\n", sector, cyl, sec, head); X printf("Cmd Op: 0x%x Drive: %d\nCmd Head: %d Sect: %d Cyl: %d Count: %d Ctl: 0x%x\n", X command.op, (command.headdr & 0x20)>>5, command.headdr & 0x0f, X command.sector, command.cyl, command.count, command.ctl); X#endif X X /* This loop allows a failed operation to be repeated. */ X for(errors = MAX_ERRORS; errors; errors--) { X if (w_need_reset) w_reset(); X X w_dma_setup(wn); /* Now set up the DMA chip. */ X X /* Perform the transfer. */ X if((r = xt_command(&command, DMA_INT, 0, 0)) == ERR_CORR) { X /* Correctable ECC error, read ECC burst length */ X r = read_ecc(); X /* do we read the data NOW, or has it already been read? */ X printf("\nECC Corr (%d) Drive=%d Cyl=%d Head=%d Sect=%d\n", X r, command.headdr & 0x20, command.cyl, X command.headdr & 0x0f, command.sector); X r = OK; X break; X } else { X if(r != OK) X printf("\nDisk Error (0x%x) Drive=%d Cyl=%d Head=%d Sect=%d\n", X r, command.headdr & 0x20, command.cyl, X command.headdr & 0x0f, command.sector); X } X if (r == OK) break; /* if successful, exit loop */ X } X X return((r == OK) ? BLOCK_SIZE : EIO); X} X X X/*==========================================================================* X * w_dma_setup * X *==========================================================================*/ XPRIVATE void w_dma_setup(wn) Xstruct wini *wn; /* pointer to the drive struct */ X{ X/* The IBM PC can perform DMA operations by using the DMA chip. To use it, X * the DMA (Direct Memory Access) chip is loaded with the 20-bit memory address X * to by read from or written to, the byte count minus 1, and a read or write X * opcode. This routine sets up the DMA chip. Note that the chip is not X * capable of doing a DMA across a 64K boundary (e.g., you can't read a X * 512-byte block starting at physical address 65520). X */ X X int mode, low_addr, high_addr, top_addr, low_ct, high_ct, top_end; X vir_bytes vir, ct; X phys_bytes user_phys; X X mode = (wn->wn_op == DISK_READ ? DMA_READ : DMA_WRITE); X vir = (vir_bytes) wn->wn_address; X ct = (vir_bytes) wn->wn_count; X user_phys = numap(wn->wn_procnr, vir, ct); X low_addr = (int) user_phys & BYTE; X high_addr = (int) (user_phys >> 8) & BYTE; X top_addr = (int) (user_phys >> 16) & BYTE; X low_ct = (int) (ct - 1) & BYTE; X high_ct = (int) ( (ct - 1) >> 8) & BYTE; X X /* Check to see if the transfer will require the DMA address counter to X * go from one 64K segment to another. If so, do not even start it, since X * the hardware does not carry from bit 15 to bit 16 of the DMA address. X * Also check for bad buffer address. These errors mean FS contains a bug. X */ X if (user_phys == 0) X panic("FS gave winchester disk driver bad addr", (int) vir); X top_end = (int) (((user_phys + ct - 1) >> 16) & BYTE); X if (top_end != top_addr) X panic("Trying to DMA across 64K boundary", top_addr); X X /* Now set up the DMA registers. */ X#ifdef DEBUG_DMA X printf("DMA Set: DMA_M2,M1 = 0x%x ", mode); /* set the DMA mode */ X printf("DMA_ADDR(l) = 0x%x ", low_addr); /* output low-order 8 bits */ X printf("DMA_ADDR(h) = 0x%x\n", high_addr);/* output next 8 bits */ X printf("DMA Set: DMA_TOP = 0x%x ", top_addr); /* output highest 4 bits */ X printf("DMA_COUNT(l) = 0x%x ", low_ct); /* output low 8 bits of count - 1 */ X printf("DMA_COUNT(h) = 0x%x\n", high_ct);/* output high 8 bits of count - 1 */ X#endif X out_byte(DMA_INIT, DMA_RESET_VAL); /* Disable DRQ */ X out_byte(DMA_M2, mode); /* set the DMA mode */ X out_byte(DMA_M1, mode); /* set it again */ X out_byte(DMA_ADDR, low_addr); /* output low-order 8 bits */ X out_byte(DMA_ADDR, high_addr);/* output next 8 bits */ X out_byte(DMA_TOP, top_addr); /* output highest 4 bits */ X out_byte(DMA_COUNT, low_ct); /* output low 8 bits of count - 1 */ X out_byte(DMA_COUNT, high_ct); /* output high 8 bits of count - 1 */ X/* out_byte(DMA_INIT, DMA_SET_VAL); /* initialize DMA -- NOT HERE!! */ X} X X X/*===========================================================================* X * w_reset * X *===========================================================================*/ XPRIVATE int w_reset() X{ X/* Issue a reset to the controller. This is done after any catastrophe, X * like the controller refusing to respond. X */ X int r, i; X struct xt_cmd command; X X#ifdef DEBUG_RESET X printf("Wini RESET\n"); X#endif X out_byte(WIN_STATUS, 0); /* Strobe reset bit low. */ X X for(i = MAX_WIN_RETRY/10; i; --i) X ; /* Spin loop for a while */ X out_byte(WIN_SELECT, 0); /* Issue a select to the ctlr */ X X i=stat_wait(WST_BUSY | WST_CMD | WST_REQ); X X if (i != OK) { /* timeout */ X printf("Hard disk won't reset, status = %x\n", r); X return(ERR); X } X X /* Reset succeeded. Tell WIN drive parameters. */ X w_need_reset = FALSE; X X for (i=0; i<nr_drives; i++) { X if(win_specify(i, &drive[i]) != OK) X return (ERR); X X command.op = WIN_RECALIBRATE; X command.headdr = i << 5; X command.ctl = drive[i].control; X if ((r=xt_command(&command, INT, 0, 0)) != OK) { X printf("Reset: recalibrate error 0x%s\n", r); X return(ERR); X } X } X return(OK); X} X X X/*=========================================================================* X * win_specify * X *=========================================================================*/ XPRIVATE int win_specify(drive, paramp) X int drive; X struct param *paramp; X{ struct xt_cmd command; X struct xt_param config; X int r; X X command.op = WIN_SPECIFY; /* Specify some parameters */ X command.headdr = drive << 5; /* Drive number */ X X config.cyl_h = paramp->cylinders >> 8;/* No. of cylinders (high byte) */ X config.cyl_l = paramp->cylinders; /* No. of cylinders (low byte) */ X config.heads = paramp->heads; /* No. of heads */ X config.rwr_h = paramp->reduced_wr >> 8;/* Start reduced write (high byte) */ X config.rwr_l = paramp->reduced_wr; /* Start reduced write (low byte) */ X config.wpre_h= paramp->wr_precomp >> 8;/* Start write precompensation (high byte) */ X config.wpre_l= paramp->wr_precomp; /* Start write precompensation (low byte) */ X config.ecc = paramp->max_ecc; /* Ecc burst length */ X X if((r=xt_command(&command, PGM_WRITE, 8, &config)) != OK) { X printf("Win_Specify Error: 0x%x\n", r); X w_need_reset = TRUE; X return(ERR); X } else X return(OK); X} X X/*=========================================================================* X * read_ecc * X *=========================================================================*/ XPRIVATE int read_ecc() X{ X/* Read the ecc burst-length and let the controller correct the data */ X/* Is this really necessary? I don't think so --GpO */ X struct xt_cmd command; X X command.op = WIN_ECC_READ; X if (xt_command(&command, PGM_READ, 1, buf) != OK) { X return(-1); /* error reading ECC burst length!! */ X } X return(buf[0]); X} X X X/*=========================================================================* X * stat_wait * X *=========================================================================*/ X/* Wait for the controler status to change to some setting */ XPRIVATE int stat_wait(s) X int s; X{ int i; X void st_check(); X X for (i = MAX_WIN_RETRY; i ; i--) { X if((in_byte(WIN_STATUS) & s) == s) { X return(OK); X } X } X X/* The controler did not select quickly (it might be a WD that does controler Xdiagnostics when reset) so set a clock alarm and check it later. X XThe method for delaying on clock stolen from floppy.c. Delaying on the clock Xis required because fast CPUs can timeout a signed int much quicker than the Xcontroler can complete it's diagnostics. Software timing loops are never Xa good thing to use, but for quick loops (like transfering data to the Xcontroler and a fast select, etc) there is more overhead in setting up the Xclock than just using a spin loop. X XThe approach I take here is to spin for one "RETRY" time (which should be Xshort) then start delaying on the clock (in 2/60 intervals). X*/ X#ifdef DEBUG_STAT X printf("Stat_Wait: on Clock\n"); X#endif X for (i = MAX_WIN_RESET; i ; i--) { X if((in_byte(WIN_STATUS) & s) == s) { X#ifdef DEBUG_STAT X printf("Stat_Wait: Took %d ticks\n", i); X#endif X return(OK); X } X w_clock_mess(2, st_check); X receive(CLOCK, &w_mess); X } X#ifdef DEBUG_STAT X printf("Stat_Wait: Timed OUT! 0x%x\n", in_byte(WIN_STATUS)); X#endif X return(ERR); X} X X/*===========================================================================* X * w_clock_mess * X *===========================================================================*/ X/* Stolen from floppy.c */ XPRIVATE int w_clock_mess(ticks, func) Xint ticks; /* how many clock ticks to wait */ Xvoid (*func)(); /* function to call upon time out */ X{ X/* Send the clock task a message. */ X X w_mess.m_type = SET_ALARM; X w_mess.CLOCK_PROC_NR = WINCHESTER; X w_mess.DELTA_TICKS = (long) ticks; X w_mess.FUNC_TO_CALL = func; X sendrec(CLOCK, &w_mess); X} X X/*===========================================================================* X * st_check * X *===========================================================================*/ X/* stolen from floppy.c */ XPRIVATE void st_check() X{ X/* This routine is called when the clock task has timed out on motor startup.*/ X X send(WINCHESTER, &w_mess); X} X X/*=========================================================================* X * xt_command * X *=========================================================================*/ XPRIVATE int xt_command(command, mode, count, address) X struct xt_cmd *command; X int mode, count; X char *address; X{ struct xt_cmd stat; X int rc; X X/* Execute a command and return a full error report if there was an error */ X rc = xt_do(command, mode, count, address); X X if (rc != OK) { /* Error bit set! X Issue a "Return Controler Status" command for details */ X stat.op = WIN_SENSE; X stat.headdr = command->headdr & 0x20; X xt_do(&stat, PGM_READ, 4, buf); X return(buf[0]); X } X return(OK); X} X X/*=========================================================================* X * xt_do * X *=========================================================================*/ XPRIVATE int xt_do(command, mode, count, address) X char *command; X int mode, count; X char *address; X{ X/* Issue a command to the winchester controler and return the completion X status. If data is to be transfered with programmed I/O, do that, too. X Otherwise rely on the DMA controler (which must be already set up). X X If interrupts/dma are used, handle enabling/disabling of DRQ/INT on the X controler, and DMA IRQ in the DMAC. X X If an error occurs, just return the fact that it has and let the caller X (usually xt_command) worry about issueing a "read status of last operation" X comand (using this routine again) and a read_ecc_burst. X */ X X int rc; /* Retry Count & Return Code */ X int c, d, v; X X#ifdef DEBUG_DO1 X printf("xt_do: command = 0x%x, mode 0x%x, %d long\n", command[0], mode, count); X#endif X X out_byte(WIN_DMA, mode); /* enable DRQ and IRQ on controler */ X out_byte(WIN_SELECT, mode); X X rc = stat_wait(WST_BUSY); /* wait for controler to ack select */ X X if (rc != OK) { X w_need_reset = TRUE; X return(ERR); X } X X /* command to controler */ X rc = xt_rw(WST_BUSY | WST_CMD | WST_INPUT | WST_REQ, X WST_BUSY | WST_CMD | WST_REQ, 6, command); X if(rc == ERR) printf("xt_do: Command Output error!\n"); X X if (mode & INT) { /* (no data) wait for interrupt at the end of operation */ X#ifdef DEBUG_DO2 X printf("xt_do: wait-int"); X#endif X if(mode & DMA_) { X out_byte(DMA_INIT, 3); /* allow DRQ in DMA controler */ X#ifdef DEBUG_DMA X printf("..dma..\n"); X } else { printf("..no dma..\n"); X#endif X } X receive(HARDWARE, &w_mess); X X if(mode & DMA_) X out_byte(DMA_INIT, 0x07); /* Disable int from DMA */ X out_byte(WIN_DMA, 0); /* Disable DRQ and INT */ X cim_xt_wini(); X } else if (mode & PGM_READ) { /* read from controler */ X#ifdef DEBUG_DO2 X printf("xt_do: read\n"); X#endif X rc = xt_rw(WST_BUSY | WST_CMD | WST_INPUT | WST_REQ, X WST_BUSY | WST_INPUT | WST_REQ, X count, address); X } else if (mode & PGM_WRITE) { /* write to controler */ X#ifdef DEBUG_DO2 X printf("xt_do: write\n"); X#endif X rc = xt_rw(WST_BUSY | WST_CMD | WST_INPUT | WST_REQ, X WST_BUSY | WST_REQ, X count, address); X } X X rc = stat_wait(WST_BUSY | WST_CMD | WST_INPUT | WST_REQ); X X rc = in_byte(WIN_DATA); X#ifdef DEBUG_DO1 X printf("xt_do: RC 0x%x\n", rc); X#endif X/*w_clock_mess(240, st_check); receive(CLOCK, &w_mess);*/ X X return(OK); X} X X/*=========================================================================* X * xt_rw * X *=========================================================================*/ XPRIVATE int xt_rw(mask, bits, count, addr) X int mask, bits, count; X char *addr; X{ X/* Send or receive commands and data to and from the controler. X Every time a byte is sent, we must wait for the "REQ" bit to be set. X We also must watch for an incorrect change of state (C/D, I/O) in the X controler in case it is doing something we don't want it to be. X */ X int rc; /* Retry Count */ X X#ifdef DEBUG_RW1 X printf("xt_rw: mask=0x%x bits=0x%x count=%d addr=0x%x\n", mask, bits, count, addr); X#endif X X while (count--) { X for (rc=MAX_WIN_RETRY; X rc && ((in_byte(WIN_STATUS) & mask) != bits); rc--) X /* Wait for controler to get in the right mode */ X ; X if (!rc) { /* oops! timed out. Reset and try again */ X#ifdef DEBUG_RW1 X printf("xt_rw: timed out 0x%x\n", in_byte(WIN_STATUS)); X#endif X w_need_reset = TRUE; X return(ERR); X } X#ifdef DEBUG_RW3 X printf("xt_rw: Waited %d (0x%x)\n", MAX_WIN_RETRY - rc, *addr ); X#endif X#ifdef DEBUG_RW2 X printf(" 0x%x ", *addr); X#endif X X if (bits & WST_INPUT) X *addr++ = in_byte(WIN_DATA); X else X out_byte(WIN_DATA,*addr++); X } X return(OK); X} X X X/*=========================================================================* X * init_params * X *=========================================================================*/ X X/* Initialization Process XFor ROM-table controlers: X look at the switch settings (some WD ROMs use more switches than IBM) X copy the table from ROM, based on the switch settings. X XFor WD Autoconfigure controlers: X read sector 0 and get the parameters from the 17 bytes preceeding the X partition table X XSet parameters in "wini" structure from (rom) "param" structure. X XRead partition table of each drive X X*/ X XPRIVATE void init_params() X{ X/* This routine is called at startup to initialize the partition table, X * the number of drives and the controller X */ X unsigned int i, segment, offset; X int type_0, type_1; X X /* Get the number of drives from the bios */ X phys_copy(0x475L, umap(proc_ptr, D, buf, 1), 1L); X nr_drives = *buf & 0xFF; X if (nr_drives > MAX_DRIVES) nr_drives = MAX_DRIVES; X X#ifdef DEBUG_CONF X printf("%d Winchester Drives\n", nr_drives); X#endif X X /* Read the switches from the controller */ X i = in_byte(WIN_SELECT); X X#if WINI_EXT_SW X /* More WD compatability fun: WD "G" ROMs use THREE switches to indicate X which configuration table to use. */ X type_0 = (i & 0x03) | (i & 0x40 >>4); X type_1 = ((i & 0x0c) >> 2) | ((i & 0x80) >> 5); X#else X /* Calculate the drive types */ X type_0 = i & 3; X type_1 = (i >> 2) & 3; X#endif X X#if AUTO_BIOS X /* Get the drive parameters from sector zero of the drive if the X autoconfig mode of the controller has been selected */ X if(i & AUTO_ENABLE) { X wd_autoconf(nr_drives, type_0, type_1); X } else { X#endif /* AUTO_BIOS */ X X rom_conf(nr_drives, type_0, type_1); X X#if AUTO_BIOS X } X#endif X X#ifdef DEBUG_CONF X /* Print Drive Table */ X prdrive(&drive[0],0); prdrive(&drive[1],1); X/*w_clock_mess(3600, st_check); receive(CLOCK, &w_mess);*/ X /* ----- ----- ----- */ X#endif X X wini[0].wn_low = 0L; X wini[0].wn_size = (long)((long)drive[0].cylinders * X (long)drive[0].heads * (long) drive[0].sectors); X X if(nr_drives > 1) { X wini[DEV_PER_DRIVE].wn_drive = 1; X wini[DEV_PER_DRIVE].wn_low = 0L; X wini[DEV_PER_DRIVE].wn_size = (long)((long)drive[1].cylinders * X (long)drive[1].heads * (long) drive[1].sectors); X } X X /* Initialize the controller */ X cim_xt_wini(); /* ready for XT wini interrupts */ X if ((nr_drives > 0) && (w_reset() != OK)) nr_drives = 0; X X /* Read the partition table for each drive and save them */ X for (i = 0; i < nr_drives; i++) { X w_mess.DEVICE = i * DEV_PER_DRIVE; X w_mess.POSITION = 0L; X w_mess.COUNT = BLOCK_SIZE; X w_mess.ADDRESS = (char *) buf; X w_mess.PROC_NR = WINCHESTER; X w_mess.m_type = DISK_READ; X if (w_do_rdwt(&w_mess) != BLOCK_SIZE) { X printf("Can't read partition table of winchester %d\n", i); X continue; X } X X copy_prt(i, &wini[i * DEV_PER_DRIVE + 1], &buf[PART_TABLE_OFF]); X X#ifdef DEBUG_CONF X /* Print Partition Table */ X prpart(&buf[PART_TABLE_OFF]); X /* ----- --------- ----- */ X /* Print Wini Table */ X prwini(&wini[i * DEV_PER_DRIVE]); X /* ----- ---- ----- */ X /*w_clock_mess(3600, st_check); receive(CLOCK, &w_mess);*/ X#endif X } X} X X X/*=========================================================================* X * rom_conf * X *=========================================================================*/ XPRIVATE void rom_conf(nr_drives, type_0, type_1) X int nr_drives, type_0, type_1; X{ X unsigned int segment, offset; X phys_bytes address; X X /* Copy the parameter vector from the saved vector table */ X offset = vec_table[2 * WINI_0_PARM_VEC]; X segment = vec_table[2 * WINI_0_PARM_VEC + 1]; X X /* Calculate the address off the parameters and copy them to buf */ X address = hclick_to_physb(segment) + offset; X phys_copy(address, umap(proc_ptr, D, buf, 64), 64L); X X /* Copy the parameters to the structures */ X copy_param(&buf[type_0 * 16], &drive[0]); X if (nr_drives > 1) copy_param(&buf[type_1 * 16], &drive[1]); X} X X#if AUTO_BIOS X/*=========================================================================* X * wd_autoconf * X *=========================================================================*/ XPRIVATE void wd_autoconf(nr_drives, type_0, type_1) X int nr_drives, type_0, type_1; X{ int rc; X/* set up some phoney parameters so that we can read the first X sector from the winchester. All drives will have one cylinder X and one head, but set up initially to the mini scribe drives X from ibm. */ X X drive[0].cylinders = AUTO_CYLS; drive[0].heads = AUTO_HEADS; X drive[0].reduced_wr = AUTO_RWC; drive[0].max_ecc= AUTO_ECC; X drive[0].wr_precomp = AUTO_WPC; drive[0].control= AUTO_CTRL; X drive[0].sectors = NR_SECTORS; X X wini[0].wn_low = 0L; wini[0].wn_drive= 0; X wini[0].wn_size =(long)AUTO_CYLS * (long)AUTO_HEADS * (long)NR_SECTORS; X X if(nr_drives > 1) { X drive[1] = drive[0]; X wini[DEV_PER_DRIVE] = wini[0]; X wini[DEV_PER_DRIVE].wn_drive = 1; /* set drive number */ X } X X cim_xt_wini(); /* ready for XT wini interrupts */ X if(w_reset() != OK) { X printf("cannot setup for reading winchester parameter tables"); X nr_drives = 0; X } X X /* generate the request to read the first sector from the wini 0 */ X w_mess.DEVICE = 0; w_mess.POSITION = 0L; X w_mess.COUNT = BLOCK_SIZE; w_mess.ADDRESS = (char *) buf; X w_mess.PROC_NR = WINCHESTER; w_mess.m_type = DISK_READ; X if((rc=w_do_rdwt(&w_mess)) != BLOCK_SIZE) X panic("cannot read drive parameters from winchester 0", rc); X X /* copy the parameter tables into the structures for later use */ X copy_param(&buf[AUTO_PARAM], &drive[0]); X X if (nr_drives > 1) { X /* generate the request to read the first sector from the winchester */ X /*cp(wini[DEV_PER_DRIVE], wini[0], sizeof(struct wini));*/ X w_mess.DEVICE = DEV_PER_DRIVE; w_mess.POSITION = 0L; X w_mess.COUNT = BLOCK_SIZE; w_mess.m_type = DISK_READ; X w_mess.ADDRESS = (char *) buf; w_mess.PROC_NR = WINCHESTER; X if((rc=w_do_rdwt(&w_mess)) != BLOCK_SIZE) X panic("cannot read drive parameters from winchester 5", rc); X /* copy the parameter tables into the structures for later use */ X copy_param(&buf[AUTO_PARAM], &drive[1]); X } X} X#endif /* AUTO_BIOS */ X X/*=========================================================================* X * copy_param * X *=========================================================================*/ XPRIVATE void copy_param(src, dest) Xregister unsigned char *src; Xregister struct param *dest; X{ X/* This routine copies the disk parameter table to the local "param" X structure (actually, a simple byte copy should suffice) and checks X some parameters. X X If the number of sectors per track is zero, set it to NR_SECTORS */ X X/* cp(dest, src, sizeof(struct param));*/ X#ifdef DEBUG_INIT X printf("Param %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", X src[0], src[1], src[2], src[3], src[4], src[5], src[6], src[7], src[8], X src[9], src[10], src[11], src[12], src[13], src[14], src[15]); X#endif X X dest->cylinders = *(u16_t *)src; X dest->heads = src[2]; X dest->reduced_wr = *(u16_t *)&src[3]; X dest->wr_precomp = *(u16_t *)&src[5]; X dest->max_ecc = src[7]; X dest->control = src[8]; X dest->timeout = src[9]; X dest->ftimeout= src[10]; X dest->chktimeout= src[11]; X dest->landing = *(u16_t *)src[12]; X dest->sectors = src[14]; X X if(dest->sectors == 0) dest->sectors = NR_SECTORS; X /*w_clock_mess(480, st_check); receive(CLOCK, &w_mess);*/ X} X X/*==========================================================================* X * copy_prt * X *==========================================================================*/ XPRIVATE void copy_prt(dev, win, part) X int dev; X struct wini *win; X struct part_entry *part; X{ X/* This routine copies the partition table for the selected drive to X * the variables wn_low and wn_size X */ X X register struct wini *wn; X register struct part_entry *pe; X int i; X X wn = win; X pe = part; X X wn->wn_drive = dev; X for (i=0; i < NR_PARTITIONS ; i++, pe++, wn++) { X X wn->wn_drive = dev; X wn->wn_low = pe->lowsec; X wn->wn_size = pe->size; X X /* Adjust low sector to a multiple of (BLOCK_SIZE/SECTOR_SIZE) for old X * Minix partitions only. We can assume the ratio is 2 and round to X * even, which is slightly simpler. X */ X if (pe->sysind == OLD_MINIX_PART && wn->wn_low & 1) { X ++wn->wn_low; X --wn->wn_size; X } X } X sort(win); X} X X/*=========================================================================* X * sort * X *=========================================================================*/ XPRIVATE void sort(wn) Xregister struct wini wn[]; X{ X register int i,j; X struct wini tmp; X X for (i = 0; i < NR_PARTITIONS; i++) X for (j = 0; j < NR_PARTITIONS-1; j++) X if ((wn[j].wn_low == 0 && wn[j+1].wn_low != 0) || X (wn[j].wn_low > wn[j+1].wn_low && wn[j+1].wn_low != 0)) { X tmp = wn[j]; X wn[j] = wn[j+1]; X wn[j+1] = tmp; X } X} X X/*=========================================================================* X * cp * X *=========================================================================*/ XPRIVATE void cp(dest, src, count) X register char *dest, *src; X register int count; X{ X while(count--) X *dest++ = *src++; X} X X#ifdef DEBUG_CONF Xprdrive(p, drive) X struct param *p; X int drive; X{ X printf("Drive %d\n", drive); X#define BIT(A,B) ((A&B)?"1":"0") X printf("%-12s%-12s%-12s%-12s%-12s\n","","","Reduced","Write",""); X printf("%-12s%-12s%-12s%-12s%-12s\n","Cylinders:","Total","Write","Precomp","Landing"); X printf("%-12s%-12u%-12u%-12u%-12u\n","", p->cylinders, p->reduced_wr, p->wr_precomp, p->landing); X/* printf("%-12s%-12s%-12s%-12s\n","Timeout","Standard","Format","Check"); X printf("%-12s%-12d%-12d%-12d\n","", p->timeout, p->ftimeout, p->chktimeout);*/ X printf("%-12s%-12s%-12s%-12s\n","","Heads","Max ECC","Sectors"); X printf("%-12s%-12d%-12d%-12d\n","", p->heads, p->max_ecc, p->sectors); X X printf("%-12s%-6s%-6s%-12s%-12s%-12s\n","Control","ACC","ECC","Defect",">8 Heads","StepOp"); X printf("%-12s%-6s%-6s%-12s%-12s%-12d\n\n","", X BIT(p->control, 0x80), BIT(p->control, 0x40), X BIT(p->control, 0x20), BIT(p->control, 0x08), X (p->control & 0x07)); X} X X Xprpart(pp) X struct part_entry *pp; X{ int i; X for(i=0;i<NR_PARTITIONS;i++, pp++) X printf("[%d] %02x %02x %02x %02x %02x %02x %02x %02x %l %l\n", i, X pp->bootind, pp->start_head, pp->start_sec, pp->start_cyl, X pp->sysind, pp->last_head, pp->last_sec, pp->last_cyl, X pp->lowsec, pp->size); X} X Xprwini(wn) X struct wini *wn; X{ X int i; X printf("Wini Struct...\n"); X for(i=0; i < DEV_PER_DRIVE; i++, wn++) { X printf("[%d] Low %ld Size=%ld Drive=%d\n", i, wn->wn_low, wn->wn_size, wn->wn_drive); X } X} X#endif / -- Glen Overby <overby@plains.nodak.edu> uunet!plains!overby (UUCP) overby@plains (Bitnet)