/* * Copyright 1987 Alan Kent * * Permission is granted to redistribute this code as long * as this message is retained in the code and the code is * not sold without written permission from the author. * * UUCP: {seismo,hplabs,mcvax,ukc,nttlab}!munnari!goanna.oz!ajk * ACSnet: ajk@goanna.oz * ARPA: munnari!goanna.oz!ajk@SEISMO.ARPA */ #include "hd.h" #include #include extern void rdsec (); extern void wrsec (); extern int init_irq (); extern void free_irq (); extern void wait_for_irq (); extern void set_cmd_issued (); extern ULONG * NewHashTable (); extern void FreeHashTable (); extern void AddHashTable (); extern LONG TestHashTable (); ULONG * bad_hash_table; struct { LONG cmd_issued; struct Task * task; LONG sig_bits; } data; struct wd1002 { UBYTE pad0; UBYTE data; UBYTE pad1; UBYTE error; #define write_precomp error UBYTE pad2; UBYTE sec_count; UBYTE pad3; UBYTE sec_num; UBYTE pad4; UBYTE cyl_low; UBYTE pad5; UBYTE cyl_high; UBYTE pad6; UBYTE select; UBYTE pad7; UBYTE status; #define cmd_reg status }; #define WD ((struct wd1002 *)0x9ffff0) #define WDS_BUSY 0x80 #define WDS_READY 0x40 #define WDS_WRFAULT 0x20 #define WDS_SEEKDONE 0x10 #define WDS_DRQ 0x08 #define WDS_ERRCORRECTED 0x04 #define WDS_ERROR 0x01 #define WDE_BADBLOCK 0x80 #define WDE_CRCERROR 0x40 #define WDE_IDNOTFOUND 0x10 #define WDE_ABORT 0x04 #define WDE_TR0ERROR 0x02 #define WDE_DAMERROR 0x01 #define WDC_RESTORE 0x10 #define WDC_STEPSPEED 0x00 #define WDC_SEEK 0x70 #define WDC_READ 0x20 #define WDC_WRITE 0x30 #define WDC_FORMAT 0x50 #define WDC_MULTIPLE 0x04 /* drive 1, ECC, 512 byte sectors */ #define WD_SELECT 0xa0 /* deselct by selecting drive 2 */ #define WD_DESELECT 0xa8 #define WD_HEAD 0x03 static int wd_head; int wd_open () { int status; char dummy; struct posn posn; UBYTE *p; int i; /* first make sure its plugged in!!! */ WD->sec_count = 0; if ( WD->sec_count != 0 ) return ( -1 ); WD->sec_count = 56; if ( WD->sec_count != 56 ) return ( -1 ); /* initialize hash table for quick bad sector map checking */ bad_hash_table = NewHashTable ( 4000L ); if ( bad_hash_table == NULL ) return ( -1 ); /* initialize interrupts */ if ( init_irq () < 0 ) { FreeHashTable ( bad_hash_table ); return ( -1 ); } /* select the drive */ WD->select = WD_SELECT; busy_wait (); while ( WD->status & WDS_DRQ ) { dummy = WD->data; WD->data = 0; } WD->write_precomp = 32; WD->sec_count = 1; WD->cyl_low = 0; WD->cyl_high = 0; WD->select = WD_SELECT; wd_head = 0; status = wd_cmd ( WDC_RESTORE | WDC_STEPSPEED , TRUE ); if ( status == 0 ) { deselect (); /* ok, now read in the header sectors */ posn.sector = 0; posn.block = 0; posn.surface = 0; posn.cylinder = 0; p = (UBYTE*) &first; status = read_sector ( &posn , p ); if ( status == 0 ) { if ( first.magic != HD_MAGIC ) { /* no magic sectors at beginning. default something */ first.sectors = 16; first.heads = 4; first.cylinders = 480; first.bad_sectors = 0; first.park_cylinder = 512; first.map_sectors = 0; } else { /* read in rest of first structure */ for ( posn.sector = 1; posn.sector < first.map_sectors; posn.sector++ ) { p += HD_SECTOR; posn.block = posn.sector; status = read_sector ( &posn , p ); if ( status != 0 ) break; } /* if all ok, set up hash table for bad sectors */ if ( status == 0 ) { for ( i = 0; i < first.bad_sectors; i++ ) AddHashTable ( bad_hash_table , first.map[i] ); } } } } if ( status != 0 ) { free_irq (); FreeHashTable ( bad_hash_table ); return ( -1 ); } return ( 0 ); } void wd_close () { free_irq (); FreeHashTable ( bad_hash_table ); } int wd_cmd ( cmd , wait ) int cmd; int wait; { register int error; register int status; char dummy; WD->select = WD_SELECT | wd_head; busy_wait (); while ( ( WD->status & WDS_SEEKDONE ) == 0 ) ; set_cmd_issued (); WD->cmd_reg = cmd; if ( wait ) { wait_for_irq (); } busy_wait (); while ( ( WD->status & WDS_SEEKDONE ) == 0 ) ; error = WD->status; status = 0; if ( ( error & WDS_READY ) == 0 ) status = TDERR_NotSpecified; else if ( error & WDS_WRFAULT ) status = TDERR_WriteProt; else if ( error & WDS_ERROR ) { error = WD->error; if ( error & WDE_BADBLOCK ) status = TDERR_BadSecSum; else if ( error & WDE_ABORT ) status = TDERR_NotSpecified; else if ( error & WDE_TR0ERROR ) status = TDERR_SeekError; else if ( error & WDE_DAMERROR ) status = TDERR_BadSecPreamble; else if ( error & WDE_IDNOTFOUND ) status = TDERR_NoSecHdr; else if ( error & WDE_CRCERROR ) status = TDERR_BadSecSum; else status = TDERR_NotSpecified; } if ( status != 0 ) { /* try and recover from error - dont leave registers in bad way */ busy_wait (); while ( WD->status & WDS_DRQ ) dummy = WD->data; /* force a read of the error register */ error = WD->error; while ( WD->status & WDS_DRQ ) dummy = WD->data; /* do a restore too */ WD->select = WD_SELECT; busy_wait (); while ( ( WD->status & WDS_SEEKDONE ) == 0 ) ; set_cmd_issued (); WD->cmd_reg = WDC_RESTORE; wait_for_irq (); busy_wait (); while ( ( WD->status & WDS_SEEKDONE ) == 0 ) ; error = WD->status; } return ( status ); } int wd_seek ( posn ) register struct posn *posn; { int status; WD->select = WD_SELECT; busy_wait (); wd_head = posn->surface; WD->cyl_low = posn->cylinder; WD->cyl_high = posn->cylinder >> 8; WD->sec_num = posn->sector; status = wd_cmd ( WDC_SEEK , TRUE ); if ( status != 0 ) { bad_error = status; bad_posn = *posn; } else deselect (); return ( status ); } wd_park () { int status; int dummy; WD->select = WD_SELECT; wd_head = 0; busy_wait (); WD->cyl_low = first.park_cylinder; WD->cyl_high = first.park_cylinder >> 8; WD->sec_num = 0; status = wd_cmd ( WDC_SEEK , TRUE ); if ( status != 0 && bad_error == 0 ) { bad_error = status; bad_posn = cur_posn; } /* flush anything else (or if error) */ while ( WD->status & WDS_DRQ ) dummy = WD->data; /* if parked, dont deselect unless error */ /* (my drive's light turns yellow when parked) */ if ( status != 0 ) deselect (); return ( status ); } int read_sector ( posn , buf ) struct posn *posn; UBYTE *buf; { int status; int dummy; cur_posn = *posn; bad_remap ( &cur_posn ); WD->select = WD_SELECT; wd_head = cur_posn.surface; busy_wait (); WD->cyl_low = cur_posn.cylinder; WD->cyl_high = cur_posn.cylinder >> 8; WD->sec_num = cur_posn.sector; status = wd_cmd ( WDC_READ , TRUE ); if ( status != 0 && bad_error == 0 ) { bad_error = status; bad_posn = cur_posn; } if ( status == 0 ) rdsec ( buf , &WD->data , (LONG)HD_SECTOR ); /* flush anything else (or if error) */ while ( WD->status & WDS_DRQ ) dummy = WD->data; deselect (); return ( status ); } #ifdef DAM_SECTOR_MAPPING /* This wont really work because of bad sector remapping */ int read_track ( posn , buf ) register struct posn *posn; UBYTE *buf; { int status; int dummy; int sector; cur_posn = *posn; bad_remap ( &cur_posn ); WD->select = WD_SELECT; wd_head = cur_posn.surface; busy_wait (); WD->cyl_low = cur_posn.cylinder; WD->cyl_high = cur_posn.cylinder >> 8; for ( sector = 0; sector < first.sectors; sector++ ) { WD->sec_num = sector; status = wd_cmd ( WDC_READ , TRUE ); if ( status != 0 && bad_error == 0 ) { bad_error = status; bad_posn = cur_posn; } if ( status != 0 ) break; rdsec ( buf , &WD->data , (LONG)HD_SECTOR ); buf += HD_SECTOR; } /* flush anything else (or if error) */ while ( WD->status & WDS_DRQ ) dummy = WD->data; deselect (); return ( status ); } #endif /* this version simply does multiple calls to read_sector() */ /* so the bad sector mapping should be ok */ int read_track ( posn , buf ) struct posn *posn; UBYTE *buf; { int status; struct posn save; int sector; save = *posn; for ( save.sector = 0; save.sector < first.sectors; save.sector++ ) { save.block = save.sector + save.surface * first.sectors + save.cylinder * first.heads * first.sectors; status = read_sector ( &save , buf ); if ( status != 0 ) break; buf += HD_SECTOR; } return ( status ); } int write_sector ( posn , buf ) register struct posn *posn; char *buf; { int status; int dummy; if ( posn->cylinder < 0 ) { return; } cur_posn = *posn; bad_remap ( &cur_posn ); WD->select = WD_SELECT; wd_head = cur_posn.surface; busy_wait (); WD->cyl_low = cur_posn.cylinder; WD->cyl_high = cur_posn.cylinder >> 8; WD->sec_num = cur_posn.sector; status = wd_cmd ( WDC_WRITE , FALSE ); if ( status != 0 && bad_error == 0 ) { bad_error = status; bad_posn = cur_posn; } if ( status == 0 ) wrsec ( buf , &WD->data , (LONG)HD_SECTOR ); /* Flush buffer in case of error */ while ( WD->status & WDS_DRQ ) dummy = WD->data; wait_for_irq (); deselect (); return ( status ); } int wd_format_track ( posn ) register struct posn *posn; { register int i; int dummy; WD->sec_num = 0; WD->cyl_low = posn->cylinder; WD->cyl_high = posn->cylinder >> 8; WD->select = WD_SELECT | posn->surface; WD->sec_count = first.sectors; set_cmd_issued (); WD->cmd_reg = WDC_FORMAT; /* format track */ busy_wait (); while ( ( WD->status & WDS_SEEKDONE ) == 0 ); while ( ( WD->status & WDS_DRQ ) == 0 ); wrsec ( first.interleave , &WD->data , (LONG)HD_SECTOR ); wait_for_irq (); deselect (); return ( 0 ); } deselect () { busy_wait (); WD->select = WD_DESELECT; } busy_wait () { while ( WD->status & WDS_BUSY ) /*Delay ( 1L )*/; } bad_remap ( posn ) register struct posn *posn; { register int i; if ( ! TestHashTable ( bad_hash_table , posn->block ) ) return; for ( i = 0; i < first.bad_sectors; i++ ) { if ( first.map[i] == posn->block ) { posn->block = i + HD_MAP_SECTORS; posn->sector = posn->block % first.sectors; posn->surface = ( posn->block / first.sectors ) % first.heads; posn->cylinder = posn->block / ( first.sectors * first.heads ); break; } } } /************************ Interrupt handling code ***********************/ /* change task that should get sent interrupt signals */ wd_subtask ( task ) struct Task *task; { data.task = task; } int init_irq () { /* set up data structure */ data.cmd_issued = 0; data.task = FindTask ( 0L ); data.sig_bits = AllocSignal ( -1L ); if ( data.sig_bits < 0 ) { return ( -1 ); } data.sig_bits = 1L << data.sig_bits; /* allocate interrupt structure node */ HDInterrupt = (struct Interrupt *) AllocMem ( (LONG)sizeof ( struct Interrupt ) , (LONG)MEMF_PUBLIC ); if ( HDInterrupt == NULL ) { return ( -1 ); } /* set up interrupt structure flags */ HDInterrupt->is_Node.ln_Type = NT_INTERRUPT; HDInterrupt->is_Node.ln_Pri = 0; HDInterrupt->is_Node.ln_Name = "Hard Disk IRQ"; HDInterrupt->is_Data = (APTR) &data; HDInterrupt->is_Code = (VOID(*)()) HDHandler; AddIntServer ( (LONG)INTB_PORTS , HDInterrupt ); return ( 0 ); } void free_irq () { if ( HDInterrupt != NULL ) { RemIntServer ( (LONG)INTB_PORTS , HDInterrupt ); FreeMem ( HDInterrupt , (LONG)sizeof ( struct Interrupt ) ); HDInterrupt = NULL; } } void set_cmd_issued () { /* clear spurious signals */ SetSignal ( (LONG)0 , data.sig_bits ); data.cmd_issued = 1; } void wait_for_irq () { Wait ( data.sig_bits ); } LONG HDHandler () { if ( ! ( WD->status & WDS_BUSY ) ) { if ( data.cmd_issued ) { data.cmd_issued = 0; Signal ( data.task , data.sig_bits ); return ( 1 ); } } return ( 0 ); }