patch-2.2.14 linux/drivers/s390/net/iucv.c

Next file: linux/drivers/s390/net/iucv.h
Previous file: linux/drivers/s390/net/ctc.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.2.13/linux/drivers/s390/net/iucv.c linux/drivers/s390/net/iucv.c
@@ -0,0 +1,1110 @@
+/*
+ *  drivers/s390/net/iucv.c
+ *    Network driver for VM using iucv
+ *
+ *  S390 version
+ *    Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ *    Author(s): Stefan Hegewald <hegewald@de.ibm.com>
+ *               Hartmut Penner <hpenner@de.ibm.com> 
+ */
+
+#ifndef __KERNEL__
+#define __KERNEL__
+#endif
+
+#include <linux/version.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>              /* printk()                         */
+#include <linux/malloc.h>              /* kmalloc()                        */
+#include <linux/errno.h>               /* error codes                      */
+#include <linux/types.h>               /* size_t                           */
+#include <linux/interrupt.h>           /* mark_bh                          */
+#include <linux/netdevice.h>           /* struct device, and other headers */
+#include <linux/inetdevice.h>          /* struct device, and other headers */
+#include <linux/if_arp.h>
+#include <linux/rtnetlink.h>
+#include <linux/ip.h>                  /* struct iphdr                     */
+#include <linux/tcp.h>                 /* struct tcphdr                    */
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <asm/checksum.h>
+#include <asm/io.h>
+#include <asm/string.h>
+
+#include "iucv.h"
+
+#define DEBUG123
+#define MAX_DEVICES  10
+
+extern char _ascebc[];
+
+/* 
+ *  global structures 
+ */
+static char iucv_userid[MAX_DEVICES][8];
+static char iucv_ascii_userid[MAX_DEVICES][8];
+static int  iucv_pathid[MAX_DEVICES] = {0};
+static unsigned char iucv_ext_int_buffer[40] __attribute__((aligned (8))) ={0};
+static unsigned char glob_command_buffer[40] __attribute__((aligned (8)));
+struct device iucv_devs[];
+
+
+/* This structure is private to each device. It is used to pass */
+/* packets in and out, so there is place for a packet           */
+struct iucv_priv {
+    struct net_device_stats stats;
+    int packetlen;
+    int status;
+    u8 *packetdata;
+    int pathid;                    /* used device */
+    unsigned char command_buffer[40] __attribute__((aligned (8)));
+    unsigned char ext_int_buffer[40] __attribute__((aligned (8)));
+    u8* receive_buffer;
+    int receive_buffer_len;
+    u8* send_buffer;
+    int send_buffer_len;    
+    char * new_send_buf;       /* send buffer ptr */
+    unsigned char recv_buf[2048];  /* size is just a guess */
+    unsigned char userid[8];
+};
+
+struct iucv_header {
+  short len;
+};
+
+/*
+ * Following the iucv primitives 
+ */
+
+
+extern inline void b2f0(int code,void* parm)
+{
+  asm volatile ("LR    1,%1\n\tLR    0,%0\n\t.long 0xb2f01000" ::
+                "d" (code) ,"a" (parm) :"0", "1");
+}
+
+int iucv_enable(void *parms)
+{
+  MASK_T *parm = parms;
+  memset(parms,0,sizeof(parm));
+  parm->ipmask = 0xF8;
+  b2f0(SETMASK,parm);
+  memset(parms,0,sizeof(parm));
+  parm->ipmask = 0xF8;
+  b2f0(SETCMASK,parm);
+  return parm->iprcode;
+}
+
+
+int iucv_declare_buffer(void *parms, DCLBFR_T *buffer)
+{
+  DCLBFR_T *parm = parms;
+  memset(parms,0,sizeof(parm));
+  parm->ipflags1= 0x00;
+  parm->ipbfadr1 = virt_to_phys(buffer);
+  b2f0(DECLARE_BUFFER, parm);
+  return parm->iprcode;
+}
+
+
+int iucv_retrieve_buffer(void *parms)
+{
+  DCLBFR_T *parm = parms;
+  memset(parms,0x0,sizeof(parm));
+  parm->iprcode = 0x0;
+  b2f0(RETRIEVE_BUFFER, parm);
+  return parm->iprcode;
+}
+
+
+int iucv_connect(void *parms,
+                 const char *userid,
+                 const char *host,
+                 const char *ipusr,
+                 unsigned short * used_pathid)
+{
+  CONNECT_T *parm = parms;                              /* ipflags was 0x60*/
+  memset(parms,0x0,sizeof(parm));
+  parm->ipflags1 = 0x80;
+  parm->ipmsglim = 0x0a;
+  memcpy(parm->ipvmid,userid,8);
+  if (ipusr)
+    memcpy(parm->ipuser,ipusr,16);
+  memcpy(parm->iptarget,host,8);
+  b2f0(CONNECT, parm);
+  *used_pathid = parm->ippathid;
+  return parm->iprcode;
+}
+
+
+
+int iucv_accept(void *parms,int pathid)
+{
+#ifdef DEBUG
+  int i=0;
+#endif
+  ACCEPT_T *parm = parms;
+  memset(parms,0,sizeof(parm));
+  parm->ippathid = pathid;
+  parm->ipflags1 = 0x80;
+  parm->ipmsglim = 0x0a;
+#ifdef DEBUG
+  printk("iucv: iucv_accept input.\n");
+  for (i=0;i<40; i++)
+  {
+    printk("%02x ",((char *)parms)[i]);
+  }
+  printk("\n");
+#endif
+  b2f0(ACCEPT, parm);
+  return parm->iprcode;
+}
+
+
+
+int iucv_receive(void *parms,void *bufferarray,int len)
+{
+#ifdef DEBUG
+  int i=0;
+#endif
+  RECEIVE_T *parm = parms;
+  memset(parms,0x0,sizeof(parm));
+  /*parm->ipflags1 = 0x42;*/
+  parm->ipflags1 = 0x0;
+  parm->ipmsgid  = 0x0;
+  parm->iptrgcls = 0x0;
+  parm->ipbfadr1 = (ULONG) virt_to_phys(bufferarray);
+  parm->ipbfln1f  = len;
+  parm->ipbfln2f  = 0x0;
+  b2f0(RECEIVE, parm);
+  if (parm->iprcode == 0)
+    len = parm->ipbfln1f;
+//  len = len-parm->ipbfln1f;
+#ifdef DEBUG
+  printk("iucv: iucv_receive command input:\n");
+   for (i=0;i<40;i++)  /* show iucv buffer before send */
+    {
+      printk("%02x ",((char *)parms)[i]);
+    }
+  printk("\n");
+
+  printk("iucv: iucv_receive data buffer:\n");
+  for (i=0;i<len;i++)  /* show data received */
+   {
+     printk("%02x ",((char *)bufferarray)[i]);
+   }
+  printk("\n");
+  printk("received length: %02x ",len);
+
+#endif
+  return parm->iprcode;
+}
+
+
+int iucv_send(void *parms,int pathid,void *bufferarray,int len,
+              void *recv_buf, int recv_len)
+{
+#ifdef DEBUG
+  int i=0;
+#endif
+  SEND_T *parm = parms;
+  memset(parms,0x0,sizeof(parm));
+  /*  parm->ipflags1 = 0x48; ??*/
+  parm->ippathid = pathid;
+  parm->ipflags1 = 0x14;                       /* any options ?? */
+  parm->ipmsgid  = 0x0;
+  parm->iptrgcls = 0x0;
+  parm->ipbfadr1 = virt_to_phys(bufferarray);
+  parm->ipbfln1f = len;
+  parm->ipsrccls = 0x0;
+  parm->ipmsgtag = 0x0;
+  parm->ipbfadr2 = virt_to_phys(recv_buf);
+  parm->ipbfln2f = recv_len;
+
+
+#ifdef DEBUG
+  printk("iucv: iucv_send command input:\n");
+  for (i=0;i<40;i++)  /* show iucv buffer before send */
+   {
+     printk("%02x ",((char *)parms)[i]);
+   }
+  printk("\n");
+
+  printk("iucv: iucv_send data buffer:\n");
+  for (i=0;i<len;i++)  /* show send data before send */
+   {
+     printk("%02x ",((char *)bufferarray)[i]);
+   }
+  printk("\n");
+#endif
+
+  b2f0(SEND, parm);
+
+#ifdef DEBUGXX
+ printk("iucv: iucv_send buffer after send:\n");
+ for (i=0;i<len;i++) /* show send buffer after send */
+   {
+     printk("%1x",((char *)bufferarray)[i]);
+   }
+   printk("\n");
+#endif
+
+  return parm->iprcode;
+}
+
+
+
+int iucv_sever(void *parms)
+{
+  SEVER_T *parm = parms;
+  memset(parms,0x0,sizeof(parm));
+  parm->ippathid = 0x0;
+  parm->ipflags1 = 0x0;
+  parm->iprcode  = 0xF;
+  memset(parm->ipuser,0,16);
+  b2f0(SEVER, parm);
+  return parm->iprcode;
+}
+
+
+#ifdef DEBUG
+/*--------------------------*/
+/* Dump buffer formatted    */
+/*--------------------------*/
+static void dumpit(char* buf, int len)
+{
+  int i;
+  for (i=0;i<len;i++) {
+    if (!(i%16)&&i!=0)
+      printk("\n");
+    else if (!(i%4)&&i!=0)
+      printk(" ");
+    printk(  "%02X",buf[i]);
+  }
+  if (len%16)
+    printk(  "\n");
+}
+#endif
+
+
+
+/*--------------------------*/
+/* Get device from pathid   */
+/*--------------------------*/
+struct device * get_device_from_pathid(int pathid)
+{
+   int i;
+    for (i=0;i<=MAX_DEVICES;i++)
+    {
+      if (iucv_pathid[i] == pathid)
+        return &iucv_devs[i];
+    }
+    printk("iucv: get_device_from_pathid: no device for pathid %X\n",pathid);
+ return 0;
+}
+
+
+
+/*--------------------------*/
+/* Get device from userid   */
+/*--------------------------*/
+struct device * get_device_from_userid(char * userid)
+{
+   int i;
+   struct device * dev;
+   struct iucv_priv *privptr;
+      for (i=0;i<=MAX_DEVICES;i++)
+      {
+        dev = &iucv_devs[i];
+        privptr = (struct iucv_priv *)(dev->priv);
+        if (memcmp(privptr->userid,userid,8)==0)
+          return &iucv_devs[i];
+      }
+      printk("iucv: get_device_from_uid: no device for userid %s\n",userid);
+   return 0;
+}
+
+
+/*--------------------------*/
+/* Open iucv Device Driver */
+/*--------------------------*/
+int iucv_open(struct device *dev)
+{
+    int rc;
+    unsigned short iucv_used_pathid;
+    struct iucv_priv *privptr;
+    char iucv_host[8]   ={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; 
+    char vmident[16]    ={0xf0,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
+                          0xf0,0x40,0x40,0x40,0x40,0x40,0x40,0x40};
+
+#ifdef DEBUG
+    printk(  "iucv: iucv_open, device: %s\n",dev->name);
+#endif
+
+    privptr = (struct iucv_priv *)(dev->priv);
+    if(privptr->pathid != -1) {
+       dev->start = 1;
+       dev->tbusy = 0;
+       return 0;
+    }
+    if ((rc = iucv_connect(privptr->command_buffer,
+                           privptr->userid,
+                           iucv_host,
+                           vmident,
+                           &iucv_used_pathid))!=0) {
+      printk(  "iucv: iucv connect failed with rc %X\n",rc);
+      iucv_retrieve_buffer(privptr->command_buffer);
+      return -ENODEV;
+    }
+
+    privptr->pathid = iucv_used_pathid;
+    iucv_pathid[dev-iucv_devs]=privptr->pathid;
+
+#ifdef DEBUG
+    printk(  "iucv: iucv_connect ended with rc: %X\n",rc);
+    printk(  "iucv[%d] pathid %X \n",(int)(dev-iucv_devs),privptr->pathid);
+#endif
+    dev->start = 1;
+    dev->tbusy = 0;
+    return 0;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Receive a packet: retrieve, encapsulate and pass over to upper levels */
+/*-----------------------------------------------------------------------*/
+void iucv_rx(struct device *dev, int len, unsigned char *buf)
+{
+
+  struct sk_buff *skb;
+  struct iucv_priv *privptr = (struct iucv_priv *)dev->priv;
+
+#ifdef DEBUG
+  printk(  "iucv: iucv_rx len: %X, device %s\n",len,dev->name);
+  printk(  "iucv rx: received orig:\n");
+  dumpit(buf,len);
+#endif
+
+  /* strip iucv header now */
+  len = len - 2;   /* short header */
+  buf = buf + 2;   /* short header */
+
+  skb = dev_alloc_skb(len+2); /* why +2 ? alignment ? */
+  if (!skb) {
+    printk(  "iucv rx: low on mem, returning...\n");
+    return;
+  }
+  skb_reserve(skb, 2);                        /* align IP on 16B boundary*/
+  memcpy(skb_put(skb, len), buf, len);
+#ifdef DEBUG
+  printk(  "iucv rx: data before netif_rx()\n");
+  dumpit(buf,len);
+#endif
+
+  /* Write metadata, and then pass to the receive level */
+  skb->mac.raw = skb->data;
+  skb->pkt_type = PACKET_HOST;
+  skb->dev = dev;
+  skb->protocol = htons(ETH_P_IP);
+  skb->ip_summed = CHECKSUM_UNNECESSARY;                /* don't check it*/
+  privptr->stats.rx_packets++;
+  netif_rx(skb);
+
+  return;
+} /* end  iucv_rx() */
+
+
+
+
+/*----------------------------*/
+/* handle interrupts          */
+/*----------------------------*/
+void do_iucv_interrupt(void)
+{
+  int rc;
+  struct in_device *indev;
+  struct in_ifaddr *inaddr;
+  unsigned long len=0;
+  struct device *dev=0;
+  struct iucv_priv *privptr;
+  INTERRUPT_T * extern_int_buffer;
+  unsigned short iucv_data_len=0;
+  unsigned short iucv_next=0;
+  unsigned char * rcvptr;
+  
+  /* get own buffer: */
+  extern_int_buffer = (INTERRUPT_T*) iucv_ext_int_buffer;
+  
+  dev->interrupt = 1;        /* lock ! */
+  
+#ifdef DEBUG
+  printk(  "iucv: do_iucv_interrupt %x received; pathid: %02X\n",
+	   extern_int_buffer->iptype,extern_int_buffer->ippathid);
+  printk(   "iucv: extern_int_buffer:\n");
+  dumpit((char *)&extern_int_buffer[0],40);
+#endif
+  
+  switch (extern_int_buffer->iptype)
+    {
+    case 0x01: /* connection pending ext interrrupt */
+#ifdef DEBUG
+      printk(  "iucv: connection pending IRQ.\n");
+#endif
+      
+      rc = iucv_accept(glob_command_buffer,
+		       extern_int_buffer->ippathid);
+      if (rc != 0) {
+	printk(  "iucv: iucv_accept failed with rc: %X\n",rc);
+	iucv_retrieve_buffer(glob_command_buffer);
+	break;
+      }
+#ifdef DEBUG
+      dumpit(&((char *)extern_int_buffer)[8],8);
+#endif
+      dev = get_device_from_userid(&((char*)extern_int_buffer)[8]);
+      privptr = (struct iucv_priv *)(dev->priv);
+      privptr->pathid =  extern_int_buffer->ippathid;
+      
+#ifdef DEBUG
+      printk(  "iucv: iucv_accept ended with rc: %X\n",rc);
+      printk(  "iucv: device %s found.\n",dev->name);
+#endif
+      break;
+      
+    case 0x02: /* connection completed ext interrrupt */
+      /* set own global IP address */
+      /* & set global routing addr */
+#ifdef DEBUG
+      printk(  "connection completed.\n");
+#endif
+      
+      if( extern_int_buffer->ipmsgtag !=0)
+	{
+	  /* get ptr's to kernel struct with local & broadcast address */
+	  dev = get_device_from_pathid(extern_int_buffer->ippathid);
+	  privptr = (struct iucv_priv *)(dev->priv);
+	  indev = dev->ip_ptr;
+	  inaddr = (struct in_ifaddr*) indev->ifa_list;
+	}
+      break;
+      
+      
+    case 0x03: /* connection severed ext interrrupt */
+      /* we do not handle this one at this time */
+#ifdef DEBUG
+      printk(  "connection severed.\n");
+#endif
+      break;
+      
+      
+    case 0x04: /* connection quiesced ext interrrupt */
+      /* we do not handle this one at this time */
+#ifdef DEBUG
+      printk(  "connection quiesced.\n");
+#endif
+      break;
+      
+      
+    case 0x05: /* connection resumed ext interrrupt */
+      /* we do not handle this one at this time */
+#ifdef DEBUG
+      printk(  "connection resumed.\n");
+#endif
+      break;
+      
+      
+    case 0x06: /* priority message complete ext interrupt */
+    case 0x07: /* non priority message complete ext interrupt */
+      /* send it to iucv_rx for handling */
+#ifdef DEBUG
+      printk(  "message completed.\n");
+#endif
+      
+      if (extern_int_buffer->ipaudit ==0)  /* ok case */
+	{
+#ifdef DEBUG
+	  printk(  "iucv: msg complete interrupt successful, rc: %X\n",
+		   (unsigned int)extern_int_buffer->ipaudit);
+#endif
+	  ;
+	}
+      else
+	{
+	  printk(  "iucv: msg complete interrupt error, rc: %X\n",
+		   (unsigned int)extern_int_buffer->ipaudit);
+	}
+      /* a transmission is over: tell we are no more busy */
+      dev = get_device_from_pathid(extern_int_buffer->ippathid);
+      privptr = (struct iucv_priv *)(dev->priv);
+      privptr->stats.tx_packets++;
+      mark_bh(NET_BH);
+      dev->tbusy = 0;                 /* transmission is no longer busy*/
+      break;
+      
+      
+    case 0x08: /* priority message pending */
+    case 0x09: /* non priority message pending */
+#ifdef DEBUG
+      printk(  "message pending.\n");
+#endif
+      dev = get_device_from_pathid(extern_int_buffer->ippathid);
+      privptr = (struct iucv_priv *)(dev->priv);
+      rcvptr = &privptr->receive_buffer[0];
+      
+      /* re-set receive buffer */
+      memset(privptr->receive_buffer,0,privptr->receive_buffer_len);
+      len = privptr->receive_buffer_len;
+      
+        /* get data now */
+        if (extern_int_buffer->ipflags1 & 0x80)
+	  {  /* data is in the message */
+#ifdef DEBUG
+	    printk(  "iucv: iucv_receive data is in header!\n");
+#endif
+	    memcpy(privptr->receive_buffer,
+		   (char *)extern_int_buffer->iprmmsg1,
+		   (unsigned long)(extern_int_buffer->iprmmsg2));
+	  }
+        else /* data is in buffer, do a receive */
+	  {
+	    rc = iucv_receive(privptr->command_buffer,rcvptr,len);
+	    if (rc != 0  || len == 0)
+	      {
+		printk(  "iucv: iucv_receive failed with rc: %X, length: %lX\n",rc,len);
+		iucv_retrieve_buffer(privptr->command_buffer);
+		break;
+	      }
+	  } /* end else */
+	
+      iucv_next = 0; 
+      /* get next packet offset */  
+      iucv_data_len= *((unsigned short*)rcvptr); 
+        do{ /* until receive buffer is empty, i.e. iucv_next == 0 ! */
+
+        /* get data length:    */
+        iucv_data_len= iucv_data_len - iucv_next;
+	
+#ifdef DEBUG
+        printk(  "iucv: iucv_receive: len is %02X, last: %02X\n",
+		 iucv_data_len,iucv_next);
+#endif
+        /* transmit upstairs */
+        iucv_rx(dev,(iucv_data_len),rcvptr);
+	
+#ifdef DEBUG
+        printk(  "iucv: transaction complete now.\n");
+#endif
+        rcvptr = rcvptr + iucv_data_len;
+        iucv_next = iucv_data_len; 
+        /* get next packet offset */  
+        iucv_data_len= *((unsigned short*)rcvptr);
+	
+      } while (iucv_data_len != 0);
+      dev->tbusy = 0;                 /* transmission is no longer busy*/
+      break;
+      
+    default:
+      printk(  "unknown iucv interrupt \n");
+      break;
+      
+    } /* end switch */
+  dev->interrupt = 0;                                       /* release lock*/
+  
+#ifdef DEBUG
+  printk(  "iucv: leaving do_iucv_interrupt.\n");
+#endif
+  
+}  /* end    do_iucv_interrupt()  */
+
+
+
+/*-------------------------------------------*/
+/*   Transmit a packet (low level interface) */
+/*-------------------------------------------*/
+int iucv_hw_tx(char *send_buf, int len,struct device *dev)
+{
+  /* This function deals with hw details.                         */
+  /* This interface strips off the ethernet header details.       */
+  /* In other words, this function implements the iucv behaviour,*/
+  /* while all other procedures are rather device-independent     */
+  struct iucv_priv *privptr;
+  int rc, recv_len=2000;
+  
+  privptr = (struct iucv_priv *)(dev->priv);
+  
+#ifdef DEBUG
+  printk(  "iucv: iucv_hw_tx, device %s\n",dev->name);
+  printk(  "iucv: hw_TX_data len: %X\n",len);
+  dumpit(send_buf,len);
+#endif
+  
+  /* I am paranoid. Ain't I? */
+  if (len < sizeof(struct iphdr))
+    {
+      printk(  "iucv: Hmm... packet too short (%i octets)\n",len);
+      return -EINVAL;
+    }
+  
+  /*
+   * build IUCV header (preceeding halfword offset)   
+   * works as follows: Each packet is preceded by the 
+   * halfword offset to the next one. 
+   * The last packet is followed by an offset of zero.
+   * E.g., AL2(12),10-byte packet, AL2(34), 32-byte packet, AL2(0)
+   */
+  
+  memcpy(&privptr->send_buffer[2],send_buf,len+2);
+  privptr->send_buffer[len+2] = 0;
+  privptr->send_buffer[len+3] = 0;
+  *((unsigned short*) &privptr->send_buffer[0]) = len + 2;
+  
+#ifdef DEBUG
+  printk(  "iucv: iucv_hw_tx, device %s\n",dev->name);
+  printk(  "iucv: send len: %X\n",len+4);
+  dumpit(privptr->send_buffer,len+4);
+#endif
+  *((unsigned short*) &privptr->send_buffer[0]) = len + 2;
+  
+  /* Ok, now the packet is ready for transmission: send it. */
+  if ((rc = iucv_send(privptr->command_buffer,
+		      privptr->pathid,
+		      &privptr->send_buffer[0],len+4,
+		      privptr->recv_buf,recv_len))!=0) {
+    printk(  "iucv: send_iucv failed, rc: %X\n",rc);
+    iucv_retrieve_buffer(privptr->command_buffer);
+  }
+#ifdef DEBUG
+  printk(  "iucv: send_iucv ended, rc: %X\n",rc);
+#endif
+  return rc;
+} /* end   iucv_hw_tx()  */
+
+
+
+
+
+
+/*------------------------------------------*/
+/* Transmit a packet (called by the kernel) */
+/*------------------------------------------*/
+int iucv_tx(struct sk_buff *skb, struct device *dev)
+{
+    int retval=0;
+
+    struct iucv_priv *privptr;
+
+    if (dev == NULL)
+    {
+      printk("iucv: NULL dev passed\n");
+      return 0;
+    }
+
+    privptr = (struct iucv_priv *) (dev->priv);
+
+    if (skb == NULL)
+    {
+      printk("iucv: %s: NULL buffer passed\n", dev->name);
+      privptr->stats.tx_errors++;
+      return 0;
+    }
+
+#ifdef DEBUG
+    printk(  "iucv: enter iucv_tx, using %s\n",dev->name);
+#endif
+
+    if (dev->tbusy)                                   /* shouldn't happen*/
+    {
+      privptr->stats.tx_errors++;
+      dev_kfree_skb(skb);
+      printk("iucv: %s: transmit access conflict ! leaving iucv_tx.\n", dev->name);
+    }
+
+    dev->tbusy = 1;                                   /* transmission is busy*/
+    dev->trans_start = jiffies;                       /* save the timestamp*/
+
+    /* actual deliver of data is device-specific, and not shown here */
+    retval = iucv_hw_tx(skb->data, skb->len, dev);
+
+    dev_kfree_skb(skb);                               /* release it*/
+
+#ifdef DEBUG
+    printk(  "iucv:leaving iucv_tx, device %s\n",dev->name);
+#endif
+
+    return retval;              /* zero == done; nonzero == fail*/
+}   /* end  iucv_tx( struct sk_buff *skb, struct device *dev)  */
+
+
+
+
+
+
+/*---------------*/
+/* iucv_release */
+/*---------------*/
+int iucv_release(struct device *dev)
+{
+    int rc =0;
+    struct iucv_priv *privptr;
+    privptr = (struct iucv_priv *) (dev->priv);
+
+    dev->start = 0;
+    dev->tbusy = 1;                              /* can't transmit any more*/
+    rc = iucv_sever(privptr->command_buffer);
+    if (rc!=0)
+    {
+       printk("iucv: %s: iucv_release pending...rc:%02x\n",dev->name,rc);
+    }
+
+#ifdef DEBUG
+      printk("iucv: iucv_sever ended with rc: %X\n",rc);
+#endif
+
+    return rc;
+} /* end  iucv_release() */
+
+
+
+
+
+/*-----------------------------------------------*/
+/* Configuration changes (passed on by ifconfig) */
+/*-----------------------------------------------*/
+int iucv_config(struct device *dev, struct ifmap *map)
+{
+   if (dev->flags & IFF_UP)        /* can't act on a running interface*/
+        return -EBUSY;
+
+   /* ignore other fields */
+   return 0;
+}
+/*  end  iucv_config()  */
+
+
+
+
+
+/*----------------*/
+/* Ioctl commands */
+/*----------------*/
+int iucv_ioctl(struct device *dev, struct ifreq *rq, int cmd)
+{
+    printk(  "iucv: device %s; iucv_ioctl\n",dev->name);
+    return 0;
+}
+
+/*---------------------------------*/
+/* Return statistics to the caller */
+/*---------------------------------*/
+struct net_device_stats *iucv_stats(struct device *dev)
+{
+    struct iucv_priv *priv = (struct iucv_priv *)dev->priv;
+#ifdef DEBUG
+    printk(  "iucv: device %s; iucv_stats\n",dev->name);
+#endif
+    return &priv->stats;
+}
+
+
+/*
+ * iucv_change_mtu     
+ * IUCV can handle MTU sizes from 576 to approx. 32000    
+ */
+
+static int iucv_change_mtu(struct device *dev, int new_mtu)
+{
+#ifdef DEBUG
+    printk(  "iucv: device %s; iucv_change_mtu\n",dev->name);
+#endif
+       if ((new_mtu < 64) || (new_mtu > 32000))
+	 return -EINVAL;
+       dev->mtu = new_mtu;
+       return 0;
+}
+
+
+
+
+/*--------------------------------------------*/
+/* The init function (sometimes called probe).*/
+/* It is invoked by register_netdev()         */
+/*--------------------------------------------*/
+int iucv_init(struct device *dev)
+{
+    int rc;
+    struct iucv_priv *privptr;
+
+#ifdef DEBUG
+    printk(  "iucv: iucv_init, device: %s\n",dev->name);
+#endif
+
+    dev->open            = iucv_open;
+    dev->stop            = iucv_release;
+    dev->set_config      = iucv_config;
+    dev->hard_start_xmit = iucv_tx;
+    dev->do_ioctl        = iucv_ioctl;
+    dev->get_stats       = iucv_stats;
+    dev->change_mtu      = iucv_change_mtu;
+
+    /* keep the default flags, just add NOARP */
+
+    dev->hard_header_len = 0;
+    dev->addr_len        = 0;
+    dev->type            = ARPHRD_SLIP;
+    dev->tx_queue_len    = 100;
+    dev->flags           = IFF_NOARP|IFF_POINTOPOINT;
+    dev->mtu    = 4092;
+
+    dev_init_buffers(dev);
+
+    /* Then, allocate the priv field. This encloses the statistics */
+    /* and a few private fields.*/
+    dev->priv = kmalloc(sizeof(struct iucv_priv), GFP_KERNEL);
+    if (dev->priv == NULL){
+       printk(  "iucv: no memory for dev->priv.\n");
+       return -ENOMEM;
+    }
+    memset(dev->priv, 0, sizeof(struct iucv_priv));
+    privptr = (struct iucv_priv *)(dev->priv);
+
+
+    privptr->send_buffer = (u8*) __get_free_pages(GFP_KERNEL+GFP_DMA,8);
+    if (privptr->send_buffer == NULL) {
+      printk(KERN_INFO "%s: could not get pages for send buffer\n",
+	     dev->name);
+      return -ENOMEM;
+    }
+    memset(privptr->send_buffer, 0, 8*PAGE_SIZE);
+    privptr->send_buffer_len=8*PAGE_SIZE;
+    
+    privptr->receive_buffer = (u8*) __get_free_pages(GFP_KERNEL+GFP_DMA,8);
+    if (privptr->receive_buffer == NULL) {
+      printk(KERN_INFO "%s: could not get pages for receive buffer\n",
+	     dev->name);
+      return -ENOMEM;
+    }
+    memset(privptr->receive_buffer, 0, 8*PAGE_SIZE);
+    privptr->receive_buffer_len=8*PAGE_SIZE;
+
+    /* now use the private fields ... */
+    /* init pathid                    */
+    privptr->pathid = -1;
+
+    /* init private userid from global userid */
+    memcpy(privptr->userid,iucv_userid[dev-iucv_devs],8);
+
+
+    /* we can use only ONE buffer for external interrupt ! */
+    rc=iucv_declare_buffer(privptr->command_buffer,
+                           (DCLBFR_T *)iucv_ext_int_buffer);
+    if (rc!=0 && rc!=19)   /* ignore existing buffer */
+      {
+         printk(  "iucv:iucv_declare failed, rc: %X\n",rc);
+         return -ENODEV;
+      }
+
+    rc = iucv_enable(privptr->command_buffer);
+    if (rc!=0)
+    {
+            printk(  "iucv:iucv_enable failed, rc: %x\n",rc);
+            iucv_retrieve_buffer(privptr->command_buffer);
+            return -ENODEV;
+    }
+#ifdef DEBUG
+    printk(  "iucv: iucv_init endend OK for device %s.\n",dev->name);
+#endif
+    return 0;
+}
+
+
+/*
+ * setup iucv devices
+ * 
+ * string passed: iucv=userid1,...,useridn 
+ */
+
+__initfunc(int iucv_setup(char* str, int *ints))
+{
+    int result=0, i=0,j=0, k=0, device_present=0;
+    char *s = str;
+    struct device * dev ={0};
+
+#ifdef DEBUG
+    printk(  "iucv: start registering device(s)... \n");
+#endif
+
+    /*
+     * scan device userids
+     */
+
+    while(*s != 0x20 && *s != '\0'){
+       if(*s == ','){
+          /* fill userid up to 8 chars */
+          for(k=i;k<8;k++){
+             iucv_userid[j][k] = 0x40;
+          } /* end for */
+          /* new device  */
+          j++;
+          s++; /* ignore current char */
+          i=0;
+          if (j>MAX_DEVICES) {
+             printk("iucv: setup devices: max devices %d reached.\n",
+		    MAX_DEVICES);
+             break;
+          } /* end if */
+          continue;
+       } /* end if */
+       iucv_ascii_userid[j][i] = (int)*s;
+       iucv_userid[j][i] = _ascebc[(int)*s++];
+       i++;
+    } /* end while */
+
+    /* 
+     * fill last userid up to 8 chars
+     */
+    for(k=i;k<8;k++) {
+      iucv_userid[j][k] = 0x40;
+    }
+
+    /*
+     * set device name and register
+     */
+
+    for (k=0;k<=j;k++) {
+      memcpy(iucv_devs[k].name, "iucv0", 4);
+      dev = &iucv_devs[k];
+      dev->name[4] = k + '0';
+
+#ifdef DEBUGX
+      printk("iucv: (ASCII- )Userid:%s\n",&iucv_ascii_userid[k][0]);
+      printk("iucv: (ASCII-)Userid: ");
+      for (i=0;i<8;i++) {
+        printk(  "%02X ",(int)iucv_ascii_userid[k][i]);
+      }
+      printk("\n");
+      printk("iucv: (EBCDIC-)Userid: ");
+      for (i=0;i<8;i++) {
+         printk(  "%02X ",(int)iucv_userid[k][i]);
+      }
+      printk("\n");
+      printk("iucv: device name :%s\n",iucv_devs[k].name);
+#endif
+
+      if ( (result = register_netdev(iucv_devs + k)) )
+          printk("iucv: error %i registering device \"%s\"\n",
+                 result, iucv_devs[k].name);
+      else
+      {
+              device_present++;
+      }
+    } /* end for */
+
+#ifdef DEBUG
+    printk(  "iucv: end register devices, %d devices present\n",device_present);
+#endif
+    return device_present ? 0 : -ENODEV;
+}
+
+
+
+
+
+/*-------------*/
+/* The devices */
+/*-------------*/
+char iucv_names[MAX_DEVICES*8]; /* MAX_DEVICES eight-byte buffers */
+struct device iucv_devs[MAX_DEVICES] = {
+    {
+        iucv_names, /* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    },
+    {
+        iucv_names+8,/* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    },
+    {
+        iucv_names+16,/* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    },
+    {
+        iucv_names+24,/* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    },
+    {
+        iucv_names+32,/* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    },
+    {
+        iucv_names+40,/* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    },
+    {
+        iucv_names+48,/* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    },
+    {
+        iucv_names+56,/* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    },
+    {
+        iucv_names+64,/* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    },
+    {
+        iucv_names+72,/* name -- set at load time */
+        0, 0, 0, 0,  /* shmem addresses */
+        0x000,       /* ioport */
+        0,           /* irq line */
+        0, 0, 0,     /* various flags: init to 0 */
+        NULL,        /* next ptr */
+        iucv_init,  /* init function, fill other fields with NULL's */
+    }
+};
+

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