xref: /original-bsd/sys/vax/mba/ht.c (revision ad93c43e)
1 /*
2  * Copyright (c) 1982, 1986 Regents of the University of California.
3  * All rights reserved.  The Berkeley software License Agreement
4  * specifies the terms and conditions for redistribution.
5  *
6  *	@(#)ht.c	7.5 (Berkeley) 06/18/87
7  */
8 
9 #include "tu.h"
10 #if NHT > 0
11 /*
12  * TM03/TU?? tape driver
13  *
14  * TODO:
15  *	cleanup messages on errors
16  *	test ioctl's
17  *	see how many rewind interrups we get if we kick when not at BOT
18  *	fixup rle error on block tape code
19  */
20 #include "param.h"
21 #include "systm.h"
22 #include "buf.h"
23 #include "conf.h"
24 #include "dir.h"
25 #include "file.h"
26 #include "user.h"
27 #include "map.h"
28 #include "ioctl.h"
29 #include "mtio.h"
30 #include "cmap.h"
31 #include "uio.h"
32 #include "tty.h"
33 #include "syslog.h"
34 
35 #include "../machine/pte.h"
36 #include "../vax/cpu.h"
37 #include "mbareg.h"
38 #include "mbavar.h"
39 #include "htreg.h"
40 
41 struct	buf	rhtbuf[NHT];
42 struct	buf	chtbuf[NHT];
43 
44 short	httypes[] =
45 	{ MBDT_TM03, MBDT_TE16, MBDT_TU45, MBDT_TU77, 0 };
46 struct	mba_device *htinfo[NHT];
47 struct	mba_slave *tuinfo[NTU];
48 int	htattach(), htslave(), htustart(), htndtint(), htdtint();
49 struct	mba_driver htdriver =
50     { htattach, htslave, htustart, 0, htdtint, htndtint,
51       httypes, "ht", "tu", htinfo };
52 
53 #define MASKREG(r)	((r) & 0xffff)
54 
55 /* bits in minor device */
56 #define	TUUNIT(dev)	(minor(dev)&03)
57 #define	H_NOREWIND	04
58 #define	H_DENS(dev)	((minor(dev) >> 3) & 03)
59 
60 #define HTUNIT(dev)	(tutoht[TUUNIT(dev)])
61 
62 #define	INF	(daddr_t)1000000L	/* a block number that wont exist */
63 
64 struct	tu_softc {
65 	char	sc_openf;
66 	char	sc_flags;
67 	daddr_t	sc_blkno;
68 	daddr_t	sc_nxrec;
69 	u_short	sc_erreg;
70 	u_short	sc_dsreg;
71 	short	sc_resid;
72 	short	sc_dens;
73 	struct	mba_device *sc_mi;
74 	int	sc_slave;
75 	struct	tty *sc_ttyp;		/* record user's tty for errors */
76 	int	sc_blks;	/* number of I/O operations since open */
77 	int	sc_softerrs;	/* number of soft I/O errors since open */
78 } tu_softc[NTU];
79 short	tutoht[NTU];
80 
81 /*
82  * Bits for sc_flags.
83  */
84 #define	H_WRITTEN 1	/* last operation was a write */
85 #define H_ERASED  2	/* last write retry was an erase gap */
86 #define H_REWIND  4	/* last unit start was a rewind */
87 
88 char	hter_bits[] = HTER_BITS;
89 char	htds_bits[] = HTDS_BITS;
90 
91 /*ARGSUSED*/
92 htattach(mi)
93 	struct mba_device *mi;
94 {
95 
96 }
97 
98 htslave(mi, ms, sn)
99 	struct mba_device *mi;
100 	struct mba_slave *ms;
101 	int sn;
102 {
103 	register struct tu_softc *sc = &tu_softc[ms->ms_unit];
104 	register struct htdevice *htaddr = (struct htdevice *)mi->mi_drv;
105 
106 	htaddr->httc = sn;
107 	if (htaddr->htdt & HTDT_SPR) {
108 		sc->sc_mi = mi;
109 		sc->sc_slave = sn;
110 		tuinfo[ms->ms_unit] = ms;
111 		tutoht[ms->ms_unit] = mi->mi_unit;
112 		return (1);
113 	} else
114 		return (0);
115 }
116 
117 int	htdens[4] = { HTTC_800BPI, HTTC_1600BPI, HTTC_6250BPI, HTTC_800BPI };
118 
119 htopen(dev, flag)
120 	dev_t dev;
121 	int flag;
122 {
123 	register int tuunit;
124 	register struct mba_device *mi;
125 	register struct tu_softc *sc;
126 	int olddens, dens;
127 
128 	tuunit = TUUNIT(dev);
129 	if (tuunit >= NTU || tuinfo[tuunit]->ms_alive == 0 ||
130 	    (mi = htinfo[HTUNIT(dev)]) == 0 || mi->mi_alive == 0)
131 		return (ENXIO);
132 	if ((sc = &tu_softc[tuunit])->sc_openf)
133 		return (EBUSY);
134 	sc->sc_openf = 1;
135 	olddens = sc->sc_dens;
136 	dens = sc->sc_dens = htdens[H_DENS(dev)] | HTTC_PDP11 | sc->sc_slave;
137 	htcommand(dev, HT_SENSE, 1);
138 	sc->sc_dens = olddens;
139 	if ((sc->sc_dsreg & HTDS_MOL) == 0) {
140 		sc->sc_openf = 0;
141 		uprintf("tu%d: not online\n", tuunit);
142 		return (EIO);
143 	}
144 	if ((flag&FWRITE) && (sc->sc_dsreg&HTDS_WRL)) {
145 		sc->sc_openf = 0;
146 		uprintf("tu%d: no write ring\n", tuunit);
147 		return (EIO);
148 	}
149 	if ((sc->sc_dsreg & HTDS_BOT) == 0 && (flag&FWRITE) &&
150 	    dens != sc->sc_dens) {
151 		sc->sc_openf = 0;
152 		uprintf("tu%d: can't change density in mid-tape\n", tuunit);
153 		return (EIO);
154 	}
155 	sc->sc_blkno = (daddr_t)0;
156 	sc->sc_nxrec = INF;
157 	sc->sc_flags = 0;
158 	sc->sc_dens = dens;
159 	sc->sc_blks = 0;
160 	sc->sc_softerrs = 0;
161 	sc->sc_ttyp = u.u_ttyp;
162 	return (0);
163 }
164 
165 htclose(dev, flag)
166 	register dev_t dev;
167 	register flag;
168 {
169 	register struct tu_softc *sc = &tu_softc[TUUNIT(dev)];
170 
171 	if (flag == FWRITE || ((flag&FWRITE) && (sc->sc_flags&H_WRITTEN))) {
172 		htcommand(dev, HT_WEOF, 1);
173 		htcommand(dev, HT_WEOF, 1);
174 		htcommand(dev, HT_SREV, 1);
175 	}
176 	if ((minor(dev)&H_NOREWIND) == 0)
177 		htcommand(dev, HT_REW, 0);
178 	if (sc->sc_blks > 100 && sc->sc_softerrs > sc->sc_blks / 100)
179 		log(LOG_INFO, "tu%d: %d soft errors in %d blocks\n",
180 		    TUUNIT(dev), sc->sc_softerrs, sc->sc_blks);
181 	sc->sc_openf = 0;
182 }
183 
184 htcommand(dev, com, count)
185 	dev_t dev;
186 	int com, count;
187 {
188 	register struct buf *bp;
189 	register int s;
190 
191 	bp = &chtbuf[HTUNIT(dev)];
192 	s = spl5();
193 	while (bp->b_flags&B_BUSY) {
194 		if(bp->b_repcnt == 0 && (bp->b_flags&B_DONE))
195 			break;
196 		bp->b_flags |= B_WANTED;
197 		sleep((caddr_t)bp, PRIBIO);
198 	}
199 	bp->b_flags = B_BUSY|B_READ;
200 	splx(s);
201 	bp->b_dev = dev;
202 	bp->b_command = com;
203 	bp->b_repcnt = count;
204 	bp->b_blkno = 0;
205 	htstrategy(bp);
206 	if (count == 0)
207 		return;
208 	iowait(bp);
209 	if (bp->b_flags&B_WANTED)
210 		wakeup((caddr_t)bp);
211 	bp->b_flags &= B_ERROR;
212 }
213 
214 htstrategy(bp)
215 	register struct buf *bp;
216 {
217 	register struct mba_device *mi = htinfo[HTUNIT(bp->b_dev)];
218 	register struct buf *dp;
219 	register int s;
220 
221 	bp->av_forw = NULL;
222 	dp = &mi->mi_tab;
223 	s = spl5();
224 	if (dp->b_actf == NULL)
225 		dp->b_actf = bp;
226 	else
227 		dp->b_actl->av_forw = bp;
228 	dp->b_actl = bp;
229 	if (dp->b_active == 0)
230 		mbustart(mi);
231 	splx(s);
232 }
233 
234 htustart(mi)
235 	register struct mba_device *mi;
236 {
237 	register struct htdevice *htaddr =
238 	    (struct htdevice *)mi->mi_drv;
239 	register struct buf *bp = mi->mi_tab.b_actf;
240 	register struct tu_softc *sc = &tu_softc[TUUNIT(bp->b_dev)];
241 	daddr_t blkno;
242 
243 	htaddr->httc = sc->sc_dens;
244 #ifdef	notdef
245 	/* unneeded, may hang controller */
246 	if (bp == &chtbuf[HTUNIT(bp->b_dev)] && bp->b_command == HT_SENSE) {
247 		htaddr->htcs1 = HT_SENSE|HT_GO;
248 		mbclrattn(mi);
249 	}
250 #endif
251 	sc->sc_dsreg = htaddr->htds;
252 	sc->sc_erreg = htaddr->hter;
253 	sc->sc_resid = htaddr->htfc;
254 	sc->sc_flags &= ~(H_WRITTEN|H_REWIND);
255 	if ((htaddr->htdt & HTDT_SPR) == 0 || (htaddr->htds & HTDS_MOL) == 0)
256 		if (sc->sc_openf > 0)
257 			sc->sc_openf = -1;
258 	if (sc->sc_openf < 0) {
259 		bp->b_flags |= B_ERROR;
260 		return (MBU_NEXT);
261 	}
262 	if (bp != &chtbuf[HTUNIT(bp->b_dev)]) {
263 		if (bdbtofsb(bp->b_blkno) > sc->sc_nxrec) {
264 			bp->b_flags |= B_ERROR;
265 			bp->b_error = ENXIO;
266 			return (MBU_NEXT);
267 		}
268 		if (bdbtofsb(bp->b_blkno) == sc->sc_nxrec &&
269 		    bp->b_flags&B_READ) {
270 			bp->b_resid = bp->b_bcount;
271 			clrbuf(bp);
272 			return (MBU_NEXT);
273 		}
274 		if ((bp->b_flags&B_READ)==0)
275 			sc->sc_nxrec = bdbtofsb(bp->b_blkno) + 1;
276 	} else {
277 		if (bp->b_command == HT_SENSE)
278 			return (MBU_NEXT);
279 		if (bp->b_command == HT_REW)
280 			sc->sc_flags |= H_REWIND;
281 		else
282 			htaddr->htfc = -bp->b_bcount;
283 		htaddr->htcs1 = bp->b_command|HT_GO;
284 		return (MBU_STARTED);
285 	}
286 	if ((blkno = sc->sc_blkno) == bdbtofsb(bp->b_blkno)) {
287 		htaddr->htfc = -bp->b_bcount;
288 		if ((bp->b_flags&B_READ) == 0) {
289 			if (mi->mi_tab.b_errcnt) {
290 				if ((sc->sc_flags & H_ERASED) == 0) {
291 					sc->sc_flags |= H_ERASED;
292 					htaddr->htcs1 = HT_ERASE | HT_GO;
293 					return (MBU_STARTED);
294 				}
295 				sc->sc_flags &= ~H_ERASED;
296 			}
297 			if (htaddr->htds & HTDS_EOT) {
298 				bp->b_resid = bp->b_bcount;
299 				bp->b_flags |= B_ERROR;
300 				return (MBU_NEXT);
301 			}
302 		}
303 		return (MBU_DODATA);
304 	}
305 	if (blkno < bdbtofsb(bp->b_blkno)) {
306 		htaddr->htfc = blkno - bdbtofsb(bp->b_blkno);
307 		htaddr->htcs1 = HT_SFORW|HT_GO;
308 	} else {
309 		htaddr->htfc = bdbtofsb(bp->b_blkno) - blkno;
310 		htaddr->htcs1 = HT_SREV|HT_GO;
311 	}
312 	return (MBU_STARTED);
313 }
314 
315 htdtint(mi, mbsr)
316 	register struct mba_device *mi;
317 	int mbsr;
318 {
319 	register struct htdevice *htaddr = (struct htdevice *)mi->mi_drv;
320 	register struct buf *bp = mi->mi_tab.b_actf;
321 	register struct tu_softc *sc;
322 	int ds, er, mbs;
323 
324 	sc = &tu_softc[TUUNIT(bp->b_dev)];
325 	ds = sc->sc_dsreg = MASKREG(htaddr->htds);
326 	er = sc->sc_erreg = MASKREG(htaddr->hter);
327 	sc->sc_resid = MASKREG(htaddr->htfc);
328 	mbs = mbsr;
329 	sc->sc_blkno++;
330 	if((bp->b_flags & B_READ) == 0)
331 		sc->sc_flags |= H_WRITTEN;
332 	if ((ds&(HTDS_ERR|HTDS_MOL)) != HTDS_MOL || mbs & MBSR_EBITS) {
333 		htaddr->htcs1 = HT_DCLR|HT_GO;
334 		mbclrattn(mi);
335 		if (bp == &rhtbuf[HTUNIT(bp->b_dev)]) {
336 			er &= ~HTER_FCE;
337 			mbs &= ~(MBSR_DTABT|MBSR_MBEXC);
338 		}
339 		if (bp->b_flags & B_READ && ds & HTDS_PES)
340 			er &= ~(HTER_CSITM|HTER_CORCRC);
341 		if (er&HTER_HARD || mbs&MBSR_EBITS || (ds&HTDS_MOL) == 0 ||
342 		    er && ++mi->mi_tab.b_errcnt >= 7) {
343 			if ((ds & HTDS_MOL) == 0 && sc->sc_openf > 0)
344 				sc->sc_openf = -1;
345 			if ((er&HTER_HARD) == HTER_FCE &&
346 			    (mbs&MBSR_EBITS) == (MBSR_DTABT|MBSR_MBEXC) &&
347 			    (ds&HTDS_MOL))
348 				goto noprint;
349 			tprintf(sc->sc_ttyp, "tu%d: hard error bn%d mbsr=%b er=%b ds=%b\n",
350 			    TUUNIT(bp->b_dev), bp->b_blkno,
351 			    mbsr, mbsr_bits,
352 			    sc->sc_erreg, hter_bits,
353 			    sc->sc_dsreg, htds_bits);
354 noprint:
355 			bp->b_flags |= B_ERROR;
356 			return (MBD_DONE);
357 		}
358 		if (er)
359 			return (MBD_RETRY);
360 	}
361 	bp->b_resid = 0;
362 	sc->sc_blks++;
363 	if (mi->mi_tab.b_errcnt)
364 		sc->sc_softerrs++;
365 	if (bp->b_flags & B_READ)
366 		if (ds&HTDS_TM) {		/* must be a read, right? */
367 			bp->b_resid = bp->b_bcount;
368 			sc->sc_nxrec = bdbtofsb(bp->b_blkno);
369 		} else if(bp->b_bcount > MASKREG(htaddr->htfc))
370 			bp->b_resid = bp->b_bcount - MASKREG(htaddr->htfc);
371 	return (MBD_DONE);
372 }
373 
374 htndtint(mi)
375 	register struct mba_device *mi;
376 {
377 	register struct htdevice *htaddr = (struct htdevice *)mi->mi_drv;
378 	register struct buf *bp = mi->mi_tab.b_actf;
379 	register struct tu_softc *sc;
380 	int er, ds, fc;
381 
382 	ds = MASKREG(htaddr->htds);
383 	er = MASKREG(htaddr->hter);
384 	fc = MASKREG(htaddr->htfc);
385 	if (er) {
386 		htaddr->htcs1 = HT_DCLR|HT_GO;
387 		mbclrattn(mi);
388 	}
389 	if (bp == 0)
390 		return (MBN_SKIP);
391 	sc = &tu_softc[TUUNIT(bp->b_dev)];
392 	sc->sc_dsreg = ds;
393 	sc->sc_erreg = er;
394 	sc->sc_resid = fc;
395 	if (bp == &chtbuf[HTUNIT(bp->b_dev)]) {
396 		switch ((int)bp->b_command) {
397 		case HT_REWOFFL:
398 			/* offline is on purpose; don't do anything special */
399 			ds |= HTDS_MOL;
400 			break;
401 		case HT_SREV:
402 			/* if backspace file hit bot, its not an error */
403 		        if (er == (HTER_NEF|HTER_FCE) && ds&HTDS_BOT &&
404 			    bp->b_repcnt == INF)
405 				er &= ~HTER_NEF;
406 			break;
407 		}
408 		er &= ~HTER_FCE;
409 		if (er == 0)
410 			ds &= ~HTDS_ERR;
411 	}
412 	if ((ds & (HTDS_ERR|HTDS_MOL)) != HTDS_MOL) {
413 		if ((ds & HTDS_MOL) == 0 && sc->sc_openf > 0)
414 			sc->sc_openf = -1;
415 		tprintf(sc->sc_ttyp, "tu%d: hard error bn%d er=%b ds=%b\n",
416 		    TUUNIT(bp->b_dev), bp->b_blkno,
417 		    sc->sc_erreg, hter_bits, sc->sc_dsreg, htds_bits);
418 		bp->b_flags |= B_ERROR;
419 		return (MBN_DONE);
420 	}
421 	if (bp == &chtbuf[HTUNIT(bp->b_dev)]) {
422 		if (sc->sc_flags & H_REWIND)
423 			return (ds & HTDS_BOT ? MBN_DONE : MBN_RETRY);
424 		bp->b_resid = -sc->sc_resid;
425 		return (MBN_DONE);
426 	}
427 	if (ds & HTDS_TM)
428 		if (sc->sc_blkno > bdbtofsb(bp->b_blkno)) {
429 			sc->sc_nxrec = bdbtofsb(bp->b_blkno) - fc;
430 			sc->sc_blkno = sc->sc_nxrec;
431 		} else {
432 			sc->sc_blkno = bdbtofsb(bp->b_blkno) + fc;
433 			sc->sc_nxrec = sc->sc_blkno - 1;
434 		}
435 	else
436 		sc->sc_blkno = bdbtofsb(bp->b_blkno);
437 	return (MBN_RETRY);
438 }
439 
440 htread(dev, uio)
441 	dev_t dev;
442 	struct uio *uio;
443 {
444 	int errno;
445 
446 	errno = htphys(dev, uio);
447 	if (errno)
448 		return (errno);
449 	return (physio(htstrategy, &rhtbuf[HTUNIT(dev)], dev, B_READ, minphys, uio));
450 }
451 
452 htwrite(dev, uio)
453 	dev_t dev;
454 	struct uio *uio;
455 {
456 	int errno;
457 
458 	errno = htphys(dev, uio);
459 	if (errno)
460 		return (errno);
461 	return (physio(htstrategy, &rhtbuf[HTUNIT(dev)], dev, B_WRITE, minphys, uio));
462 }
463 
464 htphys(dev, uio)
465 	dev_t dev;
466 	struct uio *uio;
467 {
468 	register int htunit;
469 	register struct tu_softc *sc;
470 	register struct mba_device *mi;
471 	daddr_t a;
472 
473 	htunit = HTUNIT(dev);
474 	if (htunit >= NHT || (mi = htinfo[htunit]) == 0 || mi->mi_alive == 0)
475 		return (ENXIO);
476 	a = uio->uio_offset >> 9;
477 	sc = &tu_softc[TUUNIT(dev)];
478 	sc->sc_blkno = bdbtofsb(a);
479 	sc->sc_nxrec = bdbtofsb(a)+1;
480 	return (0);
481 }
482 
483 /*ARGSUSED*/
484 htioctl(dev, cmd, data, flag)
485 	dev_t dev;
486 	int cmd;
487 	caddr_t data;
488 	int flag;
489 {
490 	register struct tu_softc *sc = &tu_softc[TUUNIT(dev)];
491 	register struct buf *bp = &chtbuf[HTUNIT(dev)];
492 	register callcount;
493 	int fcount;
494 	struct mtop *mtop;
495 	struct mtget *mtget;
496 	/* we depend of the values and order of the MT codes here */
497 	static htops[] =
498    {HT_WEOF,HT_SFORW,HT_SREV,HT_SFORW,HT_SREV,HT_REW,HT_REWOFFL,HT_SENSE};
499 
500 	switch (cmd) {
501 
502 	case MTIOCTOP:	/* tape operation */
503 		mtop = (struct mtop *)data;
504 		switch (mtop->mt_op) {
505 
506 		case MTWEOF:
507 			callcount = mtop->mt_count;
508 			fcount = 1;
509 			break;
510 
511 		case MTFSF: case MTBSF:
512 			callcount = mtop->mt_count;
513 			fcount = INF;
514 			break;
515 
516 		case MTFSR: case MTBSR:
517 			callcount = 1;
518 			fcount = mtop->mt_count;
519 			break;
520 
521 		case MTREW: case MTOFFL:
522 			callcount = 1;
523 			fcount = 1;
524 			break;
525 
526 		default:
527 			return (ENXIO);
528 		}
529 		if (callcount <= 0 || fcount <= 0)
530 			return (EINVAL);
531 		while (--callcount >= 0) {
532 			htcommand(dev, htops[mtop->mt_op], fcount);
533 			if ((mtop->mt_op == MTFSR || mtop->mt_op == MTBSR) &&
534 			    bp->b_resid)
535 				return (EIO);
536 			if ((bp->b_flags&B_ERROR) || sc->sc_dsreg&HTDS_BOT)
537 				break;
538 		}
539 		return (geterror(bp));
540 
541 	case MTIOCGET:
542 		mtget = (struct mtget *)data;
543 		mtget->mt_dsreg = sc->sc_dsreg;
544 		mtget->mt_erreg = sc->sc_erreg;
545 		mtget->mt_resid = sc->sc_resid;
546 		mtget->mt_type = MT_ISHT;
547 		break;
548 
549 	default:
550 		return (ENXIO);
551 	}
552 	return (0);
553 }
554 
555 #define	DBSIZE	20
556 
557 htdump()
558 {
559 	register struct mba_device *mi;
560 	register struct mba_regs *mp;
561 	register struct htdevice *htaddr;
562 	int blk, num;
563 	int start;
564 
565 	start = 0;
566 	num = maxfree;
567 #define	phys(a,b)		((b)((int)(a)&0x7fffffff))
568 	if (htinfo[0] == 0)
569 		return (ENXIO);
570 	mi = phys(htinfo[0], struct mba_device *);
571 	mp = phys(mi->mi_hd, struct mba_hd *)->mh_physmba;
572 	mp->mba_cr = MBCR_IE;
573 	htaddr = (struct htdevice *)&mp->mba_drv[mi->mi_drive];
574 	htaddr->httc = HTTC_PDP11|HTTC_1600BPI;
575 	htaddr->htcs1 = HT_DCLR|HT_GO;
576 	while (num > 0) {
577 		blk = num > DBSIZE ? DBSIZE : num;
578 		htdwrite(start, blk, htaddr, mp);
579 		start += blk;
580 		num -= blk;
581 	}
582 	hteof(htaddr);
583 	hteof(htaddr);
584 	htwait(htaddr);
585 	if (htaddr->htds&HTDS_ERR)
586 		return (EIO);
587 	htaddr->htcs1 = HT_REW|HT_GO;
588 	return (0);
589 }
590 
591 htdwrite(dbuf, num, htaddr, mp)
592 	register dbuf, num;
593 	register struct htdevice *htaddr;
594 	struct mba_regs *mp;
595 {
596 	register struct pte *io;
597 	register int i;
598 
599 	htwait(htaddr);
600 	io = mp->mba_map;
601 	for (i = 0; i < num; i++)
602 		*(int *)io++ = dbuf++ | PG_V;
603 	htaddr->htfc = -(num*NBPG);
604 	mp->mba_sr = -1;
605 	mp->mba_bcr = -(num*NBPG);
606 	mp->mba_var = 0;
607 	htaddr->htcs1 = HT_WCOM|HT_GO;
608 }
609 
610 htwait(htaddr)
611 	struct htdevice *htaddr;
612 {
613 	register s;
614 
615 	do
616 		s = htaddr->htds;
617 	while ((s & HTDS_DRY) == 0);
618 }
619 
620 hteof(htaddr)
621 	struct htdevice *htaddr;
622 {
623 
624 	htwait(htaddr);
625 	htaddr->htcs1 = HT_WEOF|HT_GO;
626 }
627 #endif
628