2005-04-17 00:20:36 +02:00
|
|
|
/* drivers/atm/zatm.c - ZeitNet ZN122x 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/netdevice.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/uio.h>
|
|
|
|
#include <linux/init.h>
|
2005-07-20 21:01:46 +02:00
|
|
|
#include <linux/dma-mapping.h>
|
2005-04-17 00:20:36 +02:00
|
|
|
#include <linux/atm_zatm.h>
|
|
|
|
#include <linux/capability.h>
|
|
|
|
#include <linux/bitops.h>
|
|
|
|
#include <linux/wait.h>
|
|
|
|
#include <asm/byteorder.h>
|
|
|
|
#include <asm/system.h>
|
|
|
|
#include <asm/string.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <asm/atomic.h>
|
|
|
|
#include <asm/uaccess.h>
|
|
|
|
|
|
|
|
#include "uPD98401.h"
|
|
|
|
#include "uPD98402.h"
|
|
|
|
#include "zeprom.h"
|
|
|
|
#include "zatm.h"
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TODO:
|
|
|
|
*
|
|
|
|
* Minor features
|
|
|
|
* - support 64 kB SDUs (will have to use multibuffer batches then :-( )
|
|
|
|
* - proper use of CDV, credit = max(1,CDVT*PCR)
|
|
|
|
* - AAL0
|
|
|
|
* - better receive timestamps
|
|
|
|
* - OAM
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define ZATM_COPPER 1
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
|
|
|
|
#else
|
|
|
|
#define DPRINTK(format,args...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef CONFIG_ATM_ZATM_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%x\n", (int) (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;
|
|
|
|
|
|
|
|
printk(KERN_NOTICE "----- event dump follows -----\n");
|
|
|
|
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]);
|
|
|
|
}
|
|
|
|
printk(KERN_NOTICE "----- event dump ends here -----\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endif /* CONFIG_ATM_ZATM_DEBUG */
|
|
|
|
|
|
|
|
|
|
|
|
#define RING_BUSY 1 /* indication from do_tx that PDU has to be
|
|
|
|
backlogged */
|
|
|
|
|
|
|
|
static struct atm_dev *zatm_boards = NULL;
|
|
|
|
static unsigned long dummy[2] = {0,0};
|
|
|
|
|
|
|
|
|
|
|
|
#define zin_n(r) inl(zatm_dev->base+r*4)
|
|
|
|
#define zin(r) inl(zatm_dev->base+uPD98401_##r*4)
|
|
|
|
#define zout(v,r) outl(v,zatm_dev->base+uPD98401_##r*4)
|
|
|
|
#define zwait while (zin(CMR) & uPD98401_BUSY)
|
|
|
|
|
|
|
|
/* RX0, RX1, TX0, TX1 */
|
|
|
|
static const int mbx_entries[NR_MBX] = { 1024,1024,1024,1024 };
|
|
|
|
static const int mbx_esize[NR_MBX] = { 16,16,4,4 }; /* entry size in bytes */
|
|
|
|
|
|
|
|
#define MBX_SIZE(i) (mbx_entries[i]*mbx_esize[i])
|
|
|
|
|
|
|
|
|
|
|
|
/*-------------------------------- utilities --------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
static void zpokel(struct zatm_dev *zatm_dev,u32 value,u32 addr)
|
|
|
|
{
|
|
|
|
zwait;
|
|
|
|
zout(value,CER);
|
|
|
|
zout(uPD98401_IND_ACC | uPD98401_IA_BALL |
|
|
|
|
(uPD98401_IA_TGT_CM << uPD98401_IA_TGT_SHIFT) | addr,CMR);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static u32 zpeekl(struct zatm_dev *zatm_dev,u32 addr)
|
|
|
|
{
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_IND_ACC | uPD98401_IA_BALL | uPD98401_IA_RW |
|
|
|
|
(uPD98401_IA_TGT_CM << uPD98401_IA_TGT_SHIFT) | addr,CMR);
|
|
|
|
zwait;
|
|
|
|
return zin(CER);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*------------------------------- free lists --------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Free buffer head structure:
|
|
|
|
* [0] pointer to buffer (for SAR)
|
|
|
|
* [1] buffer descr link pointer (for SAR)
|
|
|
|
* [2] back pointer to skb (for poll_rx)
|
|
|
|
* [3] data
|
|
|
|
* ...
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct rx_buffer_head {
|
|
|
|
u32 buffer; /* pointer to buffer (for SAR) */
|
|
|
|
u32 link; /* buffer descriptor link pointer (for SAR) */
|
|
|
|
struct sk_buff *skb; /* back pointer to skb (for poll_rx) */
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void refill_pool(struct atm_dev *dev,int pool)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct rx_buffer_head *first;
|
|
|
|
unsigned long flags;
|
|
|
|
int align,offset,free,count,size;
|
|
|
|
|
|
|
|
EVENT("refill_pool\n",0,0);
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
size = (64 << (pool <= ZATM_AAL5_POOL_BASE ? 0 :
|
|
|
|
pool-ZATM_AAL5_POOL_BASE))+sizeof(struct rx_buffer_head);
|
|
|
|
if (size < PAGE_SIZE) {
|
|
|
|
align = 32; /* for 32 byte alignment */
|
|
|
|
offset = sizeof(struct rx_buffer_head);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
align = 4096;
|
|
|
|
offset = zatm_dev->pool_info[pool].offset+
|
|
|
|
sizeof(struct rx_buffer_head);
|
|
|
|
}
|
|
|
|
size += align;
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
free = zpeekl(zatm_dev,zatm_dev->pool_base+2*pool) &
|
|
|
|
uPD98401_RXFP_REMAIN;
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
if (free >= zatm_dev->pool_info[pool].low_water) return;
|
|
|
|
EVENT("starting ... POOL: 0x%x, 0x%x\n",
|
|
|
|
zpeekl(zatm_dev,zatm_dev->pool_base+2*pool),
|
|
|
|
zpeekl(zatm_dev,zatm_dev->pool_base+2*pool+1));
|
|
|
|
EVENT("dummy: 0x%08lx, 0x%08lx\n",dummy[0],dummy[1]);
|
|
|
|
count = 0;
|
|
|
|
first = NULL;
|
|
|
|
while (free < zatm_dev->pool_info[pool].high_water) {
|
|
|
|
struct rx_buffer_head *head;
|
|
|
|
|
|
|
|
skb = alloc_skb(size,GFP_ATOMIC);
|
|
|
|
if (!skb) {
|
|
|
|
printk(KERN_WARNING DEV_LABEL "(Itf %d): got no new "
|
|
|
|
"skb (%d) with %d free\n",dev->number,size,free);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
skb_reserve(skb,(unsigned char *) ((((unsigned long) skb->data+
|
|
|
|
align+offset-1) & ~(unsigned long) (align-1))-offset)-
|
|
|
|
skb->data);
|
|
|
|
head = (struct rx_buffer_head *) skb->data;
|
|
|
|
skb_reserve(skb,sizeof(struct rx_buffer_head));
|
|
|
|
if (!first) first = head;
|
|
|
|
count++;
|
|
|
|
head->buffer = virt_to_bus(skb->data);
|
|
|
|
head->link = 0;
|
|
|
|
head->skb = skb;
|
|
|
|
EVENT("enq skb 0x%08lx/0x%08lx\n",(unsigned long) skb,
|
|
|
|
(unsigned long) head);
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
if (zatm_dev->last_free[pool])
|
|
|
|
((struct rx_buffer_head *) (zatm_dev->last_free[pool]->
|
|
|
|
data))[-1].link = virt_to_bus(head);
|
|
|
|
zatm_dev->last_free[pool] = skb;
|
|
|
|
skb_queue_tail(&zatm_dev->pool[pool],skb);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
free++;
|
|
|
|
}
|
|
|
|
if (first) {
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
zwait;
|
|
|
|
zout(virt_to_bus(first),CER);
|
|
|
|
zout(uPD98401_ADD_BAT | (pool << uPD98401_POOL_SHIFT) | count,
|
|
|
|
CMR);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
EVENT ("POOL: 0x%x, 0x%x\n",
|
|
|
|
zpeekl(zatm_dev,zatm_dev->pool_base+2*pool),
|
|
|
|
zpeekl(zatm_dev,zatm_dev->pool_base+2*pool+1));
|
|
|
|
EVENT("dummy: 0x%08lx, 0x%08lx\n",dummy[0],dummy[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void drain_free(struct atm_dev *dev,int pool)
|
|
|
|
{
|
|
|
|
skb_queue_purge(&ZATM_DEV(dev)->pool[pool]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int pool_index(int max_pdu)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (max_pdu % ATM_CELL_PAYLOAD)
|
|
|
|
printk(KERN_ERR DEV_LABEL ": driver error in pool_index: "
|
|
|
|
"max_pdu is %d\n",max_pdu);
|
|
|
|
if (max_pdu > 65536) return -1;
|
|
|
|
for (i = 0; (64 << i) < max_pdu; i++);
|
|
|
|
return i+ZATM_AAL5_POOL_BASE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* use_pool isn't reentrant */
|
|
|
|
|
|
|
|
|
|
|
|
static void use_pool(struct atm_dev *dev,int pool)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
unsigned long flags;
|
|
|
|
int size;
|
|
|
|
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
if (!(zatm_dev->pool_info[pool].ref_count++)) {
|
|
|
|
skb_queue_head_init(&zatm_dev->pool[pool]);
|
|
|
|
size = pool-ZATM_AAL5_POOL_BASE;
|
|
|
|
if (size < 0) size = 0; /* 64B... */
|
|
|
|
else if (size > 10) size = 10; /* ... 64kB */
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
zpokel(zatm_dev,((zatm_dev->pool_info[pool].low_water/4) <<
|
|
|
|
uPD98401_RXFP_ALERT_SHIFT) |
|
|
|
|
(1 << uPD98401_RXFP_BTSZ_SHIFT) |
|
|
|
|
(size << uPD98401_RXFP_BFSZ_SHIFT),
|
|
|
|
zatm_dev->pool_base+pool*2);
|
|
|
|
zpokel(zatm_dev,(unsigned long) dummy,zatm_dev->pool_base+
|
|
|
|
pool*2+1);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
zatm_dev->last_free[pool] = NULL;
|
|
|
|
refill_pool(dev,pool);
|
|
|
|
}
|
|
|
|
DPRINTK("pool %d: %d\n",pool,zatm_dev->pool_info[pool].ref_count);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void unuse_pool(struct atm_dev *dev,int pool)
|
|
|
|
{
|
|
|
|
if (!(--ZATM_DEV(dev)->pool_info[pool].ref_count))
|
|
|
|
drain_free(dev,pool);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*----------------------------------- RX ------------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
static void exception(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
static int count = 0;
|
|
|
|
struct zatm_dev *zatm_dev = ZATM_DEV(vcc->dev);
|
|
|
|
struct zatm_vcc *zatm_vcc = ZATM_VCC(vcc);
|
|
|
|
unsigned long *qrp;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (count++ > 2) return;
|
|
|
|
for (i = 0; i < 8; i++)
|
|
|
|
printk("TX%d: 0x%08lx\n",i,
|
|
|
|
zpeekl(zatm_dev,zatm_vcc->tx_chan*VC_SIZE/4+i));
|
|
|
|
for (i = 0; i < 5; i++)
|
|
|
|
printk("SH%d: 0x%08lx\n",i,
|
|
|
|
zpeekl(zatm_dev,uPD98401_IM(zatm_vcc->shaper)+16*i));
|
|
|
|
qrp = (unsigned long *) zpeekl(zatm_dev,zatm_vcc->tx_chan*VC_SIZE/4+
|
|
|
|
uPD98401_TXVC_QRP);
|
|
|
|
printk("qrp=0x%08lx\n",(unsigned long) qrp);
|
|
|
|
for (i = 0; i < 4; i++) printk("QRP[%d]: 0x%08lx",i,qrp[i]);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
static const char *err_txt[] = {
|
|
|
|
"No error",
|
|
|
|
"RX buf underflow",
|
|
|
|
"RX FIFO overrun",
|
|
|
|
"Maximum len violation",
|
|
|
|
"CRC error",
|
|
|
|
"User abort",
|
|
|
|
"Length violation",
|
|
|
|
"T1 error",
|
|
|
|
"Deactivated",
|
|
|
|
"???",
|
|
|
|
"???",
|
|
|
|
"???",
|
|
|
|
"???",
|
|
|
|
"???",
|
|
|
|
"???",
|
|
|
|
"???"
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
static void poll_rx(struct atm_dev *dev,int mbx)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
unsigned long pos;
|
|
|
|
u32 x;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
EVENT("poll_rx\n",0,0);
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
pos = (zatm_dev->mbx_start[mbx] & ~0xffffUL) | zin(MTA(mbx));
|
|
|
|
while (x = zin(MWA(mbx)), (pos & 0xffff) != x) {
|
|
|
|
u32 *here;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
struct atm_vcc *vcc;
|
|
|
|
int cells,size,chan;
|
|
|
|
|
|
|
|
EVENT("MBX: host 0x%lx, nic 0x%x\n",pos,x);
|
|
|
|
here = (u32 *) pos;
|
|
|
|
if (((pos += 16) & 0xffff) == zatm_dev->mbx_end[mbx])
|
|
|
|
pos = zatm_dev->mbx_start[mbx];
|
|
|
|
cells = here[0] & uPD98401_AAL5_SIZE;
|
|
|
|
#if 0
|
|
|
|
printk("RX IND: 0x%x, 0x%x, 0x%x, 0x%x\n",here[0],here[1],here[2],here[3]);
|
|
|
|
{
|
|
|
|
unsigned long *x;
|
|
|
|
printk("POOL: 0x%08x, 0x%08x\n",zpeekl(zatm_dev,
|
|
|
|
zatm_dev->pool_base),
|
|
|
|
zpeekl(zatm_dev,zatm_dev->pool_base+1));
|
|
|
|
x = (unsigned long *) here[2];
|
|
|
|
printk("[0..3] = 0x%08lx, 0x%08lx, 0x%08lx, 0x%08lx\n",
|
|
|
|
x[0],x[1],x[2],x[3]);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
error = 0;
|
|
|
|
if (here[3] & uPD98401_AAL5_ERR) {
|
|
|
|
error = (here[3] & uPD98401_AAL5_ES) >>
|
|
|
|
uPD98401_AAL5_ES_SHIFT;
|
|
|
|
if (error == uPD98401_AAL5_ES_DEACT ||
|
|
|
|
error == uPD98401_AAL5_ES_FREE) continue;
|
|
|
|
}
|
|
|
|
EVENT("error code 0x%x/0x%x\n",(here[3] & uPD98401_AAL5_ES) >>
|
|
|
|
uPD98401_AAL5_ES_SHIFT,error);
|
|
|
|
skb = ((struct rx_buffer_head *) bus_to_virt(here[2]))->skb;
|
2005-08-15 02:24:31 +02:00
|
|
|
__net_timestamp(skb);
|
2005-04-17 00:20:36 +02:00
|
|
|
#if 0
|
|
|
|
printk("[-3..0] 0x%08lx 0x%08lx 0x%08lx 0x%08lx\n",((unsigned *) skb->data)[-3],
|
|
|
|
((unsigned *) skb->data)[-2],((unsigned *) skb->data)[-1],
|
|
|
|
((unsigned *) skb->data)[0]);
|
|
|
|
#endif
|
|
|
|
EVENT("skb 0x%lx, here 0x%lx\n",(unsigned long) skb,
|
|
|
|
(unsigned long) here);
|
|
|
|
#if 0
|
|
|
|
printk("dummy: 0x%08lx, 0x%08lx\n",dummy[0],dummy[1]);
|
|
|
|
#endif
|
|
|
|
size = error ? 0 : ntohs(((__be16 *) skb->data)[cells*
|
|
|
|
ATM_CELL_PAYLOAD/sizeof(u16)-3]);
|
|
|
|
EVENT("got skb 0x%lx, size %d\n",(unsigned long) skb,size);
|
|
|
|
chan = (here[3] & uPD98401_AAL5_CHAN) >>
|
|
|
|
uPD98401_AAL5_CHAN_SHIFT;
|
|
|
|
if (chan < zatm_dev->chans && zatm_dev->rx_map[chan]) {
|
2005-09-02 21:18:03 +02:00
|
|
|
int pos;
|
2005-04-17 00:20:36 +02:00
|
|
|
vcc = zatm_dev->rx_map[chan];
|
2005-09-02 21:18:03 +02:00
|
|
|
pos = ZATM_VCC(vcc)->pool;
|
2005-08-10 04:25:21 +02:00
|
|
|
if (skb == zatm_dev->last_free[pos])
|
|
|
|
zatm_dev->last_free[pos] = NULL;
|
|
|
|
skb_unlink(skb, zatm_dev->pool + pos);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): RX indication "
|
|
|
|
"for non-existing channel\n",dev->number);
|
|
|
|
size = 0;
|
|
|
|
vcc = NULL;
|
|
|
|
event_dump();
|
|
|
|
}
|
|
|
|
if (error) {
|
|
|
|
static unsigned long silence = 0;
|
|
|
|
static int last_error = 0;
|
|
|
|
|
|
|
|
if (error != last_error ||
|
|
|
|
time_after(jiffies, silence) || silence == 0){
|
|
|
|
printk(KERN_WARNING DEV_LABEL "(itf %d): "
|
|
|
|
"chan %d error %s\n",dev->number,chan,
|
|
|
|
err_txt[error]);
|
|
|
|
last_error = error;
|
|
|
|
silence = (jiffies+2*HZ)|1;
|
|
|
|
}
|
|
|
|
size = 0;
|
|
|
|
}
|
|
|
|
if (size && (size > cells*ATM_CELL_PAYLOAD-ATM_AAL5_TRAILER ||
|
|
|
|
size <= (cells-1)*ATM_CELL_PAYLOAD-ATM_AAL5_TRAILER)) {
|
|
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): size %d with %d "
|
|
|
|
"cells\n",dev->number,size,cells);
|
|
|
|
size = 0;
|
|
|
|
event_dump();
|
|
|
|
}
|
|
|
|
if (size > ATM_MAX_AAL5_PDU) {
|
|
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): size too big "
|
|
|
|
"(%d)\n",dev->number,size);
|
|
|
|
size = 0;
|
|
|
|
event_dump();
|
|
|
|
}
|
|
|
|
if (!size) {
|
|
|
|
dev_kfree_skb_irq(skb);
|
|
|
|
if (vcc) atomic_inc(&vcc->stats->rx_err);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!atm_charge(vcc,skb->truesize)) {
|
|
|
|
dev_kfree_skb_irq(skb);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
skb->len = size;
|
|
|
|
ATM_SKB(skb)->vcc = vcc;
|
|
|
|
vcc->push(vcc,skb);
|
|
|
|
atomic_inc(&vcc->stats->rx);
|
|
|
|
}
|
|
|
|
zout(pos & 0xffff,MTA(mbx));
|
|
|
|
#if 0 /* probably a stupid idea */
|
|
|
|
refill_pool(dev,zatm_vcc->pool);
|
|
|
|
/* maybe this saves us a few interrupts */
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int open_rx_first(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct zatm_vcc *zatm_vcc;
|
|
|
|
unsigned long flags;
|
|
|
|
unsigned short chan;
|
|
|
|
int cells;
|
|
|
|
|
|
|
|
DPRINTK("open_rx_first (0x%x)\n",inb_p(0xc053));
|
|
|
|
zatm_dev = ZATM_DEV(vcc->dev);
|
|
|
|
zatm_vcc = ZATM_VCC(vcc);
|
|
|
|
zatm_vcc->rx_chan = 0;
|
|
|
|
if (vcc->qos.rxtp.traffic_class == ATM_NONE) return 0;
|
|
|
|
if (vcc->qos.aal == ATM_AAL5) {
|
|
|
|
if (vcc->qos.rxtp.max_sdu > 65464)
|
|
|
|
vcc->qos.rxtp.max_sdu = 65464;
|
|
|
|
/* fix this - we may want to receive 64kB SDUs
|
|
|
|
later */
|
|
|
|
cells = (vcc->qos.rxtp.max_sdu+ATM_AAL5_TRAILER+
|
|
|
|
ATM_CELL_PAYLOAD-1)/ATM_CELL_PAYLOAD;
|
|
|
|
zatm_vcc->pool = pool_index(cells*ATM_CELL_PAYLOAD);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
cells = 1;
|
|
|
|
zatm_vcc->pool = ZATM_AAL0_POOL;
|
|
|
|
}
|
|
|
|
if (zatm_vcc->pool < 0) return -EMSGSIZE;
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_OPEN_CHAN,CMR);
|
|
|
|
zwait;
|
|
|
|
DPRINTK("0x%x 0x%x\n",zin(CMR),zin(CER));
|
|
|
|
chan = (zin(CMR) & uPD98401_CHAN_ADDR) >> uPD98401_CHAN_ADDR_SHIFT;
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
DPRINTK("chan is %d\n",chan);
|
|
|
|
if (!chan) return -EAGAIN;
|
|
|
|
use_pool(vcc->dev,zatm_vcc->pool);
|
|
|
|
DPRINTK("pool %d\n",zatm_vcc->pool);
|
|
|
|
/* set up VC descriptor */
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
zpokel(zatm_dev,zatm_vcc->pool << uPD98401_RXVC_POOL_SHIFT,
|
|
|
|
chan*VC_SIZE/4);
|
|
|
|
zpokel(zatm_dev,uPD98401_RXVC_OD | (vcc->qos.aal == ATM_AAL5 ?
|
|
|
|
uPD98401_RXVC_AR : 0) | cells,chan*VC_SIZE/4+1);
|
|
|
|
zpokel(zatm_dev,0,chan*VC_SIZE/4+2);
|
|
|
|
zatm_vcc->rx_chan = chan;
|
|
|
|
zatm_dev->rx_map[chan] = vcc;
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int open_rx_second(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct zatm_vcc *zatm_vcc;
|
|
|
|
unsigned long flags;
|
|
|
|
int pos,shift;
|
|
|
|
|
|
|
|
DPRINTK("open_rx_second (0x%x)\n",inb_p(0xc053));
|
|
|
|
zatm_dev = ZATM_DEV(vcc->dev);
|
|
|
|
zatm_vcc = ZATM_VCC(vcc);
|
|
|
|
if (!zatm_vcc->rx_chan) return 0;
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
/* should also handle VPI @@@ */
|
|
|
|
pos = vcc->vci >> 1;
|
|
|
|
shift = (1-(vcc->vci & 1)) << 4;
|
|
|
|
zpokel(zatm_dev,(zpeekl(zatm_dev,pos) & ~(0xffff << shift)) |
|
|
|
|
((zatm_vcc->rx_chan | uPD98401_RXLT_ENBL) << shift),pos);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void close_rx(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct zatm_vcc *zatm_vcc;
|
|
|
|
unsigned long flags;
|
|
|
|
int pos,shift;
|
|
|
|
|
|
|
|
zatm_vcc = ZATM_VCC(vcc);
|
|
|
|
zatm_dev = ZATM_DEV(vcc->dev);
|
|
|
|
if (!zatm_vcc->rx_chan) return;
|
|
|
|
DPRINTK("close_rx\n");
|
|
|
|
/* disable receiver */
|
|
|
|
if (vcc->vpi != ATM_VPI_UNSPEC && vcc->vci != ATM_VCI_UNSPEC) {
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
pos = vcc->vci >> 1;
|
|
|
|
shift = (1-(vcc->vci & 1)) << 4;
|
|
|
|
zpokel(zatm_dev,zpeekl(zatm_dev,pos) & ~(0xffff << shift),pos);
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_NOP,CMR);
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_NOP,CMR);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
}
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_DEACT_CHAN | uPD98401_CHAN_RT | (zatm_vcc->rx_chan <<
|
|
|
|
uPD98401_CHAN_ADDR_SHIFT),CMR);
|
|
|
|
zwait;
|
|
|
|
udelay(10); /* why oh why ... ? */
|
|
|
|
zout(uPD98401_CLOSE_CHAN | uPD98401_CHAN_RT | (zatm_vcc->rx_chan <<
|
|
|
|
uPD98401_CHAN_ADDR_SHIFT),CMR);
|
|
|
|
zwait;
|
|
|
|
if (!(zin(CMR) & uPD98401_CHAN_ADDR))
|
|
|
|
printk(KERN_CRIT DEV_LABEL "(itf %d): can't close RX channel "
|
|
|
|
"%d\n",vcc->dev->number,zatm_vcc->rx_chan);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
zatm_dev->rx_map[zatm_vcc->rx_chan] = NULL;
|
|
|
|
zatm_vcc->rx_chan = 0;
|
|
|
|
unuse_pool(vcc->dev,zatm_vcc->pool);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int start_rx(struct atm_dev *dev)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
int size,i;
|
|
|
|
|
|
|
|
DPRINTK("start_rx\n");
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
size = sizeof(struct atm_vcc *)*zatm_dev->chans;
|
2006-10-04 01:27:18 +02:00
|
|
|
zatm_dev->rx_map = kzalloc(size,GFP_KERNEL);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!zatm_dev->rx_map) return -ENOMEM;
|
|
|
|
/* set VPI/VCI split (use all VCIs and give what's left to VPIs) */
|
|
|
|
zpokel(zatm_dev,(1 << dev->ci_range.vci_bits)-1,uPD98401_VRR);
|
|
|
|
/* prepare free buffer pools */
|
|
|
|
for (i = 0; i <= ZATM_LAST_POOL; i++) {
|
|
|
|
zatm_dev->pool_info[i].ref_count = 0;
|
|
|
|
zatm_dev->pool_info[i].rqa_count = 0;
|
|
|
|
zatm_dev->pool_info[i].rqu_count = 0;
|
|
|
|
zatm_dev->pool_info[i].low_water = LOW_MARK;
|
|
|
|
zatm_dev->pool_info[i].high_water = HIGH_MARK;
|
|
|
|
zatm_dev->pool_info[i].offset = 0;
|
|
|
|
zatm_dev->pool_info[i].next_off = 0;
|
|
|
|
zatm_dev->pool_info[i].next_cnt = 0;
|
|
|
|
zatm_dev->pool_info[i].next_thres = OFF_CNG_THRES;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------------- TX ------------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
static int do_tx(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct atm_vcc *vcc;
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct zatm_vcc *zatm_vcc;
|
|
|
|
u32 *dsc;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
EVENT("do_tx\n",0,0);
|
|
|
|
DPRINTK("sending skb %p\n",skb);
|
|
|
|
vcc = ATM_SKB(skb)->vcc;
|
|
|
|
zatm_dev = ZATM_DEV(vcc->dev);
|
|
|
|
zatm_vcc = ZATM_VCC(vcc);
|
|
|
|
EVENT("iovcnt=%d\n",skb_shinfo(skb)->nr_frags,0);
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
if (!skb_shinfo(skb)->nr_frags) {
|
|
|
|
if (zatm_vcc->txing == RING_ENTRIES-1) {
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
return RING_BUSY;
|
|
|
|
}
|
|
|
|
zatm_vcc->txing++;
|
|
|
|
dsc = zatm_vcc->ring+zatm_vcc->ring_curr;
|
|
|
|
zatm_vcc->ring_curr = (zatm_vcc->ring_curr+RING_WORDS) &
|
|
|
|
(RING_ENTRIES*RING_WORDS-1);
|
|
|
|
dsc[1] = 0;
|
|
|
|
dsc[2] = skb->len;
|
|
|
|
dsc[3] = virt_to_bus(skb->data);
|
|
|
|
mb();
|
|
|
|
dsc[0] = uPD98401_TXPD_V | uPD98401_TXPD_DP | uPD98401_TXPD_SM
|
|
|
|
| (vcc->qos.aal == ATM_AAL5 ? uPD98401_TXPD_AAL5 : 0 |
|
|
|
|
(ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ?
|
|
|
|
uPD98401_CLPM_1 : uPD98401_CLPM_0));
|
|
|
|
EVENT("dsc (0x%lx)\n",(unsigned long) dsc,0);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
printk("NONONONOO!!!!\n");
|
|
|
|
dsc = NULL;
|
|
|
|
#if 0
|
|
|
|
u32 *put;
|
|
|
|
int i;
|
|
|
|
|
2006-01-10 22:09:01 +01:00
|
|
|
dsc = kmalloc(uPD98401_TXPD_SIZE * 2 +
|
|
|
|
uPD98401_TXBD_SIZE * ATM_SKB(skb)->iovcnt, GFP_ATOMIC);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!dsc) {
|
2006-01-10 22:09:01 +01:00
|
|
|
if (vcc->pop)
|
|
|
|
vcc->pop(vcc, skb);
|
|
|
|
else
|
|
|
|
dev_kfree_skb_irq(skb);
|
2005-04-17 00:20:36 +02:00
|
|
|
return -EAGAIN;
|
|
|
|
}
|
|
|
|
/* @@@ should check alignment */
|
|
|
|
put = dsc+8;
|
|
|
|
dsc[0] = uPD98401_TXPD_V | uPD98401_TXPD_DP |
|
|
|
|
(vcc->aal == ATM_AAL5 ? uPD98401_TXPD_AAL5 : 0 |
|
|
|
|
(ATM_SKB(skb)->atm_options & ATM_ATMOPT_CLP ?
|
|
|
|
uPD98401_CLPM_1 : uPD98401_CLPM_0));
|
|
|
|
dsc[1] = 0;
|
2006-01-10 22:09:01 +01:00
|
|
|
dsc[2] = ATM_SKB(skb)->iovcnt * uPD98401_TXBD_SIZE;
|
2005-04-17 00:20:36 +02:00
|
|
|
dsc[3] = virt_to_bus(put);
|
|
|
|
for (i = 0; i < ATM_SKB(skb)->iovcnt; i++) {
|
|
|
|
*put++ = ((struct iovec *) skb->data)[i].iov_len;
|
|
|
|
*put++ = virt_to_bus(((struct iovec *)
|
|
|
|
skb->data)[i].iov_base);
|
|
|
|
}
|
|
|
|
put[-2] |= uPD98401_TXBD_LAST;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
ZATM_PRV_DSC(skb) = dsc;
|
|
|
|
skb_queue_tail(&zatm_vcc->tx_queue,skb);
|
|
|
|
DPRINTK("QRP=0x%08lx\n",zpeekl(zatm_dev,zatm_vcc->tx_chan*VC_SIZE/4+
|
|
|
|
uPD98401_TXVC_QRP));
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_TX_READY | (zatm_vcc->tx_chan <<
|
|
|
|
uPD98401_CHAN_ADDR_SHIFT),CMR);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
EVENT("done\n",0,0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static inline void dequeue_tx(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
struct zatm_vcc *zatm_vcc;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
|
|
|
|
EVENT("dequeue_tx\n",0,0);
|
|
|
|
zatm_vcc = ZATM_VCC(vcc);
|
|
|
|
skb = skb_dequeue(&zatm_vcc->tx_queue);
|
|
|
|
if (!skb) {
|
|
|
|
printk(KERN_CRIT DEV_LABEL "(itf %d): dequeue_tx but not "
|
|
|
|
"txing\n",vcc->dev->number);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
#if 0 /* @@@ would fail on CLP */
|
|
|
|
if (*ZATM_PRV_DSC(skb) != (uPD98401_TXPD_V | uPD98401_TXPD_DP |
|
|
|
|
uPD98401_TXPD_SM | uPD98401_TXPD_AAL5)) printk("@#*$!!!! (%08x)\n",
|
|
|
|
*ZATM_PRV_DSC(skb));
|
|
|
|
#endif
|
|
|
|
*ZATM_PRV_DSC(skb) = 0; /* mark as invalid */
|
|
|
|
zatm_vcc->txing--;
|
|
|
|
if (vcc->pop) vcc->pop(vcc,skb);
|
|
|
|
else dev_kfree_skb_irq(skb);
|
|
|
|
while ((skb = skb_dequeue(&zatm_vcc->backlog)))
|
|
|
|
if (do_tx(skb) == RING_BUSY) {
|
|
|
|
skb_queue_head(&zatm_vcc->backlog,skb);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
atomic_inc(&vcc->stats->tx);
|
|
|
|
wake_up(&zatm_vcc->tx_wait);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void poll_tx(struct atm_dev *dev,int mbx)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
unsigned long pos;
|
|
|
|
u32 x;
|
|
|
|
|
|
|
|
EVENT("poll_tx\n",0,0);
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
pos = (zatm_dev->mbx_start[mbx] & ~0xffffUL) | zin(MTA(mbx));
|
|
|
|
while (x = zin(MWA(mbx)), (pos & 0xffff) != x) {
|
|
|
|
int chan;
|
|
|
|
|
|
|
|
#if 1
|
|
|
|
u32 data,*addr;
|
|
|
|
|
|
|
|
EVENT("MBX: host 0x%lx, nic 0x%x\n",pos,x);
|
|
|
|
addr = (u32 *) pos;
|
|
|
|
data = *addr;
|
|
|
|
chan = (data & uPD98401_TXI_CONN) >> uPD98401_TXI_CONN_SHIFT;
|
|
|
|
EVENT("addr = 0x%lx, data = 0x%08x,",(unsigned long) addr,
|
|
|
|
data);
|
|
|
|
EVENT("chan = %d\n",chan,0);
|
|
|
|
#else
|
|
|
|
NO !
|
|
|
|
chan = (zatm_dev->mbx_start[mbx][pos >> 2] & uPD98401_TXI_CONN)
|
|
|
|
>> uPD98401_TXI_CONN_SHIFT;
|
|
|
|
#endif
|
|
|
|
if (chan < zatm_dev->chans && zatm_dev->tx_map[chan])
|
|
|
|
dequeue_tx(zatm_dev->tx_map[chan]);
|
|
|
|
else {
|
|
|
|
printk(KERN_CRIT DEV_LABEL "(itf %d): TX indication "
|
|
|
|
"for non-existing channel %d\n",dev->number,chan);
|
|
|
|
event_dump();
|
|
|
|
}
|
|
|
|
if (((pos += 4) & 0xffff) == zatm_dev->mbx_end[mbx])
|
|
|
|
pos = zatm_dev->mbx_start[mbx];
|
|
|
|
}
|
|
|
|
zout(pos & 0xffff,MTA(mbx));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* BUG BUG BUG: Doesn't handle "new-style" rate specification yet.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int alloc_shaper(struct atm_dev *dev,int *pcr,int min,int max,int ubr)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
unsigned long flags;
|
|
|
|
unsigned long i,m,c;
|
|
|
|
int shaper;
|
|
|
|
|
|
|
|
DPRINTK("alloc_shaper (min = %d, max = %d)\n",min,max);
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
if (!zatm_dev->free_shapers) return -EAGAIN;
|
|
|
|
for (shaper = 0; !((zatm_dev->free_shapers >> shaper) & 1); shaper++);
|
|
|
|
zatm_dev->free_shapers &= ~1 << shaper;
|
|
|
|
if (ubr) {
|
|
|
|
c = 5;
|
|
|
|
i = m = 1;
|
|
|
|
zatm_dev->ubr_ref_cnt++;
|
|
|
|
zatm_dev->ubr = shaper;
|
2006-10-04 01:28:31 +02:00
|
|
|
*pcr = 0;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (min) {
|
|
|
|
if (min <= 255) {
|
|
|
|
i = min;
|
|
|
|
m = ATM_OC3_PCR;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
i = 255;
|
|
|
|
m = ATM_OC3_PCR*255/min;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (max > zatm_dev->tx_bw) max = zatm_dev->tx_bw;
|
|
|
|
if (max <= 255) {
|
|
|
|
i = max;
|
|
|
|
m = ATM_OC3_PCR;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
i = 255;
|
|
|
|
m = (ATM_OC3_PCR*255+max-1)/max;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (i > m) {
|
|
|
|
printk(KERN_CRIT DEV_LABEL "shaper algorithm botched "
|
|
|
|
"[%d,%d] -> i=%ld,m=%ld\n",min,max,i,m);
|
|
|
|
m = i;
|
|
|
|
}
|
|
|
|
*pcr = i*ATM_OC3_PCR/m;
|
|
|
|
c = 20; /* @@@ should use max_cdv ! */
|
|
|
|
if ((min && *pcr < min) || (max && *pcr > max)) return -EINVAL;
|
|
|
|
if (zatm_dev->tx_bw < *pcr) return -EAGAIN;
|
|
|
|
zatm_dev->tx_bw -= *pcr;
|
|
|
|
}
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
DPRINTK("i = %d, m = %d, PCR = %d\n",i,m,*pcr);
|
|
|
|
zpokel(zatm_dev,(i << uPD98401_IM_I_SHIFT) | m,uPD98401_IM(shaper));
|
|
|
|
zpokel(zatm_dev,c << uPD98401_PC_C_SHIFT,uPD98401_PC(shaper));
|
|
|
|
zpokel(zatm_dev,0,uPD98401_X(shaper));
|
|
|
|
zpokel(zatm_dev,0,uPD98401_Y(shaper));
|
|
|
|
zpokel(zatm_dev,uPD98401_PS_E,uPD98401_PS(shaper));
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
return shaper;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void dealloc_shaper(struct atm_dev *dev,int shaper)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
if (shaper == zatm_dev->ubr) {
|
|
|
|
if (--zatm_dev->ubr_ref_cnt) return;
|
|
|
|
zatm_dev->ubr = -1;
|
|
|
|
}
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
zpokel(zatm_dev,zpeekl(zatm_dev,uPD98401_PS(shaper)) & ~uPD98401_PS_E,
|
|
|
|
uPD98401_PS(shaper));
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
zatm_dev->free_shapers |= 1 << shaper;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void close_tx(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct zatm_vcc *zatm_vcc;
|
|
|
|
unsigned long flags;
|
|
|
|
int chan;
|
|
|
|
|
|
|
|
zatm_vcc = ZATM_VCC(vcc);
|
|
|
|
zatm_dev = ZATM_DEV(vcc->dev);
|
|
|
|
chan = zatm_vcc->tx_chan;
|
|
|
|
if (!chan) return;
|
|
|
|
DPRINTK("close_tx\n");
|
|
|
|
if (skb_peek(&zatm_vcc->backlog)) {
|
|
|
|
printk("waiting for backlog to drain ...\n");
|
|
|
|
event_dump();
|
|
|
|
wait_event(zatm_vcc->tx_wait, !skb_peek(&zatm_vcc->backlog));
|
|
|
|
}
|
|
|
|
if (skb_peek(&zatm_vcc->tx_queue)) {
|
|
|
|
printk("waiting for TX queue to drain ...\n");
|
|
|
|
event_dump();
|
|
|
|
wait_event(zatm_vcc->tx_wait, !skb_peek(&zatm_vcc->tx_queue));
|
|
|
|
}
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
#if 0
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_DEACT_CHAN | (chan << uPD98401_CHAN_ADDR_SHIFT),CMR);
|
|
|
|
#endif
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_CLOSE_CHAN | (chan << uPD98401_CHAN_ADDR_SHIFT),CMR);
|
|
|
|
zwait;
|
|
|
|
if (!(zin(CMR) & uPD98401_CHAN_ADDR))
|
|
|
|
printk(KERN_CRIT DEV_LABEL "(itf %d): can't close TX channel "
|
|
|
|
"%d\n",vcc->dev->number,chan);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
zatm_vcc->tx_chan = 0;
|
|
|
|
zatm_dev->tx_map[chan] = NULL;
|
|
|
|
if (zatm_vcc->shaper != zatm_dev->ubr) {
|
|
|
|
zatm_dev->tx_bw += vcc->qos.txtp.min_pcr;
|
|
|
|
dealloc_shaper(vcc->dev,zatm_vcc->shaper);
|
|
|
|
}
|
2005-06-02 22:04:07 +02:00
|
|
|
kfree(zatm_vcc->ring);
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int open_tx_first(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct zatm_vcc *zatm_vcc;
|
|
|
|
unsigned long flags;
|
|
|
|
u32 *loop;
|
|
|
|
unsigned short chan;
|
|
|
|
int pcr,unlimited;
|
|
|
|
|
|
|
|
DPRINTK("open_tx_first\n");
|
|
|
|
zatm_dev = ZATM_DEV(vcc->dev);
|
|
|
|
zatm_vcc = ZATM_VCC(vcc);
|
|
|
|
zatm_vcc->tx_chan = 0;
|
|
|
|
if (vcc->qos.txtp.traffic_class == ATM_NONE) return 0;
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_OPEN_CHAN,CMR);
|
|
|
|
zwait;
|
|
|
|
DPRINTK("0x%x 0x%x\n",zin(CMR),zin(CER));
|
|
|
|
chan = (zin(CMR) & uPD98401_CHAN_ADDR) >> uPD98401_CHAN_ADDR_SHIFT;
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
DPRINTK("chan is %d\n",chan);
|
|
|
|
if (!chan) return -EAGAIN;
|
|
|
|
unlimited = vcc->qos.txtp.traffic_class == ATM_UBR &&
|
|
|
|
(!vcc->qos.txtp.max_pcr || vcc->qos.txtp.max_pcr == ATM_MAX_PCR ||
|
|
|
|
vcc->qos.txtp.max_pcr >= ATM_OC3_PCR);
|
|
|
|
if (unlimited && zatm_dev->ubr != -1) zatm_vcc->shaper = zatm_dev->ubr;
|
|
|
|
else {
|
|
|
|
if (unlimited) vcc->qos.txtp.max_sdu = ATM_MAX_AAL5_PDU;
|
|
|
|
if ((zatm_vcc->shaper = alloc_shaper(vcc->dev,&pcr,
|
|
|
|
vcc->qos.txtp.min_pcr,vcc->qos.txtp.max_pcr,unlimited))
|
|
|
|
< 0) {
|
|
|
|
close_tx(vcc);
|
|
|
|
return zatm_vcc->shaper;
|
|
|
|
}
|
|
|
|
if (pcr > ATM_OC3_PCR) pcr = ATM_OC3_PCR;
|
|
|
|
vcc->qos.txtp.min_pcr = vcc->qos.txtp.max_pcr = pcr;
|
|
|
|
}
|
|
|
|
zatm_vcc->tx_chan = chan;
|
|
|
|
skb_queue_head_init(&zatm_vcc->tx_queue);
|
|
|
|
init_waitqueue_head(&zatm_vcc->tx_wait);
|
|
|
|
/* initialize ring */
|
2006-10-04 01:27:18 +02:00
|
|
|
zatm_vcc->ring = kzalloc(RING_SIZE,GFP_KERNEL);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!zatm_vcc->ring) return -ENOMEM;
|
|
|
|
loop = zatm_vcc->ring+RING_ENTRIES*RING_WORDS;
|
|
|
|
loop[0] = uPD98401_TXPD_V;
|
|
|
|
loop[1] = loop[2] = 0;
|
|
|
|
loop[3] = virt_to_bus(zatm_vcc->ring);
|
|
|
|
zatm_vcc->ring_curr = 0;
|
|
|
|
zatm_vcc->txing = 0;
|
|
|
|
skb_queue_head_init(&zatm_vcc->backlog);
|
|
|
|
zpokel(zatm_dev,virt_to_bus(zatm_vcc->ring),
|
|
|
|
chan*VC_SIZE/4+uPD98401_TXVC_QRP);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int open_tx_second(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct zatm_vcc *zatm_vcc;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
DPRINTK("open_tx_second\n");
|
|
|
|
zatm_dev = ZATM_DEV(vcc->dev);
|
|
|
|
zatm_vcc = ZATM_VCC(vcc);
|
|
|
|
if (!zatm_vcc->tx_chan) return 0;
|
|
|
|
/* set up VC descriptor */
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
zpokel(zatm_dev,0,zatm_vcc->tx_chan*VC_SIZE/4);
|
|
|
|
zpokel(zatm_dev,uPD98401_TXVC_L | (zatm_vcc->shaper <<
|
|
|
|
uPD98401_TXVC_SHP_SHIFT) | (vcc->vpi << uPD98401_TXVC_VPI_SHIFT) |
|
|
|
|
vcc->vci,zatm_vcc->tx_chan*VC_SIZE/4+1);
|
|
|
|
zpokel(zatm_dev,0,zatm_vcc->tx_chan*VC_SIZE/4+2);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
zatm_dev->tx_map[zatm_vcc->tx_chan] = vcc;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int start_tx(struct atm_dev *dev)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
DPRINTK("start_tx\n");
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
2006-12-13 09:35:56 +01:00
|
|
|
zatm_dev->tx_map = kmalloc(sizeof(struct atm_vcc *)*
|
2005-04-17 00:20:36 +02:00
|
|
|
zatm_dev->chans,GFP_KERNEL);
|
|
|
|
if (!zatm_dev->tx_map) return -ENOMEM;
|
|
|
|
zatm_dev->tx_bw = ATM_OC3_PCR;
|
|
|
|
zatm_dev->free_shapers = (1 << NR_SHAPERS)-1;
|
|
|
|
zatm_dev->ubr = -1;
|
|
|
|
zatm_dev->ubr_ref_cnt = 0;
|
|
|
|
/* initialize shapers */
|
|
|
|
for (i = 0; i < NR_SHAPERS; i++) zpokel(zatm_dev,0,uPD98401_PS(i));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*------------------------------- interrupts --------------------------------*/
|
|
|
|
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 15:55:46 +02:00
|
|
|
static irqreturn_t zatm_int(int irq,void *dev_id)
|
2005-04-17 00:20:36 +02:00
|
|
|
{
|
|
|
|
struct atm_dev *dev;
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
u32 reason;
|
|
|
|
int handled = 0;
|
|
|
|
|
|
|
|
dev = dev_id;
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
while ((reason = zin(GSR))) {
|
|
|
|
handled = 1;
|
|
|
|
EVENT("reason 0x%x\n",reason,0);
|
|
|
|
if (reason & uPD98401_INT_PI) {
|
|
|
|
EVENT("PHY int\n",0,0);
|
|
|
|
dev->phy->interrupt(dev);
|
|
|
|
}
|
|
|
|
if (reason & uPD98401_INT_RQA) {
|
|
|
|
unsigned long pools;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
pools = zin(RQA);
|
|
|
|
EVENT("RQA (0x%08x)\n",pools,0);
|
|
|
|
for (i = 0; pools; i++) {
|
|
|
|
if (pools & 1) {
|
|
|
|
refill_pool(dev,i);
|
|
|
|
zatm_dev->pool_info[i].rqa_count++;
|
|
|
|
}
|
|
|
|
pools >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (reason & uPD98401_INT_RQU) {
|
|
|
|
unsigned long pools;
|
|
|
|
int i;
|
|
|
|
pools = zin(RQU);
|
|
|
|
printk(KERN_WARNING DEV_LABEL "(itf %d): RQU 0x%08lx\n",
|
|
|
|
dev->number,pools);
|
|
|
|
event_dump();
|
|
|
|
for (i = 0; pools; i++) {
|
|
|
|
if (pools & 1) {
|
|
|
|
refill_pool(dev,i);
|
|
|
|
zatm_dev->pool_info[i].rqu_count++;
|
|
|
|
}
|
|
|
|
pools >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* don't handle RD */
|
|
|
|
if (reason & uPD98401_INT_SPE)
|
|
|
|
printk(KERN_ALERT DEV_LABEL "(itf %d): system parity "
|
|
|
|
"error at 0x%08x\n",dev->number,zin(ADDR));
|
|
|
|
if (reason & uPD98401_INT_CPE)
|
|
|
|
printk(KERN_ALERT DEV_LABEL "(itf %d): control memory "
|
|
|
|
"parity error at 0x%08x\n",dev->number,zin(ADDR));
|
|
|
|
if (reason & uPD98401_INT_SBE) {
|
|
|
|
printk(KERN_ALERT DEV_LABEL "(itf %d): system bus "
|
|
|
|
"error at 0x%08x\n",dev->number,zin(ADDR));
|
|
|
|
event_dump();
|
|
|
|
}
|
|
|
|
/* don't handle IND */
|
|
|
|
if (reason & uPD98401_INT_MF) {
|
|
|
|
printk(KERN_CRIT DEV_LABEL "(itf %d): mailbox full "
|
|
|
|
"(0x%x)\n",dev->number,(reason & uPD98401_INT_MF)
|
|
|
|
>> uPD98401_INT_MF_SHIFT);
|
|
|
|
event_dump();
|
|
|
|
/* @@@ should try to recover */
|
|
|
|
}
|
|
|
|
if (reason & uPD98401_INT_MM) {
|
|
|
|
if (reason & 1) poll_rx(dev,0);
|
|
|
|
if (reason & 2) poll_rx(dev,1);
|
|
|
|
if (reason & 4) poll_tx(dev,2);
|
|
|
|
if (reason & 8) poll_tx(dev,3);
|
|
|
|
}
|
|
|
|
/* @@@ handle RCRn */
|
|
|
|
}
|
|
|
|
return IRQ_RETVAL(handled);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*----------------------------- (E)EPROM access -----------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
static void __devinit eprom_set(struct zatm_dev *zatm_dev,unsigned long value,
|
|
|
|
unsigned short cmd)
|
|
|
|
{
|
|
|
|
int error;
|
|
|
|
|
|
|
|
if ((error = pci_write_config_dword(zatm_dev->pci_dev,cmd,value)))
|
|
|
|
printk(KERN_ERR DEV_LABEL ": PCI write failed (0x%02x)\n",
|
|
|
|
error);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static unsigned long __devinit eprom_get(struct zatm_dev *zatm_dev,
|
|
|
|
unsigned short cmd)
|
|
|
|
{
|
|
|
|
unsigned int value;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
if ((error = pci_read_config_dword(zatm_dev->pci_dev,cmd,&value)))
|
|
|
|
printk(KERN_ERR DEV_LABEL ": PCI read failed (0x%02x)\n",
|
|
|
|
error);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void __devinit eprom_put_bits(struct zatm_dev *zatm_dev,
|
|
|
|
unsigned long data,int bits,unsigned short cmd)
|
|
|
|
{
|
|
|
|
unsigned long value;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = bits-1; i >= 0; i--) {
|
|
|
|
value = ZEPROM_CS | (((data >> i) & 1) ? ZEPROM_DI : 0);
|
|
|
|
eprom_set(zatm_dev,value,cmd);
|
|
|
|
eprom_set(zatm_dev,value | ZEPROM_SK,cmd);
|
|
|
|
eprom_set(zatm_dev,value,cmd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void __devinit eprom_get_byte(struct zatm_dev *zatm_dev,
|
|
|
|
unsigned char *byte,unsigned short cmd)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
*byte = 0;
|
|
|
|
for (i = 8; i; i--) {
|
|
|
|
eprom_set(zatm_dev,ZEPROM_CS,cmd);
|
|
|
|
eprom_set(zatm_dev,ZEPROM_CS | ZEPROM_SK,cmd);
|
|
|
|
*byte <<= 1;
|
|
|
|
if (eprom_get(zatm_dev,cmd) & ZEPROM_DO) *byte |= 1;
|
|
|
|
eprom_set(zatm_dev,ZEPROM_CS,cmd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static unsigned char __devinit eprom_try_esi(struct atm_dev *dev,
|
|
|
|
unsigned short cmd,int offset,int swap)
|
|
|
|
{
|
|
|
|
unsigned char buf[ZEPROM_SIZE];
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
for (i = 0; i < ZEPROM_SIZE; i += 2) {
|
|
|
|
eprom_set(zatm_dev,ZEPROM_CS,cmd); /* select EPROM */
|
|
|
|
eprom_put_bits(zatm_dev,ZEPROM_CMD_READ,ZEPROM_CMD_LEN,cmd);
|
|
|
|
eprom_put_bits(zatm_dev,i >> 1,ZEPROM_ADDR_LEN,cmd);
|
|
|
|
eprom_get_byte(zatm_dev,buf+i+swap,cmd);
|
|
|
|
eprom_get_byte(zatm_dev,buf+i+1-swap,cmd);
|
|
|
|
eprom_set(zatm_dev,0,cmd); /* deselect EPROM */
|
|
|
|
}
|
|
|
|
memcpy(dev->esi,buf+offset,ESI_LEN);
|
|
|
|
return memcmp(dev->esi,"\0\0\0\0\0",ESI_LEN); /* assumes ESI_LEN == 6 */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void __devinit eprom_get_esi(struct atm_dev *dev)
|
|
|
|
{
|
|
|
|
if (eprom_try_esi(dev,ZEPROM_V1_REG,ZEPROM_V1_ESI_OFF,1)) return;
|
|
|
|
(void) eprom_try_esi(dev,ZEPROM_V2_REG,ZEPROM_V2_ESI_OFF,0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*--------------------------------- entries ---------------------------------*/
|
|
|
|
|
|
|
|
|
|
|
|
static int __init zatm_init(struct atm_dev *dev)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct pci_dev *pci_dev;
|
|
|
|
unsigned short command;
|
|
|
|
unsigned char revision;
|
|
|
|
int error,i,last;
|
|
|
|
unsigned long t0,t1,t2;
|
|
|
|
|
|
|
|
DPRINTK(">zatm_init\n");
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
spin_lock_init(&zatm_dev->lock);
|
|
|
|
pci_dev = zatm_dev->pci_dev;
|
|
|
|
zatm_dev->base = pci_resource_start(pci_dev, 0);
|
|
|
|
zatm_dev->irq = pci_dev->irq;
|
|
|
|
if ((error = pci_read_config_word(pci_dev,PCI_COMMAND,&command)) ||
|
|
|
|
(error = pci_read_config_byte(pci_dev,PCI_REVISION_ID,&revision))) {
|
|
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): init error 0x%02x\n",
|
|
|
|
dev->number,error);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
if ((error = pci_write_config_word(pci_dev,PCI_COMMAND,
|
|
|
|
command | PCI_COMMAND_IO | PCI_COMMAND_MASTER))) {
|
|
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): can't enable IO (0x%02x)"
|
|
|
|
"\n",dev->number,error);
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
eprom_get_esi(dev);
|
|
|
|
printk(KERN_NOTICE DEV_LABEL "(itf %d): rev.%d,base=0x%x,irq=%d,",
|
|
|
|
dev->number,revision,zatm_dev->base,zatm_dev->irq);
|
|
|
|
/* reset uPD98401 */
|
|
|
|
zout(0,SWR);
|
|
|
|
while (!(zin(GSR) & uPD98401_INT_IND));
|
|
|
|
zout(uPD98401_GMR_ONE /*uPD98401_BURST4*/,GMR);
|
|
|
|
last = MAX_CRAM_SIZE;
|
|
|
|
for (i = last-RAM_INCREMENT; i >= 0; i -= RAM_INCREMENT) {
|
|
|
|
zpokel(zatm_dev,0x55555555,i);
|
|
|
|
if (zpeekl(zatm_dev,i) != 0x55555555) last = i;
|
|
|
|
else {
|
|
|
|
zpokel(zatm_dev,0xAAAAAAAA,i);
|
|
|
|
if (zpeekl(zatm_dev,i) != 0xAAAAAAAA) last = i;
|
|
|
|
else zpokel(zatm_dev,i,i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (i = 0; i < last; i += RAM_INCREMENT)
|
|
|
|
if (zpeekl(zatm_dev,i) != i) break;
|
|
|
|
zatm_dev->mem = i << 2;
|
|
|
|
while (i) zpokel(zatm_dev,0,--i);
|
|
|
|
/* reset again to rebuild memory pointers */
|
|
|
|
zout(0,SWR);
|
|
|
|
while (!(zin(GSR) & uPD98401_INT_IND));
|
|
|
|
zout(uPD98401_GMR_ONE | uPD98401_BURST8 | uPD98401_BURST4 |
|
|
|
|
uPD98401_BURST2 | uPD98401_GMR_PM | uPD98401_GMR_DR,GMR);
|
|
|
|
/* TODO: should shrink allocation now */
|
|
|
|
printk("mem=%dkB,%s (",zatm_dev->mem >> 10,zatm_dev->copper ? "UTP" :
|
|
|
|
"MMF");
|
|
|
|
for (i = 0; i < ESI_LEN; i++)
|
|
|
|
printk("%02X%s",dev->esi[i],i == ESI_LEN-1 ? ")\n" : "-");
|
|
|
|
do {
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
t0 = zpeekl(zatm_dev,uPD98401_TSR);
|
|
|
|
udelay(10);
|
|
|
|
t1 = zpeekl(zatm_dev,uPD98401_TSR);
|
|
|
|
udelay(1010);
|
|
|
|
t2 = zpeekl(zatm_dev,uPD98401_TSR);
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
}
|
|
|
|
while (t0 > t1 || t1 > t2); /* loop if wrapping ... */
|
|
|
|
zatm_dev->khz = t2-2*t1+t0;
|
|
|
|
printk(KERN_NOTICE DEV_LABEL "(itf %d): uPD98401 %d.%d at %d.%03d "
|
|
|
|
"MHz\n",dev->number,
|
|
|
|
(zin(VER) & uPD98401_MAJOR) >> uPD98401_MAJOR_SHIFT,
|
|
|
|
zin(VER) & uPD98401_MINOR,zatm_dev->khz/1000,zatm_dev->khz % 1000);
|
|
|
|
return uPD98402_init(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int __init zatm_start(struct atm_dev *dev)
|
|
|
|
{
|
2005-07-20 21:01:46 +02:00
|
|
|
struct zatm_dev *zatm_dev = ZATM_DEV(dev);
|
|
|
|
struct pci_dev *pdev = zatm_dev->pci_dev;
|
2005-04-17 00:20:36 +02:00
|
|
|
unsigned long curr;
|
|
|
|
int pools,vccs,rx;
|
2005-07-20 21:01:46 +02:00
|
|
|
int error, i, ld;
|
2005-04-17 00:20:36 +02:00
|
|
|
|
|
|
|
DPRINTK("zatm_start\n");
|
|
|
|
zatm_dev->rx_map = zatm_dev->tx_map = NULL;
|
2005-07-20 21:01:46 +02:00
|
|
|
for (i = 0; i < NR_MBX; i++)
|
|
|
|
zatm_dev->mbx_start[i] = 0;
|
2006-07-02 04:29:38 +02:00
|
|
|
error = request_irq(zatm_dev->irq, zatm_int, IRQF_SHARED, DEV_LABEL, dev);
|
2005-07-20 21:01:46 +02:00
|
|
|
if (error < 0) {
|
|
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): IRQ%d is already in use\n",
|
|
|
|
dev->number,zatm_dev->irq);
|
|
|
|
goto done;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
/* define memory regions */
|
|
|
|
pools = NR_POOLS;
|
|
|
|
if (NR_SHAPERS*SHAPER_SIZE > pools*POOL_SIZE)
|
|
|
|
pools = NR_SHAPERS*SHAPER_SIZE/POOL_SIZE;
|
|
|
|
vccs = (zatm_dev->mem-NR_SHAPERS*SHAPER_SIZE-pools*POOL_SIZE)/
|
|
|
|
(2*VC_SIZE+RX_SIZE);
|
|
|
|
ld = -1;
|
|
|
|
for (rx = 1; rx < vccs; rx <<= 1) ld++;
|
|
|
|
dev->ci_range.vpi_bits = 0; /* @@@ no VPI for now */
|
|
|
|
dev->ci_range.vci_bits = ld;
|
|
|
|
dev->link_rate = ATM_OC3_PCR;
|
|
|
|
zatm_dev->chans = vccs; /* ??? */
|
|
|
|
curr = rx*RX_SIZE/4;
|
|
|
|
DPRINTK("RX pool 0x%08lx\n",curr);
|
|
|
|
zpokel(zatm_dev,curr,uPD98401_PMA); /* receive pool */
|
|
|
|
zatm_dev->pool_base = curr;
|
|
|
|
curr += pools*POOL_SIZE/4;
|
|
|
|
DPRINTK("Shapers 0x%08lx\n",curr);
|
|
|
|
zpokel(zatm_dev,curr,uPD98401_SMA); /* shapers */
|
|
|
|
curr += NR_SHAPERS*SHAPER_SIZE/4;
|
|
|
|
DPRINTK("Free 0x%08lx\n",curr);
|
|
|
|
zpokel(zatm_dev,curr,uPD98401_TOS); /* free pool */
|
|
|
|
printk(KERN_INFO DEV_LABEL "(itf %d): %d shapers, %d pools, %d RX, "
|
|
|
|
"%ld VCs\n",dev->number,NR_SHAPERS,pools,rx,
|
|
|
|
(zatm_dev->mem-curr*4)/VC_SIZE);
|
|
|
|
/* create mailboxes */
|
2005-07-20 21:01:46 +02:00
|
|
|
for (i = 0; i < NR_MBX; i++) {
|
|
|
|
void *mbx;
|
|
|
|
dma_addr_t mbx_dma;
|
|
|
|
|
|
|
|
if (!mbx_entries[i])
|
|
|
|
continue;
|
|
|
|
mbx = pci_alloc_consistent(pdev, 2*MBX_SIZE(i), &mbx_dma);
|
|
|
|
if (!mbx) {
|
|
|
|
error = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Alignment provided by pci_alloc_consistent() isn't enough
|
|
|
|
* for this device.
|
|
|
|
*/
|
|
|
|
if (((unsigned long)mbx ^ mbx_dma) & 0xffff) {
|
|
|
|
printk(KERN_ERR DEV_LABEL "(itf %d): system "
|
|
|
|
"bus incompatible with driver\n", dev->number);
|
|
|
|
pci_free_consistent(pdev, 2*MBX_SIZE(i), mbx, mbx_dma);
|
|
|
|
error = -ENODEV;
|
|
|
|
goto out;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
2005-07-20 21:01:46 +02:00
|
|
|
DPRINTK("mbx@0x%08lx-0x%08lx\n", mbx, mbx + MBX_SIZE(i));
|
|
|
|
zatm_dev->mbx_start[i] = (unsigned long)mbx;
|
|
|
|
zatm_dev->mbx_dma[i] = mbx_dma;
|
|
|
|
zatm_dev->mbx_end[i] = (zatm_dev->mbx_start[i] + MBX_SIZE(i)) &
|
|
|
|
0xffff;
|
|
|
|
zout(mbx_dma >> 16, MSH(i));
|
|
|
|
zout(mbx_dma, MSL(i));
|
|
|
|
zout(zatm_dev->mbx_end[i], MBA(i));
|
|
|
|
zout((unsigned long)mbx & 0xffff, MTA(i));
|
|
|
|
zout((unsigned long)mbx & 0xffff, MWA(i));
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
error = start_tx(dev);
|
2005-07-20 21:01:46 +02:00
|
|
|
if (error)
|
|
|
|
goto out;
|
2005-04-17 00:20:36 +02:00
|
|
|
error = start_rx(dev);
|
2005-07-20 21:01:46 +02:00
|
|
|
if (error)
|
|
|
|
goto out_tx;
|
2005-04-17 00:20:36 +02:00
|
|
|
error = dev->phy->start(dev);
|
2005-07-20 21:01:46 +02:00
|
|
|
if (error)
|
|
|
|
goto out_rx;
|
2005-04-17 00:20:36 +02:00
|
|
|
zout(0xffffffff,IMR); /* enable interrupts */
|
|
|
|
/* enable TX & RX */
|
|
|
|
zout(zin(GMR) | uPD98401_GMR_SE | uPD98401_GMR_RE,GMR);
|
2005-07-20 21:01:46 +02:00
|
|
|
done:
|
|
|
|
return error;
|
|
|
|
|
|
|
|
out_rx:
|
2005-06-02 22:04:07 +02:00
|
|
|
kfree(zatm_dev->rx_map);
|
2005-07-20 21:01:46 +02:00
|
|
|
out_tx:
|
2005-06-02 22:04:07 +02:00
|
|
|
kfree(zatm_dev->tx_map);
|
2005-07-20 21:01:46 +02:00
|
|
|
out:
|
|
|
|
while (i-- > 0) {
|
|
|
|
pci_free_consistent(pdev, 2*MBX_SIZE(i),
|
|
|
|
(void *)zatm_dev->mbx_start[i],
|
|
|
|
zatm_dev->mbx_dma[i]);
|
|
|
|
}
|
2005-04-17 00:20:36 +02:00
|
|
|
free_irq(zatm_dev->irq, dev);
|
2005-07-20 21:01:46 +02:00
|
|
|
goto done;
|
2005-04-17 00:20:36 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void zatm_close(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
DPRINTK(">zatm_close\n");
|
|
|
|
if (!ZATM_VCC(vcc)) return;
|
|
|
|
clear_bit(ATM_VF_READY,&vcc->flags);
|
|
|
|
close_rx(vcc);
|
|
|
|
EVENT("close_tx\n",0,0);
|
|
|
|
close_tx(vcc);
|
|
|
|
DPRINTK("zatm_close: done waiting\n");
|
|
|
|
/* deallocate memory */
|
|
|
|
kfree(ZATM_VCC(vcc));
|
|
|
|
vcc->dev_data = NULL;
|
|
|
|
clear_bit(ATM_VF_ADDR,&vcc->flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int zatm_open(struct atm_vcc *vcc)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
struct zatm_vcc *zatm_vcc;
|
|
|
|
short vpi = vcc->vpi;
|
|
|
|
int vci = vcc->vci;
|
|
|
|
int error;
|
|
|
|
|
|
|
|
DPRINTK(">zatm_open\n");
|
|
|
|
zatm_dev = ZATM_DEV(vcc->dev);
|
|
|
|
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_AAL5) return -EINVAL; /* @@@ AAL0 */
|
|
|
|
DPRINTK(DEV_LABEL "(itf %d): open %d.%d\n",vcc->dev->number,vcc->vpi,
|
|
|
|
vcc->vci);
|
|
|
|
if (!test_bit(ATM_VF_PARTIAL,&vcc->flags)) {
|
|
|
|
zatm_vcc = kmalloc(sizeof(struct zatm_vcc),GFP_KERNEL);
|
|
|
|
if (!zatm_vcc) {
|
|
|
|
clear_bit(ATM_VF_ADDR,&vcc->flags);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
vcc->dev_data = zatm_vcc;
|
|
|
|
ZATM_VCC(vcc)->tx_chan = 0; /* for zatm_close after open_rx */
|
|
|
|
if ((error = open_rx_first(vcc))) {
|
|
|
|
zatm_close(vcc);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
if ((error = open_tx_first(vcc))) {
|
|
|
|
zatm_close(vcc);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (vci == ATM_VPI_UNSPEC || vpi == ATM_VCI_UNSPEC) return 0;
|
|
|
|
if ((error = open_rx_second(vcc))) {
|
|
|
|
zatm_close(vcc);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
if ((error = open_tx_second(vcc))) {
|
|
|
|
zatm_close(vcc);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
set_bit(ATM_VF_READY,&vcc->flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int zatm_change_qos(struct atm_vcc *vcc,struct atm_qos *qos,int flags)
|
|
|
|
{
|
|
|
|
printk("Not yet implemented\n");
|
|
|
|
return -ENOSYS;
|
|
|
|
/* @@@ */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int zatm_ioctl(struct atm_dev *dev,unsigned int cmd,void __user *arg)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
unsigned long flags;
|
|
|
|
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
switch (cmd) {
|
|
|
|
case ZATM_GETPOOLZ:
|
|
|
|
if (!capable(CAP_NET_ADMIN)) return -EPERM;
|
|
|
|
/* fall through */
|
|
|
|
case ZATM_GETPOOL:
|
|
|
|
{
|
|
|
|
struct zatm_pool_info info;
|
|
|
|
int pool;
|
|
|
|
|
|
|
|
if (get_user(pool,
|
|
|
|
&((struct zatm_pool_req __user *) arg)->pool_num))
|
|
|
|
return -EFAULT;
|
|
|
|
if (pool < 0 || pool > ZATM_LAST_POOL)
|
|
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
info = zatm_dev->pool_info[pool];
|
|
|
|
if (cmd == ZATM_GETPOOLZ) {
|
|
|
|
zatm_dev->pool_info[pool].rqa_count = 0;
|
|
|
|
zatm_dev->pool_info[pool].rqu_count = 0;
|
|
|
|
}
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
return copy_to_user(
|
|
|
|
&((struct zatm_pool_req __user *) arg)->info,
|
|
|
|
&info,sizeof(info)) ? -EFAULT : 0;
|
|
|
|
}
|
|
|
|
case ZATM_SETPOOL:
|
|
|
|
{
|
|
|
|
struct zatm_pool_info info;
|
|
|
|
int pool;
|
|
|
|
|
|
|
|
if (!capable(CAP_NET_ADMIN)) return -EPERM;
|
|
|
|
if (get_user(pool,
|
|
|
|
&((struct zatm_pool_req __user *) arg)->pool_num))
|
|
|
|
return -EFAULT;
|
|
|
|
if (pool < 0 || pool > ZATM_LAST_POOL)
|
|
|
|
return -EINVAL;
|
|
|
|
if (copy_from_user(&info,
|
|
|
|
&((struct zatm_pool_req __user *) arg)->info,
|
|
|
|
sizeof(info))) return -EFAULT;
|
|
|
|
if (!info.low_water)
|
|
|
|
info.low_water = zatm_dev->
|
|
|
|
pool_info[pool].low_water;
|
|
|
|
if (!info.high_water)
|
|
|
|
info.high_water = zatm_dev->
|
|
|
|
pool_info[pool].high_water;
|
|
|
|
if (!info.next_thres)
|
|
|
|
info.next_thres = zatm_dev->
|
|
|
|
pool_info[pool].next_thres;
|
|
|
|
if (info.low_water >= info.high_water ||
|
|
|
|
info.low_water < 0)
|
|
|
|
return -EINVAL;
|
|
|
|
spin_lock_irqsave(&zatm_dev->lock, flags);
|
|
|
|
zatm_dev->pool_info[pool].low_water =
|
|
|
|
info.low_water;
|
|
|
|
zatm_dev->pool_info[pool].high_water =
|
|
|
|
info.high_water;
|
|
|
|
zatm_dev->pool_info[pool].next_thres =
|
|
|
|
info.next_thres;
|
|
|
|
spin_unlock_irqrestore(&zatm_dev->lock, flags);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
if (!dev->phy->ioctl) return -ENOIOCTLCMD;
|
|
|
|
return dev->phy->ioctl(dev,cmd,arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int zatm_getsockopt(struct atm_vcc *vcc,int level,int optname,
|
|
|
|
void __user *optval,int optlen)
|
|
|
|
{
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int zatm_setsockopt(struct atm_vcc *vcc,int level,int optname,
|
|
|
|
void __user *optval,int optlen)
|
|
|
|
{
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int zatm_send(struct atm_vcc *vcc,struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
int error;
|
|
|
|
|
|
|
|
EVENT(">zatm_send 0x%lx\n",(unsigned long) skb,0);
|
|
|
|
if (!ZATM_VCC(vcc)->tx_chan || !test_bit(ATM_VF_READY,&vcc->flags)) {
|
|
|
|
if (vcc->pop) vcc->pop(vcc,skb);
|
|
|
|
else dev_kfree_skb(skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
if (!skb) {
|
|
|
|
printk(KERN_CRIT "!skb in zatm_send ?\n");
|
|
|
|
if (vcc->pop) vcc->pop(vcc,skb);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
ATM_SKB(skb)->vcc = vcc;
|
|
|
|
error = do_tx(skb);
|
|
|
|
if (error != RING_BUSY) return error;
|
|
|
|
skb_queue_tail(&ZATM_VCC(vcc)->backlog,skb);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void zatm_phy_put(struct atm_dev *dev,unsigned char value,
|
|
|
|
unsigned long addr)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
zwait;
|
|
|
|
zout(value,CER);
|
|
|
|
zout(uPD98401_IND_ACC | uPD98401_IA_B0 |
|
|
|
|
(uPD98401_IA_TGT_PHY << uPD98401_IA_TGT_SHIFT) | addr,CMR);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static unsigned char zatm_phy_get(struct atm_dev *dev,unsigned long addr)
|
|
|
|
{
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
|
|
|
|
zatm_dev = ZATM_DEV(dev);
|
|
|
|
zwait;
|
|
|
|
zout(uPD98401_IND_ACC | uPD98401_IA_B0 | uPD98401_IA_RW |
|
|
|
|
(uPD98401_IA_TGT_PHY << uPD98401_IA_TGT_SHIFT) | addr,CMR);
|
|
|
|
zwait;
|
|
|
|
return zin(CER) & 0xff;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static const struct atmdev_ops ops = {
|
|
|
|
.open = zatm_open,
|
|
|
|
.close = zatm_close,
|
|
|
|
.ioctl = zatm_ioctl,
|
|
|
|
.getsockopt = zatm_getsockopt,
|
|
|
|
.setsockopt = zatm_setsockopt,
|
|
|
|
.send = zatm_send,
|
|
|
|
.phy_put = zatm_phy_put,
|
|
|
|
.phy_get = zatm_phy_get,
|
|
|
|
.change_qos = zatm_change_qos,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __devinit zatm_init_one(struct pci_dev *pci_dev,
|
|
|
|
const struct pci_device_id *ent)
|
|
|
|
{
|
|
|
|
struct atm_dev *dev;
|
|
|
|
struct zatm_dev *zatm_dev;
|
|
|
|
int ret = -ENOMEM;
|
|
|
|
|
2006-12-13 09:35:56 +01:00
|
|
|
zatm_dev = kmalloc(sizeof(*zatm_dev), GFP_KERNEL);
|
2005-04-17 00:20:36 +02:00
|
|
|
if (!zatm_dev) {
|
|
|
|
printk(KERN_EMERG "%s: memory shortage\n", DEV_LABEL);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev = atm_dev_register(DEV_LABEL, &ops, -1, NULL);
|
|
|
|
if (!dev)
|
|
|
|
goto out_free;
|
|
|
|
|
|
|
|
ret = pci_enable_device(pci_dev);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out_deregister;
|
|
|
|
|
|
|
|
ret = pci_request_regions(pci_dev, DEV_LABEL);
|
|
|
|
if (ret < 0)
|
|
|
|
goto out_disable;
|
|
|
|
|
|
|
|
zatm_dev->pci_dev = pci_dev;
|
|
|
|
dev->dev_data = zatm_dev;
|
|
|
|
zatm_dev->copper = (int)ent->driver_data;
|
|
|
|
if ((ret = zatm_init(dev)) || (ret = zatm_start(dev)))
|
|
|
|
goto out_release;
|
|
|
|
|
|
|
|
pci_set_drvdata(pci_dev, dev);
|
|
|
|
zatm_dev->more = zatm_boards;
|
|
|
|
zatm_boards = dev;
|
|
|
|
ret = 0;
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
out_release:
|
|
|
|
pci_release_regions(pci_dev);
|
|
|
|
out_disable:
|
|
|
|
pci_disable_device(pci_dev);
|
|
|
|
out_deregister:
|
|
|
|
atm_dev_deregister(dev);
|
|
|
|
out_free:
|
|
|
|
kfree(zatm_dev);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
|
|
static struct pci_device_id zatm_pci_tbl[] __devinitdata = {
|
|
|
|
{ PCI_VENDOR_ID_ZEITNET, PCI_DEVICE_ID_ZEITNET_1221,
|
|
|
|
PCI_ANY_ID, PCI_ANY_ID, 0, 0, ZATM_COPPER },
|
|
|
|
{ PCI_VENDOR_ID_ZEITNET, PCI_DEVICE_ID_ZEITNET_1225,
|
|
|
|
PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
|
|
|
|
{ 0, }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, zatm_pci_tbl);
|
|
|
|
|
|
|
|
static struct pci_driver zatm_driver = {
|
|
|
|
.name = DEV_LABEL,
|
|
|
|
.id_table = zatm_pci_tbl,
|
|
|
|
.probe = zatm_init_one,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init zatm_init_module(void)
|
|
|
|
{
|
|
|
|
return pci_register_driver(&zatm_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
module_init(zatm_init_module);
|
|
|
|
/* module_exit not defined so not unloadable */
|