xref: /original-bsd/sys/i386/stand/wd.c (revision 3705696b)
1 /*-
2  * Copyright (c) 1990, 1993
3  *	The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * William Jolitz.
7  *
8  * %sccs.include.redist.c%
9  *
10  *	@(#)wd.c	8.1 (Berkeley) 06/11/93
11  */
12 
13 /*  device driver for winchester disk  */
14 
15 #include <sys/param.h>
16 #include <sys/dkbad.h>
17 #include <sys/disklabel.h>
18 
19 #include <i386/isa/isa.h>
20 #include <i386/isa/wdreg.h>
21 #include <stand/saio.h>
22 
23 #define	NWD		2	/* number of hard disk units supported, max 2 */
24 #define	RETRIES		5	/* number of retries before giving up */
25 
26 int noretries, wdquiet;
27 /*#define WDDEBUG*/
28 
29 #ifdef	SMALL
30 extern struct disklabel disklabel;
31 #else
32 struct disklabel wdsizes[NWD];
33 #endif
34 
35 extern cyloffset ;		/* bootstrap's idea of cylinder for disklabel */
36 
37 /*
38  * Record for the bad block forwarding code.
39  * This is initialized to be empty until the bad-sector table
40  * is read from the disk.
41  */
42 #define TRKSEC(trk,sec)	((trk << 8) + sec)
43 
44 struct	dkbad	dkbad[NWD];
45 static wdcport;
46 
47 wdopen(io)
48 	register struct iob *io;
49 {
50         register struct disklabel *dd;
51 
52 #ifdef WDDEBUG
53 	printf("wdopen ");
54 #endif
55 #ifdef SMALL
56         dd = &disklabel;
57 #else
58         dd = &wdsizes[io->i_unit];
59 	if (io->i_part > 8)
60                 _stop("Invalid partition number");
61 	if(io->i_ctlr > 1)
62                 _stop("Invalid controller number");
63 #endif
64         if (wdinit(io))
65                 _stop("wd initialization error");
66 	io->i_boff = dd->d_partitions[io->i_part].p_offset ;
67 	return(0);
68 }
69 
70 wdstrategy(io,func)
71 	register struct iob *io;
72 {
73 	register int iosize;    /* number of sectors to do IO for this loop */
74 	register daddr_t sector;
75 	int nblocks, cyloff;
76 	int unit, partition;
77 	char *address;
78 	register struct disklabel *dd;
79 
80 	unit = io->i_unit;
81 	partition = io->i_part;
82 #ifdef WDDEBUG
83 	printf("wdstrat %d %d ", unit, partition);
84 #endif
85 #ifdef	SMALL
86 	dd = &disklabel;
87 #else
88 	dd = &wdsizes[unit];
89 #endif
90         iosize = io->i_cc / dd->d_secsize;
91 	/*
92 	 * Convert PGSIZE "blocks" to sectors.
93 	 * Note: doing the conversions this way limits the partition size
94 	 * to about 8 million sectors (1-8 Gb).
95 	 */
96 	sector = (unsigned long) io->i_bn * DEV_BSIZE / dd->d_secsize;
97 	nblocks = dd->d_partitions[partition].p_size;
98 #ifndef SMALL
99         if (iosize < 0 || sector + iosize > nblocks || sector < 0) {
100 #ifdef WDDEBUG
101 		printf("bn = %d; sectors = %d; partition = %d; fssize = %d\n",
102 			io->i_bn, iosize, partition, nblocks);
103 #endif
104                 printf("wdstrategy - I/O out of filesystem boundaries\n");
105 		return(-1);
106 	}
107 	if (io->i_bn * DEV_BSIZE % dd->d_secsize) {
108 		printf("wdstrategy - transfer starts in midsector\n");
109 		return(-1);
110 	}
111         if (io->i_cc % dd->d_secsize) {
112 		printf("wd: transfer of partial sector\n");
113 		return(-1);
114 	}
115 #endif
116 
117 	address = io->i_ma;
118         while (iosize > 0) {
119                 if (wdio(func, unit, sector, address))
120                         return(-1);
121 		iosize--;
122 		sector++;
123                 address += dd->d_secsize;
124         }
125         return(io->i_cc);
126 }
127 
128 /*
129  * Routine to do a one-sector I/O operation, and wait for it
130  * to complete.
131  */
132 wdio(func, unit, blknm, addr)
133         short *addr;
134 {
135 	struct disklabel *dd;
136 	register wdc = wdcport;
137 	struct bt_bad *bt_ptr;
138         int    i;
139 	int retries = 0;
140         long    cylin, head, sector;
141         u_char opcode, erro;
142 
143 #ifdef	SMALL
144 	dd = &disklabel;
145 #else
146 	dd = &wdsizes[unit];
147 #endif
148         if (func == F_WRITE)
149                 opcode = WDCC_WRITE;
150         else
151                 opcode = WDCC_READ;
152 
153         /* Calculate data for output.           */
154         cylin = blknm / dd->d_secpercyl;
155         head = (blknm % dd->d_secpercyl) / dd->d_nsectors;
156         sector = blknm % dd->d_nsectors;
157 
158 	/*
159 	 * See if the current block is in the bad block list.
160 	 */
161 	if (blknm > BBSIZE/DEV_BSIZE)	/* should be BBSIZE */
162 	    for (bt_ptr = dkbad[unit].bt_bad; bt_ptr->bt_cyl != -1; bt_ptr++) {
163 		if (bt_ptr->bt_cyl > cylin)
164 			/* Sorted list, and we passed our cylinder. quit. */
165 			break;
166 		if (bt_ptr->bt_cyl == cylin &&
167 			bt_ptr->bt_trksec == (head << 8) + sector) {
168 			/*
169 			 * Found bad block.  Calculate new block addr.
170 			 * This starts at the end of the disk (skip the
171 			 * last track which is used for the bad block list),
172 			 * and works backwards to the front of the disk.
173 			 */
174 #ifdef WDDEBUG
175 			    printf("--- badblock code -> Old = %d; ",
176 				blknm);
177 #endif
178 			blknm = dd->d_secperunit - dd->d_nsectors
179 				- (bt_ptr - dkbad[unit].bt_bad) - 1;
180 			cylin = blknm / dd->d_secpercyl;
181 			head = (blknm % dd->d_secpercyl) / dd->d_nsectors;
182 			sector = blknm % dd->d_nsectors;
183 #ifdef WDDEBUG
184 			    printf("new = %d\n", blknm);
185 #endif
186 			break;
187 		}
188 	}
189 
190         sector += 1;
191 retry:
192 #ifdef WDDEBUG
193 	printf("sec %d sdh %x cylin %d ", sector,
194 		WDSD_IBM | (unit<<4) | (head & 0xf), cylin);
195 #endif
196 	outb(wdc+wd_precomp, 0xff);
197 	outb(wdc+wd_seccnt, 1);
198 	outb(wdc+wd_sector, sector);
199 	outb(wdc+wd_cyl_lo, cylin);
200 	outb(wdc+wd_cyl_hi, cylin >> 8);
201 
202 	/* Set up the SDH register (select drive).     */
203 	outb(wdc+wd_sdh, WDSD_IBM | (unit<<4) | (head & 0xf));
204 	while ((inb(wdc+wd_status) & WDCS_READY) == 0) ;
205 
206 	outb(wdc+wd_command, opcode);
207 	while (opcode == WDCC_READ && (inb(wdc+wd_status) & WDCS_BUSY))
208 		;
209 	/* Did we get an error?         */
210 	if (opcode == WDCC_READ && (inb(wdc+wd_status) & WDCS_ERR))
211 		goto error;
212 
213 	/* Ready to remove data?        */
214 	while ((inb(wdc+wd_status) & WDCS_DRQ) == 0) ;
215 
216 	if (opcode == WDCC_READ)
217 		insw(wdc+wd_data,addr,256);
218 	else	outsw(wdc+wd_data,addr,256);
219 
220 	/* Check data request (should be done).         */
221 	if (inb(wdc+wd_status) & WDCS_DRQ) goto error;
222 
223 	while (opcode == WDCC_WRITE && (inb(wdc+wd_status) & WDCS_BUSY)) ;
224 
225 	if (inb(wdc+wd_status) & WDCS_ERR) goto error;
226 
227 #ifdef WDDEBUG
228 printf("+");
229 #endif
230         return (0);
231 error:
232 	erro = inb(wdc+wd_error);
233 	if (++retries < RETRIES)
234 		goto retry;
235 	if (!wdquiet)
236 #ifdef	SMALL
237 	    printf("wd%d: hard error: sector %d status %x error %x\n", unit,
238 		blknm, inb(wdc+wd_status), erro);
239 #else
240 	    printf("wd%d: hard %s error: sector %d status %b error %b\n", unit,
241 		opcode == WDCC_READ? "read" : "write", blknm,
242 		inb(wdc+wd_status), WDCS_BITS, erro, WDERR_BITS);
243 #endif
244 	return (-1);
245 }
246 
247 wdinit(io)
248 	struct iob *io;
249 {
250 	register wdc;
251 	struct disklabel *dd;
252         unsigned int   unit;
253 	struct dkbad *db;
254 	int i, errcnt = 0;
255 	char buf[512];
256 	static open[NWD];
257 
258 	unit = io->i_unit;
259 	if (open[unit]) return(0);
260 
261 	wdcport = io->i_ctlr ? IO_WD2 : IO_WD1;
262 	wdc = wdcport;
263 
264 #ifdef	SMALL
265 	dd = &disklabel;
266 #else
267 	/* reset controller */
268 	outb(wdc+wd_ctlr, 12); wait(10); outb(wdc+wd_ctlr, 8);
269 	wdwait();
270 
271 	dd = &wdsizes[unit];
272 
273 tryagainrecal:
274 	/* set SDH, step rate, do restore to recalibrate drive */
275 	outb(wdc+wd_sdh, WDSD_IBM | (unit << 4));
276 	wdwait();
277 	outb(wdc+wd_command, WDCC_RESTORE | WD_STEP);
278 	wdwait();
279 	if ((i = inb(wdc+wd_status)) & WDCS_ERR) {
280 /*#ifdef SMALL
281 		printf("wd%d: recal status %x error %x\n",
282 			unit, i, inb(wdc+wd_error));
283 #else*/
284 		printf("wd%d: recal status %b error %b\n",
285 			unit, i, WDCS_BITS, inb(wdc+wd_error), WDERR_BITS);
286 /*#endif*/
287 		if (++errcnt < 10)
288 			goto tryagainrecal;
289 		return(-1);
290 	}
291 
292 	/*
293 	 * Some controllers require this (after a recal they
294 	 * revert to a logical translation mode to compensate for
295 	 * dos limitation on 10-bit cylinders -- *shudder* -wfj)
296 	 * note: cylinders *must* be fewer than or equal to 8 to
297 	 * compensate for some IDE drives that latch this for all time.
298 	 */
299 	outb(wdc+wd_sdh, WDSD_IBM | (unit << 4) + 8 -1);
300 	outb(wdc+wd_seccnt, 35 );
301 	outb(wdc+wd_cyl_lo, 1224);
302 	outb(wdc+wd_cyl_hi, 1224/256);
303 	outb(wdc+wd_command, 0x91);
304 	while (inb(wdc+wd_status) & WDCS_BUSY) ;
305 
306 	errcnt = 0;
307 retry:
308 	/*
309 	 * Read in LABELSECTOR to get the pack label and geometry.
310 	 */
311 	outb(wdc+wd_precomp, 0xff);	/* sometimes this is head bit 3 */
312 	outb(wdc+wd_seccnt, 1);
313 	outb(wdc+wd_sector, LABELSECTOR + 1);
314 	outb(wdc+wd_cyl_lo, (cyloffset & 0xff));
315 	outb(wdc+wd_cyl_hi, (cyloffset >> 8));
316 	outb(wdc+wd_sdh, WDSD_IBM | (unit << 4));
317 	wdwait();
318 	outb(wdc+wd_command, WDCC_READ);
319 	wdwait();
320 	if ((i = inb(wdc+wd_status)) & WDCS_ERR) {
321 		int err;
322 
323 		err = inb(wdc+wd_error);
324 		if (++errcnt < RETRIES)
325 			goto retry;
326 		if (!wdquiet)
327 /*#ifdef SMALL
328 		    printf("wd%d: reading label, status %x error %x\n",
329 			unit, i, err);
330 #else*/
331 		    printf("wd%d: reading label, status %b error %b\n",
332 			unit, i, WDCS_BITS, err, WDERR_BITS);
333 /*#endif*/
334 		return(-1);
335 	}
336 
337 	/* Ready to remove data?        */
338 	while ((inb(wdc+wd_status) & WDCS_DRQ) == 0) ;
339 
340 	i = insw(wdc+wd_data, buf, 256);
341 
342 #ifdef WDDEBUG
343 	printf("magic %x,insw %x, %x\n",
344 	((struct disklabel *) (buf + LABELOFFSET))->d_magic, i, buf);
345 #endif
346 	if (((struct disklabel *) (buf + LABELOFFSET))->d_magic == DISKMAGIC) {
347 		*dd = * (struct disklabel *) (buf + LABELOFFSET);
348 		open[unit] = 1;
349 	} else {
350 		if (!wdquiet)
351 			printf("wd%d: bad disk label\n", unit);
352 		if (io->i_flgs & F_FILE) return(-1);
353 		dkbad[unit].bt_bad[0].bt_cyl = -1;
354 		dd->d_secpercyl = 1999999 ; dd->d_nsectors = 17 ;
355 		dd->d_secsize = 512;
356 		outb(wdc+wd_precomp, 0xff);	/* force head 3 bit off */
357 		return (0) ;
358 	}
359 #endif SMALL
360 #ifdef SMALL
361 #ifdef WDDEBUG
362 	printf("magic %x sect %d\n", dd->d_magic, dd->d_nsectors);
363 #endif
364 #endif	SMALL
365 
366 
367 	/* now that we know the disk geometry, tell the controller */
368 	outb(wdc+wd_cyl_lo, dd->d_ncylinders);
369 	outb(wdc+wd_cyl_hi, (dd->d_ncylinders)>>8);
370 	outb(wdc+wd_sdh, WDSD_IBM | (unit << 4) + dd->d_ntracks-1);
371 	outb(wdc+wd_seccnt, dd->d_nsectors);
372 	outb(wdc+wd_command, 0x91);
373 	while (inb(wdc+wd_status) & WDCS_BUSY) ;
374 
375 	dkbad[unit].bt_bad[0].bt_cyl = -1;
376 	outb(wdc+wd_precomp, dd->d_precompcyl / 4);
377 
378 	/*
379 	 * Read bad sector table into memory.
380 	 */
381 	i = 0;
382 	do {
383 		int blknm = dd->d_secperunit - dd->d_nsectors + i;
384 		errcnt = wdio(F_READ, unit, blknm, buf);
385 	} while (errcnt && (i += 2) < 10 && i < dd->d_nsectors);
386 	db = (struct dkbad *)(buf);
387 #define DKBAD_MAGIC 0x4321
388 	if (errcnt == 0 && db->bt_mbz == 0 && db->bt_flag == DKBAD_MAGIC)
389 		dkbad[unit] = *db;
390 	else {
391 		if (!wdquiet)
392 			printf("wd%d: error in bad-sector file\n", unit);
393 		dkbad[unit].bt_bad[0].bt_cyl = -1;
394 	}
395 	return(0);
396 }
397 
398 wdwait()
399 {
400 	register wdc = wdcport;
401 	register i = 0;
402 
403 	while (inb(wdc+wd_status) & WDCS_BUSY)
404 		;
405 	while ((inb(wdc+wd_status) & WDCS_READY) == 0)
406 		if (i++ > 100000)
407 			return(-1);
408 	return(0);
409 }
410