Home | History | Annotate | Line # | Download | only in dev
ccd.c revision 1.2
      1 /*	$NetBSD: ccd.c,v 1.2 1994/06/29 06:31:30 cgd Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1988 University of Utah.
      5  * Copyright (c) 1990 The Regents of the University of California.
      6  * All rights reserved.
      7  *
      8  * This code is derived from software contributed to Berkeley by
      9  * the Systems Programming Group of the University of Utah Computer
     10  * Science Department.
     11  *
     12  * Redistribution and use in source and binary forms, with or without
     13  * modification, are permitted provided that the following conditions
     14  * are met:
     15  * 1. Redistributions of source code must retain the above copyright
     16  *    notice, this list of conditions and the following disclaimer.
     17  * 2. Redistributions in binary form must reproduce the above copyright
     18  *    notice, this list of conditions and the following disclaimer in the
     19  *    documentation and/or other materials provided with the distribution.
     20  * 3. All advertising materials mentioning features or use of this software
     21  *    must display the following acknowledgement:
     22  *	This product includes software developed by the University of
     23  *	California, Berkeley and its contributors.
     24  * 4. Neither the name of the University nor the names of its contributors
     25  *    may be used to endorse or promote products derived from this software
     26  *    without specific prior written permission.
     27  *
     28  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     29  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     30  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     31  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     32  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     33  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     34  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     35  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     36  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     37  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     38  * SUCH DAMAGE.
     39  *
     40  * from: Utah $Hdr: cd.c 1.6 90/11/28$
     41  *
     42  *	@(#)cd.c	7.4 (Berkeley) 5/7/91
     43  */
     44 
     45 /*
     46  * "Concatenated" disk driver.
     47  */
     48 #include "ccd.h"
     49 #if NCCD > 0
     50 
     51 #include <sys/param.h>
     52 #include <sys/systm.h>
     53 #include <sys/errno.h>
     54 #include <sys/dkstat.h>
     55 #include <sys/buf.h>
     56 #include <sys/malloc.h>
     57 #include <sys/conf.h>
     58 
     59 #include <dev/ccdvar.h>
     60 
     61 #ifdef DEBUG
     62 int ccddebug = 0x00;
     63 #define CDB_FOLLOW	0x01
     64 #define CDB_INIT	0x02
     65 #define CDB_IO		0x04
     66 #endif
     67 
     68 struct	buf ccdbuf[NCCD];
     69 struct	buf *ccdbuffer();
     70 char	*ccddevtostr();
     71 int	ccdiodone();
     72 
     73 #define	ccdunit(x)	((minor(x) >> 3) & 0x7)	/* for consistency */
     74 
     75 #define	getcbuf()	\
     76 	((struct buf *)malloc(sizeof(struct buf), M_DEVBUF, M_WAITOK))
     77 #define putcbuf(bp)	\
     78 	free((caddr_t)(bp), M_DEVBUF)
     79 
     80 struct ccd_softc {
     81 	int		 sc_flags;		/* flags */
     82 	size_t		 sc_size;		/* size of ccd */
     83 	int		 sc_ileave;		/* interleave */
     84 	int		 sc_nccdisks;		/* number of components */
     85 	struct ccdcinfo	 sc_cinfo[NCCDISKS];	/* component info */
     86 	struct ccdiinfo	 *sc_itable;		/* interleave table */
     87 	int		 sc_usecnt;		/* number of requests active */
     88 	struct buf	 *sc_bp;		/* "current" request */
     89 	int		 sc_dk;			/* disk index */
     90 } ccd_softc[NCCD];
     91 
     92 /* sc_flags */
     93 #define	CDF_ALIVE	0x01
     94 #define CDF_INITED	0x02
     95 
     96 /*
     97  * ccdattach() is called at boot time in new systems.  We do
     98  * nothing here since old systems will not call this.
     99  */
    100 
    101 void
    102 ccdattach(n)
    103         int n;
    104 {
    105 }
    106 
    107 ccdinit(ccd)
    108 	struct ccddevice *ccd;
    109 {
    110 	register struct ccd_softc *cs = &ccd_softc[ccd->ccd_unit];
    111 	register struct ccdcinfo *ci;
    112 	register size_t size;
    113 	register int ix;
    114 	size_t minsize;
    115 	dev_t dev;
    116 
    117 #ifdef DEBUG
    118 	if (ccddebug & (CDB_FOLLOW|CDB_INIT))
    119 		printf("ccdinit: unit %d\n", ccd->ccd_unit);
    120 #endif
    121 	cs->sc_dk = ccd->ccd_dk;
    122 	cs->sc_size = 0;
    123 	cs->sc_ileave = ccd->ccd_interleave;
    124 	cs->sc_nccdisks = 0;
    125 	/*
    126 	 * Verify that each component piece exists and record
    127 	 * relevant information about it.
    128 	 */
    129 	minsize = 0;
    130 	for (ix = 0; ix < NCCDISKS; ix++) {
    131 		if ((dev = ccd->ccd_dev[ix]) == NODEV)
    132 			break;
    133 		ci = &cs->sc_cinfo[ix];
    134 		ci->ci_dev = dev;
    135 		/*
    136 		 * Calculate size (truncated to interleave boundary
    137 		 * if necessary.
    138 		 */
    139 		if (bdevsw[major(dev)].d_psize) {
    140 			size = (size_t) (*bdevsw[major(dev)].d_psize)(dev);
    141 			if ((int)size < 0)
    142 				size = 0;
    143 		} else
    144 			size = 0;
    145 		if (cs->sc_ileave > 1)
    146 			size -= size % cs->sc_ileave;
    147 		if (size == 0) {
    148 			printf("ccd%d: not configured (component %s missing)\n",
    149 			       ccd->ccd_unit, ccddevtostr(ci->ci_dev));
    150 			return(0);
    151 		}
    152 		if (minsize == 0 || size < minsize)
    153 			minsize = size;
    154 		ci->ci_size = size;
    155 		cs->sc_size += size;
    156 		cs->sc_nccdisks++;
    157 	}
    158 	/*
    159 	 * If uniform interleave is desired set all sizes to that of
    160 	 * the smallest component.
    161 	 */
    162 	if (ccd->ccd_flags & CDF_UNIFORM) {
    163 		for (ci = cs->sc_cinfo;
    164 		     ci < &cs->sc_cinfo[cs->sc_nccdisks]; ci++)
    165 			ci->ci_size = minsize;
    166 		cs->sc_size = cs->sc_nccdisks * minsize;
    167 	}
    168 	/*
    169 	 * Construct the interleave table
    170 	 */
    171 	if (!ccdinterleave(cs))
    172 		return(0);
    173 	if (ccd->ccd_dk >= 0)
    174 		dk_wpms[ccd->ccd_dk] = 32 * (60 * DEV_BSIZE / 2);	/* XXX */
    175 	printf("ccd%d: %d components ", ccd->ccd_unit, cs->sc_nccdisks);
    176 	for (ix = 0; ix < cs->sc_nccdisks; ix++)
    177 		printf("%c%s%c",
    178 		       ix == 0 ? '(' : ' ',
    179 		       ccddevtostr(cs->sc_cinfo[ix].ci_dev),
    180 		       ix == cs->sc_nccdisks - 1 ? ')' : ',');
    181 	printf(", %d blocks ", cs->sc_size);
    182 	if (cs->sc_ileave)
    183 		printf("interleaved at %d blocks\n", cs->sc_ileave);
    184 	else
    185 		printf("concatenated\n");
    186 	cs->sc_flags = CDF_ALIVE | CDF_INITED;
    187 	return(1);
    188 }
    189 
    190 /*
    191  * XXX not really ccd specific.
    192  */
    193 char *
    194 ccddevtostr(dev)
    195 	dev_t dev;
    196 {
    197 	static char dbuf[5];
    198 
    199 	dbuf[1] = 'd';
    200 	switch (major(dev)) {
    201 	case 2:
    202 		dbuf[0] = 'r';
    203 		break;
    204 	case 4:
    205 		dbuf[0] = 's';
    206 		break;
    207 	case 5:
    208 		dbuf[0] = 'c';
    209 		break;
    210 	default:
    211 		dbuf[0] = dbuf[1] = '?';
    212 		break;
    213 	}
    214 	dbuf[2] = (minor(dev) >> 3) + '0';
    215 	dbuf[3] = (minor(dev) & 7) + 'a';
    216 	dbuf[4] = '\0';
    217 	return (dbuf);
    218 }
    219 
    220 ccdinterleave(cs)
    221 	register struct ccd_softc *cs;
    222 {
    223 	register struct ccdcinfo *ci, *smallci;
    224 	register struct ccdiinfo *ii;
    225 	register daddr_t bn, lbn;
    226 	register int ix;
    227 	u_long size;
    228 
    229 #ifdef DEBUG
    230 	if (ccddebug & CDB_INIT)
    231 		printf("ccdinterleave(%x): ileave %d\n", cs, cs->sc_ileave);
    232 #endif
    233 	/*
    234 	 * Allocate an interleave table.
    235 	 * Chances are this is too big, but we don't care.
    236 	 */
    237 	size = (cs->sc_nccdisks + 1) * sizeof(struct ccdiinfo);
    238 	cs->sc_itable = (struct ccdiinfo *)malloc(size, M_DEVBUF, M_WAITOK);
    239 	bzero((caddr_t)cs->sc_itable, size);
    240 	/*
    241 	 * Trivial case: no interleave (actually interleave of disk size).
    242 	 * Each table entry represent a single component in its entirety.
    243 	 */
    244 	if (cs->sc_ileave == 0) {
    245 		bn = 0;
    246 		ii = cs->sc_itable;
    247 		for (ix = 0; ix < cs->sc_nccdisks; ix++) {
    248 			ii->ii_ndisk = 1;
    249 			ii->ii_startblk = bn;
    250 			ii->ii_startoff = 0;
    251 			ii->ii_index[0] = ix;
    252 			bn += cs->sc_cinfo[ix].ci_size;
    253 			ii++;
    254 		}
    255 		ii->ii_ndisk = 0;
    256 #ifdef DEBUG
    257 		if (ccddebug & CDB_INIT)
    258 			printiinfo(cs->sc_itable);
    259 #endif
    260 		return(1);
    261 	}
    262 	/*
    263 	 * The following isn't fast or pretty; it doesn't have to be.
    264 	 */
    265 	size = 0;
    266 	bn = lbn = 0;
    267 	for (ii = cs->sc_itable; ; ii++) {
    268 		/*
    269 		 * Locate the smallest of the remaining components
    270 		 */
    271 		smallci = NULL;
    272 		for (ci = cs->sc_cinfo;
    273 		     ci < &cs->sc_cinfo[cs->sc_nccdisks]; ci++)
    274 			if (ci->ci_size > size &&
    275 			    (smallci == NULL ||
    276 			     ci->ci_size < smallci->ci_size))
    277 				smallci = ci;
    278 		/*
    279 		 * Nobody left, all done
    280 		 */
    281 		if (smallci == NULL) {
    282 			ii->ii_ndisk = 0;
    283 			break;
    284 		}
    285 		/*
    286 		 * Record starting logical block and component offset
    287 		 */
    288 		ii->ii_startblk = bn / cs->sc_ileave;
    289 		ii->ii_startoff = lbn;
    290 		/*
    291 		 * Determine how many disks take part in this interleave
    292 		 * and record their indices.
    293 		 */
    294 		ix = 0;
    295 		for (ci = cs->sc_cinfo;
    296 		     ci < &cs->sc_cinfo[cs->sc_nccdisks]; ci++)
    297 			if (ci->ci_size >= smallci->ci_size)
    298 				ii->ii_index[ix++] = ci - cs->sc_cinfo;
    299 		ii->ii_ndisk = ix;
    300 		bn += ix * (smallci->ci_size - size);
    301 		lbn = smallci->ci_size / cs->sc_ileave;
    302 		size = smallci->ci_size;
    303 	}
    304 #ifdef DEBUG
    305 	if (ccddebug & CDB_INIT)
    306 		printiinfo(cs->sc_itable);
    307 #endif
    308 	return(1);
    309 }
    310 
    311 #ifdef DEBUG
    312 printiinfo(ii)
    313 	struct ccdiinfo *ii;
    314 {
    315 	register int ix, i;
    316 
    317 	for (ix = 0; ii->ii_ndisk; ix++, ii++) {
    318 		printf(" itab[%d]: #dk %d sblk %d soff %d",
    319 		       ix, ii->ii_ndisk, ii->ii_startblk, ii->ii_startoff);
    320 		for (i = 0; i < ii->ii_ndisk; i++)
    321 			printf(" %d", ii->ii_index[i]);
    322 		printf("\n");
    323 	}
    324 }
    325 #endif
    326 
    327 ccdopen(dev, flags)
    328 	dev_t dev;
    329 {
    330 	int unit = ccdunit(dev);
    331 	register struct ccd_softc *cs = &ccd_softc[unit];
    332 
    333 #ifdef DEBUG
    334 	if (ccddebug & CDB_FOLLOW)
    335 		printf("ccdopen(%x, %x)\n", dev, flags);
    336 #endif
    337 	if (unit >= NCCD || (cs->sc_flags & CDF_ALIVE) == 0)
    338 		return(ENXIO);
    339 	return(0);
    340 }
    341 
    342 void
    343 ccdstrategy(bp)
    344 	register struct buf *bp;
    345 {
    346 	register int unit = ccdunit(bp->b_dev);
    347 	register struct ccd_softc *cs = &ccd_softc[unit];
    348 	register daddr_t bn;
    349 	register int sz, s;
    350 
    351 #ifdef DEBUG
    352 	if (ccddebug & CDB_FOLLOW)
    353 		printf("ccdstrategy(%x): unit %d\n", bp, unit);
    354 #endif
    355 	if ((cs->sc_flags & CDF_INITED) == 0) {
    356 		bp->b_error = ENXIO;
    357 		bp->b_flags |= B_ERROR;
    358 		goto done;
    359 	}
    360 	bn = bp->b_blkno;
    361 	sz = howmany(bp->b_bcount, DEV_BSIZE);
    362 	if (bn < 0 || bn + sz > cs->sc_size) {
    363 		sz = cs->sc_size - bn;
    364 		if (sz == 0) {
    365 			bp->b_resid = bp->b_bcount;
    366 			goto done;
    367 		}
    368 		if (sz < 0) {
    369 			bp->b_error = EINVAL;
    370 			bp->b_flags |= B_ERROR;
    371 			goto done;
    372 		}
    373 		bp->b_bcount = dbtob(sz);
    374 	}
    375 	bp->b_resid = bp->b_bcount;
    376 	/*
    377 	 * "Start" the unit.
    378 	 * XXX: the use of sc_bp is just to retain the "traditional"
    379 	 * interface to the start routine.
    380 	 */
    381 	s = splbio();
    382 	cs->sc_bp = bp;
    383 	ccdstart(unit);
    384 	splx(s);
    385 	return;
    386 done:
    387 	biodone(bp);
    388 }
    389 
    390 ccdstart(unit)
    391 	int unit;
    392 {
    393 	register struct ccd_softc *cs = &ccd_softc[unit];
    394 	register struct buf *bp = cs->sc_bp;
    395 	register long bcount, rcount;
    396 	struct buf *cbp;
    397 	caddr_t addr;
    398 	daddr_t bn;
    399 
    400 #ifdef DEBUG
    401 	if (ccddebug & CDB_FOLLOW)
    402 		printf("ccdstart(%d)\n", unit);
    403 #endif
    404 	/*
    405 	 * Instumentation (not real meaningful)
    406 	 */
    407 	cs->sc_usecnt++;
    408 	if (cs->sc_dk >= 0) {
    409 		dk_busy |= 1 << cs->sc_dk;
    410 		dk_xfer[cs->sc_dk]++;
    411 		dk_wds[cs->sc_dk] += bp->b_bcount >> 6;
    412 	}
    413 	/*
    414 	 * Allocate component buffers and fire off the requests
    415 	 */
    416 	bn = bp->b_blkno;
    417 	addr = bp->b_un.b_addr;
    418 	for (bcount = bp->b_bcount; bcount > 0; bcount -= rcount) {
    419 		cbp = ccdbuffer(cs, bp, bn, addr, bcount);
    420 		rcount = cbp->b_bcount;
    421 		(*bdevsw[major(cbp->b_dev)].d_strategy)(cbp);
    422 		bn += btodb(rcount);
    423 		addr += rcount;
    424 	}
    425 }
    426 
    427 /*
    428  * Build a component buffer header.
    429  */
    430 struct buf *
    431 ccdbuffer(cs, bp, bn, addr, bcount)
    432 	register struct ccd_softc *cs;
    433 	struct buf *bp;
    434 	daddr_t bn;
    435 	caddr_t addr;
    436 	long bcount;
    437 {
    438 	register struct ccdcinfo *ci;
    439 	register struct buf *cbp;
    440 	register daddr_t cbn, cboff;
    441 
    442 #ifdef DEBUG
    443 	if (ccddebug & CDB_IO)
    444 		printf("ccdbuffer(%x, %x, %d, %x, %d)\n",
    445 		       cs, bp, bn, addr, bcount);
    446 #endif
    447 	/*
    448 	 * Determine which component bn falls in.
    449 	 */
    450 	cbn = bn;
    451 	cboff = 0;
    452 	/*
    453 	 * Serially concatenated
    454 	 */
    455 	if (cs->sc_ileave == 0) {
    456 		register daddr_t sblk;
    457 
    458 		sblk = 0;
    459 		for (ci = cs->sc_cinfo; cbn >= sblk + ci->ci_size; ci++)
    460 			sblk += ci->ci_size;
    461 		cbn -= sblk;
    462 	}
    463 	/*
    464 	 * Interleaved
    465 	 */
    466 	else {
    467 		register struct ccdiinfo *ii;
    468 		int ccdisk, off;
    469 
    470 		cboff = cbn % cs->sc_ileave;
    471 		cbn /= cs->sc_ileave;
    472 		for (ii = cs->sc_itable; ii->ii_ndisk; ii++)
    473 			if (ii->ii_startblk > cbn)
    474 				break;
    475 		ii--;
    476 		off = cbn - ii->ii_startblk;
    477 		if (ii->ii_ndisk == 1) {
    478 			ccdisk = ii->ii_index[0];
    479 			cbn = ii->ii_startoff + off;
    480 		} else {
    481 			ccdisk = ii->ii_index[off % ii->ii_ndisk];
    482 			cbn = ii->ii_startoff + off / ii->ii_ndisk;
    483 		}
    484 		cbn *= cs->sc_ileave;
    485 		ci = &cs->sc_cinfo[ccdisk];
    486 	}
    487 	/*
    488 	 * Fill in the component buf structure.
    489 	 */
    490 	cbp = getcbuf();
    491 	cbp->b_flags = bp->b_flags | B_CALL;
    492 	cbp->b_iodone = ccdiodone;
    493 	cbp->b_proc = bp->b_proc;
    494 	cbp->b_dev = ci->ci_dev;
    495 	cbp->b_blkno = cbn + cboff;
    496 	cbp->b_un.b_addr = addr;
    497 	cbp->b_vp = 0;
    498 	if (cs->sc_ileave == 0)
    499 		cbp->b_bcount = dbtob(ci->ci_size - cbn);
    500 	else
    501 		cbp->b_bcount = dbtob(cs->sc_ileave - cboff);
    502 	if (cbp->b_bcount > bcount)
    503 		cbp->b_bcount = bcount;
    504 	/*
    505 	 * XXX: context for ccdiodone
    506 	 */
    507 	cbp->b_saveaddr = (caddr_t)bp;
    508 	cbp->b_pfcent = ((cs - ccd_softc) << 16) | (ci - cs->sc_cinfo);
    509 #ifdef DEBUG
    510 	if (ccddebug & CDB_IO)
    511 		printf(" dev %x(u%d): cbp %x bn %d addr %x bcnt %d\n",
    512 		       ci->ci_dev, ci-cs->sc_cinfo, cbp, cbp->b_blkno,
    513 		       cbp->b_un.b_addr, cbp->b_bcount);
    514 #endif
    515 	return(cbp);
    516 }
    517 
    518 ccdintr(unit)
    519 	int unit;
    520 {
    521 	register struct ccd_softc *cs = &ccd_softc[unit];
    522 	register struct buf *bp = cs->sc_bp;
    523 
    524 #ifdef DEBUG
    525 	if (ccddebug & CDB_FOLLOW)
    526 		printf("ccdintr(%d): bp %x\n", unit, bp);
    527 #endif
    528 	/*
    529 	 * Request is done for better or worse, wakeup the top half.
    530 	 */
    531 	if (--cs->sc_usecnt == 0 && cs->sc_dk >= 0)
    532 		dk_busy &= ~(1 << cs->sc_dk);
    533 	if (bp->b_flags & B_ERROR)
    534 		bp->b_resid = bp->b_bcount;
    535 	biodone(bp);
    536 }
    537 
    538 /*
    539  * Called by biodone at interrupt time.
    540  * Mark the component as done and if all components are done,
    541  * take a ccd interrupt.
    542  */
    543 ccdiodone(cbp)
    544 	register struct buf *cbp;
    545 {
    546 	register struct buf *bp = (struct buf *)cbp->b_saveaddr;/* XXX */
    547 	register int unit = (cbp->b_pfcent >> 16) & 0xFFFF;	/* XXX */
    548 	int count, s;
    549 
    550 	s = splbio();
    551 #ifdef DEBUG
    552 	if (ccddebug & CDB_FOLLOW)
    553 		printf("ccdiodone(%x)\n", cbp);
    554 	if (ccddebug & CDB_IO) {
    555 		printf("ccdiodone: bp %x bcount %d resid %d\n",
    556 		       bp, bp->b_bcount, bp->b_resid);
    557 		printf(" dev %x(u%d), cbp %x bn %d addr %x bcnt %d\n",
    558 		       cbp->b_dev, cbp->b_pfcent & 0xFFFF, cbp,
    559 		       cbp->b_blkno, cbp->b_un.b_addr, cbp->b_bcount);
    560 	}
    561 #endif
    562 
    563 	if (cbp->b_flags & B_ERROR) {
    564 		bp->b_flags |= B_ERROR;
    565 		bp->b_error = biowait(cbp);
    566 #ifdef DEBUG
    567 		printf("ccd%d: error %d on component %d\n",
    568 		       unit, bp->b_error, cbp->b_pfcent & 0xFFFF);
    569 #endif
    570 	}
    571 	count = cbp->b_bcount;
    572 	putcbuf(cbp);
    573 
    574 	/*
    575 	 * If all done, "interrupt".
    576 	 * Again, sc_bp is only used to preserve the traditional interface.
    577 	 */
    578 	bp->b_resid -= count;
    579 	if (bp->b_resid < 0)
    580 		panic("ccdiodone: count");
    581 	if (bp->b_resid == 0) {
    582 		ccd_softc[unit].sc_bp = bp;
    583 		ccdintr(unit);
    584 	}
    585 	splx(s);
    586 }
    587 
    588 ccdioctl(dev, cmd, data, flag, p)
    589 	dev_t dev;
    590 	int cmd;
    591 	caddr_t data;
    592 	int flag;
    593 	struct proc *p;
    594 {
    595 	return(EINVAL);
    596 }
    597 
    598 ccdsize(dev)
    599 	dev_t dev;
    600 {
    601 	int unit = ccdunit(dev);
    602 	register struct ccd_softc *cs = &ccd_softc[unit];
    603 
    604 	if (unit >= NCCD || (cs->sc_flags & CDF_INITED) == 0)
    605 		return(-1);
    606 	return(cs->sc_size);
    607 }
    608 
    609 ccddump(dev)
    610 {
    611 	return(ENXIO);
    612 }
    613 #endif
    614