Annotation of lucent/sys/src/9/pc/devether.c, revision 1.1

1.1     ! root        1: #include "u.h"
        !             2: #include "../port/lib.h"
        !             3: #include "mem.h"
        !             4: #include "dat.h"
        !             5: #include "fns.h"
        !             6: #include "../port/error.h"
        !             7: #include "io.h"
        !             8: #include "devtab.h"
        !             9: 
        !            10: #include "ether.h"
        !            11: 
        !            12: /*
        !            13:  * Half-arsed attempt at a general top-level
        !            14:  * ethernet driver. Needs work:
        !            15:  *     handle multiple controllers
        !            16:  *     much tidying
        !            17:  *     set ethernet address
        !            18:  *     need a ctl file passed down to card drivers
        !            19:  *       so we can set options.
        !            20:  */
        !            21: 
        !            22: struct Ctlr *softctlr;
        !            23: 
        !            24: Chan*
        !            25: etherclone(Chan *c, Chan *nc)
        !            26: {
        !            27:        return devclone(c, nc);
        !            28: }
        !            29: 
        !            30: int
        !            31: etherwalk(Chan *c, char *name)
        !            32: {
        !            33:        return netwalk(c, name, &softctlr->net);
        !            34: }
        !            35: 
        !            36: void
        !            37: etherstat(Chan *c, char *dp)
        !            38: {
        !            39:        netstat(c, dp, &softctlr->net);
        !            40: }
        !            41: 
        !            42: Chan*
        !            43: etheropen(Chan *c, int omode)
        !            44: {
        !            45:        return netopen(c, omode, &softctlr->net);
        !            46: }
        !            47: 
        !            48: void
        !            49: ethercreate(Chan *c, char *name, int omode, ulong perm)
        !            50: {
        !            51:        USED(c, name, omode, perm);
        !            52:        error(Eperm);
        !            53: }
        !            54: 
        !            55: void
        !            56: etherclose(Chan *c)
        !            57: {
        !            58:        if(c->stream)
        !            59:                streamclose(c);
        !            60: }
        !            61: 
        !            62: long
        !            63: etherread(Chan *c, void *a, long n, ulong offset)
        !            64: {
        !            65:        return netread(c, a, n, offset, &softctlr->net);
        !            66: }
        !            67: 
        !            68: long
        !            69: etherwrite(Chan *c, char *a, long n, ulong offset)
        !            70: {
        !            71:        USED(offset);
        !            72:        return streamwrite(c, a, n, 0);
        !            73: }
        !            74: 
        !            75: void
        !            76: etherremove(Chan *c)
        !            77: {
        !            78:        USED(c);
        !            79:        error(Eperm);
        !            80: }
        !            81: 
        !            82: void
        !            83: etherwstat(Chan *c, char *dp)
        !            84: {
        !            85:        netwstat(c, dp, &softctlr->net);
        !            86: }
        !            87: 
        !            88: static int
        !            89: isobuf(void *arg)
        !            90: {
        !            91:        Ctlr *ctlr = arg;
        !            92: 
        !            93:        return ctlr->tb[ctlr->th].owner == Host;
        !            94: }
        !            95: 
        !            96: static void
        !            97: etheroput(Queue *q, Block *bp)
        !            98: {
        !            99:        Ctlr *ctlr;
        !           100:        Type *type;
        !           101:        Etherpkt *pkt;
        !           102:        RingBuf *ring;
        !           103:        int len, n, s;
        !           104:        Block *nbp;
        !           105:        uchar ea[6];
        !           106:        char *err;
        !           107: 
        !           108:        type = q->ptr;
        !           109:        ctlr = type->ctlr;
        !           110:        if(bp->type == M_CTL){
        !           111:                err = 0;
        !           112:                qlock(ctlr);
        !           113:                if(streamparse("connect", bp)){
        !           114:                        if(type->type == -1)
        !           115:                                ctlr->all--;
        !           116:                        type->type = strtol((char*)bp->rptr, 0, 0);
        !           117:                        if(type->type == -1)
        !           118:                                ctlr->all++;
        !           119:                }
        !           120:                else if(streamparse("promiscuous", bp)) {
        !           121:                        if(type->prom)
        !           122:                                goto ctlout;
        !           123:                        if(type->filter){
        !           124:                                err = "address already set";
        !           125:                                goto ctlout;
        !           126:                        }
        !           127:                        type->prom = 1;
        !           128:                        ctlr->prom++;
        !           129:                        if(ctlr->prom+ctlr->filter == 1)
        !           130:                                (*ctlr->card.mode)(ctlr, 1);
        !           131:                }
        !           132:                else if(streamparse("address", bp)) {
        !           133:                        if(type->prom){
        !           134:                                err = "already promiscuous";
        !           135:                                goto ctlout;
        !           136:                        }
        !           137:                        if(parseether(ea, bp->rptr) < 0){
        !           138:                                err = "bad ether address";
        !           139:                                goto ctlout;
        !           140:                        }
        !           141:                        memmove(type->ea, ea, sizeof(ea));
        !           142:                        if(!type->filter){
        !           143:                                type->filter = 1;
        !           144:                                ctlr->filter++;
        !           145:                                if(ctlr->filter == 1)
        !           146:                                        (*ctlr->card.mode)(ctlr, 3);
        !           147:                        }
        !           148:                }
        !           149:        ctlout:
        !           150:                qunlock(ctlr);
        !           151:                freeb(bp);
        !           152:                if(err)
        !           153:                        error(err);
        !           154:                return;
        !           155:        }
        !           156: 
        !           157:        /*
        !           158:         * Give packet a local address, return upstream if destined for
        !           159:         * this machine.
        !           160:         */
        !           161:        if(BLEN(bp) < ETHERHDRSIZE && (bp = pullup(bp, ETHERHDRSIZE)) == 0)
        !           162:                return;
        !           163:        pkt = (Etherpkt*)bp->rptr;
        !           164:        memmove(pkt->s, type->ea, sizeof(type->ea));
        !           165:        if(memcmp(ctlr->ea, pkt->d, sizeof(ctlr->ea)) == 0){
        !           166:                len = blen(bp);
        !           167:                if(bp = expandb(bp, len >= ETHERMINTU ? len: ETHERMINTU)){
        !           168:                        putq(&ctlr->lbq, bp);
        !           169:                        wakeup(&ctlr->rr);
        !           170:                }
        !           171:                return;
        !           172:        }
        !           173:        if(memcmp(ctlr->ba, pkt->d, sizeof(ctlr->ba)) == 0 || ctlr->prom || ctlr->all){
        !           174:                len = blen(bp);
        !           175:                nbp = copyb(bp, len);
        !           176:                if(nbp = expandb(nbp, len >= ETHERMINTU ? len: ETHERMINTU)){
        !           177:                        nbp->wptr = nbp->rptr+len;
        !           178:                        putq(&ctlr->lbq, nbp);
        !           179:                        wakeup(&ctlr->rr);
        !           180:                }
        !           181:        }
        !           182: 
        !           183:        /*
        !           184:         * Only one transmitter at a time.
        !           185:         */
        !           186:        qlock(&ctlr->tlock);
        !           187:        if(waserror()){
        !           188:                qunlock(&ctlr->tlock);
        !           189:                freeb(bp);
        !           190:                nexterror();
        !           191:        }
        !           192: 
        !           193:        /*
        !           194:         * Wait till we get an output buffer.
        !           195:         * should try to restart.
        !           196:         */
        !           197:        if(isobuf(ctlr) == 0){
        !           198:                tsleep(&ctlr->tr, isobuf, ctlr, 3*1000);
        !           199:                if(isobuf(ctlr) == 0){
        !           200:                        qunlock(&ctlr->tlock);
        !           201:                        freeb(bp);
        !           202:                        poperror();
        !           203:                        return;
        !           204:                }
        !           205:        }
        !           206: 
        !           207:        ring = &ctlr->tb[ctlr->th];
        !           208: 
        !           209:        /*
        !           210:         * Copy message into buffer.
        !           211:         */
        !           212:        len = 0;
        !           213:        for(nbp = bp; nbp; nbp = nbp->next){
        !           214:                if(sizeof(Etherpkt) - len >= (n = BLEN(nbp))){
        !           215:                        memmove(ring->pkt+len, nbp->rptr, n);
        !           216:                        len += n;
        !           217:                }
        !           218:                if(bp->flags & S_DELIM)
        !           219:                        break;
        !           220:        }
        !           221: 
        !           222:        /*
        !           223:         * Pad the packet (zero the pad).
        !           224:         */
        !           225:        if(len < ETHERMINTU){
        !           226:                memset(ring->pkt+len, 0, ETHERMINTU-len);
        !           227:                len = ETHERMINTU;
        !           228:        }
        !           229: 
        !           230:        /*
        !           231:         * Set up the transmit buffer and 
        !           232:         * start the transmission.
        !           233:         */
        !           234:        s = splhi();
        !           235:        ring->len = len;
        !           236:        ring->owner = Interface;
        !           237:        ctlr->th = NEXT(ctlr->th, ctlr->ntb);
        !           238:        (*ctlr->card.transmit)(ctlr);
        !           239:        ctlr->outpackets++;
        !           240:        splx(s);
        !           241: 
        !           242:        qunlock(&ctlr->tlock);
        !           243:        freeb(bp);
        !           244:        poperror();
        !           245: }
        !           246: 
        !           247: /*
        !           248:  * Open an ether line discipline.
        !           249:  */
        !           250: static void
        !           251: etherstopen(Queue *q, Stream *s)
        !           252: {
        !           253:        Ctlr *ctlr = softctlr;
        !           254:        Type *type;
        !           255: 
        !           256:        type = &ctlr->type[s->id];
        !           257:        RD(q)->ptr = WR(q)->ptr = type;
        !           258:        type->type = 0;
        !           259:        type->q = RD(q);
        !           260:        type->inuse = 1;
        !           261:        memmove(type->ea, ctlr->ea, sizeof(type->ea));
        !           262:        type->ctlr = ctlr;
        !           263: }
        !           264: 
        !           265: /*
        !           266:  * Close ether line discipline.
        !           267:  *
        !           268:  * The locking is to synchronize changing the ethertype with
        !           269:  * sending packets up the stream on interrupts.
        !           270:  */
        !           271: static int
        !           272: isclosed(void *arg)
        !           273: {
        !           274:        return ((Type*)arg)->q == 0;
        !           275: }
        !           276: 
        !           277: static void
        !           278: etherstclose(Queue *q)
        !           279: {
        !           280:        Type *type = (Type*)(q->ptr);
        !           281:        Ctlr *ctlr = type->ctlr;
        !           282: 
        !           283:        if(type->prom){
        !           284:                qlock(ctlr);
        !           285:                ctlr->prom--;
        !           286:                if(ctlr->prom+ctlr->filter == 0)
        !           287:                        (*ctlr->card.mode)(ctlr, 0);
        !           288:                qunlock(ctlr);
        !           289:        }
        !           290:        if(type->filter){
        !           291:                qlock(ctlr);
        !           292:                ctlr->filter--;
        !           293:                if(ctlr->filter == 0)
        !           294:                        (*ctlr->card.mode)(ctlr, ctlr->prom ? 1 : 0);
        !           295:                qunlock(ctlr);
        !           296:        }
        !           297:        if(type->type == -1){
        !           298:                qlock(ctlr);
        !           299:                ctlr->all--;
        !           300:                qunlock(ctlr);
        !           301:        }
        !           302: 
        !           303:        /*
        !           304:         * Mark as closing and wait for kproc
        !           305:         * to close us.
        !           306:         */
        !           307:        lock(&ctlr->clock);
        !           308:        type->clist = ctlr->clist;
        !           309:        ctlr->clist = type;
        !           310:        unlock(&ctlr->clock);
        !           311:        wakeup(&ctlr->rr);
        !           312:        sleep(&type->cr, isclosed, type);
        !           313: 
        !           314:        type->type = 0;
        !           315:        type->prom = 0;
        !           316:        type->filter = 0;
        !           317:        type->inuse = 0;
        !           318:        netdisown(type);
        !           319:        type->ctlr = 0;
        !           320: }
        !           321: 
        !           322: static Qinfo info = {
        !           323:        nullput,
        !           324:        etheroput,
        !           325:        etherstopen,
        !           326:        etherstclose,
        !           327:        "ether"
        !           328: };
        !           329: 
        !           330: static int
        !           331: clonecon(Chan *c)
        !           332: {
        !           333:        Ctlr *ctlr = softctlr;
        !           334:        Type *type;
        !           335: 
        !           336:        USED(c);
        !           337:        for(type = ctlr->type; type < &ctlr->type[NType]; type++){
        !           338:                qlock(type);
        !           339:                if(type->inuse || type->q){
        !           340:                        qunlock(type);
        !           341:                        continue;
        !           342:                }
        !           343:                type->inuse = 1;
        !           344:                memmove(type->ea, ctlr->ea, sizeof(type->ea));
        !           345:                netown(type, u->p->user, 0);
        !           346:                qunlock(type);
        !           347:                return type - ctlr->type;
        !           348:        }
        !           349:        exhausted("ether channels");
        !           350:        return 0;
        !           351: }
        !           352: 
        !           353: static void
        !           354: statsfill(Chan *c, char *p, int n)
        !           355: {
        !           356:        Ctlr *ctlr = softctlr;
        !           357:        char buf[256];
        !           358: 
        !           359:        USED(c);
        !           360:        sprint(buf, "in: %d\nout: %d\ncrc errs %d\noverflows: %d\nframe errs %d\nbuff errs: %d\noerrs %d\naddr: %.02x:%.02x:%.02x:%.02x:%.02x:%.02x\n",
        !           361:                ctlr->inpackets, ctlr->outpackets, ctlr->crcs,
        !           362:                ctlr->overflows, ctlr->frames, ctlr->buffs, ctlr->oerrs,
        !           363:                ctlr->ea[0], ctlr->ea[1], ctlr->ea[2],
        !           364:                ctlr->ea[3], ctlr->ea[4], ctlr->ea[5]);
        !           365:        strncpy(p, buf, n);
        !           366: }
        !           367: 
        !           368: static void
        !           369: typefill(Chan *c, char *p, int n)
        !           370: {
        !           371:        char buf[16];
        !           372:        Type *type;
        !           373: 
        !           374:        type = &softctlr->type[STREAMID(c->qid.path)];
        !           375:        sprint(buf, "%d", type->type);
        !           376:        strncpy(p, buf, n);
        !           377: }
        !           378: 
        !           379: int
        !           380: eaddrmatch(Ctlr *ctlr, uchar *ea)
        !           381: {
        !           382:        Type *type;
        !           383: 
        !           384:        for(type = &ctlr->type[0]; type < &ctlr->type[NType]; type++){
        !           385:                if(type->q == 0 || ea[0] != type->ea[0])
        !           386:                        continue;
        !           387:                if(memcmp(ea, type->ea, sizeof(type->ea)) == 0)
        !           388:                        return 1;
        !           389:        }
        !           390:        return 0;
        !           391: }
        !           392: 
        !           393: static void
        !           394: etherup(Ctlr *ctlr, Etherpkt *pkt, int len)
        !           395: {
        !           396:        int t;
        !           397:        Type *type;
        !           398:        Block *bp;
        !           399: 
        !           400:        t = (pkt->type[0]<<8)|pkt->type[1];
        !           401:        for(type = &ctlr->type[0]; type < &ctlr->type[NType]; type++){
        !           402: 
        !           403:                /*
        !           404:                 * Check for open, the right type, and flow control.
        !           405:                 */
        !           406:                if(type->q == 0)
        !           407:                        continue;
        !           408:                if(t != type->type && type->type >= 0)
        !           409:                        continue;
        !           410:                if(type->q->next->len > Streamhi)
        !           411:                        continue;
        !           412: 
        !           413:                /*
        !           414:                 * Only a trace channel gets packets destined for other machines.
        !           415:                 */
        !           416:                if(type->type != -1 && pkt->d[0] != 0xFF
        !           417:                  && (*pkt->d != *type->ea || memcmp(pkt->d, type->ea, sizeof(pkt->d))))
        !           418:                        continue;
        !           419: 
        !           420:                if(waserror() == 0){
        !           421:                        bp = allocb(len);
        !           422:                        memmove(bp->rptr, pkt, len);
        !           423:                        bp->wptr += len;
        !           424:                        bp->flags |= S_DELIM;
        !           425:                        PUTNEXT(type->q, bp);
        !           426:                        poperror();
        !           427:                }
        !           428:        }
        !           429: }
        !           430: 
        !           431: static int
        !           432: isinput(void *arg)
        !           433: {
        !           434:        Ctlr *ctlr = arg;
        !           435: 
        !           436:        return ctlr->lbq.first || ctlr->rb[ctlr->rh].owner == Host || ctlr->clist;
        !           437: }
        !           438: 
        !           439: static void
        !           440: etherkproc(void *arg)
        !           441: {
        !           442:        Ctlr *ctlr = arg;
        !           443:        RingBuf *ring;
        !           444:        Block *bp;
        !           445:        Type *type;
        !           446: 
        !           447:        if(waserror()){
        !           448:                print("%s noted\n", ctlr->name);
        !           449:                /* fix
        !           450:                if(ctlr->card.reset)
        !           451:                        (*ctlr->card.reset)(ctlr);
        !           452:                 */
        !           453:                ctlr->kproc = 0;
        !           454:                nexterror();
        !           455:        }
        !           456: 
        !           457:        for(;;){
        !           458:                tsleep(&ctlr->rr, isinput, ctlr, 500);
        !           459:                if(ctlr->card.watch)
        !           460:                        (*ctlr->card.watch)(ctlr);
        !           461: 
        !           462:                /*
        !           463:                 * Process any internal loopback packets.
        !           464:                 */
        !           465:                while(bp = getq(&ctlr->lbq)){
        !           466:                        ctlr->inpackets++;
        !           467:                        etherup(ctlr, (Etherpkt*)bp->rptr, BLEN(bp));
        !           468:                        freeb(bp);
        !           469:                }
        !           470: 
        !           471:                /*
        !           472:                 * Process any received packets.
        !           473:                 */
        !           474:                while(ctlr->rb[ctlr->rh].owner == Host){
        !           475:                        ctlr->inpackets++;
        !           476:                        ring = &ctlr->rb[ctlr->rh];
        !           477:                        etherup(ctlr, (Etherpkt*)ring->pkt, ring->len);
        !           478:                        ring->owner = Interface;
        !           479:                        ctlr->rh = NEXT(ctlr->rh, ctlr->nrb);
        !           480:                }
        !           481: 
        !           482:                /*
        !           483:                 * Close Types requesting it.
        !           484:                 */
        !           485:                if(ctlr->clist){
        !           486:                        lock(&ctlr->clock);
        !           487:                        for(type = ctlr->clist; type; type = type->clist){
        !           488:                                type->q = 0;
        !           489:                                wakeup(&type->cr);
        !           490:                        }
        !           491:                        ctlr->clist = 0;
        !           492:                        unlock(&ctlr->clock);
        !           493:                }
        !           494:        }
        !           495: }
        !           496: 
        !           497: static void
        !           498: etherintr(Ureg *ur, void *a)
        !           499: {
        !           500:        Ctlr *ctlr = softctlr;
        !           501: 
        !           502:        USED(ur, a);
        !           503:        (*ctlr->card.intr)(ctlr);
        !           504: }
        !           505: 
        !           506: static void
        !           507: reset(Ctlr *ctlr)
        !           508: {
        !           509:        int i;
        !           510: 
        !           511:        if(ctlr->nrb == 0)
        !           512:                ctlr->nrb = Nrb;
        !           513:        ctlr->rb = xalloc(sizeof(RingBuf)*ctlr->nrb);
        !           514:        if(ctlr->ntb == 0)
        !           515:                ctlr->ntb = Ntb;
        !           516:        ctlr->tb = xalloc(sizeof(RingBuf)*ctlr->ntb);
        !           517: 
        !           518:        memset(ctlr->ba, 0xFF, sizeof(ctlr->ba));
        !           519: 
        !           520:        ctlr->net.name = "ether";
        !           521:        ctlr->net.nconv = NType;
        !           522:        ctlr->net.devp = &info;
        !           523:        ctlr->net.protop = 0;
        !           524:        ctlr->net.listen = 0;
        !           525:        ctlr->net.clone = clonecon;
        !           526:        ctlr->net.ninfo = 2;
        !           527:        ctlr->net.info[0].name = "stats";
        !           528:        ctlr->net.info[0].fill = statsfill;
        !           529:        ctlr->net.info[1].name = "type";
        !           530:        ctlr->net.info[1].fill = typefill;
        !           531:        for(i = 0; i < NType; i++)
        !           532:                netadd(&ctlr->net, &ctlr->type[i], i);
        !           533: }
        !           534: 
        !           535: extern int wd8003reset(Ctlr*);
        !           536: extern int ne2000reset(Ctlr*);
        !           537: extern int ccc509reset(Ctlr*);
        !           538: extern int nsciareset(Ctlr*);
        !           539: extern int ne2000PCMreset(Ctlr*);
        !           540: 
        !           541: #define NCARD 32
        !           542: struct {
        !           543:        char    *type;
        !           544:        int     (*reset)(Ctlr*);
        !           545: } cards[NCARD+1];
        !           546: 
        !           547: void
        !           548: addethercard(char *t, int (*r)(Ctlr*))
        !           549: {
        !           550:        static int ncard;
        !           551: 
        !           552:        if(ncard == NCARD)
        !           553:                panic("too many ether cards");
        !           554:        cards[ncard].type = t;
        !           555:        cards[ncard].reset = r;
        !           556:        ncard++;
        !           557: }
        !           558: 
        !           559: void
        !           560: etherreset(void)
        !           561: {
        !           562:        Ctlr *ctlr;
        !           563:        int i, n, ctlrno;
        !           564: 
        !           565:        if(softctlr == 0)
        !           566:                softctlr = xalloc(sizeof(Ctlr));
        !           567:        else
        !           568:                memset(softctlr, 0, sizeof(Ctlr));
        !           569:        ctlr = softctlr;
        !           570: 
        !           571:        for(ctlrno = i = 0; isaconfig("ether", i, &ctlr->card); i++){
        !           572:                for(n = 0; cards[n].type; n++){
        !           573:                        if(strcmp(cards[n].type, ctlr->card.type))
        !           574:                                continue;
        !           575:                        ctlr->ctlrno = ctlrno;
        !           576:                        memmove(ctlr->ea, ctlr->card.ea, sizeof(ctlr->ea));
        !           577:                        if((*cards[n].reset)(ctlr))
        !           578:                                break;
        !           579: 
        !           580:                        /*ctlrno++;*/
        !           581:                        ctlr->present = 1;
        !           582:                        /*
        !           583:                         * IRQ2 doesn't really exist, it's used to gang the interrupt
        !           584:                         * controllers together. A device set to IRQ2 will appear on
        !           585:                         * the second interrupt controller as IRQ9.
        !           586:                         */
        !           587:                        if(ctlr->card.irq == 2)
        !           588:                                ctlr->card.irq = 9;
        !           589:                        setvec(Int0vec + ctlr->card.irq, etherintr, 0);
        !           590: 
        !           591:                        print("ether%d:%s: port %lux irq %d addr %lux size %d width %d:",
        !           592:                                ctlr->ctlrno, ctlr->card.type, ctlr->card.port, ctlr->card.irq,
        !           593:                                ctlr->card.mem, ctlr->card.size, ctlr->card.bit16 ? 16: 8);
        !           594:                        for(i = 0; i < sizeof(ctlr->ea); i++)
        !           595:                                print("%2.2ux", ctlr->ea[i]);
        !           596:                        print("\n");
        !           597: 
        !           598:                        reset(ctlr);
        !           599:                        return;
        !           600:                }
        !           601:                memset(softctlr, 0, sizeof(Ctlr));
        !           602:        }
        !           603: }
        !           604: 
        !           605: void
        !           606: etherinit(void)
        !           607: {
        !           608:        Ctlr *ctlr = softctlr;
        !           609:        int i;
        !           610: 
        !           611:        if(ctlr->present == 0)
        !           612:                return;
        !           613: 
        !           614:        ctlr->rh = 0;
        !           615:        ctlr->ri = 0;
        !           616:        for(i = 0; i < ctlr->nrb; i++)
        !           617:                ctlr->rb[i].owner = Interface;
        !           618: 
        !           619:        ctlr->th = 0;
        !           620:        ctlr->ti = 0;
        !           621:        for(i = 0; i < ctlr->ntb; i++)
        !           622:                ctlr->tb[i].owner = Host;
        !           623: }
        !           624: 
        !           625: Chan*
        !           626: etherattach(char *spec)
        !           627: {
        !           628:        Ctlr *ctlr = softctlr;
        !           629: 
        !           630:        if(ctlr->present == 0)
        !           631:                error(Enodev);
        !           632: 
        !           633:        /*
        !           634:         * Enable the interface
        !           635:         * and start the kproc.
        !           636:         */     
        !           637:        (*ctlr->card.attach)(ctlr);
        !           638:        if(ctlr->kproc == 0){
        !           639:                sprint(ctlr->name, "ether%dkproc", 0);
        !           640:                ctlr->kproc = 1;
        !           641:                kproc(ctlr->name, etherkproc, ctlr);
        !           642:        }
        !           643:        return devattach('l', spec);
        !           644: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.