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