diff options
author | Michael Shalayeff <mickey@cvs.openbsd.org> | 2005-02-24 19:36:40 +0000 |
---|---|---|
committer | Michael Shalayeff <mickey@cvs.openbsd.org> | 2005-02-24 19:36:40 +0000 |
commit | a213ea1230342e373bf65cb1aa847fe70a4726f5 (patch) | |
tree | fb8808aca4366b9350a6129b2e356506521e9997 | |
parent | f58312625236d1cd77e2df24df37ceedd64581f5 (diff) |
fix a buffer-done race that makes mirroring work now;
use pool instead of malloc/free and this fixes new buffer mode;
implement DIOCGPDINFO for disklabel -d;
tested by henning@ millert@ fries@
-rw-r--r-- | sys/dev/ccd.c | 243 | ||||
-rw-r--r-- | sys/dev/ccdvar.h | 44 |
2 files changed, 155 insertions, 132 deletions
diff --git a/sys/dev/ccd.c b/sys/dev/ccd.c index 9d1740c3657..b2ee154bddc 100644 --- a/sys/dev/ccd.c +++ b/sys/dev/ccd.c @@ -1,9 +1,10 @@ -/* $OpenBSD: ccd.c,v 1.55 2004/02/15 02:45:46 tedu Exp $ */ +/* $OpenBSD: ccd.c,v 1.56 2005/02/24 19:36:39 mickey Exp $ */ /* $NetBSD: ccd.c,v 1.33 1996/05/05 04:21:14 thorpej Exp $ */ /*- * Copyright (c) 1996 The NetBSD Foundation, Inc. * Copyright (c) 1997 Niklas Hallqvist. + * Copyright (c) 2005 Michael Shalayeff. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation @@ -92,6 +93,7 @@ * * Buffer scatter/gather policy by Niklas Hallqvist. */ +/*#define CCDDEBUG */ #include <sys/param.h> #include <sys/systm.h> @@ -99,6 +101,7 @@ #include <sys/errno.h> #include <sys/buf.h> #include <sys/malloc.h> +#include <sys/pool.h> #include <sys/namei.h> #include <sys/stat.h> #include <sys/ioctl.h> @@ -140,28 +143,28 @@ int ccddebug = 0x00; #endif #define ccdunit(x) DISKUNIT(x) - -struct ccdseg { - caddr_t cs_sgaddr; /* scatter/gather segment addresses */ - long cs_sglen; /* scatter/gather segment lengths */ -}; +#define CCDLABELDEV(dev) \ + (MAKEDISKDEV(major((dev)), ccdunit((dev)), RAW_PART)) struct ccdbuf { struct buf cb_buf; /* new I/O buf */ struct buf *cb_obp; /* ptr. to original I/O buf */ - int cb_unit; /* target unit */ + struct ccd_softc*cb_sc; /* point back to the device */ + struct ccdbuf *cb_dep; /* mutual ptrs for mirror part */ int cb_comp; /* target component */ int cb_flags; /* misc. flags */ #define CBF_MIRROR 0x01 /* we're for a mirror component */ #define CBF_OLD 0x02 /* use old I/O protocol */ +#define CBF_DONE 0x04 /* this buffer is done */ int cb_sgcnt; /* scatter/gather segment count */ - struct ccdseg *cb_sg; /* scatter/gather segments */ +#define CCD_SGMAX (MAXBSIZE >> PAGE_SHIFT) + struct ccdseg { + caddr_t cs_sgaddr; /* scatter/gather segment addresses */ + long cs_sglen; /* scatter/gather segment lengths */ + } cb_sg[1]; }; -#define CCDLABELDEV(dev) \ - (MAKEDISKDEV(major((dev)), ccdunit((dev)), RAW_PART)) - /* called by main() at boot time */ void ccdattach(int); @@ -170,13 +173,14 @@ void ccdiodone(struct buf *); int ccdsize(dev_t); void ccdstart(struct ccd_softc *, struct buf *); -void ccdinterleave(struct ccd_softc *, int); +void ccdinterleave(struct ccd_softc *); void ccdintr(struct ccd_softc *, struct buf *); int ccdinit(struct ccddevice *, char **, struct proc *); int ccdlookup(char *, struct proc *p, struct vnode **); long ccdbuffer(struct ccd_softc *, struct buf *, daddr_t, caddr_t, long, struct ccdbuf **, int); -void ccdgetdisklabel(dev_t); +void ccdgetdisklabel(dev_t, struct ccd_softc *, struct disklabel *, + struct cpu_disklabel *, int); void ccdmakedisklabel(struct ccd_softc *); int ccdlock(struct ccd_softc *); void ccdunlock(struct ccd_softc *); @@ -191,28 +195,35 @@ void printiinfo(struct ccdiinfo *); struct ccd_softc *ccd_softc; struct ccddevice *ccddevs; int numccd = 0; +int ccdbufsizeof; -/* A separate map so that locking on kernel_map won't happen in interrupts */ -static struct vm_map *ccdmap; +/* + * A separate map so that locking on kernel_map won't happen in interrupts + * (XXX due to fragmentation this might fail easy and panic the kernel) + */ +struct vm_map *ccdmap; /* * Set when a process need some kvm. * XXX should we fallback to old I/O policy instead when out of ccd kvm? */ -static int ccd_need_kvm = 0; +int ccd_need_kvm = 0; + +/* + * struct ccdbuf allocator + */ +struct pool ccdbufpl; /* * Manage the ccd buffer structures. */ INLINE struct ccdbuf * -getccdbuf() +getccdbuf(void) { struct ccdbuf *cbp; - cbp = malloc(sizeof (struct ccdbuf), M_DEVBUF, M_WAITOK); - bzero(cbp, sizeof (struct ccdbuf)); - cbp->cb_sg = malloc(sizeof (struct ccdseg) * MAXBSIZE >> PAGE_SHIFT, - M_DEVBUF, M_WAITOK); + if ((cbp = pool_get(&ccdbufpl, PR_WAITOK))) + bzero(cbp, ccdbufsizeof); return (cbp); } @@ -220,8 +231,7 @@ INLINE void putccdbuf(cbp) struct ccdbuf *cbp; { - free((caddr_t)cbp->cb_sg, M_DEVBUF); - free((caddr_t)cbp, M_DEVBUF); + pool_put(&ccdbufpl, cbp); } /* @@ -239,6 +249,12 @@ ccdattach(num) return; } + ccdbufsizeof = sizeof(struct ccdbuf) + + (CCD_SGMAX - 1) * sizeof(struct ccdseg); + pool_init(&ccdbufpl, ccdbufsizeof, 0, 0, 0, "ccdbufpl", NULL); + pool_setlowat(&ccdbufpl, 16); + pool_sethiwat(&ccdbufpl, 1024); + ccd_softc = (struct ccd_softc *)malloc(num * sizeof(struct ccd_softc), M_DEVBUF, M_NOWAIT); ccddevs = (struct ccddevice *)malloc(num * sizeof(struct ccddevice), @@ -277,7 +293,8 @@ ccdinit(ccd, cpaths, p) #ifdef DEBUG if (ccddebug & (CCDB_FOLLOW|CCDB_INIT)) - printf("ccdinit: unit %d\n", ccd->ccd_unit); + printf("ccdinit: unit %d cflags %b\n", + ccd->ccd_unit, ccd->ccd_flags, CCDF_BITS); #endif cs->sc_size = 0; @@ -442,42 +459,42 @@ ccdinit(ccd, cpaths, p) * If uniform interleave is desired set all sizes to that of * the smallest component. */ + ccg->ccg_ntracks = cs->sc_nccunits = cs->sc_nccdisks; if (ccd->ccd_flags & CCDF_UNIFORM) { for (ci = cs->sc_cinfo; ci < &cs->sc_cinfo[cs->sc_nccdisks]; ci++) ci->ci_size = minsize; if (ccd->ccd_flags & CCDF_MIRROR) - cs->sc_size = (cs->sc_nccdisks / 2) * minsize; - else - cs->sc_size = cs->sc_nccdisks * minsize; + cs->sc_nccunits = ccg->ccg_ntracks /= 2; + cs->sc_size = ccg->ccg_ntracks * minsize; } + cs->sc_cflags = ccd->ccd_flags; /* So we can find out later... */ + /* * Construct the interleave table. */ - ccdinterleave(cs, ccd->ccd_unit); + ccdinterleave(cs); /* * Create pseudo-geometry based on 1MB cylinders. It's * pretty close. */ ccg->ccg_secsize = DEV_BSIZE; - ccg->ccg_ntracks = 1; - ccg->ccg_nsectors = 1024 * (1024 / ccg->ccg_secsize); - ccg->ccg_ncylinders = cs->sc_size / ccg->ccg_nsectors; + ccg->ccg_nsectors = cs->sc_ileave? cs->sc_ileave : + 1024 * (1024 / ccg->ccg_secsize); + ccg->ccg_ncylinders = cs->sc_size / ccg->ccg_ntracks / + ccg->ccg_nsectors; cs->sc_flags |= CCDF_INITED; - cs->sc_cflags = ccd->ccd_flags; /* So we can find out later... */ - cs->sc_unit = ccd->ccd_unit; return (0); } void -ccdinterleave(cs, unit) +ccdinterleave(cs) struct ccd_softc *cs; - int unit; { struct ccdcinfo *ci, *smallci; struct ccdiinfo *ii; @@ -564,7 +581,7 @@ ccdinterleave(cs, unit) */ ix = 0; for (ci = cs->sc_cinfo; - ci < &cs->sc_cinfo[cs->sc_nccdisks]; ci++) + ci < &cs->sc_cinfo[cs->sc_nccunits]; ci++) if (ci->ci_size >= smallci->ci_size) ii->ii_index[ix++] = ci - cs->sc_cinfo; ii->ii_ndisk = ix; @@ -612,7 +629,7 @@ ccdopen(dev, flags, fmt, p) * the in-core disklabel. */ if ((cs->sc_flags & CCDF_INITED) && (cs->sc_dkdev.dk_openmask == 0)) - ccdgetdisklabel(dev); + ccdgetdisklabel(dev, cs, lp, cs->sc_dkdev.dk_cpulabel, 0); /* Check that the partition exists. */ if (part != RAW_PART) { @@ -746,11 +763,12 @@ ccdstart(cs, bp) caddr_t addr; daddr_t bn; struct partition *pp; - int i, old_io = ccddevs[cs->sc_unit].ccd_flags & CCDF_OLD; + int i, old_io = cs->sc_cflags & CCDF_OLD; #ifdef DEBUG if (ccddebug & CCDB_FOLLOW) - printf("ccdstart(%p, %p)\n", cs, bp); + printf("ccdstart(%p, %p, %s)\n", cs, bp, + bp->b_flags & B_READ? "read" : "write"); #endif /* Instrumentation. */ @@ -786,9 +804,6 @@ ccdstart(cs, bp) cbpp[0]->cb_buf.b_vp->v_numoutput++; VOP_STRATEGY(&cbpp[0]->cb_buf); - /* - * Mirror requires additional write. - */ if ((cs->sc_cflags & CCDF_MIRROR) && ((cbpp[0]->cb_buf.b_flags & B_READ) == 0)) { cbpp[1]->cb_buf.b_vp->v_numoutput++; @@ -839,8 +854,8 @@ ccdbuffer(cs, bp, bn, addr, bcount, cbpp, old_io) #ifdef DEBUG if (ccddebug & CCDB_IO) - printf("ccdbuffer(%p, %p, %d, %p, %ld)\n", cs, bp, bn, addr, - bcount); + printf("ccdbuffer(%p, %p, %d, %p, %ld, %p)\n", + cs, bp, bn, addr, bcount, cbpp); #endif /* @@ -874,22 +889,23 @@ ccdbuffer(cs, bp, bn, addr, bcount, cbpp, old_io) ccdisk = ii->ii_index[0]; cbn = ii->ii_startoff + off; } else { - if (cs->sc_cflags & CCDF_MIRROR) { - ccdisk = - ii->ii_index[off % (ii->ii_ndisk / 2)]; - cbn = ii->ii_startoff + - (off / (ii->ii_ndisk / 2)); - /* Mirrored data */ - ci2 = - &cs->sc_cinfo[ccdisk + (ii->ii_ndisk / 2)]; - } else { - /* Normal case. */ - ccdisk = ii->ii_index[off % ii->ii_ndisk]; - cbn = ii->ii_startoff + off / ii->ii_ndisk; - } + ccdisk = ii->ii_index[off % ii->ii_ndisk]; + cbn = ii->ii_startoff + off / ii->ii_ndisk; + } + if (cs->sc_cflags & CCDF_MIRROR) { + /* Mirrored data */ + ci2 = &cs->sc_cinfo[ccdisk + ii->ii_ndisk]; + /* spread the read over both parts */ + if (bp->b_flags & B_READ && cbn & 1) + ccdisk += ii->ii_ndisk; } cbn *= cs->sc_ileave; ci = &cs->sc_cinfo[ccdisk]; +#ifdef DEBUG + if (ccddebug & CCDB_IO) + printf("ccdisk %d cbn %d ci %p ci2 %p\n", + ccdisk, cbn, ci, ci2); +#endif } /* Limit the operation at next component border */ @@ -920,7 +936,7 @@ ccdbuffer(cs, bp, bn, addr, bcount, cbpp, old_io) * context for ccdiodone */ cbp->cb_obp = bp; - cbp->cb_unit = cs->sc_unit; + cbp->cb_sc = cs; cbp->cb_comp = ccdisk; /* Deal with the different algorithms */ @@ -957,7 +973,8 @@ ccdbuffer(cs, bp, bn, addr, bcount, cbpp, old_io) cbp->cb_buf.b_vp = ci2->ci_vp; LIST_INIT(&cbp->cb_buf.b_dep); cbp->cb_comp = ci2 - cs->sc_cinfo; - cbpp[1] = cbp; + cbp->cb_dep = cbpp[0]; + cbpp[0]->cb_dep = cbpp[1] = cbp; } } else { /* @@ -1029,8 +1046,7 @@ ccdiodone(vbp) { struct ccdbuf *cbp = (struct ccdbuf *)vbp; struct buf *bp = cbp->cb_obp; - int unit = cbp->cb_unit; - struct ccd_softc *cs = &ccd_softc[unit]; + struct ccd_softc *cs = cbp->cb_sc; int old_io = cbp->cb_flags & CBF_OLD; int cbflags, i; long count = bp->b_bcount, off; @@ -1054,6 +1070,7 @@ ccdiodone(vbp) #endif if (vbp->b_flags & B_ERROR) { + cs->sc_cinfo[cbp->cb_comp].ci_flags |= CCIF_FAILED; if (cbp->cb_flags & CBF_MIRROR) comptype = " (mirror)"; else { @@ -1066,7 +1083,7 @@ ccdiodone(vbp) printf("%s: error %d on component %d%s\n", cs->sc_xname, bp->b_error, cbp->cb_comp, comptype); } - cbflags = cbp->cb_flags; + cbflags = cbp->cb_flags |= CBF_DONE; if (!old_io) { /* @@ -1091,21 +1108,26 @@ ccdiodone(vbp) } } count = vbp->b_bcount; + + if (cbp->cb_dep && + (cbp->cb_dep->cb_flags & CBF_DONE) != (cbflags & CBF_DONE)) + return; + putccdbuf(cbp); + if (cbp->cb_dep) + putccdbuf(cbp->cb_dep); - if ((cbflags & CBF_MIRROR) == 0) { - /* - * If all done, "interrupt". - * - * Note that mirror component buffers aren't counted against - * the original I/O buffer. - */ - if (count > bp->b_resid) - panic("ccdiodone: count"); - bp->b_resid -= count; - if (bp->b_resid == 0) - ccdintr(&ccd_softc[unit], bp); - } + /* + * If all done, "interrupt". + * + * Note that mirror component buffers aren't counted against + * the original I/O buffer. + */ + if (count > bp->b_resid) + panic("ccdiodone: count"); + bp->b_resid -= count; + if (bp->b_resid == 0) + ccdintr(cs, bp); } /* ARGSUSED */ @@ -1186,18 +1208,28 @@ ccdioctl(dev, cmd, data, flag, p) if (unit >= numccd) return (ENXIO); + cs = &ccd_softc[unit]; + if (cmd != CCDIOCSET && !(cs->sc_flags & CCDF_INITED)) + return (ENXIO); - bzero(&ccd, sizeof(ccd)); + /* access control */ + switch (cmd) { + case CCDIOCSET: + case CCDIOCCLR: + case DIOCWDINFO: + case DIOCSDINFO: + case DIOCWLABEL: + if ((flag & FWRITE) == 0) + return (EBADF); + } + bzero(&ccd, sizeof(ccd)); switch (cmd) { case CCDIOCSET: if (cs->sc_flags & CCDF_INITED) return (EBUSY); - if ((flag & FWRITE) == 0) - return (EBADF); - if ((error = ccdlock(cs)) != 0) return (error); @@ -1206,7 +1238,7 @@ ccdioctl(dev, cmd, data, flag, p) ccd.ccd_interleave = ccio->ccio_ileave; ccd.ccd_flags = ccio->ccio_flags & CCDF_USERMASK; - /* XXX the new code is unstable still */ + /* new code seems to work now but enable it after a release */ ccd.ccd_flags |= CCDF_OLD; /* @@ -1307,19 +1339,13 @@ ccdioctl(dev, cmd, data, flag, p) disk_attach(&cs->sc_dkdev); /* Try and read the disklabel. */ - ccdgetdisklabel(dev); + ccdgetdisklabel(dev, cs, cs->sc_dkdev.dk_label, + cs->sc_dkdev.dk_cpulabel, 0); ccdunlock(cs); - break; case CCDIOCCLR: - if ((cs->sc_flags & CCDF_INITED) == 0) - return (ENXIO); - - if ((flag & FWRITE) == 0) - return (EBADF); - if ((error = ccdlock(cs)) != 0) return (error); @@ -1382,20 +1408,26 @@ ccdioctl(dev, cmd, data, flag, p) ccdunlock(cs); bzero(cs, sizeof(struct ccd_softc)); splx(s); + break; + + case DIOCGPDINFO: { + struct cpu_disklabel osdep; + + if ((error = ccdlock(cs)) != 0) + return (error); + + ccdgetdisklabel(dev, cs, (struct disklabel *)data, + &osdep, 1); + ccdunlock(cs); break; + } case DIOCGDINFO: - if ((cs->sc_flags & CCDF_INITED) == 0) - return (ENXIO); - *(struct disklabel *)data = *(cs->sc_dkdev.dk_label); break; case DIOCGPART: - if ((cs->sc_flags & CCDF_INITED) == 0) - return (ENXIO); - ((struct partinfo *)data)->disklab = cs->sc_dkdev.dk_label; ((struct partinfo *)data)->part = &cs->sc_dkdev.dk_label->d_partitions[DISKPART(dev)]; @@ -1403,12 +1435,6 @@ ccdioctl(dev, cmd, data, flag, p) case DIOCWDINFO: case DIOCSDINFO: - if ((cs->sc_flags & CCDF_INITED) == 0) - return (ENXIO); - - if ((flag & FWRITE) == 0) - return (EBADF); - if ((error = ccdlock(cs)) != 0) return (error); @@ -1432,11 +1458,6 @@ ccdioctl(dev, cmd, data, flag, p) break; case DIOCWLABEL: - if ((cs->sc_flags & CCDF_INITED) == 0) - return (ENXIO); - - if ((flag & FWRITE) == 0) - return (EBADF); if (*(int *)data != 0) cs->sc_flags |= CCDF_WLABEL; else @@ -1556,15 +1577,11 @@ ccdlookup(path, p, vpp) * up. */ void -ccdgetdisklabel(dev) - dev_t dev; +ccdgetdisklabel(dev_t dev, struct ccd_softc *cs, struct disklabel *lp, + struct cpu_disklabel *clp, int spoofonly) { - int unit = ccdunit(dev); - struct ccd_softc *cs = &ccd_softc[unit]; - char *errstring; - struct disklabel *lp = cs->sc_dkdev.dk_label; - struct cpu_disklabel *clp = cs->sc_dkdev.dk_cpulabel; struct ccdgeom *ccg = &cs->sc_geom; + char *errstring; bzero(lp, sizeof(*lp)); bzero(clp, sizeof(*clp)); @@ -1596,7 +1613,7 @@ ccdgetdisklabel(dev) * Call the generic disklabel extraction routine. */ errstring = readdisklabel(CCDLABELDEV(dev), ccdstrategy, - cs->sc_dkdev.dk_label, cs->sc_dkdev.dk_cpulabel, 0); + cs->sc_dkdev.dk_label, cs->sc_dkdev.dk_cpulabel, spoofonly); if (errstring) ccdmakedisklabel(cs); diff --git a/sys/dev/ccdvar.h b/sys/dev/ccdvar.h index c081f4a9915..d247e73cf9a 100644 --- a/sys/dev/ccdvar.h +++ b/sys/dev/ccdvar.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ccdvar.h,v 1.6 2003/06/02 23:28:01 millert Exp $ */ +/* $OpenBSD: ccdvar.h,v 1.7 2005/02/24 19:36:39 mickey Exp $ */ /* $NetBSD: ccdvar.h,v 1.11 1996/02/28 01:08:32 thorpej Exp $ */ /*- @@ -88,11 +88,11 @@ * A concatenated disk is described at initialization time by this structure. */ struct ccddevice { + struct vnode **ccd_vpp; /* array of component vnodes */ + char **ccd_cpp; /* array of component pathnames */ int ccd_unit; /* logical unit of this ccd */ int ccd_interleave; /* interleave (DEV_BSIZE blocks) */ int ccd_flags; /* misc. information */ - struct vnode **ccd_vpp; /* array of component vnodes */ - char **ccd_cpp; /* array of component pathnames */ int ccd_ndev; /* number of component devices */ }; @@ -113,9 +113,12 @@ struct ccd_ioctl { #define CCDF_UNIFORM 0x02 /* use LCCD of sizes for uniform interleave */ #define CCDF_MIRROR 0x04 /* enable data mirroring */ #define CCDF_OLD 0x08 /* use slower but less restrictive I/O code */ +#define CCDF_BITS \ + "\020\01swap\02uniform\03mirror\04old" /* Mask of user-settable ccd flags. */ -#define CCDF_USERMASK (CCDF_SWAP|CCDF_UNIFORM|CCDF_MIRROR|CCDF_OLD) +#define CCDF_USERMASK \ + (CCDF_SWAP|CCDF_UNIFORM|CCDF_MIRROR|CCDF_OLD) /* * Component info table. @@ -123,12 +126,15 @@ struct ccd_ioctl { */ struct ccdcinfo { struct vnode *ci_vp; /* device's vnode */ - dev_t ci_dev; /* XXX: device's dev_t */ - size_t ci_size; /* size */ char *ci_path; /* path to component */ + size_t ci_size; /* size */ size_t ci_pathlen; /* length of component path */ + dev_t ci_dev; /* XXX: device's dev_t */ + int ci_flags; /* see below */ }; +#define CCIF_FAILED 0x01 /* had a B_ERROR on this one */ + /* * Interleave description table. * Computed at boot time to speed irregular-interleave lookups. @@ -156,10 +162,11 @@ struct ccdcinfo { * 2 starting at offset 5. */ struct ccdiinfo { - int ii_ndisk; /* # of disks range is interleaved over */ daddr_t ii_startblk; /* starting scaled block # for range */ daddr_t ii_startoff; /* starting component offset (block #) */ int *ii_index; /* ordered list of components in range */ + int *ii_parity; /* list of parity shifts */ + int ii_ndisk; /* # of disks range is interleaved over */ }; /* @@ -176,18 +183,17 @@ struct ccdgeom { * A concatenated disk is described after initialization by this structure. */ struct ccd_softc { - int sc_unit; /* logical unit number */ - int sc_flags; /* flags */ - int sc_cflags; /* configuration flags */ - size_t sc_size; /* size of ccd */ - int sc_ileave; /* interleave */ -#define CCD_MAXNDISKS 65536 - u_int sc_nccdisks; /* number of components */ - struct ccdcinfo *sc_cinfo; /* component info */ - struct ccdiinfo *sc_itable; /* interleave table */ - struct ccdgeom sc_geom; /* pseudo geometry info */ - char sc_xname[8]; /* XXX external name */ - struct disk sc_dkdev; /* generic disk device info */ + struct disk sc_dkdev; /* generic disk device info */ + struct ccdgeom sc_geom; /* pseudo geometry info */ + struct ccdcinfo *sc_cinfo; /* component info */ + struct ccdiinfo *sc_itable; /* interleave table */ + char sc_xname[8]; /* XXX external name */ + size_t sc_size; /* size of ccd */ + int sc_flags; /* flags */ + int sc_cflags; /* copy of ccd_flags */ + int sc_ileave; /* interleave */ + u_int sc_nccdisks; /* # of components */ + u_int sc_nccunits; /* # of components for data */ }; /* sc_flags */ |