schwenk@tellab5.UUCP (Mark Schwenk) (07/18/87)
I previously posted a bug report for at_wini.c which does not seem to have been widely distributed (it does not appear in the minix archives on nosc). There has recently been a request for help with this problem, so I am reposting my previous article. >The hard disk driver always tries to read BLOCK_SIZE/SECTOR_SIZE (2) sectors >at a time. This does not work if you are using a hard disk which does not >have a multiple of BLOCK_SIZE/SECTOR_SIZE sectors per cylinder. > >For example, my disk has 5 heads with 17 sectors per track, giving 85 sectors >per cylinder. With the default BLOCK_SIZE/SECTOR_SIZE of 2, the driver would >fail with an unrecoverable disk error when trying to read 1024 bytes starting >at head 4, sector 17. (Heads are numbered 0-4 and sectors are numbered 1-17 >for this example.) > >My temporary fix for this problem was to change a line in copy_params(). >I changed > dest[i].wn_maxsec = (int)src[14]; >to read > dest[i].wn_maxsec = ((int)src[14]>>1)<<1; >This changes my drive from 17 sectors per track to 16. Note that this is >just a temporary fix which I made to allow me to use the hard disk as soon >as possible. A permanent fix would involve testing for the request overlapping >the end of the cylinder and making two separate requests of the controller. > >I hope this information saves others some tedious debugging. Since then I have changed the driver to issue separate requests for the sectors of a block that span cylinder boundaries. The code is not pretty, but since I am working at restructuring the driver I have not bothered to clean it up. I have been using this new version for a couple of months without any problems. Since the context diffs for my changes were almost as long as the driver itself, I am posting the entire source. You should diff this version against the original at_wini.c to see the changes I have made. This version includes a few changes posted previously by Larry Hubble. --Mark Schwenk ihnp4!tellab5!schwenk or ihnp4!quali!mas --Don't believe a thing I say... :-; ----------------------- cut here for new at_wini.c ---------------------------- /* This file contains a driver for the IBM-AT winchester controller. * It was written by Adri Koppes. * * The driver supports two operations: read a block and * write a block. It accepts two messages, one for reading and one for * writing, both using message format m2 and with the same parameters: * * m_type DEVICE PROC_NR COUNT POSITION ADRRESS * ---------------------------------------------------------------- * | DISK_READ | device | proc nr | bytes | offset | buf ptr | * |------------+---------+---------+---------+---------+---------| * | DISK_WRITE | device | proc nr | bytes | offset | buf ptr | * ---------------------------------------------------------------- * * The file contains one entry point: * * winchester_task: main entry when system is brought up * */ #include "../h/const.h" #include "../h/type.h" #include "../h/callnr.h" #include "../h/com.h" #include "../h/error.h" #include "const.h" #include "type.h" #include "proc.h" /* I/O Ports used by winchester disk controller. */ #define WIN_REG1 0x1f0 #define WIN_REG2 0x1f1 #define WIN_REG3 0x1f2 #define WIN_REG4 0x1f3 #define WIN_REG5 0x1f4 #define WIN_REG6 0x1f5 #define WIN_REG7 0x1f6 #define WIN_REG8 0x1f7 #define WIN_REG9 0x3f6 /* Winchester disk controller command bytes. */ #define WIN_RECALIBRATE 0x10 /* command for the drive to recalibrate */ #define WIN_READ 0x20 /* command for the drive to read */ #define WIN_WRITE 0x30 /* command for the drive to write */ #define WIN_SPECIFY 0x91 /* command for the controller to accept params */ /* Parameters for the disk drive. */ #define SECTOR_SIZE 512 /* physical sector size in bytes */ #define SECS_PER_BLK (BLOCK_SIZE/SECTOR_SIZE) /* Error codes */ #define ERR -1 /* general error */ /* Miscellaneous. */ #define MAX_ERRORS 4 /* how often to try rd/wt before quitting */ #define NR_DEVICES 10 /* maximum number of drives */ #define MAX_WIN_RETRY 20000 /* max # times to try to output to WIN */ #define PART_TABLE 0x1C6 /* IBM partition table starts here in sect 0 */ #define DEV_PER_DRIVE 5 /* hd0 + hd1 + hd2 + hd3 + hd4 = 5 */ /* Variables. */ PRIVATE struct wini { /* main drive struct, one entry per drive */ int wn_opcode; /* DISK_READ or DISK_WRITE */ int wn_procnr; /* which proc wanted this operation? */ int wn_drive; /* drive number addressed */ int wn_cylinder; /* cylinder number addressed */ int wn_sector; /* sector addressed */ int wn_head; /* head number addressed */ int wn_heads; /* maximum number of heads */ int wn_maxsec; /* maximum number of sectors per track */ int wn_ctlbyte; /* control byte (steprate) */ int wn_precomp; /* write precompensation cylinder / 4 */ long wn_low; /* lowest sector of partition */ long wn_size; /* size of partition in sectors */ int wn_count; /* byte count */ vir_bytes wn_address; /* user virtual address */ } wini[NR_DEVICES]; PRIVATE int w_need_reset = FALSE; /* set to 1 when controller must be reset */ PRIVATE int nr_drives; /* Number of drives */ PRIVATE message w_mess; /* message buffer for in and out */ PRIVATE int command[8]; /* Common command block */ PRIVATE unsigned char buf[BLOCK_SIZE]; /* Buffer used by the startup routine */ /*===========================================================================* * winchester_task * *===========================================================================*/ PUBLIC winchester_task() { /* Main program of the winchester disk driver task. */ int r, caller, proc_nr; /* First initialize the controller */ init_param(); /* Here is the main loop of the disk task. It waits for a message, carries * it out, and sends a reply. */ while (TRUE) { /* First wait for a request to read or write a disk block. */ receive(ANY, &w_mess); /* get a request to do some work */ if (w_mess.m_source < 0) { printf("winchester task got message from %d ", w_mess.m_source); continue; } caller = w_mess.m_source; proc_nr = w_mess.PROC_NR; /* Now carry out the work. */ switch(w_mess.m_type) { case DISK_READ: case DISK_WRITE: r = w_do_rdwt(&w_mess); break; default: r = EINVAL; break; } /* Finally, prepare and send the reply message. */ w_mess.m_type = TASK_REPLY; w_mess.REP_PROC_NR = proc_nr; w_mess.REP_STATUS = r; /* # of bytes transferred or error code */ send(caller, &w_mess); /* send reply to caller */ } } /*===========================================================================* * w_do_rdwt * *===========================================================================*/ PRIVATE int w_do_rdwt(m_ptr) message *m_ptr; /* pointer to read or write w_message */ { /* Carry out a read or write request from the disk. */ register struct wini *wn; int r, device, errors = 0; long sector; /* Decode the w_message parameters. */ device = m_ptr->DEVICE; if (device < 0 || device >= NR_DEVICES) return(EIO); if (m_ptr->COUNT != BLOCK_SIZE) return(EINVAL); wn = &wini[device]; /* 'wn' points to entry for this drive */ wn->wn_drive = device/DEV_PER_DRIVE; /* save drive number */ if (wn->wn_drive >= nr_drives) return(EIO); wn->wn_opcode = m_ptr->m_type; /* DISK_READ or DISK_WRITE */ if (m_ptr->POSITION % BLOCK_SIZE != 0) return(EINVAL); sector = m_ptr->POSITION/SECTOR_SIZE; if ((sector+SECS_PER_BLK) > wn->wn_size) return(EOF); sector += wn->wn_low; wn->wn_cylinder = sector / (wn->wn_heads * wn->wn_maxsec); wn->wn_sector = (sector % wn->wn_maxsec) + 1; wn->wn_head = (sector % (wn->wn_heads * wn->wn_maxsec) )/wn->wn_maxsec; wn->wn_count = m_ptr->COUNT; wn->wn_address = (vir_bytes) m_ptr->ADDRESS; wn->wn_procnr = m_ptr->PROC_NR; /* This loop allows a failed operation to be repeated. */ while (errors <= MAX_ERRORS) { errors++; /* increment count once per loop cycle */ if (errors > MAX_ERRORS) return(EIO); /* First check to see if a reset is needed. */ if (w_need_reset) w_reset(); /* Perform the transfer. */ r = w_transfer(wn); if (r == OK) break; /* if successful, exit loop */ } return(r == OK ? BLOCK_SIZE : EIO); } /*===========================================================================* * w_transfer * *===========================================================================*/ PRIVATE int w_transfer(wn) register struct wini *wn; /* pointer to the drive struct */ { extern phys_bytes umap(); phys_bytes win_buf = umap(proc_addr(WINCHESTER), D, buf, BLOCK_SIZE); phys_bytes usr_buf = umap(proc_addr(wn->wn_procnr), D, wn->wn_address, BLOCK_SIZE); int remain, offset; if (win_buf == (phys_bytes)0 || usr_buf == (phys_bytes)0) return(ERR); if (wn->wn_opcode == DISK_WRITE) phys_copy(usr_buf, win_buf, (phys_bytes)BLOCK_SIZE); /* The command is issued by outputing 7 bytes to the controller chip. */ command[0] = wn->wn_head & 8; command[1] = wn->wn_precomp; command[3] = wn->wn_sector; command[4] = wn->wn_cylinder & 0xFF; command[5] = ((wn->wn_cylinder & 0x0300) >> 8); command[6] = (wn->wn_drive << 4) | wn->wn_head | 0xA0; command[7] = (wn->wn_opcode == DISK_READ ? WIN_READ : WIN_WRITE); /* * if transfer crosses a cylinder boundary, break it into 2 requests */ remain = wn->wn_maxsec - wn->wn_sector + 1; if (wn->wn_head == wn->wn_heads-1 && SECS_PER_BLK > remain) { #ifdef DEBUG printf("splitting: %d/%d/%d %d %d\n", wn->wn_cylinder,wn->wn_head,wn->wn_sector, remain,SECS_PER_BLK-remain); #endif /*DEBUG*/ offset = command[2] = remain; if (w_dotrans(wn,win_buf,usr_buf,0) == ERR) return(ERR); command[0] = 0; /* head */ command[2] = SECS_PER_BLK - remain; command[3] = 1; /* sector */ command[4] = (wn->wn_cylinder+1) & 0xFF; command[5] = (((wn->wn_cylinder+1) & 0x0300) >> 8); command[6] = (wn->wn_drive << 4) | 0xA0; if (w_dotrans(wn,win_buf,usr_buf,offset) == ERR) return(ERR); } else { command[2] = SECS_PER_BLK; if (w_dotrans(wn,win_buf,usr_buf,0) == ERR) return(ERR); } if (wn->wn_opcode == DISK_READ) phys_copy(win_buf, usr_buf, (phys_bytes)BLOCK_SIZE); return(OK); } /*===========================================================================* * w_dotrans * *===========================================================================*/ /* note that command[] must be set up before calling this routine */ PRIVATE int w_dotrans(wn,win_buf,usr_buf,offset) register struct wini *wn; /* pointer to the drive struct */ phys_bytes win_buf, usr_buf; int offset; /* sectors of BLOCK already transferred */ { register int i,j; int r; if (com_out() != OK) return(ERR); /* Block, waiting for disk interrupt. */ if (wn->wn_opcode == DISK_READ) { for (i=offset; i<offset+command[2]; i++) { receive(HARDWARE, &w_mess); for (j=0; j<256; j++) portw_in(WIN_REG1, &buf[i*512+j*2]); if (win_results() != OK) { w_need_reset = TRUE; return(ERR); } } } else { for (i=0, r=0; i<MAX_WIN_RETRY && (r&8) == 0; i++) port_in(WIN_REG8, &r); if ((r&8) == 0) { w_need_reset = TRUE; return(ERR); } for (i=offset; i<offset+command[2]; i++) { for (j=0; j<256; j++) portw_out(WIN_REG1, *(int *)&buf[i*512+j*2]); receive(HARDWARE, &w_mess); if (win_results() != OK) { w_need_reset = TRUE; return(ERR); } } } return(OK); } /*===========================================================================* * w_reset * *===========================================================================*/ PRIVATE w_reset() { /* Issue a reset to the controller. This is done after any catastrophe, * like the controller refusing to respond. */ int i, r = 4; /* Strobe reset bit low. */ lock(); port_out(WIN_REG9, r); for (i = 0; i < 10; i++) ; port_out(WIN_REG9, 0); unlock(); if (drive_busy()) { printf("Winchester wouldn't reset, drive busy\n"); return(ERR); } port_in(WIN_REG2, &r); if (r != 1) { printf("Winchester wouldn't reset, drive error\n"); return(ERR); } /* Reset succeeded. Tell WIN drive parameters. */ w_need_reset = FALSE; return(win_init()); } /*===========================================================================* * win_init * *===========================================================================*/ PRIVATE win_init() { /* Routine to initialize the drive parameters after boot or reset */ register int i; command[0] = wini[0].wn_heads & 8; command[2] = wini[0].wn_maxsec; command[4] = 0; command[6] = wini[0].wn_heads | 0xA0; command[7] = WIN_SPECIFY; /* Specify some parameters */ if (com_out() != OK) /* Output command block */ return(ERR); receive(HARDWARE, &w_mess); if (win_results() != OK) { /* See if controller accepted parameters */ w_need_reset = TRUE; return(ERR); } if (nr_drives > 1) { command[0] = wini[5].wn_heads & 8; command[2] = wini[5].wn_maxsec; command[4] = 0; command[6] = wini[5].wn_heads | 0xB0; command[7] = WIN_SPECIFY; /* Specify some parameters */ if (com_out() != OK) /* Output command block */ return(ERR); receive(HARDWARE, &w_mess); if (win_results() != OK) { /* See if controller accepted parameters */ w_need_reset = TRUE; return(ERR); } } for (i=0; i<nr_drives; i++) { command[0] = wini[i*5].wn_heads & 8; command[6] = i << 4; command[7] = WIN_RECALIBRATE | (wini[i*5].wn_ctlbyte & 0x0F); if (com_out() != OK) return(ERR); receive(HARDWARE, &w_mess); if (win_results() != OK) { w_need_reset = TRUE; return(ERR); } } return(OK); } /*============================================================================* * win_results * *============================================================================*/ PRIVATE win_results() { /* Routine to check if controller has done the operation succesfully */ int r; port_in(WIN_REG8, &r); if ((r&0x80) != 0) return(OK); if ((r&0x40) == 0 || (r&0x20) != 0 || (r&0x10) == 0 || (r&1) != 0) { if ((r&01) != 0) port_in(WIN_REG2, &r); return(ERR); } return(OK); } /*============================================================================* * drive_busy * *============================================================================*/ PRIVATE drive_busy() { /* Wait until the controller is ready to receive a command or send status */ register int i = 0; int r; for (i = 0, r = 255; i<MAX_WIN_RETRY && (r&0x80) != 0; i++) port_in(WIN_REG8, &r); if ((r&0x80) != 0 || (r&0x40) == 0 || (r&0x10) == 0) { w_need_reset = TRUE; return(ERR); } return(OK); } /*============================================================================* * com_out * *============================================================================*/ PRIVATE com_out() { /* Output the command block to the winchester controller and return status */ register int i; int r; if (drive_busy()) { w_need_reset = TRUE; return(ERR); } r = WIN_REG2; lock(); port_out(WIN_REG9, command[0]); for (i=1; i<8; i++, r++) port_out(r, command[i]); unlock(); return(OK); } /*============================================================================* * init_params * *============================================================================*/ PRIVATE init_params() { /* This routine is called at startup to initialize the partition table, * the number of drives and the controller */ unsigned int i, segment, offset; phys_bytes address; extern phys_bytes umap(); extern int vec_table[]; /* Copy the parameter vector from the saved vector table */ offset = vec_table[2 * 0x41]; segment = vec_table[2 * 0x41 + 1]; /* Calculate the address off the parameters and copy them to buf */ address = ((long)segment << 4) + offset; phys_copy(address, umap(proc_addr(WINCHESTER), D, buf, 16), 16L); /* Copy the parameters to the structures */ copy_param(buf, &wini[0]); /* Copy the parameter vector from the saved vector table */ offset = vec_table[2 * 0x46]; segment = vec_table[2 * 0x46 + 1]; /* Calculate the address off the parameters and copy them to buf */ address = ((long)segment << 4) + offset; phys_copy(address, umap(proc_addr(WINCHESTER), D, buf, 16), 16L); /* Copy the parameters to the structures */ copy_param(buf, &wini[5]); /* Get the nummer of drives from the bios */ phys_copy(0x475L, umap(proc_addr(WINCHESTER), D, buf, 1), 1L); nr_drives = (int) *buf; /* Set the parameters in the drive structure */ wini[0].wn_low = wini[5].wn_low = 0L; /* Initialize the controller */ if ((nr_drives > 0) && (win_init() != OK)) nr_drives = 0; /* Read the partition table for each drive and save them */ for (i = 0; i < nr_drives; i++) { w_mess.DEVICE = i * 5; w_mess.POSITION = 0L; w_mess.COUNT = BLOCK_SIZE; w_mess.ADDRESS = (char *) buf; w_mess.PROC_NR = WINCHESTER; w_mess.m_type = DISK_READ; if (w_do_rdwt(&w_mess) != BLOCK_SIZE) panic("Can't read partition table of winchester ", i); if (buf[510] != 0x55 || buf[511] != 0xAA) { printf("Invalid partition table\n"); continue; } copy_prt(i); } } /*============================================================================* * copy_param * *============================================================================*/ PRIVATE copy_param(src, dest) register unsigned char *src; register struct wini *dest; { /* This routine copies the parameters from src to dest * and sets the parameters for partition 0 and 5 */ register int i; long cyl, heads, sectors; for (i=0; i<5; i++) { dest[i].wn_heads = (int)src[2]; dest[i].wn_precomp = *(int *)&src[5] / 4; dest[i].wn_ctlbyte = (int)src[10]; dest[i].wn_maxsec = (int)src[14]; } cyl = (long)(*(int *)src); heads = (long)dest[0].wn_heads; sectors = (long)dest[0].wn_maxsec; dest[0].wn_size = cyl * heads * sectors; } /*============================================================================* * copy_prt * *============================================================================*/ PRIVATE copy_prt(drive) int drive; { /* This routine copies the partition table for the selected drive to * the variables wn_low and wn_size */ register int i, offset; struct wini *wn; long adjust; for (i=0; i<4; i++) { adjust = 0; wn = &wini[i + drive*5 + 1]; offset = PART_TABLE + i * 0x10; wn->wn_low = *(long *)&buf[offset]; if ((wn->wn_low % (SECS_PER_BLK)) != 0) { adjust = wn->wn_low; wn->wn_low = (wn->wn_low/(SECS_PER_BLK+1))*SECS_PER_BLK; adjust = wn->wn_low - adjust; } wn->wn_size = *(long *)&buf[offset + sizeof(long)] - adjust; } sort(&wini[drive*5 + 1]); #ifdef DEBUG for (i=drive*5; i<drive*5+5; i++) showwini(i); #endif DEBUG } sort(wn) register struct wini *wn; { register int i,j; for (i=0; i<4; i++) for (j=0; j<3; j++) if ((wn[j].wn_low == 0) && (wn[j+1].wn_low != 0)) swap(&wn[j], &wn[j+1]); else if (wn[j].wn_low > wn[j+1].wn_low && wn[j+1].wn_low != 0) swap(&wn[j], &wn[j+1]); } swap(first, second) register struct wini *first, *second; { register struct wini tmp; tmp = *first; *first = *second; *second = tmp; } #ifdef DEBUG PRIVATE showwini(device) register int device; { register struct wini *w = &wini[device]; printf("DEVICE %d\n", device); printf("op: 0x%x proc: %d drive: %d cyl: %d sec: %d hd: %d\n", w->wn_opcode, w->wn_procnr, w->wn_drive, w->wn_cylinder, w->wn_sector, w->wn_head); printf("maxhd: %d maxsec: %d ctl: %d precmp %d\n", w->wn_heads, w->wn_maxsec, w->wn_ctlbyte, w->wn_precomp); printf("low: %D size: %D cnt: %d\n", w->wn_low, w->wn_size, w->wn_count); } #endif /*DEBUG*/