ref: d916a4c3823f55227ffae35738c2497256e307b5
dir: /os/pc/devi82365.c/
#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/error.h" #include "io.h" /* * Intel 82365SL PCIC controller and compatibles. */ enum { /* * registers indices */ Rid= 0x0, /* identification and revision */ Ris= 0x1, /* interface status */ Rpc= 0x2, /* power control */ Foutena= (1<<7), /* output enable */ Fautopower= (1<<5), /* automatic power switching */ Fcardena= (1<<4), /* PC card enable */ Rigc= 0x3, /* interrupt and general control */ Fiocard= (1<<5), /* I/O card (vs memory) */ Fnotreset= (1<<6), /* reset if not set */ FSMIena= (1<<4), /* enable change interrupt on SMI */ Rcsc= 0x4, /* card status change */ Rcscic= 0x5, /* card status change interrupt config */ Fchangeena= (1<<3), /* card changed */ Fbwarnena= (1<<1), /* card battery warning */ Fbdeadena= (1<<0), /* card battery dead */ Rwe= 0x6, /* address window enable */ Fmem16= (1<<5), /* use A23-A12 to decode address */ Rio= 0x7, /* I/O control */ Fwidth16= (1<<0), /* 16 bit data width */ Fiocs16= (1<<1), /* IOCS16 determines data width */ Fzerows= (1<<2), /* zero wait state */ Ftiming= (1<<3), /* timing register to use */ Riobtm0lo= 0x8, /* I/O address 0 start low byte */ Riobtm0hi= 0x9, /* I/O address 0 start high byte */ Riotop0lo= 0xa, /* I/O address 0 stop low byte */ Riotop0hi= 0xb, /* I/O address 0 stop high byte */ Riobtm1lo= 0xc, /* I/O address 1 start low byte */ Riobtm1hi= 0xd, /* I/O address 1 start high byte */ Riotop1lo= 0xe, /* I/O address 1 stop low byte */ Riotop1hi= 0xf, /* I/O address 1 stop high byte */ Rmap= 0x10, /* map 0 */ /* * CL-PD67xx extension registers */ Rmisc1= 0x16, /* misc control 1 */ F5Vdetect= (1<<0), Fvcc3V= (1<<1), Fpmint= (1<<2), Fpsirq= (1<<3), Fspeaker= (1<<4), Finpack= (1<<7), Rfifo= 0x17, /* fifo control */ Fflush= (1<<7), /* flush fifo */ Rmisc2= 0x1E, /* misc control 2 */ Flowpow= (1<<1), /* low power mode */ Rchipinfo= 0x1F, /* chip information */ Ratactl= 0x26, /* ATA control */ /* * offsets into the system memory address maps */ Mbtmlo= 0x0, /* System mem addr mapping start low byte */ Mbtmhi= 0x1, /* System mem addr mapping start high byte */ F16bit= (1<<7), /* 16-bit wide data path */ Mtoplo= 0x2, /* System mem addr mapping stop low byte */ Mtophi= 0x3, /* System mem addr mapping stop high byte */ Ftimer1= (1<<6), /* timer set 1 */ Mofflo= 0x4, /* Card memory offset address low byte */ Moffhi= 0x5, /* Card memory offset address high byte */ Fregactive= (1<<6), /* attribute memory */ /* * configuration registers - they start at an offset in attribute * memory found in the CIS. */ Rconfig= 0, Creset= (1<<7), /* reset device */ Clevel= (1<<6), /* level sensitive interrupt line */ Cirq= (1<<2), /* IRQ enable */ Cdecode= (1<<1), /* address decode */ Cfunc= (1<<0), /* function enable */ Riobase0= 5, Riobase1= 6, Riosize= 9, }; #define MAP(x,o) (Rmap + (x)*0x8 + o) typedef struct I82365 I82365; /* a controller */ enum { Ti82365, Tpd6710, Tpd6720, Tvg46x, }; struct I82365 { int type; int dev; int nslot; int xreg; /* index register address */ int dreg; /* data register address */ int irq; }; static I82365 *controller[4]; static int ncontroller; static PCMslot *slot; static PCMslot *lastslot; static nslot; static void i82365intr(Ureg*, void*); static int pcmio(int, ISAConf*); static long pcmread(int, int, void*, long, vlong); static long pcmwrite(int, int, void*, long, vlong); static void i82365dump(PCMslot*); /* * reading and writing card registers */ static uchar rdreg(PCMslot *pp, int index) { outb(((I82365*)pp->cp)->xreg, pp->base + index); return inb(((I82365*)pp->cp)->dreg); } static void wrreg(PCMslot *pp, int index, uchar val) { outb(((I82365*)pp->cp)->xreg, pp->base + index); outb(((I82365*)pp->cp)->dreg, val); } /* * get info about card */ static void slotinfo(PCMslot *pp) { uchar isr; isr = rdreg(pp, Ris); pp->occupied = (isr & (3<<2)) == (3<<2); pp->powered = isr & (1<<6); pp->battery = (isr & 3) == 3; pp->wrprot = isr & (1<<4); pp->busy = isr & (1<<5); pp->msec = TK2MS(MACHP(0)->ticks); } static int vcode(int volt) { switch(volt){ case 5: return 1; case 12: return 2; default: return 0; } } /* * enable the slot card */ static void slotena(PCMslot *pp) { if(pp->enabled) return; /* power up and unreset, wait's are empirical (???) */ wrreg(pp, Rpc, Fautopower|Foutena|Fcardena); delay(300); wrreg(pp, Rigc, 0); delay(100); wrreg(pp, Rigc, Fnotreset); delay(5000); /* get configuration */ slotinfo(pp); if(pp->occupied){ pcmcisread(pp); pp->enabled = 1; } else wrreg(pp, Rpc, Fautopower); } /* * disable the slot card */ static void slotdis(PCMslot *pp) { wrreg(pp, Rpc, 0); /* turn off card power */ wrreg(pp, Rwe, 0); /* no windows */ pp->enabled = 0; } /* * status change interrupt */ static void i82365intr(Ureg *, void *) { uchar csc, was; PCMslot *pp; if(slot == 0) return; for(pp = slot; pp < lastslot; pp++){ csc = rdreg(pp, Rcsc); was = pp->occupied; slotinfo(pp); if(csc & (1<<3) && was != pp->occupied){ if(!pp->occupied) slotdis(pp); } } } enum { Mshift= 12, Mgran= (1<<Mshift), /* granularity of maps */ Mmask= ~(Mgran-1), /* mask for address bits important to the chip */ }; /* * get a map for pc card region, return corrected len */ PCMmap* pcmmap(int slotno, ulong offset, int len, int attr) { PCMslot *pp; uchar we, bit; PCMmap *m, *nm; int i; ulong e; pp = slot + slotno; lock(&pp->mlock); /* convert offset to granularity */ if(len <= 0) len = 1; e = ROUND(offset+len, Mgran); offset &= Mmask; len = e - offset; /* look for a map that covers the right area */ we = rdreg(pp, Rwe); bit = 1; nm = 0; for(m = pp->mmap; m < &pp->mmap[nelem(pp->mmap)]; m++){ if((we & bit)) if(m->attr == attr) if(offset >= m->ca && e <= m->cea){ m->ref++; unlock(&pp->mlock); return m; } bit <<= 1; if(nm == 0 && m->ref == 0) nm = m; } m = nm; if(m == 0){ unlock(&pp->mlock); return 0; } /* if isa space isn't big enough, free it and get more */ if(m->len < len){ if(m->isa){ umbfree(m->isa, m->len); m->len = 0; } m->isa = PADDR(umbmalloc(0, len, Mgran)); if(m->isa == 0){ print("pcmmap: out of isa space\n"); unlock(&pp->mlock); return 0; } m->len = len; } /* set up new map */ m->ca = offset; m->cea = m->ca + m->len; m->attr = attr; i = m-pp->mmap; bit = 1<<i; wrreg(pp, Rwe, we & ~bit); /* disable map before changing it */ wrreg(pp, MAP(i, Mbtmlo), m->isa>>Mshift); wrreg(pp, MAP(i, Mbtmhi), (m->isa>>(Mshift+8)) | F16bit); wrreg(pp, MAP(i, Mtoplo), (m->isa+m->len-1)>>Mshift); wrreg(pp, MAP(i, Mtophi), ((m->isa+m->len-1)>>(Mshift+8))); offset -= m->isa; offset &= (1<<25)-1; offset >>= Mshift; wrreg(pp, MAP(i, Mofflo), offset); wrreg(pp, MAP(i, Moffhi), (offset>>8) | (attr ? Fregactive : 0)); wrreg(pp, Rwe, we | bit); /* enable map */ m->ref = 1; unlock(&pp->mlock); return m; } void pcmunmap(int slotno, PCMmap* m) { PCMslot *pp; pp = slot + slotno; lock(&pp->mlock); m->ref--; unlock(&pp->mlock); } static void increfp(PCMslot *pp) { lock(pp); if(pp->ref++ == 0) slotena(pp); unlock(pp); } static void decrefp(PCMslot *pp) { lock(pp); if(pp->ref-- == 1) slotdis(pp); unlock(pp); } /* * look for a card whose version contains 'idstr' */ static int pcmcia_pcmspecial(char *idstr, ISAConf *isa) { PCMslot *pp; extern char *strstr(char*, char*); int enabled; for(pp = slot; pp < lastslot; pp++){ if(pp->special) continue; /* already taken */ /* * make sure we don't power on cards when we already know what's * in them. We'll reread every two minutes if necessary */ enabled = 0; if (pp->msec == ~0 || TK2MS(MACHP(0)->ticks) - pp->msec > 120000){ increfp(pp); enabled++; } if(pp->occupied) { if(strstr(pp->verstr, idstr)){ if (!enabled){ enabled = 1; increfp(pp); } if(isa == 0 || pcmio(pp->slotno, isa) == 0){ pp->special = 1; return pp->slotno; } } } else pp->special = 1; if (enabled) decrefp(pp); } return -1; } static void pcmcia_pcmspecialclose(int slotno) { PCMslot *pp; if(slotno >= nslot) panic("pcmspecialclose"); pp = slot + slotno; pp->special = 0; decrefp(pp); } enum { Qdir, Qmem, Qattr, Qctl, Nents = 3, }; #define SLOTNO(c) ((ulong)((c->qid.path>>8)&0xff)) #define TYPE(c) ((ulong)(c->qid.path&0xff)) #define QID(s,t) (((s)<<8)|(t)) static int pcmgen(Chan *c, char*, Dirtab *, int , int i, Dir *dp) { int slotno; Qid qid; long len; PCMslot *pp; if(i == DEVDOTDOT){ mkqid(&qid, Qdir, 0, QTDIR); devdir(c, qid, "#y", 0, eve, 0555, dp); return 1; } if(i >= Nents*nslot) return -1; slotno = i/Nents; pp = slot + slotno; len = 0; switch(i%Nents){ case 0: qid.path = QID(slotno, Qmem); snprint(up->genbuf, sizeof up->genbuf, "pcm%dmem", slotno); len = pp->memlen; break; case 1: qid.path = QID(slotno, Qattr); snprint(up->genbuf, sizeof up->genbuf, "pcm%dattr", slotno); len = pp->memlen; break; case 2: qid.path = QID(slotno, Qctl); snprint(up->genbuf, sizeof up->genbuf, "pcm%dctl", slotno); break; } qid.vers = 0; qid.type = QTFILE; devdir(c, qid, up->genbuf, len, eve, 0660, dp); return 1; } static char *chipname[] = { [Ti82365] "Intel 82365SL", [Tpd6710] "Cirrus Logic CL-PD6710", [Tpd6720] "Cirrus Logic CL-PD6720", [Tvg46x] "Vadem VG-46x", }; static I82365* i82365probe(int x, int d, int dev) { uchar c, id; I82365 *cp; ISAConf isa; int i, nslot; outb(x, Rid + (dev<<7)); id = inb(d); if((id & 0xf0) != 0x80) return 0; /* not a memory & I/O card */ if((id & 0x0f) == 0x00) return 0; /* no revision number, not possible */ cp = xalloc(sizeof(I82365)); cp->xreg = x; cp->dreg = d; cp->dev = dev; cp->type = Ti82365; cp->nslot = 2; switch(id){ case 0x82: case 0x83: case 0x84: /* could be a cirrus */ outb(x, Rchipinfo + (dev<<7)); outb(d, 0); c = inb(d); if((c & 0xc0) != 0xc0) break; c = inb(d); if((c & 0xc0) != 0x00) break; if(c & 0x20){ cp->type = Tpd6720; } else { cp->type = Tpd6710; cp->nslot = 1; } /* low power mode */ outb(x, Rmisc2 + (dev<<7)); c = inb(d); outb(d, c & ~Flowpow); break; } /* if it's not a Cirrus, it could be a Vadem... */ if(cp->type == Ti82365){ /* unlock the Vadem extended regs */ outb(x, 0x0E + (dev<<7)); outb(x, 0x37 + (dev<<7)); /* make the id register show the Vadem id */ outb(x, 0x3A + (dev<<7)); c = inb(d); outb(d, c|0xC0); outb(x, Rid + (dev<<7)); c = inb(d); if(c & 0x08) cp->type = Tvg46x; /* go back to Intel compatible id */ outb(x, 0x3A + (dev<<7)); c = inb(d); outb(d, c & ~0xC0); } memset(&isa, 0, sizeof(ISAConf)); if(isaconfig("pcmcia", ncontroller, &isa) && isa.irq) cp->irq = isa.irq; else cp->irq = IrqPCMCIA; for(i = 0; i < isa.nopt; i++){ if(cistrncmp(isa.opt[i], "nslot=", 6)) continue; nslot = strtol(&isa.opt[i][6], nil, 0); if(nslot > 0 && nslot <= 2) cp->nslot = nslot; } controller[ncontroller++] = cp; return cp; } static void i82365dump(PCMslot *pp) { int i; for(i = 0; i < 0x40; i++){ if((i&0x0F) == 0) print("\n%2.2uX: ", i); print("%2.2uX ", rdreg(pp, i)); if(((i+1) & 0x0F) == 0x08) print(" - "); } print("\n"); } /* * set up for slot cards */ void devi82365link(void) { static int already; int i, j; I82365 *cp; PCMslot *pp; char buf[32], *p; if(already) return; already = 1; if((p=getconf("pcmcia0")) && strncmp(p, "disabled", 8)==0) return; if(_pcmspecial) return; /* look for controllers if the ports aren't already taken */ if(ioalloc(0x3E0, 2, 0, "i82365.0") >= 0){ i82365probe(0x3E0, 0x3E1, 0); i82365probe(0x3E0, 0x3E1, 1); if(ncontroller == 0) iofree(0x3E0); } if(ioalloc(0x3E2, 2, 0, "i82365.1") >= 0){ i = ncontroller; i82365probe(0x3E2, 0x3E3, 0); i82365probe(0x3E2, 0x3E3, 1); if(ncontroller == i) iofree(0x3E2); } if(ncontroller == 0) return; _pcmspecial = pcmcia_pcmspecial; _pcmspecialclose = pcmcia_pcmspecialclose; for(i = 0; i < ncontroller; i++) nslot += controller[i]->nslot; slot = xalloc(nslot * sizeof(PCMslot)); lastslot = slot; for(i = 0; i < ncontroller; i++){ cp = controller[i]; print("#y%d: %d slot %s: port 0x%uX irq %d\n", i, cp->nslot, chipname[cp->type], cp->xreg, cp->irq); for(j = 0; j < cp->nslot; j++){ pp = lastslot++; pp->slotno = pp - slot; pp->memlen = 64*MB; pp->base = (cp->dev<<7) | (j<<6); pp->cp = cp; pp->msec = ~0; pp->verstr[0] = 0; slotdis(pp); /* interrupt on status change */ wrreg(pp, Rcscic, (cp->irq<<4) | Fchangeena); rdreg(pp, Rcsc); } /* for card management interrupts */ snprint(buf, sizeof buf, "i82365.%d", i); intrenable(cp->irq, i82365intr, 0, BUSUNKNOWN, buf); } } static Chan* i82365attach(char *spec) { return devattach('y', spec); } static Walkqid* i82365walk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, 0, 0, pcmgen); } static int i82365stat(Chan *c, uchar *db, int n) { return devstat(c, db, n, 0, 0, pcmgen); } static Chan* i82365open(Chan *c, int omode) { if(c->qid.type & QTDIR){ if(omode != OREAD) error(Eperm); } else increfp(slot + SLOTNO(c)); c->mode = openmode(omode); c->flag |= COPEN; c->offset = 0; return c; } static void i82365close(Chan *c) { if(c->flag & COPEN) if((c->qid.type & QTDIR) == 0) decrefp(slot+SLOTNO(c)); } /* a memmove using only bytes */ static void memmoveb(uchar *to, uchar *from, int n) { while(n-- > 0) *to++ = *from++; } /* a memmove using only shorts & bytes */ static void memmoves(uchar *to, uchar *from, int n) { ushort *t, *f; if((((ulong)to) & 1) || (((ulong)from) & 1) || (n & 1)){ while(n-- > 0) *to++ = *from++; } else { n = n/2; t = (ushort*)to; f = (ushort*)from; while(n-- > 0) *t++ = *f++; } } static long pcmread(int slotno, int attr, void *a, long n, vlong off) { int i, len; PCMmap *m; uchar *ac; PCMslot *pp; ulong offset = off; pp = slot + slotno; if(pp->memlen < offset) return 0; if(pp->memlen < offset + n) n = pp->memlen - offset; m = 0; if(waserror()){ if(m) pcmunmap(pp->slotno, m); nexterror(); } ac = a; for(len = n; len > 0; len -= i){ m = pcmmap(pp->slotno, offset, 0, attr); if(m == 0) error("cannot map PCMCIA card"); if(offset + len > m->cea) i = m->cea - offset; else i = len; memmoveb(ac, KADDR(m->isa + offset - m->ca), i); pcmunmap(pp->slotno, m); offset += i; ac += i; } poperror(); return n; } static long i82365read(Chan *c, void *a, long n, vlong off) { char *p, *buf, *e; PCMslot *pp; ulong offset = off; switch(TYPE(c)){ case Qdir: return devdirread(c, a, n, 0, 0, pcmgen); case Qmem: case Qattr: return pcmread(SLOTNO(c), TYPE(c) == Qattr, a, n, off); case Qctl: buf = p = malloc(READSTR); e = p + READSTR; pp = slot + SLOTNO(c); buf[0] = 0; if(pp->occupied){ p = seprint(p, e, "occupied\n"); if(pp->verstr[0]) p = seprint(p, e, "version %s\n", pp->verstr); } if(pp->enabled) p = seprint(p, e, "enabled\n"); if(pp->powered) p = seprint(p, e, "powered\n"); if(pp->configed) p = seprint(p, e, "configed\n"); if(pp->wrprot) p = seprint(p, e, "write protected\n"); if(pp->busy) p = seprint(p, e, "busy\n"); seprint(p, e, "battery lvl %d\n", pp->battery); n = readstr(offset, a, n, buf); free(buf); return n; } error(Ebadarg); return -1; /* not reached */ } static long pcmwrite(int dev, int attr, void *a, long n, vlong off) { int i, len; PCMmap *m; uchar *ac; PCMslot *pp; ulong offset = off; pp = slot + dev; if(pp->memlen < offset) return 0; if(pp->memlen < offset + n) n = pp->memlen - offset; m = 0; if(waserror()){ if(m) pcmunmap(pp->slotno, m); nexterror(); } ac = a; for(len = n; len > 0; len -= i){ m = pcmmap(pp->slotno, offset, 0, attr); if(m == 0) error("cannot map PCMCIA card"); if(offset + len > m->cea) i = m->cea - offset; else i = len; memmoveb(KADDR(m->isa + offset - m->ca), ac, i); pcmunmap(pp->slotno, m); offset += i; ac += i; } poperror(); return n; } static long i82365write(Chan *c, void *a, long n, vlong off) { PCMslot *pp; char buf[32]; switch(TYPE(c)){ case Qctl: if(n >= sizeof(buf)) n = sizeof(buf) - 1; strncpy(buf, a, n); buf[n] = 0; pp = slot + SLOTNO(c); if(!pp->occupied) error(Eio); /* set vpp on card */ if(strncmp(buf, "vpp", 3) == 0) wrreg(pp, Rpc, vcode(atoi(buf+3))|Fautopower|Foutena|Fcardena); return n; case Qmem: case Qattr: pp = slot + SLOTNO(c); if(pp->occupied == 0 || pp->enabled == 0) error(Eio); n = pcmwrite(pp->slotno, TYPE(c) == Qattr, a, n, off); if(n < 0) error(Eio); return n; } error(Ebadarg); return -1; /* not reached */ } Dev i82365devtab = { 'y', "i82365", devreset, devinit, devshutdown, i82365attach, i82365walk, i82365stat, i82365open, devcreate, i82365close, i82365read, devbread, i82365write, devbwrite, devremove, devwstat, }; /* * configure the PCMslot for IO. We assume very heavily that we can read * configuration info from the CIS. If not, we won't set up correctly. */ static int pcmio(int slotno, ISAConf *isa) { uchar we, x, *p; PCMslot *pp; PCMconftab *ct, *et, *t; PCMmap *m; int i, index, irq; char *cp; irq = isa->irq; if(irq == 2) irq = 9; if(slotno > nslot) return -1; pp = slot + slotno; if(!pp->occupied) return -1; et = &pp->ctab[pp->nctab]; ct = 0; for(i = 0; i < isa->nopt; i++){ if(strncmp(isa->opt[i], "index=", 6)) continue; index = strtol(&isa->opt[i][6], &cp, 0); if(cp == &isa->opt[i][6] || index >= pp->nctab) return -1; ct = &pp->ctab[index]; } if(ct == 0){ /* assume default is right */ if(pp->def) ct = pp->def; else ct = pp->ctab; /* try for best match */ if(ct->nio == 0 || ct->io[0].start != isa->port || ((1<<irq) & ct->irqs) == 0){ for(t = pp->ctab; t < et; t++) if(t->nio && t->io[0].start == isa->port && ((1<<irq) & t->irqs)){ ct = t; break; } } if(ct->nio == 0 || ((1<<irq) & ct->irqs) == 0){ for(t = pp->ctab; t < et; t++) if(t->nio && ((1<<irq) & t->irqs)){ ct = t; break; } } if(ct->nio == 0){ for(t = pp->ctab; t < et; t++) if(t->nio){ ct = t; break; } } } if(ct == et || ct->nio == 0) return -1; if(isa->port == 0 && ct->io[0].start == 0) return -1; /* route interrupts */ isa->irq = irq; wrreg(pp, Rigc, irq | Fnotreset | Fiocard); /* set power and enable device */ x = vcode(ct->vpp1); wrreg(pp, Rpc, x|Fautopower|Foutena|Fcardena); /* 16-bit data path */ if(ct->bit16) x = Ftiming|Fiocs16|Fwidth16; else x = Ftiming; if(ct->nio == 2 && ct->io[1].start) x |= x<<4; wrreg(pp, Rio, x); /* * enable io port map 0 * the 'top' register value includes the last valid address */ if(isa->port == 0) isa->port = ct->io[0].start; we = rdreg(pp, Rwe); wrreg(pp, Riobtm0lo, isa->port); wrreg(pp, Riobtm0hi, isa->port>>8); i = isa->port+ct->io[0].len-1; wrreg(pp, Riotop0lo, i); wrreg(pp, Riotop0hi, i>>8); we |= 1<<6; if(ct->nio >= 2 && ct->io[1].start){ wrreg(pp, Riobtm1lo, ct->io[1].start); wrreg(pp, Riobtm1hi, ct->io[1].start>>8); i = ct->io[1].start+ct->io[1].len-1; wrreg(pp, Riotop1lo, i); wrreg(pp, Riotop1hi, i>>8); we |= 1<<7; } wrreg(pp, Rwe, we); /* only touch Rconfig if it is present */ m = pcmmap(slotno, pp->cfg[0].caddr + Rconfig, 0x20, 1); p = KADDR(m->isa + pp->cfg[0].caddr - m->ca); if(pp->cfg[0].cpresent & (1<<Rconfig)){ /* Reset adapter */ /* set configuration and interrupt type. * if level is possible on the card, use it. */ x = ct->index; if(ct->irqtype & 0x20) x |= Clevel; /* enable the device, enable address decode and * irq enable. */ x |= Cfunc|Cdecode|Cirq; p[0] = x; //delay(5); microdelay(40); } if(pp->cfg[0].cpresent & (1<<Riobase0)){ /* set up the iobase 0 */ p[Riobase0 << 1] = isa->port; p[Riobase1 << 1] = isa->port >> 8; } if(pp->cfg[0].cpresent & (1<<Riosize)) p[Riosize << 1] = ct->io[0].len; pcmunmap(slotno, m); return 0; }