/* atmxmit.c */ /* Low level transmission drivers and buffer managers */ #include "atmdd.h" /* This is the maximum number of transmit buffer headers that */ /* can be allocated to a single LC at any point in time... */ /* It is critical that CLIP VC's have access to A LOT of these */ /* ... so if your run CLIP and use a single VC you should */ /* disable this limit by setting it to XBH_COUNT+1 */ #define MAX_XBCOUNT 3 /* (MAX_XBHCOUNT / 3) */ #define SINGLE_WAITQ #ifdef FULL_STACK #include #include #include #include #endif #undef USE_NETIF_CALLS #ifdef USE_NETIF_CALLS #define TX_HIGH_BACKLOG 6 #define TX_LOW_BACKLOG 1 static struct net_device *blkdev = 0; #endif #include /* Copyright (c) 1997 Robert Geist and James Westall * Clemson University Dept. of Computer Science * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * The IBM Corp. did not participate in the development of * this software and neither warrants it nor supports it. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /**/ /* Initialize transmit buffer management structures */ static int nxtuse = 1; /* Buffer alloc should be STRICT fifo */ static int nxtfree = 0; static int in_xmitint = 0; extern int in_recvint; static int serial = 1; static int xmit_logging = 0; static int cycle_lo; static int cycle_hi; /* These vci's are serviced in strict priority order */ /* Other's are serviced round robin.. */ unsigned short highpvcis[] = {5, 16, 17}; #define HIGHPVCIS (sizeof(highpvcis) / sizeof(unsigned short)) void atm_initxb( struct pddtype *ape) { struct tfdtype *tfd; unsigned long loc; int i; int regid; ape->tbtolowp = 0; for (i = 0; i < LC_COUNT; i++) { skb_queue_head_init(&ape->tx_queue[i]); ape->tx_backlog[i] = 0; ape->tbcount[i] = 0; } ape->tx_pending = 0; ape->last_tx_vcc = 0; /* Begin by mating each buffer descriptor to its associated buffer */ tfd = ape->xbpool->tfds; #ifdef DEBUG_XMIT printk("atm_initxb: TFD pool at virt %x \n", tfd); printk("atm_initxb: TFD pool at phys %x \n", virt_to_phys(tfd)); #endif for (i = 0; i < XBH_COUNT; i++) { memset(tfd, 0, sizeof(struct tfdtype)); tfd->bufcount = 1; #ifdef FULL_STACK tfd->skb = 0; tfd->vcc = 0; #else tfd->dbds[0].data = (char *)virt_to_phys((ape->xbpool->xbfs + i)); tfd->vdbds[0].data = (char *)(ape->xbpool->xbfs + i); tfd->dbds[0].buflen = XB_SIZE; #ifdef DEBUG_XMIT printk("atm_initxb: buf %d at virt %x \n", i, tfd->vdbds[0].data); printk("atm_initxb: buf %d at phys %x \n", i, virt_to_phys(tfd->dbds[0].data)); #endif #endif tfd += 1; } /* Hang a dummy descriptor on the Transmit Complete List (TCL) */ tfd = ape->xbpool->tfds; tfd->next = (struct tfdtype *)1; tfd->tclnext = (struct tfdtype *)1; ape->stcl = tfd; /* Initialize the TCL LFDA register in the APE 25 */ loc = (unsigned long)virt_to_phys(tfd); WT_CNTLREG(ape, TCL_LFDA_LO, loc & 0xffff); WT_CNTLREG(ape, TCL_LFDA_HI, (loc >> 16) & 0xffff); /* Queue the rest of the transmit descriptors on a driver */ /* maintained list of free transmit descriptors. */ tfd = ape->xbpool->tfds + 1; ape->sftfdl = tfd; for (i = 1; i < XBH_COUNT - 1; i++) { tfd->tclnext = tfd + 1; tfd->next = (struct tfdtype *)1; tfd += 1; } /* Flag the end of the tfd in the usual manner */ tfd->tclnext = (struct tfdtype *)1; ape->eftfdl = tfd; /* Set up the TMQ's registers... These are used to control */ /* the rate at which transmissions can occur.. See p.24 */ regid = TMQ_CFG_0; for (i = 0; i < 8; i++) { WT_CNTLREG(ape, regid, 106); regid += 2; } /* These count when a TMQ misses service and decrements the */ /* Register if that should occur. */ regid = T0MQ_MIS_CNT; for (i = 0; i < 8; i++) { WT_CNTLREG(ape, regid, 0xff); regid += 2; } init_waitqueue_head(&ape->tfd_waitq); for (i = 0; i < LC_COUNT; i++) { init_waitqueue_head(&ape->tbc_waitq[i]); } printk("SFTFDL is at %x \n", &ape->sftfdl); printk("EFTFDL is at %x \n", &ape->eftfdl); } /**/ /* This routine is called only when the state of the */ /* driver queues appears to be inconsistent with */ /* pending counts.. (which should never happen!) */ /* It dumps the state of the world and then tries */ /* to restore consistent state. */ void atm_dumpbacklog( struct pddtype *ape) { int i; int total; struct sk_buff *skb; printk("atm_xmit: tx_pending is %d \n", ape->tx_pending); total = 0; for (i = 0; i < LC_COUNT; i++) { skb = skb_peek(&ape->tx_queue[i]); printk("%3d %4d %4d %8x \n", i, ape->tx_backlog[i], ape->tbcount[i], skb); if (skb != 0) total += ape->tx_backlog[i]; else ape->tx_backlog[i] = 0; } ape->tx_pending = total; } #ifdef FULL_STACK /**/ int atm_drivexmit( struct pddtype *ape, struct tfdtype *tfd, struct atm_vcc *vcc, /* Pointer to VCC struct. */ struct sk_buff *skb) /* Pointer to socket buffer */ { int lcid; int lcndx; unsigned long loc; lcndx = ape->vci_to_lc[vcc->vci]; lcid = lcndx + FIRST_LCID; atomic_inc((atomic_t *)&ape->tbcount[lcndx]); if (vcc->vci >= 32) atomic_inc((atomic_t *)&ape->tbtolowp); tfd->next = (struct tfdtype *)1; tfd->tclnext = (struct tfdtype *)1; tfd->lci = lcid; tfd->prmstat = 0; tfd->bufcount = 1; tfd->control = 0; /* Set up length and buffer pointer.. Not using scatter */ /* gather so sdulen = buf[0].len.. */ tfd->sdulen = skb->len; tfd->dbds[0].buflen = skb->len; tfd->dbds[0].data = (char *)virt_to_phys(skb->data); tfd->skb = skb; tfd->vcc = vcc; #ifdef CAPTURE_BUF if (vcc->vci > 32) { /* printk("Dumping %d bytes for lc %d \n", skb->len, lcndx); */ memcpy(ape->xlogbuf, skb->data, skb->len); ape->xlogbuflen = skb->len; } #endif loc = (unsigned long)virt_to_phys(tfd); #if 1 /* def DEBUG_XMIT */ if (xmit_logging) printk("atm_send5skb: Sending tfd at %08x \n", loc); #endif #if 1 atm_wtsreg(ape, TRQ_AAL5_LC, lcid); atm_wtsreg(ape, TRQ_AAL5_TFDA_HI, (loc >> 16) & 0xffff); atm_wtsreg(ape, TRQ_AAL5_TFDA_LO, loc & 0xffff); #endif } /* Transmission redriver */ int atm_redrive_xmit( struct pddtype *ape, struct tfdtype *tfd) { int i; int ndx; int lcndx; int vci = 0; struct sk_buff *skb = 0; struct atm_vcc *vcc = 0; /* See if any of the high priority queues have a backlog */ /* This should be very rare because of constraints on */ /* active populations of other vccs */ for (i = 0; i < HIGHPVCIS; i++) { lcndx = ape->vci_to_lc[highpvcis[i]]; if (ape->tx_backlog[lcndx] > 0) { skb = skb_dequeue(&ape->tx_queue[lcndx]); vci = highpvcis[i]; printk("Found1 backlog %d on %d \n", ape->tx_backlog[lcndx], vci); break; } } /* If nothing hot to go... sched users vccs RR */ if (vci == 0) { printk("Max ndx is %d \n", ape->max_lc_ndx); ndx = ape->last_tx_vcc; ndx += 1; if (ndx > LC_COUNT) ndx = 0; for (i = 0; i < LC_COUNT; i++) { if ((ape->tx_backlog[ndx] > 0) && (ape->tbcount[ndx] < MAX_XBCOUNT)) { /* printk("Found2 backlog %d on %d \n", ape->tx_backlog[ndx], ndx); */ skb = skb_dequeue(&ape->tx_queue[ndx]); ape->last_tx_vcc = ndx; vci = ape->lc_to_vci[ndx]; lcndx = ndx; break; } ndx += 1; if (ndx >= LC_COUNT) ndx = 0; } } /* This situation is more or less normal.. To prevent resource hogging, */ /* no VCC is allowed to hold more than 3 hardware TFD's at a time.. thus */ /* it may well be the case that when a transmit completes there is no */ /* process eligible for scheduling a frame to the hardware.. */ if (vci == 0) { return(-1); } /* Since a backlog is counted... this shouldn't happen */ if (skb == 0) { printk("NULL skb on vci %d in rdv w/ %d \n", vci, ape->tx_pending); printk("Ndx = %d and lastvcc is %d \n", ndx, ape->last_tx_vcc); atm_dumpbacklog(ape); return(-1); } if (xmit_logging) printk("atm_xmit: redriving %d \n", vci); /* Try to recover the vcc pointer from the skb structure */ vcc = ATM_SKB(skb)->vcc; /* On signaling and ilmi vcc's the skb pointer is typically NULL */ /* so we need to recover it the "old fashioned" way . */ if (vcc == 0) { ape25_getvcc(lcndx, &vcc); } if (vcc == 0) { printk("atm_xmit:: NULL vcc in redrive!! \n"); printk("NULL skb on vci %d in rdv w/ %d \n", vci, ape->tx_pending); printk("Ndx = %d and lastvcc is %d \n", ndx, ape->last_tx_vcc); atm_dumpbacklog(ape); return(-1); } /* printk("New_tfd = %x old_tfd = %x \n", tfd->tclnext, tfd); */ ape->sftfdl = tfd->tclnext; atm_drivexmit(ape, tfd, vcc, skb); if (ape->sftfdl == (struct tfdtype *)1) { printk("atm_xmit: Fatal transmit error stfdl = 1 \n"); } ape->tx_pending -= 1; ape->tx_backlog[lcndx] -= 1; #ifdef USE_NETIF_CALLS if ((blkdev != 0) && (ape->tx_pending < TX_LOW_BACKLOG)) { printk("Queue restart with pending = %d \n", ape->tx_pending); netif_wake_queue(blkdev); blkdev = 0; } #endif return(0); } /**/ /* Transmit complete interrupt processing */ void atm_xmitint( struct pddtype *ape, unsigned short sisr) /* Int status reg at time of int */ { int i; int tfdndx; int lcndx; struct tfdtype *tfd; struct tfdtype *ltfd; int count = 0; in_xmitint = 1; /* Recover the existing pointer to the dummy element at */ /* the start of this free queue.. It should NOT have */ /* a next pointer of 1... because that means we got a */ /* trasmit done interrupt with nothing added to the */ /* queue. */ tfd = ape->stcl; #ifdef DEBUG_XMIT printk("atm_xmitint: Start of TCL list at %x \n", tfd); #endif if (tfd->tclnext == (struct tfdtype *)1) { #ifdef DEBUG_XMIT printk("atm_xmitint: Called with empty list \n"); #endif return; } /* Go through the list of free descriptors. */ do { #ifdef DEBUG_XMIT tfdndx = tfd - ape->xbpool->tfds; printk("atm_xmitint: Freed addr %08x index %08x \n", tfd, tfdndx); count += 1; if (tfdndx != nxtfree) { printk("atm_xmitint: Freed addr %08x index %08x \n", tfd, tfdndx); printk("YEOW - mismatch nxtfree = %08x \n", nxtfree); nxtfree = tfdndx; } nxtfree += 1; nxtfree %= XBH_COUNT; #endif ltfd = tfd; if (tfd->tclnext != (struct tfdtype *)1); tfd->tclnext = (struct tfdtype *)phys_to_virt((int)tfd->tclnext); tfd = tfd->tclnext; lcndx = tfd->lci - FIRST_LCID; atomic_dec((atomic_t *)&ape->tbcount[lcndx]); if (ape->lc_to_vci[lcndx] >= 32) atomic_dec((atomic_t *)&ape->tbtolowp); #ifndef SINGLE_WAITQ /* wake_up_interruptible(&ape->tbc_waitq[lcndx]); */ #endif /* If this descriptor seems to have an attached skbuff */ /* then free the skbuff. */ #ifdef FULL_STACK if (tfd->skb != 0) { if (tfd->vcc->pop == 0) dev_kfree_skb(tfd->skb); else tfd->vcc->pop(tfd->vcc, tfd->skb); } #endif } while (tfd->tclnext != (struct tfdtype *)1); /* Finally copy the consumed descriptors to the free list */ ltfd->tclnext = (struct tfdtype *)1; /* Set end of free list */ ape->eftfdl->tclnext = ape->stcl; /* Attach to free list */ ape->stcl = tfd; /* Update TCL list start */ ape->eftfdl = ltfd; /* Update free list end */ /* Now see if we can redrive anything */ while (ape->tx_pending > 0) { /* Make sure we have hardware resources available */ tfd = ape->sftfdl; if (xmit_logging) printk("Attempting to redrive tfd = %x \n", tfd); if ((tfd->tclnext == (struct tfdtype *)1) || (atm_rdsreg(ape, AAL_STATUS) & 1)) break; if (atm_redrive_xmit(ape, tfd)) break; } in_xmitint = 0; } /**/ /* This is the main entry point for sending Linux ATM SKBuffs */ int atm_send5skb( struct pddtype *ape, struct atm_vcc *vcc, /* Pointer to VCC struct. */ struct sk_buff *skb) /* Pointer to socket buffer */ { struct tfdtype *tfd; unsigned long lockflags; int lcndx; lcndx = ape->vci_to_lc[vcc->vci]; if ((lcndx < 0) || (lcndx >= LC_COUNT)) { printk("atm_send5skb: vci %d \n", lcndx, vcc->vci); return(0); } if (skb == 0) { printk("atm_send5skb: Null skb on vci %d \n", vcc->vci); return(0); } #ifdef SYNTHETIC_DROPS asm ("RDTSC"); asm ("movl %eax, cycle_lo"); asm ("movl %edx, cycle_hi"); cycle_lo &= 0x7fff; if ((cycle_lo > ape->dropthresh) && (vcc->vci > 32)) { printk("atm_xmit: Random drop on vci %d \n", vcc->vci); if (vcc->pop == 0) dev_kfree_skb(skb); else vcc->pop(vcc, skb); return(0); } if ((vcc->vci > 32) && (ape->tbcount[lcndx] >= ape->tqmax)) { printk("atm_xmit: Constrained queue drop on vci %d \n", vcc->vci); if (vcc->pop == 0) dev_kfree_skb(skb); else vcc->pop(vcc, skb); return(0); } #endif if ((vcc->vci > 32) && (ape->tx_backlog[lcndx] >= 6)) { /* printk("atm_xmit: Constrained queue drop on vci %d \n", vcc->vci); */ if (vcc->pop == 0) dev_kfree_skb(skb); else vcc->pop(vcc, skb); return(0); } /* We run this part disabled to avoid a potentially */ /* nasty race condition in which a buffer might be */ /* freed and the wakeup done (and lost) after the */ /* process performed the test for available buffers */ /* but before it went to sleep.. */ spin_lock_irqsave(&ape->xmitlock, lockflags); /* If there is anything queued on this VCC we must queue */ /* the current request to maintain fifo order... */ if (ape->tx_backlog[lcndx] > 0) { skb_queue_tail(&ape->tx_queue[lcndx], skb); ape->tx_backlog[lcndx]++; ape->tx_pending++; spin_unlock_irqrestore(&ape->xmitlock, lockflags); return(0); } #ifdef CAPTURE_PMD /* printk("Incrementing output for lc %d vci %d \n", lcndx, vcc->vci); */ ape->pmddata->lcout[vcc->vci] += 1; #endif /* Get a transmit frame descriptor from the list */ tfd = ape->sftfdl; /* Attempting to consume last element breaks list */ /* management so we don't do that. */ if ((tfd->tclnext == (struct tfdtype *)1) || (atm_rdsreg(ape, AAL_STATUS) & 1) || ((vcc->vci >= 32) && (ape->tbcount[lcndx] >= MAX_XBCOUNT))) { #ifdef CAPTURE_PMD if (tfd->tclnext == (struct tfdtype *)1) { ape->pmddata->tclwaits += 1; } else if (ape->tbcount[lcndx] >= MAX_XBCOUNT) { ape->pmddata->tbcwaits += 1; } else { ape->pmddata->tmqwaits += 1; } #endif if (vcc->vci < 32) { printk("Queueing skb %x on vcc %x for vci %d \n", skb, ATM_SKB(skb)->vcc, vcc->vci); atm_vtbq(ape); } skb_queue_tail(&ape->tx_queue[lcndx], skb); ape->tx_backlog[lcndx]++; ape->tx_pending++; /* printk("queueing packet with sendbuf %d inuse %d \n", vcc->sk->sndbuf, vcc->tx_inuse); */ #ifdef USE_NETIF_CALLS if (skb->dev == 0) printk("Queue tested with pending = %d \n", ape->tx_pending); if ((ape->tx_pending > TX_HIGH_BACKLOG) && (skb->dev != 0)) { printk("Queue stopped with pending = %d \n", ape->tx_pending); blkdev = skb->dev; netif_stop_queue(blkdev); } #endif if (xmit_logging) { printk("atm_send5skb: queued xmit for %d at %d and %d \n", vcc->vci, ape->tx_backlog[lcndx], ape->tx_pending); } spin_unlock_irqrestore(&ape->xmitlock, lockflags); return(0); } /* As long as there is more than one element in the list */ /* sftfdl and eftdfl never point to the same thing... */ /* So this assignment doesn't require mutex with int */ /* int handler. */ ape->sftfdl = tfd->tclnext; if (ape->sftfdl == (struct tfdtype *)1) { printk("atm_xmit: Fatal transmit error stfdl = 1 \n"); } atm_drivexmit(ape, tfd, vcc, skb); spin_unlock_irqrestore(&ape->xmitlock, lockflags); return(0); } #else /**/ /* This is an internal kernel entrypoint to write a frame */ /* i.e. it assumes the message is already in kernel space */ int atm_send5( struct pddtype *ape, int vcid, /* logical channel id. */ char *message, /* Pointer to data. */ int len) /* Length of message */ { struct tfdtype *tfd; unsigned long loc; int lcid; lcid = atm_testvci(ape, vcid); if (lcid < 0) return(lcid); /* Get a transmit frame descriptor from the list */ tfd = ape->sftfdl; /* Attempting to consume last element breaks list */ /* management so we don't do that. */ while (tfd->tclnext == (struct tfdtype *)1) { #ifdef DEBUG_XMIT printk("atm_send5: no free transmit buffers \n"); #endif interruptible_sleep_on(&ape->tfd_waitq); tfd = ape->sftfdl; /* return(-1); */ } /* As long as there is more than one element in the list */ /* sftfdl and eftdfl never point to the same thing... */ /* So this assignment doesn't require mutex with int */ /* int handler. */ ape->sftfdl = tfd->tclnext; tfd->next = (struct tfdtype *)1; tfd->tclnext = (struct tfdtype *)1; tfd->lci = lcid; tfd->prmstat = 0; tfd->bufcount = 1; tfd->control = 0; tfd->sdulen = len; tfd->dbds[0].buflen = len; memcpy(tfd->vdbds[0].data, message, len); loc = (unsigned long)(virt_to_phys)(tfd); #ifdef DEBUG_XMIT printk("atm_send5: Sending tfd at %08x \n", loc); #endif atm_wtsreg(ape, TRQ_AAL5_LC, lcid); atm_wtsreg(ape, TRQ_AAL5_TFDA_HI, (loc >> 16) & 0xffff); atm_wtsreg(ape, TRQ_AAL5_TFDA_LO, loc & 0xffff); return(0); } /**/ /* Send a packet from user space */ int atm_send5u( struct pddtype *ape, int vcid, /* logical channel id. */ char *message, /* Pointer to data. */ int len) /* Length of message */ { struct tfdtype *tfd; unsigned long loc; int lcid; int lcndx; int tfdndx; unsigned long flags; unsigned long lockflags; spin_lock_irqsave(&ape->xmitlock, lockflags); lcndx = vcid; lcid = lcndx + FIRST_LCID; if (ape->lcstatus[lcndx] == 0) { spin_unlock_irqrestore(&ape->xmitlock, lockflags); printk("atm_send5u: Attempt to send to bad lc %d \n", lcid); return(lcid); } /* We run this part locked to avoid a potentially */ /* nasty race condition in which a buffer might be */ /* freed and the wakeup done (and lost) after the */ /* process performed the test for available buffers */ /* but before it went to sleep.. */ /* Get a transmit frame descriptor from the list */ tfd = ape->sftfdl; /* Attempting to consume last element breaks list */ /* management so we don't do that. */ /* Also we have to read the AAL_STATUS register */ /* to check for a full queue condition. The low */ /* order bit is for AAL 5 */ while ((tfd->tclnext == (struct tfdtype *)1) || (atm_rdsreg(ape, AAL_STATUS) & 1) || (ape->tbcount[lcndx] >= MAX_XBCOUNT)) { #ifdef CAPTURE_PMD if (tfd->tclnext == (struct tfdtype *)1) { ape->pmddata->tclwaits += 1; } else if (ape->tbcount[lcndx] >= MAX_XBCOUNT) { ape->pmddata->tbcwaits += 1; } else { ape->pmddata->tmqwaits += 1; } #endif spin_unlock_irqrestore(&ape->xmitlock, lockflags); #ifdef DEBUG_XMIT printk("atm_send5u: no free transmit buffers \n"); #endif #ifdef SINGLE_WAITQ interruptible_sleep_on(&ape->tfd_waitq); #else interruptible_sleep_on(&ape->tbc_waitq[lcndx]); #endif if (signal_pending(current)) { printk("atm_recv5u: Returning on sigint \n"); return(-10); } spin_lock_irqsave(&ape->xmitlock, lockflags); tfd = ape->sftfdl; /* return(-1); */ } /* As long as there is more than one element in the list */ /* sftfdl and eftdfl never point to the same thing... */ /* So this assignment doesn't require mutex with int */ /* int handler. */ ape->sftfdl = tfd->tclnext; atomic_inc((atomic_t *)&ape->tbcount[lcndx]); if (len > XB_SIZE) len = XB_SIZE; tfd->next = (struct tfdtype *)1; tfd->tclnext = (struct tfdtype *)1; tfd->lci = lcid; tfd->prmstat = 0; tfd->bufcount = 1; tfd->control = 0; tfd->sdulen = len; tfd->dbds[0].buflen = len; copy_from_user(tfd->vdbds[0].data, message, len); loc = (unsigned long)virt_to_phys(tfd); #ifdef DEBUG_XMIT tfdndx = tfd - ape->xbpool->tfds; printk("atm_send5u: Alloc addr %08x index %08x serial %d \n", tfd, tfdndx, serial++); if (tfdndx != nxtuse) { printk("atm_send5u: Alloc addr %08x index %08x \n", tfd, tfdndx); printk("YEOW - mismatch nxtuse = %08x \n", nxtuse); nxtuse = tfdndx; } nxtuse += 1; nxtuse %= XBH_COUNT; atm_vtbq(ape); #endif atm_wtsreg(ape, TRQ_AAL5_LC, lcid); atm_wtsreg(ape, TRQ_AAL5_TFDA_HI, (loc >> 16) & 0xffff); atm_wtsreg(ape, TRQ_AAL5_TFDA_LO, loc & 0xffff); spin_unlock_irqrestore(&ape->xmitlock, lockflags); return(0); } #endif /**/ /* Validate buffer queues.. or show them invalid */ int atm_vtbq( struct pddtype *ape) { unsigned long loc; struct tfdtype *tfd; int tfdndx; int count = 0; int ndx; /* Count the xmit free list */ tfd = ape->sftfdl; atm_dumpbacklog(ape); printk("Xmit free list \n"); do { tfdndx = tfd - ape->xbpool->tfds; count += 1; printk("%4d \n", tfdndx); tfd = tfd->tclnext; } while (tfd != (struct tfdtype *)1); printk("Total free xmit buffers = %d \n", count); printk("Transmit backlog %d \n", ape->tx_pending); for (ndx = 1; ndx < 64; ndx++) { if (ape->tx_backlog[ndx]) printk("%3d %3d %3d \n", ndx, ape->lc_to_vci, ape->tbcount[ndx]); } printk("Transmit buffer allocations \n"); for (ndx = 1; ndx < 64; ndx++) { if (ape->tbcount[ndx]) printk("%3d %3d %3d \n", ndx, ape->lc_to_vci, ape->tbcount[ndx]); } /* xmit_logging = 1; */ }