patch-2.4.2 linux/drivers/s390/net/ctcmain.c
Next file: linux/drivers/s390/net/fsm.c
Previous file: linux/drivers/s390/net/ctc.c
Back to the patch index
Back to the overall index
- Lines: 3465
- Date:
Tue Feb 13 14:13:44 2001
- Orig file:
v2.4.1/linux/drivers/s390/net/ctcmain.c
- Orig date:
Wed Dec 31 16:00:00 1969
diff -u --recursive --new-file v2.4.1/linux/drivers/s390/net/ctcmain.c linux/drivers/s390/net/ctcmain.c
@@ -0,0 +1,3464 @@
+/*
+ * $Id: ctcmain.c,v 1.11 2000/12/15 19:34:54 bird Exp $
+ *
+ * CTC / ESCON network driver
+ *
+ * Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Author(s): Fritz Elfert (elfert@de.ibm.com, felfert@millenux.com)
+ * Fixes by : Jochen Röhrig (roehrig@de.ibm.com)
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ *
+ * Documentation used:
+ * - Principles of Operation (IBM doc#: SA22-7201-06)
+ * - Common IO/-Device Commands and Self Description (IBM doc#: SA22-7204-02)
+ * - Common IO/-Device Commands and Self Description (IBM doc#: SN22-5535)
+ * - ESCON Channel-to-Channel Adapter (IBM doc#: SA22-7203-00)
+ * - ESCON I/O Interface (IBM doc#: SA22-7202-029
+ *
+ * and the source of the original CTC driver by:
+ * Dieter Wellerdiek (wel@de.ibm.com)
+ * Martin Schwidefsky (schwidefsky@de.ibm.com)
+ * Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
+ * Jochen Röhrig (roehrig@de.ibm.com)
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * $Log: ctcmain.c,v $
+ * Revision 1.11 2000/12/15 19:34:54 bird
+ * struct ctc_priv_t: set type of tbusy to "unsigned long"
+ *
+ * Revision 1.10 2000/12/14 16:49:50 bird
+ * ch_action_txretry(): added missing clear_normalized_cda()
+ *
+ * Revision 1.9 2000/12/14 13:56:53 felfert
+ * Eliminated a compiler warning when building in old kernel.
+ *
+ * Revision 1.8 2000/12/14 13:11:59 felfert
+ * static ccws now separately allocated.
+ * remove locally allocated ccw for setup.
+ *
+ * Revision 1.7 2000/12/14 03:32:15 bird
+ * Fixes for >2GB memory. Switch on checksumming.
+ *
+ * Revision 1.6 2000/12/07 20:08:30 felfert
+ * Modified RX channel initialization to be compatible with VM TCP
+ *
+ * Revision 1.5 2000/12/07 18:15:05 felfert
+ * Added workaround against VM TCP bug.
+ * Fixed an error message.
+ *
+ * Revision 1.4 2000/12/06 16:55:57 felfert
+ * Removed check for double call of ctc_setup().
+ * ctc_setup can now handle mutiple calls.
+ *
+ * Revision 1.3 2000/12/06 16:48:44 felfert
+ * New initialization.
+ * Removed old cvs log from 2.2 kernel.
+ *
+ * Revision 1.2 2000/12/06 14:13:46 felfert
+ * New unified configuration.
+ *
+ * Revision 1.1 2000/11/30 11:21:08 bird
+ * Support for new ctc driver
+ */
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/malloc.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+
+#include <linux/signal.h>
+#include <linux/string.h>
+#include <linux/proc_fs.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/ip.h>
+#include <linux/if_arp.h>
+#include <linux/tcp.h>
+#include <linux/skbuff.h>
+#include <linux/ctype.h>
+#include <net/dst.h>
+
+#include <asm/io.h>
+#include <asm/bitops.h>
+#include <asm/uaccess.h>
+
+#define CTC_USE_IDALS 1
+#if CTC_USE_IDALS
+# include <asm/idals.h>
+#else
+# define set_normalized_cda(ccw, addr) ((ccw)->cda = (addr))
+# define clear_normalized_cda(ccw)
+#endif
+
+#include <asm/irq.h>
+
+#include "fsm.h"
+
+#ifdef MODULE
+MODULE_AUTHOR("(C) 2000 IBM Corp. by Fritz Elfert (felfert@millenux.com)");
+MODULE_DESCRIPTION("Linux for S/390 CTC/Escon Driver");
+MODULE_PARM(ctc, "s");
+MODULE_PARM_DESC(ctc,
+"One or more definitions in the same format like the kernel param for ctc.\n"
+"E.g.: ctc0:0x700:0x701:0:ctc1:0x702:0x703:0\n");
+
+char *ctc = NULL;
+#else
+/**
+ * Number of devices in monolithic (not module) driver version.
+ */
+#define MAX_STATIC_DEVICES 16
+#endif /* MODULE */
+
+#undef DEBUG
+
+/**
+ * CCW commands, used in this driver.
+ */
+#define CCW_CMD_WRITE 0x01
+#define CCW_CMD_READ 0x02
+#define CCW_CMD_SET_EXTENDED 0xc3
+#define CCW_CMD_PREPARE 0xe3
+
+#define CTC_PROTO_S390 0
+#define CTC_PROTO_LINUX 1
+#define CTC_PROTO_MAX 1
+
+#define CTC_BUFSIZE_LIMIT 65535
+#define CTC_BUFSIZE_DEFAULT 32768
+
+#define CTC_TIMEOUT_5SEC 5000
+
+#define CTC_INITIAL_BLOCKLEN 2
+
+#define READ 0
+#define WRITE 1
+
+/**
+ * Enum for classifying detected devices.
+ */
+enum channel_types {
+ /**
+ * Device is not a channel.
+ */
+ channel_type_none,
+
+ /**
+ * Device is a channel, but we don't know
+ * anything about it.
+ */
+ channel_type_unknown,
+
+ /**
+ * Device is a CTC/A.
+ */
+ channel_type_ctca,
+
+ /**
+ * Device is a ESCON channel.
+ */
+ channel_type_escon,
+ /**
+ * Device is an unsupported model.
+ */
+ channel_type_unsupported
+};
+
+typedef enum channel_types channel_type_t;
+
+static int ctc_no_auto;
+
+/**
+ * If running on 64 bit, this must be changed. XXX Why? (bird)
+ */
+typedef unsigned long intparm_t;
+
+#if LINUX_VERSION_CODE < 0x020300
+typedef struct device net_device;
+#else
+typedef struct net_device net_device;
+#endif
+
+/**
+ * Definition of a per device parameter block
+ */
+#define MAX_PARAM_NAME_LEN 11
+typedef struct param_t {
+ struct param_t *next;
+ int read_dev;
+ int write_dev;
+ __u16 proto;
+ char name[MAX_PARAM_NAME_LEN];
+} param;
+
+static param *params;
+
+typedef struct {
+ unsigned long maxmulti;
+ unsigned long maxcqueue;
+ unsigned long doios_single;
+ unsigned long doios_multi;
+ unsigned long txlen;
+ unsigned long tx_time;
+ struct timeval send_stamp;
+} ctc_profile;
+
+/**
+ * Definition of one channel
+ */
+typedef struct channel_t {
+
+ /**
+ * Pointer to next channel in list.
+ */
+ struct channel_t *next;
+
+ __u16 devno;
+ int irq;
+
+ /**
+ * Type of this channel.
+ * CTC/A or Escon for valid channels.
+ */
+ channel_type_t type;
+
+ /**
+ * Misc. flags. See CHANNEL_FLAGS_... below
+ */
+ __u32 flags;
+
+ /**
+ * The protocol of this channel (currently always 0)
+ */
+ __u16 protocol;
+
+ /**
+ * I/O and irq related stuff
+ */
+ ccw1_t *ccw;
+ devstat_t *devstat;
+
+ /**
+ * Bottom half task queue.
+ */
+ struct tq_struct tq;
+
+ /**
+ * RX/TX buffer for init sequence.
+ */
+ __u16 dummy_buf;
+
+ /**
+ * RX buffer size
+ */
+ int max_bufsize;
+
+ /**
+ * Receive buffer.
+ */
+ struct sk_buff *rx_skb;
+
+ /**
+ * Universal I/O queue.
+ */
+ struct sk_buff_head io_queue;
+
+ /**
+ * TX queue for collecting skb's during busy.
+ */
+ struct sk_buff_head collect_queue;
+
+ /**
+ * Amount of data in collect_queue.
+ */
+ int collect_len;
+
+ /**
+ * spinlock for collect_queue and collect_len
+ */
+ spinlock_t collect_lock;
+
+ /**
+ * Pointer to dynamic allocated CCWs for TX
+ */
+ ccw1_t *dccw;
+
+ /**
+ * Number of dynamic allocated CCWs needed for clearing IDALs.
+ */
+ int dccw_count;
+
+ /**
+ * Timer for detecting unresposive
+ * I/O operations.
+ */
+ fsm_timer timer;
+
+ /**
+ * Retry counter for misc. operations.
+ */
+ int retry;
+
+ /**
+ * The finite state machine of this channel
+ */
+ fsm_instance *fsm;
+
+ /**
+ * The corresponding net_device this channel
+ * belongs to.
+ */
+ net_device *netdev;
+
+ ctc_profile prof;
+} channel;
+
+#define CHANNEL_FLAGS_READ 0
+#define CHANNEL_FLAGS_WRITE 1
+#define CHANNEL_FLAGS_INUSE 2
+#define CHANNEL_FLAGS_RWMASK 1
+#define CHANNEL_DIRECTION(f) (f & CHANNEL_FLAGS_RWMASK)
+
+/**
+ * Linked list of all detected channels.
+ */
+static channel *channels;
+
+typedef struct ctc_priv_t {
+ struct net_device_stats stats;
+#if LINUX_VERSION_CODE >= 0x02032D
+ unsigned long tbusy;
+#endif
+ /**
+ * The finite state machine of this interface.
+ */
+ fsm_instance *fsm;
+ channel *channel[2];
+ struct proc_dir_entry *proc_dentry;
+ struct proc_dir_entry *proc_stat_entry;
+ struct proc_dir_entry *proc_ctrl_entry;
+} ctc_priv;
+
+/**
+ * Definition of our link level header.
+ */
+typedef struct ll_header_t {
+ __u16 length;
+ __u16 type;
+ __u16 unused;
+} ll_header;
+#define LL_HEADER_LENGTH (sizeof(ll_header))
+
+/**
+ * Compatibility macros for busy handling
+ * of network devices.
+ */
+#if LINUX_VERSION_CODE < 0x02032D
+static __inline__ void ctc_clear_busy(net_device *dev)
+{
+ clear_bit(0 ,(void *)&dev->tbusy);
+ mark_bh(NET_BH);
+}
+
+static __inline__ int ctc_test_and_set_busy(net_device *dev)
+{
+ return(test_and_set_bit(0, (void *)&dev->tbusy));
+}
+
+#define SET_DEVICE_START(device, value) dev->start = value
+#else
+static __inline__ void ctc_clear_busy(net_device *dev)
+{
+ clear_bit(0, &(((ctc_priv *)dev->priv)->tbusy));
+ netif_start_queue(dev);
+}
+
+static __inline__ int ctc_test_and_set_busy(net_device *dev)
+{
+ netif_stop_queue(dev);
+ return test_and_set_bit(0, &((ctc_priv *)dev->priv)->tbusy);
+}
+
+#define SET_DEVICE_START(device, value)
+#endif
+
+/**
+ * Print Banner.
+ */
+static void print_banner(void) {
+ static int printed;
+ char vbuf[] = "$Revision: 1.11 $";
+ char *version = vbuf;
+
+ if (printed)
+ return;
+ if ((version = strchr(version, ':'))) {
+ char *p = strchr(version + 1, '$');
+ if (p)
+ *p = '\0';
+ } else
+ version = " ??? ";
+ printk(KERN_INFO "CTC driver Version%s initialized\n", version);
+ printed = 1;
+}
+
+/**
+ * Return type of a detected device.
+ */
+static channel_type_t channel_type (senseid_t *id) {
+ channel_type_t type = channel_type_none;
+
+ switch (id->cu_type) {
+ case 0x3088:
+ switch (id->cu_model) {
+ case 0x08:
+ /**
+ * 3088-08 = CTCA
+ */
+ type = channel_type_ctca;
+ break;
+
+ case 0x1F:
+ /**
+ * 3088-1F = ESCON channel
+ */
+ type = channel_type_escon;
+ break;
+
+ /**
+ * 3088-01 = P390 OSA emulation
+ */
+ case 0x01:
+ /* fall thru */
+
+ /**
+ * 3088-60 = OSA/2 adapter
+ */
+ case 0x60:
+ /* fall thru */
+
+ /**
+ * 3088-61 = CISCO 7206 CLAW proto
+ * on ESCON
+ */
+ case 0x61:
+ /* fall thru */
+
+ /**
+ * 3088-62 = OSA/D device
+ */
+ case 0x62:
+ type = channel_type_unsupported;
+ break;
+
+ default:
+ type = channel_type_unknown;
+ printk(KERN_INFO
+ "channel: Unknown model found "
+ "3088-%02x\n", id->cu_model);
+ }
+ break;
+
+ default:
+ type = channel_type_none;
+ }
+ return type;
+}
+
+/**
+ * States of the interface statemachine.
+ */
+enum dev_states {
+ DEV_STATE_STOPPED,
+ DEV_STATE_STARTWAIT_RXTX,
+ DEV_STATE_STARTWAIT_RX,
+ DEV_STATE_STARTWAIT_TX,
+ DEV_STATE_STOPWAIT_RXTX,
+ DEV_STATE_STOPWAIT_RX,
+ DEV_STATE_STOPWAIT_TX,
+ DEV_STATE_RUNNING,
+ /**
+ * MUST be always the last element!!
+ */
+ NR_DEV_STATES
+};
+
+static const char *dev_state_names[] = {
+ "Stopped",
+ "StartWait RXTX",
+ "StartWait RX",
+ "StartWait TX",
+ "StopWait RXTX",
+ "StopWait RX",
+ "StopWait TX",
+ "Running",
+};
+
+/**
+ * Events of the interface statemachine.
+ */
+enum dev_events {
+ DEV_EVENT_START,
+ DEV_EVENT_STOP,
+ DEV_EVENT_RXUP,
+ DEV_EVENT_TXUP,
+ DEV_EVENT_RXDOWN,
+ DEV_EVENT_TXDOWN,
+ /**
+ * MUST be always the last element!!
+ */
+ NR_DEV_EVENTS
+};
+
+static const char *dev_event_names[] = {
+ "Start",
+ "Stop",
+ "RX up",
+ "TX up",
+ "RX down",
+ "TX down",
+};
+
+/**
+ * Events of the channel statemachine
+ */
+enum ch_events {
+ /**
+ * Events, representing return code of
+ * I/O operations (do_IO, halt_IO et al.)
+ */
+ CH_EVENT_IO_SUCCESS,
+ CH_EVENT_IO_EBUSY,
+ CH_EVENT_IO_ENODEV,
+ CH_EVENT_IO_EIO,
+ CH_EVENT_IO_UNKNOWN,
+
+ CH_EVENT_ATTNBUSY,
+ CH_EVENT_ATTN,
+ CH_EVENT_BUSY,
+
+ /**
+ * Events, representing unit-check
+ */
+ CH_EVENT_UC_RCRESET,
+ CH_EVENT_UC_RSRESET,
+ CH_EVENT_UC_TXTIMEOUT,
+ CH_EVENT_UC_TXPARITY,
+ CH_EVENT_UC_HWFAIL,
+ CH_EVENT_UC_RXPARITY,
+ CH_EVENT_UC_ZERO,
+ CH_EVENT_UC_UNKNOWN,
+
+ /**
+ * Events, representing subchannel-check
+ */
+ CH_EVENT_SC_UNKNOWN,
+
+ /**
+ * Event, representing normal IRQ
+ */
+ CH_EVENT_IRQ,
+ CH_EVENT_FINSTAT,
+
+ /**
+ * Event, representing timer expiry.
+ */
+ CH_EVENT_TIMER,
+
+ /**
+ * Events, representing commands from upper levels.
+ */
+ CH_EVENT_START,
+ CH_EVENT_STOP,
+
+ /**
+ * MUST be always the last element!!
+ */
+ NR_CH_EVENTS,
+};
+
+static const char *ch_event_names[] = {
+ "do_IO success",
+ "do_IO busy",
+ "do_IO enodev",
+ "do_IO ioerr",
+ "do_IO unknown",
+
+ "Status ATTN & BUSY",
+ "Status ATTN",
+ "Status BUSY",
+
+ "Unit check remote reset",
+ "Unit check remote system reset",
+ "Unit check TX timeout",
+ "Unit check TX parity",
+ "Unit check Hardware failure",
+ "Unit check RX parity",
+ "Unit check ZERO",
+ "Unit check Unknown",
+
+ "SubChannel check Unknown",
+
+ "IRQ normal",
+ "IRQ final",
+
+ "Timer",
+
+ "Start",
+ "Stop",
+};
+
+/**
+ * States of the channel statemachine.
+ */
+enum ch_states {
+ /**
+ * Channel not assigned to any device,
+ * initial state, direction invalid
+ */
+ CH_STATE_IDLE,
+
+ /**
+ * Channel assigned but not operating
+ */
+ CH_STATE_STOPPED,
+ CH_STATE_STARTWAIT,
+ CH_STATE_STARTRETRY,
+ CH_STATE_SETUPWAIT,
+ CH_STATE_RXINIT,
+ CH_STATE_TXINIT,
+ CH_STATE_RX,
+ CH_STATE_TX,
+ CH_STATE_RXIDLE,
+ CH_STATE_TXIDLE,
+ CH_STATE_RXERR,
+ CH_STATE_TXERR,
+ CH_STATE_TERM,
+ CH_STATE_DTERM,
+
+ /**
+ * MUST be always the last element!!
+ */
+ NR_CH_STATES,
+};
+
+static const char *ch_state_names[] = {
+ "Idle",
+ "Stopped",
+ "StartWait",
+ "StartRetry",
+ "SetupWait",
+ "RX init",
+ "TX init",
+ "RX",
+ "TX",
+ "RX idle",
+ "TX idle",
+ "RX error",
+ "TX error",
+ "Terminating",
+ "Restarting",
+};
+
+
+/**
+ * Dump header and first 16 bytes of an sk_buff for debugging purposes.
+ *
+ * @param skb The sk_buff to dump.
+ * @param offset Offset relative to skb-data, where to start the dump.
+ */
+static void ctc_dump_skb(struct sk_buff *skb, int offset)
+{
+ unsigned char *p = skb->data;
+ __u16 bl;
+ ll_header *header;
+ int i;
+
+ p += offset;
+ bl = *((__u16*)p);
+ p += 2;
+ header = (ll_header *)p;
+ p -= 2;
+
+ printk(KERN_DEBUG "dump:\n");
+ printk(KERN_DEBUG "blocklen=%d %04x\n", bl, bl);
+
+ printk(KERN_DEBUG "h->length=%d %04x\n", header->length,
+ header->length);
+ printk(KERN_DEBUG "h->type=%04x\n", header->type);
+ printk(KERN_DEBUG "h->unused=%04x\n", header->unused);
+ if (bl > 16)
+ bl = 16;
+ printk(KERN_DEBUG "data: ");
+ for (i = 0; i < bl; i++)
+ printk("%02x ", *p++);
+ printk("\n");
+}
+
+/**
+ * Bottom half routine.
+ *
+ * @param ch The channel to work on.
+ */
+static void ctc_bh(channel *ch)
+{
+ net_device *dev = ch->netdev;
+ ctc_priv *privptr = (ctc_priv *)dev->priv;
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(&ch->io_queue))) {
+ __u16 len = *((__u16*)skb->data);
+
+ skb_put(skb, 2 + LL_HEADER_LENGTH);
+ skb_pull(skb, 2);
+ skb->dev = dev;
+ skb->ip_summed = CHECKSUM_NONE;
+ while (len > 0) {
+ ll_header *header = (ll_header *)skb->data;
+ skb_pull(skb, LL_HEADER_LENGTH);
+ if ((ch->protocol == CTC_PROTO_S390) &&
+ (header->type != ETH_P_IP)) {
+ /**
+ * Check packet type only if we stick strictly
+ * to S/390's protocol of OS390. This only
+ * supports IP. Otherwise allow any packet
+ * type.
+ */
+ printk(KERN_WARNING
+ "%s Illegal packet type 0x%04x "
+ "received, dropping\n",
+ dev->name, header->type);
+ ctc_dump_skb(skb, -6);
+ privptr->stats.rx_dropped++;
+ privptr->stats.rx_frame_errors++;
+ dev_kfree_skb(skb);
+ goto again;
+ }
+ skb->protocol = ntohs(header->type);
+ header->length -= LL_HEADER_LENGTH;
+ if ((header->length > dev->mtu) ||
+ (header->length == 0)) {
+ printk(KERN_WARNING
+ "%s Illegal packet size %d "
+ "received (MTU=%d), "
+ "dropping\n", dev->name, header->length,
+ dev->mtu);
+ ctc_dump_skb(skb, -6);
+ privptr->stats.rx_dropped++;
+ privptr->stats.rx_length_errors++;
+ dev_kfree_skb(skb);
+ goto again;
+ }
+ skb_put(skb, header->length);
+ skb->mac.raw = skb->data;
+ /**
+ * Set truesize here to make the kernel's
+ * socket layer happy. If this is not done,
+ * the RX-routines of the socket code are dropping
+ * most of the received packets, because they "think"
+ * there isn't enough buffer space for the incoming
+ * data.
+ */
+ skb->truesize = skb->len;
+ len -= (LL_HEADER_LENGTH + header->length);
+ if (len > 0) {
+ /**
+ * Clone the skb only if there are still
+ * sub-packets.
+ */
+ struct sk_buff *skb2 =
+ skb_clone(skb, GFP_ATOMIC);
+ if (!skb2) {
+ printk(KERN_WARNING "%s Out of memory"
+ " in ctc_bh\n",
+ dev->name);
+ privptr->stats.rx_dropped++;
+ dev_kfree_skb(skb);
+ goto again;
+ }
+ netif_rx(skb2);
+ privptr->stats.rx_packets++;
+ privptr->stats.rx_bytes += skb2->len;
+ /**
+ * Advance pointers to next sub-packet.
+ */
+ skb_pull(skb, header->length);
+ skb_put(skb, LL_HEADER_LENGTH);
+ } else {
+ netif_rx(skb);
+ privptr->stats.rx_packets++;
+ privptr->stats.rx_bytes += skb->len;
+ }
+ }
+ again:
+ }
+ return;
+}
+
+/**
+ * Check return code of a preceeding do_IO, halt_IO etc...
+ *
+ * @param ch The channel, the error belongs to.
+ * @param return_code The error code to inspect.
+ */
+static void inline ccw_check_return_code (channel *ch, int return_code)
+{
+ switch (return_code) {
+ case 0:
+ fsm_event(ch->fsm, CH_EVENT_IO_SUCCESS, ch);
+ break;
+ case -EBUSY:
+ printk(KERN_INFO "ch-%04x: Busy !\n", ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_IO_EBUSY, ch);
+ break;
+ case -ENODEV:
+ printk(KERN_EMERG
+ "ch-%04x: Invalid device called for IO\n",
+ ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_IO_ENODEV, ch);
+ break;
+ case -EIO:
+ printk(KERN_EMERG
+ "ch-%04x: Status pending... \n", ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_IO_EIO, ch);
+ break;
+ default:
+ printk(KERN_EMERG
+ "ch-%04x: Unknown error in do_IO %04x\n",
+ ch->devno, return_code);
+ fsm_event(ch->fsm, CH_EVENT_IO_UNKNOWN, ch);
+ }
+}
+
+/**
+ * Check sense of a unit check.
+ *
+ * @param ch The channel, the sense code belongs to.
+ * @param sense The sense code to inspect.
+ */
+static void inline ccw_unit_check (channel *ch, unsigned char sense) {
+ if (sense & SNS0_INTERVENTION_REQ) {
+ if (sense & 0x01) {
+ printk(KERN_DEBUG
+ "ch-%04x: Interface disc. or Sel. reset "
+ "(remote)\n", ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_UC_RCRESET, ch);
+ } else {
+ printk(KERN_DEBUG "ch-%04x: System reset (remote)\n",
+ ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_UC_RSRESET, ch);
+ }
+ } else if (sense & SNS0_EQUIPMENT_CHECK) {
+ if (sense & SNS0_BUS_OUT_CHECK) {
+ printk(KERN_WARNING
+ "ch-%04x: Hardware malfunction (remote)\n",
+ ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_UC_HWFAIL, ch);
+ } else {
+ printk(KERN_WARNING
+ "ch-%04x: Read-data parity error (remote)\n",
+ ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_UC_RXPARITY, ch);
+ }
+ } else if (sense & SNS0_BUS_OUT_CHECK) {
+ if (sense & 0x04) {
+ printk(KERN_WARNING
+ "ch-%04x: Data-streaming timeout)\n",
+ ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_UC_TXTIMEOUT, ch);
+ } else {
+ printk(KERN_WARNING
+ "ch-%04x: Data-transfer parity error\n",
+ ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_UC_TXPARITY, ch);
+ }
+ } else if (sense == 0) {
+ printk(KERN_DEBUG "ch-%04x: Unit check\n", ch->devno);
+ fsm_event(ch->fsm, CH_EVENT_UC_ZERO, ch);
+ } else {
+ printk(KERN_WARNING
+ "ch-%04x: Unit Check with sense code: %02x\n",
+ ch->devno, sense);
+ fsm_event(ch->fsm, CH_EVENT_UC_UNKNOWN, ch);
+ }
+}
+
+static void ctc_purge_skb_queue(struct sk_buff_head *q)
+{
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(q))) {
+ atomic_dec(&skb->users);
+ dev_kfree_skb(skb);
+ }
+}
+
+/**
+ * Dummy NOP action for statemachines
+ */
+static void fsm_action_nop(fsm_instance *fi, int event, void *arg)
+{
+}
+
+/**
+ * Actions for channel - statemachines.
+ *****************************************************************************/
+
+/**
+ * Normal data has been send. Free the corresponding
+ * skb (it's in io_queue), reset dev->tbusy and
+ * revert to idle state.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_txdone(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+ ctc_priv *privptr = dev->priv;
+ struct sk_buff *skb;
+ int first = 1;
+ int i;
+
+ struct timeval done_stamp = xtime;
+ unsigned long duration =
+ (done_stamp.tv_sec - ch->prof.send_stamp.tv_sec) * 1000000 +
+ done_stamp.tv_usec - ch->prof.send_stamp.tv_usec;
+ if (duration > ch->prof.tx_time)
+ ch->prof.tx_time = duration;
+
+ if (ch->devstat->rescnt != 0)
+ printk(KERN_DEBUG "%s: TX not complete, remaining %d bytes\n",
+ dev->name, ch->devstat->rescnt);
+
+ fsm_deltimer(&ch->timer);
+ while ((skb = skb_dequeue(&ch->io_queue))) {
+ privptr->stats.tx_packets++;
+ privptr->stats.tx_bytes += skb->len - LL_HEADER_LENGTH;
+ if (first) {
+ privptr->stats.tx_bytes += 2;
+ first = 0;
+ }
+ atomic_dec(&skb->users);
+ dev_kfree_skb(skb);
+ }
+ spin_lock(&ch->collect_lock);
+ if (ch->dccw) {
+ for (i = 0; i < ch->dccw_count; i++)
+ clear_normalized_cda(&ch->dccw[i]);
+ kfree(ch->dccw);
+ ch->dccw = NULL;
+ }
+ if (ch->collect_len > 0) {
+ int rc;
+
+ ch->dccw_count = skb_queue_len(&ch->collect_queue) + 1;
+ if (ch->prof.maxmulti < (ch->collect_len + 2))
+ ch->prof.maxmulti = ch->collect_len + 2;
+ if (ch->prof.maxcqueue < ch->dccw_count)
+ ch->prof.maxcqueue = ch->dccw_count;
+
+ ch->dccw = kmalloc(ch->dccw_count *
+ sizeof(ccw1_t), GFP_ATOMIC|GFP_DMA);
+ if (!ch->dccw) {
+ spin_unlock(&ch->collect_lock);
+ printk(KERN_WARNING
+ "%s: Unable to alloc dynamic ccws\n",
+ dev->name);
+ return;
+ }
+
+ ch->dccw[0].cmd_code = CCW_CMD_PREPARE;
+ ch->dccw[0].flags = CCW_FLAG_SLI | CCW_FLAG_CC;
+ ch->dccw[0].count = 0;
+ ch->dccw[0].cda = 0;
+ i = 1;
+ while ((skb = skb_dequeue(&ch->collect_queue))) {
+ ch->prof.txlen += (skb->len - LL_HEADER_LENGTH);
+ if (i == 1)
+ *((__u16 *)skb_push(skb, 2)) =
+ ch->collect_len + 2;
+ ch->dccw[i].cmd_code = CCW_CMD_WRITE;
+ ch->dccw[i].flags = CCW_FLAG_SLI |
+ ((skb_queue_len(&ch->collect_queue))
+ ? CCW_FLAG_DC : 0);
+ ch->dccw[i].count = skb->len;
+ set_normalized_cda(&ch->dccw[i],
+ virt_to_phys(skb->data));
+ skb_queue_tail(&ch->io_queue, skb);
+ i++;
+ }
+ ch->collect_len = 0;
+ fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+ ch->prof.send_stamp = xtime;
+ rc = do_IO(ch->irq, ch->dccw, (intparm_t)ch, 0xff, 0);
+ ch->prof.doios_multi++;
+ if (rc != 0) {
+ fsm_deltimer(&ch->timer);
+ i = 0;
+ while ((skb = skb_dequeue(&ch->io_queue))) {
+ privptr->stats.tx_dropped++;
+ privptr->stats.tx_errors++;
+ atomic_dec(&skb->users);
+ dev_kfree_skb(skb);
+ i++;
+ }
+ kfree(ch->dccw);
+ ch->dccw = NULL;
+ if (i != ch->dccw_count)
+ printk(KERN_WARNING "ctc: i != nccws !!!\n");
+ ccw_check_return_code(ch, rc);
+ }
+ } else
+ fsm_newstate(fi, CH_STATE_TXIDLE);
+ ctc_clear_busy(dev);
+ spin_unlock(&ch->collect_lock);
+}
+
+/**
+ * Initial data is sent.
+ * Notify device statemachine that we are up and
+ * running.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_txidle(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+
+ fsm_deltimer(&ch->timer);
+ fsm_newstate(fi, CH_STATE_TXIDLE);
+ fsm_event(((ctc_priv *)ch->netdev->priv)->fsm, DEV_EVENT_TXUP,
+ ch->netdev);
+}
+
+/**
+ * Got normal data, check for sanity, queue it up, allocate new buffer
+ * trigger bottom half, and initiate next read.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_rx(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+ ctc_priv *privptr = dev->priv;
+ int len = ch->max_bufsize - ch->devstat->rescnt;
+ struct sk_buff *skb = ch->rx_skb;
+ __u16 block_len = *((__u16*)skb->data);
+ char *saved_data = skb->data;
+ int queued = 0;
+ int rc;
+
+ fsm_deltimer(&ch->timer);
+ if (len < 8) {
+ printk(KERN_WARNING "%s: got packet with length < 8\n",
+ dev->name);
+ privptr->stats.rx_dropped++;
+ privptr->stats.rx_length_errors++;
+ goto again;
+ }
+ if (len > ch->max_bufsize) {
+ printk(KERN_WARNING "%s: got packet with length > %d\n",
+ dev->name, ch->max_bufsize);
+ privptr->stats.rx_dropped++;
+ privptr->stats.rx_length_errors++;
+ goto again;
+ }
+ /**
+ * VM TCP seems to have a bug sending 2 trailing bytes of garbage.
+ */
+ if ((len < block_len) ||
+ ((len > block_len) && (ch->protocol != CTC_PROTO_S390)) ||
+ ((len > (block_len + 2)) && (ch->protocol == CTC_PROTO_S390))) {
+ printk(KERN_WARNING
+ "%s: got block length %d != rx length %d\n", dev->name,
+ block_len, len);
+ *((__u16*)skb->data) = len;
+ ctc_dump_skb(skb, 0);
+ privptr->stats.rx_dropped++;
+ privptr->stats.rx_length_errors++;
+ goto again;
+ }
+ block_len -= 2;
+ if (block_len > 0) {
+ *((__u16*)skb->data) = block_len;
+ skb_queue_tail(&ch->io_queue, skb);
+ queued++;
+ }
+ again:
+ if (queued) {
+ queue_task(&ch->tq, &tq_immediate);
+ mark_bh(IMMEDIATE_BH);
+ ch->rx_skb = dev_alloc_skb(ch->max_bufsize);
+ if (ch->rx_skb == NULL) {
+ printk(KERN_WARNING "%s: Couldn't alloc rx_skb in "
+ "ch_action_rx\n", dev->name);
+ /* TODO: retry after bottom half */
+ return;
+ }
+ } else {
+ skb->data = skb->tail = saved_data;
+ skb->len = 0;
+ }
+ ch->ccw[1].count = ch->max_bufsize;
+ set_normalized_cda(&ch->ccw[1], virt_to_phys(ch->rx_skb->data));
+ rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
+ if (rc != 0)
+ ccw_check_return_code(ch, rc);
+}
+
+/**
+ * Initialize connection by sending a __u16 of value 0.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_firstio(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ int rc;
+
+ if (fsm_getstate(fi) == CH_STATE_TXIDLE)
+ printk(KERN_DEBUG "ch-%04x: remote side issued READ?, "
+ "init ...\n", ch->devno);
+ fsm_deltimer(&ch->timer);
+ /**
+ * Don´t setup a timer for receiving the initial RX frame
+ * if in compatibility mode, since VM TCP delays the initial
+ * frame until it has some data to send.
+ */
+ if ((CHANNEL_DIRECTION(ch->flags) == WRITE) ||
+ (ch->protocol != CTC_PROTO_S390))
+ fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+
+ ch->dummy_buf = CTC_INITIAL_BLOCKLEN;
+ ch->ccw[1].count = 2; /* Transfer only length */
+ set_normalized_cda(&ch->ccw[1], virt_to_phys(&ch->dummy_buf));
+ fsm_newstate(fi, (CHANNEL_DIRECTION(ch->flags) == READ)
+ ? CH_STATE_RXINIT : CH_STATE_TXINIT);
+ rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
+ if (rc != 0) {
+ fsm_deltimer(&ch->timer);
+ fsm_newstate(fi, CH_STATE_SETUPWAIT);
+ ccw_check_return_code(ch, rc);
+ }
+ /**
+ * If in compatibility mode since we don´t setup a timer, we
+ * also signal RX channel up immediately. This enables us
+ * to send packets early which in turn usually triggers some
+ * reply from VM TCP which brings up the RX channel to it´s
+ * final state.
+ */
+ if ((CHANNEL_DIRECTION(ch->flags) == READ) &&
+ (ch->protocol == CTC_PROTO_S390)) {
+ net_device *dev = ch->netdev;
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_RXUP, dev);
+ }
+}
+
+/**
+ * Got initial data, check it. If OK,
+ * notify device statemachine that we are up and
+ * running.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_rxidle(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+ int rc;
+
+ fsm_deltimer(&ch->timer);
+ if (ch->dummy_buf >= CTC_INITIAL_BLOCKLEN) {
+ ch->rx_skb = dev_alloc_skb(ch->max_bufsize);
+ if (ch->rx_skb == NULL) {
+ printk(KERN_WARNING "%s: Couldn't alloc rx_skb in "
+ "ch_action_rxidle\n", dev->name);
+ return;
+ }
+ ch->ccw[1].count = ch->max_bufsize;
+ set_normalized_cda(&ch->ccw[1],
+ virt_to_phys(ch->rx_skb->data));
+ fsm_newstate(fi, CH_STATE_RXIDLE);
+ rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
+ if (rc != 0) {
+ fsm_newstate(fi, CH_STATE_RXINIT);
+ ccw_check_return_code(ch, rc);
+ } else
+ fsm_event(((ctc_priv *)dev->priv)->fsm,
+ DEV_EVENT_RXUP, dev);
+ } else {
+ printk(KERN_DEBUG "%s: Initial RX count %d not %d\n",
+ dev->name, ch->dummy_buf, CTC_INITIAL_BLOCKLEN);
+ ch_action_firstio(fi, event, arg);
+ }
+}
+
+/**
+ * Set channel into extended mode.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_setmode(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ int rc;
+ unsigned long saveflags;
+
+ fsm_deltimer(&ch->timer);
+ fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+ fsm_newstate(fi, CH_STATE_SETUPWAIT);
+ if (event == CH_EVENT_TIMER)
+ s390irq_spin_lock_irqsave(ch->irq, saveflags);
+ rc = do_IO(ch->irq, &ch->ccw[3], (intparm_t)ch, 0xff, 0);
+ if (event == CH_EVENT_TIMER)
+ s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
+ if (rc != 0) {
+ fsm_deltimer(&ch->timer);
+ fsm_newstate(fi, CH_STATE_STARTWAIT);
+ ccw_check_return_code(ch, rc);
+ } else
+ ch->retry = 0;
+}
+
+/**
+ * Setup channel.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_start(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ unsigned long saveflags;
+ int rc;
+ net_device *dev;
+
+ if (ch == NULL) {
+ printk(KERN_WARNING "ch_action_start ch=NULL\n");
+ return;
+ }
+ if (ch->netdev == NULL) {
+ printk(KERN_WARNING "ch_action_start dev=NULL, irq=%d\n",
+ ch->irq);
+ return;
+ }
+
+ dev = ch->netdev;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "%s: %s channel start\n", dev->name,
+ (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX");
+#endif
+
+ INIT_LIST_HEAD(&ch->tq.list);
+ ch->tq.sync = 0;
+ ch->tq.routine = (void *)(void *)ctc_bh;
+ ch->tq.data = ch;
+
+ ch->ccw[0].cmd_code = CCW_CMD_PREPARE;
+ ch->ccw[0].flags = CCW_FLAG_SLI | CCW_FLAG_CC;
+ ch->ccw[0].count = 0;
+ ch->ccw[0].cda = 0;
+ if (CHANNEL_DIRECTION(ch->flags) == READ) {
+ ch->ccw[1].cmd_code = CCW_CMD_READ;
+ ch->ccw[1].flags = CCW_FLAG_SLI;
+ ch->ccw[1].count = 0;
+ ch->ccw[1].cda = 0;
+ } else {
+ ch->ccw[1].cmd_code = CCW_CMD_WRITE;
+ ch->ccw[1].flags = CCW_FLAG_SLI | CCW_FLAG_CC;
+ ch->ccw[1].count = 0;
+ ch->ccw[1].cda = 0;
+ }
+ ch->ccw[2].cmd_code = CCW_CMD_NOOP; /* jointed CE + DE */
+ ch->ccw[2].flags = CCW_FLAG_SLI;
+ ch->ccw[2].count = 0;
+ ch->ccw[2].cda = 0;
+ fsm_newstate(fi, CH_STATE_STARTWAIT);
+ fsm_addtimer(&ch->timer, 1000, CH_EVENT_TIMER, ch);
+ s390irq_spin_lock_irqsave(ch->irq, saveflags);
+ rc = halt_IO(ch->irq, (intparm_t)ch, 0);
+ s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
+ if (rc != 0) {
+ fsm_deltimer(&ch->timer);
+ ccw_check_return_code(ch, rc);
+ }
+#ifdef DEBUG
+ printk(KERN_DEBUG "ctc: %s(): leaving\n", __FUNCTION__);
+#endif
+}
+
+/**
+ * Shutdown a channel.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_haltio(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ unsigned long saveflags;
+ int rc;
+ int oldstate;
+
+ fsm_deltimer(&ch->timer);
+ fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+ if (event == CH_EVENT_STOP)
+ s390irq_spin_lock_irqsave(ch->irq, saveflags);
+ oldstate = fsm_getstate(fi);
+ fsm_newstate(fi, CH_STATE_TERM);
+ rc = halt_IO (ch->irq, (intparm_t)ch, 0);
+ if (event == CH_EVENT_STOP)
+ s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
+ if (rc != 0) {
+ fsm_deltimer(&ch->timer);
+ fsm_newstate(fi, oldstate);
+ ccw_check_return_code(ch, rc);
+ }
+}
+
+/**
+ * A channel has successfully been halted.
+ * Cleanup it's queue and notify interface statemachine.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_stopped(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+
+ if (ch->dccw) {
+ printk(KERN_WARNING "ch_action_stopped: dccw !NULL\n");
+ return;
+ }
+ fsm_deltimer(&ch->timer);
+ fsm_newstate(fi, CH_STATE_STOPPED);
+ if (CHANNEL_DIRECTION(ch->flags) == READ) {
+ skb_queue_purge(&ch->io_queue);
+ if (ch->rx_skb != NULL) {
+ dev_kfree_skb(ch->rx_skb);
+ ch->rx_skb = NULL;
+ }
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
+ } else {
+ ctc_purge_skb_queue(&ch->io_queue);
+ spin_lock(&ch->collect_lock);
+ ctc_purge_skb_queue(&ch->collect_queue);
+ if (ch->dccw)
+ kfree(ch->dccw);
+ ch->dccw = NULL;
+ ch->collect_len = 0;
+ spin_unlock(&ch->collect_lock);
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
+ }
+}
+
+/**
+ * Handle error during setup of channel.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_setuperr(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+
+ /**
+ * Special case: Got UC_RCRESET on setmode.
+ * This means that remote side isn't setup. In this case
+ * simply retry after some 10 secs...
+ */
+ if ((fsm_getstate(fi) == CH_STATE_SETUPWAIT) &&
+ ((event == CH_EVENT_UC_RCRESET) ||
+ (event == CH_EVENT_UC_RSRESET) ) ) {
+ fsm_newstate(fi, CH_STATE_STARTRETRY);
+ fsm_deltimer(&ch->timer);
+ fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+ if (CHANNEL_DIRECTION(ch->flags) == READ) {
+ int rc = halt_IO (ch->irq, (intparm_t)ch, 0);
+ if (rc != 0)
+ ccw_check_return_code(ch, rc);
+ }
+ return;
+ }
+
+ printk(KERN_DEBUG "%s: Error %s during %s channel setup state=%s\n",
+ dev->name, ch_event_names[event],
+ (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX",
+ fsm_getstate_str(fi));
+ if (CHANNEL_DIRECTION(ch->flags) == READ) {
+ fsm_newstate(fi, CH_STATE_RXERR);
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
+ } else {
+ fsm_newstate(fi, CH_STATE_TXERR);
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
+ }
+}
+
+/**
+ * Restart a channel after an error.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_restart(fsm_instance *fi, int event, void *arg)
+{
+ unsigned long saveflags;
+ int oldstate;
+ int rc;
+
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+
+ printk(KERN_DEBUG "%s: %s channel restart\n", dev->name,
+ (CHANNEL_DIRECTION(ch->flags) == READ) ? "RX" : "TX");
+
+ fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+ oldstate = fsm_getstate(fi);
+ fsm_newstate(fi, CH_STATE_STARTWAIT);
+ if (event == CH_EVENT_TIMER)
+ s390irq_spin_lock_irqsave(ch->irq, saveflags);
+ rc = halt_IO (ch->irq, (intparm_t)ch, 0);
+ if (event == CH_EVENT_TIMER)
+ s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
+ if (rc != 0) {
+ fsm_deltimer(&ch->timer);
+ fsm_newstate(fi, oldstate);
+ ccw_check_return_code(ch, rc);
+ }
+}
+
+/**
+ * Handle error during RX initial handshake (exchange of
+ * 0-length block header)
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_rxiniterr(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+
+ if (event == CH_EVENT_TIMER) {
+ fsm_deltimer(&ch->timer);
+ printk(KERN_DEBUG "%s: Timeout during RX init handshake\n",
+ dev->name);
+ if (ch->retry++ < 3)
+ ch_action_restart(fi, event, arg);
+ else {
+ fsm_newstate(fi, CH_STATE_RXERR);
+ fsm_event(((ctc_priv *)dev->priv)->fsm,
+ DEV_EVENT_RXDOWN, dev);
+ }
+ } else
+ printk(KERN_WARNING "%s: Error during RX init handshake\n",
+ dev->name);
+}
+
+/**
+ * Notify device statemachine if we gave up initialization
+ * of RX channel.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_rxinitfail(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+
+ fsm_newstate(fi, CH_STATE_RXERR);
+ printk(KERN_WARNING "%s: RX initialization failed\n", dev->name);
+ printk(KERN_WARNING "%s: RX <-> RX connection detected\n", dev->name);
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
+}
+
+/**
+ * Handle RX Unit check remote reset (remote disconnected)
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_rxdisc(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ channel *ch2;
+ net_device *dev = ch->netdev;
+
+ fsm_deltimer(&ch->timer);
+ printk(KERN_DEBUG "%s: Got remote disconnect, re-initializing ...\n",
+ dev->name);
+
+ /**
+ * Notify device statemachine
+ */
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
+
+ fsm_newstate(fi, CH_STATE_DTERM);
+ ch2 = ((ctc_priv *)dev->priv)->channel[WRITE];
+ fsm_newstate(ch2->fsm, CH_STATE_DTERM);
+
+ halt_IO(ch->irq, (intparm_t)ch, 0);
+ halt_IO(ch2->irq, (intparm_t)ch2, 0);
+}
+
+/**
+ * Handle error during TX channel initialization.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_txiniterr(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+
+ if (event == CH_EVENT_TIMER) {
+ fsm_deltimer(&ch->timer);
+ printk(KERN_DEBUG "%s: Timeout during TX init handshake\n",
+ dev->name);
+ if (ch->retry++ < 3)
+ ch_action_restart(fi, event, arg);
+ else {
+ fsm_newstate(fi, CH_STATE_TXERR);
+ fsm_event(((ctc_priv *)dev->priv)->fsm,
+ DEV_EVENT_TXDOWN, dev);
+ }
+ } else
+ printk(KERN_WARNING "%s: Error during TX init handshake\n",
+ dev->name);
+}
+
+/**
+ * Handle TX timeout by retrying operation.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_txretry(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+ unsigned long saveflags;
+
+ fsm_deltimer(&ch->timer);
+ if (ch->retry++ > 3) {
+ printk(KERN_DEBUG "%s: TX retry failed, restarting channel\n",
+ dev->name);
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
+ ch_action_restart(fi, event, arg);
+ } else {
+ struct sk_buff *skb;
+
+ printk(KERN_DEBUG "%s: TX retry %d\n", dev->name, ch->retry);
+ if ((skb = skb_peek(&ch->io_queue))) {
+ int rc = 0;
+
+ fsm_addtimer(&ch->timer, 1000, CH_EVENT_TIMER, ch);
+ if (event == CH_EVENT_TIMER)
+ s390irq_spin_lock_irqsave(ch->irq, saveflags);
+ if (ch->dccw)
+ rc = do_IO(ch->irq, ch->dccw, (intparm_t)ch,
+ 0xff, 0);
+ else {
+ clear_normalized_cda(&ch->ccw[1]);
+ ch->ccw[1].count = skb->len;
+ set_normalized_cda(&ch->ccw[1],
+ virt_to_phys(skb->data));
+ rc = do_IO(ch->irq, &ch->ccw[0],
+ (intparm_t)ch, 0xff, 0);
+ }
+ if (event == CH_EVENT_TIMER)
+ s390irq_spin_unlock_irqrestore(ch->irq,
+ saveflags);
+ if (rc != 0) {
+ fsm_deltimer(&ch->timer);
+ ccw_check_return_code(ch, rc);
+ ctc_purge_skb_queue(&ch->io_queue);
+ }
+ }
+ }
+
+}
+
+/**
+ * Handle fatal errors during an I/O command.
+ *
+ * @param fi An instance of a channel statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from channel * upon call.
+ */
+static void ch_action_iofatal(fsm_instance *fi, int event, void *arg)
+{
+ channel *ch = (channel *)arg;
+ net_device *dev = ch->netdev;
+
+ fsm_deltimer(&ch->timer);
+ if (CHANNEL_DIRECTION(ch->flags) == READ) {
+ printk(KERN_DEBUG "%s: RX I/O error\n", dev->name);
+ fsm_newstate(fi, CH_STATE_RXERR);
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_RXDOWN, dev);
+ } else {
+ printk(KERN_DEBUG "%s: TX I/O error\n", dev->name);
+ fsm_newstate(fi, CH_STATE_TXERR);
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_TXDOWN, dev);
+ }
+}
+
+/**
+ * The statemachine for a channel.
+ */
+static const fsm_node ch_fsm[] = {
+ { CH_STATE_STOPPED, CH_EVENT_STOP, fsm_action_nop },
+ { CH_STATE_STOPPED, CH_EVENT_START, ch_action_start },
+ { CH_STATE_STOPPED, CH_EVENT_FINSTAT, fsm_action_nop },
+
+ { CH_STATE_STARTWAIT, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_STARTWAIT, CH_EVENT_START, fsm_action_nop },
+ { CH_STATE_STARTWAIT, CH_EVENT_FINSTAT, ch_action_setmode },
+ { CH_STATE_STARTWAIT, CH_EVENT_TIMER, ch_action_setuperr },
+ { CH_STATE_STARTWAIT, CH_EVENT_IO_ENODEV, ch_action_iofatal },
+ { CH_STATE_STARTWAIT, CH_EVENT_IO_EIO, ch_action_iofatal },
+
+ { CH_STATE_STARTRETRY, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_STARTRETRY, CH_EVENT_TIMER, ch_action_setmode },
+ { CH_STATE_STARTRETRY, CH_EVENT_FINSTAT, fsm_action_nop },
+
+ { CH_STATE_SETUPWAIT, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_SETUPWAIT, CH_EVENT_START, fsm_action_nop },
+ { CH_STATE_SETUPWAIT, CH_EVENT_FINSTAT, ch_action_firstio },
+ { CH_STATE_SETUPWAIT, CH_EVENT_UC_RCRESET, ch_action_setuperr },
+ { CH_STATE_SETUPWAIT, CH_EVENT_UC_RSRESET, ch_action_setuperr },
+ { CH_STATE_SETUPWAIT, CH_EVENT_TIMER, ch_action_setmode },
+ { CH_STATE_SETUPWAIT, CH_EVENT_IO_ENODEV, ch_action_iofatal },
+ { CH_STATE_SETUPWAIT, CH_EVENT_IO_EIO, ch_action_iofatal },
+
+ { CH_STATE_RXINIT, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_RXINIT, CH_EVENT_START, fsm_action_nop },
+ { CH_STATE_RXINIT, CH_EVENT_FINSTAT, ch_action_rxidle },
+ { CH_STATE_RXINIT, CH_EVENT_UC_RCRESET, ch_action_rxiniterr },
+ { CH_STATE_RXINIT, CH_EVENT_UC_RSRESET, ch_action_rxiniterr },
+ { CH_STATE_RXINIT, CH_EVENT_TIMER, ch_action_rxiniterr },
+ { CH_STATE_RXINIT, CH_EVENT_ATTNBUSY, ch_action_rxinitfail },
+ { CH_STATE_RXINIT, CH_EVENT_IO_ENODEV, ch_action_iofatal },
+ { CH_STATE_RXINIT, CH_EVENT_IO_EIO, ch_action_iofatal },
+ { CH_STATE_RXINIT, CH_EVENT_UC_ZERO, ch_action_firstio },
+
+ { CH_STATE_RXIDLE, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_RXIDLE, CH_EVENT_START, fsm_action_nop },
+ { CH_STATE_RXIDLE, CH_EVENT_FINSTAT, ch_action_rx },
+ { CH_STATE_RXIDLE, CH_EVENT_UC_RCRESET, ch_action_rxdisc },
+// { CH_STATE_RXIDLE, CH_EVENT_UC_RSRESET, ch_action_rxretry },
+ { CH_STATE_RXIDLE, CH_EVENT_IO_ENODEV, ch_action_iofatal },
+ { CH_STATE_RXIDLE, CH_EVENT_IO_EIO, ch_action_iofatal },
+
+ { CH_STATE_TXINIT, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_TXINIT, CH_EVENT_START, fsm_action_nop },
+ { CH_STATE_TXINIT, CH_EVENT_FINSTAT, ch_action_txidle },
+ { CH_STATE_TXINIT, CH_EVENT_UC_RCRESET, ch_action_txiniterr },
+ { CH_STATE_TXINIT, CH_EVENT_UC_RSRESET, ch_action_txiniterr },
+ { CH_STATE_TXINIT, CH_EVENT_TIMER, ch_action_txiniterr },
+ { CH_STATE_TXINIT, CH_EVENT_IO_ENODEV, ch_action_iofatal },
+ { CH_STATE_TXINIT, CH_EVENT_IO_EIO, ch_action_iofatal },
+
+ { CH_STATE_TXIDLE, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_TXIDLE, CH_EVENT_START, fsm_action_nop },
+ { CH_STATE_TXIDLE, CH_EVENT_FINSTAT, ch_action_firstio },
+ { CH_STATE_TXIDLE, CH_EVENT_UC_RCRESET, fsm_action_nop },
+ { CH_STATE_TXIDLE, CH_EVENT_UC_RSRESET, fsm_action_nop },
+ { CH_STATE_TXIDLE, CH_EVENT_IO_ENODEV, ch_action_iofatal },
+ { CH_STATE_TXIDLE, CH_EVENT_IO_EIO, ch_action_iofatal },
+
+ { CH_STATE_TERM, CH_EVENT_STOP, fsm_action_nop },
+ { CH_STATE_TERM, CH_EVENT_START, ch_action_restart },
+ { CH_STATE_TERM, CH_EVENT_FINSTAT, ch_action_stopped },
+ { CH_STATE_TERM, CH_EVENT_UC_RCRESET, fsm_action_nop },
+ { CH_STATE_TERM, CH_EVENT_UC_RSRESET, fsm_action_nop },
+
+ { CH_STATE_DTERM, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_DTERM, CH_EVENT_START, ch_action_restart },
+ { CH_STATE_DTERM, CH_EVENT_FINSTAT, ch_action_setmode },
+ { CH_STATE_DTERM, CH_EVENT_UC_RCRESET, fsm_action_nop },
+ { CH_STATE_DTERM, CH_EVENT_UC_RSRESET, fsm_action_nop },
+
+ { CH_STATE_TX, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_TX, CH_EVENT_START, fsm_action_nop },
+ { CH_STATE_TX, CH_EVENT_FINSTAT, ch_action_txdone },
+ { CH_STATE_TX, CH_EVENT_UC_RCRESET, ch_action_txretry },
+ { CH_STATE_TX, CH_EVENT_UC_RSRESET, ch_action_txretry },
+ { CH_STATE_TX, CH_EVENT_TIMER, ch_action_txretry },
+ { CH_STATE_TX, CH_EVENT_IO_ENODEV, ch_action_iofatal },
+ { CH_STATE_TX, CH_EVENT_IO_EIO, ch_action_iofatal },
+
+ { CH_STATE_RXERR, CH_EVENT_STOP, ch_action_haltio },
+ { CH_STATE_TXERR, CH_EVENT_STOP, ch_action_haltio },
+};
+
+static const int CH_FSM_LEN = sizeof(ch_fsm) / sizeof(fsm_node);
+
+/**
+ * Functions related to setup and device detection.
+ *****************************************************************************/
+
+/**
+ * Add a new channel to the list of channels.
+ * Keeps the channel list sorted.
+ *
+ * @param irq The IRQ to be used by the new channel.
+ * @param devno The device number of the new channel.
+ * @param type The type class of the new channel.
+ *
+ * @return 0 on success, !0 on error.
+ */
+static int add_channel(int irq, __u16 devno, channel_type_t type)
+{
+ channel **c = &channels;
+ channel *ch;
+ char name[10];
+ int ret = -1;
+
+ if ((ch = (channel *)kmalloc(sizeof(channel), GFP_KERNEL)) == NULL)
+ goto out;
+ memset(ch, 0, sizeof(channel));
+ if ((ch->ccw = (ccw1_t *)kmalloc(sizeof(ccw1_t) * 5,
+ GFP_KERNEL|GFP_DMA)) == NULL)
+ goto out_ch;
+
+ /**
+ * "static" ccws are used in the following way:
+ *
+ * ccw[0..2] (Channel program for generic I/O):
+ * 0: prepare
+ * 1: read or write (depending on direction)
+ * 2: nop
+ * ccw[3..4] (Channel program for initial channel setup):
+ * 3: set extended mode
+ * 4: nop
+ *
+ * ch->ccw[0..2] are initialized in ch_action_start because
+ * the channel's direction is yet unknown here.
+ */
+ ch->ccw[3].cmd_code = CCW_CMD_SET_EXTENDED;
+ ch->ccw[3].flags = CCW_FLAG_SLI;
+ ch->ccw[3].count = 0;
+ ch->ccw[3].cda = 0;
+
+ ch->ccw[4].cmd_code = CCW_CMD_NOOP;
+ ch->ccw[4].flags = CCW_FLAG_SLI;
+ ch->ccw[4].count = 0;
+ ch->ccw[4].cda = 0;
+
+ ch->irq = irq;
+ ch->devno = devno;
+ ch->type = type;
+ sprintf(name, "ch-%04x", devno);
+ ch->fsm = init_fsm(name, ch_state_names,
+ ch_event_names, NR_CH_STATES, NR_CH_EVENTS,
+ ch_fsm, CH_FSM_LEN, GFP_KERNEL);
+ if (ch->fsm == NULL)
+ goto out_ccw;
+ fsm_newstate(ch->fsm, CH_STATE_IDLE);
+ if ((ch->devstat = (devstat_t*)kmalloc(sizeof(devstat_t), GFP_KERNEL))
+ == NULL)
+ goto out_ccw;
+ memset(ch->devstat, 0, sizeof(devstat_t));
+ while (*c && ((*c)->devno < devno))
+ c = &(*c)->next;
+ if ((*c)->devno == devno) {
+ printk(KERN_DEBUG
+ "ctc: add_channel: device %04x already in list\n",
+ (*c)->devno);
+ ret = 0;
+ goto out_devstat;
+ }
+ fsm_settimer(ch->fsm, &ch->timer);
+ skb_queue_head_init(&ch->io_queue);
+ skb_queue_head_init(&ch->collect_queue);
+ ch->next = *c;
+ *c = ch;
+ return 0;
+out_devstat:
+ kfree(ch->devstat);
+out_ccw:
+ kfree(ch->ccw);
+out_ch:
+ kfree(ch);
+out:
+ if (ret)
+ printk(KERN_WARNING "ctc: Out of memory in add_channel\n");
+ return ret;
+}
+
+/**
+ * scan for all channels and create an entry in the channels list
+ * for every supported channel.
+ *
+ * @param print_result Flag: If !0, print a final result.
+ */
+static void channel_scan(int print_result)
+{
+ int irq;
+ int nr_escon = 0;
+ int nr_ctca = 0;
+ s390_dev_info_t di;
+
+ for (irq = 0; irq < NR_IRQS; irq++) {
+ if (get_dev_info_by_irq(irq, &di) == 0) {
+ if ((di.status == DEVSTAT_NOT_OPER) ||
+ (di.status == DEVSTAT_DEVICE_OWNED))
+ continue;
+ switch (channel_type(&di.sid_data)) {
+ case channel_type_ctca:
+ /* CTC/A */
+ if (!add_channel(irq, di.devno,
+ channel_type_ctca))
+ nr_ctca++;
+ break;
+ case channel_type_escon:
+ /* ESCON */
+ if (!add_channel(irq, di.devno,
+ channel_type_escon))
+ nr_escon++;
+ break;
+ default:
+ }
+ }
+ }
+ if (print_result) {
+ if (nr_escon + nr_ctca)
+ printk(KERN_INFO
+ "ctc: %d CTC/A channel%s and %d ESCON "
+ "channel%s found.\n",
+ nr_ctca, (nr_ctca == 1) ? "s" : "",
+ nr_escon, (nr_escon == 1) ? "s" : "");
+ else
+ printk(KERN_INFO "ctc: No channel devices found.\n");
+ }
+}
+
+/**
+ * Release a specific channel in the channel list.
+ *
+ * @param ch Pointer to channel struct to be released.
+ */
+static void channel_free(channel *ch)
+{
+ ch->flags &= ~CHANNEL_FLAGS_INUSE;
+ fsm_newstate(ch->fsm, CH_STATE_IDLE);
+}
+
+
+/**
+ * Get a specific channel from the channel list.
+ *
+ * @param type Type of channel we are interested in.
+ * @param devno Device number of channel we are interested in.
+ * @param direction Direction we want to use this channel for.
+ *
+ * @return Pointer to a channel or NULL if no matching channel available.
+ */
+static channel *channel_get(channel_type_t type, int devno, int direction)
+{
+ channel *ch = channels;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: %s(): searching for ch with devno %d and type %d\n",
+ __FUNCTION__, devno, type);
+#endif
+
+ while (ch && ((ch->devno != devno) || (ch->type != type))) {
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: %s(): ch=0x%p (devno=%d, type=%d\n",
+ __FUNCTION__, ch, ch->devno, ch->type);
+#endif
+ ch = ch->next;
+ }
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: %s(): ch=0x%pq (devno=%d, type=%d\n",
+ __FUNCTION__, ch, ch->devno, ch->type);
+#endif
+ if (!ch) {
+ printk(KERN_WARNING "ctc: %s(): channel with devno %d "
+ "and type %d not found in channel list\n",
+ __FUNCTION__, devno, type);
+ }
+ else {
+ if (ch->flags & CHANNEL_FLAGS_INUSE)
+ ch = NULL;
+ else {
+ ch->flags |= CHANNEL_FLAGS_INUSE;
+ ch->flags &= ~CHANNEL_FLAGS_RWMASK;
+ ch->flags |= (direction == WRITE)
+ ? CHANNEL_FLAGS_WRITE:CHANNEL_FLAGS_READ;
+ fsm_newstate(ch->fsm, CH_STATE_STOPPED);
+ }
+ }
+ return ch;
+}
+
+
+/**
+ * Get the next free channel from the channel list
+ *
+ * @param type Type of channel we are interested in.
+ * @param direction Direction we want to use this channel for.
+ *
+ * @return Pointer to a channel or NULL if no matching channel available.
+ */
+static channel *channel_get_next(channel_type_t type, int direction)
+{
+ channel *ch = channels;
+
+ while (ch && (ch->type != type || (ch->flags & CHANNEL_FLAGS_INUSE)))
+ ch = ch->next;
+ if (ch) {
+ ch->flags |= CHANNEL_FLAGS_INUSE;
+ ch->flags &= ~CHANNEL_FLAGS_RWMASK;
+ ch->flags |= (direction == WRITE)
+ ? CHANNEL_FLAGS_WRITE:CHANNEL_FLAGS_READ;
+ fsm_newstate(ch->fsm, CH_STATE_STOPPED);
+ }
+ return ch;
+}
+
+/**
+ * Return the channel type by name.
+ *
+ * @param name Name of network interface.
+ *
+ * @return Type class of channel to be used for that interface.
+ */
+static channel_type_t inline extract_channel_media(char *name)
+{
+ channel_type_t ret = channel_type_unknown;
+
+ if (name != NULL) {
+ if (strncmp(name, "ctc", 3) == 0)
+ ret = channel_type_ctca;
+ if (strncmp(name, "escon", 5) == 0)
+ ret = channel_type_escon;
+ }
+ return ret;
+}
+
+/**
+ * Find a channel in the list by its IRQ.
+ *
+ * @param irq IRQ to search for.
+ *
+ * @return Pointer to channel or NULL if no matching channel found.
+ */
+static channel *find_channel_by_irq(int irq)
+{
+ channel *ch = channels;
+ while (ch && (ch->irq != irq))
+ ch = ch->next;
+ return ch;
+}
+
+/**
+ * Main IRQ handler.
+ *
+ * @param irq The IRQ to handle.
+ * @param intparm IRQ params.
+ * @param regs CPU registers.
+ */
+static void ctc_irq_handler (int irq, void *intparm, struct pt_regs *regs)
+{
+ devstat_t *devstat = (devstat_t *)intparm;
+ channel *ch = (channel *)devstat->intparm;
+ net_device *dev;
+
+ /**
+ * Check for unsolicited interrupts.
+ * If intparm is NULL, then loop over all our known
+ * channels and try matching the irq number.
+ */
+ if (ch == NULL) {
+ if ((ch = find_channel_by_irq(irq)) == NULL) {
+ printk(KERN_WARNING
+ "ctc: Got unsolicited irq: %04x c-%02x d-%02x"
+ "f-%02x\n", devstat->devno, devstat->cstat,
+ devstat->dstat, devstat->flag);
+ goto done;
+ }
+ }
+
+ clear_normalized_cda(&ch->ccw[1]);
+ dev = (net_device *)(ch->netdev);
+ if (dev == NULL) {
+ printk(KERN_CRIT
+ "ctc: ctc_irq_handler dev = NULL irq=%d, ch=0x%p\n",
+ irq, ch);
+ goto done;
+ }
+ if (intparm == NULL)
+ printk(KERN_DEBUG "%s: Channel %04x found by IRQ %d\n",
+ dev->name, ch->devno, irq);
+
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "%s: interrupt for device: %04x received c-%02x d-%02x "
+ "f-%02x\n", dev->name, devstat->devno, devstat->cstat,
+ devstat->dstat, devstat->flag);
+#endif
+
+ /* Check for good subchannel return code, otherwise error message */
+ if (devstat->cstat) {
+ fsm_event(ch->fsm, CH_EVENT_SC_UNKNOWN, ch);
+ printk(KERN_WARNING
+ "%s: subchannel check for device: %04x - %02x %02x "
+ "%02x\n", dev->name, ch->devno, devstat->cstat,
+ devstat->dstat, devstat->flag);
+ goto done;
+ }
+
+ /* Check the reason-code of a unit check */
+ if (devstat->dstat & DEV_STAT_UNIT_CHECK) {
+ ccw_unit_check(ch, devstat->ii.sense.data[0]);
+ goto done;
+ }
+ if (devstat->dstat & DEV_STAT_BUSY) {
+ if (devstat->dstat & DEV_STAT_ATTENTION)
+ fsm_event(ch->fsm, CH_EVENT_ATTNBUSY, ch);
+ else
+ fsm_event(ch->fsm, CH_EVENT_BUSY, ch);
+ goto done;
+ }
+ if (devstat->dstat & DEV_STAT_ATTENTION) {
+ fsm_event(ch->fsm, CH_EVENT_ATTN, ch);
+ goto done;
+ }
+ if (devstat->flag & DEVSTAT_FINAL_STATUS)
+ fsm_event(ch->fsm, CH_EVENT_FINSTAT, ch);
+ else
+ fsm_event(ch->fsm, CH_EVENT_IRQ, ch);
+
+ done:
+}
+
+/**
+ * Actions for interface - statemachine.
+ *****************************************************************************/
+
+/**
+ * Startup channels by sending CH_EVENT_START to each channel.
+ *
+ * @param fi An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from net_device * upon call.
+ */
+static void dev_action_start(fsm_instance *fi, int event, void *arg)
+{
+ net_device *dev = (net_device *)arg;
+ ctc_priv *privptr = dev->priv;
+ int direction;
+
+ fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX);
+ for (direction = READ; direction <= WRITE; direction++) {
+ channel *ch = privptr->channel[direction];
+ fsm_event(ch->fsm, CH_EVENT_START, ch);
+ }
+}
+
+/**
+ * Shutdown channels by sending CH_EVENT_STOP to each channel.
+ *
+ * @param fi An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from net_device * upon call.
+ */
+static void dev_action_stop(fsm_instance *fi, int event, void *arg)
+{
+ net_device *dev = (net_device *)arg;
+ ctc_priv *privptr = dev->priv;
+ int direction;
+
+ fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX);
+ for (direction = READ; direction <= WRITE; direction++) {
+ channel *ch = privptr->channel[direction];
+ fsm_event(ch->fsm, CH_EVENT_STOP, ch);
+ }
+}
+
+/**
+ * Called from channel statemachine
+ * when a channel is up and running.
+ *
+ * @param fi An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from net_device * upon call.
+ */
+static void dev_action_chup(fsm_instance *fi, int event, void *arg)
+{
+ net_device *dev = (net_device *)arg;
+
+ switch (fsm_getstate(fi)) {
+ case DEV_STATE_STARTWAIT_RXTX:
+ if (event == DEV_EVENT_RXUP)
+ fsm_newstate(fi, DEV_STATE_STARTWAIT_TX);
+ else
+ fsm_newstate(fi, DEV_STATE_STARTWAIT_RX);
+ break;
+ case DEV_STATE_STARTWAIT_RX:
+ if (event == DEV_EVENT_RXUP) {
+ fsm_newstate(fi, DEV_STATE_RUNNING);
+ printk(KERN_INFO
+ "%s: connected with remote side\n",
+ dev->name);
+ ctc_clear_busy(dev);
+ }
+ break;
+ case DEV_STATE_STARTWAIT_TX:
+ if (event == DEV_EVENT_TXUP) {
+ fsm_newstate(fi, DEV_STATE_RUNNING);
+ printk(KERN_INFO
+ "%s: connected with remote side\n",
+ dev->name);
+ ctc_clear_busy(dev);
+ }
+ break;
+ case DEV_STATE_STOPWAIT_TX:
+ if (event == DEV_EVENT_RXUP)
+ fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX);
+ break;
+ case DEV_STATE_STOPWAIT_RX:
+ if (event == DEV_EVENT_TXUP)
+ fsm_newstate(fi, DEV_STATE_STOPWAIT_RXTX);
+ break;
+ }
+}
+
+/**
+ * Called from channel statemachine
+ * when a channel has been shutdown.
+ *
+ * @param fi An instance of an interface statemachine.
+ * @param event The event, just happened.
+ * @param arg Generic pointer, casted from net_device * upon call.
+ */
+static void dev_action_chdown(fsm_instance *fi, int event, void *arg)
+{
+ switch (fsm_getstate(fi)) {
+ case DEV_STATE_RUNNING:
+ if (event == DEV_EVENT_TXDOWN)
+ fsm_newstate(fi, DEV_STATE_STARTWAIT_TX);
+ else
+ fsm_newstate(fi, DEV_STATE_STARTWAIT_RX);
+ break;
+ case DEV_STATE_STARTWAIT_RX:
+ if (event == DEV_EVENT_TXDOWN)
+ fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX);
+ break;
+ case DEV_STATE_STARTWAIT_TX:
+ if (event == DEV_EVENT_RXDOWN)
+ fsm_newstate(fi, DEV_STATE_STARTWAIT_RXTX);
+ break;
+ case DEV_STATE_STOPWAIT_RXTX:
+ if (event == DEV_EVENT_TXDOWN)
+ fsm_newstate(fi, DEV_STATE_STOPWAIT_RX);
+ else
+ fsm_newstate(fi, DEV_STATE_STOPWAIT_TX);
+ break;
+ case DEV_STATE_STOPWAIT_RX:
+ if (event == DEV_EVENT_RXDOWN)
+ fsm_newstate(fi, DEV_STATE_STOPPED);
+ break;
+ case DEV_STATE_STOPWAIT_TX:
+ if (event == DEV_EVENT_TXDOWN)
+ fsm_newstate(fi, DEV_STATE_STOPPED);
+ break;
+ }
+}
+
+static const fsm_node dev_fsm[] = {
+ { DEV_STATE_STOPPED, DEV_EVENT_START, dev_action_start },
+
+ { DEV_STATE_STOPWAIT_RXTX, DEV_EVENT_START, dev_action_start },
+ { DEV_STATE_STOPWAIT_RXTX, DEV_EVENT_RXDOWN, dev_action_chdown },
+ { DEV_STATE_STOPWAIT_RXTX, DEV_EVENT_TXDOWN, dev_action_chdown },
+
+ { DEV_STATE_STOPWAIT_RX, DEV_EVENT_START, dev_action_start },
+ { DEV_STATE_STOPWAIT_RX, DEV_EVENT_RXUP, dev_action_chup },
+ { DEV_STATE_STOPWAIT_RX, DEV_EVENT_TXUP, dev_action_chup },
+ { DEV_STATE_STOPWAIT_RX, DEV_EVENT_RXDOWN, dev_action_chdown },
+
+ { DEV_STATE_STOPWAIT_TX, DEV_EVENT_START, dev_action_start },
+ { DEV_STATE_STOPWAIT_TX, DEV_EVENT_RXUP, dev_action_chup },
+ { DEV_STATE_STOPWAIT_TX, DEV_EVENT_TXUP, dev_action_chup },
+ { DEV_STATE_STOPWAIT_TX, DEV_EVENT_TXDOWN, dev_action_chdown },
+
+ { DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_STOP, dev_action_stop },
+ { DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_RXUP, dev_action_chup },
+ { DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_TXUP, dev_action_chup },
+ { DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_RXDOWN, dev_action_chdown },
+ { DEV_STATE_STARTWAIT_RXTX, DEV_EVENT_TXDOWN, dev_action_chdown },
+
+ { DEV_STATE_STARTWAIT_TX, DEV_EVENT_STOP, dev_action_stop },
+ { DEV_STATE_STARTWAIT_TX, DEV_EVENT_RXUP, dev_action_chup },
+ { DEV_STATE_STARTWAIT_TX, DEV_EVENT_TXUP, dev_action_chup },
+ { DEV_STATE_STARTWAIT_TX, DEV_EVENT_RXDOWN, dev_action_chdown },
+
+ { DEV_STATE_STARTWAIT_RX, DEV_EVENT_STOP, dev_action_stop },
+ { DEV_STATE_STARTWAIT_RX, DEV_EVENT_RXUP, dev_action_chup },
+ { DEV_STATE_STARTWAIT_RX, DEV_EVENT_TXUP, dev_action_chup },
+ { DEV_STATE_STARTWAIT_RX, DEV_EVENT_TXDOWN, dev_action_chdown },
+
+ { DEV_STATE_RUNNING, DEV_EVENT_STOP, dev_action_stop },
+ { DEV_STATE_RUNNING, DEV_EVENT_RXDOWN, dev_action_chdown },
+ { DEV_STATE_RUNNING, DEV_EVENT_TXDOWN, dev_action_chdown },
+ { DEV_STATE_RUNNING, DEV_EVENT_TXUP, fsm_action_nop },
+ { DEV_STATE_RUNNING, DEV_EVENT_RXUP, fsm_action_nop },
+};
+
+static const int DEV_FSM_LEN = sizeof(dev_fsm) / sizeof(fsm_node);
+
+/**
+ * Transmit a packet.
+ * This is a helper function for ctc_tx().
+ *
+ * @param ch Channel to be used for sending.
+ * @param skb Pointer to struct sk_buff of packet to send.
+ * The linklevel header has already been set up
+ * by ctc_tx().
+ *
+ * @return 0 on success, -ERRNO on failure. (Never fails.)
+ */
+static int transmit_skb(channel *ch, struct sk_buff *skb) {
+ unsigned long saveflags;
+ ll_header header;
+ int rc = 0;
+
+ if (fsm_getstate(ch->fsm) != CH_STATE_TXIDLE) {
+ int l = skb->len + LL_HEADER_LENGTH;
+
+ spin_lock_irqsave(&ch->collect_lock, saveflags);
+ if (ch->collect_len + l > ch->max_bufsize - 2)
+ rc = -EBUSY;
+ else {
+ atomic_inc(&skb->users);
+ header.length = l;
+ header.type = skb->protocol;
+ header.unused = 0;
+ memcpy(skb_push(skb, LL_HEADER_LENGTH), &header,
+ LL_HEADER_LENGTH);
+ skb_queue_tail(&ch->collect_queue, skb);
+ ch->collect_len += l;
+ }
+ spin_unlock_irqrestore(&ch->collect_lock, saveflags);
+ } else {
+ __u16 block_len;
+
+ /**
+ * Protect skb against beeing free'd by upper
+ * layers.
+ */
+ atomic_inc(&skb->users);
+ ch->prof.txlen += skb->len;
+ header.length = skb->len + LL_HEADER_LENGTH;
+ header.type = skb->protocol;
+ header.unused = 0;
+ memcpy(skb_push(skb, LL_HEADER_LENGTH), &header,
+ LL_HEADER_LENGTH);
+ block_len = skb->len + 2;
+ *((__u16 *)skb_push(skb, 2)) = block_len;
+ skb_queue_tail(&ch->io_queue, skb);
+ ch->retry = 0;
+ fsm_newstate(ch->fsm, CH_STATE_TX);
+ fsm_addtimer(&ch->timer, CTC_TIMEOUT_5SEC, CH_EVENT_TIMER, ch);
+ ch->ccw[1].count = block_len;
+ set_normalized_cda(&ch->ccw[1], virt_to_phys(skb->data));
+ s390irq_spin_lock_irqsave(ch->irq, saveflags);
+ ch->prof.send_stamp = xtime;
+ rc = do_IO(ch->irq, &ch->ccw[0], (intparm_t)ch, 0xff, 0);
+ s390irq_spin_unlock_irqrestore(ch->irq, saveflags);
+ ch->prof.doios_single++;
+ if (rc != 0) {
+ fsm_deltimer(&ch->timer);
+ ccw_check_return_code(ch, rc);
+ skb_dequeue_tail(&ch->io_queue);
+ /**
+ * Remove our header. It gets added
+ * again on retransmit.
+ */
+ skb_pull(skb, LL_HEADER_LENGTH + 2);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Interface API for upper network layers
+ *****************************************************************************/
+
+/**
+ * Open an interface.
+ * Called from generic network layer when ifconfig up is run.
+ *
+ * @param dev Pointer to interface struct.
+ *
+ * @return 0 on success, -ERRNO on failure. (Never fails.)
+ */
+static int ctc_open(net_device *dev) {
+ MOD_INC_USE_COUNT;
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_START, dev);
+ return 0;
+}
+
+/**
+ * Close an interface.
+ * Called from generic network layer when ifconfig down is run.
+ *
+ * @param dev Pointer to interface struct.
+ *
+ * @return 0 on success, -ERRNO on failure. (Never fails.)
+ */
+static int ctc_close(net_device *dev) {
+ SET_DEVICE_START(dev, 0);
+ fsm_event(((ctc_priv *)dev->priv)->fsm, DEV_EVENT_STOP, dev);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/**
+ * Start transmission of a packet.
+ * Called from generic network device layer.
+ *
+ * @param skb Pointer to buffer containing the packet.
+ * @param dev Pointer to interface struct.
+ *
+ * @return 0 if packet consumed, !0 if packet rejected.
+ * Note: If we return !0, then the packet is free'd by
+ * the generic network layer.
+ */
+static int ctc_tx(struct sk_buff *skb, net_device *dev)
+{
+ int rc = 0;
+ ctc_priv *privptr = (ctc_priv *)dev->priv;
+
+ /**
+ * Some sanity checks ...
+ */
+ if (skb == NULL) {
+ printk(KERN_WARNING "%s: NULL sk_buff passed\n", dev->name);
+ privptr->stats.tx_dropped++;
+ return 0;
+ }
+ if (skb_headroom(skb) < (LL_HEADER_LENGTH + 2)) {
+ printk(KERN_WARNING "%s: Got sk_buff with head room < %ld bytes\n",
+ dev->name, LL_HEADER_LENGTH + 2);
+ dev_kfree_skb(skb);
+ privptr->stats.tx_dropped++;
+ return 0;
+ }
+
+ /**
+ * If channels are not running, try to restart them
+ * notify anybody about a link failure and throw
+ * away packet.
+ */
+ if (fsm_getstate(privptr->fsm) != DEV_STATE_RUNNING) {
+ fsm_event(privptr->fsm, DEV_EVENT_START, dev);
+ dst_link_failure(skb);
+ dev_kfree_skb(skb);
+ privptr->stats.tx_dropped++;
+ privptr->stats.tx_errors++;
+ privptr->stats.tx_carrier_errors++;
+ return 0;
+ }
+
+ if (ctc_test_and_set_busy(dev))
+ return -EBUSY;
+
+ dev->trans_start = jiffies;
+ if (transmit_skb(privptr->channel[WRITE], skb) != 0)
+ rc = 1;
+ ctc_clear_busy(dev);
+ return rc;
+}
+
+
+/**
+ * Sets MTU of an interface.
+ *
+ * @param dev Pointer to interface struct.
+ * @param new_mtu The new MTU to use for this interface.
+ *
+ * @return 0 on success, -EINVAL if MTU is out of valid range.
+ * (valid range is 576 .. 65527). If VM is on the
+ * remote side, maximum MTU is 32760, however this is
+ * <em>not</em> checked here.
+ */
+static int ctc_change_mtu(net_device *dev, int new_mtu) {
+ ctc_priv *privptr = (ctc_priv *)dev->priv;
+
+ if ((new_mtu < 576) || (new_mtu > 65527) ||
+ (new_mtu > (privptr->channel[READ]->max_bufsize -
+ LL_HEADER_LENGTH - 2)))
+ return -EINVAL;
+ dev->mtu = new_mtu;
+ dev->hard_header_len = LL_HEADER_LENGTH + 2;
+ return 0;
+}
+
+
+/**
+ * Returns interface statistics of a device.
+ *
+ * @param dev Pointer to interface struct.
+ *
+ * @return Pointer to stats struct of this interface.
+ */
+static struct net_device_stats *ctc_stats(net_device *dev) {
+ return &((ctc_priv *)dev->priv)->stats;
+}
+
+/**
+ * procfs related structures and routines
+ *****************************************************************************/
+
+static net_device *find_netdev_by_ino(unsigned long ino)
+{
+ channel *ch = channels;
+ net_device *dev = NULL;
+ ctc_priv *privptr;
+
+ while (ch) {
+ if (ch->netdev != dev) {
+ dev = ch->netdev;
+ privptr = (ctc_priv *)dev->priv;
+
+ if ((privptr->proc_ctrl_entry->low_ino == ino) ||
+ (privptr->proc_stat_entry->low_ino == ino))
+ return dev;
+ }
+ ch = ch->next;
+ }
+ return NULL;
+}
+
+#if LINUX_VERSION_CODE < 0x020363
+/**
+ * Lock the module, if someone changes into
+ * our proc directory.
+ */
+static void ctc_fill_inode(struct inode *inode, int fill)
+{
+ if (fill) {
+ MOD_INC_USE_COUNT;
+ } else
+ MOD_DEC_USE_COUNT;
+}
+#endif
+
+#define CTRL_BUFSIZE 40
+
+static int ctc_ctrl_open(struct inode *inode, struct file *file)
+{
+ file->private_data = kmalloc(CTRL_BUFSIZE, GFP_KERNEL);
+ if (file->private_data == NULL)
+ return -ENOMEM;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int ctc_ctrl_close(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static ssize_t ctc_ctrl_write(struct file *file, const char *buf, size_t count,
+ loff_t *off)
+{
+ unsigned int ino = ((struct inode *)file->f_dentry->d_inode)->i_ino;
+ net_device *dev;
+ ctc_priv *privptr;
+ char *e;
+ int bs1;
+ char tmp[40];
+
+ if (!(dev = find_netdev_by_ino(ino)))
+ return -ENODEV;
+ if (off != &file->f_pos)
+ return -ESPIPE;
+
+ privptr = (ctc_priv *)dev->priv;
+
+ if (count >= 39)
+ return -EINVAL;
+
+ if (copy_from_user(tmp, buf, count))
+ return -EFAULT;
+ tmp[count+1] = '\0';
+ bs1 = simple_strtoul(tmp, &e, 0);
+ if ((bs1 > CTC_BUFSIZE_LIMIT) ||
+ (bs1 < (dev->mtu - LL_HEADER_LENGTH - 2)) ||
+ (e && (!isspace(*e))))
+ return -EINVAL;
+ privptr->channel[READ]->max_bufsize =
+ privptr->channel[WRITE]->max_bufsize = bs1;
+
+ return count;
+}
+
+static ssize_t ctc_ctrl_read(struct file *file, char *buf, size_t count,
+ loff_t *off)
+{
+ unsigned int ino = ((struct inode *)file->f_dentry->d_inode)->i_ino;
+ char *sbuf = (char *)file->private_data;
+ net_device *dev;
+ ctc_priv *privptr;
+ ssize_t ret = 0;
+ char *p = sbuf;
+ int l;
+
+ if (!(dev = find_netdev_by_ino(ino)))
+ return -ENODEV;
+ if (off != &file->f_pos)
+ return -ESPIPE;
+
+ privptr = (ctc_priv *)dev->priv;
+
+ if (file->f_pos == 0)
+ sprintf(sbuf, "%d\n", privptr->channel[READ]->max_bufsize);
+
+ l = strlen(sbuf);
+ p = sbuf;
+ if (file->f_pos < l) {
+ p += file->f_pos;
+ l = strlen(p);
+ ret = (count > l) ? l : count;
+ if (copy_to_user(buf, p, ret))
+ return -EFAULT;
+ }
+ file->f_pos += ret;
+ return ret;
+}
+
+#define STATS_BUFSIZE 2048
+
+static int ctc_stat_open(struct inode *inode, struct file *file)
+{
+ file->private_data = kmalloc(STATS_BUFSIZE, GFP_KERNEL);
+ if (file->private_data == NULL)
+ return -ENOMEM;
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int ctc_stat_close(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static ssize_t ctc_stat_write(struct file *file, const char *buf, size_t count,
+ loff_t *off)
+{
+ unsigned int ino = ((struct inode *)file->f_dentry->d_inode)->i_ino;
+ net_device *dev;
+ ctc_priv *privptr;
+
+ if (!(dev = find_netdev_by_ino(ino)))
+ return -ENODEV;
+ privptr = (ctc_priv *)dev->priv;
+ privptr->channel[WRITE]->prof.maxmulti = 0;
+ privptr->channel[WRITE]->prof.maxcqueue = 0;
+ privptr->channel[WRITE]->prof.doios_single = 0;
+ privptr->channel[WRITE]->prof.doios_multi = 0;
+ privptr->channel[WRITE]->prof.txlen = 0;
+ privptr->channel[WRITE]->prof.tx_time = 0;
+ return count;
+}
+
+static ssize_t ctc_stat_read(struct file *file, char *buf, size_t count,
+ loff_t *off)
+{
+ unsigned int ino = ((struct inode *)file->f_dentry->d_inode)->i_ino;
+ char *sbuf = (char *)file->private_data;
+ net_device *dev;
+ ctc_priv *privptr;
+ ssize_t ret = 0;
+ char *p = sbuf;
+ int l;
+
+ if (!(dev = find_netdev_by_ino(ino)))
+ return -ENODEV;
+ if (off != &file->f_pos)
+ return -ESPIPE;
+
+ privptr = (ctc_priv *)dev->priv;
+
+ if (file->f_pos == 0) {
+ p += sprintf(p, "Device FSM state: %s\n",
+ fsm_getstate_str(privptr->fsm));
+ p += sprintf(p, "RX channel FSM state: %s\n",
+ fsm_getstate_str(privptr->channel[READ]->fsm));
+ p += sprintf(p, "TX channel FSM state: %s\n",
+ fsm_getstate_str(privptr->channel[WRITE]->fsm));
+ p += sprintf(p, "Max. TX buffer used: %ld\n",
+ privptr->channel[WRITE]->prof.maxmulti);
+ p += sprintf(p, "Max. chained CCWs: %ld\n",
+ privptr->channel[WRITE]->prof.maxcqueue);
+ p += sprintf(p, "TX single write ops: %ld\n",
+ privptr->channel[WRITE]->prof.doios_single);
+ p += sprintf(p, "TX multi write ops: %ld\n",
+ privptr->channel[WRITE]->prof.doios_multi);
+ p += sprintf(p, "Netto bytes written: %ld\n",
+ privptr->channel[WRITE]->prof.txlen);
+ p += sprintf(p, "Max. TX IO-time: %ld\n",
+ privptr->channel[WRITE]->prof.tx_time);
+ }
+ l = strlen(sbuf);
+ p = sbuf;
+ if (file->f_pos < l) {
+ p += file->f_pos;
+ l = strlen(p);
+ ret = (count > l) ? l : count;
+ if (copy_to_user(buf, p, ret))
+ return -EFAULT;
+ }
+ file->f_pos += ret;
+ return ret;
+}
+
+static struct file_operations ctc_stat_fops = {
+ read: ctc_stat_read,
+ write: ctc_stat_write,
+ open: ctc_stat_open,
+ release: ctc_stat_close,
+};
+
+static struct file_operations ctc_ctrl_fops = {
+ read: ctc_ctrl_read,
+ write: ctc_ctrl_write,
+ open: ctc_ctrl_open,
+ release: ctc_ctrl_close,
+};
+
+static struct inode_operations ctc_stat_iops = {
+#if LINUX_VERSION_CODE < 0x020363
+ default_file_ops: &ctc_stat_fops
+#endif
+};
+static struct inode_operations ctc_ctrl_iops = {
+#if LINUX_VERSION_CODE < 0x020363
+ default_file_ops: &ctc_ctrl_fops
+#endif
+};
+
+static struct proc_dir_entry stat_entry = {
+ 0, /* low_ino */
+ 10, /* namelen */
+ "statistics", /* name */
+ S_IFREG | S_IRUGO | S_IWUSR, /* mode */
+ 1, /* nlink */
+ 0, /* uid */
+ 0, /* gid */
+ 0, /* size */
+ &ctc_stat_iops /* ops */
+};
+
+static struct proc_dir_entry ctrl_entry = {
+ 0, /* low_ino */
+ 10, /* namelen */
+ "buffersize", /* name */
+ S_IFREG | S_IRUSR | S_IWUSR, /* mode */
+ 1, /* nlink */
+ 0, /* uid */
+ 0, /* gid */
+ 0, /* size */
+ &ctc_ctrl_iops /* ops */
+};
+
+#if LINUX_VERSION_CODE < 0x020363
+static struct proc_dir_entry ctc_dir = {
+ 0, /* low_ino */
+ 3, /* namelen */
+ "ctc", /* name */
+ S_IFDIR | S_IRUGO | S_IXUGO, /* mode */
+ 2, /* nlink */
+ 0, /* uid */
+ 0, /* gid */
+ 0, /* size */
+ 0, /* ops */
+ 0, /* get_info */
+ ctc_fill_inode /* fill_ino (for locking) */
+};
+
+static struct proc_dir_entry ctc_template =
+{
+ 0, /* low_ino */
+ 0, /* namelen */
+ "", /* name */
+ S_IFDIR | S_IRUGO | S_IXUGO, /* mode */
+ 2, /* nlink */
+ 0, /* uid */
+ 0, /* gid */
+ 0, /* size */
+ 0, /* ops */
+ 0, /* get_info */
+ ctc_fill_inode /* fill_ino (for locking) */
+};
+#else
+static struct proc_dir_entry *ctc_dir = NULL;
+static struct proc_dir_entry *ctc_template = NULL;
+#endif
+
+/**
+ * Create the driver's main directory /proc/net/ctc
+ */
+static void ctc_proc_create_main(void) {
+ /**
+ * If not registered, register main proc dir-entry now
+ */
+#if LINUX_VERSION_CODE > 0x020362
+ if (!ctc_dir)
+ ctc_dir = proc_mkdir("ctc", proc_net);
+#else
+ if (ctc_dir.low_ino == 0)
+ proc_net_register(&ctc_dir);
+#endif
+}
+
+#ifdef MODULE
+/**
+ * Destroy /proc/net/ctc
+ */
+static void ctc_proc_destroy_main(void) {
+#if LINUX_VERSION_CODE > 0x020362
+ remove_proc_entry("ctc", proc_net);
+#else
+ proc_net_unregister(ctc_dir.low_ino);
+#endif
+}
+#endif MODULE
+
+/**
+ * Create a device specific subdirectory in /proc/net/ctc/ with the
+ * same name like the device. In that directory, create 2 entries
+ * "statistics" and "buffersize".
+ *
+ * @param dev The device for which the subdirectory should be created.
+ *
+ */
+static void ctc_proc_create_sub(net_device *dev) {
+ ctc_priv *privptr = dev->priv;
+
+#if LINUX_VERSION_CODE > 0x020362
+ privptr->proc_dentry = proc_mkdir(dev->name, ctc_dir);
+ privptr->proc_stat_entry =
+ create_proc_entry("statistics",
+ S_IFREG | S_IRUSR | S_IWUSR,
+ privptr->proc_dentry);
+ privptr->proc_stat_entry->proc_fops = &ctc_stat_fops;
+ privptr->proc_stat_entry->proc_iops = &ctc_stat_iops;
+ privptr->proc_ctrl_entry =
+ create_proc_entry("buffersize",
+ S_IFREG | S_IRUSR | S_IWUSR,
+ privptr->proc_dentry);
+ privptr->proc_ctrl_entry->proc_fops = &ctc_ctrl_fops;
+ privptr->proc_ctrl_entry->proc_iops = &ctc_ctrl_iops;
+#else
+ privptr->proc_dentry->name = dev->name;
+ privptr->proc_dentry->namelen = strlen(dev->name);
+ proc_register(&ctc_dir, privptr->proc_dentry);
+ proc_register(privptr->proc_dentry, privptr->proc_stat_entry);
+ proc_register(privptr->proc_dentry, privptr->proc_ctrl_entry);
+#endif
+}
+
+#ifdef MODULE
+/**
+ * Destroy a device specific subdirectory.
+ *
+ * @param privptr Pointer to device private data.
+ */
+static void ctc_proc_destroy_sub(ctc_priv *privptr) {
+#if LINUX_VERSION_CODE > 0x020362
+ remove_proc_entry("statistics", privptr->proc_dentry);
+ remove_proc_entry("buffersize", privptr->proc_dentry);
+ remove_proc_entry(privptr->proc_dentry->name, ctc_dir);
+#else
+ proc_unregister(privptr->proc_dentry,
+ privptr->proc_stat_entry->low_ino);
+ proc_unregister(privptr->proc_dentry,
+ privptr->proc_ctrl_entry->low_ino);
+ proc_unregister(&ctc_dir,
+ privptr->proc_dentry->low_ino);
+#endif
+}
+#endif MODULE
+
+/**
+ * Setup related routines
+ *****************************************************************************/
+
+/**
+ * Parse a portion of the setup string describing a single device or option
+ * providing the following syntax:
+ *
+ * [Device/OptionName[:int1][:int2][:int3]]
+ *
+ *
+ * @param setup Pointer to a pointer to the remainder of the parameter
+ * string to be parsed. On return, the content of this
+ * pointer is updated to point to the first character after
+ * the parsed portion (e.g. possible start of next portion)
+ * NOTE: The string pointed to must be writeable, since a
+ * \0 is written for termination of the device/option name.
+ *
+ * @param dev_name Pointer to a pointer to the name of the device whose
+ * parameters are parsed. On return, this is set to the
+ * name of the device/option.
+ *
+ * @param ints Pointer to an array of integer parameters. On return,
+ * element 0 is set to the number of parameters found.
+ *
+ * @param maxip Maximum number of ints to parse.
+ * (ints[] must have size maxip+1)
+ *
+ * @return 0 if string "setup" was empty, !=0 otherwise
+ */
+static int parse_opts(char **setup, char **dev_name, int *ints, int maxip) {
+ char *cur = *setup;
+ int i = 1;
+ int rc = 0;
+ int in_name = 1;
+ int noauto = 0;
+
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: parse_opts(): *setup='%s', maxip=%d\n", *setup, maxip);
+#endif
+ if (*setup) {
+ *dev_name = *setup;
+
+ if (strncmp(cur, "ctc", 3) && strncmp(cur, "escon", 5) &&
+ strncmp(cur, "noauto", 6)) {
+ if ((*setup = strchr(cur, ':')))
+ *(*setup)++ = '\0';
+ printk(KERN_WARNING
+ "ctc: Invalid device name or option '%s'\n",
+ cur);
+ return 1;
+ }
+ switch (*cur) {
+ case 'c':
+ cur += 3;
+ break;
+ case 'e':
+ cur += 5;
+ break;
+ case 'n':
+ cur += 6;
+ *cur++ = '\0';
+ noauto = 1;
+ }
+ if (!noauto) {
+ while (cur &&
+ (*cur == '-' || isdigit(*cur)) &&
+ i <= maxip) {
+ if (in_name) {
+ cur++;
+ if (*cur == ':') {
+ *cur++ = '\0';
+ in_name = 0;
+ }
+ } else {
+ ints[i++] =
+ simple_strtoul(cur, NULL, 0);
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: %s: ints[%d]=%d\n",
+ __FUNCTION__,
+ i-1, ints[i-1]);
+#endif
+ if ((cur = strchr(cur, ':')) != NULL)
+ cur++;
+ }
+ }
+ }
+ ints[0] = i - 1;
+ *setup = cur;
+ if (cur && (*cur == ':'))
+ (*setup)++;
+ rc = 1;
+ }
+ return rc;
+}
+
+/**
+ *
+ * Allocate one param struct
+ *
+ * If the driver is loaded as a module this functions is called during
+ * module set up and we can allocate the struct by using kmalloc()
+ *
+ * If the driver is statically linked into the kernel this function is called
+ * when kmalloc() is not yet available so we must allocate from a static array
+ *
+ */
+#ifdef MODULE
+#define alloc_param() ((param *)kmalloc(sizeof(param), GFP_KERNEL));
+#else
+static param parms_array[MAX_STATIC_DEVICES];
+static param *next_param = parms_array;
+#define alloc_param() ((next_param<parms_array+MAX_STATIC_DEVICES)?next_param++:NULL)
+#endif MODULE
+
+/**
+ * Returns commandline parameter using device name as key.
+ *
+ * @param name Name of interface to get parameters from.
+ *
+ * @return Pointer to corresponting param struct, NULL if not found.
+ */
+static param *find_param(char *name) {
+ param *p = params;
+
+ while (p && strcmp(p->name, name))
+ p = p->next;
+ return p;
+}
+
+/**
+ * maximum number of integer parametes that may be specified
+ * for one device in the setup string
+ */
+#define CTC_MAX_INTPARMS 3
+
+/**
+ * Parse configuration options for all interfaces.
+ *
+ * This function is called from two possible locations:
+ * - If built as module, this function is called from init_module().
+ * - If built in monolithic kernel, this function is called from within
+ * init/main.c.
+ * Parsing is always done here.
+ *
+ * Valid parameters are:
+ *
+ *
+ * [NAME[:0xRRRR[:0xWWWW[:P]]]]
+ *
+ * where P is the channel protocol (always 0)
+ * 0xRRRR is the cu number for the read channel
+ * 0xWWWW is the cu number for the write channel
+ * NAME is either ctc0 ... ctcN for CTC/A
+ * or escon0 ... esconN for Escon.
+ * or noauto
+ * which switches off auto-detection of channels.
+ *
+ * @param setup The parameter string to parse. MUST be writeable!
+ * @param ints Pointer to an array of ints. Only for kernel 2.2,
+ * builtin (not module) version. With kernel 2.2,
+ * normally all integer-parameters, preceeding some
+ * configuration-string are pre-parsed in init/main.c
+ * and handed over here.
+ * To simplify 2.2/2.4 compatibility, by definition,
+ * our parameters always start with a string and ints
+ * is always unset and ignored.
+ */
+#ifdef MODULE
+ static void ctc_setup(char *setup)
+# define ctc_setup_return return
+#else MODULE
+# if LINUX_VERSION_CODE < 0x020300
+ __initfunc(void ctc_setup(char *setup, int *ints))
+# define ctc_setup_return return
+# define ints local_ints
+# else
+ static int __init ctc_setup(char *setup)
+# define ctc_setup_return return(1)
+# endif
+#endif MODULE
+{
+ int write_dev;
+ int read_dev;
+ int proto;
+ param *par;
+ char *dev_name;
+ int ints[CTC_MAX_INTPARMS+1];
+
+ while (parse_opts(&setup, &dev_name, ints, CTC_MAX_INTPARMS)) {
+ write_dev = -1;
+ read_dev = -1;
+ proto = CTC_PROTO_S390;
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: ctc_setup(): setup='%s' dev_name='%s',"
+ " ints[0]=%d)\n",
+ setup, dev_name, ints[0]);
+#endif DEBUG
+ if (dev_name == NULL) {
+ /**
+ * happens if device name is not specified in
+ * parameter line (cf. init/main.c:get_options()
+ */
+ printk(KERN_WARNING
+ "ctc: %s(): Device name not specified\n",
+ __FUNCTION__);
+ ctc_setup_return;
+ }
+
+#ifdef DEBUG
+ printk(KERN_DEBUG "name=´%s´ argc=%d\n", dev_name, ints[0]);
+#endif
+
+ if (strcmp(dev_name, "noauto") == 0) {
+ printk(KERN_INFO "ctc: autoprobing disabled\n");
+ ctc_no_auto = 1;
+ continue;
+ }
+
+ if (find_param(dev_name) != NULL) {
+ printk(KERN_WARNING
+ "ctc: Definition for device %s already set. "
+ "Ignoring second definition\n", dev_name);
+ continue;
+ }
+
+ switch (ints[0]) {
+ case 3: /* protocol type passed */
+ proto = ints[3];
+ if (proto > CTC_PROTO_MAX) {
+ printk(KERN_WARNING
+ "%s: wrong protocol type "
+ "passed\n", dev_name);
+ ctc_setup_return;
+ }
+ case 2: /* write channel passed */
+ write_dev = ints[2];
+ case 1: /* read channel passed */
+ read_dev = ints[1];
+ if (write_dev == -1)
+ write_dev = read_dev + 1;
+ break;
+ default:
+ printk(KERN_WARNING
+ "ctc: wrong number of parameter "
+ "passed (is: %d, expected: [1..3]\n",
+ ints[0]);
+ ctc_setup_return;
+ }
+ par = alloc_param();
+ if (!par) {
+#ifdef MODULE
+ printk(KERN_WARNING
+ "ctc: Couldn't allocate setup param block\n");
+#else
+ printk(KERN_WARNING
+ "ctc: Number of device definitions in "
+ " kernel commandline exceeds builtin limit "
+ " of %d devices.\n", MAX_STATIC_DEVICES);
+#endif
+ ctc_setup_return;
+ }
+ par->read_dev = read_dev;
+ par->write_dev = write_dev;
+ par->proto = proto;
+ strncpy(par->name, dev_name, MAX_PARAM_NAME_LEN);
+ par->next = params;
+ params = par;
+#ifdef DEBUG
+ printk(KERN_DEBUG "%s: protocol=%x read=%04x write=%04x\n",
+ dev_name, proto, read_dev, write_dev);
+#endif
+ }
+ ctc_setup_return;
+}
+
+#if LINUX_VERSION_CODE >= 0x020300
+__setup("ctc=", ctc_setup);
+#endif
+
+/**
+ *
+ * Setup an interface.
+ *
+ * Like ctc_setup(), ctc_probe() can be called from two different locations:
+ * - If built as module, it is called from within init_module().
+ * - If built in monolithic kernel, it is called from within generic network
+ * layer during initialization for every corresponding device, declared in
+ * drivers/net/Space.c
+ *
+ * @param dev Pointer to net_device to be initialized.
+ *
+ * @return 0 on success, !0 on failure.
+ */
+int ctc_probe(net_device *dev)
+{
+ int devno[2];
+ __u16 proto;
+ int rc;
+ int direction;
+ channel_type_t type;
+ ctc_priv *privptr;
+ param *par;
+
+ ctc_proc_create_main();
+
+ /**
+ * Scan for available channels only the first time,
+ * ctc_probe gets control.
+ */
+ if (channels == NULL)
+ channel_scan(1);
+
+ type = extract_channel_media(dev->name);
+ if (type == channel_type_unknown)
+ return -ENODEV;
+
+ par = find_param(dev->name);
+ if (par) {
+ devno[READ] = par->read_dev;
+ devno[WRITE] = par->write_dev;
+ proto = par->proto;
+ } else {
+ if (ctc_no_auto)
+ return -ENODEV;
+ else {
+ devno[READ] = -1;
+ devno[WRITE] = -1;
+ proto = CTC_PROTO_S390;
+ }
+ }
+
+ dev->priv = kmalloc(sizeof(ctc_priv)
+ + sizeof(ctc_template)
+ + sizeof(stat_entry)
+ + sizeof(ctrl_entry)
+ , GFP_KERNEL);
+ if (dev->priv == NULL)
+ return -ENOMEM;
+ memset(dev->priv, 0, sizeof(ctc_priv));
+ privptr = (ctc_priv *)dev->priv;
+ privptr->proc_dentry = (struct proc_dir_entry *)
+ (((char *)privptr) + sizeof(ctc_priv));
+ privptr->proc_stat_entry = (struct proc_dir_entry *)
+ (((char *)privptr) + sizeof(ctc_priv) +
+ sizeof(ctc_template));
+ privptr->proc_ctrl_entry = (struct proc_dir_entry *)
+ (((char *)privptr) + sizeof(ctc_priv) +
+ sizeof(ctc_template) + sizeof(stat_entry));
+ memcpy(privptr->proc_dentry, &ctc_template, sizeof(ctc_template));
+ memcpy(privptr->proc_stat_entry, &stat_entry, sizeof(stat_entry));
+ memcpy(privptr->proc_ctrl_entry, &ctrl_entry, sizeof(ctrl_entry));
+ privptr->fsm = init_fsm(dev->name, dev_state_names,
+ dev_event_names, NR_DEV_STATES, NR_DEV_EVENTS,
+ dev_fsm, DEV_FSM_LEN, GFP_KERNEL);
+ fsm_newstate(privptr->fsm, DEV_STATE_STOPPED);
+ if (privptr->fsm == NULL) {
+ kfree(privptr);
+ return -ENOMEM;
+ }
+ dev->mtu = CTC_BUFSIZE_DEFAULT - LL_HEADER_LENGTH - 2;
+ dev->hard_start_xmit = ctc_tx;
+ dev->open = ctc_open;
+ dev->stop = ctc_close;
+ dev->get_stats = ctc_stats;
+ dev->change_mtu = ctc_change_mtu;
+ dev->hard_header_len = LL_HEADER_LENGTH + 2;
+ dev->addr_len = 0;
+ dev->type = ARPHRD_SLIP;
+ dev->tx_queue_len = 100;
+ SET_DEVICE_START(dev, 1);
+ dev_init_buffers(dev);
+ dev->flags = IFF_POINTOPOINT | IFF_NOARP;
+ for (direction = READ; direction <= WRITE; direction++) {
+ if ((ctc_no_auto == 0) || (devno[direction] == -1))
+ privptr->channel[direction] =
+ channel_get_next(type, direction);
+ else
+ privptr->channel[direction] =
+ channel_get(type, devno[direction], direction);
+ if (privptr->channel[direction] == NULL) {
+ if (direction == WRITE) {
+ free_irq(privptr->channel[READ]->irq,
+ privptr->channel[READ]->devstat);
+ channel_free(privptr->channel[READ]);
+ }
+ kfree_fsm(privptr->fsm);
+ kfree(dev->priv);
+ dev->priv = NULL;
+ return -ENODEV;
+ }
+ privptr->channel[direction]->netdev = dev;
+ privptr->channel[direction]->protocol = proto;
+ privptr->channel[direction]->max_bufsize = CTC_BUFSIZE_DEFAULT;
+ rc = request_irq(privptr->channel[direction]->irq,
+ (void *)ctc_irq_handler, SA_INTERRUPT,
+ dev->name,
+ privptr->channel[direction]->devstat);
+ if (rc) {
+ printk(KERN_WARNING
+ "%s: requested irq %d is busy rc=%02x\n",
+ dev->name, privptr->channel[direction]->irq,
+ rc);
+ if (direction == WRITE) {
+ free_irq(privptr->channel[READ]->irq,
+ privptr->channel[READ]->devstat);
+ channel_free(privptr->channel[READ]);
+ }
+ channel_free(privptr->channel[direction]);
+ kfree_fsm(privptr->fsm);
+ kfree(dev->priv);
+ dev->priv = NULL;
+ return -EBUSY;
+ }
+ }
+
+ /**
+ * register subdir in /proc/net/ctc
+ */
+ ctc_proc_create_sub(dev);
+
+ print_banner();
+
+ printk(KERN_INFO
+ "%s: read: ch %04x (irq %04x), write: ch %04x (irq %04x) proto: %d\n",
+ dev->name, privptr->channel[READ]->devno,
+ privptr->channel[READ]->irq, privptr->channel[WRITE]->devno,
+ privptr->channel[WRITE]->irq, proto);
+
+ return 0;
+}
+
+/**
+ * Module related routines
+ *****************************************************************************/
+
+#ifdef MODULE
+/**
+ * Prepare to be unloaded. Free IRQ's and release all resources.
+ * This is called just before this module is unloaded. It is
+ * <em>not</em> called, if the usage count is !0, so we don't need to check
+ * for that.
+ */
+void cleanup_module(void) {
+ channel *c = channels;
+
+ /* we are called if all interfaces are down only, so no need
+ * to bother around with locking stuff
+ */
+ channels = NULL;
+ while (c) {
+ channel *oldc = c;
+ if (c->netdev && c->netdev->priv) {
+ net_device *nd = c->netdev;
+ ctc_priv *privptr = (ctc_priv *)nd->priv;
+
+ fsm_deltimer(&privptr->channel[READ]->timer);
+ fsm_deltimer(&privptr->channel[WRITE]->timer);
+ free_irq(privptr->channel[READ]->irq,
+ privptr->channel[READ]->devstat);
+ free_irq(privptr->channel[WRITE]->irq,
+ privptr->channel[WRITE]->devstat);
+ kfree_fsm(privptr->channel[READ]->fsm);
+ kfree_fsm(privptr->channel[WRITE]->fsm);
+ unregister_netdev(nd);
+ kfree_fsm(privptr->fsm);
+ privptr->channel[READ]->netdev = NULL;
+ privptr->channel[WRITE]->netdev = NULL;
+ ctc_proc_destroy_sub(privptr);
+ kfree(privptr);
+ kfree(nd);
+ if (c->netdev != NULL)
+ printk(KERN_EMERG
+ "ctc: PANIC: channel list corrupted\n");
+ }
+ c = c->next;
+ kfree(oldc->ccw);
+ kfree(oldc);
+ }
+ ctc_proc_destroy_main();
+ printk(KERN_INFO "CTC driver unloaded\n");
+}
+
+#define ctc_init init_module
+#endif MODULE
+
+/**
+ * Initialize module.
+ * This is called just after the module is loaded.
+ *
+ * @return 0 on success, !0 on error.
+ */
+int ctc_init(void) {
+ int cnt[2];
+ int itype;
+ int activated;
+ param *par;
+
+ print_banner();
+
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: init_module(): got string '%s'\n", setup);
+#endif
+
+#ifdef MODULE
+ ctc_setup(ctc);
+#endif
+ activated = 0;
+ par = params;
+ for (itype = 0; itype < 2; itype++) {
+ net_device *dev = NULL;
+ char *bname = (itype) ? "escon" : "ctc";
+ int nlen = strlen(bname);
+ cnt[itype] = 0;
+ do {
+ dev = kmalloc(sizeof(net_device)
+#if LINUX_VERSION_CODE < 0x020300
+ + 11 /* name + zero */
+#endif
+ , GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+ memset(dev, 0, sizeof(net_device));
+#if LINUX_VERSION_CODE < 0x020300
+ dev->name = (unsigned char *)dev + sizeof(net_device);
+#endif
+ if (par && par->name) {
+ char *p;
+ int n;
+
+ sprintf(dev->name, "%s", par->name);
+ par = par->next;
+ for (p = dev->name; p && *p; p++)
+ if (isdigit(*p))
+ break;
+ if (p && *p) {
+ n = simple_strtoul(p, NULL, 0);
+ if (n >= cnt[itype] &&
+ (!strncmp(par->name, bname, nlen)))
+ cnt[itype] = n + 1;
+ }
+ } else {
+ if (ctc_no_auto) {
+ itype = 3;
+ kfree(dev);
+ dev = NULL;
+ break;
+ }
+ sprintf(dev->name, "%s%d", bname,
+ (cnt[itype])++);
+ }
+#ifdef DEBUG
+ printk(KERN_DEBUG "ctc: %s(): probing for device %s\n",
+ __FUNCTION__, dev->name);
+#endif
+ if (ctc_probe(dev) == 0) {
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: %s(): probing succeeded\n",
+ __FUNCTION__);
+ printk(KERN_DEBUG
+ "ctc: %s(): registering device %s\n",
+ __FUNCTION__, dev->name);
+#endif
+ if (register_netdev(dev) != 0) {
+ ctc_priv *privptr =
+ (ctc_priv *)dev->priv;
+ printk(KERN_WARNING
+ "ctc: Couldn't register %s\n",
+ dev->name);
+ free_irq(privptr->channel[READ]->irq,
+ privptr->channel[READ]->devstat);
+ free_irq(privptr->channel[WRITE]->irq,
+ privptr->channel[WRITE]->devstat);
+ channel_free(privptr->channel[READ]);
+ channel_free(privptr->channel[WRITE]);
+ kfree(dev->priv);
+ kfree(dev);
+ } else {
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: %s(): register succeed\n",
+ __FUNCTION__);
+#endif
+ activated++;
+ }
+
+ } else {
+#ifdef DEBUG
+ printk(KERN_DEBUG
+ "ctc: %s(): probing failed\n",
+ __FUNCTION__);
+#endif
+ kfree(dev);
+ dev = NULL;
+ }
+ } while (dev);
+ }
+ if (!activated) {
+ printk(KERN_WARNING "ctc: No devices registered\n");
+ return -ENODEV;
+ }
+ return 0;
+}
+
+#ifndef MODULE
+#if (LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0))
+__initcall(ctc_init);
+#endif /* LINUX_VERSION_CODE */
+#endif /* MODULE */
+
+/* --- This is the END my friend --- */
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)