Release 4.11 drivers/atm/eni.c
/* 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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 7 | 100.00% | 1 | 100.00% |
Total | 7 | 100.00% | 1 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 50 | 100.00% | 1 | 100.00% |
Total | 50 | 100.00% | 1 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 68 | 100.00% | 1 | 100.00% |
Total | 68 | 100.00% | 1 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 56 | 98.25% | 1 | 50.00% |
Chas Williams | 1 | 1.75% | 1 | 50.00% |
Total | 57 | 100.00% | 2 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 173 | 98.86% | 1 | 50.00% |
Chas Williams | 2 | 1.14% | 1 | 50.00% |
Total | 175 | 100.00% | 2 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 168 | 92.82% | 1 | 50.00% |
Chas Williams | 13 | 7.18% | 1 | 50.00% |
Total | 181 | 100.00% | 2 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 267 | 97.09% | 1 | 33.33% |
Chas Williams | 6 | 2.18% | 1 | 33.33% |
Al Viro | 2 | 0.73% | 1 | 33.33% |
Total | 275 | 100.00% | 3 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 224 | 88.54% | 1 | 50.00% |
Chas Williams | 29 | 11.46% | 1 | 50.00% |
Total | 253 | 100.00% | 2 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 186 | 99.47% | 1 | 50.00% |
Chas Williams | 1 | 0.53% | 1 | 50.00% |
Total | 187 | 100.00% | 2 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 913 | 96.92% | 2 | 50.00% |
Tina Johnson | 15 | 1.59% | 1 | 25.00% |
Chas Williams | 14 | 1.49% | 1 | 25.00% |
Total | 942 | 100.00% | 4 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 101 | 100.00% | 1 | 100.00% |
Total | 101 | 100.00% | 1 | 100.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
Person | Tokens | Prop | Commits | CommitProp |
Linus Torvalds (pre-git) | 207 | 98.57% | 3 | 75.00% |
Hideaki Yoshifuji / 吉藤英明 | 3 | 1.43% | 1 | 25.00% |
Total | 210 | 100.00% | 4 | 100.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(&