cregit-Linux how code gets into the kernel

Release 4.11 drivers/atm/eni.c

Directory: drivers/atm
/* drivers/atm/eni.c - Efficient Networks ENI155P device driver */
 
/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
 

#include <linux/module.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 <linux/slab.h>
#include <asm/io.h>
#include <linux/atomic.h>
#include <linux/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)
 */


#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) { }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)7100.00%1100.00%
Total7100.00%1100.00%

#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; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)50100.00%1100.00%
Total50100.00%1100.00%


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]); } }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)68100.00%1100.00%
Total68100.00%1100.00%

#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; /* 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); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)5698.25%150.00%
Chas Williams11.75%150.00%
Total57100.00%2100.00%


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"); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)17398.86%150.00%
Chas Williams21.14%150.00%
Total175100.00%2100.00%


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);*/ }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)16892.82%150.00%
Chas Williams137.18%150.00%
Total181100.00%2100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)26797.09%133.33%
Chas Williams62.18%133.33%
Al Viro20.73%133.33%
Total275100.00%3100.00%


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);*/ }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)22488.54%150.00%
Chas Williams2911.46%150.00%
Total253100.00%2100.00%

/*----------------------------------- 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); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)18699.47%150.00%
Chas Williams10.53%150.00%
Total187100.00%2100.00%


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 = dma_map_single(&eni_dev->pci_dev->dev,skb->data,skb->len, DMA_FROM_DEVICE); if (dma_mapping_error(&eni_dev->pci_dev->dev, paddr)) goto dma_map_error; 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) dma_unmap_single(&eni_dev->pci_dev->dev,paddr,skb->len, DMA_FROM_DEVICE); dma_map_error: if (skb) dev_kfree_skb_irq(skb); return -1; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)91396.92%250.00%
Tina Johnson151.59%125.00%
Chas Williams141.49%125.00%
Total942100.00%4100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)101100.00%1100.00%
Total101100.00%1100.00%

/* * 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->tstamp = 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; }

Contributors

PersonTokensPropCommitsCommitProp
Linus Torvalds (pre-git)20798.57%375.00%
Hideaki Yoshifuji / 吉藤英明31.43%125.00%
Total210100.00%4100.00%


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(&