patch-2.2.16 linux/drivers/net/wanxl.c

Next file: linux/drivers/net/wanxl.h
Previous file: linux/drivers/net/via-rhine.c
Back to the patch index
Back to the overall index

diff -urN v2.2.15/linux/drivers/net/wanxl.c linux/drivers/net/wanxl.c
@@ -0,0 +1,1233 @@
+/* wanxl.c
+ * -*- linux-c -*-
+ * 
+ *  	(C) 1999 RG Studio s.c., http://www.rgstudio.com.pl/
+ *	Written by Krzysztof Halasa <khc@rgstudio.com.pl>
+ *
+ *  	Portions (C) SBE Inc., used by permission.
+ *
+ *	Sources:
+ *		wanXL technical reference manuals
+ *		wanXL UNIXware X.25 driver
+ *		Donald Becker's skeleton.c driver
+ *		"Linux Kernel Module Programming" by Ori Pomerantz
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License
+ *	as published by the Free Software Foundation; either version
+ *	2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/malloc.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/in.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_arp.h>
+#include <linux/if_frad.h>
+
+#include <linux/pci.h>
+
+#include "syncppp.h"
+#include <linux/hdlc.h>
+#include "plx9060.h"
+#include "wanxl.h"
+
+static const char* version = "SBE wanXL driver revision: 1.0-pre9";
+static const char* irqdevname = "SBE wanXL";
+
+#ifdef __LITTLE_ENDIAN
+#define SWAP_OPTION MBX_CMD_BSWAP_1
+#else
+#error "big endian untested yet"
+#define SWAP_OPTION MBX_CMD_BSWAP_0
+#endif
+
+
+#define WANXL_DEVICE_IDS 3    /* number of possible device IDs we will look
+				 for on the PCI bus */
+
+static int SBEdeviceIDs[WANXL_DEVICE_IDS] = { PCI_DEVICE_ID_SBE_WANXL100, 
+					      PCI_DEVICE_ID_SBE_WANXL200, 
+					      PCI_DEVICE_ID_SBE_WANXL400 };
+
+#define MAX_PUTS_WAIT 20	/* in  seconds */
+#define MAX_QUERY_WAIT 1
+#define WANXL_HOST_MEM_SIZE	0x10000000
+#define WANXL_BUS2HOST_MAP_ADDR 0x00000000
+
+#define PCI_COMM_MAE            0x0002  /* memory access enable */
+#define PCI_COMM_ME             0x0004  /* master enable */
+#define PCI_CONF_COMM           0x4     /* command register, 2 bytes  */
+
+
+static sbe_card *first_card=NULL;
+static sbe_card **new_card=&first_card;
+
+static const char *cable_types[]={ "undefined0",
+				   "undefined1",
+				   "V.35",
+				   "X.21",
+				   "RS232",
+				   "EIA530",
+				   "no port",
+				   "none" };
+
+
+#define max(x, y) (x > y ? x : y)
+
+
+
+static int wanxl_run_board(sbe_card *card, char *data, u32 *length);
+#ifdef WANXL_QUERY
+static int wanxl_query_port(sbe_port *port, char *data, u32 *length);
+#endif
+
+static inline sbe_port* hdlc_to_port(hdlc_device *hdlc)
+{
+	return (sbe_port*)hdlc;
+}
+
+
+
+static inline sbe_port* dev_to_port(struct device *dev)
+{
+	return hdlc_to_port(dev_to_hdlc(dev));
+}
+
+
+
+static inline char *port_name(sbe_port *port)
+{
+	return port->hdlc.name;
+}
+
+
+
+static inline char *card_name(sbe_card *card)
+{
+	return card->first_port->hdlc.name;
+}
+
+
+
+static inline int wanxl_card_running(sbe_card *card)
+{
+	return card->running;
+}
+
+
+
+static inline void wanxl_send_interrupt(sbe_port *port, int transmit)
+{
+	if ((transmit && ((port->tx_ring->r_flags & BF_IREQ) == 0)) ||
+	    (!transmit && ((port->rx_ring->r_flags & BF_IREQ) == 0)))
+		return;		/* No need for interrupt signaling */
+  
+#ifdef DEBUG_INTR
+	printk(KERN_DEBUG "%s: wanxl_send_interrupt (%s, node=%u)\n",
+	       port_name(port), transmit ? "TX" : "RX", port->node);
+#endif
+	if (transmit)
+		transmit=TX_IRQ_BIAS;
+  
+	writel(1<<(transmit+port->node), &port->card->wx_plx_viraddr->dbr_in);
+}
+
+
+
+static void wanxl_parse_injector(sbe_port *port, void *data, u32 len)
+{
+	int i;
+
+	printk(KERN_INFO "%s: got injector (len=%X):", port->hdlc.name, len);
+  
+	for (i=0; i<(len+3)/4; i++)
+		printk(" %08X", ((u32*)data)[i]);
+	printk("\n");
+}
+
+
+
+static int wanxl_submit_injector(sbe_port *port, void *packet, u32 size,
+				 int wait)
+{
+#ifdef DEBUG_INJ
+	int i;
+#endif
+	pkt_desc *tx_pkt=port->tx_ring->v_inject;
+	pkt_desc *rx_pkt=port->rx_ring->v_inject;
+	bio_wrap *bio_tx=tx_pkt->v_data;
+	bio_wrap *bio_rx=rx_pkt->v_data;
+	u32 cmd;
+ 
+	if (tx_pkt->pd_flags & PD_READY) {
+		sleep_on_timeout(&port->tx_inj, HZ*MAX_QUERY_WAIT);
+		if (tx_pkt->pd_flags & PD_READY)
+			return -EBUSY; /* TX injector still in use */
+	}
+  
+	memcpy(bio_tx, packet, size);
+	cmd=bio_tx->type;
+	tx_pkt->pd_length=size;
+#ifdef DEBUG_INJ
+	printk(KERN_DEBUG "%s: Sending injector:", port_name(port));
+	for (i=0; i<size/4; i++)
+		printk(" %08X", ((u32*)packet)[i]);
+	printk("\n");
+#endif
+	tx_pkt->pd_flags=PD_FLAGS_INJ|PD_READY;
+	wanxl_send_interrupt(port, 1);
+
+	if (!wait)
+		return 0;
+
+	while(1) {
+		sleep_on_timeout(&port->rx_inj, HZ*MAX_QUERY_WAIT);
+		if ((rx_pkt->pd_flags & PD_READY)==0) {
+			printk(KERN_WARNING "%s: Timeout when waiting for "
+			       "injector %X\n", port_name(port), cmd);
+			return -EIO;
+		}
+  
+		if (bio_rx->type==cmd && rx_pkt->pd_length==size)
+			break;
+
+		wanxl_parse_injector(port, bio_rx, rx_pkt->pd_length);
+		rx_pkt->pd_flags=PD_FLAGS_INJ;
+		wanxl_send_interrupt(port, 0);
+	}
+
+	memcpy(packet, bio_rx, size);
+	rx_pkt->pd_flags=PD_FLAGS_INJ;
+	wanxl_send_interrupt(port, 0);
+
+	if (bio_rx->status) {
+		printk(KERN_WARNING "%s: injector %X failed, status=%X\n",
+		       port_name(port), cmd, bio_rx->status);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+
+
+static void wanxl_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	sbe_card *card=dev_id;
+	sbe_port *port;
+	u32 int_mask;
+  
+	if (!wanxl_card_running(card))
+		return;
+
+	int_mask=readl(&card->wx_plx_viraddr->dbr_out);
+	writel(int_mask, &card->wx_plx_viraddr->dbr_out); /* Confirm IRQ */
+
+	for (port=card->first_port; port!=NULL; port=port->next_port) {
+		pkt_desc *pkt;
+
+		if (port->rx_ring->v_inject->pd_flags & PD_READY)
+			if (waitqueue_active(&port->rx_inj))
+				wake_up(&port->rx_inj);	/* Got injector response */
+ 
+		if ((port->tx_ring->v_inject->pd_flags & PD_READY) == 0)
+			if (waitqueue_active(&port->tx_inj))
+				wake_up(&port->tx_inj);	/* Got free injector */
+    
+		pkt=port->next_rx_pkt;
+		while (pkt->pd_flags & PD_READY) {
+#ifdef DEBUG_PKT
+			int i;
+#endif
+			if (pkt->pd_flags & PD_ERR)
+				port->hdlc.stats.rx_errors++; /* RX error */
+			else {
+				struct sk_buff *skb=pkt->v_data;
+#ifdef DEBUG_PKT
+				printk(KERN_DEBUG "%s RX (PCI=%08x, "
+				       "proto=%x):", port_name(port),
+				       pkt->p_data, pkt->mux.fr.protid);
+				for (i=0; i<pkt->pd_length; i++)
+					printk(" %02X", skb->data[i]);
+				printk("\n");
+#endif
+				if ((port->mode & MODE_FR) && pkt->mux.fr.protid!=IP_PROTID &&
+				    pkt->mux.fr.protid!=ARP_PROTID)
+					port->hdlc.stats.rx_errors++; /* Ignore it for now */
+				else {
+					struct sk_buff *newskb=dev_alloc_skb(HDLC_MAX_MTU);
+					if (newskb==NULL)
+						port->hdlc.stats.rx_dropped++;
+					else {
+						skb_put(skb, pkt->pd_length);
+						if ((port->mode & MODE_FR) && pkt->mux.fr.protid==ARP_PROTID) {
+							skb_pull(skb, 2); /* Remove 2 byte header left by PDM */
+							skb->protocol=htons(ETH_P_ARP);
+						} else
+							skb->protocol=htons(ETH_P_IP);
+		
+						hdlc_netif_rx(&port->hdlc, skb, pkt->mux.fr.dlci);
+						pkt->v_data=newskb;
+						pkt->p_data=virt_to_bus(newskb->data);
+					}
+				}
+			}
+
+			pkt->pd_length=0;
+			pkt->pd_flags = PD_FLAGS_RX | (pkt->pd_flags & PD_LAST);
+
+			if (pkt->pd_flags & PD_LAST)
+				pkt=port->rx_ring->v_ring; /* Rewind */
+			else
+				pkt++;
+		} /* while */
+
+		port->next_rx_pkt=pkt;
+		wanxl_send_interrupt(port, 0); /* RX ring serviced */
+
+		pkt=port->next_tx_pkt_done;
+		while (pkt->pd_flags & PD_DONE) {
+			if (pkt->pd_flags & PD_ERR)
+				printk(KERN_WARNING "%s: PD_ERROR set on TX packet\n",
+				       port_name(port));
+      
+			port->hdlc.stats.tx_packets++;
+			port->hdlc.stats.tx_bytes+=pkt->pd_length;
+      
+			dev_kfree_skb(pkt->v_data);
+			pkt->pd_flags = PD_FLAGS_TX | (pkt->pd_flags & PD_LAST);
+      
+			if (pkt->pd_flags & PD_LAST)
+				pkt=port->tx_ring->v_ring; /* Rewind */
+			else
+				pkt++;
+		} /* while */
+		port->next_tx_pkt_done=pkt;
+		wanxl_send_interrupt(port, 1); /* TX ring serviced */
+	}
+}
+
+
+
+static int wanxl_set_mode(hdlc_device *hdlc, int mode)
+{
+	sbe_port *port=hdlc_to_port(hdlc);
+
+	if (wanxl_card_running(port->card)==0)
+		return -ENOSYS;	/* Card not initialized */
+  
+	if (mode!=MODE_HDLC && mode!=MODE_FR_ANSI && mode!=MODE_FR_CCITT)
+		return -ENOSYS;	/* Not supported on this hardware yet */
+
+	port->mode=mode;
+	return 0;
+}
+
+
+
+static int wanxl_create_pvc(pvc_device *pvc)
+{
+	sbe_port *port=hdlc_to_port(pvc->master);
+	u16 dlci=netdev_dlci(&pvc->netdev);
+	b_tnioc packet;
+	int result;
+
+	if (wanxl_card_running(port->card)==0)
+		return -ENOSYS;	/* Card not initialized */
+
+	if (!(hdlc_to_dev(pvc->master)->flags & IFF_UP))
+		return -ENOSYS;	/* HDLC port down */
+
+	memset(&packet, 0, sizeof(packet));
+	packet.bio.type=B_SETTUNE;
+	packet.tune.fr_pvc.dlci=dlci;
+	packet.tune.fr_pvc.cir=16000;
+	packet.tune.fr_pvc.Bc=16000;
+	packet.tune.fr_pvc.Be=2048000;
+	packet.tune.fr_pvc.stepcount=8;
+	packet.tune.fr_pvc.flowstyle=FECN;
+	/* FIXME - must be set ? why ? */
+	packet.tune.fr_pvc.protid=IP_PROTID|ARP_PROTID;
+	packet.type=FR_PVC_TYPE;
+	result = wanxl_submit_injector(port, &packet, sizeof(packet), 0);
+	if (result)
+		return result;
+
+	return 0;
+}
+
+
+
+static int wanxl_open_pvc(pvc_device *pvc)
+{
+	pvc->state |= PVC_STATE_ACTIVE; /* FIXME */
+	return 0;
+}
+
+
+
+static int wanxl_open(hdlc_device *hdlc)
+{
+	sbe_port *port=hdlc_to_port(hdlc);
+	b_tnioc packet;
+	int result;
+
+	memset(&packet, 0, sizeof(packet));
+	packet.bio.type=B_SETTUNE;
+	packet.tune.wan.WAN_baud=2048000;
+	packet.tune.wan.WAN_maxframe=HDLC_MAX_MTU;
+	packet.type=HDLC_TYPE;
+	result=wanxl_submit_injector(port, &packet, sizeof(packet), 1);
+	if (result)
+		return result;
+
+
+	memset(&packet.bio, 0, sizeof(packet.bio));
+	packet.bio.type=B_OPEN;
+	result=wanxl_submit_injector(port, &packet.bio, sizeof(packet.bio), 1);
+	if (result)
+		return result;
+
+
+	if (port->mode==MODE_FR_ANSI || port->mode==MODE_FR_CCITT) {
+		memset(&packet, 0, sizeof(packet));
+		packet.bio.type=B_SETTUNE;
+		packet.tune.fr.T391=hdlc->lmi.T391;
+		packet.tune.fr.T392=hdlc->lmi.T392;
+		packet.tune.fr.N391=hdlc->lmi.N391;
+		packet.tune.fr.N392=hdlc->lmi.N392;
+		packet.tune.fr.N393=hdlc->lmi.N393;
+		packet.tune.fr.maxframesize=HDLC_MAX_MTU;
+		packet.tune.fr.accessrate=2048000;
+		if (port->mode==MODE_FR_CCITT)
+			packet.tune.fr.standard=ITU;
+		else
+			packet.tune.fr.standard=ANSI;
+		packet.tune.fr.conform=NONE;
+		packet.type=FR_TYPE;
+		result=wanxl_submit_injector(port, &packet, sizeof(packet), 0);
+		if (result)
+			return result;
+	}
+
+
+	memset(&packet.bio, 0, sizeof(packet.bio));
+	packet.bio.type=B_START;
+	result=wanxl_submit_injector(port, &packet.bio, sizeof(packet.bio), 1);
+	if (result)
+		return result;
+  
+	MOD_INC_USE_COUNT;
+	return 0;
+}
+
+
+
+static void wanxl_close(hdlc_device *hdlc)
+{
+	sbe_port *port=hdlc_to_port(hdlc);
+	bio_wrap packet;
+
+	memset(&packet, 0, sizeof(packet));
+	packet.type=B_CLOSE;
+	wanxl_submit_injector(port, &packet, sizeof(packet), 1);
+
+	MOD_DEC_USE_COUNT;
+}
+
+
+
+static int wanxl_xmit(struct sk_buff *skb, struct device *dev)
+{
+	u16 dlci=0;
+	u8 proto=0;
+	sbe_port *port=dev_to_port(dev);
+	pkt_desc *pkt=port->next_tx_pkt;
+#ifdef DEBUG_PKT
+	int i;
+#endif
+  
+	if (pkt->pd_flags & (PD_READY|PD_DONE)) {
+#ifdef DEBUG_PKT
+		printk(KERN_DEBUG "%s: Unable to send packet\n",
+		       port_name(port));
+#endif
+		port->hdlc.stats.tx_dropped++;
+		dev_kfree_skb(skb);
+		return 0;	/* Out of card buffers - drop the skb */
+	}
+
+	if (port->mode & MODE_FR) {
+		dlci=q922_to_dlci(skb->data);
+		switch(skb->protocol) {
+		case __constant_htons(ETH_P_IP):
+			proto=IP_PROTID;
+			skb_pull(skb, 4);
+			break;
+	
+		case __constant_htons(ETH_P_ARP):
+			proto=ARP_PROTID;
+			skb_pull(skb, 10);
+			break;
+	
+		default:
+			port->hdlc.stats.tx_errors++;
+			dev_kfree_skb(skb);
+			return 0; /* Ignore packet */
+		}
+	}
+
+	/* Align buffer at 4-byte boundary */
+	if (virt_to_bus(skb->data) % 4) {
+		struct sk_buff *new=alloc_skb(skb->len, GFP_ATOMIC);
+		if (!new) {
+			port->hdlc.stats.tx_dropped++;
+			dev_kfree_skb(skb);
+			return 0; /* Out of memory - drop packet */
+		}
+
+		memcpy(new->data, skb->data, skb->len);
+		skb_put(new, skb->len);
+		dev_kfree_skb(skb);
+		skb=new;
+	}
+
+	pkt->v_data=skb;
+	pkt->p_data=virt_to_bus(skb->data);
+	pkt->pd_length=skb->len;
+	pkt->mux.fr.dlci=dlci;
+	pkt->mux.fr.protid = (skb->protocol == __constant_htons(ETH_P_ARP) ?
+			      ARP_PROTID : IP_PROTID);
+
+	if (pkt->pd_flags & PD_LAST)
+		port->next_tx_pkt=port->tx_ring->v_ring; /* Rewind */
+	else
+		port->next_tx_pkt=pkt+1;
+ 
+#ifdef DEBUG_PKT
+	printk(KERN_DEBUG "%s TX (PCI=%08x, proto=%x):",
+	       dev->name, pkt->p_data, pkt->mux.fr.protid);
+	for (i=0; i<skb->len; i++)
+		printk(" %02X", skb->data[i]);
+	printk("\n");
+#endif
+
+	pkt->pd_flags=PD_FLAGS_TX|PD_READY | (pkt->pd_flags&PD_LAST);
+	wanxl_send_interrupt(port, 1);
+	return 0;
+}
+
+
+
+static int wanxl_ioctl(hdlc_device *hdlc, struct ifreq *ifr, int cmd)
+{
+	sbe_port *port=hdlc_to_port(hdlc);
+	u32 length;
+	int result;
+
+	if(!capable(CAP_NET_ADMIN))
+		return -EPERM;
+  
+	if (ifr->ifr_data==NULL)
+		return -EFAULT;
+  
+	if (copy_from_user(&length, ifr->ifr_data, 4)!=0)
+		return -EFAULT;
+
+	switch(cmd) {
+	case HDLCRUN:
+		if (port->card->first_port != port) /* Not initial port */
+			result=-EINVAL;
+		else if (port->card->rx_ring || port->card->tx_ring)
+			result=-EBUSY; /* Already running */
+		else
+			result=wanxl_run_board(port->card, ifr->ifr_data+4,
+					       &length);
+		break;
+      
+#ifdef WANXLQUERY
+	case WANXLQUERY:
+		if (wanxl_card_running(port->card)==0)
+			result=-EIO; /* Board must be run first */
+		else
+			result=wanxl_query_port(port, ifr->ifr_data+4,
+						&length);
+		break;
+#endif
+	default:
+		result=-EINVAL;
+		length=0;
+	}
+  
+	if (copy_to_user(ifr->ifr_data, &length, 4)!=0)
+		return -EFAULT;
+
+	return result;
+}
+
+
+
+
+static inline int wanxl_init_hdlc_port(sbe_port *port)
+{
+	port->hdlc.set_mode=wanxl_set_mode;
+	port->hdlc.open=wanxl_open;
+	port->hdlc.close=wanxl_close;
+	port->hdlc.ioctl=wanxl_ioctl;
+	port->hdlc.create_pvc=wanxl_create_pvc;
+	port->hdlc.open_pvc=wanxl_open_pvc;
+	hdlc_to_dev(&port->hdlc)->hard_start_xmit=wanxl_xmit;
+	return register_hdlc_device(&port->hdlc);
+}
+
+
+
+static void wanxl_destroy_ring(desc_ring **root)
+{
+	int cnt;
+	desc_ring *node;
+	pkt_desc *pkts;
+
+	if (*root==NULL)
+		return;
+
+	node=*root;
+	while(node) {
+		pkts=node->v_ring;
+		if (pkts)
+			for (cnt=0; cnt<node->ndesc; cnt++) {
+				if (pkts->v_data)
+					dev_kfree_skb(pkts->v_data);
+				pkts++;
+			}
+		node=node->next_node ? bus_to_virt(node->next_node) : NULL;
+	}
+
+	if ((*root)->v_inject) {
+		if ((*root)->v_inject->v_data)
+			/* Delete injector buffers */
+			kfree((*root)->v_inject->v_data);
+    
+		kfree((*root)->v_inject); /* Delete pkts descriptors */
+	}
+
+
+	kfree(*root);		/* Delete ring descriptors */
+	*root=NULL;
+}
+
+
+
+static int wanxl_create_ring(desc_ring **root, int transmit, int ports)
+{
+	int node, cnt;
+	desc_ring *ring;
+	pkt_desc *pkts;
+	char *inj;
+	int descs=transmit ? WANXL_TX_BUFFERS : WANXL_RX_BUFFERS;
+
+	u32 ring_size=sizeof(desc_ring) * ports;
+	u32 pkts_size=sizeof(pkt_desc) * (descs+1) * ports;
+	u32 inj_size=MAX_IOCTL_PACKET * ports;
+  
+	ring=kmalloc(ring_size, GFP_KERNEL);
+	if (ring==NULL)
+		return -ENOBUFS;
+	memset(ring, 0, ring_size);
+
+	pkts=kmalloc(pkts_size, GFP_KERNEL);
+	if (pkts==NULL) {
+		kfree(ring);
+		return -ENOBUFS;
+	}
+	memset(pkts, 0, pkts_size);
+
+	inj=kmalloc(inj_size, GFP_KERNEL);
+	if (inj==NULL) {
+		kfree(ring);
+		kfree(pkts);
+		return -ENOBUFS;
+	}
+	memset(inj, 0, inj_size);
+
+	*root=ring;
+
+	for (node=0; node<ports; node++) {
+		ring->r_flags = HF_IREQ | HF_ACTIVE | node;
+		ring->ndesc=descs;
+		ring->pdm_offset=0;
+		ring->cpu=0;
+		ring->next_cpu=NULL;
+		ring->next_node = (node==ports-1 ? 0 : virt_to_bus(ring+1));
+
+		ring->p_inject=virt_to_bus(pkts); /* Injector */
+		ring->v_inject=pkts;
+		pkts->pd_flags=PD_FLAGS_INJ;
+		pkts->p_data=virt_to_bus(inj);
+		pkts->v_data=inj;
+		pkts->pd_max_len=MAX_IOCTL_PACKET;
+		pkts++;
+		inj+=MAX_IOCTL_PACKET;
+    
+		ring->p_ring=virt_to_bus(pkts); /* Buffers */
+		ring->v_ring=pkts; /* for host usage only */
+		for (cnt=0; cnt<descs; cnt++) {
+			if (transmit)
+				pkts->pd_flags=PD_FLAGS_TX;
+			else {
+				struct sk_buff *skb=dev_alloc_skb(HDLC_MAX_MTU);
+				if (skb==NULL) {
+					wanxl_destroy_ring(root);
+					return -ENOBUFS;
+				}
+	
+				pkts->pd_flags=PD_FLAGS_RX;
+				pkts->p_data=virt_to_bus(skb->data);
+				pkts->v_data=skb;
+			}
+
+			if (cnt==descs-1)
+				pkts->pd_flags|=PD_LAST;
+			pkts->pd_max_len=HDLC_MAX_MTU;
+			pkts++;
+		}
+		ring++;
+	}
+
+	return 0;
+}
+
+
+
+static inline int wanxl_wait_on_box(volatile u32 *box_addr,
+				    u32 *store_value, /* store value here */
+				    u32 break_mask, /* breaks */
+				    u32 equal, /* equal breaks */
+				    long ms) /* ms delay count */
+{
+	u32 result;
+	unsigned long timeout=jiffies+max(2, ms * HZ / 1000);
+	do {
+		result=readl(box_addr);
+
+		if ((result & break_mask) || (result==equal))
+			break;
+
+		schedule();
+	}while(jiffies<timeout);
+  
+	if (store_value!=NULL)
+		*store_value=result;
+
+	if ((result & break_mask) || (result==equal))
+		return 0;
+
+	return -1;
+}
+
+
+
+
+/*****************************************************************************
+ * wx_setmbx() -- board setup, set server's hardware mailbox
+ *
+ * Configuration of certain server parameters is accomplished via writing to
+ * that board's mailbox.  This routine writes into the board's Mailbox
+ * Command register, then polls for completion.  Completion is signaled by
+ * clearing of the Mailbox Command register by on/board code.  Should the
+ * clearing fail to occur or take excessive time, the polling will time-out.
+ *
+ * The delay count parameter, wcnt, should specifiy the maximum number
+ * of milliseconds (ms) to wait.  There are 1000 milliseconds per second.
+ *****************************************************************************/
+
+static int wanxl_setmbx(sbe_card *card,
+			u32 cmd, /* Mailbox Command */
+			long wcnt) /* ms delay */
+{
+	u32 result;
+	writel(cmd, &card->wx_plx_viraddr->mbox_1);
+  
+	if (wanxl_wait_on_box(&card->wx_plx_viraddr->mbox_1, &result,
+			      0, 0x0, wcnt)!=0) {
+		printk(KERN_WARNING "%s: wanxl_setmbx: timeout processing "
+		       "command 0x%x, result=0x%x\n",
+		       card_name(card), cmd, result);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+
+
+/* issue a reset to the board */
+static int wanxl_reset_board(sbe_card *card, int wait)
+{
+	u32 result;
+	unsigned long timeout;
+
+	printk(KERN_DEBUG "%s: resetting board\n", card_name(card));
+	writel(MBX_STS_PCIRESET, &card->wx_plx_viraddr->mbox_0);
+  
+	result=readl(&card->wx_plx_viraddr->control) | CTL_RESET;
+	writel(result, &card->wx_plx_viraddr->control);
+  
+	timeout = jiffies + max(2, HZ / 500);
+	do
+		schedule();
+	while(jiffies<timeout);
+      
+	result=readl(&card->wx_plx_viraddr->control) & ~CTL_RESET;
+	writel(result, &card->wx_plx_viraddr->control);
+
+	if (!wait)
+		return 0;
+
+	/* wait for successful completion of self-tests */
+	timeout = jiffies + MAX_PUTS_WAIT * HZ;
+	do {
+		result=readl(&card->wx_plx_viraddr->mbox_0);
+		if (result) {
+			if (result & MBX_STS_ERROR) {
+				printk(KERN_WARNING "%s: self-test failed, "
+				       "status=0x%x.\n",
+				       card_name(card), result);
+				return -ENODEV;
+			}
+			schedule();
+		} else
+			break;
+	}while(jiffies<timeout);
+  
+	if (result) {
+		printk(KERN_WARNING "%s: self-test failed to complete\n",
+		       card_name(card));
+		return -ENODEV;
+	}
+
+	printk(KERN_DEBUG "%s: self-test done.\n", card_name(card));
+	return 0;
+}
+
+
+
+#ifdef WANXL_QUERY
+static int wanxl_query_port(sbe_port *port, char *data, u32 *length)
+{
+	int result;
+	char packet[MAX_IOCTL_PACKET];
+
+	if (wanxl_card_running(port->card)==0)
+		return -ENOSYS;		/* Card not initialized */
+
+	if (copy_from_user(packet, data, *length))
+		return -EFAULT;
+
+	result=wanxl_submit_injector(port, packet, *length, 1);
+	if (result)
+		return result;
+
+	if (copy_to_user(data, packet, *length))
+		return -EFAULT;
+
+	return 0;
+}
+#endif
+
+
+
+static void wanxl_destroy_card(sbe_card *card, int destroy_all)
+{
+	sbe_port *port=card->first_port;
+
+	wanxl_reset_board(card, 0);
+
+	if (!destroy_all && port) {
+		port=port->next_port;	/* Omit first port */
+		card->first_port->next_port=NULL;
+	}
+
+	while (port) {
+		sbe_port *ptr=port;
+    
+		unregister_hdlc_device(&port->hdlc);
+		port=port->next_port;
+		kfree(ptr);
+	}
+
+	if (card->rx_ring)
+		wanxl_destroy_ring(&card->rx_ring);
+  
+	if (card->tx_ring)
+		wanxl_destroy_ring(&card->tx_ring);
+
+	if (destroy_all) {
+		if (card->irq)
+			free_irq(card->irq, card);
+    
+		if (card->wx_plx_viraddr)
+			iounmap(card->wx_plx_viraddr);
+		if (card->wx_mem_viraddr)
+			iounmap(card->wx_mem_viraddr);
+		kfree(card);
+	}
+}
+
+
+
+static int wanxl_run_board(sbe_card *card, char *data, u32 *length)
+{
+	sbe_port *port=card->first_port, **prev_port;
+	desc_ring *tx_ring, *rx_ring;
+	u32 len=*length;
+	int result, cnt;
+	u32 bdsize;
+	ulong mt;
+	char *ms;
+
+	if (len>MAX_PDM_LEN || len<=sizeof(PCI360Header) || len%4) {
+		printk(KERN_WARNING "%s: Invalid firmware length\n",
+		       port_name(port));
+		return -EFAULT;
+	}
+  
+  
+	/* reset board and wait for PUTS to complete */
+	result=wanxl_reset_board(card, 1);
+	if (result)
+		return result;
+  
+	/* get on-board memory size */
+	bdsize = readl(&card->wx_plx_viraddr->mbox_2) & MBX_MEMSZ_MASK;
+
+	/* sanity check the board's reported memory size */
+	if (bdsize < MBX_MEMSZ_1MB || bdsize > MBX_MEMSZ_4MB) {
+		printk(KERN_WARNING "%s: illegal board memory size 0x%x",
+		       port_name(port), bdsize);
+		return -ENODEV;
+	}
+
+	/* now, calculate the timeout value that will be added to a delay later
+	   this is to allow time for clearing of on-board memory */
+	switch (bdsize) {
+	case MBX_MEMSZ_1MB: ms = "1MB"; mt = 2800; break;
+	case MBX_MEMSZ_2MB: ms = "2MB"; mt = 3600; break;
+	case MBX_MEMSZ_4MB: ms = "4MB"; mt = 5200; break;
+	case MBX_MEMSZ_8MB: ms = "8MB"; mt = 8400; break;
+	default: ms = "(size?)"; mt = 3600;
+	}
+	printk(KERN_INFO "%s: board memory size 0x%x (%cMB)\n",
+	       port_name(port),
+	       bdsize, *ms);
+  
+	/*
+	 * The processing of the BSWAP command is estimated at 50ms (board),
+	 * there we set timeout value to 2 seconds, and add in the 0.8
+	 * seconds per MB of on/board memory.
+	 */
+
+	result=wanxl_setmbx(card, SWAP_OPTION, mt);
+	if (result) {
+		printk(KERN_WARNING "%s: unable to set byte-swap option, "
+		       "aborting.\n", port_name(port));
+		return result;
+	}
+
+	/*
+	 * The processing of the HBA and HMS Mailbox Commands is estimated at
+	 * 0.5 seconds each (wanXL), and we select 1 second for the timeout
+	 * value.
+	 *
+	 * Host Memory Base Address and Host Memory Size setup are Host
+	 * dependent.  HMS should be set first to avoid PUTS Warning msg.
+	 */
+
+	result=wanxl_setmbx(card, MBX_CMD_SETHMS+(WANXL_HOST_MEM_SIZE>>8),
+			    1000);
+	if (result) {
+		printk(KERN_WARNING "%s: mbx SETHMS command failure\n",
+		       port_name(port));
+		return result;
+	}
+  
+	result=wanxl_setmbx(card, MBX_CMD_SETHBA|(WANXL_BUS2HOST_MAP_ADDR>>8),
+			    1000);
+	if (result) {
+		printk(KERN_WARNING "%s: mbx SETHBA command failure\n",
+		       port_name(port));
+		return result;
+	}
+  
+	if (copy_from_user((char*)card->wx_mem_viraddr_order+PDM_OFFSET,
+			   data+sizeof(PCI360Header),
+			   len-sizeof(PCI360Header)))
+		return -EFAULT;
+  
+	writel(PDM_OFFSET, card->wx_mem_viraddr);
+
+	writel(0, &card->wx_plx_viraddr->mbox_5);
+	writel(0, &card->wx_plx_viraddr->mbox_6);
+	writel(0, &card->wx_plx_viraddr->mbox_7);
+
+	result=wanxl_setmbx(card, MBX_CMD_ABORTJ, 1000);
+	if (result) {
+		printk(KERN_WARNING "%s: unable to send AbortAndJump "
+		       "command\n", port_name(port));
+		return result;
+	}
+
+	if (wanxl_wait_on_box(&card->wx_plx_viraddr->mbox_5, &result,
+			      0xFFFFFFFF, 0xFFFFFFFF, 1000)!=0) {
+		printk(KERN_WARNING "%s: board never became ready after "
+		       "AbortAndJump command\n", port_name(port));
+		return -EFAULT;
+	}
+  
+	card->config=(board_cfg*)(result+card->wx_mem_viraddr);
+  
+	printk(KERN_INFO "%s: serial number %d\n", port_name(port),
+	       card->config->serial_num);
+	printk(KERN_INFO "%s: %d port%s, RAM=0x%x len=0x%x\n", port_name(port),
+	       card->config->num_ports, card->config->num_ports>1 ? "s" : "",
+	       card->config->mem_base, card->config->mem_size);
+	printk(KERN_DEBUG "%s: firmware version: %s\n", port_name(port),
+	       card->wx_mem_viraddr_order+card->config->fw_version);
+	printk(KERN_DEBUG "%s: PDM version: %s\n", port_name(port),
+	       card->wx_mem_viraddr_order+card->config->pdm_version);
+	printk(KERN_DEBUG "%s: PDM compiled: %s\n", port_name(port),
+	       card->wx_mem_viraddr_order+card->config->pdm_compiled);
+
+	if (card->config->num_ports > MAX_PORTS_PER_CARD) {
+		card->config->num_ports = MAX_PORTS_PER_CARD;
+		printk(KERN_WARNING "%s: Using only %i ports\n", port_name(port),
+		       MAX_PORTS_PER_CARD);
+	}
+  
+	if (wanxl_create_ring(&card->rx_ring, 0, MAX_PORTS_PER_CARD)!=0)
+		return -EFAULT;
+  
+	if (wanxl_create_ring(&card->tx_ring, 1, MAX_PORTS_PER_CARD)!=0) {
+		wanxl_destroy_ring(&card->rx_ring);
+		return -EFAULT;
+	}
+
+	card->config->rx_ring_head=virt_to_bus(card->rx_ring);
+	card->config->tx_ring_head=virt_to_bus(card->tx_ring);
+	card->config->rx_max_pkts=10;
+	card->config->tx_max_pkts=10;
+
+	prev_port = &port->next_port;	/* Back pointer to next_port address */
+	rx_ring = card->rx_ring;
+	tx_ring = card->tx_ring;
+
+	len=0;
+	for (cnt=0; cnt<MAX_PORTS_PER_CARD && len<card->config->num_ports;
+	     cnt++) {
+		if (((card->config->port_info[cnt]&0xE0)>>5) != 6) {
+			/* port exist */
+			if (len!=0) {
+				/* Create next ports */
+				port=kmalloc(sizeof(sbe_port), GFP_KERNEL);
+				if (port==NULL) {
+					printk(KERN_WARNING "%s: Unable to create port structure\n",
+					       card_name(card));
+					wanxl_destroy_card(card, 0);
+				}
+				memset(port, 0, sizeof(sbe_port));
+      
+				hdlc_to_dev(&port->hdlc)->irq=card->irq;
+				port->card=card;
+				*prev_port=port; /* Connect port to chain */
+				prev_port=&port->next_port;
+
+				result=wanxl_init_hdlc_port(port);
+				if (result) {
+					printk(KERN_WARNING "%s: Unable to register net device\n",
+					       port_name(port));
+					wanxl_destroy_card(card, 0);
+					return result;
+				}
+			}
+    
+			port->node=cnt;
+			printk(KERN_INFO "%s: cable=%s, PM=%s\n",
+			       port_name(port),
+			       cable_types[card->config->port_info[cnt]&0x7],
+			       cable_types[(card->config->port_info[cnt]&0xE0)>>5]);
+			len++;			/* port found */
+		}
+		port->rx_ring=rx_ring++;
+		port->tx_ring=tx_ring++;
+		port->next_rx_pkt = port->rx_ring->v_ring;
+		port->next_tx_pkt = port->next_tx_pkt_done = port->tx_ring->v_ring;
+	}
+
+	writel(HDLC_MAX_MTU, &card->config->max_frame_size);
+	writel(HDLC_MAX_MTU, &card->config->avg_msg_size);
+	writel(HDLC_MAX_MTU, &card->config->scc_rx_mblk_sz);
+	writel(100, &card->config->tx_intr_tmo);
+
+	writel(1, &card->config->valid);
+  
+	copy_to_user(data, card->config, sizeof(board_cfg));
+	*length=sizeof(board_cfg);
+	card->running=1;
+	return 0;
+}
+
+
+
+/*****************************************************************
+ *
+ *  Module initialization and destruction
+ *
+ ****************************************************************/
+  
+__initfunc(int wanxl_pci_init(struct pci_dev *pdev))
+{
+	int result;
+	u16 command;
+	sbe_card *card;
+	sbe_port *port;
+	u32 memsize;
+
+	card=kmalloc(sizeof(sbe_card), GFP_KERNEL);
+	if (card==NULL)
+		return -ENOBUFS;
+	memset(card, 0, sizeof(sbe_card));
+
+	port=kmalloc(sizeof(sbe_port), GFP_KERNEL);
+	if (port==NULL) {
+		kfree(card);
+		return -ENOBUFS;
+	}
+	memset(port, 0, sizeof(sbe_port));
+
+	card->first_port=port;
+	port->card=card;
+	port->node=0;
+	result=wanxl_init_hdlc_port(port);
+	if (result) {
+		printk(KERN_WARNING "wanXL: Unable to register hdlc device\n");
+		kfree(card);
+		kfree(port);
+		return result;
+	}
+
+	pci_read_config_word(pdev, PCI_CONF_COMM, &command);
+	if ((command & (PCI_COMM_ME|PCI_COMM_MAE))==0) {
+		command|=(PCI_COMM_ME|PCI_COMM_MAE);
+		printk(KERN_WARNING "%s: updating PCI config register %d to 0x%x\n",
+		       port_name(port), PCI_CONF_COMM, command);
+		pci_write_config_word(pdev, PCI_CONF_COMM, command);
+	}
+
+	/* set up addresses and mappings */
+	card->wx_plx_phyaddr=pdev->base_address[0];
+	card->wx_mem_phyaddr=pdev->base_address[2];
+	card->wx_plx_viraddr=ioremap_nocache(card->wx_plx_phyaddr,
+					     sizeof(plx9060));
+  
+	memsize = card->wx_plx_viraddr->mbox_2 & MBX_MEMSZ_MASK;
+	card->wx_mem_viraddr = ioremap_nocache(card->wx_mem_phyaddr,
+					       memsize*2);
+	card->wx_mem_viraddr_order = card->wx_mem_viraddr + memsize;
+  
+	printk(KERN_INFO "%s: plx9060/9080 at 0x%x, RAM at 0x%x, irq %u\n",
+	       port_name(port),
+	       card->wx_plx_phyaddr, card->wx_mem_phyaddr, pdev->irq);
+ 
+	/* allocate IRQ for the board */
+
+	if (request_irq(pdev->irq, &wanxl_intr, SA_SHIRQ, irqdevname, card)) {
+		printk(KERN_WARNING "%s: could not allocate IRQ%d.\n",
+		       port_name(port), pdev->irq);
+		wanxl_destroy_card(card, 1);
+		return(-ENODEV);
+	}
+	card->irq=hdlc_to_dev(&port->hdlc)->irq=pdev->irq;
+
+	*new_card=card;
+	new_card=&card->next_card;
+
+	return 0;
+}
+
+
+
+__initfunc(int wanxl_init(void))
+{
+	int i, devs=0, rep=0;
+  
+	if(!pci_present())
+		return -ENODEV;   
+  
+	for(i=0; i<WANXL_DEVICE_IDS; i++) {
+		struct pci_dev *pdev=NULL;
+		do {
+			pdev=pci_find_device(PCI_VENDOR_ID_SBE,
+					     SBEdeviceIDs[i], pdev);
+			if (pdev) {
+				int result;
+
+				if (!rep) {
+					printk(KERN_INFO "%s\n", version);
+					rep=1;
+				}
+
+				result=wanxl_pci_init(pdev);
+				if (result==0)
+					devs++;	/* Initialized a card */
+				else
+					printk(KERN_WARNING "wanXL: Error initializing card, result=%i\n",
+					       result);
+			}
+		}while (pdev!=NULL);
+	}
+
+	if(devs==0)
+		return -ENODEV;		/* No cards found */
+
+	return 0;
+}
+
+
+#ifdef MODULE
+
+int init_module(void)
+{
+	return wanxl_init();
+}
+
+
+void cleanup_module(void)
+{
+	sbe_card *card=first_card;
+  
+	while (card) {
+		sbe_card *ptr=card;
+		card=card->next_card;
+		wanxl_destroy_card(ptr, 1);
+	}
+}
+
+#endif /* MODULE */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)