cleanup, time is a struct timeval you know
[unix-history] / usr / src / sys / vax / uba / up.c
CommitLineData
66923854 1/* up.c 4.74 83/05/27 */
008c0481 2
66b4fb09 3#include "up.h"
a3cb8f60 4#if NSC > 0
008c0481 5/*
7ed3da3d
BJ
6 * UNIBUS disk driver with:
7 * overlapped seeks,
8 * ECC recovery, and
9 * bad sector forwarding.
0c48c799
BJ
10 *
11 * TODO:
299d67ed 12 * Check that offset recovery code works
008c0481 13 */
961945a8 14#include "../machine/pte.h"
008c0481
BJ
15
16#include "../h/param.h"
17#include "../h/systm.h"
41888f16 18#include "../h/dk.h"
3b186c1c 19#include "../h/dkbad.h"
008c0481
BJ
20#include "../h/buf.h"
21#include "../h/conf.h"
22#include "../h/dir.h"
23#include "../h/user.h"
24#include "../h/map.h"
008c0481 25#include "../h/vm.h"
0ff318b2 26#include "../h/cmap.h"
740e4029 27#include "../h/uio.h"
fb16fdca 28#include "../h/kernel.h"
7ed3da3d 29#include "../h/dkbad.h"
18eded4b
BJ
30#include "../vax/cpu.h"
31#include "../vax/nexus.h"
32#include "../vaxuba/ubavar.h"
33#include "../vaxuba/ubareg.h"
34#include "../vaxuba/upreg.h"
008c0481 35
3f3a34c3
BJ
36struct up_softc {
37 int sc_softas;
71236e46 38 int sc_ndrive;
3f3a34c3 39 int sc_wticks;
736772ef 40 int sc_recal;
a3cb8f60 41} up_softc[NSC];
008c0481 42
3f3a34c3 43/* THIS SHOULD BE READ OFF THE PACK, PER DRIVE */
f4ce106d 44struct size {
008c0481
BJ
45 daddr_t nblocks;
46 int cyloff;
991f4b05 47} up9300_sizes[8] = {
c94d7fe4
BJ
48#ifdef ERNIE
49 49324, 0, /* A=cyl 0 thru 26 */
50#else
008c0481 51 15884, 0, /* A=cyl 0 thru 26 */
c94d7fe4 52#endif
008c0481 53 33440, 27, /* B=cyl 27 thru 81 */
d1778415 54 495520, 0, /* C=cyl 0 thru 814 */
008c0481
BJ
55 15884, 562, /* D=cyl 562 thru 588 */
56 55936, 589, /* E=cyl 589 thru 680 */
0d728200
BJ
57 81376, 681, /* F=cyl 681 thru 814 */
58 153728, 562, /* G=cyl 562 thru 814 */
008c0481 59 291346, 82, /* H=cyl 82 thru 561 */
991f4b05
SL
60}, up9766_sizes[8] = {
61 15884, 0, /* A=cyl 0 thru 26 */
62 33440, 27, /* B=cyl 27 thru 81 */
63 500384, 0, /* C=cyl 0 thru 822 */
64 15884, 562, /* D=cyl 562 thru 588 */
65 55936, 589, /* E=cyl 589 thru 680 */
66 86240, 681, /* F=cyl 681 thru 822 */
67 158592, 562, /* G=cyl 562 thru 822 */
68 291346, 82, /* H=cyl 82 thru 561 */
69}, up160_sizes[8] = {
3f3a34c3
BJ
70 15884, 0, /* A=cyl 0 thru 49 */
71 33440, 50, /* B=cyl 50 thru 154 */
72 263360, 0, /* C=cyl 0 thru 822 */
991f4b05
SL
73 15884, 155, /* D=cyl 155 thru 204 */
74 55936, 205, /* E=cyl 205 thru 379 */
75 141664, 380, /* F=cyl 380 thru 822 */
76 213664, 155, /* G=cyl 155 thru 822 */
3f3a34c3 77 0, 0,
a1ef7a52 78}, upam_sizes[8] = {
0cc9b5a9
BJ
79 15884, 0, /* A=cyl 0 thru 31 */
80 33440, 32, /* B=cyl 32 thru 97 */
81 524288, 0, /* C=cyl 0 thru 1023 */
991f4b05
SL
82 15884, 668, /* D=cyl 668 thru 699 */
83 55936, 700, /* E=cyl 700 thru 809 */
84 109472, 810, /* F=cyl 810 thru 1023 */
85 182176, 668, /* G=cyl 668 thru 1023 */
0cc9b5a9 86 291346, 98, /* H=cyl 98 thru 667 */
008c0481 87};
3f3a34c3 88/* END OF STUFF WHICH SHOULD BE READ IN PER DISK */
008c0481 89
cc7ff771
BJ
90/*
91 * On a 780 upSDIST could be 2, but
92 * in the interest of 750's...
93 */
94#define _upSDIST 3 /* 1.5 msec */
3f3a34c3
BJ
95#define _upRDIST 4 /* 2.0 msec */
96
97int upSDIST = _upSDIST;
98int upRDIST = _upRDIST;
99
71236e46 100int upprobe(), upslave(), upattach(), updgo(), upintr();
89bd2f01
BJ
101struct uba_ctlr *upminfo[NSC];
102struct uba_device *updinfo[NUP];
c10bcf0a
BJ
103#define UPIPUNITS 8
104struct uba_device *upip[NSC][UPIPUNITS]; /* fuji w/fixed head gives n,n+4 */
d763a2b7 105
71236e46 106u_short upstd[] = { 0776700, 0774400, 0776300, 0 };
0801d37f 107struct uba_driver scdriver =
71236e46 108 { upprobe, upslave, upattach, updgo, upstd, "up", updinfo, "sc", upminfo };
3f3a34c3 109struct buf uputab[NUP];
7ed3da3d 110char upinit[NUP];
3b186c1c 111char upinit[NUP];
3f3a34c3
BJ
112
113struct upst {
114 short nsect;
115 short ntrak;
116 short nspc;
117 short ncyl;
118 struct size *sizes;
119} upst[] = {
991f4b05
SL
120 32, 19, 32*19, 815, up9300_sizes, /* 9300 */
121 32, 19, 32*19, 823, up9766_sizes, /* 9766 */
122 32, 10, 32*10, 823, up160_sizes, /* fujitsu 160m */
a1ef7a52 123 32, 16, 32*16, 1024, upam_sizes, /* ampex capricorn */
bb9acdbd 124 0, 0, 0, 0, 0
3f3a34c3 125};
008c0481 126
2601dfbd 127u_char up_offset[16] = {
7ed3da3d
BJ
128 UPOF_P400, UPOF_M400, UPOF_P400, UPOF_M400,
129 UPOF_P800, UPOF_M800, UPOF_P800, UPOF_M800,
130 UPOF_P1200, UPOF_M1200, UPOF_P1200, UPOF_M1200,
131 0, 0, 0, 0
2601dfbd 132};
008c0481 133
0801d37f 134struct buf rupbuf[NUP];
7ed3da3d
BJ
135struct buf bupbuf[NUP];
136struct dkbad upbad[NUP];
3b186c1c
SL
137#ifndef NOBADSECT
138struct buf bupbuf[NUP];
139struct dkbad upbad[NUP];
140#endif
008c0481 141
008c0481
BJ
142#define b_cylin b_resid
143
008c0481
BJ
144#ifdef INTRLVE
145daddr_t dkblock();
146#endif
3f3a34c3
BJ
147
148int upwstart, upwatch(); /* Have started guardian */
7e00c42b 149int upseek;
f88f8fdb 150int upwaitdry;
3f3a34c3
BJ
151
152/*ARGSUSED*/
71236e46 153upprobe(reg)
3f3a34c3 154 caddr_t reg;
008c0481 155{
d763a2b7
BJ
156 register int br, cvec;
157
71236e46 158#ifdef lint
66923854 159 br = 0; cvec = br; br = cvec; upintr(0);
71236e46 160#endif
2601dfbd 161 ((struct updevice *)reg)->upcs1 = UP_IE|UP_RDY;
71236e46 162 DELAY(10);
2601dfbd 163 ((struct updevice *)reg)->upcs1 = 0;
9c0adba0 164 return (sizeof (struct updevice));
3f3a34c3
BJ
165}
166
71236e46 167upslave(ui, reg)
89bd2f01 168 struct uba_device *ui;
3f3a34c3
BJ
169 caddr_t reg;
170{
2601dfbd 171 register struct updevice *upaddr = (struct updevice *)reg;
3f3a34c3
BJ
172
173 upaddr->upcs1 = 0; /* conservative */
71236e46 174 upaddr->upcs2 = ui->ui_slave;
c224ab94 175 upaddr->upcs1 = UP_NOP|UP_GO;
7ed3da3d 176 upaddr->upcs1 = UP_NOP|UP_GO;
299d67ed 177 if (upaddr->upcs2&UPCS2_NED) {
2601dfbd 178 upaddr->upcs1 = UP_DCLR|UP_GO;
3f3a34c3
BJ
179 return (0);
180 }
71236e46
BJ
181 return (1);
182}
183
184upattach(ui)
89bd2f01 185 register struct uba_device *ui;
71236e46
BJ
186{
187
6a81870e 188 if (upwstart == 0) {
7780575a 189 timeout(upwatch, (caddr_t)0, hz);
6a81870e
BJ
190 upwstart++;
191 }
b7333467
BJ
192 if (ui->ui_dk >= 0)
193 dk_mspw[ui->ui_dk] = .0000020345;
71236e46
BJ
194 upip[ui->ui_ctlr][ui->ui_slave] = ui;
195 up_softc[ui->ui_ctlr].sc_ndrive++;
bb9acdbd
SL
196 ui->ui_type = upmaptype(ui);
197}
198
199upmaptype(ui)
200 register struct uba_device *ui;
201{
202 register struct updevice *upaddr = (struct updevice *)ui->ui_addr;
203 int type = ui->ui_type;
204 register struct upst *st;
205
2601dfbd
BJ
206 upaddr->upcs1 = 0;
207 upaddr->upcs2 = ui->ui_slave;
26e15512 208 upaddr->uphr = UPHR_MAXTRAK;
bb9acdbd
SL
209 for (st = upst; st->nsect != 0; st++)
210 if (upaddr->uphr == st->ntrak - 1) {
211 type = st - upst;
212 break;
213 }
214 if (st->nsect == 0)
215 printf("up%d: uphr=%x\n", ui->ui_slave, upaddr->uphr);
216 if (type == 0) {
99a8f9c5
HS
217 upaddr->uphr = UPHR_MAXCYL;
218 if (upaddr->uphr == 822)
bb9acdbd 219 type++;
99a8f9c5 220 }
26e15512 221 upaddr->upcs2 = UPCS2_CLR;
bb9acdbd 222 return (type);
3f3a34c3
BJ
223}
224
3b186c1c
SL
225upopen(dev)
226 dev_t dev;
227{
228 register int unit = minor(dev) >> 3;
229 register struct uba_device *ui;
230
231 if (unit >= NUP || (ui = updinfo[unit]) == 0 || ui->ui_alive == 0)
232 return (ENXIO);
233 return (0);
234}
235
3f3a34c3
BJ
236upstrategy(bp)
237 register struct buf *bp;
238{
89bd2f01 239 register struct uba_device *ui;
3f3a34c3
BJ
240 register struct upst *st;
241 register int unit;
7e00c42b 242 register struct buf *dp;
3f3a34c3 243 int xunit = minor(bp->b_dev) & 07;
7e00c42b 244 long bn, sz;
3f3a34c3 245
7e00c42b 246 sz = (bp->b_bcount+511) >> 9;
008c0481 247 unit = dkunit(bp);
3f3a34c3
BJ
248 if (unit >= NUP)
249 goto bad;
250 ui = updinfo[unit];
251 if (ui == 0 || ui->ui_alive == 0)
252 goto bad;
253 st = &upst[ui->ui_type];
254 if (bp->b_blkno < 0 ||
255 (bn = dkblock(bp))+sz > st->sizes[xunit].nblocks)
256 goto bad;
257 bp->b_cylin = bn/st->nspc + st->sizes[xunit].cyloff;
0cc9b5a9 258 (void) spl5();
7e00c42b
BJ
259 dp = &uputab[ui->ui_unit];
260 disksort(dp, bp);
261 if (dp->b_active == 0) {
3f3a34c3
BJ
262 (void) upustart(ui);
263 bp = &ui->ui_mi->um_tab;
264 if (bp->b_actf && bp->b_active == 0)
265 (void) upstart(ui->ui_mi);
008c0481 266 }
0cc9b5a9 267 (void) spl0();
3f3a34c3
BJ
268 return;
269
270bad:
271 bp->b_flags |= B_ERROR;
272 iodone(bp);
273 return;
008c0481
BJ
274}
275
736772ef
BJ
276/*
277 * Unit start routine.
278 * Seek the drive to be where the data is
279 * and then generate another interrupt
280 * to actually start the transfer.
281 * If there is only one drive on the controller,
282 * or we are very close to the data, don't
283 * bother with the search. If called after
284 * searching once, don't bother to look where
285 * we are, just queue for transfer (to avoid
286 * positioning forever without transferrring.)
287 */
3f3a34c3 288upustart(ui)
89bd2f01 289 register struct uba_device *ui;
008c0481
BJ
290{
291 register struct buf *bp, *dp;
89bd2f01 292 register struct uba_ctlr *um;
2601dfbd 293 register struct updevice *upaddr;
3f3a34c3 294 register struct upst *st;
008c0481 295 daddr_t bn;
736772ef 296 int sn, csn;
71236e46
BJ
297 /*
298 * The SC21 cancels commands if you just say
2601dfbd 299 * cs1 = UP_IE
71236e46
BJ
300 * so we are cautious about handling of cs1.
301 * Also don't bother to clear as bits other than in upintr().
302 */
736772ef
BJ
303 int didie = 0;
304
305 if (ui == 0)
306 return (0);
89bd2f01 307 um = ui->ui_mi;
3f3a34c3
BJ
308 dk_busy &= ~(1<<ui->ui_dk);
309 dp = &uputab[ui->ui_unit];
7bc8d985 310 if ((bp = dp->b_actf) == NULL)
eb891eaa 311 goto out;
736772ef
BJ
312 /*
313 * If the controller is active, just remember
314 * that this device would like to be positioned...
315 * if we tried to position now we would confuse the SC21.
316 */
3f3a34c3 317 if (um->um_tab.b_active) {
d763a2b7 318 up_softc[um->um_ctlr].sc_softas |= 1<<ui->ui_slave;
2a3b9a7f
BJ
319 return (0);
320 }
736772ef
BJ
321 /*
322 * If we have already positioned this drive,
323 * then just put it on the ready queue.
324 */
a3f430e0
BJ
325 if (dp->b_active)
326 goto done;
327 dp->b_active = 1;
2601dfbd 328 upaddr = (struct updevice *)um->um_addr;
3f3a34c3 329 upaddr->upcs2 = ui->ui_slave;
736772ef
BJ
330 /*
331 * If drive has just come up,
332 * setup the pack.
333 */
7ed3da3d
BJ
334 if ((upaddr->upds & UPDS_VV) == 0 || upinit[ui->ui_unit] == 0) {
335#ifndef NOBADSECT
336 struct buf *bbp = &bupbuf[ui->ui_unit];
337#endif
71236e46 338 /* SHOULD WARN SYSTEM THAT THIS HAPPENED */
7ed3da3d 339 upinit[ui->ui_unit] = 1;
3b186c1c 340 upinit[ui->ui_unit] = 1;
2601dfbd
BJ
341 upaddr->upcs1 = UP_IE|UP_DCLR|UP_GO;
342 upaddr->upcs1 = UP_IE|UP_PRESET|UP_GO;
299d67ed 343 upaddr->upof = UPOF_FMT22;
eb891eaa 344 didie = 1;
3b186c1c
SL
345 st = &upst[ui->ui_type];
346 bbp->b_flags = B_READ|B_BUSY;
347 bbp->b_dev = bp->b_dev;
348 bbp->b_bcount = 512;
349 bbp->b_un.b_addr = (caddr_t)&upbad[ui->ui_unit];
350 bbp->b_blkno = st->ncyl * st->nspc - st->nsect;
351 bbp->b_cylin = st->ncyl - 1;
352 dp->b_actf = bbp;
353 bbp->av_forw = bp;
354 bp = bbp;
7ed3da3d
BJ
355#ifndef NOBADSECT
356 st = &upst[ui->ui_type];
357 bbp->b_flags = B_READ|B_BUSY;
358 bbp->b_dev = bp->b_dev;
359 bbp->b_bcount = 512;
360 bbp->b_un.b_addr = (caddr_t)&upbad[ui->ui_unit];
361 bbp->b_blkno = st->ncyl * st->nspc - st->nsect;
362 bbp->b_cylin = st->ncyl - 1;
363 dp->b_actf = bbp;
364 bbp->av_forw = bp;
365 bp = bbp;
366#endif
008c0481 367 }
736772ef
BJ
368 /*
369 * If drive is offline, forget about positioning.
370 */
299d67ed 371 if ((upaddr->upds & (UPDS_DPR|UPDS_MOL)) != (UPDS_DPR|UPDS_MOL))
2a3b9a7f 372 goto done;
736772ef
BJ
373 /*
374 * If there is only one drive,
375 * dont bother searching.
376 */
71236e46
BJ
377 if (up_softc[um->um_ctlr].sc_ndrive == 1)
378 goto done;
736772ef
BJ
379 /*
380 * Figure out where this transfer is going to
381 * and see if we are close enough to justify not searching.
382 */
3f3a34c3 383 st = &upst[ui->ui_type];
008c0481 384 bn = dkblock(bp);
3f3a34c3
BJ
385 sn = bn%st->nspc;
386 sn = (sn + st->nsect - upSDIST) % st->nsect;
736772ef 387 if (bp->b_cylin - upaddr->updc)
7bc8d985 388 goto search; /* Not on-cylinder */
2a3b9a7f
BJ
389 else if (upseek)
390 goto done; /* Ok just to be on-cylinder */
008c0481 391 csn = (upaddr->upla>>6) - sn - 1;
7bc8d985 392 if (csn < 0)
3f3a34c3
BJ
393 csn += st->nsect;
394 if (csn > st->nsect - upRDIST)
008c0481 395 goto done;
008c0481 396search:
736772ef
BJ
397 upaddr->updc = bp->b_cylin;
398 /*
399 * Not on cylinder at correct position,
400 * seek/search.
401 */
2a3b9a7f 402 if (upseek)
2601dfbd 403 upaddr->upcs1 = UP_IE|UP_SEEK|UP_GO;
7e00c42b 404 else {
2a3b9a7f 405 upaddr->upda = sn;
2601dfbd 406 upaddr->upcs1 = UP_IE|UP_SEARCH|UP_GO;
2a3b9a7f 407 }
eb891eaa 408 didie = 1;
736772ef
BJ
409 /*
410 * Mark unit busy for iostat.
411 */
3f3a34c3
BJ
412 if (ui->ui_dk >= 0) {
413 dk_busy |= 1<<ui->ui_dk;
414 dk_seek[ui->ui_dk]++;
008c0481 415 }
eb891eaa 416 goto out;
008c0481 417done:
736772ef
BJ
418 /*
419 * Device is ready to go.
420 * Put it on the ready queue for the controller
421 * (unless its already there.)
422 */
2601dfbd
BJ
423 if (dp->b_active != 2) {
424 dp->b_forw = NULL;
425 if (um->um_tab.b_actf == NULL)
426 um->um_tab.b_actf = dp;
427 else
428 um->um_tab.b_actl->b_forw = dp;
429 um->um_tab.b_actl = dp;
430 dp->b_active = 2;
431 }
eb891eaa
BJ
432out:
433 return (didie);
008c0481
BJ
434}
435
736772ef
BJ
436/*
437 * Start up a transfer on a drive.
438 */
3f3a34c3 439upstart(um)
89bd2f01 440 register struct uba_ctlr *um;
008c0481
BJ
441{
442 register struct buf *bp, *dp;
89bd2f01 443 register struct uba_device *ui;
2601dfbd 444 register struct updevice *upaddr;
7e00c42b 445 struct upst *st;
008c0481 446 daddr_t bn;
f88f8fdb 447 int dn, sn, tn, cmd, waitdry;
008c0481 448
008c0481 449loop:
736772ef
BJ
450 /*
451 * Pull a request off the controller queue
452 */
3f3a34c3 453 if ((dp = um->um_tab.b_actf) == NULL)
eb891eaa 454 return (0);
008c0481 455 if ((bp = dp->b_actf) == NULL) {
3f3a34c3 456 um->um_tab.b_actf = dp->b_forw;
008c0481
BJ
457 goto loop;
458 }
736772ef
BJ
459 /*
460 * Mark controller busy, and
461 * determine destination of this request.
462 */
3f3a34c3
BJ
463 um->um_tab.b_active++;
464 ui = updinfo[dkunit(bp)];
008c0481 465 bn = dkblock(bp);
3f3a34c3
BJ
466 dn = ui->ui_slave;
467 st = &upst[ui->ui_type];
468 sn = bn%st->nspc;
469 tn = sn/st->nsect;
470 sn %= st->nsect;
2601dfbd 471 upaddr = (struct updevice *)ui->ui_addr;
736772ef
BJ
472 /*
473 * Select drive if not selected already.
474 */
475 if ((upaddr->upcs2&07) != dn)
476 upaddr->upcs2 = dn;
477 /*
478 * Check that it is ready and online
479 */
f88f8fdb 480 waitdry = 0;
299d67ed 481 while ((upaddr->upds&UPDS_DRY) == 0) {
7ed3da3d 482 printf("up%d: ds wait ds=%o\n",dkunit(bp),upaddr->upds);
ac1276f8 483 printf("up%d: ds wait ds=%o\n",dkunit(bp),upaddr->upds);
f88f8fdb
BJ
484 if (++waitdry > 512)
485 break;
486 upwaitdry++;
487 }
299d67ed 488 if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) {
f6d201ff 489 printf("up%d: not ready", dkunit(bp));
299d67ed 490 if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) {
71236e46 491 printf("\n");
3f3a34c3
BJ
492 um->um_tab.b_active = 0;
493 um->um_tab.b_errcnt = 0;
88253fd2
BJ
494 dp->b_actf = bp->av_forw;
495 dp->b_active = 0;
496 bp->b_flags |= B_ERROR;
497 iodone(bp);
88253fd2
BJ
498 goto loop;
499 }
736772ef
BJ
500 /*
501 * Oh, well, sometimes this
502 * happens, for reasons unknown.
503 */
2601dfbd 504 printf(" (flakey)\n");
008c0481 505 }
736772ef
BJ
506 /*
507 * Setup for the transfer, and get in the
508 * UNIBUS adaptor queue.
509 */
5aa9d5ea 510 upaddr->updc = bp->b_cylin;
008c0481 511 upaddr->upda = (tn << 8) + sn;
008c0481
BJ
512 upaddr->upwc = -bp->b_bcount / sizeof (short);
513 if (bp->b_flags & B_READ)
2601dfbd 514 cmd = UP_IE|UP_RCOM|UP_GO;
008c0481 515 else
2601dfbd 516 cmd = UP_IE|UP_WCOM|UP_GO;
b7333467 517 um->um_cmd = cmd;
a0eab615 518 (void) ubago(ui);
eb891eaa 519 return (1);
008c0481
BJ
520}
521
736772ef
BJ
522/*
523 * Now all ready to go, stuff the registers.
524 */
b7333467 525updgo(um)
89bd2f01 526 struct uba_ctlr *um;
3f3a34c3 527{
2601dfbd 528 register struct updevice *upaddr = (struct updevice *)um->um_addr;
7e00c42b 529
de59ccc2 530 um->um_tab.b_active = 2; /* should now be 2 */
b7333467
BJ
531 upaddr->upba = um->um_ubinfo;
532 upaddr->upcs1 = um->um_cmd|((um->um_ubinfo>>8)&0x300);
3f3a34c3
BJ
533}
534
736772ef
BJ
535/*
536 * Handle a disk interrupt.
537 */
443c8066 538upintr(sc21)
3f3a34c3 539 register sc21;
008c0481
BJ
540{
541 register struct buf *bp, *dp;
89bd2f01
BJ
542 register struct uba_ctlr *um = upminfo[sc21];
543 register struct uba_device *ui;
2601dfbd 544 register struct updevice *upaddr = (struct updevice *)um->um_addr;
008c0481 545 register unit;
7e00c42b 546 struct up_softc *sc = &up_softc[um->um_ctlr];
71236e46 547 int as = (upaddr->upas & 0377) | sc->sc_softas;
f88f8fdb 548 int needie = 1, waitdry;
008c0481 549
7e00c42b 550 sc->sc_wticks = 0;
71236e46 551 sc->sc_softas = 0;
736772ef
BJ
552 /*
553 * If controller wasn't transferring, then this is an
554 * interrupt for attention status on seeking drives.
555 * Just service them.
556 */
cc7ff771 557 if (um->um_tab.b_active != 2 && !sc->sc_recal) {
736772ef
BJ
558 if (upaddr->upcs1 & UP_TRE)
559 upaddr->upcs1 = UP_TRE;
560 goto doattn;
561 }
de59ccc2 562 um->um_tab.b_active = 1;
7ed3da3d 563 um->um_tab.b_active = 1;
736772ef
BJ
564 /*
565 * Get device and block structures, and a pointer
89bd2f01 566 * to the uba_device for the drive. Select the drive.
736772ef
BJ
567 */
568 dp = um->um_tab.b_actf;
569 bp = dp->b_actf;
570 ui = updinfo[dkunit(bp)];
571 dk_busy &= ~(1 << ui->ui_dk);
572 if ((upaddr->upcs2&07) != ui->ui_slave)
3f3a34c3 573 upaddr->upcs2 = ui->ui_slave;
3b186c1c
SL
574 if (bp->b_flags&B_BAD) {
575 if (upecc(ui, CONT))
576 return;
577 }
7ed3da3d
BJ
578#ifndef NOBADSECT
579 if (bp->b_flags&B_BAD) {
580 if (upecc(ui, CONT))
581 return;
582 }
583#endif
736772ef
BJ
584 /*
585 * Check for and process errors on
586 * either the drive or the controller.
587 */
299d67ed 588 if ((upaddr->upds&UPDS_ERR) || (upaddr->upcs1&UP_TRE)) {
f88f8fdb 589 waitdry = 0;
299d67ed 590 while ((upaddr->upds & UPDS_DRY) == 0) {
f88f8fdb
BJ
591 if (++waitdry > 512)
592 break;
593 upwaitdry++;
594 }
299d67ed 595 if (upaddr->uper1&UPER1_WLE) {
736772ef
BJ
596 /*
597 * Give up on write locked devices
598 * immediately.
599 */
f6d201ff 600 printf("up%d: write locked\n", dkunit(bp));
736772ef
BJ
601 bp->b_flags |= B_ERROR;
602 } else if (++um->um_tab.b_errcnt > 27) {
603 /*
604 * After 28 retries (16 without offset, and
605 * 12 with offset positioning) give up.
45663547
HS
606 * If the error was header CRC, the header is
607 * screwed up, and the sector may in fact exist
608 * in the bad sector table, better check...
736772ef 609 */
45663547
HS
610 if (upaddr->uper1&UPER1_HCRC) {
611 if (upecc(ui, BSE))
612 return;
613 }
3b186c1c 614 hard:
7ed3da3d 615 hard:
f6d201ff 616 harderr(bp, "up");
7ed3da3d
BJ
617 printf("cn=%d tn=%d sn=%d cs2=%b er1=%b er2=%b\n",
618 upaddr->updc, ((upaddr->upda)>>8)&077,
619 (upaddr->upda)&037,
620 upaddr->upcs2, UPCS2_BITS,
621 upaddr->uper1, UPER1_BITS,
622 upaddr->uper2, UPER2_BITS);
736772ef 623 bp->b_flags |= B_ERROR;
7ed3da3d 624 } else if (upaddr->uper2 & UPER2_BSE) {
7ed3da3d
BJ
625 if (upecc(ui, BSE))
626 return;
627 else
3b186c1c
SL
628 goto hard;
629 } else if (upaddr->uper2 & UPER2_BSE) {
630#ifndef NOBADSECT
631 if (upecc(ui, BSE))
632 return;
633 else
7ed3da3d
BJ
634#endif
635 goto hard;
736772ef
BJ
636 } else {
637 /*
638 * Retriable error.
639 * If a soft ecc, correct it (continuing
640 * by returning if necessary.
641 * Otherwise fall through and retry the transfer
642 */
55de6ac9 643 if ((upaddr->uper1&(UPER1_DCK|UPER1_ECH))==UPER1_DCK) {
7ed3da3d 644 if (upecc(ui, ECC))
2601dfbd 645 return;
55de6ac9
BJ
646 } else
647 um->um_tab.b_active = 0; /* force retry */
7ed3da3d
BJ
648 } else
649 um->um_tab.b_active = 0; /* force retry */
736772ef
BJ
650 }
651 /*
652 * Clear drive error and, every eight attempts,
653 * (starting with the fourth)
654 * recalibrate to clear the slate.
655 */
656 upaddr->upcs1 = UP_TRE|UP_IE|UP_DCLR|UP_GO;
657 needie = 0;
fc4d0a69 658 if ((um->um_tab.b_errcnt&07) == 4 && um->um_tab.b_active == 0) {
736772ef 659 upaddr->upcs1 = UP_RECAL|UP_IE|UP_GO;
a6442a2f
BJ
660 sc->sc_recal = 0;
661 goto nextrecal;
736772ef
BJ
662 }
663 }
664 /*
a6442a2f
BJ
665 * Advance recalibration finite state machine
666 * if recalibrate in progress, through
667 * RECAL
668 * SEEK
669 * OFFSET (optional)
670 * RETRY
736772ef 671 */
a6442a2f
BJ
672 switch (sc->sc_recal) {
673
674 case 1:
675 upaddr->updc = bp->b_cylin;
676 upaddr->upcs1 = UP_SEEK|UP_IE|UP_GO;
677 goto nextrecal;
678 case 2:
679 if (um->um_tab.b_errcnt < 16 || (bp->b_flags&B_READ) == 0)
680 goto donerecal;
299d67ed 681 upaddr->upof = up_offset[um->um_tab.b_errcnt & 017] | UPOF_FMT22;
a6442a2f
BJ
682 upaddr->upcs1 = UP_IE|UP_OFFSET|UP_GO;
683 goto nextrecal;
684 nextrecal:
685 sc->sc_recal++;
686 um->um_tab.b_active = 1;
687 return;
688 donerecal:
689 case 3:
736772ef 690 sc->sc_recal = 0;
a6442a2f
BJ
691 um->um_tab.b_active = 0;
692 break;
736772ef
BJ
693 }
694 /*
695 * If still ``active'', then don't need any more retries.
696 */
697 if (um->um_tab.b_active) {
698 /*
699 * If we were offset positioning,
700 * return to centerline.
701 */
702 if (um->um_tab.b_errcnt >= 16) {
299d67ed 703 upaddr->upof = UPOF_FMT22;
736772ef 704 upaddr->upcs1 = UP_RTC|UP_GO|UP_IE;
299d67ed 705 while (upaddr->upds & UPDS_PIP)
736772ef 706 DELAY(25);
eb891eaa 707 needie = 0;
008c0481 708 }
736772ef
BJ
709 um->um_tab.b_active = 0;
710 um->um_tab.b_errcnt = 0;
711 um->um_tab.b_actf = dp->b_forw;
712 dp->b_active = 0;
713 dp->b_errcnt = 0;
714 dp->b_actf = bp->av_forw;
715 bp->b_resid = (-upaddr->upwc * sizeof(short));
716 iodone(bp);
717 /*
718 * If this unit has more work to do,
719 * then start it up right away.
720 */
721 if (dp->b_actf)
722 if (upustart(ui))
eb891eaa 723 needie = 0;
008c0481 724 }
736772ef 725 as &= ~(1<<ui->ui_slave);
8c58f40c
BJ
726 /*
727 * Release unibus resources and flush data paths.
728 */
729 ubadone(um);
736772ef
BJ
730doattn:
731 /*
732 * Process other units which need attention.
733 * For each unit which needs attention, call
734 * the unit start routine to place the slave
735 * on the controller device queue.
736 */
a6442a2f
BJ
737 while (unit = ffs(as)) {
738 unit--; /* was 1 origin */
739 as &= ~(1<<unit);
740 upaddr->upas = 1<<unit;
c10bcf0a 741 if (unit < UPIPUNITS && upustart(upip[sc21][unit]))
a6442a2f
BJ
742 needie = 0;
743 }
736772ef
BJ
744 /*
745 * If the controller is not transferring, but
746 * there are devices ready to transfer, start
747 * the controller.
748 */
3f3a34c3
BJ
749 if (um->um_tab.b_actf && um->um_tab.b_active == 0)
750 if (upstart(um))
eb891eaa 751 needie = 0;
2a3b9a7f 752 if (needie)
2601dfbd 753 upaddr->upcs1 = UP_IE;
008c0481
BJ
754}
755
740e4029 756upread(dev, uio)
0801d37f 757 dev_t dev;
740e4029 758 struct uio *uio;
008c0481 759{
0801d37f 760 register int unit = minor(dev) >> 3;
7e00c42b 761
0801d37f 762 if (unit >= NUP)
0cd5eac7
BJ
763 return (ENXIO);
764 return (physio(upstrategy, &rupbuf[unit], dev, B_READ, minphys, uio));
008c0481
BJ
765}
766
002227dd 767upwrite(dev, uio)
0801d37f 768 dev_t dev;
002227dd 769 struct uio *uio;
008c0481 770{
0801d37f 771 register int unit = minor(dev) >> 3;
7e00c42b 772
0801d37f 773 if (unit >= NUP)
0cd5eac7
BJ
774 return (ENXIO);
775 return (physio(upstrategy, &rupbuf[unit], dev, B_WRITE, minphys, uio));
008c0481
BJ
776}
777
7bc8d985
BJ
778/*
779 * Correct an ECC error, and restart the i/o to complete
780 * the transfer if necessary. This is quite complicated because
781 * the transfer may be going to an odd memory address base and/or
782 * across a page boundary.
783 */
7ed3da3d 784upecc(ui, flag)
89bd2f01 785 register struct uba_device *ui;
7ed3da3d 786 int flag;
3b186c1c 787 int flag;
008c0481 788{
2601dfbd 789 register struct updevice *up = (struct updevice *)ui->ui_addr;
3f3a34c3 790 register struct buf *bp = uputab[ui->ui_unit].b_actf;
89bd2f01 791 register struct uba_ctlr *um = ui->ui_mi;
3f3a34c3
BJ
792 register struct upst *st;
793 struct uba_regs *ubp = ui->ui_hd->uh_uba;
7bc8d985 794 register int i;
008c0481 795 caddr_t addr;
7bc8d985 796 int reg, bit, byte, npf, mask, o, cmd, ubaddr;
008c0481
BJ
797 int bn, cn, tn, sn;
798
008c0481 799 /*
7bc8d985
BJ
800 * Npf is the number of sectors transferred before the sector
801 * containing the ECC error, and reg is the UBA register
802 * mapping (the first part of) the transfer.
803 * O is offset within a memory page of the first byte transferred.
008c0481 804 */
7ed3da3d
BJ
805#ifndef NOBADSECT
806 if (flag == CONT)
807 npf = bp->b_error;
808 else
809#endif
810 npf = btop((up->upwc * sizeof(short)) + bp->b_bcount);
b7333467 811 reg = btop(um->um_ubinfo&0x3ffff) + npf;
008c0481 812 o = (int)bp->b_un.b_addr & PGOFSET;
008c0481 813 mask = up->upec2;
299d67ed 814#ifdef UPECCDEBUG
8c58f40c
BJ
815 printf("npf %d reg %x o %d mask %o pos %d\n", npf, reg, o, mask,
816 up->upec1);
299d67ed 817#endif
7ed3da3d
BJ
818 bn = dkblock(bp);
819 st = &upst[ui->ui_type];
820 cn = bp->b_cylin;
821 sn = bn%st->nspc + npf;
822 tn = sn/st->nsect;
823 sn %= st->nsect;
824 cn += tn/st->ntrak;
825 tn %= st->ntrak;
060afaf6 826 ubapurge(um);
7ed3da3d 827 um->um_tab.b_active=2;
7bc8d985 828 /*
7ed3da3d 829 * action taken depends on the flag
7bc8d985 830 */
7ed3da3d
BJ
831 switch(flag){
832 case ECC:
833 npf--;
834 reg--;
835 mask = up->upec2;
836 printf("up%d%c: soft ecc sn%d\n", dkunit(bp),
837 'a'+(minor(bp->b_dev)&07), bp->b_blkno + npf);
838 /*
839 * Flush the buffered data path, and compute the
840 * byte and bit position of the error. The variable i
841 * is the byte offset in the transfer, the variable byte
842 * is the offset from a page boundary in main memory.
843 */
844 i = up->upec1 - 1; /* -1 makes 0 origin */
845 bit = i&07;
846 i = (i&~07)>>3;
847 byte = i + o;
848 /*
849 * Correct while possible bits remain of mask. Since mask
850 * contains 11 bits, we continue while the bit offset is > -11.
851 * Also watch out for end of this block and the end of the whole
852 * transfer.
853 */
854 while (i < 512 && (int)ptob(npf)+i < bp->b_bcount && bit > -11) {
855 addr = ptob(ubp->uba_map[reg+btop(byte)].pg_pfnum)+
856 (byte & PGOFSET);
857#ifdef UPECCDEBUG
858 printf("addr %x map reg %x\n",
859 addr, *(int *)(&ubp->uba_map[reg+btop(byte)]));
860 printf("old: %x, ", getmemc(addr));
861#endif
862 putmemc(addr, getmemc(addr)^(mask<<bit));
299d67ed 863#ifdef UPECCDEBUG
7ed3da3d 864 printf("new: %x\n", getmemc(addr));
299d67ed 865#endif
7ed3da3d
BJ
866 byte++;
867 i++;
868 }
869 if (up->upwc == 0)
870 return (0);
871 npf++;
872 reg++;
873 break;
874#ifndef NOBADSECT
875 case BSE:
876 /*
877 * if not in bad sector table, return 0
878 */
879 if ((bn = isbad(&upbad[ui->ui_unit], cn, tn, sn)) < 0)
880 return(0);
881 /*
882 * flag this one as bad
883 */
884 bp->b_flags |= B_BAD;
885 bp->b_error = npf + 1;
299d67ed 886#ifdef UPECCDEBUG
7ed3da3d
BJ
887 printf("BSE: restart at %d\n",npf+1);
888#endif
889 bn = st->ncyl * st->nspc -st->nsect - 1 - bn;
890 cn = bn / st->nspc;
891 sn = bn % st->nspc;
892 tn = sn / st->nsect;
893 sn %= st->nsect;
894 up->upwc = -(512 / sizeof (short));
895#ifdef UPECCDEBUG
896 printf("revector to cn %d tn %d sn %d\n", cn, tn, sn);
897#endif
898 break;
899 case CONT:
900#ifdef UPECCDEBUG
901 printf("upecc, CONT: bn %d cn %d tn %d sn %d\n", bn, cn, tn, sn);
902#endif
903 bp->b_flags &= ~B_BAD;
904 up->upwc = -((bp->b_bcount - (int)ptob(npf)) / sizeof(short));
905 if (up->upwc == 0)
906 return(0);
907 break;
299d67ed 908#endif
008c0481 909 }
55de6ac9
BJ
910 if (up->upwc == 0) {
911 um->um_tab.b_active = 0;
008c0481 912 return (0);
55de6ac9 913 }
7ed3da3d 914 }
7bc8d985
BJ
915 /*
916 * Have to continue the transfer... clear the drive,
917 * and compute the position where the transfer is to continue.
918 * We have completed npf+1 sectors of the transfer already;
919 * restart at offset o of next sector (i.e. in UBA register reg+1).
920 */
2601dfbd
BJ
921#ifdef notdef
922 up->uper1 = 0;
923 up->upcs1 |= UP_GO;
924#else
925 up->upcs1 = UP_TRE|UP_IE|UP_DCLR|UP_GO;
008c0481 926 up->updc = cn;
7bc8d985 927 up->upda = (tn << 8) | sn;
7ed3da3d 928 ubaddr = (int)ptob(reg) + o;
7bc8d985
BJ
929 up->upba = ubaddr;
930 cmd = (ubaddr >> 8) & 0x300;
7ed3da3d
BJ
931 cmd |= ((bp->b_flags&B_READ)?UP_RCOM:UP_WCOM)|UP_IE|UP_GO;
932 um->um_tab.b_errcnt = 0;
7bc8d985 933 up->upcs1 = cmd;
2601dfbd 934#endif
008c0481
BJ
935 return (1);
936}
977c2848
BJ
937
938/*
939 * Reset driver after UBA init.
940 * Cancel software state of all pending transfers
941 * and restart all units and the controller.
942 */
3f3a34c3 943upreset(uban)
f6d201ff 944 int uban;
977c2848 945{
89bd2f01
BJ
946 register struct uba_ctlr *um;
947 register struct uba_device *ui;
3f3a34c3
BJ
948 register sc21, unit;
949
a3cb8f60 950 for (sc21 = 0; sc21 < NSC; sc21++) {
7e00c42b
BJ
951 if ((um = upminfo[sc21]) == 0 || um->um_ubanum != uban ||
952 um->um_alive == 0)
3f3a34c3 953 continue;
f6d201ff 954 printf(" sc%d", sc21);
3f3a34c3
BJ
955 um->um_tab.b_active = 0;
956 um->um_tab.b_actf = um->um_tab.b_actl = 0;
f6d201ff 957 up_softc[sc21].sc_recal = 0;
cc7ff771 958 up_softc[sc21].sc_wticks = 0;
b7333467
BJ
959 if (um->um_ubinfo) {
960 printf("<%d>", (um->um_ubinfo>>28)&0xf);
96b997eb 961 um->um_ubinfo = 0;
3f3a34c3 962 }
299d67ed 963 ((struct updevice *)(um->um_addr))->upcs2 = UPCS2_CLR;
3f3a34c3
BJ
964 for (unit = 0; unit < NUP; unit++) {
965 if ((ui = updinfo[unit]) == 0)
966 continue;
f6d201ff 967 if (ui->ui_alive == 0 || ui->ui_mi != um)
3f3a34c3
BJ
968 continue;
969 uputab[unit].b_active = 0;
970 (void) upustart(ui);
971 }
972 (void) upstart(um);
977c2848 973 }
977c2848 974}
6a81870e
BJ
975
976/*
977 * Wake up every second and if an interrupt is pending
978 * but nothing has happened increment a counter.
f6d201ff 979 * If nothing happens for 20 seconds, reset the UNIBUS
6a81870e
BJ
980 * and begin anew.
981 */
982upwatch()
983{
89bd2f01 984 register struct uba_ctlr *um;
3f3a34c3 985 register sc21, unit;
7e00c42b 986 register struct up_softc *sc;
6a81870e 987
7780575a 988 timeout(upwatch, (caddr_t)0, hz);
a3cb8f60 989 for (sc21 = 0; sc21 < NSC; sc21++) {
3f3a34c3 990 um = upminfo[sc21];
7e00c42b
BJ
991 if (um == 0 || um->um_alive == 0)
992 continue;
993 sc = &up_softc[sc21];
3f3a34c3
BJ
994 if (um->um_tab.b_active == 0) {
995 for (unit = 0; unit < NUP; unit++)
2601dfbd
BJ
996 if (uputab[unit].b_active &&
997 updinfo[unit]->ui_mi == um)
3f3a34c3 998 goto active;
7e00c42b 999 sc->sc_wticks = 0;
3f3a34c3
BJ
1000 continue;
1001 }
f6d201ff 1002active:
7e00c42b
BJ
1003 sc->sc_wticks++;
1004 if (sc->sc_wticks >= 20) {
1005 sc->sc_wticks = 0;
f6d201ff 1006 printf("sc%d: lost interrupt\n", sc21);
a3cb8f60 1007 ubareset(um->um_ubanum);
3f3a34c3 1008 }
6a81870e
BJ
1009 }
1010}
0ff318b2
BJ
1011
1012#define DBSIZE 20
1013
1014updump(dev)
1015 dev_t dev;
1016{
2601dfbd 1017 struct updevice *upaddr;
0ff318b2 1018 char *start;
a0eab615 1019 int num, blk, unit;
0ff318b2 1020 struct size *sizes;
3f3a34c3 1021 register struct uba_regs *uba;
89bd2f01 1022 register struct uba_device *ui;
0ff318b2 1023 register short *rp;
3f3a34c3 1024 struct upst *st;
cd8ce595 1025 register int retry;
7ed3da3d 1026 register int retry;
0ff318b2 1027
0ff318b2 1028 unit = minor(dev) >> 3;
0c48c799
BJ
1029 if (unit >= NUP)
1030 return (ENXIO);
7e00c42b 1031#define phys(cast, addr) ((cast)((int)addr & 0x7fffffff))
89bd2f01 1032 ui = phys(struct uba_device *, updinfo[unit]);
0c48c799
BJ
1033 if (ui->ui_alive == 0)
1034 return (ENXIO);
3f3a34c3 1035 uba = phys(struct uba_hd *, ui->ui_hd)->uh_physuba;
89bd2f01 1036 ubainit(uba);
2601dfbd 1037 upaddr = (struct updevice *)ui->ui_physaddr;
cd8ce595 1038 DELAY(5000000);
3f3a34c3 1039 num = maxfree;
0ff318b2 1040 upaddr->upcs2 = unit;
89bd2f01 1041 DELAY(100);
cd8ce595
SL
1042 upaddr->upcs1 = UP_DCLR|UP_GO;
1043 upaddr->upcs1 = UP_PRESET|UP_GO;
1044 upaddr->upof = UPOF_FMT22;
1045 retry = 0;
1046 do {
1047 DELAY(25);
1048 if (++retry > 527)
1049 break;
0599cbf1 1050 } while ((upaddr->upds & UP_RDY) == 0);
299d67ed 1051 if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY)
0c48c799 1052 return (EFAULT);
cd8ce595 1053 start = 0;
7e00c42b 1054 st = &upst[ui->ui_type];
7ed3da3d 1055 start = 0;
3f3a34c3 1056 sizes = phys(struct size *, st->sizes);
0c48c799
BJ
1057 if (dumplo < 0 || dumplo + num >= sizes[minor(dev)&07].nblocks)
1058 return (EINVAL);
0ff318b2
BJ
1059 while (num > 0) {
1060 register struct pte *io;
1061 register int i;
1062 int cn, sn, tn;
1063 daddr_t bn;
1064
1065 blk = num > DBSIZE ? DBSIZE : num;
3f3a34c3 1066 io = uba->uba_map;
0ff318b2 1067 for (i = 0; i < blk; i++)
89bd2f01 1068 *(int *)io++ = (btop(start)+i) | (1<<21) | UBAMR_MRV;
0ff318b2
BJ
1069 *(int *)io = 0;
1070 bn = dumplo + btop(start);
71236e46
BJ
1071 cn = bn/st->nspc + sizes[minor(dev)&07].cyloff;
1072 sn = bn%st->nspc;
1073 tn = sn/st->nsect;
1074 sn = sn%st->nsect;
0ff318b2
BJ
1075 upaddr->updc = cn;
1076 rp = (short *) &upaddr->upda;
1077 *rp = (tn << 8) + sn;
1078 *--rp = 0;
1079 *--rp = -blk*NBPG / sizeof (short);
2601dfbd 1080 *--rp = UP_GO|UP_WCOM;
cd8ce595 1081 retry = 0;
7ed3da3d 1082 retry = 0;
0ff318b2
BJ
1083 do {
1084 DELAY(25);
cd8ce595
SL
1085 if (++retry > 527)
1086 break;
7ed3da3d
BJ
1087 if (++retry > 527)
1088 break;
2601dfbd 1089 } while ((upaddr->upcs1 & UP_RDY) == 0);
cd8ce595 1090 if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) {
0599cbf1 1091 printf("up%d: not ready", unit);
cd8ce595
SL
1092 if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) {
1093 printf("\n");
1094 return (EIO);
1095 }
1096 printf(" (flakey)\n");
1097 }
7ed3da3d
BJ
1098 if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) {
1099 printf("up%d: not ready", unit);
1100 if ((upaddr->upds & UPDS_DREADY) != UPDS_DREADY) {
1101 printf("\n");
1102 return (EIO);
1103 }
1104 printf(" (flakey)\n");
1105 }
299d67ed 1106 if (upaddr->upds&UPDS_ERR)
0c48c799 1107 return (EIO);
0ff318b2
BJ
1108 start += blk*NBPG;
1109 num -= blk;
1110 }
0ff318b2
BJ
1111 return (0);
1112}
cf83ab8e
SL
1113
1114upsize(dev)
1115 dev_t dev;
1116{
1117 int unit = minor(dev) >> 3;
1118 struct uba_device *ui;
1119 struct upst *st;
1120
1121 if (unit >= NUP || (ui = updinfo[unit]) == 0 || ui->ui_alive == 0)
1122 return (-1);
1123 st = &upst[ui->ui_type];
1124 return (st->sizes[minor(dev) & 07].nblocks);
1125}
63c35a63 1126#endif