xref: /csrg-svn/sys/vax/stand/up.c (revision 10410)
1*10410Shelge /*	up.c	4.4	83/01/18	*/
29974Ssam 
310023Ssam /*
410023Ssam  * UNIBUS peripheral standalone driver
510023Ssam  * with ECC correction and bad block forwarding.
610023Ssam  * Also supports header operation and write
710023Ssam  * check for data and/or header.
810023Ssam  */
910352Shelge 
109974Ssam #include "../h/param.h"
119974Ssam #include "../h/inode.h"
129974Ssam #include "../h/fs.h"
139974Ssam #include "../h/dkbad.h"
149974Ssam #include "../h/vmmac.h"
159974Ssam 
169974Ssam #include "../vax/pte.h"
179974Ssam #include "../vaxuba/upreg.h"
189974Ssam #include "../vaxuba/ubareg.h"
199974Ssam 
2010023Ssam #include "saio.h"
219974Ssam #include "savax.h"
229974Ssam 
2310352Shelge #define MAXBADDESC	126	/* max number of bad sectors recorded */
2410352Shelge #define SECTSIZ		512	/* sector size in bytes */
2510352Shelge #define HDRSIZ		4	/* number of bytes in sector header */
2610352Shelge #define MAXECC		5	/* max number of bad bits accepted in
2710352Shelge 				 * a soft ecc error when F_ECCLM is set */
2810352Shelge #define	NUPTYPES	3
2910352Shelge 
3010023Ssam u_short	ubastd[] = { 0776700 };
319974Ssam 
329974Ssam char	up_gottype[MAXNUBA*8] = { 0 };
339974Ssam char	up_type[MAXNUBA*8] = { 0 };
349974Ssam short	up_off[] = { 0, 27, 68, -1, -1, -1, -1, 82 };
359974Ssam short	fj_off[] = { 0, 50, 0, -1, -1, -1, -1, 155 };
369974Ssam /* this is called upam instead of am because hp.c has a similar array */
379974Ssam short	upam_off[] = { 0, 32, 0, 668, 723, 778, 668, 98 };
3810023Ssam 
3910352Shelge struct st upst[NUPTYPES] = {
4010023Ssam 	32,	19,	32*19,	823,	up_off,		/* 9300/equiv */
4110023Ssam 	32,	10,	32*10,	823,	fj_off,		/* Fuji 160 */
4210023Ssam 	32,	16,	32*16,	1024,	upam_off,	/* Capricorn */
439974Ssam };
4410023Ssam 
459974Ssam u_char	up_offset[16] = {
469974Ssam 	UPOF_P400, UPOF_M400, UPOF_P400, UPOF_M400,
479974Ssam 	UPOF_P800, UPOF_M800, UPOF_P800, UPOF_M800,
489974Ssam 	UPOF_P1200, UPOF_M1200, UPOF_P1200, UPOF_M1200,
499974Ssam 	0, 0, 0, 0
509974Ssam };
519974Ssam 
5210023Ssam struct  dkbad upbad[MAXNUBA*8];		/* bad sector table */
5310352Shelge int 	sectsiz;			/* real sector size */
5410023Ssam 
559974Ssam upopen(io)
569974Ssam 	register struct iob *io;
579974Ssam {
5810352Shelge 	register unit = io->i_unit;
5910023Ssam 	register struct updevice *upaddr;
6010352Shelge 	register struct st *st = &upst[up_type[unit]];
619974Ssam 
6210023Ssam 	if (io->i_boff < 0 || io->i_boff > 7 || st->off[io->i_boff] == -1)
6310023Ssam 		_stop("up bad unit");
6410352Shelge 	upaddr = (struct updevice *)ubamem(unit, ubastd[0]);
659974Ssam 	while ((upaddr->upcs1 & UP_DVA) == 0) /* infinite wait */
669974Ssam 		;
6710352Shelge 	if (up_gottype[unit] == 0) {
6810023Ssam 		register int i;
6910023Ssam 		struct iob tio;
7010023Ssam 
719974Ssam 		upaddr->uphr = UPHR_MAXTRAK;
7210023Ssam 		for (st = upst; st < &upst[NUPTYPES]; st++)
7310023Ssam 			if (upaddr->uphr == st->ntrak - 1) {
7410352Shelge 				up_type[unit] = st - upst;
7510023Ssam 				break;
7610023Ssam 			}
7710023Ssam 		if (st == &upst[NUPTYPES]) {
7810352Shelge 			printf("up%d: uphr=%x\n", unit, upaddr->uphr);
7910023Ssam 			_stop("unknown drive type");
8010023Ssam 		}
819974Ssam 		upaddr->upcs2 = UPCS2_CLR;
829974Ssam #ifdef DEBUG
8310352Shelge 		printf("Unittype=%d\n",up_type[unit]);
849974Ssam #endif
859974Ssam 
8610023Ssam 		/*
8710023Ssam 		 * Read in the bad sector table:
8810023Ssam 		 *	copy the contents of the io structure
8910023Ssam 		 *	to tio for use during the bb pointer
9010023Ssam 		 *	read operation.
9110023Ssam 		 */
9210023Ssam 		tio = *io;
9310023Ssam 		tio.i_bn = st->nspc * st->ncyl - st->nsect;
949974Ssam 		tio.i_ma = (char *)&upbad[tio.i_unit];
9510023Ssam 		tio.i_cc = sizeof (upbad);
9610023Ssam 		tio.i_flgs |= F_RDDATA;
9710023Ssam 		for (i = 0; i < 5; i++) {
9810023Ssam 			if (upstrategy(&tio, READ) == sizeof (upbad))
9910023Ssam 				break;
1009974Ssam 			tio.i_bn += 2;
1019974Ssam 		}
1029974Ssam 		if (i == 5) {
10310023Ssam 			printf("Unable to read bad sector table\n");
10410352Shelge 			for (i = 0; i < MAXBADDESC; i++) {
10510352Shelge 				upbad[unit].bt_bad[i].bt_cyl = -1;
10610352Shelge 				upbad[unit].bt_bad[i].bt_trksec = -1;
1079974Ssam 			}
1089974Ssam 		}
10910352Shelge 		up_gottype[unit] = 1;
1109974Ssam 	}
1119974Ssam 	io->i_boff = st->off[io->i_boff] * st->nspc;
11210023Ssam 	io->i_flgs &= ~F_TYPEMASK;
1139974Ssam }
1149974Ssam 
1159974Ssam upstrategy(io, func)
1169974Ssam 	register struct iob *io;
1179974Ssam {
11810352Shelge 	int cn, tn, sn;
11910352Shelge 	register unit = io->i_unit;
1209974Ssam 	daddr_t bn;
1219974Ssam 	int recal, info, waitdry;
1229974Ssam 	register struct updevice *upaddr =
12310352Shelge 	    (struct updevice *)ubamem(unit, ubastd[0]);
12410352Shelge 	register struct st *st = &upst[up_type[unit]];
1259974Ssam 
12610352Shelge 	sectsiz = SECTSIZ;
12710352Shelge 	if ((io->i_flgs & (F_HDR|F_CHECK)) != 0)
12810352Shelge 		sectsiz += HDRSIZ;
1299974Ssam 	io->i_errcnt = 0;
13010352Shelge 	recal = 1;
1319974Ssam 	upaddr->upcs2 = unit;
1329974Ssam 	if ((upaddr->upds & UPDS_VV) == 0) {
1339974Ssam 		upaddr->upcs1 = UP_DCLR|UP_GO;
1349974Ssam 		upaddr->upcs1 = UP_PRESET|UP_GO;
1359974Ssam 		upaddr->upof = UPOF_FMT22;
1369974Ssam 	}
1379974Ssam 	if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY)
1389974Ssam 		_stop("up not ready");
1399974Ssam 	info = ubasetup(io, 1);
1409974Ssam 	upaddr->upwc = -io->i_cc / sizeof (short);
1419974Ssam 	upaddr->upba = info;
1429974Ssam readmore:
14310352Shelge 	bn = io->i_bn + (io->i_cc + upaddr->upwc*sizeof(short))/sectsiz;
14410023Ssam 	while((upaddr->upds & UPDS_DRY) == 0)
14510023Ssam 		;
14610352Shelge 	if (upstart(io, bn) != 0) {
14710352Shelge 		ubafree(io, info);
1489974Ssam 		return (-1);
14910352Shelge 	}
1509974Ssam 	do {
1519974Ssam 		DELAY(25);
1529974Ssam 	} while ((upaddr->upcs1 & UP_RDY) == 0);
1539974Ssam 
15410352Shelge 	if (((upaddr->upds&UPDS_ERR) | (upaddr->upcs1&UP_TRE)) == 0 ) {
15510352Shelge 		ubafree(io, info);
1569974Ssam 		return(io->i_cc);
15710352Shelge 	}
1589974Ssam 
1599974Ssam #ifdef LOGALLERRS
1609974Ssam 	printf("uper: (c,t,s)=(%d,%d,%d) cs2=%b er1=%b er2=%b wc=%x\n",
1619974Ssam 			upaddr->updc, upaddr->upda>>8, (upaddr->upda&0x1f-1),
1629974Ssam 		    	upaddr->upcs2, UPCS2_BITS, upaddr->uper1,
1639974Ssam 			UPER1_BITS, upaddr->uper2, UPER2_BITS,-upaddr->upwc);
1649974Ssam #endif
1659974Ssam 	waitdry = 0;
16610352Shelge 	while ((upaddr->upds & UPDS_DRY) == 0 && ++waitdry < sectsiz)
16710023Ssam 		DELAY(5);
1689974Ssam 	if (++io->i_errcnt > 27) {
1699974Ssam 		/*
1709974Ssam 		 * After 28 retries (16 without offset, and
1719974Ssam 		 * 12 with offset positioning) give up.
1729974Ssam 		 */
17310023Ssam 		io->i_error = EHER;
17410023Ssam 		if (upaddr->upcs2 & UPCS2_WCE)
17510023Ssam 			io->i_error = EWCK;
17610352Shelge hard:
17710352Shelge 		bn = io->i_bn + (io->i_cc + upaddr->upwc*sizeof(short))/sectsiz;
1789974Ssam 		cn = bn/st->nspc;
1799974Ssam 		sn = bn%st->nspc;
1809974Ssam 		tn = sn/st->nsect;
1819974Ssam 		sn = sn%st->nsect;
1829974Ssam 		printf("up error: (cyl,trk,sec)=(%d,%d,%d) cs2=%b er1=%b er2=%b\n",
1839974Ssam 		    	cn, tn, sn,
1849974Ssam 		    	upaddr->upcs2, UPCS2_BITS, upaddr->uper1,
1859974Ssam 			UPER1_BITS, upaddr->uper2, UPER2_BITS);
1869974Ssam 		upaddr->upcs1 = UP_TRE|UP_DCLR|UP_GO;
18710352Shelge 		io->i_errblk = bn;
1889974Ssam 		return (io->i_cc + upaddr->upwc*sizeof(short));
1899974Ssam 	} else
1909974Ssam 		if (upaddr->uper1&UPER1_WLE) {
19110023Ssam 			/*
19210023Ssam 			 * Give up on write locked devices
19310023Ssam 			 * immediately.
19410023Ssam 			 */
19510023Ssam 			printf("up%d: write locked\n", unit);
19610023Ssam 			return(-1);
1979974Ssam 		}
1989974Ssam #ifndef NOBADSECT
1999974Ssam 	else if (upaddr->uper2 & UPER2_BSE) {
20010352Shelge 		if (io->i_flgs & F_NBSF) {
20110352Shelge 			io->i_error = EBSE;
20210352Shelge 			goto hard;
20310352Shelge 		}
20410352Shelge 		if (upecc( io, BSE) == 0)
2059974Ssam 			goto success;
2069974Ssam 		else {
20710023Ssam 			io->i_error = EBSE;
2089974Ssam 			goto hard;
2099974Ssam 		}
2109974Ssam 	}
2119974Ssam #endif
2129974Ssam 	else {
2139974Ssam 		/*
2149974Ssam 		 * Retriable error.
2159974Ssam 		 * If a soft ecc, correct it
2169974Ssam 		 * Otherwise fall through and retry the transfer
2179974Ssam 		 */
21810352Shelge 		if ((upaddr->uper1 & UPER1_DCK) != 0) {
21910352Shelge 			/*
22010352Shelge 			 * If a write check command is active, all
22110352Shelge 			 * ecc errors give UPER1_ECH.
22210352Shelge 			 */
22310352Shelge 			if (((upaddr->uper1 & UPER1_ECH) == 0 )||
22410352Shelge 			    ((upaddr->upcs2 & UPCS2_WCE) != 0 )) {
22510352Shelge 				if (upecc(io, ECC) == 0)
22610352Shelge 					goto success;
22710352Shelge 				else {
22810352Shelge 					io->i_error = EECC;
22910352Shelge 					goto hard;
23010352Shelge 				}
23110352Shelge 			}
23210352Shelge 		}
23310352Shelge 		io->i_active = 0; /* else force retry */
2349974Ssam 	}
2359974Ssam 	/*
2369974Ssam 	 * Clear drive error and, every eight attempts,
2379974Ssam 	 * (starting with the fourth)
2389974Ssam 	 * recalibrate to clear the slate.
2399974Ssam 	 */
2409974Ssam 	upaddr->upcs1 = UP_TRE|UP_DCLR|UP_GO;
2419974Ssam 	if ((io->i_errcnt&07) == 4 && io->i_active == 0) {
2429974Ssam 		upaddr->upcs1 = UP_RECAL|UP_GO;
2439974Ssam 		recal = 0;
2449974Ssam 		goto nextrecal;
2459974Ssam 	}
2469974Ssam 	/*
2479974Ssam 	 * Advance recalibration finite state machine
2489974Ssam 	 * if recalibrate in progress, through
2499974Ssam 	 *	RECAL
2509974Ssam 	 *	SEEK
2519974Ssam 	 *	OFFSET (optional)
2529974Ssam 	 *	RETRY
2539974Ssam 	 */
2549974Ssam 	switch (recal) {
2559974Ssam 
2569974Ssam 	case 1:
2579974Ssam 		upaddr->updc = cn;
2589974Ssam 		upaddr->upcs1 = UP_SEEK|UP_GO;
2599974Ssam 		goto nextrecal;
26010023Ssam 
2619974Ssam 	case 2:
2629974Ssam 		if (io->i_errcnt < 16 || (func & READ) == 0)
2639974Ssam 			goto donerecal;
2649974Ssam 		upaddr->upof = up_offset[io->i_errcnt & 017] | UPOF_FMT22;
2659974Ssam 		upaddr->upcs1 = UP_OFFSET|UP_GO;
2669974Ssam 	nextrecal:
2679974Ssam 		recal++;
2689974Ssam 		io->i_active = 1;
2699974Ssam 		goto readmore;
27010023Ssam 
2719974Ssam 	donerecal:
2729974Ssam 	case 3:
2739974Ssam 		recal = 0;
2749974Ssam 		io->i_active = 0;
2759974Ssam 		break;
2769974Ssam 	}
2779974Ssam 	/*
2789974Ssam 	 * If still ``active'', then don't need any more retries.
2799974Ssam 	 */
2809974Ssam 	if (io->i_active) {
2819974Ssam 		/*
2829974Ssam 		 * If we were offset positioning,
2839974Ssam 		 * return to centerline.
2849974Ssam 		 */
2859974Ssam 		if (io->i_errcnt >= 16) {
2869974Ssam 			upaddr->upof = UPOF_FMT22;
2879974Ssam 			upaddr->upcs1 = UP_RTC|UP_GO;
28810023Ssam 			while ((upaddr->upds&UPDS_DRY) == 0)
2899974Ssam 				DELAY(25);
2909974Ssam 		}
2919974Ssam 		goto readmore;
2929974Ssam 	}
2939974Ssam success:
2949974Ssam 	io->i_active = 1;
29510023Ssam 	if (upaddr->upwc != 0)
2969974Ssam 		goto readmore;
2979974Ssam 	/*
2989974Ssam 	 * Release unibus
2999974Ssam 	 */
3009974Ssam 	ubafree(io, info);
3019974Ssam 	return (io->i_cc);
3029974Ssam }
3039974Ssam 
3049974Ssam /*
3059974Ssam  * Correct an ECC error, and restart the i/o to complete
3069974Ssam  * the transfer if necessary.  This is quite complicated because
3079974Ssam  * the transfer may be going to an odd memory address base and/or
3089974Ssam  * across a page boundary.
3099974Ssam  */
31010023Ssam upecc(io, flag)
3119974Ssam 	register struct iob *io;
3129974Ssam 	int flag;
3139974Ssam {
3149974Ssam 	register struct updevice *up =
3159974Ssam 		(struct updevice *)ubamem(io->i_unit, ubastd[0]);
31610352Shelge 	register struct st *st;
3179974Ssam 	register int i;
3189974Ssam 	caddr_t addr;
319*10410Shelge 	int bn, twc, npf, mask, cn, tn, sn;
32010352Shelge 	daddr_t bbn;
3219974Ssam 
3229974Ssam 	/*
3239974Ssam 	 * Npf is the number of sectors transferred before the sector
3249974Ssam 	 * containing the ECC error, bn is the current block number
3259974Ssam 	 */
32610352Shelge 	twc = up->upwc;
32710352Shelge 	npf = ((twc * sizeof(short)) + io->i_cc)/sectsiz;
3289974Ssam #ifdef UPECCDEBUG
3299974Ssam 	printf("npf %d mask 0x%x pos %d wc 0x%x\n",npf,mask,up->upec1,-up->upwc);
3309974Ssam #endif
33110352Shelge 	bn = io->i_bn + npf ;
3329974Ssam 	st = &upst[up_type[io->i_unit]];
3339974Ssam 	io->i_active = 2;
334*10410Shelge 	cn = bn/st->nspc;
335*10410Shelge 	sn = bn%st->nspc;
336*10410Shelge 	tn = sn/st->nsect;
337*10410Shelge 	sn = sn%st->nsect;
3389974Ssam 	/*
3399974Ssam 	 * action taken depends on the flag
3409974Ssam 	 */
3419974Ssam 	if (flag == ECC) {
34210352Shelge 		int bit, byte, ecccnt;
34310352Shelge 		ecccnt = 0;
3449974Ssam 		mask = up->upec2;
34510352Shelge 		printf("up%d: soft ecc sn%d\n", io->i_unit, io->i_bn + npf );
3469974Ssam 		/*
3479974Ssam 		 * Compute the
3489974Ssam 		 * byte and bit position of the error.  The variable i
3499974Ssam 		 * is the byte offset in the transfer.
3509974Ssam 		 */
3519974Ssam 		i = up->upec1 - 1;		/* -1 makes 0 origin */
3529974Ssam 		bit = i&07;
3539974Ssam 		i = (i&~07)>>3;
3549974Ssam 		byte = i;
3559974Ssam 		up->upcs1 = UP_TRE|UP_DCLR|UP_GO;
3569974Ssam 		/*
3579974Ssam 		 * Correct while possible bits remain of mask.  Since mask
3589974Ssam 		 * contains 11 bits, we continue while the bit offset is > -11.
3599974Ssam 		 * Also watch out for end of this block and the end of the whole
3609974Ssam 		 * transfer.
3619974Ssam 		 */
36210352Shelge 		while (i < sectsiz && (npf*sectsiz)+i < io->i_cc && bit > -11) {
3639974Ssam 			/*
36410023Ssam 			 * addr = vax base addr + (number of sectors transferred
3659974Ssam 			 *	  before the error sector times the sector size)
3669974Ssam 			 *	  + byte number
3679974Ssam 			 */
36810352Shelge 			addr = io->i_ma + (npf*sectsiz) + byte;
3699974Ssam #ifdef UPECCDEBUG
37010352Shelge 			printf("addr %x old: %x ",addr, (*addr&0xff));
3719974Ssam #endif
37210352Shelge 			if ((io->i_flgs & (F_CHECK|F_HCHECK)) == 0)
37310352Shelge 				*addr ^= (mask << bit);
3749974Ssam #ifdef UPECCDEBUG
37510352Shelge 			printf("new: %x\n", (*addr&0xff));
3769974Ssam #endif
3779974Ssam 			byte++;
3789974Ssam 			i++;
3799974Ssam 			bit -= 8;
38010352Shelge 			if ((ecccnt++ >= MAXECC) && ((io->i_flgs&F_ECCLM) != 0))
38110352Shelge 				return(1);
3829974Ssam 		}
38310352Shelge 		return(0);
3849974Ssam #ifndef NOBADSECT
3859974Ssam 	} else if (flag == BSE) {
3869974Ssam 		/*
38710352Shelge 		 * if not in bad sector table, return 1 (= hard error)
3889974Ssam 		 */
38910352Shelge 		up->upcs1 = UP_TRE|UP_DCLR|UP_GO;
390*10410Shelge 		if ((bbn = isbad(&upbad[io->i_unit], cn, tn, sn)) < 0)
39110352Shelge 			return(1);
3929974Ssam 		bbn = st->ncyl * st->nspc -st->nsect - 1 - bbn;
39310352Shelge 		twc = up->upwc + sectsiz;
39410352Shelge 		up->upwc = -(sectsiz / sizeof (short));
3959974Ssam #ifdef UPECCDEBUG
3969974Ssam 		printf("revector to block %d\n", bbn);
3979974Ssam #endif
3989974Ssam 		/*
3999974Ssam 	 	* Clear the drive & read the replacement sector.
4009974Ssam 	 	* If this is in the middle of a transfer, then set up the
4019974Ssam 	 	* controller registers in a normal fashion.
4029974Ssam 	 	* The ub-address need not be changed.
4039974Ssam 	 	*/
40410023Ssam 		while (up->upcs1 & UP_RDY == 0)
4059974Ssam 			;
40610023Ssam 		if (upstart(io, bbn) != 0)
40710352Shelge 			return (1);		/* error */
40810352Shelge 		io->i_errcnt = 0;		/* success */
4099974Ssam 		do {
4109974Ssam 			DELAY(25);
4119974Ssam 		} while ( up->upcs1 & UP_RDY == 0) ;
4129974Ssam 		if (up->upds & UPDS_ERR || up->upcs1 & UP_TRE) {
41310352Shelge 			up->upwc = twc -sectsiz;
41410352Shelge 			return (1);
4159974Ssam 		}
4169974Ssam 	}
41710023Ssam 	if (twc != 0)
4189974Ssam 		up->upwc = twc;
41910352Shelge 	return (0);
4209974Ssam }
4219974Ssam 
4229974Ssam upstart(io, bn)
42310023Ssam 	register struct iob *io;
42410023Ssam 	daddr_t bn;
4259974Ssam {
4269974Ssam 	register struct updevice *upaddr =
42710023Ssam 		(struct updevice *)ubamem(io->i_unit, ubastd[0]);
42810352Shelge 	register struct st *st = &upst[up_type[io->i_unit]];
4299974Ssam 	int sn, tn;
4309974Ssam 
4319974Ssam 	sn = bn%st->nspc;
4329974Ssam 	tn = sn/st->nsect;
4339974Ssam 	sn %= st->nsect;
4349974Ssam 	upaddr->updc = bn/st->nspc;
4359974Ssam 	upaddr->upda = (tn << 8) + sn;
43610352Shelge 	switch (io->i_flgs & F_TYPEMASK) {
43710023Ssam 
43810023Ssam 	case F_RDDATA:
43910023Ssam 		upaddr->upcs1 = UP_RCOM|UP_GO;
4409974Ssam 		break;
44110023Ssam 
44210023Ssam 	case F_WRDATA:
44310023Ssam 		upaddr->upcs1 = UP_WCOM|UP_GO;
4449974Ssam 		break;
44510023Ssam 
44610023Ssam 	case F_HDR|F_RDDATA:
44710023Ssam 		upaddr->upcs1 = UP_RHDR|UP_GO;
44810023Ssam 		break;
44910023Ssam 
45010023Ssam 	case F_HDR|F_WRDATA:
45110023Ssam 		upaddr->upcs1 = UP_WHDR|UP_GO;
45210023Ssam 		break;
45310023Ssam 
45410023Ssam 	case F_CHECK|F_WRDATA:
45510023Ssam 	case F_CHECK|F_RDDATA:
4569974Ssam 		upaddr->upcs1 = UP_WCDATA|UP_GO;
4579974Ssam 		break;
45810023Ssam 
45910023Ssam 	case F_HCHECK|F_WRDATA:
46010023Ssam 	case F_HCHECK|F_RDDATA:
4619974Ssam 		upaddr->upcs1 = UP_WCHDR|UP_GO;
4629974Ssam 		break;
46310023Ssam 
4649974Ssam 	default:
46510023Ssam 		io->i_error = ECMD;
46610023Ssam 		io->i_flgs &= ~F_TYPEMASK;
46710023Ssam 		return (1);
4689974Ssam 	}
46910023Ssam 	return (0);
4709974Ssam }
4719974Ssam 
47210023Ssam /*ARGSUSED*/
47310023Ssam upioctl(io, cmd, arg)
47410023Ssam 	struct iob *io;
47510023Ssam 	int cmd;
47610023Ssam 	caddr_t arg;
47710023Ssam {
47810023Ssam 
47910352Shelge 	struct st *st = &upst[up_type[io->i_unit]], *tmp;
48010352Shelge 
48110352Shelge 	switch(cmd) {
48210352Shelge 
48310352Shelge 	case SAIODEVDATA:
48410352Shelge 		tmp = (struct st *)arg;
48510352Shelge 		*tmp = *st;
48610352Shelge 		return(0);
48710352Shelge 
48810352Shelge 	default:
48910352Shelge 		return (ECMD);
49010352Shelge 	}
49110023Ssam }
49210352Shelge 
493