4.4BSD snapshot (revision 8.1); add 1993 to copyright
[unix-history] / usr / src / sys / dev / cd.c
CommitLineData
60f56dfc
KM
1/*
2 * Copyright (c) 1988 University of Utah.
06b4439d
KB
3 * Copyright (c) 1990, 1993
4 * The Regents of the University of California. All rights reserved.
60f56dfc
KM
5 *
6 * This code is derived from software contributed to Berkeley by
7 * the Systems Programming Group of the University of Utah Computer
8 * Science Department.
9 *
10 * %sccs.include.redist.c%
11 *
f7081d19 12 * from: Utah $Hdr: cd.c 1.6 90/11/28$
60f56dfc 13 *
06b4439d 14 * @(#)cd.c 8.1 (Berkeley) %G%
60f56dfc
KM
15 */
16
17/*
18 * "Concatenated" disk driver.
19 */
20#include "cd.h"
21#if NCD > 0
22
38a01dbe
KB
23#include <sys/param.h>
24#include <sys/systm.h>
27f6f581 25#include <sys/proc.h>
38a01dbe
KB
26#include <sys/errno.h>
27#include <sys/dkstat.h>
28#include <sys/buf.h>
29#include <sys/malloc.h>
30#include <sys/conf.h>
e24d3c41
MH
31#include <sys/stat.h>
32#ifdef COMPAT_NOLABEL
33#include <sys/ioctl.h>
34#include <sys/disklabel.h>
35#include <sys/fcntl.h>
36#endif
60f56dfc 37
38a01dbe 38#include <dev/cdvar.h>
60f56dfc
KM
39
40#ifdef DEBUG
41int cddebug = 0x00;
42#define CDB_FOLLOW 0x01
43#define CDB_INIT 0x02
44#define CDB_IO 0x04
45#endif
46
60f56dfc 47struct buf *cdbuffer();
f7081d19 48char *cddevtostr();
27f6f581 49void cdiodone();
60f56dfc 50
27f6f581 51#define cdunit(x) ((minor(x) >> 3) & 0xf) /* for consistency */
60f56dfc
KM
52
53#define getcbuf() \
54 ((struct buf *)malloc(sizeof(struct buf), M_DEVBUF, M_WAITOK))
55#define putcbuf(bp) \
56 free((caddr_t)(bp), M_DEVBUF)
57
58struct cd_softc {
59 int sc_flags; /* flags */
60 size_t sc_size; /* size of cd */
61 int sc_ileave; /* interleave */
62 int sc_ncdisks; /* number of components */
63 struct cdcinfo sc_cinfo[NCDISKS]; /* component info */
64 struct cdiinfo *sc_itable; /* interleave table */
65 int sc_usecnt; /* number of requests active */
60f56dfc 66 int sc_dk; /* disk index */
27f6f581 67};
60f56dfc
KM
68
69/* sc_flags */
70#define CDF_ALIVE 0x01
71#define CDF_INITED 0x02
72
27f6f581
MH
73struct cd_softc *cd_softc;
74int numcd;
75
76/*
77 * Since this is called after auto-configuration of devices,
78 * we can handle the initialization here.
79 *
80 * XXX this will not work if you want to use a cd as your primary
81 * swap device since swapconf() has been called before now.
82 */
83void
84cdattach(num)
85 int num;
86{
87 char *mem;
88 register u_long size;
89 register struct cddevice *cd;
90 extern int dkn;
91
92 if (num <= 0)
93 return;
94 size = num * sizeof(struct cd_softc);
95 mem = malloc(size, M_DEVBUF, M_NOWAIT);
96 if (mem == NULL) {
97 printf("WARNING: no memory for concatonated disks\n");
98 return;
99 }
100 bzero(mem, size);
101 cd_softc = (struct cd_softc *)mem;
102 numcd = num;
103 for (cd = cddevice; cd->cd_unit >= 0; cd++) {
104 /*
105 * XXX
106 * Assign disk index first so that init routine
107 * can use it (saves having the driver drag around
108 * the cddevice pointer just to set up the dk_*
109 * info in the open routine).
110 */
111 if (dkn < DK_NDRIVE)
112 cd->cd_dk = dkn++;
113 else
114 cd->cd_dk = -1;
115 if (cdinit(cd))
116 printf("cd%d configured\n", cd->cd_unit);
117 else if (cd->cd_dk >= 0) {
118 cd->cd_dk = -1;
119 dkn--;
120 }
121 }
122}
123
60f56dfc
KM
124cdinit(cd)
125 struct cddevice *cd;
126{
127 register struct cd_softc *cs = &cd_softc[cd->cd_unit];
128 register struct cdcinfo *ci;
129 register size_t size;
130 register int ix;
131 size_t minsize;
132 dev_t dev;
e24d3c41
MH
133 struct bdevsw *bsw;
134 int error;
27f6f581 135 struct proc *p = curproc; /* XXX */
60f56dfc
KM
136
137#ifdef DEBUG
138 if (cddebug & (CDB_FOLLOW|CDB_INIT))
139 printf("cdinit: unit %d\n", cd->cd_unit);
140#endif
141 cs->sc_dk = cd->cd_dk;
142 cs->sc_size = 0;
143 cs->sc_ileave = cd->cd_interleave;
144 cs->sc_ncdisks = 0;
145 /*
146 * Verify that each component piece exists and record
147 * relevant information about it.
148 */
149 minsize = 0;
150 for (ix = 0; ix < NCDISKS; ix++) {
151 if ((dev = cd->cd_dev[ix]) == NODEV)
152 break;
153 ci = &cs->sc_cinfo[ix];
154 ci->ci_dev = dev;
e24d3c41
MH
155 bsw = &bdevsw[major(dev)];
156 /*
157 * Open the partition
158 */
27f6f581
MH
159 if (bsw->d_open &&
160 (error = (*bsw->d_open)(dev, 0, S_IFBLK, p))) {
e24d3c41
MH
161 printf("cd%d: component %s open failed, error = %d\n",
162 cd->cd_unit, cddevtostr(dev), error);
163 return(0);
164 }
60f56dfc
KM
165 /*
166 * Calculate size (truncated to interleave boundary
167 * if necessary.
168 */
e24d3c41
MH
169 if (bsw->d_psize) {
170 size = (size_t) (*bsw->d_psize)(dev);
f7081d19 171 if ((int)size < 0)
60f56dfc
KM
172 size = 0;
173 } else
174 size = 0;
175 if (cs->sc_ileave > 1)
176 size -= size % cs->sc_ileave;
f7081d19
MH
177 if (size == 0) {
178 printf("cd%d: not configured (component %s missing)\n",
e24d3c41 179 cd->cd_unit, cddevtostr(dev));
60f56dfc 180 return(0);
f7081d19 181 }
e24d3c41
MH
182#ifdef COMPAT_NOLABEL
183 /*
184 * XXX if this is a 'c' partition then we need to mark the
185 * label area writeable since there cannot be a label.
186 */
187 if ((minor(dev) & 7) == 2 && bsw->d_open) {
188 int i, flag;
189
190 for (i = 0; i < nchrdev; i++)
191 if (cdevsw[i].d_open == bsw->d_open)
192 break;
193 if (i != nchrdev && cdevsw[i].d_ioctl) {
194 flag = 1;
195 (void)(*cdevsw[i].d_ioctl)(dev, DIOCWLABEL,
27f6f581 196 (caddr_t)&flag, FWRITE, p);
e24d3c41
MH
197 }
198 }
199#endif
60f56dfc
KM
200 if (minsize == 0 || size < minsize)
201 minsize = size;
202 ci->ci_size = size;
203 cs->sc_size += size;
204 cs->sc_ncdisks++;
205 }
206 /*
207 * If uniform interleave is desired set all sizes to that of
208 * the smallest component.
209 */
210 if (cd->cd_flags & CDF_UNIFORM) {
211 for (ci = cs->sc_cinfo;
212 ci < &cs->sc_cinfo[cs->sc_ncdisks]; ci++)
213 ci->ci_size = minsize;
214 cs->sc_size = cs->sc_ncdisks * minsize;
215 }
216 /*
217 * Construct the interleave table
218 */
219 if (!cdinterleave(cs))
220 return(0);
221 if (cd->cd_dk >= 0)
222 dk_wpms[cd->cd_dk] = 32 * (60 * DEV_BSIZE / 2); /* XXX */
f7081d19
MH
223 printf("cd%d: %d components ", cd->cd_unit, cs->sc_ncdisks);
224 for (ix = 0; ix < cs->sc_ncdisks; ix++)
225 printf("%c%s%c",
226 ix == 0 ? '(' : ' ',
227 cddevtostr(cs->sc_cinfo[ix].ci_dev),
228 ix == cs->sc_ncdisks - 1 ? ')' : ',');
229 printf(", %d blocks ", cs->sc_size);
60f56dfc 230 if (cs->sc_ileave)
f7081d19 231 printf("interleaved at %d blocks\n", cs->sc_ileave);
60f56dfc 232 else
f7081d19 233 printf("concatenated\n");
60f56dfc
KM
234 cs->sc_flags = CDF_ALIVE | CDF_INITED;
235 return(1);
236}
237
f7081d19
MH
238/*
239 * XXX not really cd specific.
27f6f581 240 * Could be called something like bdevtostr in machine/conf.c.
f7081d19
MH
241 */
242char *
243cddevtostr(dev)
244 dev_t dev;
245{
246 static char dbuf[5];
247
f7081d19 248 switch (major(dev)) {
27f6f581 249#ifdef hp300
f7081d19 250 case 2:
27f6f581 251 dbuf[0] = 'r'; dbuf[1] = 'd';
f7081d19
MH
252 break;
253 case 4:
27f6f581 254 dbuf[0] = 's'; dbuf[1] = 'd';
f7081d19
MH
255 break;
256 case 5:
27f6f581
MH
257 dbuf[0] = 'c'; dbuf[1] = 'd';
258 break;
259 case 6:
260 dbuf[0] = 'v'; dbuf[1] = 'n';
f7081d19 261 break;
27f6f581 262#endif
f7081d19
MH
263 default:
264 dbuf[0] = dbuf[1] = '?';
265 break;
266 }
267 dbuf[2] = (minor(dev) >> 3) + '0';
268 dbuf[3] = (minor(dev) & 7) + 'a';
269 dbuf[4] = '\0';
270 return (dbuf);
271}
272
60f56dfc
KM
273cdinterleave(cs)
274 register struct cd_softc *cs;
275{
276 register struct cdcinfo *ci, *smallci;
277 register struct cdiinfo *ii;
278 register daddr_t bn, lbn;
279 register int ix;
280 u_long size;
281
282#ifdef DEBUG
283 if (cddebug & CDB_INIT)
284 printf("cdinterleave(%x): ileave %d\n", cs, cs->sc_ileave);
285#endif
286 /*
287 * Allocate an interleave table.
288 * Chances are this is too big, but we don't care.
289 */
290 size = (cs->sc_ncdisks + 1) * sizeof(struct cdiinfo);
291 cs->sc_itable = (struct cdiinfo *)malloc(size, M_DEVBUF, M_WAITOK);
292 bzero((caddr_t)cs->sc_itable, size);
293 /*
294 * Trivial case: no interleave (actually interleave of disk size).
295 * Each table entry represent a single component in its entirety.
296 */
297 if (cs->sc_ileave == 0) {
298 bn = 0;
299 ii = cs->sc_itable;
300 for (ix = 0; ix < cs->sc_ncdisks; ix++) {
301 ii->ii_ndisk = 1;
302 ii->ii_startblk = bn;
303 ii->ii_startoff = 0;
304 ii->ii_index[0] = ix;
305 bn += cs->sc_cinfo[ix].ci_size;
306 ii++;
307 }
308 ii->ii_ndisk = 0;
309#ifdef DEBUG
310 if (cddebug & CDB_INIT)
311 printiinfo(cs->sc_itable);
312#endif
313 return(1);
314 }
315 /*
316 * The following isn't fast or pretty; it doesn't have to be.
317 */
318 size = 0;
319 bn = lbn = 0;
320 for (ii = cs->sc_itable; ; ii++) {
321 /*
322 * Locate the smallest of the remaining components
323 */
324 smallci = NULL;
325 for (ci = cs->sc_cinfo;
326 ci < &cs->sc_cinfo[cs->sc_ncdisks]; ci++)
327 if (ci->ci_size > size &&
328 (smallci == NULL ||
329 ci->ci_size < smallci->ci_size))
330 smallci = ci;
331 /*
332 * Nobody left, all done
333 */
334 if (smallci == NULL) {
335 ii->ii_ndisk = 0;
336 break;
337 }
338 /*
339 * Record starting logical block and component offset
340 */
341 ii->ii_startblk = bn / cs->sc_ileave;
342 ii->ii_startoff = lbn;
343 /*
344 * Determine how many disks take part in this interleave
345 * and record their indices.
346 */
347 ix = 0;
348 for (ci = cs->sc_cinfo;
349 ci < &cs->sc_cinfo[cs->sc_ncdisks]; ci++)
350 if (ci->ci_size >= smallci->ci_size)
351 ii->ii_index[ix++] = ci - cs->sc_cinfo;
352 ii->ii_ndisk = ix;
353 bn += ix * (smallci->ci_size - size);
354 lbn = smallci->ci_size / cs->sc_ileave;
355 size = smallci->ci_size;
356 }
357#ifdef DEBUG
358 if (cddebug & CDB_INIT)
359 printiinfo(cs->sc_itable);
360#endif
361 return(1);
362}
363
364#ifdef DEBUG
365printiinfo(ii)
366 struct cdiinfo *ii;
367{
368 register int ix, i;
369
370 for (ix = 0; ii->ii_ndisk; ix++, ii++) {
371 printf(" itab[%d]: #dk %d sblk %d soff %d",
372 ix, ii->ii_ndisk, ii->ii_startblk, ii->ii_startoff);
373 for (i = 0; i < ii->ii_ndisk; i++)
374 printf(" %d", ii->ii_index[i]);
375 printf("\n");
376 }
377}
378#endif
379
380cdopen(dev, flags)
381 dev_t dev;
382{
383 int unit = cdunit(dev);
384 register struct cd_softc *cs = &cd_softc[unit];
385
386#ifdef DEBUG
387 if (cddebug & CDB_FOLLOW)
388 printf("cdopen(%x, %x)\n", dev, flags);
389#endif
27f6f581 390 if (unit >= numcd || (cs->sc_flags & CDF_ALIVE) == 0)
60f56dfc
KM
391 return(ENXIO);
392 return(0);
393}
394
395cdstrategy(bp)
396 register struct buf *bp;
397{
398 register int unit = cdunit(bp->b_dev);
399 register struct cd_softc *cs = &cd_softc[unit];
0e1872ad
KM
400 register daddr_t bn;
401 register int sz, s;
60f56dfc
KM
402
403#ifdef DEBUG
404 if (cddebug & CDB_FOLLOW)
405 printf("cdstrategy(%x): unit %d\n", bp, unit);
406#endif
407 if ((cs->sc_flags & CDF_INITED) == 0) {
408 bp->b_error = ENXIO;
0e1872ad
KM
409 bp->b_flags |= B_ERROR;
410 goto done;
60f56dfc
KM
411 }
412 bn = bp->b_blkno;
0e1872ad 413 sz = howmany(bp->b_bcount, DEV_BSIZE);
60f56dfc 414 if (bn < 0 || bn + sz > cs->sc_size) {
0e1872ad
KM
415 sz = cs->sc_size - bn;
416 if (sz == 0) {
417 bp->b_resid = bp->b_bcount;
418 goto done;
419 }
420 if (sz < 0) {
421 bp->b_error = EINVAL;
422 bp->b_flags |= B_ERROR;
60f56dfc 423 goto done;
0e1872ad
KM
424 }
425 bp->b_bcount = dbtob(sz);
60f56dfc 426 }
0e1872ad 427 bp->b_resid = bp->b_bcount;
60f56dfc
KM
428 /*
429 * "Start" the unit.
60f56dfc
KM
430 */
431 s = splbio();
27f6f581 432 cdstart(cs, bp);
60f56dfc
KM
433 splx(s);
434 return;
60f56dfc 435done:
0e1872ad 436 biodone(bp);
60f56dfc
KM
437}
438
27f6f581
MH
439cdstart(cs, bp)
440 register struct cd_softc *cs;
441 register struct buf *bp;
60f56dfc 442{
60f56dfc
KM
443 register long bcount, rcount;
444 struct buf *cbp;
445 caddr_t addr;
446 daddr_t bn;
447
448#ifdef DEBUG
449 if (cddebug & CDB_FOLLOW)
27f6f581 450 printf("cdstart(%x, %x)\n", cs, bp);
60f56dfc
KM
451#endif
452 /*
453 * Instumentation (not real meaningful)
454 */
455 cs->sc_usecnt++;
456 if (cs->sc_dk >= 0) {
457 dk_busy |= 1 << cs->sc_dk;
458 dk_xfer[cs->sc_dk]++;
459 dk_wds[cs->sc_dk] += bp->b_bcount >> 6;
460 }
461 /*
462 * Allocate component buffers and fire off the requests
463 */
464 bn = bp->b_blkno;
465 addr = bp->b_un.b_addr;
466 for (bcount = bp->b_bcount; bcount > 0; bcount -= rcount) {
467 cbp = cdbuffer(cs, bp, bn, addr, bcount);
468 rcount = cbp->b_bcount;
469 (*bdevsw[major(cbp->b_dev)].d_strategy)(cbp);
470 bn += btodb(rcount);
471 addr += rcount;
472 }
473}
474
475/*
476 * Build a component buffer header.
477 */
478struct buf *
479cdbuffer(cs, bp, bn, addr, bcount)
480 register struct cd_softc *cs;
481 struct buf *bp;
482 daddr_t bn;
483 caddr_t addr;
484 long bcount;
485{
486 register struct cdcinfo *ci;
487 register struct buf *cbp;
488 register daddr_t cbn, cboff;
489
490#ifdef DEBUG
491 if (cddebug & CDB_IO)
492 printf("cdbuffer(%x, %x, %d, %x, %d)\n",
493 cs, bp, bn, addr, bcount);
494#endif
495 /*
496 * Determine which component bn falls in.
497 */
498 cbn = bn;
499 cboff = 0;
500 /*
501 * Serially concatenated
502 */
503 if (cs->sc_ileave == 0) {
504 register daddr_t sblk;
505
506 sblk = 0;
507 for (ci = cs->sc_cinfo; cbn >= sblk + ci->ci_size; ci++)
508 sblk += ci->ci_size;
509 cbn -= sblk;
510 }
511 /*
512 * Interleaved
513 */
514 else {
515 register struct cdiinfo *ii;
516 int cdisk, off;
517
518 cboff = cbn % cs->sc_ileave;
519 cbn /= cs->sc_ileave;
520 for (ii = cs->sc_itable; ii->ii_ndisk; ii++)
521 if (ii->ii_startblk > cbn)
522 break;
523 ii--;
524 off = cbn - ii->ii_startblk;
525 if (ii->ii_ndisk == 1) {
526 cdisk = ii->ii_index[0];
527 cbn = ii->ii_startoff + off;
528 } else {
529 cdisk = ii->ii_index[off % ii->ii_ndisk];
530 cbn = ii->ii_startoff + off / ii->ii_ndisk;
531 }
532 cbn *= cs->sc_ileave;
533 ci = &cs->sc_cinfo[cdisk];
534 }
535 /*
536 * Fill in the component buf structure.
537 */
538 cbp = getcbuf();
539 cbp->b_flags = bp->b_flags | B_CALL;
540 cbp->b_iodone = cdiodone;
541 cbp->b_proc = bp->b_proc;
542 cbp->b_dev = ci->ci_dev;
543 cbp->b_blkno = cbn + cboff;
544 cbp->b_un.b_addr = addr;
0e1872ad 545 cbp->b_vp = 0;
60f56dfc
KM
546 if (cs->sc_ileave == 0)
547 cbp->b_bcount = dbtob(ci->ci_size - cbn);
548 else
549 cbp->b_bcount = dbtob(cs->sc_ileave - cboff);
550 if (cbp->b_bcount > bcount)
551 cbp->b_bcount = bcount;
552 /*
27f6f581 553 * XXX context for cdiodone
60f56dfc 554 */
0e1872ad 555 cbp->b_saveaddr = (caddr_t)bp;
60f56dfc
KM
556 cbp->b_pfcent = ((cs - cd_softc) << 16) | (ci - cs->sc_cinfo);
557#ifdef DEBUG
558 if (cddebug & CDB_IO)
559 printf(" dev %x(u%d): cbp %x bn %d addr %x bcnt %d\n",
560 ci->ci_dev, ci-cs->sc_cinfo, cbp, cbp->b_blkno,
561 cbp->b_un.b_addr, cbp->b_bcount);
562#endif
563 return(cbp);
564}
565
27f6f581
MH
566cdintr(cs, bp)
567 register struct cd_softc *cs;
568 register struct buf *bp;
60f56dfc 569{
60f56dfc
KM
570
571#ifdef DEBUG
572 if (cddebug & CDB_FOLLOW)
27f6f581 573 printf("cdintr(%x, %x)\n", cs, bp);
60f56dfc
KM
574#endif
575 /*
576 * Request is done for better or worse, wakeup the top half.
577 */
578 if (--cs->sc_usecnt == 0 && cs->sc_dk >= 0)
579 dk_busy &= ~(1 << cs->sc_dk);
580 if (bp->b_flags & B_ERROR)
581 bp->b_resid = bp->b_bcount;
0e1872ad 582 biodone(bp);
60f56dfc
KM
583}
584
585/*
0e1872ad 586 * Called by biodone at interrupt time.
60f56dfc
KM
587 * Mark the component as done and if all components are done,
588 * take a cd interrupt.
589 */
27f6f581 590void
60f56dfc
KM
591cdiodone(cbp)
592 register struct buf *cbp;
593{
0e1872ad 594 register struct buf *bp = (struct buf *)cbp->b_saveaddr;/* XXX */
60f56dfc
KM
595 register int unit = (cbp->b_pfcent >> 16) & 0xFFFF; /* XXX */
596 int count, s;
597
598 s = splbio();
599#ifdef DEBUG
600 if (cddebug & CDB_FOLLOW)
601 printf("cdiodone(%x)\n", cbp);
602 if (cddebug & CDB_IO) {
603 printf("cdiodone: bp %x bcount %d resid %d\n",
604 bp, bp->b_bcount, bp->b_resid);
605 printf(" dev %x(u%d), cbp %x bn %d addr %x bcnt %d\n",
606 cbp->b_dev, cbp->b_pfcent & 0xFFFF, cbp,
607 cbp->b_blkno, cbp->b_un.b_addr, cbp->b_bcount);
608 }
609#endif
610
611 if (cbp->b_flags & B_ERROR) {
612 bp->b_flags |= B_ERROR;
0e1872ad 613 bp->b_error = biowait(cbp);
60f56dfc
KM
614#ifdef DEBUG
615 printf("cd%d: error %d on component %d\n",
616 unit, bp->b_error, cbp->b_pfcent & 0xFFFF);
617#endif
618 }
619 count = cbp->b_bcount;
620 putcbuf(cbp);
621
622 /*
623 * If all done, "interrupt".
60f56dfc
KM
624 */
625 bp->b_resid -= count;
626 if (bp->b_resid < 0)
627 panic("cdiodone: count");
27f6f581
MH
628 if (bp->b_resid == 0)
629 cdintr(&cd_softc[unit], bp);
60f56dfc
KM
630 splx(s);
631}
632
633cdread(dev, uio)
634 dev_t dev;
635 struct uio *uio;
636{
637 register int unit = cdunit(dev);
638
639#ifdef DEBUG
640 if (cddebug & CDB_FOLLOW)
641 printf("cdread(%x, %x)\n", dev, uio);
642#endif
27f6f581 643 return(physio(cdstrategy, NULL, dev, B_READ, minphys, uio));
60f56dfc
KM
644}
645
646cdwrite(dev, uio)
647 dev_t dev;
648 struct uio *uio;
649{
650 register int unit = cdunit(dev);
651
652#ifdef DEBUG
653 if (cddebug & CDB_FOLLOW)
654 printf("cdwrite(%x, %x)\n", dev, uio);
655#endif
27f6f581 656 return(physio(cdstrategy, NULL, dev, B_WRITE, minphys, uio));
60f56dfc
KM
657}
658
659cdioctl(dev, cmd, data, flag)
660 dev_t dev;
661 int cmd;
662 caddr_t data;
663 int flag;
664{
665 return(EINVAL);
666}
667
668cdsize(dev)
669 dev_t dev;
670{
671 int unit = cdunit(dev);
672 register struct cd_softc *cs = &cd_softc[unit];
673
27f6f581 674 if (unit >= numcd || (cs->sc_flags & CDF_INITED) == 0)
60f56dfc
KM
675 return(-1);
676 return(cs->sc_size);
677}
678
679cddump(dev)
680{
681 return(ENXIO);
682}
683#endif