141085Swilliam /*- 241085Swilliam * Copyright (c) 1990 The Regents of the University of California. 341085Swilliam * All rights reserved. 441085Swilliam * 541085Swilliam * This code is derived from software contributed to Berkeley by 641085Swilliam * William Jolitz. 741085Swilliam * 848827Swilliam * %sccs.include.redist.c% 941085Swilliam * 10*56514Sbostic * @(#)wd.c 7.4 (Berkeley) 10/11/92 1141085Swilliam */ 1241085Swilliam 1341085Swilliam /* device driver for winchester disk */ 1441085Swilliam 15*56514Sbostic #include <sys/param.h> 16*56514Sbostic #include <sys/dkbad.h> 17*56514Sbostic #include <sys/disklabel.h> 1841085Swilliam 19*56514Sbostic #include <i386/isa/isa.h> 20*56514Sbostic #include <i386/isa/wdreg.h> 21*56514Sbostic #include <stand/saio.h> 22*56514Sbostic 2341085Swilliam #define NWD 2 /* number of hard disk units supported, max 2 */ 2441085Swilliam #define RETRIES 5 /* number of retries before giving up */ 2541085Swilliam 2648827Swilliam int noretries, wdquiet; 2748827Swilliam /*#define WDDEBUG*/ 2841085Swilliam 2941085Swilliam #ifdef SMALL 3041085Swilliam extern struct disklabel disklabel; 3141085Swilliam #else 3241085Swilliam struct disklabel wdsizes[NWD]; 3341085Swilliam #endif 3441085Swilliam 3548827Swilliam extern cyloffset ; /* bootstrap's idea of cylinder for disklabel */ 3648827Swilliam 3741085Swilliam /* 3841085Swilliam * Record for the bad block forwarding code. 3941085Swilliam * This is initialized to be empty until the bad-sector table 4041085Swilliam * is read from the disk. 4141085Swilliam */ 4241085Swilliam #define TRKSEC(trk,sec) ((trk << 8) + sec) 4341085Swilliam 4441085Swilliam struct dkbad dkbad[NWD]; 4548827Swilliam static wdcport; 4641085Swilliam 4741085Swilliam wdopen(io) 4841085Swilliam register struct iob *io; 4941085Swilliam { 5041085Swilliam register struct disklabel *dd; 5141085Swilliam 5248827Swilliam #ifdef WDDEBUG 5348827Swilliam printf("wdopen "); 5448827Swilliam #endif 5541085Swilliam #ifdef SMALL 5641085Swilliam dd = &disklabel; 5741085Swilliam #else 5848827Swilliam dd = &wdsizes[io->i_unit]; 5948827Swilliam if (io->i_part > 8) 6041085Swilliam _stop("Invalid partition number"); 6148827Swilliam if(io->i_ctlr > 1) 6248827Swilliam _stop("Invalid controller number"); 6341085Swilliam #endif 6441085Swilliam if (wdinit(io)) 6541085Swilliam _stop("wd initialization error"); 6648827Swilliam io->i_boff = dd->d_partitions[io->i_part].p_offset ; 6748827Swilliam return(0); 6841085Swilliam } 6941085Swilliam 7041085Swilliam wdstrategy(io,func) 7141085Swilliam register struct iob *io; 7241085Swilliam { 7341085Swilliam register int iosize; /* number of sectors to do IO for this loop */ 7441085Swilliam register daddr_t sector; 7541085Swilliam int nblocks, cyloff; 7641085Swilliam int unit, partition; 7741085Swilliam char *address; 7841085Swilliam register struct disklabel *dd; 7941085Swilliam 8048827Swilliam unit = io->i_unit; 8148827Swilliam partition = io->i_part; 8248827Swilliam #ifdef WDDEBUG 8348827Swilliam printf("wdstrat %d %d ", unit, partition); 8448827Swilliam #endif 8541085Swilliam #ifdef SMALL 8641085Swilliam dd = &disklabel; 8741085Swilliam #else 8841085Swilliam dd = &wdsizes[unit]; 8941085Swilliam #endif 9048827Swilliam iosize = io->i_cc / dd->d_secsize; 9141085Swilliam /* 9241085Swilliam * Convert PGSIZE "blocks" to sectors. 9341085Swilliam * Note: doing the conversions this way limits the partition size 9441085Swilliam * to about 8 million sectors (1-8 Gb). 9541085Swilliam */ 9648827Swilliam sector = (unsigned long) io->i_bn * DEV_BSIZE / dd->d_secsize; 9748827Swilliam nblocks = dd->d_partitions[partition].p_size; 9848827Swilliam #ifndef SMALL 9941085Swilliam if (iosize < 0 || sector + iosize > nblocks || sector < 0) { 10041085Swilliam #ifdef WDDEBUG 10141085Swilliam printf("bn = %d; sectors = %d; partition = %d; fssize = %d\n", 10241085Swilliam io->i_bn, iosize, partition, nblocks); 10341085Swilliam #endif 10441085Swilliam printf("wdstrategy - I/O out of filesystem boundaries\n"); 10541085Swilliam return(-1); 10641085Swilliam } 10748827Swilliam if (io->i_bn * DEV_BSIZE % dd->d_secsize) { 10841085Swilliam printf("wdstrategy - transfer starts in midsector\n"); 10941085Swilliam return(-1); 11041085Swilliam } 11148827Swilliam if (io->i_cc % dd->d_secsize) { 11241085Swilliam printf("wd: transfer of partial sector\n"); 11341085Swilliam return(-1); 11441085Swilliam } 11548827Swilliam #endif 11641085Swilliam 11741085Swilliam address = io->i_ma; 11841085Swilliam while (iosize > 0) { 11941085Swilliam if (wdio(func, unit, sector, address)) 12041085Swilliam return(-1); 12141085Swilliam iosize--; 12241085Swilliam sector++; 12348827Swilliam address += dd->d_secsize; 12441085Swilliam } 12541085Swilliam return(io->i_cc); 12641085Swilliam } 12741085Swilliam 12841085Swilliam /* 12941085Swilliam * Routine to do a one-sector I/O operation, and wait for it 13041085Swilliam * to complete. 13141085Swilliam */ 13241085Swilliam wdio(func, unit, blknm, addr) 13341085Swilliam short *addr; 13441085Swilliam { 13541085Swilliam struct disklabel *dd; 13648827Swilliam register wdc = wdcport; 13741085Swilliam struct bt_bad *bt_ptr; 13841085Swilliam int i; 13941085Swilliam int retries = 0; 14041085Swilliam long cylin, head, sector; 14148827Swilliam u_char opcode, erro; 14241085Swilliam 14341085Swilliam #ifdef SMALL 14441085Swilliam dd = &disklabel; 14541085Swilliam #else 14641085Swilliam dd = &wdsizes[unit]; 14741085Swilliam #endif 14849080Sbostic if (func == F_WRITE) 14941085Swilliam opcode = WDCC_WRITE; 15041085Swilliam else 15141085Swilliam opcode = WDCC_READ; 15241085Swilliam 15341085Swilliam /* Calculate data for output. */ 15448827Swilliam cylin = blknm / dd->d_secpercyl; 15548827Swilliam head = (blknm % dd->d_secpercyl) / dd->d_nsectors; 15648827Swilliam sector = blknm % dd->d_nsectors; 15741085Swilliam 15841085Swilliam /* 15941085Swilliam * See if the current block is in the bad block list. 16041085Swilliam */ 16148827Swilliam if (blknm > BBSIZE/DEV_BSIZE) /* should be BBSIZE */ 16241085Swilliam for (bt_ptr = dkbad[unit].bt_bad; bt_ptr->bt_cyl != -1; bt_ptr++) { 16341085Swilliam if (bt_ptr->bt_cyl > cylin) 16441085Swilliam /* Sorted list, and we passed our cylinder. quit. */ 16541085Swilliam break; 16641085Swilliam if (bt_ptr->bt_cyl == cylin && 16741085Swilliam bt_ptr->bt_trksec == (head << 8) + sector) { 16841085Swilliam /* 16941085Swilliam * Found bad block. Calculate new block addr. 17041085Swilliam * This starts at the end of the disk (skip the 17141085Swilliam * last track which is used for the bad block list), 17241085Swilliam * and works backwards to the front of the disk. 17341085Swilliam */ 17441085Swilliam #ifdef WDDEBUG 17541085Swilliam printf("--- badblock code -> Old = %d; ", 17641085Swilliam blknm); 17741085Swilliam #endif 17848827Swilliam blknm = dd->d_secperunit - dd->d_nsectors 17941085Swilliam - (bt_ptr - dkbad[unit].bt_bad) - 1; 18048827Swilliam cylin = blknm / dd->d_secpercyl; 18148827Swilliam head = (blknm % dd->d_secpercyl) / dd->d_nsectors; 18248827Swilliam sector = blknm % dd->d_nsectors; 18341085Swilliam #ifdef WDDEBUG 18441085Swilliam printf("new = %d\n", blknm); 18541085Swilliam #endif 18641085Swilliam break; 18741085Swilliam } 18841085Swilliam } 18941085Swilliam 19048827Swilliam sector += 1; 19148827Swilliam retry: 19248827Swilliam #ifdef WDDEBUG 19348827Swilliam printf("sec %d sdh %x cylin %d ", sector, 19448827Swilliam WDSD_IBM | (unit<<4) | (head & 0xf), cylin); 19541085Swilliam #endif 19641085Swilliam outb(wdc+wd_precomp, 0xff); 19741085Swilliam outb(wdc+wd_seccnt, 1); 19841085Swilliam outb(wdc+wd_sector, sector); 19941085Swilliam outb(wdc+wd_cyl_lo, cylin); 20041085Swilliam outb(wdc+wd_cyl_hi, cylin >> 8); 20141085Swilliam 20241085Swilliam /* Set up the SDH register (select drive). */ 20341085Swilliam outb(wdc+wd_sdh, WDSD_IBM | (unit<<4) | (head & 0xf)); 20448827Swilliam while ((inb(wdc+wd_status) & WDCS_READY) == 0) ; 20541085Swilliam 20641085Swilliam outb(wdc+wd_command, opcode); 20748827Swilliam while (opcode == WDCC_READ && (inb(wdc+wd_status) & WDCS_BUSY)) 20841085Swilliam ; 20941085Swilliam /* Did we get an error? */ 21048827Swilliam if (opcode == WDCC_READ && (inb(wdc+wd_status) & WDCS_ERR)) 21141085Swilliam goto error; 21241085Swilliam 21341085Swilliam /* Ready to remove data? */ 21448827Swilliam while ((inb(wdc+wd_status) & WDCS_DRQ) == 0) ; 21541085Swilliam 21641085Swilliam if (opcode == WDCC_READ) 21741085Swilliam insw(wdc+wd_data,addr,256); 21841085Swilliam else outsw(wdc+wd_data,addr,256); 21941085Swilliam 22041085Swilliam /* Check data request (should be done). */ 22148827Swilliam if (inb(wdc+wd_status) & WDCS_DRQ) goto error; 22241085Swilliam 22348827Swilliam while (opcode == WDCC_WRITE && (inb(wdc+wd_status) & WDCS_BUSY)) ; 22441085Swilliam 22548827Swilliam if (inb(wdc+wd_status) & WDCS_ERR) goto error; 22641085Swilliam 22748827Swilliam #ifdef WDDEBUG 22848827Swilliam printf("+"); 22948827Swilliam #endif 23041085Swilliam return (0); 23141085Swilliam error: 23248827Swilliam erro = inb(wdc+wd_error); 23341085Swilliam if (++retries < RETRIES) 23441085Swilliam goto retry; 23541085Swilliam if (!wdquiet) 23648827Swilliam #ifdef SMALL 23748827Swilliam printf("wd%d: hard error: sector %d status %x error %x\n", unit, 23848827Swilliam blknm, inb(wdc+wd_status), erro); 23948827Swilliam #else 24048827Swilliam printf("wd%d: hard %s error: sector %d status %b error %b\n", unit, 24141085Swilliam opcode == WDCC_READ? "read" : "write", blknm, 24248827Swilliam inb(wdc+wd_status), WDCS_BITS, erro, WDERR_BITS); 24348827Swilliam #endif 24441085Swilliam return (-1); 24541085Swilliam } 24641085Swilliam 24741085Swilliam wdinit(io) 24841085Swilliam struct iob *io; 24941085Swilliam { 25048827Swilliam register wdc; 25141085Swilliam struct disklabel *dd; 25241085Swilliam unsigned int unit; 25341085Swilliam struct dkbad *db; 25441085Swilliam int i, errcnt = 0; 25541085Swilliam char buf[512]; 25641085Swilliam static open[NWD]; 25741085Swilliam 25848827Swilliam unit = io->i_unit; 25948827Swilliam if (open[unit]) return(0); 26041085Swilliam 26148827Swilliam wdcport = io->i_ctlr ? IO_WD2 : IO_WD1; 26248827Swilliam wdc = wdcport; 26348827Swilliam 26441085Swilliam #ifdef SMALL 26541085Swilliam dd = &disklabel; 26648827Swilliam #else 26748827Swilliam /* reset controller */ 26848827Swilliam outb(wdc+wd_ctlr, 12); wait(10); outb(wdc+wd_ctlr, 8); 26941085Swilliam wdwait(); 27048827Swilliam 27141085Swilliam dd = &wdsizes[unit]; 27241085Swilliam 27341085Swilliam tryagainrecal: 27448827Swilliam /* set SDH, step rate, do restore to recalibrate drive */ 27541085Swilliam outb(wdc+wd_sdh, WDSD_IBM | (unit << 4)); 27641085Swilliam wdwait(); 27741085Swilliam outb(wdc+wd_command, WDCC_RESTORE | WD_STEP); 27841085Swilliam wdwait(); 27941085Swilliam if ((i = inb(wdc+wd_status)) & WDCS_ERR) { 28048827Swilliam /*#ifdef SMALL 28148827Swilliam printf("wd%d: recal status %x error %x\n", 28248827Swilliam unit, i, inb(wdc+wd_error)); 28348827Swilliam #else*/ 28441085Swilliam printf("wd%d: recal status %b error %b\n", 28541085Swilliam unit, i, WDCS_BITS, inb(wdc+wd_error), WDERR_BITS); 28648827Swilliam /*#endif*/ 28741085Swilliam if (++errcnt < 10) 28841085Swilliam goto tryagainrecal; 28941085Swilliam return(-1); 29041085Swilliam } 29148827Swilliam 29248827Swilliam /* 29348827Swilliam * Some controllers require this (after a recal they 29448827Swilliam * revert to a logical translation mode to compensate for 29548827Swilliam * dos limitation on 10-bit cylinders -- *shudder* -wfj) 29648827Swilliam * note: cylinders *must* be fewer than or equal to 8 to 29748827Swilliam * compensate for some IDE drives that latch this for all time. 29848827Swilliam */ 29948827Swilliam outb(wdc+wd_sdh, WDSD_IBM | (unit << 4) + 8 -1); 30048827Swilliam outb(wdc+wd_seccnt, 35 ); 30148827Swilliam outb(wdc+wd_cyl_lo, 1224); 30248827Swilliam outb(wdc+wd_cyl_hi, 1224/256); 30348827Swilliam outb(wdc+wd_command, 0x91); 30448827Swilliam while (inb(wdc+wd_status) & WDCS_BUSY) ; 30548827Swilliam 30641085Swilliam errcnt = 0; 30741085Swilliam retry: 30841085Swilliam /* 30948827Swilliam * Read in LABELSECTOR to get the pack label and geometry. 31041085Swilliam */ 31141085Swilliam outb(wdc+wd_precomp, 0xff); /* sometimes this is head bit 3 */ 31241085Swilliam outb(wdc+wd_seccnt, 1); 31348827Swilliam outb(wdc+wd_sector, LABELSECTOR + 1); 31441085Swilliam outb(wdc+wd_cyl_lo, (cyloffset & 0xff)); 31541085Swilliam outb(wdc+wd_cyl_hi, (cyloffset >> 8)); 31641085Swilliam outb(wdc+wd_sdh, WDSD_IBM | (unit << 4)); 31741085Swilliam wdwait(); 31841085Swilliam outb(wdc+wd_command, WDCC_READ); 31941085Swilliam wdwait(); 32041085Swilliam if ((i = inb(wdc+wd_status)) & WDCS_ERR) { 32148827Swilliam int err; 32248827Swilliam 32348827Swilliam err = inb(wdc+wd_error); 32441085Swilliam if (++errcnt < RETRIES) 32541085Swilliam goto retry; 32641085Swilliam if (!wdquiet) 32748827Swilliam /*#ifdef SMALL 32848827Swilliam printf("wd%d: reading label, status %x error %x\n", 32948827Swilliam unit, i, err); 33048827Swilliam #else*/ 33141085Swilliam printf("wd%d: reading label, status %b error %b\n", 33248827Swilliam unit, i, WDCS_BITS, err, WDERR_BITS); 33348827Swilliam /*#endif*/ 33441085Swilliam return(-1); 33541085Swilliam } 33641085Swilliam 33741085Swilliam /* Ready to remove data? */ 33848827Swilliam while ((inb(wdc+wd_status) & WDCS_DRQ) == 0) ; 33941085Swilliam 34041085Swilliam i = insw(wdc+wd_data, buf, 256); 34141085Swilliam 34248827Swilliam #ifdef WDDEBUG 34348827Swilliam printf("magic %x,insw %x, %x\n", 34448827Swilliam ((struct disklabel *) (buf + LABELOFFSET))->d_magic, i, buf); 34548827Swilliam #endif 34648827Swilliam if (((struct disklabel *) (buf + LABELOFFSET))->d_magic == DISKMAGIC) { 34741085Swilliam *dd = * (struct disklabel *) (buf + LABELOFFSET); 34841085Swilliam open[unit] = 1; 34941085Swilliam } else { 35041085Swilliam if (!wdquiet) 35141085Swilliam printf("wd%d: bad disk label\n", unit); 35241085Swilliam if (io->i_flgs & F_FILE) return(-1); 35341085Swilliam dkbad[unit].bt_bad[0].bt_cyl = -1; 35448827Swilliam dd->d_secpercyl = 1999999 ; dd->d_nsectors = 17 ; 35548827Swilliam dd->d_secsize = 512; 35641085Swilliam outb(wdc+wd_precomp, 0xff); /* force head 3 bit off */ 35741085Swilliam return (0) ; 35841085Swilliam } 35948827Swilliam #endif SMALL 36048827Swilliam #ifdef SMALL 36148827Swilliam #ifdef WDDEBUG 36248827Swilliam printf("magic %x sect %d\n", dd->d_magic, dd->d_nsectors); 36348827Swilliam #endif 36441085Swilliam #endif SMALL 36541085Swilliam 36648827Swilliam 36748827Swilliam /* now that we know the disk geometry, tell the controller */ 36848827Swilliam outb(wdc+wd_cyl_lo, dd->d_ncylinders); 36948827Swilliam outb(wdc+wd_cyl_hi, (dd->d_ncylinders)>>8); 37048827Swilliam outb(wdc+wd_sdh, WDSD_IBM | (unit << 4) + dd->d_ntracks-1); 37148827Swilliam outb(wdc+wd_seccnt, dd->d_nsectors); 37248827Swilliam outb(wdc+wd_command, 0x91); 37348827Swilliam while (inb(wdc+wd_status) & WDCS_BUSY) ; 37448827Swilliam 37548827Swilliam dkbad[unit].bt_bad[0].bt_cyl = -1; 37648827Swilliam outb(wdc+wd_precomp, dd->d_precompcyl / 4); 37748827Swilliam 37841085Swilliam /* 37941085Swilliam * Read bad sector table into memory. 38041085Swilliam */ 38141085Swilliam i = 0; 38241085Swilliam do { 38348827Swilliam int blknm = dd->d_secperunit - dd->d_nsectors + i; 38449080Sbostic errcnt = wdio(F_READ, unit, blknm, buf); 38548827Swilliam } while (errcnt && (i += 2) < 10 && i < dd->d_nsectors); 38641085Swilliam db = (struct dkbad *)(buf); 38748827Swilliam #define DKBAD_MAGIC 0x4321 38841085Swilliam if (errcnt == 0 && db->bt_mbz == 0 && db->bt_flag == DKBAD_MAGIC) 38941085Swilliam dkbad[unit] = *db; 39041085Swilliam else { 39141085Swilliam if (!wdquiet) 39241085Swilliam printf("wd%d: error in bad-sector file\n", unit); 39341085Swilliam dkbad[unit].bt_bad[0].bt_cyl = -1; 39441085Swilliam } 39541085Swilliam return(0); 39641085Swilliam } 39741085Swilliam 39841085Swilliam wdwait() 39941085Swilliam { 40048827Swilliam register wdc = wdcport; 40141085Swilliam register i = 0; 40241085Swilliam 40348827Swilliam while (inb(wdc+wd_status) & WDCS_BUSY) 40441085Swilliam ; 40548827Swilliam while ((inb(wdc+wd_status) & WDCS_READY) == 0) 40641085Swilliam if (i++ > 100000) 40741085Swilliam return(-1); 40841085Swilliam return(0); 40941085Swilliam } 410