/* This file contains a driver for the IBM-AT winchester controller. * It was written by Adri Koppes. * * The driver supports the following operations (using message format m2): * * 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 | * ---------------------------------------------------------------- * |SCATTERED_IO| device | proc nr | requests| | iov ptr | * ---------------------------------------------------------------- * * The file contains one entry point: * * winchester_task: main entry when system is brought up * */ #include "kernel.h" #include <minix/callnr.h> #include <minix/com.h> #include <minix/partition.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_FORMAT 0x50 /* command for the drive to format track */ #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 */ /* Error codes */ #define ERR -1 /* general error */ /* Miscellaneous. */ #define MAX_ERRORS 4 /* how often to try rd/wt before quitting */ #define MAX_DRIVES 2 /* this driver supports 2 drives (hd0 - hd9)*/ #define NR_DEVICES (MAX_DRIVES * DEV_PER_DRIVE) #define MAX_WIN_RETRY 10000 /* max # times to try to output to WIN */ #define DEV_PER_DRIVE (1 + NR_PARTITIONS) /* whole drive & each partn */ /* 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 cylinder of partition */ long wn_size; /* size of partition in blocks */ int wn_count; /* byte count */ vir_bytes wn_address; /* user virtual address */ } wini[NR_DEVICES]; PUBLIC int using_bios = FALSE; /* this disk driver does not use the BIOS */ 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 */ FORWARD int com_out(); FORWARD int controller_ready(); FORWARD void copy_params(); FORWARD void copy_prt(); FORWARD int drive_busy(); FORWARD void init_params(); FORWARD void sort(); FORWARD int w_do_rdwt(); FORWARD int w_reset(); FORWARD int w_transfer(); FORWARD int win_init(); FORWARD int win_results(); /*===========================================================================* * winchester_task * *===========================================================================*/ PUBLIC void winchester_task() { /* Main program of the winchester disk driver task. */ int r, caller, proc_nr; /* First initialize the controller */ init_params(); /* 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; case SCATTERED_IO: r = do_vrdwt(&w_mess, w_do_rdwt); 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+BLOCK_SIZE/SECTOR_SIZE) > wn->wn_size) return(0); 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 */ { phys_bytes usr_buf; register int i; int r = 0; /* The command is issued by outputing 7 bytes to the controller chip. */ usr_buf = numap(wn->wn_procnr, wn->wn_address, BLOCK_SIZE); if (usr_buf == (phys_bytes)0) return(ERR); command[0] = wn->wn_ctlbyte; command[1] = wn->wn_precomp; command[2] = BLOCK_SIZE/SECTOR_SIZE; command[3] = wn->wn_sector; command[4] = wn->wn_cylinder & 0xFF; command[5] = (wn->wn_cylinder >> 8) & BYTE; command[6] = (wn->wn_drive << 4) | wn->wn_head | 0xA0; command[7] = (wn->wn_opcode == DISK_READ ? WIN_READ : WIN_WRITE); if (com_out() != OK) return(ERR); /* Block, waiting for disk interrupt. */ if (wn->wn_opcode == DISK_READ) { for (i=0; i<BLOCK_SIZE/SECTOR_SIZE; i++) { receive(HARDWARE, &w_mess); if (win_results() != OK) { w_need_reset = TRUE; return(ERR); } port_read(WIN_REG1, usr_buf, (unsigned) SECTOR_SIZE); usr_buf += SECTOR_SIZE; } r = OK; } else { for (i=0; i<MAX_WIN_RETRY && (r&8) == 0; i++) r = in_byte(WIN_REG8); if ((r&8) == 0) { w_need_reset = TRUE; return(ERR); } /* There will be an interrupt for each sector, except the format-track * command only interrupts once. Formatting may be done just like * writing by setting command[7] = WIN_FORMAT. There is no clean way * to do this yet. */ if (command[7] == WIN_FORMAT) i = 1; else i = BLOCK_SIZE / SECTOR_SIZE; do { port_write(WIN_REG1, usr_buf, (unsigned) SECTOR_SIZE); usr_buf += SECTOR_SIZE; receive(HARDWARE, &w_mess); if (win_results() != OK) { w_need_reset = TRUE; return(ERR); } } while (--i != 0); r = OK; } if (r == ERR) w_need_reset = TRUE; return(r); } /*===========================================================================* * w_reset * *===========================================================================*/ PRIVATE int w_reset() { /* Issue a reset to the controller. This is done after any catastrophe, * like the controller refusing to respond. */ int i, r; /* Strobe reset bit low. */ out_byte(WIN_REG9, 4); for (i = 0; i < 10; i++) ; out_byte(WIN_REG9, wini[0].wn_ctlbyte & 0x0F); for (i = 0; i < MAX_WIN_RETRY && drive_busy(); i++) ; if (drive_busy()) { printf("Winchester wouldn't reset, drive busy\n"); return(ERR); } r = in_byte(WIN_REG2); 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 int win_init() { /* Routine to initialize the drive parameters after boot or reset */ register int i; command[0] = wini[0].wn_ctlbyte; command[1] = wini[0].wn_precomp; command[2] = wini[0].wn_maxsec; command[4] = 0; command[6] = (wini[0].wn_heads - 1) | 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_ctlbyte; command[1] = wini[5].wn_precomp; command[2] = wini[5].wn_maxsec; command[4] = 0; command[6] = (wini[5].wn_heads - 1) | 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_ctlbyte; command[6] = i << 4 | 0xA0; command[7] = WIN_RECALIBRATE; 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 int win_results() { /* Extract results from the controller after an operation, then allow wini * interrupts again. */ register int r; r = in_byte(WIN_REG8); if ( (r & 0x80) == 0 && (r & (0x40 | 0x20 | 0x10 | 0x01)) != (0x40 | 0x10)) { if (r & 0x01) in_byte(WIN_REG2); cim_at_wini(); return(ERR); } cim_at_wini(); return(OK); } /*==========================================================================* * controller_ready * *==========================================================================*/ PRIVATE int controller_ready() { /* Wait until controller is ready for output; return zero if this times out. */ #define MAX_CONTROLLER_READY_RETRIES 1000 /* should calibrate this */ #define WIN_BUSY 0x80 #define WIN_OUTREADY 0x40 /* Bruce's guess based on FDC*/ #define WIN_STATUS WIN_REG8 register int retries; retries = MAX_CONTROLLER_READY_RETRIES + 1; while (--retries != 0 && (in_byte(WIN_STATUS) & (WIN_BUSY | WIN_OUTREADY)) != WIN_OUTREADY) ; /* wait until not busy */ return(retries); /* nonzero if ready */ } /*============================================================================* * drive_busy * *============================================================================*/ PRIVATE int drive_busy() { /* Wait until the controller is ready to receive a command or send status */ controller_ready(); if ( (in_byte(WIN_REG8) & (0x80 | 0x40 | 0x10)) != (0x40 | 0x10)) { w_need_reset = TRUE; return(ERR); } return(OK); } /*============================================================================* * com_out * *============================================================================*/ PRIVATE int com_out() { /* Output the command block to the winchester controller and return status */ register int *commandp; register int port; if (!controller_ready()) { printf("Controller not ready in com_out\n"); w_need_reset = TRUE; return(ERR); } out_byte(WIN_REG9, command[0]); for (commandp = &command[1], port = WIN_REG2; port <= WIN_REG8; ++port) out_byte(port, *commandp++); return(OK); } /*============================================================================* * init_params * *============================================================================*/ PRIVATE void 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; /* Copy the parameter vector from the saved vector table */ offset = vec_table[2 * WINI_0_PARM_VEC]; segment = vec_table[2 * WINI_0_PARM_VEC + 1]; /* Calculate the address off the parameters and copy them to buf */ address = hclick_to_physb(segment) + offset; phys_copy(address, umap(proc_ptr, D, (vir_bytes)buf, 16), 16L); /* Copy the parameters to the structures */ copy_params(buf, &wini[0]); /* Copy the parameter vector from the saved vector table */ offset = vec_table[2 * WINI_1_PARM_VEC]; segment = vec_table[2 * WINI_1_PARM_VEC + 1]; /* Calculate the address off the parameters and copy them to buf */ address = hclick_to_physb(segment) + offset; phys_copy(address, umap(proc_ptr, D, (vir_bytes)buf, 16), 16L); /* Copy the parameters to the structures */ copy_params(buf, &wini[5]); /* Get the nummer of drives from the bios */ phys_copy(0x475L, umap(proc_ptr, D, (vir_bytes)buf, 1), 1L); nr_drives = (int) *buf; if (nr_drives > MAX_DRIVES) nr_drives = MAX_DRIVES; /* Set the parameters in the drive structure */ wini[0].wn_low = wini[5].wn_low = 0L; /* Initialize the controller */ cim_at_wini(); /* ready for AT wini interrupts */ if (nr_drives > 0 && win_init() != OK && w_reset() != 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) { printf("Can't read partition table on winchester %d\n",i); milli_delay(20000); continue; } if (buf[510] != 0x55 || buf[511] != 0xAA) { printf("Invalid partition table on winchester %d\n",i); milli_delay(20000); continue; } copy_prt((int)i*5); } } /*============================================================================* * copy_params * *============================================================================*/ PRIVATE void copy_params(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 = *(u16_t *)&src[5] >> 2; dest[i].wn_ctlbyte = (int)src[8]; dest[i].wn_maxsec = (int)src[14]; } cyl = (long)(*(u16_t *)src); heads = (long)dest[0].wn_heads; sectors = (long)dest[0].wn_maxsec; dest[0].wn_size = cyl * heads * sectors; } /*===========================================================================* * copy_prt * *===========================================================================*/ PRIVATE void copy_prt(base_dev) int base_dev; /* base device for drive */ { /* This routine copies the partition table for the selected drive to * the variables wn_low and wn_size */ register struct part_entry *pe; register struct wini *wn; for (pe = (struct part_entry *) &buf[PART_TABLE_OFF], wn = &wini[base_dev + 1]; pe < ((struct part_entry *) &buf[PART_TABLE_OFF]) + NR_PARTITIONS; ++pe, ++wn) { wn->wn_low = pe->lowsec; wn->wn_size = pe->size; /* Adjust low sector to a multiple of (BLOCK_SIZE/SECTOR_SIZE) for old * Minix partitions only. We can assume the ratio is 2 and round to * even, which is slightly simpler. */ if (pe->sysind == OLD_MINIX_PART && wn->wn_low & 1) { ++wn->wn_low; --wn->wn_size; } } sort(&wini[base_dev + 1]); } /*===========================================================================* * sort * *===========================================================================*/ PRIVATE void sort(wn) register struct wini wn[]; { register int i,j; struct wini tmp; for (i = 0; i < NR_PARTITIONS; i++) for (j = 0; j < NR_PARTITIONS-1; j++) if ((wn[j].wn_low == 0 && wn[j+1].wn_low != 0) || (wn[j].wn_low > wn[j+1].wn_low && wn[j+1].wn_low != 0)) { tmp = wn[j]; wn[j] = wn[j+1]; wn[j+1] = tmp; } }