diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/atm/eni.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/atm/eni.c')
-rw-r--r-- | drivers/atm/eni.c | 2299 |
1 files changed, 2299 insertions, 0 deletions
diff --git a/drivers/atm/eni.c b/drivers/atm/eni.c new file mode 100644 index 00000000000..78e34ee79df --- /dev/null +++ b/drivers/atm/eni.c @@ -0,0 +1,2299 @@ +/* drivers/atm/eni.c - Efficient Networks ENI155P device driver */ + +/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ + + +#include <linux/module.h> +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/pci.h> +#include <linux/errno.h> +#include <linux/atm.h> +#include <linux/atmdev.h> +#include <linux/sonet.h> +#include <linux/skbuff.h> +#include <linux/time.h> +#include <linux/delay.h> +#include <linux/uio.h> +#include <linux/init.h> +#include <linux/atm_eni.h> +#include <linux/bitops.h> +#include <asm/system.h> +#include <asm/io.h> +#include <asm/atomic.h> +#include <asm/uaccess.h> +#include <asm/string.h> +#include <asm/byteorder.h> + +#include "tonga.h" +#include "midway.h" +#include "suni.h" +#include "eni.h" + +#if !defined(__i386__) && !defined(__x86_64__) +#ifndef ioremap_nocache +#define ioremap_nocache(X,Y) ioremap(X,Y) +#endif +#endif + +/* + * TODO: + * + * Show stoppers + * none + * + * Minor + * - OAM support + * - fix bugs listed below + */ + +/* + * KNOWN BUGS: + * + * - may run into JK-JK bug and deadlock + * - should allocate UBR channel first + * - buffer space allocation algorithm is stupid + * (RX: should be maxSDU+maxdelay*rate + * TX: should be maxSDU+min(maxSDU,maxdelay*rate) ) + * - doesn't support OAM cells + * - eni_put_free may hang if not putting memory fragments that _complete_ + * 2^n block (never happens in real life, though) + * - keeps IRQ even if initialization fails + */ + + +#if 0 +#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args) +#else +#define DPRINTK(format,args...) +#endif + + +#ifndef CONFIG_ATM_ENI_TUNE_BURST +#define CONFIG_ATM_ENI_BURST_TX_8W +#define CONFIG_ATM_ENI_BURST_RX_4W +#endif + + +#ifndef CONFIG_ATM_ENI_DEBUG + + +#define NULLCHECK(x) + +#define EVENT(s,a,b) + + +static void event_dump(void) +{ +} + + +#else + + +/* + * NULL pointer checking + */ + +#define NULLCHECK(x) \ + if ((unsigned long) (x) < 0x30) \ + printk(KERN_CRIT #x "==0x%lx\n",(unsigned long) (x)) + +/* + * Very extensive activity logging. Greatly improves bug detection speed but + * costs a few Mbps if enabled. + */ + +#define EV 64 + +static const char *ev[EV]; +static unsigned long ev_a[EV],ev_b[EV]; +static int ec = 0; + + +static void EVENT(const char *s,unsigned long a,unsigned long b) +{ + ev[ec] = s; + ev_a[ec] = a; + ev_b[ec] = b; + ec = (ec+1) % EV; +} + + +static void event_dump(void) +{ + int n,i; + + for (n = 0; n < EV; n++) { + i = (ec+n) % EV; + printk(KERN_NOTICE); + printk(ev[i] ? ev[i] : "(null)",ev_a[i],ev_b[i]); + } +} + + +#endif /* CONFIG_ATM_ENI_DEBUG */ + + +/* + * NExx must not be equal at end + * EExx may be equal at end + * xxPJOK verify validity of pointer jumps + * xxPMOK operating on a circular buffer of "c" words + */ + +#define NEPJOK(a0,a1,b) \ + ((a0) < (a1) ? (b) <= (a0) || (b) > (a1) : (b) <= (a0) && (b) > (a1)) +#define EEPJOK(a0,a1,b) \ + ((a0) < (a1) ? (b) < (a0) || (b) >= (a1) : (b) < (a0) && (b) >= (a1)) +#define NEPMOK(a0,d,b,c) NEPJOK(a0,(a0+d) & (c-1),b) +#define EEPMOK(a0,d,b,c) EEPJOK(a0,(a0+d) & (c-1),b) + + +static int tx_complete = 0,dma_complete = 0,queued = 0,requeued = 0, + backlogged = 0,rx_enqueued = 0,rx_dequeued = 0,pushed = 0,submitted = 0, + putting = 0; + +static struct atm_dev *eni_boards = NULL; + +static u32 *cpu_zeroes = NULL; /* aligned "magic" zeroes */ +static dma_addr_t zeroes; + +/* Read/write registers on card */ +#define eni_in(r) readl(eni_dev->reg+(r)*4) +#define eni_out(v,r) writel((v),eni_dev->reg+(r)*4) + + +/*-------------------------------- utilities --------------------------------*/ + + +static void dump_mem(struct eni_dev *eni_dev) +{ + int i; + + for (i = 0; i < eni_dev->free_len; i++) + printk(KERN_DEBUG " %d: %p %d\n",i, + eni_dev->free_list[i].start, + 1 << eni_dev->free_list[i].order); +} + + +static void dump(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + + int i; + + eni_dev = ENI_DEV(dev); + printk(KERN_NOTICE "Free memory\n"); + dump_mem(eni_dev); + printk(KERN_NOTICE "TX buffers\n"); + for (i = 0; i < NR_CHAN; i++) + if (eni_dev->tx[i].send) + printk(KERN_NOTICE " TX %d @ %p: %ld\n",i, + eni_dev->tx[i].send,eni_dev->tx[i].words*4); + printk(KERN_NOTICE "RX buffers\n"); + for (i = 0; i < 1024; i++) + if (eni_dev->rx_map[i] && ENI_VCC(eni_dev->rx_map[i])->rx) + printk(KERN_NOTICE " RX %d @ %p: %ld\n",i, + ENI_VCC(eni_dev->rx_map[i])->recv, + ENI_VCC(eni_dev->rx_map[i])->words*4); + printk(KERN_NOTICE "----\n"); +} + + +static void eni_put_free(struct eni_dev *eni_dev, void __iomem *start, + unsigned long size) +{ + struct eni_free *list; + int len,order; + + DPRINTK("init 0x%lx+%ld(0x%lx)\n",start,size,size); + start += eni_dev->base_diff; + list = eni_dev->free_list; + len = eni_dev->free_len; + while (size) { + if (len >= eni_dev->free_list_size) { + printk(KERN_CRIT "eni_put_free overflow (%p,%ld)\n", + start,size); + break; + } + for (order = 0; !(((unsigned long)start | size) & (1 << order)); order++); + if (MID_MIN_BUF_SIZE > (1 << order)) { + printk(KERN_CRIT "eni_put_free: order %d too small\n", + order); + break; + } + list[len].start = (void __iomem *) start; + list[len].order = order; + len++; + start += 1 << order; + size -= 1 << order; + } + eni_dev->free_len = len; + /*dump_mem(eni_dev);*/ +} + + +static void __iomem *eni_alloc_mem(struct eni_dev *eni_dev, unsigned long *size) +{ + struct eni_free *list; + void __iomem *start; + int len,i,order,best_order,index; + + list = eni_dev->free_list; + len = eni_dev->free_len; + if (*size < MID_MIN_BUF_SIZE) *size = MID_MIN_BUF_SIZE; + if (*size > MID_MAX_BUF_SIZE) return NULL; + for (order = 0; (1 << order) < *size; order++); + DPRINTK("trying: %ld->%d\n",*size,order); + best_order = 65; /* we don't have more than 2^64 of anything ... */ + index = 0; /* silence GCC */ + for (i = 0; i < len; i++) + if (list[i].order == order) { + best_order = order; + index = i; + break; + } + else if (best_order > list[i].order && list[i].order > order) { + best_order = list[i].order; + index = i; + } + if (best_order == 65) return NULL; + start = list[index].start-eni_dev->base_diff; + list[index] = list[--len]; + eni_dev->free_len = len; + *size = 1 << order; + eni_put_free(eni_dev,start+*size,(1 << best_order)-*size); + DPRINTK("%ld bytes (order %d) at 0x%lx\n",*size,order,start); + memset_io(start,0,*size); /* never leak data */ + /*dump_mem(eni_dev);*/ + return start; +} + + +static void eni_free_mem(struct eni_dev *eni_dev, void __iomem *start, + unsigned long size) +{ + struct eni_free *list; + int len,i,order; + + start += eni_dev->base_diff; + list = eni_dev->free_list; + len = eni_dev->free_len; + for (order = -1; size; order++) size >>= 1; + DPRINTK("eni_free_mem: %p+0x%lx (order %d)\n",start,size,order); + for (i = 0; i < len; i++) + if (((unsigned long) list[i].start) == ((unsigned long)start^(1 << order)) && + list[i].order == order) { + DPRINTK("match[%d]: 0x%lx/0x%lx(0x%x), %d/%d\n",i, + list[i].start,start,1 << order,list[i].order,order); + list[i] = list[--len]; + start = (void __iomem *) ((unsigned long) start & ~(unsigned long) (1 << order)); + order++; + i = -1; + continue; + } + if (len >= eni_dev->free_list_size) { + printk(KERN_ALERT "eni_free_mem overflow (%p,%d)\n",start, + order); + return; + } + list[len].start = start; + list[len].order = order; + eni_dev->free_len = len+1; + /*dump_mem(eni_dev);*/ +} + + +/*----------------------------------- RX ------------------------------------*/ + + +#define ENI_VCC_NOS ((struct atm_vcc *) 1) + + +static void rx_ident_err(struct atm_vcc *vcc) +{ + struct atm_dev *dev; + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + + dev = vcc->dev; + eni_dev = ENI_DEV(dev); + /* immediately halt adapter */ + eni_out(eni_in(MID_MC_S) & + ~(MID_DMA_ENABLE | MID_TX_ENABLE | MID_RX_ENABLE),MID_MC_S); + /* dump useful information */ + eni_vcc = ENI_VCC(vcc); + printk(KERN_ALERT DEV_LABEL "(itf %d): driver error - RX ident " + "mismatch\n",dev->number); + printk(KERN_ALERT " VCI %d, rxing %d, words %ld\n",vcc->vci, + eni_vcc->rxing,eni_vcc->words); + printk(KERN_ALERT " host descr 0x%lx, rx pos 0x%lx, descr value " + "0x%x\n",eni_vcc->descr,eni_vcc->rx_pos, + (unsigned) readl(eni_vcc->recv+eni_vcc->descr*4)); + printk(KERN_ALERT " last %p, servicing %d\n",eni_vcc->last, + eni_vcc->servicing); + EVENT("---dump ends here---\n",0,0); + printk(KERN_NOTICE "---recent events---\n"); + event_dump(); + ENI_DEV(dev)->fast = NULL; /* really stop it */ + ENI_DEV(dev)->slow = NULL; + skb_queue_head_init(&ENI_DEV(dev)->rx_queue); +} + + +static int do_rx_dma(struct atm_vcc *vcc,struct sk_buff *skb, + unsigned long skip,unsigned long size,unsigned long eff) +{ + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + u32 dma_rd,dma_wr; + u32 dma[RX_DMA_BUF*2]; + dma_addr_t paddr; + unsigned long here; + int i,j; + + eni_dev = ENI_DEV(vcc->dev); + eni_vcc = ENI_VCC(vcc); + paddr = 0; /* GCC, shut up */ + if (skb) { + paddr = pci_map_single(eni_dev->pci_dev,skb->data,skb->len, + PCI_DMA_FROMDEVICE); + ENI_PRV_PADDR(skb) = paddr; + if (paddr & 3) + printk(KERN_CRIT DEV_LABEL "(itf %d): VCI %d has " + "mis-aligned RX data (0x%lx)\n",vcc->dev->number, + vcc->vci,(unsigned long) paddr); + ENI_PRV_SIZE(skb) = size+skip; + /* PDU plus descriptor */ + ATM_SKB(skb)->vcc = vcc; + } + j = 0; + if ((eff && skip) || 1) { /* @@@ actually, skip is always == 1 ... */ + here = (eni_vcc->descr+skip) & (eni_vcc->words-1); + dma[j++] = (here << MID_DMA_COUNT_SHIFT) | (vcc->vci + << MID_DMA_VCI_SHIFT) | MID_DT_JK; + j++; + } + here = (eni_vcc->descr+size+skip) & (eni_vcc->words-1); + if (!eff) size += skip; + else { + unsigned long words; + + if (!size) { + DPRINTK("strange things happen ...\n"); + EVENT("strange things happen ... (skip=%ld,eff=%ld)\n", + size,eff); + } + words = eff; + if (paddr & 15) { + unsigned long init; + + init = 4-((paddr & 15) >> 2); + if (init > words) init = words; + dma[j++] = MID_DT_WORD | (init << MID_DMA_COUNT_SHIFT) | + (vcc->vci << MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += init << 2; + words -= init; + } +#ifdef CONFIG_ATM_ENI_BURST_RX_16W /* may work with some PCI chipsets ... */ + if (words & ~15) { + dma[j++] = MID_DT_16W | ((words >> 4) << + MID_DMA_COUNT_SHIFT) | (vcc->vci << + MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += (words & ~15) << 2; + words &= 15; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_8W /* works only with *some* PCI chipsets ... */ + if (words & ~7) { + dma[j++] = MID_DT_8W | ((words >> 3) << + MID_DMA_COUNT_SHIFT) | (vcc->vci << + MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += (words & ~7) << 2; + words &= 7; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_4W /* recommended */ + if (words & ~3) { + dma[j++] = MID_DT_4W | ((words >> 2) << + MID_DMA_COUNT_SHIFT) | (vcc->vci << + MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += (words & ~3) << 2; + words &= 3; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_RX_2W /* probably useless if RX_4W, RX_8W, ... */ + if (words & ~1) { + dma[j++] = MID_DT_2W | ((words >> 1) << + MID_DMA_COUNT_SHIFT) | (vcc->vci << + MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + paddr += (words & ~1) << 2; + words &= 1; + } +#endif + if (words) { + dma[j++] = MID_DT_WORD | (words << MID_DMA_COUNT_SHIFT) + | (vcc->vci << MID_DMA_VCI_SHIFT); + dma[j++] = paddr; + } + } + if (size != eff) { + dma[j++] = (here << MID_DMA_COUNT_SHIFT) | + (vcc->vci << MID_DMA_VCI_SHIFT) | MID_DT_JK; + j++; + } + if (!j || j > 2*RX_DMA_BUF) { + printk(KERN_CRIT DEV_LABEL "!j or j too big!!!\n"); + goto trouble; + } + dma[j-2] |= MID_DMA_END; + j = j >> 1; + dma_wr = eni_in(MID_DMA_WR_RX); + dma_rd = eni_in(MID_DMA_RD_RX); + /* + * Can I move the dma_wr pointer by 2j+1 positions without overwriting + * data that hasn't been read (position of dma_rd) yet ? + */ + if (!NEPMOK(dma_wr,j+j+1,dma_rd,NR_DMA_RX)) { /* @@@ +1 is ugly */ + printk(KERN_WARNING DEV_LABEL "(itf %d): RX DMA full\n", + vcc->dev->number); + goto trouble; + } + for (i = 0; i < j; i++) { + writel(dma[i*2],eni_dev->rx_dma+dma_wr*8); + writel(dma[i*2+1],eni_dev->rx_dma+dma_wr*8+4); + dma_wr = (dma_wr+1) & (NR_DMA_RX-1); + } + if (skb) { + ENI_PRV_POS(skb) = eni_vcc->descr+size+1; + skb_queue_tail(&eni_dev->rx_queue,skb); + eni_vcc->last = skb; +rx_enqueued++; + } + eni_vcc->descr = here; + eni_out(dma_wr,MID_DMA_WR_RX); + return 0; + +trouble: + if (paddr) + pci_unmap_single(eni_dev->pci_dev,paddr,skb->len, + PCI_DMA_FROMDEVICE); + if (skb) dev_kfree_skb_irq(skb); + return -1; +} + + +static void discard(struct atm_vcc *vcc,unsigned long size) +{ + struct eni_vcc *eni_vcc; + + eni_vcc = ENI_VCC(vcc); + EVENT("discard (size=%ld)\n",size,0); + while (do_rx_dma(vcc,NULL,1,size,0)) EVENT("BUSY LOOP",0,0); + /* could do a full fallback, but that might be more expensive */ + if (eni_vcc->rxing) ENI_PRV_POS(eni_vcc->last) += size+1; + else eni_vcc->rx_pos = (eni_vcc->rx_pos+size+1) & (eni_vcc->words-1); +} + + +/* + * TODO: should check whether direct copies (without DMA setup, dequeuing on + * interrupt, etc.) aren't much faster for AAL0 + */ + +static int rx_aal0(struct atm_vcc *vcc) +{ + struct eni_vcc *eni_vcc; + unsigned long descr; + unsigned long length; + struct sk_buff *skb; + + DPRINTK(">rx_aal0\n"); + eni_vcc = ENI_VCC(vcc); + descr = readl(eni_vcc->recv+eni_vcc->descr*4); + if ((descr & MID_RED_IDEN) != (MID_RED_RX_ID << MID_RED_SHIFT)) { + rx_ident_err(vcc); + return 1; + } + if (descr & MID_RED_T) { + DPRINTK(DEV_LABEL "(itf %d): trashing empty cell\n", + vcc->dev->number); + length = 0; + atomic_inc(&vcc->stats->rx_err); + } + else { + length = ATM_CELL_SIZE-1; /* no HEC */ + } + skb = length ? atm_alloc_charge(vcc,length,GFP_ATOMIC) : NULL; + if (!skb) { + discard(vcc,length >> 2); + return 0; + } + skb_put(skb,length); + skb->stamp = eni_vcc->timestamp; + DPRINTK("got len %ld\n",length); + if (do_rx_dma(vcc,skb,1,length >> 2,length >> 2)) return 1; + eni_vcc->rxing++; + return 0; +} + + +static int rx_aal5(struct atm_vcc *vcc) +{ + struct eni_vcc *eni_vcc; + unsigned long descr; + unsigned long size,eff,length; + struct sk_buff *skb; + + EVENT("rx_aal5\n",0,0); + DPRINTK(">rx_aal5\n"); + eni_vcc = ENI_VCC(vcc); + descr = readl(eni_vcc->recv+eni_vcc->descr*4); + if ((descr & MID_RED_IDEN) != (MID_RED_RX_ID << MID_RED_SHIFT)) { + rx_ident_err(vcc); + return 1; + } + if (descr & (MID_RED_T | MID_RED_CRC_ERR)) { + if (descr & MID_RED_T) { + EVENT("empty cell (descr=0x%lx)\n",descr,0); + DPRINTK(DEV_LABEL "(itf %d): trashing empty cell\n", + vcc->dev->number); + size = 0; + } + else { + static unsigned long silence = 0; + + if (time_after(jiffies, silence) || silence == 0) { + printk(KERN_WARNING DEV_LABEL "(itf %d): " + "discarding PDU(s) with CRC error\n", + vcc->dev->number); + silence = (jiffies+2*HZ)|1; + } + size = (descr & MID_RED_COUNT)*(ATM_CELL_PAYLOAD >> 2); + EVENT("CRC error (descr=0x%lx,size=%ld)\n",descr, + size); + } + eff = length = 0; + atomic_inc(&vcc->stats->rx_err); + } + else { + size = (descr & MID_RED_COUNT)*(ATM_CELL_PAYLOAD >> 2); + DPRINTK("size=%ld\n",size); + length = readl(eni_vcc->recv+(((eni_vcc->descr+size-1) & + (eni_vcc->words-1)))*4) & 0xffff; + /* -trailer(2)+header(1) */ + if (length && length <= (size << 2)-8 && length <= + ATM_MAX_AAL5_PDU) eff = (length+3) >> 2; + else { /* ^ trailer length (8) */ + EVENT("bad PDU (descr=0x08%lx,length=%ld)\n",descr, + length); + printk(KERN_ERR DEV_LABEL "(itf %d): bad AAL5 PDU " + "(VCI=%d,length=%ld,size=%ld (descr 0x%lx))\n", + vcc->dev->number,vcc->vci,length,size << 2,descr); + length = eff = 0; + atomic_inc(&vcc->stats->rx_err); + } + } + skb = eff ? atm_alloc_charge(vcc,eff << 2,GFP_ATOMIC) : NULL; + if (!skb) { + discard(vcc,size); + return 0; + } + skb_put(skb,length); + DPRINTK("got len %ld\n",length); + if (do_rx_dma(vcc,skb,1,size,eff)) return 1; + eni_vcc->rxing++; + return 0; +} + + +static inline int rx_vcc(struct atm_vcc *vcc) +{ + void __iomem *vci_dsc; + unsigned long tmp; + struct eni_vcc *eni_vcc; + + eni_vcc = ENI_VCC(vcc); + vci_dsc = ENI_DEV(vcc->dev)->vci+vcc->vci*16; + EVENT("rx_vcc(1)\n",0,0); + while (eni_vcc->descr != (tmp = (readl(vci_dsc+4) & MID_VCI_DESCR) >> + MID_VCI_DESCR_SHIFT)) { + EVENT("rx_vcc(2: host dsc=0x%lx, nic dsc=0x%lx)\n", + eni_vcc->descr,tmp); + DPRINTK("CB_DESCR %ld REG_DESCR %d\n",ENI_VCC(vcc)->descr, + (((unsigned) readl(vci_dsc+4) & MID_VCI_DESCR) >> + MID_VCI_DESCR_SHIFT)); + if (ENI_VCC(vcc)->rx(vcc)) return 1; + } + /* clear IN_SERVICE flag */ + writel(readl(vci_dsc) & ~MID_VCI_IN_SERVICE,vci_dsc); + /* + * If new data has arrived between evaluating the while condition and + * clearing IN_SERVICE, we wouldn't be notified until additional data + * follows. So we have to loop again to be sure. + */ + EVENT("rx_vcc(3)\n",0,0); + while (ENI_VCC(vcc)->descr != (tmp = (readl(vci_dsc+4) & MID_VCI_DESCR) + >> MID_VCI_DESCR_SHIFT)) { + EVENT("rx_vcc(4: host dsc=0x%lx, nic dsc=0x%lx)\n", + eni_vcc->descr,tmp); + DPRINTK("CB_DESCR %ld REG_DESCR %d\n",ENI_VCC(vcc)->descr, + (((unsigned) readl(vci_dsc+4) & MID_VCI_DESCR) >> + MID_VCI_DESCR_SHIFT)); + if (ENI_VCC(vcc)->rx(vcc)) return 1; + } + return 0; +} + + +static void poll_rx(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + struct atm_vcc *curr; + + eni_dev = ENI_DEV(dev); + while ((curr = eni_dev->fast)) { + EVENT("poll_rx.fast\n",0,0); + if (rx_vcc(curr)) return; + eni_dev->fast = ENI_VCC(curr)->next; + ENI_VCC(curr)->next = ENI_VCC_NOS; + barrier(); + ENI_VCC(curr)->servicing--; + } + while ((curr = eni_dev->slow)) { + EVENT("poll_rx.slow\n",0,0); + if (rx_vcc(curr)) return; + eni_dev->slow = ENI_VCC(curr)->next; + ENI_VCC(curr)->next = ENI_VCC_NOS; + barrier(); + ENI_VCC(curr)->servicing--; + } +} + + +static void get_service(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + struct atm_vcc *vcc; + unsigned long vci; + + DPRINTK(">get_service\n"); + eni_dev = ENI_DEV(dev); + while (eni_in(MID_SERV_WRITE) != eni_dev->serv_read) { + vci = readl(eni_dev->service+eni_dev->serv_read*4); + eni_dev->serv_read = (eni_dev->serv_read+1) & (NR_SERVICE-1); + vcc = eni_dev->rx_map[vci & 1023]; + if (!vcc) { + printk(KERN_CRIT DEV_LABEL "(itf %d): VCI %ld not " + "found\n",dev->number,vci); + continue; /* nasty but we try to go on anyway */ + /* @@@ nope, doesn't work */ + } + EVENT("getting from service\n",0,0); + if (ENI_VCC(vcc)->next != ENI_VCC_NOS) { + EVENT("double service\n",0,0); + DPRINTK("Grr, servicing VCC %ld twice\n",vci); + continue; + } + do_gettimeofday(&ENI_VCC(vcc)->timestamp); + ENI_VCC(vcc)->next = NULL; + if (vcc->qos.rxtp.traffic_class == ATM_CBR) { + if (eni_dev->fast) + ENI_VCC(eni_dev->last_fast)->next = vcc; + else eni_dev->fast = vcc; + eni_dev->last_fast = vcc; + } + else { + if (eni_dev->slow) + ENI_VCC(eni_dev->last_slow)->next = vcc; + else eni_dev->slow = vcc; + eni_dev->last_slow = vcc; + } +putting++; + ENI_VCC(vcc)->servicing++; + } +} + + +static void dequeue_rx(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + struct atm_vcc *vcc; + struct sk_buff *skb; + void __iomem *vci_dsc; + int first; + + eni_dev = ENI_DEV(dev); + first = 1; + while (1) { + skb = skb_dequeue(&eni_dev->rx_queue); + if (!skb) { + if (first) { + DPRINTK(DEV_LABEL "(itf %d): RX but not " + "rxing\n",dev->number); + EVENT("nothing to dequeue\n",0,0); + } + break; + } + EVENT("dequeued (size=%ld,pos=0x%lx)\n",ENI_PRV_SIZE(skb), + ENI_PRV_POS(skb)); +rx_dequeued++; + vcc = ATM_SKB(skb)->vcc; + eni_vcc = ENI_VCC(vcc); + first = 0; + vci_dsc = eni_dev->vci+vcc->vci*16; + if (!EEPMOK(eni_vcc->rx_pos,ENI_PRV_SIZE(skb), + (readl(vci_dsc+4) & MID_VCI_READ) >> MID_VCI_READ_SHIFT, + eni_vcc->words)) { + EVENT("requeuing\n",0,0); + skb_queue_head(&eni_dev->rx_queue,skb); + break; + } + eni_vcc->rxing--; + eni_vcc->rx_pos = ENI_PRV_POS(skb) & (eni_vcc->words-1); + pci_unmap_single(eni_dev->pci_dev,ENI_PRV_PADDR(skb),skb->len, + PCI_DMA_TODEVICE); + if (!skb->len) dev_kfree_skb_irq(skb); + else { + EVENT("pushing (len=%ld)\n",skb->len,0); + if (vcc->qos.aal == ATM_AAL0) + *(unsigned long *) skb->data = + ntohl(*(unsigned long *) skb->data); + memset(skb->cb,0,sizeof(struct eni_skb_prv)); + vcc->push(vcc,skb); + pushed++; + } + atomic_inc(&vcc->stats->rx); + } + wake_up(&eni_dev->rx_wait); +} + + +static int open_rx_first(struct atm_vcc *vcc) +{ + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + unsigned long size; + + DPRINTK("open_rx_first\n"); + eni_dev = ENI_DEV(vcc->dev); + eni_vcc = ENI_VCC(vcc); + eni_vcc->rx = NULL; + if (vcc->qos.rxtp.traffic_class == ATM_NONE) return 0; + size = vcc->qos.rxtp.max_sdu*eni_dev->rx_mult/100; + if (size > MID_MAX_BUF_SIZE && vcc->qos.rxtp.max_sdu <= + MID_MAX_BUF_SIZE) + size = MID_MAX_BUF_SIZE; + eni_vcc->recv = eni_alloc_mem(eni_dev,&size); + DPRINTK("rx at 0x%lx\n",eni_vcc->recv); + eni_vcc->words = size >> 2; + if (!eni_vcc->recv) return -ENOBUFS; + eni_vcc->rx = vcc->qos.aal == ATM_AAL5 ? rx_aal5 : rx_aal0; + eni_vcc->descr = 0; + eni_vcc->rx_pos = 0; + eni_vcc->rxing = 0; + eni_vcc->servicing = 0; + eni_vcc->next = ENI_VCC_NOS; + return 0; +} + + +static int open_rx_second(struct atm_vcc *vcc) +{ + void __iomem *here; + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + unsigned long size; + int order; + + DPRINTK("open_rx_second\n"); + eni_dev = ENI_DEV(vcc->dev); + eni_vcc = ENI_VCC(vcc); + if (!eni_vcc->rx) return 0; + /* set up VCI descriptor */ + here = eni_dev->vci+vcc->vci*16; + DPRINTK("loc 0x%x\n",(unsigned) (eni_vcc->recv-eni_dev->ram)/4); + size = eni_vcc->words >> 8; + for (order = -1; size; order++) size >>= 1; + writel(0,here+4); /* descr, read = 0 */ + writel(0,here+8); /* write, state, count = 0 */ + if (eni_dev->rx_map[vcc->vci]) + printk(KERN_CRIT DEV_LABEL "(itf %d): BUG - VCI %d already " + "in use\n",vcc->dev->number,vcc->vci); + eni_dev->rx_map[vcc->vci] = vcc; /* now it counts */ + writel(((vcc->qos.aal != ATM_AAL5 ? MID_MODE_RAW : MID_MODE_AAL5) << + MID_VCI_MODE_SHIFT) | MID_VCI_PTI_MODE | + (((eni_vcc->recv-eni_dev->ram) >> (MID_LOC_SKIP+2)) << + MID_VCI_LOCATION_SHIFT) | (order << MID_VCI_SIZE_SHIFT),here); + return 0; +} + + +static void close_rx(struct atm_vcc *vcc) +{ + DECLARE_WAITQUEUE(wait,current); + void __iomem *here; + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + + eni_vcc = ENI_VCC(vcc); + if (!eni_vcc->rx) return; + eni_dev = ENI_DEV(vcc->dev); + if (vcc->vpi != ATM_VPI_UNSPEC && vcc->vci != ATM_VCI_UNSPEC) { + here = eni_dev->vci+vcc->vci*16; + /* block receiver */ + writel((readl(here) & ~MID_VCI_MODE) | (MID_MODE_TRASH << + MID_VCI_MODE_SHIFT),here); + /* wait for receiver to become idle */ + udelay(27); + /* discard pending cell */ + writel(readl(here) & ~MID_VCI_IN_SERVICE,here); + /* don't accept any new ones */ + eni_dev->rx_map[vcc->vci] = NULL; + /* wait for RX queue to drain */ + DPRINTK("eni_close: waiting for RX ...\n"); + EVENT("RX closing\n",0,0); + add_wait_queue(&eni_dev->rx_wait,&wait); + set_current_state(TASK_UNINTERRUPTIBLE); + barrier(); + for (;;) { + /* transition service->rx: rxing++, servicing-- */ + if (!eni_vcc->servicing) { + barrier(); + if (!eni_vcc->rxing) break; + } + EVENT("drain PDUs (rx %ld, serv %ld)\n",eni_vcc->rxing, + eni_vcc->servicing); + printk(KERN_INFO "%d+%d RX left\n",eni_vcc->servicing, + eni_vcc->rxing); + schedule(); + set_current_state(TASK_UNINTERRUPTIBLE); + } + for (;;) { + int at_end; + u32 tmp; + + tasklet_disable(&eni_dev->task); + tmp = readl(eni_dev->vci+vcc->vci*16+4) & MID_VCI_READ; + at_end = eni_vcc->rx_pos == tmp >> MID_VCI_READ_SHIFT; + tasklet_enable(&eni_dev->task); + if (at_end) break; + EVENT("drain discard (host 0x%lx, nic 0x%lx)\n", + eni_vcc->rx_pos,tmp); + printk(KERN_INFO "draining RX: host 0x%lx, nic 0x%x\n", + eni_vcc->rx_pos,tmp); + schedule(); + set_current_state(TASK_UNINTERRUPTIBLE); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&eni_dev->rx_wait,&wait); + } + eni_free_mem(eni_dev,eni_vcc->recv,eni_vcc->words << 2); + eni_vcc->rx = NULL; +} + + +static int start_rx(struct atm_dev *dev) +{ + struct eni_dev *eni_dev; + + eni_dev = ENI_DEV(dev); + eni_dev->rx_map = (struct atm_vcc **) get_zeroed_page(GFP_KERNEL); + if (!eni_dev->rx_map) { + printk(KERN_ERR DEV_LABEL "(itf %d): couldn't get free page\n", + dev->number); + free_page((unsigned long) eni_dev->free_list); + return -ENOMEM; + } + memset(eni_dev->rx_map,0,PAGE_SIZE); + eni_dev->rx_mult = DEFAULT_RX_MULT; + eni_dev->fast = eni_dev->last_fast = NULL; + eni_dev->slow = eni_dev->last_slow = NULL; + init_waitqueue_head(&eni_dev->rx_wait); + skb_queue_head_init(&eni_dev->rx_queue); + eni_dev->serv_read = eni_in(MID_SERV_WRITE); + eni_out(0,MID_DMA_WR_RX); + return 0; +} + + +/*----------------------------------- TX ------------------------------------*/ + + +enum enq_res { enq_ok,enq_next,enq_jam }; + + +static inline void put_dma(int chan,u32 *dma,int *j,dma_addr_t paddr, + u32 size) +{ + u32 init,words; + + DPRINTK("put_dma: 0x%lx+0x%x\n",(unsigned long) paddr,size); + EVENT("put_dma: 0x%lx+0x%lx\n",(unsigned long) paddr,size); +#if 0 /* don't complain anymore */ + if (paddr & 3) + printk(KERN_ERR "put_dma: unaligned addr (0x%lx)\n",paddr); + if (size & 3) + printk(KERN_ERR "put_dma: unaligned size (0x%lx)\n",size); +#endif + if (paddr & 3) { + init = 4-(paddr & 3); + if (init > size || size < 7) init = size; + DPRINTK("put_dma: %lx DMA: %d/%d bytes\n", + (unsigned long) paddr,init,size); + dma[(*j)++] = MID_DT_BYTE | (init << MID_DMA_COUNT_SHIFT) | + (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += init; + size -= init; + } + words = size >> 2; + size &= 3; + if (words && (paddr & 31)) { + init = 8-((paddr & 31) >> 2); + if (init > words) init = words; + DPRINTK("put_dma: %lx DMA: %d/%d words\n", + (unsigned long) paddr,init,words); + dma[(*j)++] = MID_DT_WORD | (init << MID_DMA_COUNT_SHIFT) | + (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += init << 2; + words -= init; + } +#ifdef CONFIG_ATM_ENI_BURST_TX_16W /* may work with some PCI chipsets ... */ + if (words & ~15) { + DPRINTK("put_dma: %lx DMA: %d*16/%d words\n", + (unsigned long) paddr,words >> 4,words); + dma[(*j)++] = MID_DT_16W | ((words >> 4) << MID_DMA_COUNT_SHIFT) + | (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += (words & ~15) << 2; + words &= 15; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_8W /* recommended */ + if (words & ~7) { + DPRINTK("put_dma: %lx DMA: %d*8/%d words\n", + (unsigned long) paddr,words >> 3,words); + dma[(*j)++] = MID_DT_8W | ((words >> 3) << MID_DMA_COUNT_SHIFT) + | (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += (words & ~7) << 2; + words &= 7; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_4W /* probably useless if TX_8W or TX_16W */ + if (words & ~3) { + DPRINTK("put_dma: %lx DMA: %d*4/%d words\n", + (unsigned long) paddr,words >> 2,words); + dma[(*j)++] = MID_DT_4W | ((words >> 2) << MID_DMA_COUNT_SHIFT) + | (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += (words & ~3) << 2; + words &= 3; + } +#endif +#ifdef CONFIG_ATM_ENI_BURST_TX_2W /* probably useless if TX_4W, TX_8W, ... */ + if (words & ~1) { + DPRINTK("put_dma: %lx DMA: %d*2/%d words\n", + (unsigned long) paddr,words >> 1,words); + dma[(*j)++] = MID_DT_2W | ((words >> 1) << MID_DMA_COUNT_SHIFT) + | (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += (words & ~1) << 2; + words &= 1; + } +#endif + if (words) { + DPRINTK("put_dma: %lx DMA: %d words\n",(unsigned long) paddr, + words); + dma[(*j)++] = MID_DT_WORD | (words << MID_DMA_COUNT_SHIFT) | + (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + paddr += words << 2; + } + if (size) { + DPRINTK("put_dma: %lx DMA: %d bytes\n",(unsigned long) paddr, + size); + dma[(*j)++] = MID_DT_BYTE | (size << MID_DMA_COUNT_SHIFT) | + (chan << MID_DMA_CHAN_SHIFT); + dma[(*j)++] = paddr; + } +} + + +static enum enq_res do_tx(struct sk_buff *skb) +{ + struct atm_vcc *vcc; + struct eni_dev *eni_dev; + struct eni_vcc *eni_vcc; + struct eni_tx *tx; + dma_addr_t paddr; + u32 dma_rd,dma_wr; + u32 size; /* in words */ + int aal5,dma_size,i,j; + + DPRINTK(">do_tx\n"); + NULLCHECK(skb); + EVENT("do_tx: skb=0x%lx, %ld bytes\n",(unsigned long) skb,skb->len); + vcc = ATM_SKB(skb)->vcc; + NULLCHECK(vcc); + eni_dev = ENI_DEV(vcc->dev); + NULLCHECK(eni_dev); + eni_vcc = ENI_VCC(vcc); + tx = eni_vcc->tx; + NULLCHECK(tx); +#if 0 /* Enable this for testing with the "align" program */ + { + unsigned int hack = *((char *) skb->data)-'0'; + + if (hack < 8) { + skb->data += hack; + skb->len -= hack; + } + } +#endif +#if 0 /* should work now */ + if ((unsigned long) skb->data & 3) + printk(KERN_ERR DEV_LABEL "(itf %d): VCI %d has mis-aligned " + "TX data\n",vcc->dev->number,vcc->vci); +#endif + /* + * Potential future IP speedup: make hard_header big enough to put + * segmentation descriptor directly into PDU. Saves: 4 slave writes, + * 1 DMA xfer & 2 DMA'ed bytes (protocol layering is for wimps :-) + */ + + aal5 = vcc->qos.aal == ATM_AAL5; + /* check space in buffer */ + if (!aal5) + size = (ATM_CELL_PAYLOAD >> 2)+TX_DESCR_SIZE; + /* cell without HEC plus segmentation header (includes + four-byte cell header) */ + else { + size = skb->len+4*AAL5_TRAILER+ATM_CELL_PAYLOAD-1; + /* add AAL5 trailer */ + size = ((size-(size % ATM_CELL_PAYLOAD)) >> 2)+TX_DESCR_SIZE; + /* add segmentation header */ + } + /* + * Can I move tx_pos by size bytes without getting closer than TX_GAP + * to the read pointer ? TX_GAP means to leave some space for what + * the manual calls "too close". + */ + if (!NEPMOK(tx->tx_pos,size+TX_GAP, + eni_in(MID_TX_RDPTR(tx->index)),tx->words)) { + DPRINTK(DEV_LABEL "(itf %d): TX full (size %d)\n", + vcc->dev->number,size); + return enq_next; + } + /* check DMA */ + dma_wr = eni_in(MID_DMA_WR_TX); + dma_rd = eni_in(MID_DMA_RD_TX); + dma_size = 3; /* JK for descriptor and final fill, plus final size + mis-alignment fix */ +DPRINTK("iovcnt = %d\n",skb_shinfo(skb)->nr_frags); + if (!skb_shinfo(skb)->nr_frags) dma_size += 5; + else dma_size += 5*(skb_shinfo(skb)->nr_frags+1); + if (dma_size > TX_DMA_BUF) { + printk(KERN_CRIT DEV_LABEL "(itf %d): needs %d DMA entries " + "(got only %d)\n",vcc->dev->number,dma_size,TX_DMA_BUF); + } + DPRINTK("dma_wr is %d, tx_pos is %ld\n",dma_wr,tx->tx_pos); + if (dma_wr != dma_rd && ((dma_rd+NR_DMA_TX-dma_wr) & (NR_DMA_TX-1)) < + dma_size) { + printk(KERN_WARNING DEV_LABEL "(itf %d): TX DMA full\n", + vcc->dev->number); + return enq_jam; + } + paddr = pci_map_single(eni_dev->pci_dev,skb->data,skb->len, + PCI_DMA_TODEVICE); + ENI_PRV_PADDR(skb) = paddr; + /* prepare DMA queue entries */ + j = 0; + eni_dev->dma[j++] = (((tx->tx_pos+TX_DESCR_SIZE) & (tx->words-1)) << + MID_DM |