cagney@chook.ua.oz (Andrew Cagney - aka Noid) (05/29/89)
This is a SCSI driver for PC minix on a Micro Byte PC 230. The PC 230 uses a WD 33C93 SCSI controller. Obiviously this driver will only work on a PC 230. If you are writing a SCSI driver for another pc with the same controller chip set then this will make a very good starting point. If you are interested in how simple it is to drive a SCSI port then the code below is again interesting. Features: This controller assumes that the PC230 is at SCSI address 0 and the hard disk is at SCSI address 1. This will be changed when I or Martin Standiford get the time. Notes: This will not link with 8 character linkers (eg the one on minix) as the two identifiers w_scsi_dma_setup and w_scsi_cdb_setup are identical in the first 8 characters. If you change these names then it should work. Finally Martin Stanford (of micro byte) was the author, he should receive the credit, not me. Andrew Cagney cagney@cs.ua.oz.au ------------------------------------------------------------------------- static w_do_rdwt(),w_scsi_dma_setup(),w_wd3393_setup(),w_scsi_cdb_setup(), w_transfer(),win_results(),w_wait_int(),init_params(),copy_prt(); /***********************************************************************/ /* This file contains a driver for the WD 33C93 SCSI controller * chip used in the Micro Byte Systems PC230. * * Original ST506 code written by Adri Koppes. * Extreme modification by Martin Sandiford for SCSI controller. * * 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" #define DEBUG FALSE /* TRUE: enable debug messages */ #define MONITOR TRUE /* TRUE: monitor performance of busy loops */ #define MAX_DRIVES 1 /* I/O Ports used by SCSI disk task. */ PRIVATE int _scsiix = 0x0E8; /* SCSI controller index port */ PRIVATE int _scsida = 0x0E9; /* SCSI controller data port */ #define INTDMA3 0x0E6 /* PC230 Set DMA3/IRQ5 internal */ #define DMA_ADDR 0x006 /* port for low 16 bits of DMA address */ #define DMA_COUNT 0x007 /* port for DMA count (count = bytes - 1) */ #define DMA_STATUS 0x008 #define DMA_INIT 0x00A /* DMA init port */ #define DMA_M1 0x00B /* DMA status port */ #define DMA_M2 0x00C /* DMA status port */ #define DMA_TOP 0x082 /* port for top 4 bits of 20-bit DMA addr */ /* SCSI unit command bytes. */ #define SCSI_READ 0x08 /* command for the drive to read */ #define SCSI_WRITE 0x0A /* command for the drive to write */ /* SCSI controller indexes */ #define SCSI_CTRL 0x01 #define SCSI_TIMEO 0x02 #define SCSI_CDB1 0x03 /* Command Descriptor Block (1st byte) */ #define SCSI_TLUN 0x0F /* Target LUN (always 0 on PC230) */ #define SCSI_TCMSB 0x12 /* Transfer count MSB etc. */ #define SCSI_TCNSB 0x13 #define SCSI_TCLSB 0x14 #define SCSI_DESTID 0x15 #define SCSI_SOURCEID 0x16 #define SCSI_STATUS 0x17 /* Status of command after completion interrupt */ #define SCSI_CMND 0x18 /* WD3393 command register */ /* WD33C93 commands */ #define SCSI_SELATNS 0x08 /* Select with atn and transfer cmd for chip */ /* SCSI controller status bits */ #define SST_CIP 0x10 /* Command in progress bit */ #define SST_BUSY 0x20 /* Controller busy bit */ #define SST_INTERRUPT 0x80 /* Command complete interrupt happened bit */ /* DMA channel commands. */ #define DMA_READ 0x47 /* DMA read opcode */ #define DMA_WRITE 0x4B /* DMA write opcode */ /* 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_RESULTS 4 /* max number of bytes controller returns */ #define NR_DEVICES 10 /* maximum number of drives */ #define MAX_RETRY 32000 /* 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 (<< 5) */ long wn_secaddr; /* Sector address of req'd sector */ 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 */ char wn_results[MAX_RESULTS]; /* the controller can give lots of output */ } wini[NR_DEVICES]; PRIVATE int nr_drives; /* Number of drives */ PRIVATE message w_mess; /* message buffer for in and out */ PRIVATE char command[6]; /* 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_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_WRITE: case DISK_READ: 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_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(EOF); sector += wn->wn_low; wn->wn_secaddr = sector; 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); /* Setup WD33C93 with drive information */ w_wd3393_setup(wn); /* Setup SCSI CDB bytes */ w_scsi_cdb_setup(wn); /* Setup SCSI transfer count and DMA addr/len */ w_scsi_dma_setup(wn); /* Do the transfer */ r = w_transfer(); if (r == OK) break; /* if successful, exit loop */ } return(r == OK ? BLOCK_SIZE : EIO); } /*==========================================================================* * w_wd3393_setup * *==========================================================================*/ PRIVATE w_wd3393_setup(wn) register struct wini *wn; { /* wn is not used as yet, but it may be necessary in future to send the correct destination ID. Currently this is hard wired to 1. */ int old_state; old_state = lock(); port_out(_scsiix, SCSI_SOURCEID); port_out(_scsida, 0); port_out(_scsiix, SCSI_CTRL); port_out(_scsida, 0x08); port_out(_scsiix, SCSI_TIMEO); port_out(_scsida, 12); port_out(_scsiix, SCSI_DESTID); port_out(_scsida, 1); port_out(_scsiix, SCSI_TLUN); port_out(_scsida, 0); restore(old_state); } /*==========================================================================* * w_scsi_cdb_setup * *==========================================================================*/ PRIVATE w_scsi_cdb_setup(wn) register struct wini *wn; { int old_state, i; /* The command is issued by outputing 6 bytes to the controller chip. */ command[0] = (wn->wn_opcode == DISK_READ ? SCSI_READ : SCSI_WRITE); command[1] = (wn->wn_secaddr >> 16) & 0x1F; command[2] = (wn->wn_secaddr >> 8) & 0xFF; command[3] = wn->wn_secaddr & 0xFF; command[4] = BLOCK_SIZE/SECTOR_SIZE; command[5] = 0; old_state = lock(); for (i = 0; i < 6; i++) { port_out(_scsiix, SCSI_CDB1+i); port_out(_scsida, command[i]); } port_out(_scsiix, SCSI_CTRL); port_out(_scsida, 0x88); restore(old_state); } /*==========================================================================* * w_dma_scsi_setup * *==========================================================================*/ PRIVATE w_scsi_dma_setup(wn) register struct wini *wn; /* pointer to the drive struct */ { /* The IBM PC can perform DMA operations by using the DMA chip. To use it, * the DMA (Direct Memory Access) chip is loaded with the 20-bit memory address * to by read from or written to, the byte count minus 1, and a read or write * opcode. This routine sets up the DMA chip. Note that the chip is not * capable of doing a DMA across a 64K boundary (e.g., you can't read a * 512-byte block starting at physical address 65520). */ int mode, low_addr, high_addr, top_addr, low_ct, high_ct, top_end, old_state; int low_ct1, high_ct1; vir_bytes vir, ct; phys_bytes user_phys; extern phys_bytes umap(); mode = (wn->wn_opcode == DISK_READ ? DMA_READ : DMA_WRITE); vir = (vir_bytes) wn->wn_address; ct = (vir_bytes) wn->wn_count; user_phys = umap(proc_addr(wn->wn_procnr), D, vir, ct); low_addr = (int) user_phys & BYTE; high_addr = (int) (user_phys >> 8) & BYTE; top_addr = (int) (user_phys >> 16) & BYTE; low_ct = (int) (ct - 1) & BYTE; high_ct = (int) ( (ct - 1) >> 8) & BYTE; low_ct1 = (int) ct & BYTE; high_ct1 = (int) (ct >> 8) & BYTE; /* Check to see if the transfer will require the DMA address counter to * go from one 64K segment to another. If so, do not even start it, since * the hardware does not carry from bit 15 to bit 16 of the DMA address. * Also check for bad buffer address. These errors mean FS contains a bug. */ if (user_phys == 0) panic("FS gave winchester disk driver bad addr", (int) vir); top_end = (int) (((user_phys + ct - 1) >> 16) & BYTE); if (top_end != top_addr) panic("Trying to DMA across 64K boundary", top_addr); /* Now set up the DMA registers. */ old_state = lock(); port_out(_scsiix, SCSI_TCMSB); port_out(_scsida, 0); port_out(_scsiix, SCSI_TCNSB); port_out(_scsida, high_ct1); port_out(_scsiix, SCSI_TCLSB); port_out(_scsida, low_ct1); port_out(DMA_M1, mode); /* send command */ port_out(DMA_M2, mode); /* clear flip/flop */ port_out(DMA_TOP, top_addr); /* output highest 4 bits */ port_out(DMA_ADDR, low_addr); /* output low-order 8 bits */ port_out(DMA_ADDR, high_addr);/* output next 8 bits */ port_out(DMA_COUNT, low_ct); /* output low 8 bits of count - 1 */ port_out(DMA_COUNT, high_ct); /* output high 8 bits of count - 1 */ restore(old_state); } /*=========================================================================* * w_transfer * *=========================================================================*/ PRIVATE int w_transfer(wn) register struct wini *wn; /* pointer to the drive struct */ { /* The drive is now on the proper cylinder. Read or write 1 block. */ int old_state; port_out(DMA_INIT, 3); /* initialize DMA */ port_in(DMA_STATUS, &old_state); old_state = lock(); port_out(_scsiix, SCSI_CMND); port_out(_scsida, SCSI_SELATNS); restore(old_state); /* Block, waiting for disk interrupt. */ w_wait_int(); /* Get controller status and check for errors. */ if (win_results(wn) == OK) return(OK); return(ERR); } /*===========================================================================* * win_results * *===========================================================================*/ PRIVATE int win_results(wn) register struct wini *wn; /* pointer to the drive struct */ { /* Extract results from the controller after an operation. */ int status; port_out(_scsiix, SCSI_STATUS); port_in(_scsida, &status); if (!(status & 0xE0)) /* Test "error" bits */ return(OK); return(ERR); } /*===========================================================================* * w_wait_int * *===========================================================================*/ PRIVATE w_wait_int() { int r, i; /* Some local storage */ receive(HARDWARE, &w_mess); for(i=0; i<MAX_RETRY; ++i) { port_in(_scsiix, &r); if(r & SST_INTERRUPT) break; /* Exit if end of int */ } #if MONITOR if(i > 10) { /* Some arbitrary limit below which we don't really care */ if(i == MAX_RETRY) printf("wini: timeout waiting for INTERRUPT status\n"); else printf("wini: %d loops waiting for INTERRUPT status\n", i); } #endif /* MONITOR */ } /*============================================================================* * 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; extern phys_bytes umap(); /* Set DMA channel 3 and interrupt 5 to internal */ port_out(INTDMA3, 0); /* Get the number of drives from the bios */ phys_copy(0x475L, umap(proc_addr(WINCHESTER), D, buf, 1), 1L); nr_drives = (int) *buf > MAX_DRIVES ? MAX_DRIVES : (int) *buf; /* Get the address of the WD33C93 SCSI chip */ phys_copy(0x48CL, umap(proc_addr(WINCHESTER), D, buf, 1), 2L); _scsiix = *buf + (buf[1] << 8); _scsida = _scsiix+1; /* Set the parameters in the drive structure */ for (i = 0; i < DEV_PER_DRIVE; i++) { wini[i].wn_drive = 0 << 5; /* Set drive number */ } wini[0].wn_low = wini[DEV_PER_DRIVE].wn_low = 0L; /* You may wish to modify this to reflect the actual number of cyls, hds, spt */ wini[0].wn_size = 603L*17L*4L; for (i = DEV_PER_DRIVE; i < (2*DEV_PER_DRIVE); i++) { wini[i].wn_drive = 1 << 5; /* Set drive number */ } wini[DEV_PER_DRIVE].wn_size = 603L * 8L * 34L; /* Read the partition table for each drive and save them */ for (i = 0; i < nr_drives; i++) { w_mess.DEVICE = i * DEV_PER_DRIVE; 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 of winchester %d ", i); continue; } copy_prt(i * DEV_PER_DRIVE); } } /*==========================================================================* * 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 + 1]; offset = PART_TABLE + i * 0x10; wn->wn_low = *(long *)&buf[offset]; if ((wn->wn_low % (BLOCK_SIZE/SECTOR_SIZE)) != 0) { adjust = wn->wn_low; wn->wn_low = (wn->wn_low/(BLOCK_SIZE/SECTOR_SIZE)+1)*(BLOCK_SIZE/SECTOR_SIZE); adjust = wn->wn_low - adjust; } wn->wn_size = *(long *)&buf[offset + sizeof(long)] - adjust; } sort(&wini[drive + 1]); } 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; } /* end of the file 230_wini.c */