mirror of
https://github.com/torvalds/linux.git
synced 2024-11-14 16:12:02 +00:00
d9ca676bcb
The ATM subsystem was incorrectly creating the 'device' link for ATM nodes in sysfs. This led to incorrect device/parent relationships exposed by sysfs and udev. Instead of rolling the 'device' link by hand in the generic ATM code, pass each ATM driver's bus device down to the sysfs code and let sysfs do this stuff correctly. Signed-off-by: Dan Williams <dcbw@redhat.com> Signed-off-by: David S. Miller <davem@davemloft.net>
2311 lines
61 KiB
C
2311 lines
61 KiB
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/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)
|
|
*/
|
|
|
|
|
|
#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->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;
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
ENI_VCC(vcc)->timestamp = ktime_get_real();
|
|
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;
|
|
}
|
|
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_DMA_COUNT_SHIFT) | (tx->index << MID_DMA_CHAN_SHIFT) |
|
|
MID_DT_JK;
|
|
j++;
|
|
if (!skb_shinfo(skb)->nr_frags)
|
|
if (aal5) put_dma(tx->index,eni_dev->dma,&j,paddr,skb->len);
|
|
else put_dma(tx->index,eni_dev->dma,&j,paddr+4,skb->len-4);
|
|
else {
|
|
DPRINTK("doing direct send\n"); /* @@@ well, this doesn't work anyway */
|
|
for (i = -1; i < skb_shinfo(skb)->nr_frags; i++)
|
|
if (i == -1)
|
|
put_dma(tx->index,eni_dev->dma,&j,(unsigned long)
|
|
skb->data,
|
|
skb_headlen(skb));
|
|
else
|
|
put_dma(tx->index,eni_dev->dma,&j,(unsigned long)
|
|
skb_shinfo(skb)->frags[i].page + skb_shinfo(skb)->frags[i].page_offset,
|
|
skb_shinfo(skb)->frags[i].size);
|
|
}
|
|
if (skb->len & 3)
|
|
put_dma(tx->index,eni_dev->dma,&j,zeroes,4-(skb->len & 3));
|
|
/* JK for AAL5 trailer - AAL0 doesn't need it, but who cares ... */
|
|
eni_dev->dma[j++] = (((tx->tx_pos+size) & (tx->words-1)) <<
|
|
MID_DMA_COUNT_SHIFT) | (tx->index << MID_DMA_CHAN_SHIFT) |
|
|
MID_DMA_END | MID_DT_JK;
|
|
j++;
|
|
DPRINTK("DMA at end: %d\n",j);
|
|
/* store frame */
|
|
writel((MID_SEG_TX_ID << MID_SEG_ID_SHIFT) |
|
|
(aal5 ? MID_SEG_AAL5 : 0) | (tx->prescaler << MID_SEG_PR_SHIFT) |
|
|
(tx->resolution << MID_SEG_RATE_SHIFT) |
|
|
(size/(ATM_CELL_PAYLOAD/4)),tx->send+tx->tx_pos*4);
|
|
/*printk("dsc = 0x%08lx\n",(unsigned long) readl(tx->send+tx->tx_pos*4));*/
|
|
writel((vcc->vci << MID_SEG_VCI_SHIFT) |
|
|
(aal5 ? 0 : (skb->data[3] & 0xf)) |
|
|
(ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ? MID_SEG_CLP : 0),
|
|
tx->send+((tx->tx_pos+1) & (tx->words-1))*4);
|
|
DPRINTK("size: %d, len:%d\n",size,skb->len);
|
|
if (aal5)
|
|
writel(skb->len,tx->send+
|
|
((tx->tx_pos+size-AAL5_TRAILER) & (tx->words-1))*4);
|
|
j = j >> 1;
|
|
for (i = 0; i < j; i++) {
|
|
writel(eni_dev->dma[i*2],eni_dev->tx_dma+dma_wr*8);
|
|
writel(eni_dev->dma[i*2+1],eni_dev->tx_dma+dma_wr*8+4);
|
|
dma_wr = (dma_wr+1) & (NR_DMA_TX-1);
|
|
}
|
|
ENI_PRV_POS(skb) = tx->tx_pos;
|
|
ENI_PRV_SIZE(skb) = size;
|
|
ENI_VCC(vcc)->txing += size;
|
|
tx->tx_pos = (tx->tx_pos+size) & (tx->words-1);
|
|
DPRINTK("dma_wr set to %d, tx_pos is now %ld\n",dma_wr,tx->tx_pos);
|
|
eni_out(dma_wr,MID_DMA_WR_TX);
|
|
skb_queue_tail(&eni_dev->tx_queue,skb);
|
|
queued++;
|
|
return enq_ok;
|
|
}
|
|
|
|
|
|
static void poll_tx(struct atm_dev *dev)
|
|
{
|
|
struct eni_tx *tx;
|
|
struct sk_buff *skb;
|
|
enum enq_res res;
|
|
int i;
|
|
|
|
DPRINTK(">poll_tx\n");
|
|
for (i = NR_CHAN-1; i >= 0; i--) {
|
|
tx = &ENI_DEV(dev)->tx[i];
|
|
if (tx->send)
|
|
while ((skb = skb_dequeue(&tx->backlog))) {
|
|
res = do_tx(skb);
|
|
if (res == enq_ok) continue;
|
|
DPRINTK("re-queuing TX PDU\n");
|
|
skb_queue_head(&tx->backlog,skb);
|
|
requeued++;
|
|
if (res == enq_jam) return;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void dequeue_tx(struct atm_dev *dev)
|
|
{
|
|
struct eni_dev *eni_dev;
|
|
struct atm_vcc *vcc;
|
|
struct sk_buff *skb;
|
|
struct eni_tx *tx;
|
|
|
|
NULLCHECK(dev);
|
|
eni_dev = ENI_DEV(dev);
|
|
NULLCHECK(eni_dev);
|
|
while ((skb = skb_dequeue(&eni_dev->tx_queue))) {
|
|
vcc = ATM_SKB(skb)->vcc;
|
|
NULLCHECK(vcc);
|
|
tx = ENI_VCC(vcc)->tx;
|
|
NULLCHECK(ENI_VCC(vcc)->tx);
|
|
DPRINTK("dequeue_tx: next 0x%lx curr 0x%x\n",ENI_PRV_POS(skb),
|
|
(unsigned) eni_in(MID_TX_DESCRSTART(tx->index)));
|
|
if (ENI_VCC(vcc)->txing < tx->words && ENI_PRV_POS(skb) ==
|
|
eni_in(MID_TX_DESCRSTART(tx->index))) {
|
|
skb_queue_head(&eni_dev->tx_queue,skb);
|
|
break;
|
|
}
|
|
ENI_VCC(vcc)->txing -= ENI_PRV_SIZE(skb);
|
|
pci_unmap_single(eni_dev->pci_dev,ENI_PRV_PADDR(skb),skb->len,
|
|
PCI_DMA_TODEVICE);
|
|
if (vcc->pop) vcc->pop(vcc,skb);
|
|
else dev_kfree_skb_irq(skb);
|
|
atomic_inc(&vcc->stats->tx);
|
|
wake_up(&eni_dev->tx_wait);
|
|
dma_complete++;
|
|
}
|
|
}
|
|
|
|
|
|
static struct eni_tx *alloc_tx(struct eni_dev *eni_dev,int ubr)
|
|
{
|
|
int i;
|
|
|
|
for (i = !ubr; i < NR_CHAN; i++)
|
|
if (!eni_dev->tx[i].send) return eni_dev->tx+i;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int comp_tx(struct eni_dev *eni_dev,int *pcr,int reserved,int *pre,
|
|
int *res,int unlimited)
|
|
{
|
|
static const int pre_div[] = { 4,16,128,2048 };
|
|
/* 2^(((x+2)^2-(x+2))/2+1) */
|
|
|
|
if (unlimited) *pre = *res = 0;
|
|
else {
|
|
if (*pcr > 0) {
|
|
int div;
|
|
|
|
for (*pre = 0; *pre < 3; (*pre)++)
|
|
if (TS_CLOCK/pre_div[*pre]/64 <= *pcr) break;
|
|
div = pre_div[*pre]**pcr;
|
|
DPRINTK("min div %d\n",div);
|
|
*res = TS_CLOCK/div-1;
|
|
}
|
|
else {
|
|
int div;
|
|
|
|
if (!*pcr) *pcr = eni_dev->tx_bw+reserved;
|
|
for (*pre = 3; *pre >= 0; (*pre)--)
|
|
if (TS_CLOCK/pre_div[*pre]/64 > -*pcr) break;
|
|
if (*pre < 3) (*pre)++; /* else fail later */
|
|
div = pre_div[*pre]*-*pcr;
|
|
DPRINTK("max div %d\n",div);
|
|
*res = DIV_ROUND_UP(TS_CLOCK, div)-1;
|
|
}
|
|
if (*res < 0) *res = 0;
|
|
if (*res > MID_SEG_MAX_RATE) *res = MID_SEG_MAX_RATE;
|
|
}
|
|
*pcr = TS_CLOCK/pre_div[*pre]/(*res+1);
|
|
DPRINTK("out pcr: %d (%d:%d)\n",*pcr,*pre,*res);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int reserve_or_set_tx(struct atm_vcc *vcc,struct atm_trafprm *txtp,
|
|
int set_rsv,int set_shp)
|
|
{
|
|
struct eni_dev *eni_dev = ENI_DEV(vcc->dev);
|
|
struct eni_vcc *eni_vcc = ENI_VCC(vcc);
|
|
struct eni_tx *tx;
|
|
unsigned long size;
|
|
void __iomem *mem;
|
|
int rate,ubr,unlimited,new_tx;
|
|
int pre,res,order;
|
|
int error;
|
|
|
|
rate = atm_pcr_goal(txtp);
|
|
ubr = txtp->traffic_class == ATM_UBR;
|
|
unlimited = ubr && (!rate || rate <= -ATM_OC3_PCR ||
|
|
rate >= ATM_OC3_PCR);
|
|
if (!unlimited) {
|
|
size = txtp->max_sdu*eni_dev->tx_mult/100;
|
|
if (size > MID_MAX_BUF_SIZE && txtp->max_sdu <=
|
|
MID_MAX_BUF_SIZE)
|
|
size = MID_MAX_BUF_SIZE;
|
|
}
|
|
else {
|
|
if (eni_dev->ubr) {
|
|
eni_vcc->tx = eni_dev->ubr;
|
|
txtp->pcr = ATM_OC3_PCR;
|
|
return 0;
|
|
}
|
|
size = UBR_BUFFER;
|
|
}
|
|
new_tx = !eni_vcc->tx;
|
|
mem = NULL; /* for gcc */
|
|
if (!new_tx) tx = eni_vcc->tx;
|
|
else {
|
|
mem = eni_alloc_mem(eni_dev,&size);
|
|
if (!mem) return -ENOBUFS;
|
|
tx = alloc_tx(eni_dev,unlimited);
|
|
if (!tx) {
|
|
eni_free_mem(eni_dev,mem,size);
|
|
return -EBUSY;
|
|
}
|
|
DPRINTK("got chan %d\n",tx->index);
|
|
tx->reserved = tx->shaping = 0;
|
|
tx->send = mem;
|
|
tx->words = size >> 2;
|
|
skb_queue_head_init(&tx->backlog);
|
|
for (order = 0; size > (1 << (order+10)); order++);
|
|
eni_out((order << MID_SIZE_SHIFT) |
|
|
((tx->send-eni_dev->ram) >> (MID_LOC_SKIP+2)),
|
|
MID_TX_PLACE(tx->index));
|
|
tx->tx_pos = eni_in(MID_TX_DESCRSTART(tx->index)) &
|
|
MID_DESCR_START;
|
|
}
|
|
error = comp_tx(eni_dev,&rate,tx->reserved,&pre,&res,unlimited);
|
|
if (!error && txtp->min_pcr > rate) error = -EINVAL;
|
|
if (!error && txtp->max_pcr && txtp->max_pcr != ATM_MAX_PCR &&
|
|
txtp->max_pcr < rate) error = -EINVAL;
|
|
if (!error && !ubr && rate > eni_dev->tx_bw+tx->reserved)
|
|
error = -EINVAL;
|
|
if (!error && set_rsv && !set_shp && rate < tx->shaping)
|
|
error = -EINVAL;
|
|
if (!error && !set_rsv && rate > tx->reserved && !ubr)
|
|
error = -EINVAL;
|
|
if (error) {
|
|
if (new_tx) {
|
|
tx->send = NULL;
|
|
eni_free_mem(eni_dev,mem,size);
|
|
}
|
|
return error;
|
|
}
|
|
txtp->pcr = rate;
|
|
if (set_rsv && !ubr) {
|
|
eni_dev->tx_bw += tx->reserved;
|
|
tx->reserved = rate;
|
|
eni_dev->tx_bw -= rate;
|
|
}
|
|
if (set_shp || (unlimited && new_tx)) {
|
|
if (unlimited && new_tx) eni_dev->ubr = tx;
|
|
tx->prescaler = pre;
|
|
tx->resolution = res;
|
|
tx->shaping = rate;
|
|
}
|
|
if (set_shp) eni_vcc->tx = tx;
|
|
DPRINTK("rsv %d shp %d\n",tx->reserved,tx->shaping);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int open_tx_first(struct atm_vcc *vcc)
|
|
{
|
|
ENI_VCC(vcc)->tx = NULL;
|
|
if (vcc->qos.txtp.traffic_class == ATM_NONE) return 0;
|
|
ENI_VCC(vcc)->txing = 0;
|
|
return reserve_or_set_tx(vcc,&vcc->qos.txtp,1,1);
|
|
}
|
|
|
|
|
|
static int open_tx_second(struct atm_vcc *vcc)
|
|
{
|
|
return 0; /* nothing to do */
|
|
}
|
|
|
|
|
|
static void close_tx(struct atm_vcc *vcc)
|
|
{
|
|
DECLARE_WAITQUEUE(wait,current);
|
|
struct eni_dev *eni_dev;
|
|
struct eni_vcc *eni_vcc;
|
|
|
|
eni_vcc = ENI_VCC(vcc);
|
|
if (!eni_vcc->tx) return;
|
|
eni_dev = ENI_DEV(vcc->dev);
|
|
/* wait for TX queue to drain */
|
|
DPRINTK("eni_close: waiting for TX ...\n");
|
|
add_wait_queue(&eni_dev->tx_wait,&wait);
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
for (;;) {
|
|
int txing;
|
|
|
|
tasklet_disable(&eni_dev->task);
|
|
txing = skb_peek(&eni_vcc->tx->backlog) || eni_vcc->txing;
|
|
tasklet_enable(&eni_dev->task);
|
|
if (!txing) break;
|
|
DPRINTK("%d TX left\n",eni_vcc->txing);
|
|
schedule();
|
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
|
}
|
|
set_current_state(TASK_RUNNING);
|
|
remove_wait_queue(&eni_dev->tx_wait,&wait);
|
|
if (eni_vcc->tx != eni_dev->ubr) {
|
|
/*
|
|
* Looping a few times in here is probably far cheaper than
|
|
* keeping track of TX completions all the time, so let's poll
|
|
* a bit ...
|
|
*/
|
|
while (eni_in(MID_TX_RDPTR(eni_vcc->tx->index)) !=
|
|
eni_in(MID_TX_DESCRSTART(eni_vcc->tx->index)))
|
|
schedule();
|
|
eni_free_mem(eni_dev,eni_vcc->tx->send,eni_vcc->tx->words << 2);
|
|
eni_vcc->tx->send = NULL;
|
|
eni_dev->tx_bw += eni_vcc->tx->reserved;
|
|
}
|
|
eni_vcc->tx = NULL;
|
|
}
|
|
|
|
|
|
static int start_tx(struct atm_dev *dev)
|
|
{
|
|
struct eni_dev *eni_dev;
|
|
int i;
|
|
|
|
eni_dev = ENI_DEV(dev);
|
|
eni_dev->lost = 0;
|
|
eni_dev->tx_bw = ATM_OC3_PCR;
|
|
eni_dev->tx_mult = DEFAULT_TX_MULT;
|
|
init_waitqueue_head(&eni_dev->tx_wait);
|
|
eni_dev->ubr = NULL;
|
|
skb_queue_head_init(&eni_dev->tx_queue);
|
|
eni_out(0,MID_DMA_WR_TX);
|
|
for (i = 0; i < NR_CHAN; i++) {
|
|
eni_dev->tx[i].send = NULL;
|
|
eni_dev->tx[i].index = i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*--------------------------------- common ----------------------------------*/
|
|
|
|
|
|
#if 0 /* may become useful again when tuning things */
|
|
|
|
static void foo(void)
|
|
{
|
|
printk(KERN_INFO
|
|
"tx_complete=%d,dma_complete=%d,queued=%d,requeued=%d,sub=%d,\n"
|
|
"backlogged=%d,rx_enqueued=%d,rx_dequeued=%d,putting=%d,pushed=%d\n",
|
|
tx_complete,dma_complete,queued,requeued,submitted,backlogged,
|
|
rx_enqueued,rx_dequeued,putting,pushed);
|
|
if (eni_boards) printk(KERN_INFO "loss: %ld\n",ENI_DEV(eni_boards)->lost);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
static void bug_int(struct atm_dev *dev,unsigned long reason)
|
|
{
|
|
struct eni_dev *eni_dev;
|
|
|
|
DPRINTK(">bug_int\n");
|
|
eni_dev = ENI_DEV(dev);
|
|
if (reason & MID_DMA_ERR_ACK)
|
|
printk(KERN_CRIT DEV_LABEL "(itf %d): driver error - DMA "
|
|
"error\n",dev->number);
|
|
if (reason & MID_TX_IDENT_MISM)
|
|
printk(KERN_CRIT DEV_LABEL "(itf %d): driver error - ident "
|
|
"mismatch\n",dev->number);
|
|
if (reason & MID_TX_DMA_OVFL)
|
|
printk(KERN_CRIT DEV_LABEL "(itf %d): driver error - DMA "
|
|
"overflow\n",dev->number);
|
|
EVENT("---dump ends here---\n",0,0);
|
|
printk(KERN_NOTICE "---recent events---\n");
|
|
event_dump();
|
|
}
|
|
|
|
|
|
static irqreturn_t eni_int(int irq,void *dev_id)
|
|
{
|
|
struct atm_dev *dev;
|
|
struct eni_dev *eni_dev;
|
|
u32 reason;
|
|
|
|
DPRINTK(">eni_int\n");
|
|
dev = dev_id;
|
|
eni_dev = ENI_DEV(dev);
|
|
reason = eni_in(MID_ISA);
|
|
DPRINTK(DEV_LABEL ": int 0x%lx\n",(unsigned long) reason);
|
|
/*
|
|
* Must handle these two right now, because reading ISA doesn't clear
|
|
* them, so they re-occur and we never make it to the tasklet. Since
|
|
* they're rare, we don't mind the occasional invocation of eni_tasklet
|
|
* with eni_dev->events == 0.
|
|
*/
|
|
if (reason & MID_STAT_OVFL) {
|
|
EVENT("stat overflow\n",0,0);
|
|
eni_dev->lost += eni_in(MID_STAT) & MID_OVFL_TRASH;
|
|
}
|
|
if (reason & MID_SUNI_INT) {
|
|
EVENT("SUNI int\n",0,0);
|
|
dev->phy->interrupt(dev);
|
|
#if 0
|
|
foo();
|
|
#endif
|
|
}
|
|
spin_lock(&eni_dev->lock);
|
|
eni_dev->events |= reason;
|
|
spin_unlock(&eni_dev->lock);
|
|
tasklet_schedule(&eni_dev->task);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
|
|
static void eni_tasklet(unsigned long data)
|
|
{
|
|
struct atm_dev *dev = (struct atm_dev *) data;
|
|
struct eni_dev *eni_dev = ENI_DEV(dev);
|
|
unsigned long flags;
|
|
u32 events;
|
|
|
|
DPRINTK("eni_tasklet (dev %p)\n",dev);
|
|
spin_lock_irqsave(&eni_dev->lock,flags);
|
|
events = xchg(&eni_dev->events,0);
|
|
spin_unlock_irqrestore(&eni_dev->lock,flags);
|
|
if (events & MID_RX_DMA_COMPLETE) {
|
|
EVENT("INT: RX DMA complete, starting dequeue_rx\n",0,0);
|
|
dequeue_rx(dev);
|
|
EVENT("dequeue_rx done, starting poll_rx\n",0,0);
|
|
poll_rx(dev);
|
|
EVENT("poll_rx done\n",0,0);
|
|
/* poll_tx ? */
|
|
}
|
|
if (events & MID_SERVICE) {
|
|
EVENT("INT: service, starting get_service\n",0,0);
|
|
get_service(dev);
|
|
EVENT("get_service done, starting poll_rx\n",0,0);
|
|
poll_rx(dev);
|
|
EVENT("poll_rx done\n",0,0);
|
|
}
|
|
if (events & MID_TX_DMA_COMPLETE) {
|
|
EVENT("INT: TX DMA COMPLETE\n",0,0);
|
|
dequeue_tx(dev);
|
|
}
|
|
if (events & MID_TX_COMPLETE) {
|
|
EVENT("INT: TX COMPLETE\n",0,0);
|
|
tx_complete++;
|
|
wake_up(&eni_dev->tx_wait);
|
|
/* poll_rx ? */
|
|
}
|
|
if (events & (MID_DMA_ERR_ACK | MID_TX_IDENT_MISM | MID_TX_DMA_OVFL)) {
|
|
EVENT("bug interrupt\n",0,0);
|
|
bug_int(dev,events);
|
|
}
|
|
poll_tx(dev);
|
|
}
|
|
|
|
|
|
/*--------------------------------- entries ---------------------------------*/
|
|
|
|
|
|
static const char *media_name[] __devinitdata = {
|
|
"MMF", "SMF", "MMF", "03?", /* 0- 3 */
|
|
"UTP", "05?", "06?", "07?", /* 4- 7 */
|
|
"TAXI","09?", "10?", "11?", /* 8-11 */
|
|
"12?", "13?", "14?", "15?", /* 12-15 */
|
|
"MMF", "SMF", "18?", "19?", /* 16-19 */
|
|
"UTP", "21?", "22?", "23?", /* 20-23 */
|
|
"24?", "25?", "26?", "27?", /* 24-27 */
|
|
"28?", "29?", "30?", "31?" /* 28-31 */
|
|
};
|
|
|
|
|
|
#define SET_SEPROM \
|
|
({ if (!error && !pci_error) { \
|
|
pci_error = pci_write_config_byte(eni_dev->pci_dev,PCI_TONGA_CTRL,tonga); \
|
|
udelay(10); /* 10 usecs */ \
|
|
} })
|
|
#define GET_SEPROM \
|
|
({ if (!error && !pci_error) { \
|
|
pci_error = pci_read_config_byte(eni_dev->pci_dev,PCI_TONGA_CTRL,&tonga); \
|
|
udelay(10); /* 10 usecs */ \
|
|
} })
|
|
|
|
|
|
static int __devinit get_esi_asic(struct atm_dev *dev)
|
|
{
|
|
struct eni_dev *eni_dev;
|
|
unsigned char tonga;
|
|
int error,failed,pci_error;
|
|
int address,i,j;
|
|
|
|
eni_dev = ENI_DEV(dev);
|
|
error = pci_error = 0;
|
|
tonga = SEPROM_MAGIC | SEPROM_DATA | SEPROM_CLK;
|
|
SET_SEPROM;
|
|
for (i = 0; i < ESI_LEN && !error && !pci_error; i++) {
|
|
/* start operation */
|
|
tonga |= SEPROM_DATA;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_CLK;
|
|
SET_SEPROM;
|
|
tonga &= ~SEPROM_DATA;
|
|
SET_SEPROM;
|
|
tonga &= ~SEPROM_CLK;
|
|
SET_SEPROM;
|
|
/* send address */
|
|
address = ((i+SEPROM_ESI_BASE) << 1)+1;
|
|
for (j = 7; j >= 0; j--) {
|
|
tonga = (address >> j) & 1 ? tonga | SEPROM_DATA :
|
|
tonga & ~SEPROM_DATA;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_CLK;
|
|
SET_SEPROM;
|
|
tonga &= ~SEPROM_CLK;
|
|
SET_SEPROM;
|
|
}
|
|
/* get ack */
|
|
tonga |= SEPROM_DATA;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_CLK;
|
|
SET_SEPROM;
|
|
GET_SEPROM;
|
|
failed = tonga & SEPROM_DATA;
|
|
tonga &= ~SEPROM_CLK;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_DATA;
|
|
SET_SEPROM;
|
|
if (failed) error = -EIO;
|
|
else {
|
|
dev->esi[i] = 0;
|
|
for (j = 7; j >= 0; j--) {
|
|
dev->esi[i] <<= 1;
|
|
tonga |= SEPROM_DATA;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_CLK;
|
|
SET_SEPROM;
|
|
GET_SEPROM;
|
|
if (tonga & SEPROM_DATA) dev->esi[i] |= 1;
|
|
tonga &= ~SEPROM_CLK;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_DATA;
|
|
SET_SEPROM;
|
|
}
|
|
/* get ack */
|
|
tonga |= SEPROM_DATA;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_CLK;
|
|
SET_SEPROM;
|
|
GET_SEPROM;
|
|
if (!(tonga & SEPROM_DATA)) error = -EIO;
|
|
tonga &= ~SEPROM_CLK;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_DATA;
|
|
SET_SEPROM;
|
|
}
|
|
/* stop operation */
|
|
tonga &= ~SEPROM_DATA;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_CLK;
|
|
SET_SEPROM;
|
|
tonga |= SEPROM_DATA;
|
|
SET_SEPROM;
|
|
}
|
|
if (pci_error) {
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): error reading ESI "
|
|
"(0x%02x)\n",dev->number,pci_error);
|
|
error = -EIO;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
#undef SET_SEPROM
|
|
#undef GET_SEPROM
|
|
|
|
|
|
static int __devinit get_esi_fpga(struct atm_dev *dev, void __iomem *base)
|
|
{
|
|
void __iomem *mac_base;
|
|
int i;
|
|
|
|
mac_base = base+EPROM_SIZE-sizeof(struct midway_eprom);
|
|
for (i = 0; i < ESI_LEN; i++) dev->esi[i] = readb(mac_base+(i^3));
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int __devinit eni_do_init(struct atm_dev *dev)
|
|
{
|
|
struct midway_eprom __iomem *eprom;
|
|
struct eni_dev *eni_dev;
|
|
struct pci_dev *pci_dev;
|
|
unsigned long real_base;
|
|
void __iomem *base;
|
|
int error,i,last;
|
|
|
|
DPRINTK(">eni_init\n");
|
|
dev->ci_range.vpi_bits = 0;
|
|
dev->ci_range.vci_bits = NR_VCI_LD;
|
|
dev->link_rate = ATM_OC3_PCR;
|
|
eni_dev = ENI_DEV(dev);
|
|
pci_dev = eni_dev->pci_dev;
|
|
real_base = pci_resource_start(pci_dev, 0);
|
|
eni_dev->irq = pci_dev->irq;
|
|
if ((error = pci_write_config_word(pci_dev,PCI_COMMAND,
|
|
PCI_COMMAND_MEMORY |
|
|
(eni_dev->asic ? PCI_COMMAND_PARITY | PCI_COMMAND_SERR : 0)))) {
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): can't enable memory "
|
|
"(0x%02x)\n",dev->number,error);
|
|
return -EIO;
|
|
}
|
|
printk(KERN_NOTICE DEV_LABEL "(itf %d): rev.%d,base=0x%lx,irq=%d,",
|
|
dev->number,pci_dev->revision,real_base,eni_dev->irq);
|
|
if (!(base = ioremap_nocache(real_base,MAP_MAX_SIZE))) {
|
|
printk("\n");
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): can't set up page "
|
|
"mapping\n",dev->number);
|
|
return error;
|
|
}
|
|
eni_dev->base_diff = real_base - (unsigned long) base;
|
|
/* id may not be present in ASIC Tonga boards - check this @@@ */
|
|
if (!eni_dev->asic) {
|
|
eprom = (base+EPROM_SIZE-sizeof(struct midway_eprom));
|
|
if (readl(&eprom->magic) != ENI155_MAGIC) {
|
|
printk("\n");
|
|
printk(KERN_ERR DEV_LABEL
|
|
"(itf %d): bad magic - expected 0x%x, got 0x%x\n",
|
|
dev->number, ENI155_MAGIC,
|
|
(unsigned)readl(&eprom->magic));
|
|
error = -EINVAL;
|
|
goto unmap;
|
|
}
|
|
}
|
|
eni_dev->phy = base+PHY_BASE;
|
|
eni_dev->reg = base+REG_BASE;
|
|
eni_dev->ram = base+RAM_BASE;
|
|
last = MAP_MAX_SIZE-RAM_BASE;
|
|
for (i = last-RAM_INCREMENT; i >= 0; i -= RAM_INCREMENT) {
|
|
writel(0x55555555,eni_dev->ram+i);
|
|
if (readl(eni_dev->ram+i) != 0x55555555) last = i;
|
|
else {
|
|
writel(0xAAAAAAAA,eni_dev->ram+i);
|
|
if (readl(eni_dev->ram+i) != 0xAAAAAAAA) last = i;
|
|
else writel(i,eni_dev->ram+i);
|
|
}
|
|
}
|
|
for (i = 0; i < last; i += RAM_INCREMENT)
|
|
if (readl(eni_dev->ram+i) != i) break;
|
|
eni_dev->mem = i;
|
|
memset_io(eni_dev->ram,0,eni_dev->mem);
|
|
/* TODO: should shrink allocation now */
|
|
printk("mem=%dkB (",eni_dev->mem >> 10);
|
|
/* TODO: check for non-SUNI, check for TAXI ? */
|
|
if (!(eni_in(MID_RES_ID_MCON) & 0x200) != !eni_dev->asic) {
|
|
printk(")\n");
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): ERROR - wrong id 0x%x\n",
|
|
dev->number,(unsigned) eni_in(MID_RES_ID_MCON));
|
|
error = -EINVAL;
|
|
goto unmap;
|
|
}
|
|
error = eni_dev->asic ? get_esi_asic(dev) : get_esi_fpga(dev,base);
|
|
if (error)
|
|
goto unmap;
|
|
for (i = 0; i < ESI_LEN; i++)
|
|
printk("%s%02X",i ? "-" : "",dev->esi[i]);
|
|
printk(")\n");
|
|
printk(KERN_NOTICE DEV_LABEL "(itf %d): %s,%s\n",dev->number,
|
|
eni_in(MID_RES_ID_MCON) & 0x200 ? "ASIC" : "FPGA",
|
|
media_name[eni_in(MID_RES_ID_MCON) & DAUGTHER_ID]);
|
|
|
|
error = suni_init(dev);
|
|
if (error)
|
|
goto unmap;
|
|
out:
|
|
return error;
|
|
unmap:
|
|
iounmap(base);
|
|
goto out;
|
|
}
|
|
|
|
|
|
static int __devinit eni_start(struct atm_dev *dev)
|
|
{
|
|
struct eni_dev *eni_dev;
|
|
|
|
void __iomem *buf;
|
|
unsigned long buffer_mem;
|
|
int error;
|
|
|
|
DPRINTK(">eni_start\n");
|
|
eni_dev = ENI_DEV(dev);
|
|
if (request_irq(eni_dev->irq,&eni_int,IRQF_SHARED,DEV_LABEL,dev)) {
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): IRQ%d is already in use\n",
|
|
dev->number,eni_dev->irq);
|
|
error = -EAGAIN;
|
|
goto out;
|
|
}
|
|
pci_set_master(eni_dev->pci_dev);
|
|
if ((error = pci_write_config_word(eni_dev->pci_dev,PCI_COMMAND,
|
|
PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER |
|
|
(eni_dev->asic ? PCI_COMMAND_PARITY | PCI_COMMAND_SERR : 0)))) {
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): can't enable memory+"
|
|
"master (0x%02x)\n",dev->number,error);
|
|
goto free_irq;
|
|
}
|
|
if ((error = pci_write_config_byte(eni_dev->pci_dev,PCI_TONGA_CTRL,
|
|
END_SWAP_DMA))) {
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): can't set endian swap "
|
|
"(0x%02x)\n",dev->number,error);
|
|
goto free_irq;
|
|
}
|
|
/* determine addresses of internal tables */
|
|
eni_dev->vci = eni_dev->ram;
|
|
eni_dev->rx_dma = eni_dev->ram+NR_VCI*16;
|
|
eni_dev->tx_dma = eni_dev->rx_dma+NR_DMA_RX*8;
|
|
eni_dev->service = eni_dev->tx_dma+NR_DMA_TX*8;
|
|
buf = eni_dev->service+NR_SERVICE*4;
|
|
DPRINTK("vci 0x%lx,rx 0x%lx, tx 0x%lx,srv 0x%lx,buf 0x%lx\n",
|
|
eni_dev->vci,eni_dev->rx_dma,eni_dev->tx_dma,
|
|
eni_dev->service,buf);
|
|
spin_lock_init(&eni_dev->lock);
|
|
tasklet_init(&eni_dev->task,eni_tasklet,(unsigned long) dev);
|
|
eni_dev->events = 0;
|
|
/* initialize memory management */
|
|
buffer_mem = eni_dev->mem - (buf - eni_dev->ram);
|
|
eni_dev->free_list_size = buffer_mem/MID_MIN_BUF_SIZE/2;
|
|
eni_dev->free_list = kmalloc(
|
|
sizeof(struct eni_free)*(eni_dev->free_list_size+1),GFP_KERNEL);
|
|
if (!eni_dev->free_list) {
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): couldn't get free page\n",
|
|
dev->number);
|
|
error = -ENOMEM;
|
|
goto free_irq;
|
|
}
|
|
eni_dev->free_len = 0;
|
|
eni_put_free(eni_dev,buf,buffer_mem);
|
|
memset_io(eni_dev->vci,0,16*NR_VCI); /* clear VCI table */
|
|
/*
|
|
* byte_addr free (k)
|
|
* 0x00000000 512 VCI table
|
|
* 0x00004000 496 RX DMA
|
|
* 0x00005000 492 TX DMA
|
|
* 0x00006000 488 service list
|
|
* 0x00007000 484 buffers
|
|
* 0x00080000 0 end (512kB)
|
|
*/
|
|
eni_out(0xffffffff,MID_IE);
|
|
error = start_tx(dev);
|
|
if (error) goto free_list;
|
|
error = start_rx(dev);
|
|
if (error) goto free_list;
|
|
error = dev->phy->start(dev);
|
|
if (error) goto free_list;
|
|
eni_out(eni_in(MID_MC_S) | (1 << MID_INT_SEL_SHIFT) |
|
|
MID_TX_LOCK_MODE | MID_DMA_ENABLE | MID_TX_ENABLE | MID_RX_ENABLE,
|
|
MID_MC_S);
|
|
/* Tonga uses SBus INTReq1 */
|
|
(void) eni_in(MID_ISA); /* clear Midway interrupts */
|
|
return 0;
|
|
|
|
free_list:
|
|
kfree(eni_dev->free_list);
|
|
|
|
free_irq:
|
|
free_irq(eni_dev->irq, eni_dev);
|
|
|
|
out:
|
|
return error;
|
|
}
|
|
|
|
|
|
static void eni_close(struct atm_vcc *vcc)
|
|
{
|
|
DPRINTK(">eni_close\n");
|
|
if (!ENI_VCC(vcc)) return;
|
|
clear_bit(ATM_VF_READY,&vcc->flags);
|
|
close_rx(vcc);
|
|
close_tx(vcc);
|
|
DPRINTK("eni_close: done waiting\n");
|
|
/* deallocate memory */
|
|
kfree(ENI_VCC(vcc));
|
|
vcc->dev_data = NULL;
|
|
clear_bit(ATM_VF_ADDR,&vcc->flags);
|
|
/*foo();*/
|
|
}
|
|
|
|
|
|
static int eni_open(struct atm_vcc *vcc)
|
|
{
|
|
struct eni_dev *eni_dev;
|
|
struct eni_vcc *eni_vcc;
|
|
int error;
|
|
short vpi = vcc->vpi;
|
|
int vci = vcc->vci;
|
|
|
|
DPRINTK(">eni_open\n");
|
|
EVENT("eni_open\n",0,0);
|
|
if (!test_bit(ATM_VF_PARTIAL,&vcc->flags))
|
|
vcc->dev_data = NULL;
|
|
eni_dev = ENI_DEV(vcc->dev);
|
|
if (vci != ATM_VPI_UNSPEC && vpi != ATM_VCI_UNSPEC)
|
|
set_bit(ATM_VF_ADDR,&vcc->flags);
|
|
if (vcc->qos.aal != ATM_AAL0 && vcc->qos.aal != ATM_AAL5)
|
|
return -EINVAL;
|
|
DPRINTK(DEV_LABEL "(itf %d): open %d.%d\n",vcc->dev->number,vcc->vpi,
|
|
vcc->vci);
|
|
if (!test_bit(ATM_VF_PARTIAL,&vcc->flags)) {
|
|
eni_vcc = kmalloc(sizeof(struct eni_vcc),GFP_KERNEL);
|
|
if (!eni_vcc) return -ENOMEM;
|
|
vcc->dev_data = eni_vcc;
|
|
eni_vcc->tx = NULL; /* for eni_close after open_rx */
|
|
if ((error = open_rx_first(vcc))) {
|
|
eni_close(vcc);
|
|
return error;
|
|
}
|
|
if ((error = open_tx_first(vcc))) {
|
|
eni_close(vcc);
|
|
return error;
|
|
}
|
|
}
|
|
if (vci == ATM_VPI_UNSPEC || vpi == ATM_VCI_UNSPEC) return 0;
|
|
if ((error = open_rx_second(vcc))) {
|
|
eni_close(vcc);
|
|
return error;
|
|
}
|
|
if ((error = open_tx_second(vcc))) {
|
|
eni_close(vcc);
|
|
return error;
|
|
}
|
|
set_bit(ATM_VF_READY,&vcc->flags);
|
|
/* should power down SUNI while !ref_count @@@ */
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int eni_change_qos(struct atm_vcc *vcc,struct atm_qos *qos,int flgs)
|
|
{
|
|
struct eni_dev *eni_dev = ENI_DEV(vcc->dev);
|
|
struct eni_tx *tx = ENI_VCC(vcc)->tx;
|
|
struct sk_buff *skb;
|
|
int error,rate,rsv,shp;
|
|
|
|
if (qos->txtp.traffic_class == ATM_NONE) return 0;
|
|
if (tx == eni_dev->ubr) return -EBADFD;
|
|
rate = atm_pcr_goal(&qos->txtp);
|
|
if (rate < 0) rate = -rate;
|
|
rsv = shp = 0;
|
|
if ((flgs & ATM_MF_DEC_RSV) && rate && rate < tx->reserved) rsv = 1;
|
|
if ((flgs & ATM_MF_INC_RSV) && (!rate || rate > tx->reserved)) rsv = 1;
|
|
if ((flgs & ATM_MF_DEC_SHP) && rate && rate < tx->shaping) shp = 1;
|
|
if ((flgs & ATM_MF_INC_SHP) && (!rate || rate > tx->shaping)) shp = 1;
|
|
if (!rsv && !shp) return 0;
|
|
error = reserve_or_set_tx(vcc,&qos->txtp,rsv,shp);
|
|
if (error) return error;
|
|
if (shp && !(flgs & ATM_MF_IMMED)) return 0;
|
|
/*
|
|
* Walk through the send buffer and patch the rate information in all
|
|
* segmentation buffer descriptors of this VCC.
|
|
*/
|
|
tasklet_disable(&eni_dev->task);
|
|
skb_queue_walk(&eni_dev->tx_queue, skb) {
|
|
void __iomem *dsc;
|
|
|
|
if (ATM_SKB(skb)->vcc != vcc) continue;
|
|
dsc = tx->send+ENI_PRV_POS(skb)*4;
|
|
writel((readl(dsc) & ~(MID_SEG_RATE | MID_SEG_PR)) |
|
|
(tx->prescaler << MID_SEG_PR_SHIFT) |
|
|
(tx->resolution << MID_SEG_RATE_SHIFT), dsc);
|
|
}
|
|
tasklet_enable(&eni_dev->task);
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int eni_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
|
|
{
|
|
struct eni_dev *eni_dev = ENI_DEV(dev);
|
|
|
|
if (cmd == ENI_MEMDUMP) {
|
|
if (!capable(CAP_NET_ADMIN)) return -EPERM;
|
|
printk(KERN_WARNING "Please use /proc/atm/" DEV_LABEL ":%d "
|
|
"instead of obsolete ioctl ENI_MEMDUMP\n",dev->number);
|
|
dump(dev);
|
|
return 0;
|
|
}
|
|
if (cmd == ENI_SETMULT) {
|
|
struct eni_multipliers mult;
|
|
|
|
if (!capable(CAP_NET_ADMIN)) return -EPERM;
|
|
if (copy_from_user(&mult, arg,
|
|
sizeof(struct eni_multipliers)))
|
|
return -EFAULT;
|
|
if ((mult.tx && mult.tx <= 100) || (mult.rx &&mult.rx <= 100) ||
|
|
mult.tx > 65536 || mult.rx > 65536)
|
|
return -EINVAL;
|
|
if (mult.tx) eni_dev->tx_mult = mult.tx;
|
|
if (mult.rx) eni_dev->rx_mult = mult.rx;
|
|
return 0;
|
|
}
|
|
if (cmd == ATM_SETCIRANGE) {
|
|
struct atm_cirange ci;
|
|
|
|
if (copy_from_user(&ci, arg,sizeof(struct atm_cirange)))
|
|
return -EFAULT;
|
|
if ((ci.vpi_bits == 0 || ci.vpi_bits == ATM_CI_MAX) &&
|
|
(ci.vci_bits == NR_VCI_LD || ci.vpi_bits == ATM_CI_MAX))
|
|
return 0;
|
|
return -EINVAL;
|
|
}
|
|
if (!dev->phy->ioctl) return -ENOIOCTLCMD;
|
|
return dev->phy->ioctl(dev,cmd,arg);
|
|
}
|
|
|
|
|
|
static int eni_getsockopt(struct atm_vcc *vcc,int level,int optname,
|
|
void __user *optval,int optlen)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
static int eni_setsockopt(struct atm_vcc *vcc,int level,int optname,
|
|
void __user *optval,unsigned int optlen)
|
|
{
|
|
return -EINVAL;
|
|
}
|
|
|
|
|
|
static int eni_send(struct atm_vcc *vcc,struct sk_buff *skb)
|
|
{
|
|
enum enq_res res;
|
|
|
|
DPRINTK(">eni_send\n");
|
|
if (!ENI_VCC(vcc)->tx) {
|
|
if (vcc->pop) vcc->pop(vcc,skb);
|
|
else dev_kfree_skb(skb);
|
|
return -EINVAL;
|
|
}
|
|
if (!skb) {
|
|
printk(KERN_CRIT "!skb in eni_send ?\n");
|
|
if (vcc->pop) vcc->pop(vcc,skb);
|
|
return -EINVAL;
|
|
}
|
|
if (vcc->qos.aal == ATM_AAL0) {
|
|
if (skb->len != ATM_CELL_SIZE-1) {
|
|
if (vcc->pop) vcc->pop(vcc,skb);
|
|
else dev_kfree_skb(skb);
|
|
return -EINVAL;
|
|
}
|
|
*(u32 *) skb->data = htonl(*(u32 *) skb->data);
|
|
}
|
|
submitted++;
|
|
ATM_SKB(skb)->vcc = vcc;
|
|
tasklet_disable(&ENI_DEV(vcc->dev)->task);
|
|
res = do_tx(skb);
|
|
tasklet_enable(&ENI_DEV(vcc->dev)->task);
|
|
if (res == enq_ok) return 0;
|
|
skb_queue_tail(&ENI_VCC(vcc)->tx->backlog,skb);
|
|
backlogged++;
|
|
tasklet_schedule(&ENI_DEV(vcc->dev)->task);
|
|
return 0;
|
|
}
|
|
|
|
static void eni_phy_put(struct atm_dev *dev,unsigned char value,
|
|
unsigned long addr)
|
|
{
|
|
writel(value,ENI_DEV(dev)->phy+addr*4);
|
|
}
|
|
|
|
|
|
|
|
static unsigned char eni_phy_get(struct atm_dev *dev,unsigned long addr)
|
|
{
|
|
return readl(ENI_DEV(dev)->phy+addr*4);
|
|
}
|
|
|
|
|
|
static int eni_proc_read(struct atm_dev *dev,loff_t *pos,char *page)
|
|
{
|
|
struct hlist_node *node;
|
|
struct sock *s;
|
|
static const char *signal[] = { "LOST","unknown","okay" };
|
|
struct eni_dev *eni_dev = ENI_DEV(dev);
|
|
struct atm_vcc *vcc;
|
|
int left,i;
|
|
|
|
left = *pos;
|
|
if (!left)
|
|
return sprintf(page,DEV_LABEL "(itf %d) signal %s, %dkB, "
|
|
"%d cps remaining\n",dev->number,signal[(int) dev->signal],
|
|
eni_dev->mem >> 10,eni_dev->tx_bw);
|
|
if (!--left)
|
|
return sprintf(page,"%4sBursts: TX"
|
|
#if !defined(CONFIG_ATM_ENI_BURST_TX_16W) && \
|
|
!defined(CONFIG_ATM_ENI_BURST_TX_8W) && \
|
|
!defined(CONFIG_ATM_ENI_BURST_TX_4W) && \
|
|
!defined(CONFIG_ATM_ENI_BURST_TX_2W)
|
|
" none"
|
|
#endif
|
|
#ifdef CONFIG_ATM_ENI_BURST_TX_16W
|
|
" 16W"
|
|
#endif
|
|
#ifdef CONFIG_ATM_ENI_BURST_TX_8W
|
|
" 8W"
|
|
#endif
|
|
#ifdef CONFIG_ATM_ENI_BURST_TX_4W
|
|
" 4W"
|
|
#endif
|
|
#ifdef CONFIG_ATM_ENI_BURST_TX_2W
|
|
" 2W"
|
|
#endif
|
|
", RX"
|
|
#if !defined(CONFIG_ATM_ENI_BURST_RX_16W) && \
|
|
!defined(CONFIG_ATM_ENI_BURST_RX_8W) && \
|
|
!defined(CONFIG_ATM_ENI_BURST_RX_4W) && \
|
|
!defined(CONFIG_ATM_ENI_BURST_RX_2W)
|
|
" none"
|
|
#endif
|
|
#ifdef CONFIG_ATM_ENI_BURST_RX_16W
|
|
" 16W"
|
|
#endif
|
|
#ifdef CONFIG_ATM_ENI_BURST_RX_8W
|
|
" 8W"
|
|
#endif
|
|
#ifdef CONFIG_ATM_ENI_BURST_RX_4W
|
|
" 4W"
|
|
#endif
|
|
#ifdef CONFIG_ATM_ENI_BURST_RX_2W
|
|
" 2W"
|
|
#endif
|
|
#ifndef CONFIG_ATM_ENI_TUNE_BURST
|
|
" (default)"
|
|
#endif
|
|
"\n","");
|
|
if (!--left)
|
|
return sprintf(page,"%4sBuffer multipliers: tx %d%%, rx %d%%\n",
|
|
"",eni_dev->tx_mult,eni_dev->rx_mult);
|
|
for (i = 0; i < NR_CHAN; i++) {
|
|
struct eni_tx *tx = eni_dev->tx+i;
|
|
|
|
if (!tx->send) continue;
|
|
if (!--left) {
|
|
return sprintf(page,"tx[%d]: 0x%ld-0x%ld "
|
|
"(%6ld bytes), rsv %d cps, shp %d cps%s\n",i,
|
|
(unsigned long) (tx->send - eni_dev->ram),
|
|
tx->send-eni_dev->ram+tx->words*4-1,tx->words*4,
|
|
tx->reserved,tx->shaping,
|
|
tx == eni_dev->ubr ? " (UBR)" : "");
|
|
}
|
|
if (--left) continue;
|
|
return sprintf(page,"%10sbacklog %u packets\n","",
|
|
skb_queue_len(&tx->backlog));
|
|
}
|
|
read_lock(&vcc_sklist_lock);
|
|
for(i = 0; i < VCC_HTABLE_SIZE; ++i) {
|
|
struct hlist_head *head = &vcc_hash[i];
|
|
|
|
sk_for_each(s, node, head) {
|
|
struct eni_vcc *eni_vcc;
|
|
int length;
|
|
|
|
vcc = atm_sk(s);
|
|
if (vcc->dev != dev)
|
|
continue;
|
|
eni_vcc = ENI_VCC(vcc);
|
|
if (--left) continue;
|
|
length = sprintf(page,"vcc %4d: ",vcc->vci);
|
|
if (eni_vcc->rx) {
|
|
length += sprintf(page+length,"0x%ld-0x%ld "
|
|
"(%6ld bytes)",
|
|
(unsigned long) (eni_vcc->recv - eni_dev->ram),
|
|
eni_vcc->recv-eni_dev->ram+eni_vcc->words*4-1,
|
|
eni_vcc->words*4);
|
|
if (eni_vcc->tx) length += sprintf(page+length,", ");
|
|
}
|
|
if (eni_vcc->tx)
|
|
length += sprintf(page+length,"tx[%d], txing %d bytes",
|
|
eni_vcc->tx->index,eni_vcc->txing);
|
|
page[length] = '\n';
|
|
read_unlock(&vcc_sklist_lock);
|
|
return length+1;
|
|
}
|
|
}
|
|
read_unlock(&vcc_sklist_lock);
|
|
for (i = 0; i < eni_dev->free_len; i++) {
|
|
struct eni_free *fe = eni_dev->free_list+i;
|
|
unsigned long offset;
|
|
|
|
if (--left) continue;
|
|
offset = (unsigned long) eni_dev->ram+eni_dev->base_diff;
|
|
return sprintf(page,"free %p-%p (%6d bytes)\n",
|
|
fe->start-offset,fe->start-offset+(1 << fe->order)-1,
|
|
1 << fe->order);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static const struct atmdev_ops ops = {
|
|
.open = eni_open,
|
|
.close = eni_close,
|
|
.ioctl = eni_ioctl,
|
|
.getsockopt = eni_getsockopt,
|
|
.setsockopt = eni_setsockopt,
|
|
.send = eni_send,
|
|
.phy_put = eni_phy_put,
|
|
.phy_get = eni_phy_get,
|
|
.change_qos = eni_change_qos,
|
|
.proc_read = eni_proc_read
|
|
};
|
|
|
|
|
|
static int __devinit eni_init_one(struct pci_dev *pci_dev,
|
|
const struct pci_device_id *ent)
|
|
{
|
|
struct atm_dev *dev;
|
|
struct eni_dev *eni_dev;
|
|
int error = -ENOMEM;
|
|
|
|
DPRINTK("eni_init_one\n");
|
|
|
|
if (pci_enable_device(pci_dev)) {
|
|
error = -EIO;
|
|
goto out0;
|
|
}
|
|
|
|
eni_dev = kmalloc(sizeof(struct eni_dev),GFP_KERNEL);
|
|
if (!eni_dev) goto out0;
|
|
if (!cpu_zeroes) {
|
|
cpu_zeroes = pci_alloc_consistent(pci_dev,ENI_ZEROES_SIZE,
|
|
&zeroes);
|
|
if (!cpu_zeroes) goto out1;
|
|
}
|
|
dev = atm_dev_register(DEV_LABEL, &pci_dev->dev, &ops, -1, NULL);
|
|
if (!dev) goto out2;
|
|
pci_set_drvdata(pci_dev, dev);
|
|
eni_dev->pci_dev = pci_dev;
|
|
dev->dev_data = eni_dev;
|
|
eni_dev->asic = ent->driver_data;
|
|
error = eni_do_init(dev);
|
|
if (error) goto out3;
|
|
error = eni_start(dev);
|
|
if (error) goto out3;
|
|
eni_dev->more = eni_boards;
|
|
eni_boards = dev;
|
|
return 0;
|
|
out3:
|
|
atm_dev_deregister(dev);
|
|
out2:
|
|
pci_free_consistent(eni_dev->pci_dev,ENI_ZEROES_SIZE,cpu_zeroes,zeroes);
|
|
cpu_zeroes = NULL;
|
|
out1:
|
|
kfree(eni_dev);
|
|
out0:
|
|
return error;
|
|
}
|
|
|
|
|
|
static struct pci_device_id eni_pci_tbl[] = {
|
|
{ PCI_VDEVICE(EF, PCI_DEVICE_ID_EF_ATM_FPGA), 0 /* FPGA */ },
|
|
{ PCI_VDEVICE(EF, PCI_DEVICE_ID_EF_ATM_ASIC), 1 /* ASIC */ },
|
|
{ 0, }
|
|
};
|
|
MODULE_DEVICE_TABLE(pci,eni_pci_tbl);
|
|
|
|
|
|
static void __devexit eni_remove_one(struct pci_dev *pci_dev)
|
|
{
|
|
/* grrr */
|
|
}
|
|
|
|
|
|
static struct pci_driver eni_driver = {
|
|
.name = DEV_LABEL,
|
|
.id_table = eni_pci_tbl,
|
|
.probe = eni_init_one,
|
|
.remove = __devexit_p(eni_remove_one),
|
|
};
|
|
|
|
|
|
static int __init eni_init(void)
|
|
{
|
|
struct sk_buff *skb; /* dummy for sizeof */
|
|
|
|
if (sizeof(skb->cb) < sizeof(struct eni_skb_prv)) {
|
|
printk(KERN_ERR "eni_detect: skb->cb is too small (%Zd < %Zd)\n",
|
|
sizeof(skb->cb),sizeof(struct eni_skb_prv));
|
|
return -EIO;
|
|
}
|
|
return pci_register_driver(&eni_driver);
|
|
}
|
|
|
|
|
|
module_init(eni_init);
|
|
/* @@@ since exit routine not defined, this module can not be unloaded */
|
|
|
|
MODULE_LICENSE("GPL");
|