mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-25 18:30:06 +00:00
![Linus Torvalds](/assets/img/avatar_default.png)
Core changes: - Provide IRQF_NO_AUTOEN as a flag for request*_irq() so drivers can be cleaned up which either use a seperate mechanism to prevent auto-enable at request time or have a racy mechanism which disables the interrupt right after request. - Get rid of the last usage of irq_create_identity_mapping() and remove the interface. - An overhaul of tasklet_disable(). Most usage sites of tasklet_disable() are in task context and usually in cleanup, teardown code pathes. tasklet_disable() spinwaits for a tasklet which is currently executed. That's not only a problem for PREEMPT_RT where this can lead to a live lock when the disabling task preempts the softirq thread. It's also problematic in context of virtualization when the vCPU which runs the tasklet is scheduled out and the disabling code has to spin wait until it's scheduled back in. Though there are a few code pathes which invoke tasklet_disable() from non-sleepable context. For these a new disable variant which still spinwaits is provided which allows to switch tasklet_disable() to a sleep wait mechanism. For the atomic use cases this does not solve the live lock issue on PREEMPT_RT. That is mitigated by blocking on the RT specific softirq lock. - The PREEMPT_RT specific implementation of softirq processing and local_bh_disable/enable(). On RT enabled kernels soft interrupt processing happens always in task context and all interrupt handlers, which are not explicitly marked to be invoked in hard interrupt context are forced into task context as well. This allows to protect against softirq processing with a per CPU lock, which in turn allows to make BH disabled regions preemptible. Most of the softirq handling code is still shared. The RT/non-RT specific differences are addressed with a set of inline functions which provide the context specific functionality. The local_bh_disable() / local_bh_enable() mechanism are obviously seperate. - The usual set of small improvements and cleanups Driver changes: - New drivers for Nuvoton WPCM450 and DT 79rc3243x interrupt controllers - Extended functionality for MStar, STM32 and SC7280 irq chips - Enhanced robustness for ARM GICv3/4.1 drivers - The usual set of cleanups and improvements all over the place -----BEGIN PGP SIGNATURE----- iQJHBAABCgAxFiEEQp8+kY+LLUocC4bMphj1TA10mKEFAmCGh5wTHHRnbHhAbGlu dXRyb25peC5kZQAKCRCmGPVMDXSYoZ+/EACWBpQ/2ZHizEw1bzjaDzJrR8U228xu wNi7nSP92Y07nJ3cCX7a6TJ53mqd0n3RT+DprlsOuqSN0D7Ktr/x44V/aZtm0d3N GkFOlpeGCRnHusLaUTwk7a8289LuoQ7OhSxIB409n1I4nLI96ZK41D1tYonMYl6E nxDiGADASfjaciBWbjwJO/mlwmiW/VRpSTxswx0wzakFfbIx9iKyKv1bCJQZ5JK+ lHmf0jxpDIs1EVK/ElJ9Ky6TMBlEmZyiX7n6rujtwJ1W+Jc/uL/y8pLJvGwooVmI yHTYsLMqzviCbAMhJiB3h1qs3GbCGlM78prgJTnOd0+xEUOCcopCRQlsTXVBq8Nb OS+HNkYmYXRfiSH6lINJsIok8Xis28bAw/qWz2Ho+8wLq0TI8crK38roD1fPndee FNJRhsPPOBkscpIldJ0Cr0X5lclkJFiAhAxORPHoseKvQSm7gBMB7H99xeGRffTn yB3XqeTJMvPNmAHNN4Brv6ey3OjwnEWBgwcnIM2LtbIlRtlmxTYuR+82OPOgEvzk fSrjFFJqu0LEMLEOXS4pYN824PawjV//UAy4IaG8AodmUUCSGHgw1gTVa4sIf72t tXY54HqWfRWRpujhVRgsZETqBUtZkL6yvpoe8f6H7P91W5tAfv3oj4ch9RkhUo+Z b0/u9T0+Fpbg+w== =id4G -----END PGP SIGNATURE----- Merge tag 'irq-core-2021-04-26' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip Pull irq updates from Thomas Gleixner: "The usual updates from the irq departement: Core changes: - Provide IRQF_NO_AUTOEN as a flag for request*_irq() so drivers can be cleaned up which either use a seperate mechanism to prevent auto-enable at request time or have a racy mechanism which disables the interrupt right after request. - Get rid of the last usage of irq_create_identity_mapping() and remove the interface. - An overhaul of tasklet_disable(). Most usage sites of tasklet_disable() are in task context and usually in cleanup, teardown code pathes. tasklet_disable() spinwaits for a tasklet which is currently executed. That's not only a problem for PREEMPT_RT where this can lead to a live lock when the disabling task preempts the softirq thread. It's also problematic in context of virtualization when the vCPU which runs the tasklet is scheduled out and the disabling code has to spin wait until it's scheduled back in. There are a few code pathes which invoke tasklet_disable() from non-sleepable context. For these a new disable variant which still spinwaits is provided which allows to switch tasklet_disable() to a sleep wait mechanism. For the atomic use cases this does not solve the live lock issue on PREEMPT_RT. That is mitigated by blocking on the RT specific softirq lock. - The PREEMPT_RT specific implementation of softirq processing and local_bh_disable/enable(). On RT enabled kernels soft interrupt processing happens always in task context and all interrupt handlers, which are not explicitly marked to be invoked in hard interrupt context are forced into task context as well. This allows to protect against softirq processing with a per CPU lock, which in turn allows to make BH disabled regions preemptible. Most of the softirq handling code is still shared. The RT/non-RT specific differences are addressed with a set of inline functions which provide the context specific functionality. The local_bh_disable() / local_bh_enable() mechanism are obviously seperate. - The usual set of small improvements and cleanups Driver changes: - New drivers for Nuvoton WPCM450 and DT 79rc3243x interrupt controllers - Extended functionality for MStar, STM32 and SC7280 irq chips - Enhanced robustness for ARM GICv3/4.1 drivers - The usual set of cleanups and improvements all over the place" * tag 'irq-core-2021-04-26' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (53 commits) irqchip/xilinx: Expose Kconfig option for Zynq/ZynqMP irqchip/gic-v3: Do not enable irqs when handling spurious interrups dt-bindings: interrupt-controller: Add IDT 79RC3243x Interrupt Controller irqchip: Add support for IDT 79rc3243x interrupt controller irqdomain: Drop references to recusive irqdomain setup irqdomain: Get rid of irq_create_strict_mappings() irqchip/jcore-aic: Kill use of irq_create_strict_mappings() ARM: PXA: Kill use of irq_create_strict_mappings() irqchip/gic-v4.1: Disable vSGI upon (GIC CPUIF < v4.1) detection irqchip/tb10x: Use 'fallthrough' to eliminate a warning genirq: Reduce irqdebug cacheline bouncing kernel: Initialize cpumask before parsing irqchip/wpcm450: Drop COMPILE_TEST irqchip/irq-mst: Support polarity configuration irqchip: Add driver for WPCM450 interrupt controller dt-bindings: interrupt-controller: Add nuvoton, wpcm450-aic dt-bindings: qcom,pdc: Add compatible for sc7280 irqchip/stm32: Add usart instances exti direct event support irqchip/gic-v3: Fix OF_BAD_ADDR error handling irqchip/sifive-plic: Mark two global variables __ro_after_init ...
2319 lines
61 KiB
C
2319 lines
61 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* 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"
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/* 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 = 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;
|
|
dma[j++] = 0;
|
|
}
|
|
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;
|
|
dma[j++] = 0;
|
|
}
|
|
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;
|
|
}
|
|
|
|
|
|
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);
|
|
dma_unmap_single(&eni_dev->pci_dev->dev,ENI_PRV_PADDR(skb),skb->len,
|
|
DMA_TO_DEVICE);
|
|
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;
|
|
unsigned char skb_data3;
|
|
|
|
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;
|
|
}
|
|
skb_data3 = skb->data[3];
|
|
paddr = dma_map_single(&eni_dev->pci_dev->dev,skb->data,skb->len,
|
|
DMA_TO_DEVICE);
|
|
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_frag_page(&skb_shinfo(skb)->frags[i]) +
|
|
skb_frag_off(&skb_shinfo(skb)->frags[i]),
|
|
skb_frag_size(&skb_shinfo(skb)->frags[i]));
|
|
}
|
|
if (skb->len & 3) {
|
|
put_dma(tx->index, eni_dev->dma, &j, eni_dev->zero.dma,
|
|
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_data3 & 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);
|
|
dma_unmap_single(&eni_dev->pci_dev->dev,ENI_PRV_PADDR(skb),skb->len,
|
|
DMA_TO_DEVICE);
|
|
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)
|
|
{
|
|
DPRINTK(">bug_int\n");
|
|
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 char * const media_name[] = {
|
|
"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 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 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 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(real_base,MAP_MAX_SIZE))) {
|
|
printk("\n");
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): can't set up page "
|
|
"mapping\n",dev->number);
|
|
return -ENOMEM;
|
|
}
|
|
eni_dev->ioaddr = base;
|
|
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) & DAUGHTER_ID]);
|
|
|
|
error = suni_init(dev);
|
|
if (error)
|
|
goto unmap;
|
|
out:
|
|
return error;
|
|
unmap:
|
|
iounmap(base);
|
|
goto out;
|
|
}
|
|
|
|
static void eni_do_release(struct atm_dev *dev)
|
|
{
|
|
struct eni_dev *ed = ENI_DEV(dev);
|
|
|
|
dev->phy->stop(dev);
|
|
dev->phy = NULL;
|
|
iounmap(ed->ioaddr);
|
|
}
|
|
|
|
static int 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_array(eni_dev->free_list_size + 1,
|
|
sizeof(*eni_dev->free_list),
|
|
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, 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_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;
|
|
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_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_in_atomic(&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 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%lx-0x%lx "
|
|
"(%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, 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%lx-0x%lx "
|
|
"(%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,
|
|
.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 eni_init_one(struct pci_dev *pci_dev,
|
|
const struct pci_device_id *ent)
|
|
{
|
|
struct atm_dev *dev;
|
|
struct eni_dev *eni_dev;
|
|
struct eni_zero *zero;
|
|
int rc;
|
|
|
|
rc = pci_enable_device(pci_dev);
|
|
if (rc < 0)
|
|
goto out;
|
|
|
|
rc = dma_set_mask_and_coherent(&pci_dev->dev, DMA_BIT_MASK(32));
|
|
if (rc < 0)
|
|
goto err_disable;
|
|
|
|
rc = -ENOMEM;
|
|
eni_dev = kmalloc(sizeof(struct eni_dev), GFP_KERNEL);
|
|
if (!eni_dev)
|
|
goto err_disable;
|
|
|
|
zero = &eni_dev->zero;
|
|
zero->addr = dma_alloc_coherent(&pci_dev->dev,
|
|
ENI_ZEROES_SIZE, &zero->dma, GFP_KERNEL);
|
|
if (!zero->addr)
|
|
goto err_kfree;
|
|
|
|
dev = atm_dev_register(DEV_LABEL, &pci_dev->dev, &ops, -1, NULL);
|
|
if (!dev)
|
|
goto err_free_consistent;
|
|
|
|
dev->dev_data = eni_dev;
|
|
pci_set_drvdata(pci_dev, dev);
|
|
eni_dev->pci_dev = pci_dev;
|
|
eni_dev->asic = ent->driver_data;
|
|
|
|
rc = eni_do_init(dev);
|
|
if (rc < 0)
|
|
goto err_unregister;
|
|
|
|
rc = eni_start(dev);
|
|
if (rc < 0)
|
|
goto err_eni_release;
|
|
|
|
eni_dev->more = eni_boards;
|
|
eni_boards = dev;
|
|
out:
|
|
return rc;
|
|
|
|
err_eni_release:
|
|
dev->phy = NULL;
|
|
iounmap(ENI_DEV(dev)->ioaddr);
|
|
err_unregister:
|
|
atm_dev_deregister(dev);
|
|
err_free_consistent:
|
|
dma_free_coherent(&pci_dev->dev, ENI_ZEROES_SIZE, zero->addr, zero->dma);
|
|
err_kfree:
|
|
kfree(eni_dev);
|
|
err_disable:
|
|
pci_disable_device(pci_dev);
|
|
goto out;
|
|
}
|
|
|
|
|
|
static const 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 eni_remove_one(struct pci_dev *pdev)
|
|
{
|
|
struct atm_dev *dev = pci_get_drvdata(pdev);
|
|
struct eni_dev *ed = ENI_DEV(dev);
|
|
struct eni_zero *zero = &ed->zero;
|
|
|
|
eni_do_release(dev);
|
|
atm_dev_deregister(dev);
|
|
dma_free_coherent(&pdev->dev, ENI_ZEROES_SIZE, zero->addr, zero->dma);
|
|
kfree(ed);
|
|
pci_disable_device(pdev);
|
|
}
|
|
|
|
|
|
static struct pci_driver eni_driver = {
|
|
.name = DEV_LABEL,
|
|
.id_table = eni_pci_tbl,
|
|
.probe = eni_init_one,
|
|
.remove = eni_remove_one,
|
|
};
|
|
|
|
|
|
static int __init eni_init(void)
|
|
{
|
|
struct sk_buff *skb; /* dummy for sizeof */
|
|
|
|
BUILD_BUG_ON(sizeof(skb->cb) < sizeof(struct eni_skb_prv));
|
|
return pci_register_driver(&eni_driver);
|
|
}
|
|
|
|
|
|
module_init(eni_init);
|
|
/* @@@ since exit routine not defined, this module can not be unloaded */
|
|
|
|
MODULE_LICENSE("GPL");
|