141085Swilliam /*-
2*63368Sbostic * Copyright (c) 1990, 1993
3*63368Sbostic * The Regents of the University of California. 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*63368Sbostic * @(#)wd.c 8.1 (Berkeley) 06/11/93
1141085Swilliam */
1241085Swilliam
1341085Swilliam /* device driver for winchester disk */
1441085Swilliam
1556514Sbostic #include <sys/param.h>
1656514Sbostic #include <sys/dkbad.h>
1756514Sbostic #include <sys/disklabel.h>
1841085Swilliam
1956514Sbostic #include <i386/isa/isa.h>
2056514Sbostic #include <i386/isa/wdreg.h>
2156514Sbostic #include <stand/saio.h>
2256514Sbostic
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
wdopen(io)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
wdstrategy(io,func)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 */
wdio(func,unit,blknm,addr)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
wdwait()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